From b5baeb9cbe9e358395cfa1f0369f33e7f2d02174 Mon Sep 17 00:00:00 2001 From: Anton McClure Date: Mon, 17 Jun 2024 15:59:24 -0400 Subject: [PATCH 001/748] Fix encoding and input issues (#5) * Fix "decoding str is not supported" errors * Replace raw_input with input --------- Co-authored-by: Anton McClure --- bin/newlist | 4 ++-- configure | 4 ++-- configure.in | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/bin/newlist b/bin/newlist index a4ed575c..8628a8a1 100755 --- a/bin/newlist +++ b/bin/newlist @@ -164,7 +164,7 @@ def main(): if len(args) > 0: listname = args[0] else: - listname = raw_input('Enter the name of the list: ') + listname = input('Enter the name of the list: ') listname = listname.lower() if '@' in listname: @@ -184,7 +184,7 @@ def main(): if len(args) > 1: owner_mail = args[1] else: - owner_mail = raw_input( + owner_mail = input( C_('Enter the email of the person running the list: ')) if len(args) > 2: diff --git a/configure b/configure index c5b79d6c..e2b96a78 100755 --- a/configure +++ b/configure @@ -2709,7 +2709,7 @@ printf %s "checking Japanese codecs... " >&6; } cat > conftest.py <&6; } cat > conftest.py < conftest.py < conftest.py < Date: Thu, 10 Oct 2024 08:37:08 -0400 Subject: [PATCH 002/748] Various improvements and bugfixes (#7) * Fix "decoding str is not supported" errors * Replace raw_input with input * Add .gitignore for files that got generated during ./configure and make. * Fix TypeError that trings must be encoded before hashing. * Bump version number * Add workaround to removing `cmp` and remove python 2 imports. * Update gitignore * Updated during build * Fix missing apostrophe * Fixes to string interpolation There are still many strings that need fixed still. * Multitude of changes * Fix encoding error * Fix pickle.dump TypeError --------- Co-authored-by: Anton McClure --- .gitignore | 84 ++++ Mailman/Cgi/admin.py | 119 +++--- Mailman/Cgi/admindb.py | 40 +- Mailman/Cgi/confirm.py | 142 +++---- Mailman/Cgi/create.py | 32 +- Mailman/Cgi/edithtml.py | 12 +- Mailman/Cgi/listinfo.py | 18 +- Mailman/Cgi/options.py | 80 ++-- Mailman/Cgi/private.py | 6 +- Mailman/Cgi/rmlist.py | 18 +- Mailman/Cgi/roster.py | 4 +- Mailman/Cgi/subscribe.py | 34 +- Mailman/HTMLFormatter.py | 84 ++-- Mailman/Pending.py | 6 +- Mailman/Version.py | 2 +- bin/newlist | 22 +- messages/ar/LC_MESSAGES/mailman.po | 571 ++++++++++++++++++-------- messages/ast/LC_MESSAGES/mailman.po | 474 +++++++++++++++++---- messages/ca/LC_MESSAGES/mailman.po | 435 +++++++++++++++++--- messages/cs/LC_MESSAGES/mailman.po | 413 +++++++++++++++---- messages/da/LC_MESSAGES/mailman.po | 436 +++++++++++++++++--- messages/de/LC_MESSAGES/mailman.po | 475 ++++++++++++++++++--- messages/el/LC_MESSAGES/mailman.po | 435 +++++++++++++++++--- messages/eo/LC_MESSAGES/mailman.po | 342 +++++++++++---- messages/es/LC_MESSAGES/mailman.po | 485 ++++++++++++++++++---- messages/et/LC_MESSAGES/mailman.po | 444 +++++++++++++++++--- messages/eu/LC_MESSAGES/mailman.po | 436 +++++++++++++++++--- messages/fa/LC_MESSAGES/mailman.po | 378 +++++++++++++---- messages/fi/LC_MESSAGES/mailman.po | 440 +++++++++++++++++--- messages/fr/LC_MESSAGES/mailman.po | 443 +++++++++++++++++--- messages/gl/LC_MESSAGES/mailman.po | 464 ++++++++++++++++++--- messages/he/LC_MESSAGES/mailman.po | 445 +++++++++++++++++--- messages/hr/LC_MESSAGES/mailman.po | 442 +++++++++++++++----- messages/hu/LC_MESSAGES/mailman.po | 399 +++++++++++++++--- messages/ia/LC_MESSAGES/mailman.po | 448 ++++++++++++++++---- messages/it/LC_MESSAGES/mailman.po | 424 ++++++++++++++++--- messages/ja/LC_MESSAGES/mailman.po | 413 +++++++++++++++++-- messages/ko/LC_MESSAGES/mailman.po | 387 +++++++++++------ messages/lt/LC_MESSAGES/mailman.po | 307 ++++++++++---- messages/nl/LC_MESSAGES/mailman.po | 407 ++++++++++++++---- messages/no/LC_MESSAGES/mailman.po | 395 +++++++++++++++--- messages/pl/LC_MESSAGES/mailman.po | 426 ++++++++++++++++--- messages/pt/LC_MESSAGES/mailman.po | 355 +++++++++++++--- messages/pt_BR/LC_MESSAGES/mailman.po | 470 ++++++++++++++++++--- messages/ro/LC_MESSAGES/mailman.po | 409 +++++++++++++++--- messages/ru/LC_MESSAGES/mailman.po | 517 +++++++++++++++++++---- messages/sk/LC_MESSAGES/mailman.po | 421 ++++++++++++++++--- messages/sl/LC_MESSAGES/mailman.po | 420 ++++++++++++++++--- messages/sr/LC_MESSAGES/mailman.po | 321 +++++++++++---- messages/sv/LC_MESSAGES/mailman.po | 370 ++++++++++++++--- messages/tr/LC_MESSAGES/mailman.po | 443 +++++++++++++++----- messages/uk/LC_MESSAGES/mailman.po | 404 +++++++++++++++--- messages/vi/LC_MESSAGES/mailman.po | 429 ++++++++++++++++--- messages/zh_CN/LC_MESSAGES/mailman.po | 396 +++++++++++++++--- messages/zh_TW/LC_MESSAGES/mailman.po | 386 +++++++++++++---- 55 files changed, 14162 insertions(+), 3046 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..935691ca --- /dev/null +++ b/.gitignore @@ -0,0 +1,84 @@ +mailman.po~ +Mailman/Archiver/Makefile +Mailman/Bouncers/Makefile +Mailman/Cgi/Makefile +Mailman/Commands/Makefile +Mailman/Defaults.py +Mailman/Gui/Makefile +Mailman/Handlers/Makefile +Mailman/Logging/Makefile +Mailman/MTA/Makefile +Mailman/Makefile +Mailman/Queue/Makefile +Mailman/__pycache__/ +Mailman/mm_cfg.py.dist +Makefile +bin/Makefile +build/ +config.log +config.status +cron/Makefile +cron/crontab.in +messages/Makefile +messages/ar/LC_MESSAGES/mailman.mo +messages/ast/LC_MESSAGES/mailman.mo +messages/ca/LC_MESSAGES/mailman.mo +messages/cs/LC_MESSAGES/mailman.mo +messages/da/LC_MESSAGES/mailman.mo +messages/de/LC_MESSAGES/mailman.mo +messages/el/LC_MESSAGES/mailman.mo +messages/eo/LC_MESSAGES/mailman.mo +messages/es/LC_MESSAGES/mailman.mo +messages/et/LC_MESSAGES/mailman.mo +messages/eu/LC_MESSAGES/mailman.mo +messages/fa/LC_MESSAGES/mailman.mo +messages/fi/LC_MESSAGES/mailman.mo +messages/fr/LC_MESSAGES/mailman.mo +messages/gl/LC_MESSAGES/mailman.mo +messages/he/LC_MESSAGES/mailman.mo +messages/hr/LC_MESSAGES/mailman.mo +messages/hu/LC_MESSAGES/mailman.mo +messages/ia/LC_MESSAGES/mailman.mo +messages/it/LC_MESSAGES/mailman.mo +messages/ja/LC_MESSAGES/mailman.mo +messages/ko/LC_MESSAGES/mailman.mo +messages/lt/LC_MESSAGES/mailman.mo +messages/nl/LC_MESSAGES/mailman.mo +messages/no/LC_MESSAGES/mailman.mo +messages/pl/LC_MESSAGES/mailman.mo +messages/pt/LC_MESSAGES/mailman.mo +messages/pt_BR/LC_MESSAGES/mailman.mo +messages/ro/LC_MESSAGES/mailman.mo +messages/ru/LC_MESSAGES/mailman.mo +messages/sk/LC_MESSAGES/mailman.mo +messages/sl/LC_MESSAGES/mailman.mo +messages/sr/LC_MESSAGES/mailman.mo +messages/sv/LC_MESSAGES/mailman.mo +messages/tr/LC_MESSAGES/mailman.mo +messages/uk/LC_MESSAGES/mailman.mo +messages/vi/LC_MESSAGES/mailman.mo +messages/zh_CN/LC_MESSAGES/mailman.mo +messages/zh_TW/LC_MESSAGES/mailman.mo +misc/Makefile +misc/mailman +misc/paths.py +scripts/Makefile +src/Makefile +src/admin +src/admindb +src/common.o +src/confirm +src/create +src/edithtml +src/listinfo +src/mailman +src/options +src/private +src/rmlist +src/roster +src/subscribe +src/vsnprintf.o +templates/Makefile +tests/Makefile +tests/bounces/Makefile +tests/msgs/Makefile diff --git a/Mailman/Cgi/admin.py b/Mailman/Cgi/admin.py index 9b4cab84..27193888 100644 --- a/Mailman/Cgi/admin.py +++ b/Mailman/Cgi/admin.py @@ -17,11 +17,10 @@ """Process and produce the list-administration options forms.""" -# For Python 2.1.x compatibility -from __future__ import nested_scopes -from __future__ import print_function +def cmp(a, b): + return (a > b) - (a < b) -from past.builtins import cmp +#from future.builtins import cmp import sys import os import re @@ -74,7 +73,7 @@ def main(): safelistname = Utils.websafe(listname) # Send this with a 404 status. print('Status: 404 Not Found') - admin_overview(_('No such list %(safelistname)s')) + admin_overview(_(f'No such list {safelistname}')) syslog('error', 'admin: No such list "%s": %s\n', listname, e) return @@ -220,7 +219,7 @@ def sigterm_handler(signum, frame, mlist=mlist): # Additional sanity checks if not mlist.digestable and not mlist.nondigestable: doc.addError( - _('''You have turned off delivery of both digest and + _(f'''You have turned off delivery of both digest and non-digest messages. This is an incompatible state of affairs. You must turn on either digest delivery or non-digest delivery or your mailing list will basically be @@ -229,14 +228,14 @@ def sigterm_handler(signum, frame, mlist=mlist): dm = mlist.getDigestMemberKeys() if not mlist.digestable and dm: doc.addError( - _('''You have digest members, but digests are turned + _(f'''You have digest members, but digests are turned off. Those people will not receive mail. Affected member(s) %(dm)r.'''), tag=_('Warning: ')) rm = mlist.getRegularMemberKeys() if not mlist.nondigestable and rm: doc.addError( - _('''You have regular list members but non-digestified mail is + _(f'''You have regular list members but non-digestified mail is turned off. They will receive non-digestified mail until you fix this problem. Affected member(s) %(rm)r.'''), tag=_('Warning: ')) @@ -261,7 +260,7 @@ def admin_overview(msg=''): # This page should be displayed in the server's default language, which # should have already been set. hostname = Utils.get_domain() - legend = _('%(hostname)s mailing lists - Admin Links') + legend = _(f"{hostname} mailing lists - Admin Links") # The html `document' doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) @@ -303,14 +302,14 @@ def admin_overview(msg=''): if not advertised: welcome.extend([ greeting, - _('''

There currently are no publicly-advertised %(mailmanlink)s - mailing lists on %(hostname)s.'''), + _(f'''

There currently are no publicly-advertised {mailmanlink} + mailing lists on {hostname}.'''), ]) else: welcome.extend([ greeting, - _('''

Below is the collection of publicly-advertised - %(mailmanlink)s mailing lists on %(hostname)s. Click on a list + _(f'''

Below is the collection of publicly-advertised + {mailmanlink} mailing lists on {hostname}. Click on a list name to visit the configuration pages for that list.'''), ]) @@ -318,10 +317,10 @@ def admin_overview(msg=''): mailman_owner = Utils.get_site_email() extra = msg and _('right ') or '' welcome.extend([ - _('''To visit the administrators configuration page for an + _(f'''To visit the administrators configuration page for an unadvertised list, open a URL similar to this one, but with a '/' and - the %(extra)slist name appended. If you have the proper authority, - you can also create a new mailing list. + the {extra}list name appended. If you have the proper authority, + you can also create a new mailing list.

General list information can be found at '''), Link(Utils.ScriptURL('listinfo'), @@ -388,14 +387,14 @@ def option_help(mlist, varhelp): get_item_characteristics(item) # Set up the document realname = mlist.real_name - legend = _("""%(realname)s Mailing list Configuration Help -
%(varname)s Option""") + legend = _(f"""{realname} Mailing list Configuration Help +
{varname} Option""") header = Table(width='100%') header.AddRow([Center(Header(3, legend))]) header.AddCellInfo(header.GetCurrentRowIndex(), 0, colspan=2, bgcolor=mm_cfg.WEB_HEADER_COLOR) - doc.SetTitle(_("Mailman %(varname)s List Option Help")) + doc.SetTitle(_("Mailman {varname} List Option Help")) doc.AddItem(header) doc.AddItem("%s (%s): %s

" % (varname, category, description)) if elaboration: @@ -413,7 +412,7 @@ def option_help(mlist, varhelp): form.AddItem(Center(submit_button())) doc.AddItem(Center(form)) - doc.AddItem(_("""Warning: changing this option here + doc.AddItem(_(f"""Warning: changing this option here could cause other screens to be out-of-sync. Be sure to reload any other pages that are displaying this option for this mailing list. You can also """)) @@ -424,7 +423,7 @@ def option_help(mlist, varhelp): else: url = '%s/%s' % (adminurl, category) categoryname = mlist.GetConfigCategories()[category][0] - doc.AddItem(Link(url, _('return to the %(categoryname)s options page.'))) + doc.AddItem(Link(url, _(f'return to the {categoryname} options page.'))) doc.AddItem('') doc.AddItem(mlist.GetMailmanFooter()) print(doc.Format()) @@ -439,9 +438,9 @@ def show_results(mlist, doc, category, subcat, cgidata): # Set up the document's headers realname = mlist.real_name - doc.SetTitle(_('%(realname)s Administration (%(label)s)')) + doc.SetTitle(_(f'{realname} Administration ({label})')) doc.AddItem(Center(Header(2, _( - '%(realname)s mailing list administration
%(label)s Section')))) + '{realname} mailing list administration
{label} Section')))) doc.AddItem('


') # Now we need to craft the form that will be submitted, which will contain # all the variable settings, etc. This is a bit of a kludge because we @@ -536,7 +535,7 @@ def show_results(mlist, doc, category, subcat, cgidata): form.AddItem(linktable) form.AddItem('
') form.AddItem( - _('''Make your changes in the following section, then submit them + _(f'''Make your changes in the following section, then submit them using the Submit Your Changes button below.''') + '

') @@ -560,7 +559,7 @@ def show_results(mlist, doc, category, subcat, cgidata): # Add a blank separator row table.AddRow([' ', ' ']) # Add a section to set the moderation bit for all members - table.AddRow([_("""

  • Set everyone's moderation bit, including + table.AddRow([_(f"""
  • Set everyone's moderation bit, including those members not currently visible""")]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) table.AddRow([RadioButtonArray('allmodbit_val', @@ -661,7 +660,7 @@ def get_item_characteristics(record): elif len(record) == 6: varname, kind, params, dependancies, descr, elaboration = record else: - raise ValueError('Badly formed options entry:\n %(record)s') + raise ValueError(f'Badly formed options entry:\n {record}') return varname, kind, params, dependancies, descr, elaboration @@ -863,16 +862,16 @@ def get_item_gui_description(mlist, category, subcat, else: varhelp = '/?VARHELP=%s/%s' % (category, varname) if descr == elaboration: - linktext = _('
    (Edit %(varname)s)') + linktext = _(f'
    (Edit {varname})') else: - linktext = _('
    (Details for %(varname)s)') + linktext = _(f'
    (Details for {varname})') link = Link(mlist.GetScriptURL('admin') + varhelp, linktext).Format() text = Label('%s %s' % (descr, link)).Format() else: text = Label(descr).Format() if varname[0] == '_': - text += Label(_('''
    Note: + text += Label(_(f'''
    Note: setting this value performs an immediate action but does not modify permanent state.''')).Format() return text @@ -923,7 +922,7 @@ def membership_options(mlist, subcat, cgidata, doc, form): link = Link('https://docs.python.org/2/library/re.html' '#regular-expression-syntax', _('(help)')).Format() - table.AddRow([Label(_('Find member %(link)s:')), + table.AddRow([Label(_(f'Find member {link}:')), TextBox('findmember', value=cgidata.getfirst('findmember', '')), SubmitButton('findmember_btn', _('Search...'))]) @@ -1007,9 +1006,9 @@ def membership_options(mlist, subcat, cgidata, doc, form): if bucket: membercnt = len(members) usertable.AddRow([Center(Italic(_( - '%(allcnt)s members total, %(membercnt)s shown')))]) + '{allcnt} members total, {membercnt} shown')))]) else: - usertable.AddRow([Center(Italic(_('%(allcnt)s members total')))]) + usertable.AddRow([Center(Italic(_(f'{allcnt} members total')))]) usertable.AddCellInfo(usertable.GetCurrentRowIndex(), usertable.GetCurrentCellIndex(), colspan=OPTCOLUMNS, @@ -1140,11 +1139,11 @@ def membership_options(mlist, subcat, cgidata, doc, form): legend.AddItem( _('unsub -- Click on this to unsubscribe the member.')) legend.AddItem( - _("""mod -- The user's personal moderation flag. If this is + _(f"""mod -- The user's personal moderation flag. If this is set, postings from them will be moderated, otherwise they will be approved.""")) legend.AddItem( - _("""hide -- Is the member's address concealed on + _(f"""hide -- Is the member's address concealed on the list of subscribers?""")) legend.AddItem(_( """nomail -- Is delivery to the member disabled? If so, an @@ -1161,19 +1160,19 @@ def membership_options(mlist, subcat, cgidata, doc, form): in older versions of Mailman. """)) legend.AddItem( - _('''ack -- Does the member get acknowledgements of their + _(f'''ack -- Does the member get acknowledgements of their posts?''')) legend.AddItem( - _('''not metoo -- Does the member want to avoid copies of their + _(f'''not metoo -- Does the member want to avoid copies of their own postings?''')) legend.AddItem( - _('''nodupes -- Does the member want to avoid duplicates of the + _(f'''nodupes -- Does the member want to avoid duplicates of the same message?''')) legend.AddItem( - _('''digest -- Does the member get messages in digests? + _(f'''digest -- Does the member get messages in digests? (otherwise, individual messages)''')) legend.AddItem( - _('''plain -- If getting digests, does the member get plain + _(f'''plain -- If getting digests, does the member get plain text digests? (otherwise, MIME)''')) legend.AddItem(_("language -- Language preferred by the user")) addlegend = '' @@ -1200,7 +1199,7 @@ def membership_options(mlist, subcat, cgidata, doc, form): if chunkindex is not None: buttons = [] url = adminurl + '/members?%sletter=%s&' % (addlegend, bucket) - footer = _('''

    To view more members, click on the appropriate + footer = _(f'''

    To view more members, click on the appropriate range listed below:''') chunkmembers = buckets[bucket] last = len(chunkmembers) @@ -1214,7 +1213,7 @@ def membership_options(mlist, subcat, cgidata, doc, form): thisurl = thisurl.encode( Utils.GetCharSet(mlist.preferred_language), errors='ignore') - link = Link(thisurl, _('from %(start)s to %(end)s')) + link = Link(thisurl, _(f'from {start} to {end}')) buttons.append(link) buttons = UnorderedList(*buttons) container.AddItem(footer + buttons.Format() + '

    ') @@ -1263,7 +1262,7 @@ def mass_subscribe(mlist, container): container.AddItem(Center(table)) # Invitation text table.AddRow([' ', ' ']) - table.AddRow([Italic(_("""Below, enter additional text to be added to the + table.AddRow([Italic(_(f"""Below, enter additional text to be added to the top of your invitation or the subscription notification. Include at least one blank line at the end..."""))]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) @@ -1309,7 +1308,7 @@ def address_change(mlist, container): # ADDRESS CHANGE GREY = mm_cfg.WEB_ADMINITEM_COLOR table = Table(width='90%') - table.AddRow([Italic(_("""To change a list member's address, enter the + table.AddRow([Italic(_(f"""To change a list member's address, enter the member's current and new addresses below. Use the check boxes to send notice of the change to the old and/or new address(es)."""))]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=3) @@ -1357,7 +1356,7 @@ def password_inputs(mlist): table.AddRow([Center(Header(2, _('Change list ownership passwords')))]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, bgcolor=mm_cfg.WEB_HEADER_COLOR) - table.AddRow([_("""\ + table.AddRow([_(f"""\ The list administrators are the people who have ultimate control over all parameters of this mailing list. They are able to change any list configuration variable available through these administration web pages. @@ -1371,7 +1370,7 @@ def password_inputs(mlist):

    In order to split the list ownership duties into administrators and moderators, you must set a separate moderator password in the fields below, and also provide the email addresses of the list moderators in the -general options section.""")]) +general options section.""")]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) # Set up the admin password table on the left atable = Table(border=0, cellspacing=3, cellpadding=4, @@ -1389,7 +1388,7 @@ def password_inputs(mlist): PasswordBox('confirmmodpw', size=20)]) # Add these tables to the overall password table table.AddRow([atable, mtable]) - table.AddRow([_("""\ + table.AddRow([_(f"""\ In addition to the above passwords you may specify a password for pre-approving posts to the list. Either of the above two passwords can be used in an Approved: header or first body line pseudo-header to @@ -1522,7 +1521,7 @@ def safeint(formvar, defaultval=None): (safeentry, _('Hostile address (illegal characters)'))) except Errors.MembershipIsBanned as pattern: subscribe_errors.append( - (safeentry, _('Banned address (matched %(pattern)s)'))) + (safeentry, _(f'Banned address (matched {pattern})'))) else: member = Utils.uncanonstr(formataddr((fullname, address))) subscribe_success.append(Utils.websafe(member)) @@ -1595,12 +1594,12 @@ def safeint(formvar, defaultval=None): elif mlist.isMember(change_to): # ApprovedChangeMemberAddress will just delete the old address # and we don't want that here. - msg = _('%(schange_to)s is already a list member.') + msg = _(f'{schange_to} is already a list member.') else: try: Utils.ValidateEmail(change_to) except (Errors.MMBadEmailError, Errors.MMHostileAddress): - msg = _('%(schange_to)s is not a valid email address.') + msg = _(f'{schange_to} is not a valid email address.') if msg: doc.AddItem(Header(3, msg)) doc.AddItem('

    ') @@ -1608,24 +1607,24 @@ def safeint(formvar, defaultval=None): try: mlist.ApprovedChangeMemberAddress(change_from, change_to, False) except Errors.NotAMemberError: - msg = _('%(schange_from)s is not a member') + msg = _(f'{schange_from} is not a member') except Errors.MMAlreadyAMember: - msg = _('%(schange_to)s is already a member') + msg = _(f'{schange_to} is already a member') except Errors.MembershipIsBanned as pat: spat = Utils.websafe(str(pat)) - msg = _('%(schange_to)s matches banned pattern %(spat)s') + msg = _(f'{schange_to} matches banned pattern {spat}') else: - msg = _('Address %(schange_from)s changed to %(schange_to)s') + msg = _(f'Address {schange_from} changed to {schange_to}') success = True doc.AddItem(Header(3, msg)) lang = mlist.getMemberLanguage(change_to) otrans = i18n.get_translation() i18n.set_language(lang) list_name = mlist.getListAddress() - text = Utils.wrap(_("""The member address %(change_from)s on the -%(list_name)s list has been changed to %(change_to)s. + text = Utils.wrap(_(f"""The member address {change_from} on the +{list_name} list has been changed to {change_to}. """)) - subject = _('%(list_name)s address change notice.') + subject = _(f'{list_name} address change notice.') i18n.set_translation(otrans) if success and cgidata.getfirst('notice_old', '') == 'yes': # Send notice to old address. @@ -1636,7 +1635,7 @@ def safeint(formvar, defaultval=None): lang=lang ) msg.send(mlist) - doc.AddItem(Header(3, _('Notification sent to %(schange_from)s.'))) + doc.AddItem(Header(3, _(f'Notification sent to {schange_from}.'))) if success and cgidata.getfirst('notice_new', '') == 'yes': # Send notice to new address. msg = Message.UserNotification(change_to, @@ -1646,7 +1645,7 @@ def safeint(formvar, defaultval=None): lang=lang ) msg.send(mlist) - doc.AddItem(Header(3, _('Notification sent to %(schange_to)s.'))) + doc.AddItem(Header(3, _(f'Notification sent to {schange_to}.'))) doc.AddItem('

    ') # sync operation @@ -1695,7 +1694,7 @@ def clean_input(x): (safeentry, _('Hostile address (illegal characters)'))) except Errors.MembershipIsBanned as pattern: subscribe_errors.append( - (safeentry, _('Banned address (matched %(pattern)s)'))) + (safeentry, _(f'Banned address (matched {pattern})'))) else: member = Utils.uncanonstr(formataddr((fullname, address))) subscribe_success.append(Utils.websafe(member)) @@ -1765,7 +1764,7 @@ def clean_input(x): errors.append((user, _('Not subscribed'))) continue if not mlist.isMember(user): - doc.addError(_('Ignoring changes to deleted member: %(user)s'), + doc.addError(_(f'Ignoring changes to deleted member: {user}'), tag=_('Warning: ')) continue value = '%s_digest' % quser in cgidata diff --git a/Mailman/Cgi/admindb.py b/Mailman/Cgi/admindb.py index c0083696..bf394f3f 100644 --- a/Mailman/Cgi/admindb.py +++ b/Mailman/Cgi/admindb.py @@ -121,7 +121,7 @@ def main(): safelistname = Utils.websafe(listname) # Send this with a 404 status. print('Status: 404 Not Found') - handle_no_list(_('No such list %(safelistname)s')) + handle_no_list(_(f'No such list {safelistname}')) syslog('error', 'admindb: No such list "%s": %s\n', listname, e) return @@ -236,10 +236,10 @@ def sigterm_handler(signum, frame, mlist=mlist): if not list(cgidata.keys()) or 'admlogin' in cgidata: # If this is not a form submission (i.e. there are no keys in the # form) or it's a login, then we don't need to do much special. - doc.SetTitle(_('%(realname)s Administrative Database')) + doc.SetTitle(_(f'{realname} Administrative Database')) elif not details: # This is a form submission - doc.SetTitle(_('%(realname)s Administrative Database Results')) + doc.SetTitle(_(f'{realname} Administrative Database Results')) if csrf_checked: process_form(mlist, doc, cgidata) else: @@ -249,7 +249,7 @@ def sigterm_handler(signum, frame, mlist=mlist): # are no pending requests, but be sure to save the results! admindburl = mlist.GetScriptURL('admindb', absolute=1) if not mlist.NumRequestsPending(): - title = _('%(realname)s Administrative Database') + title = _(f'{realname} Administrative Database') doc.SetTitle(title) doc.AddItem(Header(2, title)) doc.AddItem(_('There are no pending requests.')) @@ -299,7 +299,7 @@ def sigterm_handler(signum, frame, mlist=mlist): addform = 1 if sender: esender = Utils.websafe(sender) - d['description'] = _("all of %(esender)s's held messages.") + d['description'] = _("all of {esender}'s held messages.") doc.AddItem(Utils.maketext('admindbpreamble.html', d, raw=1, mlist=mlist)) show_sender_requests(mlist, form, sender) @@ -363,7 +363,7 @@ def handle_no_list(msg=''): doc.AddItem(msg) url = Utils.ScriptURL('admin', absolute=1) link = Link(url, _('list of available mailing lists.')).Format() - doc.AddItem(_('You must specify a list name. Here is the %(link)s')) + doc.AddItem(_(f'You must specify a list name. Here is the {link}')) doc.AddItem('


    ') doc.AddItem(MailmanLogo()) print(doc.Format()) @@ -410,7 +410,7 @@ def show_pending_subs(mlist, form): checked=0).Format() if addr not in mlist.ban_list: radio += ('
    ' + '') # While the address may be a unicode, it must be ascii @@ -419,7 +419,7 @@ def show_pending_subs(mlist, form): Utils.websafe(fullname), displaytime), radio, - TextBox('comment-%d' % id, size=40) + TextBox(f'comment-%d' % id, size=40) ]) num += 1 if num > 0: @@ -472,7 +472,7 @@ def show_pending_unsubs(mlist, form): mm_cfg.REJECT, mm_cfg.DISCARD), checked=0), - TextBox('comment-%d' % id, size=45) + TextBox(f'comment-%d' % id, size=45) ]) if num > 0: form.AddItem('
    ') @@ -569,7 +569,7 @@ def show_helds_overview(mlist, form, ssort=SSENDER): '' ]) left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) @@ -585,14 +585,14 @@ def show_helds_overview(mlist, form, ssort=SSENDER): '']) left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) right = Table(border=0) right.AddRow([ - _("""Click on the message number to view the individual + _(f"""Click on the message number to view the individual message, or you can """) + - Link(senderurl, _('view all messages from %(esender)s')).Format() + Link(senderurl, _(f'view all messages from {esender}')).Format() ]) right.AddCellInfo(right.GetCurrentRowIndex(), 0, colspan=2) right.AddRow([' ', ' ']) @@ -684,7 +684,7 @@ def show_post_requests(mlist, id, info, total, count, form): # Header shown on each held posting (including count of total) msg = _('Posting Held for Approval') if total != 1: - msg += _(' (%(count)d of %(total)d)') + msg += _(f' (%(count)d of %(total)d)') form.AddItem(Center(Header(2, msg))) # We need to get the headers and part of the textual body of the message # being held. The best way to do this is to use the email Parser to get @@ -695,7 +695,7 @@ def show_post_requests(mlist, id, info, total, count, form): except IOError as e: if e.errno != errno.ENOENT: raise - form.AddItem(_('Message with id #%(id)d was lost.')) + form.AddItem(_(f'Message with id #%(id)d was lost.')) form.AddItem('

    ') # BAW: kludge to remove id from requests.db. try: @@ -704,7 +704,7 @@ def show_post_requests(mlist, id, info, total, count, form): pass return except email.errors.MessageParseError: - form.AddItem(_('Message with id #%(id)d is corrupted.')) + form.AddItem(_(f'Message with id #%(id)d is corrupted.')) # BAW: Should we really delete this, or shuttle it off for site admin # to look more closely at? form.AddItem('

    ') @@ -769,16 +769,16 @@ def show_post_requests(mlist, id, info, total, count, form): t.AddCellInfo(t.GetCurrentRowIndex(), col-1, align='right') t.AddRow([' ', '' ]) t.AddRow([' ', '' + - TextBox('forward-addr-%d' % id, size=47, + TextBox(f'forward-addr-%d' % id, size=47, value=mlist.GetOwnerEmail()).Format() ]) notice = msgdata.get('rejection_notice', _('[No explanation given]')) @@ -974,7 +974,7 @@ def process_form(mlist, doc, cgidata): if banaddrs: for addr, patt in banaddrs: addr = Utils.websafe(addr) - doc.AddItem(_('%(addr)s is banned (matched: %(patt)s)') + '
    ') + doc.AddItem(_(f'{addr} is banned (matched: {patt})') + '
    ') if badaddrs: for addr in badaddrs: addr = Utils.websafe(addr) diff --git a/Mailman/Cgi/confirm.py b/Mailman/Cgi/confirm.py index 0be9542b..66ad2b61 100644 --- a/Mailman/Cgi/confirm.py +++ b/Mailman/Cgi/confirm.py @@ -54,7 +54,7 @@ def main(): except Errors.MMListError as e: # Avoid cross-site scripting attacks safelistname = Utils.websafe(listname) - bad_confirmation(doc, _('No such list %(safelistname)s')) + bad_confirmation(doc, _('No such list {safelistname}')) doc.AddItem(MailmanLogo()) # Send this with a 404 status. print('Status: 404 Not Found') @@ -100,14 +100,14 @@ def main(): confirmurl = mlist.GetScriptURL('confirm', absolute=1) # Avoid cross-site scripting attacks safecookie = Utils.websafe(cookie) - badconfirmstr = _('''Invalid confirmation string: - %(safecookie)s. + badconfirmstr = _(f'''Invalid confirmation string: + {safecookie}.

    Note that confirmation strings expire approximately - %(days)s days after the initial request. They also expire if the + {days} days after the initial request. They also expire if the request has already been handled in some way. If your confirmation has expired, please try to re-submit your request. - Otherwise, re-enter your confirmation + Otherwise, re-enter your confirmation string.''') content = mlist.pend_confirm(cookie, expunge=False) @@ -134,7 +134,7 @@ def main(): else: unsubscription_prompt(mlist, doc, cookie, *content[1:]) except Errors.NotAMemberError: - doc.addError(_("""The address requesting unsubscription is not + doc.addError(_(f"""The address requesting unsubscription is not a member of the mailing list. Perhaps you have already been unsubscribed, e.g. by the list administrator?""")) # Expunge this record from the pending database. @@ -150,7 +150,7 @@ def main(): try: addrchange_prompt(mlist, doc, cookie, *content[1:]) except Errors.NotAMemberError: - doc.addError(_("""The address requesting to be changed has + doc.addError(_(f"""The address requesting to be changed has been subsequently unsubscribed. This request has been cancelled.""")) # Expunge this record from the pending database. @@ -170,7 +170,7 @@ def main(): else: reenable_prompt(mlist, doc, cookie, *content[1:]) else: - bad_confirmation(doc, _('System error, bad content: %(content)s')) + bad_confirmation(doc, _('System error, bad content: {content}')) except Errors.MMBadConfirmation: bad_confirmation(doc, badconfirmstr) @@ -212,7 +212,7 @@ def ask_for_cookie(mlist, doc, extra=''): table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) # Add cookie entry box - table.AddRow([_("""Please enter the confirmation string + table.AddRow([_(f"""Please enter the confirmation string (i.e. cookie) that you received in your email message, in the box below. Then hit the Submit button to proceed to the next confirmation step.""")]) @@ -251,8 +251,8 @@ def subscription_prompt(mlist, doc, cookie, userdesc): # We do things this way so we don't have to reformat this paragraph, which # would mess up translations. If you modify this text for other reasons, # please refill the paragraph, and clean up the logic. - result = _("""Your confirmation is required in order to complete the - subscription request to the mailing list %(listname)s. Your + result = _(f"""Your confirmation is required in order to complete the + subscription request to the mailing list {listname}. Your subscription settings are shown below; make any necessary changes and hit Subscribe to complete the confirmation process. Once you've confirmed your subscription request, you will be shown your account @@ -267,8 +267,8 @@ def subscription_prompt(mlist, doc, cookie, userdesc): if (mlist.subscribe_policy in (2, 3) and not getattr(userdesc, 'invitation', False)): # Confirmation is required - result = _("""Your confirmation is required in order to continue with - the subscription request to the mailing list %(listname)s. + result = _(f"""Your confirmation is required in order to continue with + the subscription request to the mailing list {listname}. Your subscription settings are shown below; make any necessary changes and hit Subscribe to list ... to complete the confirmation process. Once you've confirmed your subscription request, the @@ -309,7 +309,7 @@ def subscription_prompt(mlist, doc, cookie, userdesc): table.AddRow([Hidden('cookie', cookie)]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) table.AddRow([ - Label(SubmitButton('submit', _('Subscribe to list %(listname)s'))), + Label(SubmitButton('submit', _('Subscribe to list {listname}'))), SubmitButton('cancel', _('Cancel my subscription request')) ]) form.AddItem(table) @@ -378,25 +378,25 @@ def sigterm_handler(signum, frame, mlist=mlist): title = _('Awaiting moderator approval') doc.SetTitle(title) doc.AddItem(Header(3, Bold(FontAttr(title, size='+2')))) - doc.AddItem(_("""\ + doc.AddItem(_(f"""\ You have successfully confirmed your subscription request to the - mailing list %(listname)s, however final approval is required from + mailing list {listname}, however final approval is required from the list moderator before you will be subscribed. Your request has been forwarded to the list moderator, and you will be notified of the moderator's decision.""")) except (Errors.NotAMemberError, TypeError): - bad_confirmation(doc, _('''Invalid confirmation string. It is + bad_confirmation(doc, _(f'''Invalid confirmation string. It is possible that you are attempting to confirm a request for an address that has already been unsubscribed.''')) except Errors.MMAlreadyAMember: doc.addError(_("You are already a member of this mailing list!")) except Errors.MembershipIsBanned: owneraddr = mlist.GetOwnerEmail() - doc.addError(_("""You are currently banned from subscribing to + doc.addError(_(f"""You are currently banned from subscribing to this list. If you think this restriction is erroneous, please - contact the list owners at %(owneraddr)s.""")) + contact the list owners at {owneraddr}.""")) except Errors.HostileSubscriptionError: - doc.addError(_("""\ + doc.addError(_(f"""\ You were not invited to this mailing list. The invitation has been discarded, and both list administrators have been alerted.""")) @@ -410,14 +410,14 @@ def sigterm_handler(signum, frame, mlist=mlist): optionsurl = mlist.GetOptionsURL(addr, absolute=1) doc.SetTitle(title) doc.AddItem(Header(3, Bold(FontAttr(title, size='+2')))) - doc.AddItem(_('''\ + doc.AddItem(_(f'''\ You have successfully confirmed your subscription request for - "%(addr)s" to the %(listname)s mailing list. A separate + "{addr}" to the {listname} mailing list. A separate confirmation message will be sent to your email address, along with your password, and other useful information and links.

    You can now - proceed to your membership login + proceed to your membership login page.''')) mlist.Save() finally: @@ -451,7 +451,7 @@ def sigterm_handler(signum, frame, mlist=mlist): op, addr = mlist.ProcessConfirmation(cookie) # See comment about TypeError in subscription_confirm. except (Errors.NotAMemberError, TypeError): - bad_confirmation(doc, _('''Invalid confirmation string. It is + bad_confirmation(doc, _(f'''Invalid confirmation string. It is possible that you are attempting to confirm a request for an address that has already been unsubscribed.''')) else: @@ -461,9 +461,9 @@ def sigterm_handler(signum, frame, mlist=mlist): listinfourl = mlist.GetScriptURL('listinfo', absolute=1) doc.SetTitle(title) doc.AddItem(Header(3, Bold(FontAttr(title, size='+2')))) - doc.AddItem(_("""\ - You have successfully unsubscribed from the %(listname)s mailing - list. You can now visit the list's main + doc.AddItem(_(f"""\ + You have successfully unsubscribed from the {listname} mailing + list. You can now visit the list's main information page.""")) mlist.Save() finally: @@ -490,12 +490,12 @@ def unsubscription_prompt(mlist, doc, cookie, addr): fullname = _('Not available') else: fullname = Utils.websafe(Utils.uncanonstr(fullname, lang)) - table.AddRow([_("""Your confirmation is required in order to complete the - unsubscription request from the mailing list %(listname)s. You + table.AddRow([_(f"""Your confirmation is required in order to complete the + unsubscription request from the mailing list {listname}. You are currently subscribed with -

    • Real name: %(fullname)s -
    • Email address: %(addr)s +
      • Real name: {fullname} +
      • Email address: {addr}
      Hit the Unsubscribe button below to complete the confirmation @@ -541,19 +541,19 @@ def sigterm_handler(signum, frame, mlist=mlist): op, oldaddr, newaddr = mlist.ProcessConfirmation(cookie) # See comment about TypeError in subscription_confirm. except (Errors.NotAMemberError, TypeError): - bad_confirmation(doc, _('''Invalid confirmation string. It is + bad_confirmation(doc, _(f'''Invalid confirmation string. It is possible that you are attempting to confirm a request for an address that has already been unsubscribed.''')) except Errors.MembershipIsBanned: owneraddr = mlist.GetOwnerEmail() realname = mlist.real_name - doc.addError(_("""%(newaddr)s is banned from subscribing to the - %(realname)s list. If you think this restriction is erroneous, - please contact the list owners at %(owneraddr)s.""")) + doc.addError(_(f"""{newaddr} is banned from subscribing to the + {realname} list. If you think this restriction is erroneous, + please contact the list owners at {owneraddr}.""")) except Errors.MMAlreadyAMember: realname = mlist.real_name - bad_confirmation(doc, _("""%(newaddr)s is already a member of - the %(realname)s list. It is possible that you are attempting + bad_confirmation(doc, _(f"""{newaddr} is already a member of + the {realname} list. It is possible that you are attempting to confirm a request for an address that has already been subscribed.""")) else: @@ -563,10 +563,10 @@ def sigterm_handler(signum, frame, mlist=mlist): optionsurl = mlist.GetOptionsURL(newaddr, absolute=1) doc.SetTitle(title) doc.AddItem(Header(3, Bold(FontAttr(title, size='+2')))) - doc.AddItem(_("""\ - You have successfully changed your address on the %(listname)s - mailing list from %(oldaddr)s to %(newaddr)s. You - can now proceed to your membership + doc.AddItem(_(f"""\ + You have successfully changed your address on the {listname} + mailing list from {oldaddr} to {newaddr}. You + can now proceed to your membership login page.""")) mlist.Save() finally: @@ -597,17 +597,17 @@ def addrchange_prompt(mlist, doc, cookie, oldaddr, newaddr, globally): globallys = _('globally') else: globallys = '' - table.AddRow([_("""Your confirmation is required in order to complete the - change of address request for the mailing list %(listname)s. You + table.AddRow([_(f"""Your confirmation is required in order to complete the + change of address request for the mailing list {listname}. You are currently subscribed with -
      • Real name: %(fullname)s -
      • Old email address: %(oldaddr)s +
        • Real name: {fullname} +
        • Old email address: {oldaddr}
        - and you have requested to %(globallys)s change your email address to + and you have requested to {globallys} change your email address to -
        • New email address: %(newaddr)s +
          • New email address: {newaddr}
          Hit the Change address button below to complete the confirmation @@ -635,7 +635,7 @@ def heldmsg_cancel(mlist, doc, cookie): bgcolor=mm_cfg.WEB_HEADER_COLOR) # Expunge this record from the pending database. expunge(mlist, cookie) - table.AddRow([_('''Okay, the list moderator will still have the + table.AddRow([_(f'''Okay, the list moderator will still have the opportunity to approve or reject this message.''')]) doc.AddItem(table) @@ -666,8 +666,8 @@ def sigterm_handler(signum, frame, mlist=mlist): _('Sender discarded message via web.')) # See comment about TypeError in subscription_confirm. except (Errors.LostHeldMessage, KeyError, TypeError): - bad_confirmation(doc, _('''The held message with the Subject: - header %(subject)s could not be found. The most likely + bad_confirmation(doc, _(f'''The held message with the Subject: + header {subject} could not be found. The most likely reason for this is that the list moderator has already approved or rejected the message. You were not able to cancel it in time.''')) @@ -677,10 +677,10 @@ def sigterm_handler(signum, frame, mlist=mlist): title = _('Posted message canceled') doc.SetTitle(title) doc.AddItem(Header(3, Bold(FontAttr(title, size='+2')))) - doc.AddItem(_('''\ + doc.AddItem(_(f'''\ You have successfully canceled the posting of your message with - the Subject: header %(subject)s to the mailing list - %(listname)s.''')) + the Subject: header {subject} to the mailing list + {listname}.''')) mlist.Save() finally: mlist.Unlock() @@ -713,7 +713,7 @@ def sigterm_handler(signum, frame, mlist=mlist): mlist.Unlock() if data is None: - bad_confirmation(doc, _("""The held message you were referred to has + bad_confirmation(doc, _(f"""The held message you were referred to has already been handled by the list administrator.""")) return @@ -727,12 +727,12 @@ def sigterm_handler(signum, frame, mlist=mlist): subject = Utils.websafe(Utils.oneline(msgsubject, Utils.GetCharSet(lang))) reason = Utils.websafe(_(givenreason)) listname = mlist.real_name - table.AddRow([_('''Your confirmation is required in order to cancel the - posting of your message to the mailing list %(listname)s: + table.AddRow([_(f'''Your confirmation is required in order to cancel the + posting of your message to the mailing list {listname}: -
          • Sender: %(sender)s -
          • Subject: %(subject)s -
          • Reason: %(reason)s +
            • Sender: {sender} +
            • Subject: {subject} +
            • Reason: {reason}
            Hit the Cancel posting button to discard the posting. @@ -755,7 +755,7 @@ def reenable_cancel(mlist, doc, cookie): # Don't actually discard this cookie, since the user may decide to # re-enable their membership at a future time, and we may be sending out # future notifications with this cookie value. - doc.AddItem(_("""You have canceled the re-enabling of your membership. If + doc.AddItem(_(f"""You have canceled the re-enabling of your membership. If we continue to receive bounces from your address, it could be deleted from this mailing list.""")) @@ -780,7 +780,7 @@ def sigterm_handler(signum, frame, mlist=mlist): op, addr = mlist.ProcessConfirmation(cookie) # See comment about TypeError in subscription_confirm. except (Errors.NotAMemberError, TypeError): - bad_confirmation(doc, _('''Invalid confirmation string. It is + bad_confirmation(doc, _(f'''Invalid confirmation string. It is possible that you are attempting to confirm a request for an address that has already been unsubscribed.''')) else: @@ -790,10 +790,10 @@ def sigterm_handler(signum, frame, mlist=mlist): optionsurl = mlist.GetOptionsURL(addr, absolute=1) doc.SetTitle(title) doc.AddItem(Header(3, Bold(FontAttr(title, size='+2')))) - doc.AddItem(_("""\ + doc.AddItem(_(f"""\ You have successfully re-enabled your membership in the - %(listname)s mailing list. You can now visit your member options page. + {listname} mailing list. You can now visit your member options page. """)) mlist.Save() finally: @@ -819,9 +819,9 @@ def reenable_prompt(mlist, doc, cookie, list, member): if not info: listinfourl = mlist.GetScriptURL('listinfo', absolute=1) # They've already be unsubscribed - table.AddRow([_("""We're sorry, but you have already been unsubscribed + table.AddRow([_(f"""We're sorry, but you have already been unsubscribed from this mailing list. To re-subscribe, please visit the - list information page.""")]) + list information page.""")]) return date = time.strftime('%A, %B %d, %Y', @@ -838,16 +838,16 @@ def reenable_prompt(mlist, doc, cookie, list, member): else: username = Utils.websafe(Utils.uncanonstr(username, lang)) - table.AddRow([_("""Your membership in the %(realname)s mailing list is + table.AddRow([_(f"""Your membership in the {realname} mailing list is currently disabled due to excessive bounces. Your confirmation is required in order to re-enable delivery to your address. We have the following information on file: -
            • Member address: %(member)s -
            • Member name: %(username)s -
            • Last bounce received on: %(date)s +
              • Member address: {member} +
              • Member name: {username} +
              • Last bounce received on: {date}
              • Approximate number of days before you are permanently removed - from this list: %(daysleft)s + from this list: {daysleft}
              Hit the Re-enable membership button to resume receiving postings diff --git a/Mailman/Cgi/create.py b/Mailman/Cgi/create.py index 6aea412b..3fd73cfd 100644 --- a/Mailman/Cgi/create.py +++ b/Mailman/Cgi/create.py @@ -113,14 +113,14 @@ def process_request(doc, cgidata): safelistname = Utils.websafe(listname) if '@' in listname: request_creation(doc, cgidata, - _('List name must not include "@": %(safelistname)s')) + _('List name must not include "@": {safelistname}')) return if Utils.list_exists(listname): # BAW: should we tell them the list already exists? This could be # used to mine/guess the existance of non-advertised lists. Then # again, that can be done in other ways already, so oh well. request_creation(doc, cgidata, - _('List already exists: %(safelistname)s')) + _('List already exists: {safelistname}')) return if not listname: request_creation(doc, cgidata, @@ -135,7 +135,7 @@ def process_request(doc, cgidata): if password or confirm: request_creation( doc, cgidata, - _('''Leave the initial password (and confirmation) fields + _(f'''Leave the initial password (and confirmation) fields blank if you want Mailman to autogenerate the list passwords.''')) return @@ -180,7 +180,7 @@ def process_request(doc, cgidata): hostname not in mm_cfg.VIRTUAL_HOSTS: safehostname = Utils.websafe(hostname) request_creation(doc, cgidata, - _('Unknown virtual host: %(safehostname)s')) + _('Unknown virtual host: {safehostname}')) return emailhost = mm_cfg.VIRTUAL_HOSTS.get(hostname, mm_cfg.DEFAULT_EMAIL_HOST) # We've got all the data we need, so go ahead and try to create the list @@ -216,12 +216,12 @@ def sigterm_handler(signum, frame, mlist=mlist): else: s = Utils.websafe(owner) request_creation(doc, cgidata, - _('Bad owner email address: %(s)s')) + _('Bad owner email address: {s}')) return except Errors.MMListAlreadyExistsError: # MAS: List already exists so we don't need to websafe it. request_creation(doc, cgidata, - _('List already exists: %(listname)s')) + _('List already exists: {listname}')) return except Errors.BadListNameError as e: if e.args: @@ -229,12 +229,12 @@ def sigterm_handler(signum, frame, mlist=mlist): else: s = Utils.websafe(listname) request_creation(doc, cgidata, - _('Illegal list name: %(s)s')) + _('Illegal list name: {s}')) return except Errors.MMListError: request_creation( doc, cgidata, - _('''Some unknown error occurred while creating the list. + _(f'''Some unknown error occurred while creating the list. Please contact the site administrator for assistance.''')) return @@ -271,7 +271,7 @@ def sigterm_handler(signum, frame, mlist=mlist): }, mlist=mlist) msg = Message.UserNotification( owner, siteowner, - _('Your new mailing list: %(listname)s'), + _('Your new mailing list: {listname}'), text, mlist.preferred_language) msg.send(mlist) @@ -286,9 +286,9 @@ def sigterm_handler(signum, frame, mlist=mlist): table.AddRow([Center(Bold(FontAttr(title, size='+1')))]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, bgcolor=mm_cfg.WEB_HEADER_COLOR) - table.AddRow([_('''You have successfully created the mailing list - %(listname)s and notification has been sent to the list owner - %(owner)s. You can now:''')]) + table.AddRow([_(f'''You have successfully created the mailing list + {listname} and notification has been sent to the list owner + {owner}. You can now:''')]) ullist = UnorderedList() ullist.AddItem(Link(listinfo_url, _("Visit the list's info page"))) ullist.AddItem(Link(admin_url, _("Visit the list's admin page"))) @@ -310,7 +310,7 @@ def request_creation(doc, cgidata=dummy, errmsg=None): # What virtual domain are we using? hostname = Utils.get_domain() # Set up the document - title = _('Create a %(hostname)s Mailing List') + title = _(f"Create a {hostname} Mailing List") doc.SetTitle(title) table = Table(border=0, width='100%') table.AddRow([Center(Bold(FontAttr(title, size='+1')))]) @@ -321,7 +321,7 @@ def request_creation(doc, cgidata=dummy, errmsg=None): table.AddRow([Header(3, Bold( FontAttr(_('Error: '), color='#ff0000', size='+2').Format() + Italic(errmsg).Format()))]) - table.AddRow([_("""You can create a new mailing list by entering the + table.AddRow([_(f"""You can create a new mailing list by entering the relevant information into the form below. The name of the mailing list will be used as the primary address for posting messages to the list, so it should be lowercased. You will not be able to change this once the @@ -401,7 +401,7 @@ def request_creation(doc, cgidata=dummy, errmsg=None): ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, colspan=2) ftable.AddRow([ - Label(_("""Should new members be quarantined before they + Label(_(f"""Should new members be quarantined before they are allowed to post unmoderated to this list? Answer Yes to hold new member postings for moderator approval by default.""")), RadioButtonArray('moderate', (_('No'), _('Yes')), @@ -433,7 +433,7 @@ def request_creation(doc, cgidata=dummy, errmsg=None): ftable.AddRow([Label(_( '''Initial list of supported languages.

              Note that if you do not select at least one initial language, the list will use the server - default language of %(deflang)s''')), + default language of {deflang}''')), CheckBoxArray('langs', [_(Utils.GetLanguageDescr(L)) for L in langs], checked=checked, diff --git a/Mailman/Cgi/edithtml.py b/Mailman/Cgi/edithtml.py index 18a9634e..b1f5601c 100644 --- a/Mailman/Cgi/edithtml.py +++ b/Mailman/Cgi/edithtml.py @@ -84,7 +84,7 @@ def _(s): except Errors.MMListError as e: # Avoid cross-site scripting attacks safelistname = Utils.websafe(listname) - doc.AddItem(Header(2, _('No such list %(safelistname)s'))) + doc.AddItem(Header(2, _('No such list {safelistname}'))) # Send this with a 404 status. print('Status: 404 Not Found') print(doc.Format()) @@ -154,19 +154,19 @@ def _(s): if template == template_name: template_info = _(info) doc.SetTitle(_( - '%(realname)s -- Edit html for %(template_info)s')) + '{realname} -- Edit html for {template_info}')) break else: # Avoid cross-site scripting attacks safetemplatename = Utils.websafe(template_name) doc.SetTitle(_('Edit HTML : Error')) - doc.AddItem(Header(2, _("%(safetemplatename)s: Invalid template"))) + doc.AddItem(Header(2, _("{safetemplatename}: Invalid template"))) doc.AddItem(mlist.GetMailmanFooter()) print(doc.Format()) return else: - doc.SetTitle(_('%(realname)s -- HTML Page Editing')) - doc.AddItem(Header(1, _('%(realname)s -- HTML Page Editing'))) + doc.SetTitle(_('{realname} -- HTML Page Editing')) + doc.AddItem(Header(1, _('{realname} -- HTML Page Editing'))) doc.AddItem(Header(2, _('Select page to edit:'))) template_list = UnorderedList() for (template, info) in template_data: @@ -243,7 +243,7 @@ def ChangeHTML(mlist, cgi_info, template_name, doc, lang=None): code = cgi_info['html_code'].value if Utils.suspiciousHTML(code): doc.AddItem(Header(3, - _("""The page you saved contains suspicious HTML that could + _(f"""The page you saved contains suspicious HTML that could potentially expose your users to cross-site scripting attacks. This change has therefore been rejected. If you still want to make these changes, you must have shell access to your Mailman server. diff --git a/Mailman/Cgi/listinfo.py b/Mailman/Cgi/listinfo.py index dc878d66..9df2e6cc 100644 --- a/Mailman/Cgi/listinfo.py +++ b/Mailman/Cgi/listinfo.py @@ -54,7 +54,7 @@ def main(): safelistname = Utils.websafe(listname) # Send this with a 404 status. print('Status: 404 Not Found') - listinfo_overview(_('No such list %(safelistname)s')) + listinfo_overview(_(f'No such list {safelistname}')) syslog('error', 'listinfo: No such list "%s": %s', listname, e) return @@ -88,7 +88,7 @@ def listinfo_overview(msg=''): doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) - legend = (hostname + "s Mailing Lists") + legend = (hostname + "'s Mailing Lists") doc.SetTitle(legend) table = Table(border=0, width="100%") @@ -126,12 +126,12 @@ def listinfo_overview(msg=''): mailmanlink = Link(mm_cfg.MAILMAN_URL, _('Mailman')).Format() if not advertised: welcome.extend( - _('''

              There currently are no publicly-advertised - %(mailmanlink)s mailing lists on %(hostname)s.''')) + _(f'''

              There currently are no publicly-advertised + {mailmanlink} mailing lists on {hostname}.''')) else: welcome.append( - _('''

              Below is a listing of all the public mailing lists on - %(hostname)s. Click on a list name to get more information about + _(f'''

              Below is a listing of all the public mailing lists on + {hostname}. Click on a list name to get more information about the list, or to subscribe, unsubscribe, and change the preferences on your subscription.''')) @@ -139,13 +139,13 @@ def listinfo_overview(msg=''): adj = msg and _('right') or '' siteowner = Utils.get_site_email() welcome.extend( - (_(''' To visit the general information page for an unadvertised list, - open a URL similar to this one, but with a '/' and the %(adj)s + (_(f''' To visit the general information page for an unadvertised list, + open a URL similar to this one, but with a '/' and the {adj} list name appended.

              List administrators, you can visit '''), Link(Utils.ScriptURL('admin'), _('the list admin overview page')), - _(''' to find the management interface for your list. + _(f''' to find the management interface for your list.

              If you are having trouble using the lists, please contact '''), Link('mailto:' + siteowner, siteowner), '.

              ')) diff --git a/Mailman/Cgi/options.py b/Mailman/Cgi/options.py index 85ce4178..1c509f97 100644 --- a/Mailman/Cgi/options.py +++ b/Mailman/Cgi/options.py @@ -62,7 +62,7 @@ def main(): title = _('CGI script error') doc.SetTitle(title) doc.AddItem(Header(2, title)) - doc.addError(_('Invalid request method: %(method)s')) + doc.addError(_('Invalid request method: {method}')) doc.AddItem('


              ') doc.AddItem(MailmanLogo()) print('Status: 405 Method Not Allowed') @@ -92,7 +92,7 @@ def main(): title = _('CGI script error') doc.SetTitle(title) doc.AddItem(Header(2, title)) - doc.addError(_('No such list %(safelistname)s')) + doc.addError(_('No such list {safelistname}')) doc.AddItem('
              ') doc.AddItem(MailmanLogo()) # Send this with a 404 status. @@ -165,7 +165,7 @@ def main(): # using public rosters, otherwise, we'll leak membership information. if not mlist.isMember(user): if mlist.private_roster == 0: - doc.addError(_('No such member: %(safeuser)s.')) + doc.addError(_('No such member: {safeuser}.')) loginpage(mlist, doc, None, language) print(doc.Format()) return @@ -203,7 +203,7 @@ def main(): # Are we processing an unsubscription request from the login screen? msgc = _('If you are a list member, a confirmation email has been sent.') msgb = _('You already have a subscription pending confirmation') - msga = _("""If you are a list member, your unsubscription request has been + msga = _(f"""If you are a list member, your unsubscription request has been forwarded to the list administrator for approval.""") if 'login-unsub' in cgidata: # Because they can't supply a password for unsubscribing, we'll need @@ -233,7 +233,7 @@ def main(): # Not a member if mlist.private_roster == 0: # Public rosters - doc.addError(_('No such member: %(safeuser)s.')) + doc.addError(_('No such member: {safeuser}.')) else: syslog('mischief', 'Unsub attempt of non-member w/ private rosters: %s', @@ -247,7 +247,7 @@ def main(): return # Are we processing a password reminder from the login screen? - msg = _("""If you are a list member, + msg = _(f"""If you are a list member, your password has been emailed to you.""") if 'login-remind' in cgidata: if mlist.isMember(user): @@ -257,7 +257,7 @@ def main(): # Not a member if mlist.private_roster == 0: # Public rosters - doc.addError(_('No such member: %(safeuser)s.')) + doc.addError(_('No such member: {safeuser}.')) else: syslog('mischief', 'Reminder attempt of non-member w/ private rosters: %s', @@ -367,16 +367,16 @@ def main(): if 'othersubs' in cgidata: # Only the user or site administrator can view all subscriptions. if not is_user_or_siteadmin: - doc.addError(_("""The list administrator may not view the other + doc.addError(_(f"""The list administrator may not view the other subscriptions for this user."""), _('Note: ')) options_page(mlist, doc, user, cpuser, userlang) print(doc.Format()) return hostname = mlist.host_name - title = _('List subscriptions for %(safeuser)s on %(hostname)s') + title = _(f'List subscriptions for {safeuser} on {hostname}') doc.SetTitle(title) doc.AddItem(Header(2, title)) - doc.AddItem(_('''Click on a link to visit your options page for the + doc.AddItem(_(f'''Click on a link to visit your options page for the requested mailing list.''')) # Troll through all the mailing lists that match host_name and see if @@ -414,7 +414,7 @@ def main(): # list admin is /not/ allowed to make global changes. globally = cgidata.getfirst('changeaddr-globally') if globally and not is_user_or_siteadmin: - doc.addError(_("""The list administrator may not change the names + doc.addError(_(f"""The list administrator may not change the names or addresses for this user's other subscriptions. However, the subscription for this mailing list has been changed."""), _('Note: ')) @@ -454,16 +454,16 @@ def main(): safenewaddr = Utils.websafe(newaddr) if globally: listname = mlist.real_name - msg += _("""\ -The new address you requested %(newaddr)s is already a member of the -%(listname)s mailing list, however you have also requested a global change of + msg += _(f"""\ +The new address you requested {newaddr} is already a member of the +{listname} mailing list, however you have also requested a global change of address. Upon confirmation, any other mailing list containing the address -%(safeuser)s will be changed. """) +{safeuser} will be changed. """) # Don't return else: options_page( mlist, doc, user, cpuser, userlang, - _('The new address is already a member: %(newaddr)s')) + _('The new address is already a member: {newaddr}')) print(doc.Format()) return set_address = 1 @@ -483,7 +483,7 @@ def sigterm_handler(signum, frame, mlist=mlist): if cpuser is None: cpuser = user # Register the pending change after the list is locked - msg += _('A confirmation message has been sent to %(newaddr)s. ') + msg += _('A confirmation message has been sent to {newaddr}. ') mlist.Lock() try: try: @@ -496,12 +496,12 @@ def sigterm_handler(signum, frame, mlist=mlist): except Errors.MMHostileAddress: msg = _('Illegal email address provided') except Errors.MMAlreadyAMember: - msg = _('%(newaddr)s is already a member of the list.') + msg = _('{newaddr} is already a member of the list.') except Errors.MembershipIsBanned: owneraddr = mlist.GetOwnerEmail() - msg = _("""%(newaddr)s is banned from this list. If you + msg = _(f"""{newaddr} is banned from this list. If you think this restriction is erroneous, please contact - the list owners at %(owneraddr)s.""") + the list owners at {owneraddr}.""") if set_membername: mlist.Lock() @@ -520,7 +520,7 @@ def sigterm_handler(signum, frame, mlist=mlist): # Is this list admin and is list admin allowed to change passwords. if not (is_user_or_siteadmin or mm_cfg.OWNERS_CAN_CHANGE_MEMBER_PASSWORDS): - doc.addError(_("""The list administrator may not change the + doc.addError(_(f"""The list administrator may not change the password for a user.""")) options_page(mlist, doc, user, cpuser, userlang) print(doc.Format()) @@ -542,7 +542,7 @@ def sigterm_handler(signum, frame, mlist=mlist): # the list admin is /not/ allowed to change passwords globally. pw_globally = cgidata.getfirst('pw-globally') if pw_globally and not is_user_or_siteadmin: - doc.addError(_("""The list administrator may not change the + doc.addError(_(f"""The list administrator may not change the password for this user's other subscriptions. However, the password for this mailing list has been changed."""), _('Note: ')) @@ -568,7 +568,7 @@ def sigterm_handler(signum, frame, mlist=mlist): if not cgidata.getfirst('unsubconfirm'): options_page( mlist, doc, user, cpuser, userlang, - _('''You must confirm your unsubscription request by turning + _(f'''You must confirm your unsubscription request by turning on the checkbox below the Unsubscribe button. You have not been unsubscribed!''')) print(doc.Format()) @@ -613,16 +613,16 @@ def sigterm_handler(signum, frame, mlist=mlist): doc.SetTitle(title) doc.AddItem(Header(2, title)) if needapproval: - doc.AddItem(_("""Your unsubscription request has been received and + doc.AddItem(_(f"""Your unsubscription request has been received and forwarded on to the list moderators for approval. You will receive notification once the list moderators have made their decision.""")) else: - doc.AddItem(_("""You have been successfully unsubscribed from the - mailing list %(fqdn_listname)s. If you were receiving digest + doc.AddItem(_(f"""You have been successfully unsubscribed from the + mailing list {fqdn_listname}. If you were receiving digest deliveries you may get one more digest. If you have any questions about your unsubscription, please contact the list owners at - %(owneraddr)s.""")) + {owneraddr}.""")) doc.AddItem(mlist.GetMailmanFooter()) print(doc.Format()) return @@ -767,7 +767,7 @@ def __bool__(self): # /not/ if this is the list admin. if globalopts: if not is_user_or_siteadmin: - doc.addError(_("""The list administrator may not change the + doc.addError(_(f"""The list administrator may not change the options for this user's other subscriptions. However the options for this mailing list subscription has been changed."""), _('Note: ')) @@ -777,11 +777,11 @@ def __bool__(self): # Now print the results if cantdigest: - msg = _('''The list administrator has disabled digest delivery for + msg = _(f'''The list administrator has disabled digest delivery for this list, so your delivery option has not been set. However your other options have been set successfully.''') elif mustdigest: - msg = _('''The list administrator has disabled non-digest delivery + msg = _(f'''The list administrator has disabled non-digest delivery for this list, so your delivery option has not been set. However your other options have been set successfully.''') else: @@ -896,7 +896,7 @@ def options_page(mlist, doc, user, cpuser, userlang, message=''): units = _('days') else: units = _('day') - replacements[''] = _('%(days)d %(units)s') + replacements[''] = _('%(days)d {units}') replacements[''] = mlist.FormatBox('new-address') replacements[''] = mlist.FormatBox( @@ -936,9 +936,9 @@ def options_page(mlist, doc, user, cpuser, userlang, message=''): mlist.FormatOptionButton(mm_cfg.ReceiveNonmatchingTopics, 1, user)) if cpuser is not None: - replacements[''] = _(''' + replacements[''] = _(f''' You are subscribed to this list with the case-preserved address -%(cpuser)s.''') +{cpuser}.''') else: replacements[''] = '' @@ -952,11 +952,11 @@ def loginpage(mlist, doc, user, lang): realname = mlist.real_name actionurl = mlist.GetScriptURL('options') if user is None: - title = _('%(realname)s list: member options login page') + title = _('{realname} list: member options login page') extra = _('email address and ') else: safeuser = Utils.websafe(user) - title = _('%(realname)s list: member options for user %(safeuser)s') + title = _('{realname} list: member options for user {safeuser}') obuser = Utils.ObscureEmail(user) extra = '' # Set up the title @@ -982,8 +982,8 @@ def loginpage(mlist, doc, user, lang): form = Form(actionurl) form.AddItem(Hidden('language', lang)) table = Table(width='100%', border=0, cellspacing=4, cellpadding=5) - table.AddRow([_("""In order to change your membership option, you must - first log in by giving your %(extra)smembership password in the section + table.AddRow([_(f"""In order to change your membership option, you must + first log in by giving your {extra}membership password in the section below. If you don't remember your membership password, you can have it emailed to you by clicking on the button below. If you just want to unsubscribe from this list, click on the Unsubscribe button and a @@ -1010,7 +1010,7 @@ def loginpage(mlist, doc, user, lang): table.AddCellInfo(table.GetCurrentRowIndex(), 0, bgcolor=mm_cfg.WEB_HEADER_COLOR) - table.AddRow([_("""By clicking on the Unsubscribe button, a + table.AddRow([_(f"""By clicking on the Unsubscribe button, a confirmation message will be emailed to you. This message will have a link that you should click on to complete the removal process (you can also confirm by email; see the instructions in the confirmation @@ -1022,7 +1022,7 @@ def loginpage(mlist, doc, user, lang): table.AddCellInfo(table.GetCurrentRowIndex(), 0, bgcolor=mm_cfg.WEB_HEADER_COLOR) - table.AddRow([_("""By clicking on the Remind button, your + table.AddRow([_(f"""By clicking on the Remind button, your password will be emailed to you.""")]) table.AddRow([Center(SubmitButton('login-remind', _('Remind')))]) @@ -1136,7 +1136,7 @@ def topic_details(mlist, doc, user, cpuser, userlang, varhelp): if not name: options_page(mlist, doc, user, cpuser, userlang, - _('Requested topic is not valid: %(topicname)s')) + _('Requested topic is not valid: {topicname}')) print(doc.Format()) return diff --git a/Mailman/Cgi/private.py b/Mailman/Cgi/private.py index f193e357..0ab10427 100644 --- a/Mailman/Cgi/private.py +++ b/Mailman/Cgi/private.py @@ -106,8 +106,8 @@ def main(): except Errors.MMListError as e: # Avoid cross-site scripting attacks safelistname = Utils.websafe(listname) - msg = _('No such list %(safelistname)s') - doc.SetTitle(_("Private Archive Error - %(msg)s")) + msg = _('No such list {safelistname}') + doc.SetTitle(_("Private Archive Error - {msg}")) doc.AddItem(Header(2, msg)) # Send this with a 404 status. print('Status: 404 Not Found') @@ -155,7 +155,7 @@ def main(): # Are we processing a password reminder from the login screen? if 'login-remind' in cgidata: if username: - message = Bold(FontSize('+1', _("""If you are a list member, + message = Bold(FontSize('+1', _(f"""If you are a list member, your password has been emailed to you."""))).Format() else: message = Bold(FontSize('+1', diff --git a/Mailman/Cgi/rmlist.py b/Mailman/Cgi/rmlist.py index a30a2939..54f2ad55 100644 --- a/Mailman/Cgi/rmlist.py +++ b/Mailman/Cgi/rmlist.py @@ -73,8 +73,8 @@ def main(): except Errors.MMListError as e: # Avoid cross-site scripting attacks safelistname = Utils.websafe(listname) - title = _('No such list %(safelistname)s') - doc.SetTitle(_('No such list %(safelistname)s')) + title = _('No such list {safelistname}') + doc.SetTitle(_('No such list {safelistname}')) doc.AddItem( Header(3, Bold(FontAttr(title, color='#ff0000', size='+2')))) @@ -185,12 +185,12 @@ def process_request(doc, cgidata, mlist): table.AddCellInfo(table.GetCurrentRowIndex(), 0, bgcolor=mm_cfg.WEB_HEADER_COLOR) if not problems: - table.AddRow([_('''You have successfully deleted the mailing list - %(listname)s.''')]) + table.AddRow([_(f'''You have successfully deleted the mailing list + {listname}.''')]) else: sitelist = Utils.get_site_email(mlist.host_name) - table.AddRow([_('''There were some problems deleting the mailing list - %(listname)s. Contact your site administrator at %(sitelist)s + table.AddRow([_(f'''There were some problems deleting the mailing list + {listname}. Contact your site administrator at {sitelist} for details.''')]) doc.AddItem(table) doc.AddItem('
              ') @@ -206,8 +206,8 @@ def process_request(doc, cgidata, mlist): def request_deletion(doc, mlist, errmsg=None): realname = mlist.real_name - title = _('Permanently remove mailing list %(realname)s') - doc.SetTitle(_('Permanently remove mailing list %(realname)s')) + title = _('Permanently remove mailing list {realname}') + doc.SetTitle(_('Permanently remove mailing list {realname}')) table = Table(border=0, width='100%') table.AddRow([Center(Bold(FontAttr(title, size='+1')))]) @@ -220,7 +220,7 @@ def request_deletion(doc, mlist, errmsg=None): FontAttr(_('Error: '), color='#ff0000', size='+2').Format() + Italic(errmsg).Format()))]) - table.AddRow([_("""This page allows you as the list owner, to permanently + table.AddRow([_(f"""This page allows you as the list owner, to permanently remove this mailing list from the system. This action is not undoable so you should undertake it only if you are absolutely sure this mailing list has served its purpose and is no longer necessary. diff --git a/Mailman/Cgi/roster.py b/Mailman/Cgi/roster.py index 33afdf11..6868f8e7 100644 --- a/Mailman/Cgi/roster.py +++ b/Mailman/Cgi/roster.py @@ -57,7 +57,7 @@ def main(): safelistname = Utils.websafe(listname) # Send this with a 404 status. print('Status: 404 Not Found') - error_page(_('No such list %(safelistname)s')) + error_page(_('No such list {safelistname}')) syslog('error', 'roster: No such list "%s": %s', listname, e) return @@ -116,7 +116,7 @@ def main(): doc.set_language(lang) # Send this with a 401 status. print('Status: 401 Unauthorized') - error_page_doc(doc, _('%(realname)s roster authentication failed.')) + error_page_doc(doc, _('{realname} roster authentication failed.')) doc.AddItem(mlist.GetMailmanFooter()) print(doc.Format()) remote = os.environ.get('HTTP_FORWARDED_FOR', diff --git a/Mailman/Cgi/subscribe.py b/Mailman/Cgi/subscribe.py index 0ea11d5e..776719fb 100644 --- a/Mailman/Cgi/subscribe.py +++ b/Mailman/Cgi/subscribe.py @@ -65,7 +65,7 @@ def main(): # Avoid cross-site scripting attacks safelistname = Utils.websafe(listname) doc.AddItem(Header(2, _("Error"))) - doc.AddItem(Bold(_('No such list %(safelistname)s'))) + doc.AddItem(Bold(_('No such list {safelistname}'))) # Send this with a 404 status. print('Status: 404 Not Found') print(doc.Format()) @@ -153,10 +153,10 @@ def process_form(mlist, doc, cgidata, lang): httpresp.close() if not captcha_response['success']: e_codes = COMMASPACE.join(captcha_response['error-codes']) - results.append(_('reCAPTCHA validation failed: %(e_codes)s')) + results.append(_('reCAPTCHA validation failed: {e_codes}')) except urllib.error.URLError as e: e_reason = e.reason - results.append(_('reCAPTCHA could not be validated: %(e_reason)s')) + results.append(_('reCAPTCHA could not be validated: {e_reason}')) # Are we checking the hidden data? if mm_cfg.SUBSCRIBE_FORM_SECRET: @@ -245,7 +245,7 @@ def process_form(mlist, doc, cgidata, lang): # Public rosters privacy_results = '' else: - privacy_results = _("""\ + privacy_results = _(f"""\ Your subscription request has been received, and will soon be acted upon. Depending on the configuration of this mailing list, your subscription request may have to be first confirmed by you via email, or approved by the list @@ -259,15 +259,15 @@ def process_form(mlist, doc, cgidata, lang): # Check for all the errors that mlist.AddMember can throw options on the # web page for this cgi except Errors.MembershipIsBanned: - results = _("""The email address you supplied is banned from this + results = _(f"""The email address you supplied is banned from this mailing list. If you think this restriction is erroneous, please - contact the list owners at %(listowner)s.""") + contact the list owners at {listowner}.""") except Errors.MMBadEmailError: - results = _("""\ + results = _(f"""\ The email address you supplied is not valid. (E.g. it must contain an `@'.)""") except Errors.MMHostileAddress: - results = _("""\ + results = _(f"""\ Your subscription is not allowed because the email address you gave is insecure.""") except Errors.MMSubscribeNeedsConfirmation: @@ -275,10 +275,10 @@ def process_form(mlist, doc, cgidata, lang): if privacy_results: results = privacy_results else: - results = _("""\ + results = _(f"""\ Confirmation from your email address is required, to prevent anyone from subscribing you without permission. Instructions are being sent to you at -%(email)s. Please note your subscription will not start until you confirm +{email}. Please note your subscription will not start until you confirm your subscription.""") except Errors.MMNeedApproval as x: # Results string depends on whether we have private rosters or not @@ -287,8 +287,8 @@ def process_form(mlist, doc, cgidata, lang): else: # We need to interpolate into x.__str__() x = _(str(x)) - results = _("""\ -Your subscription request was deferred because %(x)s. Your request has been + results = _(f"""\ +Your subscription request was deferred because {x}. Your request has been forwarded to the list moderator. You will receive email informing you of the moderator's decision when they get to your request.""") except Errors.MMAlreadyPending: @@ -313,9 +313,9 @@ def process_form(mlist, doc, cgidata, lang): mlist.getMemberCPAddress(email), mlist.GetBouncesEmail(), _('Mailman privacy alert'), - _("""\ + _(f"""\ An attempt was made to subscribe your address to the mailing list -%(listaddr)s. You are already subscribed to this mailing list. +{listaddr}. You are already subscribed to this mailing list. Note that the list membership is not public, so it is possible that a bad person was trying to probe the list for its membership. This would be a @@ -325,7 +325,7 @@ def process_form(mlist, doc, cgidata, lang): subscribed to the list, then you can ignore this message. If you suspect that an attempt is being made to covertly discover whether you are a member of this list, and you are worried about your privacy, then feel free to send a message -to the list administrator at %(listowner)s. +to the list administrator at {listowner}. """), lang=mlang) finally: i18n.set_translation(otrans) @@ -341,8 +341,8 @@ def process_form(mlist, doc, cgidata, lang): if privacy_results: results = privacy_results else: - results = _("""\ -You have been successfully subscribed to the %(realname)s mailing list.""") + results = _(f"""\ +You have been successfully subscribed to the {realname} mailing list.""") # Show the results print_results(mlist, results, doc, lang) diff --git a/Mailman/HTMLFormatter.py b/Mailman/HTMLFormatter.py index 677b7f62..5a1bc3af 100644 --- a/Mailman/HTMLFormatter.py +++ b/Mailman/HTMLFormatter.py @@ -48,7 +48,7 @@ def GetMailmanFooter(self): hostname = self.host_name listinfo_link = Link(self.GetScriptURL('listinfo'), realname).Format() owner_link = Link('mailto:' + self.GetOwnerEmail(), ownertext).Format() - innertext = _('%(listinfo_link)s list run by %(owner_link)s') + innertext = _(f'{listinfo_link} list run by {owner_link}') return Container( '
              ', Address( @@ -56,11 +56,11 @@ def GetMailmanFooter(self): innertext, '
              ', Link(self.GetScriptURL('admin'), - _('%(realname)s administrative interface')), + _(f'{realname} administrative interface')), _(' (requires authorization)'), '
              ', Link(Utils.ScriptURL('listinfo'), - _('Overview of all %(hostname)s mailing lists')), + _(f'Overview of all {hostname} mailing lists')), '

              ', MailmanLogo()))).Format() def FormatUsers(self, digest, lang=None, list_hidden=False): @@ -145,32 +145,32 @@ def FormatDisabledNotice(self, user): elif status == MemberAdaptor.BYBOUNCE: date = time.strftime('%d-%b-%Y', time.localtime(Utils.midnight(info.date))) - reason = _('''; it was disabled due to excessive bounces. The - last bounce was received on %(date)s''') + reason = _(f'''; it was disabled due to excessive bounces. The + last bounce was received on {date}''') elif status == MemberAdaptor.UNKNOWN: reason = _('; it was disabled for unknown reasons') if reason: note = FontSize('+1', _( - 'Note: your list delivery is currently disabled%(reason)s.' + f'Note: your list delivery is currently disabled{reason}.' )).Format() link = Link('#disable', _('Mail delivery')).Format() mailto = Link('mailto:' + self.GetOwnerEmail(), _('the list administrator')).Format() - return _('''

              %(note)s + return _(f'''

              {note}

              You may have disabled list delivery intentionally, or it may have been triggered by bounces from your email address. In either case, to re-enable delivery, change the - %(link)s option below. Contact %(mailto)s if you have any + {link} option below. Contact {mailto} if you have any questions or need assistance.''') elif info and info.score > 0: # Provide information about their current bounce score. We know # their membership is currently enabled. score = info.score total = self.bounce_score_threshold - return _('''

              We have received some recent bounces from your - address. Your current bounce score is %(score)s out of a - maximum of %(total)s. Please double check that your subscribed + return _(f'''

              We have received some recent bounces from your + address. Your current bounce score is {score} out of a + maximum of {total}. Please double check that your subscribed address is correct and that there are no problems with delivery to this address. Your bounce score will be automatically reset if the problems are corrected soon.''') @@ -180,9 +180,9 @@ def FormatDisabledNotice(self, user): def FormatUmbrellaNotice(self, user, type): addr = self.GetMemberAdminEmail(user) if self.umbrella_list: - return _("(Note - you are subscribing to a list of mailing lists, " - "so the %(type)s notice will be sent to the admin address" - " for your membership, %(addr)s.)

              ") + return _(f"(Note - you are subscribing to a list of mailing lists, " + "so the {type} notice will be sent to the admin address" + " for your membership, {addr}.)

              ") else: return "" @@ -190,15 +190,15 @@ def FormatSubscriptionMsg(self): msg = '' also = '' if self.subscribe_policy == 1: - msg += _('''You will be sent email requesting confirmation, to + msg += _(f'''You will be sent email requesting confirmation, to prevent others from gratuitously subscribing you.''') elif self.subscribe_policy == 2: - msg += _("""This is a closed list, which means your subscription + msg += _(f"""This is a closed list, which means your subscription will be held for approval. You will be notified of the list moderator's decision by email.""") also = _('also ') elif self.subscribe_policy == 3: - msg += _("""You will be sent email requesting confirmation, to + msg += _(f"""You will be sent email requesting confirmation, to prevent others from gratuitously subscribing you. Once confirmation is received, your request will be held for approval by the list moderator. You will be notified of the moderator's @@ -207,24 +207,24 @@ def FormatSubscriptionMsg(self): if msg: msg += ' ' if self.private_roster == 1: - msg += _('''This is %(also)sa private list, which means that the + msg += _(f'''This is {also}a private list, which means that the list of members is not available to non-members.''') elif self.private_roster: - msg += _('''This is %(also)sa hidden list, which means that the + msg += _(f'''This is {also}a hidden list, which means that the list of members is available only to the list administrator.''') else: - msg += _('''This is %(also)sa public list, which means that the + msg += _(f'''This is {also}a public list, which means that the list of members list is available to everyone.''') if self.obscure_addresses: - msg += _(''' (but we obscure the addresses so they are not + msg += _(f''' (but we obscure the addresses so they are not easily recognizable by spammers).''') if self.umbrella_list: sfx = self.umbrella_member_suffix - msg += _("""

              (Note that this is an umbrella list, intended to + msg += _(f"""

              (Note that this is an umbrella list, intended to have only other mailing lists as members. Among other things, this means that your confirmation request will be sent to the - `%(sfx)s' account for your address.)""") + `{sfx}' account for your address.)""") return msg def FormatUndigestButton(self): @@ -255,20 +255,28 @@ def FormatEditingOption(self, lang): either = '' realname = self.real_name - text = (_('''To unsubscribe from %(realname)s, get a password reminder, - or change your subscription options %(either)senter your subscription + text = _(f'''To unsubscribe from {realname}, get a password reminder, + or change your subscription options {either}enter your subscription email address: -

              ''') - + TextBox('email', size=30).Format() - + ' ' - + SubmitButton('UserOptions', - _('Unsubscribe or edit options')).Format() - + Hidden('language', lang).Format() - + '
              ') +

              ''') +# text += TextBox('email', size=30).Format() + text += (' ') + text += SubmitButton('UserOptions', _(f'Unsubscribe or edit options')).Format() + text += Hidden('language', lang).Format() + text += ('

              ') +#` text = (_(f'''To unsubscribe from {realname}, get a password reminder, +#` or change your subscription options {either}enter your subscription +#` email address: +#`

              ''') +#` + TextBox('email', size=30).Format() +#` + f' ' +#` + SubmitButton('UserOptions', _(f'Unsubscribe or edit options')).Format() +#` + Hidden('language', lang).Format() +#` + f'
              ') if self.private_roster == 0: - text += _('''

              ... or select your entry from + text += _(f'''

              ... or select your entry from the subscribers list (see above).''') - text += _(''' If you leave the field blank, you will be prompted for + text += _(f''' If you leave the field blank, you will be prompted for your email address''') return text @@ -277,10 +285,10 @@ def RestrictedListMessage(self, which, restriction): return '' elif restriction == 1: return _( - '''(%(which)s is only available to the list + f'''({which} is only available to the list members.)''') else: - return _('''(%(which)s is only available to the list + return _(f'''({which} is only available to the list administrator.)''') def FormatRosterOptionForUser(self, lang): @@ -313,7 +321,7 @@ def RosterOption(self, lang): + whom + " ") container.AddItem(self.FormatBox('roster-email')) - container.AddItem(_("Password: ") + container.AddItem(_(" Password: ") + self.FormatSecureBox('roster-pw') + "  ") container.AddItem(SubmitButton('SubscriberRoster', @@ -329,7 +337,7 @@ def FormatFormStart(self, name, extra='', else: full_url = base_url if mlist: - return ("""

              + return (f""" """ % (full_url, csrf_token(mlist, contexts, user))) return ('' % full_url) diff --git a/Mailman/Pending.py b/Mailman/Pending.py index aaa4d373..0d75342d 100644 --- a/Mailman/Pending.py +++ b/Mailman/Pending.py @@ -68,8 +68,8 @@ def pend_new(self, op, *content, **kws): # are discarded because they're the most predictable bits. while True: now = time.time() - x = random.random() + now % 1.0 + time.clock() % 1.0 - cookie = sha_new(repr(x)).hexdigest() + x = random.random() + now % 1.0 + cookie = sha_new(repr(x).encode()).hexdigest() # We'll never get a duplicate, but we'll be anal about checking # anyway. if cookie not in db: @@ -112,7 +112,7 @@ def __save(self, db): tmpfile = '%s.tmp.%d.%d' % (self.__pendfile, os.getpid(), now) omask = os.umask(0o007) try: - fp = open(tmpfile, 'w') + fp = open(tmpfile, 'wb') try: pickle.dump(db, fp) fp.flush() diff --git a/Mailman/Version.py b/Mailman/Version.py index a5a806c3..d310973b 100644 --- a/Mailman/Version.py +++ b/Mailman/Version.py @@ -16,7 +16,7 @@ # USA. # Mailman version -VERSION = '2.1.39' +VERSION = '2.1.40-alpha1' # And as a hex number in the manner of PY_VERSION_HEX ALPHA = 0xa diff --git a/bin/newlist b/bin/newlist index 8628a8a1..8c9a680e 100755 --- a/bin/newlist +++ b/bin/newlist @@ -17,9 +17,9 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, # USA. -"""Create a new, unpopulated mailing list. +f"""Create a new, unpopulated mailing list. -Usage: %(PROGRAM)s [options] [listname [listadmin-addr [admin-password]]] +Usage: %(PROGRAM) [options] [listname [listadmin-addr [admin-password]]] Options: @@ -159,7 +159,7 @@ def main(): # Is the language known? if lang not in mm_cfg.LC_DESCRIPTIONS.keys(): - usage(1, C_('Unknown language: %(lang)s')) + usage(1, C_(f'Unknown language: {lang}')) if len(args) > 0: listname = args[0] @@ -179,7 +179,7 @@ def main(): web_page_url = mm_cfg.DEFAULT_URL_PATTERN % urlhost if Utils.list_exists(listname): - usage(1, C_('List already exists: %(listname)s')) + usage(1, C_(f"List already exists: {listname}")) if len(args) > 1: owner_mail = args[1] @@ -190,7 +190,7 @@ def main(): if len(args) > 2: listpasswd = args[2] else: - listpasswd = getpass.getpass(C_('Initial %(listname)s password: ')) + listpasswd = getpass.getpass(C_(f'Initial {listname} password: ')) # List passwords cannot be empty listpasswd = listpasswd.strip() if not listpasswd: @@ -198,7 +198,7 @@ def main(): mlist = MailList.MailList() try: - pw = Utils.sha_new(listpasswd).hexdigest() + pw = Utils.sha_new(listpasswd.encode()).hexdigest() # Guarantee that all newly created files have the proper permission. # proper group ownership should be assured by the autoconf script # enforcing that all directories have the group sticky bit set @@ -214,13 +214,13 @@ def main(): finally: os.umask(oldmask) except Errors.BadListNameError as s: - usage(1, C_('Illegal list name: %(s)s')) + usage(1, C_(f'Illegal list name: %(s)s')) except Errors.EmailAddressError as s: - usage(1, C_('Bad owner email address: %(s)s') + + usage(1, C_(f'Bad owner email address: %(s)s') + C_(' - owner addresses need to be fully-qualified names' ' like "owner@example.com", not just "owner".')) except Errors.MMListAlreadyExistsError: - usage(1, C_('List already exists: %(listname)s')) + usage(1, C_(f"List already exists: {listname}")) # Assign domain-specific attributes mlist.host_name = host_name @@ -241,7 +241,7 @@ def main(): # And send the notice to the list owner if not quiet and not automate: - print('Hit enter to notify %(listname)s owner...'), + print(f"Hit enter to notify {listname} owner..."), sys.stdin.readline() if not quiet: siteowner = Utils.get_site_email(mlist.host_name, 'owner') @@ -262,7 +262,7 @@ def main(): try: msg = Message.UserNotification( owner_mail, siteowner, - _('Your new mailing list: %(listname)s'), + _(f'Your new mailing list: {listname}'), text, mlist.preferred_language) msg.send(mlist) finally: diff --git a/messages/ar/LC_MESSAGES/mailman.po b/messages/ar/LC_MESSAGES/mailman.po index d388e467..9467c188 100755 --- a/messages/ar/LC_MESSAGES/mailman.po +++ b/messages/ar/LC_MESSAGES/mailman.po @@ -69,10 +69,12 @@ msgid "

              Currently, there are no archives.

              " msgstr "

              حالياً، لا يوجد أي Ø£Ø±Ø´ÙŠÙØ§Øª.

              " #: Mailman/Archiver/HyperArch.py:821 +#, fuzzy msgid "Gzip'd Text%(sz)s" msgstr "نص مضغوط%(sz)s" #: Mailman/Archiver/HyperArch.py:826 +#, fuzzy msgid "Text%(sz)s" msgstr "نص%(sz)s" @@ -145,18 +147,22 @@ msgid "Third" msgstr "ثالت" #: Mailman/Archiver/HyperArch.py:938 +#, fuzzy msgid "%(ord)s quarter %(year)i" msgstr "%(ord)s ربع %(year)i" #: Mailman/Archiver/HyperArch.py:945 +#, fuzzy msgid "%(month)s %(year)i" msgstr "%(month)s %(year)i" #: Mailman/Archiver/HyperArch.py:950 +#, fuzzy msgid "The Week Of Monday %(day)i %(month)s %(year)i" msgstr "أسبوع الإثنين %(day)i %(month)s %(year)i" #: Mailman/Archiver/HyperArch.py:954 +#, fuzzy msgid "%(day)i %(month)s %(year)i" msgstr "%(day)i %(month)s %(year)i" @@ -165,10 +171,12 @@ msgid "Computing threaded index\n" msgstr "يتم حساب Ùهرس النقاشات\n" #: Mailman/Archiver/HyperArch.py:1319 +#, fuzzy msgid "Updating HTML for article %(seq)s" msgstr "تحديث نص HTML للمقالة %(seq)s" #: Mailman/Archiver/HyperArch.py:1326 +#, fuzzy msgid "article file %(filename)s is missing!" msgstr "مل٠المقالة %(filename)s Ù…Ùقود!" @@ -185,6 +193,7 @@ msgid "Pickling archive state into " msgstr "كبس حالة الأرشي٠ÙÙŠ " #: Mailman/Archiver/pipermail.py:453 +#, fuzzy msgid "Updating index files for archive [%(archive)s]" msgstr "تحديث Ù…Ù„ÙØ§Øª الÙهارس للأرشي٠[%(archive)s]" @@ -193,6 +202,7 @@ msgid " Thread" msgstr " نقاش" #: Mailman/Archiver/pipermail.py:597 +#, fuzzy msgid "#%(counter)05d %(msgid)s" msgstr "#%(counter)05d %(msgid)s" @@ -230,6 +240,7 @@ msgid "disabled address" msgstr "معطل" #: Mailman/Bouncer.py:304 +#, fuzzy msgid " The last bounce received from you was dated %(date)s" msgstr "استقبل رد Ø§Ù„Ø±ÙØ¶ الأخير من قبلك بتاريخ %(date)s" @@ -257,6 +268,7 @@ msgstr "مشرÙ" #: Mailman/Cgi/options.py:101 Mailman/Cgi/private.py:108 #: Mailman/Cgi/rmlist.py:75 Mailman/Cgi/roster.py:59 #: Mailman/Cgi/subscribe.py:67 +#, fuzzy msgid "No such list %(safelistname)s" msgstr "لا يوجد قائمة بالإسم %(safelistname)s" @@ -328,6 +340,7 @@ msgstr "" " سو٠يستلم هؤلاء الناس البريد إلى أن تحل المشكلة%(rm)r" #: Mailman/Cgi/admin.py:268 +#, fuzzy msgid "%(hostname)s mailing lists - Admin Links" msgstr "%(hostname)s قوائم بريدية - ارتباطات إشراÙية" @@ -340,6 +353,7 @@ msgid "Mailman" msgstr "ميلمان" #: Mailman/Cgi/admin.py:310 +#, fuzzy msgid "" "

              There currently are no publicly-advertised %(mailmanlink)s\n" " mailing lists on %(hostname)s." @@ -348,6 +362,7 @@ msgstr "" " على %(hostname)s." #: Mailman/Cgi/admin.py:316 +#, fuzzy msgid "" "

              Below is the collection of publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s. Click on a list\n" @@ -362,6 +377,7 @@ msgid "right " msgstr "صحيح " #: Mailman/Cgi/admin.py:325 +#, fuzzy msgid "" "To visit the administrators configuration page for an\n" " unadvertised list, open a URL similar to this one, but with a '/' " @@ -403,12 +419,14 @@ msgid "No valid variable name found." msgstr "لا يوجد اسم متغير صحيح" #: Mailman/Cgi/admin.py:395 +#, fuzzy msgid "" "%(realname)s Mailing list Configuration Help\n" "
              %(varname)s Option" msgstr "%(realname)s مساعدة ضبط القائمة البريدية
              %(varname)s خيار" #: Mailman/Cgi/admin.py:402 +#, fuzzy msgid "Mailman %(varname)s List Option Help" msgstr "مساعدة خيارات قائمة ميلمان %(varname)s" @@ -426,14 +444,17 @@ msgstr "" "الخيار لهذه القائمة البريدية. ويمكنك أيضاً" #: Mailman/Cgi/admin.py:431 +#, fuzzy msgid "return to the %(categoryname)s options page." msgstr "أن تعود إلى ØµÙØ­Ø© خيارات الـ %(categoryname)s" #: Mailman/Cgi/admin.py:446 +#, fuzzy msgid "%(realname)s Administration (%(label)s)" msgstr "%(realname)s إشرا٠(%(label)s)" #: Mailman/Cgi/admin.py:447 +#, fuzzy msgid "%(realname)s mailing list administration
              %(label)s Section" msgstr "الإشرا٠على القائمة البريدية %(realname)s
              قسم %(label)s" @@ -513,6 +534,7 @@ msgid "Value" msgstr "القيمة" #: Mailman/Cgi/admin.py:668 +#, fuzzy msgid "" "Badly formed options entry:\n" " %(record)s" @@ -613,10 +635,12 @@ msgid "Move rule down" msgstr "حرك القاعدة للأسÙÙ„:" #: Mailman/Cgi/admin.py:870 +#, fuzzy msgid "
              (Edit %(varname)s)" msgstr "
              (حرر %(varname)s)" #: Mailman/Cgi/admin.py:872 +#, fuzzy msgid "
              (Details for %(varname)s)" msgstr "
              (ØªÙØ§ØµÙŠÙ„ %(varname)s)" @@ -656,6 +680,7 @@ msgid "(help)" msgstr "(مساعدة)" #: Mailman/Cgi/admin.py:930 +#, fuzzy msgid "Find member %(link)s:" msgstr "ابحث عن مشترك %(link)s:" @@ -668,10 +693,12 @@ msgid "Bad regular expression: " msgstr "صيغة نظامية سيئة: " #: Mailman/Cgi/admin.py:1013 +#, fuzzy msgid "%(allcnt)s members total, %(membercnt)s shown" msgstr "%(allcnt)s مجموع المشتركين, %(membercnt)s تم عرضه" #: Mailman/Cgi/admin.py:1016 +#, fuzzy msgid "%(allcnt)s members total" msgstr "%(allcnt)s مجموع المشتركين" @@ -844,6 +871,7 @@ msgid "" msgstr "

              لعرض مشتركين أكثر، اضغط على المجال المناسب المعروض تحت:" #: Mailman/Cgi/admin.py:1221 +#, fuzzy msgid "from %(start)s to %(end)s" msgstr "من %(start)s إلى %(end)s" @@ -980,6 +1008,7 @@ msgid "Change list ownership passwords" msgstr "غير كلمة سر ملكية القائمة" #: Mailman/Cgi/admin.py:1364 +#, fuzzy msgid "" "The list administrators are the people who have ultimate control " "over\n" @@ -1144,8 +1173,9 @@ msgid "%(schange_to)s is already a member" msgstr "عضو أصلاً" #: Mailman/Cgi/admin.py:1620 +#, fuzzy msgid "%(schange_to)s matches banned pattern %(spat)s" -msgstr "" +msgstr "عضو أصلاً" #: Mailman/Cgi/admin.py:1622 msgid "Address %(schange_from)s changed to %(schange_to)s" @@ -1185,6 +1215,7 @@ msgid "Not subscribed" msgstr "غير مشترك" #: Mailman/Cgi/admin.py:1773 +#, fuzzy msgid "Ignoring changes to deleted member: %(user)s" msgstr "تجاهل التعديلات لعنصر المحذوÙ: %(user)s" @@ -1197,10 +1228,12 @@ msgid "Error Unsubscribing:" msgstr "خطأ ÙÙŠ إلغاء الاشتراك" #: Mailman/Cgi/admindb.py:235 Mailman/Cgi/admindb.py:248 +#, fuzzy msgid "%(realname)s Administrative Database" msgstr "قاعدة بيانات الإشرا٠للمشر٠%(realname)s" #: Mailman/Cgi/admindb.py:238 +#, fuzzy msgid "%(realname)s Administrative Database Results" msgstr "نتائج قاعدة بيانات الإشرا٠للمشر٠%(realname)s" @@ -1229,6 +1262,7 @@ msgid "Discard all messages marked Defer" msgstr "قم بإلغاء جميع الرسائل المحدد لها تأجيل" #: Mailman/Cgi/admindb.py:298 +#, fuzzy msgid "all of %(esender)s's held messages." msgstr "جميع الرسائل Ø§Ù„Ù…ØªÙˆÙ‚ÙØ© لـ %(esender)s" @@ -1249,6 +1283,7 @@ msgid "list of available mailing lists." msgstr "قائمة بالقوئم البريدية الموجودة." #: Mailman/Cgi/admindb.py:362 +#, fuzzy msgid "You must specify a list name. Here is the %(link)s" msgstr "يجب أن تحدد اسم القائمة. هنا هو الارتباط %(link)s" @@ -1331,6 +1366,7 @@ msgid "The sender is now a member of this list" msgstr "المرسل الآن عضو ÙÙŠ هذه القائمة" #: Mailman/Cgi/admindb.py:568 +#, fuzzy msgid "Add %(esender)s to one of these sender filters:" msgstr "أض٠%(esender)s إلى أحد مصÙيات المرسل:" @@ -1351,6 +1387,7 @@ msgid "Rejects" msgstr "Ø±Ø§ÙØ¶ÙŠÙ†" #: Mailman/Cgi/admindb.py:584 +#, fuzzy msgid "" "Ban %(esender)s from ever subscribing to this\n" " mailing list" @@ -1363,6 +1400,7 @@ msgid "" msgstr "انقر على رقم الرسالة لعرض الرسالة Ø§Ù„Ù…ÙØ±Ø¯Ø©ØŒ أو يمكنك " #: Mailman/Cgi/admindb.py:591 +#, fuzzy msgid "view all messages from %(esender)s" msgstr "عرض جميع الرسائل المرسلة من قبل %(esender)s" @@ -1486,6 +1524,7 @@ msgstr "" "إلغاؤه." #: Mailman/Cgi/confirm.py:178 +#, fuzzy msgid "System error, bad content: %(content)s" msgstr "خطأ ÙÙŠ النظام، محتوى سيء: %(content)s" @@ -1521,6 +1560,7 @@ msgid "Confirm subscription request" msgstr "أكد طلب الاشتراك" #: Mailman/Cgi/confirm.py:259 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " subscription request to the mailing list %(listname)s. Your\n" @@ -1547,6 +1587,7 @@ msgstr "" "اشتراكي إذا كنت لم تعد تريد الاشتراك ÙÙŠ هذه القائمة." #: Mailman/Cgi/confirm.py:275 +#, fuzzy msgid "" "Your confirmation is required in order to continue with\n" " the subscription request to the mailing list %(listname)s.\n" @@ -1566,8 +1607,8 @@ msgid "" " this mailing list, you can hit Cancel my subscription\n" " request." msgstr "" -"إن تأكيدك مطلوب للاستمرار ÙÙŠ طلب اشتراكك ÙÙŠ القائمة البريدية " -"%(listname)s.\n" +"إن تأكيدك مطلوب للاستمرار ÙÙŠ طلب اشتراكك ÙÙŠ القائمة البريدية " +"%(listname)s.\n" "خيارات اشتراكك معروضة تحت، قم بأي تعديلات ضرورية واضغط على Subscribe to " "list ... لإكمال عملية التأكيد. Ùور قيامك بتأكيد طلب اشتراكك، ÙØ¥Ù† المنظم " "يجب عليه أن يقبل أو ÙŠØ±ÙØ¶ طلب عضويتك. ستستلم ملاحظة حول قراره.\n" @@ -1593,6 +1634,7 @@ msgid "Preferred language:" msgstr "اللغة Ø§Ù„Ù…ÙØ¶Ù„Ø©:" #: Mailman/Cgi/confirm.py:317 +#, fuzzy msgid "Subscribe to list %(listname)s" msgstr "اشترك ÙÙŠ القائمة %(listname)s" @@ -1609,6 +1651,7 @@ msgid "Awaiting moderator approval" msgstr "بانتظار مواÙقة المنظم" #: Mailman/Cgi/confirm.py:386 +#, fuzzy msgid "" " You have successfully confirmed your subscription request to " "the\n" @@ -1661,6 +1704,7 @@ msgid "Subscription request confirmed" msgstr "تم تأكيد طلب الاشتراك" #: Mailman/Cgi/confirm.py:418 +#, fuzzy msgid "" " You have successfully confirmed your subscription request for\n" " \"%(addr)s\" to the %(listname)s mailing list. A separate\n" @@ -1686,6 +1730,7 @@ msgid "Unsubscription request confirmed" msgstr "تم تأكيد إلغاء الاشتراك" #: Mailman/Cgi/confirm.py:469 +#, fuzzy msgid "" " You have successfully unsubscribed from the %(listname)s " "mailing\n" @@ -1705,6 +1750,7 @@ msgid "Not available" msgstr "غير Ù…ØªÙˆÙØ±" #: Mailman/Cgi/confirm.py:498 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " unsubscription request from the mailing list %(listname)s. " @@ -1721,8 +1767,8 @@ msgid "" "

              Or hit Cancel and discard to cancel this unsubscription\n" " request." msgstr "" -"تأكيدك مطلوب من أجل أن يتم إكمال طلب إلغاء الاشتراك من القائمة البريدية " -"%(listname)s. أنت مسجل الآن بالعنوان:\n" +"تأكيدك مطلوب من أجل أن يتم إكمال طلب إلغاء الاشتراك من القائمة البريدية " +"%(listname)s. أنت مسجل الآن بالعنوان:\n" "

              • الاسم الحقيقي: %(fullname)s\n" "
              • العنوان الإلكتروني: %(addr)s\n" "
              \n" @@ -1769,6 +1815,7 @@ msgid "Change of address request confirmed" msgstr "تم تأكيد طلب تغيير العنوان" #: Mailman/Cgi/confirm.py:571 +#, fuzzy msgid "" " You have successfully changed your address on the %(listname)s\n" " mailing list from %(oldaddr)s to %(newaddr)s. " @@ -1789,6 +1836,7 @@ msgid "globally" msgstr "بشكل كامل عام" #: Mailman/Cgi/confirm.py:605 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " change of address request for the mailing list %(listname)s. " @@ -1811,8 +1859,8 @@ msgid "" "

              Or hit Cancel and discard to cancel this change of address\n" " request." msgstr "" -"تأكيدك مطلوب للاستمرار ÙÙŠ طلب تغيير عنوانك ÙÙŠ القائمة البريدية " -"%(listname)s. أنت الآن مشترك بالعنوان \n" +"تأكيدك مطلوب للاستمرار ÙÙŠ طلب تغيير عنوانك ÙÙŠ القائمة البريدية " +"%(listname)s. أنت الآن مشترك بالعنوان \n" "

              • الاسم الحقيقي: %(fullname)s\n" "
              • العنوان البريدي الإلكتروني القديم: %(oldaddr)s\n" "
              \n" @@ -1842,6 +1890,7 @@ msgid "Sender discarded message via web." msgstr "المرسل أزال الرسالة من خلال الموقع." #: Mailman/Cgi/confirm.py:674 +#, fuzzy msgid "" "The held message with the Subject:\n" " header %(subject)s could not be found. The most " @@ -1860,6 +1909,7 @@ msgid "Posted message canceled" msgstr "تم إلغاء الرسالة المرسلة" #: Mailman/Cgi/confirm.py:685 +#, fuzzy msgid "" " You have successfully canceled the posting of your message with\n" " the Subject: header %(subject)s to the mailing list\n" @@ -1880,6 +1930,7 @@ msgstr "" "الرسالة المعلقة التي تم تحويلك إليها تم التعامل معها أصلاً من قبل المشرÙ." #: Mailman/Cgi/confirm.py:735 +#, fuzzy msgid "" "Your confirmation is required in order to cancel the\n" " posting of your message to the mailing list %(listname)s:\n" @@ -1894,8 +1945,8 @@ msgid "" "

              Or hit the Continue awaiting approval button to continue to\n" " allow the list moderator to approve or reject the message." msgstr "" -"تأكيدك مطلوب من أجل إلغاء إرسال رسالتك إلى القائمة البريدية " -"%(listname)s:\n" +"تأكيدك مطلوب من أجل إلغاء إرسال رسالتك إلى القائمة البريدية " +"%(listname)s:\n" "

              • المرسل: %(sender)s\n" "
              • العنوان: %(subject)s\n" "
              • السبب: %(reason)s\n" @@ -1924,6 +1975,7 @@ msgid "Membership re-enabled." msgstr "تم إعادة تمكين العضوية." #: Mailman/Cgi/confirm.py:798 +#, fuzzy msgid "" " You have successfully re-enabled your membership in the\n" " %(listname)s mailing list. You can now not available" msgstr "غير موجودة" #: Mailman/Cgi/confirm.py:846 +#, fuzzy msgid "" "Your membership in the %(realname)s mailing list is\n" " currently disabled due to excessive bounces. Your confirmation is\n" @@ -2017,10 +2071,12 @@ msgid "administrative list overview" msgstr "ملخص عام عن القائمة الاشراÙية" #: Mailman/Cgi/create.py:115 +#, fuzzy msgid "List name must not include \"@\": %(safelistname)s" msgstr "اسم القائمة يجب أن لا يحتوي على \"@\": %(safelistname)s" #: Mailman/Cgi/create.py:122 +#, fuzzy msgid "List already exists: %(safelistname)s" msgstr "القائمة موجودة أصلاً: %(safelistname)s" @@ -2054,18 +2110,22 @@ msgid "You are not authorized to create new mailing lists" msgstr "أنت ليس مسموحاً لك بإنشاء قوائم بريدية جديدة" #: Mailman/Cgi/create.py:182 +#, fuzzy msgid "Unknown virtual host: %(safehostname)s" msgstr "عنوان مستضي٠تخيلي غير معروÙ: %(safehostname)s" #: Mailman/Cgi/create.py:218 bin/newlist:219 +#, fuzzy msgid "Bad owner email address: %(s)s" msgstr "عنوان بريد مالك سيء: %(s)s" #: Mailman/Cgi/create.py:223 bin/newlist:182 bin/newlist:223 +#, fuzzy msgid "List already exists: %(listname)s" msgstr "القائمة موجودة أصلاً: %(listname)s" #: Mailman/Cgi/create.py:231 bin/newlist:217 +#, fuzzy msgid "Illegal list name: %(s)s" msgstr "اسم قائمة غير نظامي: %(s)s" @@ -2078,6 +2138,7 @@ msgstr "" "الموقع للمساعدة." #: Mailman/Cgi/create.py:273 bin/newlist:265 +#, fuzzy msgid "Your new mailing list: %(listname)s" msgstr "قائمتك البريدية الجديدة: %(listname)s" @@ -2086,6 +2147,7 @@ msgid "Mailing list creation results" msgstr "نتائج إنشاء القائمة البريدية" #: Mailman/Cgi/create.py:288 +#, fuzzy msgid "" "You have successfully created the mailing list\n" " %(listname)s and notification has been sent to the list owner\n" @@ -2107,6 +2169,7 @@ msgid "Create another list" msgstr "أنشء قائمة بريدية أخرى" #: Mailman/Cgi/create.py:312 +#, fuzzy msgid "Create a %(hostname)s Mailing List" msgstr "أنشء القائمة البريدية %(hostname)s" @@ -2196,6 +2259,7 @@ msgstr "" "المنظم بشكل Ø§ÙØªØ±Ø§Ø¶ÙŠ." #: Mailman/Cgi/create.py:432 +#, fuzzy msgid "" "Initial list of supported languages.

                Note that if you do not\n" " select at least one initial language, the list will use the server\n" @@ -2301,6 +2365,7 @@ msgid "List name is required." msgstr "اسم القائمة مطلوب." #: Mailman/Cgi/edithtml.py:154 +#, fuzzy msgid "%(realname)s -- Edit html for %(template_info)s" msgstr "%(realname)s -- عدل صيغة html لـ %(template_info)s" @@ -2309,10 +2374,12 @@ msgid "Edit HTML : Error" msgstr "تعديل HTML : خطأ" #: Mailman/Cgi/edithtml.py:161 +#, fuzzy msgid "%(safetemplatename)s: Invalid template" msgstr "%(safetemplatename)s: قالب غير صالح" #: Mailman/Cgi/edithtml.py:166 Mailman/Cgi/edithtml.py:167 +#, fuzzy msgid "%(realname)s -- HTML Page Editing" msgstr "%(realname)s -- تحريرصيغة ØµÙØ­Ø© HTML " @@ -2371,10 +2438,12 @@ msgid "HTML successfully updated." msgstr "تم تحديث HTML بنجاح." #: Mailman/Cgi/listinfo.py:89 +#, fuzzy msgid "%(hostname)s Mailing Lists" msgstr "%(hostname)s القوائم البريدية لخادم Ø§Ù„Ø§Ø³ØªØ¶Ø§ÙØ©" #: Mailman/Cgi/listinfo.py:127 +#, fuzzy msgid "" "

                There currently are no publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s." @@ -2383,6 +2452,7 @@ msgstr "" "%(hostname)s." #: Mailman/Cgi/listinfo.py:131 +#, fuzzy msgid "" "

                Below is a listing of all the public mailing lists on\n" " %(hostname)s. Click on a list name to get more information " @@ -2400,6 +2470,7 @@ msgid "right" msgstr "صحيح" #: Mailman/Cgi/listinfo.py:140 +#, fuzzy msgid "" " To visit the general information page for an unadvertised list,\n" " open a URL similar to this one, but with a '/' and the %(adj)s\n" @@ -2461,6 +2532,7 @@ msgstr "عنوان بريد إلكتروني غير نظامي" #: Mailman/Cgi/options.py:183 Mailman/Cgi/options.py:240 #: Mailman/Cgi/options.py:264 +#, fuzzy msgid "No such member: %(safeuser)s." msgstr "لا يوجد مثل هذ العضو: %(safeuser)s" @@ -2509,6 +2581,7 @@ msgid "Note: " msgstr "ملاحظة: " #: Mailman/Cgi/options.py:378 +#, fuzzy msgid "List subscriptions for %(safeuser)s on %(hostname)s" msgstr "اشتراكات القائمة لـ %(safeuser)s على الخادم %(hostname)s" @@ -2536,6 +2609,7 @@ msgid "You are already using that email address" msgstr "أنت تستعمل أصلاً ذلك العنوان البريدي الإلكتروني" #: Mailman/Cgi/options.py:459 +#, fuzzy msgid "" "The new address you requested %(newaddr)s is already a member of the\n" "%(listname)s mailing list, however you have also requested a global change " @@ -2548,6 +2622,7 @@ msgstr "" "تغيير كل قائمة بريدية تحتوي على العنوان %(safeuser)s. " #: Mailman/Cgi/options.py:468 +#, fuzzy msgid "The new address is already a member: %(newaddr)s" msgstr "العنوان الجديد عضو أصلاً: %(newaddr)s" @@ -2556,6 +2631,7 @@ msgid "Addresses may not be blank" msgstr "العناوين يجب أن لا تكون ÙØ§Ø±ØºØ©" #: Mailman/Cgi/options.py:488 +#, fuzzy msgid "A confirmation message has been sent to %(newaddr)s. " msgstr "تم إرسال رسالة تأكيد إلى: %(newaddr)s. " @@ -2568,6 +2644,7 @@ msgid "Illegal email address provided" msgstr "تم التزويد بعنوان غير نظامي" #: Mailman/Cgi/options.py:501 +#, fuzzy msgid "%(newaddr)s is already a member of the list." msgstr "%(newaddr)s عضو أصلاً ÙÙŠ القائمة." @@ -2647,6 +2724,7 @@ msgstr "" "إعلام Ùور اتخاذ منظمي القائمة قرارهم." #: Mailman/Cgi/options.py:623 +#, fuzzy msgid "" "You have been successfully unsubscribed from the\n" " mailing list %(fqdn_listname)s. If you were receiving digest\n" @@ -2732,6 +2810,7 @@ msgid "day" msgstr "يوم" #: Mailman/Cgi/options.py:900 +#, fuzzy msgid "%(days)d %(units)s" msgstr "%(days)d %(units)s" @@ -2744,6 +2823,7 @@ msgid "No topics defined" msgstr "لا يوجد مواضيع Ù…Ø¹Ø±ÙØ©" #: Mailman/Cgi/options.py:940 +#, fuzzy msgid "" "\n" "You are subscribed to this list with the case-preserved address\n" @@ -2753,6 +2833,7 @@ msgstr "" "أنت مشترك ÙÙŠ هذه القائمة بالعنوان المحÙوظ حالة أحرÙÙ‡ %(cpuser)s." #: Mailman/Cgi/options.py:956 +#, fuzzy msgid "%(realname)s list: member options login page" msgstr "ØµÙØ­Ø© الدخول إلى خيارات عضو القائمة %(realname)s " @@ -2761,10 +2842,12 @@ msgid "email address and " msgstr "عنوان إلكتروني Ùˆ " #: Mailman/Cgi/options.py:960 +#, fuzzy msgid "%(realname)s list: member options for user %(safeuser)s" msgstr "خيارات عضو القائمة %(realname)s للمستخدم %(safeuser)s" #: Mailman/Cgi/options.py:986 +#, fuzzy msgid "" "In order to change your membership option, you must\n" " first log in by giving your %(extra)smembership password in the section\n" @@ -2833,6 +2916,7 @@ msgid "" msgstr "<Ù…Ùقود>" #: Mailman/Cgi/options.py:1140 +#, fuzzy msgid "Requested topic is not valid: %(topicname)s" msgstr "موضوع مطلوب غير صحيح: %(topicname)s" @@ -2861,6 +2945,7 @@ msgid "Private archive - \"./\" and \"../\" not allowed in URL." msgstr "" #: Mailman/Cgi/private.py:109 +#, fuzzy msgid "Private Archive Error - %(msg)s" msgstr "خطأ ÙÙŠ الأرشي٠الخاص - %(msg)s" @@ -2898,12 +2983,14 @@ msgid "Mailing list deletion results" msgstr "نتائج إزالة القائمة البريدية" #: Mailman/Cgi/rmlist.py:187 +#, fuzzy msgid "" "You have successfully deleted the mailing list\n" " %(listname)s." msgstr "لقد قمت بنجاح بحذ٠القائمة البريدية %(listname)s." #: Mailman/Cgi/rmlist.py:191 +#, fuzzy msgid "" "There were some problems deleting the mailing list\n" " %(listname)s. Contact your site administrator at " @@ -2915,6 +3002,7 @@ msgstr "" " من أجل Ø§Ù„ØªÙØ§ØµÙŠÙ„." #: Mailman/Cgi/rmlist.py:208 +#, fuzzy msgid "Permanently remove mailing list %(realname)s" msgstr "أزل القائمة %(realname)s بشكل دائم" @@ -2978,6 +3066,7 @@ msgid "Invalid options to CGI script" msgstr "خيارات غير صحيحة لبرنامج CGI" #: Mailman/Cgi/roster.py:118 +#, fuzzy msgid "%(realname)s roster authentication failed." msgstr "ÙØ´Ù„ التحقق من الشخصية للجدول %(realname)s." @@ -3044,6 +3133,7 @@ msgstr "" "تعليمات أخرى." #: Mailman/Cgi/subscribe.py:262 +#, fuzzy msgid "" "The email address you supplied is banned from this\n" " mailing list. If you think this restriction is erroneous, please\n" @@ -3067,6 +3157,7 @@ msgid "" msgstr "اشتراكك غير مسموح بسبب أن العنوان البريدي المعطى غير مأمون." #: Mailman/Cgi/subscribe.py:278 +#, fuzzy msgid "" "Confirmation from your email address is required, to prevent anyone from\n" "subscribing you without permission. Instructions are being sent to you at\n" @@ -3078,6 +3169,7 @@ msgstr "" "اشتراكك لن يبدأ حتى تؤكد اشتراكك." #: Mailman/Cgi/subscribe.py:290 +#, fuzzy msgid "" "Your subscription request was deferred because %(x)s. Your request has " "been\n" @@ -3097,6 +3189,7 @@ msgid "Mailman privacy alert" msgstr "إنذار ميلمان حول الخصوصية" #: Mailman/Cgi/subscribe.py:316 +#, fuzzy msgid "" "An attempt was made to subscribe your address to the mailing list\n" "%(listaddr)s. You are already subscribed to this mailing list.\n" @@ -3132,6 +3225,7 @@ msgid "This list only supports digest delivery." msgstr "هذه القائمة تدعم Ùقط الإرسال على Ø¯ÙØ¹Ø§Øª." #: Mailman/Cgi/subscribe.py:344 +#, fuzzy msgid "You have been successfully subscribed to the %(realname)s mailing list." msgstr "لقد اشتركت بنجاح ÙÙŠ القائمة البريدية %(realname)s." @@ -3258,26 +3352,32 @@ msgid "n/a" msgstr "غير ممكن" #: Mailman/Commands/cmd_info.py:44 +#, fuzzy msgid "List name: %(listname)s" msgstr "اسم القائمة: %(listname)s" #: Mailman/Commands/cmd_info.py:45 +#, fuzzy msgid "Description: %(description)s" msgstr "الشرح: %(description)s" #: Mailman/Commands/cmd_info.py:46 +#, fuzzy msgid "Postings to: %(postaddr)s" msgstr "الإرسالات إلى: %(postaddr)s" #: Mailman/Commands/cmd_info.py:47 +#, fuzzy msgid "List Helpbot: %(requestaddr)s" msgstr "مساعد القائمة الآلي: %(requestaddr)s" #: Mailman/Commands/cmd_info.py:48 +#, fuzzy msgid "List Owners: %(owneraddr)s" msgstr "مالكي القائمة: %(owneraddr)s" #: Mailman/Commands/cmd_info.py:49 +#, fuzzy msgid "More information: %(listurl)s" msgstr "معلومات إضاÙية: %(listurl)s" @@ -3301,18 +3401,22 @@ msgstr "" "هذا.\n" #: Mailman/Commands/cmd_lists.py:44 +#, fuzzy msgid "Public mailing lists at %(hostname)s:" msgstr "القوائم البريدية العمومية على الخادم %(hostname)s:" #: Mailman/Commands/cmd_lists.py:66 +#, fuzzy msgid "%(i)3d. List name: %(realname)s" msgstr "%(i)3d. اسم القائمة: %(realname)s" #: Mailman/Commands/cmd_lists.py:67 +#, fuzzy msgid " Description: %(description)s" msgstr " الشرح: %(description)s" #: Mailman/Commands/cmd_lists.py:68 +#, fuzzy msgid " Requests to: %(requestaddr)s" msgstr " الطلبات إلى: %(requestaddr)s" @@ -3344,12 +3448,14 @@ msgstr "" " ترسل الإجابة دائماً إلى عنوان الاشتراك\n" #: Mailman/Commands/cmd_password.py:51 Mailman/Commands/cmd_password.py:66 +#, fuzzy msgid "Your password is: %(password)s" msgstr "كلمة سرك هي: %(password)s" #: Mailman/Commands/cmd_password.py:57 Mailman/Commands/cmd_password.py:72 #: Mailman/Commands/cmd_password.py:95 Mailman/Commands/cmd_password.py:121 #: Mailman/Commands/cmd_set.py:149 Mailman/Commands/cmd_set.py:219 +#, fuzzy msgid "You are not a member of the %(listname)s mailing list" msgstr "أنت لست مشتركاً ÙÙŠ القائمة البريدية %(listname)s " @@ -3527,6 +3633,7 @@ msgstr "" "لكلمة السر لهذه القائمة.\n" #: Mailman/Commands/cmd_set.py:122 +#, fuzzy msgid "Bad set command: %(subcmd)s" msgstr "أمر set غير جيد: %(subcmd)s" @@ -3547,6 +3654,7 @@ msgid "on" msgstr "ممكن" #: Mailman/Commands/cmd_set.py:154 +#, fuzzy msgid " ack %(onoff)s" msgstr " إعلام %(onoff)s" @@ -3584,22 +3692,27 @@ msgid "due to bounces" msgstr "بسبب رد Ø±ÙØ¶" #: Mailman/Commands/cmd_set.py:186 +#, fuzzy msgid " %(status)s (%(how)s on %(date)s)" msgstr " %(status)s (%(how)s ÙÙŠ %(date)s)" #: Mailman/Commands/cmd_set.py:192 +#, fuzzy msgid " myposts %(onoff)s" msgstr " إرسالاتي %(onoff)s" #: Mailman/Commands/cmd_set.py:195 +#, fuzzy msgid " hide %(onoff)s" msgstr " Ø¥Ø®ÙØ§Ø¡ %(onoff)s" #: Mailman/Commands/cmd_set.py:199 +#, fuzzy msgid " duplicates %(onoff)s" msgstr " مكررات %(onoff)s" #: Mailman/Commands/cmd_set.py:203 +#, fuzzy msgid " reminders %(onoff)s" msgstr " مذكر %(onoff)s" @@ -3608,6 +3721,7 @@ msgid "You did not give the correct password" msgstr "لم تقم بإعطاء كلمة السر الصحيحة" #: Mailman/Commands/cmd_set.py:236 Mailman/Commands/cmd_set.py:283 +#, fuzzy msgid "Bad argument: %(arg)s" msgstr "معامل أمر غير جيد: %(arg)s" @@ -3682,6 +3796,7 @@ msgstr "" " أن تحدد Ø¨Ø¥Ø¶Ø§ÙØ© `address=

                ' (بدون أقواس ولا علامات اقتباس)\n" #: Mailman/Commands/cmd_subscribe.py:62 +#, fuzzy msgid "Bad digest specifier: %(arg)s" msgstr "معامل Ø¯ÙØ¹Ø§Øª غير جيد: %(arg)s" @@ -3690,6 +3805,7 @@ msgid "No valid address found to subscribe" msgstr "لم يتم العثور على عنوان صحيح للاشتراك" #: Mailman/Commands/cmd_subscribe.py:113 +#, fuzzy msgid "" "The email address you supplied is banned from this mailing list.\n" "If you think this restriction is erroneous, please contact the list\n" @@ -3726,6 +3842,7 @@ msgid "This list only supports digest subscriptions!" msgstr "هذه القائمة تدعم Ùقط اشتراكات Ø¯ÙØ¹Ø§Øª الرسائل!" #: Mailman/Commands/cmd_subscribe.py:146 +#, fuzzy msgid "" "Your subscription request has been forwarded to the list administrator\n" "at %(listowner)s for review." @@ -3759,6 +3876,7 @@ msgstr "" " `address=
                ' (بدون أقواس ولا علامات اقتباس)\n" #: Mailman/Commands/cmd_unsubscribe.py:62 +#, fuzzy msgid "%(address)s is not a member of the %(listname)s mailing list" msgstr "العنوان %(address)s ليس عضواً ÙÙŠ القائمة البريدية %(listname)s" @@ -4003,6 +4121,7 @@ msgid "Chinese (Taiwan)" msgstr "الصينية (التايوانية)" #: Mailman/Deliverer.py:53 +#, fuzzy msgid "" "Note: Since this is a list of mailing lists, administrative\n" "notices like the password reminder will be sent to\n" @@ -4016,14 +4135,17 @@ msgid " (Digest mode)" msgstr "(وضع Ø§Ù„Ø¯ÙØ¹Ø§Øª)" #: Mailman/Deliverer.py:79 +#, fuzzy msgid "Welcome to the \"%(realname)s\" mailing list%(digmode)s" msgstr "أهلاً ÙÙŠ القائمة البريدية \"%(realname)s\" %(digmode)s" #: Mailman/Deliverer.py:89 +#, fuzzy msgid "You have been unsubscribed from the %(realname)s mailing list" msgstr "تم إلغاء اشتراكك ÙÙŠ القائمة البريدية %(realname)s" #: Mailman/Deliverer.py:116 +#, fuzzy msgid "%(listfullname)s mailing list reminder" msgstr "مذكر القائمة البريدية %(listfullname)s" @@ -4036,6 +4158,7 @@ msgid "Hostile subscription attempt detected" msgstr "تم كش٠محاولة اشتراك خبيثة" #: Mailman/Deliverer.py:169 +#, fuzzy msgid "" "%(address)s was invited to a different mailing\n" "list, but in a deliberate malicious attempt they tried to confirm the\n" @@ -4047,6 +4170,7 @@ msgstr "" "قد تحب أن تعرÙ. لا يوجد شيء مطلوب منك أن ØªÙØ¹Ù„Ù‡ بعد." #: Mailman/Deliverer.py:188 +#, fuzzy msgid "" "You invited %(address)s to your list, but in a\n" "deliberate malicious attempt, they tried to confirm the invitation to a\n" @@ -4059,6 +4183,7 @@ msgstr "" "يوجد عمل مطلوب منك أن تعمله بعد." #: Mailman/Deliverer.py:221 +#, fuzzy msgid "%(listname)s mailing list probe message" msgstr "رسالة جس النبض للقائمة البريدية %(listname)s" @@ -4253,8 +4378,8 @@ msgid "" " membership.\n" "\n" "

                You can control both the\n" -" number\n" +" number\n" " of reminders the member will receive and the\n" " No those messages too will get discarded. You may " "want\n" " to set up an\n" -" autoresponse\n" +" autoresponse\n" " message for email to the -owner and -admin address." msgstr "" "مع أن مكتش٠ردود Ø§Ù„Ø±ÙØ¶ الخاص بـ ميلمان قوي بشكل مقبول، إلا أنه من المستحيل " @@ -4501,6 +4626,7 @@ msgstr "" "كثيرة. سو٠يتم عمل محاولة تنبيه للمشترك دائماً." #: Mailman/Gui/Bounce.py:194 +#, fuzzy msgid "" "Bad value for %(property)s: %(val)s" @@ -4611,8 +4737,9 @@ msgstr "" "image/gif. اترك النوع Ø§Ù„ÙØ±Ø¹ÙŠ Ù„Ø¥Ø²Ø§Ù„Ø© جميع الأقسام التي تطابق نوع " "المحتويات الرئيسي، مثل image.\n" "

                سيتم تجاهل الأسطر Ø§Ù„ÙØ§Ø±ØºØ©.\n" -"

                أنظر أيضاً pass_mime_types من أجل الحصول على القائمة البيضاء لأنواع المحتويات." +"

                أنظر أيضاً pass_mime_types من أجل الحصول على القائمة البيضاء " +"لأنواع المحتويات." #: Mailman/Gui/ContentFilter.py:94 msgid "" @@ -4628,8 +4755,8 @@ msgid "" "Use this option to remove each message attachment that does\n" " not have a matching content type. Requirements and formats " "are\n" -" exactly like filter_mime_types.\n" "\n" "

                Note: if you add entries to this list but don't add\n" @@ -4731,6 +4858,7 @@ msgstr "" "مشر٠الموقع." #: Mailman/Gui/ContentFilter.py:171 +#, fuzzy msgid "Bad MIME type ignored: %(spectype)s" msgstr "نوع محتويات سيئ متجاهل: %(spectype)s" @@ -4829,6 +4957,7 @@ msgid "" msgstr "هل على ميلمان أن يرسل Ø§Ù„Ø¯ÙØ¹Ø© التالية الآن إذا لم تكن ÙØ§Ø±ØºØ©ØŸ" #: Mailman/Gui/Digest.py:145 +#, fuzzy msgid "" "The next digest will be sent as volume\n" " %(volume)s, number %(number)s" @@ -4843,14 +4972,17 @@ msgid "There was no digest to send." msgstr "لم يرسل أي Ø¯ÙØ¹Ø©." #: Mailman/Gui/GUIBase.py:173 +#, fuzzy msgid "Invalid value for variable: %(property)s" msgstr "قيمة غير صالحة للمتحول: %(property)s" #: Mailman/Gui/GUIBase.py:178 +#, fuzzy msgid "Bad email address for option %(property)s: %(error)s" msgstr "عنوان إلكتروني سيء للخيار%(property)s : %(error)s" #: Mailman/Gui/GUIBase.py:204 +#, fuzzy msgid "" "The following illegal substitution variables were\n" " found in the %(property)s string:\n" @@ -4859,13 +4991,14 @@ msgid "" "this\n" " problem." msgstr "" -"تم إيجاد متغيرات التبديل غير النظامية التالية ÙÙŠ مجموعة المحار٠" -"%(property)s:\n" +"تم إيجاد متغيرات التبديل غير النظامية التالية ÙÙŠ مجموعة المحار٠" +"%(property)s:\n" " %(bad)s\n" "

                قد لا تعمل قائمتك بشكل صحيح إلى أن تقوم بتصحيح هذه " "المشلكة." #: Mailman/Gui/GUIBase.py:218 +#, fuzzy msgid "" "Your %(property)s string appeared to\n" " have some correctable problems in its new value.\n" @@ -4960,8 +5093,8 @@ msgid "" "

                In order to split the list ownership duties into\n" " administrators and moderators, you must\n" " set a separate moderator password,\n" -" and also provide the email\n" +" and also provide the email\n" " addresses of the list moderators. Note that the field you\n" " are changing here specifies the list administrators." msgstr "" @@ -4974,9 +5107,9 @@ msgstr "" "Ùˆ تدبير الإرسالات المعلقة. بالطبع ÙØ¥Ù† مشرÙÙŠ القائمة يمكنهم أيضاً أن " "يأخذوا على عاتقهم الطلبات المعلقة.\n" "

                ومن أجل تقسيم واجبات ملكية القائمة على مشرÙين ومنظمين ÙØ¹Ù„يك " -"وضع كلمة سر Ù…Ù†ÙØµÙ„Ø© للمنظمين, وتزود أيضاً بـ العناوين البريدية لمنظمي القائمة. لاحظ " -"أن الحقل الذي يتغير هنا يحدد مشرÙÙŠ القائمة." +"وضع كلمة سر Ù…Ù†ÙØµÙ„Ø© للمنظمين, وتزود أيضاً بـ العناوين البريدية لمنظمي القائمة. " +"لاحظ أن الحقل الذي يتغير هنا يحدد مشرÙÙŠ القائمة." #: Mailman/Gui/General.py:102 msgid "" @@ -5019,9 +5152,9 @@ msgstr "" "Ùˆ تدبير الإرسالات المعلقة. بالطبع ÙØ¥Ù† مشرÙÙŠ القائمة يمكنهم أيضاً أن " "يأخذوا على عاتقهم الطلبات المعلقة.\n" "

                ومن أجل تقسيم واجبات ملكية القائمة على مشرÙين ومنظمين ÙØ¹Ù„يك " -"وضع كلمة سر Ù…Ù†ÙØµÙ„Ø© للمنظمين, وتزود أيضاً بـ العناوين البريدية لمنظمي القائمة. لاحظ " -"أن الحقل الذي يتغير هنا يحدد مشرÙÙŠ القائمة." +"وضع كلمة سر Ù…Ù†ÙØµÙ„Ø© للمنظمين, وتزود أيضاً بـ العناوين البريدية لمنظمي القائمة. " +"لاحظ أن الحقل الذي يتغير هنا يحدد مشرÙÙŠ القائمة." #: Mailman/Gui/General.py:126 msgid "A terse phrase identifying this list." @@ -5258,13 +5391,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5294,8 +5427,8 @@ msgstr "" "أن بعض المرسلين يعتمد على ترويسة Reply-To: خاصة بهم من أجل إيضاح " "عنوان الإرجاع الصحيح. والسبب الآخر هو أن تعديل Reply-To: يجعل " "إرسال ردود الشخصية بين المشتركين أصعب كثيراً. انظر إلى `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful من أجل نقاش عام حول القضية. انظر " "إلى Reply-" @@ -5317,8 +5450,8 @@ msgstr "ترويسة Reply-To: مصرحة." msgid "" "This is the address set in the Reply-To: header\n" " when the reply_goes_to_list\n" +" href=\"?VARHELP=general/" +"reply_goes_to_list\">reply_goes_to_list\n" " option is set to Explicit address.\n" "\n" "

                There are many reasons not to introduce or override the\n" @@ -5326,13 +5459,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5354,8 +5487,9 @@ msgid "" " Reply-To: header, it will not be changed." msgstr "" "هذا هو العنوان الذي سيوضع ÙÙŠ ترويسة Reply-To: عندما يكون الخيار reply_goes_to_list قد ضبط على القيمة عنوان مصرح. " +" href=\"?VARHELP=general/" +"reply_goes_to_list\">reply_goes_to_list قد ضبط على القيمة عنوان " +"مصرح. " #: Mailman/Gui/General.py:305 msgid "Umbrella list settings" @@ -5401,8 +5535,8 @@ msgid "" " member list addresses, but rather to the owner of those member\n" " lists. In that case, the value of this setting is appended to\n" " the member's account name for such notices. `-owner' is the\n" -" typical choice. This setting has no effect when \"umbrella_list" -"\"\n" +" typical choice. This setting has no effect when " +"\"umbrella_list\"\n" " is \"No\"." msgstr "" "عندما تضبط \"umbrella_list\" لتشير إلى أن هذه القائمة لها قوائم بريدية أخرى " @@ -5677,8 +5811,8 @@ msgid "" " does not affect the inclusion of the other List-*:\n" " headers.)" msgstr "" -"إن ترويسة List-Post: هي إحدى الترويسات المنصوح بها من قبل RFC 2369.\n" +"إن ترويسة List-Post: هي إحدى الترويسات المنصوح بها من قبل RFC 2369.\n" " وبكل حال للقوائم البريدية الإعلانية ÙقطÙقط مجموعة " "انتقائية صغيرة من الناس يسمح لهم أن يرسلوا إلى القائمة، العضوية العامة غير " "مسموح لها بالإرسال بشكل اعتيادي. القوائم من هذه الطبيعة تكون الترويسة " @@ -6268,6 +6402,7 @@ msgstr "" "مواÙقتهم." #: Mailman/Gui/Privacy.py:104 +#, fuzzy msgid "" "This section allows you to configure subscription and\n" " membership exposure policy. You can also control whether this\n" @@ -6276,8 +6411,8 @@ msgid "" " separate archive-related privacy settings." msgstr "" "يسمح لك هذا القسم أن تضبط سياسة إظهار الاشتراك والعضوية. ويمكنك أن تتحكم " -"بكون هذه القائمة للعموم أم لا. انظر أيضاً إلى قسم خيارات أرشيÙية من أجل خيارات خصوصية متعلقة Ø¨Ø§Ù„Ø£Ø±Ø´ÙØ©." +"بكون هذه القائمة للعموم أم لا. انظر أيضاً إلى قسم خيارات أرشيÙية من أجل خيارات خصوصية متعلقة Ø¨Ø§Ù„Ø£Ø±Ø´ÙØ©." #: Mailman/Gui/Privacy.py:110 msgid "Subscribing" @@ -6445,8 +6580,8 @@ msgid "" " either individually or as a group. Any\n" " posting from a non-member who is not explicitly accepted,\n" " rejected, or discarded, will have their posting filtered by the\n" -" general\n" +" general\n" " non-member rules.\n" "\n" "

                In the text boxes below, add one address per line; start the\n" @@ -6469,8 +6604,8 @@ msgstr "" "

                يمكن أن يتم تلقائياً قبول إرساليات الغير أعضاء,\n" -" أو تعليق للتنظيم,\n" +" أو تعليق للتنظيم,\n" " أو Ø±ÙØ¶ (مع رد), أو\n" " moderation flag which says\n" " whether messages from the list member can be posted directly " @@ -6543,8 +6679,8 @@ msgid "" "If a member posts this many times, within a period of time\n" " the member is automatically moderated. Use 0 to disable. " "See\n" -" member_verbosity_interval for details on the time " "period.\n" "\n" @@ -6952,8 +7088,8 @@ msgstr "" "عندما يتم استلام إرسال من غير أعضاء Ùيتم مقارنة مرسل الرسالة بقائمة عناوين " "محددة للقبول,\n" -" والتعليق,\n" +" والتعليق,\n" " ÙˆØ§Ù„Ø±ÙØ¶ (رد Ø±ÙØ¶), \n" " The body of the message can also be optionally scanned for\n" " Subject: and Keywords: headers, as\n" " specified by the topics_bodylines_limit\n" +" href=\"?VARHELP=topics/" +"topics_bodylines_limit\">topics_bodylines_limit\n" " configuration variable." msgstr "" "يصن٠مصÙÙŠ الموضوع كل رسالة بريد قائمة حسب ويمكن اختيارياً ØªÙØ­Øµ نص الرسالة أيضاً من للبحث عن " "ترويسات Subject: Ùˆ Keywords: كما هو " "محدد ÙÙŠ متحول الضبط topics_bodylines_limit" +" href=\"?VARHELP=topics/" +"topics_bodylines_limit\">topics_bodylines_limit" #: Mailman/Gui/Topics.py:72 msgid "How many body lines should the topic matcher scan?" @@ -7317,6 +7455,7 @@ msgstr "" "تحديد الموضوع يتطلب اسماً ووحدة متكررة. سيتم تجاهل العناوين الغير مكتملة." #: Mailman/Gui/Topics.py:135 +#, fuzzy msgid "" "The topic pattern '%(safepattern)s' is not a\n" " legal regular expression. It will be discarded." @@ -7502,8 +7641,8 @@ msgid "" " the linked\n" " newsgroup fields are filled in." msgstr "" -"لا تستطيع أن تمكن النقل حتى تقوم بتعبيئة حقل خادم الأخبار Ùˆ\n" +"لا تستطيع أن تمكن النقل حتى تقوم بتعبيئة حقل خادم الأخبار Ùˆ\n" " حقل المجموعة " "المربوطة." @@ -7512,6 +7651,7 @@ msgid "%(listinfo_link)s list run by %(owner_link)s" msgstr "القائمة %(listinfo_link)s مشغلة من قبل %(owner_link)s" #: Mailman/HTMLFormatter.py:57 +#, fuzzy msgid "%(realname)s administrative interface" msgstr "واجهة الإدارة للقائمة %(realname)s" @@ -7520,6 +7660,7 @@ msgid " (requires authorization)" msgstr "(مطلوب التحقق من الهوية)" #: Mailman/HTMLFormatter.py:61 +#, fuzzy msgid "Overview of all %(hostname)s mailing lists" msgstr "عرض عام لجميع القوائم البريدية للموقع %(hostname)s " @@ -7540,6 +7681,7 @@ msgid "; it was disabled by the list administrator" msgstr " وتم التعطيل من قبل مدير القائمة" #: Mailman/HTMLFormatter.py:146 +#, fuzzy msgid "" "; it was disabled due to excessive bounces. The\n" " last bounce was received on %(date)s" @@ -7551,6 +7693,7 @@ msgid "; it was disabled for unknown reasons" msgstr " وتم التعطيل لأسباب غير Ù…Ø¹Ø±ÙˆÙØ©" #: Mailman/HTMLFormatter.py:151 +#, fuzzy msgid "Note: your list delivery is currently disabled%(reason)s." msgstr "ملاحظة: توصيل قائمتك معطل الآن %(reason)s." @@ -7563,6 +7706,7 @@ msgid "the list administrator" msgstr "مدير القائمة" #: Mailman/HTMLFormatter.py:157 +#, fuzzy msgid "" "

                %(note)s\n" "\n" @@ -7580,6 +7724,7 @@ msgstr "" "تحتاج للمساعدة." #: Mailman/HTMLFormatter.py:169 +#, fuzzy msgid "" "

                We have received some recent bounces from your\n" " address. Your current bounce score is %(score)s out of " @@ -7596,6 +7741,7 @@ msgstr "" "تصÙير معدل الرد Ø§Ù„Ø±Ø§ÙØ¶ الخاص بك تلقائياً إن تم تصحيح المشاكل قريباً." #: Mailman/HTMLFormatter.py:181 +#, fuzzy msgid "" "(Note - you are subscribing to a list of mailing lists, so the %(type)s " "notice will be sent to the admin address for your membership, %(addr)s.)

                " @@ -7638,6 +7784,7 @@ msgstr "" "القائمة. سيتم تنبيهك لقرار منظم القائمة بالبريد." #: Mailman/HTMLFormatter.py:208 +#, fuzzy msgid "" "This is %(also)sa private list, which means that the\n" " list of members is not available to non-members." @@ -7646,6 +7793,7 @@ msgstr "" "لغير الأعضاء." #: Mailman/HTMLFormatter.py:211 +#, fuzzy msgid "" "This is %(also)sa hidden list, which means that the\n" " list of members is available only to the list administrator." @@ -7653,6 +7801,7 @@ msgstr "" "هذه %(also)s قائمة مخÙية، مما يعني أن قائمة الأعضاء Ù…ØªÙˆÙØ±Ø© Ùقط لمدير القائمة." #: Mailman/HTMLFormatter.py:214 +#, fuzzy msgid "" "This is %(also)sa public list, which means that the\n" " list of members list is available to everyone." @@ -7667,6 +7816,7 @@ msgstr "" "الغير مرغوب Ùيها)." #: Mailman/HTMLFormatter.py:222 +#, fuzzy msgid "" "

                (Note that this is an umbrella list, intended to\n" " have only other mailing lists as members. Among other things,\n" @@ -7682,6 +7832,7 @@ msgid "either " msgstr "إما " #: Mailman/HTMLFormatter.py:256 +#, fuzzy msgid "" "To unsubscribe from %(realname)s, get a password reminder,\n" " or change your subscription options %(either)senter your " @@ -7710,12 +7861,14 @@ msgid "" msgstr "إذا تركت هذا الحقل ÙØ§Ø±ØºØ§Ù‹ ÙØ³ÙˆÙ يطلب منك عنوانك الإلكتروني" #: Mailman/HTMLFormatter.py:277 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " members.)" msgstr "(%(which)s هي Ù…ØªÙˆÙØ±Ø© Ùقط لأعضاء القائمة.)" #: Mailman/HTMLFormatter.py:281 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " administrator.)" @@ -7774,6 +7927,7 @@ msgid "The current archive" msgstr "الأرشي٠الحالي" #: Mailman/Handlers/Acknowledge.py:59 +#, fuzzy msgid "%(realname)s post acknowledgement" msgstr "إعلام وصول الإرسال إلى %(realname)s" @@ -7786,6 +7940,7 @@ msgid "" msgstr "" #: Mailman/Handlers/CalcRecips.py:79 +#, fuzzy msgid "" "Your urgent message to the %(realname)s mailing list was not authorized for\n" "delivery. The original message as received by Mailman is attached.\n" @@ -7860,6 +8015,7 @@ msgid "Message may contain administrivia" msgstr "قد تحوي على تعليمات إدارية" #: Mailman/Handlers/Hold.py:84 +#, fuzzy msgid "" "Please do *not* post administrative requests to the mailing\n" "list. If you wish to subscribe, visit %(listurl)s or send a message with " @@ -7896,10 +8052,12 @@ msgid "Posting to a moderated newsgroup" msgstr "إرسال إلى قائمة إخبارية منظمة" #: Mailman/Handlers/Hold.py:252 +#, fuzzy msgid "Your message to %(listname)s awaits moderator approval" msgstr "رسالتك إلى القائمة البريدية %(listname)s تنتظر مواÙقة المنظم" #: Mailman/Handlers/Hold.py:271 +#, fuzzy msgid "%(listname)s post from %(sender)s requires approval" msgstr "إرسال إلى القائمة %(listname)s من قبل %(sender)s تحتاج للمواÙقة" @@ -7940,6 +8098,7 @@ msgid "After content filtering, the message was empty" msgstr "بعد تصÙية المحتويات صارت الرسالة ÙØ§Ø±ØºØ©" #: Mailman/Handlers/MimeDel.py:269 +#, fuzzy msgid "" "The attached message matched the %(listname)s mailing list's content " "filtering\n" @@ -7981,6 +8140,7 @@ msgid "The attached message has been automatically discarded." msgstr "تم إهمال الرسالة المرÙقة تلقائياً." #: Mailman/Handlers/Replybot.py:75 +#, fuzzy msgid "Auto-response for your message to the \"%(realname)s\" mailing list" msgstr "رد تلقائي لرسالتك المرسلة إلى القائم البريدية \"%(realname)s\"" @@ -8004,6 +8164,7 @@ msgid "HTML attachment scrubbed and removed" msgstr "ÙØµÙ„ المرÙÙ‚ من نوع HTML وأزيل" #: Mailman/Handlers/Scrubber.py:234 Mailman/Handlers/Scrubber.py:259 +#, fuzzy msgid "" "An HTML attachment was scrubbed...\n" "URL: %(url)s\n" @@ -8056,6 +8217,7 @@ msgstr "" "الرابط : %(url)s\n" #: Mailman/Handlers/Scrubber.py:347 +#, fuzzy msgid "Skipped content of type %(partctype)s\n" msgstr "أنواع المحتويات المتروكة %(partctype)s\n" @@ -8085,6 +8247,7 @@ msgid "Message rejected by filter rule match" msgstr "Ø±ÙØ¶Øª الرسالة بسبب تطابق قاعدة مصÙÙŠ" #: Mailman/Handlers/ToDigest.py:173 +#, fuzzy msgid "%(realname)s Digest, Vol %(volume)d, Issue %(issue)d" msgstr "%(realname)s Ø¯ÙØ¹Ø§Øª, الجزء %(volume)d, الإصدار %(issue)d" @@ -8121,6 +8284,7 @@ msgid "End of " msgstr "نهاية " #: Mailman/ListAdmin.py:309 +#, fuzzy msgid "Posting of your message titled \"%(subject)s\"" msgstr "إرسال رسالتك المعنونة \"%(subject)s\"" @@ -8133,6 +8297,7 @@ msgid "Forward of moderated message" msgstr "تحويل لرسالة منظمة" #: Mailman/ListAdmin.py:405 +#, fuzzy msgid "New subscription request to list %(realname)s from %(addr)s" msgstr "طلب اشتراك جديد ÙÙŠ القائمة %(realname)s من العنوان %(addr)s" @@ -8146,6 +8311,7 @@ msgid "via admin approval" msgstr "استمر ÙÙŠ انتظار القبول" #: Mailman/ListAdmin.py:466 +#, fuzzy msgid "New unsubscription request from %(realname)s by %(addr)s" msgstr "طلب إلغاء اشتراك من القائمة %(realname)s من قبل %(addr)s" @@ -8158,10 +8324,12 @@ msgid "Original Message" msgstr "الرسالة الأصلية" #: Mailman/ListAdmin.py:526 +#, fuzzy msgid "Request to mailing list %(realname)s rejected" msgstr "تم Ø±ÙØ¶ طلب إلى القائمة البريدية %(realname)s" #: Mailman/MTA/Manual.py:66 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been created via the through-the-web\n" "interface. In order to complete the activation of this mailing list, the\n" @@ -8185,14 +8353,17 @@ msgstr "" "الأسطر التالية، وكذلك تشغيل البرنامج `newaliases':\n" #: Mailman/MTA/Manual.py:82 +#, fuzzy msgid "## %(listname)s mailing list" msgstr "## %(listname)s mailing list" #: Mailman/MTA/Manual.py:99 +#, fuzzy msgid "Mailing list creation request for list %(listname)s" msgstr "طلب إنشاء للقائمة %(listname)s" #: Mailman/MTA/Manual.py:113 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been removed via the through-the-web\n" "interface. In order to complete the de-activation of this mailing list, " @@ -8208,6 +8379,7 @@ msgstr "" "يجب أن تحذ٠من المل٠/etc/aliases:\n" #: Mailman/MTA/Manual.py:123 +#, fuzzy msgid "" "\n" "To finish removing your mailing list, you must edit your /etc/aliases (or\n" @@ -8223,14 +8395,17 @@ msgstr "" "## %(listname)s mailing list" #: Mailman/MTA/Manual.py:142 +#, fuzzy msgid "Mailing list removal request for list %(listname)s" msgstr "طلب إزالة القائمة البريدية %(listname)s" #: Mailman/MTA/Postfix.py:442 +#, fuzzy msgid "checking permissions on %(file)s" msgstr "ØªÙØ­Øµ أذونات المل٠%(file)s" #: Mailman/MTA/Postfix.py:452 +#, fuzzy msgid "%(file)s permissions must be 0664 (got %(octmode)s)" msgstr "أذونات المل٠%(file)s يجب أن تكون 0664 (وهي الآن %(octmode)s)" @@ -8244,35 +8419,43 @@ msgid "(fixing)" msgstr "إصلاح" #: Mailman/MTA/Postfix.py:470 +#, fuzzy msgid "checking ownership of %(dbfile)s" msgstr "التحقق من ملكية المل٠%(dbfile)s" #: Mailman/MTA/Postfix.py:478 +#, fuzzy msgid "%(dbfile)s owned by %(owner)s (must be owned by %(user)s" msgstr "" "المل٠%(dbfile)s مملوك من قبل %(owner)s (ويجب أن يكون مملوكاً من قبل %(user)s" #: Mailman/MTA/Postfix.py:491 +#, fuzzy msgid "%(dbfile)s permissions must be 0664 (got %(octmode)s)" msgstr "أذونات المل٠%(dbfile)s يجب أن تكون 0664 (وهي الآن %(octmode)s)" #: Mailman/MailList.py:219 +#, fuzzy msgid "Your confirmation is required to join the %(listname)s mailing list" msgstr "تأكيدك مطلوب للانضمام إلى القائمة البريدية %(listname)s ." #: Mailman/MailList.py:230 +#, fuzzy msgid "Your confirmation is required to leave the %(listname)s mailing list" msgstr "تأكيدك مطلوب لترك القائمة البريدية %(listname)s ." #: Mailman/MailList.py:999 Mailman/MailList.py:1492 +#, fuzzy msgid " from %(remote)s" msgstr "من قبل %(remote)s" #: Mailman/MailList.py:1043 +#, fuzzy msgid "subscriptions to %(realname)s require moderator approval" msgstr "تحتاج الاشتراكات ÙÙŠ %(realname)s مواÙقة المنظم" #: Mailman/MailList.py:1125 bin/add_members:299 +#, fuzzy msgid "%(realname)s subscription notification" msgstr "تنبيه اشتراك %(realname)s" @@ -8281,6 +8464,7 @@ msgid "unsubscriptions require moderator approval" msgstr "يحتاج إلغاء الاشتراك إلى مواÙقة المدير" #: Mailman/MailList.py:1166 +#, fuzzy msgid "%(realname)s unsubscribe notification" msgstr "تنبيه إلغاء اشتراك %(realname)s" @@ -8300,6 +8484,7 @@ msgid "via web confirmation" msgstr "مجموعة محار٠تأكيد سيئة" #: Mailman/MailList.py:1396 +#, fuzzy msgid "subscriptions to %(name)s require administrator approval" msgstr "تحتاج الاشتراكات ÙÙŠ %(name)s إلى مواÙقة المدير" @@ -8318,6 +8503,7 @@ msgid "Last autoresponse notification for today" msgstr "آخر تنبيه رد تلقائي لهذا اليوم" #: Mailman/Queue/BounceRunner.py:360 +#, fuzzy msgid "" "The attached message was received as a bounce, but either the bounce format\n" "was not recognized, or no member addresses could be extracted from it. " @@ -8402,6 +8588,7 @@ msgid "Original message suppressed by Mailman site configuration\n" msgstr "" #: Mailman/htmlformat.py:675 +#, fuzzy msgid "Delivered by Mailman
                version %(version)s" msgstr "تم توصيلها من قبل برنامج ميلمان
                الاصدار %(version)s" @@ -8490,6 +8677,7 @@ msgid "Server Local Time" msgstr "الوقت المحلي للخادم" #: Mailman/i18n.py:180 +#, fuzzy msgid "" "%(wday)s %(mon)s %(day)2i %(hh)02i:%(mm)02i:%(ss)02i %(tzname)s %(year)04i" msgstr "" @@ -8601,6 +8789,7 @@ msgstr "" "files can be `-'.\n" #: bin/add_members:162 bin/add_members:172 +#, fuzzy msgid "Already a member: %(member)s" msgstr "عضو مسجل أصلاً: %(member)s" @@ -8609,10 +8798,12 @@ msgid "Bad/Invalid email address: blank line" msgstr "عنوان إلكتروني سيء/غير صالح: سطر ÙØ§Ø±Øº" #: bin/add_members:180 +#, fuzzy msgid "Bad/Invalid email address: %(member)s" msgstr "عنوان إلكتروني سيء/غير صالح: %(member)s" #: bin/add_members:182 +#, fuzzy msgid "Hostile address (illegal characters): %(member)s" msgstr "عنوان إلكتروني عدائي )أحر٠غير صالحة(: %(member)s" @@ -8622,16 +8813,19 @@ msgid "Invited: %(member)s" msgstr "تم تسجيله: %(member)s" #: bin/add_members:187 +#, fuzzy msgid "Subscribed: %(member)s" msgstr "تم تسجيله: %(member)s" #: bin/add_members:237 +#, fuzzy msgid "Bad argument to -w/--welcome-msg: %(arg)s" msgstr "معاملات سيئة لـ -w/--welcome-msg : %(arg)s" #: bin/add_members:244 +#, fuzzy msgid "Bad argument to -a/--admin-notify: %(arg)s" -msgstr "" +msgstr "معاملات سيئة لـ -w/--welcome-msg : %(arg)s" #: bin/add_members:252 msgid "Cannot read both digest and normal members from standard input." @@ -8644,8 +8838,9 @@ msgstr "" #: bin/add_members:261 bin/config_list:110 bin/export.py:271 bin/find_member:97 #: bin/inject:91 bin/list_admins:90 bin/list_members:252 bin/sync_members:222 #: cron/bumpdigests:86 +#, fuzzy msgid "No such list: %(listname)s" -msgstr "" +msgstr "لا يوجد قائمة بالإسم %(safelistname)s" #: bin/add_members:285 bin/change_pw:159 bin/check_db:114 bin/discard:83 #: bin/sync_members:244 bin/update:302 bin/update:323 bin/update:577 @@ -8707,10 +8902,11 @@ msgid "listname is required" msgstr "" #: bin/arch:143 bin/change_pw:107 bin/config_list:257 +#, fuzzy msgid "" "No such list \"%(listname)s\"\n" "%(e)s" -msgstr "" +msgstr "لا يوجد قائمة بالإسم %(safelistname)s" #: bin/arch:168 msgid "Cannot open mbox file %(mbox)s: %(msg)s" @@ -8794,20 +8990,23 @@ msgid "" msgstr "" #: bin/change_pw:145 +#, fuzzy msgid "Bad arguments: %(strargs)s" -msgstr "" +msgstr "معامل أمر غير جيد: %(arg)s" #: bin/change_pw:149 msgid "Empty list passwords are not allowed" msgstr "" #: bin/change_pw:181 +#, fuzzy msgid "New %(listname)s password: %(notifypassword)s" -msgstr "" +msgstr "كلمة السر الابتدائية للقائمة:" #: bin/change_pw:190 +#, fuzzy msgid "Your new %(listname)s list password" -msgstr "" +msgstr "كلمة السر الابتدائية للقائمة:" #: bin/change_pw:191 msgid "" @@ -8885,40 +9084,47 @@ msgid "" msgstr "" #: bin/check_perms:110 +#, fuzzy msgid " checking gid and mode for %(path)s" -msgstr "" +msgstr "ØªÙØ­Øµ أذونات المل٠%(file)s" #: bin/check_perms:122 msgid "%(path)s bad group (has: %(groupname)s, expected %(MAILMAN_GROUP)s)" msgstr "" #: bin/check_perms:151 +#, fuzzy msgid "directory permissions must be %(octperms)s: %(path)s" -msgstr "" +msgstr "أذونات المل٠%(dbfile)s يجب أن تكون 0664 (وهي الآن %(octmode)s)" #: bin/check_perms:160 +#, fuzzy msgid "source perms must be %(octperms)s: %(path)s" -msgstr "" +msgstr "أذونات المل٠%(dbfile)s يجب أن تكون 0664 (وهي الآن %(octmode)s)" #: bin/check_perms:171 +#, fuzzy msgid "article db files must be %(octperms)s: %(path)s" -msgstr "" +msgstr "أذونات المل٠%(dbfile)s يجب أن تكون 0664 (وهي الآن %(octmode)s)" #: bin/check_perms:183 +#, fuzzy msgid "checking mode for %(prefix)s" -msgstr "" +msgstr "ØªÙØ­Øµ أذونات المل٠%(file)s" #: bin/check_perms:193 msgid "WARNING: directory does not exist: %(d)s" msgstr "" #: bin/check_perms:197 +#, fuzzy msgid "directory must be at least 02775: %(d)s" -msgstr "" +msgstr "أذونات المل٠%(file)s يجب أن تكون 0664 (وهي الآن %(octmode)s)" #: bin/check_perms:209 +#, fuzzy msgid "checking perms on %(private)s" -msgstr "" +msgstr "ØªÙØ­Øµ أذونات المل٠%(file)s" #: bin/check_perms:214 msgid "%(private)s must not be other-readable" @@ -8946,40 +9152,46 @@ msgid "checking cgi-bin permissions" msgstr "" #: bin/check_perms:278 +#, fuzzy msgid " checking set-gid for %(path)s" -msgstr "" +msgstr "ØªÙØ­Øµ أذونات المل٠%(file)s" #: bin/check_perms:282 msgid "%(path)s must be set-gid" msgstr "" #: bin/check_perms:292 +#, fuzzy msgid "checking set-gid for %(wrapper)s" -msgstr "" +msgstr "ØªÙØ­Øµ أذونات المل٠%(file)s" #: bin/check_perms:296 msgid "%(wrapper)s must be set-gid" msgstr "" #: bin/check_perms:306 +#, fuzzy msgid "checking permissions on %(pwfile)s" -msgstr "" +msgstr "ØªÙØ­Øµ أذونات المل٠%(file)s" #: bin/check_perms:315 +#, fuzzy msgid "%(pwfile)s permissions must be exactly 0640 (got %(octmode)s)" -msgstr "" +msgstr "أذونات المل٠%(file)s يجب أن تكون 0664 (وهي الآن %(octmode)s)" #: bin/check_perms:340 msgid "checking permissions on list data" msgstr "" #: bin/check_perms:348 +#, fuzzy msgid " checking permissions on: %(path)s" -msgstr "" +msgstr "ØªÙØ­Øµ أذونات المل٠%(file)s" #: bin/check_perms:356 +#, fuzzy msgid "file permissions must be at least 660: %(path)s" -msgstr "" +msgstr "أذونات المل٠%(file)s يجب أن تكون 0664 (وهي الآن %(octmode)s)" #: bin/check_perms:401 msgid "No problems found" @@ -9032,8 +9244,9 @@ msgid "Unix-From line changed: %(lineno)d" msgstr "" #: bin/cleanarch:111 +#, fuzzy msgid "Bad status number: %(arg)s" -msgstr "" +msgstr "معامل أمر غير جيد: %(arg)s" #: bin/cleanarch:167 msgid "%(messages)d messages found" @@ -9130,14 +9343,16 @@ msgid " original address removed:" msgstr "" #: bin/clone_member:202 +#, fuzzy msgid "Not a valid email address: %(toaddr)s" -msgstr "" +msgstr "عنوان إلكتروني سيء/غير صالح: %(member)s" #: bin/clone_member:215 +#, fuzzy msgid "" "Error opening list \"%(listname)s\", skipping.\n" "%(e)s" -msgstr "" +msgstr "قائمتك البريدية الجديدة: %(listname)s" #: bin/config_list:20 msgid "" @@ -9222,12 +9437,14 @@ msgid "Non-standard property restored: %(k)s" msgstr "" #: bin/config_list:288 +#, fuzzy msgid "Invalid value for property: %(k)s" -msgstr "" +msgstr "قيمة غير صالحة للمتحول: %(property)s" #: bin/config_list:291 +#, fuzzy msgid "Bad email address for option %(k)s: %(v)s" -msgstr "" +msgstr "عنوان إلكتروني سيء للخيار%(property)s : %(error)s" #: bin/config_list:348 msgid "Only one of -i or -o is allowed" @@ -9274,16 +9491,19 @@ msgid "" msgstr "" #: bin/discard:94 +#, fuzzy msgid "Ignoring non-held message: %(f)s" -msgstr "" +msgstr "تجاهل التعديلات لعنصر المحذوÙ: %(user)s" #: bin/discard:100 +#, fuzzy msgid "Ignoring held msg w/bad id: %(f)s" -msgstr "" +msgstr "تجاهل التعديلات لعنصر المحذوÙ: %(user)s" #: bin/discard:112 +#, fuzzy msgid "Discarded held msg #%(id)s for list %(listname)s" -msgstr "" +msgstr "اشترك ÙÙŠ القائمة %(listname)s" #: bin/dumpdb:19 msgid "" @@ -9327,8 +9547,9 @@ msgid "No filename given." msgstr "" #: bin/dumpdb:108 +#, fuzzy msgid "Bad arguments: %(pargs)s" -msgstr "" +msgstr "معامل أمر غير جيد: %(arg)s" #: bin/dumpdb:118 msgid "Please specify either -p or -m." @@ -9578,8 +9799,9 @@ msgid "" msgstr "" #: bin/list_admins:97 +#, fuzzy msgid "List: %(listname)s, \tOwners: %(owners)s" -msgstr "" +msgstr "مالكي القائمة: %(owneraddr)s" #: bin/list_lists:19 msgid "" @@ -9684,12 +9906,14 @@ msgid "" msgstr "" #: bin/list_members:198 +#, fuzzy msgid "Bad --nomail option: %(why)s" -msgstr "" +msgstr "معامل Ø¯ÙØ¹Ø§Øª غير جيد: %(arg)s" #: bin/list_members:209 +#, fuzzy msgid "Bad --digest option: %(kind)s" -msgstr "" +msgstr "معامل Ø¯ÙØ¹Ø§Øª غير جيد: %(arg)s" #: bin/list_members:213 bin/list_members:217 bin/list_members:221 #: bin/list_members:225 @@ -9873,8 +10097,9 @@ msgid "" msgstr "" #: bin/mailmanctl:280 cron/mailpasswds:119 +#, fuzzy msgid "Site list is missing: %(sitelistname)s" -msgstr "" +msgstr "القائمة موجودة أصلاً: %(safelistname)s" #: bin/mailmanctl:305 msgid "Run this program as root or as the %(name)s user, or use -u." @@ -9885,8 +10110,9 @@ msgid "No command given." msgstr "" #: bin/mailmanctl:339 +#, fuzzy msgid "Bad command: %(command)s" -msgstr "" +msgstr "أمر set غير جيد: %(subcmd)s" #: bin/mailmanctl:344 msgid "Warning! You may encounter permission problems." @@ -10100,8 +10326,9 @@ msgid "" msgstr "" #: bin/newlist:162 +#, fuzzy msgid "Unknown language: %(lang)s" -msgstr "" +msgstr "قائمتك البريدية الجديدة: %(listname)s" #: bin/newlist:167 msgid "Enter the name of the list: " @@ -10112,8 +10339,9 @@ msgid "Enter the email of the person running the list: " msgstr "" #: bin/newlist:193 +#, fuzzy msgid "Initial %(listname)s password: " -msgstr "" +msgstr "كلمة السر الابتدائية للقائمة:" #: bin/newlist:197 msgid "The list password cannot be empty" @@ -10121,8 +10349,8 @@ msgstr "" #: bin/newlist:220 msgid "" -" - owner addresses need to be fully-qualified names like \"owner@example.com" -"\", not just \"owner\"." +" - owner addresses need to be fully-qualified names like \"owner@example." +"com\", not just \"owner\"." msgstr "" #: bin/newlist:244 @@ -10290,16 +10518,19 @@ msgid "Could not open file for reading: %(filename)s." msgstr "" #: bin/remove_members:163 +#, fuzzy msgid "Error opening list %(listname)s... skipping." -msgstr "" +msgstr "قائمتك البريدية الجديدة: %(listname)s" #: bin/remove_members:173 +#, fuzzy msgid "No such member: %(addr)s" -msgstr "" +msgstr "لا يوجد مثل هذ العضو: %(safeuser)s" #: bin/remove_members:178 +#, fuzzy msgid "User `%(addr)s' removed from list: %(listname)s." -msgstr "" +msgstr "تمت إزالة %(member)s من القائمة %(listname)s.\n" #: bin/reset_pw.py:21 msgid "" @@ -10322,8 +10553,9 @@ msgid "" msgstr "" #: bin/reset_pw.py:77 +#, fuzzy msgid "Changing passwords for list: %(listname)s" -msgstr "" +msgstr "طلب إزالة القائمة البريدية %(listname)s" #: bin/reset_pw.py:83 msgid "New password for member %(member)40s: %(randompw)s" @@ -10360,8 +10592,9 @@ msgid "%(listname)s %(msg)s not found as %(filename)s" msgstr "" #: bin/rmlist:105 +#, fuzzy msgid "No such list (or list already deleted): %(listname)s" -msgstr "" +msgstr "لا يوجد قائمة بالإسم %(safelistname)s" #: bin/rmlist:108 msgid "No such list: %(listname)s. Removing its residual archives." @@ -10495,8 +10728,9 @@ msgid "No argument to -f given" msgstr "" #: bin/sync_members:172 +#, fuzzy msgid "Illegal option: %(opt)s" -msgstr "" +msgstr "اسم قائمة غير نظامي: %(s)s" #: bin/sync_members:178 msgid "No listname given" @@ -10631,8 +10865,9 @@ msgid "" msgstr "" #: bin/update:107 +#, fuzzy msgid "Fixing language templates: %(listname)s" -msgstr "" +msgstr "قائمتك البريدية الجديدة: %(listname)s" #: bin/update:196 bin/update:711 msgid "WARNING: could not acquire lock for list: %(listname)s" @@ -10786,8 +11021,9 @@ msgid "done" msgstr "" #: bin/update:691 +#, fuzzy msgid "Updating mailing list: %(listname)s" -msgstr "" +msgstr "قائمتك البريدية الجديدة: %(listname)s" #: bin/update:694 msgid "Updating Usenet watermarks" @@ -10992,16 +11228,18 @@ msgid "" msgstr "" #: bin/withlist:175 +#, fuzzy msgid "Unlocking (but not saving) list: %(listname)s" -msgstr "" +msgstr "قائمتك البريدية الجديدة: %(listname)s" #: bin/withlist:179 msgid "Finalizing" msgstr "" #: bin/withlist:188 +#, fuzzy msgid "Loading list %(listname)s" -msgstr "" +msgstr "قائمتك البريدية الجديدة: %(listname)s" #: bin/withlist:190 msgid "(locked)" @@ -11012,8 +11250,9 @@ msgid "(unlocked)" msgstr "" #: bin/withlist:197 +#, fuzzy msgid "Unknown list: %(listname)s" -msgstr "" +msgstr "قائمتك البريدية الجديدة: %(listname)s" #: bin/withlist:237 msgid "No list name supplied." @@ -11220,8 +11459,9 @@ msgid "Password // URL" msgstr "" #: cron/mailpasswds:222 +#, fuzzy msgid "%(host)s mailing list memberships reminder" -msgstr "" +msgstr "مذكر القائمة البريدية %(listfullname)s" #: cron/nightly_gzip:19 msgid "" @@ -11497,9 +11737,6 @@ msgstr "" #~ "تم اشتراك %(member)s بنجاح ÙÙŠ القائمة %(listname)s.\n" #~ "\n" -#~ msgid "%(member)s has been removed from %(listname)s.\n" -#~ msgstr "تمت إزالة %(member)s من القائمة %(listname)s.\n" - #~ msgid "" #~ "\n" #~ "\n" @@ -11508,11 +11745,11 @@ msgstr "" #~ "\n" #~ "\n" #~ "%(message)s\n" -#~ " \n" +#~ "
                \n" #~ " \n" -#~ " \n" @@ -11552,11 +11789,11 @@ msgstr "" #~ "\n" #~ "\n" #~ "%(message)s\n" -#~ "
                \n" +#~ " \n" #~ "\t%(listname)s %(who)s\n" #~ "\t Authentication\n" #~ "
                \n" +#~ "
                \n" #~ " \n" -#~ " \n" @@ -11824,8 +12061,8 @@ msgstr "" #~ " %(title)s\n" #~ " \n" #~ " \n" -#~ " \n" +#~ " \n" #~ " \n" #~ " %(encoding)s\n" #~ " %(prev)s\n" @@ -11834,8 +12071,8 @@ msgstr "" #~ " \n" #~ "

                %(subject_html)s

                \n" #~ " %(author_html)s \n" -#~ " %(email_html)s\n" #~ "
                \n" #~ " %(datestr_html)s\n" @@ -11879,8 +12116,8 @@ msgstr "" #~ " %(title)s\n" #~ " \n" #~ " \n" -#~ " \n" +#~ " \n" #~ " \n" #~ " %(encoding)s\n" #~ " %(prev)s\n" @@ -11889,8 +12126,8 @@ msgstr "" #~ " \n" #~ "

                %(subject_html)s

                \n" #~ " %(author_html)s \n" -#~ " %(email_html)s\n" #~ "
                \n" #~ " %(datestr_html)s\n" @@ -12352,11 +12589,11 @@ msgstr "" #~ " \n" #~ "\n" #~ "

                \n" -#~ "

                \n" +#~ " \n" #~ "\tالتحقق من الشخصية لـ %(who)s " #~ "للقائمة %(listname)s\n" #~ "
                \n" +#~ "
                \n" #~ "\t\n" -#~ "\t \n" @@ -12502,11 +12739,11 @@ msgstr "" #~ " \n" #~ "\n" #~ "

                \n" -#~ "

                \n" +#~ "\t \n" #~ "\t --\n" #~ "\t\n" #~ "\t
                \n" +#~ "
                \n" #~ "\t\n" -#~ "\t \n" @@ -12770,8 +13007,8 @@ msgstr "" #~ " \n" #~ "\n" #~ "\n" -#~ "
                \n" +#~ "\t \n" #~ "\t --\n" #~ "\t\n" #~ "\t
                \n" +#~ "
                \n" #~ " \n" #~ " \n" #~ " + +

                -

                \n" #~ " \n" #~ " mailing list membership configuration for\n" @@ -12820,8 +13057,8 @@ msgstr "" #~ "\n" #~ "
                \n" -#~ " \n" +#~ "
                \n" #~ " \n" #~ " \n" @@ -12833,8 +13070,8 @@ msgstr "" #~ "
                New address:
                \n" #~ "
                \n" -#~ " \n" +#~ "
                \n" #~ " \n" #~ " \n" @@ -12874,8 +13111,8 @@ msgstr "" #~ "
                Your name\n" #~ " (optional):
                \n" #~ "\n" #~ "\n" -#~ " \n" #~ "\n" @@ -12896,8 +13133,8 @@ msgstr "" #~ " \n" #~ "
                \n" #~ "

                Change Your Password

                \n" -#~ "
                \n" +#~ "
                \n" #~ " Your Password\n" #~ "
                \n" +#~ "
                \n" #~ " \n" #~ " \n" @@ -13098,8 +13335,8 @@ msgstr "" #~ " \n" #~ "\n" #~ "\n" -#~ "
                New\n" #~ " password:
                \n" +#~ "
                \n" #~ " \n" #~ " \n" #~ "
                \n" #~ " \n" #~ " ضبط القائمة البريدية للمستخدم\n" @@ -13148,8 +13385,8 @@ msgstr "" #~ "\n" #~ "
                \n" -#~ " \n" +#~ "
                \n" #~ " \n" #~ " \n" @@ -13161,10 +13398,10 @@ msgstr "" #~ "
                العنوان الجديد:
                \n" #~ "
                \n" -#~ " \n" -#~ " \n" +#~ "
                اسمك )اختياري(:
                \n" +#~ " \n" #~ " \n" #~ " \n" #~ "
                اسمك )اختياري(:
                \n" @@ -13202,8 +13439,8 @@ msgstr "" #~ "
                \n" #~ "\n" #~ "\n" -#~ " \n" #~ "\n" @@ -13223,8 +13460,8 @@ msgstr "" #~ " \n" #~ "
                \n" #~ "

                غير كلمة سرك

                \n" -#~ "
                \n" +#~ "
                \n" #~ " كلمة سرك للقائمة البريدية \n" #~ "
                \n" +#~ "
                \n" #~ " \n" #~ " \n" @@ -13501,11 +13738,11 @@ msgstr "" #~ "\n" #~ "\n" #~ "%(message)s\n" -#~ "
                كلمة السر " #~ "الجديدة:
                \n" +#~ "
                \n" #~ " \n" -#~ " \n" @@ -13549,11 +13786,11 @@ msgstr "" #~ "\n" #~ "\n" #~ "%(message)s\n" -#~ "
                \n" +#~ " \n" #~ "\t%(realname)s Private\n" #~ "\t Archives Authentication\n" #~ "
                \n" +#~ "
                \n" #~ " \n" -#~ " \n" diff --git a/messages/ast/LC_MESSAGES/mailman.po b/messages/ast/LC_MESSAGES/mailman.po index 6dd54fcd..73e2b5cc 100755 --- a/messages/ast/LC_MESSAGES/mailman.po +++ b/messages/ast/LC_MESSAGES/mailman.po @@ -65,10 +65,12 @@ msgid "

                Currently, there are no archives.

                " msgstr "

                Anguaño nun hai ficheru.

                " #: Mailman/Archiver/HyperArch.py:821 +#, fuzzy msgid "Gzip'd Text%(sz)s" msgstr "Testu Gzip%(sz)s" #: Mailman/Archiver/HyperArch.py:826 +#, fuzzy msgid "Text%(sz)s" msgstr "Testu%(sz)s" @@ -141,18 +143,22 @@ msgid "Third" msgstr "Tercer" #: Mailman/Archiver/HyperArch.py:938 +#, fuzzy msgid "%(ord)s quarter %(year)i" msgstr "%(ord)s cuartu %(year)i" #: Mailman/Archiver/HyperArch.py:945 +#, fuzzy msgid "%(month)s %(year)i" msgstr "%(month)s %(year)i" #: Mailman/Archiver/HyperArch.py:950 +#, fuzzy msgid "The Week Of Monday %(day)i %(month)s %(year)i" msgstr "La selmana del %(day)i %(month)s %(year)i" #: Mailman/Archiver/HyperArch.py:954 +#, fuzzy msgid "%(day)i %(month)s %(year)i" msgstr "%(day)i %(month)s %(year)i" @@ -161,10 +167,12 @@ msgid "Computing threaded index\n" msgstr "Calculando l'índiz de filos\n" #: Mailman/Archiver/HyperArch.py:1319 +#, fuzzy msgid "Updating HTML for article %(seq)s" msgstr "Anovando'l códigu HTML del artículu %(seq)s" #: Mailman/Archiver/HyperArch.py:1326 +#, fuzzy msgid "article file %(filename)s is missing!" msgstr "nun s'alcuentra'l ficheru %(filename)s asociáu al artículu!" @@ -181,6 +189,7 @@ msgid "Pickling archive state into " msgstr "Preparando l'estáu del ficheru a " #: Mailman/Archiver/pipermail.py:453 +#, fuzzy msgid "Updating index files for archive [%(archive)s]" msgstr "Anovando l'índiz de los ficheros de [%(archive)s]" @@ -189,6 +198,7 @@ msgid " Thread" msgstr " Filu" #: Mailman/Archiver/pipermail.py:597 +#, fuzzy msgid "#%(counter)05d %(msgid)s" msgstr "#%(counter)05d %(msgid)s" @@ -226,6 +236,7 @@ msgid "disabled address" msgstr "desactivada" #: Mailman/Bouncer.py:304 +#, fuzzy msgid " The last bounce received from you was dated %(date)s" msgstr "El caberu rebote recibíu de ti foi fae %(date)s" @@ -253,6 +264,7 @@ msgstr "Alministrador" #: Mailman/Cgi/options.py:101 Mailman/Cgi/private.py:108 #: Mailman/Cgi/rmlist.py:75 Mailman/Cgi/roster.py:59 #: Mailman/Cgi/subscribe.py:67 +#, fuzzy msgid "No such list %(safelistname)s" msgstr "La llista %(safelistname)s nun esiste" @@ -326,6 +338,7 @@ msgstr "" " recibirán corréu fasta qu'igües esti problema.%(rm)r" #: Mailman/Cgi/admin.py:268 +#, fuzzy msgid "%(hostname)s mailing lists - Admin Links" msgstr "Llistes de corréu en %(hostname)s - Enllaces d'alministración" @@ -338,6 +351,7 @@ msgid "Mailman" msgstr "Mailman" #: Mailman/Cgi/admin.py:310 +#, fuzzy msgid "" "

                There currently are no publicly-advertised %(mailmanlink)s\n" " mailing lists on %(hostname)s." @@ -346,6 +360,7 @@ msgstr "" " públicamente en %(hostname)s. " #: Mailman/Cgi/admin.py:316 +#, fuzzy msgid "" "

                Below is the collection of publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s. Click on a list\n" @@ -361,6 +376,7 @@ msgid "right " msgstr "correuta" #: Mailman/Cgi/admin.py:325 +#, fuzzy msgid "" "To visit the administrators configuration page for an\n" " unadvertised list, open a URL similar to this one, but with a '/' " @@ -405,14 +421,16 @@ msgid "No valid variable name found." msgstr "Atopóse un nome de variable non válidu." #: Mailman/Cgi/admin.py:395 +#, fuzzy msgid "" "%(realname)s Mailing list Configuration Help\n" "
                %(varname)s Option" msgstr "" -"Aida de configuración de la llista de corréu %(realname)s, opción
                " -"%(varname)s" +"Aida de configuración de la llista de corréu %(realname)s, opción " +"
                %(varname)s" #: Mailman/Cgi/admin.py:402 +#, fuzzy msgid "Mailman %(varname)s List Option Help" msgstr "Aida de Mailman pa la opción de llista %(varname)s" @@ -433,14 +451,17 @@ msgstr "" "puedes\n" #: Mailman/Cgi/admin.py:431 +#, fuzzy msgid "return to the %(categoryname)s options page." msgstr "volver a la páxina d'opciones %(categoryname)s" #: Mailman/Cgi/admin.py:446 +#, fuzzy msgid "%(realname)s Administration (%(label)s)" msgstr "Alministración de %(realname)s (%(label)s)" #: Mailman/Cgi/admin.py:447 +#, fuzzy msgid "%(realname)s mailing list administration
                %(label)s Section" msgstr "" "Alministración de la llista de corréu %(realname)s
                Seición de %(label)s" @@ -525,6 +546,7 @@ msgid "Value" msgstr "Valor" #: Mailman/Cgi/admin.py:668 +#, fuzzy msgid "" "Badly formed options entry:\n" " %(record)s" @@ -625,10 +647,12 @@ msgid "Move rule down" msgstr "Mover la regla p'abaxo" #: Mailman/Cgi/admin.py:870 +#, fuzzy msgid "
                (Edit %(varname)s)" msgstr "
                (Editar %(varname)s)" #: Mailman/Cgi/admin.py:872 +#, fuzzy msgid "
                (Details for %(varname)s)" msgstr "
                (Detalles de %(varname)s)" @@ -669,6 +693,7 @@ msgid "(help)" msgstr "(aida)" #: Mailman/Cgi/admin.py:930 +#, fuzzy msgid "Find member %(link)s:" msgstr "Atopar soscritor %(link)s:" @@ -681,10 +706,12 @@ msgid "Bad regular expression: " msgstr "Espresión regular mal formada: " #: Mailman/Cgi/admin.py:1013 +#, fuzzy msgid "%(allcnt)s members total, %(membercnt)s shown" msgstr "%(allcnt)s soscritores en total, amuésense %(membercnt)s" #: Mailman/Cgi/admin.py:1016 +#, fuzzy msgid "%(allcnt)s members total" msgstr "%(allcnt)s soscritores en total" @@ -879,6 +906,7 @@ msgstr "" " llistáu embaxo:" #: Mailman/Cgi/admin.py:1221 +#, fuzzy msgid "from %(start)s to %(end)s" msgstr "de %(start)s a %(end)s" @@ -1018,6 +1046,7 @@ msgid "Change list ownership passwords" msgstr "Camudar la contraseña del propietariu de la llista" #: Mailman/Cgi/admin.py:1364 +#, fuzzy msgid "" "The list administrators are the people who have ultimate control " "over\n" @@ -1131,6 +1160,7 @@ msgstr "Direici #: Mailman/Cgi/admin.py:1529 Mailman/Cgi/admin.py:1703 bin/add_members:175 #: bin/clone_member:136 bin/sync_members:268 +#, fuzzy msgid "Banned address (matched %(pattern)s)" msgstr "Direición baneada (coincidencia %(pattern)s)" @@ -1233,6 +1263,7 @@ msgid "Not subscribed" msgstr "Non soscritu" #: Mailman/Cgi/admin.py:1773 +#, fuzzy msgid "Ignoring changes to deleted member: %(user)s" msgstr "Inorando los cambeos del usuariu desaniciáu: %(user)s" @@ -1245,10 +1276,12 @@ msgid "Error Unsubscribing:" msgstr "Fallu desoscribiendo:" #: Mailman/Cgi/admindb.py:235 Mailman/Cgi/admindb.py:248 +#, fuzzy msgid "%(realname)s Administrative Database" msgstr "Base de Datos Alministrativa %(realname)s" #: Mailman/Cgi/admindb.py:238 +#, fuzzy msgid "%(realname)s Administrative Database Results" msgstr "Resultaos de la base de datos alministrativa de %(realname)s" @@ -1277,6 +1310,7 @@ msgid "Discard all messages marked Defer" msgstr "Descartar tolos mensaxes marcaos como Diferir" #: Mailman/Cgi/admindb.py:298 +#, fuzzy msgid "all of %(esender)s's held messages." msgstr "tolos mensaxes reteníos de %(esender)s." @@ -1297,6 +1331,7 @@ msgid "list of available mailing lists." msgstr "llistáu de llistes de corréu que tán disponibles." #: Mailman/Cgi/admindb.py:362 +#, fuzzy msgid "You must specify a list name. Here is the %(link)s" msgstr "Tienes qu'especificar un nome de llista. Equí ta'l %(link)s" @@ -1378,6 +1413,7 @@ msgid "The sender is now a member of this list" msgstr "El remitente ye agora soscritor d'esta llista" #: Mailman/Cgi/admindb.py:568 +#, fuzzy msgid "Add %(esender)s to one of these sender filters:" msgstr "amestar %(esender)s a una d'estes peñeres de remitentes:" @@ -1398,6 +1434,7 @@ msgid "Rejects" msgstr "Refugar" #: Mailman/Cgi/admindb.py:584 +#, fuzzy msgid "" "Ban %(esender)s from ever subscribing to this\n" " mailing list" @@ -1414,6 +1451,7 @@ msgstr "" " individual, o tu puedes" #: Mailman/Cgi/admindb.py:591 +#, fuzzy msgid "view all messages from %(esender)s" msgstr "ver tolos mensaxes de %(esender)s" @@ -1492,6 +1530,7 @@ msgid " is already a member" msgstr " yá ta soscritu" #: Mailman/Cgi/admindb.py:973 +#, fuzzy msgid "%(addr)s is banned (matched: %(patt)s)" msgstr "%(addr)s ta vetada (concordancia: %(patt)s)" @@ -1542,6 +1581,7 @@ msgstr "" " encaboxóxe." #: Mailman/Cgi/confirm.py:178 +#, fuzzy msgid "System error, bad content: %(content)s" msgstr "Fallu nel sistema, conteníu corruptu: %(content)s" @@ -1579,6 +1619,7 @@ msgid "Confirm subscription request" msgstr "Confirmar la solicitú de soscripción" #: Mailman/Cgi/confirm.py:259 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " subscription request to the mailing list %(listname)s. Your\n" @@ -1617,6 +1658,7 @@ msgstr "" " soscribite a esta llista." #: Mailman/Cgi/confirm.py:275 +#, fuzzy msgid "" "Your confirmation is required in order to continue with\n" " the subscription request to the mailing list %(listname)s.\n" @@ -1671,6 +1713,7 @@ msgid "Preferred language:" msgstr "Llingua preferida:" #: Mailman/Cgi/confirm.py:317 +#, fuzzy msgid "Subscribe to list %(listname)s" msgstr "Soscribise a la llista %(listname)s" @@ -1687,6 +1730,7 @@ msgid "Awaiting moderator approval" msgstr "Esperando pola aprobación d'un llendador" #: Mailman/Cgi/confirm.py:386 +#, fuzzy msgid "" " You have successfully confirmed your subscription request to " "the\n" @@ -1719,6 +1763,7 @@ msgid "You are already a member of this mailing list!" msgstr "¡Yá tas soscritu a esta llista de corréu!" #: Mailman/Cgi/confirm.py:400 +#, fuzzy msgid "" "You are currently banned from subscribing to\n" " this list. If you think this restriction is erroneous, please\n" @@ -1743,6 +1788,7 @@ msgid "Subscription request confirmed" msgstr "Petición de soscrición confirmada" #: Mailman/Cgi/confirm.py:418 +#, fuzzy msgid "" " You have successfully confirmed your subscription request for\n" " \"%(addr)s\" to the %(listname)s mailing list. A separate\n" @@ -1770,6 +1816,7 @@ msgid "Unsubscription request confirmed" msgstr "Confirmóse la petición de baxa de la soscripción" #: Mailman/Cgi/confirm.py:469 +#, fuzzy msgid "" " You have successfully unsubscribed from the %(listname)s " "mailing\n" @@ -1791,6 +1838,7 @@ msgid "Not available" msgstr "Non disponible" #: Mailman/Cgi/confirm.py:498 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " unsubscription request from the mailing list %(listname)s. " @@ -1834,6 +1882,7 @@ msgid "You have canceled your change of address request." msgstr "Encaboxasti la solicitú de cambéu de direición" #: Mailman/Cgi/confirm.py:555 +#, fuzzy msgid "" "%(newaddr)s is banned from subscribing to the\n" " %(realname)s list. If you think this restriction is erroneous,\n" @@ -1845,6 +1894,7 @@ msgstr "" "%(owneraddr)s." #: Mailman/Cgi/confirm.py:560 +#, fuzzy msgid "" "%(newaddr)s is already a member of\n" " the %(realname)s list. It is possible that you are attempting\n" @@ -1861,6 +1911,7 @@ msgid "Change of address request confirmed" msgstr "Confirmóse la solicitú de cambéu de direición" #: Mailman/Cgi/confirm.py:571 +#, fuzzy msgid "" " You have successfully changed your address on the %(listname)s\n" " mailing list from %(oldaddr)s to %(newaddr)s. " @@ -1882,6 +1933,7 @@ msgid "globally" msgstr "globalmente" #: Mailman/Cgi/confirm.py:605 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " change of address request for the mailing list %(listname)s. " @@ -1944,6 +1996,7 @@ msgid "Sender discarded message via web." msgstr "El remitente descartó'l mensax vía web." #: Mailman/Cgi/confirm.py:674 +#, fuzzy msgid "" "The held message with the Subject:\n" " header %(subject)s could not be found. The most " @@ -1964,6 +2017,7 @@ msgid "Posted message canceled" msgstr "Mensax unviáu encaboxáu" #: Mailman/Cgi/confirm.py:685 +#, fuzzy msgid "" " You have successfully canceled the posting of your message with\n" " the Subject: header %(subject)s to the mailing list\n" @@ -1986,6 +2040,7 @@ msgstr "" " tratáu pol alministrador de la llista." #: Mailman/Cgi/confirm.py:735 +#, fuzzy msgid "" "Your confirmation is required in order to cancel the\n" " posting of your message to the mailing list %(listname)s:\n" @@ -2034,6 +2089,7 @@ msgid "Membership re-enabled." msgstr "Soscrición reactivada." #: Mailman/Cgi/confirm.py:798 +#, fuzzy msgid "" " You have successfully re-enabled your membership in the\n" " %(listname)s mailing list. You can now not available" msgstr "Non disponible" #: Mailman/Cgi/confirm.py:846 +#, fuzzy msgid "" "Your membership in the %(realname)s mailing list is\n" " currently disabled due to excessive bounces. Your confirmation is\n" @@ -2134,10 +2192,12 @@ msgid "administrative list overview" msgstr "Descripción xeneral llista alministrativa" #: Mailman/Cgi/create.py:115 +#, fuzzy msgid "List name must not include \"@\": %(safelistname)s" msgstr "El nome de la llista nun puede caltener \"@\": %(safelistname)s" #: Mailman/Cgi/create.py:122 +#, fuzzy msgid "List already exists: %(safelistname)s" msgstr "La llista yá esiste: %(safelistname)s" @@ -2172,18 +2232,22 @@ msgid "You are not authorized to create new mailing lists" msgstr "Nun tas autorizáu pa criar llistes de corréu nueves" #: Mailman/Cgi/create.py:182 +#, fuzzy msgid "Unknown virtual host: %(safehostname)s" msgstr "Agospiador virtual desconocíu: %(safehostname)s" #: Mailman/Cgi/create.py:218 bin/newlist:219 +#, fuzzy msgid "Bad owner email address: %(s)s" msgstr "Direición de corréu electrónicu del propietariu incorreuta: %(s)s" #: Mailman/Cgi/create.py:223 bin/newlist:182 bin/newlist:223 +#, fuzzy msgid "List already exists: %(listname)s" msgstr "La llista yá esiste: %(listname)s" #: Mailman/Cgi/create.py:231 bin/newlist:217 +#, fuzzy msgid "Illegal list name: %(s)s" msgstr "Nome de llista illegal: %(s)s" @@ -2196,6 +2260,7 @@ msgstr "" " Por favor, contauta col alministrador del sitiu p'aidate." #: Mailman/Cgi/create.py:273 bin/newlist:265 +#, fuzzy msgid "Your new mailing list: %(listname)s" msgstr "La to nueva llista de corréu: %(listname)s" @@ -2204,6 +2269,7 @@ msgid "Mailing list creation results" msgstr "Resultaos de la criación de les llistes de corréu" #: Mailman/Cgi/create.py:288 +#, fuzzy msgid "" "You have successfully created the mailing list\n" " %(listname)s and notification has been sent to the list owner\n" @@ -2227,6 +2293,7 @@ msgid "Create another list" msgstr "Criar otra llista" #: Mailman/Cgi/create.py:312 +#, fuzzy msgid "Create a %(hostname)s Mailing List" msgstr "Criar una llista de corréu de %(hostname)s" @@ -2328,6 +2395,7 @@ msgstr "" "defeutu." #: Mailman/Cgi/create.py:432 +#, fuzzy msgid "" "Initial list of supported languages.

                Note that if you do not\n" " select at least one initial language, the list will use the server\n" @@ -2435,6 +2503,7 @@ msgid "List name is required." msgstr "Requierse'l Nome de Llista." #: Mailman/Cgi/edithtml.py:154 +#, fuzzy msgid "%(realname)s -- Edit html for %(template_info)s" msgstr "%(realname)s -- Editar el códigu html pa %(template_info)s" @@ -2443,10 +2512,12 @@ msgid "Edit HTML : Error" msgstr "Editar HTML : Fallu" #: Mailman/Cgi/edithtml.py:161 +#, fuzzy msgid "%(safetemplatename)s: Invalid template" msgstr "%(safetemplatename)s: Plantía non válida" #: Mailman/Cgi/edithtml.py:166 Mailman/Cgi/edithtml.py:167 +#, fuzzy msgid "%(realname)s -- HTML Page Editing" msgstr "%(realname)s -- Edición del códigu HTML de les Páxines" @@ -2509,10 +2580,12 @@ msgid "HTML successfully updated." msgstr "HTML anováu satisfactoriamente." #: Mailman/Cgi/listinfo.py:89 +#, fuzzy msgid "%(hostname)s Mailing Lists" msgstr "Llistes de Corréu de %(hostname)s" #: Mailman/Cgi/listinfo.py:127 +#, fuzzy msgid "" "

                There currently are no publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s." @@ -2521,6 +2594,7 @@ msgstr "" " anunciaes públicamente en %(hostname)s. " #: Mailman/Cgi/listinfo.py:131 +#, fuzzy msgid "" "

                Below is a listing of all the public mailing lists on\n" " %(hostname)s. Click on a list name to get more information " @@ -2541,6 +2615,7 @@ msgid "right" msgstr "correuto" #: Mailman/Cgi/listinfo.py:140 +#, fuzzy msgid "" " To visit the general information page for an unadvertised list,\n" " open a URL similar to this one, but with a '/' and the %(adj)s\n" @@ -2603,6 +2678,7 @@ msgstr "Direici #: Mailman/Cgi/options.py:183 Mailman/Cgi/options.py:240 #: Mailman/Cgi/options.py:264 +#, fuzzy msgid "No such member: %(safeuser)s." msgstr "Nun esiste soscritor: %(safeuser)s." @@ -2655,6 +2731,7 @@ msgid "Note: " msgstr "Nota: " #: Mailman/Cgi/options.py:378 +#, fuzzy msgid "List subscriptions for %(safeuser)s on %(hostname)s" msgstr "soscriciones de %(safeuser)s en %(hostname)s" @@ -2686,6 +2763,7 @@ msgid "You are already using that email address" msgstr "Yá tas usando esa direición de corréu electrónicu" #: Mailman/Cgi/options.py:459 +#, fuzzy msgid "" "The new address you requested %(newaddr)s is already a member of the\n" "%(listname)s mailing list, however you have also requested a global change " @@ -2699,6 +2777,7 @@ msgstr "" "cualisquier otra llista de corréu que caltenga la direición %(safeuser)s." #: Mailman/Cgi/options.py:468 +#, fuzzy msgid "The new address is already a member: %(newaddr)s" msgstr "La direición nueva yá ta dada d'alta: %(newaddr)s" @@ -2707,6 +2786,7 @@ msgid "Addresses may not be blank" msgstr "Les direiciones nun pueden tar ermes" #: Mailman/Cgi/options.py:488 +#, fuzzy msgid "A confirmation message has been sent to %(newaddr)s. " msgstr "Unvióse un mensax de confirmación a %(newaddr)s" @@ -2719,10 +2799,12 @@ msgid "Illegal email address provided" msgstr "Dióse una direición de corréu electrónicu illegal" #: Mailman/Cgi/options.py:501 +#, fuzzy msgid "%(newaddr)s is already a member of the list." msgstr "%(newaddr)s yá ta soscritu a la llista." #: Mailman/Cgi/options.py:504 +#, fuzzy msgid "" "%(newaddr)s is banned from this list. If you\n" " think this restriction is erroneous, please contact\n" @@ -2798,6 +2880,7 @@ msgstr "" " cuando'l llendador tenga tomao la so decisión." #: Mailman/Cgi/options.py:623 +#, fuzzy msgid "" "You have been successfully unsubscribed from the\n" " mailing list %(fqdn_listname)s. If you were receiving digest\n" @@ -2895,6 +2978,7 @@ msgid "day" msgstr "día" #: Mailman/Cgi/options.py:900 +#, fuzzy msgid "%(days)d %(units)s" msgstr "%(days)d %(units)s" @@ -2907,6 +2991,7 @@ msgid "No topics defined" msgstr "Temes non definíos" #: Mailman/Cgi/options.py:940 +#, fuzzy msgid "" "\n" "You are subscribed to this list with the case-preserved address\n" @@ -2917,6 +3002,7 @@ msgstr "" "%(cpuser)s." #: Mailman/Cgi/options.py:956 +#, fuzzy msgid "%(realname)s list: member options login page" msgstr "Llista %(realname)s: páxina d'entrada d'opciones soscritor" @@ -2925,10 +3011,12 @@ msgid "email address and " msgstr "direición de corréu electrónicu y " #: Mailman/Cgi/options.py:960 +#, fuzzy msgid "%(realname)s list: member options for user %(safeuser)s" msgstr "Llista %(realname)s: opciones de soscrición de %(safeuser)s" #: Mailman/Cgi/options.py:986 +#, fuzzy msgid "" "In order to change your membership option, you must\n" " first log in by giving your %(extra)smembership password in the section\n" @@ -3010,6 +3098,7 @@ msgid "" msgstr "" #: Mailman/Cgi/options.py:1140 +#, fuzzy msgid "Requested topic is not valid: %(topicname)s" msgstr "El tema solicitáu nun ye válidu: %(topicname)s" @@ -3038,6 +3127,7 @@ msgid "Private archive - \"./\" and \"../\" not allowed in URL." msgstr "L'archivu priváu - \"./\" y \"../\" nun permitíes na URL." #: Mailman/Cgi/private.py:109 +#, fuzzy msgid "Private Archive Error - %(msg)s" msgstr "Fallu nel archivu Priváu - %(msg)s" @@ -3075,6 +3165,7 @@ msgid "Mailing list deletion results" msgstr "Resultaos del desaniciáu de la llista de corréu" #: Mailman/Cgi/rmlist.py:187 +#, fuzzy msgid "" "You have successfully deleted the mailing list\n" " %(listname)s." @@ -3083,6 +3174,7 @@ msgstr "" " %(listname)s." #: Mailman/Cgi/rmlist.py:191 +#, fuzzy msgid "" "There were some problems deleting the mailing list\n" " %(listname)s. Contact your site administrator at " @@ -3094,6 +3186,7 @@ msgstr "" " pa más detalles." #: Mailman/Cgi/rmlist.py:208 +#, fuzzy msgid "Permanently remove mailing list %(realname)s" msgstr "Desaniciar dafechu la llista de corréu %(realname)s" @@ -3163,6 +3256,7 @@ msgid "Invalid options to CGI script" msgstr "Opciones nun válides nel script CGI" #: Mailman/Cgi/roster.py:118 +#, fuzzy msgid "%(realname)s roster authentication failed." msgstr "L'autentificación a la llista de %(realname)s falló." @@ -3231,6 +3325,7 @@ msgstr "" "coles intrucciones a siguir." #: Mailman/Cgi/subscribe.py:262 +#, fuzzy msgid "" "The email address you supplied is banned from this\n" " mailing list. If you think this restriction is erroneous, please\n" @@ -3257,6 +3352,7 @@ msgstr "" "ye insegura." #: Mailman/Cgi/subscribe.py:278 +#, fuzzy msgid "" "Confirmation from your email address is required, to prevent anyone from\n" "subscribing you without permission. Instructions are being sent to you at\n" @@ -3269,6 +3365,7 @@ msgstr "" "entamará fasta que la confirmes." #: Mailman/Cgi/subscribe.py:290 +#, fuzzy msgid "" "Your subscription request was deferred because %(x)s. Your request has " "been\n" @@ -3290,6 +3387,7 @@ msgid "Mailman privacy alert" msgstr "Alerta de privacidá de Mailman" #: Mailman/Cgi/subscribe.py:316 +#, fuzzy msgid "" "An attempt was made to subscribe your address to the mailing list\n" "%(listaddr)s. You are already subscribed to this mailing list.\n" @@ -3334,6 +3432,7 @@ msgid "This list only supports digest delivery." msgstr "Esta llista namái almite entregues agrupaes." #: Mailman/Cgi/subscribe.py:344 +#, fuzzy msgid "You have been successfully subscribed to the %(realname)s mailing list." msgstr "Tas soscritu dafechu a la llista de corréu %(realname)s." @@ -3385,6 +3484,7 @@ msgstr "" "direición de corréu electrónicu?" #: Mailman/Commands/cmd_confirm.py:69 +#, fuzzy msgid "" "You are currently banned from subscribing to this list. If you think this\n" "restriction is erroneous, please contact the list owners at\n" @@ -3467,26 +3567,32 @@ msgid "n/a" msgstr "n/a" #: Mailman/Commands/cmd_info.py:44 +#, fuzzy msgid "List name: %(listname)s" msgstr "Nome de la llista: %(listname)s" #: Mailman/Commands/cmd_info.py:45 +#, fuzzy msgid "Description: %(description)s" msgstr "Descripción: %(description)s" #: Mailman/Commands/cmd_info.py:46 +#, fuzzy msgid "Postings to: %(postaddr)s" msgstr "Unviar mensaxes a: %(postaddr)s" #: Mailman/Commands/cmd_info.py:47 +#, fuzzy msgid "List Helpbot: %(requestaddr)s" msgstr "Robot d'aida de llista: %(requestaddr)s" #: Mailman/Commands/cmd_info.py:48 +#, fuzzy msgid "List Owners: %(owneraddr)s" msgstr "Propietarios: %(owneraddr)s" #: Mailman/Commands/cmd_info.py:49 +#, fuzzy msgid "More information: %(listurl)s" msgstr "Más información: %(listurl)s" @@ -3510,18 +3616,22 @@ msgstr "" "GNU Mailman.\n" #: Mailman/Commands/cmd_lists.py:44 +#, fuzzy msgid "Public mailing lists at %(hostname)s:" msgstr "Llistes de corréu públiques en %(hostname)s" #: Mailman/Commands/cmd_lists.py:66 +#, fuzzy msgid "%(i)3d. List name: %(realname)s" msgstr "%(i)3d. Nome de la llista: %(realname)s" #: Mailman/Commands/cmd_lists.py:67 +#, fuzzy msgid " Description: %(description)s" msgstr " Descripción: %(description)s" #: Mailman/Commands/cmd_lists.py:68 +#, fuzzy msgid " Requests to: %(requestaddr)s" msgstr " Solicitúes a: %(requestaddr)s" @@ -3558,12 +3668,14 @@ msgstr "" " rempuesta siempre s'unviará a la direición soscrita.\n" #: Mailman/Commands/cmd_password.py:51 Mailman/Commands/cmd_password.py:66 +#, fuzzy msgid "Your password is: %(password)s" msgstr "La contraseña nueva ye %(password)s" #: Mailman/Commands/cmd_password.py:57 Mailman/Commands/cmd_password.py:72 #: Mailman/Commands/cmd_password.py:95 Mailman/Commands/cmd_password.py:121 #: Mailman/Commands/cmd_set.py:149 Mailman/Commands/cmd_set.py:219 +#, fuzzy msgid "You are not a member of the %(listname)s mailing list" msgstr "Nun yes un miembru de la llista de corréu %(listname)s" @@ -3760,6 +3872,7 @@ msgstr "" " de la llista de corréu mensualmente.\n" #: Mailman/Commands/cmd_set.py:122 +#, fuzzy msgid "Bad set command: %(subcmd)s" msgstr "Comandu incorreutu: %(subcmd)s" @@ -3780,6 +3893,7 @@ msgid "on" msgstr "Activar" #: Mailman/Commands/cmd_set.py:154 +#, fuzzy msgid " ack %(onoff)s" msgstr " ack %(onoff)s" @@ -3817,22 +3931,27 @@ msgid "due to bounces" msgstr "debío a rebotes" #: Mailman/Commands/cmd_set.py:186 +#, fuzzy msgid " %(status)s (%(how)s on %(date)s)" msgstr " %(status)s (%(how)s el %(date)s)" #: Mailman/Commands/cmd_set.py:192 +#, fuzzy msgid " myposts %(onoff)s" msgstr " los mios mensaxes %(onoff)s" #: Mailman/Commands/cmd_set.py:195 +#, fuzzy msgid " hide %(onoff)s" msgstr " anubrir %(onoff)s" #: Mailman/Commands/cmd_set.py:199 +#, fuzzy msgid " duplicates %(onoff)s" msgstr " duplicaos %(onoff)s" #: Mailman/Commands/cmd_set.py:203 +#, fuzzy msgid " reminders %(onoff)s" msgstr " remembrar %(onoff)s" @@ -3841,6 +3960,7 @@ msgid "You did not give the correct password" msgstr "Nun disti la contraseña correuta" #: Mailman/Commands/cmd_set.py:236 Mailman/Commands/cmd_set.py:283 +#, fuzzy msgid "Bad argument: %(arg)s" msgstr "Argumentos incorreutos: %(arg)s" @@ -3919,6 +4039,7 @@ msgstr "" " de la direición del corréu, y ensin comines!).\n" #: Mailman/Commands/cmd_subscribe.py:62 +#, fuzzy msgid "Bad digest specifier: %(arg)s" msgstr "Especificación d'agrupamientu incorreuta: %(arg)s" @@ -3927,6 +4048,7 @@ msgid "No valid address found to subscribe" msgstr "Nun s'alcontró direición válida pa soscribise" #: Mailman/Commands/cmd_subscribe.py:113 +#, fuzzy msgid "" "The email address you supplied is banned from this mailing list.\n" "If you think this restriction is erroneous, please contact the list\n" @@ -3967,6 +4089,7 @@ msgid "This list only supports digest subscriptions!" msgstr "¡Esta llista namái sofita soscriciones con mensaxes agrupaos!" #: Mailman/Commands/cmd_subscribe.py:146 +#, fuzzy msgid "" "Your subscription request has been forwarded to the list administrator\n" "at %(listowner)s for review." @@ -4005,6 +4128,7 @@ msgstr "" " comines!)\n" #: Mailman/Commands/cmd_unsubscribe.py:62 +#, fuzzy msgid "%(address)s is not a member of the %(listname)s mailing list" msgstr "%(address)s nun ta soscritu a la llista de corréu %(listname)s" @@ -4259,6 +4383,7 @@ msgid "Chinese (Taiwan)" msgstr "Chinu (Taiwan)" #: Mailman/Deliverer.py:53 +#, fuzzy msgid "" "Note: Since this is a list of mailing lists, administrative\n" "notices like the password reminder will be sent to\n" @@ -4273,14 +4398,17 @@ msgid " (Digest mode)" msgstr " (Mou Resume)" #: Mailman/Deliverer.py:79 +#, fuzzy msgid "Welcome to the \"%(realname)s\" mailing list%(digmode)s" msgstr "Bienveníu a \"%(realname)s\" la llista de corréu %(digmode)s" #: Mailman/Deliverer.py:89 +#, fuzzy msgid "You have been unsubscribed from the %(realname)s mailing list" msgstr "Disti de baxa la to soscrición de la llista de corréu %(realname)s" #: Mailman/Deliverer.py:116 +#, fuzzy msgid "%(listfullname)s mailing list reminder" msgstr "Remembrar llista de corréu %(listfullname)s" @@ -4293,6 +4421,7 @@ msgid "Hostile subscription attempt detected" msgstr "Deteutóse intentu de soscrición hostil" #: Mailman/Deliverer.py:169 +#, fuzzy msgid "" "%(address)s was invited to a different mailing\n" "list, but in a deliberate malicious attempt they tried to confirm the\n" @@ -4305,6 +4434,7 @@ msgstr "" "faigas res." #: Mailman/Deliverer.py:188 +#, fuzzy msgid "" "You invited %(address)s to your list, but in a\n" "deliberate malicious attempt, they tried to confirm the invitation to a\n" @@ -4317,6 +4447,7 @@ msgstr "" "Camentamos que prestaríate sabelo. Nun fai falta que faigas res." #: Mailman/Deliverer.py:221 +#, fuzzy msgid "%(listname)s mailing list probe message" msgstr "Mensaxe de preba de la llista de corréu %(listname)s" @@ -4525,8 +4656,8 @@ msgid "" " membership.\n" "\n" "

                You can control both the\n" -" number\n" +" number\n" " of reminders the member will receive and the\n" " Puedes remanar tanto'l\n" -" númberu\n" +" númberu\n" " de recordatorios que recibirá'l soscritor como la\n" " \n" @@ -4743,8 +4874,8 @@ msgid "" " No those messages too will get discarded. You may " "want\n" " to set up an\n" -" autoresponse\n" +" autoresponse\n" " message for email to the -owner and -admin address." msgstr "" "Magar que'l deteutor de mensaxes rebotaos de Mailman ye enforma robustu, ye\n" @@ -4846,6 +4977,7 @@ msgstr "" "soscritor." #: Mailman/Gui/Bounce.py:194 +#, fuzzy msgid "" "Bad value for %(property)s: %(val)s" @@ -4924,8 +5056,8 @@ msgstr "" " esbórrase. Si'l mensax saliente queda ermu dempués d'esta peñera, " "entós descártase'l\n" " mensax enteru. De siguío, si ta activáu\n" -" collapse_alternatives, cada seición\n" +" collapse_alternatives, cada seición\n" " multipart/alternative trócase cola primer alternativa que " "nun tea erma dempués de la peñera.\n" "\n" @@ -4978,8 +5110,8 @@ msgstr "" "\n" "

                Les llinies ermes inórense.\n" "\n" -"

                Mira tamién Mira tamién pass_mime_types pa obtener una llista de tribes con " "conteníu válidu." @@ -4998,8 +5130,8 @@ msgid "" "Use this option to remove each message attachment that does\n" " not have a matching content type. Requirements and formats " "are\n" -" exactly like filter_mime_types.\n" "\n" "

                Note: if you add entries to this list but don't add\n" @@ -5121,6 +5253,7 @@ msgstr "" " activó l'alministrador del sitiu." #: Mailman/Gui/ContentFilter.py:171 +#, fuzzy msgid "Bad MIME type ignored: %(spectype)s" msgstr "Triba MIME incorreuta inorada: %(spectype)s" @@ -5229,6 +5362,7 @@ msgstr "" " ermu?" #: Mailman/Gui/Digest.py:145 +#, fuzzy msgid "" "The next digest will be sent as volume\n" " %(volume)s, number %(number)s" @@ -5245,14 +5379,17 @@ msgid "There was no digest to send." msgstr "Nun hai agrupáu pa unviar." #: Mailman/Gui/GUIBase.py:173 +#, fuzzy msgid "Invalid value for variable: %(property)s" msgstr "Valor incorreutu pa la variable: %(property)s" #: Mailman/Gui/GUIBase.py:178 +#, fuzzy msgid "Bad email address for option %(property)s: %(error)s" msgstr "Direición de corréu incorreuta pa la opción %(property)s: %(error)s" #: Mailman/Gui/GUIBase.py:204 +#, fuzzy msgid "" "The following illegal substitution variables were\n" " found in the %(property)s string:\n" @@ -5268,6 +5405,7 @@ msgstr "" " qu'igües esti problema." #: Mailman/Gui/GUIBase.py:218 +#, fuzzy msgid "" "Your %(property)s string appeared to\n" " have some correctable problems in its new value.\n" @@ -5369,8 +5507,8 @@ msgid "" "

                In order to split the list ownership duties into\n" " administrators and moderators, you must\n" " set a separate moderator password,\n" -" and also provide the email\n" +" and also provide the email\n" " addresses of the list moderators. Note that the field you\n" " are changing here specifies the list administrators." msgstr "" @@ -5714,13 +5852,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5762,8 +5900,8 @@ msgstr "" " camudando la cabecera Responder A: fae que seya más " "dificil\n" " unviar rempuestes privaes. Mira \n" -" `Reply-To' Munging\n" +" `Reply-To' Munging\n" " Considered Harmful pa una discusión xeneral sobre esti " "tema.\n" " Mira Reply-To: header\n" " when the reply_goes_to_list\n" +" href=\"?VARHELP=general/" +"reply_goes_to_list\">reply_goes_to_list\n" " option is set to Explicit address.\n" "\n" "

                There are many reasons not to introduce or override the\n" @@ -5801,13 +5939,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5842,12 +5980,12 @@ msgstr "" " Responder A: fai que seya más abegoso unviar " "rempuestes\n" " privaes. Mira\n" -" \n" +" \n" " `Reply-To' Munging Considered Harmful pa una\n" " discusión xeneral sobre esti tema. Vea\n" -" href=\"http://marc.merlins.org/netrants/reply-to-useful.html" -"\">Reply-To\n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">Reply-To\n" " Munging Considered Useful pa ver una opinión contraria.\n" "\n" "

                Dalgunes llistes de corréu tienen torgada\n" @@ -5915,8 +6053,8 @@ msgid "" " member list addresses, but rather to the owner of those member\n" " lists. In that case, the value of this setting is appended to\n" " the member's account name for such notices. `-owner' is the\n" -" typical choice. This setting has no effect when \"umbrella_list" -"\"\n" +" typical choice. This setting has no effect when " +"\"umbrella_list\"\n" " is \"No\"." msgstr "" "Cuando \"umbrella_list\" te activa ye pa indicar qu'esta\n" @@ -6946,6 +7084,7 @@ msgstr "" " soscriban a terceres persones ensin el so preste." #: Mailman/Gui/Privacy.py:104 +#, fuzzy msgid "" "This section allows you to configure subscription and\n" " membership exposure policy. You can also control whether this\n" @@ -7146,8 +7285,8 @@ msgid "" " either individually or as a group. Any\n" " posting from a non-member who is not explicitly accepted,\n" " rejected, or discarded, will have their posting filtered by the\n" -" general\n" +" general\n" " non-member rules.\n" "\n" "

                In the text boxes below, add one address per line; start the\n" @@ -7174,20 +7313,20 @@ msgstr "" " remanar si los mensaxes de los soscritores van llendase o non.\n" "\n" "

                Los mensaxes de non-soscriptores pueden\n" -" aceutar,\n" -" retener\n" +" aceutar,\n" +" retener\n" " pa llendadura,\n" -" refugar (rebotar), o\n" -" descartar automáticamente,\n" +" refugar (rebotar), o\n" +" descartar automáticamente,\n" " tantu individualmente o en grupu. Cualisquier\n" " mensax d'un nonsoscritor que nun s'aceute,\n" " refugue o descarte automáticamente, peñeraráse poles\n" -" regles\n" +" regles\n" " xenerales pa los non-soscritores.\n" "\n" "

                Na caxa de testu de más abaxo, amiesta una direición en cada " @@ -7213,6 +7352,7 @@ msgstr "" " soscritores nuevos?" #: Mailman/Gui/Privacy.py:218 +#, fuzzy msgid "" "Each list member has a moderation flag which says\n" " whether messages from the list member can be posted directly " @@ -7253,8 +7393,8 @@ msgstr "" " llendar los mensaxes de los soscritores por defeutu. Siempre " "puedes\n" " afitar manualmente la marca de llendadura de cada soscritor\n" -" usando les seiciones d'alministración de\n" +" usando les seiciones d'alministración de\n" " los soscritores." #: Mailman/Gui/Privacy.py:234 @@ -7269,8 +7409,8 @@ msgid "" "If a member posts this many times, within a period of time\n" " the member is automatically moderated. Use 0 to disable. " "See\n" -" member_verbosity_interval for details on the time " "period.\n" "\n" @@ -7721,14 +7861,14 @@ msgstr "" "Cuando se recibe un mensax d'un non soscritor, compruébase si'l\n" " remitente s'alcuentra nel llistáu de direiciones " "esplícitamente\n" -" aceutaes,\n" -" retenies,\n" -" refugaes (rebotaes), o\n" -" descartaes.\n" +" aceutaes,\n" +" retenies,\n" +" refugaes (rebotaes), o\n" +" descartaes.\n" " Si nun s'alcuentra en nengún d'estos llistaos, fáise esta " "aición." @@ -7741,6 +7881,7 @@ msgstr "" " non soscritores que se descarten automáticamente?" #: Mailman/Gui/Privacy.py:494 +#, fuzzy msgid "" "Text to include in any rejection notice to be sent to\n" " non-members who post to this list. This notice can include\n" @@ -8011,6 +8152,7 @@ msgstr "" " Inoraránse les regles de peñeráu incompletes." #: Mailman/Gui/Privacy.py:693 +#, fuzzy msgid "" "The header filter rule pattern\n" " '%(safepattern)s' is not a legal regular expression. This\n" @@ -8061,13 +8203,13 @@ msgid "" "

                The body of the message can also be optionally scanned for\n" " Subject: and Keywords: headers, as\n" " specified by the topics_bodylines_limit\n" +" href=\"?VARHELP=topics/" +"topics_bodylines_limit\">topics_bodylines_limit\n" " configuration variable." msgstr "" "La peñera según el tema, clasifica cada mensax recibíu\n" -" según: les \n" +" según: les \n" " peñeres d'espresiones regulares qu'especifiques embaxo. Si " "les cabeceres\n" " Asuntu: o Pallabres clave\n" @@ -8089,8 +8231,8 @@ msgstr "" " cabeceres Asuntu: y Pallabres clave:, " "como s'especifica na\n" " variable de configuración\n" -" topics_bodylines_limit." +" topics_bodylines_limit." #: Mailman/Gui/Topics.py:72 msgid "How many body lines should the topic matcher scan?" @@ -8166,6 +8308,7 @@ msgstr "" " un patrón. Inórense les definiciones incompletes." #: Mailman/Gui/Topics.py:135 +#, fuzzy msgid "" "The topic pattern '%(safepattern)s' is not a\n" " legal regular expression. It will be discarded." @@ -8407,6 +8550,7 @@ msgid "%(listinfo_link)s list run by %(owner_link)s" msgstr "%(listinfo_link)s alminístrala %(owner_link)s" #: Mailman/HTMLFormatter.py:57 +#, fuzzy msgid "%(realname)s administrative interface" msgstr "Interface alministrativa de %(realname)s" @@ -8415,6 +8559,7 @@ msgid " (requires authorization)" msgstr " (requier autorización)" #: Mailman/HTMLFormatter.py:61 +#, fuzzy msgid "Overview of all %(hostname)s mailing lists" msgstr "Panorámica de toles llistes de corréu de %(hostname)s" @@ -8435,6 +8580,7 @@ msgid "; it was disabled by the list administrator" msgstr "; foi desactivada pol alministrador de la llista" #: Mailman/HTMLFormatter.py:146 +#, fuzzy msgid "" "; it was disabled due to excessive bounces. The\n" " last bounce was received on %(date)s" @@ -8447,6 +8593,7 @@ msgid "; it was disabled for unknown reasons" msgstr "; foi desactivada por dalgún motivu desconocíu" #: Mailman/HTMLFormatter.py:151 +#, fuzzy msgid "Note: your list delivery is currently disabled%(reason)s." msgstr "Nota: Anguaño tienes desactivada la entrega de corréu%(reason)s." @@ -8459,6 +8606,7 @@ msgid "the list administrator" msgstr "l'alministrador de la llista" #: Mailman/HTMLFormatter.py:157 +#, fuzzy msgid "" "

                %(note)s\n" "\n" @@ -8480,6 +8628,7 @@ msgstr "" " tienes dalguna entruga o si necesites aida." #: Mailman/HTMLFormatter.py:169 +#, fuzzy msgid "" "

                We have received some recent bounces from your\n" " address. Your current bounce score is %(score)s out of " @@ -8501,6 +8650,7 @@ msgstr "" " los problemes se corrixen aína." #: Mailman/HTMLFormatter.py:181 +#, fuzzy msgid "" "(Note - you are subscribing to a list of mailing lists, so the %(type)s " "notice will be sent to the admin address for your membership, %(addr)s.)

                " @@ -8551,6 +8701,7 @@ msgstr "" " decisión del alministrador per corréu-e." #: Mailman/HTMLFormatter.py:208 +#, fuzzy msgid "" "This is %(also)sa private list, which means that the\n" " list of members is not available to non-members." @@ -8560,6 +8711,7 @@ msgstr "" " nun ten soscritos." #: Mailman/HTMLFormatter.py:211 +#, fuzzy msgid "" "This is %(also)sa hidden list, which means that the\n" " list of members is available only to the list administrator." @@ -8569,6 +8721,7 @@ msgstr "" " al alministrador de la llista." #: Mailman/HTMLFormatter.py:214 +#, fuzzy msgid "" "This is %(also)sa public list, which means that the\n" " list of members list is available to everyone." @@ -8586,6 +8739,7 @@ msgstr "" " reconocibles polos qu'unvien corréu puxarra)." #: Mailman/HTMLFormatter.py:222 +#, fuzzy msgid "" "

                (Note that this is an umbrella list, intended to\n" " have only other mailing lists as members. Among other things,\n" @@ -8603,6 +8757,7 @@ msgid "either " msgstr "cualesquiera " #: Mailman/HTMLFormatter.py:256 +#, fuzzy msgid "" "To unsubscribe from %(realname)s, get a password reminder,\n" " or change your subscription options %(either)senter your " @@ -8637,6 +8792,7 @@ msgstr "" " direición de corréu electrónicu" #: Mailman/HTMLFormatter.py:277 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " members.)" @@ -8645,6 +8801,7 @@ msgstr "" " los soscritores de la llista.)" #: Mailman/HTMLFormatter.py:281 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " administrator.)" @@ -8707,6 +8864,7 @@ msgid "The current archive" msgstr "L'archivu actual" #: Mailman/Handlers/Acknowledge.py:59 +#, fuzzy msgid "%(realname)s post acknowledgement" msgstr "Confirmación d'unvíu a llista de corréu %(realname)s" @@ -8719,6 +8877,7 @@ msgid "" msgstr "" #: Mailman/Handlers/CalcRecips.py:79 +#, fuzzy msgid "" "Your urgent message to the %(realname)s mailing list was not authorized for\n" "delivery. The original message as received by Mailman is attached.\n" @@ -8796,6 +8955,7 @@ msgid "Message may contain administrivia" msgstr "El mensax puede caltener solicitudes alministratives" #: Mailman/Handlers/Hold.py:84 +#, fuzzy msgid "" "Please do *not* post administrative requests to the mailing\n" "list. If you wish to subscribe, visit %(listurl)s or send a message with " @@ -8837,10 +8997,12 @@ msgid "Posting to a moderated newsgroup" msgstr "Unvíu a un grupu de noticies moderáu" #: Mailman/Handlers/Hold.py:252 +#, fuzzy msgid "Your message to %(listname)s awaits moderator approval" msgstr "Mensax unviáu a %(listname)s espera l'aprobacion del llendador" #: Mailman/Handlers/Hold.py:271 +#, fuzzy msgid "%(listname)s post from %(sender)s requires approval" msgstr "L'unvíu a %(listname)s de %(sender)s precisa d'aprobacion" @@ -8884,6 +9046,7 @@ msgid "After content filtering, the message was empty" msgstr "Dempués de la peñera del conteníu, el mensax quedó ermu" #: Mailman/Handlers/MimeDel.py:269 +#, fuzzy msgid "" "The attached message matched the %(listname)s mailing list's content " "filtering\n" @@ -8926,6 +9089,7 @@ msgid "The attached message has been automatically discarded." msgstr "El siguiente mensaxe refugóse automáticamente." #: Mailman/Handlers/Replybot.py:75 +#, fuzzy msgid "Auto-response for your message to the \"%(realname)s\" mailing list" msgstr "Rempuesta automatica al mensax empobináu a la llista \"%(realname)s\"" @@ -8934,6 +9098,7 @@ msgid "The Mailman Replybot" msgstr "El contestador automáticu de Mailman" #: Mailman/Handlers/Scrubber.py:208 +#, fuzzy msgid "" "An embedded and charset-unspecified text was scrubbed...\n" "Name: %(filename)s\n" @@ -8948,6 +9113,7 @@ msgid "HTML attachment scrubbed and removed" msgstr "Desaniciáu'l documentu HTML axuntu" #: Mailman/Handlers/Scrubber.py:234 Mailman/Handlers/Scrubber.py:259 +#, fuzzy msgid "" "An HTML attachment was scrubbed...\n" "URL: %(url)s\n" @@ -8968,6 +9134,7 @@ msgid "unknown sender" msgstr "remitente desconocíu" #: Mailman/Handlers/Scrubber.py:276 +#, fuzzy msgid "" "An embedded message was scrubbed...\n" "From: %(who)s\n" @@ -8984,6 +9151,7 @@ msgstr "" "Url : %(url)s\n" #: Mailman/Handlers/Scrubber.py:308 +#, fuzzy msgid "" "A non-text attachment was scrubbed...\n" "Name: %(filename)s\n" @@ -9000,6 +9168,7 @@ msgstr "" "Url : %(url)s\n" #: Mailman/Handlers/Scrubber.py:347 +#, fuzzy msgid "Skipped content of type %(partctype)s\n" msgstr "Saltada la triba de conteníu %(partctype)s\n" @@ -9031,6 +9200,7 @@ msgid "Message rejected by filter rule match" msgstr "Mensax refugáu por activar una regla de peñera" #: Mailman/Handlers/ToDigest.py:173 +#, fuzzy msgid "%(realname)s Digest, Vol %(volume)d, Issue %(issue)d" msgstr "Resume de %(realname)s, Vol %(volume)d, Unvíu %(issue)d" @@ -9067,6 +9237,7 @@ msgid "End of " msgstr "Fin de " #: Mailman/ListAdmin.py:309 +#, fuzzy msgid "Posting of your message titled \"%(subject)s\"" msgstr "El mensax unviáu tenía como asuntu \"%(subject)s\"" @@ -9079,6 +9250,7 @@ msgid "Forward of moderated message" msgstr "Reunvíu de mensax moderáu" #: Mailman/ListAdmin.py:405 +#, fuzzy msgid "New subscription request to list %(realname)s from %(addr)s" msgstr "Peticion de soscrición a la llista %(realname)s de %(addr)s" @@ -9092,6 +9264,7 @@ msgid "via admin approval" msgstr "Siguir esperando aprobación" #: Mailman/ListAdmin.py:466 +#, fuzzy msgid "New unsubscription request from %(realname)s by %(addr)s" msgstr "Solicitú de baxa de la llista %(realname)s de %(addr)s" @@ -9104,10 +9277,12 @@ msgid "Original Message" msgstr "Mensax orixinal" #: Mailman/ListAdmin.py:526 +#, fuzzy msgid "Request to mailing list %(realname)s rejected" msgstr "La peticion a la llista de corréu %(realname)s refugóse" #: Mailman/MTA/Manual.py:66 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been created via the through-the-web\n" "interface. In order to complete the activation of this mailing list, the\n" @@ -9135,14 +9310,17 @@ msgstr "" "programa `newaliases':\n" #: Mailman/MTA/Manual.py:82 +#, fuzzy msgid "## %(listname)s mailing list" msgstr "## llista de corréu %(listname)s" #: Mailman/MTA/Manual.py:99 +#, fuzzy msgid "Mailing list creation request for list %(listname)s" msgstr "Solicitú de criación de la llista de corréu %(listname)s" #: Mailman/MTA/Manual.py:113 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been removed via the through-the-web\n" "interface. In order to complete the de-activation of this mailing list, " @@ -9162,6 +9340,7 @@ msgstr "" "Equí tán les entraes que tienen de desaniciase nel ficheru /etc/aliases:\n" #: Mailman/MTA/Manual.py:123 +#, fuzzy msgid "" "\n" "To finish removing your mailing list, you must edit your /etc/aliases (or\n" @@ -9179,14 +9358,17 @@ msgstr "" "## Llista de corréu %(listname)s" #: Mailman/MTA/Manual.py:142 +#, fuzzy msgid "Mailing list removal request for list %(listname)s" msgstr "Solicitú de desaniciáu de la llista de corréu %(listname)s" #: Mailman/MTA/Postfix.py:442 +#, fuzzy msgid "checking permissions on %(file)s" msgstr "comprobando los permisos de %(file)s" #: Mailman/MTA/Postfix.py:452 +#, fuzzy msgid "%(file)s permissions must be 0664 (got %(octmode)s)" msgstr "Los permisos de %(file)s tendríen de ser 0664 (y son %(octmode)s)" @@ -9200,37 +9382,45 @@ msgid "(fixing)" msgstr "(iguando)" #: Mailman/MTA/Postfix.py:470 +#, fuzzy msgid "checking ownership of %(dbfile)s" msgstr "Comprobando la propiedá de %(dbfile)s" #: Mailman/MTA/Postfix.py:478 +#, fuzzy msgid "%(dbfile)s owned by %(owner)s (must be owned by %(user)s" msgstr "" "el propietariu de%(dbfile)s ye %(owner)s (tien de pertenecer a %(user)s" #: Mailman/MTA/Postfix.py:491 +#, fuzzy msgid "%(dbfile)s permissions must be 0664 (got %(octmode)s)" msgstr "Los permisos de %(dbfile)s tendríen de ser 0664 (y son %(octmode)s)" #: Mailman/MailList.py:219 +#, fuzzy msgid "Your confirmation is required to join the %(listname)s mailing list" msgstr "" "Fai falta que confirmes pa soscribite a la llista de corréu %(listname)s." #: Mailman/MailList.py:230 +#, fuzzy msgid "Your confirmation is required to leave the %(listname)s mailing list" msgstr "Fai falta que confirmes p'abandonar la llista de corréu %(listname)s" #: Mailman/MailList.py:999 Mailman/MailList.py:1492 +#, fuzzy msgid " from %(remote)s" msgstr " de %(remote)s" #: Mailman/MailList.py:1043 +#, fuzzy msgid "subscriptions to %(realname)s require moderator approval" msgstr "" "les soscriciones a %(realname)s necesiten l'aprobación del alministrador" #: Mailman/MailList.py:1125 bin/add_members:299 +#, fuzzy msgid "%(realname)s subscription notification" msgstr "Notificación de soscrición a %(realname)s" @@ -9239,6 +9429,7 @@ msgid "unsubscriptions require moderator approval" msgstr "les baxes de %(realname)s necesiten l'aprobación del llendador" #: Mailman/MailList.py:1166 +#, fuzzy msgid "%(realname)s unsubscribe notification" msgstr "Notificación de desoscrición a %(realname)s" @@ -9258,6 +9449,7 @@ msgid "via web confirmation" msgstr "Cadena de confirmación incorreuta" #: Mailman/MailList.py:1396 +#, fuzzy msgid "subscriptions to %(name)s require administrator approval" msgstr "La soscrición a %(name)s requier aprobación del alministrador" @@ -9276,6 +9468,7 @@ msgid "Last autoresponse notification for today" msgstr "Cabera notificación d'autorempuesta pa güei" #: Mailman/Queue/BounceRunner.py:360 +#, fuzzy msgid "" "The attached message was received as a bounce, but either the bounce format\n" "was not recognized, or no member addresses could be extracted from it. " @@ -9365,6 +9558,7 @@ msgid "Original message suppressed by Mailman site configuration\n" msgstr "" #: Mailman/htmlformat.py:675 +#, fuzzy msgid "Delivered by Mailman
                version %(version)s" msgstr "Entregáu per Mailman
                versión %(version)s" @@ -9453,6 +9647,7 @@ msgid "Server Local Time" msgstr "Hora llocal del sirvidor" #: Mailman/i18n.py:180 +#, fuzzy msgid "" "%(wday)s %(mon)s %(day)2i %(hh)02i:%(mm)02i:%(ss)02i %(tzname)s %(year)04i" msgstr "" @@ -9570,6 +9765,7 @@ msgstr "" "ficheros puede ser `-'.\n" #: bin/add_members:162 bin/add_members:172 +#, fuzzy msgid "Already a member: %(member)s" msgstr "Yá ta soscritu: %(member)s" @@ -9578,10 +9774,12 @@ msgid "Bad/Invalid email address: blank line" msgstr "Direición de corréu-e incorreuta/inválida: llinia erma" #: bin/add_members:180 +#, fuzzy msgid "Bad/Invalid email address: %(member)s" msgstr "Direición de corréu-e incorreuta/inválida: %(member)s" #: bin/add_members:182 +#, fuzzy msgid "Hostile address (illegal characters): %(member)s" msgstr "Direición hostil (carauteres non válidos): %(member)s" @@ -9591,14 +9789,17 @@ msgid "Invited: %(member)s" msgstr "soscritu: %(member)s" #: bin/add_members:187 +#, fuzzy msgid "Subscribed: %(member)s" msgstr "soscritu: %(member)s" #: bin/add_members:237 +#, fuzzy msgid "Bad argument to -w/--welcome-msg: %(arg)s" msgstr "Argumentu incorreutu a -w/--welcome-msg: %(arg)s" #: bin/add_members:244 +#, fuzzy msgid "Bad argument to -a/--admin-notify: %(arg)s" msgstr "Argumentu incorreutu a -a/--admin-notify: %(arg)s" @@ -9615,6 +9816,7 @@ msgstr "" #: bin/add_members:261 bin/config_list:110 bin/export.py:271 bin/find_member:97 #: bin/inject:91 bin/list_admins:90 bin/list_members:252 bin/sync_members:222 #: cron/bumpdigests:86 +#, fuzzy msgid "No such list: %(listname)s" msgstr "Nun esiste llista: %(listname)s" @@ -9625,6 +9827,7 @@ msgid "Nothing to do." msgstr "Res que facer." #: bin/arch:19 +#, fuzzy msgid "" "Rebuild a list's archive.\n" "\n" @@ -9718,6 +9921,7 @@ msgid "listname is required" msgstr "Fae falta un nome de llista" #: bin/arch:143 bin/change_pw:107 bin/config_list:257 +#, fuzzy msgid "" "No such list \"%(listname)s\"\n" "%(e)s" @@ -9726,10 +9930,12 @@ msgstr "" "%(e)s" #: bin/arch:168 +#, fuzzy msgid "Cannot open mbox file %(mbox)s: %(msg)s" msgstr "Nun pudo abrise'l ficheru mbox %(mbox)s: %(msg)s" #: bin/b4b5-archfix:19 +#, fuzzy msgid "" "Fix the MM2.1b4 archives.\n" "\n" @@ -9879,6 +10085,7 @@ msgstr "" " Amosar esti mensax y colar.\n" #: bin/change_pw:145 +#, fuzzy msgid "Bad arguments: %(strargs)s" msgstr "Argumentu incorreutu: %(strargs)s" @@ -9887,14 +10094,17 @@ msgid "Empty list passwords are not allowed" msgstr "Nun se permiten contraseñes ermes" #: bin/change_pw:181 +#, fuzzy msgid "New %(listname)s password: %(notifypassword)s" msgstr "Contraseña nueva de %(listname)s: %(notifypassword)s" #: bin/change_pw:190 +#, fuzzy msgid "Your new %(listname)s list password" msgstr "La to contraseña nueva de la llista %(listname)s" #: bin/change_pw:191 +#, fuzzy msgid "" "The site administrator at %(hostname)s has changed the password for your\n" "mailing list %(listname)s. It is now\n" @@ -9923,6 +10133,7 @@ msgstr "" " %(adminurl)s\n" #: bin/check_db:19 +#, fuzzy msgid "" "Check a list's config database file for integrity.\n" "\n" @@ -10002,10 +10213,12 @@ msgid "List:" msgstr "Llista:" #: bin/check_db:148 +#, fuzzy msgid " %(file)s: okay" msgstr " %(file)s: Correuto" #: bin/check_perms:20 +#, fuzzy msgid "" "Check the permissions for the Mailman installation.\n" "\n" @@ -10026,44 +10239,54 @@ msgstr "" "estendía.\n" #: bin/check_perms:110 +#, fuzzy msgid " checking gid and mode for %(path)s" msgstr " comprobando gid y mou de %(path)s" #: bin/check_perms:122 +#, fuzzy msgid "%(path)s bad group (has: %(groupname)s, expected %(MAILMAN_GROUP)s)" msgstr "" "%(path)s tien un grupu incorreutu (tien: %(groupname)s, esperábase que " "tuviera %(MAILMAN_GROUP)s)" #: bin/check_perms:151 +#, fuzzy msgid "directory permissions must be %(octperms)s: %(path)s" msgstr "los permisos del direutoriu tienen que ser %(octperms)s: %(path)s" #: bin/check_perms:160 +#, fuzzy msgid "source perms must be %(octperms)s: %(path)s" msgstr "los permisos tienen que ser %(octperms)s: %(path)s" #: bin/check_perms:171 +#, fuzzy msgid "article db files must be %(octperms)s: %(path)s" msgstr "los permisos de los ficheros db tienen que ser %(octperms)s: %(path)s" #: bin/check_perms:183 +#, fuzzy msgid "checking mode for %(prefix)s" msgstr "comprebando'l mou pa %(prefix)s" #: bin/check_perms:193 +#, fuzzy msgid "WARNING: directory does not exist: %(d)s" msgstr "AVISU: el direutoriu nun esiste: %(d)s" #: bin/check_perms:197 +#, fuzzy msgid "directory must be at least 02775: %(d)s" msgstr "El direutoriu tien que tar como mínimu a 02775: %(d)s" #: bin/check_perms:209 +#, fuzzy msgid "checking perms on %(private)s" msgstr "Comprebando los permisos en %(private)s" #: bin/check_perms:214 +#, fuzzy msgid "%(private)s must not be other-readable" msgstr "%(private)s nun puede ser llexible por terceros" @@ -10086,6 +10309,7 @@ msgid "mbox file must be at least 0660:" msgstr "El ficheru mbox tien que tar como mínimo a 0660" #: bin/check_perms:263 +#, fuzzy msgid "%(dbdir)s \"other\" perms must be 000" msgstr "Los permisos de %(dbdir)s pal \"restu\" tienen que ser 000" @@ -10094,26 +10318,32 @@ msgid "checking cgi-bin permissions" msgstr "comprebando los permisos de los cgi-bin" #: bin/check_perms:278 +#, fuzzy msgid " checking set-gid for %(path)s" msgstr " comprebando'l set-gid de %(path)s" #: bin/check_perms:282 +#, fuzzy msgid "%(path)s must be set-gid" msgstr "%(path)s tien que ser sert-gid" #: bin/check_perms:292 +#, fuzzy msgid "checking set-gid for %(wrapper)s" msgstr "comprebando'l set-gid de %(wrapper)s" #: bin/check_perms:296 +#, fuzzy msgid "%(wrapper)s must be set-gid" msgstr "%(wrapper)s tien que ser set-gid" #: bin/check_perms:306 +#, fuzzy msgid "checking permissions on %(pwfile)s" msgstr "comprebando los permisos de %(pwfile)s" #: bin/check_perms:315 +#, fuzzy msgid "%(pwfile)s permissions must be exactly 0640 (got %(octmode)s)" msgstr "" "Los permisos de %(pwfile)s tienen que ser esautamente 06740 (tien " @@ -10124,10 +10354,12 @@ msgid "checking permissions on list data" msgstr "comprebando los permisos sobro los datos de la llista" #: bin/check_perms:348 +#, fuzzy msgid " checking permissions on: %(path)s" msgstr " comprebando los permisos de %(path)s" #: bin/check_perms:356 +#, fuzzy msgid "file permissions must be at least 660: %(path)s" msgstr "Los permisos del ficheru tienen que tar como mínimu a 660: %(path)s" @@ -10180,8 +10412,8 @@ msgstr "" "Llimpiar un ficheru .mbox\n" "\n" "L'archivador gueta llinies Unix dixebrando mensaxes nun ficheru mbox.\n" -"Debío a la compatibilidá, gueta específicamente llinies qu'entamen con \"From" -"\"\n" +"Debío a la compatibilidá, gueta específicamente llinies qu'entamen con " +"\"From\"\n" "-- Por ex. les lletres F en mayúscules, r minúscules, o, m, espaciu en " "blancu,\n" "ignorando cualisquier cosa más na mesma llinia.\n" @@ -10217,6 +10449,7 @@ msgid "Unix-From line changed: %(lineno)d" msgstr "Llinia From estilu Unix camudada: %(lineno)d" #: bin/cleanarch:111 +#, fuzzy msgid "Bad status number: %(arg)s" msgstr "Númberu d'estáu incorreutu: %(arg)s" @@ -10367,10 +10600,12 @@ msgid " original address removed:" msgstr " la direición orixinal esborróse:" #: bin/clone_member:202 +#, fuzzy msgid "Not a valid email address: %(toaddr)s" msgstr "Nun ye una direición válida: %(toaddr)s" #: bin/clone_member:215 +#, fuzzy msgid "" "Error opening list \"%(listname)s\", skipping.\n" "%(e)s" @@ -10489,6 +10724,7 @@ msgstr "" "\n" #: bin/config_list:118 +#, fuzzy msgid "" "# -*- python -*-\n" "# -*- coding: %(charset)s -*-\n" @@ -10510,22 +10746,27 @@ msgid "legal values are:" msgstr "los valores correutos son:" #: bin/config_list:270 +#, fuzzy msgid "attribute \"%(k)s\" ignored" msgstr "inoráu l'atributu \"%(k)s\"" #: bin/config_list:273 +#, fuzzy msgid "attribute \"%(k)s\" changed" msgstr "atributu \"%(k)s\" camudáu" #: bin/config_list:279 +#, fuzzy msgid "Non-standard property restored: %(k)s" msgstr "Propiedá non estándar restaurada: %(k)s" #: bin/config_list:288 +#, fuzzy msgid "Invalid value for property: %(k)s" msgstr "Valor inválidu de la propiedá: %(k)s" #: bin/config_list:291 +#, fuzzy msgid "Bad email address for option %(k)s: %(v)s" msgstr "Direición de corréu-e incorreuta na opción %(k)s: %(v)s" @@ -10591,18 +10832,22 @@ msgstr "" " Non amosar los mensaxes d'estáu.\n" #: bin/discard:94 +#, fuzzy msgid "Ignoring non-held message: %(f)s" msgstr "Inorando mensax non reteníu: %(f)s" #: bin/discard:100 +#, fuzzy msgid "Ignoring held msg w/bad id: %(f)s" msgstr "Inorando mensax reteníu con identificación errónea: %(f)s" #: bin/discard:112 +#, fuzzy msgid "Discarded held msg #%(id)s for list %(listname)s" msgstr "Descartáu mensax reteníu #%(id)s dirixíu a la llista %(listname)s" #: bin/dumpdb:19 +#, fuzzy msgid "" "Dump the contents of any Mailman `database' file.\n" "\n" @@ -10675,6 +10920,7 @@ msgid "No filename given." msgstr "Ensin nome de ficheru dau." #: bin/dumpdb:108 +#, fuzzy msgid "Bad arguments: %(pargs)s" msgstr "Argumentos incorreutos: %(pargs)s" @@ -10683,14 +10929,17 @@ msgid "Please specify either -p or -m." msgstr "Por favor, especifica -p o -m." #: bin/dumpdb:133 +#, fuzzy msgid "[----- start %(typename)s file -----]" msgstr "[--- entamu %(typename)s ficheru ---]" #: bin/dumpdb:139 +#, fuzzy msgid "[----- end %(typename)s file -----]" msgstr "[--- final %(typename)s ficheru --]" #: bin/dumpdb:142 +#, fuzzy msgid "<----- start object %(cnt)s ----->" msgstr "<----- entamu oxetut %(cnt)s ----->" @@ -10910,6 +11159,7 @@ msgid "Setting web_page_url to: %(web_page_url)s" msgstr "Asignando'l valor %(web_page_url)s a web_page_url" #: bin/fix_url.py:88 +#, fuzzy msgid "Setting host_name to: %(mailhost)s" msgstr "Asignando'l valor %(mailhost)s a host_name" @@ -11001,6 +11251,7 @@ msgstr "" "Si nun se pon, usase la entrada estándar.\n" #: bin/inject:84 +#, fuzzy msgid "Bad queue directory: %(qdir)s" msgstr "El direutoriu que representa la cola ye incorreutu: %(qdir)s" @@ -11009,6 +11260,7 @@ msgid "A list name is required" msgstr "Fae falta un nome de llista" #: bin/list_admins:20 +#, fuzzy msgid "" "List all the owners of a mailing list.\n" "\n" @@ -11054,6 +11306,7 @@ msgstr "" "indicase más d'una llista na llinia d'ordenes.\n" #: bin/list_admins:97 +#, fuzzy msgid "List: %(listname)s, \tOwners: %(owners)s" msgstr "Llista: %(listname)s, \tPropietarios: %(owners)s" @@ -11207,8 +11460,8 @@ msgstr "" " --nomail[=why] / -n [why]\n" " Amuesa los soscritores que tienen la receición del corréu " "desactivada.\n" -" Un argumentu opcional puede ser \"byadmin\", \"byuser\", \"bybounce" -"\" o \"unknown\"\n" +" Un argumentu opcional puede ser \"byadmin\", \"byuser\", " +"\"bybounce\" o \"unknown\"\n" " que seleiciona a aquellos soscritores que tienen la receición " "desactivada por\n" " dicha razón. Tamién puede ser \"enabled\" que sólo imprenta " @@ -11243,10 +11496,12 @@ msgstr "" "\n" #: bin/list_members:198 +#, fuzzy msgid "Bad --nomail option: %(why)s" msgstr "Fallu n'opción --nomail: %(why)s" #: bin/list_members:209 +#, fuzzy msgid "Bad --digest option: %(kind)s" msgstr "Fallu n'opción --digest: %(kind)s" @@ -11260,6 +11515,7 @@ msgid "Could not open file for writing:" msgstr "Nun pudo abrise'l ficheru en mou escritura:" #: bin/list_owners:20 +#, fuzzy msgid "" "List the owners of a mailing list, or all mailing lists.\n" "\n" @@ -11313,6 +11569,7 @@ msgid "" msgstr "" #: bin/mailmanctl:20 +#, fuzzy msgid "" "Primary start-up and shutdown script for Mailman's qrunner daemon.\n" "\n" @@ -11506,6 +11763,7 @@ msgstr "" " un mensaxe.\n" #: bin/mailmanctl:152 +#, fuzzy msgid "PID unreadable in: %(pidfile)s" msgstr "PID ilexible en: %(pidfile)s" @@ -11514,6 +11772,7 @@ msgid "Is qrunner even running?" msgstr "¿Ta qrunner corriendo?" #: bin/mailmanctl:160 +#, fuzzy msgid "No child with pid: %(pid)s" msgstr "Nengún fíu con pid %(pid)s" @@ -11540,6 +11799,7 @@ msgstr "" "ya hubiera otru qrunner principal executándose.\n" #: bin/mailmanctl:233 +#, fuzzy msgid "" "The master qrunner lock could not be acquired, because it appears as if " "some\n" @@ -11566,10 +11826,12 @@ msgstr "" "Colando." #: bin/mailmanctl:280 cron/mailpasswds:119 +#, fuzzy msgid "Site list is missing: %(sitelistname)s" msgstr "El sitiu de la llista nun s'alcuentra: %(sitelistname)s" #: bin/mailmanctl:305 +#, fuzzy msgid "Run this program as root or as the %(name)s user, or use -u." msgstr "" "Executa esti programa como root, como l'usuario %(name)s o usa la opción -u." @@ -11579,6 +11841,7 @@ msgid "No command given." msgstr "Nun se dió denguna orde" #: bin/mailmanctl:339 +#, fuzzy msgid "Bad command: %(command)s" msgstr "Orde incorreuta: %(command)s" @@ -11603,6 +11866,7 @@ msgid "Starting Mailman's master qrunner." msgstr "Arrancando'l qrunner principal de Mailman" #: bin/mmsitepass:19 +#, fuzzy msgid "" "Set the site password, prompting from the terminal.\n" "\n" @@ -11657,6 +11921,7 @@ msgid "list creator" msgstr "criador de la llista" #: bin/mmsitepass:86 +#, fuzzy msgid "New %(pwdesc)s password: " msgstr "Contraseña nueva de %(pwdesc)s" @@ -11931,6 +12196,7 @@ msgstr "" "Decátate que los nomes de les llistes conviértense a minúscules.\n" #: bin/newlist:162 +#, fuzzy msgid "Unknown language: %(lang)s" msgstr "Llingua desconocida: %(lang)s" @@ -11943,6 +12209,7 @@ msgid "Enter the email of the person running the list: " msgstr "Indica la direición de corréu de la persona que xestionará la llista: " #: bin/newlist:193 +#, fuzzy msgid "Initial %(listname)s password: " msgstr "Contraseña inicial de %(listname)s: " @@ -11952,15 +12219,17 @@ msgstr "La contrase #: bin/newlist:220 msgid "" -" - owner addresses need to be fully-qualified names like \"owner@example.com" -"\", not just \"owner\"." +" - owner addresses need to be fully-qualified names like \"owner@example." +"com\", not just \"owner\"." msgstr "" #: bin/newlist:244 +#, fuzzy msgid "Hit enter to notify %(listname)s owner..." msgstr "Calca enter pa notificar al propietariu de la llista %(listname)s..." #: bin/qrunner:20 +#, fuzzy msgid "" "Run one or more qrunners, once or repeatedly.\n" "\n" @@ -12088,6 +12357,7 @@ msgstr "" "separadamente.\n" #: bin/qrunner:179 +#, fuzzy msgid "%(name)s runs the %(runnername)s qrunner" msgstr "%(name)s executa'l qrunner %(runnername)s" @@ -12100,6 +12370,7 @@ msgid "No runner name given." msgstr "Nun se dió nengún nome de runner." #: bin/rb-archfix:21 +#, fuzzy msgid "" "Reduce disk space usage for Pipermail archives.\n" "\n" @@ -12245,18 +12516,22 @@ msgstr "" " direición1 ... son les direiciones adicionales a desaniciar.\n" #: bin/remove_members:156 +#, fuzzy msgid "Could not open file for reading: %(filename)s." msgstr "Nun pudo abrise ficheru pa lleer: %(filename)s." #: bin/remove_members:163 +#, fuzzy msgid "Error opening list %(listname)s... skipping." msgstr "Fallu abriendo la llista \"%(listname)s\"... saltándola." #: bin/remove_members:173 +#, fuzzy msgid "No such member: %(addr)s" msgstr "Nun esiste tal soscritor: %(addr)s" #: bin/remove_members:178 +#, fuzzy msgid "User `%(addr)s' removed from list: %(listname)s." msgstr "El soscritor `%(addr)s' diose de baxa de la llista %(listname)s." @@ -12298,10 +12573,12 @@ msgstr "" " Amuesa qué ye lo que ta faciendo'l script.\n" #: bin/reset_pw.py:77 +#, fuzzy msgid "Changing passwords for list: %(listname)s" msgstr "Camudando les contraseñes de la llista: %(listname)s" #: bin/reset_pw.py:83 +#, fuzzy msgid "New password for member %(member)40s: %(randompw)s" msgstr "Contraseña nueva pal soscritor %(member)40s: %(randompw)s" @@ -12346,18 +12623,22 @@ msgstr "" " amosar esti mensax d'aida y colar.\n" #: bin/rmlist:73 bin/rmlist:76 +#, fuzzy msgid "Removing %(msg)s" msgstr "Desaniciando %(msg)s" #: bin/rmlist:81 +#, fuzzy msgid "%(listname)s %(msg)s not found as %(filename)s" msgstr "%(msg)s de %(listname)s nun s'alcontró como %(filename)s" #: bin/rmlist:105 +#, fuzzy msgid "No such list (or list already deleted): %(listname)s" msgstr "Nun esiste tal llista (o esborróse dafechu): %(listname)s" #: bin/rmlist:108 +#, fuzzy msgid "No such list: %(listname)s. Removing its residual archives." msgstr "Nun esiste llista: %(listname)s. Esborrando ficheros residuales." @@ -12417,6 +12698,7 @@ msgstr "" "Exemplu: show_qfiles qfiles/shunt/*.pck\n" #: bin/sync_members:19 +#, fuzzy msgid "" "Synchronize a mailing list's membership with a flat file.\n" "\n" @@ -12553,6 +12835,7 @@ msgstr "" " Necesario. Indica la llista a sincronizar.\n" #: bin/sync_members:115 +#, fuzzy msgid "Bad choice: %(yesno)s" msgstr "Mala eleición: %(yesno)s" @@ -12569,6 +12852,7 @@ msgid "No argument to -f given" msgstr "Nun se dio argumentu a -f" #: bin/sync_members:172 +#, fuzzy msgid "Illegal option: %(opt)s" msgstr "Opción illegal: %(opt)s" @@ -12581,6 +12865,7 @@ msgid "Must have a listname and a filename" msgstr "Tien que tener un nome de llista y un nome de ficheru" #: bin/sync_members:191 +#, fuzzy msgid "Cannot read address file: %(filename)s: %(msg)s" msgstr "Nun puede lleese ficheru de direiciones: %(filename)s: %(msg)s" @@ -12597,14 +12882,17 @@ msgid "You must fix the preceding invalid addresses first." msgstr "Primero tienes qu'iguar la direición non válida precedente." #: bin/sync_members:264 +#, fuzzy msgid "Added : %(s)s" msgstr "Amestada: %(s)s" #: bin/sync_members:289 +#, fuzzy msgid "Removed: %(s)s" msgstr "Desaniciáu: %(s)s" #: bin/transcheck:19 +#, fuzzy msgid "" "\n" "Check a given Mailman translation, making sure that variables and\n" @@ -12684,6 +12972,7 @@ msgid "scan the po file comparing msgids with msgstrs" msgstr "recuerre'l ficheru po comparando msgids con msgstrs" #: bin/unshunt:20 +#, fuzzy msgid "" "Move a message from the shunt queue to the original queue.\n" "\n" @@ -12714,6 +13003,7 @@ msgstr "" "resultará en perder tolos mensaxes encolaos.\n" #: bin/unshunt:85 +#, fuzzy msgid "" "Cannot unshunt message %(filebase)s, skipping:\n" "%(e)s" @@ -12722,6 +13012,7 @@ msgstr "" "%(e)s" #: bin/update:20 +#, fuzzy msgid "" "Perform all necessary upgrades.\n" "\n" @@ -12759,14 +13050,17 @@ msgstr "" "versión anterior. Sabes de versiones vieyes, fast0 la 1.0b4 (?).\n" #: bin/update:107 +#, fuzzy msgid "Fixing language templates: %(listname)s" msgstr "Igüando la plantilla de les llingües: %(listname)s" #: bin/update:196 bin/update:711 +#, fuzzy msgid "WARNING: could not acquire lock for list: %(listname)s" msgstr "AVISU: nun pudo algamase'l bloquéu de la llista: %(listname)s" #: bin/update:215 +#, fuzzy msgid "Resetting %(n)s BYBOUNCEs disabled addrs with no bounce info" msgstr "" "Reafitando %(n)s BYBOUNCEs direiciones desactivaes ensin información de " @@ -12785,6 +13079,7 @@ msgstr "" "con b6, polo que toi renomando a %(mbox_dir)s.tmp y procediendo." #: bin/update:255 +#, fuzzy msgid "" "\n" "%(listname)s has both public and private mbox archives. Since this list\n" @@ -12834,6 +13129,7 @@ msgid "- updating old private mbox file" msgstr "- anovando l'antiguu ficheru mbox priváu" #: bin/update:295 +#, fuzzy msgid "" " unknown file in the way, moving\n" " %(o_pri_mbox_file)s\n" @@ -12850,6 +13146,7 @@ msgid "- updating old public mbox file" msgstr "- anovando l'antiguu ficheru mbox públicu" #: bin/update:317 +#, fuzzy msgid "" " unknown file in the way, moving\n" " %(o_pub_mbox_file)s\n" @@ -12878,18 +13175,22 @@ msgid "- %(o_tmpl)s doesn't exist, leaving untouched" msgstr "- %(o_tmpl)s nun esiste, dexándolo intautu" #: bin/update:396 +#, fuzzy msgid "removing directory %(src)s and everything underneath" msgstr "desaniciando'l direutoriu %(src)s y tolo que hai dientro" #: bin/update:399 +#, fuzzy msgid "removing %(src)s" msgstr "desaniciando %(src)s" #: bin/update:403 +#, fuzzy msgid "Warning: couldn't remove %(src)s -- %(rest)s" msgstr "Avisu: nun pudo desaniciase %(src)s -- %(rest)s" #: bin/update:408 +#, fuzzy msgid "couldn't remove old file %(pyc)s -- %(rest)s" msgstr "nun pudo desaniciase'l ficheru antiguu %(pyc)s -- %(rest)s" @@ -12898,14 +13199,17 @@ msgid "updating old qfiles" msgstr "anovando los qfiles antiguos" #: bin/update:455 +#, fuzzy msgid "Warning! Not a directory: %(dirpath)s" msgstr "¡Avisu! Nun ye un direutoriu: %(dirpath)s" #: bin/update:530 +#, fuzzy msgid "message is unparsable: %(filebase)s" msgstr "El mensax nun ye analizable: %(filebase)s" #: bin/update:544 +#, fuzzy msgid "Warning! Deleting empty .pck file: %(pckfile)s" msgstr "¡Avisu! Desaniciando'l ficheru .pck ermu: %(pckfile)s" @@ -12918,10 +13222,12 @@ msgid "Updating Mailman 2.1.4 pending.pck database" msgstr "Anovando la base de datos pending.pck de Mailman 2.1.4" #: bin/update:598 +#, fuzzy msgid "Ignoring bad pended data: %(key)s: %(val)s" msgstr "Inorando los datos pendientes frayaos: %(key)s: %(val)s" #: bin/update:614 +#, fuzzy msgid "WARNING: Ignoring duplicate pending ID: %(id)s." msgstr "AVISU: Inorando l'ID pendiente duplicáu: %(id)s." @@ -12947,6 +13253,7 @@ msgid "done" msgstr "fecho" #: bin/update:691 +#, fuzzy msgid "Updating mailing list: %(listname)s" msgstr "anovando la llista de corréu" @@ -13008,6 +13315,7 @@ msgid "No updates are necessary." msgstr "Nun faen falta anovamientos." #: bin/update:796 +#, fuzzy msgid "" "Downgrade detected, from version %(hexlversion)s to version %(hextversion)s\n" "This is probably not safe.\n" @@ -13019,10 +13327,12 @@ msgstr "" "Colando." #: bin/update:801 +#, fuzzy msgid "Upgrading from version %(hexlversion)s to %(hextversion)s" msgstr "Anovando de la versión %(hexlversion)s a la %(hextversion)s" #: bin/update:810 +#, fuzzy msgid "" "\n" "ERROR:\n" @@ -13309,6 +13619,7 @@ msgstr "" " " #: bin/withlist:175 +#, fuzzy msgid "Unlocking (but not saving) list: %(listname)s" msgstr "Desbloquiando (pero ensin guardar) la llista: %(listname)s" @@ -13317,6 +13628,7 @@ msgid "Finalizing" msgstr "Finando" #: bin/withlist:188 +#, fuzzy msgid "Loading list %(listname)s" msgstr "Cargando la llista %(listname)s" @@ -13329,6 +13641,7 @@ msgid "(unlocked)" msgstr "(desbloquiáu)" #: bin/withlist:197 +#, fuzzy msgid "Unknown list: %(listname)s" msgstr "Llista desconocida: %(listname)s" @@ -13341,18 +13654,22 @@ msgid "--all requires --run" msgstr "--all requier --run" #: bin/withlist:266 +#, fuzzy msgid "Importing %(module)s..." msgstr "Importando %(module)s..." #: bin/withlist:270 +#, fuzzy msgid "Running %(module)s.%(callable)s()..." msgstr "Executando %(module)s.%(callable)s()..." #: bin/withlist:291 +#, fuzzy msgid "The variable `m' is the %(listname)s MailList instance" msgstr "La variable 'm' ye la instancia Llista de corréu %(listname)s" #: cron/bumpdigests:19 +#, fuzzy msgid "" "Increment the digest volume number and reset the digest number to one.\n" "\n" @@ -13381,6 +13698,7 @@ msgstr "" "nome de nenguna llista, aplícase a toes.\n" #: cron/checkdbs:20 +#, fuzzy msgid "" "Check for pending admin requests and mail the list owners if necessary.\n" "\n" @@ -13410,10 +13728,12 @@ msgstr "" "\n" #: cron/checkdbs:121 +#, fuzzy msgid "%(count)d %(realname)s moderator request(s) waiting" msgstr "%(count)d solicitudes de %(realname)s a la espera del llendador" #: cron/checkdbs:124 +#, fuzzy msgid "%(realname)s moderator request check result" msgstr "Resultáu de la comprebación solicitada pol llendador de %(realname)s" @@ -13435,6 +13755,7 @@ msgstr "" "Unvíos pendientes:" #: cron/checkdbs:169 +#, fuzzy msgid "" "From: %(sender)s on %(date)s\n" "Subject: %(subject)s\n" @@ -13445,6 +13766,7 @@ msgstr "" "Motivu: %(reason)s" #: cron/cull_bad_shunt:20 +#, fuzzy msgid "" "Cull bad and shunt queues, recommended once per day.\n" "\n" @@ -13480,6 +13802,7 @@ msgstr "" " Amosar esti mensax y colar.\n" #: cron/disabled:20 +#, fuzzy msgid "" "Process disabled members, recommended once per day.\n" "\n" @@ -13609,6 +13932,7 @@ msgstr "" "\n" #: cron/mailpasswds:19 +#, fuzzy msgid "" "Send password reminders for all lists to all users.\n" "\n" @@ -13662,10 +13986,12 @@ msgid "Password // URL" msgstr "Contraseña // URL" #: cron/mailpasswds:222 +#, fuzzy msgid "%(host)s mailing list memberships reminder" msgstr "Recordatorios de llista de corréu de %(host)s" #: cron/nightly_gzip:19 +#, fuzzy msgid "" "Re-generate the Pipermail gzip'd archive flat files.\n" "\n" @@ -13768,8 +14094,8 @@ msgstr "" #~ " applied" #~ msgstr "" #~ "Testu que s'incluyirá nes\n" -#~ " notificaciones de refugu que s'unvíen\n" #~ " como rempuesta a mensaxes de miembros moderaos de la llista." @@ -13879,8 +14205,8 @@ msgstr "" #~ "\n" #~ "(observe que las comillas invertidas hacen falta)\n" #~ "\n" -#~ "Posiblemente querrá borrar los ficheros -article.bak creados por este\\n" -#~ "\"\n" +#~ "Posiblemente querrá borrar los ficheros -article.bak creados por " +#~ "este\\n\"\n" #~ "\"guión cuando esté satisfecho con los resultados." #~ msgid "delivery option set" diff --git a/messages/ca/LC_MESSAGES/mailman.po b/messages/ca/LC_MESSAGES/mailman.po index 01649179..b1b88d6f 100755 --- a/messages/ca/LC_MESSAGES/mailman.po +++ b/messages/ca/LC_MESSAGES/mailman.po @@ -74,10 +74,12 @@ msgid "

                Currently, there are no archives.

                " msgstr "

                Ara mateix no hi ha cap arxiu.

                " #: Mailman/Archiver/HyperArch.py:821 +#, fuzzy msgid "Gzip'd Text%(sz)s" msgstr "Text en gzip%(sz)s" #: Mailman/Archiver/HyperArch.py:826 +#, fuzzy msgid "Text%(sz)s" msgstr "Text%(sz)s" @@ -150,18 +152,22 @@ msgid "Third" msgstr "Tercer" #: Mailman/Archiver/HyperArch.py:938 +#, fuzzy msgid "%(ord)s quarter %(year)i" msgstr "%(ord)s trimestre de %(year)i" #: Mailman/Archiver/HyperArch.py:945 +#, fuzzy msgid "%(month)s %(year)i" msgstr "%(month)s %(year)i" #: Mailman/Archiver/HyperArch.py:950 +#, fuzzy msgid "The Week Of Monday %(day)i %(month)s %(year)i" msgstr "La setmana del %(day)i %(month)s %(year)i" #: Mailman/Archiver/HyperArch.py:954 +#, fuzzy msgid "%(day)i %(month)s %(year)i" msgstr "%(day)i %(month)s %(year)i" @@ -170,10 +176,12 @@ msgid "Computing threaded index\n" msgstr "S'està calculant l'índex dels fils\n" #: Mailman/Archiver/HyperArch.py:1319 +#, fuzzy msgid "Updating HTML for article %(seq)s" msgstr "S'està actualitzant el codi HTML per a l'article %(seq)s" #: Mailman/Archiver/HyperArch.py:1326 +#, fuzzy msgid "article file %(filename)s is missing!" msgstr "no es troba el fitxer d'article %(filename)s!" @@ -191,6 +199,7 @@ msgid "Pickling archive state into " msgstr "S'està emmagatzemant l'estat de l'arxiu a " #: Mailman/Archiver/pipermail.py:453 +#, fuzzy msgid "Updating index files for archive [%(archive)s]" msgstr "S'estan actualitzant els fitxers d'índex per a l'arxiu [%(archive)s]" @@ -199,6 +208,7 @@ msgid " Thread" msgstr " Fil" #: Mailman/Archiver/pipermail.py:597 +#, fuzzy msgid "#%(counter)05d %(msgid)s" msgstr "#%(counter)05d %(msgid)s" @@ -236,6 +246,7 @@ msgid "disabled address" msgstr "inhabilitat" #: Mailman/Bouncer.py:304 +#, fuzzy msgid " The last bounce received from you was dated %(date)s" msgstr " El vostre darrer missatge retornat rebut és del %(date)s" @@ -263,6 +274,7 @@ msgstr "Administrador" #: Mailman/Cgi/options.py:101 Mailman/Cgi/private.py:108 #: Mailman/Cgi/rmlist.py:75 Mailman/Cgi/roster.py:59 #: Mailman/Cgi/subscribe.py:67 +#, fuzzy msgid "No such list %(safelistname)s" msgstr "La llista %(safelistname)s no existeix" @@ -338,6 +350,7 @@ msgstr "" " aquests usuaris no podran rebre cap correu.%(rm)r" #: Mailman/Cgi/admin.py:268 +#, fuzzy msgid "%(hostname)s mailing lists - Admin Links" msgstr "Llistes de correu de %(hostname)s - Enllaços administratius" @@ -350,6 +363,7 @@ msgid "Mailman" msgstr "Mailman" #: Mailman/Cgi/admin.py:310 +#, fuzzy msgid "" "

                There currently are no publicly-advertised %(mailmanlink)s\n" " mailing lists on %(hostname)s." @@ -358,6 +372,7 @@ msgstr "" " %(mailmanlink)s a %(hostname)s." #: Mailman/Cgi/admin.py:316 +#, fuzzy msgid "" "

                Below is the collection of publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s. Click on a list\n" @@ -372,6 +387,7 @@ msgid "right " msgstr "correcte " #: Mailman/Cgi/admin.py:325 +#, fuzzy msgid "" "To visit the administrators configuration page for an\n" " unadvertised list, open a URL similar to this one, but with a '/' " @@ -417,6 +433,7 @@ msgid "No valid variable name found." msgstr "No s'ha trobat cap nom de variable vàlid." #: Mailman/Cgi/admin.py:395 +#, fuzzy msgid "" "%(realname)s Mailing list Configuration Help\n" "
                %(varname)s Option" @@ -425,6 +442,7 @@ msgstr "" "
                opció %(varname)s" #: Mailman/Cgi/admin.py:402 +#, fuzzy msgid "Mailman %(varname)s List Option Help" msgstr "Ajuda per a l'opció de llista %(varname)s del Mailman" @@ -445,14 +463,17 @@ msgstr "" " " #: Mailman/Cgi/admin.py:431 +#, fuzzy msgid "return to the %(categoryname)s options page." msgstr "tornar a la pàgina d'opcions de la categoria %(categoryname)s." #: Mailman/Cgi/admin.py:446 +#, fuzzy msgid "%(realname)s Administration (%(label)s)" msgstr "Administració de la llista %(realname)s (%(label)s)" #: Mailman/Cgi/admin.py:447 +#, fuzzy msgid "%(realname)s mailing list administration
                %(label)s Section" msgstr "Administració de la llista de correu %(realname)s
                Secció %(label)s" @@ -534,6 +555,7 @@ msgid "Value" msgstr "Valor" #: Mailman/Cgi/admin.py:668 +#, fuzzy msgid "" "Badly formed options entry:\n" " %(record)s" @@ -634,10 +656,12 @@ msgid "Move rule down" msgstr "Mou la regla cap a avall" #: Mailman/Cgi/admin.py:870 +#, fuzzy msgid "
                (Edit %(varname)s)" msgstr "
                (Edita %(varname)s)" #: Mailman/Cgi/admin.py:872 +#, fuzzy msgid "
                (Details for %(varname)s)" msgstr "
                (Detalls per a %(varname)s)" @@ -681,6 +705,7 @@ msgstr "(ajuda)" # El %(link)s mostra un enllaç a l'ajuda del Python per a la sintaxi de les # expressions regulars (dpm) #: Mailman/Cgi/admin.py:930 +#, fuzzy msgid "Find member %(link)s:" msgstr "Cerca de subscriptors %(link)s:" @@ -693,10 +718,12 @@ msgid "Bad regular expression: " msgstr "L'expressió regular és incorrecta: " #: Mailman/Cgi/admin.py:1013 +#, fuzzy msgid "%(allcnt)s members total, %(membercnt)s shown" msgstr "%(allcnt)s subscriptors en total, %(membercnt)s mostrats" #: Mailman/Cgi/admin.py:1016 +#, fuzzy msgid "%(allcnt)s members total" msgstr "%(allcnt)s membres en total" @@ -894,6 +921,7 @@ msgstr "" " que es llista a continuació:" #: Mailman/Cgi/admin.py:1221 +#, fuzzy msgid "from %(start)s to %(end)s" msgstr "des de %(start)s fins a %(end)s" @@ -1035,6 +1063,7 @@ msgid "Change list ownership passwords" msgstr "Canvi de les contrasenyes d'administració de la llista" #: Mailman/Cgi/admin.py:1364 +#, fuzzy msgid "" "The list administrators are the people who have ultimate control " "over\n" @@ -1150,6 +1179,7 @@ msgstr "Adreça hostil (caràcters il·legals)" #: Mailman/Cgi/admin.py:1529 Mailman/Cgi/admin.py:1703 bin/add_members:175 #: bin/clone_member:136 bin/sync_members:268 +#, fuzzy msgid "Banned address (matched %(pattern)s)" msgstr "L'adreça està bandejada (coincideix amb %(pattern)s)" @@ -1252,6 +1282,7 @@ msgid "Not subscribed" msgstr "No subscrit" #: Mailman/Cgi/admin.py:1773 +#, fuzzy msgid "Ignoring changes to deleted member: %(user)s" msgstr "S'estan ignorant els canvis al membre suprimit: %(user)s" @@ -1264,10 +1295,12 @@ msgid "Error Unsubscribing:" msgstr "Error en cancel·lar la subscripció:" #: Mailman/Cgi/admindb.py:235 Mailman/Cgi/admindb.py:248 +#, fuzzy msgid "%(realname)s Administrative Database" msgstr "Base de dades administrativa de la llista %(realname)s" #: Mailman/Cgi/admindb.py:238 +#, fuzzy msgid "%(realname)s Administrative Database Results" msgstr "Resultats de la base de dades administrativa de la llista %(realname)s" @@ -1296,6 +1329,7 @@ msgid "Discard all messages marked Defer" msgstr "Descarta tots els missatges marcats com a Ajorna" #: Mailman/Cgi/admindb.py:298 +#, fuzzy msgid "all of %(esender)s's held messages." msgstr "tots els missatges retinguts de %(esender)s." @@ -1316,6 +1350,7 @@ msgid "list of available mailing lists." msgstr "llista de les llista de correu disponibles" #: Mailman/Cgi/admindb.py:362 +#, fuzzy msgid "You must specify a list name. Here is the %(link)s" msgstr "Heu d'especificar el nom d'una llista. Aquí hi ha la %(link)s" @@ -1397,6 +1432,7 @@ msgid "The sender is now a member of this list" msgstr "El remitent és ara membre de la llista" #: Mailman/Cgi/admindb.py:568 +#, fuzzy msgid "Add %(esender)s to one of these sender filters:" msgstr "Afegeix %(esender)s a un dels filtres de remitents següents:" @@ -1417,6 +1453,7 @@ msgid "Rejects" msgstr "Rebutjos" #: Mailman/Cgi/admindb.py:584 +#, fuzzy msgid "" "Ban %(esender)s from ever subscribing to this\n" " mailing list" @@ -1433,6 +1470,7 @@ msgstr "" " individual, o també podeu " #: Mailman/Cgi/admindb.py:591 +#, fuzzy msgid "view all messages from %(esender)s" msgstr "veure tots els missatges de %(esender)s" @@ -1511,6 +1549,7 @@ msgid " is already a member" msgstr " ja és un membre" #: Mailman/Cgi/admindb.py:973 +#, fuzzy msgid "%(addr)s is banned (matched: %(patt)s)" msgstr "%(addr)s està bandejat (coincideix amb: %(patt)s)" @@ -1565,6 +1604,7 @@ msgstr "" " cancel·lada." #: Mailman/Cgi/confirm.py:178 +#, fuzzy msgid "System error, bad content: %(content)s" msgstr "Error de sistema, contingut erroni: %(content)s " @@ -1602,6 +1642,7 @@ msgid "Confirm subscription request" msgstr "Confirmeu la sol·licitud de subscripció" #: Mailman/Cgi/confirm.py:259 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " subscription request to the mailing list %(listname)s. Your\n" @@ -1638,6 +1679,7 @@ msgstr "" " cas que no us vulgueu subscriure a aquesta llista." #: Mailman/Cgi/confirm.py:275 +#, fuzzy msgid "" "Your confirmation is required in order to continue with\n" " the subscription request to the mailing list %(listname)s.\n" @@ -1691,6 +1733,7 @@ msgid "Preferred language:" msgstr "Llengua preferida:" #: Mailman/Cgi/confirm.py:317 +#, fuzzy msgid "Subscribe to list %(listname)s" msgstr "Subscriu-me a la llista %(listname)s" @@ -1707,6 +1750,7 @@ msgid "Awaiting moderator approval" msgstr "S'està esperant l'aprovació del moderador" #: Mailman/Cgi/confirm.py:386 +#, fuzzy msgid "" " You have successfully confirmed your subscription request to " "the\n" @@ -1740,6 +1784,7 @@ msgid "You are already a member of this mailing list!" msgstr "Ja ets membre d'aquesta llista de correu!" #: Mailman/Cgi/confirm.py:400 +#, fuzzy msgid "" "You are currently banned from subscribing to\n" " this list. If you think this restriction is erroneous, please\n" @@ -1765,6 +1810,7 @@ msgid "Subscription request confirmed" msgstr "S'ha confirmat la sol·licitud de subscripció" #: Mailman/Cgi/confirm.py:418 +#, fuzzy msgid "" " You have successfully confirmed your subscription request for\n" " \"%(addr)s\" to the %(listname)s mailing list. A separate\n" @@ -1794,6 +1840,7 @@ msgid "Unsubscription request confirmed" msgstr "S'ha confirmat la sol·licitud de cancel·lació de subscripció" #: Mailman/Cgi/confirm.py:469 +#, fuzzy msgid "" " You have successfully unsubscribed from the %(listname)s " "mailing\n" @@ -1816,6 +1863,7 @@ msgid "Not available" msgstr "No disponible" #: Mailman/Cgi/confirm.py:498 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " unsubscription request from the mailing list %(listname)s. " @@ -1862,6 +1910,7 @@ msgid "You have canceled your change of address request." msgstr "Heu cancel·lat la vostra sol·licitud de canvi d'adreça" #: Mailman/Cgi/confirm.py:555 +#, fuzzy msgid "" "%(newaddr)s is banned from subscribing to the\n" " %(realname)s list. If you think this restriction is erroneous,\n" @@ -1873,6 +1922,7 @@ msgstr "" " a %(owneraddr)s." #: Mailman/Cgi/confirm.py:560 +#, fuzzy msgid "" "%(newaddr)s is already a member of\n" " the %(realname)s list. It is possible that you are attempting\n" @@ -1889,6 +1939,7 @@ msgid "Change of address request confirmed" msgstr "Petició de canvi de adreça confirmada" #: Mailman/Cgi/confirm.py:571 +#, fuzzy msgid "" " You have successfully changed your address on the %(listname)s\n" " mailing list from %(oldaddr)s to %(newaddr)s. " @@ -1912,6 +1963,7 @@ msgid "globally" msgstr "globalment" #: Mailman/Cgi/confirm.py:605 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " change of address request for the mailing list %(listname)s. " @@ -1935,8 +1987,8 @@ msgid "" " request." msgstr "" "Es requereix la vostra confirmació per a completar la\n" -" vostra sol·licitud de canvi d'adreça a la llista de correu " -"%(listname)s. \n" +" vostra sol·licitud de canvi d'adreça a la llista de correu " +"%(listname)s. \n" " Actualment estàs subscrit amb \n" "\n" "

                • Nom real: %(fullname)s\n" @@ -1977,6 +2029,7 @@ msgid "Sender discarded message via web." msgstr "El remitent ha descartat el missatge via web." #: Mailman/Cgi/confirm.py:674 +#, fuzzy msgid "" "The held message with the Subject:\n" " header %(subject)s could not be found. The most " @@ -1997,6 +2050,7 @@ msgid "Posted message canceled" msgstr "L'enviament ha estat cancel·lat" #: Mailman/Cgi/confirm.py:685 +#, fuzzy msgid "" " You have successfully canceled the posting of your message with\n" " the Subject: header %(subject)s to the mailing list\n" @@ -2021,6 +2075,7 @@ msgstr "" "ja ha estat tractat per l'administrador de la llista. " #: Mailman/Cgi/confirm.py:735 +#, fuzzy msgid "" "Your confirmation is required in order to cancel the\n" " posting of your message to the mailing list %(listname)s:\n" @@ -2069,6 +2124,7 @@ msgid "Membership re-enabled." msgstr "Subscripció reactivada." #: Mailman/Cgi/confirm.py:798 +#, fuzzy msgid "" " You have successfully re-enabled your membership in the\n" " %(listname)s mailing list. You can now not available" msgstr "no disponible" #: Mailman/Cgi/confirm.py:846 +#, fuzzy msgid "" "Your membership in the %(realname)s mailing list is\n" " currently disabled due to excessive bounces. Your confirmation is\n" @@ -2169,10 +2227,12 @@ msgid "administrative list overview" msgstr "visualització general administrativa de la llista" #: Mailman/Cgi/create.py:115 +#, fuzzy msgid "List name must not include \"@\": %(safelistname)s" msgstr "El nom de la llista no pot incloure «@»: %(safelistname)s" #: Mailman/Cgi/create.py:122 +#, fuzzy msgid "List already exists: %(safelistname)s" msgstr "La llista ja existeix: %(safelistname)s" @@ -2207,18 +2267,22 @@ msgid "You are not authorized to create new mailing lists" msgstr "No teniu autorització per a crear una llistes de correu noves" #: Mailman/Cgi/create.py:182 +#, fuzzy msgid "Unknown virtual host: %(safehostname)s" msgstr "Màquina virtual desconeguda: %(safehostname)s" #: Mailman/Cgi/create.py:218 bin/newlist:219 +#, fuzzy msgid "Bad owner email address: %(s)s" msgstr "L'adreça de correu electrònic de l'amo és errònia: %(s)s" #: Mailman/Cgi/create.py:223 bin/newlist:182 bin/newlist:223 +#, fuzzy msgid "List already exists: %(listname)s" msgstr "La llista ja existeix: %(listname)s" #: Mailman/Cgi/create.py:231 bin/newlist:217 +#, fuzzy msgid "Illegal list name: %(s)s" msgstr "El nom de la llista no és vàlid: %(s)s" @@ -2231,6 +2295,7 @@ msgstr "" " ia." #: Mailman/Cgi/create.py:273 bin/newlist:265 +#, fuzzy msgid "Your new mailing list: %(listname)s" msgstr "La vostra nova llista de correu:" @@ -2239,6 +2304,7 @@ msgid "Mailing list creation results" msgstr "Resultats de la creació de la llista de correu" #: Mailman/Cgi/create.py:288 +#, fuzzy msgid "" "You have successfully created the mailing list\n" " %(listname)s and notification has been sent to the list owner\n" @@ -2262,6 +2328,7 @@ msgid "Create another list" msgstr "Crea una altra llista" #: Mailman/Cgi/create.py:312 +#, fuzzy msgid "Create a %(hostname)s Mailing List" msgstr "Crea una llista de correu a %(hostname)s" @@ -2366,6 +2433,7 @@ msgstr "" "defecte." #: Mailman/Cgi/create.py:432 +#, fuzzy msgid "" "Initial list of supported languages.

                  Note that if you do not\n" " select at least one initial language, the list will use the server\n" @@ -2473,6 +2541,7 @@ msgstr "Es requereix el nom de la llista." # FIXME: l'HTML o el HTML? jm #: Mailman/Cgi/edithtml.py:154 +#, fuzzy msgid "%(realname)s -- Edit html for %(template_info)s" msgstr "%(realname)s -- Edita l'html per %(template_info)s" @@ -2481,10 +2550,12 @@ msgid "Edit HTML : Error" msgstr "Edita l'HTML: Error" #: Mailman/Cgi/edithtml.py:161 +#, fuzzy msgid "%(safetemplatename)s: Invalid template" msgstr "%(safetemplatename)s: és una plantilla errònia" #: Mailman/Cgi/edithtml.py:166 Mailman/Cgi/edithtml.py:167 +#, fuzzy msgid "%(realname)s -- HTML Page Editing" msgstr "%(realname)s -- Edició de la pàgina HTML" @@ -2550,10 +2621,12 @@ msgid "HTML successfully updated." msgstr "S'ha actualitzat l'HTML satisfactòriament." #: Mailman/Cgi/listinfo.py:89 +#, fuzzy msgid "%(hostname)s Mailing Lists" msgstr "Llistes de correu a %(hostname)s" #: Mailman/Cgi/listinfo.py:127 +#, fuzzy msgid "" "

                  There currently are no publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s." @@ -2562,6 +2635,7 @@ msgstr "" " públiques a %(hostname)s." #: Mailman/Cgi/listinfo.py:131 +#, fuzzy msgid "" "

                  Below is a listing of all the public mailing lists on\n" " %(hostname)s. Click on a list name to get more information " @@ -2582,6 +2656,7 @@ msgid "right" msgstr "correcte" #: Mailman/Cgi/listinfo.py:140 +#, fuzzy msgid "" " To visit the general information page for an unadvertised list,\n" " open a URL similar to this one, but with a '/' and the %(adj)s\n" @@ -2643,6 +2718,7 @@ msgstr "Adreça de correu electrònic errònia" #: Mailman/Cgi/options.py:183 Mailman/Cgi/options.py:240 #: Mailman/Cgi/options.py:264 +#, fuzzy msgid "No such member: %(safeuser)s." msgstr "El membre no existeix: %(safeuser)s." @@ -2700,6 +2776,7 @@ msgid "Note: " msgstr "Nota: " #: Mailman/Cgi/options.py:378 +#, fuzzy msgid "List subscriptions for %(safeuser)s on %(hostname)s" msgstr "Subscripcions de la llista per %(safeuser)s a %(hostname)s" @@ -2731,6 +2808,7 @@ msgid "You are already using that email address" msgstr "Ja esteu fent servir aquesta adreça de correu electrònic" #: Mailman/Cgi/options.py:459 +#, fuzzy msgid "" "The new address you requested %(newaddr)s is already a member of the\n" "%(listname)s mailing list, however you have also requested a global change " @@ -2745,6 +2823,7 @@ msgstr "" "canviada." #: Mailman/Cgi/options.py:468 +#, fuzzy msgid "The new address is already a member: %(newaddr)s" msgstr "La nova adreça de correu electrònic ja es un membre: %(newaddr)s" @@ -2753,6 +2832,7 @@ msgid "Addresses may not be blank" msgstr "les adreces no poden estar en blanc" #: Mailman/Cgi/options.py:488 +#, fuzzy msgid "A confirmation message has been sent to %(newaddr)s. " msgstr "Un missatge de confirmació ha estat enviat a %(newaddr)s." @@ -2765,10 +2845,12 @@ msgid "Illegal email address provided" msgstr "L'adreça proporcionada es errònia " #: Mailman/Cgi/options.py:501 +#, fuzzy msgid "%(newaddr)s is already a member of the list." msgstr "%(newaddr)s ja es un membre de la llista." #: Mailman/Cgi/options.py:504 +#, fuzzy msgid "" "%(newaddr)s is banned from this list. If you\n" " think this restriction is erroneous, please contact\n" @@ -2850,6 +2932,7 @@ msgstr "" " notificació quan el moderador prengui una decisió." #: Mailman/Cgi/options.py:623 +#, fuzzy msgid "" "You have been successfully unsubscribed from the\n" " mailing list %(fqdn_listname)s. If you were receiving digest\n" @@ -2953,6 +3036,7 @@ msgid "day" msgstr "dia" #: Mailman/Cgi/options.py:900 +#, fuzzy msgid "%(days)d %(units)s" msgstr "%(days)d %(units)s" @@ -2965,6 +3049,7 @@ msgid "No topics defined" msgstr "No hi ha temes definits " #: Mailman/Cgi/options.py:940 +#, fuzzy msgid "" "\n" "You are subscribed to this list with the case-preserved address\n" @@ -2975,6 +3060,7 @@ msgstr "" "se n'han mantingut les majúscules i minúscules. " #: Mailman/Cgi/options.py:956 +#, fuzzy msgid "%(realname)s list: member options login page" msgstr "Llista %(realname)s: pàgina d'entrada a les opcions de membre " @@ -2983,10 +3069,12 @@ msgid "email address and " msgstr "adreça electrònica i " #: Mailman/Cgi/options.py:960 +#, fuzzy msgid "%(realname)s list: member options for user %(safeuser)s" msgstr "Llista %(realname)s: opcions d'usuari per a %(safeuser)s" #: Mailman/Cgi/options.py:986 +#, fuzzy msgid "" "In order to change your membership option, you must\n" " first log in by giving your %(extra)smembership password in the section\n" @@ -3065,6 +3153,7 @@ msgid "" msgstr "" #: Mailman/Cgi/options.py:1140 +#, fuzzy msgid "Requested topic is not valid: %(topicname)s" msgstr "El tema sol·licitat no és vàlid: %(topicname)s" @@ -3093,6 +3182,7 @@ msgid "Private archive - \"./\" and \"../\" not allowed in URL." msgstr "Arxiu privat - no s'adment «./» ni «../» a l'URL." #: Mailman/Cgi/private.py:109 +#, fuzzy msgid "Private Archive Error - %(msg)s" msgstr "Error a l'arxiu privat - %(msg)s" @@ -3132,6 +3222,7 @@ msgid "Mailing list deletion results" msgstr "Resultats de l'eliminació de la llista de correu" #: Mailman/Cgi/rmlist.py:187 +#, fuzzy msgid "" "You have successfully deleted the mailing list\n" " %(listname)s." @@ -3140,6 +3231,7 @@ msgstr "" " %(listname)s." #: Mailman/Cgi/rmlist.py:191 +#, fuzzy msgid "" "There were some problems deleting the mailing list\n" " %(listname)s. Contact your site administrator at " @@ -3151,6 +3243,7 @@ msgstr "" " per a obtenir-ne més detalls." #: Mailman/Cgi/rmlist.py:208 +#, fuzzy msgid "Permanently remove mailing list %(realname)s" msgstr "Suprimeix la llista de correu %(realname)s de forma permanent" @@ -3223,6 +3316,7 @@ msgid "Invalid options to CGI script" msgstr "Opcions no vàlides a l'script CGI." #: Mailman/Cgi/roster.py:118 +#, fuzzy msgid "%(realname)s roster authentication failed." msgstr "L'autenticació de la llista %(realname)s ha fallat." @@ -3290,6 +3384,7 @@ msgstr "" "correu electrònic amb més instruccions." #: Mailman/Cgi/subscribe.py:262 +#, fuzzy msgid "" "The email address you supplied is banned from this\n" " mailing list. If you think this restriction is erroneous, please\n" @@ -3316,6 +3411,7 @@ msgstr "" "no és segura." #: Mailman/Cgi/subscribe.py:278 +#, fuzzy msgid "" "Confirmation from your email address is required, to prevent anyone from\n" "subscribing you without permission. Instructions are being sent to you at\n" @@ -3328,6 +3424,7 @@ msgstr "" "confirmi. " #: Mailman/Cgi/subscribe.py:290 +#, fuzzy msgid "" "Your subscription request was deferred because %(x)s. Your request has " "been\n" @@ -3350,6 +3447,7 @@ msgid "Mailman privacy alert" msgstr "Alerta de privacitat de Mailman" #: Mailman/Cgi/subscribe.py:316 +#, fuzzy msgid "" "An attempt was made to subscribe your address to the mailing list\n" "%(listaddr)s. You are already subscribed to this mailing list.\n" @@ -3395,6 +3493,7 @@ msgid "This list only supports digest delivery." msgstr "Aquesta llista només funciona amb l'entrega de resums." #: Mailman/Cgi/subscribe.py:344 +#, fuzzy msgid "You have been successfully subscribed to the %(realname)s mailing list." msgstr "" "Se us ha subscrit satisfactòriament a la llista de correu %(realname)s." @@ -3447,6 +3546,7 @@ msgstr "" "o canviat la vostra adreça de correu?" #: Mailman/Commands/cmd_confirm.py:69 +#, fuzzy msgid "" "You are currently banned from subscribing to this list. If you think this\n" "restriction is erroneous, please contact the list owners at\n" @@ -3532,27 +3632,33 @@ msgid "n/a" msgstr "n/a" #: Mailman/Commands/cmd_info.py:44 +#, fuzzy msgid "List name: %(listname)s" msgstr "Nom de la llista: %(listname)s" #: Mailman/Commands/cmd_info.py:45 +#, fuzzy msgid "Description: %(description)s" msgstr "Descripció: %(description)s" #: Mailman/Commands/cmd_info.py:46 +#, fuzzy msgid "Postings to: %(postaddr)s" msgstr "Enviaments a: %(postaddr)s" # FIXME: helpbot (dpm) #: Mailman/Commands/cmd_info.py:47 +#, fuzzy msgid "List Helpbot: %(requestaddr)s" msgstr "Helpbot de la llista: %(requestaddr)s" #: Mailman/Commands/cmd_info.py:48 +#, fuzzy msgid "List Owners: %(owneraddr)s" msgstr "Propietaris de la llista: %(owneraddr)s" #: Mailman/Commands/cmd_info.py:49 +#, fuzzy msgid "More information: %(listurl)s" msgstr "Més informació: %(listurl)s" @@ -3576,18 +3682,22 @@ msgstr "" " servidor del Mailman de GNU.\n" #: Mailman/Commands/cmd_lists.py:44 +#, fuzzy msgid "Public mailing lists at %(hostname)s:" msgstr "Llistes de correu públiques a %(hostname)s:" #: Mailman/Commands/cmd_lists.py:66 +#, fuzzy msgid "%(i)3d. List name: %(realname)s" msgstr "%(i)3d. Nom de la llista: %(realname)s" #: Mailman/Commands/cmd_lists.py:67 +#, fuzzy msgid " Description: %(description)s" msgstr " Descripció: %(description)s" #: Mailman/Commands/cmd_lists.py:68 +#, fuzzy msgid " Requests to: %(requestaddr)s" msgstr " Sol·licituds a: %(requestaddr)s" @@ -3626,12 +3736,14 @@ msgstr "" " l'adreça de subscripció.\n" #: Mailman/Commands/cmd_password.py:51 Mailman/Commands/cmd_password.py:66 +#, fuzzy msgid "Your password is: %(password)s" msgstr "La vostra contrasenya es: %(password)s" #: Mailman/Commands/cmd_password.py:57 Mailman/Commands/cmd_password.py:72 #: Mailman/Commands/cmd_password.py:95 Mailman/Commands/cmd_password.py:121 #: Mailman/Commands/cmd_set.py:149 Mailman/Commands/cmd_set.py:219 +#, fuzzy msgid "You are not a member of the %(listname)s mailing list" msgstr "No esteu subscrit a la llista de correu %(listname)s" @@ -3826,6 +3938,7 @@ msgstr "" " contrasenya de la llista.\n" #: Mailman/Commands/cmd_set.py:122 +#, fuzzy msgid "Bad set command: %(subcmd)s" msgstr "L'ordre per a «set» no és correcta: %(subcmd)s" @@ -3846,6 +3959,7 @@ msgid "on" msgstr "activat" #: Mailman/Commands/cmd_set.py:154 +#, fuzzy msgid " ack %(onoff)s" msgstr " ack %(onoff)s" @@ -3883,22 +3997,27 @@ msgid "due to bounces" msgstr "a causa de retorns" #: Mailman/Commands/cmd_set.py:186 +#, fuzzy msgid " %(status)s (%(how)s on %(date)s)" msgstr " %(status)s (%(how)s el %(date)s)" #: Mailman/Commands/cmd_set.py:192 +#, fuzzy msgid " myposts %(onoff)s" msgstr " els meus enviaments %(onoff)s" #: Mailman/Commands/cmd_set.py:195 +#, fuzzy msgid " hide %(onoff)s" msgstr " ocultació %(onoff)s" #: Mailman/Commands/cmd_set.py:199 +#, fuzzy msgid " duplicates %(onoff)s" msgstr " duplicats %(onoff)s" #: Mailman/Commands/cmd_set.py:203 +#, fuzzy msgid " reminders %(onoff)s" msgstr " recordatoris %(onoff)s" @@ -3907,6 +4026,7 @@ msgid "You did not give the correct password" msgstr "No heu introduït la contrasenya correcta" #: Mailman/Commands/cmd_set.py:236 Mailman/Commands/cmd_set.py:283 +#, fuzzy msgid "Bad argument: %(arg)s" msgstr "Argument erroni: %(arg)s" @@ -3981,6 +4101,7 @@ msgstr "" " «address=» (sense els caràcters <> ni cometes).\n" #: Mailman/Commands/cmd_subscribe.py:62 +#, fuzzy msgid "Bad digest specifier: %(arg)s" msgstr "Argument de resum erroni: %(arg)s" @@ -3989,6 +4110,7 @@ msgid "No valid address found to subscribe" msgstr "No s'ha trobat cap adreça vàlida a subscriure" #: Mailman/Commands/cmd_subscribe.py:113 +#, fuzzy msgid "" "The email address you supplied is banned from this mailing list.\n" "If you think this restriction is erroneous, please contact the list\n" @@ -4028,6 +4150,7 @@ msgid "This list only supports digest subscriptions!" msgstr "Aquesta llista només funciona amb subscripcions" #: Mailman/Commands/cmd_subscribe.py:146 +#, fuzzy msgid "" "Your subscription request has been forwarded to the list administrator\n" "at %(listowner)s for review." @@ -4064,6 +4187,7 @@ msgstr "" " l'argument «address=» (sense els caràcters <> ni cometes).\n" #: Mailman/Commands/cmd_unsubscribe.py:62 +#, fuzzy msgid "%(address)s is not a member of the %(listname)s mailing list" msgstr "%(address)s no està subscrita a la llista de correu %(listname)s" @@ -4318,6 +4442,7 @@ msgid "Chinese (Taiwan)" msgstr "xinès (Taiwan)" #: Mailman/Deliverer.py:53 +#, fuzzy msgid "" "Note: Since this is a list of mailing lists, administrative\n" "notices like the password reminder will be sent to\n" @@ -4333,15 +4458,18 @@ msgid " (Digest mode)" msgstr " (mode de resum)" #: Mailman/Deliverer.py:79 +#, fuzzy msgid "Welcome to the \"%(realname)s\" mailing list%(digmode)s" msgstr "Us donem la benvinguda a la llista de correu%(digmode)s «%(realname)s»" #: Mailman/Deliverer.py:89 +#, fuzzy msgid "You have been unsubscribed from the %(realname)s mailing list" msgstr "" "S'ha cancel·lat la vostra subscripció a la llista de correu %(realname)s" #: Mailman/Deliverer.py:116 +#, fuzzy msgid "%(listfullname)s mailing list reminder" msgstr "Recordatori de la llista de correu %(listfullname)s" @@ -4354,6 +4482,7 @@ msgid "Hostile subscription attempt detected" msgstr "S'ha detectat un intent de subscripció hostil" #: Mailman/Deliverer.py:169 +#, fuzzy msgid "" "%(address)s was invited to a different mailing\n" "list, but in a deliberate malicious attempt they tried to confirm the\n" @@ -4367,6 +4496,7 @@ msgstr "" "per part vostra." #: Mailman/Deliverer.py:188 +#, fuzzy msgid "" "You invited %(address)s to your list, but in a\n" "deliberate malicious attempt, they tried to confirm the invitation to a\n" @@ -4381,6 +4511,7 @@ msgstr "" "cap acció per part vostra." #: Mailman/Deliverer.py:221 +#, fuzzy msgid "%(listname)s mailing list probe message" msgstr "Missatge de sondeig de la llista de correu %(listname)s" @@ -4585,8 +4716,8 @@ msgid "" " membership.\n" "\n" "

                  You can control both the\n" -" number\n" +" number\n" " of reminders the member will receive and the\n" " nombre " "de recordatoris\n" " que rebrà el subscriptor com la\n" -" freqüència amb la que s'enviaran les notificacions.\n" "\n" "

                  Hi ha una altra variable de configuració important que controla " "que,\n" " transcorregut un determinat període de temps -- durant el qual no es " "reben\n" -" rebots del subscriptor -- la informació de rebot es considera a href=" -"\"?VARHELP=bounce/bounce_info_stale_after\">\n" +" rebots del subscriptor -- la informació de rebot es considera a " +"href=\"?VARHELP=bounce/bounce_info_stale_after\">\n" " caduca i es descartada. D'aquesta manera, ajustant aquest valor\n" " i el llindar de missatges rebotats, es pot controlar la velocitat a " "la qual\n" @@ -4809,8 +4940,8 @@ msgid "" " No those messages too will get discarded. You may " "want\n" " to set up an\n" -" autoresponse\n" +" autoresponse\n" " message for email to the -owner and -admin address." msgstr "" "Encara que el detector de missatges rebotats de Mailman és bastant robust, " @@ -4907,6 +5038,7 @@ msgstr "" " Sempre s'intentarà avisar al membre." #: Mailman/Gui/Bounce.py:194 +#, fuzzy msgid "" "Bad value for %(property)s: %(val)s" @@ -5068,8 +5200,8 @@ msgid "" "Use this option to remove each message attachment that does\n" " not have a matching content type. Requirements and formats " "are\n" -" exactly like filter_mime_types.\n" "\n" "

                  Note: if you add entries to this list but don't add\n" @@ -5195,6 +5327,7 @@ msgstr "" " habilitat l'administrador. " #: Mailman/Gui/ContentFilter.py:171 +#, fuzzy msgid "Bad MIME type ignored: %(spectype)s" msgstr "Tipus MIME erroni ignorat: %(spectype)s" @@ -5302,6 +5435,7 @@ msgstr "" " que no sigui buit?" #: Mailman/Gui/Digest.py:145 +#, fuzzy msgid "" "The next digest will be sent as volume\n" " %(volume)s, number %(number)s" @@ -5318,15 +5452,18 @@ msgid "There was no digest to send." msgstr "No hi havia cap resum per a enviar." #: Mailman/Gui/GUIBase.py:173 +#, fuzzy msgid "Invalid value for variable: %(property)s" msgstr "Valor erroni de la variable: %(property)s" #: Mailman/Gui/GUIBase.py:178 +#, fuzzy msgid "Bad email address for option %(property)s: %(error)s" msgstr "" "Adreça de correu electrònic errònia per a la opció %(property)s: %(error)s" #: Mailman/Gui/GUIBase.py:204 +#, fuzzy msgid "" "The following illegal substitution variables were\n" " found in the %(property)s string:\n" @@ -5342,6 +5479,7 @@ msgstr "" " que corregeixi el problema." #: Mailman/Gui/GUIBase.py:218 +#, fuzzy msgid "" "Your %(property)s string appeared to\n" " have some correctable problems in its new value.\n" @@ -5450,8 +5588,8 @@ msgid "" "

                  In order to split the list ownership duties into\n" " administrators and moderators, you must\n" " set a separate moderator password,\n" -" and also provide the email\n" +" and also provide the email\n" " addresses of the list moderators. Note that the field you\n" " are changing here specifies the list administrators." msgstr "" @@ -5475,12 +5613,12 @@ msgstr "" "pendents.\n" "\n" "

                  Per a poder dividir les tasques d'administració entre\n" -" els administradors i els moderadors, ha de posar-li una\n" +" els administradors i els moderadors, ha de posar-li una\n" " contrasenya distinta als moderadors a la secció inferior, a " "més d'indicar en\n" -" aquesta secció les adreces de correu electrònic\n" +" aquesta secció les adreces de correu electrònic\n" " dels moderadors. Observi que els camps que es canvien aquí " "indiquen\n" " els administradors de la llista. " @@ -5539,12 +5677,12 @@ msgstr "" "pendents.\n" "\n" "

                  Per a poder dividir les tasques d'administració entre\n" -" els administradors i els moderadors, ha de posar-li una\n" +" els administradors i els moderadors, ha de posar-li una\n" " contrasenya distinta als moderadors a la secció inferior, a " "més d'indicar en\n" -" aquesta secció les adreces de correu electrònic\n" +" aquesta secció les adreces de correu electrònic\n" " dels moderadors. Observi que els camps que es canvien aquí " "indiquen\n" " els moderadors de la llista. " @@ -5801,13 +5939,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5851,8 +5989,8 @@ msgstr "" " raó es deu al fet que modificant la capçalera Reply-To: fa que sigui més\n" " difícil enviar respostes privades. Vegi's `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful per a una discussió general " "sobre aquest tema.\n" "\n" @@ -5876,8 +6014,8 @@ msgstr "Capçalera Contestar-a: explícita." msgid "" "This is the address set in the Reply-To: header\n" " when the reply_goes_to_list\n" +" href=\"?VARHELP=general/" +"reply_goes_to_list\">reply_goes_to_list\n" " option is set to Explicit address.\n" "\n" "

                  There are many reasons not to introduce or override the\n" @@ -5885,13 +6023,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5914,8 +6052,8 @@ msgid "" msgstr "" "Aquesta és l'adreça de la capçalera Reply-To:\n" " quan l'opció reply_goes_to_list\n" +" href=\"?VARHELP=general/" +"reply_goes_to_list\">reply_goes_to_list\n" " és establerta a Adreça explícita.\n" "\n" "

                  Hi ha moltes raons per a no sobreescriure la capçalera " @@ -5927,8 +6065,8 @@ msgstr "" " raó es deu al fet que modificant la capçalera Reply-To: fa que sigui més\n" " difícil enviar respostes privades. Vegi's `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful per a una discussió general " "sobre aquest tema. Vegi Reply-" @@ -6007,8 +6145,8 @@ msgid "" " member list addresses, but rather to the owner of those member\n" " lists. In that case, the value of this setting is appended to\n" " the member's account name for such notices. `-owner' is the\n" -" typical choice. This setting has no effect when \"umbrella_list" -"\"\n" +" typical choice. This setting has no effect when " +"\"umbrella_list\"\n" " is \"No\"." msgstr "" "Quan \"umbrella_list\" està activa és per a indicar que aquesta llista té\n" @@ -7036,6 +7174,7 @@ msgstr "" " gent sense el seu consentiment." #: Mailman/Gui/Privacy.py:104 +#, fuzzy msgid "" "This section allows you to configure subscription and\n" " membership exposure policy. You can also control whether this\n" @@ -7238,8 +7377,8 @@ msgid "" " either individually or as a group. Any\n" " posting from a non-member who is not explicitly accepted,\n" " rejected, or discarded, will have their posting filtered by the\n" -" general\n" +" general\n" " non-member rules.\n" "\n" "

                  In the text boxes below, add one address per line; start the\n" @@ -7269,8 +7408,8 @@ msgstr "" "

                  Els enviaments dels No-membres poden ser automàticament\n" " acceptats,\n" -" retinguts per a la seva\n" +" retinguts per a la seva\n" " moderació,\n" " denegats (bounced), or\n" @@ -7279,8 +7418,8 @@ msgstr "" " tant individualment com per grups. Qualsevol enviament\n" " de un no-membre que no és exlicitament acceptat,\n" " denegat, o descartat, es filtrarà amb les\n" -" regles\n" +" regles\n" " generals de no-membres.\n" "\n" "

                  A les caixes de text d'abaix, afegeix una adreça per línia; " @@ -7303,6 +7442,7 @@ msgid "By default, should new list member postings be moderated?" msgstr "Per defecte, han de ser els missatges dels nous membres moderats?" #: Mailman/Gui/Privacy.py:218 +#, fuzzy msgid "" "Each list member has a moderation flag which says\n" " whether messages from the list member can be posted directly " @@ -7359,8 +7499,8 @@ msgid "" "If a member posts this many times, within a period of time\n" " the member is automatically moderated. Use 0 to disable. " "See\n" -" member_verbosity_interval for details on the time " "period.\n" "\n" @@ -7814,8 +7954,8 @@ msgstr "" " remitent es troba en el llistat d'adreces explícitament\n" " acceptades,\n" -" retingudes,\n" +" retingudes,\n" " denegades (rebotat), i\n" " The body of the message can also be optionally scanned for\n" " Subject: and Keywords: headers, as\n" " specified by the topics_bodylines_limit\n" +" href=\"?VARHELP=topics/" +"topics_bodylines_limit\">topics_bodylines_limit\n" " configuration variable." msgstr "" "El filtre segons el tema, classifica cada missatge rebut segons:\n" @@ -8186,8 +8328,8 @@ msgstr "" " recerca de capçaleres Subject: i Keyword:,\n" " com s'especifica en la variable de configuració a\n" -" href=\"?VARHELP=topics/topics_bodylines_limit" -"\">topics_bodylines_limit." +" href=\"?VARHELP=topics/" +"topics_bodylines_limit\">topics_bodylines_limit." #: Mailman/Gui/Topics.py:72 msgid "How many body lines should the topic matcher scan?" @@ -8259,6 +8401,7 @@ msgstr "" " un nom com un patró. S'ignoraran les definicions incompletes." #: Mailman/Gui/Topics.py:135 +#, fuzzy msgid "" "The topic pattern '%(safepattern)s' is not a\n" " legal regular expression. It will be discarded." @@ -8501,6 +8644,7 @@ msgid "%(listinfo_link)s list run by %(owner_link)s" msgstr "Llista %(listinfo_link)s, administrada per %(owner_link)s" #: Mailman/HTMLFormatter.py:57 +#, fuzzy msgid "%(realname)s administrative interface" msgstr "Interfície administrativa de la llista %(realname)s" @@ -8509,6 +8653,7 @@ msgid " (requires authorization)" msgstr " (requereix autorització)" #: Mailman/HTMLFormatter.py:61 +#, fuzzy msgid "Overview of all %(hostname)s mailing lists" msgstr "Vista general de totes les llistes de correu de %(hostname)s" @@ -8529,6 +8674,7 @@ msgid "; it was disabled by the list administrator" msgstr "; va ser inhabilitat per l'administrador de la llista" #: Mailman/HTMLFormatter.py:146 +#, fuzzy msgid "" "; it was disabled due to excessive bounces. The\n" " last bounce was received on %(date)s" @@ -8541,6 +8687,7 @@ msgid "; it was disabled for unknown reasons" msgstr "; va ser deshabilitat per raons desconegudes" #: Mailman/HTMLFormatter.py:151 +#, fuzzy msgid "Note: your list delivery is currently disabled%(reason)s." msgstr "Atenció: el lliurament de la vostra llista està desactivat %(reason)s." @@ -8553,6 +8700,7 @@ msgid "the list administrator" msgstr "l'administrador de la llista" #: Mailman/HTMLFormatter.py:157 +#, fuzzy msgid "" "

                  %(note)s\n" "\n" @@ -8573,6 +8721,7 @@ msgstr "" " o si necessita ajuda, contacti amb %(mailto)s." #: Mailman/HTMLFormatter.py:169 +#, fuzzy msgid "" "

                  We have received some recent bounces from your\n" " address. Your current bounce score is %(score)s out of " @@ -8592,6 +8741,7 @@ msgstr "" "automàticament re-establert si els problemes son solucionats." #: Mailman/HTMLFormatter.py:181 +#, fuzzy msgid "" "(Note - you are subscribing to a list of mailing lists, so the %(type)s " "notice will be sent to the admin address for your membership, %(addr)s.)

                  " @@ -8640,6 +8790,7 @@ msgstr "" " del moderador amb un correu electrònic." #: Mailman/HTMLFormatter.py:208 +#, fuzzy msgid "" "This is %(also)sa private list, which means that the\n" " list of members is not available to non-members." @@ -8649,6 +8800,7 @@ msgstr "" "que no hi estiguin subscrits." #: Mailman/HTMLFormatter.py:211 +#, fuzzy msgid "" "This is %(also)sa hidden list, which means that the\n" " list of members is available only to the list administrator." @@ -8658,6 +8810,7 @@ msgstr "" "de la llista." #: Mailman/HTMLFormatter.py:214 +#, fuzzy msgid "" "This is %(also)sa public list, which means that the\n" " list of members list is available to everyone." @@ -8674,6 +8827,7 @@ msgstr "" " reconegudes pels spammers)." #: Mailman/HTMLFormatter.py:222 +#, fuzzy msgid "" "

                  (Note that this is an umbrella list, intended to\n" " have only other mailing lists as members. Among other things,\n" @@ -8692,6 +8846,7 @@ msgid "either " msgstr "qualsevol" #: Mailman/HTMLFormatter.py:256 +#, fuzzy msgid "" "To unsubscribe from %(realname)s, get a password reminder,\n" " or change your subscription options %(either)senter your " @@ -8727,6 +8882,7 @@ msgstr "" " adreça de correu electrònic" #: Mailman/HTMLFormatter.py:277 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " members.)" @@ -8735,6 +8891,7 @@ msgstr "" " subscriptors de la llista.)" #: Mailman/HTMLFormatter.py:281 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " administrator.)" @@ -8797,6 +8954,7 @@ msgid "The current archive" msgstr "L'arxiu actual" #: Mailman/Handlers/Acknowledge.py:59 +#, fuzzy msgid "%(realname)s post acknowledgement" msgstr "Confirmació de l'enviament de la llista %(realname)s." @@ -8809,6 +8967,7 @@ msgid "" msgstr "" #: Mailman/Handlers/CalcRecips.py:79 +#, fuzzy msgid "" "Your urgent message to the %(realname)s mailing list was not authorized for\n" "delivery. The original message as received by Mailman is attached.\n" @@ -8892,6 +9051,7 @@ msgid "Message may contain administrivia" msgstr "El missatge pot contenir sol·licituts administratives" #: Mailman/Handlers/Hold.py:84 +#, fuzzy msgid "" "Please do *not* post administrative requests to the mailing\n" "list. If you wish to subscribe, visit %(listurl)s or send a message with " @@ -8932,12 +9092,14 @@ msgid "Posting to a moderated newsgroup" msgstr "Enviament a un grup de notícies moderat" #: Mailman/Handlers/Hold.py:252 +#, fuzzy msgid "Your message to %(listname)s awaits moderator approval" msgstr "" "El vostre missatge a la llista %(listname)s espera l'aprovació de " "l'administrador" #: Mailman/Handlers/Hold.py:271 +#, fuzzy msgid "%(listname)s post from %(sender)s requires approval" msgstr "Els enviaments a %(listname)s de %(sender)s requereixen aprovació" @@ -8981,6 +9143,7 @@ msgid "After content filtering, the message was empty" msgstr "Després de filtrar el contigut, el missatga era buit" #: Mailman/Handlers/MimeDel.py:269 +#, fuzzy msgid "" "The attached message matched the %(listname)s mailing list's content " "filtering\n" @@ -9025,6 +9188,7 @@ msgid "The attached message has been automatically discarded." msgstr "El missatge adjunt ha estat automàticament descartat." #: Mailman/Handlers/Replybot.py:75 +#, fuzzy msgid "Auto-response for your message to the \"%(realname)s\" mailing list" msgstr "Resposta automàtica per al vostre missatge a la llista «%(realname)s»" @@ -9044,6 +9208,7 @@ msgid "HTML attachment scrubbed and removed" msgstr "Eliminat el document HTML adjunt" #: Mailman/Handlers/Scrubber.py:234 Mailman/Handlers/Scrubber.py:259 +#, fuzzy msgid "" "An HTML attachment was scrubbed...\n" "URL: %(url)s\n" @@ -9098,6 +9263,7 @@ msgstr "" "Url : %(url)s\n" #: Mailman/Handlers/Scrubber.py:347 +#, fuzzy msgid "Skipped content of type %(partctype)s\n" msgstr "S'ha omès el contingut de tipus %(partctype)s\n" @@ -9130,6 +9296,7 @@ msgid "Message rejected by filter rule match" msgstr "S'ha rebutjat el missatge perquè coincideix amb una regla de filtre" #: Mailman/Handlers/ToDigest.py:173 +#, fuzzy msgid "%(realname)s Digest, Vol %(volume)d, Issue %(issue)d" msgstr "Resum de %(realname)s, vol %(volume)d, número %(issue)d" @@ -9166,6 +9333,7 @@ msgid "End of " msgstr "Final de " #: Mailman/ListAdmin.py:309 +#, fuzzy msgid "Posting of your message titled \"%(subject)s\"" msgstr "Enviament del vostre missatge anomenat «%(subject)s»" @@ -9178,6 +9346,7 @@ msgid "Forward of moderated message" msgstr "Reenviament de missatge moderat" #: Mailman/ListAdmin.py:405 +#, fuzzy msgid "New subscription request to list %(realname)s from %(addr)s" msgstr "" "Sol·licitud de subscripció nova per a la llista %(realname)s de %(addr)s" @@ -9192,6 +9361,7 @@ msgid "via admin approval" msgstr "Continua esperant l'aprovació" #: Mailman/ListAdmin.py:466 +#, fuzzy msgid "New unsubscription request from %(realname)s by %(addr)s" msgstr "" "Sol·licitud de cancel·lació de subscripció nova a %(realname)s de %(addr)s" @@ -9205,10 +9375,12 @@ msgid "Original Message" msgstr "Missatge original" #: Mailman/ListAdmin.py:526 +#, fuzzy msgid "Request to mailing list %(realname)s rejected" msgstr "S'ha refusat la sol·licitud a la llista de correu %(realname)s" #: Mailman/MTA/Manual.py:66 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been created via the through-the-web\n" "interface. In order to complete the activation of this mailing list, the\n" @@ -9237,14 +9409,17 @@ msgstr "" "següents i possiblement executar el programa «newaliases»:\n" #: Mailman/MTA/Manual.py:82 +#, fuzzy msgid "## %(listname)s mailing list" msgstr "## Llista de correu %(listname)s" #: Mailman/MTA/Manual.py:99 +#, fuzzy msgid "Mailing list creation request for list %(listname)s" msgstr "Sol·licitud de creació de la llista de correu %(listname)s" #: Mailman/MTA/Manual.py:113 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been removed via the through-the-web\n" "interface. In order to complete the de-activation of this mailing list, " @@ -9264,6 +9439,7 @@ msgstr "" "Aquí hi ha les entrades de /etc/aliases que han de ser esborrades:\n" #: Mailman/MTA/Manual.py:123 +#, fuzzy msgid "" "\n" "To finish removing your mailing list, you must edit your /etc/aliases (or\n" @@ -9281,14 +9457,17 @@ msgstr "" "## %(listname)s llista de correu" #: Mailman/MTA/Manual.py:142 +#, fuzzy msgid "Mailing list removal request for list %(listname)s" msgstr "Sol·licitud de supressió de la llista %(listname)s" #: Mailman/MTA/Postfix.py:442 +#, fuzzy msgid "checking permissions on %(file)s" msgstr "s'estan comprovant els permisos de %(file)s" #: Mailman/MTA/Postfix.py:452 +#, fuzzy msgid "%(file)s permissions must be 0664 (got %(octmode)s)" msgstr "El fitxer %(file)s hauria de tenir permisos 0664 (té %(octmode)s)" @@ -9302,38 +9481,46 @@ msgid "(fixing)" msgstr "(s'està reparant)" #: Mailman/MTA/Postfix.py:470 +#, fuzzy msgid "checking ownership of %(dbfile)s" msgstr "s'està comprovant el propietari de %(dbfile)s" #: Mailman/MTA/Postfix.py:478 +#, fuzzy msgid "%(dbfile)s owned by %(owner)s (must be owned by %(user)s" msgstr "%(dbfile)s propietat de %(owner)s (ha de ser propietat de %(user)s" #: Mailman/MTA/Postfix.py:491 +#, fuzzy msgid "%(dbfile)s permissions must be 0664 (got %(octmode)s)" msgstr "El fitxer %(dbfile)s hauria de tenir permisos 0664 (té %(octmode)s)" #: Mailman/MailList.py:219 +#, fuzzy msgid "Your confirmation is required to join the %(listname)s mailing list" msgstr "" "Es requereix la vostra confirmació per a unir-vos a la llista de correu " "%(listname)s" #: Mailman/MailList.py:230 +#, fuzzy msgid "Your confirmation is required to leave the %(listname)s mailing list" msgstr "" "Es requereix la vostra confirmació per a abandonar la llista de correu " "%(listname)s" #: Mailman/MailList.py:999 Mailman/MailList.py:1492 +#, fuzzy msgid " from %(remote)s" msgstr " de %(remote)s" #: Mailman/MailList.py:1043 +#, fuzzy msgid "subscriptions to %(realname)s require moderator approval" msgstr "les subscripcions a %(realname)s requereixen l'aprovació del moderador" #: Mailman/MailList.py:1125 bin/add_members:299 +#, fuzzy msgid "%(realname)s subscription notification" msgstr "Notificació de subscripció a %(realname)s" @@ -9343,6 +9530,7 @@ msgstr "" "les cancel·lacions de subscripció requereixen l'aprovació del moderador" #: Mailman/MailList.py:1166 +#, fuzzy msgid "%(realname)s unsubscribe notification" msgstr "Notificació de la cancel·lació de la subscripció a %(realname)s" @@ -9362,6 +9550,7 @@ msgid "via web confirmation" msgstr "Cadena de confirmació errònia." #: Mailman/MailList.py:1396 +#, fuzzy msgid "subscriptions to %(name)s require administrator approval" msgstr "" "les subscripcions a %(name)s requereixen l'aprovació de l'administrador" @@ -9381,6 +9570,7 @@ msgid "Last autoresponse notification for today" msgstr "Última notificació d'auto-resposta per avui" #: Mailman/Queue/BounceRunner.py:360 +#, fuzzy msgid "" "The attached message was received as a bounce, but either the bounce format\n" "was not recognized, or no member addresses could be extracted from it. " @@ -9473,6 +9663,7 @@ msgstr "" # FIXME: Ve a ser el mateix que «Powered by», però juguen amb el fet que «delivery» # té més connotació de correu. #: Mailman/htmlformat.py:675 +#, fuzzy msgid "Delivered by Mailman
                  version %(version)s" msgstr "Funciona amb el Mailman
                  versió %(version)s" @@ -9561,6 +9752,7 @@ msgid "Server Local Time" msgstr "Hora local del servidor" #: Mailman/i18n.py:180 +#, fuzzy msgid "" "%(wday)s %(mon)s %(day)2i %(hh)02i:%(mm)02i:%(ss)02i %(tzname)s %(year)04i" msgstr "" @@ -9674,6 +9866,7 @@ msgstr "" "dels fitxers pot ser `-'.\n" #: bin/add_members:162 bin/add_members:172 +#, fuzzy msgid "Already a member: %(member)s" msgstr "Ja és un membre: %(member)s" @@ -9682,10 +9875,12 @@ msgid "Bad/Invalid email address: blank line" msgstr "Adreça de correu electrònic incorrecta: línia en blanc" #: bin/add_members:180 +#, fuzzy msgid "Bad/Invalid email address: %(member)s" msgstr "Adreça de correu electrònic incorrecta: %(member)s" #: bin/add_members:182 +#, fuzzy msgid "Hostile address (illegal characters): %(member)s" msgstr "Adreça hostil (caràcter il·legals): %(member)s" @@ -9695,14 +9890,17 @@ msgid "Invited: %(member)s" msgstr "Subscrit: %(member)s" #: bin/add_members:187 +#, fuzzy msgid "Subscribed: %(member)s" msgstr "Subscrit: %(member)s" #: bin/add_members:237 +#, fuzzy msgid "Bad argument to -w/--welcome-msg: %(arg)s" msgstr "Argument erroni -w/--welcome-msg: %(arg)s" #: bin/add_members:244 +#, fuzzy msgid "Bad argument to -a/--admin-notify: %(arg)s" msgstr "Argument erroni a -a/--admin-notify: %(arg)s" @@ -9719,6 +9917,7 @@ msgstr "" #: bin/add_members:261 bin/config_list:110 bin/export.py:271 bin/find_member:97 #: bin/inject:91 bin/list_admins:90 bin/list_members:252 bin/sync_members:222 #: cron/bumpdigests:86 +#, fuzzy msgid "No such list: %(listname)s" msgstr "La llista no existeix: %(listname)s" @@ -9729,6 +9928,7 @@ msgid "Nothing to do." msgstr "Res per fer." #: bin/arch:19 +#, fuzzy msgid "" "Rebuild a list's archive.\n" "\n" @@ -9822,6 +10022,7 @@ msgid "listname is required" msgstr "El nom de la llista és requerit" #: bin/arch:143 bin/change_pw:107 bin/config_list:257 +#, fuzzy msgid "" "No such list \"%(listname)s\"\n" "%(e)s" @@ -9830,10 +10031,12 @@ msgstr "" "%(e)s" #: bin/arch:168 +#, fuzzy msgid "Cannot open mbox file %(mbox)s: %(msg)s" msgstr "No es pot obrir el fitxer mbox %(mbox)s: %(msg)s" #: bin/b4b5-archfix:19 +#, fuzzy msgid "" "Fix the MM2.1b4 archives.\n" "\n" @@ -9986,6 +10189,7 @@ msgstr "" " Mostra aquest missatge d'ajuda i surt.\n" #: bin/change_pw:145 +#, fuzzy msgid "Bad arguments: %(strargs)s" msgstr "Arguments erronis: %(strargs)s" @@ -9994,14 +10198,17 @@ msgid "Empty list passwords are not allowed" msgstr "Les contrasenyes buides per les llistes no estan permeses" #: bin/change_pw:181 +#, fuzzy msgid "New %(listname)s password: %(notifypassword)s" msgstr "Contrasenya nova per a %(listname)s: %(notifypassword)s" #: bin/change_pw:190 +#, fuzzy msgid "Your new %(listname)s list password" msgstr "La contrasenya nova de %(listname)s" #: bin/change_pw:191 +#, fuzzy msgid "" "The site administrator at %(hostname)s has changed the password for your\n" "mailing list %(listname)s. It is now\n" @@ -10030,6 +10237,7 @@ msgstr "" # FIXME Pickle. jm #: bin/check_db:19 +#, fuzzy msgid "" "Check a list's config database file for integrity.\n" "\n" @@ -10106,6 +10314,7 @@ msgid "List:" msgstr "Llista:" #: bin/check_db:148 +#, fuzzy msgid " %(file)s: okay" msgstr " %(file)s: correcte" @@ -10131,42 +10340,52 @@ msgstr "" "\n" #: bin/check_perms:110 +#, fuzzy msgid " checking gid and mode for %(path)s" msgstr " comprovació del GID i del mode de %(path)s" #: bin/check_perms:122 +#, fuzzy msgid "%(path)s bad group (has: %(groupname)s, expected %(MAILMAN_GROUP)s)" msgstr "%(path)s grup erroni (té: %(groupname)s, s'esperava %(MAILMAN_GROUP)s)" #: bin/check_perms:151 +#, fuzzy msgid "directory permissions must be %(octperms)s: %(path)s" msgstr "els permisos de directori han de ser %(octperms)s: %(path)s" #: bin/check_perms:160 +#, fuzzy msgid "source perms must be %(octperms)s: %(path)s" msgstr "els permisos dels fonts han de ser %(octperms)s: %(path)s" #: bin/check_perms:171 +#, fuzzy msgid "article db files must be %(octperms)s: %(path)s" msgstr "Els permisos dels fitxers db han de ser %(octperms)s: %(path)s" #: bin/check_perms:183 +#, fuzzy msgid "checking mode for %(prefix)s" msgstr "comprovació del mode per %(prefix)s" #: bin/check_perms:193 +#, fuzzy msgid "WARNING: directory does not exist: %(d)s" msgstr "AVÃS: no existeix el directori: %(d)s" #: bin/check_perms:197 +#, fuzzy msgid "directory must be at least 02775: %(d)s" msgstr "el directori ha de ser almenys 02775: %(d)s" #: bin/check_perms:209 +#, fuzzy msgid "checking perms on %(private)s" msgstr "comprovació dels permisos de %(private)s" #: bin/check_perms:214 +#, fuzzy msgid "%(private)s must not be other-readable" msgstr "%(private)s no ha de ser de lectura per a altres usuaris" @@ -10192,6 +10411,7 @@ msgid "mbox file must be at least 0660:" msgstr "El fitxer mbox ha de ser almenys 0660:" #: bin/check_perms:263 +#, fuzzy msgid "%(dbdir)s \"other\" perms must be 000" msgstr "els permisos de %(dbdir)s \"other\" han de ser 000" @@ -10200,26 +10420,32 @@ msgid "checking cgi-bin permissions" msgstr "comprovant permisos de cgi-bin" #: bin/check_perms:278 +#, fuzzy msgid " checking set-gid for %(path)s" msgstr "comprovant permisos de set-gid per %(path)s" #: bin/check_perms:282 +#, fuzzy msgid "%(path)s must be set-gid" msgstr "%(path)s ha de ser set-gid" #: bin/check_perms:292 +#, fuzzy msgid "checking set-gid for %(wrapper)s" msgstr "comprovant permisos de set-gid per %(wrapper)s" #: bin/check_perms:296 +#, fuzzy msgid "%(wrapper)s must be set-gid" msgstr "%(wrapper)s ha de ser set-gid" #: bin/check_perms:306 +#, fuzzy msgid "checking permissions on %(pwfile)s" msgstr "comprovant permisos de %(pwfile)s" #: bin/check_perms:315 +#, fuzzy msgid "%(pwfile)s permissions must be exactly 0640 (got %(octmode)s)" msgstr "" "els permisos de %(pwfile)s han de ser exactament 0640 (got %(octmode)s)" @@ -10229,10 +10455,12 @@ msgid "checking permissions on list data" msgstr "comprovant permisos a la informació de la llista" #: bin/check_perms:348 +#, fuzzy msgid " checking permissions on: %(path)s" msgstr " comprovant permisos a: %(path)s" #: bin/check_perms:356 +#, fuzzy msgid "file permissions must be at least 660: %(path)s" msgstr "els permisos dels fitxers han de ser almenys 660: %(path)s" @@ -10319,6 +10547,7 @@ msgid "Unix-From line changed: %(lineno)d" msgstr "Línia From de Unix canviada: %(lineno)d" #: bin/cleanarch:111 +#, fuzzy msgid "Bad status number: %(arg)s" msgstr "Número d'estat erroni: %(arg)s" @@ -10467,10 +10696,12 @@ msgid " original address removed:" msgstr " Adreça original eliminada:" #: bin/clone_member:202 +#, fuzzy msgid "Not a valid email address: %(toaddr)s" msgstr "No és una adreça vàlida: %(toaddr)s" #: bin/clone_member:215 +#, fuzzy msgid "" "Error opening list \"%(listname)s\", skipping.\n" "%(e)s" @@ -10585,6 +10816,7 @@ msgstr "" "\n" #: bin/config_list:118 +#, fuzzy msgid "" "# -*- python -*-\n" "# -*- coding: %(charset)s -*-\n" @@ -10605,22 +10837,27 @@ msgid "legal values are:" msgstr "els valors permesos són:" #: bin/config_list:270 +#, fuzzy msgid "attribute \"%(k)s\" ignored" msgstr "s'ha ignorat l' atribut «%(k)s»" #: bin/config_list:273 +#, fuzzy msgid "attribute \"%(k)s\" changed" msgstr "ha canviat l'atribut «%(k)s»" #: bin/config_list:279 +#, fuzzy msgid "Non-standard property restored: %(k)s" msgstr "Propietat no estàndard restaurada: %(k)s" #: bin/config_list:288 +#, fuzzy msgid "Invalid value for property: %(k)s" msgstr "Valor erroni per a la propietat: %(k)s" #: bin/config_list:291 +#, fuzzy msgid "Bad email address for option %(k)s: %(v)s" msgstr "Adreça de correu electrònic errònia per a l'opció %(k)s: %(v)s " @@ -10686,18 +10923,22 @@ msgstr "" " No mostris els missatges d'estat.\n" #: bin/discard:94 +#, fuzzy msgid "Ignoring non-held message: %(f)s" msgstr "S'ignorarà el missatge no retingut: %(f)s" #: bin/discard:100 +#, fuzzy msgid "Ignoring held msg w/bad id: %(f)s" msgstr "S'ignorarà el missatge retingut que té una id incorrecta: %(f)s" #: bin/discard:112 +#, fuzzy msgid "Discarded held msg #%(id)s for list %(listname)s" msgstr "S'ha descartat el missatge retingut #%(id)s de la llista %(listname)s" #: bin/dumpdb:19 +#, fuzzy msgid "" "Dump the contents of any Mailman `database' file.\n" "\n" @@ -10772,6 +11013,7 @@ msgid "No filename given." msgstr "No s'ha especificat cap nom de fitxer." #: bin/dumpdb:108 +#, fuzzy msgid "Bad arguments: %(pargs)s" msgstr "Els arguments són erronis: %(pargs)s" @@ -10780,14 +11022,17 @@ msgid "Please specify either -p or -m." msgstr "Heu d'especifica una de les opcions -p o -m." #: bin/dumpdb:133 +#, fuzzy msgid "[----- start %(typename)s file -----]" msgstr "[----- inici del fitxer %(typename)s -----]" #: bin/dumpdb:139 +#, fuzzy msgid "[----- end %(typename)s file -----]" msgstr "[----- final del fitxer %(typename)s -----]" #: bin/dumpdb:142 +#, fuzzy msgid "<----- start object %(cnt)s ----->" msgstr "<----- inici de l'objecte %(cnt)s ----->" @@ -11012,6 +11257,7 @@ msgid "Setting web_page_url to: %(web_page_url)s" msgstr "Establint web_page_url a: %(web_page_url)s" #: bin/fix_url.py:88 +#, fuzzy msgid "Setting host_name to: %(mailhost)s" msgstr "Establint host_name a: %(mailhost)s" @@ -11090,6 +11336,7 @@ msgstr "" "Si s'omet, es fa servir el standard input.\n" #: bin/inject:84 +#, fuzzy msgid "Bad queue directory: %(qdir)s" msgstr "Directori de cua erroni: %(qdir)s" @@ -11098,6 +11345,7 @@ msgid "A list name is required" msgstr "Un nom per a la llista és requerit" #: bin/list_admins:20 +#, fuzzy msgid "" "List all the owners of a mailing list.\n" "\n" @@ -11145,6 +11393,7 @@ msgstr "" "Pots posar més d'un nom a la línia d'ordres.\n" #: bin/list_admins:97 +#, fuzzy msgid "List: %(listname)s, \tOwners: %(owners)s" msgstr "Llista: %(listname)s, \tAmos: %(owners)s" @@ -11341,10 +11590,12 @@ msgstr "" "s'indicarà l'estat de l'adreça.\n" #: bin/list_members:198 +#, fuzzy msgid "Bad --nomail option: %(why)s" msgstr "Opció --normal errònia: %(why)s" #: bin/list_members:209 +#, fuzzy msgid "Bad --digest option: %(kind)s" msgstr "Opció --digest errònia: %(kind)s" @@ -11358,6 +11609,7 @@ msgid "Could not open file for writing:" msgstr "No es pot obrir el fitxer per a escriptura:" #: bin/list_owners:20 +#, fuzzy msgid "" "List the owners of a mailing list, or all mailing lists.\n" "\n" @@ -11413,6 +11665,7 @@ msgid "" msgstr "" #: bin/mailmanctl:20 +#, fuzzy msgid "" "Primary start-up and shutdown script for Mailman's qrunner daemon.\n" "\n" @@ -11596,6 +11849,7 @@ msgstr "" " la próxima vegada se hi next.\n" #: bin/mailmanctl:152 +#, fuzzy msgid "PID unreadable in: %(pidfile)s" msgstr "el PID no es pot llegir a: %(pidfile)s" @@ -11604,6 +11858,7 @@ msgid "Is qrunner even running?" msgstr "encara s'està executant el qrunner?" #: bin/mailmanctl:160 +#, fuzzy msgid "No child with pid: %(pid)s" msgstr "No hi ha cap fill amb el pid: %(pid)s" @@ -11631,6 +11886,7 @@ msgstr "" "amb la senyal -s.\n" #: bin/mailmanctl:233 +#, fuzzy msgid "" "The master qrunner lock could not be acquired, because it appears as if " "some\n" @@ -11657,10 +11913,12 @@ msgstr "" "Sortint." #: bin/mailmanctl:280 cron/mailpasswds:119 +#, fuzzy msgid "Site list is missing: %(sitelistname)s" msgstr "La llista del Site no hi és: %(sitelistname)s" #: bin/mailmanctl:305 +#, fuzzy msgid "Run this program as root or as the %(name)s user, or use -u." msgstr "" "Executar aquest programa com root o amb el usuari %(name)s, o utilitza " @@ -11671,6 +11929,7 @@ msgid "No command given." msgstr "No has donat cap ordre." #: bin/mailmanctl:339 +#, fuzzy msgid "Bad command: %(command)s" msgstr "Ordre errònia: %(command)s" @@ -11695,6 +11954,7 @@ msgid "Starting Mailman's master qrunner." msgstr "Iniciant el qrunner principal de Mailman" #: bin/mmsitepass:19 +#, fuzzy msgid "" "Set the site password, prompting from the terminal.\n" "\n" @@ -11750,6 +12010,7 @@ msgid "list creator" msgstr "creador de la llista" #: bin/mmsitepass:86 +#, fuzzy msgid "New %(pwdesc)s password: " msgstr "Nova contrasenya %(pwdesc)s: " @@ -11997,6 +12258,7 @@ msgstr "" "Nota que els noms de les llistes han de ser en minúscules.\n" #: bin/newlist:162 +#, fuzzy msgid "Unknown language: %(lang)s" msgstr "Llengua desconeguda: %(lang)s" @@ -12009,6 +12271,7 @@ msgid "Enter the email of the person running the list: " msgstr "Introduïu l'adreça electrònica de l'encarregat de la llista: " #: bin/newlist:193 +#, fuzzy msgid "Initial %(listname)s password: " msgstr "Contrasenya inicial de %(listname)s: " @@ -12018,16 +12281,18 @@ msgstr "La contrasenya de la llista no pot estar buida" #: bin/newlist:220 msgid "" -" - owner addresses need to be fully-qualified names like \"owner@example.com" -"\", not just \"owner\"." +" - owner addresses need to be fully-qualified names like \"owner@example." +"com\", not just \"owner\"." msgstr "" #: bin/newlist:244 +#, fuzzy msgid "Hit enter to notify %(listname)s owner..." msgstr "" "Premeu la tecla de retorn per a notificar el propietari de %(listname)s..." #: bin/qrunner:20 +#, fuzzy msgid "" "Run one or more qrunners, once or repeatedly.\n" "\n" @@ -12152,6 +12417,7 @@ msgstr "" "mostrats per l'opció -l.\n" #: bin/qrunner:179 +#, fuzzy msgid "%(name)s runs the %(runnername)s qrunner" msgstr "%(name)s executa el qrunner %(runnername)s" @@ -12164,6 +12430,7 @@ msgid "No runner name given." msgstr "No heu proporcionat cap nom de runner." #: bin/rb-archfix:21 +#, fuzzy msgid "" "Reduce disk space usage for Pipermail archives.\n" "\n" @@ -12312,18 +12579,22 @@ msgstr "" "\n" #: bin/remove_members:156 +#, fuzzy msgid "Could not open file for reading: %(filename)s." msgstr "No es pot obrir el fitxer per llegir: %(filename)s." #: bin/remove_members:163 +#, fuzzy msgid "Error opening list %(listname)s... skipping." msgstr "Error obrint la llista %(listname)s... saltant. " #: bin/remove_members:173 +#, fuzzy msgid "No such member: %(addr)s" msgstr "El membre no existeix: %(addr)s" #: bin/remove_members:178 +#, fuzzy msgid "User `%(addr)s' removed from list: %(listname)s." msgstr "El usuari `%(addr)s' ha estat eliminat de la llista: %(listname)s." @@ -12348,10 +12619,12 @@ msgid "" msgstr "" #: bin/reset_pw.py:77 +#, fuzzy msgid "Changing passwords for list: %(listname)s" msgstr "S'estan canviant les contrasenyes per a la llista: %(listname)s" #: bin/reset_pw.py:83 +#, fuzzy msgid "New password for member %(member)40s: %(randompw)s" msgstr "Contrasenya nova per al subscriptor %(member)40s: %(randompw)s" @@ -12397,21 +12670,25 @@ msgstr "" "\n" #: bin/rmlist:73 bin/rmlist:76 +#, fuzzy msgid "Removing %(msg)s" msgstr "Eliminant %(msg)s" #: bin/rmlist:81 +#, fuzzy msgid "%(listname)s %(msg)s not found as %(filename)s" msgstr "" "No s'ha trobat el missatge %(msg)s de la llista %(listname)s com a fitxer " "%(filename)s" #: bin/rmlist:105 +#, fuzzy msgid "No such list (or list already deleted): %(listname)s" msgstr "" "La llista no existeix (o la llista ja ha estat eliminada): %(listname)s" #: bin/rmlist:108 +#, fuzzy msgid "No such list: %(listname)s. Removing its residual archives." msgstr "" "La llista no existeix: %(listname)s. Eliminant els seus fitxers residuals." @@ -12473,6 +12750,7 @@ msgstr "" "Exemple: show_qfiles qfiles/shunt/*.pck\n" #: bin/sync_members:19 +#, fuzzy msgid "" "Synchronize a mailing list's membership with a flat file.\n" "\n" @@ -12613,6 +12891,7 @@ msgstr "" "\n" #: bin/sync_members:115 +#, fuzzy msgid "Bad choice: %(yesno)s" msgstr "Elecció errònia: %(yesno)s" @@ -12629,6 +12908,7 @@ msgid "No argument to -f given" msgstr "No s'han donat arguments a -f" #: bin/sync_members:172 +#, fuzzy msgid "Illegal option: %(opt)s" msgstr "Opció errònia: %(opt)s" @@ -12641,6 +12921,7 @@ msgid "Must have a listname and a filename" msgstr "Es necessari tenir un nom de llista i un nom de fitxer" #: bin/sync_members:191 +#, fuzzy msgid "Cannot read address file: %(filename)s: %(msg)s" msgstr "No es pot llegir el fitxer d'adreça: %(filename)s: %(msg)s" @@ -12657,14 +12938,17 @@ msgid "You must fix the preceding invalid addresses first." msgstr "Primer has de fixar l'anterior adreça invàlida." #: bin/sync_members:264 +#, fuzzy msgid "Added : %(s)s" msgstr "afegit : %(s)s" #: bin/sync_members:289 +#, fuzzy msgid "Removed: %(s)s" msgstr "Eliminat: %(s)s" #: bin/transcheck:19 +#, fuzzy msgid "" "\n" "Check a given Mailman translation, making sure that variables and\n" @@ -12774,6 +13058,7 @@ msgstr "" "qfiles/shunt.\n" #: bin/unshunt:85 +#, fuzzy msgid "" "Cannot unshunt message %(filebase)s, skipping:\n" "%(e)s" @@ -12782,6 +13067,7 @@ msgstr "" "%(e)s" #: bin/update:20 +#, fuzzy msgid "" "Perform all necessary upgrades.\n" "\n" @@ -12820,14 +13106,17 @@ msgstr "" # FIXME: fixant. jm #: bin/update:107 +#, fuzzy msgid "Fixing language templates: %(listname)s" msgstr "S'està fixant la plantilla de llengua: %(listname)s" #: bin/update:196 bin/update:711 +#, fuzzy msgid "WARNING: could not acquire lock for list: %(listname)s" msgstr "Atenció: no s'ha pogut adquirir el bloqueig de la llista: %(listname)s" #: bin/update:215 +#, fuzzy msgid "Resetting %(n)s BYBOUNCEs disabled addrs with no bounce info" msgstr "" "S'estan posant a zero %(n)s BYBOUNCEs adreces inhabilitades sense informació " @@ -12846,6 +13135,7 @@ msgstr "" "la b6, així que es canviarà el nom a %(mbox_dir)s.tmp i es continuarà." #: bin/update:255 +#, fuzzy msgid "" "\n" "%(listname)s has both public and private mbox archives. Since this list\n" @@ -12894,6 +13184,7 @@ msgid "- updating old private mbox file" msgstr "- s'està actualitzant l'antic fitxer mbox privat" #: bin/update:295 +#, fuzzy msgid "" " unknown file in the way, moving\n" " %(o_pri_mbox_file)s\n" @@ -12910,6 +13201,7 @@ msgid "- updating old public mbox file" msgstr "- s'està actualitzant el fitxer mbox públic antic" #: bin/update:317 +#, fuzzy msgid "" " unknown file in the way, moving\n" " %(o_pub_mbox_file)s\n" @@ -12938,18 +13230,22 @@ msgid "- %(o_tmpl)s doesn't exist, leaving untouched" msgstr "- no existeix %(o_tmpl)s; no es modificarà" #: bin/update:396 +#, fuzzy msgid "removing directory %(src)s and everything underneath" msgstr "s'està suprimint el directori %(src)s i tot el seu contingut" #: bin/update:399 +#, fuzzy msgid "removing %(src)s" msgstr "s'està suprimint %(src)s" #: bin/update:403 +#, fuzzy msgid "Warning: couldn't remove %(src)s -- %(rest)s" msgstr "Atenció: no es pot eliminar %(src)s -- %(rest)s" #: bin/update:408 +#, fuzzy msgid "couldn't remove old file %(pyc)s -- %(rest)s" msgstr "No es pot eliminar l'antic fitxer %(pyc)s -- %(rest)s" @@ -12958,14 +13254,17 @@ msgid "updating old qfiles" msgstr "S'estan actualitzant els antics qfiles" #: bin/update:455 +#, fuzzy msgid "Warning! Not a directory: %(dirpath)s" msgstr "Avís! No és un directori: %(dirpath)s" #: bin/update:530 +#, fuzzy msgid "message is unparsable: %(filebase)s" msgstr "no es pot analitzar el missatge: %(filebase)s" #: bin/update:544 +#, fuzzy msgid "Warning! Deleting empty .pck file: %(pckfile)s" msgstr "Avís! S'està suprimint un fitxer .pck buit: %(pckfile)s" @@ -13008,6 +13307,7 @@ msgid "done" msgstr "fet" #: bin/update:691 +#, fuzzy msgid "Updating mailing list: %(listname)s" msgstr "S'està actualitzant la llista de correu: %(listname)s" @@ -13072,6 +13372,7 @@ msgid "No updates are necessary." msgstr "No cal fer cap actualització." #: bin/update:796 +#, fuzzy msgid "" "Downgrade detected, from version %(hexlversion)s to version %(hextversion)s\n" "This is probably not safe.\n" @@ -13083,10 +13384,12 @@ msgstr "" "Sortint." #: bin/update:801 +#, fuzzy msgid "Upgrading from version %(hexlversion)s to %(hextversion)s" msgstr "S'està actualitzant la versió %(hexlversion)s a la %(hextversion)s" #: bin/update:810 +#, fuzzy msgid "" "\n" "ERROR:\n" @@ -13358,6 +13661,7 @@ msgstr "" " " #: bin/withlist:175 +#, fuzzy msgid "Unlocking (but not saving) list: %(listname)s" msgstr "S'està desbloquejant (però no desant) la llista: %(listname)s" @@ -13366,6 +13670,7 @@ msgid "Finalizing" msgstr "S'està finalitzant" #: bin/withlist:188 +#, fuzzy msgid "Loading list %(listname)s" msgstr "S'està carregant la llista %(listname)s" @@ -13378,6 +13683,7 @@ msgid "(unlocked)" msgstr "(desblocat)" #: bin/withlist:197 +#, fuzzy msgid "Unknown list: %(listname)s" msgstr "Llista desconeguda: %(listname)s" @@ -13390,18 +13696,22 @@ msgid "--all requires --run" msgstr "--all necessita --run" #: bin/withlist:266 +#, fuzzy msgid "Importing %(module)s..." msgstr "Important %(module)s..." #: bin/withlist:270 +#, fuzzy msgid "Running %(module)s.%(callable)s()..." msgstr "Executant %(module)s.%(callable)s()..." #: bin/withlist:291 +#, fuzzy msgid "The variable `m' is the %(listname)s MailList instance" msgstr "La variable «m» és la instància de la llista de correu %(listname)s" #: cron/bumpdigests:19 +#, fuzzy msgid "" "Increment the digest volume number and reset the digest number to one.\n" "\n" @@ -13431,6 +13741,7 @@ msgstr "" "no s'especifiqui cap llista, s'incrementaran totes.\n" #: cron/checkdbs:20 +#, fuzzy msgid "" "Check for pending admin requests and mail the list owners if necessary.\n" "\n" @@ -13460,12 +13771,14 @@ msgstr "" "\n" #: cron/checkdbs:121 +#, fuzzy msgid "%(count)d %(realname)s moderator request(s) waiting" msgstr "" "Hi ha %(count)d sol·licituds de moderació de la llista %(realname)s per " "processar" #: cron/checkdbs:124 +#, fuzzy msgid "%(realname)s moderator request check result" msgstr "resultat de la comprovació de la petició de moderació de %(realname)s" @@ -13487,6 +13800,7 @@ msgstr "" "Enviaments pendents:" #: cron/checkdbs:169 +#, fuzzy msgid "" "From: %(sender)s on %(date)s\n" "Subject: %(subject)s\n" @@ -13497,6 +13811,7 @@ msgstr "" "Causa: %(reason)s" #: cron/cull_bad_shunt:20 +#, fuzzy msgid "" "Cull bad and shunt queues, recommended once per day.\n" "\n" @@ -13538,6 +13853,7 @@ msgstr "" " Mostra aquest missatge i surt.\n" #: cron/disabled:20 +#, fuzzy msgid "" "Process disabled members, recommended once per day.\n" "\n" @@ -13669,6 +13985,7 @@ msgstr "" "\n" #: cron/mailpasswds:19 +#, fuzzy msgid "" "Send password reminders for all lists to all users.\n" "\n" @@ -13725,10 +14042,12 @@ msgid "Password // URL" msgstr "Contrasenya // URL" #: cron/mailpasswds:222 +#, fuzzy msgid "%(host)s mailing list memberships reminder" msgstr "Recordatori de pertinença a les llistes de correu de %(host)s" #: cron/nightly_gzip:19 +#, fuzzy msgid "" "Re-generate the Pipermail gzip'd archive flat files.\n" "\n" @@ -13826,8 +14145,8 @@ msgstr "" #~ " applied" #~ msgstr "" #~ "Text a incloure a les\n" -#~ " notificacions de denegació a\n" #~ " enviar als membres moderats que enviïn a aquesta llista." diff --git a/messages/cs/LC_MESSAGES/mailman.po b/messages/cs/LC_MESSAGES/mailman.po index 22f3f454..9989e9ff 100755 --- a/messages/cs/LC_MESSAGES/mailman.po +++ b/messages/cs/LC_MESSAGES/mailman.po @@ -16,8 +16,8 @@ msgstr "" "Content-Type: text/plain; charset=ISO-8859-2\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: KBabel 1.11.4\n" -"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" -"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " +"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" #: Mailman/Archiver/HyperArch.py:124 msgid "size not available" @@ -70,10 +70,12 @@ msgid "

                  Currently, there are no archives.

                  " msgstr "

                  V souèasné dobì neexistují ¾ádné archivy.

                  " #: Mailman/Archiver/HyperArch.py:821 +#, fuzzy msgid "Gzip'd Text%(sz)s" msgstr "Komprimovaný text %(sz)s" #: Mailman/Archiver/HyperArch.py:826 +#, fuzzy msgid "Text%(sz)s" msgstr "Text %(sz)s" @@ -146,18 +148,22 @@ msgid "Third" msgstr "Tøetí" #: Mailman/Archiver/HyperArch.py:938 +#, fuzzy msgid "%(ord)s quarter %(year)i" msgstr "%(ord)s ètvrtletí %(year)i" #: Mailman/Archiver/HyperArch.py:945 +#, fuzzy msgid "%(month)s %(year)i" msgstr "%(month)s %(year)i" #: Mailman/Archiver/HyperArch.py:950 +#, fuzzy msgid "The Week Of Monday %(day)i %(month)s %(year)i" msgstr "Týden, který zaèíná pondìlím %(day)i %(month)s %(year)i" #: Mailman/Archiver/HyperArch.py:954 +#, fuzzy msgid "%(day)i %(month)s %(year)i" msgstr "%(day)i %(month)s %(year)i" @@ -166,10 +172,12 @@ msgid "Computing threaded index\n" msgstr "Generuji index pro pøíspìvky sdru¾ené do vláken\n" #: Mailman/Archiver/HyperArch.py:1319 +#, fuzzy msgid "Updating HTML for article %(seq)s" msgstr "Aktualizuji HTML stránky pro pøíspìvek %(seq)s" #: Mailman/Archiver/HyperArch.py:1326 +#, fuzzy msgid "article file %(filename)s is missing!" msgstr "soubor %(filename)s ve kterém je ulo¾en pøíspìvek nebyl nalezen!" @@ -186,6 +194,7 @@ msgid "Pickling archive state into " msgstr "Ukládám stav archivu do" #: Mailman/Archiver/pipermail.py:453 +#, fuzzy msgid "Updating index files for archive [%(archive)s]" msgstr "Aktualizuji indexový soubor pro archiv konference [%(archive)s]" @@ -194,6 +203,7 @@ msgid " Thread" msgstr " Thread" #: Mailman/Archiver/pipermail.py:597 +#, fuzzy msgid "#%(counter)05d %(msgid)s" msgstr "#%(counter)05d %(msgid)s" @@ -231,6 +241,7 @@ msgid "disabled address" msgstr "zakázáno" #: Mailman/Bouncer.py:304 +#, fuzzy msgid " The last bounce received from you was dated %(date)s" msgstr "" " Poslední pøíspìvek, který se vrátil jako nedoruèitelný na Va¹i adresu má " @@ -260,6 +271,7 @@ msgstr "Administr #: Mailman/Cgi/options.py:101 Mailman/Cgi/private.py:108 #: Mailman/Cgi/rmlist.py:75 Mailman/Cgi/roster.py:59 #: Mailman/Cgi/subscribe.py:67 +#, fuzzy msgid "No such list %(safelistname)s" msgstr "Nenalezl jsem konferenci %(safelistname)s." @@ -330,6 +342,7 @@ msgstr "" " povoleno jen rozesíláni digestu. Tito nebudou dostávat po¹tu.%(rm)r" #: Mailman/Cgi/admin.py:268 +#, fuzzy msgid "%(hostname)s mailing lists - Admin Links" msgstr "Konference na serveru %(hostname)s - Administrativní odkazy" @@ -342,6 +355,7 @@ msgid "Mailman" msgstr "Mailman" #: Mailman/Cgi/admin.py:310 +#, fuzzy msgid "" "

                  There currently are no publicly-advertised %(mailmanlink)s\n" " mailing lists on %(hostname)s." @@ -350,6 +364,7 @@ msgstr "" " veøejnì pøístupné konference spravované %(mailmanlink)s." #: Mailman/Cgi/admin.py:316 +#, fuzzy msgid "" "

                  Below is the collection of publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s. Click on a list\n" @@ -364,6 +379,7 @@ msgid "right " msgstr "Vpravo" #: Mailman/Cgi/admin.py:325 +#, fuzzy msgid "" "To visit the administrators configuration page for an\n" " unadvertised list, open a URL similar to this one, but with a '/' " @@ -408,6 +424,7 @@ msgid "No valid variable name found." msgstr "Nenalezl jsem ¾ádný platný název promìnné" #: Mailman/Cgi/admin.py:395 +#, fuzzy msgid "" "%(realname)s Mailing list Configuration Help\n" "
                  %(varname)s Option" @@ -416,6 +433,7 @@ msgstr "" "
                  Nápovìda pro volbu %(varname)s." #: Mailman/Cgi/admin.py:402 +#, fuzzy msgid "Mailman %(varname)s List Option Help" msgstr "Nápovìda pro promìnnou %(varname)s" @@ -433,14 +451,17 @@ msgstr "" " v¹echny obnovit, ne¾ na nich budete dìlat nìjaké zmìny. Nebo mù¾ete " #: Mailman/Cgi/admin.py:431 +#, fuzzy msgid "return to the %(categoryname)s options page." msgstr "pøejít zpìt na stránku %(categoryname)s." #: Mailman/Cgi/admin.py:446 +#, fuzzy msgid "%(realname)s Administration (%(label)s)" msgstr "Administrace konference %(realname)s (%(label)s)" #: Mailman/Cgi/admin.py:447 +#, fuzzy msgid "%(realname)s mailing list administration
                  %(label)s Section" msgstr "Konfigurace konference %(realname)s
                  %(label)s" @@ -523,6 +544,7 @@ msgid "Value" msgstr "Hodnota" #: Mailman/Cgi/admin.py:668 +#, fuzzy msgid "" "Badly formed options entry:\n" " %(record)s" @@ -623,10 +645,12 @@ msgid "Move rule down" msgstr "Posunout pravidlo ní¾e" #: Mailman/Cgi/admin.py:870 +#, fuzzy msgid "
                  (Edit %(varname)s)" msgstr "
                  (Editovat %(varname)s)" #: Mailman/Cgi/admin.py:872 +#, fuzzy msgid "
                  (Details for %(varname)s)" msgstr "
                  (Detaily o %(varname)s)" @@ -667,6 +691,7 @@ msgid "(help)" msgstr "(nápovìda)" #: Mailman/Cgi/admin.py:930 +#, fuzzy msgid "Find member %(link)s:" msgstr "Nalezni úèastníka %(link)s:" @@ -679,10 +704,12 @@ msgid "Bad regular expression: " msgstr "Chybný regulární výraz: " #: Mailman/Cgi/admin.py:1013 +#, fuzzy msgid "%(allcnt)s members total, %(membercnt)s shown" msgstr "Konference má %(allcnt)s úèastníkù, %(membercnt)s je zobrazeno" #: Mailman/Cgi/admin.py:1016 +#, fuzzy msgid "%(allcnt)s members total" msgstr "Celkem %(allcnt)s úèastníkù" @@ -864,6 +891,7 @@ msgstr "" "chcete zobrazit." #: Mailman/Cgi/admin.py:1221 +#, fuzzy msgid "from %(start)s to %(end)s" msgstr "od %(start)s do %(end)s" @@ -1003,6 +1031,7 @@ msgid "Change list ownership passwords" msgstr "Zmìna hesla vlastníka konference" #: Mailman/Cgi/admin.py:1364 +#, fuzzy msgid "" "The list administrators are the people who have ultimate control " "over\n" @@ -1031,8 +1060,8 @@ msgstr "" "Nemohou mìnit konfiguraci, pouze rozhodují o distribuci pozastavených\n" "pøíspìvkù a ¾ádostech o pøihlá¹ení do konference.\n" "\n" -"

                  Moderátoøi se zakládají vepsáním jejich emailových adres do pøíslu¹ných políèek na stránce v¹eobecných " +"

                  Moderátoøi se zakládají vepsáním jejich emailových adres do pøíslu¹ných políèek na stránce v¹eobecných " "vlastností. a zadáním hesla do ní¾e uvedeného vstupního pole." #: Mailman/Cgi/admin.py:1383 @@ -1112,6 +1141,7 @@ msgstr "Neplatn #: Mailman/Cgi/admin.py:1529 Mailman/Cgi/admin.py:1703 bin/add_members:175 #: bin/clone_member:136 bin/sync_members:268 +#, fuzzy msgid "Banned address (matched %(pattern)s)" msgstr "Zakázaná adresa (vyhovuje vzoru %(pattern)s)" @@ -1213,6 +1243,7 @@ msgid "Not subscribed" msgstr "Není pøihlá¹en" #: Mailman/Cgi/admin.py:1773 +#, fuzzy msgid "Ignoring changes to deleted member: %(user)s" msgstr "Ignoruji zmìny provedené u odhlá¹eného úèastníka: %(user)s" @@ -1225,10 +1256,12 @@ msgid "Error Unsubscribing:" msgstr "Chyba pøi odhla¹ování" #: Mailman/Cgi/admindb.py:235 Mailman/Cgi/admindb.py:248 +#, fuzzy msgid "%(realname)s Administrative Database" msgstr "Konference %(realname)s -- administrace" #: Mailman/Cgi/admindb.py:238 +#, fuzzy msgid "%(realname)s Administrative Database Results" msgstr "Konference %(realname)s -- výsledky editace databáze po¾adavkù" @@ -1257,6 +1290,7 @@ msgid "Discard all messages marked Defer" msgstr "Zahodit v¹echny zprávy oznaèené jako Odlo¾" #: Mailman/Cgi/admindb.py:298 +#, fuzzy msgid "all of %(esender)s's held messages." msgstr "v¹echny pozdr¾ené zprávy od úèastníka %(esender)s." @@ -1277,6 +1311,7 @@ msgid "list of available mailing lists." msgstr "seznam konferencí" #: Mailman/Cgi/admindb.py:362 +#, fuzzy msgid "You must specify a list name. Here is the %(link)s" msgstr "Musíte zadat název konference, zde je odkaz %(link)s" @@ -1359,6 +1394,7 @@ msgid "The sender is now a member of this list" msgstr "Odesílatel je nyní úèastníkem konference." #: Mailman/Cgi/admindb.py:568 +#, fuzzy msgid "Add %(esender)s to one of these sender filters:" msgstr "Pøidej %(esender)s do jednoho z tìchto filtrù odesílatelù:" @@ -1379,6 +1415,7 @@ msgid "Rejects" msgstr "Odmítni" #: Mailman/Cgi/admindb.py:584 +#, fuzzy msgid "" "Ban %(esender)s from ever subscribing to this\n" " mailing list" @@ -1395,6 +1432,7 @@ msgstr "" "nebo mù¾ete" #: Mailman/Cgi/admindb.py:591 +#, fuzzy msgid "view all messages from %(esender)s" msgstr "zobrazit v¹echny zprávy od %(esender)s" @@ -1473,6 +1511,7 @@ msgid " is already a member" msgstr "je úèastníkem konference" #: Mailman/Cgi/admindb.py:973 +#, fuzzy msgid "%(addr)s is banned (matched: %(patt)s)" msgstr "%(addr)s je zakázána (vyhovuje vzoru: %(patt)s)" @@ -1524,6 +1563,7 @@ msgstr "" "Po¾adavek byl zru¹en." #: Mailman/Cgi/confirm.py:178 +#, fuzzy msgid "System error, bad content: %(content)s" msgstr "System error, bad content: %(content)s" @@ -1562,6 +1602,7 @@ msgid "Confirm subscription request" msgstr "Potvrï ¾ádosti o pøihlá¹ení" #: Mailman/Cgi/confirm.py:259 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " subscription request to the mailing list %(listname)s. Your\n" @@ -1595,6 +1636,7 @@ msgstr "" "a sma¾e zadané údaje." #: Mailman/Cgi/confirm.py:275 +#, fuzzy msgid "" "Your confirmation is required in order to continue with\n" " the subscription request to the mailing list %(listname)s.\n" @@ -1647,6 +1689,7 @@ msgid "Preferred language:" msgstr "Preferovaný jazyk:" #: Mailman/Cgi/confirm.py:317 +#, fuzzy msgid "Subscribe to list %(listname)s" msgstr "Pøihlá¹ení do konference %(listname)s" @@ -1663,6 +1706,7 @@ msgid "Awaiting moderator approval" msgstr "Pøíspìvek byl pozastaven do souhlasu moderátora" #: Mailman/Cgi/confirm.py:386 +#, fuzzy msgid "" " You have successfully confirmed your subscription request to " "the\n" @@ -1694,6 +1738,7 @@ msgid "You are already a member of this mailing list!" msgstr "Ji¾ jste úèastníkem této konference!" #: Mailman/Cgi/confirm.py:400 +#, fuzzy msgid "" "You are currently banned from subscribing to\n" " this list. If you think this restriction is erroneous, please\n" @@ -1718,6 +1763,7 @@ msgid "Subscription request confirmed" msgstr "®ádost o pøihlá¹ení byla potvrzena" #: Mailman/Cgi/confirm.py:418 +#, fuzzy msgid "" " You have successfully confirmed your subscription request for\n" " \"%(addr)s\" to the %(listname)s mailing list. A separate\n" @@ -1746,6 +1792,7 @@ msgid "Unsubscription request confirmed" msgstr "®ádost o odhlá¹ení byla potvrzena" #: Mailman/Cgi/confirm.py:469 +#, fuzzy msgid "" " You have successfully unsubscribed from the %(listname)s " "mailing\n" @@ -1766,6 +1813,7 @@ msgid "Not available" msgstr "Není definováno ¾ádné téma" #: Mailman/Cgi/confirm.py:498 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " unsubscription request from the mailing list %(listname)s. " @@ -1807,6 +1855,7 @@ msgid "You have canceled your change of address request." msgstr "Zru¹il jste po¾adavek na zmìnu adresy." #: Mailman/Cgi/confirm.py:555 +#, fuzzy msgid "" "%(newaddr)s is banned from subscribing to the\n" " %(realname)s list. If you think this restriction is erroneous,\n" @@ -1835,6 +1884,7 @@ msgid "Change of address request confirmed" msgstr "Zmìna adresy byla odsouhlasena" #: Mailman/Cgi/confirm.py:571 +#, fuzzy msgid "" " You have successfully changed your address on the %(listname)s\n" " mailing list from %(oldaddr)s to %(newaddr)s. " @@ -1858,6 +1908,7 @@ msgid "globally" msgstr "globálnì" #: Mailman/Cgi/confirm.py:605 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " change of address request for the mailing list %(listname)s. " @@ -1916,6 +1967,7 @@ msgid "Sender discarded message via web." msgstr "Odesílatel zru¹il pøíspìvek pøes webovské rozhraní." #: Mailman/Cgi/confirm.py:674 +#, fuzzy msgid "" "The held message with the Subject:\n" " header %(subject)s could not be found. The most " @@ -1935,6 +1987,7 @@ msgid "Posted message canceled" msgstr "Pøíspìvek byl stornován" #: Mailman/Cgi/confirm.py:685 +#, fuzzy msgid "" " You have successfully canceled the posting of your message with\n" " the Subject: header %(subject)s to the mailing list\n" @@ -1955,6 +2008,7 @@ msgid "" msgstr "Pøíspìvek, který jste chtìl zobrazit byl ji¾ vyøízen moderátorem." #: Mailman/Cgi/confirm.py:735 +#, fuzzy msgid "" "Your confirmation is required in order to cancel the\n" " posting of your message to the mailing list %(listname)s:\n" @@ -2002,6 +2056,7 @@ msgid "Membership re-enabled." msgstr "Èlenství bylo znovu povoleno." #: Mailman/Cgi/confirm.py:798 +#, fuzzy msgid "" " You have successfully re-enabled your membership in the\n" " %(listname)s mailing list. You can now not available" msgstr "není k dispozici" #: Mailman/Cgi/confirm.py:846 +#, fuzzy msgid "" "Your membership in the %(realname)s mailing list is\n" " currently disabled due to excessive bounces. Your confirmation is\n" @@ -2097,10 +2154,12 @@ msgid "administrative list overview" msgstr "stránku s administrací konference" #: Mailman/Cgi/create.py:115 +#, fuzzy msgid "List name must not include \"@\": %(safelistname)s" msgstr "Jméno konference nesmí obsahovat \"@\": %(safelistname)s" #: Mailman/Cgi/create.py:122 +#, fuzzy msgid "List already exists: %(safelistname)s" msgstr "Konference ji¾ existuje : %(safelistname)s" @@ -2134,18 +2193,22 @@ msgid "You are not authorized to create new mailing lists" msgstr "Nemáte právo zalo¾it novou konferenci." #: Mailman/Cgi/create.py:182 +#, fuzzy msgid "Unknown virtual host: %(safehostname)s" msgstr "Neznámá virtuální host: %(safehostname)s" #: Mailman/Cgi/create.py:218 bin/newlist:219 +#, fuzzy msgid "Bad owner email address: %(s)s" msgstr "Neplatná adresa vlastníka: %(s)s" #: Mailman/Cgi/create.py:223 bin/newlist:182 bin/newlist:223 +#, fuzzy msgid "List already exists: %(listname)s" msgstr "Konference ji¾ existuje : %(listname)s" #: Mailman/Cgi/create.py:231 bin/newlist:217 +#, fuzzy msgid "Illegal list name: %(s)s" msgstr "Nepøípustný název konference %(s)s" @@ -2158,6 +2221,7 @@ msgstr "" " Prosíme, kontaktuje správce serveru." #: Mailman/Cgi/create.py:273 bin/newlist:265 +#, fuzzy msgid "Your new mailing list: %(listname)s" msgstr "Va¹e nová konference : %(listname)s" @@ -2166,6 +2230,7 @@ msgid "Mailing list creation results" msgstr "Výsledky vytvoøení konference" #: Mailman/Cgi/create.py:288 +#, fuzzy msgid "" "You have successfully created the mailing list\n" " %(listname)s and notification has been sent to the list owner\n" @@ -2188,6 +2253,7 @@ msgid "Create another list" msgstr "Zalo¾it dal¹í konferenci?" #: Mailman/Cgi/create.py:312 +#, fuzzy msgid "Create a %(hostname)s Mailing List" msgstr "Vytvoø konferenci %(hostname)s" @@ -2278,6 +2344,7 @@ msgstr "" "moderátora." #: Mailman/Cgi/create.py:432 +#, fuzzy msgid "" "Initial list of supported languages.

                  Note that if you do not\n" " select at least one initial language, the list will use the server\n" @@ -2384,6 +2451,7 @@ msgid "List name is required." msgstr "Jméno konference je vy¾adováno." #: Mailman/Cgi/edithtml.py:154 +#, fuzzy msgid "%(realname)s -- Edit html for %(template_info)s" msgstr "Konference %(realname)s -- Editovat ¹ablony pro %(template_info)s" @@ -2392,10 +2460,12 @@ msgid "Edit HTML : Error" msgstr "Chyba pøi editaci HTML ¹ablon" #: Mailman/Cgi/edithtml.py:161 +#, fuzzy msgid "%(safetemplatename)s: Invalid template" msgstr "%(safetemplatename)s: vadná ¹ablona" #: Mailman/Cgi/edithtml.py:166 Mailman/Cgi/edithtml.py:167 +#, fuzzy msgid "%(realname)s -- HTML Page Editing" msgstr "Konference %(realname)s -- Editace HTML stránek" @@ -2454,10 +2524,12 @@ msgid "HTML successfully updated." msgstr "HTML ¹ablony byly úspì¹nì aktualizovány." #: Mailman/Cgi/listinfo.py:89 +#, fuzzy msgid "%(hostname)s Mailing Lists" msgstr "Konference na serveru %(hostname)s" #: Mailman/Cgi/listinfo.py:127 +#, fuzzy msgid "" "

                  There currently are no publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s." @@ -2466,6 +2538,7 @@ msgstr "" " %(mailmanlink)s na serveru %(hostname)s." #: Mailman/Cgi/listinfo.py:131 +#, fuzzy msgid "" "

                  Below is a listing of all the public mailing lists on\n" " %(hostname)s. Click on a list name to get more information " @@ -2485,6 +2558,7 @@ msgid "right" msgstr "Vpravo" #: Mailman/Cgi/listinfo.py:140 +#, fuzzy msgid "" " To visit the general information page for an unadvertised list,\n" " open a URL similar to this one, but with a '/' and the %(adj)s\n" @@ -2546,6 +2620,7 @@ msgstr "Neplatn #: Mailman/Cgi/options.py:183 Mailman/Cgi/options.py:240 #: Mailman/Cgi/options.py:264 +#, fuzzy msgid "No such member: %(safeuser)s." msgstr "Nenalezl jsem úèastníka: %(safeuser)s." @@ -2598,6 +2673,7 @@ msgid "Note: " msgstr "Poznámka " #: Mailman/Cgi/options.py:378 +#, fuzzy msgid "List subscriptions for %(safeuser)s on %(hostname)s" msgstr "" "Seznam konferencí, ve kterých je pøihlá¹en u¾ivatel %(safeuser)s na serveru " @@ -2628,6 +2704,7 @@ msgid "You are already using that email address" msgstr "Tuto emailovou adresu ji¾ pou¾íváte" #: Mailman/Cgi/options.py:459 +#, fuzzy msgid "" "The new address you requested %(newaddr)s is already a member of the\n" "%(listname)s mailing list, however you have also requested a global change " @@ -2641,6 +2718,7 @@ msgstr "" "konferencích, kterých je u¾ivatel %(safeuser)s èlenem." #: Mailman/Cgi/options.py:468 +#, fuzzy msgid "The new address is already a member: %(newaddr)s" msgstr "Nová adresa je v konferenci ji¾ pøihlá¹ena: %(newaddr)s" @@ -2649,6 +2727,7 @@ msgid "Addresses may not be blank" msgstr "Adresy nesmí zùstat nevyplnìné." #: Mailman/Cgi/options.py:488 +#, fuzzy msgid "A confirmation message has been sent to %(newaddr)s. " msgstr "Zpráva s upozornìním byla zaslána na adresu %(newaddr)s" @@ -2661,10 +2740,12 @@ msgid "Illegal email address provided" msgstr "Neplatná emailová adresa" #: Mailman/Cgi/options.py:501 +#, fuzzy msgid "%(newaddr)s is already a member of the list." msgstr "%(newaddr)s ji¾ je pøihlá¹en." #: Mailman/Cgi/options.py:504 +#, fuzzy msgid "" "%(newaddr)s is banned from this list. If you\n" " think this restriction is erroneous, please contact\n" @@ -2740,6 +2821,7 @@ msgstr "" " O výsledku budete informován " #: Mailman/Cgi/options.py:623 +#, fuzzy msgid "" "You have been successfully unsubscribed from the\n" " mailing list %(fqdn_listname)s. If you were receiving digest\n" @@ -2832,6 +2914,7 @@ msgid "day" msgstr "den" #: Mailman/Cgi/options.py:900 +#, fuzzy msgid "%(days)d %(units)s" msgstr "%(days)d %(units)s" @@ -2844,6 +2927,7 @@ msgid "No topics defined" msgstr "Není definováno ¾ádné téma" #: Mailman/Cgi/options.py:940 +#, fuzzy msgid "" "\n" "You are subscribed to this list with the case-preserved address\n" @@ -2855,6 +2939,7 @@ msgstr "" "%(cpuser)s." #: Mailman/Cgi/options.py:956 +#, fuzzy msgid "%(realname)s list: member options login page" msgstr "Konference %(realname)s: pøihlá¹ení úèastníka pro editaci parametrù" @@ -2863,10 +2948,12 @@ msgid "email address and " msgstr "emailová adresa a " #: Mailman/Cgi/options.py:960 +#, fuzzy msgid "%(realname)s list: member options for user %(safeuser)s" msgstr "Nastavení parametrù pro %(safeuser)s v konferenci %(realname)s" #: Mailman/Cgi/options.py:986 +#, fuzzy msgid "" "In order to change your membership option, you must\n" " first log in by giving your %(extra)smembership password in the section\n" @@ -2943,6 +3030,7 @@ msgid "" msgstr "" #: Mailman/Cgi/options.py:1140 +#, fuzzy msgid "Requested topic is not valid: %(topicname)s" msgstr "Téma, které po¾adujete, neexistuje: %(topicname)s" @@ -2971,6 +3059,7 @@ msgid "Private archive - \"./\" and \"../\" not allowed in URL." msgstr "V URL soukromého archivu nejsou dovoleny znaky \"./\" and \"../\"" #: Mailman/Cgi/private.py:109 +#, fuzzy msgid "Private Archive Error - %(msg)s" msgstr "Chyba v neveøejném archivu - %(msg)s" @@ -3009,6 +3098,7 @@ msgid "Mailing list deletion results" msgstr "Výsledek smazání konference" #: Mailman/Cgi/rmlist.py:187 +#, fuzzy msgid "" "You have successfully deleted the mailing list\n" " %(listname)s." @@ -3017,6 +3107,7 @@ msgstr "" " %(listname)s." #: Mailman/Cgi/rmlist.py:191 +#, fuzzy msgid "" "There were some problems deleting the mailing list\n" " %(listname)s. Contact your site administrator at " @@ -3027,6 +3118,7 @@ msgstr "" "Kontaktujte správce serveru %(sitelist)s pro dal¹í informace. " #: Mailman/Cgi/rmlist.py:208 +#, fuzzy msgid "Permanently remove mailing list %(realname)s" msgstr "Skuteènì nevratnì zru¹it konferenci %(realname)s" @@ -3091,6 +3183,7 @@ msgid "Invalid options to CGI script" msgstr "Neplatné parametry cgi skriptu." #: Mailman/Cgi/roster.py:118 +#, fuzzy msgid "%(realname)s roster authentication failed." msgstr "U¾ivatel %(realname)s nepodaøilo se pøihlásit." @@ -3159,6 +3252,7 @@ msgstr "" "zprávu s podrobnými instrukcemi." #: Mailman/Cgi/subscribe.py:262 +#, fuzzy msgid "" "The email address you supplied is banned from this\n" " mailing list. If you think this restriction is erroneous, please\n" @@ -3184,6 +3278,7 @@ msgstr "" "Jste si jist, ¾e do ní Exchange nepøidala neviditelné znaky?" #: Mailman/Cgi/subscribe.py:278 +#, fuzzy msgid "" "Confirmation from your email address is required, to prevent anyone from\n" "subscribing you without permission. Instructions are being sent to you at\n" @@ -3196,6 +3291,7 @@ msgstr "" "udìlat." #: Mailman/Cgi/subscribe.py:290 +#, fuzzy msgid "" "Your subscription request was deferred because %(x)s. Your request has " "been\n" @@ -3216,6 +3312,7 @@ msgid "Mailman privacy alert" msgstr "Upozornìní na mo¾né poru¹ení soukromí" #: Mailman/Cgi/subscribe.py:316 +#, fuzzy msgid "" "An attempt was made to subscribe your address to the mailing list\n" "%(listaddr)s. You are already subscribed to this mailing list.\n" @@ -3259,6 +3356,7 @@ msgid "This list only supports digest delivery." msgstr "Tato konference podporuje jedinì digest re¾im." #: Mailman/Cgi/subscribe.py:344 +#, fuzzy msgid "You have been successfully subscribed to the %(realname)s mailing list." msgstr "Úspì¹nì jste se pøihlásil do konference %(realname)s." @@ -3308,6 +3406,7 @@ msgstr "" "adresu?" #: Mailman/Commands/cmd_confirm.py:69 +#, fuzzy msgid "" "You are currently banned from subscribing to this list. If you think this\n" "restriction is erroneous, please contact the list owners at\n" @@ -3391,26 +3490,32 @@ msgid "n/a" msgstr "není známo" #: Mailman/Commands/cmd_info.py:44 +#, fuzzy msgid "List name: %(listname)s" msgstr "Konference: %(listname)s" #: Mailman/Commands/cmd_info.py:45 +#, fuzzy msgid "Description: %(description)s" msgstr "Popis: %(description)s" #: Mailman/Commands/cmd_info.py:46 +#, fuzzy msgid "Postings to: %(postaddr)s" msgstr "Pøíspìvky na adresu: %(postaddr)s" #: Mailman/Commands/cmd_info.py:47 +#, fuzzy msgid "List Helpbot: %(requestaddr)s" msgstr "Adresa robota s nápovìdou: %(requestaddr)s" #: Mailman/Commands/cmd_info.py:48 +#, fuzzy msgid "List Owners: %(owneraddr)s" msgstr "Správci konference: %(owneraddr)s" #: Mailman/Commands/cmd_info.py:49 +#, fuzzy msgid "More information: %(listurl)s" msgstr "Dal¹í informace jsou na: %(listurl)s" @@ -3433,18 +3538,22 @@ msgstr "" " Zobraz seznam konferencí na tomto serveru.\n" #: Mailman/Commands/cmd_lists.py:44 +#, fuzzy msgid "Public mailing lists at %(hostname)s:" msgstr "Veøejnì pøístupné konference provozované na %(hostname)s:" #: Mailman/Commands/cmd_lists.py:66 +#, fuzzy msgid "%(i)3d. List name: %(realname)s" msgstr "%(i)3d. Konference: %(realname)s" #: Mailman/Commands/cmd_lists.py:67 +#, fuzzy msgid " Description: %(description)s" msgstr " Popis: %(description)s" #: Mailman/Commands/cmd_lists.py:68 +#, fuzzy msgid " Requests to: %(requestaddr)s" msgstr " Po¾adavky na adresu: %(requestaddr)s" @@ -3478,12 +3587,14 @@ msgstr "" " jste pøihlá¹eni a ne na tu aktuální.\n" #: Mailman/Commands/cmd_password.py:51 Mailman/Commands/cmd_password.py:66 +#, fuzzy msgid "Your password is: %(password)s" msgstr "Va¹e heslo je: %(password)s" #: Mailman/Commands/cmd_password.py:57 Mailman/Commands/cmd_password.py:72 #: Mailman/Commands/cmd_password.py:95 Mailman/Commands/cmd_password.py:121 #: Mailman/Commands/cmd_set.py:149 Mailman/Commands/cmd_set.py:219 +#, fuzzy msgid "You are not a member of the %(listname)s mailing list" msgstr "Nejste úèastníkem konference %(listname)s." @@ -3672,6 +3783,7 @@ msgstr "" " s heslem.\n" #: Mailman/Commands/cmd_set.py:122 +#, fuzzy msgid "Bad set command: %(subcmd)s" msgstr "Chybný parametr u pøíkazu set: %(subcmd)s" @@ -3692,6 +3804,7 @@ msgid "on" msgstr "on" #: Mailman/Commands/cmd_set.py:154 +#, fuzzy msgid " ack %(onoff)s" msgstr " ack %(onoff)s" @@ -3729,22 +3842,27 @@ msgid "due to bounces" msgstr "kvùli nedoruèitelnosti" #: Mailman/Commands/cmd_set.py:186 +#, fuzzy msgid " %(status)s (%(how)s on %(date)s)" msgstr " %(status)s (%(how)s dne %(date)s)" #: Mailman/Commands/cmd_set.py:192 +#, fuzzy msgid " myposts %(onoff)s" msgstr " myposts %(onoff)s" #: Mailman/Commands/cmd_set.py:195 +#, fuzzy msgid " hide %(onoff)s" msgstr " hide %(onoff)s" #: Mailman/Commands/cmd_set.py:199 +#, fuzzy msgid " duplicates %(onoff)s" msgstr " duplicates %(onoff)s" #: Mailman/Commands/cmd_set.py:203 +#, fuzzy msgid " reminders %(onoff)s" msgstr " reminders %(onoff)s" @@ -3753,6 +3871,7 @@ msgid "You did not give the correct password" msgstr "Zadali jste ¹patné heslo." #: Mailman/Commands/cmd_set.py:236 Mailman/Commands/cmd_set.py:283 +#, fuzzy msgid "Bad argument: %(arg)s" msgstr "Chybný argument: %(arg)s" @@ -3836,6 +3955,7 @@ msgstr "" " address=pepa@freemail.fr\n" #: Mailman/Commands/cmd_subscribe.py:62 +#, fuzzy msgid "Bad digest specifier: %(arg)s" msgstr "Chybná volba digest: %(arg)s" @@ -3844,6 +3964,7 @@ msgid "No valid address found to subscribe" msgstr "Nebyla nalezena platná adresa pro pøihlá¹ení" #: Mailman/Commands/cmd_subscribe.py:113 +#, fuzzy msgid "" "The email address you supplied is banned from this mailing list.\n" "If you think this restriction is erroneous, please contact the list\n" @@ -3883,6 +4004,7 @@ msgid "This list only supports digest subscriptions!" msgstr "Tato konference podporuje jedinì digest re¾im." #: Mailman/Commands/cmd_subscribe.py:146 +#, fuzzy msgid "" "Your subscription request has been forwarded to the list administrator\n" "at %(listowner)s for review." @@ -3917,6 +4039,7 @@ msgstr "" " volbu za heslo, tentokrát bez address=. \n" #: Mailman/Commands/cmd_unsubscribe.py:62 +#, fuzzy msgid "%(address)s is not a member of the %(listname)s mailing list" msgstr "%(address)s není úèastníkem konference %(listname)s" @@ -4161,6 +4284,7 @@ msgid "Chinese (Taiwan)" msgstr "Èínsky (Taiwan)" #: Mailman/Deliverer.py:53 +#, fuzzy msgid "" "Note: Since this is a list of mailing lists, administrative\n" "notices like the password reminder will be sent to\n" @@ -4174,14 +4298,17 @@ msgid " (Digest mode)" msgstr "Digest" #: Mailman/Deliverer.py:79 +#, fuzzy msgid "Welcome to the \"%(realname)s\" mailing list%(digmode)s" msgstr "Vítejte v konferenci \"%(realname)s\"! %(digmode)s" #: Mailman/Deliverer.py:89 +#, fuzzy msgid "You have been unsubscribed from the %(realname)s mailing list" msgstr "Odhlá¹en z konference \"%(realname)s\"" #: Mailman/Deliverer.py:116 +#, fuzzy msgid "%(listfullname)s mailing list reminder" msgstr "Konference %(listfullname)s -- pøipomínka hesla" @@ -4194,6 +4321,7 @@ msgid "Hostile subscription attempt detected" msgstr "Byl detekován zmateèný pokus o pøihlá¹ení." #: Mailman/Deliverer.py:169 +#, fuzzy msgid "" "%(address)s was invited to a different mailing\n" "list, but in a deliberate malicious attempt they tried to confirm the\n" @@ -4205,6 +4333,7 @@ msgstr "" "Jedná se pouze o upozornìní, není tøeba s tím nic dìlat. " #: Mailman/Deliverer.py:188 +#, fuzzy msgid "" "You invited %(address)s to your list, but in a\n" "deliberate malicious attempt, they tried to confirm the invitation to a\n" @@ -4217,6 +4346,7 @@ msgstr "" "Jedná se pouze o upozornìní, není tøeba s tím cokoliv dìlat." #: Mailman/Deliverer.py:221 +#, fuzzy msgid "%(listname)s mailing list probe message" msgstr "Konference %(listname)s -- zku¹ební zpráva" @@ -4419,8 +4549,8 @@ msgid "" " membership.\n" "\n" "

                  You can control both the\n" -" number\n" +" number\n" " of reminders the member will receive and the\n" " Mù¾ete nastavit jednak\n" -" poèet\n" +" poèet\n" " zaslaných upozornìní, které budou úèastníkovi zaslány a " "také\n" " Poslední dùle¾itý bod konfigurace je nastavení doby po které\n" " bude skóre resetováno, pokud se nevrátí ¾ádný nedoruèitelný " "pøíspìvek.\n" -" Nazývá se doba vypr¹ení\n" +" Nazývá se doba vypr¹ení\n" " pozastavení. Tuto hodnotu musíte sladit s objemem " "komunikace\n" " pøes konferenci a spolu s maximálním skóre urèuje jak rychle " @@ -4630,8 +4760,8 @@ msgid "" " No those messages too will get discarded. You may " "want\n" " to set up an\n" -" autoresponse\n" +" autoresponse\n" " message for email to the -owner and -admin address." msgstr "" "Aèkoliv je detekce nedoruèitelných pøíspìvkù v Mailmanu pomìrnì dokonalá\n" @@ -4726,12 +4856,13 @@ msgstr "" "o dùvodu odhlá¹ení v ka¾dém pøípadì." #: Mailman/Gui/Bounce.py:194 +#, fuzzy msgid "" "Bad value for %(property)s: %(val)s" msgstr "" -"Neplatná hodnota promìnné " -"%(property)s: %(val)s" +"Neplatná hodnota promìnné %(property)s: %(val)s" #: Mailman/Gui/ContentFilter.py:30 msgid "Content filtering" @@ -4875,8 +5006,8 @@ msgid "" "Use this option to remove each message attachment that does\n" " not have a matching content type. Requirements and formats " "are\n" -" exactly like filter_mime_types.\n" "\n" "

                  Note: if you add entries to this list but don't add\n" @@ -4992,6 +5123,7 @@ msgstr "" " nepovolil. " #: Mailman/Gui/ContentFilter.py:171 +#, fuzzy msgid "Bad MIME type ignored: %(spectype)s" msgstr "Chybný MIME typ byl ignorován: %(spectype)s" @@ -5098,6 +5230,7 @@ msgstr "" "prázdná, nebude odesláno nic.)" #: Mailman/Gui/Digest.py:145 +#, fuzzy msgid "" "The next digest will be sent as volume\n" " %(volume)s, number %(number)s" @@ -5114,14 +5247,17 @@ msgid "There was no digest to send." msgstr "®ádné digest dávky neèekají na odeslání." #: Mailman/Gui/GUIBase.py:173 +#, fuzzy msgid "Invalid value for variable: %(property)s" msgstr "Chybná hodnota promìnné: %(property)s" #: Mailman/Gui/GUIBase.py:178 +#, fuzzy msgid "Bad email address for option %(property)s: %(error)s" msgstr "Chybná emailová adresa pro parametr %(property)s: %(error)s" #: Mailman/Gui/GUIBase.py:204 +#, fuzzy msgid "" "The following illegal substitution variables were\n" " found in the %(property)s string:\n" @@ -5136,6 +5272,7 @@ msgstr "" "

                  Dokud nebude problém vyøe¹en, konference nemusí fungovat." #: Mailman/Gui/GUIBase.py:218 +#, fuzzy msgid "" "Your %(property)s string appeared to\n" " have some correctable problems in its new value.\n" @@ -5232,8 +5369,8 @@ msgid "" "

                  In order to split the list ownership duties into\n" " administrators and moderators, you must\n" " set a separate moderator password,\n" -" and also provide the email\n" +" and also provide the email\n" " addresses of the list moderators. Note that the field you\n" " are changing here specifies the list administrators." msgstr "" @@ -5525,13 +5662,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5589,8 +5726,8 @@ msgstr "Nastavovat hlavi msgid "" "This is the address set in the Reply-To: header\n" " when the reply_goes_to_list\n" +" href=\"?VARHELP=general/" +"reply_goes_to_list\">reply_goes_to_list\n" " option is set to Explicit address.\n" "\n" "

                  There are many reasons not to introduce or override the\n" @@ -5598,13 +5735,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5706,8 +5843,8 @@ msgid "" " member list addresses, but rather to the owner of those member\n" " lists. In that case, the value of this setting is appended to\n" " the member's account name for such notices. `-owner' is the\n" -" typical choice. This setting has no effect when \"umbrella_list" -"\"\n" +" typical choice. This setting has no effect when " +"\"umbrella_list\"\n" " is \"No\"." msgstr "" "Pokud je nastaveno, ¾e konference je \"de¹tníková\", znamená to, ¾e jejími " @@ -6141,8 +6278,8 @@ msgid "" " email posted by list members." msgstr "" "Toto je výchozí jazyk pou¾ívaný pro komunikaci s konferencí.\n" -" Pokud je v jazyky k dispozici\n" +" Pokud je v jazyky k dispozici\n" " vybráno více voleb, budou mít úèastníci mo¾nost vybrat si " "jazyk ze seznamu \n" " Obsahujícího vybrané jazyky. Tam kde není akce vázána na " @@ -6410,8 +6547,8 @@ msgid "" " page.\n" "

                \n" msgstr "" -"Pokud je povolena volba personifikace is \n" +"Pokud je povolena volba personifikace is \n" "pro tuto konferenci, je mo¾né pou¾ívat dal¹í promìnné v hlavièkách a " "patièkách emailù.\n" "\n" @@ -6620,6 +6757,7 @@ msgstr "" "neplatné emailové adresy." #: Mailman/Gui/Privacy.py:104 +#, fuzzy msgid "" "This section allows you to configure subscription and\n" " membership exposure policy. You can also control whether this\n" @@ -6806,8 +6944,8 @@ msgid "" " either individually or as a group. Any\n" " posting from a non-member who is not explicitly accepted,\n" " rejected, or discarded, will have their posting filtered by the\n" -" general\n" +" general\n" " non-member rules.\n" "\n" "

                In the text boxes below, add one address per line; start the\n" @@ -6833,10 +6971,10 @@ msgstr "" "\n" "

                Pøíspìvky z adres, které se neúèastní konference mohou být " "automaticky \n" -" akceptovány,\n" -" pozdr¾eny do rozhodnutí moderátora ,\n" +" akceptovány,\n" +" pozdr¾eny do rozhodnutí moderátora ,\n" " odmítnuty (vráceny) a nebo\n" " výchozí akce\n" +" výchozí akce\n" " pro pøíspìvky od neúèastníkù.\n" "\n" "

                Do ní¾e zobrazených rámeèkù zapisujte na ka¾dý øádek jednu " @@ -6869,6 +7007,7 @@ msgid "By default, should new list member postings be moderated?" msgstr "Mají být pøíspìvky nových úèastníkù schvalovány moderátorem?" #: Mailman/Gui/Privacy.py:218 +#, fuzzy msgid "" "Each list member has a moderation flag which says\n" " whether messages from the list member can be posted directly " @@ -6922,8 +7061,8 @@ msgid "" "If a member posts this many times, within a period of time\n" " the member is automatically moderated. Use 0 to disable. " "See\n" -" member_verbosity_interval for details on the time " "period.\n" "\n" @@ -7352,8 +7491,8 @@ msgstr "" " porovnává jeho adresu se seznamy automaticky \n" " akceptovaných,\n" -" pozastavovaných,\n" +" pozastavovaných,\n" " zamítaných (vracených) a \n" " The body of the message can also be optionally scanned for\n" " Subject: and Keywords: headers, as\n" " specified by the topics_bodylines_limit\n" +" href=\"?VARHELP=topics/" +"topics_bodylines_limit\">topics_bodylines_limit\n" " configuration variable." msgstr "" "Tématický filtr zatøídí ka¾dou pøijatou zprávu s pou¾itím %(note)s\n" "\n" @@ -8025,6 +8172,7 @@ msgstr "" " Kontaktujte %(mailto)s, pokud máte dotaz nebo potøebujete pomoci." #: Mailman/HTMLFormatter.py:169 +#, fuzzy msgid "" "

                We have received some recent bounces from your\n" " address. Your current bounce score is %(score)s out of " @@ -8047,6 +8195,7 @@ msgstr "" " problémù, bude Va¹e skóre resetováno." #: Mailman/HTMLFormatter.py:181 +#, fuzzy msgid "" "(Note - you are subscribing to a list of mailing lists, so the %(type)s " "notice will be sent to the admin address for your membership, %(addr)s.)

                " @@ -8091,6 +8240,7 @@ msgstr "" "informováni elektronickou po¹tou." #: Mailman/HTMLFormatter.py:208 +#, fuzzy msgid "" "This is %(also)sa private list, which means that the\n" " list of members is not available to non-members." @@ -8099,6 +8249,7 @@ msgstr "" "úèastníkù je pøístupný pouze úèastníkùm." #: Mailman/HTMLFormatter.py:211 +#, fuzzy msgid "" "This is %(also)sa hidden list, which means that the\n" " list of members is available only to the list administrator." @@ -8107,6 +8258,7 @@ msgstr "" "administrátorovi." #: Mailman/HTMLFormatter.py:214 +#, fuzzy msgid "" "This is %(also)sa public list, which means that the\n" " list of members list is available to everyone." @@ -8123,6 +8275,7 @@ msgstr "" "dal¹ích úprav, pro spamming.)" #: Mailman/HTMLFormatter.py:222 +#, fuzzy msgid "" "

                (Note that this is an umbrella list, intended to\n" " have only other mailing lists as members. Among other things,\n" @@ -8138,6 +8291,7 @@ msgid "either " msgstr " nebo " #: Mailman/HTMLFormatter.py:256 +#, fuzzy msgid "" "To unsubscribe from %(realname)s, get a password reminder,\n" " or change your subscription options %(either)senter your " @@ -8168,12 +8322,14 @@ msgid "" msgstr "Pokud toto políèko nevyplníte, budete po¾ádán o emailovou adresu." #: Mailman/HTMLFormatter.py:277 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " members.)" msgstr "%(which)s je k dispozici pouze pro úèastníky konference.)" #: Mailman/HTMLFormatter.py:281 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " administrator.)" @@ -8234,6 +8390,7 @@ msgid "The current archive" msgstr "aktuální archiv" #: Mailman/Handlers/Acknowledge.py:59 +#, fuzzy msgid "%(realname)s post acknowledgement" msgstr "Konference %(realname)s - potvrzení o pøijetí pøíspìvku k distribuci" @@ -8246,6 +8403,7 @@ msgid "" msgstr "" #: Mailman/Handlers/CalcRecips.py:79 +#, fuzzy msgid "" "Your urgent message to the %(realname)s mailing list was not authorized for\n" "delivery. The original message as received by Mailman is attached.\n" @@ -8327,6 +8485,7 @@ msgid "Message may contain administrivia" msgstr "Zpráva pravdìpodobnì obsahuje pøíkazy pro listserver" #: Mailman/Handlers/Hold.py:84 +#, fuzzy msgid "" "Please do *not* post administrative requests to the mailing\n" "list. If you wish to subscribe, visit %(listurl)s or send a message with " @@ -8368,10 +8527,12 @@ msgid "Posting to a moderated newsgroup" msgstr "Pøíspìvek do moderované konference" #: Mailman/Handlers/Hold.py:252 +#, fuzzy msgid "Your message to %(listname)s awaits moderator approval" msgstr "Vá¹ pøíspìvek do konference %(listname)s èeká na schválení moderátorem" #: Mailman/Handlers/Hold.py:271 +#, fuzzy msgid "%(listname)s post from %(sender)s requires approval" msgstr "" "Pøíspìvek od %(sender)s do konference %(listname)s vy¾aduje souhlas " @@ -8415,6 +8576,7 @@ msgid "After content filtering, the message was empty" msgstr "Po pøefiltrování obsahu nezbyl ¾ádný text k odeslání" #: Mailman/Handlers/MimeDel.py:269 +#, fuzzy msgid "" "The attached message matched the %(listname)s mailing list's content " "filtering\n" @@ -8460,6 +8622,7 @@ msgid "The attached message has been automatically discarded." msgstr "Pøilo¾ená zpráva byla automaticky zahozena." #: Mailman/Handlers/Replybot.py:75 +#, fuzzy msgid "Auto-response for your message to the \"%(realname)s\" mailing list" msgstr "Automatická odpovìï na zprávu zaslanou do konference \"%(realname)s\"" @@ -8483,6 +8646,7 @@ msgid "HTML attachment scrubbed and removed" msgstr "Pøíspìvek byl vyèi¹tìn a HTML èást byla odstranìna" #: Mailman/Handlers/Scrubber.py:234 Mailman/Handlers/Scrubber.py:259 +#, fuzzy msgid "" "An HTML attachment was scrubbed...\n" "URL: %(url)s\n" @@ -8537,6 +8701,7 @@ msgstr "" "Url : %(url)s\n" #: Mailman/Handlers/Scrubber.py:347 +#, fuzzy msgid "Skipped content of type %(partctype)s\n" msgstr "Zde byl umístìn nepøijatelný obsah typu: %(partctype)s\n" @@ -8568,6 +8733,7 @@ msgid "Message rejected by filter rule match" msgstr "Zpráva byla zamítnuta filtrovacím pravidlem" #: Mailman/Handlers/ToDigest.py:173 +#, fuzzy msgid "%(realname)s Digest, Vol %(volume)d, Issue %(issue)d" msgstr "%(realname)s Digest, Vol %(volume)d, Issue %(issue)d" @@ -8604,6 +8770,7 @@ msgid "End of " msgstr "Konec: " #: Mailman/ListAdmin.py:309 +#, fuzzy msgid "Posting of your message titled \"%(subject)s\"" msgstr "Va¹e zpráva ve vìci %(subject)s\"" @@ -8616,6 +8783,7 @@ msgid "Forward of moderated message" msgstr "Forwardovat moderovanou zprávu" #: Mailman/ListAdmin.py:405 +#, fuzzy msgid "New subscription request to list %(realname)s from %(addr)s" msgstr "Po¾adavek na pøihlá¹ení do konference %(realname)s od %(addr)s" @@ -8629,6 +8797,7 @@ msgid "via admin approval" msgstr "Pokraèuj v èekání na souhlas" #: Mailman/ListAdmin.py:466 +#, fuzzy msgid "New unsubscription request from %(realname)s by %(addr)s" msgstr "Po¾adavek na pøihlá¹ení do konference %(realname)s od %(addr)s" @@ -8641,10 +8810,12 @@ msgid "Original Message" msgstr "Pùvodní zpráva" #: Mailman/ListAdmin.py:526 +#, fuzzy msgid "Request to mailing list %(realname)s rejected" msgstr "Po¾adavek do konference %(realname)s byl zamítnut." #: Mailman/MTA/Manual.py:66 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been created via the through-the-web\n" "interface. In order to complete the activation of this mailing list, the\n" @@ -8670,14 +8841,17 @@ msgstr "" "pøíkaz newaliases).\n" #: Mailman/MTA/Manual.py:82 +#, fuzzy msgid "## %(listname)s mailing list" msgstr "## %(listname)s mailing list" #: Mailman/MTA/Manual.py:99 +#, fuzzy msgid "Mailing list creation request for list %(listname)s" msgstr "Administrativní po¾adavky pro konferenci '%(listname)s' " #: Mailman/MTA/Manual.py:113 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been removed via the through-the-web\n" "interface. In order to complete the de-activation of this mailing list, " @@ -8693,6 +8867,7 @@ msgstr "" "\n" #: Mailman/MTA/Manual.py:123 +#, fuzzy msgid "" "\n" "To finish removing your mailing list, you must edit your /etc/aliases (or\n" @@ -8708,14 +8883,17 @@ msgstr "" "## %(listname)s mailing list" #: Mailman/MTA/Manual.py:142 +#, fuzzy msgid "Mailing list removal request for list %(listname)s" msgstr "Po¾adavek na zru¹ení konference %(listname)s" #: Mailman/MTA/Postfix.py:442 +#, fuzzy msgid "checking permissions on %(file)s" msgstr "Ovìøuji práva souboru %(file)s" #: Mailman/MTA/Postfix.py:452 +#, fuzzy msgid "%(file)s permissions must be 0664 (got %(octmode)s)" msgstr "Práva na souboru: %(file)s musí být 0664 (jsou %(octmode)s)" @@ -8729,35 +8907,43 @@ msgid "(fixing)" msgstr "(opravuji)" #: Mailman/MTA/Postfix.py:470 +#, fuzzy msgid "checking ownership of %(dbfile)s" msgstr "Ovìøuji práva souboru %(dbfile)s" #: Mailman/MTA/Postfix.py:478 +#, fuzzy msgid "%(dbfile)s owned by %(owner)s (must be owned by %(user)s" msgstr "" "Soubor %(dbfile)s patøí u¾ivateli %(owner)s (musí být vlastnìn %(user)s)" #: Mailman/MTA/Postfix.py:491 +#, fuzzy msgid "%(dbfile)s permissions must be 0664 (got %(octmode)s)" msgstr "Práva na souboru: %(dbfile)s musí být 0664 (jsou %(octmode)s)" #: Mailman/MailList.py:219 +#, fuzzy msgid "Your confirmation is required to join the %(listname)s mailing list" msgstr "Pro pøihlá¹ení do konference %(listname)s je nutné ovìøení" #: Mailman/MailList.py:230 +#, fuzzy msgid "Your confirmation is required to leave the %(listname)s mailing list" msgstr "Pro odhlá¹ení z konference %(listname)s je nutné ovìøení." #: Mailman/MailList.py:999 Mailman/MailList.py:1492 +#, fuzzy msgid " from %(remote)s" msgstr "od %(remote)s" #: Mailman/MailList.py:1043 +#, fuzzy msgid "subscriptions to %(realname)s require moderator approval" msgstr "pøihlá¹ky do konference %(realname)s vy¾adují souhlas moderátora" #: Mailman/MailList.py:1125 bin/add_members:299 +#, fuzzy msgid "%(realname)s subscription notification" msgstr "%(realname)s zpráva o pøihlá¹ení." @@ -8766,6 +8952,7 @@ msgid "unsubscriptions require moderator approval" msgstr "odhlá¹ení z konference vy¾aduje souhlas moderátora" #: Mailman/MailList.py:1166 +#, fuzzy msgid "%(realname)s unsubscribe notification" msgstr "%(realname)s zpráva o odhlá¹ení" @@ -8785,6 +8972,7 @@ msgid "via web confirmation" msgstr "Chybný potvrzovací øetìzec" #: Mailman/MailList.py:1396 +#, fuzzy msgid "subscriptions to %(name)s require administrator approval" msgstr "pøihlá¹ky do konference %(name)s vy¾adují souhlas moderátora" @@ -8803,6 +8991,7 @@ msgid "Last autoresponse notification for today" msgstr "Poslední dne¹ní automatická odpovìï" #: Mailman/Queue/BounceRunner.py:360 +#, fuzzy msgid "" "The attached message was received as a bounce, but either the bounce format\n" "was not recognized, or no member addresses could be extracted from it. " @@ -8895,6 +9084,7 @@ msgid "Original message suppressed by Mailman site configuration\n" msgstr "" #: Mailman/htmlformat.py:675 +#, fuzzy msgid "Delivered by Mailman
                version %(version)s" msgstr "Doruèeno Mailmanem
                verze %(version)s" @@ -8983,6 +9173,7 @@ msgid "Server Local Time" msgstr "Místní èas serveru" #: Mailman/i18n.py:180 +#, fuzzy msgid "" "%(wday)s %(mon)s %(day)2i %(hh)02i:%(mm)02i:%(ss)02i %(tzname)s %(year)04i" msgstr "" @@ -9052,6 +9243,7 @@ msgid "" msgstr "" #: bin/add_members:162 bin/add_members:172 +#, fuzzy msgid "Already a member: %(member)s" msgstr "Je ji¾ úèastníkem %(member)s" @@ -9060,10 +9252,12 @@ msgid "Bad/Invalid email address: blank line" msgstr "Neplatná emailová adresa: prázdný øádek" #: bin/add_members:180 +#, fuzzy msgid "Bad/Invalid email address: %(member)s" msgstr "Neplatná emailová adresa: %(member)s" #: bin/add_members:182 +#, fuzzy msgid "Hostile address (illegal characters): %(member)s" msgstr "Neplatná adresa (obsahuje nepovolené znaky): %(member)s" @@ -9073,14 +9267,17 @@ msgid "Invited: %(member)s" msgstr "Pøihlá¹en: %(member)s" #: bin/add_members:187 +#, fuzzy msgid "Subscribed: %(member)s" msgstr "Pøihlá¹en: %(member)s" #: bin/add_members:237 +#, fuzzy msgid "Bad argument to -w/--welcome-msg: %(arg)s" msgstr "Chybný parametr u -w/--welcome-msg: %(arg)s" #: bin/add_members:244 +#, fuzzy msgid "Bad argument to -a/--admin-notify: %(arg)s" msgstr "Chybný parametr u -a/--admin-notify: %(arg)s" @@ -9097,6 +9294,7 @@ msgstr "" #: bin/add_members:261 bin/config_list:110 bin/export.py:271 bin/find_member:97 #: bin/inject:91 bin/list_admins:90 bin/list_members:252 bin/sync_members:222 #: cron/bumpdigests:86 +#, fuzzy msgid "No such list: %(listname)s" msgstr "Nenalezl jsem konferenci %(listname)s" @@ -9160,6 +9358,7 @@ msgid "listname is required" msgstr "Jméno konference je vy¾adováno." #: bin/arch:143 bin/change_pw:107 bin/config_list:257 +#, fuzzy msgid "" "No such list \"%(listname)s\"\n" "%(e)s" @@ -9168,6 +9367,7 @@ msgstr "" "%(e)s" #: bin/arch:168 +#, fuzzy msgid "Cannot open mbox file %(mbox)s: %(msg)s" msgstr "Nemohu otevøít mbox %(mbox)s: %(msg)s" @@ -9249,6 +9449,7 @@ msgid "" msgstr "" #: bin/change_pw:145 +#, fuzzy msgid "Bad arguments: %(strargs)s" msgstr "Chybné argumenty: %(strargs)s" @@ -9257,10 +9458,12 @@ msgid "Empty list passwords are not allowed" msgstr "Není dovoleno, aby správce konference mìl prázdné heslo." #: bin/change_pw:181 +#, fuzzy msgid "New %(listname)s password: %(notifypassword)s" msgstr "Nové heslo pro konferenci %(listname)s: %(notifypassword)s" #: bin/change_pw:190 +#, fuzzy msgid "Your new %(listname)s list password" msgstr "Va¹e nové heslo pro %(listname)s" @@ -9340,40 +9543,47 @@ msgid "" msgstr "" #: bin/check_perms:110 +#, fuzzy msgid " checking gid and mode for %(path)s" -msgstr "" +msgstr "Ovìøuji práva souboru %(file)s" #: bin/check_perms:122 msgid "%(path)s bad group (has: %(groupname)s, expected %(MAILMAN_GROUP)s)" msgstr "" #: bin/check_perms:151 +#, fuzzy msgid "directory permissions must be %(octperms)s: %(path)s" -msgstr "" +msgstr "Práva na souboru: %(dbfile)s musí být 0664 (jsou %(octmode)s)" #: bin/check_perms:160 +#, fuzzy msgid "source perms must be %(octperms)s: %(path)s" -msgstr "" +msgstr "Práva na souboru: %(dbfile)s musí být 0664 (jsou %(octmode)s)" #: bin/check_perms:171 +#, fuzzy msgid "article db files must be %(octperms)s: %(path)s" -msgstr "" +msgstr "Práva na souboru: %(dbfile)s musí být 0664 (jsou %(octmode)s)" #: bin/check_perms:183 +#, fuzzy msgid "checking mode for %(prefix)s" -msgstr "" +msgstr "Ovìøuji práva souboru %(file)s" #: bin/check_perms:193 msgid "WARNING: directory does not exist: %(d)s" msgstr "" #: bin/check_perms:197 +#, fuzzy msgid "directory must be at least 02775: %(d)s" -msgstr "" +msgstr "Práva na souboru: %(file)s musí být 0664 (jsou %(octmode)s)" #: bin/check_perms:209 +#, fuzzy msgid "checking perms on %(private)s" -msgstr "" +msgstr "Ovìøuji práva souboru %(file)s" #: bin/check_perms:214 msgid "%(private)s must not be other-readable" @@ -9401,40 +9611,46 @@ msgid "checking cgi-bin permissions" msgstr "" #: bin/check_perms:278 +#, fuzzy msgid " checking set-gid for %(path)s" -msgstr "" +msgstr "Ovìøuji práva souboru %(file)s" #: bin/check_perms:282 msgid "%(path)s must be set-gid" msgstr "" #: bin/check_perms:292 +#, fuzzy msgid "checking set-gid for %(wrapper)s" -msgstr "" +msgstr "Ovìøuji práva souboru %(file)s" #: bin/check_perms:296 msgid "%(wrapper)s must be set-gid" msgstr "" #: bin/check_perms:306 +#, fuzzy msgid "checking permissions on %(pwfile)s" -msgstr "" +msgstr "Ovìøuji práva souboru %(file)s" #: bin/check_perms:315 +#, fuzzy msgid "%(pwfile)s permissions must be exactly 0640 (got %(octmode)s)" -msgstr "" +msgstr "Práva na souboru: %(file)s musí být 0664 (jsou %(octmode)s)" #: bin/check_perms:340 msgid "checking permissions on list data" msgstr "" #: bin/check_perms:348 +#, fuzzy msgid " checking permissions on: %(path)s" -msgstr "" +msgstr "Ovìøuji práva souboru %(file)s" #: bin/check_perms:356 +#, fuzzy msgid "file permissions must be at least 660: %(path)s" -msgstr "" +msgstr "Práva na souboru: %(file)s musí být 0664 (jsou %(octmode)s)" #: bin/check_perms:401 msgid "No problems found" @@ -9487,6 +9703,7 @@ msgid "Unix-From line changed: %(lineno)d" msgstr "" #: bin/cleanarch:111 +#, fuzzy msgid "Bad status number: %(arg)s" msgstr "Chybný stavový kód: %(arg)s" @@ -9585,14 +9802,16 @@ msgid " original address removed:" msgstr "" #: bin/clone_member:202 +#, fuzzy msgid "Not a valid email address: %(toaddr)s" msgstr "Neplatná emailová adresa %(toaddr)s" #: bin/clone_member:215 +#, fuzzy msgid "" "Error opening list \"%(listname)s\", skipping.\n" "%(e)s" -msgstr "" +msgstr "Chyba pøi otevírání konfigurace konference %(listname)s... pøeskakuji." #: bin/config_list:20 msgid "" @@ -9649,6 +9868,7 @@ msgid "" msgstr "" #: bin/config_list:118 +#, fuzzy msgid "" "# -*- python -*-\n" "# -*- coding: %(charset)s -*-\n" @@ -9681,10 +9901,12 @@ msgid "Non-standard property restored: %(k)s" msgstr "" #: bin/config_list:288 +#, fuzzy msgid "Invalid value for property: %(k)s" msgstr "Chybná hodnota parametru: %(k)s" #: bin/config_list:291 +#, fuzzy msgid "Bad email address for option %(k)s: %(v)s" msgstr "Chybná emailová adresa pro parametr %(k)s: %(v)s" @@ -9733,16 +9955,19 @@ msgid "" msgstr "" #: bin/discard:94 +#, fuzzy msgid "Ignoring non-held message: %(f)s" msgstr "Ignoruji nepozdr¾enou zprávu: %(f)s" #: bin/discard:100 +#, fuzzy msgid "Ignoring held msg w/bad id: %(f)s" -msgstr "" +msgstr "Ignoruji nepozdr¾enou zprávu: %(f)s" #: bin/discard:112 +#, fuzzy msgid "Discarded held msg #%(id)s for list %(listname)s" -msgstr "" +msgstr "Pøihlá¹ení do konference %(listname)s" #: bin/dumpdb:19 msgid "" @@ -9786,8 +10011,9 @@ msgid "No filename given." msgstr "Nebyl zadán název souboru." #: bin/dumpdb:108 +#, fuzzy msgid "Bad arguments: %(pargs)s" -msgstr "" +msgstr "Chybný argument: %(arg)s" #: bin/dumpdb:118 msgid "Please specify either -p or -m." @@ -10037,8 +10263,9 @@ msgid "" msgstr "" #: bin/list_admins:97 +#, fuzzy msgid "List: %(listname)s, \tOwners: %(owners)s" -msgstr "" +msgstr "Správci konference: %(owneraddr)s" #: bin/list_lists:19 msgid "" @@ -10143,12 +10370,14 @@ msgid "" msgstr "" #: bin/list_members:198 +#, fuzzy msgid "Bad --nomail option: %(why)s" -msgstr "" +msgstr "Chybná volba digest: %(arg)s" #: bin/list_members:209 +#, fuzzy msgid "Bad --digest option: %(kind)s" -msgstr "" +msgstr "Chybná volba digest: %(arg)s" #: bin/list_members:213 bin/list_members:217 bin/list_members:221 #: bin/list_members:225 @@ -10332,8 +10561,9 @@ msgid "" msgstr "" #: bin/mailmanctl:280 cron/mailpasswds:119 +#, fuzzy msgid "Site list is missing: %(sitelistname)s" -msgstr "" +msgstr "Konference ji¾ existuje : %(safelistname)s" #: bin/mailmanctl:305 msgid "Run this program as root or as the %(name)s user, or use -u." @@ -10344,8 +10574,9 @@ msgid "No command given." msgstr "Nebyl zadán ¾ádný pøíkaz." #: bin/mailmanctl:339 +#, fuzzy msgid "Bad command: %(command)s" -msgstr "" +msgstr "Chybný parametr u pøíkazu set: %(subcmd)s" #: bin/mailmanctl:344 msgid "Warning! You may encounter permission problems." @@ -10559,6 +10790,7 @@ msgid "" msgstr "" #: bin/newlist:162 +#, fuzzy msgid "Unknown language: %(lang)s" msgstr "Neznámý jazyk: %(lang)s" @@ -10571,6 +10803,7 @@ msgid "Enter the email of the person running the list: " msgstr "Zadejte emailovou adresu správce konference:" #: bin/newlist:193 +#, fuzzy msgid "Initial %(listname)s password: " msgstr "Poèáteèní heslo pro konferenci %(listname)s: " @@ -10580,11 +10813,12 @@ msgstr "Heslo pro konferenci nesm #: bin/newlist:220 msgid "" -" - owner addresses need to be fully-qualified names like \"owner@example.com" -"\", not just \"owner\"." +" - owner addresses need to be fully-qualified names like \"owner@example." +"com\", not just \"owner\"." msgstr "" #: bin/newlist:244 +#, fuzzy msgid "Hit enter to notify %(listname)s owner..." msgstr "" "Stisknìte ENTER pro zaslání informace o zalo¾ení konference na adresu " @@ -10751,14 +10985,17 @@ msgid "Could not open file for reading: %(filename)s." msgstr "" #: bin/remove_members:163 +#, fuzzy msgid "Error opening list %(listname)s... skipping." msgstr "Chyba pøi otevírání konfigurace konference %(listname)s... pøeskakuji." #: bin/remove_members:173 +#, fuzzy msgid "No such member: %(addr)s" msgstr "Nenalezl jsem úèastníka: %(addr)s." #: bin/remove_members:178 +#, fuzzy msgid "User `%(addr)s' removed from list: %(listname)s." msgstr "Úèastníka `%(addr)s' byl odhlá¹en z konference: %(listname)s." @@ -10783,8 +11020,9 @@ msgid "" msgstr "" #: bin/reset_pw.py:77 +#, fuzzy msgid "Changing passwords for list: %(listname)s" -msgstr "" +msgstr "Po¾adavek na zru¹ení konference %(listname)s" #: bin/reset_pw.py:83 msgid "New password for member %(member)40s: %(randompw)s" @@ -10821,10 +11059,12 @@ msgid "%(listname)s %(msg)s not found as %(filename)s" msgstr "" #: bin/rmlist:105 +#, fuzzy msgid "No such list (or list already deleted): %(listname)s" msgstr "Nenalezl jsem konferenci (tøeba byla smazána) - %(listname)s" #: bin/rmlist:108 +#, fuzzy msgid "No such list: %(listname)s. Removing its residual archives." msgstr "Nenalezl jsem konferenci %(listname)s - odstraòuji zbytky archivù." @@ -10956,8 +11196,9 @@ msgid "No argument to -f given" msgstr "Nebyl zadán parametr k -f" #: bin/sync_members:172 +#, fuzzy msgid "Illegal option: %(opt)s" -msgstr "" +msgstr "Nepøípustný název konference %(s)s" #: bin/sync_members:178 msgid "No listname given" @@ -10968,8 +11209,9 @@ msgid "Must have a listname and a filename" msgstr "" #: bin/sync_members:191 +#, fuzzy msgid "Cannot read address file: %(filename)s: %(msg)s" -msgstr "" +msgstr "Nemohu otevøít mbox %(mbox)s: %(msg)s" #: bin/sync_members:203 msgid "Ignore : %(addr)30s" @@ -11092,8 +11334,9 @@ msgid "" msgstr "" #: bin/update:107 +#, fuzzy msgid "Fixing language templates: %(listname)s" -msgstr "" +msgstr "Va¹e nová konference : %(listname)s" #: bin/update:196 bin/update:711 msgid "WARNING: could not acquire lock for list: %(listname)s" @@ -11247,8 +11490,9 @@ msgid "done" msgstr "hotovo" #: bin/update:691 +#, fuzzy msgid "Updating mailing list: %(listname)s" -msgstr "" +msgstr "Va¹e nová konference : %(listname)s" #: bin/update:694 msgid "Updating Usenet watermarks" @@ -11453,6 +11697,7 @@ msgid "" msgstr "" #: bin/withlist:175 +#, fuzzy msgid "Unlocking (but not saving) list: %(listname)s" msgstr "Odemkni, ale neulo¾, konferenci : %(listname)s" @@ -11461,8 +11706,9 @@ msgid "Finalizing" msgstr "" #: bin/withlist:188 +#, fuzzy msgid "Loading list %(listname)s" -msgstr "" +msgstr "Neznámá konference: %(listname)s" #: bin/withlist:190 msgid "(locked)" @@ -11473,6 +11719,7 @@ msgid "(unlocked)" msgstr "" #: bin/withlist:197 +#, fuzzy msgid "Unknown list: %(listname)s" msgstr "Neznámá konference: %(listname)s" @@ -11531,12 +11778,14 @@ msgid "" msgstr "" #: cron/checkdbs:121 +#, fuzzy msgid "%(count)d %(realname)s moderator request(s) waiting" msgstr "%(realname)s - Na rozhodnutí moderátora èeká %(count)d pøíspìvkù." #: cron/checkdbs:124 +#, fuzzy msgid "%(realname)s moderator request check result" -msgstr "" +msgstr "%(realname)s - Na rozhodnutí moderátora èeká %(count)d pøíspìvkù." #: cron/checkdbs:144 msgid "Pending subscriptions:" @@ -11556,6 +11805,7 @@ msgstr "" "Èekající pøíspìvky:" #: cron/checkdbs:169 +#, fuzzy msgid "" "From: %(sender)s on %(date)s\n" "Subject: %(subject)s\n" @@ -11686,6 +11936,7 @@ msgid "Password // URL" msgstr "Heslo // URL" #: cron/mailpasswds:222 +#, fuzzy msgid "%(host)s mailing list memberships reminder" msgstr "Server %(host)s: Upozornìní na úèast v konferencích" @@ -11753,8 +12004,8 @@ msgstr "" #~ " applied" #~ msgstr "" #~ "Text, který bude pøilo¾en ke ka¾dé\n" -#~ " zprávì o zamítnutí pøíspìvku,\n" #~ " která je zaslána autorovi zprávy." diff --git a/messages/da/LC_MESSAGES/mailman.po b/messages/da/LC_MESSAGES/mailman.po index f8e051b4..9ed66f7f 100755 --- a/messages/da/LC_MESSAGES/mailman.po +++ b/messages/da/LC_MESSAGES/mailman.po @@ -68,10 +68,12 @@ msgid "

                Currently, there are no archives.

                " msgstr "

                Arkivet er for tiden tomt.

                " #: Mailman/Archiver/HyperArch.py:821 +#, fuzzy msgid "Gzip'd Text%(sz)s" msgstr "Gzip'et tekst%(sz)s" #: Mailman/Archiver/HyperArch.py:826 +#, fuzzy msgid "Text%(sz)s" msgstr "Tekst%(sz)s" @@ -144,18 +146,22 @@ msgid "Third" msgstr "Tredje" #: Mailman/Archiver/HyperArch.py:938 +#, fuzzy msgid "%(ord)s quarter %(year)i" msgstr "%(ord)s kvartal %(year)i" #: Mailman/Archiver/HyperArch.py:945 +#, fuzzy msgid "%(month)s %(year)i" msgstr "%(month)s %(year)i" #: Mailman/Archiver/HyperArch.py:950 +#, fuzzy msgid "The Week Of Monday %(day)i %(month)s %(year)i" msgstr "Ugen fra mandag %(day)i %(month)s %(year)i" #: Mailman/Archiver/HyperArch.py:954 +#, fuzzy msgid "%(day)i %(month)s %(year)i" msgstr "%(day)i %(month)s %(year)i" @@ -164,10 +170,12 @@ msgid "Computing threaded index\n" msgstr "Opretter indholdsfortegnelse\n" #: Mailman/Archiver/HyperArch.py:1319 +#, fuzzy msgid "Updating HTML for article %(seq)s" msgstr "Opdaterer HTML for artikel %(seq)s" #: Mailman/Archiver/HyperArch.py:1326 +#, fuzzy msgid "article file %(filename)s is missing!" msgstr "artikelfilen %(filename)s mangler!" @@ -184,6 +192,7 @@ msgid "Pickling archive state into " msgstr "Lagrer arkivets tilstand i en pickle: " #: Mailman/Archiver/pipermail.py:453 +#, fuzzy msgid "Updating index files for archive [%(archive)s]" msgstr "Opdaterer indeksfil for arkivet [%(archive)s]" @@ -192,6 +201,7 @@ msgid " Thread" msgstr " Tråd" #: Mailman/Archiver/pipermail.py:597 +#, fuzzy msgid "#%(counter)05d %(msgid)s" msgstr "#%(counter)05d %(msgid)s" @@ -229,6 +239,7 @@ msgid "disabled address" msgstr "stoppet" #: Mailman/Bouncer.py:304 +#, fuzzy msgid " The last bounce received from you was dated %(date)s" msgstr " Sidst modtagne returmail fra dig var dateret %(date)s" @@ -256,6 +267,7 @@ msgstr "Administrator" #: Mailman/Cgi/options.py:101 Mailman/Cgi/private.py:108 #: Mailman/Cgi/rmlist.py:75 Mailman/Cgi/roster.py:59 #: Mailman/Cgi/subscribe.py:67 +#, fuzzy msgid "No such list %(safelistname)s" msgstr "Listen findes ikke: %(safelistname)s" @@ -327,6 +339,7 @@ msgstr "" "fordi du har valgt denne måde at distribuere e-mail på.%(rm)r" #: Mailman/Cgi/admin.py:268 +#, fuzzy msgid "%(hostname)s mailing lists - Admin Links" msgstr "Maillister på %(hostname)s - Administrativ adgang" @@ -339,6 +352,7 @@ msgid "Mailman" msgstr "Mailman" #: Mailman/Cgi/admin.py:310 +#, fuzzy msgid "" "

                There currently are no publicly-advertised %(mailmanlink)s\n" " mailing lists on %(hostname)s." @@ -348,6 +362,7 @@ msgstr "" " på %(hostname)s." #: Mailman/Cgi/admin.py:316 +#, fuzzy msgid "" "

                Below is the collection of publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s. Click on a list\n" @@ -362,6 +377,7 @@ msgid "right " msgstr "rigtige " #: Mailman/Cgi/admin.py:325 +#, fuzzy msgid "" "To visit the administrators configuration page for an\n" " unadvertised list, open a URL similar to this one, but with a '/' " @@ -407,6 +423,7 @@ msgid "No valid variable name found." msgstr "Fandt intet gyldigt variabelnavn." #: Mailman/Cgi/admin.py:395 +#, fuzzy msgid "" "%(realname)s Mailing list Configuration Help\n" "
                %(varname)s Option" @@ -415,6 +432,7 @@ msgstr "" "
                Indstilling: %(varname)s" #: Mailman/Cgi/admin.py:402 +#, fuzzy msgid "Mailman %(varname)s List Option Help" msgstr "Indstilling: %(varname)s" @@ -433,14 +451,17 @@ msgstr "" "din netlæser.   " #: Mailman/Cgi/admin.py:431 +#, fuzzy msgid "return to the %(categoryname)s options page." msgstr "Tilbage til %(categoryname)s" #: Mailman/Cgi/admin.py:446 +#, fuzzy msgid "%(realname)s Administration (%(label)s)" msgstr "%(realname)s Administration (%(label)s)" #: Mailman/Cgi/admin.py:447 +#, fuzzy msgid "%(realname)s mailing list administration
                %(label)s Section" msgstr "%(realname)s administration
                %(label)s" @@ -524,6 +545,7 @@ msgid "Value" msgstr "Værdi" #: Mailman/Cgi/admin.py:668 +#, fuzzy msgid "" "Badly formed options entry:\n" " %(record)s" @@ -624,10 +646,12 @@ msgid "Move rule down" msgstr "Flytte regel ned" #: Mailman/Cgi/admin.py:870 +#, fuzzy msgid "
                (Edit %(varname)s)" msgstr "
                (Rediger %(varname)s)" #: Mailman/Cgi/admin.py:872 +#, fuzzy msgid "
                (Details for %(varname)s)" msgstr "
                (Detaljer for %(varname)s)" @@ -668,6 +692,7 @@ msgid "(help)" msgstr "(hjælp)" #: Mailman/Cgi/admin.py:930 +#, fuzzy msgid "Find member %(link)s:" msgstr "Find medlem %(link)s:" @@ -680,10 +705,12 @@ msgid "Bad regular expression: " msgstr "Ugyldigt regexp-udtryk: " #: Mailman/Cgi/admin.py:1013 +#, fuzzy msgid "%(allcnt)s members total, %(membercnt)s shown" msgstr "Totalt %(allcnt)s medlemmer, kun %(membercnt)s er vist." #: Mailman/Cgi/admin.py:1016 +#, fuzzy msgid "%(allcnt)s members total" msgstr "Totalt %(allcnt)s medlemmer" @@ -871,6 +898,7 @@ msgstr "" "medlemmer :" #: Mailman/Cgi/admin.py:1221 +#, fuzzy msgid "from %(start)s to %(end)s" msgstr "fra %(start)s til %(end)s" @@ -1013,6 +1041,7 @@ msgid "Change list ownership passwords" msgstr "Ændre admin/moderator adgangskode" #: Mailman/Cgi/admin.py:1364 +#, fuzzy msgid "" "The list administrators are the people who have ultimate control " "over\n" @@ -1132,6 +1161,7 @@ msgstr "Forkert e-mailadresse (indeholder ugyldige tegn)" #: Mailman/Cgi/admin.py:1529 Mailman/Cgi/admin.py:1703 bin/add_members:175 #: bin/clone_member:136 bin/sync_members:268 +#, fuzzy msgid "Banned address (matched %(pattern)s)" msgstr "Udelukket adresse (matchede %(pattern)s)" @@ -1235,6 +1265,7 @@ msgid "Not subscribed" msgstr "Ikke Tilmeldt" #: Mailman/Cgi/admin.py:1773 +#, fuzzy msgid "Ignoring changes to deleted member: %(user)s" msgstr "" "&Aelig;ndring af medlem, som er afmeldt er ikke udført: %(user)s" @@ -1248,10 +1279,12 @@ msgid "Error Unsubscribing:" msgstr "Fejl under framelding af:" #: Mailman/Cgi/admindb.py:235 Mailman/Cgi/admindb.py:248 +#, fuzzy msgid "%(realname)s Administrative Database" msgstr "Administrativ database for listen %(realname)s" #: Mailman/Cgi/admindb.py:238 +#, fuzzy msgid "%(realname)s Administrative Database Results" msgstr "Resultat fra den administrative database for listen %(realname)s" @@ -1280,6 +1313,7 @@ msgid "Discard all messages marked Defer" msgstr "Slet alle meddelelser markeret Afvent" #: Mailman/Cgi/admindb.py:298 +#, fuzzy msgid "all of %(esender)s's held messages." msgstr "alle meddelelser fra %(esender)s, der holdes tilbage for godkendelse." @@ -1300,6 +1334,7 @@ msgid "list of available mailing lists." msgstr "Liste over alle tilgængelige maillister." #: Mailman/Cgi/admindb.py:362 +#, fuzzy msgid "You must specify a list name. Here is the %(link)s" msgstr "Du skal indtaste et navn på en liste. Her er %(link)s" @@ -1381,6 +1416,7 @@ msgid "The sender is now a member of this list" msgstr "Afsender er nu medlem af denne liste" #: Mailman/Cgi/admindb.py:568 +#, fuzzy msgid "Add %(esender)s to one of these sender filters:" msgstr "Tilføj %(esender)s til et afsenderfilter som:" @@ -1401,6 +1437,7 @@ msgid "Rejects" msgstr "Afviser" #: Mailman/Cgi/admindb.py:584 +#, fuzzy msgid "" "Ban %(esender)s from ever subscribing to this\n" " mailing list" @@ -1413,6 +1450,7 @@ msgid "" msgstr "Klik på meddelelsens nummer for at se den, eller " #: Mailman/Cgi/admindb.py:591 +#, fuzzy msgid "view all messages from %(esender)s" msgstr "se alle meddelelser fra %(esender)s" @@ -1491,6 +1529,7 @@ msgid " is already a member" msgstr " er allerede medlem" #: Mailman/Cgi/admindb.py:973 +#, fuzzy msgid "%(addr)s is banned (matched: %(patt)s)" msgstr "%(addr)s er udelukket (matchede: %(patt)s)" @@ -1540,6 +1579,7 @@ msgstr "" "listen. Anmodningen blev derfor ignoreret." #: Mailman/Cgi/confirm.py:178 +#, fuzzy msgid "System error, bad content: %(content)s" msgstr "Systemfejl, ugyldigt indhold: %(content)s" @@ -1577,6 +1617,7 @@ msgid "Confirm subscription request" msgstr "Bekræft anmodning om medlemskab" #: Mailman/Cgi/confirm.py:259 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " subscription request to the mailing list %(listname)s. Your\n" @@ -1611,6 +1652,7 @@ msgstr "" "din anmodning tilbage." #: Mailman/Cgi/confirm.py:275 +#, fuzzy msgid "" "Your confirmation is required in order to continue with\n" " the subscription request to the mailing list %(listname)s.\n" @@ -1664,6 +1706,7 @@ msgid "Preferred language:" msgstr "Dit foretrukne sprog:" #: Mailman/Cgi/confirm.py:317 +#, fuzzy msgid "Subscribe to list %(listname)s" msgstr "Tilmeld mig listen %(listname)s" @@ -1682,6 +1725,7 @@ msgid "Awaiting moderator approval" msgstr "Venter på moderators godkendelse" #: Mailman/Cgi/confirm.py:386 +#, fuzzy msgid "" " You have successfully confirmed your subscription request to " "the\n" @@ -1716,6 +1760,7 @@ msgid "You are already a member of this mailing list!" msgstr "Du er allerede medlem af denne e-mail-liste!" #: Mailman/Cgi/confirm.py:400 +#, fuzzy msgid "" "You are currently banned from subscribing to\n" " this list. If you think this restriction is erroneous, please\n" @@ -1739,6 +1784,7 @@ msgid "Subscription request confirmed" msgstr "Anmodning om medlemskab bekræftet" #: Mailman/Cgi/confirm.py:418 +#, fuzzy msgid "" " You have successfully confirmed your subscription request for\n" " \"%(addr)s\" to the %(listname)s mailing list. A separate\n" @@ -1768,6 +1814,7 @@ msgid "Unsubscription request confirmed" msgstr "Anmodning om framelding bekræftet" #: Mailman/Cgi/confirm.py:469 +#, fuzzy msgid "" " You have successfully unsubscribed from the %(listname)s " "mailing\n" @@ -1788,6 +1835,7 @@ msgid "Not available" msgstr "Ikke tilgængelig" #: Mailman/Cgi/confirm.py:498 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " unsubscription request from the mailing list %(listname)s. " @@ -1836,6 +1884,7 @@ msgstr "" "udført." #: Mailman/Cgi/confirm.py:555 +#, fuzzy msgid "" "%(newaddr)s is banned from subscribing to the\n" " %(realname)s list. If you think this restriction is erroneous,\n" @@ -1846,6 +1895,7 @@ msgstr "" "%(owneraddr)s." #: Mailman/Cgi/confirm.py:560 +#, fuzzy msgid "" "%(newaddr)s is already a member of\n" " the %(realname)s list. It is possible that you are attempting\n" @@ -1863,6 +1913,7 @@ msgid "Change of address request confirmed" msgstr "ændring af adresse bekræftet" #: Mailman/Cgi/confirm.py:571 +#, fuzzy msgid "" " You have successfully changed your address on the %(listname)s\n" " mailing list from %(oldaddr)s to %(newaddr)s. " @@ -1884,6 +1935,7 @@ msgid "globally" msgstr "globalt" #: Mailman/Cgi/confirm.py:605 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " change of address request for the mailing list %(listname)s. " @@ -1945,6 +1997,7 @@ msgid "Sender discarded message via web." msgstr "Afsenderen fortrød via websiden at sende mailen." #: Mailman/Cgi/confirm.py:674 +#, fuzzy msgid "" "The held message with the Subject:\n" " header %(subject)s could not be found. The most " @@ -1963,6 +2016,7 @@ msgid "Posted message canceled" msgstr "Meddelelsen blev trukket tilbage" #: Mailman/Cgi/confirm.py:685 +#, fuzzy msgid "" " You have successfully canceled the posting of your message with\n" " the Subject: header %(subject)s to the mailing list\n" @@ -1984,6 +2038,7 @@ msgstr "" "listeadministrator." #: Mailman/Cgi/confirm.py:735 +#, fuzzy msgid "" "Your confirmation is required in order to cancel the\n" " posting of your message to the mailing list %(listname)s:\n" @@ -2038,6 +2093,7 @@ msgid "Membership re-enabled." msgstr "Du kan nu modtage e-mail fra listen igen." #: Mailman/Cgi/confirm.py:798 +#, fuzzy msgid "" " You have successfully re-enabled your membership in the\n" " %(listname)s mailing list. You can now not available" msgstr "ikke tilgængelig" #: Mailman/Cgi/confirm.py:846 +#, fuzzy msgid "" "Your membership in the %(realname)s mailing list is\n" " currently disabled due to excessive bounces. Your confirmation is\n" @@ -2135,10 +2193,12 @@ msgid "administrative list overview" msgstr "administrativ side for maillisten" #: Mailman/Cgi/create.py:115 +#, fuzzy msgid "List name must not include \"@\": %(safelistname)s" msgstr "Listenavnet må ikke indeholde \"@\": %(safelistname)s" #: Mailman/Cgi/create.py:122 +#, fuzzy msgid "List already exists: %(safelistname)s" msgstr "Listen findes allerede: %(safelistname)s !" @@ -2172,18 +2232,22 @@ msgid "You are not authorized to create new mailing lists" msgstr "Du har ikke adgang til at oprette nye maillister" #: Mailman/Cgi/create.py:182 +#, fuzzy msgid "Unknown virtual host: %(safehostname)s" msgstr "Ukendt virtuel host: %(safehostname)s" #: Mailman/Cgi/create.py:218 bin/newlist:219 +#, fuzzy msgid "Bad owner email address: %(s)s" msgstr "Ugyldig e-mailadresse: %(s)s" #: Mailman/Cgi/create.py:223 bin/newlist:182 bin/newlist:223 +#, fuzzy msgid "List already exists: %(listname)s" msgstr "Listen findes allerede: %(listname)s !" #: Mailman/Cgi/create.py:231 bin/newlist:217 +#, fuzzy msgid "Illegal list name: %(s)s" msgstr "Ulovligt listenavn: %(s)s" @@ -2196,6 +2260,7 @@ msgstr "" "Kontakt administrator for hjælp." #: Mailman/Cgi/create.py:273 bin/newlist:265 +#, fuzzy msgid "Your new mailing list: %(listname)s" msgstr "Din nye mailliste: %(listname)s" @@ -2204,6 +2269,7 @@ msgid "Mailing list creation results" msgstr "Resultat af oprettelse" #: Mailman/Cgi/create.py:288 +#, fuzzy msgid "" "You have successfully created the mailing list\n" " %(listname)s and notification has been sent to the list owner\n" @@ -2227,6 +2293,7 @@ msgid "Create another list" msgstr "Opret en ny liste" #: Mailman/Cgi/create.py:312 +#, fuzzy msgid "Create a %(hostname)s Mailing List" msgstr "Opret en mailliste på %(hostname)s" @@ -2325,6 +2392,7 @@ msgstr "" "godkendelse af listemoderator." #: Mailman/Cgi/create.py:432 +#, fuzzy msgid "" "Initial list of supported languages.

                Note that if you do not\n" " select at least one initial language, the list will use the server\n" @@ -2429,6 +2497,7 @@ msgid "List name is required." msgstr "Listens navn skal angives" #: Mailman/Cgi/edithtml.py:154 +#, fuzzy msgid "%(realname)s -- Edit html for %(template_info)s" msgstr "%(realname)s -- Rediger html for %(template_info)s" @@ -2437,10 +2506,12 @@ msgid "Edit HTML : Error" msgstr "Rediger HTML : Fejl" #: Mailman/Cgi/edithtml.py:161 +#, fuzzy msgid "%(safetemplatename)s: Invalid template" msgstr "%(safetemplatename)s: Ugyldig template" #: Mailman/Cgi/edithtml.py:166 Mailman/Cgi/edithtml.py:167 +#, fuzzy msgid "%(realname)s -- HTML Page Editing" msgstr "%(realname)s -- Rediger HTML-kode for websider" @@ -2505,10 +2576,12 @@ msgid "HTML successfully updated." msgstr "HTML-koden er opdateret." #: Mailman/Cgi/listinfo.py:89 +#, fuzzy msgid "%(hostname)s Mailing Lists" msgstr "e-maillister på %(hostname)s" #: Mailman/Cgi/listinfo.py:127 +#, fuzzy msgid "" "

                There currently are no publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s." @@ -2517,6 +2590,7 @@ msgstr "" "tilgængelige på %(hostname)s." #: Mailman/Cgi/listinfo.py:131 +#, fuzzy msgid "" "

                Below is a listing of all the public mailing lists on\n" " %(hostname)s. Click on a list name to get more information " @@ -2536,6 +2610,7 @@ msgid "right" msgstr "korrekt" #: Mailman/Cgi/listinfo.py:140 +#, fuzzy msgid "" " To visit the general information page for an unadvertised list,\n" " open a URL similar to this one, but with a '/' and the %(adj)s\n" @@ -2597,6 +2672,7 @@ msgstr "Forkert/Ugyldig e-mailadresse" #: Mailman/Cgi/options.py:183 Mailman/Cgi/options.py:240 #: Mailman/Cgi/options.py:264 +#, fuzzy msgid "No such member: %(safeuser)s." msgstr "Medlemmet findes ikke: %(safeuser)s." @@ -2651,6 +2727,7 @@ msgid "Note: " msgstr "Bemærk: " #: Mailman/Cgi/options.py:378 +#, fuzzy msgid "List subscriptions for %(safeuser)s on %(hostname)s" msgstr "Listemedlemskab for %(safeuser)s på %(hostname)s" @@ -2681,6 +2758,7 @@ msgid "You are already using that email address" msgstr "Den e-mailadresse er du allerede tilmeldt listen med" #: Mailman/Cgi/options.py:459 +#, fuzzy msgid "" "The new address you requested %(newaddr)s is already a member of the\n" "%(listname)s mailing list, however you have also requested a global change " @@ -2694,6 +2772,7 @@ msgstr "" "alle andre e-maillister som indeholder %(safeuser)s blive ændret. " #: Mailman/Cgi/options.py:468 +#, fuzzy msgid "The new address is already a member: %(newaddr)s" msgstr "Den nye adressen er allerede tilmeldt: %(newaddr)s" @@ -2702,6 +2781,7 @@ msgid "Addresses may not be blank" msgstr "Adressefelterne mæ ikke være tomme" #: Mailman/Cgi/options.py:488 +#, fuzzy msgid "A confirmation message has been sent to %(newaddr)s. " msgstr "En bekræftelse er sendt i en e-mail til %(newaddr)s. " @@ -2714,10 +2794,12 @@ msgid "Illegal email address provided" msgstr "Ulovlig e-mailadresse angivet" #: Mailman/Cgi/options.py:501 +#, fuzzy msgid "%(newaddr)s is already a member of the list." msgstr "%(newaddr)s er allerede medlem af listen." #: Mailman/Cgi/options.py:504 +#, fuzzy msgid "" "%(newaddr)s is banned from this list. If you\n" " think this restriction is erroneous, please contact\n" @@ -2792,6 +2874,7 @@ msgstr "" "relse." #: Mailman/Cgi/options.py:623 +#, fuzzy msgid "" "You have been successfully unsubscribed from the\n" " mailing list %(fqdn_listname)s. If you were receiving digest\n" @@ -2886,6 +2969,7 @@ msgid "day" msgstr "dag" #: Mailman/Cgi/options.py:900 +#, fuzzy msgid "%(days)d %(units)s" msgstr "%(days)d %(units)s" @@ -2898,6 +2982,7 @@ msgid "No topics defined" msgstr "Ingen emner er defineret" #: Mailman/Cgi/options.py:940 +#, fuzzy msgid "" "\n" "You are subscribed to this list with the case-preserved address\n" @@ -2907,6 +2992,7 @@ msgstr "" "Du er medlem af denne liste med e-mailadressen %(cpuser)s." #: Mailman/Cgi/options.py:956 +#, fuzzy msgid "%(realname)s list: member options login page" msgstr "%(realname)s: login til personlige indstillinger" @@ -2915,10 +3001,12 @@ msgid "email address and " msgstr "e-mailadresse og " #: Mailman/Cgi/options.py:960 +#, fuzzy msgid "%(realname)s list: member options for user %(safeuser)s" msgstr "%(realname)s: personlige indstillinger for %(safeuser)s" #: Mailman/Cgi/options.py:986 +#, fuzzy msgid "" "In order to change your membership option, you must\n" " first log in by giving your %(extra)smembership password in the section\n" @@ -2997,6 +3085,7 @@ msgid "" msgstr "" #: Mailman/Cgi/options.py:1140 +#, fuzzy msgid "Requested topic is not valid: %(topicname)s" msgstr "Emnet er ikke gyldigt: %(topicname)s" @@ -3025,6 +3114,7 @@ msgid "Private archive - \"./\" and \"../\" not allowed in URL." msgstr "Privat arkiv - \"./\" og \"../\" er ikke tilladt i URL'en." #: Mailman/Cgi/private.py:109 +#, fuzzy msgid "Private Archive Error - %(msg)s" msgstr "Fejl i privat arkiv - %(msg)s" @@ -3063,12 +3153,14 @@ msgid "Mailing list deletion results" msgstr "Resultat af sletning af mailliste" #: Mailman/Cgi/rmlist.py:187 +#, fuzzy msgid "" "You have successfully deleted the mailing list\n" " %(listname)s." msgstr "Du har slettet maillisten %(listname)s." #: Mailman/Cgi/rmlist.py:191 +#, fuzzy msgid "" "There were some problems deleting the mailing list\n" " %(listname)s. Contact your site administrator at " @@ -3079,6 +3171,7 @@ msgstr "" "Kontakt systemadministrator på %(sitelist)s for flere detaljer." #: Mailman/Cgi/rmlist.py:208 +#, fuzzy msgid "Permanently remove mailing list %(realname)s" msgstr "Fjern maillisten %(realname)s permanent" @@ -3149,6 +3242,7 @@ msgid "Invalid options to CGI script" msgstr "Ugyldige parametre til CGI skriptet" #: Mailman/Cgi/roster.py:118 +#, fuzzy msgid "%(realname)s roster authentication failed." msgstr "" "Adgang til medlemslisten for %(realname)s mislykkedes pga. manglende " @@ -3222,6 +3316,7 @@ msgstr "" "en e-mail med yderligere instruktioner." #: Mailman/Cgi/subscribe.py:262 +#, fuzzy msgid "" "The email address you supplied is banned from this\n" " mailing list. If you think this restriction is erroneous, please\n" @@ -3247,6 +3342,7 @@ msgstr "" "Din tilmelding tillades ikke fordi du har opgivet en usikker e-mailadresse." #: Mailman/Cgi/subscribe.py:278 +#, fuzzy msgid "" "Confirmation from your email address is required, to prevent anyone from\n" "subscribing you without permission. Instructions are being sent to you at\n" @@ -3261,6 +3357,7 @@ msgstr "" "listen." #: Mailman/Cgi/subscribe.py:290 +#, fuzzy msgid "" "Your subscription request was deferred because %(x)s. Your request has " "been\n" @@ -3282,6 +3379,7 @@ msgid "Mailman privacy alert" msgstr "Sikkerhedsmeddelelse fra Mailman" #: Mailman/Cgi/subscribe.py:316 +#, fuzzy msgid "" "An attempt was made to subscribe your address to the mailing list\n" "%(listaddr)s. You are already subscribed to this mailing list.\n" @@ -3330,6 +3428,7 @@ msgid "This list only supports digest delivery." msgstr "Denne liste understøtter kun sammendrag-modus." #: Mailman/Cgi/subscribe.py:344 +#, fuzzy msgid "You have been successfully subscribed to the %(realname)s mailing list." msgstr "Du er nu tilmeldt maillisten %(realname)s." @@ -3380,6 +3479,7 @@ msgstr "" "ændret din e-mailadresse?" #: Mailman/Commands/cmd_confirm.py:69 +#, fuzzy msgid "" "You are currently banned from subscribing to this list. If you think this\n" "restriction is erroneous, please contact the list owners at\n" @@ -3465,26 +3565,32 @@ msgid "n/a" msgstr "n/a" #: Mailman/Commands/cmd_info.py:44 +#, fuzzy msgid "List name: %(listname)s" msgstr "Listenavn: %(listname)s" #: Mailman/Commands/cmd_info.py:45 +#, fuzzy msgid "Description: %(description)s" msgstr "Beskrivelse: %(description)s" #: Mailman/Commands/cmd_info.py:46 +#, fuzzy msgid "Postings to: %(postaddr)s" msgstr "Adresse: %(postaddr)s" #: Mailman/Commands/cmd_info.py:47 +#, fuzzy msgid "List Helpbot: %(requestaddr)s" msgstr "Kommandoadresse: %(requestaddr)s" #: Mailman/Commands/cmd_info.py:48 +#, fuzzy msgid "List Owners: %(owneraddr)s" msgstr "Listens ejer(e): %(owneraddr)s" #: Mailman/Commands/cmd_info.py:49 +#, fuzzy msgid "More information: %(listurl)s" msgstr "Mere information: %(listurl)s" @@ -3509,18 +3615,22 @@ msgstr "" " GNU Mailman tjeneste.\n" #: Mailman/Commands/cmd_lists.py:44 +#, fuzzy msgid "Public mailing lists at %(hostname)s:" msgstr "E-maillister offentligt tilgængelige på %(hostname)s:" #: Mailman/Commands/cmd_lists.py:66 +#, fuzzy msgid "%(i)3d. List name: %(realname)s" msgstr "%(i)3d. Listenavn: %(realname)s" #: Mailman/Commands/cmd_lists.py:67 +#, fuzzy msgid " Description: %(description)s" msgstr " Beskrivelse: %(description)s" #: Mailman/Commands/cmd_lists.py:68 +#, fuzzy msgid " Requests to: %(requestaddr)s" msgstr " Anmodninger til: %(requestaddr)s" @@ -3557,12 +3667,14 @@ msgstr "" " e-mailadresse.\n" #: Mailman/Commands/cmd_password.py:51 Mailman/Commands/cmd_password.py:66 +#, fuzzy msgid "Your password is: %(password)s" msgstr "Din adgangskode er: %(password)s" #: Mailman/Commands/cmd_password.py:57 Mailman/Commands/cmd_password.py:72 #: Mailman/Commands/cmd_password.py:95 Mailman/Commands/cmd_password.py:121 #: Mailman/Commands/cmd_set.py:149 Mailman/Commands/cmd_set.py:219 +#, fuzzy msgid "You are not a member of the %(listname)s mailing list" msgstr "Du er ikke medlem af maillisten %(listname)s" @@ -3748,6 +3860,7 @@ msgstr "" " adgangskoden en gang om måneden.\n" #: Mailman/Commands/cmd_set.py:122 +#, fuzzy msgid "Bad set command: %(subcmd)s" msgstr "Ugyldig indstilling: %(subcmd)s" @@ -3768,6 +3881,7 @@ msgid "on" msgstr "til" #: Mailman/Commands/cmd_set.py:154 +#, fuzzy msgid " ack %(onoff)s" msgstr " bekræft: %(onoff)s" @@ -3805,22 +3919,27 @@ msgid "due to bounces" msgstr "på grund af returmails" #: Mailman/Commands/cmd_set.py:186 +#, fuzzy msgid " %(status)s (%(how)s on %(date)s)" msgstr " %(status)s (%(how)s %(date)s)" #: Mailman/Commands/cmd_set.py:192 +#, fuzzy msgid " myposts %(onoff)s" msgstr " ikke-mine: %(onoff)s" #: Mailman/Commands/cmd_set.py:195 +#, fuzzy msgid " hide %(onoff)s" msgstr " skjult: %(onoff)s" #: Mailman/Commands/cmd_set.py:199 +#, fuzzy msgid " duplicates %(onoff)s" msgstr " undgå dubletter: %(onoff)s" #: Mailman/Commands/cmd_set.py:203 +#, fuzzy msgid " reminders %(onoff)s" msgstr " påmindelser: %(onoff)s" @@ -3829,6 +3948,7 @@ msgid "You did not give the correct password" msgstr "Du har angivet en forkert adgangskode" #: Mailman/Commands/cmd_set.py:236 Mailman/Commands/cmd_set.py:283 +#, fuzzy msgid "Bad argument: %(arg)s" msgstr "Ugyldige parametre: %(arg)s" @@ -3905,6 +4025,7 @@ msgstr "" " '<' og '>', og uden apostroffer!)\n" #: Mailman/Commands/cmd_subscribe.py:62 +#, fuzzy msgid "Bad digest specifier: %(arg)s" msgstr "Ugyldig sammendrag-modus parameter: %(arg)s" @@ -3913,6 +4034,7 @@ msgid "No valid address found to subscribe" msgstr "Ingen gyldig e-mailadresse for tilmelding blev fundet" #: Mailman/Commands/cmd_subscribe.py:113 +#, fuzzy msgid "" "The email address you supplied is banned from this mailing list.\n" "If you think this restriction is erroneous, please contact the list\n" @@ -3954,6 +4076,7 @@ msgid "This list only supports digest subscriptions!" msgstr "Denne liste understøtter kun sammendrag-modus!" #: Mailman/Commands/cmd_subscribe.py:146 +#, fuzzy msgid "" "Your subscription request has been forwarded to the list administrator\n" "at %(listowner)s for review." @@ -3991,6 +4114,7 @@ msgstr "" " (uden '<' og '>', og uden apostroffer!)\n" #: Mailman/Commands/cmd_unsubscribe.py:62 +#, fuzzy msgid "%(address)s is not a member of the %(listname)s mailing list" msgstr "%(address)s er ikke medlem af maillisten %(listname)s." @@ -4244,6 +4368,7 @@ msgid "Chinese (Taiwan)" msgstr "Kinesisk (Taiwan)" #: Mailman/Deliverer.py:53 +#, fuzzy msgid "" "Note: Since this is a list of mailing lists, administrative\n" "notices like the password reminder will be sent to\n" @@ -4259,14 +4384,17 @@ msgid " (Digest mode)" msgstr " (Sammendrag-modus)" #: Mailman/Deliverer.py:79 +#, fuzzy msgid "Welcome to the \"%(realname)s\" mailing list%(digmode)s" msgstr "Velkommen til maillisten \"%(realname)s\"%(digmode)s" #: Mailman/Deliverer.py:89 +#, fuzzy msgid "You have been unsubscribed from the %(realname)s mailing list" msgstr "Du er nu fjernet fra maillisten \"%(realname)s\"" #: Mailman/Deliverer.py:116 +#, fuzzy msgid "%(listfullname)s mailing list reminder" msgstr "Påmindelse fra maillisten %(listfullname)s" @@ -4279,6 +4407,7 @@ msgid "Hostile subscription attempt detected" msgstr "Fjendtlig tilmelding forsøgt" #: Mailman/Deliverer.py:169 +#, fuzzy msgid "" "%(address)s was invited to a different mailing\n" "list, but in a deliberate malicious attempt they tried to confirm the\n" @@ -4291,6 +4420,7 @@ msgstr "" "vide dette. Du skal ikke foretage dig yderligere." #: Mailman/Deliverer.py:188 +#, fuzzy msgid "" "You invited %(address)s to your list, but in a\n" "deliberate malicious attempt, they tried to confirm the invitation to a\n" @@ -4304,6 +4434,7 @@ msgstr "" "vide dette. Du skal ikke foretage dig yderligere." #: Mailman/Deliverer.py:221 +#, fuzzy msgid "%(listname)s mailing list probe message" msgstr "Påmindelse fra maillisten %(listname)s" @@ -4510,8 +4641,8 @@ msgid "" " membership.\n" "\n" "

                You can control both the\n" -" number\n" +" number\n" " of reminders the member will receive and the\n" " Du kan bestemme hvor mange advarsler\n" +"

                Du kan bestemme hvor mange advarsler\n" "medlemmet skal have og hvor ofte\n" "han/hun skal modtage sådanne advarsler.\n" @@ -4718,8 +4849,8 @@ msgid "" " No those messages too will get discarded. You may " "want\n" " to set up an\n" -" autoresponse\n" +" autoresponse\n" " message for email to the -owner and -admin address." msgstr "" "Mailmans automatiske returmailhåndtering er meget robust, men det er " @@ -4808,12 +4939,13 @@ msgstr "" "besked til medlemmet." #: Mailman/Gui/Bounce.py:194 +#, fuzzy msgid "" "Bad value for %(property)s: %(val)s" msgstr "" -"Ugyldig vædi for " -"%(property)s: %(val)s" +"Ugyldig vædi for %(property)s: %(val)s" #: Mailman/Gui/ContentFilter.py:30 msgid "Content filtering" @@ -4872,8 +5004,8 @@ msgstr "" "en e-mail og du\n" "har beskyttelse med filtrering af indhold aktiveret, sammenlignes fø" "rst eventuelle\n" -"vedhæftninger med MIME filtrene.\n" +"vedhæftninger med MIME filtrene.\n" "Hvis en vedhæftning passer med et af disse filtre, bliver vedhæ" "ftningen fjernet.\n" "\n" @@ -4957,8 +5089,8 @@ msgid "" "Use this option to remove each message attachment that does\n" " not have a matching content type. Requirements and formats " "are\n" -" exactly like filter_mime_types.\n" "\n" "

                Note: if you add entries to this list but don't add\n" @@ -5077,6 +5209,7 @@ msgstr "" "serveradministrator har tilladt det." #: Mailman/Gui/ContentFilter.py:171 +#, fuzzy msgid "Bad MIME type ignored: %(spectype)s" msgstr "Ignorerer ugyldig MIME type: %(spectype)s" @@ -5183,6 +5316,7 @@ msgid "" msgstr "Skal Mailman udsende næste samle-email nu hvis den ikke er tom?" #: Mailman/Gui/Digest.py:145 +#, fuzzy msgid "" "The next digest will be sent as volume\n" " %(volume)s, number %(number)s" @@ -5199,14 +5333,17 @@ msgid "There was no digest to send." msgstr "Det var ingen samle-email der skulle sendes." #: Mailman/Gui/GUIBase.py:173 +#, fuzzy msgid "Invalid value for variable: %(property)s" msgstr "Ugyldig værdi for: %(property)s" #: Mailman/Gui/GUIBase.py:178 +#, fuzzy msgid "Bad email address for option %(property)s: %(error)s" msgstr "Ugyldig e-mailadresse for indstillingen %(property)s: %(error)s" #: Mailman/Gui/GUIBase.py:204 +#, fuzzy msgid "" "The following illegal substitution variables were\n" " found in the %(property)s string:\n" @@ -5220,6 +5357,7 @@ msgstr "" "

                Din liste vil muligvis ikke fungere ordentligt før du retter dette." #: Mailman/Gui/GUIBase.py:218 +#, fuzzy msgid "" "Your %(property)s string appeared to\n" " have some correctable problems in its new value.\n" @@ -5324,8 +5462,8 @@ msgid "" "

                In order to split the list ownership duties into\n" " administrators and moderators, you must\n" " set a separate moderator password,\n" -" and also provide the email\n" +" and also provide the email\n" " addresses of the list moderators. Note that the field you\n" " are changing here specifies the list administrators." msgstr "" @@ -5658,13 +5796,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5691,8 +5829,8 @@ msgstr "" "Egendefineret adresse, vil Mailman tilføje, evt. erstatte,\n" "et Reply-To: felt. (Egendefineret adresse indsætter " "værdien\n" -"af indstillingen reply_to_address).\n" +"af indstillingen reply_to_address).\n" "\n" "

                Der er flere grunde til ikke at indføre eller erstatte Reply-" "To:\n" @@ -5730,8 +5868,8 @@ msgstr "Egendefineret Reply-To: adresse." msgid "" "This is the address set in the Reply-To: header\n" " when the reply_goes_to_list\n" +" href=\"?VARHELP=general/" +"reply_goes_to_list\">reply_goes_to_list\n" " option is set to Explicit address.\n" "\n" "

                There are many reasons not to introduce or override the\n" @@ -5739,13 +5877,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5767,8 +5905,8 @@ msgid "" " Reply-To: header, it will not be changed." msgstr "" "Her definerer du adressen der skal sættes i Reply-To: feltet\n" -"når indstillingen reply_goes_to_list\n" +"når indstillingen reply_goes_to_list\n" "er sat til Egendefineret adresse.\n" "\n" "

                Der findes mange grunde til at ikke indføre eller erstatte " @@ -5852,8 +5990,8 @@ msgid "" " member list addresses, but rather to the owner of those member\n" " lists. In that case, the value of this setting is appended to\n" " the member's account name for such notices. `-owner' is the\n" -" typical choice. This setting has no effect when \"umbrella_list" -"\"\n" +" typical choice. This setting has no effect when " +"\"umbrella_list\"\n" " is \"No\"." msgstr "" "Når \"umbrella_list\" indikerer at denne liste har andre maillister " @@ -6385,8 +6523,8 @@ msgid "" " language must be included." msgstr "" "Her er alle sprog, som denne liste har understøttelse for.\n" -"Bemærk at standardsproget\n" +"Bemærk at standardsproget\n" "skal være med." #: Mailman/Gui/Language.py:90 @@ -6906,6 +7044,7 @@ msgstr "" "imod deres vilje." #: Mailman/Gui/Privacy.py:104 +#, fuzzy msgid "" "This section allows you to configure subscription and\n" " membership exposure policy. You can also control whether this\n" @@ -7104,8 +7243,8 @@ msgid "" " either individually or as a group. Any\n" " posting from a non-member who is not explicitly accepted,\n" " rejected, or discarded, will have their posting filtered by the\n" -" general\n" +" general\n" " non-member rules.\n" "\n" "

                In the text boxes below, add one address per line; start the\n" @@ -7140,8 +7279,8 @@ msgstr "" "enten enkeltvis eller som en gruppe. Al e-mail fra ikke-medlemmer,\n" "som ikke specifikt bliver godkendt, sendt retur eller slettet, vil blive " "behandlet\n" -"alt efter hvad generelle regler for ikke-medlemmer viser.\n" +"alt efter hvad generelle regler for ikke-medlemmer viser.\n" "\n" "

                I tekstboksene nedenfor kan du tilføje en e-mailadresse pr. " "linie.\n" @@ -7166,6 +7305,7 @@ msgstr "" "moderator?" #: Mailman/Gui/Privacy.py:218 +#, fuzzy msgid "" "Each list member has a moderation flag which says\n" " whether messages from the list member can be posted directly " @@ -7224,8 +7364,8 @@ msgid "" "If a member posts this many times, within a period of time\n" " the member is automatically moderated. Use 0 to disable. " "See\n" -" member_verbosity_interval for details on the time " "period.\n" "\n" @@ -7648,14 +7788,14 @@ msgstr "" "Når en e-mail fra et ikke-medlem bliver modtaget, sammenlignes e-" "mailens afsender med\n" " listen over e-mailadresser der skal\n" -" godkendes,\n" -" holdes tilbage,\n" +" godkendes,\n" +" holdes tilbage,\n" " afvises (sendes retur), eller\n" -" slettes.\n" +" slettes.\n" " Hvis afsenderadressen ikke stemmer overens med en adresse der " "findes i listerne,\n" " bliver følgende afgørelse truffet." @@ -7669,6 +7809,7 @@ msgstr "" " videresendes til listemoderator?" #: Mailman/Gui/Privacy.py:494 +#, fuzzy msgid "" "Text to include in any rejection notice to be sent to\n" " non-members who post to this list. This notice can include\n" @@ -7930,6 +8071,7 @@ msgstr "" " Ikke komplette filtre vil ikke være aktive." #: Mailman/Gui/Privacy.py:693 +#, fuzzy msgid "" "The header filter rule pattern\n" " '%(safepattern)s' is not a legal regular expression. This\n" @@ -7982,8 +8124,8 @@ msgid "" "

                The body of the message can also be optionally scanned for\n" " Subject: and Keywords: headers, as\n" " specified by the topics_bodylines_limit\n" +" href=\"?VARHELP=topics/" +"topics_bodylines_limit\">topics_bodylines_limit\n" " configuration variable." msgstr "" "Emnefilteret kategoriserer hver e-mail som kommer til listen,\n" @@ -8073,6 +8215,7 @@ msgstr "" "emner vil ikke blive taget i brug." #: Mailman/Gui/Topics.py:135 +#, fuzzy msgid "" "The topic pattern '%(safepattern)s' is not a\n" " legal regular expression. It will be discarded." @@ -8234,8 +8377,8 @@ msgid "" " normal Subject: prefixes, they won't be prefixed for\n" " gated messages either." msgstr "" -"Mailman tilføjer normalt en \n" +"Mailman tilføjer normalt en \n" "tekst du selv kan tilrette (emne prefix) foran emnefeltet i mail som\n" "sendes til listen, og normalt sker dette også for mail som sendes\n" "videre til Usenet. Du kan sætte denne indstilling til Nej " @@ -8294,6 +8437,7 @@ msgid "%(listinfo_link)s list run by %(owner_link)s" msgstr "E-mailisten %(listinfo_link)s administreres af %(owner_link)s" #: Mailman/HTMLFormatter.py:57 +#, fuzzy msgid "%(realname)s administrative interface" msgstr "Administrativ side for %(realname)s" @@ -8302,6 +8446,7 @@ msgid " (requires authorization)" msgstr " (kræver login)" #: Mailman/HTMLFormatter.py:61 +#, fuzzy msgid "Overview of all %(hostname)s mailing lists" msgstr "Liste over alle maillister på %(hostname)s" @@ -8322,6 +8467,7 @@ msgid "; it was disabled by the list administrator" msgstr " af listeadministrator" #: Mailman/HTMLFormatter.py:146 +#, fuzzy msgid "" "; it was disabled due to excessive bounces. The\n" " last bounce was received on %(date)s" @@ -8334,6 +8480,7 @@ msgid "; it was disabled for unknown reasons" msgstr "; af ukendt grund" #: Mailman/HTMLFormatter.py:151 +#, fuzzy msgid "Note: your list delivery is currently disabled%(reason)s." msgstr "Bemærk: Levering af e-mail fra listen er stoppet%(reason)s." @@ -8346,6 +8493,7 @@ msgid "the list administrator" msgstr "listeadministrator" #: Mailman/HTMLFormatter.py:157 +#, fuzzy msgid "" "

                %(note)s\n" "\n" @@ -8365,6 +8513,7 @@ msgstr "" "yderligere hjælp." #: Mailman/HTMLFormatter.py:169 +#, fuzzy msgid "" "

                We have received some recent bounces from your\n" " address. Your current bounce score is %(score)s out of " @@ -8384,6 +8533,7 @@ msgstr "" "snart." #: Mailman/HTMLFormatter.py:181 +#, fuzzy msgid "" "(Note - you are subscribing to a list of mailing lists, so the %(type)s " "notice will be sent to the admin address for your membership, %(addr)s.)

                " @@ -8431,6 +8581,7 @@ msgstr "" "Du vil derefter få moderators afgørelse tilsendt i en e-mail." #: Mailman/HTMLFormatter.py:208 +#, fuzzy msgid "" "This is %(also)sa private list, which means that the\n" " list of members is not available to non-members." @@ -8439,6 +8590,7 @@ msgstr "" "tilgængelig for andre end dem der er medlem af maillisten." #: Mailman/HTMLFormatter.py:211 +#, fuzzy msgid "" "This is %(also)sa hidden list, which means that the\n" " list of members is available only to the list administrator." @@ -8447,6 +8599,7 @@ msgstr "" "tilgængelig for listeadministrator." #: Mailman/HTMLFormatter.py:214 +#, fuzzy msgid "" "This is %(also)sa public list, which means that the\n" " list of members list is available to everyone." @@ -8462,6 +8615,7 @@ msgstr "" " (men vi gemmer e-mailadressen så de ikke genkendes af spammere). " #: Mailman/HTMLFormatter.py:222 +#, fuzzy msgid "" "

                (Note that this is an umbrella list, intended to\n" " have only other mailing lists as members. Among other things,\n" @@ -8478,6 +8632,7 @@ msgid "either " msgstr "enten " #: Mailman/HTMLFormatter.py:256 +#, fuzzy msgid "" "To unsubscribe from %(realname)s, get a password reminder,\n" " or change your subscription options %(either)senter your " @@ -8510,12 +8665,14 @@ msgid "" msgstr "Hvis du efterlader feltet tomt, vil du blive bedt om din e-mailadresse" #: Mailman/HTMLFormatter.py:277 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " members.)" msgstr "(%(which)s er kun tilgængelig for medlemmer af listen.)" #: Mailman/HTMLFormatter.py:281 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " administrator.)" @@ -8576,6 +8733,7 @@ msgid "The current archive" msgstr "Arkivet" #: Mailman/Handlers/Acknowledge.py:59 +#, fuzzy msgid "%(realname)s post acknowledgement" msgstr "Meddelelse om modtaget e-mail til %(realname)s" @@ -8592,6 +8750,7 @@ msgstr "" "kan feltet ikke fjernes sikkert.\n" #: Mailman/Handlers/CalcRecips.py:79 +#, fuzzy msgid "" "Your urgent message to the %(realname)s mailing list was not authorized for\n" "delivery. The original message as received by Mailman is attached.\n" @@ -8671,6 +8830,7 @@ msgid "Message may contain administrivia" msgstr "Meddelelsen kan have administrativt indhold" #: Mailman/Handlers/Hold.py:84 +#, fuzzy msgid "" "Please do *not* post administrative requests to the mailing\n" "list. If you wish to subscribe, visit %(listurl)s or send a message with " @@ -8709,12 +8869,14 @@ msgid "Posting to a moderated newsgroup" msgstr "Meddelelse sendt til modereret nyhedsgruppe" #: Mailman/Handlers/Hold.py:252 +#, fuzzy msgid "Your message to %(listname)s awaits moderator approval" msgstr "" "Meddelelsen du sendte til listen %(listname)s venter på godkendelse fra " "moderator." #: Mailman/Handlers/Hold.py:271 +#, fuzzy msgid "%(listname)s post from %(sender)s requires approval" msgstr "Meddelelse til %(listname)s fra %(sender)s kræver godkendelse" @@ -8760,6 +8922,7 @@ msgid "After content filtering, the message was empty" msgstr "Efter filtrering af indholdet var meddelelsen tom" #: Mailman/Handlers/MimeDel.py:269 +#, fuzzy msgid "" "The attached message matched the %(listname)s mailing list's content " "filtering\n" @@ -8803,6 +8966,7 @@ msgid "The attached message has been automatically discarded." msgstr "Den vedlagte meddelelse er automatisk blevet afvist." #: Mailman/Handlers/Replybot.py:75 +#, fuzzy msgid "Auto-response for your message to the \"%(realname)s\" mailing list" msgstr "Auto-svar for din meddelelse til \"%(realname)s\" mailliste " @@ -8811,6 +8975,7 @@ msgid "The Mailman Replybot" msgstr "Mailmans Automatiske Svar" #: Mailman/Handlers/Scrubber.py:208 +#, fuzzy msgid "" "An embedded and charset-unspecified text was scrubbed...\n" "Name: %(filename)s\n" @@ -8825,6 +8990,7 @@ msgid "HTML attachment scrubbed and removed" msgstr "En HTML-vedhæftning blev filtreret fra og fjernet" #: Mailman/Handlers/Scrubber.py:234 Mailman/Handlers/Scrubber.py:259 +#, fuzzy msgid "" "An HTML attachment was scrubbed...\n" "URL: %(url)s\n" @@ -8845,6 +9011,7 @@ msgid "unknown sender" msgstr "ukendt afsender" #: Mailman/Handlers/Scrubber.py:276 +#, fuzzy msgid "" "An embedded message was scrubbed...\n" "From: %(who)s\n" @@ -8861,6 +9028,7 @@ msgstr "" "Url: %(url)s\n" #: Mailman/Handlers/Scrubber.py:308 +#, fuzzy msgid "" "A non-text attachment was scrubbed...\n" "Name: %(filename)s\n" @@ -8877,6 +9045,7 @@ msgstr "" "URL: %(url)s\n" #: Mailman/Handlers/Scrubber.py:347 +#, fuzzy msgid "Skipped content of type %(partctype)s\n" msgstr "Ignorerer indhold af typen %(partctype)s\n" @@ -8909,6 +9078,7 @@ msgid "Message rejected by filter rule match" msgstr "Meddelelsen afvist, fordi den blev fanget af en filterregel" #: Mailman/Handlers/ToDigest.py:173 +#, fuzzy msgid "%(realname)s Digest, Vol %(volume)d, Issue %(issue)d" msgstr "Sammendrag af %(realname)s, Vol %(volume)d, Udgave %(issue)d" @@ -8945,6 +9115,7 @@ msgid "End of " msgstr "Slut på " #: Mailman/ListAdmin.py:309 +#, fuzzy msgid "Posting of your message titled \"%(subject)s\"" msgstr "Din meddelelse med emnet \"%(subject)s\"" @@ -8957,6 +9128,7 @@ msgid "Forward of moderated message" msgstr "Videresending af modereret meddelelse" #: Mailman/ListAdmin.py:405 +#, fuzzy msgid "New subscription request to list %(realname)s from %(addr)s" msgstr "Ny anmodning om medlemskab på listen %(realname)s fra %(addr)s" @@ -8970,6 +9142,7 @@ msgid "via admin approval" msgstr "Fortsæt med at vente på moderators godkendelse" #: Mailman/ListAdmin.py:466 +#, fuzzy msgid "New unsubscription request from %(realname)s by %(addr)s" msgstr "Ny Anmodning fra %(addr)s om framelding fra listen %(realname)s" @@ -8982,10 +9155,12 @@ msgid "Original Message" msgstr "Oprindelig meddelelse" #: Mailman/ListAdmin.py:526 +#, fuzzy msgid "Request to mailing list %(realname)s rejected" msgstr "Anmodning til maillisten %(realname)s ikke godkendt" #: Mailman/MTA/Manual.py:66 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been created via the through-the-web\n" "interface. In order to complete the activation of this mailing list, the\n" @@ -9013,14 +9188,17 @@ msgstr "" "programmet 'newaliases':\n" #: Mailman/MTA/Manual.py:82 +#, fuzzy msgid "## %(listname)s mailing list" msgstr "## %(listname)s mailliste" #: Mailman/MTA/Manual.py:99 +#, fuzzy msgid "Mailing list creation request for list %(listname)s" msgstr "Resultat af oprettelse af maillisten %(listname)s" #: Mailman/MTA/Manual.py:113 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been removed via the through-the-web\n" "interface. In order to complete the de-activation of this mailing list, " @@ -9038,6 +9216,7 @@ msgstr "" "Her er linierne som skal fjernes fra aliasfilen:\n" #: Mailman/MTA/Manual.py:123 +#, fuzzy msgid "" "\n" "To finish removing your mailing list, you must edit your /etc/aliases (or\n" @@ -9055,14 +9234,17 @@ msgstr "" "## Mailliste: %(listname)s" #: Mailman/MTA/Manual.py:142 +#, fuzzy msgid "Mailing list removal request for list %(listname)s" msgstr "Anmodning om at fjerne maillisten %(listname)s" #: Mailman/MTA/Postfix.py:442 +#, fuzzy msgid "checking permissions on %(file)s" msgstr "kontrollerer rettigheder for %(file)s" #: Mailman/MTA/Postfix.py:452 +#, fuzzy msgid "%(file)s permissions must be 0664 (got %(octmode)s)" msgstr "rettigheden til %(file)s skal være 0664 (men er %(octmode)s)" @@ -9076,34 +9258,42 @@ msgid "(fixing)" msgstr "(fixer)" #: Mailman/MTA/Postfix.py:470 +#, fuzzy msgid "checking ownership of %(dbfile)s" msgstr "undersøger ejerskab til filen %(dbfile)s" #: Mailman/MTA/Postfix.py:478 +#, fuzzy msgid "%(dbfile)s owned by %(owner)s (must be owned by %(user)s" msgstr "Filen %(dbfile)s ejes af %(owner)s (skal ejes af %(user)s)" #: Mailman/MTA/Postfix.py:491 +#, fuzzy msgid "%(dbfile)s permissions must be 0664 (got %(octmode)s)" msgstr "rettigheden til %(dbfile)s skal være 0664 (men er %(octmode)s)" #: Mailman/MailList.py:219 +#, fuzzy msgid "Your confirmation is required to join the %(listname)s mailing list" msgstr "Du skal bekræfte at du gerne vil tilmeldes %(listname)s mail listen" #: Mailman/MailList.py:230 +#, fuzzy msgid "Your confirmation is required to leave the %(listname)s mailing list" msgstr "Du skal bekræfte at du gerne vil forlade %(listname)s mail listen" #: Mailman/MailList.py:999 Mailman/MailList.py:1492 +#, fuzzy msgid " from %(remote)s" msgstr " fra %(remote)s" #: Mailman/MailList.py:1043 +#, fuzzy msgid "subscriptions to %(realname)s require moderator approval" msgstr "tilmelding til %(realname)s kræver godkendelse af moderator" #: Mailman/MailList.py:1125 bin/add_members:299 +#, fuzzy msgid "%(realname)s subscription notification" msgstr "Meddelelse om tilmelding til maillisten %(realname)s" @@ -9112,6 +9302,7 @@ msgid "unsubscriptions require moderator approval" msgstr "Framelding kræver godkendelse af moderator" #: Mailman/MailList.py:1166 +#, fuzzy msgid "%(realname)s unsubscribe notification" msgstr "Meddelelse om framelding fra maillisten %(realname)s" @@ -9131,6 +9322,7 @@ msgid "via web confirmation" msgstr "Ugyldig identifikator for bekræftelse!" #: Mailman/MailList.py:1396 +#, fuzzy msgid "subscriptions to %(name)s require administrator approval" msgstr "tilmelding til %(name)s kræver godkendelse af administrator" @@ -9149,6 +9341,7 @@ msgid "Last autoresponse notification for today" msgstr "Sidste automatiske svar i dag" #: Mailman/Queue/BounceRunner.py:360 +#, fuzzy msgid "" "The attached message was received as a bounce, but either the bounce format\n" "was not recognized, or no member addresses could be extracted from it. " @@ -9240,6 +9433,7 @@ msgstr "" "Den oprindelige meddelelse er undertrykt pga. Mailmans site konfiguration\n" #: Mailman/htmlformat.py:675 +#, fuzzy msgid "Delivered by Mailman
                version %(version)s" msgstr "Leveret af Mailman
                version %(version)s" @@ -9328,6 +9522,7 @@ msgid "Server Local Time" msgstr "Lokal tid" #: Mailman/i18n.py:180 +#, fuzzy msgid "" "%(wday)s %(mon)s %(day)2i %(hh)02i:%(mm)02i:%(ss)02i %(tzname)s %(year)04i" msgstr "" @@ -9445,6 +9640,7 @@ msgstr "" "være \"-\".\n" #: bin/add_members:162 bin/add_members:172 +#, fuzzy msgid "Already a member: %(member)s" msgstr "Allerede medlem: %(member)s" @@ -9453,10 +9649,12 @@ msgid "Bad/Invalid email address: blank line" msgstr "Forkert/Ugyldig e-mailadresse: tom linie" #: bin/add_members:180 +#, fuzzy msgid "Bad/Invalid email address: %(member)s" msgstr "Forkert/Ugyldig e-mailadresse: %(member)s" #: bin/add_members:182 +#, fuzzy msgid "Hostile address (illegal characters): %(member)s" msgstr "Ugyldige tegn i e-mailadressen: %(member)s" @@ -9466,14 +9664,17 @@ msgid "Invited: %(member)s" msgstr "Tilmeldt: %(member)s" #: bin/add_members:187 +#, fuzzy msgid "Subscribed: %(member)s" msgstr "Tilmeldt: %(member)s" #: bin/add_members:237 +#, fuzzy msgid "Bad argument to -w/--welcome-msg: %(arg)s" msgstr "Ugyldigt argument til -w/--welcome-msg: %(arg)s" #: bin/add_members:244 +#, fuzzy msgid "Bad argument to -a/--admin-notify: %(arg)s" msgstr "Ugyldigt argument til -a/--admin-notify: %(arg)s" @@ -9490,6 +9691,7 @@ msgstr "" #: bin/add_members:261 bin/config_list:110 bin/export.py:271 bin/find_member:97 #: bin/inject:91 bin/list_admins:90 bin/list_members:252 bin/sync_members:222 #: cron/bumpdigests:86 +#, fuzzy msgid "No such list: %(listname)s" msgstr "Listen findes ikke: %(listname)s" @@ -9500,6 +9702,7 @@ msgid "Nothing to do." msgstr "Intet at gøre." #: bin/arch:19 +#, fuzzy msgid "" "Rebuild a list's archive.\n" "\n" @@ -9596,6 +9799,7 @@ msgid "listname is required" msgstr "kræver listens navn" #: bin/arch:143 bin/change_pw:107 bin/config_list:257 +#, fuzzy msgid "" "No such list \"%(listname)s\"\n" "%(e)s" @@ -9604,10 +9808,12 @@ msgstr "" "%(e)s" #: bin/arch:168 +#, fuzzy msgid "Cannot open mbox file %(mbox)s: %(msg)s" msgstr "Kan ikke åbne mbox-fil %(mbox)s: %(msg)s" #: bin/b4b5-archfix:19 +#, fuzzy msgid "" "Fix the MM2.1b4 archives.\n" "\n" @@ -9748,6 +9954,7 @@ msgstr "" " Viser denne hjælpetekst.\n" #: bin/change_pw:145 +#, fuzzy msgid "Bad arguments: %(strargs)s" msgstr "Ugyldige parametre: %(strargs)s" @@ -9756,14 +9963,17 @@ msgid "Empty list passwords are not allowed" msgstr "Tomme listeadgangskoder er ikke tilladt" #: bin/change_pw:181 +#, fuzzy msgid "New %(listname)s password: %(notifypassword)s" msgstr "Ny adgangskode for %(listname)s: %(notifypassword)s" #: bin/change_pw:190 +#, fuzzy msgid "Your new %(listname)s list password" msgstr "Den nye adgangskode for maillisten %(listname)s" #: bin/change_pw:191 +#, fuzzy msgid "" "The site administrator at %(hostname)s has changed the password for your\n" "mailing list %(listname)s. It is now\n" @@ -9791,6 +10001,7 @@ msgstr "" " %(adminurl)s\n" #: bin/check_db:19 +#, fuzzy msgid "" "Check a list's config database file for integrity.\n" "\n" @@ -9866,10 +10077,12 @@ msgid "List:" msgstr "Liste:" #: bin/check_db:148 +#, fuzzy msgid " %(file)s: okay" msgstr " %(file)s: ok" #: bin/check_perms:20 +#, fuzzy msgid "" "Check the permissions for the Mailman installation.\n" "\n" @@ -9890,44 +10103,54 @@ msgstr "" "alle fejl undervejs. Med -v vises detaljeret information.\n" #: bin/check_perms:110 +#, fuzzy msgid " checking gid and mode for %(path)s" msgstr " kontrollerer gid og rettigheder for %(path)s" #: bin/check_perms:122 +#, fuzzy msgid "%(path)s bad group (has: %(groupname)s, expected %(MAILMAN_GROUP)s)" msgstr "" "forkert gruppe for %(path)s (har: %(groupname)s, forventer %(MAILMAN_GROUP)s)" #: bin/check_perms:151 +#, fuzzy msgid "directory permissions must be %(octperms)s: %(path)s" msgstr "rettighederne på mappen skal være %(octperms)s: %(path)s" #: bin/check_perms:160 +#, fuzzy msgid "source perms must be %(octperms)s: %(path)s" msgstr "rettighederne på kilden skal være %(octperms)s: %(path)s" #: bin/check_perms:171 +#, fuzzy msgid "article db files must be %(octperms)s: %(path)s" msgstr "" "rettighederne på artikeldatabasefilerne skal være %(octperms)s: %(path)s" #: bin/check_perms:183 +#, fuzzy msgid "checking mode for %(prefix)s" msgstr "kontrollerer rettigheder for %(prefix)s" #: bin/check_perms:193 +#, fuzzy msgid "WARNING: directory does not exist: %(d)s" msgstr "ADVARSEL: mappen eksisterer ikke: %(d)s" #: bin/check_perms:197 +#, fuzzy msgid "directory must be at least 02775: %(d)s" msgstr "mappen skal mindst have rettighederne 02775: %(d)s" #: bin/check_perms:209 +#, fuzzy msgid "checking perms on %(private)s" msgstr "kontrollerer rettigheder for: %(private)s" #: bin/check_perms:214 +#, fuzzy msgid "%(private)s must not be other-readable" msgstr "%(private)s må ikke være læselig for alle" @@ -9950,6 +10173,7 @@ msgid "mbox file must be at least 0660:" msgstr "mbox-filen skal som minimum have rettighederne 0660:" #: bin/check_perms:263 +#, fuzzy msgid "%(dbdir)s \"other\" perms must be 000" msgstr "rettigheder for \"alle andre\" for kataloget %(dbdir)s skal være 000" @@ -9958,26 +10182,32 @@ msgid "checking cgi-bin permissions" msgstr "kontrollerer rettigheder til cgi-bin" #: bin/check_perms:278 +#, fuzzy msgid " checking set-gid for %(path)s" msgstr " kontrollerer set-gid for %(path)s" #: bin/check_perms:282 +#, fuzzy msgid "%(path)s must be set-gid" msgstr "%(path)s skal være set-gid" #: bin/check_perms:292 +#, fuzzy msgid "checking set-gid for %(wrapper)s" msgstr "kontrollerer set-gid for %(wrapper)s" #: bin/check_perms:296 +#, fuzzy msgid "%(wrapper)s must be set-gid" msgstr "%(wrapper)s skal være set-gid" #: bin/check_perms:306 +#, fuzzy msgid "checking permissions on %(pwfile)s" msgstr "kontrollerer rettigheder for %(pwfile)s" #: bin/check_perms:315 +#, fuzzy msgid "%(pwfile)s permissions must be exactly 0640 (got %(octmode)s)" msgstr "" "rettighededer for %(pwfile)s skal være sat til 0640 (de er %(octmode)s)" @@ -9987,10 +10217,12 @@ msgid "checking permissions on list data" msgstr "kontrollerer rettigheder for listedata" #: bin/check_perms:348 +#, fuzzy msgid " checking permissions on: %(path)s" msgstr " kontrollerer rettigheder for: %(path)s" #: bin/check_perms:356 +#, fuzzy msgid "file permissions must be at least 660: %(path)s" msgstr "filrettigheder skal som minimum være 660: %(path)s" @@ -10080,6 +10312,7 @@ msgid "Unix-From line changed: %(lineno)d" msgstr "Unix-From linie ændret: %(lineno)d" #: bin/cleanarch:111 +#, fuzzy msgid "Bad status number: %(arg)s" msgstr "Ugyldigt status nummer: %(arg)s" @@ -10227,10 +10460,12 @@ msgid " original address removed:" msgstr " den oprindelige adresse blev ikke fjernet:" #: bin/clone_member:202 +#, fuzzy msgid "Not a valid email address: %(toaddr)s" msgstr "Ikke en gyldig e-mailadresse: %(toaddr)s" #: bin/clone_member:215 +#, fuzzy msgid "" "Error opening list \"%(listname)s\", skipping.\n" "%(e)s" @@ -10341,6 +10576,7 @@ msgstr "" "\n" #: bin/config_list:118 +#, fuzzy msgid "" "# -*- python -*-\n" "# -*- coding: %(charset)s -*-\n" @@ -10361,22 +10597,27 @@ msgid "legal values are:" msgstr "gyldige værdier:" #: bin/config_list:270 +#, fuzzy msgid "attribute \"%(k)s\" ignored" msgstr "ignorerer attributen \"%(k)s\"" #: bin/config_list:273 +#, fuzzy msgid "attribute \"%(k)s\" changed" msgstr "Attributen ændret \"%(k)s\"" #: bin/config_list:279 +#, fuzzy msgid "Non-standard property restored: %(k)s" msgstr "Ikke-standard egenskab genoprettet: %(k)s" #: bin/config_list:288 +#, fuzzy msgid "Invalid value for property: %(k)s" msgstr "Ugyldig værdi for egenskab: %(k)s" #: bin/config_list:291 +#, fuzzy msgid "Bad email address for option %(k)s: %(v)s" msgstr "Ugyldig e-mailadresse for indstillingen %(k)s: %(v)s" @@ -10441,18 +10682,22 @@ msgstr "" " Don't print status messages.\n" #: bin/discard:94 +#, fuzzy msgid "Ignoring non-held message: %(f)s" msgstr "Ignorerer ikke-tilbageholdt meddelelse: %(f)s" #: bin/discard:100 +#, fuzzy msgid "Ignoring held msg w/bad id: %(f)s" msgstr "Ignorerer tilbageholdt meddelelse med forkert id: %(f)s" #: bin/discard:112 +#, fuzzy msgid "Discarded held msg #%(id)s for list %(listname)s" msgstr "Tilbageholdt meddelelse #%(id)s til listen %(listname)s slettet" #: bin/dumpdb:19 +#, fuzzy msgid "" "Dump the contents of any Mailman `database' file.\n" "\n" @@ -10511,8 +10756,8 @@ msgstr "" "som\n" " en pickle. Nyttig med 'python -i bin/dumpdb '. I det " "tilfælde\n" -" vil roden af træet befinde sig i en global variabel med navnet \"msg" -"\".\n" +" vil roden af træet befinde sig i en global variabel med navnet " +"\"msg\".\n" "\n" " --help / -h\n" " Viser denne hjælpetekst.\n" @@ -10530,6 +10775,7 @@ msgid "No filename given." msgstr "Intet filnavn angivet" #: bin/dumpdb:108 +#, fuzzy msgid "Bad arguments: %(pargs)s" msgstr "Ugyldige parametre: %(pargs)s" @@ -10538,14 +10784,17 @@ msgid "Please specify either -p or -m." msgstr "Benyt venligst -p eller -m." #: bin/dumpdb:133 +#, fuzzy msgid "[----- start %(typename)s file -----]" msgstr "[----- start %(typename)s fil -----]" #: bin/dumpdb:139 +#, fuzzy msgid "[----- end %(typename)s file -----]" msgstr "[----- afslut %(typename)s fil -----]" #: bin/dumpdb:142 +#, fuzzy msgid "<----- start object %(cnt)s ----->" msgstr "<----- start objekt %(cnt)s ----->" @@ -10766,6 +11015,7 @@ msgid "Setting web_page_url to: %(web_page_url)s" msgstr "Sætter web_page_url til: %(web_page_url)s" #: bin/fix_url.py:88 +#, fuzzy msgid "Setting host_name to: %(mailhost)s" msgstr "Sætter host_name til: %(mailhost)s" @@ -10804,6 +11054,7 @@ msgstr "" " Viser denne hjælpetekst.\n" #: bin/genaliases:84 +#, fuzzy msgid "genaliases can't do anything useful with mm_cfg.MTA = %(mta)s." msgstr "genaliases virker ikke med mm_cfg.MTA = %(mta)s." @@ -10855,6 +11106,7 @@ msgstr "" "ind i en kø. Hvis ingen fil angives, benyttes standard input.\n" #: bin/inject:84 +#, fuzzy msgid "Bad queue directory: %(qdir)s" msgstr "Ugyldig kø-folder: %(qdir)s" @@ -10863,6 +11115,7 @@ msgid "A list name is required" msgstr "Navnet på listen skal indtastes" #: bin/list_admins:20 +#, fuzzy msgid "" "List all the owners of a mailing list.\n" "\n" @@ -10909,10 +11162,12 @@ msgstr "" "navn på flere maillister.\n" #: bin/list_admins:97 +#, fuzzy msgid "List: %(listname)s, \tOwners: %(owners)s" msgstr "Liste: %(listname)s, \tEiere: %(owners)s" #: bin/list_lists:19 +#, fuzzy msgid "" "List all mailing lists.\n" "\n" @@ -11083,10 +11338,12 @@ msgstr "" "vises.\n" #: bin/list_members:198 +#, fuzzy msgid "Bad --nomail option: %(why)s" msgstr "Ugyldig --nomail parameter: %(why)s" #: bin/list_members:209 +#, fuzzy msgid "Bad --digest option: %(kind)s" msgstr "Ugyldig --digest parameter: %(kind)s" @@ -11100,6 +11357,7 @@ msgid "Could not open file for writing:" msgstr "Kan ikke åbne filen for skrivning:" #: bin/list_owners:20 +#, fuzzy msgid "" "List the owners of a mailing list, or all mailing lists.\n" "\n" @@ -11152,6 +11410,7 @@ msgid "" msgstr "" #: bin/mailmanctl:20 +#, fuzzy msgid "" "Primary start-up and shutdown script for Mailman's qrunner daemon.\n" "\n" @@ -11330,6 +11589,7 @@ msgstr "" " næste gang noget skal skrives til dem.\n" #: bin/mailmanctl:152 +#, fuzzy msgid "PID unreadable in: %(pidfile)s" msgstr "Ulæselig PID i: %(pidfile)s" @@ -11338,6 +11598,7 @@ msgid "Is qrunner even running?" msgstr "Kører qrunneren i det hele taget?" #: bin/mailmanctl:160 +#, fuzzy msgid "No child with pid: %(pid)s" msgstr "Ingen child med pid: %(pid)s" @@ -11364,6 +11625,7 @@ msgstr "" "eksisterer en gammel låsefil. Kør mailmanctl med \"-s\" valget.\n" #: bin/mailmanctl:233 +#, fuzzy msgid "" "The master qrunner lock could not be acquired, because it appears as if " "some\n" @@ -11390,10 +11652,12 @@ msgstr "" "Afbryder." #: bin/mailmanctl:280 cron/mailpasswds:119 +#, fuzzy msgid "Site list is missing: %(sitelistname)s" msgstr "Systemets mailliste mangler: %(sitelistname)s" #: bin/mailmanctl:305 +#, fuzzy msgid "Run this program as root or as the %(name)s user, or use -u." msgstr "Kør dette program som root eller som %(name)s, eller brug -u." @@ -11402,6 +11666,7 @@ msgid "No command given." msgstr "Ingen kommando angivet." #: bin/mailmanctl:339 +#, fuzzy msgid "Bad command: %(command)s" msgstr "Ugyldig kommando: %(command)s" @@ -11426,6 +11691,7 @@ msgid "Starting Mailman's master qrunner." msgstr "Starter Mailmans master qrunner." #: bin/mmsitepass:19 +#, fuzzy msgid "" "Set the site password, prompting from the terminal.\n" "\n" @@ -11484,6 +11750,7 @@ msgid "list creator" msgstr "person som listen blev oprettet af" #: bin/mmsitepass:86 +#, fuzzy msgid "New %(pwdesc)s password: " msgstr "Ny %(pwdesc)s adgangskode: " @@ -11746,6 +12013,7 @@ msgstr "" "Bemærk at listenavn vil blive ændret til små bogstaver.\n" #: bin/newlist:162 +#, fuzzy msgid "Unknown language: %(lang)s" msgstr "Ukendt sprog: %(lang)s" @@ -11758,6 +12026,7 @@ msgid "Enter the email of the person running the list: " msgstr "Opgiv e-mailadressen for personen der er ansvarlig for listen:" #: bin/newlist:193 +#, fuzzy msgid "Initial %(listname)s password: " msgstr "Den første adgangskode for \"%(listname)s\" er: " @@ -11768,17 +12037,19 @@ msgstr "" #: bin/newlist:220 msgid "" -" - owner addresses need to be fully-qualified names like \"owner@example.com" -"\", not just \"owner\"." +" - owner addresses need to be fully-qualified names like \"owner@example." +"com\", not just \"owner\"." msgstr "" " - ejer adresser skal være fuldt kvalificerede adresser som \"owner@example." "com\", ikke kun \"owner\"." #: bin/newlist:244 +#, fuzzy msgid "Hit enter to notify %(listname)s owner..." msgstr "Tryk [Enter] for at sende besked til ejeren af listen %(listname)s..." #: bin/qrunner:20 +#, fuzzy msgid "" "Run one or more qrunners, once or repeatedly.\n" "\n" @@ -11899,6 +12170,7 @@ msgstr "" "Det giver kun mening og køre det i hånden til debug formål.\n" #: bin/qrunner:179 +#, fuzzy msgid "%(name)s runs the %(runnername)s qrunner" msgstr "%(name)s starter %(runnername)s qrunner" @@ -11911,6 +12183,7 @@ msgid "No runner name given." msgstr "Intet runner navn blev angivet." #: bin/rb-archfix:21 +#, fuzzy msgid "" "Reduce disk space usage for Pipermail archives.\n" "\n" @@ -12052,18 +12325,22 @@ msgstr "" " addr1 ... er yderligere adresser som skal fjernes.\n" #: bin/remove_members:156 +#, fuzzy msgid "Could not open file for reading: %(filename)s." msgstr "Kunne ikke åbne filen \"%(filename)s\" for lesing." #: bin/remove_members:163 +#, fuzzy msgid "Error opening list %(listname)s... skipping." msgstr "Overspringer listen \"%(listname)s\" på grund af fejl under åbning." #: bin/remove_members:173 +#, fuzzy msgid "No such member: %(addr)s" msgstr "Medlemmet findes ikke: %(addr)s." #: bin/remove_members:178 +#, fuzzy msgid "User `%(addr)s' removed from list: %(listname)s." msgstr "%(addr)s er nu fjernet fra listen %(listname)s." @@ -12101,10 +12378,12 @@ msgstr "" " Udskriv hvad scriptet laver.\n" #: bin/reset_pw.py:77 +#, fuzzy msgid "Changing passwords for list: %(listname)s" msgstr "Ændrer adgangskoder for maillisten: %(listname)s" #: bin/reset_pw.py:83 +#, fuzzy msgid "New password for member %(member)40s: %(randompw)s" msgstr "Ny adgangskode for medlem %(member)40s: %(randompw)s" @@ -12149,18 +12428,22 @@ msgstr "" "\n" #: bin/rmlist:73 bin/rmlist:76 +#, fuzzy msgid "Removing %(msg)s" msgstr "Fjerner %(msg)s" #: bin/rmlist:81 +#, fuzzy msgid "%(listname)s %(msg)s not found as %(filename)s" msgstr "%(listname)s %(msg)s ikke fundet som %(filename)s" #: bin/rmlist:105 +#, fuzzy msgid "No such list (or list already deleted): %(listname)s" msgstr "Listen findes ikke (eller er allerede slettet): %(listname)s" #: bin/rmlist:108 +#, fuzzy msgid "No such list: %(listname)s. Removing its residual archives." msgstr "Listen findes ikke: %(listname)s. Fjerner arkivet som ligger tilbage." @@ -12221,6 +12504,7 @@ msgstr "" "Eksempel: show_qfiles qfiles/shunt/*.pck\n" #: bin/sync_members:19 +#, fuzzy msgid "" "Synchronize a mailing list's membership with a flat file.\n" "\n" @@ -12353,6 +12637,7 @@ msgstr "" " Skal benyttes. Angiver navnet på listen der skal synkroniseres.\n" #: bin/sync_members:115 +#, fuzzy msgid "Bad choice: %(yesno)s" msgstr "Ugyldigt valg: %(yesno)s" @@ -12369,6 +12654,7 @@ msgid "No argument to -f given" msgstr "\"-f\" parameteren mangler værdi" #: bin/sync_members:172 +#, fuzzy msgid "Illegal option: %(opt)s" msgstr "Ugyldig parameter: %(opt)s" @@ -12381,6 +12667,7 @@ msgid "Must have a listname and a filename" msgstr "Skal have et listenavn og et filnavn" #: bin/sync_members:191 +#, fuzzy msgid "Cannot read address file: %(filename)s: %(msg)s" msgstr "Kan ikke læse adressefil: %(filename)s: %(msg)s" @@ -12397,14 +12684,17 @@ msgid "You must fix the preceding invalid addresses first." msgstr "Du skal rette de ugyldige adresser først." #: bin/sync_members:264 +#, fuzzy msgid "Added : %(s)s" msgstr "Tilføjet : %(s)s" #: bin/sync_members:289 +#, fuzzy msgid "Removed: %(s)s" msgstr "Fjernet: %(s)s" #: bin/transcheck:19 +#, fuzzy msgid "" "\n" "Check a given Mailman translation, making sure that variables and\n" @@ -12483,6 +12773,7 @@ msgid "scan the po file comparing msgids with msgstrs" msgstr "scan po filen og sammenlign msgids med msgstrs" #: bin/unshunt:20 +#, fuzzy msgid "" "Move a message from the shunt queue to the original queue.\n" "\n" @@ -12515,6 +12806,7 @@ msgstr "" "vil det medføre tab af alle meddelelser i den kø.\n" #: bin/unshunt:85 +#, fuzzy msgid "" "Cannot unshunt message %(filebase)s, skipping:\n" "%(e)s" @@ -12523,6 +12815,7 @@ msgstr "" "%(e)s" #: bin/update:20 +#, fuzzy msgid "" "Perform all necessary upgrades.\n" "\n" @@ -12563,14 +12856,17 @@ msgstr "" "1.0b4 (?).\n" #: bin/update:107 +#, fuzzy msgid "Fixing language templates: %(listname)s" msgstr "Opdaterer sprogfiler: %(listname)s" #: bin/update:196 bin/update:711 +#, fuzzy msgid "WARNING: could not acquire lock for list: %(listname)s" msgstr "ADVARSEL: kunne ikke låse listen: %(listname)s" #: bin/update:215 +#, fuzzy msgid "Resetting %(n)s BYBOUNCEs disabled addrs with no bounce info" msgstr "" "Reset af %(n)s adresser som blev stoppet på grund af returmails, men som " @@ -12589,6 +12885,7 @@ msgstr "" "virke i b6, så jeg ændrer navnet til %(mbox_dir)s.tmp og fortsætter." #: bin/update:255 +#, fuzzy msgid "" "\n" "%(listname)s has both public and private mbox archives. Since this list\n" @@ -12638,6 +12935,7 @@ msgid "- updating old private mbox file" msgstr "- opdaterer den gamle private mbox-fil" #: bin/update:295 +#, fuzzy msgid "" " unknown file in the way, moving\n" " %(o_pri_mbox_file)s\n" @@ -12654,6 +12952,7 @@ msgid "- updating old public mbox file" msgstr "- opdaterer den gamle offentlige mbox-fil" #: bin/update:317 +#, fuzzy msgid "" " unknown file in the way, moving\n" " %(o_pub_mbox_file)s\n" @@ -12682,18 +12981,22 @@ msgid "- %(o_tmpl)s doesn't exist, leaving untouched" msgstr "- %(o_tmpl)s eksisterer ikke, ingen ændring foretaget" #: bin/update:396 +#, fuzzy msgid "removing directory %(src)s and everything underneath" msgstr "fjerner katalog %(src)s og alle underkataloger" #: bin/update:399 +#, fuzzy msgid "removing %(src)s" msgstr "fjerner %(src)s" #: bin/update:403 +#, fuzzy msgid "Warning: couldn't remove %(src)s -- %(rest)s" msgstr "Advarsel: kunne ikke fjerne %(src)s -- %(rest)s" #: bin/update:408 +#, fuzzy msgid "couldn't remove old file %(pyc)s -- %(rest)s" msgstr "kunne ikke fjerne den gamle fil %(pyc)s -- %(rest)s" @@ -12702,14 +13005,17 @@ msgid "updating old qfiles" msgstr "opdaterer gamle qfiler" #: bin/update:455 +#, fuzzy msgid "Warning! Not a directory: %(dirpath)s" msgstr "Advarsel: Ikke en mappe: %(dirpath)s" #: bin/update:530 +#, fuzzy msgid "message is unparsable: %(filebase)s" msgstr "Meddelelsen kan ikke fortolkes: %(filebase)s" #: bin/update:544 +#, fuzzy msgid "Warning! Deleting empty .pck file: %(pckfile)s" msgstr "Advarsel! Sletter tom .pck fil: %(pckfile)s" @@ -12722,10 +13028,12 @@ msgid "Updating Mailman 2.1.4 pending.pck database" msgstr "Opdaterer Mailman 2.1.4 pending_subscriptions.db database" #: bin/update:598 +#, fuzzy msgid "Ignoring bad pended data: %(key)s: %(val)s" msgstr "Ignorerer dårlige udestående data: %(key)s: %(val)s" #: bin/update:614 +#, fuzzy msgid "WARNING: Ignoring duplicate pending ID: %(id)s." msgstr "ADVARSEL: Ignorerer duplikeret udestående ID: %(id)s." @@ -12751,6 +13059,7 @@ msgid "done" msgstr "udført" #: bin/update:691 +#, fuzzy msgid "Updating mailing list: %(listname)s" msgstr "Opdaterer maillliste: %(listname)s" @@ -12813,6 +13122,7 @@ msgid "No updates are necessary." msgstr "Ingen opdatering er nødvendig." #: bin/update:796 +#, fuzzy msgid "" "Downgrade detected, from version %(hexlversion)s to version %(hextversion)s\n" "This is probably not safe.\n" @@ -12823,10 +13133,12 @@ msgstr "" "Afbryder." #: bin/update:801 +#, fuzzy msgid "Upgrading from version %(hexlversion)s to %(hextversion)s" msgstr "Opgraderer fra version %(hexlversion)s til %(hextversion)s" #: bin/update:810 +#, fuzzy msgid "" "\n" "ERROR:\n" @@ -13102,6 +13414,7 @@ msgstr "" "en fejlkode eller hvis os._exit() bliver kaldt. " #: bin/withlist:175 +#, fuzzy msgid "Unlocking (but not saving) list: %(listname)s" msgstr "Låser op (men gemmer ikke) listen: %(listname)s" @@ -13110,6 +13423,7 @@ msgid "Finalizing" msgstr "Afslutter" #: bin/withlist:188 +#, fuzzy msgid "Loading list %(listname)s" msgstr "Indlæser listen %(listname)s" @@ -13122,6 +13436,7 @@ msgid "(unlocked)" msgstr "(åben)" #: bin/withlist:197 +#, fuzzy msgid "Unknown list: %(listname)s" msgstr "Ukendt liste: %(listname)s" @@ -13134,18 +13449,22 @@ msgid "--all requires --run" msgstr "--all kræver --run" #: bin/withlist:266 +#, fuzzy msgid "Importing %(module)s..." msgstr "Importerer %(module)s..." #: bin/withlist:270 +#, fuzzy msgid "Running %(module)s.%(callable)s()..." msgstr "Kører %(module)s.%(callable)s()..." #: bin/withlist:291 +#, fuzzy msgid "The variable `m' is the %(listname)s MailList instance" msgstr "Variablen 'm' er forekomsten af %(listname)s MailList objektet" #: cron/bumpdigests:19 +#, fuzzy msgid "" "Increment the digest volume number and reset the digest number to one.\n" "\n" @@ -13174,6 +13493,7 @@ msgstr "" "volume nummer for alle lister.\n" #: cron/checkdbs:20 +#, fuzzy msgid "" "Check for pending admin requests and mail the list owners if necessary.\n" "\n" @@ -13202,10 +13522,12 @@ msgstr "" "\n" #: cron/checkdbs:121 +#, fuzzy msgid "%(count)d %(realname)s moderator request(s) waiting" msgstr "%(count)d anmodninger venter på behandling på listen %(realname)s" #: cron/checkdbs:124 +#, fuzzy msgid "%(realname)s moderator request check result" msgstr "%(realname)s moderator anmodning check resultat" @@ -13226,6 +13548,7 @@ msgstr "" "e-mail til listen som kræver godkendelse:" #: cron/checkdbs:169 +#, fuzzy msgid "" "From: %(sender)s on %(date)s\n" "Subject: %(subject)s\n" @@ -13236,6 +13559,7 @@ msgstr "" "Begrundelse: %(reason)s" #: cron/cull_bad_shunt:20 +#, fuzzy msgid "" "Cull bad and shunt queues, recommended once per day.\n" "\n" @@ -13275,6 +13599,7 @@ msgstr "" " Vis denne hjælpetekst.\n" #: cron/disabled:20 +#, fuzzy msgid "" "Process disabled members, recommended once per day.\n" "\n" @@ -13398,6 +13723,7 @@ msgstr "" "\n" #: cron/mailpasswds:19 +#, fuzzy msgid "" "Send password reminders for all lists to all users.\n" "\n" @@ -13453,10 +13779,12 @@ msgid "Password // URL" msgstr "Adgangskode // URL" #: cron/mailpasswds:222 +#, fuzzy msgid "%(host)s mailing list memberships reminder" msgstr "Påmindelse om adgangskode for maillister på %(host)s" #: cron/nightly_gzip:19 +#, fuzzy msgid "" "Re-generate the Pipermail gzip'd archive flat files.\n" "\n" @@ -14402,8 +14730,8 @@ msgstr "" #~ msgid "%(rname)s member %(addr)s bouncing - %(negative)s%(did)s" #~ msgstr "" -#~ "Adressen til %(rname)s, %(addr)s, kommer bare i retur - %(negative)s" -#~ "%(did)s" +#~ "Adressen til %(rname)s, %(addr)s, kommer bare i retur - " +#~ "%(negative)s%(did)s" #~ msgid "User not found." #~ msgstr "Medlemmet findes ikke." diff --git a/messages/de/LC_MESSAGES/mailman.po b/messages/de/LC_MESSAGES/mailman.po index 36316894..05c63231 100755 --- a/messages/de/LC_MESSAGES/mailman.po +++ b/messages/de/LC_MESSAGES/mailman.po @@ -76,10 +76,12 @@ msgid "

                Currently, there are no archives.

                " msgstr "

                Keine Archive vorhanden.

                " #: Mailman/Archiver/HyperArch.py:821 +#, fuzzy msgid "Gzip'd Text%(sz)s" msgstr "%(sz)s Text gepackt (gzip)" #: Mailman/Archiver/HyperArch.py:826 +#, fuzzy msgid "Text%(sz)s" msgstr "Text%(sz)s" @@ -159,18 +161,22 @@ msgid "Third" msgstr "Dritte(s)" #: Mailman/Archiver/HyperArch.py:938 +#, fuzzy msgid "%(ord)s quarter %(year)i" msgstr "%(ord)s Quartal %(year)i" #: Mailman/Archiver/HyperArch.py:945 +#, fuzzy msgid "%(month)s %(year)i" msgstr "%(month)s %(year)i" #: Mailman/Archiver/HyperArch.py:950 +#, fuzzy msgid "The Week Of Monday %(day)i %(month)s %(year)i" msgstr "Woche %(day)i %(month)s %(year)i" #: Mailman/Archiver/HyperArch.py:954 +#, fuzzy msgid "%(day)i %(month)s %(year)i" msgstr "%(day)i %(month)s %(year)i" @@ -180,10 +186,12 @@ msgstr "Berechne verketteten Index\n" # Mailman/Archiver/pipermail.py:414 #: Mailman/Archiver/HyperArch.py:1319 +#, fuzzy msgid "Updating HTML for article %(seq)s" msgstr "Aktualisiere HTML für Artikel %(seq)s" #: Mailman/Archiver/HyperArch.py:1326 +#, fuzzy msgid "article file %(filename)s is missing!" msgstr "Artikel-Datei %(filename)s fehlt!" @@ -204,6 +212,7 @@ msgstr "Schreibe Archivzustand in Datei " # Mailman/Archiver/pipermail.py:414 #: Mailman/Archiver/pipermail.py:453 +#, fuzzy msgid "Updating index files for archive [%(archive)s]" msgstr "Index-Dateien für Archiv [%(archive)s] werden aktualisiert" @@ -213,6 +222,7 @@ msgid " Thread" msgstr " Diskussionsfaden" #: Mailman/Archiver/pipermail.py:597 +#, fuzzy msgid "#%(counter)05d %(msgid)s" msgstr "#%(counter)05d %(msgid)s" @@ -253,6 +263,7 @@ msgid "disabled address" msgstr "deaktivierte Adresse" #: Mailman/Bouncer.py:304 +#, fuzzy msgid " The last bounce received from you was dated %(date)s" msgstr " Die letzte Unzustellbarkeitsmeldung von Ihnen kam am %(date)s" @@ -291,6 +302,7 @@ msgstr "Administrator" #: Mailman/Cgi/options.py:101 Mailman/Cgi/private.py:108 #: Mailman/Cgi/rmlist.py:75 Mailman/Cgi/roster.py:59 #: Mailman/Cgi/subscribe.py:67 +#, fuzzy msgid "No such list %(safelistname)s" msgstr "Keine Liste mit Namen %(safelistname)s vorhanden." @@ -375,6 +387,7 @@ msgstr "" # Mailman/Cgi/admin.py:203 #: Mailman/Cgi/admin.py:268 +#, fuzzy msgid "%(hostname)s mailing lists - Admin Links" msgstr "%(hostname)s E-Mail Listen - Administrative Links" @@ -390,6 +403,7 @@ msgstr "Mailman" # Mailman/Cgi/admin.py:232 #: Mailman/Cgi/admin.py:310 +#, fuzzy msgid "" "

                There currently are no publicly-advertised %(mailmanlink)s\n" " mailing lists on %(hostname)s." @@ -399,6 +413,7 @@ msgstr "" # Mailman/Cgi/admin.py:238 #: Mailman/Cgi/admin.py:316 +#, fuzzy msgid "" "

                Below is the collection of publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s. Click on a list\n" @@ -415,6 +430,7 @@ msgstr "rechts " # Mailman/Cgi/admin.py:247 #: Mailman/Cgi/admin.py:325 +#, fuzzy msgid "" "To visit the administrators configuration page for an\n" " unadvertised list, open a URL similar to this one, but with a '/' " @@ -466,6 +482,7 @@ msgstr "Kein g # Mailman/Cgi/admin.py:314 #: Mailman/Cgi/admin.py:395 +#, fuzzy msgid "" "%(realname)s Mailing list Configuration Help\n" "
                %(varname)s Option" @@ -473,6 +490,7 @@ msgstr "%(realname)s Listenkonfigurationshilfe
                %(varname)s Option" # Mailman/Cgi/admin.py:321 #: Mailman/Cgi/admin.py:402 +#, fuzzy msgid "Mailman %(varname)s List Option Help" msgstr "Hilfe für Mailman %(varname)s Listen-Optionen" @@ -492,16 +510,19 @@ msgstr "" # Mailman/Cgi/admin.py:340 #: Mailman/Cgi/admin.py:431 +#, fuzzy msgid "return to the %(categoryname)s options page." msgstr "zur Konfigurationsseite für %(categoryname)s zurückkehren." # Mailman/Cgi/admin.py:355 #: Mailman/Cgi/admin.py:446 +#, fuzzy msgid "%(realname)s Administration (%(label)s)" msgstr "%(realname)s Administration (%(label)s)" # Mailman/Cgi/admin.py:356 #: Mailman/Cgi/admin.py:447 +#, fuzzy msgid "%(realname)s mailing list administration
                %(label)s Section" msgstr "%(realname)s Listen-Administration
                Sektion %(label)s" @@ -601,6 +622,7 @@ msgstr "Wert" # Mailman/Cgi/admin.py:562 #: Mailman/Cgi/admin.py:668 +#, fuzzy msgid "" "Badly formed options entry:\n" " %(record)s" @@ -713,10 +735,12 @@ msgid "Move rule down" msgstr "Regel tiefer schieben" #: Mailman/Cgi/admin.py:870 +#, fuzzy msgid "
                (Edit %(varname)s)" msgstr "
                (Details zu %(varname)s)" #: Mailman/Cgi/admin.py:872 +#, fuzzy msgid "
                (Details for %(varname)s)" msgstr "
                (Details zu %(varname)s)" @@ -760,6 +784,7 @@ msgid "(help)" msgstr "(Hilfe)" #: Mailman/Cgi/admin.py:930 +#, fuzzy msgid "Find member %(link)s:" msgstr "Mitglied finden %(link)s:" @@ -774,11 +799,13 @@ msgstr "Fehlerhafter regul # Mailman/Cgi/admin.py:788 #: Mailman/Cgi/admin.py:1013 +#, fuzzy msgid "%(allcnt)s members total, %(membercnt)s shown" msgstr "%(allcnt)s Mitglieder insgesamt, %(membercnt)s werden angezeigt" # Mailman/Cgi/admin.py:791 #: Mailman/Cgi/admin.py:1016 +#, fuzzy msgid "%(allcnt)s members total" msgstr "%(allcnt)s Mitglieder insgesamt" @@ -987,6 +1014,7 @@ msgstr "" # Mailman/Cgi/admin.py:913 #: Mailman/Cgi/admin.py:1221 +#, fuzzy msgid "from %(start)s to %(end)s" msgstr "von %(start)s bis %(end)s" @@ -1166,6 +1194,7 @@ msgstr "Passworte der Listenadministration # Mailman/Cgi/admin.py:997 #: Mailman/Cgi/admin.py:1364 +#, fuzzy msgid "" "The list administrators are the people who have ultimate control " "over\n" @@ -1203,8 +1232,8 @@ msgstr "" "müssen Sie\n" "in den entsprechenden Feldern unten ein separates Passwort für Moderatoren " "setzen\n" -"und die Adressen der Moderatoren im Abschnitt Allgemeine Optionen angeben. " +"und die Adressen der Moderatoren im Abschnitt Allgemeine Optionen angeben. " # Mailman/Cgi/admin.py:1017 #: Mailman/Cgi/admin.py:1383 @@ -1301,6 +1330,7 @@ msgstr "Unzul #: Mailman/Cgi/admin.py:1529 Mailman/Cgi/admin.py:1703 bin/add_members:175 #: bin/clone_member:136 bin/sync_members:268 +#, fuzzy msgid "Banned address (matched %(pattern)s)" msgstr "Blockierte Adresse (passte auf %(pattern)s)" @@ -1366,6 +1396,7 @@ msgid "%(schange_to)s is already a member" msgstr "%(schange_to)s ist bereits Mitglied" #: Mailman/Cgi/admin.py:1620 +#, fuzzy msgid "%(schange_to)s matches banned pattern %(spat)s" msgstr "%(schange_to)s ist durch das Muster %(spat)s blockiert" @@ -1408,6 +1439,7 @@ msgid "Not subscribed" msgstr "Nicht abonniert:" #: Mailman/Cgi/admin.py:1773 +#, fuzzy msgid "Ignoring changes to deleted member: %(user)s" msgstr "Änderungen für gelöschtes Mitglied %(user)s ignoriert" @@ -1423,11 +1455,13 @@ msgstr "Fehler beim Beenden des Abonnement:" # Mailman/Cgi/admindb.py:111 #: Mailman/Cgi/admindb.py:235 Mailman/Cgi/admindb.py:248 +#, fuzzy msgid "%(realname)s Administrative Database" msgstr "%(realname)s Administrative Datenbank" # Mailman/Cgi/admindb.py:114 #: Mailman/Cgi/admindb.py:238 +#, fuzzy msgid "%(realname)s Administrative Database Results" msgstr "%(realname)s Administrative Datenbank-Ergebnisse" @@ -1462,6 +1496,7 @@ msgstr "" "Alle mit Entscheidung aufschieben markierten Nachrichten verwerfen." #: Mailman/Cgi/admindb.py:298 +#, fuzzy msgid "all of %(esender)s's held messages." msgstr "alle festgehaltenen Nachrichten vom %(esender)s." @@ -1487,6 +1522,7 @@ msgstr "Liste der verf # Mailman/Cgi/admindb.py:137 #: Mailman/Cgi/admindb.py:362 +#, fuzzy msgid "You must specify a list name. Here is the %(link)s" msgstr "Sie müssen einen Listennamen angeben. Benutzen Sie bitte %(link)s" @@ -1579,6 +1615,7 @@ msgid "The sender is now a member of this list" msgstr "Der Absender ist kein Mitglied der Liste" #: Mailman/Cgi/admindb.py:568 +#, fuzzy msgid "Add %(esender)s to one of these sender filters:" msgstr "%(esender)s zu einem dieser Filter hinzufügen:" @@ -1601,6 +1638,7 @@ msgid "Rejects" msgstr "Ablehnen" #: Mailman/Cgi/admindb.py:584 +#, fuzzy msgid "" "Ban %(esender)s from ever subscribing to this\n" " mailing list" @@ -1617,6 +1655,7 @@ msgstr "" " Nachricht zu lesen, oder " #: Mailman/Cgi/admindb.py:591 +#, fuzzy msgid "view all messages from %(esender)s" msgstr "alle Nachrichten von Absender %(esender)s ansehen" @@ -1715,6 +1754,7 @@ msgid " is already a member" msgstr " ist bereits Mitglied." #: Mailman/Cgi/admindb.py:973 +#, fuzzy msgid "%(addr)s is banned (matched: %(patt)s)" msgstr "%(addr)s ist blockiert (passte auf: %(patt)s)" @@ -1725,6 +1765,7 @@ msgstr "Leere Best # Mailman/Cgi/confirm.py:84 #: Mailman/Cgi/confirm.py:108 +#, fuzzy msgid "" "Invalid confirmation string:\n" " %(safecookie)s.\n" @@ -1743,8 +1784,8 @@ msgstr "" " %(days)s Tage nach der Anfrage sowie nach einmaliger Verwendung\n" " ungültig werden. Falls Ihre Bestätigung ungültig ist, senden Sie bitte " "eine\n" -" neue Anfrage. Andernfalls geben Sie bitte Ihre Bestätigung erneut ein." +" neue Anfrage. Andernfalls geben Sie bitte Ihre Bestätigung erneut ein." #: Mailman/Cgi/confirm.py:142 msgid "" @@ -1767,6 +1808,7 @@ msgstr "" "Anfrage wurde verworfen." #: Mailman/Cgi/confirm.py:178 +#, fuzzy msgid "System error, bad content: %(content)s" msgstr "Systemfehler, ungültiger Inhalt: %(content)s" @@ -1810,6 +1852,7 @@ msgstr "Mitgliedsantrag best # Mailman/Cgi/confirm.py:188 #: Mailman/Cgi/confirm.py:259 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " subscription request to the mailing list %(listname)s. Your\n" @@ -1846,6 +1889,7 @@ msgstr "" # Mailman/Cgi/confirm.py:188 #: Mailman/Cgi/confirm.py:275 +#, fuzzy msgid "" "Your confirmation is required in order to continue with\n" " the subscription request to the mailing list %(listname)s.\n" @@ -1906,6 +1950,7 @@ msgstr "Bevorzugte Sprache:" # Mailman/Cgi/rmlist.py:60 Mailman/Cgi/roster.py:55 # Mailman/Cgi/subscribe.py:57 #: Mailman/Cgi/confirm.py:317 +#, fuzzy msgid "Subscribe to list %(listname)s" msgstr "Eintrag in Liste %(listname)s" @@ -1926,6 +1971,7 @@ msgstr "Warten auf Best # Mailman/Cgi/confirm.py:271 #: Mailman/Cgi/confirm.py:386 +#, fuzzy msgid "" " You have successfully confirmed your subscription request to " "the\n" @@ -1961,6 +2007,7 @@ msgid "You are already a member of this mailing list!" msgstr "Sie haben diese Mailingliste bereits bestellt!" #: Mailman/Cgi/confirm.py:400 +#, fuzzy msgid "" "You are currently banned from subscribing to\n" " this list. If you think this restriction is erroneous, please\n" @@ -1986,6 +2033,7 @@ msgstr "Antrag auf Abo best # Mailman/Cgi/confirm.py:299 #: Mailman/Cgi/confirm.py:418 +#, fuzzy msgid "" " You have successfully confirmed your subscription request for\n" " \"%(addr)s\" to the %(listname)s mailing list. A separate\n" @@ -2014,6 +2062,7 @@ msgstr "K # Mailman/Cgi/confirm.py:349 #: Mailman/Cgi/confirm.py:469 +#, fuzzy msgid "" " You have successfully unsubscribed from the %(listname)s " "mailing\n" @@ -2037,6 +2086,7 @@ msgstr "Nicht verf # Mailman/Cgi/confirm.py:372 #: Mailman/Cgi/confirm.py:498 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " unsubscription request from the mailing list %(listname)s. " @@ -2084,6 +2134,7 @@ msgid "You have canceled your change of address request." msgstr "Sie haben die Änderung Ihrer Adresse abgebrochen." #: Mailman/Cgi/confirm.py:555 +#, fuzzy msgid "" "%(newaddr)s is banned from subscribing to the\n" " %(realname)s list. If you think this restriction is erroneous,\n" @@ -2097,6 +2148,7 @@ msgstr "" # Mailman/Cgi/confirm.py:278 Mailman/Cgi/confirm.py:339 # Mailman/Cgi/confirm.py:421 #: Mailman/Cgi/confirm.py:560 +#, fuzzy msgid "" "%(newaddr)s is already a member of\n" " the %(realname)s list. It is possible that you are attempting\n" @@ -2114,6 +2166,7 @@ msgstr " # Mailman/Cgi/confirm.py:431 #: Mailman/Cgi/confirm.py:571 +#, fuzzy msgid "" " You have successfully changed your address on the %(listname)s\n" " mailing list from %(oldaddr)s to %(newaddr)s. " @@ -2137,6 +2190,7 @@ msgstr "generell" # Mailman/Cgi/confirm.py:459 #: Mailman/Cgi/confirm.py:605 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " change of address request for the mailing list %(listname)s. " @@ -2198,6 +2252,7 @@ msgstr "Absender hat die Nachricht via WWW verworfen." # Mailman/Cgi/confirm.py:525 #: Mailman/Cgi/confirm.py:674 +#, fuzzy msgid "" "The held message with the Subject:\n" " header %(subject)s could not be found. The most " @@ -2219,13 +2274,15 @@ msgstr "Ver # Mailman/Cgi/confirm.py:536 #: Mailman/Cgi/confirm.py:685 +#, fuzzy msgid "" " You have successfully canceled the posting of your message with\n" " the Subject: header %(subject)s to the mailing list\n" " %(listname)s." msgstr "" -" Sie haben die bereits Nachricht mit dem Betreff header " -"%(subject)s an die Mailingliste %(listname)s erfolgreich zurückgezogen." +" Sie haben die bereits Nachricht mit dem Betreff header " +"%(subject)s an die Mailingliste %(listname)s erfolgreich " +"zurückgezogen." # Mailman/Cgi/confirm.py:547 #: Mailman/Cgi/confirm.py:696 @@ -2241,6 +2298,7 @@ msgstr "" # Mailman/Cgi/confirm.py:571 #: Mailman/Cgi/confirm.py:735 +#, fuzzy msgid "" "Your confirmation is required in order to cancel the\n" " posting of your message to the mailing list %(listname)s:\n" @@ -2255,8 +2313,8 @@ msgid "" "

                Or hit the Continue awaiting approval button to continue to\n" " allow the list moderator to approve or reject the message." msgstr "" -"Ihre Bestätigung ist erforderlich um Ihre Nachricht an die Mailingliste " -"%(listname)s zu verwerfen:\n" +"Ihre Bestätigung ist erforderlich um Ihre Nachricht an die Mailingliste " +"%(listname)s zu verwerfen:\n" "

                • Absender: %(sender)s
                • Betreff: %(subject)s " "
                • Begründung: %(reason)s
                \n" " Klicken Sie auf Nachricht verwerfen zum verwerfen der Nachricht.\n" @@ -2287,6 +2345,7 @@ msgstr "Mitgliedschaft reaktiviert" # Mailman/Cgi/confirm.py:349 #: Mailman/Cgi/confirm.py:798 +#, fuzzy msgid "" " You have successfully re-enabled your membership in the\n" " %(listname)s mailing list. You can now Informationsseite der Mailingliste besuchen." +"%(listname)sreaktiviert. Sie können nun die allgemeine Informationsseite der Mailingliste besuchen." # Mailman/Deliverer.py:103 #: Mailman/Cgi/confirm.py:810 @@ -2304,14 +2363,15 @@ msgstr "Reaktivierung der Mitgliedschaft" # Mailman/Cgi/confirm.py:349 #: Mailman/Cgi/confirm.py:827 +#, fuzzy msgid "" "We're sorry, but you have already been unsubscribed\n" " from this mailing list. To re-subscribe, please visit the\n" " list information page." msgstr "" "Sie wurden aus der Liste bereits erfolgreich ausgetragen.\n" -"Um sich neu einzutragen, besuchen Sie bitte die Seite Informationsseite der Mailingliste." +"Um sich neu einzutragen, besuchen Sie bitte die Seite Informationsseite der Mailingliste." # Mailman/Cgi/confirm.py:371 Mailman/Cgi/confirm.py:454 #: Mailman/Cgi/confirm.py:842 @@ -2319,6 +2379,7 @@ msgid "not available" msgstr "Nicht verfügbar" #: Mailman/Cgi/confirm.py:846 +#, fuzzy msgid "" "Your membership in the %(realname)s mailing list is\n" " currently disabled due to excessive bounces. Your confirmation is\n" @@ -2399,12 +2460,14 @@ msgstr "Administrativen Listen # Mailman/Cgi/create.py:95 #: Mailman/Cgi/create.py:115 +#, fuzzy msgid "List name must not include \"@\": %(safelistname)s" msgstr "Der Listenname darf \"@\" nicht enthalten: %(safelistname)s" # Mailman/Cgi/create.py:101 Mailman/Cgi/create.py:174 bin/newlist:122 # bin/newlist:154 #: Mailman/Cgi/create.py:122 +#, fuzzy msgid "List already exists: %(safelistname)s" msgstr "Liste existiert bereits: %(safelistname)s" @@ -2447,21 +2510,25 @@ msgstr "" # Mailman/Cgi/create.py:203 bin/newlist:184 #: Mailman/Cgi/create.py:182 +#, fuzzy msgid "Unknown virtual host: %(safehostname)s" msgstr "Unbekannter virtueller host: %(safehostname)s" # Mailman/Cgi/create.py:170 #: Mailman/Cgi/create.py:218 bin/newlist:219 +#, fuzzy msgid "Bad owner email address: %(s)s" msgstr "Falsche E-Mail-Adresse des Eigentümers: %(s)s" # Mailman/Cgi/create.py:101 Mailman/Cgi/create.py:174 bin/newlist:122 # bin/newlist:154 #: Mailman/Cgi/create.py:223 bin/newlist:182 bin/newlist:223 +#, fuzzy msgid "List already exists: %(listname)s" msgstr "Liste existiert bereits: %(listname)s" #: Mailman/Cgi/create.py:231 bin/newlist:217 +#, fuzzy msgid "Illegal list name: %(s)s" msgstr "Ungültiger Listenname: %(s)s" @@ -2476,6 +2543,7 @@ msgstr "" # Mailman/Cgi/create.py:203 bin/newlist:184 #: Mailman/Cgi/create.py:273 bin/newlist:265 +#, fuzzy msgid "Your new mailing list: %(listname)s" msgstr "Ihre neue Mailingliste: %(listname)s" @@ -2486,6 +2554,7 @@ msgstr "Ergebnis des Anlegens einer neuen Mailingliste" # Mailman/Cgi/create.py:218 #: Mailman/Cgi/create.py:288 +#, fuzzy msgid "" "You have successfully created the mailing list\n" " %(listname)s and notification has been sent to the list owner\n" @@ -2512,6 +2581,7 @@ msgstr "Ein weitere Mailingliste anlegen?" # Mailman/Cgi/create.py:249 #: Mailman/Cgi/create.py:312 +#, fuzzy msgid "Create a %(hostname)s Mailing List" msgstr "Anlegen einer Mailingliste auf %(hostname)s " @@ -2609,6 +2679,7 @@ msgstr "Sollen neue Mitglieder zuerst auf moderiert gesetzt werden?" # Mailman/Cgi/create.py:335 #: Mailman/Cgi/create.py:432 +#, fuzzy msgid "" "Initial list of supported languages.

                Note that if you do not\n" " select at least one initial language, the list will use the server\n" @@ -2722,6 +2793,7 @@ msgstr "Der Name der Liste ist erforderlich" # Mailman/Cgi/edithtml.py:95 #: Mailman/Cgi/edithtml.py:154 +#, fuzzy msgid "%(realname)s -- Edit html for %(template_info)s" msgstr "%(realname)s -- html für %(template_info)s bearbeiten" @@ -2732,11 +2804,13 @@ msgstr "HTML bearbeiten: Fehler" # Mailman/Cgi/edithtml.py:100 #: Mailman/Cgi/edithtml.py:161 +#, fuzzy msgid "%(safetemplatename)s: Invalid template" msgstr "%(safetemplatename)s: Ungültige Vorlage" # Mailman/Cgi/edithtml.py:105 Mailman/Cgi/edithtml.py:106 #: Mailman/Cgi/edithtml.py:166 Mailman/Cgi/edithtml.py:167 +#, fuzzy msgid "%(realname)s -- HTML Page Editing" msgstr "%(realname)s -- Bearbeitung der HTML-Seiten" @@ -2808,11 +2882,13 @@ msgstr "HTML erfolgreich aktualisiert" # Mailman/Cgi/listinfo.py:69 #: Mailman/Cgi/listinfo.py:89 +#, fuzzy msgid "%(hostname)s Mailing Lists" msgstr " Mailinglisten auf %(hostname)s" # Mailman/Cgi/listinfo.py:103 #: Mailman/Cgi/listinfo.py:127 +#, fuzzy msgid "" "

                There currently are no publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s." @@ -2822,6 +2898,7 @@ msgstr "" # Mailman/Cgi/listinfo.py:107 #: Mailman/Cgi/listinfo.py:131 +#, fuzzy msgid "" "

                Below is a listing of all the public mailing lists on\n" " %(hostname)s. Click on a list name to get more information " @@ -2843,6 +2920,7 @@ msgstr "rechts" # Mailman/Cgi/listinfo.py:116 #: Mailman/Cgi/listinfo.py:140 +#, fuzzy msgid "" " To visit the general information page for an unadvertised list,\n" " open a URL similar to this one, but with a '/' and the %(adj)s\n" @@ -2900,6 +2978,7 @@ msgid "CGI script error" msgstr "CGI Skript Fehler" #: Mailman/Cgi/options.py:71 +#, fuzzy msgid "Invalid request method: %(method)s" msgstr "Ungültige Anfrage: %(method)s" @@ -2916,6 +2995,7 @@ msgstr "Ung # Mailman/Cgi/options.py:93 #: Mailman/Cgi/options.py:183 Mailman/Cgi/options.py:240 #: Mailman/Cgi/options.py:264 +#, fuzzy msgid "No such member: %(safeuser)s." msgstr " %(safeuser)s ist nicht Abonnent." @@ -2971,6 +3051,7 @@ msgstr "Bachten Sie: " # Mailman/Cgi/options.py:187 #: Mailman/Cgi/options.py:378 +#, fuzzy msgid "List subscriptions for %(safeuser)s on %(hostname)s" msgstr "Abonnierte Mailinglisten für %(safeuser)s auf %(hostname)s " @@ -3004,6 +3085,7 @@ msgid "You are already using that email address" msgstr "Sie verwenden bereits diese E-Mail-Adresse" #: Mailman/Cgi/options.py:459 +#, fuzzy msgid "" "The new address you requested %(newaddr)s is already a member of the\n" "%(listname)s mailing list, however you have also requested a global change " @@ -3020,6 +3102,7 @@ msgstr "" # Mailman/Cgi/admindb.py:364 #: Mailman/Cgi/options.py:468 +#, fuzzy msgid "The new address is already a member: %(newaddr)s" msgstr "Die neue Adresse ist bereits Mitglied: %(newaddr)s" @@ -3030,6 +3113,7 @@ msgstr "Die Adresse darf nicht leer sein" # Mailman/Cgi/options.py:258 #: Mailman/Cgi/options.py:488 +#, fuzzy msgid "A confirmation message has been sent to %(newaddr)s. " msgstr "Eine Bestätigung wurde an %(newaddr)s geschickt. " @@ -3045,10 +3129,12 @@ msgstr "E-Mail-Adresse ist nicht erlaubt" # Mailman/Cgi/options.py:271 #: Mailman/Cgi/options.py:501 +#, fuzzy msgid "%(newaddr)s is already a member of the list." msgstr "%(newaddr)s ist bereits Abonnent der Liste." #: Mailman/Cgi/options.py:504 +#, fuzzy msgid "" "%(newaddr)s is banned from this list. If you\n" " think this restriction is erroneous, please contact\n" @@ -3133,6 +3219,7 @@ msgstr "" # Mailman/Cgi/options.py:352 #: Mailman/Cgi/options.py:623 +#, fuzzy msgid "" "You have been successfully unsubscribed from the\n" " mailing list %(fqdn_listname)s. If you were receiving digest\n" @@ -3238,6 +3325,7 @@ msgstr "Tag" # Mailman/Cgi/options.py:564 #: Mailman/Cgi/options.py:900 +#, fuzzy msgid "%(days)d %(units)s" msgstr "%(days)d %(units)s " @@ -3253,6 +3341,7 @@ msgstr "Keine Themen definiert" # Mailman/Cgi/options.py:606 #: Mailman/Cgi/options.py:940 +#, fuzzy msgid "" "\n" "You are subscribed to this list with the case-preserved address\n" @@ -3264,6 +3353,7 @@ msgstr "" # Mailman/Cgi/options.py:619 #: Mailman/Cgi/options.py:956 +#, fuzzy msgid "%(realname)s list: member options login page" msgstr "Mailingliste %(realname)s: Login für Mitglieder" @@ -3274,11 +3364,13 @@ msgstr "und E-Mail-Adresse" # Mailman/Cgi/options.py:623 #: Mailman/Cgi/options.py:960 +#, fuzzy msgid "%(realname)s list: member options for user %(safeuser)s" msgstr "Mailingliste %(realname)s: Optionen für das Mitglied %(safeuser)s " # Mailman/Cgi/options.py:636 #: Mailman/Cgi/options.py:986 +#, fuzzy msgid "" "In order to change your membership option, you must\n" " first log in by giving your %(extra)smembership password in the section\n" @@ -3375,6 +3467,7 @@ msgstr "" # Mailman/Cgi/options.py:787 #: Mailman/Cgi/options.py:1140 +#, fuzzy msgid "Requested topic is not valid: %(topicname)s" msgstr "Angefordertes Thema ist nicht in Ordnung: %(topicname)s" @@ -3409,6 +3502,7 @@ msgstr "Privates Archiv - \"./\" und \"../\" sind nicht in der URL erlaubt." # Mailman/Cgi/private.py:97 #: Mailman/Cgi/private.py:109 +#, fuzzy msgid "Private Archive Error - %(msg)s" msgstr " Fehler im privaten Archiv - %(msg)s" @@ -3437,6 +3531,7 @@ msgstr "Archivdatei nicht gefunden" # Mailman/Cgi/rmlist.py:60 Mailman/Cgi/roster.py:55 # Mailman/Cgi/subscribe.py:57 #: Mailman/Cgi/rmlist.py:76 +#, fuzzy msgid "No such list %(safelistname)s" msgstr "Die Liste %(safelistname)s ist nicht vorhanden." @@ -3457,12 +3552,14 @@ msgstr "Ergebnis des L # Mailman/Cgi/rmlist.py:156 #: Mailman/Cgi/rmlist.py:187 +#, fuzzy msgid "" "You have successfully deleted the mailing list\n" " %(listname)s." msgstr "Sie haben erfolgreich die Mailingliste %(listname)s gelöscht." #: Mailman/Cgi/rmlist.py:191 +#, fuzzy msgid "" "There were some problems deleting the mailing list\n" " %(listname)s. Contact your site administrator at " @@ -3475,11 +3572,13 @@ msgstr "" # Mailman/Cgi/rmlist.py:172 #: Mailman/Cgi/rmlist.py:208 +#, fuzzy msgid "Permanently remove mailing list %(realname)s" msgstr "Dauerhaft die Mailingliste %(realname)s löschen" # Mailman/Cgi/rmlist.py:172 #: Mailman/Cgi/rmlist.py:209 +#, fuzzy msgid "Permanently remove mailing list %(realname)s" msgstr "Die Mailingliste %(realname)s dauerhaft löschen" @@ -3554,6 +3653,7 @@ msgstr "Ung # Mailman/Cgi/roster.py:97 #: Mailman/Cgi/roster.py:118 +#, fuzzy msgid "%(realname)s roster authentication failed." msgstr "%(realname)s Teilnehmerliste - Anmeldung fehlgeschlagen" @@ -3630,6 +3730,7 @@ msgstr "" "erhalten Sie in Kürze eine erklärende E-Mail, mit genauen Anweisungen." #: Mailman/Cgi/subscribe.py:262 +#, fuzzy msgid "" "The email address you supplied is banned from this\n" " mailing list. If you think this restriction is erroneous, please\n" @@ -3659,6 +3760,7 @@ msgstr "" # Mailman/Cgi/subscribe.py:174 #: Mailman/Cgi/subscribe.py:278 +#, fuzzy msgid "" "Confirmation from your email address is required, to prevent anyone from\n" "subscribing you without permission. Instructions are being sent to you at\n" @@ -3672,6 +3774,7 @@ msgstr "" # Mailman/Cgi/subscribe.py:183 #: Mailman/Cgi/subscribe.py:290 +#, fuzzy msgid "" "Your subscription request was deferred because %(x)s. Your request has " "been\n" @@ -3693,6 +3796,7 @@ msgid "Mailman privacy alert" msgstr "Datenschutz-Warnung von Mailman" #: Mailman/Cgi/subscribe.py:316 +#, fuzzy msgid "" "An attempt was made to subscribe your address to the mailing list\n" "%(listaddr)s. You are already subscribed to this mailing list.\n" @@ -3739,6 +3843,7 @@ msgstr "Diese Liste erlaubt nur Abonnements von Nachrichtensammlungen!" # Mailman/Cgi/subscribe.py:203 #: Mailman/Cgi/subscribe.py:344 +#, fuzzy msgid "You have been successfully subscribed to the %(realname)s mailing list." msgstr "Sie haben die Mailingliste %(realname)s erfolgreich abonniert." @@ -3765,6 +3870,7 @@ msgstr "Verwendung:" # Mailman/MailCommandHandler.py:684 #: Mailman/Commands/cmd_confirm.py:50 +#, fuzzy msgid "" "Invalid confirmation string. Note that confirmation strings expire\n" "approximately %(days)s days after the initial request. They also expire if\n" @@ -3794,6 +3900,7 @@ msgstr "" "sind Sie mit einer anderen Adresse eingetragen?" #: Mailman/Commands/cmd_confirm.py:69 +#, fuzzy msgid "" "You are currently banned from subscribing to this list. If you think this\n" "restriction is erroneous, please contact the list owners at\n" @@ -3876,26 +3983,32 @@ msgstr "n/a" # Mailman/Cgi/create.py:95 #: Mailman/Commands/cmd_info.py:44 +#, fuzzy msgid "List name: %(listname)s" msgstr "Listenname: %(listname)s" #: Mailman/Commands/cmd_info.py:45 +#, fuzzy msgid "Description: %(description)s" msgstr "Beschreibung: %(description)s" #: Mailman/Commands/cmd_info.py:46 +#, fuzzy msgid "Postings to: %(postaddr)s" msgstr "E-Mails an: %(postaddr)s" #: Mailman/Commands/cmd_info.py:47 +#, fuzzy msgid "List Helpbot: %(requestaddr)s" msgstr "Listen-Programm: %(requestaddr)s" #: Mailman/Commands/cmd_info.py:48 +#, fuzzy msgid "List Owners: %(owneraddr)s" msgstr "Liste-Administrator: %(owneraddr)s" #: Mailman/Commands/cmd_info.py:49 +#, fuzzy msgid "More information: %(listurl)s" msgstr "Weitere Informationen: %(listurl)s" @@ -3919,18 +4032,22 @@ msgstr "" # Mailman/MailCommandHandler.py:449 #: Mailman/Commands/cmd_lists.py:44 +#, fuzzy msgid "Public mailing lists at %(hostname)s:" msgstr "Öffentliche Mailingliste auf %(hostname)s:" #: Mailman/Commands/cmd_lists.py:66 +#, fuzzy msgid "%(i)3d. List name: %(realname)s" msgstr "%(i)3d. Listen-Name: %(realname)s" #: Mailman/Commands/cmd_lists.py:67 +#, fuzzy msgid " Description: %(description)s" msgstr " Beschreibung: %(description)s" #: Mailman/Commands/cmd_lists.py:68 +#, fuzzy msgid " Requests to: %(requestaddr)s" msgstr " Kommandos an: %(requestaddr)s" @@ -3969,6 +4086,7 @@ msgstr "" " wird!\n" #: Mailman/Commands/cmd_password.py:51 Mailman/Commands/cmd_password.py:66 +#, fuzzy msgid "Your password is: %(password)s" msgstr "Ihr Passwort ist: %(password)s" @@ -3976,6 +4094,7 @@ msgstr "Ihr Passwort ist: %(password)s" #: Mailman/Commands/cmd_password.py:57 Mailman/Commands/cmd_password.py:72 #: Mailman/Commands/cmd_password.py:95 Mailman/Commands/cmd_password.py:121 #: Mailman/Commands/cmd_set.py:149 Mailman/Commands/cmd_set.py:219 +#, fuzzy msgid "You are not a member of the %(listname)s mailing list" msgstr "Sie sind kein Mitglied der Liste %(listname)s" @@ -4167,6 +4286,7 @@ msgstr "" " Paßworterinnerungsmail bekommen möchten.\n" #: Mailman/Commands/cmd_set.py:122 +#, fuzzy msgid "Bad set command: %(subcmd)s" msgstr "Unverständliche Anweisung: %(subcmd)s" @@ -4201,6 +4321,7 @@ msgid "on" msgstr "Ein" #: Mailman/Commands/cmd_set.py:154 +#, fuzzy msgid " ack %(onoff)s" msgstr " ack %(onoff)s" @@ -4244,22 +4365,27 @@ msgid "due to bounces" msgstr "wegen unzustellbarerer Nachrichten" #: Mailman/Commands/cmd_set.py:186 +#, fuzzy msgid " %(status)s (%(how)s on %(date)s)" msgstr " %(status)s (%(how)s am %(date)s)" #: Mailman/Commands/cmd_set.py:192 +#, fuzzy msgid " myposts %(onoff)s" msgstr " mypost %(onoff)s" #: Mailman/Commands/cmd_set.py:195 +#, fuzzy msgid " hide %(onoff)s" msgstr " hide %(onoff)s" #: Mailman/Commands/cmd_set.py:199 +#, fuzzy msgid " duplicates %(onoff)s" msgstr " duplicates %(onoff)s" #: Mailman/Commands/cmd_set.py:203 +#, fuzzy msgid " reminders %(onoff)s" msgstr " reminders %(onoff)s" @@ -4270,6 +4396,7 @@ msgid "You did not give the correct password" msgstr "Falsches Passwort." #: Mailman/Commands/cmd_set.py:236 Mailman/Commands/cmd_set.py:283 +#, fuzzy msgid "Bad argument: %(arg)s" msgstr "Ungültiges Argument: %(arg)s" @@ -4358,6 +4485,7 @@ msgstr "" # Mailman/Gui/Digest.py:27 #: Mailman/Commands/cmd_subscribe.py:62 +#, fuzzy msgid "Bad digest specifier: %(arg)s" msgstr "Falsche digest-Angabe: %(arg)s" @@ -4366,6 +4494,7 @@ msgid "No valid address found to subscribe" msgstr "Keine gültige Adresse zum eintragen gefunden." #: Mailman/Commands/cmd_subscribe.py:113 +#, fuzzy msgid "" "The email address you supplied is banned from this mailing list.\n" "If you think this restriction is erroneous, please contact the list\n" @@ -4410,6 +4539,7 @@ msgstr "Diese Liste erlaubt nur Abonnements von Nachrichtensammlungen!" # Mailman/MailCommandHandler.py:641 #: Mailman/Commands/cmd_subscribe.py:146 +#, fuzzy msgid "" "Your subscription request has been forwarded to the list administrator\n" "at %(listowner)s for review." @@ -4448,6 +4578,7 @@ msgstr "" # Mailman/MailCommandHandler.py:359 Mailman/MailCommandHandler.py:365 # Mailman/MailCommandHandler.py:420 Mailman/MailCommandHandler.py:587 #: Mailman/Commands/cmd_unsubscribe.py:62 +#, fuzzy msgid "%(address)s is not a member of the %(listname)s mailing list" msgstr "%(address)s ist kein Mitglied der Liste %(listname)s." @@ -4717,6 +4848,7 @@ msgstr "Chinesisch (Taiwan)" # Mailman/Deliverer.py:42 #: Mailman/Deliverer.py:53 +#, fuzzy msgid "" "Note: Since this is a list of mailing lists, administrative\n" "notices like the password reminder will be sent to\n" @@ -4733,16 +4865,19 @@ msgstr " (Nachrichtensammlungsmodus)" # Mailman/Deliverer.py:67 #: Mailman/Deliverer.py:79 +#, fuzzy msgid "Welcome to the \"%(realname)s\" mailing list%(digmode)s" msgstr "Willkommen bei der \"%(realname)s\" Mailingliste %(digmode)s " # Mailman/Deliverer.py:76 #: Mailman/Deliverer.py:89 +#, fuzzy msgid "You have been unsubscribed from the %(realname)s mailing list" msgstr "Sie haben die Mailingliste \"%(realname)s\" abbestellt" # Mailman/Deliverer.py:103 #: Mailman/Deliverer.py:116 +#, fuzzy msgid "%(listfullname)s mailing list reminder" msgstr "%(listfullname)s Mailinglisten Erinnerung" @@ -4756,6 +4891,7 @@ msgid "Hostile subscription attempt detected" msgstr "Versuch unrechtmäßiger Listeneintragung entdeckt." #: Mailman/Deliverer.py:169 +#, fuzzy msgid "" "%(address)s was invited to a different mailing\n" "list, but in a deliberate malicious attempt they tried to confirm the\n" @@ -4769,6 +4905,7 @@ msgstr "" "Sie das. Von Ihrer Seite ist keine weitere Reaktion nötig. " #: Mailman/Deliverer.py:188 +#, fuzzy msgid "" "You invited %(address)s to your list, but in a\n" "deliberate malicious attempt, they tried to confirm the invitation to a\n" @@ -4783,6 +4920,7 @@ msgstr "" # Mailman/Deliverer.py:103 #: Mailman/Deliverer.py:221 +#, fuzzy msgid "%(listname)s mailing list probe message" msgstr "%(listname)s Mailinglisten Testnachricht " @@ -5010,8 +5148,8 @@ msgid "" " membership.\n" "\n" "

                You can control both the\n" -" number\n" +" number\n" " of reminders the member will receive and the\n" " Sie können sowohl die\n" -" Anzahl der Erinnerungen als auch dieAnzahl der Erinnerungen als auch " +"dieHäufigkeit einstellen, in der die Erinnerungen versendet " "werden.\n" "\n" @@ -5076,8 +5214,8 @@ msgstr "" "bestimmten Zeit,\n" " in der keine Bounces von einem Mitglied empfangen werden, wird " "der Bounce-Wert\n" -" zurückgesetzt.\n" +" zurückgesetzt.\n" " Mit dieser Einstellung können Sie steuern, wie schnell die " "Zustellung deaktiviert wird.\n" " Passen Sie dies an Ihre Liste an." @@ -5216,8 +5354,8 @@ msgid "" " No those messages too will get discarded. You may " "want\n" " to set up an\n" -" autoresponse\n" +" autoresponse\n" " message for email to the -owner and -admin address." msgstr "" "Obwohl die Bounce-Erkennung von Mailman sehr stabil arbeitet, ist\n" @@ -5244,8 +5382,9 @@ msgstr "" "Einstellung auf No\n" " gesetzt, werden diese E-Mails ebenfalls verworfen! Sie können " "einen\n" -" Autoresponder einsetzen, um diese E-Mails beantworten zu lassen!" +" Autoresponder einsetzen, um diese E-Mails " +"beantworten zu lassen!" #: Mailman/Gui/Bounce.py:147 msgid "" @@ -5316,6 +5455,7 @@ msgstr "" "über die Deaktivierung jedoch immer benachrichtigt." #: Mailman/Gui/Bounce.py:194 +#, fuzzy msgid "" "Bad value for %(property)s: %(val)s" @@ -5464,8 +5604,8 @@ msgid "" "Use this option to remove each message attachment that does\n" " not have a matching content type. Requirements and formats " "are\n" -" exactly like filter_mime_types.\n" "\n" "

                Note: if you add entries to this list but don't add\n" @@ -5475,8 +5615,8 @@ msgstr "" "Nutzen Sie diese Option um alle Nachrichten entfernen zu lassen, deren\n" " Inhalt nicht passend ist. Das Format dieses Parameters ist wie " "in\n" -" filter_mime_types.\n" +" filter_mime_types.\n" "\n" "

                Beachten Sie:Wenn Sie hier Einträge vornehmen und\n" " nicht auch den Eintrag multipart mit aufnehmen, wird " @@ -5586,6 +5726,7 @@ msgstr "" " Nur der Seitenadministrator kann diese Nachrichten verwerfen." #: Mailman/Gui/ContentFilter.py:171 +#, fuzzy msgid "Bad MIME type ignored: %(spectype)s" msgstr "Falscher MIME-Typ ignoriert: %(spectype)s" @@ -5711,6 +5852,7 @@ msgstr "" "wenn sie nicht leer ist?" #: Mailman/Gui/Digest.py:145 +#, fuzzy msgid "" "The next digest will be sent as volume\n" " %(volume)s, number %(number)s" @@ -5729,15 +5871,18 @@ msgid "There was no digest to send." msgstr "Es stand keine Sammlung zum Versand aus." #: Mailman/Gui/GUIBase.py:173 +#, fuzzy msgid "Invalid value for variable: %(property)s" msgstr "Ungültiger Wert: %(property)s" # Mailman/Cgi/admin.py:1169 #: Mailman/Gui/GUIBase.py:178 +#, fuzzy msgid "Bad email address for option %(property)s: %(error)s" msgstr "Ungültige E-Mail-Adresse für %(property)s: %(error)s" #: Mailman/Gui/GUIBase.py:204 +#, fuzzy msgid "" "The following illegal substitution variables were\n" " found in the %(property)s string:\n" @@ -5752,6 +5897,7 @@ msgstr "" "Problem nicht gelöst ist!" #: Mailman/Gui/GUIBase.py:218 +#, fuzzy msgid "" "Your %(property)s string appeared to\n" " have some correctable problems in its new value.\n" @@ -5856,8 +6002,8 @@ msgid "" "

                In order to split the list ownership duties into\n" " administrators and moderators, you must\n" " set a separate moderator password,\n" -" and also provide the email\n" +" and also provide the email\n" " addresses of the list moderators. Note that the field you\n" " are changing here specifies the list administrators." msgstr "" @@ -6269,13 +6415,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -6306,11 +6452,11 @@ msgstr "" "Reply-To: Header ahaben, da sie damit ihre gewünschte " "Rücksendeadresse übermitteln. Ein weiterer Grund ist, dass es ein " "modifizierter Reply-To: Header es viel schwieriger macht, private E-" -"Mailantworten zu verschicken. Siehe hier zu die Diskussion auf: Reply-To Munging " -"Considered Harmful. Gegenteilige Ansichten finden Sie auf Reply-To Munging " -"Considered Useful. Soweit dazu....\n" +"Mailantworten zu verschicken. Siehe hier zu die Diskussion auf: Reply-To " +"Munging Considered Harmful. Gegenteilige Ansichten finden Sie auf Reply-To " +"Munging Considered Useful. Soweit dazu....\n" "

                Einige Mailinglisten laufen mit eingeschränkten Nutzungsrechten, " "begleitet von einer parallelen Liste für Diskussionszwecke. Beispiele " "hierfür sind `patches' oder `checkin' Listen, auf denen Software-Änderungen " @@ -6330,8 +6476,8 @@ msgstr "Expliziter Reply-To: Header" msgid "" "This is the address set in the Reply-To: header\n" " when the reply_goes_to_list\n" +" href=\"?VARHELP=general/" +"reply_goes_to_list\">reply_goes_to_list\n" " option is set to Explicit address.\n" "\n" "

                There are many reasons not to introduce or override the\n" @@ -6339,13 +6485,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -6439,8 +6585,8 @@ msgid "" " member list addresses, but rather to the owner of those member\n" " lists. In that case, the value of this setting is appended to\n" " the member's account name for such notices. `-owner' is the\n" -" typical choice. This setting has no effect when \"umbrella_list" -"\"\n" +" typical choice. This setting has no effect when " +"\"umbrella_list\"\n" " is \"No\"." msgstr "" "Wenn die \"Regenschirm-Liste\" gesetzt ist, um anzuzeigen, dass diese Liste " @@ -7477,6 +7623,7 @@ msgstr "" " in Mailinglisten eintragen." #: Mailman/Gui/Privacy.py:104 +#, fuzzy msgid "" "This section allows you to configure subscription and\n" " membership exposure policy. You can also control whether this\n" @@ -7685,8 +7832,8 @@ msgid "" " either individually or as a group. Any\n" " posting from a non-member who is not explicitly accepted,\n" " rejected, or discarded, will have their posting filtered by the\n" -" general\n" +" general\n" " non-member rules.\n" "\n" "

                In the text boxes below, add one address per line; start the\n" @@ -7741,6 +7888,7 @@ msgid "By default, should new list member postings be moderated?" msgstr "Sollen die Beiträge neuer Listenmitglieder moderiert werden?" #: Mailman/Gui/Privacy.py:218 +#, fuzzy msgid "" "Each list member has a moderation flag which says\n" " whether messages from the list member can be posted directly " @@ -7793,8 +7941,8 @@ msgid "" "If a member posts this many times, within a period of time\n" " the member is automatically moderated. Use 0 to disable. " "See\n" -" member_verbosity_interval for details on the time " "period.\n" "\n" @@ -7812,8 +7960,8 @@ msgstr "" " Nachrichten verfasst, wird es automatisch auf \"moderiert\" " "umgeschaltet.\n" " Verwenden Sie 0 zum Abschalten. Unter\n" -" member_verbosity_interval\n" +" member_verbosity_interval\n" " finden Sie weitere Informationen über die Zeitspanne.\n" "\n" "

                Diese Einstellung ist dazu gedacht, Mitglieder, die sich " @@ -7912,6 +8060,7 @@ msgstr "" "Liste durchgelassen werden." #: Mailman/Gui/Privacy.py:290 +#, fuzzy msgid "" "Action to take when anyone posts to the\n" " list from a domain with a DMARC Reject%(quarantine)s Policy." @@ -8066,6 +8215,7 @@ msgstr "" " stärker wäre." #: Mailman/Gui/Privacy.py:353 +#, fuzzy msgid "" "Text to include in any\n" " Zurückweisungsnachricht beigefügt wird, wenn E-Mails\n" " von einer Domain gesendet werden, für die eine\n" " DMARC-Reject%(quarantine)s-Regel gilt." @@ -8374,6 +8524,7 @@ msgstr "" "Moderator der Liste weitergeleitet werden?" #: Mailman/Gui/Privacy.py:494 +#, fuzzy msgid "" "Text to include in any rejection notice to be sent to\n" " non-members who post to this list. This notice can include\n" @@ -8621,6 +8772,7 @@ msgstr "" "Regeln werden ignoriert." #: Mailman/Gui/Privacy.py:693 +#, fuzzy msgid "" "The header filter rule pattern\n" " '%(safepattern)s' is not a legal regular expression. This\n" @@ -8676,8 +8828,8 @@ msgid "" "

                The body of the message can also be optionally scanned for\n" " Subject: and Keywords: headers, as\n" " specified by the topics_bodylines_limit\n" +" href=\"?VARHELP=topics/" +"topics_bodylines_limit\">topics_bodylines_limit\n" " configuration variable." msgstr "" "Der Themenfilter kategorisiert jede eingehende E-Mail-Nachricht gemäss Optional kann auch der Nachrichtentext auf Vorkommen von Subject: und Keyword: Header durchsucht werden. Spezifizieren Sie " -"hierzu die Option topics_bodylines_limit." +"hierzu die Option topics_bodylines_limit." # Mailman/Gui/Topics.py:57 #: Mailman/Gui/Topics.py:72 @@ -8765,6 +8917,7 @@ msgstr "" "werden ignoriert." #: Mailman/Gui/Topics.py:135 +#, fuzzy msgid "" "The topic pattern '%(safepattern)s' is not a\n" " legal regular expression. It will be discarded." @@ -9004,6 +9157,7 @@ msgstr "Die Mailingliste %(listinfo_link)s wird betrieben von %(owner_link)s" # Mailman/HTMLFormatter.py:55 #: Mailman/HTMLFormatter.py:57 +#, fuzzy msgid "%(realname)s administrative interface" msgstr "%(realname)s Schnittstelle zur Administration" @@ -9014,6 +9168,7 @@ msgstr " (Authentifikation erforderlich)" # Mailman/HTMLFormatter.py:59 #: Mailman/HTMLFormatter.py:61 +#, fuzzy msgid "Overview of all %(hostname)s mailing lists" msgstr "Übersicht aller Mailinglisten auf %(hostname)s" @@ -9035,6 +9190,7 @@ msgid "; it was disabled by the list administrator" msgstr "; es wurde vom Listen-Administrator deaktiviert" #: Mailman/HTMLFormatter.py:146 +#, fuzzy msgid "" "; it was disabled due to excessive bounces. The\n" " last bounce was received on %(date)s" @@ -9048,6 +9204,7 @@ msgstr "; es wurde aus unbekannten Gr # Mailman/HTMLFormatter.py:133 #: Mailman/HTMLFormatter.py:151 +#, fuzzy msgid "Note: your list delivery is currently disabled%(reason)s." msgstr "" "Hinweis: die Zustellung von Nachrichten ist momentan abgeschaltet%(reason)s." @@ -9064,6 +9221,7 @@ msgstr "der Administrator der Liste" # Mailman/HTMLFormatter.py:138 #: Mailman/HTMLFormatter.py:157 +#, fuzzy msgid "" "

                %(note)s\n" "\n" @@ -9081,6 +9239,7 @@ msgstr "" "%(mailto)s in Verbindung." #: Mailman/HTMLFormatter.py:169 +#, fuzzy msgid "" "

                We have received some recent bounces from your\n" " address. Your current bounce score is %(score)s out of " @@ -9102,6 +9261,7 @@ msgstr "" # Mailman/HTMLFormatter.py:151 #: Mailman/HTMLFormatter.py:181 +#, fuzzy msgid "" "(Note - you are subscribing to a list of mailing lists, so the %(type)s " "notice will be sent to the admin address for your membership, %(addr)s.)

                " @@ -9152,6 +9312,7 @@ msgstr "" # Mailman/HTMLFormatter.py:176 #: Mailman/HTMLFormatter.py:208 +#, fuzzy msgid "" "This is %(also)sa private list, which means that the\n" " list of members is not available to non-members." @@ -9161,6 +9322,7 @@ msgstr "" # Mailman/HTMLFormatter.py:179 #: Mailman/HTMLFormatter.py:211 +#, fuzzy msgid "" "This is %(also)sa hidden list, which means that the\n" " list of members is available only to the list administrator." @@ -9170,6 +9332,7 @@ msgstr "" # Mailman/HTMLFormatter.py:182 #: Mailman/HTMLFormatter.py:214 +#, fuzzy msgid "" "This is %(also)sa public list, which means that the\n" " list of members list is available to everyone." @@ -9188,6 +9351,7 @@ msgstr "" # Mailman/HTMLFormatter.py:190 #: Mailman/HTMLFormatter.py:222 +#, fuzzy msgid "" "

                (Note that this is an umbrella list, intended to\n" " have only other mailing lists as members. Among other things,\n" @@ -9205,6 +9369,7 @@ msgstr "entweder" # Mailman/HTMLFormatter.py:224 #: Mailman/HTMLFormatter.py:256 +#, fuzzy msgid "" "To unsubscribe from %(realname)s, get a password reminder,\n" " or change your subscription options %(either)senter your " @@ -9242,6 +9407,7 @@ msgstr "" # Mailman/HTMLFormatter.py:244 #: Mailman/HTMLFormatter.py:277 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " members.)" @@ -9249,6 +9415,7 @@ msgstr "(%(which)s ist nur f # Mailman/HTMLFormatter.py:248 #: Mailman/HTMLFormatter.py:281 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " administrator.)" @@ -9323,6 +9490,7 @@ msgstr "Das aktuelle Archiv" # Mailman/Handlers/Acknowledge.py:62 #: Mailman/Handlers/Acknowledge.py:59 +#, fuzzy msgid "%(realname)s post acknowledgement" msgstr "%(realname)s Veröffentlichung bestätigt" @@ -9339,6 +9507,7 @@ msgstr "" "dass sie nicht korrekt entfernt werden kann.\n" #: Mailman/Handlers/CalcRecips.py:79 +#, fuzzy msgid "" "Your urgent message to the %(realname)s mailing list was not authorized for\n" "delivery. The original message as received by Mailman is attached.\n" @@ -9350,6 +9519,7 @@ msgstr "" # Mailman/Cgi/admin.py:355 #: Mailman/Handlers/CookHeaders.py:180 +#, fuzzy msgid "%(realname)s via %(lrn)s" msgstr "%(realname)s via %(lrn)s" @@ -9435,6 +9605,7 @@ msgstr "Nachricht k # Mailman/Handlers/Hold.py:83 #: Mailman/Handlers/Hold.py:84 +#, fuzzy msgid "" "Please do *not* post administrative requests to the mailing\n" "list. If you wish to subscribe, visit %(listurl)s or send a message with " @@ -9481,11 +9652,13 @@ msgstr "Mail geht an eine moderierte NNTP-Newsgruppe " # Mailman/Handlers/Hold.py:258 #: Mailman/Handlers/Hold.py:252 +#, fuzzy msgid "Your message to %(listname)s awaits moderator approval" msgstr "Ihre Nachricht an %(listname)s wartet auf Bestätigung des Moderators" # Mailman/Handlers/Hold.py:279 #: Mailman/Handlers/Hold.py:271 +#, fuzzy msgid "%(listname)s post from %(sender)s requires approval" msgstr "%(listname)s Veröffentlichung von %(sender)s erfordert Bestätigung" @@ -9536,6 +9709,7 @@ msgid "After content filtering, the message was empty" msgstr "Nach dem Filtern der Mail blieb kein Inhalt mehr übrig..." #: Mailman/Handlers/MimeDel.py:269 +#, fuzzy msgid "" "The attached message matched the %(listname)s mailing list's content " "filtering\n" @@ -9554,6 +9728,7 @@ msgid "Content filtered message notification" msgstr "Benachrichtigung über gefilterte E-Mail" #: Mailman/Handlers/Moderate.py:145 +#, fuzzy msgid "" "Your message has been rejected, probably because you are not subscribed to " "the\n" @@ -9579,10 +9754,11 @@ msgstr "Die angeh # Mailman/Handlers/Replybot.py:66 #: Mailman/Handlers/Replybot.py:75 +#, fuzzy msgid "Auto-response for your message to the \"%(realname)s\" mailing list" msgstr "" -"Automatische Beantwortung Ihrer Nachricht an die Mailingliste \"%(realname)s" -"\"" +"Automatische Beantwortung Ihrer Nachricht an die Mailingliste " +"\"%(realname)s\"" # Mailman/Handlers/Replybot.py:94 #: Mailman/Handlers/Replybot.py:108 @@ -9590,6 +9766,7 @@ msgid "The Mailman Replybot" msgstr "Der Mailman ReplyBot" #: Mailman/Handlers/Scrubber.py:208 +#, fuzzy msgid "" "An embedded and charset-unspecified text was scrubbed...\n" "Name: %(filename)s\n" @@ -9604,6 +9781,7 @@ msgid "HTML attachment scrubbed and removed" msgstr "Ein Dateianhang mit HTML-Daten wurde abgetrennt und entfernt" #: Mailman/Handlers/Scrubber.py:234 Mailman/Handlers/Scrubber.py:259 +#, fuzzy msgid "" "An HTML attachment was scrubbed...\n" "URL: %(url)s\n" @@ -9627,6 +9805,7 @@ msgid "unknown sender" msgstr "unbekannter Sender" #: Mailman/Handlers/Scrubber.py:276 +#, fuzzy msgid "" "An embedded message was scrubbed...\n" "From: %(who)s\n" @@ -9643,6 +9822,7 @@ msgstr "" "URL: %(url)s\n" #: Mailman/Handlers/Scrubber.py:308 +#, fuzzy msgid "" "A non-text attachment was scrubbed...\n" "Name: %(filename)s\n" @@ -9659,6 +9839,7 @@ msgstr "" "URL : %(url)s\n" #: Mailman/Handlers/Scrubber.py:347 +#, fuzzy msgid "Skipped content of type %(partctype)s\n" msgstr "Weggelassener Inhalt vom Typ %(partctype)s\n" @@ -9667,10 +9848,12 @@ msgid "-------------- next part --------------\n" msgstr "-------------- nächster Teil --------------\n" #: Mailman/Handlers/SpamDetect.py:64 +#, fuzzy msgid "Header matched regexp: %(pattern)s" msgstr "Header stimmt mit regexp überein (passte auf %(pattern)s)" #: Mailman/Handlers/SpamDetect.py:127 +#, fuzzy msgid "" "You are not allowed to post to this mailing list From: a domain which\n" "publishes a DMARC policy of reject or quarantine, and your message has been\n" @@ -9690,6 +9873,7 @@ msgstr "Nachricht wurde durch Filter-Regeln blockiert" # Mailman/Handlers/ToDigest.py:148 #: Mailman/Handlers/ToDigest.py:173 +#, fuzzy msgid "%(realname)s Digest, Vol %(volume)d, Issue %(issue)d" msgstr "%(realname)s Nachrichtensammlung, Band %(volume)d, Eintrag %(issue)d" @@ -9734,6 +9918,7 @@ msgstr "Ende " # Mailman/ListAdmin.py:257 #: Mailman/ListAdmin.py:309 +#, fuzzy msgid "Posting of your message titled \"%(subject)s\"" msgstr "Veröffentlichung Ihrer Nachricht betreffend \"%(subject)s\"" @@ -9748,6 +9933,7 @@ msgstr "Weiterleitung der moderierten Nachricht " # Mailman/ListAdmin.py:344 #: Mailman/ListAdmin.py:405 +#, fuzzy msgid "New subscription request to list %(realname)s from %(addr)s" msgstr "Neuer Abonnementantrag für die Liste %(realname)s von %(addr)s" @@ -9763,6 +9949,7 @@ msgstr "durch Admin Kontrolle" # Mailman/Cgi/confirm.py:345 #: Mailman/ListAdmin.py:466 +#, fuzzy msgid "New unsubscription request from %(realname)s by %(addr)s" msgstr "Neuer Abonnement-Antrag für Liste %(realname)s von %(addr)s" @@ -9778,11 +9965,13 @@ msgstr "Urspr # Mailman/ListAdmin.py:402 #: Mailman/ListAdmin.py:526 +#, fuzzy msgid "Request to mailing list %(realname)s rejected" msgstr "Die Aufforderung an die Mailingliste %(realname)s wurde zurückgewiesen" # Mailman/MTA/Manual.py:38 #: Mailman/MTA/Manual.py:66 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been created via the through-the-web\n" "interface. In order to complete the activation of this mailing list, the\n" @@ -9815,16 +10004,19 @@ msgstr "" # Mailman/Handlers/Replybot.py:67 #: Mailman/MTA/Manual.py:82 +#, fuzzy msgid "## %(listname)s mailing list" msgstr "## %(listname)s Mailingliste" # Mailman/MTA/Manual.py:66 #: Mailman/MTA/Manual.py:99 +#, fuzzy msgid "Mailing list creation request for list %(listname)s" msgstr "Anforderung zur Neuanlage der Mailingliste %(listname)s" # Mailman/MTA/Manual.py:81 #: Mailman/MTA/Manual.py:113 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been removed via the through-the-web\n" "interface. In order to complete the de-activation of this mailing list, " @@ -9846,6 +10038,7 @@ msgstr "" # Mailman/MTA/Manual.py:91 #: Mailman/MTA/Manual.py:123 +#, fuzzy msgid "" "\n" "To finish removing your mailing list, you must edit your /etc/aliases (or\n" @@ -9865,15 +10058,18 @@ msgstr "" # Mailman/MTA/Manual.py:109 #: Mailman/MTA/Manual.py:142 +#, fuzzy msgid "Mailing list removal request for list %(listname)s" msgstr "Löschanforderung für Liste %(listname)s" #: Mailman/MTA/Postfix.py:442 +#, fuzzy msgid "checking permissions on %(file)s" msgstr "überprüfe Zugriffsrechte von %(file)s" # Mailman/MTA/Postfix.py:232 #: Mailman/MTA/Postfix.py:452 +#, fuzzy msgid "%(file)s permissions must be 0664 (got %(octmode)s)" msgstr "%(file)s Zugriffsrechte sollten 0664 sein (ist aber %(octmode)s)" @@ -9892,42 +10088,50 @@ msgstr "(korrigiere)" # Mailman/MTA/Postfix.py:241 #: Mailman/MTA/Postfix.py:470 +#, fuzzy msgid "checking ownership of %(dbfile)s" msgstr "überprüfe Eigentümer von %(dbfile)s" # Mailman/MTA/Postfix.py:249 #: Mailman/MTA/Postfix.py:478 +#, fuzzy msgid "%(dbfile)s owned by %(owner)s (must be owned by %(user)s" msgstr "%(dbfile)s ist Eigentum von %(owner)s (sollte aber %(user)s gehören)" # Mailman/MTA/Postfix.py:232 #: Mailman/MTA/Postfix.py:491 +#, fuzzy msgid "%(dbfile)s permissions must be 0664 (got %(octmode)s)" msgstr "%(dbfile)s Zugriffsrechte sollten 0664 sein (sind aber %(octmode)s) " # Mailman/Deliverer.py:76 #: Mailman/MailList.py:219 +#, fuzzy msgid "Your confirmation is required to join the %(listname)s mailing list" msgstr "Ihre Bestätigung ist nötig um die Liste %(listname)s zu abonnieren." # Mailman/Deliverer.py:76 #: Mailman/MailList.py:230 +#, fuzzy msgid "Your confirmation is required to leave the %(listname)s mailing list" msgstr "Ihre Bestätigung ist nötig um die Liste %(listname)s abzubestellen." # Mailman/MailList.py:614 Mailman/MailList.py:886 #: Mailman/MailList.py:999 Mailman/MailList.py:1492 +#, fuzzy msgid " from %(remote)s" msgstr " von %(remote)s" # Mailman/MailList.py:649 #: Mailman/MailList.py:1043 +#, fuzzy msgid "subscriptions to %(realname)s require moderator approval" msgstr "" "Das Abonnieren von %(realname)s erfordert die Bestätigung des Moderators" # Mailman/MailList.py:711 bin/add_members:258 #: Mailman/MailList.py:1125 bin/add_members:299 +#, fuzzy msgid "%(realname)s subscription notification" msgstr "%(realname)s Abonnierungsbenachrichtigung" @@ -9938,11 +10142,13 @@ msgstr "Abbestellungen erfordern die Best # Mailman/MailList.py:739 #: Mailman/MailList.py:1166 +#, fuzzy msgid "%(realname)s unsubscribe notification" msgstr "%(realname)s Abbestellungbenachrichtigung" # Mailman/MailList.py:739 #: Mailman/MailList.py:1328 +#, fuzzy msgid "%(realname)s address change notification" msgstr "Adressänderungsbenachrichtigung für %(realname)s" @@ -9958,6 +10164,7 @@ msgstr "durch Web Best # Mailman/MailList.py:860 #: Mailman/MailList.py:1396 +#, fuzzy msgid "subscriptions to %(name)s require administrator approval" msgstr "" "Das Abonnieren von %(name)s erfordert die Bestätigung des Aministrators" @@ -9977,6 +10184,7 @@ msgid "Last autoresponse notification for today" msgstr "Letzte automatische Benachrichtigung für heute" #: Mailman/Queue/BounceRunner.py:360 +#, fuzzy msgid "" "The attached message was received as a bounce, but either the bounce format\n" "was not recognized, or no member addresses could be extracted from it. " @@ -10068,6 +10276,7 @@ msgstr "Original Nachricht durch Mailmain Konfiguration unterdr # Mailman/htmlformat.py:611 #: Mailman/htmlformat.py:675 +#, fuzzy msgid "Delivered by Mailman
                version %(version)s" msgstr "Zugestellt von Mailman
                version %(version)s" @@ -10187,6 +10396,7 @@ msgid "Server Local Time" msgstr "Lokale Serverzeit" #: Mailman/i18n.py:180 +#, fuzzy msgid "" "%(wday)s %(mon)s %(day)2i %(hh)02i:%(mm)02i:%(ss)02i %(tzname)s %(year)04i" msgstr "" @@ -10299,6 +10509,7 @@ msgstr "" # Mailman/Cgi/admin.py:1228 #: bin/add_members:162 bin/add_members:172 +#, fuzzy msgid "Already a member: %(member)s" msgstr "Ist bereits Mitglied: %(member)s" @@ -10309,29 +10520,35 @@ msgstr "Ung # Mailman/Cgi/admin.py:1232 Mailman/Cgi/admin.py:1235 #: bin/add_members:180 +#, fuzzy msgid "Bad/Invalid email address: %(member)s" msgstr "Ungültige E-Mail-Adresse: %(member)s" # Mailman/Cgi/admin.py:1238 #: bin/add_members:182 +#, fuzzy msgid "Hostile address (illegal characters): %(member)s" msgstr "Vermutlich feindliche Adresse (ungültige Zeichen): %(member)s" # Mailman/Cgi/admin.py:1281 #: bin/add_members:185 +#, fuzzy msgid "Invited: %(member)s" msgstr "Eingeladen: %(member)s" # Mailman/Cgi/admin.py:1281 #: bin/add_members:187 +#, fuzzy msgid "Subscribed: %(member)s" msgstr "Abonniert: %(member)s" #: bin/add_members:237 +#, fuzzy msgid "Bad argument to -w/--welcome-msg: %(arg)s" msgstr "Ungültiges Argument für -w/--welcome-msg: %(arg)s" #: bin/add_members:244 +#, fuzzy msgid "Bad argument to -a/--admin-notify: %(arg)s" msgstr "Ungültiges Argument für -a/--admin-notify: %(arg)s" @@ -10353,6 +10570,7 @@ msgstr "Einstellung invite-msg-file braucht --invite." #: bin/add_members:261 bin/config_list:110 bin/export.py:271 bin/find_member:97 #: bin/inject:91 bin/list_admins:90 bin/list_members:252 bin/sync_members:222 #: cron/bumpdigests:86 +#, fuzzy msgid "No such list: %(listname)s" msgstr "Liste nicht vorhanden: %(listname)s" @@ -10363,6 +10581,7 @@ msgid "Nothing to do." msgstr "Nichts zu tun." #: bin/arch:19 +#, fuzzy msgid "" "Rebuild a list's archive.\n" "\n" @@ -10462,6 +10681,7 @@ msgstr "Der Name der Liste ist erforderlich" # Mailman/Cgi/rmlist.py:60 Mailman/Cgi/roster.py:55 # Mailman/Cgi/subscribe.py:57 #: bin/arch:143 bin/change_pw:107 bin/config_list:257 +#, fuzzy msgid "" "No such list \"%(listname)s\"\n" "%(e)s" @@ -10470,10 +10690,12 @@ msgstr "" "%(e)s" #: bin/arch:168 +#, fuzzy msgid "Cannot open mbox file %(mbox)s: %(msg)s" msgstr "Kann die mbox-Datei %(mbox)s nicht öffnen. Grund: %(msg)s" #: bin/b4b5-archfix:19 +#, fuzzy msgid "" "Fix the MM2.1b4 archives.\n" "\n" @@ -10617,6 +10839,7 @@ msgstr "" " Diese Hilfe zeigen und beenden.\n" #: bin/change_pw:145 +#, fuzzy msgid "Bad arguments: %(strargs)s" msgstr "Ungültiges Argument: %(strargs)s" @@ -10626,15 +10849,18 @@ msgid "Empty list passwords are not allowed" msgstr "Leere Listen-Passwörter sind nicht erlaubt" #: bin/change_pw:181 +#, fuzzy msgid "New %(listname)s password: %(notifypassword)s" msgstr "Neues Passwort der Liste %(listname)s: %(notifypassword)s" # Mailman/Cgi/create.py:307 #: bin/change_pw:190 +#, fuzzy msgid "Your new %(listname)s list password" msgstr "Ihr neues Passwort für die Mailingliste %(listname)s" #: bin/change_pw:191 +#, fuzzy msgid "" "The site administrator at %(hostname)s has changed the password for your\n" "mailing list %(listname)s. It is now\n" @@ -10661,6 +10887,7 @@ msgstr "" " %(adminurl)s\n" #: bin/check_db:19 +#, fuzzy msgid "" "Check a list's config database file for integrity.\n" "\n" @@ -10739,10 +10966,12 @@ msgid "List:" msgstr "Liste:" #: bin/check_db:148 +#, fuzzy msgid " %(file)s: okay" msgstr " %(file)s: in Ordnung" #: bin/check_perms:20 +#, fuzzy msgid "" "Check the permissions for the Mailman installation.\n" "\n" @@ -10764,42 +10993,52 @@ msgstr "" "\n" #: bin/check_perms:110 +#, fuzzy msgid " checking gid and mode for %(path)s" msgstr "überprüfe GID und Modus für %(path)s" #: bin/check_perms:122 +#, fuzzy msgid "%(path)s bad group (has: %(groupname)s, expected %(MAILMAN_GROUP)s)" msgstr "%(path)s falsche GID (ist: %(groupname)s, soll: %(MAILMAN_GROUP)s)" #: bin/check_perms:151 +#, fuzzy msgid "directory permissions must be %(octperms)s: %(path)s" msgstr "Verzeichnisrechte müssen %(octperms)s sein für: %(path)s" #: bin/check_perms:160 +#, fuzzy msgid "source perms must be %(octperms)s: %(path)s" msgstr "Verzeichnisrechte müssen %(octperms)s sein: %(path)s" #: bin/check_perms:171 +#, fuzzy msgid "article db files must be %(octperms)s: %(path)s" msgstr "Dateirechte müssen %(octperms)s sein: %(path)s" #: bin/check_perms:183 +#, fuzzy msgid "checking mode for %(prefix)s" msgstr "überprüfe Modus für %(prefix)s" #: bin/check_perms:193 +#, fuzzy msgid "WARNING: directory does not exist: %(d)s" msgstr "WARNUNG: Verzeichnis existiert nicht: %(d)s" #: bin/check_perms:197 +#, fuzzy msgid "directory must be at least 02775: %(d)s" msgstr "Verzeichnisrechte müssen midestens 02775 betragen: %(d)s" #: bin/check_perms:209 +#, fuzzy msgid "checking perms on %(private)s" msgstr "überprüfe Dateirechte von %(private)s" #: bin/check_perms:214 +#, fuzzy msgid "%(private)s must not be other-readable" msgstr "%(private)s darf nicht weltweit lesbar sein" @@ -10825,6 +11064,7 @@ msgid "mbox file must be at least 0660:" msgstr "mbox-Dateirechte müssen mindestens 0660 sein:" #: bin/check_perms:263 +#, fuzzy msgid "%(dbdir)s \"other\" perms must be 000" msgstr "%(dbdir)s Dateirechte für 'Rest der Welt' müssen 000 sein" @@ -10833,26 +11073,32 @@ msgid "checking cgi-bin permissions" msgstr "überprüfe cgi-bin Dateirechte" #: bin/check_perms:278 +#, fuzzy msgid " checking set-gid for %(path)s" msgstr "überprüfe set-gid Dateirechte für %(path)s" #: bin/check_perms:282 +#, fuzzy msgid "%(path)s must be set-gid" msgstr "%(path)s erfordert set-gid Dateirechte" #: bin/check_perms:292 +#, fuzzy msgid "checking set-gid for %(wrapper)s" msgstr "überprüfe set-gid Dateirechte für %(wrapper)s" #: bin/check_perms:296 +#, fuzzy msgid "%(wrapper)s must be set-gid" msgstr "%(wrapper)s erfordert set-gid Dateirechte" #: bin/check_perms:306 +#, fuzzy msgid "checking permissions on %(pwfile)s" msgstr "überprüfe Zugriffsrechte von: %(pwfile)s" #: bin/check_perms:315 +#, fuzzy msgid "%(pwfile)s permissions must be exactly 0640 (got %(octmode)s)" msgstr "%(pwfile)s Zugriffsrechte müssen exakt 0640 sein (sind %(octmode)s)." @@ -10861,10 +11107,12 @@ msgid "checking permissions on list data" msgstr "überprüfe Zugriffsrechte der Listendaten" #: bin/check_perms:348 +#, fuzzy msgid " checking permissions on: %(path)s" msgstr "überprüfe Zugriffsrechte von: %(path)s" #: bin/check_perms:356 +#, fuzzy msgid "file permissions must be at least 660: %(path)s" msgstr "Dateizugriffsrechte müssen mindestens '660' sein: %(path)s" @@ -10949,6 +11197,7 @@ msgid "Unix-From line changed: %(lineno)d" msgstr "Unix-From Zeile geändert: %(lineno)d" #: bin/cleanarch:111 +#, fuzzy msgid "Bad status number: %(arg)s" msgstr "Ungültiges Argument: %(arg)s" @@ -11102,10 +11351,12 @@ msgstr " originale E-Mail-Adresse wurde entfernt: " # Mailman/Cgi/create.py:170 #: bin/clone_member:202 +#, fuzzy msgid "Not a valid email address: %(toaddr)s" msgstr "Ungültige E-Mail-Adresse: %(toaddr)s" #: bin/clone_member:215 +#, fuzzy msgid "" "Error opening list \"%(listname)s\", skipping.\n" "%(e)s" @@ -11216,6 +11467,7 @@ msgstr "" "\n" #: bin/config_list:118 +#, fuzzy msgid "" "# -*- python -*-\n" "# -*- coding: %(charset)s -*-\n" @@ -11237,23 +11489,28 @@ msgid "legal values are:" msgstr "zulässige Werte sind: " #: bin/config_list:270 +#, fuzzy msgid "attribute \"%(k)s\" ignored" msgstr "Attribut \"%(k)s\" wurde ignoriert" #: bin/config_list:273 +#, fuzzy msgid "attribute \"%(k)s\" changed" msgstr "Attribut \"%(k)s\" wurde geändert" #: bin/config_list:279 +#, fuzzy msgid "Non-standard property restored: %(k)s" msgstr "Nicht-Standard-Einstellung wurde wiederhergestellt: %(k)s" #: bin/config_list:288 +#, fuzzy msgid "Invalid value for property: %(k)s" msgstr "Ungültiger Wert für %(k)s" # Mailman/Cgi/admin.py:1169 #: bin/config_list:291 +#, fuzzy msgid "Bad email address for option %(k)s: %(v)s" msgstr "Ungültige E-Mail-Adresse für %(k)s: %(v)s" @@ -11322,10 +11579,12 @@ msgstr "" " Keine Ausgabe von Meldungen.\n" #: bin/discard:94 +#, fuzzy msgid "Ignoring non-held message: %(f)s" msgstr "Ignoriere nichtgehaltene Nachricht: %(f)s" #: bin/discard:100 +#, fuzzy msgid "Ignoring held msg w/bad id: %(f)s" msgstr "Ignoriere gehaltene Nachricht mit falscher ID: %(f)s" @@ -11335,10 +11594,12 @@ msgstr "Ignoriere gehaltene Nachricht mit falscher ID: %(f)s" # Mailman/Cgi/rmlist.py:60 Mailman/Cgi/roster.py:55 # Mailman/Cgi/subscribe.py:57 #: bin/discard:112 +#, fuzzy msgid "Discarded held msg #%(id)s for list %(listname)s" msgstr "Verwerfe gehaltene Nachricht #%(id)s für Liste %(listname)s" #: bin/dumpdb:19 +#, fuzzy msgid "" "Dump the contents of any Mailman `database' file.\n" "\n" @@ -11411,6 +11672,7 @@ msgid "No filename given." msgstr "Dateiname nicht angegeben." #: bin/dumpdb:108 +#, fuzzy msgid "Bad arguments: %(pargs)s" msgstr "Ungültiges Argument: %(pargs)s" @@ -11419,14 +11681,17 @@ msgid "Please specify either -p or -m." msgstr "Bitte entweder -p oder -m angeben." #: bin/dumpdb:133 +#, fuzzy msgid "[----- start %(typename)s file -----]" msgstr "[----- start %(typename)s file -----]" #: bin/dumpdb:139 +#, fuzzy msgid "[----- end %(typename)s file -----]" msgstr "[----- end %(typename)s file -----]" #: bin/dumpdb:142 +#, fuzzy msgid "<----- start object %(cnt)s ----->" msgstr "<----- start object %(cnt)s ----->" @@ -11647,6 +11912,7 @@ msgid "Setting web_page_url to: %(web_page_url)s" msgstr "Setze web_page_url auf: %(web_page_url)s" #: bin/fix_url.py:88 +#, fuzzy msgid "Setting host_name to: %(mailhost)s" msgstr "Setze host_name auf: %(mailhost)s" @@ -11684,6 +11950,7 @@ msgstr "" " Diese Hilfe zeigen und beenden.\n" #: bin/genaliases:84 +#, fuzzy msgid "genaliases can't do anything useful with mm_cfg.MTA = %(mta)s." msgstr "genaliases kann mit mm_cfg.MTA = %(mta)s nichts anfangen." @@ -11739,6 +12006,7 @@ msgstr "" "Ist keine Datei angegeben, so wird die Standardeingabe verwendet.\n" #: bin/inject:84 +#, fuzzy msgid "Bad queue directory: %(qdir)s" msgstr "Falsches queue-Verzeichnis: %(qdir)s" @@ -11748,6 +12016,7 @@ msgid "A list name is required" msgstr "Ein Name der Liste ist erforderlich" #: bin/list_admins:20 +#, fuzzy msgid "" "List all the owners of a mailing list.\n" "\n" @@ -11794,10 +12063,12 @@ msgstr "" "sollen. Es können mehrere Listen auf der Kommandozeile angegeben werden.\n" #: bin/list_admins:97 +#, fuzzy msgid "List: %(listname)s, \tOwners: %(owners)s" msgstr "Liste: %(listname)s, \tEigentümer: %(owners)s" #: bin/list_lists:19 +#, fuzzy msgid "" "List all mailing lists.\n" "\n" @@ -11859,6 +12130,7 @@ msgid "matching mailing lists found:" msgstr "Passende Mailinglisten gefunden: " #: bin/list_members:19 +#, fuzzy msgid "" "List all the members of a mailing list.\n" "\n" @@ -11946,8 +12218,8 @@ msgstr "" " --nomail[=why] / -n [why]\n" " Listet Mitglieder, deren Account deaktiviert ist. Optional können " "Sie\n" -" nach der Ursache der Deaktivierung filtern: \"byadmin\", \"byuser" -"\", \n" +" nach der Ursache der Deaktivierung filtern: \"byadmin\", " +"\"byuser\", \n" " \"bybounce\", oder \"unknown\". Sie können auch \"enabled\"\n" " angeben, das listet alle normalen Nutzer auf.\n" "\n" @@ -11983,11 +12255,13 @@ msgstr "" "welcher Katagorie ein Benutzer Mitglied ist.\n" #: bin/list_members:198 +#, fuzzy msgid "Bad --nomail option: %(why)s" msgstr "Falsche --nomail Option: %(why)s" # Mailman/Gui/Digest.py:27 #: bin/list_members:209 +#, fuzzy msgid "Bad --digest option: %(kind)s" msgstr "Falsche --digest Option: %(kind)s" @@ -12001,6 +12275,7 @@ msgid "Could not open file for writing:" msgstr "Logdatei konnte nicht zum Schreiben geöffnet werden: " #: bin/list_owners:20 +#, fuzzy msgid "" "List the owners of a mailing list, or all mailing lists.\n" "\n" @@ -12056,6 +12331,7 @@ msgstr "" "für diese Installation von Mailman an. Benötigt python 2." #: bin/mailmanctl:20 +#, fuzzy msgid "" "Primary start-up and shutdown script for Mailman's qrunner daemon.\n" "\n" @@ -12218,6 +12494,7 @@ msgstr "" " reopen - Schliessen der Logdateien, gefolgt von einem Neuöffnen.\n" #: bin/mailmanctl:152 +#, fuzzy msgid "PID unreadable in: %(pidfile)s" msgstr "PID in %(pidfile)s nicht lesbar" @@ -12226,6 +12503,7 @@ msgid "Is qrunner even running?" msgstr "Läuft qrunner überhaupt?" #: bin/mailmanctl:160 +#, fuzzy msgid "No child with pid: %(pid)s" msgstr "Kein Kindprozess mit der PID %(pid)s vorhanden" @@ -12253,6 +12531,7 @@ msgstr "" "Rufen Sie das das Programm mailmanctl mit der -s Option nochmals auf.\n" #: bin/mailmanctl:233 +#, fuzzy msgid "" "The master qrunner lock could not be acquired, because it appears as if " "some\n" @@ -12282,10 +12561,12 @@ msgstr "" # Mailman/Cgi/create.py:101 Mailman/Cgi/create.py:174 bin/newlist:122 # bin/newlist:154 #: bin/mailmanctl:280 cron/mailpasswds:119 +#, fuzzy msgid "Site list is missing: %(sitelistname)s" msgstr "Vermisse die Mailingliste: %(sitelistname)s" #: bin/mailmanctl:305 +#, fuzzy msgid "Run this program as root or as the %(name)s user, or use -u." msgstr "" "Führen Sie dieses Programm als Benutzer root, oder %(name)s aus,\n" @@ -12297,6 +12578,7 @@ msgid "No command given." msgstr "Keine Anweisung angegeben." #: bin/mailmanctl:339 +#, fuzzy msgid "Bad command: %(command)s" msgstr "Unverständliche Anweisung: %(command)s" @@ -12321,6 +12603,7 @@ msgid "Starting Mailman's master qrunner." msgstr "Starte Mailman's qrunner-Masterprozess." #: bin/mmsitepass:19 +#, fuzzy msgid "" "Set the site password, prompting from the terminal.\n" "\n" @@ -12378,6 +12661,7 @@ msgid "list creator" msgstr "Administrator" #: bin/mmsitepass:86 +#, fuzzy msgid "New %(pwdesc)s password: " msgstr "Neues %(pwdesc)s Passwort: " @@ -12465,6 +12749,7 @@ msgid "Return the generated output." msgstr "Gibt die erzeugte Ausgabe zurück." #: bin/newlist:20 +#, fuzzy msgid "" "Create a new, unpopulated mailing list.\n" "\n" @@ -12641,6 +12926,7 @@ msgstr "" "Bitte beachten Sie, das Listennamen in Kleinbuchstaben umgewandelt werden.\n" #: bin/newlist:162 +#, fuzzy msgid "Unknown language: %(lang)s" msgstr "Unbekannte Sprache: %(lang)s" @@ -12654,6 +12940,7 @@ msgstr "E-Mail-Adresse des Listenverwalters: " # Mailman/Cgi/create.py:307 #: bin/newlist:193 +#, fuzzy msgid "Initial %(listname)s password: " msgstr "Erstmaliges Passwort für die Liste %(listname)s: " @@ -12664,18 +12951,20 @@ msgstr "Das Passwort f #: bin/newlist:220 msgid "" -" - owner addresses need to be fully-qualified names like \"owner@example.com" -"\", not just \"owner\"." +" - owner addresses need to be fully-qualified names like \"owner@example." +"com\", not just \"owner\"." msgstr "" " - die Adressen des Listenbesitzers müssen gültig sein wie bspw. " "\"owner@example.com\" , nicht einfach \"Besitzer\"." #: bin/newlist:244 +#, fuzzy msgid "Hit enter to notify %(listname)s owner..." msgstr "" "Enter drücken, um den Besitzer der Liste %(listname)s zu benachrichtigen..." #: bin/qrunner:20 +#, fuzzy msgid "" "Run one or more qrunners, once or repeatedly.\n" "\n" @@ -12802,6 +13091,7 @@ msgstr "" "Betriebs aufgerufen.\n" #: bin/qrunner:179 +#, fuzzy msgid "%(name)s runs the %(runnername)s qrunner" msgstr "%(name)s startet den %(runnername)s qrunner" @@ -12815,6 +13105,7 @@ msgid "No runner name given." msgstr "Kein runner-Name angegeben." #: bin/rb-archfix:21 +#, fuzzy msgid "" "Reduce disk space usage for Pipermail archives.\n" "\n" @@ -12957,20 +13248,24 @@ msgstr "" " adresse1 ... Adresse(n), die entfernt werden sollen.\n" #: bin/remove_members:156 +#, fuzzy msgid "Could not open file for reading: %(filename)s." msgstr "Kann Datei %(filename)s nicht zum Lesen öffnen." #: bin/remove_members:163 +#, fuzzy msgid "Error opening list %(listname)s... skipping." msgstr "Fehler beim Öffnen der Liste %(listname)s, wird übersprungen." # Mailman/Cgi/options.py:93 #: bin/remove_members:173 +#, fuzzy msgid "No such member: %(addr)s" msgstr " %(addr)s ist nicht Abonnent." # Mailman/MTA/Manual.py:109 #: bin/remove_members:178 +#, fuzzy msgid "User `%(addr)s' removed from list: %(listname)s." msgstr "Nutzer `%(addr)s' von der Liste %(listname)s entfernt." @@ -13010,10 +13305,12 @@ msgstr "" # Mailman/MTA/Manual.py:109 #: bin/reset_pw.py:77 +#, fuzzy msgid "Changing passwords for list: %(listname)s" msgstr "Ändere Passwort für die Liste %(listname)s" #: bin/reset_pw.py:83 +#, fuzzy msgid "New password for member %(member)40s: %(randompw)s" msgstr "Neues Passwort für das Mitglied %(member)40s: %(randompw)s" @@ -13057,10 +13354,12 @@ msgstr "" "\n" #: bin/rmlist:73 bin/rmlist:76 +#, fuzzy msgid "Removing %(msg)s" msgstr "Entferne %(msg)s" #: bin/rmlist:81 +#, fuzzy msgid "%(listname)s %(msg)s not found as %(filename)s" msgstr "%(listname)s %(msg)s nicht als %(filename)s gefunden" @@ -13070,6 +13369,7 @@ msgstr "%(listname)s %(msg)s nicht als %(filename)s gefunden" # Mailman/Cgi/rmlist.py:60 Mailman/Cgi/roster.py:55 # Mailman/Cgi/subscribe.py:57 #: bin/rmlist:105 +#, fuzzy msgid "No such list (or list already deleted): %(listname)s" msgstr "Liste nicht vorhanden (oder bereits gelöscht): %(listname)s" @@ -13079,6 +13379,7 @@ msgstr "Liste nicht vorhanden (oder bereits gel # Mailman/Cgi/rmlist.py:60 Mailman/Cgi/roster.py:55 # Mailman/Cgi/subscribe.py:57 #: bin/rmlist:108 +#, fuzzy msgid "No such list: %(listname)s. Removing its residual archives." msgstr "Liste nicht vorhanden: %(listname)s. Entferne verbliebene Archive." @@ -13140,6 +13441,7 @@ msgstr "" "Beispiel: show_qfiles qfiles/shunt/*.pck\n" #: bin/sync_members:19 +#, fuzzy msgid "" "Synchronize a mailing list's membership with a flat file.\n" "\n" @@ -13267,6 +13569,7 @@ msgstr "" " Erforderlich. Liste, mit der synchronisiert werden soll.\n" #: bin/sync_members:115 +#, fuzzy msgid "Bad choice: %(yesno)s" msgstr "Schlecht gewählt: %(yesno)s" @@ -13284,6 +13587,7 @@ msgid "No argument to -f given" msgstr "Kein Wert für Parameter -f angegeben" #: bin/sync_members:172 +#, fuzzy msgid "Illegal option: %(opt)s" msgstr "Unzulässige Option: %(opt)s" @@ -13297,6 +13601,7 @@ msgid "Must have a listname and a filename" msgstr "Benötige Listenname und Dateiname" #: bin/sync_members:191 +#, fuzzy msgid "Cannot read address file: %(filename)s: %(msg)s" msgstr "Kann Adressdatei nicht lesen: %(filename)s: %(msg)s" @@ -13314,14 +13619,17 @@ msgid "You must fix the preceding invalid addresses first." msgstr "Korrigieren Sie zuerst die vorangehende ungültige Adresse." #: bin/sync_members:264 +#, fuzzy msgid "Added : %(s)s" msgstr "Hinzugefügt: %(s)s" #: bin/sync_members:289 +#, fuzzy msgid "Removed: %(s)s" msgstr "Entfernt: %(s)s" #: bin/transcheck:19 +#, fuzzy msgid "" "\n" "Check a given Mailman translation, making sure that variables and\n" @@ -13399,6 +13707,7 @@ msgid "scan the po file comparing msgids with msgstrs" msgstr "durchsuche die PO-Datei, um msgids mit msgstrs zu vergleichen" #: bin/unshunt:20 +#, fuzzy msgid "" "Move a message from the shunt queue to the original queue.\n" "\n" @@ -13430,6 +13739,7 @@ msgstr "" "Daten in dieser Queue.\n" #: bin/unshunt:85 +#, fuzzy msgid "" "Cannot unshunt message %(filebase)s, skipping:\n" "%(e)s" @@ -13438,6 +13748,7 @@ msgstr "" "%(e)s" #: bin/update:20 +#, fuzzy msgid "" "Perform all necessary upgrades.\n" "\n" @@ -13475,15 +13786,18 @@ msgstr "" # Mailman/Cgi/create.py:101 Mailman/Cgi/create.py:174 bin/newlist:122 # bin/newlist:154 #: bin/update:107 +#, fuzzy msgid "Fixing language templates: %(listname)s" msgstr "Korrigiere Sprachschablone für Liste: %(listname)s" # Mailman/Cgi/create.py:203 bin/newlist:184 #: bin/update:196 bin/update:711 +#, fuzzy msgid "WARNING: could not acquire lock for list: %(listname)s" msgstr "WARNUNG: Keine Kontrolle über die Dateisperre der Liste: %(listname)s" #: bin/update:215 +#, fuzzy msgid "Resetting %(n)s BYBOUNCEs disabled addrs with no bounce info" msgstr "" "%(n)s BYBOUNCEs deaktivierte Adressen ohne Bounce-Informationen werden " @@ -13503,6 +13817,7 @@ msgstr "" "%(mbox_dir)s.tmp umbenennen und fortfahren werde." #: bin/update:255 +#, fuzzy msgid "" "\n" "%(listname)s has both public and private mbox archives. Since this list\n" @@ -13552,6 +13867,7 @@ msgid "- updating old private mbox file" msgstr "- aktualisiere alte, öffentliche mbox-Datei" #: bin/update:295 +#, fuzzy msgid "" " unknown file in the way, moving\n" " %(o_pri_mbox_file)s\n" @@ -13565,6 +13881,7 @@ msgid "- updating old public mbox file" msgstr "- aktualisiere alte, öffentliche mbox-Datei" #: bin/update:317 +#, fuzzy msgid "" " unknown file in the way, moving\n" " %(o_pub_mbox_file)s\n" @@ -13590,18 +13907,22 @@ msgid "- %(o_tmpl)s doesn't exist, leaving untouched" msgstr "- %(o_tmpl)s existiert nicht, keine Aktion durchgeführt" #: bin/update:396 +#, fuzzy msgid "removing directory %(src)s and everything underneath" msgstr "Lösche Verzeichnis %(src)s und alles darunter" #: bin/update:399 +#, fuzzy msgid "removing %(src)s" msgstr "Entferne %(src)s" #: bin/update:403 +#, fuzzy msgid "Warning: couldn't remove %(src)s -- %(rest)s" msgstr "Warnung: Konnte Datei %(src)s nicht entfernen -- %(rest)s" #: bin/update:408 +#, fuzzy msgid "couldn't remove old file %(pyc)s -- %(rest)s" msgstr "konnte alte Datei %(pyc)s nicht entfernen -- %(rest)s" @@ -13610,14 +13931,17 @@ msgid "updating old qfiles" msgstr "update alte qfiles" #: bin/update:455 +#, fuzzy msgid "Warning! Not a directory: %(dirpath)s" msgstr "Warnung - Kein Verzeichnis: %(dirpath)s " #: bin/update:530 +#, fuzzy msgid "message is unparsable: %(filebase)s" msgstr "Nachricht ist nur interpretierbar: %(filebase)s" #: bin/update:544 +#, fuzzy msgid "Warning! Deleting empty .pck file: %(pckfile)s" msgstr "Warnung! Lösche leere .pck-Datei: %(pckfile)s" @@ -13630,10 +13954,12 @@ msgid "Updating Mailman 2.1.4 pending.pck database" msgstr "Update der alten pending_subscriptions.db Datenbank von Mailman 2.1.4" #: bin/update:598 +#, fuzzy msgid "Ignoring bad pended data: %(key)s: %(val)s" msgstr "Ungültige zurückgehaltene Daten werden ignoriert: %(key)s: %(val)s" #: bin/update:614 +#, fuzzy msgid "WARNING: Ignoring duplicate pending ID: %(id)s." msgstr "WARNUNG: Ignoriere doppelte Pending-IDs: %(id)s." @@ -13673,6 +13999,7 @@ msgstr "erledigt" # Mailman/Cgi/create.py:203 bin/newlist:184 #: bin/update:691 +#, fuzzy msgid "Updating mailing list: %(listname)s" msgstr "Aktualisiere Mailingliste: %(listname)s" @@ -13730,6 +14057,7 @@ msgid "No updates are necessary." msgstr "Keine Updates erforderlich." #: bin/update:796 +#, fuzzy msgid "" "Downgrade detected, from version %(hexlversion)s to version %(hextversion)s\n" "This is probably not safe.\n" @@ -13740,10 +14068,12 @@ msgstr "" "Programmende." #: bin/update:801 +#, fuzzy msgid "Upgrading from version %(hexlversion)s to %(hextversion)s" msgstr "Update von Version %(hexlversion)s auf %(hextversion)s" #: bin/update:810 +#, fuzzy msgid "" "\n" "ERROR:\n" @@ -14028,6 +14358,7 @@ msgstr "" # Mailman/Cgi/create.py:203 bin/newlist:184 #: bin/withlist:175 +#, fuzzy msgid "Unlocking (but not saving) list: %(listname)s" msgstr "Gebe Mailingliste wieder frei (aber speichere sie nicht): %(listname)s" @@ -14037,6 +14368,7 @@ msgstr "Vollende" # Mailman/Cgi/create.py:203 bin/newlist:184 #: bin/withlist:188 +#, fuzzy msgid "Loading list %(listname)s" msgstr "Lade Mailingliste: %(listname)s" @@ -14050,6 +14382,7 @@ msgstr "(entsperrt)" # Mailman/Cgi/create.py:203 bin/newlist:184 #: bin/withlist:197 +#, fuzzy msgid "Unknown list: %(listname)s" msgstr "Unbekannte Liste: %(listname)s" @@ -14063,18 +14396,22 @@ msgid "--all requires --run" msgstr "--all requires --run" #: bin/withlist:266 +#, fuzzy msgid "Importing %(module)s..." msgstr "Importiere %(module)s..." #: bin/withlist:270 +#, fuzzy msgid "Running %(module)s.%(callable)s()..." msgstr "Ausführen von %(module)s.%(callable)s()..." #: bin/withlist:291 +#, fuzzy msgid "The variable `m' is the %(listname)s MailList instance" msgstr "Die Variable `m' ist die MailList-Instanz für %(listname)s" #: cron/bumpdigests:19 +#, fuzzy msgid "" "Increment the digest volume number and reset the digest number to one.\n" "\n" @@ -14103,6 +14440,7 @@ msgstr "" "Listenname angegeben, werden alle vorhandenen Listen geschoben.\n" #: cron/checkdbs:20 +#, fuzzy msgid "" "Check for pending admin requests and mail the list owners if necessary.\n" "\n" @@ -14133,10 +14471,12 @@ msgstr "" "\n" #: cron/checkdbs:121 +#, fuzzy msgid "%(count)d %(realname)s moderator request(s) waiting" msgstr "%(count)d %(realname)s Moderatoranforderung(en) warten" #: cron/checkdbs:124 +#, fuzzy msgid "%(realname)s moderator request check result" msgstr "%(realname)s Moderatoranforderungen warten" @@ -14159,6 +14499,7 @@ msgstr "" "Offene Eingänge:" #: cron/checkdbs:169 +#, fuzzy msgid "" "From: %(sender)s on %(date)s\n" "Subject: %(subject)s\n" @@ -14169,6 +14510,7 @@ msgstr "" "Grund: %(reason)s" #: cron/cull_bad_shunt:20 +#, fuzzy msgid "" "Cull bad and shunt queues, recommended once per day.\n" "\n" @@ -14211,6 +14553,7 @@ msgstr "" " Diese Hilfe zeigen und beenden.\n" #: cron/disabled:20 +#, fuzzy msgid "" "Process disabled members, recommended once per day.\n" "\n" @@ -14346,6 +14689,7 @@ msgstr "" "\n" #: cron/mailpasswds:19 +#, fuzzy msgid "" "Send password reminders for all lists to all users.\n" "\n" @@ -14401,10 +14745,12 @@ msgstr "Passwort // URL" # Mailman/Deliverer.py:103 #: cron/mailpasswds:222 +#, fuzzy msgid "%(host)s mailing list memberships reminder" msgstr "%(host)s Mitgliedschafts-Erinnerung" #: cron/nightly_gzip:19 +#, fuzzy msgid "" "Re-generate the Pipermail gzip'd archive flat files.\n" "\n" @@ -14453,6 +14799,7 @@ msgstr "" ".\n" #: cron/senddigests:20 +#, fuzzy msgid "" "Dispatch digests for lists w/pending messages and digest_send_periodic set.\n" "\n" diff --git a/messages/el/LC_MESSAGES/mailman.po b/messages/el/LC_MESSAGES/mailman.po index 1b795bb2..8a713446 100755 --- a/messages/el/LC_MESSAGES/mailman.po +++ b/messages/el/LC_MESSAGES/mailman.po @@ -69,10 +69,12 @@ msgid "

                Currently, there are no archives.

                " msgstr "

                ÁõôÞ ôç óôéãìÞ, äåí õðÜñ÷ïõí éóôïñéêÜ áñ÷åßá ôùí ìçíõìÜôùí.

                " #: Mailman/Archiver/HyperArch.py:821 +#, fuzzy msgid "Gzip'd Text%(sz)s" msgstr "ÓõìðéåóìÝíï Êåßìåíï%(sz)s" #: Mailman/Archiver/HyperArch.py:826 +#, fuzzy msgid "Text%(sz)s" msgstr "Êåßìåíï%(sz)s" @@ -145,18 +147,22 @@ msgid "Third" msgstr "Ôñßôï" #: Mailman/Archiver/HyperArch.py:938 +#, fuzzy msgid "%(ord)s quarter %(year)i" msgstr "%(ord)s ôÝôáñôï %(year)i" #: Mailman/Archiver/HyperArch.py:945 +#, fuzzy msgid "%(month)s %(year)i" msgstr "%(month)s %(year)i" #: Mailman/Archiver/HyperArch.py:950 +#, fuzzy msgid "The Week Of Monday %(day)i %(month)s %(year)i" msgstr "ÅâäïìÜäá ôçò ÄåõôÝñáò %(day)i %(month)s %(year)i" #: Mailman/Archiver/HyperArch.py:954 +#, fuzzy msgid "%(day)i %(month)s %(year)i" msgstr "%(day)i %(month)s %(year)i" @@ -165,10 +171,12 @@ msgid "Computing threaded index\n" msgstr "Ãßíåôáé õðïëïãéóìüò ôùí ðåñéå÷ïìÝíùí\n" #: Mailman/Archiver/HyperArch.py:1319 +#, fuzzy msgid "Updating HTML for article %(seq)s" msgstr "ÁíáíÝùóç ôçò HTML ãéá ôï Üñèñï %(seq)s" #: Mailman/Archiver/HyperArch.py:1326 +#, fuzzy msgid "article file %(filename)s is missing!" msgstr "ôï %(filename)s áñ÷åßï ôïõ Üñèñïõ ëåßðåé!" @@ -185,6 +193,7 @@ msgid "Pickling archive state into " msgstr "Pickling archive state into " #: Mailman/Archiver/pipermail.py:453 +#, fuzzy msgid "Updating index files for archive [%(archive)s]" msgstr "ÁíáíÝùóç ôùí áñ÷åßùí êáôáëüãïõ ãéá ôï éóôïñéêü áñ÷åßï [%(archive)s]" @@ -193,6 +202,7 @@ msgid " Thread" msgstr " Áêïëïõèßá ìçíýìáôïò" #: Mailman/Archiver/pipermail.py:597 +#, fuzzy msgid "#%(counter)05d %(msgid)s" msgstr "#%(counter)05d %(msgid)s" @@ -230,6 +240,7 @@ msgid "disabled address" msgstr "áðåíåñãïðïéçìÝíï" #: Mailman/Bouncer.py:304 +#, fuzzy msgid " The last bounce received from you was dated %(date)s" msgstr "" " Ôï ôåëåõôáßï åðéóôñåöüìåíï ìÞíõìá ôï ïðïßï åëÞöèç áðü åóÜò Þôáí óôéò " @@ -259,6 +270,7 @@ msgstr " #: Mailman/Cgi/options.py:101 Mailman/Cgi/private.py:108 #: Mailman/Cgi/rmlist.py:75 Mailman/Cgi/roster.py:59 #: Mailman/Cgi/subscribe.py:67 +#, fuzzy msgid "No such list %(safelistname)s" msgstr "Äåí õðÜñ÷åé ôÝôïéá ëßóôá %(safelistname)s" @@ -338,6 +350,7 @@ msgstr "" "åðçñåÜæïíôáé %(rm)r." #: Mailman/Cgi/admin.py:268 +#, fuzzy msgid "%(hostname)s mailing lists - Admin Links" msgstr "%(hostname)s ëßóôåò çëåêôñïíéêïý ôá÷õäñïìåßïõ - Óýíäåóìïé Äéá÷åßñéóçò" @@ -350,6 +363,7 @@ msgid "Mailman" msgstr "Mailman" #: Mailman/Cgi/admin.py:310 +#, fuzzy msgid "" "

                There currently are no publicly-advertised %(mailmanlink)s\n" " mailing lists on %(hostname)s." @@ -358,6 +372,7 @@ msgstr "" " ëßóôåò óôï %(hostname)s." #: Mailman/Cgi/admin.py:316 +#, fuzzy msgid "" "

                Below is the collection of publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s. Click on a list\n" @@ -372,6 +387,7 @@ msgid "right " msgstr "äåîéÜ " #: Mailman/Cgi/admin.py:325 +#, fuzzy msgid "" "To visit the administrators configuration page for an\n" " unadvertised list, open a URL similar to this one, but with a '/' " @@ -418,6 +434,7 @@ msgid "No valid variable name found." msgstr "Äå âñÝèçêå Ýãêõñï üíïìá ìåôáâëçôÞò" #: Mailman/Cgi/admin.py:395 +#, fuzzy msgid "" "%(realname)s Mailing list Configuration Help\n" "
                %(varname)s Option" @@ -426,6 +443,7 @@ msgstr "" "
                ÅðéëïãÞ %(varname)s" #: Mailman/Cgi/admin.py:402 +#, fuzzy msgid "Mailman %(varname)s List Option Help" msgstr "ÂïÞèåéá ãéá ôçí %(varname)s ÅðéëïãÞ ôçò Ëßóôáò ôïõ Mailman" @@ -446,14 +464,17 @@ msgstr "" " " #: Mailman/Cgi/admin.py:431 +#, fuzzy msgid "return to the %(categoryname)s options page." msgstr "åðéóôñÝøåôå óôç óåëßäá åðéëïãþí: %(categoryname)s" #: Mailman/Cgi/admin.py:446 +#, fuzzy msgid "%(realname)s Administration (%(label)s)" msgstr " (%(label)s) Äéá÷åßñéóç %(realname)s" #: Mailman/Cgi/admin.py:447 +#, fuzzy msgid "%(realname)s mailing list administration
                %(label)s Section" msgstr "Äéá÷åßñéóç ôçò ëßóôáò %(realname)s
                %(label)s" @@ -538,6 +559,7 @@ msgid "Value" msgstr "ÔéìÞ" #: Mailman/Cgi/admin.py:668 +#, fuzzy msgid "" "Badly formed options entry:\n" " %(record)s" @@ -638,10 +660,12 @@ msgid "Move rule down" msgstr "Ìåôáêßíçóç êáíüíá êÜôù" #: Mailman/Cgi/admin.py:870 +#, fuzzy msgid "
                (Edit %(varname)s)" msgstr "
                (Åðåîåñãáóßá %(varname)s)" #: Mailman/Cgi/admin.py:872 +#, fuzzy msgid "
                (Details for %(varname)s)" msgstr "
                (ËåðôïìÝñåéåò ãéá %(varname)s)" @@ -682,6 +706,7 @@ msgid "(help)" msgstr "(âïÞèåéá)" #: Mailman/Cgi/admin.py:930 +#, fuzzy msgid "Find member %(link)s:" msgstr "Åýñåóç ìÝëïõò %(link)s:" @@ -694,10 +719,12 @@ msgid "Bad regular expression: " msgstr "ËáíèáóìÝíç êáíïíéêÞ Ýêöñáóç (regular expression): " #: Mailman/Cgi/admin.py:1013 +#, fuzzy msgid "%(allcnt)s members total, %(membercnt)s shown" msgstr "%(allcnt)s óýíïëï ìåëþí, %(membercnt)s ïñáôÜ" #: Mailman/Cgi/admin.py:1016 +#, fuzzy msgid "%(allcnt)s members total" msgstr "%(allcnt)s óýíïëï ìåëþí" @@ -895,6 +922,7 @@ msgstr "" " äéÜóôçìá ðáñáêÜôù:
                " #: Mailman/Cgi/admin.py:1221 +#, fuzzy msgid "from %(start)s to %(end)s" msgstr "áðü %(start)s ìÝ÷ñé %(end)s" @@ -1033,6 +1061,7 @@ msgid "Change list ownership passwords" msgstr "ÁëëáãÞ ôùí êùäéêþí ôçò ëßóôáò" #: Mailman/Cgi/admin.py:1364 +#, fuzzy msgid "" "The list administrators are the people who have ultimate control " "over\n" @@ -1160,6 +1189,7 @@ msgstr " #: Mailman/Cgi/admin.py:1529 Mailman/Cgi/admin.py:1703 bin/add_members:175 #: bin/clone_member:136 bin/sync_members:268 +#, fuzzy msgid "Banned address (matched %(pattern)s)" msgstr "ÁðáãïñåõìÝíç äéåýèõíóç (matched %(pattern)s)" @@ -1262,6 +1292,7 @@ msgid "Not subscribed" msgstr "Äåí õðÜñ÷åé åããñáöÞ" #: Mailman/Cgi/admin.py:1773 +#, fuzzy msgid "Ignoring changes to deleted member: %(user)s" msgstr "Ïé áëëáãÝò óôï äéåãñáììÝíï ìÝëïò èá áãíïçèïýí: %(user)s" @@ -1274,10 +1305,12 @@ msgid "Error Unsubscribing:" msgstr "ËÜèïò óôç äéáãñáöÞ:" #: Mailman/Cgi/admindb.py:235 Mailman/Cgi/admindb.py:248 +#, fuzzy msgid "%(realname)s Administrative Database" msgstr "Äéá÷åéñéóôéêÞ âÜóç ôïõ %(realname)s" #: Mailman/Cgi/admindb.py:238 +#, fuzzy msgid "%(realname)s Administrative Database Results" msgstr "ÁðïôåëÝóìáôá áðü ôç Äéá÷åéñéóôéêÞ ÂÜóç ôïõ %(realname)s" @@ -1308,6 +1341,7 @@ msgstr "" "ÅíÝñãåéá\"" #: Mailman/Cgi/admindb.py:298 +#, fuzzy msgid "all of %(esender)s's held messages." msgstr "üëá ôá ìçíýìáôá ôïõ %(esender)s ðïõ êñáôïýíôáé." @@ -1328,6 +1362,7 @@ msgid "list of available mailing lists." msgstr "ëßóôá ìå üëåò ôéò äéáèÝóéìåò ëßóôåò çëåêôñïíéêïý ôá÷õäñïìåßïõ." #: Mailman/Cgi/admindb.py:362 +#, fuzzy msgid "You must specify a list name. Here is the %(link)s" msgstr "ÐñÝðåé íá ïñßóåôå Ýíá üíïìá ëßóôáò. Áõôü åßíáé ôï %(link)s" @@ -1409,6 +1444,7 @@ msgid "The sender is now a member of this list" msgstr "Ï áðïóôïëÝáò åßíáé ôþñá ìÝëïò áõôÞò ôçò ëßóôáò" #: Mailman/Cgi/admindb.py:568 +#, fuzzy msgid "Add %(esender)s to one of these sender filters:" msgstr "" "ÐñïóèÞêç %(esender)s óå Ýíá áðü áõôÜ ôá ößëôñá ãéá ôïõò áðïóôïëåßò:" @@ -1430,6 +1466,7 @@ msgid "Rejects" msgstr "Áðïññßøåéò" #: Mailman/Cgi/admindb.py:584 +#, fuzzy msgid "" "Ban %(esender)s from ever subscribing to this\n" " mailing list" @@ -1446,6 +1483,7 @@ msgstr "" " ìÞíõìá, Þ ìðïñåßôå " #: Mailman/Cgi/admindb.py:591 +#, fuzzy msgid "view all messages from %(esender)s" msgstr "íá äåßôå üëá ôá ìçíýìáôá áðü ôïí %(esender)s" @@ -1524,6 +1562,7 @@ msgid " is already a member" msgstr " åßíáé Þäç ìÝëïò" #: Mailman/Cgi/admindb.py:973 +#, fuzzy msgid "%(addr)s is banned (matched: %(patt)s)" msgstr "%(addr)s áðáãïñåýåôáé (ôáéñéÜæåé: %(patt)s)" @@ -1575,6 +1614,7 @@ msgstr "" " äéáãñáöåß. Ôï áßôçìÜ óáò áêõñþíåôáé." #: Mailman/Cgi/confirm.py:178 +#, fuzzy msgid "System error, bad content: %(content)s" msgstr "ËÜèïò óõóôÞìáôïò, êáêü ðåñéå÷üìåíï: %(content)s" @@ -1613,6 +1653,7 @@ msgid "Confirm subscription request" msgstr "Åðéâåâáßùóç ôïõ áéôÞìáôïò åããñáöÞò" #: Mailman/Cgi/confirm.py:259 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " subscription request to the mailing list %(listname)s. Your\n" @@ -1652,6 +1693,7 @@ msgstr "" " íá åããñáöåßôå óôç ëßóôá." #: Mailman/Cgi/confirm.py:275 +#, fuzzy msgid "" "Your confirmation is required in order to continue with\n" " the subscription request to the mailing list %(listname)s.\n" @@ -1710,6 +1752,7 @@ msgid "Preferred language:" msgstr "Ðñïôéìþìåíç ãëþóóá:" #: Mailman/Cgi/confirm.py:317 +#, fuzzy msgid "Subscribe to list %(listname)s" msgstr "ÅããñáöÞ óôç ëßóôá %(listname)s" @@ -1726,6 +1769,7 @@ msgid "Awaiting moderator approval" msgstr "Åí' áíáìïíÞ ôçò Ýãêñéóçò ôïõ äéá÷åéñéóôÞ" #: Mailman/Cgi/confirm.py:386 +#, fuzzy msgid "" " You have successfully confirmed your subscription request to " "the\n" @@ -1761,6 +1805,7 @@ msgid "You are already a member of this mailing list!" msgstr "Åßóôå Þäç ìÝëïò áõôÞò ôçò ëßóôáò çëåêôñïíéêïý ôá÷õäñïìåßïõ!" #: Mailman/Cgi/confirm.py:400 +#, fuzzy msgid "" "You are currently banned from subscribing to\n" " this list. If you think this restriction is erroneous, please\n" @@ -1787,6 +1832,7 @@ msgid "Subscription request confirmed" msgstr "Ôï Áßôçìá ÅããñáöÞò åðéâåâáéþèçêå" #: Mailman/Cgi/confirm.py:418 +#, fuzzy msgid "" " You have successfully confirmed your subscription request for\n" " \"%(addr)s\" to the %(listname)s mailing list. A separate\n" @@ -1815,6 +1861,7 @@ msgid "Unsubscription request confirmed" msgstr "Åðéâåâáéþèçêå ôï áßôçìá äéáãñáöÞò" #: Mailman/Cgi/confirm.py:469 +#, fuzzy msgid "" " You have successfully unsubscribed from the %(listname)s " "mailing\n" @@ -1837,6 +1884,7 @@ msgid "Not available" msgstr "Äåí åßíáé äéáèÝóéìï" #: Mailman/Cgi/confirm.py:498 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " unsubscription request from the mailing list %(listname)s. " @@ -1854,8 +1902,8 @@ msgid "" " request." msgstr "" "Áðáéôåßôáé ç åðéâåâáßùóÞ óáò ðñïêåéìÝíïõ íá ïëïêëçñùèåß ôï\n" -" áßôçìá äéáãñáöÞò áðü ôç ëßóôá çëåêôñïíéêïý ôá÷õäñïìåßïõ " -"%(listname)s.\n" +" áßôçìá äéáãñáöÞò áðü ôç ëßóôá çëåêôñïíéêïý ôá÷õäñïìåßïõ " +"%(listname)s.\n" " ÁõôÞ ôç óôéãìÞ åßóôå åããåãñáììÝíïò ìå\n" "\n" "

                • Ðñáãìáôéêü üíïìá: %(fullname)s\n" @@ -1884,6 +1932,7 @@ msgid "You have canceled your change of address request." msgstr "Áêõñþóáôå ôï áßôçìá ãéá áëëáãÞ äéåýèõíóçò." #: Mailman/Cgi/confirm.py:555 +#, fuzzy msgid "" "%(newaddr)s is banned from subscribing to the\n" " %(realname)s list. If you think this restriction is erroneous,\n" @@ -1895,6 +1944,7 @@ msgstr "" " éäéïêôÞôåò ôçò ëßóôáò óôï %(owneraddr)s." #: Mailman/Cgi/confirm.py:560 +#, fuzzy msgid "" "%(newaddr)s is already a member of\n" " the %(realname)s list. It is possible that you are attempting\n" @@ -1911,6 +1961,7 @@ msgid "Change of address request confirmed" msgstr "Ôï áßôçìá ãéá áëëáãÞ äéåýèõíóçò åðéâåâáéþèçêå" #: Mailman/Cgi/confirm.py:571 +#, fuzzy msgid "" " You have successfully changed your address on the %(listname)s\n" " mailing list from %(oldaddr)s to %(newaddr)s. " @@ -1932,6 +1983,7 @@ msgid "globally" msgstr "êáèïëéêÞ" #: Mailman/Cgi/confirm.py:605 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " change of address request for the mailing list %(listname)s. " @@ -1955,8 +2007,8 @@ msgid "" " request." msgstr "" "Ç åðéâåâáßùóÞ óáò áðáéôåßôáé ðñïêåéìÝíïõ íá ïëïêëçñùèåß ôï áßôçìá\n" -" ãéá áëëáãÞ äéåýèõíóçò ãéá ôç ëßóôá çëåêôñïíéêïý ôá÷õäñïìåßïõ " -"%(listname)s.\n" +" ãéá áëëáãÞ äéåýèõíóçò ãéá ôç ëßóôá çëåêôñïíéêïý ôá÷õäñïìåßïõ " +"%(listname)s.\n" " ÁõôÞ ôç óôéãìÞ åßóôå åããåãñáììÝíïò ìå\n" "\n" "
                  • Ðñáãìáôéêü ¼íïìá: %(fullname)s\n" @@ -1998,6 +2050,7 @@ msgid "Sender discarded message via web." msgstr "Ï áðïóôïëÝáò áðÝññéøå ÷ùñßò åíçìÝñùóç ôï ìÞíõìá ìÝóù ôïõ web." #: Mailman/Cgi/confirm.py:674 +#, fuzzy msgid "" "The held message with the Subject:\n" " header %(subject)s could not be found. The most " @@ -2019,6 +2072,7 @@ msgid "Posted message canceled" msgstr "Ôï ìÞíõìá ðïõ óôåßëáôå áêõñþèçêå" #: Mailman/Cgi/confirm.py:685 +#, fuzzy msgid "" " You have successfully canceled the posting of your message with\n" " the Subject: header %(subject)s to the mailing list\n" @@ -2042,6 +2096,7 @@ msgstr "" " áðü ôï äéá÷åéñéóôÞ ôçò ëßóôáò." #: Mailman/Cgi/confirm.py:735 +#, fuzzy msgid "" "Your confirmation is required in order to cancel the\n" " posting of your message to the mailing list %(listname)s:\n" @@ -2091,6 +2146,7 @@ msgid "Membership re-enabled." msgstr "Ç åããñáöÞ åðáíá-åíåñãïðïéÞèçêå." #: Mailman/Cgi/confirm.py:798 +#, fuzzy msgid "" " You have successfully re-enabled your membership in the\n" " %(listname)s mailing list. You can now not available" msgstr "äåí åßíáé äéáèÝóéìï" #: Mailman/Cgi/confirm.py:846 +#, fuzzy msgid "" "Your membership in the %(realname)s mailing list is\n" " currently disabled due to excessive bounces. Your confirmation is\n" @@ -2192,10 +2250,12 @@ msgid "administrative list overview" msgstr "äéá÷åéñéóôéêÞ åðéóêüðçóç ôçò ëßóôáò" #: Mailman/Cgi/create.py:115 +#, fuzzy msgid "List name must not include \"@\": %(safelistname)s" msgstr "Ôï üíïìá ôçò ëßóôáò äå ðñÝðåé íá ðåñéÝ÷åé \"@\": %(safelistname)s" #: Mailman/Cgi/create.py:122 +#, fuzzy msgid "List already exists: %(safelistname)s" msgstr "Ç ëßóôá õðÜñ÷åé Þäç: %(safelistname)s" @@ -2231,18 +2291,22 @@ msgstr "" "ôá÷õäñïìåßïõ" #: Mailman/Cgi/create.py:182 +#, fuzzy msgid "Unknown virtual host: %(safehostname)s" msgstr "¢ãíùóôïò åéêïíéêüò åîõðçñåôçôÞò: %(safehostname)s" #: Mailman/Cgi/create.py:218 bin/newlist:219 +#, fuzzy msgid "Bad owner email address: %(s)s" msgstr "ÊáêÞ çëåêôñïíéêÞ äéåýèõíóç éäéïêôÞôç: %(s)s" #: Mailman/Cgi/create.py:223 bin/newlist:182 bin/newlist:223 +#, fuzzy msgid "List already exists: %(listname)s" msgstr "Ç ëßóôá õðÜñ÷åé Þäç: %(listname)s" #: Mailman/Cgi/create.py:231 bin/newlist:217 +#, fuzzy msgid "Illegal list name: %(s)s" msgstr "Ìç Ýãêõñï üíïìá ëßóôáò: %(s)s" @@ -2255,6 +2319,7 @@ msgstr "" " Ðáñáêáëïýìå åðéêïéíùíÞóôå ìå ôï äéá÷åéñéóôÞ ãéá âïÞèåéá." #: Mailman/Cgi/create.py:273 bin/newlist:265 +#, fuzzy msgid "Your new mailing list: %(listname)s" msgstr "Ç íÝá óáò ëßóôá çëåêôñïíéêïý ôá÷õäñïìåßïõ: %(listname)s" @@ -2263,6 +2328,7 @@ msgid "Mailing list creation results" msgstr "ÁðïôåëÝóìáôá äçìéïõñãßáò ëßóôáò çëåêôñïíéêïý ôá÷õäñïìåßïõ" #: Mailman/Cgi/create.py:288 +#, fuzzy msgid "" "You have successfully created the mailing list\n" " %(listname)s and notification has been sent to the list owner\n" @@ -2286,6 +2352,7 @@ msgid "Create another list" msgstr "ÄçìéïõñãÞóåôå Üëëç ëßóôá" #: Mailman/Cgi/create.py:312 +#, fuzzy msgid "Create a %(hostname)s Mailing List" msgstr "Äçìéïõñãßá ìéáò ëßóôáò óôï %(hostname)s" @@ -2393,6 +2460,7 @@ msgstr "" "ïñéóìïý" #: Mailman/Cgi/create.py:432 +#, fuzzy msgid "" "Initial list of supported languages.

                    Note that if you do not\n" " select at least one initial language, the list will use the server\n" @@ -2499,6 +2567,7 @@ msgid "List name is required." msgstr "Áðáéôåßôáé ôï üíïìá ôçò ëßóôáò." #: Mailman/Cgi/edithtml.py:154 +#, fuzzy msgid "%(realname)s -- Edit html for %(template_info)s" msgstr "%(realname)s -- Åðåîåñãáóßá ôçò html ãéá %(template_info)s" @@ -2507,10 +2576,12 @@ msgid "Edit HTML : Error" msgstr "Åðåîåñãáóßá HTML : ËÜèïò" #: Mailman/Cgi/edithtml.py:161 +#, fuzzy msgid "%(safetemplatename)s: Invalid template" msgstr "%(safetemplatename)s: Ìç Ýãêõñï template" #: Mailman/Cgi/edithtml.py:166 Mailman/Cgi/edithtml.py:167 +#, fuzzy msgid "%(realname)s -- HTML Page Editing" msgstr "%(realname)s -- Åðåîåñãáóßá HTML óåëßäáò" @@ -2575,10 +2646,12 @@ msgid "HTML successfully updated." msgstr "Ç HTML Üëëáîå ìå åðéôõ÷ßá." #: Mailman/Cgi/listinfo.py:89 +#, fuzzy msgid "%(hostname)s Mailing Lists" msgstr "Ëßóôåò Çëåêôñïíéêïý Ôá÷õäñïìåßïõ ôïõ %(hostname)s" #: Mailman/Cgi/listinfo.py:127 +#, fuzzy msgid "" "

                    There currently are no publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s." @@ -2588,6 +2661,7 @@ msgstr "" "%(hostname)s." #: Mailman/Cgi/listinfo.py:131 +#, fuzzy msgid "" "

                    Below is a listing of all the public mailing lists on\n" " %(hostname)s. Click on a list name to get more information " @@ -2608,6 +2682,7 @@ msgid "right" msgstr "äåîéÜ" #: Mailman/Cgi/listinfo.py:140 +#, fuzzy msgid "" " To visit the general information page for an unadvertised list,\n" " open a URL similar to this one, but with a '/' and the %(adj)s\n" @@ -2671,6 +2746,7 @@ msgstr " #: Mailman/Cgi/options.py:183 Mailman/Cgi/options.py:240 #: Mailman/Cgi/options.py:264 +#, fuzzy msgid "No such member: %(safeuser)s." msgstr "Äåí õðÜñ÷åé ôÝôïéï ìÝëïò: %(safeuser)s." @@ -2723,6 +2799,7 @@ msgid "Note: " msgstr "Óçìåßùóç:" #: Mailman/Cgi/options.py:378 +#, fuzzy msgid "List subscriptions for %(safeuser)s on %(hostname)s" msgstr "ÅããñáöÝò óôç ëßóôá ãéá %(safeuser)s óôï %(hostname)s" @@ -2753,6 +2830,7 @@ msgid "You are already using that email address" msgstr "×ñçóéìïðïéåßôå Þäç áõôÞ ôç äéåýèõíóç çëåêôñïíéêïý ôá÷õäñïìåßïõ" #: Mailman/Cgi/options.py:459 +#, fuzzy msgid "" "The new address you requested %(newaddr)s is already a member of the\n" "%(listname)s mailing list, however you have also requested a global change " @@ -2767,6 +2845,7 @@ msgstr "" "%(safeuser)s èá áëëá÷ôåß. " #: Mailman/Cgi/options.py:468 +#, fuzzy msgid "The new address is already a member: %(newaddr)s" msgstr "Ç íÝá äéåýèõíóç åßíáé Þäç ìÝëïò: %(newaddr)s" @@ -2775,6 +2854,7 @@ msgid "Addresses may not be blank" msgstr "Ïé äéåõèýíóåéò äå ìðïñåß íá åßíáé êåíÝò" #: Mailman/Cgi/options.py:488 +#, fuzzy msgid "A confirmation message has been sent to %(newaddr)s. " msgstr "¸íá ìÞíõìá åðéâåâáßùóçò Ý÷åé óôáëåß óôï %(newaddr)s. " @@ -2787,10 +2867,12 @@ msgid "Illegal email address provided" msgstr "Äüèçêå ìç Ýãêõñç email äéåýèõíóç" #: Mailman/Cgi/options.py:501 +#, fuzzy msgid "%(newaddr)s is already a member of the list." msgstr "%(newaddr)s åßíáé Þäç ìÝëïò ôçò ëßóôáò." #: Mailman/Cgi/options.py:504 +#, fuzzy msgid "" "%(newaddr)s is banned from this list. If you\n" " think this restriction is erroneous, please contact\n" @@ -2866,6 +2948,7 @@ msgstr "" " åéäïðïßçóç ìüëéò ïé äéá÷åéñéóôÝò ðÜñïõí ôçí áðüöáóÞ ôïõò." #: Mailman/Cgi/options.py:623 +#, fuzzy msgid "" "You have been successfully unsubscribed from the\n" " mailing list %(fqdn_listname)s. If you were receiving digest\n" @@ -2963,6 +3046,7 @@ msgid "day" msgstr "çìÝñá" #: Mailman/Cgi/options.py:900 +#, fuzzy msgid "%(days)d %(units)s" msgstr "%(days)d %(units)s" @@ -2975,6 +3059,7 @@ msgid "No topics defined" msgstr "Äåí ïñßóôçêáí èÝìáôá" #: Mailman/Cgi/options.py:940 +#, fuzzy msgid "" "\n" "You are subscribed to this list with the case-preserved address\n" @@ -2986,6 +3071,7 @@ msgstr "" "%(cpuser)s." #: Mailman/Cgi/options.py:956 +#, fuzzy msgid "%(realname)s list: member options login page" msgstr "%(realname)s ëßóôá: óåëßäá óýíäåóçò ãéá ôéò åðéëïãÝò ôùí ìåëþí" @@ -2994,10 +3080,12 @@ msgid "email address and " msgstr "email äéåýèõíóç êáé " #: Mailman/Cgi/options.py:960 +#, fuzzy msgid "%(realname)s list: member options for user %(safeuser)s" msgstr "%(realname)s ëßóôá: åðéëïãÝò ìÝëïõò ãéá ôï ÷ñÞóôç %(safeuser)s" #: Mailman/Cgi/options.py:986 +#, fuzzy msgid "" "In order to change your membership option, you must\n" " first log in by giving your %(extra)smembership password in the section\n" @@ -3080,6 +3168,7 @@ msgid "" msgstr "<äåí õðÜñ÷åé>" #: Mailman/Cgi/options.py:1140 +#, fuzzy msgid "Requested topic is not valid: %(topicname)s" msgstr "Ôï æçôïýìåíï èÝìá äåí åßíáé Ýãêõñï: %(topicname)s" @@ -3110,6 +3199,7 @@ msgstr "" "äéêôõáêü ôüðï." #: Mailman/Cgi/private.py:109 +#, fuzzy msgid "Private Archive Error - %(msg)s" msgstr "ËÜèïò óôï Éäéùôéêü Éóôïñéêü Áñ÷åéü - %(msg)s" @@ -3148,6 +3238,7 @@ msgid "Mailing list deletion results" msgstr "ÁðïôåëÝóìáôá äéáãñáöÞò ëßóôáò" #: Mailman/Cgi/rmlist.py:187 +#, fuzzy msgid "" "You have successfully deleted the mailing list\n" " %(listname)s." @@ -3156,6 +3247,7 @@ msgstr "" " %(listname)s ìå åðéôõ÷ßá." #: Mailman/Cgi/rmlist.py:191 +#, fuzzy msgid "" "There were some problems deleting the mailing list\n" " %(listname)s. Contact your site administrator at " @@ -3168,6 +3260,7 @@ msgstr "" " %(sitelist)s ãéá ëåðôïìÝñåéåò." #: Mailman/Cgi/rmlist.py:208 +#, fuzzy msgid "Permanently remove mailing list %(realname)s" msgstr "ÏñéóôéêÞ äéáãñáöÞ ôçò ëßóôáò %(realname)s" @@ -3243,6 +3336,7 @@ msgid "Invalid options to CGI script" msgstr "Ìç Ýãêõñåò åðéëïãÝò óôï CGI script" #: Mailman/Cgi/roster.py:118 +#, fuzzy msgid "%(realname)s roster authentication failed." msgstr "ç ðéóôïðïßçóç ôïõ %(realname)s óôïí êáôÜëïãï áðÝôõ÷å." @@ -3312,6 +3406,7 @@ msgstr "" " åðéâåâáßùóçò ôï ïðïßï ðåñéÝ÷åé ðåñáéôÝñù åíôïëÝò." #: Mailman/Cgi/subscribe.py:262 +#, fuzzy msgid "" "The email address you supplied is banned from this\n" " mailing list. If you think this restriction is erroneous, please\n" @@ -3339,6 +3434,7 @@ msgstr "" "åßíáé áóöáëÞò." #: Mailman/Cgi/subscribe.py:278 +#, fuzzy msgid "" "Confirmation from your email address is required, to prevent anyone from\n" "subscribing you without permission. Instructions are being sent to you at\n" @@ -3353,6 +3449,7 @@ msgstr "" "íá åðéâåâáéþóåôå ôçí åããñáöÞ." #: Mailman/Cgi/subscribe.py:290 +#, fuzzy msgid "" "Your subscription request was deferred because %(x)s. Your request has " "been\n" @@ -3375,6 +3472,7 @@ msgid "Mailman privacy alert" msgstr "ÉäéùôéêÞ ðñïåéäïðïßçóç ôïõ Mailman" #: Mailman/Cgi/subscribe.py:316 +#, fuzzy msgid "" "An attempt was made to subscribe your address to the mailing list\n" "%(listaddr)s. You are already subscribed to this mailing list.\n" @@ -3421,6 +3519,7 @@ msgid "This list only supports digest delivery." msgstr "ÁõôÞ ç ëßóôá õðïóôçñßæåé ìüíï óõãêåíôñùôéêÞ ðáñÜäïóç ìçíõìÜôùí." #: Mailman/Cgi/subscribe.py:344 +#, fuzzy msgid "You have been successfully subscribed to the %(realname)s mailing list." msgstr "" "¸÷åôå åããñáöåß ìå åðéôõ÷ßá óôç ëßóôá çëåêôñïíéêïý ôá÷õäñïìåßïõ %(realname)s." @@ -3475,6 +3574,7 @@ msgstr "" "áëëÜîáôå ôçí çëåêôñïíéêÞ óáò äéåýèõíóç;" #: Mailman/Commands/cmd_confirm.py:69 +#, fuzzy msgid "" "You are currently banned from subscribing to this list. If you think this\n" "restriction is erroneous, please contact the list owners at\n" @@ -3562,26 +3662,32 @@ msgid "n/a" msgstr "Äåí åßíáé äéáèÝóéìï" #: Mailman/Commands/cmd_info.py:44 +#, fuzzy msgid "List name: %(listname)s" msgstr "¼íïìá ëßóôáò: %(listname)s" #: Mailman/Commands/cmd_info.py:45 +#, fuzzy msgid "Description: %(description)s" msgstr "ÐåñéãñáöÞ: %(description)s" #: Mailman/Commands/cmd_info.py:46 +#, fuzzy msgid "Postings to: %(postaddr)s" msgstr "ÁðïóôïëÝò óôï: %(postaddr)s" #: Mailman/Commands/cmd_info.py:47 +#, fuzzy msgid "List Helpbot: %(requestaddr)s" msgstr "ÁõôïìáôïðïéçìÝíç âïÞèåéá ëßóôáò: %(requestaddr)s" #: Mailman/Commands/cmd_info.py:48 +#, fuzzy msgid "List Owners: %(owneraddr)s" msgstr "ÉäéïêôÞôåò ôçò ëßóôáò: %(owneraddr)s" #: Mailman/Commands/cmd_info.py:49 +#, fuzzy msgid "More information: %(listurl)s" msgstr "Ðåñéóóüôåñåò ðëçñïöïñßåò: %(listurl)s" @@ -3606,18 +3712,22 @@ msgstr "" " áõôü ôï GNU Mailman server.\n" #: Mailman/Commands/cmd_lists.py:44 +#, fuzzy msgid "Public mailing lists at %(hostname)s:" msgstr "Äçìüóéåò ëßóôåò çëåêôñïíéêïý ôá÷õäñïìåßïõ óôï %(hostname)s:" #: Mailman/Commands/cmd_lists.py:66 +#, fuzzy msgid "%(i)3d. List name: %(realname)s" msgstr "%(i)3d. ¼íïìá ëßóôáò: %(realname)s" #: Mailman/Commands/cmd_lists.py:67 +#, fuzzy msgid " Description: %(description)s" msgstr " ÐåñéãñáöÞ: %(description)s" #: Mailman/Commands/cmd_lists.py:68 +#, fuzzy msgid " Requests to: %(requestaddr)s" msgstr " Áßôçìá ðñïò: %(requestaddr)s" @@ -3655,12 +3765,14 @@ msgstr "" " åããåãñáììÝíåò äéåõèýíóåéò.\n" #: Mailman/Commands/cmd_password.py:51 Mailman/Commands/cmd_password.py:66 +#, fuzzy msgid "Your password is: %(password)s" msgstr "Ï êùäéêüò óáò åßíáé: %(password)s" #: Mailman/Commands/cmd_password.py:57 Mailman/Commands/cmd_password.py:72 #: Mailman/Commands/cmd_password.py:95 Mailman/Commands/cmd_password.py:121 #: Mailman/Commands/cmd_set.py:149 Mailman/Commands/cmd_set.py:219 +#, fuzzy msgid "You are not a member of the %(listname)s mailing list" msgstr "Äåí åßóôå ìÝëïò ôçò %(listname)s ëßóôáò" @@ -3871,6 +3983,7 @@ msgstr "" " ìçíéáßá õðåíèýìéóç ôïõ êùäéêïý ãéá áõôÞ ôç ëßóôá.\n" #: Mailman/Commands/cmd_set.py:122 +#, fuzzy msgid "Bad set command: %(subcmd)s" msgstr "ÊáêÞ åíôïëÞ ñýèìéóçò: %(subcmd)s" @@ -3891,6 +4004,7 @@ msgid "on" msgstr "åíåñãïðïéçìÝíï" #: Mailman/Commands/cmd_set.py:154 +#, fuzzy msgid " ack %(onoff)s" msgstr " ack %(onoff)s" @@ -3928,22 +4042,27 @@ msgid "due to bounces" msgstr "ëïãþ ìçíõìÜôùí åðéóôñïöÞò" #: Mailman/Commands/cmd_set.py:186 +#, fuzzy msgid " %(status)s (%(how)s on %(date)s)" msgstr " %(status)s (%(how)s óôéò %(date)s)" #: Mailman/Commands/cmd_set.py:192 +#, fuzzy msgid " myposts %(onoff)s" msgstr " myposts %(onoff)s" #: Mailman/Commands/cmd_set.py:195 +#, fuzzy msgid " hide %(onoff)s" msgstr " áðüêñõøç %(onoff)s" #: Mailman/Commands/cmd_set.py:199 +#, fuzzy msgid " duplicates %(onoff)s" msgstr " äéðëÜ ìçíýìáôá %(onoff)s" #: Mailman/Commands/cmd_set.py:203 +#, fuzzy msgid " reminders %(onoff)s" msgstr " õðåíèõìßóåéò %(onoff)s" @@ -3952,6 +4071,7 @@ msgid "You did not give the correct password" msgstr "Äåí äþóáôå ôï óùóôü êùäéêü ðñüóâáóçò" #: Mailman/Commands/cmd_set.py:236 Mailman/Commands/cmd_set.py:283 +#, fuzzy msgid "Bad argument: %(arg)s" msgstr "ËáíèáóìÝíï üñéóìá: %(arg)s" @@ -4031,6 +4151,7 @@ msgstr "" " ãýñù áðü ôçí email äéåýèõíóç, êáé êáèüëïõ ïõñÝò áíáìïíÞò!)\n" #: Mailman/Commands/cmd_subscribe.py:62 +#, fuzzy msgid "Bad digest specifier: %(arg)s" msgstr "ËáíèáóìÝíï ïñéóìüò ãéá ôçí åðéëïãÞ digest: %(arg)s" @@ -4039,6 +4160,7 @@ msgid "No valid address found to subscribe" msgstr "Äå âñÝèçêå Ýãêõñç äéåýèõíóç ãéá åããñáöÞ" #: Mailman/Commands/cmd_subscribe.py:113 +#, fuzzy msgid "" "The email address you supplied is banned from this mailing list.\n" "If you think this restriction is erroneous, please contact the list\n" @@ -4078,6 +4200,7 @@ msgid "This list only supports digest subscriptions!" msgstr "ÁõôÞ ç ëßóôá õðïóôçñßæåé åããñáöÝò ìüíï ãéá ôç ëÞøç digests!" #: Mailman/Commands/cmd_subscribe.py:146 +#, fuzzy msgid "" "Your subscription request has been forwarded to the list administrator\n" "at %(listowner)s for review." @@ -4116,6 +4239,7 @@ msgstr "" " '<,>' ãýñù áðü ôçí email äéåýèõíóç, êáé ÷ùñßò ôá åéóáãùãéêÜ!)\n" #: Mailman/Commands/cmd_unsubscribe.py:62 +#, fuzzy msgid "%(address)s is not a member of the %(listname)s mailing list" msgstr "ç äéåýèõíóç %(address)s äåí åßíáé ìÝëïò ôçò %(listname)s ëßóôáò" @@ -4371,6 +4495,7 @@ msgid "Chinese (Taiwan)" msgstr "ÊéíÝæéêá (ÔáúâÜí)" #: Mailman/Deliverer.py:53 +#, fuzzy msgid "" "Note: Since this is a list of mailing lists, administrative\n" "notices like the password reminder will be sent to\n" @@ -4386,15 +4511,18 @@ msgid " (Digest mode)" msgstr " (Digest mode)" #: Mailman/Deliverer.py:79 +#, fuzzy msgid "Welcome to the \"%(realname)s\" mailing list%(digmode)s" msgstr "" "Êáëþò Þñèáôå óôç ëßóôá çëåêôñïíéêïý ôá÷õäñïìåßïõ \"%(realname)s\" %(digmode)s" #: Mailman/Deliverer.py:89 +#, fuzzy msgid "You have been unsubscribed from the %(realname)s mailing list" msgstr "¸÷åôå äéáãñáöåß áðü ôçí %(realname)s ëßóôá çëåêôñïíéêïý ôá÷õäñïìåßïõ" #: Mailman/Deliverer.py:116 +#, fuzzy msgid "%(listfullname)s mailing list reminder" msgstr "%(listfullname)s õðåíèýìéóç ëßóôáò" @@ -4407,6 +4535,7 @@ msgid "Hostile subscription attempt detected" msgstr "Åíôïðßóôçêå ðñïóðÜèåéá å÷èñéêÞò åããñáöÞò" #: Mailman/Deliverer.py:169 +#, fuzzy msgid "" "%(address)s was invited to a different mailing\n" "list, but in a deliberate malicious attempt they tried to confirm the\n" @@ -4419,6 +4548,7 @@ msgstr "" "ðñï÷ùñÞóåôå óå ðñüóèåôåò åíÝñãåéåò." #: Mailman/Deliverer.py:188 +#, fuzzy msgid "" "You invited %(address)s to your list, but in a\n" "deliberate malicious attempt, they tried to confirm the invitation to a\n" @@ -4432,6 +4562,7 @@ msgstr "" "ðñï÷ùñÞóåôå óå ðñüóèåôåò åíÝñãåéåò." #: Mailman/Deliverer.py:221 +#, fuzzy msgid "%(listname)s mailing list probe message" msgstr "ìÞíõìá åëÝã÷ïõ ëßóôáò áëëçëïãñáößáò %(listname)s" @@ -4642,8 +4773,8 @@ msgid "" " membership.\n" "\n" "

                    You can control both the\n" -" number\n" +" number\n" " of reminders the member will receive and the\n" " Ìðïñåßôå íá ñõèìßóåôå ôüóï ôïí\n" -" áñéèìü\n" +" áñéèìü\n" " ôùí õðåíèõìßóåùí ðïõ èá ëáìâÜíåé ôï ìÝëïò üóï êáé ôçí\n" " óõ÷íüôçôá \n" @@ -4887,8 +5018,8 @@ msgid "" " No those messages too will get discarded. You may " "want\n" " to set up an\n" -" autoresponse\n" +" autoresponse\n" " message for email to the -owner and -admin address." msgstr "" "Áí êáé ôï óýóôçìá åíôïðéóìïý ôùí åðéóôñåöüìåíùí ìçíõìÜôùí ôïõ Mailman\n" @@ -5000,6 +5131,7 @@ msgstr "" " ÷ñÞóôçò èá ãßíåôáé ðÜíôá." #: Mailman/Gui/Bounce.py:194 +#, fuzzy msgid "" "Bad value for %(property)s: %(val)s" @@ -5064,8 +5196,8 @@ msgstr "" " ëáìâÜíåôáé áðü ôç ëßóôá êáé Ý÷åé åíåñãïðïéçèåß ï Ýëåã÷ïò ôïõ \n" " ðåñéå÷ïìÝíïõ, ôá îå÷ùñéóôÜ åðéóõíáðôüìåíá áñ÷åßá óõãêñßíïíôáé " "ðñþôá\n" -" ìå ôïõò ôýðïõò\n" +" ìå ôïõò ôýðïõò\n" " ößëôñùí. Áí ï ôýðïò ôïõ åðéóõíáðôüìåíïõ áñ÷åßïõ ðåñéÝ÷åôáé " "óôç\n" " ëßóôá, ôï ìÞíõìá áðïññßðôåôáé.\n" @@ -5138,8 +5270,9 @@ msgstr "" " ðåñéå÷ïìÝíïõ, ð.÷. åéêüíá.\n" "

                    Ïé êåíÝò ãñáììÝò äåí ëáìâÜíïíôáé õðüøéí.\n" "\n" -"

                    Äåßôå åðßóçò pass_mime_types ãéá ôçí ëßóôá ìå ôïõò åðéôñåðüìåíïõò ôýðïõò\n" +"

                    Äåßôå åðßóçò pass_mime_types ãéá ôçí ëßóôá ìå ôïõò åðéôñåðüìåíïõò " +"ôýðïõò\n" " ðåñéå÷ïìÝíïõ (ëåõêÞ ëßóôá)." #: Mailman/Gui/ContentFilter.py:94 @@ -5157,8 +5290,8 @@ msgid "" "Use this option to remove each message attachment that does\n" " not have a matching content type. Requirements and formats " "are\n" -" exactly like filter_mime_types.\n" "\n" "

                    Note: if you add entries to this list but don't add\n" @@ -5168,8 +5301,8 @@ msgstr "" "ÁõôÞ ç åðéëïãÞ ðñÝðåé íá ÷ñçóéìïðïéåßôáé ãéá ôç äéáãñáöÞ êÜèå\n" " åðéóõíáðôüìåíïõ ðïõ äåí ôáéñéÜæåé ìå êÜðïéïí áðü áõôïýò ôïõò\n" " ôýðïõò ðåñéå÷ïìÝíïõ. Ïé áðáéôÞóåéò êáé ïé ìïñöÝò åßíáé\n" -" áêñéâþò üðùò filter_mime_types.\n" +" áêñéâþò üðùò filter_mime_types.\n" "\n" "

                    Óçìåßùóç: áí êÜíåôå íÝåò êáôá÷ùñÞóåéò áëëÜ äåí " "ðñïóèÝóåôå\n" @@ -5290,6 +5423,7 @@ msgstr "" " äéá÷åéñéóôÞ ôçò óåëßäáò." #: Mailman/Gui/ContentFilter.py:171 +#, fuzzy msgid "Bad MIME type ignored: %(spectype)s" msgstr "Áãíïåßôáé ï ëáíèáóìÝíïò MIME ôýðïò: %(spectype)s" @@ -5399,6 +5533,7 @@ msgstr "" " áõôÞ äåí åßíáé êåíÞ;" #: Mailman/Gui/Digest.py:145 +#, fuzzy msgid "" "The next digest will be sent as volume\n" " %(volume)s, number %(number)s" @@ -5415,16 +5550,19 @@ msgid "There was no digest to send." msgstr "Äåí õðÜñ÷åé óýíïøç íá áðïóôáëåß." #: Mailman/Gui/GUIBase.py:173 +#, fuzzy msgid "Invalid value for variable: %(property)s" msgstr "Ìç Ýãêõñç ôéìÞ ãéá ôç ìåôáâëçôÞ: %(property)s" #: Mailman/Gui/GUIBase.py:178 +#, fuzzy msgid "Bad email address for option %(property)s: %(error)s" msgstr "" "ËáíèáóìÝíç äéåýèõíóç çëåêôñïíéêïý ôá÷õäñïìåßïõ ãéá ôçí åðéëïãÞ %(property)s: " "%(error)s" #: Mailman/Gui/GUIBase.py:204 +#, fuzzy msgid "" "The following illegal substitution variables were\n" " found in the %(property)s string:\n" @@ -5441,6 +5579,7 @@ msgstr "" " áõôü ôï ðñüâëçìá." #: Mailman/Gui/GUIBase.py:218 +#, fuzzy msgid "" "Your %(property)s string appeared to\n" " have some correctable problems in its new value.\n" @@ -5551,8 +5690,8 @@ msgid "" "

                    In order to split the list ownership duties into\n" " administrators and moderators, you must\n" " set a separate moderator password,\n" -" and also provide the email\n" +" and also provide the email\n" " addresses of the list moderators. Note that the field you\n" " are changing here specifies the list administrators." msgstr "" @@ -5582,8 +5721,8 @@ msgstr "" " ôùí äéá÷åéñéóôþí êáé ôùí ìåóïëáâçôþí, ðñÝðåé íá\n" " íá ïñéóèåß Ýíáò îå÷ùñéóôüò êùäéêüò " "ðñüóâáóçò ãéá ôïí ìåóïëáâçôÞ,\n" -" êáé íá äïèïýí åðßóçò ïé email\n" +" êáé íá äïèïýí åðßóçò ïé email\n" " äéåõèýíóåéò ôùí ìåóïëáâçôþí ôçò ëßóôáò. ÐñÝðåé íá " "óçìåéùèåß üôé ôï ðåäßï ðïõ\n" " áëëÜæåôå åäþ êáèïñßæåé ôçí ëßóôá ôùí äéá÷åéñéóôþí." @@ -5907,13 +6046,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5945,8 +6084,8 @@ msgstr "" "ìÞíõìá áí áõôü\n" " åßíáé áðáñáßôçôï (Ç åðéëïãÞ ÑçôÞ äéåýèõíóç åéóÜãåé " "ôçí \n" -"ôéìÞ ôïõ áðÜíôçóç_óôçí_äéåýèõíóç).\n" +"ôéìÞ ôïõ áðÜíôçóç_óôçí_äéåýèõíóç).\n" " \n" "

                    ÕðÜñ÷ïõí ðïëý ëüãïé íá ìçí ÷ñçóéìïðïéçèåß Þ õðåñêáëõöèåß ç " "åðéêåöáëßäá\n" @@ -5962,8 +6101,8 @@ msgstr "" "harmful.html\">`Reply-to'\n" " Munging Considered Harmful \n" " ãéá ìéá ãåíéêÞ óõæÞôçóç áõôïý ôïõ èÝìáôïò.\n" -"Äåßôå ôï Reply-to\n" +"Äåßôå ôï Reply-to\n" " Munging Considered Useful\n" " ãéá ôçí áíôßèåôç Üðïøç.\n" "\n" @@ -5989,8 +6128,8 @@ msgstr " msgid "" "This is the address set in the Reply-To: header\n" " when the reply_goes_to_list\n" +" href=\"?VARHELP=general/" +"reply_goes_to_list\">reply_goes_to_list\n" " option is set to Explicit address.\n" "\n" "

                    There are many reasons not to introduce or override the\n" @@ -5998,13 +6137,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -6026,8 +6165,8 @@ msgid "" " Reply-To: header, it will not be changed." msgstr "" "ÁõôÞ åßíáé ç äéåýèõíóç ðïõ ïñßæåôáé óôçí åðéêåöáëßäá ÁðÜíôçóç-Óå:\n" -" üôáí ç áðÜíôçóç_ðçãáßíåé_óôçí_ëßóôá \n" +" üôáí ç áðÜíôçóç_ðçãáßíåé_óôçí_ëßóôá \n" " åðéëïãÞ ïñßæåôáé óôï ÑçôÞ äéåýèõíóç.\n" " \n" "

                    ÕðÜñ÷ïõí ðïëý ëüãïé ãéá íá ìçí ÷ñçóéìïðïéçèåß Þ íá " @@ -6043,8 +6182,8 @@ msgstr "" " Äåßôå ôï `Reply-To'\n" " Munging Considered Harmful \n" -" ãéá ìéá ãåíéêÞ óõæÞôçóç áõôïý ôïõ èÝìáôïò. Äåßôå ôï Reply-to \n" +" ãéá ìéá ãåíéêÞ óõæÞôçóç áõôïý ôïõ èÝìáôïò. Äåßôå ôï Reply-to \n" " Munging Considered Useful \n" " ãéá ôçí áíôßèåôç Üðïøç.\n" "\n" @@ -6118,8 +6257,8 @@ msgid "" " member list addresses, but rather to the owner of those member\n" " lists. In that case, the value of this setting is appended to\n" " the member's account name for such notices. `-owner' is the\n" -" typical choice. This setting has no effect when \"umbrella_list" -"\"\n" +" typical choice. This setting has no effect when " +"\"umbrella_list\"\n" " is \"No\"." msgstr "" "¼ôáí óôéò ñõèìßóåéò \"umbrella_list\", ïñßæåôáé ãéá íá äçëþóåé üôé áõôÞ ç " @@ -7226,6 +7365,7 @@ msgstr "" " ãéá Üëëïõò ÷ùñßò ôç óõãêáôÜèåóç ôïõò." #: Mailman/Gui/Privacy.py:104 +#, fuzzy msgid "" "This section allows you to configure subscription and\n" " membership exposure policy. You can also control whether this\n" @@ -7435,8 +7575,8 @@ msgid "" " either individually or as a group. Any\n" " posting from a non-member who is not explicitly accepted,\n" " rejected, or discarded, will have their posting filtered by the\n" -" general\n" +" general\n" " non-member rules.\n" "\n" "

                    In the text boxes below, add one address per line; start the\n" @@ -7478,15 +7618,15 @@ msgstr "" " åßôå áíåîÜñôçôá, åßôå óáí ïìÜäá. ÊÜèå áðïóôïëÞ åêôüò ôùí ìåëþí " "ç ïðïßá äåí ãßíåôáé áðïäåêôÞ, áðïññßðôåôáé, Þ áðïññßðôåôáé ÷ùñßò åíçìÝñùóç,\n" " èá ðåñÜóåé áðü ôçí åðåîåñãáóßá ôùí ößëôñùí\n" -" Ãåíéêïß\n" +" Ãåíéêïß\n" " êáíüíåò åêôüò ôùí ìåëþí.\n" "\n" "

                    Óôá áêüëïõèá ðëáßóéá êåéìÝíïõ, ðñïóèÝóôå ìéá äéåýèõíóç óå " "êÜèå ãñáììÞ. Áñ÷ßóôå \n" -" ôç ãñáììÞ ìå ôïí ÷áñáêôÞñá ^ ãéá íá äçëþóåôå ìéá Python regular expression. " -"¼ôáí åéóÜãåôå áíÜðïäåò êáèÝôïõò (backslashes), \n" +" ôç ãñáììÞ ìå ôïí ÷áñáêôÞñá ^ ãéá íá äçëþóåôå ìéá Python regular " +"expression. ¼ôáí åéóÜãåôå áíÜðïäåò êáèÝôïõò (backslashes), \n" "êÜíôå ôï óáí \n" " íá ÷ñçóéìïðïéïýóáôå áðëÝò óõìâïëïóåéñÝò Python (ð.÷. ìðïñåßôå " "ãåíéêÜ \n" @@ -7506,6 +7646,7 @@ msgstr "" "ôïí ìåóïëáâçôÞ;" #: Mailman/Gui/Privacy.py:218 +#, fuzzy msgid "" "Each list member has a moderation flag which says\n" " whether messages from the list member can be posted directly " @@ -7567,8 +7708,8 @@ msgid "" "If a member posts this many times, within a period of time\n" " the member is automatically moderated. Use 0 to disable. " "See\n" -" member_verbosity_interval for details on the time " "period.\n" "\n" @@ -8038,15 +8179,15 @@ msgid "" msgstr "" "¼ôáí ìéá áðïóôïëÞ åêôüò áðü ôá ìÝëç öôÜóåé, ï áðïóôïëÝáò ôïõ \n" " ìçíýìáôïò åëÝã÷åôáé ìå ôç ëßóôá ôùí ñçôÜ ïñéæïìÝíùí ùò\n" -" áðïäåêôþí,\n" -" êñáôçìÝíùí,\n" -" áðïññéðôÝùí ìå åíçìÝñùóç, êáé\n" -" áðïññéðôÝùí ÷ùñßò åíçìÝñùóç äéåõèýíóåùí. Áí äåí ôáéñéÜæåé, ôüôå " -"ãßíåôáé \n" +" áðïäåêôþí,\n" +" êñáôçìÝíùí,\n" +" áðïññéðôÝùí ìå åíçìÝñùóç, êáé\n" +" áðïññéðôÝùí ÷ùñßò åíçìÝñùóç äéåõèýíóåùí. Áí " +"äåí ôáéñéÜæåé, ôüôå ãßíåôáé \n" " ç åíÝñãåéá áõôÞ." #: Mailman/Gui/Privacy.py:490 @@ -8059,6 +8200,7 @@ msgstr "" "ôçò ëßóôáò;" #: Mailman/Gui/Privacy.py:494 +#, fuzzy msgid "" "Text to include in any rejection notice to be sent to\n" " non-members who post to this list. This notice can include\n" @@ -8336,6 +8478,7 @@ msgstr "" " Çìéôåëåßò êáíüíåò ößëôñùí èá áãíïïýíôáé." #: Mailman/Gui/Privacy.py:693 +#, fuzzy msgid "" "The header filter rule pattern\n" " '%(safepattern)s' is not a legal regular expression. This\n" @@ -8389,8 +8532,8 @@ msgid "" "

                    The body of the message can also be optionally scanned for\n" " Subject: and Keywords: headers, as\n" " specified by the topics_bodylines_limit\n" +" href=\"?VARHELP=topics/" +"topics_bodylines_limit\">topics_bodylines_limit\n" " configuration variable." msgstr "" "Ôï ößëôñï èÝìáôïò êáôçãïñéïðïéåß êÜèå åéóåñ÷üìåíï ìÞíõìá\n" @@ -8498,6 +8641,7 @@ msgstr "" " ÅëëéðÞ èÝìáôá èá áãíïçèïýí." #: Mailman/Gui/Topics.py:135 +#, fuzzy msgid "" "The topic pattern '%(safepattern)s' is not a\n" " legal regular expression. It will be discarded." @@ -8744,6 +8888,7 @@ msgid "%(listinfo_link)s list run by %(owner_link)s" msgstr "%(listinfo_link)s ç ëßóôá ôñÝ÷åé ìå %(owner_link)s" #: Mailman/HTMLFormatter.py:57 +#, fuzzy msgid "%(realname)s administrative interface" msgstr "Ðåñéâáëëïí äéá÷åßñéóçò ôïõ %(realname)s" @@ -8752,6 +8897,7 @@ msgid " (requires authorization)" msgstr " (áðáéôåß åîïõóéïäüôçóç)" #: Mailman/HTMLFormatter.py:61 +#, fuzzy msgid "Overview of all %(hostname)s mailing lists" msgstr "Áíáêåöáëáßùóç üëùí ôùí ëéóôþí óôïí %(hostname)s" @@ -8772,6 +8918,7 @@ msgid "; it was disabled by the list administrator" msgstr "· Þôáí áðåíåñãïðïéçìÝíï áðü ôïí äéá÷åéñéóôÞ ôçò ëßóôáò" #: Mailman/HTMLFormatter.py:146 +#, fuzzy msgid "" "; it was disabled due to excessive bounces. The\n" " last bounce was received on %(date)s" @@ -8785,6 +8932,7 @@ msgid "; it was disabled for unknown reasons" msgstr "· Þôáí áðåíåñãïðïéçìÝíï ãéá Üãíùóôïõò ëüãïõò" #: Mailman/HTMLFormatter.py:151 +#, fuzzy msgid "Note: your list delivery is currently disabled%(reason)s." msgstr "" "Óçìåßùóç: ç ðáñÜäïóç óôç ëßóôá åßíáé ôç óôéãìÞ áõôÞ áðåíåñãïðïéçìÝíç " @@ -8799,6 +8947,7 @@ msgid "the list administrator" msgstr "ï äéá÷åéñéóôÞò ôçò ëßóôáò" #: Mailman/HTMLFormatter.py:157 +#, fuzzy msgid "" "

                    %(note)s\n" "\n" @@ -8821,6 +8970,7 @@ msgstr "" " åñþôçóç Þ ÷ñåéÜæåóôå âïÞèåéá." #: Mailman/HTMLFormatter.py:169 +#, fuzzy msgid "" "

                    We have received some recent bounces from your\n" " address. Your current bounce score is %(score)s out of " @@ -8842,6 +8992,7 @@ msgstr "" " ôá ðñïâëÞìáôá äéïñèùèïýí óôï ìÝëëïí." #: Mailman/HTMLFormatter.py:181 +#, fuzzy msgid "" "(Note - you are subscribing to a list of mailing lists, so the %(type)s " "notice will be sent to the admin address for your membership, %(addr)s.)

                    " @@ -8892,6 +9043,7 @@ msgstr "" " ìåóïëáâçôÞ ìå email." #: Mailman/HTMLFormatter.py:208 +#, fuzzy msgid "" "This is %(also)sa private list, which means that the\n" " list of members is not available to non-members." @@ -8902,6 +9054,7 @@ msgstr "" "ìÝëç ôçò ëßóôáò." #: Mailman/HTMLFormatter.py:211 +#, fuzzy msgid "" "This is %(also)sa hidden list, which means that the\n" " list of members is available only to the list administrator." @@ -8910,6 +9063,7 @@ msgstr "" " ï ðßíáêáò ôùí ìåëþí åßíáé äéáèÝóéìïò óôïí äéá÷åéñéóôÞ ôçò ëßóôáò." #: Mailman/HTMLFormatter.py:214 +#, fuzzy msgid "" "This is %(also)sa public list, which means that the\n" " list of members list is available to everyone." @@ -8926,6 +9080,7 @@ msgstr "" " åßíáé åýêïëá áíáãíùñßóéìåò áðü ôïõò spammers)." #: Mailman/HTMLFormatter.py:222 +#, fuzzy msgid "" "

                    (Note that this is an umbrella list, intended to\n" " have only other mailing lists as members. Among other things,\n" @@ -8942,6 +9097,7 @@ msgid "either " msgstr "åßôå" #: Mailman/HTMLFormatter.py:256 +#, fuzzy msgid "" "To unsubscribe from %(realname)s, get a password reminder,\n" " or change your subscription options %(either)senter your " @@ -8977,6 +9133,7 @@ msgstr "" "\t\täéåýèõíóç óáò" #: Mailman/HTMLFormatter.py:277 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " members.)" @@ -8985,6 +9142,7 @@ msgstr "" " ôçò ëßóôáò.)" #: Mailman/HTMLFormatter.py:281 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " administrator.)" @@ -9047,6 +9205,7 @@ msgid "The current archive" msgstr "Ôï ôñÝ÷ïí éóôïñéêü áñ÷åßï" #: Mailman/Handlers/Acknowledge.py:59 +#, fuzzy msgid "%(realname)s post acknowledgement" msgstr "åðéâåâáßùóç ëÞøçò ôïõ ìçíýìáôïò ôïõ %(realname)s" @@ -9059,6 +9218,7 @@ msgid "" msgstr "" #: Mailman/Handlers/CalcRecips.py:79 +#, fuzzy msgid "" "Your urgent message to the %(realname)s mailing list was not authorized for\n" "delivery. The original message as received by Mailman is attached.\n" @@ -9136,6 +9296,7 @@ msgid "Message may contain administrivia" msgstr "Ôï ìÞíõìá ìðïñåß íá ðåñéëáìâÜíåé administrivia " #: Mailman/Handlers/Hold.py:84 +#, fuzzy msgid "" "Please do *not* post administrative requests to the mailing\n" "list. If you wish to subscribe, visit %(listurl)s or send a message with " @@ -9180,10 +9341,12 @@ msgid "Posting to a moderated newsgroup" msgstr "ÁðïóôïëÞ óå ìéá ïìÜäá óõæçôÞóåùí ìå ìåóïëáâçôÞ" #: Mailman/Handlers/Hold.py:252 +#, fuzzy msgid "Your message to %(listname)s awaits moderator approval" msgstr "Ôï ìÞíõìá óáò óôçí %(listname)s ðáñéìÝíåé ôçí Ýãêñéóç ôïõ ìåóïëáâçôÞ" #: Mailman/Handlers/Hold.py:271 +#, fuzzy msgid "%(listname)s post from %(sender)s requires approval" msgstr "Ôï ìÞíõìá óôçí %(listname)s áðü ôïí %(sender)s áðáéôåß Ýãêñéóç" @@ -9227,6 +9390,7 @@ msgid "After content filtering, the message was empty" msgstr "ÌåôÜ ôï öéëôñÜñéóìá ôïõ ðåñéå÷ïìÝíïõ, ôï ìÞíõìá Ýìåéíå êåíü" #: Mailman/Handlers/MimeDel.py:269 +#, fuzzy msgid "" "The attached message matched the %(listname)s mailing list's content " "filtering\n" @@ -9271,6 +9435,7 @@ msgid "The attached message has been automatically discarded." msgstr "Ôï åðéóõíáðôüìåíï ìÞíõìá áðïññßöèçêå áõôüìáôá ÷ùñßò åíçìÝñùóç" #: Mailman/Handlers/Replybot.py:75 +#, fuzzy msgid "Auto-response for your message to the \"%(realname)s\" mailing list" msgstr "Áõôüìáôç áðÜíôçóç ãéá ôï ìÞíõìá óáò óôçí ëßóôá \"%(realname)s\"" @@ -9279,6 +9444,7 @@ msgid "The Mailman Replybot" msgstr "Óõóôçìá áõôüìáôçò áðÜíôçóçò ôïõ Mailman" #: Mailman/Handlers/Scrubber.py:208 +#, fuzzy msgid "" "An embedded and charset-unspecified text was scrubbed...\n" "Name: %(filename)s\n" @@ -9294,6 +9460,7 @@ msgid "HTML attachment scrubbed and removed" msgstr "To óçíçìÝíï HTML êáèáñßóôçêå êáé áðïìáêñýíèçêå" #: Mailman/Handlers/Scrubber.py:234 Mailman/Handlers/Scrubber.py:259 +#, fuzzy msgid "" "An HTML attachment was scrubbed...\n" "URL: %(url)s\n" @@ -9314,6 +9481,7 @@ msgid "unknown sender" msgstr "Üãíùóôïò áðïóôïëÝáò" #: Mailman/Handlers/Scrubber.py:276 +#, fuzzy msgid "" "An embedded message was scrubbed...\n" "From: %(who)s\n" @@ -9330,6 +9498,7 @@ msgstr "" "Äéêôõáêüò Ôüðïò: %(url)s\n" #: Mailman/Handlers/Scrubber.py:308 +#, fuzzy msgid "" "A non-text attachment was scrubbed...\n" "Name: %(filename)s\n" @@ -9346,6 +9515,7 @@ msgstr "" "Äéêôõáêüò Ôüðïò: %(url)s\n" #: Mailman/Handlers/Scrubber.py:347 +#, fuzzy msgid "Skipped content of type %(partctype)s\n" msgstr "ÐáñÜëçøç ðåñéå÷ïìÝíïõ ìå ôýðï %(partctype)s\n" @@ -9377,6 +9547,7 @@ msgid "Message rejected by filter rule match" msgstr "Ôï ìÞíõìá áðïññßöèçêå åðåéäÞ ôáéñéÜæåé óå Ýíáí êáíüíá öéëôñáñßóìáôïò " #: Mailman/Handlers/ToDigest.py:173 +#, fuzzy msgid "%(realname)s Digest, Vol %(volume)d, Issue %(issue)d" msgstr "%(realname)s Óýíïøç, Vol %(volume)d, äçìïóßåõóç %(issue)d" @@ -9413,6 +9584,7 @@ msgid "End of " msgstr "ÔÝëïò ôïõ " #: Mailman/ListAdmin.py:309 +#, fuzzy msgid "Posting of your message titled \"%(subject)s\"" msgstr "ÁðïóôïëÞ ôïõ ìçíýìáôïò óáò ìå ôßôëï \"%(subject)s\"" @@ -9425,6 +9597,7 @@ msgid "Forward of moderated message" msgstr "Ðñïþèçóç ìçíýìáôïò åãêåêñéìÝíïõ áðü ôïí ìåóïëáâçôÞ" #: Mailman/ListAdmin.py:405 +#, fuzzy msgid "New subscription request to list %(realname)s from %(addr)s" msgstr "ÍÝï áßôçìá åããñáöÞò óôçí ëßóôá %(realname)s áðü %(addr)s" @@ -9438,6 +9611,7 @@ msgid "via admin approval" msgstr "ÓõíÝ÷éóç áíáìïíÞò Ýãêñéóçò" #: Mailman/ListAdmin.py:466 +#, fuzzy msgid "New unsubscription request from %(realname)s by %(addr)s" msgstr "ÍÝï áßôçìá äéáãñáöÞò áðü ôçí %(realname)s áðü %(addr)s" @@ -9450,10 +9624,12 @@ msgid "Original Message" msgstr "Áñ÷éêü ìÞíõìá" #: Mailman/ListAdmin.py:526 +#, fuzzy msgid "Request to mailing list %(realname)s rejected" msgstr "Ôï áßôçìá ðñïò ôç ëßóôá %(realname)s áðïññßöèçêå" #: Mailman/MTA/Manual.py:66 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been created via the through-the-web\n" "interface. In order to complete the activation of this mailing list, the\n" @@ -9481,14 +9657,17 @@ msgstr "" "ðéèáíüí åêôåëþíôáò ôï ðñüãñáììá `newaliases'\n" #: Mailman/MTA/Manual.py:82 +#, fuzzy msgid "## %(listname)s mailing list" msgstr "## %(listname)s ëßóôá" #: Mailman/MTA/Manual.py:99 +#, fuzzy msgid "Mailing list creation request for list %(listname)s" msgstr "Áßôçìá äçìéïõñãßáò ëßóôáò ãéá ôçí ëßóôá %(listname)s" #: Mailman/MTA/Manual.py:113 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been removed via the through-the-web\n" "interface. In order to complete the de-activation of this mailing list, " @@ -9508,6 +9687,7 @@ msgstr "" "äéáãñáöïýí:\n" #: Mailman/MTA/Manual.py:123 +#, fuzzy msgid "" "\n" "To finish removing your mailing list, you must edit your /etc/aliases (or\n" @@ -9526,14 +9706,17 @@ msgstr "" "## %(listname)s ëßóôá" #: Mailman/MTA/Manual.py:142 +#, fuzzy msgid "Mailing list removal request for list %(listname)s" msgstr "Áßôçìá äéáãñáöÞò ãéá ôçí ëßóôá %(listname)s" #: Mailman/MTA/Postfix.py:442 +#, fuzzy msgid "checking permissions on %(file)s" msgstr "Ýëåã÷ïò äéêáéùìÜôùí óôï %(file)s" #: Mailman/MTA/Postfix.py:452 +#, fuzzy msgid "%(file)s permissions must be 0664 (got %(octmode)s)" msgstr "" " Ôá äéêáéþìáôá ôïõ %(file)s ðñÝðåé íá åßíáé 0664 (åíþ åßíáé %(octmode)s)" @@ -9548,37 +9731,45 @@ msgid "(fixing)" msgstr "(äéüñèùóç)" #: Mailman/MTA/Postfix.py:470 +#, fuzzy msgid "checking ownership of %(dbfile)s" msgstr "Ýëåã÷ïò éäéïêôÞôç ôïõ %(dbfile)s" #: Mailman/MTA/Postfix.py:478 +#, fuzzy msgid "%(dbfile)s owned by %(owner)s (must be owned by %(user)s" msgstr "" "Ôï áñ÷åßï %(dbfile)s Ý÷åé éäéïêôÞôç %(owner)s (ðñÝðåé íá Ý÷åé éäéïêôÞôç " "%(user)s" #: Mailman/MTA/Postfix.py:491 +#, fuzzy msgid "%(dbfile)s permissions must be 0664 (got %(octmode)s)" msgstr "" " Ôá äéêáéþìáôá ôïõ %(dbfile)s ðñÝðåé íá åßíáé 0664 (åíþ åßíáé %(octmode)s)" #: Mailman/MailList.py:219 +#, fuzzy msgid "Your confirmation is required to join the %(listname)s mailing list" msgstr "¸÷åôå ðñïóêëçèåß íá óõíäåèåßôå óôçí ëßóôá %(listname)s" #: Mailman/MailList.py:230 +#, fuzzy msgid "Your confirmation is required to leave the %(listname)s mailing list" msgstr "Äåí åßóôå ìÝëïò ôçò %(listname)s ëßóôáò" #: Mailman/MailList.py:999 Mailman/MailList.py:1492 +#, fuzzy msgid " from %(remote)s" msgstr " áðü ôçí ip %(remote)s" #: Mailman/MailList.py:1043 +#, fuzzy msgid "subscriptions to %(realname)s require moderator approval" msgstr "ç åããñáöÞ óôçí %(realname)s áðáéôåß ôçí Ýãêñéóç ôïõ ìåóïëáâçôÞ" #: Mailman/MailList.py:1125 bin/add_members:299 +#, fuzzy msgid "%(realname)s subscription notification" msgstr "åéäïðïßçóç åããñáöÞò óôçí %(realname)s" @@ -9587,6 +9778,7 @@ msgid "unsubscriptions require moderator approval" msgstr "ç äéáãñáöÞ áðáéôåß ôçí Ýãêñéóç ôïõ ìåóïëáâçôÞ" #: Mailman/MailList.py:1166 +#, fuzzy msgid "%(realname)s unsubscribe notification" msgstr "åéäïðïßçóç äéáãñáöÞò áðü ôçí %(realname)s" @@ -9606,6 +9798,7 @@ msgid "via web confirmation" msgstr "Ìç Ýãêõñç áêïõëïõèßá åðéâåâáßùóçò" #: Mailman/MailList.py:1396 +#, fuzzy msgid "subscriptions to %(name)s require administrator approval" msgstr "ç åããñáöÞ óôçí %(name)s áðáéôåß ôçí Ýãêñéóç ôïõ äéá÷åéñéóôÞ" @@ -9624,6 +9817,7 @@ msgid "Last autoresponse notification for today" msgstr "Ôåëåõôáßá åéäïðïßçóç áõôüìáôçò áðÜíôçóçò ãéá óÞìåñá" #: Mailman/Queue/BounceRunner.py:360 +#, fuzzy msgid "" "The attached message was received as a bounce, but either the bounce format\n" "was not recognized, or no member addresses could be extracted from it. " @@ -9715,6 +9909,7 @@ msgid "Original message suppressed by Mailman site configuration\n" msgstr "" #: Mailman/htmlformat.py:675 +#, fuzzy msgid "Delivered by Mailman
                    version %(version)s" msgstr "Ðáñáäüèçêå ìÝóù ôïõ Mailman
                    Ýêäïóç %(version)s" @@ -9803,6 +9998,7 @@ msgid "Server Local Time" msgstr "ÔïðéêÞ ¿ñá ÅîõðçñåôçôÞ" #: Mailman/i18n.py:180 +#, fuzzy msgid "" "%(wday)s %(mon)s %(day)2i %(hh)02i:%(mm)02i:%(ss)02i %(tzname)s %(year)04i" msgstr "" @@ -9919,6 +10115,7 @@ msgstr "" "áñ÷åßá ðñÝðåé íá åßíáé `-'.\n" #: bin/add_members:162 bin/add_members:172 +#, fuzzy msgid "Already a member: %(member)s" msgstr "Åßíáé Þäç ìÝëïò: %(member)s" @@ -9927,10 +10124,12 @@ msgid "Bad/Invalid email address: blank line" msgstr "ÊáêÞ/¢êõñç äéåýèõíóç email: ç êåíÞ ãñáììÞ " #: bin/add_members:180 +#, fuzzy msgid "Bad/Invalid email address: %(member)s" msgstr "ÊáêÞ/¢êõñç äéåýèõíóç email: %(member)s" #: bin/add_members:182 +#, fuzzy msgid "Hostile address (illegal characters): %(member)s" msgstr "Å÷èñéêÞ äéåýèõíóç (áíåðßôñåðôïé ÷áñáêôÞñåò): %(member)s" @@ -9940,14 +10139,17 @@ msgid "Invited: %(member)s" msgstr "ÅããñáöÞ óôç ëßóôá %(member)s" #: bin/add_members:187 +#, fuzzy msgid "Subscribed: %(member)s" msgstr "ÅããñáöÞ óôç ëßóôá %(member)s" #: bin/add_members:237 +#, fuzzy msgid "Bad argument to -w/--welcome-msg: %(arg)s" msgstr "ÅóöáëìÝíï üñéóìá ôï -w/--welcome-msg: %(arg)s" #: bin/add_members:244 +#, fuzzy msgid "Bad argument to -a/--admin-notify: %(arg)s" msgstr "ÅóöáëìÝíï üñéóìá ôï -a/--admin-notify: %(arg)s" @@ -9964,6 +10166,7 @@ msgstr "" #: bin/add_members:261 bin/config_list:110 bin/export.py:271 bin/find_member:97 #: bin/inject:91 bin/list_admins:90 bin/list_members:252 bin/sync_members:222 #: cron/bumpdigests:86 +#, fuzzy msgid "No such list: %(listname)s" msgstr "Äåí õðÜñ÷åé ôÝôïéá ëßóôá: %(listname)s" @@ -9974,6 +10177,7 @@ msgid "Nothing to do." msgstr "Äåí õðÜñ÷åé ôßðïôå ðñïò åíÝñãåéá." #: bin/arch:19 +#, fuzzy msgid "" "Rebuild a list's archive.\n" "\n" @@ -10073,6 +10277,7 @@ msgid "listname is required" msgstr "áðáéôåßôáé ôï üíïìá ôçò ëßóôáò" #: bin/arch:143 bin/change_pw:107 bin/config_list:257 +#, fuzzy msgid "" "No such list \"%(listname)s\"\n" "%(e)s" @@ -10081,10 +10286,12 @@ msgstr "" "%(e)s" #: bin/arch:168 +#, fuzzy msgid "Cannot open mbox file %(mbox)s: %(msg)s" msgstr "Äåí ìðïñþ íá áíïßîù ôï ãñáììáôïêéâþôéï %(mbox)s: %(msg)s" #: bin/b4b5-archfix:19 +#, fuzzy msgid "" "Fix the MM2.1b4 archives.\n" "\n" @@ -10240,6 +10447,7 @@ msgstr "" " Åêôõðþíåé ôï âïçèçôéêü ìÞíõìá êáé åîÝñ÷åôáé.\n" #: bin/change_pw:145 +#, fuzzy msgid "Bad arguments: %(strargs)s" msgstr "ÅóöáëìÝíá ïñßóìáôá: %(strargs)s" @@ -10248,14 +10456,17 @@ msgid "Empty list passwords are not allowed" msgstr "Äåí åðéôñÝðåôáé êåíÞ ëßóôá êùäéêþí ðñüóâáóçò" #: bin/change_pw:181 +#, fuzzy msgid "New %(listname)s password: %(notifypassword)s" msgstr "ÍÝïò %(listname)s êùäéêüò ðñüóâáóçò: %(notifypassword)s" #: bin/change_pw:190 +#, fuzzy msgid "Your new %(listname)s list password" msgstr "Ï êùäéêüò ðñüóâáóçò ôçò íÝáò óáò ëßóôáò %(listname)s " #: bin/change_pw:191 +#, fuzzy msgid "" "The site administrator at %(hostname)s has changed the password for your\n" "mailing list %(listname)s. It is now\n" @@ -10286,6 +10497,7 @@ msgstr "" " %(adminurl)s\n" #: bin/check_db:19 +#, fuzzy msgid "" "Check a list's config database file for integrity.\n" "\n" @@ -10366,10 +10578,12 @@ msgid "List:" msgstr "Ëßóôá:" #: bin/check_db:148 +#, fuzzy msgid " %(file)s: okay" msgstr " %(file)s: åßíáé åíôÜîåé" #: bin/check_perms:20 +#, fuzzy msgid "" "Check the permissions for the Mailman installation.\n" "\n" @@ -10392,44 +10606,54 @@ msgstr "" "\n" #: bin/check_perms:110 +#, fuzzy msgid " checking gid and mode for %(path)s" msgstr " Ýëåã÷ïò gid êáé mode ãéá %(path)s" #: bin/check_perms:122 +#, fuzzy msgid "%(path)s bad group (has: %(groupname)s, expected %(MAILMAN_GROUP)s)" msgstr "" "%(path)s ëÜèïò group (has: %(groupname)s, áðáéôåßôáé %(MAILMAN_GROUP)s)" #: bin/check_perms:151 +#, fuzzy msgid "directory permissions must be %(octperms)s: %(path)s" msgstr "ôá äéêáéþìáôá ôïõ êáôáëüãïõ ðñÝðåé íá åßíáé %(octperms)s: %(path)s" #: bin/check_perms:160 +#, fuzzy msgid "source perms must be %(octperms)s: %(path)s" msgstr "" "ôá äéêáéþìáôá ôçò ðçãÞò (source) ðñÝðåé íá åßíáé %(octperms)s: %(path)s" #: bin/check_perms:171 +#, fuzzy msgid "article db files must be %(octperms)s: %(path)s" msgstr "Ôá áñ÷åßá db Üñèñïõ ðñÝðåé íá åßíáé %(octperms)s: %(path)s" #: bin/check_perms:183 +#, fuzzy msgid "checking mode for %(prefix)s" msgstr "Ýëåã÷ïò ôïõ ôñüðïõ ãéá %(prefix)s" #: bin/check_perms:193 +#, fuzzy msgid "WARNING: directory does not exist: %(d)s" msgstr "ÐÑÏÅÉÄÏÐÏÉÇÓÇ: ï êáôÜëïãïò äåí õðÜñ÷åé: %(d)s" #: bin/check_perms:197 +#, fuzzy msgid "directory must be at least 02775: %(d)s" msgstr "ï êáôÜëïãïò ðñÝðåé íá Ý÷åé ôïõëÜ÷éóôïí 02775: %(d)s" #: bin/check_perms:209 +#, fuzzy msgid "checking perms on %(private)s" msgstr "Ýëåã÷ïò äéêáéùìÜôùí óôï %(private)s" #: bin/check_perms:214 +#, fuzzy msgid "%(private)s must not be other-readable" msgstr "%(private)s äåí ðñÝðåé íá åßíáé other-readable" @@ -10453,6 +10677,7 @@ msgid "mbox file must be at least 0660:" msgstr "Ôï áñ÷åßï mbox ðñÝðåé íá åßíáé ôïõëÜ÷éóôïí 0660:" #: bin/check_perms:263 +#, fuzzy msgid "%(dbdir)s \"other\" perms must be 000" msgstr "%(dbdir)s \"others\" perms ðñÝðåé íá åßíáé 000" @@ -10461,26 +10686,32 @@ msgid "checking cgi-bin permissions" msgstr "Ýëåã÷ïò ôùí cgi-bin permissions" #: bin/check_perms:278 +#, fuzzy msgid " checking set-gid for %(path)s" msgstr "Ýëåã÷ïò set-gid ãéá %(path)s" #: bin/check_perms:282 +#, fuzzy msgid "%(path)s must be set-gid" msgstr "%(path)s ðñÝðåé íá åßíáé set-gid" #: bin/check_perms:292 +#, fuzzy msgid "checking set-gid for %(wrapper)s" msgstr "Ýëåã÷ïò set-gid ãéá %(wrapper)s" #: bin/check_perms:296 +#, fuzzy msgid "%(wrapper)s must be set-gid" msgstr "%(wrapper)s ðñÝðåé íá åßíáé set-gid" #: bin/check_perms:306 +#, fuzzy msgid "checking permissions on %(pwfile)s" msgstr "Ýëåã÷ïò ôùí permissions %(pwfile)s" #: bin/check_perms:315 +#, fuzzy msgid "%(pwfile)s permissions must be exactly 0640 (got %(octmode)s)" msgstr "" "%(pwfile)s ôá permissions ðñÝðåé íá åßíáé áêñéâþò 0640 (got %(octmode)s)" @@ -10490,10 +10721,12 @@ msgid "checking permissions on list data" msgstr "Ýëåã÷ïò ôùí permissions óôá óôïé÷åßá êáôáëüãùí" #: bin/check_perms:348 +#, fuzzy msgid " checking permissions on: %(path)s" msgstr "\tÝëåã÷ïò ôùí permissions óôï: %(path)s" #: bin/check_perms:356 +#, fuzzy msgid "file permissions must be at least 660: %(path)s" msgstr "ôá permissions ôïõ áñ÷åßïõ ðñÝðåé íá åßíáé ôïõëÜ÷éóôïí 660: %(path)s" @@ -10586,6 +10819,7 @@ msgid "Unix-From line changed: %(lineno)d" msgstr "Unix-Áðü ôç ãñáììÞ ðïõ áëëÜæïõí: %(lineno)d" #: bin/cleanarch:111 +#, fuzzy msgid "Bad status number: %(arg)s" msgstr "Êáêüò áñéèìüò èÝóçò: %(arg)s" @@ -10743,10 +10977,12 @@ msgid " original address removed:" msgstr " ç áñ÷éêÞ äéåýèõíóç äéáãñÜöçêå:" #: bin/clone_member:202 +#, fuzzy msgid "Not a valid email address: %(toaddr)s" msgstr "Ìç Ýãêõñç email äéåýèõíóç: %(toaddr)s" #: bin/clone_member:215 +#, fuzzy msgid "" "Error opening list \"%(listname)s\", skipping.\n" "%(e)s" @@ -10870,6 +11106,7 @@ msgstr "" "\n" #: bin/config_list:118 +#, fuzzy msgid "" "# -*- python -*-\n" "# -*- coding: %(charset)s -*-\n" @@ -10890,22 +11127,27 @@ msgid "legal values are:" msgstr "åðéôñåðôÝò ôéìÝò åßíáé:" #: bin/config_list:270 +#, fuzzy msgid "attribute \"%(k)s\" ignored" msgstr "ï ÷áñáêôÞñáò \"%(k)s\" áãíïÞèçêå" #: bin/config_list:273 +#, fuzzy msgid "attribute \"%(k)s\" changed" msgstr "ï ÷áñáêôÞñáò \"%(k)s\" Üëëáîå" #: bin/config_list:279 +#, fuzzy msgid "Non-standard property restored: %(k)s" msgstr "Ìç áðïäåêôÞ éäéüôçôá åðáíïñèþèçêå: %(k)s" #: bin/config_list:288 +#, fuzzy msgid "Invalid value for property: %(k)s" msgstr "Áêõñç ôéìÞ ãéá ôçí éäéüôçôá: %(k)s" #: bin/config_list:291 +#, fuzzy msgid "Bad email address for option %(k)s: %(v)s" msgstr "ÊáêÞ email äéåýèõíóç ãéá ôçí åðéëïãÞ %(k)s: %(v)s" @@ -10971,20 +11213,24 @@ msgstr "" " ×ùñßò åêôýðùóç ìçíõìÜôùí êáôÜóôáóçò.\n" #: bin/discard:94 +#, fuzzy msgid "Ignoring non-held message: %(f)s" msgstr "ÁãíïÞóôå ôï ìç-êñáôçìÝíï ìÞíõìá: %(f)s" #: bin/discard:100 +#, fuzzy msgid "Ignoring held msg w/bad id: %(f)s" msgstr "ÁãíïÞóôå ôï êñáôçìÝíï ìÞíõìá ìå ôï êáêü ÷áñáêôçñéóôéêü: %(f)s" #: bin/discard:112 +#, fuzzy msgid "Discarded held msg #%(id)s for list %(listname)s" msgstr "" "Áðïññßöèçêå ÷ùñßò åíçìÝñùóç ôï êñáôçìÝíï ìÞíõìá #%(id)s ãéá ôçí ëßóôá " "%(listname)s" #: bin/dumpdb:19 +#, fuzzy msgid "" "Dump the contents of any Mailman `database' file.\n" "\n" @@ -11060,6 +11306,7 @@ msgid "No filename given." msgstr "Äåí äüèçêå êáíÝíá üíïìá áñ÷åßïõ." #: bin/dumpdb:108 +#, fuzzy msgid "Bad arguments: %(pargs)s" msgstr "ËÜèïò ïñßóìáôá: %(pargs)s" @@ -11068,14 +11315,17 @@ msgid "Please specify either -p or -m." msgstr "Ðáñáêáëþ äéåõêñéíßóôå åßôå -p Þ ôï -m" #: bin/dumpdb:133 +#, fuzzy msgid "[----- start %(typename)s file -----]" msgstr "[----- start %(typename)s file-----]" #: bin/dumpdb:139 +#, fuzzy msgid "[----- end %(typename)s file -----]" msgstr "[----- end %(typename)s file -----]" #: bin/dumpdb:142 +#, fuzzy msgid "<----- start object %(cnt)s ----->" msgstr "<----- start object %(cnt)s ----->" @@ -11304,6 +11554,7 @@ msgid "Setting web_page_url to: %(web_page_url)s" msgstr "Ñýèìéóç ôïõ web_page_url óå: %(web_page_url)s" #: bin/fix_url.py:88 +#, fuzzy msgid "Setting host_name to: %(mailhost)s" msgstr "Ñýèìéóç ôïõ host_name óå: %(mailhost)s" @@ -11344,6 +11595,7 @@ msgstr "" " Åêôõðþíåé ôï ìÞíõìá êáé åîÝñ÷åôáé.\n" #: bin/genaliases:84 +#, fuzzy msgid "genaliases can't do anything useful with mm_cfg.MTA = %(mta)s." msgstr "" "ôï genaliases äåí ìðïñåß íá êÜíåé êÜôé ÷ñÞóéìï ìå ôï mm_cfg.MTA = %(mta)s. " @@ -11402,6 +11654,7 @@ msgstr "" "÷ñçóéìïðïéåßôáé ç ôõðïðïéçìÝíç åßóïóäïò (standard input).\n" #: bin/inject:84 +#, fuzzy msgid "Bad queue directory: %(qdir)s" msgstr "Êáêüò êáôÜëïãïò ïõñþí áíáìïíÞò: %(qdir)s" @@ -11410,6 +11663,7 @@ msgid "A list name is required" msgstr "Áðáéôåßôáé ôï üíïìá ôçò ëßóôáò" #: bin/list_admins:20 +#, fuzzy msgid "" "List all the owners of a mailing list.\n" "\n" @@ -11457,6 +11711,7 @@ msgstr "" "íá Ý÷åôå ðåñéóóüôåñåò áðü ìéá ïíïìáóôéêÝò ëßóôåò óôçí ãñáììÞ åíôïëþí.\n" #: bin/list_admins:97 +#, fuzzy msgid "List: %(listname)s, \tOwners: %(owners)s" msgstr "Ëßóôá: %(listname)s, \tÉäéïêôÞôåò: %(owners)s" @@ -11650,10 +11905,12 @@ msgstr "" "êáôÜóôáóç ôïõò.\n" #: bin/list_members:198 +#, fuzzy msgid "Bad --nomail option: %(why)s" msgstr "ÊáêÞ åðéëïãÞ --nomail: %(why)s" #: bin/list_members:209 +#, fuzzy msgid "Bad --digest option: %(kind)s" msgstr "ÊáêÞ åðéëïãÞ --digest: %(kind)s" @@ -11667,6 +11924,7 @@ msgid "Could not open file for writing:" msgstr "Äåí èá ìðïñïýóå íá áíïßîåé ôï áñ÷åßï ãéá ôï ãñÜøéìï:" #: bin/list_owners:20 +#, fuzzy msgid "" "List the owners of a mailing list, or all mailing lists.\n" "\n" @@ -11722,6 +11980,7 @@ msgid "" msgstr "" #: bin/mailmanctl:20 +#, fuzzy msgid "" "Primary start-up and shutdown script for Mailman's qrunner daemon.\n" "\n" @@ -11946,6 +12205,7 @@ msgid "" msgstr "" #: bin/mailmanctl:280 cron/mailpasswds:119 +#, fuzzy msgid "Site list is missing: %(sitelistname)s" msgstr "Ç ëßóôá ôçò ôïðïèåóßáò (site) äåí õðÜñ÷åé Þäç: %(sitelistname)s" @@ -11958,6 +12218,7 @@ msgid "No command given." msgstr "Äåí äüèçêå åíôïëÞ" #: bin/mailmanctl:339 +#, fuzzy msgid "Bad command: %(command)s" msgstr "ËÜèïòåíôïëÞ: %(command)s" @@ -11982,6 +12243,7 @@ msgid "Starting Mailman's master qrunner." msgstr "" #: bin/mmsitepass:19 +#, fuzzy msgid "" "Set the site password, prompting from the terminal.\n" "\n" @@ -12038,6 +12300,7 @@ msgid "list creator" msgstr "Äçìéïõñãüò ôçò ëßóôáò" #: bin/mmsitepass:86 +#, fuzzy msgid "New %(pwdesc)s password: " msgstr "ÍÝïò Êùäéêüò ðñüóâáóçò ãéá %(pwdesc)s " @@ -12196,8 +12459,9 @@ msgid "" msgstr "" #: bin/newlist:162 +#, fuzzy msgid "Unknown language: %(lang)s" -msgstr "" +msgstr "¢ãíùóôç ëßóôá: %(listname)s" #: bin/newlist:167 msgid "Enter the name of the list: " @@ -12208,6 +12472,7 @@ msgid "Enter the email of the person running the list: " msgstr "" #: bin/newlist:193 +#, fuzzy msgid "Initial %(listname)s password: " msgstr "Áñ÷éêüò êùäéêüò ôçò %(listname)s :" @@ -12217,8 +12482,8 @@ msgstr " #: bin/newlist:220 msgid "" -" - owner addresses need to be fully-qualified names like \"owner@example.com" -"\", not just \"owner\"." +" - owner addresses need to be fully-qualified names like \"owner@example." +"com\", not just \"owner\"." msgstr "" #: bin/newlist:244 @@ -12427,18 +12692,22 @@ msgstr "" " addr1 ... Ïé åðéðëÝïí äéåõèýíóåéò ðïõ èá áöáéñåèïýí. \n" #: bin/remove_members:156 +#, fuzzy msgid "Could not open file for reading: %(filename)s." msgstr "Äåí Þôáí äõíáôü ôï Üíïéãìá ôïõ áñ÷åßïõ ãéá áíÜãíùóç: %(filename)s." #: bin/remove_members:163 +#, fuzzy msgid "Error opening list %(listname)s... skipping." msgstr "ËÜèïò óôï Üíïéãìá ôçò ëßóôáò %(listname)s ... ðáñáêÜìðôåôáé." #: bin/remove_members:173 +#, fuzzy msgid "No such member: %(addr)s" msgstr "Äåí õðÜñ÷åé ôÝôïéï ìÝëïò: %(addr)s" #: bin/remove_members:178 +#, fuzzy msgid "User `%(addr)s' removed from list: %(listname)s." msgstr "Ï ÷ñÞóôçò '%(addr)s' äéáãñÜöçêå áðü ôç ëßóôá: %(listname)s." @@ -12480,10 +12749,12 @@ msgstr "" " Eêôõðþíåé ôé êÜíåé ôï script.\n" #: bin/reset_pw.py:77 +#, fuzzy msgid "Changing passwords for list: %(listname)s" msgstr "ÁëëáãÞ ôùí êùäéêþí ðñüóâáóçò ãéá ôç ëßóôá %(listname)s" #: bin/reset_pw.py:83 +#, fuzzy msgid "New password for member %(member)40s: %(randompw)s" msgstr "ÍÝïò êùäéêüò ðñüóâáóçò ãéá ôï ìÝëïò %(member)40s: %(randompw)s" @@ -12531,18 +12802,22 @@ msgstr "" "\n" #: bin/rmlist:73 bin/rmlist:76 +#, fuzzy msgid "Removing %(msg)s" msgstr "ÁðáëïéöÞ %(msg)s" #: bin/rmlist:81 +#, fuzzy msgid "%(listname)s %(msg)s not found as %(filename)s" msgstr "%(listname)s %(msg)s äåí õðÜñ÷åé óáí %(filename)s" #: bin/rmlist:105 +#, fuzzy msgid "No such list (or list already deleted): %(listname)s" msgstr "Äåí õðÜñ÷åé ôÝôïéá ëßóôá (Þ ç ëßóôá Ý÷åé Þäç äéáãñáöåß): %(listname)s" #: bin/rmlist:108 +#, fuzzy msgid "No such list: %(listname)s. Removing its residual archives." msgstr "" "Äåí õðÜñ÷åé ôÝôïéá ëßóôá: %(listname)s. ÄéáãñáöÞ ôùí éóôïñéêþí áñ÷åßùí ðïõ " @@ -12674,6 +12949,7 @@ msgid "" msgstr "" #: bin/sync_members:115 +#, fuzzy msgid "Bad choice: %(yesno)s" msgstr "ÊáêÞ åðéëïãÞ: %(yesno)s" @@ -12690,6 +12966,7 @@ msgid "No argument to -f given" msgstr "Äå äüèçêå üñéóìá óôï -f" #: bin/sync_members:172 +#, fuzzy msgid "Illegal option: %(opt)s" msgstr "Ìç Ýãêõñç åðéëïãÞ: %(opt)s" @@ -12702,6 +12979,7 @@ msgid "Must have a listname and a filename" msgstr "Èá ðñÝðåé íá äïèåß Ýíá üíïìá ëßóôáò êáé Ýíáí üíïìá áñ÷åßïõ" #: bin/sync_members:191 +#, fuzzy msgid "Cannot read address file: %(filename)s: %(msg)s" msgstr "Äåí ìðïñþ íá äéáâÜóù ôï áñ÷åßï äéåõèýíóåùí: %(filename)s: %(msg)s" @@ -12718,10 +12996,12 @@ msgid "You must fix the preceding invalid addresses first." msgstr "Èá ðñÝðåé íá äéïñèþóåôå ôçí ðñïçãïýìåíç Üêõñç äéåýèõíóç ðñþôá." #: bin/sync_members:264 +#, fuzzy msgid "Added : %(s)s" msgstr "ÐñïóôÝèçêå : %(s)s" #: bin/sync_members:289 +#, fuzzy msgid "Removed: %(s)s" msgstr "ÄéáãñÜöçêå: %(s)s" @@ -12828,10 +13108,12 @@ msgid "" msgstr "" #: bin/update:107 +#, fuzzy msgid "Fixing language templates: %(listname)s" msgstr "Äéüñèùóç ôùí ðñïôýðùí ôçò ãëþóóáò: %(listname)s" #: bin/update:196 bin/update:711 +#, fuzzy msgid "WARNING: could not acquire lock for list: %(listname)s" msgstr "" "ÐÑÏÅÉÄÏÐÏÉÇÓÇ: Äåí ìðïñþ íá áðïêôÞóù êëåßäùìá ãéá ôç ëßóôá: %(listname)s" @@ -12921,18 +13203,22 @@ msgid "- %(o_tmpl)s doesn't exist, leaving untouched" msgstr "- %(o_tmpl)s Äåí õðÜñ÷åé, ìÝíåé áíåðçñÝáóôï" #: bin/update:396 +#, fuzzy msgid "removing directory %(src)s and everything underneath" msgstr " áðáëïéöÞ ôïõ directory %(src)s êáé üôéäÞðïôå êÜôù áðü" #: bin/update:399 +#, fuzzy msgid "removing %(src)s" msgstr "áðáëïéöÞ %(src)s" #: bin/update:403 +#, fuzzy msgid "Warning: couldn't remove %(src)s -- %(rest)s" msgstr "Ðñïåéäïðïßçóç: äåí Þôáí äõíáôÞ ç áðáëïéöÞ %(src)s -- %(rest)s" #: bin/update:408 +#, fuzzy msgid "couldn't remove old file %(pyc)s -- %(rest)s" msgstr "äåí Þôáí äõíáôÞ ç áðáëïéöÞ ôïõ ðáëéïý áñ÷åßïõ %(pyc)s -- %(rest)s" @@ -12941,14 +13227,17 @@ msgid "updating old qfiles" msgstr "åíçìÝñùóç ðáëéþí qfiles" #: bin/update:455 +#, fuzzy msgid "Warning! Not a directory: %(dirpath)s" msgstr "Ðñïóï÷Þ! äåí åßíáé 'åíá directory %(dirpath)s" #: bin/update:530 +#, fuzzy msgid "message is unparsable: %(filebase)s" msgstr "ôï ìÞíõìá åßíáé ìÞ áíáëýóéìï: %(filebase)s" #: bin/update:544 +#, fuzzy msgid "Warning! Deleting empty .pck file: %(pckfile)s" msgstr "Ðñïóï÷Þ! ÄéáãñáöÞ Üäåéï. .pck áñ÷åßï: %(pckfile)s" @@ -12961,10 +13250,12 @@ msgid "Updating Mailman 2.1.4 pending.pck database" msgstr "ÅíçìÝñùóç Mailman 2.1.4 pending_subscriptions.db ÂÜóç ÄåäïìÝíùí" #: bin/update:598 +#, fuzzy msgid "Ignoring bad pended data: %(key)s: %(val)s" msgstr "ÐáñÜëåéøç êáêþò óå áíáìïíÞ (bad pended)) äåäïìÝíùí: %(key)s: %(val)s" #: bin/update:614 +#, fuzzy msgid "WARNING: Ignoring duplicate pending ID: %(id)s." msgstr "Ðñïåéäïðïßçóç: ÐáñÜëåéøç äéðëÜ óå áíáìïíÞ áíáãíùñéóôéêþí (ID): %(id)s." @@ -12990,6 +13281,7 @@ msgid "done" msgstr "Êáìßá" #: bin/update:691 +#, fuzzy msgid "Updating mailing list: %(listname)s" msgstr "ÅíçìÝñùóç ëßóôáò çëåêôñïíéêïý ôá÷õäñïìåßïõ: %(listname)s" @@ -13033,6 +13325,7 @@ msgid "No updates are necessary." msgstr "Äåù ÷ñåéÜæïíáôé åíçìåñþóåéò" #: bin/update:796 +#, fuzzy msgid "" "Downgrade detected, from version %(hexlversion)s to version %(hextversion)s\n" "This is probably not safe.\n" @@ -13044,6 +13337,7 @@ msgstr "" "¸îïäïò." #: bin/update:801 +#, fuzzy msgid "Upgrading from version %(hexlversion)s to %(hextversion)s" msgstr "ÁíáâÜèìéóç áðï Ýêäïóç %(hexlversion)s óå %(hextversion)s" @@ -13200,6 +13494,7 @@ msgid "" msgstr "" #: bin/withlist:175 +#, fuzzy msgid "Unlocking (but not saving) list: %(listname)s" msgstr "Îåêëåßäùìá (áëëÜ ü÷é áðïèÞêåõóç) ëßóôáò: %(listname)s" @@ -13208,6 +13503,7 @@ msgid "Finalizing" msgstr "Ôåëåßùìá" #: bin/withlist:188 +#, fuzzy msgid "Loading list %(listname)s" msgstr "Öüñôùóç ëßóôáò %(listname)s" @@ -13220,6 +13516,7 @@ msgid "(unlocked)" msgstr "îåêëåéäþèçêå" #: bin/withlist:197 +#, fuzzy msgid "Unknown list: %(listname)s" msgstr "¢ãíùóôç ëßóôá: %(listname)s" @@ -13232,14 +13529,17 @@ msgid "--all requires --run" msgstr "--all áðáéôåß --run" #: bin/withlist:266 +#, fuzzy msgid "Importing %(module)s..." msgstr "ÅéóáãùãÞ %(module)s..." #: bin/withlist:270 +#, fuzzy msgid "Running %(module)s.%(callable)s()..." msgstr "ÅêôÝëåóç %(module)s.%(callable)s()..." #: bin/withlist:291 +#, fuzzy msgid "The variable `m' is the %(listname)s MailList instance" msgstr "Ç ìåôáâëçôÞ `m' åßíáé ôï %(listname)s ôçò MailList " @@ -13430,6 +13730,7 @@ msgid "Password // URL" msgstr "Password // URL" #: cron/mailpasswds:222 +#, fuzzy msgid "%(host)s mailing list memberships reminder" msgstr "%(host)s mailing list memberships reminder" @@ -13516,8 +13817,8 @@ msgstr "" #~ " applied" #~ msgstr "" #~ "Êåßìåíï ðïõ èá óõìðåñéëáìâÜíåôáé óå êÜèå\n" -#~ " åéäïðïßçóç áðüññéøçò ðïõ\n" #~ " óôÝëíåôáé óôá åëåã÷üìåíá áðü ìåóïëáâçôÞ ìÝëç ôá ïðïßá " #~ "óôÝëíïõí ìçíýìáôá ó' áõôÞ ôçí ëßóôá." diff --git a/messages/eo/LC_MESSAGES/mailman.po b/messages/eo/LC_MESSAGES/mailman.po index 1ad97d8b..118817da 100644 --- a/messages/eo/LC_MESSAGES/mailman.po +++ b/messages/eo/LC_MESSAGES/mailman.po @@ -64,10 +64,12 @@ msgid "

                    Currently, there are no archives.

                    " msgstr "

                    Nun ne estas arĥivoj.

                    " #: Mailman/Archiver/HyperArch.py:821 +#, fuzzy msgid "Gzip'd Text%(sz)s" msgstr "Teksto kunpremita per gzip%(sz)s" #: Mailman/Archiver/HyperArch.py:826 +#, fuzzy msgid "Text%(sz)s" msgstr "Teksto%(sz)s" @@ -140,18 +142,22 @@ msgid "Third" msgstr "Tria" #: Mailman/Archiver/HyperArch.py:938 +#, fuzzy msgid "%(ord)s quarter %(year)i" msgstr "%(ord)s kvarmonato %(year)i" #: Mailman/Archiver/HyperArch.py:945 +#, fuzzy msgid "%(month)s %(year)i" msgstr "%(month)s %(year)i" #: Mailman/Archiver/HyperArch.py:950 +#, fuzzy msgid "The Week Of Monday %(day)i %(month)s %(year)i" msgstr "La semajno de lundo la %(day)in de %(month)s %(year)i" #: Mailman/Archiver/HyperArch.py:954 +#, fuzzy msgid "%(day)i %(month)s %(year)i" msgstr "%(day)in de %(month)s %(year)i" @@ -160,10 +166,12 @@ msgid "Computing threaded index\n" msgstr "Komputante fadenatan indekson\n" #: Mailman/Archiver/HyperArch.py:1319 +#, fuzzy msgid "Updating HTML for article %(seq)s" msgstr "Ĝisdatigante la HTML'on de la artikolo %(seq)s" #: Mailman/Archiver/HyperArch.py:1326 +#, fuzzy msgid "article file %(filename)s is missing!" msgstr "artikoldosiero %(filename)s ne trovita!" @@ -252,8 +260,9 @@ msgstr "" #: Mailman/Cgi/options.py:101 Mailman/Cgi/private.py:108 #: Mailman/Cgi/rmlist.py:75 Mailman/Cgi/roster.py:59 #: Mailman/Cgi/subscribe.py:67 +#, fuzzy msgid "No such list %(safelistname)s" -msgstr "" +msgstr "Listo neekzistanta: %(safelistname)s" #: Mailman/Cgi/admin.py:97 Mailman/Cgi/admindb.py:136 Mailman/Cgi/confirm.py:80 #: Mailman/Cgi/confirm.py:334 Mailman/Cgi/create.py:50 @@ -314,8 +323,9 @@ msgid "" msgstr "" #: Mailman/Cgi/admin.py:268 +#, fuzzy msgid "%(hostname)s mailing lists - Admin Links" -msgstr "" +msgstr "Dissendolistoj ĉe %(hostname)s" #: Mailman/Cgi/admin.py:303 Mailman/Cgi/listinfo.py:121 msgid "Welcome!" @@ -326,23 +336,30 @@ msgid "Mailman" msgstr "" #: Mailman/Cgi/admin.py:310 +#, fuzzy msgid "" "

                    There currently are no publicly-advertised %(mailmanlink)s\n" " mailing lists on %(hostname)s." msgstr "" +"

                    Estas neniu listo %(mailmanlink)s\n" +" publike videbla ĉe %(hostname)s." #: Mailman/Cgi/admin.py:316 +#, fuzzy msgid "" "

                    Below is the collection of publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s. Click on a list\n" " name to visit the configuration pages for that list." msgstr "" +"

                    Estas neniu listo %(mailmanlink)s\n" +" publike videbla ĉe %(hostname)s." #: Mailman/Cgi/admin.py:323 msgid "right " msgstr "" #: Mailman/Cgi/admin.py:325 +#, fuzzy msgid "" "To visit the administrators configuration page for an\n" " unadvertised list, open a URL similar to this one, but with a '/' " @@ -353,6 +370,11 @@ msgid "" "\n" "

                    General list information can be found at " msgstr "" +" Por viziti la informo-paÄon de nepublika listo,\n" +" malfermu retadreson URL similan al ĉi tiu, sed kun '/' kaj la " +"%(adj)s\n" +" nomon de la listo aldonita.\n" +"

                    Se vi estas listestsro, vi povas viziti " #: Mailman/Cgi/admin.py:332 msgid "the mailing list overview page" @@ -404,12 +426,14 @@ msgid "return to the %(categoryname)s options page." msgstr "" #: Mailman/Cgi/admin.py:446 +#, fuzzy msgid "%(realname)s Administration (%(label)s)" -msgstr "" +msgstr "Mastruma interfaco de %(realname)s" #: Mailman/Cgi/admin.py:447 +#, fuzzy msgid "%(realname)s mailing list administration
                    %(label)s Section" -msgstr "" +msgstr "Mastruma interfaco de %(realname)s" #: Mailman/Cgi/admin.py:464 msgid "Configuration Categories" @@ -1021,8 +1045,9 @@ msgstr "" #: Mailman/Cgi/admin.py:1529 Mailman/Cgi/admin.py:1703 bin/add_members:175 #: bin/clone_member:136 bin/sync_members:268 +#, fuzzy msgid "Banned address (matched %(pattern)s)" -msgstr "" +msgstr "PoÅto blokita (kaplinio respondas al %(pattern)s)" #: Mailman/Cgi/admin.py:1535 msgid "Successfully invited:" @@ -1127,12 +1152,14 @@ msgid "Error Unsubscribing:" msgstr "" #: Mailman/Cgi/admindb.py:235 Mailman/Cgi/admindb.py:248 +#, fuzzy msgid "%(realname)s Administrative Database" -msgstr "" +msgstr "Mastruma interfaco de %(realname)s" #: Mailman/Cgi/admindb.py:238 +#, fuzzy msgid "%(realname)s Administrative Database Results" -msgstr "" +msgstr "Mastruma interfaco de %(realname)s" #: Mailman/Cgi/admindb.py:251 msgid "There are no pending requests." @@ -1179,8 +1206,9 @@ msgid "list of available mailing lists." msgstr "" #: Mailman/Cgi/admindb.py:362 +#, fuzzy msgid "You must specify a list name. Here is the %(link)s" -msgstr "" +msgstr "Vi devas indiki liston." #: Mailman/Cgi/admindb.py:375 msgid "Subscription Requests" @@ -1370,14 +1398,16 @@ msgid " is already a member" msgstr "" #: Mailman/Cgi/admindb.py:973 +#, fuzzy msgid "%(addr)s is banned (matched: %(patt)s)" -msgstr "" +msgstr "PoÅto blokita (kaplinio respondas al %(pattern)s)" #: Mailman/Cgi/confirm.py:88 msgid "Confirmation string was empty." msgstr "La konfirmo-kodo estis malplena." #: Mailman/Cgi/confirm.py:108 +#, fuzzy msgid "" "Invalid confirmation string:\n" " %(safecookie)s.\n" @@ -1421,6 +1451,7 @@ msgstr "" " nuligita." #: Mailman/Cgi/confirm.py:178 +#, fuzzy msgid "System error, bad content: %(content)s" msgstr "Sistem-eraro, malbona enhavo: %(content)s" @@ -1458,6 +1489,7 @@ msgid "Confirm subscription request" msgstr "Konfirmu abonpeton" #: Mailman/Cgi/confirm.py:259 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " subscription request to the mailing list %(listname)s. Your\n" @@ -1490,6 +1522,7 @@ msgstr "" " deziras aboni la liston." #: Mailman/Cgi/confirm.py:275 +#, fuzzy msgid "" "Your confirmation is required in order to continue with\n" " the subscription request to the mailing list %(listname)s.\n" @@ -1540,6 +1573,7 @@ msgid "Preferred language:" msgstr "Preferata lingvo:" #: Mailman/Cgi/confirm.py:317 +#, fuzzy msgid "Subscribe to list %(listname)s" msgstr "Abonigu min al %(listname)s" @@ -1556,6 +1590,7 @@ msgid "Awaiting moderator approval" msgstr "Atendate aprobon de moderiganto" #: Mailman/Cgi/confirm.py:386 +#, fuzzy msgid "" " You have successfully confirmed your subscription request to " "the\n" @@ -1586,6 +1621,7 @@ msgid "You are already a member of this mailing list!" msgstr "Vi jam abonas ĉi tiun liston!" #: Mailman/Cgi/confirm.py:400 +#, fuzzy msgid "" "You are currently banned from subscribing to\n" " this list. If you think this restriction is erroneous, please\n" @@ -1610,6 +1646,7 @@ msgid "Subscription request confirmed" msgstr "Abonpeto konfirmita" #: Mailman/Cgi/confirm.py:418 +#, fuzzy msgid "" " You have successfully confirmed your subscription request for\n" " \"%(addr)s\" to the %(listname)s mailing list. A separate\n" @@ -1639,6 +1676,7 @@ msgid "Unsubscription request confirmed" msgstr "Malabonpeto konfirmita" #: Mailman/Cgi/confirm.py:469 +#, fuzzy msgid "" " You have successfully unsubscribed from the %(listname)s " "mailing\n" @@ -1659,6 +1697,7 @@ msgid "Not available" msgstr "Ne disponebla" #: Mailman/Cgi/confirm.py:498 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " unsubscription request from the mailing list %(listname)s. " @@ -1702,6 +1741,7 @@ msgid "You have canceled your change of address request." msgstr "Vi nuligis vian peton ÅanÄi adreson." #: Mailman/Cgi/confirm.py:555 +#, fuzzy msgid "" "%(newaddr)s is banned from subscribing to the\n" " %(realname)s list. If you think this restriction is erroneous,\n" @@ -1712,6 +1752,7 @@ msgstr "" " kontaktu la listestron ĉe %(owneraddr)s." #: Mailman/Cgi/confirm.py:560 +#, fuzzy msgid "" "%(newaddr)s is already a member of\n" " the %(realname)s list. It is possible that you are attempting\n" @@ -1726,6 +1767,7 @@ msgid "Change of address request confirmed" msgstr "Peto ÅanÄi adreson konfirmita" #: Mailman/Cgi/confirm.py:571 +#, fuzzy msgid "" " You have successfully changed your address on the %(listname)s\n" " mailing list from %(oldaddr)s to %(newaddr)s. " @@ -1747,6 +1789,7 @@ msgid "globally" msgstr "ĉie" #: Mailman/Cgi/confirm.py:605 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " change of address request for the mailing list %(listname)s. " @@ -1808,6 +1851,7 @@ msgid "Sender discarded message via web." msgstr "La sendinto forigis la mesaÄon per retejo." #: Mailman/Cgi/confirm.py:674 +#, fuzzy msgid "" "The held message with the Subject:\n" " header %(subject)s could not be found. The most " @@ -1827,6 +1871,7 @@ msgid "Posted message canceled" msgstr "Sendo de poÅto nuligita" #: Mailman/Cgi/confirm.py:685 +#, fuzzy msgid "" " You have successfully canceled the posting of your message with\n" " the Subject: header %(subject)s to the mailing list\n" @@ -1849,6 +1894,7 @@ msgstr "" " jam pritraktis la listestro." #: Mailman/Cgi/confirm.py:735 +#, fuzzy msgid "" "Your confirmation is required in order to cancel the\n" " posting of your message to the mailing list %(listname)s:\n" @@ -1896,6 +1942,7 @@ msgid "Membership re-enabled." msgstr "Abono reÅaltita." #: Mailman/Cgi/confirm.py:798 +#, fuzzy msgid "" " You have successfully re-enabled your membership in the\n" " %(listname)s mailing list. You can now not available" msgstr "ne disponebla" #: Mailman/Cgi/confirm.py:846 +#, fuzzy msgid "" "Your membership in the %(realname)s mailing list is\n" " currently disabled due to excessive bounces. Your confirmation is\n" @@ -1992,12 +2041,14 @@ msgid "administrative list overview" msgstr "" #: Mailman/Cgi/create.py:115 +#, fuzzy msgid "List name must not include \"@\": %(safelistname)s" -msgstr "" +msgstr "Listonomo: %(listname)s" #: Mailman/Cgi/create.py:122 +#, fuzzy msgid "List already exists: %(safelistname)s" -msgstr "" +msgstr "Listo neekzistanta: %(safelistname)s" #: Mailman/Cgi/create.py:126 msgid "You forgot to enter the list name" @@ -2027,16 +2078,19 @@ msgid "You are not authorized to create new mailing lists" msgstr "" #: Mailman/Cgi/create.py:182 +#, fuzzy msgid "Unknown virtual host: %(safehostname)s" -msgstr "" +msgstr "Listo neekzistanta: %(safelistname)s" #: Mailman/Cgi/create.py:218 bin/newlist:219 +#, fuzzy msgid "Bad owner email address: %(s)s" -msgstr "" +msgstr "Via retpoÅtadreso:" #: Mailman/Cgi/create.py:223 bin/newlist:182 bin/newlist:223 +#, fuzzy msgid "List already exists: %(listname)s" -msgstr "" +msgstr "Listonomo: %(listname)s" #: Mailman/Cgi/create.py:231 bin/newlist:217 msgid "Illegal list name: %(s)s" @@ -2049,8 +2103,9 @@ msgid "" msgstr "" #: Mailman/Cgi/create.py:273 bin/newlist:265 +#, fuzzy msgid "Your new mailing list: %(listname)s" -msgstr "" +msgstr "Publikaj dissendolistoj ĉe %(hostname)s:" #: Mailman/Cgi/create.py:282 msgid "Mailing list creation results" @@ -2076,8 +2131,9 @@ msgid "Create another list" msgstr "" #: Mailman/Cgi/create.py:312 +#, fuzzy msgid "Create a %(hostname)s Mailing List" -msgstr "" +msgstr "Dissendolistoj ĉe %(hostname)s" #: Mailman/Cgi/create.py:321 Mailman/Cgi/rmlist.py:219 #: Mailman/Gui/Bounce.py:196 Mailman/htmlformat.py:360 @@ -2311,10 +2367,12 @@ msgid "HTML successfully updated." msgstr "" #: Mailman/Cgi/listinfo.py:89 +#, fuzzy msgid "%(hostname)s Mailing Lists" msgstr "Dissendolistoj ĉe %(hostname)s" #: Mailman/Cgi/listinfo.py:127 +#, fuzzy msgid "" "

                    There currently are no publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s." @@ -2323,6 +2381,7 @@ msgstr "" " publike videbla ĉe %(hostname)s." #: Mailman/Cgi/listinfo.py:131 +#, fuzzy msgid "" "

                    Below is a listing of all the public mailing lists on\n" " %(hostname)s. Click on a list name to get more information " @@ -2342,6 +2401,7 @@ msgid "right" msgstr "dekstren" #: Mailman/Cgi/listinfo.py:140 +#, fuzzy msgid "" " To visit the general information page for an unadvertised list,\n" " open a URL similar to this one, but with a '/' and the %(adj)s\n" @@ -2391,6 +2451,7 @@ msgid "CGI script error" msgstr "Eraro de la recepto CGI" #: Mailman/Cgi/options.py:71 +#, fuzzy msgid "Invalid request method: %(method)s" msgstr "Nevalida pet-maniero: %(method)s" @@ -2404,6 +2465,7 @@ msgstr "RetpoÅtadreso nevalida" #: Mailman/Cgi/options.py:183 Mailman/Cgi/options.py:240 #: Mailman/Cgi/options.py:264 +#, fuzzy msgid "No such member: %(safeuser)s." msgstr "Membro neekzistanta: %(safeuser)s." @@ -2455,6 +2517,7 @@ msgid "Note: " msgstr "Rimarku" #: Mailman/Cgi/options.py:378 +#, fuzzy msgid "List subscriptions for %(safeuser)s on %(hostname)s" msgstr "Abonoj de %(safeuser)s ĉe %(hostname)s" @@ -2485,6 +2548,7 @@ msgid "You are already using that email address" msgstr "Vi jam uzas ĉi tiun retpoÅtadreson" #: Mailman/Cgi/options.py:459 +#, fuzzy msgid "" "The new address you requested %(newaddr)s is already a member of the\n" "%(listname)s mailing list, however you have also requested a global change " @@ -2498,6 +2562,7 @@ msgstr "" "%(safeuser)s ÅanÄiÄos. " #: Mailman/Cgi/options.py:468 +#, fuzzy msgid "The new address is already a member: %(newaddr)s" msgstr "La nova adreso jam estas abonanto: %(newaddr)s" @@ -2506,6 +2571,7 @@ msgid "Addresses may not be blank" msgstr "La adreso ne povas esti neniu" #: Mailman/Cgi/options.py:488 +#, fuzzy msgid "A confirmation message has been sent to %(newaddr)s. " msgstr "Konfirmilo estas sendita al %(newaddr)s." @@ -2518,10 +2584,12 @@ msgid "Illegal email address provided" msgstr "RetpoÅtadreso nevalida" #: Mailman/Cgi/options.py:501 +#, fuzzy msgid "%(newaddr)s is already a member of the list." msgstr "%(newaddr)s jam abonas la liston." #: Mailman/Cgi/options.py:504 +#, fuzzy msgid "" "%(newaddr)s is banned from this list. If you\n" " think this restriction is erroneous, please contact\n" @@ -2597,6 +2665,7 @@ msgstr "" " sciigon post la decido de la moderigantoj." #: Mailman/Cgi/options.py:623 +#, fuzzy msgid "" "You have been successfully unsubscribed from the\n" " mailing list %(fqdn_listname)s. If you were receiving digest\n" @@ -2690,6 +2759,7 @@ msgid "day" msgstr "tago" #: Mailman/Cgi/options.py:900 +#, fuzzy msgid "%(days)d %(units)s" msgstr "%(days)d %(units)s" @@ -2702,6 +2772,7 @@ msgid "No topics defined" msgstr "Neniu temo difinita" #: Mailman/Cgi/options.py:940 +#, fuzzy msgid "" "\n" "You are subscribed to this list with the case-preserved address\n" @@ -2712,6 +2783,7 @@ msgstr "" "%(cpuser)s." #: Mailman/Cgi/options.py:956 +#, fuzzy msgid "%(realname)s list: member options login page" msgstr "Listo %(realname)s: ensaluto en la agordo-paÄon de la abonanto" @@ -2720,10 +2792,12 @@ msgid "email address and " msgstr "retpoÅtadreson kaj " #: Mailman/Cgi/options.py:960 +#, fuzzy msgid "%(realname)s list: member options for user %(safeuser)s" msgstr "Listo %(realname)s: abon-agordoj por uzanto %(safeuser)s" #: Mailman/Cgi/options.py:986 +#, fuzzy msgid "" "In order to change your membership option, you must\n" " first log in by giving your %(extra)smembership password in the section\n" @@ -2798,6 +2872,7 @@ msgid "" msgstr "" #: Mailman/Cgi/options.py:1140 +#, fuzzy msgid "Requested topic is not valid: %(topicname)s" msgstr "La temo ne estas valida: %(topicname)s" @@ -2826,6 +2901,7 @@ msgid "Private archive - \"./\" and \"../\" not allowed in URL." msgstr "Privata arÄ¥ivo - \"./\" kaj \"../\" ne akceptebla en URL." #: Mailman/Cgi/private.py:109 +#, fuzzy msgid "Private Archive Error - %(msg)s" msgstr "Eraro en la privata arÄ¥ivo - %(msg)s" @@ -2846,6 +2922,7 @@ msgid "Private archive file not found" msgstr "Dosiero de la privata arÄ¥ivo ne trovita" #: Mailman/Cgi/rmlist.py:76 +#, fuzzy msgid "No such list %(safelistname)s" msgstr "Listo neekzistanta: %(safelistname)s" @@ -2880,8 +2957,9 @@ msgid "Permanently remove mailing list %(realname)s" msgstr "" #: Mailman/Cgi/rmlist.py:209 +#, fuzzy msgid "Permanently remove mailing list %(realname)s" -msgstr "" +msgstr "Publikaj dissendolistoj ĉe %(hostname)s:" #: Mailman/Cgi/rmlist.py:222 msgid "" @@ -2927,6 +3005,7 @@ msgid "Invalid options to CGI script" msgstr "Agordoj nevalidaj al recepto CGI" #: Mailman/Cgi/roster.py:118 +#, fuzzy msgid "%(realname)s roster authentication failed." msgstr "Malsukcesa ensaluto al listanaro de %(realname)s." @@ -2995,6 +3074,7 @@ msgstr "" "Vi baldaÅ­ ricevos konfirmilon per retpoÅto kun pliaj instrukcioj. " #: Mailman/Cgi/subscribe.py:262 +#, fuzzy msgid "" "The email address you supplied is banned from this\n" " mailing list. If you think this restriction is erroneous, please\n" @@ -3021,6 +3101,7 @@ msgstr "" "ne estas sekura." #: Mailman/Cgi/subscribe.py:278 +#, fuzzy msgid "" "Confirmation from your email address is required, to prevent anyone from\n" "subscribing you without permission. Instructions are being sent to you at\n" @@ -3033,6 +3114,7 @@ msgstr "" "Rimarku ke via abono ne komenciÄos dum vi ne konfirmos." #: Mailman/Cgi/subscribe.py:290 +#, fuzzy msgid "" "Your subscription request was deferred because %(x)s. Your request has " "been\n" @@ -3053,6 +3135,7 @@ msgid "Mailman privacy alert" msgstr "Alarmo de privateco Mailman" #: Mailman/Cgi/subscribe.py:316 +#, fuzzy msgid "" "An attempt was made to subscribe your address to the mailing list\n" "%(listaddr)s. You are already subscribed to this mailing list.\n" @@ -3092,6 +3175,7 @@ msgid "This list only supports digest delivery." msgstr "Ĉi tiu listo nur liveras resumojn." #: Mailman/Cgi/subscribe.py:344 +#, fuzzy msgid "You have been successfully subscribed to the %(realname)s mailing list." msgstr "Vi estas sukcese malabonigita el la listo %(realname)s." @@ -3115,6 +3199,7 @@ msgid "Usage:" msgstr "Uzo:" #: Mailman/Commands/cmd_confirm.py:50 +#, fuzzy msgid "" "Invalid confirmation string. Note that confirmation strings expire\n" "approximately %(days)s days after the initial request. They also expire if\n" @@ -3139,6 +3224,7 @@ msgstr "" "retpoÅtadreson?" #: Mailman/Commands/cmd_confirm.py:69 +#, fuzzy msgid "" "You are currently banned from subscribing to this list. If you think this\n" "restriction is erroneous, please contact the list owners at\n" @@ -3219,26 +3305,32 @@ msgid "n/a" msgstr "n/a" #: Mailman/Commands/cmd_info.py:44 +#, fuzzy msgid "List name: %(listname)s" msgstr "Listonomo: %(listname)s" #: Mailman/Commands/cmd_info.py:45 +#, fuzzy msgid "Description: %(description)s" msgstr "Priskribo: %(description)s" #: Mailman/Commands/cmd_info.py:46 +#, fuzzy msgid "Postings to: %(postaddr)s" msgstr "PoÅto al: %(postaddr)s" #: Mailman/Commands/cmd_info.py:47 +#, fuzzy msgid "List Helpbot: %(requestaddr)s" msgstr "Helpo-roboto: %(requestaddr)s" #: Mailman/Commands/cmd_info.py:48 +#, fuzzy msgid "List Owners: %(owneraddr)s" msgstr "Listestroj: %(owneraddr)s" #: Mailman/Commands/cmd_info.py:49 +#, fuzzy msgid "More information: %(listurl)s" msgstr "Plia informo: %(listurl)s" @@ -3262,18 +3354,22 @@ msgstr "" "GNU.\n" #: Mailman/Commands/cmd_lists.py:44 +#, fuzzy msgid "Public mailing lists at %(hostname)s:" msgstr "Publikaj dissendolistoj ĉe %(hostname)s:" #: Mailman/Commands/cmd_lists.py:66 +#, fuzzy msgid "%(i)3d. List name: %(realname)s" msgstr "%(i)3d. Nomo de la listo: %(realname)s" #: Mailman/Commands/cmd_lists.py:67 +#, fuzzy msgid " Description: %(description)s" msgstr " Priskribo: %(description)s" #: Mailman/Commands/cmd_lists.py:68 +#, fuzzy msgid " Requests to: %(requestaddr)s" msgstr " Petoj al: %(requestaddr)s" @@ -3309,12 +3405,14 @@ msgstr "" " respondo ĉiam estas sendata al la abon-adreso.\n" #: Mailman/Commands/cmd_password.py:51 Mailman/Commands/cmd_password.py:66 +#, fuzzy msgid "Your password is: %(password)s" msgstr "Via pasvorto estas: %(password)s" #: Mailman/Commands/cmd_password.py:57 Mailman/Commands/cmd_password.py:72 #: Mailman/Commands/cmd_password.py:95 Mailman/Commands/cmd_password.py:121 #: Mailman/Commands/cmd_set.py:149 Mailman/Commands/cmd_set.py:219 +#, fuzzy msgid "You are not a member of the %(listname)s mailing list" msgstr "Vi ne estas membro de la dissendolisto %(listname)s" @@ -3502,6 +3600,7 @@ msgstr "" " de la pasvorto de ĉi tiu dissendolisto.\n" #: Mailman/Commands/cmd_set.py:122 +#, fuzzy msgid "Bad set command: %(subcmd)s" msgstr "MalÄusta ordono 'set': %(subcmd)s" @@ -3522,6 +3621,7 @@ msgid "on" msgstr "aktiva" #: Mailman/Commands/cmd_set.py:154 +#, fuzzy msgid " ack %(onoff)s" msgstr " sciigo (ack) %(onoff)s" @@ -3559,22 +3659,27 @@ msgid "due to bounces" msgstr "pro resaltoj" #: Mailman/Commands/cmd_set.py:186 +#, fuzzy msgid " %(status)s (%(how)s on %(date)s)" msgstr " %(status)s (%(how)s la %(date)s)" #: Mailman/Commands/cmd_set.py:192 +#, fuzzy msgid " myposts %(onoff)s" msgstr " mia poÅto %(onoff)s" #: Mailman/Commands/cmd_set.py:195 +#, fuzzy msgid " hide %(onoff)s" msgstr " kaÅado %(onoff)s" #: Mailman/Commands/cmd_set.py:199 +#, fuzzy msgid " duplicates %(onoff)s" msgstr " duoblaĵoj %(onoff)s" #: Mailman/Commands/cmd_set.py:203 +#, fuzzy msgid " reminders %(onoff)s" msgstr " memorigoj %(onoff)s" @@ -3583,6 +3688,7 @@ msgid "You did not give the correct password" msgstr "Vi ne enskribis la Äustan pasvorton" #: Mailman/Commands/cmd_set.py:236 Mailman/Commands/cmd_set.py:283 +#, fuzzy msgid "Bad argument: %(arg)s" msgstr "MalÄusta ordonero: %(arg)s" @@ -3659,6 +3765,7 @@ msgstr "" " 'address=' (sen krampoj nek citiloj!)\n" #: Mailman/Commands/cmd_subscribe.py:62 +#, fuzzy msgid "Bad digest specifier: %(arg)s" msgstr "MalÄusta precizigo de resumoj: %(arg)s" @@ -3667,6 +3774,7 @@ msgid "No valid address found to subscribe" msgstr "Abonebla adreso ne estas trovita" #: Mailman/Commands/cmd_subscribe.py:113 +#, fuzzy msgid "" "The email address you supplied is banned from this mailing list.\n" "If you think this restriction is erroneous, please contact the list\n" @@ -3705,6 +3813,7 @@ msgid "This list only supports digest subscriptions!" msgstr "Ĉi tiu listo nur akceptas resum-abonojn!" #: Mailman/Commands/cmd_subscribe.py:146 +#, fuzzy msgid "" "Your subscription request has been forwarded to the list administrator\n" "at %(listowner)s for review." @@ -3738,6 +3847,7 @@ msgstr "" " (sen krampoj nek citiloj!)\n" #: Mailman/Commands/cmd_unsubscribe.py:62 +#, fuzzy msgid "%(address)s is not a member of the %(listname)s mailing list" msgstr "%(address)s ne abonas la dissendoliston %(listname)s" @@ -3988,6 +4098,7 @@ msgid "Chinese (Taiwan)" msgstr "ĉina (Tajvano)" #: Mailman/Deliverer.py:53 +#, fuzzy msgid "" "Note: Since this is a list of mailing lists, administrative\n" "notices like the password reminder will be sent to\n" @@ -4002,14 +4113,17 @@ msgid " (Digest mode)" msgstr " (resum-modo)" #: Mailman/Deliverer.py:79 +#, fuzzy msgid "Welcome to the \"%(realname)s\" mailing list%(digmode)s" msgstr "Bonvenon en la liston \"%(realname)s\"%(digmode)s" #: Mailman/Deliverer.py:89 +#, fuzzy msgid "You have been unsubscribed from the %(realname)s mailing list" msgstr "Vi estas malabonigita el la listo %(realname)s" #: Mailman/Deliverer.py:116 +#, fuzzy msgid "%(listfullname)s mailing list reminder" msgstr "Rememorigo el la dissendolisto %(listfullname)s" @@ -4022,6 +4136,7 @@ msgid "Hostile subscription attempt detected" msgstr "Malica abonprovo rimarkita" #: Mailman/Deliverer.py:169 +#, fuzzy msgid "" "%(address)s was invited to a different mailing\n" "list, but in a deliberate malicious attempt they tried to confirm the\n" @@ -4034,6 +4149,7 @@ msgstr "" "reago via estas bezonata." #: Mailman/Deliverer.py:188 +#, fuzzy msgid "" "You invited %(address)s to your list, but in a\n" "deliberate malicious attempt, they tried to confirm the invitation to a\n" @@ -4047,6 +4163,7 @@ msgstr "" "reago via estas bezonata." #: Mailman/Deliverer.py:221 +#, fuzzy msgid "%(listname)s mailing list probe message" msgstr "ProvmesaÄo de la dissendolisto %(listname)s" @@ -4222,8 +4339,8 @@ msgid "" " membership.\n" "\n" "

                    You can control both the\n" -" number\n" +" number\n" " of reminders the member will receive and the\n" " No those messages too will get discarded. You may " "want\n" " to set up an\n" -" autoresponse\n" +" autoresponse\n" " message for email to the -owner and -admin address." msgstr "" @@ -4493,8 +4610,8 @@ msgid "" "Use this option to remove each message attachment that does\n" " not have a matching content type. Requirements and formats " "are\n" -" exactly like filter_mime_types.\n" "\n" "

                    Note: if you add entries to this list but don't add\n" @@ -4681,8 +4798,9 @@ msgid "Invalid value for variable: %(property)s" msgstr "" #: Mailman/Gui/GUIBase.py:178 +#, fuzzy msgid "Bad email address for option %(property)s: %(error)s" -msgstr "" +msgstr "RetpoÅtadreso malÄusta" #: Mailman/Gui/GUIBase.py:204 msgid "" @@ -4778,8 +4896,8 @@ msgid "" "

                    In order to split the list ownership duties into\n" " administrators and moderators, you must\n" " set a separate moderator password,\n" -" and also provide the email\n" +" and also provide the email\n" " addresses of the list moderators. Note that the field you\n" " are changing here specifies the list administrators." msgstr "" @@ -5025,13 +5143,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5056,8 +5174,8 @@ msgstr "" msgid "" "This is the address set in the Reply-To: header\n" " when the reply_goes_to_list\n" +" href=\"?VARHELP=general/" +"reply_goes_to_list\">reply_goes_to_list\n" " option is set to Explicit address.\n" "\n" "

                    There are many reasons not to introduce or override the\n" @@ -5065,13 +5183,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5130,8 +5248,8 @@ msgid "" " member list addresses, but rather to the owner of those member\n" " lists. In that case, the value of this setting is appended to\n" " the member's account name for such notices. `-owner' is the\n" -" typical choice. This setting has no effect when \"umbrella_list" -"\"\n" +" typical choice. This setting has no effect when " +"\"umbrella_list\"\n" " is \"No\"." msgstr "" @@ -5982,8 +6100,8 @@ msgid "" " either individually or as a group. Any\n" " posting from a non-member who is not explicitly accepted,\n" " rejected, or discarded, will have their posting filtered by the\n" -" general\n" +" general\n" " non-member rules.\n" "\n" "

                    In the text boxes below, add one address per line; start the\n" @@ -6042,8 +6160,8 @@ msgid "" "If a member posts this many times, within a period of time\n" " the member is automatically moderated. Use 0 to disable. " "See\n" -" member_verbosity_interval for details on the time " "period.\n" "\n" @@ -6606,8 +6724,8 @@ msgid "" "

                    The body of the message can also be optionally scanned for\n" " Subject: and Keywords: headers, as\n" " specified by the topics_bodylines_limit\n" +" href=\"?VARHELP=topics/" +"topics_bodylines_limit\">topics_bodylines_limit\n" " configuration variable." msgstr "" @@ -6816,6 +6934,7 @@ msgid "%(listinfo_link)s list run by %(owner_link)s" msgstr "Listo %(listinfo_link)s mastrumata de %(owner_link)s" #: Mailman/HTMLFormatter.py:57 +#, fuzzy msgid "%(realname)s administrative interface" msgstr "Mastruma interfaco de %(realname)s" @@ -6824,6 +6943,7 @@ msgid " (requires authorization)" msgstr " (postulas rajtigon)" #: Mailman/HTMLFormatter.py:61 +#, fuzzy msgid "Overview of all %(hostname)s mailing lists" msgstr "Tuto de ĉiuj listoj ĉe %(hostname)s" @@ -6844,6 +6964,7 @@ msgid "; it was disabled by the list administrator" msgstr "; Äin malÅaltis la listestro" #: Mailman/HTMLFormatter.py:146 +#, fuzzy msgid "" "; it was disabled due to excessive bounces. The\n" " last bounce was received on %(date)s" @@ -6856,6 +6977,7 @@ msgid "; it was disabled for unknown reasons" msgstr "; Äi estas malÅaltita pro nekonata kialo" #: Mailman/HTMLFormatter.py:151 +#, fuzzy msgid "Note: your list delivery is currently disabled%(reason)s." msgstr "Atentu: liverado de via listo estas nun malÅaltita%(reason)s." @@ -6868,6 +6990,7 @@ msgid "the list administrator" msgstr "la listestro" #: Mailman/HTMLFormatter.py:157 +#, fuzzy msgid "" "

                    %(note)s\n" "\n" @@ -6886,6 +7009,7 @@ msgstr "" " havas demandon aÅ­ bezonas helpon." #: Mailman/HTMLFormatter.py:169 +#, fuzzy msgid "" "

                    We have received some recent bounces from your\n" " address. Your current bounce score is %(score)s out of " @@ -6904,6 +7028,7 @@ msgstr "" " rekomencita, se la problemoj estas solvataj baldaÅ­." #: Mailman/HTMLFormatter.py:181 +#, fuzzy msgid "" "(Note - you are subscribing to a list of mailing lists, so the %(type)s " "notice will be sent to the admin address for your membership, %(addr)s.)

                    " @@ -6950,6 +7075,7 @@ msgstr "" " per retpoÅto pri la decido de la moderiganto." #: Mailman/HTMLFormatter.py:208 +#, fuzzy msgid "" "This is %(also)sa private list, which means that the\n" " list of members is not available to non-members." @@ -6958,6 +7084,7 @@ msgstr "" " la listanaro ne estas videbla al ne-abonantoj." #: Mailman/HTMLFormatter.py:211 +#, fuzzy msgid "" "This is %(also)sa hidden list, which means that the\n" " list of members is available only to the list administrator." @@ -6966,6 +7093,7 @@ msgstr "" " la listanaro estas videbla nur por la listestro." #: Mailman/HTMLFormatter.py:214 +#, fuzzy msgid "" "This is %(also)sa public list, which means that the\n" " list of members list is available to everyone." @@ -6982,6 +7110,7 @@ msgstr "" " trudpoÅtistoj facile)." #: Mailman/HTMLFormatter.py:222 +#, fuzzy msgid "" "

                    (Note that this is an umbrella list, intended to\n" " have only other mailing lists as members. Among other things,\n" @@ -6998,6 +7127,7 @@ msgid "either " msgstr "aÅ­ " #: Mailman/HTMLFormatter.py:256 +#, fuzzy msgid "" "To unsubscribe from %(realname)s, get a password reminder,\n" " or change your subscription options %(either)senter your " @@ -7032,6 +7162,7 @@ msgstr "" " vian retpoÅtadreson." #: Mailman/HTMLFormatter.py:277 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " members.)" @@ -7040,6 +7171,7 @@ msgstr "" " abonantoj.)" #: Mailman/HTMLFormatter.py:281 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " administrator.)" @@ -7100,6 +7232,7 @@ msgid "The current archive" msgstr "La nuna arÄ¥ivo" #: Mailman/Handlers/Acknowledge.py:59 +#, fuzzy msgid "%(realname)s post acknowledgement" msgstr "Rekono de mesaÄo de %(realname)s" @@ -7116,6 +7249,7 @@ msgstr "" "estas kodigita en la HTML Äi ne povas esti sekure forviÅata.\n" #: Mailman/Handlers/CalcRecips.py:79 +#, fuzzy msgid "" "Your urgent message to the %(realname)s mailing list was not authorized for\n" "delivery. The original message as received by Mailman is attached.\n" @@ -7193,6 +7327,7 @@ msgstr "" "anstataÅ­ la liston" #: Mailman/Handlers/Hold.py:84 +#, fuzzy msgid "" "Please do *not* post administrative requests to the mailing\n" "list. If you wish to subscribe, visit %(listurl)s or send a message with " @@ -7234,10 +7369,12 @@ msgid "Posting to a moderated newsgroup" msgstr "PoÅto al moderigata novaĵgrupo" #: Mailman/Handlers/Hold.py:252 +#, fuzzy msgid "Your message to %(listname)s awaits moderator approval" msgstr "Via mesaÄo al %(listname)s atendas la aprobon de la moderiganto" #: Mailman/Handlers/Hold.py:271 +#, fuzzy msgid "%(listname)s post from %(sender)s requires approval" msgstr "PoÅto al %(listname)s de %(sender)s postulas aprobon" @@ -7280,6 +7417,7 @@ msgid "After content filtering, the message was empty" msgstr "Post filtrado de la enhavo, la mesaÄo estis malplena" #: Mailman/Handlers/MimeDel.py:269 +#, fuzzy msgid "" "The attached message matched the %(listname)s mailing list's content " "filtering\n" @@ -7322,6 +7460,7 @@ msgid "The attached message has been automatically discarded." msgstr "La alkroĉita mesaÄo estis aÅ­tomate forviÅita." #: Mailman/Handlers/Replybot.py:75 +#, fuzzy msgid "Auto-response for your message to the \"%(realname)s\" mailing list" msgstr "AÅ­toimata respondo de via mesaÄo al la dissendolisto\"%(realname)s\" " @@ -7345,6 +7484,7 @@ msgid "HTML attachment scrubbed and removed" msgstr "HTML-aranÄa alkroĉaĵo estis elsarkita kaj forviÅita" #: Mailman/Handlers/Scrubber.py:234 Mailman/Handlers/Scrubber.py:259 +#, fuzzy msgid "" "An HTML attachment was scrubbed...\n" "URL: %(url)s\n" @@ -7399,6 +7539,7 @@ msgstr "" "Retadreso: %(url)s\n" #: Mailman/Handlers/Scrubber.py:347 +#, fuzzy msgid "Skipped content of type %(partctype)s\n" msgstr "Preterlasita enhavo el speco %(partctype)s\n" @@ -7431,6 +7572,7 @@ msgid "Message rejected by filter rule match" msgstr "PoÅto malakceptita pro kontentigo de filtro" #: Mailman/Handlers/ToDigest.py:173 +#, fuzzy msgid "%(realname)s Digest, Vol %(volume)d, Issue %(issue)d" msgstr "Resumo de %(realname)s, volumo %(volume)d, eldono %(issue)d" @@ -7468,6 +7610,7 @@ msgid "End of " msgstr "Fino de " #: Mailman/ListAdmin.py:309 +#, fuzzy msgid "Posting of your message titled \"%(subject)s\"" msgstr "Sendo de via mesaÄo titolita \"%(subject)s\"" @@ -7480,8 +7623,9 @@ msgid "Forward of moderated message" msgstr "Plusendo de moderigita mesaÄo" #: Mailman/ListAdmin.py:405 +#, fuzzy msgid "New subscription request to list %(realname)s from %(addr)s" -msgstr "" +msgstr "Malabonpeto konfirmita" #: Mailman/ListAdmin.py:432 msgid "Subscription request" @@ -7493,8 +7637,9 @@ msgid "via admin approval" msgstr "AnkoraÅ­ atendas aprobon" #: Mailman/ListAdmin.py:466 +#, fuzzy msgid "New unsubscription request from %(realname)s by %(addr)s" -msgstr "" +msgstr "Malabonpeto konfirmita" #: Mailman/ListAdmin.py:490 msgid "Unsubscription request" @@ -7505,8 +7650,9 @@ msgid "Original Message" msgstr "" #: Mailman/ListAdmin.py:526 +#, fuzzy msgid "Request to mailing list %(realname)s rejected" -msgstr "" +msgstr "Publikaj dissendolistoj ĉe %(hostname)s:" #: Mailman/MTA/Manual.py:66 msgid "" @@ -7526,8 +7672,9 @@ msgid "" msgstr "" #: Mailman/MTA/Manual.py:82 +#, fuzzy msgid "## %(listname)s mailing list" -msgstr "" +msgstr "Dissendolistoj ĉe %(hostname)s" #: Mailman/MTA/Manual.py:99 msgid "Mailing list creation request for list %(listname)s" @@ -7588,23 +7735,28 @@ msgid "%(dbfile)s permissions must be 0664 (got %(octmode)s)" msgstr "" #: Mailman/MailList.py:219 +#, fuzzy msgid "Your confirmation is required to join the %(listname)s mailing list" msgstr "Via konfirmo estas bezonata por aboni la dissendoliston %(listname)s" #: Mailman/MailList.py:230 +#, fuzzy msgid "Your confirmation is required to leave the %(listname)s mailing list" msgstr "" "Via konfirmo estas bezonata por malaboni la dissendoliston %(listname)s" #: Mailman/MailList.py:999 Mailman/MailList.py:1492 +#, fuzzy msgid " from %(remote)s" msgstr " de %(remote)s" #: Mailman/MailList.py:1043 +#, fuzzy msgid "subscriptions to %(realname)s require moderator approval" msgstr "aboni la liston %(realname)s postulas aprobon de moderiganto" #: Mailman/MailList.py:1125 bin/add_members:299 +#, fuzzy msgid "%(realname)s subscription notification" msgstr "Sciigo de abono de %(realname)s" @@ -7613,10 +7765,12 @@ msgid "unsubscriptions require moderator approval" msgstr "malabonoj postulas aprobon de moderiganto" #: Mailman/MailList.py:1166 +#, fuzzy msgid "%(realname)s unsubscribe notification" msgstr "Sciigo de malabono de %(realname)s" #: Mailman/MailList.py:1328 +#, fuzzy msgid "%(realname)s address change notification" msgstr "Sciigo de adresoÅanÄo de %(realname)s" @@ -7631,6 +7785,7 @@ msgid "via web confirmation" msgstr "Konfirmokodo nevalida" #: Mailman/MailList.py:1396 +#, fuzzy msgid "subscriptions to %(name)s require administrator approval" msgstr "aboni la liston %(name)s postulas aprobon de listestro" @@ -7893,28 +8048,32 @@ msgid "Bad/Invalid email address: blank line" msgstr "" #: bin/add_members:180 +#, fuzzy msgid "Bad/Invalid email address: %(member)s" -msgstr "" +msgstr "RetpoÅtadreso malÄusta" #: bin/add_members:182 msgid "Hostile address (illegal characters): %(member)s" msgstr "" #: bin/add_members:185 +#, fuzzy msgid "Invited: %(member)s" -msgstr "" +msgstr "RetpoÅtadreso malÄusta" #: bin/add_members:187 msgid "Subscribed: %(member)s" msgstr "" #: bin/add_members:237 +#, fuzzy msgid "Bad argument to -w/--welcome-msg: %(arg)s" -msgstr "" +msgstr "MalÄusta ordonero: %(arg)s" #: bin/add_members:244 +#, fuzzy msgid "Bad argument to -a/--admin-notify: %(arg)s" -msgstr "" +msgstr "MalÄusta ordonero: %(arg)s" #: bin/add_members:252 msgid "Cannot read both digest and normal members from standard input." @@ -7927,8 +8086,9 @@ msgstr "" #: bin/add_members:261 bin/config_list:110 bin/export.py:271 bin/find_member:97 #: bin/inject:91 bin/list_admins:90 bin/list_members:252 bin/sync_members:222 #: cron/bumpdigests:86 +#, fuzzy msgid "No such list: %(listname)s" -msgstr "" +msgstr "Listo neekzistanta: %(safelistname)s" #: bin/add_members:285 bin/change_pw:159 bin/check_db:114 bin/discard:83 #: bin/sync_members:244 bin/update:302 bin/update:323 bin/update:577 @@ -7990,10 +8150,11 @@ msgid "listname is required" msgstr "" #: bin/arch:143 bin/change_pw:107 bin/config_list:257 +#, fuzzy msgid "" "No such list \"%(listname)s\"\n" "%(e)s" -msgstr "" +msgstr "Listo neekzistanta: %(safelistname)s" #: bin/arch:168 msgid "Cannot open mbox file %(mbox)s: %(msg)s" @@ -8077,8 +8238,9 @@ msgid "" msgstr "" #: bin/change_pw:145 +#, fuzzy msgid "Bad arguments: %(strargs)s" -msgstr "" +msgstr "MalÄusta ordonero: %(arg)s" #: bin/change_pw:149 msgid "Empty list passwords are not allowed" @@ -8315,8 +8477,9 @@ msgid "Unix-From line changed: %(lineno)d" msgstr "" #: bin/cleanarch:111 +#, fuzzy msgid "Bad status number: %(arg)s" -msgstr "" +msgstr "MalÄusta ordonero: %(arg)s" #: bin/cleanarch:167 msgid "%(messages)d messages found" @@ -8413,14 +8576,16 @@ msgid " original address removed:" msgstr "" #: bin/clone_member:202 +#, fuzzy msgid "Not a valid email address: %(toaddr)s" -msgstr "" +msgstr "Vi devas indiki validan retpoÅtadreson." #: bin/clone_member:215 +#, fuzzy msgid "" "Error opening list \"%(listname)s\", skipping.\n" "%(e)s" -msgstr "" +msgstr "Listo neekzistanta: %(safelistname)s" #: bin/config_list:20 msgid "" @@ -8509,8 +8674,9 @@ msgid "Invalid value for property: %(k)s" msgstr "" #: bin/config_list:291 +#, fuzzy msgid "Bad email address for option %(k)s: %(v)s" -msgstr "" +msgstr "RetpoÅtadreso malÄusta" #: bin/config_list:348 msgid "Only one of -i or -o is allowed" @@ -8565,8 +8731,9 @@ msgid "Ignoring held msg w/bad id: %(f)s" msgstr "" #: bin/discard:112 +#, fuzzy msgid "Discarded held msg #%(id)s for list %(listname)s" -msgstr "" +msgstr "Abonigu min al %(listname)s" #: bin/dumpdb:19 msgid "" @@ -8610,8 +8777,9 @@ msgid "No filename given." msgstr "" #: bin/dumpdb:108 +#, fuzzy msgid "Bad arguments: %(pargs)s" -msgstr "" +msgstr "MalÄusta ordonero: %(arg)s" #: bin/dumpdb:118 msgid "Please specify either -p or -m." @@ -8859,8 +9027,9 @@ msgid "" msgstr "" #: bin/list_admins:97 +#, fuzzy msgid "List: %(listname)s, \tOwners: %(owners)s" -msgstr "" +msgstr "Listestroj: %(owneraddr)s" #: bin/list_lists:19 msgid "" @@ -8965,12 +9134,14 @@ msgid "" msgstr "" #: bin/list_members:198 +#, fuzzy msgid "Bad --nomail option: %(why)s" -msgstr "" +msgstr "MalÄusta precizigo de resumoj: %(arg)s" #: bin/list_members:209 +#, fuzzy msgid "Bad --digest option: %(kind)s" -msgstr "" +msgstr "MalÄusta precizigo de resumoj: %(arg)s" #: bin/list_members:213 bin/list_members:217 bin/list_members:221 #: bin/list_members:225 @@ -9154,8 +9325,9 @@ msgid "" msgstr "" #: bin/mailmanctl:280 cron/mailpasswds:119 +#, fuzzy msgid "Site list is missing: %(sitelistname)s" -msgstr "" +msgstr "Listo neekzistanta: %(safelistname)s" #: bin/mailmanctl:305 msgid "Run this program as root or as the %(name)s user, or use -u." @@ -9166,8 +9338,9 @@ msgid "No command given." msgstr "" #: bin/mailmanctl:339 +#, fuzzy msgid "Bad command: %(command)s" -msgstr "" +msgstr "MalÄusta ordono 'set': %(subcmd)s" #: bin/mailmanctl:344 msgid "Warning! You may encounter permission problems." @@ -9381,8 +9554,9 @@ msgid "" msgstr "" #: bin/newlist:162 +#, fuzzy msgid "Unknown language: %(lang)s" -msgstr "" +msgstr "Listo neekzistanta: %(safelistname)s" #: bin/newlist:167 msgid "Enter the name of the list: " @@ -9402,8 +9576,8 @@ msgstr "" #: bin/newlist:220 msgid "" -" - owner addresses need to be fully-qualified names like \"owner@example.com" -"\", not just \"owner\"." +" - owner addresses need to be fully-qualified names like \"owner@example." +"com\", not just \"owner\"." msgstr "" #: bin/newlist:244 @@ -9571,12 +9745,14 @@ msgid "Could not open file for reading: %(filename)s." msgstr "" #: bin/remove_members:163 +#, fuzzy msgid "Error opening list %(listname)s... skipping." -msgstr "" +msgstr "Listo neekzistanta: %(safelistname)s" #: bin/remove_members:173 +#, fuzzy msgid "No such member: %(addr)s" -msgstr "" +msgstr "Membro neekzistanta: %(safeuser)s." #: bin/remove_members:178 msgid "User `%(addr)s' removed from list: %(listname)s." @@ -9603,8 +9779,9 @@ msgid "" msgstr "" #: bin/reset_pw.py:77 +#, fuzzy msgid "Changing passwords for list: %(listname)s" -msgstr "" +msgstr "Listo neekzistanta: %(safelistname)s" #: bin/reset_pw.py:83 msgid "New password for member %(member)40s: %(randompw)s" @@ -9641,8 +9818,9 @@ msgid "%(listname)s %(msg)s not found as %(filename)s" msgstr "" #: bin/rmlist:105 +#, fuzzy msgid "No such list (or list already deleted): %(listname)s" -msgstr "" +msgstr "Listo neekzistanta: %(safelistname)s" #: bin/rmlist:108 msgid "No such list: %(listname)s. Removing its residual archives." @@ -9911,8 +10089,9 @@ msgid "" msgstr "" #: bin/update:107 +#, fuzzy msgid "Fixing language templates: %(listname)s" -msgstr "" +msgstr "Publikaj dissendolistoj ĉe %(hostname)s:" #: bin/update:196 bin/update:711 msgid "WARNING: could not acquire lock for list: %(listname)s" @@ -10066,8 +10245,9 @@ msgid "done" msgstr "" #: bin/update:691 +#, fuzzy msgid "Updating mailing list: %(listname)s" -msgstr "" +msgstr "Publikaj dissendolistoj ĉe %(hostname)s:" #: bin/update:694 msgid "Updating Usenet watermarks" @@ -10272,16 +10452,18 @@ msgid "" msgstr "" #: bin/withlist:175 +#, fuzzy msgid "Unlocking (but not saving) list: %(listname)s" -msgstr "" +msgstr "Listo neekzistanta: %(safelistname)s" #: bin/withlist:179 msgid "Finalizing" msgstr "" #: bin/withlist:188 +#, fuzzy msgid "Loading list %(listname)s" -msgstr "" +msgstr "Listo neekzistanta: %(safelistname)s" #: bin/withlist:190 msgid "(locked)" @@ -10292,8 +10474,9 @@ msgid "(unlocked)" msgstr "" #: bin/withlist:197 +#, fuzzy msgid "Unknown list: %(listname)s" -msgstr "" +msgstr "Listo neekzistanta: %(safelistname)s" #: bin/withlist:237 msgid "No list name supplied." @@ -10499,8 +10682,9 @@ msgid "Password // URL" msgstr "" #: cron/mailpasswds:222 +#, fuzzy msgid "%(host)s mailing list memberships reminder" -msgstr "" +msgstr "Rememorigo el la dissendolisto %(listfullname)s" #: cron/nightly_gzip:19 msgid "" diff --git a/messages/es/LC_MESSAGES/mailman.po b/messages/es/LC_MESSAGES/mailman.po index 029fd11c..59abfde6 100644 --- a/messages/es/LC_MESSAGES/mailman.po +++ b/messages/es/LC_MESSAGES/mailman.po @@ -63,10 +63,12 @@ msgid "

                    Currently, there are no archives.

                    " msgstr "Actualmente no hay archivo.

                    " #: Mailman/Archiver/HyperArch.py:821 +#, fuzzy msgid "Gzip'd Text%(sz)s" msgstr "Texto Gzip%(sz)s" #: Mailman/Archiver/HyperArch.py:826 +#, fuzzy msgid "Text%(sz)s" msgstr "Texto%(sz)s" @@ -139,18 +141,22 @@ msgid "Third" msgstr "Tercero" #: Mailman/Archiver/HyperArch.py:938 +#, fuzzy msgid "%(ord)s quarter %(year)i" msgstr "%(ord)s cuarto %(year)i" #: Mailman/Archiver/HyperArch.py:945 +#, fuzzy msgid "%(month)s %(year)i" msgstr "%(month)s %(year)i" #: Mailman/Archiver/HyperArch.py:950 +#, fuzzy msgid "The Week Of Monday %(day)i %(month)s %(year)i" msgstr "La semana del %(day)i %(month)s %(year)i" #: Mailman/Archiver/HyperArch.py:954 +#, fuzzy msgid "%(day)i %(month)s %(year)i" msgstr "%(day)i %(month)s %(year)i" @@ -159,10 +165,12 @@ msgid "Computing threaded index\n" msgstr "Calculando el índice de hilos\n" #: Mailman/Archiver/HyperArch.py:1319 +#, fuzzy msgid "Updating HTML for article %(seq)s" msgstr "Actualizando el código HTML del artículo %(seq)s" #: Mailman/Archiver/HyperArch.py:1326 +#, fuzzy msgid "article file %(filename)s is missing!" msgstr "no se encuentra el fichero %(filename)s asociado al artículo!" @@ -179,6 +187,7 @@ msgid "Pickling archive state into " msgstr "Preparando el estado del archivo a " #: Mailman/Archiver/pipermail.py:453 +#, fuzzy msgid "Updating index files for archive [%(archive)s]" msgstr "Actualizando el índice de los ficheros de [%(archive)s]" @@ -187,6 +196,7 @@ msgid " Thread" msgstr " Hilo" #: Mailman/Archiver/pipermail.py:597 +#, fuzzy msgid "#%(counter)05d %(msgid)s" msgstr "#%(counter)05d %(msgid)s" @@ -223,6 +233,7 @@ msgid "disabled address" msgstr "dirección deshabilitada" #: Mailman/Bouncer.py:304 +#, fuzzy msgid " The last bounce received from you was dated %(date)s" msgstr "El último rebote recibido de usted fue hace %(date)s" @@ -250,6 +261,7 @@ msgstr "Administrador" #: Mailman/Cgi/options.py:101 Mailman/Cgi/private.py:108 #: Mailman/Cgi/rmlist.py:75 Mailman/Cgi/roster.py:59 #: Mailman/Cgi/subscribe.py:67 +#, fuzzy msgid "No such list %(safelistname)s" msgstr "La lista %(safelistname)s no existe" @@ -326,6 +338,7 @@ msgstr "" "afectado(s) %(rm)r" #: Mailman/Cgi/admin.py:268 +#, fuzzy msgid "%(hostname)s mailing lists - Admin Links" msgstr "Listas de distribución en %(hostname)s - Enlaces de administración" @@ -338,6 +351,7 @@ msgid "Mailman" msgstr "Mailman" #: Mailman/Cgi/admin.py:310 +#, fuzzy msgid "" "

                    There currently are no publicly-advertised %(mailmanlink)s\n" " mailing lists on %(hostname)s." @@ -346,6 +360,7 @@ msgstr "" "públicamente en %(hostname)s. " #: Mailman/Cgi/admin.py:316 +#, fuzzy msgid "" "

                    Below is the collection of publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s. Click on a list\n" @@ -362,6 +377,7 @@ msgid "right " msgstr "correcta " #: Mailman/Cgi/admin.py:325 +#, fuzzy msgid "" "To visit the administrators configuration page for an\n" " unadvertised list, open a URL similar to this one, but with a '/' " @@ -406,6 +422,7 @@ msgid "No valid variable name found." msgstr "Encontrado un nombre de variable no válido." #: Mailman/Cgi/admin.py:395 +#, fuzzy msgid "" "%(realname)s Mailing list Configuration Help\n" "
                    %(varname)s Option" @@ -414,6 +431,7 @@ msgstr "" "
                    %(varname)s" #: Mailman/Cgi/admin.py:402 +#, fuzzy msgid "Mailman %(varname)s List Option Help" msgstr "Ayuda de Mailman para la opción de lista %(varname)s" @@ -432,14 +450,17 @@ msgstr "" " esta opción para esta lista de distribución. También puede " #: Mailman/Cgi/admin.py:431 +#, fuzzy msgid "return to the %(categoryname)s options page." msgstr "volver a la página de opciones %(categoryname)s" #: Mailman/Cgi/admin.py:446 +#, fuzzy msgid "%(realname)s Administration (%(label)s)" msgstr "Administración de %(realname)s (%(label)s)" #: Mailman/Cgi/admin.py:447 +#, fuzzy msgid "%(realname)s mailing list administration
                    %(label)s Section" msgstr "" "Administración de la lista de distribución %(realname)s
                    Sección de " @@ -527,6 +548,7 @@ msgid "Value" msgstr "Valor" #: Mailman/Cgi/admin.py:668 +#, fuzzy msgid "" "Badly formed options entry:\n" " %(record)s" @@ -627,10 +649,12 @@ msgid "Move rule down" msgstr "Mover la regla hacia abajo" #: Mailman/Cgi/admin.py:870 +#, fuzzy msgid "
                    (Edit %(varname)s)" msgstr "
                    (Editar %(varname)s)" #: Mailman/Cgi/admin.py:872 +#, fuzzy msgid "
                    (Details for %(varname)s)" msgstr "
                    (Detalles de %(varname)s)" @@ -669,6 +693,7 @@ msgid "(help)" msgstr "(ayuda)" #: Mailman/Cgi/admin.py:930 +#, fuzzy msgid "Find member %(link)s:" msgstr "Localizar suscriptor %(link)s:" @@ -681,10 +706,12 @@ msgid "Bad regular expression: " msgstr "Expresión regular mal formada: " #: Mailman/Cgi/admin.py:1013 +#, fuzzy msgid "%(allcnt)s members total, %(membercnt)s shown" msgstr "%(allcnt)s suscriptores en total, se muestran %(membercnt)s" #: Mailman/Cgi/admin.py:1016 +#, fuzzy msgid "%(allcnt)s members total" msgstr "%(allcnt)s suscriptores en total" @@ -881,6 +908,7 @@ msgstr "" " al rango apropiado de entre los que se listan abajo:" #: Mailman/Cgi/admin.py:1221 +#, fuzzy msgid "from %(start)s to %(end)s" msgstr "de %(start)s a %(end)s" @@ -1017,6 +1045,7 @@ msgid "Change list ownership passwords" msgstr "Cambiar la clave del propietario de la lista" #: Mailman/Cgi/admin.py:1364 +#, fuzzy msgid "" "The list administrators are the people who have ultimate control " "over\n" @@ -1138,6 +1167,7 @@ msgstr "Direcci #: Mailman/Cgi/admin.py:1529 Mailman/Cgi/admin.py:1703 bin/add_members:175 #: bin/clone_member:136 bin/sync_members:268 +#, fuzzy msgid "Banned address (matched %(pattern)s)" msgstr "Dirección vetada (coincidencia %(pattern)s)" @@ -1194,6 +1224,7 @@ msgid "%(schange_to)s is already a member" msgstr "%(schange_to)s ya es un suscriptor" #: Mailman/Cgi/admin.py:1620 +#, fuzzy msgid "%(schange_to)s matches banned pattern %(spat)s" msgstr "%(schange_to)s está vetada (concordancia: %(spat)s)" @@ -1234,6 +1265,7 @@ msgid "Not subscribed" msgstr "No está suscrito" #: Mailman/Cgi/admin.py:1773 +#, fuzzy msgid "Ignoring changes to deleted member: %(user)s" msgstr "Ignorando los cambios del usuario borrado: %(user)s" @@ -1246,10 +1278,12 @@ msgid "Error Unsubscribing:" msgstr "Error dando de baja la suscripción:" #: Mailman/Cgi/admindb.py:235 Mailman/Cgi/admindb.py:248 +#, fuzzy msgid "%(realname)s Administrative Database" msgstr "Base de datos Administrativa %(realname)s" #: Mailman/Cgi/admindb.py:238 +#, fuzzy msgid "%(realname)s Administrative Database Results" msgstr "Resultados de la base de datos administrativa de %(realname)s" @@ -1278,6 +1312,7 @@ msgid "Discard all messages marked Defer" msgstr "Descartar todos los mensajes marcados como Diferir" #: Mailman/Cgi/admindb.py:298 +#, fuzzy msgid "all of %(esender)s's held messages." msgstr "todos los mensajes retenidos de %(esender)s." @@ -1298,6 +1333,7 @@ msgid "list of available mailing lists." msgstr "lista de listas de distribución que están disponibles." #: Mailman/Cgi/admindb.py:362 +#, fuzzy msgid "You must specify a list name. Here is the %(link)s" msgstr "Tienes que especificar un nombre de lista. Aquí está el %(link)s" @@ -1379,6 +1415,7 @@ msgid "The sender is now a member of this list" msgstr "El remitente es ahora suscriptor de esta lista" #: Mailman/Cgi/admindb.py:568 +#, fuzzy msgid "Add %(esender)s to one of these sender filters:" msgstr "añadir %(esender)s a uno de estos filtros de remitentes:" @@ -1399,6 +1436,7 @@ msgid "Rejects" msgstr "Rechazar" #: Mailman/Cgi/admindb.py:584 +#, fuzzy msgid "" "Ban %(esender)s from ever subscribing to this\n" " mailing list" @@ -1416,6 +1454,7 @@ msgstr "" " individualmente, o puede " #: Mailman/Cgi/admindb.py:591 +#, fuzzy msgid "view all messages from %(esender)s" msgstr "ver todos los mensajes de %(esender)s" @@ -1494,6 +1533,7 @@ msgid " is already a member" msgstr " ya es un suscriptor" #: Mailman/Cgi/admindb.py:973 +#, fuzzy msgid "%(addr)s is banned (matched: %(patt)s)" msgstr "%(addr)s está vetada (concordancia: %(patt)s)" @@ -1502,6 +1542,7 @@ msgid "Confirmation string was empty." msgstr "La cadena de confirmación está vacia" #: Mailman/Cgi/confirm.py:108 +#, fuzzy msgid "" "Invalid confirmation string:\n" " %(safecookie)s.\n" @@ -1544,6 +1585,7 @@ msgstr "" " cancelada" #: Mailman/Cgi/confirm.py:178 +#, fuzzy msgid "System error, bad content: %(content)s" msgstr "Error del sistema, contenido corrupto: %(content)s" @@ -1582,6 +1624,7 @@ msgid "Confirm subscription request" msgstr "Confirmar la solicitud de suscripción" #: Mailman/Cgi/confirm.py:259 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " subscription request to the mailing list %(listname)s. Your\n" @@ -1618,6 +1661,7 @@ msgstr "" " desea suscribirse a esta lista." #: Mailman/Cgi/confirm.py:275 +#, fuzzy msgid "" "Your confirmation is required in order to continue with\n" " the subscription request to the mailing list %(listname)s.\n" @@ -1670,6 +1714,7 @@ msgid "Preferred language:" msgstr "Idioma preferido:" #: Mailman/Cgi/confirm.py:317 +#, fuzzy msgid "Subscribe to list %(listname)s" msgstr "Subscribirse a la lista %(listname)s" @@ -1686,6 +1731,7 @@ msgid "Awaiting moderator approval" msgstr "Esperando el visto bueno del moderador" #: Mailman/Cgi/confirm.py:386 +#, fuzzy msgid "" " You have successfully confirmed your subscription request to " "the\n" @@ -1719,6 +1765,7 @@ msgid "You are already a member of this mailing list!" msgstr "¡Ya está suscrito a esta lista de distribución!" #: Mailman/Cgi/confirm.py:400 +#, fuzzy msgid "" "You are currently banned from subscribing to\n" " this list. If you think this restriction is erroneous, please\n" @@ -1744,6 +1791,7 @@ msgid "Subscription request confirmed" msgstr "Petición de suscripción confirmada" #: Mailman/Cgi/confirm.py:418 +#, fuzzy msgid "" " You have successfully confirmed your subscription request for\n" " \"%(addr)s\" to the %(listname)s mailing list. A separate\n" @@ -1774,6 +1822,7 @@ msgid "Unsubscription request confirmed" msgstr "Petición de desuscripción confirmada" #: Mailman/Cgi/confirm.py:469 +#, fuzzy msgid "" " You have successfully unsubscribed from the %(listname)s " "mailing\n" @@ -1794,6 +1843,7 @@ msgid "Not available" msgstr "No disponible" #: Mailman/Cgi/confirm.py:498 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " unsubscription request from the mailing list %(listname)s. " @@ -1837,6 +1887,7 @@ msgid "You have canceled your change of address request." msgstr "Ha cancelado la solicitud de cambio de dirección" #: Mailman/Cgi/confirm.py:555 +#, fuzzy msgid "" "%(newaddr)s is banned from subscribing to the\n" " %(realname)s list. If you think this restriction is erroneous,\n" @@ -1848,6 +1899,7 @@ msgstr "" "lista en la dirección %(owneraddr)s." #: Mailman/Cgi/confirm.py:560 +#, fuzzy msgid "" "%(newaddr)s is already a member of\n" " the %(realname)s list. It is possible that you are attempting\n" @@ -1863,6 +1915,7 @@ msgid "Change of address request confirmed" msgstr "Solicitud de cambio de dirección confirmada" #: Mailman/Cgi/confirm.py:571 +#, fuzzy msgid "" " You have successfully changed your address on the %(listname)s\n" " mailing list from %(oldaddr)s to %(newaddr)s. " @@ -1884,6 +1937,7 @@ msgid "globally" msgstr "globalmente" #: Mailman/Cgi/confirm.py:605 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " change of address request for the mailing list %(listname)s. " @@ -1946,6 +2000,7 @@ msgid "Sender discarded message via web." msgstr "El remitente descartó el mensaje via web." #: Mailman/Cgi/confirm.py:674 +#, fuzzy msgid "" "The held message with the Subject:\n" " header %(subject)s could not be found. The most " @@ -1966,6 +2021,7 @@ msgid "Posted message canceled" msgstr "Mensaje enviado cancelado" #: Mailman/Cgi/confirm.py:685 +#, fuzzy msgid "" " You have successfully canceled the posting of your message with\n" " the Subject: header %(subject)s to the mailing list\n" @@ -1988,6 +2044,7 @@ msgstr "" " tratado por el administrador de la lista." #: Mailman/Cgi/confirm.py:735 +#, fuzzy msgid "" "Your confirmation is required in order to cancel the\n" " posting of your message to the mailing list %(listname)s:\n" @@ -2039,6 +2096,7 @@ msgid "Membership re-enabled." msgstr "Subscripción reactivada." #: Mailman/Cgi/confirm.py:798 +#, fuzzy msgid "" " You have successfully re-enabled your membership in the\n" " %(listname)s mailing list. You can now not available" msgstr "No disponible" #: Mailman/Cgi/confirm.py:846 +#, fuzzy msgid "" "Your membership in the %(realname)s mailing list is\n" " currently disabled due to excessive bounces. Your confirmation is\n" @@ -2140,10 +2200,12 @@ msgid "administrative list overview" msgstr "lista general administrativa" #: Mailman/Cgi/create.py:115 +#, fuzzy msgid "List name must not include \"@\": %(safelistname)s" msgstr "El nombre de la lista no puede contener \"@\": %(safelistname)s" #: Mailman/Cgi/create.py:122 +#, fuzzy msgid "List already exists: %(safelistname)s" msgstr "La lista ya existe: %(safelistname)s" @@ -2177,18 +2239,22 @@ msgid "You are not authorized to create new mailing lists" msgstr "No está autorizado para crear listas de distribución nuevas" #: Mailman/Cgi/create.py:182 +#, fuzzy msgid "Unknown virtual host: %(safehostname)s" msgstr "Host virtual desconocido: %(safehostname)s" #: Mailman/Cgi/create.py:218 bin/newlist:219 +#, fuzzy msgid "Bad owner email address: %(s)s" msgstr "Dirección de correo electrónico del propietario incorrecta: %(s)s" #: Mailman/Cgi/create.py:223 bin/newlist:182 bin/newlist:223 +#, fuzzy msgid "List already exists: %(listname)s" msgstr "La lista ya existe: %(listname)s" #: Mailman/Cgi/create.py:231 bin/newlist:217 +#, fuzzy msgid "Illegal list name: %(s)s" msgstr "Nombre de lista ilegal: %(s)s" @@ -2202,6 +2268,7 @@ msgstr "" " de la lista para obtener ayuda." #: Mailman/Cgi/create.py:273 bin/newlist:265 +#, fuzzy msgid "Your new mailing list: %(listname)s" msgstr "Su nueva lista de distribución: %(listname)s" @@ -2210,6 +2277,7 @@ msgid "Mailing list creation results" msgstr "Resultados de la creación de las listas de distribución" #: Mailman/Cgi/create.py:288 +#, fuzzy msgid "" "You have successfully created the mailing list\n" " %(listname)s and notification has been sent to the list owner\n" @@ -2233,6 +2301,7 @@ msgid "Create another list" msgstr "Crear otra lista" #: Mailman/Cgi/create.py:312 +#, fuzzy msgid "Create a %(hostname)s Mailing List" msgstr "Crear un lista de distribución de %(hostname)s" @@ -2334,6 +2403,7 @@ msgstr "" "moderador, por defecto." #: Mailman/Cgi/create.py:432 +#, fuzzy msgid "" "Initial list of supported languages.

                    Note that if you do not\n" " select at least one initial language, the list will use the server\n" @@ -2437,6 +2507,7 @@ msgid "List name is required." msgstr "Nombre de la máquina que prefiere la lista." #: Mailman/Cgi/edithtml.py:154 +#, fuzzy msgid "%(realname)s -- Edit html for %(template_info)s" msgstr "%(realname)s -- Editar el código html para %(template_info)s" @@ -2445,10 +2516,12 @@ msgid "Edit HTML : Error" msgstr "Editar HTML : Error" #: Mailman/Cgi/edithtml.py:161 +#, fuzzy msgid "%(safetemplatename)s: Invalid template" msgstr "%(safetemplatename)s: Plantilla inválida" #: Mailman/Cgi/edithtml.py:166 Mailman/Cgi/edithtml.py:167 +#, fuzzy msgid "%(realname)s -- HTML Page Editing" msgstr "%(realname)s -- Edición del código HTML de las Páginas" @@ -2511,10 +2584,12 @@ msgid "HTML successfully updated." msgstr "Se ha cambiado el código HTML satisfactoriamente" #: Mailman/Cgi/listinfo.py:89 +#, fuzzy msgid "%(hostname)s Mailing Lists" msgstr "Listas de distribución de %(hostname)s" #: Mailman/Cgi/listinfo.py:127 +#, fuzzy msgid "" "

                    There currently are no publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s." @@ -2523,6 +2598,7 @@ msgstr "" " anunciadas públicamente en %(hostname)s. " #: Mailman/Cgi/listinfo.py:131 +#, fuzzy msgid "" "

                    Below is a listing of all the public mailing lists on\n" " %(hostname)s. Click on a list name to get more information " @@ -2545,6 +2621,7 @@ msgid "right" msgstr "correcto" #: Mailman/Cgi/listinfo.py:140 +#, fuzzy msgid "" " To visit the general information page for an unadvertised list,\n" " open a URL similar to this one, but with a '/' and the %(adj)s\n" @@ -2596,6 +2673,7 @@ msgid "CGI script error" msgstr "Error del script CGI" #: Mailman/Cgi/options.py:71 +#, fuzzy msgid "Invalid request method: %(method)s" msgstr "Método de petición inválido: %(method)s" @@ -2610,6 +2688,7 @@ msgstr "Direcci #: Mailman/Cgi/options.py:183 Mailman/Cgi/options.py:240 #: Mailman/Cgi/options.py:264 +#, fuzzy msgid "No such member: %(safeuser)s." msgstr "No existe tal suscriptor: %(safeuser)s." @@ -2661,6 +2740,7 @@ msgid "Note: " msgstr "Nota: " #: Mailman/Cgi/options.py:378 +#, fuzzy msgid "List subscriptions for %(safeuser)s on %(hostname)s" msgstr "suscripciones de %(safeuser)s en %(hostname)s" @@ -2692,6 +2772,7 @@ msgid "You are already using that email address" msgstr "Ya estás usando esa dirección de correo electrónico" #: Mailman/Cgi/options.py:459 +#, fuzzy msgid "" "The new address you requested %(newaddr)s is already a member of the\n" "%(listname)s mailing list, however you have also requested a global change " @@ -2706,6 +2787,7 @@ msgstr "" "%(safeuser)s. " #: Mailman/Cgi/options.py:468 +#, fuzzy msgid "The new address is already a member: %(newaddr)s" msgstr "La dirección nueva ya está dada de alta: %(newaddr)s" @@ -2714,6 +2796,7 @@ msgid "Addresses may not be blank" msgstr "Las direcciones no deberín estar en blanco" #: Mailman/Cgi/options.py:488 +#, fuzzy msgid "A confirmation message has been sent to %(newaddr)s. " msgstr "Se le ha enviado un mensaje de confirmación a %(newaddr)s" @@ -2726,10 +2809,12 @@ msgid "Illegal email address provided" msgstr "Se ha proporcionado una dirección de correo ilegal" #: Mailman/Cgi/options.py:501 +#, fuzzy msgid "%(newaddr)s is already a member of the list." msgstr "%(newaddr)s ya está suscrito a la lista." #: Mailman/Cgi/options.py:504 +#, fuzzy msgid "" "%(newaddr)s is banned from this list. If you\n" " think this restriction is erroneous, please contact\n" @@ -2808,6 +2893,7 @@ msgstr "" " una vez que el moderador haya tomado una decisión" #: Mailman/Cgi/options.py:623 +#, fuzzy msgid "" "You have been successfully unsubscribed from the\n" " mailing list %(fqdn_listname)s. If you were receiving digest\n" @@ -2903,6 +2989,7 @@ msgid "day" msgstr "día" #: Mailman/Cgi/options.py:900 +#, fuzzy msgid "%(days)d %(units)s" msgstr "%(days)d %(units)s" @@ -2915,6 +3002,7 @@ msgid "No topics defined" msgstr "No se han definido temas" #: Mailman/Cgi/options.py:940 +#, fuzzy msgid "" "\n" "You are subscribed to this list with the case-preserved address\n" @@ -2925,6 +3013,7 @@ msgstr "" "mayúsculas y minúsculas." #: Mailman/Cgi/options.py:956 +#, fuzzy msgid "%(realname)s list: member options login page" msgstr "Lista %(realname)s: página de entrada de preferencias de suscriptor" @@ -2933,10 +3022,12 @@ msgid "email address and " msgstr "dirección de correo electrónico y su " #: Mailman/Cgi/options.py:960 +#, fuzzy msgid "%(realname)s list: member options for user %(safeuser)s" msgstr "Lista %(realname)s: opciones de suscripción de %(safeuser)s" #: Mailman/Cgi/options.py:986 +#, fuzzy msgid "" "In order to change your membership option, you must\n" " first log in by giving your %(extra)smembership password in the section\n" @@ -3024,6 +3115,7 @@ msgid "" msgstr "" #: Mailman/Cgi/options.py:1140 +#, fuzzy msgid "Requested topic is not valid: %(topicname)s" msgstr "El tema solicitado no es válido: %(topicname)s" @@ -3052,6 +3144,7 @@ msgid "Private archive - \"./\" and \"../\" not allowed in URL." msgstr "El archivo privado - \"./\" and \"../\" no se permiten en la URL." #: Mailman/Cgi/private.py:109 +#, fuzzy msgid "Private Archive Error - %(msg)s" msgstr "Error en el Archivo Privado - %(msg)s" @@ -3072,6 +3165,7 @@ msgid "Private archive file not found" msgstr "Fichero de archivo privado no encontrado" #: Mailman/Cgi/rmlist.py:76 +#, fuzzy msgid "No such list %(safelistname)s" msgstr "No existe tal lista: %(safelistname)s" @@ -3088,6 +3182,7 @@ msgid "Mailing list deletion results" msgstr "Resultados del borrado de listas de distribución" #: Mailman/Cgi/rmlist.py:187 +#, fuzzy msgid "" "You have successfully deleted the mailing list\n" " %(listname)s." @@ -3096,6 +3191,7 @@ msgstr "" " %(listname)s." #: Mailman/Cgi/rmlist.py:191 +#, fuzzy msgid "" "There were some problems deleting the mailing list\n" " %(listname)s. Contact your site administrator at " @@ -3108,10 +3204,12 @@ msgstr "" " para mayor detalle." #: Mailman/Cgi/rmlist.py:208 +#, fuzzy msgid "Permanently remove mailing list %(realname)s" msgstr "Borrar permanentemente la lista de distribución %(realname)s" #: Mailman/Cgi/rmlist.py:209 +#, fuzzy msgid "Permanently remove mailing list %(realname)s" msgstr "Borrar permanentemente la lista de distribución %(realname)s" @@ -3178,6 +3276,7 @@ msgid "Invalid options to CGI script" msgstr "Argumentos incorrectos al script CGI" #: Mailman/Cgi/roster.py:118 +#, fuzzy msgid "%(realname)s roster authentication failed." msgstr "" "La autentificación a la lista de suscriptores de %(realname)s ha sido " @@ -3248,6 +3347,7 @@ msgstr "" "de correo electrónico de confirmación con las instrucciones a seguir." #: Mailman/Cgi/subscribe.py:262 +#, fuzzy msgid "" "The email address you supplied is banned from this\n" " mailing list. If you think this restriction is erroneous, please\n" @@ -3275,6 +3375,7 @@ msgstr "" "que ha dado es insegura." #: Mailman/Cgi/subscribe.py:278 +#, fuzzy msgid "" "Confirmation from your email address is required, to prevent anyone from\n" "subscribing you without permission. Instructions are being sent to you at\n" @@ -3289,6 +3390,7 @@ msgstr "" "no confirme su suscripción" #: Mailman/Cgi/subscribe.py:290 +#, fuzzy msgid "" "Your subscription request was deferred because %(x)s. Your request has " "been\n" @@ -3311,6 +3413,7 @@ msgid "Mailman privacy alert" msgstr "Alerta de privacidad de Mailman" #: Mailman/Cgi/subscribe.py:316 +#, fuzzy msgid "" "An attempt was made to subscribe your address to the mailing list\n" "%(listaddr)s. You are already subscribed to this mailing list.\n" @@ -3358,6 +3461,7 @@ msgstr "" "recopilaciones (digest)" #: Mailman/Cgi/subscribe.py:344 +#, fuzzy msgid "You have been successfully subscribed to the %(realname)s mailing list." msgstr "" "Se ha suscrito satisfactoriamente a la lista de distribución\n" @@ -3384,6 +3488,7 @@ msgid "Usage:" msgstr "Sintaxis:" #: Mailman/Commands/cmd_confirm.py:50 +#, fuzzy msgid "" "Invalid confirmation string. Note that confirmation strings expire\n" "approximately %(days)s days after the initial request. They also expire if\n" @@ -3409,6 +3514,7 @@ msgstr "" "dirección de correo electrónico?" #: Mailman/Commands/cmd_confirm.py:69 +#, fuzzy msgid "" "You are currently banned from subscribing to this list. If you think this\n" "restriction is erroneous, please contact the list owners at\n" @@ -3493,26 +3599,32 @@ msgid "n/a" msgstr "n/d" #: Mailman/Commands/cmd_info.py:44 +#, fuzzy msgid "List name: %(listname)s" msgstr "Nombre de la lista: %(listname)s" #: Mailman/Commands/cmd_info.py:45 +#, fuzzy msgid "Description: %(description)s" msgstr "Descripción: %(description)s" #: Mailman/Commands/cmd_info.py:46 +#, fuzzy msgid "Postings to: %(postaddr)s" msgstr "Enviar los mensajes a: %(postaddr)s" #: Mailman/Commands/cmd_info.py:47 +#, fuzzy msgid "List Helpbot: %(requestaddr)s" msgstr "Robot de ayuda de la lista: %(requestaddr)s" #: Mailman/Commands/cmd_info.py:48 +#, fuzzy msgid "List Owners: %(owneraddr)s" msgstr "Propietarios: %(owneraddr)s" #: Mailman/Commands/cmd_info.py:49 +#, fuzzy msgid "More information: %(listurl)s" msgstr "Más información en: %(listurl)s" @@ -3536,18 +3648,22 @@ msgstr "" "servidor GNU Mailman.\n" #: Mailman/Commands/cmd_lists.py:44 +#, fuzzy msgid "Public mailing lists at %(hostname)s:" msgstr "Listas de distribución públicas gestionadas por mailman@%(hostname)s" #: Mailman/Commands/cmd_lists.py:66 +#, fuzzy msgid "%(i)3d. List name: %(realname)s" msgstr "%(i)3d. Nombre de la lista: %(realname)s" #: Mailman/Commands/cmd_lists.py:67 +#, fuzzy msgid " Description: %(description)s" msgstr " Descripción: %(description)s" #: Mailman/Commands/cmd_lists.py:68 +#, fuzzy msgid " Requests to: %(requestaddr)s" msgstr " Enviar solicitudes a: %(requestaddr)s" @@ -3585,12 +3701,14 @@ msgstr "" " la respuesta siempre se enviará a la dirección suscrita.\n" #: Mailman/Commands/cmd_password.py:51 Mailman/Commands/cmd_password.py:66 +#, fuzzy msgid "Your password is: %(password)s" msgstr "Clave nueva: %(password)s" #: Mailman/Commands/cmd_password.py:57 Mailman/Commands/cmd_password.py:72 #: Mailman/Commands/cmd_password.py:95 Mailman/Commands/cmd_password.py:121 #: Mailman/Commands/cmd_set.py:149 Mailman/Commands/cmd_set.py:219 +#, fuzzy msgid "You are not a member of the %(listname)s mailing list" msgstr "Se ha dado de baja de la lista de distribución %(listname)s" @@ -3798,6 +3916,7 @@ msgstr "" " de su clave para esta lista de distribución.\n" #: Mailman/Commands/cmd_set.py:122 +#, fuzzy msgid "Bad set command: %(subcmd)s" msgstr "Orden set incorrecta: %(subcmd)s" @@ -3818,6 +3937,7 @@ msgid "on" msgstr "Activar" #: Mailman/Commands/cmd_set.py:154 +#, fuzzy msgid " ack %(onoff)s" msgstr " ack %(onoff)s" @@ -3855,22 +3975,27 @@ msgid "due to bounces" msgstr "debido a rebotes" #: Mailman/Commands/cmd_set.py:186 +#, fuzzy msgid " %(status)s (%(how)s on %(date)s)" msgstr " %(status)s (%(how)s el %(date)s)" #: Mailman/Commands/cmd_set.py:192 +#, fuzzy msgid " myposts %(onoff)s" msgstr " myposts %(onoff)s (sus propios mensajes)" #: Mailman/Commands/cmd_set.py:195 +#, fuzzy msgid " hide %(onoff)s" msgstr " hide %(onoff)s (dirección oculta)" #: Mailman/Commands/cmd_set.py:199 +#, fuzzy msgid " duplicates %(onoff)s" msgstr " duplicates %(onoff)s (mensajes duplicados)" #: Mailman/Commands/cmd_set.py:203 +#, fuzzy msgid " reminders %(onoff)s" msgstr " reminders %(onoff)s (recordatorios mensuales)" @@ -3879,6 +4004,7 @@ msgid "You did not give the correct password" msgstr "La clave que ha enviado no es la correcta" #: Mailman/Commands/cmd_set.py:236 Mailman/Commands/cmd_set.py:283 +#, fuzzy msgid "Bad argument: %(arg)s" msgstr "Argumentos incorrectos: %(arg)s" @@ -3957,6 +4083,7 @@ msgstr "" " comillas!).\n" #: Mailman/Commands/cmd_subscribe.py:62 +#, fuzzy msgid "Bad digest specifier: %(arg)s" msgstr "Especificación de recopilación (digest) incorrecta: %(arg)s" @@ -3965,6 +4092,7 @@ msgid "No valid address found to subscribe" msgstr "No se ha encontrado una dirección válida para suscribirs" #: Mailman/Commands/cmd_subscribe.py:113 +#, fuzzy msgid "" "The email address you supplied is banned from this mailing list.\n" "If you think this restriction is erroneous, please contact the list\n" @@ -4005,6 +4133,7 @@ msgid "This list only supports digest subscriptions!" msgstr "Esta lista solo admite suscripciones tipo digest" #: Mailman/Commands/cmd_subscribe.py:146 +#, fuzzy msgid "" "Your subscription request has been forwarded to the list administrator\n" "at %(listowner)s for review." @@ -4043,6 +4172,7 @@ msgstr "" "sin comillas!)\n" #: Mailman/Commands/cmd_unsubscribe.py:62 +#, fuzzy msgid "%(address)s is not a member of the %(listname)s mailing list" msgstr "La dirección %(address)s no está suscrita a la lista %(listname)s." @@ -4290,6 +4420,7 @@ msgid "Chinese (Taiwan)" msgstr "Chino (Taiwan)" #: Mailman/Deliverer.py:53 +#, fuzzy msgid "" "Note: Since this is a list of mailing lists, administrative\n" "notices like the password reminder will be sent to\n" @@ -4305,14 +4436,17 @@ msgid " (Digest mode)" msgstr " (Modo Resumen)" #: Mailman/Deliverer.py:79 +#, fuzzy msgid "Welcome to the \"%(realname)s\" mailing list%(digmode)s" msgstr "Bienvenido a la lista de distribución %(realname)s%(digmode)s" #: Mailman/Deliverer.py:89 +#, fuzzy msgid "You have been unsubscribed from the %(realname)s mailing list" msgstr "Se ha dado de baja de la lista de distribución %(realname)s" #: Mailman/Deliverer.py:116 +#, fuzzy msgid "%(listfullname)s mailing list reminder" msgstr "Recordatorio de la lista de distribución %(listfullname)s" @@ -4325,6 +4459,7 @@ msgid "Hostile subscription attempt detected" msgstr "Se ha detectado un intento de suscripción hostil" #: Mailman/Deliverer.py:169 +#, fuzzy msgid "" "%(address)s was invited to a different mailing\n" "list, but in a deliberate malicious attempt they tried to confirm the\n" @@ -4337,6 +4472,7 @@ msgstr "" "haga nada por su parte." #: Mailman/Deliverer.py:188 +#, fuzzy msgid "" "You invited %(address)s to your list, but in a\n" "deliberate malicious attempt, they tried to confirm the invitation to a\n" @@ -4350,6 +4486,7 @@ msgstr "" "parte." #: Mailman/Deliverer.py:221 +#, fuzzy msgid "%(listname)s mailing list probe message" msgstr "Mensaje de prueba de la lista de distribución %(listname)s" @@ -4566,8 +4703,8 @@ msgid "" " membership.\n" "\n" "

                    You can control both the\n" -" number\n" +" number\n" " of reminders the member will receive and the\n" " Puede controlar tanto el\n" -" número\n" +" número\n" " de recordatorios que recibirá el suscriptor como la\n" " \n" @@ -4795,8 +4932,8 @@ msgid "" " No those messages too will get discarded. You may " "want\n" " to set up an\n" -" autoresponse\n" +" autoresponse\n" " message for email to the -owner and -admin address." msgstr "" "Aunque el detector de mensjaes rebotados de Mailman es bastante robusto, es\n" @@ -4826,8 +4963,8 @@ msgstr "" " No dichos mensajes se descartarán. Si lo desea, puede " "configurar\n" " un\n" -" contestador automático\n" +" contestador automático\n" " para los mensajes dirigidos a las direcciones -owner and -" "admin." @@ -4897,6 +5034,7 @@ msgstr "" " attempt to notify the member will always be made." #: Mailman/Gui/Bounce.py:194 +#, fuzzy msgid "" "Bad value for %(property)s: %(val)s" @@ -4979,8 +5117,8 @@ msgstr "" " se borra. Si el mensaje saliente queda vacio después de este " "filtrado, entonces se descarta\n" " el mensaje entero. A continuación, si está habilitado\n" -" collapse_alternatives, cada sección\n" +" collapse_alternatives, cada sección\n" " multipart/alternative se reemplaza con el primer " "alternativo que no esté vacio después\n" " del filtrado.\n" @@ -5036,8 +5174,8 @@ msgstr "" "\n" "

                    Las líneas en blanco se ignoran.\n" "\n" -"

                    Vea también Vea también pass_mime_types para obtener un lista de tipos de " "contenido válidos." @@ -5057,8 +5195,8 @@ msgid "" "Use this option to remove each message attachment that does\n" " not have a matching content type. Requirements and formats " "are\n" -" exactly like filter_mime_types.\n" "\n" "

                    Note: if you add entries to this list but don't add\n" @@ -5183,6 +5321,7 @@ msgstr "" " habilitado el gestor del sitio." #: Mailman/Gui/ContentFilter.py:171 +#, fuzzy msgid "Bad MIME type ignored: %(spectype)s" msgstr "Tipo MIME incorrecto ignorado: %(spectype)s" @@ -5299,6 +5438,7 @@ msgstr "" "cuando no esté vacío?" #: Mailman/Gui/Digest.py:145 +#, fuzzy msgid "" "The next digest will be sent as volume\n" " %(volume)s, number %(number)s" @@ -5315,14 +5455,17 @@ msgid "There was no digest to send." msgstr "No había recopilación que enviar." #: Mailman/Gui/GUIBase.py:173 +#, fuzzy msgid "Invalid value for variable: %(property)s" msgstr "Valor incorrecto para la variable: %(property)s" #: Mailman/Gui/GUIBase.py:178 +#, fuzzy msgid "Bad email address for option %(property)s: %(error)s" msgstr "Dirección de correo-e incorrecta de la opción %(property)s: %(error)s" #: Mailman/Gui/GUIBase.py:204 +#, fuzzy msgid "" "The following illegal substitution variables were\n" " found in the %(property)s string:\n" @@ -5338,6 +5481,7 @@ msgstr "" " corrija el problema." #: Mailman/Gui/GUIBase.py:218 +#, fuzzy msgid "" "Your %(property)s string appeared to\n" " have some correctable problems in its new value.\n" @@ -5442,8 +5586,8 @@ msgid "" "

                    In order to split the list ownership duties into\n" " administrators and moderators, you must\n" " set a separate moderator password,\n" -" and also provide the email\n" +" and also provide the email\n" " addresses of the list moderators. Note that the field you\n" " are changing here specifies the list administrators." msgstr "" @@ -5877,13 +6021,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5922,8 +6066,8 @@ msgstr "" " modificando la cabecera Reply-To: hace que sea más " "dificil\n" " mandar respuestas privadas. Véase \n" -" `Reply-To' Munging\n" +" `Reply-To' Munging\n" " Considered Harmful para una discusión general sobre este " "tema.\n" " Véase Reply-To: header\n" " when the reply_goes_to_list\n" +" href=\"?VARHELP=general/" +"reply_goes_to_list\">reply_goes_to_list\n" " option is set to Explicit address.\n" "\n" "

                    There are many reasons not to introduce or override the\n" @@ -5962,13 +6106,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -6002,12 +6146,12 @@ msgstr "" " Otra razón se debe a que modificando la cabecera\n" " Reply-To: hace que sea más dificil mandar respuestas\n" " privadas. Véase \n" -" \n" +" \n" " `Reply-To' Munging Considered Harmful para una\n" " discusión general sobre este tema. Vea\n" -" href=\"http://marc.merlins.org/netrants/reply-to-useful.html" -"\">Reply-To\n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">Reply-To\n" " Munging Considered Useful para ver una opinión contraria.\n" "\n" "

                    Algunas listas de distribución tienen restringida\n" @@ -6077,8 +6221,8 @@ msgid "" " member list addresses, but rather to the owner of those member\n" " lists. In that case, the value of this setting is appended to\n" " the member's account name for such notices. `-owner' is the\n" -" typical choice. This setting has no effect when \"umbrella_list" -"\"\n" +" typical choice. This setting has no effect when " +"\"umbrella_list\"\n" " is \"No\"." msgstr "" "Cuando \"umbrella_list\" está activa es para indicar que\n" @@ -6770,8 +6914,8 @@ msgid "" msgstr "" "¿Debe personalizar Mailman cada envío regular?. A menudo es\n" " útil para listas destinadas al envían información.\n" -" Pinche en los detalles para\n" +" Pinche en los detalles para\n" " leer una discusión de temas que afectan al rendimiento." #: Mailman/Gui/NonDigest.py:61 @@ -7142,6 +7286,7 @@ msgstr "" "consentimiento." #: Mailman/Gui/Privacy.py:104 +#, fuzzy msgid "" "This section allows you to configure subscription and\n" " membership exposure policy. You can also control whether this\n" @@ -7342,8 +7487,8 @@ msgid "" " either individually or as a group. Any\n" " posting from a non-member who is not explicitly accepted,\n" " rejected, or discarded, will have their posting filtered by the\n" -" general\n" +" general\n" " non-member rules.\n" "\n" "

                    In the text boxes below, add one address per line; start the\n" @@ -7370,20 +7515,20 @@ msgstr "" " controlar si los mensajes de los suscriptores se moderan o no.\n" "\n" "

                    Los mensajes de no-susbcriptores se pueden\n" -" aceptar,\n" -" retener\n" +" aceptar,\n" +" retener\n" " para su moderación,\n" -" rechazar (rebotar), o\n" -" descartar automáticamente,\n" +" rechazar (rebotar), o\n" +" descartar automáticamente,\n" " tanto individualmente o en grupo. Cualquier\n" " mensaje de un no-suscriptor que no se acepte,\n" " rechace o descarte automáticamente, se filtrará por las\n" -" reglas\n" +" reglas\n" " generales para los no-suscriptores.\n" "\n" "

                    En la caja de texto de más abajo, agrega una dirección en " @@ -7410,6 +7555,7 @@ msgstr "" "los suscriptores nuevos?" #: Mailman/Gui/Privacy.py:218 +#, fuzzy msgid "" "Each list member has a moderation flag which says\n" " whether messages from the list member can be posted directly " @@ -7452,8 +7598,8 @@ msgstr "" "siempre puede\n" " establecer manualmente la marca de moderación de cada " "suscriptor\n" -" usando las secciones de administración de\n" +" usando las secciones de administración de\n" " los suscriptores." #: Mailman/Gui/Privacy.py:234 @@ -7468,8 +7614,8 @@ msgid "" "If a member posts this many times, within a period of time\n" " the member is automatically moderated. Use 0 to disable. " "See\n" -" member_verbosity_interval for details on the time " "period.\n" "\n" @@ -7554,13 +7700,14 @@ msgstr "" " como respuesta a mensajes de miembros moderados de la lista." #: Mailman/Gui/Privacy.py:290 +#, fuzzy msgid "" "Action to take when anyone posts to the\n" " list from a domain with a DMARC Reject%(quarantine)s Policy." msgstr "" "Acción a tomar cuando alguien publica en la\n" -" lista desde un dominio que tiene una política DMARC Reject" -"%(quarantine)s." +" lista desde un dominio que tiene una política DMARC " +"Reject%(quarantine)s." #: Mailman/Gui/Privacy.py:293 #, fuzzy @@ -7912,14 +8059,14 @@ msgstr "" "Cuando se recibe un mensaje de un no-suscriptor, se comprueba si\n" " el remitente se encuentra en el listado de direcciones " "explícitamente\n" -" aceptadas,\n" -" retenidas,\n" -" rechazadas (rebotadas), o\n" -" descartadas.\n" +" aceptadas,\n" +" retenidas,\n" +" rechazadas (rebotadas), o\n" +" descartadas.\n" " Si no se encuentra en ninguno de estos listados, se realiza " "esta acción." @@ -7932,6 +8079,7 @@ msgstr "" " no-suscriptores que sean automáticamente descartados?" #: Mailman/Gui/Privacy.py:494 +#, fuzzy msgid "" "Text to include in any rejection notice to be sent to\n" " non-members who post to this list. This notice can include\n" @@ -8190,6 +8338,7 @@ msgstr "" " Se ignorarán las reglas de filtrado incompletas." #: Mailman/Gui/Privacy.py:693 +#, fuzzy msgid "" "The header filter rule pattern\n" " '%(safepattern)s' is not a legal regular expression. This\n" @@ -8241,13 +8390,13 @@ msgid "" "

                    The body of the message can also be optionally scanned for\n" " Subject: and Keywords: headers, as\n" " specified by the topics_bodylines_limit\n" +" href=\"?VARHELP=topics/" +"topics_bodylines_limit\">topics_bodylines_limit\n" " configuration variable." msgstr "" "El filtro según el tema, clasifica cada mensaje recibido\n" -" según: los \n" +" según: los \n" " filtros de expresiones regulares que especifique abajo. Si " "las cabeceras\n" " Subject: (asunto) o Keywords " @@ -8271,8 +8420,8 @@ msgstr "" " cabeceras Subject: y Keyword:, como " "se especifica en la\n" " variable de configuración\n" -" topics_bodylines_limit." +" topics_bodylines_limit." #: Mailman/Gui/Topics.py:72 msgid "How many body lines should the topic matcher scan?" @@ -8347,6 +8496,7 @@ msgstr "" " un patrón. Se ignorarán las definiciones incompletas." #: Mailman/Gui/Topics.py:135 +#, fuzzy msgid "" "The topic pattern '%(safepattern)s' is not a\n" " legal regular expression. It will be discarded." @@ -8591,6 +8741,7 @@ msgid "%(listinfo_link)s list run by %(owner_link)s" msgstr "%(listinfo_link)s la administra %(owner_link)s" #: Mailman/HTMLFormatter.py:57 +#, fuzzy msgid "%(realname)s administrative interface" msgstr "Interfaz administrativa de %(realname)s" @@ -8599,6 +8750,7 @@ msgid " (requires authorization)" msgstr " (requiere autorización)" #: Mailman/HTMLFormatter.py:61 +#, fuzzy msgid "Overview of all %(hostname)s mailing lists" msgstr "Panorámica de todas las listas de distribución de %(hostname)s" @@ -8619,6 +8771,7 @@ msgid "; it was disabled by the list administrator" msgstr "; fue inhabilitada por el administrador de su lista" #: Mailman/HTMLFormatter.py:146 +#, fuzzy msgid "" "; it was disabled due to excessive bounces. The\n" " last bounce was received on %(date)s" @@ -8631,6 +8784,7 @@ msgid "; it was disabled for unknown reasons" msgstr "; fue inhabilitado por algún motivo desconocido" #: Mailman/HTMLFormatter.py:151 +#, fuzzy msgid "Note: your list delivery is currently disabled%(reason)s." msgstr "Nota: Actualmente tiene inhabilitada la entrega de correo%(reason)s." @@ -8643,6 +8797,7 @@ msgid "the list administrator" msgstr "el administrador de su lista" #: Mailman/HTMLFormatter.py:157 +#, fuzzy msgid "" "

                    %(note)s\n" "\n" @@ -8663,6 +8818,7 @@ msgstr "" " tiene alguna pregunta o si necesita ayuda." #: Mailman/HTMLFormatter.py:169 +#, fuzzy msgid "" "

                    We have received some recent bounces from your\n" " address. Your current bounce score is %(score)s out of " @@ -8684,6 +8840,7 @@ msgstr "" " los problemas se corrigen pronto." #: Mailman/HTMLFormatter.py:181 +#, fuzzy msgid "" "(Note - you are subscribing to a list of mailing lists, so the %(type)s " "notice will be sent to the admin address for your membership, %(addr)s.)

                    " @@ -8734,6 +8891,7 @@ msgstr "" " decisión del administrador por correo electrónico." #: Mailman/HTMLFormatter.py:208 +#, fuzzy msgid "" "This is %(also)sa private list, which means that the\n" " list of members is not available to non-members." @@ -8743,6 +8901,7 @@ msgstr "" " no estén suscritos." #: Mailman/HTMLFormatter.py:211 +#, fuzzy msgid "" "This is %(also)sa hidden list, which means that the\n" " list of members is available only to the list administrator." @@ -8752,6 +8911,7 @@ msgstr "" " para el administrador de la lista." #: Mailman/HTMLFormatter.py:214 +#, fuzzy msgid "" "This is %(also)sa public list, which means that the\n" " list of members list is available to everyone." @@ -8769,6 +8929,7 @@ msgstr "" " reconocibles por los spammers)." #: Mailman/HTMLFormatter.py:222 +#, fuzzy msgid "" "

                    (Note that this is an umbrella list, intended to\n" " have only other mailing lists as members. Among other things,\n" @@ -8787,6 +8948,7 @@ msgid "either " msgstr " " #: Mailman/HTMLFormatter.py:256 +#, fuzzy msgid "" "To unsubscribe from %(realname)s, get a password reminder,\n" " or change your subscription options %(either)senter your " @@ -8821,6 +8983,7 @@ msgstr "" " dirección de correo electrónico" #: Mailman/HTMLFormatter.py:277 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " members.)" @@ -8829,6 +8992,7 @@ msgstr "" " los suscriptores de la lista.)" #: Mailman/HTMLFormatter.py:281 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " administrator.)" @@ -8892,6 +9056,7 @@ msgid "The current archive" msgstr "El archivo actual" #: Mailman/Handlers/Acknowledge.py:59 +#, fuzzy msgid "%(realname)s post acknowledgement" msgstr "Confirmación de envío a lista de distribución %(realname)s" @@ -8904,6 +9069,7 @@ msgid "" msgstr "" #: Mailman/Handlers/CalcRecips.py:79 +#, fuzzy msgid "" "Your urgent message to the %(realname)s mailing list was not authorized for\n" "delivery. The original message as received by Mailman is attached.\n" @@ -8914,6 +9080,7 @@ msgstr "" "Mailman.\n" #: Mailman/Handlers/CookHeaders.py:180 +#, fuzzy msgid "%(realname)s via %(lrn)s" msgstr "%(realname)s vía %(lrn)s" @@ -8983,6 +9150,7 @@ msgid "Message may contain administrivia" msgstr "El mensaje puede contener solicitudes administrativas" #: Mailman/Handlers/Hold.py:84 +#, fuzzy msgid "" "Please do *not* post administrative requests to the mailing\n" "list. If you wish to subscribe, visit %(listurl)s or send a message with " @@ -9023,10 +9191,12 @@ msgid "Posting to a moderated newsgroup" msgstr "Envío a un grupo de noticias moderado" #: Mailman/Handlers/Hold.py:252 +#, fuzzy msgid "Your message to %(listname)s awaits moderator approval" msgstr "El mensaje enviado a %(listname)s espera la aprobacion del moderador" #: Mailman/Handlers/Hold.py:271 +#, fuzzy msgid "%(listname)s post from %(sender)s requires approval" msgstr "El envio a %(listname)s de %(sender)s precisa de aprobacion" @@ -9071,6 +9241,7 @@ msgid "After content filtering, the message was empty" msgstr "Después del filtrado del contenido, el mensaje quedó vacío" #: Mailman/Handlers/MimeDel.py:269 +#, fuzzy msgid "" "The attached message matched the %(listname)s mailing list's content " "filtering\n" @@ -9089,6 +9260,7 @@ msgid "Content filtered message notification" msgstr "Notificación de mensaje filtrado por contenido" #: Mailman/Handlers/Moderate.py:145 +#, fuzzy msgid "" "Your message has been rejected, probably because you are not subscribed to " "the\n" @@ -9112,6 +9284,7 @@ msgid "The attached message has been automatically discarded." msgstr "El siguiente mensaje ha sido rechazado automáticamente." #: Mailman/Handlers/Replybot.py:75 +#, fuzzy msgid "Auto-response for your message to the \"%(realname)s\" mailing list" msgstr "Respuesta automatica al mensaje dirigido a la lista \"%(realname)s\"" @@ -9120,6 +9293,7 @@ msgid "The Mailman Replybot" msgstr "El contestador automatico de Mailman" #: Mailman/Handlers/Scrubber.py:208 +#, fuzzy msgid "" "An embedded and charset-unspecified text was scrubbed...\n" "Name: %(filename)s\n" @@ -9135,6 +9309,7 @@ msgid "HTML attachment scrubbed and removed" msgstr "Eliminado el documento HTML adjunto" #: Mailman/Handlers/Scrubber.py:234 Mailman/Handlers/Scrubber.py:259 +#, fuzzy msgid "" "An HTML attachment was scrubbed...\n" "URL: %(url)s\n" @@ -9155,6 +9330,7 @@ msgid "unknown sender" msgstr "remitente desconocido" #: Mailman/Handlers/Scrubber.py:276 +#, fuzzy msgid "" "An embedded message was scrubbed...\n" "From: %(who)s\n" @@ -9171,6 +9347,7 @@ msgstr "" "Url : %(url)s\n" #: Mailman/Handlers/Scrubber.py:308 +#, fuzzy msgid "" "A non-text attachment was scrubbed...\n" "Name: %(filename)s\n" @@ -9187,6 +9364,7 @@ msgstr "" "Url : %(url)s\n" #: Mailman/Handlers/Scrubber.py:347 +#, fuzzy msgid "Skipped content of type %(partctype)s\n" msgstr "Omitido el contenido de tipo %(partctype)s\n" @@ -9195,10 +9373,12 @@ msgid "-------------- next part --------------\n" msgstr "------------ próxima parte ------------\n" #: Mailman/Handlers/SpamDetect.py:64 +#, fuzzy msgid "Header matched regexp: %(pattern)s" msgstr "Cabecera coincidente por regla: %(pattern)s" #: Mailman/Handlers/SpamDetect.py:127 +#, fuzzy msgid "" "You are not allowed to post to this mailing list From: a domain which\n" "publishes a DMARC policy of reject or quarantine, and your message has been\n" @@ -9218,6 +9398,7 @@ msgid "Message rejected by filter rule match" msgstr "Mensaje rechazado por activar una regla de filtrado" #: Mailman/Handlers/ToDigest.py:173 +#, fuzzy msgid "%(realname)s Digest, Vol %(volume)d, Issue %(issue)d" msgstr "Resumen de %(realname)s, Vol %(volume)d, Envío %(issue)d" @@ -9254,6 +9435,7 @@ msgid "End of " msgstr "Fin de " #: Mailman/ListAdmin.py:309 +#, fuzzy msgid "Posting of your message titled \"%(subject)s\"" msgstr "El mensaje enviado tenía como asunto \"%(subject)s\"" @@ -9266,6 +9448,7 @@ msgid "Forward of moderated message" msgstr "Reenvío de mensaje moderado" #: Mailman/ListAdmin.py:405 +#, fuzzy msgid "New subscription request to list %(realname)s from %(addr)s" msgstr "Peticion de suscripcion a la lista %(realname)s de %(addr)s" @@ -9278,6 +9461,7 @@ msgid "via admin approval" msgstr "a través de la aprobación del administrador" #: Mailman/ListAdmin.py:466 +#, fuzzy msgid "New unsubscription request from %(realname)s by %(addr)s" msgstr "Solicitud de baja a la lista %(realname)s de %(addr)s" @@ -9290,10 +9474,12 @@ msgid "Original Message" msgstr "Mensaje original" #: Mailman/ListAdmin.py:526 +#, fuzzy msgid "Request to mailing list %(realname)s rejected" msgstr "La peticion a la lista de distribucion %(realname)s ha sido rechazada" #: Mailman/MTA/Manual.py:66 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been created via the through-the-web\n" "interface. In order to complete the activation of this mailing list, the\n" @@ -9319,14 +9505,17 @@ msgstr "" "programa `newaliases':\n" #: Mailman/MTA/Manual.py:82 +#, fuzzy msgid "## %(listname)s mailing list" msgstr "## lista de distribución %(listname)s" #: Mailman/MTA/Manual.py:99 +#, fuzzy msgid "Mailing list creation request for list %(listname)s" msgstr "Solicitud de creación de la lista de distribución %(listname)s" #: Mailman/MTA/Manual.py:113 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been removed via the through-the-web\n" "interface. In order to complete the de-activation of this mailing list, " @@ -9344,6 +9533,7 @@ msgstr "" "la orden 'newaliases'.\n" #: Mailman/MTA/Manual.py:123 +#, fuzzy msgid "" "\n" "To finish removing your mailing list, you must edit your /etc/aliases (or\n" @@ -9361,14 +9551,17 @@ msgstr "" "## Lista de distribución %(listname)s" #: Mailman/MTA/Manual.py:142 +#, fuzzy msgid "Mailing list removal request for list %(listname)s" msgstr "Solicitud de eliminación de la lista de distribución %(listname)s" #: Mailman/MTA/Postfix.py:442 +#, fuzzy msgid "checking permissions on %(file)s" msgstr "comprobando los permisos de %(file)s" #: Mailman/MTA/Postfix.py:452 +#, fuzzy msgid "%(file)s permissions must be 0664 (got %(octmode)s)" msgstr "Los permisos de %(file)s deberían ser 0664 (y son %(octmode)s)" @@ -9382,40 +9575,48 @@ msgid "(fixing)" msgstr "(corrigiendo)" #: Mailman/MTA/Postfix.py:470 +#, fuzzy msgid "checking ownership of %(dbfile)s" msgstr "Comprobando la propiedad de %(dbfile)s" #: Mailman/MTA/Postfix.py:478 +#, fuzzy msgid "%(dbfile)s owned by %(owner)s (must be owned by %(user)s" msgstr "" "El propietario de %(dbfile)s es %(owner)s (tiene que pertenecer a %(user)s" #: Mailman/MTA/Postfix.py:491 +#, fuzzy msgid "%(dbfile)s permissions must be 0664 (got %(octmode)s)" msgstr "Los permisos de %(dbfile)s deberían ser 0664 (y son %(octmode)s)" #: Mailman/MailList.py:219 +#, fuzzy msgid "Your confirmation is required to join the %(listname)s mailing list" msgstr "" "Hace falta su confirmación para suscribirse a la lista de distribución " "%(listname)s." #: Mailman/MailList.py:230 +#, fuzzy msgid "Your confirmation is required to leave the %(listname)s mailing list" msgstr "" "Hace falta su confirmación para abandonar la lista de distribución " "%(listname)s." #: Mailman/MailList.py:999 Mailman/MailList.py:1492 +#, fuzzy msgid " from %(remote)s" msgstr " de %(remote)s" #: Mailman/MailList.py:1043 +#, fuzzy msgid "subscriptions to %(realname)s require moderator approval" msgstr "" "las suscripciones a %(realname)s necesitan el visto bueno del administrador" #: Mailman/MailList.py:1125 bin/add_members:299 +#, fuzzy msgid "%(realname)s subscription notification" msgstr "Notificación de suscripción a %(realname)s" @@ -9424,6 +9625,7 @@ msgid "unsubscriptions require moderator approval" msgstr "las bajas a %(realname)s necesitan el visto bueno del moderador" #: Mailman/MailList.py:1166 +#, fuzzy msgid "%(realname)s unsubscribe notification" msgstr "Notificación de desuscripción a %(realname)s" @@ -9443,6 +9645,7 @@ msgid "via web confirmation" msgstr "Cadena de confirmación incorrecta" #: Mailman/MailList.py:1396 +#, fuzzy msgid "subscriptions to %(name)s require administrator approval" msgstr "" "La suscripción a %(name)s requiere aprobación por\n" @@ -9463,6 +9666,7 @@ msgid "Last autoresponse notification for today" msgstr "Última notificación de autorespuesta de hoy" #: Mailman/Queue/BounceRunner.py:360 +#, fuzzy msgid "" "The attached message was received as a bounce, but either the bounce format\n" "was not recognized, or no member addresses could be extracted from it. " @@ -9552,6 +9756,7 @@ msgid "Original message suppressed by Mailman site configuration\n" msgstr "Mensaje original omitido por la configuración global de Mailman" #: Mailman/htmlformat.py:675 +#, fuzzy msgid "Delivered by Mailman
                    version %(version)s" msgstr "Entregado via Mailman
                    versión %(version)s" @@ -9640,6 +9845,7 @@ msgid "Server Local Time" msgstr "Hora local del servidor" #: Mailman/i18n.py:180 +#, fuzzy msgid "" "%(wday)s %(mon)s %(day)2i %(hh)02i:%(mm)02i:%(ss)02i %(tzname)s %(year)04i" msgstr "" @@ -9759,6 +9965,7 @@ msgstr "" "ficheros puede ser `-'.\n" #: bin/add_members:162 bin/add_members:172 +#, fuzzy msgid "Already a member: %(member)s" msgstr "Ya está suscrito: %(member)s" @@ -9767,26 +9974,32 @@ msgid "Bad/Invalid email address: blank line" msgstr "Dirección de correo electrónico incorrecta/inválida: línea en blanco" #: bin/add_members:180 +#, fuzzy msgid "Bad/Invalid email address: %(member)s" msgstr "Dirección de correo electrónico incorrecta/inválida: %(member)s" #: bin/add_members:182 +#, fuzzy msgid "Hostile address (illegal characters): %(member)s" msgstr "Dirección hostil (caracteres no válidos): %(member)s" #: bin/add_members:185 +#, fuzzy msgid "Invited: %(member)s" msgstr "Invitado: %(member)s" #: bin/add_members:187 +#, fuzzy msgid "Subscribed: %(member)s" msgstr "suscrito: %(member)s" #: bin/add_members:237 +#, fuzzy msgid "Bad argument to -w/--welcome-msg: %(arg)s" msgstr "Argumento incorrecto a -w/--welcome-msg: %(arg)s" #: bin/add_members:244 +#, fuzzy msgid "Bad argument to -a/--admin-notify: %(arg)s" msgstr "Argumento incorrecto a -a/--admin-notify: %(arg)s" @@ -9803,6 +10016,7 @@ msgstr "Para incluir un invite-msg-file hay que usar la opci #: bin/add_members:261 bin/config_list:110 bin/export.py:271 bin/find_member:97 #: bin/inject:91 bin/list_admins:90 bin/list_members:252 bin/sync_members:222 #: cron/bumpdigests:86 +#, fuzzy msgid "No such list: %(listname)s" msgstr "No existe tal lista: %(listname)s" @@ -9813,6 +10027,7 @@ msgid "Nothing to do." msgstr "No hace falta hacer nada." #: bin/arch:19 +#, fuzzy msgid "" "Rebuild a list's archive.\n" "\n" @@ -9909,6 +10124,7 @@ msgid "listname is required" msgstr "Hace falta un nombre de lista" #: bin/arch:143 bin/change_pw:107 bin/config_list:257 +#, fuzzy msgid "" "No such list \"%(listname)s\"\n" "%(e)s" @@ -9917,10 +10133,12 @@ msgstr "" "%(e)s" #: bin/arch:168 +#, fuzzy msgid "Cannot open mbox file %(mbox)s: %(msg)s" msgstr "No se ha podido abrir el fichero mbox %(mbox)s: %(msg)s" #: bin/b4b5-archfix:19 +#, fuzzy msgid "" "Fix the MM2.1b4 archives.\n" "\n" @@ -10070,6 +10288,7 @@ msgstr "" " Imprimir este mensaje y salir.\n" #: bin/change_pw:145 +#, fuzzy msgid "Bad arguments: %(strargs)s" msgstr "Argumento incorrecto: %(strargs)s" @@ -10078,14 +10297,17 @@ msgid "Empty list passwords are not allowed" msgstr "No se permiten claves vacias" #: bin/change_pw:181 +#, fuzzy msgid "New %(listname)s password: %(notifypassword)s" msgstr "Clave nueva de %(listname)s: %(notifypassword)s" #: bin/change_pw:190 +#, fuzzy msgid "Your new %(listname)s list password" msgstr "Su clave nueva de la lista %(listname)s" #: bin/change_pw:191 +#, fuzzy msgid "" "The site administrator at %(hostname)s has changed the password for your\n" "mailing list %(listname)s. It is now\n" @@ -10114,6 +10336,7 @@ msgstr "" " %(adminurl)s\n" #: bin/check_db:19 +#, fuzzy msgid "" "Check a list's config database file for integrity.\n" "\n" @@ -10194,10 +10417,12 @@ msgid "List:" msgstr "Lista:" #: bin/check_db:148 +#, fuzzy msgid " %(file)s: okay" msgstr " %(file)s: Correcto" #: bin/check_perms:20 +#, fuzzy msgid "" "Check the permissions for the Mailman installation.\n" "\n" @@ -10220,44 +10445,54 @@ msgstr "" " ser verbosa.\n" #: bin/check_perms:110 +#, fuzzy msgid " checking gid and mode for %(path)s" msgstr " comprobando gid y modo de %(path)s" #: bin/check_perms:122 +#, fuzzy msgid "%(path)s bad group (has: %(groupname)s, expected %(MAILMAN_GROUP)s)" msgstr "" "%(path)s tiene un grupo incorrecto (tiene: %(groupname)s, se esperaba que " "tuviera %(MAILMAN_GROUP)s)" #: bin/check_perms:151 +#, fuzzy msgid "directory permissions must be %(octperms)s: %(path)s" msgstr "los permisos del directorio tienen que ser %(octperms)s: %(path)s" #: bin/check_perms:160 +#, fuzzy msgid "source perms must be %(octperms)s: %(path)s" msgstr "los permisos tienen que ser %(octperms)s: %(path)s" #: bin/check_perms:171 +#, fuzzy msgid "article db files must be %(octperms)s: %(path)s" msgstr "los permisos de los ficheros db tienen que ser %(octperms)s: %(path)s" #: bin/check_perms:183 +#, fuzzy msgid "checking mode for %(prefix)s" msgstr "comprobando el modo para %(prefix)s" #: bin/check_perms:193 +#, fuzzy msgid "WARNING: directory does not exist: %(d)s" msgstr "ADVERTENCIA: el directorio no existe: %(d)s" #: bin/check_perms:197 +#, fuzzy msgid "directory must be at least 02775: %(d)s" msgstr "El directorio tiene que estar como mínimo a 02775: %(d)s" #: bin/check_perms:209 +#, fuzzy msgid "checking perms on %(private)s" msgstr "Comprobando los permisos en %(private)s" #: bin/check_perms:214 +#, fuzzy msgid "%(private)s must not be other-readable" msgstr "%(private)s no puede se legible por terceros" @@ -10280,6 +10515,7 @@ msgid "mbox file must be at least 0660:" msgstr "El fichero mbox tiene que estar como mínimo a 0660" #: bin/check_perms:263 +#, fuzzy msgid "%(dbdir)s \"other\" perms must be 000" msgstr "Los permisos de %(dbdir)s para \"el resto\" tienen que ser 000" @@ -10288,26 +10524,32 @@ msgid "checking cgi-bin permissions" msgstr "comprobando los permisos de los cgi-bin" #: bin/check_perms:278 +#, fuzzy msgid " checking set-gid for %(path)s" msgstr " comprobando el set-gid de %(path)s" #: bin/check_perms:282 +#, fuzzy msgid "%(path)s must be set-gid" msgstr "%(path)s tiene que ser sert-gid" #: bin/check_perms:292 +#, fuzzy msgid "checking set-gid for %(wrapper)s" msgstr "comprobando el set-gid de %(wrapper)s" #: bin/check_perms:296 +#, fuzzy msgid "%(wrapper)s must be set-gid" msgstr "%(wrapper)s tiene que se set-gid" #: bin/check_perms:306 +#, fuzzy msgid "checking permissions on %(pwfile)s" msgstr "comprobando los permisos de %(pwfile)s" #: bin/check_perms:315 +#, fuzzy msgid "%(pwfile)s permissions must be exactly 0640 (got %(octmode)s)" msgstr "" "Los permisos de %(pwfile)s tienen que ser exactamente 06740 (tiene " @@ -10318,10 +10560,12 @@ msgid "checking permissions on list data" msgstr "comprobando los permisos sobre los datos de la lista" #: bin/check_perms:348 +#, fuzzy msgid " checking permissions on: %(path)s" msgstr " comprobando los permisos de %(path)s" #: bin/check_perms:356 +#, fuzzy msgid "file permissions must be at least 660: %(path)s" msgstr "Los permisos del fichero tienen que estar como mínimo a 660: %(path)s" @@ -10414,6 +10658,7 @@ msgid "Unix-From line changed: %(lineno)d" msgstr "Línea From estilo Unix cambiada: %(lineno)d" #: bin/cleanarch:111 +#, fuzzy msgid "Bad status number: %(arg)s" msgstr "Número de status incorrecto: %(arg)s" @@ -10570,10 +10815,12 @@ msgid " original address removed:" msgstr " la dirección original se ha borrado:" #: bin/clone_member:202 +#, fuzzy msgid "Not a valid email address: %(toaddr)s" msgstr "No es una dirección válida: %(toaddr)s" #: bin/clone_member:215 +#, fuzzy msgid "" "Error opening list \"%(listname)s\", skipping.\n" "%(e)s" @@ -10694,6 +10941,7 @@ msgstr "" "\n" #: bin/config_list:118 +#, fuzzy msgid "" "# -*- python -*-\n" "# -*- coding: %(charset)s -*-\n" @@ -10715,22 +10963,27 @@ msgid "legal values are:" msgstr "los valores correctos son:" #: bin/config_list:270 +#, fuzzy msgid "attribute \"%(k)s\" ignored" msgstr "se ha ignorado el atributo \"%(k)s\"" #: bin/config_list:273 +#, fuzzy msgid "attribute \"%(k)s\" changed" msgstr "atributo \"%(k)s\" cambiado" #: bin/config_list:279 +#, fuzzy msgid "Non-standard property restored: %(k)s" msgstr "Propiedad no estándar restaurada: %(k)s" #: bin/config_list:288 +#, fuzzy msgid "Invalid value for property: %(k)s" msgstr "Valor inválido de la propiedad: %(k)s" #: bin/config_list:291 +#, fuzzy msgid "Bad email address for option %(k)s: %(v)s" msgstr "Dirección de correo-e incorrecta en la opción %(k)s: %(v)s" @@ -10796,19 +11049,23 @@ msgstr "" " No mostrar los mensajes de estatus.\n" #: bin/discard:94 +#, fuzzy msgid "Ignoring non-held message: %(f)s" msgstr "Ignorando mensaje no retenido: %(f)s" #: bin/discard:100 +#, fuzzy msgid "Ignoring held msg w/bad id: %(f)s" msgstr "Ignorando mensaje retenido con identificación errónea: %(f)s" #: bin/discard:112 +#, fuzzy msgid "Discarded held msg #%(id)s for list %(listname)s" msgstr "" "Descartado el mensaje retenido #%(id)s dirigido a la lista %(listname)s" #: bin/dumpdb:19 +#, fuzzy msgid "" "Dump the contents of any Mailman `database' file.\n" "\n" @@ -10882,6 +11139,7 @@ msgid "No filename given." msgstr "No se ha proporcionado ningún nombre de fichero." #: bin/dumpdb:108 +#, fuzzy msgid "Bad arguments: %(pargs)s" msgstr "Argumentos incorrectos: %(pargs)s" @@ -10890,14 +11148,17 @@ msgid "Please specify either -p or -m." msgstr "Por favor, especifique -p o -m." #: bin/dumpdb:133 +#, fuzzy msgid "[----- start %(typename)s file -----]" msgstr "[----- principio del fichero %(typename)s -----]" #: bin/dumpdb:139 +#, fuzzy msgid "[----- end %(typename)s file -----]" msgstr "[----- final del fichero %(typename)s -----]" #: bin/dumpdb:142 +#, fuzzy msgid "<----- start object %(cnt)s ----->" msgstr "<----- principio del objecto %(cnt)s ----->" @@ -11121,6 +11382,7 @@ msgid "Setting web_page_url to: %(web_page_url)s" msgstr "Asignando el valor %(web_page_url)s a web_page_url" #: bin/fix_url.py:88 +#, fuzzy msgid "Setting host_name to: %(mailhost)s" msgstr "Asinando el valor %(mailhost)s a host_name" @@ -11160,6 +11422,7 @@ msgstr "" " Imprime este mensaje y termina.\n" #: bin/genaliases:84 +#, fuzzy msgid "genaliases can't do anything useful with mm_cfg.MTA = %(mta)s." msgstr "genaliases no puede hacer nada útil con mm_cfg.MTA = %(mta)s." @@ -11213,6 +11476,7 @@ msgstr "" "Si no se pone, se usa la entrada estándar.\n" #: bin/inject:84 +#, fuzzy msgid "Bad queue directory: %(qdir)s" msgstr "El directorio que representa la cola es incorrecto: %(qdir)s" @@ -11221,6 +11485,7 @@ msgid "A list name is required" msgstr "Hace falta un nombre de lista" #: bin/list_admins:20 +#, fuzzy msgid "" "List all the owners of a mailing list.\n" "\n" @@ -11269,6 +11534,7 @@ msgstr "" "indicar más de una lista en la línea de ordenes.\n" #: bin/list_admins:97 +#, fuzzy msgid "List: %(listname)s, \tOwners: %(owners)s" msgstr "Lista: %(listname)s, \tPropietario: %(owners)s" @@ -11460,10 +11726,12 @@ msgstr "" "\n" #: bin/list_members:198 +#, fuzzy msgid "Bad --nomail option: %(why)s" msgstr "Error en opción --nomail: %(why)s" #: bin/list_members:209 +#, fuzzy msgid "Bad --digest option: %(kind)s" msgstr "Error en opción --digest: %(kind)s" @@ -11477,6 +11745,7 @@ msgid "Could not open file for writing:" msgstr "No he podido abrir el fichero en modo escritura." #: bin/list_owners:20 +#, fuzzy msgid "" "List the owners of a mailing list, or all mailing lists.\n" "\n" @@ -11535,6 +11804,7 @@ msgstr "" "sobre la instalación de Mailman. Requires python 2." #: bin/mailmanctl:20 +#, fuzzy msgid "" "Primary start-up and shutdown script for Mailman's qrunner daemon.\n" "\n" @@ -11739,6 +12009,7 @@ msgstr "" " un mensaje.\n" #: bin/mailmanctl:152 +#, fuzzy msgid "PID unreadable in: %(pidfile)s" msgstr "PID ilegible en: %(pidfile)s" @@ -11747,6 +12018,7 @@ msgid "Is qrunner even running?" msgstr "¿Está el qrunner corriendo acaso?" #: bin/mailmanctl:160 +#, fuzzy msgid "No child with pid: %(pid)s" msgstr "Ningún hijo con pid %(pid)s" @@ -11776,6 +12048,7 @@ msgstr "" "re-ejecutar mailmanctl con la opción -s.\n" #: bin/mailmanctl:233 +#, fuzzy msgid "" "The master qrunner lock could not be acquired, because it appears as if " "some\n" @@ -11803,10 +12076,12 @@ msgstr "" "Saliendo." #: bin/mailmanctl:280 cron/mailpasswds:119 +#, fuzzy msgid "Site list is missing: %(sitelistname)s" msgstr "El sitio de la lista no se encuentra: %(sitelistname)s" #: bin/mailmanctl:305 +#, fuzzy msgid "Run this program as root or as the %(name)s user, or use -u." msgstr "" "Ejecute este programa como root, como el usuario %(name)s o use la opción -u." @@ -11816,6 +12091,7 @@ msgid "No command given." msgstr "No se ha dado ninguna orden" #: bin/mailmanctl:339 +#, fuzzy msgid "Bad command: %(command)s" msgstr "Orden incorrecta: %(command)s" @@ -11840,6 +12116,7 @@ msgid "Starting Mailman's master qrunner." msgstr "Arrancando el qrunner maestro de Mailman" #: bin/mmsitepass:19 +#, fuzzy msgid "" "Set the site password, prompting from the terminal.\n" "\n" @@ -11895,6 +12172,7 @@ msgid "list creator" msgstr "creador de la lista" #: bin/mmsitepass:86 +#, fuzzy msgid "New %(pwdesc)s password: " msgstr "Clave nueva de %(pwdesc)s" @@ -12174,6 +12452,7 @@ msgstr "" "Observe que los nombres de las listas se convierten a minúsculas.\n" #: bin/newlist:162 +#, fuzzy msgid "Unknown language: %(lang)s" msgstr "Idioma desconocido: %(lang)s" @@ -12186,6 +12465,7 @@ msgid "Enter the email of the person running the list: " msgstr "Indique la dirección de correo de la persona que gestionará la lista: " #: bin/newlist:193 +#, fuzzy msgid "Initial %(listname)s password: " msgstr "Clave inicial de %(listname)s: " @@ -12195,13 +12475,14 @@ msgstr "La clave de la lista no puede estar vacia" #: bin/newlist:220 msgid "" -" - owner addresses need to be fully-qualified names like \"owner@example.com" -"\", not just \"owner\"." +" - owner addresses need to be fully-qualified names like \"owner@example." +"com\", not just \"owner\"." msgstr "" " - las direcciones del propietario deben ser completas, como " "\"propietario@example.com\", no solo \"propietario\"." #: bin/newlist:244 +#, fuzzy msgid "Hit enter to notify %(listname)s owner..." msgstr "" "Presione el retorno de carro para notificar al propietario de la lista " @@ -12334,6 +12615,7 @@ msgstr "" "mostrados por la opción -l.\n" #: bin/qrunner:179 +#, fuzzy msgid "%(name)s runs the %(runnername)s qrunner" msgstr "%(name)s ejecuta el qrunner %(runnername)s" @@ -12346,6 +12628,7 @@ msgid "No runner name given." msgstr "No se ha proporcionado ningún nombre de runner" #: bin/rb-archfix:21 +#, fuzzy msgid "" "Reduce disk space usage for Pipermail archives.\n" "\n" @@ -12495,18 +12778,22 @@ msgstr "" " dirección1 ... son las direcciones adicionales a eliminar.\n" #: bin/remove_members:156 +#, fuzzy msgid "Could not open file for reading: %(filename)s." msgstr "No se ha podido abrir leer el fichero: %(filename)s." #: bin/remove_members:163 +#, fuzzy msgid "Error opening list %(listname)s... skipping." msgstr "Error abriendo la lista \"%(listname)s\", omitiéndola." #: bin/remove_members:173 +#, fuzzy msgid "No such member: %(addr)s" msgstr "No existe tal suscriptor: %(addr)s." #: bin/remove_members:178 +#, fuzzy msgid "User `%(addr)s' removed from list: %(listname)s." msgstr "El susbcriptor `%(addr)s' se ha dado de baja de la lista %(listname)s." @@ -12549,10 +12836,12 @@ msgstr "" " Muestra que es lo que está haciendo el script." #: bin/reset_pw.py:77 +#, fuzzy msgid "Changing passwords for list: %(listname)s" msgstr "Cambiando las claves de la lista: %(listname)s" #: bin/reset_pw.py:83 +#, fuzzy msgid "New password for member %(member)40s: %(randompw)s" msgstr "Clave nueva para el suscriptor %(member)40s: %(randompw)s" @@ -12599,18 +12888,22 @@ msgstr "" " mostrar este mensaje de ayuda y salir\n" #: bin/rmlist:73 bin/rmlist:76 +#, fuzzy msgid "Removing %(msg)s" msgstr "Borrando %(msg)s" #: bin/rmlist:81 +#, fuzzy msgid "%(listname)s %(msg)s not found as %(filename)s" msgstr "%(msg)s de %(listname)s no se ha encontrado como %(filename)s" #: bin/rmlist:105 +#, fuzzy msgid "No such list (or list already deleted): %(listname)s" msgstr "No existe tal lista (o se ha borrado ya): %(listname)s" #: bin/rmlist:108 +#, fuzzy msgid "No such list: %(listname)s. Removing its residual archives." msgstr "No existe tal lista: %(listname)s. Borrando sus ficheros residuales." @@ -12668,6 +12961,7 @@ msgstr "" "Ejemplo: show_qfiles qfiles/shunt/*.pck" #: bin/sync_members:19 +#, fuzzy msgid "" "Synchronize a mailing list's membership with a flat file.\n" "\n" @@ -12806,6 +13100,7 @@ msgstr "" " Necesario. Indica la lista a sincronizar.\n" #: bin/sync_members:115 +#, fuzzy msgid "Bad choice: %(yesno)s" msgstr "Mala elección: %(yesno)s" @@ -12822,6 +13117,7 @@ msgid "No argument to -f given" msgstr "No se ha proporcionado ningún argumento a -f" #: bin/sync_members:172 +#, fuzzy msgid "Illegal option: %(opt)s" msgstr "Opción ilegal: %(opt)s" @@ -12834,6 +13130,7 @@ msgid "Must have a listname and a filename" msgstr "Tiene que haber un nombre de lista y un nombre de fichero" #: bin/sync_members:191 +#, fuzzy msgid "Cannot read address file: %(filename)s: %(msg)s" msgstr "No puedo leer el fichero de direcciones: %(filename)s: %(msg)s" @@ -12850,14 +13147,17 @@ msgid "You must fix the preceding invalid addresses first." msgstr "Primero tienes que corregir la dirección inválida precedente." #: bin/sync_members:264 +#, fuzzy msgid "Added : %(s)s" msgstr "Añadida: %(s)s" #: bin/sync_members:289 +#, fuzzy msgid "Removed: %(s)s" msgstr "Borrado: %(s)s" #: bin/transcheck:19 +#, fuzzy msgid "" "\n" "Check a given Mailman translation, making sure that variables and\n" @@ -12967,6 +13267,7 @@ msgstr "" "qfiles/shunt.\n" #: bin/unshunt:85 +#, fuzzy msgid "" "Cannot unshunt message %(filebase)s, skipping:\n" "%(e)s" @@ -12975,6 +13276,7 @@ msgstr "" "%(e)s" #: bin/update:20 +#, fuzzy msgid "" "Perform all necessary upgrades.\n" "\n" @@ -13013,14 +13315,17 @@ msgstr "" "versión anterior. Sabe de versiones viejas, hasta la 1.0b4 (?).\n" #: bin/update:107 +#, fuzzy msgid "Fixing language templates: %(listname)s" msgstr "Corrigiendo la plantilla de los lenguajes: %(listname)s" #: bin/update:196 bin/update:711 +#, fuzzy msgid "WARNING: could not acquire lock for list: %(listname)s" msgstr "ADVERTENCIA: no se pudo adquirir el bloqueo de la lista: %(listname)s" #: bin/update:215 +#, fuzzy msgid "Resetting %(n)s BYBOUNCEs disabled addrs with no bounce info" msgstr "" "Poniendo a cero %(n)s BYBOUNCEs direcciones inhabilitadas sin información de " @@ -13039,6 +13344,7 @@ msgstr "" "con b6, por lo que lo estoy renombrando a %(mbox_dir)s.tmp y procediendo." #: bin/update:255 +#, fuzzy msgid "" "\n" "%(listname)s has both public and private mbox archives. Since this list\n" @@ -13090,6 +13396,7 @@ msgid "- updating old private mbox file" msgstr "- actualizando el antiguo fichero mbox privado" #: bin/update:295 +#, fuzzy msgid "" " unknown file in the way, moving\n" " %(o_pri_mbox_file)s\n" @@ -13106,6 +13413,7 @@ msgid "- updating old public mbox file" msgstr "- actualizando el antiguo fichero mbox público" #: bin/update:317 +#, fuzzy msgid "" " unknown file in the way, moving\n" " %(o_pub_mbox_file)s\n" @@ -13134,18 +13442,22 @@ msgid "- %(o_tmpl)s doesn't exist, leaving untouched" msgstr "- %(o_tmpl)s no existe, dejándolo intacto" #: bin/update:396 +#, fuzzy msgid "removing directory %(src)s and everything underneath" msgstr "eliminando el directorio %(src)s y todo lo que hay en su interior" #: bin/update:399 +#, fuzzy msgid "removing %(src)s" msgstr "borrando %(src)s" #: bin/update:403 +#, fuzzy msgid "Warning: couldn't remove %(src)s -- %(rest)s" msgstr "Advertencia: no pude borrar %(src)s -- %(rest)s" #: bin/update:408 +#, fuzzy msgid "couldn't remove old file %(pyc)s -- %(rest)s" msgstr "no pude borrar el fichero antiguo %(pyc)s -- %(rest)s" @@ -13154,14 +13466,17 @@ msgid "updating old qfiles" msgstr "actualizando los qfiles antiguos" #: bin/update:455 +#, fuzzy msgid "Warning! Not a directory: %(dirpath)s" msgstr "¡Atención! No es un directorio: %(dirpath)s" #: bin/update:530 +#, fuzzy msgid "message is unparsable: %(filebase)s" msgstr "El mensaje es inanalizable: %(filebase)s" #: bin/update:544 +#, fuzzy msgid "Warning! Deleting empty .pck file: %(pckfile)s" msgstr "¡Atención! Borrando el fichero .pck vacio: %(pckfile)s" @@ -13174,10 +13489,12 @@ msgid "Updating Mailman 2.1.4 pending.pck database" msgstr "Actualizando la base de datos pending.pck de Mailman 2.1.4" #: bin/update:598 +#, fuzzy msgid "Ignoring bad pended data: %(key)s: %(val)s" msgstr "Ignorando los datos pendientes corruptos: %(key)s: %(val)s" #: bin/update:614 +#, fuzzy msgid "WARNING: Ignoring duplicate pending ID: %(id)s." msgstr "ATENCION: Ignorando el ID pendiente duplicado: %(id)s." @@ -13203,6 +13520,7 @@ msgid "done" msgstr "hecho" #: bin/update:691 +#, fuzzy msgid "Updating mailing list: %(listname)s" msgstr "actualizando la lista de distribución" @@ -13264,6 +13582,7 @@ msgid "No updates are necessary." msgstr "No hacen falta actualizaciones." #: bin/update:796 +#, fuzzy msgid "" "Downgrade detected, from version %(hexlversion)s to version %(hextversion)s\n" "This is probably not safe.\n" @@ -13275,10 +13594,12 @@ msgstr "" "Saliendo." #: bin/update:801 +#, fuzzy msgid "Upgrading from version %(hexlversion)s to %(hextversion)s" msgstr "Actualizando de la versión %(hexlversion)s a la %(hextversion)s" #: bin/update:810 +#, fuzzy msgid "" "\n" "ERROR:\n" @@ -13575,6 +13896,7 @@ msgstr "" " " #: bin/withlist:175 +#, fuzzy msgid "Unlocking (but not saving) list: %(listname)s" msgstr "Desbloqueando (pero sin guardar) la lista: %(listname)s" @@ -13583,6 +13905,7 @@ msgid "Finalizing" msgstr "Terminando" #: bin/withlist:188 +#, fuzzy msgid "Loading list %(listname)s" msgstr "Cargando la lista %(listname)s" @@ -13595,6 +13918,7 @@ msgid "(unlocked)" msgstr "(desbloqueado)" #: bin/withlist:197 +#, fuzzy msgid "Unknown list: %(listname)s" msgstr "Lista desconocida: %(listname)s" @@ -13607,18 +13931,22 @@ msgid "--all requires --run" msgstr "--all requiere --run" #: bin/withlist:266 +#, fuzzy msgid "Importing %(module)s..." msgstr "Importando %(module)s..." #: bin/withlist:270 +#, fuzzy msgid "Running %(module)s.%(callable)s()..." msgstr "Ejecutando %(module)s.%(callable)s()..." #: bin/withlist:291 +#, fuzzy msgid "The variable `m' is the %(listname)s MailList instance" msgstr "La variable 'm' es la instancia Lista de distribución %(listname)s" #: cron/bumpdigests:19 +#, fuzzy msgid "" "Increment the digest volume number and reset the digest number to one.\n" "\n" @@ -13648,6 +13976,7 @@ msgstr "" "el nombre de ninguna lista, se aplica a todas.\n" #: cron/checkdbs:20 +#, fuzzy msgid "" "Check for pending admin requests and mail the list owners if necessary.\n" "\n" @@ -13677,10 +14006,12 @@ msgstr "" "\n" #: cron/checkdbs:121 +#, fuzzy msgid "%(count)d %(realname)s moderator request(s) waiting" msgstr "%(count)d solicitudes de %(realname)s a la espera del moderador" #: cron/checkdbs:124 +#, fuzzy msgid "%(realname)s moderator request check result" msgstr "" "Resultado de la comprobación solicitada por el moderador de %(realname)s" @@ -13702,6 +14033,7 @@ msgstr "" "Envíos pendientes:" #: cron/checkdbs:169 +#, fuzzy msgid "" "From: %(sender)s on %(date)s\n" "Subject: %(subject)s\n" @@ -13712,6 +14044,7 @@ msgstr "" "Motivo: %(reason)s" #: cron/cull_bad_shunt:20 +#, fuzzy msgid "" "Cull bad and shunt queues, recommended once per day.\n" "\n" @@ -13750,6 +14083,7 @@ msgstr "" " Mostrar este mensaje y salir.\n" #: cron/disabled:20 +#, fuzzy msgid "" "Process disabled members, recommended once per day.\n" "\n" @@ -13881,6 +14215,7 @@ msgstr "" "\n" #: cron/mailpasswds:19 +#, fuzzy msgid "" "Send password reminders for all lists to all users.\n" "\n" @@ -13938,10 +14273,12 @@ msgid "Password // URL" msgstr "Clave // URL" #: cron/mailpasswds:222 +#, fuzzy msgid "%(host)s mailing list memberships reminder" msgstr "Recordatorios de lista de distribución de %(host)s" #: cron/nightly_gzip:19 +#, fuzzy msgid "" "Re-generate the Pipermail gzip'd archive flat files.\n" "\n" @@ -14045,8 +14382,8 @@ msgstr "" #~ " applied" #~ msgstr "" #~ "Texto que se incluirá en las\n" -#~ " notificaciones de rechazo que se envían\n" #~ " como respuesta a mensajes de miembros moderados de la lista." @@ -14156,8 +14493,8 @@ msgstr "" #~ "\n" #~ "(observe que las comillas invertidas hacen falta)\n" #~ "\n" -#~ "Posiblemente querrá borrar los ficheros -article.bak creados por este\\n" -#~ "\"\n" +#~ "Posiblemente querrá borrar los ficheros -article.bak creados por " +#~ "este\\n\"\n" #~ "\"script cuando esté satisfecho con los resultados." #~ msgid "delivery option set" diff --git a/messages/et/LC_MESSAGES/mailman.po b/messages/et/LC_MESSAGES/mailman.po index d0517994..f948b053 100755 --- a/messages/et/LC_MESSAGES/mailman.po +++ b/messages/et/LC_MESSAGES/mailman.po @@ -64,10 +64,12 @@ msgid "

                    Currently, there are no archives.

                    " msgstr "

                    Arhiiv on tühi.

                    " #: Mailman/Archiver/HyperArch.py:821 +#, fuzzy msgid "Gzip'd Text%(sz)s" msgstr "Gzip pakitud tekst %(sz)s" #: Mailman/Archiver/HyperArch.py:826 +#, fuzzy msgid "Text%(sz)s" msgstr "Tekst%(sz)s" @@ -140,18 +142,22 @@ msgid "Third" msgstr "Kolmas" #: Mailman/Archiver/HyperArch.py:938 +#, fuzzy msgid "%(ord)s quarter %(year)i" msgstr "%(year)i %(ord)s kvartal" #: Mailman/Archiver/HyperArch.py:945 +#, fuzzy msgid "%(month)s %(year)i" msgstr "%(month)s %(year)i" #: Mailman/Archiver/HyperArch.py:950 +#, fuzzy msgid "The Week Of Monday %(day)i %(month)s %(year)i" msgstr "Nädal, mis algab esmaspäeval %(day)i %(month)s %(year)i " #: Mailman/Archiver/HyperArch.py:954 +#, fuzzy msgid "%(day)i %(month)s %(year)i" msgstr "%(day)i %(month)s %(year)i" @@ -160,10 +166,12 @@ msgid "Computing threaded index\n" msgstr "Lõimede indeksi tekitamine\n" #: Mailman/Archiver/HyperArch.py:1319 +#, fuzzy msgid "Updating HTML for article %(seq)s" msgstr "Kirja %(seq)s HTMLi uuendamine" #: Mailman/Archiver/HyperArch.py:1326 +#, fuzzy msgid "article file %(filename)s is missing!" msgstr "artiklifail %(filename)s on puudu!" @@ -180,6 +188,7 @@ msgid "Pickling archive state into " msgstr "Arhiivi staatuse salvestamine " #: Mailman/Archiver/pipermail.py:453 +#, fuzzy msgid "Updating index files for archive [%(archive)s]" msgstr "Arhiivi [%(archive)s] indeksfailide uuendamine" @@ -188,6 +197,7 @@ msgid " Thread" msgstr " Lõim" #: Mailman/Archiver/pipermail.py:597 +#, fuzzy msgid "#%(counter)05d %(msgid)s" msgstr "#%(counter)05d %(msgid)s" @@ -225,6 +235,7 @@ msgid "disabled address" msgstr "peatatud" #: Mailman/Bouncer.py:304 +#, fuzzy msgid " The last bounce received from you was dated %(date)s" msgstr " Viimase tagastuse kuupäevs %(date)s" @@ -252,6 +263,7 @@ msgstr "omaniku" #: Mailman/Cgi/options.py:101 Mailman/Cgi/private.py:108 #: Mailman/Cgi/rmlist.py:75 Mailman/Cgi/roster.py:59 #: Mailman/Cgi/subscribe.py:67 +#, fuzzy msgid "No such list %(safelistname)s" msgstr "Sellist listi pole: %(safelistname)s" @@ -323,6 +335,7 @@ msgstr "" "ei jõua.%(rm)r" #: Mailman/Cgi/admin.py:268 +#, fuzzy msgid "%(hostname)s mailing lists - Admin Links" msgstr "%(hostname)s listid - Administreerimine" @@ -335,6 +348,7 @@ msgid "Mailman" msgstr "Mailman" #: Mailman/Cgi/admin.py:310 +#, fuzzy msgid "" "

                    There currently are no publicly-advertised %(mailmanlink)s\n" " mailing lists on %(hostname)s." @@ -343,6 +357,7 @@ msgstr "" " listi." #: Mailman/Cgi/admin.py:316 +#, fuzzy msgid "" "

                    Below is the collection of publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s. Click on a list\n" @@ -358,6 +373,7 @@ msgid "right " msgstr "right " #: Mailman/Cgi/admin.py:325 +#, fuzzy msgid "" "To visit the administrators configuration page for an\n" " unadvertised list, open a URL similar to this one, but with a '/' " @@ -402,6 +418,7 @@ msgid "No valid variable name found." msgstr "See termin on mulle tundmatu." #: Mailman/Cgi/admin.py:395 +#, fuzzy msgid "" "%(realname)s Mailing list Configuration Help\n" "
                    %(varname)s Option" @@ -410,6 +427,7 @@ msgstr "" "
                    Termin: %(varname)s " #: Mailman/Cgi/admin.py:402 +#, fuzzy msgid "Mailman %(varname)s List Option Help" msgstr "Mailmani listi seadistuse %(varname)s abiinfo" @@ -427,14 +445,17 @@ msgstr "" "see seadistus kuvatud on. Võid ka..." #: Mailman/Cgi/admin.py:431 +#, fuzzy msgid "return to the %(categoryname)s options page." msgstr "tagasi %(categoryname)s kategooria seadistuste juurde minna." #: Mailman/Cgi/admin.py:446 +#, fuzzy msgid "%(realname)s Administration (%(label)s)" msgstr "%(realname)s administreerimine (%(label)s)" #: Mailman/Cgi/admin.py:447 +#, fuzzy msgid "%(realname)s mailing list administration
                    %(label)s Section" msgstr "%(realname)s listi seadistamine.
                    Kategooria: %(label)s" @@ -512,6 +533,7 @@ msgid "Value" msgstr "Väärtus" #: Mailman/Cgi/admin.py:668 +#, fuzzy msgid "" "Badly formed options entry:\n" " %(record)s" @@ -612,10 +634,12 @@ msgid "Move rule down" msgstr "Liiguta reeglit alla" #: Mailman/Cgi/admin.py:870 +#, fuzzy msgid "
                    (Edit %(varname)s)" msgstr "
                    (%(varname)s redigeerimine)" #: Mailman/Cgi/admin.py:872 +#, fuzzy msgid "
                    (Details for %(varname)s)" msgstr "
                    (%(varname)s info)" @@ -657,6 +681,7 @@ msgid "(help)" msgstr "(abi)" #: Mailman/Cgi/admin.py:930 +#, fuzzy msgid "Find member %(link)s:" msgstr "Otsi aadressi %(link)s:" @@ -669,10 +694,12 @@ msgid "Bad regular expression: " msgstr "Vigane regulaaravaldis: " #: Mailman/Cgi/admin.py:1013 +#, fuzzy msgid "%(allcnt)s members total, %(membercnt)s shown" msgstr "Kokku on %(allcnt)s liiget, selles nimekirjas %(membercnt)s" #: Mailman/Cgi/admin.py:1016 +#, fuzzy msgid "%(allcnt)s members total" msgstr "Kokku %(allcnt)s liiget" @@ -851,6 +878,7 @@ msgstr "" " numbritega linkidele:" #: Mailman/Cgi/admin.py:1221 +#, fuzzy msgid "from %(start)s to %(end)s" msgstr "%(start)s - %(end)s" @@ -987,6 +1015,7 @@ msgid "Change list ownership passwords" msgstr "Muuda listi omanikuparooli" #: Mailman/Cgi/admin.py:1364 +#, fuzzy msgid "" "The list administrators are the people who have ultimate control " "over\n" @@ -1159,8 +1188,9 @@ msgid "%(schange_to)s is already a member" msgstr " on juba liige" #: Mailman/Cgi/admin.py:1620 +#, fuzzy msgid "%(schange_to)s matches banned pattern %(spat)s" -msgstr "" +msgstr " on juba liige" #: Mailman/Cgi/admin.py:1622 msgid "Address %(schange_from)s changed to %(schange_to)s" @@ -1200,6 +1230,7 @@ msgid "Not subscribed" msgstr "ei liidetud" #: Mailman/Cgi/admin.py:1773 +#, fuzzy msgid "Ignoring changes to deleted member: %(user)s" msgstr "kustutatud liikmele %(user)s rakendatud muudatusi eirati" @@ -1212,10 +1243,12 @@ msgid "Error Unsubscribing:" msgstr "Viga aadressi kustutamisel:" #: Mailman/Cgi/admindb.py:235 Mailman/Cgi/admindb.py:248 +#, fuzzy msgid "%(realname)s Administrative Database" msgstr "Listi %(realname)s administratiivtaotluste andmebaas" #: Mailman/Cgi/admindb.py:238 +#, fuzzy msgid "%(realname)s Administrative Database Results" msgstr "%(realname)s administratiivtaotluste andmebaas" @@ -1244,6 +1277,7 @@ msgid "Discard all messages marked Defer" msgstr "Kustutada kõik kirjad staatusega peatatud" #: Mailman/Cgi/admindb.py:298 +#, fuzzy msgid "all of %(esender)s's held messages." msgstr "kõik %(esender)s peatatud kirjad." @@ -1264,6 +1298,7 @@ msgid "list of available mailing lists." msgstr "listide nimekiri." #: Mailman/Cgi/admindb.py:362 +#, fuzzy msgid "You must specify a list name. Here is the %(link)s" msgstr "Peate valima listi nime. Siin on %(link)s" @@ -1346,6 +1381,7 @@ msgid "The sender is now a member of this list" msgstr "Saatja on nüüd listi liige" #: Mailman/Cgi/admindb.py:568 +#, fuzzy msgid "Add %(esender)s to one of these sender filters:" msgstr "Lisa %(esender)s ühte nendest saatjafiltritest" @@ -1366,6 +1402,7 @@ msgid "Rejects" msgstr "Keeldub" #: Mailman/Cgi/admindb.py:584 +#, fuzzy msgid "" "Ban %(esender)s from ever subscribing to this\n" " mailing list" @@ -1380,6 +1417,7 @@ msgid "" msgstr "Klikk kirja numbril näitab selle sisu; " #: Mailman/Cgi/admindb.py:591 +#, fuzzy msgid "view all messages from %(esender)s" msgstr "kõiki %(esender)s kirjad" @@ -1512,6 +1550,7 @@ msgstr "" "soov on seetõttu tühistatud. " #: Mailman/Cgi/confirm.py:178 +#, fuzzy msgid "System error, bad content: %(content)s" msgstr "Viga programmis, vigased andmed: %(content)s" @@ -1548,6 +1587,7 @@ msgid "Confirm subscription request" msgstr "Kinnita tellimust" #: Mailman/Cgi/confirm.py:259 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " subscription request to the mailing list %(listname)s. Your\n" @@ -1578,6 +1618,7 @@ msgstr "" "

                    Kliki Katkesta nupule tellimuse tühistamiseks." #: Mailman/Cgi/confirm.py:275 +#, fuzzy msgid "" "Your confirmation is required in order to continue with\n" " the subscription request to the mailing list %(listname)s.\n" @@ -1628,6 +1669,7 @@ msgid "Preferred language:" msgstr "Eelistatud keel:" #: Mailman/Cgi/confirm.py:317 +#, fuzzy msgid "Subscribe to list %(listname)s" msgstr "Telli %(listname)s list" @@ -1644,6 +1686,7 @@ msgid "Awaiting moderator approval" msgstr "Ootab toimetaja otsust" #: Mailman/Cgi/confirm.py:386 +#, fuzzy msgid "" " You have successfully confirmed your subscription request to " "the\n" @@ -1697,6 +1740,7 @@ msgid "Subscription request confirmed" msgstr "Tellimus kinnitati" #: Mailman/Cgi/confirm.py:418 +#, fuzzy msgid "" " You have successfully confirmed your subscription request for\n" " \"%(addr)s\" to the %(listname)s mailing list. A separate\n" @@ -1711,8 +1755,8 @@ msgstr "" " \"%(addr)s\". Selle aadressile saadetakse kohe ka vastav\n" " meil, mis sisaldab sinu parooli ning abiinfot listi kasutamise\n" " kohta.\n" -"

                    Tellimuse seadistamiseks logi sisse." +"

                    Tellimuse seadistamiseks logi sisse." #: Mailman/Cgi/confirm.py:436 msgid "You have canceled your unsubscription request." @@ -1723,6 +1767,7 @@ msgid "Unsubscription request confirmed" msgstr "Tellimuse lõpetamine on kinnitatud" #: Mailman/Cgi/confirm.py:469 +#, fuzzy msgid "" " You have successfully unsubscribed from the %(listname)s " "mailing\n" @@ -1742,6 +1787,7 @@ msgid "Not available" msgstr "pole teada" #: Mailman/Cgi/confirm.py:498 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " unsubscription request from the mailing list %(listname)s. " @@ -1809,6 +1855,7 @@ msgid "Change of address request confirmed" msgstr "Aadressi vahetus on kinnitatud." #: Mailman/Cgi/confirm.py:571 +#, fuzzy msgid "" " You have successfully changed your address on the %(listname)s\n" " mailing list from %(oldaddr)s to %(newaddr)s. " @@ -1816,9 +1863,9 @@ msgid "" " can now proceed to your membership\n" " login page." msgstr "" -"Sinu aadress listis %(listname)s on edukalt vahetatud. Vana aadress oli " -"%(oldaddr)s, uus aadress on %(newaddr)s. Kui soovid, siis saad ka " -"muuta tellimuse omadusi." +"Sinu aadress listis %(listname)s on edukalt vahetatud. Vana aadress oli " +"%(oldaddr)s, uus aadress on %(newaddr)s. Kui soovid, siis saad " +"ka muuta tellimuse omadusi." #: Mailman/Cgi/confirm.py:583 msgid "Confirm change of address request" @@ -1829,6 +1876,7 @@ msgid "globally" msgstr "kõigi listide" #: Mailman/Cgi/confirm.py:605 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " change of address request for the mailing list %(listname)s. " @@ -1889,6 +1937,7 @@ msgid "Sender discarded message via web." msgstr "Saatja tühistas kirja veebi kaudu." #: Mailman/Cgi/confirm.py:674 +#, fuzzy msgid "" "The held message with the Subject:\n" " header %(subject)s could not be found. The most " @@ -1907,6 +1956,7 @@ msgid "Posted message canceled" msgstr "Saadetud kiri tühistati" #: Mailman/Cgi/confirm.py:685 +#, fuzzy msgid "" " You have successfully canceled the posting of your message with\n" " the Subject: header %(subject)s to the mailing list\n" @@ -1925,6 +1975,7 @@ msgid "" msgstr "Listi omanik juba tegeles selle kirjaga" #: Mailman/Cgi/confirm.py:735 +#, fuzzy msgid "" "Your confirmation is required in order to cancel the\n" " posting of your message to the mailing list %(listname)s:\n" @@ -1972,6 +2023,7 @@ msgid "Membership re-enabled." msgstr "Tellimus on taastatud." #: Mailman/Cgi/confirm.py:798 +#, fuzzy msgid "" " You have successfully re-enabled your membership in the\n" " %(listname)s mailing list. You can now not available" msgstr " pole teada" #: Mailman/Cgi/confirm.py:846 +#, fuzzy msgid "" "Your membership in the %(realname)s mailing list is\n" " currently disabled due to excessive bounces. Your confirmation is\n" @@ -2063,10 +2117,12 @@ msgid "administrative list overview" msgstr "listi administratiivlehele." #: Mailman/Cgi/create.py:115 +#, fuzzy msgid "List name must not include \"@\": %(safelistname)s" msgstr "Listi nimes ei tohi olla \"@\"-i: %(safelistname)s" #: Mailman/Cgi/create.py:122 +#, fuzzy msgid "List already exists: %(safelistname)s" msgstr "Sellise nimega list on juba olemas: %(safelistname)s" @@ -2100,18 +2156,22 @@ msgid "You are not authorized to create new mailing lists" msgstr "Sul pole õigusi uute listide loomiseks." #: Mailman/Cgi/create.py:182 +#, fuzzy msgid "Unknown virtual host: %(safehostname)s" msgstr "Tundmatu virtuaalhost: %(safehostname)s" #: Mailman/Cgi/create.py:218 bin/newlist:219 +#, fuzzy msgid "Bad owner email address: %(s)s" msgstr "Viga halduri aadressis: %(s)s" #: Mailman/Cgi/create.py:223 bin/newlist:182 bin/newlist:223 +#, fuzzy msgid "List already exists: %(listname)s" msgstr "Sellise nimega list on juba olemas: %(listname)s" #: Mailman/Cgi/create.py:231 bin/newlist:217 +#, fuzzy msgid "Illegal list name: %(s)s" msgstr "Viga listi nimes: %(s)s" @@ -2124,6 +2184,7 @@ msgstr "" " Palun võtke ühendust listserveri omanikuga." #: Mailman/Cgi/create.py:273 bin/newlist:265 +#, fuzzy msgid "Your new mailing list: %(listname)s" msgstr "Sinu uus list: %(listname)s" @@ -2132,6 +2193,7 @@ msgid "Mailing list creation results" msgstr "List on loodud." #: Mailman/Cgi/create.py:288 +#, fuzzy msgid "" "You have successfully created the mailing list\n" " %(listname)s and notification has been sent to the list owner\n" @@ -2154,6 +2216,7 @@ msgid "Create another list" msgstr "Loo veel üks list" #: Mailman/Cgi/create.py:312 +#, fuzzy msgid "Create a %(hostname)s Mailing List" msgstr "Loo uus list serverisse %(hostname)s" @@ -2246,6 +2309,7 @@ msgstr "" "siis peetakse kõigi uute liikmete kirjad modereerimiseks kinni." #: Mailman/Cgi/create.py:432 +#, fuzzy msgid "" "Initial list of supported languages.

                    Note that if you do not\n" " select at least one initial language, the list will use the server\n" @@ -2351,6 +2415,7 @@ msgid "List name is required." msgstr "Listi nimi on puudu. " #: Mailman/Cgi/edithtml.py:154 +#, fuzzy msgid "%(realname)s -- Edit html for %(template_info)s" msgstr "%(realname)s - Muuda %(template_info)s HTMLi" @@ -2359,10 +2424,12 @@ msgid "Edit HTML : Error" msgstr "HTMLi muutmine : Viga" #: Mailman/Cgi/edithtml.py:161 +#, fuzzy msgid "%(safetemplatename)s: Invalid template" msgstr "%(safetemplatename)s: Vigane template" #: Mailman/Cgi/edithtml.py:166 Mailman/Cgi/edithtml.py:167 +#, fuzzy msgid "%(realname)s -- HTML Page Editing" msgstr "%(realname)s -- HTMLi muutmine" @@ -2421,10 +2488,12 @@ msgid "HTML successfully updated." msgstr "HTML salvestatud." #: Mailman/Cgi/listinfo.py:89 +#, fuzzy msgid "%(hostname)s Mailing Lists" msgstr "Listid serveris %(hostname)s" #: Mailman/Cgi/listinfo.py:127 +#, fuzzy msgid "" "

                    There currently are no publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s." @@ -2433,6 +2502,7 @@ msgstr "" " %(mailmanlink)s listi." #: Mailman/Cgi/listinfo.py:131 +#, fuzzy msgid "" "

                    Below is a listing of all the public mailing lists on\n" " %(hostname)s. Click on a list name to get more information " @@ -2451,6 +2521,7 @@ msgid "right" msgstr "right" #: Mailman/Cgi/listinfo.py:140 +#, fuzzy msgid "" " To visit the general information page for an unadvertised list,\n" " open a URL similar to this one, but with a '/' and the %(adj)s\n" @@ -2510,6 +2581,7 @@ msgstr "Viga aadressis" #: Mailman/Cgi/options.py:183 Mailman/Cgi/options.py:240 #: Mailman/Cgi/options.py:264 +#, fuzzy msgid "No such member: %(safeuser)s." msgstr "Sellist liiget pole: %(safeuser)s." @@ -2560,6 +2632,7 @@ msgid "Note: " msgstr "" #: Mailman/Cgi/options.py:378 +#, fuzzy msgid "List subscriptions for %(safeuser)s on %(hostname)s" msgstr "%(safeuser)s listide tellimused serveris %(hostname)s" @@ -2585,6 +2658,7 @@ msgid "You are already using that email address" msgstr "Sa ju kasutad juba seda aadressi" #: Mailman/Cgi/options.py:459 +#, fuzzy msgid "" "The new address you requested %(newaddr)s is already a member of the\n" "%(listname)s mailing list, however you have also requested a global change " @@ -2598,6 +2672,7 @@ msgstr "" "ära kõigis listides mille liige %(safeuser)s preagu on." #: Mailman/Cgi/options.py:468 +#, fuzzy msgid "The new address is already a member: %(newaddr)s" msgstr "%(newaddr)s on juba selle listi tellja." @@ -2606,6 +2681,7 @@ msgid "Addresses may not be blank" msgstr "Aadressid ei tohi olla tühjad" #: Mailman/Cgi/options.py:488 +#, fuzzy msgid "A confirmation message has been sent to %(newaddr)s. " msgstr "Kinnitus saadeti aadressile %(newaddr)s." @@ -2618,6 +2694,7 @@ msgid "Illegal email address provided" msgstr "Vigane meiliaadress" #: Mailman/Cgi/options.py:501 +#, fuzzy msgid "%(newaddr)s is already a member of the list." msgstr "%(newaddr)s on juba selle listi tellinud." @@ -2699,6 +2776,7 @@ msgstr "" "sellest sulle." #: Mailman/Cgi/options.py:623 +#, fuzzy msgid "" "You have been successfully unsubscribed from the\n" " mailing list %(fqdn_listname)s. If you were receiving digest\n" @@ -2785,6 +2863,7 @@ msgid "day" msgstr "päev" #: Mailman/Cgi/options.py:900 +#, fuzzy msgid "%(days)d %(units)s" msgstr "%(days)d %(units)s" @@ -2797,6 +2876,7 @@ msgid "No topics defined" msgstr "Ühtegi teemat pole määratud" #: Mailman/Cgi/options.py:940 +#, fuzzy msgid "" "\n" "You are subscribed to this list with the case-preserved address\n" @@ -2807,6 +2887,7 @@ msgstr "" "%(cpuser)s." #: Mailman/Cgi/options.py:956 +#, fuzzy msgid "%(realname)s list: member options login page" msgstr "List %(realname)s: Logi sisse" @@ -2815,10 +2896,12 @@ msgid "email address and " msgstr "meiliaadress ja " #: Mailman/Cgi/options.py:960 +#, fuzzy msgid "%(realname)s list: member options for user %(safeuser)s" msgstr "Listi %(realname)s seadistused liikmele %(safeuser)s" #: Mailman/Cgi/options.py:986 +#, fuzzy msgid "" "In order to change your membership option, you must\n" " first log in by giving your %(extra)smembership password in the section\n" @@ -2892,6 +2975,7 @@ msgid "" msgstr "" #: Mailman/Cgi/options.py:1140 +#, fuzzy msgid "Requested topic is not valid: %(topicname)s" msgstr "Soovitud teema on vigane: %(topicname)s" @@ -2920,6 +3004,7 @@ msgid "Private archive - \"./\" and \"../\" not allowed in URL." msgstr "" #: Mailman/Cgi/private.py:109 +#, fuzzy msgid "Private Archive Error - %(msg)s" msgstr "Privaatarhiivi viga - %(msg)s" @@ -2957,12 +3042,14 @@ msgid "Mailing list deletion results" msgstr "Listi kustutamise raport" #: Mailman/Cgi/rmlist.py:187 +#, fuzzy msgid "" "You have successfully deleted the mailing list\n" " %(listname)s." msgstr "%(listname)s list on kustutatud." #: Mailman/Cgi/rmlist.py:191 +#, fuzzy msgid "" "There were some problems deleting the mailing list\n" " %(listname)s. Contact your site administrator at " @@ -2973,6 +3060,7 @@ msgstr "" "Võta ühendust serveri administraatoriga aadressil %(sitelist)s" #: Mailman/Cgi/rmlist.py:208 +#, fuzzy msgid "Permanently remove mailing list %(realname)s" msgstr "Kustutada %(realname)s list" @@ -3038,6 +3126,7 @@ msgid "Invalid options to CGI script" msgstr "CGI skript sai vigased argumendid" #: Mailman/Cgi/roster.py:118 +#, fuzzy msgid "%(realname)s roster authentication failed." msgstr "%(realname)s autoriseerimine ebaõnnestus." @@ -3105,6 +3194,7 @@ msgstr "" "kirja." #: Mailman/Cgi/subscribe.py:262 +#, fuzzy msgid "" "The email address you supplied is banned from this\n" " mailing list. If you think this restriction is erroneous, please\n" @@ -3131,6 +3221,7 @@ msgstr "" "reeglitele." #: Mailman/Cgi/subscribe.py:278 +#, fuzzy msgid "" "Confirmation from your email address is required, to prevent anyone from\n" "subscribing you without permission. Instructions are being sent to you at\n" @@ -3143,6 +3234,7 @@ msgstr "" "aadressile %(email)s. Sinu tellimus jõustub alles peale kinnitust." #: Mailman/Cgi/subscribe.py:290 +#, fuzzy msgid "" "Your subscription request was deferred because %(x)s. Your request has " "been\n" @@ -3162,6 +3254,7 @@ msgid "Mailman privacy alert" msgstr "Mailman privaatsushäire" #: Mailman/Cgi/subscribe.py:316 +#, fuzzy msgid "" "An attempt was made to subscribe your address to the mailing list\n" "%(listaddr)s. You are already subscribed to this mailing list.\n" @@ -3201,6 +3294,7 @@ msgid "This list only supports digest delivery." msgstr "See list toetab ainult kokkuvõtete saatmist." #: Mailman/Cgi/subscribe.py:344 +#, fuzzy msgid "You have been successfully subscribed to the %(realname)s mailing list." msgstr "Sa oled nüüd listi %(realname)s liige." @@ -3328,26 +3422,32 @@ msgid "n/a" msgstr "n/a" #: Mailman/Commands/cmd_info.py:44 +#, fuzzy msgid "List name: %(listname)s" msgstr "Listi nimi: %(listname)s" #: Mailman/Commands/cmd_info.py:45 +#, fuzzy msgid "Description: %(description)s" msgstr "Kirjeldus: %(description)s" #: Mailman/Commands/cmd_info.py:46 +#, fuzzy msgid "Postings to: %(postaddr)s" msgstr "Postitusaadress: %(postaddr)s" #: Mailman/Commands/cmd_info.py:47 +#, fuzzy msgid "List Helpbot: %(requestaddr)s" msgstr "Listi abiinfo: %(requestaddr)s" #: Mailman/Commands/cmd_info.py:48 +#, fuzzy msgid "List Owners: %(owneraddr)s" msgstr "Listi omanikud: %(owneraddr)s" #: Mailman/Commands/cmd_info.py:49 +#, fuzzy msgid "More information: %(listurl)s" msgstr "Lähem info: %(listurl)s" @@ -3371,18 +3471,22 @@ msgstr "" "nimekiri.\n" #: Mailman/Commands/cmd_lists.py:44 +#, fuzzy msgid "Public mailing lists at %(hostname)s:" msgstr "Avalikud listid serveris %(hostname)s:" #: Mailman/Commands/cmd_lists.py:66 +#, fuzzy msgid "%(i)3d. List name: %(realname)s" msgstr "%(i)3d. Listi nimi: %(realname)s" #: Mailman/Commands/cmd_lists.py:67 +#, fuzzy msgid " Description: %(description)s" msgstr " Kirjeldus: %(description)s" #: Mailman/Commands/cmd_lists.py:68 +#, fuzzy msgid " Requests to: %(requestaddr)s" msgstr " Päringud aadressile: %(requestaddr)s" @@ -3415,12 +3519,14 @@ msgstr "" " Sel juhul saadetakse ka kinnitus sellele aadressile\n" #: Mailman/Commands/cmd_password.py:51 Mailman/Commands/cmd_password.py:66 +#, fuzzy msgid "Your password is: %(password)s" msgstr "Parool on: %(password)s" #: Mailman/Commands/cmd_password.py:57 Mailman/Commands/cmd_password.py:72 #: Mailman/Commands/cmd_password.py:95 Mailman/Commands/cmd_password.py:121 #: Mailman/Commands/cmd_set.py:149 Mailman/Commands/cmd_set.py:219 +#, fuzzy msgid "You are not a member of the %(listname)s mailing list" msgstr "Sa ei ole listi %(listname)s liige" @@ -3616,6 +3722,7 @@ msgstr "" " saada meeldetuletust oma selle listi parooli kohta.\n" #: Mailman/Commands/cmd_set.py:122 +#, fuzzy msgid "Bad set command: %(subcmd)s" msgstr "Vigane seadistuskorraldus: %(subcmd)s" @@ -3636,6 +3743,7 @@ msgid "on" msgstr "jah" #: Mailman/Commands/cmd_set.py:154 +#, fuzzy msgid " ack %(onoff)s" msgstr " ack %(onoff)s" @@ -3673,22 +3781,27 @@ msgid "due to bounces" msgstr "tagastatud kirjade tõttu" #: Mailman/Commands/cmd_set.py:186 +#, fuzzy msgid " %(status)s (%(how)s on %(date)s)" msgstr " %(status)s (%(how)s %(date)s)" #: Mailman/Commands/cmd_set.py:192 +#, fuzzy msgid " myposts %(onoff)s" msgstr " minu kirjad %(onoff)s" #: Mailman/Commands/cmd_set.py:195 +#, fuzzy msgid " hide %(onoff)s" msgstr " peida %(onoff)s" #: Mailman/Commands/cmd_set.py:199 +#, fuzzy msgid " duplicates %(onoff)s" msgstr " duplikaadid %(onoff)s" #: Mailman/Commands/cmd_set.py:203 +#, fuzzy msgid " reminders %(onoff)s" msgstr " meeldetuletused %(onoff)s" @@ -3697,6 +3810,7 @@ msgid "You did not give the correct password" msgstr "Vale parool" #: Mailman/Commands/cmd_set.py:236 Mailman/Commands/cmd_set.py:283 +#, fuzzy msgid "Bad argument: %(arg)s" msgstr "Vigane argument: %(arg)s" @@ -3774,6 +3888,7 @@ msgstr "" " (ilma nurksulgudeta aadressi ümber ja ilma apostroofideta\n" #: Mailman/Commands/cmd_subscribe.py:62 +#, fuzzy msgid "Bad digest specifier: %(arg)s" msgstr "Vigane argument digest seadistusele: %(arg)s" @@ -3782,6 +3897,7 @@ msgid "No valid address found to subscribe" msgstr "Ei leidnud ühtegi korrektset aadressi tellimuse vormistamiseks" #: Mailman/Commands/cmd_subscribe.py:113 +#, fuzzy msgid "" "The email address you supplied is banned from this mailing list.\n" "If you think this restriction is erroneous, please contact the list\n" @@ -3820,6 +3936,7 @@ msgid "This list only supports digest subscriptions!" msgstr "See list toetab ainult kokkuvõtete tellimist!" #: Mailman/Commands/cmd_subscribe.py:146 +#, fuzzy msgid "" "Your subscription request has been forwarded to the list administrator\n" "at %(listowner)s for review." @@ -3856,6 +3973,7 @@ msgstr "" " apostroofideta!)\n" #: Mailman/Commands/cmd_unsubscribe.py:62 +#, fuzzy msgid "%(address)s is not a member of the %(listname)s mailing list" msgstr "%(address)s ei ole listi %(listname)s listi liige" @@ -4103,6 +4221,7 @@ msgid "Chinese (Taiwan)" msgstr "Hiina (Taivan)" #: Mailman/Deliverer.py:53 +#, fuzzy msgid "" "Note: Since this is a list of mailing lists, administrative\n" "notices like the password reminder will be sent to\n" @@ -4117,14 +4236,17 @@ msgid " (Digest mode)" msgstr " (Kokkuvõtted)" #: Mailman/Deliverer.py:79 +#, fuzzy msgid "Welcome to the \"%(realname)s\" mailing list%(digmode)s" msgstr "Tere tulemast listi \"%(realname)s\" %(digmode)s" #: Mailman/Deliverer.py:89 +#, fuzzy msgid "You have been unsubscribed from the %(realname)s mailing list" msgstr "%(realname)s listi tellimus on lõppenud." #: Mailman/Deliverer.py:116 +#, fuzzy msgid "%(listfullname)s mailing list reminder" msgstr "%(listfullname)s listi meeldetuletus" @@ -4137,6 +4259,7 @@ msgid "Hostile subscription attempt detected" msgstr "Avastati pahatahtlik tellimiskatse." #: Mailman/Deliverer.py:169 +#, fuzzy msgid "" "%(address)s was invited to a different mailing\n" "list, but in a deliberate malicious attempt they tried to confirm the\n" @@ -4148,6 +4271,7 @@ msgstr "" "genereeritud teade. Sa ei pea sellele kuidagi reageerima." #: Mailman/Deliverer.py:188 +#, fuzzy msgid "" "You invited %(address)s to your list, but in a\n" "deliberate malicious attempt, they tried to confirm the invitation to a\n" @@ -4160,6 +4284,7 @@ msgstr "" "teade. Sa ei pea sellele kuidagi reageerima." #: Mailman/Deliverer.py:221 +#, fuzzy msgid "%(listname)s mailing list probe message" msgstr "%(listname)s listi testkiri" @@ -4358,8 +4483,8 @@ msgid "" " membership.\n" "\n" "

                    You can control both the\n" -" number\n" +" number\n" " of reminders the member will receive and the\n" " Meeldetuletuste arvu ja saatmise intervalli saab " -"muuta.\n" +"

                    Meeldetuletuste arvu ja saatmise " +"intervalli saab muuta.\n" "\n" "

                    Veel üks oluline seadistus on tagastuste kehtivus.\n" @@ -4548,8 +4673,8 @@ msgid "" " No those messages too will get discarded. You may " "want\n" " to set up an\n" -" autoresponse\n" +" autoresponse\n" " message for email to the -owner and -admin address." msgstr "" "Kuigi Mailmani tagastuste töötleja on küllaltki intelligentne, ei suuda\n" @@ -4634,6 +4759,7 @@ msgstr "" "teavitada." #: Mailman/Gui/Bounce.py:194 +#, fuzzy msgid "" "Bad value for %(property)s: %(val)s" @@ -4699,10 +4825,10 @@ msgstr "" "on aktiveeritud, siis võrreldakse kirjas olevate manuste tüüpe kõigepealt keelatud tüübid " "nimestikus olevatega. Kõik keelatud tüübiga manused kustutatakse.

                    Edasi, " -"kui on defineeritud lubatud tüübid, siis visatakse minema kõik manused, mille tüüp " -"puudub sellest nimestikust. Kui lubatud tüüpe pole defineeritud, " -"jäetakse see kontroll vahele.\n" +"kui on defineeritud lubatud tüübid, siis visatakse minema kõik manused, " +"mille tüüp puudub sellest nimestikust. Kui lubatud tüüpe pole " +"defineeritud, jäetakse see kontroll vahele.\n" ".

                    Peale seda esialgset filtreerimist eemaldatakse tühjad " "multipart\n" " manused. Kui kirjal peale seda enam sisu pole, siis " @@ -4764,8 +4890,8 @@ msgid "" "Use this option to remove each message attachment that does\n" " not have a matching content type. Requirements and formats " "are\n" -" exactly like filter_mime_types.\n" "\n" "

                    Note: if you add entries to this list but don't add\n" @@ -4869,6 +4995,7 @@ msgstr "" "kasutada ainult siis kui listserveri administraator on seda lubanud." #: Mailman/Gui/ContentFilter.py:171 +#, fuzzy msgid "Bad MIME type ignored: %(spectype)s" msgstr "Vigast MIME tüüpi eirati: %(spectype)s" @@ -4973,6 +5100,7 @@ msgid "" msgstr "Kas Mailman peaks järgmise kokkuvõtte saatma kohe?" #: Mailman/Gui/Digest.py:145 +#, fuzzy msgid "" "The next digest will be sent as volume\n" " %(volume)s, number %(number)s" @@ -4987,14 +5115,17 @@ msgid "There was no digest to send." msgstr "Kokkuvõtet polnud võimalik saata." #: Mailman/Gui/GUIBase.py:173 +#, fuzzy msgid "Invalid value for variable: %(property)s" msgstr "Vigane väärtus muutujale: %(property)s" #: Mailman/Gui/GUIBase.py:178 +#, fuzzy msgid "Bad email address for option %(property)s: %(error)s" msgstr "Vigane meiliaadress seades %(property)s: %(error)s" #: Mailman/Gui/GUIBase.py:204 +#, fuzzy msgid "" "The following illegal substitution variables were\n" " found in the %(property)s string:\n" @@ -5009,6 +5140,7 @@ msgstr "" "

                    Listi töö võib olla häiritud kuni probleemi kõrvaldamiseni." #: Mailman/Gui/GUIBase.py:218 +#, fuzzy msgid "" "Your %(property)s string appeared to\n" " have some correctable problems in its new value.\n" @@ -5103,8 +5235,8 @@ msgid "" "

                    In order to split the list ownership duties into\n" " administrators and moderators, you must\n" " set a separate moderator password,\n" -" and also provide the email\n" +" and also provide the email\n" " addresses of the list moderators. Note that the field you\n" " are changing here specifies the list administrators." msgstr "" @@ -5411,13 +5543,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5470,8 +5602,8 @@ msgstr "Kirjadele m msgid "" "This is the address set in the Reply-To: header\n" " when the reply_goes_to_list\n" +" href=\"?VARHELP=general/" +"reply_goes_to_list\">reply_goes_to_list\n" " option is set to Explicit address.\n" "\n" "

                    There are many reasons not to introduce or override the\n" @@ -5479,13 +5611,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5580,8 +5712,8 @@ msgid "" " member list addresses, but rather to the owner of those member\n" " lists. In that case, the value of this setting is appended to\n" " the member's account name for such notices. `-owner' is the\n" -" typical choice. This setting has no effect when \"umbrella_list" -"\"\n" +" typical choice. This setting has no effect when " +"\"umbrella_list\"\n" " is \"No\"." msgstr "" "Kui \"umbrella_list\" seadistuse abil on tekitatud teistest\n" @@ -5857,8 +5989,8 @@ msgid "" " does not affect the inclusion of the other List-*:\n" " headers.)" msgstr "" -"List-Post: on üks RFC 2369\n" +"List-Post: on üks RFC 2369\n" "defineeritud päistest. Samas on mõnedes teated listides\n" "postitamisõigus piiratud, ainult vähesed väljavalitud saavad postitada.\n" "Seda tüüpi listides tekitab List-Post: päis ainult segadust.\n" @@ -6233,8 +6365,8 @@ msgstr "" "To päises listi aadress listi liikme aadressiga\n" "\n" "

                    Kui kasutada personaliseerimist siis on kirjade päistes ja jalustes võimalik kasutada\n" +"msg_header\">kirjade päistes ja jalustes võimalik kasutada\n" "spetsiaalseid muutujaid:\n" "\n" "

                    • user_address - liikme aadress, väikeste tähtedega.\n" @@ -6263,9 +6395,9 @@ msgid "" " page.\n" "
                    \n" msgstr "" -"Kui list kasutatab personaliseerimist, siis saab kirjade päistes ja jalustes kasutada " -"järgnevaid\n" +"Kui list kasutatab personaliseerimist, siis saab kirjade päistes ja jalustes " +"kasutada järgnevaid\n" "lisamuutujaid:\n" "\n" "
                    • user_address - liikme aadress väiketähtedega\n" @@ -6481,6 +6613,7 @@ msgstr "" " nende omad. " #: Mailman/Gui/Privacy.py:104 +#, fuzzy msgid "" "This section allows you to configure subscription and\n" " membership exposure policy. You can also control whether this\n" @@ -6489,8 +6622,8 @@ msgid "" " separate archive-related privacy settings." msgstr "" "Siin saab muuta listiga liitumise ja liikmete nimekirja seadistusi\n" -"Samuti on siit võimalik teha listi suletuks või avatuks. Vaata ka Arhiiviseadeid listi kirjade\n" +"Samuti on siit võimalik teha listi suletuks või avatuks. Vaata ka Arhiiviseadeid listi kirjade\n" "arhiivile ligipääsupiirangute kehtestamiseks." #: Mailman/Gui/Privacy.py:110 @@ -6661,8 +6794,8 @@ msgid "" " either individually or as a group. Any\n" " posting from a non-member who is not explicitly accepted,\n" " rejected, or discarded, will have their posting filtered by the\n" -" general\n" +" general\n" " non-member rules.\n" "\n" "

                      In the text boxes below, add one address per line; start the\n" @@ -6689,9 +6822,9 @@ msgstr "" "

                      Võõraste saadetud kirjad võidakse automaatselt listi lubada,\n" "modereerimiseks " -"kinni pidada, tühistada (tagastada) või kustutada\n" +"kinni pidada, tühistada (tagastada) või kustutada\n" "kas siis individuaaselt või grupina. Iga kirja võõralt aadressilt mida ei\n" "aksepteerita, tühistata või kustutata automaatselt kontrollitakse vastavalt " "võõrastele " @@ -6714,6 +6847,7 @@ msgid "By default, should new list member postings be moderated?" msgstr "Suunata uute liikmete kirjad modereerimisele?" #: Mailman/Gui/Privacy.py:218 +#, fuzzy msgid "" "Each list member has a moderation flag which says\n" " whether messages from the list member can be posted directly " @@ -6762,8 +6896,8 @@ msgid "" "If a member posts this many times, within a period of time\n" " the member is automatically moderated. Use 0 to disable. " "See\n" -" member_verbosity_interval for details on the time " "period.\n" "\n" @@ -6976,8 +7110,8 @@ msgid "" msgstr "" "Nendelt aadressidelt saadetud kirjad kustutakse automaatselt\n" "ilma saatjat teavitamata. Listi toimetajad võivad soovi korral\n" -"lasta endale saata koopiad nendest kirjadest.\n" +"lasta endale saata koopiad nendest kirjadest.\n" "\n" "

                      Kirjuta iga aadress eraldi reale; alusta rida ^ märgiga, kui tegemist\n" "on regulaaravaldisega." @@ -7147,8 +7281,8 @@ msgid "" msgstr "" "Nendelt aadressidelt saadetud kirjad kustutakse automaatselt\n" "ilma saatjat teavitamata. Listi toimetajad võivad soovi korral\n" -"lasta endale saata koopiad nendest kirjadest.\n" +"lasta endale saata koopiad nendest kirjadest.\n" "\n" "

                      Kirjuta iga aadress eraldi reale; alusta rida ^ märgiga, kui tegemist\n" "on regulaaravaldisega." @@ -7195,6 +7329,7 @@ msgid "" msgstr "Saata kustutatud kirjadest koopiad listi toimetajale?" #: Mailman/Gui/Privacy.py:494 +#, fuzzy msgid "" "Text to include in any rejection notice to be sent to\n" " non-members who post to this list. This notice can include\n" @@ -7408,6 +7543,7 @@ msgstr "" "Poolikuid reegleid eiratakse." #: Mailman/Gui/Privacy.py:693 +#, fuzzy msgid "" "The header filter rule pattern\n" " '%(safepattern)s' is not a legal regular expression. This\n" @@ -7458,8 +7594,8 @@ msgid "" "

                      The body of the message can also be optionally scanned for\n" " Subject: and Keywords: headers, as\n" " specified by the topics_bodylines_limit\n" +" href=\"?VARHELP=topics/" +"topics_bodylines_limit\">topics_bodylines_limit\n" " configuration variable." msgstr "" "Teemafilter kategoriseerib sissetulevaid kirju vastavalt See seadistus töötab ainult üksikkirjade ja mitte kokkuvõtetega.\n" "\n" "

                      Teema: ja Keywords: koode võidakse otsida\n" -"ka kirja sisust sõltuvalt topics_bodylines_limit seadistuse sisust." +"ka kirja sisust sõltuvalt topics_bodylines_limit seadistuse sisust." #: Mailman/Gui/Topics.py:72 msgid "How many body lines should the topic matcher scan?" @@ -7537,6 +7673,7 @@ msgstr "" "Kategooria koosneb nimest ja reeglist. Poolikuid kategooriaid eiratakse." #: Mailman/Gui/Topics.py:135 +#, fuzzy msgid "" "The topic pattern '%(safepattern)s' is not a\n" " legal regular expression. It will be discarded." @@ -7710,15 +7847,16 @@ msgid "" " the linked\n" " newsgroup fields are filled in." msgstr "" -"Lüüsi ei saa kasutada enne, kui serveri nimi ja uudisegrupi nimi väljad on täidetud." +"Lüüsi ei saa kasutada enne, kui serveri nimi ja uudisegrupi nimi väljad on täidetud." #: Mailman/HTMLFormatter.py:49 msgid "%(listinfo_link)s list run by %(owner_link)s" msgstr "Listi %(listinfo_link)s omanik on %(owner_link)s" #: Mailman/HTMLFormatter.py:57 +#, fuzzy msgid "%(realname)s administrative interface" msgstr "%(realname)s administreerimine" @@ -7727,6 +7865,7 @@ msgid " (requires authorization)" msgstr " (vajab autoriseerimist)" #: Mailman/HTMLFormatter.py:61 +#, fuzzy msgid "Overview of all %(hostname)s mailing lists" msgstr "Listid serveris %(hostname)s " @@ -7747,6 +7886,7 @@ msgid "; it was disabled by the list administrator" msgstr " listi omanik peatas tellimuse" #: Mailman/HTMLFormatter.py:146 +#, fuzzy msgid "" "; it was disabled due to excessive bounces. The\n" " last bounce was received on %(date)s" @@ -7757,6 +7897,7 @@ msgid "; it was disabled for unknown reasons" msgstr " tellimus peatati, täpsem põhjus teadmata." #: Mailman/HTMLFormatter.py:151 +#, fuzzy msgid "Note: your list delivery is currently disabled%(reason)s." msgstr "NB: tellimus on peatatud, põhjus: %(reason)s" @@ -7769,6 +7910,7 @@ msgid "the list administrator" msgstr "listi omaniku" #: Mailman/HTMLFormatter.py:157 +#, fuzzy msgid "" "

                      %(note)s\n" "\n" @@ -7786,6 +7928,7 @@ msgstr "" "poole. " #: Mailman/HTMLFormatter.py:169 +#, fuzzy msgid "" "

                      We have received some recent bounces from your\n" " address. Your current bounce score is %(score)s out of " @@ -7803,6 +7946,7 @@ msgstr "" "kirju enam ei tagastata." #: Mailman/HTMLFormatter.py:181 +#, fuzzy msgid "" "(Note - you are subscribing to a list of mailing lists, so the %(type)s " "notice will be sent to the admin address for your membership, %(addr)s.)

                      " @@ -7848,6 +7992,7 @@ msgstr "" "ja otsustamiseks. Moderaatori otsusest saate teada meili teel." #: Mailman/HTMLFormatter.py:208 +#, fuzzy msgid "" "This is %(also)sa private list, which means that the\n" " list of members is not available to non-members." @@ -7856,6 +8001,7 @@ msgstr "" "saavad liikmete nimekirja vaadata." #: Mailman/HTMLFormatter.py:211 +#, fuzzy msgid "" "This is %(also)sa hidden list, which means that the\n" " list of members is available only to the list administrator." @@ -7864,6 +8010,7 @@ msgstr "" " saab listi liikmete nimekirja vaadata." #: Mailman/HTMLFormatter.py:214 +#, fuzzy msgid "" "This is %(also)sa public list, which means that the\n" " list of members list is available to everyone." @@ -7880,6 +8027,7 @@ msgstr "" "neid võimalikult raske kätte saada)." #: Mailman/HTMLFormatter.py:222 +#, fuzzy msgid "" "

                      (Note that this is an umbrella list, intended to\n" " have only other mailing lists as members. Among other things,\n" @@ -7895,6 +8043,7 @@ msgid "either " msgstr "või " #: Mailman/HTMLFormatter.py:256 +#, fuzzy msgid "" "To unsubscribe from %(realname)s, get a password reminder,\n" " or change your subscription options %(either)senter your " @@ -7924,12 +8073,14 @@ msgid "" msgstr "Kui jätad selle välja tühjaks, siis küsitakse sinult su meiliaadressi" #: Mailman/HTMLFormatter.py:277 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " members.)" msgstr "(%(which)s on nähtav ainult listi liikmetele.)" #: Mailman/HTMLFormatter.py:281 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " administrator.)" @@ -7988,6 +8139,7 @@ msgid "The current archive" msgstr "Aktiivne arhiiv" #: Mailman/Handlers/Acknowledge.py:59 +#, fuzzy msgid "%(realname)s post acknowledgement" msgstr "%(realname)s postituse kinnitus" @@ -8000,6 +8152,7 @@ msgid "" msgstr "" #: Mailman/Handlers/CalcRecips.py:79 +#, fuzzy msgid "" "Your urgent message to the %(realname)s mailing list was not authorized for\n" "delivery. The original message as received by Mailman is attached.\n" @@ -8075,6 +8228,7 @@ msgid "Message may contain administrivia" msgstr "Kiri sisaldab administratiivkorraldusi." #: Mailman/Handlers/Hold.py:84 +#, fuzzy msgid "" "Please do *not* post administrative requests to the mailing\n" "list. If you wish to subscribe, visit %(listurl)s or send a message with " @@ -8115,10 +8269,12 @@ msgid "Posting to a moderated newsgroup" msgstr "Postitus modereeritud uudisegruppi" #: Mailman/Handlers/Hold.py:252 +#, fuzzy msgid "Your message to %(listname)s awaits moderator approval" msgstr "Kiri listi %(listname)s ootab toimetaja otsust." #: Mailman/Handlers/Hold.py:271 +#, fuzzy msgid "%(listname)s post from %(sender)s requires approval" msgstr "Kiri listi %(listname)s aadressilt %(sender)s vajab kinnitust." @@ -8159,6 +8315,7 @@ msgid "After content filtering, the message was empty" msgstr "Peale sisu filtreerimist ei jäänud kirjast mitte midagi järgi" #: Mailman/Handlers/MimeDel.py:269 +#, fuzzy msgid "" "The attached message matched the %(listname)s mailing list's content " "filtering\n" @@ -8199,6 +8356,7 @@ msgid "The attached message has been automatically discarded." msgstr "Lisatud kiri kustutati automaatselt." #: Mailman/Handlers/Replybot.py:75 +#, fuzzy msgid "Auto-response for your message to the \"%(realname)s\" mailing list" msgstr "Automaatne vastus kirjale listi \"%(realname)s\"" @@ -8223,6 +8381,7 @@ msgid "HTML attachment scrubbed and removed" msgstr "HTML manus eemaldati." #: Mailman/Handlers/Scrubber.py:234 Mailman/Handlers/Scrubber.py:259 +#, fuzzy msgid "" "An HTML attachment was scrubbed...\n" "URL: %(url)s\n" @@ -8277,6 +8436,7 @@ msgstr "" "Link : %(url)s\n" #: Mailman/Handlers/Scrubber.py:347 +#, fuzzy msgid "Skipped content of type %(partctype)s\n" msgstr "Kirja osa tüübiga %(partctype)s eirati\n" @@ -8306,6 +8466,7 @@ msgid "Message rejected by filter rule match" msgstr "Filter pidas kirja kinni" #: Mailman/Handlers/ToDigest.py:173 +#, fuzzy msgid "%(realname)s Digest, Vol %(volume)d, Issue %(issue)d" msgstr "%(realname)s kokkuvõte, köide %(volume)d, number %(issue)d" @@ -8342,6 +8503,7 @@ msgid "End of " msgstr "Lõpp" #: Mailman/ListAdmin.py:309 +#, fuzzy msgid "Posting of your message titled \"%(subject)s\"" msgstr "Kiri teemal \"%(subject)s\"" @@ -8354,6 +8516,7 @@ msgid "Forward of moderated message" msgstr "Modereeritud kirja edastus " #: Mailman/ListAdmin.py:405 +#, fuzzy msgid "New subscription request to list %(realname)s from %(addr)s" msgstr "Uus listi %(realname)s tellimuse soov aadressilt %(addr)s" @@ -8367,6 +8530,7 @@ msgid "via admin approval" msgstr "Jätka ootamist." #: Mailman/ListAdmin.py:466 +#, fuzzy msgid "New unsubscription request from %(realname)s by %(addr)s" msgstr "Uus listi %(realname)s tellimuse lõpetamise soov aadressilt %(addr)s" @@ -8379,10 +8543,12 @@ msgid "Original Message" msgstr "Esialgne kiri" #: Mailman/ListAdmin.py:526 +#, fuzzy msgid "Request to mailing list %(realname)s rejected" msgstr "Taotlus listile %(realname)s lükati tagasi" #: Mailman/MTA/Manual.py:66 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been created via the through-the-web\n" "interface. In order to complete the activation of this mailing list, the\n" @@ -8408,14 +8574,17 @@ msgstr "" "nimeline programmi:\n" #: Mailman/MTA/Manual.py:82 +#, fuzzy msgid "## %(listname)s mailing list" msgstr "## \"%(listname)s\" list." #: Mailman/MTA/Manual.py:99 +#, fuzzy msgid "Mailing list creation request for list %(listname)s" msgstr "Listi %(listname)s loomise taotlus" #: Mailman/MTA/Manual.py:113 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been removed via the through-the-web\n" "interface. In order to complete the de-activation of this mailing list, " @@ -8434,6 +8603,7 @@ msgstr "" "/etc/aliases failist tuleb eemaldada järgnevad read:\n" #: Mailman/MTA/Manual.py:123 +#, fuzzy msgid "" "\n" "To finish removing your mailing list, you must edit your /etc/aliases (or\n" @@ -8450,14 +8620,17 @@ msgstr "" "## %(listname)s list" #: Mailman/MTA/Manual.py:142 +#, fuzzy msgid "Mailing list removal request for list %(listname)s" msgstr "Listi %(listname)s kustutamise taotlus" #: Mailman/MTA/Postfix.py:442 +#, fuzzy msgid "checking permissions on %(file)s" msgstr "%(file)s ligipääsuõiguste kontroll" #: Mailman/MTA/Postfix.py:452 +#, fuzzy msgid "%(file)s permissions must be 0664 (got %(octmode)s)" msgstr "faili %(file)s ligipääsuõigused peavad olema 0664 (aga on %(octmode)s)" @@ -8471,35 +8644,43 @@ msgid "(fixing)" msgstr "(parandan)" #: Mailman/MTA/Postfix.py:470 +#, fuzzy msgid "checking ownership of %(dbfile)s" msgstr "kontrollin %(dbfile)s ligipääsuõigusi" #: Mailman/MTA/Postfix.py:478 +#, fuzzy msgid "%(dbfile)s owned by %(owner)s (must be owned by %(user)s" msgstr "Faili %(dbfile)s omanik on %(owner)s (aga peab olema %(user)s)" #: Mailman/MTA/Postfix.py:491 +#, fuzzy msgid "%(dbfile)s permissions must be 0664 (got %(octmode)s)" msgstr "" "faili %(dbfile)s ligipääsuõigused peavad olema 0664 (aga on %(octmode)s)" #: Mailman/MailList.py:219 +#, fuzzy msgid "Your confirmation is required to join the %(listname)s mailing list" msgstr "%(listname)s listi tellimiseks on tarvis sinu kinnitust" #: Mailman/MailList.py:230 +#, fuzzy msgid "Your confirmation is required to leave the %(listname)s mailing list" msgstr " %(listname)s listist lahkumiseks on tarvis sinu kinnitust" #: Mailman/MailList.py:999 Mailman/MailList.py:1492 +#, fuzzy msgid " from %(remote)s" msgstr " from %(remote)s" #: Mailman/MailList.py:1043 +#, fuzzy msgid "subscriptions to %(realname)s require moderator approval" msgstr "Listi %(realname)s tellimiseks on vaja toimetaja nõusolekut." #: Mailman/MailList.py:1125 bin/add_members:299 +#, fuzzy msgid "%(realname)s subscription notification" msgstr "%(realname)s tellimuse teavitus" @@ -8508,6 +8689,7 @@ msgid "unsubscriptions require moderator approval" msgstr "tellimuse lõpetamiseks on vaja omaniku nõusolekut." #: Mailman/MailList.py:1166 +#, fuzzy msgid "%(realname)s unsubscribe notification" msgstr "%(realname)s tellimuse lõpetamise teavitus" @@ -8527,6 +8709,7 @@ msgid "via web confirmation" msgstr "Vigane kinnituskood" #: Mailman/MailList.py:1396 +#, fuzzy msgid "subscriptions to %(name)s require administrator approval" msgstr "Listi %(name)s tellimiseks on vaja listi omaniku nõusolekut." @@ -8545,6 +8728,7 @@ msgid "Last autoresponse notification for today" msgstr "Viimane automaatvastus tänaseks" #: Mailman/Queue/BounceRunner.py:360 +#, fuzzy msgid "" "The attached message was received as a bounce, but either the bounce format\n" "was not recognized, or no member addresses could be extracted from it. " @@ -8633,6 +8817,7 @@ msgid "Original message suppressed by Mailman site configuration\n" msgstr "" #: Mailman/htmlformat.py:675 +#, fuzzy msgid "Delivered by Mailman
                      version %(version)s" msgstr "Kirja toimetas kätte Mailman
                      versioon %(version)s" @@ -8721,6 +8906,7 @@ msgid "Server Local Time" msgstr "Serveri kellaaeg" #: Mailman/i18n.py:180 +#, fuzzy msgid "" "%(wday)s %(mon)s %(day)2i %(hh)02i:%(mm)02i:%(ss)02i %(tzname)s %(year)04i" msgstr "" @@ -8837,6 +9023,7 @@ msgstr "" "olla `-'.\n" #: bin/add_members:162 bin/add_members:172 +#, fuzzy msgid "Already a member: %(member)s" msgstr "%(member)s on juba liige" @@ -8845,10 +9032,12 @@ msgid "Bad/Invalid email address: blank line" msgstr "Vigane meiliaadress: tühi rida" #: bin/add_members:180 +#, fuzzy msgid "Bad/Invalid email address: %(member)s" msgstr "Vigane meiliaadress: %(member)s" #: bin/add_members:182 +#, fuzzy msgid "Hostile address (illegal characters): %(member)s" msgstr "Aadress sisaldab keelatud märke: %(member)s" @@ -8858,14 +9047,17 @@ msgid "Invited: %(member)s" msgstr "Liidetud: %(member)s" #: bin/add_members:187 +#, fuzzy msgid "Subscribed: %(member)s" msgstr "Liidetud: %(member)s" #: bin/add_members:237 +#, fuzzy msgid "Bad argument to -w/--welcome-msg: %(arg)s" msgstr "Vigane -w/--welcome-msg argument: %(arg)s" #: bin/add_members:244 +#, fuzzy msgid "Bad argument to -a/--admin-notify: %(arg)s" msgstr "Vigane -a/--admin-notify argument: %(arg)s" @@ -8880,6 +9072,7 @@ msgstr "" #: bin/add_members:261 bin/config_list:110 bin/export.py:271 bin/find_member:97 #: bin/inject:91 bin/list_admins:90 bin/list_members:252 bin/sync_members:222 #: cron/bumpdigests:86 +#, fuzzy msgid "No such list: %(listname)s" msgstr "Selle nimega listi pole: %(listname)s" @@ -8890,6 +9083,7 @@ msgid "Nothing to do." msgstr "Ei oska midagi teha." #: bin/arch:19 +#, fuzzy msgid "" "Rebuild a list's archive.\n" "\n" @@ -8983,6 +9177,7 @@ msgid "listname is required" msgstr "listi nimi on puudu" #: bin/arch:143 bin/change_pw:107 bin/config_list:257 +#, fuzzy msgid "" "No such list \"%(listname)s\"\n" "%(e)s" @@ -8991,10 +9186,12 @@ msgstr "" "%(e)s" #: bin/arch:168 +#, fuzzy msgid "Cannot open mbox file %(mbox)s: %(msg)s" msgstr "Ei saanud avada mbox faili %(mbox)s: %(msg)s" #: bin/b4b5-archfix:19 +#, fuzzy msgid "" "Fix the MM2.1b4 archives.\n" "\n" @@ -9137,6 +9334,7 @@ msgstr "" " Väljastab selle teate ja lõpetab programmi töö.\n" #: bin/change_pw:145 +#, fuzzy msgid "Bad arguments: %(strargs)s" msgstr "Vigased argumendid: %(strargs)s" @@ -9145,14 +9343,17 @@ msgid "Empty list passwords are not allowed" msgstr "Tühjad paroolid pole lubatud." #: bin/change_pw:181 +#, fuzzy msgid "New %(listname)s password: %(notifypassword)s" msgstr "Uus %(listname)s parool: %(notifypassword)s" #: bin/change_pw:190 +#, fuzzy msgid "Your new %(listname)s list password" msgstr "Uus %(listname)s parool." #: bin/change_pw:191 +#, fuzzy msgid "" "The site administrator at %(hostname)s has changed the password for your\n" "mailing list %(listname)s. It is now\n" @@ -9178,6 +9379,7 @@ msgstr "" " %(adminurl)s\n" #: bin/check_db:19 +#, fuzzy msgid "" "Check a list's config database file for integrity.\n" "\n" @@ -9258,10 +9460,12 @@ msgid "List:" msgstr "List:" #: bin/check_db:148 +#, fuzzy msgid " %(file)s: okay" msgstr " %(file)s: OK" #: bin/check_perms:20 +#, fuzzy msgid "" "Check the permissions for the Mailman installation.\n" "\n" @@ -9283,44 +9487,54 @@ msgstr "" "\n" #: bin/check_perms:110 +#, fuzzy msgid " checking gid and mode for %(path)s" msgstr " kontrollin %(path)s ligipääsuõigusi ja grupikuuluvust" #: bin/check_perms:122 +#, fuzzy msgid "%(path)s bad group (has: %(groupname)s, expected %(MAILMAN_GROUP)s)" msgstr "" "%(path)s kuulub valele grupile (on %(groupname)s, peab olema " "%(MAILMAN_GROUP)s)" #: bin/check_perms:151 +#, fuzzy msgid "directory permissions must be %(octperms)s: %(path)s" msgstr "kataloogi ligipääsuõigused peavad olema: %(octperms)s: %(path)s" #: bin/check_perms:160 +#, fuzzy msgid "source perms must be %(octperms)s: %(path)s" msgstr "lähtefaili ligipääsuõigused peavad olema: %(octperms)s: %(path)s " #: bin/check_perms:171 +#, fuzzy msgid "article db files must be %(octperms)s: %(path)s" msgstr "artiklifaili ligipääsuõigused peavad olema: %(octperms)s: %(path)s" #: bin/check_perms:183 +#, fuzzy msgid "checking mode for %(prefix)s" msgstr " %(prefix)s ligipääsuõiguste kontroll" #: bin/check_perms:193 +#, fuzzy msgid "WARNING: directory does not exist: %(d)s" msgstr "NB: kataloogi %(d)s pole olemas" #: bin/check_perms:197 +#, fuzzy msgid "directory must be at least 02775: %(d)s" msgstr "kataloogi ligipääsuõigused peavad olema vähemalt 02775: %(d)s" #: bin/check_perms:209 +#, fuzzy msgid "checking perms on %(private)s" msgstr "%(private)s ligipääsuõiguste kontroll" #: bin/check_perms:214 +#, fuzzy msgid "%(private)s must not be other-readable" msgstr "%(private)s ei tohi olla kõigile loetav." @@ -9338,6 +9552,7 @@ msgid "mbox file must be at least 0660:" msgstr "mbox faili ligipääsuõigused peavad olema vähemalt 0660:" #: bin/check_perms:263 +#, fuzzy msgid "%(dbdir)s \"other\" perms must be 000" msgstr "%(dbdir)s \"other\" perms must be 000" @@ -9346,26 +9561,32 @@ msgid "checking cgi-bin permissions" msgstr "kontrollin cgi-bin ligipääsuõigusi" #: bin/check_perms:278 +#, fuzzy msgid " checking set-gid for %(path)s" msgstr " kontrollin %(path)s grupikuuluvust." #: bin/check_perms:282 +#, fuzzy msgid "%(path)s must be set-gid" msgstr "%(path)s peab olema set-gid" #: bin/check_perms:292 +#, fuzzy msgid "checking set-gid for %(wrapper)s" msgstr "kontrollin %(wrapper)s set-gid'i" #: bin/check_perms:296 +#, fuzzy msgid "%(wrapper)s must be set-gid" msgstr "%(wrapper)s peab olema set-gid" #: bin/check_perms:306 +#, fuzzy msgid "checking permissions on %(pwfile)s" msgstr "kontrollin %(pwfile)s ligipääsuõigused" #: bin/check_perms:315 +#, fuzzy msgid "%(pwfile)s permissions must be exactly 0640 (got %(octmode)s)" msgstr "%(pwfile)s loabitid peavad olema 0640 (aga on %(octmode)s)" @@ -9374,10 +9595,12 @@ msgid "checking permissions on list data" msgstr "kontrollin listi andmete loabitte" #: bin/check_perms:348 +#, fuzzy msgid " checking permissions on: %(path)s" msgstr " kontrollin loabitte: %(path)s" #: bin/check_perms:356 +#, fuzzy msgid "file permissions must be at least 660: %(path)s" msgstr "Faili loabitid peavad olema vähemalt 660: %(path)s" @@ -9433,6 +9656,7 @@ msgid "Unix-From line changed: %(lineno)d" msgstr "Unixi From rida muutus: %(lineno)d" #: bin/cleanarch:111 +#, fuzzy msgid "Bad status number: %(arg)s" msgstr "Vigane staatus: %(arg)s" @@ -9580,10 +9804,12 @@ msgid " original address removed:" msgstr " vana aadress eemaldati:" #: bin/clone_member:202 +#, fuzzy msgid "Not a valid email address: %(toaddr)s" msgstr "Vigane meiliaadress: %(toaddr)s" #: bin/clone_member:215 +#, fuzzy msgid "" "Error opening list \"%(listname)s\", skipping.\n" "%(e)s" @@ -9646,6 +9872,7 @@ msgid "" msgstr "" #: bin/config_list:118 +#, fuzzy msgid "" "# -*- python -*-\n" "# -*- coding: %(charset)s -*-\n" @@ -9666,22 +9893,27 @@ msgid "legal values are:" msgstr "lubatud väärtused on:" #: bin/config_list:270 +#, fuzzy msgid "attribute \"%(k)s\" ignored" msgstr "atribuuti \"%(k)s\" eirati" #: bin/config_list:273 +#, fuzzy msgid "attribute \"%(k)s\" changed" msgstr "atribuuti \"%(k)s\" muudeti" #: bin/config_list:279 +#, fuzzy msgid "Non-standard property restored: %(k)s" msgstr "Mittestandardne omadus on taastatud: %(k)s" #: bin/config_list:288 +#, fuzzy msgid "Invalid value for property: %(k)s" msgstr "Vigane väärtus: %(k)s" #: bin/config_list:291 +#, fuzzy msgid "Bad email address for option %(k)s: %(v)s" msgstr "Vigane meiliaadress %(k)s: %(v)s" @@ -9746,18 +9978,22 @@ msgstr "" " Üksikutest kustutamisoperatsioonidest ei teatata\n" #: bin/discard:94 +#, fuzzy msgid "Ignoring non-held message: %(f)s" msgstr "Mitte-peatatud kirja %(f)s eiratakse." #: bin/discard:100 +#, fuzzy msgid "Ignoring held msg w/bad id: %(f)s" msgstr "Peatatud vigase id'ga %(f)s kirja eiratakse" #: bin/discard:112 +#, fuzzy msgid "Discarded held msg #%(id)s for list %(listname)s" msgstr "Peatatud kiri msg #%(id)s kustutakse listist %(listname)s" #: bin/dumpdb:19 +#, fuzzy msgid "" "Dump the contents of any Mailman `database' file.\n" "\n" @@ -9834,6 +10070,7 @@ msgid "No filename given." msgstr "Faili nimi puudub." #: bin/dumpdb:108 +#, fuzzy msgid "Bad arguments: %(pargs)s" msgstr "Vigased argumendid: %(pargs)s" @@ -9852,6 +10089,7 @@ msgid "[----- end %(typename)s file -----]" msgstr "[----- arhiivifaili lõpp -----]" #: bin/dumpdb:142 +#, fuzzy msgid "<----- start object %(cnt)s ----->" msgstr "<----- objekt algab %(cnt)s ----->" @@ -10063,6 +10301,7 @@ msgid "Setting web_page_url to: %(web_page_url)s" msgstr "web_page_url uus väärtus: %(web_page_url)s" #: bin/fix_url.py:88 +#, fuzzy msgid "Setting host_name to: %(mailhost)s" msgstr "host_name uus väärtus: %(mailhost)s" @@ -10145,8 +10384,8 @@ msgstr "" " -q järjekord --queue=järjekord\n" "Järjekorra nimi millesse kiri lisada. Nimeks peab olema üks qfiles " "kataloogis\n" -"asuvatest alamkataloogidest. Kui nimi ära jätta siis kasutatakse \"incoming" -"\"\n" +"asuvatest alamkataloogidest. Kui nimi ära jätta siis kasutatakse " +"\"incoming\"\n" "järjekorda.\n" "\n" "Fail määrab ära vabatekstis kirja järjekorda paigutamiseks. Kui see ära " @@ -10154,6 +10393,7 @@ msgstr "" "siis kasutatakse standardsisendit.\n" #: bin/inject:84 +#, fuzzy msgid "Bad queue directory: %(qdir)s" msgstr "Vigane järjekorra kataloog: %(qdir)s" @@ -10162,6 +10402,7 @@ msgid "A list name is required" msgstr "Listi nimi on määramata" #: bin/list_admins:20 +#, fuzzy msgid "" "List all the owners of a mailing list.\n" "\n" @@ -10208,6 +10449,7 @@ msgstr "" "ka mitme listi nimed.\n" #: bin/list_admins:97 +#, fuzzy msgid "List: %(listname)s, \tOwners: %(owners)s" msgstr "List: %(listname)s, \tomanikud: %(owners)s" @@ -10384,10 +10626,12 @@ msgstr "" "pole võimalik vahet teha.\n" #: bin/list_members:198 +#, fuzzy msgid "Bad --nomail option: %(why)s" msgstr "Vigane --nomail argument: %(why)s" #: bin/list_members:209 +#, fuzzy msgid "Bad --digest option: %(kind)s" msgstr "Vigane --digest argument: %(kind)s" @@ -10401,6 +10645,7 @@ msgid "Could not open file for writing:" msgstr "Faili avamine kirjutamiseks ebaõnnestus:" #: bin/list_owners:20 +#, fuzzy msgid "" "List the owners of a mailing list, or all mailing lists.\n" "\n" @@ -10549,6 +10794,7 @@ msgid "" msgstr "" #: bin/mailmanctl:152 +#, fuzzy msgid "PID unreadable in: %(pidfile)s" msgstr "PID on loetamatu: %(pidfile)s" @@ -10557,6 +10803,7 @@ msgid "Is qrunner even running?" msgstr "Kas qrunner käib üldse?" #: bin/mailmanctl:160 +#, fuzzy msgid "No child with pid: %(pid)s" msgstr "Sellise PIDiga protsessi pole: %(pid)s" @@ -10584,6 +10831,7 @@ msgstr "" "võtmega.\n" #: bin/mailmanctl:233 +#, fuzzy msgid "" "The master qrunner lock could not be acquired, because it appears as if " "some\n" @@ -10609,10 +10857,12 @@ msgstr "" "Programm lõpetab." #: bin/mailmanctl:280 cron/mailpasswds:119 +#, fuzzy msgid "Site list is missing: %(sitelistname)s" msgstr "Saidi list puudub: %(sitelistname)s" #: bin/mailmanctl:305 +#, fuzzy msgid "Run this program as root or as the %(name)s user, or use -u." msgstr "" "Seda programmi peab käivitama root või %(name)s kasutaja alt. Võib ka " @@ -10623,6 +10873,7 @@ msgid "No command given." msgstr "Sa ei andnud ühtegi käsku." #: bin/mailmanctl:339 +#, fuzzy msgid "Bad command: %(command)s" msgstr "Vigane korraldus: %(command)s" @@ -10647,6 +10898,7 @@ msgid "Starting Mailman's master qrunner." msgstr "Mailmani qrunneri.käivitamine." #: bin/mmsitepass:19 +#, fuzzy msgid "" "Set the site password, prompting from the terminal.\n" "\n" @@ -10701,6 +10953,7 @@ msgid "list creator" msgstr "listi looja" #: bin/mmsitepass:86 +#, fuzzy msgid "New %(pwdesc)s password: " msgstr "Uus %(pwdesc)s parool: " @@ -10939,6 +11192,7 @@ msgstr "" "NB, listide nimed teisendatakse väiketähtedeks.\n" #: bin/newlist:162 +#, fuzzy msgid "Unknown language: %(lang)s" msgstr "Tundmatu keel: %(lang)s" @@ -10951,6 +11205,7 @@ msgid "Enter the email of the person running the list: " msgstr "Listi omaniku meiliaadress:" #: bin/newlist:193 +#, fuzzy msgid "Initial %(listname)s password: " msgstr "%(listname)s listi esialgne parool:" @@ -10960,11 +11215,12 @@ msgstr "Listil peab olema parool" #: bin/newlist:220 msgid "" -" - owner addresses need to be fully-qualified names like \"owner@example.com" -"\", not just \"owner\"." +" - owner addresses need to be fully-qualified names like \"owner@example." +"com\", not just \"owner\"." msgstr "" #: bin/newlist:244 +#, fuzzy msgid "Hit enter to notify %(listname)s owner..." msgstr "Vajuta Enterit %(listname)s omaniku teavitamiseks..." @@ -11094,6 +11350,7 @@ msgstr "" "üks neist nimedest mida -l väljastab.\n" #: bin/qrunner:179 +#, fuzzy msgid "%(name)s runs the %(runnername)s qrunner" msgstr "%(name)s käivitab the %(runnername)s järjekorra töötlemise" @@ -11106,6 +11363,7 @@ msgid "No runner name given." msgstr "järjekorra töötleja nimi puudub." #: bin/rb-archfix:21 +#, fuzzy msgid "" "Reduce disk space usage for Pipermail archives.\n" "\n" @@ -11258,18 +11516,22 @@ msgstr "" "\n" #: bin/remove_members:156 +#, fuzzy msgid "Could not open file for reading: %(filename)s." msgstr "Faili avamine lugemiseks ebaõnnestus: %(filename)s." #: bin/remove_members:163 +#, fuzzy msgid "Error opening list %(listname)s... skipping." msgstr "Viga listi %(listname)s töötlemisel .. jätan vahele." #: bin/remove_members:173 +#, fuzzy msgid "No such member: %(addr)s" msgstr "Sellise aadressiga liiget pole: %(addr)s" #: bin/remove_members:178 +#, fuzzy msgid "User `%(addr)s' removed from list: %(listname)s." msgstr "Aadress `%(addr)s' eemaldati listi %(listname)s liikmete nimekirjast." @@ -11305,10 +11567,12 @@ msgstr "" " Annab tagasisidet skripti poolt tehtavate muudatuste kohta.\n" #: bin/reset_pw.py:77 +#, fuzzy msgid "Changing passwords for list: %(listname)s" msgstr " %(listname)s listi paroolivahetus" #: bin/reset_pw.py:83 +#, fuzzy msgid "New password for member %(member)40s: %(randompw)s" msgstr "Liikme %(member)40s: uus parool on %(randompw)s" @@ -11355,18 +11619,22 @@ msgstr "" " Väljastab selle teksti ja lõpetab programmi töö.\n" #: bin/rmlist:73 bin/rmlist:76 +#, fuzzy msgid "Removing %(msg)s" msgstr "%(msg)s kustutamine" #: bin/rmlist:81 +#, fuzzy msgid "%(listname)s %(msg)s not found as %(filename)s" msgstr "%(listname)s %(msg)s ei leitud failist %(filename)s" #: bin/rmlist:105 +#, fuzzy msgid "No such list (or list already deleted): %(listname)s" msgstr "Selle nimega listi pole: %(listname)s" #: bin/rmlist:108 +#, fuzzy msgid "No such list: %(listname)s. Removing its residual archives." msgstr "Selle nimega listi pole: %(listname)s. Arhiivide kustutamine." @@ -11426,6 +11694,7 @@ msgstr "" "Näide: show_qfiles qfiles/shunt/*.pck\n" #: bin/sync_members:19 +#, fuzzy msgid "" "Synchronize a mailing list's membership with a flat file.\n" "\n" @@ -11562,6 +11831,7 @@ msgstr "" " Peab olema määratud ja täpsustab listi nime mida sünkroniseerida.\n" #: bin/sync_members:115 +#, fuzzy msgid "Bad choice: %(yesno)s" msgstr "Vigane valik: %(yesno)s" @@ -11578,6 +11848,7 @@ msgid "No argument to -f given" msgstr "-f on ilma argumendita" #: bin/sync_members:172 +#, fuzzy msgid "Illegal option: %(opt)s" msgstr "Vigane seadistus: %(opt)s" @@ -11590,6 +11861,7 @@ msgid "Must have a listname and a filename" msgstr "Listi ja faili nimi peavad määratud olema" #: bin/sync_members:191 +#, fuzzy msgid "Cannot read address file: %(filename)s: %(msg)s" msgstr "Aadressifaili %(filename)s lugemine ebaõnnestus: %(msg)s" @@ -11606,14 +11878,17 @@ msgid "You must fix the preceding invalid addresses first." msgstr "Paranda kõigepealt eelnevad vigased aadressid." #: bin/sync_members:264 +#, fuzzy msgid "Added : %(s)s" msgstr "Lisatud : %(s)s" #: bin/sync_members:289 +#, fuzzy msgid "Removed: %(s)s" msgstr "Eemaldatud: %(s)s" #: bin/transcheck:19 +#, fuzzy msgid "" "\n" "Check a given Mailman translation, making sure that variables and\n" @@ -11720,6 +11995,7 @@ msgstr "" "ja mitte qfiles/shunt kataloogi sisu.\n" #: bin/unshunt:85 +#, fuzzy msgid "" "Cannot unshunt message %(filebase)s, skipping:\n" "%(e)s" @@ -11728,6 +12004,7 @@ msgstr "" "%(e)s" #: bin/update:20 +#, fuzzy msgid "" "Perform all necessary upgrades.\n" "\n" @@ -11764,14 +12041,17 @@ msgstr "" "tunneb kõiki versioone alates numbrist 1.0b4 (?).\n" #: bin/update:107 +#, fuzzy msgid "Fixing language templates: %(listname)s" msgstr "%(listname)s keeletempleitide parandamine" #: bin/update:196 bin/update:711 +#, fuzzy msgid "WARNING: could not acquire lock for list: %(listname)s" msgstr "HOIATUS: listi %(listname)s lukustamine ebaõnnestus." #: bin/update:215 +#, fuzzy msgid "Resetting %(n)s BYBOUNCEs disabled addrs with no bounce info" msgstr "%(n)s tagastuste tõttu peatatud tellimust taastatakse" @@ -11788,6 +12068,7 @@ msgstr "" "b6-ga; jätkamiseks nimetatakse see ümber, uus nimi on %(mbox_dir)s.tmp." #: bin/update:255 +#, fuzzy msgid "" "\n" "%(listname)s has both public and private mbox archives. Since this list\n" @@ -11836,6 +12117,7 @@ msgid "- updating old private mbox file" msgstr " vana privaatse mbox faili uuendamine" #: bin/update:295 +#, fuzzy msgid "" " unknown file in the way, moving\n" " %(o_pri_mbox_file)s\n" @@ -11852,6 +12134,7 @@ msgid "- updating old public mbox file" msgstr " vana avaliku mbox faili uuendamine" #: bin/update:317 +#, fuzzy msgid "" " unknown file in the way, moving\n" " %(o_pub_mbox_file)s\n" @@ -11880,18 +12163,22 @@ msgid "- %(o_tmpl)s doesn't exist, leaving untouched" msgstr "-%(o_tmpl)s pole olemas, jääb puutumata" #: bin/update:396 +#, fuzzy msgid "removing directory %(src)s and everything underneath" msgstr "kataloogi %(src)s ja selle sisu kustutamine" #: bin/update:399 +#, fuzzy msgid "removing %(src)s" msgstr "%(src)s kustutamine" #: bin/update:403 +#, fuzzy msgid "Warning: couldn't remove %(src)s -- %(rest)s" msgstr "Hoiatus: %(src)s--%(rest)s kustutamine ebaõnnestus. " #: bin/update:408 +#, fuzzy msgid "couldn't remove old file %(pyc)s -- %(rest)s" msgstr "%(pyc)s--%(rest)s kustutamine ebaõnnestus. " @@ -11900,10 +12187,12 @@ msgid "updating old qfiles" msgstr "vanade qfile'de uuendamine" #: bin/update:455 +#, fuzzy msgid "Warning! Not a directory: %(dirpath)s" msgstr "Vigane järjekorra kataloog: %(dirpath)s" #: bin/update:530 +#, fuzzy msgid "message is unparsable: %(filebase)s" msgstr "kiri ei allu parserile: %(filebase)s" @@ -11920,10 +12209,12 @@ msgid "Updating Mailman 2.1.4 pending.pck database" msgstr "Uuendan Mailman 2.1.4 pending.pck andmebaasi" #: bin/update:598 +#, fuzzy msgid "Ignoring bad pended data: %(key)s: %(val)s" msgstr "Vigaseid andmeid eiratakse: %(key)s: %(val)s" #: bin/update:614 +#, fuzzy msgid "WARNING: Ignoring duplicate pending ID: %(id)s." msgstr "HOIATUS: duplikaat ID-sid %(id)s eiratakse." @@ -11949,6 +12240,7 @@ msgid "done" msgstr "tehtud" #: bin/update:691 +#, fuzzy msgid "Updating mailing list: %(listname)s" msgstr "Listi %(listname)s uuendamine." @@ -12009,6 +12301,7 @@ msgid "No updates are necessary." msgstr "Midagi pole vaja uuendada." #: bin/update:796 +#, fuzzy msgid "" "Downgrade detected, from version %(hexlversion)s to version %(hextversion)s\n" "This is probably not safe.\n" @@ -12019,11 +12312,13 @@ msgstr "" "Lõpetan." #: bin/update:801 +#, fuzzy msgid "Upgrading from version %(hexlversion)s to %(hextversion)s" msgstr "" "Versiooniuuendus: vana versioon %(hexlversion)s, uus versioon %(hextversion)s" #: bin/update:810 +#, fuzzy msgid "" "\n" "ERROR:\n" @@ -12224,6 +12519,7 @@ msgstr "" " " #: bin/withlist:175 +#, fuzzy msgid "Unlocking (but not saving) list: %(listname)s" msgstr "Listilt %(listname)s võeti lukk maha (aga midagi ei salvestatud)" @@ -12232,6 +12528,7 @@ msgid "Finalizing" msgstr "Lõpetan" #: bin/withlist:188 +#, fuzzy msgid "Loading list %(listname)s" msgstr "Listi %(listname)s laadimine" @@ -12244,6 +12541,7 @@ msgid "(unlocked)" msgstr "(lukustamata)" #: bin/withlist:197 +#, fuzzy msgid "Unknown list: %(listname)s" msgstr "Tundmatu list: %(listname)s" @@ -12256,18 +12554,22 @@ msgid "--all requires --run" msgstr "--all eeldab --run olemasolu" #: bin/withlist:266 +#, fuzzy msgid "Importing %(module)s..." msgstr "%(module)s import..." #: bin/withlist:270 +#, fuzzy msgid "Running %(module)s.%(callable)s()..." msgstr "%(module)s.%(callable)s() töötlemine..." #: bin/withlist:291 +#, fuzzy msgid "The variable `m' is the %(listname)s MailList instance" msgstr "Muutuja `m' on %(listname)s MailList instants" #: cron/bumpdigests:19 +#, fuzzy msgid "" "Increment the digest volume number and reset the digest number to one.\n" "\n" @@ -12296,6 +12598,7 @@ msgstr "" "Kui ühtegi listi ette ei antud, siis tehakse seda kõigi listidga.\n" #: cron/checkdbs:20 +#, fuzzy msgid "" "Check for pending admin requests and mail the list owners if necessary.\n" "\n" @@ -12325,10 +12628,12 @@ msgstr "" "\n" #: cron/checkdbs:121 +#, fuzzy msgid "%(count)d %(realname)s moderator request(s) waiting" msgstr "%(count)d %(realname)s modereerimisnõuet ootel." #: cron/checkdbs:124 +#, fuzzy msgid "%(realname)s moderator request check result" msgstr "%(realname)s modereerimisnõuete kontrolli tulemus" @@ -12350,6 +12655,7 @@ msgstr "" "Ootel postitused:" #: cron/checkdbs:169 +#, fuzzy msgid "" "From: %(sender)s on %(date)s\n" "Subject: %(subject)s\n" @@ -12381,6 +12687,7 @@ msgid "" msgstr "" #: cron/disabled:20 +#, fuzzy msgid "" "Process disabled members, recommended once per day.\n" "\n" @@ -12506,6 +12813,7 @@ msgstr "" "\n" #: cron/mailpasswds:19 +#, fuzzy msgid "" "Send password reminders for all lists to all users.\n" "\n" @@ -12560,10 +12868,12 @@ msgid "Password // URL" msgstr "Parool // URL" #: cron/mailpasswds:222 +#, fuzzy msgid "%(host)s mailing list memberships reminder" msgstr "%(host)s listide tellimuste meeldetuletus" #: cron/nightly_gzip:19 +#, fuzzy msgid "" "Re-generate the Pipermail gzip'd archive flat files.\n" "\n" @@ -12660,8 +12970,8 @@ msgstr "" #~ " action other than Accept, that action rather than this is\n" #~ " applied" #~ msgstr "" -#~ " Kui kirja listi saatmisest keeldutakse, siis " #~ "saadetakse kirja autorile järgnev tekst. " diff --git a/messages/eu/LC_MESSAGES/mailman.po b/messages/eu/LC_MESSAGES/mailman.po index 76b4946a..cf7f641a 100755 --- a/messages/eu/LC_MESSAGES/mailman.po +++ b/messages/eu/LC_MESSAGES/mailman.po @@ -68,10 +68,12 @@ msgid "

                      Currently, there are no archives.

                      " msgstr "

                      Une honetan ez dago fitxategirik

                      " #: Mailman/Archiver/HyperArch.py:821 +#, fuzzy msgid "Gzip'd Text%(sz)s" msgstr "Gzip-kin komprimitutako testu %(sz)s" #: Mailman/Archiver/HyperArch.py:826 +#, fuzzy msgid "Text%(sz)s" msgstr "%(sz)s testua" @@ -144,18 +146,22 @@ msgid "Third" msgstr "Hirugarren" #: Mailman/Archiver/HyperArch.py:938 +#, fuzzy msgid "%(ord)s quarter %(year)i" msgstr "%(year)i. urteko %(ord)s lauhilebetekoa" #: Mailman/Archiver/HyperArch.py:945 +#, fuzzy msgid "%(month)s %(year)i" msgstr "%(year)i-ko %(month)s" #: Mailman/Archiver/HyperArch.py:950 +#, fuzzy msgid "The Week Of Monday %(day)i %(month)s %(year)i" msgstr "%(year)i-ko %(month)s-ren %(day)i astearen astelhenean" #: Mailman/Archiver/HyperArch.py:954 +#, fuzzy msgid "%(day)i %(month)s %(year)i" msgstr "%(year)i-ko %(month)s-ren %(day)i" @@ -164,10 +170,12 @@ msgid "Computing threaded index\n" msgstr "Harien araberako aurkibidea kalkulatzen\n" #: Mailman/Archiver/HyperArch.py:1319 +#, fuzzy msgid "Updating HTML for article %(seq)s" msgstr " %(seq)s artikuluaren HTML kodea eguneratzen" #: Mailman/Archiver/HyperArch.py:1326 +#, fuzzy msgid "article file %(filename)s is missing!" msgstr "artikuluari lotutako %(filename)s fitxategia ezin da aurkitu!" @@ -184,6 +192,7 @@ msgid "Pickling archive state into " msgstr "Ftxagiaren egoera prestatzen " #: Mailman/Archiver/pipermail.py:453 +#, fuzzy msgid "Updating index files for archive [%(archive)s]" msgstr "[%(archive)s] artxiboko fitxategiko indizeak eguneratzen" @@ -192,6 +201,7 @@ msgid " Thread" msgstr " Haria" #: Mailman/Archiver/pipermail.py:597 +#, fuzzy msgid "#%(counter)05d %(msgid)s" msgstr "#%(counter)05d %(msgid)s" @@ -229,6 +239,7 @@ msgid "disabled address" msgstr "ezgaitua" #: Mailman/Bouncer.py:304 +#, fuzzy msgid " The last bounce received from you was dated %(date)s" msgstr " Zugandik jasotako azken errebotea %(date)s-datakoa zen." @@ -256,6 +267,7 @@ msgstr "Kudeatzailearen" #: Mailman/Cgi/options.py:101 Mailman/Cgi/private.py:108 #: Mailman/Cgi/rmlist.py:75 Mailman/Cgi/roster.py:59 #: Mailman/Cgi/subscribe.py:67 +#, fuzzy msgid "No such list %(safelistname)s" msgstr "%(safelistname)s zerrendarik ez dago" @@ -329,6 +341,7 @@ msgstr "" "mezurik jasoko.%(rm)r" #: Mailman/Cgi/admin.py:268 +#, fuzzy msgid "%(hostname)s mailing lists - Admin Links" msgstr "%(hostname)s Zerbitzariko Posta Zerrendak - Kudeaketarako Loturak" @@ -341,6 +354,7 @@ msgid "Mailman" msgstr "Mailman" #: Mailman/Cgi/admin.py:310 +#, fuzzy msgid "" "

                      There currently are no publicly-advertised %(mailmanlink)s\n" " mailing lists on %(hostname)s." @@ -349,6 +363,7 @@ msgstr "" " %(mailmanlink)s posta zerrendarik. " #: Mailman/Cgi/admin.py:316 +#, fuzzy msgid "" "

                      Below is the collection of publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s. Click on a list\n" @@ -364,6 +379,7 @@ msgid "right " msgstr "zuzena" #: Mailman/Cgi/admin.py:325 +#, fuzzy msgid "" "To visit the administrators configuration page for an\n" " unadvertised list, open a URL similar to this one, but with a '/' " @@ -408,6 +424,7 @@ msgid "No valid variable name found." msgstr "Ez da aldagai izen zuzenik aurkitu" #: Mailman/Cgi/admin.py:395 +#, fuzzy msgid "" "%(realname)s Mailing list Configuration Help\n" "
                      %(varname)s Option" @@ -416,6 +433,7 @@ msgstr "" "
                      %(varname)s Aukera" #: Mailman/Cgi/admin.py:402 +#, fuzzy msgid "Mailman %(varname)s List Option Help" msgstr "%(varname)s Aukerarako Mailmanen Laguntza" @@ -434,14 +452,17 @@ msgstr "" " " #: Mailman/Cgi/admin.py:431 +#, fuzzy msgid "return to the %(categoryname)s options page." msgstr "%(categoryname)s Aukera orrira itzuli" #: Mailman/Cgi/admin.py:446 +#, fuzzy msgid "%(realname)s Administration (%(label)s)" msgstr "%(realname)s (%(label)s) Kudeaketa" #: Mailman/Cgi/admin.py:447 +#, fuzzy msgid "%(realname)s mailing list administration
                      %(label)s Section" msgstr "%(realname)s Zerrendaren Kudeaketa
                      %(label)s Atala" @@ -523,6 +544,7 @@ msgid "Value" msgstr "Balioa" #: Mailman/Cgi/admin.py:668 +#, fuzzy msgid "" "Badly formed options entry:\n" " %(record)s" @@ -623,10 +645,12 @@ msgid "Move rule down" msgstr "Erregela jeitsi" #: Mailman/Cgi/admin.py:870 +#, fuzzy msgid "
                      (Edit %(varname)s)" msgstr "
                      (%(varname)s editatu)" #: Mailman/Cgi/admin.py:872 +#, fuzzy msgid "
                      (Details for %(varname)s)" msgstr "
                      (%(varname)s aldagaiaren xehetasunak)" @@ -666,6 +690,7 @@ msgid "(help)" msgstr "(laguntza)" #: Mailman/Cgi/admin.py:930 +#, fuzzy msgid "Find member %(link)s:" msgstr "Bilatu partaidearen %(link)s:" @@ -678,10 +703,12 @@ msgid "Bad regular expression: " msgstr "Espresio erregularr okerra: " #: Mailman/Cgi/admin.py:1013 +#, fuzzy msgid "%(allcnt)s members total, %(membercnt)s shown" msgstr "Guztira %(allcnt)s zerrendakide, %(membercnt)s bistan" #: Mailman/Cgi/admin.py:1016 +#, fuzzy msgid "%(allcnt)s members total" msgstr "Guztira %(allcnt)s zerrendakide" @@ -871,6 +898,7 @@ msgstr "" " zerrendan:" #: Mailman/Cgi/admin.py:1221 +#, fuzzy msgid "from %(start)s to %(end)s" msgstr "%(start)s(e)tik %(end)s(e)ra" @@ -1010,6 +1038,7 @@ msgid "Change list ownership passwords" msgstr "Zerrendako arduradunen pasahitzak aldatu" #: Mailman/Cgi/admin.py:1364 +#, fuzzy msgid "" "The list administrators are the people who have ultimate control " "over\n" @@ -1127,6 +1156,7 @@ msgstr "Gaizkieratutako helbidea (karakterrak okerrak)" #: Mailman/Cgi/admin.py:1529 Mailman/Cgi/admin.py:1703 bin/add_members:175 #: bin/clone_member:136 bin/sync_members:268 +#, fuzzy msgid "Banned address (matched %(pattern)s)" msgstr "Blokeatutako helbidea (matched %(pattern)s)" @@ -1183,6 +1213,7 @@ msgid "%(schange_to)s is already a member" msgstr "%(schange_to)s dagoeneko harpideduna da" #: Mailman/Cgi/admin.py:1620 +#, fuzzy msgid "%(schange_to)s matches banned pattern %(spat)s" msgstr "" "%(schange_to)s helbideak debekatutako eredu honekin bat egiten du: %(spat)s" @@ -1224,6 +1255,7 @@ msgid "Not subscribed" msgstr "Harpidetu gabea" #: Mailman/Cgi/admin.py:1773 +#, fuzzy msgid "Ignoring changes to deleted member: %(user)s" msgstr "Ezabatutako harpidearen aldaketei jaramonik ez: %(user)s" @@ -1236,10 +1268,12 @@ msgid "Error Unsubscribing:" msgstr "Zerrenda uztean errorea:" #: Mailman/Cgi/admindb.py:235 Mailman/Cgi/admindb.py:248 +#, fuzzy msgid "%(realname)s Administrative Database" msgstr "%(realname)s Kudeatzeko Datu-basea" #: Mailman/Cgi/admindb.py:238 +#, fuzzy msgid "%(realname)s Administrative Database Results" msgstr "%(realname)s Kudeatzeko Datu-basearen Emaitzak" @@ -1268,6 +1302,7 @@ msgid "Discard all messages marked Defer" msgstr "Atzeratu marka duten mezu guztiak baztertu" #: Mailman/Cgi/admindb.py:298 +#, fuzzy msgid "all of %(esender)s's held messages." msgstr "%(esender)s zerrendakidearen atxikitako mezu guztiak." @@ -1288,6 +1323,7 @@ msgid "list of available mailing lists." msgstr "Posta zerrenda eskuragarrien zerrenda" #: Mailman/Cgi/admindb.py:362 +#, fuzzy msgid "You must specify a list name. Here is the %(link)s" msgstr "" "Zerrenda izen bat aukeratu adierazi behar duzu. Hemen daude %(link)s-ak" @@ -1370,6 +1406,7 @@ msgid "The sender is now a member of this list" msgstr "Bidaltzailea orain zerrendako partaidea da" #: Mailman/Cgi/admindb.py:568 +#, fuzzy msgid "Add %(esender)s to one of these sender filters:" msgstr "%(esender)s gehitu hauetako hartzaile iragazki batetara:" @@ -1390,6 +1427,7 @@ msgid "Rejects" msgstr "Ez onartu" #: Mailman/Cgi/admindb.py:584 +#, fuzzy msgid "" "Ban %(esender)s from ever subscribing to this\n" " mailing list" @@ -1406,6 +1444,7 @@ msgstr "" " ikusteko, edo bestela, " #: Mailman/Cgi/admindb.py:591 +#, fuzzy msgid "view all messages from %(esender)s" msgstr "%(esender)s -ren mezu guztiak ikusi" @@ -1484,6 +1523,7 @@ msgid " is already a member" msgstr "dagoeneko harpidedun" #: Mailman/Cgi/admindb.py:973 +#, fuzzy msgid "%(addr)s is banned (matched: %(patt)s)" msgstr "%(addr)s blokeatutako helbidea da (%(patt)s -rekin bat egiten du)" @@ -1492,6 +1532,7 @@ msgid "Confirmation string was empty." msgstr "Berrespen katea hutsik zegoen." #: Mailman/Cgi/confirm.py:108 +#, fuzzy msgid "" "Invalid confirmation string:\n" " %(safecookie)s.\n" @@ -1535,6 +1576,7 @@ msgstr "" " ezeztatu egin da." #: Mailman/Cgi/confirm.py:178 +#, fuzzy msgid "System error, bad content: %(content)s" msgstr "Sistemaren errorea; eduki okerra: %(content)s" @@ -1572,6 +1614,7 @@ msgid "Confirm subscription request" msgstr "Harpidetza eskariaberretsi." #: Mailman/Cgi/confirm.py:259 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " subscription request to the mailing list %(listname)s. Your\n" @@ -1604,6 +1647,7 @@ msgstr "" " botoia." #: Mailman/Cgi/confirm.py:275 +#, fuzzy msgid "" "Your confirmation is required in order to continue with\n" " the subscription request to the mailing list %(listname)s.\n" @@ -1655,6 +1699,7 @@ msgid "Preferred language:" msgstr "Lehentsitako hizkuntza:" #: Mailman/Cgi/confirm.py:317 +#, fuzzy msgid "Subscribe to list %(listname)s" msgstr "%(listname)s zerrendara harpidetza egin." @@ -1671,6 +1716,7 @@ msgid "Awaiting moderator approval" msgstr "Moderatzailearen onespenaren zai." #: Mailman/Cgi/confirm.py:386 +#, fuzzy msgid "" " You have successfully confirmed your subscription request to " "the\n" @@ -1702,6 +1748,7 @@ msgid "You are already a member of this mailing list!" msgstr "Dagoeneko bazara zerrenda honetako harpidedun!" #: Mailman/Cgi/confirm.py:400 +#, fuzzy msgid "" "You are currently banned from subscribing to\n" " this list. If you think this restriction is erroneous, please\n" @@ -1726,6 +1773,7 @@ msgid "Subscription request confirmed" msgstr "Harpidetza-eskaera baieztatuta" #: Mailman/Cgi/confirm.py:418 +#, fuzzy msgid "" " You have successfully confirmed your subscription request for\n" " \"%(addr)s\" to the %(listname)s mailing list. A separate\n" @@ -1754,6 +1802,7 @@ msgid "Unsubscription request confirmed" msgstr "Zerrenda uzteko eskaera baieztatu egin duzu" #: Mailman/Cgi/confirm.py:469 +#, fuzzy msgid "" " You have successfully unsubscribed from the %(listname)s " "mailing\n" @@ -1775,6 +1824,7 @@ msgid "Not available" msgstr "Ez dago eskuragarri" #: Mailman/Cgi/confirm.py:498 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " unsubscription request from the mailing list %(listname)s. " @@ -1819,6 +1869,7 @@ msgid "You have canceled your change of address request." msgstr "Helbidea aldatzeko eskaera ezereztatu egin duzu." #: Mailman/Cgi/confirm.py:555 +#, fuzzy msgid "" "%(newaddr)s is banned from subscribing to the\n" " %(realname)s list. If you think this restriction is erroneous,\n" @@ -1829,6 +1880,7 @@ msgstr "" " uste baduzu, idatzi zerrenda jabeari %(owneraddr)s helbidera." #: Mailman/Cgi/confirm.py:560 +#, fuzzy msgid "" "%(newaddr)s is already a member of\n" " the %(realname)s list. It is possible that you are attempting\n" @@ -1844,6 +1896,7 @@ msgid "Change of address request confirmed" msgstr "Helbidea aldatzeko eskaera baieztaturik" #: Mailman/Cgi/confirm.py:571 +#, fuzzy msgid "" " You have successfully changed your address on the %(listname)s\n" " mailing list from %(oldaddr)s to %(newaddr)s. " @@ -1866,6 +1919,7 @@ msgid "globally" msgstr "orokorrean" #: Mailman/Cgi/confirm.py:605 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " change of address request for the mailing list %(listname)s. " @@ -1927,6 +1981,7 @@ msgid "Sender discarded message via web." msgstr "Bidaltzaileak web gunearen bidez mezua ezereztatu egin du." #: Mailman/Cgi/confirm.py:674 +#, fuzzy msgid "" "The held message with the Subject:\n" " header %(subject)s could not be found. The most " @@ -1946,6 +2001,7 @@ msgid "Posted message canceled" msgstr "Mezua ezereztatu egin da" #: Mailman/Cgi/confirm.py:685 +#, fuzzy msgid "" " You have successfully canceled the posting of your message with\n" " the Subject: header %(subject)s to the mailing list\n" @@ -1968,6 +2024,7 @@ msgstr "" " esku dago." #: Mailman/Cgi/confirm.py:735 +#, fuzzy msgid "" "Your confirmation is required in order to cancel the\n" " posting of your message to the mailing list %(listname)s:\n" @@ -2017,6 +2074,7 @@ msgid "Membership re-enabled." msgstr "Harpidetza gaitu egin dugu berriro." #: Mailman/Cgi/confirm.py:798 +#, fuzzy msgid "" " You have successfully re-enabled your membership in the\n" " %(listname)s mailing list. You can now not available" msgstr "ez dago eskuragarri" #: Mailman/Cgi/confirm.py:846 +#, fuzzy msgid "" "Your membership in the %(realname)s mailing list is\n" " currently disabled due to excessive bounces. Your confirmation is\n" @@ -2114,10 +2174,12 @@ msgid "administrative list overview" msgstr "kudeaketarako zerrenda orokorra" #: Mailman/Cgi/create.py:115 +#, fuzzy msgid "List name must not include \"@\": %(safelistname)s" msgstr "Zerrenda izenak ezin du \"@\" ikurrik izan: %(safelistname)s " #: Mailman/Cgi/create.py:122 +#, fuzzy msgid "List already exists: %(safelistname)s" msgstr "Zerrenda hori dagoeneko badago: %(safelistname)s" @@ -2152,18 +2214,22 @@ msgid "You are not authorized to create new mailing lists" msgstr "Ez duzu zerrenda berriak sortzeko baimenik" #: Mailman/Cgi/create.py:182 +#, fuzzy msgid "Unknown virtual host: %(safehostname)s" msgstr "Zerbitzari birtual ezezaguna: %(safehostname)s" #: Mailman/Cgi/create.py:218 bin/newlist:219 +#, fuzzy msgid "Bad owner email address: %(s)s" msgstr "Jabearen ePosta helbidea okerra da: %(s)s" #: Mailman/Cgi/create.py:223 bin/newlist:182 bin/newlist:223 +#, fuzzy msgid "List already exists: %(listname)s" msgstr "Zerrenda hori dagoeneko badago: %(listname)s" #: Mailman/Cgi/create.py:231 bin/newlist:217 +#, fuzzy msgid "Illegal list name: %(s)s" msgstr "Zerrenda izen okerra: %(s)s" @@ -2176,6 +2242,7 @@ msgstr "" " Mesedez, jarri harremanetan gunearen kudeatzailearekin." #: Mailman/Cgi/create.py:273 bin/newlist:265 +#, fuzzy msgid "Your new mailing list: %(listname)s" msgstr "Zure posta zerrenda berria: %(listname)s" @@ -2184,6 +2251,7 @@ msgid "Mailing list creation results" msgstr "Posta zerrendaren sormenaren emaitza" #: Mailman/Cgi/create.py:288 +#, fuzzy msgid "" "You have successfully created the mailing list\n" " %(listname)s and notification has been sent to the list owner\n" @@ -2206,6 +2274,7 @@ msgid "Create another list" msgstr "Beste zerrenda bat sortu" #: Mailman/Cgi/create.py:312 +#, fuzzy msgid "Create a %(hostname)s Mailing List" msgstr "%(hostname)s Zerbitzarian Posta Zerrenda Bat Sortu" @@ -2304,6 +2373,7 @@ msgstr "" " berrien mezuak moderatzaileak gainbegiratzea nahi baduzu." #: Mailman/Cgi/create.py:432 +#, fuzzy msgid "" "Initial list of supported languages.

                      Note that if you do not\n" " select at least one initial language, the list will use the server\n" @@ -2402,6 +2472,7 @@ msgid "List name is required." msgstr "Zerrenda izena beharrezkoa da" #: Mailman/Cgi/edithtml.py:154 +#, fuzzy msgid "%(realname)s -- Edit html for %(template_info)s" msgstr "%(realname)s -- %(template_info)s-ren html-a editatu" @@ -2410,10 +2481,12 @@ msgid "Edit HTML : Error" msgstr "HTML editatu : Errorea" #: Mailman/Cgi/edithtml.py:161 +#, fuzzy msgid "%(safetemplatename)s: Invalid template" msgstr "%(safetemplatename)s txantiloi okerra" #: Mailman/Cgi/edithtml.py:166 Mailman/Cgi/edithtml.py:167 +#, fuzzy msgid "%(realname)s -- HTML Page Editing" msgstr "%(realname)s -- HTML Orrien Edizioa" @@ -2477,10 +2550,12 @@ msgid "HTML successfully updated." msgstr "HTML kodea behar bezala aldatu da." #: Mailman/Cgi/listinfo.py:89 +#, fuzzy msgid "%(hostname)s Mailing Lists" msgstr "%(hostname)s >erbitzariko ePosta zerrendak" #: Mailman/Cgi/listinfo.py:127 +#, fuzzy msgid "" "

                      There currently are no publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s." @@ -2489,6 +2564,7 @@ msgstr "" " %(hostname)s zerbitzarian." #: Mailman/Cgi/listinfo.py:131 +#, fuzzy msgid "" "

                      Below is a listing of all the public mailing lists on\n" " %(hostname)s. Click on a list name to get more information " @@ -2508,6 +2584,7 @@ msgid "right" msgstr "zuzena" #: Mailman/Cgi/listinfo.py:140 +#, fuzzy msgid "" " To visit the general information page for an unadvertised list,\n" " open a URL similar to this one, but with a '/' and the %(adj)s\n" @@ -2557,6 +2634,7 @@ msgid "CGI script error" msgstr "CGI scriptaren errorea" #: Mailman/Cgi/options.py:71 +#, fuzzy msgid "Invalid request method: %(method)s" msgstr "Eskaera metodo okerra: %(method)s" @@ -2570,6 +2648,7 @@ msgstr "ePosta helbide okerra" #: Mailman/Cgi/options.py:183 Mailman/Cgi/options.py:240 #: Mailman/Cgi/options.py:264 +#, fuzzy msgid "No such member: %(safeuser)s." msgstr "Harpidedun ezezaguna: %(safeuser)s." @@ -2621,6 +2700,7 @@ msgid "Note: " msgstr "Oharra: " #: Mailman/Cgi/options.py:378 +#, fuzzy msgid "List subscriptions for %(safeuser)s on %(hostname)s" msgstr "" "%(hostname)s zerbitzariko posta zerrendetako %(safeuser)s-ren hapidetza " @@ -2653,6 +2733,7 @@ msgid "You are already using that email address" msgstr "Dagoeneko ePosta helbide hori darabizu" #: Mailman/Cgi/options.py:459 +#, fuzzy msgid "" "The new address you requested %(newaddr)s is already a member of the\n" "%(listname)s mailing list, however you have also requested a global change " @@ -2666,6 +2747,7 @@ msgstr "" "zerrenda guztiak ere aldatu egingo dira." #: Mailman/Cgi/options.py:468 +#, fuzzy msgid "The new address is already a member: %(newaddr)s" msgstr "Helbide berria zerrendakide da dagoeneko: %(newaddr)s" @@ -2674,6 +2756,7 @@ msgid "Addresses may not be blank" msgstr "Helbideak ezin dira hutsik egon" #: Mailman/Cgi/options.py:488 +#, fuzzy msgid "A confirmation message has been sent to %(newaddr)s. " msgstr "Egiaztapen mezu bat bidali da %(newaddr)s helbidera. " @@ -2686,10 +2769,12 @@ msgid "Illegal email address provided" msgstr "Emandako ePosta helbidea okerra da." #: Mailman/Cgi/options.py:501 +#, fuzzy msgid "%(newaddr)s is already a member of the list." msgstr "%(newaddr)s dagoeneko zerrendako partaide da." #: Mailman/Cgi/options.py:504 +#, fuzzy msgid "" "%(newaddr)s is banned from this list. If you\n" " think this restriction is erroneous, please contact\n" @@ -2767,6 +2852,7 @@ msgstr "" " zein den." #: Mailman/Cgi/options.py:623 +#, fuzzy msgid "" "You have been successfully unsubscribed from the\n" " mailing list %(fqdn_listname)s. If you were receiving digest\n" @@ -2860,6 +2946,7 @@ msgid "day" msgstr "egun" #: Mailman/Cgi/options.py:900 +#, fuzzy msgid "%(days)d %(units)s" msgstr "%(days)d %(units)s" @@ -2872,6 +2959,7 @@ msgid "No topics defined" msgstr "Gaiak ez dira zehaztu" #: Mailman/Cgi/options.py:940 +#, fuzzy msgid "" "\n" "You are subscribed to this list with the case-preserved address\n" @@ -2882,6 +2970,7 @@ msgstr "" "eta txikiak errespetatuta) %(cpuser)s" #: Mailman/Cgi/options.py:956 +#, fuzzy msgid "%(realname)s list: member options login page" msgstr "%(realname)s zerrenda: erabiltzailearen aukeren sarrera orria" @@ -2890,10 +2979,12 @@ msgid "email address and " msgstr "ePosta helbidea eta " #: Mailman/Cgi/options.py:960 +#, fuzzy msgid "%(realname)s list: member options for user %(safeuser)s" msgstr "%(realname)s zerrenda: %(safeuser)s erabiltzailearen aukerak" #: Mailman/Cgi/options.py:986 +#, fuzzy msgid "" "In order to change your membership option, you must\n" " first log in by giving your %(extra)smembership password in the section\n" @@ -2972,6 +3063,7 @@ msgid "" msgstr "" #: Mailman/Cgi/options.py:1140 +#, fuzzy msgid "Requested topic is not valid: %(topicname)s" msgstr "Eskatuta gaia ez da zuzena: %(topicname)s" @@ -3000,6 +3092,7 @@ msgid "Private archive - \"./\" and \"../\" not allowed in URL." msgstr "Artxibo pribatuak (\"./\" eta \"../\") ez dira onartzen URLan." #: Mailman/Cgi/private.py:109 +#, fuzzy msgid "Private Archive Error - %(msg)s" msgstr "Errorea Artxibo Pribatuan - %(msg)s" @@ -3020,6 +3113,7 @@ msgid "Private archive file not found" msgstr "Artxibo Pribatua ez da aurkitu" #: Mailman/Cgi/rmlist.py:76 +#, fuzzy msgid "No such list %(safelistname)s" msgstr "Zerrenda hau ez dago: %(safelistname)s" @@ -3036,6 +3130,7 @@ msgid "Mailing list deletion results" msgstr "Eposta zerrenda ezabatze emaitzak" #: Mailman/Cgi/rmlist.py:187 +#, fuzzy msgid "" "You have successfully deleted the mailing list\n" " %(listname)s." @@ -3044,6 +3139,7 @@ msgstr "" " ezabatu duzu." #: Mailman/Cgi/rmlist.py:191 +#, fuzzy msgid "" "There were some problems deleting the mailing list\n" " %(listname)s. Contact your site administrator at " @@ -3055,10 +3151,12 @@ msgstr "" " honetara: %(sitelist)s." #: Mailman/Cgi/rmlist.py:208 +#, fuzzy msgid "Permanently remove mailing list %(realname)s" msgstr "%(realname)s Eposta zerrenda betirako ezabatu" #: Mailman/Cgi/rmlist.py:209 +#, fuzzy msgid "Permanently remove mailing list %(realname)s" msgstr "%(realname)s posta zerrenda betirako ezabatu" @@ -3125,6 +3223,7 @@ msgid "Invalid options to CGI script" msgstr "CGI skripterako aukera okerra" #: Mailman/Cgi/roster.py:118 +#, fuzzy msgid "%(realname)s roster authentication failed." msgstr "%(realname)s zerrendaren egiaztapenak huts egin du." @@ -3196,6 +3295,7 @@ msgstr "" "." #: Mailman/Cgi/subscribe.py:262 +#, fuzzy msgid "" "The email address you supplied is banned from this\n" " mailing list. If you think this restriction is erroneous, please\n" @@ -3222,6 +3322,7 @@ msgstr "" "delako ziurra." #: Mailman/Cgi/subscribe.py:278 +#, fuzzy msgid "" "Confirmation from your email address is required, to prevent anyone from\n" "subscribing you without permission. Instructions are being sent to you at\n" @@ -3235,6 +3336,7 @@ msgstr "" "berretsi arte." #: Mailman/Cgi/subscribe.py:290 +#, fuzzy msgid "" "Your subscription request was deferred because %(x)s. Your request has " "been\n" @@ -3256,6 +3358,7 @@ msgid "Mailman privacy alert" msgstr "Mailmanen Pribazitate Abisua" #: Mailman/Cgi/subscribe.py:316 +#, fuzzy msgid "" "An attempt was made to subscribe your address to the mailing list\n" "%(listaddr)s. You are already subscribed to this mailing list.\n" @@ -3301,6 +3404,7 @@ msgid "This list only supports digest delivery." msgstr "Zerrendak mezuak bildumetan jasotzeko aukera bakarrik ematen du." #: Mailman/Cgi/subscribe.py:344 +#, fuzzy msgid "You have been successfully subscribed to the %(realname)s mailing list." msgstr "%(realname)s posta zerrendako harpidedun egin zara." @@ -3324,6 +3428,7 @@ msgid "Usage:" msgstr "Erabilera:" #: Mailman/Commands/cmd_confirm.py:50 +#, fuzzy msgid "" "Invalid confirmation string. Note that confirmation strings expire\n" "approximately %(days)s days after the initial request. They also expire if\n" @@ -3350,6 +3455,7 @@ msgstr "" "edo helbidea aldatu?" #: Mailman/Commands/cmd_confirm.py:69 +#, fuzzy msgid "" "You are currently banned from subscribing to this list. If you think this\n" "restriction is erroneous, please contact the list owners at\n" @@ -3433,26 +3539,32 @@ msgid "n/a" msgstr "ez dagokio" #: Mailman/Commands/cmd_info.py:44 +#, fuzzy msgid "List name: %(listname)s" msgstr "Zerrendaren izena: %(listname)s" #: Mailman/Commands/cmd_info.py:45 +#, fuzzy msgid "Description: %(description)s" msgstr "Azalpena: %(description)s" #: Mailman/Commands/cmd_info.py:46 +#, fuzzy msgid "Postings to: %(postaddr)s" msgstr "Mezuak hona: %(postaddr)s" #: Mailman/Commands/cmd_info.py:47 +#, fuzzy msgid "List Helpbot: %(requestaddr)s" msgstr "Zerrendaren laguntza robota: %(requestaddr)s" #: Mailman/Commands/cmd_info.py:48 +#, fuzzy msgid "List Owners: %(owneraddr)s" msgstr "Zerrenda-jabeak: %(owneraddr)s" #: Mailman/Commands/cmd_info.py:49 +#, fuzzy msgid "More information: %(listurl)s" msgstr "Argibide gehiago: %(listurl)s" @@ -3476,18 +3588,22 @@ msgstr "" "ikusi.\n" #: Mailman/Commands/cmd_lists.py:44 +#, fuzzy msgid "Public mailing lists at %(hostname)s:" msgstr "%(hostname)s guneko posta zerrenda irekiak hauexek dira:" #: Mailman/Commands/cmd_lists.py:66 +#, fuzzy msgid "%(i)3d. List name: %(realname)s" msgstr "%(i)3d. Zerremda izena: %(realname)s" #: Mailman/Commands/cmd_lists.py:67 +#, fuzzy msgid " Description: %(description)s" msgstr " Azalpena: %(description)s" #: Mailman/Commands/cmd_lists.py:68 +#, fuzzy msgid " Requests to: %(requestaddr)s" msgstr " Eskaerak hona egin: %(requestaddr)s" @@ -3525,12 +3641,14 @@ msgstr "" "\n" #: Mailman/Commands/cmd_password.py:51 Mailman/Commands/cmd_password.py:66 +#, fuzzy msgid "Your password is: %(password)s" msgstr "Zure pasahitza hau da: %(password)s" #: Mailman/Commands/cmd_password.py:57 Mailman/Commands/cmd_password.py:72 #: Mailman/Commands/cmd_password.py:95 Mailman/Commands/cmd_password.py:121 #: Mailman/Commands/cmd_set.py:149 Mailman/Commands/cmd_set.py:219 +#, fuzzy msgid "You are not a member of the %(listname)s mailing list" msgstr "Zu ez zara %(listname)s ePosta zerrendako harpidedun" @@ -3728,6 +3846,7 @@ msgstr "" " desgaitu nahi baduzu zerrenda honetarako.\n" #: Mailman/Commands/cmd_set.py:122 +#, fuzzy msgid "Bad set command: %(subcmd)s" msgstr "Set agindu okerra: %(subcmd)s" @@ -3748,6 +3867,7 @@ msgid "on" msgstr "aktibatu" #: Mailman/Commands/cmd_set.py:154 +#, fuzzy msgid " ack %(onoff)s" msgstr " ack %(onoff)s" @@ -3785,22 +3905,27 @@ msgid "due to bounces" msgstr "erreboteak direla eta" #: Mailman/Commands/cmd_set.py:186 +#, fuzzy msgid " %(status)s (%(how)s on %(date)s)" msgstr " %(status)s (%(how)s %(date)s-(e)an)" #: Mailman/Commands/cmd_set.py:192 +#, fuzzy msgid " myposts %(onoff)s" msgstr " nire mezuak %(onoff)s" #: Mailman/Commands/cmd_set.py:195 +#, fuzzy msgid " hide %(onoff)s" msgstr " ezkutuan %(onoff)s" #: Mailman/Commands/cmd_set.py:199 +#, fuzzy msgid " duplicates %(onoff)s" msgstr " duplikatuak %(onoff)s" #: Mailman/Commands/cmd_set.py:203 +#, fuzzy msgid " reminders %(onoff)s" msgstr " hileroko gogorarazpenak %(onoff)s" @@ -3809,6 +3934,7 @@ msgid "You did not give the correct password" msgstr "Ez duzu pasahitz zuzena idatzi" #: Mailman/Commands/cmd_set.py:236 Mailman/Commands/cmd_set.py:283 +#, fuzzy msgid "Bad argument: %(arg)s" msgstr "Argumentu okerra: %(arg)s" @@ -3887,6 +4013,7 @@ msgstr "" " markarik gabe).\n" #: Mailman/Commands/cmd_subscribe.py:62 +#, fuzzy msgid "Bad digest specifier: %(arg)s" msgstr "Bildumak gaizki zehaztuta: %(arg)s" @@ -3895,6 +4022,7 @@ msgid "No valid address found to subscribe" msgstr "Ez da helbide egokirik aurkitu harpidetzeko" #: Mailman/Commands/cmd_subscribe.py:113 +#, fuzzy msgid "" "The email address you supplied is banned from this mailing list.\n" "If you think this restriction is erroneous, please contact the list\n" @@ -3934,6 +4062,7 @@ msgid "This list only supports digest subscriptions!" msgstr "Zerrenda honek bilduma harpidetzak bakarrik onartzen ditu!" #: Mailman/Commands/cmd_subscribe.py:146 +#, fuzzy msgid "" "Your subscription request has been forwarded to the list administrator\n" "at %(listowner)s for review." @@ -3971,6 +4100,7 @@ msgstr "" " \n" #: Mailman/Commands/cmd_unsubscribe.py:62 +#, fuzzy msgid "%(address)s is not a member of the %(listname)s mailing list" msgstr "%(address)s ez da %(listname)s ePosta zerrendako partaide" @@ -4224,6 +4354,7 @@ msgid "Chinese (Taiwan)" msgstr "Txinera (Taiwan)" #: Mailman/Deliverer.py:53 +#, fuzzy msgid "" "Note: Since this is a list of mailing lists, administrative\n" "notices like the password reminder will be sent to\n" @@ -4238,14 +4369,17 @@ msgid " (Digest mode)" msgstr " (Mezu-Bilduma)" #: Mailman/Deliverer.py:79 +#, fuzzy msgid "Welcome to the \"%(realname)s\" mailing list%(digmode)s" msgstr "Ongi etorri \"%(realname)s\" %(digmode)s posta zerrendara" #: Mailman/Deliverer.py:89 +#, fuzzy msgid "You have been unsubscribed from the %(realname)s mailing list" msgstr "Ondoko zerrenda hau utzi egin duzu: %(realname)s " #: Mailman/Deliverer.py:116 +#, fuzzy msgid "%(listfullname)s mailing list reminder" msgstr "%(listfullname)s posta zerrendaren gogorarazpena" @@ -4258,6 +4392,7 @@ msgid "Hostile subscription attempt detected" msgstr "Harpidetza gaizto baten saiakera igerri da" #: Mailman/Deliverer.py:169 +#, fuzzy msgid "" "%(address)s was invited to a different mailing\n" "list, but in a deliberate malicious attempt they tried to confirm the\n" @@ -4270,6 +4405,7 @@ msgstr "" "dugu. Ez da zure aldetik akziorik espero." #: Mailman/Deliverer.py:188 +#, fuzzy msgid "" "You invited %(address)s to your list, but in a\n" "deliberate malicious attempt, they tried to confirm the invitation to a\n" @@ -4284,6 +4420,7 @@ msgstr "" "dugu. Ez da zure aldetik akziorik espero." #: Mailman/Deliverer.py:221 +#, fuzzy msgid "%(listname)s mailing list probe message" msgstr "%(listname)s posta zerrendaren gogorarazpena" @@ -4491,8 +4628,8 @@ msgid "" " membership.\n" "\n" "

                      You can control both the\n" -" number\n" +" number\n" " of reminders the member will receive and the\n" " Aldagai bi hauek kontrola ditzakezu:\n" " harpidedunak jasoko dituen\n" -" jakinarazpen mezuen\n" +" jakinarazpen mezuen\n" " kopurua\n" " eta mezuak bidaltzeko\n" " No those messages too will get discarded. You may " "want\n" " to set up an\n" -" autoresponse\n" +" autoresponse\n" " message for email to the -owner and -admin address." msgstr "" "Mailmanek erreboteak ezagutzeko duen sistema oso sendoa den arren\n" @@ -4739,8 +4876,8 @@ msgstr "" " Ez aukeratu bada, mezu horiek ezabatu egingo dira. " "Nahi baduzu,\n" " -owner eta -admin helbideetan jasotako mezuentzat\n" -" erantzun\n" +" erantzun\n" " automatikoa konfigura dezakezu." #: Mailman/Gui/Bounce.py:147 @@ -4809,6 +4946,7 @@ msgstr "" " Harpidedunari abisatzeko saiakera bai egingo da." #: Mailman/Gui/Bounce.py:194 +#, fuzzy msgid "" "Bad value for %(property)s: %(val)s" @@ -4873,8 +5011,8 @@ msgstr "" "bat\n" " jasotzen den bakoitzean, edukien iragazpena gaitu baduzu,\n" " artxibo erantsiak\n" -" iragazitako\n" +" iragazitako\n" " eduki-motekin erkatzen dira. Erantsi mota iragazkiak " "lehenetsitakoekin bat badator,\n" " baztertu egiten da.\n" @@ -4964,8 +5102,8 @@ msgid "" "Use this option to remove each message attachment that does\n" " not have a matching content type. Requirements and formats " "are\n" -" exactly like filter_mime_types.\n" "\n" "

                      Note: if you add entries to this list but don't add\n" @@ -5082,6 +5220,7 @@ msgstr "" " behar izaten du." #: Mailman/Gui/ContentFilter.py:171 +#, fuzzy msgid "Bad MIME type ignored: %(spectype)s" msgstr "MIME okerrari jaramonik ez: %(spectype)s" @@ -5189,6 +5328,7 @@ msgid "" msgstr "Mezu-bilduma orain bidali behar al da (hutsik egon ezean)?" #: Mailman/Gui/Digest.py:145 +#, fuzzy msgid "" "The next digest will be sent as volume\n" " %(volume)s, number %(number)s" @@ -5205,14 +5345,17 @@ msgid "There was no digest to send." msgstr "Ez zegoen mezu-bildumarik bidaltzeko." #: Mailman/Gui/GUIBase.py:173 +#, fuzzy msgid "Invalid value for variable: %(property)s" msgstr "Aldagai honentzat balio okerra: %(property)s" #: Mailman/Gui/GUIBase.py:178 +#, fuzzy msgid "Bad email address for option %(property)s: %(error)s" msgstr "%(property)s ezaugarriarentzat helbide okerra: %(error)s" #: Mailman/Gui/GUIBase.py:204 +#, fuzzy msgid "" "The following illegal substitution variables were\n" " found in the %(property)s string:\n" @@ -5228,6 +5371,7 @@ msgstr "" "izatea." #: Mailman/Gui/GUIBase.py:218 +#, fuzzy msgid "" "Your %(property)s string appeared to\n" " have some correctable problems in its new value.\n" @@ -5331,8 +5475,8 @@ msgid "" "

                      In order to split the list ownership duties into\n" " administrators and moderators, you must\n" " set a separate moderator password,\n" -" and also provide the email\n" +" and also provide the email\n" " addresses of the list moderators. Note that the field you\n" " are changing here specifies the list administrators." msgstr "" @@ -5671,13 +5815,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5717,12 +5861,12 @@ msgstr "" " dute erantzunak emateko. Bestalde, Reply-To: " "eraldatuz,\n" " zailagoa izango da erantzun pribatuak ematea. Ikus `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful webgunea gai honi buruzko\n" " datu gehiago izateko. Edo bestela `Reply-To'\n" +" ref=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">`Reply-To'\n" " Munging Considered Useful.\n" "\n" "

                      Posta zerrenda batzuetan mugatuta dago mezuak bidaltzeko " @@ -5746,8 +5890,8 @@ msgstr "Reply-To: goiburu esplizitua." msgid "" "This is the address set in the Reply-To: header\n" " when the reply_goes_to_list\n" +" href=\"?VARHELP=general/" +"reply_goes_to_list\">reply_goes_to_list\n" " option is set to Explicit address.\n" "\n" "

                      There are many reasons not to introduce or override the\n" @@ -5755,13 +5899,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5784,8 +5928,8 @@ msgid "" msgstr "" "Hauxe da Reply-To: goiburuan helbidea sartzeko aukera.\n" " Sartu helbidea reply_goes_to_list\n" +" href=\"?VARHELP=general/" +"reply_goes_to_list\">reply_goes_to_list\n" " aukeran Helbide esplizitua hautatu duzunean.\n" "\n" "

                      Arrazoi asko dago Reply-To: goiburua ez ezabatzeko\n" @@ -5796,8 +5940,8 @@ msgstr "" " dute erantzunak emateko. Bestalde, Reply-To: " "eraldatuz,\n" " zailagoa izango da erantzun pribatuak ematea. Ikus`Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful webgunea gai honi buruzko\n" " datu gehiago izateko. Edo bestela Reply-" @@ -5869,8 +6013,8 @@ msgid "" " member list addresses, but rather to the owner of those member\n" " lists. In that case, the value of this setting is appended to\n" " the member's account name for such notices. `-owner' is the\n" -" typical choice. This setting has no effect when \"umbrella_list" -"\"\n" +" typical choice. This setting has no effect when " +"\"umbrella_list\"\n" " is \"No\"." msgstr "" "\"umbrella_list\" aktibatuta dagoenean, esan nahi du enbor-zerrenda honek\n" @@ -6488,8 +6632,8 @@ msgstr "" "Mezu arruntak ere personalizatu behar al ditu Mailmanek?\n" " Erabilgarria izaten da iragarkiak bidaltzen dituzten " "zerrendetan.\n" -" Irakurri xehetasunak\n" +" Irakurri xehetasunak\n" " atala gai garrantzitsuak aztertzen dituelako." #: Mailman/Gui/NonDigest.py:61 @@ -6851,6 +6995,7 @@ msgstr "" " betiere harpidedunaren onerako." #: Mailman/Gui/Privacy.py:104 +#, fuzzy msgid "" "This section allows you to configure subscription and\n" " membership exposure policy. You can also control whether this\n" @@ -7052,8 +7197,8 @@ msgid "" " either individually or as a group. Any\n" " posting from a non-member who is not explicitly accepted,\n" " rejected, or discarded, will have their posting filtered by the\n" -" general\n" +" general\n" " non-member rules.\n" "\n" "

                      In the text boxes below, add one address per line; start the\n" @@ -7082,8 +7227,8 @@ msgstr "" "

                      Zerrendakide ez diren mezuen kasuan, aukerak hauexek dira:\n" " onartu,\n" -" moderatzeko\n" +" moderatzeko\n" " atxiki,\n" " ez onartu (errebotea bidali), edo\n" @@ -7091,8 +7236,8 @@ msgstr "" " >baztertu,\n" " bai banaka, bai denak batera. Zerrendakide ez diren\n" " mezuak ez badira moderatzen (onartu, ez onartu, baztertu),\n" -" zerrendakide\n" +" zerrendakide\n" " ez direnen arau orokorrak\n" " jarraituz iragaziko dira.\n" "\n" @@ -7118,6 +7263,7 @@ msgid "By default, should new list member postings be moderated?" msgstr "Zerrendakide berrien mezuak, hasiera batean moderatu egin behar dira?" #: Mailman/Gui/Privacy.py:218 +#, fuzzy msgid "" "Each list member has a moderation flag which says\n" " whether messages from the list member can be posted directly " @@ -7173,8 +7319,8 @@ msgid "" "If a member posts this many times, within a period of time\n" " the member is automatically moderated. Use 0 to disable. " "See\n" -" member_verbosity_interval for details on the time " "period.\n" "\n" @@ -7263,6 +7409,7 @@ msgstr "" " >oharra." #: Mailman/Gui/Privacy.py:290 +#, fuzzy msgid "" "Action to take when anyone posts to the\n" " list from a domain with a DMARC Reject%(quarantine)s Policy." @@ -7366,6 +7513,7 @@ msgid "" msgstr "" #: Mailman/Gui/Privacy.py:353 +#, fuzzy msgid "" "Text to include in any\n" " onartuen,\n" -" moderatuen,\n" +" moderatuen,\n" " ez onartuen eta\n" " The body of the message can also be optionally scanned for\n" " Subject: and Keywords: headers, as\n" " specified by the topics_bodylines_limit\n" +" href=\"?VARHELP=topics/" +"topics_bodylines_limit\">topics_bodylines_limit\n" " configuration variable." msgstr "" "Gaien iragazkiak, heltzen diren mezu guztiak sailkatu egiten ditu\n" @@ -7963,8 +8112,8 @@ msgstr "" " Subject: eta Keyword: bezalako " "goiburuak\n" " bilatzeko,\n" -" topics_bodylines_limit\n" +" topics_bodylines_limit\n" " aldagaian zehazten den moduan." #: Mailman/Gui/Topics.py:72 @@ -8036,6 +8185,7 @@ msgstr "" " Zerbait falta zaien definizioak ez dira onartuko." #: Mailman/Gui/Topics.py:135 +#, fuzzy msgid "" "The topic pattern '%(safepattern)s' is not a\n" " legal regular expression. It will be discarded." @@ -8247,6 +8397,7 @@ msgid "%(listinfo_link)s list run by %(owner_link)s" msgstr "%(listinfo_link)s zerrendako kudeatzailea %(owner_link)s da" #: Mailman/HTMLFormatter.py:57 +#, fuzzy msgid "%(realname)s administrative interface" msgstr "%(realname)s kudeatzeko interfazea" @@ -8255,6 +8406,7 @@ msgid " (requires authorization)" msgstr " (baimena beharrezkoa)" #: Mailman/HTMLFormatter.py:61 +#, fuzzy msgid "Overview of all %(hostname)s mailing lists" msgstr "" "%(hostname)s zerbitzariko posta zerrendak gainbegiratzeko egin klik hemen" @@ -8276,6 +8428,7 @@ msgid "; it was disabled by the list administrator" msgstr "; kudeatzaileak ezgaitu du" #: Mailman/HTMLFormatter.py:146 +#, fuzzy msgid "" "; it was disabled due to excessive bounces. The\n" " last bounce was received on %(date)s" @@ -8288,6 +8441,7 @@ msgid "; it was disabled for unknown reasons" msgstr "; arrazoi ezezagunegatik ezgaiturik dago hau." #: Mailman/HTMLFormatter.py:151 +#, fuzzy msgid "Note: your list delivery is currently disabled%(reason)s." msgstr "Oharra: une honetan mezuen banaketa ezgaituta duzu %(reason)s." @@ -8300,6 +8454,7 @@ msgid "the list administrator" msgstr "zerrenda kudeatzailea" #: Mailman/HTMLFormatter.py:157 +#, fuzzy msgid "" "

                      %(note)s\n" "\n" @@ -8319,6 +8474,7 @@ msgstr "" " laguntzarik behar baduzu, idatzi %(mailto)s helbidera." #: Mailman/HTMLFormatter.py:169 +#, fuzzy msgid "" "

                      We have received some recent bounces from your\n" " address. Your current bounce score is %(score)s out of " @@ -8339,6 +8495,7 @@ msgstr "" " jarriko da." #: Mailman/HTMLFormatter.py:181 +#, fuzzy msgid "" "(Note - you are subscribing to a list of mailing lists, so the %(type)s " "notice will be sent to the admin address for your membership, %(addr)s.)

                      " @@ -8387,6 +8544,7 @@ msgstr "" "zaizu." #: Mailman/HTMLFormatter.py:208 +#, fuzzy msgid "" "This is %(also)sa private list, which means that the\n" " list of members is not available to non-members." @@ -8395,6 +8553,7 @@ msgstr "" " harpidedun ez direnentzat kontsultagai." #: Mailman/HTMLFormatter.py:211 +#, fuzzy msgid "" "This is %(also)sa hidden list, which means that the\n" " list of members is available only to the list administrator." @@ -8403,6 +8562,7 @@ msgstr "" " kudeatzaileak bakarrik ikus ditzake." #: Mailman/HTMLFormatter.py:214 +#, fuzzy msgid "" "This is %(also)sa public list, which means that the\n" " list of members list is available to everyone." @@ -8419,6 +8579,7 @@ msgstr "" " sahiesteko)." #: Mailman/HTMLFormatter.py:222 +#, fuzzy msgid "" "

                      (Note that this is an umbrella list, intended to\n" " have only other mailing lists as members. Among other things,\n" @@ -8435,6 +8596,7 @@ msgid "either " msgstr "ezta " #: Mailman/HTMLFormatter.py:256 +#, fuzzy msgid "" "To unsubscribe from %(realname)s, get a password reminder,\n" " or change your subscription options %(either)senter your " @@ -8468,6 +8630,7 @@ msgstr "" " eskatuko zaizu." #: Mailman/HTMLFormatter.py:277 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " members.)" @@ -8476,6 +8639,7 @@ msgstr "" " ikus dezakete.)" #: Mailman/HTMLFormatter.py:281 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " administrator.)" @@ -8534,6 +8698,7 @@ msgid "The current archive" msgstr "Oraingo fitxategia" #: Mailman/Handlers/Acknowledge.py:59 +#, fuzzy msgid "%(realname)s post acknowledgement" msgstr "%(realname)s posta zerrendara idatzi izanaren konfirmazioa" @@ -8546,6 +8711,7 @@ msgid "" msgstr "" #: Mailman/Handlers/CalcRecips.py:79 +#, fuzzy msgid "" "Your urgent message to the %(realname)s mailing list was not authorized for\n" "delivery. The original message as received by Mailman is attached.\n" @@ -8554,6 +8720,7 @@ msgstr "" "Jatorrizko mezua erantsita, Mailmanek jaso zuen moduan.\n" #: Mailman/Handlers/CookHeaders.py:180 +#, fuzzy msgid "%(realname)s via %(lrn)s" msgstr "%(realname)s , %(lrn)s-en bidez" @@ -8624,6 +8791,7 @@ msgid "Message may contain administrivia" msgstr "Mezu honek eskaera administratiboak izan ditzake." #: Mailman/Handlers/Hold.py:84 +#, fuzzy msgid "" "Please do *not* post administrative requests to the mailing\n" "list. If you wish to subscribe, visit %(listurl)s or send a message with " @@ -8662,12 +8830,14 @@ msgid "Posting to a moderated newsgroup" msgstr "Moderatutako berri-talde baterako mezua." #: Mailman/Handlers/Hold.py:252 +#, fuzzy msgid "Your message to %(listname)s awaits moderator approval" msgstr "" "%(listname)s zerrendara bidali duzun mezua moderatzaileak noiz onartuko zain " "dago" #: Mailman/Handlers/Hold.py:271 +#, fuzzy msgid "%(listname)s post from %(sender)s requires approval" msgstr "" "%(sender)s zerrendakideak %(listname)s zerrendara bidalitako\n" @@ -8710,6 +8880,7 @@ msgid "After content filtering, the message was empty" msgstr "Edukia iragazi ondoren mezua hutsik dago" #: Mailman/Handlers/MimeDel.py:269 +#, fuzzy msgid "" "The attached message matched the %(listname)s mailing list's content " "filtering\n" @@ -8729,6 +8900,7 @@ msgid "Content filtered message notification" msgstr "Edukiarengatik iragazitako mezuaren jakinarazpena" #: Mailman/Handlers/Moderate.py:145 +#, fuzzy msgid "" "Your message has been rejected, probably because you are not subscribed to " "the\n" @@ -8752,6 +8924,7 @@ msgid "The attached message has been automatically discarded." msgstr "Erantsitako mezua automatikoki baztertu da." #: Mailman/Handlers/Replybot.py:75 +#, fuzzy msgid "Auto-response for your message to the \"%(realname)s\" mailing list" msgstr "Zure \"%(realname)s\"-rabidalitako mezuaren auto-erantzuna" @@ -8760,6 +8933,7 @@ msgid "The Mailman Replybot" msgstr "Mailmanen Erantzun-Robota" #: Mailman/Handlers/Scrubber.py:208 +#, fuzzy msgid "" "An embedded and charset-unspecified text was scrubbed...\n" "Name: %(filename)s\n" @@ -8774,6 +8948,7 @@ msgid "HTML attachment scrubbed and removed" msgstr "Erantsitako HTML dokumentua ezabatuta" #: Mailman/Handlers/Scrubber.py:234 Mailman/Handlers/Scrubber.py:259 +#, fuzzy msgid "" "An HTML attachment was scrubbed...\n" "URL: %(url)s\n" @@ -8794,6 +8969,7 @@ msgid "unknown sender" msgstr "bidaltzaile ezezaguna" #: Mailman/Handlers/Scrubber.py:276 +#, fuzzy msgid "" "An embedded message was scrubbed...\n" "From: %(who)s\n" @@ -8810,6 +8986,7 @@ msgstr "" "URL: %(url)s\n" #: Mailman/Handlers/Scrubber.py:308 +#, fuzzy msgid "" "A non-text attachment was scrubbed...\n" "Name: %(filename)s\n" @@ -8826,6 +9003,7 @@ msgstr "" "URL : %(url)s\n" #: Mailman/Handlers/Scrubber.py:347 +#, fuzzy msgid "Skipped content of type %(partctype)s\n" msgstr "%(partctype)s eduki mota ez da eraldatu\n" @@ -8839,6 +9017,7 @@ msgid "Header matched regexp: %(pattern)s" msgstr "Blokeatutako helbidea (matched %(pattern)s)" #: Mailman/Handlers/SpamDetect.py:127 +#, fuzzy msgid "" "You are not allowed to post to this mailing list From: a domain which\n" "publishes a DMARC policy of reject or quarantine, and your message has been\n" @@ -8856,6 +9035,7 @@ msgid "Message rejected by filter rule match" msgstr "Iragazki erregela batekin bat egiteagatik atzera botatako mezua" #: Mailman/Handlers/ToDigest.py:173 +#, fuzzy msgid "%(realname)s Digest, Vol %(volume)d, Issue %(issue)d" msgstr "%(realname)s Mezu-Bilduma, %(volume)d bilduma, %(issue)d. zenbakia" @@ -8892,6 +9072,7 @@ msgid "End of " msgstr "Bilduma honen bukaera: " #: Mailman/ListAdmin.py:309 +#, fuzzy msgid "Posting of your message titled \"%(subject)s\"" msgstr "Bidalitako mezuaren gaia: \"%(subject)s\"" @@ -8904,6 +9085,7 @@ msgid "Forward of moderated message" msgstr "Mezu moderatua birbidali egin da" #: Mailman/ListAdmin.py:405 +#, fuzzy msgid "New subscription request to list %(realname)s from %(addr)s" msgstr "Harpidetza eskaera berria %(realname)s zerrendatik. Nork: %(addr)s" @@ -8917,6 +9099,7 @@ msgid "via admin approval" msgstr "Onespenaren zai jarraitu" #: Mailman/ListAdmin.py:466 +#, fuzzy msgid "New unsubscription request from %(realname)s by %(addr)s" msgstr "" "Zerrenda uzteko eskaera berria %(realname)s zerrendatik. Nork: %(addr)s" @@ -8930,10 +9113,12 @@ msgid "Original Message" msgstr "Jatorrizko Mezua" #: Mailman/ListAdmin.py:526 +#, fuzzy msgid "Request to mailing list %(realname)s rejected" msgstr "%(realname)s posta zerrendara egindako eskaera ez da onartu" #: Mailman/MTA/Manual.py:66 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been created via the through-the-web\n" "interface. In order to complete the activation of this mailing list, the\n" @@ -8959,14 +9144,17 @@ msgstr "" "egin behar da, Normalean 'newaliases' programa ere abiarazi beharko duzu\n" #: Mailman/MTA/Manual.py:82 +#, fuzzy msgid "## %(listname)s mailing list" msgstr "## %(listname)s-ren posta zerrenda." #: Mailman/MTA/Manual.py:99 +#, fuzzy msgid "Mailing list creation request for list %(listname)s" msgstr "%(listname)s posta zerrenda sortzeko eskaera" #: Mailman/MTA/Manual.py:113 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been removed via the through-the-web\n" "interface. In order to complete the de-activation of this mailing list, " @@ -8983,6 +9171,7 @@ msgstr "" "Hemen /etc/aliase fitxategitik ezabatu behar diren lerroak:\n" #: Mailman/MTA/Manual.py:123 +#, fuzzy msgid "" "\n" "To finish removing your mailing list, you must edit your /etc/aliases (or\n" @@ -8999,14 +9188,17 @@ msgstr "" "## %(listname)s posta zerrenda" #: Mailman/MTA/Manual.py:142 +#, fuzzy msgid "Mailing list removal request for list %(listname)s" msgstr "%(listname)s izenez posta zerrenda ezabatze eskakizuna" #: Mailman/MTA/Postfix.py:442 +#, fuzzy msgid "checking permissions on %(file)s" msgstr "%(file)s-etako baimenak aztertzen" #: Mailman/MTA/Postfix.py:452 +#, fuzzy msgid "%(file)s permissions must be 0664 (got %(octmode)s)" msgstr "%(file)s baimena 0664 izan behar du (got %(octmode)s)" @@ -9020,34 +9212,42 @@ msgid "(fixing)" msgstr "(zuzentzen)" #: Mailman/MTA/Postfix.py:470 +#, fuzzy msgid "checking ownership of %(dbfile)s" msgstr "%(dbfile)s fitxategiaren jabetza egiaztatzen" #: Mailman/MTA/Postfix.py:478 +#, fuzzy msgid "%(dbfile)s owned by %(owner)s (must be owned by %(user)s" msgstr "%(dbfile)s jabea %(owner)s da, ( %(user)s izan beharko litzateke)" #: Mailman/MTA/Postfix.py:491 +#, fuzzy msgid "%(dbfile)s permissions must be 0664 (got %(octmode)s)" msgstr "%(dbfile)s baimena 0664 izan behar du (got %(octmode)s)" #: Mailman/MailList.py:219 +#, fuzzy msgid "Your confirmation is required to join the %(listname)s mailing list" msgstr "%(listname)s : harpidetzeko zure konfirmazioa behar dugu" #: Mailman/MailList.py:230 +#, fuzzy msgid "Your confirmation is required to leave the %(listname)s mailing list" msgstr "%(listname)s : harpidetza uzteko zure konfirmazioa behar dugu" #: Mailman/MailList.py:999 Mailman/MailList.py:1492 +#, fuzzy msgid " from %(remote)s" msgstr " %(remote)s-(e)tik" #: Mailman/MailList.py:1043 +#, fuzzy msgid "subscriptions to %(realname)s require moderator approval" msgstr "%(realname)s zerrendan harpidetzeko moderatzailearen onespena behar da" #: Mailman/MailList.py:1125 bin/add_members:299 +#, fuzzy msgid "%(realname)s subscription notification" msgstr "%(realname)s harpidetza jakinarazpena" @@ -9056,10 +9256,12 @@ msgid "unsubscriptions require moderator approval" msgstr "zerrenda uzteko moderatzailearen onespena behar da" #: Mailman/MailList.py:1166 +#, fuzzy msgid "%(realname)s unsubscribe notification" msgstr "%(realname)s zerrenda utzi izanaren jakinarazpena" #: Mailman/MailList.py:1328 +#, fuzzy msgid "%(realname)s address change notification" msgstr "%(realname)s helbidea aldatu izanaren jakinarazpena" @@ -9074,6 +9276,7 @@ msgid "via web confirmation" msgstr "Berrespen kate okerra" #: Mailman/MailList.py:1396 +#, fuzzy msgid "subscriptions to %(name)s require administrator approval" msgstr "%(name)s zerrendako kide izateko, kudeatzailearen onespena behar da" @@ -9092,6 +9295,7 @@ msgid "Last autoresponse notification for today" msgstr "Gaurko azkenengo jakinarazpen automatikoa" #: Mailman/Queue/BounceRunner.py:360 +#, fuzzy msgid "" "The attached message was received as a bounce, but either the bounce format\n" "was not recognized, or no member addresses could be extracted from it. " @@ -9180,6 +9384,7 @@ msgid "Original message suppressed by Mailman site configuration\n" msgstr "Mezuaren gorputza Mailmanen gune konfigurazioak ezabatu du\n" #: Mailman/htmlformat.py:675 +#, fuzzy msgid "Delivered by Mailman
                      version %(version)s" msgstr "Mailmanen
                      %(version)s bertsioak banatuta" @@ -9268,6 +9473,7 @@ msgid "Server Local Time" msgstr "Zerbitzariaren Bertako Ordua" #: Mailman/i18n.py:180 +#, fuzzy msgid "" "%(wday)s %(mon)s %(day)2i %(hh)02i:%(mm)02i:%(ss)02i %(tzname)s %(year)04i" msgstr "" @@ -9390,6 +9596,7 @@ msgstr "" "bat izan behar da`-'\n" #: bin/add_members:162 bin/add_members:172 +#, fuzzy msgid "Already a member: %(member)s" msgstr "Dagoeneko harpidedun: %(member)s" @@ -9398,10 +9605,12 @@ msgid "Bad/Invalid email address: blank line" msgstr "Eposta hebide okerra/baliogabea: errezkada txuria" #: bin/add_members:180 +#, fuzzy msgid "Bad/Invalid email address: %(member)s" msgstr "Eposta hebide okerra/baliogabea: %(member)s" #: bin/add_members:182 +#, fuzzy msgid "Hostile address (illegal characters): %(member)s" msgstr "Helbide okerra (karaktere debekatuak): %(member)s" @@ -9411,14 +9620,17 @@ msgid "Invited: %(member)s" msgstr "Harpideduna: %(member)s" #: bin/add_members:187 +#, fuzzy msgid "Subscribed: %(member)s" msgstr "Harpideduna: %(member)s" #: bin/add_members:237 +#, fuzzy msgid "Bad argument to -w/--welcome-msg: %(arg)s" msgstr "-w/--welcome-msg, argumentu okerra: %(arg)s" #: bin/add_members:244 +#, fuzzy msgid "Bad argument to -a/--admin-notify: %(arg)s" msgstr "-a/--admin-notify, argumentu okerra: %(arg)s" @@ -9435,6 +9647,7 @@ msgstr "" #: bin/add_members:261 bin/config_list:110 bin/export.py:271 bin/find_member:97 #: bin/inject:91 bin/list_admins:90 bin/list_members:252 bin/sync_members:222 #: cron/bumpdigests:86 +#, fuzzy msgid "No such list: %(listname)s" msgstr "Zerrenda hau ez dago: %(listname)s" @@ -9445,6 +9658,7 @@ msgid "Nothing to do." msgstr "Ez dago ezer egiterik." #: bin/arch:19 +#, fuzzy msgid "" "Rebuild a list's archive.\n" "\n" @@ -9539,6 +9753,7 @@ msgid "listname is required" msgstr "zerrendaren izena beharrezkoa da" #: bin/arch:143 bin/change_pw:107 bin/config_list:257 +#, fuzzy msgid "" "No such list \"%(listname)s\"\n" "%(e)s" @@ -9547,6 +9762,7 @@ msgstr "" "%(e)s" #: bin/arch:168 +#, fuzzy msgid "Cannot open mbox file %(mbox)s: %(msg)s" msgstr "%(mbox)s mbox fitxategia ezin da zabaldu: %(msg)s" @@ -9628,6 +9844,7 @@ msgid "" msgstr "" #: bin/change_pw:145 +#, fuzzy msgid "Bad arguments: %(strargs)s" msgstr "Argumentu okerra: %(strargs)s" @@ -9636,14 +9853,17 @@ msgid "Empty list passwords are not allowed" msgstr "Zerrenda pasahitza ezin da hutsik egon" #: bin/change_pw:181 +#, fuzzy msgid "New %(listname)s password: %(notifypassword)s" msgstr "Pasahitz berria %(listname)s zerrendarako: %(notifypassword)s" #: bin/change_pw:190 +#, fuzzy msgid "Your new %(listname)s list password" msgstr "Zure pasahitz berria %(listname)s zerrendarako" #: bin/change_pw:191 +#, fuzzy msgid "" "The site administrator at %(hostname)s has changed the password for your\n" "mailing list %(listname)s. It is now\n" @@ -9671,6 +9891,7 @@ msgstr "" " %(adminurl)s\n" #: bin/check_db:19 +#, fuzzy msgid "" "Check a list's config database file for integrity.\n" "\n" @@ -9739,10 +9960,12 @@ msgid "List:" msgstr "Zerrenda:" #: bin/check_db:148 +#, fuzzy msgid " %(file)s: okay" msgstr " %(file)s: ondo" #: bin/check_perms:20 +#, fuzzy msgid "" "Check the permissions for the Mailman installation.\n" "\n" @@ -9762,44 +9985,54 @@ msgstr "" "informazio luzea emango du (verbose).\n" #: bin/check_perms:110 +#, fuzzy msgid " checking gid and mode for %(path)s" msgstr " gid-a eta modua egiaztatzen: %(path)s" #: bin/check_perms:122 +#, fuzzy msgid "%(path)s bad group (has: %(groupname)s, expected %(MAILMAN_GROUP)s)" msgstr "" "Talde okerra aurkitu da hemen: %(path)s. Duena %(groupname)s da, eta espero " "zena %(MAILMAN_GROUP)s)" #: bin/check_perms:151 +#, fuzzy msgid "directory permissions must be %(octperms)s: %(path)s" msgstr "Karpeta baimenak %(octperms)s: %(path)s izan behar dute." #: bin/check_perms:160 +#, fuzzy msgid "source perms must be %(octperms)s: %(path)s" msgstr "Jatorriaren baimenak %(octperms)s: %(path)s izan behar dute" #: bin/check_perms:171 +#, fuzzy msgid "article db files must be %(octperms)s: %(path)s" msgstr "db fitxategien baimenak %(octperms)s izan behar dira: %(path)s" #: bin/check_perms:183 +#, fuzzy msgid "checking mode for %(prefix)s" msgstr "%(prefix)s aldagaiaren egoera gainbegiratzen" #: bin/check_perms:193 +#, fuzzy msgid "WARNING: directory does not exist: %(d)s" msgstr "KONTUZ: karpeta ez da existitzen:%(d)s" #: bin/check_perms:197 +#, fuzzy msgid "directory must be at least 02775: %(d)s" msgstr "karpetak behintzat 02775 izan behar du: %(d)s" #: bin/check_perms:209 +#, fuzzy msgid "checking perms on %(private)s" msgstr "hemengo baimenak egiaztatzen: %(private)s" #: bin/check_perms:214 +#, fuzzy msgid "%(private)s must not be other-readable" msgstr "%(private)s ezingo du beste batek irakurri" @@ -9817,6 +10050,7 @@ msgid "mbox file must be at least 0660:" msgstr "mbox fitxategia 0660 baimenak izan behar ditu gutxienez:" #: bin/check_perms:263 +#, fuzzy msgid "%(dbdir)s \"other\" perms must be 000" msgstr "\"besteak\" kasuan, %(dbdir)s 000 izan behar da" @@ -9825,26 +10059,32 @@ msgid "checking cgi-bin permissions" msgstr "cgi-bin-en baimenak egiaztatzen" #: bin/check_perms:278 +#, fuzzy msgid " checking set-gid for %(path)s" msgstr " %(path)s aldagaiaren set-gida gainbegiratzen" #: bin/check_perms:282 +#, fuzzy msgid "%(path)s must be set-gid" msgstr "%(path)s aldagaia set-gid izan beharko da" #: bin/check_perms:292 +#, fuzzy msgid "checking set-gid for %(wrapper)s" msgstr "%(wrapper)s aldagaiaren set-gida gainbegiratzen" #: bin/check_perms:296 +#, fuzzy msgid "%(wrapper)s must be set-gid" msgstr "%(wrapper)s aldagaia set-gid izan beharko da" #: bin/check_perms:306 +#, fuzzy msgid "checking permissions on %(pwfile)s" msgstr "%(pwfile)s fitxategiko baimenak egiaztatzen" #: bin/check_perms:315 +#, fuzzy msgid "%(pwfile)s permissions must be exactly 0640 (got %(octmode)s)" msgstr "" "%(pwfile)s fitxategiaren baimenek 0640 izan behar dute (%(octmode)s daude)" @@ -9854,10 +10094,12 @@ msgid "checking permissions on list data" msgstr "zerrendako datuen baimenak egiaztatzen" #: bin/check_perms:348 +#, fuzzy msgid " checking permissions on: %(path)s" msgstr " %(path)s fitxategiko baimenak egiaztatzen" #: bin/check_perms:356 +#, fuzzy msgid "file permissions must be at least 660: %(path)s" msgstr "Fitxategi baimenak behintzat 660 izan behar du: %(path)s" @@ -9913,6 +10155,7 @@ msgid "Unix-From line changed: %(lineno)d" msgstr "Unix-From lerroa aldaturik: %(lineno)d" #: bin/cleanarch:111 +#, fuzzy msgid "Bad status number: %(arg)s" msgstr "Egoera zenbaki okerra: %(arg)s" @@ -10062,10 +10305,12 @@ msgid " original address removed:" msgstr " helbide zaharra ezabatuta:" #: bin/clone_member:202 +#, fuzzy msgid "Not a valid email address: %(toaddr)s" msgstr "Ez da helbide zuzena: %(toaddr)s" #: bin/clone_member:215 +#, fuzzy msgid "" "Error opening list \"%(listname)s\", skipping.\n" "%(e)s" @@ -10128,6 +10373,7 @@ msgid "" msgstr "" #: bin/config_list:118 +#, fuzzy msgid "" "# -*- python -*-\n" "# -*- coding: %(charset)s -*-\n" @@ -10148,22 +10394,27 @@ msgid "legal values are:" msgstr "balio zuzenak hauek dira:" #: bin/config_list:270 +#, fuzzy msgid "attribute \"%(k)s\" ignored" msgstr "\"%(k)s\" atributua ez da aintzakotzat hartu" #: bin/config_list:273 +#, fuzzy msgid "attribute \"%(k)s\" changed" msgstr "\"%(k)s\" atributua aldatuta" #: bin/config_list:279 +#, fuzzy msgid "Non-standard property restored: %(k)s" msgstr "Ezaugarri ez-estandarrak leheneratuta: %(k)s" #: bin/config_list:288 +#, fuzzy msgid "Invalid value for property: %(k)s" msgstr "Balio okerra ezaugarri honendako: %(k)s" #: bin/config_list:291 +#, fuzzy msgid "Bad email address for option %(k)s: %(v)s" msgstr "%(k)s aukerarentzat helbide okerra: %(v)s" @@ -10212,14 +10463,17 @@ msgid "" msgstr "" #: bin/discard:94 +#, fuzzy msgid "Ignoring non-held message: %(f)s" msgstr "Atxikitu gabeko mezua utzita: %(f)s" #: bin/discard:100 +#, fuzzy msgid "Ignoring held msg w/bad id: %(f)s" msgstr "Atxikitutako mezua utzita id okerragatik: %(f)s" #: bin/discard:112 +#, fuzzy msgid "Discarded held msg #%(id)s for list %(listname)s" msgstr "Ezetsi dugu atxikitutako mezua #%(id)s %(listname)s zerrendarako." @@ -10265,6 +10519,7 @@ msgid "No filename given." msgstr "Ez dago fitxategi izenik" #: bin/dumpdb:108 +#, fuzzy msgid "Bad arguments: %(pargs)s" msgstr "Argumentu okerra: %(pargs)s" @@ -10273,14 +10528,17 @@ msgid "Please specify either -p or -m." msgstr "Mesedez, aukeratu -p edo -m" #: bin/dumpdb:133 +#, fuzzy msgid "[----- start %(typename)s file -----]" msgstr "[----- %(typename)s artxiboaren hasiera -----]" #: bin/dumpdb:139 +#, fuzzy msgid "[----- end %(typename)s file -----]" msgstr "[----- %(typename)s artxiboaren bukaera -----]" #: bin/dumpdb:142 +#, fuzzy msgid "<----- start object %(cnt)s ----->" msgstr "<----- %(cnt)s objektu hasiera ----->" @@ -10432,6 +10690,7 @@ msgid "Setting web_page_url to: %(web_page_url)s" msgstr "Web orriaren url-a: %(web_page_url)s ezartzen" #: bin/fix_url.py:88 +#, fuzzy msgid "Setting host_name to: %(mailhost)s" msgstr "Serbitzari_izena ezartzen %(mailhost)s-erako" @@ -10487,6 +10746,7 @@ msgid "" msgstr "" #: bin/inject:84 +#, fuzzy msgid "Bad queue directory: %(qdir)s" msgstr "Ilara karpeta okerra: %(qdir)s" @@ -10495,6 +10755,7 @@ msgid "A list name is required" msgstr "Zerrenda baten izena behar da" #: bin/list_admins:20 +#, fuzzy msgid "" "List all the owners of a mailing list.\n" "\n" @@ -10541,6 +10802,7 @@ msgstr "" "zerrenda izen bat baino gehiago izan dezakezu.\n" #: bin/list_admins:97 +#, fuzzy msgid "List: %(listname)s, \tOwners: %(owners)s" msgstr "Zerrenda: %(listname)s, \tJabea: %(owners)s" @@ -10671,10 +10933,12 @@ msgid "" msgstr "" #: bin/list_members:198 +#, fuzzy msgid "Bad --nomail option: %(why)s" msgstr "--nomail aukera okerra: %(why)s" #: bin/list_members:209 +#, fuzzy msgid "Bad --digest option: %(kind)s" msgstr "--digest aukera okerra: %(kind)s" @@ -10688,6 +10952,7 @@ msgid "Could not open file for writing:" msgstr "Fitxategia ezin da idazkera moduan ireki" #: bin/list_owners:20 +#, fuzzy msgid "" "List the owners of a mailing list, or all mailing lists.\n" "\n" @@ -10838,6 +11103,7 @@ msgid "" msgstr "" #: bin/mailmanctl:152 +#, fuzzy msgid "PID unreadable in: %(pidfile)s" msgstr "PID irakurezina : %(pidfile)s" @@ -10846,6 +11112,7 @@ msgid "Is qrunner even running?" msgstr "qruner abiarazita al dago?" #: bin/mailmanctl:160 +#, fuzzy msgid "No child with pid: %(pid)s" msgstr "Pid hau duen semerik ez: %(pid)s" @@ -10875,6 +11142,7 @@ msgstr "" "saiatu.\n" #: bin/mailmanctl:233 +#, fuzzy msgid "" "The master qrunner lock could not be acquired, because it appears as if " "some\n" @@ -10899,10 +11167,12 @@ msgstr "" "Uzten." #: bin/mailmanctl:280 cron/mailpasswds:119 +#, fuzzy msgid "Site list is missing: %(sitelistname)s" msgstr "Gunearen zerrenda ez da agertzen: %(sitelistname)s" #: bin/mailmanctl:305 +#, fuzzy msgid "Run this program as root or as the %(name)s user, or use -u." msgstr "" "Programa hau root gisa exekutatau, %(name)s erabiltzaile gisa, edo -u aukera " @@ -10913,6 +11183,7 @@ msgid "No command given." msgstr "Ez da agindurik eman." #: bin/mailmanctl:339 +#, fuzzy msgid "Bad command: %(command)s" msgstr "Okerreko komandoa: %(command)s" @@ -10937,6 +11208,7 @@ msgid "Starting Mailman's master qrunner." msgstr "Mailman qrunner nagusia abiarazten." #: bin/mmsitepass:19 +#, fuzzy msgid "" "Set the site password, prompting from the terminal.\n" "\n" @@ -10991,6 +11263,7 @@ msgid "list creator" msgstr "zerrenda sortzailea" #: bin/mmsitepass:86 +#, fuzzy msgid "New %(pwdesc)s password: " msgstr "%(pwdesc)s pasahitz berria:" @@ -11207,6 +11480,7 @@ msgstr "" "Observe que los nombres de las listas se convierten a minúsculas.\n" #: bin/newlist:162 +#, fuzzy msgid "Unknown language: %(lang)s" msgstr "Hizkuntza ezezaguna: %(lang)s" @@ -11219,6 +11493,7 @@ msgid "Enter the email of the person running the list: " msgstr "Zerrenda abiarazita duen pertsonaren helbidea sartu:" #: bin/newlist:193 +#, fuzzy msgid "Initial %(listname)s password: " msgstr "%(listname)s zerrendaren hasierako pasahitza: " @@ -11228,13 +11503,14 @@ msgstr "Zerrendaren pasahitza ezin da hutsik egon" #: bin/newlist:220 msgid "" -" - owner addresses need to be fully-qualified names like \"owner@example.com" -"\", not just \"owner\"." +" - owner addresses need to be fully-qualified names like \"owner@example." +"com\", not just \"owner\"." msgstr "" " - jabearen helbideak helbide osoa izan behar du, \"jabea@adibidea.eus\" " "bezala, eta ez \"jabea\" bakarrik." #: bin/newlist:244 +#, fuzzy msgid "Hit enter to notify %(listname)s owner..." msgstr "Sakatu intro %(listname)s zerrendako jabeari jakinarazteko..." @@ -11306,6 +11582,7 @@ msgid "" msgstr "" #: bin/qrunner:179 +#, fuzzy msgid "%(name)s runs the %(runnername)s qrunner" msgstr "%(name)s-ek %(runnername)s qrunner-a exekutatzen du" @@ -11438,18 +11715,22 @@ msgstr "" "\n" #: bin/remove_members:156 +#, fuzzy msgid "Could not open file for reading: %(filename)s." msgstr "Fitxategi hau ezin izan da zabaldu: %(filename)s." #: bin/remove_members:163 +#, fuzzy msgid "Error opening list %(listname)s... skipping." msgstr "%(listname)s zerrenda zabaltzean errorea... saltatzen." #: bin/remove_members:173 +#, fuzzy msgid "No such member: %(addr)s" msgstr "Zerrendakide ezezaguna: %(addr)s" #: bin/remove_members:178 +#, fuzzy msgid "User `%(addr)s' removed from list: %(listname)s." msgstr "`%(addr)s' erabiltzailea zerrenda honetatik kenduta: %(listname)s." @@ -11474,10 +11755,12 @@ msgid "" msgstr "" #: bin/reset_pw.py:77 +#, fuzzy msgid "Changing passwords for list: %(listname)s" msgstr "%(listname)s zerrendaren pasahitzak aldatzen." #: bin/reset_pw.py:83 +#, fuzzy msgid "New password for member %(member)40s: %(randompw)s" msgstr "Pasahitz berria %(member)40s erabiltzailearentzat: %(randompw)s" @@ -11524,18 +11807,22 @@ msgstr "" "\n" #: bin/rmlist:73 bin/rmlist:76 +#, fuzzy msgid "Removing %(msg)s" msgstr "%(msg)s ezabatzen" #: bin/rmlist:81 +#, fuzzy msgid "%(listname)s %(msg)s not found as %(filename)s" msgstr "%(listname)s-ko %(msg)s ez da %(filename)s bezala aurkitu" #: bin/rmlist:105 +#, fuzzy msgid "No such list (or list already deleted): %(listname)s" msgstr "Zerrenda ezezaguna (beharbada ezabatuta): %(listname)s" #: bin/rmlist:108 +#, fuzzy msgid "No such list: %(listname)s. Removing its residual archives." msgstr "Zerrenda hau ez dago: %(listname)s. Hondar-fitxategiak ezabatzen." @@ -11650,6 +11937,7 @@ msgid "" msgstr "" #: bin/sync_members:115 +#, fuzzy msgid "Bad choice: %(yesno)s" msgstr "Aukera okerra: %(yesno)s" @@ -11666,6 +11954,7 @@ msgid "No argument to -f given" msgstr "-f aukerak ez du argumenturik" #: bin/sync_members:172 +#, fuzzy msgid "Illegal option: %(opt)s" msgstr "Ezinezko aukera: %(opt)s" @@ -11678,6 +11967,7 @@ msgid "Must have a listname and a filename" msgstr "Zerrendaren eta artxiboaren izena beharrezkoak dira" #: bin/sync_members:191 +#, fuzzy msgid "Cannot read address file: %(filename)s: %(msg)s" msgstr "Helbideen fitxategia ezin da irakurri: %(filename)s: %(msg)s" @@ -11694,10 +11984,12 @@ msgid "You must fix the preceding invalid addresses first." msgstr "Lehenengo eta behin, helbide okerra zuzendu behar da." #: bin/sync_members:264 +#, fuzzy msgid "Added : %(s)s" msgstr "Gehituta : %(s)s" #: bin/sync_members:289 +#, fuzzy msgid "Removed: %(s)s" msgstr "Kenduta: %(s)s" @@ -11768,6 +12060,7 @@ msgid "scan the po file comparing msgids with msgstrs" msgstr "po fitxategiko msgids eta msgstrs parekatuaz arakatu" #: bin/unshunt:20 +#, fuzzy msgid "" "Move a message from the shunt queue to the original queue.\n" "\n" @@ -11798,6 +12091,7 @@ msgstr "" "dauden mezu guztiak galduko dira. \n" #: bin/unshunt:85 +#, fuzzy msgid "" "Cannot unshunt message %(filebase)s, skipping:\n" "%(e)s" @@ -11806,6 +12100,7 @@ msgstr "" "%(e)s" #: bin/update:20 +#, fuzzy msgid "" "Perform all necessary upgrades.\n" "\n" @@ -11841,14 +12136,17 @@ msgstr "" "Bertsio zaharrekin ere egin dezake lana; 1.0b4 (?) bertsiora arte.\n" #: bin/update:107 +#, fuzzy msgid "Fixing language templates: %(listname)s" msgstr "Hizkuntza txantilloiak ezartzen: %(listname)s" #: bin/update:196 bin/update:711 +#, fuzzy msgid "WARNING: could not acquire lock for list: %(listname)s" msgstr "KONTUZ: %(listname)s zerrenda ezin izan da blokeatu" #: bin/update:215 +#, fuzzy msgid "Resetting %(n)s BYBOUNCEs disabled addrs with no bounce info" msgstr "" "Errebote informazio gabe berrrarazten errebote gegigatik ezabatutako %(n)s " @@ -11868,6 +12166,7 @@ msgstr "" "prozesatzen." #: bin/update:255 +#, fuzzy msgid "" "\n" "%(listname)s has both public and private mbox archives. Since this list\n" @@ -11922,6 +12221,7 @@ msgid "- updating old private mbox file" msgstr "- mbox fitxategi pribatu zaharra eguneratzen" #: bin/update:295 +#, fuzzy msgid "" " unknown file in the way, moving\n" " %(o_pri_mbox_file)s\n" @@ -11938,6 +12238,7 @@ msgid "- updating old public mbox file" msgstr "- mbox fitxategi publiko zaharra eguneratzen" #: bin/update:317 +#, fuzzy msgid "" " unknown file in the way, moving\n" " %(o_pub_mbox_file)s\n" @@ -11966,18 +12267,22 @@ msgid "- %(o_tmpl)s doesn't exist, leaving untouched" msgstr "- %(o_tmpl)s ez dago, ez da ikutu" #: bin/update:396 +#, fuzzy msgid "removing directory %(src)s and everything underneath" msgstr "%(src)s direktorioa eta bere azpiko guztia ezabatzen" #: bin/update:399 +#, fuzzy msgid "removing %(src)s" msgstr "%(src)s ezabatzen" #: bin/update:403 +#, fuzzy msgid "Warning: couldn't remove %(src)s -- %(rest)s" msgstr "Kontuz: %(src)s -- %(rest)s ezin ezabatu" #: bin/update:408 +#, fuzzy msgid "couldn't remove old file %(pyc)s -- %(rest)s" msgstr "%(pyc)s -- %(rest)s fitxategi zaharra ezin ezabatu" @@ -11986,14 +12291,17 @@ msgid "updating old qfiles" msgstr "qfile zaharrak eguneratzen" #: bin/update:455 +#, fuzzy msgid "Warning! Not a directory: %(dirpath)s" msgstr "Ilara karpeta okerra: %(dirpath)s" #: bin/update:530 +#, fuzzy msgid "message is unparsable: %(filebase)s" msgstr "Mezua ezin izan da arakatu: %(filebase)s" #: bin/update:544 +#, fuzzy msgid "Warning! Deleting empty .pck file: %(pckfile)s" msgstr "Kontuz! empty.pck fitxategia ezabatzen: %(pckfile)s" @@ -12006,10 +12314,12 @@ msgid "Updating Mailman 2.1.4 pending.pck database" msgstr "Mailman 2.14 pending.pck datubasea eguneratzen" #: bin/update:598 +#, fuzzy msgid "Ignoring bad pended data: %(key)s: %(val)s" msgstr "Oker utzitako datuei ez diegu jaramonik egingo: %(key)s: %(val)s" #: bin/update:614 +#, fuzzy msgid "WARNING: Ignoring duplicate pending ID: %(id)s." msgstr "KONTUZ: falta ziren ID: %(id)s duplikatuei ez diegu jaramonik egingo." @@ -12034,6 +12344,7 @@ msgid "done" msgstr "eginda" #: bin/update:691 +#, fuzzy msgid "Updating mailing list: %(listname)s" msgstr "Posta zerrenda eguneratzen: %(listname)s" @@ -12096,6 +12407,7 @@ msgid "No updates are necessary." msgstr "Ez da eguneraketarik behar." #: bin/update:796 +#, fuzzy msgid "" "Downgrade detected, from version %(hexlversion)s to version %(hextversion)s\n" "This is probably not safe.\n" @@ -12107,10 +12419,12 @@ msgstr "" "Irtetzen." #: bin/update:801 +#, fuzzy msgid "Upgrading from version %(hexlversion)s to %(hextversion)s" msgstr "%(hexlversion)s bertsioa %(hextversion)s bertsiora eguneratzen" #: bin/update:810 +#, fuzzy msgid "" "\n" "ERROR:\n" @@ -12282,6 +12596,7 @@ msgstr "" " " #: bin/withlist:175 +#, fuzzy msgid "Unlocking (but not saving) list: %(listname)s" msgstr "Zerrenda desblokeatzen (baina ez gordetzen): %(listname)s" @@ -12290,6 +12605,7 @@ msgid "Finalizing" msgstr "Amaitzen" #: bin/withlist:188 +#, fuzzy msgid "Loading list %(listname)s" msgstr "%(listname)s zerrenda kargatzen" @@ -12302,6 +12618,7 @@ msgid "(unlocked)" msgstr "(irekia)" #: bin/withlist:197 +#, fuzzy msgid "Unknown list: %(listname)s" msgstr "Zerrenda ezezaguna: %(listname)s" @@ -12314,18 +12631,22 @@ msgid "--all requires --run" msgstr "--eskakizun guztiak --abiarazi" #: bin/withlist:266 +#, fuzzy msgid "Importing %(module)s..." msgstr "%(module)s inportatzen..." #: bin/withlist:270 +#, fuzzy msgid "Running %(module)s.%(callable)s()..." msgstr "%(module)s.%(callable)s() abiarazten..." #: bin/withlist:291 +#, fuzzy msgid "The variable `m' is the %(listname)s MailList instance" msgstr "'m' aldagaia izango da %(listname)s Posta Zerrenda instantzia" #: cron/bumpdigests:19 +#, fuzzy msgid "" "Increment the digest volume number and reset the digest number to one.\n" "\n" @@ -12354,6 +12675,7 @@ msgstr "" "aipatzen, denei aplikatzen zaie.\n" #: cron/checkdbs:20 +#, fuzzy msgid "" "Check for pending admin requests and mail the list owners if necessary.\n" "\n" @@ -12379,10 +12701,12 @@ msgid "" msgstr "Oharra: %(discarded)d eskaera zahar automatikoki iraungi da/dira.\n" #: cron/checkdbs:121 +#, fuzzy msgid "%(count)d %(realname)s moderator request(s) waiting" msgstr "%(realname)s zerrendako %(count)d moderatzaileak noiz onartuko zain" #: cron/checkdbs:124 +#, fuzzy msgid "%(realname)s moderator request check result" msgstr "%(realname)s moderatzailearen eskaera frogaren emaitza" @@ -12403,6 +12727,7 @@ msgstr "" "Zain dauden mezuak:" #: cron/checkdbs:169 +#, fuzzy msgid "" "From: %(sender)s on %(date)s\n" "Subject: %(subject)s\n" @@ -12513,6 +12838,7 @@ msgstr "" "\n" #: cron/mailpasswds:19 +#, fuzzy msgid "" "Send password reminders for all lists to all users.\n" "\n" @@ -12566,10 +12892,12 @@ msgid "Password // URL" msgstr "Pasahitza // URL" #: cron/mailpasswds:222 +#, fuzzy msgid "%(host)s mailing list memberships reminder" msgstr "%(host)s posta zerrendetako partaidetza gogorarazpenak" #: cron/nightly_gzip:19 +#, fuzzy msgid "" "Re-generate the Pipermail gzip'd archive flat files.\n" "\n" @@ -12672,8 +13000,8 @@ msgstr "" #~ "Posta zerrenda honetara mezuak bidaltzen\n" #~ " dituzten moderatutako harpidedunei euren mezua baztertzean " #~ "bidaltzen zaien\n" -#~ " oharra." #~ msgid "" diff --git a/messages/fa/LC_MESSAGES/mailman.po b/messages/fa/LC_MESSAGES/mailman.po index fc1f04f2..31b7acab 100644 --- a/messages/fa/LC_MESSAGES/mailman.po +++ b/messages/fa/LC_MESSAGES/mailman.po @@ -65,10 +65,12 @@ msgid "

                      Currently, there are no archives.

                      " msgstr "

                      اکنون هیچ بایگانی‌ای وجود ندارد

                      " #: Mailman/Archiver/HyperArch.py:821 +#, fuzzy msgid "Gzip'd Text%(sz)s" msgstr "متن ÙØ´Ø±Ø¯Ù‡ شده با جی‌زیپ %(sz)s" #: Mailman/Archiver/HyperArch.py:826 +#, fuzzy msgid "Text%(sz)s" msgstr "متن%(sz)s" @@ -147,18 +149,22 @@ msgid "Third" msgstr "سومین" #: Mailman/Archiver/HyperArch.py:938 +#, fuzzy msgid "%(ord)s quarter %(year)i" msgstr "%(ord)s چهارک %(year)i" #: Mailman/Archiver/HyperArch.py:945 +#, fuzzy msgid "%(month)s %(year)i" msgstr "%(month)s %(year)i" #: Mailman/Archiver/HyperArch.py:950 +#, fuzzy msgid "The Week Of Monday %(day)i %(month)s %(year)i" msgstr "Ù‡ÙØªÙ‡â€ŒÛŒ از دوشنبه %(day)i %(month)s %(year)i" #: Mailman/Archiver/HyperArch.py:954 +#, fuzzy msgid "%(day)i %(month)s %(year)i" msgstr "%(day)i %(month)s %(year)i" @@ -168,10 +174,12 @@ msgid "Computing threaded index\n" msgstr "در حال پردازش نمایه‌ی مبحثی\n" #: Mailman/Archiver/HyperArch.py:1319 +#, fuzzy msgid "Updating HTML for article %(seq)s" msgstr "در حال روزآمدسازی زنگام برای مقاله %(seq)s" #: Mailman/Archiver/HyperArch.py:1326 +#, fuzzy msgid "article file %(filename)s is missing!" msgstr "پرونده‌ی مقاله %(filename)s Ú¯Ù… شده است!" @@ -193,6 +201,7 @@ msgid "Pickling archive state into " msgstr "در حال ترشی انداختن وضعیت بایگانی به " #: Mailman/Archiver/pipermail.py:453 +#, fuzzy msgid "Updating index files for archive [%(archive)s]" msgstr "در حال روزآمدسازی پرونده‌های نمایه برای بایگانی [%(archive)s]" @@ -201,6 +210,7 @@ msgid " Thread" msgstr "مبحث" #: Mailman/Archiver/pipermail.py:597 +#, fuzzy msgid "#%(counter)05d %(msgid)s" msgstr "#%(counter)05d %(msgid)s" @@ -238,6 +248,7 @@ msgid "disabled address" msgstr "از کار انداخته" #: Mailman/Bouncer.py:304 +#, fuzzy msgid " The last bounce received from you was dated %(date)s" msgstr "آخرین مورد واگشتی Ø¯Ø±ÛŒØ§ÙØª شده از شما در این تاریخ بود: %(date)s " @@ -265,6 +276,7 @@ msgstr "سرپرست" #: Mailman/Cgi/options.py:101 Mailman/Cgi/private.py:108 #: Mailman/Cgi/rmlist.py:75 Mailman/Cgi/roster.py:59 #: Mailman/Cgi/subscribe.py:67 +#, fuzzy msgid "No such list %(safelistname)s" msgstr "چنین Ùهرستی وجود ندارد %(safelistname)s" @@ -337,6 +349,7 @@ msgstr "" " Ø¯Ø±ÛŒØ§ÙØª خواهند کرد. اعضای متاثر از این مشکل %(rm)r." #: Mailman/Cgi/admin.py:268 +#, fuzzy msgid "%(hostname)s mailing lists - Admin Links" msgstr "Ùهرست پستی %(hostname)s - پیوندهای سرپرست" @@ -349,6 +362,7 @@ msgid "Mailman" msgstr "میل‌من" #: Mailman/Cgi/admin.py:310 +#, fuzzy msgid "" "

                      There currently are no publicly-advertised %(mailmanlink)s\n" " mailing lists on %(hostname)s." @@ -357,6 +371,7 @@ msgstr "" "بر روی %(hostname)s نیست." #: Mailman/Cgi/admin.py:316 +#, fuzzy msgid "" "

                      Below is the collection of publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s. Click on a list\n" @@ -371,6 +386,7 @@ msgid "right " msgstr "راست" #: Mailman/Cgi/admin.py:325 +#, fuzzy msgid "" "To visit the administrators configuration page for an\n" " unadvertised list, open a URL similar to this one, but with a '/' " @@ -414,6 +430,7 @@ msgid "No valid variable name found." msgstr "هیچ نام متغیر معتبری پیدا نشد." #: Mailman/Cgi/admin.py:395 +#, fuzzy msgid "" "%(realname)s Mailing list Configuration Help\n" "
                      %(varname)s Option" @@ -422,6 +439,7 @@ msgstr "" "گزینه‌ی
                      %(varname)s " #: Mailman/Cgi/admin.py:402 +#, fuzzy msgid "Mailman %(varname)s List Option Help" msgstr "راهنمای گزینه‌ی %(varname)s Ùهرست میل‌من" @@ -442,15 +460,18 @@ msgstr "" " " #: Mailman/Cgi/admin.py:431 +#, fuzzy msgid "return to the %(categoryname)s options page." msgstr "بازگشت به ØµÙØ­Ù‡â€ŒÛŒ گزینه‌های %(categoryname)s" # LISTNAME Administration (General Options) #: Mailman/Cgi/admin.py:446 +#, fuzzy msgid "%(realname)s Administration (%(label)s)" msgstr "(%(label)s) سرپرستی %(realname)s" #: Mailman/Cgi/admin.py:447 +#, fuzzy msgid "%(realname)s mailing list administration
                      %(label)s Section" msgstr "سرپرستی Ùهرست پستی %(realname)s
                      بخش %(label)s" @@ -532,6 +553,7 @@ msgid "Value" msgstr "مقدار" #: Mailman/Cgi/admin.py:668 +#, fuzzy msgid "" "Badly formed options entry:\n" " %(record)s" @@ -632,10 +654,12 @@ msgid "Move rule down" msgstr "جابه‌جایی قاعده به پایین" #: Mailman/Cgi/admin.py:870 +#, fuzzy msgid "
                      (Edit %(varname)s)" msgstr "
                      (ویرایش %(varname)s)" #: Mailman/Cgi/admin.py:872 +#, fuzzy msgid "
                      (Details for %(varname)s)" msgstr "
                      (جزئیات برای %(varname)s)" @@ -676,6 +700,7 @@ msgid "(help)" msgstr "(راهنما)" #: Mailman/Cgi/admin.py:930 +#, fuzzy msgid "Find member %(link)s:" msgstr "ÛŒØ§ÙØªÙ† عضو %(link)s:" @@ -688,10 +713,12 @@ msgid "Bad regular expression: " msgstr "عبارت باقاعده‌ی نادرست:" #: Mailman/Cgi/admin.py:1013 +#, fuzzy msgid "%(allcnt)s members total, %(membercnt)s shown" msgstr "در مجموع %(allcnt)s عضو Ú©Ù‡ %(membercnt)s Ù†ÙØ±Ø´Ø§Ù† نشان داده می‌شود" #: Mailman/Cgi/admin.py:1016 +#, fuzzy msgid "%(allcnt)s members total" msgstr "در مجموع %(allcnt)s عضو" @@ -883,6 +910,7 @@ msgstr "" " از بردهای Ùهرست شده‌ی پایین کلیک کنید:" #: Mailman/Cgi/admin.py:1221 +#, fuzzy msgid "from %(start)s to %(end)s" msgstr "از %(start)s تا %(end)s" @@ -1020,6 +1048,7 @@ msgid "Change list ownership passwords" msgstr "تغییر گذرواژه‌ها‌ی مالکیت Ùهرست" #: Mailman/Cgi/admin.py:1364 +#, fuzzy msgid "" "The list administrators are the people who have ultimate control " "over\n" @@ -1136,6 +1165,7 @@ msgstr "نشانی خصومت‌آمیز (نویسه‌های غیرمجاز)" #: Mailman/Cgi/admin.py:1529 Mailman/Cgi/admin.py:1703 bin/add_members:175 #: bin/clone_member:136 bin/sync_members:268 +#, fuzzy msgid "Banned address (matched %(pattern)s)" msgstr "نشانی تحریم‌شده (با %(pattern)s جور در آمد)" @@ -1237,6 +1267,7 @@ msgid "Not subscribed" msgstr "مشترک نشده" #: Mailman/Cgi/admin.py:1773 +#, fuzzy msgid "Ignoring changes to deleted member: %(user)s" msgstr "چشم‌پوشی از تغییرات بر عضو حذ٠شده: %(user)s" @@ -1249,10 +1280,12 @@ msgid "Error Unsubscribing:" msgstr "خطای در لغو اشتراک" #: Mailman/Cgi/admindb.py:235 Mailman/Cgi/admindb.py:248 +#, fuzzy msgid "%(realname)s Administrative Database" msgstr "پایگاه‌داده‌ی سرپرستی %(realname)s" #: Mailman/Cgi/admindb.py:238 +#, fuzzy msgid "%(realname)s Administrative Database Results" msgstr "نتیجه‌های پایگاه‌داده‌ی سرپرستی %(realname)s" @@ -1281,6 +1314,7 @@ msgid "Discard all messages marked Defer" msgstr "تمام پیام‌های نشان‌داده‌شده با به‌تاخیرانداختن را رد Ú©Ù†" #: Mailman/Cgi/admindb.py:298 +#, fuzzy msgid "all of %(esender)s's held messages." msgstr "تمام پیام‌های نگه‌داشته‌شده‌ی %(esender)s" @@ -1301,6 +1335,7 @@ msgid "list of available mailing lists." msgstr "Ùهرستی از Ùهرست‌های پستی موجود." #: Mailman/Cgi/admindb.py:362 +#, fuzzy msgid "You must specify a list name. Here is the %(link)s" msgstr "باید یک نام Ùهرست مشخص کنید. این هم %(link)s" @@ -1382,6 +1417,7 @@ msgid "The sender is now a member of this list" msgstr "ÙØ±Ø³ØªÙ†Ø¯Ù‡ اکنون عضو این Ùهرست است" #: Mailman/Cgi/admindb.py:568 +#, fuzzy msgid "Add %(esender)s to one of these sender filters:" msgstr " %(esender)sرا به یکی از این پالایه‌های مربوط به ÙØ±Ø³ØªÙ†Ø¯Ù‡ Ø¨ÛŒØ§ÙØ²Ø§:" @@ -1402,6 +1438,7 @@ msgid "Rejects" msgstr "پس‌زده‌شده‌ها" #: Mailman/Cgi/admindb.py:584 +#, fuzzy msgid "" "Ban %(esender)s from ever subscribing to this\n" " mailing list" @@ -1418,6 +1455,7 @@ msgstr "" " به تنهایی ببینید، یا می‌توانید " #: Mailman/Cgi/admindb.py:591 +#, fuzzy msgid "view all messages from %(esender)s" msgstr "دیدن همه‌ی پیام‌ها از طر٠%(esender)s " @@ -1496,6 +1534,7 @@ msgid " is already a member" msgstr "پیشاپیش عضو است" #: Mailman/Cgi/admindb.py:973 +#, fuzzy msgid "%(addr)s is banned (matched: %(patt)s)" msgstr "%(addr)s تحریم است (جور در آمد با: %(patt)s)" @@ -1522,8 +1561,8 @@ msgstr "" "

                      به‌یاد داشته‌باشید Ú©Ù‡ کدهای تاییدیه تقریباً پس از مدت\n" " %(days)s روز از درخواست اشتراک، منقضی می‌شوند.\n" " اگر کد تاییدیه شما نیز منقضی شده Ù„Ø·ÙØ§Ù‹ درخواست خود را دوباره Ø¨ÙØ±Ø³ØªÛŒØ¯.\n" -" در غیر این صورت، دوباره رشته‌ی تاییدیه‌ را وارد کنید. " +" در غیر این صورت، دوباره رشته‌ی تاییدیه‌ را وارد کنید. " #: Mailman/Cgi/confirm.py:142 msgid "" @@ -1547,6 +1586,7 @@ msgstr "" "این درخواست لغو گشت." #: Mailman/Cgi/confirm.py:178 +#, fuzzy msgid "System error, bad content: %(content)s" msgstr "خطای سیستم. محتوای نادرست: %(content)s" @@ -1584,6 +1624,7 @@ msgid "Confirm subscription request" msgstr "درخواست اشتراک را تایید کنید" #: Mailman/Cgi/confirm.py:259 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " subscription request to the mailing list %(listname)s. Your\n" @@ -1615,6 +1656,7 @@ msgstr "" "

                      اگر دیگر قصد اشتراک در این Ùهرست را ندارید کلید لغو را بزنید." #: Mailman/Cgi/confirm.py:275 +#, fuzzy msgid "" "Your confirmation is required in order to continue with\n" " the subscription request to the mailing list %(listname)s.\n" @@ -1668,6 +1710,7 @@ msgid "Preferred language:" msgstr "زبان ترجیحی:" #: Mailman/Cgi/confirm.py:317 +#, fuzzy msgid "Subscribe to list %(listname)s" msgstr "اشتراک در Ùهرست %(listname)s" @@ -1684,6 +1727,7 @@ msgid "Awaiting moderator approval" msgstr "در انتظار تایید میان‌دار" #: Mailman/Cgi/confirm.py:386 +#, fuzzy msgid "" " You have successfully confirmed your subscription request to " "the\n" @@ -1716,6 +1760,7 @@ msgid "You are already a member of this mailing list!" msgstr "شما پیشاپیش، عضو این Ùهرست پستی هستید!" #: Mailman/Cgi/confirm.py:400 +#, fuzzy msgid "" "You are currently banned from subscribing to\n" " this list. If you think this restriction is erroneous, please\n" @@ -1740,6 +1785,7 @@ msgid "Subscription request confirmed" msgstr "درخواست اشتراک، تایید شد." #: Mailman/Cgi/confirm.py:418 +#, fuzzy msgid "" " You have successfully confirmed your subscription request for\n" " \"%(addr)s\" to the %(listname)s mailing list. A separate\n" @@ -1768,6 +1814,7 @@ msgid "Unsubscription request confirmed" msgstr "درخواست لغو اشتراک، تایید شد" #: Mailman/Cgi/confirm.py:469 +#, fuzzy msgid "" " You have successfully unsubscribed from the %(listname)s " "mailing\n" @@ -1789,6 +1836,7 @@ msgid "Not available" msgstr "موجود نیست" #: Mailman/Cgi/confirm.py:498 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " unsubscription request from the mailing list %(listname)s. " @@ -1832,6 +1880,7 @@ msgid "You have canceled your change of address request." msgstr "شما درخواست تغییر نشانی خود را لغو کرده‌اید." #: Mailman/Cgi/confirm.py:555 +#, fuzzy msgid "" "%(newaddr)s is banned from subscribing to the\n" " %(realname)s list. If you think this restriction is erroneous,\n" @@ -1842,18 +1891,23 @@ msgstr "" "با مالکان لیست به نشانی %(owneraddr)s تماس بگیرید." #: Mailman/Cgi/confirm.py:560 +#, fuzzy msgid "" "%(newaddr)s is already a member of\n" " the %(realname)s list. It is possible that you are attempting\n" " to confirm a request for an address that has already been\n" " subscribed." msgstr "" +"رشته‌ی تاییدیه نادرست است. احتمال دارد\n" +" شما دارید درخواستی را تایید می‌کنید Ú©Ù‡\n" +" پیش‌تر لغو اشتراک شده است." #: Mailman/Cgi/confirm.py:567 msgid "Change of address request confirmed" msgstr "درخواست تغییر نشانی تایید شد" #: Mailman/Cgi/confirm.py:571 +#, fuzzy msgid "" " You have successfully changed your address on the %(listname)s\n" " mailing list from %(oldaddr)s to %(newaddr)s. " @@ -1876,6 +1930,7 @@ msgid "globally" msgstr "سراسری" #: Mailman/Cgi/confirm.py:605 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " change of address request for the mailing list %(listname)s. " @@ -1953,6 +2008,7 @@ msgid "Posted message canceled" msgstr "پیام ÙØ±Ø³ØªØ§Ø¯Ù‡ شده لغو شد" #: Mailman/Cgi/confirm.py:685 +#, fuzzy msgid "" " You have successfully canceled the posting of your message with\n" " the Subject: header %(subject)s to the mailing list\n" @@ -1973,6 +2029,7 @@ msgid "" msgstr "" #: Mailman/Cgi/confirm.py:735 +#, fuzzy msgid "" "Your confirmation is required in order to cancel the\n" " posting of your message to the mailing list %(listname)s:\n" @@ -2017,18 +2074,24 @@ msgid "Membership re-enabled." msgstr "عضویت، دوباره به کار Ø§ÙØªØ§Ø¯" #: Mailman/Cgi/confirm.py:798 +#, fuzzy msgid "" " You have successfully re-enabled your membership in the\n" " %(listname)s mailing list. You can now visit your member options page.\n" " " msgstr "" +" شما با موÙقیت اشتراک خود را از این Ùهرست پستی لغو کردید: " +"%(listname)s \n" +" اکنون می‌توانیداز ØµÙØ­Ù‡ اطلاعات اصلی " +"Ùهرست بازدید کنید." #: Mailman/Cgi/confirm.py:810 msgid "Re-enable mailing list membership" msgstr "به کار انداختن مجدد عضویت در Ùهرست پستی " #: Mailman/Cgi/confirm.py:827 +#, fuzzy msgid "" "We're sorry, but you have already been unsubscribed\n" " from this mailing list. To re-subscribe, please visit the\n" @@ -2093,10 +2156,12 @@ msgid "administrative list overview" msgstr "مرور Ú©Ù„ÛŒ سرپرستی Ùهرست" #: Mailman/Cgi/create.py:115 +#, fuzzy msgid "List name must not include \"@\": %(safelistname)s" -msgstr "" +msgstr "نام Ùهرست: %(listname)s" #: Mailman/Cgi/create.py:122 +#, fuzzy msgid "List already exists: %(safelistname)s" msgstr "Ùهرست پیشاپیش وجود دارد: %(safelistname)s" @@ -2131,18 +2196,22 @@ msgid "You are not authorized to create new mailing lists" msgstr "شما اجازه‌ی ایجاد Ùهرست‌های پستی جدید را ندارید" #: Mailman/Cgi/create.py:182 +#, fuzzy msgid "Unknown virtual host: %(safehostname)s" msgstr "میزبان مجازی ناشناخته: %(safehostname)s" #: Mailman/Cgi/create.py:218 bin/newlist:219 +#, fuzzy msgid "Bad owner email address: %(s)s" msgstr "نشانی رایانامه‌ی مالک نادرست است: %(s)s" #: Mailman/Cgi/create.py:223 bin/newlist:182 bin/newlist:223 +#, fuzzy msgid "List already exists: %(listname)s" msgstr "Ùهرست پیشاپیش وجود دارد%(listname)s" #: Mailman/Cgi/create.py:231 bin/newlist:217 +#, fuzzy msgid "Illegal list name: %(s)s" msgstr "نام غیرمجاز برای Ùهرست: %(s)s " @@ -2153,19 +2222,23 @@ msgid "" msgstr "" #: Mailman/Cgi/create.py:273 bin/newlist:265 +#, fuzzy msgid "Your new mailing list: %(listname)s" -msgstr "" +msgstr "در حال روزآمدسازی Ùهرست پستی: %(listname)s" #: Mailman/Cgi/create.py:282 msgid "Mailing list creation results" msgstr "نتایج ایجاد Ùهرست پستی" #: Mailman/Cgi/create.py:288 +#, fuzzy msgid "" "You have successfully created the mailing list\n" " %(listname)s and notification has been sent to the list owner\n" " %(owner)s. You can now:" msgstr "" +"شما این Ùهرست پستی را با موÙقیت حذ٠کردید:\n" +" %(listname)s." #: Mailman/Cgi/create.py:292 msgid "Visit the list's info page" @@ -2180,8 +2253,9 @@ msgid "Create another list" msgstr "ایجاد یک Ùهرست دیگر" #: Mailman/Cgi/create.py:312 +#, fuzzy msgid "Create a %(hostname)s Mailing List" -msgstr "" +msgstr "مرور کلیه‌ی Ùهرست‌های پستی %(hostname)s " #: Mailman/Cgi/create.py:321 Mailman/Cgi/rmlist.py:219 #: Mailman/Gui/Bounce.py:196 Mailman/htmlformat.py:360 @@ -2420,14 +2494,18 @@ msgid "HTML successfully updated." msgstr "" #: Mailman/Cgi/listinfo.py:89 +#, fuzzy msgid "%(hostname)s Mailing Lists" -msgstr "" +msgstr "Ùهرست پستی ## %(listname)s" #: Mailman/Cgi/listinfo.py:127 +#, fuzzy msgid "" "

                      There currently are no publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s." msgstr "" +"

                      اکنون هیچ Ùهرست پستی عمومی %(mailmanlink)s تبلیغ‌شده‌ای \n" +"بر روی %(hostname)s نیست." #: Mailman/Cgi/listinfo.py:131 msgid "" @@ -2444,12 +2522,19 @@ msgid "right" msgstr "راست" #: Mailman/Cgi/listinfo.py:140 +#, fuzzy msgid "" " To visit the general information page for an unadvertised list,\n" " open a URL similar to this one, but with a '/' and the %(adj)s\n" " list name appended.\n" "

                      List administrators, you can visit " msgstr "" +"برای دیدن ØµÙØ­Ù‡â€ŒÛŒ تنظیمات سرپرست‌ها برای یک Ùهرست\n" +" تبلیغ نشده، یک نشانی وبی مثل این یکی باز کنید ولی یک '/' Ùˆ \n" +" نام %(extra)s Ùهرست را به آن Ø¨ÛŒØ§ÙØ²Ø§ÛŒÛŒØ¯. اگر اجازه لازم را دارید\n" +" همچنین می‌توانید یک Ùهرست جدید بسازی.\n" +"\n" +"

                      اطلاعات عمومی Ùهرست را می‌توان این‌جا دید:" #: Mailman/Cgi/listinfo.py:145 msgid "the list admin overview page" @@ -2499,8 +2584,9 @@ msgstr "نشانی رایا‌نامه‌ی غیرمجاز" #: Mailman/Cgi/options.py:183 Mailman/Cgi/options.py:240 #: Mailman/Cgi/options.py:264 +#, fuzzy msgid "No such member: %(safeuser)s." -msgstr "" +msgstr "چنین Ùهرستی وجود ندارد: %(listname)s" #: Mailman/Cgi/options.py:208 #, fuzzy @@ -2587,14 +2673,16 @@ msgid "" msgstr "" #: Mailman/Cgi/options.py:468 +#, fuzzy msgid "The new address is already a member: %(newaddr)s" -msgstr "" +msgstr "%(newaddr)s پیشاپیش، عضو این Ùهرست می‌باشد." #: Mailman/Cgi/options.py:474 msgid "Addresses may not be blank" msgstr "نشانی‌ها نمی تواند خالی باشد" #: Mailman/Cgi/options.py:488 +#, fuzzy msgid "A confirmation message has been sent to %(newaddr)s. " msgstr "یک پیام تایید برای %(newaddr)s ÙØ±Ø³ØªØ§Ø¯Ù‡ شد." @@ -2607,15 +2695,20 @@ msgid "Illegal email address provided" msgstr "نشانی رایا‌نامه وارد شده، غیرمجاز است" #: Mailman/Cgi/options.py:501 +#, fuzzy msgid "%(newaddr)s is already a member of the list." msgstr "%(newaddr)s پیشاپیش، عضو این Ùهرست می‌باشد." #: Mailman/Cgi/options.py:504 +#, fuzzy msgid "" "%(newaddr)s is banned from this list. If you\n" " think this restriction is erroneous, please contact\n" " the list owners at %(owneraddr)s." msgstr "" +"%(newaddr)s از اشتراک در Ùهرست %(realname)s تحریم شده‌است \n" +"اگر می‌پندارید این محدودیت اشتباه شده است، \n" +"با مالکان لیست به نشانی %(owneraddr)s تماس بگیرید." #: Mailman/Cgi/options.py:515 msgid "Member name successfully changed. " @@ -2675,6 +2768,7 @@ msgid "" msgstr "" #: Mailman/Cgi/options.py:623 +#, fuzzy msgid "" "You have been successfully unsubscribed from the\n" " mailing list %(fqdn_listname)s. If you were receiving digest\n" @@ -2780,6 +2874,7 @@ msgid "" msgstr "" #: Mailman/Cgi/options.py:956 +#, fuzzy msgid "%(realname)s list: member options login page" msgstr "Ùهرست %(realname)s: ØµÙØ­Ù‡â€ŒÛŒ ورود به گزینه‌های اعضا" @@ -2788,10 +2883,12 @@ msgid "email address and " msgstr "نشانی رایا‌نامه Ùˆ " #: Mailman/Cgi/options.py:960 +#, fuzzy msgid "%(realname)s list: member options for user %(safeuser)s" -msgstr "" +msgstr "Ùهرست %(realname)s: ØµÙØ­Ù‡â€ŒÛŒ ورود به گزینه‌های اعضا" #: Mailman/Cgi/options.py:986 +#, fuzzy msgid "" "In order to change your membership option, you must\n" " first log in by giving your %(extra)smembership password in the section\n" @@ -2861,6 +2958,7 @@ msgid "" msgstr "" #: Mailman/Cgi/options.py:1140 +#, fuzzy msgid "Requested topic is not valid: %(topicname)s" msgstr "Ø³Ø±ÙØµÙ„ درخواست‌شده، معتبر نیست: %(topicname)s" @@ -2889,8 +2987,9 @@ msgid "Private archive - \"./\" and \"../\" not allowed in URL." msgstr "" #: Mailman/Cgi/private.py:109 +#, fuzzy msgid "Private Archive Error - %(msg)s" -msgstr "" +msgstr "خطای بایگانی خصوصی" #: Mailman/Cgi/private.py:157 msgid "" @@ -2926,6 +3025,7 @@ msgid "Mailing list deletion results" msgstr "نتایج حذ٠Ùهرست پستی" #: Mailman/Cgi/rmlist.py:187 +#, fuzzy msgid "" "You have successfully deleted the mailing list\n" " %(listname)s." @@ -2942,8 +3042,9 @@ msgid "" msgstr "" #: Mailman/Cgi/rmlist.py:208 +#, fuzzy msgid "Permanently remove mailing list %(realname)s" -msgstr "" +msgstr "درخواست ÙØ±Ø³ØªØ§Ø¯Ù‡â€ŒØ´Ø¯Ù‡ به Ùهرست پستی %(realname)s پس‌زده‌شد" #: Mailman/Cgi/rmlist.py:209 #, fuzzy @@ -2994,8 +3095,9 @@ msgid "Invalid options to CGI script" msgstr "گزینه‌های نامعتبر برای اسکریپت CGI" #: Mailman/Cgi/roster.py:118 +#, fuzzy msgid "%(realname)s roster authentication failed." -msgstr "" +msgstr "اصالت‌سنجی شکست خورد." #: Mailman/Cgi/subscribe.py:128 msgid "You must supply a valid email address." @@ -3056,6 +3158,7 @@ msgid "" msgstr "" #: Mailman/Cgi/subscribe.py:262 +#, fuzzy msgid "" "The email address you supplied is banned from this\n" " mailing list. If you think this restriction is erroneous, please\n" @@ -3133,6 +3236,7 @@ msgid "This list only supports digest delivery." msgstr "این Ùهرست، Ùقط حالت رساندن یک‌جا را پشتیبانی می‌کند." #: Mailman/Cgi/subscribe.py:344 +#, fuzzy msgid "You have been successfully subscribed to the %(realname)s mailing list." msgstr "شما با موÙقیت در Ùهرست پستی the %(realname)s مشترک شدید." @@ -3177,6 +3281,7 @@ msgstr "" "لغو اشتراک کرده یا تغییر داده‌اید؟" #: Mailman/Commands/cmd_confirm.py:69 +#, fuzzy msgid "" "You are currently banned from subscribing to this list. If you think this\n" "restriction is erroneous, please contact the list owners at\n" @@ -3244,26 +3349,32 @@ msgid "n/a" msgstr "موجود نیست" #: Mailman/Commands/cmd_info.py:44 +#, fuzzy msgid "List name: %(listname)s" msgstr "نام Ùهرست: %(listname)s" #: Mailman/Commands/cmd_info.py:45 +#, fuzzy msgid "Description: %(description)s" msgstr "توضیح: %(description)s" #: Mailman/Commands/cmd_info.py:46 +#, fuzzy msgid "Postings to: %(postaddr)s" msgstr "ÙØ±Ø³ØªØ§Ø¯Ù† به: %(postaddr)s" #: Mailman/Commands/cmd_info.py:47 +#, fuzzy msgid "List Helpbot: %(requestaddr)s" -msgstr "" +msgstr " درخواست‌های ÙØ±Ø³ØªØ§Ø¯Ù‡ شده به: %(requestaddr)s" #: Mailman/Commands/cmd_info.py:48 +#, fuzzy msgid "List Owners: %(owneraddr)s" msgstr "مالکان Ùهرست: %(owneraddr)s" #: Mailman/Commands/cmd_info.py:49 +#, fuzzy msgid "More information: %(listurl)s" msgstr "اطلاعات بیشتر: %(listurl)s" @@ -3283,18 +3394,22 @@ msgid "" msgstr "" #: Mailman/Commands/cmd_lists.py:44 +#, fuzzy msgid "Public mailing lists at %(hostname)s:" msgstr "Ùهرست‌های پستی همگانی در: %(hostname)s:" #: Mailman/Commands/cmd_lists.py:66 +#, fuzzy msgid "%(i)3d. List name: %(realname)s" -msgstr "" +msgstr "نام Ùهرست: %(listname)s" #: Mailman/Commands/cmd_lists.py:67 +#, fuzzy msgid " Description: %(description)s" msgstr "توضیح: %(description)s" #: Mailman/Commands/cmd_lists.py:68 +#, fuzzy msgid " Requests to: %(requestaddr)s" msgstr " درخواست‌های ÙØ±Ø³ØªØ§Ø¯Ù‡ شده به: %(requestaddr)s" @@ -3317,12 +3432,14 @@ msgid "" msgstr "" #: Mailman/Commands/cmd_password.py:51 Mailman/Commands/cmd_password.py:66 +#, fuzzy msgid "Your password is: %(password)s" msgstr "گذرواژه شما این است: %(password)s" #: Mailman/Commands/cmd_password.py:57 Mailman/Commands/cmd_password.py:72 #: Mailman/Commands/cmd_password.py:95 Mailman/Commands/cmd_password.py:121 #: Mailman/Commands/cmd_set.py:149 Mailman/Commands/cmd_set.py:219 +#, fuzzy msgid "You are not a member of the %(listname)s mailing list" msgstr "شما عضو Ùهرست پستی %(listname)s نیستید." @@ -3436,8 +3553,9 @@ msgid "" msgstr "" #: Mailman/Commands/cmd_set.py:122 +#, fuzzy msgid "Bad set command: %(subcmd)s" -msgstr "" +msgstr "دستور نادرست: %(command)s" #: Mailman/Commands/cmd_set.py:151 msgid "Your current option settings:" @@ -3456,8 +3574,9 @@ msgid "on" msgstr "روشن" #: Mailman/Commands/cmd_set.py:154 +#, fuzzy msgid " ack %(onoff)s" -msgstr "" +msgstr "پنهان کردن %(onoff)s" #: Mailman/Commands/cmd_set.py:160 msgid " digest plain" @@ -3497,18 +3616,22 @@ msgid " %(status)s (%(how)s on %(date)s)" msgstr "" #: Mailman/Commands/cmd_set.py:192 +#, fuzzy msgid " myposts %(onoff)s" -msgstr "" +msgstr "موارد تکراری %(onoff)s" #: Mailman/Commands/cmd_set.py:195 +#, fuzzy msgid " hide %(onoff)s" msgstr "پنهان کردن %(onoff)s" #: Mailman/Commands/cmd_set.py:199 +#, fuzzy msgid " duplicates %(onoff)s" msgstr "موارد تکراری %(onoff)s" #: Mailman/Commands/cmd_set.py:203 +#, fuzzy msgid " reminders %(onoff)s" msgstr " یادآوری‌کننده‌ها %(onoff)s" @@ -3579,14 +3702,16 @@ msgid "" msgstr "" #: Mailman/Commands/cmd_subscribe.py:62 +#, fuzzy msgid "Bad digest specifier: %(arg)s" -msgstr "" +msgstr "تنظیم گزینه‌ی حالت یک‌جا" #: Mailman/Commands/cmd_subscribe.py:92 msgid "No valid address found to subscribe" msgstr "هیچ نشانی معتبری برای مشترک کردن پیدا نشد" #: Mailman/Commands/cmd_subscribe.py:113 +#, fuzzy msgid "" "The email address you supplied is banned from this mailing list.\n" "If you think this restriction is erroneous, please contact the list\n" @@ -3622,6 +3747,7 @@ msgid "This list only supports digest subscriptions!" msgstr "این Ùهرست ØµØ±ÙØ§Ù‹ اشتراک در حالت یک‌جا را پشتیبانی می‌کند." #: Mailman/Commands/cmd_subscribe.py:146 +#, fuzzy msgid "" "Your subscription request has been forwarded to the list administrator\n" "at %(listowner)s for review." @@ -3648,6 +3774,7 @@ msgid "" msgstr "" #: Mailman/Commands/cmd_unsubscribe.py:62 +#, fuzzy msgid "%(address)s is not a member of the %(listname)s mailing list" msgstr "%(address)s عضوی از Ùهرست پستی %(listname)s نیست." @@ -3895,16 +4022,19 @@ msgid " (Digest mode)" msgstr " (حالت یک‌جا)" #: Mailman/Deliverer.py:79 +#, fuzzy msgid "Welcome to the \"%(realname)s\" mailing list%(digmode)s" msgstr "به Ùهرست پستی \"%(realname)s\" خوش آمدید %(digmode)s" #: Mailman/Deliverer.py:89 +#, fuzzy msgid "You have been unsubscribed from the %(realname)s mailing list" msgstr "شما از Ùهرست پستی %(realname)s لغو اشتراک شده‌اید." #: Mailman/Deliverer.py:116 +#, fuzzy msgid "%(listfullname)s mailing list reminder" -msgstr "" +msgstr "Ùهرست پستی ## %(listname)s" #: Mailman/Deliverer.py:144 msgid "No reason given" @@ -3932,8 +4062,9 @@ msgid "" msgstr "" #: Mailman/Deliverer.py:221 +#, fuzzy msgid "%(listname)s mailing list probe message" -msgstr "" +msgstr "Ùهرست پستی ## %(listname)s" #: Mailman/Errors.py:123 msgid "For some unknown reason" @@ -4107,8 +4238,8 @@ msgid "" " membership.\n" "\n" "

                      You can control both the\n" -" number\n" +" number\n" " of reminders the member will receive and the\n" " No those messages too will get discarded. You may " "want\n" " to set up an\n" -" autoresponse\n" +" autoresponse\n" " message for email to the -owner and -admin address." msgstr "" @@ -4378,8 +4509,8 @@ msgid "" "Use this option to remove each message attachment that does\n" " not have a matching content type. Requirements and formats " "are\n" -" exactly like filter_mime_types.\n" "\n" "

                      Note: if you add entries to this list but don't add\n" @@ -4567,10 +4698,12 @@ msgid "There was no digest to send." msgstr "هیچ رایانامه‌ی یک‌جایی برای ÙØ±Ø³ØªØ§Ø¯Ù†ØŒ موجود نبود." #: Mailman/Gui/GUIBase.py:173 +#, fuzzy msgid "Invalid value for variable: %(property)s" msgstr "مقدار نامعتبر برای متغیر: %(property)s" #: Mailman/Gui/GUIBase.py:178 +#, fuzzy msgid "Bad email address for option %(property)s: %(error)s" msgstr "نشانی رایانامه‌ی نادرست برای گزینه‌ی %(property)s: %(error)s" @@ -4668,8 +4801,8 @@ msgid "" "

                      In order to split the list ownership duties into\n" " administrators and moderators, you must\n" " set a separate moderator password,\n" -" and also provide the email\n" +" and also provide the email\n" " addresses of the list moderators. Note that the field you\n" " are changing here specifies the list administrators." msgstr "" @@ -4916,13 +5049,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -4947,8 +5080,8 @@ msgstr "" msgid "" "This is the address set in the Reply-To: header\n" " when the reply_goes_to_list\n" +" href=\"?VARHELP=general/" +"reply_goes_to_list\">reply_goes_to_list\n" " option is set to Explicit address.\n" "\n" "

                      There are many reasons not to introduce or override the\n" @@ -4956,13 +5089,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5021,8 +5154,8 @@ msgid "" " member list addresses, but rather to the owner of those member\n" " lists. In that case, the value of this setting is appended to\n" " the member's account name for such notices. `-owner' is the\n" -" typical choice. This setting has no effect when \"umbrella_list" -"\"\n" +" typical choice. This setting has no effect when " +"\"umbrella_list\"\n" " is \"No\"." msgstr "" @@ -5885,8 +6018,8 @@ msgid "" " either individually or as a group. Any\n" " posting from a non-member who is not explicitly accepted,\n" " rejected, or discarded, will have their posting filtered by the\n" -" general\n" +" general\n" " non-member rules.\n" "\n" "

                      In the text boxes below, add one address per line; start the\n" @@ -5947,8 +6080,8 @@ msgid "" "If a member posts this many times, within a period of time\n" " the member is automatically moderated. Use 0 to disable. " "See\n" -" member_verbosity_interval for details on the time " "period.\n" "\n" @@ -6511,8 +6644,8 @@ msgid "" "

                      The body of the message can also be optionally scanned for\n" " Subject: and Keywords: headers, as\n" " specified by the topics_bodylines_limit\n" +" href=\"?VARHELP=topics/" +"topics_bodylines_limit\">topics_bodylines_limit\n" " configuration variable." msgstr "" @@ -6721,6 +6854,7 @@ msgid "%(listinfo_link)s list run by %(owner_link)s" msgstr "لیست %(listinfo_link)s با سرپرستی %(owner_link)s" #: Mailman/HTMLFormatter.py:57 +#, fuzzy msgid "%(realname)s administrative interface" msgstr "رابط کاربری سرپرستی %(realname)s" @@ -6729,6 +6863,7 @@ msgid " (requires authorization)" msgstr "(مستلزم اجازه‌دهی)" #: Mailman/HTMLFormatter.py:61 +#, fuzzy msgid "Overview of all %(hostname)s mailing lists" msgstr "مرور کلیه‌ی Ùهرست‌های پستی %(hostname)s " @@ -6759,6 +6894,7 @@ msgid "; it was disabled for unknown reasons" msgstr "; به دلایل ناشناخته، از کار انداخته‌شده" #: Mailman/HTMLFormatter.py:151 +#, fuzzy msgid "Note: your list delivery is currently disabled%(reason)s." msgstr "" "یادداشت: امکان رساندن Ùهرست شما اکنون از کار انداخته شده زیرا: %(reason)s." @@ -6838,18 +6974,25 @@ msgstr "" " نتیجه‌ی تصمیم میان‌دار با رایانامه به اطلاع‌تان خواهد رسید." #: Mailman/HTMLFormatter.py:208 +#, fuzzy msgid "" "This is %(also)sa private list, which means that the\n" " list of members is not available to non-members." msgstr "" +"این %(also)s یک Ùهرست همگانی است Ú©Ù‡ یعنی Ùهرست اعضای \n" +"آن برای هرکس Ùˆ ناکسی دسترسی‌پذیر است." #: Mailman/HTMLFormatter.py:211 +#, fuzzy msgid "" "This is %(also)sa hidden list, which means that the\n" " list of members is available only to the list administrator." msgstr "" +"این %(also)s یک Ùهرست همگانی است Ú©Ù‡ یعنی Ùهرست اعضای \n" +"آن برای هرکس Ùˆ ناکسی دسترسی‌پذیر است." #: Mailman/HTMLFormatter.py:214 +#, fuzzy msgid "" "This is %(also)sa public list, which means that the\n" " list of members list is available to everyone." @@ -6866,6 +7009,7 @@ msgstr "" " Ú©Ù‡ به آسانی توسط Ù‡Ø±Ø²ÙØ±Ø³Øªâ€ŒÙ‡Ø§ قابل تشخیص نباشد)." #: Mailman/HTMLFormatter.py:222 +#, fuzzy msgid "" "

                      (Note that this is an umbrella list, intended to\n" " have only other mailing lists as members. Among other things,\n" @@ -6882,6 +7026,7 @@ msgid "either " msgstr "یا " #: Mailman/HTMLFormatter.py:256 +#, fuzzy msgid "" "To unsubscribe from %(realname)s, get a password reminder,\n" " or change your subscription options %(either)senter your " @@ -6915,12 +7060,14 @@ msgstr "" " نشانی رایا‌نامه‌تان پرسیده خواهد شد." #: Mailman/HTMLFormatter.py:277 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " members.)" msgstr "(%(which)s تنها برای اعضای Ùهرست دسترسی‌پذیر است.)" #: Mailman/HTMLFormatter.py:281 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " administrator.)" @@ -7099,12 +7246,14 @@ msgid "Posting to a moderated newsgroup" msgstr "ÙØ±Ø³ØªØ§Ø¯Ù† به گروه خبری میان‌داری شده" #: Mailman/Handlers/Hold.py:252 +#, fuzzy msgid "Your message to %(listname)s awaits moderator approval" msgstr "پیام شما به %(listname)s در انتظار تایید میان‌دار است" #: Mailman/Handlers/Hold.py:271 +#, fuzzy msgid "%(listname)s post from %(sender)s requires approval" -msgstr "" +msgstr "ÙØ±Ø³ØªØ§Ø¯Ù† به یک Ùهرست محرمانه توسط این ÙØ±Ø³ØªÙ†Ø¯Ù‡ØŒ نیاز به تایید دارد" #: Mailman/Handlers/Hold.py:278 msgid "" @@ -7175,8 +7324,9 @@ msgid "The attached message has been automatically discarded." msgstr "پیام پیوست‌شده، به طور خودکار رد شد." #: Mailman/Handlers/Replybot.py:75 +#, fuzzy msgid "Auto-response for your message to the \"%(realname)s\" mailing list" -msgstr "" +msgstr "به Ùهرست پستی \"%(realname)s\" خوش آمدید %(digmode)s" #: Mailman/Handlers/Replybot.py:108 msgid "The Mailman Replybot" @@ -7298,6 +7448,7 @@ msgid "End of " msgstr "پایان " #: Mailman/ListAdmin.py:309 +#, fuzzy msgid "Posting of your message titled \"%(subject)s\"" msgstr "ÙØ±Ø³ØªØ§Ø¯Ù† پیام شما با عنوان \"%(subject)s\"" @@ -7310,6 +7461,7 @@ msgid "Forward of moderated message" msgstr "پیش‌سوکردن پیام میان‌داری شده" #: Mailman/ListAdmin.py:405 +#, fuzzy msgid "New subscription request to list %(realname)s from %(addr)s" msgstr "درخواست اشتراک جدید در Ùهرست %(realname)s از طر٠%(addr)s" @@ -7323,6 +7475,7 @@ msgid "via admin approval" msgstr "ادامه‌دادن انتظار برای تایید" #: Mailman/ListAdmin.py:466 +#, fuzzy msgid "New unsubscription request from %(realname)s by %(addr)s" msgstr "درخواست لغو اشتراک جدید از طر٠%(realname)s توسط %(addr)s" @@ -7335,6 +7488,7 @@ msgid "Original Message" msgstr "پیام اصلی" #: Mailman/ListAdmin.py:526 +#, fuzzy msgid "Request to mailing list %(realname)s rejected" msgstr "درخواست ÙØ±Ø³ØªØ§Ø¯Ù‡â€ŒØ´Ø¯Ù‡ به Ùهرست پستی %(realname)s پس‌زده‌شد" @@ -7356,12 +7510,14 @@ msgid "" msgstr "" #: Mailman/MTA/Manual.py:82 +#, fuzzy msgid "## %(listname)s mailing list" msgstr "Ùهرست پستی ## %(listname)s" #: Mailman/MTA/Manual.py:99 +#, fuzzy msgid "Mailing list creation request for list %(listname)s" -msgstr "" +msgstr "نتایج ایجاد Ùهرست پستی" #: Mailman/MTA/Manual.py:113 msgid "" @@ -7385,10 +7541,12 @@ msgid "" msgstr "" #: Mailman/MTA/Manual.py:142 +#, fuzzy msgid "Mailing list removal request for list %(listname)s" -msgstr "" +msgstr "نتایج ایجاد Ùهرست پستی" #: Mailman/MTA/Postfix.py:442 +#, fuzzy msgid "checking permissions on %(file)s" msgstr "در حال بررسی اجازه‌های %(file)s" @@ -7406,6 +7564,7 @@ msgid "(fixing)" msgstr "(در حال تعمیر)" #: Mailman/MTA/Postfix.py:470 +#, fuzzy msgid "checking ownership of %(dbfile)s" msgstr "در حال بررسی مالکیت %(dbfile)s" @@ -7418,10 +7577,12 @@ msgid "%(dbfile)s permissions must be 0664 (got %(octmode)s)" msgstr "" #: Mailman/MailList.py:219 +#, fuzzy msgid "Your confirmation is required to join the %(listname)s mailing list" msgstr "برای پیوستن به Ùهرست پستی %(listname)s نیاز به تایید شما وجود دارد." #: Mailman/MailList.py:230 +#, fuzzy msgid "Your confirmation is required to leave the %(listname)s mailing list" msgstr "برای ترک Ùهرست پستی %(listname)s نیاز به تایید شما وجود دارد." @@ -7430,10 +7591,12 @@ msgid " from %(remote)s" msgstr "" #: Mailman/MailList.py:1043 +#, fuzzy msgid "subscriptions to %(realname)s require moderator approval" msgstr "برای اشتراک در %(realname)s نیاز به تایید میان‌دار است" #: Mailman/MailList.py:1125 bin/add_members:299 +#, fuzzy msgid "%(realname)s subscription notification" msgstr "آگاه‌سازی از اشتراک%(realname)s " @@ -7442,8 +7605,9 @@ msgid "unsubscriptions require moderator approval" msgstr "برای لغو اشتراک، تایید میان‌دار لازم است." #: Mailman/MailList.py:1166 +#, fuzzy msgid "%(realname)s unsubscribe notification" -msgstr "" +msgstr "آگاه‌سازی از اشتراک%(realname)s " #: Mailman/MailList.py:1328 #, fuzzy @@ -7461,6 +7625,7 @@ msgid "via web confirmation" msgstr "کد تاییدیه نادرست" #: Mailman/MailList.py:1396 +#, fuzzy msgid "subscriptions to %(name)s require administrator approval" msgstr "برای اشتراک در %(name)s نیاز به تایید سرپرست است." @@ -7715,6 +7880,7 @@ msgid "" msgstr "" #: bin/add_members:162 bin/add_members:172 +#, fuzzy msgid "Already a member: %(member)s" msgstr "پیشاپیش عضو است: %(member)s" @@ -7723,12 +7889,14 @@ msgid "Bad/Invalid email address: blank line" msgstr "" #: bin/add_members:180 +#, fuzzy msgid "Bad/Invalid email address: %(member)s" -msgstr "" +msgstr "نشانی رایانامه‌ی نادرست یا نا معتبر" #: bin/add_members:182 +#, fuzzy msgid "Hostile address (illegal characters): %(member)s" -msgstr "" +msgstr "نشانی خصومت‌آمیز (نویسه‌های غیرمجاز)" #: bin/add_members:185 #, fuzzy @@ -7736,6 +7904,7 @@ msgid "Invited: %(member)s" msgstr "مشترک شد: %(member)s" #: bin/add_members:187 +#, fuzzy msgid "Subscribed: %(member)s" msgstr "مشترک شد: %(member)s" @@ -7758,6 +7927,7 @@ msgstr "" #: bin/add_members:261 bin/config_list:110 bin/export.py:271 bin/find_member:97 #: bin/inject:91 bin/list_admins:90 bin/list_members:252 bin/sync_members:222 #: cron/bumpdigests:86 +#, fuzzy msgid "No such list: %(listname)s" msgstr "چنین Ùهرستی وجود ندارد: %(listname)s" @@ -7821,6 +7991,7 @@ msgid "listname is required" msgstr "نام Ùهرست، لازم است" #: bin/arch:143 bin/change_pw:107 bin/config_list:257 +#, fuzzy msgid "" "No such list \"%(listname)s\"\n" "%(e)s" @@ -7918,12 +8089,14 @@ msgid "Empty list passwords are not allowed" msgstr "" #: bin/change_pw:181 +#, fuzzy msgid "New %(listname)s password: %(notifypassword)s" -msgstr "" +msgstr "گذرواژه اولیه Ùهرست:" #: bin/change_pw:190 +#, fuzzy msgid "Your new %(listname)s list password" -msgstr "" +msgstr "گذرواژه اولیه Ùهرست:" #: bin/change_pw:191 msgid "" @@ -7986,6 +8159,7 @@ msgid "List:" msgstr "Ùهرست:" #: bin/check_db:148 +#, fuzzy msgid " %(file)s: okay" msgstr " %(file)s: خوب است" @@ -8001,8 +8175,9 @@ msgid "" msgstr "" #: bin/check_perms:110 +#, fuzzy msgid " checking gid and mode for %(path)s" -msgstr "" +msgstr "در حال بررسی اجازه‌های موجود در: %(path)s" #: bin/check_perms:122 msgid "%(path)s bad group (has: %(groupname)s, expected %(MAILMAN_GROUP)s)" @@ -8021,8 +8196,9 @@ msgid "article db files must be %(octperms)s: %(path)s" msgstr "" #: bin/check_perms:183 +#, fuzzy msgid "checking mode for %(prefix)s" -msgstr "" +msgstr "در حال بررسی اجازه‌های %(file)s" #: bin/check_perms:193 msgid "WARNING: directory does not exist: %(d)s" @@ -8033,8 +8209,9 @@ msgid "directory must be at least 02775: %(d)s" msgstr "" #: bin/check_perms:209 +#, fuzzy msgid "checking perms on %(private)s" -msgstr "" +msgstr "در حال بررسی اجازه‌های %(file)s" #: bin/check_perms:214 msgid "%(private)s must not be other-readable" @@ -8062,24 +8239,27 @@ msgid "checking cgi-bin permissions" msgstr "" #: bin/check_perms:278 +#, fuzzy msgid " checking set-gid for %(path)s" -msgstr "" +msgstr "در حال بررسی اجازه‌های موجود در: %(path)s" #: bin/check_perms:282 msgid "%(path)s must be set-gid" msgstr "" #: bin/check_perms:292 +#, fuzzy msgid "checking set-gid for %(wrapper)s" -msgstr "" +msgstr "در حال بررسی اجازه‌های موجود در: %(path)s" #: bin/check_perms:296 msgid "%(wrapper)s must be set-gid" msgstr "" #: bin/check_perms:306 +#, fuzzy msgid "checking permissions on %(pwfile)s" -msgstr "" +msgstr "در حال بررسی اجازه‌های %(file)s" #: bin/check_perms:315 msgid "%(pwfile)s permissions must be exactly 0640 (got %(octmode)s)" @@ -8090,6 +8270,7 @@ msgid "checking permissions on list data" msgstr "" #: bin/check_perms:348 +#, fuzzy msgid " checking permissions on: %(path)s" msgstr "در حال بررسی اجازه‌های موجود در: %(path)s" @@ -8246,14 +8427,18 @@ msgid " original address removed:" msgstr "نشانی اصلی، حذ٠شد:" #: bin/clone_member:202 +#, fuzzy msgid "Not a valid email address: %(toaddr)s" msgstr "نشانی رایانامه‌ی نامعتبر: %(toaddr)s" #: bin/clone_member:215 +#, fuzzy msgid "" "Error opening list \"%(listname)s\", skipping.\n" "%(e)s" msgstr "" +"چنین Ùهرستی وجود ندارد \"%(listname)s\"\n" +"%(e)s" #: bin/config_list:20 msgid "" @@ -8326,10 +8511,12 @@ msgid "legal values are:" msgstr "مقادیر مجاز عبارتند از:" #: bin/config_list:270 +#, fuzzy msgid "attribute \"%(k)s\" ignored" -msgstr "" +msgstr "ویژگی \"%(k)s\" تغییر کرد" #: bin/config_list:273 +#, fuzzy msgid "attribute \"%(k)s\" changed" msgstr "ویژگی \"%(k)s\" تغییر کرد" @@ -8338,12 +8525,14 @@ msgid "Non-standard property restored: %(k)s" msgstr "" #: bin/config_list:288 +#, fuzzy msgid "Invalid value for property: %(k)s" msgstr "مقدار نامعتبر برای مولÙÙ‡: %(k)s" #: bin/config_list:291 +#, fuzzy msgid "Bad email address for option %(k)s: %(v)s" -msgstr "" +msgstr "نشانی رایانامه‌ی نادرست برای گزینه‌ی %(property)s: %(error)s" #: bin/config_list:348 msgid "Only one of -i or -o is allowed" @@ -8390,16 +8579,19 @@ msgid "" msgstr "" #: bin/discard:94 +#, fuzzy msgid "Ignoring non-held message: %(f)s" -msgstr "" +msgstr "چشم‌پوشی از تغییرات بر عضو حذ٠شده: %(user)s" #: bin/discard:100 +#, fuzzy msgid "Ignoring held msg w/bad id: %(f)s" -msgstr "" +msgstr "چشم‌پوشی از تغییرات بر عضو حذ٠شده: %(user)s" #: bin/discard:112 +#, fuzzy msgid "Discarded held msg #%(id)s for list %(listname)s" -msgstr "" +msgstr "اشتراک در Ùهرست %(listname)s" #: bin/dumpdb:19 msgid "" @@ -8692,8 +8884,9 @@ msgid "" msgstr "" #: bin/list_admins:97 +#, fuzzy msgid "List: %(listname)s, \tOwners: %(owners)s" -msgstr "" +msgstr "مالکان Ùهرست: %(owneraddr)s" #: bin/list_lists:19 msgid "" @@ -8798,12 +8991,14 @@ msgid "" msgstr "" #: bin/list_members:198 +#, fuzzy msgid "Bad --nomail option: %(why)s" -msgstr "" +msgstr "تنظیم گزینه‌ی حالت یک‌جا" #: bin/list_members:209 +#, fuzzy msgid "Bad --digest option: %(kind)s" -msgstr "" +msgstr "تنظیم گزینه‌ی حالت یک‌جا" #: bin/list_members:213 bin/list_members:217 bin/list_members:221 #: bin/list_members:225 @@ -8987,8 +9182,9 @@ msgid "" msgstr "" #: bin/mailmanctl:280 cron/mailpasswds:119 +#, fuzzy msgid "Site list is missing: %(sitelistname)s" -msgstr "" +msgstr "Ùهرست پیشاپیش وجود دارد: %(safelistname)s" #: bin/mailmanctl:305 msgid "Run this program as root or as the %(name)s user, or use -u." @@ -8999,6 +9195,7 @@ msgid "No command given." msgstr "هیچ ÙØ±Ù…انی داده نشده است." #: bin/mailmanctl:339 +#, fuzzy msgid "Bad command: %(command)s" msgstr "دستور نادرست: %(command)s" @@ -9056,6 +9253,7 @@ msgid "list creator" msgstr "سازنده‌ی Ùهرست" #: bin/mmsitepass:86 +#, fuzzy msgid "New %(pwdesc)s password: " msgstr "گذرواژه جدید %(pwdesc)s: " @@ -9214,6 +9412,7 @@ msgid "" msgstr "" #: bin/newlist:162 +#, fuzzy msgid "Unknown language: %(lang)s" msgstr "زبان ناشناخته: %(lang)s" @@ -9226,8 +9425,9 @@ msgid "Enter the email of the person running the list: " msgstr "" #: bin/newlist:193 +#, fuzzy msgid "Initial %(listname)s password: " -msgstr "" +msgstr "گذرواژه اولیه Ùهرست:" #: bin/newlist:197 msgid "The list password cannot be empty" @@ -9235,8 +9435,8 @@ msgstr "گذرواژه‌ی Ùهرست نمی تواند خالی بماند" #: bin/newlist:220 msgid "" -" - owner addresses need to be fully-qualified names like \"owner@example.com" -"\", not just \"owner\"." +" - owner addresses need to be fully-qualified names like \"owner@example." +"com\", not just \"owner\"." msgstr "" #: bin/newlist:244 @@ -9400,16 +9600,19 @@ msgid "" msgstr "" #: bin/remove_members:156 +#, fuzzy msgid "Could not open file for reading: %(filename)s." -msgstr "" +msgstr "امکان بازکردن این پرونده برای نوشتن در آن ÙØ±Ø§Ù‡Ù… نشد:" #: bin/remove_members:163 +#, fuzzy msgid "Error opening list %(listname)s... skipping." -msgstr "" +msgstr "در حال بارگذاری Ùهرست %(listname)s" #: bin/remove_members:173 +#, fuzzy msgid "No such member: %(addr)s" -msgstr "" +msgstr "چنین Ùهرستی وجود ندارد: %(listname)s" #: bin/remove_members:178 msgid "User `%(addr)s' removed from list: %(listname)s." @@ -9436,8 +9639,9 @@ msgid "" msgstr "" #: bin/reset_pw.py:77 +#, fuzzy msgid "Changing passwords for list: %(listname)s" -msgstr "" +msgstr "در حال بارگذاری Ùهرست %(listname)s" #: bin/reset_pw.py:83 msgid "New password for member %(member)40s: %(randompw)s" @@ -9466,6 +9670,7 @@ msgid "" msgstr "" #: bin/rmlist:73 bin/rmlist:76 +#, fuzzy msgid "Removing %(msg)s" msgstr "در حال حذ٠%(msg)s" @@ -9474,12 +9679,14 @@ msgid "%(listname)s %(msg)s not found as %(filename)s" msgstr "" #: bin/rmlist:105 +#, fuzzy msgid "No such list (or list already deleted): %(listname)s" msgstr "چنین Ùهرستی وجود ندارد (یا قبلاً پاک شده است): %(listname)s" #: bin/rmlist:108 +#, fuzzy msgid "No such list: %(listname)s. Removing its residual archives." -msgstr "" +msgstr "چنین Ùهرستی وجود ندارد: %(listname)s" #: bin/rmlist:112 msgid "Not removing archives. Reinvoke with -a to remove them." @@ -9608,6 +9815,7 @@ msgid "No argument to -f given" msgstr "" #: bin/sync_members:172 +#, fuzzy msgid "Illegal option: %(opt)s" msgstr "گزینه‌ی غیرمجاز: %(opt)s" @@ -9636,10 +9844,12 @@ msgid "You must fix the preceding invalid addresses first." msgstr "" #: bin/sync_members:264 +#, fuzzy msgid "Added : %(s)s" msgstr "اضاÙÙ‡ شد: %(s)s" #: bin/sync_members:289 +#, fuzzy msgid "Removed: %(s)s" msgstr "حذ٠شد: %(s)s" @@ -9744,8 +9954,9 @@ msgid "" msgstr "" #: bin/update:107 +#, fuzzy msgid "Fixing language templates: %(listname)s" -msgstr "" +msgstr "در حال روزآمدسازی Ùهرست پستی: %(listname)s" #: bin/update:196 bin/update:711 msgid "WARNING: could not acquire lock for list: %(listname)s" @@ -9837,6 +10048,7 @@ msgid "removing directory %(src)s and everything underneath" msgstr "" #: bin/update:399 +#, fuzzy msgid "removing %(src)s" msgstr "در حال حذ٠%(src)s" @@ -9899,6 +10111,7 @@ msgid "done" msgstr "انجام شد" #: bin/update:691 +#, fuzzy msgid "Updating mailing list: %(listname)s" msgstr "در حال روزآمدسازی Ùهرست پستی: %(listname)s" @@ -10105,6 +10318,7 @@ msgid "" msgstr "" #: bin/withlist:175 +#, fuzzy msgid "Unlocking (but not saving) list: %(listname)s" msgstr "در حال Ù‚Ùل‌گشایی (ولی نه ذخیره‌سازی) Ùهرست: %(listname)s" @@ -10113,6 +10327,7 @@ msgid "Finalizing" msgstr "" #: bin/withlist:188 +#, fuzzy msgid "Loading list %(listname)s" msgstr "در حال بارگذاری Ùهرست %(listname)s" @@ -10125,6 +10340,7 @@ msgid "(unlocked)" msgstr "" #: bin/withlist:197 +#, fuzzy msgid "Unknown list: %(listname)s" msgstr "Ùهرست ناشناخته: %(listname)s" @@ -10137,6 +10353,7 @@ msgid "--all requires --run" msgstr "" #: bin/withlist:266 +#, fuzzy msgid "Importing %(module)s..." msgstr "در حال Ø¯Ø±ÙˆÙ†â€ŒØ¨ÙØ±Ø¯ %(module)s..." @@ -10334,8 +10551,9 @@ msgid "Password // URL" msgstr "گذرواژه// URL" #: cron/mailpasswds:222 +#, fuzzy msgid "%(host)s mailing list memberships reminder" -msgstr "" +msgstr "به کار انداختن مجدد عضویت در Ùهرست پستی " #: cron/nightly_gzip:19 msgid "" diff --git a/messages/fi/LC_MESSAGES/mailman.po b/messages/fi/LC_MESSAGES/mailman.po index d1288c45..8db5badd 100755 --- a/messages/fi/LC_MESSAGES/mailman.po +++ b/messages/fi/LC_MESSAGES/mailman.po @@ -73,10 +73,12 @@ msgid "

                      Currently, there are no archives.

                      " msgstr "

                      Arkistoja ei ole.

                      " #: Mailman/Archiver/HyperArch.py:821 +#, fuzzy msgid "Gzip'd Text%(sz)s" msgstr "Gzip teksti%(sz)s" #: Mailman/Archiver/HyperArch.py:826 +#, fuzzy msgid "Text%(sz)s" msgstr "Teksti%(sz)s" @@ -149,18 +151,22 @@ msgid "Third" msgstr "Kolmas" #: Mailman/Archiver/HyperArch.py:938 +#, fuzzy msgid "%(ord)s quarter %(year)i" msgstr "%(year)i %(ord)s neljännes" #: Mailman/Archiver/HyperArch.py:945 +#, fuzzy msgid "%(month)s %(year)i" msgstr "%(month)s %(year)i" #: Mailman/Archiver/HyperArch.py:950 +#, fuzzy msgid "The Week Of Monday %(day)i %(month)s %(year)i" msgstr "%(day)i. %(month)sta %(year)i alkava viikko" #: Mailman/Archiver/HyperArch.py:954 +#, fuzzy msgid "%(day)i %(month)s %(year)i" msgstr "%(day)i. %(month)sta %(year)i" @@ -169,10 +175,12 @@ msgid "Computing threaded index\n" msgstr "Luodaan viestipuuta\n" #: Mailman/Archiver/HyperArch.py:1319 +#, fuzzy msgid "Updating HTML for article %(seq)s" msgstr "Päivitetään HTML-sivut artikkelille %(seq)s" #: Mailman/Archiver/HyperArch.py:1326 +#, fuzzy msgid "article file %(filename)s is missing!" msgstr "viestitiedosto %(filename)s puuttuu!" @@ -189,6 +197,7 @@ msgid "Pickling archive state into " msgstr "Tallennetaan arkiston tilan tiedostoon " #: Mailman/Archiver/pipermail.py:453 +#, fuzzy msgid "Updating index files for archive [%(archive)s]" msgstr "Päivitetään indeksi-tiedostot arkistolle [%(archive)s]" @@ -197,6 +206,7 @@ msgid " Thread" msgstr " Ketju" #: Mailman/Archiver/pipermail.py:597 +#, fuzzy msgid "#%(counter)05d %(msgid)s" msgstr "#%(counter)05d %(msgid)s" @@ -234,6 +244,7 @@ msgid "disabled address" msgstr "ei käytössä" #: Mailman/Bouncer.py:304 +#, fuzzy msgid " The last bounce received from you was dated %(date)s" msgstr " Viimeisin palautus osoitteestasi oli päivätty %(date)s" @@ -262,6 +273,7 @@ msgstr "Yll #: Mailman/Cgi/options.py:101 Mailman/Cgi/private.py:108 #: Mailman/Cgi/rmlist.py:75 Mailman/Cgi/roster.py:59 #: Mailman/Cgi/subscribe.py:67 +#, fuzzy msgid "No such list %(safelistname)s" msgstr "Listaa %(safelistname)s ei ole olemassa." @@ -333,6 +345,7 @@ msgstr "" " viestit ovat poissa käytöstä. Nämä henkilöt eivät saa viestejä.%(rm)r" #: Mailman/Cgi/admin.py:268 +#, fuzzy msgid "%(hostname)s mailing lists - Admin Links" msgstr "%(hostname)s postituslistat - Ylläpitäjän linkit" @@ -345,6 +358,7 @@ msgid "Mailman" msgstr "Mailman" #: Mailman/Cgi/admin.py:310 +#, fuzzy msgid "" "

                      There currently are no publicly-advertised %(mailmanlink)s\n" " mailing lists on %(hostname)s." @@ -353,6 +367,7 @@ msgstr "" " %(mailmanlink)s postituslistoja." #: Mailman/Cgi/admin.py:316 +#, fuzzy msgid "" "

                      Below is the collection of publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s. Click on a list\n" @@ -368,6 +383,7 @@ msgid "right " msgstr "oikea " #: Mailman/Cgi/admin.py:325 +#, fuzzy msgid "" "To visit the administrators configuration page for an\n" " unadvertised list, open a URL similar to this one, but with a '/' " @@ -414,6 +430,7 @@ msgid "No valid variable name found." msgstr "Kelvollista muuttujanimeä ei löydy." #: Mailman/Cgi/admin.py:395 +#, fuzzy msgid "" "%(realname)s Mailing list Configuration Help\n" "
                      %(varname)s Option" @@ -422,6 +439,7 @@ msgstr "" "
                      %(varname)s Optio" #: Mailman/Cgi/admin.py:402 +#, fuzzy msgid "Mailman %(varname)s List Option Help" msgstr "Mailman %(varname)s Listan Muokkaus Ohje" @@ -441,14 +459,17 @@ msgstr "" " " #: Mailman/Cgi/admin.py:431 +#, fuzzy msgid "return to the %(categoryname)s options page." msgstr "palaa %(categoryname)s valintojen sivulle." #: Mailman/Cgi/admin.py:446 +#, fuzzy msgid "%(realname)s Administration (%(label)s)" msgstr "%(realname)s ylläpito (%(label)s)" #: Mailman/Cgi/admin.py:447 +#, fuzzy msgid "%(realname)s mailing list administration
                      %(label)s Section" msgstr "%(realname)s postituslistan ylläpito
                      %(label)s" @@ -530,6 +551,7 @@ msgid "Value" msgstr "Arvo" #: Mailman/Cgi/admin.py:668 +#, fuzzy msgid "" "Badly formed options entry:\n" " %(record)s" @@ -630,10 +652,12 @@ msgid "Move rule down" msgstr "Siirrä sääntö alas" #: Mailman/Cgi/admin.py:870 +#, fuzzy msgid "
                      (Edit %(varname)s)" msgstr "
                      (Muokkaa %(varname)s)" #: Mailman/Cgi/admin.py:872 +#, fuzzy msgid "
                      (Details for %(varname)s)" msgstr "
                      (Yksityiskohdat %(varname)s)" @@ -674,6 +698,7 @@ msgid "(help)" msgstr "(apua)" #: Mailman/Cgi/admin.py:930 +#, fuzzy msgid "Find member %(link)s:" msgstr "Etsi jäsen %(link)s:" @@ -686,10 +711,12 @@ msgid "Bad regular expression: " msgstr "Väärä vakioilmaus: " #: Mailman/Cgi/admin.py:1013 +#, fuzzy msgid "%(allcnt)s members total, %(membercnt)s shown" msgstr "%(allcnt)s jäsentä yhteensä, %(membercnt)s näytetään" #: Mailman/Cgi/admin.py:1016 +#, fuzzy msgid "%(allcnt)s members total" msgstr "%(allcnt)s jäsentä yhteensä" @@ -877,6 +904,7 @@ msgstr "" " sopivaa aluetta:" #: Mailman/Cgi/admin.py:1221 +#, fuzzy msgid "from %(start)s to %(end)s" msgstr "%(start)s sta %(end)s aan" @@ -1013,6 +1041,7 @@ msgid "Change list ownership passwords" msgstr "Vaihda listan omistussuhteen salasanat" #: Mailman/Cgi/admin.py:1364 +#, fuzzy msgid "" "The list administrators are the people who have ultimate control " "over\n" @@ -1123,6 +1152,7 @@ msgstr "Vahingollinen osoite (v #: Mailman/Cgi/admin.py:1529 Mailman/Cgi/admin.py:1703 bin/add_members:175 #: bin/clone_member:136 bin/sync_members:268 +#, fuzzy msgid "Banned address (matched %(pattern)s)" msgstr "" "Osoite on kiellettyjen osoitteiden listalla (täsmäsi kaavaan %(pattern)s)" @@ -1227,6 +1257,7 @@ msgid "Not subscribed" msgstr "Ei liitetty" #: Mailman/Cgi/admin.py:1773 +#, fuzzy msgid "Ignoring changes to deleted member: %(user)s" msgstr "Ei käsitellä muutoksia poistetulle käyttäjälle: %(user)s" @@ -1239,10 +1270,12 @@ msgid "Error Unsubscribing:" msgstr "Virhe eroamisessa:" #: Mailman/Cgi/admindb.py:235 Mailman/Cgi/admindb.py:248 +#, fuzzy msgid "%(realname)s Administrative Database" msgstr "%(realname)s Ylläpidon tietokanta" #: Mailman/Cgi/admindb.py:238 +#, fuzzy msgid "%(realname)s Administrative Database Results" msgstr "%(realname)s Ylläpidon tietokannan tulokset" @@ -1272,6 +1305,7 @@ msgstr "" "Hylkää kaikki viestit jotka ovat merkitty Poistettavaksi (Defer)" #: Mailman/Cgi/admindb.py:298 +#, fuzzy msgid "all of %(esender)s's held messages." msgstr "kaikki %(esender)s n odottavat viestit." @@ -1292,6 +1326,7 @@ msgid "list of available mailing lists." msgstr "lista käytössä olevista postituslistoista" #: Mailman/Cgi/admindb.py:362 +#, fuzzy msgid "You must specify a list name. Here is the %(link)s" msgstr "Sinun täytyy määritellä listan nimi. Tässä on %(link)s" @@ -1373,6 +1408,7 @@ msgid "The sender is now a member of this list" msgstr "Lähettäjä on nyt postituslistan käyttäjä" #: Mailman/Cgi/admindb.py:568 +#, fuzzy msgid "Add %(esender)s to one of these sender filters:" msgstr "Lisää %(esender)s lähettäjän suodatukseen" @@ -1393,6 +1429,7 @@ msgid "Rejects" msgstr "Torjutut" #: Mailman/Cgi/admindb.py:584 +#, fuzzy msgid "" "Ban %(esender)s from ever subscribing to this\n" " mailing list" @@ -1409,6 +1446,7 @@ msgstr "" " viesti, tai voit " #: Mailman/Cgi/admindb.py:591 +#, fuzzy msgid "view all messages from %(esender)s" msgstr "näytä kaikki %(esender)s viestit" @@ -1487,6 +1525,7 @@ msgid " is already a member" msgstr " on jo jäsen" #: Mailman/Cgi/admindb.py:973 +#, fuzzy msgid "%(addr)s is banned (matched: %(patt)s)" msgstr "%(addr)s on kielletty (banned) (täsmäsi kaavaan: %(patt)s)" @@ -1535,6 +1574,7 @@ msgstr "" " irtisanottu listalta jälkeenpäin. Tämä pyyntö on peruttu." #: Mailman/Cgi/confirm.py:178 +#, fuzzy msgid "System error, bad content: %(content)s" msgstr "Järjestelmävirhe, väärä sisältö: %(content)s" @@ -1572,6 +1612,7 @@ msgid "Confirm subscription request" msgstr "Vahvista liittymispyyntö" #: Mailman/Cgi/confirm.py:259 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " subscription request to the mailing list %(listname)s. Your\n" @@ -1603,6 +1644,7 @@ msgstr "" "tämän liittymispyynnön." #: Mailman/Cgi/confirm.py:275 +#, fuzzy msgid "" "Your confirmation is required in order to continue with\n" " the subscription request to the mailing list %(listname)s.\n" @@ -1650,6 +1692,7 @@ msgid "Preferred language:" msgstr "Valitse kieli:" #: Mailman/Cgi/confirm.py:317 +#, fuzzy msgid "Subscribe to list %(listname)s" msgstr "Liitä listalle %(listname)s" @@ -1666,6 +1709,7 @@ msgid "Awaiting moderator approval" msgstr "Odotetaan pääkäyttäjän hyväksymistä" #: Mailman/Cgi/confirm.py:386 +#, fuzzy msgid "" " You have successfully confirmed your subscription request to " "the\n" @@ -1699,6 +1743,7 @@ msgstr "Sin # ####### #: Mailman/Cgi/confirm.py:400 +#, fuzzy msgid "" "You are currently banned from subscribing to\n" " this list. If you think this restriction is erroneous, please\n" @@ -1723,6 +1768,7 @@ msgid "Subscription request confirmed" msgstr "Liittymispyyntö vahvistettu" #: Mailman/Cgi/confirm.py:418 +#, fuzzy msgid "" " You have successfully confirmed your subscription request for\n" " \"%(addr)s\" to the %(listname)s mailing list. A separate\n" @@ -1750,6 +1796,7 @@ msgid "Unsubscription request confirmed" msgstr "Irtisanomispyyntö vahvistettu" #: Mailman/Cgi/confirm.py:469 +#, fuzzy msgid "" " You have successfully unsubscribed from the %(listname)s " "mailing\n" @@ -1770,6 +1817,7 @@ msgid "Not available" msgstr "Ei saatavilla" #: Mailman/Cgi/confirm.py:498 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " unsubscription request from the mailing list %(listname)s. " @@ -1815,6 +1863,7 @@ msgstr "Olet peruuttanut osoitteenmuutospyynn # ####### #: Mailman/Cgi/confirm.py:555 +#, fuzzy msgid "" "%(newaddr)s is banned from subscribing to the\n" " %(realname)s list. If you think this restriction is erroneous,\n" @@ -1826,6 +1875,7 @@ msgstr "" " ota yhteyttä listan omistajiin %(owneraddr)s." #: Mailman/Cgi/confirm.py:560 +#, fuzzy msgid "" "%(newaddr)s is already a member of\n" " the %(realname)s list. It is possible that you are attempting\n" @@ -1841,6 +1891,7 @@ msgid "Change of address request confirmed" msgstr "Osoitteenmuutospyyntö vahvistettu" #: Mailman/Cgi/confirm.py:571 +#, fuzzy msgid "" " You have successfully changed your address on the %(listname)s\n" " mailing list from %(oldaddr)s to %(newaddr)s. " @@ -1863,6 +1914,7 @@ msgid "globally" msgstr "globaalisti" #: Mailman/Cgi/confirm.py:605 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " change of address request for the mailing list %(listname)s. " @@ -1924,6 +1976,7 @@ msgid "Sender discarded message via web." msgstr "Lähettäjä hylkäsi viestin webin kautta." #: Mailman/Cgi/confirm.py:674 +#, fuzzy msgid "" "The held message with the Subject:\n" " header %(subject)s could not be found. The most " @@ -1943,6 +1996,7 @@ msgid "Posted message canceled" msgstr "Lähetetty viesti peruutettu" #: Mailman/Cgi/confirm.py:685 +#, fuzzy msgid "" " You have successfully canceled the posting of your message with\n" " the Subject: header %(subject)s to the mailing list\n" @@ -1965,6 +2019,7 @@ msgstr "" "toimesta." #: Mailman/Cgi/confirm.py:735 +#, fuzzy msgid "" "Your confirmation is required in order to cancel the\n" " posting of your message to the mailing list %(listname)s:\n" @@ -2012,6 +2067,7 @@ msgid "Membership re-enabled." msgstr "Jäsenyys uudelleenaktivoitu." #: Mailman/Cgi/confirm.py:798 +#, fuzzy msgid "" " You have successfully re-enabled your membership in the\n" " %(listname)s mailing list. You can now not available" msgstr "ei saatavilla" #: Mailman/Cgi/confirm.py:846 +#, fuzzy msgid "" "Your membership in the %(realname)s mailing list is\n" " currently disabled due to excessive bounces. Your confirmation is\n" @@ -2108,10 +2166,12 @@ msgid "administrative list overview" msgstr "tietoa ylläpitolistasta" #: Mailman/Cgi/create.py:115 +#, fuzzy msgid "List name must not include \"@\": %(safelistname)s" msgstr "Listan nimessä ei saa olla \"@\": %(safelistname)s" #: Mailman/Cgi/create.py:122 +#, fuzzy msgid "List already exists: %(safelistname)s" msgstr "Lista on jo olemassa: %(safelistname)s" @@ -2146,18 +2206,22 @@ msgid "You are not authorized to create new mailing lists" msgstr "Sinulla ei ole oikeutta luoda uutta postituslistaa" #: Mailman/Cgi/create.py:182 +#, fuzzy msgid "Unknown virtual host: %(safehostname)s" msgstr "Tuntematon palvelin (virtual host): %(safehostname)s" #: Mailman/Cgi/create.py:218 bin/newlist:219 +#, fuzzy msgid "Bad owner email address: %(s)s" msgstr "Omistajan sähköpostiosoite on väärä: %(s)s" #: Mailman/Cgi/create.py:223 bin/newlist:182 bin/newlist:223 +#, fuzzy msgid "List already exists: %(listname)s" msgstr "Lista on jo olemassa: %(listname)s" #: Mailman/Cgi/create.py:231 bin/newlist:217 +#, fuzzy msgid "Illegal list name: %(s)s" msgstr "Virheellinen listan nimi: %(s)s" @@ -2170,6 +2234,7 @@ msgstr "" " Ota yhteyttä järjestelmän ylläpitäjään." #: Mailman/Cgi/create.py:273 bin/newlist:265 +#, fuzzy msgid "Your new mailing list: %(listname)s" msgstr "Uusi postituslistasi: %(listname)s" @@ -2178,6 +2243,7 @@ msgid "Mailing list creation results" msgstr "Postituslistan luonnin tulokset" #: Mailman/Cgi/create.py:288 +#, fuzzy msgid "" "You have successfully created the mailing list\n" " %(listname)s and notification has been sent to the list owner\n" @@ -2200,6 +2266,7 @@ msgid "Create another list" msgstr "Luoda toisen listan" #: Mailman/Cgi/create.py:312 +#, fuzzy msgid "Create a %(hostname)s Mailing List" msgstr "Luoda %(hostname)s postituslista" @@ -2295,6 +2362,7 @@ msgstr "" "Vastaa Kyllä jos haluat pitää oletuksena ylläpidon tarkistuksen." #: Mailman/Cgi/create.py:432 +#, fuzzy msgid "" "Initial list of supported languages.

                      Note that if you do not\n" " select at least one initial language, the list will use the server\n" @@ -2400,6 +2468,7 @@ msgid "List name is required." msgstr "Listan nimi vaaditaan." #: Mailman/Cgi/edithtml.py:154 +#, fuzzy msgid "%(realname)s -- Edit html for %(template_info)s" msgstr "%(realname)s -- Muokkaa html-koodia %(template_info)s lle" @@ -2408,10 +2477,12 @@ msgid "Edit HTML : Error" msgstr "Muokkaa HTML : Virhe" #: Mailman/Cgi/edithtml.py:161 +#, fuzzy msgid "%(safetemplatename)s: Invalid template" msgstr "%(safetemplatename)s: Viallinen mallipohja" #: Mailman/Cgi/edithtml.py:166 Mailman/Cgi/edithtml.py:167 +#, fuzzy msgid "%(realname)s -- HTML Page Editing" msgstr "%(realname)s -- HTML sivun muokkaus" @@ -2474,10 +2545,12 @@ msgid "HTML successfully updated." msgstr "HMTL on muutettu onnistuneesti." #: Mailman/Cgi/listinfo.py:89 +#, fuzzy msgid "%(hostname)s Mailing Lists" msgstr "%(hostname)s postituslistat" #: Mailman/Cgi/listinfo.py:127 +#, fuzzy msgid "" "

                      There currently are no publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s." @@ -2486,6 +2559,7 @@ msgstr "" " %(mailmanlink)s postituslistoja." #: Mailman/Cgi/listinfo.py:131 +#, fuzzy msgid "" "

                      Below is a listing of all the public mailing lists on\n" " %(hostname)s. Click on a list name to get more information " @@ -2504,6 +2578,7 @@ msgid "right" msgstr "oikea" #: Mailman/Cgi/listinfo.py:140 +#, fuzzy msgid "" " To visit the general information page for an unadvertised list,\n" " open a URL similar to this one, but with a '/' and the %(adj)s\n" @@ -2566,6 +2641,7 @@ msgstr "Virheellinen s #: Mailman/Cgi/options.py:183 Mailman/Cgi/options.py:240 #: Mailman/Cgi/options.py:264 +#, fuzzy msgid "No such member: %(safeuser)s." msgstr "Ei sellaista jäsentä: %(safeuser)s." @@ -2617,6 +2693,7 @@ msgid "Note: " msgstr "Viesti: " #: Mailman/Cgi/options.py:378 +#, fuzzy msgid "List subscriptions for %(safeuser)s on %(hostname)s" msgstr "%(safeuser)s, listojen jäsenyydet palvelimella %(hostname)s" @@ -2647,6 +2724,7 @@ msgid "You are already using that email address" msgstr "Käytät jo sitä sähköpostiosoitetta" #: Mailman/Cgi/options.py:459 +#, fuzzy msgid "" "The new address you requested %(newaddr)s is already a member of the\n" "%(listname)s mailing list, however you have also requested a global change " @@ -2661,6 +2739,7 @@ msgstr "" "kaikki muut listat jotka sisältävät osoitteen %(safeuser)s muutetaan." #: Mailman/Cgi/options.py:468 +#, fuzzy msgid "The new address is already a member: %(newaddr)s" msgstr "%(newaddr)s on jo listan jäsen." @@ -2669,6 +2748,7 @@ msgid "Addresses may not be blank" msgstr "Osoitteet eivät voi olla tyhjiä" #: Mailman/Cgi/options.py:488 +#, fuzzy msgid "A confirmation message has been sent to %(newaddr)s. " msgstr "Vahvistusviesti on lähetetty osoitteeseen %(newaddr)s" @@ -2681,11 +2761,13 @@ msgid "Illegal email address provided" msgstr "Virheellinen sähköpostiosoite" #: Mailman/Cgi/options.py:501 +#, fuzzy msgid "%(newaddr)s is already a member of the list." msgstr "%(newaddr)s on jo listan jäsen." # ####### #: Mailman/Cgi/options.py:504 +#, fuzzy msgid "" "%(newaddr)s is banned from this list. If you\n" " think this restriction is erroneous, please contact\n" @@ -2763,6 +2845,7 @@ msgstr "" " " #: Mailman/Cgi/options.py:623 +#, fuzzy msgid "" "You have been successfully unsubscribed from the\n" " mailing list %(fqdn_listname)s. If you were receiving digest\n" @@ -2855,6 +2938,7 @@ msgid "day" msgstr "päivä" #: Mailman/Cgi/options.py:900 +#, fuzzy msgid "%(days)d %(units)s" msgstr "%(days)d %(units)s" @@ -2867,6 +2951,7 @@ msgid "No topics defined" msgstr "Aihetta ei ole määritelty" #: Mailman/Cgi/options.py:940 +#, fuzzy msgid "" "\n" "You are subscribed to this list with the case-preserved address\n" @@ -2877,6 +2962,7 @@ msgstr "" "%(cpuser)s." #: Mailman/Cgi/options.py:956 +#, fuzzy msgid "%(realname)s list: member options login page" msgstr "%(realname)s listan: jäsenen sisäänkirjautumissivu" @@ -2885,10 +2971,12 @@ msgid "email address and " msgstr "sähköpostiosoite ja" #: Mailman/Cgi/options.py:960 +#, fuzzy msgid "%(realname)s list: member options for user %(safeuser)s" msgstr "%(realname)s lista: jäsenen %(safeuser)s valinnat" #: Mailman/Cgi/options.py:986 +#, fuzzy msgid "" "In order to change your membership option, you must\n" " first log in by giving your %(extra)smembership password in the section\n" @@ -2972,6 +3060,7 @@ msgid "" msgstr "" #: Mailman/Cgi/options.py:1140 +#, fuzzy msgid "Requested topic is not valid: %(topicname)s" msgstr "Pyydetty aihe ei kelpaa: %(topicname)s" @@ -3002,6 +3091,7 @@ msgstr "" "osoitteessa." #: Mailman/Cgi/private.py:109 +#, fuzzy msgid "Private Archive Error - %(msg)s" msgstr "Yksityisarkistovirhe - %(msg)s" @@ -3039,6 +3129,7 @@ msgid "Mailing list deletion results" msgstr "Postituslistan tuhoamisen tulokset" #: Mailman/Cgi/rmlist.py:187 +#, fuzzy msgid "" "You have successfully deleted the mailing list\n" " %(listname)s." @@ -3048,6 +3139,7 @@ msgstr "" # ####### #: Mailman/Cgi/rmlist.py:191 +#, fuzzy msgid "" "There were some problems deleting the mailing list\n" " %(listname)s. Contact your site administrator at " @@ -3059,6 +3151,7 @@ msgstr "" " tarkempien tietojen saamiseksi." #: Mailman/Cgi/rmlist.py:208 +#, fuzzy msgid "Permanently remove mailing list %(realname)s" msgstr "Pysyvä %(realname)s postituslistan poistaminen" @@ -3128,6 +3221,7 @@ msgid "Invalid options to CGI script" msgstr "CGI-koodissa on väärä välinta" #: Mailman/Cgi/roster.py:118 +#, fuzzy msgid "%(realname)s roster authentication failed." msgstr "%(realname)s luettelon todennus epäonnistui" @@ -3196,6 +3290,7 @@ msgstr "" # ####### #: Mailman/Cgi/subscribe.py:262 +#, fuzzy msgid "" "The email address you supplied is banned from this\n" " mailing list. If you think this restriction is erroneous, please\n" @@ -3223,6 +3318,7 @@ msgstr "" "turvaton." #: Mailman/Cgi/subscribe.py:278 +#, fuzzy msgid "" "Confirmation from your email address is required, to prevent anyone from\n" "subscribing you without permission. Instructions are being sent to you at\n" @@ -3235,6 +3331,7 @@ msgstr "" "vahvistat liittymisesi." #: Mailman/Cgi/subscribe.py:290 +#, fuzzy msgid "" "Your subscription request was deferred because %(x)s. Your request has " "been\n" @@ -3256,6 +3353,7 @@ msgid "Mailman privacy alert" msgstr "Mailmanin varoitus yksityisyyden suojasta" #: Mailman/Cgi/subscribe.py:316 +#, fuzzy msgid "" "An attempt was made to subscribe your address to the mailing list\n" "%(listaddr)s. You are already subscribed to this mailing list.\n" @@ -3295,6 +3393,7 @@ msgid "This list only supports digest delivery." msgstr "Tämä lista tukee vain lukemistolähetyksiä. " #: Mailman/Cgi/subscribe.py:344 +#, fuzzy msgid "You have been successfully subscribed to the %(realname)s mailing list." msgstr "Sinut on onnistuneesti liitetty %(realname)s postituslistalle." @@ -3346,6 +3445,7 @@ msgstr "" # ####### #: Mailman/Commands/cmd_confirm.py:69 +#, fuzzy msgid "" "You are currently banned from subscribing to this list. If you think this\n" "restriction is erroneous, please contact the list owners at\n" @@ -3431,28 +3531,34 @@ msgid "n/a" msgstr "ei soveltuva" #: Mailman/Commands/cmd_info.py:44 +#, fuzzy msgid "List name: %(listname)s" msgstr "Listan nimi: %(listname)s" #: Mailman/Commands/cmd_info.py:45 +#, fuzzy msgid "Description: %(description)s" msgstr "Kuvaus: %(description)s " #: Mailman/Commands/cmd_info.py:46 +#, fuzzy msgid "Postings to: %(postaddr)s" msgstr "Lähetyksiä osoitteelle: %(postaddr)s" #: Mailman/Commands/cmd_info.py:47 +#, fuzzy msgid "List Helpbot: %(requestaddr)s" msgstr "Listan ohjebotti: %(requestaddr)s" # ####### #: Mailman/Commands/cmd_info.py:48 +#, fuzzy msgid "List Owners: %(owneraddr)s" msgstr "Listan omistajat: %(owneraddr)s" # ####### #: Mailman/Commands/cmd_info.py:49 +#, fuzzy msgid "More information: %(listurl)s" msgstr "Lisätietoja: %(listurl)s" @@ -3479,21 +3585,25 @@ msgstr "" "postituslistoista.\n" #: Mailman/Commands/cmd_lists.py:44 +#, fuzzy msgid "Public mailing lists at %(hostname)s:" msgstr "Julkiset postituslistat palvelimella %(hostname)s" # ####### #: Mailman/Commands/cmd_lists.py:66 +#, fuzzy msgid "%(i)3d. List name: %(realname)s" msgstr "%(i)3d. Listan nimi: %(realname)s" # ####### #: Mailman/Commands/cmd_lists.py:67 +#, fuzzy msgid " Description: %(description)s" msgstr " Kuvaus: %(description)s" # ####### #: Mailman/Commands/cmd_lists.py:68 +#, fuzzy msgid " Requests to: %(requestaddr)s" msgstr " Pyynnöt osoitteeseen: %(requestaddr)s" @@ -3531,12 +3641,14 @@ msgstr "" # ####### #: Mailman/Commands/cmd_password.py:51 Mailman/Commands/cmd_password.py:66 +#, fuzzy msgid "Your password is: %(password)s" msgstr "Salasanasi on: %(password)s" #: Mailman/Commands/cmd_password.py:57 Mailman/Commands/cmd_password.py:72 #: Mailman/Commands/cmd_password.py:95 Mailman/Commands/cmd_password.py:121 #: Mailman/Commands/cmd_set.py:149 Mailman/Commands/cmd_set.py:219 +#, fuzzy msgid "You are not a member of the %(listname)s mailing list" msgstr "Sinä et ole postituslistan %(listname)s jäsen" @@ -3693,6 +3805,7 @@ msgstr "" # ####### #: Mailman/Commands/cmd_set.py:122 +#, fuzzy msgid "Bad set command: %(subcmd)s" msgstr "Kelvoton set-käsky: %(subcmd)s" @@ -3715,6 +3828,7 @@ msgid "on" msgstr "päällä" #: Mailman/Commands/cmd_set.py:154 +#, fuzzy msgid " ack %(onoff)s" msgstr " kuittaus %(onoff)s" @@ -3754,24 +3868,29 @@ msgid "due to bounces" msgstr "palautuksista johtuen" #: Mailman/Commands/cmd_set.py:186 +#, fuzzy msgid " %(status)s (%(how)s on %(date)s)" msgstr " %(status)s (%(how)s, päivämäärä: %(date)s)" #: Mailman/Commands/cmd_set.py:192 +#, fuzzy msgid " myposts %(onoff)s" msgstr " postitukseni %(onoff)s" #: Mailman/Commands/cmd_set.py:195 +#, fuzzy msgid " hide %(onoff)s" msgstr "piilota %(onoff)s" # ####### #: Mailman/Commands/cmd_set.py:199 +#, fuzzy msgid " duplicates %(onoff)s" msgstr " kaksoiskappaleet %(onoff)s" # ####### #: Mailman/Commands/cmd_set.py:203 +#, fuzzy msgid " reminders %(onoff)s" msgstr " muistutukset %(onoff)s" @@ -3781,6 +3900,7 @@ msgstr "Et antanut oikeaa salasanaa" # ####### #: Mailman/Commands/cmd_set.py:236 Mailman/Commands/cmd_set.py:283 +#, fuzzy msgid "Bad argument: %(arg)s" msgstr "Virheellinen määre: %(arg)s" @@ -3860,6 +3980,7 @@ msgstr "" # ####### #: Mailman/Commands/cmd_subscribe.py:62 +#, fuzzy msgid "Bad digest specifier: %(arg)s" msgstr "Kelvoton lukemiston määre: %(arg)s" @@ -3869,6 +3990,7 @@ msgstr "Liittymiseen ei l # ####### #: Mailman/Commands/cmd_subscribe.py:113 +#, fuzzy msgid "" "The email address you supplied is banned from this mailing list.\n" "If you think this restriction is erroneous, please contact the list\n" @@ -3914,6 +4036,7 @@ msgstr "T # ####### #: Mailman/Commands/cmd_subscribe.py:146 +#, fuzzy msgid "" "Your subscription request has been forwarded to the list administrator\n" "at %(listowner)s for review." @@ -3951,6 +4074,7 @@ msgstr "" " lainausmerkkejä määreen ympärille!)\n" #: Mailman/Commands/cmd_unsubscribe.py:62 +#, fuzzy msgid "%(address)s is not a member of the %(listname)s mailing list" msgstr "Osoite %(address)s ei ole postilistalla %(listname)s" @@ -4216,6 +4340,7 @@ msgid "Chinese (Taiwan)" msgstr "Kiina (Taiwan)" #: Mailman/Deliverer.py:53 +#, fuzzy msgid "" "Note: Since this is a list of mailing lists, administrative\n" "notices like the password reminder will be sent to\n" @@ -4230,14 +4355,17 @@ msgid " (Digest mode)" msgstr " (Lukemisto-muoto)" #: Mailman/Deliverer.py:79 +#, fuzzy msgid "Welcome to the \"%(realname)s\" mailing list%(digmode)s" msgstr "Tervetuloa \"%(realname)s\" postituslistalle %(digmode)s" #: Mailman/Deliverer.py:89 +#, fuzzy msgid "You have been unsubscribed from the %(realname)s mailing list" msgstr "Sinut on irtisanottu %(realname)s postituslistalta" #: Mailman/Deliverer.py:116 +#, fuzzy msgid "%(listfullname)s mailing list reminder" msgstr "%(listfullname)s postituslistan muistuttaja" @@ -4250,6 +4378,7 @@ msgid "Hostile subscription attempt detected" msgstr "Vihamielinen kirjautumisyritys havaittu" #: Mailman/Deliverer.py:169 +#, fuzzy msgid "" "%(address)s was invited to a different mailing\n" "list, but in a deliberate malicious attempt they tried to confirm the\n" @@ -4261,6 +4390,7 @@ msgstr "" "Lähetimme tämän viestin tiedoksesi, sinun ei tarvitse tehdä mitään." #: Mailman/Deliverer.py:188 +#, fuzzy msgid "" "You invited %(address)s to your list, but in a\n" "deliberate malicious attempt, they tried to confirm the invitation to a\n" @@ -4273,6 +4403,7 @@ msgstr "" "Lähetimme tämän viestin tiedoksesi, sinun ei tarvitse tehdä mitään." #: Mailman/Deliverer.py:221 +#, fuzzy msgid "%(listname)s mailing list probe message" msgstr "%(listname)s postituslistan muistuttaja" @@ -4478,8 +4609,8 @@ msgid "" " membership.\n" "\n" "

                      You can control both the\n" -" number\n" +" number\n" " of reminders the member will receive and the\n" " Voit ohjata sekä\n" -" muistutuksien\n" +" muistutuksien\n" " määrää jotka käyttäjä saa että\n" " No those messages too will get discarded. You may " "want\n" " to set up an\n" -" autoresponse\n" +" autoresponse\n" " message for email to the -owner and -admin address." msgstr "" "Vaikka Mailmanin palautettujen viestien havainnointi on varsin järeä,\n" @@ -4726,8 +4857,8 @@ msgstr "" "arvoksi \n" " on asetettu Ei myöskään näitä viestejä ei huomioida. " "Haluat ehkä \n" -" asettaa \n" +" asettaa \n" " automaattivastauksen kaikille niille sähköposteille, \n" " jotka lähetetään omistaja tai ylläpitäjän osoitteisiin." @@ -4794,6 +4925,7 @@ msgstr "" " vuoksi. Jäsenelle yritetään aina ilmoittaa." #: Mailman/Gui/Bounce.py:194 +#, fuzzy msgid "" "Bad value for %(property)s: %(val)s" @@ -4929,8 +5061,8 @@ msgstr "" "\n" "

                      Tyhjät rivit jätetään huomiotta.\n" "\n" -"

                      Katso myös Katso myös pass_mime_types nähdäksesi sisältötyyppien luettelon." # ####### @@ -4949,8 +5081,8 @@ msgid "" "Use this option to remove each message attachment that does\n" " not have a matching content type. Requirements and formats " "are\n" -" exactly like filter_mime_types.\n" "\n" "

                      Note: if you add entries to this list but don't add\n" @@ -4960,8 +5092,8 @@ msgstr "" "Käytä tätä valitsinta poistaaksesi jokaisen liitetiedoston,\n" " jonka tyyppi ei vastaa valittuja sisältötyyppejä. Vaatimusten\n" " ja muodon täytyy vastata tarkkaan suodattimen MIME-tyyppejä\n" -" (filter_mime_types).\n" +" (filter_mime_types).\n" "\n" "

                      Huomaa: Jos lisäät kohtia tähän luetteloon, mutta et\n" " lisää siihen multipart-osuutta, kaikki " @@ -5048,11 +5180,11 @@ msgstr "" "Jokin näistä toiminnoista suoritetaan, kun viesti vastaa jotakin\n" " sisällönsuodatuksen säännöistä, mikä tarkoittaa, että ylätason\n" " sisältötyyppi vastaa jotakin suodatuksen MIME-tyyppiä\n" -" (), tai ylätason sisältötyyppi\n" +" (), tai ylätason sisältötyyppi\n" " ei vastaa jotakin pääsytyypeistä\n" -" (pass_mime_types), tai jos aliosien suodatuksen\n" +" (pass_mime_types), tai jos aliosien suodatuksen\n" " jälkeen viesti on tyhjä.\n" "\n" " Huomaa, että toimenpidettä ei suoriteta, jos suodatuksen " @@ -5079,6 +5211,7 @@ msgstr "" # ####### #: Mailman/Gui/ContentFilter.py:171 +#, fuzzy msgid "Bad MIME type ignored: %(spectype)s" msgstr "Kelvoton MIME-tyyppi jätettiin huomiotta: %(spectype)s" @@ -5184,6 +5317,7 @@ msgstr "" " jos se ei ole tyhjä?" #: Mailman/Gui/Digest.py:145 +#, fuzzy msgid "" "The next digest will be sent as volume\n" " %(volume)s, number %(number)s" @@ -5200,14 +5334,17 @@ msgid "There was no digest to send." msgstr "Ei koostetta mitä lähettää." #: Mailman/Gui/GUIBase.py:173 +#, fuzzy msgid "Invalid value for variable: %(property)s" msgstr "Muuttujalla %(property)s on väärä arvo." #: Mailman/Gui/GUIBase.py:178 +#, fuzzy msgid "Bad email address for option %(property)s: %(error)s" msgstr "Virheellinen sähköpostiosoite valinnalle %(property)s: %(error)s" #: Mailman/Gui/GUIBase.py:204 +#, fuzzy msgid "" "The following illegal substitution variables were\n" " found in the %(property)s string:\n" @@ -5223,6 +5360,7 @@ msgstr "" " ongelman." #: Mailman/Gui/GUIBase.py:218 +#, fuzzy msgid "" "Your %(property)s string appeared to\n" " have some correctable problems in its new value.\n" @@ -5324,8 +5462,8 @@ msgid "" "

                      In order to split the list ownership duties into\n" " administrators and moderators, you must\n" " set a separate moderator password,\n" -" and also provide the email\n" +" and also provide the email\n" " addresses of the list moderators. Note that the field you\n" " are changing here specifies the list administrators." msgstr "" @@ -5345,8 +5483,8 @@ msgstr "" "

                      Jakaaksesi listan omistajasuhteen ylläpitäjiin ja\n" " pääkäyttäjiin, sinun täytyy \n" " asettaa erillinen pääkäyttäjän salasana,\n" -" ja antaa myös pääkäyttäjien\n" +" ja antaa myös pääkäyttäjien\n" " sähköpostiosoitteet. Huomaa, että kenttä, jota\n" " muut täällä, määrittelee listan ylläpitäjät." @@ -5400,8 +5538,8 @@ msgstr "" "

                      Jakaaksesi listan omistajasuhteen ylläpitäjiin ja\n" " pääkäyttäjiin, sinun täytyy \n" " asettaa erillinen pääkäyttäjän salasana,\n" -" ja antaa myös pääkäyttäjien\n" +" ja antaa myös pääkäyttäjien\n" " sähköpostiosoitteet. Huomaa, että kenttä, jota\n" " muutat täällä, määrittelee listan pääkäyttäjät." @@ -5648,13 +5786,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5692,8 +5830,8 @@ msgstr "" " vastausosoitteensa. Toisekseen muokkaamalla Vastaus:\n" " arvoa vaikeutetaan yksityisten vastausten lähettämistä. Katso " "Vastaus'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">Vastaus'\n" " Munging pidetään haitallisena tämän aiheen yleistä " "keskustelua\n" " Katso Reply-To: header\n" " when the reply_goes_to_list\n" +" href=\"?VARHELP=general/" +"reply_goes_to_list\">reply_goes_to_list\n" " option is set to Explicit address.\n" "\n" "

                      There are many reasons not to introduce or override the\n" @@ -5732,13 +5870,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5761,8 +5899,8 @@ msgid "" msgstr "" "Tämä on osoite, joka on asetettu Vastaus: kenttään\n" " kun reply_goes_to_list\n" +" href=\"?VARHELP=general/" +"reply_goes_to_list\">reply_goes_to_list\n" " valinta on asetettu Tiettyyn osoitteeseen.\n" "\n" "

                      On monia syitä olla ottamatta käyttöön tai kumota\n" @@ -5844,8 +5982,8 @@ msgid "" " member list addresses, but rather to the owner of those member\n" " lists. In that case, the value of this setting is appended to\n" " the member's account name for such notices. `-owner' is the\n" -" typical choice. This setting has no effect when \"umbrella_list" -"\"\n" +" typical choice. This setting has no effect when " +"\"umbrella_list\"\n" " is \"No\"." msgstr "" "Kun \"umbrella_list\" määritellään osoittaakseen, että tällä listalla on " @@ -6845,6 +6983,7 @@ msgstr "" " toisten puolesta ilman heidän suostumustaan." #: Mailman/Gui/Privacy.py:104 +#, fuzzy msgid "" "This section allows you to configure subscription and\n" " membership exposure policy. You can also control whether this\n" @@ -7044,8 +7183,8 @@ msgid "" " either individually or as a group. Any\n" " posting from a non-member who is not explicitly accepted,\n" " rejected, or discarded, will have their posting filtered by the\n" -" general\n" +" general\n" " non-member rules.\n" "\n" "

                      In the text boxes below, add one address per line; start the\n" @@ -7080,8 +7219,8 @@ msgstr "" " joko yksittäin tai ryhmänä. Mikä tahansa\n" " ei-jäseneltä tuleva viesti, jota ei erityisesti ole hyväksytty,\n" " palautettu, or hylätty, suodatetaan \n" -" yleisten\n" +" yleisten\n" " ei-jäsenten sääntöjen mukaan.\n" "\n" "

                      Alla olevissa tekstilaatikoissa, lisää yksi osoite riville;\n" @@ -7104,6 +7243,7 @@ msgid "By default, should new list member postings be moderated?" msgstr "Pitäisikö oletusarvoisesti uuden listan jäsenen lähetetykset hyväksyä?" #: Mailman/Gui/Privacy.py:218 +#, fuzzy msgid "" "Each list member has a moderation flag which says\n" " whether messages from the list member can be posted directly " @@ -7159,8 +7299,8 @@ msgid "" "If a member posts this many times, within a period of time\n" " the member is automatically moderated. Use 0 to disable. " "See\n" -" member_verbosity_interval for details on the time " "period.\n" "\n" @@ -7604,8 +7744,8 @@ msgstr "" " tiedot tarkistetaan nimenomaisesti\n" " hyväksyttyjen listasta,\n" -" pidätetään,\n" +" pidätetään,\n" " hylätään (palautetaan), ja\n" " The body of the message can also be optionally scanned for\n" " Subject: and Keywords: headers, as\n" " specified by the topics_bodylines_limit\n" +" href=\"?VARHELP=topics/" +"topics_bodylines_limit\">topics_bodylines_limit\n" " configuration variable." msgstr "" "Aiheen suodattimet luokittelee jokaisen tulevan sähköpostin\n" @@ -7963,8 +8105,8 @@ msgstr "" "

                      Viestiosa voidaan vaihtoehtoisesti myös selata\n" " Aihe: ja Avainsanat: kentillä,\n" " kuten määritelty topics_bodylines_limit\n" +" href=\"?VARHELP=topics/" +"topics_bodylines_limit\">topics_bodylines_limit\n" " määritysmuuttujassa." #: Mailman/Gui/Topics.py:72 @@ -8033,6 +8175,7 @@ msgstr "" " mallin. Epätäydellisiä aiheita ei huomioida." #: Mailman/Gui/Topics.py:135 +#, fuzzy msgid "" "The topic pattern '%(safepattern)s' is not a\n" " legal regular expression. It will be discarded." @@ -8204,8 +8347,8 @@ msgid "" " gated messages either." msgstr "" "Mailman lisää etuliitteen \"Subject:\"-otsikoihin,\n" -" joissa on muuteltavissa olevaa tekstiä, ja tavallisesti\n" +" joissa on muuteltavissa olevaa tekstiä, ja tavallisesti\n" " tämä etuliite näkyy viesteissä, jotka kulkevat yhdyskäytävää\n" " pitkin Usenetiin. Voit asettaa tämän valitsimen arvoon\n" " No (ei), mikäli haluat kieltää etuliitteet " @@ -8270,6 +8413,7 @@ msgid "%(listinfo_link)s list run by %(owner_link)s" msgstr "%(listinfo_link)s lista %(owner_link)s ylläpitäjänä" #: Mailman/HTMLFormatter.py:57 +#, fuzzy msgid "%(realname)s administrative interface" msgstr "%(realname)s ylläpito käyttöliittymä" @@ -8278,6 +8422,7 @@ msgid " (requires authorization)" msgstr " (vaatii valtuutuksen)" #: Mailman/HTMLFormatter.py:61 +#, fuzzy msgid "Overview of all %(hostname)s mailing lists" msgstr "Yleiskuva kaikista %(hostname)s postituslistoista" @@ -8298,6 +8443,7 @@ msgid "; it was disabled by the list administrator" msgstr "; listan ylläpitäjä on ottanut sen pois käytöstä" #: Mailman/HTMLFormatter.py:146 +#, fuzzy msgid "" "; it was disabled due to excessive bounces. The\n" " last bounce was received on %(date)s" @@ -8311,6 +8457,7 @@ msgid "; it was disabled for unknown reasons" msgstr "; se on otettu pois käytöstä tuntemattomasta syystä" #: Mailman/HTMLFormatter.py:151 +#, fuzzy msgid "Note: your list delivery is currently disabled%(reason)s." msgstr "Huomaa: jakelu sinulle on otettu pois käytöstä: %(reason)s." @@ -8323,6 +8470,7 @@ msgid "the list administrator" msgstr "listan ylläpitäjä" #: Mailman/HTMLFormatter.py:157 +#, fuzzy msgid "" "

                      %(note)s\n" "\n" @@ -8343,6 +8491,7 @@ msgstr "" " kysyttävää tai tarvitset apua." #: Mailman/HTMLFormatter.py:169 +#, fuzzy msgid "" "

                      We have received some recent bounces from your\n" " address. Your current bounce score is %(score)s out of " @@ -8362,6 +8511,7 @@ msgstr "" " ongelmat korjataan pian." #: Mailman/HTMLFormatter.py:181 +#, fuzzy msgid "" "(Note - you are subscribing to a list of mailing lists, so the %(type)s " "notice will be sent to the admin address for your membership, %(addr)s.)

                      " @@ -8408,6 +8558,7 @@ msgstr "" " sähköpostitse." #: Mailman/HTMLFormatter.py:208 +#, fuzzy msgid "" "This is %(also)sa private list, which means that the\n" " list of members is not available to non-members." @@ -8416,6 +8567,7 @@ msgstr "" " listan jäsentiedot eivät ole tarjolla ei-jäsenille." #: Mailman/HTMLFormatter.py:211 +#, fuzzy msgid "" "This is %(also)sa hidden list, which means that the\n" " list of members is available only to the list administrator." @@ -8424,6 +8576,7 @@ msgstr "" " listan jäsentiedot ovat tarjolla vain listan ylläpitäjälle." #: Mailman/HTMLFormatter.py:214 +#, fuzzy msgid "" "This is %(also)sa public list, which means that the\n" " list of members list is available to everyone." @@ -8440,6 +8593,7 @@ msgstr "" " yleisten roskapostilähettäjien tunnistettavissa)." #: Mailman/HTMLFormatter.py:222 +#, fuzzy msgid "" "

                      (Note that this is an umbrella list, intended to\n" " have only other mailing lists as members. Among other things,\n" @@ -8456,6 +8610,7 @@ msgid "either " msgstr "jompikumpi " #: Mailman/HTMLFormatter.py:256 +#, fuzzy msgid "" "To unsubscribe from %(realname)s, get a password reminder,\n" " or change your subscription options %(either)senter your " @@ -8490,6 +8645,7 @@ msgstr "" " sähköpostiosoitteesi" #: Mailman/HTMLFormatter.py:277 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " members.)" @@ -8498,6 +8654,7 @@ msgstr "" " saatavilla.)" #: Mailman/HTMLFormatter.py:281 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " administrator.)" @@ -8558,6 +8715,7 @@ msgid "The current archive" msgstr "Nykyinen arkisto" #: Mailman/Handlers/Acknowledge.py:59 +#, fuzzy msgid "%(realname)s post acknowledgement" msgstr "%(realname)s postitiedot" @@ -8570,6 +8728,7 @@ msgid "" msgstr "" #: Mailman/Handlers/CalcRecips.py:79 +#, fuzzy msgid "" "Your urgent message to the %(realname)s mailing list was not authorized for\n" "delivery. The original message as received by Mailman is attached.\n" @@ -8645,6 +8804,7 @@ msgid "Message may contain administrivia" msgstr "Viesti voi sisältää yläpitoa" #: Mailman/Handlers/Hold.py:84 +#, fuzzy msgid "" "Please do *not* post administrative requests to the mailing\n" "list. If you wish to subscribe, visit %(listurl)s or send a message with " @@ -8686,10 +8846,12 @@ msgid "Posting to a moderated newsgroup" msgstr "Lähetys moderoidulle listalle" #: Mailman/Handlers/Hold.py:252 +#, fuzzy msgid "Your message to %(listname)s awaits moderator approval" msgstr "Viestisi %(listname)s odottaa pääkäyttäjän hyväksyntää" #: Mailman/Handlers/Hold.py:271 +#, fuzzy msgid "%(listname)s post from %(sender)s requires approval" msgstr "%(listname)s postitukset %(sender)s vaatii hyväksynnän" @@ -8732,6 +8894,7 @@ msgid "After content filtering, the message was empty" msgstr "Viesti oli tyhjä sisältösuodatuksen jälkeen" #: Mailman/Handlers/MimeDel.py:269 +#, fuzzy msgid "" "The attached message matched the %(listname)s mailing list's content " "filtering\n" @@ -8772,6 +8935,7 @@ msgid "The attached message has been automatically discarded." msgstr "Oheinen viesti on automaattisesti hylätty." #: Mailman/Handlers/Replybot.py:75 +#, fuzzy msgid "Auto-response for your message to the \"%(realname)s\" mailing list" msgstr "Automaattinen vastaus viestiisi \"%(realname)s\" postituslistalle" @@ -8780,6 +8944,7 @@ msgid "The Mailman Replybot" msgstr "Mailmanin vastaus" #: Mailman/Handlers/Scrubber.py:208 +#, fuzzy msgid "" "An embedded and charset-unspecified text was scrubbed...\n" "Name: %(filename)s\n" @@ -8794,6 +8959,7 @@ msgid "HTML attachment scrubbed and removed" msgstr "HTML liitetiedosto poistettu" #: Mailman/Handlers/Scrubber.py:234 Mailman/Handlers/Scrubber.py:259 +#, fuzzy msgid "" "An HTML attachment was scrubbed...\n" "URL: %(url)s\n" @@ -8814,6 +8980,7 @@ msgid "unknown sender" msgstr "tuntematon lähettäjä" #: Mailman/Handlers/Scrubber.py:276 +#, fuzzy msgid "" "An embedded message was scrubbed...\n" "From: %(who)s\n" @@ -8830,6 +8997,7 @@ msgstr "" "Url: %(url)s\n" #: Mailman/Handlers/Scrubber.py:308 +#, fuzzy msgid "" "A non-text attachment was scrubbed...\n" "Name: %(filename)s\n" @@ -8846,6 +9014,7 @@ msgstr "" "Url : %(url)s\n" #: Mailman/Handlers/Scrubber.py:347 +#, fuzzy msgid "Skipped content of type %(partctype)s\n" msgstr "Ohitettu tiedostotyyppi %(partctype)s\n" @@ -8877,6 +9046,7 @@ msgid "Message rejected by filter rule match" msgstr "Viesti torjuttu suodatussäännön perusteella" #: Mailman/Handlers/ToDigest.py:173 +#, fuzzy msgid "%(realname)s Digest, Vol %(volume)d, Issue %(issue)d" msgstr "%(realname)s Lukemisto, Vol %(volume)d, Aihe %(issue)d" @@ -8913,6 +9083,7 @@ msgid "End of " msgstr "Loppu " #: Mailman/ListAdmin.py:309 +#, fuzzy msgid "Posting of your message titled \"%(subject)s\"" msgstr "Viestisi, jonka otsikko on \"%(subject)s\"" @@ -8925,6 +9096,7 @@ msgid "Forward of moderated message" msgstr "Ylläpidetyn viestin edelleenlähetys" #: Mailman/ListAdmin.py:405 +#, fuzzy msgid "New subscription request to list %(realname)s from %(addr)s" msgstr "Uusi liittymispyyntö listalle %(realname)s %(addr)s lta" @@ -8938,6 +9110,7 @@ msgid "via admin approval" msgstr "Jatka hyväksymisen odottamista" #: Mailman/ListAdmin.py:466 +#, fuzzy msgid "New unsubscription request from %(realname)s by %(addr)s" msgstr "Uusi irtisanomispyyntö listalle %(realname)s %(addr)s lta" @@ -8950,10 +9123,12 @@ msgid "Original Message" msgstr "Alkuperäinen viesti" #: Mailman/ListAdmin.py:526 +#, fuzzy msgid "Request to mailing list %(realname)s rejected" msgstr "Pyyntö listalle %(realname)s on hylätty" #: Mailman/MTA/Manual.py:66 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been created via the through-the-web\n" "interface. In order to complete the activation of this mailing list, the\n" @@ -8980,14 +9155,17 @@ msgstr "" "oheiset rivit, ja mahdollisesti ajamalla sen jälkeen ohjelma 'newaliases':\n" #: Mailman/MTA/Manual.py:82 +#, fuzzy msgid "## %(listname)s mailing list" msgstr "## %(listname)s postituslista" #: Mailman/MTA/Manual.py:99 +#, fuzzy msgid "Mailing list creation request for list %(listname)s" msgstr "Postituslistan luontipyyntö listalle %(listname)s" #: Mailman/MTA/Manual.py:113 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been removed via the through-the-web\n" "interface. In order to complete the de-activation of this mailing list, " @@ -9005,6 +9183,7 @@ msgstr "" "Tässä rivit jotka /etc/aliases tiedostosta tulisi poistaa:\n" #: Mailman/MTA/Manual.py:123 +#, fuzzy msgid "" "\n" "To finish removing your mailing list, you must edit your /etc/aliases (or\n" @@ -9021,14 +9200,17 @@ msgstr "" "## %(listname)s mailing list" #: Mailman/MTA/Manual.py:142 +#, fuzzy msgid "Mailing list removal request for list %(listname)s" msgstr "Postituslistan poistopyyntö listalle %(listname)s" #: Mailman/MTA/Postfix.py:442 +#, fuzzy msgid "checking permissions on %(file)s" msgstr "tarkistetaan tiedoston %(file)s oikeudet" #: Mailman/MTA/Postfix.py:452 +#, fuzzy msgid "%(file)s permissions must be 0664 (got %(octmode)s)" msgstr "tiedoston %(file)s oikeudet pitää olla 0664 (sain %(octmode)s)" @@ -9042,35 +9224,43 @@ msgid "(fixing)" msgstr "(korjaan)" #: Mailman/MTA/Postfix.py:470 +#, fuzzy msgid "checking ownership of %(dbfile)s" msgstr "tarkistetaan tiedoston %(dbfile)s omistusoikeudet" #: Mailman/MTA/Postfix.py:478 +#, fuzzy msgid "%(dbfile)s owned by %(owner)s (must be owned by %(user)s" msgstr "" "%(dbfile)s on käyttäjän %(owner)s omistama (pitäisi olla käyttäjän %(user)s" #: Mailman/MTA/Postfix.py:491 +#, fuzzy msgid "%(dbfile)s permissions must be 0664 (got %(octmode)s)" msgstr "tiedoston %(dbfile)s oikeudet pitää olla 0664 (sain %(octmode)s)" #: Mailman/MailList.py:219 +#, fuzzy msgid "Your confirmation is required to join the %(listname)s mailing list" msgstr "Vahvista jäsenyytesi postituslistalle %(listname)s ennen liittymistä" #: Mailman/MailList.py:230 +#, fuzzy msgid "Your confirmation is required to leave the %(listname)s mailing list" msgstr "Vahvista jäsenyytesti postituslistalle %(listname)s ennen poistumista" #: Mailman/MailList.py:999 Mailman/MailList.py:1492 +#, fuzzy msgid " from %(remote)s" msgstr " lähde: %(remote)s" #: Mailman/MailList.py:1043 +#, fuzzy msgid "subscriptions to %(realname)s require moderator approval" msgstr "listan %(realname)s tilaukset vaativat moderaattorin hyväksynnän" #: Mailman/MailList.py:1125 bin/add_members:299 +#, fuzzy msgid "%(realname)s subscription notification" msgstr "ilmoitus listan %(realname)s tilauksesta" @@ -9079,6 +9269,7 @@ msgid "unsubscriptions require moderator approval" msgstr "listalta poistuminen vaatii moderaattorin hyväksynnän" #: Mailman/MailList.py:1166 +#, fuzzy msgid "%(realname)s unsubscribe notification" msgstr "ilmoitus listalta %(realname)s poistumisesta" @@ -9098,6 +9289,7 @@ msgid "via web confirmation" msgstr "Väärä vahvistusmerkkijono" #: Mailman/MailList.py:1396 +#, fuzzy msgid "subscriptions to %(name)s require administrator approval" msgstr "listan %(name)s tilaaminen vaatii ylläpitäjän hyväksynnän" @@ -9116,6 +9308,7 @@ msgid "Last autoresponse notification for today" msgstr "Viimeinen automaattinen paluuviesti -ilmoitus tälle päivälle" #: Mailman/Queue/BounceRunner.py:360 +#, fuzzy msgid "" "The attached message was received as a bounce, but either the bounce format\n" "was not recognized, or no member addresses could be extracted from it. " @@ -9204,6 +9397,7 @@ msgid "Original message suppressed by Mailman site configuration\n" msgstr "" #: Mailman/htmlformat.py:675 +#, fuzzy msgid "Delivered by Mailman
                      version %(version)s" msgstr "Toimitettu Mailmanilla
                      versio %(version)s" @@ -9292,6 +9486,7 @@ msgid "Server Local Time" msgstr "Palvelimen paikallisaika" #: Mailman/i18n.py:180 +#, fuzzy msgid "" "%(wday)s %(mon)s %(day)2i %(hh)02i:%(mm)02i:%(ss)02i %(tzname)s %(year)04i" msgstr "" @@ -9404,6 +9599,7 @@ msgstr "" "tiedostoista voi olla '-'.\n" #: bin/add_members:162 bin/add_members:172 +#, fuzzy msgid "Already a member: %(member)s" msgstr "Jo jäsen: %(member)s" @@ -9412,10 +9608,12 @@ msgid "Bad/Invalid email address: blank line" msgstr "Epäkelpo/väärä sähköpostiosoite: tyhjä rivi" #: bin/add_members:180 +#, fuzzy msgid "Bad/Invalid email address: %(member)s" msgstr "Epäkelpo/väärä sähköpostiosoite: %(member)s" #: bin/add_members:182 +#, fuzzy msgid "Hostile address (illegal characters): %(member)s" msgstr "Vihamielinen osoite (laittomia merkkejä): %(member)s" @@ -9425,14 +9623,17 @@ msgid "Invited: %(member)s" msgstr "Tilattu: %(member)s" #: bin/add_members:187 +#, fuzzy msgid "Subscribed: %(member)s" msgstr "Tilattu: %(member)s" #: bin/add_members:237 +#, fuzzy msgid "Bad argument to -w/--welcome-msg: %(arg)s" msgstr "Väärät argumentit valitsimelle -w/--welcome-msg: %(arg)s" #: bin/add_members:244 +#, fuzzy msgid "Bad argument to -a/--admin-notify: %(arg)s" msgstr "Väärät argumentit valitsimelle -a/--admin-notify: %(arg)s" @@ -9447,6 +9648,7 @@ msgstr "" #: bin/add_members:261 bin/config_list:110 bin/export.py:271 bin/find_member:97 #: bin/inject:91 bin/list_admins:90 bin/list_members:252 bin/sync_members:222 #: cron/bumpdigests:86 +#, fuzzy msgid "No such list: %(listname)s" msgstr "Lista ei ole olemassa: %(listname)s" @@ -9457,6 +9659,7 @@ msgid "Nothing to do." msgstr "Ei mitään tehtävää." #: bin/arch:19 +#, fuzzy msgid "" "Rebuild a list's archive.\n" "\n" @@ -9553,6 +9756,7 @@ msgid "listname is required" msgstr "vaaditaan listannimi" #: bin/arch:143 bin/change_pw:107 bin/config_list:257 +#, fuzzy msgid "" "No such list \"%(listname)s\"\n" "%(e)s" @@ -9561,10 +9765,12 @@ msgstr "" "%(e)s" #: bin/arch:168 +#, fuzzy msgid "Cannot open mbox file %(mbox)s: %(msg)s" msgstr "Ei voida avata mbox -tiedostoa %(mbox)s: %(msg)s" #: bin/b4b5-archfix:19 +#, fuzzy msgid "" "Fix the MM2.1b4 archives.\n" "\n" @@ -9710,6 +9916,7 @@ msgstr "" " Print this help message and exit.\n" #: bin/change_pw:145 +#, fuzzy msgid "Bad arguments: %(strargs)s" msgstr "Väärät argumentit: %(strargs)s" @@ -9718,14 +9925,17 @@ msgid "Empty list passwords are not allowed" msgstr "Tyhjät listan salasanat eivät ole sallittu" #: bin/change_pw:181 +#, fuzzy msgid "New %(listname)s password: %(notifypassword)s" msgstr "Uusi %(listname)s salasana: %(notifypassword)s" #: bin/change_pw:190 +#, fuzzy msgid "Your new %(listname)s list password" msgstr "Uuden postituslistasi %(listname)s listasalasana" #: bin/change_pw:191 +#, fuzzy msgid "" "The site administrator at %(hostname)s has changed the password for your\n" "mailing list %(listname)s. It is now\n" @@ -9753,6 +9963,7 @@ msgstr "" " %(adminurl)s\n" #: bin/check_db:19 +#, fuzzy msgid "" "Check a list's config database file for integrity.\n" "\n" @@ -9830,10 +10041,12 @@ msgid "List:" msgstr "Lista:" #: bin/check_db:148 +#, fuzzy msgid " %(file)s: okay" msgstr " %(file)s: selvä" #: bin/check_perms:20 +#, fuzzy msgid "" "Check the permissions for the Mailman installation.\n" "\n" @@ -9852,44 +10065,54 @@ msgstr "" "permission problems found. With -v be verbose.\n" #: bin/check_perms:110 +#, fuzzy msgid " checking gid and mode for %(path)s" msgstr " tarkistetaan polun %(path)s ryhmätunnistetta (gid) ja oikeuksia" #: bin/check_perms:122 +#, fuzzy msgid "%(path)s bad group (has: %(groupname)s, expected %(MAILMAN_GROUP)s)" msgstr "" "Polulla %(path)s on epäkelpo ryhmä (%(groupname)s, odotettu " "%(MAILMAN_GROUP)s)" #: bin/check_perms:151 +#, fuzzy msgid "directory permissions must be %(octperms)s: %(path)s" msgstr "Hakemiston oikeuksien pitää olla %(octperms)s: %(path)s" #: bin/check_perms:160 +#, fuzzy msgid "source perms must be %(octperms)s: %(path)s" msgstr "lähteen oikeudet pitää olla %(octperms)s: %(path)s" #: bin/check_perms:171 +#, fuzzy msgid "article db files must be %(octperms)s: %(path)s" msgstr "artikkelitietokantojen oikeuksien pitää olla %(octperms)s: %(path)s" #: bin/check_perms:183 +#, fuzzy msgid "checking mode for %(prefix)s" msgstr "tarkistetaan oikeuksia %(prefix)s" #: bin/check_perms:193 +#, fuzzy msgid "WARNING: directory does not exist: %(d)s" msgstr "Hakemiston pitää olla vähintään 02775: %(d)s" #: bin/check_perms:197 +#, fuzzy msgid "directory must be at least 02775: %(d)s" msgstr "Hakemiston pitää olla vähintään 02775: %(d)s" #: bin/check_perms:209 +#, fuzzy msgid "checking perms on %(private)s" msgstr "tarkistetaan oikeuksia polulle %(private)s" #: bin/check_perms:214 +#, fuzzy msgid "%(private)s must not be other-readable" msgstr "%(private)s ei saa olla muiden (other) luettavissa" @@ -9913,6 +10136,7 @@ msgid "mbox file must be at least 0660:" msgstr "mbox -tiedoston pitää olla vähintään 0660:" #: bin/check_perms:263 +#, fuzzy msgid "%(dbdir)s \"other\" perms must be 000" msgstr "%(dbdir)s \"other\" -oikeudet pitää olla 000" @@ -9921,26 +10145,32 @@ msgid "checking cgi-bin permissions" msgstr "Tarkistetaan cgi-bin oikeuksia" #: bin/check_perms:278 +#, fuzzy msgid " checking set-gid for %(path)s" msgstr " tarkistetaan set-gid:tä polulle %(path)s" #: bin/check_perms:282 +#, fuzzy msgid "%(path)s must be set-gid" msgstr "%(path)s pitää olla set-gid" #: bin/check_perms:292 +#, fuzzy msgid "checking set-gid for %(wrapper)s" msgstr "tarkistetaan set-gid %(wrapper)s:ille" #: bin/check_perms:296 +#, fuzzy msgid "%(wrapper)s must be set-gid" msgstr "%(wrapper)s pitää olla set-gid" #: bin/check_perms:306 +#, fuzzy msgid "checking permissions on %(pwfile)s" msgstr "tarkistetaan oikeuksia tiedostolle %(pwfile)s" #: bin/check_perms:315 +#, fuzzy msgid "%(pwfile)s permissions must be exactly 0640 (got %(octmode)s)" msgstr "" "tiedoston %(pwfile)s oikeuksien pitää olla tarkalleen 0640 (nyt %(octmode)s)" @@ -9950,10 +10180,12 @@ msgid "checking permissions on list data" msgstr "Tarkistetaan oikeuksia listadatalle" #: bin/check_perms:348 +#, fuzzy msgid " checking permissions on: %(path)s" msgstr " tarkistetaan oikeuksia polulle %(path)s" #: bin/check_perms:356 +#, fuzzy msgid "file permissions must be at least 660: %(path)s" msgstr "Tiedosto-oikeuksien pitää olla vähintään 660: %(path)s" @@ -10040,6 +10272,7 @@ msgid "Unix-From line changed: %(lineno)d" msgstr "Unix-From rivi muuttunut: %(lineno)d" #: bin/cleanarch:111 +#, fuzzy msgid "Bad status number: %(arg)s" msgstr "Epäkelpo statusnumero: %(arg)s" @@ -10192,10 +10425,12 @@ msgid " original address removed:" msgstr " alkuperäinen osoite poistettu:" #: bin/clone_member:202 +#, fuzzy msgid "Not a valid email address: %(toaddr)s" msgstr "Laiton sähköpostiosoite: %(toaddr)s" #: bin/clone_member:215 +#, fuzzy msgid "" "Error opening list \"%(listname)s\", skipping.\n" "%(e)s" @@ -10308,6 +10543,7 @@ msgstr "" "\n" #: bin/config_list:118 +#, fuzzy msgid "" "# -*- python -*-\n" "# -*- coding: %(charset)s -*-\n" @@ -10328,22 +10564,27 @@ msgid "legal values are:" msgstr "lailliset arvot ovat:" #: bin/config_list:270 +#, fuzzy msgid "attribute \"%(k)s\" ignored" msgstr "lisämääre \"%(k)s\" jätetty huomiotta" #: bin/config_list:273 +#, fuzzy msgid "attribute \"%(k)s\" changed" msgstr "lisämääre \"%(k)s\" muuttunut" #: bin/config_list:279 +#, fuzzy msgid "Non-standard property restored: %(k)s" msgstr "Ei-standardi ominaisuus palautettu: %(k)s" #: bin/config_list:288 +#, fuzzy msgid "Invalid value for property: %(k)s" msgstr "Muuttujalla väärä arvo: %(k)s" #: bin/config_list:291 +#, fuzzy msgid "Bad email address for option %(k)s: %(v)s" msgstr "Virheellinen sähköpostiosoite valinnalle %(k)s: %(v)s" @@ -10408,18 +10649,22 @@ msgstr "" " Don't print status messages.\n" #: bin/discard:94 +#, fuzzy msgid "Ignoring non-held message: %(f)s" msgstr "Ohitetaan pitämätön (non-held) viesti: %(f)s" #: bin/discard:100 +#, fuzzy msgid "Ignoring held msg w/bad id: %(f)s" msgstr "Ohitetaan pidetty (held) viesti jossa virheellinen id: %(f)s" #: bin/discard:112 +#, fuzzy msgid "Discarded held msg #%(id)s for list %(listname)s" msgstr "Poistettu pidetty (held) viesti #%(id)s listalle %(listname)s" #: bin/dumpdb:19 +#, fuzzy msgid "" "Dump the contents of any Mailman `database' file.\n" "\n" @@ -10494,6 +10739,7 @@ msgid "No filename given." msgstr "Tiedostonimeä ei annettu." #: bin/dumpdb:108 +#, fuzzy msgid "Bad arguments: %(pargs)s" msgstr "Epäkelvolliset argumentit: %(pargs)s" @@ -10502,14 +10748,17 @@ msgid "Please specify either -p or -m." msgstr "Määritä joko -p tai -m." #: bin/dumpdb:133 +#, fuzzy msgid "[----- start %(typename)s file -----]" msgstr "[----- aloitus %(typename)s tiedosto -----]" #: bin/dumpdb:139 +#, fuzzy msgid "[----- end %(typename)s file -----]" msgstr "[----- lopetus %(typename)s tiedosto -----]" #: bin/dumpdb:142 +#, fuzzy msgid "<----- start object %(cnt)s ----->" msgstr "<----- aloita kohde %(cnt)s ----->" @@ -10731,6 +10980,7 @@ msgid "Setting web_page_url to: %(web_page_url)s" msgstr "Asetetaan web_page_url osoitteeksi: %(web_page_url)s" #: bin/fix_url.py:88 +#, fuzzy msgid "Setting host_name to: %(mailhost)s" msgstr "Asetetaan host_name nimelle: %(mailhost)s" @@ -10823,6 +11073,7 @@ msgstr "" "standard input is used.\n" #: bin/inject:84 +#, fuzzy msgid "Bad queue directory: %(qdir)s" msgstr "Virheellinen jonokansio %(qdir)s" @@ -10831,6 +11082,7 @@ msgid "A list name is required" msgstr "Listan nimi vaaditaan." #: bin/list_admins:20 +#, fuzzy msgid "" "List all the owners of a mailing list.\n" "\n" @@ -10877,6 +11129,7 @@ msgstr "" "have more than one named list on the command line.\n" #: bin/list_admins:97 +#, fuzzy msgid "List: %(listname)s, \tOwners: %(owners)s" msgstr "Lista: %(listname)s, \tOmistajat: %(owners)s" @@ -11061,10 +11314,12 @@ msgstr "" "status.\n" #: bin/list_members:198 +#, fuzzy msgid "Bad --nomail option: %(why)s" msgstr "Virheellinen --nomail valitsin: %(why)s" #: bin/list_members:209 +#, fuzzy msgid "Bad --digest option: %(kind)s" msgstr "Virheellinen --digest valitsin: %(kind)s" @@ -11078,6 +11333,7 @@ msgid "Could not open file for writing:" msgstr "Ei voi avata tiedostoa kirjoitusta varten:" #: bin/list_owners:20 +#, fuzzy msgid "" "List the owners of a mailing list, or all mailing lists.\n" "\n" @@ -11132,6 +11388,7 @@ msgid "" msgstr "" #: bin/mailmanctl:20 +#, fuzzy msgid "" "Primary start-up and shutdown script for Mailman's qrunner daemon.\n" "\n" @@ -11318,6 +11575,7 @@ msgstr "" " next time a message is written to them\n" #: bin/mailmanctl:152 +#, fuzzy msgid "PID unreadable in: %(pidfile)s" msgstr "PID ei luettavissa: %(pidfile)s" @@ -11326,6 +11584,7 @@ msgid "Is qrunner even running?" msgstr "Onko qrunner ajossa?" #: bin/mailmanctl:160 +#, fuzzy msgid "No child with pid: %(pid)s" msgstr "Ei lasta pid:llä: %(pid)s" @@ -11353,6 +11612,7 @@ msgstr "" "-s valitsinta.\n" #: bin/mailmanctl:233 +#, fuzzy msgid "" "The master qrunner lock could not be acquired, because it appears as if " "some\n" @@ -11377,10 +11637,12 @@ msgstr "" "Lopetetaan." #: bin/mailmanctl:280 cron/mailpasswds:119 +#, fuzzy msgid "Site list is missing: %(sitelistname)s" msgstr "Palvelinlista puuttuu: %(sitelistname)s" #: bin/mailmanctl:305 +#, fuzzy msgid "Run this program as root or as the %(name)s user, or use -u." msgstr "Aja tämä ohjelma käyttänä root tai %(name)s, tai käytä valitsinta -u." @@ -11389,6 +11651,7 @@ msgid "No command given." msgstr "Ei komentoa" #: bin/mailmanctl:339 +#, fuzzy msgid "Bad command: %(command)s" msgstr "Virheellinen käsky: %(command)s" @@ -11413,6 +11676,7 @@ msgid "Starting Mailman's master qrunner." msgstr "Käynnistetään Mailman ylläpito grunner." #: bin/mmsitepass:19 +#, fuzzy msgid "" "Set the site password, prompting from the terminal.\n" "\n" @@ -11466,6 +11730,7 @@ msgid "list creator" msgstr "listan luoja" #: bin/mmsitepass:86 +#, fuzzy msgid "New %(pwdesc)s password: " msgstr "Uusi %(pwdesc)s salasana: " @@ -11732,6 +11997,7 @@ msgstr "" "Note that listnames are forced to lowercase.\n" #: bin/newlist:162 +#, fuzzy msgid "Unknown language: %(lang)s" msgstr "Tuntematon kieli: %(lang)s" @@ -11744,6 +12010,7 @@ msgid "Enter the email of the person running the list: " msgstr "Anna listan ylläpitäjän sähköpostiosoite: " #: bin/newlist:193 +#, fuzzy msgid "Initial %(listname)s password: " msgstr "Alustava %(listname)s salasana: " @@ -11753,11 +12020,12 @@ msgstr "Listan salasana ei voi olla tyhj #: bin/newlist:220 msgid "" -" - owner addresses need to be fully-qualified names like \"owner@example.com" -"\", not just \"owner\"." +" - owner addresses need to be fully-qualified names like \"owner@example." +"com\", not just \"owner\"." msgstr "" #: bin/newlist:244 +#, fuzzy msgid "Hit enter to notify %(listname)s owner..." msgstr "Paina enteriä ilmoittaaksesi listan %(listname)s omistajalle..." @@ -11893,6 +12161,7 @@ msgstr "" "operation. It is only useful for debugging if it is run separately.\n" #: bin/qrunner:179 +#, fuzzy msgid "%(name)s runs the %(runnername)s qrunner" msgstr "%(name)s käyttää %(runnername)s qrunner:ia" @@ -12052,18 +12321,22 @@ msgstr "" " addr1 ... are additional addresses to remove.\n" #: bin/remove_members:156 +#, fuzzy msgid "Could not open file for reading: %(filename)s." msgstr "Ei voitu avata tiedostoa lukemista varten: %(filename)s." #: bin/remove_members:163 +#, fuzzy msgid "Error opening list %(listname)s... skipping." msgstr "Virhe avattaessa postituslistaa %(listname)s...ohitetaan." #: bin/remove_members:173 +#, fuzzy msgid "No such member: %(addr)s" msgstr "Ei jäsentä: %(addr)s." #: bin/remove_members:178 +#, fuzzy msgid "User `%(addr)s' removed from list: %(listname)s." msgstr "Käyttäjä `%(addr)s' poistettu listalta: %(listname)s." @@ -12104,10 +12377,12 @@ msgstr "" " Print what the script is doing.\n" #: bin/reset_pw.py:77 +#, fuzzy msgid "Changing passwords for list: %(listname)s" msgstr "Vaihdetaan salasanat postilistalle %(listname)s" #: bin/reset_pw.py:83 +#, fuzzy msgid "New password for member %(member)40s: %(randompw)s" msgstr "Uusi salasana käyttäjälle %(member)40s: %(randompw)s" @@ -12152,18 +12427,22 @@ msgstr "" "\n" #: bin/rmlist:73 bin/rmlist:76 +#, fuzzy msgid "Removing %(msg)s" msgstr "Poistetaan %(msg)s" #: bin/rmlist:81 +#, fuzzy msgid "%(listname)s %(msg)s not found as %(filename)s" msgstr "%(listname)s %(msg)s ei löytynyt hakemistosta %(filename)s" #: bin/rmlist:105 +#, fuzzy msgid "No such list (or list already deleted): %(listname)s" msgstr "Ei listaa (tai lista on jo poistettu): %(listname)s" #: bin/rmlist:108 +#, fuzzy msgid "No such list: %(listname)s. Removing its residual archives." msgstr "Ei postituslistaa: %(listname)s. Poistetaan jääneet arkistot." @@ -12224,6 +12503,7 @@ msgstr "" "Example: show_qfiles qfiles/shunt/*.pck\n" #: bin/sync_members:19 +#, fuzzy msgid "" "Synchronize a mailing list's membership with a flat file.\n" "\n" @@ -12358,6 +12638,7 @@ msgstr "" " Required. This specifies the list to synchronize.\n" #: bin/sync_members:115 +#, fuzzy msgid "Bad choice: %(yesno)s" msgstr "Epäkelpo valinta: %(yesno)s" @@ -12374,6 +12655,7 @@ msgid "No argument to -f given" msgstr "Argumentti valitsimelle -f puuttuu" #: bin/sync_members:172 +#, fuzzy msgid "Illegal option: %(opt)s" msgstr "Laiton valitsin: %(opt)s" @@ -12386,6 +12668,7 @@ msgid "Must have a listname and a filename" msgstr "Tarvitaan sekä listan nimi että tiedostonimi" #: bin/sync_members:191 +#, fuzzy msgid "Cannot read address file: %(filename)s: %(msg)s" msgstr "Ei voida lukea osoitetiedostoa: %(filename)s: %(msg)s" @@ -12402,14 +12685,17 @@ msgid "You must fix the preceding invalid addresses first." msgstr "Sinun täytyy ensiksi korjata edeltävät virheelliset osoitteet." #: bin/sync_members:264 +#, fuzzy msgid "Added : %(s)s" msgstr "Lisätty : %(s)s" #: bin/sync_members:289 +#, fuzzy msgid "Removed: %(s)s" msgstr "Poistettu: %(s)s" #: bin/transcheck:19 +#, fuzzy msgid "" "\n" "Check a given Mailman translation, making sure that variables and\n" @@ -12487,6 +12773,7 @@ msgid "scan the po file comparing msgids with msgstrs" msgstr "käy läpi po tiedosto ja vertaa msgid arvoja msgstr- arvoihin" #: bin/unshunt:20 +#, fuzzy msgid "" "Move a message from the shunt queue to the original queue.\n" "\n" @@ -12517,6 +12804,7 @@ msgstr "" "will result in losing all the messages in that queue.\n" #: bin/unshunt:85 +#, fuzzy msgid "" "Cannot unshunt message %(filebase)s, skipping:\n" "%(e)s" @@ -12525,6 +12813,7 @@ msgstr "" "%(e)s" #: bin/update:20 +#, fuzzy msgid "" "Perform all necessary upgrades.\n" "\n" @@ -12561,14 +12850,17 @@ msgstr "" "julkaisuun. Skripti tuntee vanhat julkaisut aina versioon 1.0b4 asti (?).\n" #: bin/update:107 +#, fuzzy msgid "Fixing language templates: %(listname)s" msgstr "Korjataan kielikohtaisia kaavaintiedostoja: %(listname)s" #: bin/update:196 bin/update:711 +#, fuzzy msgid "WARNING: could not acquire lock for list: %(listname)s" msgstr "VAROITUS: listaa %(listname)s ei voitu lukita" #: bin/update:215 +#, fuzzy msgid "Resetting %(n)s BYBOUNCEs disabled addrs with no bounce info" msgstr "" "Resetoidaan %(n)s BYBOUNCE:a estetty osoite ilman kimmoke (bounce) tietoa" @@ -12587,6 +12879,7 @@ msgstr "" "joten muutan tiedoston nimelle %(mbox_dir)s.tmp ja jatkan." #: bin/update:255 +#, fuzzy msgid "" "\n" "%(listname)s has both public and private mbox archives. Since this list\n" @@ -12637,6 +12930,7 @@ msgid "- updating old private mbox file" msgstr "- päivitetään vanha yksityinen mbox tiedosto" #: bin/update:295 +#, fuzzy msgid "" " unknown file in the way, moving\n" " %(o_pri_mbox_file)s\n" @@ -12653,6 +12947,7 @@ msgid "- updating old public mbox file" msgstr "- päivitetään vanha julkinen mbox tiedosto" #: bin/update:317 +#, fuzzy msgid "" " unknown file in the way, moving\n" " %(o_pub_mbox_file)s\n" @@ -12681,18 +12976,22 @@ msgid "- %(o_tmpl)s doesn't exist, leaving untouched" msgstr "- %(o_tmpl)s ei ole olemassa, ei tehdä mitään" #: bin/update:396 +#, fuzzy msgid "removing directory %(src)s and everything underneath" msgstr "poistetaan hakemisto %(src)s alihakemistoineen" #: bin/update:399 +#, fuzzy msgid "removing %(src)s" msgstr "poistetaan %(src)s" #: bin/update:403 +#, fuzzy msgid "Warning: couldn't remove %(src)s -- %(rest)s" msgstr "Varoitus: ei voitu poistaa %(src)s -- %(rest)s" #: bin/update:408 +#, fuzzy msgid "couldn't remove old file %(pyc)s -- %(rest)s" msgstr "ei voitu poistaa vanhaa tiedostoa %(pyc)s -- %(rest)s" @@ -12701,14 +13000,17 @@ msgid "updating old qfiles" msgstr "päivitetään vanhoja q-tiedostoja" #: bin/update:455 +#, fuzzy msgid "Warning! Not a directory: %(dirpath)s" msgstr "Varoitus! Ei hakemistoa: %(dirpath)s" #: bin/update:530 +#, fuzzy msgid "message is unparsable: %(filebase)s" msgstr "viestiä ei voitu käsitellä (unparsable): %(filebase)s" #: bin/update:544 +#, fuzzy msgid "Warning! Deleting empty .pck file: %(pckfile)s" msgstr "Varoitus! Poistetaan tyhjä .pck tiedosto: %(pckfile)s" @@ -12721,10 +13023,12 @@ msgid "Updating Mailman 2.1.4 pending.pck database" msgstr "Päivitetään Mailman 2.1.4 pending.pck tietokanta" #: bin/update:598 +#, fuzzy msgid "Ignoring bad pended data: %(key)s: %(val)s" msgstr "Ohitetaan virheellinen data (pended data): %(key)s: %(val)s" #: bin/update:614 +#, fuzzy msgid "WARNING: Ignoring duplicate pending ID: %(id)s." msgstr "VAROITUS: Ohitetaan kaksinkertainen ID (duplicate pending ID): %(id)s." @@ -12750,6 +13054,7 @@ msgid "done" msgstr "valmis" #: bin/update:691 +#, fuzzy msgid "Updating mailing list: %(listname)s" msgstr "Päivitetään postituslistoja: %(listname)s" @@ -12812,6 +13117,7 @@ msgid "No updates are necessary." msgstr "Päivitys ei ole tarpeen." #: bin/update:796 +#, fuzzy msgid "" "Downgrade detected, from version %(hexlversion)s to version %(hextversion)s\n" "This is probably not safe.\n" @@ -12823,10 +13129,12 @@ msgstr "" "Poistutaan." #: bin/update:801 +#, fuzzy msgid "Upgrading from version %(hexlversion)s to %(hextversion)s" msgstr "Päivitetään versiosta %(hexlversion)s versioon %(hextversion)s" #: bin/update:810 +#, fuzzy msgid "" "\n" "ERROR:\n" @@ -13108,6 +13416,7 @@ msgstr "" " " #: bin/withlist:175 +#, fuzzy msgid "Unlocking (but not saving) list: %(listname)s" msgstr "Avataan (mutta ei talleteta) listaa: %(listname)s" @@ -13116,6 +13425,7 @@ msgid "Finalizing" msgstr "Viimeistellään" #: bin/withlist:188 +#, fuzzy msgid "Loading list %(listname)s" msgstr "Ladataan listaa %(listname)s" @@ -13128,6 +13438,7 @@ msgid "(unlocked)" msgstr "(ei lukittu)" #: bin/withlist:197 +#, fuzzy msgid "Unknown list: %(listname)s" msgstr "Tuntematon postituslista: %(listname)s" @@ -13140,18 +13451,22 @@ msgid "--all requires --run" msgstr "--all vaatii parametrin --run" #: bin/withlist:266 +#, fuzzy msgid "Importing %(module)s..." msgstr "Lisätään (import) %(module)s..." #: bin/withlist:270 +#, fuzzy msgid "Running %(module)s.%(callable)s()..." msgstr "Suoritetaan %(module)s.%(callable)s()..." #: bin/withlist:291 +#, fuzzy msgid "The variable `m' is the %(listname)s MailList instance" msgstr "Muuttuja `m' on %(listname)s MailList instanssi" #: cron/bumpdigests:19 +#, fuzzy msgid "" "Increment the digest volume number and reset the digest number to one.\n" "\n" @@ -13179,6 +13494,7 @@ msgstr "" "all lists are bumped.\n" #: cron/checkdbs:20 +#, fuzzy msgid "" "Check for pending admin requests and mail the list owners if necessary.\n" "\n" @@ -13208,10 +13524,12 @@ msgstr "" "\n" #: cron/checkdbs:121 +#, fuzzy msgid "%(count)d %(realname)s moderator request(s) waiting" msgstr "%(count)d %(realname)s moderaattoripyyntö(ä) odottamassa" #: cron/checkdbs:124 +#, fuzzy msgid "%(realname)s moderator request check result" msgstr "%(realname)s moderaattori pyyntö tulos" @@ -13233,6 +13551,7 @@ msgstr "" "Odottavat viestit:" #: cron/checkdbs:169 +#, fuzzy msgid "" "From: %(sender)s on %(date)s\n" "Subject: %(subject)s\n" @@ -13243,6 +13562,7 @@ msgstr "" "Syy: %(reason)s" #: cron/cull_bad_shunt:20 +#, fuzzy msgid "" "Cull bad and shunt queues, recommended once per day.\n" "\n" @@ -13281,6 +13601,7 @@ msgstr "" " Print this message and exit.\n" #: cron/disabled:20 +#, fuzzy msgid "" "Process disabled members, recommended once per day.\n" "\n" @@ -13405,6 +13726,7 @@ msgstr "" "\n" #: cron/mailpasswds:19 +#, fuzzy msgid "" "Send password reminders for all lists to all users.\n" "\n" @@ -13459,10 +13781,12 @@ msgid "Password // URL" msgstr "Salasana // URL" #: cron/mailpasswds:222 +#, fuzzy msgid "%(host)s mailing list memberships reminder" msgstr "%(host)s postituslista käyttäjämuistutukset" #: cron/nightly_gzip:19 +#, fuzzy msgid "" "Re-generate the Pipermail gzip'd archive flat files.\n" "\n" @@ -13563,8 +13887,8 @@ msgstr "" #~ " applied" #~ msgstr "" #~ "Teksti, joka lisätään mihin tahansa\n" -#~ " hylkäysilmoitukseen to\n" #~ " lähetettäväksi jäsenelle, joka postittaa tälle listalle." diff --git a/messages/fr/LC_MESSAGES/mailman.po b/messages/fr/LC_MESSAGES/mailman.po index 5ceecc76..67d4f8ed 100755 --- a/messages/fr/LC_MESSAGES/mailman.po +++ b/messages/fr/LC_MESSAGES/mailman.po @@ -66,10 +66,12 @@ msgid "

                      Currently, there are no archives.

                      " msgstr "

                      Actuellement, pas d'archives.

                      " #: Mailman/Archiver/HyperArch.py:821 +#, fuzzy msgid "Gzip'd Text%(sz)s" msgstr "Text%(sz)s Gzipés" #: Mailman/Archiver/HyperArch.py:826 +#, fuzzy msgid "Text%(sz)s" msgstr "Text%(sz)s" @@ -142,18 +144,22 @@ msgid "Third" msgstr "Troisième" #: Mailman/Archiver/HyperArch.py:938 +#, fuzzy msgid "%(ord)s quarter %(year)i" msgstr "%(ord)s trimestre %(year)i" #: Mailman/Archiver/HyperArch.py:945 +#, fuzzy msgid "%(month)s %(year)i" msgstr "%(month)s %(year)i" #: Mailman/Archiver/HyperArch.py:950 +#, fuzzy msgid "The Week Of Monday %(day)i %(month)s %(year)i" msgstr "La semaine du lundi %(day)i %(month)s %(year)i" #: Mailman/Archiver/HyperArch.py:954 +#, fuzzy msgid "%(day)i %(month)s %(year)i" msgstr "%(day)i %(month)s %(year)i" @@ -162,10 +168,12 @@ msgid "Computing threaded index\n" msgstr "" #: Mailman/Archiver/HyperArch.py:1319 +#, fuzzy msgid "Updating HTML for article %(seq)s" msgstr "Mise à jour des fichiers HTML pour l'article %(seq)s" #: Mailman/Archiver/HyperArch.py:1326 +#, fuzzy msgid "article file %(filename)s is missing!" msgstr "le fichier article %(filename)s est manquant!" @@ -182,6 +190,7 @@ msgid "Pickling archive state into " msgstr "Pickle de l'état des archives vers " #: Mailman/Archiver/pipermail.py:453 +#, fuzzy msgid "Updating index files for archive [%(archive)s]" msgstr "Mise à jour des fichiers d'index pour les archives [%(archive)s]" @@ -190,6 +199,7 @@ msgid " Thread" msgstr "\tEnfilade" #: Mailman/Archiver/pipermail.py:597 +#, fuzzy msgid "#%(counter)05d %(msgid)s" msgstr "#%(counter)05d %(msgid)s" @@ -227,6 +237,7 @@ msgid "disabled address" msgstr "désactivé" #: Mailman/Bouncer.py:304 +#, fuzzy msgid " The last bounce received from you was dated %(date)s" msgstr " Le dernier rejet en provenance de votre adresse date du %(date)s" @@ -254,6 +265,7 @@ msgstr "Administrateur" #: Mailman/Cgi/options.py:101 Mailman/Cgi/private.py:108 #: Mailman/Cgi/rmlist.py:75 Mailman/Cgi/roster.py:59 #: Mailman/Cgi/subscribe.py:67 +#, fuzzy msgid "No such list %(safelistname)s" msgstr "Liste %(safelistname)s inexistante" @@ -326,6 +338,7 @@ msgstr "" "problème.%(rm)r" #: Mailman/Cgi/admin.py:268 +#, fuzzy msgid "%(hostname)s mailing lists - Admin Links" msgstr "Listes de diffusion de %(hostname)s - Liens de l'Admin" @@ -338,6 +351,7 @@ msgid "Mailman" msgstr "Mailman" #: Mailman/Cgi/admin.py:310 +#, fuzzy msgid "" "

                      There currently are no publicly-advertised %(mailmanlink)s\n" " mailing lists on %(hostname)s." @@ -346,6 +360,7 @@ msgstr "" "\tpubliques sur %(hostname)s - " #: Mailman/Cgi/admin.py:316 +#, fuzzy msgid "" "

                      Below is the collection of publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s. Click on a list\n" @@ -361,6 +376,7 @@ msgid "right " msgstr "droite " #: Mailman/Cgi/admin.py:325 +#, fuzzy msgid "" "To visit the administrators configuration page for an\n" " unadvertised list, open a URL similar to this one, but with a '/' " @@ -406,6 +422,7 @@ msgid "No valid variable name found." msgstr "Aucun nom de variable valide trouvé." #: Mailman/Cgi/admin.py:395 +#, fuzzy msgid "" "%(realname)s Mailing list Configuration Help\n" "
                      %(varname)s Option" @@ -414,6 +431,7 @@ msgstr "" "\t
                      Option %(varname)s" #: Mailman/Cgi/admin.py:402 +#, fuzzy msgid "Mailman %(varname)s List Option Help" msgstr "Aide Mailman sur l'option de liste %(varname)s" @@ -434,14 +452,17 @@ msgstr "" " " #: Mailman/Cgi/admin.py:431 +#, fuzzy msgid "return to the %(categoryname)s options page." msgstr "Retourner à la page des options de type %(categoryname)s." #: Mailman/Cgi/admin.py:446 +#, fuzzy msgid "%(realname)s Administration (%(label)s)" msgstr "Administration %(realname)s (%(label)s)" #: Mailman/Cgi/admin.py:447 +#, fuzzy msgid "%(realname)s mailing list administration
                      %(label)s Section" msgstr "Administration de la liste %(realname)s
                      Section %(label)s" @@ -524,6 +545,7 @@ msgid "Value" msgstr "Valeur" #: Mailman/Cgi/admin.py:668 +#, fuzzy msgid "" "Badly formed options entry:\n" " %(record)s" @@ -624,10 +646,12 @@ msgid "Move rule down" msgstr "Descendre la règle" #: Mailman/Cgi/admin.py:870 +#, fuzzy msgid "
                      (Edit %(varname)s)" msgstr "
                      (Editer %(varname)s)" #: Mailman/Cgi/admin.py:872 +#, fuzzy msgid "
                      (Details for %(varname)s)" msgstr "
                      (Détails de %(varname)s)" @@ -668,6 +692,7 @@ msgid "(help)" msgstr "(aide)" #: Mailman/Cgi/admin.py:930 +#, fuzzy msgid "Find member %(link)s:" msgstr "Rechercher l'abonné: %(link)s:" @@ -680,10 +705,12 @@ msgid "Bad regular expression: " msgstr "Expression régulière invalide : " #: Mailman/Cgi/admin.py:1013 +#, fuzzy msgid "%(allcnt)s members total, %(membercnt)s shown" msgstr "Total des abonnés %(allcnt)s, %(membercnt)s affichés" #: Mailman/Cgi/admin.py:1016 +#, fuzzy msgid "%(allcnt)s members total" msgstr "total des abonnés %(allcnt)s" @@ -869,6 +896,7 @@ msgstr "" "dessous :" #: Mailman/Cgi/admin.py:1221 +#, fuzzy msgid "from %(start)s to %(end)s" msgstr "De %(start)s à %(end)s" @@ -1006,6 +1034,7 @@ msgid "Change list ownership passwords" msgstr "Modifier le mot de passe des propriétaires de la liste" #: Mailman/Cgi/admin.py:1364 +#, fuzzy msgid "" "The list administrators are the people who have ultimate control " "over\n" @@ -1119,8 +1148,9 @@ msgstr "Adresse hostile (caract #: Mailman/Cgi/admin.py:1529 Mailman/Cgi/admin.py:1703 bin/add_members:175 #: bin/clone_member:136 bin/sync_members:268 +#, fuzzy msgid "Banned address (matched %(pattern)s)" -msgstr "" +msgstr "%(addr)s est interdite (correspondance: %(patt)s)" #: Mailman/Cgi/admin.py:1535 msgid "Successfully invited:" @@ -1222,6 +1252,7 @@ msgid "Not subscribed" msgstr "Pas abonné" #: Mailman/Cgi/admin.py:1773 +#, fuzzy msgid "Ignoring changes to deleted member: %(user)s" msgstr "" "Les modifications apportées à l'abonné supprimé sont ignorées : %(user)s" @@ -1235,10 +1266,12 @@ msgid "Error Unsubscribing:" msgstr "Erreur lors de la résiliation :" #: Mailman/Cgi/admindb.py:235 Mailman/Cgi/admindb.py:248 +#, fuzzy msgid "%(realname)s Administrative Database" msgstr "Base de données administrative de %(realname)s" #: Mailman/Cgi/admindb.py:238 +#, fuzzy msgid "%(realname)s Administrative Database Results" msgstr "Résultats de la base de données administrative de %(realname)s" @@ -1267,6 +1300,7 @@ msgid "Discard all messages marked Defer" msgstr "" #: Mailman/Cgi/admindb.py:298 +#, fuzzy msgid "all of %(esender)s's held messages." msgstr "tous les messages de %(esender)s en attente." @@ -1287,6 +1321,7 @@ msgid "list of available mailing lists." msgstr "Catalogue des listes de diffusion disponibles." #: Mailman/Cgi/admindb.py:362 +#, fuzzy msgid "You must specify a list name. Here is the %(link)s" msgstr "Vous devez donner le nom d'une liste. Voici les %(link)s" @@ -1368,6 +1403,7 @@ msgid "The sender is now a member of this list" msgstr "L'expéditeur est maintenant membre de cette liste" #: Mailman/Cgi/admindb.py:568 +#, fuzzy msgid "Add %(esender)s to one of these sender filters:" msgstr "Ajouter %(esender)s à un filtre expéditeur :" @@ -1388,6 +1424,7 @@ msgid "Rejects" msgstr "Rejets" #: Mailman/Cgi/admindb.py:584 +#, fuzzy msgid "" "Ban %(esender)s from ever subscribing to this\n" " mailing list" @@ -1404,6 +1441,7 @@ msgstr "" " individuel, ou" #: Mailman/Cgi/admindb.py:591 +#, fuzzy msgid "view all messages from %(esender)s" msgstr "visualisez tous les messages de %(esender)s" @@ -1482,6 +1520,7 @@ msgid " is already a member" msgstr " est déjà abonné" #: Mailman/Cgi/admindb.py:973 +#, fuzzy msgid "%(addr)s is banned (matched: %(patt)s)" msgstr "%(addr)s est interdite (correspondance: %(patt)s)" @@ -1533,6 +1572,7 @@ msgstr "" "\ttemps. Cette requête a été annulée." #: Mailman/Cgi/confirm.py:178 +#, fuzzy msgid "System error, bad content: %(content)s" msgstr "Erreur système, mauvais contenu : %(content)s" @@ -1570,6 +1610,7 @@ msgid "Confirm subscription request" msgstr "Confirmez la requête d'abonnement" #: Mailman/Cgi/confirm.py:259 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " subscription request to the mailing list %(listname)s. Your\n" @@ -1604,6 +1645,7 @@ msgstr "" " être abonné à cette liste." #: Mailman/Cgi/confirm.py:275 +#, fuzzy msgid "" "Your confirmation is required in order to continue with\n" " the subscription request to the mailing list %(listname)s.\n" @@ -1655,6 +1697,7 @@ msgid "Preferred language:" msgstr "Langue préférée :" #: Mailman/Cgi/confirm.py:317 +#, fuzzy msgid "Subscribe to list %(listname)s" msgstr "Abonnement à la liste : %(listname)s" @@ -1671,6 +1714,7 @@ msgid "Awaiting moderator approval" msgstr "En attente d'approbation par le modérateur" #: Mailman/Cgi/confirm.py:386 +#, fuzzy msgid "" " You have successfully confirmed your subscription request to " "the\n" @@ -1703,6 +1747,7 @@ msgid "You are already a member of this mailing list!" msgstr "Vous déjà membre de cette liste !" #: Mailman/Cgi/confirm.py:400 +#, fuzzy msgid "" "You are currently banned from subscribing to\n" " this list. If you think this restriction is erroneous, please\n" @@ -1728,6 +1773,7 @@ msgid "Subscription request confirmed" msgstr "Requête d'abonnement confirmée" #: Mailman/Cgi/confirm.py:418 +#, fuzzy msgid "" " You have successfully confirmed your subscription request for\n" " \"%(addr)s\" to the %(listname)s mailing list. A separate\n" @@ -1756,6 +1802,7 @@ msgid "Unsubscription request confirmed" msgstr "Requête de résiliation confirmée" #: Mailman/Cgi/confirm.py:469 +#, fuzzy msgid "" " You have successfully unsubscribed from the %(listname)s " "mailing\n" @@ -1764,8 +1811,8 @@ msgid "" " information page." msgstr "" " Votre abonnement à la liste %(listname)s a été résilié avec\n" -" succès. Vous pourrez à présent visiter la\n" +" succès. Vous pourrez à présent visiter la\n" " page principal d'informations de la liste." #: Mailman/Cgi/confirm.py:480 @@ -1777,6 +1824,7 @@ msgid "Not available" msgstr "Non disponible" #: Mailman/Cgi/confirm.py:498 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " unsubscription request from the mailing list %(listname)s. " @@ -1821,6 +1869,7 @@ msgid "You have canceled your change of address request." msgstr "Vous avez annulé votre requête de changement d'adresse" #: Mailman/Cgi/confirm.py:555 +#, fuzzy msgid "" "%(newaddr)s is banned from subscribing to the\n" " %(realname)s list. If you think this restriction is erroneous,\n" @@ -1848,6 +1897,7 @@ msgid "Change of address request confirmed" msgstr "Requête de changement d'adresse confirmée" #: Mailman/Cgi/confirm.py:571 +#, fuzzy msgid "" " You have successfully changed your address on the %(listname)s\n" " mailing list from %(oldaddr)s to %(newaddr)s. " @@ -1871,6 +1921,7 @@ msgid "globally" msgstr "globalement" #: Mailman/Cgi/confirm.py:605 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " change of address request for the mailing list %(listname)s. " @@ -1933,6 +1984,7 @@ msgid "Sender discarded message via web." msgstr "L'expéditeur a ignoré le message via l'interface web." #: Mailman/Cgi/confirm.py:674 +#, fuzzy msgid "" "The held message with the Subject:\n" " header %(subject)s could not be found. The most " @@ -1954,6 +2006,7 @@ msgid "Posted message canceled" msgstr "Annulation du message soumis" #: Mailman/Cgi/confirm.py:685 +#, fuzzy msgid "" " You have successfully canceled the posting of your message with\n" " the Subject: header %(subject)s to the mailing list\n" @@ -1975,6 +2028,7 @@ msgstr "" " a déjà été traité par l'administrateur de liste." #: Mailman/Cgi/confirm.py:735 +#, fuzzy msgid "" "Your confirmation is required in order to cancel the\n" " posting of your message to the mailing list %(listname)s:\n" @@ -2025,6 +2079,7 @@ msgid "Membership re-enabled." msgstr "Ré-activation de l'abonnement." #: Mailman/Cgi/confirm.py:798 +#, fuzzy msgid "" " You have successfully re-enabled your membership in the\n" " %(listname)s mailing list. You can now visiter \n" +" succès. Vous pourrez à présent visiter \n" " votre page d'options d'abonné." #: Mailman/Cgi/confirm.py:810 @@ -2041,6 +2096,7 @@ msgid "Re-enable mailing list membership" msgstr "Ré-activez l'abonnement à la liste" #: Mailman/Cgi/confirm.py:827 +#, fuzzy msgid "" "We're sorry, but you have already been unsubscribed\n" " from this mailing list. To re-subscribe, please visit the\n" @@ -2055,6 +2111,7 @@ msgid "not available" msgstr "non disponible" #: Mailman/Cgi/confirm.py:846 +#, fuzzy msgid "" "Your membership in the %(realname)s mailing list is\n" " currently disabled due to excessive bounces. Your confirmation is\n" @@ -2122,10 +2179,12 @@ msgid "administrative list overview" msgstr "Panorama administratif de la liste" #: Mailman/Cgi/create.py:115 +#, fuzzy msgid "List name must not include \"@\": %(safelistname)s" msgstr "Le nom d'une liste ne doit pas contenir \"@\": %(safelistname)s" #: Mailman/Cgi/create.py:122 +#, fuzzy msgid "List already exists: %(safelistname)s" msgstr "La liste %(safelistname)s existe déjà" @@ -2161,18 +2220,22 @@ msgid "You are not authorized to create new mailing lists" msgstr "Vous n'êtes pas autorisé à créer de nouvelles listes" #: Mailman/Cgi/create.py:182 +#, fuzzy msgid "Unknown virtual host: %(safehostname)s" msgstr "Hôte virtuel inconnu : %(safehostname)s" #: Mailman/Cgi/create.py:218 bin/newlist:219 +#, fuzzy msgid "Bad owner email address: %(s)s" msgstr "Mauvaise adresse courriel du propriétaire : %(s)s" #: Mailman/Cgi/create.py:223 bin/newlist:182 bin/newlist:223 +#, fuzzy msgid "List already exists: %(listname)s" msgstr "La liste %(listname)s existe déjà" #: Mailman/Cgi/create.py:231 bin/newlist:217 +#, fuzzy msgid "Illegal list name: %(s)s" msgstr "Option invalide : %(s)s" @@ -2186,6 +2249,7 @@ msgstr "" "\tPour assistance veuillez contactez l'administrateur du site." #: Mailman/Cgi/create.py:273 bin/newlist:265 +#, fuzzy msgid "Your new mailing list: %(listname)s" msgstr "Votre nouvelle liste de diffusion : %(listname)s" @@ -2194,6 +2258,7 @@ msgid "Mailing list creation results" msgstr "Resultats de la création de la liste de diffusion" #: Mailman/Cgi/create.py:288 +#, fuzzy msgid "" "You have successfully created the mailing list\n" " %(listname)s and notification has been sent to the list owner\n" @@ -2216,6 +2281,7 @@ msgid "Create another list" msgstr "Créer une autre liste" #: Mailman/Cgi/create.py:312 +#, fuzzy msgid "Create a %(hostname)s Mailing List" msgstr "Créer une liste de diffusion sur %(hostname)s" @@ -2315,6 +2381,7 @@ msgstr "" " abonnés en attente de l'approbation du modérateur." #: Mailman/Cgi/create.py:432 +#, fuzzy msgid "" "Initial list of supported languages.

                      Note that if you do not\n" " select at least one initial language, the list will use the server\n" @@ -2420,6 +2487,7 @@ msgid "List name is required." msgstr "Le nom de la liste est requis." #: Mailman/Cgi/edithtml.py:154 +#, fuzzy msgid "%(realname)s -- Edit html for %(template_info)s" msgstr "%(realname)s -- Modifier html pour %(template_info)s" @@ -2428,10 +2496,12 @@ msgid "Edit HTML : Error" msgstr "Modifier HTML : Erreur" #: Mailman/Cgi/edithtml.py:161 +#, fuzzy msgid "%(safetemplatename)s: Invalid template" msgstr "%(safetemplatename)s : modèle invalide" #: Mailman/Cgi/edithtml.py:166 Mailman/Cgi/edithtml.py:167 +#, fuzzy msgid "%(realname)s -- HTML Page Editing" msgstr "%(realname)s -- Modification de Page HTML" @@ -2489,10 +2559,12 @@ msgid "HTML successfully updated." msgstr "HTML mis à jour avec succès." #: Mailman/Cgi/listinfo.py:89 +#, fuzzy msgid "%(hostname)s Mailing Lists" msgstr "Listes de diffusion sur %(hostname)s" #: Mailman/Cgi/listinfo.py:127 +#, fuzzy msgid "" "

                      There currently are no publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s." @@ -2501,6 +2573,7 @@ msgstr "" " %(mailmanlink)s publiques sur %(hostname)s." #: Mailman/Cgi/listinfo.py:131 +#, fuzzy msgid "" "

                      Below is a listing of all the public mailing lists on\n" " %(hostname)s. Click on a list name to get more information " @@ -2520,6 +2593,7 @@ msgid "right" msgstr "droite" #: Mailman/Cgi/listinfo.py:140 +#, fuzzy msgid "" " To visit the general information page for an unadvertised list,\n" " open a URL similar to this one, but with a '/' and the %(adj)s\n" @@ -2581,6 +2655,7 @@ msgstr "Adresse courriel invalide" #: Mailman/Cgi/options.py:183 Mailman/Cgi/options.py:240 #: Mailman/Cgi/options.py:264 +#, fuzzy msgid "No such member: %(safeuser)s." msgstr "Abonné inconnu : %(safeuser)s." @@ -2633,6 +2708,7 @@ msgid "Note: " msgstr "Note :" #: Mailman/Cgi/options.py:378 +#, fuzzy msgid "List subscriptions for %(safeuser)s on %(hostname)s" msgstr "Les abonnements de %(safeuser)s sur %(hostname)s" @@ -2663,6 +2739,7 @@ msgid "You are already using that email address" msgstr "Vous utilisez déjà cette adresse courriel." #: Mailman/Cgi/options.py:459 +#, fuzzy msgid "" "The new address you requested %(newaddr)s is already a member of the\n" "%(listname)s mailing list, however you have also requested a global change " @@ -2677,6 +2754,7 @@ msgstr "" "seront modifiées." #: Mailman/Cgi/options.py:468 +#, fuzzy msgid "The new address is already a member: %(newaddr)s" msgstr "La nouvelle adresse est déjà abonnée : %(newaddr)s" @@ -2685,6 +2763,7 @@ msgid "Addresses may not be blank" msgstr "Les adresses ne doivent pas être vides" #: Mailman/Cgi/options.py:488 +#, fuzzy msgid "A confirmation message has been sent to %(newaddr)s. " msgstr "Un message de confirmation a été envoyé à %(newaddr)s. " @@ -2697,10 +2776,12 @@ msgid "Illegal email address provided" msgstr "Adresse courriel fournie invalide" #: Mailman/Cgi/options.py:501 +#, fuzzy msgid "%(newaddr)s is already a member of the list." msgstr "%(newaddr)s est déjà abonné à la liste." #: Mailman/Cgi/options.py:504 +#, fuzzy msgid "" "%(newaddr)s is banned from this list. If you\n" " think this restriction is erroneous, please contact\n" @@ -2777,6 +2858,7 @@ msgstr "" " modérateurs de la liste auront pris une décision." #: Mailman/Cgi/options.py:623 +#, fuzzy msgid "" "You have been successfully unsubscribed from the\n" " mailing list %(fqdn_listname)s. If you were receiving digest\n" @@ -2870,6 +2952,7 @@ msgid "day" msgstr "jour" #: Mailman/Cgi/options.py:900 +#, fuzzy msgid "%(days)d %(units)s" msgstr "%(units)s %(days)d" @@ -2882,6 +2965,7 @@ msgid "No topics defined" msgstr "Aucun thème défini" #: Mailman/Cgi/options.py:940 +#, fuzzy msgid "" "\n" "You are subscribed to this list with the case-preserved address\n" @@ -2892,6 +2976,7 @@ msgstr "" "casse %(cpuser)s." #: Mailman/Cgi/options.py:956 +#, fuzzy msgid "%(realname)s list: member options login page" msgstr "Liste %(realname)s : Page de login d'abonné" @@ -2900,10 +2985,12 @@ msgid "email address and " msgstr "adresse courriel et " #: Mailman/Cgi/options.py:960 +#, fuzzy msgid "%(realname)s list: member options for user %(safeuser)s" msgstr "Liste %(realname)s : options d'abonné de l'utilisateur %(safeuser)s" #: Mailman/Cgi/options.py:986 +#, fuzzy msgid "" "In order to change your membership option, you must\n" " first log in by giving your %(extra)smembership password in the section\n" @@ -2979,6 +3066,7 @@ msgid "" msgstr "" #: Mailman/Cgi/options.py:1140 +#, fuzzy msgid "Requested topic is not valid: %(topicname)s" msgstr "Le thème demandé n'est pas valide : %(topicname)s" @@ -3007,6 +3095,7 @@ msgid "Private archive - \"./\" and \"../\" not allowed in URL." msgstr "Archive privée - \"./\" et \"../\" non permises dans l'URL." #: Mailman/Cgi/private.py:109 +#, fuzzy msgid "Private Archive Error - %(msg)s" msgstr "Erreur d'archive privée - %(msg)s" @@ -3044,6 +3133,7 @@ msgid "Mailing list deletion results" msgstr "Résultats de la suppression de la liste" #: Mailman/Cgi/rmlist.py:187 +#, fuzzy msgid "" "You have successfully deleted the mailing list\n" " %(listname)s." @@ -3052,6 +3142,7 @@ msgstr "" "\t%(listname)s." #: Mailman/Cgi/rmlist.py:191 +#, fuzzy msgid "" "There were some problems deleting the mailing list\n" " %(listname)s. Contact your site administrator at " @@ -3063,6 +3154,7 @@ msgstr "" "pour les détails." #: Mailman/Cgi/rmlist.py:208 +#, fuzzy msgid "Permanently remove mailing list %(realname)s" msgstr "Supprimer définitivement la liste de diffusion %(realname)s" @@ -3134,6 +3226,7 @@ msgid "Invalid options to CGI script" msgstr "Options invalides pour le script CGI" #: Mailman/Cgi/roster.py:118 +#, fuzzy msgid "%(realname)s roster authentication failed." msgstr "Echec d'authentification du roster de %(realname)s." @@ -3202,6 +3295,7 @@ msgstr "" "instructions supplémentaires." #: Mailman/Cgi/subscribe.py:262 +#, fuzzy msgid "" "The email address you supplied is banned from this\n" " mailing list. If you think this restriction is erroneous, please\n" @@ -3229,6 +3323,7 @@ msgstr "" "n'est pas sûre." #: Mailman/Cgi/subscribe.py:278 +#, fuzzy msgid "" "Confirmation from your email address is required, to prevent anyone from\n" "subscribing you without permission. Instructions are being sent to you at\n" @@ -3241,6 +3336,7 @@ msgstr "" "abonnement ne débutera que si vous confirmez votre requête." #: Mailman/Cgi/subscribe.py:290 +#, fuzzy msgid "" "Your subscription request was deferred because %(x)s. Your request has " "been\n" @@ -3262,6 +3358,7 @@ msgid "Mailman privacy alert" msgstr "Alerte de confidentialité Mailman" #: Mailman/Cgi/subscribe.py:316 +#, fuzzy msgid "" "An attempt was made to subscribe your address to the mailing list\n" "%(listaddr)s. You are already subscribed to this mailing list.\n" @@ -3303,6 +3400,7 @@ msgid "This list only supports digest delivery." msgstr "Cette liste ne supporte que les remises groupées" #: Mailman/Cgi/subscribe.py:344 +#, fuzzy msgid "You have been successfully subscribed to the %(realname)s mailing list." msgstr "Vous avez été abonné avec succès à la liste %(realname)s." @@ -3353,6 +3451,7 @@ msgstr "" "avez-vous changé d'adresse courriel ?" #: Mailman/Commands/cmd_confirm.py:69 +#, fuzzy msgid "" "You are currently banned from subscribing to this list. If you think this\n" "restriction is erroneous, please contact the list owners at\n" @@ -3434,26 +3533,32 @@ msgid "n/a" msgstr "n/a" #: Mailman/Commands/cmd_info.py:44 +#, fuzzy msgid "List name: %(listname)s" msgstr "Nom de la liste : %(listname)s" #: Mailman/Commands/cmd_info.py:45 +#, fuzzy msgid "Description: %(description)s" msgstr "Description : %(description)s" #: Mailman/Commands/cmd_info.py:46 +#, fuzzy msgid "Postings to: %(postaddr)s" msgstr "Envois à : %(postaddr)s" #: Mailman/Commands/cmd_info.py:47 +#, fuzzy msgid "List Helpbot: %(requestaddr)s" msgstr "Robot d'aide de la liste : %(requestaddr)s" #: Mailman/Commands/cmd_info.py:48 +#, fuzzy msgid "List Owners: %(owneraddr)s" msgstr "Propriétaires de la liste : %(owneraddr)s" #: Mailman/Commands/cmd_info.py:49 +#, fuzzy msgid "More information: %(listurl)s" msgstr "Plus d'informations : %(listurl)s" @@ -3477,18 +3582,22 @@ msgstr "" " serveur GNU Mailman.\n" #: Mailman/Commands/cmd_lists.py:44 +#, fuzzy msgid "Public mailing lists at %(hostname)s:" msgstr "Listes de diffusion publiques sur %(hostname)s :" #: Mailman/Commands/cmd_lists.py:66 +#, fuzzy msgid "%(i)3d. List name: %(realname)s" msgstr "%(i)3d. Nom de la liste : %(realname)s" #: Mailman/Commands/cmd_lists.py:67 +#, fuzzy msgid " Description: %(description)s" msgstr " Description : %(description)s" #: Mailman/Commands/cmd_lists.py:68 +#, fuzzy msgid " Requests to: %(requestaddr)s" msgstr " Requêtes à : %(requestaddr)s" @@ -3524,12 +3633,14 @@ msgstr "" "\n" #: Mailman/Commands/cmd_password.py:51 Mailman/Commands/cmd_password.py:66 +#, fuzzy msgid "Your password is: %(password)s" msgstr "Votre mot de passe est : %(password)s" #: Mailman/Commands/cmd_password.py:57 Mailman/Commands/cmd_password.py:72 #: Mailman/Commands/cmd_password.py:95 Mailman/Commands/cmd_password.py:121 #: Mailman/Commands/cmd_set.py:149 Mailman/Commands/cmd_set.py:219 +#, fuzzy msgid "You are not a member of the %(listname)s mailing list" msgstr "Vous n'êtes pas abonné à la liste %(listname)s" @@ -3717,6 +3828,7 @@ msgstr "" " rappel mensuel de mot de passe pour cette liste.\n" #: Mailman/Commands/cmd_set.py:122 +#, fuzzy msgid "Bad set command: %(subcmd)s" msgstr "Mauvaise commande set : %(subcmd)s" @@ -3737,6 +3849,7 @@ msgid "on" msgstr "on" #: Mailman/Commands/cmd_set.py:154 +#, fuzzy msgid " ack %(onoff)s" msgstr " ack (accusé de reception) %(onoff)s" @@ -3774,22 +3887,27 @@ msgid "due to bounces" msgstr "suite à des rejets" #: Mailman/Commands/cmd_set.py:186 +#, fuzzy msgid " %(status)s (%(how)s on %(date)s)" msgstr " %(status)s (%(how)s le %(date)s)" #: Mailman/Commands/cmd_set.py:192 +#, fuzzy msgid " myposts %(onoff)s" msgstr " myposts (mes messages) %(onoff)s" #: Mailman/Commands/cmd_set.py:195 +#, fuzzy msgid " hide %(onoff)s" msgstr " hide (cacher mon adresse) %(onoff)s" #: Mailman/Commands/cmd_set.py:199 +#, fuzzy msgid " duplicates %(onoff)s" msgstr " duplicates (doublons) %(onoff)s" #: Mailman/Commands/cmd_set.py:203 +#, fuzzy msgid " reminders %(onoff)s" msgstr " reminders (rappels mensuel de mot de passe) %(onoff)s" @@ -3798,6 +3916,7 @@ msgid "You did not give the correct password" msgstr "Le mot de passe fourni n'est pas correct" #: Mailman/Commands/cmd_set.py:236 Mailman/Commands/cmd_set.py:283 +#, fuzzy msgid "Bad argument: %(arg)s" msgstr "Mauvais argument : %(arg)s" @@ -3874,6 +3993,7 @@ msgstr "" " l'adresse et sans guillemets!)\n" #: Mailman/Commands/cmd_subscribe.py:62 +#, fuzzy msgid "Bad digest specifier: %(arg)s" msgstr "Mauvaise spécification de remise : %(arg)s" @@ -3882,6 +4002,7 @@ msgid "No valid address found to subscribe" msgstr "Aucune adresse valide n'a été trouvée pour être abonné" #: Mailman/Commands/cmd_subscribe.py:113 +#, fuzzy msgid "" "The email address you supplied is banned from this mailing list.\n" "If you think this restriction is erroneous, please contact the list\n" @@ -3920,6 +4041,7 @@ msgid "This list only supports digest subscriptions!" msgstr "Cette liste ne supporte que les abonnements de type remise groupée !" #: Mailman/Commands/cmd_subscribe.py:146 +#, fuzzy msgid "" "Your subscription request has been forwarded to the list administrator\n" "at %(listowner)s for review." @@ -3956,6 +4078,7 @@ msgstr "" " et > autour de l'adresse et sans guillemets!)\n" #: Mailman/Commands/cmd_unsubscribe.py:62 +#, fuzzy msgid "%(address)s is not a member of the %(listname)s mailing list" msgstr "%(address)s n'est pas abonné à la liste %(listname)s" @@ -4205,6 +4328,7 @@ msgid "Chinese (Taiwan)" msgstr "Chinois (Taiwan)" #: Mailman/Deliverer.py:53 +#, fuzzy msgid "" "Note: Since this is a list of mailing lists, administrative\n" "notices like the password reminder will be sent to\n" @@ -4219,14 +4343,17 @@ msgid " (Digest mode)" msgstr " (Mode Groupé)" #: Mailman/Deliverer.py:79 +#, fuzzy msgid "Welcome to the \"%(realname)s\" mailing list%(digmode)s" msgstr "Bienvenue sur la liste %(digmode)s \"%(realname)s\" " #: Mailman/Deliverer.py:89 +#, fuzzy msgid "You have been unsubscribed from the %(realname)s mailing list" msgstr "Votre abonnement à la liste %(realname)s a été résilié" #: Mailman/Deliverer.py:116 +#, fuzzy msgid "%(listfullname)s mailing list reminder" msgstr "Rappel de la liste de diffusion %(listfullname)s" @@ -4239,6 +4366,7 @@ msgid "Hostile subscription attempt detected" msgstr "Essai d'abonnement hostile détecté" #: Mailman/Deliverer.py:169 +#, fuzzy msgid "" "%(address)s was invited to a different mailing\n" "list, but in a deliberate malicious attempt they tried to confirm the\n" @@ -4250,6 +4378,7 @@ msgstr "" "l'inscription sur votre liste. Aucune action de votre part n'est nécessaire." #: Mailman/Deliverer.py:188 +#, fuzzy msgid "" "You invited %(address)s to your list, but in a\n" "deliberate malicious attempt, they tried to confirm the invitation to a\n" @@ -4263,6 +4392,7 @@ msgstr "" "Aucune action de votre part n'est nécessaire." #: Mailman/Deliverer.py:221 +#, fuzzy msgid "%(listname)s mailing list probe message" msgstr "Message de vérification de la liste %(listname)s" @@ -4473,8 +4603,8 @@ msgid "" " membership.\n" "\n" "

                      You can control both the\n" -" number\n" +" number\n" " of reminders the member will receive and the\n" " No those messages too will get discarded. You may " "want\n" " to set up an\n" -" autoresponse\n" +" autoresponse\n" " message for email to the -owner and -admin address." msgstr "" "Bien que le gestionnaire de rejets de Mailman soit assez robuste, il\n" @@ -4806,6 +4936,7 @@ msgstr "" " l'abonné informé." #: Mailman/Gui/Bounce.py:194 +#, fuzzy msgid "" "Bad value for %(property)s: %(val)s" @@ -4936,8 +5067,8 @@ msgstr "" "\n" "

                      Les lignes vides sont ignorées.\n" "\n" -"

                      Voir aussi Voir aussi types_mime_acceptés pour la liste des types de contenu." #: Mailman/Gui/ContentFilter.py:94 @@ -4955,8 +5086,8 @@ msgid "" "Use this option to remove each message attachment that does\n" " not have a matching content type. Requirements and formats " "are\n" -" exactly like filter_mime_types.\n" "\n" "

                      Note: if you add entries to this list but don't add\n" @@ -5075,6 +5206,7 @@ msgstr "" " par l'administrateur du site." #: Mailman/Gui/ContentFilter.py:171 +#, fuzzy msgid "Bad MIME type ignored: %(spectype)s" msgstr "Mauvais type MIME ignoré : %(spectype)s" @@ -5182,6 +5314,7 @@ msgstr "" "Mailman peut-il envoyer le prochain résumé maintenant, s'il n'est pas vide ?" #: Mailman/Gui/Digest.py:145 +#, fuzzy msgid "" "The next digest will be sent as volume\n" " %(volume)s, number %(number)s" @@ -5198,14 +5331,17 @@ msgid "There was no digest to send." msgstr "Pas de résumé à envoyer." #: Mailman/Gui/GUIBase.py:173 +#, fuzzy msgid "Invalid value for variable: %(property)s" msgstr "Valeur inavalide pour la variable : %(property)s" #: Mailman/Gui/GUIBase.py:178 +#, fuzzy msgid "Bad email address for option %(property)s: %(error)s" msgstr "Adresse courriel invalide pour l'option %(property)s : %(error)s" #: Mailman/Gui/GUIBase.py:204 +#, fuzzy msgid "" "The following illegal substitution variables were\n" " found in the %(property)s string:\n" @@ -5222,6 +5358,7 @@ msgstr "" " que vous ayez corrigé ce problème." #: Mailman/Gui/GUIBase.py:218 +#, fuzzy msgid "" "Your %(property)s string appeared to\n" " have some correctable problems in its new value.\n" @@ -5325,8 +5462,8 @@ msgid "" "

                      In order to split the list ownership duties into\n" " administrators and moderators, you must\n" " set a separate moderator password,\n" -" and also provide the email\n" +" and also provide the email\n" " addresses of the list moderators. Note that the field you\n" " are changing here specifies the list administrators." msgstr "" @@ -5677,13 +5814,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5746,8 +5883,8 @@ msgstr "En-t msgid "" "This is the address set in the Reply-To: header\n" " when the reply_goes_to_list\n" +" href=\"?VARHELP=general/" +"reply_goes_to_list\">reply_goes_to_list\n" " option is set to Explicit address.\n" "\n" "

                      There are many reasons not to introduce or override the\n" @@ -5755,13 +5892,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5874,8 +6011,8 @@ msgid "" " member list addresses, but rather to the owner of those member\n" " lists. In that case, the value of this setting is appended to\n" " the member's account name for such notices. `-owner' is the\n" -" typical choice. This setting has no effect when \"umbrella_list" -"\"\n" +" typical choice. This setting has no effect when " +"\"umbrella_list\"\n" " is \"No\"." msgstr "" "Lorsque \"umbrella_list\" est activé pour indiquer que d'autres listes sont\n" @@ -6209,8 +6346,8 @@ msgid "" " headers.)" msgstr "" "L'en-tête List-Post: fait partie des en-têtes recommandés\n" -" par la RFC2369.\n" +" par la RFC2369.\n" " Toutefois, pour certaines listes spécialisées pour\n" " l'annonce uniquement, seul un nombre restreint " "d'individus\n" @@ -6616,10 +6753,10 @@ msgstr "" "

                      Lorsque la personnalisation des listes personnalisées " "est activée,\n" " quelques autres variables de remplacement pouvant\n" -" être ajoutées à l' en-tête du\n" -" message et au pied de\n" +" être ajoutées à l' en-tête du\n" +" message et au pied de\n" " page du message sont disponibles.\n" "\n" "

                      Ces variables complémentaires de substitution seront " @@ -6893,6 +7030,7 @@ msgstr "" "\t\t\tà leur insu." #: Mailman/Gui/Privacy.py:104 +#, fuzzy msgid "" "This section allows you to configure subscription and\n" " membership exposure policy. You can also control whether this\n" @@ -7098,8 +7236,8 @@ msgid "" " either individually or as a group. Any\n" " posting from a non-member who is not explicitly accepted,\n" " rejected, or discarded, will have their posting filtered by the\n" -" general\n" +" general\n" " non-member rules.\n" "\n" "

                      In the text boxes below, add one address per line; start the\n" @@ -7124,23 +7262,23 @@ msgstr "" " réservé par défaut aux envois des abonnés.\n" "\n" "

                      Les envois des non-abonnés peuvent être automatiquement acceptés,\n" +" href=\"?VARHELP=privacy/sender/" +"accept_these_nonmembers\">acceptés,\n" " mis " "en\n" " attente pour modération,\n" -" rejetés\n" +" rejetés\n" " (rebonds) ou\n" -" supprimés\n" +" supprimés\n" " soit individuellement, soit par groupe. Tout message en " "provenance d'un\n" " non-abonné non explicitement accepté, rejeté ou supprimé devra " "subir\n" " les filtres définis par les règles\n" +" href=\"?VARHELP=privacy/sender/" +"generic_nonmember_action\">règles\n" " générales des non-abonnés.\n" "\n" "

                      Dans la zone de texte ci-dessous, ajouter une adresse par " @@ -7166,6 +7304,7 @@ msgstr "" "défaut ?" #: Mailman/Gui/Privacy.py:218 +#, fuzzy msgid "" "Each list member has a moderation flag which says\n" " whether messages from the list member can be posted directly " @@ -7226,8 +7365,8 @@ msgid "" "If a member posts this many times, within a period of time\n" " the member is automatically moderated. Use 0 to disable. " "See\n" -" member_verbosity_interval for details on the time " "period.\n" "\n" @@ -7618,8 +7757,8 @@ msgstr "" "pas\n" " appropriée pour les spammers connus; leurs messages doivent " "être automatiquement\n" +" href=\"?VARHELP=privacy/sender/" +"discard_these_nonmembers\">automatiquement\n" " ignorés.\n" "\n" "

                      Ajouter les adresses des abonnés, une par ligne;\n" @@ -7687,13 +7826,13 @@ msgid "" msgstr "" "Lorsqu'un message est reçu d'un non-abonné, l'adresse de l'expéditeur\n" " est comparée aux adresses acceptées,\n" +" href=\"?VARHELP=privacy/sender/" +"accept_these_nonmembers\">acceptées,\n" " mise " "en\n" " attente rejetées\n" +" href=\"?VARHELP=privacy/sender/" +"reject_these_nonmembers\">rejetées\n" " (rejet) et supprimées. Si aucune adresse ne correspond, cette mesure " @@ -7709,6 +7848,7 @@ msgstr "" " transmis au modérateur de la liste ?" #: Mailman/Gui/Privacy.py:494 +#, fuzzy msgid "" "Text to include in any rejection notice to be sent to\n" " non-members who post to this list. This notice can include\n" @@ -7976,6 +8116,7 @@ msgstr "" "\t\t Les règles de filtrage incomplètes seront ignorées.\n" #: Mailman/Gui/Privacy.py:693 +#, fuzzy msgid "" "The header filter rule pattern\n" " '%(safepattern)s' is not a legal regular expression. This\n" @@ -8027,8 +8168,8 @@ msgid "" "

                      The body of the message can also be optionally scanned for\n" " Subject: and Keywords: headers, as\n" " specified by the topics_bodylines_limit\n" +" href=\"?VARHELP=topics/" +"topics_bodylines_limit\">topics_bodylines_limit\n" " configuration variable." msgstr "" "Le filtre de thème place chaque courriel qui arrive dans une catégorie\n" @@ -8136,6 +8277,7 @@ msgstr "" " thèmes incomplets seront ignorés." #: Mailman/Gui/Topics.py:135 +#, fuzzy msgid "" "The topic pattern '%(safepattern)s' is not a\n" " legal regular expression. It will be discarded." @@ -8364,14 +8506,15 @@ msgstr "" " tant que vous n'aurez pas renseigner et le champs serveur de " "news\n" -" et le champ groupe de news lié." +" et le champ groupe de news lié." #: Mailman/HTMLFormatter.py:49 msgid "%(listinfo_link)s list run by %(owner_link)s" msgstr "Liste de diffusion %(listinfo_link)s gérée par %(owner_link)s" #: Mailman/HTMLFormatter.py:57 +#, fuzzy msgid "%(realname)s administrative interface" msgstr "Interface administrative de %(realname)s" @@ -8380,6 +8523,7 @@ msgid " (requires authorization)" msgstr " (autorisation requise)" #: Mailman/HTMLFormatter.py:61 +#, fuzzy msgid "Overview of all %(hostname)s mailing lists" msgstr "Panorama de toutes les listes sur %(hostname)s" @@ -8400,6 +8544,7 @@ msgid "; it was disabled by the list administrator" msgstr "; il a été désactivé par l'administrateur de la liste" #: Mailman/HTMLFormatter.py:146 +#, fuzzy msgid "" "; it was disabled due to excessive bounces. The\n" " last bounce was received on %(date)s" @@ -8412,6 +8557,7 @@ msgid "; it was disabled for unknown reasons" msgstr "; il a été désactivé pour des raisons inconnues" #: Mailman/HTMLFormatter.py:151 +#, fuzzy msgid "Note: your list delivery is currently disabled%(reason)s." msgstr "" "Note: les remises sur votre liste sont actuellement désactivées %(reason)s." @@ -8425,6 +8571,7 @@ msgid "the list administrator" msgstr "l'administrateur de la liste" #: Mailman/HTMLFormatter.py:157 +#, fuzzy msgid "" "

                      %(note)s\n" "\n" @@ -8445,6 +8592,7 @@ msgstr "" "d'assistance." #: Mailman/HTMLFormatter.py:169 +#, fuzzy msgid "" "

                      We have received some recent bounces from your\n" " address. Your current bounce score is %(score)s out of " @@ -8464,6 +8612,7 @@ msgstr "" " re-initialisé si le problème est réglé dans les meilleurs délais." #: Mailman/HTMLFormatter.py:181 +#, fuzzy msgid "" "(Note - you are subscribing to a list of mailing lists, so the %(type)s " "notice will be sent to the admin address for your membership, %(addr)s.)

                      " @@ -8512,6 +8661,7 @@ msgstr "" " décision du modérateur vous sera annoncée par courriel." #: Mailman/HTMLFormatter.py:208 +#, fuzzy msgid "" "This is %(also)sa private list, which means that the\n" " list of members is not available to non-members." @@ -8520,6 +8670,7 @@ msgstr "" " abonnés n'est pas consultable par les non-abonnés." #: Mailman/HTMLFormatter.py:211 +#, fuzzy msgid "" "This is %(also)sa hidden list, which means that the\n" " list of members is available only to the list administrator." @@ -8528,6 +8679,7 @@ msgstr "" " abonnés n'est consultable que par l'administrateur." #: Mailman/HTMLFormatter.py:214 +#, fuzzy msgid "" "This is %(also)sa public list, which means that the\n" " list of members list is available to everyone." @@ -8544,6 +8696,7 @@ msgstr "" " facilement reconnaissables par les spammers)." #: Mailman/HTMLFormatter.py:222 +#, fuzzy msgid "" "

                      (Note that this is an umbrella list, intended to\n" " have only other mailing lists as members. Among other things,\n" @@ -8563,6 +8716,7 @@ msgid "either " msgstr "ou" #: Mailman/HTMLFormatter.py:256 +#, fuzzy msgid "" "To unsubscribe from %(realname)s, get a password reminder,\n" " or change your subscription options %(either)senter your " @@ -8597,6 +8751,7 @@ msgstr "" " votre adresse courriel" #: Mailman/HTMLFormatter.py:277 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " members.)" @@ -8605,6 +8760,7 @@ msgstr "" " abonnés.)" #: Mailman/HTMLFormatter.py:281 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " administrator.)" @@ -8665,6 +8821,7 @@ msgid "The current archive" msgstr "L'archive en cours" #: Mailman/Handlers/Acknowledge.py:59 +#, fuzzy msgid "%(realname)s post acknowledgement" msgstr "accusé de réception de l'envoi de %(realname)s" @@ -8677,6 +8834,7 @@ msgid "" msgstr "" #: Mailman/Handlers/CalcRecips.py:79 +#, fuzzy msgid "" "Your urgent message to the %(realname)s mailing list was not authorized for\n" "delivery. The original message as received by Mailman is attached.\n" @@ -8755,6 +8913,7 @@ msgid "Message may contain administrivia" msgstr "Le message contient peut être des requêtes administratives" #: Mailman/Handlers/Hold.py:84 +#, fuzzy msgid "" "Please do *not* post administrative requests to the mailing\n" "list. If you wish to subscribe, visit %(listurl)s or send a message with " @@ -8796,10 +8955,12 @@ msgid "Posting to a moderated newsgroup" msgstr "Envoi sur un newsgroup modéré" #: Mailman/Handlers/Hold.py:252 +#, fuzzy msgid "Your message to %(listname)s awaits moderator approval" msgstr "Votre message à la liste %(listname)s est en attente d'approbation" #: Mailman/Handlers/Hold.py:271 +#, fuzzy msgid "%(listname)s post from %(sender)s requires approval" msgstr "" "Un envoi sur la liste %(listname)s à partir de %(sender)s requiert une " @@ -8844,6 +9005,7 @@ msgid "After content filtering, the message was empty" msgstr "Après filtrage du contenu, le message était vide" #: Mailman/Handlers/MimeDel.py:269 +#, fuzzy msgid "" "The attached message matched the %(listname)s mailing list's content " "filtering\n" @@ -8887,6 +9049,7 @@ msgid "The attached message has been automatically discarded." msgstr "Le message attaché a été automatiquement supprimé." #: Mailman/Handlers/Replybot.py:75 +#, fuzzy msgid "Auto-response for your message to the \"%(realname)s\" mailing list" msgstr "Réponse automatique à votre message pour la liste \"%(realname)s\" " @@ -8895,6 +9058,7 @@ msgid "The Mailman Replybot" msgstr "Le robot de Réponse de Mailman" #: Mailman/Handlers/Scrubber.py:208 +#, fuzzy msgid "" "An embedded and charset-unspecified text was scrubbed...\n" "Name: %(filename)s\n" @@ -8910,6 +9074,7 @@ msgid "HTML attachment scrubbed and removed" msgstr "La pièce HTML jointe a été nettoyée et enlevée" #: Mailman/Handlers/Scrubber.py:234 Mailman/Handlers/Scrubber.py:259 +#, fuzzy msgid "" "An HTML attachment was scrubbed...\n" "URL: %(url)s\n" @@ -8930,6 +9095,7 @@ msgid "unknown sender" msgstr "expéditeur inconnu" #: Mailman/Handlers/Scrubber.py:276 +#, fuzzy msgid "" "An embedded message was scrubbed...\n" "From: %(who)s\n" @@ -8946,6 +9112,7 @@ msgstr "" "URL: %(url)s\n" #: Mailman/Handlers/Scrubber.py:308 +#, fuzzy msgid "" "A non-text attachment was scrubbed...\n" "Name: %(filename)s\n" @@ -8962,6 +9129,7 @@ msgstr "" "URL: %(url)s\n" #: Mailman/Handlers/Scrubber.py:347 +#, fuzzy msgid "Skipped content of type %(partctype)s\n" msgstr "Contenu de type %(partctype)s sauté\n" @@ -8970,8 +9138,9 @@ msgid "-------------- next part --------------\n" msgstr "-------------- section suivante --------------\n" #: Mailman/Handlers/SpamDetect.py:64 +#, fuzzy msgid "Header matched regexp: %(pattern)s" -msgstr "" +msgstr "%(addr)s est interdite (correspondance: %(patt)s)" #: Mailman/Handlers/SpamDetect.py:127 #, fuzzy @@ -8992,6 +9161,7 @@ msgid "Message rejected by filter rule match" msgstr "Message rejeté par correspondance avec une règle de filtrage" #: Mailman/Handlers/ToDigest.py:173 +#, fuzzy msgid "%(realname)s Digest, Vol %(volume)d, Issue %(issue)d" msgstr "Lot %(realname)s, Vol %(volume)d, Parution %(issue)d" @@ -9028,6 +9198,7 @@ msgid "End of " msgstr "Fin de " #: Mailman/ListAdmin.py:309 +#, fuzzy msgid "Posting of your message titled \"%(subject)s\"" msgstr "Envoi de votre message ayant comme objet \"%(subject)s\"" @@ -9040,6 +9211,7 @@ msgid "Forward of moderated message" msgstr "Faire suivre un message modéré" #: Mailman/ListAdmin.py:405 +#, fuzzy msgid "New subscription request to list %(realname)s from %(addr)s" msgstr "Nouvelle demande d'abonnement à la liste %(realname)s de %(addr)s" @@ -9053,6 +9225,7 @@ msgid "via admin approval" msgstr "Laissez en attente d'approbation" #: Mailman/ListAdmin.py:466 +#, fuzzy msgid "New unsubscription request from %(realname)s by %(addr)s" msgstr "" "Nouvelle demande de résiliation de l'abonnement à la liste\n" @@ -9067,10 +9240,12 @@ msgid "Original Message" msgstr "Message d'origine" #: Mailman/ListAdmin.py:526 +#, fuzzy msgid "Request to mailing list %(realname)s rejected" msgstr "Requête à destination de la liste %(realname)s rejetée" #: Mailman/MTA/Manual.py:66 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been created via the through-the-web\n" "interface. In order to complete the activation of this mailing list, the\n" @@ -9098,14 +9273,17 @@ msgstr "" "lignes suivantes et peut être exécuter le programme `newaliases':\n" #: Mailman/MTA/Manual.py:82 +#, fuzzy msgid "## %(listname)s mailing list" msgstr "liste de diffusion ## %(listname)s" #: Mailman/MTA/Manual.py:99 +#, fuzzy msgid "Mailing list creation request for list %(listname)s" msgstr "Requête de création de la liste %(listname)s" #: Mailman/MTA/Manual.py:113 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been removed via the through-the-web\n" "interface. In order to complete the de-activation of this mailing list, " @@ -9124,6 +9302,7 @@ msgstr "" "Voici la liste des entrées à supprimer du fichier /etc/aliases :\n" #: Mailman/MTA/Manual.py:123 +#, fuzzy msgid "" "\n" "To finish removing your mailing list, you must edit your /etc/aliases (or\n" @@ -9140,14 +9319,17 @@ msgstr "" "##Liste de diffusion %(listname)s" #: Mailman/MTA/Manual.py:142 +#, fuzzy msgid "Mailing list removal request for list %(listname)s" msgstr "Requête de suppression de la liste de diffusion %(listname)s" #: Mailman/MTA/Postfix.py:442 +#, fuzzy msgid "checking permissions on %(file)s" msgstr "Vérification des permissions sur %(file)s" #: Mailman/MTA/Postfix.py:452 +#, fuzzy msgid "%(file)s permissions must be 0664 (got %(octmode)s)" msgstr "les permissions de %(file)s doivent être 0664 (reçu %(octmode)s)" @@ -9161,34 +9343,42 @@ msgid "(fixing)" msgstr "(réparation)" #: Mailman/MTA/Postfix.py:470 +#, fuzzy msgid "checking ownership of %(dbfile)s" msgstr "Vérification de la propriété de %(dbfile)s" #: Mailman/MTA/Postfix.py:478 +#, fuzzy msgid "%(dbfile)s owned by %(owner)s (must be owned by %(user)s" msgstr "%(dbfile)s appartient à %(owner)s (doit appartenir à %(user)s)" #: Mailman/MTA/Postfix.py:491 +#, fuzzy msgid "%(dbfile)s permissions must be 0664 (got %(octmode)s)" msgstr "les permissions de %(dbfile)s doivent être 0664 (reçu %(octmode)s)" #: Mailman/MailList.py:219 +#, fuzzy msgid "Your confirmation is required to join the %(listname)s mailing list" msgstr "Votre confirmation est nécessaire pour accéder à la liste %(listname)s" #: Mailman/MailList.py:230 +#, fuzzy msgid "Your confirmation is required to leave the %(listname)s mailing list" msgstr "Votre confirmation est nécessaire pour quitter la liste %(listname)s" #: Mailman/MailList.py:999 Mailman/MailList.py:1492 +#, fuzzy msgid " from %(remote)s" msgstr " à partir de %(remote)s" #: Mailman/MailList.py:1043 +#, fuzzy msgid "subscriptions to %(realname)s require moderator approval" msgstr "les abonnements à %(realname)s nécessitent l'approbation du modérateur" #: Mailman/MailList.py:1125 bin/add_members:299 +#, fuzzy msgid "%(realname)s subscription notification" msgstr "notification d'abonnement de %(realname)s" @@ -9197,6 +9387,7 @@ msgid "unsubscriptions require moderator approval" msgstr "Les résiliations d'abonnements nécessitent l'approbation du modérateur" #: Mailman/MailList.py:1166 +#, fuzzy msgid "%(realname)s unsubscribe notification" msgstr "notification de résiliation de %(realname)s" @@ -9216,6 +9407,7 @@ msgid "via web confirmation" msgstr "Mauvaise chaîne de confirmation" #: Mailman/MailList.py:1396 +#, fuzzy msgid "subscriptions to %(name)s require administrator approval" msgstr "" "L'abonnement à la liste %(name)s requiert une approbation de l'administrateur" @@ -9235,6 +9427,7 @@ msgid "Last autoresponse notification for today" msgstr "Dernier avis d'envoi de réponse automatique pour la journée" #: Mailman/Queue/BounceRunner.py:360 +#, fuzzy msgid "" "The attached message was received as a bounce, but either the bounce format\n" "was not recognized, or no member addresses could be extracted from it. " @@ -9324,6 +9517,7 @@ msgid "Original message suppressed by Mailman site configuration\n" msgstr "" #: Mailman/htmlformat.py:675 +#, fuzzy msgid "Delivered by Mailman
                      version %(version)s" msgstr "Delivré par Mailman
                      version %(version)s" @@ -9412,6 +9606,7 @@ msgid "Server Local Time" msgstr "Serveur de temps local" #: Mailman/i18n.py:180 +#, fuzzy msgid "" "%(wday)s %(mon)s %(day)2i %(hh)02i:%(mm)02i:%(ss)02i %(tzname)s %(year)04i" msgstr "" @@ -9525,6 +9720,7 @@ msgstr "" "fichiers peut être `-'.\n" #: bin/add_members:162 bin/add_members:172 +#, fuzzy msgid "Already a member: %(member)s" msgstr "Déjà abonné : %(member)s" @@ -9533,10 +9729,12 @@ msgid "Bad/Invalid email address: blank line" msgstr "Adresse courriel Mauvaise/Invalide : ligne vide" #: bin/add_members:180 +#, fuzzy msgid "Bad/Invalid email address: %(member)s" msgstr "Adresse courriel Mauvaise/Invalide : %(member)s" #: bin/add_members:182 +#, fuzzy msgid "Hostile address (illegal characters): %(member)s" msgstr "Adresse hostile (caractères illégaux) : %(member)s" @@ -9546,14 +9744,17 @@ msgid "Invited: %(member)s" msgstr "Abonné : %(member)s" #: bin/add_members:187 +#, fuzzy msgid "Subscribed: %(member)s" msgstr "Abonné : %(member)s" #: bin/add_members:237 +#, fuzzy msgid "Bad argument to -w/--welcome-msg: %(arg)s" msgstr "Mauvais argument fourni à -w/--welcome-msg : %(arg)s" #: bin/add_members:244 +#, fuzzy msgid "Bad argument to -a/--admin-notify: %(arg)s" msgstr "Mauvais argument fourni à -a/--admin-notify : %(arg)s" @@ -9570,6 +9771,7 @@ msgstr "" #: bin/add_members:261 bin/config_list:110 bin/export.py:271 bin/find_member:97 #: bin/inject:91 bin/list_admins:90 bin/list_members:252 bin/sync_members:222 #: cron/bumpdigests:86 +#, fuzzy msgid "No such list: %(listname)s" msgstr "Liste introuvable : %(listname)s" @@ -9580,6 +9782,7 @@ msgid "Nothing to do." msgstr "Rien à faire." #: bin/arch:19 +#, fuzzy msgid "" "Rebuild a list's archive.\n" "\n" @@ -9665,6 +9868,7 @@ msgid "listname is required" msgstr "Nom de liste requis" #: bin/arch:143 bin/change_pw:107 bin/config_list:257 +#, fuzzy msgid "" "No such list \"%(listname)s\"\n" "%(e)s" @@ -9673,10 +9877,12 @@ msgstr "" "%(e)s" #: bin/arch:168 +#, fuzzy msgid "Cannot open mbox file %(mbox)s: %(msg)s" msgstr "Impossible d'ouvrir le fichier mbox %(mbox)s : %(msg)s" #: bin/b4b5-archfix:19 +#, fuzzy msgid "" "Fix the MM2.1b4 archives.\n" "\n" @@ -9822,6 +10028,7 @@ msgstr "" " Afficher ce message d'aide puis quitter.\n" #: bin/change_pw:145 +#, fuzzy msgid "Bad arguments: %(strargs)s" msgstr "Mauvais arguments : %(strargs)s" @@ -9830,14 +10037,17 @@ msgid "Empty list passwords are not allowed" msgstr "Les mot de passe vide ne sont pas admis" #: bin/change_pw:181 +#, fuzzy msgid "New %(listname)s password: %(notifypassword)s" msgstr "Nouveau mot de passe de %(listname)s : %(notifypassword)s" #: bin/change_pw:190 +#, fuzzy msgid "Your new %(listname)s list password" msgstr "Votre nouveau mot de passe pour %(listname)s" #: bin/change_pw:191 +#, fuzzy msgid "" "The site administrator at %(hostname)s has changed the password for your\n" "mailing list %(listname)s. It is now\n" @@ -9865,6 +10075,7 @@ msgstr "" "\t%(adminurl)s\n" #: bin/check_db:19 +#, fuzzy msgid "" "Check a list's config database file for integrity.\n" "\n" @@ -9943,10 +10154,12 @@ msgid "List:" msgstr "Liste :" #: bin/check_db:148 +#, fuzzy msgid " %(file)s: okay" msgstr " %(file)s : Ok" #: bin/check_perms:20 +#, fuzzy msgid "" "Check the permissions for the Mailman installation.\n" "\n" @@ -9967,46 +10180,56 @@ msgstr "" "l'option -v, mode bavard.\n" #: bin/check_perms:110 +#, fuzzy msgid " checking gid and mode for %(path)s" msgstr " vérification du gid et du mode pour %(path)s" #: bin/check_perms:122 +#, fuzzy msgid "%(path)s bad group (has: %(groupname)s, expected %(MAILMAN_GROUP)s)" msgstr "" "Mauvais gid pour %(path)s (Obtenu: %(groupname)s, Attendu %(MAILMAN_GROUP)s)" #: bin/check_perms:151 +#, fuzzy msgid "directory permissions must be %(octperms)s: %(path)s" msgstr "" "Les permissions sur les répertoires doivent être de %(octperms)s : %(path)s" #: bin/check_perms:160 +#, fuzzy msgid "source perms must be %(octperms)s: %(path)s" msgstr "" "Les permissions sur les sources doivent être de %(octperms)s : %(path)s" #: bin/check_perms:171 +#, fuzzy msgid "article db files must be %(octperms)s: %(path)s" msgstr "" "Les permissions sur les fichiers db doivent être de %(octperms)s: %(path)s" #: bin/check_perms:183 +#, fuzzy msgid "checking mode for %(prefix)s" msgstr "Vérification du mode pour %(prefix)s" #: bin/check_perms:193 +#, fuzzy msgid "WARNING: directory does not exist: %(d)s" msgstr "ATTENTION : le répertoire %(d)s n'existe pas" #: bin/check_perms:197 +#, fuzzy msgid "directory must be at least 02775: %(d)s" msgstr "Les répertoires doivent être au moins en 02775 : %(d)s" #: bin/check_perms:209 +#, fuzzy msgid "checking perms on %(private)s" msgstr "Vérification des permissions sur %(private)s" #: bin/check_perms:214 +#, fuzzy msgid "%(private)s must not be other-readable" msgstr "%(private)s ne doit pas être lisible par le groupe \"reste du monde\"" @@ -10030,6 +10253,7 @@ msgid "mbox file must be at least 0660:" msgstr "Un fichier mbox doit avoir un jeu de permissions d'au moins 0660 :" #: bin/check_perms:263 +#, fuzzy msgid "%(dbdir)s \"other\" perms must be 000" msgstr "%(dbdir)s les permissions pour \"reste du monde\" doivent être de 000" @@ -10038,26 +10262,32 @@ msgid "checking cgi-bin permissions" msgstr "Vérification des permissions du répertoire cgi-bin" #: bin/check_perms:278 +#, fuzzy msgid " checking set-gid for %(path)s" msgstr "\tvérification du bit set-gid pour %(path)s" #: bin/check_perms:282 +#, fuzzy msgid "%(path)s must be set-gid" msgstr "le bit set-gid doit être actif sur %(path)s" #: bin/check_perms:292 +#, fuzzy msgid "checking set-gid for %(wrapper)s" msgstr "vérification du bit set-gid pour %(wrapper)s" #: bin/check_perms:296 +#, fuzzy msgid "%(wrapper)s must be set-gid" msgstr "%(wrapper)s doit avoir le bit set-gid activé" #: bin/check_perms:306 +#, fuzzy msgid "checking permissions on %(pwfile)s" msgstr "Vérification des permissions sur %(pwfile)s" #: bin/check_perms:315 +#, fuzzy msgid "%(pwfile)s permissions must be exactly 0640 (got %(octmode)s)" msgstr "" "le fichier %(pwfile)s doit avoir un jeu de permissions de 0640 exactement " @@ -10068,10 +10298,12 @@ msgid "checking permissions on list data" msgstr "Vérification des permissions sur les données de la liste" #: bin/check_perms:348 +#, fuzzy msgid " checking permissions on: %(path)s" msgstr "\tvérification des permission sur : %(path)s" #: bin/check_perms:356 +#, fuzzy msgid "file permissions must be at least 660: %(path)s" msgstr "les permissions sur les fichiers doivent au moins être 660 : %(path)s" @@ -10157,6 +10389,7 @@ msgid "Unix-From line changed: %(lineno)d" msgstr "Ligne Unix-From modifiée : %(lineno)d" #: bin/cleanarch:111 +#, fuzzy msgid "Bad status number: %(arg)s" msgstr "Mauvais numéros de statut : %(arg)s" @@ -10304,10 +10537,12 @@ msgid " original address removed:" msgstr " Adresse d'origine supprimée :" #: bin/clone_member:202 +#, fuzzy msgid "Not a valid email address: %(toaddr)s" msgstr "Adresse courriel invalide : %(toaddr)s" #: bin/clone_member:215 +#, fuzzy msgid "" "Error opening list \"%(listname)s\", skipping.\n" "%(e)s" @@ -10413,6 +10648,7 @@ msgstr "" "Les options -o et -i s'excluent mutuellement\n" #: bin/config_list:118 +#, fuzzy msgid "" "# -*- python -*-\n" "# -*- coding: %(charset)s -*-\n" @@ -10433,22 +10669,27 @@ msgid "legal values are:" msgstr "Les valeurs admises sont :" #: bin/config_list:270 +#, fuzzy msgid "attribute \"%(k)s\" ignored" msgstr "propriété \"%(k)s\" ignorée" #: bin/config_list:273 +#, fuzzy msgid "attribute \"%(k)s\" changed" msgstr "propriété \"%(k)s\" modifiée" #: bin/config_list:279 +#, fuzzy msgid "Non-standard property restored: %(k)s" msgstr "Propriété non-standard réactivée : %(k)s" #: bin/config_list:288 +#, fuzzy msgid "Invalid value for property: %(k)s" msgstr "Valeur invalide pour la propriété : %(k)s" #: bin/config_list:291 +#, fuzzy msgid "Bad email address for option %(k)s: %(v)s" msgstr "Adresse courriel invalide pour l'option %(k)s : %(v)s" @@ -10512,18 +10753,22 @@ msgstr "" " N'affiche aucun message.\n" #: bin/discard:94 +#, fuzzy msgid "Ignoring non-held message: %(f)s" msgstr "Ignorer le message : %(f)s" #: bin/discard:100 +#, fuzzy msgid "Ignoring held msg w/bad id: %(f)s" msgstr "Ignorer le message en attente avec mauvais id : %(f)s" #: bin/discard:112 +#, fuzzy msgid "Discarded held msg #%(id)s for list %(listname)s" msgstr "Détruire le message en attente #%(id)s pour la liste %(listname)s" #: bin/dumpdb:19 +#, fuzzy msgid "" "Dump the contents of any Mailman `database' file.\n" "\n" @@ -10595,6 +10840,7 @@ msgid "No filename given." msgstr "Aucun nom de fichier fourni." #: bin/dumpdb:108 +#, fuzzy msgid "Bad arguments: %(pargs)s" msgstr "Mauvais argument : %(pargs)s" @@ -10603,14 +10849,17 @@ msgid "Please specify either -p or -m." msgstr "Veuillez spécifier l'une des options -p ou -m." #: bin/dumpdb:133 +#, fuzzy msgid "[----- start %(typename)s file -----]" msgstr "[-----début du fichier %(typename)s -----]" #: bin/dumpdb:139 +#, fuzzy msgid "[----- end %(typename)s file -----]" msgstr "[----- fin du fichier %(typename)s -----]" #: bin/dumpdb:142 +#, fuzzy msgid "<----- start object %(cnt)s ----->" msgstr "<----- début de l'objet %(cnt)s ----->" @@ -10834,6 +11083,7 @@ msgid "Setting web_page_url to: %(web_page_url)s" msgstr "réglage de url_page_web à : %(web_page_url)s" #: bin/fix_url.py:88 +#, fuzzy msgid "Setting host_name to: %(mailhost)s" msgstr "Réglage de nom_hôte à : %(mailhost)s" @@ -10926,6 +11176,7 @@ msgstr "" "injecter. L'entrée standard est utilisée si vous l'omettez.\n" #: bin/inject:84 +#, fuzzy msgid "Bad queue directory: %(qdir)s" msgstr "Nom de répertoire file d'attente erroné : %(qdir)s" @@ -10934,6 +11185,7 @@ msgid "A list name is required" msgstr "Un nom de liste est requis" #: bin/list_admins:20 +#, fuzzy msgid "" "List all the owners of a mailing list.\n" "\n" @@ -10977,6 +11229,7 @@ msgstr "" "d'une liste sur la ligne de commande.\n" #: bin/list_admins:97 +#, fuzzy msgid "List: %(listname)s, \tOwners: %(owners)s" msgstr "Liste : %(listname)s, \tPropriétaire : %(owners)s" @@ -11165,10 +11418,12 @@ msgstr "" "\n" #: bin/list_members:198 +#, fuzzy msgid "Bad --nomail option: %(why)s" msgstr "Mauvaise option --noncourriel : %(why)s" #: bin/list_members:209 +#, fuzzy msgid "Bad --digest option: %(kind)s" msgstr "Mauvaise option --digest : %(kind)s" @@ -11182,6 +11437,7 @@ msgid "Could not open file for writing:" msgstr "Impossible d'ouvrir le fichier en écriture :" #: bin/list_owners:20 +#, fuzzy msgid "" "List the owners of a mailing list, or all mailing lists.\n" "\n" @@ -11234,6 +11490,7 @@ msgid "" msgstr "" #: bin/mailmanctl:20 +#, fuzzy msgid "" "Primary start-up and shutdown script for Mailman's qrunner daemon.\n" "\n" @@ -11411,6 +11668,7 @@ msgstr "" "\n" #: bin/mailmanctl:152 +#, fuzzy msgid "PID unreadable in: %(pidfile)s" msgstr "PID illisible dans : %(pidfile)s" @@ -11419,6 +11677,7 @@ msgid "Is qrunner even running?" msgstr "qrunner tourne-t-il ?" #: bin/mailmanctl:160 +#, fuzzy msgid "No child with pid: %(pid)s" msgstr "Pas de processus fils avec un pid : %(pid)s" @@ -11447,6 +11706,7 @@ msgstr "" "mailmanctl avec l'option -s.\n" #: bin/mailmanctl:233 +#, fuzzy msgid "" "The master qrunner lock could not be acquired, because it appears as if " "some\n" @@ -11473,10 +11733,12 @@ msgstr "" "Sortie." #: bin/mailmanctl:280 cron/mailpasswds:119 +#, fuzzy msgid "Site list is missing: %(sitelistname)s" msgstr "La liste du site est introuvable : %(sitelistname)s" #: bin/mailmanctl:305 +#, fuzzy msgid "Run this program as root or as the %(name)s user, or use -u." msgstr "" "Exécutez ce programme en tant que root ou en tant qu'utilisateur\n" @@ -11487,6 +11749,7 @@ msgid "No command given." msgstr "Aucune commande fournie." #: bin/mailmanctl:339 +#, fuzzy msgid "Bad command: %(command)s" msgstr "Mauvaise commande : %(command)s" @@ -11511,6 +11774,7 @@ msgid "Starting Mailman's master qrunner." msgstr "Démarrage du qrunner principal de Mailman." #: bin/mmsitepass:19 +#, fuzzy msgid "" "Set the site password, prompting from the terminal.\n" "\n" @@ -11567,6 +11831,7 @@ msgid "list creator" msgstr "créateur de la liste" #: bin/mmsitepass:86 +#, fuzzy msgid "New %(pwdesc)s password: " msgstr "Nouveau mot de passe %(pwdesc)s :" @@ -11833,6 +12098,7 @@ msgstr "" "Notez que les noms de liste sont ramenés en minuscule.\n" #: bin/newlist:162 +#, fuzzy msgid "Unknown language: %(lang)s" msgstr "Langue inconnue : %(lang)s" @@ -11845,6 +12111,7 @@ msgid "Enter the email of the person running the list: " msgstr "Entrez l'adresse courriel du gestionnaire de la liste : " #: bin/newlist:193 +#, fuzzy msgid "Initial %(listname)s password: " msgstr "Mot de passe initial de la liste %(listname)s :" @@ -11854,15 +12121,17 @@ msgstr "Le mot de passe de la liste ne peut pas #: bin/newlist:220 msgid "" -" - owner addresses need to be fully-qualified names like \"owner@example.com" -"\", not just \"owner\"." +" - owner addresses need to be fully-qualified names like \"owner@example." +"com\", not just \"owner\"." msgstr "" #: bin/newlist:244 +#, fuzzy msgid "Hit enter to notify %(listname)s owner..." msgstr "Tapez sur Entrée pour aviser le propriétaire de %(listname)s..." #: bin/qrunner:20 +#, fuzzy msgid "" "Run one or more qrunners, once or repeatedly.\n" "\n" @@ -11990,6 +12259,7 @@ msgstr "" "Lancé seul, il n'est utile qu'à des fins de débogage.\n" #: bin/qrunner:179 +#, fuzzy msgid "%(name)s runs the %(runnername)s qrunner" msgstr "-r %(name)s exécute le qrunner %(runnername)s" @@ -12002,6 +12272,7 @@ msgid "No runner name given." msgstr "Nom de runner non fourni." #: bin/rb-archfix:21 +#, fuzzy msgid "" "Reduce disk space usage for Pipermail archives.\n" "\n" @@ -12152,18 +12423,22 @@ msgstr "" " addr1 ... sont les adresses additionnelles à supprimer.\n" #: bin/remove_members:156 +#, fuzzy msgid "Could not open file for reading: %(filename)s." msgstr "Impossible d'ouvrir le fichier en lecture : %(filename)s" #: bin/remove_members:163 +#, fuzzy msgid "Error opening list %(listname)s... skipping." msgstr "Erreur lors de l'ouverture de la liste \"%(listname)s\"... au suivant." #: bin/remove_members:173 +#, fuzzy msgid "No such member: %(addr)s" msgstr "Abonné inconnu : %(addr)s." #: bin/remove_members:178 +#, fuzzy msgid "User `%(addr)s' removed from list: %(listname)s." msgstr "Abonné `%(addr)s' supprimé de la liste : %(listname)s." @@ -12204,6 +12479,7 @@ msgstr "" " Affiche ce que fait le script.\n" #: bin/reset_pw.py:77 +#, fuzzy msgid "Changing passwords for list: %(listname)s" msgstr "Changer les mots de passe pour la liste : %(listname)s" @@ -12253,18 +12529,22 @@ msgstr "" " Afficher ce message d'aide puis quitter.\n" #: bin/rmlist:73 bin/rmlist:76 +#, fuzzy msgid "Removing %(msg)s" msgstr "Suppression de %(msg)s" #: bin/rmlist:81 +#, fuzzy msgid "%(listname)s %(msg)s not found as %(filename)s" msgstr "%(msg)s %(listname)s non trouvé en tant que %(filename)s" #: bin/rmlist:105 +#, fuzzy msgid "No such list (or list already deleted): %(listname)s" msgstr "Liste introuvable (ou liste déjà supprimée) : %(listname)s" #: bin/rmlist:108 +#, fuzzy msgid "No such list: %(listname)s. Removing its residual archives." msgstr "" "Liste introuvable : %(listname)s. Suppression de ses archives\n" @@ -12326,6 +12606,7 @@ msgstr "" "Exemple : show_qfiles qfiles/shunt/*.pck\n" #: bin/sync_members:19 +#, fuzzy msgid "" "Synchronize a mailing list's membership with a flat file.\n" "\n" @@ -12459,6 +12740,7 @@ msgstr "" " Requis. Il définit la liste à synchroniser.\n" #: bin/sync_members:115 +#, fuzzy msgid "Bad choice: %(yesno)s" msgstr "Mauvais choix : %(yesno)s" @@ -12475,6 +12757,7 @@ msgid "No argument to -f given" msgstr "aucun argument n'a été fourni à -f" #: bin/sync_members:172 +#, fuzzy msgid "Illegal option: %(opt)s" msgstr "Option invalide : %(opt)s" @@ -12487,6 +12770,7 @@ msgid "Must have a listname and a filename" msgstr "Nom de liste et nom de fichier nécessaires" #: bin/sync_members:191 +#, fuzzy msgid "Cannot read address file: %(filename)s: %(msg)s" msgstr "Impossible de lire le fichier des adresses : %(filename)s: %(msg)s" @@ -12504,14 +12788,17 @@ msgstr "" "Vous devez d'abord régler le problème de l'adresse invalide précédente." #: bin/sync_members:264 +#, fuzzy msgid "Added : %(s)s" msgstr "Ajouté : %(s)s" #: bin/sync_members:289 +#, fuzzy msgid "Removed: %(s)s" msgstr "Suppression de : %(s)s" #: bin/transcheck:19 +#, fuzzy msgid "" "\n" "Check a given Mailman translation, making sure that variables and\n" @@ -12593,6 +12880,7 @@ msgid "scan the po file comparing msgids with msgstrs" msgstr "Scan le fichier po en comparant msgids et msgstrs" #: bin/unshunt:20 +#, fuzzy msgid "" "Move a message from the shunt queue to the original queue.\n" "\n" @@ -12625,6 +12913,7 @@ msgstr "" "pour résultat la perte de tous les messages de cette file.\n" #: bin/unshunt:85 +#, fuzzy msgid "" "Cannot unshunt message %(filebase)s, skipping:\n" "%(e)s" @@ -12633,6 +12922,7 @@ msgstr "" "%(e)s" #: bin/update:20 +#, fuzzy msgid "" "Perform all necessary upgrades.\n" "\n" @@ -12670,15 +12960,18 @@ msgstr "" "1.0b4 (?).\n" #: bin/update:107 +#, fuzzy msgid "Fixing language templates: %(listname)s" msgstr "Réparation des modèles de langue : %(listname)s" #: bin/update:196 bin/update:711 +#, fuzzy msgid "WARNING: could not acquire lock for list: %(listname)s" msgstr "" "ATTENTION : impossible de disposer d'un verrou sur la liste %(listname)s" #: bin/update:215 +#, fuzzy msgid "Resetting %(n)s BYBOUNCEs disabled addrs with no bounce info" msgstr "" "Réinitialiser les addrs désactivées %(n)s BYBOUNCEs sans infos de rejets" @@ -12697,6 +12990,7 @@ msgstr "" "%(mbox_dir)s.tmp pour pouvoir poursuivre." #: bin/update:255 +#, fuzzy msgid "" "\n" "%(listname)s has both public and private mbox archives. Since this list\n" @@ -12749,6 +13043,7 @@ msgid "- updating old private mbox file" msgstr "- mise à jour de l'ancien fichier mbox privé" #: bin/update:295 +#, fuzzy msgid "" " unknown file in the way, moving\n" " %(o_pri_mbox_file)s\n" @@ -12765,6 +13060,7 @@ msgid "- updating old public mbox file" msgstr "- Mise à jour de l'ancien fichier mbox public" #: bin/update:317 +#, fuzzy msgid "" " unknown file in the way, moving\n" " %(o_pub_mbox_file)s\n" @@ -12793,18 +13089,22 @@ msgid "- %(o_tmpl)s doesn't exist, leaving untouched" msgstr "- %(o_tmpl)s n'existe pas, aucune modification" #: bin/update:396 +#, fuzzy msgid "removing directory %(src)s and everything underneath" msgstr "Suppression du répertoire %(src)s et de tout ce qu'il contient" #: bin/update:399 +#, fuzzy msgid "removing %(src)s" msgstr "Suppression de %(src)s" #: bin/update:403 +#, fuzzy msgid "Warning: couldn't remove %(src)s -- %(rest)s" msgstr "Attention : impossible de supprimer %(src)s -- %(rest)s" #: bin/update:408 +#, fuzzy msgid "couldn't remove old file %(pyc)s -- %(rest)s" msgstr "Impossible de supprimer l'ancien fichier %(pyc)s -- %(rest)s" @@ -12813,10 +13113,12 @@ msgid "updating old qfiles" msgstr "mise à jour des anciens fichiers qfiles" #: bin/update:455 +#, fuzzy msgid "Warning! Not a directory: %(dirpath)s" msgstr "Avertissement ! Cen'est pas un répertoire : %(dirpath)s" #: bin/update:530 +#, fuzzy msgid "message is unparsable: %(filebase)s" msgstr "le message n'est pas analysable %(filebase)s" @@ -12834,10 +13136,12 @@ msgid "Updating Mailman 2.1.4 pending.pck database" msgstr "Mise à jour de la base de données pending.pck de Mailman 2.1.4" #: bin/update:598 +#, fuzzy msgid "Ignoring bad pended data: %(key)s: %(val)s" msgstr "Ignorer les données en attente corrompues : %(key)s: %(val)s" #: bin/update:614 +#, fuzzy msgid "WARNING: Ignoring duplicate pending ID: %(id)s." msgstr "ATTENTION : ignore les ID dupliqués : %(id)s." @@ -12864,6 +13168,7 @@ msgid "done" msgstr "terminé" #: bin/update:691 +#, fuzzy msgid "Updating mailing list: %(listname)s" msgstr "Mise à jour de la liste de diffusion %(listname)s" @@ -12924,6 +13229,7 @@ msgid "No updates are necessary." msgstr "Pas de mise à jour nécessaire." #: bin/update:796 +#, fuzzy msgid "" "Downgrade detected, from version %(hexlversion)s to version %(hextversion)s\n" "This is probably not safe.\n" @@ -12934,10 +13240,12 @@ msgstr "" "Je me barre !" #: bin/update:801 +#, fuzzy msgid "Upgrading from version %(hexlversion)s to %(hextversion)s" msgstr "Mise à jour de la version %(hexlversion)s vers %(hextversion)s" #: bin/update:810 +#, fuzzy msgid "" "\n" "ERROR:\n" @@ -13218,6 +13526,7 @@ msgstr "" "\t" #: bin/withlist:175 +#, fuzzy msgid "Unlocking (but not saving) list: %(listname)s" msgstr "Déverrouillage (sans sauvegarde) de la liste : %(listname)s" @@ -13226,6 +13535,7 @@ msgid "Finalizing" msgstr "Finalisation" #: bin/withlist:188 +#, fuzzy msgid "Loading list %(listname)s" msgstr "Chargement de la liste %(listname)s en cours" @@ -13238,6 +13548,7 @@ msgid "(unlocked)" msgstr "(déverrouillé)" #: bin/withlist:197 +#, fuzzy msgid "Unknown list: %(listname)s" msgstr "Liste inconnue : %(listname)s" @@ -13250,20 +13561,24 @@ msgid "--all requires --run" msgstr "--all va avec --run" #: bin/withlist:266 +#, fuzzy msgid "Importing %(module)s..." msgstr "Importation de %(module)s..." #: bin/withlist:270 +#, fuzzy msgid "Running %(module)s.%(callable)s()..." msgstr "Exécution de %(module)s.%(callable)s()..." #: bin/withlist:291 +#, fuzzy msgid "The variable `m' is the %(listname)s MailList instance" msgstr "" "La variable `m' est une instance de la classe MailList de\n" "%(listname)s" #: cron/bumpdigests:19 +#, fuzzy msgid "" "Increment the digest volume number and reset the digest number to one.\n" "\n" @@ -13289,6 +13604,7 @@ msgstr "" "aucun nom de liste n'est fourni, toutes les listes sont traitées.\n" #: cron/checkdbs:20 +#, fuzzy msgid "" "Check for pending admin requests and mail the list owners if necessary.\n" "\n" @@ -13318,10 +13634,12 @@ msgstr "" "\n" #: cron/checkdbs:121 +#, fuzzy msgid "%(count)d %(realname)s moderator request(s) waiting" msgstr "%(count)d requête(s) pour le modérateur de %(realname)s en attente" #: cron/checkdbs:124 +#, fuzzy msgid "%(realname)s moderator request check result" msgstr "le modérateur %(realname)s requiert la vérification des résultats" @@ -13343,6 +13661,7 @@ msgstr "" "Envois en attente :" #: cron/checkdbs:169 +#, fuzzy msgid "" "From: %(sender)s on %(date)s\n" "Subject: %(subject)s\n" @@ -13374,6 +13693,7 @@ msgid "" msgstr "" #: cron/disabled:20 +#, fuzzy msgid "" "Process disabled members, recommended once per day.\n" "\n" @@ -13505,6 +13825,7 @@ msgstr "" " Afficher ce message puis quitter.\n" #: cron/mailpasswds:19 +#, fuzzy msgid "" "Send password reminders for all lists to all users.\n" "\n" @@ -13558,10 +13879,12 @@ msgid "Password // URL" msgstr "Mot de passe // URL" #: cron/mailpasswds:222 +#, fuzzy msgid "%(host)s mailing list memberships reminder" msgstr "Rappel pour les abonnements aux listes sur %(host)s" #: cron/nightly_gzip:19 +#, fuzzy msgid "" "Re-generate the Pipermail gzip'd archive flat files.\n" "\n" @@ -13679,8 +14002,8 @@ msgstr "" #~ " applied" #~ msgstr "" #~ "Texte à inclure dans les avis de\n" +#~ " href=\"?VARHELP/privacy/sender/" +#~ "member_moderation_action\">avis de\n" #~ " rejet à envoyer aux abonnés sous modération ayant soumis " #~ "un\n" #~ " message à la liste." diff --git a/messages/gl/LC_MESSAGES/mailman.po b/messages/gl/LC_MESSAGES/mailman.po index c95f1ba4..950c0046 100755 --- a/messages/gl/LC_MESSAGES/mailman.po +++ b/messages/gl/LC_MESSAGES/mailman.po @@ -67,10 +67,12 @@ msgid "

                      Currently, there are no archives.

                      " msgstr "

                      Actualmente non hai ningún ficheiro.

                      " #: Mailman/Archiver/HyperArch.py:821 +#, fuzzy msgid "Gzip'd Text%(sz)s" msgstr "Texto Gzip%(sz)s" #: Mailman/Archiver/HyperArch.py:826 +#, fuzzy msgid "Text%(sz)s" msgstr "Texto%(sz)s" @@ -143,18 +145,22 @@ msgid "Third" msgstr "Terceiro" #: Mailman/Archiver/HyperArch.py:938 +#, fuzzy msgid "%(ord)s quarter %(year)i" msgstr "%(ord)s cuarto %(year)i" #: Mailman/Archiver/HyperArch.py:945 +#, fuzzy msgid "%(month)s %(year)i" msgstr "%(month)s %(year)i" #: Mailman/Archiver/HyperArch.py:950 +#, fuzzy msgid "The Week Of Monday %(day)i %(month)s %(year)i" msgstr "A semana do %(day)i %(month)s %(year)i" #: Mailman/Archiver/HyperArch.py:954 +#, fuzzy msgid "%(day)i %(month)s %(year)i" msgstr "%(day)i %(month)s %(year)i" @@ -163,10 +169,12 @@ msgid "Computing threaded index\n" msgstr "Estase a calcular o índice dos fíos\n" #: Mailman/Archiver/HyperArch.py:1319 +#, fuzzy msgid "Updating HTML for article %(seq)s" msgstr "Estase a actualizar o código HTML do artigo %(seq)s" #: Mailman/Archiver/HyperArch.py:1326 +#, fuzzy msgid "article file %(filename)s is missing!" msgstr "non se atopa o ficheiro %(filename)s asociado ao artigo" @@ -183,6 +191,7 @@ msgid "Pickling archive state into " msgstr "Estase a preparar o estado do arquivo a " #: Mailman/Archiver/pipermail.py:453 +#, fuzzy msgid "Updating index files for archive [%(archive)s]" msgstr "Estase a actualizar o índice dos ficheiros de [%(archive)s]" @@ -191,6 +200,7 @@ msgid " Thread" msgstr " Fío" #: Mailman/Archiver/pipermail.py:597 +#, fuzzy msgid "#%(counter)05d %(msgid)s" msgstr "#%(counter)05d %(msgid)s" @@ -228,6 +238,7 @@ msgid "disabled address" msgstr "inhabilitada" #: Mailman/Bouncer.py:304 +#, fuzzy msgid " The last bounce received from you was dated %(date)s" msgstr "A última devolución que se recibiu foi hai %(date)s" @@ -255,6 +266,7 @@ msgstr "Administrador" #: Mailman/Cgi/options.py:101 Mailman/Cgi/private.py:108 #: Mailman/Cgi/rmlist.py:75 Mailman/Cgi/roster.py:59 #: Mailman/Cgi/subscribe.py:67 +#, fuzzy msgid "No such list %(safelistname)s" msgstr "A rolda %(safelistname)s non existe" @@ -328,6 +340,7 @@ msgstr "" " recibirán correo até que corrixir este problema.%(rm)r" #: Mailman/Cgi/admin.py:268 +#, fuzzy msgid "%(hostname)s mailing lists - Admin Links" msgstr "Roldas de distribución en %(hostname)s - Ligazóns da administración" @@ -340,6 +353,7 @@ msgid "Mailman" msgstr "Mailman" #: Mailman/Cgi/admin.py:310 +#, fuzzy msgid "" "

                      There currently are no publicly-advertised %(mailmanlink)s\n" " mailing lists on %(hostname)s." @@ -348,6 +362,7 @@ msgstr "" " que anunciar publicamente en %(hostname)s. " #: Mailman/Cgi/admin.py:316 +#, fuzzy msgid "" "

                      Below is the collection of publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s. Click on a list\n" @@ -363,6 +378,7 @@ msgid "right " msgstr " correcta" #: Mailman/Cgi/admin.py:325 +#, fuzzy msgid "" "To visit the administrators configuration page for an\n" " unadvertised list, open a URL similar to this one, but with a '/' " @@ -407,14 +423,16 @@ msgid "No valid variable name found." msgstr "Achouse un nome de variábel que non é válido" #: Mailman/Cgi/admin.py:395 +#, fuzzy msgid "" "%(realname)s Mailing list Configuration Help\n" "
                      %(varname)s Option" msgstr "" -"Axuda de configuración da rolda de distribución %(realname)s, opción
                      " -"%(varname)s" +"Axuda de configuración da rolda de distribución %(realname)s, opción " +"
                      %(varname)s" #: Mailman/Cgi/admin.py:402 +#, fuzzy msgid "Mailman %(varname)s List Option Help" msgstr "Axuda do Mailman para a opción da rolda %(varname)s" @@ -434,14 +452,17 @@ msgstr "" " " #: Mailman/Cgi/admin.py:431 +#, fuzzy msgid "return to the %(categoryname)s options page." msgstr "Volver á páxina de opcións %(categoryname)s" #: Mailman/Cgi/admin.py:446 +#, fuzzy msgid "%(realname)s Administration (%(label)s)" msgstr "Administración de %(realname)s (%(label)s)" #: Mailman/Cgi/admin.py:447 +#, fuzzy msgid "%(realname)s mailing list administration
                      %(label)s Section" msgstr "" "Administración da rolda de distribución %(realname)s
                      Sección de %(label)s" @@ -526,6 +547,7 @@ msgid "Value" msgstr "Valor" #: Mailman/Cgi/admin.py:668 +#, fuzzy msgid "" "Badly formed options entry:\n" " %(record)s" @@ -626,10 +648,12 @@ msgid "Move rule down" msgstr "Mover regra cara abaixo" #: Mailman/Cgi/admin.py:870 +#, fuzzy msgid "
                      (Edit %(varname)s)" msgstr "
                      (Editar %(varname)s)" #: Mailman/Cgi/admin.py:872 +#, fuzzy msgid "
                      (Details for %(varname)s)" msgstr "
                      (Detalles de %(varname)s)" @@ -671,6 +695,7 @@ msgid "(help)" msgstr "(axuda)" #: Mailman/Cgi/admin.py:930 +#, fuzzy msgid "Find member %(link)s:" msgstr "Localizar o subscritor %(link)s:" @@ -683,10 +708,12 @@ msgid "Bad regular expression: " msgstr "A expresión regular está mal formada: " #: Mailman/Cgi/admin.py:1013 +#, fuzzy msgid "%(allcnt)s members total, %(membercnt)s shown" msgstr "hai %(allcnt)s subscritores en total e amósanse %(membercnt)s" #: Mailman/Cgi/admin.py:1016 +#, fuzzy msgid "%(allcnt)s members total" msgstr "%(allcnt)s subscritores en total" @@ -882,6 +909,7 @@ msgstr "" " o rango apropiado entre os que se relacionan abaixo:" #: Mailman/Cgi/admin.py:1221 +#, fuzzy msgid "from %(start)s to %(end)s" msgstr "de %(start)s a %(end)s" @@ -1019,6 +1047,7 @@ msgid "Change list ownership passwords" msgstr "Cambiar a contrasinal do xestor da rolda" #: Mailman/Cgi/admin.py:1364 +#, fuzzy msgid "" "The list administrators are the people who have ultimate control " "over\n" @@ -1133,6 +1162,7 @@ msgstr "Enderezo hostil (os caracteres non son válidos)" #: Mailman/Cgi/admin.py:1529 Mailman/Cgi/admin.py:1703 bin/add_members:175 #: bin/clone_member:136 bin/sync_members:268 +#, fuzzy msgid "Banned address (matched %(pattern)s)" msgstr "Enderezo expulsado (concorda %(pattern)s)" @@ -1235,6 +1265,7 @@ msgid "Not subscribed" msgstr "Non está subscrito" #: Mailman/Cgi/admin.py:1773 +#, fuzzy msgid "Ignoring changes to deleted member: %(user)s" msgstr "Estanse a ignorar os cambios do usuario borrado: %(user)s" @@ -1247,10 +1278,12 @@ msgid "Error Unsubscribing:" msgstr "Produciuse un erro ao dar de baixa a subscrición:" #: Mailman/Cgi/admindb.py:235 Mailman/Cgi/admindb.py:248 +#, fuzzy msgid "%(realname)s Administrative Database" msgstr "Base de datos administrativa %(realname)s" #: Mailman/Cgi/admindb.py:238 +#, fuzzy msgid "%(realname)s Administrative Database Results" msgstr "Resultados da base de datos administrativa de %(realname)s" @@ -1279,6 +1312,7 @@ msgid "Discard all messages marked Defer" msgstr "Descartar todas as mensaxes marcadas Diferir" #: Mailman/Cgi/admindb.py:298 +#, fuzzy msgid "all of %(esender)s's held messages." msgstr "todas as mensaxes retidas de %(esender)s." @@ -1299,6 +1333,7 @@ msgid "list of available mailing lists." msgstr "relación das roldas de distribución que estiveren dispoñíbeis." #: Mailman/Cgi/admindb.py:362 +#, fuzzy msgid "You must specify a list name. Here is the %(link)s" msgstr "" "Ten de se especificar un nome de rolda. Aquí ten a a ligazón a %(link)s" @@ -1382,6 +1417,7 @@ msgid "The sender is now a member of this list" msgstr "O remitinte é agora subscritor desta rolda" #: Mailman/Cgi/admindb.py:568 +#, fuzzy msgid "Add %(esender)s to one of these sender filters:" msgstr "Engadir %(esender)s a un destes filtros de remitentes:" @@ -1402,6 +1438,7 @@ msgid "Rejects" msgstr "Rexeitar" #: Mailman/Cgi/admindb.py:584 +#, fuzzy msgid "" "Ban %(esender)s from ever subscribing to this\n" " mailing list" @@ -1418,6 +1455,7 @@ msgstr "" " mensaxe individualmente, ou pode" #: Mailman/Cgi/admindb.py:591 +#, fuzzy msgid "view all messages from %(esender)s" msgstr "ver todas as mensaxes de %(esender)s" @@ -1496,6 +1534,7 @@ msgid " is already a member" msgstr " xa é un subscritor" #: Mailman/Cgi/admindb.py:973 +#, fuzzy msgid "%(addr)s is banned (matched: %(patt)s)" msgstr "%(addr)s está expulsado (coincide: %(patt)s)" @@ -1548,6 +1587,7 @@ msgstr "" " cancelouse" #: Mailman/Cgi/confirm.py:178 +#, fuzzy msgid "System error, bad content: %(content)s" msgstr "Error do sistema; o contido está danado: %(content)s" @@ -1586,6 +1626,7 @@ msgid "Confirm subscription request" msgstr "Confirmar a solicitude de subscrición" #: Mailman/Cgi/confirm.py:259 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " subscription request to the mailing list %(listname)s. Your\n" @@ -1603,8 +1644,24 @@ msgid "" "to\n" " subscribe to this list." msgstr "" +"É necesaria a confirmación para continuar coa solicitude de\n" +" subscrición á rolda de distribución\n" +" %(listname)s. A seguir, amósanse as súas preferencias\n" +" de subscrición; pode realizar calquera cambio que estimar conveniente e\n" +" prema Subscribir para completar o proceso de\n" +" confirmación. Logo de confirmar a solicitude, o moderador\n" +" ten que darlle a aprobación, que recibirá nunha mensaxe.\n" +"\n" +" Nota: enviaráselle o seu contrasinal por correo electrónico logo de se " +"confirmar\n" +" a súa subscrición. Pode cambialo na súa páxina de opcións personais.\n" +"\n" +"

                      Se mudou de parecer e non quere subscribirse a esta rolda de\n" +" distribución, pode premer Cancelar a miña solicitude de subscrición." #: Mailman/Cgi/confirm.py:275 +#, fuzzy msgid "" "Your confirmation is required in order to continue with\n" " the subscription request to the mailing list %(listname)s.\n" @@ -1657,6 +1714,7 @@ msgid "Preferred language:" msgstr "Idioma preferido:" #: Mailman/Cgi/confirm.py:317 +#, fuzzy msgid "Subscribe to list %(listname)s" msgstr "Subscribirse á rolda %(listname)s" @@ -1673,6 +1731,7 @@ msgid "Awaiting moderator approval" msgstr "Estase a agardar a aprobación do moderador" #: Mailman/Cgi/confirm.py:386 +#, fuzzy msgid "" " You have successfully confirmed your subscription request to " "the\n" @@ -1706,11 +1765,16 @@ msgid "You are already a member of this mailing list!" msgstr "Xa está subscrito a esta rolda de distribución." #: Mailman/Cgi/confirm.py:400 +#, fuzzy msgid "" "You are currently banned from subscribing to\n" " this list. If you think this restriction is erroneous, please\n" " contact the list owners at %(owneraddr)s." msgstr "" +"O enderezo de correo electrónico que se especificou vetouse\n" +"nesta rolda de distribución. Se xulga que esta restrición está\n" +"errada, por favor, póñase en contacto co propietario da\n" +"rolda no enderezo %(listowner)s." #: Mailman/Cgi/confirm.py:404 msgid "" @@ -1727,6 +1791,7 @@ msgid "Subscription request confirmed" msgstr "Confirmouse a solicitude de subcrición" #: Mailman/Cgi/confirm.py:418 +#, fuzzy msgid "" " You have successfully confirmed your subscription request for\n" " \"%(addr)s\" to the %(listname)s mailing list. A separate\n" @@ -1756,6 +1821,7 @@ msgid "Unsubscription request confirmed" msgstr "Confirmouse a solicitude de baixa" #: Mailman/Cgi/confirm.py:469 +#, fuzzy msgid "" " You have successfully unsubscribed from the %(listname)s " "mailing\n" @@ -1776,6 +1842,7 @@ msgid "Not available" msgstr "Non está dispoñíbel" #: Mailman/Cgi/confirm.py:498 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " unsubscription request from the mailing list %(listname)s. " @@ -1819,11 +1886,16 @@ msgid "You have canceled your change of address request." msgstr "Cancelou a solicitude de cambio de enderezo" #: Mailman/Cgi/confirm.py:555 +#, fuzzy msgid "" "%(newaddr)s is banned from subscribing to the\n" " %(realname)s list. If you think this restriction is erroneous,\n" " please contact the list owners at %(owneraddr)s." msgstr "" +"O enderezo de correo electrónico que se especificou vetouse\n" +"nesta rolda de distribución. Se xulga que esta restrición está\n" +"errada, por favor, póñase en contacto co propietario da\n" +"rolda no enderezo %(listowner)s." #: Mailman/Cgi/confirm.py:560 #, fuzzy @@ -1842,6 +1914,7 @@ msgid "Change of address request confirmed" msgstr "Confirmouse a solicitude do cambio do enderezo" #: Mailman/Cgi/confirm.py:571 +#, fuzzy msgid "" " You have successfully changed your address on the %(listname)s\n" " mailing list from %(oldaddr)s to %(newaddr)s. " @@ -1863,6 +1936,7 @@ msgid "globally" msgstr "globalmente" #: Mailman/Cgi/confirm.py:605 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " change of address request for the mailing list %(listname)s. " @@ -1924,6 +1998,7 @@ msgid "Sender discarded message via web." msgstr "O remitente rexeitou a mensaxe por medio do web." #: Mailman/Cgi/confirm.py:674 +#, fuzzy msgid "" "The held message with the Subject:\n" " header %(subject)s could not be found. The most " @@ -1944,6 +2019,7 @@ msgid "Posted message canceled" msgstr "Cancelouse o envío da mensaxe" #: Mailman/Cgi/confirm.py:685 +#, fuzzy msgid "" " You have successfully canceled the posting of your message with\n" " the Subject: header %(subject)s to the mailing list\n" @@ -1966,6 +2042,7 @@ msgstr "" " tratada polo administrador da rolda." #: Mailman/Cgi/confirm.py:735 +#, fuzzy msgid "" "Your confirmation is required in order to cancel the\n" " posting of your message to the mailing list %(listname)s:\n" @@ -2016,6 +2093,7 @@ msgid "Membership re-enabled." msgstr "Reactivouse a subscrición." #: Mailman/Cgi/confirm.py:798 +#, fuzzy msgid "" " You have successfully re-enabled your membership in the\n" " %(listname)s mailing list. You can now not available" msgstr "Non está dispoñíbel" #: Mailman/Cgi/confirm.py:846 +#, fuzzy msgid "" "Your membership in the %(realname)s mailing list is\n" " currently disabled due to excessive bounces. Your confirmation is\n" @@ -2115,10 +2195,12 @@ msgid "administrative list overview" msgstr "listaxe xeral administrativa" #: Mailman/Cgi/create.py:115 +#, fuzzy msgid "List name must not include \"@\": %(safelistname)s" msgstr "O nome da rolda non pode incluír \"@\": %(safelistname)s" #: Mailman/Cgi/create.py:122 +#, fuzzy msgid "List already exists: %(safelistname)s" msgstr "A rolda xa existe: %(safelistname)s" @@ -2152,18 +2234,22 @@ msgid "You are not authorized to create new mailing lists" msgstr "Non está autorizado para crear novas roldas de distribución" #: Mailman/Cgi/create.py:182 +#, fuzzy msgid "Unknown virtual host: %(safehostname)s" msgstr "Servidor virtual descoñecido: %(safehostname)s" #: Mailman/Cgi/create.py:218 bin/newlist:219 +#, fuzzy msgid "Bad owner email address: %(s)s" msgstr "O enderezo de correo electrónico do propietario é incorrecto: %(s)s" #: Mailman/Cgi/create.py:223 bin/newlist:182 bin/newlist:223 +#, fuzzy msgid "List already exists: %(listname)s" msgstr "A rolda xa existe: %(listname)s" #: Mailman/Cgi/create.py:231 bin/newlist:217 +#, fuzzy msgid "Illegal list name: %(s)s" msgstr "O nome da rolda é ilegal: %(s)s" @@ -2177,6 +2263,7 @@ msgstr "" " da rolda para obter axuda." #: Mailman/Cgi/create.py:273 bin/newlist:265 +#, fuzzy msgid "Your new mailing list: %(listname)s" msgstr "A súa nova rolda de distribución: %(listname)s" @@ -2185,6 +2272,7 @@ msgid "Mailing list creation results" msgstr "Resultados da creación das roldas de distribución" #: Mailman/Cgi/create.py:288 +#, fuzzy msgid "" "You have successfully created the mailing list\n" " %(listname)s and notification has been sent to the list owner\n" @@ -2207,6 +2295,7 @@ msgid "Create another list" msgstr "Crear unha outra rolda" #: Mailman/Cgi/create.py:312 +#, fuzzy msgid "Create a %(hostname)s Mailing List" msgstr "Crear un rolda de distribución de %(hostname)s" @@ -2306,6 +2395,7 @@ msgstr "" "omisión." #: Mailman/Cgi/create.py:432 +#, fuzzy msgid "" "Initial list of supported languages.

                      Note that if you do not\n" " select at least one initial language, the list will use the server\n" @@ -2414,6 +2504,7 @@ msgid "List name is required." msgstr "É preciso inserir o nome da rolda." #: Mailman/Cgi/edithtml.py:154 +#, fuzzy msgid "%(realname)s -- Edit html for %(template_info)s" msgstr "%(realname)s -- Editar o código HTML para %(template_info)s" @@ -2422,10 +2513,12 @@ msgid "Edit HTML : Error" msgstr "Editación do HTML : erro" #: Mailman/Cgi/edithtml.py:161 +#, fuzzy msgid "%(safetemplatename)s: Invalid template" msgstr "%(safetemplatename)s: o modelo non é válido" #: Mailman/Cgi/edithtml.py:166 Mailman/Cgi/edithtml.py:167 +#, fuzzy msgid "%(realname)s -- HTML Page Editing" msgstr "%(realname)s -- Edición do código HTML das páxinas" @@ -2484,10 +2577,12 @@ msgid "HTML successfully updated." msgstr "Actualizouse o código HTML con éxito" #: Mailman/Cgi/listinfo.py:89 +#, fuzzy msgid "%(hostname)s Mailing Lists" msgstr "Roldas de distribución de %(hostname)s" #: Mailman/Cgi/listinfo.py:127 +#, fuzzy msgid "" "

                      There currently are no publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s." @@ -2496,6 +2591,7 @@ msgstr "" " que se anuncie publicamente en %(hostname)s. " #: Mailman/Cgi/listinfo.py:131 +#, fuzzy msgid "" "

                      Below is a listing of all the public mailing lists on\n" " %(hostname)s. Click on a list name to get more information " @@ -2515,6 +2611,7 @@ msgid "right" msgstr "correcto" #: Mailman/Cgi/listinfo.py:140 +#, fuzzy msgid "" " To visit the general information page for an unadvertised list,\n" " open a URL similar to this one, but with a '/' and the %(adj)s\n" @@ -2577,6 +2674,7 @@ msgstr "O enderezo de correo electrónico é incorrecto" #: Mailman/Cgi/options.py:183 Mailman/Cgi/options.py:240 #: Mailman/Cgi/options.py:264 +#, fuzzy msgid "No such member: %(safeuser)s." msgstr "Non existe ese subscritor: %(safeuser)s." @@ -2627,6 +2725,7 @@ msgid "Note: " msgstr "Nota: " #: Mailman/Cgi/options.py:378 +#, fuzzy msgid "List subscriptions for %(safeuser)s on %(hostname)s" msgstr "Subscricións de %(safeuser)s en %(hostname)s" @@ -2654,6 +2753,7 @@ msgid "You are already using that email address" msgstr "Xa está a empregar ese enderezo de correo electrónico" #: Mailman/Cgi/options.py:459 +#, fuzzy msgid "" "The new address you requested %(newaddr)s is already a member of the\n" "%(listname)s mailing list, however you have also requested a global change " @@ -2668,6 +2768,7 @@ msgstr "" "%(safeuser)s. " #: Mailman/Cgi/options.py:468 +#, fuzzy msgid "The new address is already a member: %(newaddr)s" msgstr "O enderezo novo xa está dado de alta: %(newaddr)s" @@ -2676,6 +2777,7 @@ msgid "Addresses may not be blank" msgstr "Os enderezos non poden estar en branco" #: Mailman/Cgi/options.py:488 +#, fuzzy msgid "A confirmation message has been sent to %(newaddr)s. " msgstr "Envióuselles unha mensaxe de confirmación a %(newaddr)s" @@ -2688,15 +2790,21 @@ msgid "Illegal email address provided" msgstr "Proporcionouse un enderezo de correo que non é correcto" #: Mailman/Cgi/options.py:501 +#, fuzzy msgid "%(newaddr)s is already a member of the list." msgstr "%(newaddr)s xa está subscrito á rolda." #: Mailman/Cgi/options.py:504 +#, fuzzy msgid "" "%(newaddr)s is banned from this list. If you\n" " think this restriction is erroneous, please contact\n" " the list owners at %(owneraddr)s." msgstr "" +"O enderezo de correo electrónico que se especificou vetouse\n" +"nesta rolda de distribución. Se xulga que esta restrición está\n" +"errada, póñase en contacto co propietario da\n" +"rolda en %(listowner)s." #: Mailman/Cgi/options.py:515 msgid "Member name successfully changed. " @@ -2765,6 +2873,7 @@ msgstr "" " despois de o moderador tomar unha decisión" #: Mailman/Cgi/options.py:623 +#, fuzzy msgid "" "You have been successfully unsubscribed from the\n" " mailing list %(fqdn_listname)s. If you were receiving digest\n" @@ -2855,6 +2964,7 @@ msgid "day" msgstr "día" #: Mailman/Cgi/options.py:900 +#, fuzzy msgid "%(days)d %(units)s" msgstr "%(days)d %(units)s" @@ -2867,6 +2977,7 @@ msgid "No topics defined" msgstr "Non se definiu ningún tema" #: Mailman/Cgi/options.py:940 +#, fuzzy msgid "" "\n" "You are subscribed to this list with the case-preserved address\n" @@ -2877,6 +2988,7 @@ msgstr "" "as maiúsculas e as minúsculas." #: Mailman/Cgi/options.py:956 +#, fuzzy msgid "%(realname)s list: member options login page" msgstr "Rolda %(realname)s: páxina de entrada das opcións do subscritor" @@ -2885,10 +2997,12 @@ msgid "email address and " msgstr "enderezo de correo electrónico e " #: Mailman/Cgi/options.py:960 +#, fuzzy msgid "%(realname)s list: member options for user %(safeuser)s" msgstr "Rolda %(realname)s: opcións de subscrición de %(safeuser)s" #: Mailman/Cgi/options.py:986 +#, fuzzy msgid "" "In order to change your membership option, you must\n" " first log in by giving your %(extra)smembership password in the section\n" @@ -2974,6 +3088,7 @@ msgid "" msgstr "" #: Mailman/Cgi/options.py:1140 +#, fuzzy msgid "Requested topic is not valid: %(topicname)s" msgstr "O tema solicitado non é válido: %(topicname)s" @@ -3002,6 +3117,7 @@ msgid "Private archive - \"./\" and \"../\" not allowed in URL." msgstr "" #: Mailman/Cgi/private.py:109 +#, fuzzy msgid "Private Archive Error - %(msg)s" msgstr "Erro no arquivo privado - %(msg)s" @@ -3039,6 +3155,7 @@ msgid "Mailing list deletion results" msgstr "Resultados da eliminación das roldas de distribución" #: Mailman/Cgi/rmlist.py:187 +#, fuzzy msgid "" "You have successfully deleted the mailing list\n" " %(listname)s." @@ -3047,6 +3164,7 @@ msgstr "" " %(listname)s." #: Mailman/Cgi/rmlist.py:191 +#, fuzzy msgid "" "There were some problems deleting the mailing list\n" " %(listname)s. Contact your site administrator at " @@ -3059,6 +3177,7 @@ msgstr "" " para máis detalles." #: Mailman/Cgi/rmlist.py:208 +#, fuzzy msgid "Permanently remove mailing list %(realname)s" msgstr "Borrar permanentemente a rolda de distribución %(realname)s" @@ -3130,6 +3249,7 @@ msgid "Invalid options to CGI script" msgstr "Argumentos incorrectos para a escritura CGI" #: Mailman/Cgi/roster.py:118 +#, fuzzy msgid "%(realname)s roster authentication failed." msgstr "" "Produciuse un erro na autenticación á listaxe de subscritores de %(realname)s" @@ -3199,6 +3319,7 @@ msgstr "" "electrónico de confirmación coas instrucións que debe seguir." #: Mailman/Cgi/subscribe.py:262 +#, fuzzy msgid "" "The email address you supplied is banned from this\n" " mailing list. If you think this restriction is erroneous, please\n" @@ -3226,6 +3347,7 @@ msgstr "" "que especificou non é seguro." #: Mailman/Cgi/subscribe.py:278 +#, fuzzy msgid "" "Confirmation from your email address is required, to prevent anyone from\n" "subscribing you without permission. Instructions are being sent to you at\n" @@ -3240,6 +3362,7 @@ msgstr "" "a confirmación da subscrición" #: Mailman/Cgi/subscribe.py:290 +#, fuzzy msgid "" "Your subscription request was deferred because %(x)s. Your request has " "been\n" @@ -3289,6 +3412,7 @@ msgid "This list only supports digest delivery." msgstr "Este rolda só admite a distribución das mensaxes en recompilacións" #: Mailman/Cgi/subscribe.py:344 +#, fuzzy msgid "You have been successfully subscribed to the %(realname)s mailing list." msgstr "" "Subscribiuse satisfactoriamente á rolda de distribución\n" @@ -3333,11 +3457,16 @@ msgid "" msgstr "" #: Mailman/Commands/cmd_confirm.py:69 +#, fuzzy msgid "" "You are currently banned from subscribing to this list. If you think this\n" "restriction is erroneous, please contact the list owners at\n" "%(owneraddr)s." msgstr "" +"O enderezo de correo electrónico que se especificou vetouse\n" +"nesta rolda de distribución. Se xulga que esta restrición está\n" +"errada, póñase en contacto co propietario da\n" +"rolda en %(listowner)s." #: Mailman/Commands/cmd_confirm.py:74 msgid "" @@ -3411,26 +3540,32 @@ msgid "n/a" msgstr "n/d" #: Mailman/Commands/cmd_info.py:44 +#, fuzzy msgid "List name: %(listname)s" msgstr "Nome da rolda: %(listname)s" #: Mailman/Commands/cmd_info.py:45 +#, fuzzy msgid "Description: %(description)s" msgstr "Descrición: %(description)s" #: Mailman/Commands/cmd_info.py:46 +#, fuzzy msgid "Postings to: %(postaddr)s" msgstr "Enviar as mensaxes a: %(postaddr)s" #: Mailman/Commands/cmd_info.py:47 +#, fuzzy msgid "List Helpbot: %(requestaddr)s" msgstr "Robot de axuda da rolda: %(requestaddr)s" #: Mailman/Commands/cmd_info.py:48 +#, fuzzy msgid "List Owners: %(owneraddr)s" msgstr "Propietarios: %(owneraddr)s" #: Mailman/Commands/cmd_info.py:49 +#, fuzzy msgid "More information: %(listurl)s" msgstr "Pode achar máis información en: %(listurl)s" @@ -3454,18 +3589,22 @@ msgstr "" "Mailman do GNU.\n" #: Mailman/Commands/cmd_lists.py:44 +#, fuzzy msgid "Public mailing lists at %(hostname)s:" msgstr "Rodas de distribución públicas xestionados por %(hostname)s" #: Mailman/Commands/cmd_lists.py:66 +#, fuzzy msgid "%(i)3d. List name: %(realname)s" msgstr "%(i)3d. Nome da rolda: %(realname)s" #: Mailman/Commands/cmd_lists.py:67 +#, fuzzy msgid " Description: %(description)s" msgstr " Descrición: %(description)s" #: Mailman/Commands/cmd_lists.py:68 +#, fuzzy msgid " Requests to: %(requestaddr)s" msgstr " Enviar solicitudes a: %(requestaddr)s" @@ -3509,6 +3648,7 @@ msgstr "Contrasinal novo de %(listname)s: %(notifypassword)s" #: Mailman/Commands/cmd_password.py:57 Mailman/Commands/cmd_password.py:72 #: Mailman/Commands/cmd_password.py:95 Mailman/Commands/cmd_password.py:121 #: Mailman/Commands/cmd_set.py:149 Mailman/Commands/cmd_set.py:219 +#, fuzzy msgid "You are not a member of the %(listname)s mailing list" msgstr "Deuse de baixa da rolda de distribución %(listname)s" @@ -3713,6 +3853,7 @@ msgstr "" " do seu contrasinal para esta rolda de distribución.\n" #: Mailman/Commands/cmd_set.py:122 +#, fuzzy msgid "Bad set command: %(subcmd)s" msgstr "A orde set é incorrecta: %(subcmd)s" @@ -3733,6 +3874,7 @@ msgid "on" msgstr "Activar" #: Mailman/Commands/cmd_set.py:154 +#, fuzzy msgid " ack %(onoff)s" msgstr " ack %(onoff)s" @@ -3770,22 +3912,27 @@ msgid "due to bounces" msgstr "debido a devolucións" #: Mailman/Commands/cmd_set.py:186 +#, fuzzy msgid " %(status)s (%(how)s on %(date)s)" msgstr " %(status)s (%(how)s o %(date)s)" #: Mailman/Commands/cmd_set.py:192 +#, fuzzy msgid " myposts %(onoff)s" msgstr " myposts %(onoff)s (as súas propias mensaxes)" #: Mailman/Commands/cmd_set.py:195 +#, fuzzy msgid " hide %(onoff)s" msgstr " hide %(onoff)s (enderezo oculto)" #: Mailman/Commands/cmd_set.py:199 +#, fuzzy msgid " duplicates %(onoff)s" msgstr " duplicadas %(onoff)s (mensaxes duplicadas)" #: Mailman/Commands/cmd_set.py:203 +#, fuzzy msgid " reminders %(onoff)s" msgstr " reminders %(onoff)s (lembranzas mensuais)" @@ -3794,6 +3941,7 @@ msgid "You did not give the correct password" msgstr "O contrasinal que enviou non é a correcto" #: Mailman/Commands/cmd_set.py:236 Mailman/Commands/cmd_set.py:283 +#, fuzzy msgid "Bad argument: %(arg)s" msgstr "Argumentos incorrectos: %(arg)s" @@ -3872,6 +4020,7 @@ msgstr "" " aos lados do enderezo e sen comiñas)\n" #: Mailman/Commands/cmd_subscribe.py:62 +#, fuzzy msgid "Bad digest specifier: %(arg)s" msgstr "A especificación de recompilación non é correcta: %(arg)s" @@ -3880,6 +4029,7 @@ msgid "No valid address found to subscribe" msgstr "Non se achou un enderezo válido para subscribir" #: Mailman/Commands/cmd_subscribe.py:113 +#, fuzzy msgid "" "The email address you supplied is banned from this mailing list.\n" "If you think this restriction is erroneous, please contact the list\n" @@ -3920,6 +4070,7 @@ msgid "This list only supports digest subscriptions!" msgstr "Esta rolda só admite subscricións de recompilacións" #: Mailman/Commands/cmd_subscribe.py:146 +#, fuzzy msgid "" "Your subscription request has been forwarded to the list administrator\n" "at %(listowner)s for review." @@ -3958,6 +4109,7 @@ msgstr "" "sen comiñas)\n" #: Mailman/Commands/cmd_unsubscribe.py:62 +#, fuzzy msgid "%(address)s is not a member of the %(listname)s mailing list" msgstr "O enderezo %(address)s non está subscrito á rolda %(listname)s." @@ -4209,6 +4361,7 @@ msgid "Chinese (Taiwan)" msgstr "" #: Mailman/Deliverer.py:53 +#, fuzzy msgid "" "Note: Since this is a list of mailing lists, administrative\n" "notices like the password reminder will be sent to\n" @@ -4224,14 +4377,17 @@ msgid " (Digest mode)" msgstr " (Modo compilación)" #: Mailman/Deliverer.py:79 +#, fuzzy msgid "Welcome to the \"%(realname)s\" mailing list%(digmode)s" msgstr "Dámoslle a benvida á rolda de distribución %(realname)s%(digmode)s" #: Mailman/Deliverer.py:89 +#, fuzzy msgid "You have been unsubscribed from the %(realname)s mailing list" msgstr "Deuse de baixa da rolda de distribución %(realname)s" #: Mailman/Deliverer.py:116 +#, fuzzy msgid "%(listfullname)s mailing list reminder" msgstr "Lembranza da rolda de distribución %(listfullname)s" @@ -4244,6 +4400,7 @@ msgid "Hostile subscription attempt detected" msgstr "Detectouse un intento de subscrición hostil" #: Mailman/Deliverer.py:169 +#, fuzzy msgid "" "%(address)s was invited to a different mailing\n" "list, but in a deliberate malicious attempt they tried to confirm the\n" @@ -4256,6 +4413,7 @@ msgstr "" "que faga nada." #: Mailman/Deliverer.py:188 +#, fuzzy msgid "" "You invited %(address)s to your list, but in a\n" "deliberate malicious attempt, they tried to confirm the invitation to a\n" @@ -4268,8 +4426,9 @@ msgstr "" "Non fai falla que faga nada pola súa parte." #: Mailman/Deliverer.py:221 +#, fuzzy msgid "%(listname)s mailing list probe message" -msgstr "" +msgstr "Lembranza da rolda de distribución %(listfullname)s" #: Mailman/Errors.py:123 msgid "For some unknown reason" @@ -4483,8 +4642,8 @@ msgid "" " membership.\n" "\n" "

                      You can control both the\n" -" number\n" +" number\n" " of reminders the member will receive and the\n" " Pode controlar tanto o\n" -" número\n" +" número\n" " de lembranzas que recibirá o subscritor como a\n" " \n" @@ -4692,8 +4851,8 @@ msgid "" " No those messages too will get discarded. You may " "want\n" " to set up an\n" -" autoresponse\n" +" autoresponse\n" " message for email to the -owner and -admin address." msgstr "" "Aínda que o detector de mensaxes devoltas do Mailman é bastante bo, é\n" @@ -4722,8 +4881,8 @@ msgstr "" " Non as ditas mensaxes descartaranse. Se o desexar, " "pode configurar\n" " un\n" -" contestador automático\n" +" contestador automático\n" " para as mensaxes dirixidas aos enderezos -owner and -admin." #: Mailman/Gui/Bounce.py:147 @@ -4786,6 +4945,7 @@ msgid "" msgstr "" #: Mailman/Gui/Bounce.py:194 +#, fuzzy msgid "" "Bad value for %(property)s: %(val)s" @@ -4883,8 +5043,8 @@ msgstr "" " o subtipo se quere eliminar todas as partes co tipo de contido\n" " principal, ex. image.\n" "

                      As liñas en branco ignóranse.\n" -"

                      Vexa tamén Vexa tamén pass_mime_types para obter un listaxe dos tipos de contido." #: Mailman/Gui/ContentFilter.py:94 @@ -4902,8 +5062,8 @@ msgid "" "Use this option to remove each message attachment that does\n" " not have a matching content type. Requirements and formats " "are\n" -" exactly like filter_mime_types.\n" "\n" "

                      Note: if you add entries to this list but don't add\n" @@ -4992,6 +5152,7 @@ msgid "" msgstr "" #: Mailman/Gui/ContentFilter.py:171 +#, fuzzy msgid "Bad MIME type ignored: %(spectype)s" msgstr "Ignorouse o tipo MIME incorrecto: %(spectype)s" @@ -5107,6 +5268,7 @@ msgstr "" " sempre e cando non estiver baleira?" #: Mailman/Gui/Digest.py:145 +#, fuzzy msgid "" "The next digest will be sent as volume\n" " %(volume)s, number %(number)s" @@ -5123,16 +5285,19 @@ msgid "There was no digest to send." msgstr "Non había ningunha recompilación que enviar." #: Mailman/Gui/GUIBase.py:173 +#, fuzzy msgid "Invalid value for variable: %(property)s" msgstr "O valor da variable non é válido: %(property)s" #: Mailman/Gui/GUIBase.py:178 +#, fuzzy msgid "Bad email address for option %(property)s: %(error)s" msgstr "" "O enderezo de correo electrónico é incorrecto na opción %(property)s: " "%(error)s" #: Mailman/Gui/GUIBase.py:204 +#, fuzzy msgid "" "The following illegal substitution variables were\n" " found in the %(property)s string:\n" @@ -5148,6 +5313,7 @@ msgstr "" " corrixa o problema." #: Mailman/Gui/GUIBase.py:218 +#, fuzzy msgid "" "Your %(property)s string appeared to\n" " have some correctable problems in its new value.\n" @@ -5251,8 +5417,8 @@ msgid "" "

                      In order to split the list ownership duties into\n" " administrators and moderators, you must\n" " set a separate moderator password,\n" -" and also provide the email\n" +" and also provide the email\n" " addresses of the list moderators. Note that the field you\n" " are changing here specifies the list administrators." msgstr "" @@ -5579,13 +5745,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5610,8 +5776,8 @@ msgstr "Cabeceira explícita Reply-To:" msgid "" "This is the address set in the Reply-To: header\n" " when the reply_goes_to_list\n" +" href=\"?VARHELP=general/" +"reply_goes_to_list\">reply_goes_to_list\n" " option is set to Explicit address.\n" "\n" "

                      There are many reasons not to introduce or override the\n" @@ -5619,13 +5785,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5699,8 +5865,8 @@ msgid "" " member list addresses, but rather to the owner of those member\n" " lists. In that case, the value of this setting is appended to\n" " the member's account name for such notices. `-owner' is the\n" -" typical choice. This setting has no effect when \"umbrella_list" -"\"\n" +" typical choice. This setting has no effect when " +"\"umbrella_list\"\n" " is \"No\"." msgstr "" "Cando \"umbrella_list\" está estabelecida para indicar que\n" @@ -6681,6 +6847,7 @@ msgstr "" "consentimento." #: Mailman/Gui/Privacy.py:104 +#, fuzzy msgid "" "This section allows you to configure subscription and\n" " membership exposure policy. You can also control whether this\n" @@ -6881,8 +7048,8 @@ msgid "" " either individually or as a group. Any\n" " posting from a non-member who is not explicitly accepted,\n" " rejected, or discarded, will have their posting filtered by the\n" -" general\n" +" general\n" " non-member rules.\n" "\n" "

                      In the text boxes below, add one address per line; start the\n" @@ -6908,19 +7075,19 @@ msgstr "" " controlarse se as mensaxes dos subscritores se moderan ou non.\n" "\n" "

                      As mensaxes de non-subscritores pódense\n" -" aceptar,\n" +" aceptar,\n" " reter\n" " para a súa moderación,\n" -" rexeitar (rebotar), ou\n" -" descartar automaticamente,\n" +" rexeitar (rebotar), ou\n" +" descartar automaticamente,\n" " tanto individualmente ou en grupo. Calquera\n" " mensaxe dun non-subscritor que non se acepte,\n" " rexeite ou descarte automaticamente, filtrarase polas\n" -" regras\n" +" regras\n" " xerais para os non-subscritores.\n" "\n" "

                      Na caixa de texto de máis abaixo, engada un enderezo en cada " @@ -6947,6 +7114,7 @@ msgstr "" "subscritores novos?" #: Mailman/Gui/Privacy.py:218 +#, fuzzy msgid "" "Each list member has a moderation flag which says\n" " whether messages from the list member can be posted directly " @@ -6986,8 +7154,8 @@ msgstr "" " as moderar. Vostede sempre pode\n" " estabelecer manualmente a marca de moderación de cada " "subscritor\n" -" se emprega as seccións de administración dos\n" +" se emprega as seccións de administración dos\n" " subscritores." #: Mailman/Gui/Privacy.py:234 @@ -7002,8 +7170,8 @@ msgid "" "If a member posts this many times, within a period of time\n" " the member is automatically moderated. Use 0 to disable. " "See\n" -" member_verbosity_interval for details on the time " "period.\n" "\n" @@ -7456,14 +7624,14 @@ msgid "" msgstr "" "Cando se recibe unha mensaxe dun non-subscritor, compróbase se\n" " o remitinte está na rolda de enderezos explicitamente\n" -" aceptados,\n" -" retidos,\n" -" rexeitados (devoltos), ou\n" -" descartados.\n" +" aceptados,\n" +" retidos,\n" +" rexeitados (devoltos), ou\n" +" descartados.\n" " Se non se achar en ningunha destas roldas, realizarase esta " "acción." @@ -7735,13 +7903,13 @@ msgid "" "

                      The body of the message can also be optionally scanned for\n" " Subject: and Keywords: headers, as\n" " specified by the topics_bodylines_limit\n" +" href=\"?VARHELP=topics/" +"topics_bodylines_limit\">topics_bodylines_limit\n" " configuration variable." msgstr "" "O filtro segundo o tema, clasifica cada mensaxe recibida\n" -" segundo os \n" +" segundo os \n" " filtros de expresións regulares que especifique abaixo. Se " "as cabeceiras\n" " Subject: (asunto) ou Keywords " @@ -7765,8 +7933,8 @@ msgstr "" " cabeceiras Subject: e Keyword:, como " "se especifica na\n" " variábel de configuración\n" -" topics_bodylines_limit." +" topics_bodylines_limit." #: Mailman/Gui/Topics.py:72 msgid "How many body lines should the topic matcher scan?" @@ -8078,6 +8246,7 @@ msgid "%(listinfo_link)s list run by %(owner_link)s" msgstr "A rolda %(listinfo_link)s está administrada por %(owner_link)s" #: Mailman/HTMLFormatter.py:57 +#, fuzzy msgid "%(realname)s administrative interface" msgstr "Interface administrativa de %(realname)s" @@ -8086,6 +8255,7 @@ msgid " (requires authorization)" msgstr " (require a autorización)" #: Mailman/HTMLFormatter.py:61 +#, fuzzy msgid "Overview of all %(hostname)s mailing lists" msgstr "Panorámica de todas as roldas de distribución de %(hostname)s" @@ -8106,6 +8276,7 @@ msgid "; it was disabled by the list administrator" msgstr "; foi desactivado polo administrador do súa rolda" #: Mailman/HTMLFormatter.py:146 +#, fuzzy msgid "" "; it was disabled due to excessive bounces. The\n" " last bounce was received on %(date)s" @@ -8118,6 +8289,7 @@ msgid "; it was disabled for unknown reasons" msgstr "; foi desactivado por algún motivo que se descoñece" #: Mailman/HTMLFormatter.py:151 +#, fuzzy msgid "Note: your list delivery is currently disabled%(reason)s." msgstr "Nota: actualmente ten desactivada a entrega de correo %(reason)s." @@ -8130,6 +8302,7 @@ msgid "the list administrator" msgstr "O administrador da súa rolda" #: Mailman/HTMLFormatter.py:157 +#, fuzzy msgid "" "

                      %(note)s\n" "\n" @@ -8150,6 +8323,7 @@ msgstr "" " ten algunha pregunta ou se precisa axuda." #: Mailman/HTMLFormatter.py:169 +#, fuzzy msgid "" "

                      We have received some recent bounces from your\n" " address. Your current bounce score is %(score)s out of " @@ -8170,6 +8344,7 @@ msgstr "" " os problemas se corrixen axiña." #: Mailman/HTMLFormatter.py:181 +#, fuzzy msgid "" "(Note - you are subscribing to a list of mailing lists, so the %(type)s " "notice will be sent to the admin address for your membership, %(addr)s.)

                      " @@ -8221,6 +8396,7 @@ msgstr "" " coa decisión do administrador por correo electrónico." #: Mailman/HTMLFormatter.py:208 +#, fuzzy msgid "" "This is %(also)sa private list, which means that the\n" " list of members is not available to non-members." @@ -8230,6 +8406,7 @@ msgstr "" " non estean subscritos. " #: Mailman/HTMLFormatter.py:211 +#, fuzzy msgid "" "This is %(also)sa hidden list, which means that the\n" " list of members is available only to the list administrator." @@ -8239,6 +8416,7 @@ msgstr "" " para o administrador." #: Mailman/HTMLFormatter.py:214 +#, fuzzy msgid "" "This is %(also)sa public list, which means that the\n" " list of members list is available to everyone." @@ -8256,6 +8434,7 @@ msgstr "" " recoñecíbeis polos programas de correo lixo)." #: Mailman/HTMLFormatter.py:222 +#, fuzzy msgid "" "

                      (Note that this is an umbrella list, intended to\n" " have only other mailing lists as members. Among other things,\n" @@ -8274,6 +8453,7 @@ msgid "either " msgstr "outro " #: Mailman/HTMLFormatter.py:256 +#, fuzzy msgid "" "To unsubscribe from %(realname)s, get a password reminder,\n" " or change your subscription options %(either)senter your " @@ -8309,6 +8489,7 @@ msgstr "" " enderezo de correo electrónico" #: Mailman/HTMLFormatter.py:277 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " members.)" @@ -8317,6 +8498,7 @@ msgstr "" " os subscritores da rolda.)" #: Mailman/HTMLFormatter.py:281 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " administrator.)" @@ -8378,6 +8560,7 @@ msgid "The current archive" msgstr "O arquivo actual" #: Mailman/Handlers/Acknowledge.py:59 +#, fuzzy msgid "%(realname)s post acknowledgement" msgstr "Confirmación de envío á rolda de distribución %(realname)s" @@ -8390,6 +8573,7 @@ msgid "" msgstr "" #: Mailman/Handlers/CalcRecips.py:79 +#, fuzzy msgid "" "Your urgent message to the %(realname)s mailing list was not authorized for\n" "delivery. The original message as received by Mailman is attached.\n" @@ -8470,6 +8654,7 @@ msgid "Message may contain administrivia" msgstr "A mensaxe pode conter solicitudes administrativas" #: Mailman/Handlers/Hold.py:84 +#, fuzzy msgid "" "Please do *not* post administrative requests to the mailing\n" "list. If you wish to subscribe, visit %(listurl)s or send a message with " @@ -8510,11 +8695,13 @@ msgid "Posting to a moderated newsgroup" msgstr "Envío a un grupo de novas moderado" #: Mailman/Handlers/Hold.py:252 +#, fuzzy msgid "Your message to %(listname)s awaits moderator approval" msgstr "" "A mensaxe enviada a %(listname)s está a agardar a aprobación do moderador" #: Mailman/Handlers/Hold.py:271 +#, fuzzy msgid "%(listname)s post from %(sender)s requires approval" msgstr "O envio a %(listname)s de %(sender)s precisa a aprobación" @@ -8557,6 +8744,7 @@ msgid "After content filtering, the message was empty" msgstr "Despois do filtrado do contido, a mensaxe ficou baleira" #: Mailman/Handlers/MimeDel.py:269 +#, fuzzy msgid "" "The attached message matched the %(listname)s mailing list's content " "filtering\n" @@ -8600,6 +8788,7 @@ msgid "The attached message has been automatically discarded." msgstr "A seguinte mensaxe foi rexeitada automaticamente." #: Mailman/Handlers/Replybot.py:75 +#, fuzzy msgid "Auto-response for your message to the \"%(realname)s\" mailing list" msgstr "Resposta automática á mensaxe dirixida á rolda \"%(realname)s\"" @@ -8619,6 +8808,7 @@ msgid "HTML attachment scrubbed and removed" msgstr "Eliminouse o documento de HTML anexo" #: Mailman/Handlers/Scrubber.py:234 Mailman/Handlers/Scrubber.py:259 +#, fuzzy msgid "" "An HTML attachment was scrubbed...\n" "URL: %(url)s\n" @@ -8704,6 +8894,7 @@ msgid "Message rejected by filter rule match" msgstr "" #: Mailman/Handlers/ToDigest.py:173 +#, fuzzy msgid "%(realname)s Digest, Vol %(volume)d, Issue %(issue)d" msgstr "Resumo de %(realname)s, vol %(volume)d, envío %(issue)d" @@ -8740,6 +8931,7 @@ msgid "End of " msgstr "Fin de " #: Mailman/ListAdmin.py:309 +#, fuzzy msgid "Posting of your message titled \"%(subject)s\"" msgstr "A mensaxe enviada tiña como asunto \"%(subject)s\"" @@ -8752,6 +8944,7 @@ msgid "Forward of moderated message" msgstr "Reenvío da mensaxe moderada" #: Mailman/ListAdmin.py:405 +#, fuzzy msgid "New subscription request to list %(realname)s from %(addr)s" msgstr "Nova solicitude de subscrición á rolda %(realname)s de %(addr)s" @@ -8765,6 +8958,7 @@ msgid "via admin approval" msgstr "Continuar a agardar a aprobación" #: Mailman/ListAdmin.py:466 +#, fuzzy msgid "New unsubscription request from %(realname)s by %(addr)s" msgstr "Nova solicitude de baixa á rolda %(realname)s de %(addr)s" @@ -8777,10 +8971,12 @@ msgid "Original Message" msgstr "Mensaxe orixinal" #: Mailman/ListAdmin.py:526 +#, fuzzy msgid "Request to mailing list %(realname)s rejected" msgstr "A solicitude á rolda de distribución %(realname)s foi rexeitada" #: Mailman/MTA/Manual.py:66 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been created via the through-the-web\n" "interface. In order to complete the activation of this mailing list, the\n" @@ -8805,14 +9001,17 @@ msgstr "" "engadir as liñas seguintes e executar o programa `newaliases:'\n" #: Mailman/MTA/Manual.py:82 +#, fuzzy msgid "## %(listname)s mailing list" msgstr "## rolda de distribución %(listname)s" #: Mailman/MTA/Manual.py:99 +#, fuzzy msgid "Mailing list creation request for list %(listname)s" msgstr "Solicitude de creación da rolda de distribución %(listname)s" #: Mailman/MTA/Manual.py:113 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been removed via the through-the-web\n" "interface. In order to complete the de-activation of this mailing list, " @@ -8829,6 +9028,7 @@ msgstr "" "a orde 'newaliases'\n" #: Mailman/MTA/Manual.py:123 +#, fuzzy msgid "" "\n" "To finish removing your mailing list, you must edit your /etc/aliases (or\n" @@ -8847,14 +9047,17 @@ msgstr "" "## Rolda de distribución %(listname)s" #: Mailman/MTA/Manual.py:142 +#, fuzzy msgid "Mailing list removal request for list %(listname)s" msgstr "Solicitude de eliminación da rolda de distribución %(listname)s" #: Mailman/MTA/Postfix.py:442 +#, fuzzy msgid "checking permissions on %(file)s" msgstr "estase a comprobar os permisos de %(file)s" #: Mailman/MTA/Postfix.py:452 +#, fuzzy msgid "%(file)s permissions must be 0664 (got %(octmode)s)" msgstr "Os permisos de %(file)s deberían ser 0664 (e son %(octmode)s)" @@ -8868,34 +9071,42 @@ msgid "(fixing)" msgstr "(corrixindo)" #: Mailman/MTA/Postfix.py:470 +#, fuzzy msgid "checking ownership of %(dbfile)s" msgstr "estase a comprobar a propiedade de %(dbfile)s" #: Mailman/MTA/Postfix.py:478 +#, fuzzy msgid "%(dbfile)s owned by %(owner)s (must be owned by %(user)s" msgstr "o propietario de%(dbfile)s é %(owner)s (ten que pertencer a %(user)s" #: Mailman/MTA/Postfix.py:491 +#, fuzzy msgid "%(dbfile)s permissions must be 0664 (got %(octmode)s)" msgstr "Os permisos de %(file)s deberían ser 0664 (e son %(octmode)s)" #: Mailman/MailList.py:219 +#, fuzzy msgid "Your confirmation is required to join the %(listname)s mailing list" -msgstr "" +msgstr "Deuse de baixa da rolda de distribución %(listname)s" #: Mailman/MailList.py:230 +#, fuzzy msgid "Your confirmation is required to leave the %(listname)s mailing list" -msgstr "" +msgstr "Deuse de baixa da rolda de distribución %(listname)s" #: Mailman/MailList.py:999 Mailman/MailList.py:1492 +#, fuzzy msgid " from %(remote)s" msgstr " de %(remote)s" #: Mailman/MailList.py:1043 +#, fuzzy msgid "subscriptions to %(realname)s require moderator approval" msgstr "as subscricións a %(realname)s precisan a aprobación do administrador" #: Mailman/MailList.py:1125 bin/add_members:299 +#, fuzzy msgid "%(realname)s subscription notification" msgstr "Notificación de subscrición a %(realname)s" @@ -8904,6 +9115,7 @@ msgid "unsubscriptions require moderator approval" msgstr "as baixas de %(realname)s precisan a aprobación do moderador" #: Mailman/MailList.py:1166 +#, fuzzy msgid "%(realname)s unsubscribe notification" msgstr "Notificación da baixa de %(realname)s" @@ -8923,6 +9135,7 @@ msgid "via web confirmation" msgstr "A cadea de confirmación é incorrecta" #: Mailman/MailList.py:1396 +#, fuzzy msgid "subscriptions to %(name)s require administrator approval" msgstr "" "a subscrición a %(name)s require a aprobación por\n" @@ -8943,6 +9156,7 @@ msgid "Last autoresponse notification for today" msgstr "última notificación da resposta automática de hoxe" #: Mailman/Queue/BounceRunner.py:360 +#, fuzzy msgid "" "The attached message was received as a bounce, but either the bounce format\n" "was not recognized, or no member addresses could be extracted from it. " @@ -9032,6 +9246,7 @@ msgid "Original message suppressed by Mailman site configuration\n" msgstr "" #: Mailman/htmlformat.py:675 +#, fuzzy msgid "Delivered by Mailman
                      version %(version)s" msgstr "Entregado por Mailman
                      versión %(version)s" @@ -9120,6 +9335,7 @@ msgid "Server Local Time" msgstr "Hora local do servidor" #: Mailman/i18n.py:180 +#, fuzzy msgid "" "%(wday)s %(mon)s %(day)2i %(hh)02i:%(mm)02i:%(ss)02i %(tzname)s %(year)04i" msgstr "" @@ -9189,6 +9405,7 @@ msgid "" msgstr "" #: bin/add_members:162 bin/add_members:172 +#, fuzzy msgid "Already a member: %(member)s" msgstr "Xa está subscrito: %(member)s" @@ -9199,12 +9416,14 @@ msgstr "" "en branco" #: bin/add_members:180 +#, fuzzy msgid "Bad/Invalid email address: %(member)s" msgstr "" "O enderezo de correo electrónico quer é incorrecto quer non é válido: " "%(member)s" #: bin/add_members:182 +#, fuzzy msgid "Hostile address (illegal characters): %(member)s" msgstr "Enderezo hostil (os caracteres non son válidos): %(member)s" @@ -9214,14 +9433,17 @@ msgid "Invited: %(member)s" msgstr "Subscribiuse: %(member)s" #: bin/add_members:187 +#, fuzzy msgid "Subscribed: %(member)s" msgstr "Subscribiuse: %(member)s" #: bin/add_members:237 +#, fuzzy msgid "Bad argument to -w/--welcome-msg: %(arg)s" msgstr "Hai un argumento incorrecto a -w/--welcome-msg: %(arg)s" #: bin/add_members:244 +#, fuzzy msgid "Bad argument to -a/--admin-notify: %(arg)s" msgstr "Hai un argumento incorrecto a -a/--admin-notify: %(arg)s" @@ -9238,6 +9460,7 @@ msgstr "" #: bin/add_members:261 bin/config_list:110 bin/export.py:271 bin/find_member:97 #: bin/inject:91 bin/list_admins:90 bin/list_members:252 bin/sync_members:222 #: cron/bumpdigests:86 +#, fuzzy msgid "No such list: %(listname)s" msgstr "Non existe esa rolda: %(listname)s" @@ -9248,6 +9471,7 @@ msgid "Nothing to do." msgstr "Non é necesario facer nada." #: bin/arch:19 +#, fuzzy msgid "" "Rebuild a list's archive.\n" "\n" @@ -9341,6 +9565,7 @@ msgid "listname is required" msgstr "é preciso un nome de rolda" #: bin/arch:143 bin/change_pw:107 bin/config_list:257 +#, fuzzy msgid "" "No such list \"%(listname)s\"\n" "%(e)s" @@ -9349,10 +9574,12 @@ msgstr "" "%(e)s" #: bin/arch:168 +#, fuzzy msgid "Cannot open mbox file %(mbox)s: %(msg)s" msgstr "Non se puido abrir o ficheiro de entrada %(mbox)s: %(msg)s" #: bin/b4b5-archfix:19 +#, fuzzy msgid "" "Fix the MM2.1b4 archives.\n" "\n" @@ -9447,6 +9674,7 @@ msgid "" msgstr "" #: bin/change_pw:145 +#, fuzzy msgid "Bad arguments: %(strargs)s" msgstr "Argumento incorrecto: %(strargs)s" @@ -9455,14 +9683,17 @@ msgid "Empty list passwords are not allowed" msgstr "Os contrasinais non poden estar baleiros" #: bin/change_pw:181 +#, fuzzy msgid "New %(listname)s password: %(notifypassword)s" msgstr "Contrasinal novo de %(listname)s: %(notifypassword)s" #: bin/change_pw:190 +#, fuzzy msgid "Your new %(listname)s list password" msgstr "O seu contrasinal novo da rolda %(listname)s" #: bin/change_pw:191 +#, fuzzy msgid "" "The site administrator at %(hostname)s has changed the password for your\n" "mailing list %(listname)s. It is now\n" @@ -9491,6 +9722,7 @@ msgstr "" " %(adminurl)s\n" #: bin/check_db:19 +#, fuzzy msgid "" "Check a list's config database file for integrity.\n" "\n" @@ -9571,6 +9803,7 @@ msgid "List:" msgstr "Rolda:" #: bin/check_db:148 +#, fuzzy msgid " %(file)s: okay" msgstr " %(file)s: correcto" @@ -9586,46 +9819,56 @@ msgid "" msgstr "" #: bin/check_perms:110 +#, fuzzy msgid " checking gid and mode for %(path)s" msgstr " estase a comprobar o gid e o modo de %(path)s" #: bin/check_perms:122 +#, fuzzy msgid "%(path)s bad group (has: %(groupname)s, expected %(MAILMAN_GROUP)s)" msgstr "" "%(path)s ten un grupo incorrecto (ten: %(groupname)s, pois agardábase que " "tivese %(MAILMAN_GROUP)s)" #: bin/check_perms:151 +#, fuzzy msgid "directory permissions must be %(octperms)s: %(path)s" msgstr "os permisos do directorio teñen que ser %(octperms)s: %(path)s" #: bin/check_perms:160 +#, fuzzy msgid "source perms must be %(octperms)s: %(path)s" msgstr "os permisos teñen que ser %(octperms)s: %(path)s" #: bin/check_perms:171 +#, fuzzy msgid "article db files must be %(octperms)s: %(path)s" msgstr "" "os permisos dos ficheiros da base de datos teñen que ser %(octperms)s: " "%(path)s" #: bin/check_perms:183 +#, fuzzy msgid "checking mode for %(prefix)s" msgstr "estase a comprobar o modo para %(prefix)s" #: bin/check_perms:193 +#, fuzzy msgid "WARNING: directory does not exist: %(d)s" msgstr "Aviso: o directorio non existe: %(d)s" #: bin/check_perms:197 +#, fuzzy msgid "directory must be at least 02775: %(d)s" msgstr "o directorio ten que estar, como mínimo, a 02775: %(d)s" #: bin/check_perms:209 +#, fuzzy msgid "checking perms on %(private)s" msgstr "estase a comprobar os permisos en %(private)s" #: bin/check_perms:214 +#, fuzzy msgid "%(private)s must not be other-readable" msgstr "%(private)s non pode ser lido por terceiras persoas" @@ -9643,6 +9886,7 @@ msgid "mbox file must be at least 0660:" msgstr "O ficheiro de entrada ten que estar como mínimo a 0660" #: bin/check_perms:263 +#, fuzzy msgid "%(dbdir)s \"other\" perms must be 000" msgstr "Os permisos de %(dbdir)s para \"o resto\" teñen que ser 000" @@ -9651,26 +9895,32 @@ msgid "checking cgi-bin permissions" msgstr "estanse a comprobar os permisos dos cgi-bin" #: bin/check_perms:278 +#, fuzzy msgid " checking set-gid for %(path)s" msgstr " estase a comprobar o set-gid de %(path)s" #: bin/check_perms:282 +#, fuzzy msgid "%(path)s must be set-gid" msgstr "%(path)s ten que ser sert-gid" #: bin/check_perms:292 +#, fuzzy msgid "checking set-gid for %(wrapper)s" msgstr "estase a comprobar o set-gid de %(wrapper)s" #: bin/check_perms:296 +#, fuzzy msgid "%(wrapper)s must be set-gid" msgstr "%(wrapper)s ten que ser set-gid" #: bin/check_perms:306 +#, fuzzy msgid "checking permissions on %(pwfile)s" msgstr "estase a comprobar os permisos de %(pwfile)s" #: bin/check_perms:315 +#, fuzzy msgid "%(pwfile)s permissions must be exactly 0640 (got %(octmode)s)" msgstr "" "Os permisos de %(pwfile)s teñen que ser exactamente 06740 (ten %(octmode)s)" @@ -9680,10 +9930,12 @@ msgid "checking permissions on list data" msgstr "estase a comprobar os permisos sobre os datos da rolda" #: bin/check_perms:348 +#, fuzzy msgid " checking permissions on: %(path)s" msgstr " estase a comprobar os permisos de %(path)s" #: bin/check_perms:356 +#, fuzzy msgid "file permissions must be at least 660: %(path)s" msgstr "os permisos do ficheiro teñen que estar como mínimo a 660: %(path)s" @@ -9774,6 +10026,7 @@ msgid "Unix-From line changed: %(lineno)d" msgstr "Cambiouse a liña From ao estilo Unix: %(lineno)d" #: bin/cleanarch:111 +#, fuzzy msgid "Bad status number: %(arg)s" msgstr "O número do estado é incorrecto: %(arg)s" @@ -9924,10 +10177,12 @@ msgid " original address removed:" msgstr " o enderezo orixinal borrouse:" #: bin/clone_member:202 +#, fuzzy msgid "Not a valid email address: %(toaddr)s" msgstr "Non é un enderezo válido: %(toaddr)s" #: bin/clone_member:215 +#, fuzzy msgid "" "Error opening list \"%(listname)s\", skipping.\n" "%(e)s" @@ -10059,22 +10314,27 @@ msgid "legal values are:" msgstr "os valores correctos son:" #: bin/config_list:270 +#, fuzzy msgid "attribute \"%(k)s\" ignored" msgstr "ignorouse o atributo \"%(k)s\"" #: bin/config_list:273 +#, fuzzy msgid "attribute \"%(k)s\" changed" msgstr "cambiouse o atributo \"%(k)s\" " #: bin/config_list:279 +#, fuzzy msgid "Non-standard property restored: %(k)s" msgstr "Restaurouse a propiedade non estándar: %(k)s" #: bin/config_list:288 +#, fuzzy msgid "Invalid value for property: %(k)s" msgstr "O valor da propiedade non é válido: %(k)s" #: bin/config_list:291 +#, fuzzy msgid "Bad email address for option %(k)s: %(v)s" msgstr "O enderezo de correo electrónico é incorrecto na opción %(k)s: %(v)s" @@ -10129,18 +10389,22 @@ msgid "" msgstr "" #: bin/discard:94 +#, fuzzy msgid "Ignoring non-held message: %(f)s" -msgstr "" +msgstr "Estanse a ignorar os cambios do usuario borrado: %(user)s" #: bin/discard:100 +#, fuzzy msgid "Ignoring held msg w/bad id: %(f)s" -msgstr "" +msgstr "Estanse a ignorar os cambios do usuario borrado: %(user)s" #: bin/discard:112 +#, fuzzy msgid "Discarded held msg #%(id)s for list %(listname)s" -msgstr "" +msgstr "Subscribirse á rolda %(listname)s" #: bin/dumpdb:19 +#, fuzzy msgid "" "Dump the contents of any Mailman `database' file.\n" "\n" @@ -10213,6 +10477,7 @@ msgid "No filename given." msgstr "No se proporcionou ningún nome de ficheiro" #: bin/dumpdb:108 +#, fuzzy msgid "Bad arguments: %(pargs)s" msgstr "Argumentos incorrectos: %(pargs)s" @@ -10437,6 +10702,7 @@ msgid "Setting web_page_url to: %(web_page_url)s" msgstr "Estase a asignar o valor %(web_page_url)s a web_page_url" #: bin/fix_url.py:88 +#, fuzzy msgid "Setting host_name to: %(mailhost)s" msgstr "Estase a estabelecer o valor %(mailhost)s a host_name" @@ -10529,6 +10795,7 @@ msgstr "" "De non se especificar, empregarase a entrada estándar.\n" #: bin/inject:84 +#, fuzzy msgid "Bad queue directory: %(qdir)s" msgstr "O directorio que representa a cola é incorrecto: %(qdir)s" @@ -10537,6 +10804,7 @@ msgid "A list name is required" msgstr "É preciso un nome de rolda" #: bin/list_admins:20 +#, fuzzy msgid "" "List all the owners of a mailing list.\n" "\n" @@ -10584,6 +10852,7 @@ msgstr "" "indicar máis dunha rolda na liña de ordes.\n" #: bin/list_admins:97 +#, fuzzy msgid "List: %(listname)s, \tOwners: %(owners)s" msgstr "Rolda: %(listname)s, \tPropietario: %(owners)s" @@ -10714,10 +10983,12 @@ msgid "" msgstr "" #: bin/list_members:198 +#, fuzzy msgid "Bad --nomail option: %(why)s" msgstr "Hai un erro na opción --nomail: %(why)s" #: bin/list_members:209 +#, fuzzy msgid "Bad --digest option: %(kind)s" msgstr "Hai un erro na opción --digest: %(kind)s" @@ -10731,6 +11002,7 @@ msgid "Could not open file for writing:" msgstr "Non foi posíbel abrir o ficheiro en modo escritura." #: bin/list_owners:20 +#, fuzzy msgid "" "List the owners of a mailing list, or all mailing lists.\n" "\n" @@ -10880,6 +11152,7 @@ msgid "" msgstr "" #: bin/mailmanctl:152 +#, fuzzy msgid "PID unreadable in: %(pidfile)s" msgstr "PID ilexíbel en: %(pidfile)s" @@ -10888,6 +11161,7 @@ msgid "Is qrunner even running?" msgstr "Estará o qrunner a se executar?" #: bin/mailmanctl:160 +#, fuzzy msgid "No child with pid: %(pid)s" msgstr "Ningún fillo con pid %(pid)s" @@ -10912,6 +11186,7 @@ msgid "" msgstr "" #: bin/mailmanctl:233 +#, fuzzy msgid "" "The master qrunner lock could not be acquired, because it appears as if " "some\n" @@ -10939,10 +11214,12 @@ msgstr "" "Estase a saír." #: bin/mailmanctl:280 cron/mailpasswds:119 +#, fuzzy msgid "Site list is missing: %(sitelistname)s" msgstr "A rolda do sitio non se atopa: %(sitelistname)s" #: bin/mailmanctl:305 +#, fuzzy msgid "Run this program as root or as the %(name)s user, or use -u." msgstr "" "Debe executar este programa como superusuario, como o usuario %(name)s ou " @@ -10953,6 +11230,7 @@ msgid "No command given." msgstr "Non se deu ningunha orde" #: bin/mailmanctl:339 +#, fuzzy msgid "Bad command: %(command)s" msgstr "Orde incorrecta: %(command)s" @@ -10977,6 +11255,7 @@ msgid "Starting Mailman's master qrunner." msgstr "Estase a executar o qrunner mestre do Mailman" #: bin/mmsitepass:19 +#, fuzzy msgid "" "Set the site password, prompting from the terminal.\n" "\n" @@ -11030,6 +11309,7 @@ msgid "list creator" msgstr "creador da rolda" #: bin/mmsitepass:86 +#, fuzzy msgid "New %(pwdesc)s password: " msgstr "Contrasinal novo de %(pwdesc)s" @@ -11213,6 +11493,7 @@ msgid "" msgstr "" #: bin/newlist:162 +#, fuzzy msgid "Unknown language: %(lang)s" msgstr "Idioma descoñecido: %(lang)s" @@ -11225,6 +11506,7 @@ msgid "Enter the email of the person running the list: " msgstr "Indique o enderezo de correo da persoa que xestionará a rolda: " #: bin/newlist:193 +#, fuzzy msgid "Initial %(listname)s password: " msgstr "Contrasinal inicial de %(listname)s: " @@ -11234,8 +11516,8 @@ msgstr "O contrasinal da rolda non pode estar baleiro" #: bin/newlist:220 msgid "" -" - owner addresses need to be fully-qualified names like \"owner@example.com" -"\", not just \"owner\"." +" - owner addresses need to be fully-qualified names like \"owner@example." +"com\", not just \"owner\"." msgstr "" #: bin/newlist:244 @@ -11310,6 +11592,7 @@ msgid "" msgstr "" #: bin/qrunner:179 +#, fuzzy msgid "%(name)s runs the %(runnername)s qrunner" msgstr "%(name)s executa o qrunner %(runnername)s" @@ -11399,18 +11682,22 @@ msgid "" msgstr "" #: bin/remove_members:156 +#, fuzzy msgid "Could not open file for reading: %(filename)s." msgstr "Non foi posíbel abrir o ficheiro %(filename)s para a súa lectura." #: bin/remove_members:163 +#, fuzzy msgid "Error opening list %(listname)s... skipping." msgstr "Produciuse un erro ao abrir a rolda \"%(listname)s\", e omitirase." #: bin/remove_members:173 +#, fuzzy msgid "No such member: %(addr)s" msgstr "Non existe ese subscritor: %(addr)s." #: bin/remove_members:178 +#, fuzzy msgid "User `%(addr)s' removed from list: %(listname)s." msgstr "O subscritor `%(addr)s' deuse de baixa da rolda %(listname)s." @@ -11435,8 +11722,9 @@ msgid "" msgstr "" #: bin/reset_pw.py:77 +#, fuzzy msgid "Changing passwords for list: %(listname)s" -msgstr "" +msgstr "Solicitude de eliminación da rolda de distribución %(listname)s" #: bin/reset_pw.py:83 msgid "New password for member %(member)40s: %(randompw)s" @@ -11484,18 +11772,22 @@ msgstr "" " amosa esta mensaxe de axuda e sae\n" #: bin/rmlist:73 bin/rmlist:76 +#, fuzzy msgid "Removing %(msg)s" msgstr "Estase a eliminar %(msg)s" #: bin/rmlist:81 +#, fuzzy msgid "%(listname)s %(msg)s not found as %(filename)s" msgstr "%(msg)s de %(listname)s non se atopou como %(filename)s" #: bin/rmlist:105 +#, fuzzy msgid "No such list (or list already deleted): %(listname)s" msgstr "Non existe esa rolda (ou xa se borrou): %(listname)s" #: bin/rmlist:108 +#, fuzzy msgid "No such list: %(listname)s. Removing its residual archives." msgstr "" "Non existe esa rolda: %(listname)s. Estanse a eliminar os seus ficheiros " @@ -11545,6 +11837,7 @@ msgid "" msgstr "" #: bin/sync_members:19 +#, fuzzy msgid "" "Synchronize a mailing list's membership with a flat file.\n" "\n" @@ -11680,6 +11973,7 @@ msgstr "" " Necesario. Indica a rolda que se sincronizará.\n" #: bin/sync_members:115 +#, fuzzy msgid "Bad choice: %(yesno)s" msgstr "Mala elección: %(yesno)s" @@ -11696,6 +11990,7 @@ msgid "No argument to -f given" msgstr "Non se proporcionou ningún argumento a -f" #: bin/sync_members:172 +#, fuzzy msgid "Illegal option: %(opt)s" msgstr "A opción é ilegal: %(opt)s" @@ -11708,6 +12003,7 @@ msgid "Must have a listname and a filename" msgstr "Ten que haber un nome de rolda e de ficheiro" #: bin/sync_members:191 +#, fuzzy msgid "Cannot read address file: %(filename)s: %(msg)s" msgstr "Non foi posíbel ler o ficheiro de enderezos: %(filename)s: %(msg)s" @@ -11724,14 +12020,17 @@ msgid "You must fix the preceding invalid addresses first." msgstr "Primeiro tense que corrixir o enderezo que non é válido." #: bin/sync_members:264 +#, fuzzy msgid "Added : %(s)s" msgstr "Engadiuse: %(s)s" #: bin/sync_members:289 +#, fuzzy msgid "Removed: %(s)s" msgstr "Eliminouse: %(s)s" #: bin/transcheck:19 +#, fuzzy msgid "" "\n" "Check a given Mailman translation, making sure that variables and\n" @@ -11840,6 +12139,7 @@ msgstr "" "qfiles/shunt.\n" #: bin/unshunt:85 +#, fuzzy msgid "" "Cannot unshunt message %(filebase)s, skipping:\n" "%(e)s" @@ -11848,6 +12148,7 @@ msgstr "" "estase a omitir %(e)s" #: bin/update:20 +#, fuzzy msgid "" "Perform all necessary upgrades.\n" "\n" @@ -11886,14 +12187,17 @@ msgstr "" "versión anterior. Coñece as versións anteriores até a 1.0b4 (?).\n" #: bin/update:107 +#, fuzzy msgid "Fixing language templates: %(listname)s" msgstr "Estase a corrixir os modelos das linguaxes: %(listname)s" #: bin/update:196 bin/update:711 +#, fuzzy msgid "WARNING: could not acquire lock for list: %(listname)s" msgstr "Aviso: non se puido adquirir o bloqueo da rolda: %(listname)s" #: bin/update:215 +#, fuzzy msgid "Resetting %(n)s BYBOUNCEs disabled addrs with no bounce info" msgstr "" "Estase a restabelecer %(n)s BYBOUNCE enderezos inhabilitadas sen información " @@ -11913,6 +12217,7 @@ msgstr "" "continuarase." #: bin/update:255 +#, fuzzy msgid "" "\n" "%(listname)s has both public and private mbox archives. Since this list\n" @@ -11966,6 +12271,7 @@ msgid "- updating old private mbox file" msgstr "- estase a actualizar o antigo ficheiro de entrada privado" #: bin/update:295 +#, fuzzy msgid "" " unknown file in the way, moving\n" " %(o_pri_mbox_file)s\n" @@ -11982,6 +12288,7 @@ msgid "- updating old public mbox file" msgstr "- estase a actualizar o antigo ficheiro de entrada público" #: bin/update:317 +#, fuzzy msgid "" " unknown file in the way, moving\n" " %(o_pub_mbox_file)s\n" @@ -12010,19 +12317,23 @@ msgid "- %(o_tmpl)s doesn't exist, leaving untouched" msgstr "" #: bin/update:396 +#, fuzzy msgid "removing directory %(src)s and everything underneath" msgstr "" "estase a eliminar o directorio %(src)s e todo o que hai no seu interior" #: bin/update:399 +#, fuzzy msgid "removing %(src)s" msgstr "estase a eliminar %(src)s" #: bin/update:403 +#, fuzzy msgid "Warning: couldn't remove %(src)s -- %(rest)s" msgstr "Aviso: non se puido eliminar %(src)s -- %(rest)s" #: bin/update:408 +#, fuzzy msgid "couldn't remove old file %(pyc)s -- %(rest)s" msgstr "non se puido eliminar o ficheiro antigo %(pyc)s -- %(rest)s" @@ -12080,6 +12391,7 @@ msgid "done" msgstr "completouse" #: bin/update:691 +#, fuzzy msgid "Updating mailing list: %(listname)s" msgstr "Estase a actualizar a rolda de distribución %(listname)s" @@ -12143,6 +12455,7 @@ msgid "No updates are necessary." msgstr "Non son necesarias as actualizacións." #: bin/update:796 +#, fuzzy msgid "" "Downgrade detected, from version %(hexlversion)s to version %(hextversion)s\n" "This is probably not safe.\n" @@ -12154,10 +12467,12 @@ msgstr "" "Estase a saír." #: bin/update:801 +#, fuzzy msgid "Upgrading from version %(hexlversion)s to %(hextversion)s" msgstr "Estase a actualizar da versión %(hexlversion)s á %(hextversion)s" #: bin/update:810 +#, fuzzy msgid "" "\n" "ERROR:\n" @@ -12330,6 +12645,7 @@ msgstr "" " " #: bin/withlist:175 +#, fuzzy msgid "Unlocking (but not saving) list: %(listname)s" msgstr "Estase a desbloquear (mais sen gardar) a rolda: %(listname)s" @@ -12338,6 +12654,7 @@ msgid "Finalizing" msgstr "Estase a rematar" #: bin/withlist:188 +#, fuzzy msgid "Loading list %(listname)s" msgstr "Estase a cargar a rolda %(listname)s" @@ -12350,6 +12667,7 @@ msgid "(unlocked)" msgstr "(desbloqueado)" #: bin/withlist:197 +#, fuzzy msgid "Unknown list: %(listname)s" msgstr "Rolda descoñecida: %(listname)s" @@ -12362,18 +12680,22 @@ msgid "--all requires --run" msgstr "--all require --run" #: bin/withlist:266 +#, fuzzy msgid "Importing %(module)s..." msgstr "Estase a importar %(module)s..." #: bin/withlist:270 +#, fuzzy msgid "Running %(module)s.%(callable)s()..." msgstr "Estase a executar %(module)s.%(callable)s()..." #: bin/withlist:291 +#, fuzzy msgid "The variable `m' is the %(listname)s MailList instance" msgstr "A variable 'm' é a instancia da rolda de distribución %(listname)s" #: cron/bumpdigests:19 +#, fuzzy msgid "" "Increment the digest volume number and reset the digest number to one.\n" "\n" @@ -12421,12 +12743,14 @@ msgid "" msgstr "" #: cron/checkdbs:121 +#, fuzzy msgid "%(count)d %(realname)s moderator request(s) waiting" msgstr "%(count)d solicitudes de %(realname)s a agardaren o moderador" #: cron/checkdbs:124 +#, fuzzy msgid "%(realname)s moderator request check result" -msgstr "" +msgstr "%(count)d solicitudes de %(realname)s a agardaren o moderador" #: cron/checkdbs:144 msgid "Pending subscriptions:" @@ -12446,6 +12770,7 @@ msgstr "" "Envíos pendentes:" #: cron/checkdbs:169 +#, fuzzy msgid "" "From: %(sender)s on %(date)s\n" "Subject: %(subject)s\n" @@ -12558,6 +12883,7 @@ msgstr "" "\n" #: cron/mailpasswds:19 +#, fuzzy msgid "" "Send password reminders for all lists to all users.\n" "\n" @@ -12613,10 +12939,12 @@ msgid "Password // URL" msgstr "Contrasinal // URL" #: cron/mailpasswds:222 +#, fuzzy msgid "%(host)s mailing list memberships reminder" msgstr "Lembranzas de rolda de distribución de %(host)s" #: cron/nightly_gzip:19 +#, fuzzy msgid "" "Re-generate the Pipermail gzip'd archive flat files.\n" "\n" @@ -12713,8 +13041,8 @@ msgstr "" #~ " applied" #~ msgstr "" #~ "Texto que se incluirá nas\n" -#~ " notificacións de rexeitamento que se envían\n" #~ " como resposta a mensaxes de membros moderados da rolda." diff --git a/messages/he/LC_MESSAGES/mailman.po b/messages/he/LC_MESSAGES/mailman.po index dc360f45..01360f91 100755 --- a/messages/he/LC_MESSAGES/mailman.po +++ b/messages/he/LC_MESSAGES/mailman.po @@ -67,10 +67,12 @@ msgid "

                      Currently, there are no archives.

                      " msgstr "

                      .כרגע ×ין ×רכיוני×

                      " #: Mailman/Archiver/HyperArch.py:821 +#, fuzzy msgid "Gzip'd Text%(sz)s" msgstr "טקסט הדחוס בעזרת gzip %(sz)s" #: Mailman/Archiver/HyperArch.py:826 +#, fuzzy msgid "Text%(sz)s" msgstr "טקסט%(sz)s" @@ -143,18 +145,22 @@ msgid "Third" msgstr "השלישי" #: Mailman/Archiver/HyperArch.py:938 +#, fuzzy msgid "%(ord)s quarter %(year)i" msgstr "רבעון %(ord)s %(year)i" #: Mailman/Archiver/HyperArch.py:945 +#, fuzzy msgid "%(month)s %(year)i" msgstr "%(month)s %(year)i" #: Mailman/Archiver/HyperArch.py:950 +#, fuzzy msgid "The Week Of Monday %(day)i %(month)s %(year)i" msgstr "השבוע שמתחיל ×‘×™×•× ×©× ×™ %(day)i %(month)s %(year)i" #: Mailman/Archiver/HyperArch.py:954 +#, fuzzy msgid "%(day)i %(month)s %(year)i" msgstr "%(day)i %(month)s %(year)i" @@ -163,10 +169,12 @@ msgid "Computing threaded index\n" msgstr "מחשב ×ינדקס לפי שיחה\n" #: Mailman/Archiver/HyperArch.py:1319 +#, fuzzy msgid "Updating HTML for article %(seq)s" msgstr "מעדכן ×ת ×”-HTML עבור פריט %(seq)s" #: Mailman/Archiver/HyperArch.py:1326 +#, fuzzy msgid "article file %(filename)s is missing!" msgstr "חסר קובץ פריט %(filename)s!" @@ -183,6 +191,7 @@ msgid "Pickling archive state into " msgstr "משמר מצב ×”×רכיון ×ל תוך " #: Mailman/Archiver/pipermail.py:453 +#, fuzzy msgid "Updating index files for archive [%(archive)s]" msgstr "מעדכן קבצי ×ינדקס עבור ×”×רכיון [%(archive)s]" @@ -191,6 +200,7 @@ msgid " Thread" msgstr " שיחה" #: Mailman/Archiver/pipermail.py:597 +#, fuzzy msgid "#%(counter)05d %(msgid)s" msgstr "#%(counter)05d %(msgid)s" @@ -228,6 +238,7 @@ msgid "disabled address" msgstr "מבוטל" #: Mailman/Bouncer.py:304 +#, fuzzy msgid " The last bounce received from you was dated %(date)s" msgstr " ההחזר ×”×חרון שהתקבל ממך ×”×™×” בת×ריך %(date)s" @@ -255,6 +266,7 @@ msgstr "מנהל" #: Mailman/Cgi/options.py:101 Mailman/Cgi/private.py:108 #: Mailman/Cgi/rmlist.py:75 Mailman/Cgi/roster.py:59 #: Mailman/Cgi/subscribe.py:67 +#, fuzzy msgid "No such list %(safelistname)s" msgstr "×ין רשימה ×‘×©× %(safelistname)s" @@ -325,6 +337,7 @@ msgstr "" " מבוטל. ×”× ×œ× ×™×§×‘×œ×• דו×ר עד תיקון בעיה זו.%(rm)r" #: Mailman/Cgi/admin.py:268 +#, fuzzy msgid "%(hostname)s mailing lists - Admin Links" msgstr "רשימות הדיוור של %(hostname)s - ×§×™×©×•×¨×™× ×ž× ×”×œ×ª×™×™×" @@ -337,12 +350,14 @@ msgid "Mailman" msgstr "דוור" #: Mailman/Cgi/admin.py:310 +#, fuzzy msgid "" "

                      There currently are no publicly-advertised %(mailmanlink)s\n" " mailing lists on %(hostname)s." msgstr "

                      כרגע ×ין רשימות תפוצה ב- %(mailmanlink)s -×œ× ×ž×•×¡×ª×¨×™× ×‘%(hostname)s." #: Mailman/Cgi/admin.py:316 +#, fuzzy msgid "" "

                      Below is the collection of publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s. Click on a list\n" @@ -357,6 +372,7 @@ msgid "right " msgstr "ימין " #: Mailman/Cgi/admin.py:325 +#, fuzzy msgid "" "To visit the administrators configuration page for an\n" " unadvertised list, open a URL similar to this one, but with a '/' " @@ -399,6 +415,7 @@ msgid "No valid variable name found." msgstr "×œ× × ×ž×¦× ×©× ×ž×©×ª× ×” חוקי." #: Mailman/Cgi/admin.py:395 +#, fuzzy msgid "" "%(realname)s Mailing list Configuration Help\n" "
                      %(varname)s Option" @@ -407,6 +424,7 @@ msgstr "" "
                      הגדרת %(varname)s" #: Mailman/Cgi/admin.py:402 +#, fuzzy msgid "Mailman %(varname)s List Option Help" msgstr "עזרת ×פשריות של רשימת דוור %(varname)s" @@ -426,14 +444,17 @@ msgstr "" " " #: Mailman/Cgi/admin.py:431 +#, fuzzy msgid "return to the %(categoryname)s options page." msgstr "לחזור ×ל עמוד ×”×פשריות של %(categoryname)s" #: Mailman/Cgi/admin.py:446 +#, fuzzy msgid "%(realname)s Administration (%(label)s)" msgstr "ניהול %(realname)s: %(label)s" #: Mailman/Cgi/admin.py:447 +#, fuzzy msgid "%(realname)s mailing list administration
                      %(label)s Section" msgstr "ניהול רשימת הדיוור %(realname)s
                      קטע %(label)s" @@ -515,6 +536,7 @@ msgid "Value" msgstr "ערך" #: Mailman/Cgi/admin.py:668 +#, fuzzy msgid "" "Badly formed options entry:\n" " %(record)s" @@ -615,10 +637,12 @@ msgid "Move rule down" msgstr "הזז חוק למטה" #: Mailman/Cgi/admin.py:870 +#, fuzzy msgid "
                      (Edit %(varname)s)" msgstr "
                      (ערוך %(varname)s)" #: Mailman/Cgi/admin.py:872 +#, fuzzy msgid "
                      (Details for %(varname)s)" msgstr "
                      (×¤×¨×˜×™× ×¢×‘×•×¨ %(varname)s)" @@ -659,6 +683,7 @@ msgid "(help)" msgstr "(עזרה)" #: Mailman/Cgi/admin.py:930 +#, fuzzy msgid "Find member %(link)s:" msgstr "×ž×¦× ×ž× ×•×™ %(link)s:" @@ -671,10 +696,12 @@ msgid "Bad regular expression: " msgstr "ביטוי רגולרי ×œ× ×—×•×§×™: " #: Mailman/Cgi/admin.py:1013 +#, fuzzy msgid "%(allcnt)s members total, %(membercnt)s shown" msgstr "סך כל המנוי×: %(allcnt)s; %(membercnt)s ×ž× ×•×™× ×ž×•×¦×’×™×" #: Mailman/Cgi/admin.py:1016 +#, fuzzy msgid "%(allcnt)s members total" msgstr "סך כל המנוי×: %(allcnt)s" @@ -855,6 +882,7 @@ msgid "" msgstr "

                      כדי להציג ×ž× ×•×™× × ×•×¡×¤×™×, לחץ על הקישור המת××™× ×œ×ž×˜×”:/em>" #: Mailman/Cgi/admin.py:1221 +#, fuzzy msgid "from %(start)s to %(end)s" msgstr "מ-%(start)s ל-%(end)s" @@ -991,6 +1019,7 @@ msgid "Change list ownership passwords" msgstr "שנה ×ת סיסמ×ות של בעלי הרשימה" #: Mailman/Cgi/admin.py:1364 +#, fuzzy msgid "" "The list administrators are the people who have ultimate control " "over\n" @@ -1101,6 +1130,7 @@ msgstr "כתובת ×ויינת (×ª×•×•×™× ×œ× ×—×•×§×™×™×)" #: Mailman/Cgi/admin.py:1529 Mailman/Cgi/admin.py:1703 bin/add_members:175 #: bin/clone_member:136 bin/sync_members:268 +#, fuzzy msgid "Banned address (matched %(pattern)s)" msgstr "כתובת חסומה (הת××™× ×œ-%(pattern)s)" @@ -1203,6 +1233,7 @@ msgid "Not subscribed" msgstr "×ינו מנוי" #: Mailman/Cgi/admin.py:1773 +#, fuzzy msgid "Ignoring changes to deleted member: %(user)s" msgstr "×ž×ª×¢×œ× ×ž×©×™× ×•×™×™× ×œ×ž× ×•×™ שנמחק: %(user)s" @@ -1215,10 +1246,12 @@ msgid "Error Unsubscribing:" msgstr "שגי××” בביטול המנוי:" #: Mailman/Cgi/admindb.py:235 Mailman/Cgi/admindb.py:248 +#, fuzzy msgid "%(realname)s Administrative Database" msgstr "בסיס ×”× ×ª×•× ×™× ×”×ž× ×”×œ×ª×™ %(realname)s" #: Mailman/Cgi/admindb.py:238 +#, fuzzy msgid "%(realname)s Administrative Database Results" msgstr "תוצ×ות בסיס ×”× ×ª×•× ×™× ×”×ž× ×”×œ×ª×™ %(realname)s " @@ -1247,6 +1280,7 @@ msgid "Discard all messages marked Defer" msgstr "מחק ×ת כל ×”×ž×¡×¨×™× ×”×ž×¡×•×ž× ×™× ×”×©×”×”" #: Mailman/Cgi/admindb.py:298 +#, fuzzy msgid "all of %(esender)s's held messages." msgstr "כל ×”×ž×¡×¨×™× ×”×ž×ž×ª×™× ×™× ×©×œ %(esender)s." @@ -1267,6 +1301,7 @@ msgid "list of available mailing lists." msgstr "רשימת רשימות הדיוור הזמינות." #: Mailman/Cgi/admindb.py:362 +#, fuzzy msgid "You must specify a list name. Here is the %(link)s" msgstr "עליך לציין ×©× ×¨×©×™×ž×ª דיוור. ×”× ×” ×”- %(link)s" @@ -1349,6 +1384,7 @@ msgid "The sender is now a member of this list" msgstr "מעכשיו השולח הנו מנוי ברשימה זו" #: Mailman/Cgi/admindb.py:568 +#, fuzzy msgid "Add %(esender)s to one of these sender filters:" msgstr "הוסף ×ת %(esender)s ×ל ×חד ממסנני השולח ×”×לה:" @@ -1369,6 +1405,7 @@ msgid "Rejects" msgstr "דוחה" #: Mailman/Cgi/admindb.py:584 +#, fuzzy msgid "" "Ban %(esender)s from ever subscribing to this\n" " mailing list" @@ -1383,6 +1420,7 @@ msgstr "" " בודד, ×ו ש×פשר ×’× " #: Mailman/Cgi/admindb.py:591 +#, fuzzy msgid "view all messages from %(esender)s" msgstr "לצפות בכל ×”×ž×¡×¨×™× ×ž- %(esender)s" @@ -1461,6 +1499,7 @@ msgid " is already a member" msgstr " הנו כבר מנוי" #: Mailman/Cgi/admindb.py:973 +#, fuzzy msgid "%(addr)s is banned (matched: %(patt)s)" msgstr "%(addr)s ×—×¡×•× (הת××™× ×œ-%(patt)s)" @@ -1509,6 +1548,7 @@ msgstr "" " על כן, הבקשה בוטלה." #: Mailman/Cgi/confirm.py:178 +#, fuzzy msgid "System error, bad content: %(content)s" msgstr "שגי×ת מערכת, תכולה פגומה: %(content)s" @@ -1545,6 +1585,7 @@ msgid "Confirm subscription request" msgstr "×ישור בקשת המנוי" #: Mailman/Cgi/confirm.py:259 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " subscription request to the mailing list %(listname)s. Your\n" @@ -1575,6 +1616,7 @@ msgstr "" "לרשימה זו." #: Mailman/Cgi/confirm.py:275 +#, fuzzy msgid "" "Your confirmation is required in order to continue with\n" " the subscription request to the mailing list %(listname)s.\n" @@ -1623,6 +1665,7 @@ msgid "Preferred language:" msgstr "שפה מועדפת:" #: Mailman/Cgi/confirm.py:317 +#, fuzzy msgid "Subscribe to list %(listname)s" msgstr "×”×¨×©× ×œ×¨×©×™×ž×” %(listname)s" @@ -1639,6 +1682,7 @@ msgid "Awaiting moderator approval" msgstr "ממתין ל×ישור מפקח" #: Mailman/Cgi/confirm.py:386 +#, fuzzy msgid "" " You have successfully confirmed your subscription request to " "the\n" @@ -1668,6 +1712,7 @@ msgid "You are already a member of this mailing list!" msgstr "×תה כבר מנוי לרשימת דיוור זו!" #: Mailman/Cgi/confirm.py:400 +#, fuzzy msgid "" "You are currently banned from subscribing to\n" " this list. If you think this restriction is erroneous, please\n" @@ -1690,6 +1735,7 @@ msgid "Subscription request confirmed" msgstr "בקשת המנוי ×ושרה" #: Mailman/Cgi/confirm.py:418 +#, fuzzy msgid "" " You have successfully confirmed your subscription request for\n" " \"%(addr)s\" to the %(listname)s mailing list. A separate\n" @@ -1718,6 +1764,7 @@ msgid "Unsubscription request confirmed" msgstr "בקשת ביטול המנוי ×ושרה" #: Mailman/Cgi/confirm.py:469 +#, fuzzy msgid "" " You have successfully unsubscribed from the %(listname)s " "mailing\n" @@ -1738,6 +1785,7 @@ msgid "Not available" msgstr "×œ× ×–×ž×™×Ÿ" #: Mailman/Cgi/confirm.py:498 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " unsubscription request from the mailing list %(listname)s. " @@ -1779,6 +1827,7 @@ msgid "You have canceled your change of address request." msgstr "ביטלת ×ת בקשת שינוי הכתובת שלך." #: Mailman/Cgi/confirm.py:555 +#, fuzzy msgid "" "%(newaddr)s is banned from subscribing to the\n" " %(realname)s list. If you think this restriction is erroneous,\n" @@ -1804,6 +1853,7 @@ msgid "Change of address request confirmed" msgstr "בקשת שינוי הכתובת ×ושרה" #: Mailman/Cgi/confirm.py:571 +#, fuzzy msgid "" " You have successfully changed your address on the %(listname)s\n" " mailing list from %(oldaddr)s to %(newaddr)s. " @@ -1824,6 +1874,7 @@ msgid "globally" msgstr "גלובלית" #: Mailman/Cgi/confirm.py:605 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " change of address request for the mailing list %(listname)s. " @@ -1883,6 +1934,7 @@ msgid "Sender discarded message via web." msgstr "השולח מחק ×ת המסר דרך ×”×תר." #: Mailman/Cgi/confirm.py:674 +#, fuzzy msgid "" "The held message with the Subject:\n" " header %(subject)s could not be found. The most " @@ -1902,6 +1954,7 @@ msgid "Posted message canceled" msgstr "המסר שנשלח נמחק" #: Mailman/Cgi/confirm.py:685 +#, fuzzy msgid "" " You have successfully canceled the posting of your message with\n" " the Subject: header %(subject)s to the mailing list\n" @@ -1924,6 +1977,7 @@ msgstr "" " מנהל הרשימה." #: Mailman/Cgi/confirm.py:735 +#, fuzzy msgid "" "Your confirmation is required in order to cancel the\n" " posting of your message to the mailing list %(listname)s:\n" @@ -1970,6 +2024,7 @@ msgid "Membership re-enabled." msgstr "המנוי חודש." #: Mailman/Cgi/confirm.py:798 +#, fuzzy msgid "" " You have successfully re-enabled your membership in the\n" " %(listname)s mailing list. You can now %(listname)s and notification has been sent to the list owner\n" @@ -2159,6 +2223,7 @@ msgid "Create another list" msgstr "ליצור רשימה נוספת" #: Mailman/Cgi/create.py:312 +#, fuzzy msgid "Create a %(hostname)s Mailing List" msgstr "צור רשימת דיוור %(hostname)s" @@ -2253,6 +2318,7 @@ msgstr "" " ×ž×¡×¨×™× ×ž×ž× ×•×™× ×—×“×©×™× ×œ×ישור מפקח כברירת מחדל." #: Mailman/Cgi/create.py:432 +#, fuzzy msgid "" "Initial list of supported languages.

                      Note that if you do not\n" " select at least one initial language, the list will use the server\n" @@ -2358,6 +2424,7 @@ msgid "List name is required." msgstr "×©× ×¨×©×™×ž×” חובה" #: Mailman/Cgi/edithtml.py:154 +#, fuzzy msgid "%(realname)s -- Edit html for %(template_info)s" msgstr "%(realname)s -- עורך html עבור %(template_info)s" @@ -2366,10 +2433,12 @@ msgid "Edit HTML : Error" msgstr "עורך HTML: שגי××”" #: Mailman/Cgi/edithtml.py:161 +#, fuzzy msgid "%(safetemplatename)s: Invalid template" msgstr "%(safetemplatename)s: תבנית ×œ× ×—×•×§×™×ª" #: Mailman/Cgi/edithtml.py:166 Mailman/Cgi/edithtml.py:167 +#, fuzzy msgid "%(realname)s -- HTML Page Editing" msgstr "%(realname)s -- עורך עמוד HTML" @@ -2428,10 +2497,12 @@ msgid "HTML successfully updated." msgstr "HTML עודכן בהצלחה" #: Mailman/Cgi/listinfo.py:89 +#, fuzzy msgid "%(hostname)s Mailing Lists" msgstr "רשימות הדיוור של %(hostname)s" #: Mailman/Cgi/listinfo.py:127 +#, fuzzy msgid "" "

                      There currently are no publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s." @@ -2440,6 +2511,7 @@ msgstr "" " על %(hostname)s ש×ינן מוסתרות." #: Mailman/Cgi/listinfo.py:131 +#, fuzzy msgid "" "

                      Below is a listing of all the public mailing lists on\n" " %(hostname)s. Click on a list name to get more information " @@ -2458,6 +2530,7 @@ msgid "right" msgstr "ימין" #: Mailman/Cgi/listinfo.py:140 +#, fuzzy msgid "" " To visit the general information page for an unadvertised list,\n" " open a URL similar to this one, but with a '/' and the %(adj)s\n" @@ -2519,6 +2592,7 @@ msgstr "כתובת דו×\"ל ×œ× ×—×•×§×™×ª" #: Mailman/Cgi/options.py:183 Mailman/Cgi/options.py:240 #: Mailman/Cgi/options.py:264 +#, fuzzy msgid "No such member: %(safeuser)s." msgstr "×ין ×›×–×” מנוי: %(safeuser)s." @@ -2571,6 +2645,7 @@ msgid "Note: " msgstr "הערה:" #: Mailman/Cgi/options.py:378 +#, fuzzy msgid "List subscriptions for %(safeuser)s on %(hostname)s" msgstr "מנויי רשימה של %(safeuser)s על %(hostname)s" @@ -2601,6 +2676,7 @@ msgid "You are already using that email address" msgstr "×תה משתמש כבר בכתובת זו" #: Mailman/Cgi/options.py:459 +#, fuzzy msgid "" "The new address you requested %(newaddr)s is already a member of the\n" "%(listname)s mailing list, however you have also requested a global change " @@ -2613,6 +2689,7 @@ msgstr "" "כל רשימה ×חרת המכילה ×ת הכתובת %(safeuser)s תתעדכן." #: Mailman/Cgi/options.py:468 +#, fuzzy msgid "The new address is already a member: %(newaddr)s" msgstr "הכתובת החדשה ×”× ×” כבר מנויה: %(newaddr)s" @@ -2621,6 +2698,7 @@ msgid "Addresses may not be blank" msgstr "הכתובת ×œ× ×™×›×•×œ×” להיות ריקה" #: Mailman/Cgi/options.py:488 +#, fuzzy msgid "A confirmation message has been sent to %(newaddr)s. " msgstr "נשלחה הודעת ×ישוד ×ל %(newaddr)s. " @@ -2633,10 +2711,12 @@ msgid "Illegal email address provided" msgstr "הוקלדה כתובת דו×\"ל ×œ× ×—×•×§×™×ª" #: Mailman/Cgi/options.py:501 +#, fuzzy msgid "%(newaddr)s is already a member of the list." msgstr "%(newaddr)s ×”× ×” כבר מנויה ברשימה." #: Mailman/Cgi/options.py:504 +#, fuzzy msgid "" "%(newaddr)s is banned from this list. If you\n" " think this restriction is erroneous, please contact\n" @@ -2712,6 +2792,7 @@ msgstr "" " קבלת החלטה של מפקחי הרשימה." #: Mailman/Cgi/options.py:623 +#, fuzzy msgid "" "You have been successfully unsubscribed from the\n" " mailing list %(fqdn_listname)s. If you were receiving digest\n" @@ -2805,6 +2886,7 @@ msgid "day" msgstr "יו×" #: Mailman/Cgi/options.py:900 +#, fuzzy msgid "%(days)d %(units)s" msgstr "%(units)s %(days)d" @@ -2817,6 +2899,7 @@ msgid "No topics defined" msgstr "×œ× ×”×•×’×“×¨×• נוש××™×" #: Mailman/Cgi/options.py:940 +#, fuzzy msgid "" "\n" "You are subscribed to this list with the case-preserved address\n" @@ -2827,6 +2910,7 @@ msgstr "" "(×¢× ×©×ž×™×¨×ª ×ותיות גדולות/קטנות) em>%(cpuser)s." #: Mailman/Cgi/options.py:956 +#, fuzzy msgid "%(realname)s list: member options login page" msgstr "רשימת %(realname)s: עמוד כניסה להגדרות מנוי×" @@ -2835,10 +2919,12 @@ msgid "email address and " msgstr "כתובת דו×\"ל ו-" #: Mailman/Cgi/options.py:960 +#, fuzzy msgid "%(realname)s list: member options for user %(safeuser)s" msgstr "רשימת %(realname)s: הגדרות מנוי עבור %(safeuser)s " #: Mailman/Cgi/options.py:986 +#, fuzzy msgid "" "In order to change your membership option, you must\n" " first log in by giving your %(extra)smembership password in the section\n" @@ -2911,6 +2997,7 @@ msgid "" msgstr "<חסר>" #: Mailman/Cgi/options.py:1140 +#, fuzzy msgid "Requested topic is not valid: %(topicname)s" msgstr "×”× ×•×©× ×”×ž×‘×•×§×© ×ינו חוקי: %(topicname)s" @@ -2939,6 +3026,7 @@ msgid "Private archive - \"./\" and \"../\" not allowed in URL." msgstr "×רכיון פרטי. ×œ× × ×™×ª×Ÿ להשתמש ב-\"./\" ו- \"../\" בקישור" #: Mailman/Cgi/private.py:109 +#, fuzzy msgid "Private Archive Error - %(msg)s" msgstr "שגי×ת ×רכיון פרטי - %(msg)s" @@ -2976,6 +3064,7 @@ msgid "Mailing list deletion results" msgstr "תוצ×ות מחיקת רשימת הדיוור" #: Mailman/Cgi/rmlist.py:187 +#, fuzzy msgid "" "You have successfully deleted the mailing list\n" " %(listname)s." @@ -2984,6 +3073,7 @@ msgstr "" " %(listname)s." #: Mailman/Cgi/rmlist.py:191 +#, fuzzy msgid "" "There were some problems deleting the mailing list\n" " %(listname)s. Contact your site administrator at " @@ -2995,6 +3085,7 @@ msgstr "" " לקבלת פרטי×." #: Mailman/Cgi/rmlist.py:208 +#, fuzzy msgid "Permanently remove mailing list %(realname)s" msgstr "הסר לצמיתות ×ת רשימת הדיוור %(realname)s" @@ -3060,6 +3151,7 @@ msgid "Invalid options to CGI script" msgstr "×פשריות ×œ× ×—×•×§×™×•×ª הועברו ל×צווה CGI" #: Mailman/Cgi/roster.py:118 +#, fuzzy msgid "%(realname)s roster authentication failed." msgstr "×ימות לוח שמות של %(realname)s נכשל" @@ -3127,6 +3219,7 @@ msgstr "" "ובה הור×ות נוספות." #: Mailman/Cgi/subscribe.py:262 +#, fuzzy msgid "" "The email address you supplied is banned from this\n" " mailing list. If you think this restriction is erroneous, please\n" @@ -3151,6 +3244,7 @@ msgid "" msgstr "המנוי שלך × ×סר ×›×™ נרשמת בכתובת דו×\"ל ×œ× ×ž×ובטחת." #: Mailman/Cgi/subscribe.py:278 +#, fuzzy msgid "" "Confirmation from your email address is required, to prevent anyone from\n" "subscribing you without permission. Instructions are being sent to you at\n" @@ -3162,6 +3256,7 @@ msgstr "" "המנוי שלך ×œ× ×™×™×›× ×¡ לתוקף עד קבלת ×ישור המנוי שלך." #: Mailman/Cgi/subscribe.py:290 +#, fuzzy msgid "" "Your subscription request was deferred because %(x)s. Your request has " "been\n" @@ -3182,6 +3277,7 @@ msgid "Mailman privacy alert" msgstr "×זעקת פרטיות של דוור" #: Mailman/Cgi/subscribe.py:316 +#, fuzzy msgid "" "An attempt was made to subscribe your address to the mailing list\n" "%(listaddr)s. You are already subscribed to this mailing list.\n" @@ -3219,6 +3315,7 @@ msgid "This list only supports digest delivery." msgstr "רשימה זו תומכת ×‘×ª×§×¦×™×¨×™× ×‘×œ×‘×“." #: Mailman/Cgi/subscribe.py:344 +#, fuzzy msgid "You have been successfully subscribed to the %(realname)s mailing list." msgstr "נרשמת בהצלחה לרשימת הדיוור %(realname)s" @@ -3264,6 +3361,7 @@ msgid "" msgstr "×ין לך מנוי בתוקף. כבר ביטלת ×ת המנוי שלך ×ו שינית ×ת הכתוכת שלך?" #: Mailman/Commands/cmd_confirm.py:69 +#, fuzzy msgid "" "You are currently banned from subscribing to this list. If you think this\n" "restriction is erroneous, please contact the list owners at\n" @@ -3342,26 +3440,32 @@ msgid "n/a" msgstr "×œ× × ×ž×¡×¨" #: Mailman/Commands/cmd_info.py:44 +#, fuzzy msgid "List name: %(listname)s" msgstr "×©× ×”×¨×©×™×ž×”: %(listname)s" #: Mailman/Commands/cmd_info.py:45 +#, fuzzy msgid "Description: %(description)s" msgstr "ת×ור %(description)s" #: Mailman/Commands/cmd_info.py:46 +#, fuzzy msgid "Postings to: %(postaddr)s" msgstr "×ž×¡×¨×™× ×ל: %(postaddr)s" #: Mailman/Commands/cmd_info.py:47 +#, fuzzy msgid "List Helpbot: %(requestaddr)s" msgstr "רובוט העזרה של הרשימה: %(requestaddr)s" #: Mailman/Commands/cmd_info.py:48 +#, fuzzy msgid "List Owners: %(owneraddr)s" msgstr "בעלי הרשימה: %(owneraddr)s" #: Mailman/Commands/cmd_info.py:49 +#, fuzzy msgid "More information: %(listurl)s" msgstr "מידע נוסף: %(listurl)s" @@ -3384,18 +3488,22 @@ msgstr "" " הצג רשימה של הרשימות ×”×œ× ×ž×•×¡×ª×¨×•×ª בשרת דוור GNU ×–×”.\n" #: Mailman/Commands/cmd_lists.py:44 +#, fuzzy msgid "Public mailing lists at %(hostname)s:" msgstr "רשימות דיוור ×œ× ×ž×•×¡×ª×¨×•×ª ב-%(hostname)s:" #: Mailman/Commands/cmd_lists.py:66 +#, fuzzy msgid "%(i)3d. List name: %(realname)s" msgstr "×©× ×¨×©×™×ž×” %(i)3d.: %(realname)s" #: Mailman/Commands/cmd_lists.py:67 +#, fuzzy msgid " Description: %(description)s" msgstr " ת×ור: %(description)s" #: Mailman/Commands/cmd_lists.py:68 +#, fuzzy msgid " Requests to: %(requestaddr)s" msgstr " בקשות ×ל: %(requestaddr)s" @@ -3428,12 +3536,14 @@ msgstr "" " התשובה תמיד תישלח לכתובת המנוי.\n" #: Mailman/Commands/cmd_password.py:51 Mailman/Commands/cmd_password.py:66 +#, fuzzy msgid "Your password is: %(password)s" msgstr "×”×¡×™×¡×ž× ×©×œ×š ×”×™×: %(password)s" #: Mailman/Commands/cmd_password.py:57 Mailman/Commands/cmd_password.py:72 #: Mailman/Commands/cmd_password.py:95 Mailman/Commands/cmd_password.py:121 #: Mailman/Commands/cmd_set.py:149 Mailman/Commands/cmd_set.py:219 +#, fuzzy msgid "You are not a member of the %(listname)s mailing list" msgstr "×ינך מנוי לרשימת הידוור %(listname)s" @@ -3610,6 +3720,7 @@ msgstr "" " החודשית של רשימה זו.\n" #: Mailman/Commands/cmd_set.py:122 +#, fuzzy msgid "Bad set command: %(subcmd)s" msgstr "פקודת set ×œ× ×—×•×§×™×ª: %(subcmd)s" @@ -3630,6 +3741,7 @@ msgid "on" msgstr "פעיל" #: Mailman/Commands/cmd_set.py:154 +#, fuzzy msgid " ack %(onoff)s" msgstr " ack %(onoff)s" @@ -3667,22 +3779,27 @@ msgid "due to bounces" msgstr "בגלל החזרי מסרי×" #: Mailman/Commands/cmd_set.py:186 +#, fuzzy msgid " %(status)s (%(how)s on %(date)s)" msgstr " %(status)s (%(how)s בת×ריך %(date)s)" #: Mailman/Commands/cmd_set.py:192 +#, fuzzy msgid " myposts %(onoff)s" msgstr " myposts %(onoff)s" #: Mailman/Commands/cmd_set.py:195 +#, fuzzy msgid " hide %(onoff)s" msgstr " hide %(onoff)s" #: Mailman/Commands/cmd_set.py:199 +#, fuzzy msgid " duplicates %(onoff)s" msgstr " duplicates %(onoff)s" #: Mailman/Commands/cmd_set.py:203 +#, fuzzy msgid " reminders %(onoff)s" msgstr " reminders %(onoff)s" @@ -3691,6 +3808,7 @@ msgid "You did not give the correct password" msgstr "×œ× ×”×§×œ×“×ª ×¡×™×¡×ž× × ×›×•× ×”" #: Mailman/Commands/cmd_set.py:236 Mailman/Commands/cmd_set.py:283 +#, fuzzy msgid "Bad argument: %(arg)s" msgstr "פרמטר ×œ× ×ª×§×™×Ÿ: %(arg)s" @@ -3764,6 +3882,7 @@ msgstr "" " ×•×œ×œ× ×ž×¨×›×ות!)\n" #: Mailman/Commands/cmd_subscribe.py:62 +#, fuzzy msgid "Bad digest specifier: %(arg)s" msgstr "מציין ×ª×§×¦×™×¨×™× ×œ× ×—×•×§×™: %(arg)s" @@ -3772,6 +3891,7 @@ msgid "No valid address found to subscribe" msgstr "×œ× × ×ž×¦××” כתובת חוקית להרשמה" #: Mailman/Commands/cmd_subscribe.py:113 +#, fuzzy msgid "" "The email address you supplied is banned from this mailing list.\n" "If you think this restriction is erroneous, please contact the list\n" @@ -3808,6 +3928,7 @@ msgid "This list only supports digest subscriptions!" msgstr "רשימה זו תומכת במנויי ×ª×§×¦×™×¨×™× ×‘×œ×‘×“!" #: Mailman/Commands/cmd_subscribe.py:146 +#, fuzzy msgid "" "Your subscription request has been forwarded to the list administrator\n" "at %(listowner)s for review." @@ -3842,6 +3963,7 @@ msgstr "" " ×•×œ×œ× ×ž×¨×›×ות!)\n" #: Mailman/Commands/cmd_unsubscribe.py:62 +#, fuzzy msgid "%(address)s is not a member of the %(listname)s mailing list" msgstr "%(address)s ×œ× ×ž× ×•×™ ברשימת הדיוור %(listname)s" @@ -4086,6 +4208,7 @@ msgid "Chinese (Taiwan)" msgstr "Chinese (Taiwan)" #: Mailman/Deliverer.py:53 +#, fuzzy msgid "" "Note: Since this is a list of mailing lists, administrative\n" "notices like the password reminder will be sent to\n" @@ -4099,14 +4222,17 @@ msgid " (Digest mode)" msgstr "(מצב תקציר)" #: Mailman/Deliverer.py:79 +#, fuzzy msgid "Welcome to the \"%(realname)s\" mailing list%(digmode)s" msgstr "ברוך בו×ך ×ל%(digmode)s רשימת הדיוור \"%(realname)s\"" #: Mailman/Deliverer.py:89 +#, fuzzy msgid "You have been unsubscribed from the %(realname)s mailing list" msgstr "מנוי שלך לרשימת הדיוור %(realname)s בוטל" #: Mailman/Deliverer.py:116 +#, fuzzy msgid "%(listfullname)s mailing list reminder" msgstr "תזכורת של רשימת הדיוור %(listfullname)s" @@ -4119,6 +4245,7 @@ msgid "Hostile subscription attempt detected" msgstr "התגלה ניסיון מנוי ×ויין" #: Mailman/Deliverer.py:169 +#, fuzzy msgid "" "%(address)s was invited to a different mailing\n" "list, but in a deliberate malicious attempt they tried to confirm the\n" @@ -4130,6 +4257,7 @@ msgstr "" "רק חשבנו שתרצה לדעת מזה. ×œ× ×“×¨×•×©×” כל פעולה מצדך." #: Mailman/Deliverer.py:188 +#, fuzzy msgid "" "You invited %(address)s to your list, but in a\n" "deliberate malicious attempt, they tried to confirm the invitation to a\n" @@ -4141,6 +4269,7 @@ msgstr "" "לרשימה ×חרת. רק חשבנו שתרצה לדעת מזה. ×œ× ×“×¨×•×©×” כל פעולה מצדך." #: Mailman/Deliverer.py:221 +#, fuzzy msgid "%(listname)s mailing list probe message" msgstr "הודעת גישוש של רשימת הדיוור %(listname)s" @@ -4339,8 +4468,8 @@ msgid "" " membership.\n" "\n" "

                      You can control both the\n" -" number\n" +" number\n" " of reminders the member will receive and the\n" " ×תה שולט ×’× ×‘-\n" -" כמות\n" +" כמות\n" " התזכורות שהמנוי יקבל ×•×’× ×‘-\n" " No those messages too will get discarded. You may " "want\n" " to set up an\n" -" autoresponse\n" +" autoresponse\n" " message for email to the -owner and -admin address." msgstr "" "על ××£ שגל××™ ההחזרות של דוור עמיד למדי, הו×\n" @@ -4624,6 +4753,7 @@ msgstr "" " בכל מקרה יעשה ניסיון להודיע למשתמש עצמו." #: Mailman/Gui/Bounce.py:194 +#, fuzzy msgid "" "Bad value for %(property)s: %(val)s" @@ -4767,8 +4897,8 @@ msgid "" "Use this option to remove each message attachment that does\n" " not have a matching content type. Requirements and formats " "are\n" -" exactly like filter_mime_types.\n" "\n" "

                      Note: if you add entries to this list but don't add\n" @@ -4872,6 +5002,7 @@ msgstr "" " נמחקי×. ×”×פשרות ×”×חרונה זמינה רק ×× ×יפשר ×–×ת מנהל ×”×תר." #: Mailman/Gui/ContentFilter.py:171 +#, fuzzy msgid "Bad MIME type ignored: %(spectype)s" msgstr "×ž×ª×¢×œ× ×ž×¡×•×’ MIME ×œ× ×—×•×§×™: %(spectype)s" @@ -4972,6 +5103,7 @@ msgid "" msgstr "×”×× ×¢×œ דוור ×œ×”×•×¦×™× ×ת התקציר ×”×‘× ×›×¢×ª, ×× ×ינו ריק?" #: Mailman/Gui/Digest.py:145 +#, fuzzy msgid "" "The next digest will be sent as volume\n" " %(volume)s, number %(number)s" @@ -4988,14 +5120,17 @@ msgid "There was no digest to send." msgstr "×ין תקציר לשלוח." #: Mailman/Gui/GUIBase.py:173 +#, fuzzy msgid "Invalid value for variable: %(property)s" msgstr "ערך ×œ× ×—×•×§×™ עבור המשתנה: %(property)s" #: Mailman/Gui/GUIBase.py:178 +#, fuzzy msgid "Bad email address for option %(property)s: %(error)s" msgstr "כתובת דו×\"ל ×œ× ×—×•×§×™ עבור ×פשרות %(property)s: %(error)s" #: Mailman/Gui/GUIBase.py:204 +#, fuzzy msgid "" "The following illegal substitution variables were\n" " found in the %(property)s string:\n" @@ -5010,6 +5145,7 @@ msgstr "" " בעיה זו." #: Mailman/Gui/GUIBase.py:218 +#, fuzzy msgid "" "Your %(property)s string appeared to\n" " have some correctable problems in its new value.\n" @@ -5108,8 +5244,8 @@ msgid "" "

                      In order to split the list ownership duties into\n" " administrators and moderators, you must\n" " set a separate moderator password,\n" -" and also provide the email\n" +" and also provide the email\n" " addresses of the list moderators. Note that the field you\n" " are changing here specifies the list administrators." msgstr "" @@ -5426,13 +5562,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5456,8 +5592,8 @@ msgstr "" " מפורשת גורמת לדוור להכניס כותרת השב-×ל: מסוימת\n" " בכל המסרי×, ומוחק ×ת הכותרת המקורית ×× ×¦×¨×™×š. (כתובת מפורשת\n" -" מכניס ×ת הערך של \n" +" מכניס ×ת הערך של \n" " כתובת השב-×ל).\n" "\n" " ישנן סיבות רבות מדוע ×œ× ×œ×”×›× ×™×¡ ×ו למחוק ×ת כותרת ×”-השב-×ל:השב-×ל: ×’×•×¨× " "לקושי\n" " במתן ×ž×¢× ×™× ×¤×¨×˜×™×™×. ר××” שינוי בלתי הפיך של\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\"> שינוי בלתי הפיך של\n" " `השב-×ל' נחשב פוגעני לדיון מקיף ×‘× ×•×©× ×–×” .\n" " ר××” " @@ -5491,8 +5627,8 @@ msgstr "כותרת השב-×ל: מפורשת." msgid "" "This is the address set in the Reply-To: header\n" " when the reply_goes_to_list\n" +" href=\"?VARHELP=general/" +"reply_goes_to_list\">reply_goes_to_list\n" " option is set to Explicit address.\n" "\n" "

                      There are many reasons not to introduce or override the\n" @@ -5500,13 +5636,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5532,15 +5668,15 @@ msgstr "" "הרשימה\n" " נקבעת כ-כתובת מפורשת.\n" "\n" -"

                      -ישנן סיבות רבות מדוע ×œ× ×œ×”×›× ×™×¡ ×ו למחוק ×ת כותרת ×” השב-" -"×ל:.\n" +"

                      -ישנן סיבות רבות מדוע ×œ× ×œ×”×›× ×™×¡ ×ו למחוק ×ת כותרת ×” " +"השב-×ל:.\n" " ×חת ×”×™× ×©×™×© ×©×•×œ×—×™× ×©×¡×•×ž×›×™× ×¢×œ הגדרות ×”- השב-×ל: ×©×œ×”× " "כדי לציין\n" " ×ת הכתובת התקפה שלה×. ×חרת ×”×™× ×©×©×™× ×•×™ השב-×ל: ×’×•×¨× " "לקושי\n" " במתן ×ž×¢× ×™× ×¤×¨×˜×™×™×. ר××” שינוי בלתי הפיך של\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\"> שינוי בלתי הפיך של\n" " 'השב-×ל' נחשב פוגעני לדיון מקיף ×‘× ×•×©× ×–×”. \n" " ר××” " @@ -5602,8 +5738,8 @@ msgid "" " member list addresses, but rather to the owner of those member\n" " lists. In that case, the value of this setting is appended to\n" " the member's account name for such notices. `-owner' is the\n" -" typical choice. This setting has no effect when \"umbrella_list" -"\"\n" +" typical choice. This setting has no effect when " +"\"umbrella_list\"\n" " is \"No\"." msgstr "" "×›×שר \"רשימת מיטרייה\" פעילה ומסמנת שרשימות ×חרות מנויות ברשימה\n" @@ -5912,8 +6048,8 @@ msgstr "" "של ×נשי×\n" " זכות לשלוח ×ל הרשימה, ×•×œ×ž× ×•×™×™× ×”××—×¨×™× ×œ×¨×•×‘ ×ין זכות לשלוח דו×ר " "×ל הרשימה.\n" -" עבור רשימות מסוג ×–×” הכותרת List-Post: מטעה. בחר ב-" -"ל×\n" +" עבור רשימות מסוג ×–×” הכותרת List-Post: מטעה. בחר " +"ב-ל×\n" " כדי למנוע ×ת הכללת כותרת זו. (×–×” ×ינו משפיע על הופעתן של " "כותרות\n" " List-*: ×חרות.(" @@ -6137,8 +6273,8 @@ msgstr "" "×œ× ×™×§×“×“\n" " קידומות ASCII ×›×שר ש×ר הכותרת מכילה רק ×ª×•×•×™× ASCII, ×בל ×× " "הכותרת המקורית\n" -" מכילה ×ª×•×•×™× ×©×ינן ASCII, ×”×•× ×™×§×“×“ ×ת הקידומת. ×–×” מונע דו-" -"משמעות ×‘×ª×§× ×™× ×©×™×›×•×œ\n" +" מכילה ×ª×•×•×™× ×©×ינן ASCII, ×”×•× ×™×§×“×“ ×ת הקידומת. ×–×” מונע " +"דו-משמעות ×‘×ª×§× ×™× ×©×™×›×•×œ\n" " ×œ×’×¨×•× ×œ×§×•×¨××™ דו×\"ל ×ž×¡×•×™×ž×™× ×œ×”×¦×™×’ ×¨×•×•×—×™× ×ž×™×•×ª×¨×™× ×ו להחסיר " "×¨×•×•×™× ×‘×™×Ÿ הקידומת לכותרת\n" " המקורית." @@ -6535,6 +6671,7 @@ msgstr "" " ×ž× ×•×™×™× ×‘×©× ××—×¨×™× ×œ×œ× ×ישור×." #: Mailman/Gui/Privacy.py:104 +#, fuzzy msgid "" "This section allows you to configure subscription and\n" " membership exposure policy. You can also control whether this\n" @@ -6716,8 +6853,8 @@ msgid "" " either individually or as a group. Any\n" " posting from a non-member who is not explicitly accepted,\n" " rejected, or discarded, will have their posting filtered by the\n" -" general\n" +" general\n" " non-member rules.\n" "\n" "

                      In the text boxes below, add one address per line; start the\n" @@ -6744,8 +6881,8 @@ msgstr "" "

                      עבור מי ש×ינו מנוי, ×פשר --\n" " לקבל,\n" -" להחזיק\n" +" להחזיק\n" " עבור פיקוח,\n" " לדחות (להחזיר) ×ו \n" @@ -6754,8 +6891,8 @@ msgstr "" " כל ×חד לחוד ×ו ×›×•×œ× ×‘×™×—×“. כל מסר ממי ש×ינו מנוי ×©×œ× ×ž×§×‘×œ×™×, " "נדחי×, ×ו מוחקי×\n" " במפורש, יעבור סינון על ידי ×”-\n" -" כללי×\n" +" כללי×\n" " של מי ×ינו מנוי.\n" "\n" "

                      ×©×™× ×œ×‘ שהת×מות ×œ× ×œ×¤×™ ×‘×™×˜×•×™×™× ×¨×’×•×œ×¨×™×™× ×ª×ž×™×“ ×ž×ª×‘×¦×¢×™× ×§×•×“×." @@ -6769,6 +6906,7 @@ msgid "By default, should new list member postings be moderated?" msgstr "כברירת מחדל, ×”×× ×™×© לפקח על ×ž×¡×¨×™× ×ž×ž× ×•×™× ×—×“×©×™×?" #: Mailman/Gui/Privacy.py:218 +#, fuzzy msgid "" "Each list member has a moderation flag which says\n" " whether messages from the list member can be posted directly " @@ -6820,8 +6958,8 @@ msgid "" "If a member posts this many times, within a period of time\n" " the member is automatically moderated. Use 0 to disable. " "See\n" -" member_verbosity_interval for details on the time " "period.\n" "\n" @@ -7240,8 +7378,8 @@ msgstr "" "כתובות\n" " מ×ושרות,\n" -" מוחזקות,\n" +" מוחזקות,\n" " נדחות (החזרות), ו-\n" " The body of the message can also be optionally scanned for\n" " Subject: and Keywords: headers, as\n" " specified by the topics_bodylines_limit\n" +" href=\"?VARHELP=topics/" +"topics_bodylines_limit\">topics_bodylines_limit\n" " configuration variable." msgstr "" "מסנן הנוש××™× ×ž×פיין כל מסר דו×\"ל שנכנס בהת×× ×œ\n" " \n" -" מסנני ×‘×™×˜×•×™× ×¨×’×•×œ×¨×™×™× ×©×תה מגדיר למטה. ×× ×›×•×ª×¨×ª ×”-" -"נוש×:\n" +" מסנני ×‘×™×˜×•×™× ×¨×’×•×œ×¨×™×™× ×©×תה מגדיר למטה. ×× ×›×•×ª×¨×ª " +"×”-נוש×:\n" " ×ו מלות-המפתח: מכילה הת×מה מול מסנן נוש×, המסר " "נכנס לוגית ×ל תוך\n" " דלי נוש××™×. כל מנוי יכול לבחור לקבל רק ×ž×¡×¨×™× ×ž×”×¨×©×™×ž×” " @@ -7570,8 +7710,8 @@ msgstr "" "

                      קיימת ×’× ×פשרות לסרוק ×ת גוף המסר לחפש כותרות נוש×:\n" " ×ו מלות-מפתח: כפי שמצוין במשתנה תצורה\n" -" גבול_שורות_גוף_הנוש×" +" גבול_שורות_גוף_הנוש×" #: Mailman/Gui/Topics.py:72 msgid "How many body lines should the topic matcher scan?" @@ -7621,8 +7761,8 @@ msgid "" " \"header\" on which matching is also performed." msgstr "" "כל מלת מפתח הנו למעשה ביטוי רגולרי, שנבדק מול ×§×˜×¢×™× ×ž×¡×•×™×ž×™× ×©×œ\n" -" הודעת דו×\"ל, בפרט מול כותרות מלות-מפתח: ו-" -"נוש×:.\n" +" הודעת דו×\"ל, בפרט מול כותרות מלות-מפתח: " +"ו-נוש×:.\n" " ×©×™× ×œ×‘ ×©×’× ×”×©×•×¨×•×ª הר×שונות של ההודעה יכולות להכיל \"כותרת\"\n" " מלות-מפתח: ו- נוש×: ×ž×•×œ× ×’× ×ž×ª×‘×¦×¢ " "הת×מה." @@ -7636,6 +7776,7 @@ msgstr "" " × ×•×©× ×œ× ×©×œ× ×œ× ×™×˜×•×¤×œ." #: Mailman/Gui/Topics.py:135 +#, fuzzy msgid "" "The topic pattern '%(safepattern)s' is not a\n" " legal regular expression. It will be discarded." @@ -7794,8 +7935,8 @@ msgid "" msgstr "" "דוור מוסיף קידומת נוש×: ×¢×\n" " טקסט הניתן להת×מה\n" -" ×ישית ובדרך כלל קידומת זו נר×ית ×‘×ž×¡×¨×™× ×”× ×©×œ×—×™× ×“×¨×š השער ל-" -"Usenet.\n" +" ×ישית ובדרך כלל קידומת זו נר×ית ×‘×ž×¡×¨×™× ×”× ×©×œ×—×™× ×“×¨×š השער " +"ל-Usenet.\n" " ×פשר לקבוע הגדרה זו ל-×œ× ×›×“×™ לבטל ×ת הקידומת על מסרי×\n" " ×©×¢×•×‘×¨×™× ×“×¨×š השער. כמובן, ×× ×תה מכבה קידומות נוש×: " "רגילות,\n" @@ -7852,6 +7993,7 @@ msgid "%(listinfo_link)s list run by %(owner_link)s" msgstr "רשימת %(listinfo_link)s·בניהול %(owner_link)s" #: Mailman/HTMLFormatter.py:57 +#, fuzzy msgid "%(realname)s administrative interface" msgstr "ממשק הניהול של %(realname)s" @@ -7860,6 +8002,7 @@ msgid " (requires authorization)" msgstr "(מחייב ×ימות)" #: Mailman/HTMLFormatter.py:61 +#, fuzzy msgid "Overview of all %(hostname)s mailing lists" msgstr "×¡×™×›×•× ×›×œ רשימות הדיוור של %(hostname)s" @@ -7880,6 +8023,7 @@ msgid "; it was disabled by the list administrator" msgstr "; הושהה על ידי מנהל הרשימה" #: Mailman/HTMLFormatter.py:146 +#, fuzzy msgid "" "; it was disabled due to excessive bounces. The\n" " last bounce was received on %(date)s" @@ -7892,6 +8036,7 @@ msgid "; it was disabled for unknown reasons" msgstr "; הושהה מסיבות ×œ× ×™×“×•×¢×•×ª" #: Mailman/HTMLFormatter.py:151 +#, fuzzy msgid "Note: your list delivery is currently disabled%(reason)s." msgstr "×©×™× ×œ×‘: המנוי שלך ברשימה כרגה מושהה%(reason)s." @@ -7904,6 +8049,7 @@ msgid "the list administrator" msgstr "מנהל הרשימה" #: Mailman/HTMLFormatter.py:157 +#, fuzzy msgid "" "

                      %(note)s\n" "\n" @@ -7921,6 +8067,7 @@ msgstr "" " %(mailto)s ×× ×©×™ לך ש×לות ×ו ×× ×תה זקוק לעזרה." #: Mailman/HTMLFormatter.py:169 +#, fuzzy msgid "" "

                      We have received some recent bounces from your\n" " address. Your current bounce score is %(score)s out of " @@ -7939,6 +8086,7 @@ msgstr "" " ב×ופן ×וטומטי ×× ×”×‘×¢×™×” נפתרת בקרוב." #: Mailman/HTMLFormatter.py:181 +#, fuzzy msgid "" "(Note - you are subscribing to a list of mailing lists, so the %(type)s " "notice will be sent to the admin address for your membership, %(addr)s.)

                      " @@ -7983,6 +8131,7 @@ msgstr "" " הרשימה. החלטת המפקח תישלח ×ליך בדו×\"ל." #: Mailman/HTMLFormatter.py:208 +#, fuzzy msgid "" "This is %(also)sa private list, which means that the\n" " list of members is not available to non-members." @@ -7991,6 +8140,7 @@ msgstr "" " ×œ× ×—×©×•×¤×” למי ש×ינו מנוי." #: Mailman/HTMLFormatter.py:211 +#, fuzzy msgid "" "This is %(also)sa hidden list, which means that the\n" " list of members is available only to the list administrator." @@ -7999,6 +8149,7 @@ msgstr "" " רק למנהל הרשימה." #: Mailman/HTMLFormatter.py:214 +#, fuzzy msgid "" "This is %(also)sa public list, which means that the\n" " list of members list is available to everyone." @@ -8015,6 +8166,7 @@ msgstr "" " דו×ר זבל ×œ× ×™×•×›×œ×• לזהות ×ותן בקלות)." #: Mailman/HTMLFormatter.py:222 +#, fuzzy msgid "" "

                      (Note that this is an umbrella list, intended to\n" " have only other mailing lists as members. Among other things,\n" @@ -8031,6 +8183,7 @@ msgid "either " msgstr "×ו " #: Mailman/HTMLFormatter.py:256 +#, fuzzy msgid "" "To unsubscribe from %(realname)s, get a password reminder,\n" " or change your subscription options %(either)senter your " @@ -8064,12 +8217,14 @@ msgstr "" " כתובת דו×\"ל שלך." #: Mailman/HTMLFormatter.py:277 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " members.)" msgstr "(%(which)s זמינה רק למנויי הרשימה.)" #: Mailman/HTMLFormatter.py:281 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " administrator.)" @@ -8128,6 +8283,7 @@ msgid "The current archive" msgstr "×”×רכיון הנוכחי" #: Mailman/Handlers/Acknowledge.py:59 +#, fuzzy msgid "%(realname)s post acknowledgement" msgstr "×ישור קבלה %(realname)s" @@ -8140,6 +8296,7 @@ msgid "" msgstr "" #: Mailman/Handlers/CalcRecips.py:79 +#, fuzzy msgid "" "Your urgent message to the %(realname)s mailing list was not authorized for\n" "delivery. The original message as received by Mailman is attached.\n" @@ -8215,6 +8372,7 @@ msgid "Message may contain administrivia" msgstr "יתכן שהמסר מכיל הור×ות מנהלתיות" #: Mailman/Handlers/Hold.py:84 +#, fuzzy msgid "" "Please do *not* post administrative requests to the mailing\n" "list. If you wish to subscribe, visit %(listurl)s or send a message with " @@ -8255,10 +8413,12 @@ msgid "Posting to a moderated newsgroup" msgstr "מסר נשלח ×ל קבוצת חדשות מפוקחת" #: Mailman/Handlers/Hold.py:252 +#, fuzzy msgid "Your message to %(listname)s awaits moderator approval" msgstr "המסר שלך ×ל %(listname)s ממתין ל×ישור מפקח" #: Mailman/Handlers/Hold.py:271 +#, fuzzy msgid "%(listname)s post from %(sender)s requires approval" msgstr "מסר ב-%(listname)s שנשלח מ-%(sender)s צריך ×ישור" @@ -8300,6 +8460,7 @@ msgid "After content filtering, the message was empty" msgstr "ל×חר סינון תוכן, המסר נותר ריק" #: Mailman/Handlers/MimeDel.py:269 +#, fuzzy msgid "" "The attached message matched the %(listname)s mailing list's content " "filtering\n" @@ -8341,6 +8502,7 @@ msgid "The attached message has been automatically discarded." msgstr "ההודעה המצורפת נמחקה ×וטומטית." #: Mailman/Handlers/Replybot.py:75 +#, fuzzy msgid "Auto-response for your message to the \"%(realname)s\" mailing list" msgstr "מענה-×וטומטי למסר שלך לרשימת דיוור·\"%(realname)s\"" @@ -8364,6 +8526,7 @@ msgid "HTML attachment scrubbed and removed" msgstr "מצורף HTML קורצף ונמחק" #: Mailman/Handlers/Scrubber.py:234 Mailman/Handlers/Scrubber.py:259 +#, fuzzy msgid "" "An HTML attachment was scrubbed...\n" "URL: %(url)s\n" @@ -8418,6 +8581,7 @@ msgstr "" "קישור: %(url)s\n" #: Mailman/Handlers/Scrubber.py:347 +#, fuzzy msgid "Skipped content of type %(partctype)s\n" msgstr "דילגתי על תוכן מסוג %(partctype)s\n" @@ -8448,6 +8612,7 @@ msgid "Message rejected by filter rule match" msgstr "המסר נדחה בגלל הת×מה לחוק סינון" #: Mailman/Handlers/ToDigest.py:173 +#, fuzzy msgid "%(realname)s Digest, Vol %(volume)d, Issue %(issue)d" msgstr "תקציר של %(realname)s, כרך %(volume)d, גליון %(issue)d" @@ -8484,6 +8649,7 @@ msgid "End of " msgstr "סוף ×”" #: Mailman/ListAdmin.py:309 +#, fuzzy msgid "Posting of your message titled \"%(subject)s\"" msgstr "משלוח המסר שלך ×‘×©× \"%(subject)s\"" @@ -8496,6 +8662,7 @@ msgid "Forward of moderated message" msgstr "העברה של מסר מפוקח" #: Mailman/ListAdmin.py:405 +#, fuzzy msgid "New subscription request to list %(realname)s from %(addr)s" msgstr "בקשת הרשמה חדשה לרשימה %(realname)s מ×ת %(addr)s" @@ -8509,6 +8676,7 @@ msgid "via admin approval" msgstr "להמשיך להמתין ל×ישור" #: Mailman/ListAdmin.py:466 +#, fuzzy msgid "New unsubscription request from %(realname)s by %(addr)s" msgstr "בקשת מנוי מ×ת %(realname)s לפי %(addr)s" @@ -8521,10 +8689,12 @@ msgid "Original Message" msgstr "ההודעה המקורית" #: Mailman/ListAdmin.py:526 +#, fuzzy msgid "Request to mailing list %(realname)s rejected" msgstr "הבקשה ×ל רשימת הדיוור %(realname)s נדחתה" #: Mailman/MTA/Manual.py:66 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been created via the through-the-web\n" "interface. In order to complete the activation of this mailing list, the\n" @@ -8549,14 +8719,17 @@ msgstr "" "(×ו שווה ערך) על ידי הוספת השורות הב×ות, ו×ולי ×’× ×”×¨×¦×ª התכנית `newaliases':\n" #: Mailman/MTA/Manual.py:82 +#, fuzzy msgid "## %(listname)s mailing list" msgstr "## רשימת הדיוור %(listname)s" #: Mailman/MTA/Manual.py:99 +#, fuzzy msgid "Mailing list creation request for list %(listname)s" msgstr "בקשת יצירת רשימת דיוור %(listname)s" #: Mailman/MTA/Manual.py:113 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been removed via the through-the-web\n" "interface. In order to complete the de-activation of this mailing list, " @@ -8574,6 +8747,7 @@ msgstr "" "×”× ×” הרשומות שיש למחוק מקובץ /etc/aliases:\n" #: Mailman/MTA/Manual.py:123 +#, fuzzy msgid "" "\n" "To finish removing your mailing list, you must edit your /etc/aliases (or\n" @@ -8590,14 +8764,17 @@ msgstr "" "## רשימת דיוור %(listname)s" #: Mailman/MTA/Manual.py:142 +#, fuzzy msgid "Mailing list removal request for list %(listname)s" msgstr "בקשת מחיקת רשימת דיוור %(listname)s" #: Mailman/MTA/Postfix.py:442 +#, fuzzy msgid "checking permissions on %(file)s" msgstr "בודק הרש×ות של הקובץ %(file)s" #: Mailman/MTA/Postfix.py:452 +#, fuzzy msgid "%(file)s permissions must be 0664 (got %(octmode)s)" msgstr "ההרש×ות של %(file)s צריכות להיות 0664 (קיבלתי %(octmode)s)" @@ -8611,34 +8788,42 @@ msgid "(fixing)" msgstr "(מתקן)" #: Mailman/MTA/Postfix.py:470 +#, fuzzy msgid "checking ownership of %(dbfile)s" msgstr "בודק בעלות של %(dbfile)s" #: Mailman/MTA/Postfix.py:478 +#, fuzzy msgid "%(dbfile)s owned by %(owner)s (must be owned by %(user)s" msgstr "×”×‘×¢×œ×™× ×©×œ %(dbfile)s ×”×•× %(owner)s (%(user)s צריך להיות הבעלי×" #: Mailman/MTA/Postfix.py:491 +#, fuzzy msgid "%(dbfile)s permissions must be 0664 (got %(octmode)s)" msgstr "ההרש×ות של %(dbfile)s צריכות להיות 0664 (קיבלתי %(octmode)s)" #: Mailman/MailList.py:219 +#, fuzzy msgid "Your confirmation is required to join the %(listname)s mailing list" msgstr "דרוש ×ישורך כדי להצטרף לרשימת דיוור %(listname)s" #: Mailman/MailList.py:230 +#, fuzzy msgid "Your confirmation is required to leave the %(listname)s mailing list" msgstr "×ישורך דרוש כדי לעזוב ×ת רשימת דיוור %(listname)s" #: Mailman/MailList.py:999 Mailman/MailList.py:1492 +#, fuzzy msgid " from %(remote)s" msgstr "מ- %(remote)s" #: Mailman/MailList.py:1043 +#, fuzzy msgid "subscriptions to %(realname)s require moderator approval" msgstr "×ž× ×•×™×™× ×œ-%(realname)s ×ž×—×™×™×‘×™× ×ישור מפקח" #: Mailman/MailList.py:1125 bin/add_members:299 +#, fuzzy msgid "%(realname)s subscription notification" msgstr "הודעת מנוי של %(realname)s" @@ -8647,6 +8832,7 @@ msgid "unsubscriptions require moderator approval" msgstr "ביטולי ×ž× ×•×™×™× ×ž×—×™×™×‘×™× ×ישור מפקח" #: Mailman/MailList.py:1166 +#, fuzzy msgid "%(realname)s unsubscribe notification" msgstr "הודעת עזיבה של %(realname)s" @@ -8666,6 +8852,7 @@ msgid "via web confirmation" msgstr "מחרוזת ×ישור שגויה" #: Mailman/MailList.py:1396 +#, fuzzy msgid "subscriptions to %(name)s require administrator approval" msgstr "×ž× ×•×™×™× ×œ-%(name)s ×ž×—×™×™×‘×™× ×ישור מנהל" @@ -8684,6 +8871,7 @@ msgid "Last autoresponse notification for today" msgstr "מענה-×וטומטי ×חרון להיו×" #: Mailman/Queue/BounceRunner.py:360 +#, fuzzy msgid "" "The attached message was received as a bounce, but either the bounce format\n" "was not recognized, or no member addresses could be extracted from it. " @@ -8772,6 +8960,7 @@ msgid "Original message suppressed by Mailman site configuration\n" msgstr "" #: Mailman/htmlformat.py:675 +#, fuzzy msgid "Delivered by Mailman
                      version %(version)s" msgstr "נשלח על ידי דוור
                      ×’×™×¨×¡× %(version)s" @@ -8860,6 +9049,7 @@ msgid "Server Local Time" msgstr "זמן שרת מקומי" #: Mailman/i18n.py:180 +#, fuzzy msgid "" "%(wday)s %(mon)s %(day)2i %(hh)02i:%(mm)02i:%(ss)02i %(tzname)s %(year)04i" msgstr "" @@ -8948,8 +9138,8 @@ msgstr "" "\n" " --welcome-msg=\n" " -w \n" -" קבע ב×× ×œ×©×œ×•×— למנויי הרשימה הודעת ברכת הצטרפות, ×ו ×œ× ×¢× ×”×”×¦×œ×—×”/××™-" -"הצלחה\n" +" קבע ב×× ×œ×©×œ×•×— למנויי הרשימה הודעת ברכת הצטרפות, ×ו ×œ× ×¢× " +"ההצלחה/××™-הצלחה\n" " של ×ž× ×•×™× ×לה, תוך עקיפת הגדרת `admin_notify_mchanges'.\n" "\n" " --help\n" @@ -8963,6 +9153,7 @@ msgstr "" "להיות `-'.\n" #: bin/add_members:162 bin/add_members:172 +#, fuzzy msgid "Already a member: %(member)s" msgstr "הנו כבר מנוי: %(member)s" @@ -8971,10 +9162,12 @@ msgid "Bad/Invalid email address: blank line" msgstr "כתובת דו×\"ל ×œ× ×ª×§×™×Ÿ/×œ× ×—×•×§×™: שורה ריקה" #: bin/add_members:180 +#, fuzzy msgid "Bad/Invalid email address: %(member)s" msgstr "כתובת·דו×\"ל·ל×·תקין/ל×·חוקי: %(member)s" #: bin/add_members:182 +#, fuzzy msgid "Hostile address (illegal characters): %(member)s" msgstr "כתובת ×ויינת (×ª×•×•×™× ×œ× ×—×•×§×™×™×): %(member)s" @@ -8984,14 +9177,17 @@ msgid "Invited: %(member)s" msgstr "נרש×: %(member)s" #: bin/add_members:187 +#, fuzzy msgid "Subscribed: %(member)s" msgstr "נרש×: %(member)s" #: bin/add_members:237 +#, fuzzy msgid "Bad argument to -w/--welcome-msg: %(arg)s" msgstr "×רגומנט ×œ× ×ª×§×™×Ÿ עבור -w/--welcome-msg:·%(arg)s" #: bin/add_members:244 +#, fuzzy msgid "Bad argument to -a/--admin-notify: %(arg)s" msgstr "×רגומנט·ל×·תקין·עבור - -a/--admin-notify:·%(arg)s" @@ -9006,6 +9202,7 @@ msgstr "" #: bin/add_members:261 bin/config_list:110 bin/export.py:271 bin/find_member:97 #: bin/inject:91 bin/list_admins:90 bin/list_members:252 bin/sync_members:222 #: cron/bumpdigests:86 +#, fuzzy msgid "No such list: %(listname)s" msgstr "×ין כזו רשימה: %(listname)s" @@ -9016,6 +9213,7 @@ msgid "Nothing to do." msgstr "×ין מה לעשות" #: bin/arch:19 +#, fuzzy msgid "" "Rebuild a list's archive.\n" "\n" @@ -9108,6 +9306,7 @@ msgid "listname is required" msgstr "חובה לציין ×©× ×¨×©×™×ž×”" #: bin/arch:143 bin/change_pw:107 bin/config_list:257 +#, fuzzy msgid "" "No such list \"%(listname)s\"\n" "%(e)s" @@ -9116,10 +9315,12 @@ msgstr "" "%(e)s" #: bin/arch:168 +#, fuzzy msgid "Cannot open mbox file %(mbox)s: %(msg)s" msgstr "×œ× ×”×¦×œ×—×ª×™ לפתוח קובץ mbox %(mbox)s: %(msg)s" #: bin/b4b5-archfix:19 +#, fuzzy msgid "" "Fix the MM2.1b4 archives.\n" "\n" @@ -9262,6 +9463,7 @@ msgstr "" " הדפס הודעת עזרה זו, וצ×.\n" #: bin/change_pw:145 +#, fuzzy msgid "Bad arguments: %(strargs)s" msgstr "××¨×’×•×ž× ×˜×™× ×œ× ×ª×§×™× ×™×: %(strargs)s" @@ -9270,14 +9472,17 @@ msgid "Empty list passwords are not allowed" msgstr "סיסמ×ות ריקות לרשימה ×סורות" #: bin/change_pw:181 +#, fuzzy msgid "New %(listname)s password: %(notifypassword)s" msgstr "×”×¡×™×¡×ž× ×”×—×“×©×” של %(listname)s: %(notifypassword)s" #: bin/change_pw:190 +#, fuzzy msgid "Your new %(listname)s list password" msgstr "×”×¡×™×¡×ž× ×”×—×“×©×” שלך ברשימה %(listname)s" #: bin/change_pw:191 +#, fuzzy msgid "" "The site administrator at %(hostname)s has changed the password for your\n" "mailing list %(listname)s. It is now\n" @@ -9304,6 +9509,7 @@ msgstr "" " %(adminurl)s\n" #: bin/check_db:19 +#, fuzzy msgid "" "Check a list's config database file for integrity.\n" "\n" @@ -9378,10 +9584,12 @@ msgid "List:" msgstr "הרשימה:" #: bin/check_db:148 +#, fuzzy msgid " %(file)s: okay" msgstr " %(file)s: OK" #: bin/check_perms:20 +#, fuzzy msgid "" "Check the permissions for the Mailman installation.\n" "\n" @@ -9401,42 +9609,52 @@ msgstr "" "מילולי.\n" #: bin/check_perms:110 +#, fuzzy msgid " checking gid and mode for %(path)s" msgstr " בודק gid ומצב עבור %(path)s" #: bin/check_perms:122 +#, fuzzy msgid "%(path)s bad group (has: %(groupname)s, expected %(MAILMAN_GROUP)s)" msgstr "%(path)s קבוצה שגויה (יש: %(groupname)s, ציפיתי ל-%(MAILMAN_GROUP)s)" #: bin/check_perms:151 +#, fuzzy msgid "directory permissions must be %(octperms)s: %(path)s" msgstr "הרש×ות המחיצה צריכות להיות %(octperms)s: %(path)s" #: bin/check_perms:160 +#, fuzzy msgid "source perms must be %(octperms)s: %(path)s" msgstr "הרש×ות מקור חיובות להיות %(octperms)s: %(path)s" #: bin/check_perms:171 +#, fuzzy msgid "article db files must be %(octperms)s: %(path)s" msgstr "קבצי בסיס × ×ª×•× ×™× ×©×œ ×¤×¨×™×˜×™× ×—×™×•×‘×™× ×œ×”×™×•×ª %(octperms)s: %(path)s" #: bin/check_perms:183 +#, fuzzy msgid "checking mode for %(prefix)s" msgstr "בודק מצב של %(prefix)s" #: bin/check_perms:193 +#, fuzzy msgid "WARNING: directory does not exist: %(d)s" msgstr "×זהרה: המחיצה ×œ× ×§×™×™×ž×ª: %(d)s" #: bin/check_perms:197 +#, fuzzy msgid "directory must be at least 02775: %(d)s" msgstr "על המחיצה להיות לפחות 02775: %(d)s" #: bin/check_perms:209 +#, fuzzy msgid "checking perms on %(private)s" msgstr "בודק הרש×ות של %(private)s" #: bin/check_perms:214 +#, fuzzy msgid "%(private)s must not be other-readable" msgstr "×סור ל-%(private)s להיות קרי××” על ידי ×חרי×" @@ -9459,6 +9677,7 @@ msgid "mbox file must be at least 0660:" msgstr "קובץ mbox צריך להיות לפחות 0660:" #: bin/check_perms:263 +#, fuzzy msgid "%(dbdir)s \"other\" perms must be 000" msgstr "הרש×ות \"×חרי×\" של %(dbdir)s צריכות להיות 000" @@ -9467,26 +9686,32 @@ msgid "checking cgi-bin permissions" msgstr "בודק הרש×ות של cgi-bin" #: bin/check_perms:278 +#, fuzzy msgid " checking set-gid for %(path)s" msgstr " בודק set-gid עבור %(path)s" #: bin/check_perms:282 +#, fuzzy msgid "%(path)s must be set-gid" msgstr "נתיב %(path)s חייב להיות set-gid" #: bin/check_perms:292 +#, fuzzy msgid "checking set-gid for %(wrapper)s" msgstr "בודק set-gid עבור %(wrapper)s" #: bin/check_perms:296 +#, fuzzy msgid "%(wrapper)s must be set-gid" msgstr "%(wrapper)s חייב להיות set-gid" #: bin/check_perms:306 +#, fuzzy msgid "checking permissions on %(pwfile)s" msgstr "בודק הרש×ות של %(pwfile)s" #: bin/check_perms:315 +#, fuzzy msgid "%(pwfile)s permissions must be exactly 0640 (got %(octmode)s)" msgstr "ההרש×ות של %(pwfile)s חייבות להיות בדיוק 0640 (קיבלתי %(octmode)s)" @@ -9495,10 +9720,12 @@ msgid "checking permissions on list data" msgstr "בודק הרש×ות של מידע הרשימות" #: bin/check_perms:348 +#, fuzzy msgid " checking permissions on: %(path)s" msgstr " בודק הרש×ות ב: %(path)s" #: bin/check_perms:356 +#, fuzzy msgid "file permissions must be at least 660: %(path)s" msgstr "הרש×ות ×§×‘×¦×™× ×—×™×™×‘×•×ª להיות לפחות 660: %(path)s" @@ -9582,6 +9809,7 @@ msgid "Unix-From line changed: %(lineno)d" msgstr "שונתה שורת From של Unix: %(lineno)d" #: bin/cleanarch:111 +#, fuzzy msgid "Bad status number: %(arg)s" msgstr "מספר סט×טוס ×œ× ×ª×§×™×Ÿ: %(arg)s" @@ -9722,10 +9950,12 @@ msgid " original address removed:" msgstr " הכתובת המקורית נמחקה:" #: bin/clone_member:202 +#, fuzzy msgid "Not a valid email address: %(toaddr)s" msgstr "כתובת דו×\"ל ×œ× ×ª×§×™× ×”: %(toaddr)s" #: bin/clone_member:215 +#, fuzzy msgid "" "Error opening list \"%(listname)s\", skipping.\n" "%(e)s" @@ -9831,6 +10061,7 @@ msgstr "" "\n" #: bin/config_list:118 +#, fuzzy msgid "" "# -*- python -*-\n" "# -*- coding: %(charset)s -*-\n" @@ -9851,22 +10082,27 @@ msgid "legal values are:" msgstr "×¢×¨×›×™× ×—×•×§×™×™×:" #: bin/config_list:270 +#, fuzzy msgid "attribute \"%(k)s\" ignored" msgstr "×ž×ª×¢×œ× ×ž×ª×›×•× ×” \"%(k)s\"" #: bin/config_list:273 +#, fuzzy msgid "attribute \"%(k)s\" changed" msgstr "שונתה תכונה \"%(k)s\"" #: bin/config_list:279 +#, fuzzy msgid "Non-standard property restored: %(k)s" msgstr "שוחזר מ×פיין ×œ× ×¡×˜× ×“×¨×˜×™: %(k)s" #: bin/config_list:288 +#, fuzzy msgid "Invalid value for property: %(k)s" msgstr "ערך ×œ× ×—×•×§×™ עבור מ×פיין: %(k)s" #: bin/config_list:291 +#, fuzzy msgid "Bad email address for option %(k)s: %(v)s" msgstr "כתובת דו×\"ל ×œ× ×—×•×§×™×ª עבור %(k)s: %(v)s" @@ -9931,18 +10167,22 @@ msgstr "" " ×ל תדפיס הודעות סט×טוס.\n" #: bin/discard:94 +#, fuzzy msgid "Ignoring non-held message: %(f)s" msgstr "×ž×ª×¢×œ× ×ž×ž×¡×¨ ×œ× ×ž×•×—×–×§: %(f)s" #: bin/discard:100 +#, fuzzy msgid "Ignoring held msg w/bad id: %(f)s" msgstr "×ž×ª×¢×œ× ×ž×ž×¡×¨ מוחזק בעל זיהוי ×œ× ×ª×§×™×Ÿ: %(f)s" #: bin/discard:112 +#, fuzzy msgid "Discarded held msg #%(id)s for list %(listname)s" msgstr "מוחק מסר מוחזק #%(id)s ברשימה %(listname)s" #: bin/dumpdb:19 +#, fuzzy msgid "" "Dump the contents of any Mailman `database' file.\n" "\n" @@ -10011,6 +10251,7 @@ msgid "No filename given." msgstr "×œ× ×¦×™×•×Ÿ ×©× ×§×•×‘×¥." #: bin/dumpdb:108 +#, fuzzy msgid "Bad arguments: %(pargs)s" msgstr "××¨×’×•×ž× ×˜×™× ×œ× ×—×•×§×™×™×: %(pargs)s" @@ -10029,6 +10270,7 @@ msgid "[----- end %(typename)s file -----]" msgstr "[----- סוף-קובץ-משמר -----]" #: bin/dumpdb:142 +#, fuzzy msgid "<----- start object %(cnt)s ----->" msgstr "<----- תחילת ×וביקט %(cnt)s ----->" @@ -10218,8 +10460,8 @@ msgstr "" "מתחו×\n" " וירטו×לי ×חד ×œ×ª×—×•× ×•×™×¨×˜×•×לי ×חר.\n" "\n" -" בלי ×פשרות זו, ×ž×©×ª×ž×©×™× ×‘×¢×¨×›×™ ברירת המחדל עבור web_page_url·ו-" -"·host_name\n" +" בלי ×פשרות זו, ×ž×©×ª×ž×©×™× ×‘×¢×¨×›×™ ברירת המחדל עבור " +"web_page_url·ו-·host_name\n" "\n" " -v / --verbose\n" " הדפס ×ת פעולות ×”×צווה.\n" @@ -10236,6 +10478,7 @@ msgid "Setting web_page_url to: %(web_page_url)s" msgstr "קובע ×ת web_page_url ל-: %(web_page_url)s" #: bin/fix_url.py:88 +#, fuzzy msgid "Setting host_name to: %(mailhost)s" msgstr "קובע ×ת host_name ל-: %(mailhost)s" @@ -10324,6 +10567,7 @@ msgstr "" "סטנדרטי.\n" #: bin/inject:84 +#, fuzzy msgid "Bad queue directory: %(qdir)s" msgstr "מחיצת טור ×œ× ×—×•×§×™×ª: %(qdir)s" @@ -10332,6 +10576,7 @@ msgid "A list name is required" msgstr "דרוש ×©× ×¨×©×™×ž×”" #: bin/list_admins:20 +#, fuzzy msgid "" "List all the owners of a mailing list.\n" "\n" @@ -10376,6 +10621,7 @@ msgstr "" "×חת בשורת הפקודה.\n" #: bin/list_admins:97 +#, fuzzy msgid "List: %(listname)s, \tOwners: %(owners)s" msgstr "רשימה: %(listname)s, \tבעלי×: %(owners)s" @@ -10557,10 +10803,12 @@ msgstr "" "כל ×ינדקציה ב×שר סט×טוס הכתובת.\n" #: bin/list_members:198 +#, fuzzy msgid "Bad --nomail option: %(why)s" msgstr "×פשרות --nomail ×œ× ×—×•×§×™: %(why)s" #: bin/list_members:209 +#, fuzzy msgid "Bad --digest option: %(kind)s" msgstr "×פשרות --digest ×œ× ×—×•×§×™: %(kind)s" @@ -10574,6 +10822,7 @@ msgid "Could not open file for writing:" msgstr "×œ× ×”×¦×œ×—×ª×™ לפתוח ×ת הקובץ לכתיבה:" #: bin/list_owners:20 +#, fuzzy msgid "" "List the owners of a mailing list, or all mailing lists.\n" "\n" @@ -10626,6 +10875,7 @@ msgid "" msgstr "" #: bin/mailmanctl:20 +#, fuzzy msgid "" "Primary start-up and shutdown script for Mailman's qrunner daemon.\n" "\n" @@ -10807,6 +11057,7 @@ msgstr "" "שנכתב ××œ×™×”× ×ž×¡×¨.\n" #: bin/mailmanctl:152 +#, fuzzy msgid "PID unreadable in: %(pidfile)s" msgstr "×”-PID ×œ× ×§×¨×™× ×‘:- %(pidfile)s" @@ -10815,6 +11066,7 @@ msgid "Is qrunner even running?" msgstr "×”×× qrunner רץ בכלל?" #: bin/mailmanctl:160 +#, fuzzy msgid "No child with pid: %(pid)s" msgstr "×ין ילד ×¢× pid: %(pid)s" @@ -10841,6 +11093,7 @@ msgstr "" "mailmanctl ×¢× ×“×’×œ -s.\n" #: bin/mailmanctl:233 +#, fuzzy msgid "" "The master qrunner lock could not be acquired, because it appears as if " "some\n" @@ -10866,10 +11119,12 @@ msgstr "" "יוצ×." #: bin/mailmanctl:280 cron/mailpasswds:119 +#, fuzzy msgid "Site list is missing: %(sitelistname)s" msgstr "רשימת ×”×תר חסרה: %(sitelistname)s" #: bin/mailmanctl:305 +#, fuzzy msgid "Run this program as root or as the %(name)s user, or use -u." msgstr "הרץ תכנית זו תחת root ×ו תחת המשתמש %(name)s, ×ו השתמש ב- -u." @@ -10878,6 +11133,7 @@ msgid "No command given." msgstr "×œ× × ×™×ª× ×” פקודה." #: bin/mailmanctl:339 +#, fuzzy msgid "Bad command: %(command)s" msgstr "פקודה ×œ× ×—×•×§×™×ª: %(command)s" @@ -10902,6 +11158,7 @@ msgid "Starting Mailman's master qrunner." msgstr "מ×תחל ×ת ×”- qrunner ר×שי של דוור." #: bin/mmsitepass:19 +#, fuzzy msgid "" "Set the site password, prompting from the terminal.\n" "\n" @@ -10953,6 +11210,7 @@ msgid "list creator" msgstr "יוצר הרשימה" #: bin/mmsitepass:86 +#, fuzzy msgid "New %(pwdesc)s password: " msgstr "×”×¡×™×¡×ž× ×”×—×“×©×” של %(pwdesc)s" @@ -11014,8 +11272,8 @@ msgstr "" "×פשריות:\n" " -o קובץ\n" " --output_file=קובץ\n" -" ציין ×ת קובץ הפלט ×ליו יש לכתוב. ×× ×—×¡×¨, הפלט ייכתב לקובץ ×‘×©× ×©×-" -"קובץ.po\n" +" ציין ×ת קובץ הפלט ×ליו יש לכתוב. ×× ×—×¡×¨, הפלט ייכתב לקובץ ×‘×©× " +"ש×-קובץ.po\n" " (מבוסס על ×©× ×§×•×‘×¥ הקלט).\n" "\n" " -h / --help\n" @@ -11206,6 +11464,7 @@ msgstr "" "×©×™× ×œ×‘ ששמות רשימה מ×ולצות ל×ותיות קטנות.\n" #: bin/newlist:162 +#, fuzzy msgid "Unknown language: %(lang)s" msgstr "שפה ×œ× ×ž×•×›×¨×ª: %(lang)s" @@ -11218,6 +11477,7 @@ msgid "Enter the email of the person running the list: " msgstr "הכנס ×ת הדו×\"ל שה××“× ×©×ž×¤×¢×™×œ ×ת הרשימה: " #: bin/newlist:193 +#, fuzzy msgid "Initial %(listname)s password: " msgstr "×¡×™×¡×ž× ×¨×שונית של %(listname)s: " @@ -11227,15 +11487,17 @@ msgstr "סיסמת הרשימה ×œ× ×™×›×•×œ×” להיות ריקה" #: bin/newlist:220 msgid "" -" - owner addresses need to be fully-qualified names like \"owner@example.com" -"\", not just \"owner\"." +" - owner addresses need to be fully-qualified names like \"owner@example." +"com\", not just \"owner\"." msgstr "" #: bin/newlist:244 +#, fuzzy msgid "Hit enter to notify %(listname)s owner..." msgstr "לחץ \"×נטר\" כדי להודיע ×œ×‘×¢×œ×™× ×©×œ %(listname)s..." #: bin/qrunner:20 +#, fuzzy msgid "" "Run one or more qrunners, once or repeatedly.\n" "\n" @@ -11360,6 +11622,7 @@ msgstr "" "ל×יתור תקלות ×× ×ž×¨×™×¦×™× ×ותו בנפרד.\n" #: bin/qrunner:179 +#, fuzzy msgid "%(name)s runs the %(runnername)s qrunner" msgstr "%(name)s מריץ ×ת ×”-qrunner %(runnername)s" @@ -11372,6 +11635,7 @@ msgid "No runner name given." msgstr "×œ× × ×™×ª×Ÿ ×©× ×©×œ runner." #: bin/rb-archfix:21 +#, fuzzy msgid "" "Reduce disk space usage for Pipermail archives.\n" "\n" @@ -11514,18 +11778,22 @@ msgstr "" " כתובת1 ... הנן כתובות נוספות למחיקה.\n" #: bin/remove_members:156 +#, fuzzy msgid "Could not open file for reading: %(filename)s." msgstr "×œ× ×”×¦×œ×—×ª×™ לפתוח ×ת הקובץ לקרי××”: %(filename)s." #: bin/remove_members:163 +#, fuzzy msgid "Error opening list %(listname)s... skipping." msgstr "שגי××” בפתיחת הרשימה %(listname)s...מדלג." #: bin/remove_members:173 +#, fuzzy msgid "No such member: %(addr)s" msgstr "×ין ×›×–×” מנוי: %(addr)s" #: bin/remove_members:178 +#, fuzzy msgid "User `%(addr)s' removed from list: %(listname)s." msgstr "כתובת המנוי `%(addr)s' נמחק מהרשימה: %(listname)s." @@ -11564,10 +11832,12 @@ msgstr "" " הצג ×ת פעולות ×”×צווה.\n" #: bin/reset_pw.py:77 +#, fuzzy msgid "Changing passwords for list: %(listname)s" msgstr "משנה סיסמ×ות עבור רשימה: %(listname)s" #: bin/reset_pw.py:83 +#, fuzzy msgid "New password for member %(member)40s: %(randompw)s" msgstr "×¡×™×¡×ž× ×—×“×©×” עבור מנוי %(member)40s: %(randompw)s" @@ -11611,18 +11881,22 @@ msgstr "" "\n" #: bin/rmlist:73 bin/rmlist:76 +#, fuzzy msgid "Removing %(msg)s" msgstr "מוחק %(msg)s" #: bin/rmlist:81 +#, fuzzy msgid "%(listname)s %(msg)s not found as %(filename)s" msgstr "×œ× × ×ž×¦× %(listname)s %(msg)s ×›-%(filename)s" #: bin/rmlist:105 +#, fuzzy msgid "No such list (or list already deleted): %(listname)s" msgstr "×ין כזו רשימה (×ו שנמחקה כבר): %(listname)s" #: bin/rmlist:108 +#, fuzzy msgid "No such list: %(listname)s. Removing its residual archives." msgstr "×ין כזו רשימה: %(listname)s. מוחק ×ת ש×רית ×רכיוני×." @@ -11682,6 +11956,7 @@ msgstr "" "דוגמ×: show_qfiles qfiles/shunt/*.pck\n" #: bin/sync_members:19 +#, fuzzy msgid "" "Synchronize a mailing list's membership with a flat file.\n" "\n" @@ -11811,6 +12086,7 @@ msgstr "" " חובה. ×–×” ×ž×¦×™×™× ×ת הרשימה שיש לסנכרן.\n" #: bin/sync_members:115 +#, fuzzy msgid "Bad choice: %(yesno)s" msgstr "בחירה ×œ× ×—×•×§×™×ª: %(yesno)s" @@ -11827,6 +12103,7 @@ msgid "No argument to -f given" msgstr "× × × ×™×ª× ×” ×רגומנט ל- f-" #: bin/sync_members:172 +#, fuzzy msgid "Illegal option: %(opt)s" msgstr "×פשרות ×œ× ×—×•×§×™×ª: %(opt)s" @@ -11839,6 +12116,7 @@ msgid "Must have a listname and a filename" msgstr "חייב לציין ×©× ×§×•×‘×¥ ×•×©× ×¨×©×™×ž×”" #: bin/sync_members:191 +#, fuzzy msgid "Cannot read address file: %(filename)s: %(msg)s" msgstr "×œ× ×™×›×•×œ ×œ×§×¨×•× ×ת קובץ הכתובות: %(filename)s: %(msg)s" @@ -11855,14 +12133,17 @@ msgid "You must fix the preceding invalid addresses first." msgstr "ר×שית, עליך לתקן ×ת הכתובת ×”×œ× ×—×•×§×™×ª הקודמת." #: bin/sync_members:264 +#, fuzzy msgid "Added : %(s)s" msgstr "הוספתי : %(s)s" #: bin/sync_members:289 +#, fuzzy msgid "Removed: %(s)s" msgstr "מחקתי: %(s)s" #: bin/transcheck:19 +#, fuzzy msgid "" "\n" "Check a given Mailman translation, making sure that variables and\n" @@ -11970,6 +12251,7 @@ msgstr "" "shunt.\n" #: bin/unshunt:85 +#, fuzzy msgid "" "Cannot unshunt message %(filebase)s, skipping:\n" "%(e)s" @@ -11978,6 +12260,7 @@ msgstr "" "%(e)s" #: bin/update:20 +#, fuzzy msgid "" "Perform all necessary upgrades.\n" "\n" @@ -12014,14 +12297,17 @@ msgstr "" "יותר. ×”×™× ×ž×›×™×¨×” גירס×ות ×חרה עד ×’×™×¨×¡× 1.0b4·(?).\n" #: bin/update:107 +#, fuzzy msgid "Fixing language templates: %(listname)s" msgstr "מתקן תבניות שפה: %(listname)s" #: bin/update:196 bin/update:711 +#, fuzzy msgid "WARNING: could not acquire lock for list: %(listname)s" msgstr "×זהרה: ×œ× ×”×©×’×ª×™ מנעול לרשימה: %(listname)s" #: bin/update:215 +#, fuzzy msgid "Resetting %(n)s BYBOUNCEs disabled addrs with no bounce info" msgstr "" "מ×פס ×ת הכתובת ×”×œ× ×¤×¢×™×œ×” על ידי BYBOUNCEs של ·%(n)s על ידי מחיקת המידע ×ודות " @@ -12040,6 +12326,7 @@ msgstr "" "×”×©× ×©×œ×• ל- %(mbox_dir)s.tmp וממשיך." #: bin/update:255 +#, fuzzy msgid "" "\n" "%(listname)s has both public and private mbox archives. Since this list\n" @@ -12089,6 +12376,7 @@ msgid "- updating old private mbox file" msgstr "- מעדכן קובץ mbox פרטי ישן" #: bin/update:295 +#, fuzzy msgid "" " unknown file in the way, moving\n" " %(o_pri_mbox_file)s\n" @@ -12105,6 +12393,7 @@ msgid "- updating old public mbox file" msgstr "- מעדכן קובץ mbox ציבורי ישן" #: bin/update:317 +#, fuzzy msgid "" " unknown file in the way, moving\n" " %(o_pub_mbox_file)s\n" @@ -12133,18 +12422,22 @@ msgid "- %(o_tmpl)s doesn't exist, leaving untouched" msgstr "- %(o_tmpl)s ×œ× ×§×™×™×, ×œ× × ×•×’×¢" #: bin/update:396 +#, fuzzy msgid "removing directory %(src)s and everything underneath" msgstr "מוחק מחיצה %(src)s וכל מה שיש מתחתיה" #: bin/update:399 +#, fuzzy msgid "removing %(src)s" msgstr "מוחק %(src)s" #: bin/update:403 +#, fuzzy msgid "Warning: couldn't remove %(src)s -- %(rest)s" msgstr "×זהרה: ×œ× ×”×¦×œ×—×ª×™ למחוק ×ת %(src)s -- %(rest)s" #: bin/update:408 +#, fuzzy msgid "couldn't remove old file %(pyc)s -- %(rest)s" msgstr "×œ× ×”×¦×œ×—×ª×™ למחוק קובץ ישן %(pyc)s -- %(rest)s" @@ -12153,14 +12446,17 @@ msgid "updating old qfiles" msgstr "מעדכן קבצי qfile-×™× ×™×©× ×™×" #: bin/update:455 +#, fuzzy msgid "Warning! Not a directory: %(dirpath)s" msgstr "×זהרה! ××™× × ×” מחיצה: %(dirpath)s" #: bin/update:530 +#, fuzzy msgid "message is unparsable: %(filebase)s" msgstr "×œ× × ×™×ª×Ÿ לחלץ ×ת המילי×: %(filebase)s" #: bin/update:544 +#, fuzzy msgid "Warning! Deleting empty .pck file: %(pckfile)s" msgstr "×זהרה! מוחק קובץ .pck ריק: %(pckfile)s" @@ -12173,10 +12469,12 @@ msgid "Updating Mailman 2.1.4 pending.pck database" msgstr "מעדכן ×ת בסיס נתוני pending.pck של דוור 2.1.4" #: bin/update:598 +#, fuzzy msgid "Ignoring bad pended data: %(key)s: %(val)s" msgstr "×ž×ª×¢×œ× ×ž× ×ª×•× ×™× ×‘×”×ž×ª× ×”: %(key)s: %(val)s" #: bin/update:614 +#, fuzzy msgid "WARNING: Ignoring duplicate pending ID: %(id)s." msgstr "×זהרה! ×ž×ª×¢×œ× ×ž×–×™×”×•×™ ID ממתין כפול: %(id)s." @@ -12201,6 +12499,7 @@ msgid "done" msgstr "בוצע" #: bin/update:691 +#, fuzzy msgid "Updating mailing list: %(listname)s" msgstr "מעדכן רשימת דיוור: %(listname)s" @@ -12262,6 +12561,7 @@ msgid "No updates are necessary." msgstr "×œ× ×“×¨×•×© עדכון" #: bin/update:796 +#, fuzzy msgid "" "Downgrade detected, from version %(hexlversion)s to version %(hextversion)s\n" "This is probably not safe.\n" @@ -12272,10 +12572,12 @@ msgstr "" "יוצ×." #: bin/update:801 +#, fuzzy msgid "Upgrading from version %(hexlversion)s to %(hextversion)s" msgstr "מעדכן ×ž×’×™×¨×¡× %(hexlversion)s ×œ×’×™×¨×¡× %(hextversion)s" #: bin/update:810 +#, fuzzy msgid "" "\n" "ERROR:\n" @@ -12551,6 +12853,7 @@ msgstr "" " " #: bin/withlist:175 +#, fuzzy msgid "Unlocking (but not saving) list: %(listname)s" msgstr "פותח נעילה (×בל ×œ× ×©×•×ž×¨) של הרשימה: %(listname)s" @@ -12559,6 +12862,7 @@ msgid "Finalizing" msgstr "מסיי×" #: bin/withlist:188 +#, fuzzy msgid "Loading list %(listname)s" msgstr "טוען רשימה %(listname)s" @@ -12571,6 +12875,7 @@ msgid "(unlocked)" msgstr "(×œ× × ×¢×•×œ)" #: bin/withlist:197 +#, fuzzy msgid "Unknown list: %(listname)s" msgstr "רשימה ×œ× ×ž×•×›×¨×ª: %(listname)s" @@ -12583,18 +12888,22 @@ msgid "--all requires --run" msgstr "--all מחייב --run" #: bin/withlist:266 +#, fuzzy msgid "Importing %(module)s..." msgstr "×ž×™×™×‘× %(module)s..." #: bin/withlist:270 +#, fuzzy msgid "Running %(module)s.%(callable)s()..." msgstr "מריץ ×ת ×”-%(callable)s() של %(module)s..." #: bin/withlist:291 +#, fuzzy msgid "The variable `m' is the %(listname)s MailList instance" msgstr "המשתנה `m' ×”×•× ×”×¨×¦×ª MailList של %(listname)s" #: cron/bumpdigests:19 +#, fuzzy msgid "" "Increment the digest volume number and reset the digest number to one.\n" "\n" @@ -12621,6 +12930,7 @@ msgstr "" "bumped.\n" #: cron/checkdbs:20 +#, fuzzy msgid "" "Check for pending admin requests and mail the list owners if necessary.\n" "\n" @@ -12648,10 +12958,12 @@ msgstr "" "\n" #: cron/checkdbs:121 +#, fuzzy msgid "%(count)d %(realname)s moderator request(s) waiting" msgstr "ממתינה(ות) %(count)d בקשות פיקוח של %(realname)s" #: cron/checkdbs:124 +#, fuzzy msgid "%(realname)s moderator request check result" msgstr "תוצ×ות הבדיקה של בקשות הפיקוח של %(realname)s" @@ -12673,6 +12985,7 @@ msgstr "" "×ž×¡×¨×™× ×ž×ž×ª×™× ×™×:" #: cron/checkdbs:169 +#, fuzzy msgid "" "From: %(sender)s on %(date)s\n" "Subject: %(subject)s\n" @@ -12704,6 +13017,7 @@ msgid "" msgstr "" #: cron/disabled:20 +#, fuzzy msgid "" "Process disabled members, recommended once per day.\n" "\n" @@ -12821,6 +13135,7 @@ msgstr "" "\n" #: cron/mailpasswds:19 +#, fuzzy msgid "" "Send password reminders for all lists to all users.\n" "\n" @@ -12873,10 +13188,12 @@ msgid "Password // URL" msgstr "// קישור לסיסמ×" #: cron/mailpasswds:222 +#, fuzzy msgid "%(host)s mailing list memberships reminder" msgstr "תזכורות מנוי לרשימת דיוור של %(host)s" #: cron/nightly_gzip:19 +#, fuzzy msgid "" "Re-generate the Pipermail gzip'd archive flat files.\n" "\n" @@ -12972,8 +13289,8 @@ msgstr "" #~ " applied" #~ msgstr "" #~ "הטקסט שיש לכתוב בכל\n" -#~ " הודעת דחייה ×ל ×ž× ×•×™×™× ×ž×¤×•×§×—×™× ×©×©×•×œ×—×™× ×ל רשימה זו." #~ msgid "" diff --git a/messages/hr/LC_MESSAGES/mailman.po b/messages/hr/LC_MESSAGES/mailman.po index e466c85e..4288f144 100755 --- a/messages/hr/LC_MESSAGES/mailman.po +++ b/messages/hr/LC_MESSAGES/mailman.po @@ -66,10 +66,12 @@ msgid "

                      Currently, there are no archives.

                      " msgstr "

                      Trenutno nema arhive.

                      " #: Mailman/Archiver/HyperArch.py:821 +#, fuzzy msgid "Gzip'd Text%(sz)s" msgstr "Gzipovani Tekst%(sz)s" #: Mailman/Archiver/HyperArch.py:826 +#, fuzzy msgid "Text%(sz)s" msgstr "Tekst%(sz)s" @@ -142,18 +144,22 @@ msgid "Third" msgstr "Treæi" #: Mailman/Archiver/HyperArch.py:938 +#, fuzzy msgid "%(ord)s quarter %(year)i" msgstr "%(ord)s kvartal %(year)i" #: Mailman/Archiver/HyperArch.py:945 +#, fuzzy msgid "%(month)s %(year)i" msgstr "%(month)s %(year)i" #: Mailman/Archiver/HyperArch.py:950 +#, fuzzy msgid "The Week Of Monday %(day)i %(month)s %(year)i" msgstr "Tjedan Ponedjeljka %(day)i %(month)s %(year)i" #: Mailman/Archiver/HyperArch.py:954 +#, fuzzy msgid "%(day)i %(month)s %(year)i" msgstr "%(day)i %(month)s %(year)i" @@ -162,10 +168,12 @@ msgid "Computing threaded index\n" msgstr "Izraèunavam indekse diskusija\n" #: Mailman/Archiver/HyperArch.py:1319 +#, fuzzy msgid "Updating HTML for article %(seq)s" msgstr "Osvje¾avam HTML za èlanak %(seq)s" #: Mailman/Archiver/HyperArch.py:1326 +#, fuzzy msgid "article file %(filename)s is missing!" msgstr "datoteka èlanka %(filename)s nedostaje!" @@ -182,6 +190,7 @@ msgid "Pickling archive state into " msgstr "Pakiram stanje arhive u " #: Mailman/Archiver/pipermail.py:453 +#, fuzzy msgid "Updating index files for archive [%(archive)s]" msgstr "Osvje¾avam indeksne datoteke za arhivu [%(archive)s]" @@ -190,6 +199,7 @@ msgid " Thread" msgstr " Diskusija" #: Mailman/Archiver/pipermail.py:597 +#, fuzzy msgid "#%(counter)05d %(msgid)s" msgstr "#%(counter)05d %(msgid)s" @@ -227,6 +237,7 @@ msgid "disabled address" msgstr "onemogucen" #: Mailman/Bouncer.py:304 +#, fuzzy msgid " The last bounce received from you was dated %(date)s" msgstr " Datum va¹eg posljednjeg odbijanja %(date)s" @@ -254,6 +265,7 @@ msgstr "Administrator" #: Mailman/Cgi/options.py:101 Mailman/Cgi/private.py:108 #: Mailman/Cgi/rmlist.py:75 Mailman/Cgi/roster.py:59 #: Mailman/Cgi/subscribe.py:67 +#, fuzzy msgid "No such list %(safelistname)s" msgstr "Takva lista ne postoji %(safelistname)s" @@ -325,6 +337,7 @@ msgstr "" " problem.%(rm)r" #: Mailman/Cgi/admin.py:268 +#, fuzzy msgid "%(hostname)s mailing lists - Admin Links" msgstr "%(hostname)s mailing liste - Admin Linkovi" @@ -337,6 +350,7 @@ msgid "Mailman" msgstr "Mailman" #: Mailman/Cgi/admin.py:310 +#, fuzzy msgid "" "

                      There currently are no publicly-advertised %(mailmanlink)s\n" " mailing lists on %(hostname)s." @@ -345,6 +359,7 @@ msgstr "" " mailing lista na %(hostname)s." #: Mailman/Cgi/admin.py:316 +#, fuzzy msgid "" "

                      Below is the collection of publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s. Click on a list\n" @@ -359,6 +374,7 @@ msgid "right " msgstr "u redu " #: Mailman/Cgi/admin.py:325 +#, fuzzy msgid "" "To visit the administrators configuration page for an\n" " unadvertised list, open a URL similar to this one, but with a '/' " @@ -403,6 +419,7 @@ msgid "No valid variable name found." msgstr "Ispravno ime varijable nije pronaðeno." #: Mailman/Cgi/admin.py:395 +#, fuzzy msgid "" "%(realname)s Mailing list Configuration Help\n" "
                      %(varname)s Option" @@ -411,6 +428,7 @@ msgstr "" "
                      %(varname)s Opcija" #: Mailman/Cgi/admin.py:402 +#, fuzzy msgid "Mailman %(varname)s List Option Help" msgstr "Mailmanova pomoæ za opcije liste %(varname)s" @@ -431,14 +449,17 @@ msgstr "" " " #: Mailman/Cgi/admin.py:431 +#, fuzzy msgid "return to the %(categoryname)s options page." msgstr "vratite se na %(categoryname)s stranicu sa opcijama." #: Mailman/Cgi/admin.py:446 +#, fuzzy msgid "%(realname)s Administration (%(label)s)" msgstr "%(realname)s Administracija (%(label)s)" #: Mailman/Cgi/admin.py:447 +#, fuzzy msgid "%(realname)s mailing list administration
                      %(label)s Section" msgstr "Administracija mailing liste %(realname)s
                      Sekcija %(label)s" @@ -521,6 +542,7 @@ msgid "Value" msgstr "Vrijednost" #: Mailman/Cgi/admin.py:668 +#, fuzzy msgid "" "Badly formed options entry:\n" " %(record)s" @@ -621,10 +643,12 @@ msgid "Move rule down" msgstr "Pomakni pravilo prema dole" #: Mailman/Cgi/admin.py:870 +#, fuzzy msgid "
                      (Edit %(varname)s)" msgstr "
                      (Uredi %(varname)s)" #: Mailman/Cgi/admin.py:872 +#, fuzzy msgid "
                      (Details for %(varname)s)" msgstr "
                      (Detalji za %(varname)s)" @@ -666,6 +690,7 @@ msgid "(help)" msgstr "(pomoæ)" #: Mailman/Cgi/admin.py:930 +#, fuzzy msgid "Find member %(link)s:" msgstr "Pronaði èlana %(link)s:" @@ -678,10 +703,12 @@ msgid "Bad regular expression: " msgstr "Pogre¹an regular expression:" #: Mailman/Cgi/admin.py:1013 +#, fuzzy msgid "%(allcnt)s members total, %(membercnt)s shown" msgstr "%(allcnt)s èlanova ukupno, %(membercnt)s prikazano" #: Mailman/Cgi/admin.py:1016 +#, fuzzy msgid "%(allcnt)s members total" msgstr "%(allcnt)s èlanova ukupno" @@ -872,6 +899,7 @@ msgstr "" " èlan skupa koji je prikazan dolje:
                      " #: Mailman/Cgi/admin.py:1221 +#, fuzzy msgid "from %(start)s to %(end)s" msgstr "od %(start)s do %(end)s" @@ -1009,6 +1037,7 @@ msgid "Change list ownership passwords" msgstr "Promjeni lozinku vlasnika liste" #: Mailman/Cgi/admin.py:1364 +#, fuzzy msgid "" "The list administrators are the people who have ultimate control " "over\n" @@ -1181,8 +1210,9 @@ msgid "%(schange_to)s is already a member" msgstr " je veæ èlan" #: Mailman/Cgi/admin.py:1620 +#, fuzzy msgid "%(schange_to)s matches banned pattern %(spat)s" -msgstr "" +msgstr " je veæ èlan" #: Mailman/Cgi/admin.py:1622 msgid "Address %(schange_from)s changed to %(schange_to)s" @@ -1222,6 +1252,7 @@ msgid "Not subscribed" msgstr "Nije pretplaæen" #: Mailman/Cgi/admin.py:1773 +#, fuzzy msgid "Ignoring changes to deleted member: %(user)s" msgstr "Ignoriram promjene obrisanog èlana: %(user)s" @@ -1234,10 +1265,12 @@ msgid "Error Unsubscribing:" msgstr "Gre¹ka kod Odjavljivanja:" #: Mailman/Cgi/admindb.py:235 Mailman/Cgi/admindb.py:248 +#, fuzzy msgid "%(realname)s Administrative Database" msgstr "%(realname)s Administracijska Baza Podataka" #: Mailman/Cgi/admindb.py:238 +#, fuzzy msgid "%(realname)s Administrative Database Results" msgstr "%(realname)s Rezultati Administracijske Baze Podataka" @@ -1266,6 +1299,7 @@ msgid "Discard all messages marked Defer" msgstr "" #: Mailman/Cgi/admindb.py:298 +#, fuzzy msgid "all of %(esender)s's held messages." msgstr "sve od %(esender)s's zadr¾anih poruka." @@ -1286,6 +1320,7 @@ msgid "list of available mailing lists." msgstr "lista dostupnih mailing lista." #: Mailman/Cgi/admindb.py:362 +#, fuzzy msgid "You must specify a list name. Here is the %(link)s" msgstr "Morate specificirati naziv liste. Ovdje je %(link)s" @@ -1368,6 +1403,7 @@ msgid "The sender is now a member of this list" msgstr "Po¹iljatelj je sada èlan ove liste" #: Mailman/Cgi/admindb.py:568 +#, fuzzy msgid "Add %(esender)s to one of these sender filters:" msgstr "Dodaj %(esender)s u jedan od ovih filtera po¹iljatelja:" @@ -1388,6 +1424,7 @@ msgid "Rejects" msgstr "Odbijeno" #: Mailman/Cgi/admindb.py:584 +#, fuzzy msgid "" "Ban %(esender)s from ever subscribing to this\n" " mailing list" @@ -1404,6 +1441,7 @@ msgstr "" " ili mo¾ete " #: Mailman/Cgi/admindb.py:591 +#, fuzzy msgid "view all messages from %(esender)s" msgstr "pogledajte sve poruke od %(esender)s" @@ -1535,6 +1573,7 @@ msgstr "" " odbaèen." #: Mailman/Cgi/confirm.py:178 +#, fuzzy msgid "System error, bad content: %(content)s" msgstr "Sistemska gre¹ka, krivi sadr¾aj: %(content)s" @@ -1572,6 +1611,7 @@ msgid "Confirm subscription request" msgstr "Potvrdi zahtjev za pretplatom" #: Mailman/Cgi/confirm.py:259 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " subscription request to the mailing list %(listname)s. Your\n" @@ -1610,6 +1650,7 @@ msgstr "" " zahtjev." #: Mailman/Cgi/confirm.py:275 +#, fuzzy msgid "" "Your confirmation is required in order to continue with\n" " the subscription request to the mailing list %(listname)s.\n" @@ -1667,6 +1708,7 @@ msgid "Preferred language:" msgstr "Preferirani jezik:" #: Mailman/Cgi/confirm.py:317 +#, fuzzy msgid "Subscribe to list %(listname)s" msgstr "Pretplatite se na listu %(listname)s" @@ -1683,6 +1725,7 @@ msgid "Awaiting moderator approval" msgstr "Oèekujem odobrenje moderatora" #: Mailman/Cgi/confirm.py:386 +#, fuzzy msgid "" " You have successfully confirmed your subscription request to " "the\n" @@ -1740,6 +1783,7 @@ msgid "Subscription request confirmed" msgstr "Zahtjev za pretplatom je potvrðen" #: Mailman/Cgi/confirm.py:418 +#, fuzzy msgid "" " You have successfully confirmed your subscription request for\n" " \"%(addr)s\" to the %(listname)s mailing list. A separate\n" @@ -1770,6 +1814,7 @@ msgid "Unsubscription request confirmed" msgstr "Zahtjev za odjavom je povtrðen" #: Mailman/Cgi/confirm.py:469 +#, fuzzy msgid "" " You have successfully unsubscribed from the %(listname)s " "mailing\n" @@ -1791,6 +1836,7 @@ msgid "Not available" msgstr "Nije dostupno" #: Mailman/Cgi/confirm.py:498 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " unsubscription request from the mailing list %(listname)s. " @@ -1862,6 +1908,7 @@ msgid "Change of address request confirmed" msgstr "Zahtjev za promjenom adrese je potvrðen" #: Mailman/Cgi/confirm.py:571 +#, fuzzy msgid "" " You have successfully changed your address on the %(listname)s\n" " mailing list from %(oldaddr)s to %(newaddr)s. " @@ -1884,6 +1931,7 @@ msgid "globally" msgstr "opæenito" #: Mailman/Cgi/confirm.py:605 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " change of address request for the mailing list %(listname)s. " @@ -1946,6 +1994,7 @@ msgid "Sender discarded message via web." msgstr "Po¹iljatelj je odbacio poruku koristeæi web." #: Mailman/Cgi/confirm.py:674 +#, fuzzy msgid "" "The held message with the Subject:\n" " header %(subject)s could not be found. The most " @@ -1966,6 +2015,7 @@ msgid "Posted message canceled" msgstr "Poslana poruka je zanemarena" #: Mailman/Cgi/confirm.py:685 +#, fuzzy msgid "" " You have successfully canceled the posting of your message with\n" " the Subject: header %(subject)s to the mailing list\n" @@ -1988,6 +2038,7 @@ msgstr "" " veæ obraðena od strane administratora liste." #: Mailman/Cgi/confirm.py:735 +#, fuzzy msgid "" "Your confirmation is required in order to cancel the\n" " posting of your message to the mailing list %(listname)s:\n" @@ -2038,6 +2089,7 @@ msgid "Membership re-enabled." msgstr "Èlanstvo je obnovljeno." #: Mailman/Cgi/confirm.py:798 +#, fuzzy msgid "" " You have successfully re-enabled your membership in the\n" " %(listname)s mailing list. You can now not available" msgstr "nije dostupno" #: Mailman/Cgi/confirm.py:846 +#, fuzzy msgid "" "Your membership in the %(realname)s mailing list is\n" " currently disabled due to excessive bounces. Your confirmation is\n" @@ -2136,10 +2190,12 @@ msgid "administrative list overview" msgstr "administracijski pregled liste" #: Mailman/Cgi/create.py:115 +#, fuzzy msgid "List name must not include \"@\": %(safelistname)s" msgstr "Naziv liste ne smije ukljuèivati \"@\": %(safelistname)s" #: Mailman/Cgi/create.py:122 +#, fuzzy msgid "List already exists: %(safelistname)s" msgstr "Lista veæ postoji: %(safelistname)s" @@ -2173,18 +2229,22 @@ msgid "You are not authorized to create new mailing lists" msgstr "Vi niste ovla¹teni da biste kreirali nove mailing liste" #: Mailman/Cgi/create.py:182 +#, fuzzy msgid "Unknown virtual host: %(safehostname)s" msgstr "Nepoznati virtualni host: %(safehostname)s" #: Mailman/Cgi/create.py:218 bin/newlist:219 +#, fuzzy msgid "Bad owner email address: %(s)s" msgstr "Kriva e-mail adresa vlasnika: %(s)s" #: Mailman/Cgi/create.py:223 bin/newlist:182 bin/newlist:223 +#, fuzzy msgid "List already exists: %(listname)s" msgstr "Lista veæ postoji: %(listname)s" #: Mailman/Cgi/create.py:231 bin/newlist:217 +#, fuzzy msgid "Illegal list name: %(s)s" msgstr "Krivi naziv liste: %(s)s" @@ -2197,6 +2257,7 @@ msgstr "" " Molim kontaktirajte administratora sitea za pomoæ." #: Mailman/Cgi/create.py:273 bin/newlist:265 +#, fuzzy msgid "Your new mailing list: %(listname)s" msgstr "Va¹a nova mailing lista: %(listname)s" @@ -2205,6 +2266,7 @@ msgid "Mailing list creation results" msgstr "Rezultati kreiranja mailing liste" #: Mailman/Cgi/create.py:288 +#, fuzzy msgid "" "You have successfully created the mailing list\n" " %(listname)s and notification has been sent to the list owner\n" @@ -2227,6 +2289,7 @@ msgid "Create another list" msgstr "Kreiraj drugu listu" #: Mailman/Cgi/create.py:312 +#, fuzzy msgid "Create a %(hostname)s Mailing List" msgstr "Kreiraj %(hostname)s Mailing Listu" @@ -2325,6 +2388,7 @@ msgstr "" " ostavili èlanske poruke dok ih moderator ne odobri." #: Mailman/Cgi/create.py:432 +#, fuzzy msgid "" "Initial list of supported languages.

                      Note that if you do not\n" " select at least one initial language, the list will use the server\n" @@ -2431,6 +2495,7 @@ msgid "List name is required." msgstr "Potreban je naziv liste." #: Mailman/Cgi/edithtml.py:154 +#, fuzzy msgid "%(realname)s -- Edit html for %(template_info)s" msgstr "%(realname)s -- Uredi html za %(template_info)s" @@ -2439,10 +2504,12 @@ msgid "Edit HTML : Error" msgstr "Uredi HTML : Gre¹ka" #: Mailman/Cgi/edithtml.py:161 +#, fuzzy msgid "%(safetemplatename)s: Invalid template" msgstr "%(safetemplatename)s: Pogre¹an predlo¾ak" #: Mailman/Cgi/edithtml.py:166 Mailman/Cgi/edithtml.py:167 +#, fuzzy msgid "%(realname)s -- HTML Page Editing" msgstr "%(realname)s -- Ureðivanje HTML Stranice" @@ -2501,10 +2568,12 @@ msgid "HTML successfully updated." msgstr "HTML je uspje¹no osvje¾en." #: Mailman/Cgi/listinfo.py:89 +#, fuzzy msgid "%(hostname)s Mailing Lists" msgstr "%(hostname)s Mailing Liste" #: Mailman/Cgi/listinfo.py:127 +#, fuzzy msgid "" "

                      There currently are no publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s." @@ -2513,6 +2582,7 @@ msgstr "" " %(mailmanlink)s mailing lista na %(hostname)s." #: Mailman/Cgi/listinfo.py:131 +#, fuzzy msgid "" "

                      Below is a listing of all the public mailing lists on\n" " %(hostname)s. Click on a list name to get more information " @@ -2533,6 +2603,7 @@ msgid "right" msgstr "u redu" #: Mailman/Cgi/listinfo.py:140 +#, fuzzy msgid "" " To visit the general information page for an unadvertised list,\n" " open a URL similar to this one, but with a '/' and the %(adj)s\n" @@ -2595,6 +2666,7 @@ msgstr "Pogre #: Mailman/Cgi/options.py:183 Mailman/Cgi/options.py:240 #: Mailman/Cgi/options.py:264 +#, fuzzy msgid "No such member: %(safeuser)s." msgstr "Nema takvog èlana: %(safeuser)s." @@ -2645,6 +2717,7 @@ msgid "Note: " msgstr "" #: Mailman/Cgi/options.py:378 +#, fuzzy msgid "List subscriptions for %(safeuser)s on %(hostname)s" msgstr "Pretplata na listu za %(safeuser)s na %(hostname)s" @@ -2672,6 +2745,7 @@ msgid "You are already using that email address" msgstr "Veæ koristite tu e-mail adresu" #: Mailman/Cgi/options.py:459 +#, fuzzy msgid "" "The new address you requested %(newaddr)s is already a member of the\n" "%(listname)s mailing list, however you have also requested a global change " @@ -2686,6 +2760,7 @@ msgstr "" "%(safeuser)s bit æe promjenjena. " #: Mailman/Cgi/options.py:468 +#, fuzzy msgid "The new address is already a member: %(newaddr)s" msgstr "Nova adresa je veæ èlan: %(newaddr)s" @@ -2694,6 +2769,7 @@ msgid "Addresses may not be blank" msgstr "Adrese ne smiju biti prazne" #: Mailman/Cgi/options.py:488 +#, fuzzy msgid "A confirmation message has been sent to %(newaddr)s. " msgstr "Potvrdna purka je poslana na %(newaddr)s. " @@ -2706,6 +2782,7 @@ msgid "Illegal email address provided" msgstr "Dana je pogre¹na e-mail adresa" #: Mailman/Cgi/options.py:501 +#, fuzzy msgid "%(newaddr)s is already a member of the list." msgstr "%(newaddr)s je veæ èlan liste." @@ -2788,6 +2865,7 @@ msgstr "" " æete obavijest." #: Mailman/Cgi/options.py:623 +#, fuzzy msgid "" "You have been successfully unsubscribed from the\n" " mailing list %(fqdn_listname)s. If you were receiving digest\n" @@ -2878,6 +2956,7 @@ msgid "day" msgstr "dan" #: Mailman/Cgi/options.py:900 +#, fuzzy msgid "%(days)d %(units)s" msgstr "%(days)d %(units)s" @@ -2890,6 +2969,7 @@ msgid "No topics defined" msgstr "Nijedna tema nije napisana" #: Mailman/Cgi/options.py:940 +#, fuzzy msgid "" "\n" "You are subscribed to this list with the case-preserved address\n" @@ -2901,6 +2981,7 @@ msgstr "" "%(cpuser)s." #: Mailman/Cgi/options.py:956 +#, fuzzy msgid "%(realname)s list: member options login page" msgstr "%(realname)s lista: èlanska ulazna stranica sa opcijama" @@ -2909,10 +2990,12 @@ msgid "email address and " msgstr "e-mail adresa i " #: Mailman/Cgi/options.py:960 +#, fuzzy msgid "%(realname)s list: member options for user %(safeuser)s" msgstr "%(realname)s lista: èlanske opcije za korisnika %(safeuser)s" #: Mailman/Cgi/options.py:986 +#, fuzzy msgid "" "In order to change your membership option, you must\n" " first log in by giving your %(extra)smembership password in the section\n" @@ -2991,6 +3074,7 @@ msgid "" msgstr "" #: Mailman/Cgi/options.py:1140 +#, fuzzy msgid "Requested topic is not valid: %(topicname)s" msgstr "Tra¾eni naslov (tema) nije ispravan: %(topicname)s" @@ -3019,6 +3103,7 @@ msgid "Private archive - \"./\" and \"../\" not allowed in URL." msgstr "" #: Mailman/Cgi/private.py:109 +#, fuzzy msgid "Private Archive Error - %(msg)s" msgstr "Gre¹ka u Privatnoj Arhivi - %(msg)s" @@ -3056,12 +3141,14 @@ msgid "Mailing list deletion results" msgstr "Rezultati brisanja mailing liste" #: Mailman/Cgi/rmlist.py:187 +#, fuzzy msgid "" "You have successfully deleted the mailing list\n" " %(listname)s." msgstr "Uspje¹no ste obrisali mailing listu %(listname)s." #: Mailman/Cgi/rmlist.py:191 +#, fuzzy msgid "" "There were some problems deleting the mailing list\n" " %(listname)s. Contact your site administrator at " @@ -3074,6 +3161,7 @@ msgstr "" " za detalje." #: Mailman/Cgi/rmlist.py:208 +#, fuzzy msgid "Permanently remove mailing list %(realname)s" msgstr "Trajno ukloni mailing listu %(realname)s" @@ -3146,6 +3234,7 @@ msgid "Invalid options to CGI script" msgstr "Pogre¹ne opcije za CGI skriptu" #: Mailman/Cgi/roster.py:118 +#, fuzzy msgid "%(realname)s roster authentication failed." msgstr "%(realname)s popis sa krivom autentikacijom." @@ -3214,6 +3303,7 @@ msgstr "" "sadr¾i daljnje upute." #: Mailman/Cgi/subscribe.py:262 +#, fuzzy msgid "" "The email address you supplied is banned from this\n" " mailing list. If you think this restriction is erroneous, please\n" @@ -3240,6 +3330,7 @@ msgstr "" "nesigurna." #: Mailman/Cgi/subscribe.py:278 +#, fuzzy msgid "" "Confirmation from your email address is required, to prevent anyone from\n" "subscribing you without permission. Instructions are being sent to you at\n" @@ -3252,6 +3343,7 @@ msgstr "" "istu." #: Mailman/Cgi/subscribe.py:290 +#, fuzzy msgid "" "Your subscription request was deferred because %(x)s. Your request has " "been\n" @@ -3272,6 +3364,7 @@ msgid "Mailman privacy alert" msgstr "Uzbuna zbog povrede Mailmanove privatnosti" #: Mailman/Cgi/subscribe.py:316 +#, fuzzy msgid "" "An attempt was made to subscribe your address to the mailing list\n" "%(listaddr)s. You are already subscribed to this mailing list.\n" @@ -3313,6 +3406,7 @@ msgid "This list only supports digest delivery." msgstr "Ova lista podr¾ava samo digest dostavu." #: Mailman/Cgi/subscribe.py:344 +#, fuzzy msgid "You have been successfully subscribed to the %(realname)s mailing list." msgstr "Uspje¹no ste pretplaæeni na %(realname)s mailing listu." @@ -3443,26 +3537,32 @@ msgid "n/a" msgstr "n/a" #: Mailman/Commands/cmd_info.py:44 +#, fuzzy msgid "List name: %(listname)s" msgstr "Naziv liste: %(listname)s" #: Mailman/Commands/cmd_info.py:45 +#, fuzzy msgid "Description: %(description)s" msgstr "Opis: %(description)s" #: Mailman/Commands/cmd_info.py:46 +#, fuzzy msgid "Postings to: %(postaddr)s" msgstr "Slanje poruka na: %(postaddr)s" #: Mailman/Commands/cmd_info.py:47 +#, fuzzy msgid "List Helpbot: %(requestaddr)s" msgstr "Pomoæni robot liste: %(requestaddr)s" #: Mailman/Commands/cmd_info.py:48 +#, fuzzy msgid "List Owners: %(owneraddr)s" msgstr "Vlasnici Liste: %(owneraddr)s" #: Mailman/Commands/cmd_info.py:49 +#, fuzzy msgid "More information: %(listurl)s" msgstr "Vi¹e informacija: %(listurl)s" @@ -3485,18 +3585,22 @@ msgstr "" " Pogledaj listu javnih mailing lista na ovom GNU Mailman serveru.\n" #: Mailman/Commands/cmd_lists.py:44 +#, fuzzy msgid "Public mailing lists at %(hostname)s:" msgstr "Javne mailing liste na %(hostname)s:" #: Mailman/Commands/cmd_lists.py:66 +#, fuzzy msgid "%(i)3d. List name: %(realname)s" msgstr "%(i)3d. Naziv liste: %(realname)s" #: Mailman/Commands/cmd_lists.py:67 +#, fuzzy msgid " Description: %(description)s" msgstr " Opis: %(description)s" #: Mailman/Commands/cmd_lists.py:68 +#, fuzzy msgid " Requests to: %(requestaddr)s" msgstr " Zahtjevi na: %(requestaddr)s" @@ -3532,12 +3636,14 @@ msgstr "" " odgovor uvijek poslan na pretplatnièku adresu.\n" #: Mailman/Commands/cmd_password.py:51 Mailman/Commands/cmd_password.py:66 +#, fuzzy msgid "Your password is: %(password)s" msgstr "Va¹a lozinka je: %(password)s" #: Mailman/Commands/cmd_password.py:57 Mailman/Commands/cmd_password.py:72 #: Mailman/Commands/cmd_password.py:95 Mailman/Commands/cmd_password.py:121 #: Mailman/Commands/cmd_set.py:149 Mailman/Commands/cmd_set.py:219 +#, fuzzy msgid "You are not a member of the %(listname)s mailing list" msgstr "Vi niste èlan %(listname)s mailing liste" @@ -3735,6 +3841,7 @@ msgstr "" " za ovu mailing listu.\n" #: Mailman/Commands/cmd_set.py:122 +#, fuzzy msgid "Bad set command: %(subcmd)s" msgstr "Krivo postavljena komanda: %(subcmd)s" @@ -3755,6 +3862,7 @@ msgid "on" msgstr "ukljuèeno" #: Mailman/Commands/cmd_set.py:154 +#, fuzzy msgid " ack %(onoff)s" msgstr " ack %(onoff)s" @@ -3792,22 +3900,27 @@ msgid "due to bounces" msgstr "zbog odbijanja" #: Mailman/Commands/cmd_set.py:186 +#, fuzzy msgid " %(status)s (%(how)s on %(date)s)" msgstr " %(status)s (%(how)s na %(date)s)" #: Mailman/Commands/cmd_set.py:192 +#, fuzzy msgid " myposts %(onoff)s" msgstr " moje poruke %(onoff)s" #: Mailman/Commands/cmd_set.py:195 +#, fuzzy msgid " hide %(onoff)s" msgstr " sakrij %(onoff)s" #: Mailman/Commands/cmd_set.py:199 +#, fuzzy msgid " duplicates %(onoff)s" msgstr " duplikati %(onoff)s" #: Mailman/Commands/cmd_set.py:203 +#, fuzzy msgid " reminders %(onoff)s" msgstr " podsjetnici %(onoff)s" @@ -3816,6 +3929,7 @@ msgid "You did not give the correct password" msgstr "Niste unjeli toènu lozinku" #: Mailman/Commands/cmd_set.py:236 Mailman/Commands/cmd_set.py:283 +#, fuzzy msgid "Bad argument: %(arg)s" msgstr "Krivi argument: %(arg)s" @@ -3894,6 +4008,7 @@ msgstr "" " e-mail adrese, i bez navodnika!)\n" #: Mailman/Commands/cmd_subscribe.py:62 +#, fuzzy msgid "Bad digest specifier: %(arg)s" msgstr "Krivi digest specifikator: %(arg)s" @@ -3902,6 +4017,7 @@ msgid "No valid address found to subscribe" msgstr "Nije pronaðena ispravna adresa za pretplatu" #: Mailman/Commands/cmd_subscribe.py:113 +#, fuzzy msgid "" "The email address you supplied is banned from this mailing list.\n" "If you think this restriction is erroneous, please contact the list\n" @@ -3940,6 +4056,7 @@ msgid "This list only supports digest subscriptions!" msgstr "Ova lista podr¾ava samo digest pretplate!" #: Mailman/Commands/cmd_subscribe.py:146 +#, fuzzy msgid "" "Your subscription request has been forwarded to the list administrator\n" "at %(listowner)s for review." @@ -3978,6 +4095,7 @@ msgstr "" " navodnika!)\n" #: Mailman/Commands/cmd_unsubscribe.py:62 +#, fuzzy msgid "%(address)s is not a member of the %(listname)s mailing list" msgstr "%(address)s nije èlan %(listname)s mailing liste" @@ -4225,6 +4343,7 @@ msgid "Chinese (Taiwan)" msgstr "Kineski (Tajvan)" #: Mailman/Deliverer.py:53 +#, fuzzy msgid "" "Note: Since this is a list of mailing lists, administrative\n" "notices like the password reminder will be sent to\n" @@ -4239,14 +4358,17 @@ msgid " (Digest mode)" msgstr " (Digest mod)" #: Mailman/Deliverer.py:79 +#, fuzzy msgid "Welcome to the \"%(realname)s\" mailing list%(digmode)s" msgstr "Dobrodo¹li na \"%(realname)s\" mailing listu%(digmode)s" #: Mailman/Deliverer.py:89 +#, fuzzy msgid "You have been unsubscribed from the %(realname)s mailing list" msgstr "Odjavljeni ste sa %(realname)s mailing liste" #: Mailman/Deliverer.py:116 +#, fuzzy msgid "%(listfullname)s mailing list reminder" msgstr "%(listfullname)s podsjetnik mailing liste" @@ -4259,6 +4381,7 @@ msgid "Hostile subscription attempt detected" msgstr "Detektiran je nedozvoljeni poku¹aj pretplate" #: Mailman/Deliverer.py:169 +#, fuzzy msgid "" "%(address)s was invited to a different mailing\n" "list, but in a deliberate malicious attempt they tried to confirm the\n" @@ -4271,6 +4394,7 @@ msgstr "" "nikakva daljnja akcija." #: Mailman/Deliverer.py:188 +#, fuzzy msgid "" "You invited %(address)s to your list, but in a\n" "deliberate malicious attempt, they tried to confirm the invitation to a\n" @@ -4284,6 +4408,7 @@ msgstr "" "nikakva daljnja akcija." #: Mailman/Deliverer.py:221 +#, fuzzy msgid "%(listname)s mailing list probe message" msgstr "Testna poruka %(listname)s mailing liste" @@ -4488,8 +4613,8 @@ msgid "" " membership.\n" "\n" "

                      You can control both the\n" -" number\n" +" number\n" " of reminders the member will receive and the\n" " Vi mo¾ete kontrolirati i\n" -" broj\n" +" broj\n" " podsjetnika koje æe èlan primiti i\n" " No those messages too will get discarded. You may " "want\n" " to set up an\n" -" autoresponse\n" +" autoresponse\n" " message for email to the -owner and -admin address." msgstr "" "Iako je Mailmanov detektor odbijenih poruka poprilièno robustan,\n" @@ -4731,8 +4856,8 @@ msgstr "" "poruke biti\n" " takoðer odbaèene. Ako ¾elite, mo¾ete\n" " postaviti\n" -" automatski\n" +" automatski\n" " odgovor na poruku za e-mail poslan na -owner i -admin " "adresu." @@ -4799,6 +4924,7 @@ msgstr "" " Poku¹aj da se èlan o tome obavijesti bit æe uvijek napravljen." #: Mailman/Gui/Bounce.py:194 +#, fuzzy msgid "" "Bad value for %(property)s: %(val)s" @@ -4935,8 +5061,8 @@ msgstr "" "\n" "

                      Prazne linije se ignoriraju.\n" "\n" -"

                      Pogledajte Pogledajte prolazni_mime_tipovi za tip sadr¾aja na bijeloj listi." #: Mailman/Gui/ContentFilter.py:94 @@ -4953,8 +5079,8 @@ msgid "" "Use this option to remove each message attachment that does\n" " not have a matching content type. Requirements and formats " "are\n" -" exactly like filter_mime_types.\n" "\n" "

                      Note: if you add entries to this list but don't add\n" @@ -5081,6 +5207,7 @@ msgstr "" " administratora." #: Mailman/Gui/ContentFilter.py:171 +#, fuzzy msgid "Bad MIME type ignored: %(spectype)s" msgstr "Krivi MIME tip koji je ignoriran: %(spectype)s" @@ -5185,6 +5312,7 @@ msgstr "" " prazan?" #: Mailman/Gui/Digest.py:145 +#, fuzzy msgid "" "The next digest will be sent as volume\n" " %(volume)s, number %(number)s" @@ -5201,14 +5329,17 @@ msgid "There was no digest to send." msgstr "Nije poslan nikakav digest." #: Mailman/Gui/GUIBase.py:173 +#, fuzzy msgid "Invalid value for variable: %(property)s" msgstr "Pogre¹na vrijednost za varijablu: %(property)s" #: Mailman/Gui/GUIBase.py:178 +#, fuzzy msgid "Bad email address for option %(property)s: %(error)s" msgstr "Pogre¹na e-mail adresa za opciju %(property)s: %(error)s" #: Mailman/Gui/GUIBase.py:204 +#, fuzzy msgid "" "The following illegal substitution variables were\n" " found in the %(property)s string:\n" @@ -5225,6 +5356,7 @@ msgstr "" " problem." #: Mailman/Gui/GUIBase.py:218 +#, fuzzy msgid "" "Your %(property)s string appeared to\n" " have some correctable problems in its new value.\n" @@ -5330,8 +5462,8 @@ msgid "" "

                      In order to split the list ownership duties into\n" " administrators and moderators, you must\n" " set a separate moderator password,\n" -" and also provide the email\n" +" and also provide the email\n" " addresses of the list moderators. Note that the field you\n" " are changing here specifies the list administrators." msgstr "" @@ -5674,13 +5806,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5721,8 +5853,8 @@ msgstr "" "To:\n" " polja urokuje mnogo te¾e slanje privatnih odgovora. Pogledajte " "`Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful za detelje o opasnostima " "navedenog.\n" " Pogledajte Reply-To: zaglavlje." msgid "" "This is the address set in the Reply-To: header\n" " when the reply_goes_to_list\n" +" href=\"?VARHELP=general/" +"reply_goes_to_list\">reply_goes_to_list\n" " option is set to Explicit address.\n" "\n" "

                      There are many reasons not to introduce or override the\n" @@ -5760,13 +5892,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5789,8 +5921,8 @@ msgid "" msgstr "" "Ovo je adresa koja je postavljena u Reply-To: zaglavlju\n" " kada je opcija odgovor_ide_na_listu\n" +" href=\"?VARHELP=general/" +"reply_goes_to_list\">odgovor_ide_na_listu\n" " postavljena na Eksplicitna adresa.\n" "\n" "

                      Postoji mnogo razloga za¹to ne ukljuèiti ili pregaziti\n" @@ -5801,8 +5933,8 @@ msgstr "" "To:\n" " polja urokuje mnogo te¾e slanje privatnih odgovora. Pogledajte " "`Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful za detelje o opasnostima " "navedenog.\n" " Pogledajte vi¹e nego\n" +" Ako je podr¾an vi¹e nego\n" " jedan jezik tada æe korisnici moæi izabrati svoje postavke " "na\n" " nekom od navedenih jezika za rad s listom. Ovo ukljuèuje i web " @@ -6514,8 +6646,8 @@ msgstr "" "Treba li Mailman personalizirati svaku ne-digest dostavu?\n" " Ovo je obièno korisno za samo-objava liste, ali za " "diskusiju o\n" -" performansama proèitajte detalje" +" performansama proèitajte detalje" #: Mailman/Gui/NonDigest.py:61 msgid "" @@ -6863,6 +6995,7 @@ msgstr "" " za druge, bez njihovog pristanka." #: Mailman/Gui/Privacy.py:104 +#, fuzzy msgid "" "This section allows you to configure subscription and\n" " membership exposure policy. You can also control whether this\n" @@ -7059,8 +7192,8 @@ msgid "" " either individually or as a group. Any\n" " posting from a non-member who is not explicitly accepted,\n" " rejected, or discarded, will have their posting filtered by the\n" -" general\n" +" general\n" " non-member rules.\n" "\n" "

                      In the text boxes below, add one address per line; start the\n" @@ -7090,8 +7223,8 @@ msgstr "" "

                      Poruke od osoba koje nisu èlanovi mogu biti automatski\n" " odobrene,\n" -" zadr¾ane za\n" +" zadr¾ane za\n" " moderaciju,\n" " odbijene ili\n" @@ -7100,8 +7233,8 @@ msgstr "" " pojedinaèno ili grupno. Svaka poruka koju je poslala\n" " osoba koja nije èlan liste i koja nije eksplicitno prihvaæena,\n" " odbijena, ili odbaèena biti æe filtrirana po\n" -" opæim\n" +" opæim\n" " pravilima za one koji nisu èlanovi liste.\n" "\n" "

                      U donjim tekst poljima, dodajte jednu adresu po liniji; " @@ -7124,6 +7257,7 @@ msgid "By default, should new list member postings be moderated?" msgstr "Trebaju li poruke novih èlanova liste biti moderirane?" #: Mailman/Gui/Privacy.py:218 +#, fuzzy msgid "" "Each list member has a moderation flag which says\n" " whether messages from the list member can be posted directly " @@ -7180,8 +7314,8 @@ msgid "" "If a member posts this many times, within a period of time\n" " the member is automatically moderated. Use 0 to disable. " "See\n" -" member_verbosity_interval for details on the time " "period.\n" "\n" @@ -7629,8 +7763,8 @@ msgstr "" " se usporeðuje s adresama koje mogu biti\n" " prihvaæene,\n" -" zadr¾ane,\n" +" zadr¾ane,\n" " odbijene (bounced) i\n" " The body of the message can also be optionally scanned for\n" " Subject: and Keywords: headers, as\n" " specified by the topics_bodylines_limit\n" +" href=\"?VARHELP=topics/" +"topics_bodylines_limit\">topics_bodylines_limit\n" " configuration variable." msgstr "" "Filter naslova kategorizira svaku dolaznu e-mail poruku\n" @@ -7966,8 +8101,8 @@ msgstr "" " Subject: i Keywords: zaglavljima, " "kao\n" " ¹to je specificirano topics_bodylines_limit\n" +" href=\"?VARHELP=topics/" +"topics_bodylines_limit\">topics_bodylines_limit\n" " konfiguracijskom varijablom." #: Mailman/Gui/Topics.py:72 @@ -8045,6 +8180,7 @@ msgstr "" " Nepotpuni uzorci æe biti ignorirani." #: Mailman/Gui/Topics.py:135 +#, fuzzy msgid "" "The topic pattern '%(safepattern)s' is not a\n" " legal regular expression. It will be discarded." @@ -8274,6 +8410,7 @@ msgid "%(listinfo_link)s list run by %(owner_link)s" msgstr "%(listinfo_link)s listu vrti %(owner_link)s" #: Mailman/HTMLFormatter.py:57 +#, fuzzy msgid "%(realname)s administrative interface" msgstr "%(realname)s administrativno suèelje" @@ -8282,6 +8419,7 @@ msgid " (requires authorization)" msgstr " (zahtijeva autorizaciju)" #: Mailman/HTMLFormatter.py:61 +#, fuzzy msgid "Overview of all %(hostname)s mailing lists" msgstr "Pregled svih %(hostname)s mailing listi" @@ -8302,6 +8440,7 @@ msgid "; it was disabled by the list administrator" msgstr "; onemoguæeno je od strane administratora liste" #: Mailman/HTMLFormatter.py:146 +#, fuzzy msgid "" "; it was disabled due to excessive bounces. The\n" " last bounce was received on %(date)s" @@ -8314,6 +8453,7 @@ msgid "; it was disabled for unknown reasons" msgstr "; onemoguæeno je zbog nepoznatih razloga" #: Mailman/HTMLFormatter.py:151 +#, fuzzy msgid "Note: your list delivery is currently disabled%(reason)s." msgstr "Primjetite: dostava sa va¹e liste je trenutno onemoguæena%(reason)s." @@ -8326,6 +8466,7 @@ msgid "the list administrator" msgstr "administrator liste" #: Mailman/HTMLFormatter.py:157 +#, fuzzy msgid "" "

                      %(note)s\n" "\n" @@ -8347,6 +8488,7 @@ msgstr "" " bilo kakvih pitanja ili trebate pomoæ." #: Mailman/HTMLFormatter.py:169 +#, fuzzy msgid "" "

                      We have received some recent bounces from your\n" " address. Your current bounce score is %(score)s out of " @@ -8366,6 +8508,7 @@ msgstr "" "problemi brzo isprave." #: Mailman/HTMLFormatter.py:181 +#, fuzzy msgid "" "(Note - you are subscribing to a list of mailing lists, so the %(type)s " "notice will be sent to the admin address for your membership, %(addr)s.)

                      " @@ -8413,6 +8556,7 @@ msgstr "" " moderatora." #: Mailman/HTMLFormatter.py:208 +#, fuzzy msgid "" "This is %(also)sa private list, which means that the\n" " list of members is not available to non-members." @@ -8421,6 +8565,7 @@ msgstr "" " èlanova nije dostupna onima koji nisu èlanovi." #: Mailman/HTMLFormatter.py:211 +#, fuzzy msgid "" "This is %(also)sa hidden list, which means that the\n" " list of members is available only to the list administrator." @@ -8429,6 +8574,7 @@ msgstr "" " èlanova dostupna samo administratorima liste." #: Mailman/HTMLFormatter.py:214 +#, fuzzy msgid "" "This is %(also)sa public list, which means that the\n" " list of members list is available to everyone." @@ -8445,6 +8591,7 @@ msgstr "" " jednostavno prepoznate od strane spammera)." #: Mailman/HTMLFormatter.py:222 +#, fuzzy msgid "" "

                      (Note that this is an umbrella list, intended to\n" " have only other mailing lists as members. Among other things,\n" @@ -8462,6 +8609,7 @@ msgid "either " msgstr "ili " #: Mailman/HTMLFormatter.py:256 +#, fuzzy msgid "" "To unsubscribe from %(realname)s, get a password reminder,\n" " or change your subscription options %(either)senter your " @@ -8496,6 +8644,7 @@ msgstr "" " e-mail adresu" #: Mailman/HTMLFormatter.py:277 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " members.)" @@ -8504,6 +8653,7 @@ msgstr "" " liste.)" #: Mailman/HTMLFormatter.py:281 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " administrator.)" @@ -8564,6 +8714,7 @@ msgid "The current archive" msgstr "Trenutna arhiva" #: Mailman/Handlers/Acknowledge.py:59 +#, fuzzy msgid "%(realname)s post acknowledgement" msgstr "%(realname)s potvrda poruke" @@ -8576,6 +8727,7 @@ msgid "" msgstr "" #: Mailman/Handlers/CalcRecips.py:79 +#, fuzzy msgid "" "Your urgent message to the %(realname)s mailing list was not authorized for\n" "delivery. The original message as received by Mailman is attached.\n" @@ -8652,6 +8804,7 @@ msgid "Message may contain administrivia" msgstr "Poruka mo¾e sadr¾avati administrativne zahtjeve" #: Mailman/Handlers/Hold.py:84 +#, fuzzy msgid "" "Please do *not* post administrative requests to the mailing\n" "list. If you wish to subscribe, visit %(listurl)s or send a message with " @@ -8693,10 +8846,12 @@ msgid "Posting to a moderated newsgroup" msgstr "Slanje poruke na moderiranu news grupu" #: Mailman/Handlers/Hold.py:252 +#, fuzzy msgid "Your message to %(listname)s awaits moderator approval" msgstr "Va¹a poruka na %(listname)s èeka odobrenje moderatora" #: Mailman/Handlers/Hold.py:271 +#, fuzzy msgid "%(listname)s post from %(sender)s requires approval" msgstr "%(listname)s poruka od strane %(sender)s treba odobrenje" @@ -8741,6 +8896,7 @@ msgid "After content filtering, the message was empty" msgstr "Nakon filtriranja sadr¾aja, poruka je bila prazna" #: Mailman/Handlers/MimeDel.py:269 +#, fuzzy msgid "" "The attached message matched the %(listname)s mailing list's content " "filtering\n" @@ -8785,6 +8941,7 @@ msgid "The attached message has been automatically discarded." msgstr "Poruka u privitku je autmatski odbaèena." #: Mailman/Handlers/Replybot.py:75 +#, fuzzy msgid "Auto-response for your message to the \"%(realname)s\" mailing list" msgstr "Auto-odgovor za va¹u poruku na \"%(realname)s\" mailing listu" @@ -8804,6 +8961,7 @@ msgid "HTML attachment scrubbed and removed" msgstr "HTML privitak izbaèen i uklonjen" #: Mailman/Handlers/Scrubber.py:234 Mailman/Handlers/Scrubber.py:259 +#, fuzzy msgid "" "An HTML attachment was scrubbed...\n" "URL: %(url)s\n" @@ -8889,6 +9047,7 @@ msgid "Message rejected by filter rule match" msgstr "" #: Mailman/Handlers/ToDigest.py:173 +#, fuzzy msgid "%(realname)s Digest, Vol %(volume)d, Issue %(issue)d" msgstr "%(realname)s Digest, Broj %(volume)d, Izdanje %(issue)d" @@ -8925,6 +9084,7 @@ msgid "End of " msgstr "Kraj " #: Mailman/ListAdmin.py:309 +#, fuzzy msgid "Posting of your message titled \"%(subject)s\"" msgstr "Slanje poruke naslovljene \"%(subject)s\"" @@ -8937,6 +9097,7 @@ msgid "Forward of moderated message" msgstr "Prosljeðivanje moderirane poruke" #: Mailman/ListAdmin.py:405 +#, fuzzy msgid "New subscription request to list %(realname)s from %(addr)s" msgstr "Novi zahtjev za pretplatom na listu %(realname)s sa %(addr)s" @@ -8950,6 +9111,7 @@ msgid "via admin approval" msgstr "Nastavi oèekivano odobrenje" #: Mailman/ListAdmin.py:466 +#, fuzzy msgid "New unsubscription request from %(realname)s by %(addr)s" msgstr "Novi zahtjev za odjavom od %(realname)s sa %(addr)s" @@ -8962,10 +9124,12 @@ msgid "Original Message" msgstr "Izvorna Poruka" #: Mailman/ListAdmin.py:526 +#, fuzzy msgid "Request to mailing list %(realname)s rejected" msgstr "Zahtjev na mailing listi %(realname)s je odbijen" #: Mailman/MTA/Manual.py:66 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been created via the through-the-web\n" "interface. In order to complete the activation of this mailing list, the\n" @@ -8994,14 +9158,17 @@ msgstr "" "`newaliases' programa:\n" #: Mailman/MTA/Manual.py:82 +#, fuzzy msgid "## %(listname)s mailing list" msgstr "## %(listname)s mailing lista" #: Mailman/MTA/Manual.py:99 +#, fuzzy msgid "Mailing list creation request for list %(listname)s" msgstr "Zahtjev za kreiranjem mailing liste za listu %(listname)s" #: Mailman/MTA/Manual.py:113 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been removed via the through-the-web\n" "interface. In order to complete the de-activation of this mailing list, " @@ -9020,6 +9187,7 @@ msgstr "" "Ovdje su stavke za /etc/aliases datoteku koje trebaju biti uklonjene:\n" #: Mailman/MTA/Manual.py:123 +#, fuzzy msgid "" "\n" "To finish removing your mailing list, you must edit your /etc/aliases (or\n" @@ -9038,14 +9206,17 @@ msgstr "" "## %(listname)s mailing lista" #: Mailman/MTA/Manual.py:142 +#, fuzzy msgid "Mailing list removal request for list %(listname)s" msgstr "Zahtjev za uklanjanjem mailing liste za listu %(listname)s" #: Mailman/MTA/Postfix.py:442 +#, fuzzy msgid "checking permissions on %(file)s" msgstr "provjeravam dozvole za %(file)s" #: Mailman/MTA/Postfix.py:452 +#, fuzzy msgid "%(file)s permissions must be 0664 (got %(octmode)s)" msgstr "%(file)s dozvole moraju biti 0664 (imam %(octmode)s)" @@ -9059,14 +9230,17 @@ msgid "(fixing)" msgstr "(popravljam)" #: Mailman/MTA/Postfix.py:470 +#, fuzzy msgid "checking ownership of %(dbfile)s" msgstr "provjeravam vlasni¹tvo %(dbfile)s" #: Mailman/MTA/Postfix.py:478 +#, fuzzy msgid "%(dbfile)s owned by %(owner)s (must be owned by %(user)s" msgstr "%(dbfile)s vlasni¹tvo %(owner)s (mora biti u vlasni¹tvu %(user)s" #: Mailman/MTA/Postfix.py:491 +#, fuzzy msgid "%(dbfile)s permissions must be 0664 (got %(octmode)s)" msgstr "%(dbfile)s dozvole moraju biti 0664 (imam %(octmode)s)" @@ -9081,14 +9255,17 @@ msgid "Your confirmation is required to leave the %(listname)s mailing list" msgstr "Vi niste èlan %(listname)s mailing liste" #: Mailman/MailList.py:999 Mailman/MailList.py:1492 +#, fuzzy msgid " from %(remote)s" msgstr " od %(remote)s" #: Mailman/MailList.py:1043 +#, fuzzy msgid "subscriptions to %(realname)s require moderator approval" msgstr "prtplate na %(realname)s zahtjevaju odobrenje moderatora" #: Mailman/MailList.py:1125 bin/add_members:299 +#, fuzzy msgid "%(realname)s subscription notification" msgstr "%(realname)s obavijest o pretplati" @@ -9097,6 +9274,7 @@ msgid "unsubscriptions require moderator approval" msgstr "odjava treba odobrenje moderatora" #: Mailman/MailList.py:1166 +#, fuzzy msgid "%(realname)s unsubscribe notification" msgstr "%(realname)s obavijest o odjavi" @@ -9116,6 +9294,7 @@ msgid "via web confirmation" msgstr "Krivi potvrdni tekst" #: Mailman/MailList.py:1396 +#, fuzzy msgid "subscriptions to %(name)s require administrator approval" msgstr "pretplata na %(name)s treba odobrenje administratora" @@ -9134,6 +9313,7 @@ msgid "Last autoresponse notification for today" msgstr "Posljednja obavijest o automatskom odgovoru za danas" #: Mailman/Queue/BounceRunner.py:360 +#, fuzzy msgid "" "The attached message was received as a bounce, but either the bounce format\n" "was not recognized, or no member addresses could be extracted from it. " @@ -9224,6 +9404,7 @@ msgid "Original message suppressed by Mailman site configuration\n" msgstr "" #: Mailman/htmlformat.py:675 +#, fuzzy msgid "Delivered by Mailman
                      version %(version)s" msgstr "Dostavljeno od strane Mailmana
                      verzija %(version)s" @@ -9312,6 +9493,7 @@ msgid "Server Local Time" msgstr "Lokalno Vrijeme Servera" #: Mailman/i18n.py:180 +#, fuzzy msgid "" "%(wday)s %(mon)s %(day)2i %(hh)02i:%(mm)02i:%(ss)02i %(tzname)s %(year)04i" msgstr "" @@ -9381,20 +9563,23 @@ msgid "" msgstr "" #: bin/add_members:162 bin/add_members:172 +#, fuzzy msgid "Already a member: %(member)s" -msgstr "" +msgstr "Veæ je èlan" #: bin/add_members:178 msgid "Bad/Invalid email address: blank line" msgstr "" #: bin/add_members:180 +#, fuzzy msgid "Bad/Invalid email address: %(member)s" -msgstr "" +msgstr "Kriva/Neispravna e-mail adresa" #: bin/add_members:182 +#, fuzzy msgid "Hostile address (illegal characters): %(member)s" -msgstr "" +msgstr "Hostile adresa (nedozvoljeni znakovi)" #: bin/add_members:185 #, fuzzy @@ -9402,16 +9587,19 @@ msgid "Invited: %(member)s" msgstr "Èlanovi liste" #: bin/add_members:187 +#, fuzzy msgid "Subscribed: %(member)s" -msgstr "" +msgstr "Èlanovi liste" #: bin/add_members:237 +#, fuzzy msgid "Bad argument to -w/--welcome-msg: %(arg)s" -msgstr "" +msgstr "Krivi argument: %(arg)s" #: bin/add_members:244 +#, fuzzy msgid "Bad argument to -a/--admin-notify: %(arg)s" -msgstr "" +msgstr "Krivi argument: %(arg)s" #: bin/add_members:252 msgid "Cannot read both digest and normal members from standard input." @@ -9424,8 +9612,9 @@ msgstr "" #: bin/add_members:261 bin/config_list:110 bin/export.py:271 bin/find_member:97 #: bin/inject:91 bin/list_admins:90 bin/list_members:252 bin/sync_members:222 #: cron/bumpdigests:86 +#, fuzzy msgid "No such list: %(listname)s" -msgstr "" +msgstr "Takva lista ne postoji %(safelistname)s" #: bin/add_members:285 bin/change_pw:159 bin/check_db:114 bin/discard:83 #: bin/sync_members:244 bin/update:302 bin/update:323 bin/update:577 @@ -9487,10 +9676,11 @@ msgid "listname is required" msgstr "" #: bin/arch:143 bin/change_pw:107 bin/config_list:257 +#, fuzzy msgid "" "No such list \"%(listname)s\"\n" "%(e)s" -msgstr "" +msgstr "Takva lista ne postoji %(safelistname)s" #: bin/arch:168 msgid "Cannot open mbox file %(mbox)s: %(msg)s" @@ -9574,20 +9764,23 @@ msgid "" msgstr "" #: bin/change_pw:145 +#, fuzzy msgid "Bad arguments: %(strargs)s" -msgstr "" +msgstr "Krivi argument: %(arg)s" #: bin/change_pw:149 msgid "Empty list passwords are not allowed" msgstr "" #: bin/change_pw:181 +#, fuzzy msgid "New %(listname)s password: %(notifypassword)s" -msgstr "" +msgstr "Inicijalna lozinka liste:" #: bin/change_pw:190 +#, fuzzy msgid "Your new %(listname)s list password" -msgstr "" +msgstr "Inicijalna lozinka liste:" #: bin/change_pw:191 msgid "" @@ -9665,40 +9858,47 @@ msgid "" msgstr "" #: bin/check_perms:110 +#, fuzzy msgid " checking gid and mode for %(path)s" -msgstr "" +msgstr "provjeravam dozvole za %(file)s" #: bin/check_perms:122 msgid "%(path)s bad group (has: %(groupname)s, expected %(MAILMAN_GROUP)s)" msgstr "" #: bin/check_perms:151 +#, fuzzy msgid "directory permissions must be %(octperms)s: %(path)s" -msgstr "" +msgstr "%(dbfile)s dozvole moraju biti 0664 (imam %(octmode)s)" #: bin/check_perms:160 +#, fuzzy msgid "source perms must be %(octperms)s: %(path)s" -msgstr "" +msgstr "%(dbfile)s dozvole moraju biti 0664 (imam %(octmode)s)" #: bin/check_perms:171 +#, fuzzy msgid "article db files must be %(octperms)s: %(path)s" -msgstr "" +msgstr "%(dbfile)s dozvole moraju biti 0664 (imam %(octmode)s)" #: bin/check_perms:183 +#, fuzzy msgid "checking mode for %(prefix)s" -msgstr "" +msgstr "provjeravam dozvole za %(file)s" #: bin/check_perms:193 msgid "WARNING: directory does not exist: %(d)s" msgstr "" #: bin/check_perms:197 +#, fuzzy msgid "directory must be at least 02775: %(d)s" -msgstr "" +msgstr "%(file)s dozvole moraju biti 0664 (imam %(octmode)s)" #: bin/check_perms:209 +#, fuzzy msgid "checking perms on %(private)s" -msgstr "" +msgstr "provjeravam dozvole za %(file)s" #: bin/check_perms:214 msgid "%(private)s must not be other-readable" @@ -9726,40 +9926,46 @@ msgid "checking cgi-bin permissions" msgstr "" #: bin/check_perms:278 +#, fuzzy msgid " checking set-gid for %(path)s" -msgstr "" +msgstr "provjeravam dozvole za %(file)s" #: bin/check_perms:282 msgid "%(path)s must be set-gid" msgstr "" #: bin/check_perms:292 +#, fuzzy msgid "checking set-gid for %(wrapper)s" -msgstr "" +msgstr "provjeravam dozvole za %(file)s" #: bin/check_perms:296 msgid "%(wrapper)s must be set-gid" msgstr "" #: bin/check_perms:306 +#, fuzzy msgid "checking permissions on %(pwfile)s" -msgstr "" +msgstr "provjeravam dozvole za %(file)s" #: bin/check_perms:315 +#, fuzzy msgid "%(pwfile)s permissions must be exactly 0640 (got %(octmode)s)" -msgstr "" +msgstr "%(file)s dozvole moraju biti 0664 (imam %(octmode)s)" #: bin/check_perms:340 msgid "checking permissions on list data" msgstr "" #: bin/check_perms:348 +#, fuzzy msgid " checking permissions on: %(path)s" -msgstr "" +msgstr "provjeravam dozvole za %(file)s" #: bin/check_perms:356 +#, fuzzy msgid "file permissions must be at least 660: %(path)s" -msgstr "" +msgstr "%(file)s dozvole moraju biti 0664 (imam %(octmode)s)" #: bin/check_perms:401 msgid "No problems found" @@ -9812,8 +10018,9 @@ msgid "Unix-From line changed: %(lineno)d" msgstr "" #: bin/cleanarch:111 +#, fuzzy msgid "Bad status number: %(arg)s" -msgstr "" +msgstr "Krivi argument: %(arg)s" #: bin/cleanarch:167 msgid "%(messages)d messages found" @@ -9910,14 +10117,16 @@ msgid " original address removed:" msgstr "" #: bin/clone_member:202 +#, fuzzy msgid "Not a valid email address: %(toaddr)s" -msgstr "" +msgstr "Kriva/Neispravna e-mail adresa" #: bin/clone_member:215 +#, fuzzy msgid "" "Error opening list \"%(listname)s\", skipping.\n" "%(e)s" -msgstr "" +msgstr "Va¹a nova mailing lista: %(listname)s" #: bin/config_list:20 msgid "" @@ -10002,12 +10211,14 @@ msgid "Non-standard property restored: %(k)s" msgstr "" #: bin/config_list:288 +#, fuzzy msgid "Invalid value for property: %(k)s" -msgstr "" +msgstr "Pogre¹na vrijednost za varijablu: %(property)s" #: bin/config_list:291 +#, fuzzy msgid "Bad email address for option %(k)s: %(v)s" -msgstr "" +msgstr "Pogre¹na e-mail adresa za opciju %(property)s: %(error)s" #: bin/config_list:348 msgid "Only one of -i or -o is allowed" @@ -10059,8 +10270,9 @@ msgid "Ignoring non-held message: %(f)s" msgstr "Ignoriram promjene obrisanog èlana: %(f)s" #: bin/discard:100 +#, fuzzy msgid "Ignoring held msg w/bad id: %(f)s" -msgstr "" +msgstr "Ignoriram promjene obrisanog èlana: %(f)s" #: bin/discard:112 #, fuzzy @@ -10109,8 +10321,9 @@ msgid "No filename given." msgstr "" #: bin/dumpdb:108 +#, fuzzy msgid "Bad arguments: %(pargs)s" -msgstr "" +msgstr "Krivi argument: %(arg)s" #: bin/dumpdb:118 msgid "Please specify either -p or -m." @@ -10360,8 +10573,9 @@ msgid "" msgstr "" #: bin/list_admins:97 +#, fuzzy msgid "List: %(listname)s, \tOwners: %(owners)s" -msgstr "" +msgstr "Vlasnici Liste: %(owneraddr)s" #: bin/list_lists:19 msgid "" @@ -10466,12 +10680,14 @@ msgid "" msgstr "" #: bin/list_members:198 +#, fuzzy msgid "Bad --nomail option: %(why)s" -msgstr "" +msgstr "Krivi digest specifikator: %(arg)s" #: bin/list_members:209 +#, fuzzy msgid "Bad --digest option: %(kind)s" -msgstr "" +msgstr "Krivi digest specifikator: %(arg)s" #: bin/list_members:213 bin/list_members:217 bin/list_members:221 #: bin/list_members:225 @@ -10655,8 +10871,9 @@ msgid "" msgstr "" #: bin/mailmanctl:280 cron/mailpasswds:119 +#, fuzzy msgid "Site list is missing: %(sitelistname)s" -msgstr "" +msgstr "Lista veæ postoji: %(safelistname)s" #: bin/mailmanctl:305 msgid "Run this program as root or as the %(name)s user, or use -u." @@ -10667,8 +10884,9 @@ msgid "No command given." msgstr "" #: bin/mailmanctl:339 +#, fuzzy msgid "Bad command: %(command)s" -msgstr "" +msgstr "Krivo postavljena komanda: %(subcmd)s" #: bin/mailmanctl:344 msgid "Warning! You may encounter permission problems." @@ -10882,8 +11100,9 @@ msgid "" msgstr "" #: bin/newlist:162 +#, fuzzy msgid "Unknown language: %(lang)s" -msgstr "" +msgstr "Va¹a nova mailing lista: %(listname)s" #: bin/newlist:167 msgid "Enter the name of the list: " @@ -10894,8 +11113,9 @@ msgid "Enter the email of the person running the list: " msgstr "" #: bin/newlist:193 +#, fuzzy msgid "Initial %(listname)s password: " -msgstr "" +msgstr "Inicijalna lozinka liste:" #: bin/newlist:197 msgid "The list password cannot be empty" @@ -10903,8 +11123,8 @@ msgstr "" #: bin/newlist:220 msgid "" -" - owner addresses need to be fully-qualified names like \"owner@example.com" -"\", not just \"owner\"." +" - owner addresses need to be fully-qualified names like \"owner@example." +"com\", not just \"owner\"." msgstr "" #: bin/newlist:244 @@ -11072,12 +11292,14 @@ msgid "Could not open file for reading: %(filename)s." msgstr "" #: bin/remove_members:163 +#, fuzzy msgid "Error opening list %(listname)s... skipping." -msgstr "" +msgstr "Va¹a nova mailing lista: %(listname)s" #: bin/remove_members:173 +#, fuzzy msgid "No such member: %(addr)s" -msgstr "" +msgstr "Nema takvog èlana: %(safeuser)s." #: bin/remove_members:178 msgid "User `%(addr)s' removed from list: %(listname)s." @@ -11143,8 +11365,9 @@ msgid "%(listname)s %(msg)s not found as %(filename)s" msgstr "" #: bin/rmlist:105 +#, fuzzy msgid "No such list (or list already deleted): %(listname)s" -msgstr "" +msgstr "Takva lista ne postoji %(safelistname)s" #: bin/rmlist:108 msgid "No such list: %(listname)s. Removing its residual archives." @@ -11278,8 +11501,9 @@ msgid "No argument to -f given" msgstr "" #: bin/sync_members:172 +#, fuzzy msgid "Illegal option: %(opt)s" -msgstr "" +msgstr "Krivi naziv liste: %(s)s" #: bin/sync_members:178 msgid "No listname given" @@ -11414,8 +11638,9 @@ msgid "" msgstr "" #: bin/update:107 +#, fuzzy msgid "Fixing language templates: %(listname)s" -msgstr "" +msgstr "Va¹a nova mailing lista: %(listname)s" #: bin/update:196 bin/update:711 msgid "WARNING: could not acquire lock for list: %(listname)s" @@ -11569,8 +11794,9 @@ msgid "done" msgstr "" #: bin/update:691 +#, fuzzy msgid "Updating mailing list: %(listname)s" -msgstr "" +msgstr "Va¹a nova mailing lista: %(listname)s" #: bin/update:694 msgid "Updating Usenet watermarks" @@ -11775,16 +12001,18 @@ msgid "" msgstr "" #: bin/withlist:175 +#, fuzzy msgid "Unlocking (but not saving) list: %(listname)s" -msgstr "" +msgstr "Va¹a nova mailing lista: %(listname)s" #: bin/withlist:179 msgid "Finalizing" msgstr "" #: bin/withlist:188 +#, fuzzy msgid "Loading list %(listname)s" -msgstr "" +msgstr "Va¹a nova mailing lista: %(listname)s" #: bin/withlist:190 msgid "(locked)" @@ -11795,8 +12023,9 @@ msgid "(unlocked)" msgstr "" #: bin/withlist:197 +#, fuzzy msgid "Unknown list: %(listname)s" -msgstr "" +msgstr "Va¹a nova mailing lista: %(listname)s" #: bin/withlist:237 msgid "No list name supplied." @@ -11815,8 +12044,9 @@ msgid "Running %(module)s.%(callable)s()..." msgstr "" #: bin/withlist:291 +#, fuzzy msgid "The variable `m' is the %(listname)s MailList instance" -msgstr "" +msgstr "Pozvani ste da se pridru¾ite %(listname)s mailing listi" #: cron/bumpdigests:19 msgid "" @@ -12003,8 +12233,9 @@ msgid "Password // URL" msgstr "" #: cron/mailpasswds:222 +#, fuzzy msgid "%(host)s mailing list memberships reminder" -msgstr "" +msgstr "%(listfullname)s podsjetnik mailing liste" #: cron/nightly_gzip:19 msgid "" @@ -12067,8 +12298,8 @@ msgstr "" #~ " applied" #~ msgstr "" #~ "Tekst koji æe biti ukljuèen u svaku\n" -#~ " obavijest o odbijanju\n" #~ " a treba biti poslan moderiranim èlanovima koji ¹alju poruke " #~ "na listu." @@ -12095,9 +12326,6 @@ msgstr "" #~ " za koje se ne ¹alju obavijesti. Ova opcija \n" #~ " sprjeèava slanje obavijesti." -#~ msgid "You have been invited to join the %(listname)s mailing list" -#~ msgstr "Pozvani ste da se pridru¾ite %(listname)s mailing listi" - #~ msgid "delivery option set" #~ msgstr "opcija dostave postavljena" diff --git a/messages/hu/LC_MESSAGES/mailman.po b/messages/hu/LC_MESSAGES/mailman.po index dc8faef1..c33aaa77 100755 --- a/messages/hu/LC_MESSAGES/mailman.po +++ b/messages/hu/LC_MESSAGES/mailman.po @@ -65,10 +65,12 @@ msgid "

                      Currently, there are no archives.

                      " msgstr "

                      Még nincs archívum.

                      " #: Mailman/Archiver/HyperArch.py:821 +#, fuzzy msgid "Gzip'd Text%(sz)s" msgstr "Tömörített Szöveg%(sz)s" #: Mailman/Archiver/HyperArch.py:826 +#, fuzzy msgid "Text%(sz)s" msgstr "Szöveg%(sz)s" @@ -141,18 +143,22 @@ msgid "Third" msgstr "harmadik" #: Mailman/Archiver/HyperArch.py:938 +#, fuzzy msgid "%(ord)s quarter %(year)i" msgstr "%(year)i %(ord)s negyedéve" #: Mailman/Archiver/HyperArch.py:945 +#, fuzzy msgid "%(month)s %(year)i" msgstr "%(year)i %(month)s" #: Mailman/Archiver/HyperArch.py:950 +#, fuzzy msgid "The Week Of Monday %(day)i %(month)s %(year)i" msgstr "%(year)i %(month)s %(day)i hétfõi nappal kezdõdõ hét" #: Mailman/Archiver/HyperArch.py:954 +#, fuzzy msgid "%(day)i %(month)s %(year)i" msgstr "%(year)i %(month)s %(day)i" @@ -161,10 +167,12 @@ msgid "Computing threaded index\n" msgstr "Téma index készítése\n" #: Mailman/Archiver/HyperArch.py:1319 +#, fuzzy msgid "Updating HTML for article %(seq)s" msgstr "%(seq)s üzenet HTML-oldalának frissítése" #: Mailman/Archiver/HyperArch.py:1326 +#, fuzzy msgid "article file %(filename)s is missing!" msgstr "%(filename)s üzenet állománya hiányzik!" @@ -181,6 +189,7 @@ msgid "Pickling archive state into " msgstr "Az archívum állapotának mentése " #: Mailman/Archiver/pipermail.py:453 +#, fuzzy msgid "Updating index files for archive [%(archive)s]" msgstr "[%(archive)s] archívum index állományainak frissítése" @@ -189,6 +198,7 @@ msgid " Thread" msgstr " Téma" #: Mailman/Archiver/pipermail.py:597 +#, fuzzy msgid "#%(counter)05d %(msgid)s" msgstr "#%(counter)05d %(msgid)s" @@ -226,6 +236,7 @@ msgid "disabled address" msgstr "kikapcsolva" #: Mailman/Bouncer.py:304 +#, fuzzy msgid " The last bounce received from you was dated %(date)s" msgstr " Az utolsó visszapattanásod ideje: %(date)s" @@ -253,6 +264,7 @@ msgstr "Adminisztr #: Mailman/Cgi/options.py:101 Mailman/Cgi/private.py:108 #: Mailman/Cgi/rmlist.py:75 Mailman/Cgi/roster.py:59 #: Mailman/Cgi/subscribe.py:67 +#, fuzzy msgid "No such list %(safelistname)s" msgstr "Nincs %(safelistname)s nevû lista" @@ -326,6 +338,7 @@ msgstr "" "\t a problémát el nem hárítod.%(rm)r" #: Mailman/Cgi/admin.py:268 +#, fuzzy msgid "%(hostname)s mailing lists - Admin Links" msgstr "%(hostname)s levelezõlisták - Adminisztrációs oldalak" @@ -338,6 +351,7 @@ msgid "Mailman" msgstr "Mailman" #: Mailman/Cgi/admin.py:310 +#, fuzzy msgid "" "

                      There currently are no publicly-advertised %(mailmanlink)s\n" " mailing lists on %(hostname)s." @@ -346,6 +360,7 @@ msgstr "" "\t gépen." #: Mailman/Cgi/admin.py:316 +#, fuzzy msgid "" "

                      Below is the collection of publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s. Click on a list\n" @@ -360,6 +375,7 @@ msgid "right " msgstr "megfelelõ " #: Mailman/Cgi/admin.py:325 +#, fuzzy msgid "" "To visit the administrators configuration page for an\n" " unadvertised list, open a URL similar to this one, but with a '/' " @@ -404,12 +420,14 @@ msgid "No valid variable name found." msgstr "Nem található megadott névvel változó." #: Mailman/Cgi/admin.py:395 +#, fuzzy msgid "" "%(realname)s Mailing list Configuration Help\n" "
                      %(varname)s Option" msgstr "%(realname)s Levelezõlista beállítások súgó
                      %(varname)s" #: Mailman/Cgi/admin.py:402 +#, fuzzy msgid "Mailman %(varname)s List Option Help" msgstr "Mailman %(varname)s lista beállítások súgó" @@ -428,14 +446,17 @@ msgstr "" " Vissza a(z) " #: Mailman/Cgi/admin.py:431 +#, fuzzy msgid "return to the %(categoryname)s options page." msgstr "%(categoryname)s beállítások oldalra." #: Mailman/Cgi/admin.py:446 +#, fuzzy msgid "%(realname)s Administration (%(label)s)" msgstr "%(realname)s lista adminisztrációja (%(label)s)" #: Mailman/Cgi/admin.py:447 +#, fuzzy msgid "%(realname)s mailing list administration
                      %(label)s Section" msgstr "%(realname)s levelezõlista adminisztráció
                      %(label)s oldala" @@ -518,6 +539,7 @@ msgid "Value" msgstr "Érték" #: Mailman/Cgi/admin.py:668 +#, fuzzy msgid "" "Badly formed options entry:\n" " %(record)s" @@ -618,10 +640,12 @@ msgid "Move rule down" msgstr "Szabály mozgatása le" #: Mailman/Cgi/admin.py:870 +#, fuzzy msgid "
                      (Edit %(varname)s)" msgstr "
                      (%(varname)s módosítása)" #: Mailman/Cgi/admin.py:872 +#, fuzzy msgid "
                      (Details for %(varname)s)" msgstr "
                      (Bõvebben a(z) %(varname)s témáról)" @@ -663,6 +687,7 @@ msgid "(help)" msgstr "(súgó)" #: Mailman/Cgi/admin.py:930 +#, fuzzy msgid "Find member %(link)s:" msgstr "Felhasználó keresése %(link)s:" @@ -675,10 +700,12 @@ msgid "Bad regular expression: " msgstr "Hibás keresési kifejezés: " #: Mailman/Cgi/admin.py:1013 +#, fuzzy msgid "%(allcnt)s members total, %(membercnt)s shown" msgstr "Összesen %(allcnt)s tag, %(membercnt)s kiírva" #: Mailman/Cgi/admin.py:1016 +#, fuzzy msgid "%(allcnt)s members total" msgstr "Összesen %(allcnt)s tag" @@ -856,6 +883,7 @@ msgid "" msgstr "

                      Többi tag megtekintéséhez kattints a megfelelõ részre lent" #: Mailman/Cgi/admin.py:1221 +#, fuzzy msgid "from %(start)s to %(end)s" msgstr "%(start)s-tõl a %(end)s-ig" @@ -995,6 +1023,7 @@ msgid "Change list ownership passwords" msgstr "Lista tulajdonosi jelszavak módosítása " #: Mailman/Cgi/admin.py:1364 +#, fuzzy msgid "" "The list administrators are the people who have ultimate control " "over\n" @@ -1168,8 +1197,9 @@ msgid "%(schange_to)s is already a member" msgstr " már tag" #: Mailman/Cgi/admin.py:1620 +#, fuzzy msgid "%(schange_to)s matches banned pattern %(spat)s" -msgstr "" +msgstr " már tag" #: Mailman/Cgi/admin.py:1622 msgid "Address %(schange_from)s changed to %(schange_to)s" @@ -1209,6 +1239,7 @@ msgid "Not subscribed" msgstr "Nincs feliratkozva" #: Mailman/Cgi/admin.py:1773 +#, fuzzy msgid "Ignoring changes to deleted member: %(user)s" msgstr "Módosítások kihagyása a törölt tagon: %(user)s" @@ -1221,10 +1252,12 @@ msgid "Error Unsubscribing:" msgstr "Hiba a törlésnél:" #: Mailman/Cgi/admindb.py:235 Mailman/Cgi/admindb.py:248 +#, fuzzy msgid "%(realname)s Administrative Database" msgstr "%(realname)s adminisztráció adatbázisa" #: Mailman/Cgi/admindb.py:238 +#, fuzzy msgid "%(realname)s Administrative Database Results" msgstr "%(realname)s Adminisztrációs adatbázis kimenet" @@ -1253,6 +1286,7 @@ msgid "Discard all messages marked Defer" msgstr "" #: Mailman/Cgi/admindb.py:298 +#, fuzzy msgid "all of %(esender)s's held messages." msgstr "%(esender)s összes jóváhagyásra váró üzenete" @@ -1273,6 +1307,7 @@ msgid "list of available mailing lists." msgstr "elérhetõ levelezõlisták sora." #: Mailman/Cgi/admindb.py:362 +#, fuzzy msgid "You must specify a list name. Here is the %(link)s" msgstr "Meg kell adnod a lista nevét. Itt az %(link)s" @@ -1355,6 +1390,7 @@ msgid "The sender is now a member of this list" msgstr "A feladó mostmár tagja ennek a levelezõlistának" #: Mailman/Cgi/admindb.py:568 +#, fuzzy msgid "Add %(esender)s to one of these sender filters:" msgstr "%(esender)s hozzáadása valamelyik feladók szûrõhöz:" @@ -1375,6 +1411,7 @@ msgid "Rejects" msgstr "Visszautasított" #: Mailman/Cgi/admindb.py:584 +#, fuzzy msgid "" "Ban %(esender)s from ever subscribing to this\n" " mailing list" @@ -1388,6 +1425,7 @@ msgid "" msgstr "Az üzenet megtekintéséhez kattints a sorszámára, vagy " #: Mailman/Cgi/admindb.py:591 +#, fuzzy msgid "view all messages from %(esender)s" msgstr "nézd meg %(esender)s összes levelét." @@ -1517,6 +1555,7 @@ msgstr "" "A módosítási kérelmet töröltük." #: Mailman/Cgi/confirm.py:178 +#, fuzzy msgid "System error, bad content: %(content)s" msgstr "Általános hiba, hibás tartalom: %(content)s" @@ -1553,6 +1592,7 @@ msgid "Confirm subscription request" msgstr "Feliratkozási kérelem megerõsítése" #: Mailman/Cgi/confirm.py:259 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " subscription request to the mailing list %(listname)s. Your\n" @@ -1586,6 +1626,7 @@ msgstr "" " Mégsem és elvet gombot." #: Mailman/Cgi/confirm.py:275 +#, fuzzy msgid "" "Your confirmation is required in order to continue with\n" " the subscription request to the mailing list %(listname)s.\n" @@ -1638,6 +1679,7 @@ msgid "Preferred language:" msgstr "Választott nyelv:" #: Mailman/Cgi/confirm.py:317 +#, fuzzy msgid "Subscribe to list %(listname)s" msgstr "Feliratkozás a(z) %(listname)s listára" @@ -1654,6 +1696,7 @@ msgid "Awaiting moderator approval" msgstr "Jóváhagyásra váró levelek" #: Mailman/Cgi/confirm.py:386 +#, fuzzy msgid "" " You have successfully confirmed your subscription request to " "the\n" @@ -1710,6 +1753,7 @@ msgid "Subscription request confirmed" msgstr "Feliratkozási kérelem megerõsítve" #: Mailman/Cgi/confirm.py:418 +#, fuzzy msgid "" " You have successfully confirmed your subscription request for\n" " \"%(addr)s\" to the %(listname)s mailing list. A separate\n" @@ -1735,6 +1779,7 @@ msgid "Unsubscription request confirmed" msgstr "Leiratkozási kérelem megerõsítve" #: Mailman/Cgi/confirm.py:469 +#, fuzzy msgid "" " You have successfully unsubscribed from the %(listname)s " "mailing\n" @@ -1755,6 +1800,7 @@ msgid "Not available" msgstr "Nem elérhetõ" #: Mailman/Cgi/confirm.py:498 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " unsubscription request from the mailing list %(listname)s. " @@ -1826,6 +1872,7 @@ msgid "Change of address request confirmed" msgstr "Feliratkozási cím sikeresen megváltoztatva" #: Mailman/Cgi/confirm.py:571 +#, fuzzy msgid "" " You have successfully changed your address on the %(listname)s\n" " mailing list from %(oldaddr)s to %(newaddr)s. " @@ -1835,8 +1882,8 @@ msgid "" msgstr "" " Sikeresen megváltoztattad a(z) %(listname)s levelezõlistán\n" " nyilvántartott régi %(oldaddr)s e-mail címedet\n" -" %(newaddr)s címre. Tovább a listatagsági\n" +" %(newaddr)s címre. Tovább a listatagsági\n" " beállítások oldalra." #: Mailman/Cgi/confirm.py:583 @@ -1848,6 +1895,7 @@ msgid "globally" msgstr "mindenhol" #: Mailman/Cgi/confirm.py:605 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " change of address request for the mailing list %(listname)s. " @@ -1908,6 +1956,7 @@ msgid "Sender discarded message via web." msgstr "A feladó web-en keresztül törölte az üzenetet." #: Mailman/Cgi/confirm.py:674 +#, fuzzy msgid "" "The held message with the Subject:\n" " header %(subject)s could not be found. The most " @@ -1926,6 +1975,7 @@ msgid "Posted message canceled" msgstr "Beküldött levél törölve" #: Mailman/Cgi/confirm.py:685 +#, fuzzy msgid "" " You have successfully canceled the posting of your message with\n" " the Subject: header %(subject)s to the mailing list\n" @@ -1947,6 +1997,7 @@ msgstr "" "adminisztrátora már gondoskodott." #: Mailman/Cgi/confirm.py:735 +#, fuzzy msgid "" "Your confirmation is required in order to cancel the\n" " posting of your message to the mailing list %(listname)s:\n" @@ -1997,6 +2048,7 @@ msgid "Membership re-enabled." msgstr "Listatagság visszaállítva." #: Mailman/Cgi/confirm.py:798 +#, fuzzy msgid "" " You have successfully re-enabled your membership in the\n" " %(listname)s mailing list. You can now not available" msgstr "nem elérhetõ" #: Mailman/Cgi/confirm.py:846 +#, fuzzy msgid "" "Your membership in the %(realname)s mailing list is\n" " currently disabled due to excessive bounces. Your confirmation is\n" @@ -2092,10 +2146,12 @@ msgid "administrative list overview" msgstr "levelezõlisták adminisztrációs oldalára." #: Mailman/Cgi/create.py:115 +#, fuzzy msgid "List name must not include \"@\": %(safelistname)s" msgstr "A lista nevében nem lehet \"@\" jel: %(safelistname)s" #: Mailman/Cgi/create.py:122 +#, fuzzy msgid "List already exists: %(safelistname)s" msgstr "A lista már létezik: %(safelistname)s" @@ -2129,18 +2185,22 @@ msgid "You are not authorized to create new mailing lists" msgstr "Jogosultság hiányában nem hozhatsz létre új levelezõlistákat." #: Mailman/Cgi/create.py:182 +#, fuzzy msgid "Unknown virtual host: %(safehostname)s" msgstr "Ismeretlen virtuális név: %(safehostname)s" #: Mailman/Cgi/create.py:218 bin/newlist:219 +#, fuzzy msgid "Bad owner email address: %(s)s" msgstr "Hibás tulajdonois e-mail cím: %(s)s" #: Mailman/Cgi/create.py:223 bin/newlist:182 bin/newlist:223 +#, fuzzy msgid "List already exists: %(listname)s" msgstr "A lista már létezik: %(listname)s" #: Mailman/Cgi/create.py:231 bin/newlist:217 +#, fuzzy msgid "Illegal list name: %(s)s" msgstr "Érvénytelen listanév: %(s)s" @@ -2153,6 +2213,7 @@ msgstr "" "\t\tFordulj a rendszer adminisztrátorához segítségért." #: Mailman/Cgi/create.py:273 bin/newlist:265 +#, fuzzy msgid "Your new mailing list: %(listname)s" msgstr "Az új levelezõlistád: %(listname)s" @@ -2161,6 +2222,7 @@ msgid "Mailing list creation results" msgstr "Levelezõlista létrehozás eredménye" #: Mailman/Cgi/create.py:288 +#, fuzzy msgid "" "You have successfully created the mailing list\n" " %(listname)s and notification has been sent to the list owner\n" @@ -2182,6 +2244,7 @@ msgid "Create another list" msgstr "Létrehozhatsz egy újabb listát" #: Mailman/Cgi/create.py:312 +#, fuzzy msgid "Create a %(hostname)s Mailing List" msgstr "Új %(hostname)s levelezõlista létrehozása" @@ -2281,6 +2344,7 @@ msgstr "" "választva az új tag beküldéseit mindig külön engedélyeznünk kell." #: Mailman/Cgi/create.py:432 +#, fuzzy msgid "" "Initial list of supported languages.

                      Note that if you do not\n" " select at least one initial language, the list will use the server\n" @@ -2386,6 +2450,7 @@ msgid "List name is required." msgstr "Meg kell adni a lista nevét." #: Mailman/Cgi/edithtml.py:154 +#, fuzzy msgid "%(realname)s -- Edit html for %(template_info)s" msgstr "%(realname)s -- %(template_info)s html szerkesztése" @@ -2394,10 +2459,12 @@ msgid "Edit HTML : Error" msgstr "HTML szerkesztése: Hiba" #: Mailman/Cgi/edithtml.py:161 +#, fuzzy msgid "%(safetemplatename)s: Invalid template" msgstr "%(safetemplatename)s: Érvénytelen sablon" #: Mailman/Cgi/edithtml.py:166 Mailman/Cgi/edithtml.py:167 +#, fuzzy msgid "%(realname)s -- HTML Page Editing" msgstr "%(realname)s -- HTML oldal szerkesztése" @@ -2456,10 +2523,12 @@ msgid "HTML successfully updated." msgstr "HTML sikeresen frissítve." #: Mailman/Cgi/listinfo.py:89 +#, fuzzy msgid "%(hostname)s Mailing Lists" msgstr "%(hostname)s Levelezõlisták" #: Mailman/Cgi/listinfo.py:127 +#, fuzzy msgid "" "

                      There currently are no publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s." @@ -2468,6 +2537,7 @@ msgstr "" "gépen." #: Mailman/Cgi/listinfo.py:131 +#, fuzzy msgid "" "

                      Below is a listing of all the public mailing lists on\n" " %(hostname)s. Click on a list name to get more information " @@ -2486,6 +2556,7 @@ msgid "right" msgstr "megfelelõ" #: Mailman/Cgi/listinfo.py:140 +#, fuzzy msgid "" " To visit the general information page for an unadvertised list,\n" " open a URL similar to this one, but with a '/' and the %(adj)s\n" @@ -2547,6 +2618,7 @@ msgstr " #: Mailman/Cgi/options.py:183 Mailman/Cgi/options.py:240 #: Mailman/Cgi/options.py:264 +#, fuzzy msgid "No such member: %(safeuser)s." msgstr "Nem található %(safeuser)s címmel tag." @@ -2597,6 +2669,7 @@ msgid "Note: " msgstr "" #: Mailman/Cgi/options.py:378 +#, fuzzy msgid "List subscriptions for %(safeuser)s on %(hostname)s" msgstr "%(safeuser)s felhasználó listatagságai a(z) %(hostname)s gépen" @@ -2624,6 +2697,7 @@ msgid "You are already using that email address" msgstr "Jelenleg is ezt az e-mail címet használod." #: Mailman/Cgi/options.py:459 +#, fuzzy msgid "" "The new address you requested %(newaddr)s is already a member of the\n" "%(listname)s mailing list, however you have also requested a global change " @@ -2638,6 +2712,7 @@ msgstr "" "a változtatás a megerõsítési után sikeresen megtörténik majd." #: Mailman/Cgi/options.py:468 +#, fuzzy msgid "The new address is already a member: %(newaddr)s" msgstr "A megadott címmel már van tag a listán: %(newaddr)s" @@ -2646,6 +2721,7 @@ msgid "Addresses may not be blank" msgstr "Legalább egy címet meg kell adnod." #: Mailman/Cgi/options.py:488 +#, fuzzy msgid "A confirmation message has been sent to %(newaddr)s. " msgstr "A megerõsítési értesítés a(z) %(newaddr)s címre el lett küldve." @@ -2658,6 +2734,7 @@ msgid "Illegal email address provided" msgstr "A megadott e-mail cím érvénytelen." #: Mailman/Cgi/options.py:501 +#, fuzzy msgid "%(newaddr)s is already a member of the list." msgstr "%(newaddr)s már tagja a listának." @@ -2739,6 +2816,7 @@ msgstr "" "jóváhagyásra. A szerkesztõ döntésérõl e-mailben kapsz értesítést." #: Mailman/Cgi/options.py:623 +#, fuzzy msgid "" "You have been successfully unsubscribed from the\n" " mailing list %(fqdn_listname)s. If you were receiving digest\n" @@ -2828,6 +2906,7 @@ msgid "day" msgstr "nap" #: Mailman/Cgi/options.py:900 +#, fuzzy msgid "%(days)d %(units)s" msgstr "%(days)d %(units)s" @@ -2840,6 +2919,7 @@ msgid "No topics defined" msgstr "Nincsen témaszûrés" #: Mailman/Cgi/options.py:940 +#, fuzzy msgid "" "\n" "You are subscribed to this list with the case-preserved address\n" @@ -2849,6 +2929,7 @@ msgstr "" "Feliratkozásod betûhelyesen a következõ %(cpuser)s." #: Mailman/Cgi/options.py:956 +#, fuzzy msgid "%(realname)s list: member options login page" msgstr "%(realname)s lista: listatagsági oldal" @@ -2857,10 +2938,12 @@ msgid "email address and " msgstr "e-mail címed és " #: Mailman/Cgi/options.py:960 +#, fuzzy msgid "%(realname)s list: member options for user %(safeuser)s" msgstr "%(realname)s lista: %(safeuser)s tag beállításai" #: Mailman/Cgi/options.py:986 +#, fuzzy msgid "" "In order to change your membership option, you must\n" " first log in by giving your %(extra)smembership password in the section\n" @@ -2940,6 +3023,7 @@ msgid "" msgstr "" #: Mailman/Cgi/options.py:1140 +#, fuzzy msgid "Requested topic is not valid: %(topicname)s" msgstr "A kért témaszûrés nem megfelelõ: %(topicname)s" @@ -2968,6 +3052,7 @@ msgid "Private archive - \"./\" and \"../\" not allowed in URL." msgstr "" #: Mailman/Cgi/private.py:109 +#, fuzzy msgid "Private Archive Error - %(msg)s" msgstr "Privát Archívum Hiba - %(msg)s" @@ -3005,6 +3090,7 @@ msgid "Mailing list deletion results" msgstr "Levelezõlista törlésének eredménye" #: Mailman/Cgi/rmlist.py:187 +#, fuzzy msgid "" "You have successfully deleted the mailing list\n" " %(listname)s." @@ -3013,6 +3099,7 @@ msgstr "" " %(listname)s" #: Mailman/Cgi/rmlist.py:191 +#, fuzzy msgid "" "There were some problems deleting the mailing list\n" " %(listname)s. Contact your site administrator at " @@ -3024,6 +3111,7 @@ msgstr "" "információkért." #: Mailman/Cgi/rmlist.py:208 +#, fuzzy msgid "Permanently remove mailing list %(realname)s" msgstr "%(realname)s levelezõ lista végleges törlése" @@ -3091,6 +3179,7 @@ msgid "Invalid options to CGI script" msgstr "Érvénytelen CGI szkript kapcsoló" #: Mailman/Cgi/roster.py:118 +#, fuzzy msgid "%(realname)s roster authentication failed." msgstr "%(realname)s listatag azonosításánál hiba történt." @@ -3158,6 +3247,7 @@ msgstr "" "e-mailben részletes leírást fogsz kapni." #: Mailman/Cgi/subscribe.py:262 +#, fuzzy msgid "" "The email address you supplied is banned from this\n" " mailing list. If you think this restriction is erroneous, please\n" @@ -3180,6 +3270,7 @@ msgid "" msgstr "Feliratkozásod a nem megfelelõ e-mail címed miatt nem engedélyezett." #: Mailman/Cgi/subscribe.py:278 +#, fuzzy msgid "" "Confirmation from your email address is required, to prevent anyone from\n" "subscribing you without permission. Instructions are being sent to you at\n" @@ -3191,6 +3282,7 @@ msgstr "" "Fontos, hogy feliratkozásod csakis a kérelem megerõsítése után lesz végleges." #: Mailman/Cgi/subscribe.py:290 +#, fuzzy msgid "" "Your subscription request was deferred because %(x)s. Your request has " "been\n" @@ -3211,6 +3303,7 @@ msgid "Mailman privacy alert" msgstr "Értesítés jogosulatlan taglista hozzáférési kísérletrõl" #: Mailman/Cgi/subscribe.py:316 +#, fuzzy msgid "" "An attempt was made to subscribe your address to the mailing list\n" "%(listaddr)s. You are already subscribed to this mailing list.\n" @@ -3250,6 +3343,7 @@ msgid "This list only supports digest delivery." msgstr "A listán csak a digest küldés mûködik." #: Mailman/Cgi/subscribe.py:344 +#, fuzzy msgid "You have been successfully subscribed to the %(realname)s mailing list." msgstr "Feliratkozásod a(z) %(realname)s listára sikeresen megtörtént." @@ -3384,26 +3478,32 @@ msgid "n/a" msgstr "n/a" #: Mailman/Commands/cmd_info.py:44 +#, fuzzy msgid "List name: %(listname)s" msgstr "Lista neve: %(listname)s" #: Mailman/Commands/cmd_info.py:45 +#, fuzzy msgid "Description: %(description)s" msgstr "Leírás: %(description)s" #: Mailman/Commands/cmd_info.py:46 +#, fuzzy msgid "Postings to: %(postaddr)s" msgstr "Lista címe: %(postaddr)s" #: Mailman/Commands/cmd_info.py:47 +#, fuzzy msgid "List Helpbot: %(requestaddr)s" msgstr "Lista súgó: %(requestaddr)s" #: Mailman/Commands/cmd_info.py:48 +#, fuzzy msgid "List Owners: %(owneraddr)s" msgstr "Lista tulajdonosok: %(owneraddr)s" #: Mailman/Commands/cmd_info.py:49 +#, fuzzy msgid "More information: %(listurl)s" msgstr "További információk: %(listurl)s" @@ -3427,18 +3527,22 @@ msgstr "" " levelezõlistákat.\n" #: Mailman/Commands/cmd_lists.py:44 +#, fuzzy msgid "Public mailing lists at %(hostname)s:" msgstr "Nyilvános levelezõlisták a(z) %(hostname)s gépen:" #: Mailman/Commands/cmd_lists.py:66 +#, fuzzy msgid "%(i)3d. List name: %(realname)s" msgstr "%(i)3d. Lista neve: %(realname)s" #: Mailman/Commands/cmd_lists.py:67 +#, fuzzy msgid " Description: %(description)s" msgstr " Leírás: %(description)s" #: Mailman/Commands/cmd_lists.py:68 +#, fuzzy msgid " Requests to: %(requestaddr)s" msgstr " Request címe: %(requestaddr)s" @@ -3475,12 +3579,14 @@ msgstr "" " kerül elküldésre a parancs kimenete.\n" #: Mailman/Commands/cmd_password.py:51 Mailman/Commands/cmd_password.py:66 +#, fuzzy msgid "Your password is: %(password)s" msgstr "Jelszavad a következõ: %(password)s" #: Mailman/Commands/cmd_password.py:57 Mailman/Commands/cmd_password.py:72 #: Mailman/Commands/cmd_password.py:95 Mailman/Commands/cmd_password.py:121 #: Mailman/Commands/cmd_set.py:149 Mailman/Commands/cmd_set.py:219 +#, fuzzy msgid "You are not a member of the %(listname)s mailing list" msgstr "Nem vagy tagja a(z) %(listname)s levelezõlistának." @@ -3672,6 +3778,7 @@ msgstr "" " jelszóemlékeztetõ-értesítést.\n" #: Mailman/Commands/cmd_set.py:122 +#, fuzzy msgid "Bad set command: %(subcmd)s" msgstr "Hibásan megadott set parancs: %(subcmd)s" @@ -3692,6 +3799,7 @@ msgid "on" msgstr "bekapcsolva" #: Mailman/Commands/cmd_set.py:154 +#, fuzzy msgid " ack %(onoff)s" msgstr " ack %(onoff)s" @@ -3729,22 +3837,27 @@ msgid "due to bounces" msgstr "visszapattanás miatt" #: Mailman/Commands/cmd_set.py:186 +#, fuzzy msgid " %(status)s (%(how)s on %(date)s)" msgstr " %(status)s (%(how)s %(date)s napon)" #: Mailman/Commands/cmd_set.py:192 +#, fuzzy msgid " myposts %(onoff)s" msgstr " myposts %(onoff)s" #: Mailman/Commands/cmd_set.py:195 +#, fuzzy msgid " hide %(onoff)s" msgstr " hide %(onoff)s" #: Mailman/Commands/cmd_set.py:199 +#, fuzzy msgid " duplicates %(onoff)s" msgstr " duplicates %(onoff)s" #: Mailman/Commands/cmd_set.py:203 +#, fuzzy msgid " reminders %(onoff)s" msgstr " reminders %(onoff)s" @@ -3753,6 +3866,7 @@ msgid "You did not give the correct password" msgstr "Hibás jelszót adtál meg." #: Mailman/Commands/cmd_set.py:236 Mailman/Commands/cmd_set.py:283 +#, fuzzy msgid "Bad argument: %(arg)s" msgstr "Hibás paraméterek: %(arg)s" @@ -3830,6 +3944,7 @@ msgstr "" " nélkül).\n" #: Mailman/Commands/cmd_subscribe.py:62 +#, fuzzy msgid "Bad digest specifier: %(arg)s" msgstr "Hibás digest kapcsoló: %(arg)s" @@ -3838,6 +3953,7 @@ msgid "No valid address found to subscribe" msgstr "A feliratkozáshoz nem található érvényes e-mail cím" #: Mailman/Commands/cmd_subscribe.py:113 +#, fuzzy msgid "" "The email address you supplied is banned from this mailing list.\n" "If you think this restriction is erroneous, please contact the list\n" @@ -3875,6 +3991,7 @@ msgid "This list only supports digest subscriptions!" msgstr "A lista csak digest típusú feliratkozást fogad el!" #: Mailman/Commands/cmd_subscribe.py:146 +#, fuzzy msgid "" "Your subscription request has been forwarded to the list administrator\n" "at %(listowner)s for review." @@ -3915,6 +4032,7 @@ msgstr "" " nélkül).\n" #: Mailman/Commands/cmd_unsubscribe.py:62 +#, fuzzy msgid "%(address)s is not a member of the %(listname)s mailing list" msgstr "%(address)s nem tagja a(z) %(listname)s levelezõlistának" @@ -4163,6 +4281,7 @@ msgid "Chinese (Taiwan)" msgstr "Kínai (Tajvan)/Chinese (Taiwan)" #: Mailman/Deliverer.py:53 +#, fuzzy msgid "" "Note: Since this is a list of mailing lists, administrative\n" "notices like the password reminder will be sent to\n" @@ -4177,14 +4296,17 @@ msgid " (Digest mode)" msgstr " (Digest mód)" #: Mailman/Deliverer.py:79 +#, fuzzy msgid "Welcome to the \"%(realname)s\" mailing list%(digmode)s" msgstr "Üdvözlet a(z) \"%(realname)s\" levelezõlistán%(digmode)s" #: Mailman/Deliverer.py:89 +#, fuzzy msgid "You have been unsubscribed from the %(realname)s mailing list" msgstr "Sikeresen leiratkoztál a(z) %(realname)s levelezõlistáról" #: Mailman/Deliverer.py:116 +#, fuzzy msgid "%(listfullname)s mailing list reminder" msgstr "%(listfullname)s levelezõlista emlékeztetõ" @@ -4197,6 +4319,7 @@ msgid "Hostile subscription attempt detected" msgstr "Illetéktelen feliratkozási kísérlet." #: Mailman/Deliverer.py:169 +#, fuzzy msgid "" "%(address)s was invited to a different mailing\n" "list, but in a deliberate malicious attempt they tried to confirm the\n" @@ -4209,6 +4332,7 @@ msgstr "" "további beavatkozást tenned, csak értesítettünk errõl a félreértésrõl." #: Mailman/Deliverer.py:188 +#, fuzzy msgid "" "You invited %(address)s to your list, but in a\n" "deliberate malicious attempt, they tried to confirm the invitation to a\n" @@ -4222,6 +4346,7 @@ msgstr "" "beavatkozást tenned, csak értesítettünk errõl a félreértésrõl." #: Mailman/Deliverer.py:221 +#, fuzzy msgid "%(listname)s mailing list probe message" msgstr "%(listname)s levelezõlista próba üzenet" @@ -4425,8 +4550,8 @@ msgid "" " membership.\n" "\n" "

                      You can control both the\n" -" number\n" +" number\n" " of reminders the member will receive and the\n" " Mind az értesítõk\n" +"

                      Mind az értesítõk\n" "számát, mind azok kiküldésének gyakoriságát be lehet állítani.\n" @@ -4629,8 +4754,8 @@ msgid "" " No those messages too will get discarded. You may " "want\n" " to set up an\n" -" autoresponse\n" +" autoresponse\n" " message for email to the -owner and -admin address." msgstr "" "A Mailman visszapattanás-kezelõje képes sok, de nem minden\n" @@ -4719,6 +4844,7 @@ msgstr "" "a program mindig megpróbálja kikapcsolásáról értesíteni." #: Mailman/Gui/Bounce.py:194 +#, fuzzy msgid "" "Bad value for %(property)s: %(val)s" @@ -4874,8 +5000,8 @@ msgid "" "Use this option to remove each message attachment that does\n" " not have a matching content type. Requirements and formats " "are\n" -" exactly like filter_mime_types.\n" "\n" "

                      Note: if you add entries to this list but don't add\n" @@ -4992,6 +5118,7 @@ msgstr "" "a rendszer adminisztrátora engedélyezte." #: Mailman/Gui/ContentFilter.py:171 +#, fuzzy msgid "Bad MIME type ignored: %(spectype)s" msgstr "Hibás MIME típus figyelmen kívül hagyva: %(spectype)s" @@ -5102,6 +5229,7 @@ msgstr "" " \t az nem üres?" #: Mailman/Gui/Digest.py:145 +#, fuzzy msgid "" "The next digest will be sent as volume\n" " %(volume)s, number %(number)s" @@ -5118,14 +5246,17 @@ msgid "There was no digest to send." msgstr "Nem volt digest, amit ki lehetett küldeni." #: Mailman/Gui/GUIBase.py:173 +#, fuzzy msgid "Invalid value for variable: %(property)s" msgstr "Érvénytelen érték a(z) %(property)s változónál." #: Mailman/Gui/GUIBase.py:178 +#, fuzzy msgid "Bad email address for option %(property)s: %(error)s" msgstr "Rossz e-mail cím lett a %(property)s részben megadva: %(error)s" #: Mailman/Gui/GUIBase.py:204 +#, fuzzy msgid "" "The following illegal substitution variables were\n" " found in the %(property)s string:\n" @@ -5141,6 +5272,7 @@ msgstr "" "amíg a hibát ki nem javítja." #: Mailman/Gui/GUIBase.py:218 +#, fuzzy msgid "" "Your %(property)s string appeared to\n" " have some correctable problems in its new value.\n" @@ -5237,8 +5369,8 @@ msgid "" "

                      In order to split the list ownership duties into\n" " administrators and moderators, you must\n" " set a separate moderator password,\n" -" and also provide the email\n" +" and also provide the email\n" " addresses of the list moderators. Note that the field you\n" " are changing here specifies the list administrators." msgstr "" @@ -5552,13 +5684,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5622,8 +5754,8 @@ msgstr "K msgid "" "This is the address set in the Reply-To: header\n" " when the reply_goes_to_list\n" +" href=\"?VARHELP=general/" +"reply_goes_to_list\">reply_goes_to_list\n" " option is set to Explicit address.\n" "\n" "

                      There are many reasons not to introduce or override the\n" @@ -5631,13 +5763,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5738,8 +5870,8 @@ msgid "" " member list addresses, but rather to the owner of those member\n" " lists. In that case, the value of this setting is appended to\n" " the member's account name for such notices. `-owner' is the\n" -" typical choice. This setting has no effect when \"umbrella_list" -"\"\n" +" typical choice. This setting has no effect when " +"\"umbrella_list\"\n" " is \"No\"." msgstr "" "\"Gyûjtõlista\" esetén, amikor csak más levelezõlisták a tagok, az " @@ -6420,8 +6552,8 @@ msgstr "" "közvetlenül a\n" "felhasználónak és nem a listának lett volna címezve.\n" "\n" -"

                      Az egyedi levél küldésnél a levél\n" +"

                      Az egyedi levél küldésnél a levél\n" "fejlécében és levél\n" "láblécében használni lehet majd az alábbi változókat is.\n" "\n" @@ -6457,8 +6589,8 @@ msgid "" " page.\n" "

                    \n" msgstr "" -"Ha a listán engedélyezték az egyéni levelek\n" +"Ha a listán engedélyezték az egyéni levelek\n" "küldését, akkor még a további változók is használhatóak a levelek fej- " "és láblécében:\n" "\n" @@ -6660,6 +6792,7 @@ msgstr "" "jogosulatlan felirattatása." #: Mailman/Gui/Privacy.py:104 +#, fuzzy msgid "" "This section allows you to configure subscription and\n" " membership exposure policy. You can also control whether this\n" @@ -6848,8 +6981,8 @@ msgid "" " either individually or as a group. Any\n" " posting from a non-member who is not explicitly accepted,\n" " rejected, or discarded, will have their posting filtered by the\n" -" general\n" +" general\n" " non-member rules.\n" "\n" "

                    In the text boxes below, add one address per line; start the\n" @@ -6875,13 +7008,13 @@ msgstr "" "levelei alapesetben moderálással vagy anélkül jelenjenek meg.\n" "\n" "

                    Külsõ beküldõk leveleit automatikusan, egyenként vagy\n" -"csoportba foglalva engedélyezheted,\n" "jóváhagyásra\n" "küldheted,\n" -"visszautasíthatod\n" +"visszautasíthatod\n" "(értesítéssel), vagy elvetheted. Azokra a külsõ beküldõkre, akikre nincsen érvényes " @@ -6909,6 +7042,7 @@ msgid "By default, should new list member postings be moderated?" msgstr "Kell alapesetben az új listatag leveleit moderálni?" #: Mailman/Gui/Privacy.py:218 +#, fuzzy msgid "" "Each list member has a moderation flag which says\n" " whether messages from the list member can be posted directly " @@ -6946,8 +7080,8 @@ msgstr "" "a\n" "levelek alapesetben elõször szerkesztõi jóváhagyásra kerülnek. " "Listatagoknál\n" -"egyenként lehet állítani a moderálási jelzõt a \n" +"egyenként lehet állítani a moderálási jelzõt a \n" "listatagok kezelése oldalon." #: Mailman/Gui/Privacy.py:234 @@ -6963,8 +7097,8 @@ msgid "" "If a member posts this many times, within a period of time\n" " the member is automatically moderated. Use 0 to disable. " "See\n" -" member_verbosity_interval for details on the time " "period.\n" "\n" @@ -7408,8 +7542,8 @@ msgstr "" "tartani,\n" "visszautasítani\n" -"(értesítéssel) és elvetni lehet. Ha a fentiek közül egyik helyen sincs a feladó\n" "felsorolva, akkor az itt megadott beállítás kerül érvényre." @@ -7655,6 +7789,7 @@ msgstr "" "Hiányosan megadott feltételeket a program figyelmen kívül hagy." #: Mailman/Gui/Privacy.py:693 +#, fuzzy msgid "" "The header filter rule pattern\n" " '%(safepattern)s' is not a legal regular expression. This\n" @@ -7705,8 +7840,8 @@ msgid "" "

                    The body of the message can also be optionally scanned for\n" " Subject: and Keywords: headers, as\n" " specified by the topics_bodylines_limit\n" +" href=\"?VARHELP=topics/" +"topics_bodylines_limit\">topics_bodylines_limit\n" " configuration variable." msgstr "" "A témaszûrõ minden egyes bejövõ levelet megvizsgál a késõbbiekben megadott " @@ -7790,6 +7925,7 @@ msgstr "" "Hiányosan megadott témákat a program figyelmen kívül hagy." #: Mailman/Gui/Topics.py:135 +#, fuzzy msgid "" "The topic pattern '%(safepattern)s' is not a\n" " legal regular expression. It will be discarded." @@ -8005,6 +8141,7 @@ msgid "%(listinfo_link)s list run by %(owner_link)s" msgstr "%(listinfo_link)s listát mûködteti: %(owner_link)s" #: Mailman/HTMLFormatter.py:57 +#, fuzzy msgid "%(realname)s administrative interface" msgstr "%(realname)s adminisztrációs felület" @@ -8013,6 +8150,7 @@ msgid " (requires authorization)" msgstr " (azonosítás szükséges)" #: Mailman/HTMLFormatter.py:61 +#, fuzzy msgid "Overview of all %(hostname)s mailing lists" msgstr "%(hostname)s levelezõlisták" @@ -8033,6 +8171,7 @@ msgid "; it was disabled by the list administrator" msgstr "A lista adminisztrátor döntése alapján" #: Mailman/HTMLFormatter.py:146 +#, fuzzy msgid "" "; it was disabled due to excessive bounces. The\n" " last bounce was received on %(date)s" @@ -8045,6 +8184,7 @@ msgid "; it was disabled for unknown reasons" msgstr "Ismeretlen ok miatt" #: Mailman/HTMLFormatter.py:151 +#, fuzzy msgid "Note: your list delivery is currently disabled%(reason)s." msgstr "Megjegyzés: %(reason)s a listáról jelenleg nem kapsz levelet." @@ -8057,6 +8197,7 @@ msgid "the list administrator" msgstr "a lista adminisztrátor" #: Mailman/HTMLFormatter.py:157 +#, fuzzy msgid "" "

                    %(note)s\n" "\n" @@ -8075,6 +8216,7 @@ msgstr "" "\t %(mailto)s címre." #: Mailman/HTMLFormatter.py:169 +#, fuzzy msgid "" "

                    We have received some recent bounces from your\n" " address. Your current bounce score is %(score)s out of " @@ -8095,6 +8237,7 @@ msgstr "" "probléma megszûnik." #: Mailman/HTMLFormatter.py:181 +#, fuzzy msgid "" "(Note - you are subscribing to a list of mailing lists, so the %(type)s " "notice will be sent to the admin address for your membership, %(addr)s.)

                    " @@ -8138,12 +8281,14 @@ msgstr "" "mailben értesítünk. " #: Mailman/HTMLFormatter.py:208 +#, fuzzy msgid "" "This is %(also)sa private list, which means that the\n" " list of members is not available to non-members." msgstr "A lista %(also)szártkörû, a tagok listája külsõsöknek nem elérhetõ. " #: Mailman/HTMLFormatter.py:211 +#, fuzzy msgid "" "This is %(also)sa hidden list, which means that the\n" " list of members is available only to the list administrator." @@ -8152,6 +8297,7 @@ msgstr "" "adminisztrátora számára érhetõ el. " #: Mailman/HTMLFormatter.py:214 +#, fuzzy msgid "" "This is %(also)sa public list, which means that the\n" " list of members list is available to everyone." @@ -8168,6 +8314,7 @@ msgstr "" "spammereknek). " #: Mailman/HTMLFormatter.py:222 +#, fuzzy msgid "" "

                    (Note that this is an umbrella list, intended to\n" " have only other mailing lists as members. Among other things,\n" @@ -8183,6 +8330,7 @@ msgid "either " msgstr "vagy " #: Mailman/HTMLFormatter.py:256 +#, fuzzy msgid "" "To unsubscribe from %(realname)s, get a password reminder,\n" " or change your subscription options %(either)senter your " @@ -8216,12 +8364,14 @@ msgstr "" "\tcímedet" #: Mailman/HTMLFormatter.py:277 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " members.)" msgstr "(%(which)s csak a lista tagjai számára érhetõ el.)" #: Mailman/HTMLFormatter.py:281 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " administrator.)" @@ -8282,6 +8432,7 @@ msgid "The current archive" msgstr "Az aktuális archívum" #: Mailman/Handlers/Acknowledge.py:59 +#, fuzzy msgid "%(realname)s post acknowledgement" msgstr "%(realname)s levél nyugtázva" @@ -8294,6 +8445,7 @@ msgid "" msgstr "" #: Mailman/Handlers/CalcRecips.py:79 +#, fuzzy msgid "" "Your urgent message to the %(realname)s mailing list was not authorized for\n" "delivery. The original message as received by Mailman is attached.\n" @@ -8370,6 +8522,7 @@ msgid "Message may contain administrivia" msgstr "A levél valószínûleg adminisztrációs tartalmú." #: Mailman/Handlers/Hold.py:84 +#, fuzzy msgid "" "Please do *not* post administrative requests to the mailing\n" "list. If you wish to subscribe, visit %(listurl)s or send a message with " @@ -8411,10 +8564,12 @@ msgid "Posting to a moderated newsgroup" msgstr "A levél moderált hírcsoportra érkezett." #: Mailman/Handlers/Hold.py:252 +#, fuzzy msgid "Your message to %(listname)s awaits moderator approval" msgstr "A(z) %(listname)s listára küldött leveled szerkesztõi engedélyére vár" #: Mailman/Handlers/Hold.py:271 +#, fuzzy msgid "%(listname)s post from %(sender)s requires approval" msgstr "" "A(z) %(listname)s listára %(sender)s feladótól jövõ levél engedélyezésre vár" @@ -8458,6 +8613,7 @@ msgid "After content filtering, the message was empty" msgstr "A tartalom szûrés miatt üres lett a levél." #: Mailman/Handlers/MimeDel.py:269 +#, fuzzy msgid "" "The attached message matched the %(listname)s mailing list's content " "filtering\n" @@ -8502,6 +8658,7 @@ msgid "The attached message has been automatically discarded." msgstr "A mellékletet automatikusan töröltem." #: Mailman/Handlers/Replybot.py:75 +#, fuzzy msgid "Auto-response for your message to the \"%(realname)s\" mailing list" msgstr "" "Automatikus válasz a(z) \"%(realname)s\" levelezõlistára küldött üzenetedre" @@ -8522,6 +8679,7 @@ msgid "HTML attachment scrubbed and removed" msgstr "A csatolt HTML állomány át lett konvertálva és törölve." #: Mailman/Handlers/Scrubber.py:234 Mailman/Handlers/Scrubber.py:259 +#, fuzzy msgid "" "An HTML attachment was scrubbed...\n" "URL: %(url)s\n" @@ -8609,6 +8767,7 @@ msgstr "" "feltételre" #: Mailman/Handlers/ToDigest.py:173 +#, fuzzy msgid "%(realname)s Digest, Vol %(volume)d, Issue %(issue)d" msgstr "%(realname)s digest, %(volume)d kötet, %(issue)d szám" @@ -8645,6 +8804,7 @@ msgid "End of " msgstr "Vége: " #: Mailman/ListAdmin.py:309 +#, fuzzy msgid "Posting of your message titled \"%(subject)s\"" msgstr "A leveled tárgya: \"%(subject)s\"" @@ -8657,6 +8817,7 @@ msgid "Forward of moderated message" msgstr "Továbbküldött moderált levél" #: Mailman/ListAdmin.py:405 +#, fuzzy msgid "New subscription request to list %(realname)s from %(addr)s" msgstr "%(realname)s listára %(addr)s feliratkozási kérelme" @@ -8670,6 +8831,7 @@ msgid "via admin approval" msgstr "Jóváhagyásra várakozás" #: Mailman/ListAdmin.py:466 +#, fuzzy msgid "New unsubscription request from %(realname)s by %(addr)s" msgstr "%(addr)s leiratkozási kérelme %(realname)s listáról" @@ -8682,11 +8844,13 @@ msgid "Original Message" msgstr "Eredeti üzenet" #: Mailman/ListAdmin.py:526 +#, fuzzy msgid "Request to mailing list %(realname)s rejected" msgstr "" "A(z) %(realname)s levelezõlistára való feliratkozásod vissza lett utasítva" #: Mailman/MTA/Manual.py:66 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been created via the through-the-web\n" "interface. In order to complete the activation of this mailing list, the\n" @@ -8712,14 +8876,17 @@ msgstr "" "futtatni utána a `newaliases' programot:\n" #: Mailman/MTA/Manual.py:82 +#, fuzzy msgid "## %(listname)s mailing list" msgstr "## %(listname)s levelezõlista" #: Mailman/MTA/Manual.py:99 +#, fuzzy msgid "Mailing list creation request for list %(listname)s" msgstr "%(listname)s levelezõlista létrehozás kérelme" #: Mailman/MTA/Manual.py:113 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been removed via the through-the-web\n" "interface. In order to complete the de-activation of this mailing list, " @@ -8736,6 +8903,7 @@ msgstr "" "A következõ sorokat kell az /etc/aliases állományból törölni:\n" #: Mailman/MTA/Manual.py:123 +#, fuzzy msgid "" "\n" "To finish removing your mailing list, you must edit your /etc/aliases (or\n" @@ -8752,14 +8920,17 @@ msgstr "" "## %(listname)s levelezõlista" #: Mailman/MTA/Manual.py:142 +#, fuzzy msgid "Mailing list removal request for list %(listname)s" msgstr "Kérelem a(z) %(listname)s levelezõlista törlésére" #: Mailman/MTA/Postfix.py:442 +#, fuzzy msgid "checking permissions on %(file)s" msgstr "jogosultságok ellenõrzése a(z) %(file)s fájlon" #: Mailman/MTA/Postfix.py:452 +#, fuzzy msgid "%(file)s permissions must be 0664 (got %(octmode)s)" msgstr "%(file)s jogosultságának 0664-nak kell lennie (most %(octmode)s)" @@ -8773,14 +8944,17 @@ msgid "(fixing)" msgstr "(kijavítva)" #: Mailman/MTA/Postfix.py:470 +#, fuzzy msgid "checking ownership of %(dbfile)s" msgstr "tulajdonos ellenõrzése a %(dbfile)s fájlon" #: Mailman/MTA/Postfix.py:478 +#, fuzzy msgid "%(dbfile)s owned by %(owner)s (must be owned by %(user)s" msgstr "%(dbfile)s tulajdonosa %(owner)s (%(user)s legyen a tulajdonos)" #: Mailman/MTA/Postfix.py:491 +#, fuzzy msgid "%(dbfile)s permissions must be 0664 (got %(octmode)s)" msgstr "%(dbfile)s jogosultságának 0664-nak kell lennie (most %(octmode)s)" @@ -8795,15 +8969,18 @@ msgid "Your confirmation is required to leave the %(listname)s mailing list" msgstr "Nem vagy tagja a(z) %(listname)s levelezõlistának." #: Mailman/MailList.py:999 Mailman/MailList.py:1492 +#, fuzzy msgid " from %(remote)s" msgstr " %(remote)s" #: Mailman/MailList.py:1043 +#, fuzzy msgid "subscriptions to %(realname)s require moderator approval" msgstr "" "feliratkozáshoz a(z) %(realname)s listára szerkesztõi jóváhagyás szükséges" #: Mailman/MailList.py:1125 bin/add_members:299 +#, fuzzy msgid "%(realname)s subscription notification" msgstr "Értesítés feliratkozásról a(z) %(realname)s listán" @@ -8812,6 +8989,7 @@ msgid "unsubscriptions require moderator approval" msgstr "leiratkozáshoz szerkesztõi jóváhagyás szükséges" #: Mailman/MailList.py:1166 +#, fuzzy msgid "%(realname)s unsubscribe notification" msgstr "Értesítés leiratkozásról a(z) %(realname)s listán" @@ -8831,6 +9009,7 @@ msgid "via web confirmation" msgstr "Hibás megerõsítési azonosító" #: Mailman/MailList.py:1396 +#, fuzzy msgid "subscriptions to %(name)s require administrator approval" msgstr "" "feliratkozáshoz a(z) %(name)s listára adminisztrátori jóváhagyás szükséges" @@ -8850,6 +9029,7 @@ msgid "Last autoresponse notification for today" msgstr "A mai napra az utolsó automatikus válasz" #: Mailman/Queue/BounceRunner.py:360 +#, fuzzy msgid "" "The attached message was received as a bounce, but either the bounce format\n" "was not recognized, or no member addresses could be extracted from it. " @@ -8938,6 +9118,7 @@ msgid "Original message suppressed by Mailman site configuration\n" msgstr "" #: Mailman/htmlformat.py:675 +#, fuzzy msgid "Delivered by Mailman
                    version %(version)s" msgstr "Mailman listakezelõ
                    %(version)s verzió" @@ -9026,6 +9207,7 @@ msgid "Server Local Time" msgstr "Helyi idõ a rendszeren" #: Mailman/i18n.py:180 +#, fuzzy msgid "" "%(wday)s %(mon)s %(day)2i %(hh)02i:%(mm)02i:%(ss)02i %(tzname)s %(year)04i" msgstr "" @@ -9138,6 +9320,7 @@ msgstr "" "Legfeljebb az egyik állománynévnek adható meg `-'.\n" #: bin/add_members:162 bin/add_members:172 +#, fuzzy msgid "Already a member: %(member)s" msgstr "Már tag: %(member)s" @@ -9146,10 +9329,12 @@ msgid "Bad/Invalid email address: blank line" msgstr "Hibás/érvénytelen e-mail cím: üres sor" #: bin/add_members:180 +#, fuzzy msgid "Bad/Invalid email address: %(member)s" msgstr "Hibás/érvénytelen e-mail cím: %(member)s" #: bin/add_members:182 +#, fuzzy msgid "Hostile address (illegal characters): %(member)s" msgstr "Tiltott cím (illegális karakterek): %(member)s" @@ -9159,14 +9344,17 @@ msgid "Invited: %(member)s" msgstr "Felírva: %(member)s" #: bin/add_members:187 +#, fuzzy msgid "Subscribed: %(member)s" msgstr "Felírva: %(member)s" #: bin/add_members:237 +#, fuzzy msgid "Bad argument to -w/--welcome-msg: %(arg)s" msgstr "Hibás paraméter a -w/--welcome-msg kapcsolónál: %(arg)s" #: bin/add_members:244 +#, fuzzy msgid "Bad argument to -a/--admin-notify: %(arg)s" msgstr "Hibás paraméter az -a/--admin-notify kapcsolónál: %(arg)s" @@ -9183,6 +9371,7 @@ msgstr "" #: bin/add_members:261 bin/config_list:110 bin/export.py:271 bin/find_member:97 #: bin/inject:91 bin/list_admins:90 bin/list_members:252 bin/sync_members:222 #: cron/bumpdigests:86 +#, fuzzy msgid "No such list: %(listname)s" msgstr "Nincs %(listname)s nevû lista" @@ -9193,6 +9382,7 @@ msgid "Nothing to do." msgstr "Nincs teendõ." #: bin/arch:19 +#, fuzzy msgid "" "Rebuild a list's archive.\n" "\n" @@ -9285,6 +9475,7 @@ msgid "listname is required" msgstr "listanevet is meg kell adni" #: bin/arch:143 bin/change_pw:107 bin/config_list:257 +#, fuzzy msgid "" "No such list \"%(listname)s\"\n" "%(e)s" @@ -9293,10 +9484,12 @@ msgstr "" "%(e)s" #: bin/arch:168 +#, fuzzy msgid "Cannot open mbox file %(mbox)s: %(msg)s" msgstr "%(mbox)s mbox állomány nem nyitható meg: %(msg)s" #: bin/b4b5-archfix:19 +#, fuzzy msgid "" "Fix the MM2.1b4 archives.\n" "\n" @@ -9440,6 +9633,7 @@ msgstr "" "\t Megjeleníti ezt a súgót és kilép.\n" #: bin/change_pw:145 +#, fuzzy msgid "Bad arguments: %(strargs)s" msgstr "Hibás paraméterek: %(strargs)s" @@ -9448,14 +9642,17 @@ msgid "Empty list passwords are not allowed" msgstr "Üres admin jelszót nem lehet megadni" #: bin/change_pw:181 +#, fuzzy msgid "New %(listname)s password: %(notifypassword)s" msgstr "Az új %(listname)s induló jelszava: %(notifypassword)s" #: bin/change_pw:190 +#, fuzzy msgid "Your new %(listname)s list password" msgstr "Az új %(listname)s lista induló jelszava: " #: bin/change_pw:191 +#, fuzzy msgid "" "The site administrator at %(hostname)s has changed the password for your\n" "mailing list %(listname)s. It is now\n" @@ -9483,6 +9680,7 @@ msgstr "" " %(adminurl)s\n" #: bin/check_db:19 +#, fuzzy msgid "" "Check a list's config database file for integrity.\n" "\n" @@ -9557,6 +9755,7 @@ msgid "List:" msgstr "Lista:" #: bin/check_db:148 +#, fuzzy msgid " %(file)s: okay" msgstr " %(file)s: rendben" @@ -9582,42 +9781,52 @@ msgstr "" "\n" #: bin/check_perms:110 +#, fuzzy msgid " checking gid and mode for %(path)s" msgstr " gid és jogok ellenõrzése a következõn: %(path)s" #: bin/check_perms:122 +#, fuzzy msgid "%(path)s bad group (has: %(groupname)s, expected %(MAILMAN_GROUP)s)" msgstr "%(path)s hibás giddel (most: %(groupname)s, várt: %(MAILMAN_GROUP)s)" #: bin/check_perms:151 +#, fuzzy msgid "directory permissions must be %(octperms)s: %(path)s" msgstr "Könyvtár jogosultságnak %(octperms)s-nek kell lennie: %(path)s" #: bin/check_perms:160 +#, fuzzy msgid "source perms must be %(octperms)s: %(path)s" msgstr "a forrás jogosultságának %(octperms)s-nek kell lennie: %(path)s" #: bin/check_perms:171 +#, fuzzy msgid "article db files must be %(octperms)s: %(path)s" msgstr "article db jogosultságának %(octperms)s-nek kell lennie: %(path)s" #: bin/check_perms:183 +#, fuzzy msgid "checking mode for %(prefix)s" msgstr "jogok ellenõrzése a következõn: %(prefix)s" #: bin/check_perms:193 +#, fuzzy msgid "WARNING: directory does not exist: %(d)s" msgstr "FIGYELMEZTETÉS: az alábbi könyvtár nem létezik: %(d)s" #: bin/check_perms:197 +#, fuzzy msgid "directory must be at least 02775: %(d)s" msgstr "a könyvtár jogosultságának 02775 -nek kell lennie: %(d)s" #: bin/check_perms:209 +#, fuzzy msgid "checking perms on %(private)s" msgstr "jogosultságok ellenõrzése a következõn: %(private)s" #: bin/check_perms:214 +#, fuzzy msgid "%(private)s must not be other-readable" msgstr "%(private)s nem lehet olvasható más számára" @@ -9635,6 +9844,7 @@ msgid "mbox file must be at least 0660:" msgstr "A mbox-nak legalább 0660 jogosultsággal kell rendelkeznie:" #: bin/check_perms:263 +#, fuzzy msgid "%(dbdir)s \"other\" perms must be 000" msgstr "%(dbdir)s \"többiek\" jogosultságának 000 -nak kell lennie" @@ -9643,26 +9853,32 @@ msgid "checking cgi-bin permissions" msgstr "cgi-bin jogosultságainak ellenõrzése" #: bin/check_perms:278 +#, fuzzy msgid " checking set-gid for %(path)s" msgstr " set-gid ellenõrzése a következõn: %(path)s" #: bin/check_perms:282 +#, fuzzy msgid "%(path)s must be set-gid" msgstr "%(path)s set-gid-nek kell lennie" #: bin/check_perms:292 +#, fuzzy msgid "checking set-gid for %(wrapper)s" msgstr "%(wrapper)s sit-gid állapotának ellenõrzése" #: bin/check_perms:296 +#, fuzzy msgid "%(wrapper)s must be set-gid" msgstr "%(wrapper)s set-gid-esnek kell lennie" #: bin/check_perms:306 +#, fuzzy msgid "checking permissions on %(pwfile)s" msgstr "%(pwfile)s jogosultságainak ellenõrzése" #: bin/check_perms:315 +#, fuzzy msgid "%(pwfile)s permissions must be exactly 0640 (got %(octmode)s)" msgstr "%(pwfile)s jogosultságnak 0640-nek kell lennie (most %(octmode)s)" @@ -9671,10 +9887,12 @@ msgid "checking permissions on list data" msgstr "jogosultságok ellenõrzése a lista adatain" #: bin/check_perms:348 +#, fuzzy msgid " checking permissions on: %(path)s" msgstr " jogosultságok ellenõrzése: %(path)s" #: bin/check_perms:356 +#, fuzzy msgid "file permissions must be at least 660: %(path)s" msgstr "állomány jogosultságnak legalább 660-nak kell lennie: %(path)s" @@ -9758,6 +9976,7 @@ msgid "Unix-From line changed: %(lineno)d" msgstr "Unix-From sor megváltoztatva: %(lineno)d" #: bin/cleanarch:111 +#, fuzzy msgid "Bad status number: %(arg)s" msgstr "Hibás jelzõszám: %(arg)s" @@ -9904,10 +10123,12 @@ msgid " original address removed:" msgstr " eredeti cím törölve:" #: bin/clone_member:202 +#, fuzzy msgid "Not a valid email address: %(toaddr)s" msgstr "Nem érvényes az e-mail cím: %(toaddr)s" #: bin/clone_member:215 +#, fuzzy msgid "" "Error opening list \"%(listname)s\", skipping.\n" "%(e)s" @@ -10015,6 +10236,7 @@ msgstr "" "\n" #: bin/config_list:118 +#, fuzzy msgid "" "# -*- python -*-\n" "# -*- coding: %(charset)s -*-\n" @@ -10035,22 +10257,27 @@ msgid "legal values are:" msgstr "megadható értékek:" #: bin/config_list:270 +#, fuzzy msgid "attribute \"%(k)s\" ignored" msgstr "\"%(k)s\" attribútum figyelmen kívül hagyva" #: bin/config_list:273 +#, fuzzy msgid "attribute \"%(k)s\" changed" msgstr "\"%(k)s\" attribútum beállítva" #: bin/config_list:279 +#, fuzzy msgid "Non-standard property restored: %(k)s" msgstr "Nem jól megadott változó visszaállítva: %(k)s" #: bin/config_list:288 +#, fuzzy msgid "Invalid value for property: %(k)s" msgstr "Érvénytelen érték a(z) %(k)s változónál." #: bin/config_list:291 +#, fuzzy msgid "Bad email address for option %(k)s: %(v)s" msgstr "Rossz e-mail cím lett a(z) %(k)s részben megadva: %(v)s" @@ -10116,19 +10343,23 @@ msgstr "" " Nem ír ki állapot üzeneteket.\n" #: bin/discard:94 +#, fuzzy msgid "Ignoring non-held message: %(f)s" msgstr "Jóváhagyásra nem váró levél átugrása: %(f)s" #: bin/discard:100 +#, fuzzy msgid "Ignoring held msg w/bad id: %(f)s" msgstr "Hibás azonosítóval rendelkezõ jóváhagyásra váró levél törlése: %(f)s" #: bin/discard:112 +#, fuzzy msgid "Discarded held msg #%(id)s for list %(listname)s" msgstr "" "#%(id)s sorszámú jóváhagyásra váró levél törlése a(z) %(listname)s listáról." #: bin/dumpdb:19 +#, fuzzy msgid "" "Dump the contents of any Mailman `database' file.\n" "\n" @@ -10199,6 +10430,7 @@ msgid "No filename given." msgstr "Nem lett állománynév megadva" #: bin/dumpdb:108 +#, fuzzy msgid "Bad arguments: %(pargs)s" msgstr "Hibás paraméterek: %(pargs)s" @@ -10207,14 +10439,17 @@ msgid "Please specify either -p or -m." msgstr "Kérlek vagy a -p vagy az -m kapcsolót add meg." #: bin/dumpdb:133 +#, fuzzy msgid "[----- start %(typename)s file -----]" msgstr "[----- %(typename)s fájl kezdete -----]" #: bin/dumpdb:139 +#, fuzzy msgid "[----- end %(typename)s file -----]" msgstr "[----- %(typename)s fájl vége -----]" #: bin/dumpdb:142 +#, fuzzy msgid "<----- start object %(cnt)s ----->" msgstr "<----- %(cnt)s objektum kezdete ----->" @@ -10421,6 +10656,7 @@ msgid "Setting web_page_url to: %(web_page_url)s" msgstr "web_page_url értékének beállítása: %(web_page_url)s" #: bin/fix_url.py:88 +#, fuzzy msgid "Setting host_name to: %(mailhost)s" msgstr "host_name értékének beállítása: %(mailhost)s" @@ -10516,6 +10752,7 @@ msgstr "" "bemeneti forrást használja a program.\n" #: bin/inject:84 +#, fuzzy msgid "Bad queue directory: %(qdir)s" msgstr "Hibás feldolgozási sor könyvtár: %(qdir)s" @@ -10524,6 +10761,7 @@ msgid "A list name is required" msgstr "Egy listanevet is meg kell adni." #: bin/list_admins:20 +#, fuzzy msgid "" "List all the owners of a mailing list.\n" "\n" @@ -10567,6 +10805,7 @@ msgstr "" " Kiírja ezt a súgót és kilép.\n" #: bin/list_admins:97 +#, fuzzy msgid "List: %(listname)s, \tOwners: %(owners)s" msgstr "Lista: %(listname)s, \tTulajdonosok: %(owners)s" @@ -10754,10 +10993,12 @@ msgstr "" "meg.\n" #: bin/list_members:198 +#, fuzzy msgid "Bad --nomail option: %(why)s" msgstr "Hibás --nomail kapcsoló: %(why)s" #: bin/list_members:209 +#, fuzzy msgid "Bad --digest option: %(kind)s" msgstr "Hibás --digest kapcsoló: %(kind)s" @@ -10771,6 +11012,7 @@ msgid "Could not open file for writing:" msgstr "Nem lehet írásra megnyitni az állományt:" #: bin/list_owners:20 +#, fuzzy msgid "" "List the owners of a mailing list, or all mailing lists.\n" "\n" @@ -10826,6 +11068,7 @@ msgid "" msgstr "" #: bin/mailmanctl:20 +#, fuzzy msgid "" "Primary start-up and shutdown script for Mailman's qrunner daemon.\n" "\n" @@ -10997,6 +11240,7 @@ msgstr "" " reopen - A naplóállományokat újra nyitja.\n" #: bin/mailmanctl:152 +#, fuzzy msgid "PID unreadable in: %(pidfile)s" msgstr "Nem lehet kiolvasni a PID-et: %(pidfile)s" @@ -11005,6 +11249,7 @@ msgid "Is qrunner even running?" msgstr "A qrunner még mindig fut?" #: bin/mailmanctl:160 +#, fuzzy msgid "No child with pid: %(pid)s" msgstr "A következõ pid-del nem található alfolyamat: %(pid)s" @@ -11031,6 +11276,7 @@ msgstr "" "zárolás van jelen. Használjuk a mailmanctl -s kapcsolóját.\n" #: bin/mailmanctl:233 +#, fuzzy msgid "" "The master qrunner lock could not be acquired, because it appears as if " "some\n" @@ -11056,10 +11302,12 @@ msgstr "" "Kilépek." #: bin/mailmanctl:280 cron/mailpasswds:119 +#, fuzzy msgid "Site list is missing: %(sitelistname)s" msgstr "Hiányzik a rendszerszintû lista: %(sitelistname)s" #: bin/mailmanctl:305 +#, fuzzy msgid "Run this program as root or as the %(name)s user, or use -u." msgstr "" "A programot root-ként vagy %(name)s felhasználóként futtasd, vagy\n" @@ -11070,6 +11318,7 @@ msgid "No command given." msgstr "Nincs parancs megadva" #: bin/mailmanctl:339 +#, fuzzy msgid "Bad command: %(command)s" msgstr "Hibás parancs: %(command)s" @@ -11094,6 +11343,7 @@ msgid "Starting Mailman's master qrunner." msgstr "Folyamat indítása: Mailman fõ qrunner" #: bin/mmsitepass:19 +#, fuzzy msgid "" "Set the site password, prompting from the terminal.\n" "\n" @@ -11147,6 +11397,7 @@ msgid "list creator" msgstr "listalétrehozó" #: bin/mmsitepass:86 +#, fuzzy msgid "New %(pwdesc)s password: " msgstr "Új %(pwdesc)s jelszó: " @@ -11375,6 +11626,7 @@ msgstr "" "Fontos, hogy a lista neve mindenképp kisbetûs lesz.\n" #: bin/newlist:162 +#, fuzzy msgid "Unknown language: %(lang)s" msgstr "Ismeretlen nyelv: %(lang)s" @@ -11387,6 +11639,7 @@ msgid "Enter the email of the person running the list: " msgstr "Add meg a listát mûködtetõ e-mail címét: " #: bin/newlist:193 +#, fuzzy msgid "Initial %(listname)s password: " msgstr "%(listname)s induló jelszava: " @@ -11396,11 +11649,12 @@ msgstr "Nem lehet #: bin/newlist:220 msgid "" -" - owner addresses need to be fully-qualified names like \"owner@example.com" -"\", not just \"owner\"." +" - owner addresses need to be fully-qualified names like \"owner@example." +"com\", not just \"owner\"." msgstr "" #: bin/newlist:244 +#, fuzzy msgid "Hit enter to notify %(listname)s owner..." msgstr "Nyomd meg az entert a(z) %(listname)s tulajdonosának értesítéséhez..." @@ -11521,6 +11775,7 @@ msgstr "" "adva. A névnek az -l kapcsolóval felsoroltak közül kell lennie.\n" #: bin/qrunner:179 +#, fuzzy msgid "%(name)s runs the %(runnername)s qrunner" msgstr "-r %(name)s a %(runnername)s qrunner-t futtatja" @@ -11533,6 +11788,7 @@ msgid "No runner name given." msgstr "Nem lett runnernév megadva." #: bin/rb-archfix:21 +#, fuzzy msgid "" "Reduce disk space usage for Pipermail archives.\n" "\n" @@ -11678,18 +11934,22 @@ msgstr "" "\n" #: bin/remove_members:156 +#, fuzzy msgid "Could not open file for reading: %(filename)s." msgstr "%(filename)s állományt nem lehet olvasni." #: bin/remove_members:163 +#, fuzzy msgid "Error opening list %(listname)s... skipping." msgstr "Hiba a(z) %(listname)s lista megnyitásakor... átugorva." #: bin/remove_members:173 +#, fuzzy msgid "No such member: %(addr)s" msgstr "Nincs ilyen listatag: %(addr)s" #: bin/remove_members:178 +#, fuzzy msgid "User `%(addr)s' removed from list: %(listname)s." msgstr "`%(addr)s' felhasználó címének törlése a(z) %(listname)s listáról." @@ -11762,18 +12022,22 @@ msgstr "" "\n" #: bin/rmlist:73 bin/rmlist:76 +#, fuzzy msgid "Removing %(msg)s" msgstr "Eltávolítva %(msg)s" #: bin/rmlist:81 +#, fuzzy msgid "%(listname)s %(msg)s not found as %(filename)s" msgstr "%(listname)s %(msg)s nem található %(filename)s fájlnévvel" #: bin/rmlist:105 +#, fuzzy msgid "No such list (or list already deleted): %(listname)s" msgstr "Nincs %(listname)s nevû lista (vagy már törölték)." #: bin/rmlist:108 +#, fuzzy msgid "No such list: %(listname)s. Removing its residual archives." msgstr "Nincs %(listname)s nevû lista. Megmaradt archívuma törölve." @@ -11834,6 +12098,7 @@ msgstr "" "Példa: show_qfiles qfiles/shunt/*.pck\n" #: bin/sync_members:19 +#, fuzzy msgid "" "Synchronize a mailing list's membership with a flat file.\n" "\n" @@ -11967,6 +12232,7 @@ msgstr "" "\t Kötelezõ megadni. A megadott listán hajtja végre a módosításokat.\n" #: bin/sync_members:115 +#, fuzzy msgid "Bad choice: %(yesno)s" msgstr "Rossz választás: %(yesno)s" @@ -11983,6 +12249,7 @@ msgid "No argument to -f given" msgstr "Nem lett az -f kapcsolóval érték megadva" #: bin/sync_members:172 +#, fuzzy msgid "Illegal option: %(opt)s" msgstr "Érvénytelen kapcsoló: %(opt)s" @@ -11995,6 +12262,7 @@ msgid "Must have a listname and a filename" msgstr "Meg kell adni egy lista- és állománynevet" #: bin/sync_members:191 +#, fuzzy msgid "Cannot read address file: %(filename)s: %(msg)s" msgstr "A címeket tartalmazó állomány nem olvasható: %(filename)s: %(msg)s " @@ -12011,14 +12279,17 @@ msgid "You must fix the preceding invalid addresses first." msgstr "Elõször az érvénytelen címeket kell kijavítanod." #: bin/sync_members:264 +#, fuzzy msgid "Added : %(s)s" msgstr "Hozzáadva: %(s)s)" #: bin/sync_members:289 +#, fuzzy msgid "Removed: %(s)s" msgstr "Eltávolítva %(s)s" #: bin/transcheck:19 +#, fuzzy msgid "" "\n" "Check a given Mailman translation, making sure that variables and\n" @@ -12127,6 +12398,7 @@ msgstr "" "másolja át az állományokat.\n" #: bin/unshunt:85 +#, fuzzy msgid "" "Cannot unshunt message %(filebase)s, skipping:\n" "%(e)s" @@ -12135,6 +12407,7 @@ msgstr "" "%(e)s" #: bin/update:20 +#, fuzzy msgid "" "Perform all necessary upgrades.\n" "\n" @@ -12170,14 +12443,17 @@ msgstr "" "a legfrisebbre. A program az 1.0b4 (?) verzióktól használható.\n" #: bin/update:107 +#, fuzzy msgid "Fixing language templates: %(listname)s" msgstr "Nyelvi sablonok javítása: %(listname)s" #: bin/update:196 bin/update:711 +#, fuzzy msgid "WARNING: could not acquire lock for list: %(listname)s" msgstr "FIGYELMEZTETÉS: nem lehet zárolni a %(listname)s listát" #: bin/update:215 +#, fuzzy msgid "Resetting %(n)s BYBOUNCEs disabled addrs with no bounce info" msgstr "%(n)s ismeretlen BYBOUNCE által kikapcsolt állapot megszüntetve" @@ -12194,6 +12470,7 @@ msgstr "" "ezért átnevezem %(mbox_dir)s.tmp-é, majd folytatom a frissítést." #: bin/update:255 +#, fuzzy msgid "" "\n" "%(listname)s has both public and private mbox archives. Since this list\n" @@ -12210,8 +12487,8 @@ msgstr "" "%(listname)s rendelkezik mind nyilvános, mind privát mbox archívummal. A " "lista\n" "jelenleg privát archiválásra van beállítva, ezért a privát mbox archívumot\n" -"-- %(o_pri_mbox_file)s -- állítom be aktuális archívumnak és átnevezem a(z)\t" -"%(o_pub_mbox_file)s\n" +"-- %(o_pri_mbox_file)s -- állítom be aktuális archívumnak és átnevezem " +"a(z)\t%(o_pub_mbox_file)s\n" "archívumot\n" "\t%(o_pub_mbox_file)s.preb6\n" "archívummá. Az 'arch' programmal bármikor össze lehet fésülni ezt az " @@ -12244,6 +12521,7 @@ msgid "- updating old private mbox file" msgstr "- régi privát mbox állomány frissítése" #: bin/update:295 +#, fuzzy msgid "" " unknown file in the way, moving\n" " %(o_pri_mbox_file)s\n" @@ -12260,6 +12538,7 @@ msgid "- updating old public mbox file" msgstr "- régi nyilvános mbox állomány frissítése" #: bin/update:317 +#, fuzzy msgid "" " unknown file in the way, moving\n" " %(o_pub_mbox_file)s\n" @@ -12289,18 +12568,22 @@ msgid "- %(o_tmpl)s doesn't exist, leaving untouched" msgstr "- %(o_tmpl)s állomány nem található, nincsen változtatás" #: bin/update:396 +#, fuzzy msgid "removing directory %(src)s and everything underneath" msgstr "%(src)s környvtár és alkönyvtárainak törlése" #: bin/update:399 +#, fuzzy msgid "removing %(src)s" msgstr "%(src)s törlése" #: bin/update:403 +#, fuzzy msgid "Warning: couldn't remove %(src)s -- %(rest)s" msgstr "Figyelmeztetés: %(src)s -t nem lehet törölni -- %(rest)s" #: bin/update:408 +#, fuzzy msgid "couldn't remove old file %(pyc)s -- %(rest)s" msgstr "régi %(pyc)s állományt nem lehet törölni -- %(rest)s" @@ -12309,10 +12592,12 @@ msgid "updating old qfiles" msgstr "régi qfiles állományok frissítése" #: bin/update:455 +#, fuzzy msgid "Warning! Not a directory: %(dirpath)s" msgstr "Hibás feldolgozási sor könyvtár: %(dirpath)s" #: bin/update:530 +#, fuzzy msgid "message is unparsable: %(filebase)s" msgstr "az üzenetet nem lehet feldolgozni: %(filebase)s" @@ -12329,11 +12614,13 @@ msgid "Updating Mailman 2.1.4 pending.pck database" msgstr "2.1.4-es Mailman pending.pck adatbázisának frissítése" #: bin/update:598 +#, fuzzy msgid "Ignoring bad pended data: %(key)s: %(val)s" msgstr "" "Hibás beavatkozásra váró adat figyelmen kívül hagyása: %(key)s: %(val)s" #: bin/update:614 +#, fuzzy msgid "WARNING: Ignoring duplicate pending ID: %(id)s." msgstr "" "FIGYELMEZTETÉS: Alább ismétlõdõ azonosító figyelmen kívül hagyva: %(id)s" @@ -12359,6 +12646,7 @@ msgid "done" msgstr "kész" #: bin/update:691 +#, fuzzy msgid "Updating mailing list: %(listname)s" msgstr "Levelezõlista frissítése: %(listname)s" @@ -12420,6 +12708,7 @@ msgid "No updates are necessary." msgstr "Nincs szükség frissítésre." #: bin/update:796 +#, fuzzy msgid "" "Downgrade detected, from version %(hexlversion)s to version %(hextversion)s\n" "This is probably not safe.\n" @@ -12431,10 +12720,12 @@ msgstr "" "Kilépek." #: bin/update:801 +#, fuzzy msgid "Upgrading from version %(hexlversion)s to %(hextversion)s" msgstr "Frissítés %(hexlversion)s verzióról %(hextversion)s verzióra" #: bin/update:810 +#, fuzzy msgid "" "\n" "ERROR:\n" @@ -12725,6 +13016,7 @@ msgstr "" " " #: bin/withlist:175 +#, fuzzy msgid "Unlocking (but not saving) list: %(listname)s" msgstr "Zárolása megszüntetése (mentés nélkül) a(z) %(listname)s listán" @@ -12733,6 +13025,7 @@ msgid "Finalizing" msgstr "Véglegesítés" #: bin/withlist:188 +#, fuzzy msgid "Loading list %(listname)s" msgstr "%(listname)s lista betöltése" @@ -12745,6 +13038,7 @@ msgid "(unlocked)" msgstr "(zárolás megszüntetve)" #: bin/withlist:197 +#, fuzzy msgid "Unknown list: %(listname)s" msgstr "Ismeretlen lista: %(listname)s" @@ -12757,18 +13051,22 @@ msgid "--all requires --run" msgstr "--all használatához --run is szükséges" #: bin/withlist:266 +#, fuzzy msgid "Importing %(module)s..." msgstr "%(module)s beolvasása..." #: bin/withlist:270 +#, fuzzy msgid "Running %(module)s.%(callable)s()..." msgstr "Folyamatban %(module)s.%(callable)s()..." #: bin/withlist:291 +#, fuzzy msgid "The variable `m' is the %(listname)s MailList instance" msgstr "A(z) %(listname)s listára az `m' változó hivatkozik a MailListben" #: cron/bumpdigests:19 +#, fuzzy msgid "" "Increment the digest volume number and reset the digest number to one.\n" "\n" @@ -12796,6 +13094,7 @@ msgstr "" "megadva, akkor az összes listán elvégzi a változtatásokat.\n" #: cron/checkdbs:20 +#, fuzzy msgid "" "Check for pending admin requests and mail the list owners if necessary.\n" "\n" @@ -12823,6 +13122,7 @@ msgid "" msgstr "" #: cron/checkdbs:121 +#, fuzzy msgid "%(count)d %(realname)s moderator request(s) waiting" msgstr "" "%(count)d kérelem vár szerkesztõi jóváhagyásra a(z) %(realname)s listán" @@ -12851,6 +13151,7 @@ msgstr "" "Jóváhagyásra váró levelek:" #: cron/checkdbs:169 +#, fuzzy msgid "" "From: %(sender)s on %(date)s\n" "Subject: %(subject)s\n" @@ -12882,6 +13183,7 @@ msgid "" msgstr "" #: cron/disabled:20 +#, fuzzy msgid "" "Process disabled members, recommended once per day.\n" "\n" @@ -13008,6 +13310,7 @@ msgstr "" "\n" #: cron/mailpasswds:19 +#, fuzzy msgid "" "Send password reminders for all lists to all users.\n" "\n" @@ -13060,10 +13363,12 @@ msgid "Password // URL" msgstr "Jelszó // URL" #: cron/mailpasswds:222 +#, fuzzy msgid "%(host)s mailing list memberships reminder" msgstr "%(host)s levelezõlista emlékeztetõ" #: cron/nightly_gzip:19 +#, fuzzy msgid "" "Re-generate the Pipermail gzip'd archive flat files.\n" "\n" diff --git a/messages/ia/LC_MESSAGES/mailman.po b/messages/ia/LC_MESSAGES/mailman.po index 55d79498..5307686a 100644 --- a/messages/ia/LC_MESSAGES/mailman.po +++ b/messages/ia/LC_MESSAGES/mailman.po @@ -66,10 +66,12 @@ msgid "

                    Currently, there are no archives.

                    " msgstr "

                    Actualmente il non ha archivos.

                    " #: Mailman/Archiver/HyperArch.py:821 +#, fuzzy msgid "Gzip'd Text%(sz)s" msgstr "Texto comprimite con gzip%(sz)s" #: Mailman/Archiver/HyperArch.py:826 +#, fuzzy msgid "Text%(sz)s" msgstr "Texto%(sz)s" @@ -142,18 +144,22 @@ msgid "Third" msgstr "Tertie" #: Mailman/Archiver/HyperArch.py:938 +#, fuzzy msgid "%(ord)s quarter %(year)i" msgstr "%(ord)s trimestre %(year)i" #: Mailman/Archiver/HyperArch.py:945 +#, fuzzy msgid "%(month)s %(year)i" msgstr "%(month)s %(year)i" #: Mailman/Archiver/HyperArch.py:950 +#, fuzzy msgid "The Week Of Monday %(day)i %(month)s %(year)i" msgstr "Le septimana de lunedi le %(day)i de %(month)s %(year)i" #: Mailman/Archiver/HyperArch.py:954 +#, fuzzy msgid "%(day)i %(month)s %(year)i" msgstr "%(day)i de %(month)s %(year)i" @@ -162,10 +168,12 @@ msgid "Computing threaded index\n" msgstr "Computante le indice per discussion\n" #: Mailman/Archiver/HyperArch.py:1319 +#, fuzzy msgid "Updating HTML for article %(seq)s" msgstr "Actualisante le HTML pro le articulo %(seq)s" #: Mailman/Archiver/HyperArch.py:1326 +#, fuzzy msgid "article file %(filename)s is missing!" msgstr "file de articulo %(filename)s non trovate!" @@ -182,6 +190,7 @@ msgid "Pickling archive state into " msgstr "Conservante le stato del archivo in " #: Mailman/Archiver/pipermail.py:453 +#, fuzzy msgid "Updating index files for archive [%(archive)s]" msgstr "Actualisante files de indice pro le archivo [%(archive)s]" @@ -190,6 +199,7 @@ msgid " Thread" msgstr " Discussion" #: Mailman/Archiver/pipermail.py:597 +#, fuzzy msgid "#%(counter)05d %(msgid)s" msgstr "#%(counter)05d %(msgid)s" @@ -227,6 +237,7 @@ msgid "disabled address" msgstr "disactivate" #: Mailman/Bouncer.py:304 +#, fuzzy msgid " The last bounce received from you was dated %(date)s" msgstr " Le ultime message retrosaltate recipite de te data del %(date)s" @@ -254,6 +265,7 @@ msgstr "Administrator" #: Mailman/Cgi/options.py:101 Mailman/Cgi/private.py:108 #: Mailman/Cgi/rmlist.py:75 Mailman/Cgi/roster.py:59 #: Mailman/Cgi/subscribe.py:67 +#, fuzzy msgid "No such list %(safelistname)s" msgstr "Le lista %(safelistname)s non existe" @@ -326,6 +338,7 @@ msgstr "" " usque tu corrige le problema." #: Mailman/Cgi/admin.py:268 +#, fuzzy msgid "%(hostname)s mailing lists - Admin Links" msgstr "Listas in %(hostname)s - Ligamines administrative" @@ -338,6 +351,7 @@ msgid "Mailman" msgstr "Mailman" #: Mailman/Cgi/admin.py:310 +#, fuzzy msgid "" "

                    There currently are no publicly-advertised %(mailmanlink)s\n" " mailing lists on %(hostname)s." @@ -346,6 +360,7 @@ msgstr "" " visibile in %(hostname)s." #: Mailman/Cgi/admin.py:316 +#, fuzzy msgid "" "

                    Below is the collection of publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s. Click on a list\n" @@ -360,6 +375,7 @@ msgid "right " msgstr "dextra " #: Mailman/Cgi/admin.py:325 +#, fuzzy msgid "" "To visit the administrators configuration page for an\n" " unadvertised list, open a URL similar to this one, but with a '/' " @@ -404,6 +420,7 @@ msgid "No valid variable name found." msgstr "Nulle nomine de variabile valide ha essite trovate." #: Mailman/Cgi/admin.py:395 +#, fuzzy msgid "" "%(realname)s Mailing list Configuration Help\n" "
                    %(varname)s Option" @@ -412,6 +429,7 @@ msgstr "" " Option
                    %(varname)s" #: Mailman/Cgi/admin.py:402 +#, fuzzy msgid "Mailman %(varname)s List Option Help" msgstr "Adjuta pro le optiones del lista %(varname)s" @@ -430,14 +448,17 @@ msgstr "" " " #: Mailman/Cgi/admin.py:431 +#, fuzzy msgid "return to the %(categoryname)s options page." msgstr "retornar al pagina de optiones de %(categoryname)s." #: Mailman/Cgi/admin.py:446 +#, fuzzy msgid "%(realname)s Administration (%(label)s)" msgstr "Administration de %(realname)s (%(label)s)" #: Mailman/Cgi/admin.py:447 +#, fuzzy msgid "%(realname)s mailing list administration
                    %(label)s Section" msgstr "administration del lista %(realname)s
                    Section %(label)s" @@ -519,6 +540,7 @@ msgid "Value" msgstr "Valor" #: Mailman/Cgi/admin.py:668 +#, fuzzy msgid "" "Badly formed options entry:\n" " %(record)s" @@ -619,10 +641,12 @@ msgid "Move rule down" msgstr "Displaciar le regula a basso" #: Mailman/Cgi/admin.py:870 +#, fuzzy msgid "
                    (Edit %(varname)s)" msgstr "
                    (Modificar %(varname)s)" #: Mailman/Cgi/admin.py:872 +#, fuzzy msgid "
                    (Details for %(varname)s)" msgstr "
                    (Detalios pro %(varname)s)" @@ -663,6 +687,7 @@ msgid "(help)" msgstr "(adjuta)" #: Mailman/Cgi/admin.py:930 +#, fuzzy msgid "Find member %(link)s:" msgstr "Cercar membro %(link)s:" @@ -675,10 +700,12 @@ msgid "Bad regular expression: " msgstr "Expression regular invalide: " #: Mailman/Cgi/admin.py:1013 +#, fuzzy msgid "%(allcnt)s members total, %(membercnt)s shown" msgstr "%(allcnt)s membros in total, %(membercnt)s monstrate" #: Mailman/Cgi/admin.py:1016 +#, fuzzy msgid "%(allcnt)s members total" msgstr "%(allcnt)s membros in total" @@ -870,6 +897,7 @@ msgstr "" " intervallo listate infra:" #: Mailman/Cgi/admin.py:1221 +#, fuzzy msgid "from %(start)s to %(end)s" msgstr "de %(start)s a %(end)s" @@ -1007,6 +1035,7 @@ msgid "Change list ownership passwords" msgstr "Cambiar le contrasignos de proprietario del lista" #: Mailman/Cgi/admin.py:1364 +#, fuzzy msgid "" "The list administrators are the people who have ultimate control " "over\n" @@ -1126,6 +1155,7 @@ msgstr "Adresse invalide (characteres illegal)" #: Mailman/Cgi/admin.py:1529 Mailman/Cgi/admin.py:1703 bin/add_members:175 #: bin/clone_member:136 bin/sync_members:268 +#, fuzzy msgid "Banned address (matched %(pattern)s)" msgstr "Adresse bannite (corresponde a %(pattern)s)" @@ -1185,8 +1215,9 @@ msgid "%(schange_to)s is already a member" msgstr " ja es un membro" #: Mailman/Cgi/admin.py:1620 +#, fuzzy msgid "%(schange_to)s matches banned pattern %(spat)s" -msgstr "" +msgstr " ja es un membro" #: Mailman/Cgi/admin.py:1622 msgid "Address %(schange_from)s changed to %(schange_to)s" @@ -1226,6 +1257,7 @@ msgid "Not subscribed" msgstr "Non abonate" #: Mailman/Cgi/admin.py:1773 +#, fuzzy msgid "Ignoring changes to deleted member: %(user)s" msgstr "Ignorante cambiamentos al membro delite: %(user)s" @@ -1238,10 +1270,12 @@ msgid "Error Unsubscribing:" msgstr "Error in disabonar:" #: Mailman/Cgi/admindb.py:235 Mailman/Cgi/admindb.py:248 +#, fuzzy msgid "%(realname)s Administrative Database" msgstr "Pannello de controlo de %(realname)s" #: Mailman/Cgi/admindb.py:238 +#, fuzzy msgid "%(realname)s Administrative Database Results" msgstr "Resultatos del pannello de controlo de %(realname)s" @@ -1270,6 +1304,7 @@ msgid "Discard all messages marked Defer" msgstr "Abandonar tote messages marcate Postpone" #: Mailman/Cgi/admindb.py:298 +#, fuzzy msgid "all of %(esender)s's held messages." msgstr "tote le messages retenite de %(esender)s." @@ -1290,6 +1325,7 @@ msgid "list of available mailing lists." msgstr "lista del listas disponibile." #: Mailman/Cgi/admindb.py:362 +#, fuzzy msgid "You must specify a list name. Here is the %(link)s" msgstr "Tu debe specificar le nomine de un lista. Ecce le %(link)s" @@ -1371,6 +1407,7 @@ msgid "The sender is now a member of this list" msgstr "Le expeditor es ora un membro de iste lista" #: Mailman/Cgi/admindb.py:568 +#, fuzzy msgid "Add %(esender)s to one of these sender filters:" msgstr "Adde %(esender)s a un de iste filtros de expeditor:" @@ -1391,6 +1428,7 @@ msgid "Rejects" msgstr "Rejecta" #: Mailman/Cgi/admindb.py:584 +#, fuzzy msgid "" "Ban %(esender)s from ever subscribing to this\n" " mailing list" @@ -1407,6 +1445,7 @@ msgstr "" " in particular, o tu pote " #: Mailman/Cgi/admindb.py:591 +#, fuzzy msgid "view all messages from %(esender)s" msgstr "vider tote messages de %(esender)s" @@ -1485,14 +1524,16 @@ msgid " is already a member" msgstr " ja es un membro" #: Mailman/Cgi/admindb.py:973 +#, fuzzy msgid "%(addr)s is banned (matched: %(patt)s)" -msgstr "" +msgstr "Adresse bannite (corresponde a %(pattern)s)" #: Mailman/Cgi/confirm.py:88 msgid "Confirmation string was empty." msgstr "Le codice de confirmation esseva vacue." #: Mailman/Cgi/confirm.py:108 +#, fuzzy msgid "" "Invalid confirmation string:\n" " %(safecookie)s.\n" @@ -1536,6 +1577,7 @@ msgstr "" " cancellate." #: Mailman/Cgi/confirm.py:178 +#, fuzzy msgid "System error, bad content: %(content)s" msgstr "Error de systema, contento improprie: %(content)s" @@ -1574,6 +1616,7 @@ msgid "Confirm subscription request" msgstr "Confirma le requesta de abonamento" #: Mailman/Cgi/confirm.py:259 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " subscription request to the mailing list %(listname)s. Your\n" @@ -1608,6 +1651,7 @@ msgstr "" " vole abonar te a iste lista." #: Mailman/Cgi/confirm.py:275 +#, fuzzy msgid "" "Your confirmation is required in order to continue with\n" " the subscription request to the mailing list %(listname)s.\n" @@ -1662,6 +1706,7 @@ msgid "Preferred language:" msgstr "Lingua preferite:" #: Mailman/Cgi/confirm.py:317 +#, fuzzy msgid "Subscribe to list %(listname)s" msgstr "Abona me al lista %(listname)s" @@ -1678,6 +1723,7 @@ msgid "Awaiting moderator approval" msgstr "Attendente le approbation del moderator" #: Mailman/Cgi/confirm.py:386 +#, fuzzy msgid "" " You have successfully confirmed your subscription request to " "the\n" @@ -1709,6 +1755,7 @@ msgid "You are already a member of this mailing list!" msgstr "Tu ja es un membro de iste lista!" #: Mailman/Cgi/confirm.py:400 +#, fuzzy msgid "" "You are currently banned from subscribing to\n" " this list. If you think this restriction is erroneous, please\n" @@ -1733,6 +1780,7 @@ msgid "Subscription request confirmed" msgstr "Requesta de abonamento confirmate" #: Mailman/Cgi/confirm.py:418 +#, fuzzy msgid "" " You have successfully confirmed your subscription request for\n" " \"%(addr)s\" to the %(listname)s mailing list. A separate\n" @@ -1764,6 +1812,7 @@ msgid "Unsubscription request confirmed" msgstr "Requesta de disabonamento confirmate" #: Mailman/Cgi/confirm.py:469 +#, fuzzy msgid "" " You have successfully unsubscribed from the %(listname)s " "mailing\n" @@ -1785,6 +1834,7 @@ msgid "Not available" msgstr "Non disponibile" #: Mailman/Cgi/confirm.py:498 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " unsubscription request from the mailing list %(listname)s. " @@ -1830,6 +1880,7 @@ msgid "You have canceled your change of address request." msgstr "Tu ha cancellate tu requesta de cambiamento de adresse." #: Mailman/Cgi/confirm.py:555 +#, fuzzy msgid "" "%(newaddr)s is banned from subscribing to the\n" " %(realname)s list. If you think this restriction is erroneous,\n" @@ -1840,6 +1891,7 @@ msgstr "" " contacta le proprietarios del lista a %(owneraddr)s." #: Mailman/Cgi/confirm.py:560 +#, fuzzy msgid "" "%(newaddr)s is already a member of\n" " the %(realname)s list. It is possible that you are attempting\n" @@ -1855,6 +1907,7 @@ msgid "Change of address request confirmed" msgstr "Requesta de cambiamento de adresse confirmate" #: Mailman/Cgi/confirm.py:571 +#, fuzzy msgid "" " You have successfully changed your address on the %(listname)s\n" " mailing list from %(oldaddr)s to %(newaddr)s. " @@ -1877,6 +1930,7 @@ msgid "globally" msgstr "globalmente" #: Mailman/Cgi/confirm.py:605 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " change of address request for the mailing list %(listname)s. " @@ -1941,6 +1995,7 @@ msgid "Sender discarded message via web." msgstr "Le expeditor abandonava le message via web." #: Mailman/Cgi/confirm.py:674 +#, fuzzy msgid "" "The held message with the Subject:\n" " header %(subject)s could not be found. The most " @@ -1961,6 +2016,7 @@ msgid "Posted message canceled" msgstr "Invio de message cancellate" #: Mailman/Cgi/confirm.py:685 +#, fuzzy msgid "" " You have successfully canceled the posting of your message with\n" " the Subject: header %(subject)s to the mailing list\n" @@ -1983,6 +2039,7 @@ msgstr "" " ha jam essite tractate per le administrator del lista." #: Mailman/Cgi/confirm.py:735 +#, fuzzy msgid "" "Your confirmation is required in order to cancel the\n" " posting of your message to the mailing list %(listname)s:\n" @@ -2031,6 +2088,7 @@ msgid "Membership re-enabled." msgstr "Membrato reactivate." #: Mailman/Cgi/confirm.py:798 +#, fuzzy msgid "" " You have successfully re-enabled your membership in the\n" " %(listname)s mailing list. You can now not available" msgstr "non disponibile" #: Mailman/Cgi/confirm.py:846 +#, fuzzy msgid "" "Your membership in the %(realname)s mailing list is\n" " currently disabled due to excessive bounces. Your confirmation is\n" @@ -2129,10 +2189,12 @@ msgid "administrative list overview" msgstr "pagina general de administration del lista" #: Mailman/Cgi/create.py:115 +#, fuzzy msgid "List name must not include \"@\": %(safelistname)s" msgstr "Le nomine del lista non debe includer \"@\": %(safelistname)s" #: Mailman/Cgi/create.py:122 +#, fuzzy msgid "List already exists: %(safelistname)s" msgstr "Lista ja existe: %(safelistname)s" @@ -2167,18 +2229,22 @@ msgid "You are not authorized to create new mailing lists" msgstr "Tu non es autorisate a crear nove listas de diffusion" #: Mailman/Cgi/create.py:182 +#, fuzzy msgid "Unknown virtual host: %(safehostname)s" msgstr "Hospite virtual incognite: %(safehostname)s" #: Mailman/Cgi/create.py:218 bin/newlist:219 +#, fuzzy msgid "Bad owner email address: %(s)s" msgstr "Adresse de e-mail del proprietario invalide: %(s)s" #: Mailman/Cgi/create.py:223 bin/newlist:182 bin/newlist:223 +#, fuzzy msgid "List already exists: %(listname)s" msgstr "Lista ja existe: %(listname)s" #: Mailman/Cgi/create.py:231 bin/newlist:217 +#, fuzzy msgid "Illegal list name: %(s)s" msgstr "Nomine de lista invalide: %(s)s" @@ -2191,6 +2257,7 @@ msgstr "" " Per favor contacta le administrator del sito pro assistentia." #: Mailman/Cgi/create.py:273 bin/newlist:265 +#, fuzzy msgid "Your new mailing list: %(listname)s" msgstr "Tu nove lista de diffusion: %(listname)s" @@ -2199,6 +2266,7 @@ msgid "Mailing list creation results" msgstr "Resultatos del creation del lista de diffusion" #: Mailman/Cgi/create.py:288 +#, fuzzy msgid "" "You have successfully created the mailing list\n" " %(listname)s and notification has been sent to the list owner\n" @@ -2221,6 +2289,7 @@ msgid "Create another list" msgstr "Crear un altere lista" #: Mailman/Cgi/create.py:312 +#, fuzzy msgid "Create a %(hostname)s Mailing List" msgstr "Crea un lista de diffusion in %(hostname)s" @@ -2320,6 +2389,7 @@ msgstr "" " normalmente le messages del nome membros sub moderation." #: Mailman/Cgi/create.py:432 +#, fuzzy msgid "" "Initial list of supported languages.

                    Note that if you do not\n" " select at least one initial language, the list will use the server\n" @@ -2424,6 +2494,7 @@ msgid "List name is required." msgstr "Le nomine del lista es requirite." #: Mailman/Cgi/edithtml.py:154 +#, fuzzy msgid "%(realname)s -- Edit html for %(template_info)s" msgstr "%(realname)s -- Edita le html pro %(template_info)s" @@ -2432,10 +2503,12 @@ msgid "Edit HTML : Error" msgstr "Edita le HTML: Error" #: Mailman/Cgi/edithtml.py:161 +#, fuzzy msgid "%(safetemplatename)s: Invalid template" msgstr "%(safetemplatename)s: modello invalide" #: Mailman/Cgi/edithtml.py:166 Mailman/Cgi/edithtml.py:167 +#, fuzzy msgid "%(realname)s -- HTML Page Editing" msgstr "%(realname)s -- Editation del pagina HTML" @@ -2493,10 +2566,12 @@ msgid "HTML successfully updated." msgstr "HTML modificate con successo." #: Mailman/Cgi/listinfo.py:89 +#, fuzzy msgid "%(hostname)s Mailing Lists" msgstr "Listas de diffusion in %(hostname)s" #: Mailman/Cgi/listinfo.py:127 +#, fuzzy msgid "" "

                    There currently are no publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s." @@ -2505,6 +2580,7 @@ msgstr "" " visibile publicamente in %(hostname)s." #: Mailman/Cgi/listinfo.py:131 +#, fuzzy msgid "" "

                    Below is a listing of all the public mailing lists on\n" " %(hostname)s. Click on a list name to get more information " @@ -2525,6 +2601,7 @@ msgid "right" msgstr "dextera" #: Mailman/Cgi/listinfo.py:140 +#, fuzzy msgid "" " To visit the general information page for an unadvertised list,\n" " open a URL similar to this one, but with a '/' and the %(adj)s\n" @@ -2587,6 +2664,7 @@ msgstr "Adresse de e-mail invalide" #: Mailman/Cgi/options.py:183 Mailman/Cgi/options.py:240 #: Mailman/Cgi/options.py:264 +#, fuzzy msgid "No such member: %(safeuser)s." msgstr "Membro inexistente: %(safeuser)s." @@ -2637,6 +2715,7 @@ msgid "Note: " msgstr "" #: Mailman/Cgi/options.py:378 +#, fuzzy msgid "List subscriptions for %(safeuser)s on %(hostname)s" msgstr "Abonamentos de %(safeuser)s in %(hostname)s" @@ -2664,6 +2743,7 @@ msgid "You are already using that email address" msgstr "Tu jam usa iste adresse de e-mail" #: Mailman/Cgi/options.py:459 +#, fuzzy msgid "" "The new address you requested %(newaddr)s is already a member of the\n" "%(listname)s mailing list, however you have also requested a global change " @@ -2678,6 +2758,7 @@ msgstr "" "%(safeuser)s essera cambiate. " #: Mailman/Cgi/options.py:468 +#, fuzzy msgid "The new address is already a member: %(newaddr)s" msgstr "Le nove adresse jam es un membro: %(newaddr)s" @@ -2686,6 +2767,7 @@ msgid "Addresses may not be blank" msgstr "Adresses non pote esser vacue" #: Mailman/Cgi/options.py:488 +#, fuzzy msgid "A confirmation message has been sent to %(newaddr)s. " msgstr "Un message de confirmation ha essite inviate a %(newaddr)s." @@ -2698,10 +2780,12 @@ msgid "Illegal email address provided" msgstr "Adresse de e-mail invalide" #: Mailman/Cgi/options.py:501 +#, fuzzy msgid "%(newaddr)s is already a member of the list." msgstr "%(newaddr)s jam es un membro del lista." #: Mailman/Cgi/options.py:504 +#, fuzzy msgid "" "%(newaddr)s is banned from this list. If you\n" " think this restriction is erroneous, please contact\n" @@ -2779,6 +2863,7 @@ msgstr "" " decision." #: Mailman/Cgi/options.py:623 +#, fuzzy msgid "" "You have been successfully unsubscribed from the\n" " mailing list %(fqdn_listname)s. If you were receiving digest\n" @@ -2869,6 +2954,7 @@ msgid "day" msgstr "die" #: Mailman/Cgi/options.py:900 +#, fuzzy msgid "%(days)d %(units)s" msgstr "%(days)d %(units)s" @@ -2881,6 +2967,7 @@ msgid "No topics defined" msgstr "Nulle thema definite" #: Mailman/Cgi/options.py:940 +#, fuzzy msgid "" "\n" "You are subscribed to this list with the case-preserved address\n" @@ -2891,6 +2978,7 @@ msgstr "" "%(cpuser)s." #: Mailman/Cgi/options.py:956 +#, fuzzy msgid "%(realname)s list: member options login page" msgstr "Lista %(realname)s: pagina de entrata al optiones del membro" @@ -2899,10 +2987,12 @@ msgid "email address and " msgstr "adresse de e-mail e " #: Mailman/Cgi/options.py:960 +#, fuzzy msgid "%(realname)s list: member options for user %(safeuser)s" msgstr "Lista %(realname)s: optiones del usator %(safeuser)s" #: Mailman/Cgi/options.py:986 +#, fuzzy msgid "" "In order to change your membership option, you must\n" " first log in by giving your %(extra)smembership password in the section\n" @@ -2980,6 +3070,7 @@ msgid "" msgstr "" #: Mailman/Cgi/options.py:1140 +#, fuzzy msgid "Requested topic is not valid: %(topicname)s" msgstr "Le thema non es valide: %(topicname)s" @@ -3008,6 +3099,7 @@ msgid "Private archive - \"./\" and \"../\" not allowed in URL." msgstr "" #: Mailman/Cgi/private.py:109 +#, fuzzy msgid "Private Archive Error - %(msg)s" msgstr "Error Archivo private - %(msg)s" @@ -3028,6 +3120,7 @@ msgid "Private archive file not found" msgstr "File del archivo private non trovate" #: Mailman/Cgi/rmlist.py:76 +#, fuzzy msgid "No such list %(safelistname)s" msgstr "Lista inexistente: %(safelistname)s" @@ -3044,6 +3137,7 @@ msgid "Mailing list deletion results" msgstr "Resultatos del deletion del lista" #: Mailman/Cgi/rmlist.py:187 +#, fuzzy msgid "" "You have successfully deleted the mailing list\n" " %(listname)s." @@ -3052,6 +3146,7 @@ msgstr "" " %(listname)s." #: Mailman/Cgi/rmlist.py:191 +#, fuzzy msgid "" "There were some problems deleting the mailing list\n" " %(listname)s. Contact your site administrator at " @@ -3064,10 +3159,12 @@ msgstr "" " pro detalios." #: Mailman/Cgi/rmlist.py:208 +#, fuzzy msgid "Permanently remove mailing list %(realname)s" msgstr "Remove permanentemente le lista %(realname)s" #: Mailman/Cgi/rmlist.py:209 +#, fuzzy msgid "Permanently remove mailing list %(realname)s" msgstr "Remover permanentemente le lista %(realname)s" @@ -3132,6 +3229,7 @@ msgid "Invalid options to CGI script" msgstr "Option invalide al script CGI" #: Mailman/Cgi/roster.py:118 +#, fuzzy msgid "%(realname)s roster authentication failed." msgstr "Authentication fallite al roster %(realname)s." @@ -3200,6 +3298,7 @@ msgstr "" "que contine altere instructiones." #: Mailman/Cgi/subscribe.py:262 +#, fuzzy msgid "" "The email address you supplied is banned from this\n" " mailing list. If you think this restriction is erroneous, please\n" @@ -3226,6 +3325,7 @@ msgstr "" "insecur." #: Mailman/Cgi/subscribe.py:278 +#, fuzzy msgid "" "Confirmation from your email address is required, to prevent anyone from\n" "subscribing you without permission. Instructions are being sent to you at\n" @@ -3237,6 +3337,7 @@ msgstr "" "Nota que tu abonamento non es active usque quando tu lo confirma." #: Mailman/Cgi/subscribe.py:290 +#, fuzzy msgid "" "Your subscription request was deferred because %(x)s. Your request has " "been\n" @@ -3258,6 +3359,7 @@ msgid "Mailman privacy alert" msgstr "Alerta de privacy Mailman" #: Mailman/Cgi/subscribe.py:316 +#, fuzzy msgid "" "An attempt was made to subscribe your address to the mailing list\n" "%(listaddr)s. You are already subscribed to this mailing list.\n" @@ -3297,6 +3399,7 @@ msgid "This list only supports digest delivery." msgstr "Iste lista supporta solmente livration in digesto." #: Mailman/Cgi/subscribe.py:344 +#, fuzzy msgid "You have been successfully subscribed to the %(realname)s mailing list." msgstr "Tu ha essite abonate con successo al lista %(realname)s." @@ -3321,6 +3424,7 @@ msgid "Usage:" msgstr "Usage:" #: Mailman/Commands/cmd_confirm.py:50 +#, fuzzy msgid "" "Invalid confirmation string. Note that confirmation strings expire\n" "approximately %(days)s days after the initial request. They also expire if\n" @@ -3346,6 +3450,7 @@ msgstr "" "tu adresse email?" #: Mailman/Commands/cmd_confirm.py:69 +#, fuzzy msgid "" "You are currently banned from subscribing to this list. If you think this\n" "restriction is erroneous, please contact the list owners at\n" @@ -3426,26 +3531,32 @@ msgid "n/a" msgstr "n/d" #: Mailman/Commands/cmd_info.py:44 +#, fuzzy msgid "List name: %(listname)s" msgstr "Nomine del lista: %(listname)s" #: Mailman/Commands/cmd_info.py:45 +#, fuzzy msgid "Description: %(description)s" msgstr "Description: %(description)s" #: Mailman/Commands/cmd_info.py:46 +#, fuzzy msgid "Postings to: %(postaddr)s" msgstr "Messages a: %(postaddr)s" #: Mailman/Commands/cmd_info.py:47 +#, fuzzy msgid "List Helpbot: %(requestaddr)s" msgstr "Robot pro adjuta: %(requestaddr)s" #: Mailman/Commands/cmd_info.py:48 +#, fuzzy msgid "List Owners: %(owneraddr)s" msgstr "Proprietarios: %(owneraddr)s" #: Mailman/Commands/cmd_info.py:49 +#, fuzzy msgid "More information: %(listurl)s" msgstr "Plus informationes: %(listurl)s" @@ -3469,18 +3580,22 @@ msgstr "" "Mailman.\n" #: Mailman/Commands/cmd_lists.py:44 +#, fuzzy msgid "Public mailing lists at %(hostname)s:" msgstr "Listas de diffusion public a %(hostname)s:" #: Mailman/Commands/cmd_lists.py:66 +#, fuzzy msgid "%(i)3d. List name: %(realname)s" msgstr "%(i)3d. Nomine de lista: %(realname)s" #: Mailman/Commands/cmd_lists.py:67 +#, fuzzy msgid " Description: %(description)s" msgstr " Description: %(description)s" #: Mailman/Commands/cmd_lists.py:68 +#, fuzzy msgid " Requests to: %(requestaddr)s" msgstr " Requestas a: %(requestaddr)s" @@ -3516,12 +3631,14 @@ msgstr "" " le responsa es sempre inviate al adresse abonante.\n" #: Mailman/Commands/cmd_password.py:51 Mailman/Commands/cmd_password.py:66 +#, fuzzy msgid "Your password is: %(password)s" msgstr "Tu contrasigno es: %(password)s" #: Mailman/Commands/cmd_password.py:57 Mailman/Commands/cmd_password.py:72 #: Mailman/Commands/cmd_password.py:95 Mailman/Commands/cmd_password.py:121 #: Mailman/Commands/cmd_set.py:149 Mailman/Commands/cmd_set.py:219 +#, fuzzy msgid "You are not a member of the %(listname)s mailing list" msgstr "Tu non es un membro del lista de diffusion %(listname)s" @@ -3723,6 +3840,7 @@ msgstr "" " mensual del contrasigno pro iste lista de diffusion.\n" #: Mailman/Commands/cmd_set.py:122 +#, fuzzy msgid "Bad set command: %(subcmd)s" msgstr "Incorrecte commando de fixar: %(subcmd)s" @@ -3743,6 +3861,7 @@ msgid "on" msgstr "active" #: Mailman/Commands/cmd_set.py:154 +#, fuzzy msgid " ack %(onoff)s" msgstr " recognoscentia (ack) %(onoff)s" @@ -3780,22 +3899,27 @@ msgid "due to bounces" msgstr "a causa de messages retrosaltate" #: Mailman/Commands/cmd_set.py:186 +#, fuzzy msgid " %(status)s (%(how)s on %(date)s)" msgstr " %(status)s (%(how)s a %(date)s)" #: Mailman/Commands/cmd_set.py:192 +#, fuzzy msgid " myposts %(onoff)s" msgstr " mi messages %(onoff)s" #: Mailman/Commands/cmd_set.py:195 +#, fuzzy msgid " hide %(onoff)s" msgstr " cela %(onoff)s" #: Mailman/Commands/cmd_set.py:199 +#, fuzzy msgid " duplicates %(onoff)s" msgstr " duplicatos %(onoff)s" #: Mailman/Commands/cmd_set.py:203 +#, fuzzy msgid " reminders %(onoff)s" msgstr " rememorationes %(onoff)s" @@ -3804,6 +3928,7 @@ msgid "You did not give the correct password" msgstr "Tu non scribeva le contrasigno correcte" #: Mailman/Commands/cmd_set.py:236 Mailman/Commands/cmd_set.py:283 +#, fuzzy msgid "Bad argument: %(arg)s" msgstr "Argumento incorrecte: %(arg)s" @@ -3882,6 +4007,7 @@ msgstr "" " circum le adresse de e-mail e nulle signos de citation!)\n" #: Mailman/Commands/cmd_subscribe.py:62 +#, fuzzy msgid "Bad digest specifier: %(arg)s" msgstr "Incorrecte specification de digesto: %(arg)s" @@ -3890,6 +4016,7 @@ msgid "No valid address found to subscribe" msgstr "Nulle adresse valide esseva trovate pro abonar se" #: Mailman/Commands/cmd_subscribe.py:113 +#, fuzzy msgid "" "The email address you supplied is banned from this mailing list.\n" "If you think this restriction is erroneous, please contact the list\n" @@ -3930,6 +4057,7 @@ msgid "This list only supports digest subscriptions!" msgstr "Iste lista solmente supporta abonamentos de digestos!" #: Mailman/Commands/cmd_subscribe.py:146 +#, fuzzy msgid "" "Your subscription request has been forwarded to the list administrator\n" "at %(listowner)s for review." @@ -3969,6 +4097,7 @@ msgstr "" " signos de citation!)\n" #: Mailman/Commands/cmd_unsubscribe.py:62 +#, fuzzy msgid "%(address)s is not a member of the %(listname)s mailing list" msgstr "%(address)s non es un membro del lista de diffusion %(listname)s" @@ -4216,6 +4345,7 @@ msgid "Chinese (Taiwan)" msgstr "chinese (Taiwan)" #: Mailman/Deliverer.py:53 +#, fuzzy msgid "" "Note: Since this is a list of mailing lists, administrative\n" "notices like the password reminder will be sent to\n" @@ -4230,14 +4360,17 @@ msgid " (Digest mode)" msgstr " (modo digesto)" #: Mailman/Deliverer.py:79 +#, fuzzy msgid "Welcome to the \"%(realname)s\" mailing list%(digmode)s" msgstr "Benvenite al lista \"%(realname)s\"%(digmode)s" #: Mailman/Deliverer.py:89 +#, fuzzy msgid "You have been unsubscribed from the %(realname)s mailing list" msgstr "Tu ha essite disabonate del lista de diffusion %(realname)s" #: Mailman/Deliverer.py:116 +#, fuzzy msgid "%(listfullname)s mailing list reminder" msgstr "Rememoration del lista de diffusion %(listfullname)s" @@ -4250,6 +4383,7 @@ msgid "Hostile subscription attempt detected" msgstr "Essayo de abonamento hostil detegite" #: Mailman/Deliverer.py:169 +#, fuzzy msgid "" "%(address)s was invited to a different mailing\n" "list, but in a deliberate malicious attempt they tried to confirm the\n" @@ -4264,6 +4398,7 @@ msgstr "" "ulterior es demandate de te." #: Mailman/Deliverer.py:188 +#, fuzzy msgid "" "You invited %(address)s to your list, but in a\n" "deliberate malicious attempt, they tried to confirm the invitation to a\n" @@ -4279,6 +4414,7 @@ msgstr "" "de te." #: Mailman/Deliverer.py:221 +#, fuzzy msgid "%(listname)s mailing list probe message" msgstr "Message de test del lista de diffusion %(listname)s" @@ -4488,8 +4624,8 @@ msgid "" " membership.\n" "\n" "

                    You can control both the\n" -" number\n" +" number\n" " of reminders the member will receive and the\n" " Tu pote controlar e le\n" -" numero\n" +" numero\n" " de rememorationes que le membro va reciper, e le\n" " No those messages too will get discarded. You may " "want\n" " to set up an\n" -" autoresponse\n" +" autoresponse\n" " message for email to the -owner and -admin address." msgstr "" "Mesmo si le derector de retrosaltamento de Mailman es assatis robuste,\n" @@ -4751,8 +4887,8 @@ msgstr "" " No anque iste messages essera delite. Tu pote " "desirar\n" " crear un message de\n" -" autoresponse\n" +" autoresponse\n" " pro e-mail al adresses del -owner (proprietario) e -admin " "(administrator)." @@ -4824,6 +4960,7 @@ msgstr "" " essayo de informar le membro essera facite sempre." #: Mailman/Gui/Bounce.py:194 +#, fuzzy msgid "" "Bad value for %(property)s: %(val)s" @@ -4959,8 +5096,8 @@ msgstr "" "\n" "

                    Lineas blanc es ignorate.\n" "\n" -"

                    Vide anque Vide anque pass_mime_types pro un lista blanc de typos de contento." #: Mailman/Gui/ContentFilter.py:94 @@ -4979,8 +5116,8 @@ msgid "" "Use this option to remove each message attachment that does\n" " not have a matching content type. Requirements and formats " "are\n" -" exactly like filter_mime_types.\n" "\n" "

                    Note: if you add entries to this list but don't add\n" @@ -5111,6 +5248,7 @@ msgstr "" " del sito." #: Mailman/Gui/ContentFilter.py:171 +#, fuzzy msgid "Bad MIME type ignored: %(spectype)s" msgstr "Incorrecte typo de MIME ignorate: %(spectype)s" @@ -5220,6 +5358,7 @@ msgstr "" " es vacue?" #: Mailman/Gui/Digest.py:145 +#, fuzzy msgid "" "The next digest will be sent as volume\n" " %(volume)s, number %(number)s" @@ -5236,14 +5375,17 @@ msgid "There was no digest to send." msgstr "Il habeva nulle digesto a inviar." #: Mailman/Gui/GUIBase.py:173 +#, fuzzy msgid "Invalid value for variable: %(property)s" msgstr "Valor invalide pro le variabile: %(property)s" #: Mailman/Gui/GUIBase.py:178 +#, fuzzy msgid "Bad email address for option %(property)s: %(error)s" msgstr "Incorrecte adresse de e-mail pro option %(property)s: %(error)s" #: Mailman/Gui/GUIBase.py:204 +#, fuzzy msgid "" "The following illegal substitution variables were\n" " found in the %(property)s string:\n" @@ -5260,6 +5402,7 @@ msgstr "" " problema." #: Mailman/Gui/GUIBase.py:218 +#, fuzzy msgid "" "Your %(property)s string appeared to\n" " have some correctable problems in its new value.\n" @@ -5367,8 +5510,8 @@ msgid "" "

                    In order to split the list ownership duties into\n" " administrators and moderators, you must\n" " set a separate moderator password,\n" -" and also provide the email\n" +" and also provide the email\n" " addresses of the list moderators. Note that the field you\n" " are changing here specifies the list administrators." msgstr "" @@ -5393,8 +5536,8 @@ msgstr "" " administratores e moderatores, tu debe\n" " definir un contrasigno separate de " "moderator,\n" -" e fornir le adresses de e-mail\n" +" e fornir le adresses de e-mail\n" " del moderatores del lista. Nota que le quadro que tu\n" " modifica ci specifica le administratores del lista." @@ -5453,8 +5596,8 @@ msgstr "" " administratores e moderatores, tu debe\n" " definir un contrasigno separate de " "moderator,\n" -" e fornir le adresses de e-mail\n" +" e fornir le adresses de e-mail\n" " del moderatores del lista. Nota que le quadro que tu\n" " modifica ci specifica le administratores del lista." @@ -5709,13 +5852,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5753,8 +5896,8 @@ msgstr "" " adresse de retorno valide. Un altere es que modificar " "Reply-To:\n" " rende plus difficile inviar responsas private. Vide \n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">\n" " reimplaciamento del `Reply-To' considerate como nocive pro " "un\n" " discussion general de iste thema. Reguarda Reply-To: explicite." msgid "" "This is the address set in the Reply-To: header\n" " when the reply_goes_to_list\n" +" href=\"?VARHELP=general/" +"reply_goes_to_list\">reply_goes_to_list\n" " option is set to Explicit address.\n" "\n" "

                    There are many reasons not to introduce or override the\n" @@ -5793,13 +5936,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5822,8 +5965,8 @@ msgid "" msgstr "" "Isto es le adresse fixate in le capite Reply-To:\n" " quando le option reply_goes_to_list\n" +" href=\"?VARHELP=general/" +"reply_goes_to_list\">reply_goes_to_list\n" " es fixate a Adresse explicite.\n" "\n" "

                    Il ha multe rationes pro non introducer o superscriber le\n" @@ -5835,8 +5978,8 @@ msgstr "" "\n" " lo rende multo plus difficile inviar responsas private. " "Reguarda \n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">\n" " Rescriptura del `Reply-To'\n" " considerate malitiose pro un discussion general de iste\n" " thema. Reguarda general\n" +" general\n" " non-member rules.\n" "\n" "

                    In the text boxes below, add one address per line; start the\n" @@ -7150,8 +7294,8 @@ msgstr "" " o individualmente o como un gruppo. Ulle\n" " invios de un non-membro que non es explicitemente acceptate,\n" " rejectate o abandonate, videra lor invios filtrate per le\n" -" general\n" +" general\n" " regulas pro non-membros.\n" "\n" "

                    In le cassas textual infra, adde un adresse per linea; " @@ -7173,6 +7317,7 @@ msgid "By default, should new list member postings be moderated?" msgstr "Como standard, debe messages de nove membros al lista esser moderate?" #: Mailman/Gui/Privacy.py:218 +#, fuzzy msgid "" "Each list member has a moderation flag which says\n" " whether messages from the list member can be posted directly " @@ -7231,8 +7376,8 @@ msgid "" "If a member posts this many times, within a period of time\n" " the member is automatically moderated. Use 0 to disable. " "See\n" -" member_verbosity_interval for details on the time " "period.\n" "\n" @@ -7321,6 +7466,7 @@ msgstr "" " inviate a membros moderate qui scribe al lista." #: Mailman/Gui/Privacy.py:290 +#, fuzzy msgid "" "Action to take when anyone posts to the\n" " list from a domain with a DMARC Reject%(quarantine)s Policy." @@ -7423,6 +7569,7 @@ msgid "" msgstr "" #: Mailman/Gui/Privacy.py:353 +#, fuzzy msgid "" "Text to include in any\n" " The body of the message can also be optionally scanned for\n" " Subject: and Keywords: headers, as\n" " specified by the topics_bodylines_limit\n" +" href=\"?VARHELP=topics/" +"topics_bodylines_limit\">topics_bodylines_limit\n" " configuration variable." msgstr "" "Le filtro de themas categorisa cata message entrante in\n" @@ -8029,8 +8178,8 @@ msgstr "" " cercate capites Subject: e Keywords:,\n" " como specificate in le variabile de configuration topics_bodylines_limit." +" href=\"?VARHELP=topics/" +"topics_bodylines_limit\">topics_bodylines_limit." #: Mailman/Gui/Topics.py:72 msgid "How many body lines should the topic matcher scan?" @@ -8100,6 +8249,7 @@ msgstr "" " e un patrono. Themas incomplete essera ignorate." #: Mailman/Gui/Topics.py:135 +#, fuzzy msgid "" "The topic pattern '%(safepattern)s' is not a\n" " legal regular expression. It will be discarded." @@ -8324,6 +8474,7 @@ msgid "%(listinfo_link)s list run by %(owner_link)s" msgstr "%(listinfo_link)s es gestite per %(owner_link)s" #: Mailman/HTMLFormatter.py:57 +#, fuzzy msgid "%(realname)s administrative interface" msgstr "%(realname)s interfacie administrative" @@ -8332,6 +8483,7 @@ msgid " (requires authorization)" msgstr " (require autorisation)" #: Mailman/HTMLFormatter.py:61 +#, fuzzy msgid "Overview of all %(hostname)s mailing lists" msgstr "Panorama de tote listas in %(hostname)s" @@ -8352,6 +8504,7 @@ msgid "; it was disabled by the list administrator" msgstr "; disactivate per le administrator" #: Mailman/HTMLFormatter.py:146 +#, fuzzy msgid "" "; it was disabled due to excessive bounces. The\n" " last bounce was received on %(date)s" @@ -8365,6 +8518,7 @@ msgid "; it was disabled for unknown reasons" msgstr "; disactivate per rationes incognite" #: Mailman/HTMLFormatter.py:151 +#, fuzzy msgid "Note: your list delivery is currently disabled%(reason)s." msgstr "Nota: tu reception del lista es actualmente disactivate%(reason)s." @@ -8377,6 +8531,7 @@ msgid "the list administrator" msgstr "le administrator del lista" #: Mailman/HTMLFormatter.py:157 +#, fuzzy msgid "" "

                    %(note)s\n" "\n" @@ -8395,6 +8550,7 @@ msgstr "" " questiones o necessita adjuta." #: Mailman/HTMLFormatter.py:169 +#, fuzzy msgid "" "

                    We have received some recent bounces from your\n" " address. Your current bounce score is %(score)s out of " @@ -8417,6 +8573,7 @@ msgstr "" " reinitialisate, si le problemas es corrigite tosto." #: Mailman/HTMLFormatter.py:181 +#, fuzzy msgid "" "(Note - you are subscribing to a list of mailing lists, so the %(type)s " "notice will be sent to the admin address for your membership, %(addr)s.)

                    " @@ -8465,6 +8622,7 @@ msgstr "" " del moderator per e-mail." #: Mailman/HTMLFormatter.py:208 +#, fuzzy msgid "" "This is %(also)sa private list, which means that the\n" " list of members is not available to non-members." @@ -8473,6 +8631,7 @@ msgstr "" " le lista del membros non es visibile per non-membros." #: Mailman/HTMLFormatter.py:211 +#, fuzzy msgid "" "This is %(also)sa hidden list, which means that the\n" " list of members is available only to the list administrator." @@ -8482,6 +8641,7 @@ msgstr "" "del lista." #: Mailman/HTMLFormatter.py:214 +#, fuzzy msgid "" "This is %(also)sa public list, which means that the\n" " list of members list is available to everyone." @@ -8498,6 +8658,7 @@ msgstr "" " facilemente recognoscibile per spamatores)." #: Mailman/HTMLFormatter.py:222 +#, fuzzy msgid "" "

                    (Note that this is an umbrella list, intended to\n" " have only other mailing lists as members. Among other things,\n" @@ -8516,6 +8677,7 @@ msgid "either " msgstr "o " #: Mailman/HTMLFormatter.py:256 +#, fuzzy msgid "" "To unsubscribe from %(realname)s, get a password reminder,\n" " or change your subscription options %(either)senter your " @@ -8550,6 +8712,7 @@ msgstr "" " tu adresse de e-mail." #: Mailman/HTMLFormatter.py:277 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " members.)" @@ -8558,6 +8721,7 @@ msgstr "" " membros.)" #: Mailman/HTMLFormatter.py:281 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " administrator.)" @@ -8618,6 +8782,7 @@ msgid "The current archive" msgstr "Le archivo currente" #: Mailman/Handlers/Acknowledge.py:59 +#, fuzzy msgid "%(realname)s post acknowledgement" msgstr "recognoscentia de message de %(realname)s" @@ -8630,6 +8795,7 @@ msgid "" msgstr "" #: Mailman/Handlers/CalcRecips.py:79 +#, fuzzy msgid "" "Your urgent message to the %(realname)s mailing list was not authorized for\n" "delivery. The original message as received by Mailman is attached.\n" @@ -8707,6 +8873,7 @@ msgid "Message may contain administrivia" msgstr "Le message pote continer administrivia" #: Mailman/Handlers/Hold.py:84 +#, fuzzy msgid "" "Please do *not* post administrative requests to the mailing\n" "list. If you wish to subscribe, visit %(listurl)s or send a message with " @@ -8749,10 +8916,12 @@ msgid "Posting to a moderated newsgroup" msgstr "Messages a un moderate gruppo de novas" #: Mailman/Handlers/Hold.py:252 +#, fuzzy msgid "Your message to %(listname)s awaits moderator approval" msgstr "Tu message a %(listname)s attende le approbation del moderator" #: Mailman/Handlers/Hold.py:271 +#, fuzzy msgid "%(listname)s post from %(sender)s requires approval" msgstr "Posta a %(listname)s de %(sender)s require approbation" @@ -8796,6 +8965,7 @@ msgid "After content filtering, the message was empty" msgstr "Post filtrar le contento, le message esseva vacue" #: Mailman/Handlers/MimeDel.py:269 +#, fuzzy msgid "" "The attached message matched the %(listname)s mailing list's content " "filtering\n" @@ -8839,6 +9009,7 @@ msgid "The attached message has been automatically discarded." msgstr "Le message attachate ha essite delite automaticamente." #: Mailman/Handlers/Replybot.py:75 +#, fuzzy msgid "Auto-response for your message to the \"%(realname)s\" mailing list" msgstr "Auto-responsa a tu message al lista de diffusion \"%(realname)s\" " @@ -8863,6 +9034,7 @@ msgid "HTML attachment scrubbed and removed" msgstr "Attachamentos in formato HTML esseva removite" #: Mailman/Handlers/Scrubber.py:234 Mailman/Handlers/Scrubber.py:259 +#, fuzzy msgid "" "An HTML attachment was scrubbed...\n" "URL: %(url)s\n" @@ -8916,6 +9088,7 @@ msgstr "" "Adresse in Internet: %(url)s\n" #: Mailman/Handlers/Scrubber.py:347 +#, fuzzy msgid "Skipped content of type %(partctype)s\n" msgstr "Contento omittite del typo %(partctype)s\n" @@ -8948,6 +9121,7 @@ msgid "Message rejected by filter rule match" msgstr "Message rejectate per equalation a regula de filtro" #: Mailman/Handlers/ToDigest.py:173 +#, fuzzy msgid "%(realname)s Digest, Vol %(volume)d, Issue %(issue)d" msgstr "Digesto de %(realname)s, volumine %(volume)d, edition %(issue)d" @@ -8985,6 +9159,7 @@ msgid "End of " msgstr "Fin de " #: Mailman/ListAdmin.py:309 +#, fuzzy msgid "Posting of your message titled \"%(subject)s\"" msgstr "Expedition de tu message titulate \"%(subject)s\"" @@ -8997,6 +9172,7 @@ msgid "Forward of moderated message" msgstr "Re-invia de message moderate" #: Mailman/ListAdmin.py:405 +#, fuzzy msgid "New subscription request to list %(realname)s from %(addr)s" msgstr "Requesta de nove abonamento al lista %(realname)s de %(addr)s" @@ -9010,6 +9186,7 @@ msgid "via admin approval" msgstr "Continua a attender le approbation" #: Mailman/ListAdmin.py:466 +#, fuzzy msgid "New unsubscription request from %(realname)s by %(addr)s" msgstr "Requesta de nove disabonamento de %(realname)s de %(addr)s" @@ -9022,10 +9199,12 @@ msgid "Original Message" msgstr "Message original" #: Mailman/ListAdmin.py:526 +#, fuzzy msgid "Request to mailing list %(realname)s rejected" msgstr "Requesta al lista de diffusion %(realname)s rejectate" #: Mailman/MTA/Manual.py:66 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been created via the through-the-web\n" "interface. In order to complete the activation of this mailing list, the\n" @@ -9055,14 +9234,17 @@ msgstr "" "programma `newaliases':\n" #: Mailman/MTA/Manual.py:82 +#, fuzzy msgid "## %(listname)s mailing list" msgstr "lista de diffusion ## %(listname)s " #: Mailman/MTA/Manual.py:99 +#, fuzzy msgid "Mailing list creation request for list %(listname)s" msgstr "Requesta de crear lista de diffusion pro le lista %(listname)s" #: Mailman/MTA/Manual.py:113 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been removed via the through-the-web\n" "interface. In order to complete the de-activation of this mailing list, " @@ -9082,6 +9264,7 @@ msgstr "" "Ecce le entratas in le file /etc/aliases file que debe esser removite:\n" #: Mailman/MTA/Manual.py:123 +#, fuzzy msgid "" "\n" "To finish removing your mailing list, you must edit your /etc/aliases (or\n" @@ -9099,14 +9282,17 @@ msgstr "" "lista de diffusion ## %(listname)s" #: Mailman/MTA/Manual.py:142 +#, fuzzy msgid "Mailing list removal request for list %(listname)s" msgstr "Requesta de remover lista de diffusion pro le lista %(listname)s" #: Mailman/MTA/Postfix.py:442 +#, fuzzy msgid "checking permissions on %(file)s" msgstr "examinante permissiones de %(file)s" #: Mailman/MTA/Postfix.py:452 +#, fuzzy msgid "%(file)s permissions must be 0664 (got %(octmode)s)" msgstr "permissiones de %(file)s debe esser 0664 (tu ha %(octmode)s)" @@ -9120,37 +9306,45 @@ msgid "(fixing)" msgstr "(corrigente)" #: Mailman/MTA/Postfix.py:470 +#, fuzzy msgid "checking ownership of %(dbfile)s" msgstr "examinante le proprietario de %(dbfile)s" #: Mailman/MTA/Postfix.py:478 +#, fuzzy msgid "%(dbfile)s owned by %(owner)s (must be owned by %(user)s" msgstr "" "%(dbfile)s in possession de %(owner)s (debe esser in possession de %(user)s" #: Mailman/MTA/Postfix.py:491 +#, fuzzy msgid "%(dbfile)s permissions must be 0664 (got %(octmode)s)" msgstr "permissiones de %(dbfile)s debe esser 0664 (tu ha %(octmode)s)" #: Mailman/MailList.py:219 +#, fuzzy msgid "Your confirmation is required to join the %(listname)s mailing list" msgstr "" "Tu confirmation es requirite pro entrar in le lista de diffusion %(listname)s" #: Mailman/MailList.py:230 +#, fuzzy msgid "Your confirmation is required to leave the %(listname)s mailing list" msgstr "" "Tu confirmation es requirite pro abandonar le lista de diffusion %(listname)s" #: Mailman/MailList.py:999 Mailman/MailList.py:1492 +#, fuzzy msgid " from %(remote)s" msgstr " de %(remote)s" #: Mailman/MailList.py:1043 +#, fuzzy msgid "subscriptions to %(realname)s require moderator approval" msgstr "abonamentos a %(realname)s require approbation del moderator" #: Mailman/MailList.py:1125 bin/add_members:299 +#, fuzzy msgid "%(realname)s subscription notification" msgstr "notification de abonamento de %(realname)s" @@ -9159,6 +9353,7 @@ msgid "unsubscriptions require moderator approval" msgstr "disabonamento require approbation del moderator" #: Mailman/MailList.py:1166 +#, fuzzy msgid "%(realname)s unsubscribe notification" msgstr "notification de disabonamento de %(realname)s" @@ -9178,6 +9373,7 @@ msgid "via web confirmation" msgstr "Codice de confirmation invalide" #: Mailman/MailList.py:1396 +#, fuzzy msgid "subscriptions to %(name)s require administrator approval" msgstr "abonamentos a %(name)s require approbation del administrator" @@ -9196,6 +9392,7 @@ msgid "Last autoresponse notification for today" msgstr "Ultime notification de auto-responsa pro hodie" #: Mailman/Queue/BounceRunner.py:360 +#, fuzzy msgid "" "The attached message was received as a bounce, but either the bounce format\n" "was not recognized, or no member addresses could be extracted from it. " @@ -9288,6 +9485,7 @@ msgid "Original message suppressed by Mailman site configuration\n" msgstr "" #: Mailman/htmlformat.py:675 +#, fuzzy msgid "Delivered by Mailman
                    version %(version)s" msgstr "Livrate per Mailman
                    version %(version)s" @@ -9376,6 +9574,7 @@ msgid "Server Local Time" msgstr "Tempore local" #: Mailman/i18n.py:180 +#, fuzzy msgid "" "%(wday)s %(mon)s %(day)2i %(hh)02i:%(mm)02i:%(ss)02i %(tzname)s %(year)04i" msgstr "" @@ -9445,6 +9644,7 @@ msgid "" msgstr "" #: bin/add_members:162 bin/add_members:172 +#, fuzzy msgid "Already a member: %(member)s" msgstr "Jam es un membro: %(member)s" @@ -9453,10 +9653,12 @@ msgid "Bad/Invalid email address: blank line" msgstr "Errate/invalide adresse email: linea vacue" #: bin/add_members:180 +#, fuzzy msgid "Bad/Invalid email address: %(member)s" msgstr "Errate/invalide adresse email: %(member)s" #: bin/add_members:182 +#, fuzzy msgid "Hostile address (illegal characters): %(member)s" msgstr "Adresse hostil (characteres illegal): %(member)s" @@ -9466,16 +9668,19 @@ msgid "Invited: %(member)s" msgstr "Abonate: %(member)s" #: bin/add_members:187 +#, fuzzy msgid "Subscribed: %(member)s" msgstr "Abonate: %(member)s" #: bin/add_members:237 +#, fuzzy msgid "Bad argument to -w/--welcome-msg: %(arg)s" -msgstr "" +msgstr "Argumento incorrecte: %(arg)s" #: bin/add_members:244 +#, fuzzy msgid "Bad argument to -a/--admin-notify: %(arg)s" -msgstr "" +msgstr "Argumento incorrecte: %(arg)s" #: bin/add_members:252 msgid "Cannot read both digest and normal members from standard input." @@ -9488,6 +9693,7 @@ msgstr "" #: bin/add_members:261 bin/config_list:110 bin/export.py:271 bin/find_member:97 #: bin/inject:91 bin/list_admins:90 bin/list_members:252 bin/sync_members:222 #: cron/bumpdigests:86 +#, fuzzy msgid "No such list: %(listname)s" msgstr "Lista inexistente: %(listname)s" @@ -9551,6 +9757,7 @@ msgid "listname is required" msgstr "nomine del lista requirite" #: bin/arch:143 bin/change_pw:107 bin/config_list:257 +#, fuzzy msgid "" "No such list \"%(listname)s\"\n" "%(e)s" @@ -9559,6 +9766,7 @@ msgstr "" "%(e)s" #: bin/arch:168 +#, fuzzy msgid "Cannot open mbox file %(mbox)s: %(msg)s" msgstr "Impossibile aperir le file mbox %(mbox)s: %(msg)s" @@ -9640,6 +9848,7 @@ msgid "" msgstr "" #: bin/change_pw:145 +#, fuzzy msgid "Bad arguments: %(strargs)s" msgstr "Argumentos errate: %(strargs)s" @@ -9648,10 +9857,12 @@ msgid "Empty list passwords are not allowed" msgstr "Contrasignos de lista vacue non es permittite" #: bin/change_pw:181 +#, fuzzy msgid "New %(listname)s password: %(notifypassword)s" msgstr "Nove contrasigno pro %(listname)s: %(notifypassword)s" #: bin/change_pw:190 +#, fuzzy msgid "Your new %(listname)s list password" msgstr "Tu nove contrasigno pro %(listname)s" @@ -9716,6 +9927,7 @@ msgid "List:" msgstr "Lista:" #: bin/check_db:148 +#, fuzzy msgid " %(file)s: okay" msgstr " %(file)s: bon" @@ -9731,40 +9943,47 @@ msgid "" msgstr "" #: bin/check_perms:110 +#, fuzzy msgid " checking gid and mode for %(path)s" -msgstr "" +msgstr "examinante permissiones de %(file)s" #: bin/check_perms:122 msgid "%(path)s bad group (has: %(groupname)s, expected %(MAILMAN_GROUP)s)" msgstr "" #: bin/check_perms:151 +#, fuzzy msgid "directory permissions must be %(octperms)s: %(path)s" -msgstr "" +msgstr "permissiones de %(dbfile)s debe esser 0664 (tu ha %(octmode)s)" #: bin/check_perms:160 +#, fuzzy msgid "source perms must be %(octperms)s: %(path)s" -msgstr "" +msgstr "permissiones de %(dbfile)s debe esser 0664 (tu ha %(octmode)s)" #: bin/check_perms:171 +#, fuzzy msgid "article db files must be %(octperms)s: %(path)s" -msgstr "" +msgstr "permissiones de %(dbfile)s debe esser 0664 (tu ha %(octmode)s)" #: bin/check_perms:183 +#, fuzzy msgid "checking mode for %(prefix)s" -msgstr "" +msgstr "examinante permissiones de %(file)s" #: bin/check_perms:193 msgid "WARNING: directory does not exist: %(d)s" msgstr "" #: bin/check_perms:197 +#, fuzzy msgid "directory must be at least 02775: %(d)s" -msgstr "" +msgstr "permissiones de %(file)s debe esser 0664 (tu ha %(octmode)s)" #: bin/check_perms:209 +#, fuzzy msgid "checking perms on %(private)s" -msgstr "" +msgstr "examinante permissiones de %(file)s" #: bin/check_perms:214 msgid "%(private)s must not be other-readable" @@ -9792,40 +10011,46 @@ msgid "checking cgi-bin permissions" msgstr "" #: bin/check_perms:278 +#, fuzzy msgid " checking set-gid for %(path)s" -msgstr "" +msgstr "examinante permissiones de %(file)s" #: bin/check_perms:282 msgid "%(path)s must be set-gid" msgstr "" #: bin/check_perms:292 +#, fuzzy msgid "checking set-gid for %(wrapper)s" -msgstr "" +msgstr "examinante permissiones de %(file)s" #: bin/check_perms:296 msgid "%(wrapper)s must be set-gid" msgstr "" #: bin/check_perms:306 +#, fuzzy msgid "checking permissions on %(pwfile)s" -msgstr "" +msgstr "examinante permissiones de %(file)s" #: bin/check_perms:315 +#, fuzzy msgid "%(pwfile)s permissions must be exactly 0640 (got %(octmode)s)" -msgstr "" +msgstr "permissiones de %(file)s debe esser 0664 (tu ha %(octmode)s)" #: bin/check_perms:340 msgid "checking permissions on list data" msgstr "" #: bin/check_perms:348 +#, fuzzy msgid " checking permissions on: %(path)s" -msgstr "" +msgstr "examinante permissiones de %(file)s" #: bin/check_perms:356 +#, fuzzy msgid "file permissions must be at least 660: %(path)s" -msgstr "" +msgstr "permissiones de %(file)s debe esser 0664 (tu ha %(octmode)s)" #: bin/check_perms:401 msgid "No problems found" @@ -9878,8 +10103,9 @@ msgid "Unix-From line changed: %(lineno)d" msgstr "" #: bin/cleanarch:111 +#, fuzzy msgid "Bad status number: %(arg)s" -msgstr "" +msgstr "Argumento incorrecte: %(arg)s" #: bin/cleanarch:167 msgid "%(messages)d messages found" @@ -9976,14 +10202,16 @@ msgid " original address removed:" msgstr " adresse original removite:" #: bin/clone_member:202 +#, fuzzy msgid "Not a valid email address: %(toaddr)s" msgstr "Non un adresse email valide: %(toaddr)s" #: bin/clone_member:215 +#, fuzzy msgid "" "Error opening list \"%(listname)s\", skipping.\n" "%(e)s" -msgstr "" +msgstr "Error in aperir le lista %(listname)s... Omittente." #: bin/config_list:20 msgid "" @@ -10056,24 +10284,29 @@ msgid "legal values are:" msgstr "valores admittite es:" #: bin/config_list:270 +#, fuzzy msgid "attribute \"%(k)s\" ignored" msgstr "attributo \"%(k)s\" ignorate" #: bin/config_list:273 +#, fuzzy msgid "attribute \"%(k)s\" changed" msgstr "attributo \"%(k)s\" cambiate" #: bin/config_list:279 +#, fuzzy msgid "Non-standard property restored: %(k)s" msgstr "Proprietate non standard reponite: %(k)s" #: bin/config_list:288 +#, fuzzy msgid "Invalid value for property: %(k)s" -msgstr "" +msgstr "Valor invalide pro le variabile: %(property)s" #: bin/config_list:291 +#, fuzzy msgid "Bad email address for option %(k)s: %(v)s" -msgstr "" +msgstr "Incorrecte adresse de e-mail pro option %(property)s: %(error)s" #: bin/config_list:348 msgid "Only one of -i or -o is allowed" @@ -10120,16 +10353,19 @@ msgid "" msgstr "" #: bin/discard:94 +#, fuzzy msgid "Ignoring non-held message: %(f)s" -msgstr "" +msgstr "Ignorante cambiamentos al membro delite: %(user)s" #: bin/discard:100 +#, fuzzy msgid "Ignoring held msg w/bad id: %(f)s" -msgstr "" +msgstr "Ignorante cambiamentos al membro delite: %(user)s" #: bin/discard:112 +#, fuzzy msgid "Discarded held msg #%(id)s for list %(listname)s" -msgstr "" +msgstr "Abona me al lista %(listname)s" #: bin/dumpdb:19 msgid "" @@ -10173,8 +10409,9 @@ msgid "No filename given." msgstr "" #: bin/dumpdb:108 +#, fuzzy msgid "Bad arguments: %(pargs)s" -msgstr "" +msgstr "Argumento incorrecte: %(arg)s" #: bin/dumpdb:118 msgid "Please specify either -p or -m." @@ -10430,6 +10667,7 @@ msgstr "" "insertion standard es usate.\n" #: bin/inject:84 +#, fuzzy msgid "Bad queue directory: %(qdir)s" msgstr "Mal directorio de cauda: %(qdir)s" @@ -10438,6 +10676,7 @@ msgid "A list name is required" msgstr "Un nomine del lista es requirite" #: bin/list_admins:20 +#, fuzzy msgid "" "List all the owners of a mailing list.\n" "\n" @@ -10485,6 +10724,7 @@ msgstr "" "haber plus que un lista nominate in le linea de commando.\n" #: bin/list_admins:97 +#, fuzzy msgid "List: %(listname)s, \tOwners: %(owners)s" msgstr "Lista: %(listname)s, \tPossessores: %(owners)s" @@ -10672,10 +10912,12 @@ msgstr "" "de adresse.\n" #: bin/list_members:198 +#, fuzzy msgid "Bad --nomail option: %(why)s" msgstr "Mal option de --nomail: %(why)s" #: bin/list_members:209 +#, fuzzy msgid "Bad --digest option: %(kind)s" msgstr "Mal option de --digest: %(kind)s" @@ -10689,6 +10931,7 @@ msgid "Could not open file for writing:" msgstr "Non poteva aperir file pro scriber:" #: bin/list_owners:20 +#, fuzzy msgid "" "List the owners of a mailing list, or all mailing lists.\n" "\n" @@ -10745,6 +10988,7 @@ msgid "" msgstr "" #: bin/mailmanctl:20 +#, fuzzy msgid "" "Primary start-up and shutdown script for Mailman's qrunner daemon.\n" "\n" @@ -10939,6 +11183,7 @@ msgstr "" " le proxime vice que un message es scribite a illos.\n" #: bin/mailmanctl:152 +#, fuzzy msgid "PID unreadable in: %(pidfile)s" msgstr "PID illegibile in: %(pidfile)s" @@ -10947,6 +11192,7 @@ msgid "Is qrunner even running?" msgstr "Esque qrunner de facto curre?" #: bin/mailmanctl:160 +#, fuzzy msgid "No child with pid: %(pid)s" msgstr "Nulle processo filio con pid: %(pid)s" @@ -10974,6 +11220,7 @@ msgstr "" "marca -s.\n" #: bin/mailmanctl:233 +#, fuzzy msgid "" "The master qrunner lock could not be acquired, because it appears as if " "some\n" @@ -11000,10 +11247,12 @@ msgstr "" "Exiente." #: bin/mailmanctl:280 cron/mailpasswds:119 +#, fuzzy msgid "Site list is missing: %(sitelistname)s" msgstr "Lista del sito manca: %(sitelistname)s" #: bin/mailmanctl:305 +#, fuzzy msgid "Run this program as root or as the %(name)s user, or use -u." msgstr "Curre iste programma como radice o como le usator %(name)s, o usa -u." @@ -11012,6 +11261,7 @@ msgid "No command given." msgstr "Nulle commando date." #: bin/mailmanctl:339 +#, fuzzy msgid "Bad command: %(command)s" msgstr "Commando incorrecte: %(command)s" @@ -11036,6 +11286,7 @@ msgid "Starting Mailman's master qrunner." msgstr "Startante le qrunner maestral de Mailman." #: bin/mmsitepass:19 +#, fuzzy msgid "" "Set the site password, prompting from the terminal.\n" "\n" @@ -11093,6 +11344,7 @@ msgid "list creator" msgstr "creator de lista" #: bin/mmsitepass:86 +#, fuzzy msgid "New %(pwdesc)s password: " msgstr "Nove contrasigno %(pwdesc)s: " @@ -11367,6 +11619,7 @@ msgstr "" "Note that listnames are forced to lowercase.\n" #: bin/newlist:162 +#, fuzzy msgid "Unknown language: %(lang)s" msgstr "Lingua incognoscite: %(lang)s" @@ -11379,6 +11632,7 @@ msgid "Enter the email of the person running the list: " msgstr "Scribe le e-mail del persona qui administra le lista: " #: bin/newlist:193 +#, fuzzy msgid "Initial %(listname)s password: " msgstr "Contrasigno initial de %(listname)s: " @@ -11388,11 +11642,12 @@ msgstr "Le contrasigno del lista non pote esser vacue" #: bin/newlist:220 msgid "" -" - owner addresses need to be fully-qualified names like \"owner@example.com" -"\", not just \"owner\"." +" - owner addresses need to be fully-qualified names like \"owner@example." +"com\", not just \"owner\"." msgstr "" #: bin/newlist:244 +#, fuzzy msgid "Hit enter to notify %(listname)s owner..." msgstr "Preme Enter pro notificar le proprietario de %(listname)s..." @@ -11529,6 +11784,7 @@ msgstr "" "monstrate per le option -l.\n" #: bin/qrunner:179 +#, fuzzy msgid "%(name)s runs the %(runnername)s qrunner" msgstr "%(name)s activa le qrunner %(runnername)s" @@ -11541,6 +11797,7 @@ msgid "No runner name given." msgstr "Nulle nomine de runner indicate." #: bin/rb-archfix:21 +#, fuzzy msgid "" "Reduce disk space usage for Pipermail archives.\n" "\n" @@ -11695,18 +11952,22 @@ msgstr "" "\n" #: bin/remove_members:156 +#, fuzzy msgid "Could not open file for reading: %(filename)s." msgstr "Non poteva aperir le file pro leger: %(filename)s." #: bin/remove_members:163 +#, fuzzy msgid "Error opening list %(listname)s... skipping." msgstr "Error in aperir le lista %(listname)s... Omittente." #: bin/remove_members:173 +#, fuzzy msgid "No such member: %(addr)s" msgstr "Nulle tal membro: %(addr)s" #: bin/remove_members:178 +#, fuzzy msgid "User `%(addr)s' removed from list: %(listname)s." msgstr "Usator `%(addr)s' removite del lista: %(listname)s." @@ -11748,10 +12009,12 @@ msgstr "" " Imprime lo que le scripto face.\n" #: bin/reset_pw.py:77 +#, fuzzy msgid "Changing passwords for list: %(listname)s" msgstr "Cambia contrasignos pro le lista: %(listname)s" #: bin/reset_pw.py:83 +#, fuzzy msgid "New password for member %(member)40s: %(randompw)s" msgstr "Nove contrasigno pro membro %(member)40s: %(randompw)s" @@ -11799,18 +12062,22 @@ msgstr "" "\n" #: bin/rmlist:73 bin/rmlist:76 +#, fuzzy msgid "Removing %(msg)s" msgstr "Removente %(msg)s" #: bin/rmlist:81 +#, fuzzy msgid "%(listname)s %(msg)s not found as %(filename)s" msgstr "%(msg)s de %(listname)s non trovate como %(filename)s" #: bin/rmlist:105 +#, fuzzy msgid "No such list (or list already deleted): %(listname)s" msgstr "Nulle tal lista (o lista ja delite): %(listname)s" #: bin/rmlist:108 +#, fuzzy msgid "No such list: %(listname)s. Removing its residual archives." msgstr "Nulle tal lista: %(listname)s. Removente su archivos residual." @@ -11955,8 +12222,9 @@ msgid "No argument to -f given" msgstr "" #: bin/sync_members:172 +#, fuzzy msgid "Illegal option: %(opt)s" -msgstr "" +msgstr "Nomine de lista invalide: %(s)s" #: bin/sync_members:178 msgid "No listname given" @@ -11967,8 +12235,9 @@ msgid "Must have a listname and a filename" msgstr "" #: bin/sync_members:191 +#, fuzzy msgid "Cannot read address file: %(filename)s: %(msg)s" -msgstr "" +msgstr "Impossibile aperir le file mbox %(mbox)s: %(msg)s" #: bin/sync_members:203 msgid "Ignore : %(addr)30s" @@ -11987,8 +12256,9 @@ msgid "Added : %(s)s" msgstr "" #: bin/sync_members:289 +#, fuzzy msgid "Removed: %(s)s" -msgstr "" +msgstr "Removente %(msg)s" #: bin/transcheck:19 msgid "" @@ -12091,8 +12361,9 @@ msgid "" msgstr "" #: bin/update:107 +#, fuzzy msgid "Fixing language templates: %(listname)s" -msgstr "" +msgstr "Tu nove lista de diffusion: %(listname)s" #: bin/update:196 bin/update:711 msgid "WARNING: could not acquire lock for list: %(listname)s" @@ -12184,8 +12455,9 @@ msgid "removing directory %(src)s and everything underneath" msgstr "" #: bin/update:399 +#, fuzzy msgid "removing %(src)s" -msgstr "" +msgstr "Removente %(msg)s" #: bin/update:403 msgid "Warning: couldn't remove %(src)s -- %(rest)s" @@ -12200,6 +12472,7 @@ msgid "updating old qfiles" msgstr "" #: bin/update:455 +#, fuzzy msgid "Warning! Not a directory: %(dirpath)s" msgstr "Mal directorio de cauda: %(dirpath)s" @@ -12246,8 +12519,9 @@ msgid "done" msgstr "" #: bin/update:691 +#, fuzzy msgid "Updating mailing list: %(listname)s" -msgstr "" +msgstr "Tu nove lista de diffusion: %(listname)s" #: bin/update:694 msgid "Updating Usenet watermarks" @@ -12452,16 +12726,18 @@ msgid "" msgstr "" #: bin/withlist:175 +#, fuzzy msgid "Unlocking (but not saving) list: %(listname)s" -msgstr "" +msgstr "Tu nove lista de diffusion: %(listname)s" #: bin/withlist:179 msgid "Finalizing" msgstr "" #: bin/withlist:188 +#, fuzzy msgid "Loading list %(listname)s" -msgstr "" +msgstr "Tu nove lista de diffusion: %(listname)s" #: bin/withlist:190 msgid "(locked)" @@ -12472,8 +12748,9 @@ msgid "(unlocked)" msgstr "" #: bin/withlist:197 +#, fuzzy msgid "Unknown list: %(listname)s" -msgstr "" +msgstr "Lista inexistente: %(listname)s" #: bin/withlist:237 msgid "No list name supplied." @@ -12680,8 +12957,9 @@ msgid "Password // URL" msgstr "" #: cron/mailpasswds:222 +#, fuzzy msgid "%(host)s mailing list memberships reminder" -msgstr "" +msgstr "Rememoration del lista de diffusion %(listfullname)s" #: cron/nightly_gzip:19 msgid "" @@ -12747,8 +13025,8 @@ msgstr "" #~ " applied" #~ msgstr "" #~ "Texto a includer in ulle\n" -#~ " notitia de rejection\n" #~ " inviate a membros moderate qui scribe al lista." diff --git a/messages/it/LC_MESSAGES/mailman.po b/messages/it/LC_MESSAGES/mailman.po index 6914e6fd..abf03d9a 100755 --- a/messages/it/LC_MESSAGES/mailman.po +++ b/messages/it/LC_MESSAGES/mailman.po @@ -71,10 +71,12 @@ msgid "

                    Currently, there are no archives.

                    " msgstr "

                    Attualmente non ci sono archivi.

                    " #: Mailman/Archiver/HyperArch.py:821 +#, fuzzy msgid "Gzip'd Text%(sz)s" msgstr "Testo zippato%(sz)s" #: Mailman/Archiver/HyperArch.py:826 +#, fuzzy msgid "Text%(sz)s" msgstr "Testo%(sz)s" @@ -156,18 +158,22 @@ msgid "Third" msgstr "Terzo" #: Mailman/Archiver/HyperArch.py:938 +#, fuzzy msgid "%(ord)s quarter %(year)i" msgstr "%(ord)s trimestre %(year)i" #: Mailman/Archiver/HyperArch.py:945 +#, fuzzy msgid "%(month)s %(year)i" msgstr "%(month)s %(year)i" #: Mailman/Archiver/HyperArch.py:950 +#, fuzzy msgid "The Week Of Monday %(day)i %(month)s %(year)i" msgstr "La settimana di Lunedì %(day)i %(month)s %(year)i" #: Mailman/Archiver/HyperArch.py:954 +#, fuzzy msgid "%(day)i %(month)s %(year)i" msgstr "%(day)i %(month)s %(year)i" @@ -176,10 +182,12 @@ msgid "Computing threaded index\n" msgstr "Calcolo l'indice per thread\n" #: Mailman/Archiver/HyperArch.py:1319 +#, fuzzy msgid "Updating HTML for article %(seq)s" msgstr "Aggiorno l'HTML per l'articolo %(seq)s" #: Mailman/Archiver/HyperArch.py:1326 +#, fuzzy msgid "article file %(filename)s is missing!" msgstr "manca il file dell'articolo %(filename)s!" @@ -198,6 +206,7 @@ msgid "Pickling archive state into " msgstr "Salvo lo stato dell'archivio in " #: Mailman/Archiver/pipermail.py:453 +#, fuzzy msgid "Updating index files for archive [%(archive)s]" msgstr "Aggiorno gli indici per l'archivio [%(archive)s]" @@ -206,6 +215,7 @@ msgid " Thread" msgstr " Argomento" #: Mailman/Archiver/pipermail.py:597 +#, fuzzy msgid "#%(counter)05d %(msgid)s" msgstr "#%(counter)05d %(msgid)s" @@ -245,6 +255,7 @@ msgid "disabled address" msgstr "disabilitata" #: Mailman/Bouncer.py:304 +#, fuzzy msgid " The last bounce received from you was dated %(date)s" msgstr " L'ultimo errore sul tuo indirizzo è datato %(date)s" @@ -276,6 +287,7 @@ msgstr "l'amministratore della lista" #: Mailman/Cgi/options.py:101 Mailman/Cgi/private.py:108 #: Mailman/Cgi/rmlist.py:75 Mailman/Cgi/roster.py:59 #: Mailman/Cgi/subscribe.py:67 +#, fuzzy msgid "No such list %(safelistname)s" msgstr "Non esiste la lista %(safelistname)s" @@ -373,6 +385,7 @@ msgstr "" # /home/mailman/Mailman/Cgi/admin.py:197 #: Mailman/Cgi/admin.py:268 +#, fuzzy msgid "%(hostname)s mailing lists - Admin Links" msgstr "Liste di %(hostname)s - Amministrazione" @@ -387,6 +400,7 @@ msgid "Mailman" msgstr "Mailman" #: Mailman/Cgi/admin.py:310 +#, fuzzy msgid "" "

                    There currently are no publicly-advertised %(mailmanlink)s\n" " mailing lists on %(hostname)s." @@ -395,6 +409,7 @@ msgstr "" " pubblico su %(hostname)s." #: Mailman/Cgi/admin.py:316 +#, fuzzy msgid "" "

                    Below is the collection of publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s. Click on a list\n" @@ -413,6 +428,7 @@ msgid "right " msgstr "destra " #: Mailman/Cgi/admin.py:325 +#, fuzzy msgid "" "To visit the administrators configuration page for an\n" " unadvertised list, open a URL similar to this one, but with a '/' " @@ -468,6 +484,7 @@ msgstr "Nessun nome di variabile valido trovato." # /home/mailman/Mailman/Cgi/admin.py:426 #: Mailman/Cgi/admin.py:395 +#, fuzzy msgid "" "%(realname)s Mailing list Configuration Help\n" "
                    %(varname)s Option" @@ -477,6 +494,7 @@ msgstr "" # /home/mailman/Mailman/Cgi/admin.py:431 #: Mailman/Cgi/admin.py:402 +#, fuzzy msgid "Mailman %(varname)s List Option Help" msgstr "Aiuto sulle Opzioni della lista %(varname)s" @@ -494,14 +512,17 @@ msgstr "" " di ricaricare le pagine e di vuotare le cache. Puoi anche " #: Mailman/Cgi/admin.py:431 +#, fuzzy msgid "return to the %(categoryname)s options page." msgstr "ritornare alla pagina di opzioni %(categoryname)s." #: Mailman/Cgi/admin.py:446 +#, fuzzy msgid "%(realname)s Administration (%(label)s)" msgstr "Amministrazione di %(realname)s (%(label)s)" #: Mailman/Cgi/admin.py:447 +#, fuzzy msgid "%(realname)s mailing list administration
                    %(label)s Section" msgstr "Amministrazione della lista %(realname)s
                    Sezione %(label)s" @@ -590,6 +611,7 @@ msgstr "Valore" # /home/mailman/Mailman/Cgi/admin.py:459 #: Mailman/Cgi/admin.py:668 +#, fuzzy msgid "" "Badly formed options entry:\n" " %(record)s" @@ -695,10 +717,12 @@ msgid "Move rule down" msgstr "Muovi la regola verso il basso" #: Mailman/Cgi/admin.py:870 +#, fuzzy msgid "
                    (Edit %(varname)s)" msgstr "
                    (Modifica %(varname)s)" #: Mailman/Cgi/admin.py:872 +#, fuzzy msgid "
                    (Details for %(varname)s)" msgstr "
                    (Dettagli per %(varname)s)" @@ -743,6 +767,7 @@ msgid "(help)" msgstr "(aiuto)" #: Mailman/Cgi/admin.py:930 +#, fuzzy msgid "Find member %(link)s:" msgstr "Trova iscritto %(link)s:" @@ -755,10 +780,12 @@ msgid "Bad regular expression: " msgstr "Espressione regolare non valida: " #: Mailman/Cgi/admin.py:1013 +#, fuzzy msgid "%(allcnt)s members total, %(membercnt)s shown" msgstr "%(allcnt)s iscritti in totale, mostrati %(membercnt)s" #: Mailman/Cgi/admin.py:1016 +#, fuzzy msgid "%(allcnt)s members total" msgstr "%(allcnt)s iscritti in totale" @@ -964,6 +991,7 @@ msgstr "" # /home/mailman/Mailman/Cgi/admin.py:564 #: Mailman/Cgi/admin.py:1221 +#, fuzzy msgid "from %(start)s to %(end)s" msgstr "da %(start)s a %(end)s" @@ -1131,6 +1159,7 @@ msgid "Change list ownership passwords" msgstr "Cambia la password del proprietario della lista" #: Mailman/Cgi/admin.py:1364 +#, fuzzy msgid "" "The list administrators are the people who have ultimate control " "over\n" @@ -1254,6 +1283,7 @@ msgstr "Indirizzo errato (caratteri non permessi)" #: Mailman/Cgi/admin.py:1529 Mailman/Cgi/admin.py:1703 bin/add_members:175 #: bin/clone_member:136 bin/sync_members:268 +#, fuzzy msgid "Banned address (matched %(pattern)s)" msgstr "Indirizzo interdetto (corrispondenza con %(pattern)s)" @@ -1369,6 +1399,7 @@ msgid "Not subscribed" msgstr "Non iscritto" #: Mailman/Cgi/admin.py:1773 +#, fuzzy msgid "Ignoring changes to deleted member: %(user)s" msgstr "Ignoro i cambiamenti per l'ex-iscritto: %(user)s" @@ -1384,10 +1415,12 @@ msgid "Error Unsubscribing:" msgstr "Errore durante la cancellazione:" #: Mailman/Cgi/admindb.py:235 Mailman/Cgi/admindb.py:248 +#, fuzzy msgid "%(realname)s Administrative Database" msgstr "Pannello di controllo di %(realname)s" #: Mailman/Cgi/admindb.py:238 +#, fuzzy msgid "%(realname)s Administrative Database Results" msgstr "Risultati del pannello di controllo di %(realname)s" @@ -1419,6 +1452,7 @@ msgid "Discard all messages marked Defer" msgstr "Scarta tutti i messaggi marcati Rimando la decisione" #: Mailman/Cgi/admindb.py:298 +#, fuzzy msgid "all of %(esender)s's held messages." msgstr "tutti i messaggi di %(esender)s che sono stati trattenuti." @@ -1446,6 +1480,7 @@ msgstr "elenco delle liste disponibili." # /home/mailman/Mailman/Cgi/private.py:66 # /home/mailman/Mailman/Cgi/private.py:78 #: Mailman/Cgi/admindb.py:362 +#, fuzzy msgid "You must specify a list name. Here is the %(link)s" msgstr "Devi indicare una lista. Qui c'è il %(link)s" @@ -1535,6 +1570,7 @@ msgid "The sender is now a member of this list" msgstr "Il mittente ora è un iscritto di questa lista" #: Mailman/Cgi/admindb.py:568 +#, fuzzy msgid "Add %(esender)s to one of these sender filters:" msgstr "Aggiungi %(esender)s ad uno di questi filtri mittenti" @@ -1556,6 +1592,7 @@ msgid "Rejects" msgstr "Rigetta" #: Mailman/Cgi/admindb.py:584 +#, fuzzy msgid "" "Ban %(esender)s from ever subscribing to this\n" " mailing list" @@ -1570,6 +1607,7 @@ msgstr "" " oppure puoi " #: Mailman/Cgi/admindb.py:591 +#, fuzzy msgid "view all messages from %(esender)s" msgstr "vedere tutti i messaggi inviati da %(esender)s" @@ -1654,6 +1692,7 @@ msgid " is already a member" msgstr " è già iscritto" #: Mailman/Cgi/admindb.py:973 +#, fuzzy msgid "%(addr)s is banned (matched: %(patt)s)" msgstr "%(addr)s è interdetto (corrispondenza con: %(patt)s)" @@ -1706,6 +1745,7 @@ msgstr "" " è stata ignorata." #: Mailman/Cgi/confirm.py:178 +#, fuzzy msgid "System error, bad content: %(content)s" msgstr "Errore di sistema, contenuto non valido: %(content)s" @@ -1743,6 +1783,7 @@ msgid "Confirm subscription request" msgstr "Richieste di iscrizione" #: Mailman/Cgi/confirm.py:259 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " subscription request to the mailing list %(listname)s. Your\n" @@ -1776,6 +1817,7 @@ msgstr "" " questa richiesta." #: Mailman/Cgi/confirm.py:275 +#, fuzzy msgid "" "Your confirmation is required in order to continue with\n" " the subscription request to the mailing list %(listname)s.\n" @@ -1828,6 +1870,7 @@ msgid "Preferred language:" msgstr "Lingua preferita:" #: Mailman/Cgi/confirm.py:317 +#, fuzzy msgid "Subscribe to list %(listname)s" msgstr "Iscrivimi a %(listname)s" @@ -1846,6 +1889,7 @@ msgid "Awaiting moderator approval" msgstr "Messaggio sospeso per approvazione" #: Mailman/Cgi/confirm.py:386 +#, fuzzy msgid "" " You have successfully confirmed your subscription request to " "the\n" @@ -1878,6 +1922,7 @@ msgid "You are already a member of this mailing list!" msgstr "Sei già un membro di questa lista!" #: Mailman/Cgi/confirm.py:400 +#, fuzzy msgid "" "You are currently banned from subscribing to\n" " this list. If you think this restriction is erroneous, please\n" @@ -1904,6 +1949,7 @@ msgid "Subscription request confirmed" msgstr "Richiesta di iscrizione confermata" #: Mailman/Cgi/confirm.py:418 +#, fuzzy msgid "" " You have successfully confirmed your subscription request for\n" " \"%(addr)s\" to the %(listname)s mailing list. A separate\n" @@ -1935,6 +1981,7 @@ msgid "Unsubscription request confirmed" msgstr "Richiesta di cancellazione confermata" #: Mailman/Cgi/confirm.py:469 +#, fuzzy msgid "" " You have successfully unsubscribed from the %(listname)s " "mailing\n" @@ -1956,6 +2003,7 @@ msgid "Not available" msgstr "Non disponibile" #: Mailman/Cgi/confirm.py:498 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " unsubscription request from the mailing list %(listname)s. " @@ -2001,6 +2049,7 @@ msgid "You have canceled your change of address request." msgstr "Hai cancellato la tua richiesta di cambio indirizzo." #: Mailman/Cgi/confirm.py:555 +#, fuzzy msgid "" "%(newaddr)s is banned from subscribing to the\n" " %(realname)s list. If you think this restriction is erroneous,\n" @@ -2028,6 +2077,7 @@ msgid "Change of address request confirmed" msgstr "Richiesta di cambio indirizzo confermata" #: Mailman/Cgi/confirm.py:571 +#, fuzzy msgid "" " You have successfully changed your address on the %(listname)s\n" " mailing list from %(oldaddr)s to %(newaddr)s. " @@ -2050,6 +2100,7 @@ msgid "globally" msgstr "globalmente" #: Mailman/Cgi/confirm.py:605 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " change of address request for the mailing list %(listname)s. " @@ -2111,6 +2162,7 @@ msgid "Sender discarded message via web." msgstr "Il mittente ha scartato il messaggio via web." #: Mailman/Cgi/confirm.py:674 +#, fuzzy msgid "" "The held message with the Subject:\n" " header %(subject)s could not be found. The most " @@ -2131,6 +2183,7 @@ msgid "Posted message canceled" msgstr "Il messaggio inviato è stato cancellato" #: Mailman/Cgi/confirm.py:685 +#, fuzzy msgid "" " You have successfully canceled the posting of your message with\n" " the Subject: header %(subject)s to the mailing list\n" @@ -2152,6 +2205,7 @@ msgstr "" " stato gestito dall'amministratore della lista." #: Mailman/Cgi/confirm.py:735 +#, fuzzy msgid "" "Your confirmation is required in order to cancel the\n" " posting of your message to the mailing list %(listname)s:\n" @@ -2200,6 +2254,7 @@ msgid "Membership re-enabled." msgstr "Iscrizione riabilitata." #: Mailman/Cgi/confirm.py:798 +#, fuzzy msgid "" " You have successfully re-enabled your membership in the\n" " %(listname)s mailing list. You can now not available" msgstr "non disponibile" #: Mailman/Cgi/confirm.py:846 +#, fuzzy msgid "" "Your membership in the %(realname)s mailing list is\n" " currently disabled due to excessive bounces. Your confirmation is\n" @@ -2302,10 +2359,12 @@ msgid "administrative list overview" msgstr "pannello di controllo" #: Mailman/Cgi/create.py:115 +#, fuzzy msgid "List name must not include \"@\": %(safelistname)s" msgstr "Il nome della lista non deve contenere \"@\": %(safelistname)s" #: Mailman/Cgi/create.py:122 +#, fuzzy msgid "List already exists: %(safelistname)s" msgstr "Lista già esistente: %(safelistname)s" @@ -2348,19 +2407,23 @@ msgid "You are not authorized to create new mailing lists" msgstr "Non sei autorizzato a creare nuove liste" #: Mailman/Cgi/create.py:182 +#, fuzzy msgid "Unknown virtual host: %(safehostname)s" msgstr "Host virtuale ignota: %(safehostname)s" # /home/mailman/Mailman/Cgi/admin.py:861 #: Mailman/Cgi/create.py:218 bin/newlist:219 +#, fuzzy msgid "Bad owner email address: %(s)s" msgstr "Indirizzo del proprietario non valido: %(s)s" #: Mailman/Cgi/create.py:223 bin/newlist:182 bin/newlist:223 +#, fuzzy msgid "List already exists: %(listname)s" msgstr "Lista già esistente: %(listname)s" #: Mailman/Cgi/create.py:231 bin/newlist:217 +#, fuzzy msgid "Illegal list name: %(s)s" msgstr "Nome lista non valido: %(s)s" @@ -2374,6 +2437,7 @@ msgstr "" " l'amministratore di Mailman per assistenza." #: Mailman/Cgi/create.py:273 bin/newlist:265 +#, fuzzy msgid "Your new mailing list: %(listname)s" msgstr "La tua nuova lista: %(listname)s" @@ -2382,6 +2446,7 @@ msgid "Mailing list creation results" msgstr "Risultato della creazione della lista" #: Mailman/Cgi/create.py:288 +#, fuzzy msgid "" "You have successfully created the mailing list\n" " %(listname)s and notification has been sent to the list owner\n" @@ -2406,6 +2471,7 @@ msgid "Create another list" msgstr "Crea una nuova lista" #: Mailman/Cgi/create.py:312 +#, fuzzy msgid "Create a %(hostname)s Mailing List" msgstr "Crea una lista su %(hostname)s" @@ -2521,6 +2587,7 @@ msgstr "" " dai nuoi iscritti siano sospesi per default." #: Mailman/Cgi/create.py:432 +#, fuzzy msgid "" "Initial list of supported languages.

                    Note that if you do not\n" " select at least one initial language, the list will use the server\n" @@ -2639,6 +2706,7 @@ msgstr "Il nome della lista è obbligatorio." # /home/mailman/Mailman/Cgi/edithtml.py:86 #: Mailman/Cgi/edithtml.py:154 +#, fuzzy msgid "%(realname)s -- Edit html for %(template_info)s" msgstr "%(realname)s -- Variazione html per %(template_info)s" @@ -2649,12 +2717,14 @@ msgstr "Modifica HTML : Errore" # /home/mailman/Mailman/Cgi/edithtml.py:91 #: Mailman/Cgi/edithtml.py:161 +#, fuzzy msgid "%(safetemplatename)s: Invalid template" msgstr "%(safetemplatename)s: Template non valido" # /home/mailman/Mailman/Cgi/edithtml.py:96 # /home/mailman/Mailman/Cgi/edithtml.py:97 #: Mailman/Cgi/edithtml.py:166 Mailman/Cgi/edithtml.py:167 +#, fuzzy msgid "%(realname)s -- HTML Page Editing" msgstr "%(realname)s -- Modifica di pagina HTML" @@ -2725,10 +2795,12 @@ msgid "HTML successfully updated." msgstr "HTML modificato con successo." #: Mailman/Cgi/listinfo.py:89 +#, fuzzy msgid "%(hostname)s Mailing Lists" msgstr "Liste su %(hostname)s" #: Mailman/Cgi/listinfo.py:127 +#, fuzzy msgid "" "

                    There currently are no publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s." @@ -2737,6 +2809,7 @@ msgstr "" " su %(hostname)s." #: Mailman/Cgi/listinfo.py:131 +#, fuzzy msgid "" "

                    Below is a listing of all the public mailing lists on\n" " %(hostname)s. Click on a list name to get more information " @@ -2757,6 +2830,7 @@ msgid "right" msgstr "destra" #: Mailman/Cgi/listinfo.py:140 +#, fuzzy msgid "" " To visit the general information page for an unadvertised list,\n" " open a URL similar to this one, but with a '/' and the %(adj)s\n" @@ -2823,6 +2897,7 @@ msgstr "Indirizzo email errato/non valido" #: Mailman/Cgi/options.py:183 Mailman/Cgi/options.py:240 #: Mailman/Cgi/options.py:264 +#, fuzzy msgid "No such member: %(safeuser)s." msgstr "Nessun iscritto: %(safeuser)s." @@ -2877,6 +2952,7 @@ msgstr "Nota: " # /home/mailman/Mailman/Cgi/handle_opts.py:147 #: Mailman/Cgi/options.py:378 +#, fuzzy msgid "List subscriptions for %(safeuser)s on %(hostname)s" msgstr "Lista delle iscrizioni per %(safeuser)s su %(hostname)s" @@ -2908,6 +2984,7 @@ msgid "You are already using that email address" msgstr "Stai già usando questo indirizzo" #: Mailman/Cgi/options.py:459 +#, fuzzy msgid "" "The new address you requested %(newaddr)s is already a member of the\n" "%(listname)s mailing list, however you have also requested a global change " @@ -2921,6 +2998,7 @@ msgstr "" "l'indirizzo %(safeuser)s sarà cambiata." #: Mailman/Cgi/options.py:468 +#, fuzzy msgid "The new address is already a member: %(newaddr)s" msgstr "Il nuovo indirizzo è già un iscritto: %(newaddr)s" @@ -2929,6 +3007,7 @@ msgid "Addresses may not be blank" msgstr "Gli indirizzi non possono essere vuoti" #: Mailman/Cgi/options.py:488 +#, fuzzy msgid "A confirmation message has been sent to %(newaddr)s. " msgstr "Una richiesta di conferma è stata spedita a %(newaddr)s" @@ -2944,10 +3023,12 @@ msgstr "Indirizzo email errato/non valido" # /home/mailman/Mailman/MailCommandHandler.py:297 #: Mailman/Cgi/options.py:501 +#, fuzzy msgid "%(newaddr)s is already a member of the list." msgstr "%(newaddr)s è già iscritto alla lista." #: Mailman/Cgi/options.py:504 +#, fuzzy msgid "" "%(newaddr)s is banned from this list. If you\n" " think this restriction is erroneous, please contact\n" @@ -3028,6 +3109,7 @@ msgstr "" " preso una decisione." #: Mailman/Cgi/options.py:623 +#, fuzzy msgid "" "You have been successfully unsubscribed from the\n" " mailing list %(fqdn_listname)s. If you were receiving digest\n" @@ -3128,6 +3210,7 @@ msgid "day" msgstr "giorno" #: Mailman/Cgi/options.py:900 +#, fuzzy msgid "%(days)d %(units)s" msgstr "%(days)d %(units)s" @@ -3141,6 +3224,7 @@ msgstr "Non sono stati definiti argomenti" # /home/mailman/Mailman/Cgi/options.py:151 #: Mailman/Cgi/options.py:940 +#, fuzzy msgid "" "\n" "You are subscribed to this list with the case-preserved address\n" @@ -3152,6 +3236,7 @@ msgstr "" "%(cpuser)s." #: Mailman/Cgi/options.py:956 +#, fuzzy msgid "%(realname)s list: member options login page" msgstr "lista %(realname)s: login alla pagina di opzioni per gli iscritti" @@ -3161,10 +3246,12 @@ msgid "email address and " msgstr "indirizzo email e" #: Mailman/Cgi/options.py:960 +#, fuzzy msgid "%(realname)s list: member options for user %(safeuser)s" msgstr "Lista %(realname)s: opzioni di iscrizione per %(safeuser)s" #: Mailman/Cgi/options.py:986 +#, fuzzy msgid "" "In order to change your membership option, you must\n" " first log in by giving your %(extra)smembership password in the section\n" @@ -3245,6 +3332,7 @@ msgid "" msgstr "" #: Mailman/Cgi/options.py:1140 +#, fuzzy msgid "Requested topic is not valid: %(topicname)s" msgstr "L'argomento richiesto non è valido: %(topicname)s" @@ -3280,6 +3368,7 @@ msgstr "Archivio privato - \"./\" e \"../\" non permessi nell'URL." # /home/mailman/Mailman/Cgi/private.py:99 #: Mailman/Cgi/private.py:109 +#, fuzzy msgid "Private Archive Error - %(msg)s" msgstr "Errore di archivio privato - %(msg)s" @@ -3323,6 +3412,7 @@ msgid "Mailing list deletion results" msgstr "Risultati di cancellazione lista" #: Mailman/Cgi/rmlist.py:187 +#, fuzzy msgid "" "You have successfully deleted the mailing list\n" " %(listname)s." @@ -3331,6 +3421,7 @@ msgstr "" " %(listname)s." #: Mailman/Cgi/rmlist.py:191 +#, fuzzy msgid "" "There were some problems deleting the mailing list\n" " %(listname)s. Contact your site administrator at " @@ -3342,6 +3433,7 @@ msgstr "" " server all'indirizzo %(sitelist)s per i dettagli" #: Mailman/Cgi/rmlist.py:208 +#, fuzzy msgid "Permanently remove mailing list %(realname)s" msgstr "Rimuovi permanentemente la lista %(realname)s" @@ -3415,6 +3507,7 @@ msgstr "Opzioni non valide allo script CGI" # /home/mailman/Mailman/Cgi/roster.py:72 #: Mailman/Cgi/roster.py:118 +#, fuzzy msgid "%(realname)s roster authentication failed." msgstr "Autenticazione fallita per %(realname)s roster." @@ -3487,6 +3580,7 @@ msgstr "" "contenente istruzioni dettagliate." #: Mailman/Cgi/subscribe.py:262 +#, fuzzy msgid "" "The email address you supplied is banned from this\n" " mailing list. If you think this restriction is erroneous, please\n" @@ -3513,6 +3607,7 @@ msgstr "" "l'indirizzo email che hai presentato non è sicuro." #: Mailman/Cgi/subscribe.py:278 +#, fuzzy msgid "" "Confirmation from your email address is required, to prevent anyone from\n" "subscribing you without permission. Instructions are being sent to you at\n" @@ -3528,6 +3623,7 @@ msgstr "" "richiesta di conferma." #: Mailman/Cgi/subscribe.py:290 +#, fuzzy msgid "" "Your subscription request was deferred because %(x)s. Your request has " "been\n" @@ -3551,6 +3647,7 @@ msgid "Mailman privacy alert" msgstr "Avvertimento privacy di Mailman" #: Mailman/Cgi/subscribe.py:316 +#, fuzzy msgid "" "An attempt was made to subscribe your address to the mailing list\n" "%(listaddr)s. You are already subscribed to this mailing list.\n" @@ -3597,6 +3694,7 @@ msgstr "Questa lista supporta solamente il modo digest." # /home/mailman/Mailman/Cgi/subscribe.py:208 #: Mailman/Cgi/subscribe.py:344 +#, fuzzy msgid "You have been successfully subscribed to the %(realname)s mailing list." msgstr "Sei stato correttamente iscritto alla lista %(realname)s." @@ -3650,6 +3748,7 @@ msgstr "" "Non sei un iscritto. Ti sei già cancellato o hai cambiato il tuo indirizzo?" #: Mailman/Commands/cmd_confirm.py:69 +#, fuzzy msgid "" "You are currently banned from subscribing to this list. If you think this\n" "restriction is erroneous, please contact the list owners at\n" @@ -3733,26 +3832,32 @@ msgid "n/a" msgstr "n/a" #: Mailman/Commands/cmd_info.py:44 +#, fuzzy msgid "List name: %(listname)s" msgstr "Nome lista: %(listname)s" #: Mailman/Commands/cmd_info.py:45 +#, fuzzy msgid "Description: %(description)s" msgstr "Descrizione: %(description)s" #: Mailman/Commands/cmd_info.py:46 +#, fuzzy msgid "Postings to: %(postaddr)s" msgstr "Messaggi a: %(postaddr)s" #: Mailman/Commands/cmd_info.py:47 +#, fuzzy msgid "List Helpbot: %(requestaddr)s" msgstr "Comandi per la lista: %(requestaddr)s" #: Mailman/Commands/cmd_info.py:48 +#, fuzzy msgid "List Owners: %(owneraddr)s" msgstr "Proprietari: %(owneraddr)s" #: Mailman/Commands/cmd_info.py:49 +#, fuzzy msgid "More information: %(listurl)s" msgstr "Altre informazioni: %(listurl)s" @@ -3777,18 +3882,22 @@ msgstr "" # /home/mailman/Mailman/MailCommandHandler.py:383 #: Mailman/Commands/cmd_lists.py:44 +#, fuzzy msgid "Public mailing lists at %(hostname)s:" msgstr "Liste pubbliche su %(hostname)s:" #: Mailman/Commands/cmd_lists.py:66 +#, fuzzy msgid "%(i)3d. List name: %(realname)s" msgstr "%(i)3d. Nome lista: %(realname)s" #: Mailman/Commands/cmd_lists.py:67 +#, fuzzy msgid " Description: %(description)s" msgstr " Descrizione: %(description)s" #: Mailman/Commands/cmd_lists.py:68 +#, fuzzy msgid " Requests to: %(requestaddr)s" msgstr " Richieste a: %(requestaddr)s" @@ -3823,6 +3932,7 @@ msgstr "" " registrato.\n" #: Mailman/Commands/cmd_password.py:51 Mailman/Commands/cmd_password.py:66 +#, fuzzy msgid "Your password is: %(password)s" msgstr "La tua password è: %(password)s" @@ -3830,6 +3940,7 @@ msgstr "La tua password #: Mailman/Commands/cmd_password.py:57 Mailman/Commands/cmd_password.py:72 #: Mailman/Commands/cmd_password.py:95 Mailman/Commands/cmd_password.py:121 #: Mailman/Commands/cmd_set.py:149 Mailman/Commands/cmd_set.py:219 +#, fuzzy msgid "You are not a member of the %(listname)s mailing list" msgstr "Non sei iscritto alla lista %(listname)s" @@ -4010,6 +4121,7 @@ msgstr "" " del promemoria contenente la tua password per questa lista.\n" #: Mailman/Commands/cmd_set.py:122 +#, fuzzy msgid "Bad set command: %(subcmd)s" msgstr "Comando set errato: %(subcmd)s" @@ -4031,6 +4143,7 @@ msgid "on" msgstr "on" #: Mailman/Commands/cmd_set.py:154 +#, fuzzy msgid " ack %(onoff)s" msgstr " ack %(onoff)s" @@ -4073,22 +4186,27 @@ msgid "due to bounces" msgstr "a causa degli errori" #: Mailman/Commands/cmd_set.py:186 +#, fuzzy msgid " %(status)s (%(how)s on %(date)s)" msgstr " %(status)s (%(how)s il %(date)s)" #: Mailman/Commands/cmd_set.py:192 +#, fuzzy msgid " myposts %(onoff)s" msgstr " myposts %(onoff)s" #: Mailman/Commands/cmd_set.py:195 +#, fuzzy msgid " hide %(onoff)s" msgstr " hide %(onoff)s" #: Mailman/Commands/cmd_set.py:199 +#, fuzzy msgid " duplicates %(onoff)s" msgstr " duplicates %(onoff)s" #: Mailman/Commands/cmd_set.py:203 +#, fuzzy msgid " reminders %(onoff)s" msgstr " reminders %(onoff)s" @@ -4101,6 +4219,7 @@ msgid "You did not give the correct password" msgstr "La password fornita è sbagliata" #: Mailman/Commands/cmd_set.py:236 Mailman/Commands/cmd_set.py:283 +#, fuzzy msgid "Bad argument: %(arg)s" msgstr "Parametro errato: %(arg)s" @@ -4184,6 +4303,7 @@ msgstr "" # /home/mailman/Mailman/Cgi/admin.py:42 #: Mailman/Commands/cmd_subscribe.py:62 +#, fuzzy msgid "Bad digest specifier: %(arg)s" msgstr "Parametro digest errato: %(arg)s" @@ -4192,6 +4312,7 @@ msgid "No valid address found to subscribe" msgstr "Non è stato trovato un indirizzo valido da iscrivere" #: Mailman/Commands/cmd_subscribe.py:113 +#, fuzzy msgid "" "The email address you supplied is banned from this mailing list.\n" "If you think this restriction is erroneous, please contact the list\n" @@ -4238,6 +4359,7 @@ msgid "This list only supports digest subscriptions!" msgstr "Questa lista supporta solamente il modo digest!" #: Mailman/Commands/cmd_subscribe.py:146 +#, fuzzy msgid "" "Your subscription request has been forwarded to the list administrator\n" "at %(listowner)s for review." @@ -4275,6 +4397,7 @@ msgstr "" # /home/mailman/Mailman/MailCommandHandler.py:297 #: Mailman/Commands/cmd_unsubscribe.py:62 +#, fuzzy msgid "%(address)s is not a member of the %(listname)s mailing list" msgstr "%(address)s non è un iscritto alla lista %(listname)s." @@ -4540,6 +4663,7 @@ msgstr "Cinese (Taiwan)" # /home/mailman/Mailman/Deliverer.py:208 #: Mailman/Deliverer.py:53 +#, fuzzy msgid "" "Note: Since this is a list of mailing lists, administrative\n" "notices like the password reminder will be sent to\n" @@ -4556,15 +4680,18 @@ msgstr " (modalità digest)" # /home/mailman/Mailman/Deliverer.py:236 #: Mailman/Deliverer.py:79 +#, fuzzy msgid "Welcome to the \"%(realname)s\" mailing list%(digmode)s" msgstr "Benvenuto nella lista \"%(realname)s\"%(digmode)s" # /home/mailman/Mailman/Deliverer.py:242 #: Mailman/Deliverer.py:89 +#, fuzzy msgid "You have been unsubscribed from the %(realname)s mailing list" msgstr "Sei stato cancellato dalla lista %(realname)s" #: Mailman/Deliverer.py:116 +#, fuzzy msgid "%(listfullname)s mailing list reminder" msgstr "promemoria della mailing list %(listfullname)s" @@ -4578,6 +4705,7 @@ msgid "Hostile subscription attempt detected" msgstr "Rilevato tentativo ostile di iscrizione alla lista" #: Mailman/Deliverer.py:169 +#, fuzzy msgid "" "%(address)s was invited to a different mailing\n" "list, but in a deliberate malicious attempt they tried to confirm the\n" @@ -4591,6 +4719,7 @@ msgstr "" "da parte tua." #: Mailman/Deliverer.py:188 +#, fuzzy msgid "" "You invited %(address)s to your list, but in a\n" "deliberate malicious attempt, they tried to confirm the invitation to a\n" @@ -4605,6 +4734,7 @@ msgstr "" "azioni da parte tua." #: Mailman/Deliverer.py:221 +#, fuzzy msgid "%(listname)s mailing list probe message" msgstr "messaggio sonda dalla lista %(listname)s" @@ -4816,8 +4946,8 @@ msgid "" " membership.\n" "\n" "

                    You can control both the\n" -" number\n" +" number\n" " of reminders the member will receive and the\n" " No those messages too will get discarded. You may " "want\n" " to set up an\n" -" autoresponse\n" +" autoresponse\n" " message for email to the -owner and -admin address." msgstr "" "Anche se l'inteprete dei messaggi di errore ricevuti da\n" @@ -5132,6 +5262,7 @@ msgstr "" " viene sempre fatto." #: Mailman/Gui/Bounce.py:194 +#, fuzzy msgid "" "Bad value for %(property)s: %(val)s" @@ -5263,8 +5394,8 @@ msgstr "" "\n" "

                    Le righe vuote sono ignorate.\n" "\n" -"

                    Vedi anche Vedi anche pass_mime_types per una lista bianca dei tipi di " "contenuto\n" " permessi." @@ -5284,8 +5415,8 @@ msgid "" "Use this option to remove each message attachment that does\n" " not have a matching content type. Requirements and formats " "are\n" -" exactly like filter_mime_types.\n" "\n" "

                    Note: if you add entries to this list but don't add\n" @@ -5410,6 +5541,7 @@ msgstr "" " disponibile solo se abilitata dall'amministratore." #: Mailman/Gui/ContentFilter.py:171 +#, fuzzy msgid "Bad MIME type ignored: %(spectype)s" msgstr "Tipo MIME non valido ignorato: %(spectype)s" @@ -5531,6 +5663,7 @@ msgstr "" " vuoto?" #: Mailman/Gui/Digest.py:145 +#, fuzzy msgid "" "The next digest will be sent as volume\n" " %(volume)s, number %(number)s" @@ -5547,14 +5680,17 @@ msgid "There was no digest to send." msgstr "Non c'erano digest in attesa." #: Mailman/Gui/GUIBase.py:173 +#, fuzzy msgid "Invalid value for variable: %(property)s" msgstr "Valore non valido per la variabile %(property)s" #: Mailman/Gui/GUIBase.py:178 +#, fuzzy msgid "Bad email address for option %(property)s: %(error)s" msgstr "Indirizzo errato per l'opzione %(property)s: %(error)s" #: Mailman/Gui/GUIBase.py:204 +#, fuzzy msgid "" "The following illegal substitution variables were\n" " found in the %(property)s string:\n" @@ -5571,6 +5707,7 @@ msgstr "" " finchè non risolvi il problema." #: Mailman/Gui/GUIBase.py:218 +#, fuzzy msgid "" "Your %(property)s string appeared to\n" " have some correctable problems in its new value.\n" @@ -5678,8 +5815,8 @@ msgid "" "

                    In order to split the list ownership duties into\n" " administrators and moderators, you must\n" " set a separate moderator password,\n" -" and also provide the email\n" +" and also provide the email\n" " addresses of the list moderators. Note that the field you\n" " are changing here specifies the list administrators." msgstr "" @@ -6016,13 +6153,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -6060,12 +6197,12 @@ msgstr "" " Un altro è che modificando il Reply-To:\n" " diventa più difficile inviare risposte private.\n" " Vedi\n" -" \n" +" \n" " `Reply-To' Munging Considered Harmful per una discussione\n" " generale su questo argomento. Vedi\n" -" \n" +" \n" " Reply-To Munging Considered Useful per un'opinione\n" " diversa.\n" "\n" @@ -6087,8 +6224,8 @@ msgstr "Intestazione Reply-To: esplicita." msgid "" "This is the address set in the Reply-To: header\n" " when the reply_goes_to_list\n" +" href=\"?VARHELP=general/" +"reply_goes_to_list\">reply_goes_to_list\n" " option is set to Explicit address.\n" "\n" "

                    There are many reasons not to introduce or override the\n" @@ -6096,13 +6233,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -6126,8 +6263,8 @@ msgstr "" "Questo è il valore che assume il campo\n" " Reply-To: dei messaggi che passano sulla lista\n" " quando l'opzione\n" -" reply_goes_to_list\n" +" reply_goes_to_list\n" " è settata a Indirizzo esplicito.\n" "\n" "

                    Ci sono molti motivi per i quali non è\n" @@ -6138,12 +6275,12 @@ msgstr "" " Un altro è che modificando il Reply-To:\n" " diventa più difficile inviare risposte private.\n" " Vedi\n" -" \n" +" \n" " `Reply-To' Munging Considered Harmful per una discussione\n" " generale su questo argomento. Vedi\n" -" \n" +" \n" " Reply-To Munging Considered Useful per una opinione\n" " diversa.\n" "\n" @@ -6210,8 +6347,8 @@ msgid "" " member list addresses, but rather to the owner of those member\n" " lists. In that case, the value of this setting is appended to\n" " the member's account name for such notices. `-owner' is the\n" -" typical choice. This setting has no effect when \"umbrella_list" -"\"\n" +" typical choice. This setting has no effect when " +"\"umbrella_list\"\n" " is \"No\"." msgstr "" "Quando abiliti \"umbrella_list\" per indicare che questa\n" @@ -6703,8 +6840,8 @@ msgid "" " email posted by list members." msgstr "" "Questa è la lingua naturale predefinita per questa lista.\n" -" Se più\n" +" Se più\n" " di una lingua è supportata, allora gli utenti\n" " potranno scegliere la loro personale preferenza per quando\n" " interagiscono con la lista. Tutte le altre interazioni\n" @@ -7245,6 +7382,7 @@ msgstr "" " (o apposta) iscriva terzi senza il loro consenso." #: Mailman/Gui/Privacy.py:104 +#, fuzzy msgid "" "This section allows you to configure subscription and\n" " membership exposure policy. You can also control whether this\n" @@ -7449,8 +7587,8 @@ msgid "" " either individually or as a group. Any\n" " posting from a non-member who is not explicitly accepted,\n" " rejected, or discarded, will have their posting filtered by the\n" -" general\n" +" general\n" " non-member rules.\n" "\n" "

                    In the text boxes below, add one address per line; start the\n" @@ -7513,6 +7651,7 @@ msgstr "" "I messaggi inviati dai nuovi iscritti devono essere moderati per default?" #: Mailman/Gui/Privacy.py:218 +#, fuzzy msgid "" "Each list member has a moderation flag which says\n" " whether messages from the list member can be posted directly " @@ -7569,8 +7708,8 @@ msgid "" "If a member posts this many times, within a period of time\n" " the member is automatically moderated. Use 0 to disable. " "See\n" -" member_verbosity_interval for details on the time " "period.\n" "\n" @@ -7800,8 +7939,8 @@ msgstr "" " notifica. Il mittente non riceverà alcuna risposta o\n" " errore, in ogni caso i moderatori della lista possono \n" " opzionalmente ricevere\n" +" href=\"?VARHELP=privacy/sender/" +"forward_auto_discards\">ricevere\n" " copie dei messaggi auto-scartati.\n" "\n" "

                    Aggiungi gli indirizzi uno per riga; inizia la riga con un\n" @@ -7985,8 +8124,8 @@ msgstr "" " notifica. Il mittente non riceverà alcuna risposta o\n" " errore, in ogni caso i moderatori della lista possono \n" " opzionalmente ricevere\n" +" href=\"?VARHELP=privacy/sender/" +"forward_auto_discards\">ricevere\n" " copie dei messaggi auto-scartati.\n" "\n" "

                    Aggiungi gli indirizzi uno per riga; inizia la riga con un\n" @@ -8039,6 +8178,7 @@ msgstr "" "lista?" #: Mailman/Gui/Privacy.py:494 +#, fuzzy msgid "" "Text to include in any rejection notice to be sent to\n" " non-members who post to this list. This notice can include\n" @@ -8310,6 +8450,7 @@ msgstr "" " Le regole incomplete saranno ignorate." #: Mailman/Gui/Privacy.py:693 +#, fuzzy msgid "" "The header filter rule pattern\n" " '%(safepattern)s' is not a legal regular expression. This\n" @@ -8362,8 +8503,8 @@ msgid "" "

                    The body of the message can also be optionally scanned for\n" " Subject: and Keywords: headers, as\n" " specified by the topics_bodylines_limit\n" +" href=\"?VARHELP=topics/" +"topics_bodylines_limit\">topics_bodylines_limit\n" " configuration variable." msgstr "" "Il filtro per argomenti categorizza ogni messaggio entrante\n" @@ -8464,6 +8605,7 @@ msgstr "" " incompleti saranno ignorati." #: Mailman/Gui/Topics.py:135 +#, fuzzy msgid "" "The topic pattern '%(safepattern)s' is not a\n" " legal regular expression. It will be discarded." @@ -8681,8 +8823,8 @@ msgid "" " newsgroup fields are filled in." msgstr "" "Non puoi abilitare l'inoltro dei messaggi se non\n" -" hai impostato sia il campo\n" +" hai impostato sia il campo\n" " news server che il nome\n" " del gruppo linkato." @@ -8692,6 +8834,7 @@ msgid "%(listinfo_link)s list run by %(owner_link)s" msgstr "Lista %(listinfo_link)s gestita da %(owner_link)s" #: Mailman/HTMLFormatter.py:57 +#, fuzzy msgid "%(realname)s administrative interface" msgstr "Pannello di controllo di %(realname)s" @@ -8700,6 +8843,7 @@ msgid " (requires authorization)" msgstr " (richiede autorizzazione)" #: Mailman/HTMLFormatter.py:61 +#, fuzzy msgid "Overview of all %(hostname)s mailing lists" msgstr "Elenco delle liste disponibili su %(hostname)s" @@ -8723,6 +8867,7 @@ msgid "; it was disabled by the list administrator" msgstr "; è stato disabilitato dall'amministratore della lista" #: Mailman/HTMLFormatter.py:146 +#, fuzzy msgid "" "; it was disabled due to excessive bounces. The\n" " last bounce was received on %(date)s" @@ -8737,6 +8882,7 @@ msgstr "; è stato disabilitato per qualche ragione ignota" # /home/mailman/Mailman/HTMLFormatter.py:145 #: Mailman/HTMLFormatter.py:151 +#, fuzzy msgid "Note: your list delivery is currently disabled%(reason)s." msgstr "" "Nota: l'invio dei messaggi della lista al tuo indirizzo è attualmente " @@ -8753,6 +8899,7 @@ msgid "the list administrator" msgstr "l'amministratore della lista" #: Mailman/HTMLFormatter.py:157 +#, fuzzy msgid "" "

                    %(note)s\n" "\n" @@ -8772,6 +8919,7 @@ msgstr "" " o necessiti aiuto." #: Mailman/HTMLFormatter.py:169 +#, fuzzy msgid "" "

                    We have received some recent bounces from your\n" " address. Your current bounce score is %(score)s out of " @@ -8793,6 +8941,7 @@ msgstr "" " corretti velocemente." #: Mailman/HTMLFormatter.py:181 +#, fuzzy msgid "" "(Note - you are subscribing to a list of mailing lists, so the %(type)s " "notice will be sent to the admin address for your membership, %(addr)s.)

                    " @@ -8840,6 +8989,7 @@ msgstr "" "La decisione finale ti sarà notificata via email." #: Mailman/HTMLFormatter.py:208 +#, fuzzy msgid "" "This is %(also)sa private list, which means that the\n" " list of members is not available to non-members." @@ -8848,6 +8998,7 @@ msgstr "" "iscritti non è disponibile ai non iscritti." #: Mailman/HTMLFormatter.py:211 +#, fuzzy msgid "" "This is %(also)sa hidden list, which means that the\n" " list of members is available only to the list administrator." @@ -8857,6 +9008,7 @@ msgstr "" "lista." #: Mailman/HTMLFormatter.py:214 +#, fuzzy msgid "" "This is %(also)sa public list, which means that the\n" " list of members list is available to everyone." @@ -8873,6 +9025,7 @@ msgstr "" "identificabili dagli spammer). " #: Mailman/HTMLFormatter.py:222 +#, fuzzy msgid "" "

                    (Note that this is an umbrella list, intended to\n" " have only other mailing lists as members. Among other things,\n" @@ -8888,6 +9041,7 @@ msgid "either " msgstr "o " #: Mailman/HTMLFormatter.py:256 +#, fuzzy msgid "" "To unsubscribe from %(realname)s, get a password reminder,\n" " or change your subscription options %(either)senter your " @@ -8924,6 +9078,7 @@ msgstr "" # /home/mailman/Mailman/HTMLFormatter.py:266 #: Mailman/HTMLFormatter.py:277 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " members.)" @@ -8933,6 +9088,7 @@ msgstr "" # /home/mailman/Mailman/HTMLFormatter.py:269 #: Mailman/HTMLFormatter.py:281 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " administrator.)" @@ -9009,6 +9165,7 @@ msgid "The current archive" msgstr "L'archivio corrente" #: Mailman/Handlers/Acknowledge.py:59 +#, fuzzy msgid "%(realname)s post acknowledgement" msgstr "validazione del messaggio di %(realname)s" @@ -9021,6 +9178,7 @@ msgid "" msgstr "" #: Mailman/Handlers/CalcRecips.py:79 +#, fuzzy msgid "" "Your urgent message to the %(realname)s mailing list was not authorized for\n" "delivery. The original message as received by Mailman is attached.\n" @@ -9098,6 +9256,7 @@ msgid "Message may contain administrivia" msgstr "Il messaggio potrebbe contenere richieste amministrative" #: Mailman/Handlers/Hold.py:84 +#, fuzzy msgid "" "Please do *not* post administrative requests to the mailing\n" "list. If you wish to subscribe, visit %(listurl)s or send a message with " @@ -9138,10 +9297,12 @@ msgid "Posting to a moderated newsgroup" msgstr "Messaggio inviato ad un gruppo moderato" #: Mailman/Handlers/Hold.py:252 +#, fuzzy msgid "Your message to %(listname)s awaits moderator approval" msgstr "Il tuo messaggio a %(listname)s attende l'approvazione del moderatore" #: Mailman/Handlers/Hold.py:271 +#, fuzzy msgid "%(listname)s post from %(sender)s requires approval" msgstr "Messaggio a %(listname)s da %(sender)s richiede approvazione" @@ -9184,6 +9345,7 @@ msgid "After content filtering, the message was empty" msgstr "Dopo il filtraggio del contenuto il messaggio è rimasto vuoto" #: Mailman/Handlers/MimeDel.py:269 +#, fuzzy msgid "" "The attached message matched the %(listname)s mailing list's content " "filtering\n" @@ -9228,6 +9390,7 @@ msgstr "Il messaggio allegato # /home/mailman/Mailman/ListAdmin.py:146 #: Mailman/Handlers/Replybot.py:75 +#, fuzzy msgid "Auto-response for your message to the \"%(realname)s\" mailing list" msgstr "Auto-risposta al tuo messaggio diretto alla lista \"%(realname)s\"" @@ -9236,6 +9399,7 @@ msgid "The Mailman Replybot" msgstr "Il Replybot di Mailman" #: Mailman/Handlers/Scrubber.py:208 +#, fuzzy msgid "" "An embedded and charset-unspecified text was scrubbed...\n" "Name: %(filename)s\n" @@ -9251,6 +9415,7 @@ msgid "HTML attachment scrubbed and removed" msgstr "Allegato HTML pulito e rimosso" #: Mailman/Handlers/Scrubber.py:234 Mailman/Handlers/Scrubber.py:259 +#, fuzzy msgid "" "An HTML attachment was scrubbed...\n" "URL: %(url)s\n" @@ -9273,6 +9438,7 @@ msgid "unknown sender" msgstr "mittente sconosciuto" #: Mailman/Handlers/Scrubber.py:276 +#, fuzzy msgid "" "An embedded message was scrubbed...\n" "From: %(who)s\n" @@ -9289,6 +9455,7 @@ msgstr "" "URL: %(url)s\n" #: Mailman/Handlers/Scrubber.py:308 +#, fuzzy msgid "" "A non-text attachment was scrubbed...\n" "Name: %(filename)s\n" @@ -9305,6 +9472,7 @@ msgstr "" "URL: %(url)s\n" #: Mailman/Handlers/Scrubber.py:347 +#, fuzzy msgid "Skipped content of type %(partctype)s\n" msgstr "Rimosso contenuto di tipo %(partctype)s\n" @@ -9336,6 +9504,7 @@ msgid "Message rejected by filter rule match" msgstr "Messaggio respinto a causa di una regola di filtro" #: Mailman/Handlers/ToDigest.py:173 +#, fuzzy msgid "%(realname)s Digest, Vol %(volume)d, Issue %(issue)d" msgstr "Digest di %(realname)s, Volume %(volume)d, Numero %(issue)d" @@ -9378,6 +9547,7 @@ msgstr "Fine di " # /home/mailman/Mailman/ListAdmin.py:146 #: Mailman/ListAdmin.py:309 +#, fuzzy msgid "Posting of your message titled \"%(subject)s\"" msgstr "Invio del tuo messaggio intitolato \"%(subject)s\"" @@ -9392,6 +9562,7 @@ msgstr "Inoltro del messaggio moderato" # /home/mailman/Mailman/ListAdmin.py:57 #: Mailman/ListAdmin.py:405 +#, fuzzy msgid "New subscription request to list %(realname)s from %(addr)s" msgstr "Nuova richiesta di iscrizione a %(realname)s da parte di %(addr)s" @@ -9407,6 +9578,7 @@ msgstr "Continua ad attendere approvazione" # /home/mailman/Mailman/ListAdmin.py:57 #: Mailman/ListAdmin.py:466 +#, fuzzy msgid "New unsubscription request from %(realname)s by %(addr)s" msgstr "Nuova richiesta di cancellazione da %(realname)s da parte di %(addr)s" @@ -9422,10 +9594,12 @@ msgstr "Messaggio originale" # /home/mailman/Mailman/Deliverer.py:255 #: Mailman/ListAdmin.py:526 +#, fuzzy msgid "Request to mailing list %(realname)s rejected" msgstr "Richiesta alla lista %(realname)s rifiutata" #: Mailman/MTA/Manual.py:66 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been created via the through-the-web\n" "interface. In order to complete the activation of this mailing list, the\n" @@ -9454,15 +9628,18 @@ msgstr "" # /home/mailman/Mailman/Deliverer.py:236 #: Mailman/MTA/Manual.py:82 +#, fuzzy msgid "## %(listname)s mailing list" msgstr "## lista %(listname)s" # /home/mailman/Mailman/Cgi/admindb.py:262 #: Mailman/MTA/Manual.py:99 +#, fuzzy msgid "Mailing list creation request for list %(listname)s" msgstr "Richiesta di creazione della lista %(listname)s" #: Mailman/MTA/Manual.py:113 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been removed via the through-the-web\n" "interface. In order to complete the de-activation of this mailing list, " @@ -9479,6 +9656,7 @@ msgstr "" "\n" #: Mailman/MTA/Manual.py:123 +#, fuzzy msgid "" "\n" "To finish removing your mailing list, you must edit your /etc/aliases (or\n" @@ -9495,14 +9673,17 @@ msgstr "" "## %(listname)s mailing list" #: Mailman/MTA/Manual.py:142 +#, fuzzy msgid "Mailing list removal request for list %(listname)s" msgstr "Richiesta di rimozione della lista %(listname)s" #: Mailman/MTA/Postfix.py:442 +#, fuzzy msgid "checking permissions on %(file)s" msgstr "controllo dei permessi su %(file)s" #: Mailman/MTA/Postfix.py:452 +#, fuzzy msgid "%(file)s permissions must be 0664 (got %(octmode)s)" msgstr "i permessi per %(file)s devono essere 0664 (invece di %(octmode)s)" @@ -9516,38 +9697,46 @@ msgid "(fixing)" msgstr "(corretto)" #: Mailman/MTA/Postfix.py:470 +#, fuzzy msgid "checking ownership of %(dbfile)s" msgstr "controllo del proprietario di %(dbfile)s" #: Mailman/MTA/Postfix.py:478 +#, fuzzy msgid "%(dbfile)s owned by %(owner)s (must be owned by %(user)s" msgstr "%(dbfile)s appartiene a %(owner)s (deve appartenere a %(user)s" #: Mailman/MTA/Postfix.py:491 +#, fuzzy msgid "%(dbfile)s permissions must be 0664 (got %(octmode)s)" msgstr "i permessi per %(dbfile)s devono essere 0664 (invece di %(octmode)s)" # /home/mailman/Mailman/Deliverer.py:242 #: Mailman/MailList.py:219 +#, fuzzy msgid "Your confirmation is required to join the %(listname)s mailing list" msgstr "È richiesta la tua conferma per iscriverti alla lista %(listname)s" # /home/mailman/Mailman/Deliverer.py:242 #: Mailman/MailList.py:230 +#, fuzzy msgid "Your confirmation is required to leave the %(listname)s mailing list" msgstr "È richiesta la tua conferma per disiscriverti dalla lista %(listname)s" # /home/mailman/Mailman/Cgi/subscribe.py:129 #: Mailman/MailList.py:999 Mailman/MailList.py:1492 +#, fuzzy msgid " from %(remote)s" msgstr " da %(remote)s" #: Mailman/MailList.py:1043 +#, fuzzy msgid "subscriptions to %(realname)s require moderator approval" msgstr "le iscrizioni a %(realname)s richiedono l'approvazione del moderatore" # /home/mailman/Mailman/Cgi/roster.py:72 #: Mailman/MailList.py:1125 bin/add_members:299 +#, fuzzy msgid "%(realname)s subscription notification" msgstr "Notifica di iscrizione a %(realname)s" @@ -9557,6 +9746,7 @@ msgstr "le cancellazioni richiedono l'approvazione del moderatore" # /home/mailman/Mailman/Cgi/roster.py:72 #: Mailman/MailList.py:1166 +#, fuzzy msgid "%(realname)s unsubscribe notification" msgstr "notifica di cancellazione da %(realname)s" @@ -9577,6 +9767,7 @@ msgid "via web confirmation" msgstr "Stringa di conferma non valida" #: Mailman/MailList.py:1396 +#, fuzzy msgid "subscriptions to %(name)s require administrator approval" msgstr "le iscrizioni a %(name)s richiedono l'approvazione dell'amministratore" @@ -9595,6 +9786,7 @@ msgid "Last autoresponse notification for today" msgstr "Ultima notifica automatica per oggi." #: Mailman/Queue/BounceRunner.py:360 +#, fuzzy msgid "" "The attached message was received as a bounce, but either the bounce format\n" "was not recognized, or no member addresses could be extracted from it. " @@ -9686,6 +9878,7 @@ msgid "Original message suppressed by Mailman site configuration\n" msgstr "" #: Mailman/htmlformat.py:675 +#, fuzzy msgid "Delivered by Mailman
                    version %(version)s" msgstr "Consegnato da Mailman
                    versione %(version)s" @@ -9788,6 +9981,7 @@ msgid "Server Local Time" msgstr "Ora locale del server" #: Mailman/i18n.py:180 +#, fuzzy msgid "" "%(wday)s %(mon)s %(day)2i %(hh)02i:%(mm)02i:%(ss)02i %(tzname)s %(year)04i" msgstr "" @@ -9902,6 +10096,7 @@ msgstr "" # /home/mailman/Mailman/Cgi/admin.py:856 #: bin/add_members:162 bin/add_members:172 +#, fuzzy msgid "Already a member: %(member)s" msgstr "Già iscritto: %(member)s" @@ -9912,11 +10107,13 @@ msgstr "Indirizzo email errato/non valido: riga vuota" # /home/mailman/Mailman/Cgi/admin.py:861 #: bin/add_members:180 +#, fuzzy msgid "Bad/Invalid email address: %(member)s" msgstr "Indirizzo email errato/non valido: %(member)s" # /home/mailman/Mailman/Cgi/admin.py:864 #: bin/add_members:182 +#, fuzzy msgid "Hostile address (illegal characters): %(member)s" msgstr "Indirizzo con caratteri non permessi: %(member)s" @@ -9928,14 +10125,17 @@ msgstr "Iscritto: %(member)s" # /home/mailman/Mailman/Cgi/admin.py:633 #: bin/add_members:187 +#, fuzzy msgid "Subscribed: %(member)s" msgstr "Iscritto: %(member)s" #: bin/add_members:237 +#, fuzzy msgid "Bad argument to -w/--welcome-msg: %(arg)s" msgstr "Argomento errato per il parametro -w/--welcome-msg: %(arg)s" #: bin/add_members:244 +#, fuzzy msgid "Bad argument to -a/--admin-notify: %(arg)s" msgstr "Argomento errato per il parametro -a/--admin-notify: %(arg)s" @@ -9952,6 +10152,7 @@ msgstr "" #: bin/add_members:261 bin/config_list:110 bin/export.py:271 bin/find_member:97 #: bin/inject:91 bin/list_admins:90 bin/list_members:252 bin/sync_members:222 #: cron/bumpdigests:86 +#, fuzzy msgid "No such list: %(listname)s" msgstr "Non esiste la lista: %(listname)s" @@ -9962,6 +10163,7 @@ msgid "Nothing to do." msgstr "Non ho niente da fare." #: bin/arch:19 +#, fuzzy msgid "" "Rebuild a list's archive.\n" "\n" @@ -10056,6 +10258,7 @@ msgid "listname is required" msgstr "il nome della lista è obbligatorio" #: bin/arch:143 bin/change_pw:107 bin/config_list:257 +#, fuzzy msgid "" "No such list \"%(listname)s\"\n" "%(e)s" @@ -10064,10 +10267,12 @@ msgstr "" "%(e)s" #: bin/arch:168 +#, fuzzy msgid "Cannot open mbox file %(mbox)s: %(msg)s" msgstr "Non riesco ad aprire il file mbox %(mbox)s: %(msg)s" #: bin/b4b5-archfix:19 +#, fuzzy msgid "" "Fix the MM2.1b4 archives.\n" "\n" @@ -10211,6 +10416,7 @@ msgstr "" " Mostra questo messaggio d'aiuto ed esce.\n" #: bin/change_pw:145 +#, fuzzy msgid "Bad arguments: %(strargs)s" msgstr "Argomenti errati: %(strargs)s" @@ -10219,14 +10425,17 @@ msgid "Empty list passwords are not allowed" msgstr "Non sono consentite password nulle per la lista" #: bin/change_pw:181 +#, fuzzy msgid "New %(listname)s password: %(notifypassword)s" msgstr "Nuova password per la lista %(listname)s: %(notifypassword)s" #: bin/change_pw:190 +#, fuzzy msgid "Your new %(listname)s list password" msgstr "La tua nuova password per la lista %(listname)s" #: bin/change_pw:191 +#, fuzzy msgid "" "The site administrator at %(hostname)s has changed the password for your\n" "mailing list %(listname)s. It is now\n" @@ -10254,6 +10463,7 @@ msgstr "" " %(adminurl)s\n" #: bin/check_db:19 +#, fuzzy msgid "" "Check a list's config database file for integrity.\n" "\n" @@ -10333,10 +10543,12 @@ msgid "List:" msgstr "Lista:" #: bin/check_db:148 +#, fuzzy msgid " %(file)s: okay" msgstr " %(file)s: ok" #: bin/check_perms:20 +#, fuzzy msgid "" "Check the permissions for the Mailman installation.\n" "\n" @@ -10356,43 +10568,53 @@ msgstr "" "maggiormente dettagliato.\n" #: bin/check_perms:110 +#, fuzzy msgid " checking gid and mode for %(path)s" msgstr " controllo di gid e modo per %(path)s" #: bin/check_perms:122 +#, fuzzy msgid "%(path)s bad group (has: %(groupname)s, expected %(MAILMAN_GROUP)s)" msgstr "" "gruppo errato per %(path)s (ha %(groupname)s invece di %(MAILMAN_GROUP)s)" #: bin/check_perms:151 +#, fuzzy msgid "directory permissions must be %(octperms)s: %(path)s" msgstr "i permessi sulla directory devono essere %(octperms)s: %(path)s" #: bin/check_perms:160 +#, fuzzy msgid "source perms must be %(octperms)s: %(path)s" msgstr "i permessi sui sorgenti devono essere %(octperms)s: %(path)s" #: bin/check_perms:171 +#, fuzzy msgid "article db files must be %(octperms)s: %(path)s" msgstr "i permessi sul db dei messaggi devono essere %(octperms)s: %(path)s" #: bin/check_perms:183 +#, fuzzy msgid "checking mode for %(prefix)s" msgstr "controllo di modo per %(prefix)s" #: bin/check_perms:193 +#, fuzzy msgid "WARNING: directory does not exist: %(d)s" msgstr "ATTENZIONE: la directory %(d)s non esiste" #: bin/check_perms:197 +#, fuzzy msgid "directory must be at least 02775: %(d)s" msgstr "questa directory deve essere almeno 02775: %(d)s" #: bin/check_perms:209 +#, fuzzy msgid "checking perms on %(private)s" msgstr "controllo permessi su %(private)s" #: bin/check_perms:214 +#, fuzzy msgid "%(private)s must not be other-readable" msgstr "%(private)s non deve poter essere letta da tutti" @@ -10417,6 +10639,7 @@ msgid "mbox file must be at least 0660:" msgstr "il file mbox deve essere almeno 0660:" #: bin/check_perms:263 +#, fuzzy msgid "%(dbdir)s \"other\" perms must be 000" msgstr "%(dbdir)s deve avere 0 nei permessi per i non iscritti del gruppo" @@ -10425,26 +10648,32 @@ msgid "checking cgi-bin permissions" msgstr "controllo dei permessi su cgi-bin" #: bin/check_perms:278 +#, fuzzy msgid " checking set-gid for %(path)s" msgstr " controllo del flag set-gid per %(path)s" #: bin/check_perms:282 +#, fuzzy msgid "%(path)s must be set-gid" msgstr "%(path)s deve essere set-gid" #: bin/check_perms:292 +#, fuzzy msgid "checking set-gid for %(wrapper)s" msgstr "controllo del flag set-gid per %(wrapper)s" #: bin/check_perms:296 +#, fuzzy msgid "%(wrapper)s must be set-gid" msgstr "%(wrapper)s deve essere set-gid" #: bin/check_perms:306 +#, fuzzy msgid "checking permissions on %(pwfile)s" msgstr "controllo dei permessi su %(pwfile)s" #: bin/check_perms:315 +#, fuzzy msgid "%(pwfile)s permissions must be exactly 0640 (got %(octmode)s)" msgstr "" "i permessi su %(pwfile)s devono essere esattamente 0640 (invece di " @@ -10455,10 +10684,12 @@ msgid "checking permissions on list data" msgstr "controllo dei permessi sui dati di lista" #: bin/check_perms:348 +#, fuzzy msgid " checking permissions on: %(path)s" msgstr " controllo dei permessi su: %(path)s" #: bin/check_perms:356 +#, fuzzy msgid "file permissions must be at least 660: %(path)s" msgstr "i permessi di questo file devono essere almeno 660: %(path)s" @@ -10544,6 +10775,7 @@ msgid "Unix-From line changed: %(lineno)d" msgstr "Linea From Unix cambiata: %(lineno)d" #: bin/cleanarch:111 +#, fuzzy msgid "Bad status number: %(arg)s" msgstr "Numero di stato errato: %(arg)s" @@ -10698,10 +10930,12 @@ msgstr " indirizzo originale rimosso:" # /home/mailman/Mailman/Cgi/admin.py:861 #: bin/clone_member:202 +#, fuzzy msgid "Not a valid email address: %(toaddr)s" msgstr "Indirizzo email errato/non valido: %(toaddr)s" #: bin/clone_member:215 +#, fuzzy msgid "" "Error opening list \"%(listname)s\", skipping.\n" "%(e)s" @@ -10810,6 +11044,7 @@ msgstr "" "\n" #: bin/config_list:118 +#, fuzzy msgid "" "# -*- python -*-\n" "# -*- coding: %(charset)s -*-\n" @@ -10831,22 +11066,27 @@ msgid "legal values are:" msgstr "i valori permessi sono:" #: bin/config_list:270 +#, fuzzy msgid "attribute \"%(k)s\" ignored" msgstr "l'attributo \"%(k)s\" è stato ignorato" #: bin/config_list:273 +#, fuzzy msgid "attribute \"%(k)s\" changed" msgstr "l'attributo \"%(k)s\" è stato cambiato" #: bin/config_list:279 +#, fuzzy msgid "Non-standard property restored: %(k)s" msgstr "Parametro non-standard recuperato: %(k)s" #: bin/config_list:288 +#, fuzzy msgid "Invalid value for property: %(k)s" msgstr "Valore non valido per la variabile %(k)s" #: bin/config_list:291 +#, fuzzy msgid "Bad email address for option %(k)s: %(v)s" msgstr "Indirizzo errato per l'opzione %(k)s: %(v)s" @@ -10914,18 +11154,22 @@ msgstr "" " Non scrivere i messaggi di stato.\n" #: bin/discard:94 +#, fuzzy msgid "Ignoring non-held message: %(f)s" msgstr "Ignoro un messaggio non trattenuto: %(f)s" #: bin/discard:100 +#, fuzzy msgid "Ignoring held msg w/bad id: %(f)s" msgstr "Ingnoro un messaggio trattenuto con id non valido: %(f)s" #: bin/discard:112 +#, fuzzy msgid "Discarded held msg #%(id)s for list %(listname)s" msgstr "Scartato il messaggio #%(id)s per la lista %(listname)s" #: bin/dumpdb:19 +#, fuzzy msgid "" "Dump the contents of any Mailman `database' file.\n" "\n" @@ -10998,6 +11242,7 @@ msgid "No filename given." msgstr "Non è stato fornito un nome di file." #: bin/dumpdb:108 +#, fuzzy msgid "Bad arguments: %(pargs)s" msgstr "Argomenti errati: %(pargs)s" @@ -11006,14 +11251,17 @@ msgid "Please specify either -p or -m." msgstr "Per favore specifica -p oppure -m." #: bin/dumpdb:133 +#, fuzzy msgid "[----- start %(typename)s file -----]" msgstr "[----- inizio del file %(typename)s -----]" #: bin/dumpdb:139 +#, fuzzy msgid "[----- end %(typename)s file -----]" msgstr "[----- fine del file %(typename)s -----]" #: bin/dumpdb:142 +#, fuzzy msgid "<----- start object %(cnt)s ----->" msgstr "<----- inizio oggetto %(cnt)s ----->" @@ -11238,6 +11486,7 @@ msgid "Setting web_page_url to: %(web_page_url)s" msgstr "Imposto web_page_url a: %(web_page_url)s" #: bin/fix_url.py:88 +#, fuzzy msgid "Setting host_name to: %(mailhost)s" msgstr "Imposto host_nama a: %(mailhost)s" @@ -11331,6 +11580,7 @@ msgstr "" "Se omesso, verrà usato lo standard input.\n" #: bin/inject:84 +#, fuzzy msgid "Bad queue directory: %(qdir)s" msgstr "Directory di coda errata: %(qdir)s" @@ -11340,6 +11590,7 @@ msgid "A list name is required" msgstr "Il nome della lista è obbligatorio" #: bin/list_admins:20 +#, fuzzy msgid "" "List all the owners of a mailing list.\n" "\n" @@ -11385,6 +11636,7 @@ msgstr "" "proprietari. Puoi mettere sulla linea di comando i nomi di più liste.\n" #: bin/list_admins:97 +#, fuzzy msgid "List: %(listname)s, \tOwners: %(owners)s" msgstr "Lista: %(listname)s, \tProprietari: %(owners)s" @@ -11573,11 +11825,13 @@ msgstr "" "gruppi.\n" #: bin/list_members:198 +#, fuzzy msgid "Bad --nomail option: %(why)s" msgstr "Opzione --nomail errata: %(why)s" # /home/mailman/Mailman/Cgi/admin.py:42 #: bin/list_members:209 +#, fuzzy msgid "Bad --digest option: %(kind)s" msgstr "Opzione --digest errata: %(kind)s" @@ -11591,6 +11845,7 @@ msgid "Could not open file for writing:" msgstr "Non riesce ad aprire il file in scrittura:" #: bin/list_owners:20 +#, fuzzy msgid "" "List the owners of a mailing list, or all mailing lists.\n" "\n" @@ -11643,6 +11898,7 @@ msgid "" msgstr "" #: bin/mailmanctl:20 +#, fuzzy msgid "" "Primary start-up and shutdown script for Mailman's qrunner daemon.\n" "\n" @@ -11819,6 +12075,7 @@ msgstr "" " reopen - Semplicemente farà riaprire tutti i file di log.\n" #: bin/mailmanctl:152 +#, fuzzy msgid "PID unreadable in: %(pidfile)s" msgstr "Non capisco il PID in: %(pidfile)s" @@ -11827,6 +12084,7 @@ msgid "Is qrunner even running?" msgstr "qrunner sta girando?" #: bin/mailmanctl:160 +#, fuzzy msgid "No child with pid: %(pid)s" msgstr "Non ci sono figli con questo pid: %(pid)s" @@ -11854,6 +12112,7 @@ msgstr "" "l'opzione -s.\n" #: bin/mailmanctl:233 +#, fuzzy msgid "" "The master qrunner lock could not be acquired, because it appears as if " "some\n" @@ -11879,10 +12138,12 @@ msgstr "" "Esco." #: bin/mailmanctl:280 cron/mailpasswds:119 +#, fuzzy msgid "Site list is missing: %(sitelistname)s" msgstr "Lista di sito mancante: %(sitelistname)s" #: bin/mailmanctl:305 +#, fuzzy msgid "Run this program as root or as the %(name)s user, or use -u." msgstr "" "Esegui questo programma come root o come l'utente %(name)s, oppure usa -u." @@ -11893,6 +12154,7 @@ msgid "No command given." msgstr "Nessuna motivazione fornita." #: bin/mailmanctl:339 +#, fuzzy msgid "Bad command: %(command)s" msgstr "Comando errato: %(command)s" @@ -11917,6 +12179,7 @@ msgid "Starting Mailman's master qrunner." msgstr "Lancio il master qrunner di Mailman" #: bin/mmsitepass:19 +#, fuzzy msgid "" "Set the site password, prompting from the terminal.\n" "\n" @@ -11972,6 +12235,7 @@ msgid "list creator" msgstr "creatore di liste" #: bin/mmsitepass:86 +#, fuzzy msgid "New %(pwdesc)s password: " msgstr "Nuova password per %(pwdesc)s:" @@ -12227,6 +12491,7 @@ msgstr "" "Nota che i nomi di lista vengono forzati a tutte minuscole.\n" #: bin/newlist:162 +#, fuzzy msgid "Unknown language: %(lang)s" msgstr "Lingua sconosciuta: %(lang)s" @@ -12240,6 +12505,7 @@ msgid "Enter the email of the person running the list: " msgstr "Inserisci l'email della persona che l'amministra: " #: bin/newlist:193 +#, fuzzy msgid "Initial %(listname)s password: " msgstr "Password iniziale per %(listname)s: " @@ -12249,15 +12515,17 @@ msgstr "La password di lista non deve essere vuota" #: bin/newlist:220 msgid "" -" - owner addresses need to be fully-qualified names like \"owner@example.com" -"\", not just \"owner\"." +" - owner addresses need to be fully-qualified names like \"owner@example." +"com\", not just \"owner\"." msgstr "" #: bin/newlist:244 +#, fuzzy msgid "Hit enter to notify %(listname)s owner..." msgstr "Premi invio per notificare il proprietario di %(listname)s..." #: bin/qrunner:20 +#, fuzzy msgid "" "Run one or more qrunners, once or repeatedly.\n" "\n" @@ -12385,6 +12653,7 @@ msgstr "" "per motivi di debug.\n" #: bin/qrunner:179 +#, fuzzy msgid "%(name)s runs the %(runnername)s qrunner" msgstr "%(name)s esegue il qrunner %(runnername)s" @@ -12398,6 +12667,7 @@ msgid "No runner name given." msgstr "Non è stato fornito un nome di qrunner." #: bin/rb-archfix:21 +#, fuzzy msgid "" "Reduce disk space usage for Pipermail archives.\n" "\n" @@ -12540,18 +12810,22 @@ msgstr "" " indir1 ... sono indirizzi addizionali da rimuovere.\n" #: bin/remove_members:156 +#, fuzzy msgid "Could not open file for reading: %(filename)s." msgstr "Non riesco ad aprire in lettura il file: %(filename)s." #: bin/remove_members:163 +#, fuzzy msgid "Error opening list %(listname)s... skipping." msgstr "Errore nell'apertura della lista %(listname)s... la salto." #: bin/remove_members:173 +#, fuzzy msgid "No such member: %(addr)s" msgstr "Nessun iscritto: %(addr)s." #: bin/remove_members:178 +#, fuzzy msgid "User `%(addr)s' removed from list: %(listname)s." msgstr "Utente `%(addr)s' rimosso dalla lista: %(listname)s" @@ -12592,10 +12866,12 @@ msgstr "" " Scrive quello che sta facendo.\n" #: bin/reset_pw.py:77 +#, fuzzy msgid "Changing passwords for list: %(listname)s" msgstr "Cambio le password per la lista %(listname)s" #: bin/reset_pw.py:83 +#, fuzzy msgid "New password for member %(member)40s: %(randompw)s" msgstr "Nuova password per l'iscritto %(member)40s: %(randompw)s" @@ -12641,18 +12917,22 @@ msgstr "" "\n" #: bin/rmlist:73 bin/rmlist:76 +#, fuzzy msgid "Removing %(msg)s" msgstr "Rimozione di %(msg)s" #: bin/rmlist:81 +#, fuzzy msgid "%(listname)s %(msg)s not found as %(filename)s" msgstr "non trovato %(msg)s della lista %(listname)s in %(filename)s" #: bin/rmlist:105 +#, fuzzy msgid "No such list (or list already deleted): %(listname)s" msgstr "Non esiste (o è stata cancellata) la lista: %(listname)s" #: bin/rmlist:108 +#, fuzzy msgid "No such list: %(listname)s. Removing its residual archives." msgstr "Non esiste la lista: %(listname)s. Rimuovo i suoi archivi residui." @@ -12715,6 +12995,7 @@ msgstr "" "Esempio: show_qfiles qfiles/shunt/*.pck\n" #: bin/sync_members:19 +#, fuzzy msgid "" "Synchronize a mailing list's membership with a flat file.\n" "\n" @@ -12844,6 +13125,7 @@ msgstr "" " Obbligatorio. Indica quale lista va sincronizzata.\n" #: bin/sync_members:115 +#, fuzzy msgid "Bad choice: %(yesno)s" msgstr "Scelta errata: %(yesno)s" @@ -12861,6 +13143,7 @@ msgid "No argument to -f given" msgstr "Non è stata specificata l'opzione -f" #: bin/sync_members:172 +#, fuzzy msgid "Illegal option: %(opt)s" msgstr "Opzione non permessa: %(opt)s" @@ -12874,6 +13157,7 @@ msgid "Must have a listname and a filename" msgstr "Devo avere un nomelista e un nomefile" #: bin/sync_members:191 +#, fuzzy msgid "Cannot read address file: %(filename)s: %(msg)s" msgstr "Non posso leggere il file con gli indirizzi: %(filename)s: %(msg)s" @@ -12891,14 +13175,17 @@ msgid "You must fix the preceding invalid addresses first." msgstr "Devi prima sistemare i precedenti indirizzi non validi." #: bin/sync_members:264 +#, fuzzy msgid "Added : %(s)s" msgstr "Aggiunto : %(s)s" #: bin/sync_members:289 +#, fuzzy msgid "Removed: %(s)s" msgstr "Rimosso: %(s)s" #: bin/transcheck:19 +#, fuzzy msgid "" "\n" "Check a given Mailman translation, making sure that variables and\n" @@ -12978,6 +13265,7 @@ msgid "scan the po file comparing msgids with msgstrs" msgstr "esamina il file .po comparando msgid e msgstr" #: bin/unshunt:20 +#, fuzzy msgid "" "Move a message from the shunt queue to the original queue.\n" "\n" @@ -13009,6 +13297,7 @@ msgstr "" "quei messaggi.\n" #: bin/unshunt:85 +#, fuzzy msgid "" "Cannot unshunt message %(filebase)s, skipping:\n" "%(e)s" @@ -13017,6 +13306,7 @@ msgstr "" "%(e)s" #: bin/update:20 +#, fuzzy msgid "" "Perform all necessary upgrades.\n" "\n" @@ -13054,14 +13344,17 @@ msgstr "" "Conosce le specificità di tutte le versione dalla 1.0b4 in avanti (?).\n" #: bin/update:107 +#, fuzzy msgid "Fixing language templates: %(listname)s" msgstr "Correzione dei template di lingua: %(listname)s" #: bin/update:196 bin/update:711 +#, fuzzy msgid "WARNING: could not acquire lock for list: %(listname)s" msgstr "ATTENZIONE: non riesco ad acquisire il lock per la lista: %(listname)s" #: bin/update:215 +#, fuzzy msgid "Resetting %(n)s BYBOUNCEs disabled addrs with no bounce info" msgstr "" "Riabilito %(n)s indirizzi che erano stati disabilitati per errori di " @@ -13080,6 +13373,7 @@ msgstr "" "funziona con b6, quindi la rinomino in %(mbox_dir)s.tmp e procedo." #: bin/update:255 +#, fuzzy msgid "" "\n" "%(listname)s has both public and private mbox archives. Since this list\n" @@ -13128,6 +13422,7 @@ msgid "- updating old private mbox file" msgstr "- aggiornamento del vecchio file mbox privato" #: bin/update:295 +#, fuzzy msgid "" " unknown file in the way, moving\n" " %(o_pri_mbox_file)s\n" @@ -13144,6 +13439,7 @@ msgid "- updating old public mbox file" msgstr "- aggiorno il vecchio file mbox pubblico" #: bin/update:317 +#, fuzzy msgid "" " unknown file in the way, moving\n" " %(o_pub_mbox_file)s\n" @@ -13172,18 +13468,22 @@ msgid "- %(o_tmpl)s doesn't exist, leaving untouched" msgstr "- %(o_tmpl)s non esiste, lascio intoccato" #: bin/update:396 +#, fuzzy msgid "removing directory %(src)s and everything underneath" msgstr "rimuovo la directory %(src)s e tutto quello che contiene" #: bin/update:399 +#, fuzzy msgid "removing %(src)s" msgstr "rimozione di %(src)s" #: bin/update:403 +#, fuzzy msgid "Warning: couldn't remove %(src)s -- %(rest)s" msgstr "Attenzione: non riesco a rimuovere %(src)s -- %(rest)s" #: bin/update:408 +#, fuzzy msgid "couldn't remove old file %(pyc)s -- %(rest)s" msgstr "non riesco a rimuovere il vecchio file %(pyc)s -- %(rest)s" @@ -13192,14 +13492,17 @@ msgid "updating old qfiles" msgstr "aggiornamento dei vecchi qfile" #: bin/update:455 +#, fuzzy msgid "Warning! Not a directory: %(dirpath)s" msgstr "Attenzione! Non è una directory: %(dirpath)s" #: bin/update:530 +#, fuzzy msgid "message is unparsable: %(filebase)s" msgstr "il messaggio è incomprensibile: %(filebase)s" #: bin/update:544 +#, fuzzy msgid "Warning! Deleting empty .pck file: %(pckfile)s" msgstr "Attenzione! Rimozione di un file .pck vuoto: %(pckfile)s" @@ -13213,10 +13516,12 @@ msgid "Updating Mailman 2.1.4 pending.pck database" msgstr "Aggiornamento del vecchio database pending.pck di Mailman 2.1.4" #: bin/update:598 +#, fuzzy msgid "Ignoring bad pended data: %(key)s: %(val)s" msgstr "Ignoro dati pendenti errati: %(key)s: %(val)s" #: bin/update:614 +#, fuzzy msgid "WARNING: Ignoring duplicate pending ID: %(id)s." msgstr "ATTENZIONE: Ignoro ID duplicato: %(id)s." @@ -13243,6 +13548,7 @@ msgid "done" msgstr "fatto" #: bin/update:691 +#, fuzzy msgid "Updating mailing list: %(listname)s" msgstr "Aggiorno la lista: %(listname)s" @@ -13303,6 +13609,7 @@ msgid "No updates are necessary." msgstr "Non sono necessarie modifiche." #: bin/update:796 +#, fuzzy msgid "" "Downgrade detected, from version %(hexlversion)s to version %(hextversion)s\n" "This is probably not safe.\n" @@ -13313,10 +13620,12 @@ msgstr "" "Esco." #: bin/update:801 +#, fuzzy msgid "Upgrading from version %(hexlversion)s to %(hextversion)s" msgstr "Aggiornamento dalla versione %(hexlversion)s alla %(hextversion)s" #: bin/update:810 +#, fuzzy msgid "" "\n" "ERROR:\n" @@ -13594,6 +13903,7 @@ msgstr "" " " #: bin/withlist:175 +#, fuzzy msgid "Unlocking (but not saving) list: %(listname)s" msgstr "Sblocco (ma non salvo) la lista: %(listname)s" @@ -13602,6 +13912,7 @@ msgid "Finalizing" msgstr "Chiusura" #: bin/withlist:188 +#, fuzzy msgid "Loading list %(listname)s" msgstr "Carico la lista %(listname)s" @@ -13614,6 +13925,7 @@ msgid "(unlocked)" msgstr "(sbloccata)" #: bin/withlist:197 +#, fuzzy msgid "Unknown list: %(listname)s" msgstr "Lista ignota: %(listname)s" @@ -13627,19 +13939,23 @@ msgid "--all requires --run" msgstr "--all richiede --run" #: bin/withlist:266 +#, fuzzy msgid "Importing %(module)s..." msgstr "Importo %(module)s..." #: bin/withlist:270 +#, fuzzy msgid "Running %(module)s.%(callable)s()..." msgstr "Eseguo %(module)s.%(callable)s()..." #: bin/withlist:291 +#, fuzzy msgid "The variable `m' is the %(listname)s MailList instance" msgstr "" "La variabile `m' è l'istanza dell'oggetto MailList per la lista %(listname)s" #: cron/bumpdigests:19 +#, fuzzy msgid "" "Increment the digest volume number and reset the digest number to one.\n" "\n" @@ -13668,6 +13984,7 @@ msgstr "" "oppure per tutte se non è stata specificata nessuna lista.\n" #: cron/checkdbs:20 +#, fuzzy msgid "" "Check for pending admin requests and mail the list owners if necessary.\n" "\n" @@ -13697,10 +14014,12 @@ msgstr "" "\n" #: cron/checkdbs:121 +#, fuzzy msgid "%(count)d %(realname)s moderator request(s) waiting" msgstr "ci sono %(count)d richieste in attesa del moderatore di %(realname)s" #: cron/checkdbs:124 +#, fuzzy msgid "%(realname)s moderator request check result" msgstr "risultato del check sulle richieste di moderazione di %(realname)s " @@ -13724,6 +14043,7 @@ msgstr "" "Messaggi pendenti:" #: cron/checkdbs:169 +#, fuzzy msgid "" "From: %(sender)s on %(date)s\n" "Subject: %(subject)s\n" @@ -13755,6 +14075,7 @@ msgid "" msgstr "" #: cron/disabled:20 +#, fuzzy msgid "" "Process disabled members, recommended once per day.\n" "\n" @@ -13878,6 +14199,7 @@ msgstr "" "\n" #: cron/mailpasswds:19 +#, fuzzy msgid "" "Send password reminders for all lists to all users.\n" "\n" @@ -13931,10 +14253,12 @@ msgid "Password // URL" msgstr "Password // URL" #: cron/mailpasswds:222 +#, fuzzy msgid "%(host)s mailing list memberships reminder" msgstr "promemoria per gli iscritti della lista %(host)s" #: cron/nightly_gzip:19 +#, fuzzy msgid "" "Re-generate the Pipermail gzip'd archive flat files.\n" "\n" @@ -14035,8 +14359,8 @@ msgstr "" #~ " applied" #~ msgstr "" #~ "Testo da includere in ogni\n" -#~ " notifica di rifiuto che viene inviata agli iscritti\n" #~ " moderati che avevano provato a scrivere alla lista." diff --git a/messages/ja/LC_MESSAGES/mailman.po b/messages/ja/LC_MESSAGES/mailman.po index 98380d68..ff1f12fa 100644 --- a/messages/ja/LC_MESSAGES/mailman.po +++ b/messages/ja/LC_MESSAGES/mailman.po @@ -66,10 +66,12 @@ msgid "

                    Currently, there are no archives.

                    " msgstr "

                    ¸½ºß¡¢Êݸ½ñ¸Ë¤Ï¤¢¤ê¤Þ¤»¤ó¡£

                    " #: Mailman/Archiver/HyperArch.py:821 +#, fuzzy msgid "Gzip'd Text%(sz)s" msgstr "Gzip°µ½Ì¥Æ¥­¥¹¥È %(sz)s" #: Mailman/Archiver/HyperArch.py:826 +#, fuzzy msgid "Text%(sz)s" msgstr "¥Æ¥­¥¹¥È %(sz)s" @@ -142,18 +144,22 @@ msgid "Third" msgstr "Âè3" #: Mailman/Archiver/HyperArch.py:938 +#, fuzzy msgid "%(ord)s quarter %(year)i" msgstr "%(year)iǯ%(ord)s»ÍȾ´ü" #: Mailman/Archiver/HyperArch.py:945 +#, fuzzy msgid "%(month)s %(year)i" msgstr "%(year)iǯ%(month)s" #: Mailman/Archiver/HyperArch.py:950 +#, fuzzy msgid "The Week Of Monday %(day)i %(month)s %(year)i" msgstr "%(year)iǯ%(month)s%(day)iÆü(·îÍËÆü)¤Î½µ" #: Mailman/Archiver/HyperArch.py:954 +#, fuzzy msgid "%(day)i %(month)s %(year)i" msgstr "%(year)iǯ%(month)s%(day)iÆü" @@ -162,10 +168,12 @@ msgid "Computing threaded index\n" msgstr "¥¹¥ì¥Ã¥É²½º÷°ú¤òºîÀ®Ãæ\n" #: Mailman/Archiver/HyperArch.py:1319 +#, fuzzy msgid "Updating HTML for article %(seq)s" msgstr "µ­»ö %(seq)s ÈÖ¤ÎHTML¤ò¹¹¿·Ãæ" #: Mailman/Archiver/HyperArch.py:1326 +#, fuzzy msgid "article file %(filename)s is missing!" msgstr "µ­»ö¥Õ¥¡¥¤¥ë %(filename)s ¤¬¤¢¤ê¤Þ¤»¤ó." @@ -182,6 +190,7 @@ msgid "Pickling archive state into " msgstr "Êݸ½ñ¸Ë¤Î¾ðÊó¤òpickle²½¤·¤Æ¤¤¤Þ¤¹: " #: Mailman/Archiver/pipermail.py:453 +#, fuzzy msgid "Updating index files for archive [%(archive)s]" msgstr "Êݸ½ñ¸Ë [%(archive)s] ¤Îº÷°ú¥Õ¥¡¥¤¥ë¤ò¹¹¿·¤·¤Æ¤¤¤Þ¤¹" @@ -190,6 +199,7 @@ msgid " Thread" msgstr " ¥¹¥ì¥Ã¥É" #: Mailman/Archiver/pipermail.py:597 +#, fuzzy msgid "#%(counter)05d %(msgid)s" msgstr "#%(counter)05d %(msgid)s" @@ -226,6 +236,7 @@ msgid "disabled address" msgstr "ÇÛÁ÷Ää»ß¤µ¤ì¤¿¥¢¥É¥ì¥¹" #: Mailman/Bouncer.py:304 +#, fuzzy msgid " The last bounce received from you was dated %(date)s" msgstr "ºÇ¸å¤Ë¥¨¥é¡¼¥á¡¼¥ë¤ò¼õ¿®¤·¤¿ÆüÉÕ¤Ï %(date)s ¤Ç¤¹" @@ -253,6 +264,7 @@ msgstr " #: Mailman/Cgi/options.py:101 Mailman/Cgi/private.py:108 #: Mailman/Cgi/rmlist.py:75 Mailman/Cgi/roster.py:59 #: Mailman/Cgi/subscribe.py:67 +#, fuzzy msgid "No such list %(safelistname)s" msgstr "%(safelistname)s¤È¤¤¤¦¥ê¥¹¥È¤Ï¤¢¤ê¤Þ¤»¤ó" @@ -326,6 +338,7 @@ msgstr "" "¤³¤Î±Æ¶Á¤ò¼õ¤±¤ë²ñ°÷¤Ï %(rm)r ¤Ç¤¹¡£" #: Mailman/Cgi/admin.py:268 +#, fuzzy msgid "%(hostname)s mailing lists - Admin Links" msgstr "%(hostname)s ¥á¡¼¥ê¥ó¥°¥ê¥¹¥È - ´ÉÍý¥ê¥ó¥¯" @@ -338,6 +351,7 @@ msgid "Mailman" msgstr "Mailman" #: Mailman/Cgi/admin.py:310 +#, fuzzy msgid "" "

                    There currently are no publicly-advertised %(mailmanlink)s\n" " mailing lists on %(hostname)s." @@ -346,6 +360,7 @@ msgstr "" "¥á¡¼¥ê¥ó¥°¥ê¥¹¥È¤Ï¤¢¤ê¤Þ¤»¤ó¡£" #: Mailman/Cgi/admin.py:316 +#, fuzzy msgid "" "

                    Below is the collection of publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s. Click on a list\n" @@ -360,6 +375,7 @@ msgid "right " msgstr "Àµ¤·¤¤ " #: Mailman/Cgi/admin.py:325 +#, fuzzy msgid "" "To visit the administrators configuration page for an\n" " unadvertised list, open a URL similar to this one, but with a '/' " @@ -403,6 +419,7 @@ msgid "No valid variable name found." msgstr "Í­¸ú¤ÊÊÑ¿ô̾¤¬¸«¤Ä¤«¤ê¤Þ¤»¤ó¡£" #: Mailman/Cgi/admin.py:395 +#, fuzzy msgid "" "%(realname)s Mailing list Configuration Help\n" "
                    %(varname)s Option" @@ -411,6 +428,7 @@ msgstr "" "
                    %(varname)s ¥ª¥×¥·¥ç¥ó" #: Mailman/Cgi/admin.py:402 +#, fuzzy msgid "Mailman %(varname)s List Option Help" msgstr "Mailman %(varname)s ¥ê¥¹¥È¥ª¥×¥·¥ç¥ó¡¦¥Ø¥ë¥×" @@ -429,14 +447,17 @@ msgstr "" " ɬ¤ººÆÆÉ¤ß¹þ¤ß¤ÎÁàºî¤ò¤·¤Æ¤¯¤À¤µ¤¤¡£¤¢¤ë¤¤¤Ï" #: Mailman/Cgi/admin.py:431 +#, fuzzy msgid "return to the %(categoryname)s options page." msgstr "%(categoryname)s ¥ª¥×¥·¥ç¥ó¤Î¥Ú¡¼¥¸¤ËÌá¤ë¡£" #: Mailman/Cgi/admin.py:446 +#, fuzzy msgid "%(realname)s Administration (%(label)s)" msgstr "%(realname)s ´ÉÍý (%(label)s)" #: Mailman/Cgi/admin.py:447 +#, fuzzy msgid "%(realname)s mailing list administration
                    %(label)s Section" msgstr "%(realname)s ¥á¡¼¥ê¥ó¥°¥ê¥¹¥È´ÉÍý
                    %(label)s ¤ÎÉô" @@ -518,6 +539,7 @@ msgid "Value" msgstr "ÃÍ" #: Mailman/Cgi/admin.py:668 +#, fuzzy msgid "" "Badly formed options entry:\n" " %(record)s" @@ -618,10 +640,12 @@ msgid "Move rule down" msgstr "µ¬Â§¤ò²¼¤²¤ë" #: Mailman/Cgi/admin.py:870 +#, fuzzy msgid "
                    (Edit %(varname)s)" msgstr "
                    (%(varname)s¤ÎÊÔ½¸)" #: Mailman/Cgi/admin.py:872 +#, fuzzy msgid "
                    (Details for %(varname)s)" msgstr "
                    (%(varname)s¤Î¾ÜºÙ)" @@ -660,6 +684,7 @@ msgid "(help)" msgstr "(¥Ø¥ë¥×)" #: Mailman/Cgi/admin.py:930 +#, fuzzy msgid "Find member %(link)s:" msgstr "²ñ°÷¸¡º÷ %(link)s:" @@ -672,10 +697,12 @@ msgid "Bad regular expression: " msgstr "Àµµ¬É½¸½¤Ë¸í¤ê¤¬¤¢¤ê¤Þ¤¹: " #: Mailman/Cgi/admin.py:1013 +#, fuzzy msgid "%(allcnt)s members total, %(membercnt)s shown" msgstr "²ñ°÷¹ç·× %(allcnt)s ̾, ¤¦¤Á %(membercnt)s ̾ ¤òɽ¼¨¤·¤Þ¤¹" #: Mailman/Cgi/admin.py:1016 +#, fuzzy msgid "%(allcnt)s members total" msgstr "¹ç·× %(allcnt)s ̾" @@ -853,6 +880,7 @@ msgstr "" "

                    ¾¤Î²ñ°÷¤ò¸«¤ë¤Ë¤Ï¡¢²¼¤Î¥ê¥¹¥È¤ÎŬÅö¤ÊÈϰϤò¥¯¥ê¥Ã¥¯¤·¤Æ¤¯¤À¤µ¤¤:" #: Mailman/Cgi/admin.py:1221 +#, fuzzy msgid "from %(start)s to %(end)s" msgstr "%(start)s ¤«¤é %(end)s ¤Þ¤Ç" @@ -991,6 +1019,7 @@ msgid "Change list ownership passwords" msgstr "¥ê¥¹¥È´ÉÍý¼Ô¥Ñ¥¹¥ï¡¼¥ÉÊѹ¹" #: Mailman/Cgi/admin.py:1364 +#, fuzzy msgid "" "The list administrators are the people who have ultimate control " "over\n" @@ -1103,6 +1132,7 @@ msgstr " #: Mailman/Cgi/admin.py:1529 Mailman/Cgi/admin.py:1703 bin/add_members:175 #: bin/clone_member:136 bin/sync_members:268 +#, fuzzy msgid "Banned address (matched %(pattern)s)" msgstr "Æþ²ñ¶Ø»ß¥¢¥É¥ì¥¹ (%(pattern)s ¤Ë°ìÃ×)" @@ -1159,6 +1189,7 @@ msgid "%(schange_to)s is already a member" msgstr "%(schange_to)s ¤Ï´û¤Ë²ñ°÷¤Ç¤¹" #: Mailman/Cgi/admin.py:1620 +#, fuzzy msgid "%(schange_to)s matches banned pattern %(spat)s" msgstr "%(schange_to)s ¤ÏÆþ²ñ¶Ø»ß¥Ñ¥¿¡¼¥ó %(spat)s ¤Ë¥Þ¥Ã¥Á¤·¤Þ¤¹" @@ -1199,6 +1230,7 @@ msgid "Not subscribed" msgstr "Æþ²ñ¤·¤Æ¤¤¤Þ¤»¤ó" #: Mailman/Cgi/admin.py:1773 +#, fuzzy msgid "Ignoring changes to deleted member: %(user)s" msgstr "ºï½ü¤µ¤ì¤¿²ñ°÷¤ÎÊѹ¹¤ò̵»ë: %(user)s" @@ -1211,10 +1243,12 @@ msgid "Error Unsubscribing:" msgstr "Âà²ñ¼ê³¤­¤Î¥¨¥é¡¼:" #: Mailman/Cgi/admindb.py:235 Mailman/Cgi/admindb.py:248 +#, fuzzy msgid "%(realname)s Administrative Database" msgstr "%(realname)s ´ÉÍý¥Ç¡¼¥¿¥Ù¡¼¥¹" #: Mailman/Cgi/admindb.py:238 +#, fuzzy msgid "%(realname)s Administrative Database Results" msgstr "%(realname)s ´ÉÍý¥Ç¡¼¥¿¥Ù¡¼¥¹¤Î·ë²Ì" @@ -1243,6 +1277,7 @@ msgid "Discard all messages marked Defer" msgstr "±ä´ü¤Ë¥Á¥§¥Ã¥¯¤µ¤ì¤Æ¤¤¤ë¥á¡¼¥ë¤òÁ´ÉôÇË´þ¤·¤Þ¤¹" #: Mailman/Cgi/admindb.py:298 +#, fuzzy msgid "all of %(esender)s's held messages." msgstr "%(esender)s ¤Î¤¹¤Ù¤Æ¤ÎÊÝα¥á¡¼¥ë" @@ -1263,6 +1298,7 @@ msgid "list of available mailing lists." msgstr "¤³¤³¤Ë¤¢¤ë¥á¡¼¥ê¥ó¥°¥ê¥¹¥È¤Î°ìÍ÷¡£" #: Mailman/Cgi/admindb.py:362 +#, fuzzy msgid "You must specify a list name. Here is the %(link)s" msgstr "¥ê¥¹¥È̾¤ò»ØÄꤷ¤Æ¤¯¤À¤µ¤¤¡£¤³¤³¤Ë %(link)s ¤¬¤¢¤ê¤Þ¤¹¡£" @@ -1344,6 +1380,7 @@ msgid "The sender is now a member of this list" msgstr "Á÷¿®¼Ô¤Ï¤³¤Î¥á¡¼¥ê¥ó¥°¥ê¥¹¥È¤ËÆþ²ñ¤·¤Æ¤¤¤Þ¤¹" #: Mailman/Cgi/admindb.py:568 +#, fuzzy msgid "Add %(esender)s to one of these sender filters:" msgstr "%(esender)s¤ò¤É¤ì¤«¤ÎÁ÷¿®¼Ô¥Õ¥£¥ë¥¿¤ËÄɲ乤ë" @@ -1364,6 +1401,7 @@ msgid "Rejects" msgstr "µñÈÝ" #: Mailman/Cgi/admindb.py:584 +#, fuzzy msgid "" "Ban %(esender)s from ever subscribing to this\n" " mailing list" @@ -1378,6 +1416,7 @@ msgid "" msgstr "¥á¡¼¥ëÈÖ¹æ¤ò¥¯¥ê¥Ã¥¯¤·¤Æ¸Ä¡¹¤Î¥á¡¼¥ë¤ò¸«¤ë¤«¡¢" #: Mailman/Cgi/admindb.py:591 +#, fuzzy msgid "view all messages from %(esender)s" msgstr "%(esender)s ¤«¤é¤Î¤¹¤Ù¤Æ¤Î¥á¡¼¥ë¤ò¸«¤ë" @@ -1456,6 +1495,7 @@ msgid " is already a member" msgstr " ¤Ï´û¤Ë²ñ°÷¤Ç¤¹" #: Mailman/Cgi/admindb.py:973 +#, fuzzy msgid "%(addr)s is banned (matched: %(patt)s)" msgstr "%(addr)s ¤ÏÆþ²ñ¶Ø»ß¤Ç¤¹ (%(patt)s ¤Ë°ìÃ×)" @@ -1464,6 +1504,7 @@ msgid "Confirmation string was empty." msgstr "³Îǧʸ»úÎ󤬶õ¤Ç¤¹¡£" #: Mailman/Cgi/confirm.py:108 +#, fuzzy msgid "" "Invalid confirmation string:\n" " %(safecookie)s.\n" @@ -1505,6 +1546,7 @@ msgstr "" "¤³¤Î¿½ÀÁ¤ò¼è¤ê¾Ã¤·¤Þ¤¹¡£" #: Mailman/Cgi/confirm.py:178 +#, fuzzy msgid "System error, bad content: %(content)s" msgstr "¥·¥¹¥Æ¥à¥¨¥é¡¼¡£ÆâÍÆ¤¬²õ¤ì¤Æ¤¤¤Þ¤¹: %(content)s" @@ -1541,6 +1583,7 @@ msgid "Confirm subscription request" msgstr "Æþ²ñ¿½ÀÁ¤ò³Îǧ" #: Mailman/Cgi/confirm.py:259 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " subscription request to the mailing list %(listname)s. Your\n" @@ -1572,6 +1615,7 @@ msgstr "" "¥¯¥ê¥Ã¥¯¤·¤Æ¤¯¤À¤µ¤¤¡£" #: Mailman/Cgi/confirm.py:275 +#, fuzzy msgid "" "Your confirmation is required in order to continue with\n" " the subscription request to the mailing list %(listname)s.\n" @@ -1620,6 +1664,7 @@ msgid "Preferred language:" msgstr "¸À¸ì¤ÎÁªÂò:" #: Mailman/Cgi/confirm.py:317 +#, fuzzy msgid "Subscribe to list %(listname)s" msgstr "%(listname)s ¥ê¥¹¥È¤ËÆþ²ñ" @@ -1636,6 +1681,7 @@ msgid "Awaiting moderator approval" msgstr "»Ê²ñ¼Ô¤Î¾µÇ§¤òÂԤäƤ¤¤Þ¤¹" #: Mailman/Cgi/confirm.py:386 +#, fuzzy msgid "" " You have successfully confirmed your subscription request to " "the\n" @@ -1665,6 +1711,7 @@ msgid "You are already a member of this mailing list!" msgstr "´û¤Ë¤³¤Î¥á¡¼¥ê¥ó¥°¥ê¥¹¥È¤Î²ñ°÷¤Ë¤Ê¤Ã¤Æ¤¤¤Þ¤¹!" #: Mailman/Cgi/confirm.py:400 +#, fuzzy msgid "" "You are currently banned from subscribing to\n" " this list. If you think this restriction is erroneous, please\n" @@ -1688,6 +1735,7 @@ msgid "Subscription request confirmed" msgstr "Æþ²ñ¿½ÀÁ¤ò³Îǧ" #: Mailman/Cgi/confirm.py:418 +#, fuzzy msgid "" " You have successfully confirmed your subscription request for\n" " \"%(addr)s\" to the %(listname)s mailing list. A separate\n" @@ -1713,6 +1761,7 @@ msgid "Unsubscription request confirmed" msgstr "Âà²ñ¿½ÀÁ¤ò³Îǧ¤·¤Þ¤·¤¿" #: Mailman/Cgi/confirm.py:469 +#, fuzzy msgid "" " You have successfully unsubscribed from the %(listname)s " "mailing\n" @@ -1732,6 +1781,7 @@ msgid "Not available" msgstr "̵µ­Æþ" #: Mailman/Cgi/confirm.py:498 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " unsubscription request from the mailing list %(listname)s. " @@ -1771,6 +1821,7 @@ msgid "You have canceled your change of address request." msgstr "¥¢¥É¥ì¥¹Êѹ¹¤Î¿½ÀÁ¤ò¼è¤ê¾Ã¤·¤Þ¤·¤¿¡£" #: Mailman/Cgi/confirm.py:555 +#, fuzzy msgid "" "%(newaddr)s is banned from subscribing to the\n" " %(realname)s list. If you think this restriction is erroneous,\n" @@ -1782,6 +1833,7 @@ msgstr "" "¼¡¤Î¥ê¥¹¥È´ÉÍý¼Ô°¸¤Ë¤´Ï¢Íí¤¯¤À¤µ¤¤: %(owneraddr)s" #: Mailman/Cgi/confirm.py:560 +#, fuzzy msgid "" "%(newaddr)s is already a member of\n" " the %(realname)s list. It is possible that you are attempting\n" @@ -1796,6 +1848,7 @@ msgid "Change of address request confirmed" msgstr "¥¢¥É¥ì¥¹Êѹ¹¿½ÀÁ¤ò³Îǧ¤·¤Þ¤·¤¿" #: Mailman/Cgi/confirm.py:571 +#, fuzzy msgid "" " You have successfully changed your address on the %(listname)s\n" " mailing list from %(oldaddr)s to %(newaddr)s. " @@ -1816,6 +1869,7 @@ msgid "globally" msgstr "Á´Éô" #: Mailman/Cgi/confirm.py:605 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " change of address request for the mailing list %(listname)s. " @@ -1871,6 +1925,7 @@ msgid "Sender discarded message via web." msgstr "Á÷¿®¼Ô¤¬ Web ¤Ç¥á¡¼¥ë¤òÇË´þ¤·¤Þ¤·¤¿¡£" #: Mailman/Cgi/confirm.py:674 +#, fuzzy msgid "" "The held message with the Subject:\n" " header %(subject)s could not be found. The most " @@ -1889,6 +1944,7 @@ msgid "Posted message canceled" msgstr "Åê¹Æ¤¬¼è¤ê¾Ã¤µ¤ì¤Þ¤·¤¿" #: Mailman/Cgi/confirm.py:685 +#, fuzzy msgid "" " You have successfully canceled the posting of your message with\n" " the Subject: header %(subject)s to the mailing list\n" @@ -1908,6 +1964,7 @@ msgid "" msgstr "ÊÝᤵ¤ì¤Æ¤¤¤¿¥á¡¼¥ë¤Ï¡¢¤¹¤Ç¤Ë¥ê¥¹¥È´ÉÍý¼Ô¤¬½èÍý¤·¤Þ¤·¤¿¡£" #: Mailman/Cgi/confirm.py:735 +#, fuzzy msgid "" "Your confirmation is required in order to cancel the\n" " posting of your message to the mailing list %(listname)s:\n" @@ -1952,6 +2009,7 @@ msgid "Membership re-enabled." msgstr "²ñ°÷¸¢¤òÉü³è¤·¤Þ¤·¤¿¡£" #: Mailman/Cgi/confirm.py:798 +#, fuzzy msgid "" " You have successfully re-enabled your membership in the\n" " %(listname)s mailing list. You can now not available" msgstr "¤¢¤ê¤Þ¤»¤ó" #: Mailman/Cgi/confirm.py:846 +#, fuzzy msgid "" "Your membership in the %(realname)s mailing list is\n" " currently disabled due to excessive bounces. Your confirmation is\n" @@ -2043,10 +2103,12 @@ msgid "administrative list overview" msgstr "´ÉÍý¥ê¥¹¥È°ìÍ÷" #: Mailman/Cgi/create.py:115 +#, fuzzy msgid "List name must not include \"@\": %(safelistname)s" msgstr "¥ê¥¹¥È̾¤Ë \"@\" ¤Îʸ»ú¤Ï»È¤¨¤Þ¤»¤ó: %(safelistname)s" #: Mailman/Cgi/create.py:122 +#, fuzzy msgid "List already exists: %(safelistname)s" msgstr "ÅÐÏ¿ºÑ¤ß¥ê¥¹¥È¤Ç¤¹: %(safelistname)s" @@ -2080,18 +2142,22 @@ msgid "You are not authorized to create new mailing lists" msgstr "¿·¤·¤¤¥á¡¼¥ê¥ó¥°¥ê¥¹¥È¤òºîÀ®¤¹¤ë¸¢¸Â¤¬¤¢¤ê¤Þ¤»¤ó" #: Mailman/Cgi/create.py:182 +#, fuzzy msgid "Unknown virtual host: %(safehostname)s" msgstr "²¾ÁÛ¥Û¥¹¥È¤¬ÉÔÌÀ¤Ç¤¹: %(safehostname)s" #: Mailman/Cgi/create.py:218 bin/newlist:219 +#, fuzzy msgid "Bad owner email address: %(s)s" msgstr "´ÉÍý¼Ô¥á¡¼¥ë¥¢¥É¥ì¥¹¤Î¸í¤ê: %(s)s" #: Mailman/Cgi/create.py:223 bin/newlist:182 bin/newlist:223 +#, fuzzy msgid "List already exists: %(listname)s" msgstr "ÅÐÏ¿ºÑ¤ß¥ê¥¹¥È¤Ç¤¹: %(listname)s" #: Mailman/Cgi/create.py:231 bin/newlist:217 +#, fuzzy msgid "Illegal list name: %(s)s" msgstr "»ÈÍѤǤ­¤Ê¤¤¥ê¥¹¥È̾: %(s)s" @@ -2104,6 +2170,7 @@ msgstr "" "¥µ¥¤¥È´ÉÍý¼Ô¤ËÏ¢Íí¤·¤Æ¤¯¤À¤µ¤¤¡£" #: Mailman/Cgi/create.py:273 bin/newlist:265 +#, fuzzy msgid "Your new mailing list: %(listname)s" msgstr "¿·¤·¤¤¥á¡¼¥ê¥ó¥°¥ê¥¹¥È: %(listname)s" @@ -2112,6 +2179,7 @@ msgid "Mailing list creation results" msgstr "¥á¡¼¥ê¥ó¥°¥ê¥¹¥ÈºîÀ®·ë²Ì" #: Mailman/Cgi/create.py:288 +#, fuzzy msgid "" "You have successfully created the mailing list\n" " %(listname)s and notification has been sent to the list owner\n" @@ -2134,6 +2202,7 @@ msgid "Create another list" msgstr "Ê̤Υ᡼¥ê¥ó¥°¥ê¥¹¥È¤òºîÀ®¤¹¤ë" #: Mailman/Cgi/create.py:312 +#, fuzzy msgid "Create a %(hostname)s Mailing List" msgstr "%(hostname)s ¥á¡¼¥ê¥ó¥°¥ê¥¹¥È¤òºîÀ®" @@ -2222,6 +2291,7 @@ msgstr "" "À©¸ÂÉÕ¤­²ñ°÷¤È¤·¤ÆÅÐÏ¿¤µ¤ì¤Þ¤¹¡£" #: Mailman/Cgi/create.py:432 +#, fuzzy msgid "" "Initial list of supported languages.

                    Note that if you do not\n" " select at least one initial language, the list will use the server\n" @@ -2320,6 +2390,7 @@ msgid "List name is required." msgstr "¥ê¥¹¥È¤Î̾Á°¤¬É¬ÍפǤ¹¡£" #: Mailman/Cgi/edithtml.py:154 +#, fuzzy msgid "%(realname)s -- Edit html for %(template_info)s" msgstr "%(realname)s -- %(template_info)s ¤Î HTML ¤òÊÔ½¸" @@ -2328,10 +2399,12 @@ msgid "Edit HTML : Error" msgstr "HTML¤ÎÊÔ½¸ : ¥¨¥é¡¼" #: Mailman/Cgi/edithtml.py:161 +#, fuzzy msgid "%(safetemplatename)s: Invalid template" msgstr "%(safetemplatename)s: ¿÷·¿(¥Æ¥ó¥×¥ì¡¼¥È)¤¬Ìµ¸ú¤Ç¤¹" #: Mailman/Cgi/edithtml.py:166 Mailman/Cgi/edithtml.py:167 +#, fuzzy msgid "%(realname)s -- HTML Page Editing" msgstr "%(realname)s -- HTML ¥Ú¡¼¥¸¤ÎÊÔ½¸" @@ -2394,10 +2467,12 @@ msgid "HTML successfully updated." msgstr "HTML ¤ÎÊѹ¹¤¬´°Î»¤·¤Þ¤·¤¿¡£" #: Mailman/Cgi/listinfo.py:89 +#, fuzzy msgid "%(hostname)s Mailing Lists" msgstr "%(hostname)s ¥á¡¼¥ê¥ó¥°¥ê¥¹¥È" #: Mailman/Cgi/listinfo.py:127 +#, fuzzy msgid "" "

                    There currently are no publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s." @@ -2406,6 +2481,7 @@ msgstr "" "¥á¡¼¥ê¥ó¥°¥ê¥¹¥È¤Ï¤¢¤ê¤Þ¤»¤ó¡£" #: Mailman/Cgi/listinfo.py:131 +#, fuzzy msgid "" "

                    Below is a listing of all the public mailing lists on\n" " %(hostname)s. Click on a list name to get more information " @@ -2423,6 +2499,7 @@ msgid "right" msgstr "Àµ¤·¤¤" #: Mailman/Cgi/listinfo.py:140 +#, fuzzy msgid "" " To visit the general information page for an unadvertised list,\n" " open a URL similar to this one, but with a '/' and the %(adj)s\n" @@ -2470,6 +2547,7 @@ msgid "CGI script error" msgstr "CGI ¥¹¥¯¥ê¥×¥È¥¨¥é¡¼" #: Mailman/Cgi/options.py:71 +#, fuzzy msgid "Invalid request method: %(method)s" msgstr "HTTP¥ê¥¯¥¨¥¹¥È¤Î¥á¥½¥Ã¥É¤¬ÉÔÀµ¤Ç¤¹: %(method)s" @@ -2483,6 +2561,7 @@ msgstr " #: Mailman/Cgi/options.py:183 Mailman/Cgi/options.py:240 #: Mailman/Cgi/options.py:264 +#, fuzzy msgid "No such member: %(safeuser)s." msgstr "¤½¤Î¤è¤¦¤Ê²ñ°÷¤Ï¤¤¤Þ¤»¤ó: %(safeuser)s." @@ -2531,6 +2610,7 @@ msgid "Note: " msgstr "Ãí°Õ:" #: Mailman/Cgi/options.py:378 +#, fuzzy msgid "List subscriptions for %(safeuser)s on %(hostname)s" msgstr "%(hostname)s ¤Ç¤Î %(safeuser)s ¤µ¤ó¤Î¥ê¥¹¥È²ñ°÷ÅÐÏ¿¾õ¶·" @@ -2559,6 +2639,7 @@ msgid "You are already using that email address" msgstr "´û¤Ë¤½¤Î¥á¡¼¥ë¥¢¥É¥ì¥¹¤ÇÆþ²ñ¤·¤Æ¤¤¤Þ¤¹¡£" #: Mailman/Cgi/options.py:459 +#, fuzzy msgid "" "The new address you requested %(newaddr)s is already a member of the\n" "%(listname)s mailing list, however you have also requested a global change " @@ -2572,6 +2653,7 @@ msgstr "" "Êѹ¹¤·¤Þ¤¹¡£" #: Mailman/Cgi/options.py:468 +#, fuzzy msgid "The new address is already a member: %(newaddr)s" msgstr "¿·¤·¤¤¥¢¥É¥ì¥¹¤Ï´û¤ËÆþ²ñºÑ¤ß¤Ç¤¹: %(newaddr)s" @@ -2580,6 +2662,7 @@ msgid "Addresses may not be blank" msgstr "¥¢¥É¥ì¥¹¤òµ­Æþ¤·¤Æ¤¯¤À¤µ¤¤" #: Mailman/Cgi/options.py:488 +#, fuzzy msgid "A confirmation message has been sent to %(newaddr)s. " msgstr "%(newaddr)s °¸¤Ë³Îǧ¥á¡¼¥ë¤òÁ÷¿®¤·¤Þ¤·¤¿¡£" @@ -2592,10 +2675,12 @@ msgid "Illegal email address provided" msgstr "´í¸±¤Ê¥á¡¼¥ë¥¢¥É¥ì¥¹¤¬ÆþÎϤµ¤ì¤Þ¤·¤¿" #: Mailman/Cgi/options.py:501 +#, fuzzy msgid "%(newaddr)s is already a member of the list." msgstr "%(newaddr)s ¤Ï´û¤ËÆþ²ñºÑ¤ß¤Ç¤¹¡£" #: Mailman/Cgi/options.py:504 +#, fuzzy msgid "" "%(newaddr)s is banned from this list. If you\n" " think this restriction is erroneous, please contact\n" @@ -2667,6 +2752,7 @@ msgstr "" "¿½ÀÁ¤Ë´Ø¤¹¤ë·èÄê¤Î¸å¡¢·ë²Ì¤ò¥á¡¼¥ë¤ÇÄÌÃΤ·¤Þ¤¹¡£" #: Mailman/Cgi/options.py:623 +#, fuzzy msgid "" "You have been successfully unsubscribed from the\n" " mailing list %(fqdn_listname)s. If you were receiving digest\n" @@ -2756,6 +2842,7 @@ msgid "day" msgstr "Æü" #: Mailman/Cgi/options.py:900 +#, fuzzy msgid "%(days)d %(units)s" msgstr "%(days)d %(units)s" @@ -2768,6 +2855,7 @@ msgid "No topics defined" msgstr "ÏÃÂ꤬ÄêµÁ¤µ¤ì¤Æ¤¤¤Þ¤»¤ó" #: Mailman/Cgi/options.py:940 +#, fuzzy msgid "" "\n" "You are subscribed to this list with the case-preserved address\n" @@ -2778,6 +2866,7 @@ msgstr "" "¤³¤Î̾Á°¤ÎÂçʸ»ú¤È¾®Ê¸»ú¤Î¶èÊ̤ÏÊݸ¤µ¤ì¤Þ¤¹¡£" #: Mailman/Cgi/options.py:956 +#, fuzzy msgid "%(realname)s list: member options login page" msgstr "%(realname)s ¥ê¥¹¥È: ²ñ°÷¥ª¥×¥·¥ç¥ó¥í¥°¥¤¥ó¥Ú¡¼¥¸" @@ -2786,10 +2875,12 @@ msgid "email address and " msgstr "¥á¡¼¥ë¥¢¥É¥ì¥¹¤È" #: Mailman/Cgi/options.py:960 +#, fuzzy msgid "%(realname)s list: member options for user %(safeuser)s" msgstr "%(realname)s¥ê¥¹¥È: %(safeuser)s ²ñ°÷¥ª¥×¥·¥ç¥ó" #: Mailman/Cgi/options.py:986 +#, fuzzy msgid "" "In order to change your membership option, you must\n" " first log in by giving your %(extra)smembership password in the section\n" @@ -2862,6 +2953,7 @@ msgid "" msgstr "<¤¢¤ê¤Þ¤»¤ó>" #: Mailman/Cgi/options.py:1140 +#, fuzzy msgid "Requested topic is not valid: %(topicname)s" msgstr "Í׵ᤵ¤ì¤¿ÏÃÂê¤Ï̵¸ú¤Ç¤¹: %(topicname)s" @@ -2890,6 +2982,7 @@ msgid "Private archive - \"./\" and \"../\" not allowed in URL." msgstr "¸ÂÄê¸ø³«Êݸ½ñ¸Ë - \"./\" ¤È \"../\" ¤ò URL ¤ËÆþ¤ì¤ë¤³¤È¤Ï¤Ç¤­¤Þ¤»¤ó¡£" #: Mailman/Cgi/private.py:109 +#, fuzzy msgid "Private Archive Error - %(msg)s" msgstr "¸ÂÄê¸ø³«Êݸ½ñ¸Ë¥¨¥é¡¼ - %(msg)s" @@ -2910,6 +3003,7 @@ msgid "Private archive file not found" msgstr "¸ÂÄê¸ø³«Êݸ¥Õ¥¡¥¤¥ë¤¬¸«¤Ä¤«¤ê¤Þ¤»¤ó¡£" #: Mailman/Cgi/rmlist.py:76 +#, fuzzy msgid "No such list %(safelistname)s" msgstr "%(safelistname)s ¤È¤¤¤¦¥ê¥¹¥È¤Ï¤¢¤ê¤Þ¤»¤ó" @@ -2926,12 +3020,14 @@ msgid "Mailing list deletion results" msgstr "¥á¡¼¥ê¥ó¥°¥ê¥¹¥Èºï½ü¤Î·ë²Ì" #: Mailman/Cgi/rmlist.py:187 +#, fuzzy msgid "" "You have successfully deleted the mailing list\n" " %(listname)s." msgstr "¥á¡¼¥ê¥ó¥°¥ê¥¹¥È %(listname)s ¤Îºï½ü¤¬´°Î»¤·¤Þ¤·¤¿¡£" #: Mailman/Cgi/rmlist.py:191 +#, fuzzy msgid "" "There were some problems deleting the mailing list\n" " %(listname)s. Contact your site administrator at " @@ -2942,10 +3038,12 @@ msgstr "" "%(sitelist)s ¤Î¥µ¥¤¥È´ÉÍý¼Ô¤ËÏ¢Íí¤·¤Æ¤¯¤À¤µ¤¤¡£" #: Mailman/Cgi/rmlist.py:208 +#, fuzzy msgid "Permanently remove mailing list %(realname)s" msgstr "´°Á´¤Ë¥á¡¼¥ê¥ó¥°¥ê¥¹¥È %(realname)s ¤òºï½ü" #: Mailman/Cgi/rmlist.py:209 +#, fuzzy msgid "Permanently remove mailing list %(realname)s" msgstr "´°Á´¤Ë¥á¡¼¥ê¥ó¥°¥ê¥¹¥È %(realname)s ¤òºï½ü" @@ -3007,6 +3105,7 @@ msgid "Invalid options to CGI script" msgstr "CGI¥¹¥¯¥ê¥×¥È¤Î¥ª¥×¥·¥ç¥ó¤¬Ìµ¸ú¤Ç¤¹" #: Mailman/Cgi/roster.py:118 +#, fuzzy msgid "%(realname)s roster authentication failed." msgstr "%(realname)s ²ñ°÷̾Êí ǧ¾Ú¤Ë¼ºÇÔ¡£" @@ -3074,6 +3173,7 @@ msgstr "" "³Îǧ¤¬É¬ÍפʤȤ­¤Ï¡¢ÁàºîÊýË¡¤ò½ñ¤¤¤¿³Îǧ¥á¡¼¥ë¤ò¤ªÁ÷¤ê¤·¤Þ¤¹¡£" #: Mailman/Cgi/subscribe.py:262 +#, fuzzy msgid "" "The email address you supplied is banned from this\n" " mailing list. If you think this restriction is erroneous, please\n" @@ -3099,6 +3199,7 @@ msgstr "" "Æþ²ñ¿½ÀÁ¤Ï¼õ¤±ÉÕ¤±¤é¤ì¤Þ¤»¤ó¤Ç¤·¤¿." #: Mailman/Cgi/subscribe.py:278 +#, fuzzy msgid "" "Confirmation from your email address is required, to prevent anyone from\n" "subscribing you without permission. Instructions are being sent to you at\n" @@ -3111,6 +3212,7 @@ msgstr "" "Ëܿͤˤè¤ëÆþ²ñ¿½ÀÁ¤Î³Îǧ¤¬¤Ê¤±¤ì¤ÐÆþ²ñ¤Ç¤­¤Þ¤»¤ó¤Î¤Ç¤´Ãí°Õ¤¯¤À¤µ¤¤¡£" #: Mailman/Cgi/subscribe.py:290 +#, fuzzy msgid "" "Your subscription request was deferred because %(x)s. Your request has " "been\n" @@ -3131,6 +3233,7 @@ msgid "Mailman privacy alert" msgstr "Mailman¤«¤é¤Î¥×¥é¥¤¥Ð¥·¡¼·Ù¹ð" #: Mailman/Cgi/subscribe.py:316 +#, fuzzy msgid "" "An attempt was made to subscribe your address to the mailing list\n" "%(listaddr)s. You are already subscribed to this mailing list.\n" @@ -3170,6 +3273,7 @@ msgid "This list only supports digest delivery." msgstr "¤Þ¤È¤áÆÉ¤ßÀìÍѥꥹ¥È¤Ç¤¹¡£" #: Mailman/Cgi/subscribe.py:344 +#, fuzzy msgid "You have been successfully subscribed to the %(realname)s mailing list." msgstr "%(realname)s ¤Ø¤ÎÆþ²ñ¼ê³¤­¤¬´°Î»¤·¤Þ¤·¤¿¡£" @@ -3193,6 +3297,7 @@ msgid "Usage:" msgstr "»ÈÍÑË¡:" #: Mailman/Commands/cmd_confirm.py:50 +#, fuzzy msgid "" "Invalid confirmation string. Note that confirmation strings expire\n" "approximately %(days)s days after the initial request. They also expire if\n" @@ -3217,6 +3322,7 @@ msgstr "" "Âà²ñ¤·¤¿¤«¡¢¥á¡¼¥ë¥¢¥É¥ì¥¹¤òÊѹ¹¤·¤Þ¤»¤ó¤Ç¤·¤¿¤«?" #: Mailman/Commands/cmd_confirm.py:69 +#, fuzzy msgid "" "You are currently banned from subscribing to this list. If you think this\n" "restriction is erroneous, please contact the list owners at\n" @@ -3296,26 +3402,32 @@ msgid "n/a" msgstr "̵¤·" #: Mailman/Commands/cmd_info.py:44 +#, fuzzy msgid "List name: %(listname)s" msgstr "¥ê¥¹¥È̾: %(listname)s" #: Mailman/Commands/cmd_info.py:45 +#, fuzzy msgid "Description: %(description)s" msgstr "ÀâÌÀ: %(description)s" #: Mailman/Commands/cmd_info.py:46 +#, fuzzy msgid "Postings to: %(postaddr)s" msgstr "Åê¹Æ°¸Àè: %(postaddr)s" #: Mailman/Commands/cmd_info.py:47 +#, fuzzy msgid "List Helpbot: %(requestaddr)s" msgstr "¥ê¥¹¥È¥Ø¥ë¥×: %(requestaddr)s" #: Mailman/Commands/cmd_info.py:48 +#, fuzzy msgid "List Owners: %(owneraddr)s" msgstr "¥ê¥¹¥È´ÉÍý¼Ô: %(owneraddr)s" #: Mailman/Commands/cmd_info.py:49 +#, fuzzy msgid "More information: %(listurl)s" msgstr "¥Û¡¼¥à¥Ú¡¼¥¸: %(listurl)s" @@ -3338,18 +3450,22 @@ msgstr "" " ¤³¤Î GNU Mailman ¥µ¡¼¥Ð¤Î¸ø³«¥á¡¼¥ê¥ó¥°¥ê¥¹¥È¤Î°ìÍ÷¤ò¸«¤ë¡£\n" #: Mailman/Commands/cmd_lists.py:44 +#, fuzzy msgid "Public mailing lists at %(hostname)s:" msgstr "%(hostname)s ¤Î¸ø³«¥á¡¼¥ê¥ó¥°¥ê¥¹¥È" #: Mailman/Commands/cmd_lists.py:66 +#, fuzzy msgid "%(i)3d. List name: %(realname)s" msgstr "%(i)3d. ¥ê¥¹¥È̾: %(realname)s" #: Mailman/Commands/cmd_lists.py:67 +#, fuzzy msgid " Description: %(description)s" msgstr " ÀâÌÀ: %(description)s" #: Mailman/Commands/cmd_lists.py:68 +#, fuzzy msgid " Requests to: %(requestaddr)s" msgstr " ¿½ÀÁ°¸Àè: %(requestaddr)s" @@ -3382,12 +3498,14 @@ msgstr "" " ¤µ¤ì¤¿¥¢¥É¥ì¥¹¤ËÁ÷¤é¤ì¤Þ¤¹¤Î¤Ç¤´Ãí°Õ¤¯¤À¤µ¤¤¡£\n" #: Mailman/Commands/cmd_password.py:51 Mailman/Commands/cmd_password.py:66 +#, fuzzy msgid "Your password is: %(password)s" msgstr "¤¢¤Ê¤¿¤Î¥Ñ¥¹¥ï¡¼¥É¤Ï: %(password)s" #: Mailman/Commands/cmd_password.py:57 Mailman/Commands/cmd_password.py:72 #: Mailman/Commands/cmd_password.py:95 Mailman/Commands/cmd_password.py:121 #: Mailman/Commands/cmd_set.py:149 Mailman/Commands/cmd_set.py:219 +#, fuzzy msgid "You are not a member of the %(listname)s mailing list" msgstr "¤¢¤Ê¤¿¤Ï%(listname)s ¥á¡¼¥ê¥ó¥°¥ê¥¹¥È¤Î²ñ°÷¤Ç¤Ï¤¢¤ê¤Þ¤»¤ó" @@ -3568,6 +3686,7 @@ msgstr "" " Á÷¤é¤ì¤ë¥Ñ¥¹¥ï¡¼¥ÉÈ÷˺ÄÌÃΤÎÁ÷¿®¤òÄä»ß¤·¤Þ¤¹¡£\n" #: Mailman/Commands/cmd_set.py:122 +#, fuzzy msgid "Bad set command: %(subcmd)s" msgstr "set ¥³¥Þ¥ó¥É¤Ë´Ö°ã¤¤¤¬¤¢¤ê¤Þ¤¹: %(subcmd)s" @@ -3588,6 +3707,7 @@ msgid "on" msgstr "on" #: Mailman/Commands/cmd_set.py:154 +#, fuzzy msgid " ack %(onoff)s" msgstr " ack (³Îǧ) %(onoff)s" @@ -3625,22 +3745,27 @@ msgid "due to bounces" msgstr "¥¨¥é¡¼¥á¡¼¥ë¤Ë¤è¤ê" #: Mailman/Commands/cmd_set.py:186 +#, fuzzy msgid " %(status)s (%(how)s on %(date)s)" msgstr " %(status)s (%(date)s ¤Ë %(how)s)" #: Mailman/Commands/cmd_set.py:192 +#, fuzzy msgid " myposts %(onoff)s" msgstr " myposts (¼«Ê¬¤ÎÅê¹Æ) %(onoff)s" #: Mailman/Commands/cmd_set.py:195 +#, fuzzy msgid " hide %(onoff)s" msgstr " hide (±£¤ì) %(onoff)s" #: Mailman/Commands/cmd_set.py:199 +#, fuzzy msgid " duplicates %(onoff)s" msgstr " duplicates (½ÅÊ£) %(onoff)s" #: Mailman/Commands/cmd_set.py:203 +#, fuzzy msgid " reminders %(onoff)s" msgstr " reminders (È÷˺ÄÌÃÎ) %(onoff)s" @@ -3649,6 +3774,7 @@ msgid "You did not give the correct password" msgstr "¥Ñ¥¹¥ï¡¼¥É¤¬Àµ¤·¤¯¤¢¤ê¤Þ¤»¤ó" #: Mailman/Commands/cmd_set.py:236 Mailman/Commands/cmd_set.py:283 +#, fuzzy msgid "Bad argument: %(arg)s" msgstr "°ú¿ô¤Ë´Ö°ã¤¤¤¬¤¢¤ê¤Þ¤¹: %(arg)s" @@ -3721,6 +3847,7 @@ msgstr "" " (¥«¥Ã¥³¤ä°úÍÑÉä¤ÏÉÕ¤±¤Ê¤¤¤³¤È!)\n" #: Mailman/Commands/cmd_subscribe.py:62 +#, fuzzy msgid "Bad digest specifier: %(arg)s" msgstr "¡Ö¤Þ¤È¤áÆÉ¤ß¡×¤Î»Ø¼¨¤¬°ã¤¤¤Þ¤¹: %(arg)s" @@ -3729,6 +3856,7 @@ msgid "No valid address found to subscribe" msgstr "ÅÐÏ¿¤Î¤¿¤á¤ÎÀµ¤·¤¤¥¢¥É¥ì¥¹¤¬¸«¤Ä¤«¤ê¤Þ¤»¤ó¤Ç¤·¤¿" #: Mailman/Commands/cmd_subscribe.py:113 +#, fuzzy msgid "" "The email address you supplied is banned from this mailing list.\n" "If you think this restriction is erroneous, please contact the list\n" @@ -3767,6 +3895,7 @@ msgid "This list only supports digest subscriptions!" msgstr "¤Þ¤È¤áÆÉ¤ßÀìÍѤΥ᡼¥ê¥ó¥°¥ê¥¹¥È¤Ç¤¹!" #: Mailman/Commands/cmd_subscribe.py:146 +#, fuzzy msgid "" "Your subscription request has been forwarded to the list administrator\n" "at %(listowner)s for review." @@ -3801,6 +3930,7 @@ msgstr "" " (¤³¤Î¾ì¹ç¤Î¥á¡¼¥ë¥¢¥É¥ì¥¹¤Ë¤Ï <> ¤ä \" ¤òÉÕ¤±¤Ê¤¤¤Ç¤¯¤À¤µ¤¤!)\n" #: Mailman/Commands/cmd_unsubscribe.py:62 +#, fuzzy msgid "%(address)s is not a member of the %(listname)s mailing list" msgstr "%(address)s ¤Ï %(listname)s ¤Î¥ê¥¹¥È²ñ°÷¤Ç¤Ï¤¢¤ê¤Þ¤»¤ó" @@ -4049,6 +4179,7 @@ msgid "Chinese (Taiwan)" msgstr "Ãæ¹ñ¸ì(ÂæÏÑ)" #: Mailman/Deliverer.py:53 +#, fuzzy msgid "" "Note: Since this is a list of mailing lists, administrative\n" "notices like the password reminder will be sent to\n" @@ -4062,14 +4193,17 @@ msgid " (Digest mode)" msgstr " (¤Þ¤È¤áÆÉ¤ß¥â¡¼¥É)" #: Mailman/Deliverer.py:79 +#, fuzzy msgid "Welcome to the \"%(realname)s\" mailing list%(digmode)s" msgstr "\"%(realname)s\" ¥á¡¼¥ê¥ó¥°¥ê¥¹¥È %(digmode)s ¤Ø¤è¤¦¤³¤½" #: Mailman/Deliverer.py:89 +#, fuzzy msgid "You have been unsubscribed from the %(realname)s mailing list" msgstr "%(realname)s ¤«¤é¤ÎÂà²ñ¼ê³¤­¤¬´°Î»¤·¤Þ¤·¤¿" #: Mailman/Deliverer.py:116 +#, fuzzy msgid "%(listfullname)s mailing list reminder" msgstr "%(listfullname)s ¥á¡¼¥ê¥ó¥°¥ê¥¹¥ÈÈ÷˺ÄÌÃÎ" @@ -4082,6 +4216,7 @@ msgid "Hostile subscription attempt detected" msgstr "´í¸±¤ÊÆþ²ñ¤Î»î¤ß¤ò¸¡½Ð" #: Mailman/Deliverer.py:169 +#, fuzzy msgid "" "%(address)s was invited to a different mailing\n" "list, but in a deliberate malicious attempt they tried to confirm the\n" @@ -4093,6 +4228,7 @@ msgstr "" "²¿¤â¤¹¤ëɬÍפϤ¢¤ê¤Þ¤»¤ó¤¬¡¢Ç°¤Î¤¿¤á¤Ë¤ªÃΤ餻¤·¤Þ¤¹¡£" #: Mailman/Deliverer.py:188 +#, fuzzy msgid "" "You invited %(address)s to your list, but in a\n" "deliberate malicious attempt, they tried to confirm the invitation to a\n" @@ -4105,6 +4241,7 @@ msgstr "" "ǰ¤Î¤¿¤á¤ªÃΤ餻¤·¤Þ¤¹¡£" #: Mailman/Deliverer.py:221 +#, fuzzy msgid "%(listname)s mailing list probe message" msgstr "%(listname)s ¥á¡¼¥ê¥ó¥°¥ê¥¹¥È ¥¢¥É¥ì¥¹ÃµÃΥ᡼¥ë" @@ -4298,8 +4435,8 @@ msgid "" " membership.\n" "\n" "

                    You can control both the\n" -" number\n" +" number\n" " of reminders the member will receive and the\n" " No those messages too will get discarded. You may " "want\n" " to set up an\n" -" autoresponse\n" +" autoresponse\n" " message for email to the -owner and -admin address." msgstr "" "Mailman ¤ÎÇÛÁ÷¥¨¥é¡¼¸¡½Ð¥·¥¹¥Æ¥à¤ÏÈó¾ï¤Ë¤·¤Ã¤«¤ê¤·¤Æ¤¤¤Þ¤¹¤¬¡¢\n" @@ -4557,6 +4694,7 @@ msgstr "" "¤·¤«¤·²ñ°÷¤Ø¤ÎÄÌÃΤϳ¤±¤é¤ì¤Þ¤¹¡£" #: Mailman/Gui/Bounce.py:194 +#, fuzzy msgid "" "Bad value for %(property)s: %(val)s" @@ -4693,8 +4831,8 @@ msgid "" "Use this option to remove each message attachment that does\n" " not have a matching content type. Requirements and formats " "are\n" -" exactly like filter_mime_types.\n" "\n" "

                    Note: if you add entries to this list but don't add\n" @@ -4800,6 +4938,7 @@ msgstr "" "ÊÝ´É¥ª¥×¥·¥ç¥ó¤Ï¡¢¥µ¥¤¥È´ÉÍý¼Ô¤¬Í­¸ú¤Ë¤·¤¿¾ì¹ç¤Ë¤Î¤ßÁªÂò¤Ç¤­¤Þ¤¹¡£" #: Mailman/Gui/ContentFilter.py:171 +#, fuzzy msgid "Bad MIME type ignored: %(spectype)s" msgstr "MIME¥¿¥¤¥×¤Ë¸í¤ê¤¬¤¢¤ê¤Þ¤¹¡£Ìµ»ë¤·¤Þ¤¹: %(spectype)s" @@ -4901,6 +5040,7 @@ msgid "" msgstr "Á÷¿®¤Ç¤­¤ë¥á¡¼¥ë¤¬¤¢¤ì¤Ð¡¢º£¤¹¤°¤Ë¡Ö¤Þ¤È¤áÆÉ¤ß¡×¤òȯÁ÷¤·¤Þ¤¹¤«?" #: Mailman/Gui/Digest.py:145 +#, fuzzy msgid "" "The next digest will be sent as volume\n" " %(volume)s, number %(number)s" @@ -4915,14 +5055,17 @@ msgid "There was no digest to send." msgstr "ȯÁ÷¤¹¤Ù¤­¡¢¤Þ¤È¤áÆÉ¤ß¤¬¤¢¤ê¤Þ¤»¤ó¡£" #: Mailman/Gui/GUIBase.py:173 +#, fuzzy msgid "Invalid value for variable: %(property)s" msgstr "ÊÑ¿ôÃͤ¬Ìµ¸ú: %(property)s" #: Mailman/Gui/GUIBase.py:178 +#, fuzzy msgid "Bad email address for option %(property)s: %(error)s" msgstr "%(property)s ¥ª¥×¥·¥ç¥ó¤Î¥á¡¼¥ë¥¢¥É¥ì¥¹¤¬´Ö°ã¤Ã¤Æ¤¤¤Þ¤¹: %(error)s" #: Mailman/Gui/GUIBase.py:204 +#, fuzzy msgid "" "The following illegal substitution variables were\n" " found in the %(property)s string:\n" @@ -4936,6 +5079,7 @@ msgstr "" "

                    ÌäÂê¤ò½¤Àµ¤¹¤ë¤Þ¤Ç¤Ï¡¢¥á¡¼¥ê¥ó¥°¥ê¥¹¥È¤ÏÀµ¾ï¤Ëµ¡Ç½¤·¤Þ¤»¤ó¡£" #: Mailman/Gui/GUIBase.py:218 +#, fuzzy msgid "" "Your %(property)s string appeared to\n" " have some correctable problems in its new value.\n" @@ -5032,8 +5176,8 @@ msgid "" "

                    In order to split the list ownership duties into\n" " administrators and moderators, you must\n" " set a separate moderator password,\n" -" and also provide the email\n" +" and also provide the email\n" " addresses of the list moderators. Note that the field you\n" " are changing here specifies the list administrators." msgstr "" @@ -5410,13 +5554,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5474,8 +5618,8 @@ msgstr " msgid "" "This is the address set in the Reply-To: header\n" " when the reply_goes_to_list\n" +" href=\"?VARHELP=general/" +"reply_goes_to_list\">reply_goes_to_list\n" " option is set to Explicit address.\n" "\n" "

                    There are many reasons not to introduce or override the\n" @@ -5483,13 +5627,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5510,8 +5654,8 @@ msgid "" "

                    Note that if the original message contains a\n" " Reply-To: header, it will not be changed." msgstr "" -"¤³¤Î¥¢¥É¥ì¥¹¤¬¡¢reply_goes_to_list\n" +"¤³¤Î¥¢¥É¥ì¥¹¤¬¡¢reply_goes_to_list\n" " ¤òÊ̤Υ¢¥É¥ì¥¹¤ËÀßÄꤷ¤¿¤È¤­ Reply-To: ¥Ø¥Ã¥À¤Ë»È¤ï¤ì¤Þ" "¤¹¡£\n" "

                    Reply-To: ¤Î¤è¤¦¤Ê¥Ø¥Ã¥À¤ò\n" @@ -5586,8 +5730,8 @@ msgid "" " member list addresses, but rather to the owner of those member\n" " lists. In that case, the value of this setting is appended to\n" " the member's account name for such notices. `-owner' is the\n" -" typical choice. This setting has no effect when \"umbrella_list" -"\"\n" +" typical choice. This setting has no effect when " +"\"umbrella_list\"\n" " is \"No\"." msgstr "" "\"umbrella_list\" ¤òÀßÄꤹ¤ë¤È¡¢¤³¤Î¥á¡¼¥ê¥ó¥°¥ê¥¹¥È¤Ï\n" @@ -6256,8 +6400,8 @@ msgstr "" "To:¥Ø¥Ã¥À¤Î¥¢¥É¥ì¥¹¤¬¥ê¥¹¥È¤Î¥¢¥É¥ì¥¹¤Ç¤Ï¤Ê¤¯¡¢\n" "³Æ²ñ°÷¤Î¥¢¥É¥ì¥¹¤Ë¤Ê¤ê¤Þ¤¹¡£\n" "\n" -"

                    ¸Ä¿ÍÊÌÇÛÁ÷¤Ç¤Ï¡¢¼ã´³¤ÎÃÖ´¹ÊÑ¿ô¤ò\n" +"

                    ¸Ä¿ÍÊÌÇÛÁ÷¤Ç¤Ï¡¢¼ã´³¤ÎÃÖ´¹ÊÑ¿ô¤ò\n" "¥Ø¥Ã¥À¤È¥Õ¥Ã¥¿¤Ë\n" "Æþ¤ì¤é¤ì¤ë¤è¤¦¤Ë¤Ê¤ê¤Þ¤¹¡£\n" "\n" @@ -6514,6 +6658,7 @@ msgstr "" " (¤¢¤ë¤¤¤Ï°­°Õ¤¢¤ë) ¹Ô°Ù¤òËɤ°¤³¤È¤¬¤Ç¤­¤Þ¤¹¡£" #: Mailman/Gui/Privacy.py:104 +#, fuzzy msgid "" "This section allows you to configure subscription and\n" " membership exposure policy. You can also control whether this\n" @@ -6695,8 +6840,8 @@ msgid "" " either individually or as a group. Any\n" " posting from a non-member who is not explicitly accepted,\n" " rejected, or discarded, will have their posting filtered by the\n" -" general\n" +" general\n" " non-member rules.\n" "\n" "

                    In the text boxes below, add one address per line; start the\n" @@ -6747,6 +6892,7 @@ msgid "By default, should new list member postings be moderated?" msgstr "¿·¤·¤¯ÅÐÏ¿¤¹¤ë²ñ°÷¤Î¥Ç¥Õ¥©¥ë¥È¤òÀ©¸ÂÉÕ¤­²ñ°÷¤Ë¤·¤Þ¤¹¤«?" #: Mailman/Gui/Privacy.py:218 +#, fuzzy msgid "" "Each list member has a moderation flag which says\n" " whether messages from the list member can be posted directly " @@ -6795,8 +6941,8 @@ msgid "" "If a member posts this many times, within a period of time\n" " the member is automatically moderated. Use 0 to disable. " "See\n" -" member_verbosity_interval for details on the time " "period.\n" "\n" @@ -6813,8 +6959,8 @@ msgstr "" "°ìÄê»þ´ÖÆâ¤Ë°ì²ñ°÷¤ÎÅê¹Æ¿ô¤¬¤³¤Î¿ô¤Ë㤹¤ë¤È¡¢¤½¤Î²ñ°÷¤ò¼«Æ°Åª¤ËÀ©¸Â\n" " (¥â¥Ç¥ì¡¼¥È)¤·¤Þ¤¹¡£¤³¤Îµ¡Ç½¤ò̵¸ú¤Ë¤¹¤ë¤Ë¤Ï0¤ò»ØÄꤷ¤Þ¤¹¡£\n" " ¤³¤Î¡Ö°ìÄê»þ´Ö¡×¤Ë¤Ä¤¤¤Æ¤Ï\n" -" member_verbosity_interval¤ò»²¾È¤·¤Æ¤¯¤À¤µ¤¤¡£\n" "\n" "

                    ¤³¤Îµ¡Ç½¤Ïñ°ì¤Þ¤¿¤ÏÊ£¿ô¤Î¥ê¥¹¥È¤ËÅÐÏ¿¤·¤Æ¥Ü¥Ã¥È¤ò\n" @@ -6901,6 +7047,7 @@ msgstr "" ">µñÈÝÄÌÃΤ˴ޤá¤ëʸ¡£" #: Mailman/Gui/Privacy.py:290 +#, fuzzy msgid "" "Action to take when anyone posts to the\n" " list from a domain with a DMARC Reject%(quarantine)s Policy." @@ -7038,6 +7185,7 @@ msgstr "" " ¥ì¥Ý¡¼¥È¤ò¥É¥á¥¤¥ó½êÍ­¼Ô¤ØÁ÷¤ë¤Î¤òÈò¤±¤ë¤³¤È¤Ç¤¹¡£" #: Mailman/Gui/Privacy.py:353 +#, fuzzy msgid "" "Text to include in any\n" " The body of the message can also be optionally scanned for\n" " Subject: and Keywords: headers, as\n" " specified by the topics_bodylines_limit\n" +" href=\"?VARHELP=topics/" +"topics_bodylines_limit\">topics_bodylines_limit\n" " configuration variable." msgstr "" "ÏÃÂê¥Õ¥£¥ë¥¿¤Ï¡¢°Ê²¼¤ËÄêµÁ¤¹¤ë\n" @@ -7689,6 +7839,7 @@ msgstr "" "ÉÔ´°Á´¤ÊÏÃÂê¤Ï̵»ë¤µ¤ì¤Þ¤¹¡£" #: Mailman/Gui/Topics.py:135 +#, fuzzy msgid "" "The topic pattern '%(safepattern)s' is not a\n" " legal regular expression. It will be discarded." @@ -7894,6 +8045,7 @@ msgid "%(listinfo_link)s list run by %(owner_link)s" msgstr "%(listinfo_link)s ¥ê¥¹¥È´ÉÍý¿Í %(owner_link)s" #: Mailman/HTMLFormatter.py:57 +#, fuzzy msgid "%(realname)s administrative interface" msgstr "%(realname)s ´ÉÍýÍÑ¥Ú¡¼¥¸" @@ -7902,6 +8054,7 @@ msgid " (requires authorization)" msgstr " (¥Ñ¥¹¥ï¡¼¥É¤¬É¬ÍפǤ¹)" #: Mailman/HTMLFormatter.py:61 +#, fuzzy msgid "Overview of all %(hostname)s mailing lists" msgstr "%(hostname)s ¤ÎÁ´¥á¡¼¥ê¥ó¥°¥ê¥¹¥È°ìÍ÷" @@ -7922,6 +8075,7 @@ msgid "; it was disabled by the list administrator" msgstr "; ¥ê¥¹¥È´ÉÍý¼Ô¤¬ÇÛÁ÷¤òÄä»ß¤·¤Þ¤·¤¿" #: Mailman/HTMLFormatter.py:146 +#, fuzzy msgid "" "; it was disabled due to excessive bounces. The\n" " last bounce was received on %(date)s" @@ -7934,6 +8088,7 @@ msgid "; it was disabled for unknown reasons" msgstr "; ÇÛÁ÷¤¬Ää»ß¤µ¤ì¤Æ¤¤¤Þ¤¹¡£Íýͳ¤ÏÉÔÌÀ¤Ç¤¹¡£" #: Mailman/HTMLFormatter.py:151 +#, fuzzy msgid "Note: your list delivery is currently disabled%(reason)s." msgstr "Ãí°Õ: ¥á¡¼¥ë¤ÎÇÛÁ÷¤Ï°ì»þŪ¤ËÄä»ß¤µ¤ì¤Æ¤¤¤Þ¤¹ %(reason)s¡£" @@ -7946,6 +8101,7 @@ msgid "the list administrator" msgstr "¥ê¥¹¥È´ÉÍý¼Ô" #: Mailman/HTMLFormatter.py:157 +#, fuzzy msgid "" "

                    %(note)s\n" "\n" @@ -7964,6 +8120,7 @@ msgstr "" "%(mailto)s ¤ËÏ¢Íí¤·¤Æ¤¯¤À¤µ¤¤¡£" #: Mailman/HTMLFormatter.py:169 +#, fuzzy msgid "" "

                    We have received some recent bounces from your\n" " address. Your current bounce score is %(score)s out of " @@ -7981,6 +8138,7 @@ msgstr "" " ÌäÂ꤬²ò·è¤¹¤ì¤Ð¤¹¤°¥¨¥é¡¼ÅÀ¤Ï¥ê¥»¥Ã¥È¤µ¤ì¤Þ¤¹¡£" #: Mailman/HTMLFormatter.py:181 +#, fuzzy msgid "" "(Note - you are subscribing to a list of mailing lists, so the %(type)s " "notice will be sent to the admin address for your membership, %(addr)s.)

                    " @@ -8024,6 +8182,7 @@ msgstr "" "»Ê²ñ¼Ô¤Î·èÄê¤Ï¥á¡¼¥ë¤Ç¤ªÃΤ餻¤·¤Þ¤¹¡£" #: Mailman/HTMLFormatter.py:208 +#, fuzzy msgid "" "This is %(also)sa private list, which means that the\n" " list of members is not available to non-members." @@ -8032,6 +8191,7 @@ msgstr "" "Èó²ñ°÷¤ËÂФ·¤Æ¤Ï²ñ°÷̾Êí¤ò¸ø³«¤·¤Æ¤¤¤Þ¤»¤ó¡£" #: Mailman/HTMLFormatter.py:211 +#, fuzzy msgid "" "This is %(also)sa hidden list, which means that the\n" " list of members is available only to the list administrator." @@ -8040,6 +8200,7 @@ msgstr "" "¥ê¥¹¥È´ÉÍý¼Ô°Ê³°¤Ï²ñ°÷̾Êí¤ò±ÜÍ÷¤Ç¤­¤Þ¤»¤ó¡£" #: Mailman/HTMLFormatter.py:214 +#, fuzzy msgid "" "This is %(also)sa public list, which means that the\n" " list of members list is available to everyone." @@ -8055,6 +8216,7 @@ msgstr "" " (¤½¤ÎÂå¤ï¤êÌÂÏǥ᡼¥ëÁ÷¿®¼Ô¤Ëʬ¤«¤ê¤Ë¤¯¤¤¤è¤¦¤Ë¥¢¥É¥ì¥¹¤ò½ñ¤­´¹¤¨¤Þ¤¹)¡£" #: Mailman/HTMLFormatter.py:222 +#, fuzzy msgid "" "

                    (Note that this is an umbrella list, intended to\n" " have only other mailing lists as members. Among other things,\n" @@ -8070,6 +8232,7 @@ msgid "either " msgstr "¤«..." #: Mailman/HTMLFormatter.py:256 +#, fuzzy msgid "" "To unsubscribe from %(realname)s, get a password reminder,\n" " or change your subscription options %(either)senter your " @@ -8100,12 +8263,14 @@ msgid "" msgstr "̤µ­Æþ¤Î¾ì¹ç¡¢¼¡¤Î²èÌ̤ǥ᡼¥ë¥¢¥É¥ì¥¹¤òÆþÎϤ·¤Þ¤¹¡£" #: Mailman/HTMLFormatter.py:277 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " members.)" msgstr "(%(which)s ¤Ï¥á¡¼¥ê¥ó¥°¥ê¥¹¥È²ñ°÷¤À¤±¤¬ÍøÍѤǤ­¤Þ¤¹¡£)" #: Mailman/HTMLFormatter.py:281 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " administrator.)" @@ -8164,6 +8329,7 @@ msgid "The current archive" msgstr "¸½ºß¤ÎÊݸ½ñ¸Ë" #: Mailman/Handlers/Acknowledge.py:59 +#, fuzzy msgid "%(realname)s post acknowledgement" msgstr "%(realname)s Åê¹Æ³ÎǧÄÌÃÎ" @@ -8180,6 +8346,7 @@ msgstr "" "°ÂÁ´¤Ë¼è¤ê½Ð¤¹¤³¤È¤¬¤Ç¤­¤Þ¤»¤ó¡£\n" #: Mailman/Handlers/CalcRecips.py:79 +#, fuzzy msgid "" "Your urgent message to the %(realname)s mailing list was not authorized for\n" "delivery. The original message as received by Mailman is attached.\n" @@ -8188,6 +8355,7 @@ msgstr "" "ǧ¾Ú¤Ë¼ºÇÔ¤·¤Þ¤·¤¿¡£ÅºÉդΥ᡼¥ë¤Ï, Mailman¤¬¼õ¤±¼è¤Ã¤¿¸µ¤Î¥á¡¼¥ë¤Ç¤¹¡£\n" #: Mailman/Handlers/CookHeaders.py:180 +#, fuzzy msgid "%(realname)s via %(lrn)s" msgstr "%(realname)s (%(lrn)s ·Ðͳ)" @@ -8256,6 +8424,7 @@ msgid "Message may contain administrivia" msgstr "´ÉÍý¥³¥Þ¥ó¥É¤¬´Þ¤Þ¤ì¤Æ¤¤¤Þ¤¹" #: Mailman/Handlers/Hold.py:84 +#, fuzzy msgid "" "Please do *not* post administrative requests to the mailing\n" "list. If you wish to subscribe, visit %(listurl)s or send a message with " @@ -8295,10 +8464,12 @@ msgid "Posting to a moderated newsgroup" msgstr "¥â¥Ç¥ì¡¼¥Æ¥Ã¥É¡¦¥Ë¥å¡¼¥¹¥°¥ë¡¼¥×¤Ø¤ÎÅê¹Æ" #: Mailman/Handlers/Hold.py:252 +#, fuzzy msgid "Your message to %(listname)s awaits moderator approval" msgstr "%(listname)s ¤ËÅê¹Æ¤µ¤ì¤¿¥á¡¼¥ë¤Ï¡¢»Ê²ñ¼Ô¤Î¾µÇ§ÂÔ¤Á¤Ç¤¹" #: Mailman/Handlers/Hold.py:271 +#, fuzzy msgid "%(listname)s post from %(sender)s requires approval" msgstr "%(listname)s ¤Ø¤Î %(sender)s ¤ÎÅê¹Æ¤Ï¾µÇ§¤¬É¬ÍפǤ¹" @@ -8339,6 +8510,7 @@ msgid "After content filtering, the message was empty" msgstr "źÉÕ¥Õ¥¡¥¤¥ë¤ò½üµî¤·¤¿·ë²Ì¡¢¥á¡¼¥ë¤¬¶õ¤Ë¤Ê¤ê¤Þ¤·¤¿" #: Mailman/Handlers/MimeDel.py:269 +#, fuzzy msgid "" "The attached message matched the %(listname)s mailing list's content " "filtering\n" @@ -8356,6 +8528,7 @@ msgid "Content filtered message notification" msgstr "źÉÕ¥Õ¥¡¥¤¥ëºï½üÄÌÃÎ" #: Mailman/Handlers/Moderate.py:145 +#, fuzzy msgid "" "Your message has been rejected, probably because you are not subscribed to " "the\n" @@ -8379,6 +8552,7 @@ msgid "The attached message has been automatically discarded." msgstr "źÉÕ¥á¥Ã¥»¡¼¥¸¤Ï¼«Æ°Åª¤ËÇË´þ¤µ¤ì¤Þ¤·¤¿¡£" #: Mailman/Handlers/Replybot.py:75 +#, fuzzy msgid "Auto-response for your message to the \"%(realname)s\" mailing list" msgstr "\"%(realname)s\" ¥á¡¼¥ê¥ó¥°¥ê¥¹¥È°¸¤ËÁ÷¤é¤ì¤¿¥á¡¼¥ë¤Ø¤Î¼«Æ°±þÅú" @@ -8387,6 +8561,7 @@ msgid "The Mailman Replybot" msgstr "Mailman ¼«Æ°±þÅú¥·¥¹¥Æ¥à" #: Mailman/Handlers/Scrubber.py:208 +#, fuzzy msgid "" "An embedded and charset-unspecified text was scrubbed...\n" "Name: %(filename)s\n" @@ -8401,6 +8576,7 @@ msgid "HTML attachment scrubbed and removed" msgstr "HTML¤ÎźÉÕ¥Õ¥¡¥¤¥ë¤Ï, ¼è¤ê½ü¤«¤ì¤Æ¤¤¤Þ¤¹" #: Mailman/Handlers/Scrubber.py:234 Mailman/Handlers/Scrubber.py:259 +#, fuzzy msgid "" "An HTML attachment was scrubbed...\n" "URL: %(url)s\n" @@ -8421,6 +8597,7 @@ msgid "unknown sender" msgstr "Á÷¿®¼ÔÉÔÌÀ" #: Mailman/Handlers/Scrubber.py:276 +#, fuzzy msgid "" "An embedded message was scrubbed...\n" "From: %(who)s\n" @@ -8437,6 +8614,7 @@ msgstr "" "URL: %(url)s\n" #: Mailman/Handlers/Scrubber.py:308 +#, fuzzy msgid "" "A non-text attachment was scrubbed...\n" "Name: %(filename)s\n" @@ -8453,6 +8631,7 @@ msgstr "" "URL: %(url)s\n" #: Mailman/Handlers/Scrubber.py:347 +#, fuzzy msgid "Skipped content of type %(partctype)s\n" msgstr "źÉÕ¥Õ¥¡¥¤¥ë¤Î¥¿¥¤¥× %(partctype)s ¤ò̵»ë¤·¤Þ¤¹\n" @@ -8461,10 +8640,12 @@ msgid "-------------- next part --------------\n" msgstr "-------------- next part --------------\n" #: Mailman/Handlers/SpamDetect.py:64 +#, fuzzy msgid "Header matched regexp: %(pattern)s" msgstr "¥á¡¼¥ë¥Ø¥Ã¥À¡¼¤¬¼¡¤ÎÀµµ¬É½¸½¤Ë¹çÃפ·¤Þ¤·¤¿: %(pattern)s" #: Mailman/Handlers/SpamDetect.py:127 +#, fuzzy msgid "" "You are not allowed to post to this mailing list From: a domain which\n" "publishes a DMARC policy of reject or quarantine, and your message has been\n" @@ -8482,6 +8663,7 @@ msgid "Message rejected by filter rule match" msgstr "¥Õ¥£¥ë¥¿µ¬Â§¤Ë¹çÃפ·¤¿¥á¡¼¥ë¤òµñÈݤ·¤Þ¤·¤¿" #: Mailman/Handlers/ToDigest.py:173 +#, fuzzy msgid "%(realname)s Digest, Vol %(volume)d, Issue %(issue)d" msgstr "%(realname)s ¤Þ¤È¤áÆÉ¤ß, %(volume)d ´¬, %(issue)d ¹æ" @@ -8518,6 +8700,7 @@ msgid "End of " msgstr "°Ê¾å: " #: Mailman/ListAdmin.py:309 +#, fuzzy msgid "Posting of your message titled \"%(subject)s\"" msgstr "·ï̾ %(subject)s ¤ÇÅê¹Æ¤µ¤ì¤¿¥á¡¼¥ë" @@ -8530,6 +8713,7 @@ msgid "Forward of moderated message" msgstr "ÊÝᤵ¤ì¤¿¥á¡¼¥ë¤ÎžÁ÷" #: Mailman/ListAdmin.py:405 +#, fuzzy msgid "New subscription request to list %(realname)s from %(addr)s" msgstr "%(realname)s ¥ê¥¹¥È¤Ø %(addr)s ¤«¤é¿·µ¬Æþ²ñ¿½¤·¹þ¤ß" @@ -8542,6 +8726,7 @@ msgid "via admin approval" msgstr "´ÉÍý¼Ô¤Î¾µÇ§¤Ë¤è¤ê" #: Mailman/ListAdmin.py:466 +#, fuzzy msgid "New unsubscription request from %(realname)s by %(addr)s" msgstr "%(realname)s ¥ê¥¹¥È¤Ø %(addr)s ¤«¤é¿·µ¬Âà²ñ¿½¤·¹þ¤ß" @@ -8554,10 +8739,12 @@ msgid "Original Message" msgstr "¸µ¤Î¥á¡¼¥ë" #: Mailman/ListAdmin.py:526 +#, fuzzy msgid "Request to mailing list %(realname)s rejected" msgstr "%(realname)s ¥á¡¼¥ê¥ó¥°¥ê¥¹¥È¤Ø¤Î¿½ÀÁ¤ÏµÑ²¼¤µ¤ì¤Þ¤·¤¿" #: Mailman/MTA/Manual.py:66 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been created via the through-the-web\n" "interface. In order to complete the activation of this mailing list, the\n" @@ -8584,14 +8771,17 @@ msgstr "" "¤Þ¤¿¡¢`newaliases' ¥³¥Þ¥ó¥É¤Î¼Â¹Ô¤¬É¬ÍפǤ·¤ç¤¦¡£\n" #: Mailman/MTA/Manual.py:82 +#, fuzzy msgid "## %(listname)s mailing list" msgstr "## %(listname)s mailing list" #: Mailman/MTA/Manual.py:99 +#, fuzzy msgid "Mailing list creation request for list %(listname)s" msgstr "%(listname)s ¥á¡¼¥ê¥ó¥°¥ê¥¹¥ÈºîÀ®¤Î¿½ÀÁ" #: Mailman/MTA/Manual.py:113 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been removed via the through-the-web\n" "interface. In order to complete the de-activation of this mailing list, " @@ -8609,6 +8799,7 @@ msgstr "" "°Ê²¼¤Ï, /etc/aliases ¤«¤éºï½ü¤¹¤ë¹Ô¤Ç¤¹:\n" #: Mailman/MTA/Manual.py:123 +#, fuzzy msgid "" "\n" "To finish removing your mailing list, you must edit your /etc/aliases (or\n" @@ -8625,14 +8816,17 @@ msgstr "" "## %(listname)s mailing list" #: Mailman/MTA/Manual.py:142 +#, fuzzy msgid "Mailing list removal request for list %(listname)s" msgstr "%(listname)s ¥á¡¼¥ê¥ó¥°¥ê¥¹¥Èºï½ü¤Î¿½ÀÁ" #: Mailman/MTA/Postfix.py:442 +#, fuzzy msgid "checking permissions on %(file)s" msgstr " %(file)s ¤Î¥Ñ¡¼¥ß¥Ã¥·¥ç¥ó¤ò¥Á¥§¥Ã¥¯Ãæ" #: Mailman/MTA/Postfix.py:452 +#, fuzzy msgid "%(file)s permissions must be 0664 (got %(octmode)s)" msgstr "" "%(file)s ¤Î¥Ñ¡¼¥ß¥Ã¥·¥ç¥ó¤Ï 0664 ¤Ç¤Ê¤±¤ì¤Ð¤¤¤±¤Þ¤»¤ó (%(octmode)s ¤Ë¤Ê¤Ã¤Æ¤¤" @@ -8648,36 +8842,44 @@ msgid "(fixing)" msgstr "(½¤ÀµÃæ)" #: Mailman/MTA/Postfix.py:470 +#, fuzzy msgid "checking ownership of %(dbfile)s" msgstr "%(dbfile)s ¤Î½êÍ­¼Ô¤òÄ´¤Ù¤Æ¤¤¤Þ¤¹" #: Mailman/MTA/Postfix.py:478 +#, fuzzy msgid "%(dbfile)s owned by %(owner)s (must be owned by %(user)s" msgstr "%(dbfile)s ¤Î½êÍ­¼Ô¤Ï %(owner)s ¤Ç¤¹¡£(%(user)s ¤Ç¤Ê¤±¤ì¤Ð¤¤¤±¤Þ¤»¤ó)" #: Mailman/MTA/Postfix.py:491 +#, fuzzy msgid "%(dbfile)s permissions must be 0664 (got %(octmode)s)" msgstr "" "%(dbfile)s ¤Î¥Ñ¡¼¥ß¥Ã¥·¥ç¥ó¤Ï 0664 ¤Ç¤Ê¤±¤ì¤Ð¤¤¤±¤Þ¤»¤ó (%(octmode)s ¤Ë¤Ê¤Ã¤Æ" "¤¤¤Þ¤¹)" #: Mailman/MailList.py:219 +#, fuzzy msgid "Your confirmation is required to join the %(listname)s mailing list" msgstr "%(listname)s ¥á¡¼¥ê¥ó¥°¥ê¥¹¥È¤ËÆþ²ñ¤¹¤ë¤Ë¤Ï¡¢¤¢¤Ê¤¿¤Î³Îǧ¤¬É¬ÍפǤ¹" #: Mailman/MailList.py:230 +#, fuzzy msgid "Your confirmation is required to leave the %(listname)s mailing list" msgstr "%(listname)s ¥á¡¼¥ê¥ó¥°¥ê¥¹¥È¤«¤éÂà²ñ¤¹¤ë¤Ë¤Ï¡¢¤¢¤Ê¤¿¤Î³Îǧ¤¬É¬ÍפǤ¹" #: Mailman/MailList.py:999 Mailman/MailList.py:1492 +#, fuzzy msgid " from %(remote)s" msgstr " %(remote)s ¤«¤é" #: Mailman/MailList.py:1043 +#, fuzzy msgid "subscriptions to %(realname)s require moderator approval" msgstr "%(realname)s ¤Ø¤ÎÆþ²ñ¤Ë¤Ï»Ê²ñ¼Ô¤Î¾µÇ§¤¬É¬ÍפǤ¹" #: Mailman/MailList.py:1125 bin/add_members:299 +#, fuzzy msgid "%(realname)s subscription notification" msgstr "%(realname)s Æþ²ñÄÌÃÎ" @@ -8686,10 +8888,12 @@ msgid "unsubscriptions require moderator approval" msgstr "Âà²ñ¤Ë¤Ï»Ê²ñ¼Ô¤Î¾µÇ§¤¬É¬ÍפǤ¹" #: Mailman/MailList.py:1166 +#, fuzzy msgid "%(realname)s unsubscribe notification" msgstr "%(realname)s Âà²ñÄÌÃÎ" #: Mailman/MailList.py:1328 +#, fuzzy msgid "%(realname)s address change notification" msgstr "%(realname)s ¥¢¥É¥ì¥¹Êѹ¹ÄÌÃÎ" @@ -8702,6 +8906,7 @@ msgid "via web confirmation" msgstr "Web¤Ç¤Î³Îǧ¤Ë¤è¤ê" #: Mailman/MailList.py:1396 +#, fuzzy msgid "subscriptions to %(name)s require administrator approval" msgstr "%(name)s ¤Ø¤ÎÆþ²ñ¤Ë¤Ï´ÉÍý¼Ô¤Î¾µÇ§¤¬É¬ÍפǤ¹" @@ -8718,6 +8923,7 @@ msgid "Last autoresponse notification for today" msgstr "º£ÆüºÇ¸å¤ËÁ÷¤é¤ì¤¿¼«Æ°±þÅú¤ÎÄÌÃÎ" #: Mailman/Queue/BounceRunner.py:360 +#, fuzzy msgid "" "The attached message was received as a bounce, but either the bounce format\n" "was not recognized, or no member addresses could be extracted from it. " @@ -8807,6 +9013,7 @@ msgid "Original message suppressed by Mailman site configuration\n" msgstr "¸µ¤Î¥á¡¼¥ë¤Ï Mailman ¥µ¥¤¥È¤ÎÀßÄê¤Ë¤è¤êºï½ü¤µ¤ì¤Þ¤·¤¿\n" #: Mailman/htmlformat.py:675 +#, fuzzy msgid "Delivered by Mailman
                    version %(version)s" msgstr "Mailman %(version)s ¤Ë¤è¤ëÁ÷¿®" @@ -8895,6 +9102,7 @@ msgid "Server Local Time" msgstr "¥µ¡¼¥Ð¡¼¤Î¸½ÃÏ»þ¹ï" #: Mailman/i18n.py:180 +#, fuzzy msgid "" "%(wday)s %(mon)s %(day)2i %(hh)02i:%(mm)02i:%(ss)02i %(tzname)s %(year)04i" msgstr "" @@ -9022,6 +9230,7 @@ msgstr "" "ξÊý»ØÄꤹ¤ë¾ì¹ç¡¢`-' ¤¬»È¤¨¤ë¤Î¤Ï¤É¤Á¤é¤«1¤Ä¤Ç¤¹¡£\n" #: bin/add_members:162 bin/add_members:172 +#, fuzzy msgid "Already a member: %(member)s" msgstr "Æþ²ñºÑ¤ß: %(member)s" @@ -9030,26 +9239,32 @@ msgid "Bad/Invalid email address: blank line" msgstr "¸í¤ê¤Þ¤¿¤Ï̵¸ú¤Ê¥á¡¼¥ë¥¢¥É¥ì¥¹: ¶õÇò¹Ô" #: bin/add_members:180 +#, fuzzy msgid "Bad/Invalid email address: %(member)s" msgstr "¸í¤ê¤Þ¤¿¤Ï̵¸ú¤Ê¥á¡¼¥ë¥¢¥É¥ì¥¹: %(member)s" #: bin/add_members:182 +#, fuzzy msgid "Hostile address (illegal characters): %(member)s" msgstr "´í¸±¤Ê¥¢¥É¥ì¥¹ (µ¬Â§³°¤Îʸ»ú): %(member)s" #: bin/add_members:185 +#, fuzzy msgid "Invited: %(member)s" msgstr "¾·ÂÔ¤·¤Þ¤·¤¿: %(member)s" #: bin/add_members:187 +#, fuzzy msgid "Subscribed: %(member)s" msgstr "Æþ²ñ¤·¤Þ¤·¤¿: %(member)s" #: bin/add_members:237 +#, fuzzy msgid "Bad argument to -w/--welcome-msg: %(arg)s" msgstr "-w/--welcome-msg ¤Ø¤Î°ú¿ô¤¬°ã¤¤¤Þ¤¹: %(arg)s" #: bin/add_members:244 +#, fuzzy msgid "Bad argument to -a/--admin-notify: %(arg)s" msgstr "-a/--admin-notify ¤Ø¤Î°ú¿ô¤¬°ã¤¤¤Þ¤¹: %(arg)s" @@ -9064,6 +9279,7 @@ msgstr "invite-msg-file #: bin/add_members:261 bin/config_list:110 bin/export.py:271 bin/find_member:97 #: bin/inject:91 bin/list_admins:90 bin/list_members:252 bin/sync_members:222 #: cron/bumpdigests:86 +#, fuzzy msgid "No such list: %(listname)s" msgstr "%(listname)s ¤È¤¤¤¦¥ê¥¹¥È¤Ï¤¢¤ê¤Þ¤»¤ó" @@ -9074,6 +9290,7 @@ msgid "Nothing to do." msgstr "¤Ê¤Ë¤â¤¹¤ë¤³¤È¤Ï¤¢¤ê¤Þ¤»¤ó¡£" #: bin/arch:19 +#, fuzzy msgid "" "Rebuild a list's archive.\n" "\n" @@ -9164,16 +9381,19 @@ msgid "listname is required" msgstr "¥ê¥¹¥È̾¤¬É¬ÍפǤ¹¡£" #: bin/arch:143 bin/change_pw:107 bin/config_list:257 +#, fuzzy msgid "" "No such list \"%(listname)s\"\n" "%(e)s" msgstr "%(listname)s ¤È¤¤¤¦¥ê¥¹¥È¤Ï¤¢¤ê¤Þ¤»¤ó: %(e)s" #: bin/arch:168 +#, fuzzy msgid "Cannot open mbox file %(mbox)s: %(msg)s" msgstr "mbox¥Õ¥¡¥¤¥ë %(mbox)s ¤ò³«¤±¤Þ¤»¤ó: %(msg)s" #: bin/b4b5-archfix:19 +#, fuzzy msgid "" "Fix the MM2.1b4 archives.\n" "\n" @@ -9312,6 +9532,7 @@ msgstr "" " ¤³¤Î¥Ø¥ë¥×¥á¥Ã¥»¡¼¥¸¤ò½ÐÎϤ·¤Æ½ªÎ»¤¹¤ë¡£\n" #: bin/change_pw:145 +#, fuzzy msgid "Bad arguments: %(strargs)s" msgstr "°ú¿ô¤¬°ã¤¤¤Þ¤¹: %(strargs)s" @@ -9320,14 +9541,17 @@ msgid "Empty list passwords are not allowed" msgstr "¥ê¥¹¥È¤Î¥Ñ¥¹¥ï¡¼¥É¤Ï¶õÇò¤Ë¤Ç¤­¤Þ¤»¤ó" #: bin/change_pw:181 +#, fuzzy msgid "New %(listname)s password: %(notifypassword)s" msgstr "%(listname)s ¤Î¿·¥Ñ¥¹¥ï¡¼¥É: %(notifypassword)s" #: bin/change_pw:190 +#, fuzzy msgid "Your new %(listname)s list password" msgstr "%(listname)s ¤Î¿·¤·¤¤¥ê¥¹¥È¥Ñ¥¹¥ï¡¼¥É" #: bin/change_pw:191 +#, fuzzy msgid "" "The site administrator at %(hostname)s has changed the password for your\n" "mailing list %(listname)s. It is now\n" @@ -9354,6 +9578,7 @@ msgstr "" " %(adminurl)s\n" #: bin/check_db:19 +#, fuzzy msgid "" "Check a list's config database file for integrity.\n" "\n" @@ -9428,10 +9653,12 @@ msgid "List:" msgstr "¥ê¥¹¥È:" #: bin/check_db:148 +#, fuzzy msgid " %(file)s: okay" msgstr " %(file)s: ÎÉ" #: bin/check_perms:20 +#, fuzzy msgid "" "Check the permissions for the Mailman installation.\n" "\n" @@ -9451,47 +9678,57 @@ msgstr "" "-v ¥ª¥×¥·¥ç¥ó¤Ç¤è¤ê¿¤¯¤Î¾ðÊó¤ò½ÐÎϤ·¤Þ¤¹¡£\n" #: bin/check_perms:110 +#, fuzzy msgid " checking gid and mode for %(path)s" msgstr " %(path)s ¤Î gid ¤È¥â¡¼¥É¤ò¥Á¥§¥Ã¥¯" #: bin/check_perms:122 +#, fuzzy msgid "%(path)s bad group (has: %(groupname)s, expected %(MAILMAN_GROUP)s)" msgstr "" "%(path)s ¥°¥ë¡¼¥×¤Î¸í¤ê (%(groupname)s ¤Ç¤¹¤¬ %(MAILMAN_GROUP)s ¤Ë¤·¤Æ¤¯¤À¤µ" "¤¤)" #: bin/check_perms:151 +#, fuzzy msgid "directory permissions must be %(octperms)s: %(path)s" msgstr "" "%(path)s: ¥Ç¥£¥ì¥¯¥È¥ê¤Î¥Ñ¡¼¥ß¥Ã¥·¥ç¥ó¤Ï %(octperms)s ¤Ç¤Ê¤±¤ì¤Ð¤¤¤±¤Þ¤»¤ó" #: bin/check_perms:160 +#, fuzzy msgid "source perms must be %(octperms)s: %(path)s" msgstr "" "%(path)s: ¥½¡¼¥¹¥Õ¥¡¥¤¥ë¤Î¥Ñ¡¼¥ß¥Ã¥·¥ç¥ó¤Ï %(octperms)s ¤Ç¤Ê¤±¤ì¤Ð¤¤¤±¤Þ¤»¤ó" #: bin/check_perms:171 +#, fuzzy msgid "article db files must be %(octperms)s: %(path)s" msgstr "" "%(path)s: µ­»ö¤Î¥Ç¡¼¥¿¥Ù¡¼¥¹¥Õ¥¡¥¤¥ë¤Ï %(octperms)s ¤Ç¤Ê¤±¤ì¤Ð¤¤¤±¤Þ¤»¤ó" #: bin/check_perms:183 +#, fuzzy msgid "checking mode for %(prefix)s" msgstr "%(prefix)s ¤Î¥â¡¼¥É¤ò¥Á¥§¥Ã¥¯" #: bin/check_perms:193 +#, fuzzy msgid "WARNING: directory does not exist: %(d)s" msgstr "¥Ç¥£¥ì¥¯¥È¥ê¤¬¤¢¤ê¤Þ¤»¤ó: %(d)s" #: bin/check_perms:197 +#, fuzzy msgid "directory must be at least 02775: %(d)s" msgstr "%(d)s: ¥Ç¥£¥ì¥¯¥È¥ê¤ÏºÇÄã 02775 ¤Ç¤Ê¤±¤ì¤Ð¤¤¤±¤Þ¤»¤ó" #: bin/check_perms:209 +#, fuzzy msgid "checking perms on %(private)s" msgstr "%(private)s ¤Î¥Ñ¡¼¥ß¥Ã¥·¥ç¥ó¤ò¥Á¥§¥Ã¥¯" #: bin/check_perms:214 +#, fuzzy msgid "%(private)s must not be other-readable" msgstr "%(private)s ¤Ï other ¤¬ÆÉ¤ß¼è¤ê²Äǽ¤Ç¤Ê¤±¤ì¤Ð¤¤¤±¤Þ¤»¤ó" @@ -9513,6 +9750,7 @@ msgid "mbox file must be at least 0660:" msgstr "mbox ¥Õ¥¡¥¤¥ë¤ÏºÇÄã 0660 ¤Ç¤Ê¤±¤ì¤Ð¤¤¤±¤Þ¤»¤ó" #: bin/check_perms:263 +#, fuzzy msgid "%(dbdir)s \"other\" perms must be 000" msgstr "%(dbdir)s ¤Ï other ¤Ëµö²Ä¤òÍ¿¤¨¤Æ¤Ï¤¤¤±¤Þ¤»¤ó" @@ -9521,26 +9759,32 @@ msgid "checking cgi-bin permissions" msgstr "cgi-bin ¤Î¥Ñ¡¼¥ß¥Ã¥·¥ç¥ó¤ò¥Á¥§¥Ã¥¯" #: bin/check_perms:278 +#, fuzzy msgid " checking set-gid for %(path)s" msgstr " %(path)s ¤Î set-gid ¤ò¥Á¥§¥Ã¥¯" #: bin/check_perms:282 +#, fuzzy msgid "%(path)s must be set-gid" msgstr "%(path)s ¤Ï set-gid ¤µ¤ì¤Æ¤Ê¤¤¤È¤¤¤±¤Þ¤»¤ó" #: bin/check_perms:292 +#, fuzzy msgid "checking set-gid for %(wrapper)s" msgstr "%(wrapper)s ¤Î set-gid ¤ò¥Á¥§¥Ã¥¯" #: bin/check_perms:296 +#, fuzzy msgid "%(wrapper)s must be set-gid" msgstr "%(wrapper)s ¤Ï set-gid ¤µ¤ì¤Æ¤Ê¤¤¤È¤¤¤±¤Þ¤»¤ó" #: bin/check_perms:306 +#, fuzzy msgid "checking permissions on %(pwfile)s" msgstr "%(pwfile)s ¤Î¥Ñ¡¼¥ß¥Ã¥·¥ç¥ó¤ò¥Á¥§¥Ã¥¯" #: bin/check_perms:315 +#, fuzzy msgid "%(pwfile)s permissions must be exactly 0640 (got %(octmode)s)" msgstr "" "%(pwfile)s ¤Î¥Ñ¡¼¥ß¥Ã¥·¥ç¥ó¤¬ %(octmode)s ¤Ë¤Ê¤Ã¤Æ¤¤¤Þ¤¹¤¬¡¢0640 ¤Ç¤Ê¤¤¤È¤¤¤±" @@ -9551,10 +9795,12 @@ msgid "checking permissions on list data" msgstr "¥ê¥¹¥È¥Ç¡¼¥¿¤Î¥Ñ¡¼¥ß¥Ã¥·¥ç¥ó¤ò¥Á¥§¥Ã¥¯" #: bin/check_perms:348 +#, fuzzy msgid " checking permissions on: %(path)s" msgstr " %(path)s ¤Î¥Ñ¡¼¥ß¥Ã¥·¥ç¥ó¤ò¥Á¥§¥Ã¥¯" #: bin/check_perms:356 +#, fuzzy msgid "file permissions must be at least 660: %(path)s" msgstr "%(path)s ¥Õ¥¡¥¤¥ë¤Î¥Ñ¡¼¥ß¥Ã¥·¥ç¥ó¤ÏºÇÄã 660 ¤Ç¤Ê¤¤¤È¤¤¤±¤Þ¤»¤ó" @@ -9638,6 +9884,7 @@ msgid "Unix-From line changed: %(lineno)d" msgstr "Unix-From ¹Ô¤òÊѹ¹: %(lineno)d" #: bin/cleanarch:111 +#, fuzzy msgid "Bad status number: %(arg)s" msgstr "status¤Î»ØÄ꤬´Ö°ã¤Ã¤Æ¤¤¤Þ¤¹: %(arg)s" @@ -9781,10 +10028,12 @@ msgid " original address removed:" msgstr " ¸µ¤Î¥¢¥É¥ì¥¹¤òºï½ü:" #: bin/clone_member:202 +#, fuzzy msgid "Not a valid email address: %(toaddr)s" msgstr "̵¸ú¤Ê¥á¡¼¥ë¥¢¥É¥ì¥¹: %(toaddr)s" #: bin/clone_member:215 +#, fuzzy msgid "" "Error opening list \"%(listname)s\", skipping.\n" "%(e)s" @@ -9889,6 +10138,7 @@ msgstr "" "\n" #: bin/config_list:118 +#, fuzzy msgid "" "# -*- python -*-\n" "# -*- coding: %(charset)s -*-\n" @@ -9909,22 +10159,27 @@ msgid "legal values are:" msgstr "Í­¸ú¤ÊÃͤÏ:" #: bin/config_list:270 +#, fuzzy msgid "attribute \"%(k)s\" ignored" msgstr "°À­ \"%(k)s\" ¤ò̵»ë" #: bin/config_list:273 +#, fuzzy msgid "attribute \"%(k)s\" changed" msgstr "°À­ \"%(k)s\" ¤òÊѹ¹" #: bin/config_list:279 +#, fuzzy msgid "Non-standard property restored: %(k)s" msgstr "ɸ½à¤Ç¤Ê¤¤Ãͤò½¤Éü: %(k)s" #: bin/config_list:288 +#, fuzzy msgid "Invalid value for property: %(k)s" msgstr "¥×¥í¥Ñ¥Æ¥£Ãͤ¬Ìµ¸ú: %(k)s" #: bin/config_list:291 +#, fuzzy msgid "Bad email address for option %(k)s: %(v)s" msgstr "%(k)s ¥ª¥×¥·¥ç¥ó¤Î¥á¡¼¥ë¥¢¥É¥ì¥¹¤¬´Ö°ã¤Ã¤Æ¤¤¤Þ¤¹: %(v)s" @@ -9990,18 +10245,22 @@ msgstr "" " ºî¶È¾õ¶·¤ò½ÐÎϤ·¤Ê¤¤¡£\n" #: bin/discard:94 +#, fuzzy msgid "Ignoring non-held message: %(f)s" msgstr "ÊÝα°Ê³°¤Î¥á¡¼¥ë¤ò̵»ë: %(f)s" #: bin/discard:100 +#, fuzzy msgid "Ignoring held msg w/bad id: %(f)s" msgstr "´Ö°ã¤Ã¤¿ Id¤ò»ý¤Ä¥á¡¼¥ë¤ò̵»ë: %(f)s" #: bin/discard:112 +#, fuzzy msgid "Discarded held msg #%(id)s for list %(listname)s" msgstr "%(listname)s ¥ê¥¹¥È¤Î¥á¡¼¥ë #%(id)s ¤òÇË´þ" #: bin/dumpdb:19 +#, fuzzy msgid "" "Dump the contents of any Mailman `database' file.\n" "\n" @@ -10070,6 +10329,7 @@ msgid "No filename given." msgstr "¥Õ¥¡¥¤¥ë̾¤¬»ØÄꤵ¤ì¤Æ¤¤¤Þ¤»¤ó¡£" #: bin/dumpdb:108 +#, fuzzy msgid "Bad arguments: %(pargs)s" msgstr "°ú¿ô¤Î¸í¤ê: %(pargs)s" @@ -10078,14 +10338,17 @@ msgid "Please specify either -p or -m." msgstr "-p ¤Þ¤¿¤Ï -m ¤ò»ØÄꤷ¤Æ¤¯¤À¤µ¤¤" #: bin/dumpdb:133 +#, fuzzy msgid "[----- start %(typename)s file -----]" msgstr "[----- %(typename)s ¥Õ¥¡¥¤¥ë³«»Ï -----]" #: bin/dumpdb:139 +#, fuzzy msgid "[----- end %(typename)s file -----]" msgstr "[----- %(typename)s ¥Õ¥¡¥¤¥ë½ªÎ» -----]" #: bin/dumpdb:142 +#, fuzzy msgid "<----- start object %(cnt)s ----->" msgstr "<----- %(cnt)s ÈÖÌܤΥª¥Ö¥¸¥§¥¯¥È ----->" @@ -10301,6 +10564,7 @@ msgid "Setting web_page_url to: %(web_page_url)s" msgstr "web_page_url ¤ò %(web_page_url)s ¤ËÀßÄê" #: bin/fix_url.py:88 +#, fuzzy msgid "Setting host_name to: %(mailhost)s" msgstr "host_name ¤ò %(mailhost)s ¤ËÀßÄê" @@ -10338,6 +10602,7 @@ msgstr "" " ¤³¤Î¥á¥Ã¥»¡¼¥¸¤ò½ÐÎϤ·¤Æ½ªÎ»¤¹¤ë¡£\n" #: bin/genaliases:84 +#, fuzzy msgid "genaliases can't do anything useful with mm_cfg.MTA = %(mta)s." msgstr "genaliases ¤Î¼Â¹Ô¤Ï mm_cfg.MTA = %(mta)s ÀßÄê»þ¤ÏÉÔÍפǤ¹¡£" @@ -10390,6 +10655,7 @@ msgstr "" "ɸ½àÆþÎϤ¬»È¤ï¤ì¤ë¡£\n" #: bin/inject:84 +#, fuzzy msgid "Bad queue directory: %(qdir)s" msgstr "¥­¥å¡¼¥Ç¥£¥ì¥¯¥È¥ê¤Î»ØÄ꤬´Ö°ã¤Ã¤Æ¤¤¤Þ¤¹: %(qdir)s" @@ -10398,6 +10664,7 @@ msgid "A list name is required" msgstr "¥ê¥¹¥È̾¤ò»ØÄꤷ¤Æ¤¯¤À¤µ¤¤¡£" #: bin/list_admins:20 +#, fuzzy msgid "" "List all the owners of a mailing list.\n" "\n" @@ -10443,10 +10710,12 @@ msgstr "" "°Ê¾å¤Î¥ê¥¹¥È̾¤ò¥³¥Þ¥ó¥É¹Ô¤Ç»ØÄꤹ¤ë¤³¤È¤â¤Ç¤­¤ë¡£\n" #: bin/list_admins:97 +#, fuzzy msgid "List: %(listname)s, \tOwners: %(owners)s" msgstr "¥ê¥¹¥È: %(listname)s, \t´ÉÍý¼Ô: %(owners)s" #: bin/list_lists:19 +#, fuzzy msgid "" "List all mailing lists.\n" "\n" @@ -10507,6 +10776,7 @@ msgid "matching mailing lists found:" msgstr "Ŭ¹ç¤¹¤ë¥á¡¼¥ê¥ó¥°¥ê¥¹¥È¤¬¤¢¤ê¤Þ¤·¤¿:" #: bin/list_members:19 +#, fuzzy msgid "" "List all the members of a mailing list.\n" "\n" @@ -10627,10 +10897,12 @@ msgstr "" "¤µ¤ì¤ë¤¬¡¢ÇÛÁ÷¤Î¾õÂ֤ˤĤ¤¤Æ¤Ïɽ¼¨¤µ¤ì¤Ê¤¤¡£\n" #: bin/list_members:198 +#, fuzzy msgid "Bad --nomail option: %(why)s" msgstr "--nomail ¥ª¥×¥·¥ç¥ó¤¬°ã¤¤¤Þ¤¹: %(why)s" #: bin/list_members:209 +#, fuzzy msgid "Bad --digest option: %(kind)s" msgstr "--digest ¥ª¥×¥·¥ç¥ó¤¬°ã¤¤¤Þ¤¹: %(kind)s" @@ -10644,6 +10916,7 @@ msgid "Could not open file for writing:" msgstr "½ñ¤­¹þ¤à¥Õ¥¡¥¤¥ë¤¬³«¤±¤Þ¤»¤ó:" #: bin/list_owners:20 +#, fuzzy msgid "" "List the owners of a mailing list, or all mailing lists.\n" "\n" @@ -10697,6 +10970,7 @@ msgstr "" "¥Ó¥ë¥É¥ª¥×¥·¥ç¥ó¤òɽ¼¨¤¹¤ë¡£Í× Python 2¡£" #: bin/mailmanctl:20 +#, fuzzy msgid "" "Primary start-up and shutdown script for Mailman's qrunner daemon.\n" "\n" @@ -10866,6 +11140,7 @@ msgstr "" " ¤È¤­¤Ë¡¢ºÆÅÙ¥ª¡¼¥×¥ó¤µ¤»¤ë¡£\n" #: bin/mailmanctl:152 +#, fuzzy msgid "PID unreadable in: %(pidfile)s" msgstr "PID ¤ËÅþã¤Ç¤­¤Þ¤»¤ó: %(pidfile)s" @@ -10874,6 +11149,7 @@ msgid "Is qrunner even running?" msgstr "qrunner ¤¬¤Þ¤ÀÁö¤Ã¤Æ¤Þ¤¹¤«?" #: bin/mailmanctl:160 +#, fuzzy msgid "No child with pid: %(pid)s" msgstr "PID¤Î»Ò¥×¥í¥»¥¹¤¬¤¢¤ê¤Þ¤»¤ó: %(pid)s" @@ -10901,6 +11177,7 @@ msgstr "" "-s¥Õ¥é¥°¤ò¤Ä¤±¤Æ mailmanctl ¤ò¤â¤¦°ìÅÙµ¯Æ°¤·¤Æ¤¯¤À¤µ¤¤¡£\n" #: bin/mailmanctl:233 +#, fuzzy msgid "" "The master qrunner lock could not be acquired, because it appears as if " "some\n" @@ -10926,10 +11203,12 @@ msgstr "" "½ªÎ»¤·¤Þ¤¹¡£" #: bin/mailmanctl:280 cron/mailpasswds:119 +#, fuzzy msgid "Site list is missing: %(sitelistname)s" msgstr "¥µ¥¤¥È¥ê¥¹¥È̾¤¬¤¢¤ê¤Þ¤»¤ó: %(sitelistname)s" #: bin/mailmanctl:305 +#, fuzzy msgid "Run this program as root or as the %(name)s user, or use -u." msgstr "" "¤³¤Î¥×¥í¥°¥é¥à¤Ï root ¤« %(name)s ¥æ¡¼¥¶¤Ç¼Â¹Ô¤·¤Æ¤¯¤À¤µ¤¤¡£\n" @@ -10940,6 +11219,7 @@ msgid "No command given." msgstr "¥³¥Þ¥ó¥É¤¬¤¢¤ê¤Þ¤»¤ó¡£" #: bin/mailmanctl:339 +#, fuzzy msgid "Bad command: %(command)s" msgstr "¥³¥Þ¥ó¥É¤¬°ã¤¤¤Þ¤¹: %(command)s" @@ -10964,6 +11244,7 @@ msgid "Starting Mailman's master qrunner." msgstr "Mailman ¤Î¥Þ¥¹¥¿¡¼ qrunner ¤òµ¯Æ°¤·¤Þ¤¹" #: bin/mmsitepass:19 +#, fuzzy msgid "" "Set the site password, prompting from the terminal.\n" "\n" @@ -11013,6 +11294,7 @@ msgid "list creator" msgstr "¥ê¥¹¥ÈºîÀ®¼Ô" #: bin/mmsitepass:86 +#, fuzzy msgid "New %(pwdesc)s password: " msgstr "¿·¤·¤¤ %(pwdesc)s ¤Î¥Ñ¥¹¥ï¡¼¥É:" @@ -11092,6 +11374,7 @@ msgid "Return the generated output." msgstr "À¸À®¤·¤¿½ÐÎϤòÊÖ¤¹¡£" #: bin/newlist:20 +#, fuzzy msgid "" "Create a new, unpopulated mailing list.\n" "\n" @@ -11265,6 +11548,7 @@ msgstr "" "¥ê¥¹¥È̾¤Ï¾®Ê¸»ú¤ËÊÑ´¹¤µ¤ì¤ë¡£\n" #: bin/newlist:162 +#, fuzzy msgid "Unknown language: %(lang)s" msgstr "¸À¸ì¤¬ÉÔÌÀ: %(lang)s" @@ -11277,6 +11561,7 @@ msgid "Enter the email of the person running the list: " msgstr "¥ê¥¹¥È´ÉÍý¼Ô¤Î¥á¡¼¥ë¥¢¥É¥ì¥¹¤òÆþÎϤ·¤Æ¤¯¤À¤µ¤¤: " #: bin/newlist:193 +#, fuzzy msgid "Initial %(listname)s password: " msgstr "%(listname)s ¤Î½é´ü¥Ñ¥¹¥ï¡¼¥É: " @@ -11286,17 +11571,19 @@ msgstr " #: bin/newlist:220 msgid "" -" - owner addresses need to be fully-qualified names like \"owner@example.com" -"\", not just \"owner\"." +" - owner addresses need to be fully-qualified names like \"owner@example." +"com\", not just \"owner\"." msgstr "" "- ¥ê¥¹¥È´ÉÍý¼Ô¤Î¥¢¥É¥ì¥¹¤Ï¡Öowner@example.com¡×¤Î¤è¤¦¤Ë´°Á´¤Ê·Á¼°¤Ç¤¢¤ëɬÍפ¬" "¤¢¤ê¤Þ¤¹¡£" #: bin/newlist:244 +#, fuzzy msgid "Hit enter to notify %(listname)s owner..." msgstr "Enter ¤ò²¡¤·¤Æ %(listname)s ¤Î´ÉÍý¼Ô¤Ë¥á¡¼¥ëÄÌÃΤ¹¤ë..." #: bin/qrunner:20 +#, fuzzy msgid "" "Run one or more qrunners, once or repeatedly.\n" "\n" @@ -11415,6 +11702,7 @@ msgstr "" "¤·¤Æ¤ª¤¯¡£¸ÄÊ̤˼¹Ԥ¹¤ë¤³¤È¤¬Í­±×¤Ê¤Î¤Ï¥Ç¥Ð¥Ã¥°¤ò¹Ô¤¦¾ì¹ç¤À¤±¤Ç¤¢¤ë¡£\n" #: bin/qrunner:179 +#, fuzzy msgid "%(name)s runs the %(runnername)s qrunner" msgstr "%(name)s ¤Ï %(runnername)s qrunner ¤ò¼Â¹Ô" @@ -11427,6 +11715,7 @@ msgid "No runner name given." msgstr "runner ̾¤¬¤¢¤ê¤Þ¤»¤ó¡£" #: bin/rb-archfix:21 +#, fuzzy msgid "" "Reduce disk space usage for Pipermail archives.\n" "\n" @@ -11565,18 +11854,22 @@ msgstr "" "\n" #: bin/remove_members:156 +#, fuzzy msgid "Could not open file for reading: %(filename)s." msgstr "ÆÉ¤ß¹þ¤ß¥Õ¥¡¥¤¥ë¤¬³«¤±¤Þ¤»¤ó: %(filename)s" #: bin/remove_members:163 +#, fuzzy msgid "Error opening list %(listname)s... skipping." msgstr "%(listname)s ¥ê¥¹¥È¤¬¥¨¥é¡¼¤Ç³«¤±¤Þ¤»¤ó... Èô¤Ð¤·¤Þ¤¹¡£" #: bin/remove_members:173 +#, fuzzy msgid "No such member: %(addr)s" msgstr "¤½¤Î¤è¤¦¤Ê²ñ°÷¤Ï¤¤¤Þ¤»¤ó: %(addr)s." #: bin/remove_members:178 +#, fuzzy msgid "User `%(addr)s' removed from list: %(listname)s." msgstr "`%(addr)s' ¤òÂà²ñ¤µ¤»¤Þ¤·¤¿: %(listname)s" @@ -11615,10 +11908,12 @@ msgstr "" " ¥¹¥¯¥ê¥×¥È¤¬²¿¤ò¤·¤Æ¤¤¤ë¤«¤ò½ÐÎϤ¹¤ë¡£\n" #: bin/reset_pw.py:77 +#, fuzzy msgid "Changing passwords for list: %(listname)s" msgstr "%(listname)s ¥á¡¼¥ê¥ó¥°¥ê¥¹¥È¤Î¥æ¡¼¥¶¥Ñ¥¹¥ï¡¼¥É¤òÊѹ¹" #: bin/reset_pw.py:83 +#, fuzzy msgid "New password for member %(member)40s: %(randompw)s" msgstr "²ñ°÷¤Î¿·¥Ñ¥¹¥ï¡¼¥É %(member)40s: %(randompw)s" @@ -11664,18 +11959,22 @@ msgstr "" "\n" #: bin/rmlist:73 bin/rmlist:76 +#, fuzzy msgid "Removing %(msg)s" msgstr "%(msg)s ¤òºï½üÃæ" #: bin/rmlist:81 +#, fuzzy msgid "%(listname)s %(msg)s not found as %(filename)s" msgstr "%(listname)s %(msg)s ¤Ï %(filename)s ¤Ë¸«¤Ä¤«¤ê¤Þ¤»¤ó" #: bin/rmlist:105 +#, fuzzy msgid "No such list (or list already deleted): %(listname)s" msgstr "%(listname)s ¤È¤¤¤¦¥ê¥¹¥È¤Ï¤¢¤ê¤Þ¤»¤ó (Ëô¤Ï, ´û¤Ëºï½ü¤µ¤ì¤Æ¤¤¤Þ¤¹)¡£" #: bin/rmlist:108 +#, fuzzy msgid "No such list: %(listname)s. Removing its residual archives." msgstr "%(listname)s ¤È¤¤¤¦¥ê¥¹¥È¤Ï¤¢¤ê¤Þ¤»¤ó¡£»Ä¤µ¤ì¤¿Êݸ½ñ¸Ë¤òºï½ü¤·¤Þ¤¹¡£" @@ -11734,6 +12033,7 @@ msgstr "" "Îã: show_qfiles qfiles/shunt/*.pck\n" #: bin/sync_members:19 +#, fuzzy msgid "" "Synchronize a mailing list's membership with a flat file.\n" "\n" @@ -11860,6 +12160,7 @@ msgstr "" " ɬ¿Ü¡£Æ±´ü¤¹¤ë¥ê¥¹¥È¤ò»ØÄê¡£\n" #: bin/sync_members:115 +#, fuzzy msgid "Bad choice: %(yesno)s" msgstr "ÁªÂò¤¬Ìµ¸ú: %(yesno)s" @@ -11876,6 +12177,7 @@ msgid "No argument to -f given" msgstr "-f ¤Î°ú¿ô¤¬¤¢¤ê¤Þ¤»¤ó" #: bin/sync_members:172 +#, fuzzy msgid "Illegal option: %(opt)s" msgstr "¥ª¥×¥·¥ç¥ó¤¬Àµ¤·¤¯¤¢¤ê¤Þ¤»¤ó: %(opt)s" @@ -11888,6 +12190,7 @@ msgid "Must have a listname and a filename" msgstr "¥ê¥¹¥È̾¤È¥Õ¥¡¥¤¥ë̾¤¬É¬ÍפǤ¹" #: bin/sync_members:191 +#, fuzzy msgid "Cannot read address file: %(filename)s: %(msg)s" msgstr "¥¢¥É¥ì¥¹¥Õ¥¡¥¤¥ë %(filename)s ¤¬ÆÉ¤á¤Þ¤»¤ó: %(msg)s" @@ -11904,14 +12207,17 @@ msgid "You must fix the preceding invalid addresses first." msgstr "¤Þ¤º¡¢Ìµ¸ú¤Ê¥¢¥É¥ì¥¹¤ò½¤Àµ¤·¤Æ¤¯¤À¤µ¤¤¡£" #: bin/sync_members:264 +#, fuzzy msgid "Added : %(s)s" msgstr "Äɲà : %(s)s" #: bin/sync_members:289 +#, fuzzy msgid "Removed: %(s)s" msgstr "ºï½ü : %(s)s" #: bin/transcheck:19 +#, fuzzy msgid "" "\n" "Check a given Mailman translation, making sure that variables and\n" @@ -11987,6 +12293,7 @@ msgid "scan the po file comparing msgids with msgstrs" msgstr "po ¥Õ¥¡¥¤¥ë¤ò¥¹¥­¥ã¥ó¤·¤Æ msgid ¤È msgstr ¤òÈæ³Ó" #: bin/unshunt:20 +#, fuzzy msgid "" "Move a message from the shunt queue to the original queue.\n" "\n" @@ -12016,6 +12323,7 @@ msgstr "" "Î㤨¤Ð¡¢qfiles/out ¤Î¥á¡¼¥ë¤ò unshunt ¤¹¤ë¤È¥á¡¼¥ë¤¬¾Ã¤¨¤Æ¤·¤Þ¤¤¤Þ¤¹¡£\n" #: bin/unshunt:85 +#, fuzzy msgid "" "Cannot unshunt message %(filebase)s, skipping:\n" "%(e)s" @@ -12024,6 +12332,7 @@ msgstr "" "%(e)s" #: bin/update:20 +#, fuzzy msgid "" "Perform all necessary upgrades.\n" "\n" @@ -12059,14 +12368,17 @@ msgstr "" "¤ò¼Â¹Ô¤¹¤ë¡£¤³¤ì¤Ï 1.0b4 ¤Þ¤Ç¤Î¥Ð¡¼¥¸¥ç¥ó¤òÃΤäƤ¤¤ë¡£\n" #: bin/update:107 +#, fuzzy msgid "Fixing language templates: %(listname)s" msgstr "¸À¸ì¥Æ¥ó¥×¥ì¡¼¥È¤ò½¤Àµ: %(listname)s" #: bin/update:196 bin/update:711 +#, fuzzy msgid "WARNING: could not acquire lock for list: %(listname)s" msgstr "·Ù¹ð: ¥ê¥¹¥È¤Î¥í¥Ã¥¯¤ò³ÍÆÀ¤Ç¤­¤Þ¤»¤ó: %(listname)s" #: bin/update:215 +#, fuzzy msgid "Resetting %(n)s BYBOUNCEs disabled addrs with no bounce info" msgstr "ÇÛÁ÷Ää»ß¤Î %(n)s BYBOUNCE ¤ò [¾ðÊó¤Ê¤·] ¤Ë¥ê¥»¥Ã¥È" @@ -12083,6 +12395,7 @@ msgstr "" "»È¤¨¤Þ¤»¤ó¤Î¤Ç¡¢%(mbox_dir)s.tmp ¤ËÊѹ¹¤·¤ÆÀè¤Ë¿Ê¤ß¤Þ¤¹¡£" #: bin/update:255 +#, fuzzy msgid "" "\n" "%(listname)s has both public and private mbox archives. Since this list\n" @@ -12133,6 +12446,7 @@ msgid "- updating old private mbox file" msgstr "- ¸Å¤¤ private mbox ¥Õ¥¡¥¤¥ë¤ò¹¹¿·Ãæ" #: bin/update:295 +#, fuzzy msgid "" " unknown file in the way, moving\n" " %(o_pri_mbox_file)s\n" @@ -12150,6 +12464,7 @@ msgid "- updating old public mbox file" msgstr "- ¸Å¤¤ public mbox ¥Õ¥¡¥¤¥ë¤ò¹¹¿·Ãæ" #: bin/update:317 +#, fuzzy msgid "" " unknown file in the way, moving\n" " %(o_pub_mbox_file)s\n" @@ -12179,18 +12494,22 @@ msgid "- %(o_tmpl)s doesn't exist, leaving untouched" msgstr "- %(o_tmpl)s ¤Ï¤¢¤ê¤Þ¤»¤ó¡£¤½¤Î¤Þ¤Þ¤Ë¤·¤Þ¤¹" #: bin/update:396 +#, fuzzy msgid "removing directory %(src)s and everything underneath" msgstr "%(src)s ¥Ç¥£¥ì¥¯¥È¥ê°Ê²¼¤òºï½ü¤·¤Þ¤¹" #: bin/update:399 +#, fuzzy msgid "removing %(src)s" msgstr "%(src)s ¤òºï½ü¤·¤Þ¤¹" #: bin/update:403 +#, fuzzy msgid "Warning: couldn't remove %(src)s -- %(rest)s" msgstr "·Ù¹ð: %(src)s ¤òºï½ü¤Ç¤­¤Þ¤»¤ó -- %(rest)s" #: bin/update:408 +#, fuzzy msgid "couldn't remove old file %(pyc)s -- %(rest)s" msgstr "¸Å¤¤¥Õ¥¡¥¤¥ë %(pyc)s ¤òºï½ü¤Ç¤­¤Þ¤»¤ó -- %(rest)s" @@ -12199,14 +12518,17 @@ msgid "updating old qfiles" msgstr "¸Å¤¤ qfile ¤ò¹¹¿·" #: bin/update:455 +#, fuzzy msgid "Warning! Not a directory: %(dirpath)s" msgstr "·Ù¹ð! ¥Ç¥£¥ì¥¯¥È¥ê¤Ç¤Ï¤¢¤ê¤Þ¤»¤ó: %(dirpath)s" #: bin/update:530 +#, fuzzy msgid "message is unparsable: %(filebase)s" msgstr "¥á¡¼¥ë¤ò²òÀϤǤ­¤Þ¤»¤ó: %(filebase)s" #: bin/update:544 +#, fuzzy msgid "Warning! Deleting empty .pck file: %(pckfile)s" msgstr "·Ù¹ð! ¶õ¤Î .pck ¥Õ¥¡¥¤¥ë¤òºï½ü: %(pckfile)s" @@ -12219,10 +12541,12 @@ msgid "Updating Mailman 2.1.4 pending.pck database" msgstr "Mailman 2.1.4 ¤Î pending_subscriptions.db ¥Ç¡¼¥¿¥Ù¡¼¥¹¤ò¹¹¿·" #: bin/update:598 +#, fuzzy msgid "Ignoring bad pended data: %(key)s: %(val)s" msgstr "ÊÝα¥Ç¡¼¥¿¤Ë¸í¤ê¡£Ìµ»ë¤·¤Þ¤¹: %(key)s: %(val)s" #: bin/update:614 +#, fuzzy msgid "WARNING: Ignoring duplicate pending ID: %(id)s." msgstr "·Ù¹ð: ÊÝαID¤Ë½ÅÊ£¤¢¤ê¡£Ìµ»ë¤·¤Þ¤¹: %(id)s." @@ -12247,6 +12571,7 @@ msgid "done" msgstr "½ªÎ»" #: bin/update:691 +#, fuzzy msgid "Updating mailing list: %(listname)s" msgstr "%(listname)s ¥á¡¼¥ê¥ó¥°¥ê¥¹¥È¤ò¹¹¿·Ãæ" @@ -12308,6 +12633,7 @@ msgid "No updates are necessary." msgstr "¹¹¿·¤ÎɬÍפϤ¢¤ê¤Þ¤»¤ó¡£" #: bin/update:796 +#, fuzzy msgid "" "Downgrade detected, from version %(hexlversion)s to version %(hextversion)s\n" "This is probably not safe.\n" @@ -12318,10 +12644,12 @@ msgstr "" "½ªÎ»¤·¤Þ¤¹¡£" #: bin/update:801 +#, fuzzy msgid "Upgrading from version %(hexlversion)s to %(hextversion)s" msgstr "%(hexlversion)s ¤«¤é %(hextversion)s ¤Ø¥¢¥Ã¥×¥°¥ì¡¼¥É" #: bin/update:810 +#, fuzzy msgid "" "\n" "ERROR:\n" @@ -12589,6 +12917,7 @@ msgstr "" " " #: bin/withlist:175 +#, fuzzy msgid "Unlocking (but not saving) list: %(listname)s" msgstr "¥ê¥¹¥È¤ò¥í¥Ã¥¯²ò½ü (Êݸ¤Ï¤·¤Æ¤Þ¤»¤ó): %(listname)s" @@ -12597,6 +12926,7 @@ msgid "Finalizing" msgstr "ºÇ½ª½èÍýÃæ" #: bin/withlist:188 +#, fuzzy msgid "Loading list %(listname)s" msgstr "%(listname)s ¤Î¥ê¥¹¥È¤òÆÉ¤ß¹þÃæ" @@ -12609,6 +12939,7 @@ msgid "(unlocked)" msgstr "(¥í¥Ã¥¯²ò½ü)" #: bin/withlist:197 +#, fuzzy msgid "Unknown list: %(listname)s" msgstr "¥ê¥¹¥È̾¤¬ÉÔÌÀ: %(listname)s" @@ -12621,18 +12952,22 @@ msgid "--all requires --run" msgstr "--all ¤Ë¤Ï --run ¤¬É¬ÍפǤ¹¡£" #: bin/withlist:266 +#, fuzzy msgid "Importing %(module)s..." msgstr "%(module)s ¤ò import Ãæ..." #: bin/withlist:270 +#, fuzzy msgid "Running %(module)s.%(callable)s()..." msgstr "%(module)s.%(callable)s() ¤ò¼Â¹ÔÃæ..." #: bin/withlist:291 +#, fuzzy msgid "The variable `m' is the %(listname)s MailList instance" msgstr "ÊÑ¿ô `m' ¤¬ %(listname)s ¤Î MailList ¥¤¥ó¥¹¥¿¥ó¥¹¤Ç¤¹" #: cron/bumpdigests:19 +#, fuzzy msgid "" "Increment the digest volume number and reset the digest number to one.\n" "\n" @@ -12660,6 +12995,7 @@ msgstr "" "¤¹¤Ù¤Æ¤Î¥ê¥¹¥È¤ò bump ¤¹¤ë¡£\n" #: cron/checkdbs:20 +#, fuzzy msgid "" "Check for pending admin requests and mail the list owners if necessary.\n" "\n" @@ -12688,10 +13024,12 @@ msgstr "" "\n" #: cron/checkdbs:121 +#, fuzzy msgid "%(count)d %(realname)s moderator request(s) waiting" msgstr "%(count)d ·ï¤Î %(realname)s ¿½ÀÁ°Æ·ï¤¬¤¢¤ê¤Þ¤¹" #: cron/checkdbs:124 +#, fuzzy msgid "%(realname)s moderator request check result" msgstr "%(realname)s ¿½ÀÁ°Æ·ï¸¡ºº·ë²Ì" @@ -12712,6 +13050,7 @@ msgstr "" "ÊÝαÅê¹Æ:" #: cron/checkdbs:169 +#, fuzzy msgid "" "From: %(sender)s on %(date)s\n" "Subject: %(subject)s\n" @@ -12723,6 +13062,7 @@ msgstr "" "¸¶°ø: %(reason)s" #: cron/cull_bad_shunt:20 +#, fuzzy msgid "" "Cull bad and shunt queues, recommended once per day.\n" "\n" @@ -12761,6 +13101,7 @@ msgstr "" " ¤³¤Î¥á¥Ã¥»¡¼¥¸¤ò½ÐÎϤ·¤Æ½ªÎ»¤¹¤ë¡£\n" #: cron/disabled:20 +#, fuzzy msgid "" "Process disabled members, recommended once per day.\n" "\n" @@ -12879,6 +13220,7 @@ msgstr "" "\n" #: cron/mailpasswds:19 +#, fuzzy msgid "" "Send password reminders for all lists to all users.\n" "\n" @@ -12928,10 +13270,12 @@ msgid "Password // URL" msgstr "¥Ñ¥¹¥ï¡¼¥É // URL" #: cron/mailpasswds:222 +#, fuzzy msgid "%(host)s mailing list memberships reminder" msgstr "%(host)s ¥á¡¼¥ê¥ó¥°¥ê¥¹¥È²ñ°÷¾ðÊóÈ÷˺ÄÌÃÎ" #: cron/nightly_gzip:19 +#, fuzzy msgid "" "Re-generate the Pipermail gzip'd archive flat files.\n" "\n" @@ -12979,6 +13323,7 @@ msgstr "" "\n" #: cron/senddigests:20 +#, fuzzy msgid "" "Dispatch digests for lists w/pending messages and digest_send_periodic set.\n" "\n" diff --git a/messages/ko/LC_MESSAGES/mailman.po b/messages/ko/LC_MESSAGES/mailman.po index 87d79214..f662393a 100755 --- a/messages/ko/LC_MESSAGES/mailman.po +++ b/messages/ko/LC_MESSAGES/mailman.po @@ -194,6 +194,7 @@ msgid "Pickling archive state into " msgstr "ÀúÀå¼Ò »óŸ¦ ÀúÀåÁßÀÔ´Ï´Ù: " #: Mailman/Archiver/pipermail.py:453 +#, fuzzy msgid "Updating index files for archive [%(archive)s]" msgstr "ÀúÀå¼Ò [%(archive)s] ¸¦ À§ÇÑ À妽º ÆÄÀÏÀ» ¾÷µ¥ÀÌÆ® ÁßÀÔ´Ï´Ù." @@ -269,6 +270,7 @@ msgstr " #: Mailman/Cgi/options.py:101 Mailman/Cgi/private.py:108 #: Mailman/Cgi/rmlist.py:75 Mailman/Cgi/roster.py:59 #: Mailman/Cgi/subscribe.py:67 +#, fuzzy msgid "No such list %(safelistname)s" msgstr "%(safelistname)s ¶ó´Â ¸ÞÀϸµ ¸®½ºÆ®°¡ Á¸ÀçÇÏÁö ¾Ê½À´Ï´Ù." @@ -340,6 +342,7 @@ msgstr "" " ´ç½ÅÀÌ ÀÌ ¹®Á¦¸¦ °íÄ¡Áö ¾ÊÀ¸¸é °ü¸®ÀÚ¸¦ ±«·ÓÈ÷°Ô µË´Ï´Ù.%(rm)r" #: Mailman/Cgi/admin.py:268 +#, fuzzy msgid "%(hostname)s mailing lists - Admin Links" msgstr "%(hostname)s ¸ÞÀϸµ ¸®½ºµé - °ü¸® ºÎºÐ ¸µÅ©" @@ -352,6 +355,7 @@ msgid "Mailman" msgstr "" #: Mailman/Cgi/admin.py:310 +#, fuzzy msgid "" "

                    There currently are no publicly-advertised %(mailmanlink)s\n" " mailing lists on %(hostname)s." @@ -360,6 +364,7 @@ msgstr "" "\t ¸ÞÀϸµ ¸®½ºÆ®°¡ ¾ø½À´Ï´Ù." #: Mailman/Cgi/admin.py:316 +#, fuzzy msgid "" "

                    Below is the collection of publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s. Click on a list\n" @@ -374,6 +379,7 @@ msgid "right " msgstr "¿Ã¹Ù¸¥ " #: Mailman/Cgi/admin.py:325 +#, fuzzy msgid "" "To visit the administrators configuration page for an\n" " unadvertised list, open a URL similar to this one, but with a '/' " @@ -417,6 +423,7 @@ msgid "No valid variable name found." msgstr "À̸§À» ãÀ» ¼ö ¾ø½À´Ï´Ù." #: Mailman/Cgi/admin.py:395 +#, fuzzy msgid "" "%(realname)s Mailing list Configuration Help\n" "
                    %(varname)s Option" @@ -425,6 +432,7 @@ msgstr "" "
                    %(varname)s ¼³Á¤" #: Mailman/Cgi/admin.py:402 +#, fuzzy msgid "Mailman %(varname)s List Option Help" msgstr "Mailman %(varname)s ¸ÞÀϸµ ¸®½ºÆ® ¼³Á¤ µµ¿ò¸»" @@ -447,10 +455,12 @@ msgid "return to the %(categoryname)s options page." msgstr "%(categoryname)s ¼³Á¤ ÆäÀÌÁö·Î µ¹¾Æ°¡½Ç ¼ö ÀÖ½À´Ï´Ù." #: Mailman/Cgi/admin.py:446 +#, fuzzy msgid "%(realname)s Administration (%(label)s)" msgstr "%(realname)s °ü¸® (%(label)s)" #: Mailman/Cgi/admin.py:447 +#, fuzzy msgid "%(realname)s mailing list administration
                    %(label)s Section" msgstr "%(realname)s ¸ÞÀϸµ ¸®½ºÆ® °ü¸®
                    %(label)s ¼½¼Ç" @@ -532,6 +542,7 @@ msgid "Value" msgstr "°ª" #: Mailman/Cgi/admin.py:668 +#, fuzzy msgid "" "Badly formed options entry:\n" " %(record)s" @@ -637,6 +648,7 @@ msgid "
                    (Edit %(varname)s)" msgstr "
                    (%(varname)s »ó¼¼¼³¸í)" #: Mailman/Cgi/admin.py:872 +#, fuzzy msgid "
                    (Details for %(varname)s)" msgstr "
                    (%(varname)s »ó¼¼¼³¸í)" @@ -688,10 +700,12 @@ msgid "Bad regular expression: " msgstr "À߸øµÈ Á¤±Ô Ç¥Çö½Ä: " #: Mailman/Cgi/admin.py:1013 +#, fuzzy msgid "%(allcnt)s members total, %(membercnt)s shown" msgstr "ȸ¿ø ÃÑ ¼ö : %(allcnt)s ¸í, º¼ ¼ö Àִ ȸ¿ø : %(membercnt)s ¸í" #: Mailman/Cgi/admin.py:1016 +#, fuzzy msgid "%(allcnt)s members total" msgstr "ȸ¿ø ÃÑ %(allcnt)s ¸í" @@ -870,6 +884,7 @@ msgstr "" "¿ä." #: Mailman/Cgi/admin.py:1221 +#, fuzzy msgid "from %(start)s to %(end)s" msgstr "%(start)s ¿¡¼­ %(end)s ±îÁö" @@ -1004,6 +1019,7 @@ msgid "Change list ownership passwords" msgstr "¸ÞÀϸµ ¸®½ºÆ® ¼ÒÀ¯ÁÖÀÇ ÆÐ½º¿öµå¸¦ º¯°æÇÕ´Ï´Ù." #: Mailman/Cgi/admin.py:1364 +#, fuzzy msgid "" "The list administrators are the people who have ultimate control " "over\n" @@ -1174,8 +1190,9 @@ msgid "%(schange_to)s is already a member" msgstr " ´Â ÀÌ¹Ì È¸¿øÀÔ´Ï´Ù." #: Mailman/Cgi/admin.py:1620 +#, fuzzy msgid "%(schange_to)s matches banned pattern %(spat)s" -msgstr "" +msgstr " ´Â ÀÌ¹Ì È¸¿øÀÔ´Ï´Ù." #: Mailman/Cgi/admin.py:1622 msgid "Address %(schange_from)s changed to %(schange_to)s" @@ -1216,6 +1233,7 @@ msgid "Not subscribed" msgstr "°¡ÀÔÀÌ µÇÁö ¾Ê¾Ò½À´Ï´Ù." #: Mailman/Cgi/admin.py:1773 +#, fuzzy msgid "Ignoring changes to deleted member: %(user)s" msgstr "»èÁ¦ ¸í´Ü¿¡¼­ ¹«½ÃµÈ ȸ¿ø: %(user)s" @@ -1228,10 +1246,12 @@ msgid "Error Unsubscribing:" msgstr "Å»Åð ¿¡·¯:" #: Mailman/Cgi/admindb.py:235 Mailman/Cgi/admindb.py:248 +#, fuzzy msgid "%(realname)s Administrative Database" msgstr "%(realname)s °ü¸® DB" #: Mailman/Cgi/admindb.py:238 +#, fuzzy msgid "%(realname)s Administrative Database Results" msgstr "%(realname)s °ü¸® DB °á°ú¹°" @@ -1261,6 +1281,7 @@ msgid "Discard all messages marked Defer" msgstr "" #: Mailman/Cgi/admindb.py:298 +#, fuzzy msgid "all of %(esender)s's held messages." msgstr "%(esender)s ¿äûÀº °¡Áö°í ÀÖ½À´Ï´Ù." @@ -1281,6 +1302,7 @@ msgid "list of available mailing lists." msgstr "ÀÌ¿ë °¡´ÉÇÑ ¸ÞÀϸµ ¸®½ºÆ® ¸ñ·Ï." #: Mailman/Cgi/admindb.py:362 +#, fuzzy msgid "You must specify a list name. Here is the %(link)s" msgstr "" "¸ÞÀϸµ ¸®½ºÆ® À̸§À» ÁöÁ¤ÇØ ÁÖ¼Å¾ß ÇÕ´Ï´Ù. ¸ñ·ÏÀº %(link)s ¿©±âÀÖ½À´Ï´Ù." @@ -1385,6 +1407,7 @@ msgid "Rejects" msgstr "°ÅÀýÇϱâ" #: Mailman/Cgi/admindb.py:584 +#, fuzzy msgid "" "Ban %(esender)s from ever subscribing to this\n" " mailing list" @@ -1399,6 +1422,7 @@ msgid "" msgstr "°³ÀÎ ¸Þ¼¼Áö¸¦ º¸±âÀ§ÇØ ¸Þ¼¼Áö ¹øÈ£¸¦ Ŭ¸¯Çϼ¼¿ä." #: Mailman/Cgi/admindb.py:591 +#, fuzzy msgid "view all messages from %(esender)s" msgstr "%(esender)s °¡ º¸³½ ¸ðµç ¸Þ¼¼Áö º¸±â" @@ -1523,6 +1547,7 @@ msgid "" msgstr "" #: Mailman/Cgi/confirm.py:178 +#, fuzzy msgid "System error, bad content: %(content)s" msgstr "½Ã½ºÅÛ ¿¡·¯, À߸øµÈ ³»¿ë: %(content)s" @@ -1655,6 +1680,7 @@ msgid "Awaiting moderator approval" msgstr "±Û°ü¸®ÀÚÀÇ ½ÂÀÎÀ» ±â´Ù¸®°í ÀÖ½À´Ï´Ù." #: Mailman/Cgi/confirm.py:386 +#, fuzzy msgid "" " You have successfully confirmed your subscription request to " "the\n" @@ -1708,6 +1734,7 @@ msgid "Subscription request confirmed" msgstr "°¡ÀÔ ¿äûÀÌ È®ÀεǾú½À´Ï´Ù." #: Mailman/Cgi/confirm.py:418 +#, fuzzy msgid "" " You have successfully confirmed your subscription request for\n" " \"%(addr)s\" to the %(listname)s mailing list. A separate\n" @@ -1733,6 +1760,7 @@ msgid "Unsubscription request confirmed" msgstr "Å»Åð ¿äûÀÌ È®ÀÎ µÇ¾ú½À´Ï´Ù." #: Mailman/Cgi/confirm.py:469 +#, fuzzy msgid "" " You have successfully unsubscribed from the %(listname)s " "mailing\n" @@ -1752,6 +1780,7 @@ msgid "Not available" msgstr "ÀÌ¿ëÇÒ ¼ö ¾ø½À´Ï´Ù." #: Mailman/Cgi/confirm.py:498 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " unsubscription request from the mailing list %(listname)s. " @@ -1818,6 +1847,7 @@ msgid "Change of address request confirmed" msgstr "ÁÖ¼Ò ¿äû º¯°æÀÌ È®ÀεǾú½À´Ï´Ù." #: Mailman/Cgi/confirm.py:571 +#, fuzzy msgid "" " You have successfully changed your address on the %(listname)s\n" " mailing list from %(oldaddr)s to %(newaddr)s. " @@ -1839,6 +1869,7 @@ msgid "globally" msgstr "ÀüÁ¦ÀûÀ¸·Î" #: Mailman/Cgi/confirm.py:605 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " change of address request for the mailing list %(listname)s. " @@ -1899,6 +1930,7 @@ msgid "Sender discarded message via web." msgstr "º¸³½À̰¡ À¥À» ÅëÇØ ¸Þ¼¼Áö¸¦ º¸·È½À´Ï´Ù." #: Mailman/Cgi/confirm.py:674 +#, fuzzy msgid "" "The held message with the Subject:\n" " header %(subject)s could not be found. The most " @@ -1917,6 +1949,7 @@ msgid "Posted message canceled" msgstr "¹è´ÞµÈ ¸Þ¼¼Áö°¡ Ãë¼ÒµÇ¾ú½À´Ï´Ù." #: Mailman/Cgi/confirm.py:685 +#, fuzzy msgid "" " You have successfully canceled the posting of your message with\n" " the Subject: header %(subject)s to the mailing list\n" @@ -1936,6 +1969,7 @@ msgid "" msgstr "" #: Mailman/Cgi/confirm.py:735 +#, fuzzy msgid "" "Your confirmation is required in order to cancel the\n" " posting of your message to the mailing list %(listname)s:\n" @@ -1981,6 +2015,7 @@ msgid "Membership re-enabled." msgstr "ȸ¿øÀ¸·Î ÀçȰµ¿Çϱâ." #: Mailman/Cgi/confirm.py:798 +#, fuzzy msgid "" " You have successfully re-enabled your membership in the\n" " %(listname)s mailing list. You can now not available" msgstr "ÀÌ¿ë ÇÒ ¼ö ¾ø½À´Ï´Ù." #: Mailman/Cgi/confirm.py:846 +#, fuzzy msgid "" "Your membership in the %(realname)s mailing list is\n" " currently disabled due to excessive bounces. Your confirmation is\n" @@ -2112,8 +2149,9 @@ msgid "You are not authorized to create new mailing lists" msgstr "´ç½ÅÀº »õ·Î¿î ¸ÞÀϸµ ¸®½ºÆ®¸¦ »ý¼ºÇÒ ¼ö ¾ø½À´Ï´Ù." #: Mailman/Cgi/create.py:182 +#, fuzzy msgid "Unknown virtual host: %(safehostname)s" -msgstr "" +msgstr "´ç½ÅÀÇ »õ·Î¿î ¸ÞÀϸµ ¸®½ºÆ® : %(listname)s" #: Mailman/Cgi/create.py:218 bin/newlist:219 #, fuzzy @@ -2121,6 +2159,7 @@ msgid "Bad owner email address: %(s)s" msgstr "À߸øµÈ ¼ÒÀ¯ÁÖ E¸ÞÀÏ ÁÖ¼Ò : %(s)s" #: Mailman/Cgi/create.py:223 bin/newlist:182 bin/newlist:223 +#, fuzzy msgid "List already exists: %(listname)s" msgstr "¸ÞÀϸµ ¸®½ºÆ® À̸§ÀÌ ÀÌ¹Ì Á¸ÀçÇÕ´Ï´Ù: %(listname)s" @@ -2138,6 +2177,7 @@ msgstr "" "Æ® °ü¸®ÀÚ¿¡°Ô ¿¬¶ôÇϽʽÿÀ." #: Mailman/Cgi/create.py:273 bin/newlist:265 +#, fuzzy msgid "Your new mailing list: %(listname)s" msgstr "´ç½ÅÀÇ »õ·Î¿î ¸ÞÀϸµ ¸®½ºÆ® : %(listname)s" @@ -2146,6 +2186,7 @@ msgid "Mailing list creation results" msgstr "¸ÞÀϸµ ¸®½ºÆ® »ý¼º °á°ú¹°" #: Mailman/Cgi/create.py:288 +#, fuzzy msgid "" "You have successfully created the mailing list\n" " %(listname)s and notification has been sent to the list owner\n" @@ -2168,6 +2209,7 @@ msgid "Create another list" msgstr "´Ù¸¥ ¸ÞÀϸµ ¸®½ºÆ® »ý¼ºÇϱâ" #: Mailman/Cgi/create.py:312 +#, fuzzy msgid "Create a %(hostname)s Mailing List" msgstr "%(hostname)s ¸ÞÀϸµ ¸®½ºÆ® »ý¼ºÇϱâ" @@ -2251,6 +2293,7 @@ msgid "" msgstr "" #: Mailman/Cgi/create.py:432 +#, fuzzy msgid "" "Initial list of supported languages.

                    Note that if you do not\n" " select at least one initial language, the list will use the server\n" @@ -2355,6 +2398,7 @@ msgid "List name is required." msgstr "¸®½ºÆ® À̸§ÀÌ ÇÊ¿äÇÕ´Ï´Ù." #: Mailman/Cgi/edithtml.py:154 +#, fuzzy msgid "%(realname)s -- Edit html for %(template_info)s" msgstr "%(realname)s -- %(template_info)s HTML ¼öÁ¤Çϱâ" @@ -2363,10 +2407,12 @@ msgid "Edit HTML : Error" msgstr "HTML ¼öÁ¤ : ¿¡·¯" #: Mailman/Cgi/edithtml.py:161 +#, fuzzy msgid "%(safetemplatename)s: Invalid template" msgstr "%(safetemplatename)s: À߸øµÈ ÅÛÇø´" #: Mailman/Cgi/edithtml.py:166 Mailman/Cgi/edithtml.py:167 +#, fuzzy msgid "%(realname)s -- HTML Page Editing" msgstr "%(realname)s -- HTML ÆäÀÌÁö ¼öÁ¤Çϱâ" @@ -2425,10 +2471,12 @@ msgid "HTML successfully updated." msgstr "HTML ÀÌ ¼º°øÀûÀ¸·Î ¾÷µ¥ÀÌÆ® µÇ¾ú½À´Ï´Ù." #: Mailman/Cgi/listinfo.py:89 +#, fuzzy msgid "%(hostname)s Mailing Lists" msgstr "%(hostname)s ¸ÞÀϸµ ¸®½ºÆ®µé" #: Mailman/Cgi/listinfo.py:127 +#, fuzzy msgid "" "

                    There currently are no publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s." @@ -2437,6 +2485,7 @@ msgstr "" " ¸ÞÀϸµ ¸®½ºÆ®°¡ ¾ø½À´Ï´Ù." #: Mailman/Cgi/listinfo.py:131 +#, fuzzy msgid "" "

                    Below is a listing of all the public mailing lists on\n" " %(hostname)s. Click on a list name to get more information " @@ -2515,6 +2564,7 @@ msgstr " #: Mailman/Cgi/options.py:183 Mailman/Cgi/options.py:240 #: Mailman/Cgi/options.py:264 +#, fuzzy msgid "No such member: %(safeuser)s." msgstr "%(safeuser)s ¶ó´Â »ç¿ëÀÚ°¡ ¾ø½À´Ï´Ù." @@ -2620,6 +2670,7 @@ msgid "Illegal email address provided" msgstr "ÀÌ»óÇÑ Çü½ÄÀÇ E¸ÞÀÏ ÁÖ¼Ò¸¦ Áּ̽À´Ï´Ù." #: Mailman/Cgi/options.py:501 +#, fuzzy msgid "%(newaddr)s is already a member of the list." msgstr "%(newaddr)s ´Â ÀÌ¹Ì ¸®½ºÆ®ÀÇ È¸¿øÀÔ´Ï´Ù." @@ -2701,6 +2752,7 @@ msgstr "" "´ç½ÅÀº ±Û°ü¸®ÀÚÀÇ °áÁ¤¿¡ ´ëÇÑ °øÁö¸¦ E¸ÞÀÏ·Î ¹ÞÀ¸½Ç ¼ö ÀÖ½À´Ï´Ù." #: Mailman/Cgi/options.py:623 +#, fuzzy msgid "" "You have been successfully unsubscribed from the\n" " mailing list %(fqdn_listname)s. If you were receiving digest\n" @@ -2801,6 +2853,7 @@ msgid "No topics defined" msgstr "ÁÖÁ¦°¡ Á¤ÀÇµÈ °ÍÀÌ ¾ø½À´Ï´Ù." #: Mailman/Cgi/options.py:940 +#, fuzzy msgid "" "\n" "You are subscribed to this list with the case-preserved address\n" @@ -2811,6 +2864,7 @@ msgstr "" "´Ù." #: Mailman/Cgi/options.py:956 +#, fuzzy msgid "%(realname)s list: member options login page" msgstr "%(realname)s ¸®½ºÆ®: ȸ¿ø ¼³Á¤ ·Î±ä ÆäÀÌÁö" @@ -2898,6 +2952,7 @@ msgid "" msgstr "<ºüÁü>" #: Mailman/Cgi/options.py:1140 +#, fuzzy msgid "Requested topic is not valid: %(topicname)s" msgstr "¿äûÇÑ ÁÖÁ¦°¡ Ʋ¸³´Ï´Ù: %(topicname)s" @@ -2926,6 +2981,7 @@ msgid "Private archive - \"./\" and \"../\" not allowed in URL." msgstr "" #: Mailman/Cgi/private.py:109 +#, fuzzy msgid "Private Archive Error - %(msg)s" msgstr "ºñ°ø°³ ÀúÀå¼Ò ¿¡·¯ - %(msg)s" @@ -2963,6 +3019,7 @@ msgid "Mailing list deletion results" msgstr "¸ÞÀϸµ ¸®½ºÆ® »èÁ¦ °á°ú" #: Mailman/Cgi/rmlist.py:187 +#, fuzzy msgid "" "You have successfully deleted the mailing list\n" " %(listname)s." @@ -2977,6 +3034,7 @@ msgid "" msgstr "" #: Mailman/Cgi/rmlist.py:208 +#, fuzzy msgid "Permanently remove mailing list %(realname)s" msgstr "%(realname)s ¸ÞÀϸµ ¸®½ºÆ®¸¦ ¿µ¿øÈ÷ Áö¿ö¹ö¸®±â" @@ -3039,6 +3097,7 @@ msgid "Invalid options to CGI script" msgstr "CGI ½ºÅ©¸³Æ® Çü½ÄÀÌ À߸øµÇ¾ú½À´Ï´Ù." #: Mailman/Cgi/roster.py:118 +#, fuzzy msgid "%(realname)s roster authentication failed." msgstr "%(realname)s °¡ÀÔÀÚ ¸ñ·Ï º¸±â ÀÎÁõ ½ÇÆÐ" @@ -3106,6 +3165,7 @@ msgstr "" "À¸½Ç ¼ö ÀÖÀ» °ÍÀÔ´Ï´Ù." #: Mailman/Cgi/subscribe.py:262 +#, fuzzy msgid "" "The email address you supplied is banned from this\n" " mailing list. If you think this restriction is erroneous, please\n" @@ -3132,6 +3192,7 @@ msgstr "" "´Ù." #: Mailman/Cgi/subscribe.py:278 +#, fuzzy msgid "" "Confirmation from your email address is required, to prevent anyone from\n" "subscribing you without permission. Instructions are being sent to you at\n" @@ -3143,6 +3204,7 @@ msgstr "" "À» ¾Ë·Áµå¸³´Ï´Ù." #: Mailman/Cgi/subscribe.py:290 +#, fuzzy msgid "" "Your subscription request was deferred because %(x)s. Your request has " "been\n" @@ -3163,6 +3225,7 @@ msgid "Mailman privacy alert" msgstr "Mailman °³ÀÎ Á¤º¸ °æ°í" #: Mailman/Cgi/subscribe.py:316 +#, fuzzy msgid "" "An attempt was made to subscribe your address to the mailing list\n" "%(listaddr)s. You are already subscribed to this mailing list.\n" @@ -3199,6 +3262,7 @@ msgid "This list only supports digest delivery." msgstr "ÀÌ ¸®½ºÆ®´Â ¹­À½¹è´Þ¸¸ Áö¿øÇÕ´Ï´Ù." #: Mailman/Cgi/subscribe.py:344 +#, fuzzy msgid "You have been successfully subscribed to the %(realname)s mailing list." msgstr "´ç½ÅÀº %(realname)s ¸ÞÀϸµ ¸®½ºÆ®¿¡ ¼º°øÀûÀ¸·Î °¡ÀԵǼ̽À´Ï´Ù." @@ -3355,8 +3419,9 @@ msgstr "" "mailman@%(hostname)s ¿¡¼­ °ø°³ ¸ÞÀϸµ ¸®½ºÆ®´Â ¾Æ·¡¿Í °°½À´Ï´Ù." #: Mailman/Commands/cmd_lists.py:66 +#, fuzzy msgid "%(i)3d. List name: %(realname)s" -msgstr "" +msgstr "¸ÞÀϸµ ¸®½ºÆ® À̸§Àº \"@\" ¸¦ Æ÷ÇÔÇÏ¸é ¾ÈµË´Ï´Ù. : %(listname)s" #: Mailman/Commands/cmd_lists.py:67 msgid " Description: %(description)s" @@ -3523,8 +3588,9 @@ msgid "on" msgstr "¾øÀ½" #: Mailman/Commands/cmd_set.py:154 +#, fuzzy msgid " ack %(onoff)s" -msgstr "" +msgstr "´ç½ÅÀº ÀÌ¹Ì ¹­À½¹è´Þ(Digest) ¼³Á¤ÀÌ µÇ¾î ÀÖÁö ¾Ê½À´Ï´Ù." #: Mailman/Commands/cmd_set.py:160 #, fuzzy @@ -3572,20 +3638,24 @@ msgid " %(status)s (%(how)s on %(date)s)" msgstr "" #: Mailman/Commands/cmd_set.py:192 +#, fuzzy msgid " myposts %(onoff)s" -msgstr "" +msgstr "´ç½ÅÀº ÀÌ¹Ì ¹­À½¹è´Þ(Digest) ¼³Á¤ÀÌ µÇ¾î ÀÖÁö ¾Ê½À´Ï´Ù." #: Mailman/Commands/cmd_set.py:195 +#, fuzzy msgid " hide %(onoff)s" -msgstr "" +msgstr "´ç½ÅÀº ÀÌ¹Ì ¹­À½¹è´Þ(Digest) ¼³Á¤ÀÌ µÇ¾î ÀÖÁö ¾Ê½À´Ï´Ù." #: Mailman/Commands/cmd_set.py:199 +#, fuzzy msgid " duplicates %(onoff)s" -msgstr "" +msgstr "´ç½ÅÀº ÀÌ¹Ì ¹­À½¹è´Þ(Digest) ¼³Á¤ÀÌ µÇ¾î ÀÖÁö ¾Ê½À´Ï´Ù." #: Mailman/Commands/cmd_set.py:203 +#, fuzzy msgid " reminders %(onoff)s" -msgstr "" +msgstr "´ç½ÅÀº ÀÌ¹Ì ¹­À½¹è´Þ(Digest) ¼³Á¤ÀÌ µÇ¾î ÀÖÁö ¾Ê½À´Ï´Ù." #: Mailman/Commands/cmd_set.py:224 #, fuzzy @@ -3664,14 +3734,16 @@ msgid "" msgstr "" #: Mailman/Commands/cmd_subscribe.py:62 +#, fuzzy msgid "Bad digest specifier: %(arg)s" -msgstr "" +msgstr "´ÙÀÌÁ¦½ºÆ® ȸ¿ø ¿É¼Ç - ¹­À½ ¸ÞÀÏ ¹ß¼Û Èñ¸Á ȸ¿ø" #: Mailman/Commands/cmd_subscribe.py:92 msgid "No valid address found to subscribe" msgstr "" #: Mailman/Commands/cmd_subscribe.py:113 +#, fuzzy msgid "" "The email address you supplied is banned from this mailing list.\n" "If you think this restriction is erroneous, please contact the list\n" @@ -3976,6 +4048,7 @@ msgid "Chinese (Taiwan)" msgstr "" #: Mailman/Deliverer.py:53 +#, fuzzy msgid "" "Note: Since this is a list of mailing lists, administrative\n" "notices like the password reminder will be sent to\n" @@ -3989,14 +4062,17 @@ msgid " (Digest mode)" msgstr " (¹­À½¹è´Þ ¸ðµå)" #: Mailman/Deliverer.py:79 +#, fuzzy msgid "Welcome to the \"%(realname)s\" mailing list%(digmode)s" msgstr "\"%(realname)s\" ¸ÞÀϸµ ¸®½ºÆ® (%(digmode)s)¿¡ ¿À½Å°É ȯ¿µÇÕ´Ï´Ù." #: Mailman/Deliverer.py:89 +#, fuzzy msgid "You have been unsubscribed from the %(realname)s mailing list" msgstr "%(realname)s ¸ÞÀϸµ ¸®½ºÆ®¿¡¼­ Å»ÅðµÇ¼Ì½À´Ï´Ù." #: Mailman/Deliverer.py:116 +#, fuzzy msgid "%(listfullname)s mailing list reminder" msgstr "%(listfullname)s ¸ÞÀϸµ ¸®½ºÆ® ÆÐ½º¿öµå ¸ÞÀÏ" @@ -4223,8 +4299,8 @@ msgid "" " membership.\n" "\n" "

                    You can control both the\n" -" number\n" +" number\n" " of reminders the member will receive and the\n" " ¿©·¯ºÐÀº ȸ¿øµéÀÌ ¹ÞÀ» ±ÛÀÇ ¼ö¸¦ Á¦¾îÇÒ ¼ö ÀÖÀ¸¸ç, ¾ðÁ¦¸¶´Ù º¸³¾ " -"Áö¿¡ ´ëÇØ Á¦¾î ÇÒ ¼ö ÀÖ½À´Ï´Ù.

                    ±×¸®°í ´Ù¸¥ Á¤¿äÇÑ ¼³Á¤ º¯¼ö°¡ Àִµ¥ " -"ƯÁ¤ ½Ã°£ ÈÄ¿¡ (ȸ¿øÀ¸·Î ºÎÅÍ ´õ ÀÌ»ó ¹Ù¿î½º ±ÛÀ» ¹ÞÁö ¾Ê´Â µ¿¾È) ¹Ù¿î½º Á¤º¸" -"´Â \n" +"bounce_you_are_disabled_warnings\">±ÛÀÇ ¼ö¸¦ Á¦¾îÇÒ ¼ö ÀÖÀ¸¸ç, ¾ðÁ¦¸¶´Ù " +"º¸³¾ Áö¿¡ ´ëÇØ Á¦¾î ÇÒ ¼ö ÀÖ½À´Ï´Ù.

                    ±×¸®°í ´Ù¸¥ Á¤¿äÇÑ ¼³Á¤ º¯¼ö°¡ ÀÖ" +"´Âµ¥ ƯÁ¤ ½Ã°£ ÈÄ¿¡ (ȸ¿øÀ¸·Î ºÎÅÍ ´õ ÀÌ»ó ¹Ù¿î½º ±ÛÀ» ¹ÞÁö ¾Ê´Â µ¿¾È) ¹Ù¿î" +"½º Á¤º¸´Â \n" " °í·Á´ë»ó¿¡¼­ ¸Ö¾îÁö°í ¹ö·ÁÁö°Ô µË´Ï´Ù. ÀÌ °ª°ú Á¡¼ö ¹®ÅÎ À» " "ÀûÀýÈ÷ Á¶ÀýÇÔÀ¸·Î½á ¿©·¯ºÐÀº ¾ó¸¶³ª »¡¸® ¹Ù¿î½º¸¦ ÀÐÀ¸Å°´Â ȸ¿øÀ» Ȱµ¿±ÝÁö½Ã" "ųÁö¸¦ Á¦¾îÇÒ ¼ö ÀÖ½À´Ï´Ù. ´ç½ÅÀº ¶ÇÇÑ ¾ðÁ¦³ª¸¶ º¸³¾ Áö, Æ®·¡ÇÈ ±Ç¼ö¸¦ ÃÖÀû" @@ -4385,8 +4461,8 @@ msgid "" " No those messages too will get discarded. You may " "want\n" " to set up an\n" -" autoresponse\n" +" autoresponse\n" " message for email to the -owner and -admin address." msgstr "" "Mailman ÀÇ ¹Ù¿î½º ŽÁö°¡ ºñ·Ï ½ÇÇàµÇ°í ÀÖÁö¸¸ ¼¼»óÀÇ ¸ðµç ¹Ù¿î½º Çü½ÄÀ» ŽÁö" @@ -4463,6 +4539,7 @@ msgstr "" "°Ô ¾Ë¸®´Â ¸ÞÀÏÀº ¿©ÀüÈ÷ »ý¼ºÇÏ¿© ȸ¿ø¿¡°Ô º¸³»Áý´Ï´Ù." #: Mailman/Gui/Bounce.py:194 +#, fuzzy msgid "" "Bad value for %(property)s: %(val)s" @@ -4561,8 +4638,8 @@ msgid "" "Use this option to remove each message attachment that does\n" " not have a matching content type. Requirements and formats " "are\n" -" exactly like filter_mime_types.\n" "\n" "

                    Note: if you add entries to this list but don't add\n" @@ -4744,6 +4821,7 @@ msgstr "" "º¸³»°Ô ÇÒ±î¿ä?" #: Mailman/Gui/Digest.py:145 +#, fuzzy msgid "" "The next digest will be sent as volume\n" " %(volume)s, number %(number)s" @@ -4758,14 +4836,17 @@ msgid "There was no digest to send." msgstr "º¸³¾ ¹­À½¹è´ÞÀÌ ¾ø½À´Ï´Ù." #: Mailman/Gui/GUIBase.py:173 +#, fuzzy msgid "Invalid value for variable: %(property)s" msgstr "À߸øµÈ °ªÀº º¯¼ö°ª : %(property)s" #: Mailman/Gui/GUIBase.py:178 +#, fuzzy msgid "Bad email address for option %(property)s: %(error)s" msgstr "%(property)s ¿É¼ÇÀ» À§ÇÑ À߸øµÈ E¸ÞÀÏ ÁÖ¼Ò : %(error)s" #: Mailman/Gui/GUIBase.py:204 +#, fuzzy msgid "" "The following illegal substitution variables were\n" " found in the %(property)s string:\n" @@ -4779,6 +4860,7 @@ msgstr "" "¾ÈÇÒ ¼ö ÀÖ½À´Ï´Ù. " #: Mailman/Gui/GUIBase.py:218 +#, fuzzy msgid "" "Your %(property)s string appeared to\n" " have some correctable problems in its new value.\n" @@ -4873,8 +4955,8 @@ msgid "" "

                    In order to split the list ownership duties into\n" " administrators and moderators, you must\n" " set a separate moderator password,\n" -" and also provide the email\n" +" and also provide the email\n" " addresses of the list moderators. Note that the field you\n" " are changing here specifies the list administrators." msgstr "" @@ -5173,13 +5255,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5224,8 +5306,8 @@ msgstr "Explicit msgid "" "This is the address set in the Reply-To: header\n" " when the reply_goes_to_list\n" +" href=\"?VARHELP=general/" +"reply_goes_to_list\">reply_goes_to_list\n" " option is set to Explicit address.\n" "\n" "

                    There are many reasons not to introduce or override the\n" @@ -5233,13 +5315,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5266,16 +5348,16 @@ msgstr "" "°¡ ÀÖ½À´Ï´Ù. Çϳª´Â ¸î¸î º¸³½ÀÌ´Â ±×µéÀÇ È¸½Å ÁÖ¼Ò·Î ÆíÁö¸¦ ¿î¹ÝÇϱâ À§ÇØ ±×" "µé ÀÚ½ÅÀÇ Reply-To: ¿¡ ÀÇÁ¸À» Çϰí ÀÖ½À´Ï´Ù. ´Ù¸¥ °ÍÀº Reply-To:¸¦ ¼öÁ¤ÇÏ´Â " "°ÍÀº °³ÀÎÀûÀΠȸ½Å À» º¸³»´Âµ¥, ¾î·Á¿òÀ» ¸¸µì´Ï´Ù. ÀÌ ¹®Á¦¿¡ ´ëÇÑ ÀϹÝÀûÀÎ Åä" -"·Ð¿¡ ´ëÇÑ `Reply-To' Munging Considered Harmful ±ÛÀ» º¸½Ã±â ¹Ù¶ø´Ï´Ù. ÀÌ ±ÛÀÇ ¹Ý" -"´ë ÀǰßÀÎ Reply-To Munging Considered Useful ±Ûµµ º¸½Ã±â ¹Ù¶ø´Ï´Ù.

                    ¸î¸î ¸ÞÀÏ" -"¸µ ¸®½ºÆ®´Â Åä·ÐÀÌ ÀϾ ¼ö ÀÖÀ» °æ¿ì ±Û¾²´Â ±ÇÇÑÀ» Á¦ÇÑÇÒ ¼ö ÀÖ´Â ±â´ÉÀ» °¡" -"Áö°í ÀÖ½À´Ï´Ù. ¿¹¸¦ µé¸é 'patches' ȤÀº 'checkin' ¸®½ºÆ®´Â ¼ÒÇÁÆ®¿þ¾î ¹öÀü °ü" -"¸® ½Ã½ºÅÛ¿¡ ÀÇÇØ ¼ÒÇÁÇÁ¿þ¾îÀÇ º¯È­Á¡À» º¸³»°Ô µË´Ï´Ù. ÇÏÁö¸¸ ÀÌ º¯È­¿¡ ´ëÇÑ " -"Åä·ÐÀº °³¹ßÀÚ ¸ÞÀϸµ ¸®½ºÆ®¿¡¼­ ÀϾ´Ï´Ù. ±×·¯ÇÑ ÇüÅÂÀÇ ¸ÞÀϸµ ¸®½ºÆ®¸¦ Áö" -"¿øÇϱâ À§ÇØ Explicit ÁÖ¼Ò ¿Í Reply-To: ·Î ¼¼ÆÃÇÏ´Â °ÍÀº ÁÁÀº »ý°¢ ÀÎ°Í °°½À´Ï" -"´Ù." +"·Ð¿¡ ´ëÇÑ `Reply-To' Munging Considered Harmful ±ÛÀ» º¸½Ã±â ¹Ù¶ø´Ï´Ù. ÀÌ ±Û" +"ÀÇ ¹Ý´ë ÀǰßÀÎ Reply-To Munging Considered Useful ±Ûµµ º¸½Ã±â ¹Ù¶ø´Ï´Ù.

                    ¸î¸î " +"¸ÞÀϸµ ¸®½ºÆ®´Â Åä·ÐÀÌ ÀϾ ¼ö ÀÖÀ» °æ¿ì ±Û¾²´Â ±ÇÇÑÀ» Á¦ÇÑÇÒ ¼ö ÀÖ´Â ±â´É" +"À» °¡Áö°í ÀÖ½À´Ï´Ù. ¿¹¸¦ µé¸é 'patches' ȤÀº 'checkin' ¸®½ºÆ®´Â ¼ÒÇÁÆ®¿þ¾î ¹ö" +"Àü °ü¸® ½Ã½ºÅÛ¿¡ ÀÇÇØ ¼ÒÇÁÇÁ¿þ¾îÀÇ º¯È­Á¡À» º¸³»°Ô µË´Ï´Ù. ÇÏÁö¸¸ ÀÌ º¯È­¿¡ " +"´ëÇÑ Åä·ÐÀº °³¹ßÀÚ ¸ÞÀϸµ ¸®½ºÆ®¿¡¼­ ÀϾ´Ï´Ù. ±×·¯ÇÑ ÇüÅÂÀÇ ¸ÞÀϸµ ¸®½ºÆ®" +"¸¦ Áö¿øÇϱâ À§ÇØ Explicit ÁÖ¼Ò ¿Í Reply-To: ·Î ¼¼ÆÃÇÏ´Â °ÍÀº ÁÁÀº »ý°¢ ÀÎ°Í " +"°°½À´Ï´Ù." #: Mailman/Gui/General.py:305 msgid "Umbrella list settings" @@ -5323,16 +5405,16 @@ msgid "" " member list addresses, but rather to the owner of those member\n" " lists. In that case, the value of this setting is appended to\n" " the member's account name for such notices. `-owner' is the\n" -" typical choice. This setting has no effect when \"umbrella_list" -"\"\n" +" typical choice. This setting has no effect when " +"\"umbrella_list\"\n" " is \"No\"." msgstr "" -"ÀÌ ¸ÞÀϸµ ¸®½ºÆ®°¡ ȸ¿øÀ¸·Î ´Ù¸¥ ¸ÞÀϸµ ¸®½ºÆ®¸¦ °¡Áö°í ÀÖ´Â \"umbrella_list" -"\" ·Î ¼¼ÆÃµÇ¾î ÀÖ´Ù¸é È®ÀÎ ±×¸®°í ºñ¹Ð¹øÈ£ °ü·Ã ¸ÞÀϰú °°Àº °ü¸® °øÁö ³»¿ëÀº " -"ȸ¿ø ¸ñ·ÏÀÇ ÁÖ¼Ò·Î º¸³»Áú ÇÊ¿ä´Â ¾ø½À´Ï´Ù. ÇÏÁö¸¸ ÀúÂÊ È¸¿ø ¸®½ºÆ®ÀÇ ÁÖÀÎÀå¿¡" -"°Ô º¸³»´Â °ÍÀÌ ³´½À´Ï´Ù. ±×·¯ÇÑ °æ¿ì ÀÌ ¼³Á¤ÀÇ °ªÀº ȸ¿øÀÇ °èÁ¤¿¡ Ãß°¡µÇ¾î Áý" -"´Ï´Ù. '-owner' ´Â ±âº»ÀûÀÎ °ªÀ̸ç ÀÌ ¼³Á¤Àº \"umbrella_list\" ¼³Á¤ÀÌ \"¾Æ´Ï¿ä" -"\"·Î ¼³Á¤µÇ¾î ÀÖ´Ù¸é ¿µÇâÀ» ¹ÌÄ¡Áö ¾Ê½À´Ï´Ù." +"ÀÌ ¸ÞÀϸµ ¸®½ºÆ®°¡ ȸ¿øÀ¸·Î ´Ù¸¥ ¸ÞÀϸµ ¸®½ºÆ®¸¦ °¡Áö°í ÀÖ´Â " +"\"umbrella_list\" ·Î ¼¼ÆÃµÇ¾î ÀÖ´Ù¸é È®ÀÎ ±×¸®°í ºñ¹Ð¹øÈ£ °ü·Ã ¸ÞÀϰú °°Àº °ü" +"¸® °øÁö ³»¿ëÀº ȸ¿ø ¸ñ·ÏÀÇ ÁÖ¼Ò·Î º¸³»Áú ÇÊ¿ä´Â ¾ø½À´Ï´Ù. ÇÏÁö¸¸ ÀúÂÊ È¸¿ø ¸®" +"½ºÆ®ÀÇ ÁÖÀÎÀå¿¡°Ô º¸³»´Â °ÍÀÌ ³´½À´Ï´Ù. ±×·¯ÇÑ °æ¿ì ÀÌ ¼³Á¤ÀÇ °ªÀº ȸ¿øÀÇ °è" +"Á¤¿¡ Ãß°¡µÇ¾î Áý´Ï´Ù. '-owner' ´Â ±âº»ÀûÀÎ °ªÀ̸ç ÀÌ ¼³Á¤Àº " +"\"umbrella_list\" ¼³Á¤ÀÌ \"¾Æ´Ï¿ä\"·Î ¼³Á¤µÇ¾î ÀÖ´Ù¸é ¿µÇâÀ» ¹ÌÄ¡Áö ¾Ê½À´Ï´Ù." #: Mailman/Gui/General.py:335 msgid "Send monthly password reminders?" @@ -6130,6 +6212,7 @@ msgstr "" "½á ÀÎÇØ »ç¿ëÀÚ°¡ ¹ÞÀ» ¼ö ÀÖ´Â ¿µÇâÀ» ¸·°Ô ÇÕ´Ï´Ù." #: Mailman/Gui/Privacy.py:104 +#, fuzzy msgid "" "This section allows you to configure subscription and\n" " membership exposure policy. You can also control whether this\n" @@ -6138,8 +6221,8 @@ msgid "" " separate archive-related privacy settings." msgstr "" "ȸ¿ø°ú ºñȸ¿øÀ» ±¸ºÐÇϰí, ½ºÆÐ¸Ó¸¦ ¸·´Â ¹æ¹ý°ú °°Àº Á¢±Ù Á¦¾î Á¤Ã¥À» ¼³Á¤ÇÏ" -"´Â ºÎºÐÀÔ´Ï´Ù. ÀúÀå¼Ò °³ÀÎ Á¤º¸ º¸È£ Á¤Ã¥À» ¼³Á¤ÇϽǷÁ¸é (ÀúÀå¼Ò ¼³Á¤À» º¸½Ã±â ¹Ù¶ø´Ï´Ù.)" +"´Â ºÎºÐÀÔ´Ï´Ù. ÀúÀå¼Ò °³ÀÎ Á¤º¸ º¸È£ Á¤Ã¥À» ¼³Á¤ÇϽǷÁ¸é (ÀúÀå¼Ò ¼³Á¤À» º¸½Ã±â ¹Ù¶ø´Ï´Ù.)" #: Mailman/Gui/Privacy.py:110 msgid "Subscribing" @@ -6310,8 +6393,8 @@ msgid "" " either individually or as a group. Any\n" " posting from a non-member who is not explicitly accepted,\n" " rejected, or discarded, will have their posting filtered by the\n" -" general\n" +" general\n" " non-member rules.\n" "\n" "

                    In the text boxes below, add one address per line; start the\n" @@ -6341,10 +6424,10 @@ msgstr "" "°ÅÀýµÇ°Å³ª, ȤÀº ¹ö·ÁÁý´Ï´Ù.\n" "\n" "

                    ¾Æ·¡ÀÇ ÅØ½ºÆ® ¹Ú½º¿¡¼­ ÁÙ(line)¸¶´Ù ÇϳªÀÇ ÁÖ¼Ò¸¦ ³ÖÀ» ¼ö ÀÖÀ¸¸ç ^ ¹®ÀÚ" -"·Î ½ÃÀÛÇÏ´Â ÁÙÀº Python Á¤±Ô Ç¥Çö½ÄÀ» ³ªÅ¸³À´Ï´Ù. ¹é½½·¡½¬¸¦ ³ÖÀ½À¸·Î½á Python ÀÇ raw " -"¹®ÀÚ¿­À» »ç¿ëÇÒ ¼ö ÀÖ½À´Ï´Ù.(¿¹¸¦ µé¾î ´ç½ÅÀº ÀϹÝÀûÀ¸·Î ÇϳªÀÇ ¹é½½·¡½¬¸¦ »ç" -"¿ëÇÕ´Ï´Ù.)

                    ºñÁ¤±Ô½Ä ÆÐÅÏÀÌ Ç×»ó óÀ½¿¡ ¿Àµµ·Ï ÇϽʽÿÀ." +"·Î ½ÃÀÛÇÏ´Â ÁÙÀº Python Á¤±Ô Ç¥Çö½ÄÀ» ³ªÅ¸³À´Ï´Ù. ¹é½½·¡½¬¸¦ ³ÖÀ½À¸·Î½á Python ÀÇ " +"raw ¹®ÀÚ¿­À» »ç¿ëÇÒ ¼ö ÀÖ½À´Ï´Ù.(¿¹¸¦ µé¾î ´ç½ÅÀº ÀϹÝÀûÀ¸·Î ÇϳªÀÇ ¹é½½·¡½¬" +"¸¦ »ç¿ëÇÕ´Ï´Ù.)

                    ºñÁ¤±Ô½Ä ÆÐÅÏÀÌ Ç×»ó óÀ½¿¡ ¿Àµµ·Ï ÇϽʽÿÀ." #: Mailman/Gui/Privacy.py:213 msgid "Member filters" @@ -6355,6 +6438,7 @@ msgid "By default, should new list member postings be moderated?" msgstr "±âº»ÀûÀ¸·Î »õ·Î¿î ¸®½ºÆ® ȸ¿øÀÇ ±ÛÀº ±Û°ü¸® µÇµµ·Ï ÇϰڽÀ´Ï±î?" #: Mailman/Gui/Privacy.py:218 +#, fuzzy msgid "" "Each list member has a moderation flag which says\n" " whether messages from the list member can be posted directly " @@ -6402,8 +6486,8 @@ msgid "" "If a member posts this many times, within a period of time\n" " the member is automatically moderated. Use 0 to disable. " "See\n" -" member_verbosity_interval for details on the time " "period.\n" "\n" @@ -7072,8 +7156,8 @@ msgid "" "

                    The body of the message can also be optionally scanned for\n" " Subject: and Keywords: headers, as\n" " specified by the topics_bodylines_limit\n" +" href=\"?VARHELP=topics/" +"topics_bodylines_limit\">topics_bodylines_limit\n" " configuration variable." msgstr "" "ÁÖÁ¦ °É·¯³»±â´Â ´ç½ÅÀÌ ¾Æ·¡¿¡¼­ Á¤ÇÑ href=\"https://docs.python.org/2/" @@ -7084,9 +7168,9 @@ msgstr "" "ÇÑ Æ¯Á¤ ÁÖÁ¦ ¼ÒÄí¸®(ȤÀº ¼ÒÄí¸®µé) °ü·Ã ºÎºÐ¸¸ ¹ÞÀ» ¼ö ÀÖ½À´Ï´Ù. »ç¿ëÀÚ°¡ µî" "·ÏÇÑ ÁÖÁ¦ ¼ÒÄí¸®¿¡ ºÐ·ùµÇÁö ¾Ê´Â ¸Þ¼¼ÁöÀÇ °æ¿ì ¸®½ºÆ®·Î ¹è´ÞµÇÁö ¾Ê½À´Ï´Ù.

                    " "ÀÌ ±â´ÉÀº °³º°¹è´Þ¿¡¸¸ ÀÛµ¿Çϸç, ¹­À½ ¹è´Þ¿¡¼­´Â ÀÛµ¿ÇÏÁö ¾Ê½À´Ï´Ù.

                    ¸Þ¼¼Áö" -"ÀÇ º»¹®Àº ¶ÇÇÑ topics_bodylines_limit ¼³Á¤ º¯¼ö·Î Á¦¸ñ: ±×¸®°í Ű" -"¿öµå: Çì´õ¸¦ Æ÷ÇÔÇÏ¿© °Ë»öÇÒ ¼ö ÀÖ½À´Ï´Ù." +"ÀÇ º»¹®Àº ¶ÇÇÑ topics_bodylines_limit ¼³Á¤ º¯¼ö·Î Á¦¸ñ: ±×¸®°í Ű¿öµå: Çì´õ¸¦ Æ÷ÇÔÇÏ¿© °Ë»öÇÒ ¼ö ÀÖ½À´Ï´Ù." #: Mailman/Gui/Topics.py:72 msgid "How many body lines should the topic matcher scan?" @@ -7317,6 +7401,7 @@ msgid "%(listinfo_link)s list run by %(owner_link)s" msgstr "%(listinfo_link)s ´Â %(owner_link)s ´ÔÀÌ °ü¸®ÇÕ´Ï´Ù." #: Mailman/HTMLFormatter.py:57 +#, fuzzy msgid "%(realname)s administrative interface" msgstr "%(realname)s °ü¸®Çϱâ" @@ -7325,6 +7410,7 @@ msgid " (requires authorization)" msgstr " (ÀÎÁõÀÌ ÇÊ¿äÇÕ´Ï´Ù.)" #: Mailman/HTMLFormatter.py:61 +#, fuzzy msgid "Overview of all %(hostname)s mailing lists" msgstr "%(hostname)s »óÀÇ ¸ðµç ¸ÞÀϸµ ¸®½ºÆ® ¼Ò°³" @@ -7370,6 +7456,7 @@ msgid "the list administrator" msgstr "¸®½ºÆ® °ü¸®ÀÚ" #: Mailman/HTMLFormatter.py:157 +#, fuzzy msgid "" "

                    %(note)s\n" "\n" @@ -7385,6 +7472,7 @@ msgstr "" "´Ù. ¸¸¾à Áú¹®ÀÌ ÀÖ´Ù¸é %(mailto)s ¿¡°Ô ¿¬¶ôÇϽñ⠹ٶø´Ï´Ù." #: Mailman/HTMLFormatter.py:169 +#, fuzzy msgid "" "

                    We have received some recent bounces from your\n" " address. Your current bounce score is %(score)s out of " @@ -7401,6 +7489,7 @@ msgstr "" "¹Ù¶ø´Ï´Ù. ´ç½ÅÀÇ ¹Ù¿î½º Á¡¼ö´Â ¹®Á¦°¡ °íÃÄÁö¸é ÀÚµ¿À¸·Î ¸®¼ÂµË´Ï´Ù." #: Mailman/HTMLFormatter.py:181 +#, fuzzy msgid "" "(Note - you are subscribing to a list of mailing lists, so the %(type)s " "notice will be sent to the admin address for your membership, %(addr)s.)

                    " @@ -7445,6 +7534,7 @@ msgstr "" "ÀÇ °áÁ¤À» ´ãÀº ³»¿ëÀÌ °ü¸®ÀÚÀÇ °áÁ¤ ÈÄ ´ç½ÅÀÇ E¸ÞÀÏ ÁÖ¼Ò·Î º¸³»Áý´Ï´Ù." #: Mailman/HTMLFormatter.py:208 +#, fuzzy msgid "" "This is %(also)sa private list, which means that the\n" " list of members is not available to non-members." @@ -7453,6 +7543,7 @@ msgstr "" "º¼ ¼ö ¾ø½À´Ï´Ù." #: Mailman/HTMLFormatter.py:211 +#, fuzzy msgid "" "This is %(also)sa hidden list, which means that the\n" " list of members is available only to the list administrator." @@ -7461,6 +7552,7 @@ msgstr "" "ÀÖ½À´Ï´Ù. " #: Mailman/HTMLFormatter.py:214 +#, fuzzy msgid "" "This is %(also)sa public list, which means that the\n" " list of members list is available to everyone." @@ -7475,6 +7567,7 @@ msgid "" msgstr " (ÇÏÁö¸¸ ÁÖ¼Ò´Â ½ºÆÐ¸Óµé¿¡°Ô ½±°Ô ¾Ë·ÁÁöÁö ¾Êµµ·Ï ¼û°ÜÁý´Ï´Ù.) " #: Mailman/HTMLFormatter.py:222 +#, fuzzy msgid "" "

                    (Note that this is an umbrella list, intended to\n" " have only other mailing lists as members. Among other things,\n" @@ -7490,6 +7583,7 @@ msgid "either " msgstr " " #: Mailman/HTMLFormatter.py:256 +#, fuzzy msgid "" "To unsubscribe from %(realname)s, get a password reminder,\n" " or change your subscription options %(either)senter your " @@ -7587,6 +7681,7 @@ msgid "The current archive" msgstr "ÇöÀç ÀúÀå¼Ò" #: Mailman/Handlers/Acknowledge.py:59 +#, fuzzy msgid "%(realname)s post acknowledgement" msgstr "%(realname)s º¸³½µé È®ÀÎ" @@ -7599,6 +7694,7 @@ msgid "" msgstr "" #: Mailman/Handlers/CalcRecips.py:79 +#, fuzzy msgid "" "Your urgent message to the %(realname)s mailing list was not authorized for\n" "delivery. The original message as received by Mailman is attached.\n" @@ -7678,6 +7774,7 @@ msgid "Message may contain administrivia" msgstr "¸Þ¼¼Áö°¡ °ü¸® ¿äûÀ» Æ÷ÇÔÇϰí ÀÖ½À´Ï´Ù." #: Mailman/Handlers/Hold.py:84 +#, fuzzy msgid "" "Please do *not* post administrative requests to the mailing\n" "list. If you wish to subscribe, visit %(listurl)s or send a message with " @@ -7716,10 +7813,12 @@ msgid "Posting to a moderated newsgroup" msgstr "Moderator ¸®½ºÆ®¿¡ ±ÛÀ» º¸³»¼Ì½À´Ï´Ù." #: Mailman/Handlers/Hold.py:252 +#, fuzzy msgid "Your message to %(listname)s awaits moderator approval" msgstr "%(listname)s ¿¡ º¸³½ ¸Þ¼¼Áö°¡ °ü¸®ÀÚÀÇ ½ÂÀÎÀ» ±â´Ù¸®°í ÀÖ½À´Ï´Ù." #: Mailman/Handlers/Hold.py:271 +#, fuzzy msgid "%(listname)s post from %(sender)s requires approval" msgstr " %(sender)s ·Î ºÎÅÍ %(listname)s ¿¡ ¿Â ±ÛÀº ½ÂÀÎÀÌ ÇÊ¿äÇÕ´Ï´Ù." @@ -7817,6 +7916,7 @@ msgid "HTML attachment scrubbed and removed" msgstr "HTML ÷ºÎ ºÎºÐÀº ¾ø¾Ö ¹ö·È½À´Ï´Ù." #: Mailman/Handlers/Scrubber.py:234 Mailman/Handlers/Scrubber.py:259 +#, fuzzy msgid "" "An HTML attachment was scrubbed...\n" "URL: %(url)s\n" @@ -7900,6 +8000,7 @@ msgid "Message rejected by filter rule match" msgstr "" #: Mailman/Handlers/ToDigest.py:173 +#, fuzzy msgid "%(realname)s Digest, Vol %(volume)d, Issue %(issue)d" msgstr "%(realname)s ¹­À½¹è´Þ, ±Û %(volume)d, ¹øÈ£ %(issue)d" @@ -7936,6 +8037,7 @@ msgid "End of " msgstr "³¡ºÎºÐ --" #: Mailman/ListAdmin.py:309 +#, fuzzy msgid "Posting of your message titled \"%(subject)s\"" msgstr "\"%(subject)s\" Á¦¸ñÀÇ º¸³½±Û" @@ -7948,6 +8050,7 @@ msgid "Forward of moderated message" msgstr "±Û°ü¸®µÈ ¸Þ¼¼ÁöÀÇ Àü´Þ" #: Mailman/ListAdmin.py:405 +#, fuzzy msgid "New subscription request to list %(realname)s from %(addr)s" msgstr "%(addr)s ·Î ºÎÅÍÀÇ %(realname)s ¸®½ºÆ®¿¡ ´ëÇÑ »õ·Î¿î °¡ÀÔ ¿äû" @@ -7961,6 +8064,7 @@ msgid "via admin approval" msgstr "½ÂÀÎÀ» ±â´Ù¸®°Ô Çϱâ" #: Mailman/ListAdmin.py:466 +#, fuzzy msgid "New unsubscription request from %(realname)s by %(addr)s" msgstr "%(addr)s ÁÖ¼Ò·Î %(realname)s ·Î ºÎÅÍÀÇ »õ·Î¿î Å»Åð ¿äû" @@ -7973,10 +8077,12 @@ msgid "Original Message" msgstr "¿øº» ¸Þ¼¼Áö" #: Mailman/ListAdmin.py:526 +#, fuzzy msgid "Request to mailing list %(realname)s rejected" msgstr "%(realname)s ¸ÞÀϸµ ¸®½ºÆ®¿¡ ´ëÇÑ ¿äûÀÌ °ÅÀýµÇ¾ú½À´Ï´Ù." #: Mailman/MTA/Manual.py:66 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been created via the through-the-web\n" "interface. In order to complete the activation of this mailing list, the\n" @@ -8009,10 +8115,12 @@ msgid "## %(listname)s mailing list" msgstr " \"%(listname)s\" ¸ÞÀϸµ ¸®½ºÆ®" #: Mailman/MTA/Manual.py:99 +#, fuzzy msgid "Mailing list creation request for list %(listname)s" msgstr "%(listname)s ¸ÞÀϸµ ¸®½ºÆ® »ý¼º ¿äû" #: Mailman/MTA/Manual.py:113 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been removed via the through-the-web\n" "interface. In order to complete the de-activation of this mailing list, " @@ -8029,6 +8137,7 @@ msgstr "" "¾Æ·¡ Ç׸ñµéÀº /etc/aliases ÆÄÀÏ¿¡¼­ Á¦°ÅµÇ¾î¾ß ÇÒ ºÎºÐµéÀÔ´Ï´Ù:\n" #: Mailman/MTA/Manual.py:123 +#, fuzzy msgid "" "\n" "To finish removing your mailing list, you must edit your /etc/aliases (or\n" @@ -8044,14 +8153,17 @@ msgstr "" "## %(listname)s ¸ÞÀϸµ ¸®½ºÆ®" #: Mailman/MTA/Manual.py:142 +#, fuzzy msgid "Mailing list removal request for list %(listname)s" msgstr "%(listname)s ¸ÞÀϸµ ¸®½ºÆ® Á¦°Å ¿äû" #: Mailman/MTA/Postfix.py:442 +#, fuzzy msgid "checking permissions on %(file)s" msgstr "%(file)s ÆÄÀÏ¿¡ ´ëÇÑ ÆÛ¹Ì¼Ç °Ë»çÁß" #: Mailman/MTA/Postfix.py:452 +#, fuzzy msgid "%(file)s permissions must be 0664 (got %(octmode)s)" msgstr "%(file)s ÆÛ¹Ì¼ÇÀº 0664 ( %(octmode)s )¿©¾ß ÇÕ´Ï´Ù." @@ -8065,6 +8177,7 @@ msgid "(fixing)" msgstr "(¼öÁ¤Áß)" #: Mailman/MTA/Postfix.py:470 +#, fuzzy msgid "checking ownership of %(dbfile)s" msgstr "%(dbfile)s ÀÇ ¼ÒÀ¯ÁÖ¸¦ °Ë»çÁß" @@ -8076,6 +8189,7 @@ msgstr "" "´Ù." #: Mailman/MTA/Postfix.py:491 +#, fuzzy msgid "%(dbfile)s permissions must be 0664 (got %(octmode)s)" msgstr "%(dbfile)s ÆÛ¹Ì¼ÇÀº 0664 ( %(octmode)s )¿©¾ß ÇÕ´Ï´Ù." @@ -8090,14 +8204,17 @@ msgid "Your confirmation is required to leave the %(listname)s mailing list" msgstr "%(listname)s ¸ÞÀϸµ ¸®½ºÆ®¿¡¼­ Å»ÅðµÇ¼Ì½À´Ï´Ù." #: Mailman/MailList.py:999 Mailman/MailList.py:1492 +#, fuzzy msgid " from %(remote)s" msgstr "%(remote)s ºÎÅÍ" #: Mailman/MailList.py:1043 +#, fuzzy msgid "subscriptions to %(realname)s require moderator approval" msgstr "%(realname)s ¿¡ °¡ÀÔÇϱâ À§Çؼ­´Â ±Û°ü¸®ÀÚÀÇ ½ÂÀÎÀÌ ÇÊ¿äÇÕ´Ï´Ù." #: Mailman/MailList.py:1125 bin/add_members:299 +#, fuzzy msgid "%(realname)s subscription notification" msgstr "%(realname)s °¡ÀÔ °øÁö" @@ -8106,6 +8223,7 @@ msgid "unsubscriptions require moderator approval" msgstr "Å»ÅðÇϱâ´Â ±Û°ü¸®ÀÚÀÇ ½ÂÀÎÀÌ ÇÊ¿äÇÕ´Ï´Ù." #: Mailman/MailList.py:1166 +#, fuzzy msgid "%(realname)s unsubscribe notification" msgstr "%(realname)s Å»Åð °øÁö" @@ -8125,6 +8243,7 @@ msgid "via web confirmation" msgstr "À߸øµÈ È®ÀÎ ¹®ÀÚ¿­" #: Mailman/MailList.py:1396 +#, fuzzy msgid "subscriptions to %(name)s require administrator approval" msgstr "%(name)s ¿¡ °¡ÀÔÇϱâ À§Çؼ­´Â °ü¸®ÀÚÀÇ ½ÂÀÎÀÌ ÇÊ¿äÇÕ´Ï´Ù." @@ -8213,6 +8332,7 @@ msgid "Original message suppressed by Mailman site configuration\n" msgstr "" #: Mailman/htmlformat.py:675 +#, fuzzy msgid "Delivered by Mailman
                    version %(version)s" msgstr "Mailman ÀÌ ¹è´ÞÇÏ¿´½À´Ï´Ù.
                    ¹öÀü %(version)s" @@ -8374,20 +8494,23 @@ msgid "" msgstr "" #: bin/add_members:162 bin/add_members:172 +#, fuzzy msgid "Already a member: %(member)s" -msgstr "" +msgstr "ÀÌ¹Ì È¸¿øÀÔ´Ï´Ù." #: bin/add_members:178 msgid "Bad/Invalid email address: blank line" msgstr "" #: bin/add_members:180 +#, fuzzy msgid "Bad/Invalid email address: %(member)s" -msgstr "" +msgstr "À߸øµÈ/¾ø´Â E¸ÞÀÏ ÁÖ¼Ò" #: bin/add_members:182 +#, fuzzy msgid "Hostile address (illegal characters): %(member)s" -msgstr "" +msgstr "¾ÇÀÇÀûÀÎ ÁÖ¼ÒÀÔ´Ï´Ù. (Çã¿ëµÇÁö ¾Ê´Â ¹®ÀÚ¸¦ »ç¿ë>ÇÕ´Ï´Ù.)" #: bin/add_members:185 #, fuzzy @@ -8395,8 +8518,9 @@ msgid "Invited: %(member)s" msgstr "¸®½ºÆ® ȸ¿ø" #: bin/add_members:187 +#, fuzzy msgid "Subscribed: %(member)s" -msgstr "" +msgstr "¸®½ºÆ® ȸ¿ø" #: bin/add_members:237 msgid "Bad argument to -w/--welcome-msg: %(arg)s" @@ -8417,8 +8541,9 @@ msgstr "" #: bin/add_members:261 bin/config_list:110 bin/export.py:271 bin/find_member:97 #: bin/inject:91 bin/list_admins:90 bin/list_members:252 bin/sync_members:222 #: cron/bumpdigests:86 +#, fuzzy msgid "No such list: %(listname)s" -msgstr "" +msgstr "%(safelistname)s ¶ó´Â ¸ÞÀϸµ ¸®½ºÆ®°¡ Á¸ÀçÇÏÁö ¾Ê½À´Ï´Ù." #: bin/add_members:285 bin/change_pw:159 bin/check_db:114 bin/discard:83 #: bin/sync_members:244 bin/update:302 bin/update:323 bin/update:577 @@ -8480,10 +8605,11 @@ msgid "listname is required" msgstr "" #: bin/arch:143 bin/change_pw:107 bin/config_list:257 +#, fuzzy msgid "" "No such list \"%(listname)s\"\n" "%(e)s" -msgstr "" +msgstr "%(safelistname)s ¶ó´Â ¸ÞÀϸµ ¸®½ºÆ®°¡ Á¸ÀçÇÏÁö ¾Ê½À´Ï´Ù." #: bin/arch:168 msgid "Cannot open mbox file %(mbox)s: %(msg)s" @@ -8575,12 +8701,14 @@ msgid "Empty list passwords are not allowed" msgstr "" #: bin/change_pw:181 +#, fuzzy msgid "New %(listname)s password: %(notifypassword)s" -msgstr "" +msgstr "¸ÞÀϸµ ¸®½ºÆ® Ãʱ⠺ñ¹Ð¹øÈ£:" #: bin/change_pw:190 +#, fuzzy msgid "Your new %(listname)s list password" -msgstr "" +msgstr "¸ÞÀϸµ ¸®½ºÆ® Ãʱ⠺ñ¹Ð¹øÈ£:" #: bin/change_pw:191 msgid "" @@ -8658,40 +8786,47 @@ msgid "" msgstr "" #: bin/check_perms:110 +#, fuzzy msgid " checking gid and mode for %(path)s" -msgstr "" +msgstr "%(file)s ÆÄÀÏ¿¡ ´ëÇÑ ÆÛ¹Ì¼Ç °Ë»çÁß" #: bin/check_perms:122 msgid "%(path)s bad group (has: %(groupname)s, expected %(MAILMAN_GROUP)s)" msgstr "" #: bin/check_perms:151 +#, fuzzy msgid "directory permissions must be %(octperms)s: %(path)s" -msgstr "" +msgstr "%(dbfile)s ÆÛ¹Ì¼ÇÀº 0664 ( %(octmode)s )¿©¾ß ÇÕ´Ï´Ù." #: bin/check_perms:160 +#, fuzzy msgid "source perms must be %(octperms)s: %(path)s" -msgstr "" +msgstr "%(dbfile)s ÆÛ¹Ì¼ÇÀº 0664 ( %(octmode)s )¿©¾ß ÇÕ´Ï´Ù." #: bin/check_perms:171 +#, fuzzy msgid "article db files must be %(octperms)s: %(path)s" -msgstr "" +msgstr "%(dbfile)s ÆÛ¹Ì¼ÇÀº 0664 ( %(octmode)s )¿©¾ß ÇÕ´Ï´Ù." #: bin/check_perms:183 +#, fuzzy msgid "checking mode for %(prefix)s" -msgstr "" +msgstr "%(file)s ÆÄÀÏ¿¡ ´ëÇÑ ÆÛ¹Ì¼Ç °Ë»çÁß" #: bin/check_perms:193 msgid "WARNING: directory does not exist: %(d)s" msgstr "" #: bin/check_perms:197 +#, fuzzy msgid "directory must be at least 02775: %(d)s" -msgstr "" +msgstr "%(file)s ÆÛ¹Ì¼ÇÀº 0664 ( %(octmode)s )¿©¾ß ÇÕ´Ï´Ù." #: bin/check_perms:209 +#, fuzzy msgid "checking perms on %(private)s" -msgstr "" +msgstr "%(file)s ÆÄÀÏ¿¡ ´ëÇÑ ÆÛ¹Ì¼Ç °Ë»çÁß" #: bin/check_perms:214 msgid "%(private)s must not be other-readable" @@ -8719,40 +8854,46 @@ msgid "checking cgi-bin permissions" msgstr "" #: bin/check_perms:278 +#, fuzzy msgid " checking set-gid for %(path)s" -msgstr "" +msgstr "%(file)s ÆÄÀÏ¿¡ ´ëÇÑ ÆÛ¹Ì¼Ç °Ë»çÁß" #: bin/check_perms:282 msgid "%(path)s must be set-gid" msgstr "" #: bin/check_perms:292 +#, fuzzy msgid "checking set-gid for %(wrapper)s" -msgstr "" +msgstr "%(file)s ÆÄÀÏ¿¡ ´ëÇÑ ÆÛ¹Ì¼Ç °Ë»çÁß" #: bin/check_perms:296 msgid "%(wrapper)s must be set-gid" msgstr "" #: bin/check_perms:306 +#, fuzzy msgid "checking permissions on %(pwfile)s" -msgstr "" +msgstr "%(file)s ÆÄÀÏ¿¡ ´ëÇÑ ÆÛ¹Ì¼Ç °Ë»çÁß" #: bin/check_perms:315 +#, fuzzy msgid "%(pwfile)s permissions must be exactly 0640 (got %(octmode)s)" -msgstr "" +msgstr "%(file)s ÆÛ¹Ì¼ÇÀº 0664 ( %(octmode)s )¿©¾ß ÇÕ´Ï´Ù." #: bin/check_perms:340 msgid "checking permissions on list data" msgstr "" #: bin/check_perms:348 +#, fuzzy msgid " checking permissions on: %(path)s" -msgstr "" +msgstr "%(file)s ÆÄÀÏ¿¡ ´ëÇÑ ÆÛ¹Ì¼Ç °Ë»çÁß" #: bin/check_perms:356 +#, fuzzy msgid "file permissions must be at least 660: %(path)s" -msgstr "" +msgstr "%(file)s ÆÛ¹Ì¼ÇÀº 0664 ( %(octmode)s )¿©¾ß ÇÕ´Ï´Ù." #: bin/check_perms:401 msgid "No problems found" @@ -8805,8 +8946,9 @@ msgid "Unix-From line changed: %(lineno)d" msgstr "" #: bin/cleanarch:111 +#, fuzzy msgid "Bad status number: %(arg)s" -msgstr "" +msgstr "%(safeuser)s ¶ó´Â »ç¿ëÀÚ°¡ ¾ø½À´Ï´Ù." #: bin/cleanarch:167 msgid "%(messages)d messages found" @@ -8903,14 +9045,16 @@ msgid " original address removed:" msgstr "" #: bin/clone_member:202 +#, fuzzy msgid "Not a valid email address: %(toaddr)s" -msgstr "" +msgstr "À߸øµÈ/¾ø´Â E¸ÞÀÏ ÁÖ¼Ò" #: bin/clone_member:215 +#, fuzzy msgid "" "Error opening list \"%(listname)s\", skipping.\n" "%(e)s" -msgstr "" +msgstr "´ç½ÅÀÇ »õ·Î¿î ¸ÞÀϸµ ¸®½ºÆ® : %(listname)s" #: bin/config_list:20 msgid "" @@ -9055,8 +9199,9 @@ msgid "Ignoring non-held message: %(f)s" msgstr "»èÁ¦ ¸í´Ü¿¡¼­ ¹«½ÃµÈ ȸ¿ø: %(user)s" #: bin/discard:100 +#, fuzzy msgid "Ignoring held msg w/bad id: %(f)s" -msgstr "" +msgstr "»èÁ¦ ¸í´Ü¿¡¼­ ¹«½ÃµÈ ȸ¿ø: %(user)s" #: bin/discard:112 #, fuzzy @@ -9463,12 +9608,14 @@ msgid "" msgstr "" #: bin/list_members:198 +#, fuzzy msgid "Bad --nomail option: %(why)s" -msgstr "" +msgstr "´ÙÀÌÁ¦½ºÆ® ȸ¿ø ¿É¼Ç - ¹­À½ ¸ÞÀÏ ¹ß¼Û Èñ¸Á ȸ¿ø" #: bin/list_members:209 +#, fuzzy msgid "Bad --digest option: %(kind)s" -msgstr "" +msgstr "´ÙÀÌÁ¦½ºÆ® ȸ¿ø ¿É¼Ç - ¹­À½ ¸ÞÀÏ ¹ß¼Û Èñ¸Á ȸ¿ø" #: bin/list_members:213 bin/list_members:217 bin/list_members:221 #: bin/list_members:225 @@ -9652,8 +9799,9 @@ msgid "" msgstr "" #: bin/mailmanctl:280 cron/mailpasswds:119 +#, fuzzy msgid "Site list is missing: %(sitelistname)s" -msgstr "" +msgstr "¸ÞÀϸµ ¸®½ºÆ® À̸§ÀÌ ÀÌ¹Ì Á¸ÀçÇÕ´Ï´Ù: %(safelistname)s" #: bin/mailmanctl:305 msgid "Run this program as root or as the %(name)s user, or use -u." @@ -9880,6 +10028,7 @@ msgid "" msgstr "" #: bin/newlist:162 +#, fuzzy msgid "Unknown language: %(lang)s" msgstr "¾Ë¼ö¾ø´Â ¾ð¾î: %(lang)s" @@ -9892,8 +10041,9 @@ msgid "Enter the email of the person running the list: " msgstr "" #: bin/newlist:193 +#, fuzzy msgid "Initial %(listname)s password: " -msgstr "" +msgstr "¸ÞÀϸµ ¸®½ºÆ® Ãʱ⠺ñ¹Ð¹øÈ£:" #: bin/newlist:197 msgid "The list password cannot be empty" @@ -9901,8 +10051,8 @@ msgstr "" #: bin/newlist:220 msgid "" -" - owner addresses need to be fully-qualified names like \"owner@example.com" -"\", not just \"owner\"." +" - owner addresses need to be fully-qualified names like \"owner@example." +"com\", not just \"owner\"." msgstr "" #: bin/newlist:244 @@ -10070,12 +10220,14 @@ msgid "Could not open file for reading: %(filename)s." msgstr "" #: bin/remove_members:163 +#, fuzzy msgid "Error opening list %(listname)s... skipping." -msgstr "" +msgstr "´ç½ÅÀÇ »õ·Î¿î ¸ÞÀϸµ ¸®½ºÆ® : %(listname)s" #: bin/remove_members:173 +#, fuzzy msgid "No such member: %(addr)s" -msgstr "" +msgstr "%(safeuser)s ¶ó´Â »ç¿ëÀÚ°¡ ¾ø½À´Ï´Ù." #: bin/remove_members:178 msgid "User `%(addr)s' removed from list: %(listname)s." @@ -10142,8 +10294,9 @@ msgid "%(listname)s %(msg)s not found as %(filename)s" msgstr "¸ÞÀϸµ ¸®½ºÆ® À̸§Àº \"@\" ¸¦ Æ÷ÇÔÇÏ¸é ¾ÈµË´Ï´Ù. : %(listname)s" #: bin/rmlist:105 +#, fuzzy msgid "No such list (or list already deleted): %(listname)s" -msgstr "" +msgstr "%(safelistname)s ¶ó´Â ¸ÞÀϸµ ¸®½ºÆ®°¡ Á¸ÀçÇÏÁö ¾Ê½À´Ï´Ù." #: bin/rmlist:108 msgid "No such list: %(listname)s. Removing its residual archives." @@ -10277,8 +10430,9 @@ msgid "No argument to -f given" msgstr "" #: bin/sync_members:172 +#, fuzzy msgid "Illegal option: %(opt)s" -msgstr "" +msgstr "¸ÞÀϸµ ¸®½ºÆ® À̸§Àº \"@\" ¸¦ Æ÷ÇÔÇÏ¸é ¾ÈµË´Ï´Ù. : %(s)s" #: bin/sync_members:178 msgid "No listname given" @@ -10413,8 +10567,9 @@ msgid "" msgstr "" #: bin/update:107 +#, fuzzy msgid "Fixing language templates: %(listname)s" -msgstr "" +msgstr "´ç½ÅÀÇ »õ·Î¿î ¸ÞÀϸµ ¸®½ºÆ® : %(listname)s" #: bin/update:196 bin/update:711 msgid "WARNING: could not acquire lock for list: %(listname)s" @@ -10568,8 +10723,9 @@ msgid "done" msgstr "" #: bin/update:691 +#, fuzzy msgid "Updating mailing list: %(listname)s" -msgstr "" +msgstr "´ç½ÅÀÇ »õ·Î¿î ¸ÞÀϸµ ¸®½ºÆ® : %(listname)s" #: bin/update:694 msgid "Updating Usenet watermarks" @@ -10774,16 +10930,18 @@ msgid "" msgstr "" #: bin/withlist:175 +#, fuzzy msgid "Unlocking (but not saving) list: %(listname)s" -msgstr "" +msgstr "´ç½ÅÀÇ »õ·Î¿î ¸ÞÀϸµ ¸®½ºÆ® : %(listname)s" #: bin/withlist:179 msgid "Finalizing" msgstr "" #: bin/withlist:188 +#, fuzzy msgid "Loading list %(listname)s" -msgstr "" +msgstr "´ç½ÅÀÇ »õ·Î¿î ¸ÞÀϸµ ¸®½ºÆ® : %(listname)s" #: bin/withlist:190 msgid "(locked)" @@ -10794,8 +10952,9 @@ msgid "(unlocked)" msgstr "" #: bin/withlist:197 +#, fuzzy msgid "Unknown list: %(listname)s" -msgstr "" +msgstr "´ç½ÅÀÇ »õ·Î¿î ¸ÞÀϸµ ¸®½ºÆ® : %(listname)s" #: bin/withlist:237 msgid "No list name supplied." @@ -10814,8 +10973,9 @@ msgid "Running %(module)s.%(callable)s()..." msgstr "" #: bin/withlist:291 +#, fuzzy msgid "The variable `m' is the %(listname)s MailList instance" -msgstr "" +msgstr "%(realname)s ¸ÞÀϸµ ¸®½ºÆ®¿¡¼­ Å»ÅðµÇ¼Ì½À´Ï´Ù." #: cron/bumpdigests:19 msgid "" @@ -11002,8 +11162,9 @@ msgid "Password // URL" msgstr "" #: cron/mailpasswds:222 +#, fuzzy msgid "%(host)s mailing list memberships reminder" -msgstr "" +msgstr "%(listfullname)s ¸ÞÀϸµ ¸®½ºÆ® ÆÐ½º¿öµå ¸ÞÀÏ" #: cron/nightly_gzip:19 msgid "" @@ -11080,10 +11241,6 @@ msgstr "" #~ "½ÂÀÎ ´ë½Ã °øÁö´Â ½ºÆÔ ÇÊÅÍ¿¡ °É¸®°Å³ª, °øÁö°¡ ¹Þ¾ÆÁöÁö ¾ÊÀ» ¶§¸¦ ±×·¯ÇÑ " #~ "»ç½ÇÀ» ÅëÁö¹Þ°Ô µË´Ï´Ù. ÀÌ ¿É¼ÇÀº ÀÌ·¯ÇÑ °øÁö¸¦ º¸³»µµ·Ï ÇÕ´Ï´Ù." -#, fuzzy -#~ msgid "You have been invited to join the %(listname)s mailing list" -#~ msgstr "%(realname)s ¸ÞÀϸµ ¸®½ºÆ®¿¡¼­ Å»ÅðµÇ¼Ì½À´Ï´Ù." - #, fuzzy #~ msgid "delivery option set" #~ msgstr "°³ÀÎ Á¤º¸ º¸È£ Á¤Ã¥" @@ -11298,11 +11455,11 @@ msgstr "" #~ "ÀÌ ¸ÞÀÏÀº ÀÚµ¿ ÀÀ´ä ¸ÞÀϸ³´Ï´Ù.\n" #~ "°ü¸®Ã³¸® ÁÖ¼Ò <%(requestaddr)s> À» ÅëÇØ Mailman ¿¡ º¸³½ ´ç½ÅÀÇ ¸í·É¾î¿¡ ¹®" #~ "Á¦°¡ ÀÖ½À´Ï´Ù.\n" -#~ "Mailman ÀÇ E¸ÞÀÏÀ» ÅëÇÑ ¸í·É¾î¿¡ ´ëÇÑ ¼³¸í¼­¸¦ ¾òÀ¸½Ç·Á¸é<" -#~ "%(requestaddr)s> ·Î ÆíÁöÀÇ Á¦¸ñ ȤÀº º»¹®¿¡ \"help\"¶ó´Â ´Ü¾îÀ» ÀÔ·ÂÇϼÅ" +#~ "Mailman ÀÇ E¸ÞÀÏÀ» ÅëÇÑ ¸í·É¾î¿¡ ´ëÇÑ ¼³¸í¼­¸¦ ¾òÀ¸½Ç·Á¸é" +#~ "<%(requestaddr)s> ·Î ÆíÁöÀÇ Á¦¸ñ ȤÀº º»¹®¿¡ \"help\"¶ó´Â ´Ü¾îÀ» ÀÔ·ÂÇϼÅ" #~ "¼­ º¸³» Áֽñ⠹ٶø´Ï´Ù.\n" -#~ "¸¸¾à ´ç½ÅÀÌ ¸ÞÀϸµ ¸®½ºÆ®¸¦ °ü¸®ÇÏ´Â »ç¶÷¿¡°Ô º¸³»±æ ¿øÇϽŴٸé<" -#~ "%(adminaddr)s> ·Î ¿©·¯ºÐÀÇ ÆíÁö¸¦ º¸³»½Ã±â ¹Ù¶ø´Ï´Ù.\n" +#~ "¸¸¾à ´ç½ÅÀÌ ¸ÞÀϸµ ¸®½ºÆ®¸¦ °ü¸®ÇÏ´Â »ç¶÷¿¡°Ô º¸³»±æ ¿øÇϽŴٸé" +#~ "<%(adminaddr)s> ·Î ¿©·¯ºÐÀÇ ÆíÁö¸¦ º¸³»½Ã±â ¹Ù¶ø´Ï´Ù.\n" #~ "´ÙÀ½ÀÇ ºÎºÐ¿¡ ¹®Á¦°¡ ÀÖ½À´Ï´Ù.\n" #~ msgid "Mailman results for %(realname)s" diff --git a/messages/lt/LC_MESSAGES/mailman.po b/messages/lt/LC_MESSAGES/mailman.po index 272f6fe1..2d499d1e 100755 --- a/messages/lt/LC_MESSAGES/mailman.po +++ b/messages/lt/LC_MESSAGES/mailman.po @@ -275,6 +275,7 @@ msgstr "administratoriaus" #: Mailman/Cgi/options.py:101 Mailman/Cgi/private.py:108 #: Mailman/Cgi/rmlist.py:75 Mailman/Cgi/roster.py:59 #: Mailman/Cgi/subscribe.py:67 +#, fuzzy msgid "No such list %(safelistname)s" msgstr "Nëra forumo %(safelistname)s" @@ -1271,8 +1272,9 @@ msgid "%(schange_to)s is already a member" msgstr "forumo dalyvis nuo seniau" #: Mailman/Cgi/admin.py:1620 +#, fuzzy msgid "%(schange_to)s matches banned pattern %(spat)s" -msgstr "" +msgstr "forumo dalyvis nuo seniau" #: Mailman/Cgi/admin.py:1622 msgid "Address %(schange_from)s changed to %(schange_to)s" @@ -1493,6 +1495,7 @@ msgid "Rejects" msgstr "Atmesta" #: Mailman/Cgi/admindb.py:584 +#, fuzzy msgid "" "Ban %(esender)s from ever subscribing to this\n" " mailing list" @@ -1509,6 +1512,7 @@ msgstr "" "\t\tarba galite " #: Mailman/Cgi/admindb.py:591 +#, fuzzy msgid "view all messages from %(esender)s" msgstr "Parodyti þinutes, kurias iðsiuntë %(esender)s" @@ -1731,6 +1735,7 @@ msgid "Preferred language:" msgstr "Pagrindinë kalba" #: Mailman/Cgi/confirm.py:317 +#, fuzzy msgid "Subscribe to list %(listname)s" msgstr "Uþsisakyti %(listname)s" @@ -1747,6 +1752,7 @@ msgid "Awaiting moderator approval" msgstr "Laukiama priþiûrëtojo patvirtinimo" #: Mailman/Cgi/confirm.py:386 +#, fuzzy msgid "" " You have successfully confirmed your subscription request to " "the\n" @@ -1797,6 +1803,7 @@ msgid "Subscription request confirmed" msgstr "Uþsisakymas patvirtintas" #: Mailman/Cgi/confirm.py:418 +#, fuzzy msgid "" " You have successfully confirmed your subscription request for\n" " \"%(addr)s\" to the %(listname)s mailing list. A separate\n" @@ -1825,6 +1832,7 @@ msgid "Unsubscription request confirmed" msgstr "Patvirtintas atsisakymas." #: Mailman/Cgi/confirm.py:469 +#, fuzzy msgid "" " You have successfully unsubscribed from the %(listname)s " "mailing\n" @@ -1832,6 +1840,14 @@ msgid "" "main\n" " information page." msgstr "" +" Jûs (\"%(addr)s\") sëkmingai prisijungëte prie %(listname)s " +"forumo.\n" +" Gausite dar vienà laiðkà su jûsø slaptaþodþiu\n" +" bei kita naudinga informacija bei nuorodomis.\n" +"\n" +"

                    Dabar galite\n" +" eiti á dalyvio prisijungimo\n" +" puslapá." #: Mailman/Cgi/confirm.py:480 msgid "Confirm unsubscription request" @@ -1896,6 +1912,7 @@ msgid "Change of address request confirmed" msgstr "" #: Mailman/Cgi/confirm.py:571 +#, fuzzy msgid "" " You have successfully changed your address on the %(listname)s\n" " mailing list from %(oldaddr)s to %(newaddr)s. " @@ -1903,6 +1920,14 @@ msgid "" " can now proceed to your membership\n" " login page." msgstr "" +" Jûs (\"%(addr)s\") sëkmingai prisijungëte prie %(listname)s " +"forumo.\n" +" Gausite dar vienà laiðkà su jûsø slaptaþodþiu\n" +" bei kita naudinga informacija bei nuorodomis.\n" +"\n" +"

                    Dabar galite\n" +" eiti á dalyvio prisijungimo\n" +" puslapá." #: Mailman/Cgi/confirm.py:583 msgid "Confirm change of address request" @@ -2019,12 +2044,21 @@ msgid "Membership re-enabled." msgstr "" #: Mailman/Cgi/confirm.py:798 +#, fuzzy msgid "" " You have successfully re-enabled your membership in the\n" " %(listname)s mailing list. You can now visit your member options page.\n" " " msgstr "" +" Jûs (\"%(addr)s\") sëkmingai prisijungëte prie %(listname)s " +"forumo.\n" +" Gausite dar vienà laiðkà su jûsø slaptaþodþiu\n" +" bei kita naudinga informacija bei nuorodomis.\n" +"\n" +"

                    Dabar galite\n" +" eiti á dalyvio prisijungimo\n" +" puslapá." #: Mailman/Cgi/confirm.py:810 msgid "Re-enable mailing list membership" @@ -2134,14 +2168,17 @@ msgid "Unknown virtual host: %(safehostname)s" msgstr "Neþinomas forumas: %(listname)s" #: Mailman/Cgi/create.py:218 bin/newlist:219 +#, fuzzy msgid "Bad owner email address: %(s)s" msgstr "Blogas savininko adresas: %(s)s" #: Mailman/Cgi/create.py:223 bin/newlist:182 bin/newlist:223 +#, fuzzy msgid "List already exists: %(listname)s" msgstr "Forumas %(listname)s jau sukurtas anksèiau" #: Mailman/Cgi/create.py:231 bin/newlist:217 +#, fuzzy msgid "Illegal list name: %(s)s" msgstr "Blogas forumo pavadinimas: %(s)s" @@ -2152,6 +2189,7 @@ msgid "" msgstr "" #: Mailman/Cgi/create.py:273 bin/newlist:265 +#, fuzzy msgid "Your new mailing list: %(listname)s" msgstr "Naujas Jûsø forumas: %(listname)s" @@ -2179,6 +2217,7 @@ msgid "Create another list" msgstr "Sukurti kità forumà" #: Mailman/Cgi/create.py:312 +#, fuzzy msgid "Create a %(hostname)s Mailing List" msgstr "Sukurti forumà %(hostname)s" @@ -2351,6 +2390,7 @@ msgid "List name is required." msgstr "Bûtinas forumo pavadinimas" #: Mailman/Cgi/edithtml.py:154 +#, fuzzy msgid "%(realname)s -- Edit html for %(template_info)s" msgstr "%(realname)s -- Redaguoti %(template_info)s HTML" @@ -2363,6 +2403,7 @@ msgid "%(safetemplatename)s: Invalid template" msgstr "" #: Mailman/Cgi/edithtml.py:166 Mailman/Cgi/edithtml.py:167 +#, fuzzy msgid "%(realname)s -- HTML Page Editing" msgstr "%(realname)s -- HTML Puslapio Redagavimas" @@ -2421,16 +2462,21 @@ msgid "HTML successfully updated." msgstr "HTML sëkmingai atnaujintas." #: Mailman/Cgi/listinfo.py:89 +#, fuzzy msgid "%(hostname)s Mailing Lists" msgstr "%(hostname)s forumai" #: Mailman/Cgi/listinfo.py:127 +#, fuzzy msgid "" "

                    There currently are no publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s." msgstr "" +"

                    There currently are no publicly-advertised %(mailmanlink)s\n" +" mailing lists on %(hostname)s." #: Mailman/Cgi/listinfo.py:131 +#, fuzzy msgid "" "

                    Below is a listing of all the public mailing lists on\n" " %(hostname)s. Click on a list name to get more information " @@ -2449,6 +2495,7 @@ msgid "right" msgstr "deðinë" #: Mailman/Cgi/listinfo.py:140 +#, fuzzy msgid "" " To visit the general information page for an unadvertised list,\n" " open a URL similar to this one, but with a '/' and the %(adj)s\n" @@ -2511,6 +2558,7 @@ msgstr "Neteisingas adresas" #: Mailman/Cgi/options.py:183 Mailman/Cgi/options.py:240 #: Mailman/Cgi/options.py:264 +#, fuzzy msgid "No such member: %(safeuser)s." msgstr "Nëra tokio vartotojo: %(safeuser)s." @@ -2596,6 +2644,7 @@ msgid "" msgstr "" #: Mailman/Cgi/options.py:468 +#, fuzzy msgid "The new address is already a member: %(newaddr)s" msgstr "Naujasis adresas %(newaddr)s jau forumo dalyvis" @@ -2604,6 +2653,7 @@ msgid "Addresses may not be blank" msgstr "Negalima praleisti adreso" #: Mailman/Cgi/options.py:488 +#, fuzzy msgid "A confirmation message has been sent to %(newaddr)s. " msgstr "%(newaddr)s iðsiøstas patvirtinimo laiðkas." @@ -2616,6 +2666,7 @@ msgid "Illegal email address provided" msgstr "Neteisingas adresas" #: Mailman/Cgi/options.py:501 +#, fuzzy msgid "%(newaddr)s is already a member of the list." msgstr "%(newaddr)s ðio forumo dalyvis nuo anksèiau." @@ -2778,6 +2829,7 @@ msgid "" msgstr "" #: Mailman/Cgi/options.py:956 +#, fuzzy msgid "%(realname)s list: member options login page" msgstr "Forumo %(realname)s dalyviø nustatymo puslapio prisijungimas" @@ -2791,6 +2843,7 @@ msgid "%(realname)s list: member options for user %(safeuser)s" msgstr "Forumo %(realname)s dalyvio %(safeuser)s nustatymai" #: Mailman/Cgi/options.py:986 +#, fuzzy msgid "" "In order to change your membership option, you must\n" " first log in by giving your %(extra)smembership password in the section\n" @@ -2898,6 +2951,7 @@ msgid "Private archive - \"./\" and \"../\" not allowed in URL." msgstr "" #: Mailman/Cgi/private.py:109 +#, fuzzy msgid "Private Archive Error - %(msg)s" msgstr "Priataus Archyvo Klaida - %(msg)s" @@ -2949,8 +3003,9 @@ msgid "" msgstr "" #: Mailman/Cgi/rmlist.py:208 +#, fuzzy msgid "Permanently remove mailing list %(realname)s" -msgstr "" +msgstr "Naujas Jûsø forumas: %(listname)s" #: Mailman/Cgi/rmlist.py:209 #, fuzzy @@ -3001,8 +3056,9 @@ msgid "Invalid options to CGI script" msgstr "" #: Mailman/Cgi/roster.py:118 +#, fuzzy msgid "%(realname)s roster authentication failed." -msgstr "" +msgstr "Nesëkmingas prisijungimas." #: Mailman/Cgi/subscribe.py:128 msgid "You must supply a valid email address." @@ -3144,6 +3200,7 @@ msgid "This list only supports digest delivery." msgstr "Ðiame forume siunèiami tik grupuoti laiðkai." #: Mailman/Cgi/subscribe.py:344 +#, fuzzy msgid "You have been successfully subscribed to the %(realname)s mailing list." msgstr "Jûs sëkmingai prisijungëte prie forumo %(realname)s." @@ -3545,12 +3602,14 @@ msgid " %(status)s (%(how)s on %(date)s)" msgstr "" #: Mailman/Commands/cmd_set.py:192 +#, fuzzy msgid " myposts %(onoff)s" -msgstr "" +msgstr " duplicates %(onoff)s" #: Mailman/Commands/cmd_set.py:195 +#, fuzzy msgid " hide %(onoff)s" -msgstr "" +msgstr " reminders %(onoff)s" #: Mailman/Commands/cmd_set.py:199 #, fuzzy @@ -3567,6 +3626,7 @@ msgid "You did not give the correct password" msgstr "Neávedëte teisingo slaptaþodþio" #: Mailman/Commands/cmd_set.py:236 Mailman/Commands/cmd_set.py:283 +#, fuzzy msgid "Bad argument: %(arg)s" msgstr "Blogas argumentas: %(arg)s" @@ -3677,6 +3737,7 @@ msgid "This list only supports digest subscriptions!" msgstr "Ðis forumas laidþia tik rinkiniø uþsakymus!" #: Mailman/Commands/cmd_subscribe.py:146 +#, fuzzy msgid "" "Your subscription request has been forwarded to the list administrator\n" "at %(listowner)s for review." @@ -3703,8 +3764,9 @@ msgid "" msgstr "" #: Mailman/Commands/cmd_unsubscribe.py:62 +#, fuzzy msgid "%(address)s is not a member of the %(listname)s mailing list" -msgstr "" +msgstr "You are not a member of the %(listname)s mailing list" #: Mailman/Commands/cmd_unsubscribe.py:69 msgid "" @@ -3950,14 +4012,17 @@ msgid " (Digest mode)" msgstr " (Rinkiniø rëþimas)" #: Mailman/Deliverer.py:79 +#, fuzzy msgid "Welcome to the \"%(realname)s\" mailing list%(digmode)s" msgstr "Sveikiname forume '%(realname)s' %(digmode)s" #: Mailman/Deliverer.py:89 +#, fuzzy msgid "You have been unsubscribed from the %(realname)s mailing list" msgstr "Atsijungëte nuo forumo %(realname)s" #: Mailman/Deliverer.py:116 +#, fuzzy msgid "%(listfullname)s mailing list reminder" msgstr "Forumo %(listfullname)s priminimas" @@ -4163,8 +4228,8 @@ msgid "" " membership.\n" "\n" "

                    You can control both the\n" -" number\n" +" number\n" " of reminders the member will receive and the\n" " No those messages too will get discarded. You may " "want\n" " to set up an\n" -" autoresponse\n" +" autoresponse\n" " message for email to the -owner and -admin address." msgstr "" @@ -4434,8 +4499,8 @@ msgid "" "Use this option to remove each message attachment that does\n" " not have a matching content type. Requirements and formats " "are\n" -" exactly like filter_mime_types.\n" "\n" "

                    Note: if you add entries to this list but don't add\n" @@ -4509,6 +4574,7 @@ msgid "" msgstr "" #: Mailman/Gui/ContentFilter.py:171 +#, fuzzy msgid "Bad MIME type ignored: %(spectype)s" msgstr "Ignoruojamas neteisingas MIME tipas: %(spectype)s" @@ -4724,8 +4790,8 @@ msgid "" "

                    In order to split the list ownership duties into\n" " administrators and moderators, you must\n" " set a separate moderator password,\n" -" and also provide the email\n" +" and also provide the email\n" " addresses of the list moderators. Note that the field you\n" " are changing here specifies the list administrators." msgstr "" @@ -4988,13 +5054,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5019,8 +5085,8 @@ msgstr "" msgid "" "This is the address set in the Reply-To: header\n" " when the reply_goes_to_list\n" +" href=\"?VARHELP=general/" +"reply_goes_to_list\">reply_goes_to_list\n" " option is set to Explicit address.\n" "\n" "

                    There are many reasons not to introduce or override the\n" @@ -5028,13 +5094,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5093,8 +5159,8 @@ msgid "" " member list addresses, but rather to the owner of those member\n" " lists. In that case, the value of this setting is appended to\n" " the member's account name for such notices. `-owner' is the\n" -" typical choice. This setting has no effect when \"umbrella_list" -"\"\n" +" typical choice. This setting has no effect when " +"\"umbrella_list\"\n" " is \"No\"." msgstr "" @@ -5955,8 +6021,8 @@ msgid "" " either individually or as a group. Any\n" " posting from a non-member who is not explicitly accepted,\n" " rejected, or discarded, will have their posting filtered by the\n" -" general\n" +" general\n" " non-member rules.\n" "\n" "

                    In the text boxes below, add one address per line; start the\n" @@ -6015,8 +6081,8 @@ msgid "" "If a member posts this many times, within a period of time\n" " the member is automatically moderated. Use 0 to disable. " "See\n" -" member_verbosity_interval for details on the time " "period.\n" "\n" @@ -6579,8 +6645,8 @@ msgid "" "

                    The body of the message can also be optionally scanned for\n" " Subject: and Keywords: headers, as\n" " specified by the topics_bodylines_limit\n" +" href=\"?VARHELP=topics/" +"topics_bodylines_limit\">topics_bodylines_limit\n" " configuration variable." msgstr "" @@ -6789,6 +6855,7 @@ msgid "%(listinfo_link)s list run by %(owner_link)s" msgstr "Forumas %(listinfo_link)s, kurá sukûrë %(owner_link)s" #: Mailman/HTMLFormatter.py:57 +#, fuzzy msgid "%(realname)s administrative interface" msgstr "Forumo %(realname)s prieþiûra" @@ -6797,6 +6864,7 @@ msgid " (requires authorization)" msgstr " (bûtinas patvirtinimas)" #: Mailman/HTMLFormatter.py:61 +#, fuzzy msgid "Overview of all %(hostname)s mailing lists" msgstr "%(hostname)s forumø sàraðas" @@ -6898,6 +6966,7 @@ msgid "" msgstr "" #: Mailman/HTMLFormatter.py:208 +#, fuzzy msgid "" "This is %(also)sa private list, which means that the\n" " list of members is not available to non-members." @@ -6906,16 +6975,22 @@ msgstr "" "\t\tgali pamatyti jo nariø sàraðà." #: Mailman/HTMLFormatter.py:211 +#, fuzzy msgid "" "This is %(also)sa hidden list, which means that the\n" " list of members is available only to the list administrator." msgstr "" +"Ðis forumas %(also)s privatus, tai reiðkia, kad tik jo nariai\n" +"\t\tgali pamatyti jo nariø sàraðà." #: Mailman/HTMLFormatter.py:214 +#, fuzzy msgid "" "This is %(also)sa public list, which means that the\n" " list of members list is available to everyone." msgstr "" +"Ðis forumas %(also)s privatus, tai reiðkia, kad tik jo nariai\n" +"\t\tgali pamatyti jo nariø sàraðà." #: Mailman/HTMLFormatter.py:217 msgid "" @@ -6924,6 +6999,7 @@ msgid "" msgstr "" #: Mailman/HTMLFormatter.py:222 +#, fuzzy msgid "" "

                    (Note that this is an umbrella list, intended to\n" " have only other mailing lists as members. Among other things,\n" @@ -6940,6 +7016,7 @@ msgid "either " msgstr "arba" #: Mailman/HTMLFormatter.py:256 +#, fuzzy msgid "" "To unsubscribe from %(realname)s, get a password reminder,\n" " or change your subscription options %(either)senter your " @@ -7161,8 +7238,9 @@ msgid "Posting to a moderated newsgroup" msgstr "" #: Mailman/Handlers/Hold.py:252 +#, fuzzy msgid "Your message to %(listname)s awaits moderator approval" -msgstr "" +msgstr "forumo %(realname)s uþsisakymui bûtinas priþiûrëtojo patvirtinimas" #: Mailman/Handlers/Hold.py:271 msgid "%(listname)s post from %(sender)s requires approval" @@ -7364,8 +7442,9 @@ msgid "Forward of moderated message" msgstr "" #: Mailman/ListAdmin.py:405 +#, fuzzy msgid "New subscription request to list %(realname)s from %(addr)s" -msgstr "" +msgstr "Patvirtintas atsisakymas." #: Mailman/ListAdmin.py:432 msgid "Subscription request" @@ -7377,8 +7456,9 @@ msgid "via admin approval" msgstr "Atstatyti narystæ forume." #: Mailman/ListAdmin.py:466 +#, fuzzy msgid "New unsubscription request from %(realname)s by %(addr)s" -msgstr "" +msgstr "Patvirtintas atsisakymas." #: Mailman/ListAdmin.py:490 msgid "Unsubscription request" @@ -7389,8 +7469,9 @@ msgid "Original Message" msgstr "" #: Mailman/ListAdmin.py:526 +#, fuzzy msgid "Request to mailing list %(realname)s rejected" -msgstr "" +msgstr "Naujas Jûsø forumas: %(listname)s" #: Mailman/MTA/Manual.py:66 msgid "" @@ -7415,6 +7496,7 @@ msgid "## %(listname)s mailing list" msgstr "%(hostname)s forumai" #: Mailman/MTA/Manual.py:99 +#, fuzzy msgid "Mailing list creation request for list %(listname)s" msgstr "Furumo %(listname)s sukûrimo praðymas" @@ -7440,14 +7522,17 @@ msgid "" msgstr "" #: Mailman/MTA/Manual.py:142 +#, fuzzy msgid "Mailing list removal request for list %(listname)s" msgstr "Forumo %(listname)s paðalinimo praðymas" #: Mailman/MTA/Postfix.py:442 +#, fuzzy msgid "checking permissions on %(file)s" msgstr "failo %(file)s leidimø patikrinimas" #: Mailman/MTA/Postfix.py:452 +#, fuzzy msgid "%(file)s permissions must be 0664 (got %(octmode)s)" msgstr "%(file)s teisës turi bûti 0664 (yra %(octmode)s)" @@ -7461,14 +7546,17 @@ msgid "(fixing)" msgstr "(taisymas)" #: Mailman/MTA/Postfix.py:470 +#, fuzzy msgid "checking ownership of %(dbfile)s" msgstr "%(dbfile)s nuosavybës patikrinimas" #: Mailman/MTA/Postfix.py:478 +#, fuzzy msgid "%(dbfile)s owned by %(owner)s (must be owned by %(user)s" msgstr "%(dbfile)s sàvininkas yra %(owner)s (turi bûti %(user)s" #: Mailman/MTA/Postfix.py:491 +#, fuzzy msgid "%(dbfile)s permissions must be 0664 (got %(octmode)s)" msgstr "%(dbfile)s teisës turi bûti 0664 (yra %(octmode)s)" @@ -7483,14 +7571,17 @@ msgid "Your confirmation is required to leave the %(listname)s mailing list" msgstr "You are not a member of the %(listname)s mailing list" #: Mailman/MailList.py:999 Mailman/MailList.py:1492 +#, fuzzy msgid " from %(remote)s" msgstr " nuo %(remote)s" #: Mailman/MailList.py:1043 +#, fuzzy msgid "subscriptions to %(realname)s require moderator approval" msgstr "forumo %(realname)s uþsisakymui bûtinas priþiûrëtojo patvirtinimas" #: Mailman/MailList.py:1125 bin/add_members:299 +#, fuzzy msgid "%(realname)s subscription notification" msgstr "%(realname)s uþsisakymo patvirtinimas" @@ -7499,6 +7590,7 @@ msgid "unsubscriptions require moderator approval" msgstr "atsisakymui bûtinas priþiûrëtojo patvirtinimas" #: Mailman/MailList.py:1166 +#, fuzzy msgid "%(realname)s unsubscribe notification" msgstr "%(realname)s atsisakymo patvirtinimas" @@ -7518,8 +7610,9 @@ msgid "via web confirmation" msgstr "Bloga patvirtinimo eilutë" #: Mailman/MailList.py:1396 +#, fuzzy msgid "subscriptions to %(name)s require administrator approval" -msgstr "" +msgstr "forumo %(realname)s uþsisakymui bûtinas priþiûrëtojo patvirtinimas" #: Mailman/MailList.py:1406 #, fuzzy @@ -7770,20 +7863,23 @@ msgid "" msgstr "" #: bin/add_members:162 bin/add_members:172 +#, fuzzy msgid "Already a member: %(member)s" -msgstr "" +msgstr "Jau dalyvis" #: bin/add_members:178 msgid "Bad/Invalid email address: blank line" msgstr "" #: bin/add_members:180 +#, fuzzy msgid "Bad/Invalid email address: %(member)s" -msgstr "" +msgstr "Neteisingas el. paðto adresas" #: bin/add_members:182 +#, fuzzy msgid "Hostile address (illegal characters): %(member)s" -msgstr "" +msgstr "Hostile address (illegal characters)" #: bin/add_members:185 #, fuzzy @@ -7791,16 +7887,19 @@ msgid "Invited: %(member)s" msgstr "Uþsisakë: %(member)s" #: bin/add_members:187 +#, fuzzy msgid "Subscribed: %(member)s" msgstr "Uþsisakë: %(member)s" #: bin/add_members:237 +#, fuzzy msgid "Bad argument to -w/--welcome-msg: %(arg)s" -msgstr "" +msgstr "Blogas argumentas: %(arg)s" #: bin/add_members:244 +#, fuzzy msgid "Bad argument to -a/--admin-notify: %(arg)s" -msgstr "" +msgstr "Blogas argumentas: %(arg)s" #: bin/add_members:252 msgid "Cannot read both digest and normal members from standard input." @@ -7813,6 +7912,7 @@ msgstr "" #: bin/add_members:261 bin/config_list:110 bin/export.py:271 bin/find_member:97 #: bin/inject:91 bin/list_admins:90 bin/list_members:252 bin/sync_members:222 #: cron/bumpdigests:86 +#, fuzzy msgid "No such list: %(listname)s" msgstr "Nëra tokio forumo: %(listname)s" @@ -7876,10 +7976,11 @@ msgid "listname is required" msgstr "" #: bin/arch:143 bin/change_pw:107 bin/config_list:257 +#, fuzzy msgid "" "No such list \"%(listname)s\"\n" "%(e)s" -msgstr "" +msgstr "Nëra tokio forumo: %(listname)s" #: bin/arch:168 msgid "Cannot open mbox file %(mbox)s: %(msg)s" @@ -7963,20 +8064,23 @@ msgid "" msgstr "" #: bin/change_pw:145 +#, fuzzy msgid "Bad arguments: %(strargs)s" -msgstr "" +msgstr "Blogas argumentas: %(arg)s" #: bin/change_pw:149 msgid "Empty list passwords are not allowed" msgstr "" #: bin/change_pw:181 +#, fuzzy msgid "New %(listname)s password: %(notifypassword)s" -msgstr "" +msgstr "Naujas Jûsø forumas: %(listname)s" #: bin/change_pw:190 +#, fuzzy msgid "Your new %(listname)s list password" -msgstr "" +msgstr "Naujas Jûsø forumas: %(listname)s" #: bin/change_pw:191 msgid "" @@ -8054,40 +8158,47 @@ msgid "" msgstr "" #: bin/check_perms:110 +#, fuzzy msgid " checking gid and mode for %(path)s" -msgstr "" +msgstr "failo %(file)s leidimø patikrinimas" #: bin/check_perms:122 msgid "%(path)s bad group (has: %(groupname)s, expected %(MAILMAN_GROUP)s)" msgstr "" #: bin/check_perms:151 +#, fuzzy msgid "directory permissions must be %(octperms)s: %(path)s" -msgstr "" +msgstr "%(dbfile)s teisës turi bûti 0664 (yra %(octmode)s)" #: bin/check_perms:160 +#, fuzzy msgid "source perms must be %(octperms)s: %(path)s" -msgstr "" +msgstr "%(dbfile)s teisës turi bûti 0664 (yra %(octmode)s)" #: bin/check_perms:171 +#, fuzzy msgid "article db files must be %(octperms)s: %(path)s" -msgstr "" +msgstr "%(dbfile)s teisës turi bûti 0664 (yra %(octmode)s)" #: bin/check_perms:183 +#, fuzzy msgid "checking mode for %(prefix)s" -msgstr "" +msgstr "failo %(file)s leidimø patikrinimas" #: bin/check_perms:193 msgid "WARNING: directory does not exist: %(d)s" msgstr "" #: bin/check_perms:197 +#, fuzzy msgid "directory must be at least 02775: %(d)s" -msgstr "" +msgstr "%(file)s teisës turi bûti 0664 (yra %(octmode)s)" #: bin/check_perms:209 +#, fuzzy msgid "checking perms on %(private)s" -msgstr "" +msgstr "failo %(file)s leidimø patikrinimas" #: bin/check_perms:214 msgid "%(private)s must not be other-readable" @@ -8115,40 +8226,46 @@ msgid "checking cgi-bin permissions" msgstr "" #: bin/check_perms:278 +#, fuzzy msgid " checking set-gid for %(path)s" -msgstr "" +msgstr "failo %(file)s leidimø patikrinimas" #: bin/check_perms:282 msgid "%(path)s must be set-gid" msgstr "" #: bin/check_perms:292 +#, fuzzy msgid "checking set-gid for %(wrapper)s" -msgstr "" +msgstr "failo %(file)s leidimø patikrinimas" #: bin/check_perms:296 msgid "%(wrapper)s must be set-gid" msgstr "" #: bin/check_perms:306 +#, fuzzy msgid "checking permissions on %(pwfile)s" -msgstr "" +msgstr "failo %(file)s leidimø patikrinimas" #: bin/check_perms:315 +#, fuzzy msgid "%(pwfile)s permissions must be exactly 0640 (got %(octmode)s)" -msgstr "" +msgstr "%(file)s teisës turi bûti 0664 (yra %(octmode)s)" #: bin/check_perms:340 msgid "checking permissions on list data" msgstr "" #: bin/check_perms:348 +#, fuzzy msgid " checking permissions on: %(path)s" -msgstr "" +msgstr "failo %(file)s leidimø patikrinimas" #: bin/check_perms:356 +#, fuzzy msgid "file permissions must be at least 660: %(path)s" -msgstr "" +msgstr "%(file)s teisës turi bûti 0664 (yra %(octmode)s)" #: bin/check_perms:401 msgid "No problems found" @@ -8201,8 +8318,9 @@ msgid "Unix-From line changed: %(lineno)d" msgstr "" #: bin/cleanarch:111 +#, fuzzy msgid "Bad status number: %(arg)s" -msgstr "" +msgstr "Blogas argumentas: %(arg)s" #: bin/cleanarch:167 msgid "%(messages)d messages found" @@ -8299,14 +8417,16 @@ msgid " original address removed:" msgstr "" #: bin/clone_member:202 +#, fuzzy msgid "Not a valid email address: %(toaddr)s" -msgstr "" +msgstr "Neteisingas el. paðto adresas" #: bin/clone_member:215 +#, fuzzy msgid "" "Error opening list \"%(listname)s\", skipping.\n" "%(e)s" -msgstr "" +msgstr "Naujas Jûsø forumas: %(listname)s" #: bin/config_list:20 msgid "" @@ -8395,8 +8515,11 @@ msgid "Invalid value for property: %(k)s" msgstr "" #: bin/config_list:291 +#, fuzzy msgid "Bad email address for option %(k)s: %(v)s" msgstr "" +"Badly formed options entry:\n" +" %(record)s" #: bin/config_list:348 msgid "Only one of -i or -o is allowed" @@ -8448,8 +8571,9 @@ msgid "Ignoring non-held message: %(f)s" msgstr "Ignoring changes to deleted member: %(user)s" #: bin/discard:100 +#, fuzzy msgid "Ignoring held msg w/bad id: %(f)s" -msgstr "" +msgstr "Ignoring changes to deleted member: %(user)s" #: bin/discard:112 #, fuzzy @@ -8498,8 +8622,9 @@ msgid "No filename given." msgstr "" #: bin/dumpdb:108 +#, fuzzy msgid "Bad arguments: %(pargs)s" -msgstr "" +msgstr "Blogas argumentas: %(arg)s" #: bin/dumpdb:118 msgid "Please specify either -p or -m." @@ -8853,12 +8978,14 @@ msgid "" msgstr "" #: bin/list_members:198 +#, fuzzy msgid "Bad --nomail option: %(why)s" -msgstr "" +msgstr "Nurodyti neteisingi rinkiniai: %(arg)s" #: bin/list_members:209 +#, fuzzy msgid "Bad --digest option: %(kind)s" -msgstr "" +msgstr "Nurodyti neteisingi rinkiniai: %(arg)s" #: bin/list_members:213 bin/list_members:217 bin/list_members:221 #: bin/list_members:225 @@ -9042,6 +9169,7 @@ msgid "" msgstr "" #: bin/mailmanctl:280 cron/mailpasswds:119 +#, fuzzy msgid "Site list is missing: %(sitelistname)s" msgstr "Trûksta %(sitelistname)s adresø sàraðo" @@ -9054,8 +9182,9 @@ msgid "No command given." msgstr "" #: bin/mailmanctl:339 +#, fuzzy msgid "Bad command: %(command)s" -msgstr "" +msgstr "Bad set command: %(subcmd)s" #: bin/mailmanctl:344 msgid "Warning! You may encounter permission problems." @@ -9269,8 +9398,9 @@ msgid "" msgstr "" #: bin/newlist:162 +#, fuzzy msgid "Unknown language: %(lang)s" -msgstr "" +msgstr "Neþinomas forumas: %(listname)s" #: bin/newlist:167 msgid "Enter the name of the list: " @@ -9281,8 +9411,9 @@ msgid "Enter the email of the person running the list: " msgstr "Ávesite formo kûrëjo el. paðto adresà: " #: bin/newlist:193 +#, fuzzy msgid "Initial %(listname)s password: " -msgstr "" +msgstr "Pradinis forumo slaptaþodis neatitinka" #: bin/newlist:197 msgid "The list password cannot be empty" @@ -9290,8 +9421,8 @@ msgstr "" #: bin/newlist:220 msgid "" -" - owner addresses need to be fully-qualified names like \"owner@example.com" -"\", not just \"owner\"." +" - owner addresses need to be fully-qualified names like \"owner@example." +"com\", not just \"owner\"." msgstr "" #: bin/newlist:244 @@ -9459,14 +9590,17 @@ msgid "Could not open file for reading: %(filename)s." msgstr "" #: bin/remove_members:163 +#, fuzzy msgid "Error opening list %(listname)s... skipping." -msgstr "" +msgstr "Naujas Jûsø forumas: %(listname)s" #: bin/remove_members:173 +#, fuzzy msgid "No such member: %(addr)s" -msgstr "" +msgstr "Nëra tokio vartotojo: %(safeuser)s." #: bin/remove_members:178 +#, fuzzy msgid "User `%(addr)s' removed from list: %(listname)s." msgstr "Vartotojas '%(addr)s' iðbrauktas ið forumo: %(listname)s." @@ -9531,12 +9665,14 @@ msgid "%(listname)s %(msg)s not found as %(filename)s" msgstr "Forumo pavadinime nereikia nurodti '@': %(listname)s" #: bin/rmlist:105 +#, fuzzy msgid "No such list (or list already deleted): %(listname)s" -msgstr "" +msgstr "Nëra tokio forumo: %(listname)s" #: bin/rmlist:108 +#, fuzzy msgid "No such list: %(listname)s. Removing its residual archives." -msgstr "" +msgstr "Nëra tokio forumo: %(listname)s" #: bin/rmlist:112 msgid "Not removing archives. Reinvoke with -a to remove them." @@ -9666,8 +9802,9 @@ msgid "No argument to -f given" msgstr "" #: bin/sync_members:172 +#, fuzzy msgid "Illegal option: %(opt)s" -msgstr "" +msgstr "Blogas forumo pavadinimas: %(s)s" #: bin/sync_members:178 msgid "No listname given" @@ -9802,8 +9939,9 @@ msgid "" msgstr "" #: bin/update:107 +#, fuzzy msgid "Fixing language templates: %(listname)s" -msgstr "" +msgstr "Forumo %(listname)s atnaujinimas" #: bin/update:196 bin/update:711 msgid "WARNING: could not acquire lock for list: %(listname)s" @@ -9957,6 +10095,7 @@ msgid "done" msgstr "" #: bin/update:691 +#, fuzzy msgid "Updating mailing list: %(listname)s" msgstr "Forumo %(listname)s atnaujinimas" @@ -10163,16 +10302,18 @@ msgid "" msgstr "" #: bin/withlist:175 +#, fuzzy msgid "Unlocking (but not saving) list: %(listname)s" -msgstr "" +msgstr "Neþinomas forumas: %(listname)s" #: bin/withlist:179 msgid "Finalizing" msgstr "" #: bin/withlist:188 +#, fuzzy msgid "Loading list %(listname)s" -msgstr "" +msgstr "Neþinomas forumas: %(listname)s" #: bin/withlist:190 msgid "(locked)" @@ -10183,6 +10324,7 @@ msgid "(unlocked)" msgstr "" #: bin/withlist:197 +#, fuzzy msgid "Unknown list: %(listname)s" msgstr "Neþinomas forumas: %(listname)s" @@ -10203,8 +10345,9 @@ msgid "Running %(module)s.%(callable)s()..." msgstr "" #: bin/withlist:291 +#, fuzzy msgid "The variable `m' is the %(listname)s MailList instance" -msgstr "" +msgstr "Jus kvieèia prisijungti prie forumo %(listname)s" #: cron/bumpdigests:19 msgid "" @@ -10391,6 +10534,7 @@ msgid "Password // URL" msgstr "Slaptaþodis // URL" #: cron/mailpasswds:222 +#, fuzzy msgid "%(host)s mailing list memberships reminder" msgstr "priminimai %(host)s forumø nariams" @@ -10446,9 +10590,6 @@ msgstr "" #~ msgid "Successfully unsubscribed:" #~ msgstr "Sëkmingai atsisakë:" -#~ msgid "You have been invited to join the %(listname)s mailing list" -#~ msgstr "Jus kvieèia prisijungti prie forumo %(listname)s" - #~ msgid "Traditional Chinese" #~ msgstr "Kinø tradicinë" diff --git a/messages/nl/LC_MESSAGES/mailman.po b/messages/nl/LC_MESSAGES/mailman.po index bd9ecda6..ee55153d 100755 --- a/messages/nl/LC_MESSAGES/mailman.po +++ b/messages/nl/LC_MESSAGES/mailman.po @@ -68,10 +68,12 @@ msgid "

                    Currently, there are no archives.

                    " msgstr "

                    Er zijn momenteel geen archieven.

                    " #: Mailman/Archiver/HyperArch.py:821 +#, fuzzy msgid "Gzip'd Text%(sz)s" msgstr "Gezipte tekst%(sz)s" #: Mailman/Archiver/HyperArch.py:826 +#, fuzzy msgid "Text%(sz)s" msgstr "Tekst %(sz)s" @@ -144,18 +146,22 @@ msgid "Third" msgstr "Derde" #: Mailman/Archiver/HyperArch.py:938 +#, fuzzy msgid "%(ord)s quarter %(year)i" msgstr "%(ord)s kwartaal %(year)i" #: Mailman/Archiver/HyperArch.py:945 +#, fuzzy msgid "%(month)s %(year)i" msgstr "%(month)s %(year)i" #: Mailman/Archiver/HyperArch.py:950 +#, fuzzy msgid "The Week Of Monday %(day)i %(month)s %(year)i" msgstr "De week van maandag %(day)i %(month)s %(year)i" #: Mailman/Archiver/HyperArch.py:954 +#, fuzzy msgid "%(day)i %(month)s %(year)i" msgstr "%(day)i %(month)s %(year)i" @@ -164,10 +170,12 @@ msgid "Computing threaded index\n" msgstr "Berekenen van draadindex\n" #: Mailman/Archiver/HyperArch.py:1319 +#, fuzzy msgid "Updating HTML for article %(seq)s" msgstr "Updaten van HTML voor artikel %(seq)s" #: Mailman/Archiver/HyperArch.py:1326 +#, fuzzy msgid "article file %(filename)s is missing!" msgstr "artikelbestand %(filename)s is niet gevonden!" @@ -184,6 +192,7 @@ msgid "Pickling archive state into " msgstr "Opslaan van archiefstatus in " #: Mailman/Archiver/pipermail.py:453 +#, fuzzy msgid "Updating index files for archive [%(archive)s]" msgstr "Updaten van indexbestanden voor archief [%(archive)s]" @@ -192,6 +201,7 @@ msgid " Thread" msgstr " Draad" #: Mailman/Archiver/pipermail.py:597 +#, fuzzy msgid "#%(counter)05d %(msgid)s" msgstr "#%(counter)05d %(msgid)s" @@ -229,6 +239,7 @@ msgid "disabled address" msgstr "uitgezet" #: Mailman/Bouncer.py:304 +#, fuzzy msgid " The last bounce received from you was dated %(date)s" msgstr " De laatste bounce ontvangen van u was gedateerd %(date)s" @@ -256,6 +267,7 @@ msgstr "Beheerder" #: Mailman/Cgi/options.py:101 Mailman/Cgi/private.py:108 #: Mailman/Cgi/rmlist.py:75 Mailman/Cgi/roster.py:59 #: Mailman/Cgi/subscribe.py:67 +#, fuzzy msgid "No such list %(safelistname)s" msgstr "Er is geen lijst met de naam %(safelistname)s" @@ -329,6 +341,7 @@ msgstr "" " ontvangen totdat u het probleem heeft opgelost.%(rm)r" #: Mailman/Cgi/admin.py:268 +#, fuzzy msgid "%(hostname)s mailing lists - Admin Links" msgstr "Beheerlinks van %(hostname)s maillijsten" @@ -341,6 +354,7 @@ msgid "Mailman" msgstr "Mailman" #: Mailman/Cgi/admin.py:310 +#, fuzzy msgid "" "

                    There currently are no publicly-advertised %(mailmanlink)s\n" " mailing lists on %(hostname)s." @@ -349,6 +363,7 @@ msgstr "" " maillijsten op %(hostname)s." #: Mailman/Cgi/admin.py:316 +#, fuzzy msgid "" "

                    Below is the collection of publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s. Click on a list\n" @@ -363,6 +378,7 @@ msgid "right " msgstr "rechts " #: Mailman/Cgi/admin.py:325 +#, fuzzy msgid "" "To visit the administrators configuration page for an\n" " unadvertised list, open a URL similar to this one, but with a '/' " @@ -408,6 +424,7 @@ msgid "No valid variable name found." msgstr "Geen geldige variabelenaam gevonden." #: Mailman/Cgi/admin.py:395 +#, fuzzy msgid "" "%(realname)s Mailing list Configuration Help\n" "
                    %(varname)s Option" @@ -416,6 +433,7 @@ msgstr "" "
                    Hulp bij de configuratie van de %(varname)s instelling" #: Mailman/Cgi/admin.py:402 +#, fuzzy msgid "Mailman %(varname)s List Option Help" msgstr "Hulp bij de Mailman %(varname)s lijstoptie" @@ -436,14 +454,17 @@ msgstr "" " " #: Mailman/Cgi/admin.py:431 +#, fuzzy msgid "return to the %(categoryname)s options page." msgstr "terug naar de %(categoryname)spagina." #: Mailman/Cgi/admin.py:446 +#, fuzzy msgid "%(realname)s Administration (%(label)s)" msgstr "%(realname)s Beheer (%(label)s)" #: Mailman/Cgi/admin.py:447 +#, fuzzy msgid "%(realname)s mailing list administration
                    %(label)s Section" msgstr "%(realname)s maillijstbeheer
                    %(label)s" @@ -525,6 +546,7 @@ msgid "Value" msgstr "Waarde" #: Mailman/Cgi/admin.py:668 +#, fuzzy msgid "" "Badly formed options entry:\n" " %(record)s" @@ -625,10 +647,12 @@ msgid "Move rule down" msgstr "Verplaats regel naar beneden" #: Mailman/Cgi/admin.py:870 +#, fuzzy msgid "
                    (Edit %(varname)s)" msgstr "
                    (Wijzig %(varname)s)" #: Mailman/Cgi/admin.py:872 +#, fuzzy msgid "
                    (Details for %(varname)s)" msgstr "
                    (Details voor %(varname)s)" @@ -669,6 +693,7 @@ msgid "(help)" msgstr "(help)" #: Mailman/Cgi/admin.py:930 +#, fuzzy msgid "Find member %(link)s:" msgstr "Zoek een lid %(link)s:" @@ -681,10 +706,12 @@ msgid "Bad regular expression: " msgstr "Foute uitdrukking: " #: Mailman/Cgi/admin.py:1013 +#, fuzzy msgid "%(allcnt)s members total, %(membercnt)s shown" msgstr "%(allcnt)s leden in totaal, %(membercnt)s zichtbaar" #: Mailman/Cgi/admin.py:1016 +#, fuzzy msgid "%(allcnt)s members total" msgstr "%(allcnt)s leden in totaal" @@ -873,6 +900,7 @@ msgstr "" " betreffende reeks-indicatie:" #: Mailman/Cgi/admin.py:1221 +#, fuzzy msgid "from %(start)s to %(end)s" msgstr "van %(start)s tot %(end)s" @@ -1010,6 +1038,7 @@ msgid "Change list ownership passwords" msgstr "Verander de lijstbeheerderswachtwoorden" #: Mailman/Cgi/admin.py:1364 +#, fuzzy msgid "" "The list administrators are the people who have ultimate control " "over\n" @@ -1121,6 +1150,7 @@ msgstr "Verdacht adres (niet toegestane lettertekens)" #: Mailman/Cgi/admin.py:1529 Mailman/Cgi/admin.py:1703 bin/add_members:175 #: bin/clone_member:136 bin/sync_members:268 +#, fuzzy msgid "Banned address (matched %(pattern)s)" msgstr "Verboden adres (komt overeen met %(pattern)s)" @@ -1222,6 +1252,7 @@ msgid "Not subscribed" msgstr "Niet aangemeld" #: Mailman/Cgi/admin.py:1773 +#, fuzzy msgid "Ignoring changes to deleted member: %(user)s" msgstr "Negeer wijzigingen voor verwijderd lid: %(user)s" @@ -1234,10 +1265,12 @@ msgid "Error Unsubscribing:" msgstr "Fout bij afmelden:" #: Mailman/Cgi/admindb.py:235 Mailman/Cgi/admindb.py:248 +#, fuzzy msgid "%(realname)s Administrative Database" msgstr "%(realname)s Beheerdatabase" #: Mailman/Cgi/admindb.py:238 +#, fuzzy msgid "%(realname)s Administrative Database Results" msgstr "%(realname)s Beheerdatabaseresultaten" @@ -1266,6 +1299,7 @@ msgid "Discard all messages marked Defer" msgstr "Negeer alle berichten die gemarkeerd zijn met Uitstellen" #: Mailman/Cgi/admindb.py:298 +#, fuzzy msgid "all of %(esender)s's held messages." msgstr "alle vastgehouden berichten van %(esender)s." @@ -1286,6 +1320,7 @@ msgid "list of available mailing lists." msgstr "lijst van beschikbare maillijsten." #: Mailman/Cgi/admindb.py:362 +#, fuzzy msgid "You must specify a list name. Here is the %(link)s" msgstr "U moet een lijstnaam specificeren. Hier is de %(link)s" @@ -1367,6 +1402,7 @@ msgid "The sender is now a member of this list" msgstr "De afzender is nu lid van deze lijst" #: Mailman/Cgi/admindb.py:568 +#, fuzzy msgid "Add %(esender)s to one of these sender filters:" msgstr "Voeg %(esender)s toe aan een van deze afzenderfilters:" @@ -1387,6 +1423,7 @@ msgid "Rejects" msgstr "Weigeren" #: Mailman/Cgi/admindb.py:584 +#, fuzzy msgid "" "Ban %(esender)s from ever subscribing to this\n" " mailing list" @@ -1403,6 +1440,7 @@ msgstr "" " of u kunt " #: Mailman/Cgi/admindb.py:591 +#, fuzzy msgid "view all messages from %(esender)s" msgstr "alle berichten bekijken van %(esender)s" @@ -1481,6 +1519,7 @@ msgid " is already a member" msgstr " is al lid" #: Mailman/Cgi/admindb.py:973 +#, fuzzy msgid "%(addr)s is banned (matched: %(patt)s)" msgstr "%(addr)s is verboden (komt overeen met: %(patt)s)" @@ -1531,6 +1570,7 @@ msgstr "" " Dit wijzigingsverzoek is derhalve geannuleerd." #: Mailman/Cgi/confirm.py:178 +#, fuzzy msgid "System error, bad content: %(content)s" msgstr "Systeemfout, verkeerde inhoud: %(content)s" @@ -1567,6 +1607,7 @@ msgid "Confirm subscription request" msgstr "Bevestig het aanmeldingsverzoek" #: Mailman/Cgi/confirm.py:259 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " subscription request to the mailing list %(listname)s. Your\n" @@ -1599,6 +1640,7 @@ msgstr "" "

                    Als u geen lid wil worden, klik op Aanmelding annuleren." #: Mailman/Cgi/confirm.py:275 +#, fuzzy msgid "" "Your confirmation is required in order to continue with\n" " the subscription request to the mailing list %(listname)s.\n" @@ -1654,6 +1696,7 @@ msgid "Preferred language:" msgstr "Taalvoorkeur:" #: Mailman/Cgi/confirm.py:317 +#, fuzzy msgid "Subscribe to list %(listname)s" msgstr "Aanmelden op maillijst %(listname)s" @@ -1670,6 +1713,7 @@ msgid "Awaiting moderator approval" msgstr "Wachtend op moderatorgoedkeuring." #: Mailman/Cgi/confirm.py:386 +#, fuzzy msgid "" " You have successfully confirmed your subscription request to " "the\n" @@ -1702,6 +1746,7 @@ msgid "You are already a member of this mailing list!" msgstr "U bent al lid van deze maillijst!" #: Mailman/Cgi/confirm.py:400 +#, fuzzy msgid "" "You are currently banned from subscribing to\n" " this list. If you think this restriction is erroneous, please\n" @@ -1727,6 +1772,7 @@ msgid "Subscription request confirmed" msgstr "Aanmeldingsverzoek bevestigd" #: Mailman/Cgi/confirm.py:418 +#, fuzzy msgid "" " You have successfully confirmed your subscription request for\n" " \"%(addr)s\" to the %(listname)s mailing list. A separate\n" @@ -1755,6 +1801,7 @@ msgid "Unsubscription request confirmed" msgstr "Afmeldingsverzoek is bevestigd" #: Mailman/Cgi/confirm.py:469 +#, fuzzy msgid "" " You have successfully unsubscribed from the %(listname)s " "mailing\n" @@ -1776,6 +1823,7 @@ msgid "Not available" msgstr "Niet beschikbaar" #: Mailman/Cgi/confirm.py:498 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " unsubscription request from the mailing list %(listname)s. " @@ -1820,6 +1868,7 @@ msgid "You have canceled your change of address request." msgstr "U hebt uw adreswijzigingsverzoek geannuleerd." #: Mailman/Cgi/confirm.py:555 +#, fuzzy msgid "" "%(newaddr)s is banned from subscribing to the\n" " %(realname)s list. If you think this restriction is erroneous,\n" @@ -1831,6 +1880,7 @@ msgstr "" " %(owneraddr)s." #: Mailman/Cgi/confirm.py:560 +#, fuzzy msgid "" "%(newaddr)s is already a member of\n" " the %(realname)s list. It is possible that you are attempting\n" @@ -1847,6 +1897,7 @@ msgid "Change of address request confirmed" msgstr "Adreswijzigingsverzoek bevestigd" #: Mailman/Cgi/confirm.py:571 +#, fuzzy msgid "" " You have successfully changed your address on the %(listname)s\n" " mailing list from %(oldaddr)s to %(newaddr)s. " @@ -1868,6 +1919,7 @@ msgid "globally" msgstr "in alle lijsten" #: Mailman/Cgi/confirm.py:605 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " change of address request for the mailing list %(listname)s. " @@ -1931,6 +1983,7 @@ msgid "Sender discarded message via web." msgstr "Afzender heeft bericht geannuleerd via het web." #: Mailman/Cgi/confirm.py:674 +#, fuzzy msgid "" "The held message with the Subject:\n" " header %(subject)s could not be found. The most " @@ -1951,6 +2004,7 @@ msgid "Posted message canceled" msgstr "Geposte bericht geannuleerd" #: Mailman/Cgi/confirm.py:685 +#, fuzzy msgid "" " You have successfully canceled the posting of your message with\n" " the Subject: header %(subject)s to the mailing list\n" @@ -1973,6 +2027,7 @@ msgstr "" " verwerkt door de lijstbeheerder." #: Mailman/Cgi/confirm.py:735 +#, fuzzy msgid "" "Your confirmation is required in order to cancel the\n" " posting of your message to the mailing list %(listname)s:\n" @@ -2021,6 +2076,7 @@ msgid "Membership re-enabled." msgstr "Lidmaatschap gereactiveerd." #: Mailman/Cgi/confirm.py:798 +#, fuzzy msgid "" " You have successfully re-enabled your membership in the\n" " %(listname)s mailing list. You can now not available" msgstr "niet beschikbaar" #: Mailman/Cgi/confirm.py:846 +#, fuzzy msgid "" "Your membership in the %(realname)s mailing list is\n" " currently disabled due to excessive bounces. Your confirmation is\n" @@ -2119,10 +2177,12 @@ msgid "administrative list overview" msgstr "lijstbeheerdersoverzicht" #: Mailman/Cgi/create.py:115 +#, fuzzy msgid "List name must not include \"@\": %(safelistname)s" msgstr "Lijstnaam mag niet \"@\": %(safelistname)s bevatten" #: Mailman/Cgi/create.py:122 +#, fuzzy msgid "List already exists: %(safelistname)s" msgstr "Lijst bestaat reeds: %(safelistname)s" @@ -2156,18 +2216,22 @@ msgid "You are not authorized to create new mailing lists" msgstr "U bent niet geautoriseerd om nieuwe maillijsten aan te maken" #: Mailman/Cgi/create.py:182 +#, fuzzy msgid "Unknown virtual host: %(safehostname)s" msgstr "Onbekende virtual host: %(safehostname)s" #: Mailman/Cgi/create.py:218 bin/newlist:219 +#, fuzzy msgid "Bad owner email address: %(s)s" msgstr "Foutief e-mailadres van lijsteigenaar: %(s)s" #: Mailman/Cgi/create.py:223 bin/newlist:182 bin/newlist:223 +#, fuzzy msgid "List already exists: %(listname)s" msgstr "Lijst bestaat reeds: %(listname)s" #: Mailman/Cgi/create.py:231 bin/newlist:217 +#, fuzzy msgid "Illegal list name: %(s)s" msgstr "Ongeldige lijstnaam: %(s)s" @@ -2180,6 +2244,7 @@ msgstr "" " Neem contact op met de sitebeheerder voor assistentie." #: Mailman/Cgi/create.py:273 bin/newlist:265 +#, fuzzy msgid "Your new mailing list: %(listname)s" msgstr "Uw nieuwe maillijst: %(listname)s" @@ -2188,6 +2253,7 @@ msgid "Mailing list creation results" msgstr "Maillijst aanmaakresultaten:" #: Mailman/Cgi/create.py:288 +#, fuzzy msgid "" "You have successfully created the mailing list\n" " %(listname)s and notification has been sent to the list owner\n" @@ -2210,6 +2276,7 @@ msgid "Create another list" msgstr "Nog een maillijst aanmaken" #: Mailman/Cgi/create.py:312 +#, fuzzy msgid "Create a %(hostname)s Mailing List" msgstr "Een %(hostname)s maillijst aanmaken" @@ -2313,6 +2380,7 @@ msgstr "" " goedkeuring door de moderator als standaard in te stellen." #: Mailman/Cgi/create.py:432 +#, fuzzy msgid "" "Initial list of supported languages.

                    Note that if you do not\n" " select at least one initial language, the list will use the server\n" @@ -2417,6 +2485,7 @@ msgid "List name is required." msgstr "Lijstnaam is vereist." #: Mailman/Cgi/edithtml.py:154 +#, fuzzy msgid "%(realname)s -- Edit html for %(template_info)s" msgstr "%(realname)s -- Bewerken html voor %(template_info)s" @@ -2425,10 +2494,12 @@ msgid "Edit HTML : Error" msgstr "Bewerk HTML : Fout" #: Mailman/Cgi/edithtml.py:161 +#, fuzzy msgid "%(safetemplatename)s: Invalid template" msgstr "%(safetemplatename)s: Geen geldige template" #: Mailman/Cgi/edithtml.py:166 Mailman/Cgi/edithtml.py:167 +#, fuzzy msgid "%(realname)s -- HTML Page Editing" msgstr "%(realname)s -- HTML-pagina Bewerken" @@ -2491,10 +2562,12 @@ msgid "HTML successfully updated." msgstr "HTML met succes bijgewerkt." #: Mailman/Cgi/listinfo.py:89 +#, fuzzy msgid "%(hostname)s Mailing Lists" msgstr "Maillijsten op %(hostname)s" #: Mailman/Cgi/listinfo.py:127 +#, fuzzy msgid "" "

                    There currently are no publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s." @@ -2503,6 +2576,7 @@ msgstr "" " %(mailmanlink)s maillijsten op %(hostname)s." #: Mailman/Cgi/listinfo.py:131 +#, fuzzy msgid "" "

                    Below is a listing of all the public mailing lists on\n" " %(hostname)s. Click on a list name to get more information " @@ -2521,6 +2595,7 @@ msgid "right" msgstr "rechts" #: Mailman/Cgi/listinfo.py:140 +#, fuzzy msgid "" " To visit the general information page for an unadvertised list,\n" " open a URL similar to this one, but with a '/' and the %(adj)s\n" @@ -2582,6 +2657,7 @@ msgstr "Ongeldig e-mailadres" #: Mailman/Cgi/options.py:183 Mailman/Cgi/options.py:240 #: Mailman/Cgi/options.py:264 +#, fuzzy msgid "No such member: %(safeuser)s." msgstr "Niet bestaand lid: %(safeuser)s." @@ -2636,6 +2712,7 @@ msgid "Note: " msgstr "Opmerking:" #: Mailman/Cgi/options.py:378 +#, fuzzy msgid "List subscriptions for %(safeuser)s on %(hostname)s" msgstr "Aanmeldingen voor %(safeuser)s op %(hostname)s" @@ -2666,6 +2743,7 @@ msgid "You are already using that email address" msgstr "U gebruikt reeds dat e-mailadres" #: Mailman/Cgi/options.py:459 +#, fuzzy msgid "" "The new address you requested %(newaddr)s is already a member of the\n" "%(listname)s mailing list, however you have also requested a global change " @@ -2680,6 +2758,7 @@ msgstr "" "%(safeuser)s in alle maillijsten worden gewijzigd. " #: Mailman/Cgi/options.py:468 +#, fuzzy msgid "The new address is already a member: %(newaddr)s" msgstr "Het nieuwe adres %(newaddr)s is reeds lid." @@ -2688,6 +2767,7 @@ msgid "Addresses may not be blank" msgstr "E-mailadressen dienen ingevuld te worden" #: Mailman/Cgi/options.py:488 +#, fuzzy msgid "A confirmation message has been sent to %(newaddr)s. " msgstr "Een bevestigingsbericht is verzonden naar %(newaddr)s." @@ -2700,10 +2780,12 @@ msgid "Illegal email address provided" msgstr "Ongeldig e-mailadres opgegeven" #: Mailman/Cgi/options.py:501 +#, fuzzy msgid "%(newaddr)s is already a member of the list." msgstr "%(newaddr)s is al lid van de lijst." #: Mailman/Cgi/options.py:504 +#, fuzzy msgid "" "%(newaddr)s is banned from this list. If you\n" " think this restriction is erroneous, please contact\n" @@ -2781,6 +2863,7 @@ msgstr "" " genomen." #: Mailman/Cgi/options.py:623 +#, fuzzy msgid "" "You have been successfully unsubscribed from the\n" " mailing list %(fqdn_listname)s. If you were receiving digest\n" @@ -2876,6 +2959,7 @@ msgid "day" msgstr "dag" #: Mailman/Cgi/options.py:900 +#, fuzzy msgid "%(days)d %(units)s" msgstr "%(days)d %(units)s" @@ -2888,6 +2972,7 @@ msgid "No topics defined" msgstr "Geen onderwerpen gedefinieerd" #: Mailman/Cgi/options.py:940 +#, fuzzy msgid "" "\n" "You are subscribed to this list with the case-preserved address\n" @@ -2898,6 +2983,7 @@ msgstr "" "%(cpuser)s." #: Mailman/Cgi/options.py:956 +#, fuzzy msgid "%(realname)s list: member options login page" msgstr "%(realname)s lijst: inlogpagina voor lidmaatschapsinstellingen" @@ -2906,11 +2992,13 @@ msgid "email address and " msgstr "e-mailadres en " #: Mailman/Cgi/options.py:960 +#, fuzzy msgid "%(realname)s list: member options for user %(safeuser)s" msgstr "" "%(realname)s lijst: lidmaatschapsinstellingen voor gebruiker %(safeuser)s" #: Mailman/Cgi/options.py:986 +#, fuzzy msgid "" "In order to change your membership option, you must\n" " first log in by giving your %(extra)smembership password in the section\n" @@ -2985,6 +3073,7 @@ msgid "" msgstr "" #: Mailman/Cgi/options.py:1140 +#, fuzzy msgid "Requested topic is not valid: %(topicname)s" msgstr "Vermelde onderwerp is niet geldig: %(topicname)s" @@ -3013,6 +3102,7 @@ msgid "Private archive - \"./\" and \"../\" not allowed in URL." msgstr "Besloten archief - \"./\" en \"../\" niet toegestaan in URL." #: Mailman/Cgi/private.py:109 +#, fuzzy msgid "Private Archive Error - %(msg)s" msgstr "Fout in besloten archief - %(msg)s" @@ -3050,6 +3140,7 @@ msgid "Mailing list deletion results" msgstr "Maillijst verwijderresultaten" #: Mailman/Cgi/rmlist.py:187 +#, fuzzy msgid "" "You have successfully deleted the mailing list\n" " %(listname)s." @@ -3058,6 +3149,7 @@ msgstr "" " verwijderd." #: Mailman/Cgi/rmlist.py:191 +#, fuzzy msgid "" "There were some problems deleting the mailing list\n" " %(listname)s. Contact your site administrator at " @@ -3069,6 +3161,7 @@ msgstr "" "op %(sitelist)s voor details." #: Mailman/Cgi/rmlist.py:208 +#, fuzzy msgid "Permanently remove mailing list %(realname)s" msgstr "Permanent verwijderen van maillijst %(realname)s" @@ -3141,6 +3234,7 @@ msgid "Invalid options to CGI script" msgstr "Ongeldige opties voor het CGI-script" #: Mailman/Cgi/roster.py:118 +#, fuzzy msgid "%(realname)s roster authentication failed." msgstr "%(realname)s lijstauthentificatie mislukt." @@ -3208,6 +3302,7 @@ msgstr "" "is zult u spoedig een e-mail ontvangen met verdere instructies" #: Mailman/Cgi/subscribe.py:262 +#, fuzzy msgid "" "The email address you supplied is banned from this\n" " mailing list. If you think this restriction is erroneous, please\n" @@ -3235,6 +3330,7 @@ msgstr "" "onveilig is." #: Mailman/Cgi/subscribe.py:278 +#, fuzzy msgid "" "Confirmation from your email address is required, to prevent anyone from\n" "subscribing you without permission. Instructions are being sent to you at\n" @@ -3247,6 +3343,7 @@ msgstr "" "start nadat u uw aanmelding heeft bevestigd." #: Mailman/Cgi/subscribe.py:290 +#, fuzzy msgid "" "Your subscription request was deferred because %(x)s. Your request has " "been\n" @@ -3267,6 +3364,7 @@ msgid "Mailman privacy alert" msgstr "Mailman privacywaarschuwing" #: Mailman/Cgi/subscribe.py:316 +#, fuzzy msgid "" "An attempt was made to subscribe your address to the mailing list\n" "%(listaddr)s. You are already subscribed to this mailing list.\n" @@ -3308,6 +3406,7 @@ msgid "This list only supports digest delivery." msgstr "Deze lijst ondersteunt alleen de bezorging van verzamelmails." #: Mailman/Cgi/subscribe.py:344 +#, fuzzy msgid "You have been successfully subscribed to the %(realname)s mailing list." msgstr "U bent met succes aangemeld bij de %(realname)s maillijst." @@ -3356,6 +3455,7 @@ msgstr "" "hebt u uw e-mailadres veranderd?" #: Mailman/Commands/cmd_confirm.py:69 +#, fuzzy msgid "" "You are currently banned from subscribing to this list. If you think this\n" "restriction is erroneous, please contact the list owners at\n" @@ -3438,26 +3538,32 @@ msgid "n/a" msgstr "n/b" #: Mailman/Commands/cmd_info.py:44 +#, fuzzy msgid "List name: %(listname)s" msgstr "Lijstnaam: %(listname)s" #: Mailman/Commands/cmd_info.py:45 +#, fuzzy msgid "Description: %(description)s" msgstr "Omschrijving: %(description)s" #: Mailman/Commands/cmd_info.py:46 +#, fuzzy msgid "Postings to: %(postaddr)s" msgstr "Berichten richten aan: %(postaddr)s" #: Mailman/Commands/cmd_info.py:47 +#, fuzzy msgid "List Helpbot: %(requestaddr)s" msgstr "Lijst helpbot: %(requestaddr)s" #: Mailman/Commands/cmd_info.py:48 +#, fuzzy msgid "List Owners: %(owneraddr)s" msgstr "Lijstbeheerders: %(owneraddr)s" #: Mailman/Commands/cmd_info.py:49 +#, fuzzy msgid "More information: %(listurl)s" msgstr "Meer informatie: %(listurl)s" @@ -3481,18 +3587,22 @@ msgstr "" "server.\n" #: Mailman/Commands/cmd_lists.py:44 +#, fuzzy msgid "Public mailing lists at %(hostname)s:" msgstr "Publieke maillijsten op %(hostname)s:" #: Mailman/Commands/cmd_lists.py:66 +#, fuzzy msgid "%(i)3d. List name: %(realname)s" msgstr "%(i)3d. Lijstnaam: %(realname)s" #: Mailman/Commands/cmd_lists.py:67 +#, fuzzy msgid " Description: %(description)s" msgstr " Beschrijving: %(description)s" #: Mailman/Commands/cmd_lists.py:68 +#, fuzzy msgid " Requests to: %(requestaddr)s" msgstr " Verzoeken naar: %(requestaddr)s" @@ -3528,12 +3638,14 @@ msgstr "" " de bevestiging altijd naar het aangemelde adres wordt verstuurd.\n" #: Mailman/Commands/cmd_password.py:51 Mailman/Commands/cmd_password.py:66 +#, fuzzy msgid "Your password is: %(password)s" msgstr "Uw wachtwoord is: %(password)s" #: Mailman/Commands/cmd_password.py:57 Mailman/Commands/cmd_password.py:72 #: Mailman/Commands/cmd_password.py:95 Mailman/Commands/cmd_password.py:121 #: Mailman/Commands/cmd_set.py:149 Mailman/Commands/cmd_set.py:219 +#, fuzzy msgid "You are not a member of the %(listname)s mailing list" msgstr "U bent geen lid van de %(listname)s maillijst" @@ -3734,6 +3846,7 @@ msgstr "" " van deze maillijst wilt uitschakelen.\n" #: Mailman/Commands/cmd_set.py:122 +#, fuzzy msgid "Bad set command: %(subcmd)s" msgstr "Ongeldige opdracht: %(subcmd)s" @@ -3754,6 +3867,7 @@ msgid "on" msgstr "aan" #: Mailman/Commands/cmd_set.py:154 +#, fuzzy msgid " ack %(onoff)s" msgstr " ontvangstbevestiging %(onoff)s" @@ -3791,22 +3905,27 @@ msgid "due to bounces" msgstr "vanwege bounces" #: Mailman/Commands/cmd_set.py:186 +#, fuzzy msgid " %(status)s (%(how)s on %(date)s)" msgstr " %(status)s (%(how)s op %(date)s)" #: Mailman/Commands/cmd_set.py:192 +#, fuzzy msgid " myposts %(onoff)s" msgstr " eigen berichten %(onoff)s" #: Mailman/Commands/cmd_set.py:195 +#, fuzzy msgid " hide %(onoff)s" msgstr " onzichtbaar %(onoff)s" #: Mailman/Commands/cmd_set.py:199 +#, fuzzy msgid " duplicates %(onoff)s" msgstr " dubbele berichten %(onoff)s" #: Mailman/Commands/cmd_set.py:203 +#, fuzzy msgid " reminders %(onoff)s" msgstr " herinneringen %(onoff)s" @@ -3815,6 +3934,7 @@ msgid "You did not give the correct password" msgstr "U hebt niet het juiste wachtwoord opgegeven" #: Mailman/Commands/cmd_set.py:236 Mailman/Commands/cmd_set.py:283 +#, fuzzy msgid "Bad argument: %(arg)s" msgstr "Fout argument: %(arg)s" @@ -3890,6 +4010,7 @@ msgstr "" " e-mailadres, en zonder aanhalingstekens!) specificeren.\n" #: Mailman/Commands/cmd_subscribe.py:62 +#, fuzzy msgid "Bad digest specifier: %(arg)s" msgstr "Ongeldige verzamelmailspecificatie: %(arg)s" @@ -3898,6 +4019,7 @@ msgid "No valid address found to subscribe" msgstr "Er is geen geldig adres gevonden om aan te melden" #: Mailman/Commands/cmd_subscribe.py:113 +#, fuzzy msgid "" "The email address you supplied is banned from this mailing list.\n" "If you think this restriction is erroneous, please contact the list\n" @@ -3936,6 +4058,7 @@ msgid "This list only supports digest subscriptions!" msgstr "Op deze lijst kunt u zich alleen aanmelden voor verzamelmail!" #: Mailman/Commands/cmd_subscribe.py:146 +#, fuzzy msgid "" "Your subscription request has been forwarded to the list administrator\n" "at %(listowner)s for review." @@ -3971,6 +4094,7 @@ msgstr "" " en zonder aanhalingstekens!) het af te melden adres.\n" #: Mailman/Commands/cmd_unsubscribe.py:62 +#, fuzzy msgid "%(address)s is not a member of the %(listname)s mailing list" msgstr "%(address)s is geen lid van de %(listname)s maillijst" @@ -4224,6 +4348,7 @@ msgid "Chinese (Taiwan)" msgstr "Chinees (Taiwan)" #: Mailman/Deliverer.py:53 +#, fuzzy msgid "" "Note: Since this is a list of mailing lists, administrative\n" "notices like the password reminder will be sent to\n" @@ -4238,14 +4363,17 @@ msgid " (Digest mode)" msgstr " (Verzamelmail modus)" #: Mailman/Deliverer.py:79 +#, fuzzy msgid "Welcome to the \"%(realname)s\" mailing list%(digmode)s" msgstr "Welkom op de \"%(realname)s\" maillijst%(digmode)s" #: Mailman/Deliverer.py:89 +#, fuzzy msgid "You have been unsubscribed from the %(realname)s mailing list" msgstr "U bent afgemeld van de %(realname)s maillijst" #: Mailman/Deliverer.py:116 +#, fuzzy msgid "%(listfullname)s mailing list reminder" msgstr "%(listfullname)s maillijstherinnering" @@ -4258,6 +4386,7 @@ msgid "Hostile subscription attempt detected" msgstr "Kwaadwillige aanmeldingspoging ontdekt" #: Mailman/Deliverer.py:169 +#, fuzzy msgid "" "%(address)s was invited to a different mailing\n" "list, but in a deliberate malicious attempt they tried to confirm the\n" @@ -4270,6 +4399,7 @@ msgstr "" "van uw kant." #: Mailman/Deliverer.py:188 +#, fuzzy msgid "" "You invited %(address)s to your list, but in a\n" "deliberate malicious attempt, they tried to confirm the invitation to a\n" @@ -4284,6 +4414,7 @@ msgstr "" "van uw kant." #: Mailman/Deliverer.py:221 +#, fuzzy msgid "%(listname)s mailing list probe message" msgstr "%(listname)s onderzoeksbericht" @@ -4496,8 +4627,8 @@ msgid "" " membership.\n" "\n" "

                    You can control both the\n" -" number\n" +" number\n" " of reminders the member will receive and the\n" " U kunt zowel het \n" -" aantal\n" +" aantal\n" " herinneringen instellen dat het lid ontvangt als de \n" " No those messages too will get discarded. You may " "want\n" " to set up an\n" -" autoresponse\n" +" autoresponse\n" " message for email to the -owner and -admin address." msgstr "" "Ondanks het feit dat de bouncedetector tamelijk robuust is, is het\n" @@ -4757,8 +4888,8 @@ msgstr "" " en deze variabele is op Nee gezet, dan zullen ook " "deze\n" " berichten worden genegeerd. U kunt besluiten om een\n" -" automatisch\n" +" automatisch\n" " antwoord in te stellen voor e-mail gericht aan het -owner " "en -admin adres." @@ -4830,6 +4961,7 @@ msgstr "" "stellen." #: Mailman/Gui/Bounce.py:194 +#, fuzzy msgid "" "Bad value for %(property)s: %(val)s" @@ -4923,8 +5055,8 @@ msgstr "" "

                    Tenslotte zullen alle text/html delen die zijn\n" " overgebleven in het bericht worden omgezet naar text/plain\n" -" als convert_html_to_plaintext is aangezet en de site dusdanig " "is\n" " geconfigureerd dat deze omzettingen zijn toegestaan." @@ -4988,8 +5120,8 @@ msgid "" "Use this option to remove each message attachment that does\n" " not have a matching content type. Requirements and formats " "are\n" -" exactly like filter_mime_types.\n" "\n" "

                    Note: if you add entries to this list but don't add\n" @@ -5116,6 +5248,7 @@ msgstr "" " beschikbaar indien geactiveeerd door de sitebeheerder." #: Mailman/Gui/ContentFilter.py:171 +#, fuzzy msgid "Bad MIME type ignored: %(spectype)s" msgstr "Ongeldig MIME type genegeerd: %(spectype)s" @@ -5231,6 +5364,7 @@ msgstr "" " niet leeg is?" #: Mailman/Gui/Digest.py:145 +#, fuzzy msgid "" "The next digest will be sent as volume\n" " %(volume)s, number %(number)s" @@ -5247,14 +5381,17 @@ msgid "There was no digest to send." msgstr "Er was geen verzamelmail om te verzenden." #: Mailman/Gui/GUIBase.py:173 +#, fuzzy msgid "Invalid value for variable: %(property)s" msgstr "Ongeldige waarde voor variabele: %(property)s" #: Mailman/Gui/GUIBase.py:178 +#, fuzzy msgid "Bad email address for option %(property)s: %(error)s" msgstr "Ongeldig e-mailadres voor instelling %(property)s: %(error)s" #: Mailman/Gui/GUIBase.py:204 +#, fuzzy msgid "" "The following illegal substitution variables were\n" " found in the %(property)s string:\n" @@ -5271,6 +5408,7 @@ msgstr "" " totdat u dit probleem heeft gecorrigeerd." #: Mailman/Gui/GUIBase.py:218 +#, fuzzy msgid "" "Your %(property)s string appeared to\n" " have some correctable problems in its new value.\n" @@ -5375,8 +5513,8 @@ msgid "" "

                    In order to split the list ownership duties into\n" " administrators and moderators, you must\n" " set a separate moderator password,\n" -" and also provide the email\n" +" and also provide the email\n" " addresses of the list moderators. Note that the field you\n" " are changing here specifies the list administrators." msgstr "" @@ -5711,13 +5849,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5759,13 +5897,13 @@ msgstr "" "tt> adres\n" " het veel moeilijker maakt om priv�-antwoorden te versturen. Zie " "`Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful voor een algemene bespreking van " "dit\n" " onderwerp. Zie Reply-To\n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">Reply-To\n" " Munging Considered Useful voor een tegengestelde visie.\n" "\n" "

                    Sommige maillijsten hebben beperkte verzendprivileges,\n" @@ -5786,8 +5924,8 @@ msgstr "Expliciet Reply-To: adres." msgid "" "This is the address set in the Reply-To: header\n" " when the reply_goes_to_list\n" +" href=\"?VARHELP=general/" +"reply_goes_to_list\">reply_goes_to_list\n" " option is set to Explicit address.\n" "\n" "

                    There are many reasons not to introduce or override the\n" @@ -5795,13 +5933,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5823,8 +5961,8 @@ msgid "" " Reply-To: header, it will not be changed." msgstr "" "Dit is het in te stellen Reply-To: adres als de reply_goes_to_list\n" +" href=\"?VARHELP=general/" +"reply_goes_to_list\">reply_goes_to_list\n" " optie is ingesteld op Expliciet adres.\n" "\n" "

                    Er zijn vele redenen om geen Reply-To:\n" @@ -5835,13 +5973,13 @@ msgstr "" "tt> adres\n" " het veel moeilijker maakt om priv�-antwoorden te versturen. Zie " "'Reply-To\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">'Reply-To\n" " Munging' Considered Harmful voor een algemene bespreking " "van dit\n" " onderwerp. Zie Reply-To\n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">Reply-To\n" " Munging Considered Useful voor een tegengestelde visie.\n" "\n" "

                    Sommige maillijsten hebben beperkte verzendprivileges,\n" @@ -5906,8 +6044,8 @@ msgid "" " member list addresses, but rather to the owner of those member\n" " lists. In that case, the value of this setting is appended to\n" " the member's account name for such notices. `-owner' is the\n" -" typical choice. This setting has no effect when \"umbrella_list" -"\"\n" +" typical choice. This setting has no effect when " +"\"umbrella_list\"\n" " is \"No\"." msgstr "" "Als \"umbrella_list\" zodanig is ingesteld dat deze lijst uitsluitend\n" @@ -6944,6 +7082,7 @@ msgstr "" " en valse aanmeldingen zouden kunnen indienen." #: Mailman/Gui/Privacy.py:104 +#, fuzzy msgid "" "This section allows you to configure subscription and\n" " membership exposure policy. You can also control whether this\n" @@ -7142,8 +7281,8 @@ msgid "" " either individually or as a group. Any\n" " posting from a non-member who is not explicitly accepted,\n" " rejected, or discarded, will have their posting filtered by the\n" -" general\n" +" general\n" " non-member rules.\n" "\n" "

                    In the text boxes below, add one address per line; start the\n" @@ -7206,6 +7345,7 @@ msgstr "" " worden gemodereerd?" #: Mailman/Gui/Privacy.py:218 +#, fuzzy msgid "" "Each list member has a moderation flag which says\n" " whether messages from the list member can be posted directly " @@ -7258,8 +7398,8 @@ msgid "" "If a member posts this many times, within a period of time\n" " the member is automatically moderated. Use 0 to disable. " "See\n" -" member_verbosity_interval for details on the time " "period.\n" "\n" @@ -7704,8 +7844,8 @@ msgstr "" " van het bericht vergeleken met de lijst van expliciet\n" " geaccepteerde,\n" -" vastgehouden,\n" +" vastgehouden,\n" " geweigerde en\n" " The body of the message can also be optionally scanned for\n" " Subject: and Keywords: headers, as\n" " specified by the topics_bodylines_limit\n" +" href=\"?VARHELP=topics/" +"topics_bodylines_limit\">topics_bodylines_limit\n" " configuration variable." msgstr "" "De onderwerpfilter categoriseert elk binnengekomen e-mailbericht\n" @@ -8054,8 +8196,8 @@ msgstr "" "

                    De bodytekst van het bericht kan eventueel ook worden\n" " gecontroleerd op de aanwezigheid van Subject:\n" " en Keywords: headers; dit is nader toegelicht\n" -" bij de topics_bodylines_limit instelling." +" bij de topics_bodylines_limit instelling." #: Mailman/Gui/Topics.py:72 msgid "How many body lines should the topic matcher scan?" @@ -8129,6 +8271,7 @@ msgstr "" " patroon. Incomplete onderwerpen worden genegeerd." #: Mailman/Gui/Topics.py:135 +#, fuzzy msgid "" "The topic pattern '%(safepattern)s' is not a\n" " legal regular expression. It will be discarded." @@ -8357,6 +8500,7 @@ msgid "%(listinfo_link)s list run by %(owner_link)s" msgstr "%(listinfo_link)s wordt beheerd door %(owner_link)s" #: Mailman/HTMLFormatter.py:57 +#, fuzzy msgid "%(realname)s administrative interface" msgstr "%(realname)s beheerdersinstellingen" @@ -8365,6 +8509,7 @@ msgid " (requires authorization)" msgstr " (alleen toegang met authorisatie)" #: Mailman/HTMLFormatter.py:61 +#, fuzzy msgid "Overview of all %(hostname)s mailing lists" msgstr "Overzicht van alle maillijsten op %(hostname)s" @@ -8385,6 +8530,7 @@ msgid "; it was disabled by the list administrator" msgstr "; het is uitgeschakeld door de lijstbeheerder" #: Mailman/HTMLFormatter.py:146 +#, fuzzy msgid "" "; it was disabled due to excessive bounces. The\n" " last bounce was received on %(date)s" @@ -8397,6 +8543,7 @@ msgid "; it was disabled for unknown reasons" msgstr "; het is uitgeschakeld om onbekende redenen" #: Mailman/HTMLFormatter.py:151 +#, fuzzy msgid "Note: your list delivery is currently disabled%(reason)s." msgstr "Opmerking: uw mailontvangst is momenteel uitgeschakeld%(reason)s." @@ -8409,6 +8556,7 @@ msgid "the list administrator" msgstr "de lijstbeheerder" #: Mailman/HTMLFormatter.py:157 +#, fuzzy msgid "" "

                    %(note)s\n" "\n" @@ -8428,6 +8576,7 @@ msgstr "" " heeft of hulp nodig hebt." #: Mailman/HTMLFormatter.py:169 +#, fuzzy msgid "" "

                    We have received some recent bounces from your\n" " address. Your current bounce score is %(score)s out of " @@ -8450,6 +8599,7 @@ msgstr "" " worden opgelost." #: Mailman/HTMLFormatter.py:181 +#, fuzzy msgid "" "(Note - you are subscribing to a list of mailing lists, so the %(type)s " "notice will be sent to the admin address for your membership, %(addr)s.)

                    " @@ -8497,6 +8647,7 @@ msgstr "" " moderator krijgt u per e-mail toegestuurd." #: Mailman/HTMLFormatter.py:208 +#, fuzzy msgid "" "This is %(also)sa private list, which means that the\n" " list of members is not available to non-members." @@ -8505,6 +8656,7 @@ msgstr "" " dat de ledenlijst niet toegankelijk is voor niet-leden." #: Mailman/HTMLFormatter.py:211 +#, fuzzy msgid "" "This is %(also)sa hidden list, which means that the\n" " list of members is available only to the list administrator." @@ -8513,6 +8665,7 @@ msgstr "" " dat de ledenlijst alleen toegankelijk is voor de lijstbeheerder." #: Mailman/HTMLFormatter.py:214 +#, fuzzy msgid "" "This is %(also)sa public list, which means that the\n" " list of members list is available to everyone." @@ -8529,6 +8682,7 @@ msgstr "" " gemakkelijk herkenbaar zijn voor spammers)." #: Mailman/HTMLFormatter.py:222 +#, fuzzy msgid "" "

                    (Note that this is an umbrella list, intended to\n" " have only other mailing lists as members. Among other things,\n" @@ -8545,6 +8699,7 @@ msgid "either " msgstr "of " #: Mailman/HTMLFormatter.py:256 +#, fuzzy msgid "" "To unsubscribe from %(realname)s, get a password reminder,\n" " or change your subscription options %(either)senter your " @@ -8578,6 +8733,7 @@ msgstr "" " uw e-mailadres" #: Mailman/HTMLFormatter.py:277 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " members.)" @@ -8586,6 +8742,7 @@ msgstr "" " leden van de lijst.)" #: Mailman/HTMLFormatter.py:281 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " administrator.)" @@ -8646,6 +8803,7 @@ msgid "The current archive" msgstr "Het huidige archief" #: Mailman/Handlers/Acknowledge.py:59 +#, fuzzy msgid "%(realname)s post acknowledgement" msgstr "%(realname)s ontvangstbevestiging" @@ -8658,6 +8816,7 @@ msgid "" msgstr "" #: Mailman/Handlers/CalcRecips.py:79 +#, fuzzy msgid "" "Your urgent message to the %(realname)s mailing list was not authorized for\n" "delivery. The original message as received by Mailman is attached.\n" @@ -8736,6 +8895,7 @@ msgid "Message may contain administrivia" msgstr "Bericht bevat wellicht administratieve opdrachten" #: Mailman/Handlers/Hold.py:84 +#, fuzzy msgid "" "Please do *not* post administrative requests to the mailing\n" "list. If you wish to subscribe, visit %(listurl)s or send a message with " @@ -8776,10 +8936,12 @@ msgid "Posting to a moderated newsgroup" msgstr "Berichten naar een gemodereerde nieuwsgroep" #: Mailman/Handlers/Hold.py:252 +#, fuzzy msgid "Your message to %(listname)s awaits moderator approval" msgstr "Uw bericht aan %(listname)s wacht op goedkeuring door de moderator" #: Mailman/Handlers/Hold.py:271 +#, fuzzy msgid "%(listname)s post from %(sender)s requires approval" msgstr "Voor berichten van %(sender)s aan %(listname)s is uw goedkeuring nodig" @@ -8822,6 +8984,7 @@ msgid "After content filtering, the message was empty" msgstr "Na controle door de inhoudsfilter, was het bericht leeg" #: Mailman/Handlers/MimeDel.py:269 +#, fuzzy msgid "" "The attached message matched the %(listname)s mailing list's content " "filtering\n" @@ -8864,6 +9027,7 @@ msgid "The attached message has been automatically discarded." msgstr "Het bijgevoegde bericht is automatisch genegeerd." #: Mailman/Handlers/Replybot.py:75 +#, fuzzy msgid "Auto-response for your message to the \"%(realname)s\" mailing list" msgstr "" "Automatische beantwoording van uw bericht aan de \"%(realname)s\" maillijst" @@ -8873,6 +9037,7 @@ msgid "The Mailman Replybot" msgstr "De automatische beantwoorder van Mailman" #: Mailman/Handlers/Scrubber.py:208 +#, fuzzy msgid "" "An embedded and charset-unspecified text was scrubbed...\n" "Name: %(filename)s\n" @@ -8887,6 +9052,7 @@ msgid "HTML attachment scrubbed and removed" msgstr "HTML-bijlage gescrubt en verwijderd" #: Mailman/Handlers/Scrubber.py:234 Mailman/Handlers/Scrubber.py:259 +#, fuzzy msgid "" "An HTML attachment was scrubbed...\n" "URL: %(url)s\n" @@ -8907,6 +9073,7 @@ msgid "unknown sender" msgstr "onbekende afzender" #: Mailman/Handlers/Scrubber.py:276 +#, fuzzy msgid "" "An embedded message was scrubbed...\n" "From: %(who)s\n" @@ -8923,6 +9090,7 @@ msgstr "" "URL: %(url)s\n" #: Mailman/Handlers/Scrubber.py:308 +#, fuzzy msgid "" "A non-text attachment was scrubbed...\n" "Name: %(filename)s\n" @@ -8939,6 +9107,7 @@ msgstr "" "URL : %(url)s\n" #: Mailman/Handlers/Scrubber.py:347 +#, fuzzy msgid "Skipped content of type %(partctype)s\n" msgstr "Verwijderde inhoud van type %(partctype)s\n" @@ -8969,6 +9138,7 @@ msgid "Message rejected by filter rule match" msgstr "Bericht geweigerd vanwege overeenkomst met spamfilterregel" #: Mailman/Handlers/ToDigest.py:173 +#, fuzzy msgid "%(realname)s Digest, Vol %(volume)d, Issue %(issue)d" msgstr "%(realname)s Verzamelmail, Volume %(volume)d, Nummer %(issue)d" @@ -9005,6 +9175,7 @@ msgid "End of " msgstr "Eind van " #: Mailman/ListAdmin.py:309 +#, fuzzy msgid "Posting of your message titled \"%(subject)s\"" msgstr "Verzending van uw bericht met de titel \"%(subject)s\"" @@ -9017,6 +9188,7 @@ msgid "Forward of moderated message" msgstr "Doorsturen van gemodereerd bericht" #: Mailman/ListAdmin.py:405 +#, fuzzy msgid "New subscription request to list %(realname)s from %(addr)s" msgstr "Nieuw aanmeldingsverzoek aan lijst %(realname)s van %(addr)s" @@ -9030,6 +9202,7 @@ msgid "via admin approval" msgstr "Continueer goedkeuring" #: Mailman/ListAdmin.py:466 +#, fuzzy msgid "New unsubscription request from %(realname)s by %(addr)s" msgstr "Nieuw afmeldingsverzoek van %(realname)s door %(addr)s" @@ -9042,6 +9215,7 @@ msgid "Original Message" msgstr "Oorspronkelijk bericht" #: Mailman/ListAdmin.py:526 +#, fuzzy msgid "Request to mailing list %(realname)s rejected" msgstr "Verzoek aan maillijst %(realname)s geweigerd" @@ -9063,12 +9237,14 @@ msgid "" msgstr "" #: Mailman/MTA/Manual.py:82 +#, fuzzy msgid "## %(listname)s mailing list" -msgstr "" +msgstr "Maillijsten op %(hostname)s" #: Mailman/MTA/Manual.py:99 +#, fuzzy msgid "Mailing list creation request for list %(listname)s" -msgstr "" +msgstr "Maillijst aanmaakresultaten:" #: Mailman/MTA/Manual.py:113 msgid "" @@ -9092,8 +9268,9 @@ msgid "" msgstr "" #: Mailman/MTA/Manual.py:142 +#, fuzzy msgid "Mailing list removal request for list %(listname)s" -msgstr "" +msgstr "Maillijst aanmaakresultaten:" #: Mailman/MTA/Postfix.py:442 msgid "checking permissions on %(file)s" @@ -9125,23 +9302,28 @@ msgid "%(dbfile)s permissions must be 0664 (got %(octmode)s)" msgstr "" #: Mailman/MailList.py:219 +#, fuzzy msgid "Your confirmation is required to join the %(listname)s mailing list" msgstr "" "Uw bevesting is vereist om deel te kunnen nemen aan de %(listname)s maillijst" #: Mailman/MailList.py:230 +#, fuzzy msgid "Your confirmation is required to leave the %(listname)s mailing list" msgstr "Uw bevestiging is vereist voor afmelding van de %(listname)s maillijst" #: Mailman/MailList.py:999 Mailman/MailList.py:1492 +#, fuzzy msgid " from %(remote)s" msgstr " van %(remote)s" #: Mailman/MailList.py:1043 +#, fuzzy msgid "subscriptions to %(realname)s require moderator approval" msgstr "aanmeldingen bij %(realname)s vereisen goedkeuring door moderator" #: Mailman/MailList.py:1125 bin/add_members:299 +#, fuzzy msgid "%(realname)s subscription notification" msgstr "%(realname)s aanmeldingsbericht" @@ -9150,6 +9332,7 @@ msgid "unsubscriptions require moderator approval" msgstr "afmeldingen vereisen goedkeuring door moderator" #: Mailman/MailList.py:1166 +#, fuzzy msgid "%(realname)s unsubscribe notification" msgstr "%(realname)s afmeldingsbericht" @@ -9169,6 +9352,7 @@ msgid "via web confirmation" msgstr "Verkeerde bevestigingscode" #: Mailman/MailList.py:1396 +#, fuzzy msgid "subscriptions to %(name)s require administrator approval" msgstr "aanmeldingen bij %(name)s vereisen goedkeuring door beheerder" @@ -9187,6 +9371,7 @@ msgid "Last autoresponse notification for today" msgstr "Laatste automatisch bericht voor vandaag" #: Mailman/Queue/BounceRunner.py:360 +#, fuzzy msgid "" "The attached message was received as a bounce, but either the bounce format\n" "was not recognized, or no member addresses could be extracted from it. " @@ -9276,6 +9461,7 @@ msgid "Original message suppressed by Mailman site configuration\n" msgstr "" #: Mailman/htmlformat.py:675 +#, fuzzy msgid "Delivered by Mailman
                    version %(version)s" msgstr "Bezorgd door Mailman
                    versie %(version)s" @@ -9364,6 +9550,7 @@ msgid "Server Local Time" msgstr "Lokale tijd server" #: Mailman/i18n.py:180 +#, fuzzy msgid "" "%(wday)s %(mon)s %(day)2i %(hh)02i:%(mm)02i:%(ss)02i %(tzname)s %(year)04i" msgstr "" @@ -9433,20 +9620,23 @@ msgid "" msgstr "" #: bin/add_members:162 bin/add_members:172 +#, fuzzy msgid "Already a member: %(member)s" -msgstr "" +msgstr "Is al lid" #: bin/add_members:178 msgid "Bad/Invalid email address: blank line" msgstr "" #: bin/add_members:180 +#, fuzzy msgid "Bad/Invalid email address: %(member)s" -msgstr "" +msgstr "Ongeldig e-mailadres" #: bin/add_members:182 +#, fuzzy msgid "Hostile address (illegal characters): %(member)s" -msgstr "" +msgstr "Verdacht adres (niet toegestane lettertekens)" #: bin/add_members:185 #, fuzzy @@ -9454,16 +9644,19 @@ msgid "Invited: %(member)s" msgstr "Leden van de lijst" #: bin/add_members:187 +#, fuzzy msgid "Subscribed: %(member)s" -msgstr "" +msgstr "Leden van de lijst" #: bin/add_members:237 +#, fuzzy msgid "Bad argument to -w/--welcome-msg: %(arg)s" -msgstr "" +msgstr "Fout argument: %(arg)s" #: bin/add_members:244 +#, fuzzy msgid "Bad argument to -a/--admin-notify: %(arg)s" -msgstr "" +msgstr "Fout argument: %(arg)s" #: bin/add_members:252 msgid "Cannot read both digest and normal members from standard input." @@ -9476,8 +9669,9 @@ msgstr "" #: bin/add_members:261 bin/config_list:110 bin/export.py:271 bin/find_member:97 #: bin/inject:91 bin/list_admins:90 bin/list_members:252 bin/sync_members:222 #: cron/bumpdigests:86 +#, fuzzy msgid "No such list: %(listname)s" -msgstr "" +msgstr "Er is geen lijst met de naam %(safelistname)s" #: bin/add_members:285 bin/change_pw:159 bin/check_db:114 bin/discard:83 #: bin/sync_members:244 bin/update:302 bin/update:323 bin/update:577 @@ -9539,10 +9733,11 @@ msgid "listname is required" msgstr "" #: bin/arch:143 bin/change_pw:107 bin/config_list:257 +#, fuzzy msgid "" "No such list \"%(listname)s\"\n" "%(e)s" -msgstr "" +msgstr "Er is geen lijst met de naam %(safelistname)s" #: bin/arch:168 msgid "Cannot open mbox file %(mbox)s: %(msg)s" @@ -9626,20 +9821,23 @@ msgid "" msgstr "" #: bin/change_pw:145 +#, fuzzy msgid "Bad arguments: %(strargs)s" -msgstr "" +msgstr "Fout argument: %(arg)s" #: bin/change_pw:149 msgid "Empty list passwords are not allowed" msgstr "" #: bin/change_pw:181 +#, fuzzy msgid "New %(listname)s password: %(notifypassword)s" -msgstr "" +msgstr "Wachtwoord lijstbeheerder:" #: bin/change_pw:190 +#, fuzzy msgid "Your new %(listname)s list password" -msgstr "" +msgstr "Wachtwoord lijstbeheerder:" #: bin/change_pw:191 msgid "" @@ -9864,8 +10062,9 @@ msgid "Unix-From line changed: %(lineno)d" msgstr "" #: bin/cleanarch:111 +#, fuzzy msgid "Bad status number: %(arg)s" -msgstr "" +msgstr "Fout argument: %(arg)s" #: bin/cleanarch:167 msgid "%(messages)d messages found" @@ -9962,14 +10161,16 @@ msgid " original address removed:" msgstr "" #: bin/clone_member:202 +#, fuzzy msgid "Not a valid email address: %(toaddr)s" -msgstr "" +msgstr "Ongeldig e-mailadres" #: bin/clone_member:215 +#, fuzzy msgid "" "Error opening list \"%(listname)s\", skipping.\n" "%(e)s" -msgstr "" +msgstr "Uw nieuwe maillijst: %(listname)s" #: bin/config_list:20 msgid "" @@ -10054,12 +10255,14 @@ msgid "Non-standard property restored: %(k)s" msgstr "" #: bin/config_list:288 +#, fuzzy msgid "Invalid value for property: %(k)s" -msgstr "" +msgstr "Ongeldige waarde voor variabele: %(property)s" #: bin/config_list:291 +#, fuzzy msgid "Bad email address for option %(k)s: %(v)s" -msgstr "" +msgstr "Ongeldig e-mailadres voor instelling %(property)s: %(error)s" #: bin/config_list:348 msgid "Only one of -i or -o is allowed" @@ -10106,16 +10309,19 @@ msgid "" msgstr "" #: bin/discard:94 +#, fuzzy msgid "Ignoring non-held message: %(f)s" -msgstr "" +msgstr "Negeer wijzigingen voor verwijderd lid: %(user)s" #: bin/discard:100 +#, fuzzy msgid "Ignoring held msg w/bad id: %(f)s" -msgstr "" +msgstr "Negeer wijzigingen voor verwijderd lid: %(user)s" #: bin/discard:112 +#, fuzzy msgid "Discarded held msg #%(id)s for list %(listname)s" -msgstr "" +msgstr "Aanmelden op maillijst %(listname)s" #: bin/dumpdb:19 msgid "" @@ -10159,8 +10365,9 @@ msgid "No filename given." msgstr "" #: bin/dumpdb:108 +#, fuzzy msgid "Bad arguments: %(pargs)s" -msgstr "" +msgstr "Fout argument: %(arg)s" #: bin/dumpdb:118 msgid "Please specify either -p or -m." @@ -10408,8 +10615,9 @@ msgid "" msgstr "" #: bin/list_admins:97 +#, fuzzy msgid "List: %(listname)s, \tOwners: %(owners)s" -msgstr "" +msgstr "Lijstbeheerders: %(owneraddr)s" #: bin/list_lists:19 msgid "" @@ -10514,12 +10722,14 @@ msgid "" msgstr "" #: bin/list_members:198 +#, fuzzy msgid "Bad --nomail option: %(why)s" -msgstr "" +msgstr "Ongeldige verzamelmailspecificatie: %(arg)s" #: bin/list_members:209 +#, fuzzy msgid "Bad --digest option: %(kind)s" -msgstr "" +msgstr "Ongeldige verzamelmailspecificatie: %(arg)s" #: bin/list_members:213 bin/list_members:217 bin/list_members:221 #: bin/list_members:225 @@ -10703,8 +10913,9 @@ msgid "" msgstr "" #: bin/mailmanctl:280 cron/mailpasswds:119 +#, fuzzy msgid "Site list is missing: %(sitelistname)s" -msgstr "" +msgstr "Lijst bestaat reeds: %(safelistname)s" #: bin/mailmanctl:305 msgid "Run this program as root or as the %(name)s user, or use -u." @@ -10715,8 +10926,9 @@ msgid "No command given." msgstr "" #: bin/mailmanctl:339 +#, fuzzy msgid "Bad command: %(command)s" -msgstr "" +msgstr "Ongeldige opdracht: %(subcmd)s" #: bin/mailmanctl:344 msgid "Warning! You may encounter permission problems." @@ -10930,8 +11142,9 @@ msgid "" msgstr "" #: bin/newlist:162 +#, fuzzy msgid "Unknown language: %(lang)s" -msgstr "" +msgstr "Uw nieuwe maillijst: %(listname)s" #: bin/newlist:167 msgid "Enter the name of the list: " @@ -10942,8 +11155,9 @@ msgid "Enter the email of the person running the list: " msgstr "" #: bin/newlist:193 +#, fuzzy msgid "Initial %(listname)s password: " -msgstr "" +msgstr "Wachtwoord lijstbeheerder:" #: bin/newlist:197 msgid "The list password cannot be empty" @@ -10951,8 +11165,8 @@ msgstr "" #: bin/newlist:220 msgid "" -" - owner addresses need to be fully-qualified names like \"owner@example.com" -"\", not just \"owner\"." +" - owner addresses need to be fully-qualified names like \"owner@example." +"com\", not just \"owner\"." msgstr "" #: bin/newlist:244 @@ -11120,12 +11334,14 @@ msgid "Could not open file for reading: %(filename)s." msgstr "" #: bin/remove_members:163 +#, fuzzy msgid "Error opening list %(listname)s... skipping." -msgstr "" +msgstr "Uw nieuwe maillijst: %(listname)s" #: bin/remove_members:173 +#, fuzzy msgid "No such member: %(addr)s" -msgstr "" +msgstr "Niet bestaand lid: %(safeuser)s." #: bin/remove_members:178 msgid "User `%(addr)s' removed from list: %(listname)s." @@ -11152,8 +11368,9 @@ msgid "" msgstr "" #: bin/reset_pw.py:77 +#, fuzzy msgid "Changing passwords for list: %(listname)s" -msgstr "" +msgstr "Maillijst aanmaakresultaten:" #: bin/reset_pw.py:83 msgid "New password for member %(member)40s: %(randompw)s" @@ -11190,8 +11407,9 @@ msgid "%(listname)s %(msg)s not found as %(filename)s" msgstr "" #: bin/rmlist:105 +#, fuzzy msgid "No such list (or list already deleted): %(listname)s" -msgstr "" +msgstr "Er is geen lijst met de naam %(safelistname)s" #: bin/rmlist:108 msgid "No such list: %(listname)s. Removing its residual archives." @@ -11324,8 +11542,9 @@ msgid "No argument to -f given" msgstr "" #: bin/sync_members:172 +#, fuzzy msgid "Illegal option: %(opt)s" -msgstr "" +msgstr "Ongeldige lijstnaam: %(s)s" #: bin/sync_members:178 msgid "No listname given" @@ -11460,8 +11679,9 @@ msgid "" msgstr "" #: bin/update:107 +#, fuzzy msgid "Fixing language templates: %(listname)s" -msgstr "" +msgstr "Uw nieuwe maillijst: %(listname)s" #: bin/update:196 bin/update:711 msgid "WARNING: could not acquire lock for list: %(listname)s" @@ -11615,8 +11835,9 @@ msgid "done" msgstr "" #: bin/update:691 +#, fuzzy msgid "Updating mailing list: %(listname)s" -msgstr "" +msgstr "Uw nieuwe maillijst: %(listname)s" #: bin/update:694 msgid "Updating Usenet watermarks" @@ -11821,16 +12042,18 @@ msgid "" msgstr "" #: bin/withlist:175 +#, fuzzy msgid "Unlocking (but not saving) list: %(listname)s" -msgstr "" +msgstr "Uw nieuwe maillijst: %(listname)s" #: bin/withlist:179 msgid "Finalizing" msgstr "" #: bin/withlist:188 +#, fuzzy msgid "Loading list %(listname)s" -msgstr "" +msgstr "Uw nieuwe maillijst: %(listname)s" #: bin/withlist:190 msgid "(locked)" @@ -11841,8 +12064,9 @@ msgid "(unlocked)" msgstr "" #: bin/withlist:197 +#, fuzzy msgid "Unknown list: %(listname)s" -msgstr "" +msgstr "Uw nieuwe maillijst: %(listname)s" #: bin/withlist:237 msgid "No list name supplied." @@ -12048,8 +12272,9 @@ msgid "Password // URL" msgstr "" #: cron/mailpasswds:222 +#, fuzzy msgid "%(host)s mailing list memberships reminder" -msgstr "" +msgstr "%(listfullname)s maillijstherinnering" #: cron/nightly_gzip:19 msgid "" @@ -12135,8 +12360,8 @@ msgstr "" #~ " applied" #~ msgstr "" #~ "Tekst op te nemen in elke\n" -#~ " weigeringsmelding\n" #~ " naar gemodereerde leden als deze berichten naar de lijst " #~ "sturen." diff --git a/messages/no/LC_MESSAGES/mailman.po b/messages/no/LC_MESSAGES/mailman.po index d5ce9ccb..6bce808c 100755 --- a/messages/no/LC_MESSAGES/mailman.po +++ b/messages/no/LC_MESSAGES/mailman.po @@ -66,10 +66,12 @@ msgid "

                    Currently, there are no archives.

                    " msgstr "

                    Arkivet er for tiden tomt.

                    " #: Mailman/Archiver/HyperArch.py:821 +#, fuzzy msgid "Gzip'd Text%(sz)s" msgstr "Gzip'et tekst%(sz)s" #: Mailman/Archiver/HyperArch.py:826 +#, fuzzy msgid "Text%(sz)s" msgstr "Tekstt%(sz)s" @@ -142,30 +144,36 @@ msgid "Third" msgstr "Tredje" #: Mailman/Archiver/HyperArch.py:938 +#, fuzzy msgid "%(ord)s quarter %(year)i" msgstr "%(ord)s kvartal %(year)i" #: Mailman/Archiver/HyperArch.py:945 +#, fuzzy msgid "%(month)s %(year)i" -msgstr "" +msgstr "%(ord)s kvartal %(year)i" #: Mailman/Archiver/HyperArch.py:950 +#, fuzzy msgid "The Week Of Monday %(day)i %(month)s %(year)i" msgstr "Uken med mandag %(day)i %(month)s %(year)i" #: Mailman/Archiver/HyperArch.py:954 +#, fuzzy msgid "%(day)i %(month)s %(year)i" -msgstr "" +msgstr "Uken med mandag %(day)i %(month)s %(year)i" #: Mailman/Archiver/HyperArch.py:1054 msgid "Computing threaded index\n" msgstr "Bygger innholdsfortegnelse\n" #: Mailman/Archiver/HyperArch.py:1319 +#, fuzzy msgid "Updating HTML for article %(seq)s" msgstr "Oppdaterer HTML for artikkel %(seq)s" #: Mailman/Archiver/HyperArch.py:1326 +#, fuzzy msgid "article file %(filename)s is missing!" msgstr "artikkelfilen %(filename)s mangler!" @@ -182,6 +190,7 @@ msgid "Pickling archive state into " msgstr "Lagrer arkivets tilstand i en pickle: " #: Mailman/Archiver/pipermail.py:453 +#, fuzzy msgid "Updating index files for archive [%(archive)s]" msgstr "Oppdaterer indeksfil for arkivet [%(archive)s]" @@ -227,6 +236,7 @@ msgid "disabled address" msgstr "stoppet" #: Mailman/Bouncer.py:304 +#, fuzzy msgid " The last bounce received from you was dated %(date)s" msgstr " Sist mottatte returmelding fra deg var datert %(date)s" @@ -254,6 +264,7 @@ msgstr "Administrator" #: Mailman/Cgi/options.py:101 Mailman/Cgi/private.py:108 #: Mailman/Cgi/rmlist.py:75 Mailman/Cgi/roster.py:59 #: Mailman/Cgi/subscribe.py:67 +#, fuzzy msgid "No such list %(safelistname)s" msgstr "Listen finnes ikke: %(safelistname)s" @@ -326,6 +337,7 @@ msgstr "" "%(rm)r" #: Mailman/Cgi/admin.py:268 +#, fuzzy msgid "%(hostname)s mailing lists - Admin Links" msgstr "Epostlister på %(hostname)s - Administrativ tilgang" @@ -338,6 +350,7 @@ msgid "Mailman" msgstr "Mailman" #: Mailman/Cgi/admin.py:310 +#, fuzzy msgid "" "

                    There currently are no publicly-advertised %(mailmanlink)s\n" " mailing lists on %(hostname)s." @@ -346,6 +359,7 @@ msgstr "" "%(hostname)s.
                    " #: Mailman/Cgi/admin.py:316 +#, fuzzy msgid "" "

                    Below is the collection of publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s. Click on a list\n" @@ -360,6 +374,7 @@ msgid "right " msgstr "riktige " #: Mailman/Cgi/admin.py:325 +#, fuzzy msgid "" "To visit the administrators configuration page for an\n" " unadvertised list, open a URL similar to this one, but with a '/' " @@ -405,6 +420,7 @@ msgid "No valid variable name found." msgstr "Fant ingen gyldige variabelnavn." #: Mailman/Cgi/admin.py:395 +#, fuzzy msgid "" "%(realname)s Mailing list Configuration Help\n" "
                    %(varname)s Option" @@ -413,6 +429,7 @@ msgstr "" "
                    Innstilling: %(varname)s" #: Mailman/Cgi/admin.py:402 +#, fuzzy msgid "Mailman %(varname)s List Option Help" msgstr "Innstilling: %(varname)s" @@ -433,14 +450,17 @@ msgstr "" "dvendig." #: Mailman/Cgi/admin.py:431 +#, fuzzy msgid "return to the %(categoryname)s options page." msgstr "gå tilbake til %(categoryname)s" #: Mailman/Cgi/admin.py:446 +#, fuzzy msgid "%(realname)s Administration (%(label)s)" msgstr "%(realname)s Administrasjon (%(label)s)" #: Mailman/Cgi/admin.py:447 +#, fuzzy msgid "%(realname)s mailing list administration
                    %(label)s Section" msgstr "%(realname)s administrasjon
                    %(label)s" @@ -523,6 +543,7 @@ msgid "Value" msgstr "Verdi" #: Mailman/Cgi/admin.py:668 +#, fuzzy msgid "" "Badly formed options entry:\n" " %(record)s" @@ -624,10 +645,12 @@ msgid "Move rule down" msgstr "Flytt regel nedover" #: Mailman/Cgi/admin.py:870 +#, fuzzy msgid "
                    (Edit %(varname)s)" msgstr "
                    (Redigere %(varname)s)" #: Mailman/Cgi/admin.py:872 +#, fuzzy msgid "
                    (Details for %(varname)s)" msgstr "
                    (Detaljer for %(varname)s)" @@ -668,6 +691,7 @@ msgid "(help)" msgstr "(hjelp)" #: Mailman/Cgi/admin.py:930 +#, fuzzy msgid "Find member %(link)s:" msgstr "Finne medlem %(link)s:" @@ -680,10 +704,12 @@ msgid "Bad regular expression: " msgstr "Ugyldig regexp-uttrykk: " #: Mailman/Cgi/admin.py:1013 +#, fuzzy msgid "%(allcnt)s members total, %(membercnt)s shown" msgstr "Totalt %(allcnt)s medlemmer, bare %(membercnt)s er vist." #: Mailman/Cgi/admin.py:1016 +#, fuzzy msgid "%(allcnt)s members total" msgstr "Totalt %(allcnt)s medlemmer" @@ -872,6 +898,7 @@ msgstr "" "område:" #: Mailman/Cgi/admin.py:1221 +#, fuzzy msgid "from %(start)s to %(end)s" msgstr "fra %(start)s til %(end)s" @@ -1012,6 +1039,7 @@ msgid "Change list ownership passwords" msgstr "Endre admin/moderator passord" #: Mailman/Cgi/admin.py:1364 +#, fuzzy msgid "" "The list administrators are the people who have ultimate control " "over\n" @@ -1182,8 +1210,9 @@ msgid "%(schange_to)s is already a member" msgstr " er allerede medlem" #: Mailman/Cgi/admin.py:1620 +#, fuzzy msgid "%(schange_to)s matches banned pattern %(spat)s" -msgstr "" +msgstr " er allerede medlem" #: Mailman/Cgi/admin.py:1622 msgid "Address %(schange_from)s changed to %(schange_to)s" @@ -1224,6 +1253,7 @@ msgid "Not subscribed" msgstr "Ikke påmeldt" #: Mailman/Cgi/admin.py:1773 +#, fuzzy msgid "Ignoring changes to deleted member: %(user)s" msgstr "Ser bort i fra endring av et medlem som er utmeldt: %(user)s" @@ -1236,10 +1266,12 @@ msgid "Error Unsubscribing:" msgstr "Feil under utmelding av:" #: Mailman/Cgi/admindb.py:235 Mailman/Cgi/admindb.py:248 +#, fuzzy msgid "%(realname)s Administrative Database" msgstr "Administrativ database for listen %(realname)s" #: Mailman/Cgi/admindb.py:238 +#, fuzzy msgid "%(realname)s Administrative Database Results" msgstr "Resultat fra den administrative databasen til listen %(realname)s" @@ -1268,6 +1300,7 @@ msgid "Discard all messages marked Defer" msgstr "Forkast alle meldinger merket Avvent" #: Mailman/Cgi/admindb.py:298 +#, fuzzy msgid "all of %(esender)s's held messages." msgstr "alle meldinger fra %(esender)s, som holdes tilbake for godkjenning." @@ -1288,6 +1321,7 @@ msgid "list of available mailing lists." msgstr "Liste over alle tilgjengelig epostlister." #: Mailman/Cgi/admindb.py:362 +#, fuzzy msgid "You must specify a list name. Here is the %(link)s" msgstr "Du må oppgi et navn på en liste. Her er %(link)s" @@ -1370,6 +1404,7 @@ msgid "The sender is now a member of this list" msgstr "Avsender er nå medlem av denne listen" #: Mailman/Cgi/admindb.py:568 +#, fuzzy msgid "Add %(esender)s to one of these sender filters:" msgstr "Legge inn %(esender)s i en av disse avsenderfiltrene:" @@ -1390,6 +1425,7 @@ msgid "Rejects" msgstr "Avslår" #: Mailman/Cgi/admindb.py:584 +#, fuzzy msgid "" "Ban %(esender)s from ever subscribing to this\n" " mailing list" @@ -1402,6 +1438,7 @@ msgid "" msgstr "Klikk på meldingens nummer for å se den, eller du kan " #: Mailman/Cgi/admindb.py:591 +#, fuzzy msgid "view all messages from %(esender)s" msgstr "se alle meldinger fra %(esender)s" @@ -1529,6 +1566,7 @@ msgstr "" "Forespørselen ble derfor avbrutt." #: Mailman/Cgi/confirm.py:178 +#, fuzzy msgid "System error, bad content: %(content)s" msgstr "Systemfeil, ugyldig innhold: %(content)s" @@ -1565,6 +1603,7 @@ msgid "Confirm subscription request" msgstr "Bekreft søknad om medlemskap" #: Mailman/Cgi/confirm.py:259 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " subscription request to the mailing list %(listname)s. Your\n" @@ -1598,6 +1637,7 @@ msgstr "" "å melde deg på listen.." #: Mailman/Cgi/confirm.py:275 +#, fuzzy msgid "" "Your confirmation is required in order to continue with\n" " the subscription request to the mailing list %(listname)s.\n" @@ -1650,6 +1690,7 @@ msgid "Preferred language:" msgstr "Ønsket språk:" #: Mailman/Cgi/confirm.py:317 +#, fuzzy msgid "Subscribe to list %(listname)s" msgstr "Melde meg på listen %(listname)s" @@ -1666,6 +1707,7 @@ msgid "Awaiting moderator approval" msgstr "Venter på godkjenning av moderator" #: Mailman/Cgi/confirm.py:386 +#, fuzzy msgid "" " You have successfully confirmed your subscription request to " "the\n" @@ -1725,6 +1767,7 @@ msgid "Subscription request confirmed" msgstr "Søknad om medlemsskap bekreftet" #: Mailman/Cgi/confirm.py:418 +#, fuzzy msgid "" " You have successfully confirmed your subscription request for\n" " \"%(addr)s\" to the %(listname)s mailing list. A separate\n" @@ -1754,6 +1797,7 @@ msgid "Unsubscription request confirmed" msgstr "Søknad om utmelding bekreftet" #: Mailman/Cgi/confirm.py:469 +#, fuzzy msgid "" " You have successfully unsubscribed from the %(listname)s " "mailing\n" @@ -1774,6 +1818,7 @@ msgid "Not available" msgstr "Ikke tilgjengelig" #: Mailman/Cgi/confirm.py:498 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " unsubscription request from the mailing list %(listname)s. " @@ -1849,6 +1894,7 @@ msgid "Change of address request confirmed" msgstr "Endring av adresse bekreftet" #: Mailman/Cgi/confirm.py:571 +#, fuzzy msgid "" " You have successfully changed your address on the %(listname)s\n" " mailing list from %(oldaddr)s to %(newaddr)s. " @@ -1870,6 +1916,7 @@ msgid "globally" msgstr "globalt" #: Mailman/Cgi/confirm.py:605 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " change of address request for the mailing list %(listname)s. " @@ -1933,6 +1980,7 @@ msgid "Sender discarded message via web." msgstr "Avsenderen trakk tilbake sin melding via websiden." #: Mailman/Cgi/confirm.py:674 +#, fuzzy msgid "" "The held message with the Subject:\n" " header %(subject)s could not be found. The most " @@ -1951,6 +1999,7 @@ msgid "Posted message canceled" msgstr "Melding ble trukket tilbake" #: Mailman/Cgi/confirm.py:685 +#, fuzzy msgid "" " You have successfully canceled the posting of your message with\n" " the Subject: header %(subject)s to the mailing list\n" @@ -1972,6 +2021,7 @@ msgstr "" "listeadministratoren." #: Mailman/Cgi/confirm.py:735 +#, fuzzy msgid "" "Your confirmation is required in order to cancel the\n" " posting of your message to the mailing list %(listname)s:\n" @@ -2025,6 +2075,7 @@ msgid "Membership re-enabled." msgstr "Du vil nå motta epost fra listen igjen." #: Mailman/Cgi/confirm.py:798 +#, fuzzy msgid "" " You have successfully re-enabled your membership in the\n" " %(listname)s mailing list. You can now list information page." msgstr "" "Beklager, men du er allerede meldt ut av denne epostlisten.\n" -"For å melde deg på listen igjen, gå til listens webside." +"For å melde deg på listen igjen, gå til listens webside." #: Mailman/Cgi/confirm.py:842 msgid "not available" msgstr "ikke tilgjengelig" #: Mailman/Cgi/confirm.py:846 +#, fuzzy msgid "" "Your membership in the %(realname)s mailing list is\n" " currently disabled due to excessive bounces. Your confirmation is\n" @@ -2122,10 +2175,12 @@ msgid "administrative list overview" msgstr "administrativ side for epostlisten" #: Mailman/Cgi/create.py:115 +#, fuzzy msgid "List name must not include \"@\": %(safelistname)s" msgstr "Listenavnet må ikke inneholde \"@\": %(safelistname)s" #: Mailman/Cgi/create.py:122 +#, fuzzy msgid "List already exists: %(safelistname)s" msgstr "Listen finnes allerede: %(safelistname)s" @@ -2160,18 +2215,22 @@ msgid "You are not authorized to create new mailing lists" msgstr "Du har ikke tilgang til å opprette nye epostlister" #: Mailman/Cgi/create.py:182 +#, fuzzy msgid "Unknown virtual host: %(safehostname)s" msgstr "Ukjent virtuell host: %(safehostname)s" #: Mailman/Cgi/create.py:218 bin/newlist:219 +#, fuzzy msgid "Bad owner email address: %(s)s" msgstr "Ugyldig epostadresse: %(s)s" #: Mailman/Cgi/create.py:223 bin/newlist:182 bin/newlist:223 +#, fuzzy msgid "List already exists: %(listname)s" msgstr "Listen finnes allerede: %(listname)s" #: Mailman/Cgi/create.py:231 bin/newlist:217 +#, fuzzy msgid "Illegal list name: %(s)s" msgstr "Ulovlig listenavn: %(s)s" @@ -2184,6 +2243,7 @@ msgstr "" "Kontakt systemadministrator for å få hjelp." #: Mailman/Cgi/create.py:273 bin/newlist:265 +#, fuzzy msgid "Your new mailing list: %(listname)s" msgstr "Din nye epostliste: %(listname)s" @@ -2192,6 +2252,7 @@ msgid "Mailing list creation results" msgstr "Resultat av opprettelse" #: Mailman/Cgi/create.py:288 +#, fuzzy msgid "" "You have successfully created the mailing list\n" " %(listname)s and notification has been sent to the list owner\n" @@ -2215,6 +2276,7 @@ msgid "Create another list" msgstr "Opprette enda en liste" #: Mailman/Cgi/create.py:312 +#, fuzzy msgid "Create a %(hostname)s Mailing List" msgstr "Opprette en epostliste på %(hostname)s" @@ -2312,6 +2374,7 @@ msgstr "" "godkjenning av listemoderator." #: Mailman/Cgi/create.py:432 +#, fuzzy msgid "" "Initial list of supported languages.

                    Note that if you do not\n" " select at least one initial language, the list will use the server\n" @@ -2417,6 +2480,7 @@ msgid "List name is required." msgstr "Listens navn er påkrevd" #: Mailman/Cgi/edithtml.py:154 +#, fuzzy msgid "%(realname)s -- Edit html for %(template_info)s" msgstr "%(realname)s -- Redigere html for %(template_info)s" @@ -2425,10 +2489,12 @@ msgid "Edit HTML : Error" msgstr "Redigere HTML : Feil" #: Mailman/Cgi/edithtml.py:161 +#, fuzzy msgid "%(safetemplatename)s: Invalid template" msgstr "%(safetemplatename)s: Ugyldig mal" #: Mailman/Cgi/edithtml.py:166 Mailman/Cgi/edithtml.py:167 +#, fuzzy msgid "%(realname)s -- HTML Page Editing" msgstr "%(realname)s -- Redigere HTML-kode for websider" @@ -2487,10 +2553,12 @@ msgid "HTML successfully updated." msgstr "HTML-koden er oppdatert." #: Mailman/Cgi/listinfo.py:89 +#, fuzzy msgid "%(hostname)s Mailing Lists" msgstr "Epostlister på %(hostname)s" #: Mailman/Cgi/listinfo.py:127 +#, fuzzy msgid "" "

                    There currently are no publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s." @@ -2499,6 +2567,7 @@ msgstr "" "på %(hostname)s." #: Mailman/Cgi/listinfo.py:131 +#, fuzzy msgid "" "

                    Below is a listing of all the public mailing lists on\n" " %(hostname)s. Click on a list name to get more information " @@ -2518,6 +2587,7 @@ msgid "right" msgstr "riktige" #: Mailman/Cgi/listinfo.py:140 +#, fuzzy msgid "" " To visit the general information page for an unadvertised list,\n" " open a URL similar to this one, but with a '/' and the %(adj)s\n" @@ -2580,6 +2650,7 @@ msgstr "Feil/Ugyldig epostadresse" #: Mailman/Cgi/options.py:183 Mailman/Cgi/options.py:240 #: Mailman/Cgi/options.py:264 +#, fuzzy msgid "No such member: %(safeuser)s." msgstr "Medlemmet finnes ikke: %(safeuser)s." @@ -2630,6 +2701,7 @@ msgid "Note: " msgstr "" #: Mailman/Cgi/options.py:378 +#, fuzzy msgid "List subscriptions for %(safeuser)s on %(hostname)s" msgstr "Vise listemedlemskap for %(safeuser)s på %(hostname)s" @@ -2657,6 +2729,7 @@ msgid "You are already using that email address" msgstr "Den epostadressen er du allerede påmeldt listen med" #: Mailman/Cgi/options.py:459 +#, fuzzy msgid "" "The new address you requested %(newaddr)s is already a member of the\n" "%(listname)s mailing list, however you have also requested a global change " @@ -2669,6 +2742,7 @@ msgstr "" "alle andre epostlister som inneholder %(safeuser)s bli endret. " #: Mailman/Cgi/options.py:468 +#, fuzzy msgid "The new address is already a member: %(newaddr)s" msgstr "Den nye adressen er allerede påmeldt: %(newaddr)s" @@ -2677,6 +2751,7 @@ msgid "Addresses may not be blank" msgstr "Adressene kan ikke være tomme" #: Mailman/Cgi/options.py:488 +#, fuzzy msgid "A confirmation message has been sent to %(newaddr)s. " msgstr "" "En forespørsel om bekreftelse er sendt i en epost til %(newaddr)s. " @@ -2690,6 +2765,7 @@ msgid "Illegal email address provided" msgstr "Ulovlig epostadresse angitt" #: Mailman/Cgi/options.py:501 +#, fuzzy msgid "%(newaddr)s is already a member of the list." msgstr "%(newaddr)s er allerede medlem av listen." @@ -2775,6 +2851,7 @@ msgstr "" "Du vil motta en melding så snart de har tatt en avgjørelse." #: Mailman/Cgi/options.py:623 +#, fuzzy msgid "" "You have been successfully unsubscribed from the\n" " mailing list %(fqdn_listname)s. If you were receiving digest\n" @@ -2868,6 +2945,7 @@ msgid "day" msgstr "dag" #: Mailman/Cgi/options.py:900 +#, fuzzy msgid "%(days)d %(units)s" msgstr "%(days)d %(units)s" @@ -2880,6 +2958,7 @@ msgid "No topics defined" msgstr "Ingen emner er definert" #: Mailman/Cgi/options.py:940 +#, fuzzy msgid "" "\n" "You are subscribed to this list with the case-preserved address\n" @@ -2889,6 +2968,7 @@ msgstr "" "Du er medlem av denne listen med epostadressen %(cpuser)s." #: Mailman/Cgi/options.py:956 +#, fuzzy msgid "%(realname)s list: member options login page" msgstr "%(realname)s: innlogging til personlige innstillinger" @@ -2897,10 +2977,12 @@ msgid "email address and " msgstr "epostadressen og " #: Mailman/Cgi/options.py:960 +#, fuzzy msgid "%(realname)s list: member options for user %(safeuser)s" msgstr "%(realname)s: personlige innstillinger for %(safeuser)s" #: Mailman/Cgi/options.py:986 +#, fuzzy msgid "" "In order to change your membership option, you must\n" " first log in by giving your %(extra)smembership password in the section\n" @@ -2977,6 +3059,7 @@ msgid "" msgstr "" #: Mailman/Cgi/options.py:1140 +#, fuzzy msgid "Requested topic is not valid: %(topicname)s" msgstr "Emnet er ikke gyldig: %(topicname)s" @@ -3005,6 +3088,7 @@ msgid "Private archive - \"./\" and \"../\" not allowed in URL." msgstr "" #: Mailman/Cgi/private.py:109 +#, fuzzy msgid "Private Archive Error - %(msg)s" msgstr "Feil i privat arkiv - %(msg)s" @@ -3042,12 +3126,14 @@ msgid "Mailing list deletion results" msgstr "Resultat av sletting av epostliste" #: Mailman/Cgi/rmlist.py:187 +#, fuzzy msgid "" "You have successfully deleted the mailing list\n" " %(listname)s." msgstr "Du har slettet epostlisten %(listname)s." #: Mailman/Cgi/rmlist.py:191 +#, fuzzy msgid "" "There were some problems deleting the mailing list\n" " %(listname)s. Contact your site administrator at " @@ -3058,6 +3144,7 @@ msgstr "" "Kontakt systemadministratoren på %(sitelist)s for flere detaljer." #: Mailman/Cgi/rmlist.py:208 +#, fuzzy msgid "Permanently remove mailing list %(realname)s" msgstr "Fjerne epostlisten %(realname)s permanent" @@ -3129,6 +3216,7 @@ msgid "Invalid options to CGI script" msgstr "Ugyldige parametre til CGI skriptet" #: Mailman/Cgi/roster.py:118 +#, fuzzy msgid "%(realname)s roster authentication failed." msgstr "Tilgang til %(realname)s feilet." @@ -3201,6 +3289,7 @@ msgstr "" "en epost med nærmere instruksjoner." #: Mailman/Cgi/subscribe.py:262 +#, fuzzy msgid "" "The email address you supplied is banned from this\n" " mailing list. If you think this restriction is erroneous, please\n" @@ -3226,6 +3315,7 @@ msgstr "" "epostadresse." #: Mailman/Cgi/subscribe.py:278 +#, fuzzy msgid "" "Confirmation from your email address is required, to prevent anyone from\n" "subscribing you without permission. Instructions are being sent to you at\n" @@ -3239,6 +3329,7 @@ msgstr "" "bekreftet at du vil være med på listen." #: Mailman/Cgi/subscribe.py:290 +#, fuzzy msgid "" "Your subscription request was deferred because %(x)s. Your request has " "been\n" @@ -3260,6 +3351,7 @@ msgid "Mailman privacy alert" msgstr "Sikkerhetsmelding fra Mailman" #: Mailman/Cgi/subscribe.py:316 +#, fuzzy msgid "" "An attempt was made to subscribe your address to the mailing list\n" "%(listaddr)s. You are already subscribed to this mailing list.\n" @@ -3303,6 +3395,7 @@ msgid "This list only supports digest delivery." msgstr "Denne listen støtter kun sammendrag-modus." #: Mailman/Cgi/subscribe.py:344 +#, fuzzy msgid "You have been successfully subscribed to the %(realname)s mailing list." msgstr "Du er nå meldt på epostlisten %(realname)s." @@ -3437,26 +3530,32 @@ msgid "n/a" msgstr "" #: Mailman/Commands/cmd_info.py:44 +#, fuzzy msgid "List name: %(listname)s" msgstr "Listenavn: %(listname)s" #: Mailman/Commands/cmd_info.py:45 +#, fuzzy msgid "Description: %(description)s" msgstr "Beskrivelse: %(description)s" #: Mailman/Commands/cmd_info.py:46 +#, fuzzy msgid "Postings to: %(postaddr)s" msgstr "Adresse: %(postaddr)s" #: Mailman/Commands/cmd_info.py:47 +#, fuzzy msgid "List Helpbot: %(requestaddr)s" msgstr "Kommandoadresse: %(requestaddr)s" #: Mailman/Commands/cmd_info.py:48 +#, fuzzy msgid "List Owners: %(owneraddr)s" msgstr "Listens eier(e): %(owneraddr)s" #: Mailman/Commands/cmd_info.py:49 +#, fuzzy msgid "More information: %(listurl)s" msgstr "Mer informasjon: %(listurl)s" @@ -3481,18 +3580,22 @@ msgstr "" " GNU Mailman tjeneren.\n" #: Mailman/Commands/cmd_lists.py:44 +#, fuzzy msgid "Public mailing lists at %(hostname)s:" msgstr "Epostlister offentlig tilgjengelig på %(hostname)s:" #: Mailman/Commands/cmd_lists.py:66 +#, fuzzy msgid "%(i)3d. List name: %(realname)s" msgstr "%(i)3d. Listenavn: %(realname)s" #: Mailman/Commands/cmd_lists.py:67 +#, fuzzy msgid " Description: %(description)s" msgstr " Beskrivelse: %(description)s" #: Mailman/Commands/cmd_lists.py:68 +#, fuzzy msgid " Requests to: %(requestaddr)s" msgstr " Forespørsler til: %(requestaddr)s" @@ -3527,12 +3630,14 @@ msgstr "" " epostadressen.\n" #: Mailman/Commands/cmd_password.py:51 Mailman/Commands/cmd_password.py:66 +#, fuzzy msgid "Your password is: %(password)s" msgstr "Passordet ditt er: %(password)s" #: Mailman/Commands/cmd_password.py:57 Mailman/Commands/cmd_password.py:72 #: Mailman/Commands/cmd_password.py:95 Mailman/Commands/cmd_password.py:121 #: Mailman/Commands/cmd_set.py:149 Mailman/Commands/cmd_set.py:219 +#, fuzzy msgid "You are not a member of the %(listname)s mailing list" msgstr "Du er ikke medlem av epostlisten %(listname)s" @@ -3711,6 +3816,7 @@ msgstr "" " passordet ditt én gang i måneden.\n" #: Mailman/Commands/cmd_set.py:122 +#, fuzzy msgid "Bad set command: %(subcmd)s" msgstr "Ugyldig innstilling: %(subcmd)s" @@ -3731,6 +3837,7 @@ msgid "on" msgstr "på" #: Mailman/Commands/cmd_set.py:154 +#, fuzzy msgid " ack %(onoff)s" msgstr " bekreft: %(onoff)s" @@ -3768,22 +3875,27 @@ msgid "due to bounces" msgstr "på grunn av returmeldinger" #: Mailman/Commands/cmd_set.py:186 +#, fuzzy msgid " %(status)s (%(how)s on %(date)s)" msgstr " %(status)s (%(how)s %(date)s)" #: Mailman/Commands/cmd_set.py:192 +#, fuzzy msgid " myposts %(onoff)s" msgstr " ikke-mine: %(onoff)s" #: Mailman/Commands/cmd_set.py:195 +#, fuzzy msgid " hide %(onoff)s" msgstr " skjult: %(onoff)s" #: Mailman/Commands/cmd_set.py:199 +#, fuzzy msgid " duplicates %(onoff)s" msgstr " unngå duplikater: %(onoff)s" #: Mailman/Commands/cmd_set.py:203 +#, fuzzy msgid " reminders %(onoff)s" msgstr " passordpåminnelse: %(onoff)s" @@ -3792,6 +3904,7 @@ msgid "You did not give the correct password" msgstr "Du har oppgitt feil passord" #: Mailman/Commands/cmd_set.py:236 Mailman/Commands/cmd_set.py:283 +#, fuzzy msgid "Bad argument: %(arg)s" msgstr "Ugyldige parametre: %(arg)s" @@ -3867,6 +3980,7 @@ msgstr "" " '<' og '>', og uten apostrofer!)\n" #: Mailman/Commands/cmd_subscribe.py:62 +#, fuzzy msgid "Bad digest specifier: %(arg)s" msgstr "Ugyldig sammendrag-modus parameter: %(arg)s" @@ -3875,6 +3989,7 @@ msgid "No valid address found to subscribe" msgstr "Ingen gyldig epostadresse for påmelding ble funnet" #: Mailman/Commands/cmd_subscribe.py:113 +#, fuzzy msgid "" "The email address you supplied is banned from this mailing list.\n" "If you think this restriction is erroneous, please contact the list\n" @@ -3914,6 +4029,7 @@ msgid "This list only supports digest subscriptions!" msgstr "Denne listen støtter kun sammendrag-modus!" #: Mailman/Commands/cmd_subscribe.py:146 +#, fuzzy msgid "" "Your subscription request has been forwarded to the list administrator\n" "at %(listowner)s for review." @@ -3949,6 +4065,7 @@ msgstr "" " (uten '<' og '>', og uten apostrofer!)\n" #: Mailman/Commands/cmd_unsubscribe.py:62 +#, fuzzy msgid "%(address)s is not a member of the %(listname)s mailing list" msgstr "%(address)s er ikke medlem av epostlisten %(listname)s." @@ -4196,6 +4313,7 @@ msgid "Chinese (Taiwan)" msgstr "Kinesisk (Taiwan)" #: Mailman/Deliverer.py:53 +#, fuzzy msgid "" "Note: Since this is a list of mailing lists, administrative\n" "notices like the password reminder will be sent to\n" @@ -4211,14 +4329,17 @@ msgid " (Digest mode)" msgstr " (Sammendrag-modus)" #: Mailman/Deliverer.py:79 +#, fuzzy msgid "Welcome to the \"%(realname)s\" mailing list%(digmode)s" msgstr "Velkommen til epostlisten \"%(realname)s\"%(digmode)s" #: Mailman/Deliverer.py:89 +#, fuzzy msgid "You have been unsubscribed from the %(realname)s mailing list" msgstr "Du er nå fjernet fra epostlisten \"%(realname)s\"" #: Mailman/Deliverer.py:116 +#, fuzzy msgid "%(listfullname)s mailing list reminder" msgstr "Påminnelse fra epostlisten %(listfullname)s" @@ -4231,6 +4352,7 @@ msgid "Hostile subscription attempt detected" msgstr "Ulovlig påmeldingsforsøk oppdaget" #: Mailman/Deliverer.py:169 +#, fuzzy msgid "" "%(address)s was invited to a different mailing\n" "list, but in a deliberate malicious attempt they tried to confirm the\n" @@ -4243,6 +4365,7 @@ msgstr "" "Du trenger ikke foreta deg noe, da forsøket ikke lyktes." #: Mailman/Deliverer.py:188 +#, fuzzy msgid "" "You invited %(address)s to your list, but in a\n" "deliberate malicious attempt, they tried to confirm the invitation to a\n" @@ -4256,6 +4379,7 @@ msgstr "" "selvsagt ikke lyktes." #: Mailman/Deliverer.py:221 +#, fuzzy msgid "%(listname)s mailing list probe message" msgstr "Kontrollmelding fra epostlisten %(listname)s" @@ -4462,8 +4586,8 @@ msgid "" " membership.\n" "\n" "

                    You can control both the\n" -" number\n" +" number\n" " of reminders the member will receive and the\n" " Du kan bestemme hvor mange advarsler\n" +"

                    Du kan bestemme hvor mange advarsler\n" "medlemmet skal få og hvor ofte\n" "han/hun skal motta slike advarsler.\n" @@ -4669,8 +4793,8 @@ msgid "" " No those messages too will get discarded. You may " "want\n" " to set up an\n" -" autoresponse\n" +" autoresponse\n" " message for email to the -owner and -admin address." msgstr "" "Mailmans automatiske returhåndtering er svært robust, men det er likevel " @@ -4756,6 +4880,7 @@ msgstr "" "Det vil i tillegg alltid bli gjort et forsøk på gi beskjed til medlemmet." #: Mailman/Gui/Bounce.py:194 +#, fuzzy msgid "" "Bad value for %(property)s: %(val)s" @@ -4891,8 +5016,8 @@ msgid "" "Use this option to remove each message attachment that does\n" " not have a matching content type. Requirements and formats " "are\n" -" exactly like filter_mime_types.\n" "\n" "

                    Note: if you add entries to this list but don't add\n" @@ -5007,6 +5132,7 @@ msgstr "" "serveradministratoren har tillatt det." #: Mailman/Gui/ContentFilter.py:171 +#, fuzzy msgid "Bad MIME type ignored: %(spectype)s" msgstr "Hopper over ugyldig MIME type: %(spectype)s" @@ -5116,6 +5242,7 @@ msgstr "" "ikke er tom?" #: Mailman/Gui/Digest.py:145 +#, fuzzy msgid "" "The next digest will be sent as volume\n" " %(volume)s, number %(number)s" @@ -5131,14 +5258,17 @@ msgid "There was no digest to send." msgstr "Det var ingen samle-epost som skulle sendes." #: Mailman/Gui/GUIBase.py:173 +#, fuzzy msgid "Invalid value for variable: %(property)s" msgstr "Ugyldig verdi for: %(property)s" #: Mailman/Gui/GUIBase.py:178 +#, fuzzy msgid "Bad email address for option %(property)s: %(error)s" msgstr "Ugyldig epostadresse for innstillingen %(property)s: %(error)s" #: Mailman/Gui/GUIBase.py:204 +#, fuzzy msgid "" "The following illegal substitution variables were\n" " found in the %(property)s string:\n" @@ -5152,6 +5282,7 @@ msgstr "" "

                    Det kan oppstå feil med listen din med mindre du retter opp dette." #: Mailman/Gui/GUIBase.py:218 +#, fuzzy msgid "" "Your %(property)s string appeared to\n" " have some correctable problems in its new value.\n" @@ -5255,8 +5386,8 @@ msgid "" "

                    In order to split the list ownership duties into\n" " administrators and moderators, you must\n" " set a separate moderator password,\n" -" and also provide the email\n" +" and also provide the email\n" " addresses of the list moderators. Note that the field you\n" " are changing here specifies the list administrators." msgstr "" @@ -5585,13 +5716,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5617,8 +5748,8 @@ msgstr "" "Egendefinert adresse, vil Mailman legge til, evt. erstatte,\n" "et Reply-To: felt. (Egendefinert adresse setter inn " "verdien\n" -"av innstillingen reply_to_address).\n" +"av innstillingen reply_to_address).\n" "\n" "

                    Det finnes mange grunner til å ikke innføre eller erstatte " "Reply-To:\n" @@ -5654,8 +5785,8 @@ msgstr "Egendefinert Reply-To: adresse." msgid "" "This is the address set in the Reply-To: header\n" " when the reply_goes_to_list\n" +" href=\"?VARHELP=general/" +"reply_goes_to_list\">reply_goes_to_list\n" " option is set to Explicit address.\n" "\n" "

                    There are many reasons not to introduce or override the\n" @@ -5663,13 +5794,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5691,8 +5822,8 @@ msgid "" " Reply-To: header, it will not be changed." msgstr "" "Her definerer du adressen som skal settes i Reply-To: feltet\n" -"når innstillingen reply_goes_to_list\n" +"når innstillingen reply_goes_to_list\n" "er satt til Egendefinert adresse.\n" "\n" "

                    Det finnes mange grunner til å ikke innføre eller erstatte " @@ -5751,8 +5882,8 @@ msgstr "" "andre lister. Når denne er satt, vil bekreftelser og meldinger med " "passord\n" "bli sendt til en egen adresse som beregnes ut fra epostadressen som\n" -"er påmeldt listen - verdien av innstillingen \"umbrella_member_suffix" -"\"\n" +"er påmeldt listen - verdien av innstillingen " +"\"umbrella_member_suffix\"\n" "brukes til dette. Denne verdien legges til medlemmets kontonavn (det som\n" "står før @-tegnet)." @@ -5776,8 +5907,8 @@ msgid "" " member list addresses, but rather to the owner of those member\n" " lists. In that case, the value of this setting is appended to\n" " the member's account name for such notices. `-owner' is the\n" -" typical choice. This setting has no effect when \"umbrella_list" -"\"\n" +" typical choice. This setting has no effect when " +"\"umbrella_list\"\n" " is \"No\"." msgstr "" "Når \"umbrella_list\" indikerer at denne listen har andre epostlister " @@ -6755,6 +6886,7 @@ msgstr "" "på listen mot deres vilje." #: Mailman/Gui/Privacy.py:104 +#, fuzzy msgid "" "This section allows you to configure subscription and\n" " membership exposure policy. You can also control whether this\n" @@ -6955,8 +7087,8 @@ msgid "" " either individually or as a group. Any\n" " posting from a non-member who is not explicitly accepted,\n" " rejected, or discarded, will have their posting filtered by the\n" -" general\n" +" general\n" " non-member rules.\n" "\n" "

                    In the text boxes below, add one address per line; start the\n" @@ -6992,8 +7124,8 @@ msgstr "" "enten individuelt eller som en gruppe. All epost fra ikke-medlemmer,\n" "som ikke spesifikt blir godkjent, sendt i retur, eller forkastet, vil bli " "behandlet\n" -"alt etter hva generelle regler for ikke-medlemmer sier.\n" +"alt etter hva generelle regler for ikke-medlemmer sier.\n" "\n" "

                    I tekstboksene nedenfor legger du inn en epostadresse per linje.\n" "Du kan også legge inn moderation flag which says\n" " whether messages from the list member can be posted directly " @@ -7073,8 +7206,8 @@ msgid "" "If a member posts this many times, within a period of time\n" " the member is automatically moderated. Use 0 to disable. " "See\n" -" member_verbosity_interval for details on the time " "period.\n" "\n" @@ -7508,6 +7641,7 @@ msgstr "" "listemoderatoren?" #: Mailman/Gui/Privacy.py:494 +#, fuzzy msgid "" "Text to include in any rejection notice to be sent to\n" " non-members who post to this list. This notice can include\n" @@ -7761,6 +7895,7 @@ msgstr "" "tatt i bruk." #: Mailman/Gui/Privacy.py:693 +#, fuzzy msgid "" "The header filter rule pattern\n" " '%(safepattern)s' is not a legal regular expression. This\n" @@ -7811,8 +7946,8 @@ msgid "" "

                    The body of the message can also be optionally scanned for\n" " Subject: and Keywords: headers, as\n" " specified by the topics_bodylines_limit\n" +" href=\"?VARHELP=topics/" +"topics_bodylines_limit\">topics_bodylines_limit\n" " configuration variable." msgstr "" "Emnefilteret kategoriserer hver epost som kommer til listen,\n" @@ -7903,6 +8038,7 @@ msgstr "" "tatt i bruk." #: Mailman/Gui/Topics.py:135 +#, fuzzy msgid "" "The topic pattern '%(safepattern)s' is not a\n" " legal regular expression. It will be discarded." @@ -8113,6 +8249,7 @@ msgid "%(listinfo_link)s list run by %(owner_link)s" msgstr "Epostlisten %(listinfo_link)s administreres av %(owner_link)s" #: Mailman/HTMLFormatter.py:57 +#, fuzzy msgid "%(realname)s administrative interface" msgstr "Administrativ side for %(realname)s" @@ -8121,6 +8258,7 @@ msgid " (requires authorization)" msgstr " (krever innlogging)" #: Mailman/HTMLFormatter.py:61 +#, fuzzy msgid "Overview of all %(hostname)s mailing lists" msgstr "Liste over alle epostlister på %(hostname)s" @@ -8141,6 +8279,7 @@ msgid "; it was disabled by the list administrator" msgstr "; av listeadministratoren" #: Mailman/HTMLFormatter.py:146 +#, fuzzy msgid "" "; it was disabled due to excessive bounces. The\n" " last bounce was received on %(date)s" @@ -8153,6 +8292,7 @@ msgid "; it was disabled for unknown reasons" msgstr "; av ukjent grunn" #: Mailman/HTMLFormatter.py:151 +#, fuzzy msgid "Note: your list delivery is currently disabled%(reason)s." msgstr "Merk: Levering av epost fra listen er stoppet%(reason)s." @@ -8165,6 +8305,7 @@ msgid "the list administrator" msgstr "listeadministratoren" #: Mailman/HTMLFormatter.py:157 +#, fuzzy msgid "" "

                    %(note)s\n" "\n" @@ -8184,6 +8325,7 @@ msgstr "" "ytterligere hjelp." #: Mailman/HTMLFormatter.py:169 +#, fuzzy msgid "" "

                    We have received some recent bounces from your\n" " address. Your current bounce score is %(score)s out of " @@ -8203,6 +8345,7 @@ msgstr "" "snart." #: Mailman/HTMLFormatter.py:181 +#, fuzzy msgid "" "(Note - you are subscribing to a list of mailing lists, so the %(type)s " "notice will be sent to the admin address for your membership, %(addr)s.)

                    " @@ -8250,6 +8393,7 @@ msgstr "" "Du vil deretter få moderatorens avgjørelse tilsendt i en epost." #: Mailman/HTMLFormatter.py:208 +#, fuzzy msgid "" "This is %(also)sa private list, which means that the\n" " list of members is not available to non-members." @@ -8258,6 +8402,7 @@ msgstr "" "medlemmer ikke er tilgjengelig for andre enn de som er medlem av epostlisten." #: Mailman/HTMLFormatter.py:211 +#, fuzzy msgid "" "This is %(also)sa hidden list, which means that the\n" " list of members is available only to the list administrator." @@ -8266,6 +8411,7 @@ msgstr "" "medlemmer kun er tilgjengelig for listeadministratoren." #: Mailman/HTMLFormatter.py:214 +#, fuzzy msgid "" "This is %(also)sa public list, which means that the\n" " list of members list is available to everyone." @@ -8281,6 +8427,7 @@ msgstr "" " (men vi gjemmer epostadressene slik at de ikke gjenkjennes av spammere). " #: Mailman/HTMLFormatter.py:222 +#, fuzzy msgid "" "

                    (Note that this is an umbrella list, intended to\n" " have only other mailing lists as members. Among other things,\n" @@ -8296,6 +8443,7 @@ msgid "either " msgstr "enten " #: Mailman/HTMLFormatter.py:256 +#, fuzzy msgid "" "To unsubscribe from %(realname)s, get a password reminder,\n" " or change your subscription options %(either)senter your " @@ -8329,12 +8477,14 @@ msgstr "" "Dersom du lar feltet stå tomt, vil du bli spurt om epostadressen din" #: Mailman/HTMLFormatter.py:277 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " members.)" msgstr "(%(which)s er kun tilgjengelig for medlemmer av listen.)" #: Mailman/HTMLFormatter.py:281 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " administrator.)" @@ -8395,6 +8545,7 @@ msgid "The current archive" msgstr "Arkivet" #: Mailman/Handlers/Acknowledge.py:59 +#, fuzzy msgid "%(realname)s post acknowledgement" msgstr "Melding om mottatt epost til %(realname)s" @@ -8407,6 +8558,7 @@ msgid "" msgstr "" #: Mailman/Handlers/CalcRecips.py:79 +#, fuzzy msgid "" "Your urgent message to the %(realname)s mailing list was not authorized for\n" "delivery. The original message as received by Mailman is attached.\n" @@ -8486,6 +8638,7 @@ msgid "Message may contain administrivia" msgstr "Meldingen kan ha administrativt innhold" #: Mailman/Handlers/Hold.py:84 +#, fuzzy msgid "" "Please do *not* post administrative requests to the mailing\n" "list. If you wish to subscribe, visit %(listurl)s or send a message with " @@ -8525,12 +8678,14 @@ msgid "Posting to a moderated newsgroup" msgstr "Melding sendt til moderert nyhetsgruppe" #: Mailman/Handlers/Hold.py:252 +#, fuzzy msgid "Your message to %(listname)s awaits moderator approval" msgstr "" "Meldingen du sendte til listen %(listname)s venter på godkjenning av " "moderatoren." #: Mailman/Handlers/Hold.py:271 +#, fuzzy msgid "%(listname)s post from %(sender)s requires approval" msgstr "Melding til %(listname)s fra %(sender)s krever godkjenning" @@ -8575,6 +8730,7 @@ msgid "After content filtering, the message was empty" msgstr "Etter filtrering på innhold var meldingen tom" #: Mailman/Handlers/MimeDel.py:269 +#, fuzzy msgid "" "The attached message matched the %(listname)s mailing list's content " "filtering\n" @@ -8618,6 +8774,7 @@ msgid "The attached message has been automatically discarded." msgstr "Den vedlagte meldingen er automatisk forkastet." #: Mailman/Handlers/Replybot.py:75 +#, fuzzy msgid "Auto-response for your message to the \"%(realname)s\" mailing list" msgstr "" "Automatisk svar for meldingen du sendte til epostlisten \"%(realname)s\"" @@ -8642,6 +8799,7 @@ msgid "HTML attachment scrubbed and removed" msgstr "Et HTML-vedlegg ble skilt ut og fjernet" #: Mailman/Handlers/Scrubber.py:234 Mailman/Handlers/Scrubber.py:259 +#, fuzzy msgid "" "An HTML attachment was scrubbed...\n" "URL: %(url)s\n" @@ -8696,6 +8854,7 @@ msgstr "" "URL : %(url)s\n" #: Mailman/Handlers/Scrubber.py:347 +#, fuzzy msgid "Skipped content of type %(partctype)s\n" msgstr "Hopper over innhold av typen %(partctype)s\n" @@ -8727,6 +8886,7 @@ msgid "Message rejected by filter rule match" msgstr "Meldingen ble avvist av et filter" #: Mailman/Handlers/ToDigest.py:173 +#, fuzzy msgid "%(realname)s Digest, Vol %(volume)d, Issue %(issue)d" msgstr "Sammendrag av %(realname)s, Vol %(volume)d, Utgave %(issue)d" @@ -8763,6 +8923,7 @@ msgid "End of " msgstr "Slutt på " #: Mailman/ListAdmin.py:309 +#, fuzzy msgid "Posting of your message titled \"%(subject)s\"" msgstr "Din melding med tittel \"%(subject)s\"" @@ -8775,6 +8936,7 @@ msgid "Forward of moderated message" msgstr "Videresending av moderert melding" #: Mailman/ListAdmin.py:405 +#, fuzzy msgid "New subscription request to list %(realname)s from %(addr)s" msgstr "Ny søknad om medlemskap på listen %(realname)s fra %(addr)s" @@ -8788,6 +8950,7 @@ msgid "via admin approval" msgstr "Fortsett å vente på godkjenning av moderator" #: Mailman/ListAdmin.py:466 +#, fuzzy msgid "New unsubscription request from %(realname)s by %(addr)s" msgstr "Søknad fra %(addr)s om utmelding fra listen %(realname)s" @@ -8800,10 +8963,12 @@ msgid "Original Message" msgstr "Opprinnelig melding" #: Mailman/ListAdmin.py:526 +#, fuzzy msgid "Request to mailing list %(realname)s rejected" msgstr "Forespørsel til epostlisten %(realname)s ikke godkjent" #: Mailman/MTA/Manual.py:66 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been created via the through-the-web\n" "interface. In order to complete the activation of this mailing list, the\n" @@ -8831,14 +8996,17 @@ msgstr "" "muligens kjøre programmet 'newaliases':\n" #: Mailman/MTA/Manual.py:82 +#, fuzzy msgid "## %(listname)s mailing list" msgstr "## epostlisten %(listname)s" #: Mailman/MTA/Manual.py:99 +#, fuzzy msgid "Mailing list creation request for list %(listname)s" msgstr "Resultat av opprettelse av epostlisten %(listname)s" #: Mailman/MTA/Manual.py:113 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been removed via the through-the-web\n" "interface. In order to complete the de-activation of this mailing list, " @@ -8855,6 +9023,7 @@ msgstr "" "Her er linjene som må fjernes fra aliasfilen:\n" #: Mailman/MTA/Manual.py:123 +#, fuzzy msgid "" "\n" "To finish removing your mailing list, you must edit your /etc/aliases (or\n" @@ -8871,14 +9040,17 @@ msgstr "" "## Epostliste: %(listname)s" #: Mailman/MTA/Manual.py:142 +#, fuzzy msgid "Mailing list removal request for list %(listname)s" msgstr "Forespørsel om å fjerne epostlisten %(listname)s" #: Mailman/MTA/Postfix.py:442 +#, fuzzy msgid "checking permissions on %(file)s" msgstr "kontrollerer rettigheter for %(file)s" #: Mailman/MTA/Postfix.py:452 +#, fuzzy msgid "%(file)s permissions must be 0664 (got %(octmode)s)" msgstr "rettighetene på %(file)s må være 0664 (men er %(octmode)s)" @@ -8892,34 +9064,42 @@ msgid "(fixing)" msgstr "(fixer)" #: Mailman/MTA/Postfix.py:470 +#, fuzzy msgid "checking ownership of %(dbfile)s" msgstr "undersøker eierskap på filen %(dbfile)s" #: Mailman/MTA/Postfix.py:478 +#, fuzzy msgid "%(dbfile)s owned by %(owner)s (must be owned by %(user)s" msgstr "Filen %(dbfile)s eies av %(owner)s (må eies av %(user)s)" #: Mailman/MTA/Postfix.py:491 +#, fuzzy msgid "%(dbfile)s permissions must be 0664 (got %(octmode)s)" msgstr "rettighetene på %(dbfile)s må være 0664 (men er %(octmode)s)" #: Mailman/MailList.py:219 +#, fuzzy msgid "Your confirmation is required to join the %(listname)s mailing list" msgstr "Din bekreftelse kreves for å bli medlem av epostlisten %(listname)s" #: Mailman/MailList.py:230 +#, fuzzy msgid "Your confirmation is required to leave the %(listname)s mailing list" msgstr "Din bekreftelse kreves for å forlate epostlisten %(listname)s" #: Mailman/MailList.py:999 Mailman/MailList.py:1492 +#, fuzzy msgid " from %(remote)s" msgstr " fra %(remote)s" #: Mailman/MailList.py:1043 +#, fuzzy msgid "subscriptions to %(realname)s require moderator approval" msgstr "påmelding på %(realname)s krever godkjenning av moderator" #: Mailman/MailList.py:1125 bin/add_members:299 +#, fuzzy msgid "%(realname)s subscription notification" msgstr "Melding om påmelding på epostlisten %(realname)s" @@ -8928,6 +9108,7 @@ msgid "unsubscriptions require moderator approval" msgstr "utmelding krever godkjenning av moderator" #: Mailman/MailList.py:1166 +#, fuzzy msgid "%(realname)s unsubscribe notification" msgstr "Melding om utmelding av epostlisten %(realname)s" @@ -8947,6 +9128,7 @@ msgid "via web confirmation" msgstr "Ugyldig identifikator for bekreftelse!" #: Mailman/MailList.py:1396 +#, fuzzy msgid "subscriptions to %(name)s require administrator approval" msgstr "påmelding på %(name)s krever godkjenning av administrator" @@ -8965,6 +9147,7 @@ msgid "Last autoresponse notification for today" msgstr "Siste automatiske svar idag" #: Mailman/Queue/BounceRunner.py:360 +#, fuzzy msgid "" "The attached message was received as a bounce, but either the bounce format\n" "was not recognized, or no member addresses could be extracted from it. " @@ -9055,6 +9238,7 @@ msgid "Original message suppressed by Mailman site configuration\n" msgstr "" #: Mailman/htmlformat.py:675 +#, fuzzy msgid "Delivered by Mailman
                    version %(version)s" msgstr "Levert av Mailman
                    versjon %(version)s" @@ -9143,6 +9327,7 @@ msgid "Server Local Time" msgstr "Lokal tid" #: Mailman/i18n.py:180 +#, fuzzy msgid "" "%(wday)s %(mon)s %(day)2i %(hh)02i:%(mm)02i:%(ss)02i %(tzname)s %(year)04i" msgstr "" @@ -9257,6 +9442,7 @@ msgstr "" "være \"-\".\n" #: bin/add_members:162 bin/add_members:172 +#, fuzzy msgid "Already a member: %(member)s" msgstr "Allerede medlem: %(member)s" @@ -9265,10 +9451,12 @@ msgid "Bad/Invalid email address: blank line" msgstr "Feil/Ugyldig epostadresse: blank linje" #: bin/add_members:180 +#, fuzzy msgid "Bad/Invalid email address: %(member)s" msgstr "Feil/Ugyldig epostadresse: %(member)s" #: bin/add_members:182 +#, fuzzy msgid "Hostile address (illegal characters): %(member)s" msgstr "Ugyldige tegn i epostadressen: %(member)s" @@ -9278,14 +9466,17 @@ msgid "Invited: %(member)s" msgstr "Påmeldt: %(member)s" #: bin/add_members:187 +#, fuzzy msgid "Subscribed: %(member)s" msgstr "Påmeldt: %(member)s" #: bin/add_members:237 +#, fuzzy msgid "Bad argument to -w/--welcome-msg: %(arg)s" msgstr "Ugyldig argument til -w/--welcome-msg: %(arg)s" #: bin/add_members:244 +#, fuzzy msgid "Bad argument to -a/--admin-notify: %(arg)s" msgstr "Ugyldig argument til -a/--admin-notify: %(arg)s" @@ -9302,6 +9493,7 @@ msgstr "" #: bin/add_members:261 bin/config_list:110 bin/export.py:271 bin/find_member:97 #: bin/inject:91 bin/list_admins:90 bin/list_members:252 bin/sync_members:222 #: cron/bumpdigests:86 +#, fuzzy msgid "No such list: %(listname)s" msgstr "Listen finnes ikke: %(listname)s" @@ -9312,6 +9504,7 @@ msgid "Nothing to do." msgstr "Ingenting å gjøre." #: bin/arch:19 +#, fuzzy msgid "" "Rebuild a list's archive.\n" "\n" @@ -9409,6 +9602,7 @@ msgid "listname is required" msgstr "krever listens navn" #: bin/arch:143 bin/change_pw:107 bin/config_list:257 +#, fuzzy msgid "" "No such list \"%(listname)s\"\n" "%(e)s" @@ -9417,6 +9611,7 @@ msgstr "" "%(e)s" #: bin/arch:168 +#, fuzzy msgid "Cannot open mbox file %(mbox)s: %(msg)s" msgstr "Kan ikke åpne mbox-fil %(mbox)s: %(msg)s" @@ -9543,6 +9738,7 @@ msgstr "" " Viser denne hjelpeteksten.\n" #: bin/change_pw:145 +#, fuzzy msgid "Bad arguments: %(strargs)s" msgstr "Ugyldige parametre: %(strargs)s" @@ -9551,14 +9747,17 @@ msgid "Empty list passwords are not allowed" msgstr "Blanke listepassord er ikke tillatt" #: bin/change_pw:181 +#, fuzzy msgid "New %(listname)s password: %(notifypassword)s" msgstr "Nytt passord for %(listname)s: %(notifypassword)s" #: bin/change_pw:190 +#, fuzzy msgid "Your new %(listname)s list password" msgstr "Det nye passordet for epostlisten %(listname)s" #: bin/change_pw:191 +#, fuzzy msgid "" "The site administrator at %(hostname)s has changed the password for your\n" "mailing list %(listname)s. It is now\n" @@ -9585,6 +9784,7 @@ msgstr "" " %(adminurl)s\n" #: bin/check_db:19 +#, fuzzy msgid "" "Check a list's config database file for integrity.\n" "\n" @@ -9660,10 +9860,12 @@ msgid "List:" msgstr "Liste:" #: bin/check_db:148 +#, fuzzy msgid " %(file)s: okay" msgstr " %(file)s: ok" #: bin/check_perms:20 +#, fuzzy msgid "" "Check the permissions for the Mailman installation.\n" "\n" @@ -9683,43 +9885,53 @@ msgstr "" "den opp alle feil underveis. Med -v vises detaljert informasjon.\n" #: bin/check_perms:110 +#, fuzzy msgid " checking gid and mode for %(path)s" msgstr " kontrollerer gid og rettigheter for %(path)s" #: bin/check_perms:122 +#, fuzzy msgid "%(path)s bad group (has: %(groupname)s, expected %(MAILMAN_GROUP)s)" msgstr "" "feil gruppe for %(path)s (har: %(groupname)s, forventer %(MAILMAN_GROUP)s)" #: bin/check_perms:151 +#, fuzzy msgid "directory permissions must be %(octperms)s: %(path)s" msgstr "rettighetene på katalogen må være %(octperms)s: %(path)s" #: bin/check_perms:160 +#, fuzzy msgid "source perms must be %(octperms)s: %(path)s" msgstr "rettighetene på kilden må være %(octperms)s: %(path)s" #: bin/check_perms:171 +#, fuzzy msgid "article db files must be %(octperms)s: %(path)s" msgstr "rettighetene på artikkeldatabasefilene må være %(octperms)s: %(path)s" #: bin/check_perms:183 +#, fuzzy msgid "checking mode for %(prefix)s" msgstr "kontrollerer rettigheter for %(prefix)s" #: bin/check_perms:193 +#, fuzzy msgid "WARNING: directory does not exist: %(d)s" msgstr "ADVARSEL: katalogen finnes ikke: %(d)s" #: bin/check_perms:197 +#, fuzzy msgid "directory must be at least 02775: %(d)s" msgstr "katalogen må minst ha rettighetene 02775: %(d)s" #: bin/check_perms:209 +#, fuzzy msgid "checking perms on %(private)s" msgstr "kontrollerer rettigheter for: %(private)s" #: bin/check_perms:214 +#, fuzzy msgid "%(private)s must not be other-readable" msgstr "%(private)s må ikke være leselig for alle" @@ -9737,6 +9949,7 @@ msgid "mbox file must be at least 0660:" msgstr "mbox-filen må minimum ha rettighetene 0660:" #: bin/check_perms:263 +#, fuzzy msgid "%(dbdir)s \"other\" perms must be 000" msgstr "rettigheter for \"alle andre\" for katalogen %(dbdir)s må være 000" @@ -9745,26 +9958,32 @@ msgid "checking cgi-bin permissions" msgstr "kontrollerer rettigheter til cgi-bin" #: bin/check_perms:278 +#, fuzzy msgid " checking set-gid for %(path)s" msgstr " kontrollerer set-gid for %(path)s" #: bin/check_perms:282 +#, fuzzy msgid "%(path)s must be set-gid" msgstr "%(path)s må være set-gid" #: bin/check_perms:292 +#, fuzzy msgid "checking set-gid for %(wrapper)s" msgstr "kontrollerer set-gid for %(wrapper)s" #: bin/check_perms:296 +#, fuzzy msgid "%(wrapper)s must be set-gid" msgstr "%(wrapper)s må være set-gid" #: bin/check_perms:306 +#, fuzzy msgid "checking permissions on %(pwfile)s" msgstr "kontrollerer rettigheter for %(pwfile)s" #: bin/check_perms:315 +#, fuzzy msgid "%(pwfile)s permissions must be exactly 0640 (got %(octmode)s)" msgstr "rettighetene for %(pwfile)s må være satt til 0640 (de er %(octmode)s)" @@ -9773,10 +9992,12 @@ msgid "checking permissions on list data" msgstr "kontrollerer rettigheter for listedata" #: bin/check_perms:348 +#, fuzzy msgid " checking permissions on: %(path)s" msgstr " kontrollerer rettigheter for: %(path)s" #: bin/check_perms:356 +#, fuzzy msgid "file permissions must be at least 660: %(path)s" msgstr "filrettigheter må minimum være 660: %(path)s" @@ -9868,6 +10089,7 @@ msgid "Unix-From line changed: %(lineno)d" msgstr "Unix-From linje endret: %(lineno)d" #: bin/cleanarch:111 +#, fuzzy msgid "Bad status number: %(arg)s" msgstr "Ugyldig status nummer: %(arg)s" @@ -10015,10 +10237,12 @@ msgid " original address removed:" msgstr " den opprinnelige adressen ble ikke fjernet:" #: bin/clone_member:202 +#, fuzzy msgid "Not a valid email address: %(toaddr)s" msgstr "Ikke en gyldig epostadresse: %(toaddr)s" #: bin/clone_member:215 +#, fuzzy msgid "" "Error opening list \"%(listname)s\", skipping.\n" "%(e)s" @@ -10150,22 +10374,27 @@ msgid "legal values are:" msgstr "gyldige verdier:" #: bin/config_list:270 +#, fuzzy msgid "attribute \"%(k)s\" ignored" msgstr "hopper over attributten \"%(k)s\"" #: bin/config_list:273 +#, fuzzy msgid "attribute \"%(k)s\" changed" msgstr "endret på attributten \"%(k)s\"" #: bin/config_list:279 +#, fuzzy msgid "Non-standard property restored: %(k)s" msgstr "Ikke-standard egenskap gjenopprettet: %(k)s" #: bin/config_list:288 +#, fuzzy msgid "Invalid value for property: %(k)s" msgstr "Ugyldig verdi for egenskap: %(k)s" #: bin/config_list:291 +#, fuzzy msgid "Bad email address for option %(k)s: %(v)s" msgstr "Ugyldig epostadresse for innstillingen %(k)s: %(v)s" @@ -10225,18 +10454,22 @@ msgstr "" " Ikke vis statusmeldinger.\n" #: bin/discard:94 +#, fuzzy msgid "Ignoring non-held message: %(f)s" msgstr "Ser bort i fra melding som ikke ble holdt igjen: %(f)s" #: bin/discard:100 +#, fuzzy msgid "Ignoring held msg w/bad id: %(f)s" msgstr "Ser bort i fra melding med ugyldig id: %(f)s" #: bin/discard:112 +#, fuzzy msgid "Discarded held msg #%(id)s for list %(listname)s" msgstr "Forkastet melding #%(id)s for listen %(listname)s" #: bin/dumpdb:19 +#, fuzzy msgid "" "Dump the contents of any Mailman `database' file.\n" "\n" @@ -10294,8 +10527,8 @@ msgstr "" "som\n" " en pickle. Nyttig med 'python -i bin/dumpdb '. I det " "tilfellet\n" -" vil roten av treet befinne seg i en global variabel ved navn \"msg" -"\".\n" +" vil roten av treet befinne seg i en global variabel ved navn " +"\"msg\".\n" "\n" " --help / -h\n" " Viser denne hjelpeteksten.\n" @@ -10313,6 +10546,7 @@ msgid "No filename given." msgstr "Ingen filnavn angitt" #: bin/dumpdb:108 +#, fuzzy msgid "Bad arguments: %(pargs)s" msgstr "Ugyldige parametre: %(pargs)s" @@ -10535,6 +10769,7 @@ msgid "Setting web_page_url to: %(web_page_url)s" msgstr "Setter web_page_url til: %(web_page_url)s" #: bin/fix_url.py:88 +#, fuzzy msgid "Setting host_name to: %(mailhost)s" msgstr "Setter host_name til: %(mailhost)s" @@ -10609,6 +10844,7 @@ msgstr "" "inn i en kø. Hvis ingen fil angis, benyttes standard input.\n" #: bin/inject:84 +#, fuzzy msgid "Bad queue directory: %(qdir)s" msgstr "Ugyldig kø-katalog: %(qdir)s" @@ -10617,6 +10853,7 @@ msgid "A list name is required" msgstr "Navn på liste må angis" #: bin/list_admins:20 +#, fuzzy msgid "" "List all the owners of a mailing list.\n" "\n" @@ -10662,6 +10899,7 @@ msgstr "" "navn på flere epostlister.\n" #: bin/list_admins:97 +#, fuzzy msgid "List: %(listname)s, \tOwners: %(owners)s" msgstr "Liste: %(listname)s, \tEiere: %(owners)s" @@ -10840,10 +11078,12 @@ msgstr "" "vises først, deretter medlemmer i sammendrag-modus, men ingen status vises.\n" #: bin/list_members:198 +#, fuzzy msgid "Bad --nomail option: %(why)s" msgstr "Ugyldig --nomail parameter: %(why)s" #: bin/list_members:209 +#, fuzzy msgid "Bad --digest option: %(kind)s" msgstr "Ugyldig --digest parameter: %(kind)s" @@ -10857,6 +11097,7 @@ msgid "Could not open file for writing:" msgstr "Kan ikke åpne filen for skriving:" #: bin/list_owners:20 +#, fuzzy msgid "" "List the owners of a mailing list, or all mailing lists.\n" "\n" @@ -10910,6 +11151,7 @@ msgid "" msgstr "" #: bin/mailmanctl:20 +#, fuzzy msgid "" "Primary start-up and shutdown script for Mailman's qrunner daemon.\n" "\n" @@ -11087,6 +11329,7 @@ msgstr "" " nytt neste gang noe skal skrives til dem.\n" #: bin/mailmanctl:152 +#, fuzzy msgid "PID unreadable in: %(pidfile)s" msgstr "Uleselig PID i: %(pidfile)s" @@ -11095,6 +11338,7 @@ msgid "Is qrunner even running?" msgstr "Kjører qrunneren i det hele tatt?" #: bin/mailmanctl:160 +#, fuzzy msgid "No child with pid: %(pid)s" msgstr "Ingen child med pid: %(pid)s" @@ -11121,6 +11365,7 @@ msgstr "" "eksisterer en gammel låsefil. Kjør mailmanctl med \"-s\" valget.\n" #: bin/mailmanctl:233 +#, fuzzy msgid "" "The master qrunner lock could not be acquired, because it appears as if " "some\n" @@ -11148,10 +11393,12 @@ msgstr "" "Avrbyter." #: bin/mailmanctl:280 cron/mailpasswds:119 +#, fuzzy msgid "Site list is missing: %(sitelistname)s" msgstr "Systemets epostliste mangler: %(sitelistname)s" #: bin/mailmanctl:305 +#, fuzzy msgid "Run this program as root or as the %(name)s user, or use -u." msgstr "Kjør dette programmet som root eller som %(name)s, eller bruk -u." @@ -11160,6 +11407,7 @@ msgid "No command given." msgstr "Ingen kommando angitt." #: bin/mailmanctl:339 +#, fuzzy msgid "Bad command: %(command)s" msgstr "Uygldig kommando: %(command)s" @@ -11184,6 +11432,7 @@ msgid "Starting Mailman's master qrunner." msgstr "Starter Mailmans master qrunner." #: bin/mmsitepass:19 +#, fuzzy msgid "" "Set the site password, prompting from the terminal.\n" "\n" @@ -11240,6 +11489,7 @@ msgid "list creator" msgstr "person som listen ble opprettet av" #: bin/mmsitepass:86 +#, fuzzy msgid "New %(pwdesc)s password: " msgstr "Nytt %(pwdesc)s passord: " @@ -11475,6 +11725,7 @@ msgstr "" "Merk at listenavn vil bli omgjort til små bokstaver.\n" #: bin/newlist:162 +#, fuzzy msgid "Unknown language: %(lang)s" msgstr "Ukjent språk: %(lang)s" @@ -11487,6 +11738,7 @@ msgid "Enter the email of the person running the list: " msgstr "Oppgi epostadressen til personen som er ansvarlig for listen:" #: bin/newlist:193 +#, fuzzy msgid "Initial %(listname)s password: " msgstr "Det første passordet for \"%(listname)s\" er: " @@ -11496,11 +11748,12 @@ msgstr "Listen m #: bin/newlist:220 msgid "" -" - owner addresses need to be fully-qualified names like \"owner@example.com" -"\", not just \"owner\"." +" - owner addresses need to be fully-qualified names like \"owner@example." +"com\", not just \"owner\"." msgstr "" #: bin/newlist:244 +#, fuzzy msgid "Hit enter to notify %(listname)s owner..." msgstr "Trykk [Enter] for å sende melding til eieren av listen %(listname)s..." @@ -11572,6 +11825,7 @@ msgid "" msgstr "" #: bin/qrunner:179 +#, fuzzy msgid "%(name)s runs the %(runnername)s qrunner" msgstr "%(name)s starter %(runnername)s qrunneren" @@ -11702,18 +11956,22 @@ msgstr "" "\n" #: bin/remove_members:156 +#, fuzzy msgid "Could not open file for reading: %(filename)s." msgstr "Kunne ikke åpne filen \"%(filename)s\" for lesing." #: bin/remove_members:163 +#, fuzzy msgid "Error opening list %(listname)s... skipping." msgstr "Hopper over listen \"%(listname)s\" grunnet feil under åpning." #: bin/remove_members:173 +#, fuzzy msgid "No such member: %(addr)s" msgstr "Medlemmet finnes ikke: %(addr)s." #: bin/remove_members:178 +#, fuzzy msgid "User `%(addr)s' removed from list: %(listname)s." msgstr "%(addr)s er nå fjernet fra listen %(listname)s." @@ -11738,6 +11996,7 @@ msgid "" msgstr "" #: bin/reset_pw.py:77 +#, fuzzy msgid "Changing passwords for list: %(listname)s" msgstr "Endrer passord for liste: %(listname)s" @@ -11787,18 +12046,22 @@ msgstr "" "\n" #: bin/rmlist:73 bin/rmlist:76 +#, fuzzy msgid "Removing %(msg)s" msgstr "Fjerner %(msg)s" #: bin/rmlist:81 +#, fuzzy msgid "%(listname)s %(msg)s not found as %(filename)s" msgstr "Fant ikke %(listname)s %(msg)s som %(filename)s" #: bin/rmlist:105 +#, fuzzy msgid "No such list (or list already deleted): %(listname)s" msgstr "Listen finnes ikke (eller er allerede slettet): %(listname)s" #: bin/rmlist:108 +#, fuzzy msgid "No such list: %(listname)s. Removing its residual archives." msgstr "Listen finnes ikke: %(listname)s. Fjerner arkivet som ligger igjen." @@ -11847,6 +12110,7 @@ msgid "" msgstr "" #: bin/sync_members:19 +#, fuzzy msgid "" "Synchronize a mailing list's membership with a flat file.\n" "\n" @@ -11976,6 +12240,7 @@ msgstr "" " Må benyttes. Angir navnet på listen som skal synkroniseres.\n" #: bin/sync_members:115 +#, fuzzy msgid "Bad choice: %(yesno)s" msgstr "Ugyldig valg: %(yesno)s" @@ -11992,6 +12257,7 @@ msgid "No argument to -f given" msgstr "\"-f\" parameteren mangler verdi" #: bin/sync_members:172 +#, fuzzy msgid "Illegal option: %(opt)s" msgstr "Ugyldig parameter: %(opt)s" @@ -12004,6 +12270,7 @@ msgid "Must have a listname and a filename" msgstr "Må ha et listenavn og et filnavn" #: bin/sync_members:191 +#, fuzzy msgid "Cannot read address file: %(filename)s: %(msg)s" msgstr "Kan ikke lese adressefil: %(filename)s: %(msg)s" @@ -12020,10 +12287,12 @@ msgid "You must fix the preceding invalid addresses first." msgstr "Du må fixe de ugyldige adressene først." #: bin/sync_members:264 +#, fuzzy msgid "Added : %(s)s" msgstr "La inn : %(s)s" #: bin/sync_members:289 +#, fuzzy msgid "Removed: %(s)s" msgstr "Fjernet: %(s)s" @@ -12120,6 +12389,7 @@ msgid "" msgstr "" #: bin/update:20 +#, fuzzy msgid "" "Perform all necessary upgrades.\n" "\n" @@ -12158,14 +12428,17 @@ msgstr "" "1.0b4 (?).\n" #: bin/update:107 +#, fuzzy msgid "Fixing language templates: %(listname)s" msgstr "Oppdaterer språkfiler: %(listname)s" #: bin/update:196 bin/update:711 +#, fuzzy msgid "WARNING: could not acquire lock for list: %(listname)s" msgstr "ADVARSEL: kunne ikke låse listen: %(listname)s" #: bin/update:215 +#, fuzzy msgid "Resetting %(n)s BYBOUNCEs disabled addrs with no bounce info" msgstr "" "Resetter %(n)s adresser som ble stoppet grunnet returmeldinger, men som ikke " @@ -12184,6 +12457,7 @@ msgstr "" "virke i b6, så jeg endrer navnet til %(mbox_dir)s.tmp og fortsetter." #: bin/update:255 +#, fuzzy msgid "" "\n" "%(listname)s has both public and private mbox archives. Since this list\n" @@ -12236,6 +12510,7 @@ msgid "- updating old private mbox file" msgstr "- oppdaterer den gamle private mbox-filen" #: bin/update:295 +#, fuzzy msgid "" " unknown file in the way, moving\n" " %(o_pri_mbox_file)s\n" @@ -12252,6 +12527,7 @@ msgid "- updating old public mbox file" msgstr "- oppdaterer den gamle offentlige mbox-filen" #: bin/update:317 +#, fuzzy msgid "" " unknown file in the way, moving\n" " %(o_pub_mbox_file)s\n" @@ -12280,18 +12556,22 @@ msgid "- %(o_tmpl)s doesn't exist, leaving untouched" msgstr "- %(o_tmpl)s eksisterer ikke, rører ikke denne" #: bin/update:396 +#, fuzzy msgid "removing directory %(src)s and everything underneath" msgstr "fjerner katalogen %(src)s og alle underkataloger" #: bin/update:399 +#, fuzzy msgid "removing %(src)s" msgstr "fjerner %(src)s" #: bin/update:403 +#, fuzzy msgid "Warning: couldn't remove %(src)s -- %(rest)s" msgstr "Advarsel: kunne ikke fjerne %(src)s -- %(rest)s" #: bin/update:408 +#, fuzzy msgid "couldn't remove old file %(pyc)s -- %(rest)s" msgstr "kunne ikke fjerne den gamle filen %(pyc)s -- %(rest)s" @@ -12305,6 +12585,7 @@ msgid "Warning! Not a directory: %(dirpath)s" msgstr "Ugyldig kø-katalog: %(dirpath)s" #: bin/update:530 +#, fuzzy msgid "message is unparsable: %(filebase)s" msgstr "kan ikke tolke melding: %(filebase)s" @@ -12321,10 +12602,12 @@ msgid "Updating Mailman 2.1.4 pending.pck database" msgstr "Oppdaterer pending.pck databasen fra Mailman 2.1.4" #: bin/update:598 +#, fuzzy msgid "Ignoring bad pended data: %(key)s: %(val)s" msgstr "Ser bort i fra feildata: %(key)s: %(val)s" #: bin/update:614 +#, fuzzy msgid "WARNING: Ignoring duplicate pending ID: %(id)s." msgstr "ADVARSEL: Ser bort i fra duplikatmelding som venter: %(id)s" @@ -12350,6 +12633,7 @@ msgid "done" msgstr "utført" #: bin/update:691 +#, fuzzy msgid "Updating mailing list: %(listname)s" msgstr "Oppdaterer epostliste: %(listname)s" @@ -12413,6 +12697,7 @@ msgid "No updates are necessary." msgstr "Ingen oppdatering er nødvendig." #: bin/update:796 +#, fuzzy msgid "" "Downgrade detected, from version %(hexlversion)s to version %(hextversion)s\n" "This is probably not safe.\n" @@ -12423,10 +12708,12 @@ msgstr "" "Avbryter." #: bin/update:801 +#, fuzzy msgid "Upgrading from version %(hexlversion)s to %(hextversion)s" msgstr "Oppgraderer fra version %(hexlversion)s til %(hextversion)s" #: bin/update:810 +#, fuzzy msgid "" "\n" "ERROR:\n" @@ -12590,6 +12877,7 @@ msgid "" msgstr "" #: bin/withlist:175 +#, fuzzy msgid "Unlocking (but not saving) list: %(listname)s" msgstr "Låser opp (men lagrer ikke) listen: %(listname)s" @@ -12598,6 +12886,7 @@ msgid "Finalizing" msgstr "Avslutter" #: bin/withlist:188 +#, fuzzy msgid "Loading list %(listname)s" msgstr "Leser listen %(listname)s" @@ -12610,6 +12899,7 @@ msgid "(unlocked)" msgstr "(åpen)" #: bin/withlist:197 +#, fuzzy msgid "Unknown list: %(listname)s" msgstr "Ukjent liste: %(listname)s" @@ -12622,18 +12912,22 @@ msgid "--all requires --run" msgstr "--all krever --run" #: bin/withlist:266 +#, fuzzy msgid "Importing %(module)s..." msgstr "Importerer %(module)s..." #: bin/withlist:270 +#, fuzzy msgid "Running %(module)s.%(callable)s()..." msgstr "Kjører %(module)s.%(callable)s()..." #: bin/withlist:291 +#, fuzzy msgid "The variable `m' is the %(listname)s MailList instance" msgstr "Variabelen 'm' er forekomsten av %(listname)s MailList objektet" #: cron/bumpdigests:19 +#, fuzzy msgid "" "Increment the digest volume number and reset the digest number to one.\n" "\n" @@ -12662,6 +12956,7 @@ msgstr "" "volume nummer for alle lister.\n" #: cron/checkdbs:20 +#, fuzzy msgid "" "Check for pending admin requests and mail the list owners if necessary.\n" "\n" @@ -12691,10 +12986,12 @@ msgstr "" "\n" #: cron/checkdbs:121 +#, fuzzy msgid "%(count)d %(realname)s moderator request(s) waiting" msgstr "%(count)d forespørsler venter på behandling på listen %(realname)s" #: cron/checkdbs:124 +#, fuzzy msgid "%(realname)s moderator request check result" msgstr "Resultat av forespørselsjekk for listen %(realname)s" @@ -12716,6 +13013,7 @@ msgstr "" "Epost til listen som krever godkjenning:" #: cron/checkdbs:169 +#, fuzzy msgid "" "From: %(sender)s on %(date)s\n" "Subject: %(subject)s\n" @@ -12826,6 +13124,7 @@ msgstr "" "\n" #: cron/mailpasswds:19 +#, fuzzy msgid "" "Send password reminders for all lists to all users.\n" "\n" @@ -12880,10 +13179,12 @@ msgid "Password // URL" msgstr "Passord // URL" #: cron/mailpasswds:222 +#, fuzzy msgid "%(host)s mailing list memberships reminder" msgstr "Påminnelse om passord for epostlister på %(host)s" #: cron/nightly_gzip:19 +#, fuzzy msgid "" "Re-generate the Pipermail gzip'd archive flat files.\n" "\n" @@ -13791,8 +14092,8 @@ msgstr "" #~ msgid "%(rname)s member %(addr)s bouncing - %(negative)s%(did)s" #~ msgstr "" -#~ "Adressen til %(rname)s, %(addr)s, kommer bare i retur - %(negative)s" -#~ "%(did)s" +#~ "Adressen til %(rname)s, %(addr)s, kommer bare i retur - " +#~ "%(negative)s%(did)s" #~ msgid "User not found." #~ msgstr "Medlemmet finnes ikke." diff --git a/messages/pl/LC_MESSAGES/mailman.po b/messages/pl/LC_MESSAGES/mailman.po index a95fd946..81b9b404 100755 --- a/messages/pl/LC_MESSAGES/mailman.po +++ b/messages/pl/LC_MESSAGES/mailman.po @@ -67,10 +67,12 @@ msgid "

                    Currently, there are no archives.

                    " msgstr "

                    W tej chwili nie ma archiwum.

                    " #: Mailman/Archiver/HyperArch.py:821 +#, fuzzy msgid "Gzip'd Text%(sz)s" msgstr "Tekst Gzipowany%(sz)s" #: Mailman/Archiver/HyperArch.py:826 +#, fuzzy msgid "Text%(sz)s" msgstr "Tekst%(sz)s" @@ -143,18 +145,22 @@ msgid "Third" msgstr "Trzeci" #: Mailman/Archiver/HyperArch.py:938 +#, fuzzy msgid "%(ord)s quarter %(year)i" msgstr "%(ord)s kwarta³ %(year)i" #: Mailman/Archiver/HyperArch.py:945 +#, fuzzy msgid "%(month)s %(year)i" msgstr "%(month)s %(year)i" #: Mailman/Archiver/HyperArch.py:950 +#, fuzzy msgid "The Week Of Monday %(day)i %(month)s %(year)i" msgstr "Tydzieñ zaczynaj±cy siê od poniedzia³ku %(day)i %(month)s %(year)i" #: Mailman/Archiver/HyperArch.py:954 +#, fuzzy msgid "%(day)i %(month)s %(year)i" msgstr "%(day)i %(month)s %(year)i" @@ -163,10 +169,12 @@ msgid "Computing threaded index\n" msgstr "Obliczanie listy w±tków\n" #: Mailman/Archiver/HyperArch.py:1319 +#, fuzzy msgid "Updating HTML for article %(seq)s" msgstr "Aktualizacja pliku HTML dla wiadomo¶ci %(seq)s" #: Mailman/Archiver/HyperArch.py:1326 +#, fuzzy msgid "article file %(filename)s is missing!" msgstr "brakuje pliku archiwum %(filename)s !" @@ -183,6 +191,7 @@ msgid "Pickling archive state into " msgstr "Zapisywanie stanu archiwum do " #: Mailman/Archiver/pipermail.py:453 +#, fuzzy msgid "Updating index files for archive [%(archive)s]" msgstr "Aktualizacja indeksów z archiwum [%(archive)s]" @@ -191,6 +200,7 @@ msgid " Thread" msgstr " W±tek" #: Mailman/Archiver/pipermail.py:597 +#, fuzzy msgid "#%(counter)05d %(msgid)s" msgstr "#%(counter)05d %(msgid)s" @@ -228,6 +238,7 @@ msgid "disabled address" msgstr "zablokowana" #: Mailman/Bouncer.py:304 +#, fuzzy msgid " The last bounce received from you was dated %(date)s" msgstr " Ostatni zwrot otrzymano z Twojego adresu dnia %(date)s" @@ -255,6 +266,7 @@ msgstr "Administrator" #: Mailman/Cgi/options.py:101 Mailman/Cgi/private.py:108 #: Mailman/Cgi/rmlist.py:75 Mailman/Cgi/roster.py:59 #: Mailman/Cgi/subscribe.py:67 +#, fuzzy msgid "No such list %(safelistname)s" msgstr "Nie znaleziono listy %(safelistname)s" @@ -328,6 +340,7 @@ msgstr "" " nie zostanie usuniêty.%(rm)r" #: Mailman/Cgi/admin.py:268 +#, fuzzy msgid "%(hostname)s mailing lists - Admin Links" msgstr "Listy dostêpne na serwerze %(hostname)s - Administracja" @@ -340,6 +353,7 @@ msgid "Mailman" msgstr "Mailman" #: Mailman/Cgi/admin.py:310 +#, fuzzy msgid "" "

                    There currently are no publicly-advertised %(mailmanlink)s\n" " mailing lists on %(hostname)s." @@ -348,6 +362,7 @@ msgstr "" " list na serwerze %(hostname)s." #: Mailman/Cgi/admin.py:316 +#, fuzzy msgid "" "

                    Below is the collection of publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s. Click on a list\n" @@ -362,6 +377,7 @@ msgid "right " msgstr "prawo " #: Mailman/Cgi/admin.py:325 +#, fuzzy msgid "" "To visit the administrators configuration page for an\n" " unadvertised list, open a URL similar to this one, but with a '/' " @@ -406,6 +422,7 @@ msgid "No valid variable name found." msgstr "Nie znaleziono prawid³owej nazwy zmiennej." #: Mailman/Cgi/admin.py:395 +#, fuzzy msgid "" "%(realname)s Mailing list Configuration Help\n" "
                    %(varname)s Option" @@ -414,6 +431,7 @@ msgstr "" "
                    opcja %(varname)s" #: Mailman/Cgi/admin.py:402 +#, fuzzy msgid "Mailman %(varname)s List Option Help" msgstr "Opis opcji Mailmana %(varname)s" @@ -433,14 +451,17 @@ msgstr "" " " #: Mailman/Cgi/admin.py:431 +#, fuzzy msgid "return to the %(categoryname)s options page." msgstr "powróciæ do strony z opcjami %(categoryname)s." #: Mailman/Cgi/admin.py:446 +#, fuzzy msgid "%(realname)s Administration (%(label)s)" msgstr "%(realname)s - Administracja (%(label)s)" #: Mailman/Cgi/admin.py:447 +#, fuzzy msgid "%(realname)s mailing list administration
                    %(label)s Section" msgstr "%(realname)s - Administracja list±
                    Sekcja %(label)s" @@ -523,6 +544,7 @@ msgid "Value" msgstr "Warto¶æ" #: Mailman/Cgi/admin.py:668 +#, fuzzy msgid "" "Badly formed options entry:\n" " %(record)s" @@ -623,10 +645,12 @@ msgid "Move rule down" msgstr "Przesuñ regu³ê ni¿ej" #: Mailman/Cgi/admin.py:870 +#, fuzzy msgid "
                    (Edit %(varname)s)" msgstr "
                    (Edytuj %(varname)s)" #: Mailman/Cgi/admin.py:872 +#, fuzzy msgid "
                    (Details for %(varname)s)" msgstr "
                    (Wiêcej o %(varname)s)" @@ -666,6 +690,7 @@ msgid "(help)" msgstr "(pomoc)" #: Mailman/Cgi/admin.py:930 +#, fuzzy msgid "Find member %(link)s:" msgstr "Znajd¼ prenumeratora %(link)s:" @@ -678,10 +703,12 @@ msgid "Bad regular expression: " msgstr "B³êdne wyra¿enie regularne: " #: Mailman/Cgi/admin.py:1013 +#, fuzzy msgid "%(allcnt)s members total, %(membercnt)s shown" msgstr "%(allcnt)s prenumeratorów, wy¶wietlono %(membercnt)s" #: Mailman/Cgi/admin.py:1016 +#, fuzzy msgid "%(allcnt)s members total" msgstr "%(allcnt)s prenumeratorów" @@ -863,6 +890,7 @@ msgstr "" " zakres poni¿ej:" #: Mailman/Cgi/admin.py:1221 +#, fuzzy msgid "from %(start)s to %(end)s" msgstr "od %(start)s do %(end)s" @@ -1004,6 +1032,7 @@ msgid "Change list ownership passwords" msgstr "Zmieñ has³a dostêpu do listy" #: Mailman/Cgi/admin.py:1364 +#, fuzzy msgid "" "The list administrators are the people who have ultimate control " "over\n" @@ -1118,6 +1147,7 @@ msgstr "Nieprawid #: Mailman/Cgi/admin.py:1529 Mailman/Cgi/admin.py:1703 bin/add_members:175 #: bin/clone_member:136 bin/sync_members:268 +#, fuzzy msgid "Banned address (matched %(pattern)s)" msgstr "Adres na czarnej li¶cie (pasuje do %(pattern)s)" @@ -1174,6 +1204,7 @@ msgid "%(schange_to)s is already a member" msgstr "Adres \"%(schange_to)s\" jest ju¿ na li¶cie subskrybentów" #: Mailman/Cgi/admin.py:1620 +#, fuzzy msgid "%(schange_to)s matches banned pattern %(spat)s" msgstr "\"%(schange_to)s\" pasuje do zabronionego wzoru \"%(spat)s\"" @@ -1214,6 +1245,7 @@ msgid "Not subscribed" msgstr "Nie zapisano" #: Mailman/Cgi/admin.py:1773 +#, fuzzy msgid "Ignoring changes to deleted member: %(user)s" msgstr "Nie znaleziono prenumeratora: %(user)s." @@ -1226,10 +1258,12 @@ msgid "Error Unsubscribing:" msgstr "B³êdy przy wypisywaniu:" #: Mailman/Cgi/admindb.py:235 Mailman/Cgi/admindb.py:248 +#, fuzzy msgid "%(realname)s Administrative Database" msgstr "%(realname)s - baza administracyjna" #: Mailman/Cgi/admindb.py:238 +#, fuzzy msgid "%(realname)s Administrative Database Results" msgstr "%(realname)s - baza administracyjna - wyniki" @@ -1258,6 +1292,7 @@ msgid "Discard all messages marked Defer" msgstr "Zignoruj wszystkie wiadomo¶ci oznaczone jako Od³ó¿" #: Mailman/Cgi/admindb.py:298 +#, fuzzy msgid "all of %(esender)s's held messages." msgstr "wszystkie wstrzymane wiadomo¶ci od %(esender)s." @@ -1278,6 +1313,7 @@ msgid "list of available mailing lists." msgstr "spis dostêpnych list." #: Mailman/Cgi/admindb.py:362 +#, fuzzy msgid "You must specify a list name. Here is the %(link)s" msgstr "Musisz wybraæ jedn± z wymienionych list: %(link)s" @@ -1359,6 +1395,7 @@ msgid "The sender is now a member of this list" msgstr "Nadawca jest teraz prenumeratorem tej listy" #: Mailman/Cgi/admindb.py:568 +#, fuzzy msgid "Add %(esender)s to one of these sender filters:" msgstr "Dodaj %(esender)s do filtra nadawców" @@ -1379,6 +1416,7 @@ msgid "Rejects" msgstr "Odmów" #: Mailman/Cgi/admindb.py:584 +#, fuzzy msgid "" "Ban %(esender)s from ever subscribing to this\n" " mailing list" @@ -1391,6 +1429,7 @@ msgid "" msgstr "Kliknij na numerze wiadomo¶ci, ¿eby j± zobaczyæ. Mo¿esz te¿ " #: Mailman/Cgi/admindb.py:591 +#, fuzzy msgid "view all messages from %(esender)s" msgstr "zobaczyæ wszystkie wiadomo¶ci od %(esender)s" @@ -1469,6 +1508,7 @@ msgid " is already a member" msgstr " jest ju¿ prenumeratorem" #: Mailman/Cgi/admindb.py:973 +#, fuzzy msgid "%(addr)s is banned (matched: %(patt)s)" msgstr "Adres %(addr)s jest zablokowany (pasuje do: %(patt)s)" @@ -1477,6 +1517,7 @@ msgid "Confirmation string was empty." msgstr "Kod potwierdzaj±cy by³ pusty." #: Mailman/Cgi/confirm.py:108 +#, fuzzy msgid "" "Invalid confirmation string:\n" " %(safecookie)s.\n" @@ -1521,6 +1562,7 @@ msgstr "" " usuniêty. Bie¿±ce operacja zosta³a anulowana." #: Mailman/Cgi/confirm.py:178 +#, fuzzy msgid "System error, bad content: %(content)s" msgstr "B³±d systemu, niepoprawne: %(content)s" @@ -1558,6 +1600,7 @@ msgid "Confirm subscription request" msgstr "Potwierd¼ subskrypcjê" #: Mailman/Cgi/confirm.py:259 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " subscription request to the mailing list %(listname)s. Your\n" @@ -1588,6 +1631,7 @@ msgstr "" "

                    Je¶li nie chcesz siê zapisaæ, naci¶nij Anuluj." #: Mailman/Cgi/confirm.py:275 +#, fuzzy msgid "" "Your confirmation is required in order to continue with\n" " the subscription request to the mailing list %(listname)s.\n" @@ -1637,6 +1681,7 @@ msgid "Preferred language:" msgstr "Preferowany jêzyk:" #: Mailman/Cgi/confirm.py:317 +#, fuzzy msgid "Subscribe to list %(listname)s" msgstr "Zapisz siê na listê %(listname)s" @@ -1653,6 +1698,7 @@ msgid "Awaiting moderator approval" msgstr "Oczekiwanie na zgodê administratora" #: Mailman/Cgi/confirm.py:386 +#, fuzzy msgid "" " You have successfully confirmed your subscription request to " "the\n" @@ -1685,6 +1731,7 @@ msgid "You are already a member of this mailing list!" msgstr "Jeste¶ ju¿ zapisany na tê listê!" #: Mailman/Cgi/confirm.py:400 +#, fuzzy msgid "" "You are currently banned from subscribing to\n" " this list. If you think this restriction is erroneous, please\n" @@ -1708,6 +1755,7 @@ msgid "Subscription request confirmed" msgstr "Pro¶ba o subskrypcjê potwierdzona" #: Mailman/Cgi/confirm.py:418 +#, fuzzy msgid "" " You have successfully confirmed your subscription request for\n" " \"%(addr)s\" to the %(listname)s mailing list. A separate\n" @@ -1735,6 +1783,7 @@ msgid "Unsubscription request confirmed" msgstr "Potwierdzono wypisanie z listy" #: Mailman/Cgi/confirm.py:469 +#, fuzzy msgid "" " You have successfully unsubscribed from the %(listname)s " "mailing\n" @@ -1755,6 +1804,7 @@ msgid "Not available" msgstr "Niedostêpny" #: Mailman/Cgi/confirm.py:498 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " unsubscription request from the mailing list %(listname)s. " @@ -1797,6 +1847,7 @@ msgid "You have canceled your change of address request." msgstr "Anulowa³e¶ pro¶bê o zmianê adresu." #: Mailman/Cgi/confirm.py:555 +#, fuzzy msgid "" "%(newaddr)s is banned from subscribing to the\n" " %(realname)s list. If you think this restriction is erroneous,\n" @@ -1808,6 +1859,7 @@ msgstr "" " skontaktuj siê z opiekunem listy - %(owneraddr)s." #: Mailman/Cgi/confirm.py:560 +#, fuzzy msgid "" "%(newaddr)s is already a member of\n" " the %(realname)s list. It is possible that you are attempting\n" @@ -1824,6 +1876,7 @@ msgid "Change of address request confirmed" msgstr "Pro¶ba o zmianê adresu potwierdzona" #: Mailman/Cgi/confirm.py:571 +#, fuzzy msgid "" " You have successfully changed your address on the %(listname)s\n" " mailing list from %(oldaddr)s to %(newaddr)s. " @@ -1845,6 +1898,7 @@ msgid "globally" msgstr "globalnej" #: Mailman/Cgi/confirm.py:605 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " change of address request for the mailing list %(listname)s. " @@ -1906,6 +1960,7 @@ msgid "Sender discarded message via web." msgstr "Wysy³aj±cy anulowa³ list poprzez stronê WWW" #: Mailman/Cgi/confirm.py:674 +#, fuzzy msgid "" "The held message with the Subject:\n" " header %(subject)s could not be found. The most " @@ -1925,6 +1980,7 @@ msgid "Posted message canceled" msgstr "Wiadomo¶æ anulowana" #: Mailman/Cgi/confirm.py:685 +#, fuzzy msgid "" " You have successfully canceled the posting of your message with\n" " the Subject: header %(subject)s to the mailing list\n" @@ -1947,6 +2003,7 @@ msgstr "" " zosta³a ju¿ oceniona przez opiekuna listy." #: Mailman/Cgi/confirm.py:735 +#, fuzzy msgid "" "Your confirmation is required in order to cancel the\n" " posting of your message to the mailing list %(listname)s:\n" @@ -1995,6 +2052,7 @@ msgid "Membership re-enabled." msgstr "Prenumerata ponownie aktywna" #: Mailman/Cgi/confirm.py:798 +#, fuzzy msgid "" " You have successfully re-enabled your membership in the\n" " %(listname)s mailing list. You can now not available" msgstr "niedostêpne" #: Mailman/Cgi/confirm.py:846 +#, fuzzy msgid "" "Your membership in the %(realname)s mailing list is\n" " currently disabled due to excessive bounces. Your confirmation is\n" @@ -2092,10 +2152,12 @@ msgid "administrative list overview" msgstr "strony administracyjnej listy" #: Mailman/Cgi/create.py:115 +#, fuzzy msgid "List name must not include \"@\": %(safelistname)s" msgstr "Nazwa listy nie mo¿e zawieraæ znaku \"@\": %(safelistname)s" #: Mailman/Cgi/create.py:122 +#, fuzzy msgid "List already exists: %(safelistname)s" msgstr "Taka lista ju¿ istnieje: %(safelistname)s" @@ -2129,18 +2191,22 @@ msgid "You are not authorized to create new mailing lists" msgstr "Nie masz uprawnieñ do tworzenia nowych list" #: Mailman/Cgi/create.py:182 +#, fuzzy msgid "Unknown virtual host: %(safehostname)s" msgstr "Nieznany host wirtualny: %(safehostname)s" #: Mailman/Cgi/create.py:218 bin/newlist:219 +#, fuzzy msgid "Bad owner email address: %(s)s" msgstr "Niepoprawny adres administratora listy: %(s)s" #: Mailman/Cgi/create.py:223 bin/newlist:182 bin/newlist:223 +#, fuzzy msgid "List already exists: %(listname)s" msgstr "Lista %(listname)s ju¿ istnieje. " #: Mailman/Cgi/create.py:231 bin/newlist:217 +#, fuzzy msgid "Illegal list name: %(s)s" msgstr "Niedozwolona nazwa listy: %(s)s" @@ -2153,6 +2219,7 @@ msgstr "" " Skontaktuj siê z administratorem serwera." #: Mailman/Cgi/create.py:273 bin/newlist:265 +#, fuzzy msgid "Your new mailing list: %(listname)s" msgstr "Twoja nowa lista: %(listname)s" @@ -2161,6 +2228,7 @@ msgid "Mailing list creation results" msgstr "Raport z tworzenia listy" #: Mailman/Cgi/create.py:288 +#, fuzzy msgid "" "You have successfully created the mailing list\n" " %(listname)s and notification has been sent to the list owner\n" @@ -2183,6 +2251,7 @@ msgid "Create another list" msgstr "Utworzyæ now± listê" #: Mailman/Cgi/create.py:312 +#, fuzzy msgid "Create a %(hostname)s Mailing List" msgstr "Utwórz listê na serwerze %(hostname)s" @@ -2279,6 +2348,7 @@ msgstr "" "subskrybentów bêd± moderowane." #: Mailman/Cgi/create.py:432 +#, fuzzy msgid "" "Initial list of supported languages.

                    Note that if you do not\n" " select at least one initial language, the list will use the server\n" @@ -2383,6 +2453,7 @@ msgid "List name is required." msgstr "Nazwa listy jest niezbêdna." #: Mailman/Cgi/edithtml.py:154 +#, fuzzy msgid "%(realname)s -- Edit html for %(template_info)s" msgstr "%(realname)s -- Edytuj strony HTML dla szablonu %(template_info)s" @@ -2391,10 +2462,12 @@ msgid "Edit HTML : Error" msgstr "Edycja HTML : B³±d" #: Mailman/Cgi/edithtml.py:161 +#, fuzzy msgid "%(safetemplatename)s: Invalid template" msgstr "%(safetemplatename)s: Nieprawid³owy szablon" #: Mailman/Cgi/edithtml.py:166 Mailman/Cgi/edithtml.py:167 +#, fuzzy msgid "%(realname)s -- HTML Page Editing" msgstr "%(realname)s -- Edycja stron HTML" @@ -2457,10 +2530,12 @@ msgid "HTML successfully updated." msgstr "Strona HTML zosta³a zaktualizowana." #: Mailman/Cgi/listinfo.py:89 +#, fuzzy msgid "%(hostname)s Mailing Lists" msgstr "Listy dostêpne na serwerze %(hostname)s" #: Mailman/Cgi/listinfo.py:127 +#, fuzzy msgid "" "

                    There currently are no publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s." @@ -2469,6 +2544,7 @@ msgstr "" " list na serwerze %(hostname)s." #: Mailman/Cgi/listinfo.py:131 +#, fuzzy msgid "" "

                    Below is a listing of all the public mailing lists on\n" " %(hostname)s. Click on a list name to get more information " @@ -2488,6 +2564,7 @@ msgid "right" msgstr "prawo" #: Mailman/Cgi/listinfo.py:140 +#, fuzzy msgid "" " To visit the general information page for an unadvertised list,\n" " open a URL similar to this one, but with a '/' and the %(adj)s\n" @@ -2536,6 +2613,7 @@ msgid "CGI script error" msgstr "B³±d skryptu CGI" #: Mailman/Cgi/options.py:71 +#, fuzzy msgid "Invalid request method: %(method)s" msgstr "Nieprawid³owa metoda ¿±dania: %(method)s" @@ -2549,6 +2627,7 @@ msgstr "Nieprawid #: Mailman/Cgi/options.py:183 Mailman/Cgi/options.py:240 #: Mailman/Cgi/options.py:264 +#, fuzzy msgid "No such member: %(safeuser)s." msgstr "Nie znaleziono prenumeratora: %(safeuser)s." @@ -2599,6 +2678,7 @@ msgid "Note: " msgstr "Uwaga: " #: Mailman/Cgi/options.py:378 +#, fuzzy msgid "List subscriptions for %(safeuser)s on %(hostname)s" msgstr "" "Listy zaprenumerowane przez u¿ytkownika %(safeuser)s na serwerze %(hostname)s" @@ -2630,6 +2710,7 @@ msgid "You are already using that email address" msgstr "Ten adres jest ju¿ na li¶cie" #: Mailman/Cgi/options.py:459 +#, fuzzy msgid "" "The new address you requested %(newaddr)s is already a member of the\n" "%(listname)s mailing list, however you have also requested a global change " @@ -2643,6 +2724,7 @@ msgstr "" "we wszystkich innych listach zostanie zmieniony." #: Mailman/Cgi/options.py:468 +#, fuzzy msgid "The new address is already a member: %(newaddr)s" msgstr "%(newaddr)s jest ju¿ zapisany na listê." @@ -2651,6 +2733,7 @@ msgid "Addresses may not be blank" msgstr "Pole adresu nie mo¿e byæ puste" #: Mailman/Cgi/options.py:488 +#, fuzzy msgid "A confirmation message has been sent to %(newaddr)s. " msgstr "Potwierdzenie zosta³o wys³ane do %(newaddr)s" @@ -2663,10 +2746,12 @@ msgid "Illegal email address provided" msgstr "Nieprawid³owy adres" #: Mailman/Cgi/options.py:501 +#, fuzzy msgid "%(newaddr)s is already a member of the list." msgstr "%(newaddr)s jest ju¿ zapisany na listê." #: Mailman/Cgi/options.py:504 +#, fuzzy msgid "" "%(newaddr)s is banned from this list. If you\n" " think this restriction is erroneous, please contact\n" @@ -2741,6 +2826,7 @@ msgstr "" " zostaniesz poinformowany emailem." #: Mailman/Cgi/options.py:623 +#, fuzzy msgid "" "You have been successfully unsubscribed from the\n" " mailing list %(fqdn_listname)s. If you were receiving digest\n" @@ -2832,6 +2918,7 @@ msgid "day" msgstr "dzieñ" #: Mailman/Cgi/options.py:900 +#, fuzzy msgid "%(days)d %(units)s" msgstr "%(days)d %(units)s" @@ -2844,6 +2931,7 @@ msgid "No topics defined" msgstr "Nie zdefiniowano tematów" #: Mailman/Cgi/options.py:940 +#, fuzzy msgid "" "\n" "You are subscribed to this list with the case-preserved address\n" @@ -2854,6 +2942,7 @@ msgstr "" "%(cpuser)s (z zachowaniem wielko¶ci liter)." #: Mailman/Cgi/options.py:956 +#, fuzzy msgid "%(realname)s list: member options login page" msgstr "Lista %(realname)s: strona logowania dla prenumeratorów" @@ -2862,10 +2951,12 @@ msgid "email address and " msgstr "adres email oraz " #: Mailman/Cgi/options.py:960 +#, fuzzy msgid "%(realname)s list: member options for user %(safeuser)s" msgstr "Lista %(realname)s: opcje prenumeratora %(safeuser)s" #: Mailman/Cgi/options.py:986 +#, fuzzy msgid "" "In order to change your membership option, you must\n" " first log in by giving your %(extra)smembership password in the section\n" @@ -2943,6 +3034,7 @@ msgid "" msgstr "" #: Mailman/Cgi/options.py:1140 +#, fuzzy msgid "Requested topic is not valid: %(topicname)s" msgstr "¯±dany temat jest nieprawid³owy: %(topicname)s" @@ -2971,6 +3063,7 @@ msgid "Private archive - \"./\" and \"../\" not allowed in URL." msgstr "Archiwum prywatne - \"./\" oraz \"../\" s± niedozwolone w adresie." #: Mailman/Cgi/private.py:109 +#, fuzzy msgid "Private Archive Error - %(msg)s" msgstr "B³±d prywatnego archiwum - %(msg)s" @@ -2989,6 +3082,7 @@ msgid "Private archive file not found" msgstr "Plik archiwum nie znaleziony" #: Mailman/Cgi/rmlist.py:76 +#, fuzzy msgid "No such list %(safelistname)s" msgstr "Lista \"%(safelistname)s\" nie istnieje na tym serwerze" @@ -3005,12 +3099,14 @@ msgid "Mailing list deletion results" msgstr "Raport z usuniêcia listy " #: Mailman/Cgi/rmlist.py:187 +#, fuzzy msgid "" "You have successfully deleted the mailing list\n" " %(listname)s." msgstr " %(listname)s zosta³a usuniêta." #: Mailman/Cgi/rmlist.py:191 +#, fuzzy msgid "" "There were some problems deleting the mailing list\n" " %(listname)s. Contact your site administrator at " @@ -3022,10 +3118,12 @@ msgstr "" " administratorem serwera %(sitelist)s." #: Mailman/Cgi/rmlist.py:208 +#, fuzzy msgid "Permanently remove mailing list %(realname)s" msgstr "Usuñ listê %(realname)s (tej czynno¶ci nie mo¿na odwróciæ)" #: Mailman/Cgi/rmlist.py:209 +#, fuzzy msgid "Permanently remove mailing list %(realname)s" msgstr "Usuñ listê %(realname)s (tej czynno¶ci nie mo¿na odwróciæ)" @@ -3089,6 +3187,7 @@ msgid "Invalid options to CGI script" msgstr "Nieprawid³owe opcje dla skryptu CGI" #: Mailman/Cgi/roster.py:118 +#, fuzzy msgid "%(realname)s roster authentication failed." msgstr "%(realname)s - b³±d uwierzytelnienia." @@ -3156,6 +3255,7 @@ msgstr "" "emaila z dalszymi wskazówkami." #: Mailman/Cgi/subscribe.py:262 +#, fuzzy msgid "" "The email address you supplied is banned from this\n" " mailing list. If you think this restriction is erroneous, please\n" @@ -3179,6 +3279,7 @@ msgstr "" "Nie mo¿esz siê zapisaæ, poniewa¿ podany adres e-mail nie jest bezpieczny." #: Mailman/Cgi/subscribe.py:278 +#, fuzzy msgid "" "Confirmation from your email address is required, to prevent anyone from\n" "subscribing you without permission. Instructions are being sent to you at\n" @@ -3192,6 +3293,7 @@ msgstr "" "obowi±zkowe." #: Mailman/Cgi/subscribe.py:290 +#, fuzzy msgid "" "Your subscription request was deferred because %(x)s. Your request has " "been\n" @@ -3212,6 +3314,7 @@ msgid "Mailman privacy alert" msgstr "Uwaga dotycz±ca prywatno¶ci" #: Mailman/Cgi/subscribe.py:316 +#, fuzzy msgid "" "An attempt was made to subscribe your address to the mailing list\n" "%(listaddr)s. You are already subscribed to this mailing list.\n" @@ -3253,6 +3356,7 @@ msgid "This list only supports digest delivery." msgstr "Ta lista udostêpnia tylko tryb paczek." #: Mailman/Cgi/subscribe.py:344 +#, fuzzy msgid "You have been successfully subscribed to the %(realname)s mailing list." msgstr "Zosta³e¶ zapisany na listê %(realname)s." @@ -3276,6 +3380,7 @@ msgid "Usage:" msgstr "Stosowanie:" #: Mailman/Commands/cmd_confirm.py:50 +#, fuzzy msgid "" "Invalid confirmation string. Note that confirmation strings expire\n" "approximately %(days)s days after the initial request. They also expire if\n" @@ -3301,6 +3406,7 @@ msgstr "" "zmieni³e¶ swój adres?" #: Mailman/Commands/cmd_confirm.py:69 +#, fuzzy msgid "" "You are currently banned from subscribing to this list. If you think this\n" "restriction is erroneous, please contact the list owners at\n" @@ -3380,26 +3486,32 @@ msgid "n/a" msgstr "brak" #: Mailman/Commands/cmd_info.py:44 +#, fuzzy msgid "List name: %(listname)s" msgstr "Nazwa listy: %(listname)s" #: Mailman/Commands/cmd_info.py:45 +#, fuzzy msgid "Description: %(description)s" msgstr "Opis: %(description)s" #: Mailman/Commands/cmd_info.py:46 +#, fuzzy msgid "Postings to: %(postaddr)s" msgstr "Adres listy: %(postaddr)s" #: Mailman/Commands/cmd_info.py:47 +#, fuzzy msgid "List Helpbot: %(requestaddr)s" msgstr "Automat listowy: %(requestaddr)s" #: Mailman/Commands/cmd_info.py:48 +#, fuzzy msgid "List Owners: %(owneraddr)s" msgstr "Opiekun listy: %(owneraddr)s" #: Mailman/Commands/cmd_info.py:49 +#, fuzzy msgid "More information: %(listurl)s" msgstr "Wiêcej informacji: %(listurl)s" @@ -3423,18 +3535,22 @@ msgstr "" " GNU Mailman.\n" #: Mailman/Commands/cmd_lists.py:44 +#, fuzzy msgid "Public mailing lists at %(hostname)s:" msgstr "Ogólnie dostêpne listy na serwerze %(hostname)s:" #: Mailman/Commands/cmd_lists.py:66 +#, fuzzy msgid "%(i)3d. List name: %(realname)s" msgstr "%(i)3d. Nazwa listy: %(realname)s" #: Mailman/Commands/cmd_lists.py:67 +#, fuzzy msgid " Description: %(description)s" msgstr " Opis: %(description)s" #: Mailman/Commands/cmd_lists.py:68 +#, fuzzy msgid " Requests to: %(requestaddr)s" msgstr " Polecenia na: %(requestaddr)s" @@ -3468,12 +3584,14 @@ msgstr "" " prenumeratora.\n" #: Mailman/Commands/cmd_password.py:51 Mailman/Commands/cmd_password.py:66 +#, fuzzy msgid "Your password is: %(password)s" msgstr "Twoje has³o to: %(password)s" #: Mailman/Commands/cmd_password.py:57 Mailman/Commands/cmd_password.py:72 #: Mailman/Commands/cmd_password.py:95 Mailman/Commands/cmd_password.py:121 #: Mailman/Commands/cmd_set.py:149 Mailman/Commands/cmd_set.py:219 +#, fuzzy msgid "You are not a member of the %(listname)s mailing list" msgstr "Nie jeste¶ cz³onkiem listy %(listname)s" @@ -3659,6 +3777,7 @@ msgstr "" "przypominanie has³a.\n" #: Mailman/Commands/cmd_set.py:122 +#, fuzzy msgid "Bad set command: %(subcmd)s" msgstr "B³êdne polecenie set: %(subcmd)s" @@ -3679,6 +3798,7 @@ msgid "on" msgstr "w³±czone" #: Mailman/Commands/cmd_set.py:154 +#, fuzzy msgid " ack %(onoff)s" msgstr " powiadamianie %(onoff)s" @@ -3716,22 +3836,27 @@ msgid "due to bounces" msgstr "z powodu zwrotów" #: Mailman/Commands/cmd_set.py:186 +#, fuzzy msgid " %(status)s (%(how)s on %(date)s)" msgstr " %(status)s (%(how)s dnia %(date)s)" #: Mailman/Commands/cmd_set.py:192 +#, fuzzy msgid " myposts %(onoff)s" msgstr " moje listy %(onoff)s" #: Mailman/Commands/cmd_set.py:195 +#, fuzzy msgid " hide %(onoff)s" msgstr " ukryj %(onoff)s" #: Mailman/Commands/cmd_set.py:199 +#, fuzzy msgid " duplicates %(onoff)s" msgstr " kopie %(onoff)s" #: Mailman/Commands/cmd_set.py:203 +#, fuzzy msgid " reminders %(onoff)s" msgstr " przypomnienia %(onoff)s" @@ -3740,6 +3865,7 @@ msgid "You did not give the correct password" msgstr "Poda³e¶ niepoprawne has³o" #: Mailman/Commands/cmd_set.py:236 Mailman/Commands/cmd_set.py:283 +#, fuzzy msgid "Bad argument: %(arg)s" msgstr "Niepoprawne argumenty: %(arg)s" @@ -3817,6 +3943,7 @@ msgstr "" " listê z innego adresu ni¿ ten w polu nadawcy. \n" #: Mailman/Commands/cmd_subscribe.py:62 +#, fuzzy msgid "Bad digest specifier: %(arg)s" msgstr "B³êdne okre¶lenie trybu dostarczania: %(arg)s" @@ -3825,6 +3952,7 @@ msgid "No valid address found to subscribe" msgstr "W wiadomo¶ci nie znaleziono adresu do zapisania" #: Mailman/Commands/cmd_subscribe.py:113 +#, fuzzy msgid "" "The email address you supplied is banned from this mailing list.\n" "If you think this restriction is erroneous, please contact the list\n" @@ -3863,6 +3991,7 @@ msgid "This list only supports digest subscriptions!" msgstr "Ta lista umo¿liwia zapisywanie siê tylko w trybie paczek!" #: Mailman/Commands/cmd_subscribe.py:146 +#, fuzzy msgid "" "Your subscription request has been forwarded to the list administrator\n" "at %(listowner)s for review." @@ -3896,6 +4025,7 @@ msgstr "" " innego adresu ni¿ ten w polu nadawcy. \n" #: Mailman/Commands/cmd_unsubscribe.py:62 +#, fuzzy msgid "%(address)s is not a member of the %(listname)s mailing list" msgstr "%(address)s nie jest zapisany na listê %(listname)s" @@ -4145,6 +4275,7 @@ msgid "Chinese (Taiwan)" msgstr "chiñski (Tajwan)" #: Mailman/Deliverer.py:53 +#, fuzzy msgid "" "Note: Since this is a list of mailing lists, administrative\n" "notices like the password reminder will be sent to\n" @@ -4159,14 +4290,17 @@ msgid " (Digest mode)" msgstr " (Tryb paczek)" #: Mailman/Deliverer.py:79 +#, fuzzy msgid "Welcome to the \"%(realname)s\" mailing list%(digmode)s" msgstr "Witaj na li¶cie \"%(realname)s\" %(digmode)s" #: Mailman/Deliverer.py:89 +#, fuzzy msgid "You have been unsubscribed from the %(realname)s mailing list" msgstr "Zosta³e¶ wypisany z listy \"%(realname)s\"" #: Mailman/Deliverer.py:116 +#, fuzzy msgid "%(listfullname)s mailing list reminder" msgstr "Przypomnienie o li¶cie %(listfullname)s" @@ -4179,6 +4313,7 @@ msgid "Hostile subscription attempt detected" msgstr "Nieautoryzowana próba prenumeraty" #: Mailman/Deliverer.py:169 +#, fuzzy msgid "" "%(address)s was invited to a different mailing\n" "list, but in a deliberate malicious attempt they tried to confirm the\n" @@ -4191,6 +4326,7 @@ msgstr "" "wiêcej robiæ." #: Mailman/Deliverer.py:188 +#, fuzzy msgid "" "You invited %(address)s to your list, but in a\n" "deliberate malicious attempt, they tried to confirm the invitation to a\n" @@ -4204,6 +4340,7 @@ msgstr "" "wiêcej robiæ." #: Mailman/Deliverer.py:221 +#, fuzzy msgid "%(listname)s mailing list probe message" msgstr "Test listy %(listname)s" @@ -4409,8 +4546,8 @@ msgid "" " membership.\n" "\n" "

                    You can control both the\n" -" number\n" +" number\n" " of reminders the member will receive and the\n" " No those messages too will get discarded. You may " "want\n" " to set up an\n" -" autoresponse\n" +" autoresponse\n" " message for email to the -owner and -admin address." msgstr "" "Automatyczne rozpoznawanie zwrotów jest trudnym zadaniem. Mailman " @@ -4656,6 +4793,7 @@ msgstr "" "dostarczeniem wiadomo¶ci" #: Mailman/Gui/Bounce.py:194 +#, fuzzy msgid "" "Bad value for %(property)s: %(val)s" @@ -4720,8 +4858,8 @@ msgstr "" "wiadomo¶æ\n" " przyjdzie na adres listy i w³±czona jest opcja filtrowania tre¶ci, ka¿dy z " "za³±czników \n" -" porównywany jest z list± zabronionych rozszerzeñ plików.\n" +" porównywany jest z list± zabronionych rozszerzeñ plików.\n" " Je¿eli za³±cznik pasuje do jakiegokolwiek pozycji z tej listy, zostaje " "usuniêty.\n" "\n" @@ -4746,8 +4884,8 @@ msgstr "" "

                    Ostatecznie, pozosta³e w wiadomo¶ci czê¶ci text/html\n" " mog± zostaæ przekszta³cone na text/plain je¶li jest ustawiona " "opcja\n" -" convert_html_to_plaintext\n" +" convert_html_to_plaintext\n" " a serwer zezwala na takie przekszta³cenia." #: Mailman/Gui/ContentFilter.py:75 @@ -4799,8 +4937,8 @@ msgid "" "Use this option to remove each message attachment that does\n" " not have a matching content type. Requirements and formats " "are\n" -" exactly like filter_mime_types.\n" "\n" "

                    Note: if you add entries to this list but don't add\n" @@ -4889,8 +5027,9 @@ msgstr "" "Mo¿e siê tak staæ na ka¿dym etapie filtrowania tre¶ci: \n" "zabronionych typów " "filter_mime_types \n" -"jedynie dozwolonych typów pass_mime_types, albo przy koñcowej redukcji za³±czników z³o¿onych.\n" +"jedynie dozwolonych typów pass_mime_types, albo przy koñcowej redukcji " +"za³±czników z³o¿onych.\n" "

                    To ustawienie nie dotyczy sytuacji, w której wiadomo¶æ po filtrowaniu\n" "wci±¿ bêdzie zawiera³a jak±kolwiek tre¶æ. Wtedy zostanie ona przekazana \n" "wszystkim cz³onkom listy.\n" @@ -4905,6 +5044,7 @@ msgstr "" "serwera." #: Mailman/Gui/ContentFilter.py:171 +#, fuzzy msgid "Bad MIME type ignored: %(spectype)s" msgstr "Zignorowano b³êdny typ MIME: %(spectype)s" @@ -5012,6 +5152,7 @@ msgstr "" " by³aby one pusta?" #: Mailman/Gui/Digest.py:145 +#, fuzzy msgid "" "The next digest will be sent as volume\n" " %(volume)s, number %(number)s" @@ -5028,14 +5169,17 @@ msgid "There was no digest to send." msgstr "Brak skolejkowanych paczek do wys³ania." #: Mailman/Gui/GUIBase.py:173 +#, fuzzy msgid "Invalid value for variable: %(property)s" msgstr "Niepoprawna warto¶æ zmiennej: %(property)s" #: Mailman/Gui/GUIBase.py:178 +#, fuzzy msgid "Bad email address for option %(property)s: %(error)s" msgstr "B³êdny adres email dla opcji %(property)s: %(error)s" #: Mailman/Gui/GUIBase.py:204 +#, fuzzy msgid "" "The following illegal substitution variables were\n" " found in the %(property)s string:\n" @@ -5050,6 +5194,7 @@ msgstr "" " tego problemu." #: Mailman/Gui/GUIBase.py:218 +#, fuzzy msgid "" "Your %(property)s string appeared to\n" " have some correctable problems in its new value.\n" @@ -5147,8 +5292,8 @@ msgid "" "

                    In order to split the list ownership duties into\n" " administrators and moderators, you must\n" " set a separate moderator password,\n" -" and also provide the email\n" +" and also provide the email\n" " addresses of the list moderators. Note that the field you\n" " are changing here specifies the list administrators." msgstr "" @@ -5461,13 +5606,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5526,8 +5671,8 @@ msgstr "Wybrany adres w nag msgid "" "This is the address set in the Reply-To: header\n" " when the reply_goes_to_list\n" +" href=\"?VARHELP=general/" +"reply_goes_to_list\">reply_goes_to_list\n" " option is set to Explicit address.\n" "\n" "

                    There are many reasons not to introduce or override the\n" @@ -5535,13 +5680,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5563,8 +5708,8 @@ msgid "" " Reply-To: header, it will not be changed." msgstr "" "Jest to adres wprowadzany do nag³ówka Reply-To:,\n" -" gdy opcja reply_goes_to_list\n" +" gdy opcja reply_goes_to_list\n" " jest ustawiona na Wybrany adres.\n" "\n" "

                    Istnieje wiele powodów, dla których nie nale¿y wprowadzaæ lub zmieniaæ\n" @@ -5640,8 +5785,8 @@ msgid "" " member list addresses, but rather to the owner of those member\n" " lists. In that case, the value of this setting is appended to\n" " the member's account name for such notices. `-owner' is the\n" -" typical choice. This setting has no effect when \"umbrella_list" -"\"\n" +" typical choice. This setting has no effect when " +"\"umbrella_list\"\n" " is \"No\"." msgstr "" "Gdy opcja dotycz±ca listy parasolowej jest ustawiona (subskrybentami s± " @@ -5799,8 +5944,8 @@ msgid "" "Default options for new members joining this list." msgstr "" -"Domy¶lne ustawienia dla nowych prenumeratorów." +"Domy¶lne ustawienia dla nowych prenumeratorów." #: Mailman/Gui/General.py:413 msgid "" @@ -6516,6 +6661,7 @@ msgstr "" "innych bez ich zgody." #: Mailman/Gui/Privacy.py:104 +#, fuzzy msgid "" "This section allows you to configure subscription and\n" " membership exposure policy. You can also control whether this\n" @@ -6686,8 +6832,8 @@ msgid "" " either individually or as a group. Any\n" " posting from a non-member who is not explicitly accepted,\n" " rejected, or discarded, will have their posting filtered by the\n" -" general\n" +" general\n" " non-member rules.\n" "\n" "

                    In the text boxes below, add one address per line; start the\n" @@ -6722,8 +6868,8 @@ msgstr "" " zarówno indywidualnie jak i grupowo. Ka¿dy email wys³any \n" " przez osobê nie bêd±c± prenumeratorem, który nie zosta³ wprost\n" " zaakceptowany, odrzucony lub skasowany, podlega\n" -" ogólnej\n" +" ogólnej\n" " zasadzie stosowanej dla nie subskrybentów.

                    W polach nale¿y " "wpisywaæ po jednym adresie w linii. Znak ^ napocz±tku nowej linii oznacza wyra¿enie regularne w " @@ -6739,6 +6885,7 @@ msgid "By default, should new list member postings be moderated?" msgstr "Czy domy¶lnie wiadomo¶ci od nowych prenumeratorów maj± byæ moderowane?" #: Mailman/Gui/Privacy.py:218 +#, fuzzy msgid "" "Each list member has a moderation flag which says\n" " whether messages from the list member can be posted directly " @@ -6791,8 +6938,8 @@ msgid "" "If a member posts this many times, within a period of time\n" " the member is automatically moderated. Use 0 to disable. " "See\n" -" member_verbosity_interval for details on the time " "period.\n" "\n" @@ -6809,8 +6956,9 @@ msgstr "" "Je¶li subskrybent wy¶le tyle wiadomo¶ci w danym okresie,\n" " to wiadomo¶ci od niego bêd± automatycznie moderowane. U¿yj 0, aby " "wy³±czyæ.\n" -" member_verbosity_interval zawiera szczegó³y dotycz±ce okresu.\n" +" member_verbosity_interval zawiera szczegó³y " +"dotycz±ce okresu.\n" "\n" "

                    Funkcja ta ma powstrzymywaæ subskrybentów do³±czaj±cych do list " "a pó¼niej u¿ywaj±cych robotów do wysy³ania wielu niepo¿±danych wiadomo¶ci " @@ -6898,6 +7046,7 @@ msgstr "" "od moderowanego subskrybenta listy." #: Mailman/Gui/Privacy.py:290 +#, fuzzy msgid "" "Action to take when anyone posts to the\n" " list from a domain with a DMARC Reject%(quarantine)s Policy." @@ -6942,8 +7091,8 @@ msgstr "" "\n" "

                  • Odrzuæ -- automatycznie odrzuca wiadomo¶ci, wysy³aj±c " "informacjê zwrotn± do autora.\n" -" Jej tre¶æ mo¿na okre¶liæ.\n" +" Jej tre¶æ mo¿na okre¶liæ.\n" "\n" "

                  • Zignoruj -- wiadomo¶æ jest odrzucana bez powiadamiania o tym " "jej autora.\n" @@ -7022,14 +7171,15 @@ msgstr "" "dmarc_quarantine_moderation_action).

                  • Tak -- stosuje " "dmarc_moderation_action do wiadomo¶ci z nag³ówkiem \"From:\" z domeny " "z polityk± DMARC \"p=none\" je¶li\n" -" dmarc_moderation_action ustawiono na \"Munge From\" lub \"Wrap Message" -"\" oraz dmarc_quarantine_moderation_action ustawiono na \"Tak\".\n" +" dmarc_moderation_action ustawiono na \"Munge From\" lub \"Wrap " +"Message\" oraz dmarc_quarantine_moderation_action ustawiono na \"Tak\".\n" "

                    Ustawienie to ma ograniczaæ raporty o niepowodzeniu dla w³a¶ciciela " "domeny z ustawion± polityk± DMARC \"p=none\" poprzez stosowanie\n" " przekszta³ceñ wiadomo¶ci, które mia³yby miejsce gdyby polityka DMARC " "domeny by³a silniejsza." #: Mailman/Gui/Privacy.py:353 +#, fuzzy msgid "" "Text to include in any\n" " akceptowane,\n" -" wstrzymane,\n" +" wstrzymane,\n" " odrzucone (odbicia) lub\n" " The body of the message can also be optionally scanned for\n" " Subject: and Keywords: headers, as\n" " specified by the topics_bodylines_limit\n" +" href=\"?VARHELP=topics/" +"topics_bodylines_limit\">topics_bodylines_limit\n" " configuration variable." msgstr "" "Filtr tematyczny dzieli wszystkie przychodz±ce wiadomo¶ci \n" @@ -7657,6 +7808,7 @@ msgstr "" " i wzorzec. Niekompletne tematy bêd± zignorowane." #: Mailman/Gui/Topics.py:135 +#, fuzzy msgid "" "The topic pattern '%(safepattern)s' is not a\n" " legal regular expression. It will be discarded." @@ -7839,6 +7991,7 @@ msgid "%(listinfo_link)s list run by %(owner_link)s" msgstr "List± %(listinfo_link)s opiekuje siê %(owner_link)s" #: Mailman/HTMLFormatter.py:57 +#, fuzzy msgid "%(realname)s administrative interface" msgstr "Interfejs administracyjny listy %(realname)s" @@ -7847,6 +8000,7 @@ msgid " (requires authorization)" msgstr " (wymagane uwierzytelnienie)" #: Mailman/HTMLFormatter.py:61 +#, fuzzy msgid "Overview of all %(hostname)s mailing lists" msgstr "Przegl±d wszystkich list na %(hostname)s" @@ -7867,6 +8021,7 @@ msgid "; it was disabled by the list administrator" msgstr "; wy³±czone przez administratora listy" #: Mailman/HTMLFormatter.py:146 +#, fuzzy msgid "" "; it was disabled due to excessive bounces. The\n" " last bounce was received on %(date)s" @@ -7879,6 +8034,7 @@ msgid "; it was disabled for unknown reasons" msgstr "; zosta³o wy³±czone z nieznanych powodów" #: Mailman/HTMLFormatter.py:151 +#, fuzzy msgid "Note: your list delivery is currently disabled%(reason)s." msgstr "Uwaga: Dostarczanie listów jest w tym momencie wy³±czone%(reason)s." @@ -7891,6 +8047,7 @@ msgid "the list administrator" msgstr "administrator listy" #: Mailman/HTMLFormatter.py:157 +#, fuzzy msgid "" "

                    %(note)s\n" "\n" @@ -7908,6 +8065,7 @@ msgstr "" "w±tpliwo¶ci proszê kierowaæ na adres %(mailto)s." #: Mailman/HTMLFormatter.py:169 +#, fuzzy msgid "" "

                    We have received some recent bounces from your\n" " address. Your current bounce score is %(score)s out of " @@ -7928,6 +8086,7 @@ msgstr "" "problem szybko zniknie." #: Mailman/HTMLFormatter.py:181 +#, fuzzy msgid "" "(Note - you are subscribing to a list of mailing lists, so the %(type)s " "notice will be sent to the admin address for your membership, %(addr)s.)

                    " @@ -7973,6 +8132,7 @@ msgstr "" " automatycznie." #: Mailman/HTMLFormatter.py:208 +#, fuzzy msgid "" "This is %(also)sa private list, which means that the\n" " list of members is not available to non-members." @@ -7981,6 +8141,7 @@ msgstr "" " wy¶wietliæ adresów jej prenumeratorów." #: Mailman/HTMLFormatter.py:211 +#, fuzzy msgid "" "This is %(also)sa hidden list, which means that the\n" " list of members is available only to the list administrator." @@ -7989,6 +8150,7 @@ msgstr "" " lista jej cz³onków jest dostêpna tylko dla administratora." #: Mailman/HTMLFormatter.py:214 +#, fuzzy msgid "" "This is %(also)sa public list, which means that the\n" " list of members list is available to everyone." @@ -8005,6 +8167,7 @@ msgstr "" " nie s±, w ³atwy sposób, rozpoznawalne przez spamerów)." #: Mailman/HTMLFormatter.py:222 +#, fuzzy msgid "" "

                    (Note that this is an umbrella list, intended to\n" " have only other mailing lists as members. Among other things,\n" @@ -8021,6 +8184,7 @@ msgid "either " msgstr "albo " #: Mailman/HTMLFormatter.py:256 +#, fuzzy msgid "" "To unsubscribe from %(realname)s, get a password reminder,\n" " or change your subscription options %(either)senter your " @@ -8054,6 +8218,7 @@ msgstr "" " podaæ pó¼niej." #: Mailman/HTMLFormatter.py:277 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " members.)" @@ -8062,6 +8227,7 @@ msgstr "" " listy.)" #: Mailman/HTMLFormatter.py:281 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " administrator.)" @@ -8122,6 +8288,7 @@ msgid "The current archive" msgstr "Bie¿±ce archiwum" #: Mailman/Handlers/Acknowledge.py:59 +#, fuzzy msgid "%(realname)s post acknowledgement" msgstr "%(realname)s potwierdzenie wiadomo¶ci" @@ -8138,6 +8305,7 @@ msgstr "" "nie mo¿e ona zostaæ bezpiecznie usuniêta.\n" #: Mailman/Handlers/CalcRecips.py:79 +#, fuzzy msgid "" "Your urgent message to the %(realname)s mailing list was not authorized for\n" "delivery. The original message as received by Mailman is attached.\n" @@ -8146,6 +8314,7 @@ msgstr "" "%(realname)s. W za³±czniku znajdziesz swój email.\n" #: Mailman/Handlers/CookHeaders.py:180 +#, fuzzy msgid "%(realname)s via %(lrn)s" msgstr "%(realname)s przez %(lrn)s" @@ -8214,6 +8383,7 @@ msgid "Message may contain administrivia" msgstr "Wiadomo¶æ mo¿e zawieraæ polecenia administracyjne" #: Mailman/Handlers/Hold.py:84 +#, fuzzy msgid "" "Please do *not* post administrative requests to the mailing\n" "list. If you wish to subscribe, visit %(listurl)s or send a message with " @@ -8251,10 +8421,12 @@ msgid "Posting to a moderated newsgroup" msgstr "Wiadomo¶æ wys³ana na grupê moderowan±" #: Mailman/Handlers/Hold.py:252 +#, fuzzy msgid "Your message to %(listname)s awaits moderator approval" msgstr "Wiadomo¶æ wys³ana na listê %(listname)s czeka na akceptacjê" #: Mailman/Handlers/Hold.py:271 +#, fuzzy msgid "%(listname)s post from %(sender)s requires approval" msgstr "Wiadomo¶æ na %(listname)s od %(sender)s czeka na akceptacjê" @@ -8297,6 +8469,7 @@ msgid "After content filtering, the message was empty" msgstr "Po przefiltrowaniu tre¶ci, wiadomo¶æ zosta³a pusta" #: Mailman/Handlers/MimeDel.py:269 +#, fuzzy msgid "" "The attached message matched the %(listname)s mailing list's content " "filtering\n" @@ -8315,6 +8488,7 @@ msgid "Content filtered message notification" msgstr "Powiadomienie o filtrowaniu tre¶ci" #: Mailman/Handlers/Moderate.py:145 +#, fuzzy msgid "" "Your message has been rejected, probably because you are not subscribed to " "the\n" @@ -8341,6 +8515,7 @@ msgid "The attached message has been automatically discarded." msgstr "Wiadomo¶æ z za³±cznika zosta³a automatycznie odrzucona." #: Mailman/Handlers/Replybot.py:75 +#, fuzzy msgid "Auto-response for your message to the \"%(realname)s\" mailing list" msgstr "Automatyczna odpowied¼ z listy \"%(realname)s\" " @@ -8349,6 +8524,7 @@ msgid "The Mailman Replybot" msgstr "Automat Mailman" #: Mailman/Handlers/Scrubber.py:208 +#, fuzzy msgid "" "An embedded and charset-unspecified text was scrubbed...\n" "Name: %(filename)s\n" @@ -8363,6 +8539,7 @@ msgid "HTML attachment scrubbed and removed" msgstr "Za³±cznik HTML zosta³ usuniêty" #: Mailman/Handlers/Scrubber.py:234 Mailman/Handlers/Scrubber.py:259 +#, fuzzy msgid "" "An HTML attachment was scrubbed...\n" "URL: %(url)s\n" @@ -8383,6 +8560,7 @@ msgid "unknown sender" msgstr "nieznany nadawca" #: Mailman/Handlers/Scrubber.py:276 +#, fuzzy msgid "" "An embedded message was scrubbed...\n" "From: %(who)s\n" @@ -8399,6 +8577,7 @@ msgstr "" "Adres: %(url)s\n" #: Mailman/Handlers/Scrubber.py:308 +#, fuzzy msgid "" "A non-text attachment was scrubbed...\n" "Name: %(filename)s\n" @@ -8415,6 +8594,7 @@ msgstr "" "Adres: %(url)s\n" #: Mailman/Handlers/Scrubber.py:347 +#, fuzzy msgid "Skipped content of type %(partctype)s\n" msgstr "Pominiêto zawarto¶æ typu %(partctype)s\n" @@ -8428,6 +8608,7 @@ msgid "Header matched regexp: %(pattern)s" msgstr "Adres na czarnej li¶cie (pasuje do %(pattern)s)" #: Mailman/Handlers/SpamDetect.py:127 +#, fuzzy msgid "" "You are not allowed to post to this mailing list From: a domain which\n" "publishes a DMARC policy of reject or quarantine, and your message has been\n" @@ -8446,6 +8627,7 @@ msgid "Message rejected by filter rule match" msgstr "Wiadomo¶æ odrzucona przez filtr" #: Mailman/Handlers/ToDigest.py:173 +#, fuzzy msgid "%(realname)s Digest, Vol %(volume)d, Issue %(issue)d" msgstr "Paczka %(realname)s, Tom %(volume)d, Numer %(issue)d" @@ -8482,6 +8664,7 @@ msgid "End of " msgstr "Koniec " #: Mailman/ListAdmin.py:309 +#, fuzzy msgid "Posting of your message titled \"%(subject)s\"" msgstr "Twoja wiadomo¶æ o tytule \"%(subject)s\"" @@ -8494,6 +8677,7 @@ msgid "Forward of moderated message" msgstr "Przekazywanie wiadomo¶ci moderowanej" #: Mailman/ListAdmin.py:405 +#, fuzzy msgid "New subscription request to list %(realname)s from %(addr)s" msgstr "Nowa pro¶ba o zapisanie na listê %(realname)s od %(addr)s" @@ -8507,6 +8691,7 @@ msgid "via admin approval" msgstr "Oczekiwanie na reakcjê moderatora" #: Mailman/ListAdmin.py:466 +#, fuzzy msgid "New unsubscription request from %(realname)s by %(addr)s" msgstr "Nowa pro¶ba wypisania z listy %(realname)s od %(addr)s" @@ -8519,10 +8704,12 @@ msgid "Original Message" msgstr "Wiadomo¶æ oryginalna" #: Mailman/ListAdmin.py:526 +#, fuzzy msgid "Request to mailing list %(realname)s rejected" msgstr "Odrzucona pro¶ba wys³ana na listê %(realname)s" #: Mailman/MTA/Manual.py:66 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been created via the through-the-web\n" "interface. In order to complete the activation of this mailing list, the\n" @@ -8549,14 +8736,17 @@ msgstr "" "bazê aliasów poleceniem 'newaliases':\n" #: Mailman/MTA/Manual.py:82 +#, fuzzy msgid "## %(listname)s mailing list" msgstr "## Lista %(listname)s" #: Mailman/MTA/Manual.py:99 +#, fuzzy msgid "Mailing list creation request for list %(listname)s" msgstr "Pro¶ba o utworzenie listy %(listname)s" #: Mailman/MTA/Manual.py:113 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been removed via the through-the-web\n" "interface. In order to complete the de-activation of this mailing list, " @@ -8574,6 +8764,7 @@ msgstr "" "W pliku /etc/aliases nale¿y skasowaæ nastêpuj±ce linie:\n" #: Mailman/MTA/Manual.py:123 +#, fuzzy msgid "" "\n" "To finish removing your mailing list, you must edit your /etc/aliases (or\n" @@ -8590,14 +8781,17 @@ msgstr "" "## Lista %(listname)s" #: Mailman/MTA/Manual.py:142 +#, fuzzy msgid "Mailing list removal request for list %(listname)s" msgstr "Pro¶ba o usuniêcie listy %(listname)s" #: Mailman/MTA/Postfix.py:442 +#, fuzzy msgid "checking permissions on %(file)s" msgstr "kontrola praw dostêpu do pliku %(file)s" #: Mailman/MTA/Postfix.py:452 +#, fuzzy msgid "%(file)s permissions must be 0664 (got %(octmode)s)" msgstr "" "prawa dostêpu do pliku %(file)s powinny byæ ustawione na 0664 (s± " @@ -8613,36 +8807,44 @@ msgid "(fixing)" msgstr "(poprawione)" #: Mailman/MTA/Postfix.py:470 +#, fuzzy msgid "checking ownership of %(dbfile)s" msgstr "kontrola w³a¶ciciela pliku %(dbfile)s" #: Mailman/MTA/Postfix.py:478 +#, fuzzy msgid "%(dbfile)s owned by %(owner)s (must be owned by %(user)s" msgstr "%(owner)s jest w³a¶cicielem pliku %(dbfile)s (a musi byæ %(user)s)" #: Mailman/MTA/Postfix.py:491 +#, fuzzy msgid "%(dbfile)s permissions must be 0664 (got %(octmode)s)" msgstr "" "prawa dostêpu do pliku %(dbfile)s powinny byæ ustawione na 0664 (s± " "%(octmode)s)" #: Mailman/MailList.py:219 +#, fuzzy msgid "Your confirmation is required to join the %(listname)s mailing list" msgstr "Prosimy o potwierdzenie prenumeraty listy %(listname)s" #: Mailman/MailList.py:230 +#, fuzzy msgid "Your confirmation is required to leave the %(listname)s mailing list" msgstr "Prosimy o potwierdzenie wypisania siê z listy %(listname)s" #: Mailman/MailList.py:999 Mailman/MailList.py:1492 +#, fuzzy msgid " from %(remote)s" msgstr " z adresu %(remote)s" #: Mailman/MailList.py:1043 +#, fuzzy msgid "subscriptions to %(realname)s require moderator approval" msgstr "prenumerata listy %(realname)s wymaga zgody administratora" #: Mailman/MailList.py:1125 bin/add_members:299 +#, fuzzy msgid "%(realname)s subscription notification" msgstr "Powiadomienie o prenumeracie %(realname)s" @@ -8651,10 +8853,12 @@ msgid "unsubscriptions require moderator approval" msgstr "wypisanie siê wymaga zgody administratora" #: Mailman/MailList.py:1166 +#, fuzzy msgid "%(realname)s unsubscribe notification" msgstr "Powiadomienie o wypisaniu siê %(realname)s" #: Mailman/MailList.py:1328 +#, fuzzy msgid "%(realname)s address change notification" msgstr "%(realname)s - powiadomienie o zmianie adresu" @@ -8669,6 +8873,7 @@ msgid "via web confirmation" msgstr "Nieprawid³owy kod potwierdzaj±cy" #: Mailman/MailList.py:1396 +#, fuzzy msgid "subscriptions to %(name)s require administrator approval" msgstr "%(name)s prosi o zatwierdzenie jego subskrypcji" @@ -8687,6 +8892,7 @@ msgid "Last autoresponse notification for today" msgstr "Dzisiaj nie otrzymasz ju¿ kolejnych automatycznych wiadomo¶ci" #: Mailman/Queue/BounceRunner.py:360 +#, fuzzy msgid "" "The attached message was received as a bounce, but either the bounce format\n" "was not recognized, or no member addresses could be extracted from it. " @@ -8775,6 +8981,7 @@ msgid "Original message suppressed by Mailman site configuration\n" msgstr "" #: Mailman/htmlformat.py:675 +#, fuzzy msgid "Delivered by Mailman
                    version %(version)s" msgstr "Wys³ane przez program Mailman
                    , (wersja %(version)s)" @@ -8863,6 +9070,7 @@ msgid "Server Local Time" msgstr "Lokalny Czas Serwera" #: Mailman/i18n.py:180 +#, fuzzy msgid "" "%(wday)s %(mon)s %(day)2i %(hh)02i:%(mm)02i:%(ss)02i %(tzname)s %(year)04i" msgstr "" @@ -8932,6 +9140,7 @@ msgid "" msgstr "" #: bin/add_members:162 bin/add_members:172 +#, fuzzy msgid "Already a member: %(member)s" msgstr "%(member)s jest ju¿ prenumeratorem" @@ -8940,12 +9149,14 @@ msgid "Bad/Invalid email address: blank line" msgstr "B³êdny format adresu" #: bin/add_members:180 +#, fuzzy msgid "Bad/Invalid email address: %(member)s" msgstr "B³êdny adres email: %(member)s" #: bin/add_members:182 +#, fuzzy msgid "Hostile address (illegal characters): %(member)s" -msgstr "" +msgstr "Nieprawid³owy adres (niedopuszczalne znaki)" #: bin/add_members:185 #, fuzzy @@ -8953,16 +9164,19 @@ msgid "Invited: %(member)s" msgstr "Zapisano: %(member)s" #: bin/add_members:187 +#, fuzzy msgid "Subscribed: %(member)s" msgstr "Zapisano: %(member)s" #: bin/add_members:237 +#, fuzzy msgid "Bad argument to -w/--welcome-msg: %(arg)s" -msgstr "" +msgstr "B³êdne argumenty: %(pargs)s" #: bin/add_members:244 +#, fuzzy msgid "Bad argument to -a/--admin-notify: %(arg)s" -msgstr "" +msgstr "Niepoprawne argumenty: %(arg)s" #: bin/add_members:252 msgid "Cannot read both digest and normal members from standard input." @@ -8975,6 +9189,7 @@ msgstr "" #: bin/add_members:261 bin/config_list:110 bin/export.py:271 bin/find_member:97 #: bin/inject:91 bin/list_admins:90 bin/list_members:252 bin/sync_members:222 #: cron/bumpdigests:86 +#, fuzzy msgid "No such list: %(listname)s" msgstr "Lista %(listname)s nie istnieje na tym serwerze" @@ -9038,6 +9253,7 @@ msgid "listname is required" msgstr "podaj nazwê listy" #: bin/arch:143 bin/change_pw:107 bin/config_list:257 +#, fuzzy msgid "" "No such list \"%(listname)s\"\n" "%(e)s" @@ -9046,6 +9262,7 @@ msgstr "" "%(e)s" #: bin/arch:168 +#, fuzzy msgid "Cannot open mbox file %(mbox)s: %(msg)s" msgstr "Nie mo¿na otworzyæ pliku mbox %(mbox)s: %(msg)s" @@ -9127,6 +9344,7 @@ msgid "" msgstr "" #: bin/change_pw:145 +#, fuzzy msgid "Bad arguments: %(strargs)s" msgstr "B³êdny argument: %(strargs)s" @@ -9135,10 +9353,12 @@ msgid "Empty list passwords are not allowed" msgstr "Puste has³a nie s± dozwolone" #: bin/change_pw:181 +#, fuzzy msgid "New %(listname)s password: %(notifypassword)s" msgstr "Nowe has³o %(notifypassword)s dla listy %(listname)s" #: bin/change_pw:190 +#, fuzzy msgid "Your new %(listname)s list password" msgstr "Nowe has³o dla listy %(listname)s" @@ -9203,6 +9423,7 @@ msgid "List:" msgstr "Lista:" #: bin/check_db:148 +#, fuzzy msgid " %(file)s: okay" msgstr " %(file)s: OK" @@ -9218,38 +9439,46 @@ msgid "" msgstr "" #: bin/check_perms:110 +#, fuzzy msgid " checking gid and mode for %(path)s" -msgstr "" +msgstr "sprawdzam uprawnienia dla %(private)s" #: bin/check_perms:122 msgid "%(path)s bad group (has: %(groupname)s, expected %(MAILMAN_GROUP)s)" msgstr "" #: bin/check_perms:151 +#, fuzzy msgid "directory permissions must be %(octperms)s: %(path)s" -msgstr "" +msgstr "uprawnienia plików musz± byæ ustawione na co najmniej 660: %(path)s" #: bin/check_perms:160 +#, fuzzy msgid "source perms must be %(octperms)s: %(path)s" -msgstr "" +msgstr "uprawnienia plików musz± byæ ustawione na co najmniej 660: %(path)s" #: bin/check_perms:171 +#, fuzzy msgid "article db files must be %(octperms)s: %(path)s" -msgstr "" +msgstr "uprawnienia plików musz± byæ ustawione na co najmniej 660: %(path)s" #: bin/check_perms:183 +#, fuzzy msgid "checking mode for %(prefix)s" -msgstr "" +msgstr "sprawdzam uprawnienia dla %(private)s" #: bin/check_perms:193 +#, fuzzy msgid "WARNING: directory does not exist: %(d)s" msgstr "Ostrze¿enie: nie ma takiego katalogu: %(d)s" #: bin/check_perms:197 +#, fuzzy msgid "directory must be at least 02775: %(d)s" msgstr "Katalog musi mieæ przynajmniej uprawnienia 02775: %(d)s" #: bin/check_perms:209 +#, fuzzy msgid "checking perms on %(private)s" msgstr "sprawdzam uprawnienia dla %(private)s" @@ -9279,38 +9508,46 @@ msgid "checking cgi-bin permissions" msgstr "sprawdzam uprawnienia skryptów cgi-bin" #: bin/check_perms:278 +#, fuzzy msgid " checking set-gid for %(path)s" -msgstr "" +msgstr "sprawdzam uprawnienia dla %(private)s" #: bin/check_perms:282 msgid "%(path)s must be set-gid" msgstr "" #: bin/check_perms:292 +#, fuzzy msgid "checking set-gid for %(wrapper)s" -msgstr "" +msgstr "sprawdzam uprawnienia dla %(private)s" #: bin/check_perms:296 msgid "%(wrapper)s must be set-gid" msgstr "" #: bin/check_perms:306 +#, fuzzy msgid "checking permissions on %(pwfile)s" -msgstr "" +msgstr "kontrola praw dostêpu do pliku %(file)s" #: bin/check_perms:315 +#, fuzzy msgid "%(pwfile)s permissions must be exactly 0640 (got %(octmode)s)" msgstr "" +"prawa dostêpu do pliku %(file)s powinny byæ ustawione na 0664 (s± " +"%(octmode)s)" #: bin/check_perms:340 msgid "checking permissions on list data" msgstr "" #: bin/check_perms:348 +#, fuzzy msgid " checking permissions on: %(path)s" -msgstr "" +msgstr "kontrola praw dostêpu do pliku %(file)s" #: bin/check_perms:356 +#, fuzzy msgid "file permissions must be at least 660: %(path)s" msgstr "uprawnienia plików musz± byæ ustawione na co najmniej 660: %(path)s" @@ -9367,8 +9604,9 @@ msgid "Unix-From line changed: %(lineno)d" msgstr "" #: bin/cleanarch:111 +#, fuzzy msgid "Bad status number: %(arg)s" -msgstr "" +msgstr "Niepoprawne argumenty: %(arg)s" #: bin/cleanarch:167 msgid "%(messages)d messages found" @@ -9465,10 +9703,12 @@ msgid " original address removed:" msgstr "usuniêto oryginalny adres:" #: bin/clone_member:202 +#, fuzzy msgid "Not a valid email address: %(toaddr)s" msgstr "B³êdny adres email: %(toaddr)s" #: bin/clone_member:215 +#, fuzzy msgid "" "Error opening list \"%(listname)s\", skipping.\n" "%(e)s" @@ -9547,10 +9787,12 @@ msgid "legal values are:" msgstr "dozwolone warto¶ci to:" #: bin/config_list:270 +#, fuzzy msgid "attribute \"%(k)s\" ignored" msgstr "zignorowano atrybut \"%(k)s\"" #: bin/config_list:273 +#, fuzzy msgid "attribute \"%(k)s\" changed" msgstr "zmieniono atrybut \"%(k)s\"" @@ -9559,10 +9801,12 @@ msgid "Non-standard property restored: %(k)s" msgstr "" #: bin/config_list:288 +#, fuzzy msgid "Invalid value for property: %(k)s" msgstr "B³êdna warto¶æ dla opcji: %(k)s" #: bin/config_list:291 +#, fuzzy msgid "Bad email address for option %(k)s: %(v)s" msgstr "B³êdny adres e-mail dla opcji %(k)s: %(v)s" @@ -9611,14 +9855,17 @@ msgid "" msgstr "" #: bin/discard:94 +#, fuzzy msgid "Ignoring non-held message: %(f)s" msgstr "Zignorowano niewstrzyman± wiadomo¶æ: %(f)s." #: bin/discard:100 +#, fuzzy msgid "Ignoring held msg w/bad id: %(f)s" msgstr "Zignorowano wstrzyman± wiadomo¶æ o z³ym id: %(f)s" #: bin/discard:112 +#, fuzzy msgid "Discarded held msg #%(id)s for list %(listname)s" msgstr "Odrzucone wstrzymanie wiadomo¶ci #%(id)s na li¶cie %(listname)s" @@ -9664,6 +9911,7 @@ msgid "No filename given." msgstr "Nie podano nazwy pliku" #: bin/dumpdb:108 +#, fuzzy msgid "Bad arguments: %(pargs)s" msgstr "B³êdne argumenty: %(pargs)s" @@ -9672,14 +9920,17 @@ msgid "Please specify either -p or -m." msgstr "Proszê podaæ tylko jeden z argumentów -p lub -m" #: bin/dumpdb:133 +#, fuzzy msgid "[----- start %(typename)s file -----]" msgstr "[----- pocz±tek pliku %(typename)s -----]" #: bin/dumpdb:139 +#, fuzzy msgid "[----- end %(typename)s file -----]" msgstr "[----- koniec pliku %(typename)s -----]" #: bin/dumpdb:142 +#, fuzzy msgid "<----- start object %(cnt)s ----->" msgstr "<----- pocz±tek obiektu %(cnt)s ----->" @@ -9834,6 +10085,7 @@ msgid "Setting web_page_url to: %(web_page_url)s" msgstr "Ustawienie zmiennej web_page_url: %(web_page_url)s" #: bin/fix_url.py:88 +#, fuzzy msgid "Setting host_name to: %(mailhost)s" msgstr "Ustawienie zmiennej host_name na adres: %(mailhost)s" @@ -9889,6 +10141,7 @@ msgid "" msgstr "" #: bin/inject:84 +#, fuzzy msgid "Bad queue directory: %(qdir)s" msgstr "Z³y katalog kolejkowania: %(qdir)s" @@ -9922,6 +10175,7 @@ msgid "" msgstr "" #: bin/list_admins:97 +#, fuzzy msgid "List: %(listname)s, \tOwners: %(owners)s" msgstr "Lista: %(listname)s, \tOpiekunowie: %(owners)s" @@ -10028,10 +10282,12 @@ msgid "" msgstr "" #: bin/list_members:198 +#, fuzzy msgid "Bad --nomail option: %(why)s" msgstr "B³êdna opcja --nomail:: %(why)s" #: bin/list_members:209 +#, fuzzy msgid "Bad --digest option: %(kind)s" msgstr "B³êdna opcja --digest: %(kind)s" @@ -10172,6 +10428,7 @@ msgid "" msgstr "" #: bin/mailmanctl:152 +#, fuzzy msgid "PID unreadable in: %(pidfile)s" msgstr "Nie mogê odczytaæ PID w %(pidfile)s" @@ -10180,6 +10437,7 @@ msgid "Is qrunner even running?" msgstr "Czy program zarz±dzaj±cy kolejk± jest uruchomiony?" #: bin/mailmanctl:160 +#, fuzzy msgid "No child with pid: %(pid)s" msgstr "Brak pliku potomnego z pid: %(pid)s" @@ -10217,10 +10475,12 @@ msgid "" msgstr "" #: bin/mailmanctl:280 cron/mailpasswds:119 +#, fuzzy msgid "Site list is missing: %(sitelistname)s" msgstr "Brakuje listy systemowej: %(sitelistname)s" #: bin/mailmanctl:305 +#, fuzzy msgid "Run this program as root or as the %(name)s user, or use -u." msgstr "" "Uruchom program jako root lub jako u¿ytkownik %(name)s, albo \n" @@ -10231,6 +10491,7 @@ msgid "No command given." msgstr "Nie podano polecenia." #: bin/mailmanctl:339 +#, fuzzy msgid "Bad command: %(command)s" msgstr "B³êdne polecenie: %(command)s" @@ -10288,6 +10549,7 @@ msgid "list creator" msgstr "twórca list" #: bin/mmsitepass:86 +#, fuzzy msgid "New %(pwdesc)s password: " msgstr "Nowe %(pwdesc)s has³o: " @@ -10446,6 +10708,7 @@ msgid "" msgstr "" #: bin/newlist:162 +#, fuzzy msgid "Unknown language: %(lang)s" msgstr "Nieznany jêzyk: %(lang)s" @@ -10458,6 +10721,7 @@ msgid "Enter the email of the person running the list: " msgstr "Podaj email opiekuna listy: " #: bin/newlist:193 +#, fuzzy msgid "Initial %(listname)s password: " msgstr "Pocz±tkowe has³o listy %(listname)s: " @@ -10467,11 +10731,12 @@ msgstr "Has #: bin/newlist:220 msgid "" -" - owner addresses need to be fully-qualified names like \"owner@example.com" -"\", not just \"owner\"." +" - owner addresses need to be fully-qualified names like \"owner@example." +"com\", not just \"owner\"." msgstr "" #: bin/newlist:244 +#, fuzzy msgid "Hit enter to notify %(listname)s owner..." msgstr "Naci¶nij Enter, by powiadomiæ opiekuna listy %(listname)s..." @@ -10632,18 +10897,22 @@ msgid "" msgstr "" #: bin/remove_members:156 +#, fuzzy msgid "Could not open file for reading: %(filename)s." msgstr "Pliku nie mo¿na otworzyæ do odczytu: %(filename)s." #: bin/remove_members:163 +#, fuzzy msgid "Error opening list %(listname)s... skipping." msgstr "B³±d przy otwieraniu listy %(listname)s... pominiêto." #: bin/remove_members:173 +#, fuzzy msgid "No such member: %(addr)s" msgstr "Nie znaleziono prenumeratora: %(addr)s." #: bin/remove_members:178 +#, fuzzy msgid "User `%(addr)s' removed from list: %(listname)s." msgstr "U¿ytkownik '%(addr)s' zosta³ usuniêty z listy: %(listname)s." @@ -10668,10 +10937,12 @@ msgid "" msgstr "" #: bin/reset_pw.py:77 +#, fuzzy msgid "Changing passwords for list: %(listname)s" msgstr "Zmiana hase³ dla listy: %(listname)s" #: bin/reset_pw.py:83 +#, fuzzy msgid "New password for member %(member)40s: %(randompw)s" msgstr "Nowe has³o dla %(member)40s: %(randompw)s" @@ -10698,18 +10969,22 @@ msgid "" msgstr "" #: bin/rmlist:73 bin/rmlist:76 +#, fuzzy msgid "Removing %(msg)s" msgstr "Usuwanie %(msg)s" #: bin/rmlist:81 +#, fuzzy msgid "%(listname)s %(msg)s not found as %(filename)s" msgstr "%(listname)s %(msg)s nie znalezione jako %(filename)s" #: bin/rmlist:105 +#, fuzzy msgid "No such list (or list already deleted): %(listname)s" msgstr "Nie ma takiej listy (lub lista zosta³a ju¿ skasowana): %(listname)s" #: bin/rmlist:108 +#, fuzzy msgid "No such list: %(listname)s. Removing its residual archives." msgstr "Nie ma takiej listy: %(listname)s. Usuwanie pozosta³ych archiwów." @@ -10824,6 +11099,7 @@ msgid "" msgstr "" #: bin/sync_members:115 +#, fuzzy msgid "Bad choice: %(yesno)s" msgstr "Z³y wybór: %(yesno)s" @@ -10840,6 +11116,7 @@ msgid "No argument to -f given" msgstr "" #: bin/sync_members:172 +#, fuzzy msgid "Illegal option: %(opt)s" msgstr "B³êdna opcja: %(opt)s" @@ -10852,6 +11129,7 @@ msgid "Must have a listname and a filename" msgstr "Musisz podaæ nazwê listy oraz nazwê pliku" #: bin/sync_members:191 +#, fuzzy msgid "Cannot read address file: %(filename)s: %(msg)s" msgstr "Nie mogê odczytaæ pliku z adresami: %(filename)s: %(msg)s" @@ -10868,10 +11146,12 @@ msgid "You must fix the preceding invalid addresses first." msgstr "Musisz najpierw poprawiæ b³êdne adresy" #: bin/sync_members:264 +#, fuzzy msgid "Added : %(s)s" msgstr "Dodano: %(s)s" #: bin/sync_members:289 +#, fuzzy msgid "Removed: %(s)s" msgstr "Usuniêto: %(s)s" @@ -10976,10 +11256,12 @@ msgid "" msgstr "" #: bin/update:107 +#, fuzzy msgid "Fixing language templates: %(listname)s" msgstr "Poprawiam szablony jêzykowe: %(listname)s" #: bin/update:196 bin/update:711 +#, fuzzy msgid "WARNING: could not acquire lock for list: %(listname)s" msgstr "Uwaga: nie mo¿na zamkn±æ przed zapisem pliku listy %(listname)s" @@ -11065,18 +11347,22 @@ msgid "- %(o_tmpl)s doesn't exist, leaving untouched" msgstr "" #: bin/update:396 +#, fuzzy msgid "removing directory %(src)s and everything underneath" msgstr "usuwanie katalogu %(src)s i wszystkich subkatalogów" #: bin/update:399 +#, fuzzy msgid "removing %(src)s" msgstr "usuwanie %(src)s" #: bin/update:403 +#, fuzzy msgid "Warning: couldn't remove %(src)s -- %(rest)s" msgstr "Uwaga: nie mog³em usun±æ %(src)s -- %(rest)s" #: bin/update:408 +#, fuzzy msgid "couldn't remove old file %(pyc)s -- %(rest)s" msgstr "nie mog³em usun±æ starego pliku %(pyc)s -- %(rest)s" @@ -11085,14 +11371,17 @@ msgid "updating old qfiles" msgstr "aktualizacja starych plików kolejki" #: bin/update:455 +#, fuzzy msgid "Warning! Not a directory: %(dirpath)s" msgstr "Uwaga: wybrany obiekt to nie katalog: %(dirpath)s" #: bin/update:530 +#, fuzzy msgid "message is unparsable: %(filebase)s" msgstr "nie mo¿na przetworzyæ wiadomo¶ci: %(filebase)s" #: bin/update:544 +#, fuzzy msgid "Warning! Deleting empty .pck file: %(pckfile)s" msgstr "Uwaga! Usuwam pusty plik .pck: %(pckfile)s" @@ -11109,6 +11398,7 @@ msgid "Ignoring bad pended data: %(key)s: %(val)s" msgstr "" #: bin/update:614 +#, fuzzy msgid "WARNING: Ignoring duplicate pending ID: %(id)s." msgstr "" "Ostrze¿enie: Ignorowanie oczekuj±cych wiadomo¶ci o tym samym ID: %(id)s." @@ -11135,6 +11425,7 @@ msgid "done" msgstr "gotowe" #: bin/update:691 +#, fuzzy msgid "Updating mailing list: %(listname)s" msgstr "Aktualizowanie listy %(listname)s" @@ -11178,13 +11469,15 @@ msgid "No updates are necessary." msgstr "Nie s± wymagane ¿adne aktualizacje" #: bin/update:796 +#, fuzzy msgid "" "Downgrade detected, from version %(hexlversion)s to version %(hextversion)s\n" "This is probably not safe.\n" "Exiting." -msgstr "" +msgstr "Aktualizacja z wersji %(hexlversion)s do %(hextversion)s" #: bin/update:801 +#, fuzzy msgid "Upgrading from version %(hexlversion)s to %(hextversion)s" msgstr "Aktualizacja z wersji %(hexlversion)s do %(hextversion)s" @@ -11341,6 +11634,7 @@ msgid "" msgstr "" #: bin/withlist:175 +#, fuzzy msgid "Unlocking (but not saving) list: %(listname)s" msgstr "Odblokowanie listy: %(listname)s" @@ -11349,6 +11643,7 @@ msgid "Finalizing" msgstr "Prawie gotowe" #: bin/withlist:188 +#, fuzzy msgid "Loading list %(listname)s" msgstr "Wczytywanie listy %(listname)s" @@ -11361,6 +11656,7 @@ msgid "(unlocked)" msgstr "(odblokowana)" #: bin/withlist:197 +#, fuzzy msgid "Unknown list: %(listname)s" msgstr "Nieznana lista: %(listname)s" @@ -11373,6 +11669,7 @@ msgid "--all requires --run" msgstr "opcja --all wymaga --run" #: bin/withlist:266 +#, fuzzy msgid "Importing %(module)s..." msgstr "Importowania %(module)s..." @@ -11401,6 +11698,7 @@ msgid "" msgstr "" #: cron/checkdbs:20 +#, fuzzy msgid "" "Check for pending admin requests and mail the list owners if necessary.\n" "\n" @@ -11430,10 +11728,12 @@ msgstr "" "\n" #: cron/checkdbs:121 +#, fuzzy msgid "%(count)d %(realname)s moderator request(s) waiting" msgstr "Liczba zadañ dla moderatora %(realname)s: %(count)d" #: cron/checkdbs:124 +#, fuzzy msgid "%(realname)s moderator request check result" msgstr "Zadania oczekuj±ce na moderatora %(realname)s" @@ -11454,6 +11754,7 @@ msgstr "" "Wiadomo¶ci oczekuj±ce:" #: cron/checkdbs:169 +#, fuzzy msgid "" "From: %(sender)s on %(date)s\n" "Subject: %(subject)s\n" @@ -11584,6 +11885,7 @@ msgid "Password // URL" msgstr "Has³o // Adres URL" #: cron/mailpasswds:222 +#, fuzzy msgid "%(host)s mailing list memberships reminder" msgstr "Przypomnienie o listach na %(host)s" diff --git a/messages/pt/LC_MESSAGES/mailman.po b/messages/pt/LC_MESSAGES/mailman.po index fa1aa6a7..b7f65449 100755 --- a/messages/pt/LC_MESSAGES/mailman.po +++ b/messages/pt/LC_MESSAGES/mailman.po @@ -68,10 +68,12 @@ msgid "

                    Currently, there are no archives.

                    " msgstr "

                    De momento, não existem arquivos.

                    " #: Mailman/Archiver/HyperArch.py:821 +#, fuzzy msgid "Gzip'd Text%(sz)s" msgstr "Text%(sz)s gzipado" #: Mailman/Archiver/HyperArch.py:826 +#, fuzzy msgid "Text%(sz)s" msgstr "Text%(sz)s" @@ -144,18 +146,22 @@ msgid "Third" msgstr "Terceiro" #: Mailman/Archiver/HyperArch.py:938 +#, fuzzy msgid "%(ord)s quarter %(year)i" msgstr "%(ord)s trimestre de %(year)i" #: Mailman/Archiver/HyperArch.py:945 +#, fuzzy msgid "%(month)s %(year)i" msgstr "%(month)s de %(year)i" #: Mailman/Archiver/HyperArch.py:950 +#, fuzzy msgid "The Week Of Monday %(day)i %(month)s %(year)i" msgstr "A semana que começou na Segunda, %(day)i de %(month)s de %(year)i" #: Mailman/Archiver/HyperArch.py:954 +#, fuzzy msgid "%(day)i %(month)s %(year)i" msgstr "%(day)i de %(month)s de %(year)i" @@ -164,10 +170,12 @@ msgid "Computing threaded index\n" msgstr "Calculando o índice do tópico\n" #: Mailman/Archiver/HyperArch.py:1319 +#, fuzzy msgid "Updating HTML for article %(seq)s" msgstr "Actualizando o HTML do artigo %(seq)s" #: Mailman/Archiver/HyperArch.py:1326 +#, fuzzy msgid "article file %(filename)s is missing!" msgstr "falta o ficheiro do artigo %(filename)s!" @@ -184,6 +192,7 @@ msgid "Pickling archive state into " msgstr "Conservando estado dos arquivos em " #: Mailman/Archiver/pipermail.py:453 +#, fuzzy msgid "Updating index files for archive [%(archive)s]" msgstr "Actualizando índice de arquivos para [%(archive)s]" @@ -192,6 +201,7 @@ msgid " Thread" msgstr " Tópico" #: Mailman/Archiver/pipermail.py:597 +#, fuzzy msgid "#%(counter)05d %(msgid)s" msgstr "#%(counter)05d %(msgid)s" @@ -229,6 +239,7 @@ msgid "disabled address" msgstr "desactivado" #: Mailman/Bouncer.py:304 +#, fuzzy msgid " The last bounce received from you was dated %(date)s" msgstr " A última devolução recebido de si tinha data de %(date)s" @@ -256,6 +267,7 @@ msgstr "Administrador" #: Mailman/Cgi/options.py:101 Mailman/Cgi/private.py:108 #: Mailman/Cgi/rmlist.py:75 Mailman/Cgi/roster.py:59 #: Mailman/Cgi/subscribe.py:67 +#, fuzzy msgid "No such list %(safelistname)s" msgstr "Não existe essa lista %(safelistname)s" @@ -327,6 +339,7 @@ msgstr "" " que este problema seja solucionado.%(rm)r" #: Mailman/Cgi/admin.py:268 +#, fuzzy msgid "%(hostname)s mailing lists - Admin Links" msgstr "listas de discussão em %(hostname)s - Links Administrativos" @@ -339,6 +352,7 @@ msgid "Mailman" msgstr "Mailman" #: Mailman/Cgi/admin.py:310 +#, fuzzy msgid "" "

                    There currently are no publicly-advertised %(mailmanlink)s\n" " mailing lists on %(hostname)s." @@ -347,6 +361,7 @@ msgstr "" " gerida pelo %(mailmanlink)s em %(hostname)s" #: Mailman/Cgi/admin.py:316 +#, fuzzy msgid "" "

                    Below is the collection of publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s. Click on a list\n" @@ -361,6 +376,7 @@ msgid "right " msgstr "direita " #: Mailman/Cgi/admin.py:325 +#, fuzzy msgid "" "To visit the administrators configuration page for an\n" " unadvertised list, open a URL similar to this one, but with a '/' " @@ -405,6 +421,7 @@ msgid "No valid variable name found." msgstr "Não foi encontrado um nome de variável válido." #: Mailman/Cgi/admin.py:395 +#, fuzzy msgid "" "%(realname)s Mailing list Configuration Help\n" "
                    %(varname)s Option" @@ -413,6 +430,7 @@ msgstr "" " Opção
                    %(varname)s" #: Mailman/Cgi/admin.py:402 +#, fuzzy msgid "Mailman %(varname)s List Option Help" msgstr "Ajuda Mailman sobre a opção %(varname)s da lista" @@ -432,14 +450,17 @@ msgstr "" " " #: Mailman/Cgi/admin.py:431 +#, fuzzy msgid "return to the %(categoryname)s options page." msgstr "Voltar para a página de opções %(categoryname)s." #: Mailman/Cgi/admin.py:446 +#, fuzzy msgid "%(realname)s Administration (%(label)s)" msgstr "Administração de %(realname)s (%(label)s)" #: Mailman/Cgi/admin.py:447 +#, fuzzy msgid "%(realname)s mailing list administration
                    %(label)s Section" msgstr "Administração da lista %(realname)s
                    Secção %(label)s" @@ -522,6 +543,7 @@ msgid "Value" msgstr "Valor" #: Mailman/Cgi/admin.py:668 +#, fuzzy msgid "" "Badly formed options entry:\n" " %(record)s" @@ -622,10 +644,12 @@ msgid "Move rule down" msgstr "" #: Mailman/Cgi/admin.py:870 +#, fuzzy msgid "
                    (Edit %(varname)s)" msgstr "
                    (Editar %(varname)s)" #: Mailman/Cgi/admin.py:872 +#, fuzzy msgid "
                    (Details for %(varname)s)" msgstr "
                    (Detalhes de %(varname)s)" @@ -666,6 +690,7 @@ msgid "(help)" msgstr "(ajuda)" #: Mailman/Cgi/admin.py:930 +#, fuzzy msgid "Find member %(link)s:" msgstr "Procurar membro %(link)s:" @@ -678,10 +703,12 @@ msgid "Bad regular expression: " msgstr "Expressão regular incorrecta:" #: Mailman/Cgi/admin.py:1013 +#, fuzzy msgid "%(allcnt)s members total, %(membercnt)s shown" msgstr "%(allcnt)s membros no total, %(membercnt)s mostrados" #: Mailman/Cgi/admin.py:1016 +#, fuzzy msgid "%(allcnt)s members total" msgstr "%(allcnt)s membros no total" @@ -868,6 +895,7 @@ msgstr "" "

                    Para ver mais membros, clique abaixo no intervalo apropriado:" #: Mailman/Cgi/admin.py:1221 +#, fuzzy msgid "from %(start)s to %(end)s" msgstr "de %(start)s até %(end)s" @@ -1005,6 +1033,7 @@ msgid "Change list ownership passwords" msgstr "Modificar passwords de dono da lista" #: Mailman/Cgi/admin.py:1364 +#, fuzzy msgid "" "The list administrators are the people who have ultimate control " "over\n" @@ -1177,8 +1206,9 @@ msgid "%(schange_to)s is already a member" msgstr " já é um membro" #: Mailman/Cgi/admin.py:1620 +#, fuzzy msgid "%(schange_to)s matches banned pattern %(spat)s" -msgstr "" +msgstr " já é um membro" #: Mailman/Cgi/admin.py:1622 msgid "Address %(schange_from)s changed to %(schange_to)s" @@ -1218,6 +1248,7 @@ msgid "Not subscribed" msgstr "Não inscrito" #: Mailman/Cgi/admin.py:1773 +#, fuzzy msgid "Ignoring changes to deleted member: %(user)s" msgstr "Ignorando modificações num membro removido: %(user)s" @@ -1230,10 +1261,12 @@ msgid "Error Unsubscribing:" msgstr "Erro ao cancelar a inscrição:" #: Mailman/Cgi/admindb.py:235 Mailman/Cgi/admindb.py:248 +#, fuzzy msgid "%(realname)s Administrative Database" msgstr "Base de dados administrativa %(realname)s" #: Mailman/Cgi/admindb.py:238 +#, fuzzy msgid "%(realname)s Administrative Database Results" msgstr "Resultados da base de dados administrativa %(realname)s" @@ -1262,6 +1295,7 @@ msgid "Discard all messages marked Defer" msgstr "" #: Mailman/Cgi/admindb.py:298 +#, fuzzy msgid "all of %(esender)s's held messages." msgstr "todas as mensagens em espera de %(esender)s" @@ -1282,6 +1316,7 @@ msgid "list of available mailing lists." msgstr "lista de todas as listas de discussão disponíveis" #: Mailman/Cgi/admindb.py:362 +#, fuzzy msgid "You must specify a list name. Here is the %(link)s" msgstr "Tem de especificar um nome de lista. Aqui é a %(link)s" @@ -1385,6 +1420,7 @@ msgid "Rejects" msgstr "Rejeitadas" #: Mailman/Cgi/admindb.py:584 +#, fuzzy msgid "" "Ban %(esender)s from ever subscribing to this\n" " mailing list" @@ -1401,6 +1437,7 @@ msgstr "" " ou pode" #: Mailman/Cgi/admindb.py:591 +#, fuzzy msgid "view all messages from %(esender)s" msgstr "ver todas as mensagens de %(esender)s" @@ -1530,6 +1567,7 @@ msgstr "" " cancelado." #: Mailman/Cgi/confirm.py:178 +#, fuzzy msgid "System error, bad content: %(content)s" msgstr "Erro de sistema, conteúdo incorrecto: %(content)s" @@ -1601,6 +1639,7 @@ msgstr "" " pedido de inscrição." #: Mailman/Cgi/confirm.py:275 +#, fuzzy msgid "" "Your confirmation is required in order to continue with\n" " the subscription request to the mailing list %(listname)s.\n" @@ -1655,6 +1694,7 @@ msgid "Preferred language:" msgstr "Idioma preferido:" #: Mailman/Cgi/confirm.py:317 +#, fuzzy msgid "Subscribe to list %(listname)s" msgstr "Inscrever-se na lista %(listname)s" @@ -1671,6 +1711,7 @@ msgid "Awaiting moderator approval" msgstr "Aguardando aprovação do moderador" #: Mailman/Cgi/confirm.py:386 +#, fuzzy msgid "" " You have successfully confirmed your subscription request to " "the\n" @@ -1725,6 +1766,7 @@ msgid "Subscription request confirmed" msgstr "Pedido de inscrição confirmado" #: Mailman/Cgi/confirm.py:418 +#, fuzzy msgid "" " You have successfully confirmed your subscription request for\n" " \"%(addr)s\" to the %(listname)s mailing list. A separate\n" @@ -1753,6 +1795,7 @@ msgid "Unsubscription request confirmed" msgstr "Pedido de anulação da inscrição confirmado" #: Mailman/Cgi/confirm.py:469 +#, fuzzy msgid "" " You have successfully unsubscribed from the %(listname)s " "mailing\n" @@ -1773,6 +1816,7 @@ msgid "Not available" msgstr "Não disponível" #: Mailman/Cgi/confirm.py:498 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " unsubscription request from the mailing list %(listname)s. " @@ -1844,6 +1888,7 @@ msgid "Change of address request confirmed" msgstr "Confirmado o pedido de mudança de endereço" #: Mailman/Cgi/confirm.py:571 +#, fuzzy msgid "" " You have successfully changed your address on the %(listname)s\n" " mailing list from %(oldaddr)s to %(newaddr)s. " @@ -1865,6 +1910,7 @@ msgid "globally" msgstr "globalmente" #: Mailman/Cgi/confirm.py:605 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " change of address request for the mailing list %(listname)s. " @@ -1927,6 +1973,7 @@ msgid "Sender discarded message via web." msgstr "O remetente anulou a mensagem via web." #: Mailman/Cgi/confirm.py:674 +#, fuzzy msgid "" "The held message with the Subject:\n" " header %(subject)s could not be found. The most " @@ -1946,6 +1993,7 @@ msgid "Posted message canceled" msgstr "A mensagem submetida foi cancelada" #: Mailman/Cgi/confirm.py:685 +#, fuzzy msgid "" " You have successfully canceled the posting of your message with\n" " the Subject: header %(subject)s to the mailing list\n" @@ -1968,6 +2016,7 @@ msgstr "" " da lista." #: Mailman/Cgi/confirm.py:735 +#, fuzzy msgid "" "Your confirmation is required in order to cancel the\n" " posting of your message to the mailing list %(listname)s:\n" @@ -2016,6 +2065,7 @@ msgid "Membership re-enabled." msgstr "Participação reactivada." #: Mailman/Cgi/confirm.py:798 +#, fuzzy msgid "" " You have successfully re-enabled your membership in the\n" " %(listname)s mailing list. You can now página\n" +" Para se re-inscrever, por favor visite a página\n" " de informações sobre listas." #: Mailman/Cgi/confirm.py:842 @@ -2047,6 +2098,7 @@ msgid "not available" msgstr "não disponível" #: Mailman/Cgi/confirm.py:846 +#, fuzzy msgid "" "Your membership in the %(realname)s mailing list is\n" " currently disabled due to excessive bounces. Your confirmation is\n" @@ -2158,14 +2210,17 @@ msgid "Unknown virtual host: %(safehostname)s" msgstr "Lista desconhecida: %(listname)s" #: Mailman/Cgi/create.py:218 bin/newlist:219 +#, fuzzy msgid "Bad owner email address: %(s)s" msgstr "Endereço de e-mail do dono incorrecto: %(s)s" #: Mailman/Cgi/create.py:223 bin/newlist:182 bin/newlist:223 +#, fuzzy msgid "List already exists: %(listname)s" msgstr "A lista já existe: %(listname)s" #: Mailman/Cgi/create.py:231 bin/newlist:217 +#, fuzzy msgid "Illegal list name: %(s)s" msgstr "Nome de lista ilegal: %(s)s" @@ -2178,6 +2233,7 @@ msgstr "" " Por favor contacte o administrador do site para assistência." #: Mailman/Cgi/create.py:273 bin/newlist:265 +#, fuzzy msgid "Your new mailing list: %(listname)s" msgstr "A sua nova lista de discussão: %(listname)s" @@ -2186,6 +2242,7 @@ msgid "Mailing list creation results" msgstr "Resultados da criação da lista de discussão" #: Mailman/Cgi/create.py:288 +#, fuzzy msgid "" "You have successfully created the mailing list\n" " %(listname)s and notification has been sent to the list owner\n" @@ -2208,6 +2265,7 @@ msgid "Create another list" msgstr "Criar outra lista" #: Mailman/Cgi/create.py:312 +#, fuzzy msgid "Create a %(hostname)s Mailing List" msgstr "Criar uma lista de discussão em %(hostname)s" @@ -2305,6 +2363,7 @@ msgstr "" " as mensagens dos novos membros." #: Mailman/Cgi/create.py:432 +#, fuzzy msgid "" "Initial list of supported languages.

                    Note that if you do not\n" " select at least one initial language, the list will use the server\n" @@ -2410,6 +2469,7 @@ msgid "List name is required." msgstr "É necessário o nome da lista." #: Mailman/Cgi/edithtml.py:154 +#, fuzzy msgid "%(realname)s -- Edit html for %(template_info)s" msgstr "<%(realname)s -- Editar html para %(template_info)s" @@ -2418,10 +2478,12 @@ msgid "Edit HTML : Error" msgstr "Editar HTML : Erro" #: Mailman/Cgi/edithtml.py:161 +#, fuzzy msgid "%(safetemplatename)s: Invalid template" msgstr "%(safetemplatename)s: Template inválida" #: Mailman/Cgi/edithtml.py:166 Mailman/Cgi/edithtml.py:167 +#, fuzzy msgid "%(realname)s -- HTML Page Editing" msgstr "%(realname)s -- Edição de Página HTML" @@ -2480,10 +2542,12 @@ msgid "HTML successfully updated." msgstr "HTML actualizado com sucesso." #: Mailman/Cgi/listinfo.py:89 +#, fuzzy msgid "%(hostname)s Mailing Lists" msgstr "Listas de discussão em %(hostname)s" #: Mailman/Cgi/listinfo.py:127 +#, fuzzy msgid "" "

                    There currently are no publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s." @@ -2492,6 +2556,7 @@ msgstr "" " %(mailmanlink)s em %(hostname)s" #: Mailman/Cgi/listinfo.py:131 +#, fuzzy msgid "" "

                    Below is a listing of all the public mailing lists on\n" " %(hostname)s. Click on a list name to get more information " @@ -2510,6 +2575,7 @@ msgid "right" msgstr "direita" #: Mailman/Cgi/listinfo.py:140 +#, fuzzy msgid "" " To visit the general information page for an unadvertised list,\n" " open a URL similar to this one, but with a '/' and the %(adj)s\n" @@ -2572,6 +2638,7 @@ msgstr "Endere #: Mailman/Cgi/options.py:183 Mailman/Cgi/options.py:240 #: Mailman/Cgi/options.py:264 +#, fuzzy msgid "No such member: %(safeuser)s." msgstr "Membro inexistente: %(safeuser)s" @@ -2664,6 +2731,7 @@ msgstr "" "discussão contendo o endereço %(safeuser)s será alterada." #: Mailman/Cgi/options.py:468 +#, fuzzy msgid "The new address is already a member: %(newaddr)s" msgstr "O novo endereço já é membro: %(newaddr)s" @@ -2672,6 +2740,7 @@ msgid "Addresses may not be blank" msgstr "Os endereços não podem estar em branco" #: Mailman/Cgi/options.py:488 +#, fuzzy msgid "A confirmation message has been sent to %(newaddr)s. " msgstr "Uma mensagem de confirmação foi enviada para %(newaddr)s" @@ -2684,6 +2753,7 @@ msgid "Illegal email address provided" msgstr "Foi fornecido um endereço de email ilegal" #: Mailman/Cgi/options.py:501 +#, fuzzy msgid "%(newaddr)s is already a member of the list." msgstr "%(newaddr)s já é membro desta lista." @@ -2772,6 +2842,7 @@ msgstr "" " tomem uma decisão." #: Mailman/Cgi/options.py:623 +#, fuzzy msgid "" "You have been successfully unsubscribed from the\n" " mailing list %(fqdn_listname)s. If you were receiving digest\n" @@ -2865,6 +2936,7 @@ msgid "day" msgstr "dia" #: Mailman/Cgi/options.py:900 +#, fuzzy msgid "%(days)d %(units)s" msgstr "%(days)d %(units)s" @@ -2877,6 +2949,7 @@ msgid "No topics defined" msgstr "Nenhum tópico definido" #: Mailman/Cgi/options.py:940 +#, fuzzy msgid "" "\n" "You are subscribed to this list with the case-preserved address\n" @@ -2887,6 +2960,7 @@ msgstr "" "%(cpuser)s" #: Mailman/Cgi/options.py:956 +#, fuzzy msgid "%(realname)s list: member options login page" msgstr "lista %(realname)s: página de login das opções de membro" @@ -2900,6 +2974,7 @@ msgid "%(realname)s list: member options for user %(safeuser)s" msgstr "lista %(realname)s: opções de membro para o utilizador %(safeuser)s" #: Mailman/Cgi/options.py:986 +#, fuzzy msgid "" "In order to change your membership option, you must\n" " first log in by giving your %(extra)smembership password in the section\n" @@ -2974,6 +3049,7 @@ msgid "" msgstr "" #: Mailman/Cgi/options.py:1140 +#, fuzzy msgid "Requested topic is not valid: %(topicname)s" msgstr "O tópico pedido não é válido: %(topicname)s" @@ -3002,6 +3078,7 @@ msgid "Private archive - \"./\" and \"../\" not allowed in URL." msgstr "" #: Mailman/Cgi/private.py:109 +#, fuzzy msgid "Private Archive Error - %(msg)s" msgstr "Erro no Arquivo privado - %(msg)s" @@ -3039,6 +3116,7 @@ msgid "Mailing list deletion results" msgstr "Resultados da remoção da lista de discussão" #: Mailman/Cgi/rmlist.py:187 +#, fuzzy msgid "" "You have successfully deleted the mailing list\n" " %(listname)s." @@ -3047,6 +3125,7 @@ msgstr "" " %(listname)s" #: Mailman/Cgi/rmlist.py:191 +#, fuzzy msgid "" "There were some problems deleting the mailing list\n" " %(listname)s. Contact your site administrator at " @@ -3058,6 +3137,7 @@ msgstr "" " para detalhes." #: Mailman/Cgi/rmlist.py:208 +#, fuzzy msgid "Permanently remove mailing list %(realname)s" msgstr "Remover permanentemente a lista de discussão %(realname)s" @@ -3131,6 +3211,7 @@ msgid "Invalid options to CGI script" msgstr "Opções inválidas para o script CGI" #: Mailman/Cgi/roster.py:118 +#, fuzzy msgid "%(realname)s roster authentication failed." msgstr "falha de autenticação do roster de %(realname)s." @@ -3198,6 +3279,7 @@ msgstr "" "de confirmação com mais instruções. " #: Mailman/Cgi/subscribe.py:262 +#, fuzzy msgid "" "The email address you supplied is banned from this\n" " mailing list. If you think this restriction is erroneous, please\n" @@ -3224,6 +3306,7 @@ msgstr "" "forneceu é inseguro." #: Mailman/Cgi/subscribe.py:278 +#, fuzzy msgid "" "Confirmation from your email address is required, to prevent anyone from\n" "subscribing you without permission. Instructions are being sent to you at\n" @@ -3236,6 +3319,7 @@ msgstr "" "confirme sua inscrição." #: Mailman/Cgi/subscribe.py:290 +#, fuzzy msgid "" "Your subscription request was deferred because %(x)s. Your request has " "been\n" @@ -3256,6 +3340,7 @@ msgid "Mailman privacy alert" msgstr "Alerta de privacidade do Mailman" #: Mailman/Cgi/subscribe.py:316 +#, fuzzy msgid "" "An attempt was made to subscribe your address to the mailing list\n" "%(listaddr)s. You are already subscribed to this mailing list.\n" @@ -3298,6 +3383,7 @@ msgid "This list only supports digest delivery." msgstr "Esta lista só suporta entregas em modo digest." #: Mailman/Cgi/subscribe.py:344 +#, fuzzy msgid "You have been successfully subscribed to the %(realname)s mailing list." msgstr "Você foi inscrito com sucesso na lista de discussão %(realname)s." @@ -3428,26 +3514,32 @@ msgid "n/a" msgstr "n/a" #: Mailman/Commands/cmd_info.py:44 +#, fuzzy msgid "List name: %(listname)s" msgstr "Nome da Lista: %(listname)s" #: Mailman/Commands/cmd_info.py:45 +#, fuzzy msgid "Description: %(description)s" msgstr "Descrição: %(description)s" #: Mailman/Commands/cmd_info.py:46 +#, fuzzy msgid "Postings to: %(postaddr)s" msgstr "Mensagens para: %(postaddr)s" #: Mailman/Commands/cmd_info.py:47 +#, fuzzy msgid "List Helpbot: %(requestaddr)s" msgstr "Ajuda da lista: %(requestaddr)s" #: Mailman/Commands/cmd_info.py:48 +#, fuzzy msgid "List Owners: %(owneraddr)s" msgstr "Donos da Lista: %(owneraddr)s" #: Mailman/Commands/cmd_info.py:49 +#, fuzzy msgid "More information: %(listurl)s" msgstr "Mais detalhes: %(listurl)s" @@ -3471,18 +3563,22 @@ msgstr "" " GNU Mailman.\n" #: Mailman/Commands/cmd_lists.py:44 +#, fuzzy msgid "Public mailing lists at %(hostname)s:" msgstr "Listas de discussão públicas em %(hostname)s:" #: Mailman/Commands/cmd_lists.py:66 +#, fuzzy msgid "%(i)3d. List name: %(realname)s" msgstr "%(i)3d. Nome da lista: %(realname)s" #: Mailman/Commands/cmd_lists.py:67 +#, fuzzy msgid " Description: %(description)s" msgstr " Descrição: %(description)s" #: Mailman/Commands/cmd_lists.py:68 +#, fuzzy msgid " Requests to: %(requestaddr)s" msgstr " Pedidos a: %(requestaddr)s" @@ -3517,12 +3613,14 @@ msgstr "" " o endereço inscrito.\n" #: Mailman/Commands/cmd_password.py:51 Mailman/Commands/cmd_password.py:66 +#, fuzzy msgid "Your password is: %(password)s" msgstr "A sua password é: %(password)s" #: Mailman/Commands/cmd_password.py:57 Mailman/Commands/cmd_password.py:72 #: Mailman/Commands/cmd_password.py:95 Mailman/Commands/cmd_password.py:121 #: Mailman/Commands/cmd_set.py:149 Mailman/Commands/cmd_set.py:219 +#, fuzzy msgid "You are not a member of the %(listname)s mailing list" msgstr "Você não é membro da lista de discussão %(listname)s" @@ -3708,6 +3806,7 @@ msgstr "" " da password, para esta lista de discussão.\n" #: Mailman/Commands/cmd_set.py:122 +#, fuzzy msgid "Bad set command: %(subcmd)s" msgstr "Comando set incorrecto: %(subcmd)s" @@ -3728,6 +3827,7 @@ msgid "on" msgstr "activado" #: Mailman/Commands/cmd_set.py:154 +#, fuzzy msgid " ack %(onoff)s" msgstr " reconhecimento %(onoff)s" @@ -3765,22 +3865,27 @@ msgid "due to bounces" msgstr "por mensagens retornadas" #: Mailman/Commands/cmd_set.py:186 +#, fuzzy msgid " %(status)s (%(how)s on %(date)s)" msgstr " %(status)s %(how)s em %(date)s)" #: Mailman/Commands/cmd_set.py:192 +#, fuzzy msgid " myposts %(onoff)s" msgstr " myposts %(onoff)s" #: Mailman/Commands/cmd_set.py:195 +#, fuzzy msgid " hide %(onoff)s" msgstr " hide %(onoff)s" #: Mailman/Commands/cmd_set.py:199 +#, fuzzy msgid " duplicates %(onoff)s" msgstr " duplicates %(onoff)s" #: Mailman/Commands/cmd_set.py:203 +#, fuzzy msgid " reminders %(onoff)s" msgstr " reminders %(onoff)s" @@ -3789,6 +3894,7 @@ msgid "You did not give the correct password" msgstr "Não forneceu a password correcta" #: Mailman/Commands/cmd_set.py:236 Mailman/Commands/cmd_set.py:283 +#, fuzzy msgid "Bad argument: %(arg)s" msgstr "Argumento incorrecto: %(arg)s" @@ -3865,6 +3971,7 @@ msgstr "" " de email e sem aspas!).\n" #: Mailman/Commands/cmd_subscribe.py:62 +#, fuzzy msgid "Bad digest specifier: %(arg)s" msgstr "Especificador de digest incorrecto: %(arg)s" @@ -3873,6 +3980,7 @@ msgid "No valid address found to subscribe" msgstr "Não foi encontrado um endereço válido para se inscrever" #: Mailman/Commands/cmd_subscribe.py:113 +#, fuzzy msgid "" "The email address you supplied is banned from this mailing list.\n" "If you think this restriction is erroneous, please contact the list\n" @@ -3911,6 +4019,7 @@ msgid "This list only supports digest subscriptions!" msgstr "Esta lista só suporta inscrições digest!" #: Mailman/Commands/cmd_subscribe.py:146 +#, fuzzy msgid "" "Your subscription request has been forwarded to the list administrator\n" "at %(listowner)s for review." @@ -3949,6 +4058,7 @@ msgstr "" " e sem aspas).\n" #: Mailman/Commands/cmd_unsubscribe.py:62 +#, fuzzy msgid "%(address)s is not a member of the %(listname)s mailing list" msgstr "%(address)s não é membro da lista de discussão %(listname)s" @@ -4202,6 +4312,7 @@ msgid "Chinese (Taiwan)" msgstr "" #: Mailman/Deliverer.py:53 +#, fuzzy msgid "" "Note: Since this is a list of mailing lists, administrative\n" "notices like the password reminder will be sent to\n" @@ -4216,14 +4327,17 @@ msgid " (Digest mode)" msgstr " (Modo digest)" #: Mailman/Deliverer.py:79 +#, fuzzy msgid "Welcome to the \"%(realname)s\" mailing list%(digmode)s" msgstr "Bem vindo à lista de discussão \"%(realname)s\" %(digmode)s" #: Mailman/Deliverer.py:89 +#, fuzzy msgid "You have been unsubscribed from the %(realname)s mailing list" msgstr "Foi anulado a sua inscrição na lista de discussão %(realname)s" #: Mailman/Deliverer.py:116 +#, fuzzy msgid "%(listfullname)s mailing list reminder" msgstr "Nota da lista de discussão %(listfullname)s" @@ -4463,8 +4577,8 @@ msgid "" " membership.\n" "\n" "

                    You can control both the\n" -" number\n" +" number\n" " of reminders the member will receive and the\n" " No those messages too will get discarded. You may " "want\n" " to set up an\n" -" autoresponse\n" +" autoresponse\n" " message for email to the -owner and -admin address." msgstr "" "Apesar de o detector de devoluções do Mailman ser muito robusto,\n" @@ -4681,8 +4795,8 @@ msgstr "" " podem ainda querer enviar mensagens para lá. Se isso\n" " acontecer e esta variável estiver ajustada para Não\n" " esses emails também serão ignorados. Pode querer\n" -" configurar uma \n" +" configurar uma \n" " mensagem de resposta automática para os endereços\n" " -owner e -admin." @@ -4750,6 +4864,7 @@ msgstr "" " Sempre será feita uma tentativa de avisar o membro." #: Mailman/Gui/Bounce.py:194 +#, fuzzy msgid "" "Bad value for %(property)s: %(val)s" @@ -4902,8 +5017,8 @@ msgid "" "Use this option to remove each message attachment that does\n" " not have a matching content type. Requirements and formats " "are\n" -" exactly like filter_mime_types.\n" "\n" "

                    Note: if you add entries to this list but don't add\n" @@ -5025,6 +5140,7 @@ msgstr "" " administrador do site." #: Mailman/Gui/ContentFilter.py:171 +#, fuzzy msgid "Bad MIME type ignored: %(spectype)s" msgstr "Ignorado tipo MIME incorrecto: %(spectype)s" @@ -5131,6 +5247,7 @@ msgstr "" "O Mailman deve enviar o próximo digest imediatamente, caso não esteja vazio?" #: Mailman/Gui/Digest.py:145 +#, fuzzy msgid "" "The next digest will be sent as volume\n" " %(volume)s, number %(number)s" @@ -5147,14 +5264,17 @@ msgid "There was no digest to send." msgstr "Não existem digests para enviar." #: Mailman/Gui/GUIBase.py:173 +#, fuzzy msgid "Invalid value for variable: %(property)s" msgstr "Valor inválido para a variável: %(property)s" #: Mailman/Gui/GUIBase.py:178 +#, fuzzy msgid "Bad email address for option %(property)s: %(error)s" msgstr "Endereço de email incorrecto para a opção %(property)s: %(error)s" #: Mailman/Gui/GUIBase.py:204 +#, fuzzy msgid "" "The following illegal substitution variables were\n" " found in the %(property)s string:\n" @@ -5169,6 +5289,7 @@ msgstr "" " problema." #: Mailman/Gui/GUIBase.py:218 +#, fuzzy msgid "" "Your %(property)s string appeared to\n" " have some correctable problems in its new value.\n" @@ -5269,8 +5390,8 @@ msgid "" "

                    In order to split the list ownership duties into\n" " administrators and moderators, you must\n" " set a separate moderator password,\n" -" and also provide the email\n" +" and also provide the email\n" " addresses of the list moderators. Note that the field you\n" " are changing here specifies the list administrators." msgstr "" @@ -5294,8 +5415,8 @@ msgstr "" " e moderadores, você deve\n" " definir uma password separada para moderador,\n" -" e também fornecer um endereço\n" +" e também fornecer um endereço\n" " de email para os moderadores da lista. Note que o campo\n" " que está modificando aqui especifica os administradores da lista." @@ -5595,13 +5716,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5661,8 +5782,8 @@ msgstr "Cabe msgid "" "This is the address set in the Reply-To: header\n" " when the reply_goes_to_list\n" +" href=\"?VARHELP=general/" +"reply_goes_to_list\">reply_goes_to_list\n" " option is set to Explicit address.\n" "\n" "

                    There are many reasons not to introduce or override the\n" @@ -5670,13 +5791,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5698,8 +5819,9 @@ msgid "" " Reply-To: header, it will not be changed." msgstr "" "Este é o endereço ajustado no cabeçalho Reply-To:\n" -" quando a opção reply_goes_to_list está ajustada para Endereço explícito.\n" +" quando a opção reply_goes_to_list está ajustada para Endereço " +"explícito.\n" "

                    Existem muitas razões para não introduzir ou substituir o \n" " cabeçalho Reply-To:. Uma é que algumas pessoas que postam\n" " na lista depende de sua própria configuração Reply-To: " @@ -5753,8 +5875,8 @@ msgstr "" " uma cascata para outras listas de discussão. Quando\n" " configurado, meta-notícias como notas de confirmação e\n" " passwords serão encaminhadas para um endereço derivado do\n" -" endereço do membro - o qual terá o valor de \"umbrella_member_suffix" -"\"\n" +" endereço do membro - o qual terá o valor de " +"\"umbrella_member_suffix\"\n" " acrescentado ao nome de conta do membro." #: Mailman/Gui/General.py:319 @@ -5778,8 +5900,8 @@ msgid "" " member list addresses, but rather to the owner of those member\n" " lists. In that case, the value of this setting is appended to\n" " the member's account name for such notices. `-owner' is the\n" -" typical choice. This setting has no effect when \"umbrella_list" -"\"\n" +" typical choice. This setting has no effect when " +"\"umbrella_list\"\n" " is \"No\"." msgstr "" "Quando \"umbrella_list\" é ajustado para indicar que esta lista tem outras\n" @@ -6724,6 +6846,7 @@ msgstr "" " (ou maliciosas) de criar inscrições sem seu consentimento." #: Mailman/Gui/Privacy.py:104 +#, fuzzy msgid "" "This section allows you to configure subscription and\n" " membership exposure policy. You can also control whether this\n" @@ -6923,8 +7046,8 @@ msgid "" " either individually or as a group. Any\n" " posting from a non-member who is not explicitly accepted,\n" " rejected, or discarded, will have their posting filtered by the\n" -" general\n" +" general\n" " non-member rules.\n" "\n" "

                    In the text boxes below, add one address per line; start the\n" @@ -6956,15 +7079,15 @@ msgstr "" " colocadas " "em\n" " espera para moderação,\n" -" rejeitada (elas serão retornadas), ou\n" +" rejeitada (elas serão retornadas), ou\n" " descartadas,\n" " ou individualmente ou em grupo. Qualquer postagem de um não\n" " membro que não é explicitamente aceita, rejeitada ou \n" " descartada, terá sua postagem filtrada pelas\n" -" regras\n" +" regras\n" "\" gerais de não membros.\n" "\n" "

                    Nas caixas de texto abaixo, adicione um endereço por linha;\n" @@ -7042,8 +7165,8 @@ msgid "" "If a member posts this many times, within a period of time\n" " the member is automatically moderated. Use 0 to disable. " "See\n" -" member_verbosity_interval for details on the time " "period.\n" "\n" @@ -7787,8 +7910,8 @@ msgid "" "

                    The body of the message can also be optionally scanned for\n" " Subject: and Keywords: headers, as\n" " specified by the topics_bodylines_limit\n" +" href=\"?VARHELP=topics/" +"topics_bodylines_limit\">topics_bodylines_limit\n" " configuration variable." msgstr "" "O filtro de tópico categoriza cada mensagem de entrada de \n" @@ -7811,8 +7934,8 @@ msgstr "" "

                    O corpo da mensagem também poderá ser scaneada em busca dos\n" " cabeçalhos Subject: e Keyworks:.\n" " Variável de configuração\n" -" topics_bodylines_limit." +" topics_bodylines_limit." #: Mailman/Gui/Topics.py:72 msgid "How many body lines should the topic matcher scan?" @@ -8109,6 +8232,7 @@ msgid "%(listinfo_link)s list run by %(owner_link)s" msgstr "A lista %(listinfo_link)s é administrada por %(owner_link)s" #: Mailman/HTMLFormatter.py:57 +#, fuzzy msgid "%(realname)s administrative interface" msgstr "Interface administrativa de %(realname)s" @@ -8117,6 +8241,7 @@ msgid " (requires authorization)" msgstr " (requer autorização)" #: Mailman/HTMLFormatter.py:61 +#, fuzzy msgid "Overview of all %(hostname)s mailing lists" msgstr "Vista geral de todas as listas de discussão em %(hostname)s" @@ -8137,6 +8262,7 @@ msgid "; it was disabled by the list administrator" msgstr "; foi desactivado pelo administrador da lista" #: Mailman/HTMLFormatter.py:146 +#, fuzzy msgid "" "; it was disabled due to excessive bounces. The\n" " last bounce was received on %(date)s" @@ -8149,6 +8275,7 @@ msgid "; it was disabled for unknown reasons" msgstr "; foi desactivado por razões desconhecidas" #: Mailman/HTMLFormatter.py:151 +#, fuzzy msgid "Note: your list delivery is currently disabled%(reason)s." msgstr "" "Nota: a entrega de mensagens a si está de momento desactivada%(reason)s." @@ -8162,6 +8289,7 @@ msgid "the list administrator" msgstr "o administrador da lista" #: Mailman/HTMLFormatter.py:157 +#, fuzzy msgid "" "

                    %(note)s\n" "\n" @@ -8182,6 +8310,7 @@ msgstr "" " de assistência." #: Mailman/HTMLFormatter.py:169 +#, fuzzy msgid "" "

                    We have received some recent bounces from your\n" " address. Your current bounce score is %(score)s out of " @@ -8201,6 +8330,7 @@ msgstr "" " breve." #: Mailman/HTMLFormatter.py:181 +#, fuzzy msgid "" "(Note - you are subscribing to a list of mailing lists, so the %(type)s " "notice will be sent to the admin address for your membership, %(addr)s.)

                    " @@ -8247,6 +8377,7 @@ msgstr "" " Será notificado da decisão por email." #: Mailman/HTMLFormatter.py:208 +#, fuzzy msgid "" "This is %(also)sa private list, which means that the\n" " list of members is not available to non-members." @@ -8255,6 +8386,7 @@ msgstr "" " não está disponível para não-membros." #: Mailman/HTMLFormatter.py:211 +#, fuzzy msgid "" "This is %(also)sa hidden list, which means that the\n" " list of members is available only to the list administrator." @@ -8263,6 +8395,7 @@ msgstr "" " só está disponível para o administrador da lista." #: Mailman/HTMLFormatter.py:214 +#, fuzzy msgid "" "This is %(also)sa public list, which means that the\n" " list of members list is available to everyone." @@ -8279,6 +8412,7 @@ msgstr "" " reconhecidos por spammers)." #: Mailman/HTMLFormatter.py:222 +#, fuzzy msgid "" "

                    (Note that this is an umbrella list, intended to\n" " have only other mailing lists as members. Among other things,\n" @@ -8295,6 +8429,7 @@ msgid "either " msgstr "ou" #: Mailman/HTMLFormatter.py:256 +#, fuzzy msgid "" "To unsubscribe from %(realname)s, get a password reminder,\n" " or change your subscription options %(either)senter your " @@ -8329,12 +8464,14 @@ msgstr "" " email" #: Mailman/HTMLFormatter.py:277 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " members.)" msgstr "(%(which)s só está disponível para os membros da lista.)" #: Mailman/HTMLFormatter.py:281 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " administrator.)" @@ -8395,6 +8532,7 @@ msgid "The current archive" msgstr "O arquivo actual" #: Mailman/Handlers/Acknowledge.py:59 +#, fuzzy msgid "%(realname)s post acknowledgement" msgstr "Aviso de recepção de mensagem de %(realname)s" @@ -8407,6 +8545,7 @@ msgid "" msgstr "" #: Mailman/Handlers/CalcRecips.py:79 +#, fuzzy msgid "" "Your urgent message to the %(realname)s mailing list was not authorized for\n" "delivery. The original message as received by Mailman is attached.\n" @@ -8485,6 +8624,7 @@ msgid "Message may contain administrivia" msgstr "A mensagem pode conter questões administratriviais." #: Mailman/Handlers/Hold.py:84 +#, fuzzy msgid "" "Please do *not* post administrative requests to the mailing\n" "list. If you wish to subscribe, visit %(listurl)s or send a message with " @@ -8526,10 +8666,12 @@ msgid "Posting to a moderated newsgroup" msgstr "Mensagem para um newsgroup moderado" #: Mailman/Handlers/Hold.py:252 +#, fuzzy msgid "Your message to %(listname)s awaits moderator approval" msgstr "A sua mensagem para a lista %(listname)s aguarda aprovação" #: Mailman/Handlers/Hold.py:271 +#, fuzzy msgid "%(listname)s post from %(sender)s requires approval" msgstr "A mensagem de %(sender)s para a lista %(listname)s requer aprovação" @@ -8574,6 +8716,7 @@ msgid "After content filtering, the message was empty" msgstr "Após a filtragem de conteúdo, a mensagem ficou vazia" #: Mailman/Handlers/MimeDel.py:269 +#, fuzzy msgid "" "The attached message matched the %(listname)s mailing list's content " "filtering\n" @@ -8637,6 +8780,7 @@ msgid "HTML attachment scrubbed and removed" msgstr "Anexo em HTML limpo e removido" #: Mailman/Handlers/Scrubber.py:234 Mailman/Handlers/Scrubber.py:259 +#, fuzzy msgid "" "An HTML attachment was scrubbed...\n" "URL: %(url)s\n" @@ -8723,6 +8867,7 @@ msgid "Message rejected by filter rule match" msgstr "" #: Mailman/Handlers/ToDigest.py:173 +#, fuzzy msgid "%(realname)s Digest, Vol %(volume)d, Issue %(issue)d" msgstr "Digest %(realname)s, volume %(volume)d, assunto %(issue)d" @@ -8759,6 +8904,7 @@ msgid "End of " msgstr "Fim da " #: Mailman/ListAdmin.py:309 +#, fuzzy msgid "Posting of your message titled \"%(subject)s\"" msgstr "O seu envio de mensagem com o título \"%(subject)s\"" @@ -8771,6 +8917,7 @@ msgid "Forward of moderated message" msgstr "Encaminhamento de mensagem moderada" #: Mailman/ListAdmin.py:405 +#, fuzzy msgid "New subscription request to list %(realname)s from %(addr)s" msgstr "Novo pedido de inscrição na lista %(realname)s por %(addr)s" @@ -8784,6 +8931,7 @@ msgid "via admin approval" msgstr "Continuar aguardando aprovação" #: Mailman/ListAdmin.py:466 +#, fuzzy msgid "New unsubscription request from %(realname)s by %(addr)s" msgstr "Novo pedido de anulação de inscrição de %(realname)s por %(addr)s" @@ -8796,10 +8944,12 @@ msgid "Original Message" msgstr "Mensagem Original" #: Mailman/ListAdmin.py:526 +#, fuzzy msgid "Request to mailing list %(realname)s rejected" msgstr "O pedido para a lista de discussão %(realname)s foi rejeitado." #: Mailman/MTA/Manual.py:66 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been created via the through-the-web\n" "interface. In order to complete the activation of this mailing list, the\n" @@ -8833,10 +8983,12 @@ msgid "## %(listname)s mailing list" msgstr "## lista de discussão %(listname)s" #: Mailman/MTA/Manual.py:99 +#, fuzzy msgid "Mailing list creation request for list %(listname)s" msgstr "Pedido de criação da lista de discussão %(listname)s" #: Mailman/MTA/Manual.py:113 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been removed via the through-the-web\n" "interface. In order to complete the de-activation of this mailing list, " @@ -8854,6 +9006,7 @@ msgstr "" "Eis as entradas no ficheiro /etc/aliases que devem ser removidas:\n" #: Mailman/MTA/Manual.py:123 +#, fuzzy msgid "" "\n" "To finish removing your mailing list, you must edit your /etc/aliases (or\n" @@ -8871,14 +9024,17 @@ msgstr "" "## lista de discussão %(listname)s" #: Mailman/MTA/Manual.py:142 +#, fuzzy msgid "Mailing list removal request for list %(listname)s" msgstr "Pedido de remoção da lista de discussão %(listname)s" #: Mailman/MTA/Postfix.py:442 +#, fuzzy msgid "checking permissions on %(file)s" msgstr "verificando as autorizações de %(file)s" #: Mailman/MTA/Postfix.py:452 +#, fuzzy msgid "%(file)s permissions must be 0664 (got %(octmode)s)" msgstr "As autorizações de %(file)s têm de ser 0664 (faça %(octmode)s)" @@ -8892,14 +9048,17 @@ msgid "(fixing)" msgstr "(corrigindo)" #: Mailman/MTA/Postfix.py:470 +#, fuzzy msgid "checking ownership of %(dbfile)s" msgstr "verificando o dono de %(dbfile)s" #: Mailman/MTA/Postfix.py:478 +#, fuzzy msgid "%(dbfile)s owned by %(owner)s (must be owned by %(user)s" msgstr "%(dbfile)s tem como dono %(owner)s (tem de ter como dono %(user)s" #: Mailman/MTA/Postfix.py:491 +#, fuzzy msgid "%(dbfile)s permissions must be 0664 (got %(octmode)s)" msgstr "As autorizações de %(dbfile)s têm de ser 0664 (faça %(octmode)s)" @@ -8914,14 +9073,17 @@ msgid "Your confirmation is required to leave the %(listname)s mailing list" msgstr "Você não é membro da lista de discussão %(listname)s" #: Mailman/MailList.py:999 Mailman/MailList.py:1492 +#, fuzzy msgid " from %(remote)s" msgstr " de %(remote)s" #: Mailman/MailList.py:1043 +#, fuzzy msgid "subscriptions to %(realname)s require moderator approval" msgstr "as inscrições em %(realname)s requerem aprovação pelo moderador" #: Mailman/MailList.py:1125 bin/add_members:299 +#, fuzzy msgid "%(realname)s subscription notification" msgstr "notificação de inscrição de %(realname)s" @@ -8930,6 +9092,7 @@ msgid "unsubscriptions require moderator approval" msgstr "as anulações de inscrição requerem aprovação pelo moderador" #: Mailman/MailList.py:1166 +#, fuzzy msgid "%(realname)s unsubscribe notification" msgstr "notificação de anulação de inscrição de %(realname)s" @@ -8949,6 +9112,7 @@ msgid "via web confirmation" msgstr "String de confirmação incorrecta" #: Mailman/MailList.py:1396 +#, fuzzy msgid "subscriptions to %(name)s require administrator approval" msgstr "as inscrições em %(name)s requerem aprovação pelo administrador." @@ -8967,6 +9131,7 @@ msgid "Last autoresponse notification for today" msgstr "Última notificação de resposta automática por hoje" #: Mailman/Queue/BounceRunner.py:360 +#, fuzzy msgid "" "The attached message was received as a bounce, but either the bounce format\n" "was not recognized, or no member addresses could be extracted from it. " @@ -9052,6 +9217,7 @@ msgid "Original message suppressed by Mailman site configuration\n" msgstr "" #: Mailman/htmlformat.py:675 +#, fuzzy msgid "Delivered by Mailman
                    version %(version)s" msgstr "Entregue pelo Mailman
                    versão %(version)s" @@ -9140,6 +9306,7 @@ msgid "Server Local Time" msgstr "Hora local do servidor" #: Mailman/i18n.py:180 +#, fuzzy msgid "" "%(wday)s %(mon)s %(day)2i %(hh)02i:%(mm)02i:%(ss)02i %(tzname)s %(year)04i" msgstr "" @@ -9257,6 +9424,7 @@ msgstr "" "pode ser '-'.\n" #: bin/add_members:162 bin/add_members:172 +#, fuzzy msgid "Already a member: %(member)s" msgstr "Já é um membro: %(member)s" @@ -9265,10 +9433,12 @@ msgid "Bad/Invalid email address: blank line" msgstr "Endereço de email incorrecto/inválido: linha em branco" #: bin/add_members:180 +#, fuzzy msgid "Bad/Invalid email address: %(member)s" msgstr "Endereço de email incorrecto/inválido: %(member)s" #: bin/add_members:182 +#, fuzzy msgid "Hostile address (illegal characters): %(member)s" msgstr "Endereço hostil (caracteres ilegais): %(member)s" @@ -9278,14 +9448,17 @@ msgid "Invited: %(member)s" msgstr "Inscrito: %(member)s" #: bin/add_members:187 +#, fuzzy msgid "Subscribed: %(member)s" msgstr "Inscrito: %(member)s" #: bin/add_members:237 +#, fuzzy msgid "Bad argument to -w/--welcome-msg: %(arg)s" msgstr "Argumento incorrecto para -w/--welcome-msg: %(arg)s" #: bin/add_members:244 +#, fuzzy msgid "Bad argument to -a/--admin-notify: %(arg)s" msgstr "Argumento incorrecto para -a/--admin-notify: %(arg)s" @@ -9300,6 +9473,7 @@ msgstr "" #: bin/add_members:261 bin/config_list:110 bin/export.py:271 bin/find_member:97 #: bin/inject:91 bin/list_admins:90 bin/list_members:252 bin/sync_members:222 #: cron/bumpdigests:86 +#, fuzzy msgid "No such list: %(listname)s" msgstr "Lista inexistente: %(listname)s" @@ -9404,6 +9578,7 @@ msgid "listname is required" msgstr "o nome da lista é necessário" #: bin/arch:143 bin/change_pw:107 bin/config_list:257 +#, fuzzy msgid "" "No such list \"%(listname)s\"\n" "%(e)s" @@ -9412,6 +9587,7 @@ msgstr "" "%(e)s" #: bin/arch:168 +#, fuzzy msgid "Cannot open mbox file %(mbox)s: %(msg)s" msgstr "Não foi possível abrir o ficheiro mbox %(mbox)s: %(msg)s" @@ -9565,6 +9741,7 @@ msgstr "" " Mostra esta mensagem de ajuda e sai.\n" #: bin/change_pw:145 +#, fuzzy msgid "Bad arguments: %(strargs)s" msgstr "Argumentos incorrectos: %(strargs)s" @@ -9573,14 +9750,17 @@ msgid "Empty list passwords are not allowed" msgstr "Passwords vazias não são permitidas em listas" #: bin/change_pw:181 +#, fuzzy msgid "New %(listname)s password: %(notifypassword)s" msgstr "Nova password de %(listname)s: %(notifypassword)s" #: bin/change_pw:190 +#, fuzzy msgid "Your new %(listname)s list password" msgstr "A sua nova password da lista %(listname)s" #: bin/change_pw:191 +#, fuzzy msgid "" "The site administrator at %(hostname)s has changed the password for your\n" "mailing list %(listname)s. It is now\n" @@ -9683,6 +9863,7 @@ msgid "List:" msgstr "Lista:" #: bin/check_db:148 +#, fuzzy msgid " %(file)s: okay" msgstr " %(file)s: Ok" @@ -9708,27 +9889,33 @@ msgstr "" "\n" #: bin/check_perms:110 +#, fuzzy msgid " checking gid and mode for %(path)s" msgstr " verificando gid e modo de %(path)s" #: bin/check_perms:122 +#, fuzzy msgid "%(path)s bad group (has: %(groupname)s, expected %(MAILMAN_GROUP)s)" msgstr "" "%(path)s grupo incorrecto (tem: %(groupname)s, espera-se %(MAILMAN_GROUP)s)" #: bin/check_perms:151 +#, fuzzy msgid "directory permissions must be %(octperms)s: %(path)s" msgstr "As autorizações da directoria têm de ser %(octperms)s: %(path)s" #: bin/check_perms:160 +#, fuzzy msgid "source perms must be %(octperms)s: %(path)s" msgstr "as autorizações da directoria fonte têm de ser %(octperms)s: %(path)s" #: bin/check_perms:171 +#, fuzzy msgid "article db files must be %(octperms)s: %(path)s" msgstr "As autorizações dos ficheiros db têm de ser %(octperms)s: %(path)s" #: bin/check_perms:183 +#, fuzzy msgid "checking mode for %(prefix)s" msgstr "verificando o modo para %(prefix)s" @@ -9738,14 +9925,17 @@ msgid "WARNING: directory does not exist: %(d)s" msgstr "a directoria tem de ser pelo menos 02775: %(d)s" #: bin/check_perms:197 +#, fuzzy msgid "directory must be at least 02775: %(d)s" msgstr "a directoria tem de ser pelo menos 02775: %(d)s" #: bin/check_perms:209 +#, fuzzy msgid "checking perms on %(private)s" msgstr "verificando autorizações em %(private)s" #: bin/check_perms:214 +#, fuzzy msgid "%(private)s must not be other-readable" msgstr "%(private)s não deve ser lido por outros" @@ -9763,6 +9953,7 @@ msgid "mbox file must be at least 0660:" msgstr "o ficheiro mbox deve ser pelo menos 0660:" #: bin/check_perms:263 +#, fuzzy msgid "%(dbdir)s \"other\" perms must be 000" msgstr "as autorizações \"other\" de %(dbdir)s têm de ser 000" @@ -9771,26 +9962,32 @@ msgid "checking cgi-bin permissions" msgstr "verificando as autorizações de cgi-bin" #: bin/check_perms:278 +#, fuzzy msgid " checking set-gid for %(path)s" msgstr " verificando set-gid para %(path)s" #: bin/check_perms:282 +#, fuzzy msgid "%(path)s must be set-gid" msgstr "%(path)s tem de ser set-gid" #: bin/check_perms:292 +#, fuzzy msgid "checking set-gid for %(wrapper)s" msgstr "verificando set-gid para %(wrapper)s" #: bin/check_perms:296 +#, fuzzy msgid "%(wrapper)s must be set-gid" msgstr "%(wrapper)s tem de ser set-gid" #: bin/check_perms:306 +#, fuzzy msgid "checking permissions on %(pwfile)s" msgstr "verificando autorizações de %(pwfile)s" #: bin/check_perms:315 +#, fuzzy msgid "%(pwfile)s permissions must be exactly 0640 (got %(octmode)s)" msgstr "" "as autorizações de %(pwfile)s têm de ser exactamente\n" @@ -9801,10 +9998,12 @@ msgid "checking permissions on list data" msgstr "verificando as autorizações nos dados da lista" #: bin/check_perms:348 +#, fuzzy msgid " checking permissions on: %(path)s" msgstr " verificando autorizações em: %(path)s" #: bin/check_perms:356 +#, fuzzy msgid "file permissions must be at least 660: %(path)s" msgstr "as autorizações dos ficheiro têm de ser pelo menos 660: %(path)s" @@ -9893,6 +10092,7 @@ msgid "Unix-From line changed: %(lineno)d" msgstr "Linha From de Unix modificada: %(lineno)d" #: bin/cleanarch:111 +#, fuzzy msgid "Bad status number: %(arg)s" msgstr "Número de status incorrecto: %(arg)s" @@ -10040,10 +10240,12 @@ msgid " original address removed:" msgstr " endereço original removido:" #: bin/clone_member:202 +#, fuzzy msgid "Not a valid email address: %(toaddr)s" msgstr "Não é um endereço de email válido: %(toaddr)s" #: bin/clone_member:215 +#, fuzzy msgid "" "Error opening list \"%(listname)s\", skipping.\n" "%(e)s" @@ -10176,22 +10378,27 @@ msgid "legal values are:" msgstr "valores legais são:" #: bin/config_list:270 +#, fuzzy msgid "attribute \"%(k)s\" ignored" msgstr "atributo \"%(k)s ignorado" #: bin/config_list:273 +#, fuzzy msgid "attribute \"%(k)s\" changed" msgstr "atributo \"%(k)s\" modificado" #: bin/config_list:279 +#, fuzzy msgid "Non-standard property restored: %(k)s" msgstr "Propriedade não padrão restaurada: %(k)s" #: bin/config_list:288 +#, fuzzy msgid "Invalid value for property: %(k)s" msgstr "Valor inválido para a propriedade: %(k)s" #: bin/config_list:291 +#, fuzzy msgid "Bad email address for option %(k)s: %(v)s" msgstr "Endereço de e-mail incorrecto para a opção %(k)s: %(v)s" @@ -10262,8 +10469,9 @@ msgid "Ignoring non-held message: %(f)s" msgstr "Ignorando modificações num membro removido: %(user)s" #: bin/discard:100 +#, fuzzy msgid "Ignoring held msg w/bad id: %(f)s" -msgstr "" +msgstr "Ignorando modificações num membro removido: %(user)s" #: bin/discard:112 #, fuzzy @@ -10345,6 +10553,7 @@ msgid "No filename given." msgstr "Não foi fornecido o nome do ficheiro." #: bin/dumpdb:108 +#, fuzzy msgid "Bad arguments: %(pargs)s" msgstr "Argumentos incorrectos: %(pargs)s" @@ -10572,6 +10781,7 @@ msgid "Setting web_page_url to: %(web_page_url)s" msgstr "Ajustando web_page_url para: %(web_page_url)s" #: bin/fix_url.py:88 +#, fuzzy msgid "Setting host_name to: %(mailhost)s" msgstr "Ajustando host_name para: %(mailhost)s" @@ -10649,6 +10859,7 @@ msgstr "" "injetados. Se omitido, a entrada padrão é usada.\n" #: bin/inject:84 +#, fuzzy msgid "Bad queue directory: %(qdir)s" msgstr "Directoria de fila de espera inválida: %(qdir)s" @@ -10705,6 +10916,7 @@ msgstr "" "Você poderá ter mais de uma lista especificada na linha de comando.\n" #: bin/list_admins:97 +#, fuzzy msgid "List: %(listname)s, \tOwners: %(owners)s" msgstr "Lista: %(listname)s, \\tDonos: %(owners)s" @@ -10882,10 +11094,12 @@ msgstr "" "é mostrada para o statos de endereço.\n" #: bin/list_members:198 +#, fuzzy msgid "Bad --nomail option: %(why)s" msgstr "Opção --nomail incorrecta: %(why)s" #: bin/list_members:209 +#, fuzzy msgid "Bad --digest option: %(kind)s" msgstr "Opção --digest incorrecta: %(kind)s" @@ -11135,6 +11349,7 @@ msgstr "" " reopen - Isto fará que os arquivos de logs sejam reabertos.\n" #: bin/mailmanctl:152 +#, fuzzy msgid "PID unreadable in: %(pidfile)s" msgstr "PID não legível em: %(pidfile)s" @@ -11143,6 +11358,7 @@ msgid "Is qrunner even running?" msgstr "Será que o qrunner está a correr?" #: bin/mailmanctl:160 +#, fuzzy msgid "No child with pid: %(pid)s" msgstr "Não há um processo filho com o pid: %(pid)s" @@ -11198,10 +11414,12 @@ msgstr "" "Saindo." #: bin/mailmanctl:280 cron/mailpasswds:119 +#, fuzzy msgid "Site list is missing: %(sitelistname)s" msgstr "Não existe a lista do site : %(sitelistname)s" #: bin/mailmanctl:305 +#, fuzzy msgid "Run this program as root or as the %(name)s user, or use -u." msgstr "" "Re-execute este programa como root, como utilizador %(name)s ou utilize a\n" @@ -11212,6 +11430,7 @@ msgid "No command given." msgstr "Não foi fornecido um comando." #: bin/mailmanctl:339 +#, fuzzy msgid "Bad command: %(command)s" msgstr "Comando incorrecto: %(command)s" @@ -11293,6 +11512,7 @@ msgid "list creator" msgstr "criador da lista" #: bin/mmsitepass:86 +#, fuzzy msgid "New %(pwdesc)s password: " msgstr "Nova password %(pwdesc)s: " @@ -11501,6 +11721,7 @@ msgstr "" "Note que os nomes de listas são forçados a estarem em minúsculas.\n" #: bin/newlist:162 +#, fuzzy msgid "Unknown language: %(lang)s" msgstr "Idioma Desconhecido: %(lang)s" @@ -11513,6 +11734,7 @@ msgid "Enter the email of the person running the list: " msgstr "Introduza o email da pessoa que administra a lista: " #: bin/newlist:193 +#, fuzzy msgid "Initial %(listname)s password: " msgstr "Password inicial da %(listname)s: " @@ -11522,11 +11744,12 @@ msgstr "A password da lista n #: bin/newlist:220 msgid "" -" - owner addresses need to be fully-qualified names like \"owner@example.com" -"\", not just \"owner\"." +" - owner addresses need to be fully-qualified names like \"owner@example." +"com\", not just \"owner\"." msgstr "" #: bin/newlist:244 +#, fuzzy msgid "Hit enter to notify %(listname)s owner..." msgstr "Pressione Enter para notificar o dono da %(listname)s" @@ -11658,6 +11881,7 @@ msgstr "" "\n" #: bin/qrunner:179 +#, fuzzy msgid "%(name)s runs the %(runnername)s qrunner" msgstr "%(name)s executa o qrunner %(runnername)s" @@ -11781,18 +12005,22 @@ msgstr "" " endereço1... são os endereços a serem removidos.\n" #: bin/remove_members:156 +#, fuzzy msgid "Could not open file for reading: %(filename)s." msgstr "Não foi possível abrir o ficheiro para leitura: %(filename)s." #: bin/remove_members:163 +#, fuzzy msgid "Error opening list %(listname)s... skipping." msgstr "Erro ao abrir a lista %(listname)s... saltando." #: bin/remove_members:173 +#, fuzzy msgid "No such member: %(addr)s" msgstr "Membro inexistente: %(addr)s" #: bin/remove_members:178 +#, fuzzy msgid "User `%(addr)s' removed from list: %(listname)s." msgstr "Utilizador '%(addr)s' removido da lista: %(listname)s." @@ -11868,6 +12096,7 @@ msgstr "" "\n" #: bin/rmlist:73 bin/rmlist:76 +#, fuzzy msgid "Removing %(msg)s" msgstr "Removendo %(msg)s" @@ -11877,10 +12106,12 @@ msgid "%(listname)s %(msg)s not found as %(filename)s" msgstr "%(msg)s %(listname)s não foi encontrada como %(dir)s" #: bin/rmlist:105 +#, fuzzy msgid "No such list (or list already deleted): %(listname)s" msgstr "Lista inexistente (ou lista já apagada): %(listname)s" #: bin/rmlist:108 +#, fuzzy msgid "No such list: %(listname)s. Removing its residual archives." msgstr "Lista inexistente: %(listname)s. Removendo os arquivos residuais." @@ -12060,6 +12291,7 @@ msgstr "" " Requerido. Isto especifica a lista a ser sincronizada.\n" #: bin/sync_members:115 +#, fuzzy msgid "Bad choice: %(yesno)s" msgstr "Escolha incorrecta: %(yesno)s" @@ -12076,6 +12308,7 @@ msgid "No argument to -f given" msgstr "Não foi passado um argumento para -f" #: bin/sync_members:172 +#, fuzzy msgid "Illegal option: %(opt)s" msgstr "Opção ilegal: %(opt)s" @@ -12088,6 +12321,7 @@ msgid "Must have a listname and a filename" msgstr "Tem de ter um nome de lista e um nome de ficheiro" #: bin/sync_members:191 +#, fuzzy msgid "Cannot read address file: %(filename)s: %(msg)s" msgstr "Não foi possível ler arquivo de endereços: %(filename)s: %(msg)s" @@ -12104,14 +12338,17 @@ msgid "You must fix the preceding invalid addresses first." msgstr "Primeiro tem de corrigir os precedentes endereços inválidos." #: bin/sync_members:264 +#, fuzzy msgid "Added : %(s)s" msgstr "Adicionado: %(s)s" #: bin/sync_members:289 +#, fuzzy msgid "Removed: %(s)s" msgstr "Removido: %(s)s" #: bin/transcheck:19 +#, fuzzy msgid "" "\n" "Check a given Mailman translation, making sure that variables and\n" @@ -12221,6 +12458,7 @@ msgstr "" "de qfiles/shunt.\n" #: bin/unshunt:85 +#, fuzzy msgid "" "Cannot unshunt message %(filebase)s, skipping:\n" "%(e)s" @@ -12267,14 +12505,17 @@ msgstr "" "de alguma versão anterior. Ele sabe sobre versões anteriores a 1.0b4 (?).\n" #: bin/update:107 +#, fuzzy msgid "Fixing language templates: %(listname)s" msgstr "Corrigindo as templates de idioma: %(listname)s" #: bin/update:196 bin/update:711 +#, fuzzy msgid "WARNING: could not acquire lock for list: %(listname)s" msgstr "AVISO: não foi possível adquirir um lock para a lista: %(listname)s" #: bin/update:215 +#, fuzzy msgid "Resetting %(n)s BYBOUNCEs disabled addrs with no bounce info" msgstr "" "Reinicializando %(n)s BYBOUNCEs endereços desactivado que não tenham " @@ -12346,6 +12587,7 @@ msgid "- updating old private mbox file" msgstr "- actualizando o antigo ficheiro mbox privado" #: bin/update:295 +#, fuzzy msgid "" " unknown file in the way, moving\n" " %(o_pri_mbox_file)s\n" @@ -12362,6 +12604,7 @@ msgid "- updating old public mbox file" msgstr "- actualizando ficheiro antigo de mbox pública" #: bin/update:317 +#, fuzzy msgid "" " unknown file in the way, moving\n" " %(o_pub_mbox_file)s\n" @@ -12391,18 +12634,22 @@ msgid "- %(o_tmpl)s doesn't exist, leaving untouched" msgstr "- ambos %(o_tmpl)s e %(n_tmpl)s existem, deixando sem tocar" #: bin/update:396 +#, fuzzy msgid "removing directory %(src)s and everything underneath" msgstr "removendo a directoria %(src)s e tudo dentro dela" #: bin/update:399 +#, fuzzy msgid "removing %(src)s" msgstr "removendo %(src)s" #: bin/update:403 +#, fuzzy msgid "Warning: couldn't remove %(src)s -- %(rest)s" msgstr "Aviso: Não foi possível remover %(src)s -- %(rest)s" #: bin/update:408 +#, fuzzy msgid "couldn't remove old file %(pyc)s -- %(rest)s" msgstr "não foi possível remover o ficheiro antigo %(pyc)s -- %(rest)s" @@ -12463,6 +12710,7 @@ msgid "done" msgstr "concluído" #: bin/update:691 +#, fuzzy msgid "Updating mailing list: %(listname)s" msgstr "Actualizando a lista de discussão: %(listname)s" @@ -12526,6 +12774,7 @@ msgid "No updates are necessary." msgstr "Não é necessária nenhuma actualização." #: bin/update:796 +#, fuzzy msgid "" "Downgrade detected, from version %(hexlversion)s to version %(hextversion)s\n" "This is probably not safe.\n" @@ -12537,10 +12786,12 @@ msgstr "" "Saindo." #: bin/update:801 +#, fuzzy msgid "Upgrading from version %(hexlversion)s to %(hextversion)s" msgstr "Actualizando da versão %(hexlversion)s para %(hextversion)s" #: bin/update:810 +#, fuzzy msgid "" "\n" "ERROR:\n" @@ -12817,6 +13068,7 @@ msgstr "" " ocorra uma excepção." #: bin/withlist:175 +#, fuzzy msgid "Unlocking (but not saving) list: %(listname)s" msgstr "Desbloqueando (sem guardar) a lista: %(listname)s" @@ -12825,6 +13077,7 @@ msgid "Finalizing" msgstr "Finalizando" #: bin/withlist:188 +#, fuzzy msgid "Loading list %(listname)s" msgstr "Carregando a lista %(listname)s" @@ -12837,6 +13090,7 @@ msgid "(unlocked)" msgstr "(desbloqueada)" #: bin/withlist:197 +#, fuzzy msgid "Unknown list: %(listname)s" msgstr "Lista desconhecida: %(listname)s" @@ -12849,18 +13103,22 @@ msgid "--all requires --run" msgstr "--all requer --run" #: bin/withlist:266 +#, fuzzy msgid "Importing %(module)s..." msgstr "Importando %(module)s..." #: bin/withlist:270 +#, fuzzy msgid "Running %(module)s.%(callable)s()..." msgstr "Correndo %(module)s.%(callable)s()..." #: bin/withlist:291 +#, fuzzy msgid "The variable `m' is the %(listname)s MailList instance" msgstr "A variável 'm' é a instância MailList %(listname)s" #: cron/bumpdigests:19 +#, fuzzy msgid "" "Increment the digest volume number and reset the digest number to one.\n" "\n" @@ -12918,6 +13176,7 @@ msgid "" msgstr "" #: cron/checkdbs:121 +#, fuzzy msgid "%(count)d %(realname)s moderator request(s) waiting" msgstr "%(count)d pedidos de moderação de %(realname)s em espera" @@ -12944,6 +13203,7 @@ msgstr "" "Envios pendentes:" #: cron/checkdbs:169 +#, fuzzy msgid "" "From: %(sender)s on %(date)s\n" "Subject: %(subject)s\n" @@ -13100,6 +13360,7 @@ msgstr "" " Mostra este texto e sai.\n" #: cron/mailpasswds:19 +#, fuzzy msgid "" "Send password reminders for all lists to all users.\n" "\n" @@ -13151,10 +13412,12 @@ msgid "Password // URL" msgstr "Password // URL" #: cron/mailpasswds:222 +#, fuzzy msgid "%(host)s mailing list memberships reminder" msgstr "Nota para os membros da lista de discussão %(host)s" #: cron/nightly_gzip:19 +#, fuzzy msgid "" "Re-generate the Pipermail gzip'd archive flat files.\n" "\n" diff --git a/messages/pt_BR/LC_MESSAGES/mailman.po b/messages/pt_BR/LC_MESSAGES/mailman.po index 14c546d5..0184cbf0 100644 --- a/messages/pt_BR/LC_MESSAGES/mailman.po +++ b/messages/pt_BR/LC_MESSAGES/mailman.po @@ -62,10 +62,12 @@ msgid "

                    Currently, there are no archives.

                    " msgstr "

                    Atualmente, não existem arquivos das listas.

                    " #: Mailman/Archiver/HyperArch.py:821 +#, fuzzy msgid "Gzip'd Text%(sz)s" msgstr "Gzip'd Text%(sz)s" #: Mailman/Archiver/HyperArch.py:826 +#, fuzzy msgid "Text%(sz)s" msgstr "Texto%(sz)s" @@ -138,18 +140,22 @@ msgid "Third" msgstr "Terceira" #: Mailman/Archiver/HyperArch.py:938 +#, fuzzy msgid "%(ord)s quarter %(year)i" msgstr "%(ord)s quarto %(year)i" #: Mailman/Archiver/HyperArch.py:945 +#, fuzzy msgid "%(month)s %(year)i" msgstr "%(month)s %(year)i" #: Mailman/Archiver/HyperArch.py:950 +#, fuzzy msgid "The Week Of Monday %(day)i %(month)s %(year)i" msgstr "A Semana do Mes %(day)i %(month)s %(year)i" #: Mailman/Archiver/HyperArch.py:954 +#, fuzzy msgid "%(day)i %(month)s %(year)i" msgstr "%(day)i %(month)s %(year)i" @@ -158,10 +164,12 @@ msgid "Computing threaded index\n" msgstr "Computando o índice da discussão\n" #: Mailman/Archiver/HyperArch.py:1319 +#, fuzzy msgid "Updating HTML for article %(seq)s" msgstr "Atualizando HTML para o artigo %(seq)s" #: Mailman/Archiver/HyperArch.py:1326 +#, fuzzy msgid "article file %(filename)s is missing!" msgstr "o arquivo de artigo %(filename)s está faltando!" @@ -178,6 +186,7 @@ msgid "Pickling archive state into " msgstr "Conservando estado de arquivos em " #: Mailman/Archiver/pipermail.py:453 +#, fuzzy msgid "Updating index files for archive [%(archive)s]" msgstr "Atualizando índices para o arquivo [%(archive)s]" @@ -186,6 +195,7 @@ msgid " Thread" msgstr " Discussão" #: Mailman/Archiver/pipermail.py:597 +#, fuzzy msgid "#%(counter)05d %(msgid)s" msgstr "#%(counter)05d %(msgid)s" @@ -222,6 +232,7 @@ msgid "disabled address" msgstr "desabilitar endereço" #: Mailman/Bouncer.py:304 +#, fuzzy msgid " The last bounce received from you was dated %(date)s" msgstr " O último bounce recebido de você foi em %(date)s" @@ -249,6 +260,7 @@ msgstr "Administrador" #: Mailman/Cgi/options.py:101 Mailman/Cgi/private.py:108 #: Mailman/Cgi/rmlist.py:75 Mailman/Cgi/roster.py:59 #: Mailman/Cgi/subscribe.py:67 +#, fuzzy msgid "No such list %(safelistname)s" msgstr "A lista %(safelistname)s não existe" @@ -324,6 +336,7 @@ msgstr "" " resolva este problema. Membros afetado(s) %(rm)r." #: Mailman/Cgi/admin.py:268 +#, fuzzy msgid "%(hostname)s mailing lists - Admin Links" msgstr "listas de discussão em %(hostname)s - Links Administrativos" @@ -336,6 +349,7 @@ msgid "Mailman" msgstr "Mailman" #: Mailman/Cgi/admin.py:310 +#, fuzzy msgid "" "

                    There currently are no publicly-advertised %(mailmanlink)s\n" " mailing lists on %(hostname)s." @@ -344,6 +358,7 @@ msgstr "" " %(mailmanlink)s em %(hostname)s." #: Mailman/Cgi/admin.py:316 +#, fuzzy msgid "" "

                    Below is the collection of publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s. Click on a list\n" @@ -358,6 +373,7 @@ msgid "right " msgstr "direito " #: Mailman/Cgi/admin.py:325 +#, fuzzy msgid "" "To visit the administrators configuration page for an\n" " unadvertised list, open a URL similar to this one, but with a '/' " @@ -402,6 +418,7 @@ msgid "No valid variable name found." msgstr "Nenhum nome variável válido foi encontrado." #: Mailman/Cgi/admin.py:395 +#, fuzzy msgid "" "%(realname)s Mailing list Configuration Help\n" "
                    %(varname)s Option" @@ -410,6 +427,7 @@ msgstr "" " Opção
                    %(varname)s" #: Mailman/Cgi/admin.py:402 +#, fuzzy msgid "Mailman %(varname)s List Option Help" msgstr "Ajuda na lista de opções do Mailman %(varname)s" @@ -431,14 +449,17 @@ msgstr "" " " #: Mailman/Cgi/admin.py:431 +#, fuzzy msgid "return to the %(categoryname)s options page." msgstr "retornar para a página de opções %(categoryname)s." #: Mailman/Cgi/admin.py:446 +#, fuzzy msgid "%(realname)s Administration (%(label)s)" msgstr "Administração da %(realname)s (%(label)s)" #: Mailman/Cgi/admin.py:447 +#, fuzzy msgid "%(realname)s mailing list administration
                    %(label)s Section" msgstr "Seção de administração da lista de discussão %(realname)s
                    %(label)s" @@ -522,6 +543,7 @@ msgid "Value" msgstr "Valor" #: Mailman/Cgi/admin.py:668 +#, fuzzy msgid "" "Badly formed options entry:\n" " %(record)s" @@ -622,10 +644,12 @@ msgid "Move rule down" msgstr "Mover regra para baixo" #: Mailman/Cgi/admin.py:870 +#, fuzzy msgid "
                    (Edit %(varname)s)" msgstr "
                    (Editar %(varname)s)" #: Mailman/Cgi/admin.py:872 +#, fuzzy msgid "
                    (Details for %(varname)s)" msgstr "
                    (Detalhes de %(varname)s)" @@ -664,6 +688,7 @@ msgid "(help)" msgstr "(ajuda)" #: Mailman/Cgi/admin.py:930 +#, fuzzy msgid "Find member %(link)s:" msgstr "Encontrar membro %(link)s:" @@ -676,10 +701,12 @@ msgid "Bad regular expression: " msgstr "Expressão regular incorreta: " #: Mailman/Cgi/admin.py:1013 +#, fuzzy msgid "%(allcnt)s members total, %(membercnt)s shown" msgstr "%(allcnt)s membros no total, %(membercnt)s mostrados" #: Mailman/Cgi/admin.py:1016 +#, fuzzy msgid "%(allcnt)s members total" msgstr "%(allcnt)s membros no total" @@ -867,6 +894,7 @@ msgstr "" " listada abaixo:" #: Mailman/Cgi/admin.py:1221 +#, fuzzy msgid "from %(start)s to %(end)s" msgstr "de %(start)s para %(end)s" @@ -1007,6 +1035,7 @@ msgid "Change list ownership passwords" msgstr "Modificar senhas do dono da lista" #: Mailman/Cgi/admin.py:1364 +#, fuzzy msgid "" "The list administrators are the people who have ultimate control " "over\n" @@ -1124,6 +1153,7 @@ msgstr "Endere #: Mailman/Cgi/admin.py:1529 Mailman/Cgi/admin.py:1703 bin/add_members:175 #: bin/clone_member:136 bin/sync_members:268 +#, fuzzy msgid "Banned address (matched %(pattern)s)" msgstr "Endereços banidos (%(pattern)s correspondente)" @@ -1180,6 +1210,7 @@ msgid "%(schange_to)s is already a member" msgstr "%(schange_to)s já é um membro" #: Mailman/Cgi/admin.py:1620 +#, fuzzy msgid "%(schange_to)s matches banned pattern %(spat)s" msgstr "%(schange_to)s corresponde ao padrão %(spat)s para banimento" @@ -1220,6 +1251,7 @@ msgid "Not subscribed" msgstr "Não inscrito" #: Mailman/Cgi/admin.py:1773 +#, fuzzy msgid "Ignoring changes to deleted member: %(user)s" msgstr "Ignorando modificações no membro apagado: %(user)s" @@ -1232,10 +1264,12 @@ msgid "Error Unsubscribing:" msgstr "Erro ao descadastrar:" #: Mailman/Cgi/admindb.py:235 Mailman/Cgi/admindb.py:248 +#, fuzzy msgid "%(realname)s Administrative Database" msgstr "Banco de dados Administrativo %(realname)s" #: Mailman/Cgi/admindb.py:238 +#, fuzzy msgid "%(realname)s Administrative Database Results" msgstr "Resultados do banco de dados Administrativo %(realname)s" @@ -1264,6 +1298,7 @@ msgid "Discard all messages marked Defer" msgstr "Descartar todas as mensagens marcadas como Adiar" #: Mailman/Cgi/admindb.py:298 +#, fuzzy msgid "all of %(esender)s's held messages." msgstr "todas as mensagens em espera de %(esender)s." @@ -1284,6 +1319,7 @@ msgid "list of available mailing lists." msgstr "listar todas as listas de discussão disponíveis." #: Mailman/Cgi/admindb.py:362 +#, fuzzy msgid "You must specify a list name. Here is the %(link)s" msgstr "Você deverá especificar um nome de lista. Aqui é a %(link)s" @@ -1365,6 +1401,7 @@ msgid "The sender is now a member of this list" msgstr "O remetente agora é um membro desta lista" #: Mailman/Cgi/admindb.py:568 +#, fuzzy msgid "Add %(esender)s to one of these sender filters:" msgstr "Adicionar %(esender)s%(esender)s from ever subscribing to this\n" " mailing list" @@ -1401,6 +1439,7 @@ msgstr "" " ou você pode " #: Mailman/Cgi/admindb.py:591 +#, fuzzy msgid "view all messages from %(esender)s" msgstr "ver todas as mensagens do %(esender)s" @@ -1480,6 +1519,7 @@ msgid " is already a member" msgstr " já é um membro" #: Mailman/Cgi/admindb.py:973 +#, fuzzy msgid "%(addr)s is banned (matched: %(patt)s)" msgstr "%(addr)s está banido (corresponde: %(patt)s)" @@ -1488,6 +1528,7 @@ msgid "Confirmation string was empty." msgstr "A string de confirmação está vazia." #: Mailman/Cgi/confirm.py:108 +#, fuzzy msgid "" "Invalid confirmation string:\n" " %(safecookie)s.\n" @@ -1531,6 +1572,7 @@ msgstr "" " cancelada." #: Mailman/Cgi/confirm.py:178 +#, fuzzy msgid "System error, bad content: %(content)s" msgstr "Erro no sistema, conteúdo incorreto: %(content)s" @@ -1568,6 +1610,7 @@ msgid "Confirm subscription request" msgstr "Confirmar sua requisição de inscrição" #: Mailman/Cgi/confirm.py:259 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " subscription request to the mailing list %(listname)s. Your\n" @@ -1602,6 +1645,7 @@ msgstr "" " requisição de inscrição." #: Mailman/Cgi/confirm.py:275 +#, fuzzy msgid "" "Your confirmation is required in order to continue with\n" " the subscription request to the mailing list %(listname)s.\n" @@ -1657,6 +1701,7 @@ msgid "Preferred language:" msgstr "Idioma preferido:" #: Mailman/Cgi/confirm.py:317 +#, fuzzy msgid "Subscribe to list %(listname)s" msgstr "Se inscrever na lista %(listname)s" @@ -1673,6 +1718,7 @@ msgid "Awaiting moderator approval" msgstr "Aguardando aprovação do moderador" #: Mailman/Cgi/confirm.py:386 +#, fuzzy msgid "" " You have successfully confirmed your subscription request to " "the\n" @@ -1705,6 +1751,7 @@ msgid "You are already a member of this mailing list!" msgstr "Você já é membro desta lista de discussão!" #: Mailman/Cgi/confirm.py:400 +#, fuzzy msgid "" "You are currently banned from subscribing to\n" " this list. If you think this restriction is erroneous, please\n" @@ -1729,6 +1776,7 @@ msgid "Subscription request confirmed" msgstr "Requisição de inscrição confirmada" #: Mailman/Cgi/confirm.py:418 +#, fuzzy msgid "" " You have successfully confirmed your subscription request for\n" " \"%(addr)s\" to the %(listname)s mailing list. A separate\n" @@ -1758,6 +1806,7 @@ msgid "Unsubscription request confirmed" msgstr "Requisição de remoção confirmada" #: Mailman/Cgi/confirm.py:469 +#, fuzzy msgid "" " You have successfully unsubscribed from the %(listname)s " "mailing\n" @@ -1779,6 +1828,7 @@ msgid "Not available" msgstr "Não disponível" #: Mailman/Cgi/confirm.py:498 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " unsubscription request from the mailing list %(listname)s. " @@ -1823,6 +1873,7 @@ msgid "You have canceled your change of address request." msgstr "Você cancelou sua modificação do endereço requisitado." #: Mailman/Cgi/confirm.py:555 +#, fuzzy msgid "" "%(newaddr)s is banned from subscribing to the\n" " %(realname)s list. If you think this restriction is erroneous,\n" @@ -1833,6 +1884,7 @@ msgstr "" " favor contate os donos da lista em %(owneraddr)s." #: Mailman/Cgi/confirm.py:560 +#, fuzzy msgid "" "%(newaddr)s is already a member of\n" " the %(realname)s list. It is possible that you are attempting\n" @@ -1848,6 +1900,7 @@ msgid "Change of address request confirmed" msgstr "Modificação do endereço requisitado confirmada" #: Mailman/Cgi/confirm.py:571 +#, fuzzy msgid "" " You have successfully changed your address on the %(listname)s\n" " mailing list from %(oldaddr)s to %(newaddr)s. " @@ -1869,6 +1922,7 @@ msgid "globally" msgstr "globalmente" #: Mailman/Cgi/confirm.py:605 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " change of address request for the mailing list %(listname)s. " @@ -1931,6 +1985,7 @@ msgid "Sender discarded message via web." msgstr "O remetente descartou a mensagem via web." #: Mailman/Cgi/confirm.py:674 +#, fuzzy msgid "" "The held message with the Subject:\n" " header %(subject)s could not be found. The most " @@ -1950,6 +2005,7 @@ msgid "Posted message canceled" msgstr "Cancelamento da mensagem postada" #: Mailman/Cgi/confirm.py:685 +#, fuzzy msgid "" " You have successfully canceled the posting of your message with\n" " the Subject: header %(subject)s to the mailing list\n" @@ -1972,6 +2028,7 @@ msgstr "" "da lista." #: Mailman/Cgi/confirm.py:735 +#, fuzzy msgid "" "Your confirmation is required in order to cancel the\n" " posting of your message to the mailing list %(listname)s:\n" @@ -2020,6 +2077,7 @@ msgid "Membership re-enabled." msgstr "Membro reativado." #: Mailman/Cgi/confirm.py:798 +#, fuzzy msgid "" " You have successfully re-enabled your membership in the\n" " %(listname)s mailing list. You can now not available" msgstr "não disponível" #: Mailman/Cgi/confirm.py:846 +#, fuzzy msgid "" "Your membership in the %(realname)s mailing list is\n" " currently disabled due to excessive bounces. Your confirmation is\n" @@ -2118,10 +2178,12 @@ msgid "administrative list overview" msgstr "visão da lista administrativa" #: Mailman/Cgi/create.py:115 +#, fuzzy msgid "List name must not include \"@\": %(safelistname)s" msgstr "O nome da lista não deve incluir \"@\": %(safelistname)s" #: Mailman/Cgi/create.py:122 +#, fuzzy msgid "List already exists: %(safelistname)s" msgstr "A lista já existe: %(safelistname)s" @@ -2155,18 +2217,22 @@ msgid "You are not authorized to create new mailing lists" msgstr "Você não tem autorização para criar novas listas de discussão" #: Mailman/Cgi/create.py:182 +#, fuzzy msgid "Unknown virtual host: %(safehostname)s" msgstr "Host virtual desconhecido: %(safehostname)s" #: Mailman/Cgi/create.py:218 bin/newlist:219 +#, fuzzy msgid "Bad owner email address: %(s)s" msgstr "Endereço de email incorreto: %(s)s" #: Mailman/Cgi/create.py:223 bin/newlist:182 bin/newlist:223 +#, fuzzy msgid "List already exists: %(listname)s" msgstr "A lista já existe: %(listname)s" #: Mailman/Cgi/create.py:231 bin/newlist:217 +#, fuzzy msgid "Illegal list name: %(s)s" msgstr "Nome de lista inválido: %(s)s" @@ -2179,6 +2245,7 @@ msgstr "" " Por favor contate o administrador do site para assistência." #: Mailman/Cgi/create.py:273 bin/newlist:265 +#, fuzzy msgid "Your new mailing list: %(listname)s" msgstr "Sua nova lista de discussão: %(listname)s" @@ -2187,6 +2254,7 @@ msgid "Mailing list creation results" msgstr "Resultados da criação da lista de discussão" #: Mailman/Cgi/create.py:288 +#, fuzzy msgid "" "You have successfully created the mailing list\n" " %(listname)s and notification has been sent to the list owner\n" @@ -2209,6 +2277,7 @@ msgid "Create another list" msgstr "Criar outra lista" #: Mailman/Cgi/create.py:312 +#, fuzzy msgid "Create a %(hostname)s Mailing List" msgstr "Criar uma lista de discussão em %(hostname)s" @@ -2306,6 +2375,7 @@ msgstr "" " por padrão, passarem pela aprovação do moderador." #: Mailman/Cgi/create.py:432 +#, fuzzy msgid "" "Initial list of supported languages.

                    Note that if you do not\n" " select at least one initial language, the list will use the server\n" @@ -2404,6 +2474,7 @@ msgid "List name is required." msgstr "O nome da lista é requerido." #: Mailman/Cgi/edithtml.py:154 +#, fuzzy msgid "%(realname)s -- Edit html for %(template_info)s" msgstr "%(realname)s -- Editar html para %(template_info)s" @@ -2412,10 +2483,12 @@ msgid "Edit HTML : Error" msgstr "Editar HTML : Erro" #: Mailman/Cgi/edithtml.py:161 +#, fuzzy msgid "%(safetemplatename)s: Invalid template" msgstr "%(safetemplatename)s: Template inválido" #: Mailman/Cgi/edithtml.py:166 Mailman/Cgi/edithtml.py:167 +#, fuzzy msgid "%(realname)s -- HTML Page Editing" msgstr "%(realname)s -- Edição de Página HTML" @@ -2479,10 +2552,12 @@ msgid "HTML successfully updated." msgstr "HTML atualizado com sucesso." #: Mailman/Cgi/listinfo.py:89 +#, fuzzy msgid "%(hostname)s Mailing Lists" msgstr "Listas de discussão em %(hostname)s" #: Mailman/Cgi/listinfo.py:127 +#, fuzzy msgid "" "

                    There currently are no publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s." @@ -2491,6 +2566,7 @@ msgstr "" "%(mailmanlink)s em %(hostname)s." #: Mailman/Cgi/listinfo.py:131 +#, fuzzy msgid "" "

                    Below is a listing of all the public mailing lists on\n" " %(hostname)s. Click on a list name to get more information " @@ -2509,6 +2585,7 @@ msgid "right" msgstr "direita" #: Mailman/Cgi/listinfo.py:140 +#, fuzzy msgid "" " To visit the general information page for an unadvertised list,\n" " open a URL similar to this one, but with a '/' and the %(adj)s\n" @@ -2559,6 +2636,7 @@ msgid "CGI script error" msgstr "Erro no script CGI" #: Mailman/Cgi/options.py:71 +#, fuzzy msgid "Invalid request method: %(method)s" msgstr "Método de pedido inválido: %(method)s" @@ -2572,6 +2650,7 @@ msgstr "Endere #: Mailman/Cgi/options.py:183 Mailman/Cgi/options.py:240 #: Mailman/Cgi/options.py:264 +#, fuzzy msgid "No such member: %(safeuser)s." msgstr "Membro inexistente: %(safeuser)s." @@ -2624,6 +2703,7 @@ msgid "Note: " msgstr "Nota: " #: Mailman/Cgi/options.py:378 +#, fuzzy msgid "List subscriptions for %(safeuser)s on %(hostname)s" msgstr "Inscrições na lista para o usuário %(safeuser)s em %(hostname)s" @@ -2655,6 +2735,7 @@ msgid "You are already using that email address" msgstr "Você já está usando aquele endereço de email" #: Mailman/Cgi/options.py:459 +#, fuzzy msgid "" "The new address you requested %(newaddr)s is already a member of the\n" "%(listname)s mailing list, however you have also requested a global change " @@ -2668,6 +2749,7 @@ msgstr "" "discussão contendo o endereço %(safeuser)s será alterado. " #: Mailman/Cgi/options.py:468 +#, fuzzy msgid "The new address is already a member: %(newaddr)s" msgstr "O novo endereço já é um membro: %(newaddr)s" @@ -2676,6 +2758,7 @@ msgid "Addresses may not be blank" msgstr "Os endereços não podem estar em branco" #: Mailman/Cgi/options.py:488 +#, fuzzy msgid "A confirmation message has been sent to %(newaddr)s. " msgstr "Uma mensagem de confirmação deverá ser enviada para %(newaddr)s " @@ -2688,10 +2771,12 @@ msgid "Illegal email address provided" msgstr "Endereço de email ilegal fornecido" #: Mailman/Cgi/options.py:501 +#, fuzzy msgid "%(newaddr)s is already a member of the list." msgstr "%(newaddr)s já é um membro desta lista." #: Mailman/Cgi/options.py:504 +#, fuzzy msgid "" "%(newaddr)s is banned from this list. If you\n" " think this restriction is erroneous, please contact\n" @@ -2767,6 +2852,7 @@ msgstr "" " fizeram sua decisão." #: Mailman/Cgi/options.py:623 +#, fuzzy msgid "" "You have been successfully unsubscribed from the\n" " mailing list %(fqdn_listname)s. If you were receiving digest\n" @@ -2861,6 +2947,7 @@ msgid "day" msgstr "dia" #: Mailman/Cgi/options.py:900 +#, fuzzy msgid "%(days)d %(units)s" msgstr "%(days)d %(units)s" @@ -2873,6 +2960,7 @@ msgid "No topics defined" msgstr "Nenhum tópico definido" #: Mailman/Cgi/options.py:940 +#, fuzzy msgid "" "\n" "You are subscribed to this list with the case-preserved address\n" @@ -2883,6 +2971,7 @@ msgstr "" "%(cpuser)s." #: Mailman/Cgi/options.py:956 +#, fuzzy msgid "%(realname)s list: member options login page" msgstr "lista %(realname)s: página de login das opções do membro" @@ -2891,10 +2980,12 @@ msgid "email address and " msgstr "endereço de email e " #: Mailman/Cgi/options.py:960 +#, fuzzy msgid "%(realname)s list: member options for user %(safeuser)s" msgstr "lista %(realname)s: opções do membro para o usuário %(safeuser)s" #: Mailman/Cgi/options.py:986 +#, fuzzy msgid "" "In order to change your membership option, you must\n" " first log in by giving your %(extra)smembership password in the section\n" @@ -2970,6 +3061,7 @@ msgid "" msgstr "" #: Mailman/Cgi/options.py:1140 +#, fuzzy msgid "Requested topic is not valid: %(topicname)s" msgstr "O tópico requisitado não é válido: %(topicname)s" @@ -2998,6 +3090,7 @@ msgid "Private archive - \"./\" and \"../\" not allowed in URL." msgstr "Arquivo privado - \"./\" e \"../\" não são permitidos na URL." #: Mailman/Cgi/private.py:109 +#, fuzzy msgid "Private Archive Error - %(msg)s" msgstr "Erro no Arquivo privado - %(msg)s" @@ -3018,6 +3111,7 @@ msgid "Private archive file not found" msgstr "Arquivo privado arquivo não encontrado" #: Mailman/Cgi/rmlist.py:76 +#, fuzzy msgid "No such list %(safelistname)s" msgstr "A lista %(safelistname)s não existe" @@ -3034,6 +3128,7 @@ msgid "Mailing list deletion results" msgstr "Resultados de exclusão da lista de discussão" #: Mailman/Cgi/rmlist.py:187 +#, fuzzy msgid "" "You have successfully deleted the mailing list\n" " %(listname)s." @@ -3042,6 +3137,7 @@ msgstr "" " %(listname)s." #: Mailman/Cgi/rmlist.py:191 +#, fuzzy msgid "" "There were some problems deleting the mailing list\n" " %(listname)s. Contact your site administrator at " @@ -3053,10 +3149,12 @@ msgstr "" " para detalhes." #: Mailman/Cgi/rmlist.py:208 +#, fuzzy msgid "Permanently remove mailing list %(realname)s" msgstr "Remover permanentemente a lista de discussão %(realname)s" #: Mailman/Cgi/rmlist.py:209 +#, fuzzy msgid "Permanently remove mailing list %(realname)s" msgstr "Exclui permanentemente a lista de email %(realname)s" @@ -3126,6 +3224,7 @@ msgid "Invalid options to CGI script" msgstr "Opções inválidas para o script CGI" #: Mailman/Cgi/roster.py:118 +#, fuzzy msgid "%(realname)s roster authentication failed." msgstr "%(realname)s falha de autenticação na lista." @@ -3195,6 +3294,7 @@ msgstr "" "logo um e-mail de confirmação que trará instruções futuras." #: Mailman/Cgi/subscribe.py:262 +#, fuzzy msgid "" "The email address you supplied is banned from this\n" " mailing list. If you think this restriction is erroneous, please\n" @@ -3221,6 +3321,7 @@ msgstr "" "você forneceu é inseguro." #: Mailman/Cgi/subscribe.py:278 +#, fuzzy msgid "" "Confirmation from your email address is required, to prevent anyone from\n" "subscribing you without permission. Instructions are being sent to you at\n" @@ -3235,6 +3336,7 @@ msgstr "" "confirme sua inscrição na lista." #: Mailman/Cgi/subscribe.py:290 +#, fuzzy msgid "" "Your subscription request was deferred because %(x)s. Your request has " "been\n" @@ -3255,6 +3357,7 @@ msgid "Mailman privacy alert" msgstr "Alerta de privacidade do Mailman" #: Mailman/Cgi/subscribe.py:316 +#, fuzzy msgid "" "An attempt was made to subscribe your address to the mailing list\n" "%(listaddr)s. You are already subscribed to this mailing list.\n" @@ -3296,6 +3399,7 @@ msgid "This list only supports digest delivery." msgstr "Esta lista somente suporta entregas usando digest." #: Mailman/Cgi/subscribe.py:344 +#, fuzzy msgid "You have been successfully subscribed to the %(realname)s mailing list." msgstr "Você foi inscrito com sucesso na lista de discussão %(realname)s." @@ -3320,6 +3424,7 @@ msgid "Usage:" msgstr "Uso:" #: Mailman/Commands/cmd_confirm.py:50 +#, fuzzy msgid "" "Invalid confirmation string. Note that confirmation strings expire\n" "approximately %(days)s days after the initial request. They also expire if\n" @@ -3346,6 +3451,7 @@ msgstr "" "endereço de email?" #: Mailman/Commands/cmd_confirm.py:69 +#, fuzzy msgid "" "You are currently banned from subscribing to this list. If you think this\n" "restriction is erroneous, please contact the list owners at\n" @@ -3427,26 +3533,32 @@ msgid "n/a" msgstr "n/d" #: Mailman/Commands/cmd_info.py:44 +#, fuzzy msgid "List name: %(listname)s" msgstr "Nome da Lista: %(listname)s" #: Mailman/Commands/cmd_info.py:45 +#, fuzzy msgid "Description: %(description)s" msgstr "Descrição: %(description)s" #: Mailman/Commands/cmd_info.py:46 +#, fuzzy msgid "Postings to: %(postaddr)s" msgstr "Postando para: %(postaddr)s" #: Mailman/Commands/cmd_info.py:47 +#, fuzzy msgid "List Helpbot: %(requestaddr)s" msgstr "Ajuda da Lista: %(requestaddr)s" #: Mailman/Commands/cmd_info.py:48 +#, fuzzy msgid "List Owners: %(owneraddr)s" msgstr "Donos da Lista: %(owneraddr)s" #: Mailman/Commands/cmd_info.py:49 +#, fuzzy msgid "More information: %(listurl)s" msgstr "Mais detalhes: %(listurl)s" @@ -3470,18 +3582,22 @@ msgstr "" "Mailman.\n" #: Mailman/Commands/cmd_lists.py:44 +#, fuzzy msgid "Public mailing lists at %(hostname)s:" msgstr "Listas de discussão públicas em %(hostname)s:" #: Mailman/Commands/cmd_lists.py:66 +#, fuzzy msgid "%(i)3d. List name: %(realname)s" msgstr "%(i)3d. Nome da lista: %(realname)s" #: Mailman/Commands/cmd_lists.py:67 +#, fuzzy msgid " Description: %(description)s" msgstr " Descrição: %(description)s" #: Mailman/Commands/cmd_lists.py:68 +#, fuzzy msgid " Requests to: %(requestaddr)s" msgstr " Requisitar a: %(requestaddr)s" @@ -3516,12 +3632,14 @@ msgstr "" " o endereço inscrito.\n" #: Mailman/Commands/cmd_password.py:51 Mailman/Commands/cmd_password.py:66 +#, fuzzy msgid "Your password is: %(password)s" msgstr "Sua senha é: %(password)s" #: Mailman/Commands/cmd_password.py:57 Mailman/Commands/cmd_password.py:72 #: Mailman/Commands/cmd_password.py:95 Mailman/Commands/cmd_password.py:121 #: Mailman/Commands/cmd_set.py:149 Mailman/Commands/cmd_set.py:219 +#, fuzzy msgid "You are not a member of the %(listname)s mailing list" msgstr "Você não é um membro da lista de discussão %(listname)s" @@ -3707,6 +3825,7 @@ msgstr "" " para esta lista de discussão.\n" #: Mailman/Commands/cmd_set.py:122 +#, fuzzy msgid "Bad set command: %(subcmd)s" msgstr "Comando ajustado incorretamente: %(subcmd)s" @@ -3727,6 +3846,7 @@ msgid "on" msgstr "ativado" #: Mailman/Commands/cmd_set.py:154 +#, fuzzy msgid " ack %(onoff)s" msgstr " reconhecimento %(onoff)s" @@ -3764,22 +3884,27 @@ msgid "due to bounces" msgstr "devido a mensagens retornadas" #: Mailman/Commands/cmd_set.py:186 +#, fuzzy msgid " %(status)s (%(how)s on %(date)s)" msgstr " %(status)s (%(how)s em %(date)s)" #: Mailman/Commands/cmd_set.py:192 +#, fuzzy msgid " myposts %(onoff)s" msgstr " minhas postagens %(onoff)s" #: Mailman/Commands/cmd_set.py:195 +#, fuzzy msgid " hide %(onoff)s" msgstr " ocultar %(onoff)s" #: Mailman/Commands/cmd_set.py:199 +#, fuzzy msgid " duplicates %(onoff)s" msgstr " duplicadas %(onoff)s" #: Mailman/Commands/cmd_set.py:203 +#, fuzzy msgid " reminders %(onoff)s" msgstr " lembretes %(onoff)s" @@ -3788,6 +3913,7 @@ msgid "You did not give the correct password" msgstr "Você não forneceu a senha incorreta" #: Mailman/Commands/cmd_set.py:236 Mailman/Commands/cmd_set.py:283 +#, fuzzy msgid "Bad argument: %(arg)s" msgstr "Argumento incorreto: %(arg)s" @@ -3862,6 +3988,7 @@ msgstr "" " de email e sem aspas!).\n" #: Mailman/Commands/cmd_subscribe.py:62 +#, fuzzy msgid "Bad digest specifier: %(arg)s" msgstr "Especificador digest incorreto: %(arg)s" @@ -3870,6 +3997,7 @@ msgid "No valid address found to subscribe" msgstr "Nenhum endereço válido foi encontrado para se inscrever" #: Mailman/Commands/cmd_subscribe.py:113 +#, fuzzy msgid "" "The email address you supplied is banned from this mailing list.\n" "If you think this restriction is erroneous, please contact the list\n" @@ -3908,6 +4036,7 @@ msgid "This list only supports digest subscriptions!" msgstr "Esta lista somente suporta inscrições digest!" #: Mailman/Commands/cmd_subscribe.py:146 +#, fuzzy msgid "" "Your subscription request has been forwarded to the list administrator\n" "at %(listowner)s for review." @@ -3944,6 +4073,7 @@ msgstr "" " (sem chaves em torno do endereço de email, e sem aspas).\n" #: Mailman/Commands/cmd_unsubscribe.py:62 +#, fuzzy msgid "%(address)s is not a member of the %(listname)s mailing list" msgstr "%(address)s não é um membro da lista de discussão %(listname)s" @@ -4194,6 +4324,7 @@ msgid "Chinese (Taiwan)" msgstr "Chinês (Taiwan)" #: Mailman/Deliverer.py:53 +#, fuzzy msgid "" "Note: Since this is a list of mailing lists, administrative\n" "notices like the password reminder will be sent to\n" @@ -4208,14 +4339,17 @@ msgid " (Digest mode)" msgstr " (modo Digest)" #: Mailman/Deliverer.py:79 +#, fuzzy msgid "Welcome to the \"%(realname)s\" mailing list%(digmode)s" msgstr "Bem-vindo a lista de discussão \"%(realname)s\" %(digmode)s" #: Mailman/Deliverer.py:89 +#, fuzzy msgid "You have been unsubscribed from the %(realname)s mailing list" msgstr "Você se desinscreveu da lista de discussão %(realname)s" #: Mailman/Deliverer.py:116 +#, fuzzy msgid "%(listfullname)s mailing list reminder" msgstr "lembrete da lista de discussão %(listfullname)s" @@ -4228,6 +4362,7 @@ msgid "Hostile subscription attempt detected" msgstr "Tentativa de inscrição hostil detectada" #: Mailman/Deliverer.py:169 +#, fuzzy msgid "" "%(address)s was invited to a different mailing\n" "list, but in a deliberate malicious attempt they tried to confirm the\n" @@ -4240,6 +4375,7 @@ msgstr "" "seu conhecimento. Nenhuma ação por sua parte é requerida." #: Mailman/Deliverer.py:188 +#, fuzzy msgid "" "You invited %(address)s to your list, but in a\n" "deliberate malicious attempt, they tried to confirm the invitation to a\n" @@ -4253,6 +4389,7 @@ msgstr "" "para seu conhecimento. Nenhuma ação por sua parte é requerida." #: Mailman/Deliverer.py:221 +#, fuzzy msgid "%(listname)s mailing list probe message" msgstr "mensagem de teste da lista de discussão %(listname)s" @@ -4459,8 +4596,8 @@ msgid "" " membership.\n" "\n" "

                    You can control both the\n" -" number\n" +" number\n" " of reminders the member will receive and the\n" " No those messages too will get discarded. You may " "want\n" " to set up an\n" -" autoresponse\n" +" autoresponse\n" " message for email to the -owner and -admin address." msgstr "" "Enquanto o detector de bounce do Mailman é muito robusto,\n" @@ -4692,8 +4829,8 @@ msgstr "" " podem ainda querer enviar mensagens para este endereço. Se isto\n" " acontecer e esta variável estiver ajustada para Não \n" " estes emails também serão descartados. Você pode querer \n" -" configurar uma \n" +" configurar uma \n" " mensagem de auto-resposta para os endereços de email \n" " -owner e -admin." @@ -4766,6 +4903,7 @@ msgstr "" " tentativa de notificar o membro sempre será feita." #: Mailman/Gui/Bounce.py:194 +#, fuzzy msgid "" "Bad value for %(property)s: %(val)s" @@ -4853,8 +4991,8 @@ msgstr "" "

                    Finalmente, qualquer parte text/html que for \n" " deixada na mensagem pode ser convertida para text/plain\n" -" caso \n" +" caso \n" " convert_html_to_plaintext esteja ativado e o site estiver\n" " configurado para permitir estas conversões." @@ -4897,8 +5035,8 @@ msgstr "" " e.g. image.\n" " \n" "

                    Linhas em branco são ignoradas.\n" -"

                    Veja também Veja também pass_mime_types para uma lista de tipos de conteúdo." #: Mailman/Gui/ContentFilter.py:94 @@ -4915,8 +5053,8 @@ msgid "" "Use this option to remove each message attachment that does\n" " not have a matching content type. Requirements and formats " "are\n" -" exactly like filter_mime_types.\n" "\n" "

                    Note: if you add entries to this list but don't add\n" @@ -5033,6 +5171,7 @@ msgstr "" " administrador do site." #: Mailman/Gui/ContentFilter.py:171 +#, fuzzy msgid "Bad MIME type ignored: %(spectype)s" msgstr "Tipo MIME incorreto ignorado: %(spectype)s" @@ -5140,6 +5279,7 @@ msgid "" msgstr "O Mailman deve enviar o próximo digest agora, caso não esteja vazio?" #: Mailman/Gui/Digest.py:145 +#, fuzzy msgid "" "The next digest will be sent as volume\n" " %(volume)s, number %(number)s" @@ -5156,14 +5296,17 @@ msgid "There was no digest to send." msgstr "Não existem digests para serem enviados." #: Mailman/Gui/GUIBase.py:173 +#, fuzzy msgid "Invalid value for variable: %(property)s" msgstr "Valor inválido para a variável: %(property)s" #: Mailman/Gui/GUIBase.py:178 +#, fuzzy msgid "Bad email address for option %(property)s: %(error)s" msgstr "Endereço de email errado para a opção %(property)s: %(error)s" #: Mailman/Gui/GUIBase.py:204 +#, fuzzy msgid "" "The following illegal substitution variables were\n" " found in the %(property)s string:\n" @@ -5179,6 +5322,7 @@ msgstr "" " problema." #: Mailman/Gui/GUIBase.py:218 +#, fuzzy msgid "" "Your %(property)s string appeared to\n" " have some correctable problems in its new value.\n" @@ -5280,8 +5424,8 @@ msgid "" "

                    In order to split the list ownership duties into\n" " administrators and moderators, you must\n" " set a separate moderator password,\n" -" and also provide the email\n" +" and also provide the email\n" " addresses of the list moderators. Note that the field you\n" " are changing here specifies the list administrators." msgstr "" @@ -5305,8 +5449,8 @@ msgstr "" " e moderadores, você deve\n" " definir uma senha separada para moderador,\n" -" e também fornecer um endereço\n" +" e também fornecer um endereço\n" " de email para os moderadores da lista. Note que o campo\n" " que está modificando aqui especifica os administradores da lista." @@ -5560,8 +5704,8 @@ msgstr "" "mensagens onde o\n" " domínio no campo From: do cabeçalho está determinado a usar tal " "protocolo,\n" -" ver as\n" +" ver as\n" " dmarc_moderation_action configurações em opções de " "privacidade...\n" " -> Filtros de remetente.\n" @@ -5694,13 +5838,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5742,13 +5886,13 @@ msgstr "" "             endereço de devolução. Outra é que ao modificar o campo " "Reply-To: \n" "             torna muito mais difícil enviar respostas privadas. Veja ` Reply-To '\n" +"             href = \"http://marc.merlins.org/netrants/reply-to-harmful." +"html\"> ` Reply-To '\n" "             Munging é considerado prejudicial para uma discussão geral " "sobre\n" "             o assunto. Veja \n" +"             href = \"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" "              Reply-To: Munging é útil para uma opinião divergente.\n" "\n" "             

                    Algumas listas de discussão restringem os privilégios de " @@ -5773,8 +5917,8 @@ msgstr "Cabe msgid "" "This is the address set in the Reply-To: header\n" " when the reply_goes_to_list\n" +" href=\"?VARHELP=general/" +"reply_goes_to_list\">reply_goes_to_list\n" " option is set to Explicit address.\n" "\n" "

                    There are many reasons not to introduce or override the\n" @@ -5782,13 +5926,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5823,13 +5967,13 @@ msgstr "" "             endereço de devolução. Outra é que ao modificar o campo " "Reply-To: \n" "             torna muito mais difícil enviar respostas privadas. Veja ` Reply-To '\n" +"             href = \"http://marc.merlins.org/netrants/reply-to-harmful." +"html\"> ` Reply-To '\n" "             Munging é considerado prejudicial para uma discussão geral " "sobre\n" "             o assunto. Veja \n" +"             href = \"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" "              Reply-To: Munging é útil para uma opinião divergente.\n" "\n" "             

                    Algumas listas de discussão restringem os privilégios de " @@ -5899,8 +6043,8 @@ msgid "" " member list addresses, but rather to the owner of those member\n" " lists. In that case, the value of this setting is appended to\n" " the member's account name for such notices. `-owner' is the\n" -" typical choice. This setting has no effect when \"umbrella_list" -"\"\n" +" typical choice. This setting has no effect when " +"\"umbrella_list\"\n" " is \"No\"." msgstr "" "Quando \"umbrella_list\" é ajustado para indicar que esta lista tem outras\n" @@ -6897,6 +7041,7 @@ msgstr "" " (ou maliciosas) de criar inscrições sem seu consentimento." #: Mailman/Gui/Privacy.py:104 +#, fuzzy msgid "" "This section allows you to configure subscription and\n" " membership exposure policy. You can also control whether this\n" @@ -7096,8 +7241,8 @@ msgid "" " either individually or as a group. Any\n" " posting from a non-member who is not explicitly accepted,\n" " rejected, or discarded, will have their posting filtered by the\n" -" general\n" +" general\n" " non-member rules.\n" "\n" "

                    In the text boxes below, add one address per line; start the\n" @@ -7120,20 +7265,20 @@ msgstr "" " a moderação estiver ligada. Você pode controlar se\n" " postagens de membros serão moderadas por padrão ou não." "

                    Postagens de não-membros podem ser automaticamente\n" -" aceitas,\n" -" retidas para\n" +" aceitas,\n" +" retidas para\n" " moderação,\n" -" rejeitadas (retornadas), ou\n" -" descartadas,\n" +" rejeitadas (retornadas), ou\n" +" descartadas,\n" " individualmente ou agrupadas. Qualquer\n" " postagem de um não-membro que não é explicitamente aceita,\n" " rejeitada, ou descartada, terá sua postagem filtrada pelo\n" -" regras\n" +" regras\n" " gerais para não membros.

                    Nas caixas de texto abaixo, " "adicione um endereço por linha; comece a\n" " linha com um caractere ^ para designar uma moderation flag which says\n" " whether messages from the list member can be posted directly " @@ -7210,8 +7356,8 @@ msgid "" "If a member posts this many times, within a period of time\n" " the member is automatically moderated. Use 0 to disable. " "See\n" -" member_verbosity_interval for details on the time " "period.\n" "\n" @@ -7228,8 +7374,9 @@ msgstr "" "Se um membro enviar muitas mensagens, dentro de um intervalo de tempo\n" " esse será automaticamente moderado. Use 0 para desativar. " "Veja\n" -" member_verbosity_interval para detalhes sobre o intervalo de tempo.\n" +" member_verbosity_interval para detalhes " +"sobre o intervalo de tempo.\n" "\n" "

                    Objetivo é evitar pessoas que se ingressam em uma lista ou " "listas e\n" @@ -7326,6 +7473,7 @@ msgstr "" " membros moderados quando postar a esta lista." #: Mailman/Gui/Privacy.py:290 +#, fuzzy msgid "" "Action to take when anyone posts to the\n" " list from a domain with a DMARC Reject%(quarantine)s Policy." @@ -7475,6 +7623,7 @@ msgstr "" " a política dmarc do domínio forem mais fortes." #: Mailman/Gui/Privacy.py:353 +#, fuzzy msgid "" "Text to include in any\n" " \n" " por em " "espera\n" -" rejeitado\n" -" e rejeitado\n" +" e descartados. Se nenhuma conferência for encontrada, " "então\n" " esta ação é tomada." @@ -7802,6 +7951,7 @@ msgstr "" " devem ser redirecionadas ao moderador da lista?" #: Mailman/Gui/Privacy.py:494 +#, fuzzy msgid "" "Text to include in any rejection notice to be sent to\n" " non-members who post to this list. This notice can include\n" @@ -8059,6 +8209,7 @@ msgstr "" " Filtros incompletos serão ignorados." #: Mailman/Gui/Privacy.py:693 +#, fuzzy msgid "" "The header filter rule pattern\n" " '%(safepattern)s' is not a legal regular expression. This\n" @@ -8109,8 +8260,8 @@ msgid "" "

                    The body of the message can also be optionally scanned for\n" " Subject: and Keywords: headers, as\n" " specified by the topics_bodylines_limit\n" +" href=\"?VARHELP=topics/" +"topics_bodylines_limit\">topics_bodylines_limit\n" " configuration variable." msgstr "" "O filtro de tópico categoriza cada mensagem de email recebida\n" @@ -8210,6 +8361,7 @@ msgstr "" " Tópicos incompletos serão ignorados." #: Mailman/Gui/Topics.py:135 +#, fuzzy msgid "" "The topic pattern '%(safepattern)s' is not a\n" " legal regular expression. It will be discarded." @@ -8438,6 +8590,7 @@ msgid "%(listinfo_link)s list run by %(owner_link)s" msgstr "A lista %(listinfo_link)s é administrada por %(owner_link)s" #: Mailman/HTMLFormatter.py:57 +#, fuzzy msgid "%(realname)s administrative interface" msgstr "Interface administrativa de %(realname)s" @@ -8446,6 +8599,7 @@ msgid " (requires authorization)" msgstr " (requer autorização)" #: Mailman/HTMLFormatter.py:61 +#, fuzzy msgid "Overview of all %(hostname)s mailing lists" msgstr "Relação de todas as listas de discussão em %(hostname)s" @@ -8466,6 +8620,7 @@ msgid "; it was disabled by the list administrator" msgstr "; foi desativado pelo administrador da lista" #: Mailman/HTMLFormatter.py:146 +#, fuzzy msgid "" "; it was disabled due to excessive bounces. The\n" " last bounce was received on %(date)s" @@ -8478,6 +8633,7 @@ msgid "; it was disabled for unknown reasons" msgstr "; foi desativado por razões desconhecidas" #: Mailman/HTMLFormatter.py:151 +#, fuzzy msgid "Note: your list delivery is currently disabled%(reason)s." msgstr "Nota: a entrega de sua lista está atualmente desativada%(reason)s." @@ -8490,6 +8646,7 @@ msgid "the list administrator" msgstr "o administrador da lista" #: Mailman/HTMLFormatter.py:157 +#, fuzzy msgid "" "

                    %(note)s\n" "\n" @@ -8510,6 +8667,7 @@ msgstr "" " de assistência." #: Mailman/HTMLFormatter.py:169 +#, fuzzy msgid "" "

                    We have received some recent bounces from your\n" " address. Your current bounce score is %(score)s out of " @@ -8529,6 +8687,7 @@ msgstr "" " breve." #: Mailman/HTMLFormatter.py:181 +#, fuzzy msgid "" "(Note - you are subscribing to a list of mailing lists, so the %(type)s " "notice will be sent to the admin address for your membership, %(addr)s.)

                    " @@ -8576,6 +8735,7 @@ msgstr "" " Você será notificado da decisão do moderador por email." #: Mailman/HTMLFormatter.py:208 +#, fuzzy msgid "" "This is %(also)sa private list, which means that the\n" " list of members is not available to non-members." @@ -8584,6 +8744,7 @@ msgstr "" " não está disponível para não-membros." #: Mailman/HTMLFormatter.py:211 +#, fuzzy msgid "" "This is %(also)sa hidden list, which means that the\n" " list of members is available only to the list administrator." @@ -8592,6 +8753,7 @@ msgstr "" " está somente disponível para o administrador da lista." #: Mailman/HTMLFormatter.py:214 +#, fuzzy msgid "" "This is %(also)sa public list, which means that the\n" " list of members list is available to everyone." @@ -8608,6 +8770,7 @@ msgstr "" " reconhecidos por spammers)." #: Mailman/HTMLFormatter.py:222 +#, fuzzy msgid "" "

                    (Note that this is an umbrella list, intended to\n" " have only other mailing lists as members. Among other things,\n" @@ -8624,6 +8787,7 @@ msgid "either " msgstr "ou " #: Mailman/HTMLFormatter.py:256 +#, fuzzy msgid "" "To unsubscribe from %(realname)s, get a password reminder,\n" " or change your subscription options %(either)senter your " @@ -8656,6 +8820,7 @@ msgstr "" " email" #: Mailman/HTMLFormatter.py:277 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " members.)" @@ -8664,6 +8829,7 @@ msgstr "" " da lista.)" #: Mailman/HTMLFormatter.py:281 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " administrator.)" @@ -8724,6 +8890,7 @@ msgid "The current archive" msgstr "O arquivo atual" #: Mailman/Handlers/Acknowledge.py:59 +#, fuzzy msgid "%(realname)s post acknowledgement" msgstr "reconhecimento de postagem de %(realname)s" @@ -8740,6 +8907,7 @@ msgstr "" "HTML, ele não pode ser removido com segurança.\n" #: Mailman/Handlers/CalcRecips.py:79 +#, fuzzy msgid "" "Your urgent message to the %(realname)s mailing list was not authorized for\n" "delivery. The original message as received by Mailman is attached.\n" @@ -8749,6 +8917,7 @@ msgstr "" "pelo Mailman está em anexo.\n" #: Mailman/Handlers/CookHeaders.py:180 +#, fuzzy msgid "%(realname)s via %(lrn)s" msgstr "%(realname)s via %(lrn)s" @@ -8816,6 +8985,7 @@ msgid "Message may contain administrivia" msgstr "A mensagem pode conter questões administrativas" #: Mailman/Handlers/Hold.py:84 +#, fuzzy msgid "" "Please do *not* post administrative requests to the mailing\n" "list. If you wish to subscribe, visit %(listurl)s or send a message with " @@ -8858,10 +9028,12 @@ msgid "Posting to a moderated newsgroup" msgstr "Postagem para um newsgroup moderado" #: Mailman/Handlers/Hold.py:252 +#, fuzzy msgid "Your message to %(listname)s awaits moderator approval" msgstr "Sua mensagem para a lista %(listname)s aguarda aprovação" #: Mailman/Handlers/Hold.py:271 +#, fuzzy msgid "%(listname)s post from %(sender)s requires approval" msgstr "A postagem de %(sender)s para a lista %(listname)s requer aprovação" @@ -8905,6 +9077,7 @@ msgid "After content filtering, the message was empty" msgstr "Após a filtragem de conteúdo, a mensagem foi perdida" #: Mailman/Handlers/MimeDel.py:269 +#, fuzzy msgid "" "The attached message matched the %(listname)s mailing list's content " "filtering\n" @@ -8925,6 +9098,7 @@ msgid "Content filtered message notification" msgstr "Notificação de filtragem de conteúdo da mensagem" #: Mailman/Handlers/Moderate.py:145 +#, fuzzy msgid "" "Your message has been rejected, probably because you are not subscribed to " "the\n" @@ -8950,6 +9124,7 @@ msgid "The attached message has been automatically discarded." msgstr "A mensagem em anexo foi descartada automaticamente." #: Mailman/Handlers/Replybot.py:75 +#, fuzzy msgid "Auto-response for your message to the \"%(realname)s\" mailing list" msgstr "" "Auto-resposta de sua mensagem para a lista de discussão \"%(realname)s\"" @@ -8959,6 +9134,7 @@ msgid "The Mailman Replybot" msgstr "O robô de resposta do Mailman" #: Mailman/Handlers/Scrubber.py:208 +#, fuzzy msgid "" "An embedded and charset-unspecified text was scrubbed...\n" "Name: %(filename)s\n" @@ -8973,6 +9149,7 @@ msgid "HTML attachment scrubbed and removed" msgstr "Anexo em HTML limpo e removido" #: Mailman/Handlers/Scrubber.py:234 Mailman/Handlers/Scrubber.py:259 +#, fuzzy msgid "" "An HTML attachment was scrubbed...\n" "URL: %(url)s\n" @@ -8993,6 +9170,7 @@ msgid "unknown sender" msgstr "remetente desconhecido" #: Mailman/Handlers/Scrubber.py:276 +#, fuzzy msgid "" "An embedded message was scrubbed...\n" "From: %(who)s\n" @@ -9009,6 +9187,7 @@ msgstr "" "URL: %(url)s\n" #: Mailman/Handlers/Scrubber.py:308 +#, fuzzy msgid "" "A non-text attachment was scrubbed...\n" "Name: %(filename)s\n" @@ -9025,6 +9204,7 @@ msgstr "" "URL: %(url)s\n" #: Mailman/Handlers/Scrubber.py:347 +#, fuzzy msgid "Skipped content of type %(partctype)s\n" msgstr "Conteúdo pulado do tipo %(partctype)s\n" @@ -9033,10 +9213,12 @@ msgid "-------------- next part --------------\n" msgstr "-------------- Próxima Parte ----------\n" #: Mailman/Handlers/SpamDetect.py:64 +#, fuzzy msgid "Header matched regexp: %(pattern)s" msgstr "Cabeçalho correspondente com expressão regular: %(pattern)s" #: Mailman/Handlers/SpamDetect.py:127 +#, fuzzy msgid "" "You are not allowed to post to this mailing list From: a domain which\n" "publishes a DMARC policy of reject or quarantine, and your message has been\n" @@ -9057,6 +9239,7 @@ msgid "Message rejected by filter rule match" msgstr "Mensagem rejeitada pela regra de filtro" #: Mailman/Handlers/ToDigest.py:173 +#, fuzzy msgid "%(realname)s Digest, Vol %(volume)d, Issue %(issue)d" msgstr "Digest %(realname)s, volume %(volume)d, assunto %(issue)d" @@ -9093,6 +9276,7 @@ msgid "End of " msgstr "Fim da " #: Mailman/ListAdmin.py:309 +#, fuzzy msgid "Posting of your message titled \"%(subject)s\"" msgstr "Envio da mensagem intitulada \"%(subject)s\"" @@ -9105,6 +9289,7 @@ msgid "Forward of moderated message" msgstr "Encaminhamento de mensagem moderada" #: Mailman/ListAdmin.py:405 +#, fuzzy msgid "New subscription request to list %(realname)s from %(addr)s" msgstr "Nova requisição de inscrição de %(realname)s pelo %(addr)s" @@ -9117,6 +9302,7 @@ msgid "via admin approval" msgstr "via aprovação do administrador" #: Mailman/ListAdmin.py:466 +#, fuzzy msgid "New unsubscription request from %(realname)s by %(addr)s" msgstr "Nova requisição de remoção de %(realname)s por %(addr)s" @@ -9129,10 +9315,12 @@ msgid "Original Message" msgstr "Mensagem Original" #: Mailman/ListAdmin.py:526 +#, fuzzy msgid "Request to mailing list %(realname)s rejected" msgstr "Requisição para a lista de discussão %(realname)s rejeitada" #: Mailman/MTA/Manual.py:66 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been created via the through-the-web\n" "interface. In order to complete the activation of this mailing list, the\n" @@ -9159,14 +9347,17 @@ msgstr "" "e possivelmente executando o programa 'newaliases':\n" #: Mailman/MTA/Manual.py:82 +#, fuzzy msgid "## %(listname)s mailing list" msgstr "a lista de discussão \"%(listname)s" #: Mailman/MTA/Manual.py:99 +#, fuzzy msgid "Mailing list creation request for list %(listname)s" msgstr "Requisição de criação de lista de discussão para a lista %(listname)s" #: Mailman/MTA/Manual.py:113 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been removed via the through-the-web\n" "interface. In order to complete the de-activation of this mailing list, " @@ -9184,6 +9375,7 @@ msgstr "" "Aqui estão as entradas no arquivo /etc/aliases que deverão ser removidos:\n" #: Mailman/MTA/Manual.py:123 +#, fuzzy msgid "" "\n" "To finish removing your mailing list, you must edit your /etc/aliases (or\n" @@ -9200,14 +9392,17 @@ msgstr "" "## lista de discussão %(listname)s" #: Mailman/MTA/Manual.py:142 +#, fuzzy msgid "Mailing list removal request for list %(listname)s" msgstr "Requisição de remoção da lista de discussão %(listname)s" #: Mailman/MTA/Postfix.py:442 +#, fuzzy msgid "checking permissions on %(file)s" msgstr "verificando permissões de %(file)s" #: Mailman/MTA/Postfix.py:452 +#, fuzzy msgid "%(file)s permissions must be 0664 (got %(octmode)s)" msgstr "%(file)s permissões devem ser 0664 (usar %(octmode)s)" @@ -9221,37 +9416,45 @@ msgid "(fixing)" msgstr "(corrigindo)" #: Mailman/MTA/Postfix.py:470 +#, fuzzy msgid "checking ownership of %(dbfile)s" msgstr "verificando o dono de %(dbfile)s" #: Mailman/MTA/Postfix.py:478 +#, fuzzy msgid "%(dbfile)s owned by %(owner)s (must be owned by %(user)s" msgstr "%(dbfile)s tem como dono %(owner)s (deveria ser %(user)s)" #: Mailman/MTA/Postfix.py:491 +#, fuzzy msgid "%(dbfile)s permissions must be 0664 (got %(octmode)s)" msgstr "%(dbfile)s permissões devem ser 0664 (usar %(octmode)s)" #: Mailman/MailList.py:219 +#, fuzzy msgid "Your confirmation is required to join the %(listname)s mailing list" msgstr "" "É requerida sua confirmação para entrar para a lista de discussão " "%(listname)s" #: Mailman/MailList.py:230 +#, fuzzy msgid "Your confirmation is required to leave the %(listname)s mailing list" msgstr "" "É requerida sua confirmação para deixar a lista de discussão %(listname)s" #: Mailman/MailList.py:999 Mailman/MailList.py:1492 +#, fuzzy msgid " from %(remote)s" msgstr " de %(remote)s" #: Mailman/MailList.py:1043 +#, fuzzy msgid "subscriptions to %(realname)s require moderator approval" msgstr "as inscrições de %(realname)s requerem aprovação pelo moderador" #: Mailman/MailList.py:1125 bin/add_members:299 +#, fuzzy msgid "%(realname)s subscription notification" msgstr "notificação de inscrição de %(realname)s" @@ -9260,10 +9463,12 @@ msgid "unsubscriptions require moderator approval" msgstr "as desinscrições requerem aprovação pelo moderador" #: Mailman/MailList.py:1166 +#, fuzzy msgid "%(realname)s unsubscribe notification" msgstr "notificação de remoção de %(realname)s" #: Mailman/MailList.py:1328 +#, fuzzy msgid "%(realname)s address change notification" msgstr "Notificação de alteração de endereço %(realname)s" @@ -9276,6 +9481,7 @@ msgid "via web confirmation" msgstr "via confirmação web" #: Mailman/MailList.py:1396 +#, fuzzy msgid "subscriptions to %(name)s require administrator approval" msgstr "as inscrições de %(name)s requerem aprovação pelo administrador" @@ -9292,6 +9498,7 @@ msgid "Last autoresponse notification for today" msgstr "Última notificação de auto-resposta de hoje" #: Mailman/Queue/BounceRunner.py:360 +#, fuzzy msgid "" "The attached message was received as a bounce, but either the bounce format\n" "was not recognized, or no member addresses could be extracted from it. " @@ -9380,6 +9587,7 @@ msgid "Original message suppressed by Mailman site configuration\n" msgstr "Mensagem original suprimida pela configuração do Mailman\n" #: Mailman/htmlformat.py:675 +#, fuzzy msgid "Delivered by Mailman
                    version %(version)s" msgstr "Entregue pelo Mailman
                    versão %(version)s" @@ -9468,6 +9676,7 @@ msgid "Server Local Time" msgstr "Hora local do Servidor" #: Mailman/i18n.py:180 +#, fuzzy msgid "" "%(wday)s %(mon)s %(day)2i %(hh)02i:%(mm)02i:%(ss)02i %(tzname)s %(year)04i" msgstr "" @@ -9603,6 +9812,7 @@ msgstr "" "arquivos pode ser '-'.\n" #: bin/add_members:162 bin/add_members:172 +#, fuzzy msgid "Already a member: %(member)s" msgstr "Já é um membro: %(member)s" @@ -9611,26 +9821,32 @@ msgid "Bad/Invalid email address: blank line" msgstr "Endereço de email incorreto/inválido: linha em branco" #: bin/add_members:180 +#, fuzzy msgid "Bad/Invalid email address: %(member)s" msgstr "Endereço de email incorreto/inválido: %(member)s" #: bin/add_members:182 +#, fuzzy msgid "Hostile address (illegal characters): %(member)s" msgstr "Endereço hostil (caracteres ilegais): %(member)s" #: bin/add_members:185 +#, fuzzy msgid "Invited: %(member)s" msgstr "Convidados: %(member)s" #: bin/add_members:187 +#, fuzzy msgid "Subscribed: %(member)s" msgstr "Inscrito: %(member)s" #: bin/add_members:237 +#, fuzzy msgid "Bad argument to -w/--welcome-msg: %(arg)s" msgstr "Argumento incorreto para -w/--welcome-msg: %(arg)s" #: bin/add_members:244 +#, fuzzy msgid "Bad argument to -a/--admin-notify: %(arg)s" msgstr "Argumento incorreto para -a/--admin-notify: %(arg)s" @@ -9646,6 +9862,7 @@ msgstr "Definir invite-msg-file requer --invite." #: bin/add_members:261 bin/config_list:110 bin/export.py:271 bin/find_member:97 #: bin/inject:91 bin/list_admins:90 bin/list_members:252 bin/sync_members:222 #: cron/bumpdigests:86 +#, fuzzy msgid "No such list: %(listname)s" msgstr "Lista inexistente: %(listname)s" @@ -9656,6 +9873,7 @@ msgid "Nothing to do." msgstr "Nada a ser feito." #: bin/arch:19 +#, fuzzy msgid "" "Rebuild a list's archive.\n" "\n" @@ -9749,6 +9967,7 @@ msgid "listname is required" msgstr "o nome da lista é requerido" #: bin/arch:143 bin/change_pw:107 bin/config_list:257 +#, fuzzy msgid "" "No such list \"%(listname)s\"\n" "%(e)s" @@ -9757,10 +9976,12 @@ msgstr "" "%(e)s" #: bin/arch:168 +#, fuzzy msgid "Cannot open mbox file %(mbox)s: %(msg)s" msgstr "Não foi possível abrir arquivo mbox %(mbox)s: %(msg)s" #: bin/b4b5-archfix:19 +#, fuzzy msgid "" "Fix the MM2.1b4 archives.\n" "\n" @@ -9904,6 +10125,7 @@ msgstr "" " Mostra esta mensagem de ajuda e sai.\n" #: bin/change_pw:145 +#, fuzzy msgid "Bad arguments: %(strargs)s" msgstr "Argumentos incorretos: %(strargs)s" @@ -9912,14 +10134,17 @@ msgid "Empty list passwords are not allowed" msgstr "Senhas vazias de listas não são permitidas" #: bin/change_pw:181 +#, fuzzy msgid "New %(listname)s password: %(notifypassword)s" msgstr "Nova %(listname)s senha: %(notifypassword)s" #: bin/change_pw:190 +#, fuzzy msgid "Your new %(listname)s list password" msgstr "Sua nova senha da lista %(listname)s" #: bin/change_pw:191 +#, fuzzy msgid "" "The site administrator at %(hostname)s has changed the password for your\n" "mailing list %(listname)s. It is now\n" @@ -9947,6 +10172,7 @@ msgstr "" " %(adminurl)s\n" #: bin/check_db:19 +#, fuzzy msgid "" "Check a list's config database file for integrity.\n" "\n" @@ -10022,10 +10248,12 @@ msgid "List:" msgstr "Lista:" #: bin/check_db:148 +#, fuzzy msgid " %(file)s: okay" msgstr " %(file)s: Ok" #: bin/check_perms:20 +#, fuzzy msgid "" "Check the permissions for the Mailman installation.\n" "\n" @@ -10045,44 +10273,54 @@ msgstr "" "forem encontrados. Com a opção -v, mostra mais detalhes.\n" #: bin/check_perms:110 +#, fuzzy msgid " checking gid and mode for %(path)s" msgstr " verificando gid e modo de %(path)s" #: bin/check_perms:122 +#, fuzzy msgid "%(path)s bad group (has: %(groupname)s, expected %(MAILMAN_GROUP)s)" msgstr "" "grupo inválido para %(path)s (encontrado: %(groupname)s, esperado " "%(MAILMAN_GROUP)s)" #: bin/check_perms:151 +#, fuzzy msgid "directory permissions must be %(octperms)s: %(path)s" msgstr "permissões do diretório devem ser %(octperms)s: %(path)s" #: bin/check_perms:160 +#, fuzzy msgid "source perms must be %(octperms)s: %(path)s" msgstr "as permissões do diretório fonte deve ser %(octperms)s: %(path)s" #: bin/check_perms:171 +#, fuzzy msgid "article db files must be %(octperms)s: %(path)s" msgstr "as permissões dos arquivos db devem ser %(octperms)s: %(path)s" #: bin/check_perms:183 +#, fuzzy msgid "checking mode for %(prefix)s" msgstr "verificando o modo para %(prefix)s" #: bin/check_perms:193 +#, fuzzy msgid "WARNING: directory does not exist: %(d)s" msgstr "ALERTA: o diretório não existe: %(d)s" #: bin/check_perms:197 +#, fuzzy msgid "directory must be at least 02775: %(d)s" msgstr "o diretório deve ser pelo menos 02775: %(d)s" #: bin/check_perms:209 +#, fuzzy msgid "checking perms on %(private)s" msgstr "verificando permissões em %(private)s" #: bin/check_perms:214 +#, fuzzy msgid "%(private)s must not be other-readable" msgstr "%(private)s não deverá ser lido por outros" @@ -10106,6 +10344,7 @@ msgid "mbox file must be at least 0660:" msgstr "o arquivo mbox deverá ter pelo menos 0660:" #: bin/check_perms:263 +#, fuzzy msgid "%(dbdir)s \"other\" perms must be 000" msgstr "as permissões \"de outros\" de %(dbdir)s devem ser 000" @@ -10114,26 +10353,32 @@ msgid "checking cgi-bin permissions" msgstr "verificando permissões de cgi-bin" #: bin/check_perms:278 +#, fuzzy msgid " checking set-gid for %(path)s" msgstr " verificando set-gid para %(path)s" #: bin/check_perms:282 +#, fuzzy msgid "%(path)s must be set-gid" msgstr "%(path)s deverá ser set-gid" #: bin/check_perms:292 +#, fuzzy msgid "checking set-gid for %(wrapper)s" msgstr "verificando set-gid para %(wrapper)s" #: bin/check_perms:296 +#, fuzzy msgid "%(wrapper)s must be set-gid" msgstr "%(wrapper)s deverá ser set-gid" #: bin/check_perms:306 +#, fuzzy msgid "checking permissions on %(pwfile)s" msgstr "verificando permissões de %(pwfile)s" #: bin/check_perms:315 +#, fuzzy msgid "%(pwfile)s permissions must be exactly 0640 (got %(octmode)s)" msgstr "" "as permissões de %(pwfile)s deverão ser exatamente \n" @@ -10144,10 +10389,12 @@ msgid "checking permissions on list data" msgstr "verificando permissões nos dados da lista" #: bin/check_perms:348 +#, fuzzy msgid " checking permissions on: %(path)s" msgstr " verificando permissões em: %(path)s" #: bin/check_perms:356 +#, fuzzy msgid "file permissions must be at least 660: %(path)s" msgstr "permissões de arquivos devem ser pelo menos 660: %(path)s" @@ -10235,6 +10482,7 @@ msgid "Unix-From line changed: %(lineno)d" msgstr "Linha From do Unix modificada: %(lineno)d" #: bin/cleanarch:111 +#, fuzzy msgid "Bad status number: %(arg)s" msgstr "Número incorreto de status: %(arg)s" @@ -10382,10 +10630,12 @@ msgid " original address removed:" msgstr " endereço original removido:" #: bin/clone_member:202 +#, fuzzy msgid "Not a valid email address: %(toaddr)s" msgstr "Não é um endereço de email válido: %(toaddr)s" #: bin/clone_member:215 +#, fuzzy msgid "" "Error opening list \"%(listname)s\", skipping.\n" "%(e)s" @@ -10494,6 +10744,7 @@ msgstr "" "\n" #: bin/config_list:118 +#, fuzzy msgid "" "# -*- python -*-\n" "# -*- coding: %(charset)s -*-\n" @@ -10514,22 +10765,27 @@ msgid "legal values are:" msgstr "valores legais são:" #: bin/config_list:270 +#, fuzzy msgid "attribute \"%(k)s\" ignored" msgstr "atributo \"%(k)s ignorado" #: bin/config_list:273 +#, fuzzy msgid "attribute \"%(k)s\" changed" msgstr "atributo \"%(k)s\" modificado" #: bin/config_list:279 +#, fuzzy msgid "Non-standard property restored: %(k)s" msgstr "Propriedade não padrão restaurada: %(k)s" #: bin/config_list:288 +#, fuzzy msgid "Invalid value for property: %(k)s" msgstr "Valor inválido para a propriedade: %(k)s" #: bin/config_list:291 +#, fuzzy msgid "Bad email address for option %(k)s: %(v)s" msgstr "Endereço de e-mail incorreto para a opção %(k)s: %(v)s" @@ -10595,18 +10851,22 @@ msgstr "" " Não mostra mensagens de status.\n" #: bin/discard:94 +#, fuzzy msgid "Ignoring non-held message: %(f)s" msgstr "Ignorando mensagem não presa: %(f)s" #: bin/discard:100 +#, fuzzy msgid "Ignoring held msg w/bad id: %(f)s" msgstr "Ignorando a mensagem aguardando aprovação com ID inválida: %(f)s" #: bin/discard:112 +#, fuzzy msgid "Discarded held msg #%(id)s for list %(listname)s" msgstr "Descartada a mensagem presa #%(id)s para a lista %(listname)s" #: bin/dumpdb:19 +#, fuzzy msgid "" "Dump the contents of any Mailman `database' file.\n" "\n" @@ -10679,6 +10939,7 @@ msgid "No filename given." msgstr "Nenhum nome de arquivo fornecido." #: bin/dumpdb:108 +#, fuzzy msgid "Bad arguments: %(pargs)s" msgstr "Argumentos incorretos: %(pargs)s" @@ -10687,14 +10948,17 @@ msgid "Please specify either -p or -m." msgstr "Por favor especifique ou -p ou -m." #: bin/dumpdb:133 +#, fuzzy msgid "[----- start %(typename)s file -----]" msgstr "[----- início do arquivo %(typename)s -----]" #: bin/dumpdb:139 +#, fuzzy msgid "[----- end %(typename)s file -----]" msgstr "[----- fim do arquivo %(typename)s -----]" #: bin/dumpdb:142 +#, fuzzy msgid "<----- start object %(cnt)s ----->" msgstr "<-- inicio do objeto %(cnt)s -->" @@ -10918,6 +11182,7 @@ msgid "Setting web_page_url to: %(web_page_url)s" msgstr "Ajustando web_page_url para: %(web_page_url)s" #: bin/fix_url.py:88 +#, fuzzy msgid "Setting host_name to: %(mailhost)s" msgstr "Ajustando o host_name para: %(mailhost)s" @@ -10956,6 +11221,7 @@ msgstr "" " Mostra esta mensagem e sai..\n" #: bin/genaliases:84 +#, fuzzy msgid "genaliases can't do anything useful with mm_cfg.MTA = %(mta)s." msgstr "genaliases não pode fazer nada útil com mm_cfg.MTA = %(mta)s." @@ -11008,6 +11274,7 @@ msgstr "" "injetados. Se omitido, a entrada padrão é usada.\n" #: bin/inject:84 +#, fuzzy msgid "Bad queue directory: %(qdir)s" msgstr "Diretório de queue inválido: %(qdir)s" @@ -11016,6 +11283,7 @@ msgid "A list name is required" msgstr "É requerido um nome de lista" #: bin/list_admins:20 +#, fuzzy msgid "" "List all the owners of a mailing list.\n" "\n" @@ -11063,10 +11331,12 @@ msgstr "" "Você poderá ter mais de uma lista especificada na linha de comando.\n" #: bin/list_admins:97 +#, fuzzy msgid "List: %(listname)s, \tOwners: %(owners)s" msgstr "Lista: %(listname)s, \\tDonos: %(owners)s" #: bin/list_lists:19 +#, fuzzy msgid "" "List all mailing lists.\n" "\n" @@ -11127,6 +11397,7 @@ msgid "matching mailing lists found:" msgstr "listas de discussão encontradas:" #: bin/list_members:19 +#, fuzzy msgid "" "List all the members of a mailing list.\n" "\n" @@ -11258,10 +11529,12 @@ msgstr "" "indicação é dada quanto ao status de endereço.\n" #: bin/list_members:198 +#, fuzzy msgid "Bad --nomail option: %(why)s" msgstr "Opção --nomail incorreta: %(why)s" #: bin/list_members:209 +#, fuzzy msgid "Bad --digest option: %(kind)s" msgstr "Opção --digest incorreta: %(kind)s" @@ -11275,6 +11548,7 @@ msgid "Could not open file for writing:" msgstr "Não foi possível abrir o arquivo para gravação:" #: bin/list_owners:20 +#, fuzzy msgid "" "List the owners of a mailing list, or all mailing lists.\n" "\n" @@ -11330,6 +11604,7 @@ msgstr "" "instalação do Mailman. Requer python 2." #: bin/mailmanctl:20 +#, fuzzy msgid "" "Primary start-up and shutdown script for Mailman's qrunner daemon.\n" "\n" @@ -11513,6 +11788,7 @@ msgstr "" " reopen - Isto fará que os arquivos de logs sejam reabertos.\n" #: bin/mailmanctl:152 +#, fuzzy msgid "PID unreadable in: %(pidfile)s" msgstr "PID não legível em: %(pidfile)s" @@ -11521,6 +11797,7 @@ msgid "Is qrunner even running?" msgstr "O qrunner está sendo executado?" #: bin/mailmanctl:160 +#, fuzzy msgid "No child with pid: %(pid)s" msgstr "Nenhum processo filho com a pid: %(pid)s" @@ -11549,6 +11826,7 @@ msgstr "" "a opção -s.\n" #: bin/mailmanctl:233 +#, fuzzy msgid "" "The master qrunner lock could not be acquired, because it appears as if " "some\n" @@ -11577,10 +11855,12 @@ msgstr "" "Saindo." #: bin/mailmanctl:280 cron/mailpasswds:119 +#, fuzzy msgid "Site list is missing: %(sitelistname)s" msgstr "A lista do site não existe: %(sitelistname)s" #: bin/mailmanctl:305 +#, fuzzy msgid "Run this program as root or as the %(name)s user, or use -u." msgstr "" "Re-execute este programa como root ou como o usuário %(name)s ou use a \n" @@ -11591,6 +11871,7 @@ msgid "No command given." msgstr "Nenhum comando fornecido." #: bin/mailmanctl:339 +#, fuzzy msgid "Bad command: %(command)s" msgstr "Comando incorreto: %(command)s" @@ -11615,6 +11896,7 @@ msgid "Starting Mailman's master qrunner." msgstr "Iniciando o qrunner master do Mailman." #: bin/mmsitepass:19 +#, fuzzy msgid "" "Set the site password, prompting from the terminal.\n" "\n" @@ -11669,6 +11951,7 @@ msgid "list creator" msgstr "criador da lista" #: bin/mmsitepass:86 +#, fuzzy msgid "New %(pwdesc)s password: " msgstr "Nova senha %(pwdesc)s: " @@ -11754,6 +12037,7 @@ msgid "Return the generated output." msgstr "Retornara a saída gerada." #: bin/newlist:20 +#, fuzzy msgid "" "Create a new, unpopulated mailing list.\n" "\n" @@ -11951,6 +12235,7 @@ msgstr "" "Observe que os nomes das listas são convertidos para letras minúsculas.\n" #: bin/newlist:162 +#, fuzzy msgid "Unknown language: %(lang)s" msgstr "Idioma Desconhecido: %(lang)s" @@ -11963,6 +12248,7 @@ msgid "Enter the email of the person running the list: " msgstr "Entre com o email da pessoa que administra a lista: " #: bin/newlist:193 +#, fuzzy msgid "Initial %(listname)s password: " msgstr "Senha inicial da %(listname)s: " @@ -11972,17 +12258,19 @@ msgstr "A senha da lista n #: bin/newlist:220 msgid "" -" - owner addresses need to be fully-qualified names like \"owner@example.com" -"\", not just \"owner\"." +" - owner addresses need to be fully-qualified names like \"owner@example." +"com\", not just \"owner\"." msgstr "" " - endereço do dono deve também conter o domínio, exemplo: \"owner@example." "com\" e não somente \"owner\"." #: bin/newlist:244 +#, fuzzy msgid "Hit enter to notify %(listname)s owner..." msgstr "Pressione enter para notificar o dono da %(listname)s." #: bin/qrunner:20 +#, fuzzy msgid "" "Run one or more qrunners, once or repeatedly.\n" "\n" @@ -12107,6 +12395,7 @@ msgstr "" "e devem ser um dos nomes especificados pela opção -l.\n" #: bin/qrunner:179 +#, fuzzy msgid "%(name)s runs the %(runnername)s qrunner" msgstr "%(name)s executa o qrunner %(runnername)s" @@ -12119,6 +12408,7 @@ msgid "No runner name given." msgstr "Nenhum nome de runner especificado." #: bin/rb-archfix:21 +#, fuzzy msgid "" "Reduce disk space usage for Pipermail archives.\n" "\n" @@ -12261,18 +12551,22 @@ msgstr "" " endereço1... são os endereços a serem removidos.\n" #: bin/remove_members:156 +#, fuzzy msgid "Could not open file for reading: %(filename)s." msgstr "Não foi possível abrir o arquivo para leitura: %(filename)s." #: bin/remove_members:163 +#, fuzzy msgid "Error opening list %(listname)s... skipping." msgstr "Erro durante a abertura da lista %(listname)s... pulando." #: bin/remove_members:173 +#, fuzzy msgid "No such member: %(addr)s" msgstr "Membro inexistente: %(addr)s" #: bin/remove_members:178 +#, fuzzy msgid "User `%(addr)s' removed from list: %(listname)s." msgstr "Usuário '%(addr)s' removido da lista: %(listname)s." @@ -12312,10 +12606,12 @@ msgstr "" " Print what the script is doing.\n" #: bin/reset_pw.py:77 +#, fuzzy msgid "Changing passwords for list: %(listname)s" msgstr "Alterando senhas para a lista: %(listname)s" #: bin/reset_pw.py:83 +#, fuzzy msgid "New password for member %(member)40s: %(randompw)s" msgstr "Nova senha do membro %(member)40s: %(randompw)s" @@ -12361,18 +12657,22 @@ msgstr "" "\n" #: bin/rmlist:73 bin/rmlist:76 +#, fuzzy msgid "Removing %(msg)s" msgstr "Removendo %(msg)s" #: bin/rmlist:81 +#, fuzzy msgid "%(listname)s %(msg)s not found as %(filename)s" msgstr "%(listname)s %(msg)s não encontrada como %(filename)s" #: bin/rmlist:105 +#, fuzzy msgid "No such list (or list already deleted): %(listname)s" msgstr "Lista inexistente (ou lista já apagada): %(listname)s" #: bin/rmlist:108 +#, fuzzy msgid "No such list: %(listname)s. Removing its residual archives." msgstr "Lista inexistente: %(listname)s. Removendo seus arquivos residuais." @@ -12431,6 +12731,7 @@ msgstr "" "Exemplo: show_qfiles qfiles/shunt/*.pck\n" #: bin/sync_members:19 +#, fuzzy msgid "" "Synchronize a mailing list's membership with a flat file.\n" "\n" @@ -12561,6 +12862,7 @@ msgstr "" " Requerido. Isto especifica a lista a ser sincronizada.\n" #: bin/sync_members:115 +#, fuzzy msgid "Bad choice: %(yesno)s" msgstr "Escolha incorreta: %(yesno)s" @@ -12577,6 +12879,7 @@ msgid "No argument to -f given" msgstr "Nenhum argumento passado para -f" #: bin/sync_members:172 +#, fuzzy msgid "Illegal option: %(opt)s" msgstr "Opção ilegal: %(opt)s" @@ -12589,6 +12892,7 @@ msgid "Must have a listname and a filename" msgstr "Deverá ter um nome de lista e nome de arquivo" #: bin/sync_members:191 +#, fuzzy msgid "Cannot read address file: %(filename)s: %(msg)s" msgstr "Não foi possível ler um arquivo de endereços: %(filename)s: %(msg)s" @@ -12605,14 +12909,17 @@ msgid "You must fix the preceding invalid addresses first." msgstr "Você deverá corrigir os endereços precedentes inválidos primeiro." #: bin/sync_members:264 +#, fuzzy msgid "Added : %(s)s" msgstr "Adicionado: %(s)s" #: bin/sync_members:289 +#, fuzzy msgid "Removed: %(s)s" msgstr "Removido: %(s)s" #: bin/transcheck:19 +#, fuzzy msgid "" "\n" "Check a given Mailman translation, making sure that variables and\n" @@ -12692,6 +12999,7 @@ msgid "scan the po file comparing msgids with msgstrs" msgstr "scanear o arquivo po comparando msgids com msgstrs" #: bin/unshunt:20 +#, fuzzy msgid "" "Move a message from the shunt queue to the original queue.\n" "\n" @@ -12723,6 +13031,7 @@ msgstr "" "resultará na perda de todas as mensagens nessa fila.\n" #: bin/unshunt:85 +#, fuzzy msgid "" "Cannot unshunt message %(filebase)s, skipping:\n" "%(e)s" @@ -12731,6 +13040,7 @@ msgstr "" "%(e)s" #: bin/update:20 +#, fuzzy msgid "" "Perform all necessary upgrades.\n" "\n" @@ -12768,14 +13078,17 @@ msgstr "" "de alguma versão anterior. Ele sabe sobre versões anteriores a 1.0b4 (?).\n" #: bin/update:107 +#, fuzzy msgid "Fixing language templates: %(listname)s" msgstr "Corrigindo templates de idioma: %(listname)s" #: bin/update:196 bin/update:711 +#, fuzzy msgid "WARNING: could not acquire lock for list: %(listname)s" msgstr "ALERTA: não foi possível adquirir lock para a lista: %(listname)s" #: bin/update:215 +#, fuzzy msgid "Resetting %(n)s BYBOUNCEs disabled addrs with no bounce info" msgstr "" "Redefinindo %(n)s BYBOUNCEs dos endereços desabilitados e que não possuam " @@ -12795,6 +13108,7 @@ msgstr "" "prosseguindo." #: bin/update:255 +#, fuzzy msgid "" "\n" "%(listname)s has both public and private mbox archives. Since this list\n" @@ -12846,6 +13160,7 @@ msgid "- updating old private mbox file" msgstr "- atualizando arquivo mbox privado antigo" #: bin/update:295 +#, fuzzy msgid "" " unknown file in the way, moving\n" " %(o_pri_mbox_file)s\n" @@ -12862,6 +13177,7 @@ msgid "- updating old public mbox file" msgstr "- atualizando arquivo antigo de caixa pública" #: bin/update:317 +#, fuzzy msgid "" " unknown file in the way, moving\n" " %(o_pub_mbox_file)s\n" @@ -12890,18 +13206,22 @@ msgid "- %(o_tmpl)s doesn't exist, leaving untouched" msgstr "- %(o_tmpl)s não existe, deixando intocado" #: bin/update:396 +#, fuzzy msgid "removing directory %(src)s and everything underneath" msgstr "removendo o diretório %(src)s e tudo dentro dele" #: bin/update:399 +#, fuzzy msgid "removing %(src)s" msgstr "removendo %(src)s" #: bin/update:403 +#, fuzzy msgid "Warning: couldn't remove %(src)s -- %(rest)s" msgstr "Alerta: Não foi possível remover %(src)s -- %(rest)s" #: bin/update:408 +#, fuzzy msgid "couldn't remove old file %(pyc)s -- %(rest)s" msgstr "não foi possível remover arquivo antigo %(pyc)s -- %(rest)s" @@ -12910,14 +13230,17 @@ msgid "updating old qfiles" msgstr "atualizando qfiles antigos" #: bin/update:455 +#, fuzzy msgid "Warning! Not a directory: %(dirpath)s" msgstr "Cuidado! Não é um diretório: %(dirpath)s" #: bin/update:530 +#, fuzzy msgid "message is unparsable: %(filebase)s" msgstr "a mensagem não pode ser analisada: %(filebase)s" #: bin/update:544 +#, fuzzy msgid "Warning! Deleting empty .pck file: %(pckfile)s" msgstr "Atenção! Excluindo arquivo .pck vazio: %(pckfile)s" @@ -12930,10 +13253,12 @@ msgid "Updating Mailman 2.1.4 pending.pck database" msgstr "Atualizando banco de dados pending.pck do Mailman 2.1.4" #: bin/update:598 +#, fuzzy msgid "Ignoring bad pended data: %(key)s: %(val)s" msgstr "Ignorando dados incorretamente associados: %(key)s: %(val)s" #: bin/update:614 +#, fuzzy msgid "WARNING: Ignoring duplicate pending ID: %(id)s." msgstr "ALERTA: Ignorando ID duplicada pendente: %(id)s." @@ -12959,6 +13284,7 @@ msgid "done" msgstr "concluído" #: bin/update:691 +#, fuzzy msgid "Updating mailing list: %(listname)s" msgstr "Atualizando a lista de discussão: %(listname)s" @@ -13020,6 +13346,7 @@ msgid "No updates are necessary." msgstr "Nenhuma atualização é necessária." #: bin/update:796 +#, fuzzy msgid "" "Downgrade detected, from version %(hexlversion)s to version %(hextversion)s\n" "This is probably not safe.\n" @@ -13031,10 +13358,12 @@ msgstr "" "Saindo." #: bin/update:801 +#, fuzzy msgid "Upgrading from version %(hexlversion)s to %(hextversion)s" msgstr "Atualizando da versão %(hexlversion)s para %(hextversion)s" #: bin/update:810 +#, fuzzy msgid "" "\n" "ERROR:\n" @@ -13304,6 +13633,7 @@ msgstr "" " " #: bin/withlist:175 +#, fuzzy msgid "Unlocking (but not saving) list: %(listname)s" msgstr "Desbloqueando (mas não salvando) a lista: %(listname)s" @@ -13312,6 +13642,7 @@ msgid "Finalizing" msgstr "Finalizando" #: bin/withlist:188 +#, fuzzy msgid "Loading list %(listname)s" msgstr "Carregando lista %(listname)s" @@ -13324,6 +13655,7 @@ msgid "(unlocked)" msgstr "(desbloqueada)" #: bin/withlist:197 +#, fuzzy msgid "Unknown list: %(listname)s" msgstr "Lista desconhecida: %(listname)s" @@ -13336,18 +13668,22 @@ msgid "--all requires --run" msgstr "--all requer --run" #: bin/withlist:266 +#, fuzzy msgid "Importing %(module)s..." msgstr "Importando %(module)s..." #: bin/withlist:270 +#, fuzzy msgid "Running %(module)s.%(callable)s()..." msgstr "Executando %(module)s.%(callable)s()..." #: bin/withlist:291 +#, fuzzy msgid "The variable `m' is the %(listname)s MailList instance" msgstr "A variável `m' é a instância da lista de discussão %(listname)s" #: cron/bumpdigests:19 +#, fuzzy msgid "" "Increment the digest volume number and reset the digest number to one.\n" "\n" @@ -13375,6 +13711,7 @@ msgstr "" "de lista for especificado, todas as listas são conferidas.\n" #: cron/checkdbs:20 +#, fuzzy msgid "" "Check for pending admin requests and mail the list owners if necessary.\n" "\n" @@ -13405,10 +13742,12 @@ msgstr "" "\n" #: cron/checkdbs:121 +#, fuzzy msgid "%(count)d %(realname)s moderator request(s) waiting" msgstr "%(count)d requisições de moderação aguardando para %(realname)s" #: cron/checkdbs:124 +#, fuzzy msgid "%(realname)s moderator request check result" msgstr "Resultado da checagem de requisições do moderador %(realname)s" @@ -13429,6 +13768,7 @@ msgstr "" "Postagens pendentes:" #: cron/checkdbs:169 +#, fuzzy msgid "" "From: %(sender)s on %(date)s\n" "Subject: %(subject)s\n" @@ -13439,6 +13779,7 @@ msgstr "" "Causa: %(reason)s" #: cron/cull_bad_shunt:20 +#, fuzzy msgid "" "Cull bad and shunt queues, recommended once per day.\n" "\n" @@ -13480,6 +13821,7 @@ msgstr "" " Imprima esta mensagem e saia.\n" #: cron/disabled:20 +#, fuzzy msgid "" "Process disabled members, recommended once per day.\n" "\n" @@ -13606,6 +13948,7 @@ msgstr "" "\n" #: cron/mailpasswds:19 +#, fuzzy msgid "" "Send password reminders for all lists to all users.\n" "\n" @@ -13657,10 +14000,12 @@ msgid "Password // URL" msgstr "Senha // URL" #: cron/mailpasswds:222 +#, fuzzy msgid "%(host)s mailing list memberships reminder" msgstr "lembrete de membros da lista de discussão %(host)s" #: cron/nightly_gzip:19 +#, fuzzy msgid "" "Re-generate the Pipermail gzip'd archive flat files.\n" "\n" @@ -13708,6 +14053,7 @@ msgstr "" "\n" #: cron/senddigests:20 +#, fuzzy msgid "" "Dispatch digests for lists w/pending messages and digest_send_periodic set.\n" "\n" diff --git a/messages/ro/LC_MESSAGES/mailman.po b/messages/ro/LC_MESSAGES/mailman.po index 52f7ac29..f3cae8f8 100755 --- a/messages/ro/LC_MESSAGES/mailman.po +++ b/messages/ro/LC_MESSAGES/mailman.po @@ -70,10 +70,12 @@ msgid "

                    Currently, there are no archives.

                    " msgstr "

                    Nu sunt arhive în acest moment

                    " #: Mailman/Archiver/HyperArch.py:821 +#, fuzzy msgid "Gzip'd Text%(sz)s" msgstr "Text%(sz)s comprimat%(sz)s (gzip)" #: Mailman/Archiver/HyperArch.py:826 +#, fuzzy msgid "Text%(sz)s" msgstr "Text%(sz)s" @@ -146,18 +148,22 @@ msgid "Third" msgstr "III" #: Mailman/Archiver/HyperArch.py:938 +#, fuzzy msgid "%(ord)s quarter %(year)i" msgstr "Trimestrul %(ord)s, %(year)i" #: Mailman/Archiver/HyperArch.py:945 +#, fuzzy msgid "%(month)s %(year)i" msgstr "%(month)s %(year)i" #: Mailman/Archiver/HyperArch.py:950 +#, fuzzy msgid "The Week Of Monday %(day)i %(month)s %(year)i" msgstr "Săptămâna ce începe Luni, %(day)i %(month)s %(year)i" #: Mailman/Archiver/HyperArch.py:954 +#, fuzzy msgid "%(day)i %(month)s %(year)i" msgstr "%(day)i %(month)s %(year)i" @@ -166,10 +172,12 @@ msgid "Computing threaded index\n" msgstr "Calculez indexul firului de discuţie (threaded index)\n" #: Mailman/Archiver/HyperArch.py:1319 +#, fuzzy msgid "Updating HTML for article %(seq)s" msgstr "Actualizez codul HTML pentru articolul %(seq)s" #: Mailman/Archiver/HyperArch.py:1326 +#, fuzzy msgid "article file %(filename)s is missing!" msgstr "lipseşte fişierul %(filename)s al articolului!" @@ -186,6 +194,7 @@ msgid "Pickling archive state into " msgstr "Conserv informaţiile de stare ale arhivei " #: Mailman/Archiver/pipermail.py:453 +#, fuzzy msgid "Updating index files for archive [%(archive)s]" msgstr "Actualizez fişierele de index pentru arhiva [%(archive)s]" @@ -194,6 +203,7 @@ msgid " Thread" msgstr " Thread" #: Mailman/Archiver/pipermail.py:597 +#, fuzzy msgid "#%(counter)05d %(msgid)s" msgstr "#%(counter)05d %(msgid)s" @@ -231,6 +241,7 @@ msgid "disabled address" msgstr "dezactivat" #: Mailman/Bouncer.py:304 +#, fuzzy msgid " The last bounce received from you was dated %(date)s" msgstr "" "Ultimul eşec la livrarea mesajelor către adresa dumneavoastră este datat " @@ -260,6 +271,7 @@ msgstr "Administrator" #: Mailman/Cgi/options.py:101 Mailman/Cgi/private.py:108 #: Mailman/Cgi/rmlist.py:75 Mailman/Cgi/roster.py:59 #: Mailman/Cgi/subscribe.py:67 +#, fuzzy msgid "No such list %(safelistname)s" msgstr "Nu există lista %(safelistname)s" @@ -332,6 +344,7 @@ msgstr "" "Ei nu vor primi mesaje până la rezolvarea acestei probleme.%(rm)r" #: Mailman/Cgi/admin.py:268 +#, fuzzy msgid "%(hostname)s mailing lists - Admin Links" msgstr "Listele de discuţii de la %(hostname)s - Linkuri administrative" @@ -344,6 +357,7 @@ msgid "Mailman" msgstr "Mailman" #: Mailman/Cgi/admin.py:310 +#, fuzzy msgid "" "

                    There currently are no publicly-advertised %(mailmanlink)s\n" " mailing lists on %(hostname)s." @@ -352,6 +366,7 @@ msgstr "" "

                    " #: Mailman/Cgi/admin.py:316 +#, fuzzy msgid "" "

                    Below is the collection of publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s. Click on a list\n" @@ -366,6 +381,7 @@ msgid "right " msgstr "dreapta " #: Mailman/Cgi/admin.py:325 +#, fuzzy msgid "" "To visit the administrators configuration page for an\n" " unadvertised list, open a URL similar to this one, but with a '/' " @@ -409,6 +425,7 @@ msgid "No valid variable name found." msgstr "Nu am găsit un nume valid de variabilă." #: Mailman/Cgi/admin.py:395 +#, fuzzy msgid "" "%(realname)s Mailing list Configuration Help\n" "
                    %(varname)s Option" @@ -417,6 +434,7 @@ msgstr "" "Opţiunea
                    %(varname)s" #: Mailman/Cgi/admin.py:402 +#, fuzzy msgid "Mailman %(varname)s List Option Help" msgstr "Mailman - Lista %(varname)s - Opţiuni Ajutor" @@ -438,14 +456,17 @@ msgstr "" " " #: Mailman/Cgi/admin.py:431 +#, fuzzy msgid "return to the %(categoryname)s options page." msgstr "înapoi la pagina de opţiuni %(categoryname)s." #: Mailman/Cgi/admin.py:446 +#, fuzzy msgid "%(realname)s Administration (%(label)s)" msgstr "Administrare %(realname)s - (%(label)s)" #: Mailman/Cgi/admin.py:447 +#, fuzzy msgid "%(realname)s mailing list administration
                    %(label)s Section" msgstr "Administrarea listei de discuţii %(realname)s
                    Secţiunea %(label)s" @@ -528,6 +549,7 @@ msgid "Value" msgstr "Valoare" #: Mailman/Cgi/admin.py:668 +#, fuzzy msgid "" "Badly formed options entry:\n" " %(record)s" @@ -628,10 +650,12 @@ msgid "Move rule down" msgstr "Mută regula mai jos" #: Mailman/Cgi/admin.py:870 +#, fuzzy msgid "
                    (Edit %(varname)s)" msgstr "
                    (Modifică %(varname)s)" #: Mailman/Cgi/admin.py:872 +#, fuzzy msgid "
                    (Details for %(varname)s)" msgstr "
                    (Detalii pentru %(varname)s)" @@ -672,6 +696,7 @@ msgid "(help)" msgstr "(ajutor)" #: Mailman/Cgi/admin.py:930 +#, fuzzy msgid "Find member %(link)s:" msgstr "Caută membrul %(link)s:" @@ -684,10 +709,12 @@ msgid "Bad regular expression: " msgstr "Expresie regulară eronată: " #: Mailman/Cgi/admin.py:1013 +#, fuzzy msgid "%(allcnt)s members total, %(membercnt)s shown" msgstr "%(allcnt)s membri în total, %(membercnt)s afişaţi" #: Mailman/Cgi/admin.py:1016 +#, fuzzy msgid "%(allcnt)s members total" msgstr "%(allcnt)s membri în total" @@ -884,6 +911,7 @@ msgstr "" " intervalul corespunzător, afişat mai jos:" #: Mailman/Cgi/admin.py:1221 +#, fuzzy msgid "from %(start)s to %(end)s" msgstr "de la %(start)s până la %(end)s" @@ -1021,6 +1049,7 @@ msgid "Change list ownership passwords" msgstr "Parolele de proprietate ale listei" #: Mailman/Cgi/admin.py:1364 +#, fuzzy msgid "" "The list administrators are the people who have ultimate control " "over\n" @@ -1133,6 +1162,7 @@ msgstr "Adresă ostilă (caractere ilegale)" #: Mailman/Cgi/admin.py:1529 Mailman/Cgi/admin.py:1703 bin/add_members:175 #: bin/clone_member:136 bin/sync_members:268 +#, fuzzy msgid "Banned address (matched %(pattern)s)" msgstr "Adresă blocată cu expresia %(pattern)s" @@ -1235,6 +1265,7 @@ msgid "Not subscribed" msgstr "Nu sunt abonaţi" #: Mailman/Cgi/admin.py:1773 +#, fuzzy msgid "Ignoring changes to deleted member: %(user)s" msgstr "Ignor modificările în cazul membrului şters: %(user)s" @@ -1247,10 +1278,12 @@ msgid "Error Unsubscribing:" msgstr "Eroare la dezabonare:" #: Mailman/Cgi/admindb.py:235 Mailman/Cgi/admindb.py:248 +#, fuzzy msgid "%(realname)s Administrative Database" msgstr "Baza de date administrativă a %(realname)s" #: Mailman/Cgi/admindb.py:238 +#, fuzzy msgid "%(realname)s Administrative Database Results" msgstr "Rezultatele bazei de date administrative a %(realname)s" @@ -1279,6 +1312,7 @@ msgid "Discard all messages marked Defer" msgstr "Aruncă toate mesajele marcate cu Defer" #: Mailman/Cgi/admindb.py:298 +#, fuzzy msgid "all of %(esender)s's held messages." msgstr "toate mesajele reţinute trimise de %(esender)s's" @@ -1299,6 +1333,7 @@ msgid "list of available mailing lists." msgstr "lista listelor de discuţii disponibile." #: Mailman/Cgi/admindb.py:362 +#, fuzzy msgid "You must specify a list name. Here is the %(link)s" msgstr "Trebuie să specificaţi un nume pentru listă. Iată: %(link)s" @@ -1380,6 +1415,7 @@ msgid "The sender is now a member of this list" msgstr "Expeditorul este acum membru al acestei liste" #: Mailman/Cgi/admindb.py:568 +#, fuzzy msgid "Add %(esender)s to one of these sender filters:" msgstr "Adaugă %(esender)s la unul din aceste filtre de expeditor:" @@ -1400,6 +1436,7 @@ msgid "Rejects" msgstr "Rejectate" #: Mailman/Cgi/admindb.py:584 +#, fuzzy msgid "" "Ban %(esender)s from ever subscribing to this\n" " mailing list" @@ -1416,6 +1453,7 @@ msgstr "" "sau puteţi " #: Mailman/Cgi/admindb.py:591 +#, fuzzy msgid "view all messages from %(esender)s" msgstr "afişa toate mesajele de la %(esender)s" @@ -1494,6 +1532,7 @@ msgid " is already a member" msgstr " este deja membru" #: Mailman/Cgi/admindb.py:973 +#, fuzzy msgid "%(addr)s is banned (matched: %(patt)s)" msgstr "%(addr)s este blocată (expresia %(patt)s" @@ -1547,6 +1586,7 @@ msgstr "" " a fost anulată." #: Mailman/Cgi/confirm.py:178 +#, fuzzy msgid "System error, bad content: %(content)s" msgstr "Eroare de sistem, conţinut eronat: %(content)s" @@ -1584,6 +1624,7 @@ msgid "Confirm subscription request" msgstr "Cerere de confirmare a abonării" #: Mailman/Cgi/confirm.py:259 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " subscription request to the mailing list %(listname)s. Your\n" @@ -1618,6 +1659,7 @@ msgstr "" " această cerere de abonare." #: Mailman/Cgi/confirm.py:275 +#, fuzzy msgid "" "Your confirmation is required in order to continue with\n" " the subscription request to the mailing list %(listname)s.\n" @@ -1674,6 +1716,7 @@ msgid "Preferred language:" msgstr "Limba preferată:" #: Mailman/Cgi/confirm.py:317 +#, fuzzy msgid "Subscribe to list %(listname)s" msgstr "Mă abonez la lista %(listname)s" @@ -1690,6 +1733,7 @@ msgid "Awaiting moderator approval" msgstr "În aşteptarea aprobării moderatorului" #: Mailman/Cgi/confirm.py:386 +#, fuzzy msgid "" " You have successfully confirmed your subscription request to " "the\n" @@ -1723,6 +1767,7 @@ msgid "You are already a member of this mailing list!" msgstr "Sunteţi deja membru al acestei liste de discuţii!" #: Mailman/Cgi/confirm.py:400 +#, fuzzy msgid "" "You are currently banned from subscribing to\n" " this list. If you think this restriction is erroneous, please\n" @@ -1747,6 +1792,7 @@ msgid "Subscription request confirmed" msgstr "Cererea de abonare a fost confirmată" #: Mailman/Cgi/confirm.py:418 +#, fuzzy msgid "" " You have successfully confirmed your subscription request for\n" " \"%(addr)s\" to the %(listname)s mailing list. A separate\n" @@ -1775,6 +1821,7 @@ msgid "Unsubscription request confirmed" msgstr "Cererea de dezabonare a fost confirmată" #: Mailman/Cgi/confirm.py:469 +#, fuzzy msgid "" " You have successfully unsubscribed from the %(listname)s " "mailing\n" @@ -1796,6 +1843,7 @@ msgid "Not available" msgstr "Nu este disponibil" #: Mailman/Cgi/confirm.py:498 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " unsubscription request from the mailing list %(listname)s. " @@ -1840,6 +1888,7 @@ msgid "You have canceled your change of address request." msgstr "Aţi anulat cererea de modificare a adresei." #: Mailman/Cgi/confirm.py:555 +#, fuzzy msgid "" "%(newaddr)s is banned from subscribing to the\n" " %(realname)s list. If you think this restriction is erroneous,\n" @@ -1850,6 +1899,7 @@ msgstr "" " contactaţi proprietarii listei la %(owneraddr)s." #: Mailman/Cgi/confirm.py:560 +#, fuzzy msgid "" "%(newaddr)s is already a member of\n" " the %(realname)s list. It is possible that you are attempting\n" @@ -1865,6 +1915,7 @@ msgid "Change of address request confirmed" msgstr "Cererea de modificare a adresei a fost confirmată" #: Mailman/Cgi/confirm.py:571 +#, fuzzy msgid "" " You have successfully changed your address on the %(listname)s\n" " mailing list from %(oldaddr)s to %(newaddr)s. " @@ -1886,6 +1937,7 @@ msgid "globally" msgstr "global" #: Mailman/Cgi/confirm.py:605 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " change of address request for the mailing list %(listname)s. " @@ -1949,6 +2001,7 @@ msgid "Sender discarded message via web." msgstr "Expeditorul a anulat mesajul prin intermediul paginii web." #: Mailman/Cgi/confirm.py:674 +#, fuzzy msgid "" "The held message with the Subject:\n" " header %(subject)s could not be found. The most " @@ -1968,6 +2021,7 @@ msgid "Posted message canceled" msgstr "Mesajul publicat a fost anulat" #: Mailman/Cgi/confirm.py:685 +#, fuzzy msgid "" " You have successfully canceled the posting of your message with\n" " the Subject: header %(subject)s to the mailing list\n" @@ -1990,6 +2044,7 @@ msgstr "" " procesat de către administratorul listei." #: Mailman/Cgi/confirm.py:735 +#, fuzzy msgid "" "Your confirmation is required in order to cancel the\n" " posting of your message to the mailing list %(listname)s:\n" @@ -2039,6 +2094,7 @@ msgid "Membership re-enabled." msgstr "Abonamentul a fost reactivat." #: Mailman/Cgi/confirm.py:798 +#, fuzzy msgid "" " You have successfully re-enabled your membership in the\n" " %(listname)s mailing list. You can now not available" msgstr "nu este disponibil(ă)" #: Mailman/Cgi/confirm.py:846 +#, fuzzy msgid "" "Your membership in the %(realname)s mailing list is\n" " currently disabled due to excessive bounces. Your confirmation is\n" @@ -2136,10 +2194,12 @@ msgid "administrative list overview" msgstr "meniul general administrativ al listei" #: Mailman/Cgi/create.py:115 +#, fuzzy msgid "List name must not include \"@\": %(safelistname)s" msgstr "Numele listei nu poate include \"@\": %(safelistname)s" #: Mailman/Cgi/create.py:122 +#, fuzzy msgid "List already exists: %(safelistname)s" msgstr "Lista %(safelistname)s există deja!" @@ -2174,18 +2234,22 @@ msgid "You are not authorized to create new mailing lists" msgstr "Nu aveţi autorizarea necesară pentru a crea noi liste de discuţii" #: Mailman/Cgi/create.py:182 +#, fuzzy msgid "Unknown virtual host: %(safehostname)s" msgstr "Listă necunoscută: %(safelistname)s" #: Mailman/Cgi/create.py:218 bin/newlist:219 +#, fuzzy msgid "Bad owner email address: %(s)s" msgstr "Adresă eronată a proprietarului listei: %(s)s" #: Mailman/Cgi/create.py:223 bin/newlist:182 bin/newlist:223 +#, fuzzy msgid "List already exists: %(listname)s" msgstr "Lista %(listname)s există deja!" #: Mailman/Cgi/create.py:231 bin/newlist:217 +#, fuzzy msgid "Illegal list name: %(s)s" msgstr "Nume de listă ilegal: %(s)s" @@ -2199,6 +2263,7 @@ msgstr "" "tehnică." #: Mailman/Cgi/create.py:273 bin/newlist:265 +#, fuzzy msgid "Your new mailing list: %(listname)s" msgstr "Noua dumneavoastră listă: %(listname)s" @@ -2207,6 +2272,7 @@ msgid "Mailing list creation results" msgstr "Rezultatele creării listei de discuţie" #: Mailman/Cgi/create.py:288 +#, fuzzy msgid "" "You have successfully created the mailing list\n" " %(listname)s and notification has been sent to the list owner\n" @@ -2230,6 +2296,7 @@ msgid "Create another list" msgstr "Crează o altă listă" #: Mailman/Cgi/create.py:312 +#, fuzzy msgid "Create a %(hostname)s Mailing List" msgstr "Crează o listă de discuţii Mailnam la %(hostname)s" @@ -2333,6 +2400,7 @@ msgstr "" " pentru a reţine din oficiu mesajele noilor membrii pentru aprobare." #: Mailman/Cgi/create.py:432 +#, fuzzy msgid "" "Initial list of supported languages.

                    Note that if you do not\n" " select at least one initial language, the list will use the server\n" @@ -2437,6 +2505,7 @@ msgid "List name is required." msgstr "Numele listei este obligatoriu." #: Mailman/Cgi/edithtml.py:154 +#, fuzzy msgid "%(realname)s -- Edit html for %(template_info)s" msgstr "%(realname)s -- Editează codul HTML pentru %(template_info)s" @@ -2445,10 +2514,12 @@ msgid "Edit HTML : Error" msgstr "Modificare HTML : Eroare" #: Mailman/Cgi/edithtml.py:161 +#, fuzzy msgid "%(safetemplatename)s: Invalid template" msgstr "%(safetemplatename)s: Şablon (template) invalid" #: Mailman/Cgi/edithtml.py:166 Mailman/Cgi/edithtml.py:167 +#, fuzzy msgid "%(realname)s -- HTML Page Editing" msgstr "%(realname)s -- Editare pagină HTML" @@ -2511,10 +2582,12 @@ msgid "HTML successfully updated." msgstr "Codul HTML a fost actualizat cu succes." #: Mailman/Cgi/listinfo.py:89 +#, fuzzy msgid "%(hostname)s Mailing Lists" msgstr "Listele de discuţii de la %(hostname)s" #: Mailman/Cgi/listinfo.py:127 +#, fuzzy msgid "" "

                    There currently are no publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s." @@ -2523,6 +2596,7 @@ msgstr "" "%(mailmanlink)s publice la %(hostname)s." #: Mailman/Cgi/listinfo.py:131 +#, fuzzy msgid "" "

                    Below is a listing of all the public mailing lists on\n" " %(hostname)s. Click on a list name to get more information " @@ -2542,6 +2616,7 @@ msgid "right" msgstr "dreapta" #: Mailman/Cgi/listinfo.py:140 +#, fuzzy msgid "" " To visit the general information page for an unadvertised list,\n" " open a URL similar to this one, but with a '/' and the %(adj)s\n" @@ -2604,6 +2679,7 @@ msgstr "Adresă de email ilegală" #: Mailman/Cgi/options.py:183 Mailman/Cgi/options.py:240 #: Mailman/Cgi/options.py:264 +#, fuzzy msgid "No such member: %(safeuser)s." msgstr "Nu există un astfel de membru: %(safeuser)s." @@ -2657,6 +2733,7 @@ msgid "Note: " msgstr "" #: Mailman/Cgi/options.py:378 +#, fuzzy msgid "List subscriptions for %(safeuser)s on %(hostname)s" msgstr "Abonamentele pentru %(safeuser)s la %(hostname)s" @@ -2684,6 +2761,7 @@ msgid "You are already using that email address" msgstr "Folosiţi deja această adresă de email" #: Mailman/Cgi/options.py:459 +#, fuzzy msgid "" "The new address you requested %(newaddr)s is already a member of the\n" "%(listname)s mailing list, however you have also requested a global change " @@ -2697,6 +2775,7 @@ msgstr "" "adresa %(safeuser)s vor fi modificate. " #: Mailman/Cgi/options.py:468 +#, fuzzy msgid "The new address is already a member: %(newaddr)s" msgstr "Noua adresă este deja abonată: %(newaddr)s" @@ -2705,6 +2784,7 @@ msgid "Addresses may not be blank" msgstr "Adresele nu pot fi nule" #: Mailman/Cgi/options.py:488 +#, fuzzy msgid "A confirmation message has been sent to %(newaddr)s. " msgstr "Un mesaj de confirmare a fost trimis la %(newaddr)s. " @@ -2717,10 +2797,12 @@ msgid "Illegal email address provided" msgstr "Adresă de email ilegală" #: Mailman/Cgi/options.py:501 +#, fuzzy msgid "%(newaddr)s is already a member of the list." msgstr "%(newaddr)s este deja abonată pe listă." #: Mailman/Cgi/options.py:504 +#, fuzzy msgid "" "%(newaddr)s is banned from this list. If you\n" " think this restriction is erroneous, please contact\n" @@ -2796,6 +2878,7 @@ msgstr "" " o notificare de îndată ce aceştia vor lua o decizie." #: Mailman/Cgi/options.py:623 +#, fuzzy msgid "" "You have been successfully unsubscribed from the\n" " mailing list %(fqdn_listname)s. If you were receiving digest\n" @@ -2889,6 +2972,7 @@ msgid "day" msgstr "zi" #: Mailman/Cgi/options.py:900 +#, fuzzy msgid "%(days)d %(units)s" msgstr "%(days)d %(units)s" @@ -2901,6 +2985,7 @@ msgid "No topics defined" msgstr "Nu sunt topici definite" #: Mailman/Cgi/options.py:940 +#, fuzzy msgid "" "\n" "You are subscribed to this list with the case-preserved address\n" @@ -2911,6 +2996,7 @@ msgstr "" "%(cpuser)s." #: Mailman/Cgi/options.py:956 +#, fuzzy msgid "%(realname)s list: member options login page" msgstr "Lista de discuţii %(realname)s: pagina de acces la opţiunile personale" @@ -2919,11 +3005,13 @@ msgid "email address and " msgstr "adresa de email şi " #: Mailman/Cgi/options.py:960 +#, fuzzy msgid "%(realname)s list: member options for user %(safeuser)s" msgstr "" "Lista de discuţii %(realname)s: opţiunile personale pentru %(safeuser)s" #: Mailman/Cgi/options.py:986 +#, fuzzy msgid "" "In order to change your membership option, you must\n" " first log in by giving your %(extra)smembership password in the section\n" @@ -3000,6 +3088,7 @@ msgid "" msgstr "" #: Mailman/Cgi/options.py:1140 +#, fuzzy msgid "Requested topic is not valid: %(topicname)s" msgstr "Topica cerută nu este validă: %(topicname)s" @@ -3028,6 +3117,7 @@ msgid "Private archive - \"./\" and \"../\" not allowed in URL." msgstr "" #: Mailman/Cgi/private.py:109 +#, fuzzy msgid "Private Archive Error - %(msg)s" msgstr "Eroare la arhiva privată - %(msg)s" @@ -3049,6 +3139,7 @@ msgid "Private archive file not found" msgstr "Fişierul de arhivă privată nu a fost găsit" #: Mailman/Cgi/rmlist.py:76 +#, fuzzy msgid "No such list %(safelistname)s" msgstr "Nu există o astfel de listă: %(safelistname)s" @@ -3065,6 +3156,7 @@ msgid "Mailing list deletion results" msgstr "Rezultatele ştergerii listei de discuţii" #: Mailman/Cgi/rmlist.py:187 +#, fuzzy msgid "" "You have successfully deleted the mailing list\n" " %(listname)s." @@ -3073,6 +3165,7 @@ msgstr "" " %(listname)s." #: Mailman/Cgi/rmlist.py:191 +#, fuzzy msgid "" "There were some problems deleting the mailing list\n" " %(listname)s. Contact your site administrator at " @@ -3085,10 +3178,12 @@ msgstr "" " pentru detalii." #: Mailman/Cgi/rmlist.py:208 +#, fuzzy msgid "Permanently remove mailing list %(realname)s" msgstr "Şterg definitiv lista de discuţii %(realname)s" #: Mailman/Cgi/rmlist.py:209 +#, fuzzy msgid "Permanently remove mailing list %(realname)s" msgstr "terg definitiv lista de discuţii %(realname)s" @@ -3157,6 +3252,7 @@ msgid "Invalid options to CGI script" msgstr "Opţiuni invalide pentru scriptul CGI" #: Mailman/Cgi/roster.py:118 +#, fuzzy msgid "%(realname)s roster authentication failed." msgstr "autentificare eşuată pentru %(realname)s." @@ -3225,6 +3321,7 @@ msgstr "" "veţi primi un mesaj cu instrucţiuni suplimentare." #: Mailman/Cgi/subscribe.py:262 +#, fuzzy msgid "" "The email address you supplied is banned from this\n" " mailing list. If you think this restriction is erroneous, please\n" @@ -3251,6 +3348,7 @@ msgstr "" "pe care ne-aţi dat-o nu este sigură." #: Mailman/Cgi/subscribe.py:278 +#, fuzzy msgid "" "Confirmation from your email address is required, to prevent anyone from\n" "subscribing you without permission. Instructions are being sent to you at\n" @@ -3263,6 +3361,7 @@ msgstr "" "începe doar în momentul confirmării adresei de e-mail cu care v-aţi abonat." #: Mailman/Cgi/subscribe.py:290 +#, fuzzy msgid "" "Your subscription request was deferred because %(x)s. Your request has " "been\n" @@ -3285,6 +3384,7 @@ msgid "Mailman privacy alert" msgstr "Alertă Mailman" #: Mailman/Cgi/subscribe.py:316 +#, fuzzy msgid "" "An attempt was made to subscribe your address to the mailing list\n" "%(listaddr)s. You are already subscribed to this mailing list.\n" @@ -3315,6 +3415,7 @@ msgid "This list only supports digest delivery." msgstr "Această listă suportă numai livrarea de rezumate zilnice." #: Mailman/Cgi/subscribe.py:344 +#, fuzzy msgid "You have been successfully subscribed to the %(realname)s mailing list." msgstr "Aţi fost abonat cu succes la lista de discuţii %(realname)s." @@ -3365,6 +3466,7 @@ msgstr "" "sau v-aţi schimbat cumva adresa de email?" #: Mailman/Commands/cmd_confirm.py:69 +#, fuzzy msgid "" "You are currently banned from subscribing to this list. If you think this\n" "restriction is erroneous, please contact the list owners at\n" @@ -3447,26 +3549,32 @@ msgid "n/a" msgstr "n/a" #: Mailman/Commands/cmd_info.py:44 +#, fuzzy msgid "List name: %(listname)s" msgstr "Numele listei: %(listname)s" #: Mailman/Commands/cmd_info.py:45 +#, fuzzy msgid "Description: %(description)s" msgstr "Descriere: %(description)s" #: Mailman/Commands/cmd_info.py:46 +#, fuzzy msgid "Postings to: %(postaddr)s" msgstr "Publicarea la: %(postaddr)s" #: Mailman/Commands/cmd_info.py:47 +#, fuzzy msgid "List Helpbot: %(requestaddr)s" msgstr "Robotul de ajutor: %(requestaddr)s" #: Mailman/Commands/cmd_info.py:48 +#, fuzzy msgid "List Owners: %(owneraddr)s" msgstr "Proprietarii listei: %(owneraddr)s" #: Mailman/Commands/cmd_info.py:49 +#, fuzzy msgid "More information: %(listurl)s" msgstr "Mai multe informaţii: %(listurl)s" @@ -3490,18 +3598,22 @@ msgstr "" "server GNU Mailman.\n" #: Mailman/Commands/cmd_lists.py:44 +#, fuzzy msgid "Public mailing lists at %(hostname)s:" msgstr "Listele de discuţii publice de la %(hostname)s:" #: Mailman/Commands/cmd_lists.py:66 +#, fuzzy msgid "%(i)3d. List name: %(realname)s" msgstr "%(i)3d. Numele listei: %(realname)s" #: Mailman/Commands/cmd_lists.py:67 +#, fuzzy msgid " Description: %(description)s" msgstr " Descriere: %(description)s" #: Mailman/Commands/cmd_lists.py:68 +#, fuzzy msgid " Requests to: %(requestaddr)s" msgstr " Cererile se trimit la: %(requestaddr)s" @@ -3539,12 +3651,14 @@ msgstr "" " trimis la adresa de abonament.\n" #: Mailman/Commands/cmd_password.py:51 Mailman/Commands/cmd_password.py:66 +#, fuzzy msgid "Your password is: %(password)s" msgstr "Parola dumneavoastră este: %(password)s" #: Mailman/Commands/cmd_password.py:57 Mailman/Commands/cmd_password.py:72 #: Mailman/Commands/cmd_password.py:95 Mailman/Commands/cmd_password.py:121 #: Mailman/Commands/cmd_set.py:149 Mailman/Commands/cmd_set.py:219 +#, fuzzy msgid "You are not a member of the %(listname)s mailing list" msgstr "Nu sunteţi un membru al listei de discuţii %(listname)s" @@ -3690,6 +3804,7 @@ msgstr "" " set ack off\n" #: Mailman/Commands/cmd_set.py:122 +#, fuzzy msgid "Bad set command: %(subcmd)s" msgstr "Set de comenzi eronate: %(subcmd)s" @@ -3710,6 +3825,7 @@ msgid "on" msgstr "activat(ă)" #: Mailman/Commands/cmd_set.py:154 +#, fuzzy msgid " ack %(onoff)s" msgstr " ack %(onoff)s" @@ -3747,22 +3863,27 @@ msgid "due to bounces" msgstr "datorită eşecurilor de livrare" #: Mailman/Commands/cmd_set.py:186 +#, fuzzy msgid " %(status)s (%(how)s on %(date)s)" msgstr " %(status)s (%(how)s la %(date)s)" #: Mailman/Commands/cmd_set.py:192 +#, fuzzy msgid " myposts %(onoff)s" msgstr " myposts %(onoff)s" #: Mailman/Commands/cmd_set.py:195 +#, fuzzy msgid " hide %(onoff)s" msgstr " ascunde %(onoff)s" #: Mailman/Commands/cmd_set.py:199 +#, fuzzy msgid " duplicates %(onoff)s" msgstr " duplicates %(onoff)s" #: Mailman/Commands/cmd_set.py:203 +#, fuzzy msgid " reminders %(onoff)s" msgstr " reminders %(onoff)s" @@ -3771,6 +3892,7 @@ msgid "You did not give the correct password" msgstr "Nu aţi furnizat parola corectă" #: Mailman/Commands/cmd_set.py:236 Mailman/Commands/cmd_set.py:283 +#, fuzzy msgid "Bad argument: %(arg)s" msgstr "Argument eronat: %(arg)s" @@ -3849,6 +3971,7 @@ msgstr "" " (fără paranteze la adresa de email şi fără ghilimele!)\n" #: Mailman/Commands/cmd_subscribe.py:62 +#, fuzzy msgid "Bad digest specifier: %(arg)s" msgstr "Parametru eronat de specificare a rezumatului: %(arg)s" @@ -3857,6 +3980,7 @@ msgid "No valid address found to subscribe" msgstr "Nu am găsit o adresă validă pentru abonare" #: Mailman/Commands/cmd_subscribe.py:113 +#, fuzzy msgid "" "The email address you supplied is banned from this mailing list.\n" "If you think this restriction is erroneous, please contact the list\n" @@ -3895,6 +4019,7 @@ msgid "This list only supports digest subscriptions!" msgstr "Această listă suportă doar abonamente la rezumatele zilnice!" #: Mailman/Commands/cmd_subscribe.py:146 +#, fuzzy msgid "" "Your subscription request has been forwarded to the list administrator\n" "at %(listowner)s for review." @@ -3924,6 +4049,7 @@ msgstr "" " \n" #: Mailman/Commands/cmd_unsubscribe.py:62 +#, fuzzy msgid "%(address)s is not a member of the %(listname)s mailing list" msgstr "%(address)s nu este abonatal listei de discuţii %(listname)s" @@ -4174,6 +4300,7 @@ msgid "Chinese (Taiwan)" msgstr "Chineză (Taiwan)" #: Mailman/Deliverer.py:53 +#, fuzzy msgid "" "Note: Since this is a list of mailing lists, administrative\n" "notices like the password reminder will be sent to\n" @@ -4188,14 +4315,17 @@ msgid " (Digest mode)" msgstr " (Mod rezumat)" #: Mailman/Deliverer.py:79 +#, fuzzy msgid "Welcome to the \"%(realname)s\" mailing list%(digmode)s" msgstr "Bun venit la lista de discuţii \"%(realname)s\" - list%(digmode)s" #: Mailman/Deliverer.py:89 +#, fuzzy msgid "You have been unsubscribed from the %(realname)s mailing list" msgstr "Aţi fost dezabonat de la lista de discuţii %(realname)s" #: Mailman/Deliverer.py:116 +#, fuzzy msgid "%(listfullname)s mailing list reminder" msgstr "Mesaj de reamintire pentru lista de discuţii %(listfullname)s" @@ -4208,6 +4338,7 @@ msgid "Hostile subscription attempt detected" msgstr "A fost detectată o încercare de abonare ostilă" #: Mailman/Deliverer.py:169 +#, fuzzy msgid "" "%(address)s was invited to a different mailing\n" "list, but in a deliberate malicious attempt they tried to confirm the\n" @@ -4220,6 +4351,7 @@ msgstr "" "intervenţie din partea dumneavoastră." #: Mailman/Deliverer.py:188 +#, fuzzy msgid "" "You invited %(address)s to your list, but in a\n" "deliberate malicious attempt, they tried to confirm the invitation to a\n" @@ -4436,8 +4568,8 @@ msgid "" " membership.\n" "\n" "

                    You can control both the\n" -" number\n" +" number\n" " of reminders the member will receive and the\n" " No those messages too will get discarded. You may " "want\n" " to set up an\n" -" autoresponse\n" +" autoresponse\n" " message for email to the -owner and -admin address." msgstr "" "DeÅŸi detectorul Mailman al eÅŸecurilor de livrare este destul de robust, este " @@ -4688,12 +4820,13 @@ msgstr "" "dezabonat." #: Mailman/Gui/Bounce.py:194 +#, fuzzy msgid "" "Bad value for %(property)s: %(val)s" msgstr "" -"Valoare eronată pentru " -"%(property)s: %(val)s" +"Valoare eronată pentru %(property)s: %(val)s" #: Mailman/Gui/ContentFilter.py:30 msgid "Content filtering" @@ -4805,8 +4938,8 @@ msgstr "" "\n" "Liniile goale sunt ignorate\n" "\n" -"VedeÅ£i, de asemenea, pass_mime_types\n" +"VedeÅ£i, de asemenea, pass_mime_types\n" "pentru lista tipurilor de conÅ£inut acceptate." #: Mailman/Gui/ContentFilter.py:94 @@ -4824,8 +4957,8 @@ msgid "" "Use this option to remove each message attachment that does\n" " not have a matching content type. Requirements and formats " "are\n" -" exactly like filter_mime_types.\n" "\n" "

                    Note: if you add entries to this list but don't add\n" @@ -4917,12 +5050,13 @@ msgid "" msgstr "" "Una din aceste acţiuni este aplicată atunci când mesajul îndeplineşte cel " "puţin una din regulile de filtrare a conţinutului, însemnând că tipul top-" -"level este în lista filter_mime_types, sau tupil top-level nu este în " -"lista pass_mime_types, sau în cazul în care după filtrarea conţinutului, mesajul devine nul. " -"

                    Notaţi faptul că acţiunea nu este aplicată dacă după filtrare, mesajul " -"mai are conţinut. În acest caz, mesajul este întotdeauna re-trimis listei.\n" +"level este în lista filter_mime_types, sau tupil top-level nu este în lista pass_mime_types, sau în cazul în care după filtrarea " +"conţinutului, mesajul devine nul.

                    Notaţi faptul că acţiunea nu este " +"aplicată dacă după filtrare, mesajul mai are conţinut. În acest caz, " +"mesajul este întotdeauna re-trimis listei.\n" "

                    Când mesajele sunt ignorate, este înregistrat un mesaj în jurnal (log), " "conţinând Message-ID-ulmesajului ignorat. Când mesajele sunt respinse sau " "retrimise proprietarului listei, este inclus motivul acestei acţiuni în " @@ -4933,6 +5067,7 @@ msgstr "" "site-ului." #: Mailman/Gui/ContentFilter.py:171 +#, fuzzy msgid "Bad MIME type ignored: %(spectype)s" msgstr "Tip MIME invalid ignorat: %(spectype)s" @@ -5039,6 +5174,7 @@ msgid "" msgstr "Să trimită Mailman următoarea ediţie chiar acum, dacă nu este goală ?" #: Mailman/Gui/Digest.py:145 +#, fuzzy msgid "" "The next digest will be sent as volume\n" " %(volume)s, number %(number)s" @@ -5054,14 +5190,17 @@ msgid "There was no digest to send." msgstr "Nu au fost ediţii de trimis." #: Mailman/Gui/GUIBase.py:173 +#, fuzzy msgid "Invalid value for variable: %(property)s" msgstr "Valoare invalidă pentru parametrul: %(property)s" #: Mailman/Gui/GUIBase.py:178 +#, fuzzy msgid "Bad email address for option %(property)s: %(error)s" msgstr "Adresă de email eronată pentru opţiunea %(property)s: %(error)s" #: Mailman/Gui/GUIBase.py:204 +#, fuzzy msgid "" "The following illegal substitution variables were\n" " found in the %(property)s string:\n" @@ -5077,6 +5216,7 @@ msgstr "" "remedierea problemei." #: Mailman/Gui/GUIBase.py:218 +#, fuzzy msgid "" "Your %(property)s string appeared to\n" " have some correctable problems in its new value.\n" @@ -5175,8 +5315,8 @@ msgid "" "

                    In order to split the list ownership duties into\n" " administrators and moderators, you must\n" " set a separate moderator password,\n" -" and also provide the email\n" +" and also provide the email\n" " addresses of the list moderators. Note that the field you\n" " are changing here specifies the list administrators." msgstr "" @@ -5492,13 +5632,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5558,8 +5698,8 @@ msgstr "Headerul Reply-To explicit." msgid "" "This is the address set in the Reply-To: header\n" " when the reply_goes_to_list\n" +" href=\"?VARHELP=general/" +"reply_goes_to_list\">reply_goes_to_list\n" " option is set to Explicit address.\n" "\n" "

                    There are many reasons not to introduce or override the\n" @@ -5567,13 +5707,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5668,8 +5808,8 @@ msgid "" " member list addresses, but rather to the owner of those member\n" " lists. In that case, the value of this setting is appended to\n" " the member's account name for such notices. `-owner' is the\n" -" typical choice. This setting has no effect when \"umbrella_list" -"\"\n" +" typical choice. This setting has no effect when " +"\"umbrella_list\"\n" " is \"No\"." msgstr "" "Când parametrul \"umbrella_list\" etse setat, indicând faptul că această " @@ -5965,13 +6105,13 @@ msgid "" " does not affect the inclusion of the other List-*:\n" " headers.)" msgstr "" -"Headerul List-Post este unul din headerele recomandate de RFC 2369. Totuşi, în cazul " -"unor liste de anunţuri, doar un grup foarte restrâns de abonaţi are " -"dreptul de a publica mesaje, în timp ce restul abonaţilor nu are acest " -"drept. În acest caz, headerul List-Post este confuz. Selectaţi " -"Nu pentru a dezactiva acest header. (Acest fapt nu afectează însă " -"includerea celorlalte headere List-*:.)" +"Headerul List-Post este unul din headerele recomandate de RFC 2369. Totuşi, în " +"cazul unor liste de anunţuri, doar un grup foarte restrâns de " +"abonaţi are dreptul de a publica mesaje, în timp ce restul abonaţilor nu are " +"acest drept. În acest caz, headerul List-Post este confuz. " +"Selectaţi Nu pentru a dezactiva acest header. (Acest fapt nu " +"afectează însă includerea celorlalte headere List-*:.)" #: Mailman/Gui/General.py:489 #, fuzzy @@ -6562,6 +6702,7 @@ msgstr "" "se previne abonarea abuzivă, fără acordul utilizatorului." #: Mailman/Gui/Privacy.py:104 +#, fuzzy msgid "" "This section allows you to configure subscription and\n" " membership exposure policy. You can also control whether this\n" @@ -6745,8 +6886,8 @@ msgid "" " either individually or as a group. Any\n" " posting from a non-member who is not explicitly accepted,\n" " rejected, or discarded, will have their posting filtered by the\n" -" general\n" +" general\n" " non-member rules.\n" "\n" "

                    In the text boxes below, add one address per line; start the\n" @@ -6770,6 +6911,7 @@ msgid "By default, should new list member postings be moderated?" msgstr "În mod implicit, mesajele noilor membri sunt moderate?" #: Mailman/Gui/Privacy.py:218 +#, fuzzy msgid "" "Each list member has a moderation flag which says\n" " whether messages from the list member can be posted directly " @@ -6804,8 +6946,8 @@ msgstr "" "moderare ia valoarea acestei opţiuni. Dezactivaţi această opţiune pentru a " "accepta implicit mesajele noilor abonaţi. Activaţi această opţiune pentru a " "modera implicit primele mesaje ale noilor abonaţi. Veţi putea seta oricând " -"manual fanionul de moderare pentru fiecare abonat în parte, folosind paginile de administrare ale abonamentelor." +"manual fanionul de moderare pentru fiecare abonat în parte, folosind paginile de administrare ale abonamentelor." #: Mailman/Gui/Privacy.py:234 #, fuzzy @@ -6819,8 +6961,8 @@ msgid "" "If a member posts this many times, within a period of time\n" " the member is automatically moderated. Use 0 to disable. " "See\n" -" member_verbosity_interval for details on the time " "period.\n" "\n" @@ -7256,8 +7398,8 @@ msgstr "" "reţinute,\n" "respinse " "(bounced),\n" -"şi respectiv ignorate.\n" +"şi respectiv ignorate.\n" "Dacă nu este găsită nici o corespondenţă, atunci este aplicată această " "acţiune." @@ -7504,6 +7646,7 @@ msgstr "" "Regulile de filtrare incomplete vor fi ignorate." #: Mailman/Gui/Privacy.py:693 +#, fuzzy msgid "" "The header filter rule pattern\n" " '%(safepattern)s' is not a legal regular expression. This\n" @@ -7554,12 +7697,12 @@ msgid "" "

                    The body of the message can also be optionally scanned for\n" " Subject: and Keywords: headers, as\n" " specified by the topics_bodylines_limit\n" +" href=\"?VARHELP=topics/" +"topics_bodylines_limit\">topics_bodylines_limit\n" " configuration variable." msgstr "" -"Filtrul de topici categorizează fiecare e-mail sosit pe baza expresiilor regulate de " +"Filtrul de topici categorizează fiecare e-mail sosit pe baza expresiilor regulate de " "filtrare pe care le specificaţi mai jos.\n" "\n" "Dacă unul din headerele Subject:sau Keywords:\n" @@ -7650,6 +7793,7 @@ msgstr "" "incomplete vor fi ignorate." #: Mailman/Gui/Topics.py:135 +#, fuzzy msgid "" "The topic pattern '%(safepattern)s' is not a\n" " legal regular expression. It will be discarded." @@ -7842,6 +7986,7 @@ msgid "%(listinfo_link)s list run by %(owner_link)s" msgstr "Lista %(listinfo_link)s este deţinută de %(owner_link)s" #: Mailman/HTMLFormatter.py:57 +#, fuzzy msgid "%(realname)s administrative interface" msgstr "Interfaţa administrativă a %(realname)s" @@ -7850,6 +7995,7 @@ msgid " (requires authorization)" msgstr " (necesită autorizare)" #: Mailman/HTMLFormatter.py:61 +#, fuzzy msgid "Overview of all %(hostname)s mailing lists" msgstr "Toate listele de discuţii de pe %(hostname)s" @@ -7870,6 +8016,7 @@ msgid "; it was disabled by the list administrator" msgstr "; a fost dezactivată de către administratorul listei" #: Mailman/HTMLFormatter.py:146 +#, fuzzy msgid "" "; it was disabled due to excessive bounces. The\n" " last bounce was received on %(date)s" @@ -7882,6 +8029,7 @@ msgid "; it was disabled for unknown reasons" msgstr "; a fost dezactivată din motive necunoscute" #: Mailman/HTMLFormatter.py:151 +#, fuzzy msgid "Note: your list delivery is currently disabled%(reason)s." msgstr "" "Notă: livrarea mesajelor listei este în acest moment dezactivată%(reason)s." @@ -7895,6 +8043,7 @@ msgid "the list administrator" msgstr "administratorul listei" #: Mailman/HTMLFormatter.py:157 +#, fuzzy msgid "" "

                    %(note)s\n" "\n" @@ -7917,6 +8066,7 @@ msgstr "" " orice întrebări sau aveţi nevoie de ajutor." #: Mailman/HTMLFormatter.py:169 +#, fuzzy msgid "" "

                    We have received some recent bounces from your\n" " address. Your current bounce score is %(score)s out of " @@ -7935,6 +8085,7 @@ msgstr "" "resetat automat dacă problemele se rezolvă în timp scurt.

                    " #: Mailman/HTMLFormatter.py:181 +#, fuzzy msgid "" "(Note - you are subscribing to a list of mailing lists, so the %(type)s " "notice will be sent to the admin address for your membership, %(addr)s.)

                    " @@ -7983,6 +8134,7 @@ msgstr "" "moderatorului prin e-mail." #: Mailman/HTMLFormatter.py:208 +#, fuzzy msgid "" "This is %(also)sa private list, which means that the\n" " list of members is not available to non-members." @@ -7991,6 +8143,7 @@ msgstr "" " membrilor abonaţi nu este disponibilă persoanelor externe." #: Mailman/HTMLFormatter.py:211 +#, fuzzy msgid "" "This is %(also)sa hidden list, which means that the\n" " list of members is available only to the list administrator." @@ -8000,6 +8153,7 @@ msgstr "" "listei." #: Mailman/HTMLFormatter.py:214 +#, fuzzy msgid "" "This is %(also)sa public list, which means that the\n" " list of members list is available to everyone." @@ -8016,6 +8170,7 @@ msgstr "" " să nu fie uşor de recunoscut de către roboţii de spam)." #: Mailman/HTMLFormatter.py:222 +#, fuzzy msgid "" "

                    (Note that this is an umbrella list, intended to\n" " have only other mailing lists as members. Among other things,\n" @@ -8031,6 +8186,7 @@ msgid "either " msgstr "sau " #: Mailman/HTMLFormatter.py:256 +#, fuzzy msgid "" "To unsubscribe from %(realname)s, get a password reminder,\n" " or change your subscription options %(either)senter your " @@ -8062,12 +8218,14 @@ msgid "" msgstr " Dacă lăsaţi acest câmp gol, vi se va cere adresa de e-mail" #: Mailman/HTMLFormatter.py:277 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " members.)" msgstr "(%(which)s este disponibilă numai membrilor listei.)" #: Mailman/HTMLFormatter.py:281 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " administrator.)" @@ -8127,6 +8285,7 @@ msgid "The current archive" msgstr "Arhiva curentă" #: Mailman/Handlers/Acknowledge.py:59 +#, fuzzy msgid "%(realname)s post acknowledgement" msgstr "Confirmare de publicare pe %(realname)s" @@ -8139,6 +8298,7 @@ msgid "" msgstr "" #: Mailman/Handlers/CalcRecips.py:79 +#, fuzzy msgid "" "Your urgent message to the %(realname)s mailing list was not authorized for\n" "delivery. The original message as received by Mailman is attached.\n" @@ -8219,6 +8379,7 @@ msgid "Message may contain administrivia" msgstr "S-ar putea ca mesajul să conţină comenzi administrative" #: Mailman/Handlers/Hold.py:84 +#, fuzzy msgid "" "Please do *not* post administrative requests to the mailing\n" "list. If you wish to subscribe, visit %(listurl)s or send a message with " @@ -8262,10 +8423,12 @@ msgid "Posting to a moderated newsgroup" msgstr "Publicarea la un newsgroup supravegheat" #: Mailman/Handlers/Hold.py:252 +#, fuzzy msgid "Your message to %(listname)s awaits moderator approval" msgstr "Mesajul dumneavoastră la %(listname)s aşteaptă aprobarea moderatorului" #: Mailman/Handlers/Hold.py:271 +#, fuzzy msgid "%(listname)s post from %(sender)s requires approval" msgstr "Mesajul trimis de %(sender)s pentru %(listname)s necesită aprobare" @@ -8307,6 +8470,7 @@ msgid "After content filtering, the message was empty" msgstr "După filtrarea conţinutului, mesajul a rămas gol" #: Mailman/Handlers/MimeDel.py:269 +#, fuzzy msgid "" "The attached message matched the %(listname)s mailing list's content " "filtering\n" @@ -8349,6 +8513,7 @@ msgid "The attached message has been automatically discarded." msgstr "Mesajul ataşat a fost automat ignorat." #: Mailman/Handlers/Replybot.py:75 +#, fuzzy msgid "Auto-response for your message to the \"%(realname)s\" mailing list" msgstr "" "Rărpunsul automat pentru mesajul dumneavoastră către lista de discuţii " @@ -8374,6 +8539,7 @@ msgid "HTML attachment scrubbed and removed" msgstr "Ataşamentul HTML a fost eliminat" #: Mailman/Handlers/Scrubber.py:234 Mailman/Handlers/Scrubber.py:259 +#, fuzzy msgid "" "An HTML attachment was scrubbed...\n" "URL: %(url)s\n" @@ -8428,6 +8594,7 @@ msgstr "" "Url : %(url)s\n" #: Mailman/Handlers/Scrubber.py:347 +#, fuzzy msgid "Skipped content of type %(partctype)s\n" msgstr "Am sărit conţinutul de tipul %(partctype)s\n" @@ -8459,6 +8626,7 @@ msgid "Message rejected by filter rule match" msgstr "Mesaj respins prin activarea unei reguli de filtrare" #: Mailman/Handlers/ToDigest.py:173 +#, fuzzy msgid "%(realname)s Digest, Vol %(volume)d, Issue %(issue)d" msgstr "Rezumat %(realname)s, Vol %(volume)d, Nr. %(issue)d" @@ -8495,6 +8663,7 @@ msgid "End of " msgstr "Sfârşitul " #: Mailman/ListAdmin.py:309 +#, fuzzy msgid "Posting of your message titled \"%(subject)s\"" msgstr "Public mesajul dumneavoastră intitulat: \"%(subject)s\"" @@ -8507,6 +8676,7 @@ msgid "Forward of moderated message" msgstr "Forwardez mesajul moderat" #: Mailman/ListAdmin.py:405 +#, fuzzy msgid "New subscription request to list %(realname)s from %(addr)s" msgstr "Cerere nouă de abonare la lista %(realname)s de la %(addr)s" @@ -8520,6 +8690,7 @@ msgid "via admin approval" msgstr "Continuă aprobarea în aşteptare" #: Mailman/ListAdmin.py:466 +#, fuzzy msgid "New unsubscription request from %(realname)s by %(addr)s" msgstr "Cerere nouă de părăsire a listei %(realname)s de către %(addr)s" @@ -8532,10 +8703,12 @@ msgid "Original Message" msgstr "Mesaj original" #: Mailman/ListAdmin.py:526 +#, fuzzy msgid "Request to mailing list %(realname)s rejected" msgstr "Cerera către lista %(realname)s a fost respinsă" #: Mailman/MTA/Manual.py:66 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been created via the through-the-web\n" "interface. In order to complete the activation of this mailing list, the\n" @@ -8562,14 +8735,17 @@ msgstr "" "următoarelor linii, şi probabil prin rularea programului 'newaliases':\n" #: Mailman/MTA/Manual.py:82 +#, fuzzy msgid "## %(listname)s mailing list" msgstr "## Lista de discuţii %(listname)s" #: Mailman/MTA/Manual.py:99 +#, fuzzy msgid "Mailing list creation request for list %(listname)s" msgstr "Cerere de creare de listă nouă pentru %(listname)s" #: Mailman/MTA/Manual.py:113 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been removed via the through-the-web\n" "interface. In order to complete the de-activation of this mailing list, " @@ -8587,6 +8763,7 @@ msgstr "" "Iată liniile din fişierul /etc/aliases ce trebuie şterse:\n" #: Mailman/MTA/Manual.py:123 +#, fuzzy msgid "" "\n" "To finish removing your mailing list, you must edit your /etc/aliases (or\n" @@ -8603,14 +8780,17 @@ msgstr "" "## %(listname)s mailing list" #: Mailman/MTA/Manual.py:142 +#, fuzzy msgid "Mailing list removal request for list %(listname)s" msgstr "Cerere de ştergere a listei de discuţii %(listname)s" #: Mailman/MTA/Postfix.py:442 +#, fuzzy msgid "checking permissions on %(file)s" msgstr "verific permisiunile pentru %(file)s" #: Mailman/MTA/Postfix.py:452 +#, fuzzy msgid "%(file)s permissions must be 0664 (got %(octmode)s)" msgstr "Permisiunile %(file)s trebuie să fie 0664 (sunt %(octmode)s)" @@ -8624,37 +8804,45 @@ msgid "(fixing)" msgstr "(rezolv)" #: Mailman/MTA/Postfix.py:470 +#, fuzzy msgid "checking ownership of %(dbfile)s" msgstr "verific posesorul fişierelor %(dbfile)s" #: Mailman/MTA/Postfix.py:478 +#, fuzzy msgid "%(dbfile)s owned by %(owner)s (must be owned by %(user)s" msgstr "" "%(dbfile)s este deţinut de %(owner)s (trebuie să fie deţinut de %(user)s" #: Mailman/MTA/Postfix.py:491 +#, fuzzy msgid "%(dbfile)s permissions must be 0664 (got %(octmode)s)" msgstr "Permisiunile %(dbfile)s trebuie să fie 0664 (sunt %(octmode)s)" #: Mailman/MailList.py:219 +#, fuzzy msgid "Your confirmation is required to join the %(listname)s mailing list" msgstr "Invitaie abonare la lista de discuii %(listname)s" #: Mailman/MailList.py:230 +#, fuzzy msgid "Your confirmation is required to leave the %(listname)s mailing list" msgstr "" "Este necesară confirmarea dumneavoastră pentru părăsirea listei de discuţii " "%(listname)s" #: Mailman/MailList.py:999 Mailman/MailList.py:1492 +#, fuzzy msgid " from %(remote)s" msgstr " de %(remote)s" #: Mailman/MailList.py:1043 +#, fuzzy msgid "subscriptions to %(realname)s require moderator approval" msgstr "abonamentele la %(realname)s necesită aprobarea moderatorului" #: Mailman/MailList.py:1125 bin/add_members:299 +#, fuzzy msgid "%(realname)s subscription notification" msgstr "notificare de abonare la %(realname)s" @@ -8663,6 +8851,7 @@ msgid "unsubscriptions require moderator approval" msgstr "părăsirea listei necesită aprobarea moderatorului" #: Mailman/MailList.py:1166 +#, fuzzy msgid "%(realname)s unsubscribe notification" msgstr "notificare de părăsire a listei %(realname)s" @@ -8682,6 +8871,7 @@ msgid "via web confirmation" msgstr "Cod de confirmare eronat" #: Mailman/MailList.py:1396 +#, fuzzy msgid "subscriptions to %(name)s require administrator approval" msgstr "abonarea la %(name)s necesită aprobarea administratorului" @@ -8700,6 +8890,7 @@ msgid "Last autoresponse notification for today" msgstr "Ultima notificare de răspuns automat de azi" #: Mailman/Queue/BounceRunner.py:360 +#, fuzzy msgid "" "The attached message was received as a bounce, but either the bounce format\n" "was not recognized, or no member addresses could be extracted from it. " @@ -8788,6 +8979,7 @@ msgid "Original message suppressed by Mailman site configuration\n" msgstr "" #: Mailman/htmlformat.py:675 +#, fuzzy msgid "Delivered by Mailman
                    version %(version)s" msgstr "Livrat de Mailman
                    versiunea %(version)s" @@ -8876,6 +9068,7 @@ msgid "Server Local Time" msgstr "Ora locală pe server" #: Mailman/i18n.py:180 +#, fuzzy msgid "" "%(wday)s %(mon)s %(day)2i %(hh)02i:%(mm)02i:%(ss)02i %(tzname)s %(year)04i" msgstr "" @@ -8966,6 +9159,7 @@ msgstr "" " --welcome-msg=\n" #: bin/add_members:162 bin/add_members:172 +#, fuzzy msgid "Already a member: %(member)s" msgstr "Este deja membru: %(member)s" @@ -8974,10 +9168,12 @@ msgid "Bad/Invalid email address: blank line" msgstr "Adresă eronată/invalidă: rând gol" #: bin/add_members:180 +#, fuzzy msgid "Bad/Invalid email address: %(member)s" msgstr "Adresă eronată/invalidă: %(member)s" #: bin/add_members:182 +#, fuzzy msgid "Hostile address (illegal characters): %(member)s" msgstr "Adresă ostilă (caractere ilegale): %(member)s" @@ -8987,14 +9183,17 @@ msgid "Invited: %(member)s" msgstr "A fost abonat: %(member)s" #: bin/add_members:187 +#, fuzzy msgid "Subscribed: %(member)s" msgstr "A fost abonat: %(member)s" #: bin/add_members:237 +#, fuzzy msgid "Bad argument to -w/--welcome-msg: %(arg)s" msgstr "Argument eronat pentru -w/--welcome-msg: %(arg)s" #: bin/add_members:244 +#, fuzzy msgid "Bad argument to -a/--admin-notify: %(arg)s" msgstr "Argument eronat pentru -a/--admin-notify: %(arg)s" @@ -9011,6 +9210,7 @@ msgstr "" #: bin/add_members:261 bin/config_list:110 bin/export.py:271 bin/find_member:97 #: bin/inject:91 bin/list_admins:90 bin/list_members:252 bin/sync_members:222 #: cron/bumpdigests:86 +#, fuzzy msgid "No such list: %(listname)s" msgstr "Nu ecistă o astfel de listă: %(listname)s" @@ -9021,6 +9221,7 @@ msgid "Nothing to do." msgstr "Nimic de făcut." #: bin/arch:19 +#, fuzzy msgid "" "Rebuild a list's archive.\n" "\n" @@ -9120,6 +9321,7 @@ msgid "listname is required" msgstr "numele listei este necesar" #: bin/arch:143 bin/change_pw:107 bin/config_list:257 +#, fuzzy msgid "" "No such list \"%(listname)s\"\n" "%(e)s" @@ -9128,10 +9330,12 @@ msgstr "" "%(e)s" #: bin/arch:168 +#, fuzzy msgid "Cannot open mbox file %(mbox)s: %(msg)s" msgstr "Nu pot deschide fişierul mbox %(mbox)s: %(msg)s" #: bin/b4b5-archfix:19 +#, fuzzy msgid "" "Fix the MM2.1b4 archives.\n" "\n" @@ -9237,6 +9441,7 @@ msgstr "" "SHA1 hexdigest.\n" #: bin/change_pw:145 +#, fuzzy msgid "Bad arguments: %(strargs)s" msgstr "Argumente eronate: %(strargs)s" @@ -9245,14 +9450,17 @@ msgid "Empty list passwords are not allowed" msgstr "Nu sunt permise parole nule pentru listă" #: bin/change_pw:181 +#, fuzzy msgid "New %(listname)s password: %(notifypassword)s" msgstr "Noua parolă a listei %(listname)s: %(notifypassword)s" #: bin/change_pw:190 +#, fuzzy msgid "Your new %(listname)s list password" msgstr "Noua parolă pentru lista %(listname)s" #: bin/change_pw:191 +#, fuzzy msgid "" "The site administrator at %(hostname)s has changed the password for your\n" "mailing list %(listname)s. It is now\n" @@ -9334,6 +9542,7 @@ msgid "List:" msgstr "Lista:" #: bin/check_db:148 +#, fuzzy msgid " %(file)s: okay" msgstr " %(file)s: OK" @@ -9359,43 +9568,53 @@ msgstr "" "\n" #: bin/check_perms:110 +#, fuzzy msgid " checking gid and mode for %(path)s" msgstr " verific gid şi mode pentru %(path)s" #: bin/check_perms:122 +#, fuzzy msgid "%(path)s bad group (has: %(groupname)s, expected %(MAILMAN_GROUP)s)" msgstr "" "grup eronat %(path)s (are %(groupname)s, trebuie să aibă %(MAILMAN_GROUP)s)" #: bin/check_perms:151 +#, fuzzy msgid "directory permissions must be %(octperms)s: %(path)s" msgstr "permisiunile de director trebuie să fie %(octperms)s: %(path)s" #: bin/check_perms:160 +#, fuzzy msgid "source perms must be %(octperms)s: %(path)s" msgstr "permisiunile pe surse trebuie să fie %(octperms)s: %(path)s" #: bin/check_perms:171 +#, fuzzy msgid "article db files must be %(octperms)s: %(path)s" msgstr "fişierele articol db trebuie să fie %(octperms)s: %(path)s" #: bin/check_perms:183 +#, fuzzy msgid "checking mode for %(prefix)s" msgstr "verific permisiunile pentru %(prefix)s" #: bin/check_perms:193 +#, fuzzy msgid "WARNING: directory does not exist: %(d)s" msgstr "ATENŢIE: nu există directorul: %(d)s" #: bin/check_perms:197 +#, fuzzy msgid "directory must be at least 02775: %(d)s" msgstr "directorul trebuie să aibă cel puţin 02775: %(d)s" #: bin/check_perms:209 +#, fuzzy msgid "checking perms on %(private)s" msgstr "verific permisiunile la %(private)s" #: bin/check_perms:214 +#, fuzzy msgid "%(private)s must not be other-readable" msgstr "%(private)s nu trebuie să poată fi citit de alţii" @@ -9413,6 +9632,7 @@ msgid "mbox file must be at least 0660:" msgstr "Permisiunile fişierului mbox trebuie să fie măcar 0660" #: bin/check_perms:263 +#, fuzzy msgid "%(dbdir)s \"other\" perms must be 000" msgstr "\"restul\" permisiunilor %(dbdir)s trebuie să fie 000" @@ -9421,26 +9641,32 @@ msgid "checking cgi-bin permissions" msgstr "verific permisiunile cgi-bin" #: bin/check_perms:278 +#, fuzzy msgid " checking set-gid for %(path)s" msgstr " verific set-gid pentru %(path)s" #: bin/check_perms:282 +#, fuzzy msgid "%(path)s must be set-gid" msgstr "%(path)s trebuie să aibă set-gid" #: bin/check_perms:292 +#, fuzzy msgid "checking set-gid for %(wrapper)s" msgstr "verific set-gid pentru %(wrapper)s" #: bin/check_perms:296 +#, fuzzy msgid "%(wrapper)s must be set-gid" msgstr "%(wrapper)s trebuie să aibă set-gid" #: bin/check_perms:306 +#, fuzzy msgid "checking permissions on %(pwfile)s" msgstr "verific permisiunile pentru %(pwfile)s" #: bin/check_perms:315 +#, fuzzy msgid "%(pwfile)s permissions must be exactly 0640 (got %(octmode)s)" msgstr "" "permisiunile pentru %(pwfile)s trebuie să fie exact 0640 (am detectat " @@ -9451,10 +9677,12 @@ msgid "checking permissions on list data" msgstr "verific permisiunile pe datele listei" #: bin/check_perms:348 +#, fuzzy msgid " checking permissions on: %(path)s" msgstr " verific permisiunile la: %(path)s" #: bin/check_perms:356 +#, fuzzy msgid "file permissions must be at least 660: %(path)s" msgstr "permisiunile de fişier trebuie să fie cel puţin 660: %(path)s" @@ -9543,6 +9771,7 @@ msgid "Unix-From line changed: %(lineno)d" msgstr "Linia Unix-From a fost modificată: %(lineno)d" #: bin/cleanarch:111 +#, fuzzy msgid "Bad status number: %(arg)s" msgstr "Număr de stare eronat: %(arg)s" @@ -9695,10 +9924,12 @@ msgid " original address removed:" msgstr " adresa originală a fost ştearsă:" #: bin/clone_member:202 +#, fuzzy msgid "Not a valid email address: %(toaddr)s" msgstr "Adresă de email invalidă: %(toaddr)s" #: bin/clone_member:215 +#, fuzzy msgid "" "Error opening list \"%(listname)s\", skipping.\n" "%(e)s" @@ -9831,22 +10062,27 @@ msgid "legal values are:" msgstr "valorile permise sunt:" #: bin/config_list:270 +#, fuzzy msgid "attribute \"%(k)s\" ignored" msgstr "atributul \"%(k)s\" a fost ignorat" #: bin/config_list:273 +#, fuzzy msgid "attribute \"%(k)s\" changed" msgstr "atributul \"%(k)s\" a fost modificat" #: bin/config_list:279 +#, fuzzy msgid "Non-standard property restored: %(k)s" msgstr "Proprietatea non-standard a fost restaurată: %(k)s" #: bin/config_list:288 +#, fuzzy msgid "Invalid value for property: %(k)s" msgstr "Valoare invalidă pentru proprietatea: %(k)s" #: bin/config_list:291 +#, fuzzy msgid "Bad email address for option %(k)s: %(v)s" msgstr "Adresă de email invalidă pentru opţiunea %(k)s: %(v)s" @@ -9912,14 +10148,17 @@ msgstr "" " Nu tipăreşte mesajele de stare.\n" #: bin/discard:94 +#, fuzzy msgid "Ignoring non-held message: %(f)s" msgstr "Ignor mesajul ne-reţinut: %(f)s" #: bin/discard:100 +#, fuzzy msgid "Ignoring held msg w/bad id: %(f)s" msgstr "Ignor mesajul reţinut cu id eronat: %(f)s" #: bin/discard:112 +#, fuzzy msgid "Discarded held msg #%(id)s for list %(listname)s" msgstr "Am aruncat mesajul reţinut: msg #%(id)s pentru lista %(listname)s" @@ -9999,6 +10238,7 @@ msgid "No filename given." msgstr "Lipseşte numele fişierului." #: bin/dumpdb:108 +#, fuzzy msgid "Bad arguments: %(pargs)s" msgstr "Argumente eronate: %(pargs)s" @@ -10017,6 +10257,7 @@ msgid "[----- end %(typename)s file -----]" msgstr "[----- end %(typename)s file -----]" #: bin/dumpdb:142 +#, fuzzy msgid "<----- start object %(cnt)s ----->" msgstr "<----- start object %(cnt)s ----->" @@ -10228,6 +10469,7 @@ msgid "Setting web_page_url to: %(web_page_url)s" msgstr "Setez web_page_url la valoarea: %(web_page_url)s" #: bin/fix_url.py:88 +#, fuzzy msgid "Setting host_name to: %(mailhost)s" msgstr "Setez host_name la valoarea: %(mailhost)s" @@ -10321,6 +10563,7 @@ msgstr "" "standard input is used.\n" #: bin/inject:84 +#, fuzzy msgid "Bad queue directory: %(qdir)s" msgstr "Director de coadă eronat: %(qdir)s" @@ -10329,6 +10572,7 @@ msgid "A list name is required" msgstr "Un nume de listă este obligatoriu" #: bin/list_admins:20 +#, fuzzy msgid "" "List all the owners of a mailing list.\n" "\n" @@ -10377,6 +10621,7 @@ msgstr "" "Puteţi avea mai multe nume de liste în linia de comandă.\n" #: bin/list_admins:97 +#, fuzzy msgid "List: %(listname)s, \tOwners: %(owners)s" msgstr "Lista: %(listname)s, \tPosesori: %(owners)s" @@ -10542,10 +10787,12 @@ msgstr "" "status.\n" #: bin/list_members:198 +#, fuzzy msgid "Bad --nomail option: %(why)s" msgstr "Opţiune eronată --nomail: %(why)s" #: bin/list_members:209 +#, fuzzy msgid "Bad --digest option: %(kind)s" msgstr "Opţiune eronată --digest: %(kind)s" @@ -10786,6 +11033,7 @@ msgstr "" " next time a message is written to them\n" #: bin/mailmanctl:152 +#, fuzzy msgid "PID unreadable in: %(pidfile)s" msgstr "PID nu poate fi citit în: %(pidfile)s" @@ -10794,6 +11042,7 @@ msgid "Is qrunner even running?" msgstr "Dar qrunner funcţionează oare?" #: bin/mailmanctl:160 +#, fuzzy msgid "No child with pid: %(pid)s" msgstr "Nici un copil cu pid: %(pid)s" @@ -10851,10 +11100,12 @@ msgstr "" "Exiting." #: bin/mailmanctl:280 cron/mailpasswds:119 +#, fuzzy msgid "Site list is missing: %(sitelistname)s" msgstr "Lista site-ului lipseşte: %(sitelistname)s" #: bin/mailmanctl:305 +#, fuzzy msgid "Run this program as root or as the %(name)s user, or use -u." msgstr "" "Rulaţi acest program ca şi root sau ca şi userul %(name)s, sau folosiţi " @@ -10865,6 +11116,7 @@ msgid "No command given." msgstr "Lipseşte comanda." #: bin/mailmanctl:339 +#, fuzzy msgid "Bad command: %(command)s" msgstr "Comandă eronată: %(command)s" @@ -10944,6 +11196,7 @@ msgid "list creator" msgstr "creatorul listei" #: bin/mmsitepass:86 +#, fuzzy msgid "New %(pwdesc)s password: " msgstr "Noua %(pwdesc)s parolă: " @@ -11133,6 +11386,7 @@ msgstr "" "\n" #: bin/newlist:162 +#, fuzzy msgid "Unknown language: %(lang)s" msgstr "Limbă necunoscută: %(lang)s" @@ -11145,6 +11399,7 @@ msgid "Enter the email of the person running the list: " msgstr "Introduceţi adresa de email a persoanei ce administrează lista: " #: bin/newlist:193 +#, fuzzy msgid "Initial %(listname)s password: " msgstr "Parola iniţială a listei %(listname)s: " @@ -11154,11 +11409,12 @@ msgstr "Parola listei nu poate fi nulă" #: bin/newlist:220 msgid "" -" - owner addresses need to be fully-qualified names like \"owner@example.com" -"\", not just \"owner\"." +" - owner addresses need to be fully-qualified names like \"owner@example." +"com\", not just \"owner\"." msgstr "" #: bin/newlist:244 +#, fuzzy msgid "Hit enter to notify %(listname)s owner..." msgstr "Apăsaţi enter pentru a notifica proprietarul listei %(listname)s" @@ -11291,6 +11547,7 @@ msgstr "" "displayed by the -l switch.\n" #: bin/qrunner:179 +#, fuzzy msgid "%(name)s runs the %(runnername)s qrunner" msgstr "%(name)s rulează qrunner-ul %(runnername)s" @@ -11452,18 +11709,22 @@ msgstr "" "\n" #: bin/remove_members:156 +#, fuzzy msgid "Could not open file for reading: %(filename)s." msgstr "Nu am putut deschide fişierul pentru citire: %(filename)s." #: bin/remove_members:163 +#, fuzzy msgid "Error opening list %(listname)s... skipping." msgstr "Eroare la deschiderea listei %(listname)s... trec mai departe." #: bin/remove_members:173 +#, fuzzy msgid "No such member: %(addr)s" msgstr "Nu există acest membru: %(addr)s" #: bin/remove_members:178 +#, fuzzy msgid "User `%(addr)s' removed from list: %(listname)s." msgstr "Utilizatorul '%(addr)s' a fost şters din lista: %(listname)s." @@ -11510,6 +11771,7 @@ msgid "Changing passwords for list: %(listname)s" msgstr "Cerere de ştergere a listei de discuţii %(listname)s" #: bin/reset_pw.py:83 +#, fuzzy msgid "New password for member %(member)40s: %(randompw)s" msgstr "Noua parolă pentru utilizatorul %(member)40s: %(randompw)s" @@ -11555,18 +11817,22 @@ msgstr "" "\n" #: bin/rmlist:73 bin/rmlist:76 +#, fuzzy msgid "Removing %(msg)s" msgstr "Şterg %(msg)s" #: bin/rmlist:81 +#, fuzzy msgid "%(listname)s %(msg)s not found as %(filename)s" msgstr "nu am găsit %(listname)s %(msg)s ca şi %(filename)s" #: bin/rmlist:105 +#, fuzzy msgid "No such list (or list already deleted): %(listname)s" msgstr "Lista aceata nu există (sau a fost ştearsă deja): %(listname)s" #: bin/rmlist:108 +#, fuzzy msgid "No such list: %(listname)s. Removing its residual archives." msgstr "Nu există lista: %(listname)s. Şterg arhivele ei reziduale." @@ -11627,6 +11893,7 @@ msgstr "" "Exeplu: show_qfiles qfiles/shunt/*.pck\n" #: bin/sync_members:19 +#, fuzzy msgid "" "Synchronize a mailing list's membership with a flat file.\n" "\n" @@ -11768,6 +12035,7 @@ msgstr "" "\n" #: bin/sync_members:115 +#, fuzzy msgid "Bad choice: %(yesno)s" msgstr "Opţiune eronată: %(yesno)s" @@ -11784,6 +12052,7 @@ msgid "No argument to -f given" msgstr "Lipseşte argumentul pentru parametrul -f" #: bin/sync_members:172 +#, fuzzy msgid "Illegal option: %(opt)s" msgstr "Opţiune ilegală: %(opt)s" @@ -11796,6 +12065,7 @@ msgid "Must have a listname and a filename" msgstr "Trebuie să aveţi un nume de listă şi un nume de fişier" #: bin/sync_members:191 +#, fuzzy msgid "Cannot read address file: %(filename)s: %(msg)s" msgstr "Nu pot citi fişierul cu adrese: %(filename)s: %(msg)s" @@ -11812,14 +12082,17 @@ msgid "You must fix the preceding invalid addresses first." msgstr "Trebuie să corectaţi mai întâi adresa invalidă precedentă." #: bin/sync_members:264 +#, fuzzy msgid "Added : %(s)s" msgstr "Adăugat : %(s)s" #: bin/sync_members:289 +#, fuzzy msgid "Removed: %(s)s" msgstr "Şters: %(s)s" #: bin/transcheck:19 +#, fuzzy msgid "" "\n" "Check a given Mailman translation, making sure that variables and\n" @@ -11936,6 +12209,7 @@ msgstr "" "%(e)s" #: bin/update:20 +#, fuzzy msgid "" "Perform all necessary upgrades.\n" "\n" @@ -11972,10 +12246,12 @@ msgstr "" "de la versiuni începând cu 1.0b4 (?).\n" #: bin/update:107 +#, fuzzy msgid "Fixing language templates: %(listname)s" msgstr "Repar şabloanele de limbă: %(listname)s" #: bin/update:196 bin/update:711 +#, fuzzy msgid "WARNING: could not acquire lock for list: %(listname)s" msgstr "ATENŢIE: nu am putut asigura asigura lista: %(listname)s" @@ -12000,6 +12276,7 @@ msgstr "" "aşa că îl voi redenumi în %(mbox_dir)s.tmp şi voi continua." #: bin/update:255 +#, fuzzy msgid "" "\n" "%(listname)s has both public and private mbox archives. Since this list\n" @@ -12047,6 +12324,7 @@ msgid "- updating old private mbox file" msgstr " actualizez vechiul fişier privat mbox" #: bin/update:295 +#, fuzzy msgid "" " unknown file in the way, moving\n" " %(o_pri_mbox_file)s\n" @@ -12063,6 +12341,7 @@ msgid "- updating old public mbox file" msgstr "- actualizez vechiul fişier public mbox" #: bin/update:317 +#, fuzzy msgid "" " unknown file in the way, moving\n" " %(o_pub_mbox_file)s\n" @@ -12092,18 +12371,22 @@ msgid "- %(o_tmpl)s doesn't exist, leaving untouched" msgstr "- %(o_tmpl)s nu există, las locul neatins" #: bin/update:396 +#, fuzzy msgid "removing directory %(src)s and everything underneath" msgstr "şterg directorul %(src)s şi tot conţinutul acestuia" #: bin/update:399 +#, fuzzy msgid "removing %(src)s" msgstr "şterg %(src)s" #: bin/update:403 +#, fuzzy msgid "Warning: couldn't remove %(src)s -- %(rest)s" msgstr "Atenţie: nu am putut şterge %(src)s -- %(rest)s" #: bin/update:408 +#, fuzzy msgid "couldn't remove old file %(pyc)s -- %(rest)s" msgstr "nu am putut şterge vechiul fişier %(pyc)s -- %(rest)s" @@ -12117,6 +12400,7 @@ msgid "Warning! Not a directory: %(dirpath)s" msgstr "Director de coadă eronat: %(dirpath)s" #: bin/update:530 +#, fuzzy msgid "message is unparsable: %(filebase)s" msgstr "mesajul nu poate fi parcurs: %(filebase)s" @@ -12135,10 +12419,12 @@ msgid "Updating Mailman 2.1.4 pending.pck database" msgstr "Actualizez vechea bază de date pending.pck de tip Mailman 2.1.4" #: bin/update:598 +#, fuzzy msgid "Ignoring bad pended data: %(key)s: %(val)s" msgstr "Ignor date în aşteptare eronate: %(key)s: %(val)s" #: bin/update:614 +#, fuzzy msgid "WARNING: Ignoring duplicate pending ID: %(id)s." msgstr "ATENŢIE: Ignor ID duplicat în aşteptare: %(id)s." @@ -12164,6 +12450,7 @@ msgid "done" msgstr "gata" #: bin/update:691 +#, fuzzy msgid "Updating mailing list: %(listname)s" msgstr "Actualizez lista de discuţii: %(listname)s" @@ -12229,6 +12516,7 @@ msgid "No updates are necessary." msgstr "Nu sunt necesare actualizări." #: bin/update:796 +#, fuzzy msgid "" "Downgrade detected, from version %(hexlversion)s to version %(hextversion)s\n" "This is probably not safe.\n" @@ -12240,10 +12528,12 @@ msgstr "" "Termin." #: bin/update:801 +#, fuzzy msgid "Upgrading from version %(hexlversion)s to %(hextversion)s" msgstr "Actualizez de la versiunea %(hexlversion)s la %(hextversion)s" #: bin/update:810 +#, fuzzy msgid "" "\n" "ERROR:\n" @@ -12527,6 +12817,7 @@ msgstr "" " " #: bin/withlist:175 +#, fuzzy msgid "Unlocking (but not saving) list: %(listname)s" msgstr "Deblochez (dar nu salvez) lista: %(listname)s" @@ -12535,6 +12826,7 @@ msgid "Finalizing" msgstr "Finalizez" #: bin/withlist:188 +#, fuzzy msgid "Loading list %(listname)s" msgstr "Încarc lista %(listname)s" @@ -12547,6 +12839,7 @@ msgid "(unlocked)" msgstr "(deblocat)" #: bin/withlist:197 +#, fuzzy msgid "Unknown list: %(listname)s" msgstr "Listă necunoscută: %(listname)s" @@ -12559,18 +12852,22 @@ msgid "--all requires --run" msgstr "--all requires --run" #: bin/withlist:266 +#, fuzzy msgid "Importing %(module)s..." msgstr "Importez %(module)s..." #: bin/withlist:270 +#, fuzzy msgid "Running %(module)s.%(callable)s()..." msgstr "Rulez %(module)s.%(callable)s()..." #: bin/withlist:291 +#, fuzzy msgid "The variable `m' is the %(listname)s MailList instance" msgstr "Parametrul 'm' este instanţa listei %(listname)s" #: cron/bumpdigests:19 +#, fuzzy msgid "" "Increment the digest volume number and reset the digest number to one.\n" "\n" @@ -12598,6 +12895,7 @@ msgstr "" "prelucrate.\n" #: cron/checkdbs:20 +#, fuzzy msgid "" "Check for pending admin requests and mail the list owners if necessary.\n" "\n" @@ -12627,6 +12925,7 @@ msgstr "" "\n" #: cron/checkdbs:121 +#, fuzzy msgid "%(count)d %(realname)s moderator request(s) waiting" msgstr "%(count)d %(realname)s cereri moderate în aşteptare" @@ -12653,6 +12952,7 @@ msgstr "" "Publicări în aşteptare:" #: cron/checkdbs:169 +#, fuzzy msgid "" "From: %(sender)s on %(date)s\n" "Subject: %(subject)s\n" @@ -12765,6 +13065,7 @@ msgstr "" "\n" #: cron/mailpasswds:19 +#, fuzzy msgid "" "Send password reminders for all lists to all users.\n" "\n" @@ -12821,10 +13122,12 @@ msgid "Password // URL" msgstr "Parola // URL" #: cron/mailpasswds:222 +#, fuzzy msgid "%(host)s mailing list memberships reminder" msgstr "Mesaj de reamintire a datelor listei de discuţii de la %(host)s" #: cron/nightly_gzip:19 +#, fuzzy msgid "" "Re-generate the Pipermail gzip'd archive flat files.\n" "\n" @@ -12924,8 +13227,8 @@ msgstr "" #~ " applied" #~ msgstr "" #~ "Textul ce este inclus în orice\n" -#~ " notă de respingere trimisă\n" #~ " membrilor supravegheaţi ce publică mesaje pe acestă listă." diff --git a/messages/ru/LC_MESSAGES/mailman.po b/messages/ru/LC_MESSAGES/mailman.po index 390b7271..7cf241b4 100644 --- a/messages/ru/LC_MESSAGES/mailman.po +++ b/messages/ru/LC_MESSAGES/mailman.po @@ -16,8 +16,8 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" -"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " +"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" "X-Generator: Poedit 2.0.6\n" #: Mailman/Archiver/HyperArch.py:124 @@ -70,10 +70,12 @@ msgid "

                    Currently, there are no archives.

                    " msgstr "

                    Ð’ наÑтоÑщее Ð²Ñ€ÐµÐ¼Ñ Ð°Ñ€Ñ…Ð¸Ð²Ñ‹ отÑутÑтвуют.

                    " #: Mailman/Archiver/HyperArch.py:821 +#, fuzzy msgid "Gzip'd Text%(sz)s" msgstr "текÑÑ‚, Ñжатый программой gzip, %(sz)s" #: Mailman/Archiver/HyperArch.py:826 +#, fuzzy msgid "Text%(sz)s" msgstr "текÑÑ‚ %(sz)s" @@ -146,20 +148,24 @@ msgid "Third" msgstr "Третий" #: Mailman/Archiver/HyperArch.py:938 +#, fuzzy msgid "%(ord)s quarter %(year)i" msgstr "%(ord)s квартал %(year)i" #: Mailman/Archiver/HyperArch.py:945 +#, fuzzy msgid "%(month)s %(year)i" msgstr "%(month)s %(year)i" # MSS: здеÑÑŒ меÑÑц не ÑклонÑетÑÑ... Что еÑть плохо. Варианты? # fattie: а Ð½ÐµÐ»ÑŒÐ·Ñ Ð¿Ð¾Ð¿Ñ€Ð°Ð²Ð¸Ñ‚ÑŒ код? #: Mailman/Archiver/HyperArch.py:950 +#, fuzzy msgid "The Week Of Monday %(day)i %(month)s %(year)i" msgstr "ÐеделÑ, начинающаÑÑÑ Ñ Ð¿Ð¾Ð½ÐµÐ´ÐµÐ»ÑŒÐ½Ð¸ÐºÐ° %(day)i %(month)s %(year)i" #: Mailman/Archiver/HyperArch.py:954 +#, fuzzy msgid "%(day)i %(month)s %(year)i" msgstr "%(day)i %(month)s %(year)i" @@ -168,10 +174,12 @@ msgid "Computing threaded index\n" msgstr "ФормируетÑÑ Ð¸Ð½Ð´ÐµÐºÑ Ð¿Ð¾ диÑкуÑÑиÑм\n" #: Mailman/Archiver/HyperArch.py:1319 +#, fuzzy msgid "Updating HTML for article %(seq)s" msgstr "ОбновлÑетÑÑ HTML-предÑтавление Ñтатьи %(seq)s" #: Mailman/Archiver/HyperArch.py:1326 +#, fuzzy msgid "article file %(filename)s is missing!" msgstr "отÑутÑтвует файл Ñтатьи %(filename)s!" @@ -190,6 +198,7 @@ msgid "Pickling archive state into " msgstr "СоÑтоÑние архива ÑохранÑетÑÑ Ð² " #: Mailman/Archiver/pipermail.py:453 +#, fuzzy msgid "Updating index files for archive [%(archive)s]" msgstr "Идет обновление файлов индекÑов Ð´Ð»Ñ Ð°Ñ€Ñ…Ð¸Ð²Ð° [%(archive)s]" @@ -198,6 +207,7 @@ msgid " Thread" msgstr " Темы диÑкуÑÑий" #: Mailman/Archiver/pipermail.py:597 +#, fuzzy msgid "#%(counter)05d %(msgid)s" msgstr "#%(counter)05d %(msgid)s" @@ -235,6 +245,7 @@ msgid "disabled address" msgstr "заблокирована" #: Mailman/Bouncer.py:304 +#, fuzzy msgid " The last bounce received from you was dated %(date)s" msgstr " ПоÑледнее Ñообщение об ошибке, полученное от ваÑ, датировано %(date)s" @@ -262,6 +273,7 @@ msgstr "админиÑтратора" #: Mailman/Cgi/options.py:101 Mailman/Cgi/private.py:108 #: Mailman/Cgi/rmlist.py:75 Mailman/Cgi/roster.py:59 #: Mailman/Cgi/subscribe.py:67 +#, fuzzy msgid "No such list %(safelistname)s" msgstr "СпиÑок раÑÑылки %(safelistname)s не ÑущеÑтвует" @@ -334,6 +346,7 @@ msgstr "" "образом. Они ничего не получают. Таких подпиÑчиков %(rm)r." #: Mailman/Cgi/admin.py:268 +#, fuzzy msgid "%(hostname)s mailing lists - Admin Links" msgstr "СпиÑки раÑÑылки на %(hostname)s -- Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ Ð°Ð´Ð¼Ð¸Ð½Ð¸Ñтратора" @@ -346,6 +359,7 @@ msgid "Mailman" msgstr "Mailman" #: Mailman/Cgi/admin.py:310 +#, fuzzy msgid "" "

                    There currently are no publicly-advertised %(mailmanlink)s\n" " mailing lists on %(hostname)s." @@ -355,6 +369,7 @@ msgstr "" "раÑÑылки." #: Mailman/Cgi/admin.py:316 +#, fuzzy msgid "" "

                    Below is the collection of publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s. Click on a list\n" @@ -371,6 +386,7 @@ msgid "right " msgstr "правильное " #: Mailman/Cgi/admin.py:325 +#, fuzzy msgid "" "To visit the administrators configuration page for an\n" " unadvertised list, open a URL similar to this one, but with a '/' " @@ -385,8 +401,8 @@ msgstr "" " здеÑÑŒ, добавьте к адреÑу Ñтой Ñтраницы Ñимвол '/', за которым\n" " вы должны указать %(extra)sÐ¸Ð¼Ñ ÑпиÑка раÑÑылки. ЕÑли у Ð²Ð°Ñ ÐµÑть право " "Ñоздавать\n" -" ÑпиÑки раÑÑылки на Ñтом Ñервере, вы можете перейти к Ñтранице ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ ÑпиÑков раÑÑылки.\n" +" ÑпиÑки раÑÑылки на Ñтом Ñервере, вы можете перейти к Ñтранице ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ ÑпиÑков раÑÑылки.\n" "\n" "

                    ÐžÐ±Ñ‰Ð°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ ÑпиÑках раÑÑылки может быть найдена на " @@ -420,6 +436,7 @@ msgid "No valid variable name found." msgstr "Ðе найдено Ð½Ð°Ð·Ð²Ð°Ð½Ð¸Ñ Ð´Ð¾Ð¿ÑƒÑтимой переменной." #: Mailman/Cgi/admin.py:395 +#, fuzzy msgid "" "%(realname)s Mailing list Configuration Help\n" "
                    %(varname)s Option" @@ -428,6 +445,7 @@ msgstr "" "
                    Параметр %(varname)s" #: Mailman/Cgi/admin.py:402 +#, fuzzy msgid "Mailman %(varname)s List Option Help" msgstr "Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ параметре %(varname)s" @@ -449,14 +467,17 @@ msgstr "" " параметр. Ð’Ñ‹ также можете" #: Mailman/Cgi/admin.py:431 +#, fuzzy msgid "return to the %(categoryname)s options page." msgstr "вернутьÑÑ Ðº Ñтранице группы параметров %(categoryname)s" #: Mailman/Cgi/admin.py:446 +#, fuzzy msgid "%(realname)s Administration (%(label)s)" msgstr "Параметры ÑпиÑка раÑÑылки %(realname)s: %(label)s" #: Mailman/Cgi/admin.py:447 +#, fuzzy msgid "%(realname)s mailing list administration
                    %(label)s Section" msgstr "Управление ÑпиÑком раÑÑылки %(realname)s
                    Раздел \"%(label)s\"" @@ -539,6 +560,7 @@ msgid "Value" msgstr "Значение" #: Mailman/Cgi/admin.py:668 +#, fuzzy msgid "" "Badly formed options entry:\n" " %(record)s" @@ -640,10 +662,12 @@ msgid "Move rule down" msgstr "Вниз" #: Mailman/Cgi/admin.py:870 +#, fuzzy msgid "
                    (Edit %(varname)s)" msgstr "
                    (Изменить %(varname)s)" #: Mailman/Cgi/admin.py:872 +#, fuzzy msgid "
                    (Details for %(varname)s)" msgstr "
                    (ÐŸÐ¾Ð´Ñ€Ð¾Ð±Ð½Ð°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ \"%(varname)s\")" @@ -685,6 +709,7 @@ msgid "(help)" msgstr "(подÑказка)" #: Mailman/Cgi/admin.py:930 +#, fuzzy msgid "Find member %(link)s:" msgstr "Ðайти подпиÑчика %(link)s:" @@ -698,10 +723,12 @@ msgid "Bad regular expression: " msgstr "Ðеверное регулÑрное выражение: " #: Mailman/Cgi/admin.py:1013 +#, fuzzy msgid "%(allcnt)s members total, %(membercnt)s shown" msgstr "вÑего подпиÑчиков: %(allcnt)s; показано подпиÑчиков: %(membercnt)s" #: Mailman/Cgi/admin.py:1016 +#, fuzzy msgid "%(allcnt)s members total" msgstr "вÑего подпиÑчиков: %(allcnt)s" @@ -898,6 +925,7 @@ msgstr "" " разных диапазонов:" #: Mailman/Cgi/admin.py:1221 +#, fuzzy msgid "from %(start)s to %(end)s" msgstr "Ñ %(start)s по %(end)s" @@ -1038,6 +1066,7 @@ msgstr "Изменить пароль ÑпиÑка раÑÑылки" # MSS: надо чуть отполировать... #: Mailman/Cgi/admin.py:1364 +#, fuzzy msgid "" "The list administrators are the people who have ultimate control " "over\n" @@ -1150,6 +1179,7 @@ msgstr "Ошибочный Ð°Ð´Ñ€ÐµÑ (вÑтретилиÑÑŒ недопуÑти #: Mailman/Cgi/admin.py:1529 Mailman/Cgi/admin.py:1703 bin/add_members:175 #: bin/clone_member:136 bin/sync_members:268 +#, fuzzy msgid "Banned address (matched %(pattern)s)" msgstr "Заблокированный Ð°Ð´Ñ€ÐµÑ (подходит под шаблон %(pattern)s)" @@ -1206,6 +1236,7 @@ msgid "%(schange_to)s is already a member" msgstr "%(schange_to)s уже ÑвлÑетÑÑ Ð¿Ð¾Ð´Ð¿Ð¸Ñчиком" #: Mailman/Cgi/admin.py:1620 +#, fuzzy msgid "%(schange_to)s matches banned pattern %(spat)s" msgstr "%(schange_to)s заблокирован (подходит под шаблон %(spat)s)" @@ -1248,6 +1279,7 @@ msgstr "Ðе подпиÑан" # MSS: ?? #: Mailman/Cgi/admin.py:1773 +#, fuzzy msgid "Ignoring changes to deleted member: %(user)s" msgstr "ИгнорируютÑÑ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð´Ð»Ñ ÑƒÐ´Ð°Ð»Ñ‘Ð½Ð½Ð¾Ð³Ð¾ подпиÑчика: %(user)s" @@ -1260,11 +1292,13 @@ msgid "Error Unsubscribing:" msgstr "Ошибка ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ Ð¿Ð¾Ð´Ð¿Ð¸Ñки:" #: Mailman/Cgi/admindb.py:235 Mailman/Cgi/admindb.py:248 +#, fuzzy msgid "%(realname)s Administrative Database" msgstr "ЗапроÑÑ‹ Ð´Ð»Ñ ÑпиÑка раÑÑылки %(realname)s" # MSS: fattie offers "СпиÑок необработанных запроÑов", TODO: check the code! #: Mailman/Cgi/admindb.py:238 +#, fuzzy msgid "%(realname)s Administrative Database Results" msgstr "Результаты обработки запроÑов Ð´Ð»Ñ ÑпиÑка раÑÑылки %(realname)s" @@ -1293,6 +1327,7 @@ msgid "Discard all messages marked Defer" msgstr "Удалить вÑе ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð¿Ð¾Ð¼ÐµÑ‡ÐµÐ½Ð½Ñ‹Ðµ Отложить" #: Mailman/Cgi/admindb.py:298 +#, fuzzy msgid "all of %(esender)s's held messages." msgstr "вÑе задержанные ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð¾Ñ‚ %(esender)s." @@ -1317,6 +1352,7 @@ msgid "list of available mailing lists." msgstr "ÑпиÑок доÑтупных ÑпиÑков раÑÑылки." #: Mailman/Cgi/admindb.py:362 +#, fuzzy msgid "You must specify a list name. Here is the %(link)s" msgstr "Ð’Ñ‹ должны указать Ð¸Ð¼Ñ ÑпиÑка раÑÑылки; проÑмотрите %(link)s" @@ -1401,6 +1437,7 @@ msgstr "" "Отправитель ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ñ‚ÐµÐ¿ÐµÑ€ÑŒ входит в чиÑло подпиÑчиков раÑÑылки" #: Mailman/Cgi/admindb.py:568 +#, fuzzy msgid "Add %(esender)s to one of these sender filters:" msgstr "Добавить Ð°Ð´Ñ€ÐµÑ %(esender)s к Ñледующим фильтрам отправителей:" @@ -1422,6 +1459,7 @@ msgid "Rejects" msgstr "Отклонить" #: Mailman/Cgi/admindb.py:584 +#, fuzzy msgid "" "Ban %(esender)s from ever subscribing to this\n" " mailing list" @@ -1438,6 +1476,7 @@ msgstr "" " его или, еÑли хотите, " #: Mailman/Cgi/admindb.py:591 +#, fuzzy msgid "view all messages from %(esender)s" msgstr "проÑмотрите вÑе ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ñ Ð°Ð´Ñ€ÐµÑа %(esender)s" @@ -1519,6 +1558,7 @@ msgid " is already a member" msgstr " уже ÑвлÑетÑÑ Ð¿Ð¾Ð´Ð¿Ð¸Ñчиком" #: Mailman/Cgi/admindb.py:973 +#, fuzzy msgid "%(addr)s is banned (matched: %(patt)s)" msgstr "%(addr)s заблокирован (подходит под шаблон %(patt)s)" @@ -1527,6 +1567,7 @@ msgid "Confirmation string was empty." msgstr "Строка Ð¿Ð¾Ð´Ñ‚Ð²ÐµÑ€Ð¶Ð´ÐµÐ½Ð¸Ñ Ð¾Ñ‚ÑутÑтвует." #: Mailman/Cgi/confirm.py:108 +#, fuzzy msgid "" "Invalid confirmation string:\n" " %(safecookie)s.\n" @@ -1574,6 +1615,7 @@ msgstr "" " из ÑпиÑка раÑÑылки. Ð—Ð°Ð¿Ñ€Ð¾Ñ Ð¾Ñ‚ÐºÐ»Ð¾Ð½ÐµÐ½." #: Mailman/Cgi/confirm.py:178 +#, fuzzy msgid "System error, bad content: %(content)s" msgstr "СиÑÑ‚ÐµÐ¼Ð½Ð°Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ°, недопуÑтимое Ñодержимое: %(content)s" @@ -1615,6 +1657,7 @@ msgid "Confirm subscription request" msgstr "Подтвердить Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° подпиÑку" #: Mailman/Cgi/confirm.py:259 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " subscription request to the mailing list %(listname)s. Your\n" @@ -1647,6 +1690,7 @@ msgstr "" "решили не подпиÑыватьÑÑ Ð½Ð° Ñтот ÑпиÑок." #: Mailman/Cgi/confirm.py:275 +#, fuzzy msgid "" "Your confirmation is required in order to continue with\n" " the subscription request to the mailing list %(listname)s.\n" @@ -1696,6 +1740,7 @@ msgid "Preferred language:" msgstr "Предпочитаемый Ñзык:" #: Mailman/Cgi/confirm.py:317 +#, fuzzy msgid "Subscribe to list %(listname)s" msgstr "ПодпиÑатьÑÑ Ð½Ð° ÑпиÑок раÑÑылки %(listname)s" @@ -1712,6 +1757,7 @@ msgid "Awaiting moderator approval" msgstr "Ожидают Ñ€ÐµÑˆÐµÐ½Ð¸Ñ Ð¼Ð¾Ð´ÐµÑ€Ð°Ñ‚Ð¾Ñ€Ð°" #: Mailman/Cgi/confirm.py:386 +#, fuzzy msgid "" " You have successfully confirmed your subscription request to " "the\n" @@ -1745,6 +1791,7 @@ msgid "You are already a member of this mailing list!" msgstr "Ð’Ñ‹ уже ÑвлÑетеÑÑŒ подпиÑчиком Ñтого ÑпиÑка раÑÑылки!" #: Mailman/Cgi/confirm.py:400 +#, fuzzy msgid "" "You are currently banned from subscribing to\n" " this list. If you think this restriction is erroneous, please\n" @@ -1769,6 +1816,7 @@ msgid "Subscription request confirmed" msgstr "Ð—Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° подпиÑку подтвержден" #: Mailman/Cgi/confirm.py:418 +#, fuzzy msgid "" " You have successfully confirmed your subscription request for\n" " \"%(addr)s\" to the %(listname)s mailing list. A separate\n" @@ -1796,6 +1844,7 @@ msgid "Unsubscription request confirmed" msgstr "Ð—Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° удаление подпиÑки подтвержден" #: Mailman/Cgi/confirm.py:469 +#, fuzzy msgid "" " You have successfully unsubscribed from the %(listname)s " "mailing\n" @@ -1817,6 +1866,7 @@ msgstr "ÐедоÑтупно" # MSS: завершение удаление... хмм... #: Mailman/Cgi/confirm.py:498 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " unsubscription request from the mailing list %(listname)s. " @@ -1857,6 +1907,7 @@ msgid "You have canceled your change of address request." msgstr "Ð’Ñ‹ отказалиÑÑŒ от запроÑа на изменение адреÑа." #: Mailman/Cgi/confirm.py:555 +#, fuzzy msgid "" "%(newaddr)s is banned from subscribing to the\n" " %(realname)s list. If you think this restriction is erroneous,\n" @@ -1868,6 +1919,7 @@ msgstr "" " по адреÑу %(owneraddr)s." #: Mailman/Cgi/confirm.py:560 +#, fuzzy msgid "" "%(newaddr)s is already a member of\n" " the %(realname)s list. It is possible that you are attempting\n" @@ -1884,6 +1936,7 @@ msgid "Change of address request confirmed" msgstr "Ð—Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° изменение адреÑа подпиÑки подтвержден" #: Mailman/Cgi/confirm.py:571 +#, fuzzy msgid "" " You have successfully changed your address on the %(listname)s\n" " mailing list from %(oldaddr)s to %(newaddr)s. " @@ -1891,8 +1944,8 @@ msgid "" " can now proceed to your membership\n" " login page." msgstr "" -"Ваш Ð°Ð´Ñ€ÐµÑ Ð² ÑпиÑке раÑÑылки %(listname)s был уÑпешно изменен Ñ " -"%(oldaddr)s на %(newaddr)s.\n" +"Ваш Ð°Ð´Ñ€ÐµÑ Ð² ÑпиÑке раÑÑылки %(listname)s был уÑпешно изменен Ñ " +"%(oldaddr)s на %(newaddr)s.\n" "Теперь вы можете перейти на Ñтраницу входа в " "ÑиÑтему." @@ -1907,6 +1960,7 @@ msgid "globally" msgstr "везде" #: Mailman/Cgi/confirm.py:605 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " change of address request for the mailing list %(listname)s. " @@ -1962,6 +2016,7 @@ msgid "Sender discarded message via web." msgstr "Отправитель удалил Ñообщение через веб-интерфейÑ." #: Mailman/Cgi/confirm.py:674 +#, fuzzy msgid "" "The held message with the Subject:\n" " header %(subject)s could not be found. The most " @@ -1982,6 +2037,7 @@ msgstr "Сообщение отозвано" # MSS: хочетÑÑ Ñ„Ñ€Ð°Ð·Ñƒ Ñ "отозвано" или не хочетÑÑ?.. #: Mailman/Cgi/confirm.py:685 +#, fuzzy msgid "" " You have successfully canceled the posting of your message with\n" " the Subject: header %(subject)s to the mailing list\n" @@ -2004,6 +2060,7 @@ msgstr "" "админиÑтратором ÑпиÑка раÑÑылки." #: Mailman/Cgi/confirm.py:735 +#, fuzzy msgid "" "Your confirmation is required in order to cancel the\n" " posting of your message to the mailing list %(listname)s:\n" @@ -2048,6 +2105,7 @@ msgid "Membership re-enabled." msgstr "ПодпиÑка возобновлена." #: Mailman/Cgi/confirm.py:798 +#, fuzzy msgid "" " You have successfully re-enabled your membership in the\n" " %(listname)s mailing list. You can now not available" msgstr "недоÑтупно" #: Mailman/Cgi/confirm.py:846 +#, fuzzy msgid "" "Your membership in the %(realname)s mailing list is\n" " currently disabled due to excessive bounces. Your confirmation is\n" @@ -2139,11 +2199,13 @@ msgid "administrative list overview" msgstr "Ñтраницу ÑƒÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ ÑпиÑком раÑÑылки" #: Mailman/Cgi/create.py:115 +#, fuzzy msgid "List name must not include \"@\": %(safelistname)s" msgstr "" "Ðазвание ÑпиÑка раÑÑылки не должно Ñодержать Ñимвол \"@\": %(safelistname)s" #: Mailman/Cgi/create.py:122 +#, fuzzy msgid "List already exists: %(safelistname)s" msgstr "СпиÑок уже ÑущеÑтвует: %(safelistname)s" @@ -2178,18 +2240,22 @@ msgstr "Ð’Ñ‹ не имеете право Ñоздавать ÑпиÑки Ñ€Ð°Ñ # fattie: "ÐеизвеÑтный узел: %(safehostname)s" #: Mailman/Cgi/create.py:182 +#, fuzzy msgid "Unknown virtual host: %(safehostname)s" msgstr "ÐеизвеÑтный виртуальный хоÑÑ‚: %(safehostname)s" #: Mailman/Cgi/create.py:218 bin/newlist:219 +#, fuzzy msgid "Bad owner email address: %(s)s" msgstr "Ðекорректный Ð°Ð´Ñ€ÐµÑ Ð²Ð»Ð°Ð´ÐµÐ»ÑŒÑ†Ð° ÑпиÑка: %(s)s" #: Mailman/Cgi/create.py:223 bin/newlist:182 bin/newlist:223 +#, fuzzy msgid "List already exists: %(listname)s" msgstr "СпиÑок уже ÑущеÑтвует: %(listname)s" #: Mailman/Cgi/create.py:231 bin/newlist:217 +#, fuzzy msgid "Illegal list name: %(s)s" msgstr "ÐедопуÑтимое Ð¸Ð¼Ñ Ð´Ð»Ñ ÑпиÑка раÑÑылки: %(s)s" @@ -2202,6 +2268,7 @@ msgstr "" "ОбратитеÑÑŒ за помощью к админиÑтратору Ñайта." #: Mailman/Cgi/create.py:273 bin/newlist:265 +#, fuzzy msgid "Your new mailing list: %(listname)s" msgstr "Ваш новый ÑпиÑок раÑÑылки: %(listname)s" @@ -2210,6 +2277,7 @@ msgid "Mailing list creation results" msgstr "Создание ÑпиÑка раÑÑылки" #: Mailman/Cgi/create.py:288 +#, fuzzy msgid "" "You have successfully created the mailing list\n" " %(listname)s and notification has been sent to the list owner\n" @@ -2232,6 +2300,7 @@ msgid "Create another list" msgstr "Создать еще один ÑпиÑок раÑÑылки" #: Mailman/Cgi/create.py:312 +#, fuzzy msgid "Create a %(hostname)s Mailing List" msgstr "Создать ÑпиÑок раÑÑылки на Ñайте %(hostname)s" @@ -2326,6 +2395,7 @@ msgstr "" "Ð´Ð»Ñ Ð¿Ñ€Ð¾Ð²ÐµÑ€ÐºÐ¸." #: Mailman/Cgi/create.py:432 +#, fuzzy msgid "" "Initial list of supported languages.

                    Note that if you do not\n" " select at least one initial language, the list will use the server\n" @@ -2425,6 +2495,7 @@ msgid "List name is required." msgstr "Ð˜Ð¼Ñ ÑпиÑка раÑÑылки ÑвлÑетÑÑ Ð¾Ð±Ñзательным." #: Mailman/Cgi/edithtml.py:154 +#, fuzzy msgid "%(realname)s -- Edit html for %(template_info)s" msgstr "%(realname)s -- Правка HTML Ð´Ð»Ñ %(template_info)s" @@ -2433,10 +2504,12 @@ msgid "Edit HTML : Error" msgstr "Правка HTML: ошибка" #: Mailman/Cgi/edithtml.py:161 +#, fuzzy msgid "%(safetemplatename)s: Invalid template" msgstr "%(safetemplatename)s: некорректный шаблон" #: Mailman/Cgi/edithtml.py:166 Mailman/Cgi/edithtml.py:167 +#, fuzzy msgid "%(realname)s -- HTML Page Editing" msgstr "%(realname)s -- Редактирование HTML Ñтраниц" @@ -2499,10 +2572,12 @@ msgid "HTML successfully updated." msgstr "HTML-код обновлен." #: Mailman/Cgi/listinfo.py:89 +#, fuzzy msgid "%(hostname)s Mailing Lists" msgstr "СпиÑки раÑÑылки %(hostname)s" #: Mailman/Cgi/listinfo.py:127 +#, fuzzy msgid "" "

                    There currently are no publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s." @@ -2511,6 +2586,7 @@ msgstr "" "на %(hostname)s ÑÐµÐ¹Ñ‡Ð°Ñ Ð½ÐµÑ‚." #: Mailman/Cgi/listinfo.py:131 +#, fuzzy msgid "" "

                    Below is a listing of all the public mailing lists on\n" " %(hostname)s. Click on a list name to get more information " @@ -2530,6 +2606,7 @@ msgid "right" msgstr "ÑущеÑтвующего" #: Mailman/Cgi/listinfo.py:140 +#, fuzzy msgid "" " To visit the general information page for an unadvertised list,\n" " open a URL similar to this one, but with a '/' and the %(adj)s\n" @@ -2582,6 +2659,7 @@ msgid "CGI script error" msgstr "Ошибка CGI-ÑценариÑ" #: Mailman/Cgi/options.py:71 +#, fuzzy msgid "Invalid request method: %(method)s" msgstr "Ðеверный формат запроÑа: %(method)s" @@ -2596,6 +2674,7 @@ msgstr "Ðеверный адреÑ" # MSS: было "ПодпиÑчик отÑутÑтвует: %(safeuser)s."... надо думать.. Ñлушать... #: Mailman/Cgi/options.py:183 Mailman/Cgi/options.py:240 #: Mailman/Cgi/options.py:264 +#, fuzzy msgid "No such member: %(safeuser)s." msgstr "Такого Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð½Ðµ ÑущеÑтвует: %(safeuser)s." @@ -2649,6 +2728,7 @@ msgid "Note: " msgstr "Учтите: " #: Mailman/Cgi/options.py:378 +#, fuzzy msgid "List subscriptions for %(safeuser)s on %(hostname)s" msgstr "ПодпиÑки Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ %(safeuser)s на %(hostname)s" @@ -2679,6 +2759,7 @@ msgid "You are already using that email address" msgstr "Ð’Ñ‹ уже пользуетеÑÑŒ тем адреÑом Ñлектронной почты" #: Mailman/Cgi/options.py:459 +#, fuzzy msgid "" "The new address you requested %(newaddr)s is already a member of the\n" "%(listname)s mailing list, however you have also requested a global change " @@ -2692,6 +2773,7 @@ msgstr "" "во вÑех оÑтальных ÑпиÑках ваш Ð°Ð´Ñ€ÐµÑ Ð±ÑƒÐ´ÐµÑ‚ изменен." #: Mailman/Cgi/options.py:468 +#, fuzzy msgid "The new address is already a member: %(newaddr)s" msgstr "Ðовый Ð°Ð´Ñ€ÐµÑ ÑƒÐ¶Ðµ еÑть в ÑпиÑке раÑÑылки: %(newaddr)s" @@ -2700,6 +2782,7 @@ msgid "Addresses may not be blank" msgstr "ÐÐ´Ñ€ÐµÑ Ð½Ðµ может быть пуÑтым" #: Mailman/Cgi/options.py:488 +#, fuzzy msgid "A confirmation message has been sent to %(newaddr)s. " msgstr "" "Сообщение Ñ ÐºÐ¾Ð´Ð¾Ð¼ Ð¿Ð¾Ð´Ñ‚Ð²ÐµÑ€Ð¶Ð´ÐµÐ½Ð¸Ñ Ð±Ñ‹Ð»Ð¾ отправлено по адреÑу %(newaddr)s. " @@ -2713,10 +2796,12 @@ msgid "Illegal email address provided" msgstr "Указан недопуÑтимый адреÑ" #: Mailman/Cgi/options.py:501 +#, fuzzy msgid "%(newaddr)s is already a member of the list." msgstr "%(newaddr)s уже ÑодержитÑÑ Ð² ÑпиÑке раÑÑылки." #: Mailman/Cgi/options.py:504 +#, fuzzy msgid "" "%(newaddr)s is banned from this list. If you\n" " think this restriction is erroneous, please contact\n" @@ -2793,6 +2878,7 @@ msgstr "" "Ñ€ÐµÑˆÐµÐ½Ð¸Ñ Ð²Ñ‹ получите извещение." #: Mailman/Cgi/options.py:623 +#, fuzzy msgid "" "You have been successfully unsubscribed from the\n" " mailing list %(fqdn_listname)s. If you were receiving digest\n" @@ -2888,6 +2974,7 @@ msgid "day" msgstr "день" #: Mailman/Cgi/options.py:900 +#, fuzzy msgid "%(days)d %(units)s" msgstr "%(days)d %(units)s" @@ -2900,6 +2987,7 @@ msgid "No topics defined" msgstr "Разделы не определены" #: Mailman/Cgi/options.py:940 +#, fuzzy msgid "" "\n" "You are subscribed to this list with the case-preserved address\n" @@ -2910,6 +2998,7 @@ msgstr "" "%(cpuser)s." #: Mailman/Cgi/options.py:956 +#, fuzzy msgid "%(realname)s list: member options login page" msgstr "СпиÑок раÑÑылки %(realname)s: вход в ÑиÑтему" @@ -2918,11 +3007,13 @@ msgid "email address and " msgstr "Ð°Ð´Ñ€ÐµÑ Ñлектронной почты и " #: Mailman/Cgi/options.py:960 +#, fuzzy msgid "%(realname)s list: member options for user %(safeuser)s" msgstr "" "СпиÑок раÑÑылки %(realname)s: пользовательÑкие наÑтройки Ð´Ð»Ñ %(safeuser)s" #: Mailman/Cgi/options.py:986 +#, fuzzy msgid "" "In order to change your membership option, you must\n" " first log in by giving your %(extra)smembership password in the section\n" @@ -2996,6 +3087,7 @@ msgid "" msgstr "<отÑутÑтвует>" #: Mailman/Cgi/options.py:1140 +#, fuzzy msgid "Requested topic is not valid: %(topicname)s" msgstr "Запрошенный раздел некорректен: %(topicname)s" @@ -3024,6 +3116,7 @@ msgid "Private archive - \"./\" and \"../\" not allowed in URL." msgstr "Закрытый архив - \"./\" и \"../\" не разрешены в URL." #: Mailman/Cgi/private.py:109 +#, fuzzy msgid "Private Archive Error - %(msg)s" msgstr "Ошибка доÑтупа к закрытому архиву -- %(msg)s" @@ -3044,6 +3137,7 @@ msgid "Private archive file not found" msgstr "Файл закрытого архива не найден" #: Mailman/Cgi/rmlist.py:76 +#, fuzzy msgid "No such list %(safelistname)s" msgstr "Ðет такого ÑпиÑка раÑÑылки: %(safelistname)s" @@ -3061,12 +3155,14 @@ msgid "Mailing list deletion results" msgstr "Удаление ÑпиÑка раÑÑылки" #: Mailman/Cgi/rmlist.py:187 +#, fuzzy msgid "" "You have successfully deleted the mailing list\n" " %(listname)s." msgstr "СпиÑок раÑÑылки %(listname)s был уÑпешно удален." #: Mailman/Cgi/rmlist.py:191 +#, fuzzy msgid "" "There were some problems deleting the mailing list\n" " %(listname)s. Contact your site administrator at " @@ -3077,10 +3173,12 @@ msgstr "" "ОбратитеÑÑŒ за помощью к админиÑтратору Ñайта по адреÑу %(sitelist)s." #: Mailman/Cgi/rmlist.py:208 +#, fuzzy msgid "Permanently remove mailing list %(realname)s" msgstr "ПолноÑтью удалить ÑпиÑок раÑÑылки %(realname)s" #: Mailman/Cgi/rmlist.py:209 +#, fuzzy msgid "Permanently remove mailing list %(realname)s" msgstr "ПолноÑтью удалить ÑпиÑок раÑÑылки %(realname)s" @@ -3146,6 +3244,7 @@ msgid "Invalid options to CGI script" msgstr "CGI-Ñценарию переданы некорректные параметры" #: Mailman/Cgi/roster.py:118 +#, fuzzy msgid "%(realname)s roster authentication failed." msgstr "Ошибка аутентификации %(realname)s" @@ -3213,6 +3312,7 @@ msgstr "" "инÑтрукции." #: Mailman/Cgi/subscribe.py:262 +#, fuzzy msgid "" "The email address you supplied is banned from this\n" " mailing list. If you think this restriction is erroneous, please\n" @@ -3239,6 +3339,7 @@ msgstr "" "Этот Ð°Ð´Ñ€ÐµÑ Ð½Ðµ может иÑпользоватьÑÑ Ð´Ð»Ñ Ð¿Ð¾Ð´Ð¿Ð¸Ñки, так как он не защищен." #: Mailman/Cgi/subscribe.py:278 +#, fuzzy msgid "" "Confirmation from your email address is required, to prevent anyone from\n" "subscribing you without permission. Instructions are being sent to you at\n" @@ -3251,6 +3352,7 @@ msgstr "" "Ñообщений вам и от Ð²Ð°Ñ Ð² ÑпиÑок раÑÑылки оÑущеÑтвлÑтьÑÑ Ð½Ðµ будет." #: Mailman/Cgi/subscribe.py:290 +#, fuzzy msgid "" "Your subscription request was deferred because %(x)s. Your request has " "been\n" @@ -3271,6 +3373,7 @@ msgid "Mailman privacy alert" msgstr "Предупреждение Mailman" #: Mailman/Cgi/subscribe.py:316 +#, fuzzy msgid "" "An attempt was made to subscribe your address to the mailing list\n" "%(listaddr)s. You are already subscribed to this mailing list.\n" @@ -3311,6 +3414,7 @@ msgid "This list only supports digest delivery." msgstr "СпиÑок поддерживает доÑтавку Ñообщений только в виде дайджеÑтов." #: Mailman/Cgi/subscribe.py:344 +#, fuzzy msgid "You have been successfully subscribed to the %(realname)s mailing list." msgstr "Ð’Ñ‹ уÑпешно подпиÑалиÑÑŒ на ÑпиÑок раÑÑылки %(realname)s." @@ -3335,6 +3439,7 @@ msgid "Usage:" msgstr "ЗапуÑк:" #: Mailman/Commands/cmd_confirm.py:50 +#, fuzzy msgid "" "Invalid confirmation string. Note that confirmation strings expire\n" "approximately %(days)s days after the initial request. They also expire if\n" @@ -3360,6 +3465,7 @@ msgstr "" "поменÑли Ð°Ð´Ñ€ÐµÑ Ð¿Ð¾Ð´Ð¿Ð¸Ñки?" #: Mailman/Commands/cmd_confirm.py:69 +#, fuzzy msgid "" "You are currently banned from subscribing to this list. If you think this\n" "restriction is erroneous, please contact the list owners at\n" @@ -3445,26 +3551,32 @@ msgid "n/a" msgstr "недоÑтупно" #: Mailman/Commands/cmd_info.py:44 +#, fuzzy msgid "List name: %(listname)s" msgstr "СпиÑок раÑÑылки: %(listname)s" #: Mailman/Commands/cmd_info.py:45 +#, fuzzy msgid "Description: %(description)s" msgstr "ОпиÑание: %(description)s" #: Mailman/Commands/cmd_info.py:46 +#, fuzzy msgid "Postings to: %(postaddr)s" msgstr "ÐÐ´Ñ€ÐµÑ Ð´Ð»Ñ Ñообщений: %(postaddr)s" #: Mailman/Commands/cmd_info.py:47 +#, fuzzy msgid "List Helpbot: %(requestaddr)s" msgstr "Управление подпиÑкой: %(requestaddr)s" #: Mailman/Commands/cmd_info.py:48 +#, fuzzy msgid "List Owners: %(owneraddr)s" msgstr "Владельцы ÑпиÑка: %(owneraddr)s" #: Mailman/Commands/cmd_info.py:49 +#, fuzzy msgid "More information: %(listurl)s" msgstr "Ð”Ð¾Ð¿Ð¾Ð»Ð½Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ: %(listurl)s" @@ -3488,18 +3600,22 @@ msgstr "" "Ñервере.\n" #: Mailman/Commands/cmd_lists.py:44 +#, fuzzy msgid "Public mailing lists at %(hostname)s:" msgstr "ÐнонÑированные ÑпиÑки раÑÑылки на %(hostname)s:" #: Mailman/Commands/cmd_lists.py:66 +#, fuzzy msgid "%(i)3d. List name: %(realname)s" msgstr "%(i)3d. Ðазвание ÑпиÑка: %(realname)s" #: Mailman/Commands/cmd_lists.py:67 +#, fuzzy msgid " Description: %(description)s" msgstr " ОпиÑание: %(description)s" #: Mailman/Commands/cmd_lists.py:68 +#, fuzzy msgid " Requests to: %(requestaddr)s" msgstr " ÐÐ´Ñ€ÐµÑ Ð´Ð»Ñ Ð·Ð°Ð¿Ñ€Ð¾Ñов: %(requestaddr)s" @@ -3533,12 +3649,14 @@ msgstr "" " ÑпиÑке раÑÑылки.\n" #: Mailman/Commands/cmd_password.py:51 Mailman/Commands/cmd_password.py:66 +#, fuzzy msgid "Your password is: %(password)s" msgstr "Ваш пароль: %(password)s" #: Mailman/Commands/cmd_password.py:57 Mailman/Commands/cmd_password.py:72 #: Mailman/Commands/cmd_password.py:95 Mailman/Commands/cmd_password.py:121 #: Mailman/Commands/cmd_set.py:149 Mailman/Commands/cmd_set.py:219 +#, fuzzy msgid "You are not a member of the %(listname)s mailing list" msgstr "Ð’Ñ‹ не ÑвлÑетеÑÑŒ подпиÑчиком ÑпиÑка раÑÑылки %(listname)s" @@ -3728,6 +3846,7 @@ msgstr "" " воÑпользуйтеÑÑŒ командой `set reminders off'.\n" #: Mailman/Commands/cmd_set.py:122 +#, fuzzy msgid "Bad set command: %(subcmd)s" msgstr "Ðекорректные параметры команды `set': %(subcmd)s" @@ -3749,6 +3868,7 @@ msgid "on" msgstr "вкл." #: Mailman/Commands/cmd_set.py:154 +#, fuzzy msgid " ack %(onoff)s" msgstr " Ð¿Ð¾Ð´Ñ‚Ð²ÐµÑ€Ð¶Ð´ÐµÐ½Ð¸Ñ %(onoff)s" @@ -3786,22 +3906,27 @@ msgid "due to bounces" msgstr "вÑледÑтвие ошибок доÑтавки подпиÑки" #: Mailman/Commands/cmd_set.py:186 +#, fuzzy msgid " %(status)s (%(how)s on %(date)s)" msgstr " %(status)s (%(how)s %(date)s)" #: Mailman/Commands/cmd_set.py:192 +#, fuzzy msgid " myposts %(onoff)s" msgstr " получение Ñвоих Ñообщений %(onoff)s" #: Mailman/Commands/cmd_set.py:195 +#, fuzzy msgid " hide %(onoff)s" msgstr " Ñкрыть %(onoff)s" #: Mailman/Commands/cmd_set.py:199 +#, fuzzy msgid " duplicates %(onoff)s" msgstr " без копий %(onoff)s" #: Mailman/Commands/cmd_set.py:203 +#, fuzzy msgid " reminders %(onoff)s" msgstr " напоминать пароль %(onoff)s" @@ -3810,6 +3935,7 @@ msgid "You did not give the correct password" msgstr "Указанный вами пароль неверен" #: Mailman/Commands/cmd_set.py:236 Mailman/Commands/cmd_set.py:283 +#, fuzzy msgid "Bad argument: %(arg)s" msgstr "Ðекорректный аргумент: %(arg)s" @@ -3890,6 +4016,7 @@ msgstr "" " указать без угловых Ñкобок; кавычки необходимо опуÑтить).\n" #: Mailman/Commands/cmd_subscribe.py:62 +#, fuzzy msgid "Bad digest specifier: %(arg)s" msgstr "Ðекорректное значение параметра digest: %(arg)s" @@ -3898,6 +4025,7 @@ msgid "No valid address found to subscribe" msgstr "Ð”Ð»Ñ Ð¿Ð¾Ð´Ð¿Ð¸Ñки нужно указать корректный адреÑ" #: Mailman/Commands/cmd_subscribe.py:113 +#, fuzzy msgid "" "The email address you supplied is banned from this mailing list.\n" "If you think this restriction is erroneous, please contact the list\n" @@ -3937,6 +4065,7 @@ msgstr "Ð’ Ñтом ÑпиÑке доÑтупна только доÑтавка # MSS: чуть по-другому? #: Mailman/Commands/cmd_subscribe.py:146 +#, fuzzy msgid "" "Your subscription request has been forwarded to the list administrator\n" "at %(listowner)s for review." @@ -3975,6 +4104,7 @@ msgstr "" " Ñкобок!)\n" #: Mailman/Commands/cmd_unsubscribe.py:62 +#, fuzzy msgid "%(address)s is not a member of the %(listname)s mailing list" msgstr "ÐÐ´Ñ€ÐµÑ %(address)s отÑутÑтвует в Ñтом ÑпиÑке раÑÑылки" @@ -4224,6 +4354,7 @@ msgid "Chinese (Taiwan)" msgstr "КитайÑкий (Тайвань)" #: Mailman/Deliverer.py:53 +#, fuzzy msgid "" "Note: Since this is a list of mailing lists, administrative\n" "notices like the password reminder will be sent to\n" @@ -4238,14 +4369,17 @@ msgid " (Digest mode)" msgstr " (в режиме дайджеÑта)" #: Mailman/Deliverer.py:79 +#, fuzzy msgid "Welcome to the \"%(realname)s\" mailing list%(digmode)s" msgstr "Добро пожаловать в ÑпиÑок раÑÑылки \"%(realname)s\"%(digmode)s" #: Mailman/Deliverer.py:89 +#, fuzzy msgid "You have been unsubscribed from the %(realname)s mailing list" msgstr "Ваша подпиÑка на %(realname)s была удалена" #: Mailman/Deliverer.py:116 +#, fuzzy msgid "%(listfullname)s mailing list reminder" msgstr "%(listfullname)s: ваш пароль" @@ -4259,6 +4393,7 @@ msgid "Hostile subscription attempt detected" msgstr "Обнаружена попытка подпиÑки без приглашениÑ" #: Mailman/Deliverer.py:169 +#, fuzzy msgid "" "%(address)s was invited to a different mailing\n" "list, but in a deliberate malicious attempt they tried to confirm the\n" @@ -4271,6 +4406,7 @@ msgstr "" "информирует Ð²Ð°Ñ Ð¾Ð± инциденте." #: Mailman/Deliverer.py:188 +#, fuzzy msgid "" "You invited %(address)s to your list, but in a\n" "deliberate malicious attempt, they tried to confirm the invitation to a\n" @@ -4284,6 +4420,7 @@ msgstr "" "дальнейших дейÑтвий и проÑто информирует Ð²Ð°Ñ Ð¾Ð± инциденте." #: Mailman/Deliverer.py:221 +#, fuzzy msgid "%(listname)s mailing list probe message" msgstr "Ñообщение-пробник Ð´Ð»Ñ ÑпиÑка раÑÑылки %(listname)s" @@ -4486,8 +4623,8 @@ msgid "" " membership.\n" "\n" "

                    You can control both the\n" -" number\n" +" number\n" " of reminders the member will receive and the\n" " Ð’Ñ‹ можете определÑть как чиÑло\n" "извещений о приоÑтановке подпиÑки, отправлÑемых пользователю, так и\n" -"чаÑтоту их отправки.\n" +"чаÑтоту их отправки.\n" "\n" "

                    ЕÑть еще один важный параметр: поÑле определенного периода времени,\n" "в течение которого ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð¾Ð± ошибках от Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð½Ðµ приходÑÑ‚,\n" -"Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾Ð± ошибках ÑчитаетÑÑ\n" +"Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾Ð± ошибках ÑчитаетÑÑ\n" "уÑтаревшей и удалÑетÑÑ. Задав подходÑщее значение Ñтого параметра, а\n" "также макÑимально допуÑтимое значение Ñчета ошибок, вы Ñможете указать\n" "оптимальный Ñрок, поÑле которого, подпиÑку пользователей Ñледует " @@ -4691,8 +4828,8 @@ msgid "" " No those messages too will get discarded. You may " "want\n" " to set up an\n" -" autoresponse\n" +" autoresponse\n" " message for email to the -owner and -admin address." msgstr "" "Ð¥Ð¾Ñ‚Ñ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡ÐµÑÐºÐ°Ñ Ð¾Ð±Ñ€Ð°Ð±Ð¾Ñ‚ÐºÐ° ошибок в Mailman доÑтаточно хорошаÑ,\n" @@ -4792,12 +4929,13 @@ msgstr "" "пользователÑ." #: Mailman/Gui/Bounce.py:194 +#, fuzzy msgid "" "Bad value for %(property)s: %(val)s" msgstr "" -"Ðекорректное значение параметра " -"%(property)s: %(val)s" +"Ðекорректное значение параметра %(property)s: %(val)s" #: Mailman/Gui/ContentFilter.py:30 msgid "Content filtering" @@ -4873,14 +5011,14 @@ msgstr "" "\n" "

                    Затем ÐºÐ°Ð¶Ð´Ð°Ñ multipart/alternative-ÑÐµÐºÑ†Ð¸Ñ Ð·Ð°Ð¼ÐµÐ½ÑетÑÑ\n" "ее первым вариантом, оÑтавшимÑÑ Ð½Ðµ пуÑтым поÑле фильтрации, еÑли включен\n" -"параметр collapse_alternatives.\n" +"параметр collapse_alternatives.\n" "\n" "

                    Ðаконец, вÑе оÑтавшиеÑÑ text/html-чаÑти ÑообщениÑ\n" "могут быть преобразованы в text/plain, еÑли уÑтановлено " "ÑоответÑтвующее\n" -"значение параметра convert_html_to_plaintext\n" +"значение параметра convert_html_to_plaintext\n" "и Ñервер наÑтроен так, что Ñти Ð¿Ñ€ÐµÐ¾Ð±Ñ€Ð°Ð·Ð¾Ð²Ð°Ð½Ð¸Ñ Ð²Ð¾Ð·Ð¼Ð¾Ð¶Ð½Ñ‹." #: Mailman/Gui/ContentFilter.py:75 @@ -4937,8 +5075,8 @@ msgid "" "Use this option to remove each message attachment that does\n" " not have a matching content type. Requirements and formats " "are\n" -" exactly like filter_mime_types.\n" "\n" "

                    Note: if you add entries to this list but don't add\n" @@ -5033,8 +5171,8 @@ msgstr "" "правил\n" "фильтрации Ñодержимого, то еÑть еÑли тип его Ð²Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ñовпал Ñ Ð¾Ð´Ð½Ð¸Ð¼ из " "типов\n" -"в ÑпиÑке фильтра filter_mime_types\n" +"в ÑпиÑке фильтра filter_mime_types\n" "или не Ñовпал ни Ñ Ð¾Ð´Ð½Ð¸Ð¼ типом в ÑпиÑке фильтра pass_mime_types, или " "поÑле\n" @@ -5059,6 +5197,7 @@ msgstr "" "Ñайта." #: Mailman/Gui/ContentFilter.py:171 +#, fuzzy msgid "Bad MIME type ignored: %(spectype)s" msgstr "Пропущен неверный тип MIME: %(spectype)s" @@ -5167,6 +5306,7 @@ msgstr "" "еÑли он не пуÑтой?" #: Mailman/Gui/Digest.py:145 +#, fuzzy msgid "" "The next digest will be sent as volume\n" " %(volume)s, number %(number)s" @@ -5183,14 +5323,17 @@ msgid "There was no digest to send." msgstr "ПуÑтой дайджеÑÑ‚ отправлен не будет." #: Mailman/Gui/GUIBase.py:173 +#, fuzzy msgid "Invalid value for variable: %(property)s" msgstr "ÐедопуÑтимое значение переменной: %(property)s" #: Mailman/Gui/GUIBase.py:178 +#, fuzzy msgid "Bad email address for option %(property)s: %(error)s" msgstr "ÐедопуÑтимый Ð°Ð´Ñ€ÐµÑ Ð² параметре %(property)s: %(error)s" #: Mailman/Gui/GUIBase.py:204 +#, fuzzy msgid "" "The following illegal substitution variables were\n" " found in the %(property)s string:\n" @@ -5205,6 +5348,7 @@ msgstr "" "некорректно." #: Mailman/Gui/GUIBase.py:218 +#, fuzzy msgid "" "Your %(property)s string appeared to\n" " have some correctable problems in its new value.\n" @@ -5301,8 +5445,8 @@ msgid "" "

                    In order to split the list ownership duties into\n" " administrators and moderators, you must\n" " set a separate moderator password,\n" -" and also provide the email\n" +" and also provide the email\n" " addresses of the list moderators. Note that the field you\n" " are changing here specifies the list administrators." msgstr "" @@ -5601,14 +5745,14 @@ msgstr "" "Ð’ Ñлучае fist_strip_reply_to = No, Ð°Ð´Ñ€ÐµÑ Ð¾Ñ‚Ð¿Ñ€Ð°Ð²Ð¸Ñ‚ÐµÐ»Ñ Ð¸Ð· заголовка From:\n" "иÑходного пиÑьма будет добавлен в заголовок Reply-To:, еÑли его там нет.\n" "

                    Эти дейÑтвиÑ, выбранные здеÑÑŒ или в параметре\n" -"dmarc_moderation_action,\n" +"dmarc_moderation_action,\n" "не применÑÑŽÑ‚ÑÑ Ðº ÑообщениÑм дайджеÑта или архивам, а также к ÑообщениÑм,\n" "отправленным в группы Usenet через шлюз СпиÑок раÑÑылки <-> News-" "группа.\n" "

                    ЕÑли параметром\n" -"dmarc_moderation_action\n" +"dmarc_moderation_action\n" "задано какое-либо иное дейÑтвие кроме \"ПринÑть\", оно будет выполнено " "вмеÑто\n" "указанных." @@ -5680,13 +5824,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5748,8 +5892,8 @@ msgstr "Заданный Ð°Ð´Ñ€ÐµÑ Ð´Ð»Ñ Reply-To:." msgid "" "This is the address set in the Reply-To: header\n" " when the reply_goes_to_list\n" +" href=\"?VARHELP=general/" +"reply_goes_to_list\">reply_goes_to_list\n" " option is set to Explicit address.\n" "\n" "

                    There are many reasons not to introduce or override the\n" @@ -5757,13 +5901,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5785,8 +5929,8 @@ msgid "" " Reply-To: header, it will not be changed." msgstr "" "Этот параметр определÑет, какой Ð°Ð´Ñ€ÐµÑ Ñтавить в поле Reply-To:,\n" -"еÑли значением параметра reply_goes_to_list\n" +"еÑли значением параметра reply_goes_to_list\n" "ÑвлÑетÑÑ Ðа заданный адреÑ.\n" "\n" "

                    ЕÑть много причин не вÑтавлÑть и не подменÑть заголовок Reply-To:" msgstr "" -"Параметры по умолчанию Ð´Ð»Ñ Ð½Ð¾Ð²Ñ‹Ñ… подпиÑчиков." +"Параметры по умолчанию Ð´Ð»Ñ Ð½Ð¾Ð²Ñ‹Ñ… подпиÑчиков." #: Mailman/Gui/General.py:413 msgid "" @@ -6103,8 +6247,8 @@ msgid "" " recommended." msgstr "" "Должны ли ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð¸Ð· Ñтого ÑпиÑка раÑÑылки включать заголовки,\n" -"предуÑмотренные Ñтандартом RFC 2369\n" +"предуÑмотренные Ñтандартом RFC 2369\n" "(например, List-*)? РекомендуетÑÑ Ð²Ñ‹Ð±Ñ€Ð°Ñ‚ÑŒ Да." #: Mailman/Gui/General.py:454 @@ -6831,6 +6975,7 @@ msgstr "" "на ÑпиÑок раÑÑылки других людей, не ÑÐ¿Ñ€Ð°ÑˆÐ¸Ð²Ð°Ñ Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ." #: Mailman/Gui/Privacy.py:104 +#, fuzzy msgid "" "This section allows you to configure subscription and\n" " membership exposure policy. You can also control whether this\n" @@ -7021,8 +7166,8 @@ msgid "" " either individually or as a group. Any\n" " posting from a non-member who is not explicitly accepted,\n" " rejected, or discarded, will have their posting filtered by the\n" -" general\n" +" general\n" " non-member rules.\n" "\n" "

                    In the text boxes below, add one address per line; start the\n" @@ -7079,6 +7224,7 @@ msgid "By default, should new list member postings be moderated?" msgstr "Модерировать ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð½Ð¾Ð²Ñ‹Ñ… подпиÑчиков?" #: Mailman/Gui/Privacy.py:218 +#, fuzzy msgid "" "Each list member has a moderation flag which says\n" " whether messages from the list member can be posted directly " @@ -7129,8 +7275,8 @@ msgid "" "If a member posts this many times, within a period of time\n" " the member is automatically moderated. Use 0 to disable. " "See\n" -" member_verbosity_interval for details on the time " "period.\n" "\n" @@ -7148,8 +7294,9 @@ msgstr "" "указанный период времени,\n" "он автоматичеÑки переводитÑÑ Ð² режим модерации. Значение 0 отключает Ñто " "поведение.\n" -"См. параметр member_verbosity_interval Ð´Ð»Ñ Ð·Ð°Ð´Ð°Ð½Ð¸Ñ Ð²Ñ€ÐµÐ¼ÐµÐ½Ð½Ð¾Ð³Ð¾ интервала.\n" +"См. параметр member_verbosity_interval Ð´Ð»Ñ Ð·Ð°Ð´Ð°Ð½Ð¸Ñ " +"временного интервала.\n" "\n" "

                    Этот параметр предназначен Ð´Ð»Ñ Ð¿Ñ€ÐµÐ´Ð¾Ñ‚Ð²Ñ€Ð°Ñ‰ÐµÐ½Ð¸Ñ Ð¼Ð½Ð¾Ð¶ÐµÑтвенных Ñпам-" "Ñообщений от новых подпиÑчиков в короткий период времени.\n" @@ -7219,8 +7366,8 @@ msgstr "" "

                    • Задержать -- задержать Ñообщение Ð´Ð»Ñ Ð¿Ñ€Ð¾Ð²ÐµÑ€ÐºÐ¸ модератором.\n" "

                    • ОтклонÑть -- автоматичеÑки отклонить Ñообщение и отоÑлать\n" "извещение об Ñтом отправителю иÑходного ÑообщениÑ. ТекÑÑ‚ такого извещениÑ\n" -"может быть определен\n" +"может быть определен\n" "вами.\n" "

                    • Удалить -- проÑто удалить Ñообщение без уведомлениÑ\n" "отправителÑ.
                    " @@ -7237,6 +7384,7 @@ msgstr "" "об отклонении ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð´Ð»Ñ Ð¿Ð¾Ð´Ð¿Ð¸Ñчиков." #: Mailman/Gui/Privacy.py:290 +#, fuzzy msgid "" "Action to take when anyone posts to the\n" " list from a domain with a DMARC Reject%(quarantine)s Policy." @@ -7273,8 +7421,8 @@ msgid "" " if the message is From: an affected domain and the setting is\n" " other than Accept." msgstr "" -"
                    • Подменить From -- оÑущеÑтвить преобразование \"Подменить From" -"\", заданное параметром
                    • Подменить From -- оÑущеÑтвить преобразование \"Подменить " +"From\", заданное параметром from_is_list\n" "Ð´Ð»Ñ Ñтого ÑообщениÑ.\n" "

                    • Вложить Ñообщение -- оÑущеÑтвить преобразование \"Вложить " @@ -7283,8 +7431,8 @@ msgstr "" "Ð´Ð»Ñ Ñтого ÑообщениÑ.\n" "

                    • Отклонить -- автоматичеÑки отклонить Ñообщение и отоÑлать\n" "извещение об Ñтом отправителю иÑходного ÑообщениÑ. ТекÑÑ‚ такого извещениÑ\n" -"может быть задан\n" +"может быть задан\n" "Вами.\n" "

                    • Удалить -- проÑто удалить Ñообщение без ÑƒÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ñ " "отправителÑ.
                    \n" @@ -7333,8 +7481,8 @@ msgstr "" "

                  ЕÑли домен Ð¾Ñ‚Ð¿Ñ€Ð°Ð²Ð¸Ñ‚ÐµÐ»Ñ ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð¸Ð¼ÐµÐµÑ‚ DMARC-политику p=quarantine\n" "и dmarc_moderation_action к нему не применÑетÑÑ (Ñтот параметр уÑтановлен в " "\"Ðет\"),\n" -"то Ñто Ñообщение Ñкорее вÑего не будет отвергнуто, но попадет в папку \"Спам" -"\"\n" +"то Ñто Ñообщение Ñкорее вÑего не будет отвергнуто, но попадет в папку " +"\"Спам\"\n" "Ð¿Ð¾Ð»ÑƒÑ‡Ð°Ñ‚ÐµÐ»Ñ Ð¸Ð»Ð¸ другие подобные меÑта, где его будет трудно найти." #: Mailman/Gui/Privacy.py:335 @@ -7377,11 +7525,12 @@ msgstr "" "

                ЕÑли домен Ð¾Ñ‚Ð¿Ñ€Ð°Ð²Ð¸Ñ‚ÐµÐ»Ñ ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð¸Ð¼ÐµÐµÑ‚ DMARC-политику p=quarantine\n" "и dmarc_moderation_action к нему не применÑетÑÑ (Ñтот параметр уÑтановлен в " "\"Ðет\"),\n" -"то Ñто Ñообщение Ñкорее вÑего не будет отвергнуто, но попадет в папку \"Спам" -"\"\n" +"то Ñто Ñообщение Ñкорее вÑего не будет отвергнуто, но попадет в папку " +"\"Спам\"\n" "Ð¿Ð¾Ð»ÑƒÑ‡Ð°Ñ‚ÐµÐ»Ñ Ð¸Ð»Ð¸ другие подобные меÑта, где его будет трудно найти." #: Mailman/Gui/Privacy.py:353 +#, fuzzy msgid "" "Text to include in any\n" " извещение\n" +"ТекÑÑ‚, включаемый в извещение\n" "об отклонении ÑообщениÑ, отправленного в ÑпиÑок раÑÑылки Ñ Ð´Ð¾Ð¼ÐµÐ½Ð¾Ð¼\n" -"отправителÑ, наÑтроенным на иÑпользование DMARC-политики Reject" -"%(quarantine)s." +"отправителÑ, наÑтроенным на иÑпользование DMARC-политики " +"Reject%(quarantine)s." #: Mailman/Gui/Privacy.py:360 #, fuzzy @@ -7403,11 +7552,11 @@ msgid "" " >dmarc_moderation_action \n" " regardless of any domain specific DMARC Policy." msgstr "" -"ТекÑÑ‚, включаемый в извещение\n" +"ТекÑÑ‚, включаемый в извещение\n" "об отклонении ÑообщениÑ, отправленного в ÑпиÑок раÑÑылки Ñ Ð´Ð¾Ð¼ÐµÐ½Ð¾Ð¼\n" -"отправителÑ, наÑтроенным на иÑпользование DMARC-политики Reject" -"%(quarantine)s." +"отправителÑ, наÑтроенным на иÑпользование DMARC-политики " +"Reject%(quarantine)s." #: Mailman/Gui/Privacy.py:365 #, fuzzy @@ -7610,8 +7759,8 @@ msgstr "" "Ð’Ñе ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ñ Ñтих адреÑов будут автоматичеÑки отклонÑтьÑÑ.\n" "Отправители будут получать извещениÑ, Ñообщающие об Ñтом. Этот\n" "выбор не очень подходит Ð´Ð»Ñ Ð±Ð¾Ñ€ÑŒÐ±Ñ‹ Ñо Ñпамерами, рекламные ÑообщениÑ\n" -"должны автоматичеÑки\n" +"должны автоматичеÑки\n" "удалÑтьÑÑ.\n" "\n" "

                Ð’ каждой Ñтроке должен быть\n" @@ -7676,8 +7825,8 @@ msgid "" msgstr "" "При получении ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ñ Ð°Ð´Ñ€ÐµÑа, не входÑщего в ÑпиÑок раÑÑылки,\n" "его отправитель ÑравниваетÑÑ Ñо ÑпиÑками, Ñвно применÑющими какой-либо\n" -"фильтр: принÑтие\n" +"фильтр: принÑтие\n" "ÑообщениÑ, удержание длÑ\n" "модерированиÑ, The body of the message can also be optionally scanned for\n" " Subject: and Keywords: headers, as\n" " specified by the topics_bodylines_limit\n" +" href=\"?VARHELP=topics/" +"topics_bodylines_limit\">topics_bodylines_limit\n" " configuration variable." msgstr "" "Фильтр разделов Ñортирует вÑе входÑщие ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð² ÑоответÑтвии Ñ\n" @@ -8017,8 +8168,8 @@ msgstr "" "

                Во Ð²Ñ€ÐµÐ¼Ñ Ñортировки можно также проÑматривать и тело ÑообщениÑ, в\n" "нем будут также иÑкатьÑÑ Ð·Ð°Ð³Ð¾Ð»Ð¾Ð²ÐºÐ¸ Subject: и\n" "Keywords:, количеÑтво проÑматриваемых Ñтрок определÑетÑÑ\n" -"параметром topics_bodylines_limit." +"параметром topics_bodylines_limit." # MSS: улучшить! "Сколько начальных Ñтрок проÑматривать?" #: Mailman/Gui/Topics.py:72 @@ -8085,6 +8236,7 @@ msgstr "" "Ðеполные правила будут пропущены." #: Mailman/Gui/Topics.py:135 +#, fuzzy msgid "" "The topic pattern '%(safepattern)s' is not a\n" " legal regular expression. It will be discarded." @@ -8303,6 +8455,7 @@ msgid "%(listinfo_link)s list run by %(owner_link)s" msgstr "админиÑтраторы ÑпиÑка раÑÑылки %(listinfo_link)s: %(owner_link)s" #: Mailman/HTMLFormatter.py:57 +#, fuzzy msgid "%(realname)s administrative interface" msgstr "Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ Ð°Ð´Ð¼Ð¸Ð½Ð¸Ñтратора Ð´Ð»Ñ %(realname)s" @@ -8311,6 +8464,7 @@ msgid " (requires authorization)" msgstr " (требует аутентификации)" #: Mailman/HTMLFormatter.py:61 +#, fuzzy msgid "Overview of all %(hostname)s mailing lists" msgstr "СпиÑки раÑÑылки, раÑположенные на %(hostname)s" @@ -8332,6 +8486,7 @@ msgid "; it was disabled by the list administrator" msgstr " (по решению админиÑтратора)" #: Mailman/HTMLFormatter.py:146 +#, fuzzy msgid "" "; it was disabled due to excessive bounces. The\n" " last bounce was received on %(date)s" @@ -8344,6 +8499,7 @@ msgid "; it was disabled for unknown reasons" msgstr " (причина неизвеÑтна)" #: Mailman/HTMLFormatter.py:151 +#, fuzzy msgid "Note: your list delivery is currently disabled%(reason)s." msgstr "Внимание: доÑтавка вам раÑÑылки приоÑтановлена%(reason)s." @@ -8356,6 +8512,7 @@ msgid "the list administrator" msgstr "админиÑтратору ÑпиÑка раÑÑылки" #: Mailman/HTMLFormatter.py:157 +#, fuzzy msgid "" "

                %(note)s\n" "\n" @@ -8376,6 +8533,7 @@ msgstr "" "какие-либо вопроÑÑ‹." #: Mailman/HTMLFormatter.py:169 +#, fuzzy msgid "" "

                We have received some recent bounces from your\n" " address. Your current bounce score is %(score)s out of " @@ -8396,6 +8554,7 @@ msgstr "" # fattie: check %(type)s notice #: Mailman/HTMLFormatter.py:181 +#, fuzzy msgid "" "(Note - you are subscribing to a list of mailing lists, so the %(type)s " "notice will be sent to the admin address for your membership, %(addr)s.)

                " @@ -8442,6 +8601,7 @@ msgstr "" "ÑпиÑка раÑÑылки. О решении модератора Ð²Ð°Ñ ÑƒÐ²ÐµÐ´Ð¾Ð¼ÑÑ‚ по Ñлектронной почте." #: Mailman/HTMLFormatter.py:208 +#, fuzzy msgid "" "This is %(also)sa private list, which means that the\n" " list of members is not available to non-members." @@ -8450,6 +8610,7 @@ msgstr "" "могут проÑматривать только Ñами подпиÑчики." #: Mailman/HTMLFormatter.py:211 +#, fuzzy msgid "" "This is %(also)sa hidden list, which means that the\n" " list of members is available only to the list administrator." @@ -8458,6 +8619,7 @@ msgstr "" "могут проÑматривать только админиÑтраторы раÑÑылки." #: Mailman/HTMLFormatter.py:214 +#, fuzzy msgid "" "This is %(also)sa public list, which means that the\n" " list of members list is available to everyone." @@ -8474,6 +8636,7 @@ msgstr "" "автоматичеÑки.)" #: Mailman/HTMLFormatter.py:222 +#, fuzzy msgid "" "

                (Note that this is an umbrella list, intended to\n" " have only other mailing lists as members. Among other things,\n" @@ -8491,6 +8654,7 @@ msgid "either " msgstr "или " #: Mailman/HTMLFormatter.py:256 +#, fuzzy msgid "" "To unsubscribe from %(realname)s, get a password reminder,\n" " or change your subscription options %(either)senter your " @@ -8524,12 +8688,14 @@ msgstr "" "Ñвой Ð°Ð´Ñ€ÐµÑ Ñлектронной почты." #: Mailman/HTMLFormatter.py:277 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " members.)" msgstr "(%(which)s доÑтупен только подпиÑчикам ÑпиÑка раÑÑылки.)" #: Mailman/HTMLFormatter.py:281 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " administrator.)" @@ -8588,6 +8754,7 @@ msgid "The current archive" msgstr "Текущий архив" #: Mailman/Handlers/Acknowledge.py:59 +#, fuzzy msgid "%(realname)s post acknowledgement" msgstr "Подтверждение доÑтавки в ÑпиÑок раÑÑылки %(realname)s" @@ -8604,6 +8771,7 @@ msgstr "" "в формате HTML.\n" #: Mailman/Handlers/CalcRecips.py:79 +#, fuzzy msgid "" "Your urgent message to the %(realname)s mailing list was not authorized for\n" "delivery. The original message as received by Mailman is attached.\n" @@ -8612,6 +8780,7 @@ msgstr "" "разрешена. Сообщение, полученное Mailman, находитÑÑ Ð²Ð¾ вложении.\n" #: Mailman/Handlers/CookHeaders.py:180 +#, fuzzy msgid "%(realname)s via %(lrn)s" msgstr "%(realname)s via %(lrn)s" @@ -8683,6 +8852,7 @@ msgid "Message may contain administrivia" msgstr "Возможно, в Ñообщении ÑодержитÑÑ Ð°Ð´Ð¼Ð¸Ð½Ð¸Ñтративный запроÑ" #: Mailman/Handlers/Hold.py:84 +#, fuzzy msgid "" "Please do *not* post administrative requests to the mailing\n" "list. If you wish to subscribe, visit %(listurl)s or send a message with " @@ -8724,11 +8894,13 @@ msgid "Posting to a moderated newsgroup" msgstr "Отправка ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð² модерируюмую конференцию" #: Mailman/Handlers/Hold.py:252 +#, fuzzy msgid "Your message to %(listname)s awaits moderator approval" msgstr "" "Ваше Ñообщение в ÑпиÑок раÑÑылки %(listname)s ожидает обработки модератора" #: Mailman/Handlers/Hold.py:271 +#, fuzzy msgid "%(listname)s post from %(sender)s requires approval" msgstr "" "Сообщение в ÑпиÑок раÑÑылки %(listname)s Ñ Ð°Ð´Ñ€ÐµÑа %(sender)s требует проверки" @@ -8773,6 +8945,7 @@ msgid "After content filtering, the message was empty" msgstr "ПоÑле фильтрации Ñодержимого Ñообщение оказалоÑÑŒ пуÑтым" #: Mailman/Handlers/MimeDel.py:269 +#, fuzzy msgid "" "The attached message matched the %(listname)s mailing list's content " "filtering\n" @@ -8792,6 +8965,7 @@ msgid "Content filtered message notification" msgstr "Уведомление о фильтрации Ñодержимого ÑообщениÑ" #: Mailman/Handlers/Moderate.py:145 +#, fuzzy msgid "" "Your message has been rejected, probably because you are not subscribed to " "the\n" @@ -8816,6 +8990,7 @@ msgid "The attached message has been automatically discarded." msgstr "Вложенное Ñообщение было автоматичеÑки удалено." #: Mailman/Handlers/Replybot.py:75 +#, fuzzy msgid "Auto-response for your message to the \"%(realname)s\" mailing list" msgstr "ÐвтоматичеÑкий ответ на ваше Ñообщение в ÑпиÑок раÑÑылки %(realname)s" @@ -8824,6 +8999,7 @@ msgid "The Mailman Replybot" msgstr "Ðвтоответчик Mailman" #: Mailman/Handlers/Scrubber.py:208 +#, fuzzy msgid "" "An embedded and charset-unspecified text was scrubbed...\n" "Name: %(filename)s\n" @@ -8840,6 +9016,7 @@ msgid "HTML attachment scrubbed and removed" msgstr "Вложение в формате HTML извлечено и удалено" #: Mailman/Handlers/Scrubber.py:234 Mailman/Handlers/Scrubber.py:259 +#, fuzzy msgid "" "An HTML attachment was scrubbed...\n" "URL: %(url)s\n" @@ -8860,6 +9037,7 @@ msgid "unknown sender" msgstr "неизвеÑтный отправитель" #: Mailman/Handlers/Scrubber.py:276 +#, fuzzy msgid "" "An embedded message was scrubbed...\n" "From: %(who)s\n" @@ -8876,6 +9054,7 @@ msgstr "" "URL: %(url)s\n" #: Mailman/Handlers/Scrubber.py:308 +#, fuzzy msgid "" "A non-text attachment was scrubbed...\n" "Name: %(filename)s\n" @@ -8892,6 +9071,7 @@ msgstr "" "URL: %(url)s\n" #: Mailman/Handlers/Scrubber.py:347 +#, fuzzy msgid "Skipped content of type %(partctype)s\n" msgstr "Пропущена чаÑть Ñодержимого типа %(partctype)s\n" @@ -8900,10 +9080,12 @@ msgid "-------------- next part --------------\n" msgstr "----------- ÑÐ»ÐµÐ´ÑƒÑŽÑ‰Ð°Ñ Ñ‡Ð°Ñть -----------\n" #: Mailman/Handlers/SpamDetect.py:64 +#, fuzzy msgid "Header matched regexp: %(pattern)s" msgstr "Заголовок подходит под шаблон: %(pattern)s" #: Mailman/Handlers/SpamDetect.py:127 +#, fuzzy msgid "" "You are not allowed to post to this mailing list From: a domain which\n" "publishes a DMARC policy of reject or quarantine, and your message has been\n" @@ -8923,6 +9105,7 @@ msgid "Message rejected by filter rule match" msgstr "Сообщение отклонено фильтром" #: Mailman/Handlers/ToDigest.py:173 +#, fuzzy msgid "%(realname)s Digest, Vol %(volume)d, Issue %(issue)d" msgstr "" "ДайджеÑÑ‚ ÑпиÑка раÑÑылки %(realname)s; том %(volume)d, выпуÑк %(issue)d" @@ -8960,6 +9143,7 @@ msgid "End of " msgstr "Конец " #: Mailman/ListAdmin.py:309 +#, fuzzy msgid "Posting of your message titled \"%(subject)s\"" msgstr "Ваше Ñообщение Ñ Ð·Ð°Ð³Ð¾Ð»Ð¾Ð²ÐºÐ¾Ð¼ \"%(subject)s\"" @@ -8973,6 +9157,7 @@ msgstr "Перенаправление модерируемого Ñообщен # MSS: "в ÑпиÑок раÑÑылки"? #: Mailman/ListAdmin.py:405 +#, fuzzy msgid "New subscription request to list %(realname)s from %(addr)s" msgstr "" "Ðовый Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° подпиÑку в ÑпиÑок раÑÑылки %(realname)s Ñ Ð°Ð´Ñ€ÐµÑа %(addr)s" @@ -8987,6 +9172,7 @@ msgid "via admin approval" msgstr "Отложить до Ð¿Ð¾Ð»ÑƒÑ‡ÐµÐ½Ð¸Ñ Ð¿Ð¾Ð´Ñ‚Ð²ÐµÑ€Ð¶Ð´ÐµÐ½Ð¸Ñ" #: Mailman/ListAdmin.py:466 +#, fuzzy msgid "New unsubscription request from %(realname)s by %(addr)s" msgstr "" "Ðовый Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° удаление подпиÑки на ÑпиÑок раÑÑылки %(realname)s Ñ Ð°Ð´Ñ€ÐµÑа " @@ -9001,10 +9187,12 @@ msgid "Original Message" msgstr "ИÑходное Ñообщение" #: Mailman/ListAdmin.py:526 +#, fuzzy msgid "Request to mailing list %(realname)s rejected" msgstr "Ð—Ð°Ð¿Ñ€Ð¾Ñ Ð² ÑпиÑок раÑÑылки %(realname)s отклонен" #: Mailman/MTA/Manual.py:66 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been created via the through-the-web\n" "interface. In order to complete the activation of this mailing list, the\n" @@ -9032,14 +9220,17 @@ msgstr "" "Ñледующие Ñтроки, а затем запуÑтить программу 'newaliases':\n" #: Mailman/MTA/Manual.py:82 +#, fuzzy msgid "## %(listname)s mailing list" msgstr "## ÑпиÑок раÑÑылки %(listname)s" #: Mailman/MTA/Manual.py:99 +#, fuzzy msgid "Mailing list creation request for list %(listname)s" msgstr "Ð—Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° Ñоздание ÑпиÑка раÑÑылки %(listname)s" #: Mailman/MTA/Manual.py:113 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been removed via the through-the-web\n" "interface. In order to complete the de-activation of this mailing list, " @@ -9052,12 +9243,13 @@ msgstr "" "СпиÑок раÑÑылки \"%(listname)s\" был удален через веб-интерфейÑ. Чтобы " "завершить\n" "процеÑÑ ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ ÑпиÑка, вам необходимо обновить файл /etc/aliases (или\n" -"выполнÑющий аналогичные функции), а затем запуÑтить программу \"newaliases" -"\":\n" +"выполнÑющий аналогичные функции), а затем запуÑтить программу " +"\"newaliases\":\n" "\n" "Содержимое файла /etc/aliases, которое должно быть удалено:\n" #: Mailman/MTA/Manual.py:123 +#, fuzzy msgid "" "\n" "To finish removing your mailing list, you must edit your /etc/aliases (or\n" @@ -9075,14 +9267,17 @@ msgstr "" "## ÑпиÑок раÑÑылки %(listname)s" #: Mailman/MTA/Manual.py:142 +#, fuzzy msgid "Mailing list removal request for list %(listname)s" msgstr "Ð—Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° удаление ÑпиÑка раÑÑылки %(listname)s" #: Mailman/MTA/Postfix.py:442 +#, fuzzy msgid "checking permissions on %(file)s" msgstr "проверÑетÑÑ Ñ€ÐµÐ¶Ð¸Ð¼ доÑтупа к %(file)s" #: Mailman/MTA/Postfix.py:452 +#, fuzzy msgid "%(file)s permissions must be 0664 (got %(octmode)s)" msgstr "режим доÑтупа к %(file)s должен быть 0664 (а не %(octmode)s)" @@ -9096,35 +9291,43 @@ msgid "(fixing)" msgstr "(иÑправлÑÑŽÑ‚ÑÑ)" #: Mailman/MTA/Postfix.py:470 +#, fuzzy msgid "checking ownership of %(dbfile)s" msgstr "проверÑетÑÑ Ð²Ð»Ð°Ð´ÐµÐ»ÐµÑ† %(dbfile)s" #: Mailman/MTA/Postfix.py:478 +#, fuzzy msgid "%(dbfile)s owned by %(owner)s (must be owned by %(user)s" msgstr "владелец %(dbfile)s -- %(owner)s (должен быть %(user)s)" #: Mailman/MTA/Postfix.py:491 +#, fuzzy msgid "%(dbfile)s permissions must be 0664 (got %(octmode)s)" msgstr "режим доÑтупа к %(dbfile)s должен быть 0664 (а не %(octmode)s)" #: Mailman/MailList.py:219 +#, fuzzy msgid "Your confirmation is required to join the %(listname)s mailing list" msgstr "Подтвердите вашу подпиÑку на ÑпиÑок раÑÑылки %(listname)s" #: Mailman/MailList.py:230 +#, fuzzy msgid "Your confirmation is required to leave the %(listname)s mailing list" msgstr "" "Подтвердите Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° удаление вашей подпиÑки на ÑпиÑок раÑÑылки %(listname)s" #: Mailman/MailList.py:999 Mailman/MailList.py:1492 +#, fuzzy msgid " from %(remote)s" msgstr " Ñ Ð°Ð´Ñ€ÐµÑа %(remote)s" #: Mailman/MailList.py:1043 +#, fuzzy msgid "subscriptions to %(realname)s require moderator approval" msgstr "подпиÑка на ÑпиÑок %(realname)s требует Ð¿Ð¾Ð´Ñ‚Ð²ÐµÑ€Ð¶Ð´ÐµÐ½Ð¸Ñ Ð¼Ð¾Ð´ÐµÑ€Ð°Ñ‚Ð¾Ñ€Ð°" #: Mailman/MailList.py:1125 bin/add_members:299 +#, fuzzy msgid "%(realname)s subscription notification" msgstr "уведомление о подпиÑке на ÑпиÑок %(realname)s" @@ -9133,10 +9336,12 @@ msgid "unsubscriptions require moderator approval" msgstr "удаление подпиÑки требует Ð¿Ð¾Ð´Ñ‚Ð²ÐµÑ€Ð¶Ð´ÐµÐ½Ð¸Ñ Ð¼Ð¾Ð´ÐµÑ€Ð°Ñ‚Ð¾Ñ€Ð°" #: Mailman/MailList.py:1166 +#, fuzzy msgid "%(realname)s unsubscribe notification" msgstr "уведомление об удалении подпиÑки на ÑпиÑок %(realname)s" #: Mailman/MailList.py:1328 +#, fuzzy msgid "%(realname)s address change notification" msgstr "уведомление об изменении адреÑа подпиÑчика %(realname)s" @@ -9151,6 +9356,7 @@ msgid "via web confirmation" msgstr "Строка Ð¿Ð¾Ð´Ñ‚Ð²ÐµÑ€Ð¶Ð´ÐµÐ½Ð¸Ñ Ð½ÐµÐ²ÐµÑ€Ð½Ð°" #: Mailman/MailList.py:1396 +#, fuzzy msgid "subscriptions to %(name)s require administrator approval" msgstr "подпиÑка на %(name)s требует Ð¿Ð¾Ð´Ñ‚Ð²ÐµÑ€Ð¶Ð´ÐµÐ½Ð¸Ñ Ð°Ð´Ð¼Ð¸Ð½Ð¸Ñтратора" @@ -9169,6 +9375,7 @@ msgid "Last autoresponse notification for today" msgstr "ПоÑледнее автоматичеÑкое уведомление на ÑегоднÑ" #: Mailman/Queue/BounceRunner.py:360 +#, fuzzy msgid "" "The attached message was received as a bounce, but either the bounce format\n" "was not recognized, or no member addresses could be extracted from it. " @@ -9225,8 +9432,8 @@ msgid "" "To obtain instructions, send a message containing just the word \"help\".\n" msgstr "" "Ð’ Ñтом Ñообщении не было найдено команд.\n" -"Чтобы получить подÑказку, отправьте Ñообщение, Ñодержащее только Ñлово \"help" -"\".\n" +"Чтобы получить подÑказку, отправьте Ñообщение, Ñодержащее только Ñлово " +"\"help\".\n" #: Mailman/Queue/CommandRunner.py:197 msgid "" @@ -9259,6 +9466,7 @@ msgid "Original message suppressed by Mailman site configuration\n" msgstr "Вложение иÑходного ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð¾Ñ‚ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¾ наÑтройками Ñервера\n" #: Mailman/htmlformat.py:675 +#, fuzzy msgid "Delivered by Mailman
                version %(version)s" msgstr "Mailman верÑии %(version)s" @@ -9347,6 +9555,7 @@ msgid "Server Local Time" msgstr "Ð’Ñ€ÐµÐ¼Ñ Ð½Ð° Ñервере" #: Mailman/i18n.py:180 +#, fuzzy msgid "" "%(wday)s %(mon)s %(day)2i %(hh)02i:%(mm)02i:%(ss)02i %(tzname)s %(year)04i" msgstr "" @@ -9462,6 +9671,7 @@ msgstr "" "Только один из Ñтих файлов может быть задан как \"-\".\n" #: bin/add_members:162 bin/add_members:172 +#, fuzzy msgid "Already a member: %(member)s" msgstr "Уже ÑвлÑетÑÑ Ð¿Ð¾Ð´Ð¿Ð¸Ñчиком: %(member)s" @@ -9470,10 +9680,12 @@ msgid "Bad/Invalid email address: blank line" msgstr "Ðекорректный Ñлектронный адреÑ: пуÑÑ‚Ð°Ñ Ñтрока" #: bin/add_members:180 +#, fuzzy msgid "Bad/Invalid email address: %(member)s" msgstr "Ðеверный/недопуÑтимый адреÑ: %(member)s" #: bin/add_members:182 +#, fuzzy msgid "Hostile address (illegal characters): %(member)s" msgstr "ÐедопуÑтимый Ð°Ð´Ñ€ÐµÑ (Ñодержит запрещенные Ñимволы): %(member)s" @@ -9483,14 +9695,17 @@ msgid "Invited: %(member)s" msgstr "ПодпиÑан: %(member)s" #: bin/add_members:187 +#, fuzzy msgid "Subscribed: %(member)s" msgstr "ПодпиÑан: %(member)s" #: bin/add_members:237 +#, fuzzy msgid "Bad argument to -w/--welcome-msg: %(arg)s" msgstr "ÐедопуÑтимый аргумент Ð´Ð»Ñ -w/--welcome-msg: %(arg)s" #: bin/add_members:244 +#, fuzzy msgid "Bad argument to -a/--admin-notify: %(arg)s" msgstr "ÐедопуÑтимый аргумент Ð´Ð»Ñ -a/--admin-notify: %(arg)s" @@ -9508,6 +9723,7 @@ msgstr "" #: bin/add_members:261 bin/config_list:110 bin/export.py:271 bin/find_member:97 #: bin/inject:91 bin/list_admins:90 bin/list_members:252 bin/sync_members:222 #: cron/bumpdigests:86 +#, fuzzy msgid "No such list: %(listname)s" msgstr "Ðет такого ÑпиÑка раÑÑылки: %(listname)s" @@ -9518,6 +9734,7 @@ msgid "Nothing to do." msgstr "Ðичего делать не надо." #: bin/arch:19 +#, fuzzy msgid "" "Rebuild a list's archive.\n" "\n" @@ -9610,6 +9827,7 @@ msgid "listname is required" msgstr "вы должны указать Ð¸Ð¼Ñ ÑпиÑка раÑÑылки" #: bin/arch:143 bin/change_pw:107 bin/config_list:257 +#, fuzzy msgid "" "No such list \"%(listname)s\"\n" "%(e)s" @@ -9618,10 +9836,12 @@ msgstr "" "%(e)s" #: bin/arch:168 +#, fuzzy msgid "Cannot open mbox file %(mbox)s: %(msg)s" msgstr "Ðе удалоÑÑŒ открыть файл %(mbox)s: %(msg)s" #: bin/b4b5-archfix:19 +#, fuzzy msgid "" "Fix the MM2.1b4 archives.\n" "\n" @@ -9763,6 +9983,7 @@ msgstr "" " Ðапечатать Ñту подÑказку и завершить работу.\n" #: bin/change_pw:145 +#, fuzzy msgid "Bad arguments: %(strargs)s" msgstr "Ðеверные аргументы: %(strargs)s" @@ -9771,14 +9992,17 @@ msgid "Empty list passwords are not allowed" msgstr "Пароли к ÑпиÑкам не могут быть пуÑтыми" #: bin/change_pw:181 +#, fuzzy msgid "New %(listname)s password: %(notifypassword)s" msgstr "Ðовый пароль ÑпиÑка %(listname)s: %(notifypassword)s" #: bin/change_pw:190 +#, fuzzy msgid "Your new %(listname)s list password" msgstr "Ваш новый пароль Ð´Ð»Ñ ÑпиÑка %(listname)s" #: bin/change_pw:191 +#, fuzzy msgid "" "The site administrator at %(hostname)s has changed the password for your\n" "mailing list %(listname)s. It is now\n" @@ -9805,6 +10029,7 @@ msgstr "" " %(adminurl)s\n" #: bin/check_db:19 +#, fuzzy msgid "" "Check a list's config database file for integrity.\n" "\n" @@ -9880,10 +10105,12 @@ msgid "List:" msgstr "СпиÑок:" #: bin/check_db:148 +#, fuzzy msgid " %(file)s: okay" msgstr " %(file)s: не поврежден" #: bin/check_perms:20 +#, fuzzy msgid "" "Check the permissions for the Mailman installation.\n" "\n" @@ -9904,46 +10131,56 @@ msgstr "" "-v, вывод будет более подробным.\n" #: bin/check_perms:110 +#, fuzzy msgid " checking gid and mode for %(path)s" msgstr " проверка группы владельца и режима доÑтупа к файлу %(path)s" #: bin/check_perms:122 +#, fuzzy msgid "%(path)s bad group (has: %(groupname)s, expected %(MAILMAN_GROUP)s)" msgstr "" "у файла %(path)s неÑоответÑÑ‚Ð²ÑƒÑŽÑ‰Ð°Ñ Ð³Ñ€ÑƒÐ¿Ð¿Ð° владельца (%(groupname)s, " "ожидаетÑÑ %(MAILMAN_GROUP)s)" #: bin/check_perms:151 +#, fuzzy msgid "directory permissions must be %(octperms)s: %(path)s" msgstr "режим доÑтупа к каталогу должны быть %(octperms)s: %(path)s" #: bin/check_perms:160 +#, fuzzy msgid "source perms must be %(octperms)s: %(path)s" msgstr "режим доÑтупа к коду должны быть %(octperms)s: %(path)s" #: bin/check_perms:171 +#, fuzzy msgid "article db files must be %(octperms)s: %(path)s" msgstr "" "режим доÑтупа к файлам баз данных Ñтатей должны быть %(octperms)s: %(path)s" #: bin/check_perms:183 +#, fuzzy msgid "checking mode for %(prefix)s" msgstr "проверка режима доÑтупа к %(prefix)s" #: bin/check_perms:193 +#, fuzzy msgid "WARNING: directory does not exist: %(d)s" msgstr "ПРЕДУПРЕЖДЕÐИЕ: каталог не ÑущеÑтвует: %(d)s" #: bin/check_perms:197 +#, fuzzy msgid "directory must be at least 02775: %(d)s" msgstr "режим доÑтупа к каталогу должны быть по меньшей мере 02775: %(d)s" #: bin/check_perms:209 +#, fuzzy msgid "checking perms on %(private)s" msgstr "проверка режима доÑтупа к %(private)s" # MSS: "дргим пользователÑм" тоже не очень хорошо :( #: bin/check_perms:214 +#, fuzzy msgid "%(private)s must not be other-readable" msgstr "" "файл %(private)s должен быть доÑтупен Ð´Ð»Ñ Ñ‡Ñ‚ÐµÐ½Ð¸Ñ Ñ‚Ð¾Ð»ÑŒÐºÐ¾ владельцу и группе" @@ -9970,6 +10207,7 @@ msgstr "" # MSS: поÑторонние пользователи? :( #: bin/check_perms:263 +#, fuzzy msgid "%(dbdir)s \"other\" perms must be 000" msgstr "%(dbdir)s: режим доÑтупа Ð´Ð»Ñ Ð¿Ð¾Ñторонних пользователей должен быть 000" @@ -9979,29 +10217,35 @@ msgstr "проверка режима доÑтупа к файлам в cgi-bin" # MSS: на Ñамом деле, Ñто немного о другом #: bin/check_perms:278 +#, fuzzy msgid " checking set-gid for %(path)s" msgstr " проверка флага setgid Ð´Ð»Ñ %(path)s" # MSS: на Ñамом деле, Ñто немного о другом #: bin/check_perms:282 +#, fuzzy msgid "%(path)s must be set-gid" msgstr "у %(path)s должен быть флаг setgid" # MSS: на Ñамом деле, Ñто немного о другом #: bin/check_perms:292 +#, fuzzy msgid "checking set-gid for %(wrapper)s" msgstr "проверка флага setgid Ð´Ð»Ñ %(wrapper)s" # MSS: на Ñамом деле, Ñто немного о другом #: bin/check_perms:296 +#, fuzzy msgid "%(wrapper)s must be set-gid" msgstr "у %(wrapper)s должен быть флаг setgid" #: bin/check_perms:306 +#, fuzzy msgid "checking permissions on %(pwfile)s" msgstr "проверка режима доÑтупа к файлу %(pwfile)s" #: bin/check_perms:315 +#, fuzzy msgid "%(pwfile)s permissions must be exactly 0640 (got %(octmode)s)" msgstr "" "у файла %(pwfile)s режим доÑтупа должен быть в точноÑти 0640 (а не " @@ -10012,10 +10256,12 @@ msgid "checking permissions on list data" msgstr "проверка режима доÑтупа к файлам данных ÑпиÑков" #: bin/check_perms:348 +#, fuzzy msgid " checking permissions on: %(path)s" msgstr " проверка режима доÑтупа: %(path)s" #: bin/check_perms:356 +#, fuzzy msgid "file permissions must be at least 660: %(path)s" msgstr "режим доÑтупа к файлу должен быть Ñ…Ð¾Ñ‚Ñ Ð±Ñ‹ 660: %(path)s" @@ -10103,6 +10349,7 @@ msgid "Unix-From line changed: %(lineno)d" msgstr "Изменена Ñтрока, ÑÐ¾Ð´ÐµÑ€Ð¶Ð°Ñ‰Ð°Ñ \"From \": %(lineno)d" #: bin/cleanarch:111 +#, fuzzy msgid "Bad status number: %(arg)s" msgstr "ÐеÑущеÑтвующее значение ÑоÑтоÑниÑ: %(arg)s" @@ -10251,10 +10498,12 @@ msgid " original address removed:" msgstr " иÑходный Ð°Ð´Ñ€ÐµÑ ÑƒÐ´Ð°Ð»ÐµÐ½:" #: bin/clone_member:202 +#, fuzzy msgid "Not a valid email address: %(toaddr)s" msgstr "ÐедейÑтвительный Ð°Ð´Ñ€ÐµÑ Ñл. почты: %(toaddr)s" #: bin/clone_member:215 +#, fuzzy msgid "" "Error opening list \"%(listname)s\", skipping.\n" "%(e)s" @@ -10363,6 +10612,7 @@ msgstr "" "\n" #: bin/config_list:118 +#, fuzzy msgid "" "# -*- python -*-\n" "# -*- coding: %(charset)s -*-\n" @@ -10383,23 +10633,28 @@ msgid "legal values are:" msgstr "допуÑтимые значениÑ:" #: bin/config_list:270 +#, fuzzy msgid "attribute \"%(k)s\" ignored" msgstr "атрибут \"%(k)s\" пропущен" #: bin/config_list:273 +#, fuzzy msgid "attribute \"%(k)s\" changed" msgstr "атрибут \"%(k)s\" изменен" #: bin/config_list:279 +#, fuzzy msgid "Non-standard property restored: %(k)s" msgstr "ВоÑÑтановлен неÑтандартный атрибут: %(k)s" #: bin/config_list:288 +#, fuzzy msgid "Invalid value for property: %(k)s" msgstr "ÐедопуÑтимое значение атрибута: %(k)s" # MSS: или лучше "неравильный" или "некорректный"? #: bin/config_list:291 +#, fuzzy msgid "Bad email address for option %(k)s: %(v)s" msgstr "ÐедопуÑтимый Ð°Ð´Ñ€ÐµÑ Ñл. почты Ð´Ð»Ñ Ð¿Ð°Ñ€Ð°Ð¼ÐµÑ‚Ñ€Ð° %(k)s: %(v)s" @@ -10467,19 +10722,23 @@ msgstr "" # MSS: ?? #: bin/discard:94 +#, fuzzy msgid "Ignoring non-held message: %(f)s" msgstr "ПропуÑкаетÑÑ Ð½ÐµÐ·Ð°Ð´ÐµÑ€Ð¶Ð°Ð½Ð½Ð¾Ðµ Ñообщение: %(f)s" #: bin/discard:100 +#, fuzzy msgid "Ignoring held msg w/bad id: %(f)s" msgstr "" "ПропуÑкаетÑÑ Ð·Ð°Ð´ÐµÑ€Ð¶Ð°Ð½Ð½Ð¾Ðµ Ñообщение Ñ Ð½ÐµÐºÐ¾Ñ€Ñ€ÐµÐºÑ‚Ð½Ñ‹Ð¼ идентификатором: %(f)s" #: bin/discard:112 +#, fuzzy msgid "Discarded held msg #%(id)s for list %(listname)s" msgstr "Удалено задержанное Ñообщение #%(id)s из ÑпиÑка раÑÑылки %(listname)s" #: bin/dumpdb:19 +#, fuzzy msgid "" "Dump the contents of any Mailman `database' file.\n" "\n" @@ -10553,6 +10812,7 @@ msgid "No filename given." msgstr "Ðе указано Ð¸Ð¼Ñ Ñ„Ð°Ð¹Ð»Ð°." #: bin/dumpdb:108 +#, fuzzy msgid "Bad arguments: %(pargs)s" msgstr "ÐедопуÑтимые аргументы: %(pargs)s" @@ -10561,14 +10821,17 @@ msgid "Please specify either -p or -m." msgstr "Укажите один из ключей: -p или -m." #: bin/dumpdb:133 +#, fuzzy msgid "[----- start %(typename)s file -----]" msgstr "[----- начало Ð´Ð»Ñ Ñ„Ð°Ð¹Ð»Ð° %(typename)s -----]" #: bin/dumpdb:139 +#, fuzzy msgid "[----- end %(typename)s file -----]" msgstr "[----- завершение Ð´Ð»Ñ Ñ„Ð°Ð¹Ð»Ð° %(typename)s -----]" #: bin/dumpdb:142 +#, fuzzy msgid "<----- start object %(cnt)s ----->" msgstr "<----- начало объекта %(cnt)s ----->" @@ -10786,6 +11049,7 @@ msgid "Setting web_page_url to: %(web_page_url)s" msgstr "Смена Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ Ð¿Ð°Ñ€Ð°Ð¼ÐµÑ‚Ñ€Ð° web_page_url на: %(web_page_url)s" #: bin/fix_url.py:88 +#, fuzzy msgid "Setting host_name to: %(mailhost)s" msgstr "Смена Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ Ð¿Ð°Ñ€Ð°Ð¼ÐµÑ‚Ñ€Ð° host_name на: %(mailhost)s" @@ -10825,6 +11089,7 @@ msgstr "" " Ðапечатать Ñту подÑказку и завершить работу.\n" #: bin/genaliases:84 +#, fuzzy msgid "genaliases can't do anything useful with mm_cfg.MTA = %(mta)s." msgstr "Скрипт genaliases беÑполезен в Ñлучае параметра mm_cfg.MTA = %(mta)s." @@ -10878,6 +11143,7 @@ msgstr "" "пропущен, производитÑÑ Ñ‡Ñ‚ÐµÐ½Ð¸Ðµ из Ñтандартного ввода.\n" #: bin/inject:84 +#, fuzzy msgid "Bad queue directory: %(qdir)s" msgstr "ÐеÑущеÑтвующий каталог очереди: %(qdir)s" @@ -10887,6 +11153,7 @@ msgid "A list name is required" msgstr "Ðазвание ÑпиÑка раÑÑылки ÑвлÑетÑÑ Ð¾Ð±Ñзательным" #: bin/list_admins:20 +#, fuzzy msgid "" "List all the owners of a mailing list.\n" "\n" @@ -10932,10 +11199,12 @@ msgstr "" "раÑпечатать ÑпиÑок владельцев. Ð’Ñ‹ можете указать неÑколько ÑпиÑков.\n" #: bin/list_admins:97 +#, fuzzy msgid "List: %(listname)s, \tOwners: %(owners)s" msgstr "СпиÑок: %(listname)s, \tвладельцы: %(owners)s" #: bin/list_lists:19 +#, fuzzy msgid "" "List all mailing lists.\n" "\n" @@ -10997,6 +11266,7 @@ msgid "matching mailing lists found:" msgstr "найдены Ñледующие ÑпиÑки раÑÑылки:" #: bin/list_members:19 +#, fuzzy msgid "" "List all the members of a mailing list.\n" "\n" @@ -11118,10 +11388,12 @@ msgstr "" "дайджеÑтов. Ðо оба ÑпиÑка никак не разделÑÑŽÑ‚ÑÑ.\n" #: bin/list_members:198 +#, fuzzy msgid "Bad --nomail option: %(why)s" msgstr "ÐедопуÑтимое значение параметра --nomail: %(why)s" #: bin/list_members:209 +#, fuzzy msgid "Bad --digest option: %(kind)s" msgstr "ÐедопуÑтимое значение параметра --digest: %(kind)s" @@ -11135,6 +11407,7 @@ msgid "Could not open file for writing:" msgstr "Ðе удалоÑÑŒ открыть файл Ð´Ð»Ñ Ð·Ð°Ð¿Ð¸Ñи:" #: bin/list_owners:20 +#, fuzzy msgid "" "List the owners of a mailing list, or all mailing lists.\n" "\n" @@ -11191,6 +11464,7 @@ msgstr "" "Ð´Ð»Ñ Ñтой уÑтановки Mailman. ТребуетÑÑ python 2." #: bin/mailmanctl:20 +#, fuzzy msgid "" "Primary start-up and shutdown script for Mailman's qrunner daemon.\n" "\n" @@ -11365,6 +11639,7 @@ msgstr "" "\n" #: bin/mailmanctl:152 +#, fuzzy msgid "PID unreadable in: %(pidfile)s" msgstr "Ðе удалоÑÑŒ прочитать PID в: %(pidfile)s" @@ -11373,6 +11648,7 @@ msgid "Is qrunner even running?" msgstr "Запущен ли демон qrunner?" #: bin/mailmanctl:160 +#, fuzzy msgid "No child with pid: %(pid)s" msgstr "Ðет дочернего процеÑÑа Ñ PID %(pid)s" @@ -11401,6 +11677,7 @@ msgstr "" "Попробуйте перезапуÑтить mailmanctl Ñ Ñ„Ð»Ð°Ð³Ð¾Ð¼ -s.\n" #: bin/mailmanctl:233 +#, fuzzy msgid "" "The master qrunner lock could not be acquired, because it appears as if " "some\n" @@ -11426,10 +11703,12 @@ msgstr "" "Завершение работы." #: bin/mailmanctl:280 cron/mailpasswds:119 +#, fuzzy msgid "Site list is missing: %(sitelistname)s" msgstr "ОтÑутÑтвует Ñлужебный ÑпиÑок раÑÑылки: %(sitelistname)s" #: bin/mailmanctl:305 +#, fuzzy msgid "Run this program as root or as the %(name)s user, or use -u." msgstr "" "ЗапуÑтите Ñту программу от имени ÑÑƒÐ¿ÐµÑ€Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð¸Ð»Ð¸ пользователÑ\n" @@ -11440,6 +11719,7 @@ msgid "No command given." msgstr "Ðе указано ни одной команды." #: bin/mailmanctl:339 +#, fuzzy msgid "Bad command: %(command)s" msgstr "ÐеизвеÑÑ‚Ð½Ð°Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð°: %(command)s" @@ -11464,6 +11744,7 @@ msgid "Starting Mailman's master qrunner." msgstr "ЗапуÑк оÑновного обработчика Mailman." #: bin/mmsitepass:19 +#, fuzzy msgid "" "Set the site password, prompting from the terminal.\n" "\n" @@ -11517,6 +11798,7 @@ msgid "list creator" msgstr "ÑÐ¾Ð·Ð´Ð°Ñ‚ÐµÐ»Ñ ÑпиÑков раÑÑылки" #: bin/mmsitepass:86 +#, fuzzy msgid "New %(pwdesc)s password: " msgstr "Ðовый пароль %(pwdesc)s: " @@ -11599,6 +11881,7 @@ msgid "Return the generated output." msgstr "Возвратить Ñгенерированный вывод." #: bin/newlist:20 +#, fuzzy msgid "" "Create a new, unpopulated mailing list.\n" "\n" @@ -11793,6 +12076,7 @@ msgstr "" "Имена ÑпиÑков раÑÑылки вÑегда приводÑÑ‚ÑÑ Ðº нижнему региÑтру.\n" #: bin/newlist:162 +#, fuzzy msgid "Unknown language: %(lang)s" msgstr "ÐеизвеÑтный Ñзык: %(lang)s" @@ -11805,6 +12089,7 @@ msgid "Enter the email of the person running the list: " msgstr "Укажите Ð°Ð´Ñ€ÐµÑ Ð²Ð»Ð°Ð´ÐµÐ»ÑŒÑ†Ð° Ñтого ÑпиÑка раÑÑылки: " #: bin/newlist:193 +#, fuzzy msgid "Initial %(listname)s password: " msgstr "Пароль Ð´Ð»Ñ %(listname)s: " @@ -11814,19 +12099,21 @@ msgstr "Пароль ÑпиÑка раÑÑылки не может быть пу #: bin/newlist:220 msgid "" -" - owner addresses need to be fully-qualified names like \"owner@example.com" -"\", not just \"owner\"." +" - owner addresses need to be fully-qualified names like \"owner@example." +"com\", not just \"owner\"." msgstr "" " - Ð°Ð´Ñ€ÐµÑ Ð²Ð»Ð°Ð´ÐµÐ»ÑŒÑ†Ð° должен быть полноценным адреÑом e-mail, таким как " "\"owner@example.com\", а не проÑто \"owner\"." # MSS: could be better? #: bin/newlist:244 +#, fuzzy msgid "Hit enter to notify %(listname)s owner..." msgstr "" "Ðажмите Ð´Ð»Ñ Ð¾Ñ‚Ð¿Ñ€Ð°Ð²ÐºÐ¸ Ð¸Ð·Ð²ÐµÑ‰ÐµÐ½Ð¸Ñ Ð¾ Ñоздании ÑпиÑка %(listname)s..." #: bin/qrunner:20 +#, fuzzy msgid "" "Run one or more qrunners, once or repeatedly.\n" "\n" @@ -11950,6 +12237,7 @@ msgstr "" "уÑловиÑÑ…. ЗапуÑк его незавиÑимо имеет ÑмыÑл только Ð´Ð»Ñ Ñ†ÐµÐ»ÐµÐ¹ отладки.\n" #: bin/qrunner:179 +#, fuzzy msgid "%(name)s runs the %(runnername)s qrunner" msgstr "%(name)s запуÑкает обработчик %(runnername)s" @@ -11962,6 +12250,7 @@ msgid "No runner name given." msgstr "Ðе указано Ð¸Ð¼Ñ Ð¾Ð±Ñ€Ð°Ð±Ð¾Ñ‚Ñ‡Ð¸ÐºÐ°." #: bin/rb-archfix:21 +#, fuzzy msgid "" "Reduce disk space usage for Pipermail archives.\n" "\n" @@ -12108,18 +12397,22 @@ msgstr "" " ÐдреÑа, которые нужно отпиÑать.\n" #: bin/remove_members:156 +#, fuzzy msgid "Could not open file for reading: %(filename)s." msgstr "Ðе удалоÑÑŒ открыть файл на чтение: %(filename)s." #: bin/remove_members:163 +#, fuzzy msgid "Error opening list %(listname)s... skipping." msgstr "Ðе удалоÑÑŒ открыть ÑпиÑок раÑÑылки %(listname)s... Пропущен." #: bin/remove_members:173 +#, fuzzy msgid "No such member: %(addr)s" msgstr "Такой подпиÑчик отÑутÑтвует: %(addr)s" #: bin/remove_members:178 +#, fuzzy msgid "User `%(addr)s' removed from list: %(listname)s." msgstr "ПодпиÑка %(addr)s удалена из ÑпиÑка %(listname)s." @@ -12160,10 +12453,12 @@ msgstr "" " Ðапечатать Ñправку о работе ÑценариÑ.\n" #: bin/reset_pw.py:77 +#, fuzzy msgid "Changing passwords for list: %(listname)s" msgstr "ИзменÑÑŽÑ‚ÑÑ Ð¿Ð°Ñ€Ð¾Ð»Ð¸ Ð´Ð»Ñ ÑпиÑка: %(listname)s" #: bin/reset_pw.py:83 +#, fuzzy msgid "New password for member %(member)40s: %(randompw)s" msgstr "Ðовый пароль Ð´Ð»Ñ Ð¿Ð¾Ð´Ð¿Ð¸Ñчика %(member)40s: %(randompw)s" @@ -12209,18 +12504,22 @@ msgstr "" "\n" #: bin/rmlist:73 bin/rmlist:76 +#, fuzzy msgid "Removing %(msg)s" msgstr "УдалÑетÑÑ %(msg)s" #: bin/rmlist:81 +#, fuzzy msgid "%(listname)s %(msg)s not found as %(filename)s" msgstr "%(msg)s ÑпиÑка %(listname)s не найден как %(filename)s" #: bin/rmlist:105 +#, fuzzy msgid "No such list (or list already deleted): %(listname)s" msgstr "СпиÑок раÑÑылки не ÑущеÑтвует (или уже удален): %(listname)s" #: bin/rmlist:108 +#, fuzzy msgid "No such list: %(listname)s. Removing its residual archives." msgstr "СпиÑок раÑÑылки %(listname)s не ÑущеÑтвует. УдалÑÑŽÑ‚ÑÑ ÐµÐ³Ð¾ архивы." @@ -12280,6 +12579,7 @@ msgstr "" "Пример: show_qfiles qfiles/shunt/*.pck\n" #: bin/sync_members:19 +#, fuzzy msgid "" "Synchronize a mailing list's membership with a flat file.\n" "\n" @@ -12405,6 +12705,7 @@ msgstr "" " ÑинхронизациÑ.\n" #: bin/sync_members:115 +#, fuzzy msgid "Bad choice: %(yesno)s" msgstr "Ðеверный ответ: %(yesno)s" @@ -12421,6 +12722,7 @@ msgid "No argument to -f given" msgstr "Параметр -f требует аргумента" #: bin/sync_members:172 +#, fuzzy msgid "Illegal option: %(opt)s" msgstr "Ðеверный параметр: %(opt)s" @@ -12433,6 +12735,7 @@ msgid "Must have a listname and a filename" msgstr "Ð˜Ð¼Ñ ÑпиÑка раÑÑылки и Ð¸Ð¼Ñ Ñ„Ð°Ð¹Ð»Ð° должны быть указаны" #: bin/sync_members:191 +#, fuzzy msgid "Cannot read address file: %(filename)s: %(msg)s" msgstr "Ðе удалоÑÑŒ прочитать файл адреÑов: %(filename)s: %(msg)s" @@ -12450,14 +12753,17 @@ msgstr "Сначала вы должны поправить вышеуказан # MSS: check the code: singular or plural? #: bin/sync_members:264 +#, fuzzy msgid "Added : %(s)s" msgstr "Добавлен : %(s)s" #: bin/sync_members:289 +#, fuzzy msgid "Removed: %(s)s" msgstr "Удален : %(s)s" #: bin/transcheck:19 +#, fuzzy msgid "" "\n" "Check a given Mailman translation, making sure that variables and\n" @@ -12536,6 +12842,7 @@ msgid "scan the po file comparing msgids with msgstrs" msgstr "обработать po-файл, ÑÑ€Ð°Ð²Ð½Ð¸Ð²Ð°Ñ Ñодержимое полей msgid и msgstr" #: bin/unshunt:20 +#, fuzzy msgid "" "Move a message from the shunt queue to the original queue.\n" "\n" @@ -12567,6 +12874,7 @@ msgstr "" "вÑех Ñообщений из Ñтой очереди.\n" #: bin/unshunt:85 +#, fuzzy msgid "" "Cannot unshunt message %(filebase)s, skipping:\n" "%(e)s" @@ -12576,6 +12884,7 @@ msgstr "" "%(e)s" #: bin/update:20 +#, fuzzy msgid "" "Perform all necessary upgrades.\n" "\n" @@ -12614,16 +12923,19 @@ msgstr "" "1.0b4.\n" #: bin/update:107 +#, fuzzy msgid "Fixing language templates: %(listname)s" msgstr "ПоправлÑÑŽÑ‚ÑÑ ÑˆÐ°Ð±Ð»Ð¾Ð½Ñ‹ перевода: %(listname)s" #: bin/update:196 bin/update:711 +#, fuzzy msgid "WARNING: could not acquire lock for list: %(listname)s" msgstr "" "Ð’ÐИМÐÐИЕ: не удалоÑÑŒ получить единоличный доÑтуп к ÑпиÑку раÑÑылки: " "%(listname)s" #: bin/update:215 +#, fuzzy msgid "Resetting %(n)s BYBOUNCEs disabled addrs with no bounce info" msgstr "" "Разблокировано %(n)s адреÑов, Ñ‡ÑŒÑ Ð¿Ð¾Ð´Ð¿Ð¸Ñка была приоÑтановлена из-за " @@ -12645,6 +12957,7 @@ msgstr "" "продолжена." #: bin/update:255 +#, fuzzy msgid "" "\n" "%(listname)s has both public and private mbox archives. Since this list\n" @@ -12693,6 +13006,7 @@ msgid "- updating old private mbox file" msgstr "- обновлÑетÑÑ Ñтарый закрытый архив в формате mbox" #: bin/update:295 +#, fuzzy msgid "" " unknown file in the way, moving\n" " %(o_pri_mbox_file)s\n" @@ -12709,6 +13023,7 @@ msgid "- updating old public mbox file" msgstr "- обновлÑетÑÑ Ñтарый открытый архив в формате mbox" #: bin/update:317 +#, fuzzy msgid "" " unknown file in the way, moving\n" " %(o_pub_mbox_file)s\n" @@ -12738,18 +13053,22 @@ msgid "- %(o_tmpl)s doesn't exist, leaving untouched" msgstr "-- %(o_tmpl)s не ÑущеÑтвует; ничего не Ñделано" #: bin/update:396 +#, fuzzy msgid "removing directory %(src)s and everything underneath" msgstr "удалÑетÑÑ ÐºÐ°Ñ‚Ð°Ð»Ð¾Ð³ %(src)s и его подкаталоги" #: bin/update:399 +#, fuzzy msgid "removing %(src)s" msgstr "удалÑетÑÑ %(src)s" #: bin/update:403 +#, fuzzy msgid "Warning: couldn't remove %(src)s -- %(rest)s" msgstr "Предупреждение: не удалоÑÑŒ удалить %(src)s -- %(rest)s" #: bin/update:408 +#, fuzzy msgid "couldn't remove old file %(pyc)s -- %(rest)s" msgstr "не удалоÑÑŒ удалить Ñтарый %(pyc)s -- %(rest)s" @@ -12758,14 +13077,17 @@ msgid "updating old qfiles" msgstr "обновлÑÑŽÑ‚ÑÑ Ñтарые файлы очереди" #: bin/update:455 +#, fuzzy msgid "Warning! Not a directory: %(dirpath)s" msgstr "ÐеÑущеÑтвующий каталог очереди: %(dirpath)s" #: bin/update:530 +#, fuzzy msgid "message is unparsable: %(filebase)s" msgstr "Ñообщение нечитаемо: %(filebase)s" #: bin/update:544 +#, fuzzy msgid "Warning! Deleting empty .pck file: %(pckfile)s" msgstr "Предупреждение! УдалÑетÑÑ Ð¿ÑƒÑтой .pck файл: %(pckfile)s" @@ -12778,10 +13100,12 @@ msgid "Updating Mailman 2.1.4 pending.pck database" msgstr "ОбновлÑетÑÑ Ð±Ð°Ð·Ð° данных pending.pck верÑии 2.1.4" #: bin/update:598 +#, fuzzy msgid "Ignoring bad pended data: %(key)s: %(val)s" msgstr "ИгнорируютÑÑ Ð¿Ð»Ð¾Ñ…Ð¸Ðµ данные: %(key)s: %(val)s" #: bin/update:614 +#, fuzzy msgid "WARNING: Ignoring duplicate pending ID: %(id)s." msgstr "ПРЕДУПРЕЖДЕÐИЕ: игнорируютÑÑ Ð¿Ð¾Ð²Ñ‚Ð¾Ñ€ÑющиеÑÑ ID: %(id)s." @@ -12806,6 +13130,7 @@ msgid "done" msgstr "готово" #: bin/update:691 +#, fuzzy msgid "Updating mailing list: %(listname)s" msgstr "Обновление ÑпиÑка раÑÑылки: %(listname)s" @@ -12868,6 +13193,7 @@ msgid "No updates are necessary." msgstr "Обновление не требуетÑÑ." #: bin/update:796 +#, fuzzy msgid "" "Downgrade detected, from version %(hexlversion)s to version %(hextversion)s\n" "This is probably not safe.\n" @@ -12879,10 +13205,12 @@ msgstr "" "Работа завершена." #: bin/update:801 +#, fuzzy msgid "Upgrading from version %(hexlversion)s to %(hextversion)s" msgstr "Идет обновление Ñ Ð²ÐµÑ€Ñии %(hexlversion)s до %(hextversion)s" #: bin/update:810 +#, fuzzy msgid "" "\n" "ERROR:\n" @@ -13155,6 +13483,7 @@ msgstr "" " по возникновению иÑключительной Ñитуации (exception)." #: bin/withlist:175 +#, fuzzy msgid "Unlocking (but not saving) list: %(listname)s" msgstr "" "РазблокируетÑÑ (но не обновлÑетÑÑ) Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ ÑпиÑке раÑÑылки: %(listname)s" @@ -13164,6 +13493,7 @@ msgid "Finalizing" msgstr "Завершение работы" #: bin/withlist:188 +#, fuzzy msgid "Loading list %(listname)s" msgstr "ЗагружаетÑÑ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ ÑпиÑке раÑÑылки %(listname)s" @@ -13176,6 +13506,7 @@ msgid "(unlocked)" msgstr "(разблокирован)" #: bin/withlist:197 +#, fuzzy msgid "Unknown list: %(listname)s" msgstr "ÐеизвеÑтный ÑпиÑок раÑÑылки: %(listname)s" @@ -13188,20 +13519,24 @@ msgid "--all requires --run" msgstr "параметр --all требует Ð½Ð°Ð»Ð¸Ñ‡Ð¸Ñ Ð¿Ð°Ñ€Ð°Ð¼ÐµÑ‚Ñ€Ð° --run" #: bin/withlist:266 +#, fuzzy msgid "Importing %(module)s..." msgstr "ИмпортируетÑÑ %(module)s..." #: bin/withlist:270 +#, fuzzy msgid "Running %(module)s.%(callable)s()..." msgstr "ВыполнÑетÑÑ %(module)s.%(callable)s()..." #: bin/withlist:291 +#, fuzzy msgid "The variable `m' is the %(listname)s MailList instance" msgstr "" "ÐŸÐµÑ€ÐµÐ¼ÐµÐ½Ð½Ð°Ñ `m' -- ÑкземплÑÑ€ объекта, предÑтавлÑющего ÑпиÑок раÑÑылки " "%(listname)s" #: cron/bumpdigests:19 +#, fuzzy msgid "" "Increment the digest volume number and reset the digest number to one.\n" "\n" @@ -13231,6 +13566,7 @@ msgstr "" "не указан, Ð¾Ð¿ÐµÑ€Ð°Ñ†Ð¸Ñ Ð±ÑƒÐ´ÐµÑ‚ произведена над вÑеми ÑпиÑками раÑÑылки.\n" #: cron/checkdbs:20 +#, fuzzy msgid "" "Check for pending admin requests and mail the list owners if necessary.\n" "\n" @@ -13259,10 +13595,12 @@ msgstr "" "Уведомление: автоматичеÑки удалено уÑтаревших запроÑов - %(discarded)d.\n" #: cron/checkdbs:121 +#, fuzzy msgid "%(count)d %(realname)s moderator request(s) waiting" msgstr "Ðеобработанных модератором %(realname)s запроÑов: %(count)d" #: cron/checkdbs:124 +#, fuzzy msgid "%(realname)s moderator request check result" msgstr "Результаты проверки необработанных запроÑов %(realname)s" @@ -13283,6 +13621,7 @@ msgstr "" "СообщениÑ, ожидающие обработки:" #: cron/checkdbs:169 +#, fuzzy msgid "" "From: %(sender)s on %(date)s\n" "Subject: %(subject)s\n" @@ -13293,6 +13632,7 @@ msgstr "" " Причина: %(reason)s" #: cron/cull_bad_shunt:20 +#, fuzzy msgid "" "Cull bad and shunt queues, recommended once per day.\n" "\n" @@ -13333,6 +13673,7 @@ msgstr "" " ВывеÑти Ñто Ñообщение и завершить работу.\n" #: cron/disabled:20 +#, fuzzy msgid "" "Process disabled members, recommended once per day.\n" "\n" @@ -13459,6 +13800,7 @@ msgstr "" "\n" #: cron/mailpasswds:19 +#, fuzzy msgid "" "Send password reminders for all lists to all users.\n" "\n" @@ -13513,10 +13855,12 @@ msgstr "Пароль // URL" # fattie: "Ðапоминание паролей подпиÑчикам ÑпиÑков раÑÑылок на Ñервере %(host)s" #: cron/mailpasswds:222 +#, fuzzy msgid "%(host)s mailing list memberships reminder" msgstr "СпиÑок подпиÑок на Ñервере %(host)s" #: cron/nightly_gzip:19 +#, fuzzy msgid "" "Re-generate the Pipermail gzip'd archive flat files.\n" "\n" @@ -13561,6 +13905,7 @@ msgstr "" " указанные ÑпиÑки раÑÑылки.\n" #: cron/senddigests:20 +#, fuzzy msgid "" "Dispatch digests for lists w/pending messages and digest_send_periodic set.\n" "\n" diff --git a/messages/sk/LC_MESSAGES/mailman.po b/messages/sk/LC_MESSAGES/mailman.po index 7629a9ac..648b67bc 100755 --- a/messages/sk/LC_MESSAGES/mailman.po +++ b/messages/sk/LC_MESSAGES/mailman.po @@ -15,8 +15,8 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" -"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " +"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" #: Mailman/Archiver/HyperArch.py:124 msgid "size not available" @@ -69,10 +69,12 @@ msgid "

                Currently, there are no archives.

                " msgstr "

                Momentálne neexistujú žiadne archívy.

                " #: Mailman/Archiver/HyperArch.py:821 +#, fuzzy msgid "Gzip'd Text%(sz)s" msgstr "Gzip-komprimovaný text %(sz)s" #: Mailman/Archiver/HyperArch.py:826 +#, fuzzy msgid "Text%(sz)s" msgstr "Text %(sz)s" @@ -145,18 +147,22 @@ msgid "Third" msgstr "Tretí" #: Mailman/Archiver/HyperArch.py:938 +#, fuzzy msgid "%(ord)s quarter %(year)i" msgstr "%(ord)s Å¡tvrÅ¥rok %(year)i" #: Mailman/Archiver/HyperArch.py:945 +#, fuzzy msgid "%(month)s %(year)i" msgstr "%(month)s %(year)i" #: Mailman/Archiver/HyperArch.py:950 +#, fuzzy msgid "The Week Of Monday %(day)i %(month)s %(year)i" msgstr "Týždeň zaÄínajúci pondelkom %(day)i %(month)s %(year)i" #: Mailman/Archiver/HyperArch.py:954 +#, fuzzy msgid "%(day)i %(month)s %(year)i" msgstr "%(day)i %(month)s %(year)i" @@ -165,10 +171,12 @@ msgid "Computing threaded index\n" msgstr "Vytváram index pre príspevky združené do vlákien\n" #: Mailman/Archiver/HyperArch.py:1319 +#, fuzzy msgid "Updating HTML for article %(seq)s" msgstr "Aktualizujem HTML stránky pre príspevok %(seq)s" #: Mailman/Archiver/HyperArch.py:1326 +#, fuzzy msgid "article file %(filename)s is missing!" msgstr "súbor %(filename)s obsahujúci príspevok nebol nájdený!" @@ -185,6 +193,7 @@ msgid "Pickling archive state into " msgstr "Ukladám stav archívu do " #: Mailman/Archiver/pipermail.py:453 +#, fuzzy msgid "Updating index files for archive [%(archive)s]" msgstr "Aktualizujem indexový súbor pre archív konferencie [%(archive)s]" @@ -193,6 +202,7 @@ msgid " Thread" msgstr " Vlákno" #: Mailman/Archiver/pipermail.py:597 +#, fuzzy msgid "#%(counter)05d %(msgid)s" msgstr "#%(counter)05d %(msgid)s" @@ -230,6 +240,7 @@ msgid "disabled address" msgstr "vypnuté" #: Mailman/Bouncer.py:304 +#, fuzzy msgid " The last bounce received from you was dated %(date)s" msgstr "" " Posledny príspevok, ktorý sa vrátil ako nedoruÄiteľný na vaÅ¡u adresu má " @@ -259,6 +270,7 @@ msgstr "Správca" #: Mailman/Cgi/options.py:101 Mailman/Cgi/private.py:108 #: Mailman/Cgi/rmlist.py:75 Mailman/Cgi/roster.py:59 #: Mailman/Cgi/subscribe.py:67 +#, fuzzy msgid "No such list %(safelistname)s" msgstr "Neznáma konferencia %(safelistname)s." @@ -332,6 +344,7 @@ msgstr "" " títo úÄastníci nebudú dostávaÅ¥ poÅ¡tu.%(rm)r" #: Mailman/Cgi/admin.py:268 +#, fuzzy msgid "%(hostname)s mailing lists - Admin Links" msgstr "Konferencie na serveri %(hostname)s - Správcovské linky" @@ -344,6 +357,7 @@ msgid "Mailman" msgstr "Mailman" #: Mailman/Cgi/admin.py:310 +#, fuzzy msgid "" "

                There currently are no publicly-advertised %(mailmanlink)s\n" " mailing lists on %(hostname)s." @@ -352,6 +366,7 @@ msgstr "" " verejne prístupné konferencie spravované %(mailmanlink)s." #: Mailman/Cgi/admin.py:316 +#, fuzzy msgid "" "

                Below is the collection of publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s. Click on a list\n" @@ -366,6 +381,7 @@ msgid "right " msgstr "vpravo" #: Mailman/Cgi/admin.py:325 +#, fuzzy msgid "" "To visit the administrators configuration page for an\n" " unadvertised list, open a URL similar to this one, but with a '/' " @@ -409,6 +425,7 @@ msgid "No valid variable name found." msgstr "Nenašiel som žiaden platný názov premennej" #: Mailman/Cgi/admin.py:395 +#, fuzzy msgid "" "%(realname)s Mailing list Configuration Help\n" "
                %(varname)s Option" @@ -417,6 +434,7 @@ msgstr "" "
                Pomocník k nastaveniu %(varname)s." #: Mailman/Cgi/admin.py:402 +#, fuzzy msgid "Mailman %(varname)s List Option Help" msgstr "Pomocník k premennej %(varname)s" @@ -434,14 +452,17 @@ msgstr "" " všetky obnoviť skôr, ako na nich budete robiť zmeny. Alternatívne môžete " #: Mailman/Cgi/admin.py:431 +#, fuzzy msgid "return to the %(categoryname)s options page." msgstr "prejsť späť na stránku volieb %(categoryname)s." #: Mailman/Cgi/admin.py:446 +#, fuzzy msgid "%(realname)s Administration (%(label)s)" msgstr "Administrácia konferencie %(realname)s (%(label)s)" #: Mailman/Cgi/admin.py:447 +#, fuzzy msgid "%(realname)s mailing list administration
                %(label)s Section" msgstr "Konfigurácia konferencie %(realname)s
                %(label)s" @@ -524,6 +545,7 @@ msgid "Value" msgstr "Hodnota" #: Mailman/Cgi/admin.py:668 +#, fuzzy msgid "" "Badly formed options entry:\n" " %(record)s" @@ -624,10 +646,12 @@ msgid "Move rule down" msgstr "Posunúť pravidlo nižšie" #: Mailman/Cgi/admin.py:870 +#, fuzzy msgid "
                (Edit %(varname)s)" msgstr "
                (Upraviť %(varname)s)" #: Mailman/Cgi/admin.py:872 +#, fuzzy msgid "
                (Details for %(varname)s)" msgstr "
                (Podrobnosti o %(varname)s)" @@ -668,6 +692,7 @@ msgid "(help)" msgstr "(pomocník)" #: Mailman/Cgi/admin.py:930 +#, fuzzy msgid "Find member %(link)s:" msgstr "NájsÅ¥ úÄastníka %(link)s:" @@ -680,10 +705,12 @@ msgid "Bad regular expression: " msgstr "Chybný regulárny výraz: " #: Mailman/Cgi/admin.py:1013 +#, fuzzy msgid "%(allcnt)s members total, %(membercnt)s shown" msgstr "Konferencia má %(allcnt)s úÄastníkov, %(membercnt)s je zobrazených" #: Mailman/Cgi/admin.py:1016 +#, fuzzy msgid "%(allcnt)s members total" msgstr "Spolu úÄastníkov: %(allcnt)s" @@ -871,6 +898,7 @@ msgstr "" " na segment, ktorý chcete zobraziÅ¥:" #: Mailman/Cgi/admin.py:1221 +#, fuzzy msgid "from %(start)s to %(end)s" msgstr "od %(start)s do %(end)s" @@ -1008,6 +1036,7 @@ msgid "Change list ownership passwords" msgstr "Zmena hesla vlastníka konferencie" #: Mailman/Cgi/admin.py:1364 +#, fuzzy msgid "" "The list administrators are the people who have ultimate control " "over\n" @@ -1115,6 +1144,7 @@ msgstr "Neplatná adresa (obsahuje nepovolené znaky)" #: Mailman/Cgi/admin.py:1529 Mailman/Cgi/admin.py:1703 bin/add_members:175 #: bin/clone_member:136 bin/sync_members:268 +#, fuzzy msgid "Banned address (matched %(pattern)s)" msgstr "Zakázaná adresa (vyhovuje vzoru %(pattern)s)" @@ -1217,6 +1247,7 @@ msgid "Not subscribed" msgstr "Nie je prihlásený" #: Mailman/Cgi/admin.py:1773 +#, fuzzy msgid "Ignoring changes to deleted member: %(user)s" msgstr "Ignorujem zmeny prevedené u odhláseného úÄastníka: %(user)s" @@ -1229,10 +1260,12 @@ msgid "Error Unsubscribing:" msgstr "Chyba pri odhlasovaní" #: Mailman/Cgi/admindb.py:235 Mailman/Cgi/admindb.py:248 +#, fuzzy msgid "%(realname)s Administrative Database" msgstr "Konferencia %(realname)s -- administrácia" #: Mailman/Cgi/admindb.py:238 +#, fuzzy msgid "%(realname)s Administrative Database Results" msgstr "Konferencia %(realname)s -- výsledky zmeny databázy žiadostí" @@ -1261,6 +1294,7 @@ msgid "Discard all messages marked Defer" msgstr "ZahodiÅ¥ vÅ¡etky správy oznaÄené ako OdložiÅ¥" #: Mailman/Cgi/admindb.py:298 +#, fuzzy msgid "all of %(esender)s's held messages." msgstr "vÅ¡etky podržané správy od úÄastníka %(esender)s." @@ -1281,6 +1315,7 @@ msgid "list of available mailing lists." msgstr "zoznam dostupných konferencií" #: Mailman/Cgi/admindb.py:362 +#, fuzzy msgid "You must specify a list name. Here is the %(link)s" msgstr "Musíte zadaÅ¥ názov konferencie, tu je odkaz %(link)s" @@ -1362,6 +1397,7 @@ msgid "The sender is now a member of this list" msgstr "Odosielateľ je od teraz úÄastníkom konferencie." #: Mailman/Cgi/admindb.py:568 +#, fuzzy msgid "Add %(esender)s to one of these sender filters:" msgstr "PridaÅ¥ %(esender)s do jednoho z týchto filtrov odesielateľov:" @@ -1382,6 +1418,7 @@ msgid "Rejects" msgstr "OdmietnuÅ¥" #: Mailman/Cgi/admindb.py:584 +#, fuzzy msgid "" "Ban %(esender)s from ever subscribing to this\n" " mailing list" @@ -1398,6 +1435,7 @@ msgstr "" " alebo môžete " #: Mailman/Cgi/admindb.py:591 +#, fuzzy msgid "view all messages from %(esender)s" msgstr "zobraziÅ¥ vÅ¡etky správy od %(esender)s" @@ -1476,6 +1514,7 @@ msgid " is already a member" msgstr "už je úÄastníkom konferencie" #: Mailman/Cgi/admindb.py:973 +#, fuzzy msgid "%(addr)s is banned (matched: %(patt)s)" msgstr "%(addr)s je zakázaná (vyhovuje vzoru: %(patt)s)" @@ -1528,6 +1567,7 @@ msgstr "" " odhlásená. ŽiadosÅ¥ bola zruÅ¡ená." #: Mailman/Cgi/confirm.py:178 +#, fuzzy msgid "System error, bad content: %(content)s" msgstr "Systémová chyba, chybný obsah: %(content)s" @@ -1565,6 +1605,7 @@ msgid "Confirm subscription request" msgstr "PotvrÄiÅ¥ žiadosÅ¥ o prihlásenie" #: Mailman/Cgi/confirm.py:259 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " subscription request to the mailing list %(listname)s. Your\n" @@ -1595,6 +1636,7 @@ msgstr "" " ZruÅ¡iÅ¥ žiadosÅ¥ o prihlásenie." #: Mailman/Cgi/confirm.py:275 +#, fuzzy msgid "" "Your confirmation is required in order to continue with\n" " the subscription request to the mailing list %(listname)s.\n" @@ -1646,6 +1688,7 @@ msgid "Preferred language:" msgstr "Prednastavený jazyk:" #: Mailman/Cgi/confirm.py:317 +#, fuzzy msgid "Subscribe to list %(listname)s" msgstr "Prihlásenie do konferencie %(listname)s" @@ -1662,6 +1705,7 @@ msgid "Awaiting moderator approval" msgstr "Príspevok bol pozastavený do súhlasu moderátora" #: Mailman/Cgi/confirm.py:386 +#, fuzzy msgid "" " You have successfully confirmed your subscription request to " "the\n" @@ -1692,6 +1736,7 @@ msgid "You are already a member of this mailing list!" msgstr "V tejto konferenci už ste úÄastníkom!" #: Mailman/Cgi/confirm.py:400 +#, fuzzy msgid "" "You are currently banned from subscribing to\n" " this list. If you think this restriction is erroneous, please\n" @@ -1716,6 +1761,7 @@ msgid "Subscription request confirmed" msgstr "ŽiadosÅ¥ o prihlásenie bola potvrdená" #: Mailman/Cgi/confirm.py:418 +#, fuzzy msgid "" " You have successfully confirmed your subscription request for\n" " \"%(addr)s\" to the %(listname)s mailing list. A separate\n" @@ -1744,6 +1790,7 @@ msgid "Unsubscription request confirmed" msgstr "ŽiadosÅ¥ o odhlásenie bola potvrdená" #: Mailman/Cgi/confirm.py:469 +#, fuzzy msgid "" " You have successfully unsubscribed from the %(listname)s " "mailing\n" @@ -1764,6 +1811,7 @@ msgid "Not available" msgstr "Nie je dostupné" #: Mailman/Cgi/confirm.py:498 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " unsubscription request from the mailing list %(listname)s. " @@ -1805,6 +1853,7 @@ msgid "You have canceled your change of address request." msgstr "ZruÅ¡ili ste žiadosÅ¥ o zmenu adresy." #: Mailman/Cgi/confirm.py:555 +#, fuzzy msgid "" "%(newaddr)s is banned from subscribing to the\n" " %(realname)s list. If you think this restriction is erroneous,\n" @@ -1832,6 +1881,7 @@ msgid "Change of address request confirmed" msgstr "Zmena adresy bola potvrdená" #: Mailman/Cgi/confirm.py:571 +#, fuzzy msgid "" " You have successfully changed your address on the %(listname)s\n" " mailing list from %(oldaddr)s to %(newaddr)s. " @@ -1853,6 +1903,7 @@ msgid "globally" msgstr "na vÅ¡etkých konferenciách" #: Mailman/Cgi/confirm.py:605 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " change of address request for the mailing list %(listname)s. " @@ -1911,6 +1962,7 @@ msgid "Sender discarded message via web." msgstr "Odosielateľ zruÅ¡il príspevok cez webové rozhranie." #: Mailman/Cgi/confirm.py:674 +#, fuzzy msgid "" "The held message with the Subject:\n" " header %(subject)s could not be found. The most " @@ -1930,6 +1982,7 @@ msgid "Posted message canceled" msgstr "Príspevok bol zruÅ¡ený" #: Mailman/Cgi/confirm.py:685 +#, fuzzy msgid "" " You have successfully canceled the posting of your message with\n" " the Subject: header %(subject)s to the mailing list\n" @@ -1950,6 +2003,7 @@ msgid "" msgstr "Príspevok, ktorý chtete zobraziÅ¥ už bol vybavený moderátorom." #: Mailman/Cgi/confirm.py:735 +#, fuzzy msgid "" "Your confirmation is required in order to cancel the\n" " posting of your message to the mailing list %(listname)s:\n" @@ -1996,6 +2050,7 @@ msgid "Membership re-enabled." msgstr "ÚÄasÅ¥ obnovená." #: Mailman/Cgi/confirm.py:798 +#, fuzzy msgid "" " You have successfully re-enabled your membership in the\n" " %(listname)s mailing list. You can now not available" msgstr "nie je k dispozícii" #: Mailman/Cgi/confirm.py:846 +#, fuzzy msgid "" "Your membership in the %(realname)s mailing list is\n" " currently disabled due to excessive bounces. Your confirmation is\n" @@ -2091,10 +2148,12 @@ msgid "administrative list overview" msgstr "stránku s administráciou konferencie" #: Mailman/Cgi/create.py:115 +#, fuzzy msgid "List name must not include \"@\": %(safelistname)s" msgstr "Názov konferencie nesmie obsahovaÅ¥ „@“: %(safelistname)s" #: Mailman/Cgi/create.py:122 +#, fuzzy msgid "List already exists: %(safelistname)s" msgstr "Konferencia už existuje : %(safelistname)s" @@ -2129,18 +2188,22 @@ msgid "You are not authorized to create new mailing lists" msgstr "Nemáte právo založit novú konferenciu." #: Mailman/Cgi/create.py:182 +#, fuzzy msgid "Unknown virtual host: %(safehostname)s" msgstr "Neznámy virtuálny host: %(safehostname)s" #: Mailman/Cgi/create.py:218 bin/newlist:219 +#, fuzzy msgid "Bad owner email address: %(s)s" msgstr "Neplatná adresa vlastníka: %(s)s" #: Mailman/Cgi/create.py:223 bin/newlist:182 bin/newlist:223 +#, fuzzy msgid "List already exists: %(listname)s" msgstr "Konferencia už existuje : %(listname)s" #: Mailman/Cgi/create.py:231 bin/newlist:217 +#, fuzzy msgid "Illegal list name: %(s)s" msgstr "Neprípustný názov konferencie %(s)s" @@ -2153,6 +2216,7 @@ msgstr "" " Prosím kontaktujte správcu servera." #: Mailman/Cgi/create.py:273 bin/newlist:265 +#, fuzzy msgid "Your new mailing list: %(listname)s" msgstr "VaÅ¡a nová konferencia: %(listname)s" @@ -2161,6 +2225,7 @@ msgid "Mailing list creation results" msgstr "Výsledky vytvorenia konferencie" #: Mailman/Cgi/create.py:288 +#, fuzzy msgid "" "You have successfully created the mailing list\n" " %(listname)s and notification has been sent to the list owner\n" @@ -2183,6 +2248,7 @@ msgid "Create another list" msgstr "VytvoriÅ¥ ÄalÅ¡iu konferenciu" #: Mailman/Cgi/create.py:312 +#, fuzzy msgid "Create a %(hostname)s Mailing List" msgstr "VytvoriÅ¥ konferenciu %(hostname)s" @@ -2275,6 +2341,7 @@ msgstr "" "skôr ako budú rozposlané." #: Mailman/Cgi/create.py:432 +#, fuzzy msgid "" "Initial list of supported languages.

                Note that if you do not\n" " select at least one initial language, the list will use the server\n" @@ -2381,6 +2448,7 @@ msgid "List name is required." msgstr "Názov konferencie je vyžadovaný." #: Mailman/Cgi/edithtml.py:154 +#, fuzzy msgid "%(realname)s -- Edit html for %(template_info)s" msgstr "Konferencia %(realname)s -- Upraviť HTML šablóny pre %(template_info)s" @@ -2389,10 +2457,12 @@ msgid "Edit HTML : Error" msgstr "Chyba pri úprave HTML šablón" #: Mailman/Cgi/edithtml.py:161 +#, fuzzy msgid "%(safetemplatename)s: Invalid template" msgstr "%(safetemplatename)s: chybná šablóna" #: Mailman/Cgi/edithtml.py:166 Mailman/Cgi/edithtml.py:167 +#, fuzzy msgid "%(realname)s -- HTML Page Editing" msgstr "Konferencia %(realname)s -- Úprava HTML stránok" @@ -2455,10 +2525,12 @@ msgid "HTML successfully updated." msgstr "HTML šablóny boly úspešne aktualizované." #: Mailman/Cgi/listinfo.py:89 +#, fuzzy msgid "%(hostname)s Mailing Lists" msgstr "Konferencie na serveri %(hostname)s" #: Mailman/Cgi/listinfo.py:127 +#, fuzzy msgid "" "

                There currently are no publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s." @@ -2467,6 +2539,7 @@ msgstr "" " %(mailmanlink)s na serveri %(hostname)s." #: Mailman/Cgi/listinfo.py:131 +#, fuzzy msgid "" "

                Below is a listing of all the public mailing lists on\n" " %(hostname)s. Click on a list name to get more information " @@ -2486,6 +2559,7 @@ msgid "right" msgstr "Vpravo" #: Mailman/Cgi/listinfo.py:140 +#, fuzzy msgid "" " To visit the general information page for an unadvertised list,\n" " open a URL similar to this one, but with a '/' and the %(adj)s\n" @@ -2549,6 +2623,7 @@ msgstr "Neplatná e-mailová adresa" #: Mailman/Cgi/options.py:183 Mailman/Cgi/options.py:240 #: Mailman/Cgi/options.py:264 +#, fuzzy msgid "No such member: %(safeuser)s." msgstr "Neznámy úÄastník: %(safeuser)s." @@ -2601,6 +2676,7 @@ msgid "Note: " msgstr "Poznámka: " #: Mailman/Cgi/options.py:378 +#, fuzzy msgid "List subscriptions for %(safeuser)s on %(hostname)s" msgstr "" "Zoznam konferencií, v ktorých je prihlásený používateľ %(safeuser)s na " @@ -2631,6 +2707,7 @@ msgid "You are already using that email address" msgstr "Túto e-mailoú adresu už používate" #: Mailman/Cgi/options.py:459 +#, fuzzy msgid "" "The new address you requested %(newaddr)s is already a member of the\n" "%(listname)s mailing list, however you have also requested a global change " @@ -2644,6 +2721,7 @@ msgstr "" "úÄastník %(safeuser)s prihlásený." #: Mailman/Cgi/options.py:468 +#, fuzzy msgid "The new address is already a member: %(newaddr)s" msgstr "Nová adresa je v konferenci už prihlásená: %(newaddr)s" @@ -2652,6 +2730,7 @@ msgid "Addresses may not be blank" msgstr "Adresy nemôžu zostaÅ¥ prázdne" #: Mailman/Cgi/options.py:488 +#, fuzzy msgid "A confirmation message has been sent to %(newaddr)s. " msgstr "Správa s potvrdením bola zaslaná na adresu %(newaddr)s" @@ -2664,10 +2743,12 @@ msgid "Illegal email address provided" msgstr "Neplatná emailová adresa" #: Mailman/Cgi/options.py:501 +#, fuzzy msgid "%(newaddr)s is already a member of the list." msgstr "%(newaddr)s je už prihlásený v konferencii." #: Mailman/Cgi/options.py:504 +#, fuzzy msgid "" "%(newaddr)s is banned from this list. If you\n" " think this restriction is erroneous, please contact\n" @@ -2745,6 +2826,7 @@ msgstr "" " O výsledku budete informovaný." #: Mailman/Cgi/options.py:623 +#, fuzzy msgid "" "You have been successfully unsubscribed from the\n" " mailing list %(fqdn_listname)s. If you were receiving digest\n" @@ -2839,6 +2921,7 @@ msgid "day" msgstr "deň" #: Mailman/Cgi/options.py:900 +#, fuzzy msgid "%(days)d %(units)s" msgstr "%(days)d %(units)s" @@ -2851,6 +2934,7 @@ msgid "No topics defined" msgstr "Žiadne témy nie sú definované" #: Mailman/Cgi/options.py:940 +#, fuzzy msgid "" "\n" "You are subscribed to this list with the case-preserved address\n" @@ -2863,6 +2947,7 @@ msgstr "" "%(cpuser)s." #: Mailman/Cgi/options.py:956 +#, fuzzy msgid "%(realname)s list: member options login page" msgstr "Konferencia %(realname)s: prihlásenie úÄastníka pre úpravu nastavení" @@ -2871,10 +2956,12 @@ msgid "email address and " msgstr "e-mailová adresa a " #: Mailman/Cgi/options.py:960 +#, fuzzy msgid "%(realname)s list: member options for user %(safeuser)s" msgstr "Konferencia %(realname)s: používateľské nastavenia pre %(safeuser)s" #: Mailman/Cgi/options.py:986 +#, fuzzy msgid "" "In order to change your membership option, you must\n" " first log in by giving your %(extra)smembership password in the section\n" @@ -2947,6 +3034,7 @@ msgid "" msgstr "" #: Mailman/Cgi/options.py:1140 +#, fuzzy msgid "Requested topic is not valid: %(topicname)s" msgstr "Požadovaná téma neexistuje: %(topicname)s" @@ -2975,6 +3063,7 @@ msgid "Private archive - \"./\" and \"../\" not allowed in URL." msgstr "V URL súkromného archívu nie sú povolené znaky „./“ a „../“" #: Mailman/Cgi/private.py:109 +#, fuzzy msgid "Private Archive Error - %(msg)s" msgstr "Chyba v súkromnom archíve - %(msg)s" @@ -3012,6 +3101,7 @@ msgid "Mailing list deletion results" msgstr "Výsledok zmazania konferencie" #: Mailman/Cgi/rmlist.py:187 +#, fuzzy msgid "" "You have successfully deleted the mailing list\n" " %(listname)s." @@ -3020,6 +3110,7 @@ msgstr "" " %(listname)s." #: Mailman/Cgi/rmlist.py:191 +#, fuzzy msgid "" "There were some problems deleting the mailing list\n" " %(listname)s. Contact your site administrator at " @@ -3030,6 +3121,7 @@ msgstr "" "Kontaktujte správcu servera %(sitelist)s pre ÄalÅ¡ie informácie. " #: Mailman/Cgi/rmlist.py:208 +#, fuzzy msgid "Permanently remove mailing list %(realname)s" msgstr "Trvalo zmazaÅ¥ konferenciu %(realname)s" @@ -3097,6 +3189,7 @@ msgid "Invalid options to CGI script" msgstr "Neplatné parametre CGI skriptu." #: Mailman/Cgi/roster.py:118 +#, fuzzy msgid "%(realname)s roster authentication failed." msgstr "%(realname)s - nepodarilo sa prihlásiÅ¥." @@ -3164,6 +3257,7 @@ msgstr "" "s Äalšími inÅ¡trukciami." #: Mailman/Cgi/subscribe.py:262 +#, fuzzy msgid "" "The email address you supplied is banned from this\n" " mailing list. If you think this restriction is erroneous, please\n" @@ -3187,6 +3281,7 @@ msgstr "" "Neboli ste prihlásený, pretože zadaná e-mailová adresa nie je bezpeÄná." #: Mailman/Cgi/subscribe.py:278 +#, fuzzy msgid "" "Confirmation from your email address is required, to prevent anyone from\n" "subscribing you without permission. Instructions are being sent to you at\n" @@ -3199,6 +3294,7 @@ msgstr "" "boli poslané potrebné inÅ¡trukcie" #: Mailman/Cgi/subscribe.py:290 +#, fuzzy msgid "" "Your subscription request was deferred because %(x)s. Your request has " "been\n" @@ -3219,6 +3315,7 @@ msgid "Mailman privacy alert" msgstr "Upozornenie na možné poruÅ¡enie súkromia" #: Mailman/Cgi/subscribe.py:316 +#, fuzzy msgid "" "An attempt was made to subscribe your address to the mailing list\n" "%(listaddr)s. You are already subscribed to this mailing list.\n" @@ -3256,6 +3353,7 @@ msgid "This list only supports digest delivery." msgstr "Táto konferencia podporuje iba doruÄovanie formou súhrnných správ." #: Mailman/Cgi/subscribe.py:344 +#, fuzzy msgid "You have been successfully subscribed to the %(realname)s mailing list." msgstr "ÚspeÅ¡ne ste sa prihlásili do konferencie %(realname)s." @@ -3305,6 +3403,7 @@ msgstr "" "e-mailovej adresy?" #: Mailman/Commands/cmd_confirm.py:69 +#, fuzzy msgid "" "You are currently banned from subscribing to this list. If you think this\n" "restriction is erroneous, please contact the list owners at\n" @@ -3385,26 +3484,32 @@ msgid "n/a" msgstr "nie je známe" #: Mailman/Commands/cmd_info.py:44 +#, fuzzy msgid "List name: %(listname)s" msgstr "Konferencia: %(listname)s" #: Mailman/Commands/cmd_info.py:45 +#, fuzzy msgid "Description: %(description)s" msgstr "Popis: %(description)s" #: Mailman/Commands/cmd_info.py:46 +#, fuzzy msgid "Postings to: %(postaddr)s" msgstr "Príspevky na adresu: %(postaddr)s" #: Mailman/Commands/cmd_info.py:47 +#, fuzzy msgid "List Helpbot: %(requestaddr)s" msgstr "Adresa robota s pomocou: %(requestaddr)s" #: Mailman/Commands/cmd_info.py:48 +#, fuzzy msgid "List Owners: %(owneraddr)s" msgstr "Správcovia konferencie: %(owneraddr)s" #: Mailman/Commands/cmd_info.py:49 +#, fuzzy msgid "More information: %(listurl)s" msgstr "ÄŽalÅ¡ie informácie: %(listurl)s" @@ -3427,18 +3532,22 @@ msgstr "" " ZobraiÅ¥ zoznam konferencií na tomto serveri.\n" #: Mailman/Commands/cmd_lists.py:44 +#, fuzzy msgid "Public mailing lists at %(hostname)s:" msgstr "Verejne prístupné konferencie na %(hostname)s:" #: Mailman/Commands/cmd_lists.py:66 +#, fuzzy msgid "%(i)3d. List name: %(realname)s" msgstr "%(i)3d. Konferencia: %(realname)s" #: Mailman/Commands/cmd_lists.py:67 +#, fuzzy msgid " Description: %(description)s" msgstr " Popis: %(description)s" #: Mailman/Commands/cmd_lists.py:68 +#, fuzzy msgid " Requests to: %(requestaddr)s" msgstr " Požiadavky na adresu: %(requestaddr)s" @@ -3471,12 +3580,14 @@ msgstr "" " a nie na adresu odosielateľa.\n" #: Mailman/Commands/cmd_password.py:51 Mailman/Commands/cmd_password.py:66 +#, fuzzy msgid "Your password is: %(password)s" msgstr "VaÅ¡e heslo je: %(password)s" #: Mailman/Commands/cmd_password.py:57 Mailman/Commands/cmd_password.py:72 #: Mailman/Commands/cmd_password.py:95 Mailman/Commands/cmd_password.py:121 #: Mailman/Commands/cmd_set.py:149 Mailman/Commands/cmd_set.py:219 +#, fuzzy msgid "You are not a member of the %(listname)s mailing list" msgstr "Nie ste úÄastníkom konferencie %(listname)s." @@ -3664,6 +3775,7 @@ msgstr "" " s heslom.\n" #: Mailman/Commands/cmd_set.py:122 +#, fuzzy msgid "Bad set command: %(subcmd)s" msgstr "Chybný parameter v príkaze set: %(subcmd)s" @@ -3684,6 +3796,7 @@ msgid "on" msgstr "on" #: Mailman/Commands/cmd_set.py:154 +#, fuzzy msgid " ack %(onoff)s" msgstr " ack %(onoff)s" @@ -3721,22 +3834,27 @@ msgid "due to bounces" msgstr "kvôli nedoruÄiteľnosti" #: Mailman/Commands/cmd_set.py:186 +#, fuzzy msgid " %(status)s (%(how)s on %(date)s)" msgstr " %(status)s (%(how)s dňa %(date)s)" #: Mailman/Commands/cmd_set.py:192 +#, fuzzy msgid " myposts %(onoff)s" msgstr " myposts %(onoff)s" #: Mailman/Commands/cmd_set.py:195 +#, fuzzy msgid " hide %(onoff)s" msgstr " hide %(onoff)s" #: Mailman/Commands/cmd_set.py:199 +#, fuzzy msgid " duplicates %(onoff)s" msgstr " duplicates %(onoff)s" #: Mailman/Commands/cmd_set.py:203 +#, fuzzy msgid " reminders %(onoff)s" msgstr " reminders %(onoff)s" @@ -3745,6 +3863,7 @@ msgid "You did not give the correct password" msgstr "Zadali jste nesprávne heslo." #: Mailman/Commands/cmd_set.py:236 Mailman/Commands/cmd_set.py:283 +#, fuzzy msgid "Bad argument: %(arg)s" msgstr "Chybný argument: %(arg)s" @@ -3823,6 +3942,7 @@ msgstr "" " address=pepa@freemail.fr\n" #: Mailman/Commands/cmd_subscribe.py:62 +#, fuzzy msgid "Bad digest specifier: %(arg)s" msgstr "Nesprávne nastavenie súhrnných správ: %(arg)s" @@ -3831,6 +3951,7 @@ msgid "No valid address found to subscribe" msgstr "Nebola nájdená žiadna platná adresa pre prihlásenie" #: Mailman/Commands/cmd_subscribe.py:113 +#, fuzzy msgid "" "The email address you supplied is banned from this mailing list.\n" "If you think this restriction is erroneous, please contact the list\n" @@ -3870,6 +3991,7 @@ msgid "This list only supports digest subscriptions!" msgstr "Táto konferencia podporuje iba režim súhrnných správ." #: Mailman/Commands/cmd_subscribe.py:146 +#, fuzzy msgid "" "Your subscription request has been forwarded to the list administrator\n" "at %(listowner)s for review." @@ -3904,6 +4026,7 @@ msgstr "" " Äalší argument za heslo, napr. vo forme adress=pepe@freemail.fr\n" #: Mailman/Commands/cmd_unsubscribe.py:62 +#, fuzzy msgid "%(address)s is not a member of the %(listname)s mailing list" msgstr "%(address)s nie je úÄastníkom konferencie %(listname)s" @@ -4155,6 +4278,7 @@ msgid "Chinese (Taiwan)" msgstr "Čínsky (Taiwan)" #: Mailman/Deliverer.py:53 +#, fuzzy msgid "" "Note: Since this is a list of mailing lists, administrative\n" "notices like the password reminder will be sent to\n" @@ -4168,14 +4292,17 @@ msgid " (Digest mode)" msgstr " (režim súhrnných správ)" #: Mailman/Deliverer.py:79 +#, fuzzy msgid "Welcome to the \"%(realname)s\" mailing list%(digmode)s" msgstr "Vitajte v konferencii „%(realname)s“! %(digmode)s" #: Mailman/Deliverer.py:89 +#, fuzzy msgid "You have been unsubscribed from the %(realname)s mailing list" msgstr "Odhlásený z konferencie „%(realname)s“" #: Mailman/Deliverer.py:116 +#, fuzzy msgid "%(listfullname)s mailing list reminder" msgstr "Konferencia %(listfullname)s -- pripomienka hesla" @@ -4188,6 +4315,7 @@ msgid "Hostile subscription attempt detected" msgstr "Neoprávnený pokus o prihlásenie bol zistený." #: Mailman/Deliverer.py:169 +#, fuzzy msgid "" "%(address)s was invited to a different mailing\n" "list, but in a deliberate malicious attempt they tried to confirm the\n" @@ -4199,6 +4327,7 @@ msgstr "" "Toto je iba upozornenie, na ktoré nie je potrebné reagovaÅ¥." #: Mailman/Deliverer.py:188 +#, fuzzy msgid "" "You invited %(address)s to your list, but in a\n" "deliberate malicious attempt, they tried to confirm the invitation to a\n" @@ -4211,6 +4340,7 @@ msgstr "" "Toto je iba upozornenie, na ktoré nie je potrebné reagovaÅ¥." #: Mailman/Deliverer.py:221 +#, fuzzy msgid "%(listname)s mailing list probe message" msgstr "Konferencia %(listname)s -- testovacia správa" @@ -4410,8 +4540,8 @@ msgid "" " membership.\n" "\n" "

                You can control both the\n" -" number\n" +" number\n" " of reminders the member will receive and the\n" " Môžete nastaviÅ¥ \n" -" poÄet\n" +" poÄet\n" " zasielaných upozornení, ktoré budou úÄastníkovi zaslané a " "tiež\n" " doba vyprÅ¡ania\n" +" Nazýva sa doba vyprÅ¡ania\n" " pozastavenia. Túto hodnotu musíte zosúladiÅ¥ s objemom " "komunikácie\n" " v konferencii. Spolu s maximálnym skóre urÄuje ako rýchlo budú " @@ -4610,8 +4740,8 @@ msgid "" " No those messages too will get discarded. You may " "want\n" " to set up an\n" -" autoresponse\n" +" autoresponse\n" " message for email to the -owner and -admin address." msgstr "" "Napriek tomu, že je rozpoznávanie nedoruÄitelných príspevkov v Mailmane\n" @@ -4705,12 +4835,13 @@ msgstr "" "o dôvode odhlásenia v každom prípade." #: Mailman/Gui/Bounce.py:194 +#, fuzzy msgid "" "Bad value for %(property)s: %(val)s" msgstr "" -"Neplatná hodnota premennej " -"%(property)s: %(val)s" +"Neplatná hodnota premennej %(property)s: %(val)s" #: Mailman/Gui/ContentFilter.py:30 msgid "Content filtering" @@ -4838,8 +4969,8 @@ msgstr "" "

                Prázdne riadky sú ignorované.\n" "\n" "

                Pozrite tiež zoznam typov ktoré sú povolené vo voľbe\n" -" pass_mime_types." +" pass_mime_types." #: Mailman/Gui/ContentFilter.py:94 msgid "" @@ -4855,8 +4986,8 @@ msgid "" "Use this option to remove each message attachment that does\n" " not have a matching content type. Requirements and formats " "are\n" -" exactly like filter_mime_types.\n" "\n" "

                Note: if you add entries to this list but don't add\n" @@ -4975,6 +5106,7 @@ msgstr "" " nepovolil. " #: Mailman/Gui/ContentFilter.py:171 +#, fuzzy msgid "Bad MIME type ignored: %(spectype)s" msgstr "Zlý MIME typ bol ignorovaný: %(spectype)s" @@ -5085,6 +5217,7 @@ msgstr "" "prázdna, niÄ sa neodoÅ¡le.)" #: Mailman/Gui/Digest.py:145 +#, fuzzy msgid "" "The next digest will be sent as volume\n" " %(volume)s, number %(number)s" @@ -5101,14 +5234,17 @@ msgid "There was no digest to send." msgstr "Žiadne súhrnné správy neÄekajú na odoslanie." #: Mailman/Gui/GUIBase.py:173 +#, fuzzy msgid "Invalid value for variable: %(property)s" msgstr "Chybná hodnota premennej: %(property)s" #: Mailman/Gui/GUIBase.py:178 +#, fuzzy msgid "Bad email address for option %(property)s: %(error)s" msgstr "Chybná e-mailová adresa pre parameter %(property)s: %(error)s" #: Mailman/Gui/GUIBase.py:204 +#, fuzzy msgid "" "The following illegal substitution variables were\n" " found in the %(property)s string:\n" @@ -5124,6 +5260,7 @@ msgstr "" "fungovaÅ¥." #: Mailman/Gui/GUIBase.py:218 +#, fuzzy msgid "" "Your %(property)s string appeared to\n" " have some correctable problems in its new value.\n" @@ -5221,8 +5358,8 @@ msgid "" "

                In order to split the list ownership duties into\n" " administrators and moderators, you must\n" " set a separate moderator password,\n" -" and also provide the email\n" +" and also provide the email\n" " addresses of the list moderators. Note that the field you\n" " are changing here specifies the list administrators." msgstr "" @@ -5523,13 +5660,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5564,8 +5701,8 @@ msgstr "" " Iným dôvodom je, že po zmene Reply-To: je výrazne\n" " skomplikovaná možnosÅ¥ súkromne odpovedaÅ¥ na príspevky. Pre\n" " viac informácií, ako sa má táto hlaviÄka používaÅ¥ navÅ¡tívte „Reply-To“ Munging Considered Harmful. Alternatívny názor\n" " nájdete na stránke Reply-To:." msgid "" "This is the address set in the Reply-To: header\n" " when the reply_goes_to_list\n" +" href=\"?VARHELP=general/" +"reply_goes_to_list\">reply_goes_to_list\n" " option is set to Explicit address.\n" "\n" "

                There are many reasons not to introduce or override the\n" @@ -5598,13 +5735,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5636,8 +5773,8 @@ msgstr "" " Iným dôvodom je, že po zmene Reply-To: je výrazne\n" " skomplikovaná možnosÅ¥ súkromne odpovedaÅ¥ na príspevky. Pre\n" " viac informácií, ako sa má táto hlaviÄka používaÅ¥ navÅ¡tívte „Reply-To“ Munging Considered Harmful. Alternatívny názor\n" " nájdete na stránke jazyky k dispozícii\n" +" Ak je v poli jazyky k dispozícii\n" " vybraných viac volieb, budú maÅ¥ úÄastníci možnosÅ¥ vybraÅ¥\n" " si jazyk zo zoznamu jazykov. Tam, kde úkon nie je viazaný na\n" " konkrétneho úÄastníka, bude komunikácia prebiehaÅ¥ v\n" @@ -6427,8 +6564,9 @@ msgid "" " page.\n" "\n" msgstr "" -"KeÄ je zapnutá pre túto konferenciu personifikácia, je možné používaÅ¥ ÄalÅ¡ie premenné v hlaviÄkách a\n" +"KeÄ je zapnutá pre túto konferenciu personifikácia, je možné používaÅ¥ ÄalÅ¡ie premenné v " +"hlaviÄkách a\n" "pätách správ:\n" "\n" "

                • user_address/b> - adresa používateľa prevedená na\n" @@ -6654,6 +6792,7 @@ msgstr "" " úÄastníkov bez ich vôle." #: Mailman/Gui/Privacy.py:104 +#, fuzzy msgid "" "This section allows you to configure subscription and\n" " membership exposure policy. You can also control whether this\n" @@ -6843,8 +6982,8 @@ msgid "" " either individually or as a group. Any\n" " posting from a non-member who is not explicitly accepted,\n" " rejected, or discarded, will have their posting filtered by the\n" -" general\n" +" general\n" " non-member rules.\n" "\n" "

                  In the text boxes below, add one address per line; start the\n" @@ -6899,6 +7038,7 @@ msgid "By default, should new list member postings be moderated?" msgstr "Vyžaduje sa schválenie príspevkov nových úÄastníkov moderátorom?" #: Mailman/Gui/Privacy.py:218 +#, fuzzy msgid "" "Each list member has a moderation flag which says\n" " whether messages from the list member can be posted directly " @@ -6945,8 +7085,8 @@ msgid "" "If a member posts this many times, within a period of time\n" " the member is automatically moderated. Use 0 to disable. " "See\n" -" member_verbosity_interval for details on the time " "period.\n" "\n" @@ -7011,8 +7151,8 @@ msgstr "" "\n" "

                • OdmietnuÅ¥ -- vráti správu odisielateľovi. Bude k\n" " nej priložený vysvetľujúci text, ktorý može byÅ¥ vami nastavený\n" -" na tejto stránke.\n" "\n" "

                • ZahodiÅ¥ -- jednoducho zahodí správu a autor " @@ -7076,8 +7216,8 @@ msgstr "" "\n" "

                • OdmietnuÅ¥ -- vráti správu odisielateľovi. Bude k\n" " nej priložený vysvetľujúci text, ktorý može byÅ¥ vami nastavený\n" -" na tejto stránke.\n" "\n" "

                • ZahodiÅ¥ -- jednoducho zahodí správu a autor " @@ -7404,6 +7544,7 @@ msgstr "" " preposlané moderátorovi?" #: Mailman/Gui/Privacy.py:494 +#, fuzzy msgid "" "Text to include in any rejection notice to be sent to\n" " non-members who post to this list. This notice can include\n" @@ -7646,6 +7787,7 @@ msgstr "" " Nekompletné pravidlá budú ignorované." #: Mailman/Gui/Privacy.py:693 +#, fuzzy msgid "" "The header filter rule pattern\n" " '%(safepattern)s' is not a legal regular expression. This\n" @@ -7696,8 +7838,8 @@ msgid "" "

                  The body of the message can also be optionally scanned for\n" " Subject: and Keywords: headers, as\n" " specified by the topics_bodylines_limit\n" +" href=\"?VARHELP=topics/" +"topics_bodylines_limit\">topics_bodylines_limit\n" " configuration variable." msgstr "" "Tematický filter zatriedi každú prijatú správu s použitím %(note)s\n" "\n" @@ -8074,6 +8222,7 @@ msgstr "" " potrebujete pomôcť, kontaktujte %(mailto)s." #: Mailman/HTMLFormatter.py:169 +#, fuzzy msgid "" "

                  We have received some recent bounces from your\n" " address. Your current bounce score is %(score)s out of " @@ -8095,6 +8244,7 @@ msgstr "" " bude vaÅ¡e skóre nedoruÄiteľnosti automaticky vynulované." #: Mailman/HTMLFormatter.py:181 +#, fuzzy msgid "" "(Note - you are subscribing to a list of mailing lists, so the %(type)s " "notice will be sent to the admin address for your membership, %(addr)s.)

                  " @@ -8141,6 +8291,7 @@ msgstr "" " moderátora budete informovaný e-mailom." #: Mailman/HTMLFormatter.py:208 +#, fuzzy msgid "" "This is %(also)sa private list, which means that the\n" " list of members is not available to non-members." @@ -8149,6 +8300,7 @@ msgstr "" " že zoznam úÄastníkov nie je prístupný pre tretie osoby." #: Mailman/HTMLFormatter.py:211 +#, fuzzy msgid "" "This is %(also)sa hidden list, which means that the\n" " list of members is available only to the list administrator." @@ -8157,6 +8309,7 @@ msgstr "" " pre správcu konferencie." #: Mailman/HTMLFormatter.py:214 +#, fuzzy msgid "" "This is %(also)sa public list, which means that the\n" " list of members list is available to everyone." @@ -8173,6 +8326,7 @@ msgstr "" " spamu.)" #: Mailman/HTMLFormatter.py:222 +#, fuzzy msgid "" "

                  (Note that this is an umbrella list, intended to\n" " have only other mailing lists as members. Among other things,\n" @@ -8189,6 +8343,7 @@ msgid "either " msgstr "buÄ " #: Mailman/HTMLFormatter.py:256 +#, fuzzy msgid "" "To unsubscribe from %(realname)s, get a password reminder,\n" " or change your subscription options %(either)senter your " @@ -8219,12 +8374,14 @@ msgid "" msgstr "Ak toto pole nevyplníte, budete vyzvaný zadaÅ¥ e-mailovú adresu." #: Mailman/HTMLFormatter.py:277 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " members.)" msgstr "%(which)s je dostupný iba pre úÄastníkov konferencie." #: Mailman/HTMLFormatter.py:281 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " administrator.)" @@ -8283,6 +8440,7 @@ msgid "The current archive" msgstr "Aktuálny archív" #: Mailman/Handlers/Acknowledge.py:59 +#, fuzzy msgid "%(realname)s post acknowledgement" msgstr "Konferencia %(realname)s - potvrdenie o prijatí príspevku" @@ -8295,6 +8453,7 @@ msgid "" msgstr "" #: Mailman/Handlers/CalcRecips.py:79 +#, fuzzy msgid "" "Your urgent message to the %(realname)s mailing list was not authorized for\n" "delivery. The original message as received by Mailman is attached.\n" @@ -8372,6 +8531,7 @@ msgid "Message may contain administrivia" msgstr "Správa pravdepodobne obsahuje príkazy pre listserver" #: Mailman/Handlers/Hold.py:84 +#, fuzzy msgid "" "Please do *not* post administrative requests to the mailing\n" "list. If you wish to subscribe, visit %(listurl)s or send a message with " @@ -8412,11 +8572,13 @@ msgid "Posting to a moderated newsgroup" msgstr "Príspevok do moderovanej konferencie" #: Mailman/Handlers/Hold.py:252 +#, fuzzy msgid "Your message to %(listname)s awaits moderator approval" msgstr "" "Váš príspevok do konferencie %(listname)s Äaká na schválenie moderátorom" #: Mailman/Handlers/Hold.py:271 +#, fuzzy msgid "%(listname)s post from %(sender)s requires approval" msgstr "" "Príspevok od %(sender)s do konferencie %(listname)s vyžaduje schválenie" @@ -8458,6 +8620,7 @@ msgid "After content filtering, the message was empty" msgstr "Po prefiltrovaní obsahu zostala prázdna správa" #: Mailman/Handlers/MimeDel.py:269 +#, fuzzy msgid "" "The attached message matched the %(listname)s mailing list's content " "filtering\n" @@ -8500,6 +8663,7 @@ msgid "The attached message has been automatically discarded." msgstr "Priložená správa bola automaticky zahodená." #: Mailman/Handlers/Replybot.py:75 +#, fuzzy msgid "Auto-response for your message to the \"%(realname)s\" mailing list" msgstr "Automatická odpoveÄ na správu zaslanú do konferencie „%(realname)s“" @@ -8508,6 +8672,7 @@ msgid "The Mailman Replybot" msgstr "Mailman Replybot" #: Mailman/Handlers/Scrubber.py:208 +#, fuzzy msgid "" "An embedded and charset-unspecified text was scrubbed...\n" "Name: %(filename)s\n" @@ -8522,6 +8687,7 @@ msgid "HTML attachment scrubbed and removed" msgstr "HTML príloha bola vyňatá a odstránená" #: Mailman/Handlers/Scrubber.py:234 Mailman/Handlers/Scrubber.py:259 +#, fuzzy msgid "" "An HTML attachment was scrubbed...\n" "URL: %(url)s\n" @@ -8542,6 +8708,7 @@ msgid "unknown sender" msgstr "neznámy odosielateľ" #: Mailman/Handlers/Scrubber.py:276 +#, fuzzy msgid "" "An embedded message was scrubbed...\n" "From: %(who)s\n" @@ -8558,6 +8725,7 @@ msgstr "" "URL: %(url)s\n" #: Mailman/Handlers/Scrubber.py:308 +#, fuzzy msgid "" "A non-text attachment was scrubbed...\n" "Name: %(filename)s\n" @@ -8574,6 +8742,7 @@ msgstr "" "URL: %(url)s\n" #: Mailman/Handlers/Scrubber.py:347 +#, fuzzy msgid "Skipped content of type %(partctype)s\n" msgstr "PreskoÄený obsah typu: %(partctype)s\n" @@ -8605,6 +8774,7 @@ msgid "Message rejected by filter rule match" msgstr "Správa bola zamietnutá filtrovacím pravidlom" #: Mailman/Handlers/ToDigest.py:173 +#, fuzzy msgid "%(realname)s Digest, Vol %(volume)d, Issue %(issue)d" msgstr "%(realname)s Súhrnná správa, vydanie %(volume)d, Äíslo %(issue)d" @@ -8641,6 +8811,7 @@ msgid "End of " msgstr "Koniec: " #: Mailman/ListAdmin.py:309 +#, fuzzy msgid "Posting of your message titled \"%(subject)s\"" msgstr "VaÅ¡a správa s predmetom „%(subject)s“" @@ -8653,6 +8824,7 @@ msgid "Forward of moderated message" msgstr "Preposlanie moderovanej správy" #: Mailman/ListAdmin.py:405 +#, fuzzy msgid "New subscription request to list %(realname)s from %(addr)s" msgstr "Nová žiadosÅ¥ o prihlásenie do konferencie %(realname)s od %(addr)s" @@ -8666,6 +8838,7 @@ msgid "via admin approval" msgstr "PokraÄovaÅ¥ v Äakaní na súhlas" #: Mailman/ListAdmin.py:466 +#, fuzzy msgid "New unsubscription request from %(realname)s by %(addr)s" msgstr "Nová žiadosÅ¥ o odhlásenie z konferencie %(realname)s od %(addr)s" @@ -8678,10 +8851,12 @@ msgid "Original Message" msgstr "Pôvodná správa" #: Mailman/ListAdmin.py:526 +#, fuzzy msgid "Request to mailing list %(realname)s rejected" msgstr "Požiadavka do konferencie %(realname)s bola zamietnutá" #: Mailman/MTA/Manual.py:66 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been created via the through-the-web\n" "interface. In order to complete the activation of this mailing list, the\n" @@ -8709,14 +8884,17 @@ msgstr "" "príkaz newaliases):\n" #: Mailman/MTA/Manual.py:82 +#, fuzzy msgid "## %(listname)s mailing list" msgstr "## %(listname)s maiing list" #: Mailman/MTA/Manual.py:99 +#, fuzzy msgid "Mailing list creation request for list %(listname)s" msgstr "ŽiadosÅ¥ o vytvorenie konferencie %(listname)s" #: Mailman/MTA/Manual.py:113 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been removed via the through-the-web\n" "interface. In order to complete the de-activation of this mailing list, " @@ -8733,6 +8911,7 @@ msgstr "" "Tu sú riadky, ktoré by mali byÅ¥ vymazané z /etc/aliases:\n" #: Mailman/MTA/Manual.py:123 +#, fuzzy msgid "" "\n" "To finish removing your mailing list, you must edit your /etc/aliases (or\n" @@ -8748,14 +8927,17 @@ msgstr "" "## %(listname)s mailing list" #: Mailman/MTA/Manual.py:142 +#, fuzzy msgid "Mailing list removal request for list %(listname)s" msgstr "ŽiadosÅ¥ o zruÅ¡enie konferencie %(listname)s" #: Mailman/MTA/Postfix.py:442 +#, fuzzy msgid "checking permissions on %(file)s" msgstr "overujem práva na súbor %(file)s" #: Mailman/MTA/Postfix.py:452 +#, fuzzy msgid "%(file)s permissions must be 0664 (got %(octmode)s)" msgstr "práva na súbor %(file)s musia byÅ¥ 0664 (teraz sú %(octmode)s)" @@ -8769,36 +8951,44 @@ msgid "(fixing)" msgstr "(opravujem)" #: Mailman/MTA/Postfix.py:470 +#, fuzzy msgid "checking ownership of %(dbfile)s" msgstr "overujem majiteľa súboru %(dbfile)s" #: Mailman/MTA/Postfix.py:478 +#, fuzzy msgid "%(dbfile)s owned by %(owner)s (must be owned by %(user)s" msgstr "" "súbor %(dbfile)s patrí používateľovi %(owner)s (musí ho vlastniÅ¥\n" "používateľ %(user)s)" #: Mailman/MTA/Postfix.py:491 +#, fuzzy msgid "%(dbfile)s permissions must be 0664 (got %(octmode)s)" msgstr "práva na súbor %(dbfile)s musia byÅ¥ 0664 (teraz sú %(octmode)s)" #: Mailman/MailList.py:219 +#, fuzzy msgid "Your confirmation is required to join the %(listname)s mailing list" msgstr "Pre prihlásenie do konferencie %(listname)s je potrebné overenie" #: Mailman/MailList.py:230 +#, fuzzy msgid "Your confirmation is required to leave the %(listname)s mailing list" msgstr "Pro odhlásenie z konferencie %(listname)s je potrebné overenie." #: Mailman/MailList.py:999 Mailman/MailList.py:1492 +#, fuzzy msgid " from %(remote)s" msgstr "od %(remote)s" #: Mailman/MailList.py:1043 +#, fuzzy msgid "subscriptions to %(realname)s require moderator approval" msgstr "prihlásenia do konferencie %(realname)s vyžadjú súhlas moderátora" #: Mailman/MailList.py:1125 bin/add_members:299 +#, fuzzy msgid "%(realname)s subscription notification" msgstr "%(realname)s upozornenie o prihlásení." @@ -8807,6 +8997,7 @@ msgid "unsubscriptions require moderator approval" msgstr "odhlásenie z konferencie vyžaduje súhlas moderátora" #: Mailman/MailList.py:1166 +#, fuzzy msgid "%(realname)s unsubscribe notification" msgstr "%(realname)s upozornenie o odhlásení" @@ -8826,6 +9017,7 @@ msgid "via web confirmation" msgstr "Nesprávný potvrdzovací reÅ¥azec" #: Mailman/MailList.py:1396 +#, fuzzy msgid "subscriptions to %(name)s require administrator approval" msgstr "prihlásenie do konferencie %(name)s vyžaduje súhlas moderátora" @@ -8844,6 +9036,7 @@ msgid "Last autoresponse notification for today" msgstr "Posledná automatická odpoveÄ pre dneÅ¡ok" #: Mailman/Queue/BounceRunner.py:360 +#, fuzzy msgid "" "The attached message was received as a bounce, but either the bounce format\n" "was not recognized, or no member addresses could be extracted from it. " @@ -8934,6 +9127,7 @@ msgid "Original message suppressed by Mailman site configuration\n" msgstr "" #: Mailman/htmlformat.py:675 +#, fuzzy msgid "Delivered by Mailman
                  version %(version)s" msgstr "DoruÄené Mailmanom
                  verzia %(version)s" @@ -9022,6 +9216,7 @@ msgid "Server Local Time" msgstr "Miestny Äas servera" #: Mailman/i18n.py:180 +#, fuzzy msgid "" "%(wday)s %(mon)s %(day)2i %(hh)02i:%(mm)02i:%(ss)02i %(tzname)s %(year)04i" msgstr "" @@ -9134,6 +9329,7 @@ msgstr "" "môže byÅ¥ „-“.\n" #: bin/add_members:162 bin/add_members:172 +#, fuzzy msgid "Already a member: %(member)s" msgstr "%(member)s už je úÄastníkom." @@ -9142,10 +9338,12 @@ msgid "Bad/Invalid email address: blank line" msgstr "Neplatná e-mailová adresa: prázdny riadok" #: bin/add_members:180 +#, fuzzy msgid "Bad/Invalid email address: %(member)s" msgstr "Neplatná e-mailová adresa: %(member)s" #: bin/add_members:182 +#, fuzzy msgid "Hostile address (illegal characters): %(member)s" msgstr "Neplatná adresa (obsahuje nepovolené znaky): %(member)s" @@ -9155,14 +9353,17 @@ msgid "Invited: %(member)s" msgstr "Prihlásený: %(member)s" #: bin/add_members:187 +#, fuzzy msgid "Subscribed: %(member)s" msgstr "Prihlásený: %(member)s" #: bin/add_members:237 +#, fuzzy msgid "Bad argument to -w/--welcome-msg: %(arg)s" msgstr "Chybný parameter v -w/--welcome-msg: %(arg)s" #: bin/add_members:244 +#, fuzzy msgid "Bad argument to -a/--admin-notify: %(arg)s" msgstr "Chybný parameter v -a/--admin-notify: %(arg)s" @@ -9179,6 +9380,7 @@ msgstr "" #: bin/add_members:261 bin/config_list:110 bin/export.py:271 bin/find_member:97 #: bin/inject:91 bin/list_admins:90 bin/list_members:252 bin/sync_members:222 #: cron/bumpdigests:86 +#, fuzzy msgid "No such list: %(listname)s" msgstr "Neznáma konferencia: %(listname)s" @@ -9189,6 +9391,7 @@ msgid "Nothing to do." msgstr "NiÄ na vykonanie." #: bin/arch:19 +#, fuzzy msgid "" "Rebuild a list's archive.\n" "\n" @@ -9278,6 +9481,7 @@ msgid "listname is required" msgstr "Meno konferencie je vyžadované." #: bin/arch:143 bin/change_pw:107 bin/config_list:257 +#, fuzzy msgid "" "No such list \"%(listname)s\"\n" "%(e)s" @@ -9286,6 +9490,7 @@ msgstr "" "%(e)s" #: bin/arch:168 +#, fuzzy msgid "Cannot open mbox file %(mbox)s: %(msg)s" msgstr "Nemôžem otvoriÅ¥ súbor mbox %(mbox)s: %(msg)s" @@ -9367,6 +9572,7 @@ msgid "" msgstr "" #: bin/change_pw:145 +#, fuzzy msgid "Bad arguments: %(strargs)s" msgstr "Chybné argumenty: %(strargs)s" @@ -9375,14 +9581,17 @@ msgid "Empty list passwords are not allowed" msgstr "Nie je prípustné, aby mal správca konferencie prázdne heslo." #: bin/change_pw:181 +#, fuzzy msgid "New %(listname)s password: %(notifypassword)s" msgstr "Nové heslo pre konferenciu %(listname)s: %(notifypassword)s" #: bin/change_pw:190 +#, fuzzy msgid "Your new %(listname)s list password" msgstr "VaÅ¡e nové heslo pre %(listname)s" #: bin/change_pw:191 +#, fuzzy msgid "" "The site administrator at %(hostname)s has changed the password for your\n" "mailing list %(listname)s. It is now\n" @@ -9453,6 +9662,7 @@ msgid "List:" msgstr "Konferencia: " #: bin/check_db:148 +#, fuzzy msgid " %(file)s: okay" msgstr " %(file)s: v poriadku" @@ -9468,40 +9678,47 @@ msgid "" msgstr "" #: bin/check_perms:110 +#, fuzzy msgid " checking gid and mode for %(path)s" -msgstr "" +msgstr "overujem práva na súbor %(file)s" #: bin/check_perms:122 msgid "%(path)s bad group (has: %(groupname)s, expected %(MAILMAN_GROUP)s)" msgstr "" #: bin/check_perms:151 +#, fuzzy msgid "directory permissions must be %(octperms)s: %(path)s" -msgstr "" +msgstr "práva na súbor %(dbfile)s musia byÅ¥ 0664 (teraz sú %(octmode)s)" #: bin/check_perms:160 +#, fuzzy msgid "source perms must be %(octperms)s: %(path)s" -msgstr "" +msgstr "práva na súbor %(dbfile)s musia byÅ¥ 0664 (teraz sú %(octmode)s)" #: bin/check_perms:171 +#, fuzzy msgid "article db files must be %(octperms)s: %(path)s" -msgstr "" +msgstr "práva na súbor %(dbfile)s musia byÅ¥ 0664 (teraz sú %(octmode)s)" #: bin/check_perms:183 +#, fuzzy msgid "checking mode for %(prefix)s" -msgstr "" +msgstr "overujem práva na súbor %(file)s" #: bin/check_perms:193 msgid "WARNING: directory does not exist: %(d)s" msgstr "" #: bin/check_perms:197 +#, fuzzy msgid "directory must be at least 02775: %(d)s" -msgstr "" +msgstr "práva na súbor %(file)s musia byÅ¥ 0664 (teraz sú %(octmode)s)" #: bin/check_perms:209 +#, fuzzy msgid "checking perms on %(private)s" -msgstr "" +msgstr "overujem práva na súbor %(file)s" #: bin/check_perms:214 msgid "%(private)s must not be other-readable" @@ -9529,40 +9746,46 @@ msgid "checking cgi-bin permissions" msgstr "" #: bin/check_perms:278 +#, fuzzy msgid " checking set-gid for %(path)s" -msgstr "" +msgstr "overujem práva na súbor %(file)s" #: bin/check_perms:282 msgid "%(path)s must be set-gid" msgstr "" #: bin/check_perms:292 +#, fuzzy msgid "checking set-gid for %(wrapper)s" -msgstr "" +msgstr "overujem práva na súbor %(file)s" #: bin/check_perms:296 msgid "%(wrapper)s must be set-gid" msgstr "" #: bin/check_perms:306 +#, fuzzy msgid "checking permissions on %(pwfile)s" -msgstr "" +msgstr "overujem práva na súbor %(file)s" #: bin/check_perms:315 +#, fuzzy msgid "%(pwfile)s permissions must be exactly 0640 (got %(octmode)s)" -msgstr "" +msgstr "práva na súbor %(file)s musia byÅ¥ 0664 (teraz sú %(octmode)s)" #: bin/check_perms:340 msgid "checking permissions on list data" msgstr "" #: bin/check_perms:348 +#, fuzzy msgid " checking permissions on: %(path)s" -msgstr "" +msgstr "overujem práva na súbor %(file)s" #: bin/check_perms:356 +#, fuzzy msgid "file permissions must be at least 660: %(path)s" -msgstr "" +msgstr "práva na súbor %(file)s musia byÅ¥ 0664 (teraz sú %(octmode)s)" #: bin/check_perms:401 msgid "No problems found" @@ -9615,6 +9838,7 @@ msgid "Unix-From line changed: %(lineno)d" msgstr "" #: bin/cleanarch:111 +#, fuzzy msgid "Bad status number: %(arg)s" msgstr "Chybný stavový kód: %(arg)s" @@ -9713,10 +9937,12 @@ msgid " original address removed:" msgstr " pôvodná adresa odstránená:" #: bin/clone_member:202 +#, fuzzy msgid "Not a valid email address: %(toaddr)s" msgstr "Neplatná e-mailová adresa %(toaddr)s" #: bin/clone_member:215 +#, fuzzy msgid "" "Error opening list \"%(listname)s\", skipping.\n" "%(e)s" @@ -9807,10 +10033,12 @@ msgid "Non-standard property restored: %(k)s" msgstr "" #: bin/config_list:288 +#, fuzzy msgid "Invalid value for property: %(k)s" msgstr "Chybná hodnota parametra: %(k)s" #: bin/config_list:291 +#, fuzzy msgid "Bad email address for option %(k)s: %(v)s" msgstr "Chybná e-mailová adresa pre parameter %(k)s: %(v)s" @@ -9870,14 +10098,17 @@ msgstr "" " NevypisovaÅ¥ správy o stave.\n" #: bin/discard:94 +#, fuzzy msgid "Ignoring non-held message: %(f)s" msgstr "Ignorujem nepodržanú správu: %(f)s" #: bin/discard:100 +#, fuzzy msgid "Ignoring held msg w/bad id: %(f)s" msgstr "Ignorujem podržanú správu s chybným id: %(f)s" #: bin/discard:112 +#, fuzzy msgid "Discarded held msg #%(id)s for list %(listname)s" msgstr "Odstránená správa Ä. %(id)s pre konferenciu %(listname)s" @@ -9923,6 +10154,7 @@ msgid "No filename given." msgstr "Nebol zadaný názov súboru." #: bin/dumpdb:108 +#, fuzzy msgid "Bad arguments: %(pargs)s" msgstr "Nesprávne argumenty: %(pargs)s" @@ -10161,6 +10393,7 @@ msgstr "" "tak je text naÄítaný zo Å¡tandardného vstupu (stdin).\n" #: bin/inject:84 +#, fuzzy msgid "Bad queue directory: %(qdir)s" msgstr "Chybný prieÄinok fronty: %(qdir)s" @@ -10169,6 +10402,7 @@ msgid "A list name is required" msgstr "Meno konferenie je vyžadované." #: bin/list_admins:20 +#, fuzzy msgid "" "List all the owners of a mailing list.\n" "\n" @@ -10215,6 +10449,7 @@ msgstr "" "Môžete zadaÅ¥ aj viac názvov konferencií za sebou.\n" #: bin/list_admins:97 +#, fuzzy msgid "List: %(listname)s, \tOwners: %(owners)s" msgstr "Konferencia: %(listname)s, \tVlastníci: %(owners)s" @@ -10397,10 +10632,12 @@ msgstr "" "rozpoznaÅ¥, akou formou jednotliví úÄastníci odoberajú príspevky.\n" #: bin/list_members:198 +#, fuzzy msgid "Bad --nomail option: %(why)s" msgstr "Chybný parameter pre --nomail: %(why)s" #: bin/list_members:209 +#, fuzzy msgid "Bad --digest option: %(kind)s" msgstr "Chybný parameter --digest: %(kind)s" @@ -10414,6 +10651,7 @@ msgid "Could not open file for writing:" msgstr "Nemôžem otvoriÅ¥ súbor na zapisovanie:" #: bin/list_owners:20 +#, fuzzy msgid "" "List the owners of a mailing list, or all mailing lists.\n" "\n" @@ -10606,8 +10844,9 @@ msgid "" msgstr "" #: bin/mailmanctl:280 cron/mailpasswds:119 +#, fuzzy msgid "Site list is missing: %(sitelistname)s" -msgstr "" +msgstr "Konferencia už existuje : %(safelistname)s" #: bin/mailmanctl:305 msgid "Run this program as root or as the %(name)s user, or use -u." @@ -10618,6 +10857,7 @@ msgid "No command given." msgstr "Nebol zadaný žiaden prákaz." #: bin/mailmanctl:339 +#, fuzzy msgid "Bad command: %(command)s" msgstr "Chybný príkaz: %(command)s" @@ -10833,6 +11073,7 @@ msgid "" msgstr "" #: bin/newlist:162 +#, fuzzy msgid "Unknown language: %(lang)s" msgstr "Neznámy jazyk: %(lang)s" @@ -10845,6 +11086,7 @@ msgid "Enter the email of the person running the list: " msgstr "Zadajte e-mailovoú adresu správcu konferencie:" #: bin/newlist:193 +#, fuzzy msgid "Initial %(listname)s password: " msgstr "PoÄiatoÄné heslo pre konferenciu %(listname)s: " @@ -10854,11 +11096,12 @@ msgstr "Heslo pre konferenciu nesmie byÅ¥ prázdne." #: bin/newlist:220 msgid "" -" - owner addresses need to be fully-qualified names like \"owner@example.com" -"\", not just \"owner\"." +" - owner addresses need to be fully-qualified names like \"owner@example." +"com\", not just \"owner\"." msgstr "" #: bin/newlist:244 +#, fuzzy msgid "Hit enter to notify %(listname)s owner..." msgstr "StlaÄte ENTER pre upozornenie vlastníka konferencie %(listname)s ..." @@ -11019,19 +11262,23 @@ msgid "" msgstr "" #: bin/remove_members:156 +#, fuzzy msgid "Could not open file for reading: %(filename)s." msgstr "Nepodarilo sa otvoriÅ¥ súbor na Äítanie: %(filename)s." #: bin/remove_members:163 +#, fuzzy msgid "Error opening list %(listname)s... skipping." msgstr "" "Chyba pri otváraní konfigurácie konferencie %(listname)s... preskakujem." #: bin/remove_members:173 +#, fuzzy msgid "No such member: %(addr)s" msgstr "Neznámy úÄastník: %(addr)s." #: bin/remove_members:178 +#, fuzzy msgid "User `%(addr)s' removed from list: %(listname)s." msgstr "ÚÄastník „%(addr)s“ bol odhlásený z konferencie: %(listname)s." @@ -11056,8 +11303,9 @@ msgid "" msgstr "" #: bin/reset_pw.py:77 +#, fuzzy msgid "Changing passwords for list: %(listname)s" -msgstr "" +msgstr "ŽiadosÅ¥ o zruÅ¡enie konferencie %(listname)s" #: bin/reset_pw.py:83 msgid "New password for member %(member)40s: %(randompw)s" @@ -11086,18 +11334,22 @@ msgid "" msgstr "" #: bin/rmlist:73 bin/rmlist:76 +#, fuzzy msgid "Removing %(msg)s" msgstr "Odstraňuje sa %(msg)s" #: bin/rmlist:81 +#, fuzzy msgid "%(listname)s %(msg)s not found as %(filename)s" msgstr "%(listname)s %(msg)s nebolo nájdené ako %(filename)s" #: bin/rmlist:105 +#, fuzzy msgid "No such list (or list already deleted): %(listname)s" msgstr "Neznáma (alebo už zmazaná) konferencia: %(listname)s" #: bin/rmlist:108 +#, fuzzy msgid "No such list: %(listname)s. Removing its residual archives." msgstr "Neznáma konferencia: %(listname)s - odstraňujem zvyÅ¡ok archívov." @@ -11146,6 +11398,7 @@ msgid "" msgstr "" #: bin/sync_members:19 +#, fuzzy msgid "" "Synchronize a mailing list's membership with a flat file.\n" "\n" @@ -11276,6 +11529,7 @@ msgstr "" "synchronizovaÅ¥.\n" #: bin/sync_members:115 +#, fuzzy msgid "Bad choice: %(yesno)s" msgstr "Chybný výber: %(yesno)s" @@ -11292,6 +11546,7 @@ msgid "No argument to -f given" msgstr "Nebol zadaný parameter k -f" #: bin/sync_members:172 +#, fuzzy msgid "Illegal option: %(opt)s" msgstr "Chybný parameter: %(opt)s" @@ -11304,6 +11559,7 @@ msgid "Must have a listname and a filename" msgstr "Musí byÅ¥ zadaný názov konferencie a názov súboru" #: bin/sync_members:191 +#, fuzzy msgid "Cannot read address file: %(filename)s: %(msg)s" msgstr "Súbor s adresami sa nedá preÄítaÅ¥: %(filename)s: %(msg)s" @@ -11320,10 +11576,12 @@ msgid "You must fix the preceding invalid addresses first." msgstr "Je nutné najprv opraviÅ¥ predchádzajúce chybné adresy." #: bin/sync_members:264 +#, fuzzy msgid "Added : %(s)s" msgstr "Pridaní : %(s)s" #: bin/sync_members:289 +#, fuzzy msgid "Removed: %(s)s" msgstr "Odstránení: %(s)s" @@ -11428,8 +11686,9 @@ msgid "" msgstr "" #: bin/update:107 +#, fuzzy msgid "Fixing language templates: %(listname)s" -msgstr "" +msgstr "Aktualizácia konferencie: %(listname)s" #: bin/update:196 bin/update:711 msgid "WARNING: could not acquire lock for list: %(listname)s" @@ -11481,6 +11740,7 @@ msgid "- updating old private mbox file" msgstr "- aktualizujem starý súkromný mbox súbor" #: bin/update:295 +#, fuzzy msgid "" " unknown file in the way, moving\n" " %(o_pri_mbox_file)s\n" @@ -11497,6 +11757,7 @@ msgid "- updating old public mbox file" msgstr "- aktualizujem starý verejný mbox súbor" #: bin/update:317 +#, fuzzy msgid "" " unknown file in the way, moving\n" " %(o_pub_mbox_file)s\n" @@ -11525,18 +11786,22 @@ msgid "- %(o_tmpl)s doesn't exist, leaving untouched" msgstr "- %(o_tmpl)s neexistuje, ponechané bez zmeny" #: bin/update:396 +#, fuzzy msgid "removing directory %(src)s and everything underneath" msgstr "odstránenie prieÄinka %(src)s a vÅ¡etkého pod ním" #: bin/update:399 +#, fuzzy msgid "removing %(src)s" msgstr "odstránenie %(src)s" #: bin/update:403 +#, fuzzy msgid "Warning: couldn't remove %(src)s -- %(rest)s" msgstr "Pozor: nepodarilo sa zmazaÅ¥ %(src)s -- %(rest)s" #: bin/update:408 +#, fuzzy msgid "couldn't remove old file %(pyc)s -- %(rest)s" msgstr "nepodarilo sa zmazaÅ¥ starý súbor %(pyc)s -- %(rest)s" @@ -11545,14 +11810,17 @@ msgid "updating old qfiles" msgstr "aktualizujem staré qfiles" #: bin/update:455 +#, fuzzy msgid "Warning! Not a directory: %(dirpath)s" msgstr "Pozor! Nie je prieÄinok: %(dirpath)s" #: bin/update:530 +#, fuzzy msgid "message is unparsable: %(filebase)s" msgstr "nespacovateľná správa: %(filebase)s" #: bin/update:544 +#, fuzzy msgid "Warning! Deleting empty .pck file: %(pckfile)s" msgstr "Pozor! Zmazáva sa prázdny .pck súbor: %(pckfile)s" @@ -11565,10 +11833,12 @@ msgid "Updating Mailman 2.1.4 pending.pck database" msgstr "Aktualizujem databázu pending.pck systému Mailman 2.1.4" #: bin/update:598 +#, fuzzy msgid "Ignoring bad pended data: %(key)s: %(val)s" msgstr "Ignorujem chybné Äakajúce dáta: %(key)s: %(val)s" #: bin/update:614 +#, fuzzy msgid "WARNING: Ignoring duplicate pending ID: %(id)s." msgstr "POZOR: Ignorujem duplikátne Äakajúce ID: %(id)s." @@ -11591,6 +11861,7 @@ msgid "done" msgstr "dokonÄené" #: bin/update:691 +#, fuzzy msgid "Updating mailing list: %(listname)s" msgstr "Aktualizácia konferencie: %(listname)s" @@ -11634,6 +11905,7 @@ msgid "No updates are necessary." msgstr "Žiadne aktualizácie nie sú potrebné." #: bin/update:796 +#, fuzzy msgid "" "Downgrade detected, from version %(hexlversion)s to version %(hextversion)s\n" "This is probably not safe.\n" @@ -11644,6 +11916,7 @@ msgstr "" "UkonÄujem." #: bin/update:801 +#, fuzzy msgid "Upgrading from version %(hexlversion)s to %(hextversion)s" msgstr "Aktualizácia z verzie %(hexlversion)s na verziu %(hextversion)s" @@ -11800,6 +12073,7 @@ msgid "" msgstr "" #: bin/withlist:175 +#, fuzzy msgid "Unlocking (but not saving) list: %(listname)s" msgstr "Odomknúť (ale neuložiÅ¥) konferenciu : %(listname)s" @@ -11808,6 +12082,7 @@ msgid "Finalizing" msgstr "Finalizácia" #: bin/withlist:188 +#, fuzzy msgid "Loading list %(listname)s" msgstr "Nahrávam konferenciu %(listname)s" @@ -11820,6 +12095,7 @@ msgid "(unlocked)" msgstr "(odomknuté)" #: bin/withlist:197 +#, fuzzy msgid "Unknown list: %(listname)s" msgstr "Neznáma konferencia: %(listname)s" @@ -11832,18 +12108,22 @@ msgid "--all requires --run" msgstr "--all vyžaduje --run" #: bin/withlist:266 +#, fuzzy msgid "Importing %(module)s..." msgstr "Importujem %(module)s..." #: bin/withlist:270 +#, fuzzy msgid "Running %(module)s.%(callable)s()..." msgstr "Spúšťam %(module)s.%(callable)s()..." #: bin/withlist:291 +#, fuzzy msgid "The variable `m' is the %(listname)s MailList instance" msgstr "Premenná „m“ je inÅ¡tanciou konferencie %(listname)s" #: cron/bumpdigests:19 +#, fuzzy msgid "" "Increment the digest volume number and reset the digest number to one.\n" "\n" @@ -11874,6 +12154,7 @@ msgstr "" "vo vÅ¡etkých konferenciách.\n" #: cron/checkdbs:20 +#, fuzzy msgid "" "Check for pending admin requests and mail the list owners if necessary.\n" "\n" @@ -11900,10 +12181,12 @@ msgid "" msgstr "Upozornenie: poÄet automaticky vyprÅ¡ných požiadavok: %(discarded)d\n" #: cron/checkdbs:121 +#, fuzzy msgid "%(count)d %(realname)s moderator request(s) waiting" msgstr "%(realname)s - Na rozhodnutie moderátora Äaká %(count)d požiadaviek" #: cron/checkdbs:124 +#, fuzzy msgid "%(realname)s moderator request check result" msgstr "%(realname)s - výsledok kontroly požiadaviek na moderátora" @@ -11925,6 +12208,7 @@ msgstr "" "ÄŒakajúce príspevky:" #: cron/checkdbs:169 +#, fuzzy msgid "" "From: %(sender)s on %(date)s\n" "Subject: %(subject)s\n" @@ -12055,6 +12339,7 @@ msgid "Password // URL" msgstr "Heslo // URL" #: cron/mailpasswds:222 +#, fuzzy msgid "%(host)s mailing list memberships reminder" msgstr "Server %(host)s: Pripomienka úÄasÅ¥i v konferenciách" @@ -12122,8 +12407,8 @@ msgstr "" #~ " applied" #~ msgstr "" #~ "Text, ktorý bude priložený ku každej\n" -#~ " správe o zamietnutí príspevku,\n" #~ " ktorá sa poÅ¡le autorovi správy." diff --git a/messages/sl/LC_MESSAGES/mailman.po b/messages/sl/LC_MESSAGES/mailman.po index e4206659..a0acc83c 100755 --- a/messages/sl/LC_MESSAGES/mailman.po +++ b/messages/sl/LC_MESSAGES/mailman.po @@ -79,10 +79,12 @@ msgid "

                  Currently, there are no archives.

                  " msgstr "

                  Trenutno arhiva ni na voljo.

                  " #: Mailman/Archiver/HyperArch.py:821 +#, fuzzy msgid "Gzip'd Text%(sz)s" msgstr "%(sz)s besedila stisnjeno z gzip" #: Mailman/Archiver/HyperArch.py:826 +#, fuzzy msgid "Text%(sz)s" msgstr "Besedilo %(sz)s" @@ -162,18 +164,22 @@ msgid "Third" msgstr "Tretja" #: Mailman/Archiver/HyperArch.py:938 +#, fuzzy msgid "%(ord)s quarter %(year)i" msgstr "%(ord)s èetrtina %(year)i" #: Mailman/Archiver/HyperArch.py:945 +#, fuzzy msgid "%(month)s %(year)i" msgstr "%(month)s %(year)i" #: Mailman/Archiver/HyperArch.py:950 +#, fuzzy msgid "The Week Of Monday %(day)i %(month)s %(year)i" msgstr "Teden %(day)i %(month)s %(year)i" #: Mailman/Archiver/HyperArch.py:954 +#, fuzzy msgid "%(day)i %(month)s %(year)i" msgstr "%(day)i %(month)s %(year)i" @@ -183,10 +189,12 @@ msgstr "Izdelava kazala po temah\n" # Mailman/Archiver/pipermail.py:414 #: Mailman/Archiver/HyperArch.py:1319 +#, fuzzy msgid "Updating HTML for article %(seq)s" msgstr "Posodobitev HTML za prispevek %(seq)s" #: Mailman/Archiver/HyperArch.py:1326 +#, fuzzy msgid "article file %(filename)s is missing!" msgstr "Manjka datoteka prispevka %(filename)s!" @@ -207,6 +215,7 @@ msgstr "Serializiranje stanja arhiva v " # Mailman/Archiver/pipermail.py:414 #: Mailman/Archiver/pipermail.py:453 +#, fuzzy msgid "Updating index files for archive [%(archive)s]" msgstr "Posodabljanje datotek kazala za arhiv [%(archive)s]" @@ -216,6 +225,7 @@ msgid " Thread" msgstr " Tema" #: Mailman/Archiver/pipermail.py:597 +#, fuzzy msgid "#%(counter)05d %(msgid)s" msgstr "#%(counter)05d %(msgid)s" @@ -257,6 +267,7 @@ msgid "disabled address" msgstr "onemogoèeno" #: Mailman/Bouncer.py:304 +#, fuzzy msgid " The last bounce received from you was dated %(date)s" msgstr " Zadnja zavrnitev s tega naslova je z dne %(date)s" @@ -295,6 +306,7 @@ msgstr "Skrbnik" #: Mailman/Cgi/options.py:101 Mailman/Cgi/private.py:108 #: Mailman/Cgi/rmlist.py:75 Mailman/Cgi/roster.py:59 #: Mailman/Cgi/subscribe.py:67 +#, fuzzy msgid "No such list %(safelistname)s" msgstr "Seznam %(safelistname)s ne obstaja." @@ -377,6 +389,7 @@ msgstr "" # Mailman/Cgi/admin.py:203 #: Mailman/Cgi/admin.py:268 +#, fuzzy msgid "%(hostname)s mailing lists - Admin Links" msgstr "%(hostname)s po¹tni seznami - Administrativne povezave" @@ -392,6 +405,7 @@ msgstr "Mailman" # Mailman/Cgi/admin.py:232 #: Mailman/Cgi/admin.py:310 +#, fuzzy msgid "" "

                  There currently are no publicly-advertised %(mailmanlink)s\n" " mailing lists on %(hostname)s." @@ -401,6 +415,7 @@ msgstr "" # Mailman/Cgi/admin.py:238 #: Mailman/Cgi/admin.py:316 +#, fuzzy msgid "" "

                  Below is the collection of publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s. Click on a list\n" @@ -417,6 +432,7 @@ msgstr "desno " # Mailman/Cgi/admin.py:247 #: Mailman/Cgi/admin.py:325 +#, fuzzy msgid "" "To visit the administrators configuration page for an\n" " unadvertised list, open a URL similar to this one, but with a '/' " @@ -468,6 +484,7 @@ msgstr "Ni najdenega veljavnega imena spremenljivke." # Mailman/Cgi/admin.py:314 #: Mailman/Cgi/admin.py:395 +#, fuzzy msgid "" "%(realname)s Mailing list Configuration Help\n" "
                  %(varname)s Option" @@ -477,6 +494,7 @@ msgstr "" # Mailman/Cgi/admin.py:321 #: Mailman/Cgi/admin.py:402 +#, fuzzy msgid "Mailman %(varname)s List Option Help" msgstr "Pomoè za mo¾nosti Mailman %(varname)s seznama" @@ -498,16 +516,19 @@ msgstr "" # Mailman/Cgi/admin.py:340 #: Mailman/Cgi/admin.py:431 +#, fuzzy msgid "return to the %(categoryname)s options page." msgstr "vrnete na stran z mo¾nostmi %(categoryname)s." # Mailman/Cgi/admin.py:355 #: Mailman/Cgi/admin.py:446 +#, fuzzy msgid "%(realname)s Administration (%(label)s)" msgstr "Skrbni¹tvo za %(realname)s (%(label)s)" # Mailman/Cgi/admin.py:356 #: Mailman/Cgi/admin.py:447 +#, fuzzy msgid "%(realname)s mailing list administration
                  %(label)s Section" msgstr "Skrbni¹tvo po¹tnega seznama za %(realname)s
                  Podroèje %(label)s" @@ -602,6 +623,7 @@ msgstr "Vrednost" # Mailman/Cgi/admin.py:562 #: Mailman/Cgi/admin.py:668 +#, fuzzy msgid "" "Badly formed options entry:\n" " %(record)s" @@ -716,10 +738,12 @@ msgid "Move rule down" msgstr "" #: Mailman/Cgi/admin.py:870 +#, fuzzy msgid "
                  (Edit %(varname)s)" msgstr "
                  (Uredi %(varname)s)" #: Mailman/Cgi/admin.py:872 +#, fuzzy msgid "
                  (Details for %(varname)s)" msgstr "
                  (Podrobnosti za %(varname)s)" @@ -766,6 +790,7 @@ msgid "(help)" msgstr "(pomoè)" #: Mailman/Cgi/admin.py:930 +#, fuzzy msgid "Find member %(link)s:" msgstr "Najdi èlana %(link)s:" @@ -780,11 +805,13 @@ msgstr "Neveljavni regularni izraz: " # Mailman/Cgi/admin.py:788 #: Mailman/Cgi/admin.py:1013 +#, fuzzy msgid "%(allcnt)s members total, %(membercnt)s shown" msgstr "%(allcnt)s èlanov skupno, %(membercnt)s prikazanih" # Mailman/Cgi/admin.py:791 #: Mailman/Cgi/admin.py:1016 +#, fuzzy msgid "%(allcnt)s members total" msgstr "%(allcnt)s èlanov skupno" @@ -991,6 +1018,7 @@ msgstr "" # Mailman/Cgi/admin.py:913 #: Mailman/Cgi/admin.py:1221 +#, fuzzy msgid "from %(start)s to %(end)s" msgstr "od %(start)s do %(end)s" @@ -1167,6 +1195,7 @@ msgstr "Spremeni gesla za skrbni # Mailman/Cgi/admin.py:997 #: Mailman/Cgi/admin.py:1364 +#, fuzzy msgid "" "The list administrators are the people who have ultimate control " "over\n" @@ -1359,9 +1388,11 @@ msgstr "" msgid "%(schange_to)s is already a member" msgstr " je ¾e èlan" +# Mailman/Cgi/admindb.py:364 #: Mailman/Cgi/admin.py:1620 +#, fuzzy msgid "%(schange_to)s matches banned pattern %(spat)s" -msgstr "" +msgstr " je ¾e èlan" #: Mailman/Cgi/admin.py:1622 msgid "Address %(schange_from)s changed to %(schange_to)s" @@ -1405,6 +1436,7 @@ msgid "Not subscribed" msgstr "Ni prijavljen" #: Mailman/Cgi/admin.py:1773 +#, fuzzy msgid "Ignoring changes to deleted member: %(user)s" msgstr "Prezri spremembe za izbrisanega èlana: %(user)s" @@ -1420,11 +1452,13 @@ msgstr "Napaka pri odjavi:" # Mailman/Cgi/admindb.py:111 #: Mailman/Cgi/admindb.py:235 Mailman/Cgi/admindb.py:248 +#, fuzzy msgid "%(realname)s Administrative Database" msgstr "Skrbni¹ka podatkovna baza za èlana %(realname)s" # Mailman/Cgi/admindb.py:114 #: Mailman/Cgi/admindb.py:238 +#, fuzzy msgid "%(realname)s Administrative Database Results" msgstr "Rezultati skrbni¹ke podatkovne baze za %(realname)s" @@ -1458,6 +1492,7 @@ msgid "Discard all messages marked Defer" msgstr "" #: Mailman/Cgi/admindb.py:298 +#, fuzzy msgid "all of %(esender)s's held messages." msgstr "vsa èakajoèa sporoèila po¹iljatelja %(esender)s" @@ -1483,6 +1518,7 @@ msgstr "seznam razpolo # Mailman/Cgi/admindb.py:137 #: Mailman/Cgi/admindb.py:362 +#, fuzzy msgid "You must specify a list name. Here is the %(link)s" msgstr "Navesti morate ime seznama. Tukaj je %(link)s" @@ -1576,6 +1612,7 @@ msgid "The sender is now a member of this list" msgstr "Po¹iljatelj je sedaj èlan tega seznama" #: Mailman/Cgi/admindb.py:568 +#, fuzzy msgid "Add %(esender)s to one of these sender filters:" msgstr "Dodaj %(esender)s v enega od naslednjih filtrov po¹iljateljev:" @@ -1598,6 +1635,7 @@ msgid "Rejects" msgstr "zavrnjenih" #: Mailman/Cgi/admindb.py:584 +#, fuzzy msgid "" "Ban %(esender)s from ever subscribing to this\n" " mailing list" @@ -1614,6 +1652,7 @@ msgstr "" " sporoèilo ali pa " #: Mailman/Cgi/admindb.py:591 +#, fuzzy msgid "view all messages from %(esender)s" msgstr "si oglejte vsa sporoèila od %(esender)s" @@ -1762,6 +1801,7 @@ msgstr "" " odjavljen. Ta zahteva je tako preklicana." #: Mailman/Cgi/confirm.py:178 +#, fuzzy msgid "System error, bad content: %(content)s" msgstr "Napaka sistema, neveljavna vsebina: %(content)s" @@ -1840,6 +1880,7 @@ msgstr "" # Mailman/Cgi/confirm.py:188 #: Mailman/Cgi/confirm.py:275 +#, fuzzy msgid "" "Your confirmation is required in order to continue with\n" " the subscription request to the mailing list %(listname)s.\n" @@ -1899,6 +1940,7 @@ msgstr " # Mailman/Cgi/rmlist.py:60 Mailman/Cgi/roster.py:55 # Mailman/Cgi/subscribe.py:57 #: Mailman/Cgi/confirm.py:317 +#, fuzzy msgid "Subscribe to list %(listname)s" msgstr "Prijavi se na seznam %(listname)s" @@ -1919,6 +1961,7 @@ msgstr " # Mailman/Cgi/confirm.py:271 #: Mailman/Cgi/confirm.py:386 +#, fuzzy msgid "" " You have successfully confirmed your subscription request to " "the\n" @@ -1980,6 +2023,7 @@ msgstr "Zahteva za prijavo je potrjena" # Mailman/Cgi/confirm.py:299 #: Mailman/Cgi/confirm.py:418 +#, fuzzy msgid "" " You have successfully confirmed your subscription request for\n" " \"%(addr)s\" to the %(listname)s mailing list. A separate\n" @@ -2011,6 +2055,7 @@ msgstr "Zahteva za odjavo je potrjena" # Mailman/Cgi/confirm.py:349 #: Mailman/Cgi/confirm.py:469 +#, fuzzy msgid "" " You have successfully unsubscribed from the %(listname)s " "mailing\n" @@ -2034,6 +2079,7 @@ msgstr "Ni na voljo" # Mailman/Cgi/confirm.py:372 #: Mailman/Cgi/confirm.py:498 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " unsubscription request from the mailing list %(listname)s. " @@ -2113,6 +2159,7 @@ msgstr "Zahteva za spremembo naslova potrjena" # Mailman/Cgi/confirm.py:431 #: Mailman/Cgi/confirm.py:571 +#, fuzzy msgid "" " You have successfully changed your address on the %(listname)s\n" " mailing list from %(oldaddr)s to %(newaddr)s. " @@ -2139,6 +2186,7 @@ msgstr "globalno" # Mailman/Cgi/confirm.py:459 #: Mailman/Cgi/confirm.py:605 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " change of address request for the mailing list %(listname)s. " @@ -2207,6 +2255,7 @@ msgstr "Po # Mailman/Cgi/confirm.py:525 #: Mailman/Cgi/confirm.py:674 +#, fuzzy msgid "" "The held message with the Subject:\n" " header %(subject)s could not be found. The most " @@ -2228,6 +2277,7 @@ msgstr "Objavljeno sporo # Mailman/Cgi/confirm.py:536 #: Mailman/Cgi/confirm.py:685 +#, fuzzy msgid "" " You have successfully canceled the posting of your message with\n" " the Subject: header %(subject)s to the mailing list\n" @@ -2252,6 +2302,7 @@ msgstr "" # Mailman/Cgi/confirm.py:571 #: Mailman/Cgi/confirm.py:735 +#, fuzzy msgid "" "Your confirmation is required in order to cancel the\n" " posting of your message to the mailing list %(listname)s:\n" @@ -2302,6 +2353,7 @@ msgstr " # Mailman/Cgi/confirm.py:349 #: Mailman/Cgi/confirm.py:798 +#, fuzzy msgid "" " You have successfully re-enabled your membership in the\n" " %(listname)s mailing list. You can now not available" msgstr "ni na voljo" #: Mailman/Cgi/confirm.py:846 +#, fuzzy msgid "" "Your membership in the %(realname)s mailing list is\n" " currently disabled due to excessive bounces. Your confirmation is\n" @@ -2462,16 +2516,19 @@ msgstr "Neznan seznam: %(listname)s" # Mailman/Cgi/create.py:170 #: Mailman/Cgi/create.py:218 bin/newlist:219 +#, fuzzy msgid "Bad owner email address: %(s)s" msgstr "Neveljaven e-po¹tni naslov lastnika: %(s)s" # Mailman/Cgi/create.py:101 Mailman/Cgi/create.py:174 bin/newlist:122 # bin/newlist:154 #: Mailman/Cgi/create.py:223 bin/newlist:182 bin/newlist:223 +#, fuzzy msgid "List already exists: %(listname)s" msgstr "Seznam ¾e obstaja: %(listname)s" #: Mailman/Cgi/create.py:231 bin/newlist:217 +#, fuzzy msgid "Illegal list name: %(s)s" msgstr "Nedovoljeno ime seznama: %(s)s" @@ -2486,6 +2543,7 @@ msgstr "" # Mailman/Cgi/create.py:203 bin/newlist:184 #: Mailman/Cgi/create.py:273 bin/newlist:265 +#, fuzzy msgid "Your new mailing list: %(listname)s" msgstr "Va¹ novi po¹tni seznam: %(listname)s" @@ -2496,6 +2554,7 @@ msgstr "Rezultati ustvarjanja novega po # Mailman/Cgi/create.py:218 #: Mailman/Cgi/create.py:288 +#, fuzzy msgid "" "You have successfully created the mailing list\n" " %(listname)s and notification has been sent to the list owner\n" @@ -2522,6 +2581,7 @@ msgstr "Ustvarite nov seznam" # Mailman/Cgi/create.py:249 #: Mailman/Cgi/create.py:312 +#, fuzzy msgid "Create a %(hostname)s Mailing List" msgstr "Ustvarite po¹tni seznam na %(hostname)s" @@ -2628,6 +2688,7 @@ msgstr "" # Mailman/Cgi/create.py:335 #: Mailman/Cgi/create.py:432 +#, fuzzy msgid "" "Initial list of supported languages.

                  Note that if you do not\n" " select at least one initial language, the list will use the server\n" @@ -2749,6 +2810,7 @@ msgstr "Potrebno je ime seznama." # Mailman/Cgi/edithtml.py:95 #: Mailman/Cgi/edithtml.py:154 +#, fuzzy msgid "%(realname)s -- Edit html for %(template_info)s" msgstr "%(realname)s -- Uredi html za %(template_info)s" @@ -2759,11 +2821,13 @@ msgstr "Uredi HTML : Napaka" # Mailman/Cgi/edithtml.py:100 #: Mailman/Cgi/edithtml.py:161 +#, fuzzy msgid "%(safetemplatename)s: Invalid template" msgstr "%(safetemplatename)s: Neveljavna predloga" # Mailman/Cgi/edithtml.py:105 Mailman/Cgi/edithtml.py:106 #: Mailman/Cgi/edithtml.py:166 Mailman/Cgi/edithtml.py:167 +#, fuzzy msgid "%(realname)s -- HTML Page Editing" msgstr "%(realname)s -- Urejanje HTML strani" @@ -2831,11 +2895,13 @@ msgstr "HTML uspe # Mailman/Cgi/listinfo.py:69 #: Mailman/Cgi/listinfo.py:89 +#, fuzzy msgid "%(hostname)s Mailing Lists" msgstr "Po¹tni seznami na %(hostname)s" # Mailman/Cgi/listinfo.py:103 #: Mailman/Cgi/listinfo.py:127 +#, fuzzy msgid "" "

                  There currently are no publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s." @@ -2845,6 +2911,7 @@ msgstr "" # Mailman/Cgi/listinfo.py:107 #: Mailman/Cgi/listinfo.py:131 +#, fuzzy msgid "" "

                  Below is a listing of all the public mailing lists on\n" " %(hostname)s. Click on a list name to get more information " @@ -2866,6 +2933,7 @@ msgstr "desno" # Mailman/Cgi/listinfo.py:116 #: Mailman/Cgi/listinfo.py:140 +#, fuzzy msgid "" " To visit the general information page for an unadvertised list,\n" " open a URL similar to this one, but with a '/' and the %(adj)s\n" @@ -2934,6 +3002,7 @@ msgstr "Neveljavni e-po # Mailman/Cgi/options.py:93 #: Mailman/Cgi/options.py:183 Mailman/Cgi/options.py:240 #: Mailman/Cgi/options.py:264 +#, fuzzy msgid "No such member: %(safeuser)s." msgstr "Èlan ne obstaja: %(safeuser)s." @@ -2990,6 +3059,7 @@ msgstr "" # Mailman/Cgi/options.py:187 #: Mailman/Cgi/options.py:378 +#, fuzzy msgid "List subscriptions for %(safeuser)s on %(hostname)s" msgstr "Vsa èlanstva uporabnika %(safeuser)s na %(hostname)s" @@ -3020,6 +3090,7 @@ msgid "You are already using that email address" msgstr "Ta e-po¹tni naslov je ¾e uporabljen." #: Mailman/Cgi/options.py:459 +#, fuzzy msgid "" "The new address you requested %(newaddr)s is already a member of the\n" "%(listname)s mailing list, however you have also requested a global change " @@ -3034,6 +3105,7 @@ msgstr "" # Mailman/Cgi/admindb.py:364 #: Mailman/Cgi/options.py:468 +#, fuzzy msgid "The new address is already a member: %(newaddr)s" msgstr "Novi naslov je ¾e uprabljen: %(newaddr)s" @@ -3044,6 +3116,7 @@ msgstr "Naslovi ne smejo biti prazni" # Mailman/Cgi/options.py:258 #: Mailman/Cgi/options.py:488 +#, fuzzy msgid "A confirmation message has been sent to %(newaddr)s. " msgstr "Na naslov %(newaddr)s je bilo poslano potrditveno sporoèilo. " @@ -3059,6 +3132,7 @@ msgstr "Navedli ste nedovoljen e-po # Mailman/Cgi/options.py:271 #: Mailman/Cgi/options.py:501 +#, fuzzy msgid "%(newaddr)s is already a member of the list." msgstr "%(newaddr)s je ¾e èlan tega seznama." @@ -3153,6 +3227,7 @@ msgstr "" # Mailman/Cgi/options.py:352 #: Mailman/Cgi/options.py:623 +#, fuzzy msgid "" "You have been successfully unsubscribed from the\n" " mailing list %(fqdn_listname)s. If you were receiving digest\n" @@ -3257,6 +3332,7 @@ msgstr "dan" # Mailman/Cgi/options.py:564 #: Mailman/Cgi/options.py:900 +#, fuzzy msgid "%(days)d %(units)s" msgstr "%(days)d %(units)s" @@ -3272,6 +3348,7 @@ msgstr "Ni definiranih tem" # Mailman/Cgi/options.py:606 #: Mailman/Cgi/options.py:940 +#, fuzzy msgid "" "\n" "You are subscribed to this list with the case-preserved address\n" @@ -3283,6 +3360,7 @@ msgstr "" # Mailman/Cgi/options.py:619 #: Mailman/Cgi/options.py:956 +#, fuzzy msgid "%(realname)s list: member options login page" msgstr "Seznam %(realname)s: stran za prijavo v uporabni¹ke mo¾nosti" @@ -3293,11 +3371,13 @@ msgstr "e-po # Mailman/Cgi/options.py:623 #: Mailman/Cgi/options.py:960 +#, fuzzy msgid "%(realname)s list: member options for user %(safeuser)s" msgstr "Seznam %(realname)s: mo¾nosti èlanstva za uporabnika %(safeuser)s" # Mailman/Cgi/options.py:636 #: Mailman/Cgi/options.py:986 +#, fuzzy msgid "" "In order to change your membership option, you must\n" " first log in by giving your %(extra)smembership password in the section\n" @@ -3383,6 +3463,7 @@ msgstr "" # Mailman/Cgi/options.py:787 #: Mailman/Cgi/options.py:1140 +#, fuzzy msgid "Requested topic is not valid: %(topicname)s" msgstr "Zahtevana tema ni veljavna: %(topicname)s" @@ -3417,6 +3498,7 @@ msgstr "" # Mailman/Cgi/private.py:97 #: Mailman/Cgi/private.py:109 +#, fuzzy msgid "Private Archive Error - %(msg)s" msgstr "Napaka v zasebnem arhivu - %(msg)s" @@ -3466,6 +3548,7 @@ msgstr "Rezultati brisanja po # Mailman/Cgi/rmlist.py:156 #: Mailman/Cgi/rmlist.py:187 +#, fuzzy msgid "" "You have successfully deleted the mailing list\n" " %(listname)s." @@ -3474,6 +3557,7 @@ msgstr "" " %(listname)s." #: Mailman/Cgi/rmlist.py:191 +#, fuzzy msgid "" "There were some problems deleting the mailing list\n" " %(listname)s. Contact your site administrator at " @@ -3486,6 +3570,7 @@ msgstr "" # Mailman/Cgi/rmlist.py:172 #: Mailman/Cgi/rmlist.py:208 +#, fuzzy msgid "Permanently remove mailing list %(realname)s" msgstr "Trajno odstrani po¹tni seznam %(realname)s" @@ -3561,6 +3646,7 @@ msgstr "Neveljavne mo # Mailman/Cgi/roster.py:97 #: Mailman/Cgi/roster.py:118 +#, fuzzy msgid "%(realname)s roster authentication failed." msgstr "Avtentikacija èlana %(realname)s za roster ni uspela." @@ -3632,6 +3718,7 @@ msgstr "" "prejeli sporoèilo z nadaljnjimi navodili." #: Mailman/Cgi/subscribe.py:262 +#, fuzzy msgid "" "The email address you supplied is banned from this\n" " mailing list. If you think this restriction is erroneous, please\n" @@ -3660,6 +3747,7 @@ msgstr "" # Mailman/Cgi/subscribe.py:174 #: Mailman/Cgi/subscribe.py:278 +#, fuzzy msgid "" "Confirmation from your email address is required, to prevent anyone from\n" "subscribing you without permission. Instructions are being sent to you at\n" @@ -3673,6 +3761,7 @@ msgstr "" # Mailman/Cgi/subscribe.py:183 #: Mailman/Cgi/subscribe.py:290 +#, fuzzy msgid "" "Your subscription request was deferred because %(x)s. Your request has " "been\n" @@ -3694,6 +3783,7 @@ msgid "Mailman privacy alert" msgstr "Varnostno opozorilo programa Mailman" #: Mailman/Cgi/subscribe.py:316 +#, fuzzy msgid "" "An attempt was made to subscribe your address to the mailing list\n" "%(listaddr)s. You are already subscribed to this mailing list.\n" @@ -3735,6 +3825,7 @@ msgstr "Ta seznam podpira le prejemanje izvle # Mailman/Cgi/subscribe.py:203 #: Mailman/Cgi/subscribe.py:344 +#, fuzzy msgid "You have been successfully subscribed to the %(realname)s mailing list." msgstr "Uspe¹no ste prijavljeni na po¹tni seznam %(realname)s." @@ -3867,26 +3958,32 @@ msgstr "ni na voljo" # Mailman/Cgi/create.py:95 #: Mailman/Commands/cmd_info.py:44 +#, fuzzy msgid "List name: %(listname)s" msgstr "Ime seznama: %(listname)s" #: Mailman/Commands/cmd_info.py:45 +#, fuzzy msgid "Description: %(description)s" msgstr "Opis: %(description)s" #: Mailman/Commands/cmd_info.py:46 +#, fuzzy msgid "Postings to: %(postaddr)s" msgstr "Objave na: %(postaddr)s" #: Mailman/Commands/cmd_info.py:47 +#, fuzzy msgid "List Helpbot: %(requestaddr)s" msgstr "Pomoè za seznam: %(requestaddr)s" #: Mailman/Commands/cmd_info.py:48 +#, fuzzy msgid "List Owners: %(owneraddr)s" msgstr "Lastniki seznama: %(owneraddr)s" #: Mailman/Commands/cmd_info.py:49 +#, fuzzy msgid "More information: %(listurl)s" msgstr "Dodatne informacije: %(listurl)s" @@ -3910,18 +4007,22 @@ msgstr "" # Mailman/MailCommandHandler.py:449 #: Mailman/Commands/cmd_lists.py:44 +#, fuzzy msgid "Public mailing lists at %(hostname)s:" msgstr "Javni po¹tni seznami na %(hostname)s:" #: Mailman/Commands/cmd_lists.py:66 +#, fuzzy msgid "%(i)3d. List name: %(realname)s" msgstr "%(i)3d. Ime seznama: %(realname)s" #: Mailman/Commands/cmd_lists.py:67 +#, fuzzy msgid " Description: %(description)s" msgstr " Opis: %(description)s" #: Mailman/Commands/cmd_lists.py:68 +#, fuzzy msgid " Requests to: %(requestaddr)s" msgstr " Zahteve na: %(requestaddr)s" @@ -3956,6 +4057,7 @@ msgstr "" " vedno poslan na prijavljeni naslov.\n" #: Mailman/Commands/cmd_password.py:51 Mailman/Commands/cmd_password.py:66 +#, fuzzy msgid "Your password is: %(password)s" msgstr "Va¹e geslo je: %(password)s" @@ -3963,6 +4065,7 @@ msgstr "Va #: Mailman/Commands/cmd_password.py:57 Mailman/Commands/cmd_password.py:72 #: Mailman/Commands/cmd_password.py:95 Mailman/Commands/cmd_password.py:121 #: Mailman/Commands/cmd_set.py:149 Mailman/Commands/cmd_set.py:219 +#, fuzzy msgid "You are not a member of the %(listname)s mailing list" msgstr "Niste prijavljeni na po¹tni seznam %(listname)s" @@ -4155,6 +4258,7 @@ msgstr "" " opomnika z geslom za po¹tni seznam.\n" #: Mailman/Commands/cmd_set.py:122 +#, fuzzy msgid "Bad set command: %(subcmd)s" msgstr "Napaèen set ukaz: %(subcmd)s" @@ -4189,6 +4293,7 @@ msgid "on" msgstr "vkljuèeno" #: Mailman/Commands/cmd_set.py:154 +#, fuzzy msgid " ack %(onoff)s" msgstr " ack %(onoff)s" @@ -4232,22 +4337,27 @@ msgid "due to bounces" msgstr "zaradi zavrnitev po¹te" #: Mailman/Commands/cmd_set.py:186 +#, fuzzy msgid " %(status)s (%(how)s on %(date)s)" msgstr " %(status)s (%(how)s dne %(date)s)" #: Mailman/Commands/cmd_set.py:192 +#, fuzzy msgid " myposts %(onoff)s" msgstr " myposts %(onoff)s" #: Mailman/Commands/cmd_set.py:195 +#, fuzzy msgid " hide %(onoff)s" msgstr " hide %(onoff)s" #: Mailman/Commands/cmd_set.py:199 +#, fuzzy msgid " duplicates %(onoff)s" msgstr " duplicates %(onoff)s" #: Mailman/Commands/cmd_set.py:203 +#, fuzzy msgid " reminders %(onoff)s" msgstr " reminders %(onoff)s" @@ -4258,6 +4368,7 @@ msgid "You did not give the correct password" msgstr "Vnesli ste neveljavno geslo" #: Mailman/Commands/cmd_set.py:236 Mailman/Commands/cmd_set.py:283 +#, fuzzy msgid "Bad argument: %(arg)s" msgstr "Neveljaven argument: %(arg)s" @@ -4344,6 +4455,7 @@ msgstr "" # Mailman/Gui/Digest.py:27 #: Mailman/Commands/cmd_subscribe.py:62 +#, fuzzy msgid "Bad digest specifier: %(arg)s" msgstr "Neveljaven digest ukaz: %(arg)s" @@ -4352,6 +4464,7 @@ msgid "No valid address found to subscribe" msgstr "Ni veljavnega naslova za prijavo" #: Mailman/Commands/cmd_subscribe.py:113 +#, fuzzy msgid "" "The email address you supplied is banned from this mailing list.\n" "If you think this restriction is erroneous, please contact the list\n" @@ -4396,6 +4509,7 @@ msgstr "Ta seznam podpira samo prejemanje izvle # Mailman/MailCommandHandler.py:641 #: Mailman/Commands/cmd_subscribe.py:146 +#, fuzzy msgid "" "Your subscription request has been forwarded to the list administrator\n" "at %(listowner)s for review." @@ -4435,6 +4549,7 @@ msgstr "" # Mailman/MailCommandHandler.py:359 Mailman/MailCommandHandler.py:365 # Mailman/MailCommandHandler.py:420 Mailman/MailCommandHandler.py:587 #: Mailman/Commands/cmd_unsubscribe.py:62 +#, fuzzy msgid "%(address)s is not a member of the %(listname)s mailing list" msgstr "%(address)s ni prijavljen na po¹tni seznam %(listname)s" @@ -4701,6 +4816,7 @@ msgstr "" # Mailman/Deliverer.py:42 #: Mailman/Deliverer.py:53 +#, fuzzy msgid "" "Note: Since this is a list of mailing lists, administrative\n" "notices like the password reminder will be sent to\n" @@ -4717,16 +4833,19 @@ msgstr " (Izvle # Mailman/Deliverer.py:67 #: Mailman/Deliverer.py:79 +#, fuzzy msgid "Welcome to the \"%(realname)s\" mailing list%(digmode)s" msgstr "Dobrodo¹li na po¹tni seznam \"%(realname)s\" %(digmode)s" # Mailman/Deliverer.py:76 #: Mailman/Deliverer.py:89 +#, fuzzy msgid "You have been unsubscribed from the %(realname)s mailing list" msgstr "Sedaj ste odjavljeni s po¹tnega seznama %(realname)s" # Mailman/Deliverer.py:103 #: Mailman/Deliverer.py:116 +#, fuzzy msgid "%(listfullname)s mailing list reminder" msgstr "Opomnik za po¹tni seznam %(listfullname)s" @@ -4740,6 +4859,7 @@ msgid "Hostile subscription attempt detected" msgstr "Pri¹lo je do poskusa nasilne prijave na dopisni seznam" #: Mailman/Deliverer.py:169 +#, fuzzy msgid "" "%(address)s was invited to a different mailing\n" "list, but in a deliberate malicious attempt they tried to confirm the\n" @@ -4751,6 +4871,7 @@ msgstr "" "To je bilo samo obvestilo, od vas se ne zahteva nobeno drugo dejanje." #: Mailman/Deliverer.py:188 +#, fuzzy msgid "" "You invited %(address)s to your list, but in a\n" "deliberate malicious attempt, they tried to confirm the invitation to a\n" @@ -4993,8 +5114,8 @@ msgid "" " membership.\n" "\n" "

                  You can control both the\n" -" number\n" +" number\n" " of reminders the member will receive and the\n" " Nadzirate lahko tudi\n" -" ¹tevilo\n" +" ¹tevilo\n" " opomnikov, ki jih bo èlan prejel, in njihovo\n" " No those messages too will get discarded. You may " "want\n" " to set up an\n" -" autoresponse\n" +" autoresponse\n" " message for email to the -owner and -admin address." msgstr "" "Èeprav je Mailmanov sistem za iskanje zavrnitev zelo\n" @@ -5219,8 +5340,8 @@ msgstr "" " Èe pride do tega, mo¾nost pa je nastavljena na\n" " Ne, bodo izbrisana tudi ta sporoèila. V tem primeru\n" " je morda najbolje, da oblikujete\n" -" samodejni\n" +" samodejni\n" " odgovor za vso prispelo po¹to na -owner in -admin naslov." #: Mailman/Gui/Bounce.py:147 @@ -5285,6 +5406,7 @@ msgstr "" " Program bo vedno posku¹al obvestiti èlana." #: Mailman/Gui/Bounce.py:194 +#, fuzzy msgid "" "Bad value for %(property)s: %(val)s" @@ -5349,8 +5471,8 @@ msgstr "" "

                  Filtriranje vsebine izgleda takole: ko je na seznamu\n" " objavljeno novo sporoèilo, filtriranje pa je vkljuèeno,\n" " bodo najprej posamezne priponke primerjane s\n" -" filtriranimi\n" +" filtriranimi\n" " vrstami. Èe vrsta priponke odgovarja vnosu v filtru,\n" " bo zavrnjena.\n" "\n" @@ -5369,8 +5491,8 @@ msgstr "" "\n" "

                  Nazadnje bodo vsi ostali text/html deli v\n" " sporoèilu pretvorjeni v text/plain, èe je omogoèena\n" -" mo¾nostconvert_html_to_plaintext in èe je stran konfigurirana\n" " tako, da dopu¹èa tak¹no pretvorbo." @@ -5413,8 +5535,8 @@ msgstr "" "\n" "

                  Prazne vrstice ne ¹tejejo.\n" "\n" -"

                  Glej tudi Glej tudi pass_mime_types za belo listo dovoljenih vsebin." #: Mailman/Gui/ContentFilter.py:94 @@ -5431,8 +5553,8 @@ msgid "" "Use this option to remove each message attachment that does\n" " not have a matching content type. Requirements and formats " "are\n" -" exactly like filter_mime_types.\n" "\n" "

                  Note: if you add entries to this list but don't add\n" @@ -5553,6 +5675,7 @@ msgstr "" "seznama." #: Mailman/Gui/ContentFilter.py:171 +#, fuzzy msgid "Bad MIME type ignored: %(spectype)s" msgstr "Neveljavna MIME vrsta prezrta: %(spectype)s" @@ -5678,6 +5801,7 @@ msgid "" msgstr "Ali naj Mailman sedaj po¹lje nov izvleèek, èe le-ta ni prazen?" #: Mailman/Gui/Digest.py:145 +#, fuzzy msgid "" "The next digest will be sent as volume\n" " %(volume)s, number %(number)s" @@ -5696,15 +5820,18 @@ msgid "There was no digest to send." msgstr "Ni izvleèka, ki bi ga lahko poslali." #: Mailman/Gui/GUIBase.py:173 +#, fuzzy msgid "Invalid value for variable: %(property)s" msgstr "Neveljavna vrednost za spremenljivko: %(property)s" # Mailman/Cgi/admin.py:1169 #: Mailman/Gui/GUIBase.py:178 +#, fuzzy msgid "Bad email address for option %(property)s: %(error)s" msgstr "Neveljaven e-po¹tni naslov za mo¾nost %(property)s: %(error)s" #: Mailman/Gui/GUIBase.py:204 +#, fuzzy msgid "" "The following illegal substitution variables were\n" " found in the %(property)s string:\n" @@ -5720,6 +5847,7 @@ msgstr "" " ne bo deloval pravilno." #: Mailman/Gui/GUIBase.py:218 +#, fuzzy msgid "" "Your %(property)s string appeared to\n" " have some correctable problems in its new value.\n" @@ -5826,8 +5954,8 @@ msgid "" "

                  In order to split the list ownership duties into\n" " administrators and moderators, you must\n" " set a separate moderator password,\n" -" and also provide the email\n" +" and also provide the email\n" " addresses of the list moderators. Note that the field you\n" " are changing here specifies the list administrators." msgstr "" @@ -6167,13 +6295,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -6212,13 +6340,13 @@ msgstr "" " vsebujejo veljaven povratni naslov. Èe spremenite Povratni " "naslov:\n" " bo ote¾eno po¹iljanje zasebnih odgovorov. Oglejte si Spreminjanje\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">Spreminjanje\n" " 'Povratnega naslova' je lahko nevarno za splo¹no razpravo o " "tem\n" " vpra¹anju. Preberite Spreminjanje\n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">Spreminjanje\n" " Povratnega naslova je lahko koristno za nasprotno mnenje.\n" "\n" "

                  Nekateri po¹tni seznami so omejili privilegije objavljanja\n" @@ -6244,8 +6372,8 @@ msgstr "Izrecna glava Povratni naslov:" msgid "" "This is the address set in the Reply-To: header\n" " when the reply_goes_to_list\n" +" href=\"?VARHELP=general/" +"reply_goes_to_list\">reply_goes_to_list\n" " option is set to Explicit address.\n" "\n" "

                  There are many reasons not to introduce or override the\n" @@ -6253,13 +6381,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -6282,8 +6410,8 @@ msgid "" msgstr "" "Ta naslov je nastavljen v glavi Povratni naslov:,\n" " ko je mo¾nost reply_goes_to_list\n" +" href=\"?VARHELP=general/" +"reply_goes_to_list\">reply_goes_to_list\n" " nastavljena na Izrecni naslov.\n" "\n" "

                  Obstaja veè razlogov, zakaj ni dobro prepisati izvirne " @@ -6294,13 +6422,13 @@ msgstr "" " vsebujejo veljaven povratni naslov. Èe spremenite Povratni " "naslov:\n" " bo ote¾eno po¹iljanje zasebnih odgovorov. Oglejte si Spreminjanje\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">Spreminjanje\n" " 'Povratnega naslova' je lahko nevarno za splo¹no razpravo o " "tem\n" " vpra¹anju. Preberite Spreminjanje\n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">Spreminjanje\n" " Povratnega naslova je lahko koristno za nasprotno mnenje.\n" "\n" "

                  Nekateri po¹tni seznami so omejili privilegije objavljanja\n" @@ -6372,8 +6500,8 @@ msgid "" " member list addresses, but rather to the owner of those member\n" " lists. In that case, the value of this setting is appended to\n" " the member's account name for such notices. `-owner' is the\n" -" typical choice. This setting has no effect when \"umbrella_list" -"\"\n" +" typical choice. This setting has no effect when " +"\"umbrella_list\"\n" " is \"No\"." msgstr "" "Ko mo¾nost \"umbrella_list\" doloèa ta seznam kot tak, ki\n" @@ -7147,8 +7275,8 @@ msgid "" " page.\n" "

                \n" msgstr "" -"Ko za ta dopisni seznam omogoèite poosebitev,\n" +"Ko za ta dopisni seznam omogoèite poosebitev,\n" "bodo v glavah in nogah dovoljene dodatne spremenljivke:\n" "\n" "
                • user_address - Naslov uporabnika, prikazan\n" @@ -7390,6 +7518,7 @@ msgstr "" " privoljenja te osebe." #: Mailman/Gui/Privacy.py:104 +#, fuzzy msgid "" "This section allows you to configure subscription and\n" " membership exposure policy. You can also control whether this\n" @@ -7594,8 +7723,8 @@ msgid "" " either individually or as a group. Any\n" " posting from a non-member who is not explicitly accepted,\n" " rejected, or discarded, will have their posting filtered by the\n" -" general\n" +" general\n" " non-member rules.\n" "\n" "

                  In the text boxes below, add one address per line; start the\n" @@ -7622,8 +7751,8 @@ msgstr "" "

                  Sporoèila neèlanov lahko samodejno\n" " sprejmete,\n" -" shranite za\n" +" shranite za\n" " moderacijo,\n" " zavrnete ali\n" @@ -7632,8 +7761,8 @@ msgstr "" " posamezno ali kot skupino. Vsa sporoèila neèlanov, ki jih\n" " izrecno ne sprejmete, zavrnete ali izbri¹ete, bodo filtrirana " "glede na\n" -" splo¹na\n" +" splo¹na\n" " pravila za neèlane.\n" "\n" "

                  V spodnja polja vnesite naslove, vsakega v svojo vrstico,\n" @@ -7655,6 +7784,7 @@ msgid "By default, should new list member postings be moderated?" msgstr "Ali naj bodo sporoèila novih èlanov moderirana po privzetem?" #: Mailman/Gui/Privacy.py:218 +#, fuzzy msgid "" "Each list member has a moderation flag which says\n" " whether messages from the list member can be posted directly " @@ -7706,8 +7836,8 @@ msgid "" "If a member posts this many times, within a period of time\n" " the member is automatically moderated. Use 0 to disable. " "See\n" -" member_verbosity_interval for details on the time " "period.\n" "\n" @@ -8155,8 +8285,8 @@ msgstr "" " bo po¹iljatelj primerjan s seznamom izrecno\n" " odobrenih,\n" -" shranjenih,\n" +" shranjenih,\n" " zavrnjenih in\n" " The body of the message can also be optionally scanned for\n" " Subject: and Keywords: headers, as\n" " specified by the topics_bodylines_limit\n" +" href=\"?VARHELP=topics/" +"topics_bodylines_limit\">topics_bodylines_limit\n" " configuration variable." msgstr "" "Filter za teme razvrsti vsako prejeto sporoèilo glede na\n" @@ -8494,8 +8624,8 @@ msgstr "" "

                  Prav tako lahko preverite polja\n" " Zadeva: in Kljuène besede:, èe\n" " ustrezata temam, ki jih doloèa spremenljivka topics_bodylines_limit." +" href=\"?VARHELP=topics/" +"topics_bodylines_limit\">topics_bodylines_limit." # Mailman/Gui/Topics.py:57 #: Mailman/Gui/Topics.py:72 @@ -8796,8 +8926,8 @@ msgstr "" "Prehoda ne morete oznaèiti, èe nista izpolnjeni\n" " polje za novièarski " "stre¾nik in\n" -" polje povezana\n" +" polje povezana\n" " novièarska skupina." #: Mailman/HTMLFormatter.py:49 @@ -8806,6 +8936,7 @@ msgstr "%(listinfo_link)s seznam upravlja %(owner_link)s" # Mailman/HTMLFormatter.py:55 #: Mailman/HTMLFormatter.py:57 +#, fuzzy msgid "%(realname)s administrative interface" msgstr "Skrbni¹ki vmesnik za %(realname)s" @@ -8816,6 +8947,7 @@ msgstr " (zahteva avtorizacijo)" # Mailman/HTMLFormatter.py:59 #: Mailman/HTMLFormatter.py:61 +#, fuzzy msgid "Overview of all %(hostname)s mailing lists" msgstr "Pregled vseh po¹tnih seznamov na %(hostname)s" @@ -8837,6 +8969,7 @@ msgid "; it was disabled by the list administrator" msgstr "; onemogoèil je skrbnik seznama" #: Mailman/HTMLFormatter.py:146 +#, fuzzy msgid "" "; it was disabled due to excessive bounces. The\n" " last bounce was received on %(date)s" @@ -8850,6 +8983,7 @@ msgstr "; onemogo # Mailman/HTMLFormatter.py:133 #: Mailman/HTMLFormatter.py:151 +#, fuzzy msgid "Note: your list delivery is currently disabled%(reason)s." msgstr "Opozorilo: trenutno vam je dostava po¹te onemogoèena%(reason)s." @@ -8865,6 +8999,7 @@ msgstr "skrbnik seznama" # Mailman/HTMLFormatter.py:138 #: Mailman/HTMLFormatter.py:157 +#, fuzzy msgid "" "

                  %(note)s\n" "\n" @@ -8884,6 +9019,7 @@ msgstr "" " Za vpra¹anja in pomoè se obrnite na %(mailto)s." #: Mailman/HTMLFormatter.py:169 +#, fuzzy msgid "" "

                  We have received some recent bounces from your\n" " address. Your current bounce score is %(score)s out of " @@ -8905,6 +9041,7 @@ msgstr "" # Mailman/HTMLFormatter.py:151 #: Mailman/HTMLFormatter.py:181 +#, fuzzy msgid "" "(Note - you are subscribing to a list of mailing lists, so the %(type)s " "notice will be sent to the admin address for your membership, %(addr)s.)

                  " @@ -8956,6 +9093,7 @@ msgstr "" # Mailman/HTMLFormatter.py:176 #: Mailman/HTMLFormatter.py:208 +#, fuzzy msgid "" "This is %(also)sa private list, which means that the\n" " list of members is not available to non-members." @@ -8965,6 +9103,7 @@ msgstr "" # Mailman/HTMLFormatter.py:179 #: Mailman/HTMLFormatter.py:211 +#, fuzzy msgid "" "This is %(also)sa hidden list, which means that the\n" " list of members is available only to the list administrator." @@ -8974,6 +9113,7 @@ msgstr "" # Mailman/HTMLFormatter.py:182 #: Mailman/HTMLFormatter.py:214 +#, fuzzy msgid "" "This is %(also)sa public list, which means that the\n" " list of members list is available to everyone." @@ -8992,6 +9132,7 @@ msgstr "" # Mailman/HTMLFormatter.py:190 #: Mailman/HTMLFormatter.py:222 +#, fuzzy msgid "" "

                  (Note that this is an umbrella list, intended to\n" " have only other mailing lists as members. Among other things,\n" @@ -9010,6 +9151,7 @@ msgstr "lahko" # Mailman/HTMLFormatter.py:224 #: Mailman/HTMLFormatter.py:256 +#, fuzzy msgid "" "To unsubscribe from %(realname)s, get a password reminder,\n" " or change your subscription options %(either)senter your " @@ -9047,6 +9189,7 @@ msgstr "" # Mailman/HTMLFormatter.py:244 #: Mailman/HTMLFormatter.py:277 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " members.)" @@ -9056,6 +9199,7 @@ msgstr "" # Mailman/HTMLFormatter.py:248 #: Mailman/HTMLFormatter.py:281 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " administrator.)" @@ -9130,6 +9274,7 @@ msgstr "Trenutni arhiv" # Mailman/Handlers/Acknowledge.py:62 #: Mailman/Handlers/Acknowledge.py:59 +#, fuzzy msgid "%(realname)s post acknowledgement" msgstr "Potrditev prejema sporoèila na %(realname)s" @@ -9142,6 +9287,7 @@ msgid "" msgstr "" #: Mailman/Handlers/CalcRecips.py:79 +#, fuzzy msgid "" "Your urgent message to the %(realname)s mailing list was not authorized for\n" "delivery. The original message as received by Mailman is attached.\n" @@ -9225,6 +9371,7 @@ msgstr "Sporo # Mailman/Handlers/Hold.py:83 #: Mailman/Handlers/Hold.py:84 +#, fuzzy msgid "" "Please do *not* post administrative requests to the mailing\n" "list. If you wish to subscribe, visit %(listurl)s or send a message with " @@ -9268,11 +9415,13 @@ msgstr "Objava na moderirani novi # Mailman/Handlers/Hold.py:258 #: Mailman/Handlers/Hold.py:252 +#, fuzzy msgid "Your message to %(listname)s awaits moderator approval" msgstr "Va¹e sporoèilo za %(listname)s èaka na odobritev moderatorja" # Mailman/Handlers/Hold.py:279 #: Mailman/Handlers/Hold.py:271 +#, fuzzy msgid "%(listname)s post from %(sender)s requires approval" msgstr "Objava na %(listname)s od %(sender)s èaka na odobritev" @@ -9319,6 +9468,7 @@ msgid "After content filtering, the message was empty" msgstr "Potem, ko je bila vsebina filtrirana, je sporoèilo ostalo prazno" #: Mailman/Handlers/MimeDel.py:269 +#, fuzzy msgid "" "The attached message matched the %(listname)s mailing list's content " "filtering\n" @@ -9360,6 +9510,7 @@ msgid "The attached message has been automatically discarded." msgstr "Pripeto sporoèilo je bilo samodejno zavrnjeno." #: Mailman/Handlers/Replybot.py:75 +#, fuzzy msgid "Auto-response for your message to the \"%(realname)s\" mailing list" msgstr "Samodejni odgovor na va¹e sporoèilo za dopisni seznam \"%(realname)s\"" @@ -9380,6 +9531,7 @@ msgid "HTML attachment scrubbed and removed" msgstr "HTML priponka preèi¹èena in odstranjena" #: Mailman/Handlers/Scrubber.py:234 Mailman/Handlers/Scrubber.py:259 +#, fuzzy msgid "" "An HTML attachment was scrubbed...\n" "URL: %(url)s\n" @@ -9468,6 +9620,7 @@ msgstr "" # Mailman/Handlers/ToDigest.py:148 #: Mailman/Handlers/ToDigest.py:173 +#, fuzzy msgid "%(realname)s Digest, Vol %(volume)d, Issue %(issue)d" msgstr "%(realname)s izvleèek, let %(volume)d, ¹tevilka %(issue)d" @@ -9512,6 +9665,7 @@ msgstr "Konec " # Mailman/ListAdmin.py:257 #: Mailman/ListAdmin.py:309 +#, fuzzy msgid "Posting of your message titled \"%(subject)s\"" msgstr "Objava va¹ega sporoèila \"%(subject)s\"" @@ -9526,6 +9680,7 @@ msgstr "Posredovano moderirano sporo # Mailman/ListAdmin.py:344 #: Mailman/ListAdmin.py:405 +#, fuzzy msgid "New subscription request to list %(realname)s from %(addr)s" msgstr "Nova zahteva za prijavo na seznam %(realname)s od %(addr)s" @@ -9542,6 +9697,7 @@ msgstr "Nadaljuj in po # Mailman/Cgi/confirm.py:345 #: Mailman/ListAdmin.py:466 +#, fuzzy msgid "New unsubscription request from %(realname)s by %(addr)s" msgstr "Nova zahteva za odjavo s seznama %(realname)s od %(addr)s" @@ -9557,11 +9713,13 @@ msgstr "Izvirno sporo # Mailman/ListAdmin.py:402 #: Mailman/ListAdmin.py:526 +#, fuzzy msgid "Request to mailing list %(realname)s rejected" msgstr "Zahteva za po¹tni seznam %(realname)s zavrnjena" # Mailman/MTA/Manual.py:38 #: Mailman/MTA/Manual.py:66 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been created via the through-the-web\n" "interface. In order to complete the activation of this mailing list, the\n" @@ -9588,16 +9746,19 @@ msgstr "" "èe je mo¾no, zagnati program `newaliases':\n" #: Mailman/MTA/Manual.py:82 +#, fuzzy msgid "## %(listname)s mailing list" msgstr "Dopisni seznam ## %(listname)s" # Mailman/MTA/Manual.py:66 #: Mailman/MTA/Manual.py:99 +#, fuzzy msgid "Mailing list creation request for list %(listname)s" msgstr "Zahteva za ustvarjanje po¹tnega seznama %(listname)s" # Mailman/MTA/Manual.py:81 #: Mailman/MTA/Manual.py:113 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been removed via the through-the-web\n" "interface. In order to complete the de-activation of this mailing list, " @@ -9616,6 +9777,7 @@ msgstr "" # Mailman/MTA/Manual.py:91 #: Mailman/MTA/Manual.py:123 +#, fuzzy msgid "" "\n" "To finish removing your mailing list, you must edit your /etc/aliases (or\n" @@ -9634,15 +9796,18 @@ msgstr "" # Mailman/MTA/Manual.py:109 #: Mailman/MTA/Manual.py:142 +#, fuzzy msgid "Mailing list removal request for list %(listname)s" msgstr "Zahteva za odstranitev po¹tnega seznama %(listname)s" #: Mailman/MTA/Postfix.py:442 +#, fuzzy msgid "checking permissions on %(file)s" msgstr "preverjanje dovoljenj v %(file)s" # Mailman/MTA/Postfix.py:232 #: Mailman/MTA/Postfix.py:452 +#, fuzzy msgid "%(file)s permissions must be 0664 (got %(octmode)s)" msgstr "%(file)s dovoljenja morajo biti 0664 (%(octmode)s)" @@ -9661,16 +9826,19 @@ msgstr "(popravljanje)" # Mailman/MTA/Postfix.py:241 #: Mailman/MTA/Postfix.py:470 +#, fuzzy msgid "checking ownership of %(dbfile)s" msgstr "preverjanje lastni¹tva za %(dbfile)s" # Mailman/MTA/Postfix.py:249 #: Mailman/MTA/Postfix.py:478 +#, fuzzy msgid "%(dbfile)s owned by %(owner)s (must be owned by %(user)s" msgstr "%(dbfile)s ima v lasti %(owner)s (lastnik mora biti %(user)s)" # Mailman/MTA/Postfix.py:232 #: Mailman/MTA/Postfix.py:491 +#, fuzzy msgid "%(dbfile)s permissions must be 0664 (got %(octmode)s)" msgstr "%(dbfile)s dovoljenja morajo biti 0664 (%(octmode)s)" @@ -9688,16 +9856,19 @@ msgstr "Niste prijavljeni na po # Mailman/MailList.py:614 Mailman/MailList.py:886 #: Mailman/MailList.py:999 Mailman/MailList.py:1492 +#, fuzzy msgid " from %(remote)s" msgstr " od %(remote)s" # Mailman/MailList.py:649 #: Mailman/MailList.py:1043 +#, fuzzy msgid "subscriptions to %(realname)s require moderator approval" msgstr "prijave na %(realname)s zahtevajo odobritev moderatorja" # Mailman/MailList.py:711 bin/add_members:258 #: Mailman/MailList.py:1125 bin/add_members:299 +#, fuzzy msgid "%(realname)s subscription notification" msgstr "Obvestilo o prijavi na %(realname)s" @@ -9708,6 +9879,7 @@ msgstr "odjave zahtevajo odobritev moderatorja" # Mailman/MailList.py:739 #: Mailman/MailList.py:1166 +#, fuzzy msgid "%(realname)s unsubscribe notification" msgstr "Obvestilo o odjavi s seznama %(realname)s" @@ -9731,6 +9903,7 @@ msgstr "Neveljaven potrditveni niz" # Mailman/MailList.py:860 #: Mailman/MailList.py:1396 +#, fuzzy msgid "subscriptions to %(name)s require administrator approval" msgstr "prijave na %(name)s zahtevajo odobritev skrbnika" @@ -9751,6 +9924,7 @@ msgid "Last autoresponse notification for today" msgstr "Zadnje samodejno oblikovano obvestilo danes" #: Mailman/Queue/BounceRunner.py:360 +#, fuzzy msgid "" "The attached message was received as a bounce, but either the bounce format\n" "was not recognized, or no member addresses could be extracted from it. " @@ -9842,6 +10016,7 @@ msgstr "" # Mailman/htmlformat.py:611 #: Mailman/htmlformat.py:675 +#, fuzzy msgid "Delivered by Mailman
                  version %(version)s" msgstr "Po¹ilja Mailman
                  razlièica %(version)s" @@ -9961,6 +10136,7 @@ msgid "Server Local Time" msgstr "Lokalni èas stre¾nika" #: Mailman/i18n.py:180 +#, fuzzy msgid "" "%(wday)s %(mon)s %(day)2i %(hh)02i:%(mm)02i:%(ss)02i %(tzname)s %(year)04i" msgstr "" @@ -10074,6 +10250,7 @@ msgstr "" # Mailman/Cgi/admin.py:1228 #: bin/add_members:162 bin/add_members:172 +#, fuzzy msgid "Already a member: %(member)s" msgstr "®e èlan: %(member)s" @@ -10084,11 +10261,13 @@ msgstr "Napa # Mailman/Cgi/admin.py:1232 Mailman/Cgi/admin.py:1235 #: bin/add_members:180 +#, fuzzy msgid "Bad/Invalid email address: %(member)s" msgstr "Napaèen/neveljaven e-po¹tni naslov: %(member)s" # Mailman/Cgi/admin.py:1238 #: bin/add_members:182 +#, fuzzy msgid "Hostile address (illegal characters): %(member)s" msgstr "Nedopusten naslov (nedovoljeni znaki): %(member)s" @@ -10100,14 +10279,17 @@ msgstr "Prijavljen: %(member)s" # Mailman/Cgi/admin.py:1281 #: bin/add_members:187 +#, fuzzy msgid "Subscribed: %(member)s" msgstr "Prijavljen: %(member)s" #: bin/add_members:237 +#, fuzzy msgid "Bad argument to -w/--welcome-msg: %(arg)s" msgstr "Neveljaven argument za -w/--welcome-msg: %(arg)s" #: bin/add_members:244 +#, fuzzy msgid "Bad argument to -a/--admin-notify: %(arg)s" msgstr "Neveljaven argument za -a/--admin-notify: %(arg)s" @@ -10128,6 +10310,7 @@ msgstr "" #: bin/add_members:261 bin/config_list:110 bin/export.py:271 bin/find_member:97 #: bin/inject:91 bin/list_admins:90 bin/list_members:252 bin/sync_members:222 #: cron/bumpdigests:86 +#, fuzzy msgid "No such list: %(listname)s" msgstr "Seznam ne obstaja: %(listname)s" @@ -10138,6 +10321,7 @@ msgid "Nothing to do." msgstr "Ni dejanja za izvesti." #: bin/arch:19 +#, fuzzy msgid "" "Rebuild a list's archive.\n" "\n" @@ -10233,6 +10417,7 @@ msgstr "potrebno je ime seznama" # Mailman/Cgi/rmlist.py:60 Mailman/Cgi/roster.py:55 # Mailman/Cgi/subscribe.py:57 #: bin/arch:143 bin/change_pw:107 bin/config_list:257 +#, fuzzy msgid "" "No such list \"%(listname)s\"\n" "%(e)s" @@ -10241,10 +10426,12 @@ msgstr "" "%(e)s" #: bin/arch:168 +#, fuzzy msgid "Cannot open mbox file %(mbox)s: %(msg)s" msgstr "Ni mogoèe odpreti mbox datoteke %(mbox)s: %(msg)s" #: bin/b4b5-archfix:19 +#, fuzzy msgid "" "Fix the MM2.1b4 archives.\n" "\n" @@ -10388,6 +10575,7 @@ msgstr "" " Natisni to sporoèilo s pomoèjo in se vrni v ukazni naèin.\n" #: bin/change_pw:145 +#, fuzzy msgid "Bad arguments: %(strargs)s" msgstr "Neveljavni argumenti: %(strargs)s" @@ -10397,15 +10585,18 @@ msgid "Empty list passwords are not allowed" msgstr "Prazna gesla za seznam niso dovoljena" #: bin/change_pw:181 +#, fuzzy msgid "New %(listname)s password: %(notifypassword)s" msgstr "Novo geslo za %(listname)s: %(notifypassword)s" # Mailman/Cgi/create.py:307 #: bin/change_pw:190 +#, fuzzy msgid "Your new %(listname)s list password" msgstr "Va¹e novo geslo za seznam %(listname)s" #: bin/change_pw:191 +#, fuzzy msgid "" "The site administrator at %(hostname)s has changed the password for your\n" "mailing list %(listname)s. It is now\n" @@ -10431,6 +10622,7 @@ msgstr "" " %(adminurl)s\n" #: bin/check_db:19 +#, fuzzy msgid "" "Check a list's config database file for integrity.\n" "\n" @@ -10512,6 +10704,7 @@ msgid "List:" msgstr "Seznam:" #: bin/check_db:148 +#, fuzzy msgid " %(file)s: okay" msgstr " %(file)s: v redu" @@ -10537,44 +10730,54 @@ msgstr "" "\n" #: bin/check_perms:110 +#, fuzzy msgid " checking gid and mode for %(path)s" msgstr " preverjanje gid in mode za %(path)s" #: bin/check_perms:122 +#, fuzzy msgid "%(path)s bad group (has: %(groupname)s, expected %(MAILMAN_GROUP)s)" msgstr "" "%(path)s neveljavna skupina (vsebuje: %(groupname)s, prièakovano " "%(MAILMAN_GROUP)s)" #: bin/check_perms:151 +#, fuzzy msgid "directory permissions must be %(octperms)s: %(path)s" msgstr "dovoljenje za imenik mora biti %(octperms)s: %(path)s" #: bin/check_perms:160 +#, fuzzy msgid "source perms must be %(octperms)s: %(path)s" msgstr "dovoljenje za izvorno kodo mora biti %(octperms)s: %(path)s" #: bin/check_perms:171 +#, fuzzy msgid "article db files must be %(octperms)s: %(path)s" msgstr "datoteke podatkovne baze z arhivom morajo biti %(octperms)s: %(path)s" #: bin/check_perms:183 +#, fuzzy msgid "checking mode for %(prefix)s" msgstr "preverjanje mode za %(prefix)s" #: bin/check_perms:193 +#, fuzzy msgid "WARNING: directory does not exist: %(d)s" msgstr "OPOZORILO: mapa ne obstaja: %(d)s" #: bin/check_perms:197 +#, fuzzy msgid "directory must be at least 02775: %(d)s" msgstr "imenik mora biti vsaj 02775: %(d)s" #: bin/check_perms:209 +#, fuzzy msgid "checking perms on %(private)s" msgstr "preverjanje dovoljenj na %(private)s" #: bin/check_perms:214 +#, fuzzy msgid "%(private)s must not be other-readable" msgstr "%(private)s ne sme biti kako drugaèe berljiv" @@ -10592,6 +10795,7 @@ msgid "mbox file must be at least 0660:" msgstr "mbox mora biti vsaj 0660:" #: bin/check_perms:263 +#, fuzzy msgid "%(dbdir)s \"other\" perms must be 000" msgstr "%(dbdir)s \"ostala\" dovoljenja morajo biti 000" @@ -10600,26 +10804,32 @@ msgid "checking cgi-bin permissions" msgstr "preverjanje cgi-bin dovoljenj" #: bin/check_perms:278 +#, fuzzy msgid " checking set-gid for %(path)s" msgstr " preverjanje set-gid za %(path)s" #: bin/check_perms:282 +#, fuzzy msgid "%(path)s must be set-gid" msgstr "%(path)s mora biti set-gid" #: bin/check_perms:292 +#, fuzzy msgid "checking set-gid for %(wrapper)s" msgstr "preverjanje set-gid za %(wrapper)s" #: bin/check_perms:296 +#, fuzzy msgid "%(wrapper)s must be set-gid" msgstr "%(wrapper)s mora biti set-gid" #: bin/check_perms:306 +#, fuzzy msgid "checking permissions on %(pwfile)s" msgstr "preverjanje dovoljenj v %(pwfile)s" #: bin/check_perms:315 +#, fuzzy msgid "%(pwfile)s permissions must be exactly 0640 (got %(octmode)s)" msgstr "%(pwfile)s dovoljenja morajo biti natanèno 0640 (¾e %(octmode)s)" @@ -10628,10 +10838,12 @@ msgid "checking permissions on list data" msgstr "preverjanje dovoljenj za podatke o seznamu" #: bin/check_perms:348 +#, fuzzy msgid " checking permissions on: %(path)s" msgstr " preverjanje dovoljenj v: %(path)s" #: bin/check_perms:356 +#, fuzzy msgid "file permissions must be at least 660: %(path)s" msgstr "datoteèna dovoljenja morajo biti najmanj 660: %(path)s" @@ -10716,6 +10928,7 @@ msgid "Unix-From line changed: %(lineno)d" msgstr "Unix-From vrstica spremenjena: %(lineno)d" #: bin/cleanarch:111 +#, fuzzy msgid "Bad status number: %(arg)s" msgstr "Neveljavna statusna ¹tevilka: %(arg)s" @@ -10866,10 +11079,12 @@ msgstr " izvirni naslov odstranjen:" # Mailman/Cgi/create.py:170 #: bin/clone_member:202 +#, fuzzy msgid "Not a valid email address: %(toaddr)s" msgstr "Neveljaven e-po¹tni naslov: %(toaddr)s" #: bin/clone_member:215 +#, fuzzy msgid "" "Error opening list \"%(listname)s\", skipping.\n" "%(e)s" @@ -11003,23 +11218,28 @@ msgid "legal values are:" msgstr "dovoljene vrednosti so: " #: bin/config_list:270 +#, fuzzy msgid "attribute \"%(k)s\" ignored" msgstr "atribut \"%(k)s\" prezrt" #: bin/config_list:273 +#, fuzzy msgid "attribute \"%(k)s\" changed" msgstr "atribut \"%(k)s\" spremenjen" #: bin/config_list:279 +#, fuzzy msgid "Non-standard property restored: %(k)s" msgstr "Nestandardna lastnost obnovljena: %(k)s" #: bin/config_list:288 +#, fuzzy msgid "Invalid value for property: %(k)s" msgstr "Neveljavna vrednost za lastnost: %(k)s" # Mailman/Cgi/admin.py:1169 #: bin/config_list:291 +#, fuzzy msgid "Bad email address for option %(k)s: %(v)s" msgstr "Neveljaven e-po¹tni naslov za mo¾nost %(k)s: %(v)s" @@ -11085,18 +11305,22 @@ msgstr "" " Ne prika¾e sporoèil o stanju.\n" #: bin/discard:94 +#, fuzzy msgid "Ignoring non-held message: %(f)s" msgstr "Prezrto nezadr¾ano sporoèilo: %(f)s" #: bin/discard:100 +#, fuzzy msgid "Ignoring held msg w/bad id: %(f)s" msgstr "Prezrto zadr¾ano sporoèilo z neveljavnim id: %(f)s" #: bin/discard:112 +#, fuzzy msgid "Discarded held msg #%(id)s for list %(listname)s" msgstr "Zavrnjeno zadr¾ano sporoèilo #%(id)s" #: bin/dumpdb:19 +#, fuzzy msgid "" "Dump the contents of any Mailman `database' file.\n" "\n" @@ -11169,6 +11393,7 @@ msgid "No filename given." msgstr "Ni podano ime datoteke." #: bin/dumpdb:108 +#, fuzzy msgid "Bad arguments: %(pargs)s" msgstr "Neveljavni argumenti: %(pargs)s" @@ -11395,6 +11620,7 @@ msgid "Setting web_page_url to: %(web_page_url)s" msgstr "Nastavitev web_page_url na: %(web_page_url)s" #: bin/fix_url.py:88 +#, fuzzy msgid "Setting host_name to: %(mailhost)s" msgstr "Nastavitev host_name na: %(mailhost)s" @@ -11489,6 +11715,7 @@ msgstr "" "Èe ni doloèeno, bo uporabljen standardni vhod.\n" #: bin/inject:84 +#, fuzzy msgid "Bad queue directory: %(qdir)s" msgstr "Neveljaven imenik s èakalno vrsto: %(qdir)s" @@ -11498,6 +11725,7 @@ msgid "A list name is required" msgstr "Zahtevano je ime seznama" #: bin/list_admins:20 +#, fuzzy msgid "" "List all the owners of a mailing list.\n" "\n" @@ -11543,6 +11771,7 @@ msgstr "" "V ukazni vrstici lahko doloèite veè seznamov.\n" #: bin/list_admins:97 +#, fuzzy msgid "List: %(listname)s, \tOwners: %(owners)s" msgstr "Seznam: %(listname)s, \tLastniki: %(owners)s" @@ -11687,8 +11916,8 @@ msgstr "" " Prika¾e samo navadne èlane (brez izvleèka).\n" "\n" " --digest[=kind] / -d [kind]\n" -" Prike¾e samo èlane, ki prejemajo izvleèek. Neobvezni argument \"mime" -"\" ali\n" +" Prike¾e samo èlane, ki prejemajo izvleèek. Neobvezni argument " +"\"mime\" ali\n" " \"plain\" prika¾e samo tiste èlane, ki prejemajo navedeno vrsto " "izvleèka.\n" "\n" @@ -11728,11 +11957,13 @@ msgstr "" "za njimi pa tisti z izvleèkom, vendar ne bo prikazano stanje naslova.\n" #: bin/list_members:198 +#, fuzzy msgid "Bad --nomail option: %(why)s" msgstr "Neveljavna mo¾nost --nomail: %(why)s" # Mailman/Gui/Digest.py:27 #: bin/list_members:209 +#, fuzzy msgid "Bad --digest option: %(kind)s" msgstr "Neveljavna mo¾nost --digest: %(kind)s" @@ -11746,6 +11977,7 @@ msgid "Could not open file for writing:" msgstr "Ni mogoèe odpreti datoteke za zapis:" #: bin/list_owners:20 +#, fuzzy msgid "" "List the owners of a mailing list, or all mailing lists.\n" "\n" @@ -11799,6 +12031,7 @@ msgid "" msgstr "" #: bin/mailmanctl:20 +#, fuzzy msgid "" "Primary start-up and shutdown script for Mailman's qrunner daemon.\n" "\n" @@ -11982,6 +12215,7 @@ msgstr "" " naslednjiè, ko bo vanje zapisano sporoèilo.\n" #: bin/mailmanctl:152 +#, fuzzy msgid "PID unreadable in: %(pidfile)s" msgstr "Neberljiv PID v: %(pidfile)s" @@ -11990,6 +12224,7 @@ msgid "Is qrunner even running?" msgstr "Ali je qrunner program sploh zagnan?" #: bin/mailmanctl:160 +#, fuzzy msgid "No child with pid: %(pid)s" msgstr "Ni podrednega programa s pid: %(pid)s" @@ -12016,6 +12251,7 @@ msgstr "" "zastarana qrunner zapora. Za¾enite mailmanctl z mo¾nostjo -s.\n" #: bin/mailmanctl:233 +#, fuzzy msgid "" "The master qrunner lock could not be acquired, because it appears as if " "some\n" @@ -12043,10 +12279,12 @@ msgstr "" # Mailman/Cgi/create.py:101 Mailman/Cgi/create.py:174 bin/newlist:122 # bin/newlist:154 #: bin/mailmanctl:280 cron/mailpasswds:119 +#, fuzzy msgid "Site list is missing: %(sitelistname)s" msgstr "Manjka seznam strani: %(sitelistname)s" #: bin/mailmanctl:305 +#, fuzzy msgid "Run this program as root or as the %(name)s user, or use -u." msgstr "Za¾enite program kot root ali uporabnik %(name)s, oz. uporabite -u." @@ -12056,6 +12294,7 @@ msgid "No command given." msgstr "Ukaz ni podan." #: bin/mailmanctl:339 +#, fuzzy msgid "Bad command: %(command)s" msgstr "Neveljaven ukaz: %(command)s" @@ -12080,6 +12319,7 @@ msgid "Starting Mailman's master qrunner." msgstr "Zaganjanje glavnega pritajenega programa." #: bin/mmsitepass:19 +#, fuzzy msgid "" "Set the site password, prompting from the terminal.\n" "\n" @@ -12134,6 +12374,7 @@ msgid "list creator" msgstr "lastnik seznama" #: bin/mmsitepass:86 +#, fuzzy msgid "New %(pwdesc)s password: " msgstr "Novo %(pwdesc)s geslo: " @@ -12370,6 +12611,7 @@ msgstr "" "Imena seznamov morajo biti zapisana z malimi èrkami.\n" #: bin/newlist:162 +#, fuzzy msgid "Unknown language: %(lang)s" msgstr "Neznani jezik: %(lang)s" @@ -12383,6 +12625,7 @@ msgstr "Vnesite e-po # Mailman/Cgi/create.py:307 #: bin/newlist:193 +#, fuzzy msgid "Initial %(listname)s password: " msgstr "Zaèetno geslo za seznam %(listname)s: " @@ -12393,11 +12636,12 @@ msgstr "Geslo za seznam ne sme biti prazno" #: bin/newlist:220 msgid "" -" - owner addresses need to be fully-qualified names like \"owner@example.com" -"\", not just \"owner\"." +" - owner addresses need to be fully-qualified names like \"owner@example." +"com\", not just \"owner\"." msgstr "" #: bin/newlist:244 +#, fuzzy msgid "Hit enter to notify %(listname)s owner..." msgstr "Pritisnite Enter, da obvestite lastnika seznama %(listname)s..." @@ -12525,6 +12769,7 @@ msgstr "" "imen, ki jih navede mo¾nost -l.\n" #: bin/qrunner:179 +#, fuzzy msgid "%(name)s runs the %(runnername)s qrunner" msgstr "%(name)s zaganja qrunner %(runnername)s" @@ -12659,20 +12904,24 @@ msgstr "" "\n" #: bin/remove_members:156 +#, fuzzy msgid "Could not open file for reading: %(filename)s." msgstr "Ni mogoèe odpreti datoteke za branje: %(filename)s." #: bin/remove_members:163 +#, fuzzy msgid "Error opening list %(listname)s... skipping." msgstr "Napaka pri odpiranju seznama %(listname)s... prezrto." # Mailman/Cgi/options.py:93 #: bin/remove_members:173 +#, fuzzy msgid "No such member: %(addr)s" msgstr "Èlan ne obstaja: %(addr)s" # Mailman/MTA/Manual.py:109 #: bin/remove_members:178 +#, fuzzy msgid "User `%(addr)s' removed from list: %(listname)s." msgstr "Uporabnik `%(addr)s' odstranjen s seznama: %(listname)s." @@ -12747,10 +12996,12 @@ msgstr "" "\n" #: bin/rmlist:73 bin/rmlist:76 +#, fuzzy msgid "Removing %(msg)s" msgstr "Odstranjevanje %(msg)s" #: bin/rmlist:81 +#, fuzzy msgid "%(listname)s %(msg)s not found as %(filename)s" msgstr "%(listname)s %(msg)s ni mogoèe najti kot %(filename)s" @@ -12760,6 +13011,7 @@ msgstr "%(listname)s %(msg)s ni mogo # Mailman/Cgi/rmlist.py:60 Mailman/Cgi/roster.py:55 # Mailman/Cgi/subscribe.py:57 #: bin/rmlist:105 +#, fuzzy msgid "No such list (or list already deleted): %(listname)s" msgstr "Seznam ne obstaja (ali pa je ¾e izbrisan): %(listname)s" @@ -12769,6 +13021,7 @@ msgstr "Seznam ne obstaja (ali pa je # Mailman/Cgi/rmlist.py:60 Mailman/Cgi/roster.py:55 # Mailman/Cgi/subscribe.py:57 #: bin/rmlist:108 +#, fuzzy msgid "No such list: %(listname)s. Removing its residual archives." msgstr "Seznam ne obstaja: %(listname)s. Odstranjevanje ostankov arhiva." @@ -12832,6 +13085,7 @@ msgstr "" "Primer: show_qfiles qfiles/shunt/*.pck\n" #: bin/sync_members:19 +#, fuzzy msgid "" "Synchronize a mailing list's membership with a flat file.\n" "\n" @@ -12965,6 +13219,7 @@ msgstr "" " Obvezno. Doloèa seznam, ki bo sinhroniziran.\n" #: bin/sync_members:115 +#, fuzzy msgid "Bad choice: %(yesno)s" msgstr "Neveljavna izbira: %(yesno)s" @@ -12982,6 +13237,7 @@ msgid "No argument to -f given" msgstr "Argument -f ni podan" #: bin/sync_members:172 +#, fuzzy msgid "Illegal option: %(opt)s" msgstr "Nedovoljena mo¾nost: %(opt)s" @@ -12995,6 +13251,7 @@ msgid "Must have a listname and a filename" msgstr "Imeni seznama in datoteke sta obvezni" #: bin/sync_members:191 +#, fuzzy msgid "Cannot read address file: %(filename)s: %(msg)s" msgstr "Ni mogoèe brati datoteke z naslovi: %(filename)s: %(msg)s" @@ -13012,14 +13269,17 @@ msgid "You must fix the preceding invalid addresses first." msgstr "Najprej morate popraviti prej¹nje neveljavne naslove." #: bin/sync_members:264 +#, fuzzy msgid "Added : %(s)s" msgstr "Dodano: %(s)s" #: bin/sync_members:289 +#, fuzzy msgid "Removed: %(s)s" msgstr "Odstranjeno: %(s)s" #: bin/transcheck:19 +#, fuzzy msgid "" "\n" "Check a given Mailman translation, making sure that variables and\n" @@ -13127,6 +13387,7 @@ msgstr "" "prestavljeno, ki pa ne sme biti qfiles/shunt.\n" #: bin/unshunt:85 +#, fuzzy msgid "" "Cannot unshunt message %(filebase)s, skipping:\n" "%(e)s" @@ -13135,6 +13396,7 @@ msgstr "" "%(e)s" #: bin/update:20 +#, fuzzy msgid "" "Perform all necessary upgrades.\n" "\n" @@ -13172,15 +13434,18 @@ msgstr "" # Mailman/Cgi/create.py:101 Mailman/Cgi/create.py:174 bin/newlist:122 # bin/newlist:154 #: bin/update:107 +#, fuzzy msgid "Fixing language templates: %(listname)s" msgstr "Popravljanje jezikovnih predlog: %(listname)s" # Mailman/Cgi/create.py:203 bin/newlist:184 #: bin/update:196 bin/update:711 +#, fuzzy msgid "WARNING: could not acquire lock for list: %(listname)s" msgstr "OPOZORILO: ni mogoèe najti zapore za seznam: %(listname)s" #: bin/update:215 +#, fuzzy msgid "Resetting %(n)s BYBOUNCEs disabled addrs with no bounce info" msgstr "" "Ponastavitev %(n)s BYBOUNCEs onemogoèenih naslovov brez podatkov o zavrnitvah" @@ -13199,6 +13464,7 @@ msgstr "" "nadaljuje." #: bin/update:255 +#, fuzzy msgid "" "\n" "%(listname)s has both public and private mbox archives. Since this list\n" @@ -13242,6 +13508,7 @@ msgid "- updating old private mbox file" msgstr "- posodobitev stare zasebne datoteke po¹tnega predala" #: bin/update:295 +#, fuzzy msgid "" " unknown file in the way, moving\n" " %(o_pri_mbox_file)s\n" @@ -13258,6 +13525,7 @@ msgid "- updating old public mbox file" msgstr "- posodobitev stare javne datoteke po¹tnega predala" #: bin/update:317 +#, fuzzy msgid "" " unknown file in the way, moving\n" " %(o_pub_mbox_file)s\n" @@ -13287,18 +13555,22 @@ msgid "- %(o_tmpl)s doesn't exist, leaving untouched" msgstr "- obstajata tako %(o_tmpl)s kot %(n_tmpl)s, oboje je nedotaknjeno" #: bin/update:396 +#, fuzzy msgid "removing directory %(src)s and everything underneath" msgstr "odstranjevanje imenika %(src)s in celotne vsebine" #: bin/update:399 +#, fuzzy msgid "removing %(src)s" msgstr "odstranjevanje %(src)s" #: bin/update:403 +#, fuzzy msgid "Warning: couldn't remove %(src)s -- %(rest)s" msgstr "Opozorilo: ni mogoèe odstraniti %(src)s -- %(rest)s" #: bin/update:408 +#, fuzzy msgid "couldn't remove old file %(pyc)s -- %(rest)s" msgstr "ni mogoèe odstraniti stare datoteke %(pyc)s -- %(rest)s" @@ -13373,6 +13645,7 @@ msgstr "kon # Mailman/Cgi/create.py:203 bin/newlist:184 #: bin/update:691 +#, fuzzy msgid "Updating mailing list: %(listname)s" msgstr "Posodobitev po¹tnega seznama: %(listname)s" @@ -13433,6 +13706,7 @@ msgid "No updates are necessary." msgstr "Posodobitve niso potrebne." #: bin/update:796 +#, fuzzy msgid "" "Downgrade detected, from version %(hexlversion)s to version %(hextversion)s\n" "This is probably not safe.\n" @@ -13444,10 +13718,12 @@ msgstr "" "Izhod." #: bin/update:801 +#, fuzzy msgid "Upgrading from version %(hexlversion)s to %(hextversion)s" msgstr "Nadgradnja razlièice %(hexlversion)s na %(hextversion)s" #: bin/update:810 +#, fuzzy msgid "" "\n" "ERROR:\n" @@ -13724,6 +14000,7 @@ msgstr "" # Mailman/Cgi/create.py:203 bin/newlist:184 #: bin/withlist:175 +#, fuzzy msgid "Unlocking (but not saving) list: %(listname)s" msgstr "Odklepanje (ne pa shranjevanje) seznama: %(listname)s" @@ -13733,6 +14010,7 @@ msgstr "Zaklju # Mailman/Cgi/create.py:203 bin/newlist:184 #: bin/withlist:188 +#, fuzzy msgid "Loading list %(listname)s" msgstr "Nalaganje seznama %(listname)s" @@ -13746,6 +14024,7 @@ msgstr "(odklenjeno)" # Mailman/Cgi/create.py:203 bin/newlist:184 #: bin/withlist:197 +#, fuzzy msgid "Unknown list: %(listname)s" msgstr "Neznan seznam: %(listname)s" @@ -13759,18 +14038,22 @@ msgid "--all requires --run" msgstr "--all zahteva --run" #: bin/withlist:266 +#, fuzzy msgid "Importing %(module)s..." msgstr "Uva¾anje %(module)s..." #: bin/withlist:270 +#, fuzzy msgid "Running %(module)s.%(callable)s()..." msgstr "Izvajanje %(module)s.%(callable)s()..." #: bin/withlist:291 +#, fuzzy msgid "The variable `m' is the %(listname)s MailList instance" msgstr "Spremenljivka `m' je MailList primer za %(listname)s" #: cron/bumpdigests:19 +#, fuzzy msgid "" "Increment the digest volume number and reset the digest number to one.\n" "\n" @@ -13798,6 +14081,7 @@ msgstr "" "ni podanih, bodo ponastavljene v vseh seznamih.\n" #: cron/checkdbs:20 +#, fuzzy msgid "" "Check for pending admin requests and mail the list owners if necessary.\n" "\n" @@ -13824,6 +14108,7 @@ msgid "" msgstr "" #: cron/checkdbs:121 +#, fuzzy msgid "%(count)d %(realname)s moderator request(s) waiting" msgstr "%(count)d %(realname)s èakajoèih moderatorskih zahtev" @@ -13852,6 +14137,7 @@ msgstr "" "Èakajoèa sporoèila:" #: cron/checkdbs:169 +#, fuzzy msgid "" "From: %(sender)s on %(date)s\n" "Subject: %(subject)s\n" @@ -13883,6 +14169,7 @@ msgid "" msgstr "" #: cron/disabled:20 +#, fuzzy msgid "" "Process disabled members, recommended once per day.\n" "\n" @@ -14008,6 +14295,7 @@ msgstr "" "\n" #: cron/mailpasswds:19 +#, fuzzy msgid "" "Send password reminders for all lists to all users.\n" "\n" @@ -14062,10 +14350,12 @@ msgstr "Geslo // URL" # Mailman/Deliverer.py:103 #: cron/mailpasswds:222 +#, fuzzy msgid "%(host)s mailing list memberships reminder" msgstr "Opomnik o èlanstvu na po¹tnem seznamu na %(host)s" #: cron/nightly_gzip:19 +#, fuzzy msgid "" "Re-generate the Pipermail gzip'd archive flat files.\n" "\n" @@ -14161,8 +14451,8 @@ msgstr "" #~ " applied" #~ msgstr "" #~ "Besedilo, ki bo vkljuèeno v vsako\n" -#~ " obvestilo o zavrnitvi in bo\n" #~ " poslano moderiranim uporabnikom s tega seznama." diff --git a/messages/sr/LC_MESSAGES/mailman.po b/messages/sr/LC_MESSAGES/mailman.po index 4a2fc004..8d0d6997 100755 --- a/messages/sr/LC_MESSAGES/mailman.po +++ b/messages/sr/LC_MESSAGES/mailman.po @@ -68,10 +68,12 @@ msgid "

                  Currently, there are no archives.

                  " msgstr "

                  Тренутно нема архива.

                  " #: Mailman/Archiver/HyperArch.py:821 +#, fuzzy msgid "Gzip'd Text%(sz)s" msgstr "Гзип-ован ТекÑÑ‚ %(sz)s" #: Mailman/Archiver/HyperArch.py:826 +#, fuzzy msgid "Text%(sz)s" msgstr "ТекÑÑ‚ %(sz)s" @@ -144,18 +146,22 @@ msgid "Third" msgstr "Трећи" #: Mailman/Archiver/HyperArch.py:938 +#, fuzzy msgid "%(ord)s quarter %(year)i" msgstr "%(ord)s четвртина %(godine)i" #: Mailman/Archiver/HyperArch.py:945 +#, fuzzy msgid "%(month)s %(year)i" msgstr "%(month)s %(year)i" #: Mailman/Archiver/HyperArch.py:950 +#, fuzzy msgid "The Week Of Monday %(day)i %(month)s %(year)i" msgstr "Ðедјеља од понедјељка %(day)i %(month)s %(year)i" #: Mailman/Archiver/HyperArch.py:954 +#, fuzzy msgid "%(day)i %(month)s %(year)i" msgstr "%(day)i %(month)s %(year)i" @@ -186,6 +192,7 @@ msgid "Pickling archive state into " msgstr "ПоÑтављање Ñтања архиве" #: Mailman/Archiver/pipermail.py:453 +#, fuzzy msgid "Updating index files for archive [%(archive)s]" msgstr "Ðжурирање индекÑних фајлова за архиву [%(archive)s]" @@ -194,6 +201,7 @@ msgid " Thread" msgstr "Стабло" #: Mailman/Archiver/pipermail.py:597 +#, fuzzy msgid "#%(counter)05d %(msgid)s" msgstr "#%(counter)05d %(msgid)s" @@ -258,6 +266,7 @@ msgstr "ÐдминиÑтратор" #: Mailman/Cgi/options.py:101 Mailman/Cgi/private.py:108 #: Mailman/Cgi/rmlist.py:75 Mailman/Cgi/roster.py:59 #: Mailman/Cgi/subscribe.py:67 +#, fuzzy msgid "No such list %(safelistname)s" msgstr "Ðема лиÑте %(safelistname)s" @@ -330,6 +339,7 @@ msgstr "" "док не решите проблем.%(rm)r" #: Mailman/Cgi/admin.py:268 +#, fuzzy msgid "%(hostname)s mailing lists - Admin Links" msgstr "%(hostname)s лиÑте Ñлања - админиÑтраторÑке везе" @@ -342,12 +352,14 @@ msgid "Mailman" msgstr "Mailman" #: Mailman/Cgi/admin.py:310 +#, fuzzy msgid "" "

                  There currently are no publicly-advertised %(mailmanlink)s\n" " mailing lists on %(hostname)s." msgstr "

                  Тренутно нема јавно доÑтупних лиÑта на %(hostname)s.

                  " #: Mailman/Cgi/admin.py:316 +#, fuzzy msgid "" "

                  Below is the collection of publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s. Click on a list\n" @@ -363,6 +375,7 @@ msgid "right " msgstr "деÑно" #: Mailman/Cgi/admin.py:325 +#, fuzzy msgid "" "To visit the administrators configuration page for an\n" " unadvertised list, open a URL similar to this one, but with a '/' " @@ -407,6 +420,7 @@ msgid "No valid variable name found." msgstr "Ðије пронађена иÑправна променљива." #: Mailman/Cgi/admin.py:395 +#, fuzzy msgid "" "%(realname)s Mailing list Configuration Help\n" "
                  %(varname)s Option" @@ -415,6 +429,7 @@ msgstr "" "
                  %(varname)s Опција" #: Mailman/Cgi/admin.py:402 +#, fuzzy msgid "Mailman %(varname)s List Option Help" msgstr "Помоћ за: %(varname)s " @@ -434,14 +449,17 @@ msgstr "" " " #: Mailman/Cgi/admin.py:431 +#, fuzzy msgid "return to the %(categoryname)s options page." msgstr "повратак у лиÑту опција категорије %(categoryname)s o" #: Mailman/Cgi/admin.py:446 +#, fuzzy msgid "%(realname)s Administration (%(label)s)" msgstr "ÐдминиÑтрација: %(realname)s (%(label)s)" #: Mailman/Cgi/admin.py:447 +#, fuzzy msgid "%(realname)s mailing list administration
                  %(label)s Section" msgstr "админиÑтрација лиÑте Ñлања %(realname)s
                  Секција %(label)s" @@ -525,6 +543,7 @@ msgid "Value" msgstr "ВриједноÑÑ‚" #: Mailman/Cgi/admin.py:668 +#, fuzzy msgid "" "Badly formed options entry:\n" " %(record)s" @@ -625,10 +644,12 @@ msgid "Move rule down" msgstr "" #: Mailman/Cgi/admin.py:870 +#, fuzzy msgid "
                  (Edit %(varname)s)" msgstr "
                  (Промјена: %(varname)s)" #: Mailman/Cgi/admin.py:872 +#, fuzzy msgid "
                  (Details for %(varname)s)" msgstr "
                  (Детаљи за: %(varname)s)" @@ -669,6 +690,7 @@ msgid "(help)" msgstr "(помоћ)" #: Mailman/Cgi/admin.py:930 +#, fuzzy msgid "Find member %(link)s:" msgstr "Проналажење члана %(link)s:" @@ -681,10 +703,12 @@ msgid "Bad regular expression: " msgstr "Погрешан ÑиÑтематÑки израз:" #: Mailman/Cgi/admin.py:1013 +#, fuzzy msgid "%(allcnt)s members total, %(membercnt)s shown" msgstr "Укупно чланова: %(allcnt)s, чланова приказано: %(membercnt)s" #: Mailman/Cgi/admin.py:1016 +#, fuzzy msgid "%(allcnt)s members total" msgstr "Укупно чланова: %(allcnt)s." @@ -1005,6 +1029,7 @@ msgid "Change list ownership passwords" msgstr "Промјена лозинке за влаÑника лиÑте" #: Mailman/Cgi/admin.py:1364 +#, fuzzy msgid "" "The list administrators are the people who have ultimate control " "over\n" @@ -1035,8 +1060,8 @@ msgstr "" "на лиÑту.\n" "

                  Да би подијелили поÑлове управљања лиÑтом на админиÑтраторе и\n" "модераторе, морате поÑтавити поÑебну модераторÑку лозинку у доња\n" -"поља, и такође обезбједити е-адреÑе моредатора лиÑте у \n" +"поља, и такође обезбједити е-адреÑе моредатора лиÑте у \n" "Ñекцији Ñа општим опцијама." #: Mailman/Cgi/admin.py:1383 @@ -1176,8 +1201,9 @@ msgid "%(schange_to)s is already a member" msgstr " је већ члан" #: Mailman/Cgi/admin.py:1620 +#, fuzzy msgid "%(schange_to)s matches banned pattern %(spat)s" -msgstr "" +msgstr " је већ члан" #: Mailman/Cgi/admin.py:1622 msgid "Address %(schange_from)s changed to %(schange_to)s" @@ -1216,6 +1242,7 @@ msgid "Not subscribed" msgstr "Ðије-Ñу упиÑан-и" #: Mailman/Cgi/admin.py:1773 +#, fuzzy msgid "Ignoring changes to deleted member: %(user)s" msgstr "ИгнориÑање промјена за иÑкљученог члана: %(user)s" @@ -1228,12 +1255,14 @@ msgid "Error Unsubscribing:" msgstr "Грешка при иÑпиÑу:" #: Mailman/Cgi/admindb.py:235 Mailman/Cgi/admindb.py:248 +#, fuzzy msgid "%(realname)s Administrative Database" -msgstr "" +msgstr "ÐдминиÑтрација: %(realname)s (%(label)s)" #: Mailman/Cgi/admindb.py:238 +#, fuzzy msgid "%(realname)s Administrative Database Results" -msgstr "" +msgstr "ÐдминиÑтрација: %(realname)s (%(label)s)" #: Mailman/Cgi/admindb.py:251 msgid "There are no pending requests." @@ -1260,8 +1289,9 @@ msgid "Discard all messages marked Defer" msgstr "" #: Mailman/Cgi/admindb.py:298 +#, fuzzy msgid "all of %(esender)s's held messages." -msgstr "" +msgstr "Ñве задржане поруке." #: Mailman/Cgi/admindb.py:303 msgid "a single held message." @@ -1280,6 +1310,7 @@ msgid "list of available mailing lists." msgstr "лиÑта доÑтупних лиÑта Ñлања." #: Mailman/Cgi/admindb.py:362 +#, fuzzy msgid "You must specify a list name. Here is the %(link)s" msgstr "Морате унијети име лиÑте. Овдје је %(link)s" @@ -1383,6 +1414,7 @@ msgid "Rejects" msgstr "Одбацивање" #: Mailman/Cgi/admindb.py:584 +#, fuzzy msgid "" "Ban %(esender)s from ever subscribing to this\n" " mailing list" @@ -1399,6 +1431,7 @@ msgstr "" " или можете " #: Mailman/Cgi/admindb.py:591 +#, fuzzy msgid "view all messages from %(esender)s" msgstr "прегледати Ñве поруке од: %(esender)s" @@ -1513,6 +1546,7 @@ msgid "" msgstr "" #: Mailman/Cgi/confirm.py:178 +#, fuzzy msgid "System error, bad content: %(content)s" msgstr "СиÑтемÑка грешка, погрешан Ñадржај: %(content)s" @@ -1602,6 +1636,7 @@ msgid "Preferred language:" msgstr "Језик: " #: Mailman/Cgi/confirm.py:317 +#, fuzzy msgid "Subscribe to list %(listname)s" msgstr "Пријава на лиÑту %(listname)s" @@ -1985,14 +2020,17 @@ msgid "Unknown virtual host: %(safehostname)s" msgstr "Ðепозната лиÑта: %(listname)s" #: Mailman/Cgi/create.py:218 bin/newlist:219 +#, fuzzy msgid "Bad owner email address: %(s)s" msgstr "Лоша адреÑа влаÑника лиÑте: %(s)s" #: Mailman/Cgi/create.py:223 bin/newlist:182 bin/newlist:223 +#, fuzzy msgid "List already exists: %(listname)s" msgstr "ЛиÑта Ñа називом %(listname)s већ поÑтоји" #: Mailman/Cgi/create.py:231 bin/newlist:217 +#, fuzzy msgid "Illegal list name: %(s)s" msgstr "Ðедозвољено име лиÑте: %(s)s" @@ -2005,6 +2043,7 @@ msgstr "" "Молимо Ð²Ð°Ñ Ð´Ð° контактирате админиÑтратора Ñајта за помоћ." #: Mailman/Cgi/create.py:273 bin/newlist:265 +#, fuzzy msgid "Your new mailing list: %(listname)s" msgstr "Ваша нова лиÑта Ñлања: %(listname)s" @@ -2013,6 +2052,7 @@ msgid "Mailing list creation results" msgstr "Резултати отварања лиÑте " #: Mailman/Cgi/create.py:288 +#, fuzzy msgid "" "You have successfully created the mailing list\n" " %(listname)s and notification has been sent to the list owner\n" @@ -2035,6 +2075,7 @@ msgid "Create another list" msgstr "Креирајте другу лиÑту" #: Mailman/Cgi/create.py:312 +#, fuzzy msgid "Create a %(hostname)s Mailing List" msgstr "Отварање лиÑте Ñлања ( %(hostname)s)" @@ -2299,16 +2340,19 @@ msgid "HTML successfully updated." msgstr "HTML је уÑпјешно оÑвјежен." #: Mailman/Cgi/listinfo.py:89 +#, fuzzy msgid "%(hostname)s Mailing Lists" msgstr "ЛиÑте Ñлања у оквиру %(hostname)s" #: Mailman/Cgi/listinfo.py:127 +#, fuzzy msgid "" "

                  There currently are no publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s." -msgstr "" +msgstr "

                  Тренутно нема јавно доÑтупних лиÑта на %(hostname)s.

                  " #: Mailman/Cgi/listinfo.py:131 +#, fuzzy msgid "" "

                  Below is a listing of all the public mailing lists on\n" " %(hostname)s. Click on a list name to get more information " @@ -2327,12 +2371,20 @@ msgid "right" msgstr "деÑно" #: Mailman/Cgi/listinfo.py:140 +#, fuzzy msgid "" " To visit the general information page for an unadvertised list,\n" " open a URL similar to this one, but with a '/' and the %(adj)s\n" " list name appended.\n" "

                  List administrators, you can visit " msgstr "" +"Да биÑте поÑетили админиÑтраторÑку Ñтраницу за \n" +"подешавања приватне лиÑте, отворите везу Ñличну овој \n" +"али Ñа '/' и %(extra)slist ÑуфикÑом у називу. Ðко имате \n" +"одговарајуће надлежноÑти, можете и отворити " +"новулиÑту Ñлања.\n" +"\n" +"

                  ОÑновне информације могу Ñе пронаћи на " #: Mailman/Cgi/listinfo.py:145 msgid "the list admin overview page" @@ -2382,6 +2434,7 @@ msgstr "Лоша е-адреÑа" #: Mailman/Cgi/options.py:183 Mailman/Cgi/options.py:240 #: Mailman/Cgi/options.py:264 +#, fuzzy msgid "No such member: %(safeuser)s." msgstr "Ðема кориÑника %(safeuser)s." @@ -2462,14 +2515,16 @@ msgid "" msgstr "" #: Mailman/Cgi/options.py:468 +#, fuzzy msgid "The new address is already a member: %(newaddr)s" -msgstr "" +msgstr "%(newaddr)s већ је члан лиÑте." #: Mailman/Cgi/options.py:474 msgid "Addresses may not be blank" msgstr "ÐдреÑе не могу бити празне" #: Mailman/Cgi/options.py:488 +#, fuzzy msgid "A confirmation message has been sent to %(newaddr)s. " msgstr "Порука за потврду је поÑлана на %(newaddr)s. " @@ -2482,6 +2537,7 @@ msgid "Illegal email address provided" msgstr "УнеÑена је недозвољена адреÑа" #: Mailman/Cgi/options.py:501 +#, fuzzy msgid "%(newaddr)s is already a member of the list." msgstr "%(newaddr)s већ је члан лиÑте." @@ -2644,16 +2700,18 @@ msgid "" msgstr "" #: Mailman/Cgi/options.py:956 +#, fuzzy msgid "%(realname)s list: member options login page" -msgstr "" +msgstr "Страна за уређивање кориÑничких опција" #: Mailman/Cgi/options.py:957 msgid "email address and " msgstr "е-адреÑа и" #: Mailman/Cgi/options.py:960 +#, fuzzy msgid "%(realname)s list: member options for user %(safeuser)s" -msgstr "" +msgstr "Страна за уређивање кориÑничких опција" #: Mailman/Cgi/options.py:986 msgid "" @@ -2713,6 +2771,7 @@ msgid "" msgstr "<недоÑтаје>" #: Mailman/Cgi/options.py:1140 +#, fuzzy msgid "Requested topic is not valid: %(topicname)s" msgstr "Тема: %(topicname)s не поÑтоји" @@ -2741,6 +2800,7 @@ msgid "Private archive - \"./\" and \"../\" not allowed in URL." msgstr "" #: Mailman/Cgi/private.py:109 +#, fuzzy msgid "Private Archive Error - %(msg)s" msgstr "Грешка у приватној архиви - %(msg)s" @@ -2778,10 +2838,14 @@ msgid "Mailing list deletion results" msgstr "Резултати бриÑања лиÑте Ñлања" #: Mailman/Cgi/rmlist.py:187 +#, fuzzy msgid "" "You have successfully deleted the mailing list\n" " %(listname)s." msgstr "" +"УÑпјешно Ñте отворили лиÑту Ñлања\n" +" %(listname)s, обавјештење је поÑлано путем е-поште\n" +" %(owner)s. Сада можете:" #: Mailman/Cgi/rmlist.py:191 msgid "" @@ -2792,8 +2856,9 @@ msgid "" msgstr "" #: Mailman/Cgi/rmlist.py:208 +#, fuzzy msgid "Permanently remove mailing list %(realname)s" -msgstr "" +msgstr "Ваша нова лиÑта Ñлања: %(listname)s" #: Mailman/Cgi/rmlist.py:209 #, fuzzy @@ -2844,8 +2909,9 @@ msgid "Invalid options to CGI script" msgstr "" #: Mailman/Cgi/roster.py:118 +#, fuzzy msgid "%(realname)s roster authentication failed." -msgstr "" +msgstr "Пријављивање није уÑпјело." #: Mailman/Cgi/subscribe.py:128 msgid "You must supply a valid email address." @@ -2977,6 +3043,7 @@ msgid "This list only supports digest delivery." msgstr "Ова лиÑта подржава Ñамо доÑтаву порука путем прегледа." #: Mailman/Cgi/subscribe.py:344 +#, fuzzy msgid "You have been successfully subscribed to the %(realname)s mailing list." msgstr "УÑпјешно Ñте Ñе учланили на лиÑту %(realname)s." @@ -3081,26 +3148,32 @@ msgid "n/a" msgstr "непознато" #: Mailman/Commands/cmd_info.py:44 +#, fuzzy msgid "List name: %(listname)s" msgstr "Име лиÑте %(listname)s" #: Mailman/Commands/cmd_info.py:45 +#, fuzzy msgid "Description: %(description)s" msgstr "ОпиÑ: %(description)s" #: Mailman/Commands/cmd_info.py:46 +#, fuzzy msgid "Postings to: %(postaddr)s" msgstr "Слање на: %(postaddr)s" #: Mailman/Commands/cmd_info.py:47 +#, fuzzy msgid "List Helpbot: %(requestaddr)s" msgstr "Помоћ: %(requestaddr)s" #: Mailman/Commands/cmd_info.py:48 +#, fuzzy msgid "List Owners: %(owneraddr)s" msgstr "ВлаÑници лиÑте: %(owneraddr)s" #: Mailman/Commands/cmd_info.py:49 +#, fuzzy msgid "More information: %(listurl)s" msgstr "Више информација: %(listurl)s" @@ -3120,20 +3193,24 @@ msgid "" msgstr "" #: Mailman/Commands/cmd_lists.py:44 +#, fuzzy msgid "Public mailing lists at %(hostname)s:" msgstr "Јавне лиÑте Ñлања на %(hostname)s:" #: Mailman/Commands/cmd_lists.py:66 +#, fuzzy msgid "%(i)3d. List name: %(realname)s" msgstr "%(i)3d. Име лиÑте: %(realname)s" #: Mailman/Commands/cmd_lists.py:67 +#, fuzzy msgid " Description: %(description)s" msgstr " ÐžÐ¿Ð¸Ñ : %(description)s" #: Mailman/Commands/cmd_lists.py:68 +#, fuzzy msgid " Requests to: %(requestaddr)s" -msgstr "" +msgstr "Помоћ: %(requestaddr)s" #: Mailman/Commands/cmd_password.py:17 msgid "" @@ -3154,14 +3231,16 @@ msgid "" msgstr "" #: Mailman/Commands/cmd_password.py:51 Mailman/Commands/cmd_password.py:66 +#, fuzzy msgid "Your password is: %(password)s" msgstr "Ваша лозинка је: %(password)s" #: Mailman/Commands/cmd_password.py:57 Mailman/Commands/cmd_password.py:72 #: Mailman/Commands/cmd_password.py:95 Mailman/Commands/cmd_password.py:121 #: Mailman/Commands/cmd_set.py:149 Mailman/Commands/cmd_set.py:219 +#, fuzzy msgid "You are not a member of the %(listname)s mailing list" -msgstr "" +msgstr "Ви Ñте члан ове лиÑте!" #: Mailman/Commands/cmd_password.py:85 Mailman/Commands/cmd_password.py:111 msgid "" @@ -3351,8 +3430,9 @@ msgid "You did not give the correct password" msgstr "" #: Mailman/Commands/cmd_set.py:236 Mailman/Commands/cmd_set.py:283 +#, fuzzy msgid "Bad argument: %(arg)s" -msgstr "" +msgstr "Погрешни аргументи: %(strargs)s" #: Mailman/Commands/cmd_set.py:241 Mailman/Commands/cmd_set.py:261 msgid "Not authenticated" @@ -3413,8 +3493,9 @@ msgid "" msgstr "" #: Mailman/Commands/cmd_subscribe.py:62 +#, fuzzy msgid "Bad digest specifier: %(arg)s" -msgstr "" +msgstr "Погрешни аргументи: %(strargs)s" #: Mailman/Commands/cmd_subscribe.py:92 msgid "No valid address found to subscribe" @@ -3476,8 +3557,9 @@ msgid "" msgstr "" #: Mailman/Commands/cmd_unsubscribe.py:62 +#, fuzzy msgid "%(address)s is not a member of the %(listname)s mailing list" -msgstr "" +msgstr "Ви Ñте члан ове лиÑте!" #: Mailman/Commands/cmd_unsubscribe.py:69 msgid "" @@ -3722,16 +3804,19 @@ msgid " (Digest mode)" msgstr "" #: Mailman/Deliverer.py:79 +#, fuzzy msgid "Welcome to the \"%(realname)s\" mailing list%(digmode)s" -msgstr "" +msgstr "ÐутоматÑки одговор на вашу поруку: " #: Mailman/Deliverer.py:89 +#, fuzzy msgid "You have been unsubscribed from the %(realname)s mailing list" -msgstr "" +msgstr "УÑпјешно Ñте Ñе учланили на лиÑту %(realname)s." #: Mailman/Deliverer.py:116 +#, fuzzy msgid "%(listfullname)s mailing list reminder" -msgstr "" +msgstr "Отварање лиÑте Ñлања ( %(hostname)s)" #: Mailman/Deliverer.py:144 msgid "No reason given" @@ -3935,8 +4020,8 @@ msgid "" " membership.\n" "\n" "

                  You can control both the\n" -" number\n" +" number\n" " of reminders the member will receive and the\n" " No those messages too will get discarded. You may " "want\n" " to set up an\n" -" autoresponse\n" +" autoresponse\n" " message for email to the -owner and -admin address." msgstr "" @@ -4206,8 +4291,8 @@ msgid "" "Use this option to remove each message attachment that does\n" " not have a matching content type. Requirements and formats " "are\n" -" exactly like filter_mime_types.\n" "\n" "

                  Note: if you add entries to this list but don't add\n" @@ -4502,8 +4587,8 @@ msgid "" "

                  In order to split the list ownership duties into\n" " administrators and moderators, you must\n" " set a separate moderator password,\n" -" and also provide the email\n" +" and also provide the email\n" " addresses of the list moderators. Note that the field you\n" " are changing here specifies the list administrators." msgstr "" @@ -4519,8 +4604,8 @@ msgstr "" "на лиÑту.\n" "

                  Да би подијелили поÑлове управљања лиÑтом на админиÑтраторе и\n" "модераторе, морате поÑтавити поÑебну модераторÑку лозинку у доња\n" -"поља, и такође обезбједити е-адреÑе моредатора лиÑте у \n" +"поља, и такође обезбједити е-адреÑе моредатора лиÑте у \n" "Ñекцији Ñа општим опцијама." #: Mailman/Gui/General.py:102 @@ -4765,13 +4850,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -4796,8 +4881,8 @@ msgstr "" msgid "" "This is the address set in the Reply-To: header\n" " when the reply_goes_to_list\n" +" href=\"?VARHELP=general/" +"reply_goes_to_list\">reply_goes_to_list\n" " option is set to Explicit address.\n" "\n" "

                  There are many reasons not to introduce or override the\n" @@ -4805,13 +4890,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -4870,8 +4955,8 @@ msgid "" " member list addresses, but rather to the owner of those member\n" " lists. In that case, the value of this setting is appended to\n" " the member's account name for such notices. `-owner' is the\n" -" typical choice. This setting has no effect when \"umbrella_list" -"\"\n" +" typical choice. This setting has no effect when " +"\"umbrella_list\"\n" " is \"No\"." msgstr "" @@ -5728,8 +5813,8 @@ msgid "" " either individually or as a group. Any\n" " posting from a non-member who is not explicitly accepted,\n" " rejected, or discarded, will have their posting filtered by the\n" -" general\n" +" general\n" " non-member rules.\n" "\n" "

                  In the text boxes below, add one address per line; start the\n" @@ -5788,8 +5873,8 @@ msgid "" "If a member posts this many times, within a period of time\n" " the member is automatically moderated. Use 0 to disable. " "See\n" -" member_verbosity_interval for details on the time " "period.\n" "\n" @@ -6354,8 +6439,8 @@ msgid "" "

                  The body of the message can also be optionally scanned for\n" " Subject: and Keywords: headers, as\n" " specified by the topics_bodylines_limit\n" +" href=\"?VARHELP=topics/" +"topics_bodylines_limit\">topics_bodylines_limit\n" " configuration variable." msgstr "" @@ -6564,14 +6649,16 @@ msgid "%(listinfo_link)s list run by %(owner_link)s" msgstr "" #: Mailman/HTMLFormatter.py:57 +#, fuzzy msgid "%(realname)s administrative interface" -msgstr "" +msgstr "ÐдминиÑтрација: %(realname)s (%(label)s)" #: Mailman/HTMLFormatter.py:58 msgid " (requires authorization)" msgstr " (неопходна ауторизација) " #: Mailman/HTMLFormatter.py:61 +#, fuzzy msgid "Overview of all %(hostname)s mailing lists" msgstr "Преглед Ñвих лиÑта на %(hostname)s" @@ -6916,8 +7003,9 @@ msgid "Your message to %(listname)s awaits moderator approval" msgstr "" #: Mailman/Handlers/Hold.py:271 +#, fuzzy msgid "%(listname)s post from %(sender)s requires approval" -msgstr "" +msgstr "Слање на лиÑту захтјева потврду." #: Mailman/Handlers/Hold.py:278 msgid "" @@ -7004,6 +7092,7 @@ msgid "HTML attachment scrubbed and removed" msgstr "HTML прилог је прочишћен и иÑкључен " #: Mailman/Handlers/Scrubber.py:234 Mailman/Handlers/Scrubber.py:259 +#, fuzzy msgid "" "An HTML attachment was scrubbed...\n" "URL: %(url)s\n" @@ -7117,8 +7206,9 @@ msgid "Forward of moderated message" msgstr "ПроÑлијеђивање модериÑане поруке" #: Mailman/ListAdmin.py:405 +#, fuzzy msgid "New subscription request to list %(realname)s from %(addr)s" -msgstr "" +msgstr "Захтјев за иÑÐ¿Ð¸Ñ Ð¿Ð¾Ñ‚Ð²Ñ€Ñ’ÐµÐ½." #: Mailman/ListAdmin.py:432 msgid "Subscription request" @@ -7130,8 +7220,9 @@ msgid "via admin approval" msgstr "ÐаÑтави Ñа чекањем одобрења" #: Mailman/ListAdmin.py:466 +#, fuzzy msgid "New unsubscription request from %(realname)s by %(addr)s" -msgstr "" +msgstr "Захтјев за иÑÐ¿Ð¸Ñ Ð¿Ð¾Ñ‚Ð²Ñ€Ñ’ÐµÐ½." #: Mailman/ListAdmin.py:490 msgid "Unsubscription request" @@ -7142,8 +7233,9 @@ msgid "Original Message" msgstr "Изворна порука" #: Mailman/ListAdmin.py:526 +#, fuzzy msgid "Request to mailing list %(realname)s rejected" -msgstr "" +msgstr "Ваша нова лиÑта Ñлања: %(listname)s" #: Mailman/MTA/Manual.py:66 msgid "" @@ -7168,8 +7260,9 @@ msgid "## %(listname)s mailing list" msgstr "Отварање лиÑте Ñлања ( %(hostname)s)" #: Mailman/MTA/Manual.py:99 +#, fuzzy msgid "Mailing list creation request for list %(listname)s" -msgstr "" +msgstr "Резултати отварања лиÑте " #: Mailman/MTA/Manual.py:113 msgid "" @@ -7193,12 +7286,14 @@ msgid "" msgstr "" #: Mailman/MTA/Manual.py:142 +#, fuzzy msgid "Mailing list removal request for list %(listname)s" -msgstr "" +msgstr "Учитавање лиÑте %(listname)s" #: Mailman/MTA/Postfix.py:442 +#, fuzzy msgid "checking permissions on %(file)s" -msgstr "" +msgstr " провјеравање гид-а и мода за %(path)s" #: Mailman/MTA/Postfix.py:452 msgid "%(file)s permissions must be 0664 (got %(octmode)s)" @@ -7214,8 +7309,9 @@ msgid "(fixing)" msgstr "(поправљање)" #: Mailman/MTA/Postfix.py:470 +#, fuzzy msgid "checking ownership of %(dbfile)s" -msgstr "" +msgstr " провјеравање гид-а и мода за %(path)s" #: Mailman/MTA/Postfix.py:478 msgid "%(dbfile)s owned by %(owner)s (must be owned by %(user)s" @@ -7244,20 +7340,23 @@ msgid "subscriptions to %(realname)s require moderator approval" msgstr "" #: Mailman/MailList.py:1125 bin/add_members:299 +#, fuzzy msgid "%(realname)s subscription notification" -msgstr "" +msgstr "Обавештење о прекомерном Ñлању" #: Mailman/MailList.py:1145 msgid "unsubscriptions require moderator approval" msgstr "" #: Mailman/MailList.py:1166 +#, fuzzy msgid "%(realname)s unsubscribe notification" -msgstr "" +msgstr "Обавештење о прекомерном Ñлању" #: Mailman/MailList.py:1328 +#, fuzzy msgid "%(realname)s address change notification" -msgstr "" +msgstr "Обавештење о прекомерном Ñлању" #: Mailman/MailList.py:1362 #, fuzzy @@ -7513,6 +7612,7 @@ msgid "" msgstr "" #: bin/add_members:162 bin/add_members:172 +#, fuzzy msgid "Already a member: %(member)s" msgstr "%(member)s је већ члан" @@ -7521,10 +7621,12 @@ msgid "Bad/Invalid email address: blank line" msgstr "Лоша е-адреÑа: празна линија" #: bin/add_members:180 +#, fuzzy msgid "Bad/Invalid email address: %(member)s" msgstr "Лоша е-адреÑа: %(member)s" #: bin/add_members:182 +#, fuzzy msgid "Hostile address (illegal characters): %(member)s" msgstr "Лоша адреÑа (недозвољени знакови): %(member)s" @@ -7534,16 +7636,19 @@ msgid "Invited: %(member)s" msgstr "Учлањен: %(member)s" #: bin/add_members:187 +#, fuzzy msgid "Subscribed: %(member)s" msgstr "Учлањен: %(member)s" #: bin/add_members:237 +#, fuzzy msgid "Bad argument to -w/--welcome-msg: %(arg)s" -msgstr "" +msgstr "Погрешни аргументи: %(strargs)s" #: bin/add_members:244 +#, fuzzy msgid "Bad argument to -a/--admin-notify: %(arg)s" -msgstr "" +msgstr "Погрешни аргументи: %(strargs)s" #: bin/add_members:252 msgid "Cannot read both digest and normal members from standard input." @@ -7556,6 +7661,7 @@ msgstr "" #: bin/add_members:261 bin/config_list:110 bin/export.py:271 bin/find_member:97 #: bin/inject:91 bin/list_admins:90 bin/list_members:252 bin/sync_members:222 #: cron/bumpdigests:86 +#, fuzzy msgid "No such list: %(listname)s" msgstr "Ðема лиÑте под називом %(listname)s" @@ -7619,6 +7725,7 @@ msgid "listname is required" msgstr "име лиÑте је обавезно." #: bin/arch:143 bin/change_pw:107 bin/config_list:257 +#, fuzzy msgid "" "No such list \"%(listname)s\"\n" "%(e)s" @@ -7708,6 +7815,7 @@ msgid "" msgstr "" #: bin/change_pw:145 +#, fuzzy msgid "Bad arguments: %(strargs)s" msgstr "Погрешни аргументи: %(strargs)s" @@ -7716,10 +7824,12 @@ msgid "Empty list passwords are not allowed" msgstr "Морате имати лозинку за лиÑту" #: bin/change_pw:181 +#, fuzzy msgid "New %(listname)s password: %(notifypassword)s" msgstr "Ðова лозинка за лиÑту %(listname)s: %(notifypassword)s" #: bin/change_pw:190 +#, fuzzy msgid "Your new %(listname)s list password" msgstr "Ваша нова лозинка за лиÑту %(listname)s" @@ -7784,6 +7894,7 @@ msgid "List:" msgstr "ЛиÑта:" #: bin/check_db:148 +#, fuzzy msgid " %(file)s: okay" msgstr " %(file)s: у реду" @@ -7799,6 +7910,7 @@ msgid "" msgstr "" #: bin/check_perms:110 +#, fuzzy msgid " checking gid and mode for %(path)s" msgstr " провјеравање гид-а и мода за %(path)s" @@ -7819,8 +7931,9 @@ msgid "article db files must be %(octperms)s: %(path)s" msgstr "" #: bin/check_perms:183 +#, fuzzy msgid "checking mode for %(prefix)s" -msgstr "" +msgstr " провјеравање гид-а и мода за %(path)s" #: bin/check_perms:193 msgid "WARNING: directory does not exist: %(d)s" @@ -7831,8 +7944,9 @@ msgid "directory must be at least 02775: %(d)s" msgstr "" #: bin/check_perms:209 +#, fuzzy msgid "checking perms on %(private)s" -msgstr "" +msgstr " провјеравање гид-а и мода за %(path)s" #: bin/check_perms:214 msgid "%(private)s must not be other-readable" @@ -7860,24 +7974,27 @@ msgid "checking cgi-bin permissions" msgstr "" #: bin/check_perms:278 +#, fuzzy msgid " checking set-gid for %(path)s" -msgstr "" +msgstr " провјеравање гид-а и мода за %(path)s" #: bin/check_perms:282 msgid "%(path)s must be set-gid" msgstr "" #: bin/check_perms:292 +#, fuzzy msgid "checking set-gid for %(wrapper)s" -msgstr "" +msgstr " провјеравање гид-а и мода за %(path)s" #: bin/check_perms:296 msgid "%(wrapper)s must be set-gid" msgstr "" #: bin/check_perms:306 +#, fuzzy msgid "checking permissions on %(pwfile)s" -msgstr "" +msgstr " провјеравање гид-а и мода за %(path)s" #: bin/check_perms:315 msgid "%(pwfile)s permissions must be exactly 0640 (got %(octmode)s)" @@ -7888,8 +8005,9 @@ msgid "checking permissions on list data" msgstr "" #: bin/check_perms:348 +#, fuzzy msgid " checking permissions on: %(path)s" -msgstr "" +msgstr " провјеравање гид-а и мода за %(path)s" #: bin/check_perms:356 msgid "file permissions must be at least 660: %(path)s" @@ -7946,8 +8064,9 @@ msgid "Unix-From line changed: %(lineno)d" msgstr "" #: bin/cleanarch:111 +#, fuzzy msgid "Bad status number: %(arg)s" -msgstr "" +msgstr "Погрешни аргументи: %(strargs)s" #: bin/cleanarch:167 msgid "%(messages)d messages found" @@ -8044,14 +8163,18 @@ msgid " original address removed:" msgstr "" #: bin/clone_member:202 +#, fuzzy msgid "Not a valid email address: %(toaddr)s" -msgstr "" +msgstr "Лоша е-адреÑа: %(member)s" #: bin/clone_member:215 +#, fuzzy msgid "" "Error opening list \"%(listname)s\", skipping.\n" "%(e)s" msgstr "" +"Ðема лиÑте под називом \"%(listname)s\"\n" +"%(e)s" #: bin/config_list:20 msgid "" @@ -8140,8 +8263,11 @@ msgid "Invalid value for property: %(k)s" msgstr "" #: bin/config_list:291 +#, fuzzy msgid "Bad email address for option %(k)s: %(v)s" msgstr "" +"Ðеправилно формирана Ñтавка:\n" +" %(record)s" #: bin/config_list:348 msgid "Only one of -i or -o is allowed" @@ -8193,8 +8319,9 @@ msgid "Ignoring non-held message: %(f)s" msgstr "ИгнориÑање промјена за иÑкљученог члана: %(user)s" #: bin/discard:100 +#, fuzzy msgid "Ignoring held msg w/bad id: %(f)s" -msgstr "" +msgstr "ИгнориÑање промјена за иÑкљученог члана: %(user)s" #: bin/discard:112 #, fuzzy @@ -8243,8 +8370,9 @@ msgid "No filename given." msgstr "Ðије дат назив фајла." #: bin/dumpdb:108 +#, fuzzy msgid "Bad arguments: %(pargs)s" -msgstr "" +msgstr "Погрешни аргументи: %(strargs)s" #: bin/dumpdb:118 msgid "Please specify either -p or -m." @@ -8494,8 +8622,9 @@ msgid "" msgstr "" #: bin/list_admins:97 +#, fuzzy msgid "List: %(listname)s, \tOwners: %(owners)s" -msgstr "" +msgstr "ВлаÑници лиÑте: %(owneraddr)s" #: bin/list_lists:19 msgid "" @@ -8604,8 +8733,9 @@ msgid "Bad --nomail option: %(why)s" msgstr "" #: bin/list_members:209 +#, fuzzy msgid "Bad --digest option: %(kind)s" -msgstr "" +msgstr "Прегледи - опције" #: bin/list_members:213 bin/list_members:217 bin/list_members:221 #: bin/list_members:225 @@ -8789,6 +8919,7 @@ msgid "" msgstr "" #: bin/mailmanctl:280 cron/mailpasswds:119 +#, fuzzy msgid "Site list is missing: %(sitelistname)s" msgstr "ÐедоÑтаје Ñајт лиÑте: %(sitelistname)s" @@ -8858,6 +8989,7 @@ msgid "list creator" msgstr "покретач лиÑте" #: bin/mmsitepass:86 +#, fuzzy msgid "New %(pwdesc)s password: " msgstr "Ðова лозинка (%(pwdesc)s):" @@ -9016,6 +9148,7 @@ msgid "" msgstr "" #: bin/newlist:162 +#, fuzzy msgid "Unknown language: %(lang)s" msgstr "Ðепознат језик: %(lang)s" @@ -9028,6 +9161,7 @@ msgid "Enter the email of the person running the list: " msgstr "УнеÑите е-адреÑу оÑобе која покреће лиÑту: " #: bin/newlist:193 +#, fuzzy msgid "Initial %(listname)s password: " msgstr "Почетна лозинка за лиÑту %(listname)s" @@ -9037,8 +9171,8 @@ msgstr "Мора бити изабрана лозинка за лиÑту" #: bin/newlist:220 msgid "" -" - owner addresses need to be fully-qualified names like \"owner@example.com" -"\", not just \"owner\"." +" - owner addresses need to be fully-qualified names like \"owner@example." +"com\", not just \"owner\"." msgstr "" #: bin/newlist:244 @@ -9202,14 +9336,17 @@ msgid "" msgstr "" #: bin/remove_members:156 +#, fuzzy msgid "Could not open file for reading: %(filename)s." -msgstr "" +msgstr "ÐеуÑпјешно отварање фајла за пиÑање:" #: bin/remove_members:163 +#, fuzzy msgid "Error opening list %(listname)s... skipping." -msgstr "" +msgstr "Учитавање лиÑте %(listname)s" #: bin/remove_members:173 +#, fuzzy msgid "No such member: %(addr)s" msgstr "Ðема члана: %(addr)s" @@ -9269,6 +9406,7 @@ msgid "" msgstr "" #: bin/rmlist:73 bin/rmlist:76 +#, fuzzy msgid "Removing %(msg)s" msgstr "Уклањање %(msg)s" @@ -9277,12 +9415,14 @@ msgid "%(listname)s %(msg)s not found as %(filename)s" msgstr "" #: bin/rmlist:105 +#, fuzzy msgid "No such list (or list already deleted): %(listname)s" -msgstr "" +msgstr "Ðема лиÑте под називом %(listname)s" #: bin/rmlist:108 +#, fuzzy msgid "No such list: %(listname)s. Removing its residual archives." -msgstr "" +msgstr "Ðема лиÑте под називом %(listname)s" #: bin/rmlist:112 msgid "Not removing archives. Reinvoke with -a to remove them." @@ -9396,6 +9536,7 @@ msgid "" msgstr "" #: bin/sync_members:115 +#, fuzzy msgid "Bad choice: %(yesno)s" msgstr "Погрешан избор: %(yesno)s" @@ -9412,6 +9553,7 @@ msgid "No argument to -f given" msgstr "" #: bin/sync_members:172 +#, fuzzy msgid "Illegal option: %(opt)s" msgstr "Погрешна опција: %(opt)s" @@ -9440,10 +9582,12 @@ msgid "You must fix the preceding invalid addresses first." msgstr "" #: bin/sync_members:264 +#, fuzzy msgid "Added : %(s)s" msgstr "Додан: %(s)s" #: bin/sync_members:289 +#, fuzzy msgid "Removed: %(s)s" msgstr "Уклоњен: %(s)s" @@ -9548,8 +9692,9 @@ msgid "" msgstr "" #: bin/update:107 +#, fuzzy msgid "Fixing language templates: %(listname)s" -msgstr "" +msgstr "Ваша нова лиÑта Ñлања: %(listname)s" #: bin/update:196 bin/update:711 msgid "WARNING: could not acquire lock for list: %(listname)s" @@ -9641,6 +9786,7 @@ msgid "removing directory %(src)s and everything underneath" msgstr "" #: bin/update:399 +#, fuzzy msgid "removing %(src)s" msgstr "уклањање %(src)s" @@ -9705,8 +9851,9 @@ msgid "done" msgstr "завршено" #: bin/update:691 +#, fuzzy msgid "Updating mailing list: %(listname)s" -msgstr "" +msgstr "Ваша нова лиÑта Ñлања: %(listname)s" #: bin/update:694 msgid "Updating Usenet watermarks" @@ -9911,14 +10058,16 @@ msgid "" msgstr "" #: bin/withlist:175 +#, fuzzy msgid "Unlocking (but not saving) list: %(listname)s" -msgstr "" +msgstr "Ðепозната лиÑта: %(listname)s" #: bin/withlist:179 msgid "Finalizing" msgstr "Завршавање" #: bin/withlist:188 +#, fuzzy msgid "Loading list %(listname)s" msgstr "Учитавање лиÑте %(listname)s" @@ -9931,6 +10080,7 @@ msgid "(unlocked)" msgstr "(откључано)" #: bin/withlist:197 +#, fuzzy msgid "Unknown list: %(listname)s" msgstr "Ðепозната лиÑта: %(listname)s" @@ -9943,14 +10093,17 @@ msgid "--all requires --run" msgstr "--all захтјева -run" #: bin/withlist:266 +#, fuzzy msgid "Importing %(module)s..." msgstr "Увођење %(module)s..." #: bin/withlist:270 +#, fuzzy msgid "Running %(module)s.%(callable)s()..." msgstr "Извршавање %(module)s.%(callable)s()..." #: bin/withlist:291 +#, fuzzy msgid "The variable `m' is the %(listname)s MailList instance" msgstr "Варијабла `m' је %(listname)s инÑтанца" @@ -10014,6 +10167,7 @@ msgstr "" "Поруке на чекању:" #: cron/checkdbs:169 +#, fuzzy msgid "" "From: %(sender)s on %(date)s\n" "Subject: %(subject)s\n" @@ -10144,6 +10298,7 @@ msgid "Password // URL" msgstr "Лозинка // УРЛ" #: cron/mailpasswds:222 +#, fuzzy msgid "%(host)s mailing list memberships reminder" msgstr "ПодÑјетник лиÑта Ñлања %(host)s " diff --git a/messages/sv/LC_MESSAGES/mailman.po b/messages/sv/LC_MESSAGES/mailman.po index 51664e31..2f8bbf00 100755 --- a/messages/sv/LC_MESSAGES/mailman.po +++ b/messages/sv/LC_MESSAGES/mailman.po @@ -92,11 +92,13 @@ msgstr "

                  Arkivet # Mailman/Archiver/HyperArch.py:676 #: Mailman/Archiver/HyperArch.py:821 +#, fuzzy msgid "Gzip'd Text%(sz)s" msgstr "Gzippad text%(sz)s" # Mailman/Archiver/HyperArch.py:681 #: Mailman/Archiver/HyperArch.py:826 +#, fuzzy msgid "Text%(sz)s" msgstr "Text%(sz)s" @@ -201,23 +203,27 @@ msgstr "Tredje" # Mailman/Archiver/HyperArch.py:793 #: Mailman/Archiver/HyperArch.py:938 +#, fuzzy msgid "%(ord)s quarter %(year)i" msgstr "%(ord)s kvartal %(year)i" # Mailman/Archiver/HyperArch.py:800 #: Mailman/Archiver/HyperArch.py:945 +#, fuzzy msgid "%(month)s %(year)i" msgstr "%(month)s %(year)i" # Mailman/Archiver/HyperArch.py:805 #: Mailman/Archiver/HyperArch.py:950 +#, fuzzy msgid "The Week Of Monday %(day)i %(month)s %(year)i" msgstr "Veckan med måndag %(day)i %(month)s %(year)i" -# Mailman/Archiver/HyperArch.py:809 +# Mailman/Archiver/HyperArch.py:800 #: Mailman/Archiver/HyperArch.py:954 +#, fuzzy msgid "%(day)i %(month)s %(year)i" -msgstr "" +msgstr "%(month)s %(year)i" # Mailman/Archiver/HyperArch.py:906 #: Mailman/Archiver/HyperArch.py:1054 @@ -226,11 +232,13 @@ msgstr "Bygger inneh # Mailman/Archiver/HyperArch.py:1168 #: Mailman/Archiver/HyperArch.py:1319 +#, fuzzy msgid "Updating HTML for article %(seq)s" msgstr "Uppdaterar HTML för artikel %(seq)s" # Mailman/Archiver/HyperArch.py:1174 #: Mailman/Archiver/HyperArch.py:1326 +#, fuzzy msgid "article file %(filename)s is missing!" msgstr "artikelfil %(filename)s saknas!" @@ -255,6 +263,7 @@ msgstr "Lagrar arkivets tillst # Mailman/Archiver/pipermail.py:418 # Mailman/Archiver/pipermail.py:418 #: Mailman/Archiver/pipermail.py:453 +#, fuzzy msgid "Updating index files for archive [%(archive)s]" msgstr "Uppdaterar indexfiler för arkivet [%(archive)s]" @@ -264,8 +273,6 @@ msgstr "Uppdaterar indexfiler f msgid " Thread" msgstr " Tråd" -# Mailman/Archiver/pipermail.py:554 -# Mailman/Archiver/pipermail.py:556 #: Mailman/Archiver/pipermail.py:597 msgid "#%(counter)05d %(msgid)s" msgstr "" @@ -323,6 +330,7 @@ msgstr "stoppad" # Mailman/Bouncer.py:239 #: Mailman/Bouncer.py:304 +#, fuzzy msgid " The last bounce received from you was dated %(date)s" msgstr " Det sist mottagna returmeddelandet från dig var daterat %(date)s" @@ -374,6 +382,7 @@ msgstr "Administrat #: Mailman/Cgi/options.py:101 Mailman/Cgi/private.py:108 #: Mailman/Cgi/rmlist.py:75 Mailman/Cgi/roster.py:59 #: Mailman/Cgi/subscribe.py:67 +#, fuzzy msgid "No such list %(safelistname)s" msgstr "Listan finns inte: %(safelistname)s" @@ -467,6 +476,7 @@ msgstr "" # Mailman/Cgi/admin.py:212 # Mailman/Cgi/admin.py:212 #: Mailman/Cgi/admin.py:268 +#, fuzzy msgid "%(hostname)s mailing lists - Admin Links" msgstr "E-postlistor på %(hostname)s - administrativa länkar" @@ -485,6 +495,7 @@ msgstr "Mailman" # Mailman/Cgi/admin.py:248 # Mailman/Cgi/admin.py:248 #: Mailman/Cgi/admin.py:310 +#, fuzzy msgid "" "

                  There currently are no publicly-advertised %(mailmanlink)s\n" " mailing lists on %(hostname)s." @@ -495,6 +506,7 @@ msgstr "" # Mailman/Cgi/admin.py:254 # Mailman/Cgi/admin.py:254 #: Mailman/Cgi/admin.py:316 +#, fuzzy msgid "" "

                  Below is the collection of publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s. Click on a list\n" @@ -514,6 +526,7 @@ msgstr "r # Mailman/Cgi/admin.py:263 # Mailman/Cgi/admin.py:263 #: Mailman/Cgi/admin.py:325 +#, fuzzy msgid "" "To visit the administrators configuration page for an\n" " unadvertised list, open a URL similar to this one, but with a '/' " @@ -575,6 +588,7 @@ msgstr "Hittade inget giltigt variabelnamn." # Mailman/Cgi/admin.py:332 # Mailman/Cgi/admin.py:332 #: Mailman/Cgi/admin.py:395 +#, fuzzy msgid "" "%(realname)s Mailing list Configuration Help\n" "
                  %(varname)s Option" @@ -585,6 +599,7 @@ msgstr "" # Mailman/Cgi/admin.py:339 # Mailman/Cgi/admin.py:339 #: Mailman/Cgi/admin.py:402 +#, fuzzy msgid "Mailman %(varname)s List Option Help" msgstr "Inställning: %(varname)s" @@ -614,12 +629,14 @@ msgstr "g # Mailman/Cgi/admin.py:382 # Mailman/Cgi/admin.py:383 #: Mailman/Cgi/admin.py:446 +#, fuzzy msgid "%(realname)s Administration (%(label)s)" msgstr "%(realname)s Administration (%(label)s)" # Mailman/Cgi/admin.py:383 # Mailman/Cgi/admin.py:384 #: Mailman/Cgi/admin.py:447 +#, fuzzy msgid "%(realname)s mailing list administration
                  %(label)s Section" msgstr "%(realname)s administration
                  %(label)s" @@ -740,6 +757,7 @@ msgstr "V # Mailman/Cgi/admin.py:608 # Mailman/Cgi/admin.py:609 #: Mailman/Cgi/admin.py:668 +#, fuzzy msgid "" "Badly formed options entry:\n" " %(record)s" @@ -893,6 +911,7 @@ msgstr "
                  (Detaljer f # Mailman/Cgi/admin.py:744 # Mailman/Cgi/admin.py:747 #: Mailman/Cgi/admin.py:872 +#, fuzzy msgid "
                  (Details for %(varname)s)" msgstr "
                  (Detaljer för %(varname)s)" @@ -948,6 +967,7 @@ msgstr "(hj # Mailman/Cgi/admin.py:785 # Mailman/Cgi/admin.py:788 #: Mailman/Cgi/admin.py:930 +#, fuzzy msgid "Find member %(link)s:" msgstr "Finn medlem %(link)s:" @@ -966,12 +986,14 @@ msgstr "Ogiltigt regexp-uttryck: " # Mailman/Cgi/admin.py:854 # Mailman/Cgi/admin.py:860 #: Mailman/Cgi/admin.py:1013 +#, fuzzy msgid "%(allcnt)s members total, %(membercnt)s shown" msgstr "Totalt %(allcnt)s medlemmar, bara %(membercnt)s visas." # Mailman/Cgi/admin.py:857 # Mailman/Cgi/admin.py:863 #: Mailman/Cgi/admin.py:1016 +#, fuzzy msgid "%(allcnt)s members total" msgstr "Totalt %(allcnt)s medlemmar" @@ -1219,6 +1241,7 @@ msgstr "

                  F # Mailman/Cgi/admin.py:1044 # Mailman/Cgi/admin.py:1049 #: Mailman/Cgi/admin.py:1221 +#, fuzzy msgid "from %(start)s to %(end)s" msgstr "från %(start)s till %(end)s" @@ -1476,6 +1499,7 @@ msgstr " # Mailman/Cgi/admin.py:1131 # Mailman/Cgi/admin.py:1145 #: Mailman/Cgi/admin.py:1364 +#, fuzzy msgid "" "The list administrators are the people who have ultimate control " "over\n" @@ -1697,9 +1721,12 @@ msgstr "" msgid "%(schange_to)s is already a member" msgstr " är redan medlem" +# Mailman/Cgi/admindb.py:732 +# Mailman/Cgi/admindb.py:746 #: Mailman/Cgi/admin.py:1620 +#, fuzzy msgid "%(schange_to)s matches banned pattern %(spat)s" -msgstr "" +msgstr " är redan medlem" #: Mailman/Cgi/admin.py:1622 msgid "Address %(schange_from)s changed to %(schange_to)s" @@ -1752,6 +1779,7 @@ msgstr "Inte anm # Mailman/Cgi/admin.py:1339 # Mailman/Cgi/admin.py:1359 #: Mailman/Cgi/admin.py:1773 +#, fuzzy msgid "Ignoring changes to deleted member: %(user)s" msgstr "Bortser från ändring av en medlem som är avanmäld: %(user)s" @@ -1770,12 +1798,14 @@ msgstr "Fel under avanm # Mailman/Cgi/admindb.py:155 Mailman/Cgi/admindb.py:163 # Mailman/Cgi/admindb.py:159 Mailman/Cgi/admindb.py:167 #: Mailman/Cgi/admindb.py:235 Mailman/Cgi/admindb.py:248 +#, fuzzy msgid "%(realname)s Administrative Database" msgstr "Administrativ databas för listan %(realname)s" # Mailman/Cgi/admindb.py:158 # Mailman/Cgi/admindb.py:162 #: Mailman/Cgi/admindb.py:238 +#, fuzzy msgid "%(realname)s Administrative Database Results" msgstr "Resultat från den administrativa databasen till listan %(realname)s" @@ -1817,6 +1847,7 @@ msgstr "" # Mailman/Cgi/admindb.py:197 # Mailman/Cgi/admindb.py:204 #: Mailman/Cgi/admindb.py:298 +#, fuzzy msgid "all of %(esender)s's held messages." msgstr "alla meddelanden från %(esender)s, som hålls tillbaka för godkännande." @@ -1847,6 +1878,7 @@ msgstr "lista # Mailman/Cgi/admindb.py:247 # Mailman/Cgi/admindb.py:254 #: Mailman/Cgi/admindb.py:362 +#, fuzzy msgid "You must specify a list name. Here is the %(link)s" msgstr "Du måste uppge ett namn på en lista. Här är %(link)s" @@ -1992,6 +2024,7 @@ msgstr "Avsl # Mailman/Cgi/admindb.py:423 # Mailman/Cgi/admindb.py:429 #: Mailman/Cgi/admindb.py:584 +#, fuzzy msgid "" "Ban %(esender)s from ever subscribing to this\n" " mailing list" @@ -2008,6 +2041,7 @@ msgstr "Klicka p # Mailman/Cgi/admindb.py:430 # Mailman/Cgi/admindb.py:436 #: Mailman/Cgi/admindb.py:591 +#, fuzzy msgid "view all messages from %(esender)s" msgstr "se alla meddelanden från %(esender)s" @@ -2179,6 +2213,7 @@ msgstr "" # Mailman/Cgi/confirm.py:150 # Mailman/Cgi/confirm.py:150 #: Mailman/Cgi/confirm.py:178 +#, fuzzy msgid "System error, bad content: %(content)s" msgstr "Systemfel, ogiltigt innehåll: %(content)s" @@ -2357,6 +2392,7 @@ msgstr "V # Mailman/Cgi/confirm.py:318 # Mailman/Cgi/confirm.py:325 #: Mailman/Cgi/confirm.py:386 +#, fuzzy msgid "" " You have successfully confirmed your subscription request to " "the\n" @@ -2427,6 +2463,7 @@ msgstr "Bekr # Mailman/Cgi/confirm.py:338 # Mailman/Cgi/confirm.py:347 #: Mailman/Cgi/confirm.py:418 +#, fuzzy msgid "" " You have successfully confirmed your subscription request for\n" " \"%(addr)s\" to the %(listname)s mailing list. A separate\n" @@ -2462,6 +2499,7 @@ msgstr "Ans # Mailman/Cgi/confirm.py:388 # Mailman/Cgi/confirm.py:397 #: Mailman/Cgi/confirm.py:469 +#, fuzzy msgid "" " You have successfully unsubscribed from the %(listname)s " "mailing\n" @@ -2488,6 +2526,7 @@ msgstr "Inte tillg # Mailman/Cgi/confirm.py:415 # Mailman/Cgi/confirm.py:426 #: Mailman/Cgi/confirm.py:498 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " unsubscription request from the mailing list %(listname)s. " @@ -2580,6 +2619,7 @@ msgstr " # Mailman/Cgi/confirm.py:474 # Mailman/Cgi/confirm.py:485 #: Mailman/Cgi/confirm.py:571 +#, fuzzy msgid "" " You have successfully changed your address on the %(listname)s\n" " mailing list from %(oldaddr)s to %(newaddr)s. " @@ -2607,6 +2647,7 @@ msgstr " # Mailman/Cgi/confirm.py:506 # Mailman/Cgi/confirm.py:519 #: Mailman/Cgi/confirm.py:605 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " change of address request for the mailing list %(listname)s. " @@ -2680,6 +2721,7 @@ msgstr "Avs # Mailman/Cgi/confirm.py:572 # Mailman/Cgi/confirm.py:585 #: Mailman/Cgi/confirm.py:674 +#, fuzzy msgid "" "The held message with the Subject:\n" " header %(subject)s could not be found. The most " @@ -2702,6 +2744,7 @@ msgstr "Meddelandet drogs tillbaka" # Mailman/Cgi/confirm.py:583 # Mailman/Cgi/confirm.py:596 #: Mailman/Cgi/confirm.py:685 +#, fuzzy msgid "" " You have successfully canceled the posting of your message with\n" " the Subject: header %(subject)s to the mailing list\n" @@ -2728,6 +2771,7 @@ msgstr "" # Mailman/Cgi/confirm.py:623 # Mailman/Cgi/confirm.py:646 #: Mailman/Cgi/confirm.py:735 +#, fuzzy msgid "" "Your confirmation is required in order to cancel the\n" " posting of your message to the mailing list %(listname)s:\n" @@ -2788,6 +2832,7 @@ msgstr "Du kommer nu att ta emot e-postbrev fr # Mailman/Cgi/confirm.py:685 # Mailman/Cgi/confirm.py:708 #: Mailman/Cgi/confirm.py:798 +#, fuzzy msgid "" " You have successfully re-enabled your membership in the\n" " %(listname)s mailing list. You can now list information page." msgstr "" "Tyvärr är du redan avanmäld från denna e-postlista.\n" -"För att anmäla dig till listan igen, gå till listans webbsida." +"För att anmäla dig till listan igen, gå till listans webbsida." # Mailman/Cgi/confirm.py:723 # Mailman/Cgi/confirm.py:751 @@ -2825,6 +2871,7 @@ msgstr "inte tillg # Mailman/Cgi/confirm.py:725 # Mailman/Cgi/confirm.py:755 #: Mailman/Cgi/confirm.py:846 +#, fuzzy msgid "" "Your membership in the %(realname)s mailing list is\n" " currently disabled due to excessive bounces. Your confirmation is\n" @@ -2972,6 +3019,7 @@ msgstr "Ok # Mailman/Cgi/create.py:181 # Mailman/Cgi/create.py:181 bin/newlist:166 #: Mailman/Cgi/create.py:218 bin/newlist:219 +#, fuzzy msgid "Bad owner email address: %(s)s" msgstr "Ogiltig e-postadress: %(s)s" @@ -2980,12 +3028,14 @@ msgstr "Ogiltig e-postadress: %(s)s" # Mailman/Cgi/create.py:107 Mailman/Cgi/create.py:185 bin/newlist:134 # bin/newlist:168 #: Mailman/Cgi/create.py:223 bin/newlist:182 bin/newlist:223 +#, fuzzy msgid "List already exists: %(listname)s" msgstr "Listan finns redan: %(listname)s !" # bin/sync_members:168 # Mailman/Cgi/create.py:189 bin/newlist:164 #: Mailman/Cgi/create.py:231 bin/newlist:217 +#, fuzzy msgid "Illegal list name: %(s)s" msgstr "Ogiltigt listnamn: %(s)s" @@ -3002,6 +3052,7 @@ msgstr "" # Mailman/Cgi/create.py:229 bin/newlist:204 # Mailman/Cgi/create.py:233 bin/newlist:210 #: Mailman/Cgi/create.py:273 bin/newlist:265 +#, fuzzy msgid "Your new mailing list: %(listname)s" msgstr "Din nya e-postlista: %(listname)s" @@ -3014,6 +3065,7 @@ msgstr "Resultat av uppr # Mailman/Cgi/create.py:244 # Mailman/Cgi/create.py:248 #: Mailman/Cgi/create.py:288 +#, fuzzy msgid "" "You have successfully created the mailing list\n" " %(listname)s and notification has been sent to the list owner\n" @@ -3045,6 +3097,7 @@ msgstr "Uppr # Mailman/Cgi/create.py:268 # Mailman/Cgi/create.py:272 #: Mailman/Cgi/create.py:312 +#, fuzzy msgid "Create a %(hostname)s Mailing List" msgstr "Upprätta en e-postlista på %(hostname)s" @@ -3165,6 +3218,7 @@ msgstr "" # Mailman/Cgi/create.py:371 # Mailman/Cgi/create.py:375 #: Mailman/Cgi/create.py:432 +#, fuzzy msgid "" "Initial list of supported languages.

                  Note that if you do not\n" " select at least one initial language, the list will use the server\n" @@ -3302,6 +3356,7 @@ msgstr "Listans namn kr # Mailman/Cgi/edithtml.py:97 # Mailman/Cgi/edithtml.py:97 #: Mailman/Cgi/edithtml.py:154 +#, fuzzy msgid "%(realname)s -- Edit html for %(template_info)s" msgstr "%(realname)s -- Redigera html för %(template_info)s" @@ -3314,12 +3369,14 @@ msgstr "Redigera HTML : Fel" # Mailman/Cgi/edithtml.py:104 # Mailman/Cgi/edithtml.py:104 #: Mailman/Cgi/edithtml.py:161 +#, fuzzy msgid "%(safetemplatename)s: Invalid template" msgstr "%(safetemplatename)s: Ogiltig mall" # Mailman/Cgi/edithtml.py:109 Mailman/Cgi/edithtml.py:110 # Mailman/Cgi/edithtml.py:109 Mailman/Cgi/edithtml.py:110 #: Mailman/Cgi/edithtml.py:166 Mailman/Cgi/edithtml.py:167 +#, fuzzy msgid "%(realname)s -- HTML Page Editing" msgstr "%(realname)s -- Redigera HTML-kod för webbsidor" @@ -3396,12 +3453,14 @@ msgstr "HTML-koden # Mailman/Cgi/listinfo.py:71 # Mailman/Cgi/listinfo.py:71 #: Mailman/Cgi/listinfo.py:89 +#, fuzzy msgid "%(hostname)s Mailing Lists" msgstr "E-postlistor på %(hostname)s" # Mailman/Cgi/listinfo.py:103 # Mailman/Cgi/listinfo.py:103 #: Mailman/Cgi/listinfo.py:127 +#, fuzzy msgid "" "

                  There currently are no publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s." @@ -3412,6 +3471,7 @@ msgstr "" # Mailman/Cgi/listinfo.py:107 # Mailman/Cgi/listinfo.py:107 #: Mailman/Cgi/listinfo.py:131 +#, fuzzy msgid "" "

                  Below is a listing of all the public mailing lists on\n" " %(hostname)s. Click on a list name to get more information " @@ -3518,6 +3578,7 @@ msgstr "Fel/Ogiltig e-postadress" # Mailman/Cgi/options.py:176 #: Mailman/Cgi/options.py:183 Mailman/Cgi/options.py:240 #: Mailman/Cgi/options.py:264 +#, fuzzy msgid "No such member: %(safeuser)s." msgstr "Medlemmen finns inte: %(safeuser)s." @@ -3649,6 +3710,7 @@ msgstr "Adresserna kan inte vara tomma" # Mailman/Cgi/options.py:302 # Mailman/Cgi/options.py:327 #: Mailman/Cgi/options.py:488 +#, fuzzy msgid "A confirmation message has been sent to %(newaddr)s. " msgstr "" "Ett bekräftelsemeddelande har skickats i ett e-postbrev till %(newaddr)s." @@ -3668,6 +3730,7 @@ msgstr "Ogiltig e-postadress har angivits" # Mailman/Cgi/options.py:315 # Mailman/Cgi/options.py:340 #: Mailman/Cgi/options.py:501 +#, fuzzy msgid "%(newaddr)s is already a member of the list." msgstr "%(newaddr)s är redan medlem av listan." @@ -3776,6 +3839,7 @@ msgstr "" # Mailman/Cgi/options.py:405 # Mailman/Cgi/options.py:430 #: Mailman/Cgi/options.py:623 +#, fuzzy msgid "" "You have been successfully unsubscribed from the\n" " mailing list %(fqdn_listname)s. If you were receiving digest\n" @@ -3895,6 +3959,7 @@ msgstr "dag" # Mailman/Cgi/options.py:667 # Mailman/Cgi/options.py:695 #: Mailman/Cgi/options.py:900 +#, fuzzy msgid "%(days)d %(units)s" msgstr "%(days)d %(units)s" @@ -3913,6 +3978,7 @@ msgstr "Inget # Mailman/Cgi/options.py:709 # Mailman/Cgi/options.py:733 #: Mailman/Cgi/options.py:940 +#, fuzzy msgid "" "\n" "You are subscribed to this list with the case-preserved address\n" @@ -3924,6 +3990,7 @@ msgstr "" # Mailman/Cgi/options.py:723 # Mailman/Cgi/options.py:747 #: Mailman/Cgi/options.py:956 +#, fuzzy msgid "%(realname)s list: member options login page" msgstr "%(realname)s: inloggning till personliga inställningar" @@ -3943,6 +4010,7 @@ msgstr "%(realname)s: personliga inst # Mailman/Cgi/options.py:752 # Mailman/Cgi/options.py:776 #: Mailman/Cgi/options.py:986 +#, fuzzy msgid "" "In order to change your membership option, you must\n" " first log in by giving your %(extra)smembership password in the section\n" @@ -4038,6 +4106,7 @@ msgstr "" # Mailman/Cgi/options.py:906 # Mailman/Cgi/options.py:930 #: Mailman/Cgi/options.py:1140 +#, fuzzy msgid "Requested topic is not valid: %(topicname)s" msgstr "Det valda ämnet är inte giltigt: %(topicname)s" @@ -4078,6 +4147,7 @@ msgstr "" # Mailman/Cgi/private.py:99 # Mailman/Cgi/private.py:99 #: Mailman/Cgi/private.py:109 +#, fuzzy msgid "Private Archive Error - %(msg)s" msgstr "Fel i privat arkiv - %(msg)s" @@ -4137,6 +4207,7 @@ msgstr "Resultat av radering av e-postlista" # Mailman/Cgi/rmlist.py:149 # Mailman/Cgi/rmlist.py:149 #: Mailman/Cgi/rmlist.py:187 +#, fuzzy msgid "" "You have successfully deleted the mailing list\n" " %(listname)s." @@ -4153,6 +4224,7 @@ msgstr "" # Mailman/Cgi/rmlist.py:165 # Mailman/Cgi/rmlist.py:165 #: Mailman/Cgi/rmlist.py:208 +#, fuzzy msgid "Permanently remove mailing list %(realname)s" msgstr "Ta bort e-postlistan %(realname)s permanent" @@ -4239,6 +4311,7 @@ msgstr "Ogiltiga parametrar till CGI-skriptet" # Mailman/Cgi/roster.py:99 # Mailman/Cgi/roster.py:99 #: Mailman/Cgi/roster.py:118 +#, fuzzy msgid "%(realname)s roster authentication failed." msgstr "Tillgång till %(realname)s misslyckades." @@ -4321,6 +4394,7 @@ msgstr "" # Mailman/Cgi/subscribe.py:181 # Mailman/Cgi/subscribe.py:181 #: Mailman/Cgi/subscribe.py:262 +#, fuzzy msgid "" "The email address you supplied is banned from this\n" " mailing list. If you think this restriction is erroneous, please\n" @@ -4352,6 +4426,7 @@ msgstr "" # Mailman/Cgi/subscribe.py:197 # Mailman/Cgi/subscribe.py:197 #: Mailman/Cgi/subscribe.py:278 +#, fuzzy msgid "" "Confirmation from your email address is required, to prevent anyone from\n" "subscribing you without permission. Instructions are being sent to you at\n" @@ -4367,6 +4442,7 @@ msgstr "" # Mailman/Cgi/subscribe.py:209 # Mailman/Cgi/subscribe.py:209 #: Mailman/Cgi/subscribe.py:290 +#, fuzzy msgid "" "Your subscription request was deferred because %(x)s. Your request has " "been\n" @@ -4394,6 +4470,7 @@ msgstr "S # Mailman/Cgi/subscribe.py:231 # Mailman/Cgi/subscribe.py:231 #: Mailman/Cgi/subscribe.py:316 +#, fuzzy msgid "" "An attempt was made to subscribe your address to the mailing list\n" "%(listaddr)s. You are already subscribed to this mailing list.\n" @@ -4448,6 +4525,7 @@ msgstr "Denna lista st # Mailman/Cgi/subscribe.py:259 # Mailman/Cgi/subscribe.py:259 #: Mailman/Cgi/subscribe.py:344 +#, fuzzy msgid "You have been successfully subscribed to the %(realname)s mailing list." msgstr "Du är nu anmäld till e-postlistan %(realname)s." @@ -4612,36 +4690,42 @@ msgstr "" # Mailman/Commands/cmd_info.py:44 # Mailman/Commands/cmd_info.py:44 #: Mailman/Commands/cmd_info.py:44 +#, fuzzy msgid "List name: %(listname)s" msgstr "Listnamn: %(listname)s" # Mailman/Commands/cmd_info.py:45 # Mailman/Commands/cmd_info.py:45 #: Mailman/Commands/cmd_info.py:45 +#, fuzzy msgid "Description: %(description)s" msgstr "Beskrivning: %(description)s" # Mailman/Commands/cmd_info.py:46 # Mailman/Commands/cmd_info.py:46 #: Mailman/Commands/cmd_info.py:46 +#, fuzzy msgid "Postings to: %(postaddr)s" msgstr "Adress: %(postaddr)s" # Mailman/Commands/cmd_info.py:47 # Mailman/Commands/cmd_info.py:47 #: Mailman/Commands/cmd_info.py:47 +#, fuzzy msgid "List Helpbot: %(requestaddr)s" msgstr "Kommandoadress: %(requestaddr)s" # Mailman/Commands/cmd_info.py:48 # Mailman/Commands/cmd_info.py:48 #: Mailman/Commands/cmd_info.py:48 +#, fuzzy msgid "List Owners: %(owneraddr)s" msgstr "Listans ägare: %(owneraddr)s" # Mailman/Commands/cmd_info.py:49 # Mailman/Commands/cmd_info.py:49 #: Mailman/Commands/cmd_info.py:49 +#, fuzzy msgid "More information: %(listurl)s" msgstr "Mer information: %(listurl)s" @@ -4674,24 +4758,28 @@ msgstr "" # Mailman/Commands/cmd_lists.py:43 # Mailman/Commands/cmd_lists.py:44 #: Mailman/Commands/cmd_lists.py:44 +#, fuzzy msgid "Public mailing lists at %(hostname)s:" msgstr "E-postlistor offentligt tillgängliga på %(hostname)s:" # Mailman/Commands/cmd_lists.py:60 # Mailman/Commands/cmd_lists.py:66 #: Mailman/Commands/cmd_lists.py:66 +#, fuzzy msgid "%(i)3d. List name: %(realname)s" msgstr "%(i)3d. Listnamn: %(realname)s" # Mailman/Commands/cmd_lists.py:61 # Mailman/Commands/cmd_lists.py:67 #: Mailman/Commands/cmd_lists.py:67 +#, fuzzy msgid " Description: %(description)s" msgstr " Beskrivning: %(description)s" # Mailman/Commands/cmd_lists.py:62 # Mailman/Commands/cmd_lists.py:68 #: Mailman/Commands/cmd_lists.py:68 +#, fuzzy msgid " Requests to: %(requestaddr)s" msgstr " Frågor till: %(requestaddr)s" @@ -4730,6 +4818,7 @@ msgstr "" # Mailman/Commands/cmd_password.py:51 Mailman/Commands/cmd_password.py:64 # Mailman/Commands/cmd_password.py:51 Mailman/Commands/cmd_password.py:64 #: Mailman/Commands/cmd_password.py:51 Mailman/Commands/cmd_password.py:66 +#, fuzzy msgid "Your password is: %(password)s" msgstr "Ditt lösenord är: %(password)s" @@ -4742,6 +4831,7 @@ msgstr "Ditt l #: Mailman/Commands/cmd_password.py:57 Mailman/Commands/cmd_password.py:72 #: Mailman/Commands/cmd_password.py:95 Mailman/Commands/cmd_password.py:121 #: Mailman/Commands/cmd_set.py:149 Mailman/Commands/cmd_set.py:219 +#, fuzzy msgid "You are not a member of the %(listname)s mailing list" msgstr "Du är inte medlem av e-postlistan %(listname)s" @@ -4944,6 +5034,7 @@ msgstr "" # Mailman/Commands/cmd_set.py:122 # Mailman/Commands/cmd_set.py:122 #: Mailman/Commands/cmd_set.py:122 +#, fuzzy msgid "Bad set command: %(subcmd)s" msgstr "Ogiltigt kommando: %(subcmd)s" @@ -4977,11 +5068,12 @@ msgstr "av" msgid "on" msgstr "på" -# Mailman/Commands/cmd_set.py:154 -# Mailman/Commands/cmd_set.py:154 +# Mailman/Commands/cmd_set.py:195 +# Mailman/Commands/cmd_set.py:195 #: Mailman/Commands/cmd_set.py:154 +#, fuzzy msgid " ack %(onoff)s" -msgstr "" +msgstr " dölj %(onoff)s" # Mailman/Commands/cmd_set.py:286 # Mailman/Commands/cmd_set.py:160 @@ -5036,33 +5128,35 @@ msgstr "av listadministrat msgid "due to bounces" msgstr "på grund av för många returmeddelanden" -# Mailman/Commands/cmd_set.py:186 -# Mailman/Commands/cmd_set.py:186 #: Mailman/Commands/cmd_set.py:186 msgid " %(status)s (%(how)s on %(date)s)" msgstr "" -# Mailman/Commands/cmd_set.py:192 -# Mailman/Commands/cmd_set.py:192 +# Mailman/Commands/cmd_set.py:199 +# Mailman/Commands/cmd_set.py:199 #: Mailman/Commands/cmd_set.py:192 +#, fuzzy msgid " myposts %(onoff)s" -msgstr "" +msgstr " undvik kopior %(onoff)s" # Mailman/Commands/cmd_set.py:195 # Mailman/Commands/cmd_set.py:195 #: Mailman/Commands/cmd_set.py:195 +#, fuzzy msgid " hide %(onoff)s" msgstr " dölj %(onoff)s" # Mailman/Commands/cmd_set.py:199 # Mailman/Commands/cmd_set.py:199 #: Mailman/Commands/cmd_set.py:199 +#, fuzzy msgid " duplicates %(onoff)s" msgstr " undvik kopior %(onoff)s" # Mailman/Commands/cmd_set.py:203 # Mailman/Commands/cmd_set.py:203 #: Mailman/Commands/cmd_set.py:203 +#, fuzzy msgid " reminders %(onoff)s" msgstr " påminnelser om lösenord %(onoff)s" @@ -5075,6 +5169,7 @@ msgstr "Du har uppgett fel l # Mailman/Commands/cmd_set.py:236 Mailman/Commands/cmd_set.py:283 # Mailman/Commands/cmd_set.py:236 Mailman/Commands/cmd_set.py:283 #: Mailman/Commands/cmd_set.py:236 Mailman/Commands/cmd_set.py:283 +#, fuzzy msgid "Bad argument: %(arg)s" msgstr "Ogiltiga parametrar: %(arg)s" @@ -5178,6 +5273,7 @@ msgstr "" # Mailman/Commands/cmd_subscribe.py:61 # Mailman/Commands/cmd_subscribe.py:62 #: Mailman/Commands/cmd_subscribe.py:62 +#, fuzzy msgid "Bad digest specifier: %(arg)s" msgstr "Ogitlig parameter för sammandragsversion: %(arg)s" @@ -5190,6 +5286,7 @@ msgstr "Ingen giltig e-postadress f # Mailman/Commands/cmd_subscribe.py:92 # Mailman/Commands/cmd_subscribe.py:102 #: Mailman/Commands/cmd_subscribe.py:113 +#, fuzzy msgid "" "The email address you supplied is banned from this mailing list.\n" "If you think this restriction is erroneous, please contact the list\n" @@ -5242,6 +5339,7 @@ msgstr "Denna lista st # Mailman/Commands/cmd_subscribe.py:120 # Mailman/Commands/cmd_subscribe.py:130 #: Mailman/Commands/cmd_subscribe.py:146 +#, fuzzy msgid "" "Your subscription request has been forwarded to the list administrator\n" "at %(listowner)s for review." @@ -5285,6 +5383,7 @@ msgstr "" # Mailman/Commands/cmd_unsubscribe.py:62 # Mailman/Commands/cmd_unsubscribe.py:62 #: Mailman/Commands/cmd_unsubscribe.py:62 +#, fuzzy msgid "%(address)s is not a member of the %(listname)s mailing list" msgstr "%(address)s är inte medlem av e-postlistan %(listname)s." @@ -5605,6 +5704,7 @@ msgstr "" # Mailman/Deliverer.py:42 # Mailman/Deliverer.py:43 #: Mailman/Deliverer.py:53 +#, fuzzy msgid "" "Note: Since this is a list of mailing lists, administrative\n" "notices like the password reminder will be sent to\n" @@ -5624,18 +5724,21 @@ msgstr " (Sammandragsversion)" # Mailman/Deliverer.py:67 # Mailman/Deliverer.py:68 #: Mailman/Deliverer.py:79 +#, fuzzy msgid "Welcome to the \"%(realname)s\" mailing list%(digmode)s" msgstr "Välkommen till e-postlistan \"%(realname)s\"%(digmode)s" # Mailman/Deliverer.py:76 # Mailman/Deliverer.py:77 #: Mailman/Deliverer.py:89 +#, fuzzy msgid "You have been unsubscribed from the %(realname)s mailing list" msgstr "Du är nu borttagen från e-postlistan \"%(realname)s\"" # Mailman/Deliverer.py:103 # Mailman/Deliverer.py:104 #: Mailman/Deliverer.py:116 +#, fuzzy msgid "%(listfullname)s mailing list reminder" msgstr "Påminnelse från e-postlistan %(listfullname)s" @@ -5929,8 +6032,8 @@ msgid "" " membership.\n" "\n" "

                  You can control both the\n" -" number\n" +" number\n" " of reminders the member will receive and the\n" " Du kan bestämma hur många varningar\n" +"

                  Du kan bestämma hur många varningar\n" "som medlemmen ska få och hur ofta\n" "han/hon ska ta emot sådana varningar.\n" @@ -6140,8 +6243,8 @@ msgid "" " No those messages too will get discarded. You may " "want\n" " to set up an\n" -" autoresponse\n" +" autoresponse\n" " message for email to the -owner and -admin address." msgstr "" "Mailmans automatiska returhantering är mycket robust, men det är ändå " @@ -6240,6 +6343,7 @@ msgstr "" # Mailman/Gui/Bounce.py:173 # Mailman/Gui/Bounce.py:173 #: Mailman/Gui/Bounce.py:194 +#, fuzzy msgid "" "Bad value for %(property)s: %(val)s" @@ -6401,8 +6505,8 @@ msgid "" "Use this option to remove each message attachment that does\n" " not have a matching content type. Requirements and formats " "are\n" -" exactly like filter_mime_types.\n" "\n" "

                  Note: if you add entries to this list but don't add\n" @@ -6537,6 +6641,7 @@ msgstr "" # Mailman/Gui/ContentFilter.py:75 # Mailman/Gui/ContentFilter.py:154 #: Mailman/Gui/ContentFilter.py:171 +#, fuzzy msgid "Bad MIME type ignored: %(spectype)s" msgstr "Hoppar över ogiltig MIME-typ: %(spectype)s" @@ -6684,6 +6789,7 @@ msgstr "" # Mailman/Gui/Digest.py:145 # Mailman/Gui/Digest.py:145 #: Mailman/Gui/Digest.py:145 +#, fuzzy msgid "" "The next digest will be sent as volume\n" " %(volume)s, number %(number)s" @@ -6706,18 +6812,21 @@ msgstr "Det fanns ingen samlingsepost att skicka." # Mailman/Gui/GUIBase.py:143 # Mailman/Gui/GUIBase.py:149 #: Mailman/Gui/GUIBase.py:173 +#, fuzzy msgid "Invalid value for variable: %(property)s" msgstr "Ogiltigt värde för: %(property)s" # Mailman/Gui/GUIBase.py:147 # Mailman/Gui/GUIBase.py:153 #: Mailman/Gui/GUIBase.py:178 +#, fuzzy msgid "Bad email address for option %(property)s: %(error)s" msgstr "Ogiltig e-postadress för inställningen %(property)s: %(error)s" # Mailman/Gui/GUIBase.py:173 # Mailman/Gui/GUIBase.py:179 #: Mailman/Gui/GUIBase.py:204 +#, fuzzy msgid "" "The following illegal substitution variables were\n" " found in the %(property)s string:\n" @@ -6733,6 +6842,7 @@ msgstr "" # Mailman/Gui/GUIBase.py:187 # Mailman/Gui/GUIBase.py:193 #: Mailman/Gui/GUIBase.py:218 +#, fuzzy msgid "" "Your %(property)s string appeared to\n" " have some correctable problems in its new value.\n" @@ -6856,8 +6966,8 @@ msgid "" "

                  In order to split the list ownership duties into\n" " administrators and moderators, you must\n" " set a separate moderator password,\n" -" and also provide the email\n" +" and also provide the email\n" " addresses of the list moderators. Note that the field you\n" " are changing here specifies the list administrators." msgstr "" @@ -7219,13 +7329,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -7252,8 +7362,8 @@ msgstr "" "ersätta,\n" "ett Reply-To: fält. (Egendefinierad adress sätter in " "värdet\n" -"av inställningen reply_to_address).\n" +"av inställningen reply_to_address).\n" "\n" "

                  Det finns många anledningar till att inte införa eller ersätta Reply-" "To:\n" @@ -7296,8 +7406,8 @@ msgstr "Egendefinierad Reply-To: adress." msgid "" "This is the address set in the Reply-To: header\n" " when the reply_goes_to_list\n" +" href=\"?VARHELP=general/" +"reply_goes_to_list\">reply_goes_to_list\n" " option is set to Explicit address.\n" "\n" "

                  There are many reasons not to introduce or override the\n" @@ -7305,13 +7415,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -7333,8 +7443,8 @@ msgid "" " Reply-To: header, it will not be changed." msgstr "" "Här definierar du adressen som ska sättas i Reply-To: fältet\n" -"när inställningen reply_goes_to_list\n" +"när inställningen reply_goes_to_list\n" "är satt till Egendefinierd adress.\n" "\n" "

                  Det finns många anledningar till att inte införa eller ersätta Reply-" @@ -7428,8 +7538,8 @@ msgid "" " member list addresses, but rather to the owner of those member\n" " lists. In that case, the value of this setting is appended to\n" " the member's account name for such notices. `-owner' is the\n" -" typical choice. This setting has no effect when \"umbrella_list" -"\"\n" +" typical choice. This setting has no effect when " +"\"umbrella_list\"\n" " is \"No\"." msgstr "" "När \"umbrella_list\" indikerar att denna lista har andra e-postlistor som " @@ -7441,8 +7551,8 @@ msgstr "" "administratören för listan. Om detta är fallet, används värdet av denna\n" "inställning för att bestämma adressen som administrativa meddelanden ska\n" "skickas till. '-owner' är ett vanligt val för denna inställning.
                  \n" -"Denna inställning har ingen effekt när \"umbrella_list\" är satt till \"Nej" -"\"." +"Denna inställning har ingen effekt när \"umbrella_list\" är satt till " +"\"Nej\"." # Mailman/Gui/General.py:256 # Mailman/Gui/General.py:260 @@ -7983,8 +8093,8 @@ msgid "" " language must be included." msgstr "" "Här är alla språk som denna lista har stöd för.\n" -"Observera att standardspråket\n" +"Observera att standardspråket\n" "måste vara med." # Mailman/Cgi/options.py:664 @@ -8507,6 +8617,7 @@ msgstr "" # Mailman/Gui/Privacy.py:93 # Mailman/Gui/Privacy.py:94 #: Mailman/Gui/Privacy.py:104 +#, fuzzy msgid "" "This section allows you to configure subscription and\n" " membership exposure policy. You can also control whether this\n" @@ -8739,8 +8850,8 @@ msgid "" " either individually or as a group. Any\n" " posting from a non-member who is not explicitly accepted,\n" " rejected, or discarded, will have their posting filtered by the\n" -" general\n" +" general\n" " non-member rules.\n" "\n" "

                  In the text boxes below, add one address per line; start the\n" @@ -8776,12 +8887,13 @@ msgstr "" "medlemmar,\n" "som inte specifikt blir godkända, återsända, eller kastade, kommer att bli " "behandlade\n" -"allt efter vad de allmänna reglerna för icke-medlemmar säger.\n" +"allt efter vad de allmänna reglerna för icke-medlemmar " +"säger.\n" "\n" "

                  I textrutorna nedan lägger du in en e-postadress per rad.\n" -"Du kan också lägga in Python regexp-uttryck.\n" +"Du kan också lägga in Python regexp-uttryck.\n" "Börja i så fall raden med tecknet ^ för att markera att det är ett sådant " "uttryck.\n" "När du använder backslash, skriv då såsom i rena strängar (Python raw " @@ -8807,6 +8919,7 @@ msgstr "" # Mailman/Gui/Privacy.py:190 # Mailman/Gui/Privacy.py:191 #: Mailman/Gui/Privacy.py:218 +#, fuzzy msgid "" "Each list member has a moderation flag which says\n" " whether messages from the list member can be posted directly " @@ -8865,8 +8978,8 @@ msgid "" "If a member posts this many times, within a period of time\n" " the member is automatically moderated. Use 0 to disable. " "See\n" -" member_verbosity_interval for details on the time " "period.\n" "\n" @@ -9678,8 +9791,8 @@ msgid "" "

                  The body of the message can also be optionally scanned for\n" " Subject: and Keywords: headers, as\n" " specified by the topics_bodylines_limit\n" +" href=\"?VARHELP=topics/" +"topics_bodylines_limit\">topics_bodylines_limit\n" " configuration variable." msgstr "" "Ämnesfiltret kategoriserar varje e-postbrev som kommer till listan,\n" @@ -10031,6 +10144,7 @@ msgstr "E-postlistan %(listinfo_link)s administreras av %(owner_link)s" # Mailman/HTMLFormatter.py:54 # Mailman/HTMLFormatter.py:55 #: Mailman/HTMLFormatter.py:57 +#, fuzzy msgid "%(realname)s administrative interface" msgstr "Administrativ sida för %(realname)s" @@ -10043,6 +10157,7 @@ msgstr " (kr # Mailman/HTMLFormatter.py:58 # Mailman/HTMLFormatter.py:59 #: Mailman/HTMLFormatter.py:61 +#, fuzzy msgid "Overview of all %(hostname)s mailing lists" msgstr "Lista över alla e-postlistor på %(hostname)s" @@ -10110,6 +10225,7 @@ msgstr "listadministrat # Mailman/HTMLFormatter.py:150 # Mailman/HTMLFormatter.py:154 #: Mailman/HTMLFormatter.py:157 +#, fuzzy msgid "" "

                  %(note)s\n" "\n" @@ -10130,6 +10246,7 @@ msgstr "" # Mailman/HTMLFormatter.py:162 # Mailman/HTMLFormatter.py:166 #: Mailman/HTMLFormatter.py:169 +#, fuzzy msgid "" "

                  We have received some recent bounces from your\n" " address. Your current bounce score is %(score)s out of " @@ -10150,6 +10267,7 @@ msgstr "" # Mailman/HTMLFormatter.py:174 # Mailman/HTMLFormatter.py:178 #: Mailman/HTMLFormatter.py:181 +#, fuzzy msgid "" "(Note - you are subscribing to a list of mailing lists, so the %(type)s " "notice will be sent to the admin address for your membership, %(addr)s.)

                  " @@ -10208,6 +10326,7 @@ msgstr "" # Mailman/HTMLFormatter.py:199 # Mailman/HTMLFormatter.py:205 #: Mailman/HTMLFormatter.py:208 +#, fuzzy msgid "" "This is %(also)sa private list, which means that the\n" " list of members is not available to non-members." @@ -10219,6 +10338,7 @@ msgstr "" # Mailman/HTMLFormatter.py:202 # Mailman/HTMLFormatter.py:208 #: Mailman/HTMLFormatter.py:211 +#, fuzzy msgid "" "This is %(also)sa hidden list, which means that the\n" " list of members is available only to the list administrator." @@ -10229,6 +10349,7 @@ msgstr "" # Mailman/HTMLFormatter.py:205 # Mailman/HTMLFormatter.py:211 #: Mailman/HTMLFormatter.py:214 +#, fuzzy msgid "" "This is %(also)sa public list, which means that the\n" " list of members list is available to everyone." @@ -10248,6 +10369,7 @@ msgstr "" # Mailman/HTMLFormatter.py:213 # Mailman/HTMLFormatter.py:219 #: Mailman/HTMLFormatter.py:222 +#, fuzzy msgid "" "

                  (Note that this is an umbrella list, intended to\n" " have only other mailing lists as members. Among other things,\n" @@ -10267,6 +10389,7 @@ msgstr "antingen " # Mailman/HTMLFormatter.py:247 # Mailman/HTMLFormatter.py:253 #: Mailman/HTMLFormatter.py:256 +#, fuzzy msgid "" "To unsubscribe from %(realname)s, get a password reminder,\n" " or change your subscription options %(either)senter your " @@ -10408,6 +10531,7 @@ msgstr "nuvarande arkiv" # Mailman/Handlers/Acknowledge.py:64 # Mailman/Handlers/Acknowledge.py:59 #: Mailman/Handlers/Acknowledge.py:59 +#, fuzzy msgid "%(realname)s post acknowledgement" msgstr "Meddelande om mottaget e-postbrev till %(realname)s" @@ -10422,6 +10546,7 @@ msgstr "" # Mailman/Handlers/CalcRecips.py:68 # Mailman/Handlers/CalcRecips.py:68 #: Mailman/Handlers/CalcRecips.py:79 +#, fuzzy msgid "" "Your urgent message to the %(realname)s mailing list was not authorized for\n" "delivery. The original message as received by Mailman is attached.\n" @@ -10534,6 +10659,7 @@ msgstr "Meddelandet kan ha administrativt inneh # Mailman/Handlers/Hold.py:86 # Mailman/Handlers/Hold.py:84 #: Mailman/Handlers/Hold.py:84 +#, fuzzy msgid "" "Please do *not* post administrative requests to the mailing\n" "list. If you wish to subscribe, visit %(listurl)s or send a message with " @@ -10587,6 +10713,7 @@ msgstr "Meddelande till modererad nyhetsgrupp" # Mailman/Handlers/Hold.py:216 # Mailman/Handlers/Hold.py:233 #: Mailman/Handlers/Hold.py:252 +#, fuzzy msgid "Your message to %(listname)s awaits moderator approval" msgstr "" "Meddelandet som du skickade till listan %(listname)s väntar på godkännande " @@ -10595,6 +10722,7 @@ msgstr "" # Mailman/Handlers/Hold.py:236 # Mailman/Handlers/Hold.py:253 #: Mailman/Handlers/Hold.py:271 +#, fuzzy msgid "%(listname)s post from %(sender)s requires approval" msgstr "Meddelande till %(listname)s från %(sender)s kräver godkännande" @@ -10650,6 +10778,7 @@ msgstr "Efter inneh # Mailman/Handlers/MimeDel.py:208 #: Mailman/Handlers/MimeDel.py:269 +#, fuzzy msgid "" "The attached message matched the %(listname)s mailing list's content " "filtering\n" @@ -10729,6 +10858,7 @@ msgstr "En HTML-bilaga skiljdes ut och togs bort" # Mailman/Handlers/Scrubber.py:95 Mailman/Handlers/Scrubber.py:117 # Mailman/Handlers/Scrubber.py:129 Mailman/Handlers/Scrubber.py:154 #: Mailman/Handlers/Scrubber.py:234 Mailman/Handlers/Scrubber.py:259 +#, fuzzy msgid "" "An HTML attachment was scrubbed...\n" "URL: %(url)s\n" @@ -10828,6 +10958,7 @@ msgstr "" # Mailman/Handlers/ToDigest.py:140 # Mailman/Handlers/ToDigest.py:141 #: Mailman/Handlers/ToDigest.py:173 +#, fuzzy msgid "%(realname)s Digest, Vol %(volume)d, Issue %(issue)d" msgstr "Sammandrag av %(realname)s, Vol %(volume)d, Utgåva %(issue)d" @@ -10880,6 +11011,7 @@ msgstr "Slut p # Mailman/ListAdmin.py:306 # Mailman/ListAdmin.py:307 #: Mailman/ListAdmin.py:309 +#, fuzzy msgid "Posting of your message titled \"%(subject)s\"" msgstr "Ditt meddelande med titel \"%(subject)s\"" @@ -10900,6 +11032,7 @@ msgstr "Vidares # Mailman/ListAdmin.py:393 # Mailman/ListAdmin.py:405 #: Mailman/ListAdmin.py:405 +#, fuzzy msgid "New subscription request to list %(realname)s from %(addr)s" msgstr "Ny ansökan om medlemskap på listan %(realname)s från %(addr)s" @@ -10919,6 +11052,7 @@ msgstr "Forts # Mailman/ListAdmin.py:446 # Mailman/ListAdmin.py:458 #: Mailman/ListAdmin.py:466 +#, fuzzy msgid "New unsubscription request from %(realname)s by %(addr)s" msgstr "Ansökan från %(addr)s om avanmälan från listan %(realname)s" @@ -10937,12 +11071,14 @@ msgstr "Ursprungligt meddelande" # Mailman/ListAdmin.py:503 # Mailman/ListAdmin.py:515 #: Mailman/ListAdmin.py:526 +#, fuzzy msgid "Request to mailing list %(realname)s rejected" msgstr "Fråga till e-postlistan %(realname)s inte godkänd" # Mailman/MTA/Manual.py:55 # Mailman/MTA/Manual.py:57 #: Mailman/MTA/Manual.py:66 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been created via the through-the-web\n" "interface. In order to complete the activation of this mailing list, the\n" @@ -10987,12 +11123,14 @@ msgstr "e-postlistan \"%(listname)s\"" # Mailman/MTA/Manual.py:86 # Mailman/MTA/Manual.py:88 #: Mailman/MTA/Manual.py:99 +#, fuzzy msgid "Mailing list creation request for list %(listname)s" msgstr "Resultat av upprättande av e-postlistan %(listname)s" # Mailman/MTA/Manual.py:101 # Mailman/MTA/Manual.py:103 #: Mailman/MTA/Manual.py:113 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been removed via the through-the-web\n" "interface. In order to complete the de-activation of this mailing list, " @@ -11012,6 +11150,7 @@ msgstr "" # Mailman/MTA/Manual.py:111 # Mailman/MTA/Manual.py:113 #: Mailman/MTA/Manual.py:123 +#, fuzzy msgid "" "\n" "To finish removing your mailing list, you must edit your /etc/aliases (or\n" @@ -11031,18 +11170,21 @@ msgstr "" # Mailman/MTA/Manual.py:130 # Mailman/MTA/Manual.py:132 #: Mailman/MTA/Manual.py:142 +#, fuzzy msgid "Mailing list removal request for list %(listname)s" msgstr "Fråga om att radera e-postlistan %(listname)s" # Mailman/MTA/Postfix.py:299 # Mailman/MTA/Postfix.py:300 #: Mailman/MTA/Postfix.py:442 +#, fuzzy msgid "checking permissions on %(file)s" msgstr "kontrollerar rättigheter för %(file)s" # Mailman/MTA/Postfix.py:309 # Mailman/MTA/Postfix.py:310 #: Mailman/MTA/Postfix.py:452 +#, fuzzy msgid "%(file)s permissions must be 0664 (got %(octmode)s)" msgstr "rättigheterna på %(file)s måste vara 0664 (men är %(octmode)s)" @@ -11066,18 +11208,21 @@ msgstr "(fixar)" # Mailman/MTA/Postfix.py:327 # Mailman/MTA/Postfix.py:328 #: Mailman/MTA/Postfix.py:470 +#, fuzzy msgid "checking ownership of %(dbfile)s" msgstr "undersöker ägarskap på filen %(dbfile)s" # Mailman/MTA/Postfix.py:334 # Mailman/MTA/Postfix.py:336 #: Mailman/MTA/Postfix.py:478 +#, fuzzy msgid "%(dbfile)s owned by %(owner)s (must be owned by %(user)s" msgstr "%(dbfile)s ägs av %(owner)s (måste ägas av %(user)s)" # Mailman/MTA/Postfix.py:309 # Mailman/MTA/Postfix.py:310 #: Mailman/MTA/Postfix.py:491 +#, fuzzy msgid "%(dbfile)s permissions must be 0664 (got %(octmode)s)" msgstr "rättigheterna på %(dbfile)s måste vara 0664 (men är %(octmode)s)" @@ -11106,18 +11251,21 @@ msgstr "Du # Mailman/MailList.py:766 Mailman/MailList.py:1120 # Mailman/MailList.py:813 Mailman/MailList.py:1174 #: Mailman/MailList.py:999 Mailman/MailList.py:1492 +#, fuzzy msgid " from %(remote)s" msgstr " från %(remote)s" # Mailman/MailList.py:803 # Mailman/MailList.py:850 #: Mailman/MailList.py:1043 +#, fuzzy msgid "subscriptions to %(realname)s require moderator approval" msgstr "anmälan till %(realname)s kräver godkännande av moderator" # Mailman/MailList.py:861 bin/add_members:277 # Mailman/MailList.py:909 bin/add_members:281 #: Mailman/MailList.py:1125 bin/add_members:299 +#, fuzzy msgid "%(realname)s subscription notification" msgstr "Meddelande om anmälan till e-postlistan %(realname)s" @@ -11130,6 +11278,7 @@ msgstr "avanm # Mailman/MailList.py:900 # Mailman/MailList.py:945 #: Mailman/MailList.py:1166 +#, fuzzy msgid "%(realname)s unsubscribe notification" msgstr "Meddelande om avanmälan från e-postlistan %(realname)s" @@ -11157,6 +11306,7 @@ msgstr "Ogiltig identifikation f # Mailman/MailList.py:1040 # Mailman/MailList.py:1089 #: Mailman/MailList.py:1396 +#, fuzzy msgid "subscriptions to %(name)s require administrator approval" msgstr "Anmälan till %(name)s kräver godkännande av administratör" @@ -11179,6 +11329,7 @@ msgstr "Dagens sista automatiska svarsmeddelande" # Mailman/Queue/BounceRunner.py:174 #: Mailman/Queue/BounceRunner.py:360 +#, fuzzy msgid "" "The attached message was received as a bounce, but either the bounce format\n" "was not recognized, or no member addresses could be extracted from it. " @@ -11286,6 +11437,7 @@ msgstr "" # Mailman/htmlformat.py:627 # Mailman/htmlformat.py:627 #: Mailman/htmlformat.py:675 +#, fuzzy msgid "Delivered by Mailman
                  version %(version)s" msgstr "Levererat av Mailman
                  version %(version)s" @@ -11552,6 +11704,7 @@ msgstr "" # bin/add_members:163 # bin/add_members:167 #: bin/add_members:162 bin/add_members:172 +#, fuzzy msgid "Already a member: %(member)s" msgstr "Redan medlem: %(member)s" @@ -11564,12 +11717,14 @@ msgstr "Fel/Ogiltig e-postadress: tom rad" # bin/add_members:168 # bin/add_members:172 #: bin/add_members:180 +#, fuzzy msgid "Bad/Invalid email address: %(member)s" msgstr "Fel/Ogiltig e-postadress: %(member)s" # bin/add_members:170 # bin/add_members:174 #: bin/add_members:182 +#, fuzzy msgid "Hostile address (illegal characters): %(member)s" msgstr "Ogiltigt tecken i e-postadressen: %(member)s" @@ -11583,18 +11738,21 @@ msgstr "Anm # bin/add_members:172 # bin/add_members:176 #: bin/add_members:187 +#, fuzzy msgid "Subscribed: %(member)s" msgstr "Anmäld: %(member)s" # bin/add_members:226 # bin/add_members:230 #: bin/add_members:237 +#, fuzzy msgid "Bad argument to -w/--welcome-msg: %(arg)s" msgstr "Ogiltigt argument till -w/--welcome-msg: %(arg)s" # bin/add_members:233 # bin/add_members:237 #: bin/add_members:244 +#, fuzzy msgid "Bad argument to -a/--admin-notify: %(arg)s" msgstr "Ogiltigt argument till -a/--admin-notify: %(arg)s" @@ -11619,6 +11777,7 @@ msgstr "" #: bin/add_members:261 bin/config_list:110 bin/export.py:271 bin/find_member:97 #: bin/inject:91 bin/list_admins:90 bin/list_members:252 bin/sync_members:222 #: cron/bumpdigests:86 +#, fuzzy msgid "No such list: %(listname)s" msgstr "Listan finns inte: %(listname)s" @@ -11731,6 +11890,7 @@ msgstr "kr # bin/arch:120 bin/change_pw:102 bin/config_list:200 # bin/arch:127 bin/change_pw:106 bin/config_list:239 #: bin/arch:143 bin/change_pw:107 bin/config_list:257 +#, fuzzy msgid "" "No such list \"%(listname)s\"\n" "%(e)s" @@ -11741,6 +11901,7 @@ msgstr "" # bin/arch:143 # bin/arch:150 #: bin/arch:168 +#, fuzzy msgid "Cannot open mbox file %(mbox)s: %(msg)s" msgstr "Kan inte öppna mbox-fil %(mbox)s: %(msg)s" @@ -11873,6 +12034,7 @@ msgstr "" # bin/change_pw:140 # bin/change_pw:144 #: bin/change_pw:145 +#, fuzzy msgid "Bad arguments: %(strargs)s" msgstr "Ogiltiga parametrar: %(strargs)s" @@ -11885,18 +12047,21 @@ msgstr "Tomma listl # bin/change_pw:175 # bin/change_pw:179 #: bin/change_pw:181 +#, fuzzy msgid "New %(listname)s password: %(notifypassword)s" msgstr "Nytt lösenord för %(listname)s: %(notifypassword)s" # bin/change_pw:184 # bin/change_pw:188 #: bin/change_pw:190 +#, fuzzy msgid "Your new %(listname)s list password" msgstr "Ditt nya lösenord för e-postlistan %(listname)s" # bin/change_pw:185 # bin/change_pw:189 #: bin/change_pw:191 +#, fuzzy msgid "" "The site administrator at %(hostname)s has changed the password for your\n" "mailing list %(listname)s. It is now\n" @@ -11927,6 +12092,7 @@ msgstr "" # bin/check_db:19 # bin/check_db:19 #: bin/check_db:19 +#, fuzzy msgid "" "Check a list's config database file for integrity.\n" "\n" @@ -12009,6 +12175,7 @@ msgstr "Lista:" # bin/check_db:144 # bin/check_db:148 #: bin/check_db:148 +#, fuzzy msgid " %(file)s: okay" msgstr " %(file)s: ok" @@ -12038,6 +12205,7 @@ msgstr "" # bin/check_perms:86 # bin/check_perms:81 #: bin/check_perms:110 +#, fuzzy msgid " checking gid and mode for %(path)s" msgstr " kontrollerar gid och rättigheter för %(path)s" @@ -12053,6 +12221,7 @@ msgstr "" # bin/check_perms:121 # bin/check_perms:116 #: bin/check_perms:151 +#, fuzzy msgid "directory permissions must be %(octperms)s: %(path)s" msgstr "rättigheterna på katalogen måste vara %(octperms)s: %(path)s" @@ -12073,6 +12242,7 @@ msgstr "r # bin/check_perms:132 # bin/check_perms:127 #: bin/check_perms:183 +#, fuzzy msgid "checking mode for %(prefix)s" msgstr "kontrollerar rättigheter för %(prefix)s" @@ -12086,18 +12256,21 @@ msgstr "katalogen m # bin/check_perms:140 # bin/check_perms:135 #: bin/check_perms:197 +#, fuzzy msgid "directory must be at least 02775: %(d)s" msgstr "katalogen måste minst ha rättigheterna 02775: %(d)s" # bin/check_perms:153 # bin/check_perms:148 #: bin/check_perms:209 +#, fuzzy msgid "checking perms on %(private)s" msgstr "kontrollerar rättigheter för: %(private)s" # bin/check_perms:158 # bin/check_perms:153 #: bin/check_perms:214 +#, fuzzy msgid "%(private)s must not be other-readable" msgstr "%(private)s får inte vara läsbara för alla" @@ -12119,6 +12292,7 @@ msgstr "mbox-filen m # bin/check_perms:202 # bin/check_perms:197 #: bin/check_perms:263 +#, fuzzy msgid "%(dbdir)s \"other\" perms must be 000" msgstr "rättigheter för \"alla andra\" för katalogen %(dbdir)s måste vara 000" @@ -12131,36 +12305,42 @@ msgstr "kontrollerar r # bin/check_perms:218 # bin/check_perms:213 #: bin/check_perms:278 +#, fuzzy msgid " checking set-gid for %(path)s" msgstr " kontrollerar set-gid för %(path)s" # bin/check_perms:222 # bin/check_perms:217 #: bin/check_perms:282 +#, fuzzy msgid "%(path)s must be set-gid" msgstr "%(path)s måste vara set-gid" # bin/check_perms:232 # bin/check_perms:227 #: bin/check_perms:292 +#, fuzzy msgid "checking set-gid for %(wrapper)s" msgstr "kontrollerar set-gid för %(wrapper)s" # bin/check_perms:236 # bin/check_perms:231 #: bin/check_perms:296 +#, fuzzy msgid "%(wrapper)s must be set-gid" msgstr "%(wrapper)s måste vara set-gid" # bin/check_perms:246 # bin/check_perms:241 #: bin/check_perms:306 +#, fuzzy msgid "checking permissions on %(pwfile)s" msgstr "kontrollerar rättigheter för %(pwfile)s" # bin/check_perms:255 # bin/check_perms:250 #: bin/check_perms:315 +#, fuzzy msgid "%(pwfile)s permissions must be exactly 0640 (got %(octmode)s)" msgstr "" "rättigheterna för %(pwfile)s måste vara satta till 0640 (de är %(octmode)s)" @@ -12174,12 +12354,14 @@ msgstr "kontrollerar r # bin/check_perms:284 # bin/check_perms:280 #: bin/check_perms:348 +#, fuzzy msgid " checking permissions on: %(path)s" msgstr " kontrollerar rättigheter för: %(path)s" # bin/check_perms:292 # bin/check_perms:288 #: bin/check_perms:356 +#, fuzzy msgid "file permissions must be at least 660: %(path)s" msgstr "filrättigheter måste vara minst 660: %(path)s" @@ -12284,6 +12466,7 @@ msgstr "Unix-Fr # bin/cleanarch:106 # bin/cleanarch:110 #: bin/cleanarch:111 +#, fuzzy msgid "Bad status number: %(arg)s" msgstr "Ogiltigt statusnummer: %(arg)s" @@ -12455,12 +12638,14 @@ msgstr " den ursprungliga adressen togs bort:" # bin/clone_member:192 # bin/clone_member:196 #: bin/clone_member:202 +#, fuzzy msgid "Not a valid email address: %(toaddr)s" msgstr "Ingen giltig e-postadress: %(toaddr)s" # bin/clone_member:205 # bin/clone_member:209 #: bin/clone_member:215 +#, fuzzy msgid "" "Error opening list \"%(listname)s\", skipping.\n" "%(e)s" @@ -12601,17 +12786,20 @@ msgstr "giltiga v # bin/config_list:212 # bin/config_list:252 #: bin/config_list:270 +#, fuzzy msgid "attribute \"%(k)s\" ignored" msgstr "hoppar över attribut \"%(k)s\"" # bin/config_list:215 # bin/config_list:255 #: bin/config_list:273 +#, fuzzy msgid "attribute \"%(k)s\" changed" msgstr "ändrat på attributen \"%(k)s\"" # bin/config_list:261 #: bin/config_list:279 +#, fuzzy msgid "Non-standard property restored: %(k)s" msgstr "Icke standardegenskap återställd: %(k)s" @@ -12705,9 +12893,12 @@ msgstr "" msgid "Ignoring non-held message: %(f)s" msgstr "Bortser från ändring av en medlem som är avanmäld: %(user)s" +# Mailman/Cgi/admin.py:1339 +# Mailman/Cgi/admin.py:1359 #: bin/discard:100 +#, fuzzy msgid "Ignoring held msg w/bad id: %(f)s" -msgstr "" +msgstr "Bortser från ändring av en medlem som är avanmäld: %(user)s" # bin/add_members:245 bin/config_list:101 bin/find_member:93 bin/inject:86 # bin/list_admins:85 bin/list_members:175 bin/sync_members:218 @@ -12791,6 +12982,7 @@ msgstr "Inget filnamn angett" # bin/dumpdb:91 # bin/dumpdb:104 #: bin/dumpdb:108 +#, fuzzy msgid "Bad arguments: %(pargs)s" msgstr "Ogiltig parameter: %(pargs)s" @@ -13013,6 +13205,7 @@ msgstr "St # bin/fix_url.py:83 #: bin/fix_url.py:88 +#, fuzzy msgid "Setting host_name to: %(mailhost)s" msgstr "Ställer in host_name till: %(mailhost)s" @@ -13092,6 +13285,7 @@ msgstr "" # bin/inject:79 # bin/inject:83 #: bin/inject:84 +#, fuzzy msgid "Bad queue directory: %(qdir)s" msgstr "Ogiltig kökatalog: %(qdir)s" @@ -13104,6 +13298,7 @@ msgstr "Namn p # bin/list_admins:19 # bin/list_admins:19 #: bin/list_admins:20 +#, fuzzy msgid "" "List all the owners of a mailing list.\n" "\n" @@ -13153,6 +13348,7 @@ msgstr "" # bin/list_admins:91 # bin/list_admins:96 #: bin/list_admins:97 +#, fuzzy msgid "List: %(listname)s, \tOwners: %(owners)s" msgstr "Lista: %(listname)s, \tÄgare: %(owners)s" @@ -13334,12 +13530,14 @@ msgstr "" # bin/list_members:138 # bin/list_members:150 #: bin/list_members:198 +#, fuzzy msgid "Bad --nomail option: %(why)s" msgstr "Ogiltig --nomail parameter: %(why)s" # bin/list_members:149 # bin/list_members:161 #: bin/list_members:209 +#, fuzzy msgid "Bad --digest option: %(kind)s" msgstr "Ogiltig --digest parameter: %(kind)s" @@ -13572,6 +13770,7 @@ msgstr "" # bin/mailmanctl:140 # bin/mailmanctl:145 #: bin/mailmanctl:152 +#, fuzzy msgid "PID unreadable in: %(pidfile)s" msgstr "Oläslig PID i: %(pidfile)s" @@ -13583,6 +13782,7 @@ msgstr "K # bin/mailmanctl:148 # bin/mailmanctl:153 #: bin/mailmanctl:160 +#, fuzzy msgid "No child with pid: %(pid)s" msgstr "Ingen child med pid: %(pid)s" @@ -13616,6 +13816,7 @@ msgstr "" # bin/mailmanctl:220 # bin/mailmanctl:225 #: bin/mailmanctl:233 +#, fuzzy msgid "" "The master qrunner lock could not be acquired, because it appears as if " "some\n" @@ -13644,12 +13845,14 @@ msgstr "" # cron/mailpasswds:91 # cron/mailpasswds:111 #: bin/mailmanctl:280 cron/mailpasswds:119 +#, fuzzy msgid "Site list is missing: %(sitelistname)s" msgstr "Systemets e-postlista saknas: %(sitelistname)s" # bin/mailmanctl:269 # bin/mailmanctl:278 #: bin/mailmanctl:305 +#, fuzzy msgid "Run this program as root or as the %(name)s user, or use -u." msgstr "Kör detta program som root eller som %(name)s, eller använd -u." @@ -13662,6 +13865,7 @@ msgstr "Inget kommando angett." # bin/mailmanctl:303 # bin/mailmanctl:312 #: bin/mailmanctl:339 +#, fuzzy msgid "Bad command: %(command)s" msgstr "Ogiltigt kommando: %(command)s" @@ -13696,6 +13900,7 @@ msgstr "Startar Mailmans master qrunner." # bin/mmsitepass:19 # bin/mmsitepass:19 #: bin/mmsitepass:19 +#, fuzzy msgid "" "Set the site password, prompting from the terminal.\n" "\n" @@ -13758,6 +13963,7 @@ msgstr "person som uppr # bin/mmsitepass:82 # bin/mmsitepass:86 #: bin/mmsitepass:86 +#, fuzzy msgid "New %(pwdesc)s password: " msgstr "Nytt %(pwdesc)s lösenord: " @@ -13986,6 +14192,7 @@ msgstr "" # bin/newlist:114 # bin/newlist:118 #: bin/newlist:162 +#, fuzzy msgid "Unknown language: %(lang)s" msgstr "Okänt språk: %(lang)s" @@ -14004,6 +14211,7 @@ msgstr "Uppge e-postadressen till den person som ansvarar f # bin/newlist:141 # bin/newlist:145 #: bin/newlist:193 +#, fuzzy msgid "Initial %(listname)s password: " msgstr "Det första lösenordet för \"%(listname)s\" är: " @@ -14015,13 +14223,14 @@ msgstr "Listan m #: bin/newlist:220 msgid "" -" - owner addresses need to be fully-qualified names like \"owner@example.com" -"\", not just \"owner\"." +" - owner addresses need to be fully-qualified names like \"owner@example." +"com\", not just \"owner\"." msgstr "" # bin/newlist:184 # bin/newlist:190 #: bin/newlist:244 +#, fuzzy msgid "Hit enter to notify %(listname)s owner..." msgstr "" "Tryck [Enter] för att skicka meddelande till ägaren av listan " @@ -14097,6 +14306,7 @@ msgstr "" # bin/qrunner:172 # bin/qrunner:176 #: bin/qrunner:179 +#, fuzzy msgid "%(name)s runs the %(runnername)s qrunner" msgstr "%(name)s startar %(runnername)s qrunnern" @@ -14228,24 +14438,28 @@ msgstr "" # bin/remove_members:128 # bin/remove_members:132 #: bin/remove_members:156 +#, fuzzy msgid "Could not open file for reading: %(filename)s." msgstr "Kunde inte öppna filen \"%(filename)s\" för att läsa." # bin/remove_members:135 # bin/remove_members:139 #: bin/remove_members:163 +#, fuzzy msgid "Error opening list %(listname)s... skipping." msgstr "Hoppar över listan \"%(listname)s\" p g a fel under öppnandet." # bin/remove_members:145 # bin/remove_members:149 #: bin/remove_members:173 +#, fuzzy msgid "No such member: %(addr)s" msgstr "Medlemmen finns inte: %(addr)s." # bin/remove_members:149 # bin/remove_members:153 #: bin/remove_members:178 +#, fuzzy msgid "User `%(addr)s' removed from list: %(listname)s." msgstr "%(addr)s är nu borttagen från listan %(listname)s." @@ -14326,6 +14540,7 @@ msgstr "" # bin/rmlist:61 bin/rmlist:64 # bin/rmlist:65 bin/rmlist:68 #: bin/rmlist:73 bin/rmlist:76 +#, fuzzy msgid "Removing %(msg)s" msgstr "Tar bort %(msg)s" @@ -14339,12 +14554,14 @@ msgstr "Hittade inte %(listname)s %(msg)s som %(filename)s" # bin/rmlist:91 # bin/rmlist:95 #: bin/rmlist:105 +#, fuzzy msgid "No such list (or list already deleted): %(listname)s" msgstr "Listan finns inte (eller är redan raderad): %(listname)s" # bin/rmlist:93 # bin/rmlist:97 #: bin/rmlist:108 +#, fuzzy msgid "No such list: %(listname)s. Removing its residual archives." msgstr "Listan finns inte: %(listname)s. Tar bort arkivet som ligger kvar." @@ -14545,6 +14762,7 @@ msgstr "" # bin/sync_members:111 # bin/sync_members:115 #: bin/sync_members:115 +#, fuzzy msgid "Bad choice: %(yesno)s" msgstr "Ogiltigt val: %(yesno)s" @@ -14569,6 +14787,7 @@ msgstr "\"-f\" parametern saknar v # bin/sync_members:168 # bin/sync_members:172 #: bin/sync_members:172 +#, fuzzy msgid "Illegal option: %(opt)s" msgstr "Ogiltig parameter: %(opt)s" @@ -14587,6 +14806,7 @@ msgstr "M # bin/sync_members:187 # bin/sync_members:191 #: bin/sync_members:191 +#, fuzzy msgid "Cannot read address file: %(filename)s: %(msg)s" msgstr "Kan inte läsa adressfil: %(filename)s: %(msg)s" @@ -14611,16 +14831,17 @@ msgstr "Du m # bin/sync_members:254 # bin/sync_members:258 #: bin/sync_members:264 +#, fuzzy msgid "Added : %(s)s" msgstr "Lade till : %(s)s" # bin/rmlist:61 bin/rmlist:64 # bin/rmlist:65 bin/rmlist:68 #: bin/sync_members:289 +#, fuzzy msgid "Removed: %(s)s" msgstr "Tar bort %(s)s" -# bin/transcheck:18 #: bin/transcheck:19 msgid "" "\n" @@ -14703,7 +14924,6 @@ msgid "" "will result in losing all the messages in that queue.\n" msgstr "" -# bin/unshunt:81 #: bin/unshunt:85 msgid "" "Cannot unshunt message %(filebase)s, skipping:\n" @@ -14713,6 +14933,7 @@ msgstr "" # bin/update:19 # bin/update:19 #: bin/update:20 +#, fuzzy msgid "" "Perform all necessary upgrades.\n" "\n" @@ -14755,16 +14976,17 @@ msgstr "" # bin/update:99 # bin/update:101 #: bin/update:107 +#, fuzzy msgid "Fixing language templates: %(listname)s" msgstr "Uppdaterar språkfiler: %(listname)s" # bin/update:188 bin/update:442 # bin/update:190 bin/update:465 #: bin/update:196 bin/update:711 +#, fuzzy msgid "WARNING: could not acquire lock for list: %(listname)s" msgstr "VARNING: kunde inte låsa listan: %(listname)s" -# bin/update:209 #: bin/update:215 msgid "Resetting %(n)s BYBOUNCEs disabled addrs with no bounce info" msgstr "" @@ -14789,6 +15011,7 @@ msgstr "" # bin/update:227 # bin/update:249 #: bin/update:255 +#, fuzzy msgid "" "\n" "%(listname)s has both public and private mbox archives. Since this list\n" @@ -14847,6 +15070,7 @@ msgstr "- uppdaterar den gamla privata mbox-filen" # bin/update:267 # bin/update:289 #: bin/update:295 +#, fuzzy msgid "" " unknown file in the way, moving\n" " %(o_pri_mbox_file)s\n" @@ -14867,6 +15091,7 @@ msgstr "- uppdaterar den gamla offentliga mbox-filen" # bin/update:291 # bin/update:313 #: bin/update:317 +#, fuzzy msgid "" " unknown file in the way, moving\n" " %(o_pub_mbox_file)s\n" @@ -14906,24 +15131,28 @@ msgstr "- b # bin/update:361 # bin/update:383 #: bin/update:396 +#, fuzzy msgid "removing directory %(src)s and everything underneath" msgstr "tar bort katalogen %(src)s och alla underkataloger" # bin/update:364 # bin/update:386 #: bin/update:399 +#, fuzzy msgid "removing %(src)s" msgstr "tar bort %(src)s" # bin/update:368 # bin/update:390 #: bin/update:403 +#, fuzzy msgid "Warning: couldn't remove %(src)s -- %(rest)s" msgstr "Varning: kunde inte ta bort %(src)s -- %(rest)s" # bin/update:373 # bin/update:395 #: bin/update:408 +#, fuzzy msgid "couldn't remove old file %(pyc)s -- %(rest)s" msgstr "kunde inte ta bort den gamla filen %(pyc)s -- %(rest)s" @@ -15001,6 +15230,7 @@ msgstr "utf # bin/update:423 # bin/update:445 #: bin/update:691 +#, fuzzy msgid "Updating mailing list: %(listname)s" msgstr "Uppdaterar e-postlista: %(listname)s" @@ -15077,6 +15307,7 @@ msgstr "Ingen uppdatering # bin/update:536 # bin/update:563 #: bin/update:796 +#, fuzzy msgid "" "Downgrade detected, from version %(hexlversion)s to version %(hextversion)s\n" "This is probably not safe.\n" @@ -15089,12 +15320,14 @@ msgstr "" # bin/update:541 # bin/update:568 #: bin/update:801 +#, fuzzy msgid "Upgrading from version %(hexlversion)s to %(hextversion)s" msgstr "Uppgraderar från version %(hexlversion)s till %(hextversion)s" # bin/update:550 # bin/update:577 #: bin/update:810 +#, fuzzy msgid "" "\n" "ERROR:\n" @@ -15267,6 +15500,7 @@ msgstr "" # bin/withlist:158 # bin/withlist:162 #: bin/withlist:175 +#, fuzzy msgid "Unlocking (but not saving) list: %(listname)s" msgstr "Låser upp (men sparar inte ) listan: %(listname)s" @@ -15279,6 +15513,7 @@ msgstr "Avslutar" # bin/withlist:171 # bin/withlist:175 #: bin/withlist:188 +#, fuzzy msgid "Loading list %(listname)s" msgstr "Läser listan %(listname)s" @@ -15297,6 +15532,7 @@ msgstr "( # bin/withlist:180 # bin/withlist:184 #: bin/withlist:197 +#, fuzzy msgid "Unknown list: %(listname)s" msgstr "Okänd lista: %(listname)s" @@ -15315,24 +15551,28 @@ msgstr "--all kr # bin/withlist:242 # bin/withlist:246 #: bin/withlist:266 +#, fuzzy msgid "Importing %(module)s..." msgstr "Importerar %(module)s..." # bin/withlist:245 # bin/withlist:249 #: bin/withlist:270 +#, fuzzy msgid "Running %(module)s.%(callable)s()..." msgstr "Kör %(module)s.%(callable)s()..." # bin/withlist:266 # bin/withlist:270 #: bin/withlist:291 +#, fuzzy msgid "The variable `m' is the %(listname)s MailList instance" msgstr "Variabeln 'm' är förekomsten av %(listname)s MailList objektet" # cron/bumpdigests:19 # cron/bumpdigests:19 #: cron/bumpdigests:19 +#, fuzzy msgid "" "Increment the digest volume number and reset the digest number to one.\n" "\n" @@ -15394,6 +15634,7 @@ msgstr "" # cron/checkdbs:68 # cron/checkdbs:81 #: cron/checkdbs:121 +#, fuzzy msgid "%(count)d %(realname)s moderator request(s) waiting" msgstr "%(count)d frågor väntar på behandling på listan %(realname)s" @@ -15460,7 +15701,6 @@ msgid "" " Print this message and exit.\n" msgstr "" -# cron/disabled:19 #: cron/disabled:20 msgid "" "Process disabled members, recommended once per day.\n" @@ -15606,12 +15846,14 @@ msgstr "L # cron/mailpasswds:177 # cron/mailpasswds:197 #: cron/mailpasswds:222 +#, fuzzy msgid "%(host)s mailing list memberships reminder" msgstr "Påminnelse om lösenord för e-postlistor på %(host)s" # cron/nightly_gzip:19 # cron/nightly_gzip:19 #: cron/nightly_gzip:19 +#, fuzzy msgid "" "Re-generate the Pipermail gzip'd archive flat files.\n" "\n" @@ -16563,8 +16805,8 @@ msgstr "" #~ msgid "%(rname)s member %(addr)s bouncing - %(negative)s%(did)s" #~ msgstr "" -#~ "Adressen till %(rname)s, %(addr)s, kommer bara i retur - %(negative)s" -#~ "%(did)s" +#~ "Adressen till %(rname)s, %(addr)s, kommer bara i retur - " +#~ "%(negative)s%(did)s" #~ msgid "User not found." #~ msgstr "Medlemmen finns inte." diff --git a/messages/tr/LC_MESSAGES/mailman.po b/messages/tr/LC_MESSAGES/mailman.po index 15cbd0a1..62dce331 100755 --- a/messages/tr/LC_MESSAGES/mailman.po +++ b/messages/tr/LC_MESSAGES/mailman.po @@ -66,10 +66,12 @@ msgid "

                  Currently, there are no archives.

                  " msgstr "

                  Þu anda herhangi bir arþiv yok.

                  " #: Mailman/Archiver/HyperArch.py:821 +#, fuzzy msgid "Gzip'd Text%(sz)s" msgstr "Gzip'li Yazý%(sz)s" #: Mailman/Archiver/HyperArch.py:826 +#, fuzzy msgid "Text%(sz)s" msgstr "Yazý%(sz)s" @@ -142,18 +144,22 @@ msgid "Third" msgstr "Üçüncü" #: Mailman/Archiver/HyperArch.py:938 +#, fuzzy msgid "%(ord)s quarter %(year)i" msgstr "%(ord)s çeyrek %(year)i" #: Mailman/Archiver/HyperArch.py:945 +#, fuzzy msgid "%(month)s %(year)i" msgstr "%(month)s %(year)i" #: Mailman/Archiver/HyperArch.py:950 +#, fuzzy msgid "The Week Of Monday %(day)i %(month)s %(year)i" msgstr "Pazartesi %(day)i %(month)s %(year)i Haftasý" #: Mailman/Archiver/HyperArch.py:954 +#, fuzzy msgid "%(day)i %(month)s %(year)i" msgstr "%(day)i %(month)s %(year)i" @@ -162,10 +168,12 @@ msgid "Computing threaded index\n" msgstr "Thread'li indeks hesaplanýyor\n" #: Mailman/Archiver/HyperArch.py:1319 +#, fuzzy msgid "Updating HTML for article %(seq)s" msgstr "%(seq)s makalesi için HTML güncelleniyor" #: Mailman/Archiver/HyperArch.py:1326 +#, fuzzy msgid "article file %(filename)s is missing!" msgstr "makale dosyasý %(filename)s yok!" @@ -182,6 +190,7 @@ msgid "Pickling archive state into " msgstr "Arþiv durumu dönüþtürülüyor: " #: Mailman/Archiver/pipermail.py:453 +#, fuzzy msgid "Updating index files for archive [%(archive)s]" msgstr "[%(archive)s] arþivi için indeks dosyalarý güncelleniyor" @@ -190,6 +199,7 @@ msgid " Thread" msgstr " Thread" #: Mailman/Archiver/pipermail.py:597 +#, fuzzy msgid "#%(counter)05d %(msgid)s" msgstr "#%(counter)05d %(msgid)s" @@ -227,6 +237,7 @@ msgid "disabled address" msgstr "etkin deðil" #: Mailman/Bouncer.py:304 +#, fuzzy msgid " The last bounce received from you was dated %(date)s" msgstr " Sizden gelen son geri dönüþ %(date)s tarihindeydi" @@ -254,6 +265,7 @@ msgstr "Y #: Mailman/Cgi/options.py:101 Mailman/Cgi/private.py:108 #: Mailman/Cgi/rmlist.py:75 Mailman/Cgi/roster.py:59 #: Mailman/Cgi/subscribe.py:67 +#, fuzzy msgid "No such list %(safelistname)s" msgstr "%(safelistname)s adýnda bir liste yok" @@ -325,6 +337,7 @@ msgstr "" " e-posta alamayacaklar.%(rm)r" #: Mailman/Cgi/admin.py:268 +#, fuzzy msgid "%(hostname)s mailing lists - Admin Links" msgstr "%(hostname)s mesaj listeleri - Yönetici Linkleri" @@ -337,6 +350,7 @@ msgid "Mailman" msgstr "Mailman" #: Mailman/Cgi/admin.py:310 +#, fuzzy msgid "" "

                  There currently are no publicly-advertised %(mailmanlink)s\n" " mailing lists on %(hostname)s." @@ -345,6 +359,7 @@ msgstr "" "%(mailmanlink)s mesaj listesi yok." #: Mailman/Cgi/admin.py:316 +#, fuzzy msgid "" "

                  Below is the collection of publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s. Click on a list\n" @@ -361,6 +376,7 @@ msgid "right " msgstr "sað " #: Mailman/Cgi/admin.py:325 +#, fuzzy msgid "" "To visit the administrators configuration page for an\n" " unadvertised list, open a URL similar to this one, but with a '/' " @@ -406,6 +422,7 @@ msgid "No valid variable name found." msgstr "Geçerli bir deðiþken ismi bulunamadý." #: Mailman/Cgi/admin.py:395 +#, fuzzy msgid "" "%(realname)s Mailing list Configuration Help\n" "
                  %(varname)s Option" @@ -414,6 +431,7 @@ msgstr "" "
                  %(varname)s Seçeneði" #: Mailman/Cgi/admin.py:402 +#, fuzzy msgid "Mailman %(varname)s List Option Help" msgstr "Mailman%(varname)s Liste Seçenek Yardýmý" @@ -433,14 +451,17 @@ msgstr "" " " #: Mailman/Cgi/admin.py:431 +#, fuzzy msgid "return to the %(categoryname)s options page." msgstr "%(categoryname)s seçenek sayfasýna da dönebilirsiniz." #: Mailman/Cgi/admin.py:446 +#, fuzzy msgid "%(realname)s Administration (%(label)s)" msgstr "%(realname)s Yönetimi (%(label)s)" #: Mailman/Cgi/admin.py:447 +#, fuzzy msgid "%(realname)s mailing list administration
                  %(label)s Section" msgstr "%(realname)s mesaj listesi yönetimi
                  %(label)s Bölümü" @@ -523,6 +544,7 @@ msgid "Value" msgstr "Deðer" #: Mailman/Cgi/admin.py:668 +#, fuzzy msgid "" "Badly formed options entry:\n" " %(record)s" @@ -623,10 +645,12 @@ msgid "Move rule down" msgstr "Kuralý aþaðý al" #: Mailman/Cgi/admin.py:870 +#, fuzzy msgid "
                  (Edit %(varname)s)" msgstr "
                  (Düzenle: %(varname)s)" #: Mailman/Cgi/admin.py:872 +#, fuzzy msgid "
                  (Details for %(varname)s)" msgstr "
                  (%(varname)s için Ayrýntýlar)" @@ -667,6 +691,7 @@ msgid "(help)" msgstr "(yardým)" #: Mailman/Cgi/admin.py:930 +#, fuzzy msgid "Find member %(link)s:" msgstr "Üye bul %(link)s:" @@ -679,10 +704,12 @@ msgid "Bad regular expression: " msgstr "Hatalý regular expression: " #: Mailman/Cgi/admin.py:1013 +#, fuzzy msgid "%(allcnt)s members total, %(membercnt)s shown" msgstr "toplam %(allcnt)s üye, %(membercnt)s üye görüntüleniyor" #: Mailman/Cgi/admin.py:1016 +#, fuzzy msgid "%(allcnt)s members total" msgstr "toplam %(allcnt)s üye" @@ -874,6 +901,7 @@ msgstr "" " aralýða týklayýn:" #: Mailman/Cgi/admin.py:1221 +#, fuzzy msgid "from %(start)s to %(end)s" msgstr "%(start)s ile %(end)s arasý" @@ -1011,6 +1039,7 @@ msgid "Change list ownership passwords" msgstr "Liste sahibi þifrelerini deðiþtir" #: Mailman/Cgi/admin.py:1364 +#, fuzzy msgid "" "The list administrators are the people who have ultimate control " "over\n" @@ -1186,8 +1215,9 @@ msgid "%(schange_to)s is already a member" msgstr " zaten üye" #: Mailman/Cgi/admin.py:1620 +#, fuzzy msgid "%(schange_to)s matches banned pattern %(spat)s" -msgstr "" +msgstr " zaten üye" #: Mailman/Cgi/admin.py:1622 msgid "Address %(schange_from)s changed to %(schange_to)s" @@ -1227,6 +1257,7 @@ msgid "Not subscribed" msgstr "Üye deðil" #: Mailman/Cgi/admin.py:1773 +#, fuzzy msgid "Ignoring changes to deleted member: %(user)s" msgstr "Silinmiþ üye üzerindeki deðiþiklikler gözardý ediliyor: %(user)s" @@ -1239,10 +1270,12 @@ msgid "Error Unsubscribing:" msgstr "Üyelikten çýkarýlýrken hata oldu:" #: Mailman/Cgi/admindb.py:235 Mailman/Cgi/admindb.py:248 +#, fuzzy msgid "%(realname)s Administrative Database" msgstr "%(realname)s Yönetimsel Veritabaný" #: Mailman/Cgi/admindb.py:238 +#, fuzzy msgid "%(realname)s Administrative Database Results" msgstr "%(realname)s Yönetimsel Veritabaný Sonuçlarý" @@ -1271,6 +1304,7 @@ msgid "Discard all messages marked Defer" msgstr "" #: Mailman/Cgi/admindb.py:298 +#, fuzzy msgid "all of %(esender)s's held messages." msgstr "%(esender)s göndericisinin tüm bekletilen mesajlarý." @@ -1291,6 +1325,7 @@ msgid "list of available mailing lists." msgstr "mevcut mesaj listelerinin listesi." #: Mailman/Cgi/admindb.py:362 +#, fuzzy msgid "You must specify a list name. Here is the %(link)s" msgstr "Bir liste ismi belirtmelisiniz. Ýþte %(link)s" @@ -1373,6 +1408,7 @@ msgid "The sender is now a member of this list" msgstr "Gönderici artýk bu listenin bir üyesi" #: Mailman/Cgi/admindb.py:568 +#, fuzzy msgid "Add %(esender)s to one of these sender filters:" msgstr "%(esender)s adresini þu gönderici filtrelerinden birine ekle:" @@ -1393,6 +1429,7 @@ msgid "Rejects" msgstr "Reddedilecekler" #: Mailman/Cgi/admindb.py:584 +#, fuzzy msgid "" "Ban %(esender)s from ever subscribing to this\n" " mailing list" @@ -1409,6 +1446,7 @@ msgstr "" " týklayabilir, veya " #: Mailman/Cgi/admindb.py:591 +#, fuzzy msgid "view all messages from %(esender)s" msgstr "%(esender)s adresinden gelen tüm mesajlarý görebilirsiniz" @@ -1537,6 +1575,7 @@ msgstr "" " çýkarýlmýþ. Bu istek iptal edildi." #: Mailman/Cgi/confirm.py:178 +#, fuzzy msgid "System error, bad content: %(content)s" msgstr "Sistem hatasý, hatalý içerik: %(content)s" @@ -1574,6 +1613,7 @@ msgid "Confirm subscription request" msgstr "Üyelik isteðini onaylayýn" #: Mailman/Cgi/confirm.py:259 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " subscription request to the mailing list %(listname)s. Your\n" @@ -1609,6 +1649,7 @@ msgstr "" " düðmesine týklayarak üyelikten vazgeçebilirsiniz." #: Mailman/Cgi/confirm.py:275 +#, fuzzy msgid "" "Your confirmation is required in order to continue with\n" " the subscription request to the mailing list %(listname)s.\n" @@ -1663,6 +1704,7 @@ msgid "Preferred language:" msgstr "Tercih ettiðiniz dil:" #: Mailman/Cgi/confirm.py:317 +#, fuzzy msgid "Subscribe to list %(listname)s" msgstr "%(listname)s listesine üye ol" @@ -1679,6 +1721,7 @@ msgid "Awaiting moderator approval" msgstr "Moderatör onayý bekleniyor" #: Mailman/Cgi/confirm.py:386 +#, fuzzy msgid "" " You have successfully confirmed your subscription request to " "the\n" @@ -1736,6 +1779,7 @@ msgid "Subscription request confirmed" msgstr "Üyelik isteði onaylandý" #: Mailman/Cgi/confirm.py:418 +#, fuzzy msgid "" " You have successfully confirmed your subscription request for\n" " \"%(addr)s\" to the %(listname)s mailing list. A separate\n" @@ -1766,6 +1810,7 @@ msgid "Unsubscription request confirmed" msgstr "Üyelikten çýkma isteði onaylandý" #: Mailman/Cgi/confirm.py:469 +#, fuzzy msgid "" " You have successfully unsubscribed from the %(listname)s " "mailing\n" @@ -1787,6 +1832,7 @@ msgid "Not available" msgstr "Kullanýlabilir deðil" #: Mailman/Cgi/confirm.py:498 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " unsubscription request from the mailing list %(listname)s. " @@ -1858,6 +1904,7 @@ msgid "Change of address request confirmed" msgstr "Adres deðiþikliði isteði onaylandý" #: Mailman/Cgi/confirm.py:571 +#, fuzzy msgid "" " You have successfully changed your address on the %(listname)s\n" " mailing list from %(oldaddr)s to %(newaddr)s. " @@ -1879,6 +1926,7 @@ msgid "globally" msgstr "global olarak" #: Mailman/Cgi/confirm.py:605 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " change of address request for the mailing list %(listname)s. " @@ -1936,6 +1984,7 @@ msgid "Sender discarded message via web." msgstr "Gönderici mesajý web üzerinden gözardý etti." #: Mailman/Cgi/confirm.py:674 +#, fuzzy msgid "" "The held message with the Subject:\n" " header %(subject)s could not be found. The most " @@ -1956,6 +2005,7 @@ msgid "Posted message canceled" msgstr "Gönderilen mesajdan vazgeçildi" #: Mailman/Cgi/confirm.py:685 +#, fuzzy msgid "" " You have successfully canceled the posting of your message with\n" " the Subject: header %(subject)s to the mailing list\n" @@ -1977,6 +2027,7 @@ msgstr "" " tarafýndan zaten incelendi." #: Mailman/Cgi/confirm.py:735 +#, fuzzy msgid "" "Your confirmation is required in order to cancel the\n" " posting of your message to the mailing list %(listname)s:\n" @@ -2026,6 +2077,7 @@ msgid "Membership re-enabled." msgstr "Üyelik yeniden etkinleþtirildi." #: Mailman/Cgi/confirm.py:798 +#, fuzzy msgid "" " You have successfully re-enabled your membership in the\n" " %(listname)s mailing list. You can now not available" msgstr "kullanýlabilir deðil" #: Mailman/Cgi/confirm.py:846 +#, fuzzy msgid "" "Your membership in the %(realname)s mailing list is\n" " currently disabled due to excessive bounces. Your confirmation is\n" @@ -2126,10 +2180,12 @@ msgid "administrative list overview" msgstr "yönetimsel liste tanýtým sayfasý" #: Mailman/Cgi/create.py:115 +#, fuzzy msgid "List name must not include \"@\": %(safelistname)s" msgstr "Liste ismi \"@\" karakterini içeremez: %(safelistname)s" #: Mailman/Cgi/create.py:122 +#, fuzzy msgid "List already exists: %(safelistname)s" msgstr "Bu liste zaten var: %(safelistname)s" @@ -2163,18 +2219,22 @@ msgid "You are not authorized to create new mailing lists" msgstr "Yeni mesaj listesi oluþturmak için yetkili deðilsiniz" #: Mailman/Cgi/create.py:182 +#, fuzzy msgid "Unknown virtual host: %(safehostname)s" msgstr "Bilinmeyen sanal host: %(safehostname)s" #: Mailman/Cgi/create.py:218 bin/newlist:219 +#, fuzzy msgid "Bad owner email address: %(s)s" msgstr "Kötü sahip e-posta adresi: %(s)s" #: Mailman/Cgi/create.py:223 bin/newlist:182 bin/newlist:223 +#, fuzzy msgid "List already exists: %(listname)s" msgstr "Liste zaten var: %(listname)s" #: Mailman/Cgi/create.py:231 bin/newlist:217 +#, fuzzy msgid "Illegal list name: %(s)s" msgstr "Geçersiz liste ismi: %(s)s" @@ -2187,6 +2247,7 @@ msgstr "" " Yardým için yöneticinize baþvurun." #: Mailman/Cgi/create.py:273 bin/newlist:265 +#, fuzzy msgid "Your new mailing list: %(listname)s" msgstr "Yeni mesaj listeniz: %(listname)s" @@ -2195,6 +2256,7 @@ msgid "Mailing list creation results" msgstr "Mesaj listesi oluþturma sonuçlarý" #: Mailman/Cgi/create.py:288 +#, fuzzy msgid "" "You have successfully created the mailing list\n" " %(listname)s and notification has been sent to the list owner\n" @@ -2218,6 +2280,7 @@ msgid "Create another list" msgstr "Baþka bir liste oluþturabilirsiniz" #: Mailman/Cgi/create.py:312 +#, fuzzy msgid "Create a %(hostname)s Mailing List" msgstr "Bir %(hostname)s Mesaj Listesi Oluþtur" @@ -2321,6 +2384,7 @@ msgstr "" " mesajlarýnýn moderatör onayý gerektirmesi için Evet seçin." #: Mailman/Cgi/create.py:432 +#, fuzzy msgid "" "Initial list of supported languages.

                  Note that if you do not\n" " select at least one initial language, the list will use the server\n" @@ -2425,6 +2489,7 @@ msgid "List name is required." msgstr "Liste ismi gerekiyor." #: Mailman/Cgi/edithtml.py:154 +#, fuzzy msgid "%(realname)s -- Edit html for %(template_info)s" msgstr "%(realname)s -- %(template_info)s için html düzenle" @@ -2433,10 +2498,12 @@ msgid "Edit HTML : Error" msgstr "HTML düzenle: Hata" #: Mailman/Cgi/edithtml.py:161 +#, fuzzy msgid "%(safetemplatename)s: Invalid template" msgstr "%(safetemplatename)s: Geçersiz þablon" #: Mailman/Cgi/edithtml.py:166 Mailman/Cgi/edithtml.py:167 +#, fuzzy msgid "%(realname)s -- HTML Page Editing" msgstr "%(realname)s -- HTML Sayfa Düzenleme" @@ -2495,10 +2562,12 @@ msgid "HTML successfully updated." msgstr "HTML baþarýyla güncellendi." #: Mailman/Cgi/listinfo.py:89 +#, fuzzy msgid "%(hostname)s Mailing Lists" msgstr "%(hostname)s Mesaj Listeleri" #: Mailman/Cgi/listinfo.py:127 +#, fuzzy msgid "" "

                  There currently are no publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s." @@ -2507,6 +2576,7 @@ msgstr "" " %(mailmanlink)s mesaj listesi yok." #: Mailman/Cgi/listinfo.py:131 +#, fuzzy msgid "" "

                  Below is a listing of all the public mailing lists on\n" " %(hostname)s. Click on a list name to get more information " @@ -2527,6 +2597,7 @@ msgid "right" msgstr "sað" #: Mailman/Cgi/listinfo.py:140 +#, fuzzy msgid "" " To visit the general information page for an unadvertised list,\n" " open a URL similar to this one, but with a '/' and the %(adj)s\n" @@ -2589,6 +2660,7 @@ msgstr "Ge #: Mailman/Cgi/options.py:183 Mailman/Cgi/options.py:240 #: Mailman/Cgi/options.py:264 +#, fuzzy msgid "No such member: %(safeuser)s." msgstr "Böyle bir üye yok: %(safeuser)s." @@ -2639,6 +2711,7 @@ msgid "Note: " msgstr "" #: Mailman/Cgi/options.py:378 +#, fuzzy msgid "List subscriptions for %(safeuser)s on %(hostname)s" msgstr "%(safeuser)s için %(hostname)s üzerindeki liste üyelikleri" @@ -2666,6 +2739,7 @@ msgid "You are already using that email address" msgstr "Zaten o e-posta adresini kullanýyorsunuz" #: Mailman/Cgi/options.py:459 +#, fuzzy msgid "" "The new address you requested %(newaddr)s is already a member of the\n" "%(listname)s mailing list, however you have also requested a global change " @@ -2680,6 +2754,7 @@ msgstr "" "adresini içeren tüm diðer mesaj listeleri deðiþtirilecek. " #: Mailman/Cgi/options.py:468 +#, fuzzy msgid "The new address is already a member: %(newaddr)s" msgstr "Yeni adres zaten bir üye: %(newaddr)s" @@ -2688,6 +2763,7 @@ msgid "Addresses may not be blank" msgstr "Adresler boþ olamaz" #: Mailman/Cgi/options.py:488 +#, fuzzy msgid "A confirmation message has been sent to %(newaddr)s. " msgstr "%(newaddr)s adresine bir onay mesajý gönderildi. " @@ -2700,6 +2776,7 @@ msgid "Illegal email address provided" msgstr "Geçersiz e-posta adresi verildi" #: Mailman/Cgi/options.py:501 +#, fuzzy msgid "%(newaddr)s is already a member of the list." msgstr "%(newaddr)s zaten listenin bir üyesi." @@ -2785,6 +2862,7 @@ msgstr "" " bir bildirim alacaksýnýz." #: Mailman/Cgi/options.py:623 +#, fuzzy msgid "" "You have been successfully unsubscribed from the\n" " mailing list %(fqdn_listname)s. If you were receiving digest\n" @@ -2878,6 +2956,7 @@ msgid "day" msgstr "gün" #: Mailman/Cgi/options.py:900 +#, fuzzy msgid "%(days)d %(units)s" msgstr "%(days)d %(units)s" @@ -2890,6 +2969,7 @@ msgid "No topics defined" msgstr "Herhangi bir konu tanýmlanmadý" #: Mailman/Cgi/options.py:940 +#, fuzzy msgid "" "\n" "You are subscribed to this list with the case-preserved address\n" @@ -2900,6 +2980,7 @@ msgstr "" "em>adresiyle üyesiniz." #: Mailman/Cgi/options.py:956 +#, fuzzy msgid "%(realname)s list: member options login page" msgstr "%(realname)s listesi: üyelik seçenekleri giriþ sayfasý" @@ -2908,10 +2989,12 @@ msgid "email address and " msgstr "e-posta adresi ve " #: Mailman/Cgi/options.py:960 +#, fuzzy msgid "%(realname)s list: member options for user %(safeuser)s" msgstr "%(realname)s listesi: %(safeuser)s kullanýcýsý için üyelik seçenekleri" #: Mailman/Cgi/options.py:986 +#, fuzzy msgid "" "In order to change your membership option, you must\n" " first log in by giving your %(extra)smembership password in the section\n" @@ -2990,6 +3073,7 @@ msgid "" msgstr "" #: Mailman/Cgi/options.py:1140 +#, fuzzy msgid "Requested topic is not valid: %(topicname)s" msgstr "Ýstenen konu geçerli deðil: %(topicname)s" @@ -3018,6 +3102,7 @@ msgid "Private archive - \"./\" and \"../\" not allowed in URL." msgstr "" #: Mailman/Cgi/private.py:109 +#, fuzzy msgid "Private Archive Error - %(msg)s" msgstr "Özel Arþiv Hatasý - %(msg)s" @@ -3055,6 +3140,7 @@ msgid "Mailing list deletion results" msgstr "Mesaj listesi silme sonuçlarý" #: Mailman/Cgi/rmlist.py:187 +#, fuzzy msgid "" "You have successfully deleted the mailing list\n" " %(listname)s." @@ -3063,6 +3149,7 @@ msgstr "" " sildiniz." #: Mailman/Cgi/rmlist.py:191 +#, fuzzy msgid "" "There were some problems deleting the mailing list\n" " %(listname)s. Contact your site administrator at " @@ -3074,6 +3161,7 @@ msgstr "" " yöneticinizle baðlantý kurun." #: Mailman/Cgi/rmlist.py:208 +#, fuzzy msgid "Permanently remove mailing list %(realname)s" msgstr "%(realname)s mesaj listesini kalýcý olarak sil" @@ -3147,6 +3235,7 @@ msgid "Invalid options to CGI script" msgstr "CGI betiðine geçersiz seçenek" #: Mailman/Cgi/roster.py:118 +#, fuzzy msgid "%(realname)s roster authentication failed." msgstr "%(realname)s liste kimlik doðrulamasý baþarýsýz oldu." @@ -3216,6 +3305,7 @@ msgstr "" "sonra gerekli bilgileri içeren bir onay e-postasý alacaksýnýz." #: Mailman/Cgi/subscribe.py:262 +#, fuzzy msgid "" "The email address you supplied is banned from this\n" " mailing list. If you think this restriction is erroneous, please\n" @@ -3243,6 +3333,7 @@ msgstr "" "deðil." #: Mailman/Cgi/subscribe.py:278 +#, fuzzy msgid "" "Confirmation from your email address is required, to prevent anyone from\n" "subscribing you without permission. Instructions are being sent to you at\n" @@ -3255,6 +3346,7 @@ msgstr "" "dikkat edin." #: Mailman/Cgi/subscribe.py:290 +#, fuzzy msgid "" "Your subscription request was deferred because %(x)s. Your request has " "been\n" @@ -3275,6 +3367,7 @@ msgid "Mailman privacy alert" msgstr "Mailman gizlilik uyarýsý" #: Mailman/Cgi/subscribe.py:316 +#, fuzzy msgid "" "An attempt was made to subscribe your address to the mailing list\n" "%(listaddr)s. You are already subscribed to this mailing list.\n" @@ -3319,6 +3412,7 @@ msgid "This list only supports digest delivery." msgstr "Bu liste sadece toplu gönderimi destekliyor." #: Mailman/Cgi/subscribe.py:344 +#, fuzzy msgid "You have been successfully subscribed to the %(realname)s mailing list." msgstr "%(realname)s mesaj listesine baþarýyla üye oldunuz." @@ -3449,26 +3543,32 @@ msgid "n/a" msgstr "n/a" #: Mailman/Commands/cmd_info.py:44 +#, fuzzy msgid "List name: %(listname)s" msgstr "Liste ismi: %(listname)s" #: Mailman/Commands/cmd_info.py:45 +#, fuzzy msgid "Description: %(description)s" msgstr "Taným: %(description)s" #: Mailman/Commands/cmd_info.py:46 +#, fuzzy msgid "Postings to: %(postaddr)s" msgstr "Mesajlarýn gideceði adres: %(postaddr)s" #: Mailman/Commands/cmd_info.py:47 +#, fuzzy msgid "List Helpbot: %(requestaddr)s" msgstr "Liste Yardým Robotu: %(requestaddr)s" #: Mailman/Commands/cmd_info.py:48 +#, fuzzy msgid "List Owners: %(owneraddr)s" msgstr "Liste Sahipleri: %(owneraddr)s" #: Mailman/Commands/cmd_info.py:49 +#, fuzzy msgid "More information: %(listurl)s" msgstr "Ek bilgi: %(listurl)s" @@ -3492,18 +3592,22 @@ msgstr "" "gösterir.\n" #: Mailman/Commands/cmd_lists.py:44 +#, fuzzy msgid "Public mailing lists at %(hostname)s:" msgstr "%(hostname)s üzerindeki genel mesaj listeleri:" #: Mailman/Commands/cmd_lists.py:66 +#, fuzzy msgid "%(i)3d. List name: %(realname)s" msgstr "%(i)3d. Liste ismi: %(realname)s" #: Mailman/Commands/cmd_lists.py:67 +#, fuzzy msgid " Description: %(description)s" msgstr " Taným: %(description)s" #: Mailman/Commands/cmd_lists.py:68 +#, fuzzy msgid " Requests to: %(requestaddr)s" msgstr " Ýsteklerin gideceði adres: %(requestaddr)s" @@ -3541,12 +3645,14 @@ msgstr "" " gideceðini unutmayýn.\n" #: Mailman/Commands/cmd_password.py:51 Mailman/Commands/cmd_password.py:66 +#, fuzzy msgid "Your password is: %(password)s" msgstr "Þifreniz: %(password)s" #: Mailman/Commands/cmd_password.py:57 Mailman/Commands/cmd_password.py:72 #: Mailman/Commands/cmd_password.py:95 Mailman/Commands/cmd_password.py:121 #: Mailman/Commands/cmd_set.py:149 Mailman/Commands/cmd_set.py:219 +#, fuzzy msgid "You are not a member of the %(listname)s mailing list" msgstr "%(listname)s mesaj listesine üye deðilsiniz" @@ -3746,6 +3852,7 @@ msgstr "" " için `set reminders off' komutunu kullanýn.\n" #: Mailman/Commands/cmd_set.py:122 +#, fuzzy msgid "Bad set command: %(subcmd)s" msgstr "Kötü set komutu: %(subcmd)s" @@ -3766,6 +3873,7 @@ msgid "on" msgstr "on" #: Mailman/Commands/cmd_set.py:154 +#, fuzzy msgid " ack %(onoff)s" msgstr " ack %(onoff)s" @@ -3803,22 +3911,27 @@ msgid "due to bounces" msgstr "geri dönmeler sonucunda" #: Mailman/Commands/cmd_set.py:186 +#, fuzzy msgid " %(status)s (%(how)s on %(date)s)" msgstr " %(status)s (%(how)s on %(date)s)" #: Mailman/Commands/cmd_set.py:192 +#, fuzzy msgid " myposts %(onoff)s" msgstr " myposts %(onoff)s" #: Mailman/Commands/cmd_set.py:195 +#, fuzzy msgid " hide %(onoff)s" msgstr " hide %(onoff)s" #: Mailman/Commands/cmd_set.py:199 +#, fuzzy msgid " duplicates %(onoff)s" msgstr " duplicates %(onoff)s" #: Mailman/Commands/cmd_set.py:203 +#, fuzzy msgid " reminders %(onoff)s" msgstr " reminders %(onoff)s" @@ -3827,6 +3940,7 @@ msgid "You did not give the correct password" msgstr "Doðru þifreyi vermediniz" #: Mailman/Commands/cmd_set.py:236 Mailman/Commands/cmd_set.py:283 +#, fuzzy msgid "Bad argument: %(arg)s" msgstr "Kötü argüman: %(arg)s" @@ -3907,6 +4021,7 @@ msgstr "" " belirtin.\n" #: Mailman/Commands/cmd_subscribe.py:62 +#, fuzzy msgid "Bad digest specifier: %(arg)s" msgstr "Kötü digest belirtici: %(arg)s" @@ -3915,6 +4030,7 @@ msgid "No valid address found to subscribe" msgstr "Üye yapmak için geçerli adres bulunamadý" #: Mailman/Commands/cmd_subscribe.py:113 +#, fuzzy msgid "" "The email address you supplied is banned from this mailing list.\n" "If you think this restriction is erroneous, please contact the list\n" @@ -3953,6 +4069,7 @@ msgid "This list only supports digest subscriptions!" msgstr "Bu liste sadece toplu mesaj alýmýný desteklemektedir!" #: Mailman/Commands/cmd_subscribe.py:146 +#, fuzzy msgid "" "Your subscription request has been forwarded to the list administrator\n" "at %(listowner)s for review." @@ -3992,6 +4109,7 @@ msgstr "" " belirtin.\n" #: Mailman/Commands/cmd_unsubscribe.py:62 +#, fuzzy msgid "%(address)s is not a member of the %(listname)s mailing list" msgstr "%(address)s, %(listname)s mesaj listesine üye deðil" @@ -4240,6 +4358,7 @@ msgid "Chinese (Taiwan)" msgstr "Çince (Tayvan)" #: Mailman/Deliverer.py:53 +#, fuzzy msgid "" "Note: Since this is a list of mailing lists, administrative\n" "notices like the password reminder will be sent to\n" @@ -4254,14 +4373,17 @@ msgid " (Digest mode)" msgstr " (Toplu mod)" #: Mailman/Deliverer.py:79 +#, fuzzy msgid "Welcome to the \"%(realname)s\" mailing list%(digmode)s" msgstr "\"%(realname)s\" mesaj listesine%(digmode)s hoþgeldiniz" #: Mailman/Deliverer.py:89 +#, fuzzy msgid "You have been unsubscribed from the %(realname)s mailing list" msgstr "%(realname)s mesaj listesi üyeliðinden çýktýnýz" #: Mailman/Deliverer.py:116 +#, fuzzy msgid "%(listfullname)s mailing list reminder" msgstr "%(listfullname)s mesaj listesi hatýrlatýcýsý" @@ -4274,6 +4396,7 @@ msgid "Hostile subscription attempt detected" msgstr "Saldýrgan üyelik denemesi saptandý" #: Mailman/Deliverer.py:169 +#, fuzzy msgid "" "%(address)s was invited to a different mailing\n" "list, but in a deliberate malicious attempt they tried to confirm the\n" @@ -4286,6 +4409,7 @@ msgstr "" "eylemde bulunmanýza gerek yok." #: Mailman/Deliverer.py:188 +#, fuzzy msgid "" "You invited %(address)s to your list, but in a\n" "deliberate malicious attempt, they tried to confirm the invitation to a\n" @@ -4509,8 +4633,8 @@ msgid "" " membership.\n" "\n" "

                  You can control both the\n" -" number\n" +" number\n" " of reminders the member will receive and the\n" " Hem üyenin alacaðý\n" -" hatýrlatma\n" +" hatýrlatma\n" " sayýsýný hem de bu hatýrlatmalarýn gönderileceði\n" " No those messages too will get discarded. You may " "want\n" " to set up an\n" -" autoresponse\n" +" autoresponse\n" " message for email to the -owner and -admin address." msgstr "" "Mailman'in geri dönüþ algýlayýcýsýnýn yeterince saðlam olmasýna raðmen " @@ -4812,6 +4936,7 @@ msgstr "" " bildirim gönderilmesi her zaman denenecektir." #: Mailman/Gui/Bounce.py:194 +#, fuzzy msgid "" "Bad value for %(property)s: %(val)s" @@ -4962,8 +5087,8 @@ msgid "" "Use this option to remove each message attachment that does\n" " not have a matching content type. Requirements and formats " "are\n" -" exactly like filter_mime_types.\n" "\n" "

                  Note: if you add entries to this list but don't add\n" @@ -5083,6 +5208,7 @@ msgstr "" " etkinleþtirilirse kullanýlabilir." #: Mailman/Gui/ContentFilter.py:171 +#, fuzzy msgid "Bad MIME type ignored: %(spectype)s" msgstr "Kötü MIME türü gözardý edildi: %(spectype)s" @@ -5189,6 +5315,7 @@ msgstr "" " göndersin mi?" #: Mailman/Gui/Digest.py:145 +#, fuzzy msgid "" "The next digest will be sent as volume\n" " %(volume)s, number %(number)s" @@ -5205,14 +5332,17 @@ msgid "There was no digest to send." msgstr "Gönderilecek toplu mesaj yoktu." #: Mailman/Gui/GUIBase.py:173 +#, fuzzy msgid "Invalid value for variable: %(property)s" msgstr "Deðiþken için geçersiz deðer: %(property)s" #: Mailman/Gui/GUIBase.py:178 +#, fuzzy msgid "Bad email address for option %(property)s: %(error)s" msgstr "Seçenek %(property)s için kötü e-posta adresi: %(error)s" #: Mailman/Gui/GUIBase.py:204 +#, fuzzy msgid "" "The following illegal substitution variables were\n" " found in the %(property)s string:\n" @@ -5228,6 +5358,7 @@ msgstr "" " çalýþmayabilir." #: Mailman/Gui/GUIBase.py:218 +#, fuzzy msgid "" "Your %(property)s string appeared to\n" " have some correctable problems in its new value.\n" @@ -5331,8 +5462,8 @@ msgid "" "

                  In order to split the list ownership duties into\n" " administrators and moderators, you must\n" " set a separate moderator password,\n" -" and also provide the email\n" +" and also provide the email\n" " addresses of the list moderators. Note that the field you\n" " are changing here specifies the list administrators." msgstr "" @@ -5666,13 +5797,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5716,13 +5847,13 @@ msgstr "" " özel yanýtlarýn gönderilmesinin çok daha zorlaþmasýdýr. Bu " "durumun genel\n" " bir tartýþmasý için `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful sayfasýna bakabilirsiniz. Bunun " "tersi bir\n" " görüþ için de `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">`Reply-To'\n" " Munging Considered Useful sayfasýna bakabilirsiniz.\n" "\n" "

                  Bazý mesaj listeleri kýsýtlý gönderim ayrýcalýklarýna ve " @@ -5745,8 +5876,8 @@ msgstr "Farkl msgid "" "This is the address set in the Reply-To: header\n" " when the reply_goes_to_list\n" +" href=\"?VARHELP=general/" +"reply_goes_to_list\">reply_goes_to_list\n" " option is set to Explicit address.\n" "\n" "

                  There are many reasons not to introduce or override the\n" @@ -5754,13 +5885,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5782,8 +5913,8 @@ msgid "" " Reply-To: header, it will not be changed." msgstr "" "Bu, reply_goes_to_list\n" +" href=\"?VARHELP=general/" +"reply_goes_to_list\">reply_goes_to_list\n" " seçeneði Farklý adres olarak ayarlandýðýnda Reply-" "To:\n" " baþlýðýna atanacak adrestir.\n" @@ -5799,13 +5930,13 @@ msgstr "" " özel yanýtlarýn gönderilmesinin çok daha zorlaþmasýdýr. Bu " "durumun genel\n" " bir tartýþmasý için `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful sayfasýna bakabilirsiniz. Bunun " "tersi bir\n" " görüþ için de `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">`Reply-To'\n" " Munging Considered Useful sayfasýna bakabilirsiniz.\n" "\n" "

                  Bazý mesaj listeleri kýsýtlý gönderim ayrýcalýklarýna ve " @@ -5873,8 +6004,8 @@ msgid "" " member list addresses, but rather to the owner of those member\n" " lists. In that case, the value of this setting is appended to\n" " the member's account name for such notices. `-owner' is the\n" -" typical choice. This setting has no effect when \"umbrella_list" -"\"\n" +" typical choice. This setting has no effect when " +"\"umbrella_list\"\n" " is \"No\"." msgstr "" "Bu listenin üyelerinin baþka listeler olduðunu gösteren\n" @@ -6761,6 +6892,7 @@ msgstr "" " izni olmadan üyelikler yaratmasýný engeller." #: Mailman/Gui/Privacy.py:104 +#, fuzzy msgid "" "This section allows you to configure subscription and\n" " membership exposure policy. You can also control whether this\n" @@ -6961,8 +7093,8 @@ msgid "" " either individually or as a group. Any\n" " posting from a non-member who is not explicitly accepted,\n" " rejected, or discarded, will have their posting filtered by the\n" -" general\n" +" general\n" " non-member rules.\n" "\n" "

                  In the text boxes below, add one address per line; start the\n" @@ -6993,8 +7125,8 @@ msgstr "" "

                  Üye olmayan kiþilerin mesajlarý otomatik olarak\n" " onaylanabilir,\n" -" moderatör\n" +" moderatör\n" " onayý için bekletilebilir,\n" " reddedilebilir (geri döndürülür), veya\n" @@ -7003,8 +7135,8 @@ msgstr "" " bunlarýn hepsi teker teker veya grup halinde yapýlabilir. Açýkça " "onaylanmayan,\n" " reddedilmeyen veya gözardý edilmeyen kiþilerin mesajlarý,\n" -" genel\n" +" genel\n" " üye olmayan kiþi kurallarý'na göre belirlenir.\n" "\n" "

                  Aþaðýdaki yazý kutularýna, her satýra bir adres olacak " @@ -7031,6 +7163,7 @@ msgstr "" "Varsayýlan olarak yeni üyelerin mesajlarý moderatör onayý gerektirsin mi?" #: Mailman/Gui/Privacy.py:218 +#, fuzzy msgid "" "Each list member has a moderation flag which says\n" " whether messages from the list member can be posted directly " @@ -7089,8 +7222,8 @@ msgid "" "If a member posts this many times, within a period of time\n" " the member is automatically moderated. Use 0 to disable. " "See\n" -" member_verbosity_interval for details on the time " "period.\n" "\n" @@ -7545,8 +7678,8 @@ msgstr "" " açýkça\n" " onaylanan,\n" -" bekletilen,\n" +" bekletilen,\n" " reddedilen (geri döndürülen) ve\n" " The body of the message can also be optionally scanned for\n" " Subject: and Keywords: headers, as\n" " specified by the topics_bodylines_limit\n" +" href=\"?VARHELP=topics/" +"topics_bodylines_limit\">topics_bodylines_limit\n" " configuration variable." msgstr "" "Konu filtresi gelen her mesajý aþaðýda belirleyeceðiniz Ýsteðe baðlý olarak mesajýn gövdesi de topics_bodylines_limit\n" +" href=\"?VARHELP=topics/" +"topics_bodylines_limit\">topics_bodylines_limit\n" " deðiþkeninde belirlenen þekilde Konu: ve " "Keywords:\n" " baþlýklarý için taranabilir." @@ -7964,6 +8098,7 @@ msgstr "" " bir desen gerektirir. Eksik konular gözardý edilecek." #: Mailman/Gui/Topics.py:135 +#, fuzzy msgid "" "The topic pattern '%(safepattern)s' is not a\n" " legal regular expression. It will be discarded." @@ -8131,8 +8266,8 @@ msgid "" " gated messages either." msgstr "" "Mailman Konu: baþlýklarýna\n" -" özelleþtirebileceðiniz\n" +" özelleþtirebileceðiniz\n" " bir yazýyý önek olarak ekler ve normalde bu önek, Usenet'e " "geçirilen\n" " mesajlarda görünür. Bu seçeneði Hayýr olarak seçerek, " @@ -8197,6 +8332,7 @@ msgid "%(listinfo_link)s list run by %(owner_link)s" msgstr "%(owner_link)s yönetimindeki %(listinfo_link)s listesi" #: Mailman/HTMLFormatter.py:57 +#, fuzzy msgid "%(realname)s administrative interface" msgstr "%(realname)s yönetimsel arayüzü" @@ -8205,6 +8341,7 @@ msgid " (requires authorization)" msgstr " (yetki gerektirir)" #: Mailman/HTMLFormatter.py:61 +#, fuzzy msgid "Overview of all %(hostname)s mailing lists" msgstr "Tüm %(hostname)s mesaj listelerinin genel tanýtýmý" @@ -8225,6 +8362,7 @@ msgid "; it was disabled by the list administrator" msgstr "; liste yöneticisi tarafýndan devre dýþý býrakýlmýþ" #: Mailman/HTMLFormatter.py:146 +#, fuzzy msgid "" "; it was disabled due to excessive bounces. The\n" " last bounce was received on %(date)s" @@ -8237,6 +8375,7 @@ msgid "; it was disabled for unknown reasons" msgstr "; bilinmeyen nedenlerle devre dýþý býrakýlmýþ" #: Mailman/HTMLFormatter.py:151 +#, fuzzy msgid "Note: your list delivery is currently disabled%(reason)s." msgstr "Not: size mesaj gönderimi þu anda devre dýþý%(reason)s." @@ -8249,6 +8388,7 @@ msgid "the list administrator" msgstr "liste yöneticisi" #: Mailman/HTMLFormatter.py:157 +#, fuzzy msgid "" "

                  %(note)s\n" "\n" @@ -8271,6 +8411,7 @@ msgstr "" " veya yardýma ihtiyacýnýz varsa %(mailto)s ile baðlantý kurun." #: Mailman/HTMLFormatter.py:169 +#, fuzzy msgid "" "

                  We have received some recent bounces from your\n" " address. Your current bounce score is %(score)s out of " @@ -8290,6 +8431,7 @@ msgstr "" " sýfýrlanacaktýr." #: Mailman/HTMLFormatter.py:181 +#, fuzzy msgid "" "(Note - you are subscribing to a list of mailing lists, so the %(type)s " "notice will be sent to the admin address for your membership, %(addr)s.)

                  " @@ -8337,6 +8479,7 @@ msgstr "" " Moderatörün kararýný bildiren bir e-posta alacaksýnýz." #: Mailman/HTMLFormatter.py:208 +#, fuzzy msgid "" "This is %(also)sa private list, which means that the\n" " list of members is not available to non-members." @@ -8345,6 +8488,7 @@ msgstr "" " üye olmayan kiþilerden gizlidir." #: Mailman/HTMLFormatter.py:211 +#, fuzzy msgid "" "This is %(also)sa hidden list, which means that the\n" " list of members is available only to the list administrator." @@ -8353,6 +8497,7 @@ msgstr "" " sadece liste yöneticilerine görünür durumdadýr." #: Mailman/HTMLFormatter.py:214 +#, fuzzy msgid "" "This is %(also)sa public list, which means that the\n" " list of members list is available to everyone." @@ -8369,6 +8514,7 @@ msgstr "" " kolayca anlaþýlamayacak hale gelecek þekilde deðiþtiriyoruz)." #: Mailman/HTMLFormatter.py:222 +#, fuzzy msgid "" "

                  (Note that this is an umbrella list, intended to\n" " have only other mailing lists as members. Among other things,\n" @@ -8386,6 +8532,7 @@ msgid "either " msgstr "ya " #: Mailman/HTMLFormatter.py:256 +#, fuzzy msgid "" "To unsubscribe from %(realname)s, get a password reminder,\n" " or change your subscription options %(either)senter your " @@ -8420,6 +8567,7 @@ msgstr "" " istenecektir." #: Mailman/HTMLFormatter.py:277 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " members.)" @@ -8428,6 +8576,7 @@ msgstr "" " görünür durumdadýr.)" #: Mailman/HTMLFormatter.py:281 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " administrator.)" @@ -8488,6 +8637,7 @@ msgid "The current archive" msgstr "Þu andaki arþiv" #: Mailman/Handlers/Acknowledge.py:59 +#, fuzzy msgid "%(realname)s post acknowledgement" msgstr "%(realname)s gönderim bilgisi" @@ -8500,6 +8650,7 @@ msgid "" msgstr "" #: Mailman/Handlers/CalcRecips.py:79 +#, fuzzy msgid "" "Your urgent message to the %(realname)s mailing list was not authorized for\n" "delivery. The original message as received by Mailman is attached.\n" @@ -8576,6 +8727,7 @@ msgid "Message may contain administrivia" msgstr "Mesaj yönetimsel bilgi içeriyor olabilir" #: Mailman/Handlers/Hold.py:84 +#, fuzzy msgid "" "Please do *not* post administrative requests to the mailing\n" "list. If you wish to subscribe, visit %(listurl)s or send a message with " @@ -8618,10 +8770,12 @@ msgid "Posting to a moderated newsgroup" msgstr "Moderatör onaylý bir haber grubuna mesaj gönderimi" #: Mailman/Handlers/Hold.py:252 +#, fuzzy msgid "Your message to %(listname)s awaits moderator approval" msgstr "%(listname)s listesine gönderdiðiniz mesaj moderatör onayý bekliyor" #: Mailman/Handlers/Hold.py:271 +#, fuzzy msgid "%(listname)s post from %(sender)s requires approval" msgstr "" "%(sender)s tarafýndan gönderilen %(listname)s liste mesajý onay gerektiriyor" @@ -8669,6 +8823,7 @@ msgid "After content filtering, the message was empty" msgstr "Ýçerik filtreleme sonunda, mesaj boþ kaldý" #: Mailman/Handlers/MimeDel.py:269 +#, fuzzy msgid "" "The attached message matched the %(listname)s mailing list's content " "filtering\n" @@ -8711,6 +8866,7 @@ msgid "The attached message has been automatically discarded." msgstr "Ekteki mesaj otomatik olarak gözardý edildi." #: Mailman/Handlers/Replybot.py:75 +#, fuzzy msgid "Auto-response for your message to the \"%(realname)s\" mailing list" msgstr "" "\"%(realname)s\" mesaj listesine gönderdiðiniz mesaj için otomatik yanýt" @@ -8731,6 +8887,7 @@ msgid "HTML attachment scrubbed and removed" msgstr "HTML eklentisi temizlendi ve silindi" #: Mailman/Handlers/Scrubber.py:234 Mailman/Handlers/Scrubber.py:259 +#, fuzzy msgid "" "An HTML attachment was scrubbed...\n" "URL: %(url)s\n" @@ -8816,6 +8973,7 @@ msgid "Message rejected by filter rule match" msgstr "Mesaj filtre kuralý eþleþmesi sonucu reddedildi" #: Mailman/Handlers/ToDigest.py:173 +#, fuzzy msgid "%(realname)s Digest, Vol %(volume)d, Issue %(issue)d" msgstr "%(realname)s Toplu Mesajý, Sayý %(volume)d, Konu %(issue)d" @@ -8852,6 +9010,7 @@ msgid "End of " msgstr "Son: " #: Mailman/ListAdmin.py:309 +#, fuzzy msgid "Posting of your message titled \"%(subject)s\"" msgstr "\"%(subject)s\" baþlýklý mesajýnýzýn gönderimi" @@ -8864,6 +9023,7 @@ msgid "Forward of moderated message" msgstr "Moderasyon onaylý mesajýn iletimi" #: Mailman/ListAdmin.py:405 +#, fuzzy msgid "New subscription request to list %(realname)s from %(addr)s" msgstr "%(addr)s adresinden, %(realname)s listesine yeni üyelik isteði" @@ -8877,6 +9037,7 @@ msgid "via admin approval" msgstr "Onay beklemeye devam et" #: Mailman/ListAdmin.py:466 +#, fuzzy msgid "New unsubscription request from %(realname)s by %(addr)s" msgstr "%(addr)s adresinden, %(realname)s listesinden çýkma isteði" @@ -8889,10 +9050,12 @@ msgid "Original Message" msgstr "Özgün Mesaj" #: Mailman/ListAdmin.py:526 +#, fuzzy msgid "Request to mailing list %(realname)s rejected" msgstr "%(realname)s mesaj listesine istek reddedildi" #: Mailman/MTA/Manual.py:66 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been created via the through-the-web\n" "interface. In order to complete the activation of this mailing list, the\n" @@ -8920,14 +9083,17 @@ msgstr "" "`newaliases' programýný çalýþtýrmanýz gerekir:\n" #: Mailman/MTA/Manual.py:82 +#, fuzzy msgid "## %(listname)s mailing list" msgstr "## %(listname)s mesaj listesi" #: Mailman/MTA/Manual.py:99 +#, fuzzy msgid "Mailing list creation request for list %(listname)s" msgstr "%(listname)s listesi için liste oluþturma isteði" #: Mailman/MTA/Manual.py:113 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been removed via the through-the-web\n" "interface. In order to complete the de-activation of this mailing list, " @@ -8946,6 +9112,7 @@ msgstr "" "/etc/aliases dosyasýndan silinmesi gereken satýrlar:\n" #: Mailman/MTA/Manual.py:123 +#, fuzzy msgid "" "\n" "To finish removing your mailing list, you must edit your /etc/aliases (or\n" @@ -8962,14 +9129,17 @@ msgstr "" "## %(listname)s mesaj listesi" #: Mailman/MTA/Manual.py:142 +#, fuzzy msgid "Mailing list removal request for list %(listname)s" msgstr "%(listname)s mesaj listesi için silme isteði" #: Mailman/MTA/Postfix.py:442 +#, fuzzy msgid "checking permissions on %(file)s" msgstr "%(file)s dosyasý üzerindeki haklar denetleniyor" #: Mailman/MTA/Postfix.py:452 +#, fuzzy msgid "%(file)s permissions must be 0664 (got %(octmode)s)" msgstr "%(file)s haklarý 0664 olmalý (%(octmode)s görüldü)" @@ -8983,14 +9153,17 @@ msgid "(fixing)" msgstr "(düzeltiliyor)" #: Mailman/MTA/Postfix.py:470 +#, fuzzy msgid "checking ownership of %(dbfile)s" msgstr "%(dbfile)s dosyasýnýn sahipliði denetleniyor" #: Mailman/MTA/Postfix.py:478 +#, fuzzy msgid "%(dbfile)s owned by %(owner)s (must be owned by %(user)s" msgstr "%(dbfile)s sahibi %(owner)s (%(user)s olmalý" #: Mailman/MTA/Postfix.py:491 +#, fuzzy msgid "%(dbfile)s permissions must be 0664 (got %(octmode)s)" msgstr "%(dbfile)s haklarý 0664 olmalý (%(octmode)s görüldü)" @@ -9005,14 +9178,17 @@ msgid "Your confirmation is required to leave the %(listname)s mailing list" msgstr "%(listname)s mesaj listesine üye deðilsiniz" #: Mailman/MailList.py:999 Mailman/MailList.py:1492 +#, fuzzy msgid " from %(remote)s" msgstr " %(remote)s adresinden " #: Mailman/MailList.py:1043 +#, fuzzy msgid "subscriptions to %(realname)s require moderator approval" msgstr "%(realname)s listesine üyelikler moderatör onayý gerektirir" #: Mailman/MailList.py:1125 bin/add_members:299 +#, fuzzy msgid "%(realname)s subscription notification" msgstr "%(realname)s üyelik bildirimi" @@ -9021,6 +9197,7 @@ msgid "unsubscriptions require moderator approval" msgstr "üyelikten çýkmak moderatör onayý gerektirir" #: Mailman/MailList.py:1166 +#, fuzzy msgid "%(realname)s unsubscribe notification" msgstr "%(realname)s üyelikten çýkma bildirimi" @@ -9040,6 +9217,7 @@ msgid "via web confirmation" msgstr "Hatalý onay dizgisi" #: Mailman/MailList.py:1396 +#, fuzzy msgid "subscriptions to %(name)s require administrator approval" msgstr "%(name)s listesine üyelikler moderatör onayý gerektirir" @@ -9058,6 +9236,7 @@ msgid "Last autoresponse notification for today" msgstr "Bugün için son otomatik yanýt bildirimi" #: Mailman/Queue/BounceRunner.py:360 +#, fuzzy msgid "" "The attached message was received as a bounce, but either the bounce format\n" "was not recognized, or no member addresses could be extracted from it. " @@ -9146,6 +9325,7 @@ msgid "Original message suppressed by Mailman site configuration\n" msgstr "" #: Mailman/htmlformat.py:675 +#, fuzzy msgid "Delivered by Mailman
                  version %(version)s" msgstr "Mailman ile gönderildi
                  sürüm %(version)s" @@ -9234,6 +9414,7 @@ msgid "Server Local Time" msgstr "Sunucu Yerel Zamaný" #: Mailman/i18n.py:180 +#, fuzzy msgid "" "%(wday)s %(mon)s %(day)2i %(hh)02i:%(mm)02i:%(ss)02i %(tzname)s %(year)04i" msgstr "" @@ -9303,20 +9484,23 @@ msgid "" msgstr "" #: bin/add_members:162 bin/add_members:172 +#, fuzzy msgid "Already a member: %(member)s" -msgstr "" +msgstr "Zaten listeye üye" #: bin/add_members:178 msgid "Bad/Invalid email address: blank line" msgstr "" #: bin/add_members:180 +#, fuzzy msgid "Bad/Invalid email address: %(member)s" -msgstr "" +msgstr "Kötü/Geçersiz e-posta adresi" #: bin/add_members:182 +#, fuzzy msgid "Hostile address (illegal characters): %(member)s" -msgstr "" +msgstr "Saldýrgan adres (geçersiz karakterler içeriyor)" #: bin/add_members:185 #, fuzzy @@ -9324,16 +9508,19 @@ msgid "Invited: %(member)s" msgstr "Liste üyeleri" #: bin/add_members:187 +#, fuzzy msgid "Subscribed: %(member)s" -msgstr "" +msgstr "Liste üyeleri" #: bin/add_members:237 +#, fuzzy msgid "Bad argument to -w/--welcome-msg: %(arg)s" -msgstr "" +msgstr "Kötü argüman: %(arg)s" #: bin/add_members:244 +#, fuzzy msgid "Bad argument to -a/--admin-notify: %(arg)s" -msgstr "" +msgstr "Kötü argüman: %(arg)s" #: bin/add_members:252 msgid "Cannot read both digest and normal members from standard input." @@ -9346,8 +9533,9 @@ msgstr "" #: bin/add_members:261 bin/config_list:110 bin/export.py:271 bin/find_member:97 #: bin/inject:91 bin/list_admins:90 bin/list_members:252 bin/sync_members:222 #: cron/bumpdigests:86 +#, fuzzy msgid "No such list: %(listname)s" -msgstr "" +msgstr "%(safelistname)s adýnda bir liste yok" #: bin/add_members:285 bin/change_pw:159 bin/check_db:114 bin/discard:83 #: bin/sync_members:244 bin/update:302 bin/update:323 bin/update:577 @@ -9409,10 +9597,11 @@ msgid "listname is required" msgstr "" #: bin/arch:143 bin/change_pw:107 bin/config_list:257 +#, fuzzy msgid "" "No such list \"%(listname)s\"\n" "%(e)s" -msgstr "" +msgstr "%(safelistname)s adýnda bir liste yok" #: bin/arch:168 msgid "Cannot open mbox file %(mbox)s: %(msg)s" @@ -9496,20 +9685,23 @@ msgid "" msgstr "" #: bin/change_pw:145 +#, fuzzy msgid "Bad arguments: %(strargs)s" -msgstr "" +msgstr "Kötü argüman: %(arg)s" #: bin/change_pw:149 msgid "Empty list passwords are not allowed" msgstr "" #: bin/change_pw:181 +#, fuzzy msgid "New %(listname)s password: %(notifypassword)s" -msgstr "" +msgstr "Baþlangýç liste þifresi:" #: bin/change_pw:190 +#, fuzzy msgid "Your new %(listname)s list password" -msgstr "" +msgstr "Baþlangýç liste þifresi:" #: bin/change_pw:191 msgid "" @@ -9587,40 +9779,47 @@ msgid "" msgstr "" #: bin/check_perms:110 +#, fuzzy msgid " checking gid and mode for %(path)s" -msgstr "" +msgstr "%(file)s dosyasý üzerindeki haklar denetleniyor" #: bin/check_perms:122 msgid "%(path)s bad group (has: %(groupname)s, expected %(MAILMAN_GROUP)s)" msgstr "" #: bin/check_perms:151 +#, fuzzy msgid "directory permissions must be %(octperms)s: %(path)s" -msgstr "" +msgstr "%(dbfile)s haklarý 0664 olmalý (%(octmode)s görüldü)" #: bin/check_perms:160 +#, fuzzy msgid "source perms must be %(octperms)s: %(path)s" -msgstr "" +msgstr "%(dbfile)s haklarý 0664 olmalý (%(octmode)s görüldü)" #: bin/check_perms:171 +#, fuzzy msgid "article db files must be %(octperms)s: %(path)s" -msgstr "" +msgstr "%(dbfile)s haklarý 0664 olmalý (%(octmode)s görüldü)" #: bin/check_perms:183 +#, fuzzy msgid "checking mode for %(prefix)s" -msgstr "" +msgstr "%(file)s dosyasý üzerindeki haklar denetleniyor" #: bin/check_perms:193 msgid "WARNING: directory does not exist: %(d)s" msgstr "" #: bin/check_perms:197 +#, fuzzy msgid "directory must be at least 02775: %(d)s" -msgstr "" +msgstr "%(file)s haklarý 0664 olmalý (%(octmode)s görüldü)" #: bin/check_perms:209 +#, fuzzy msgid "checking perms on %(private)s" -msgstr "" +msgstr "%(file)s dosyasý üzerindeki haklar denetleniyor" #: bin/check_perms:214 msgid "%(private)s must not be other-readable" @@ -9648,40 +9847,46 @@ msgid "checking cgi-bin permissions" msgstr "" #: bin/check_perms:278 +#, fuzzy msgid " checking set-gid for %(path)s" -msgstr "" +msgstr "%(file)s dosyasý üzerindeki haklar denetleniyor" #: bin/check_perms:282 msgid "%(path)s must be set-gid" msgstr "" #: bin/check_perms:292 +#, fuzzy msgid "checking set-gid for %(wrapper)s" -msgstr "" +msgstr "%(file)s dosyasý üzerindeki haklar denetleniyor" #: bin/check_perms:296 msgid "%(wrapper)s must be set-gid" msgstr "" #: bin/check_perms:306 +#, fuzzy msgid "checking permissions on %(pwfile)s" -msgstr "" +msgstr "%(file)s dosyasý üzerindeki haklar denetleniyor" #: bin/check_perms:315 +#, fuzzy msgid "%(pwfile)s permissions must be exactly 0640 (got %(octmode)s)" -msgstr "" +msgstr "%(file)s haklarý 0664 olmalý (%(octmode)s görüldü)" #: bin/check_perms:340 msgid "checking permissions on list data" msgstr "" #: bin/check_perms:348 +#, fuzzy msgid " checking permissions on: %(path)s" -msgstr "" +msgstr "%(file)s dosyasý üzerindeki haklar denetleniyor" #: bin/check_perms:356 +#, fuzzy msgid "file permissions must be at least 660: %(path)s" -msgstr "" +msgstr "%(file)s haklarý 0664 olmalý (%(octmode)s görüldü)" #: bin/check_perms:401 msgid "No problems found" @@ -9734,8 +9939,9 @@ msgid "Unix-From line changed: %(lineno)d" msgstr "" #: bin/cleanarch:111 +#, fuzzy msgid "Bad status number: %(arg)s" -msgstr "" +msgstr "Kötü argüman: %(arg)s" #: bin/cleanarch:167 msgid "%(messages)d messages found" @@ -9832,14 +10038,16 @@ msgid " original address removed:" msgstr "" #: bin/clone_member:202 +#, fuzzy msgid "Not a valid email address: %(toaddr)s" -msgstr "" +msgstr "Kötü/Geçersiz e-posta adresi" #: bin/clone_member:215 +#, fuzzy msgid "" "Error opening list \"%(listname)s\", skipping.\n" "%(e)s" -msgstr "" +msgstr "Yeni mesaj listeniz: %(listname)s" #: bin/config_list:20 msgid "" @@ -9924,12 +10132,14 @@ msgid "Non-standard property restored: %(k)s" msgstr "" #: bin/config_list:288 +#, fuzzy msgid "Invalid value for property: %(k)s" -msgstr "" +msgstr "Deðiþken için geçersiz deðer: %(property)s" #: bin/config_list:291 +#, fuzzy msgid "Bad email address for option %(k)s: %(v)s" -msgstr "" +msgstr "Seçenek %(property)s için kötü e-posta adresi: %(error)s" #: bin/config_list:348 msgid "Only one of -i or -o is allowed" @@ -9976,16 +10186,19 @@ msgid "" msgstr "" #: bin/discard:94 +#, fuzzy msgid "Ignoring non-held message: %(f)s" -msgstr "" +msgstr "Silinmiþ üye üzerindeki deðiþiklikler gözardý ediliyor: %(user)s" #: bin/discard:100 +#, fuzzy msgid "Ignoring held msg w/bad id: %(f)s" -msgstr "" +msgstr "Silinmiþ üye üzerindeki deðiþiklikler gözardý ediliyor: %(user)s" #: bin/discard:112 +#, fuzzy msgid "Discarded held msg #%(id)s for list %(listname)s" -msgstr "" +msgstr "%(listname)s listesine üye ol" #: bin/dumpdb:19 msgid "" @@ -10029,8 +10242,9 @@ msgid "No filename given." msgstr "" #: bin/dumpdb:108 +#, fuzzy msgid "Bad arguments: %(pargs)s" -msgstr "" +msgstr "Kötü argüman: %(arg)s" #: bin/dumpdb:118 msgid "Please specify either -p or -m." @@ -10280,8 +10494,9 @@ msgid "" msgstr "" #: bin/list_admins:97 +#, fuzzy msgid "List: %(listname)s, \tOwners: %(owners)s" -msgstr "" +msgstr "Liste Sahipleri: %(owneraddr)s" #: bin/list_lists:19 msgid "" @@ -10386,12 +10601,14 @@ msgid "" msgstr "" #: bin/list_members:198 +#, fuzzy msgid "Bad --nomail option: %(why)s" -msgstr "" +msgstr "Kötü digest belirtici: %(arg)s" #: bin/list_members:209 +#, fuzzy msgid "Bad --digest option: %(kind)s" -msgstr "" +msgstr "Kötü digest belirtici: %(arg)s" #: bin/list_members:213 bin/list_members:217 bin/list_members:221 #: bin/list_members:225 @@ -10575,8 +10792,9 @@ msgid "" msgstr "" #: bin/mailmanctl:280 cron/mailpasswds:119 +#, fuzzy msgid "Site list is missing: %(sitelistname)s" -msgstr "" +msgstr "Bu liste zaten var: %(safelistname)s" #: bin/mailmanctl:305 msgid "Run this program as root or as the %(name)s user, or use -u." @@ -10587,8 +10805,9 @@ msgid "No command given." msgstr "" #: bin/mailmanctl:339 +#, fuzzy msgid "Bad command: %(command)s" -msgstr "" +msgstr "Kötü set komutu: %(subcmd)s" #: bin/mailmanctl:344 msgid "Warning! You may encounter permission problems." @@ -10802,8 +11021,9 @@ msgid "" msgstr "" #: bin/newlist:162 +#, fuzzy msgid "Unknown language: %(lang)s" -msgstr "" +msgstr "Yeni mesaj listeniz: %(listname)s" #: bin/newlist:167 msgid "Enter the name of the list: " @@ -10814,8 +11034,9 @@ msgid "Enter the email of the person running the list: " msgstr "" #: bin/newlist:193 +#, fuzzy msgid "Initial %(listname)s password: " -msgstr "" +msgstr "Baþlangýç liste þifresi:" #: bin/newlist:197 msgid "The list password cannot be empty" @@ -10823,8 +11044,8 @@ msgstr "" #: bin/newlist:220 msgid "" -" - owner addresses need to be fully-qualified names like \"owner@example.com" -"\", not just \"owner\"." +" - owner addresses need to be fully-qualified names like \"owner@example." +"com\", not just \"owner\"." msgstr "" #: bin/newlist:244 @@ -10992,12 +11213,14 @@ msgid "Could not open file for reading: %(filename)s." msgstr "" #: bin/remove_members:163 +#, fuzzy msgid "Error opening list %(listname)s... skipping." -msgstr "" +msgstr "Yeni mesaj listeniz: %(listname)s" #: bin/remove_members:173 +#, fuzzy msgid "No such member: %(addr)s" -msgstr "" +msgstr "Böyle bir üye yok: %(safeuser)s." #: bin/remove_members:178 msgid "User `%(addr)s' removed from list: %(listname)s." @@ -11063,8 +11286,9 @@ msgid "%(listname)s %(msg)s not found as %(filename)s" msgstr "" #: bin/rmlist:105 +#, fuzzy msgid "No such list (or list already deleted): %(listname)s" -msgstr "" +msgstr "%(safelistname)s adýnda bir liste yok" #: bin/rmlist:108 msgid "No such list: %(listname)s. Removing its residual archives." @@ -11198,8 +11422,9 @@ msgid "No argument to -f given" msgstr "" #: bin/sync_members:172 +#, fuzzy msgid "Illegal option: %(opt)s" -msgstr "" +msgstr "Geçersiz liste ismi: %(s)s" #: bin/sync_members:178 msgid "No listname given" @@ -11334,8 +11559,9 @@ msgid "" msgstr "" #: bin/update:107 +#, fuzzy msgid "Fixing language templates: %(listname)s" -msgstr "" +msgstr "Yeni mesaj listeniz: %(listname)s" #: bin/update:196 bin/update:711 msgid "WARNING: could not acquire lock for list: %(listname)s" @@ -11489,8 +11715,9 @@ msgid "done" msgstr "" #: bin/update:691 +#, fuzzy msgid "Updating mailing list: %(listname)s" -msgstr "" +msgstr "Yeni mesaj listeniz: %(listname)s" #: bin/update:694 msgid "Updating Usenet watermarks" @@ -11695,16 +11922,18 @@ msgid "" msgstr "" #: bin/withlist:175 +#, fuzzy msgid "Unlocking (but not saving) list: %(listname)s" -msgstr "" +msgstr "Yeni mesaj listeniz: %(listname)s" #: bin/withlist:179 msgid "Finalizing" msgstr "" #: bin/withlist:188 +#, fuzzy msgid "Loading list %(listname)s" -msgstr "" +msgstr "Yeni mesaj listeniz: %(listname)s" #: bin/withlist:190 msgid "(locked)" @@ -11715,8 +11944,9 @@ msgid "(unlocked)" msgstr "" #: bin/withlist:197 +#, fuzzy msgid "Unknown list: %(listname)s" -msgstr "" +msgstr "Yeni mesaj listeniz: %(listname)s" #: bin/withlist:237 msgid "No list name supplied." @@ -11735,8 +11965,9 @@ msgid "Running %(module)s.%(callable)s()..." msgstr "" #: bin/withlist:291 +#, fuzzy msgid "The variable `m' is the %(listname)s MailList instance" -msgstr "" +msgstr "%(listname)s mesaj listesine üye olmak için davet edildiniz" #: cron/bumpdigests:19 msgid "" @@ -11923,8 +12154,9 @@ msgid "Password // URL" msgstr "" #: cron/mailpasswds:222 +#, fuzzy msgid "%(host)s mailing list memberships reminder" -msgstr "" +msgstr "%(listfullname)s mesaj listesi hatýrlatýcýsý" #: cron/nightly_gzip:19 msgid "" @@ -11990,8 +12222,8 @@ msgstr "" #~ " applied" #~ msgstr "" #~ "Bu listeye mesaj gönderip reddedilen tüm üyelere gönderilen\n" -#~ " reddetme bildirimlerine \n" #~ " eklenecek yazý." @@ -12018,6 +12250,3 @@ msgstr "" #~ " tetiklediðinde gönderilir. Bu seçenek bildirimin " #~ "gönderilmesini\n" #~ " geçersiz kýlar." - -#~ msgid "You have been invited to join the %(listname)s mailing list" -#~ msgstr "%(listname)s mesaj listesine üye olmak için davet edildiniz" diff --git a/messages/uk/LC_MESSAGES/mailman.po b/messages/uk/LC_MESSAGES/mailman.po index 498a0ef1..0f5b0db4 100755 --- a/messages/uk/LC_MESSAGES/mailman.po +++ b/messages/uk/LC_MESSAGES/mailman.po @@ -66,10 +66,12 @@ msgid "

                  Currently, there are no archives.

                  " msgstr "

                  Ðаразі архіви відÑутні.

                  " #: Mailman/Archiver/HyperArch.py:821 +#, fuzzy msgid "Gzip'd Text%(sz)s" msgstr "текÑÑ‚, ÑтиÑнений програмою gzip, %(sz)s" #: Mailman/Archiver/HyperArch.py:826 +#, fuzzy msgid "Text%(sz)s" msgstr "текÑÑ‚ %(sz)s" @@ -142,18 +144,22 @@ msgid "Third" msgstr "Третій" #: Mailman/Archiver/HyperArch.py:938 +#, fuzzy msgid "%(ord)s quarter %(year)i" msgstr "%(ord)s квартал %(year)i" #: Mailman/Archiver/HyperArch.py:945 +#, fuzzy msgid "%(month)s %(year)i" msgstr "%(month)s %(year)i" #: Mailman/Archiver/HyperArch.py:950 +#, fuzzy msgid "The Week Of Monday %(day)i %(month)s %(year)i" msgstr "Тиждень, що зачинаєтьÑÑ Ð· понеділка %(day)i %(month)s %(year)i" #: Mailman/Archiver/HyperArch.py:954 +#, fuzzy msgid "%(day)i %(month)s %(year)i" msgstr "%(day)i %(month)s %(year)i" @@ -162,10 +168,12 @@ msgid "Computing threaded index\n" msgstr "ФормуєтьÑÑ Ñ–Ð½Ð´ÐµÐºÑ Ð·Ð° диÑкуÑÑ–Ñми\n" #: Mailman/Archiver/HyperArch.py:1319 +#, fuzzy msgid "Updating HTML for article %(seq)s" msgstr "ОновлюєтьÑÑ HTML-Ð²Ñ–Ð´Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ñтатті %(seq)s" #: Mailman/Archiver/HyperArch.py:1326 +#, fuzzy msgid "article file %(filename)s is missing!" msgstr "відÑутній файл Ñтатті %(filename)s!" @@ -182,6 +190,7 @@ msgid "Pickling archive state into " msgstr "Стан архіву запиÑуєтьÑÑ Ñƒ " #: Mailman/Archiver/pipermail.py:453 +#, fuzzy msgid "Updating index files for archive [%(archive)s]" msgstr "ВідбуваєтьÑÑ Ð¾Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ñ„Ð°Ð¹Ð»Ñ–Ð² індекÑів архіву [%(archive)s]" @@ -190,6 +199,7 @@ msgid " Thread" msgstr " Теми диÑкуÑій" #: Mailman/Archiver/pipermail.py:597 +#, fuzzy msgid "#%(counter)05d %(msgid)s" msgstr "#%(counter)05d %(msgid)s" @@ -228,6 +238,7 @@ msgid "disabled address" msgstr "заблоковано" #: Mailman/Bouncer.py:304 +#, fuzzy msgid " The last bounce received from you was dated %(date)s" msgstr " ОÑтаннє Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¾ помилку доÑтавки до Ð²Ð°Ñ Ð´Ð°Ñ‚Ð¾Ð²Ð°Ð½Ð¾ %(date)s" @@ -255,6 +266,7 @@ msgstr "адмініÑтратор" #: Mailman/Cgi/options.py:101 Mailman/Cgi/private.py:108 #: Mailman/Cgi/rmlist.py:75 Mailman/Cgi/roster.py:59 #: Mailman/Cgi/subscribe.py:67 +#, fuzzy msgid "No such list %(safelistname)s" msgstr "СпиÑок розÑилки %(safelistname)s не Ñ–Ñнує" @@ -325,6 +337,7 @@ msgstr "" " обрали цей варіант, вони нічого не отримуватимуть.%(rm)r" #: Mailman/Cgi/admin.py:268 +#, fuzzy msgid "%(hostname)s mailing lists - Admin Links" msgstr "СпиÑки розÑилок на %(hostname)s - Ñ–Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ Ð°Ð´Ð¼Ñ–Ð½Ñ–Ñтратора" @@ -337,6 +350,7 @@ msgid "Mailman" msgstr "Mailman" #: Mailman/Cgi/admin.py:310 +#, fuzzy msgid "" "

                  There currently are no publicly-advertised %(mailmanlink)s\n" " mailing lists on %(hostname)s." @@ -345,6 +359,7 @@ msgstr "" " зареєÑтровано жодного публічного ÑпиÑку розÑилки. " #: Mailman/Cgi/admin.py:316 +#, fuzzy msgid "" "

                  Below is the collection of publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s. Click on a list\n" @@ -360,6 +375,7 @@ msgid "right " msgstr "правильне " #: Mailman/Cgi/admin.py:325 +#, fuzzy msgid "" "To visit the administrators configuration page for an\n" " unadvertised list, open a URL similar to this one, but with a '/' " @@ -405,6 +421,7 @@ msgid "No valid variable name found." msgstr "Ðе знайдено правильного імені змінної." #: Mailman/Cgi/admin.py:395 +#, fuzzy msgid "" "%(realname)s Mailing list Configuration Help\n" "
                  %(varname)s Option" @@ -413,6 +430,7 @@ msgstr "" "
                  Параметр%(varname)s" #: Mailman/Cgi/admin.py:402 +#, fuzzy msgid "Mailman %(varname)s List Option Help" msgstr "Ð†Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ Ð¿Ñ€Ð¾ Ð½Ð°Ð»Ð°ÑˆÑ‚Ð¾Ð²ÑƒÐ²Ð°Ð½Ð½Ñ %(varname)s ÑиÑтеми Mailman" @@ -432,14 +450,17 @@ msgstr "" " параметр. Також можете " #: Mailman/Cgi/admin.py:431 +#, fuzzy msgid "return to the %(categoryname)s options page." msgstr "повернутиÑÑŒ до Ñторінки групи параметрів %(categoryname)s" #: Mailman/Cgi/admin.py:446 +#, fuzzy msgid "%(realname)s Administration (%(label)s)" msgstr "ÐšÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ ÑпиÑку розÑилки %(realname)s: %(label)s" #: Mailman/Cgi/admin.py:447 +#, fuzzy msgid "%(realname)s mailing list administration
                  %(label)s Section" msgstr "ÐšÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ ÑпиÑком розÑилки %(realname)s
                  Розділ %(label)s" @@ -521,6 +542,7 @@ msgid "Value" msgstr "ЗначеннÑ" #: Mailman/Cgi/admin.py:668 +#, fuzzy msgid "" "Badly formed options entry:\n" " %(record)s" @@ -621,10 +643,12 @@ msgid "Move rule down" msgstr "ПереміÑтити правило вниз" #: Mailman/Cgi/admin.py:870 +#, fuzzy msgid "
                  (Edit %(varname)s)" msgstr "
                  (Змінити %(varname)s)" #: Mailman/Cgi/admin.py:872 +#, fuzzy msgid "
                  (Details for %(varname)s)" msgstr "
                  (Докладна Ñ–Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ Ð¿Ñ€Ð¾ \"%(varname)s)" @@ -665,6 +689,7 @@ msgid "(help)" msgstr "(довідка)" #: Mailman/Cgi/admin.py:930 +#, fuzzy msgid "Find member %(link)s:" msgstr "Знайти учаÑника %(link)s:" @@ -677,10 +702,12 @@ msgid "Bad regular expression: " msgstr "Ðеправильний регулÑрний вираз: " #: Mailman/Cgi/admin.py:1013 +#, fuzzy msgid "%(allcnt)s members total, %(membercnt)s shown" msgstr "загалом отримувачів: %(allcnt)s; відображено: %(membercnt)s" #: Mailman/Cgi/admin.py:1016 +#, fuzzy msgid "%(allcnt)s members total" msgstr "загалом отримувачів: %(allcnt)s" @@ -865,6 +892,7 @@ msgstr "" " перелік учаÑників ÑпиÑку розÑилки чаÑтинами:/em>" #: Mailman/Cgi/admin.py:1221 +#, fuzzy msgid "from %(start)s to %(end)s" msgstr "з %(start)s до %(end)s" @@ -1002,6 +1030,7 @@ msgid "Change list ownership passwords" msgstr "Змінити пароль ÑпиÑку розÑилки" #: Mailman/Cgi/admin.py:1364 +#, fuzzy msgid "" "The list administrators are the people who have ultimate control " "over\n" @@ -1172,8 +1201,9 @@ msgid "%(schange_to)s is already a member" msgstr " вже Ñ” учаÑником" #: Mailman/Cgi/admin.py:1620 +#, fuzzy msgid "%(schange_to)s matches banned pattern %(spat)s" -msgstr "" +msgstr " вже Ñ” учаÑником" #: Mailman/Cgi/admin.py:1622 msgid "Address %(schange_from)s changed to %(schange_to)s" @@ -1213,6 +1243,7 @@ msgid "Not subscribed" msgstr "Ðе підпиÑаний" #: Mailman/Cgi/admin.py:1773 +#, fuzzy msgid "Ignoring changes to deleted member: %(user)s" msgstr "ІгноруютьÑÑ Ð·Ð¼Ñ–Ð½Ð¸ Ð´Ð»Ñ Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð¾Ð³Ð¾ учаÑника: %(user)s" @@ -1225,10 +1256,12 @@ msgid "Error Unsubscribing:" msgstr "Помилка Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð¿Ñ–Ð´Ð¿Ð¸Ñки:" #: Mailman/Cgi/admindb.py:235 Mailman/Cgi/admindb.py:248 +#, fuzzy msgid "%(realname)s Administrative Database" msgstr "СпиÑок запитів Ð´Ð»Ñ ÑпиÑку розÑилки %(realname)s" #: Mailman/Cgi/admindb.py:238 +#, fuzzy msgid "%(realname)s Administrative Database Results" msgstr "Результати запитів Ð´Ð»Ñ ÑпиÑку розÑилки %(realname)s " @@ -1257,6 +1290,7 @@ msgid "Discard all messages marked Defer" msgstr "Видалити уÑÑ– Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð¿Ð¾Ð·Ð½Ð°Ñ‡ÐµÐ½Ñ– Ñк Відкладені" #: Mailman/Cgi/admindb.py:298 +#, fuzzy msgid "all of %(esender)s's held messages." msgstr "уÑÑ– відкладені Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð²Ñ–Ð´ %(esender)s." @@ -1277,6 +1311,7 @@ msgid "list of available mailing lists." msgstr "ÑпиÑок доÑтупних ÑпиÑків розÑилки." #: Mailman/Cgi/admindb.py:362 +#, fuzzy msgid "You must specify a list name. Here is the %(link)s" msgstr "Ðеобхідно вказати ім'Ñ ÑпиÑку розÑилки. ПереглÑньте %(link)s" @@ -1359,6 +1394,7 @@ msgid "The sender is now a member of this list" msgstr "Відправника лиÑта додано до отримувачів цього ÑпиÑку" #: Mailman/Cgi/admindb.py:568 +#, fuzzy msgid "Add %(esender)s to one of these sender filters:" msgstr "Додати %(esender)s до одного з фільтрів відправників:" @@ -1379,6 +1415,7 @@ msgid "Rejects" msgstr "Відмовити" #: Mailman/Cgi/admindb.py:584 +#, fuzzy msgid "" "Ban %(esender)s from ever subscribing to this\n" " mailing list" @@ -1395,6 +1432,7 @@ msgstr "" " його номері, або можете " #: Mailman/Cgi/admindb.py:591 +#, fuzzy msgid "view all messages from %(esender)s" msgstr "переглÑнути уÑÑ– Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð²Ñ–Ð´ %(esender)s " @@ -1525,6 +1563,7 @@ msgstr "" " Цей запит було ÑкаÑовано." #: Mailman/Cgi/confirm.py:178 +#, fuzzy msgid "System error, bad content: %(content)s" msgstr "СиÑтемна помилка, недопуÑтимий вміÑÑ‚: %(content)s" @@ -1562,6 +1601,7 @@ msgid "Confirm subscription request" msgstr "Підтвердити запит на підпиÑку" #: Mailman/Cgi/confirm.py:259 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " subscription request to the mailing list %(listname)s. Your\n" @@ -1594,6 +1634,7 @@ msgstr "" "запит на підпиÑку." #: Mailman/Cgi/confirm.py:275 +#, fuzzy msgid "" "Your confirmation is required in order to continue with\n" " the subscription request to the mailing list %(listname)s.\n" @@ -1645,6 +1686,7 @@ msgid "Preferred language:" msgstr "Бажана мова:" #: Mailman/Cgi/confirm.py:317 +#, fuzzy msgid "Subscribe to list %(listname)s" msgstr "ПідпиÑатиÑÑŒ на ÑпиÑок %(listname)s" @@ -1661,6 +1703,7 @@ msgid "Awaiting moderator approval" msgstr "Очікує Ñ€Ñ–ÑˆÐµÐ½Ð½Ñ ÐºÐµÑ€Ñ–Ð²Ð½Ð¸ÐºÐ°" #: Mailman/Cgi/confirm.py:386 +#, fuzzy msgid "" " You have successfully confirmed your subscription request to " "the\n" @@ -1717,6 +1760,7 @@ msgid "Subscription request confirmed" msgstr "Запит на підпиÑку підтверджено" #: Mailman/Cgi/confirm.py:418 +#, fuzzy msgid "" " You have successfully confirmed your subscription request for\n" " \"%(addr)s\" to the %(listname)s mailing list. A separate\n" @@ -1746,6 +1790,7 @@ msgid "Unsubscription request confirmed" msgstr "Запит на Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ñ–Ð· ÑпиÑку підтверджено" #: Mailman/Cgi/confirm.py:469 +#, fuzzy msgid "" " You have successfully unsubscribed from the %(listname)s " "mailing\n" @@ -1766,6 +1811,7 @@ msgid "Not available" msgstr "ВідÑутній" #: Mailman/Cgi/confirm.py:498 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " unsubscription request from the mailing list %(listname)s. " @@ -1836,6 +1882,7 @@ msgid "Change of address request confirmed" msgstr "Запит на зміну адреÑи підтверджено" #: Mailman/Cgi/confirm.py:571 +#, fuzzy msgid "" " You have successfully changed your address on the %(listname)s\n" " mailing list from %(oldaddr)s to %(newaddr)s. " @@ -1857,6 +1904,7 @@ msgid "globally" msgstr "уÑюди" #: Mailman/Cgi/confirm.py:605 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " change of address request for the mailing list %(listname)s. " @@ -1918,6 +1966,7 @@ msgid "Sender discarded message via web." msgstr "Відправник видалив Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð· допомогою веб-інтерфейÑу." #: Mailman/Cgi/confirm.py:674 +#, fuzzy msgid "" "The held message with the Subject:\n" " header %(subject)s could not be found. The most " @@ -1937,6 +1986,7 @@ msgid "Posted message canceled" msgstr "ÐŸÐ¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð²Ñ–Ð´ÐºÐ»Ð¸ÐºÐ°Ð½Ð¾" #: Mailman/Cgi/confirm.py:685 +#, fuzzy msgid "" " You have successfully canceled the posting of your message with\n" " the Subject: header %(subject)s to the mailing list\n" @@ -1959,6 +2009,7 @@ msgstr "" " адмініÑтратором." #: Mailman/Cgi/confirm.py:735 +#, fuzzy msgid "" "Your confirmation is required in order to cancel the\n" " posting of your message to the mailing list %(listname)s:\n" @@ -2005,6 +2056,7 @@ msgid "Membership re-enabled." msgstr "УчаÑть у ÑпиÑку поновлено." #: Mailman/Cgi/confirm.py:798 +#, fuzzy msgid "" " You have successfully re-enabled your membership in the\n" " %(listname)s mailing list. You can now not available" msgstr "відÑутній" #: Mailman/Cgi/confirm.py:846 +#, fuzzy msgid "" "Your membership in the %(realname)s mailing list is\n" " currently disabled due to excessive bounces. Your confirmation is\n" @@ -2102,10 +2156,12 @@ msgid "administrative list overview" msgstr "адмініÑтративна Ñ–Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ Ð¿Ñ€Ð¾ ÑпиÑок" #: Mailman/Cgi/create.py:115 +#, fuzzy msgid "List name must not include \"@\": %(safelistname)s" msgstr "Ðазва ÑпиÑку не може включати Ñимвол \"@\": %(safelistname)s" #: Mailman/Cgi/create.py:122 +#, fuzzy msgid "List already exists: %(safelistname)s" msgstr "СпиÑок вже Ñ–Ñнує: %(safelistname)s" @@ -2139,18 +2195,22 @@ msgid "You are not authorized to create new mailing lists" msgstr "У Ð²Ð°Ñ Ð½ÐµÐ¼Ð°Ñ” привілей Ñтворювати ÑпиÑки розÑилки на цьому Ñервері" #: Mailman/Cgi/create.py:182 +#, fuzzy msgid "Unknown virtual host: %(safehostname)s" msgstr "Ðевідомий віртуальний вузол: %(safehostname)s" #: Mailman/Cgi/create.py:218 bin/newlist:219 +#, fuzzy msgid "Bad owner email address: %(s)s" msgstr "Ðеправильна адреÑа влаÑника: %(s)s" #: Mailman/Cgi/create.py:223 bin/newlist:182 bin/newlist:223 +#, fuzzy msgid "List already exists: %(listname)s" msgstr "СпиÑок з такою назвою уже Ñ–Ñнує: %(listname)s" #: Mailman/Cgi/create.py:231 bin/newlist:217 +#, fuzzy msgid "Illegal list name: %(s)s" msgstr "Ðеправильна назва ÑпиÑку: %(s)s" @@ -2163,6 +2223,7 @@ msgstr "" " ЗвернітьÑÑ Ð´Ð¾ адмініÑтратора Ñервера." #: Mailman/Cgi/create.py:273 bin/newlist:265 +#, fuzzy msgid "Your new mailing list: %(listname)s" msgstr "Ваш новий ÑпиÑок розÑилки: %(listname)s" @@ -2171,6 +2232,7 @@ msgid "Mailing list creation results" msgstr "Результати ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ ÑпиÑку розÑилки" #: Mailman/Cgi/create.py:288 +#, fuzzy msgid "" "You have successfully created the mailing list\n" " %(listname)s and notification has been sent to the list owner\n" @@ -2193,6 +2255,7 @@ msgid "Create another list" msgstr "Створити ще один ÑпиÑок розÑилки" #: Mailman/Cgi/create.py:312 +#, fuzzy msgid "Create a %(hostname)s Mailing List" msgstr "Створити %(hostname)s ÑпиÑок розÑилки" @@ -2290,6 +2353,7 @@ msgstr "" " керівником ÑпиÑку." #: Mailman/Cgi/create.py:432 +#, fuzzy msgid "" "Initial list of supported languages.

                  Note that if you do not\n" " select at least one initial language, the list will use the server\n" @@ -2395,6 +2459,7 @@ msgid "List name is required." msgstr "ВимагаєтьÑÑ Ñ–Ð¼'Ñ ÑпиÑку розÑилки." #: Mailman/Cgi/edithtml.py:154 +#, fuzzy msgid "%(realname)s -- Edit html for %(template_info)s" msgstr "%(realname)s - Редагувати HTML Ñторінки Ð´Ð»Ñ %(template_info)s" @@ -2403,10 +2468,12 @@ msgid "Edit HTML : Error" msgstr "Ð ÐµÐ´Ð°Ð³ÑƒÐ²Ð°Ð½Ð½Ñ HTML : Помилка" #: Mailman/Cgi/edithtml.py:161 +#, fuzzy msgid "%(safetemplatename)s: Invalid template" msgstr "%(safetemplatename)s: Ðеправильний шаблон" #: Mailman/Cgi/edithtml.py:166 Mailman/Cgi/edithtml.py:167 +#, fuzzy msgid "%(realname)s -- HTML Page Editing" msgstr "%(realname)s - Ð ÐµÐ´Ð°Ð³ÑƒÐ²Ð°Ð½Ð½Ñ HTML" @@ -2465,10 +2532,12 @@ msgid "HTML successfully updated." msgstr "HTML уÑпішно оновлено." #: Mailman/Cgi/listinfo.py:89 +#, fuzzy msgid "%(hostname)s Mailing Lists" msgstr "%(hostname)s ÑпиÑки розÑилки" #: Mailman/Cgi/listinfo.py:127 +#, fuzzy msgid "" "

                  There currently are no publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s." @@ -2477,6 +2546,7 @@ msgstr "" " ÑпиÑків розÑилки %(mailmanlink)s на %(hostname)s." #: Mailman/Cgi/listinfo.py:131 +#, fuzzy msgid "" "

                  Below is a listing of all the public mailing lists on\n" " %(hostname)s. Click on a list name to get more information " @@ -2496,6 +2566,7 @@ msgid "right" msgstr "Ñ–Ñнуючого" #: Mailman/Cgi/listinfo.py:140 +#, fuzzy msgid "" " To visit the general information page for an unadvertised list,\n" " open a URL similar to this one, but with a '/' and the %(adj)s\n" @@ -2557,6 +2628,7 @@ msgstr "Ðеправильна адреÑа" #: Mailman/Cgi/options.py:183 Mailman/Cgi/options.py:240 #: Mailman/Cgi/options.py:264 +#, fuzzy msgid "No such member: %(safeuser)s." msgstr "Такий кориÑтувач відÑутній: %(safeuser)s." @@ -2607,6 +2679,7 @@ msgid "Note: " msgstr "" #: Mailman/Cgi/options.py:378 +#, fuzzy msgid "List subscriptions for %(safeuser)s on %(hostname)s" msgstr "Перелік отримувачів %(safeuser)s на %(hostname)s" @@ -2634,6 +2707,7 @@ msgid "You are already using that email address" msgstr "Ви вже викориÑтовуєте цю адреÑу" #: Mailman/Cgi/options.py:459 +#, fuzzy msgid "" "The new address you requested %(newaddr)s is already a member of the\n" "%(listname)s mailing list, however you have also requested a global change " @@ -2647,6 +2721,7 @@ msgstr "" "в Ñких вона зуÑтрічаєтьÑÑ." #: Mailman/Cgi/options.py:468 +#, fuzzy msgid "The new address is already a member: %(newaddr)s" msgstr "Ð¦Ñ Ð°Ð´Ñ€ÐµÑа вже викориÑтовуєтьÑÑ ÐºÐ¾Ñ€Ð¸Ñтувачем: %(newaddr)s" @@ -2655,6 +2730,7 @@ msgid "Addresses may not be blank" msgstr "Поле адреÑи не може бути порожнім" #: Mailman/Cgi/options.py:488 +#, fuzzy msgid "A confirmation message has been sent to %(newaddr)s. " msgstr "ЛиÑÑ‚ Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð²Ñ–Ð´Ñ–Ñлано до %(newaddr)s." @@ -2667,6 +2743,7 @@ msgid "Illegal email address provided" msgstr "ÐедопуÑтима електронна адреÑа" #: Mailman/Cgi/options.py:501 +#, fuzzy msgid "%(newaddr)s is already a member of the list." msgstr "%(newaddr)s вже Ñ” кориÑтувачем цього ÑпиÑку розÑилки." @@ -2751,6 +2828,7 @@ msgstr "" " повідомленнÑ." #: Mailman/Cgi/options.py:623 +#, fuzzy msgid "" "You have been successfully unsubscribed from the\n" " mailing list %(fqdn_listname)s. If you were receiving digest\n" @@ -2840,6 +2918,7 @@ msgid "day" msgstr "день" #: Mailman/Cgi/options.py:900 +#, fuzzy msgid "%(days)d %(units)s" msgstr "%(days)d %(units)s" @@ -2852,6 +2931,7 @@ msgid "No topics defined" msgstr "Теми не визначені" #: Mailman/Cgi/options.py:940 +#, fuzzy msgid "" "\n" "You are subscribed to this list with the case-preserved address\n" @@ -2862,6 +2942,7 @@ msgstr "" "%(cpuser)s." #: Mailman/Cgi/options.py:956 +#, fuzzy msgid "%(realname)s list: member options login page" msgstr "СпиÑок %(realname)s: реєÑтраційна Ñторінка параметрів учаÑника" @@ -2870,10 +2951,12 @@ msgid "email address and " msgstr "електронну адреÑу та " #: Mailman/Cgi/options.py:960 +#, fuzzy msgid "%(realname)s list: member options for user %(safeuser)s" msgstr "СпиÑок %(realname)s: Ñторінка параметрів учаÑника %(safeuser)s" #: Mailman/Cgi/options.py:986 +#, fuzzy msgid "" "In order to change your membership option, you must\n" " first log in by giving your %(extra)smembership password in the section\n" @@ -2948,6 +3031,7 @@ msgid "" msgstr "<відÑутнÑ>" #: Mailman/Cgi/options.py:1140 +#, fuzzy msgid "Requested topic is not valid: %(topicname)s" msgstr "Запитана тема не Ñ” правильною: %(topicname)s" @@ -2976,6 +3060,7 @@ msgid "Private archive - \"./\" and \"../\" not allowed in URL." msgstr "" #: Mailman/Cgi/private.py:109 +#, fuzzy msgid "Private Archive Error - %(msg)s" msgstr "Помилка приватного архіву - %(msg)s" @@ -3013,6 +3098,7 @@ msgid "Mailing list deletion results" msgstr "Результати Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ ÑпиÑку розÑилки" #: Mailman/Cgi/rmlist.py:187 +#, fuzzy msgid "" "You have successfully deleted the mailing list\n" " %(listname)s." @@ -3021,6 +3107,7 @@ msgstr "" " %(listname)s." #: Mailman/Cgi/rmlist.py:191 +#, fuzzy msgid "" "There were some problems deleting the mailing list\n" " %(listname)s. Contact your site administrator at " @@ -3032,6 +3119,7 @@ msgstr "" " %(sitelist)s." #: Mailman/Cgi/rmlist.py:208 +#, fuzzy msgid "Permanently remove mailing list %(realname)s" msgstr "ОÑтаточне Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ ÑпиÑку розÑилки %(realname)s" @@ -3100,6 +3188,7 @@ msgid "Invalid options to CGI script" msgstr "Ðеправильні параметри Ð´Ð»Ñ Ñ†Ñ–Ñ”Ñ— CGI-програми." #: Mailman/Cgi/roster.py:118 +#, fuzzy msgid "%(realname)s roster authentication failed." msgstr "%(realname)s помилка автентифікації roster." @@ -3167,6 +3256,7 @@ msgstr "" "повідомленнÑ, Ñке буде міÑтити подальші інÑтрукції." #: Mailman/Cgi/subscribe.py:262 +#, fuzzy msgid "" "The email address you supplied is banned from this\n" " mailing list. If you think this restriction is erroneous, please\n" @@ -3192,6 +3282,7 @@ msgstr "" "адреÑа Ñ” небезпечною." #: Mailman/Cgi/subscribe.py:278 +#, fuzzy msgid "" "Confirmation from your email address is required, to prevent anyone from\n" "subscribing you without permission. Instructions are being sent to you at\n" @@ -3204,6 +3295,7 @@ msgstr "" "ви Ñ—Ñ— не підтвердите." #: Mailman/Cgi/subscribe.py:290 +#, fuzzy msgid "" "Your subscription request was deferred because %(x)s. Your request has " "been\n" @@ -3224,6 +3316,7 @@ msgid "Mailman privacy alert" msgstr "ЗаÑÑ‚ÐµÑ€ÐµÐ¶ÐµÐ½Ð½Ñ ÐºÐ¾Ð½Ñ„Ñ–Ð´ÐµÐ½Ñ†Ñ–Ð¹Ð½Ð¾Ñті Mailman" #: Mailman/Cgi/subscribe.py:316 +#, fuzzy msgid "" "An attempt was made to subscribe your address to the mailing list\n" "%(listaddr)s. You are already subscribed to this mailing list.\n" @@ -3264,6 +3357,7 @@ msgid "This list only supports digest delivery." msgstr "Цей ÑпиÑок підтримує доÑтавку лише збірок." #: Mailman/Cgi/subscribe.py:344 +#, fuzzy msgid "You have been successfully subscribed to the %(realname)s mailing list." msgstr "Ð’Ð°Ñ ÑƒÑпішно підпиÑано на ÑпиÑок розÑилки %(realname)s." @@ -3395,26 +3489,32 @@ msgid "n/a" msgstr "немає" #: Mailman/Commands/cmd_info.py:44 +#, fuzzy msgid "List name: %(listname)s" msgstr "Ðазва ÑпиÑку: %(listname)s" #: Mailman/Commands/cmd_info.py:45 +#, fuzzy msgid "Description: %(description)s" msgstr "ОпиÑ: %(description)s" #: Mailman/Commands/cmd_info.py:46 +#, fuzzy msgid "Postings to: %(postaddr)s" msgstr "ÐдреÑа надÑиланнÑ: %(postaddr)s" #: Mailman/Commands/cmd_info.py:47 +#, fuzzy msgid "List Helpbot: %(requestaddr)s" msgstr "ÐšÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ñ–Ð´Ð¿Ð¸Ñкою: %(requestaddr)s" #: Mailman/Commands/cmd_info.py:48 +#, fuzzy msgid "List Owners: %(owneraddr)s" msgstr "ВлаÑники ÑпиÑку: %(owneraddr)s" #: Mailman/Commands/cmd_info.py:49 +#, fuzzy msgid "More information: %(listurl)s" msgstr "Додаткова інформаціÑ: %(listurl)s" @@ -3437,18 +3537,22 @@ msgstr "" " Показати перелік публічних ÑпиÑків розÑилки на цьому Ñервері.\n" #: Mailman/Commands/cmd_lists.py:44 +#, fuzzy msgid "Public mailing lists at %(hostname)s:" msgstr "Публічні ÑпиÑки розÑилки на %(hostname)s:" #: Mailman/Commands/cmd_lists.py:66 +#, fuzzy msgid "%(i)3d. List name: %(realname)s" msgstr "%(i)3d. Ðазва ÑпиÑку: %(realname)s" #: Mailman/Commands/cmd_lists.py:67 +#, fuzzy msgid " Description: %(description)s" msgstr " ОпиÑ: %(description)s" #: Mailman/Commands/cmd_lists.py:68 +#, fuzzy msgid " Requests to: %(requestaddr)s" msgstr " ÐšÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ñ–Ð´Ð¿Ð¸Ñкою: %(requestaddr)s" @@ -3482,12 +3586,14 @@ msgstr "" " надÑилаєтьÑÑ Ð·Ð° адреÑою підпиÑки.\n" #: Mailman/Commands/cmd_password.py:51 Mailman/Commands/cmd_password.py:66 +#, fuzzy msgid "Your password is: %(password)s" msgstr "Ваш пароль: %(password)s" #: Mailman/Commands/cmd_password.py:57 Mailman/Commands/cmd_password.py:72 #: Mailman/Commands/cmd_password.py:95 Mailman/Commands/cmd_password.py:121 #: Mailman/Commands/cmd_set.py:149 Mailman/Commands/cmd_set.py:219 +#, fuzzy msgid "You are not a member of the %(listname)s mailing list" msgstr "Ви не Ñ” учаÑником ÑпиÑку розÑилки %(listname)s" @@ -3671,6 +3777,7 @@ msgstr "" " Ð½Ð°Ð³Ð°Ð´ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ð°Ñ€Ð¾Ð»ÑŽ до ÑпиÑку розÑилки.\n" #: Mailman/Commands/cmd_set.py:122 +#, fuzzy msgid "Bad set command: %(subcmd)s" msgstr "Ðеправильна команда set: %(subcmd)s" @@ -3691,6 +3798,7 @@ msgid "on" msgstr "ввімкнено" #: Mailman/Commands/cmd_set.py:154 +#, fuzzy msgid " ack %(onoff)s" msgstr " Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ (ack) %(onoff)s" @@ -3728,22 +3836,27 @@ msgid "due to bounces" msgstr "внаÑлідок помилок доÑтавки" #: Mailman/Commands/cmd_set.py:186 +#, fuzzy msgid " %(status)s (%(how)s on %(date)s)" msgstr " %(status)s (%(how)s %(date)s)" #: Mailman/Commands/cmd_set.py:192 +#, fuzzy msgid " myposts %(onoff)s" msgstr " копії влаÑних(mypost) %(onoff)s" #: Mailman/Commands/cmd_set.py:195 +#, fuzzy msgid " hide %(onoff)s" msgstr " приховуваннÑ(hide) %(onoff)s" #: Mailman/Commands/cmd_set.py:199 +#, fuzzy msgid " duplicates %(onoff)s" msgstr " дублікати(duplicates) %(onoff)s" #: Mailman/Commands/cmd_set.py:203 +#, fuzzy msgid " reminders %(onoff)s" msgstr " нагадуваннÑ(reminders) %(onoff)s" @@ -3752,6 +3865,7 @@ msgid "You did not give the correct password" msgstr "Ви вказали неправильний пароль" #: Mailman/Commands/cmd_set.py:236 Mailman/Commands/cmd_set.py:283 +#, fuzzy msgid "Bad argument: %(arg)s" msgstr "Ðеправильний параметр: %(arg)s" @@ -3826,6 +3940,7 @@ msgstr "" "дужок!).\n" #: Mailman/Commands/cmd_subscribe.py:62 +#, fuzzy msgid "Bad digest specifier: %(arg)s" msgstr "Ðеправильний параметр команди digest: %(arg)s" @@ -3834,6 +3949,7 @@ msgid "No valid address found to subscribe" msgstr "Ðе вказано правильної адреÑи підпиÑки" #: Mailman/Commands/cmd_subscribe.py:113 +#, fuzzy msgid "" "The email address you supplied is banned from this mailing list.\n" "If you think this restriction is erroneous, please contact the list\n" @@ -3848,8 +3964,8 @@ msgid "" "Mailman won't accept the given email address as a valid address.\n" "(E.g. it must have an @ in it.)" msgstr "" -"Вказана поштова адреÑа не Ñ” правильною. (Ðаприклад, вона повинна міÑтити \"@" -"\")." +"Вказана поштова адреÑа не Ñ” правильною. (Ðаприклад, вона повинна міÑтити " +"\"@\")." #: Mailman/Commands/cmd_subscribe.py:124 msgid "" @@ -3872,6 +3988,7 @@ msgid "This list only supports digest subscriptions!" msgstr "Цей ÑпиÑок підтримує підпиÑÑƒÐ²Ð°Ð½Ð½Ñ Ð»Ð¸ÑˆÐµ на збірки!" #: Mailman/Commands/cmd_subscribe.py:146 +#, fuzzy msgid "" "Your subscription request has been forwarded to the list administrator\n" "at %(listowner)s for review." @@ -3908,6 +4025,7 @@ msgstr "" " `address=<адреÑ>' (без лапок та кутових дужок!)\n" #: Mailman/Commands/cmd_unsubscribe.py:62 +#, fuzzy msgid "%(address)s is not a member of the %(listname)s mailing list" msgstr "%(address)s не Ñ” учаÑником ÑпиÑку розÑилки %(listname)s" @@ -4152,6 +4270,7 @@ msgid "Chinese (Taiwan)" msgstr "КитайÑька (Тайвань)" #: Mailman/Deliverer.py:53 +#, fuzzy msgid "" "Note: Since this is a list of mailing lists, administrative\n" "notices like the password reminder will be sent to\n" @@ -4166,14 +4285,17 @@ msgid " (Digest mode)" msgstr " (в режимі збірок)" #: Mailman/Deliverer.py:79 +#, fuzzy msgid "Welcome to the \"%(realname)s\" mailing list%(digmode)s" msgstr "ЛаÑкаво проÑимо у ÑпиÑок розÑилки \"%(realname)s\"%(digmode)s" #: Mailman/Deliverer.py:89 +#, fuzzy msgid "You have been unsubscribed from the %(realname)s mailing list" msgstr "Вашу підпиÑку на %(realname)s видалено" #: Mailman/Deliverer.py:116 +#, fuzzy msgid "%(listfullname)s mailing list reminder" msgstr "ÐÐ°Ð³Ð°Ð´ÑƒÐ²Ð°Ð½Ð½Ñ ÑпиÑку %(listfullname)s" @@ -4186,6 +4308,7 @@ msgid "Hostile subscription attempt detected" msgstr "ВиÑвлено Ñпробу зловмиÑного підпиÑуваннÑ" #: Mailman/Deliverer.py:169 +#, fuzzy msgid "" "%(address)s was invited to a different mailing\n" "list, but in a deliberate malicious attempt they tried to confirm the\n" @@ -4197,6 +4320,7 @@ msgstr "" "на ваш ÑпиÑок. Ви маєте про це знати. Ðе вимагаєтьÑÑ Ð¶Ð¾Ð´Ð½Ð¸Ñ… подальших дій." #: Mailman/Deliverer.py:188 +#, fuzzy msgid "" "You invited %(address)s to your list, but in a\n" "deliberate malicious attempt, they tried to confirm the invitation to a\n" @@ -4210,6 +4334,7 @@ msgstr "" "дій." #: Mailman/Deliverer.py:221 +#, fuzzy msgid "%(listname)s mailing list probe message" msgstr "теÑтове Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð¿Ð¾ÑˆÑ‚Ð¾Ð²Ð¾Ð³Ð¾ ÑпиÑку %(listname)s" @@ -4418,8 +4543,8 @@ msgid "" " membership.\n" "\n" "

                  You can control both the\n" -" number\n" +" number\n" " of reminders the member will receive and the\n" " Ви можете визначити\n" -" кількіÑть\n" +" кількіÑть\n" " нагадувань, Ñкі отримає учаÑник та період, через Ñкий надÑилатимутьÑÑ Ð½Ð°Ð³Ð°Ð´ÑƒÐ²Ð°Ð½Ð½Ñ.\n" @@ -4618,8 +4743,8 @@ msgid "" " No those messages too will get discarded. You may " "want\n" " to set up an\n" -" autoresponse\n" +" autoresponse\n" " message for email to the -owner and -admin address." msgstr "" "Хоча ÑиÑтема виÑÐ²Ð»ÐµÐ½Ð½Ñ Ð¿Ð¾Ð¼Ð¸Ð»Ð¾Ðº доÑтавки Mailman доÑтатньо надійна,\n" @@ -4640,8 +4765,9 @@ msgstr "" " але деÑкі люди вÑе ще можуть надÑилати повідомленнÑ\n" " за цією адреÑою. Якщо це ÑтанетьÑÑ, та Ñ†Ñ Ð·Ð¼Ñ–Ð½Ð½Ð° вÑтановлена\n" " у ÐÑ–, тоді ці Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð²Ñ–Ð´ÐºÐ¸Ð´Ð°Ñ‚Ð¸Ð¼ÑƒÑ‚ÑŒÑÑ. Ви можете\n" -" вÑтановити Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð°Ð²Ñ‚Ð¾Ð²Ñ–Ð´Ð¿Ð¾Ð²Ñ–Ð´Ð°Ñ‡Ð° Ð´Ð»Ñ -owner та -admin адреÑ." +" вÑтановити Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð°Ð²Ñ‚Ð¾Ð²Ñ–Ð´Ð¿Ð¾Ð²Ñ–Ð´Ð°Ñ‡Ð° Ð´Ð»Ñ -owner та -" +"admin адреÑ." #: Mailman/Gui/Bounce.py:147 #, fuzzy @@ -4705,6 +4831,7 @@ msgstr "" " учаÑника ÑпиÑку робитьÑÑ Ð·Ð°Ð²Ð¶Ð´Ð¸." #: Mailman/Gui/Bounce.py:194 +#, fuzzy msgid "" "Bad value for %(property)s: %(val)s" @@ -4848,8 +4975,8 @@ msgid "" "Use this option to remove each message attachment that does\n" " not have a matching content type. Requirements and formats " "are\n" -" exactly like filter_mime_types.\n" "\n" "

                  Note: if you add entries to this list but don't add\n" @@ -4964,6 +5091,7 @@ msgstr "" " адмініÑтратором вузла." #: Mailman/Gui/ContentFilter.py:171 +#, fuzzy msgid "Bad MIME type ignored: %(spectype)s" msgstr "Ðеправильний тип MIME проігноровано: %(spectype)s" @@ -5067,6 +5195,7 @@ msgid "" msgstr "ÐадіÑлати наÑтупну збірку негайно, Ñкщо вона не порожнÑ?" #: Mailman/Gui/Digest.py:145 +#, fuzzy msgid "" "The next digest will be sent as volume\n" " %(volume)s, number %(number)s" @@ -5083,14 +5212,17 @@ msgid "There was no digest to send." msgstr "Збірка порожнÑ, надÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð½Ðµ буде." #: Mailman/Gui/GUIBase.py:173 +#, fuzzy msgid "Invalid value for variable: %(property)s" msgstr "Ðеправильне Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð·Ð¼Ñ–Ð½Ð½Ð¾Ñ—: %(property)s" #: Mailman/Gui/GUIBase.py:178 +#, fuzzy msgid "Bad email address for option %(property)s: %(error)s" msgstr "Ðеправильна адреÑа Ð´Ð»Ñ Ð¿Ð°Ñ€Ð°Ð¼ÐµÑ‚Ñ€Ð° %(property)s: %(error)s" #: Mailman/Gui/GUIBase.py:204 +#, fuzzy msgid "" "The following illegal substitution variables were\n" " found in the %(property)s string:\n" @@ -5106,6 +5238,7 @@ msgstr "" " не уÑунете цю проблему." #: Mailman/Gui/GUIBase.py:218 +#, fuzzy msgid "" "Your %(property)s string appeared to\n" " have some correctable problems in its new value.\n" @@ -5203,8 +5336,8 @@ msgid "" "

                  In order to split the list ownership duties into\n" " administrators and moderators, you must\n" " set a separate moderator password,\n" -" and also provide the email\n" +" and also provide the email\n" " addresses of the list moderators. Note that the field you\n" " are changing here specifies the list administrators." msgstr "" @@ -5525,13 +5658,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5566,8 +5699,8 @@ msgstr "" " Ñправжньої зворотної адреÑи. По-друге, заміна Reply-To:\n" " уÑкладнює надÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð¿Ñ€Ð¸Ð²Ð°Ñ‚Ð½Ð¸Ñ… відповідей. ДивітьÑÑ `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful загальне Ð¾Ð±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ñ†ÑŒÐ¾Ð³Ð¾ " "питаннÑ.\n" " ДивітьÑÑ Reply-To:." msgid "" "This is the address set in the Reply-To: header\n" " when the reply_goes_to_list\n" +" href=\"?VARHELP=general/" +"reply_goes_to_list\">reply_goes_to_list\n" " option is set to Explicit address.\n" "\n" "

                  There are many reasons not to introduce or override the\n" @@ -5600,13 +5733,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5628,8 +5761,8 @@ msgid "" " Reply-To: header, it will not be changed." msgstr "" "Ð¦Ñ Ð°Ð´Ñ€ÐµÑа буде вÑтавлÑтиÑÑŒ у заголовок Reply-To: коли\n" -" параметр reply_goes_to_list\n" +" параметр reply_goes_to_list\n" " вÑтановлено у Певна адреÑа.\n" "\n" "

                  Є багато причин не додавати чи перезапиÑувати заголовок\n" @@ -5638,8 +5771,8 @@ msgstr "" " Ñправжньої зворотної адреÑи. По-друге, заміна Reply-To:\n" " уÑкладнює надÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð¿Ñ€Ð¸Ð²Ð°Ñ‚Ð½Ð¸Ñ… відповідей. ДивітьÑÑ `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful загальне Ð¾Ð±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ñ†ÑŒÐ¾Ð³Ð¾ " "питаннÑ.\n" " ДивітьÑÑ general\n" +" general\n" " non-member rules.\n" "\n" "

                  In the text boxes below, add one address per line; start the\n" @@ -6902,6 +7036,7 @@ msgid "By default, should new list member postings be moderated?" msgstr "Затримувати та перевірÑти Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð½Ð¾Ð²Ð¸Ñ… учаÑників?" #: Mailman/Gui/Privacy.py:218 +#, fuzzy msgid "" "Each list member has a moderation flag which says\n" " whether messages from the list member can be posted directly " @@ -6952,8 +7087,8 @@ msgid "" "If a member posts this many times, within a period of time\n" " the member is automatically moderated. Use 0 to disable. " "See\n" -" member_verbosity_interval for details on the time " "period.\n" "\n" @@ -7399,6 +7534,7 @@ msgstr "" " відкидаютьÑÑ, переÑилалиÑÑŒ керівнику ÑпиÑку Ð´Ð»Ñ ÐºÐ¾Ð½Ñ‚Ñ€Ð¾Ð»ÑŽ?" #: Mailman/Gui/Privacy.py:494 +#, fuzzy msgid "" "Text to include in any rejection notice to be sent to\n" " non-members who post to this list. This notice can include\n" @@ -7647,6 +7783,7 @@ msgstr "" " Ðезавершені правила фільтру будуть ігноруватиÑÑŒ." #: Mailman/Gui/Privacy.py:693 +#, fuzzy msgid "" "The header filter rule pattern\n" " '%(safepattern)s' is not a legal regular expression. This\n" @@ -7698,8 +7835,8 @@ msgid "" "

                  The body of the message can also be optionally scanned for\n" " Subject: and Keywords: headers, as\n" " specified by the topics_bodylines_limit\n" +" href=\"?VARHELP=topics/" +"topics_bodylines_limit\">topics_bodylines_limit\n" " configuration variable." msgstr "" "Фільтр тем виділÑÑ” категорії повідомлень відповідно до наведених нижче\n" @@ -7717,8 +7854,8 @@ msgstr "" "\n" "

                  У вміÑті повідомлень також можливо шукати заголовки\n" " Subject: чи Keywords:, Ñк вказано у\n" -" topics_bodylines_limit\n" +" topics_bodylines_limit\n" " змінній параметру." #: Mailman/Gui/Topics.py:72 @@ -7789,6 +7926,7 @@ msgstr "" " будуть ігноруватиÑÑŒ." #: Mailman/Gui/Topics.py:135 +#, fuzzy msgid "" "The topic pattern '%(safepattern)s' is not a\n" " legal regular expression. It will be discarded." @@ -8004,6 +8142,7 @@ msgid "%(listinfo_link)s list run by %(owner_link)s" msgstr "адмініÑтратори ÑпиÑку розÑилки %(listinfo_link)s: %(owner_link)s" #: Mailman/HTMLFormatter.py:57 +#, fuzzy msgid "%(realname)s administrative interface" msgstr "Ñ–Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ Ð°Ð´Ð¼Ñ–Ð½Ñ–Ñтратора Ð´Ð»Ñ %(realname)s" @@ -8012,6 +8151,7 @@ msgid " (requires authorization)" msgstr " (вимагає аутентифікації)" #: Mailman/HTMLFormatter.py:61 +#, fuzzy msgid "Overview of all %(hostname)s mailing lists" msgstr "СпиÑки розÑилки, розташовані на %(hostname)s" @@ -8032,6 +8172,7 @@ msgid "; it was disabled by the list administrator" msgstr " (за рішеннÑм адмініÑтратора)" #: Mailman/HTMLFormatter.py:146 +#, fuzzy msgid "" "; it was disabled due to excessive bounces. The\n" " last bounce was received on %(date)s" @@ -8044,6 +8185,7 @@ msgid "; it was disabled for unknown reasons" msgstr " (з невідомої причини)" #: Mailman/HTMLFormatter.py:151 +#, fuzzy msgid "Note: your list delivery is currently disabled%(reason)s." msgstr "Увага: доÑтавку ÑпиÑку розÑилки вам призупинено%(reason)s." @@ -8056,6 +8198,7 @@ msgid "the list administrator" msgstr "адмініÑтратору ÑпиÑку" #: Mailman/HTMLFormatter.py:157 +#, fuzzy msgid "" "

                  %(note)s\n" "\n" @@ -8074,6 +8217,7 @@ msgstr "" " Ð·Ð°Ð¿Ð¸Ñ‚Ð°Ð½Ð½Ñ Ñ‡Ð¸ вам потрібна допомога звернітьÑÑ Ð´Ð¾ %(mailto)s." #: Mailman/HTMLFormatter.py:169 +#, fuzzy msgid "" "

                  We have received some recent bounces from your\n" " address. Your current bounce score is %(score)s out of " @@ -8092,6 +8236,7 @@ msgstr "" " анульовано." #: Mailman/HTMLFormatter.py:181 +#, fuzzy msgid "" "(Note - you are subscribing to a list of mailing lists, so the %(type)s " "notice will be sent to the admin address for your membership, %(addr)s.)

                  " @@ -8136,6 +8281,7 @@ msgstr "" " керівника ÑпиÑку Ð²Ð°Ñ Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»Ñть електронною поштою." #: Mailman/HTMLFormatter.py:208 +#, fuzzy msgid "" "This is %(also)sa private list, which means that the\n" " list of members is not available to non-members." @@ -8144,6 +8290,7 @@ msgstr "" " доÑтупний оÑобам, Ñкі не беруть учаÑть у ÑпиÑку." #: Mailman/HTMLFormatter.py:211 +#, fuzzy msgid "" "This is %(also)sa hidden list, which means that the\n" " list of members is available only to the list administrator." @@ -8152,6 +8299,7 @@ msgstr "" " доÑтупний лише адмініÑтратору." #: Mailman/HTMLFormatter.py:214 +#, fuzzy msgid "" "This is %(also)sa public list, which means that the\n" " list of members list is available to everyone." @@ -8168,6 +8316,7 @@ msgstr "" " Ñпамерами)." #: Mailman/HTMLFormatter.py:222 +#, fuzzy msgid "" "

                  (Note that this is an umbrella list, intended to\n" " have only other mailing lists as members. Among other things,\n" @@ -8185,6 +8334,7 @@ msgid "either " msgstr "або " #: Mailman/HTMLFormatter.py:256 +#, fuzzy msgid "" "To unsubscribe from %(realname)s, get a password reminder,\n" " or change your subscription options %(either)senter your " @@ -8216,12 +8366,14 @@ msgid "" msgstr "Якщо ви залишите це поле порожнім, Ð²Ð°Ñ Ð±ÑƒÐ´Ðµ запитано електронну адреÑу" #: Mailman/HTMLFormatter.py:277 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " members.)" msgstr "(до %(which)s мають доÑтуп лише учаÑники ÑпиÑку розÑилки.)" #: Mailman/HTMLFormatter.py:281 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " administrator.)" @@ -8280,6 +8432,7 @@ msgid "The current archive" msgstr "Поточний архів" #: Mailman/Handlers/Acknowledge.py:59 +#, fuzzy msgid "%(realname)s post acknowledgement" msgstr "Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð½Ð°Ð´ÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð´Ð¾ %(realname)s" @@ -8292,6 +8445,7 @@ msgid "" msgstr "" #: Mailman/Handlers/CalcRecips.py:79 +#, fuzzy msgid "" "Your urgent message to the %(realname)s mailing list was not authorized for\n" "delivery. The original message as received by Mailman is attached.\n" @@ -8372,6 +8526,7 @@ msgid "Message may contain administrivia" msgstr "ÐŸÐ¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð¼Ð¾Ð¶Ð»Ð¸Ð²Ð¾ міÑтить адмініÑтративні команди" #: Mailman/Handlers/Hold.py:84 +#, fuzzy msgid "" "Please do *not* post administrative requests to the mailing\n" "list. If you wish to subscribe, visit %(listurl)s or send a message with " @@ -8413,10 +8568,12 @@ msgid "Posting to a moderated newsgroup" msgstr "ÐадÑÐ¸Ð»Ð°Ð½Ð½Ñ Ñƒ контрольовану групу новин" #: Mailman/Handlers/Hold.py:252 +#, fuzzy msgid "Your message to %(listname)s awaits moderator approval" msgstr "Ваше Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð´Ð¾ %(listname)s очікує розглÑду керівника" #: Mailman/Handlers/Hold.py:271 +#, fuzzy msgid "%(listname)s post from %(sender)s requires approval" msgstr "ÐŸÐ¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð´Ð¾ %(listname)s від %(sender)s очікує розглÑду" @@ -8458,6 +8615,7 @@ msgid "After content filtering, the message was empty" msgstr "ПіÑÐ»Ñ Ñ„Ñ–Ð»ÑŒÑ‚Ñ€ÑƒÐ²Ð°Ð½Ð½Ñ Ð²Ð¼Ñ–Ñту Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ñтало порожнім" #: Mailman/Handlers/MimeDel.py:269 +#, fuzzy msgid "" "The attached message matched the %(listname)s mailing list's content " "filtering\n" @@ -8500,6 +8658,7 @@ msgid "The attached message has been automatically discarded." msgstr "Ð’ÐºÐ»Ð°Ð´ÐµÐ½Ð½Ñ Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð±ÑƒÐ»Ð¾ автоматично відкинуто." #: Mailman/Handlers/Replybot.py:75 +#, fuzzy msgid "Auto-response for your message to the \"%(realname)s\" mailing list" msgstr "" "Ðвтоматична відповідь на ваше Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ñƒ ÑпиÑок розÑилки \"%(realname)s\"" @@ -8524,6 +8683,7 @@ msgid "HTML attachment scrubbed and removed" msgstr "HTML Ð²ÐºÐ»Ð°Ð´ÐµÐ½Ð½Ñ Ð±ÑƒÐ»Ð¾ очищене та видалено" #: Mailman/Handlers/Scrubber.py:234 Mailman/Handlers/Scrubber.py:259 +#, fuzzy msgid "" "An HTML attachment was scrubbed...\n" "URL: %(url)s\n" @@ -8578,6 +8738,7 @@ msgstr "" "Url : %(url)s\n" #: Mailman/Handlers/Scrubber.py:347 +#, fuzzy msgid "Skipped content of type %(partctype)s\n" msgstr "Пропущено вміÑÑ‚ типу %(partctype)s\n" @@ -8608,6 +8769,7 @@ msgid "Message rejected by filter rule match" msgstr "ÐŸÐ¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð²Ñ–Ð´ÐºÐ¸Ð½ÑƒÑ‚Ðµ правилом фільтру" #: Mailman/Handlers/ToDigest.py:173 +#, fuzzy msgid "%(realname)s Digest, Vol %(volume)d, Issue %(issue)d" msgstr "Збірка %(realname)s, Том %(volume)d, ВипуÑк %(issue)d" @@ -8644,6 +8806,7 @@ msgid "End of " msgstr "Кінець " #: Mailman/ListAdmin.py:309 +#, fuzzy msgid "Posting of your message titled \"%(subject)s\"" msgstr "ÐадÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð²Ð°ÑˆÐ¾Ð³Ð¾ Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð· темою \"%(subject)s\"" @@ -8656,6 +8819,7 @@ msgid "Forward of moderated message" msgstr "Переправлене переглÑнуте повідомленнÑ" #: Mailman/ListAdmin.py:405 +#, fuzzy msgid "New subscription request to list %(realname)s from %(addr)s" msgstr "Ðовий запит на підпиÑку на ÑпиÑок %(realname)s від %(addr)s" @@ -8669,6 +8833,7 @@ msgid "via admin approval" msgstr "Продовжувати очікувати підтвердженнÑ" #: Mailman/ListAdmin.py:466 +#, fuzzy msgid "New unsubscription request from %(realname)s by %(addr)s" msgstr "Запит на Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð¿Ñ–Ð´Ð¿Ð¸Ñки на %(realname)s від %(addr)s" @@ -8681,10 +8846,12 @@ msgid "Original Message" msgstr "Оригінальне повідомленнÑ" #: Mailman/ListAdmin.py:526 +#, fuzzy msgid "Request to mailing list %(realname)s rejected" msgstr "Запит на ÑпиÑок розÑилки %(realname)s відкинуто" #: Mailman/MTA/Manual.py:66 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been created via the through-the-web\n" "interface. In order to complete the activation of this mailing list, the\n" @@ -8711,14 +8878,17 @@ msgstr "" "можливо, запуÑтити програму `newaliases':\n" #: Mailman/MTA/Manual.py:82 +#, fuzzy msgid "## %(listname)s mailing list" msgstr "## ÑпиÑок розÑилки %(listname)s" #: Mailman/MTA/Manual.py:99 +#, fuzzy msgid "Mailing list creation request for list %(listname)s" msgstr "Запит ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ ÑпиÑку розÑилки %(listname)s" #: Mailman/MTA/Manual.py:113 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been removed via the through-the-web\n" "interface. In order to complete the de-activation of this mailing list, " @@ -8736,6 +8906,7 @@ msgstr "" "ОÑÑŒ елементи файл /etc/aliases Ñкі потрібно видалити:\n" #: Mailman/MTA/Manual.py:123 +#, fuzzy msgid "" "\n" "To finish removing your mailing list, you must edit your /etc/aliases (or\n" @@ -8752,14 +8923,17 @@ msgstr "" "## ÑпиÑок розÑилки %(listname)s" #: Mailman/MTA/Manual.py:142 +#, fuzzy msgid "Mailing list removal request for list %(listname)s" msgstr "Запит на Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ ÑпиÑку розÑилки %(listname)s" #: Mailman/MTA/Postfix.py:442 +#, fuzzy msgid "checking permissions on %(file)s" msgstr "перевірÑєтьÑÑ Ñ€ÐµÐ¶Ð¸Ð¼ доÑтупу до %(file)s" #: Mailman/MTA/Postfix.py:452 +#, fuzzy msgid "%(file)s permissions must be 0664 (got %(octmode)s)" msgstr "режим доÑтупу до %(file)s повинен бути 0664 (а не %(octmode)s)" @@ -8773,36 +8947,44 @@ msgid "(fixing)" msgstr "(виправленнÑ)" #: Mailman/MTA/Postfix.py:470 +#, fuzzy msgid "checking ownership of %(dbfile)s" msgstr "перевірÑєтьÑÑ Ð²Ð»Ð°Ñник %(dbfile)s" #: Mailman/MTA/Postfix.py:478 +#, fuzzy msgid "%(dbfile)s owned by %(owner)s (must be owned by %(user)s" msgstr "влаÑником %(dbfile)s Ñ” %(owner)s (повинен бути %(user)s" #: Mailman/MTA/Postfix.py:491 +#, fuzzy msgid "%(dbfile)s permissions must be 0664 (got %(octmode)s)" msgstr "режим доÑтупу до %(dbfile)s повинен бути 0664 (а не %(octmode)s)" #: Mailman/MailList.py:219 +#, fuzzy msgid "Your confirmation is required to join the %(listname)s mailing list" msgstr "" "Ð”Ð»Ñ Ð¿Ñ€Ð¸Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð´Ð¾ ÑпиÑку розÑилки %(listname)s потрібне ваше підтвердженнÑ" #: Mailman/MailList.py:230 +#, fuzzy msgid "Your confirmation is required to leave the %(listname)s mailing list" msgstr "" "Ð”Ð»Ñ Ð²Ð¸ÐºÐ»ÑŽÑ‡ÐµÐ½Ð½Ñ Ð·Ñ– ÑпиÑку розÑилки %(listname)s потрібне ваше підтвердженнÑ" #: Mailman/MailList.py:999 Mailman/MailList.py:1492 +#, fuzzy msgid " from %(remote)s" msgstr " з %(remote)s" #: Mailman/MailList.py:1043 +#, fuzzy msgid "subscriptions to %(realname)s require moderator approval" msgstr "підпиÑка на %(realname)s вимагає ÑÑ…Ð²Ð°Ð»ÐµÐ½Ð½Ñ ÐºÐµÑ€Ñ–Ð²Ð½Ð¸ÐºÐ¾Ð¼" #: Mailman/MailList.py:1125 bin/add_members:299 +#, fuzzy msgid "%(realname)s subscription notification" msgstr "%(realname)s ÑÐ¿Ð¾Ð²Ñ–Ñ‰ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¾ підпиÑку" @@ -8811,6 +8993,7 @@ msgid "unsubscriptions require moderator approval" msgstr "Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð¿Ñ–Ð´Ð¿Ð¸Ñки вимагає ÑÑ…Ð²Ð°Ð»ÐµÐ½Ð½Ñ ÐºÐµÑ€Ñ–Ð²Ð½Ð¸ÐºÐ¾Ð¼" #: Mailman/MailList.py:1166 +#, fuzzy msgid "%(realname)s unsubscribe notification" msgstr "%(realname)s ÑÐ¿Ð¾Ð²Ñ–Ñ‰ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¾ Ð¿Ñ€Ð¸Ð¿Ð¸Ð½ÐµÐ½Ð½Ñ Ð¿Ñ–Ð´Ð¿Ð¸Ñки" @@ -8830,6 +9013,7 @@ msgid "via web confirmation" msgstr "Ðеправильний Ñ€Ñдок підтвердженнÑ" #: Mailman/MailList.py:1396 +#, fuzzy msgid "subscriptions to %(name)s require administrator approval" msgstr "підпиÑка на %(name)s вимагає ÑÑ…Ð²Ð°Ð»ÐµÐ½Ð½Ñ ÐºÐµÑ€Ñ–Ð²Ð½Ð¸ÐºÐ¾Ð¼" @@ -8848,6 +9032,7 @@ msgid "Last autoresponse notification for today" msgstr "Ðвтоматична відповідь ÑÐ¿Ð¾Ð²Ñ–Ñ‰ÐµÐ½Ð½Ñ Ð½Ð° Ñьогодні" #: Mailman/Queue/BounceRunner.py:360 +#, fuzzy msgid "" "The attached message was received as a bounce, but either the bounce format\n" "was not recognized, or no member addresses could be extracted from it. " @@ -8903,8 +9088,8 @@ msgid "" "To obtain instructions, send a message containing just the word \"help\".\n" msgstr "" "У цьому повідомленні не знайдено команд.\n" -"Ð”Ð»Ñ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ñ–Ð½Ñтрукцій, надішліть Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð· одним лише Ñловом \"help" -"\".\n" +"Ð”Ð»Ñ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ñ–Ð½Ñтрукцій, надішліть Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð· одним лише Ñловом " +"\"help\".\n" #: Mailman/Queue/CommandRunner.py:197 msgid "" @@ -8937,6 +9122,7 @@ msgid "Original message suppressed by Mailman site configuration\n" msgstr "" #: Mailman/htmlformat.py:675 +#, fuzzy msgid "Delivered by Mailman
                  version %(version)s" msgstr "ДоÑтавлено Mailman
                  верÑÑ–Ñ— %(version)s" @@ -9025,6 +9211,7 @@ msgid "Server Local Time" msgstr "Ð§Ð°Ñ Ð½Ð° Ñервері" #: Mailman/i18n.py:180 +#, fuzzy msgid "" "%(wday)s %(mon)s %(day)2i %(hh)02i:%(mm)02i:%(ss)02i %(tzname)s %(year)04i" msgstr "" @@ -9137,6 +9324,7 @@ msgstr "" "можна вказати `-'.\n" #: bin/add_members:162 bin/add_members:172 +#, fuzzy msgid "Already a member: %(member)s" msgstr "Вже Ñ” учаÑником: %(member)s" @@ -9145,10 +9333,12 @@ msgid "Bad/Invalid email address: blank line" msgstr "Ðеправильна електронна адреÑа: Ñ€Ñдок порожній" #: bin/add_members:180 +#, fuzzy msgid "Bad/Invalid email address: %(member)s" msgstr "Ðеправильна адреÑа: %(member)s" #: bin/add_members:182 +#, fuzzy msgid "Hostile address (illegal characters): %(member)s" msgstr "Помилкова адреÑа (недопуÑтимі Ñимволи): %(member)s" @@ -9158,14 +9348,17 @@ msgid "Invited: %(member)s" msgstr "ПідпиÑаний: %(member)s" #: bin/add_members:187 +#, fuzzy msgid "Subscribed: %(member)s" msgstr "ПідпиÑаний: %(member)s" #: bin/add_members:237 +#, fuzzy msgid "Bad argument to -w/--welcome-msg: %(arg)s" msgstr "ÐедопуÑтимий аргумент Ð´Ð»Ñ -w/--welcome-msg: %(arg)s" #: bin/add_members:244 +#, fuzzy msgid "Bad argument to -a/--admin-notify: %(arg)s" msgstr "ÐедопуÑтимий аргумент Ð´Ð»Ñ -a/--admin-notify: %(arg)s" @@ -9182,6 +9375,7 @@ msgstr "" #: bin/add_members:261 bin/config_list:110 bin/export.py:271 bin/find_member:97 #: bin/inject:91 bin/list_admins:90 bin/list_members:252 bin/sync_members:222 #: cron/bumpdigests:86 +#, fuzzy msgid "No such list: %(listname)s" msgstr "Такого ÑпиÑку розÑилки немає: %(listname)s" @@ -9192,6 +9386,7 @@ msgid "Nothing to do." msgstr "Ðемає що робити." #: bin/arch:19 +#, fuzzy msgid "" "Rebuild a list's archive.\n" "\n" @@ -9283,6 +9478,7 @@ msgid "listname is required" msgstr "необхідно вказати назву ÑпиÑку розÑилки" #: bin/arch:143 bin/change_pw:107 bin/config_list:257 +#, fuzzy msgid "" "No such list \"%(listname)s\"\n" "%(e)s" @@ -9291,10 +9487,12 @@ msgstr "" "%(e)s" #: bin/arch:168 +#, fuzzy msgid "Cannot open mbox file %(mbox)s: %(msg)s" msgstr "Ðеможливо відкрити файл %(mbox)s: %(msg)s" #: bin/b4b5-archfix:19 +#, fuzzy msgid "" "Fix the MM2.1b4 archives.\n" "\n" @@ -9437,6 +9635,7 @@ msgstr "" " ВивеÑти цю довідку та завершитиÑÑŒ\n" #: bin/change_pw:145 +#, fuzzy msgid "Bad arguments: %(strargs)s" msgstr "неправильний аргумент: %(strargs)s" @@ -9445,14 +9644,17 @@ msgid "Empty list passwords are not allowed" msgstr "Порожні паролі ÑпиÑків не дозволÑютьÑÑ." #: bin/change_pw:181 +#, fuzzy msgid "New %(listname)s password: %(notifypassword)s" msgstr "Ваш %(listname)s пароль: %(notifypassword)s" #: bin/change_pw:190 +#, fuzzy msgid "Your new %(listname)s list password" msgstr "Ваш новий %(listname)s пароль" #: bin/change_pw:191 +#, fuzzy msgid "" "The site administrator at %(hostname)s has changed the password for your\n" "mailing list %(listname)s. It is now\n" @@ -9479,6 +9681,7 @@ msgstr "" " %(adminurl)s\n" #: bin/check_db:19 +#, fuzzy msgid "" "Check a list's config database file for integrity.\n" "\n" @@ -9554,10 +9757,12 @@ msgid "List:" msgstr "СпиÑок:" #: bin/check_db:148 +#, fuzzy msgid " %(file)s: okay" msgstr " %(file)s: гаразд" #: bin/check_perms:20 +#, fuzzy msgid "" "Check the permissions for the Mailman installation.\n" "\n" @@ -9577,43 +9782,53 @@ msgstr "" "режим\n" #: bin/check_perms:110 +#, fuzzy msgid " checking gid and mode for %(path)s" msgstr " перевірка gid та режиму %(path)s" #: bin/check_perms:122 +#, fuzzy msgid "%(path)s bad group (has: %(groupname)s, expected %(MAILMAN_GROUP)s)" msgstr "" "%(path)s неправильна група (Ñ”: %(groupname)s, очікувалоÑÑŒ %(MAILMAN_GROUP)s)" #: bin/check_perms:151 +#, fuzzy msgid "directory permissions must be %(octperms)s: %(path)s" msgstr "права каталогу повинні бути %(octperms)s: %(path)s" #: bin/check_perms:160 +#, fuzzy msgid "source perms must be %(octperms)s: %(path)s" msgstr "файли програми повинні мати права %(octperms)s: %(path)s" #: bin/check_perms:171 +#, fuzzy msgid "article db files must be %(octperms)s: %(path)s" msgstr "файли бази даних Ñтатей повинні мати права %(octperms)s: %(path)s" #: bin/check_perms:183 +#, fuzzy msgid "checking mode for %(prefix)s" msgstr "перевірка режиму Ð´Ð»Ñ %(prefix)s" #: bin/check_perms:193 +#, fuzzy msgid "WARNING: directory does not exist: %(d)s" msgstr "УВÐГÐ: каталог не Ñ–Ñнує: %(d)s" #: bin/check_perms:197 +#, fuzzy msgid "directory must be at least 02775: %(d)s" msgstr "каталог повинен мати права принаймні 02775: %(d)s" #: bin/check_perms:209 +#, fuzzy msgid "checking perms on %(private)s" msgstr "перевірÑютьÑÑ Ð¿Ñ€Ð°Ð²Ð° на %(private)s" #: bin/check_perms:214 +#, fuzzy msgid "%(private)s must not be other-readable" msgstr "%(private)s повинно бути не доÑтупним Ð´Ð»Ñ Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ Ñ–Ð½ÑˆÐ¸Ð¼" @@ -9631,6 +9846,7 @@ msgid "mbox file must be at least 0660:" msgstr "права файлу mbox повинні бути принаймні 0660:" #: bin/check_perms:263 +#, fuzzy msgid "%(dbdir)s \"other\" perms must be 000" msgstr "права \"інших\" на %(dbdir)s повинні бути 000" @@ -9639,26 +9855,32 @@ msgid "checking cgi-bin permissions" msgstr "перевірÑютьÑÑ Ð¿Ñ€Ð°Ð²Ð° cgi-bin" #: bin/check_perms:278 +#, fuzzy msgid " checking set-gid for %(path)s" msgstr " перевірÑєтьÑÑ set-gid у %(path)s" #: bin/check_perms:282 +#, fuzzy msgid "%(path)s must be set-gid" msgstr "%(path)s повинен бути set-gid" #: bin/check_perms:292 +#, fuzzy msgid "checking set-gid for %(wrapper)s" msgstr "перевірÑєтьÑÑ set-gid у %(wrapper)s" #: bin/check_perms:296 +#, fuzzy msgid "%(wrapper)s must be set-gid" msgstr "%(wrapper)s повинен бути set-gid" #: bin/check_perms:306 +#, fuzzy msgid "checking permissions on %(pwfile)s" msgstr "перевірÑютьÑÑ Ð¿Ñ€Ð°Ð²Ð° у %(pwfile)s" #: bin/check_perms:315 +#, fuzzy msgid "%(pwfile)s permissions must be exactly 0640 (got %(octmode)s)" msgstr "права доÑтупу до %(pwfile)s повинні бути 0640 (наразі %(octmode)s)" @@ -9667,10 +9889,12 @@ msgid "checking permissions on list data" msgstr "перевірÑютьÑÑ Ð¿Ñ€Ð°Ð²Ð° на дані ÑпиÑку" #: bin/check_perms:348 +#, fuzzy msgid " checking permissions on: %(path)s" msgstr " перевірÑютьÑÑ Ð¿Ñ€Ð°Ð²Ð° на: %(path)s" #: bin/check_perms:356 +#, fuzzy msgid "file permissions must be at least 660: %(path)s" msgstr "права доÑтупу повинні бути принаймні 660: %(path)s" @@ -9754,6 +9978,7 @@ msgid "Unix-From line changed: %(lineno)d" msgstr "Змінено Unix-From Ñ€Ñдок: %(lineno)d" #: bin/cleanarch:111 +#, fuzzy msgid "Bad status number: %(arg)s" msgstr "Ðеправильне чиÑло у параметрі status: %(arg)s" @@ -9899,10 +10124,12 @@ msgid " original address removed:" msgstr " початкову адреÑу видалено:" #: bin/clone_member:202 +#, fuzzy msgid "Not a valid email address: %(toaddr)s" msgstr "Ðеправильна електронна адреÑа: %(toaddr)s" #: bin/clone_member:215 +#, fuzzy msgid "" "Error opening list \"%(listname)s\", skipping.\n" "%(e)s" @@ -10031,22 +10258,27 @@ msgid "legal values are:" msgstr "допуÑтимі значеннÑ:" #: bin/config_list:270 +#, fuzzy msgid "attribute \"%(k)s\" ignored" msgstr "атрибут \"%(k)s\" проігноровано" #: bin/config_list:273 +#, fuzzy msgid "attribute \"%(k)s\" changed" msgstr "атрибут \"%(k)s\" змінено" #: bin/config_list:279 +#, fuzzy msgid "Non-standard property restored: %(k)s" msgstr "Відновлено неÑтандартну влаÑтивіÑть: %(k)s" #: bin/config_list:288 +#, fuzzy msgid "Invalid value for property: %(k)s" msgstr "Ðеправильне Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð²Ð»Ð°ÑтивоÑті: %(k)s" #: bin/config_list:291 +#, fuzzy msgid "Bad email address for option %(k)s: %(v)s" msgstr "Ðеправильна адреÑа Ð´Ð»Ñ Ð¿Ð°Ñ€Ð°Ð¼ÐµÑ‚Ñ€Ð° %(k)s: %(v)s" @@ -10111,19 +10343,23 @@ msgstr "" " Ðе виводити Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¾ Ñтан.\n" #: bin/discard:94 +#, fuzzy msgid "Ignoring non-held message: %(f)s" msgstr "ІгноруєтьÑÑ Ð½Ðµ відкладене повідомленнÑ: %(f)s" #: bin/discard:100 +#, fuzzy msgid "Ignoring held msg w/bad id: %(f)s" msgstr "" "ІгноруєтьÑÑ Ð²Ñ–Ð´ÐºÐ»Ð°Ð´ÐµÐ½Ðµ Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð· неправильним ідентифікатором: %(f)s" #: bin/discard:112 +#, fuzzy msgid "Discarded held msg #%(id)s for list %(listname)s" msgstr "Відкинуто відкладене Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ #%(id)s зі ÑпиÑку %(listname)s" #: bin/dumpdb:19 +#, fuzzy msgid "" "Dump the contents of any Mailman `database' file.\n" "\n" @@ -10195,6 +10431,7 @@ msgid "No filename given." msgstr "Ðе вказано назву файлу." #: bin/dumpdb:108 +#, fuzzy msgid "Bad arguments: %(pargs)s" msgstr "Ðеправильні параметри: %(pargs)s" @@ -10213,6 +10450,7 @@ msgid "[----- end %(typename)s file -----]" msgstr "[----- кінець файлу %(typename)s -----]" #: bin/dumpdb:142 +#, fuzzy msgid "<----- start object %(cnt)s ----->" msgstr "<----- початок об'єкту %(cnt)s ----->" @@ -10418,6 +10656,7 @@ msgid "Setting web_page_url to: %(web_page_url)s" msgstr "web_page_url вÑтановлюєтьÑÑ Ñƒ: %(web_page_url)s" #: bin/fix_url.py:88 +#, fuzzy msgid "Setting host_name to: %(mailhost)s" msgstr "host_name вÑтановлюєтьÑÑ Ñƒ: %(mailhost)s" @@ -10507,6 +10746,7 @@ msgstr "" "викориÑтовуєтьÑÑ Ð¿Ð¾Ñ‚Ñ–Ðº Ñтандартного вводу.\n" #: bin/inject:84 +#, fuzzy msgid "Bad queue directory: %(qdir)s" msgstr "Ðеправильний каталог черги: %(qdir)s" @@ -10515,6 +10755,7 @@ msgid "A list name is required" msgstr "Ðеобхідно вказати назву ÑпиÑку" #: bin/list_admins:20 +#, fuzzy msgid "" "List all the owners of a mailing list.\n" "\n" @@ -10561,6 +10802,7 @@ msgstr "" "У командному Ñ€Ñдку можна вказувати більше однієї назви.\n" #: bin/list_admins:97 +#, fuzzy msgid "List: %(listname)s, \tOwners: %(owners)s" msgstr "СпиÑок: %(listname)s, \tВлаÑники: %(owners)s" @@ -10739,10 +10981,12 @@ msgstr "" "не виводитьÑÑ Ð¶Ð¾Ð´Ð½Ð¸Ñ… ознак типу доÑтавки повідомлень.\n" #: bin/list_members:198 +#, fuzzy msgid "Bad --nomail option: %(why)s" msgstr "Ðеправильний параметр --nomail: %(why)s" #: bin/list_members:209 +#, fuzzy msgid "Bad --digest option: %(kind)s" msgstr "Ðеправильний параметр -digest: %(kind)s" @@ -10756,6 +11000,7 @@ msgid "Could not open file for writing:" msgstr "Ðеможливо відкрити файл Ð´Ð»Ñ Ð·Ð°Ð¿Ð¸Ñу:" #: bin/list_owners:20 +#, fuzzy msgid "" "List the owners of a mailing list, or all mailing lists.\n" "\n" @@ -10807,6 +11052,7 @@ msgid "" msgstr "" #: bin/mailmanctl:20 +#, fuzzy msgid "" "Primary start-up and shutdown script for Mailman's qrunner daemon.\n" "\n" @@ -10982,6 +11228,7 @@ msgstr "" " у них наÑтупного повідомленнÑ.\n" #: bin/mailmanctl:152 +#, fuzzy msgid "PID unreadable in: %(pidfile)s" msgstr "Ðеможливо зчитати PID з: %(pidfile)s" @@ -10990,6 +11237,7 @@ msgid "Is qrunner even running?" msgstr "Чи обробник черги взагалі запущений?" #: bin/mailmanctl:160 +#, fuzzy msgid "No child with pid: %(pid)s" msgstr "Ðемає нащадка з pid: %(pid)s" @@ -11017,6 +11265,7 @@ msgstr "" "перезапуÑтити з ключем -s\n" #: bin/mailmanctl:233 +#, fuzzy msgid "" "The master qrunner lock could not be acquired, because it appears as if " "some\n" @@ -11042,10 +11291,12 @@ msgstr "" "ЗавершеннÑ." #: bin/mailmanctl:280 cron/mailpasswds:119 +#, fuzzy msgid "Site list is missing: %(sitelistname)s" msgstr "СпиÑок Ñайту відÑутній: %(sitelistname)s" #: bin/mailmanctl:305 +#, fuzzy msgid "Run this program as root or as the %(name)s user, or use -u." msgstr "" "ЗапуÑтіть цю програму від root або від кориÑтувача %(name)s user,\n" @@ -11056,6 +11307,7 @@ msgid "No command given." msgstr "Ðе вказано жодної команди." #: bin/mailmanctl:339 +#, fuzzy msgid "Bad command: %(command)s" msgstr "Ðеправильна команда: %(command)s" @@ -11080,6 +11332,7 @@ msgid "Starting Mailman's master qrunner." msgstr "ЗапуÑкаєтьÑÑ Ð³Ð¾Ð»Ð¾Ð²Ð½Ð¸Ð¹ обробник Mailman" #: bin/mmsitepass:19 +#, fuzzy msgid "" "Set the site password, prompting from the terminal.\n" "\n" @@ -11132,6 +11385,7 @@ msgid "list creator" msgstr "оÑоби що Ñтворює ÑпиÑки" #: bin/mmsitepass:86 +#, fuzzy msgid "New %(pwdesc)s password: " msgstr "Ðовий пароль %(pwdesc)s:" @@ -11385,6 +11639,7 @@ msgstr "" "Зверніть увагу, назви ÑпиÑків розÑилки обов'Ñзково у нижньому регіÑтрі.\n" #: bin/newlist:162 +#, fuzzy msgid "Unknown language: %(lang)s" msgstr "Ðевідома мова: %(lang)s" @@ -11397,6 +11652,7 @@ msgid "Enter the email of the person running the list: " msgstr "Вкажіть поштову адреÑу влаÑника ÑпиÑку розÑилки: " #: bin/newlist:193 +#, fuzzy msgid "Initial %(listname)s password: " msgstr "Початковий пароль %(listname)s: " @@ -11406,11 +11662,12 @@ msgstr "Пароль ÑпиÑку розÑилки не повинен бути #: bin/newlist:220 msgid "" -" - owner addresses need to be fully-qualified names like \"owner@example.com" -"\", not just \"owner\"." +" - owner addresses need to be fully-qualified names like \"owner@example." +"com\", not just \"owner\"." msgstr "" #: bin/newlist:244 +#, fuzzy msgid "Hit enter to notify %(listname)s owner..." msgstr "ÐатиÑніть Enter щоб ÑповіÑтити влаÑника %(listname)s ..." @@ -11539,6 +11796,7 @@ msgstr "" "викориÑтанні параметра -l.\n" #: bin/qrunner:179 +#, fuzzy msgid "%(name)s runs the %(runnername)s qrunner" msgstr "%(name)s виконує обробник черги %(runnername)s" @@ -11551,6 +11809,7 @@ msgid "No runner name given." msgstr "Ðе вказано назву обробника." #: bin/rb-archfix:21 +#, fuzzy msgid "" "Reduce disk space usage for Pipermail archives.\n" "\n" @@ -11694,18 +11953,22 @@ msgstr "" "\n" #: bin/remove_members:156 +#, fuzzy msgid "Could not open file for reading: %(filename)s." msgstr "Ðеможливо відкрити файл Ð´Ð»Ñ Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ: %(filename)s." #: bin/remove_members:163 +#, fuzzy msgid "Error opening list %(listname)s... skipping." msgstr "Ðеможливо відкрити ÑпиÑок розÑилки %(listname)s... Пропущено." #: bin/remove_members:173 +#, fuzzy msgid "No such member: %(addr)s" msgstr "Такий кориÑтувач відÑутній: %(addr)s" #: bin/remove_members:178 +#, fuzzy msgid "User `%(addr)s' removed from list: %(listname)s." msgstr "КориÑтувач `%(addr)s' видалений зі ÑпиÑку: %(listname)s." @@ -11744,10 +12007,12 @@ msgstr "" " Виводити відомоÑті про хід виконаннÑ.\n" #: bin/reset_pw.py:77 +#, fuzzy msgid "Changing passwords for list: %(listname)s" msgstr "ЗмінюєтьÑÑ Ð¿Ð°Ñ€Ð¾Ð»ÑŒ ÑпиÑку ÑпиÑку розÑилки: %(listname)s" #: bin/reset_pw.py:83 +#, fuzzy msgid "New password for member %(member)40s: %(randompw)s" msgstr "Ðовий пароль учаÑника %(member)40s: %(randompw)s" @@ -11792,18 +12057,22 @@ msgstr "" "\n" #: bin/rmlist:73 bin/rmlist:76 +#, fuzzy msgid "Removing %(msg)s" msgstr "Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ %(msg)s" #: bin/rmlist:81 +#, fuzzy msgid "%(listname)s %(msg)s not found as %(filename)s" msgstr "%(listname)s %(msg)s не знайдено Ñк файл %(filename)s" #: bin/rmlist:105 +#, fuzzy msgid "No such list (or list already deleted): %(listname)s" msgstr "СпиÑок розÑилки не Ñ–Ñнує (або вже видалений): %(listname)s" #: bin/rmlist:108 +#, fuzzy msgid "No such list: %(listname)s. Removing its residual archives." msgstr "" "Ðемає такого ÑпиÑку: %(listname)s. Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ ÑƒÑÑ–Ñ… його оÑтаточних архівів." @@ -11864,6 +12133,7 @@ msgstr "" "Приклад: show_qfiles qfiles/shunt/*.pck\n" #: bin/sync_members:19 +#, fuzzy msgid "" "Synchronize a mailing list's membership with a flat file.\n" "\n" @@ -11995,6 +12265,7 @@ msgstr "" " Обов'Ñзковий параметр. Визначає ÑпиÑок Ñкий буде ÑинхронізуватиÑÑŒ.\n" #: bin/sync_members:115 +#, fuzzy msgid "Bad choice: %(yesno)s" msgstr "Ðеправильна відповідь: %(yesno)s" @@ -12011,6 +12282,7 @@ msgid "No argument to -f given" msgstr "Ð”Ð»Ñ Ð¿Ð°Ñ€Ð°Ð¼ÐµÑ‚Ñ€Ð° -f не вказаний аргумент" #: bin/sync_members:172 +#, fuzzy msgid "Illegal option: %(opt)s" msgstr "Ðеправильний параметр: %(opt)s" @@ -12023,6 +12295,7 @@ msgid "Must have a listname and a filename" msgstr "Ðеобхідно вказати назву ÑпиÑку розÑилки та назву файла" #: bin/sync_members:191 +#, fuzzy msgid "Cannot read address file: %(filename)s: %(msg)s" msgstr "Ðеможливо прочитати файл адреÑ: %(filename)s: %(msg)s" @@ -12039,14 +12312,17 @@ msgid "You must fix the preceding invalid addresses first." msgstr "Спочатку необхідно виправити наведені вище адреÑи." #: bin/sync_members:264 +#, fuzzy msgid "Added : %(s)s" msgstr "Доданий : %(s)s" #: bin/sync_members:289 +#, fuzzy msgid "Removed: %(s)s" msgstr "Видалений : %(s)s" #: bin/transcheck:19 +#, fuzzy msgid "" "\n" "Check a given Mailman translation, making sure that variables and\n" @@ -12154,6 +12430,7 @@ msgstr "" "повідомлень ніж qfiles/shunt.\n" #: bin/unshunt:85 +#, fuzzy msgid "" "Cannot unshunt message %(filebase)s, skipping:\n" "%(e)s" @@ -12162,6 +12439,7 @@ msgstr "" "%(e)s" #: bin/update:20 +#, fuzzy msgid "" "Perform all necessary upgrades.\n" "\n" @@ -12199,14 +12477,17 @@ msgstr "" "верÑÑ–Ñ—. Він \"обізнаний\" про верÑÑ–Ñ—, починаючи з 1.0b4 (?).\n" #: bin/update:107 +#, fuzzy msgid "Fixing language templates: %(listname)s" msgstr "КоригуютьÑÑ Ð¼Ð¾Ð²Ð½Ñ– шаблони: %(listname)s" #: bin/update:196 bin/update:711 +#, fuzzy msgid "WARNING: could not acquire lock for list: %(listname)s" msgstr "УВÐГÐ: неможливо отримати монопольний доÑтуп до ÑпиÑку: %(listname)s" #: bin/update:215 +#, fuzzy msgid "Resetting %(n)s BYBOUNCEs disabled addrs with no bounce info" msgstr "" "Розблоковано %(n)s адреÑ, блокованих помилками доÑтавки, Ñкі далі не давали " @@ -12225,6 +12506,7 @@ msgstr "" "файл буде перейменовано на %(mbox_dir)s.tmp та роботу буде відновлено." #: bin/update:255 +#, fuzzy msgid "" "\n" "%(listname)s has both public and private mbox archives. Since this list\n" @@ -12274,6 +12556,7 @@ msgid "- updating old private mbox file" msgstr "- оновлюєтьÑÑ Ñтарий приватний архів у форматі mbox" #: bin/update:295 +#, fuzzy msgid "" " unknown file in the way, moving\n" " %(o_pri_mbox_file)s\n" @@ -12290,6 +12573,7 @@ msgid "- updating old public mbox file" msgstr "- оновлюєтьÑÑ Ñтарий публічний архів в форматі mbox" #: bin/update:317 +#, fuzzy msgid "" " unknown file in the way, moving\n" " %(o_pub_mbox_file)s\n" @@ -12318,18 +12602,22 @@ msgid "- %(o_tmpl)s doesn't exist, leaving untouched" msgstr "- %(o_tmpl)s не Ñ–Ñнує; залишено недоторканим" #: bin/update:396 +#, fuzzy msgid "removing directory %(src)s and everything underneath" msgstr "видалÑєтьÑÑ ÐºÐ°Ñ‚Ð°Ð»Ð¾Ð³ %(src)s та його підкаталоги" #: bin/update:399 +#, fuzzy msgid "removing %(src)s" msgstr "видалÑєтьÑÑ %(src)s" #: bin/update:403 +#, fuzzy msgid "Warning: couldn't remove %(src)s -- %(rest)s" msgstr "Увага: неможливо видалити %(src)s -- %(rest)s" #: bin/update:408 +#, fuzzy msgid "couldn't remove old file %(pyc)s -- %(rest)s" msgstr "неможливо видалити Ñтарий %(pyc)s -- %(rest)s" @@ -12343,6 +12631,7 @@ msgid "Warning! Not a directory: %(dirpath)s" msgstr "Ðеправильний каталог черги: %(dirpath)s" #: bin/update:530 +#, fuzzy msgid "message is unparsable: %(filebase)s" msgstr "неможливо розібрати повідомленнÑ: %(filebase)s" @@ -12359,10 +12648,12 @@ msgid "Updating Mailman 2.1.4 pending.pck database" msgstr "ОновлюєтьÑÑ Ð±Ð°Ð·Ð° даних pending.pck від Mailman 2.1.4" #: bin/update:598 +#, fuzzy msgid "Ignoring bad pended data: %(key)s: %(val)s" msgstr "ІгноруютьÑÑ Ð½ÐµÐ¿Ñ€Ð°Ð²Ð¸Ð»ÑŒÐ½Ñ– незавершені дані: %(key)s: %(val)s" #: bin/update:614 +#, fuzzy msgid "WARNING: Ignoring duplicate pending ID: %(id)s." msgstr "ПОПЕРЕДЖЕÐÐЯ: ІгноруютьÑÑ Ð´ÑƒÐ±Ð»Ñ–ÐºÐ°Ñ‚ незавершеного ID: %(id)s." @@ -12387,6 +12678,7 @@ msgid "done" msgstr "виконано" #: bin/update:691 +#, fuzzy msgid "Updating mailing list: %(listname)s" msgstr "ÐžÐ½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ ÑпиÑку розÑилки: %(listname)s" @@ -12445,6 +12737,7 @@ msgid "No updates are necessary." msgstr "ÐžÐ½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð½Ðµ потрібне." #: bin/update:796 +#, fuzzy msgid "" "Downgrade detected, from version %(hexlversion)s to version %(hextversion)s\n" "This is probably not safe.\n" @@ -12455,10 +12748,12 @@ msgstr "" "ЗавершеннÑ." #: bin/update:801 +#, fuzzy msgid "Upgrading from version %(hexlversion)s to %(hextversion)s" msgstr "Триває Ð¾Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð· верÑÑ–Ñ— %(hexlversion)s на %(hextversion)s" #: bin/update:810 +#, fuzzy msgid "" "\n" "ERROR:\n" @@ -12725,6 +13020,7 @@ msgstr "" " при виникненні винÑткової Ñитуації (exception)." #: bin/withlist:175 +#, fuzzy msgid "Unlocking (but not saving) list: %(listname)s" msgstr "РозблоковуєтьÑÑ (але не оновлюєтьÑÑ) ÑпиÑок розÑилки: %(listname)s" @@ -12733,6 +13029,7 @@ msgid "Finalizing" msgstr "Ð—Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð½Ñ Ñ€Ð¾Ð±Ð¾Ñ‚Ð¸" #: bin/withlist:188 +#, fuzzy msgid "Loading list %(listname)s" msgstr "ЗавантажуєтьÑÑ Ñ–Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ Ð¿Ñ€Ð¾ ÑпиÑок %(listname)s" @@ -12745,6 +13042,7 @@ msgid "(unlocked)" msgstr "(розблоковано)" #: bin/withlist:197 +#, fuzzy msgid "Unknown list: %(listname)s" msgstr "Ðевідомий ÑпиÑок: %(listname)s" @@ -12757,18 +13055,22 @@ msgid "--all requires --run" msgstr "параметр --all потребує параметр --run" #: bin/withlist:266 +#, fuzzy msgid "Importing %(module)s..." msgstr "ІмпортуєтеÑÑ %(module)s..." #: bin/withlist:270 +#, fuzzy msgid "Running %(module)s.%(callable)s()..." msgstr "ВиконуєтьÑÑ %(module)s.%(callable)s()..." #: bin/withlist:291 +#, fuzzy msgid "The variable `m' is the %(listname)s MailList instance" msgstr "Змінна `m' Ñ” екземплÑром об'єкта, що предÑтавлÑÑ” ÑпиÑок %(listname)s" #: cron/bumpdigests:19 +#, fuzzy msgid "" "Increment the digest volume number and reset the digest number to one.\n" "\n" @@ -12797,6 +13099,7 @@ msgstr "" "вказано ÑпиÑків розÑилки, Ð´Ñ–Ñ Ð²Ð¸ÐºÐ¾Ð½ÑƒÐ²Ð°Ñ‚Ð¸Ð¼ÐµÑ‚ÑŒÑÑ Ð½Ð°Ð´ уÑіма ÑпиÑками.\n" #: cron/checkdbs:20 +#, fuzzy msgid "" "Check for pending admin requests and mail the list owners if necessary.\n" "\n" @@ -12826,10 +13129,12 @@ msgstr "" "\n" #: cron/checkdbs:121 +#, fuzzy msgid "%(count)d %(realname)s moderator request(s) waiting" msgstr "%(count)d %(realname)s відкладених до розглÑду запитів" #: cron/checkdbs:124 +#, fuzzy msgid "%(realname)s moderator request check result" msgstr "%(realname)s результат перевірки за запитом модератора" @@ -12851,6 +13156,7 @@ msgstr "" "ПовідомленнÑ, що очікують обробки:" #: cron/checkdbs:169 +#, fuzzy msgid "" "From: %(sender)s on %(date)s\n" "Subject: %(subject)s\n" @@ -12882,6 +13188,7 @@ msgid "" msgstr "" #: cron/disabled:20 +#, fuzzy msgid "" "Process disabled members, recommended once per day.\n" "\n" @@ -13006,6 +13313,7 @@ msgstr "" "\n" #: cron/mailpasswds:19 +#, fuzzy msgid "" "Send password reminders for all lists to all users.\n" "\n" @@ -13057,10 +13365,12 @@ msgid "Password // URL" msgstr "Пароль // URL" #: cron/mailpasswds:222 +#, fuzzy msgid "%(host)s mailing list memberships reminder" msgstr "ÐÐ°Ð³Ð°Ð´ÑƒÐ²Ð°Ð½Ð½Ñ ÑпиÑків розÑилки на Ñервері %(host)s" #: cron/nightly_gzip:19 +#, fuzzy msgid "" "Re-generate the Pipermail gzip'd archive flat files.\n" "\n" @@ -13159,8 +13469,8 @@ msgstr "" #~ " applied" #~ msgstr "" #~ "ТекÑÑ‚ що включаєтьÑÑ Ñƒ повідомленнÑ\n" -#~ " \n" +#~ " \n" #~ " з відмовою, що надÑилаютьÑÑ\n" #~ " учаÑникам коли їм відмовлÑють у надÑиланні повідомленнÑ." diff --git a/messages/vi/LC_MESSAGES/mailman.po b/messages/vi/LC_MESSAGES/mailman.po index 7e5ec88b..d2645422 100755 --- a/messages/vi/LC_MESSAGES/mailman.po +++ b/messages/vi/LC_MESSAGES/mailman.po @@ -68,10 +68,12 @@ msgid "

                  Currently, there are no archives.

                  " msgstr "

                  HIện thá»i không có kho.

                  " #: Mailman/Archiver/HyperArch.py:821 +#, fuzzy msgid "Gzip'd Text%(sz)s" msgstr "Văn bản đã nến Gzip%(sz)s" #: Mailman/Archiver/HyperArch.py:826 +#, fuzzy msgid "Text%(sz)s" msgstr "Văn bản%(sz)s" @@ -144,18 +146,22 @@ msgid "Third" msgstr "Thứ ba" #: Mailman/Archiver/HyperArch.py:938 +#, fuzzy msgid "%(ord)s quarter %(year)i" msgstr "Phần tư %(ord)s cá»§a %(year)i" #: Mailman/Archiver/HyperArch.py:945 +#, fuzzy msgid "%(month)s %(year)i" msgstr "%(month)s, năm %(year)i" #: Mailman/Archiver/HyperArch.py:950 +#, fuzzy msgid "The Week Of Monday %(day)i %(month)s %(year)i" msgstr "Tuần bắt đầu Thứ Hai, ngày %(day)i, %(month)s, năm %(year)i" #: Mailman/Archiver/HyperArch.py:954 +#, fuzzy msgid "%(day)i %(month)s %(year)i" msgstr "ngày %(day)i, %(month)s, năm %(year)i" @@ -164,10 +170,12 @@ msgid "Computing threaded index\n" msgstr "Äang tính toán chỉ mục có nhánh...\n" #: Mailman/Archiver/HyperArch.py:1319 +#, fuzzy msgid "Updating HTML for article %(seq)s" msgstr "Äang cập nhật mã HTML cho bài thư %(seq)s..." #: Mailman/Archiver/HyperArch.py:1326 +#, fuzzy msgid "article file %(filename)s is missing!" msgstr "• Thiếu tập tin bài thư %(filename)s. •" @@ -185,6 +193,7 @@ msgid "Pickling archive state into " msgstr "Äang pickle tính trạng kho sang " #: Mailman/Archiver/pipermail.py:453 +#, fuzzy msgid "Updating index files for archive [%(archive)s]" msgstr "Äang cập nhật các tập tin chỉ mục cho kho [%(archive)s]..." @@ -193,6 +202,7 @@ msgid " Thread" msgstr " Nhánh" #: Mailman/Archiver/pipermail.py:597 +#, fuzzy msgid "#%(counter)05d %(msgid)s" msgstr "#%(counter)05d %(msgid)s" @@ -230,6 +240,7 @@ msgid "disabled address" msgstr "bị tắt" #: Mailman/Bouncer.py:304 +#, fuzzy msgid " The last bounce received from you was dated %(date)s" msgstr " Thư đã nảy vỠđược nhận từ bạn có ngày %(date)s" @@ -257,6 +268,7 @@ msgstr "Quản trị" #: Mailman/Cgi/options.py:101 Mailman/Cgi/private.py:108 #: Mailman/Cgi/rmlist.py:75 Mailman/Cgi/roster.py:59 #: Mailman/Cgi/subscribe.py:67 +#, fuzzy msgid "No such list %(safelistname)s" msgstr "Không có há»™p thư chung %(safelistname)s như vậy." @@ -330,6 +342,7 @@ msgstr "" "\tnếu bạn không sá»­a vấn đỠnày.%(rm)r" #: Mailman/Cgi/admin.py:268 +#, fuzzy msgid "%(hostname)s mailing lists - Admin Links" msgstr "Các há»™p thư chung cá»§a %(hostname)s — Liên kết Quản trị" @@ -342,6 +355,7 @@ msgid "Mailman" msgstr "Mailman" #: Mailman/Cgi/admin.py:310 +#, fuzzy msgid "" "

                  There currently are no publicly-advertised %(mailmanlink)s\n" " mailing lists on %(hostname)s." @@ -350,6 +364,7 @@ msgstr "" "\tđã công bố trên máy %(hostname)s." #: Mailman/Cgi/admin.py:316 +#, fuzzy msgid "" "

                  Below is the collection of publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s. Click on a list\n" @@ -365,6 +380,7 @@ msgid "right " msgstr "đúng " #: Mailman/Cgi/admin.py:325 +#, fuzzy msgid "" "To visit the administrators configuration page for an\n" " unadvertised list, open a URL similar to this one, but with a '/' " @@ -409,6 +425,7 @@ msgid "No valid variable name found." msgstr "Không tìm thấy tên biến hợp lệ." #: Mailman/Cgi/admin.py:395 +#, fuzzy msgid "" "%(realname)s Mailing list Configuration Help\n" "
                  %(varname)s Option" @@ -417,6 +434,7 @@ msgstr "" "
                  Tùy chá»n %(varname)s" #: Mailman/Cgi/admin.py:402 +#, fuzzy msgid "Mailman %(varname)s List Option Help" msgstr "Trợ giúp vá» tùy chá»n danh sách %(varname)s Mailman" @@ -436,14 +454,17 @@ msgstr "" " " #: Mailman/Cgi/admin.py:431 +#, fuzzy msgid "return to the %(categoryname)s options page." msgstr "trở vá» trang các tùy chá»n %(categoryname)s." #: Mailman/Cgi/admin.py:446 +#, fuzzy msgid "%(realname)s Administration (%(label)s)" msgstr "Quản trị %(realname)s (%(label)s)" #: Mailman/Cgi/admin.py:447 +#, fuzzy msgid "%(realname)s mailing list administration
                  %(label)s Section" msgstr "Quản trị hộp thư chung %(realname)s
                  Phần %(label)s" @@ -525,6 +546,7 @@ msgid "Value" msgstr "Giá trị" #: Mailman/Cgi/admin.py:668 +#, fuzzy msgid "" "Badly formed options entry:\n" " %(record)s" @@ -625,10 +647,12 @@ msgid "Move rule down" msgstr "Äem quy tắc xuống" #: Mailman/Cgi/admin.py:870 +#, fuzzy msgid "
                  (Edit %(varname)s)" msgstr "
                  (Sá»­a %(varname)s)" #: Mailman/Cgi/admin.py:872 +#, fuzzy msgid "
                  (Details for %(varname)s)" msgstr "
                  (Chi tiết vá» %(varname)s)" @@ -669,6 +693,7 @@ msgid "(help)" msgstr "(trợ giúp)" #: Mailman/Cgi/admin.py:930 +#, fuzzy msgid "Find member %(link)s:" msgstr "Tìm thành viên %(link)s:" @@ -681,10 +706,12 @@ msgid "Bad regular expression: " msgstr "Biểu thức chính quy sai: " #: Mailman/Cgi/admin.py:1013 +#, fuzzy msgid "%(allcnt)s members total, %(membercnt)s shown" msgstr "Tổng thành viên %(allcnt)s, %(membercnt)s đã hiện" #: Mailman/Cgi/admin.py:1016 +#, fuzzy msgid "%(allcnt)s members total" msgstr "Tổng thành viên %(allcnt)s" @@ -871,6 +898,7 @@ msgstr "" "\tthích hợp được ghi bên dưới :" #: Mailman/Cgi/admin.py:1221 +#, fuzzy msgid "from %(start)s to %(end)s" msgstr "từ %(start)s đến %(end)s" @@ -1008,6 +1036,7 @@ msgid "Change list ownership passwords" msgstr "Thay đổi mật khẩu quản trị há»™p thư" #: Mailman/Cgi/admin.py:1364 +#, fuzzy msgid "" "The list administrators are the people who have ultimate control " "over\n" @@ -1120,6 +1149,7 @@ msgstr "Äịa chỉ đối nghịch (có ký tá»± cấm)" #: Mailman/Cgi/admin.py:1529 Mailman/Cgi/admin.py:1703 bin/add_members:175 #: bin/clone_member:136 bin/sync_members:268 +#, fuzzy msgid "Banned address (matched %(pattern)s)" msgstr "Äịa chỉ cấm (khá»›p mẫu %(pattern)s)" @@ -1222,6 +1252,7 @@ msgid "Not subscribed" msgstr "Chưa đăng ký" #: Mailman/Cgi/admin.py:1773 +#, fuzzy msgid "Ignoring changes to deleted member: %(user)s" msgstr "Äang bá» qua thay đổi vá» thành viên bị xoá bá»: %(user)s" @@ -1234,10 +1265,12 @@ msgid "Error Unsubscribing:" msgstr "Lá»—i bỠđăng ký:" #: Mailman/Cgi/admindb.py:235 Mailman/Cgi/admindb.py:248 +#, fuzzy msgid "%(realname)s Administrative Database" msgstr "CÆ¡ sở dữ liệu quản trị %(realname)s" #: Mailman/Cgi/admindb.py:238 +#, fuzzy msgid "%(realname)s Administrative Database Results" msgstr "Kết quả cÆ¡ sở dữ liệu quản trị %(realname)s" @@ -1266,6 +1299,7 @@ msgid "Discard all messages marked Defer" msgstr "Há»§y má»i thư có dấu HoãnThe sender is now a member of this list" msgstr "Ngưá»i gởi lúc bây giá» là thành viên cá»§a há»™p thư này" #: Mailman/Cgi/admindb.py:568 +#, fuzzy msgid "Add %(esender)s to one of these sender filters:" msgstr "Thêm %(esender)s vào má»™t cá»§a những bá»™ lá»c ngưá»i gởi này:" @@ -1387,6 +1423,7 @@ msgid "Rejects" msgstr "Bá» ra" #: Mailman/Cgi/admindb.py:584 +#, fuzzy msgid "" "Ban %(esender)s from ever subscribing to this\n" " mailing list" @@ -1401,6 +1438,7 @@ msgid "" msgstr "Nhắp vào số thứ tá»± thư để xem thư riêng, hoặc có thể " #: Mailman/Cgi/admindb.py:591 +#, fuzzy msgid "view all messages from %(esender)s" msgstr "xem má»i thư từ %(esender)s" @@ -1479,6 +1517,7 @@ msgid " is already a member" msgstr " đã thành viên" #: Mailman/Cgi/admindb.py:973 +#, fuzzy msgid "%(addr)s is banned (matched: %(patt)s)" msgstr "%(addr)s bị cấm (khá»›p: %(patt)s)" @@ -1528,6 +1567,7 @@ msgstr "" "\tYêu cầu này đã bị thôi." #: Mailman/Cgi/confirm.py:178 +#, fuzzy msgid "System error, bad content: %(content)s" msgstr "Lá»—i hệ thống, ná»™i dung sai: %(content)s" @@ -1565,6 +1605,7 @@ msgid "Confirm subscription request" msgstr "Xác nhận yêu cầu đăng ký" #: Mailman/Cgi/confirm.py:259 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " subscription request to the mailing list %(listname)s. Your\n" @@ -1597,6 +1638,7 @@ msgstr "" "đăng ký vá»›i há»™p thư này." #: Mailman/Cgi/confirm.py:275 +#, fuzzy msgid "" "Your confirmation is required in order to continue with\n" " the subscription request to the mailing list %(listname)s.\n" @@ -1648,6 +1690,7 @@ msgid "Preferred language:" msgstr "Ngôn ngữ ưa thích:" #: Mailman/Cgi/confirm.py:317 +#, fuzzy msgid "Subscribe to list %(listname)s" msgstr "Äăng ký vá»›i há»™p thư %(listname)s" @@ -1664,6 +1707,7 @@ msgid "Awaiting moderator approval" msgstr "Äang đợi Ä‘iá»u tiết viên tán thành" #: Mailman/Cgi/confirm.py:386 +#, fuzzy msgid "" " You have successfully confirmed your subscription request to " "the\n" @@ -1694,6 +1738,7 @@ msgid "You are already a member of this mailing list!" msgstr "Bạn đã có là thành viên cá»§a há»™p thư chung này." #: Mailman/Cgi/confirm.py:400 +#, fuzzy msgid "" "You are currently banned from subscribing to\n" " this list. If you think this restriction is erroneous, please\n" @@ -1718,6 +1763,7 @@ msgid "Subscription request confirmed" msgstr "Yêu cầu đăng ký đã được xác nhận." #: Mailman/Cgi/confirm.py:418 +#, fuzzy msgid "" " You have successfully confirmed your subscription request for\n" " \"%(addr)s\" to the %(listname)s mailing list. A separate\n" @@ -1742,6 +1788,7 @@ msgid "Unsubscription request confirmed" msgstr "Yêu cầu bỠđăng ký đã được xác nhận." #: Mailman/Cgi/confirm.py:469 +#, fuzzy msgid "" " You have successfully unsubscribed from the %(listname)s " "mailing\n" @@ -1763,6 +1810,7 @@ msgid "Not available" msgstr "Không có sẵn" #: Mailman/Cgi/confirm.py:498 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " unsubscription request from the mailing list %(listname)s. " @@ -1805,6 +1853,7 @@ msgid "You have canceled your change of address request." msgstr "Bạn má»›i thì yêu cầu thay đổi địa chỉ mình." #: Mailman/Cgi/confirm.py:555 +#, fuzzy msgid "" "%(newaddr)s is banned from subscribing to the\n" " %(realname)s list. If you think this restriction is erroneous,\n" @@ -1830,6 +1879,7 @@ msgid "Change of address request confirmed" msgstr "Yêu cầu thay đổi địa chỉ đã được xác nhận." #: Mailman/Cgi/confirm.py:571 +#, fuzzy msgid "" " You have successfully changed your address on the %(listname)s\n" " mailing list from %(oldaddr)s to %(newaddr)s. " @@ -1851,6 +1901,7 @@ msgid "globally" msgstr "toàn cục" #: Mailman/Cgi/confirm.py:605 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " change of address request for the mailing list %(listname)s. " @@ -1911,6 +1962,7 @@ msgid "Sender discarded message via web." msgstr "Ngưá»i gởi đã há»§y thư qua Web." #: Mailman/Cgi/confirm.py:674 +#, fuzzy msgid "" "The held message with the Subject:\n" " header %(subject)s could not be found. The most " @@ -1930,6 +1982,7 @@ msgid "Posted message canceled" msgstr "Thư đã gởi bị thôi" #: Mailman/Cgi/confirm.py:685 +#, fuzzy msgid "" " You have successfully canceled the posting of your message with\n" " the Subject: header %(subject)s to the mailing list\n" @@ -1952,6 +2005,7 @@ msgstr "" "\tmà đã được xá»­ lý bởi quản trị há»™p thư." #: Mailman/Cgi/confirm.py:735 +#, fuzzy msgid "" "Your confirmation is required in order to cancel the\n" " posting of your message to the mailing list %(listname)s:\n" @@ -1999,6 +2053,7 @@ msgid "Membership re-enabled." msgstr "Tính trạng thành viên đã được bật lại." #: Mailman/Cgi/confirm.py:798 +#, fuzzy msgid "" " You have successfully re-enabled your membership in the\n" " %(listname)s mailing list. You can now not available" msgstr "hiện không có" #: Mailman/Cgi/confirm.py:846 +#, fuzzy msgid "" "Your membership in the %(realname)s mailing list is\n" " currently disabled due to excessive bounces. Your confirmation is\n" @@ -2095,10 +2152,12 @@ msgid "administrative list overview" msgstr "toàn cảnh quản lý há»™p thư" #: Mailman/Cgi/create.py:115 +#, fuzzy msgid "List name must not include \"@\": %(safelistname)s" msgstr "Không cho phép tên há»™p thư gồm « @ »: %(safelistname)s" #: Mailman/Cgi/create.py:122 +#, fuzzy msgid "List already exists: %(safelistname)s" msgstr "Há»™p thư đã có : %(safelistname)s" @@ -2133,18 +2192,22 @@ msgid "You are not authorized to create new mailing lists" msgstr "Bạn không đủ quyá»n tạo há»™p thư chung má»›i." #: Mailman/Cgi/create.py:182 +#, fuzzy msgid "Unknown virtual host: %(safehostname)s" msgstr "Không biết máy ảo: %(safehostname)s" #: Mailman/Cgi/create.py:218 bin/newlist:219 +#, fuzzy msgid "Bad owner email address: %(s)s" msgstr "Äịa chỉ thư Ä‘iện tá»­ sai cho ngưá»i sở hữu : %(s)s" #: Mailman/Cgi/create.py:223 bin/newlist:182 bin/newlist:223 +#, fuzzy msgid "List already exists: %(listname)s" msgstr "Há»™p thư chung đã có : %(listname)s" #: Mailman/Cgi/create.py:231 bin/newlist:217 +#, fuzzy msgid "Illegal list name: %(s)s" msgstr "Không cho phép tên há»™p thư chung: %(s)s" @@ -2157,6 +2220,7 @@ msgstr "" "\tvui lòng liên lạc vá»›i quan trị địa chỉ này để được trợ giúp." #: Mailman/Cgi/create.py:273 bin/newlist:265 +#, fuzzy msgid "Your new mailing list: %(listname)s" msgstr "Há»™p thư chung má»›i cá»§a bạn: %(listname)s" @@ -2165,6 +2229,7 @@ msgid "Mailing list creation results" msgstr "Kết quả cá»§a việc tạo há»™p thư chung" #: Mailman/Cgi/create.py:288 +#, fuzzy msgid "" "You have successfully created the mailing list\n" " %(listname)s and notification has been sent to the list owner\n" @@ -2188,6 +2253,7 @@ msgid "Create another list" msgstr "Tạo há»™p thư thêm" #: Mailman/Cgi/create.py:312 +#, fuzzy msgid "Create a %(hostname)s Mailing List" msgstr "Tạo má»™t há»™p thư chung %(hostname)s" @@ -2282,6 +2348,7 @@ msgstr "" "\tcác thư được gởi bởi thành viên má»›i, theo mặc định." #: Mailman/Cgi/create.py:432 +#, fuzzy msgid "" "Initial list of supported languages.

                  Note that if you do not\n" " select at least one initial language, the list will use the server\n" @@ -2390,6 +2457,7 @@ msgid "List name is required." msgstr "Cần thiết tên há»™p thư." #: Mailman/Cgi/edithtml.py:154 +#, fuzzy msgid "%(realname)s -- Edit html for %(template_info)s" msgstr "%(realname)s — Hiệu chỉnh mã HTML cho %(template_info)s" @@ -2398,10 +2466,12 @@ msgid "Edit HTML : Error" msgstr "Sá»­a HTML: lá»—i" #: Mailman/Cgi/edithtml.py:161 +#, fuzzy msgid "%(safetemplatename)s: Invalid template" msgstr "%(safetemplatename)s: biểu mẫu không hợp lệ" #: Mailman/Cgi/edithtml.py:166 Mailman/Cgi/edithtml.py:167 +#, fuzzy msgid "%(realname)s -- HTML Page Editing" msgstr "%(realname)s — sá»­a đổi trang HTML" @@ -2465,10 +2535,12 @@ msgid "HTML successfully updated." msgstr "Mã HTML đã được cập nhật." #: Mailman/Cgi/listinfo.py:89 +#, fuzzy msgid "%(hostname)s Mailing Lists" msgstr "Các há»™p thư chung cá»§a %(hostname)s" #: Mailman/Cgi/listinfo.py:127 +#, fuzzy msgid "" "

                  There currently are no publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s." @@ -2477,6 +2549,7 @@ msgstr "" "\tđã công bố nào trên máy %(hostname)s." #: Mailman/Cgi/listinfo.py:131 +#, fuzzy msgid "" "

                  Below is a listing of all the public mailing lists on\n" " %(hostname)s. Click on a list name to get more information " @@ -2495,6 +2568,7 @@ msgid "right" msgstr "đúng" #: Mailman/Cgi/listinfo.py:140 +#, fuzzy msgid "" " To visit the general information page for an unadvertised list,\n" " open a URL similar to this one, but with a '/' and the %(adj)s\n" @@ -2557,6 +2631,7 @@ msgstr "Không cho phép địa chỉ" #: Mailman/Cgi/options.py:183 Mailman/Cgi/options.py:240 #: Mailman/Cgi/options.py:264 +#, fuzzy msgid "No such member: %(safeuser)s." msgstr "Không có thành viên như vậy: %(safeuser)s." @@ -2609,6 +2684,7 @@ msgid "Note: " msgstr "Ghi chú : " #: Mailman/Cgi/options.py:378 +#, fuzzy msgid "List subscriptions for %(safeuser)s on %(hostname)s" msgstr "Các sá»± đăng ký há»™p thư cho %(safeuser)s trên máy %(hostname)s" @@ -2639,6 +2715,7 @@ msgid "You are already using that email address" msgstr "Bạn Ä‘ang sá»­ dụng địa chỉ thư đó." #: Mailman/Cgi/options.py:459 +#, fuzzy msgid "" "The new address you requested %(newaddr)s is already a member of the\n" "%(listname)s mailing list, however you have also requested a global change " @@ -2652,6 +2729,7 @@ msgstr "" "\tchứa địa chỉ thư %(safeuser)s sẽ cÅ©ng được thay đổi." #: Mailman/Cgi/options.py:468 +#, fuzzy msgid "The new address is already a member: %(newaddr)s" msgstr "Äịa chỉ má»›i đã đăng ký trước: %(newaddr)s" @@ -2660,6 +2738,7 @@ msgid "Addresses may not be blank" msgstr "Không cho phép địa chỉ rá»—ng" #: Mailman/Cgi/options.py:488 +#, fuzzy msgid "A confirmation message has been sent to %(newaddr)s. " msgstr "Thư xác nhận đã được gởi cho %(newaddr)s. " @@ -2672,10 +2751,12 @@ msgid "Illegal email address provided" msgstr "Bạn đã nhập má»™t địa chỉ không được phép." #: Mailman/Cgi/options.py:501 +#, fuzzy msgid "%(newaddr)s is already a member of the list." msgstr "%(newaddr)s đã đăng ký trước này vá»›i há»™p thư này." #: Mailman/Cgi/options.py:504 +#, fuzzy msgid "" "%(newaddr)s is banned from this list. If you\n" " think this restriction is erroneous, please contact\n" @@ -2751,6 +2832,7 @@ msgstr "" "\tBạn sẽ nhận thông báo vá» cách quyết định." #: Mailman/Cgi/options.py:623 +#, fuzzy msgid "" "You have been successfully unsubscribed from the\n" " mailing list %(fqdn_listname)s. If you were receiving digest\n" @@ -2843,6 +2925,7 @@ msgstr "ngày" # Variable: don't translate / Biến: đừng dịch #: Mailman/Cgi/options.py:900 +#, fuzzy msgid "%(days)d %(units)s" msgstr "%(days)d %(units)s" @@ -2855,6 +2938,7 @@ msgid "No topics defined" msgstr "Chưa ghi rõ chá»§ Ä‘á»" #: Mailman/Cgi/options.py:940 +#, fuzzy msgid "" "\n" "You are subscribed to this list with the case-preserved address\n" @@ -2865,6 +2949,7 @@ msgstr "" "\tđã bảo tồn chữ hoa/thưá»ng %(cpuser)s." #: Mailman/Cgi/options.py:956 +#, fuzzy msgid "%(realname)s list: member options login page" msgstr "Há»™p thư chung %(realname)s: trang đăng nhập tùy chá»n thành viên" @@ -2873,12 +2958,14 @@ msgid "email address and " msgstr "địa chỉ thư và " #: Mailman/Cgi/options.py:960 +#, fuzzy msgid "%(realname)s list: member options for user %(safeuser)s" msgstr "" "Há»™p thư chung %(realname)s: các tùy chá»n thành viên cho ngưá»i dùng " "%(safeuser)s" #: Mailman/Cgi/options.py:986 +#, fuzzy msgid "" "In order to change your membership option, you must\n" " first log in by giving your %(extra)smembership password in the section\n" @@ -2954,6 +3041,7 @@ msgid "" msgstr "" #: Mailman/Cgi/options.py:1140 +#, fuzzy msgid "Requested topic is not valid: %(topicname)s" msgstr "Bạn đã yêu cầu má»™t chá»§ đỠkhông hợp lệ: %(topicname)s" @@ -2982,6 +3070,7 @@ msgid "Private archive - \"./\" and \"../\" not allowed in URL." msgstr "Kho riêng: không cho phép « . » hoặc « .. » trong địa chỉ URL." #: Mailman/Cgi/private.py:109 +#, fuzzy msgid "Private Archive Error - %(msg)s" msgstr "Lá»—i kho riêng: %(msg)s" @@ -3019,6 +3108,7 @@ msgid "Mailing list deletion results" msgstr "Kết quả xoá bá» há»™p thư chung" #: Mailman/Cgi/rmlist.py:187 +#, fuzzy msgid "" "You have successfully deleted the mailing list\n" " %(listname)s." @@ -3027,6 +3117,7 @@ msgstr "" " %(listname)s." #: Mailman/Cgi/rmlist.py:191 +#, fuzzy msgid "" "There were some problems deleting the mailing list\n" " %(listname)s. Contact your site administrator at " @@ -3039,6 +3130,7 @@ msgstr "" "\tđể biết chi tiết." #: Mailman/Cgi/rmlist.py:208 +#, fuzzy msgid "Permanently remove mailing list %(realname)s" msgstr "Gỡ bá» hoàn toàn há»™p thư chung %(realname)s" @@ -3108,6 +3200,7 @@ msgid "Invalid options to CGI script" msgstr "Tùy chá»n không hợp lệ đối vá»›i tập lệnh CGI" #: Mailman/Cgi/roster.py:118 +#, fuzzy msgid "%(realname)s roster authentication failed." msgstr "Việc xác thá»±c bản liệt kê %(realname)s bị lá»—i." @@ -3174,6 +3267,7 @@ msgstr "" "bạn sẽ nhận sá»›m má»™t lá thư xác nhận chứa hướng dẫn thêm." #: Mailman/Cgi/subscribe.py:262 +#, fuzzy msgid "" "The email address you supplied is banned from this\n" " mailing list. If you think this restriction is erroneous, please\n" @@ -3200,6 +3294,7 @@ msgstr "" "má»™t địa chỉ thư Ä‘iện tá»­ không bảo mật." #: Mailman/Cgi/subscribe.py:278 +#, fuzzy msgid "" "Confirmation from your email address is required, to prevent anyone from\n" "subscribing you without permission. Instructions are being sent to you at\n" @@ -3212,6 +3307,7 @@ msgstr "" "Ghi chú : bạn đã đăng ký được chỉ sau khi bạn đã xác nhận đăng ký thôi." #: Mailman/Cgi/subscribe.py:290 +#, fuzzy msgid "" "Your subscription request was deferred because %(x)s. Your request has " "been\n" @@ -3232,6 +3328,7 @@ msgid "Mailman privacy alert" msgstr "Cảnh giác riêng tư Mailman" #: Mailman/Cgi/subscribe.py:316 +#, fuzzy msgid "" "An attempt was made to subscribe your address to the mailing list\n" "%(listaddr)s. You are already subscribed to this mailing list.\n" @@ -3272,6 +3369,7 @@ msgid "This list only supports digest delivery." msgstr "Há»™p thư chung này há»— trợ chỉ khả năng phát bó thư thôi." #: Mailman/Cgi/subscribe.py:344 +#, fuzzy msgid "You have been successfully subscribed to the %(realname)s mailing list." msgstr "Bạn đã được đăng ký vá»›i há»™p thư chung %(realname)s." @@ -3321,6 +3419,7 @@ msgstr "" "Bạn đã bỠđăng ký hoặc thay đổi địa chỉ thư Ä‘iện tá»­ chưa?" #: Mailman/Commands/cmd_confirm.py:69 +#, fuzzy msgid "" "You are currently banned from subscribing to this list. If you think this\n" "restriction is erroneous, please contact the list owners at\n" @@ -3403,26 +3502,32 @@ msgid "n/a" msgstr "không có" #: Mailman/Commands/cmd_info.py:44 +#, fuzzy msgid "List name: %(listname)s" msgstr "Tên há»™p thư : %(listname)s" #: Mailman/Commands/cmd_info.py:45 +#, fuzzy msgid "Description: %(description)s" msgstr "Mô tả : %(description)s" #: Mailman/Commands/cmd_info.py:46 +#, fuzzy msgid "Postings to: %(postaddr)s" msgstr "Gởi thư cho : %(postaddr)s" #: Mailman/Commands/cmd_info.py:47 +#, fuzzy msgid "List Helpbot: %(requestaddr)s" msgstr "Trợ lý há»™p thư : %(requestaddr)s" #: Mailman/Commands/cmd_info.py:48 +#, fuzzy msgid "List Owners: %(owneraddr)s" msgstr "Quản trị há»™p thư : %(owneraddr)s" #: Mailman/Commands/cmd_info.py:49 +#, fuzzy msgid "More information: %(listurl)s" msgstr "Thông tin thêm: %(listurl)s" @@ -3447,18 +3552,22 @@ msgstr "" "\tXem danh sách các há»™p thư chung công trên máy Mailman GNU này.\n" #: Mailman/Commands/cmd_lists.py:44 +#, fuzzy msgid "Public mailing lists at %(hostname)s:" msgstr "Các há»™p thư chung công tại máy %(hostname)s:" #: Mailman/Commands/cmd_lists.py:66 +#, fuzzy msgid "%(i)3d. List name: %(realname)s" msgstr "%(i)3d. Tên há»™p thư : %(realname)s" #: Mailman/Commands/cmd_lists.py:67 +#, fuzzy msgid " Description: %(description)s" msgstr " Mô tả : %(description)s" #: Mailman/Commands/cmd_lists.py:68 +#, fuzzy msgid " Requests to: %(requestaddr)s" msgstr " Gởi yêu cầu cho : %(requestaddr)s" @@ -3494,12 +3603,14 @@ msgstr "" "\tcho địa chỉ đã đăng ký.\n" #: Mailman/Commands/cmd_password.py:51 Mailman/Commands/cmd_password.py:66 +#, fuzzy msgid "Your password is: %(password)s" msgstr "Mật khẩu cá»§a bạn là: %(password)s" #: Mailman/Commands/cmd_password.py:57 Mailman/Commands/cmd_password.py:72 #: Mailman/Commands/cmd_password.py:95 Mailman/Commands/cmd_password.py:121 #: Mailman/Commands/cmd_set.py:149 Mailman/Commands/cmd_set.py:219 +#, fuzzy msgid "You are not a member of the %(listname)s mailing list" msgstr "Bạn không phải là thành viên cá»§a há»™p thư chung %(listname)s." @@ -3685,6 +3796,7 @@ msgstr "" "\tthư nhắc nhở mật khẩu cho há»™p thư chung này.\n" #: Mailman/Commands/cmd_set.py:122 +#, fuzzy msgid "Bad set command: %(subcmd)s" msgstr "Lệnh set (đặt) sai : %(subcmd)s" @@ -3706,6 +3818,7 @@ msgstr "bật" # Literal: don't translate / NghÄ©a chữ: đừng dịch #: Mailman/Commands/cmd_set.py:154 +#, fuzzy msgid " ack %(onoff)s" msgstr " ack %(onoff)s" @@ -3743,22 +3856,27 @@ msgid "due to bounces" msgstr "vì thư nảy vá»" #: Mailman/Commands/cmd_set.py:186 +#, fuzzy msgid " %(status)s (%(how)s on %(date)s)" msgstr " %(status)s (%(how)s vào ngày %(date)s)" #: Mailman/Commands/cmd_set.py:192 +#, fuzzy msgid " myposts %(onoff)s" msgstr " thư mình %(onoff)s" #: Mailman/Commands/cmd_set.py:195 +#, fuzzy msgid " hide %(onoff)s" msgstr " ẩn %(onoff)s" #: Mailman/Commands/cmd_set.py:199 +#, fuzzy msgid " duplicates %(onoff)s" msgstr " nhân đôi %(onoff)s" #: Mailman/Commands/cmd_set.py:203 +#, fuzzy msgid " reminders %(onoff)s" msgstr " nhắc nhở %(onoff)s" @@ -3767,6 +3885,7 @@ msgid "You did not give the correct password" msgstr "Bạn chưa nhập mật khẩu đúng." #: Mailman/Commands/cmd_set.py:236 Mailman/Commands/cmd_set.py:283 +#, fuzzy msgid "Bad argument: %(arg)s" msgstr "Äối số sai : %(arg)s" @@ -3843,6 +3962,7 @@ msgstr "" "address=ngưá»i@miá»n.com\n" #: Mailman/Commands/cmd_subscribe.py:62 +#, fuzzy msgid "Bad digest specifier: %(arg)s" msgstr "Äặc tả bó thư sai : %(arg)s" @@ -3851,6 +3971,7 @@ msgid "No valid address found to subscribe" msgstr "Không tìm thấy địa chỉ hợp lệ cần đăng ký." #: Mailman/Commands/cmd_subscribe.py:113 +#, fuzzy msgid "" "The email address you supplied is banned from this mailing list.\n" "If you think this restriction is erroneous, please contact the list\n" @@ -3895,6 +4016,7 @@ msgstr "" "vá»›i khả năng bó thư thôi. •" #: Mailman/Commands/cmd_subscribe.py:146 +#, fuzzy msgid "" "Your subscription request has been forwarded to the list administrator\n" "at %(listowner)s for review." @@ -3930,6 +4052,7 @@ msgstr "" "address=ngưá»i@miá»n.com\n" #: Mailman/Commands/cmd_unsubscribe.py:62 +#, fuzzy msgid "%(address)s is not a member of the %(listname)s mailing list" msgstr "%(address)s không phải là thành viên cá»§a há»™p thư chung %(listname)s." @@ -4186,6 +4309,7 @@ msgid "Chinese (Taiwan)" msgstr "Trung-hoa (Äài-loan)" #: Mailman/Deliverer.py:53 +#, fuzzy msgid "" "Note: Since this is a list of mailing lists, administrative\n" "notices like the password reminder will be sent to\n" @@ -4200,14 +4324,17 @@ msgid " (Digest mode)" msgstr " (Chế độ bó thư)" #: Mailman/Deliverer.py:79 +#, fuzzy msgid "Welcome to the \"%(realname)s\" mailing list%(digmode)s" msgstr "Chào mừng bạn dùng %(digmode)slist « %(realname)s »." #: Mailman/Deliverer.py:89 +#, fuzzy msgid "You have been unsubscribed from the %(realname)s mailing list" msgstr "Bạn đã được bỠđăng ký ra há»™p thư chung %(realname)s." #: Mailman/Deliverer.py:116 +#, fuzzy msgid "%(listfullname)s mailing list reminder" msgstr "Lá»i nhắc nhở cho há»™p thư chung %(listfullname)s" @@ -4220,6 +4347,7 @@ msgid "Hostile subscription attempt detected" msgstr "Má»›i phát hiện sá»± cố gắng đăng ký đối nghịch." #: Mailman/Deliverer.py:169 +#, fuzzy msgid "" "%(address)s was invited to a different mailing\n" "list, but in a deliberate malicious attempt they tried to confirm the\n" @@ -4232,6 +4360,7 @@ msgstr "" "Bạn không cần làm gì nữa." #: Mailman/Deliverer.py:188 +#, fuzzy msgid "" "You invited %(address)s to your list, but in a\n" "deliberate malicious attempt, they tried to confirm the invitation to a\n" @@ -4246,6 +4375,7 @@ msgstr "" "Bạn không cần làm gì nữa." #: Mailman/Deliverer.py:221 +#, fuzzy msgid "%(listname)s mailing list probe message" msgstr "thư dò cá»§a há»™p thư chung %(listname)s" @@ -4451,8 +4581,8 @@ msgid "" " membership.\n" "\n" "

                  You can control both the\n" -" number\n" +" number\n" " of reminders the member will receive and the\n" " No those messages too will get discarded. You may " "want\n" " to set up an\n" -" autoresponse\n" +" autoresponse\n" " message for email to the -owner and -admin address." msgstr "" "Mặc dù bá»™ phát hiện thư nảy vá» cá»§a Mailman hÆ¡i mạnh,\n" @@ -4736,6 +4866,7 @@ msgstr "" "đã nảy vá». Luôn luôn cố thông báo thành viên đó." #: Mailman/Gui/Bounce.py:194 +#, fuzzy msgid "" "Bad value for %(property)s: %(val)s" @@ -4815,8 +4946,8 @@ msgstr "" "

                  Rồi, má»—i phần multipart/alternative (Ä‘a phần, xen " "kẽ)\n" "sẽ được thay thế bằng chỉ Ä‘iá»u xen kẽ thứ nhất không rá»—ng sau khi lá»c\n" -"nếu collapse_alternativesđược bật.\n" +"nếu collapse_alternativesđược bật.\n" "\n" "

                  Cuối cùng, phần text/html (văn bản/mã HTML) nào\n" "còn lại trong thư có thể được chuyển đổi sang text/plain (nhập " @@ -4862,8 +4993,8 @@ msgstr "" "\n" "

                  Má»i dòng trắng bị bá» qua.\n" "\n" -"

                  Xem thêm các kiểu qua MIME\n" +"

                  Xem thêm các kiểu qua MIME\n" "\t\tđể tìm danh sách trắng (bá»™ lá»c cho phép) kiểu ná»™i dung." #: Mailman/Gui/ContentFilter.py:94 @@ -4880,8 +5011,8 @@ msgid "" "Use this option to remove each message attachment that does\n" " not have a matching content type. Requirements and formats " "are\n" -" exactly like filter_mime_types.\n" "\n" "

                  Note: if you add entries to this list but don't add\n" @@ -4975,8 +5106,8 @@ msgstr "" "Má»™t cá»§a những hành động này được làm khi thư khá»›p má»™t cá»§a những quy tắc lá»c " "ná»™i dung, tức là kiểu ná»™i dung cấp đầu khá»›p vá»›i má»™t cá»§a những kiểu MIME lá»c,\n" -"hoặc kiểu ná»™i dung cấp đầu không khá»›p vá»›i má»™t cá»§a những kiểu MIME qua,\n" +"hoặc kiểu ná»™i dung cấp đầu không khá»›p vá»›i má»™t cá»§a những kiểu MIME qua,\n" "hoặc nếu sau khi lá»c những phần phụ cá»§a thư, thư xảy ra trống.\n" "\n" "

                  Ghi chú rằng hành động này không được làm nếu sau khi lá»c " @@ -4993,6 +5124,7 @@ msgstr "" "Tùy chá»n cuối cùng này chỉ sẵn sàng nếu được bật bởi quản trị địa chỉ đó." #: Mailman/Gui/ContentFilter.py:171 +#, fuzzy msgid "Bad MIME type ignored: %(spectype)s" msgstr "Kiểu MIME sai bị bá» qua : %(spectype)s" @@ -5095,6 +5227,7 @@ msgstr "" "nếu nó không trống không?" #: Mailman/Gui/Digest.py:145 +#, fuzzy msgid "" "The next digest will be sent as volume\n" " %(volume)s, number %(number)s" @@ -5111,14 +5244,17 @@ msgid "There was no digest to send." msgstr "Không có bó thư cần gởi." #: Mailman/Gui/GUIBase.py:173 +#, fuzzy msgid "Invalid value for variable: %(property)s" msgstr "Giá trị không hợp lệ cho biến: %(property)s" #: Mailman/Gui/GUIBase.py:178 +#, fuzzy msgid "Bad email address for option %(property)s: %(error)s" msgstr "Äịa chỉ thư Ä‘iện tá»­ sai cho tùy chá»n %(property)s: %(error)s" #: Mailman/Gui/GUIBase.py:204 +#, fuzzy msgid "" "The following illegal substitution variables were\n" " found in the %(property)s string:\n" @@ -5134,6 +5270,7 @@ msgstr "" "cho đến khi bạn sá»­a vấn đỠnày." #: Mailman/Gui/GUIBase.py:218 +#, fuzzy msgid "" "Your %(property)s string appeared to\n" " have some correctable problems in its new value.\n" @@ -5232,8 +5369,8 @@ msgid "" "

                  In order to split the list ownership duties into\n" " administrators and moderators, you must\n" " set a separate moderator password,\n" -" and also provide the email\n" +" and also provide the email\n" " addresses of the list moderators. Note that the field you\n" " are changing here specifies the list administrators." msgstr "" @@ -5548,13 +5685,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5611,8 +5748,8 @@ msgstr "Dòng đầu Reply-To: dứt khoát." msgid "" "This is the address set in the Reply-To: header\n" " when the reply_goes_to_list\n" +" href=\"?VARHELP=general/" +"reply_goes_to_list\">reply_goes_to_list\n" " option is set to Explicit address.\n" "\n" "

                  There are many reasons not to introduce or override the\n" @@ -5620,13 +5757,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5723,8 +5860,8 @@ msgid "" " member list addresses, but rather to the owner of those member\n" " lists. In that case, the value of this setting is appended to\n" " the member's account name for such notices. `-owner' is the\n" -" typical choice. This setting has no effect when \"umbrella_list" -"\"\n" +" typical choice. This setting has no effect when " +"\"umbrella_list\"\n" " is \"No\"." msgstr "" "Khi giá trị « umbrella_list » đưá»c đặt để ngụ ý là há»™p thư này\n" @@ -6412,8 +6549,8 @@ msgstr "" "cá»§a há»™p thư đó.\n" "\n" "

                  Khi khả năng cá nhân hoá được bật, một số biến mở rộng\n" -"thêm có thể được gồm trong những dòng đầu thư và\n" +"thêm có thể được gồm trong những dòng đầu thư và\n" "dòng chân thư.\n" "\n" "

                  Khi tính năng này được bật, các dòng đầu và dòng chân\n" @@ -6688,6 +6825,7 @@ msgstr "" "khi không được sá»± tán thành cá»§a há»." #: Mailman/Gui/Privacy.py:104 +#, fuzzy msgid "" "This section allows you to configure subscription and\n" " membership exposure policy. You can also control whether this\n" @@ -6881,8 +7019,8 @@ msgid "" " either individually or as a group. Any\n" " posting from a non-member who is not explicitly accepted,\n" " rejected, or discarded, will have their posting filtered by the\n" -" general\n" +" general\n" " non-member rules.\n" "\n" "

                  In the text boxes below, add one address per line; start the\n" @@ -6912,12 +7050,12 @@ msgstr "" "a>, được giữ lại " "để Ä‘iá»u tiết,\n" "bị từ chối " -"(bị nảy vá»), hoặc bị há»§y, hoặc từng lá má»™t hoặc theo nhóm.\n" +"(bị nảy vá»), hoặc bị há»§y, hoặc từng lá má»™t hoặc theo nhóm.\n" "Thư nào được gởi bởi ngưá»i không thành viên không\n" "được chấp nhận, bị từ chối hoặc bị há»§y má»™t cách dứt khoát,\n" -"sẽ được lá»c theo những quy tắc không thành viên chung.\n" +"sẽ được lá»c theo những quy tắc không thành viên chung.\n" "\n" "

                  Trong những trưá»ng bên dưới, hãy thêm\n" "má»™t địa chỉ thư trên má»—i dòng; bắt đầu dòng vá»›i dấu mÅ© ^\n" @@ -6940,6 +7078,7 @@ msgstr "" "không?" #: Mailman/Gui/Privacy.py:218 +#, fuzzy msgid "" "Each list member has a moderation flag which says\n" " whether messages from the list member can be posted directly " @@ -6989,8 +7128,8 @@ msgid "" "If a member posts this many times, within a period of time\n" " the member is automatically moderated. Use 0 to disable. " "See\n" -" member_verbosity_interval for details on the time " "period.\n" "\n" @@ -7069,8 +7208,8 @@ msgid "" " >rejection notice to\n" " be sent to moderated members who post to this list." msgstr "" -"Äoạn cần gồm trong thông báo từ chối nào được gởi cho\n" +"Äoạn cần gồm trong thông báo từ chối nào được gởi cho\n" "thành viên đã Ä‘iá»u tiết mà gởi thư cho há»™p thư này." #: Mailman/Gui/Privacy.py:290 @@ -7181,8 +7320,8 @@ msgid "" " be sent to anyone who posts to this list from a domain\n" " with a DMARC Reject%(quarantine)s Policy." msgstr "" -"Äoạn cần gồm trong thông báo từ chối nào được gởi cho\n" +"Äoạn cần gồm trong thông báo từ chối nào được gởi cho\n" "thành viên đã Ä‘iá»u tiết mà gởi thư cho há»™p thư này." #: Mailman/Gui/Privacy.py:360 @@ -7193,8 +7332,8 @@ msgid "" " >dmarc_moderation_action \n" " regardless of any domain specific DMARC Policy." msgstr "" -"Äoạn cần gồm trong thông báo từ chối nào được gởi cho\n" +"Äoạn cần gồm trong thông báo từ chối nào được gởi cho\n" "thành viên đã Ä‘iá»u tiết mà gởi thư cho há»™p thư này." #: Mailman/Gui/Privacy.py:365 @@ -7435,8 +7574,8 @@ msgstr "" "a>, được giữ lại,\n" "bị từ chối " -"(bị nảy vá»), và bị há»§y má»™t cách dứt khoát.\n" +"(bị nảy vá»), và bị há»§y má»™t cách dứt khoát.\n" "Nếu không tìm thấy Ä‘iá»u khá»›p, hành động này được làm." #: Mailman/Gui/Privacy.py:490 @@ -7448,6 +7587,7 @@ msgstr "" "nên được chuyển tiếp tá»›i Ä‘iá»u tiết há»™p thư không?" #: Mailman/Gui/Privacy.py:494 +#, fuzzy msgid "" "Text to include in any rejection notice to be sent to\n" " non-members who post to this list. This notice can include\n" @@ -7695,6 +7835,7 @@ msgstr "" "Quy tắc lá»c không hoàn tất sẽ bị bá» qua." #: Mailman/Gui/Privacy.py:693 +#, fuzzy msgid "" "The header filter rule pattern\n" " '%(safepattern)s' is not a legal regular expression. This\n" @@ -7746,8 +7887,8 @@ msgid "" "

                  The body of the message can also be optionally scanned for\n" " Subject: and Keywords: headers, as\n" " specified by the topics_bodylines_limit\n" +" href=\"?VARHELP=topics/" +"topics_bodylines_limit\">topics_bodylines_limit\n" " configuration variable." msgstr "" "Bá»™ lá»c chá»§ đỠphân loại má»—i thư má»›i đến tùy theo những\n" @@ -7765,8 +7906,9 @@ msgstr "" "\n" "

                  Thân cá»§a thư cÅ©ng có thể được quết lại tùy chá»n để tìm dòng đầu\n" "Subject: và Keywords:, như được ghi rõ trong\n" -"biến cấu hình topics_bodylines_limit (giá»›i hận các dòng thân các chá»§ Ä‘á»)." +"biến cấu hình topics_bodylines_limit (giá»›i hận các dòng thân " +"các chá»§ Ä‘á»)." #: Mailman/Gui/Topics.py:72 msgid "How many body lines should the topic matcher scan?" @@ -7833,6 +7975,7 @@ msgstr "" "Chá»§ đỠkhông hoàn tất sẽ bị bá» qua." #: Mailman/Gui/Topics.py:135 +#, fuzzy msgid "" "The topic pattern '%(safepattern)s' is not a\n" " legal regular expression. It will be discarded." @@ -8051,6 +8194,7 @@ msgid "%(listinfo_link)s list run by %(owner_link)s" msgstr "Há»™p thư chung %(listinfo_link)s này được chạy bởi %(owner_link)s" #: Mailman/HTMLFormatter.py:57 +#, fuzzy msgid "%(realname)s administrative interface" msgstr "Giao diện quản trị cá»§a há»™p thư chung %(realname)s" @@ -8059,6 +8203,7 @@ msgid " (requires authorization)" msgstr " (cần thiết xác thá»±c)" #: Mailman/HTMLFormatter.py:61 +#, fuzzy msgid "Overview of all %(hostname)s mailing lists" msgstr "Toàn cảnh cá»§a má»i há»™p thư chung trên %(hostname)s" @@ -8079,6 +8224,7 @@ msgid "; it was disabled by the list administrator" msgstr "; do quản trị há»™p thư tắt" #: Mailman/HTMLFormatter.py:146 +#, fuzzy msgid "" "; it was disabled due to excessive bounces. The\n" " last bounce was received on %(date)s" @@ -8091,6 +8237,7 @@ msgid "; it was disabled for unknown reasons" msgstr "; bị tắt, không biết sao" #: Mailman/HTMLFormatter.py:151 +#, fuzzy msgid "Note: your list delivery is currently disabled%(reason)s." msgstr "Ghi chú : khả năng phát thư cho bạn hiện thá»i bị tắt %(reason)s" @@ -8103,6 +8250,7 @@ msgid "the list administrator" msgstr "quản trị há»™p thư" #: Mailman/HTMLFormatter.py:157 +#, fuzzy msgid "" "

                  %(note)s\n" "\n" @@ -8121,6 +8269,7 @@ msgstr "" "nếu bạn gặp khó khăn nào." #: Mailman/HTMLFormatter.py:169 +#, fuzzy msgid "" "

                  We have received some recent bounces from your\n" " address. Your current bounce score is %(score)s out of " @@ -8139,6 +8288,7 @@ msgstr "" "Äiểm nảy vá» cá»§a bạn sẽ tá»± động được lập lại nếu vấn đỠnày được sá»­a sá»›m." #: Mailman/HTMLFormatter.py:181 +#, fuzzy msgid "" "(Note - you are subscribing to a list of mailing lists, so the %(type)s " "notice will be sent to the admin address for your membership, %(addr)s.)

                  " @@ -8184,6 +8334,7 @@ msgstr "" "Bạn sẽ nhận thư thông báo quyết định cá»§a Ä‘iá»u tiết viên." #: Mailman/HTMLFormatter.py:208 +#, fuzzy msgid "" "This is %(also)sa private list, which means that the\n" " list of members is not available to non-members." @@ -8193,6 +8344,7 @@ msgstr "" "ngưá»i không thành viên xem." #: Mailman/HTMLFormatter.py:211 +#, fuzzy msgid "" "This is %(also)sa hidden list, which means that the\n" " list of members is available only to the list administrator." @@ -8201,6 +8353,7 @@ msgstr "" "các thành viên sẵn sàng chỉ cho quản trị há»™p thư xem." #: Mailman/HTMLFormatter.py:214 +#, fuzzy msgid "" "This is %(also)sa public list, which means that the\n" " list of members list is available to everyone." @@ -8217,6 +8370,7 @@ msgstr "" "để ngăn cản ngưá»i gởi thư rác dá»… nhận diện)." #: Mailman/HTMLFormatter.py:222 +#, fuzzy msgid "" "

                  (Note that this is an umbrella list, intended to\n" " have only other mailing lists as members. Among other things,\n" @@ -8233,6 +8387,7 @@ msgid "either " msgstr "hoặc " #: Mailman/HTMLFormatter.py:256 +#, fuzzy msgid "" "To unsubscribe from %(realname)s, get a password reminder,\n" " or change your subscription options %(either)senter your " @@ -8266,12 +8421,14 @@ msgstr "" "vá»›i địa chỉ thư mình" #: Mailman/HTMLFormatter.py:277 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " members.)" msgstr "(%(which)s sẵn sàng chỉ cho các thành viên há»™p thư thôi.)" #: Mailman/HTMLFormatter.py:281 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " administrator.)" @@ -8331,6 +8488,7 @@ msgid "The current archive" msgstr "Kho hiện thá»i" #: Mailman/Handlers/Acknowledge.py:59 +#, fuzzy msgid "%(realname)s post acknowledgement" msgstr "Báo nhận thư cá»§a %(realname)s" @@ -8343,6 +8501,7 @@ msgid "" msgstr "" #: Mailman/Handlers/CalcRecips.py:79 +#, fuzzy msgid "" "Your urgent message to the %(realname)s mailing list was not authorized for\n" "delivery. The original message as received by Mailman is attached.\n" @@ -8423,6 +8582,7 @@ msgid "Message may contain administrivia" msgstr "Thư có thể chứa linh tinh quản lý" #: Mailman/Handlers/Hold.py:84 +#, fuzzy msgid "" "Please do *not* post administrative requests to the mailing\n" "list. If you wish to subscribe, visit %(listurl)s or send a message with " @@ -8465,12 +8625,14 @@ msgid "Posting to a moderated newsgroup" msgstr "Thư đã gởi cho má»™t nhóm tin đã Ä‘iá»u tiết" #: Mailman/Handlers/Hold.py:252 +#, fuzzy msgid "Your message to %(listname)s awaits moderator approval" msgstr "" "Thư do bạn gởi cho há»™p thư chung %(listname)s\n" "Ä‘ang đợi Ä‘iá»u tiết viên tán thành." #: Mailman/Handlers/Hold.py:271 +#, fuzzy msgid "%(listname)s post from %(sender)s requires approval" msgstr "Thư %(listname)s do %(sender)s gởi cần thiết tán thành" @@ -8513,6 +8675,7 @@ msgid "After content filtering, the message was empty" msgstr "Sau khi lá»c ná»™i dung, thư rá»—ng." #: Mailman/Handlers/MimeDel.py:269 +#, fuzzy msgid "" "The attached message matched the %(listname)s mailing list's content " "filtering\n" @@ -8555,6 +8718,7 @@ msgid "The attached message has been automatically discarded." msgstr "Thư đính kèm đã bị há»§y tá»± động." #: Mailman/Handlers/Replybot.py:75 +#, fuzzy msgid "Auto-response for your message to the \"%(realname)s\" mailing list" msgstr "Äáp ứng tá»± động cho thư do bạn gởi cho há»™p thư chung « %(realname)s »." @@ -8563,6 +8727,7 @@ msgid "The Mailman Replybot" msgstr "Trình trả lá»i Mailman" #: Mailman/Handlers/Scrubber.py:208 +#, fuzzy msgid "" "An embedded and charset-unspecified text was scrubbed...\n" "Name: %(filename)s\n" @@ -8577,6 +8742,7 @@ msgid "HTML attachment scrubbed and removed" msgstr "Tập tin HTML đính kèm bị lau và gỡ bá»" #: Mailman/Handlers/Scrubber.py:234 Mailman/Handlers/Scrubber.py:259 +#, fuzzy msgid "" "An HTML attachment was scrubbed...\n" "URL: %(url)s\n" @@ -8597,6 +8763,7 @@ msgid "unknown sender" msgstr "không biết ngưá»i gởi" #: Mailman/Handlers/Scrubber.py:276 +#, fuzzy msgid "" "An embedded message was scrubbed...\n" "From: %(who)s\n" @@ -8613,6 +8780,7 @@ msgstr "" "URL : %(url)s\n" #: Mailman/Handlers/Scrubber.py:308 +#, fuzzy msgid "" "A non-text attachment was scrubbed...\n" "Name: %(filename)s\n" @@ -8629,6 +8797,7 @@ msgstr "" "URL : %(url)s\n" #: Mailman/Handlers/Scrubber.py:347 +#, fuzzy msgid "Skipped content of type %(partctype)s\n" msgstr "Äã bá» qua ná»™i dụng kiểu %(partctype)s\n" @@ -8660,6 +8829,7 @@ msgid "Message rejected by filter rule match" msgstr "Thư bị từ chối vì khá»›p vá»›i quy tắc lá»c" #: Mailman/Handlers/ToDigest.py:173 +#, fuzzy msgid "%(realname)s Digest, Vol %(volume)d, Issue %(issue)d" msgstr "Bó thư %(realname)s, Tập %(volume)d, Bản %(issue)d" @@ -8696,6 +8866,7 @@ msgid "End of " msgstr "Kết thúc cá»§a " #: Mailman/ListAdmin.py:309 +#, fuzzy msgid "Posting of your message titled \"%(subject)s\"" msgstr "Việc gởi thư cá»§a bạn có chá»§ đỠ« %(subject)s »" @@ -8708,6 +8879,7 @@ msgid "Forward of moderated message" msgstr "Việc chuyển tiếp thư đã Ä‘iá»u tiết" #: Mailman/ListAdmin.py:405 +#, fuzzy msgid "New subscription request to list %(realname)s from %(addr)s" msgstr "Yêu cầu đăng ký má»›i vá»›i há»™p thư %(realname)s từ %(addr)s" @@ -8721,6 +8893,7 @@ msgid "via admin approval" msgstr "Tiếp tục đợi tán thành" #: Mailman/ListAdmin.py:466 +#, fuzzy msgid "New unsubscription request from %(realname)s by %(addr)s" msgstr "Yêu cầu bỠđăng ký má»›i vá»›i há»™p thư %(realname)s từ %(addr)s" @@ -8733,10 +8906,12 @@ msgid "Original Message" msgstr "Thư gốc" #: Mailman/ListAdmin.py:526 +#, fuzzy msgid "Request to mailing list %(realname)s rejected" msgstr "Yêu cầu cho há»™p thư chung %(realname)s bị từ chối" #: Mailman/MTA/Manual.py:66 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been created via the through-the-web\n" "interface. In order to complete the activation of this mailing list, the\n" @@ -8763,14 +8938,17 @@ msgstr "" "những dòng theo đây, và có lẽ cÅ©ng chạy chương trình « newaliases ».\n" #: Mailman/MTA/Manual.py:82 +#, fuzzy msgid "## %(listname)s mailing list" msgstr "## Há»™p thư chung %(listname)s" #: Mailman/MTA/Manual.py:99 +#, fuzzy msgid "Mailing list creation request for list %(listname)s" msgstr "Yêu cầu tạo há»™p thư chung cho há»™p thư %(listname)s" #: Mailman/MTA/Manual.py:113 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been removed via the through-the-web\n" "interface. In order to complete the de-activation of this mailing list, " @@ -8788,6 +8966,7 @@ msgstr "" "Äây là các mục nhập cần gỡ bá» ra trong tập tin </etc/aliases>:\n" #: Mailman/MTA/Manual.py:123 +#, fuzzy msgid "" "\n" "To finish removing your mailing list, you must edit your /etc/aliases (or\n" @@ -8804,14 +8983,17 @@ msgstr "" "## Há»™p thư chung %(listname)s" #: Mailman/MTA/Manual.py:142 +#, fuzzy msgid "Mailing list removal request for list %(listname)s" msgstr "Yêu cầu gỡ bá» há»™p thư chung cho há»™p thư %(listname)s" #: Mailman/MTA/Postfix.py:442 +#, fuzzy msgid "checking permissions on %(file)s" msgstr "Ä‘ang kiểm tra quyá»n truy cập tập tin %(file)s" #: Mailman/MTA/Postfix.py:452 +#, fuzzy msgid "%(file)s permissions must be 0664 (got %(octmode)s)" msgstr "quyá»n truy cập tập tin %(file)s phải là « 0664 » (còn gặp %(octmode)s)" @@ -8825,37 +9007,45 @@ msgid "(fixing)" msgstr "(Ä‘ang sá»­a)" #: Mailman/MTA/Postfix.py:470 +#, fuzzy msgid "checking ownership of %(dbfile)s" msgstr "Ä‘ang kiểm tra quyá»n sở hữu tập tin %(dbfile)s" #: Mailman/MTA/Postfix.py:478 +#, fuzzy msgid "%(dbfile)s owned by %(owner)s (must be owned by %(user)s" msgstr "Tập tin %(dbfile)s do %(owner)s sở hữu (phải do %(user)s sở hữu)" #: Mailman/MTA/Postfix.py:491 +#, fuzzy msgid "%(dbfile)s permissions must be 0664 (got %(octmode)s)" msgstr "" "quyá»n truy cập tập tin %(dbfile)s phải là « 0664 » (còn gặp %(octmode)s)" #: Mailman/MailList.py:219 +#, fuzzy msgid "Your confirmation is required to join the %(listname)s mailing list" msgstr "Cần thiết bạn xác nhận để tham gia há»™p thư chung %(listname)s" #: Mailman/MailList.py:230 +#, fuzzy msgid "Your confirmation is required to leave the %(listname)s mailing list" msgstr "Cần thiết bạn xác nhận để rá»i Ä‘i ra há»™p thư chung %(listname)s" #: Mailman/MailList.py:999 Mailman/MailList.py:1492 +#, fuzzy msgid " from %(remote)s" msgstr " từ %(remote)s" #: Mailman/MailList.py:1043 +#, fuzzy msgid "subscriptions to %(realname)s require moderator approval" msgstr "" "các việc đăng ký vá»›i há»™p thư chung %(realname)s cần thiết Ä‘iá»u tiết viên tán " "thành" #: Mailman/MailList.py:1125 bin/add_members:299 +#, fuzzy msgid "%(realname)s subscription notification" msgstr "thông báo đăng ký vá»›i há»™p thư chung %(realname)s" @@ -8864,6 +9054,7 @@ msgid "unsubscriptions require moderator approval" msgstr "các việc bỠđăng ký cần thiết Ä‘iá»u tiết viên tán thành" #: Mailman/MailList.py:1166 +#, fuzzy msgid "%(realname)s unsubscribe notification" msgstr "thông báo bỠđăng ký vá»›i há»™p thư chung %(realname)s" @@ -8883,6 +9074,7 @@ msgid "via web confirmation" msgstr "Chuá»—i xác nhận sai" #: Mailman/MailList.py:1396 +#, fuzzy msgid "subscriptions to %(name)s require administrator approval" msgstr "" "các việc đăng ký vá»›i há»™p thư chung %(name)s cần thiết Ä‘iá»u tiết viên tán " @@ -8903,6 +9095,7 @@ msgid "Last autoresponse notification for today" msgstr "Thông báo đáp ứng tá»± động cuối cùng cá»§a hôm nay" #: Mailman/Queue/BounceRunner.py:360 +#, fuzzy msgid "" "The attached message was received as a bounce, but either the bounce format\n" "was not recognized, or no member addresses could be extracted from it. " @@ -8990,6 +9183,7 @@ msgid "Original message suppressed by Mailman site configuration\n" msgstr "" #: Mailman/htmlformat.py:675 +#, fuzzy msgid "Delivered by Mailman
                  version %(version)s" msgstr "Phát do trình Mailman
                  phiên bản %(version)s" @@ -9078,6 +9272,7 @@ msgid "Server Local Time" msgstr "GiỠđịa phương cá»§a trình phục vụ" #: Mailman/i18n.py:180 +#, fuzzy msgid "" "%(wday)s %(mon)s %(day)2i %(hh)02i:%(mm)02i:%(ss)02i %(tzname)s %(year)04i" msgstr "" @@ -9191,6 +9386,7 @@ msgstr "" "Chỉ má»™t cá»§a những tập tin này có thể là « - ».\n" #: bin/add_members:162 bin/add_members:172 +#, fuzzy msgid "Already a member: %(member)s" msgstr "Äã thành viên: %(member)s" @@ -9199,10 +9395,12 @@ msgid "Bad/Invalid email address: blank line" msgstr "Äịa chỉ thư Ä‘iện tá»­ không hợp lệ hoặc sai : dòng rá»—ng" #: bin/add_members:180 +#, fuzzy msgid "Bad/Invalid email address: %(member)s" msgstr "Äịa chỉ thư Ä‘iện tá»­ không hợp lệ hoặc sai : %(member)s" #: bin/add_members:182 +#, fuzzy msgid "Hostile address (illegal characters): %(member)s" msgstr "Äịa chỉ thư đối nghịch (ký tá»± bị cấm): %(member)s" @@ -9212,14 +9410,17 @@ msgid "Invited: %(member)s" msgstr "Äã đăng ký : %(member)s" #: bin/add_members:187 +#, fuzzy msgid "Subscribed: %(member)s" msgstr "Äã đăng ký : %(member)s" #: bin/add_members:237 +#, fuzzy msgid "Bad argument to -w/--welcome-msg: %(arg)s" msgstr "Äối sô sai tá»›i « -w » / « --welcome-msg »: %(arg)s" #: bin/add_members:244 +#, fuzzy msgid "Bad argument to -a/--admin-notify: %(arg)s" msgstr "Äối sô sai tá»›i « -a » / « --admin-notify »: %(arg)s" @@ -9235,6 +9436,7 @@ msgstr "" #: bin/add_members:261 bin/config_list:110 bin/export.py:271 bin/find_member:97 #: bin/inject:91 bin/list_admins:90 bin/list_members:252 bin/sync_members:222 #: cron/bumpdigests:86 +#, fuzzy msgid "No such list: %(listname)s" msgstr "Không có há»™p thư chung như vậy: %(listname)s" @@ -9245,6 +9447,7 @@ msgid "Nothing to do." msgstr "Không có gì cần làm." #: bin/arch:19 +#, fuzzy msgid "" "Rebuild a list's archive.\n" "\n" @@ -9336,6 +9539,7 @@ msgid "listname is required" msgstr "cần thiết tên há»™p thư" #: bin/arch:143 bin/change_pw:107 bin/config_list:257 +#, fuzzy msgid "" "No such list \"%(listname)s\"\n" "%(e)s" @@ -9344,10 +9548,12 @@ msgstr "" "%(e)s" #: bin/arch:168 +#, fuzzy msgid "Cannot open mbox file %(mbox)s: %(msg)s" msgstr "Không thể mở tập tin dạng mbox %(mbox)s: %(msg)s" #: bin/b4b5-archfix:19 +#, fuzzy msgid "" "Fix the MM2.1b4 archives.\n" "\n" @@ -9493,6 +9699,7 @@ msgstr "" " Hiển thị trợ giúp này rồi thoát.\n" #: bin/change_pw:145 +#, fuzzy msgid "Bad arguments: %(strargs)s" msgstr "Äối số sai : %(strargs)s" @@ -9501,14 +9708,17 @@ msgid "Empty list passwords are not allowed" msgstr "Không cho phép mật khẩu rá»—ng cho há»™p thư chung" #: bin/change_pw:181 +#, fuzzy msgid "New %(listname)s password: %(notifypassword)s" msgstr "Mật khẩu %(listname)s má»›i : %(notifypassword)s" #: bin/change_pw:190 +#, fuzzy msgid "Your new %(listname)s list password" msgstr "Mật khẩu %(listname)s má»›i cá»§a bạn" #: bin/change_pw:191 +#, fuzzy msgid "" "The site administrator at %(hostname)s has changed the password for your\n" "mailing list %(listname)s. It is now\n" @@ -9536,6 +9746,7 @@ msgstr "" " %(adminurl)s\n" #: bin/check_db:19 +#, fuzzy msgid "" "Check a list's config database file for integrity.\n" "\n" @@ -9609,10 +9820,12 @@ msgid "List:" msgstr "Há»™p thư :" #: bin/check_db:148 +#, fuzzy msgid " %(file)s: okay" msgstr " %(file)s: không có sao" #: bin/check_perms:20 +#, fuzzy msgid "" "Check the permissions for the Mailman installation.\n" "\n" @@ -9633,43 +9846,53 @@ msgstr "" "\n" #: bin/check_perms:110 +#, fuzzy msgid " checking gid and mode for %(path)s" msgstr " Ä‘ang kiểm tra số nhận diện nhóm (GID) và chế độ cho %(path)s" #: bin/check_perms:122 +#, fuzzy msgid "%(path)s bad group (has: %(groupname)s, expected %(MAILMAN_GROUP)s)" msgstr "%(path)s nhóm sai (có : %(groupname)s, còn ngá» %(MAILMAN_GROUP)s)" #: bin/check_perms:151 +#, fuzzy msgid "directory permissions must be %(octperms)s: %(path)s" msgstr "quyá»n hạn thư mục phải là « %(octperms)s: %(path)s »" #: bin/check_perms:160 +#, fuzzy msgid "source perms must be %(octperms)s: %(path)s" msgstr "quyá»n hạn nguồn phải là « %(octperms)s: %(path)s »" #: bin/check_perms:171 +#, fuzzy msgid "article db files must be %(octperms)s: %(path)s" msgstr "" "các tập tin cá»§a cÆ¡ sở dữ liệu bài thư phải là « %(octperms)s: %(path)s »" #: bin/check_perms:183 +#, fuzzy msgid "checking mode for %(prefix)s" msgstr "Ä‘ang kiểm tra chế độ tìm %(prefix)s" #: bin/check_perms:193 +#, fuzzy msgid "WARNING: directory does not exist: %(d)s" msgstr "CẢNH BÃO : không có thư mục : %(d)s" #: bin/check_perms:197 +#, fuzzy msgid "directory must be at least 02775: %(d)s" msgstr "thư mục phải là ít nhất 02775: %(d)s" #: bin/check_perms:209 +#, fuzzy msgid "checking perms on %(private)s" msgstr "Ä‘ang kiểm tra quyá»n hạn vá» %(private)s" #: bin/check_perms:214 +#, fuzzy msgid "%(private)s must not be other-readable" msgstr "%(private)s phải có quyá»n hạn không cho phép ngưá»i khác Ä‘á»c" @@ -9681,8 +9904,8 @@ msgid "" " If you're on a shared multiuser system, you should consult the\n" " installation manual on how to fix this." msgstr "" -"Cảnh báo : thư mục kho riêng có quyá»n hạn cho phép ngưá»i khác thá»±c hiện (o" -"+x).\n" +"Cảnh báo : thư mục kho riêng có quyá»n hạn cho phép ngưá»i khác thá»±c hiện " +"(o+x).\n" "Tình trạng này có thể cho phép ngưá»i khác trên hệ thống Ä‘á»c kho riêng.\n" "Nếu bạn có hệ thống Ä‘a ngưá»i dùng đã chia sẻ, bạn nên tham khảo\n" "tập tin hướng dẫn cài đặt vá» cách sá»­a vấn đỠnày." @@ -9692,6 +9915,7 @@ msgid "mbox file must be at least 0660:" msgstr "tập tin há»™p thư mbox phải là ít nhất 0660 :" #: bin/check_perms:263 +#, fuzzy msgid "%(dbdir)s \"other\" perms must be 000" msgstr "" "quyá»n hạn « ngưá»i khác » cá»§a thư mục cÆ¡ sở dữ liệu %(dbdir)s phải là 000" @@ -9701,26 +9925,32 @@ msgid "checking cgi-bin permissions" msgstr "Ä‘ang kiểm tra quyá»n hạn cgi-bin" #: bin/check_perms:278 +#, fuzzy msgid " checking set-gid for %(path)s" msgstr " Ä‘ang kiểm tra set-gid (đặt số nhận diện nhóm) có %(path)s chưa" #: bin/check_perms:282 +#, fuzzy msgid "%(path)s must be set-gid" msgstr "%(path)s phải là set-gid" #: bin/check_perms:292 +#, fuzzy msgid "checking set-gid for %(wrapper)s" msgstr "Ä‘ang kiểm tra set-gid (đặt số nhận diện nhóm) có lá»›p bá»c %(wrapper)s" #: bin/check_perms:296 +#, fuzzy msgid "%(wrapper)s must be set-gid" msgstr "lá»›p bá»c %(wrapper)s phải là set-gid" #: bin/check_perms:306 +#, fuzzy msgid "checking permissions on %(pwfile)s" msgstr "Ä‘ang kiểm tra quyá»n hạn vá» tập tin mật khẩu %(pwfile)s" #: bin/check_perms:315 +#, fuzzy msgid "%(pwfile)s permissions must be exactly 0640 (got %(octmode)s)" msgstr "" "quyá»n hạn vá» tập tin mật khẩu %(pwfile)s phải là 0640 chính xác (còn gặp " @@ -9731,10 +9961,12 @@ msgid "checking permissions on list data" msgstr "Ä‘ang kiểm tra quyá»n hạn vá» dữ liệu há»™p thư chung" #: bin/check_perms:348 +#, fuzzy msgid " checking permissions on: %(path)s" msgstr " Ä‘ang kiểm tra quyá»n hạn vá» %(path)s" #: bin/check_perms:356 +#, fuzzy msgid "file permissions must be at least 660: %(path)s" msgstr "quyá»n truy cập tập tin phải là ít nhất 660 : %(path)s" @@ -9820,6 +10052,7 @@ msgid "Unix-From line changed: %(lineno)d" msgstr "Dòng « Unix-From » đã thay đổi : %(lineno)d" #: bin/cleanarch:111 +#, fuzzy msgid "Bad status number: %(arg)s" msgstr "Số trạng thái sau : %(arg)s" @@ -9965,10 +10198,12 @@ msgid " original address removed:" msgstr " địa chỉ gốc bị gỡ bá» :" #: bin/clone_member:202 +#, fuzzy msgid "Not a valid email address: %(toaddr)s" msgstr "Không phải là địa chỉ thư Ä‘iện tá»­ hợp lệ : %(toaddr)s" #: bin/clone_member:215 +#, fuzzy msgid "" "Error opening list \"%(listname)s\", skipping.\n" "%(e)s" @@ -10076,6 +10311,7 @@ msgstr "" "\n" #: bin/config_list:118 +#, fuzzy msgid "" "# -*- python -*-\n" "# -*- coding: %(charset)s -*-\n" @@ -10096,22 +10332,27 @@ msgid "legal values are:" msgstr "giá trị có thể :" #: bin/config_list:270 +#, fuzzy msgid "attribute \"%(k)s\" ignored" msgstr "thuá»™c tính « %(k)s » bị bá» qua" #: bin/config_list:273 +#, fuzzy msgid "attribute \"%(k)s\" changed" msgstr "thuá»™c tính « %(k)s » đã thay đổi" #: bin/config_list:279 +#, fuzzy msgid "Non-standard property restored: %(k)s" msgstr "Tài sản không chuẩn đã được phục hồi : %(k)s" #: bin/config_list:288 +#, fuzzy msgid "Invalid value for property: %(k)s" msgstr "Giá trị không hợp lệ cho tài sản : %(k)s" #: bin/config_list:291 +#, fuzzy msgid "Bad email address for option %(k)s: %(v)s" msgstr "Äịa chỉ thư Ä‘iện tá»­ sai cho tùy chá»n %(k)s : %(v)s" @@ -10176,18 +10417,22 @@ msgstr "" " Không in ra thông Ä‘iệp trạng thái (_im_).\n" #: bin/discard:94 +#, fuzzy msgid "Ignoring non-held message: %(f)s" msgstr "Äang bá» qua thư không được giữ lại : %(f)s" #: bin/discard:100 +#, fuzzy msgid "Ignoring held msg w/bad id: %(f)s" msgstr "Äang bá» qua thư không được giữ lại có ID sai : %(f)s" #: bin/discard:112 +#, fuzzy msgid "Discarded held msg #%(id)s for list %(listname)s" msgstr "Má»›i há»§y thư được giữ lại số %(id)s đối vá»›i há»™p thư chung %(listname)s" #: bin/dumpdb:19 +#, fuzzy msgid "" "Dump the contents of any Mailman `database' file.\n" "\n" @@ -10258,6 +10503,7 @@ msgid "No filename given." msgstr "Chưa nhập tên tập tin." #: bin/dumpdb:108 +#, fuzzy msgid "Bad arguments: %(pargs)s" msgstr "Äối số sai : %(pargs)s" @@ -10266,14 +10512,17 @@ msgid "Please specify either -p or -m." msgstr "Hãy ghi rõ tùy chá»n hoặc « --p » hoặc « --m »." #: bin/dumpdb:133 +#, fuzzy msgid "[----- start %(typename)s file -----]" msgstr "[----- bắt đầu tập tin %(typename)s -----]" #: bin/dumpdb:139 +#, fuzzy msgid "[----- end %(typename)s file -----]" msgstr "[----- kết thúc tập tin %(typename)s -----]" #: bin/dumpdb:142 +#, fuzzy msgid "<----- start object %(cnt)s ----->" msgstr "<----- bắt đầu đối tượng %(cnt)s ----->" @@ -10498,6 +10747,7 @@ msgstr "" "Äang đặt web_page_url (địa chỉ URL cá»§a trang Web) thành : %(web_page_url)s" #: bin/fix_url.py:88 +#, fuzzy msgid "Setting host_name to: %(mailhost)s" msgstr "Äang đặt « host_name » (tên máy) thành : %(mailhost)s" @@ -10590,6 +10840,7 @@ msgstr "" "lệnh này sá»­ dụng thiết bị nhập chuẩn.\n" #: bin/inject:84 +#, fuzzy msgid "Bad queue directory: %(qdir)s" msgstr "Thư mục hàng đợi sai : %(qdir)s" @@ -10598,6 +10849,7 @@ msgid "A list name is required" msgstr "Cần thiết tên há»™p thư chung" #: bin/list_admins:20 +#, fuzzy msgid "" "List all the owners of a mailing list.\n" "\n" @@ -10644,6 +10896,7 @@ msgstr "" "Bạn có thể nhập nhiá»u há»™p thư chung vào lệnh này trên dòng lệnh.\n" #: bin/list_admins:97 +#, fuzzy msgid "List: %(listname)s, \tOwners: %(owners)s" msgstr "Há»™p thư chung : %(listname)s, \tNgưá»i sở hữu : %(owners)s" @@ -10828,10 +11081,12 @@ msgstr "" "nhưng mà trạng thái địa chỉ không được hiển thị.\n" #: bin/list_members:198 +#, fuzzy msgid "Bad --nomail option: %(why)s" msgstr "Tùy chá»n « --nomail » (không nhận thư) sai : %(why)s" #: bin/list_members:209 +#, fuzzy msgid "Bad --digest option: %(kind)s" msgstr "Tùy chá»n « --digest » (bó thư) sai : %(kind)s" @@ -10845,6 +11100,7 @@ msgid "Could not open file for writing:" msgstr "Không thể mở tập tin để ghi :" #: bin/list_owners:20 +#, fuzzy msgid "" "List the owners of a mailing list, or all mailing lists.\n" "\n" @@ -10900,6 +11156,7 @@ msgid "" msgstr "" #: bin/mailmanctl:20 +#, fuzzy msgid "" "Primary start-up and shutdown script for Mailman's qrunner daemon.\n" "\n" @@ -11079,6 +11336,7 @@ msgstr "" "thông Ä‘iệp nào đươc ghi vào nó.\n" #: bin/mailmanctl:152 +#, fuzzy msgid "PID unreadable in: %(pidfile)s" msgstr "Không thể Ä‘á»c PID trong : %(pidfile)s" @@ -11087,6 +11345,7 @@ msgid "Is qrunner even running?" msgstr "Trình qrunner có chạy chưa?" #: bin/mailmanctl:160 +#, fuzzy msgid "No child with pid: %(pid)s" msgstr "Không có tiến trình con có PID : %(pid)s" @@ -11112,6 +11371,7 @@ msgstr "" "Hãy cố chạy lại mailmanctl vá»›i cỠ« -s ».\n" #: bin/mailmanctl:233 +#, fuzzy msgid "" "The master qrunner lock could not be acquired, because it appears as if " "some\n" @@ -11136,10 +11396,12 @@ msgstr "" "Äang thoát..." #: bin/mailmanctl:280 cron/mailpasswds:119 +#, fuzzy msgid "Site list is missing: %(sitelistname)s" msgstr "Thiếu danh sách địa chỉ : %(sitelistname)s" #: bin/mailmanctl:305 +#, fuzzy msgid "Run this program as root or as the %(name)s user, or use -u." msgstr "" "Hãy chạy chương trình này vá»›i tư cách ngưá»i chá»§ (root),\n" @@ -11150,6 +11412,7 @@ msgid "No command given." msgstr "Chưa nhập lệnh." #: bin/mailmanctl:339 +#, fuzzy msgid "Bad command: %(command)s" msgstr "Lệnh sai : %(command)s" @@ -11174,6 +11437,7 @@ msgid "Starting Mailman's master qrunner." msgstr "Äang khởi chạy qrunner cái cá»§a Mailman..." #: bin/mmsitepass:19 +#, fuzzy msgid "" "Set the site password, prompting from the terminal.\n" "\n" @@ -11226,6 +11490,7 @@ msgid "list creator" msgstr "ngưá»i tạo há»™p thư" #: bin/mmsitepass:86 +#, fuzzy msgid "New %(pwdesc)s password: " msgstr "Mật khẩu %(pwdesc)s má»›i : " @@ -11490,6 +11755,7 @@ msgstr "" "Ghi chú rằng các tên há»™p thư chung được ép buá»™c là chữ thưá»ng.\n" #: bin/newlist:162 +#, fuzzy msgid "Unknown language: %(lang)s" msgstr "Không biết ngôn ngữ : %(lang)s" @@ -11502,6 +11768,7 @@ msgid "Enter the email of the person running the list: " msgstr "Hãy gõ địa chỉ thư Ä‘iện tá»­ cá»§a ngưá»i chạy há»™p thư chung này : " #: bin/newlist:193 +#, fuzzy msgid "Initial %(listname)s password: " msgstr "Mật khẩu ban đầu cá»§a há»™p thư chung %(listname)s : " @@ -11511,15 +11778,17 @@ msgstr "Mật khẩu há»™p thư không thể là rá»—ng." #: bin/newlist:220 msgid "" -" - owner addresses need to be fully-qualified names like \"owner@example.com" -"\", not just \"owner\"." +" - owner addresses need to be fully-qualified names like \"owner@example." +"com\", not just \"owner\"." msgstr "" #: bin/newlist:244 +#, fuzzy msgid "Hit enter to notify %(listname)s owner..." msgstr "Bấm phím Enter để thông báo ngưá»i sở hữu há»™p thư %(listname)s..." #: bin/qrunner:20 +#, fuzzy msgid "" "Run one or more qrunners, once or repeatedly.\n" "\n" @@ -11647,6 +11916,7 @@ msgstr "" "thao tác chuẩn. Nó có ích khi gỡ lá»—i, chỉ khi được chạy riêng.\n" #: bin/qrunner:179 +#, fuzzy msgid "%(name)s runs the %(runnername)s qrunner" msgstr "%(name)s có chạy qrunner %(runnername)s" @@ -11659,6 +11929,7 @@ msgid "No runner name given." msgstr "Chưa nhập tên runner." #: bin/rb-archfix:21 +#, fuzzy msgid "" "Reduce disk space usage for Pipermail archives.\n" "\n" @@ -11800,18 +12071,22 @@ msgstr "" "\n" #: bin/remove_members:156 +#, fuzzy msgid "Could not open file for reading: %(filename)s." msgstr "Không thể mở tập tin để Ä‘á»c : %(filename)s" #: bin/remove_members:163 +#, fuzzy msgid "Error opening list %(listname)s... skipping." msgstr "Gặp lá»—i khi mở há»™p thư chung %(listname)s ... nên bá» qua." #: bin/remove_members:173 +#, fuzzy msgid "No such member: %(addr)s" msgstr "Không có thành viên như vậy: %(addr)s" #: bin/remove_members:178 +#, fuzzy msgid "User `%(addr)s' removed from list: %(listname)s." msgstr "Ngưá»i dùng « %(addr)s » đã bị gỡ bá» ra há»™p thư chung : %(listname)s." @@ -11852,10 +12127,12 @@ msgstr "" "\tIn ra hoạt động cá»§a tập lệnh (_chi tiết_).\n" #: bin/reset_pw.py:77 +#, fuzzy msgid "Changing passwords for list: %(listname)s" msgstr "Äang thay đổi mật khẩu cho há»™p thư chung : %(listname)s" #: bin/reset_pw.py:83 +#, fuzzy msgid "New password for member %(member)40s: %(randompw)s" msgstr "Mật khẩu má»›i cho thành viên %(member)40s: %(randompw)s" @@ -11901,18 +12178,22 @@ msgstr "" "\n" #: bin/rmlist:73 bin/rmlist:76 +#, fuzzy msgid "Removing %(msg)s" msgstr "Äang gỡ bá» %(msg)s..." #: bin/rmlist:81 +#, fuzzy msgid "%(listname)s %(msg)s not found as %(filename)s" msgstr "Không tìm thấy %(listname)s %(msg)s dạng %(filename)s" #: bin/rmlist:105 +#, fuzzy msgid "No such list (or list already deleted): %(listname)s" msgstr "Không có há»™p thư như vậy (hoặc há»™p thư đã bị xoá bá») : %(listname)s" #: bin/rmlist:108 +#, fuzzy msgid "No such list: %(listname)s. Removing its residual archives." msgstr "" "Không có há»™p thư như vậy : %(listname)s. Äang gỡ bá» các kho còn lại cá»§a nó." @@ -11974,6 +12255,7 @@ msgstr "" "Ví dụ : show_qfiles qfiles/shunt/*.pck\n" #: bin/sync_members:19 +#, fuzzy msgid "" "Synchronize a mailing list's membership with a flat file.\n" "\n" @@ -12108,6 +12390,7 @@ msgstr "" "\tGiá trị bắt buá»™c phải nhập. Nó ghi rõ há»™p thư chung cần đồng bá»™ hoá.\n" #: bin/sync_members:115 +#, fuzzy msgid "Bad choice: %(yesno)s" msgstr "Sá»± chá»n sai : %(yesno)s" @@ -12124,6 +12407,7 @@ msgid "No argument to -f given" msgstr "Chưa nhập đối số tá»›i « -f »" #: bin/sync_members:172 +#, fuzzy msgid "Illegal option: %(opt)s" msgstr "Không cho phép tùy chá»n : %(opt)s" @@ -12136,6 +12420,7 @@ msgid "Must have a listname and a filename" msgstr "Phải nhập cả tên há»™p thư chung lẫn tên tập tin" #: bin/sync_members:191 +#, fuzzy msgid "Cannot read address file: %(filename)s: %(msg)s" msgstr "Không thể Ä‘á»c tập tin địa chỉ : %(filename)s: %(msg)s" @@ -12152,14 +12437,17 @@ msgid "You must fix the preceding invalid addresses first." msgstr "Äầu tiên bạn phải sá»­a những địa chỉ không hợp lệ Ä‘i trước." #: bin/sync_members:264 +#, fuzzy msgid "Added : %(s)s" msgstr "Äã thêm : %(s)s" #: bin/sync_members:289 +#, fuzzy msgid "Removed: %(s)s" msgstr "Äã gỡ bá» : %(s)s" #: bin/transcheck:19 +#, fuzzy msgid "" "\n" "Check a given Mailman translation, making sure that variables and\n" @@ -12239,6 +12527,7 @@ msgid "scan the po file comparing msgids with msgstrs" msgstr "quét tập tin .po, so sánh chuá»—i msgid và msgstr" #: bin/unshunt:20 +#, fuzzy msgid "" "Move a message from the shunt queue to the original queue.\n" "\n" @@ -12272,6 +12561,7 @@ msgstr "" "bị mất hoàn toàn.\n" #: bin/unshunt:85 +#, fuzzy msgid "" "Cannot unshunt message %(filebase)s, skipping:\n" "%(e)s" @@ -12281,6 +12571,7 @@ msgstr "" "%(e)s" #: bin/update:20 +#, fuzzy msgid "" "Perform all necessary upgrades.\n" "\n" @@ -12318,14 +12609,17 @@ msgstr "" "từ phiên bản trước nào. Nó có thể quản lý phiên bản kể từ 1.0b4.\n" #: bin/update:107 +#, fuzzy msgid "Fixing language templates: %(listname)s" msgstr "Äang sá»­a các biểu mẫu ngôn ngữ : %(listname)s" #: bin/update:196 bin/update:711 +#, fuzzy msgid "WARNING: could not acquire lock for list: %(listname)s" msgstr "CẢNH BÃO : không thể lấy khoá cho há»™p thư : %(listname)s" #: bin/update:215 +#, fuzzy msgid "Resetting %(n)s BYBOUNCEs disabled addrs with no bounce info" msgstr "" "Äang đặt lại địa chỉ BYBOUNCE (do thư nảy vá») bị tắt %(n)s, không có thông " @@ -12345,6 +12639,7 @@ msgstr "" "thành « %(mbox_dir)s.tmp » rồi tiếp tục lại." #: bin/update:255 +#, fuzzy msgid "" "\n" "%(listname)s has both public and private mbox archives. Since this list\n" @@ -12391,6 +12686,7 @@ msgid "- updating old private mbox file" msgstr "- Ä‘ang cập nhật tập tin mbox riêng cÅ©" #: bin/update:295 +#, fuzzy msgid "" " unknown file in the way, moving\n" " %(o_pri_mbox_file)s\n" @@ -12407,6 +12703,7 @@ msgid "- updating old public mbox file" msgstr "- Ä‘ang cập nhật tập tin mbox công cÅ©" #: bin/update:317 +#, fuzzy msgid "" " unknown file in the way, moving\n" " %(o_pub_mbox_file)s\n" @@ -12435,18 +12732,22 @@ msgid "- %(o_tmpl)s doesn't exist, leaving untouched" msgstr "- %(o_tmpl)s không tồn tại, không thay đổi gì" #: bin/update:396 +#, fuzzy msgid "removing directory %(src)s and everything underneath" msgstr "Ä‘ang gỡ bá» thư mục %(src)s và toàn bá»™ ná»™i dung" #: bin/update:399 +#, fuzzy msgid "removing %(src)s" msgstr "Ä‘ang gỡ bá» %(src)s" #: bin/update:403 +#, fuzzy msgid "Warning: couldn't remove %(src)s -- %(rest)s" msgstr "Cảnh báo : không thể gỡ bá» %(src)s -- %(rest)s" #: bin/update:408 +#, fuzzy msgid "couldn't remove old file %(pyc)s -- %(rest)s" msgstr "không thể gỡ bá» tập tin cÅ© %(pyc)s -- %(rest)s" @@ -12455,14 +12756,17 @@ msgid "updating old qfiles" msgstr "Ä‘ang cập nhật các tập tin q cÅ©" #: bin/update:455 +#, fuzzy msgid "Warning! Not a directory: %(dirpath)s" msgstr "Cảnh báo ! Không phải là thư mục : %(dirpath)s" #: bin/update:530 +#, fuzzy msgid "message is unparsable: %(filebase)s" msgstr "không thể phân tách thư : %(filebase)s" #: bin/update:544 +#, fuzzy msgid "Warning! Deleting empty .pck file: %(pckfile)s" msgstr "Cảnh báo ! Äang xoá bá» tập tin .pck rá»—ng : %(pckfile)s" @@ -12478,10 +12782,12 @@ msgstr "" "Äang cập nhật cÆ¡ sở dữ liệu « pending.pck » (bị hoãn) cá»§a Mailman 2.1.4" #: bin/update:598 +#, fuzzy msgid "Ignoring bad pended data: %(key)s: %(val)s" msgstr "Äang bá» qua dữ liệu bị hoãn sai : %(key)s: %(val)s" #: bin/update:614 +#, fuzzy msgid "WARNING: Ignoring duplicate pending ID: %(id)s." msgstr "CẢNH BÃO : Ä‘ang bá» qua ID bị hoãn trùng : %(id)s." @@ -12506,6 +12812,7 @@ msgid "done" msgstr "hoàn tất" #: bin/update:691 +#, fuzzy msgid "Updating mailing list: %(listname)s" msgstr "Äang cập nhật há»™p thư chung : %(listname)s" @@ -12570,6 +12877,7 @@ msgid "No updates are necessary." msgstr "Không cần thiết cập nhật gì." #: bin/update:796 +#, fuzzy msgid "" "Downgrade detected, from version %(hexlversion)s to version %(hextversion)s\n" "This is probably not safe.\n" @@ -12580,10 +12888,12 @@ msgstr "" "Rất có thể là việc này không an toàn nên thoát." #: bin/update:801 +#, fuzzy msgid "Upgrading from version %(hexlversion)s to %(hextversion)s" msgstr "Äang nâng cấp từ phiên bản %(hexlversion)s lên %(hextversion)s" #: bin/update:810 +#, fuzzy msgid "" "\n" "ERROR:\n" @@ -12854,6 +13164,7 @@ msgstr "" " " #: bin/withlist:175 +#, fuzzy msgid "Unlocking (but not saving) list: %(listname)s" msgstr "Äang bá» khoá (còn không lưu) há»™p thư chung : %(listname)s" @@ -12862,6 +13173,7 @@ msgid "Finalizing" msgstr "Äang kết thúc..." #: bin/withlist:188 +#, fuzzy msgid "Loading list %(listname)s" msgstr "Äang tải há»™p thư chung %(listname)s" @@ -12874,6 +13186,7 @@ msgid "(unlocked)" msgstr "(đã bá» khoá)" #: bin/withlist:197 +#, fuzzy msgid "Unknown list: %(listname)s" msgstr "Không biết há»™p thư chung : %(listname)s" @@ -12886,18 +13199,22 @@ msgid "--all requires --run" msgstr "Tùy chá»n « --all » cần thiết « --run »" #: bin/withlist:266 +#, fuzzy msgid "Importing %(module)s..." msgstr "Äang nạp %(module)s..." #: bin/withlist:270 +#, fuzzy msgid "Running %(module)s.%(callable)s()..." msgstr "Äang chạy %(module)s.%(callable)s()..." #: bin/withlist:291 +#, fuzzy msgid "The variable `m' is the %(listname)s MailList instance" msgstr "Biến « m » là thể hiện MailList cá»§a há»™p thư chung %(listname)s " #: cron/bumpdigests:19 +#, fuzzy msgid "" "Increment the digest volume number and reset the digest number to one.\n" "\n" @@ -12925,6 +13242,7 @@ msgstr "" "Nếu không ghi rõ tên há»™p thư, má»i há»™p thư được tăng dần.\n" #: cron/checkdbs:20 +#, fuzzy msgid "" "Check for pending admin requests and mail the list owners if necessary.\n" "\n" @@ -12954,10 +13272,12 @@ msgstr "" "\n" #: cron/checkdbs:121 +#, fuzzy msgid "%(count)d %(realname)s moderator request(s) waiting" msgstr "Há»™p thư chung %(realname)s có %(count)d yêu cầu Ä‘iá»u hợp Ä‘ang đợi" #: cron/checkdbs:124 +#, fuzzy msgid "%(realname)s moderator request check result" msgstr "Kết quả kiểm tra yêu cầu Ä‘iá»u hợp há»™p thư chung %(realname)s" @@ -12979,6 +13299,7 @@ msgstr "" "Thư đã gởi bị hoãn :" #: cron/checkdbs:169 +#, fuzzy msgid "" "From: %(sender)s on %(date)s\n" "Subject: %(subject)s\n" @@ -13010,6 +13331,7 @@ msgid "" msgstr "" #: cron/disabled:20 +#, fuzzy msgid "" "Process disabled members, recommended once per day.\n" "\n" @@ -13134,6 +13456,7 @@ msgstr "" "\n" #: cron/mailpasswds:19 +#, fuzzy msgid "" "Send password reminders for all lists to all users.\n" "\n" @@ -13187,10 +13510,12 @@ msgid "Password // URL" msgstr "Mật khẩu // Äịa chỉ URL" #: cron/mailpasswds:222 +#, fuzzy msgid "%(host)s mailing list memberships reminder" msgstr "Lá»i nhắc nhở thành viên há»™p thư %(host)s" #: cron/nightly_gzip:19 +#, fuzzy msgid "" "Re-generate the Pipermail gzip'd archive flat files.\n" "\n" diff --git a/messages/zh_CN/LC_MESSAGES/mailman.po b/messages/zh_CN/LC_MESSAGES/mailman.po index dbf61702..d869f762 100755 --- a/messages/zh_CN/LC_MESSAGES/mailman.po +++ b/messages/zh_CN/LC_MESSAGES/mailman.po @@ -62,10 +62,12 @@ msgid "

                  Currently, there are no archives.

                  " msgstr "

                  ç›®å‰æ²¡æœ‰å½’档文件

                  " #: Mailman/Archiver/HyperArch.py:821 +#, fuzzy msgid "Gzip'd Text%(sz)s" msgstr "Gzip压缩文本大å°ä¸º %(sz)s" #: Mailman/Archiver/HyperArch.py:826 +#, fuzzy msgid "Text%(sz)s" msgstr "文本大å°ä¸º %(sz)s" @@ -138,18 +140,22 @@ msgid "Third" msgstr "第三" #: Mailman/Archiver/HyperArch.py:938 +#, fuzzy msgid "%(ord)s quarter %(year)i" msgstr "%(year)i 的第 %(ord)s 季度" #: Mailman/Archiver/HyperArch.py:945 +#, fuzzy msgid "%(month)s %(year)i" msgstr "%(month)s 月 %(year)i å¹´" #: Mailman/Archiver/HyperArch.py:950 +#, fuzzy msgid "The Week Of Monday %(day)i %(month)s %(year)i" -msgstr "" +msgstr "%(day)i æ—¥ %(month)s 月 %(year)i å¹´" #: Mailman/Archiver/HyperArch.py:954 +#, fuzzy msgid "%(day)i %(month)s %(year)i" msgstr "%(day)i æ—¥ %(month)s 月 %(year)i å¹´" @@ -158,10 +164,12 @@ msgid "Computing threaded index\n" msgstr "计算索引\n" #: Mailman/Archiver/HyperArch.py:1319 +#, fuzzy msgid "Updating HTML for article %(seq)s" msgstr "为文章 %(seq)s æ›´æ–°HTML" #: Mailman/Archiver/HyperArch.py:1326 +#, fuzzy msgid "article file %(filename)s is missing!" msgstr "文章文件 %(filename)s 丢失" @@ -178,6 +186,7 @@ msgid "Pickling archive state into " msgstr "" #: Mailman/Archiver/pipermail.py:453 +#, fuzzy msgid "Updating index files for archive [%(archive)s]" msgstr "为归档 [%(archive)s] 更新索引文件" @@ -223,6 +232,7 @@ msgid "disabled address" msgstr "ç¦æ­¢" #: Mailman/Bouncer.py:304 +#, fuzzy msgid " The last bounce received from you was dated %(date)s" msgstr "您最åŽçš„退信日期为 %(date)s" @@ -250,6 +260,7 @@ msgstr "管ç†å‘˜" #: Mailman/Cgi/options.py:101 Mailman/Cgi/private.py:108 #: Mailman/Cgi/rmlist.py:75 Mailman/Cgi/roster.py:59 #: Mailman/Cgi/subscribe.py:67 +#, fuzzy msgid "No such list %(safelistname)s" msgstr "没有类似的列表 %(safelistname)s" @@ -319,6 +330,7 @@ msgstr "" " 直到您修正上述问题å‰ï¼Œéžæ‘˜è¦æ¨¡å¼ä¼šå‘˜å°†æŒç»­æ”¶åˆ°ä¿¡ä»¶ã€‚%(rm)r" #: Mailman/Cgi/admin.py:268 +#, fuzzy msgid "%(hostname)s mailing lists - Admin Links" msgstr "%(hostname)s 的邮件列表 - 管ç†é“¾æŽ¥" @@ -331,6 +343,7 @@ msgid "Mailman" msgstr "Mailman" #: Mailman/Cgi/admin.py:310 +#, fuzzy msgid "" "

                  There currently are no publicly-advertised %(mailmanlink)s\n" " mailing lists on %(hostname)s." @@ -339,6 +352,7 @@ msgstr "" " 邮件列表。" #: Mailman/Cgi/admin.py:316 +#, fuzzy msgid "" "

                  Below is the collection of publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s. Click on a list\n" @@ -353,6 +367,7 @@ msgid "right " msgstr "正确" #: Mailman/Cgi/admin.py:325 +#, fuzzy msgid "" "To visit the administrators configuration page for an\n" " unadvertised list, open a URL similar to this one, but with a '/' " @@ -396,6 +411,7 @@ msgid "No valid variable name found." msgstr "找ä¸åˆ°æœ‰æ•ˆçš„å˜é‡å。" #: Mailman/Cgi/admin.py:395 +#, fuzzy msgid "" "%(realname)s Mailing list Configuration Help\n" "
                  %(varname)s Option" @@ -404,6 +420,7 @@ msgstr "" "
                  %(varname)s 选项" #: Mailman/Cgi/admin.py:402 +#, fuzzy msgid "Mailman %(varname)s List Option Help" msgstr "Mailman %(varname)s 列表选项帮助" @@ -422,14 +439,17 @@ msgstr "" " " #: Mailman/Cgi/admin.py:431 +#, fuzzy msgid "return to the %(categoryname)s options page." msgstr "返回 %(categoryname)s 选项页é¢." #: Mailman/Cgi/admin.py:446 +#, fuzzy msgid "%(realname)s Administration (%(label)s)" msgstr "%(realname)s 管ç†å‘˜ (%(label)s)" #: Mailman/Cgi/admin.py:447 +#, fuzzy msgid "%(realname)s mailing list administration
                  %(label)s Section" msgstr "%(realname)s 邮件列表管ç†å‘˜n
                  %(label)s 部分" @@ -511,6 +531,7 @@ msgid "Value" msgstr "值" #: Mailman/Cgi/admin.py:668 +#, fuzzy msgid "" "Badly formed options entry:\n" " %(record)s" @@ -611,10 +632,12 @@ msgid "Move rule down" msgstr "下移规则" #: Mailman/Cgi/admin.py:870 +#, fuzzy msgid "
                  (Edit %(varname)s)" msgstr "
                  (编辑 %(varname)s)" #: Mailman/Cgi/admin.py:872 +#, fuzzy msgid "
                  (Details for %(varname)s)" msgstr "
                  (%(varname)s的细节)" @@ -654,6 +677,7 @@ msgid "(help)" msgstr "(帮助)" #: Mailman/Cgi/admin.py:930 +#, fuzzy msgid "Find member %(link)s:" msgstr "查找 %(link)s çš„æˆå‘˜" @@ -666,10 +690,12 @@ msgid "Bad regular expression: " msgstr "错误的正则表达å¼" #: Mailman/Cgi/admin.py:1013 +#, fuzzy msgid "%(allcnt)s members total, %(membercnt)s shown" msgstr "æˆå‘˜æ€»æ•° %(allcnt)s, 显示了 %(membercnt)s " #: Mailman/Cgi/admin.py:1016 +#, fuzzy msgid "%(allcnt)s members total" msgstr "æˆå‘˜æ€»æ•° %(allcnt)s" @@ -844,6 +870,7 @@ msgstr "" " " #: Mailman/Cgi/admin.py:1221 +#, fuzzy msgid "from %(start)s to %(end)s" msgstr "从 %(start)s 到 %(end)s" @@ -980,6 +1007,7 @@ msgid "Change list ownership passwords" msgstr "更改列表拥有者的密ç " #: Mailman/Cgi/admin.py:1364 +#, fuzzy msgid "" "The list administrators are the people who have ultimate control " "over\n" @@ -1148,8 +1176,9 @@ msgid "%(schange_to)s is already a member" msgstr "å·²ç»æ˜¯æˆå‘˜äº†" #: Mailman/Cgi/admin.py:1620 +#, fuzzy msgid "%(schange_to)s matches banned pattern %(spat)s" -msgstr "" +msgstr "å·²ç»æ˜¯æˆå‘˜äº†" #: Mailman/Cgi/admin.py:1622 msgid "Address %(schange_from)s changed to %(schange_to)s" @@ -1189,6 +1218,7 @@ msgid "Not subscribed" msgstr "没有订阅" #: Mailman/Cgi/admin.py:1773 +#, fuzzy msgid "Ignoring changes to deleted member: %(user)s" msgstr "忽略对已删除用户的更改: %(user)s" @@ -1201,10 +1231,12 @@ msgid "Error Unsubscribing:" msgstr "é”™è¯¯å–æ¶ˆè®¢é˜…:" #: Mailman/Cgi/admindb.py:235 Mailman/Cgi/admindb.py:248 +#, fuzzy msgid "%(realname)s Administrative Database" msgstr "%(realname)s ç®¡ç†æ•°æ®åº“" #: Mailman/Cgi/admindb.py:238 +#, fuzzy msgid "%(realname)s Administrative Database Results" msgstr "%(realname)s ç®¡ç†æ•°æ®åº“结果" @@ -1233,6 +1265,7 @@ msgid "Discard all messages marked Defer" msgstr "丢弃所有标记有 推迟的信æ¯" #: Mailman/Cgi/admindb.py:298 +#, fuzzy msgid "all of %(esender)s's held messages." msgstr "所有的 %(esender)s's 滞留信æ¯." @@ -1253,6 +1286,7 @@ msgid "list of available mailing lists." msgstr "å¯ç”¨çš„邮件列表的清å•" #: Mailman/Cgi/admindb.py:362 +#, fuzzy msgid "You must specify a list name. Here is the %(link)s" msgstr "您必须给出一个列表åç§°. 这里是 %(link)s" @@ -1335,6 +1369,7 @@ msgid "The sender is now a member of this list" msgstr "这个å‘é€è€…现在是这个列表的æˆå‘˜äº†" #: Mailman/Cgi/admindb.py:568 +#, fuzzy msgid "Add %(esender)s to one of these sender filters:" msgstr "把%(esender)s 加到这些å‘é€è¿‡æ»¤å™¨ä¸­:" @@ -1355,6 +1390,7 @@ msgid "Rejects" msgstr "æ‹’ç»" #: Mailman/Cgi/admindb.py:584 +#, fuzzy msgid "" "Ban %(esender)s from ever subscribing to this\n" " mailing list" @@ -1367,6 +1403,7 @@ msgid "" msgstr "点击信æ¯åºå·æ¥æŸ¥çœ‹åˆ†åˆ«çš„ä¿¡æ¯ï¼Œæˆ–者您å¯ä»¥" #: Mailman/Cgi/admindb.py:591 +#, fuzzy msgid "view all messages from %(esender)s" msgstr "查看æ¥è‡ª %(esender)s 所有的信æ¯" @@ -1492,6 +1529,7 @@ msgstr "" " 这次请求被撤销了." #: Mailman/Cgi/confirm.py:178 +#, fuzzy msgid "System error, bad content: %(content)s" msgstr "系统错误, 错误的内容: %(content)s" @@ -1528,6 +1566,7 @@ msgid "Confirm subscription request" msgstr "确认订阅请求" #: Mailman/Cgi/confirm.py:259 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " subscription request to the mailing list %(listname)s. Your\n" @@ -1557,6 +1596,7 @@ msgstr "" "

                  或者,å¦‚æžœæ‚¨ä¸æƒ³è®¢é˜…这个列表,å¯ä»¥ç‚¹å‡»å–消我的订阅请求 " #: Mailman/Cgi/confirm.py:275 +#, fuzzy msgid "" "Your confirmation is required in order to continue with\n" " the subscription request to the mailing list %(listname)s.\n" @@ -1606,6 +1646,7 @@ msgid "Preferred language:" msgstr "喜欢的语言" #: Mailman/Cgi/confirm.py:317 +#, fuzzy msgid "Subscribe to list %(listname)s" msgstr "订阅邮件列表 %(listname)s" @@ -1622,6 +1663,7 @@ msgid "Awaiting moderator approval" msgstr "等待列表主æŒè€…的批准" #: Mailman/Cgi/confirm.py:386 +#, fuzzy msgid "" " You have successfully confirmed your subscription request to " "the\n" @@ -1674,6 +1716,7 @@ msgid "Subscription request confirmed" msgstr "订阅请求已ç»ç¡®è®¤" #: Mailman/Cgi/confirm.py:418 +#, fuzzy msgid "" " You have successfully confirmed your subscription request for\n" " \"%(addr)s\" to the %(listname)s mailing list. A separate\n" @@ -1700,6 +1743,7 @@ msgid "Unsubscription request confirmed" msgstr "å–æ¶ˆè®¢é˜…通过了验è¯" #: Mailman/Cgi/confirm.py:469 +#, fuzzy msgid "" " You have successfully unsubscribed from the %(listname)s " "mailing\n" @@ -1720,6 +1764,7 @@ msgid "Not available" msgstr "ä¸å¯ç”¨" #: Mailman/Cgi/confirm.py:498 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " unsubscription request from the mailing list %(listname)s. " @@ -1786,6 +1831,7 @@ msgid "Change of address request confirmed" msgstr "对地å€è¯·æ±‚的修改已ç»ç¡®è®¤" #: Mailman/Cgi/confirm.py:571 +#, fuzzy msgid "" " You have successfully changed your address on the %(listname)s\n" " mailing list from %(oldaddr)s to %(newaddr)s. " @@ -1806,6 +1852,7 @@ msgid "globally" msgstr "全局地" #: Mailman/Cgi/confirm.py:605 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " change of address request for the mailing list %(listname)s. " @@ -1863,6 +1910,7 @@ msgid "Sender discarded message via web." msgstr "寄件人通过web删除了信件" #: Mailman/Cgi/confirm.py:674 +#, fuzzy msgid "" "The held message with the Subject:\n" " header %(subject)s could not be found. The most " @@ -1882,6 +1930,7 @@ msgid "Posted message canceled" msgstr "å‘é€çš„ä¿¡ä»¶å·²ç»å–消" #: Mailman/Cgi/confirm.py:685 +#, fuzzy msgid "" " You have successfully canceled the posting of your message with\n" " the Subject: header %(subject)s to the mailing list\n" @@ -1901,6 +1950,7 @@ msgid "" msgstr "æäº¤ç»™ä½ çš„æ»žç•™ä¿¡ä»¶å·²ç»ç”±åˆ—表管ç†å‘˜å¤„ç†äº†." #: Mailman/Cgi/confirm.py:735 +#, fuzzy msgid "" "Your confirmation is required in order to cancel the\n" " posting of your message to the mailing list %(listname)s:\n" @@ -1946,6 +1996,7 @@ msgid "Membership re-enabled." msgstr "æˆå‘˜é‡æ–°æœ‰æ•ˆ" #: Mailman/Cgi/confirm.py:798 +#, fuzzy msgid "" " You have successfully re-enabled your membership in the\n" " %(listname)s mailing list. You can now not available" msgstr "ä¸å¯ç”¨" #: Mailman/Cgi/confirm.py:846 +#, fuzzy msgid "" "Your membership in the %(realname)s mailing list is\n" " currently disabled due to excessive bounces. Your confirmation is\n" @@ -2040,10 +2093,12 @@ msgid "administrative list overview" msgstr "管ç†åˆ—表概览" #: Mailman/Cgi/create.py:115 +#, fuzzy msgid "List name must not include \"@\": %(safelistname)s" msgstr "列表åç§°ä¸èƒ½åŒ…å« \"@\": %(safelistname)s" #: Mailman/Cgi/create.py:122 +#, fuzzy msgid "List already exists: %(safelistname)s" msgstr "列表已ç»å­˜åœ¨: %(safelistname)s" @@ -2075,18 +2130,22 @@ msgid "You are not authorized to create new mailing lists" msgstr "您没有创建新邮件列表的æƒé™" #: Mailman/Cgi/create.py:182 +#, fuzzy msgid "Unknown virtual host: %(safehostname)s" msgstr "未知的虚拟主机: %(safehostname)s " #: Mailman/Cgi/create.py:218 bin/newlist:219 +#, fuzzy msgid "Bad owner email address: %(s)s" msgstr "错误的拥有者邮件地å€: %(s)s " #: Mailman/Cgi/create.py:223 bin/newlist:182 bin/newlist:223 +#, fuzzy msgid "List already exists: %(listname)s" msgstr "列表已ç»å­˜åœ¨: %(listname)s" #: Mailman/Cgi/create.py:231 bin/newlist:217 +#, fuzzy msgid "Illegal list name: %(s)s" msgstr "éžæ³•的列表åç§°: %(s)s" @@ -2099,6 +2158,7 @@ msgstr "" " 请è”系站点管ç†å‘˜ä»¥èŽ·å¾—å¸®åŠ©." #: Mailman/Cgi/create.py:273 bin/newlist:265 +#, fuzzy msgid "Your new mailing list: %(listname)s" msgstr "您新建的邮件列表: %(listname)s" @@ -2107,6 +2167,7 @@ msgid "Mailing list creation results" msgstr "创建邮件列表的结果" #: Mailman/Cgi/create.py:288 +#, fuzzy msgid "" "You have successfully created the mailing list\n" " %(listname)s and notification has been sent to the list owner\n" @@ -2129,6 +2190,7 @@ msgid "Create another list" msgstr "创建å¦ä¸€ä¸ªåˆ—表" #: Mailman/Cgi/create.py:312 +#, fuzzy msgid "Create a %(hostname)s Mailing List" msgstr "创建一个 %(hostname)s 邮件列表" @@ -2221,6 +2283,7 @@ msgstr "" " æ–°æˆå‘˜å‘é€çš„ä¿¡æ¯æ»žç•™èµ·æ¥,等待主æŒè€…的批准(默认)." #: Mailman/Cgi/create.py:432 +#, fuzzy msgid "" "Initial list of supported languages.

                  Note that if you do not\n" " select at least one initial language, the list will use the server\n" @@ -2326,6 +2389,7 @@ msgid "List name is required." msgstr "列表å称是必需的." #: Mailman/Cgi/edithtml.py:154 +#, fuzzy msgid "%(realname)s -- Edit html for %(template_info)s" msgstr "%(realname)s -- 为 %(template_info)s 编辑html " @@ -2334,10 +2398,12 @@ msgid "Edit HTML : Error" msgstr "编辑HTML : 错误" #: Mailman/Cgi/edithtml.py:161 +#, fuzzy msgid "%(safetemplatename)s: Invalid template" msgstr "%(safetemplatename)s: 无效的模版" #: Mailman/Cgi/edithtml.py:166 Mailman/Cgi/edithtml.py:167 +#, fuzzy msgid "%(realname)s -- HTML Page Editing" msgstr "%(realname)s -- HTML 页é¢ç¼–辑" @@ -2396,16 +2462,19 @@ msgid "HTML successfully updated." msgstr "HTMLæ›´æ–°æˆåŠŸ." #: Mailman/Cgi/listinfo.py:89 +#, fuzzy msgid "%(hostname)s Mailing Lists" msgstr " %(hostname)s 邮件列表" #: Mailman/Cgi/listinfo.py:127 +#, fuzzy msgid "" "

                  There currently are no publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s." msgstr "

                  ç›®å‰åœ¨ %(hostname)s 上没有公开的 %(mailmanlink)s 邮件列表." #: Mailman/Cgi/listinfo.py:131 +#, fuzzy msgid "" "

                  Below is a listing of all the public mailing lists on\n" " %(hostname)s. Click on a list name to get more information " @@ -2423,6 +2492,7 @@ msgid "right" msgstr "正确" #: Mailman/Cgi/listinfo.py:140 +#, fuzzy msgid "" " To visit the general information page for an unadvertised list,\n" " open a URL similar to this one, but with a '/' and the %(adj)s\n" @@ -2483,6 +2553,7 @@ msgstr "éžæ³•的邮件地å€" #: Mailman/Cgi/options.py:183 Mailman/Cgi/options.py:240 #: Mailman/Cgi/options.py:264 +#, fuzzy msgid "No such member: %(safeuser)s." msgstr "ä¸å­˜åœ¨ %(safeuser)s 这个æˆå‘˜." @@ -2531,6 +2602,7 @@ msgid "Note: " msgstr "" #: Mailman/Cgi/options.py:378 +#, fuzzy msgid "List subscriptions for %(safeuser)s on %(hostname)s" msgstr "为 %(safeuser)s 列出在 %(hostname)s 上的订阅" @@ -2556,6 +2628,7 @@ msgid "You are already using that email address" msgstr "您已ç»ä½¿ç”¨äº†é‚£ä¸ªé‚®ä»¶åœ°å€" #: Mailman/Cgi/options.py:459 +#, fuzzy msgid "" "The new address you requested %(newaddr)s is already a member of the\n" "%(listname)s mailing list, however you have also requested a global change " @@ -2568,6 +2641,7 @@ msgstr "" "确认åŽ, å…¶ä»–åŒ…å« %(safeuser)s 这个地å€çš„邮件列表将被修改." #: Mailman/Cgi/options.py:468 +#, fuzzy msgid "The new address is already a member: %(newaddr)s" msgstr "æ–°çš„åœ°å€ %(newaddr)s å·²ç»æ˜¯æˆå‘˜äº†" @@ -2576,6 +2650,7 @@ msgid "Addresses may not be blank" msgstr "地å€ä¸èƒ½ä¸ºç©º" #: Mailman/Cgi/options.py:488 +#, fuzzy msgid "A confirmation message has been sent to %(newaddr)s. " msgstr "确认信æ¯å·²ç»å‘é€åˆ°äº† %(newaddr)s ." @@ -2588,6 +2663,7 @@ msgid "Illegal email address provided" msgstr "æä¾›äº†éžæ³•的邮件地å€" #: Mailman/Cgi/options.py:501 +#, fuzzy msgid "%(newaddr)s is already a member of the list." msgstr "%(newaddr)s å·²ç»æ˜¯åˆ—表的æˆå‘˜." @@ -2666,6 +2742,7 @@ msgstr "" " 管ç†å‘˜ä½œå‡ºå†³å®šåŽæ‚¨å°†å—到一å°é‚®ä»¶é€šçŸ¥ã€‚" #: Mailman/Cgi/options.py:623 +#, fuzzy msgid "" "You have been successfully unsubscribed from the\n" " mailing list %(fqdn_listname)s. If you were receiving digest\n" @@ -2752,6 +2829,7 @@ msgid "day" msgstr "天" #: Mailman/Cgi/options.py:900 +#, fuzzy msgid "%(days)d %(units)s" msgstr "%(days)d %(units)s" @@ -2764,6 +2842,7 @@ msgid "No topics defined" msgstr "没有已定义的主题" #: Mailman/Cgi/options.py:940 +#, fuzzy msgid "" "\n" "You are subscribed to this list with the case-preserved address\n" @@ -2774,6 +2853,7 @@ msgstr "" "%(cpuser)s." #: Mailman/Cgi/options.py:956 +#, fuzzy msgid "%(realname)s list: member options login page" msgstr "%(realname)s 列表: æˆå‘˜é€‰é¡¹ç™»å½•页 " @@ -2782,10 +2862,12 @@ msgid "email address and " msgstr "邮件地å€å’Œ" #: Mailman/Cgi/options.py:960 +#, fuzzy msgid "%(realname)s list: member options for user %(safeuser)s" msgstr "%(realname)s 列表: 用户 %(safeuser)s çš„æˆå‘˜é€‰é¡¹ä¿¡æ¯" #: Mailman/Cgi/options.py:986 +#, fuzzy msgid "" "In order to change your membership option, you must\n" " first log in by giving your %(extra)smembership password in the section\n" @@ -2856,6 +2938,7 @@ msgid "" msgstr "<丢失>" #: Mailman/Cgi/options.py:1140 +#, fuzzy msgid "Requested topic is not valid: %(topicname)s" msgstr "请求的主题ä¸å­˜åœ¨: %(topicname)s" @@ -2884,6 +2967,7 @@ msgid "Private archive - \"./\" and \"../\" not allowed in URL." msgstr "" #: Mailman/Cgi/private.py:109 +#, fuzzy msgid "Private Archive Error - %(msg)s" msgstr "内部存档错误 - %(msg)s" @@ -2921,6 +3005,7 @@ msgid "Mailing list deletion results" msgstr "邮件列表删除结果" #: Mailman/Cgi/rmlist.py:187 +#, fuzzy msgid "" "You have successfully deleted the mailing list\n" " %(listname)s." @@ -2929,6 +3014,7 @@ msgstr "" " %(listname)s." #: Mailman/Cgi/rmlist.py:191 +#, fuzzy msgid "" "There were some problems deleting the mailing list\n" " %(listname)s. Contact your site administrator at " @@ -2939,6 +3025,7 @@ msgstr "" " è”系您的站点管ç†å‘˜ %(sitelist)s 以获å–详情。" #: Mailman/Cgi/rmlist.py:208 +#, fuzzy msgid "Permanently remove mailing list %(realname)s" msgstr "永久删除邮件列表%(realname)s" @@ -3004,6 +3091,7 @@ msgid "Invalid options to CGI script" msgstr "无效的CGIè„šæœ¬å‚æ•°" #: Mailman/Cgi/roster.py:118 +#, fuzzy msgid "%(realname)s roster authentication failed." msgstr "%(realname)s身份验è¯å¤±è´¥" @@ -3069,6 +3157,7 @@ msgstr "" "è¯ï¼Œå¾ˆå¿«æ‚¨å°±å°†æ”¶åˆ°ä¸€å°ç¡®è®¤å‡½ï¼Œç¡®è®¤å‡½é‡Œå°†åŒ…嫿›´è¿›ä¸€æ­¥çš„æŒ‡å¯¼ä¿¡æ¯ã€‚" #: Mailman/Cgi/subscribe.py:262 +#, fuzzy msgid "" "The email address you supplied is banned from this\n" " mailing list. If you think this restriction is erroneous, please\n" @@ -3090,6 +3179,7 @@ msgid "" msgstr "您的订阅请求被拒ç»äº†ï¼Œå› ä¸ºæ‚¨æä¾›çš„é‚®ä»¶åœ°å€æ˜¯ä¸å¯é çš„。" #: Mailman/Cgi/subscribe.py:278 +#, fuzzy msgid "" "Confirmation from your email address is required, to prevent anyone from\n" "subscribing you without permission. Instructions are being sent to you at\n" @@ -3101,6 +3191,7 @@ msgstr "" "您的订阅请求,å¦åˆ™æ‚¨çš„订阅将ä¸ä¼šç”Ÿæ•ˆã€‚" #: Mailman/Cgi/subscribe.py:290 +#, fuzzy msgid "" "Your subscription request was deferred because %(x)s. Your request has " "been\n" @@ -3121,6 +3212,7 @@ msgid "Mailman privacy alert" msgstr "Mailman内部警告" #: Mailman/Cgi/subscribe.py:316 +#, fuzzy msgid "" "An attempt was made to subscribe your address to the mailing list\n" "%(listaddr)s. You are already subscribed to this mailing list.\n" @@ -3158,6 +3250,7 @@ msgid "This list only supports digest delivery." msgstr "æ­¤åˆ—è¡¨ä»…æ”¯æŒæŠ•é€’å®šæœŸæ‘˜è¦ã€‚" #: Mailman/Cgi/subscribe.py:344 +#, fuzzy msgid "You have been successfully subscribed to the %(realname)s mailing list." msgstr "æ‚¨å·²ç»æˆåŠŸåœ°è®¢é˜…äº† %(realname)s 邮件列表。" @@ -3282,26 +3375,32 @@ msgid "n/a" msgstr "n/a" #: Mailman/Commands/cmd_info.py:44 +#, fuzzy msgid "List name: %(listname)s" msgstr "列表å: %(listname)s" #: Mailman/Commands/cmd_info.py:45 +#, fuzzy msgid "Description: %(description)s" msgstr "æè¿°ï¼š %(description)s" #: Mailman/Commands/cmd_info.py:46 +#, fuzzy msgid "Postings to: %(postaddr)s" msgstr "投递到: %(postaddr)s" #: Mailman/Commands/cmd_info.py:47 +#, fuzzy msgid "List Helpbot: %(requestaddr)s" msgstr "列表帮助机器人: %(requestaddr)s" #: Mailman/Commands/cmd_info.py:48 +#, fuzzy msgid "List Owners: %(owneraddr)s" msgstr "列表属主: %(owneraddr)s" #: Mailman/Commands/cmd_info.py:49 +#, fuzzy msgid "More information: %(listurl)s" msgstr "更多信æ¯ï¼š %(listurl)s" @@ -3324,18 +3423,22 @@ msgstr "" " 查看这个GNU Mailman邮件列表æœåŠ¡å™¨ä¸Šæ‰€æœ‰å…¬å…±é‚®ä»¶åˆ—è¡¨çš„æ¸…å•。\n" #: Mailman/Commands/cmd_lists.py:44 +#, fuzzy msgid "Public mailing lists at %(hostname)s:" msgstr "%(hostname)s 上的公共邮件列表:" #: Mailman/Commands/cmd_lists.py:66 +#, fuzzy msgid "%(i)3d. List name: %(realname)s" msgstr "%(i)3d. 列表å: %(realname)s" #: Mailman/Commands/cmd_lists.py:67 +#, fuzzy msgid " Description: %(description)s" msgstr " æè¿°ï¼š %(description)s" #: Mailman/Commands/cmd_lists.py:68 +#, fuzzy msgid " Requests to: %(requestaddr)s" msgstr " 请求å‘é€åˆ°åœ°å€: %(requestaddr)s " @@ -3366,12 +3469,14 @@ msgstr "" " 此时回信将会å‘é€åˆ°æ‚¨è®¢é˜…时注册的地å€ã€‚\n" #: Mailman/Commands/cmd_password.py:51 Mailman/Commands/cmd_password.py:66 +#, fuzzy msgid "Your password is: %(password)s" msgstr "æ‚¨çš„å¯†ç æ˜¯: %(password)s" #: Mailman/Commands/cmd_password.py:57 Mailman/Commands/cmd_password.py:72 #: Mailman/Commands/cmd_password.py:95 Mailman/Commands/cmd_password.py:121 #: Mailman/Commands/cmd_set.py:149 Mailman/Commands/cmd_set.py:219 +#, fuzzy msgid "You are not a member of the %(listname)s mailing list" msgstr "æ‚¨ä¸æ˜¯é‚®ä»¶åˆ—表 %(listname)s çš„æˆå‘˜" @@ -3544,6 +3649,7 @@ msgstr "" " 使用 \"set reminders off\" åŽï¼Œé‚®ä»¶åˆ—è¡¨çš„æ¯æœˆå¯†ç æç¤ºå°±ä¼šåœæ­¢å·¥ä½œ\n" #: Mailman/Commands/cmd_set.py:122 +#, fuzzy msgid "Bad set command: %(subcmd)s" msgstr "错误的设置命令: %(subcmd)s" @@ -3564,6 +3670,7 @@ msgid "on" msgstr "å¼€å¯" #: Mailman/Commands/cmd_set.py:154 +#, fuzzy msgid " ack %(onoff)s" msgstr " 确认 %(onoff)s" @@ -3605,18 +3712,22 @@ msgid " %(status)s (%(how)s on %(date)s)" msgstr "" #: Mailman/Commands/cmd_set.py:192 +#, fuzzy msgid " myposts %(onoff)s" msgstr "我的信件 %(onoff)s" #: Mailman/Commands/cmd_set.py:195 +#, fuzzy msgid " hide %(onoff)s" msgstr "éšè— %(onoff)s" #: Mailman/Commands/cmd_set.py:199 +#, fuzzy msgid " duplicates %(onoff)s" msgstr "å¤åˆ¶ %(onoff)s" #: Mailman/Commands/cmd_set.py:203 +#, fuzzy msgid " reminders %(onoff)s" msgstr "æç¤ºå‡½ %(onoff)s" @@ -3625,6 +3736,7 @@ msgid "You did not give the correct password" msgstr "您没有输入正确的密ç " #: Mailman/Commands/cmd_set.py:236 Mailman/Commands/cmd_set.py:283 +#, fuzzy msgid "Bad argument: %(arg)s" msgstr "é”™è¯¯çš„å‚æ•°: %(arg)s" @@ -3696,6 +3808,7 @@ msgstr "" " `address=

                  ' (注æ„在email地å€å‰åŽä¸ç”¨æ‹¬å·å’Œå¼•å·ã€‚)\n" #: Mailman/Commands/cmd_subscribe.py:62 +#, fuzzy msgid "Bad digest specifier: %(arg)s" msgstr "指定了错误的分类: %(arg)s" @@ -3704,6 +3817,7 @@ msgid "No valid address found to subscribe" msgstr "è®¢é˜…çš„åœ°å€æ— æ•ˆ" #: Mailman/Commands/cmd_subscribe.py:113 +#, fuzzy msgid "" "The email address you supplied is banned from this mailing list.\n" "If you think this restriction is erroneous, please contact the list\n" @@ -3737,6 +3851,7 @@ msgid "This list only supports digest subscriptions!" msgstr "此列表仅能订阅摘è¦ï¼" #: Mailman/Commands/cmd_subscribe.py:146 +#, fuzzy msgid "" "Your subscription request has been forwarded to the list administrator\n" "at %(listowner)s for review." @@ -3767,6 +3882,7 @@ msgstr "" " `address=
                  ' (注æ„在email地å€å‰åŽä¸ç”¨æ‹¬å·å’Œå¼•å·ã€‚)\n" #: Mailman/Commands/cmd_unsubscribe.py:62 +#, fuzzy msgid "%(address)s is not a member of the %(listname)s mailing list" msgstr "%(address)s 䏿˜¯é‚®ä»¶åˆ—表 %(listname)s 中的地å€" @@ -4010,6 +4126,7 @@ msgid "Chinese (Taiwan)" msgstr "ä¸­æ–‡ï¼ˆå°æ¹¾ï¼‰" #: Mailman/Deliverer.py:53 +#, fuzzy msgid "" "Note: Since this is a list of mailing lists, administrative\n" "notices like the password reminder will be sent to\n" @@ -4023,14 +4140,17 @@ msgid " (Digest mode)" msgstr "(æ‘˜è¦æ¨¡å¼)" #: Mailman/Deliverer.py:79 +#, fuzzy msgid "Welcome to the \"%(realname)s\" mailing list%(digmode)s" msgstr "欢迎加入\"%(realname)s\"的邮件列表 %(digmode)s" #: Mailman/Deliverer.py:89 +#, fuzzy msgid "You have been unsubscribed from the %(realname)s mailing list" msgstr "您已ç»é€€è®¢äº† %(realname)s 的邮件列表" #: Mailman/Deliverer.py:116 +#, fuzzy msgid "%(listfullname)s mailing list reminder" msgstr "%(listfullname)s 邮件列表æç¤ºå‡½" @@ -4043,6 +4163,7 @@ msgid "Hostile subscription attempt detected" msgstr "检测到敌æ„的订阅å°è¯•" #: Mailman/Deliverer.py:169 +#, fuzzy msgid "" "%(address)s was invited to a different mailing\n" "list, but in a deliberate malicious attempt they tried to confirm the\n" @@ -4053,6 +4174,7 @@ msgstr "" "将其对邀请的回å¤å‘é€ç»™æ‚¨ï¼Œæˆ‘ä»¬ä¼°è®¡æ‚¨ä¹æ„了解这个情况但您ä¸å¿…采å–任何行动。" #: Mailman/Deliverer.py:188 +#, fuzzy msgid "" "You invited %(address)s to your list, but in a\n" "deliberate malicious attempt, they tried to confirm the invitation to a\n" @@ -4065,6 +4187,7 @@ msgstr "" "行动。" #: Mailman/Deliverer.py:221 +#, fuzzy msgid "%(listname)s mailing list probe message" msgstr "%(listname)s 的邮件列表探测消æ¯" @@ -4259,8 +4382,8 @@ msgid "" " membership.\n" "\n" "

                  You can control both the\n" -" number\n" +" number\n" " of reminders the member will receive and the\n" " No those messages too will get discarded. You may " "want\n" " to set up an\n" -" autoresponse\n" +" autoresponse\n" " message for email to the -owner and -admin address." msgstr "" "虽然Mainman的退信检测器相当强大, 它ä»ä¸å¯èƒ½æ£€æµ‹ä¸–界上\n" @@ -4528,6 +4651,7 @@ msgstr "" " è¯•é€šçŸ¥å°†è¢«ç¦æ­¢çš„用户." #: Mailman/Gui/Bounce.py:194 +#, fuzzy msgid "" "Bad value for %(property)s: %(val)s" @@ -4643,8 +4767,8 @@ msgstr "" "\n" "

                  空行会被忽略.\n" "\n" -"

                  å‚è§ pass_mime_types, 那里有安全附件类型的列表." +"

                  å‚è§ pass_mime_types, 那里有安全附件类型的列表." #: Mailman/Gui/ContentFilter.py:94 msgid "" @@ -4660,8 +4784,8 @@ msgid "" "Use this option to remove each message attachment that does\n" " not have a matching content type. Requirements and formats " "are\n" -" exactly like filter_mime_types.\n" "\n" "

                  Note: if you add entries to this list but don't add\n" @@ -4763,6 +4887,7 @@ msgstr "" " 有当站点管ç†å‘˜å…è®¸æ—¶æ‰æœ‰æ•ˆ." #: Mailman/Gui/ContentFilter.py:171 +#, fuzzy msgid "Bad MIME type ignored: %(spectype)s" msgstr "被忽略的错误的MIME类型:%(spectype)s" @@ -4865,6 +4990,7 @@ msgstr "" " ?" #: Mailman/Gui/Digest.py:145 +#, fuzzy msgid "" "The next digest will be sent as volume\n" " %(volume)s, number %(number)s" @@ -4881,14 +5007,17 @@ msgid "There was no digest to send." msgstr "没有è¦å‘é€çš„æ‘˜è¦ã€‚" #: Mailman/Gui/GUIBase.py:173 +#, fuzzy msgid "Invalid value for variable: %(property)s" msgstr "该å˜é‡çš„å€¼éžæ³•:%(property)s" #: Mailman/Gui/GUIBase.py:178 +#, fuzzy msgid "Bad email address for option %(property)s: %(error)s" msgstr "错误的邮件地å€é€‰é¡¹%(property)s: %(error)s" #: Mailman/Gui/GUIBase.py:204 +#, fuzzy msgid "" "The following illegal substitution variables were\n" " found in the %(property)s string:\n" @@ -4902,6 +5031,7 @@ msgstr "" "

                  䏿›´æ­£è¿™äº›é—®é¢˜ï¼Œä½ çš„列表å¯èƒ½æ— æ³•正常è¿ä½œ" #: Mailman/Gui/GUIBase.py:218 +#, fuzzy msgid "" "Your %(property)s string appeared to\n" " have some correctable problems in its new value.\n" @@ -4999,8 +5129,8 @@ msgid "" "

                  In order to split the list ownership duties into\n" " administrators and moderators, you must\n" " set a separate moderator password,\n" -" and also provide the email\n" +" and also provide the email\n" " addresses of the list moderators. Note that the field you\n" " are changing here specifies the list administrators." msgstr "" @@ -5311,13 +5441,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5350,8 +5480,8 @@ msgstr "" " Reply-To:è®¾ç½®æ¥æºå¸¦æœ‰æ•ˆçš„回å¤åœ°å€ã€‚\n" " å¦ä¸€ä¸ªåŽŸå› æ˜¯ä¿®æ”¹Reply-To:会使得å‘é€\n" " å•独回å¤å˜å¾—困难。对这个问题的综åˆè®¨è®ºè§`Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful。ä¸èµžæˆçš„观点è§Reply-" "To\n" @@ -5373,8 +5503,8 @@ msgstr "显å¼çš„Reply-To:头部。" msgid "" "This is the address set in the Reply-To: header\n" " when the reply_goes_to_list\n" +" href=\"?VARHELP=general/" +"reply_goes_to_list\">reply_goes_to_list\n" " option is set to Explicit address.\n" "\n" "

                  There are many reasons not to introduce or override the\n" @@ -5382,13 +5512,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5410,8 +5540,8 @@ msgid "" " Reply-To: header, it will not be changed." msgstr "" "这是当reply_goes_to_list\n" +" href=\"?VARHELP=general/" +"reply_goes_to_list\">reply_goes_to_list\n" " 选项被设置为显å¼åœ°å€æ—¶ï¼Œè¢«è®¾ç½®åœ¨Reply-To:中的" "地å€ã€‚\n" "\n" @@ -5420,8 +5550,8 @@ msgstr "" " Reply-To:è®¾ç½®æ¥æºå¸¦æœ‰æ•ˆçš„回å¤åœ°å€ã€‚\n" " å¦ä¸€ä¸ªåŽŸå› æ˜¯ä¿®æ”¹Reply-To:会使得å‘é€\n" " å•独回å¤å˜å¾—困难。对这个问题的综åˆè®¨è®ºè§`Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful。ä¸åŒæ„的观点è§Reply-" "To\n" @@ -5479,8 +5609,8 @@ msgid "" " member list addresses, but rather to the owner of those member\n" " lists. In that case, the value of this setting is appended to\n" " the member's account name for such notices. `-owner' is the\n" -" typical choice. This setting has no effect when \"umbrella_list" -"\"\n" +" typical choice. This setting has no effect when " +"\"umbrella_list\"\n" " is \"No\"." msgstr "" "当\"umbrella_list\"被的设置指示该列表有其它邮件列表作为æˆå‘˜æ—¶ï¼Œ\n" @@ -5762,8 +5892,8 @@ msgid "" " does not affect the inclusion of the other List-*:\n" " headers.)" msgstr "" -"List-Post:头部是RFC 2369.\n" +"List-Post:头部是RFC 2369.\n" " 推è的头部之一。然而,对一些åªé™é€šçŸ¥çš„邮件列表,仅有一" "些人有å‘列表\n" " å‘é€é‚®ä»¶çš„æƒåˆ©ï¼›ä¸å…许普通æˆå‘˜å‘é€é‚®ä»¶ã€‚对这ç§ç±»åž‹çš„列表," @@ -6363,6 +6493,7 @@ msgstr "" "表。" #: Mailman/Gui/Privacy.py:104 +#, fuzzy msgid "" "This section allows you to configure subscription and\n" " membership exposure policy. You can also control whether this\n" @@ -6541,8 +6672,8 @@ msgid "" " either individually or as a group. Any\n" " posting from a non-member who is not explicitly accepted,\n" " rejected, or discarded, will have their posting filtered by the\n" -" general\n" +" general\n" " non-member rules.\n" "\n" "

                  In the text boxes below, add one address per line; start the\n" @@ -6598,6 +6729,7 @@ msgid "By default, should new list member postings be moderated?" msgstr "默认情况下,列表新æˆå‘˜çš„信件是å¦è¢«æš‚存?" #: Mailman/Gui/Privacy.py:218 +#, fuzzy msgid "" "Each list member has a moderation flag which says\n" " whether messages from the list member can be posted directly " @@ -6647,8 +6779,8 @@ msgid "" "If a member posts this many times, within a period of time\n" " the member is automatically moderated. Use 0 to disable. " "See\n" -" member_verbosity_interval for details on the time " "period.\n" "\n" @@ -7074,6 +7206,7 @@ msgid "" msgstr "è¢«è‡ªåŠ¨ä¸¢å¼ƒçš„éžæˆå‘˜ä¿¡ä»¶æ˜¯å¦åº”该被转å‘给列表管ç†å‘˜ï¼Ÿ" #: Mailman/Gui/Privacy.py:494 +#, fuzzy msgid "" "Text to include in any rejection notice to be sent to\n" " non-members who post to this list. This notice can include\n" @@ -7304,6 +7437,7 @@ msgstr "" " ä¸å®Œæ•´çš„过滤器规则将被忽略。" #: Mailman/Gui/Privacy.py:693 +#, fuzzy msgid "" "The header filter rule pattern\n" " '%(safepattern)s' is not a legal regular expression. This\n" @@ -7354,8 +7488,8 @@ msgid "" "

                  The body of the message can also be optionally scanned for\n" " Subject: and Keywords: headers, as\n" " specified by the topics_bodylines_limit\n" +" href=\"?VARHELP=topics/" +"topics_bodylines_limit\">topics_bodylines_limit\n" " configuration variable." msgstr "" "ä¸»é¢˜è¿‡æ»¤å™¨æ ¹æ®æ‚¨åœ¨ä¸‹é¢æŒ‡å®šçš„æ³¨æ„,此特性åªå·¥ä½œåœ¨æ™®é€šæŠ•递模å¼ï¼Œè€Œä¸å·¥ä½œäºŽæ‘˜è¦æŠ•递模å¼ã€‚\n" "\n" "

                  您也å¯ä»¥åœ¨topics_bodylines_limit\n" +" href=\"?VARHELP=topics/" +"topics_bodylines_limit\">topics_bodylines_limit\n" " 指定é…置选项以在信件正文中寻找Subject:头和" "Keywords:\n" " 头。" @@ -7442,6 +7576,7 @@ msgstr "" " 的主题将被忽略。" #: Mailman/Gui/Topics.py:135 +#, fuzzy msgid "" "The topic pattern '%(safepattern)s' is not a\n" " legal regular expression. It will be discarded." @@ -7649,6 +7784,7 @@ msgid "%(listinfo_link)s list run by %(owner_link)s" msgstr "ç”± %(owner_link)s è¿è¡Œçš„ %(listinfo_link)s 邮件列表" #: Mailman/HTMLFormatter.py:57 +#, fuzzy msgid "%(realname)s administrative interface" msgstr "%(realname)s çš„ç®¡ç†æŽ¥å£" @@ -7657,6 +7793,7 @@ msgid " (requires authorization)" msgstr "(éœ€è¦æŽˆæƒ)" #: Mailman/HTMLFormatter.py:61 +#, fuzzy msgid "Overview of all %(hostname)s mailing lists" msgstr "%(hostname)s 上的所有邮件列表" @@ -7677,6 +7814,7 @@ msgid "; it was disabled by the list administrator" msgstr ";列表管ç†å‘˜ç¦æ­¢äº†è¯¥é€‰é¡¹" #: Mailman/HTMLFormatter.py:146 +#, fuzzy msgid "" "; it was disabled due to excessive bounces. The\n" " last bounce was received on %(date)s" @@ -7687,6 +7825,7 @@ msgid "; it was disabled for unknown reasons" msgstr "ï¼›ç”±äºŽä¸æ˜ŽåŽŸå› ï¼Œå®ƒè¢«ç¦æ­¢äº†" #: Mailman/HTMLFormatter.py:151 +#, fuzzy msgid "Note: your list delivery is currently disabled%(reason)s." msgstr "注æ„:你在列表中的å‘ä¿¡æƒçŽ°åœ¨è¢«ç¦æ­¢äº†ï¼Œå› ä¸º %(reason)s。" @@ -7699,6 +7838,7 @@ msgid "the list administrator" msgstr "列表管ç†å‘˜" #: Mailman/HTMLFormatter.py:157 +#, fuzzy msgid "" "

                  %(note)s\n" "\n" @@ -7716,6 +7856,7 @@ msgstr "" " %(mailto)s。" #: Mailman/HTMLFormatter.py:169 +#, fuzzy msgid "" "

                  We have received some recent bounces from your\n" " address. Your current bounce score is %(score)s out of " @@ -7734,6 +7875,7 @@ msgstr "" " 的退信数将很快被自动é‡è®¾ã€‚" #: Mailman/HTMLFormatter.py:181 +#, fuzzy msgid "" "(Note - you are subscribing to a list of mailing lists, so the %(type)s " "notice will be sent to the admin address for your membership, %(addr)s.)

                  " @@ -7774,18 +7916,21 @@ msgstr "" " 过电å­é‚®ä»¶å‘ŠçŸ¥ä½ ã€‚" #: Mailman/HTMLFormatter.py:208 +#, fuzzy msgid "" "This is %(also)sa private list, which means that the\n" " list of members is not available to non-members." msgstr "这是一个 %(also)s ç§æœ‰åˆ—表,这æ„味ç€éžæˆå‘˜æ— æ³•访问æˆå‘˜åˆ—表。" #: Mailman/HTMLFormatter.py:211 +#, fuzzy msgid "" "This is %(also)sa hidden list, which means that the\n" " list of members is available only to the list administrator." msgstr "这是一个 %(also)s éšè—列表,这æ„å‘³ç€æˆå‘˜åˆ—表仅对列表管ç†å‘˜å¯è§ã€‚" #: Mailman/HTMLFormatter.py:214 +#, fuzzy msgid "" "This is %(also)sa public list, which means that the\n" " list of members list is available to everyone." @@ -7798,6 +7943,7 @@ msgid "" msgstr "(ä½†æ˜¯æˆ‘ä»¬å°†åœ°å€æ¨¡ç³ŠåŒ–,这样它们将ä¸ä¼šè½»æ˜“è¢«åžƒåœ¾ä¿¡æ¯æ”¶é›†å™¨è¯†åˆ«)。" #: Mailman/HTMLFormatter.py:222 +#, fuzzy msgid "" "

                  (Note that this is an umbrella list, intended to\n" " have only other mailing lists as members. Among other things,\n" @@ -7813,6 +7959,7 @@ msgid "either " msgstr "任一的" #: Mailman/HTMLFormatter.py:256 +#, fuzzy msgid "" "To unsubscribe from %(realname)s, get a password reminder,\n" " or change your subscription options %(either)senter your " @@ -7844,12 +7991,14 @@ msgid "" msgstr "如果您将该字段设为空白,您将被æç¤ºè¾“入您的邮件地å€ã€‚" #: Mailman/HTMLFormatter.py:277 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " members.)" msgstr "(%(which)såªå¯¹åˆ—表æˆå‘˜æœ‰æ•ˆ)" #: Mailman/HTMLFormatter.py:281 +#, fuzzy msgid "" "(%(which)s is only available to the list\n" " administrator.)" @@ -7908,6 +8057,7 @@ msgid "The current archive" msgstr "当å‰å½’æ¡£" #: Mailman/Handlers/Acknowledge.py:59 +#, fuzzy msgid "%(realname)s post acknowledgement" msgstr "%(realname)så‘é€ç¡®è®¤" @@ -7920,6 +8070,7 @@ msgid "" msgstr "" #: Mailman/Handlers/CalcRecips.py:79 +#, fuzzy msgid "" "Your urgent message to the %(realname)s mailing list was not authorized for\n" "delivery. The original message as received by Mailman is attached.\n" @@ -7994,6 +8145,7 @@ msgid "Message may contain administrivia" msgstr "消æ¯ä¸­åŒ…å«ç®¡ç†æŒ‡ä»¤" #: Mailman/Handlers/Hold.py:84 +#, fuzzy msgid "" "Please do *not* post administrative requests to the mailing\n" "list. If you wish to subscribe, visit %(listurl)s or send a message with " @@ -8030,10 +8182,12 @@ msgid "Posting to a moderated newsgroup" msgstr "å‘é€ä¿¡æ¯è‡³å—节制的新闻组" #: Mailman/Handlers/Hold.py:252 +#, fuzzy msgid "Your message to %(listname)s awaits moderator approval" msgstr "您å‘é€åˆ° %(listname)s 的信件已ç»äº¤ç”±åˆ—表主æŒè€…审批" #: Mailman/Handlers/Hold.py:271 +#, fuzzy msgid "%(listname)s post from %(sender)s requires approval" msgstr "%(listname)s 中æ¥è‡ª %(sender)s 的信件需è¦å®¡æ‰¹" @@ -8074,6 +8228,7 @@ msgid "After content filtering, the message was empty" msgstr "ç»è¿‡å†…容过滤åŽï¼Œè¯¥ä¿¡ä»¶ä¸ºç©º" #: Mailman/Handlers/MimeDel.py:269 +#, fuzzy msgid "" "The attached message matched the %(listname)s mailing list's content " "filtering\n" @@ -8114,6 +8269,7 @@ msgid "The attached message has been automatically discarded." msgstr "附带的消æ¯è¢«è‡ªåŠ¨ä¸¢å¼ƒ" #: Mailman/Handlers/Replybot.py:75 +#, fuzzy msgid "Auto-response for your message to the \"%(realname)s\" mailing list" msgstr "对您å‘é€è‡³ \"%(realname)s\" 邮件列表信件的自动答å¤" @@ -8137,6 +8293,7 @@ msgid "HTML attachment scrubbed and removed" msgstr "HTML附件被移除" #: Mailman/Handlers/Scrubber.py:234 Mailman/Handlers/Scrubber.py:259 +#, fuzzy msgid "" "An HTML attachment was scrubbed...\n" "URL: %(url)s\n" @@ -8191,6 +8348,7 @@ msgstr "" "Url: %(url)s\n" #: Mailman/Handlers/Scrubber.py:347 +#, fuzzy msgid "Skipped content of type %(partctype)s\n" msgstr "跳过的 %(partctype)s 类型的内容\n" @@ -8219,6 +8377,7 @@ msgid "Message rejected by filter rule match" msgstr "由于匹é…过滤规则而被拒ç»çš„ä¿¡ä»¶" #: Mailman/Handlers/ToDigest.py:173 +#, fuzzy msgid "%(realname)s Digest, Vol %(volume)d, Issue %(issue)d" msgstr "%(realname)s 摘è¦, å· %(volume)d, å‘布 %(issue)d" @@ -8255,6 +8414,7 @@ msgid "End of " msgstr "结æŸ" #: Mailman/ListAdmin.py:309 +#, fuzzy msgid "Posting of your message titled \"%(subject)s\"" msgstr "您以标题 \"%(subject)s\" å‘布的信件" @@ -8267,6 +8427,7 @@ msgid "Forward of moderated message" msgstr "被暂存信件的转å‘" #: Mailman/ListAdmin.py:405 +#, fuzzy msgid "New subscription request to list %(realname)s from %(addr)s" msgstr "æ¥è‡ª %(addr)s 的对 %(realname)s 列表的订阅请求" @@ -8280,6 +8441,7 @@ msgid "via admin approval" msgstr "继续等待批准" #: Mailman/ListAdmin.py:466 +#, fuzzy msgid "New unsubscription request from %(realname)s by %(addr)s" msgstr "æ¥è‡ª %(addr)s 的对 %(realname)s 列表的退订请求" @@ -8292,10 +8454,12 @@ msgid "Original Message" msgstr "原文" #: Mailman/ListAdmin.py:526 +#, fuzzy msgid "Request to mailing list %(realname)s rejected" msgstr "对于 %(realname)s 邮件列表的请求被拒ç»" #: Mailman/MTA/Manual.py:66 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been created via the through-the-web\n" "interface. In order to complete the activation of this mailing list, the\n" @@ -8320,14 +8484,17 @@ msgstr "" "并添加如下行内容,然åŽè¿è¡Œnewaliases程åºï¼š\n" #: Mailman/MTA/Manual.py:82 +#, fuzzy msgid "## %(listname)s mailing list" msgstr "## %(listname)s 邮件列表" #: Mailman/MTA/Manual.py:99 +#, fuzzy msgid "Mailing list creation request for list %(listname)s" msgstr "邮件列表 %(listname)s 创建请求" #: Mailman/MTA/Manual.py:113 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been removed via the through-the-web\n" "interface. In order to complete the de-activation of this mailing list, " @@ -8343,6 +8510,7 @@ msgstr "" "以下为需è¦åˆ é™¤çš„/etc/aliases 相关项:\n" #: Mailman/MTA/Manual.py:123 +#, fuzzy msgid "" "\n" "To finish removing your mailing list, you must edit your /etc/aliases (or\n" @@ -8358,14 +8526,17 @@ msgstr "" "## %(listname)s 邮件列表" #: Mailman/MTA/Manual.py:142 +#, fuzzy msgid "Mailing list removal request for list %(listname)s" msgstr "对邮件列表 %(listname)s 的删除请求" #: Mailman/MTA/Postfix.py:442 +#, fuzzy msgid "checking permissions on %(file)s" msgstr "查看 %(file)s çš„æƒé™" #: Mailman/MTA/Postfix.py:452 +#, fuzzy msgid "%(file)s permissions must be 0664 (got %(octmode)s)" msgstr "%(file)s çš„æƒé™å¿…须是 0664 (获得 %(octmode)s)" @@ -8379,34 +8550,42 @@ msgid "(fixing)" msgstr "(固定)" #: Mailman/MTA/Postfix.py:470 +#, fuzzy msgid "checking ownership of %(dbfile)s" msgstr "查看 %(dbfile)s 的所有者" #: Mailman/MTA/Postfix.py:478 +#, fuzzy msgid "%(dbfile)s owned by %(owner)s (must be owned by %(user)s" msgstr "%(dbfile)s 为 %(owner)s 所有(必须为 %(user)s 所有" #: Mailman/MTA/Postfix.py:491 +#, fuzzy msgid "%(dbfile)s permissions must be 0664 (got %(octmode)s)" msgstr "%(dbfile)s çš„æƒé™å¿…须是 0664 (获得 %(octmode)s)" #: Mailman/MailList.py:219 +#, fuzzy msgid "Your confirmation is required to join the %(listname)s mailing list" msgstr "您需è¦å¯¹åŠ å…¥ %(listname)s 邮件列表进行确认" #: Mailman/MailList.py:230 +#, fuzzy msgid "Your confirmation is required to leave the %(listname)s mailing list" msgstr "您需è¦å¯¹é€€å‡º %(listname)s 邮件列表进行确认" #: Mailman/MailList.py:999 Mailman/MailList.py:1492 +#, fuzzy msgid " from %(remote)s" msgstr "æ¥è‡ª %(remote)s" #: Mailman/MailList.py:1043 +#, fuzzy msgid "subscriptions to %(realname)s require moderator approval" msgstr "对 %(realname)s 的订阅请求需è¦åˆ—表主æŒè€…批准" #: Mailman/MailList.py:1125 bin/add_members:299 +#, fuzzy msgid "%(realname)s subscription notification" msgstr "%(realname)s 订阅通知" @@ -8415,6 +8594,7 @@ msgid "unsubscriptions require moderator approval" msgstr "退订需è¦åˆ—表主æŒäººæ‰¹å‡†" #: Mailman/MailList.py:1166 +#, fuzzy msgid "%(realname)s unsubscribe notification" msgstr "%(realname)s 退订通知" @@ -8434,6 +8614,7 @@ msgid "via web confirmation" msgstr "错误的验è¯å­—符串" #: Mailman/MailList.py:1396 +#, fuzzy msgid "subscriptions to %(name)s require administrator approval" msgstr "%(name)s 的订阅需è¦ç®¡ç†å‘˜æ‰¹å‡†" @@ -8452,6 +8633,7 @@ msgid "Last autoresponse notification for today" msgstr "本日最åŽä¸€æ¡è‡ªåŠ¨å›žå¤é€šçŸ¥" #: Mailman/Queue/BounceRunner.py:360 +#, fuzzy msgid "" "The attached message was received as a bounce, but either the bounce format\n" "was not recognized, or no member addresses could be extracted from it. " @@ -8538,6 +8720,7 @@ msgid "Original message suppressed by Mailman site configuration\n" msgstr "" #: Mailman/htmlformat.py:675 +#, fuzzy msgid "Delivered by Mailman
                  version %(version)s" msgstr "ç”± Mailman
                  %(version)s 投递" @@ -8626,6 +8809,7 @@ msgid "Server Local Time" msgstr "æœåŠ¡å™¨æœ¬åœ°æ—¶é—´" #: Mailman/i18n.py:180 +#, fuzzy msgid "" "%(wday)s %(mon)s %(day)2i %(hh)02i:%(mm)02i:%(ss)02i %(tzname)s %(year)04i" msgstr "" @@ -8730,6 +8914,7 @@ msgstr "" "-rå’Œ-d选项您必须至少C指定一个.最多å¯ä»¥æŒ‡å®šä¸€ä¸ªæ–‡ä»¶ä¸º`-'.\n" #: bin/add_members:162 bin/add_members:172 +#, fuzzy msgid "Already a member: %(member)s" msgstr "å·²ç»æ˜¯æˆå‘˜ï¼š%(member)s" @@ -8738,10 +8923,12 @@ msgid "Bad/Invalid email address: blank line" msgstr "错误/ä¸åˆæ³•的邮件地å€: 空行" #: bin/add_members:180 +#, fuzzy msgid "Bad/Invalid email address: %(member)s" msgstr "错误/ä¸åˆæ³•的邮件地å€ï¼š %(member)s" #: bin/add_members:182 +#, fuzzy msgid "Hostile address (illegal characters): %(member)s" msgstr "æ¶æ„地å€(éžæ³•字符): %(member)s" @@ -8751,14 +8938,17 @@ msgid "Invited: %(member)s" msgstr "已订阅: %(member)s" #: bin/add_members:187 +#, fuzzy msgid "Subscribed: %(member)s" msgstr "已订阅: %(member)s" #: bin/add_members:237 +#, fuzzy msgid "Bad argument to -w/--welcome-msg: %(arg)s" msgstr "-w/--welcome-msgé€‰é¡¹çš„é”™è¯¯å‚æ•°: %(arg)s " #: bin/add_members:244 +#, fuzzy msgid "Bad argument to -a/--admin-notify: %(arg)s" msgstr "-a/--admin-notifyé€‰é¡¹çš„é”™è¯¯å‚æ•°: %(arg)s" @@ -8773,6 +8963,7 @@ msgstr "" #: bin/add_members:261 bin/config_list:110 bin/export.py:271 bin/find_member:97 #: bin/inject:91 bin/list_admins:90 bin/list_members:252 bin/sync_members:222 #: cron/bumpdigests:86 +#, fuzzy msgid "No such list: %(listname)s" msgstr "没有此列表: %(listname)s" @@ -8783,6 +8974,7 @@ msgid "Nothing to do." msgstr "什么也ä¸éœ€è¦åš." #: bin/arch:19 +#, fuzzy msgid "" "Rebuild a list's archive.\n" "\n" @@ -8870,6 +9062,7 @@ msgid "listname is required" msgstr "需è¦åˆ—表å" #: bin/arch:143 bin/change_pw:107 bin/config_list:257 +#, fuzzy msgid "" "No such list \"%(listname)s\"\n" "%(e)s" @@ -8878,10 +9071,12 @@ msgstr "" "%(e)s" #: bin/arch:168 +#, fuzzy msgid "Cannot open mbox file %(mbox)s: %(msg)s" msgstr "无法打开mbox文件 %(mbox)s: %(msg)s" #: bin/b4b5-archfix:19 +#, fuzzy msgid "" "Fix the MM2.1b4 archives.\n" "\n" @@ -9014,6 +9209,7 @@ msgstr "" " æ‰“å°æ­¤å¸®åŠ©ä¿¡æ¯ç„¶åŽé€€å‡ºã€‚\n" #: bin/change_pw:145 +#, fuzzy msgid "Bad arguments: %(strargs)s" msgstr "é”™è¯¯çš„å‚æ•°ï¼š %(strargs)s" @@ -9022,14 +9218,17 @@ msgid "Empty list passwords are not allowed" msgstr "列表å£ä»¤ä¸å¾—为空" #: bin/change_pw:181 +#, fuzzy msgid "New %(listname)s password: %(notifypassword)s" msgstr "%(listname)s的新å£ä»¤ï¼š %(notifypassword)s" #: bin/change_pw:190 +#, fuzzy msgid "Your new %(listname)s list password" msgstr "您的 %(listname)s列表的新å£ä»¤" #: bin/change_pw:191 +#, fuzzy msgid "" "The site administrator at %(hostname)s has changed the password for your\n" "mailing list %(listname)s. It is now\n" @@ -9054,6 +9253,7 @@ msgstr "" " %(adminurl)s\n" #: bin/check_db:19 +#, fuzzy msgid "" "Check a list's config database file for integrity.\n" "\n" @@ -9130,10 +9330,12 @@ msgid "List:" msgstr "列表:" #: bin/check_db:148 +#, fuzzy msgid " %(file)s: okay" msgstr " %(file)s: 完好" #: bin/check_perms:20 +#, fuzzy msgid "" "Check the permissions for the Mailman installation.\n" "\n" @@ -9152,42 +9354,52 @@ msgstr "" "指定-v选项将导致冗余输出。\n" #: bin/check_perms:110 +#, fuzzy msgid " checking gid and mode for %(path)s" msgstr " 为 %(path)s 检查gidå’Œè®¸å¯æƒ" #: bin/check_perms:122 +#, fuzzy msgid "%(path)s bad group (has: %(groupname)s, expected %(MAILMAN_GROUP)s)" msgstr "%(path)s所属组错误(当å‰ç»„: %(groupname)s, 期望值为 %(MAILMAN_GROUP)s)" #: bin/check_perms:151 +#, fuzzy msgid "directory permissions must be %(octperms)s: %(path)s" msgstr "ç›®å½•è®¸å¯æƒå¿…须是 %(octperms)s: %(path)s" #: bin/check_perms:160 +#, fuzzy msgid "source perms must be %(octperms)s: %(path)s" msgstr "æºè®¸å¯æƒå¿…须是 %(octperms)s: %(path)s" #: bin/check_perms:171 +#, fuzzy msgid "article db files must be %(octperms)s: %(path)s" msgstr "文章数æ®åº“文件必须是 %(octperms)s: %(path)s" #: bin/check_perms:183 +#, fuzzy msgid "checking mode for %(prefix)s" msgstr "检查 %(prefix)s çš„è®¸å¯æƒ" #: bin/check_perms:193 +#, fuzzy msgid "WARNING: directory does not exist: %(d)s" msgstr "警告:目录ä¸å­˜åœ¨ï¼š %(d)s" #: bin/check_perms:197 +#, fuzzy msgid "directory must be at least 02775: %(d)s" msgstr "ç›®å½•è®¸å¯æƒå¿…须至少为02775: %(d)s" #: bin/check_perms:209 +#, fuzzy msgid "checking perms on %(private)s" msgstr "检查 %(private)s çš„è®¸å¯æƒ" #: bin/check_perms:214 +#, fuzzy msgid "%(private)s must not be other-readable" msgstr "%(private)sä¸èƒ½æ˜¯å…¶ä»–人å¯è¯»(other-readable)" @@ -9205,6 +9417,7 @@ msgid "mbox file must be at least 0660:" msgstr "mboxæ–‡ä»¶è®¸å¯æƒå¿…须至少为0660:" #: bin/check_perms:263 +#, fuzzy msgid "%(dbdir)s \"other\" perms must be 000" msgstr "%(dbdir)sçš„\"其它\"è®¸å¯æƒå¿…须为000" @@ -9213,26 +9426,32 @@ msgid "checking cgi-bin permissions" msgstr "检查cgi-binè®¸å¯æƒ" #: bin/check_perms:278 +#, fuzzy msgid " checking set-gid for %(path)s" msgstr " 检查 %(path)s çš„set-gidè®¸å¯æƒ" #: bin/check_perms:282 +#, fuzzy msgid "%(path)s must be set-gid" msgstr "%(path)s必须是set-gidçš„" #: bin/check_perms:292 +#, fuzzy msgid "checking set-gid for %(wrapper)s" msgstr "检查 %(wrapper)s çš„set-gidè®¸å¯æƒ" #: bin/check_perms:296 +#, fuzzy msgid "%(wrapper)s must be set-gid" msgstr "%(wrapper)s必须是set-gidçš„" #: bin/check_perms:306 +#, fuzzy msgid "checking permissions on %(pwfile)s" msgstr "检查 %(pwfile)s çš„è®¸å¯æƒ" #: bin/check_perms:315 +#, fuzzy msgid "%(pwfile)s permissions must be exactly 0640 (got %(octmode)s)" msgstr "%(pwfile)s çš„è®¸å¯æƒå¿…须是0640(现在是 %(octmode)s)" @@ -9241,10 +9460,12 @@ msgid "checking permissions on list data" msgstr "检查列表数æ®çš„è®¸å¯æƒ" #: bin/check_perms:348 +#, fuzzy msgid " checking permissions on: %(path)s" msgstr " 检查 %(path)sçš„è®¸å¯æƒ" #: bin/check_perms:356 +#, fuzzy msgid "file permissions must be at least 660: %(path)s" msgstr "æ–‡ä»¶è®¸å¯æƒè‡³å°‘是660: %(path)s" @@ -9328,6 +9549,7 @@ msgid "Unix-From line changed: %(lineno)d" msgstr "Unix-From行被改å˜ï¼š %(lineno)d" #: bin/cleanarch:111 +#, fuzzy msgid "Bad status number: %(arg)s" msgstr "错误的状æ€å·ï¼š %(arg)s" @@ -9471,10 +9693,12 @@ msgid " original address removed:" msgstr " æºåœ°å€è¢«ç§»é™¤ï¼š" #: bin/clone_member:202 +#, fuzzy msgid "Not a valid email address: %(toaddr)s" msgstr "éžæ³•email地å€ï¼š %(toaddr)s" #: bin/clone_member:215 +#, fuzzy msgid "" "Error opening list \"%(listname)s\", skipping.\n" "%(e)s" @@ -9598,22 +9822,27 @@ msgid "legal values are:" msgstr "åˆæ³•的值:" #: bin/config_list:270 +#, fuzzy msgid "attribute \"%(k)s\" ignored" msgstr "属性\"%(k)s\"被忽略" #: bin/config_list:273 +#, fuzzy msgid "attribute \"%(k)s\" changed" msgstr "属性\"%(k)s\"被修改" #: bin/config_list:279 +#, fuzzy msgid "Non-standard property restored: %(k)s" msgstr "éžæ ‡å‡†å±žæ€§å¤ä½ï¼š %(k)s" #: bin/config_list:288 +#, fuzzy msgid "Invalid value for property: %(k)s" msgstr "å±žæ€§å€¼éžæ³•: %(k)s" #: bin/config_list:291 +#, fuzzy msgid "Bad email address for option %(k)s: %(v)s" msgstr "选项 %(k)s çš„email地å€é”™è¯¯ï¼š %(v)s" @@ -9678,18 +9907,22 @@ msgstr "" " 䏿‰“å°çжæ€ä¿¡æ¯ã€‚\n" #: bin/discard:94 +#, fuzzy msgid "Ignoring non-held message: %(f)s" msgstr "忽略未被ä¿å­˜çš„信件: %(f)s" #: bin/discard:100 +#, fuzzy msgid "Ignoring held msg w/bad id: %(f)s" msgstr "忽略ä¿å­˜çš„ä¿¡ä»¶w/bad id: %(f)s" #: bin/discard:112 +#, fuzzy msgid "Discarded held msg #%(id)s for list %(listname)s" msgstr "丢弃列表 %(listname)s 中ä¿å­˜çš„ä¿¡ä»¶ #%(id)s" #: bin/dumpdb:19 +#, fuzzy msgid "" "Dump the contents of any Mailman `database' file.\n" "\n" @@ -9742,8 +9975,8 @@ msgstr "" "unpickled\n" " 表示时å分有用。以'python -i bin/dumpdb '的形å¼ä½¿ç”¨éžå¸¸æœ‰ç”¨ã€‚" "è¿™\n" -" æ ·çš„è¯ï¼Œthe root of the tree will be left in a global called \"msg" -"\"。\n" +" æ ·çš„è¯ï¼Œthe root of the tree will be left in a global called " +"\"msg\"。\n" "\n" " --help/-h\n" " æ‰“å°æ­¤å¸®åŠ©ä¿¡æ¯ç„¶åŽé€€å‡º\n" @@ -9759,6 +9992,7 @@ msgid "No filename given." msgstr "没有给出文件å" #: bin/dumpdb:108 +#, fuzzy msgid "Bad arguments: %(pargs)s" msgstr "傿•°é”™è¯¯: %(pargs)s" @@ -9777,6 +10011,7 @@ msgid "[----- end %(typename)s file -----]" msgstr "[----- end %(typename)s file -----]" #: bin/dumpdb:142 +#, fuzzy msgid "<----- start object %(cnt)s ----->" msgstr "<----- start object %(cnt)s ----->" @@ -9978,6 +10213,7 @@ msgid "Setting web_page_url to: %(web_page_url)s" msgstr "设置web_page_url为: %(web_page_url)s" #: bin/fix_url.py:88 +#, fuzzy msgid "Setting host_name to: %(mailhost)s" msgstr "设置host_name为: %(mailhost)s" @@ -10065,6 +10301,7 @@ msgstr "" "filenameæ˜¯è¦æ’å…¥çš„æ–‡æœ¬ä¿¡æ¯æ–‡ä»¶ã€‚如果çœç•¥çš„è¯ï¼Œå°†ä½¿ç”¨æ ‡å‡†è¾“入。\n" #: bin/inject:84 +#, fuzzy msgid "Bad queue directory: %(qdir)s" msgstr "队列目录错误: %(qdir)s" @@ -10073,6 +10310,7 @@ msgid "A list name is required" msgstr "éœ€è¦æŒ‡å®šä¸€ä¸ªåˆ—表å" #: bin/list_admins:20 +#, fuzzy msgid "" "List all the owners of a mailing list.\n" "\n" @@ -10117,6 +10355,7 @@ msgstr "" "'listname'是è¦åˆ—出属主的邮件列表å。å¯ä»¥åœ¨å‘½ä»¤è¡Œä¸ŠæŒ‡å®šå¤šä¸ªåˆ—表。\n" #: bin/list_admins:97 +#, fuzzy msgid "List: %(listname)s, \tOwners: %(owners)s" msgstr "列表: %(listname)s, \t属主: %(owners)s" @@ -10263,8 +10502,8 @@ msgstr "" " --nomail[=why] / -n [why]\n" " åˆ—å‡ºç¦æ­¢æŠ•递的æˆå‘˜ã€‚å¯é€‰çš„傿•°å¯ä»¥æ˜¯\"byadmin\", \"byuser\", " "\"bybounce\",\n" -" 或者\"unknown\"ï¼Œå°†ä»…åˆ—å‡ºå› ç›¸åº”åŽŸå› ç¦æ­¢æŠ•递的æˆå‘˜ã€‚也å¯ä»¥æ˜¯\"enabled" -"\",这样\n" +" 或者\"unknown\"ï¼Œå°†ä»…åˆ—å‡ºå› ç›¸åº”åŽŸå› ç¦æ­¢æŠ•递的æˆå‘˜ã€‚也å¯ä»¥æ˜¯" +"\"enabled\",这样\n" " å°†ä»…åˆ—å‡ºæœªç¦æ­¢æŠ•递的æˆå‘˜ã€‚\n" "\n" " --fullnames / -f\n" @@ -10293,10 +10532,12 @@ msgstr "" "给出地å€çжæ€çš„æŒ‡ç¤ºã€‚\n" #: bin/list_members:198 +#, fuzzy msgid "Bad --nomail option: %(why)s" msgstr "错误的 --nomail 选项: %(why)s" #: bin/list_members:209 +#, fuzzy msgid "Bad --digest option: %(kind)s" msgstr "错误的 --digest 选项: %(kind)s" @@ -10310,6 +10551,7 @@ msgid "Could not open file for writing:" msgstr "æ— æ³•ä»¥å†™æ–¹å¼æ‰“开文件:" #: bin/list_owners:20 +#, fuzzy msgid "" "List the owners of a mailing list, or all mailing lists.\n" "\n" @@ -10360,6 +10602,7 @@ msgid "" msgstr "" #: bin/mailmanctl:20 +#, fuzzy msgid "" "Primary start-up and shutdown script for Mailman's qrunner daemon.\n" "\n" @@ -10532,6 +10775,7 @@ msgstr "" "们。\n" #: bin/mailmanctl:152 +#, fuzzy msgid "PID unreadable in: %(pidfile)s" msgstr "在: %(pidfile)s 中找ä¸åˆ°PID" @@ -10540,6 +10784,7 @@ msgid "Is qrunner even running?" msgstr "队列管ç†å™¨æ­£åœ¨è¿è¡Œå—?" #: bin/mailmanctl:160 +#, fuzzy msgid "No child with pid: %(pid)s" msgstr "进程: %(pid)s 没有å­è¿›ç¨‹" @@ -10564,6 +10809,7 @@ msgstr "" "å¯ä»¥å°è¯•使用 -s flag æ¥é‡å¯mailmanctl。\n" #: bin/mailmanctl:233 +#, fuzzy msgid "" "The master qrunner lock could not be acquired, because it appears as if " "some\n" @@ -10587,10 +10833,12 @@ msgstr "" "正在退出。" #: bin/mailmanctl:280 cron/mailpasswds:119 +#, fuzzy msgid "Site list is missing: %(sitelistname)s" msgstr "缺少站点列表: %(sitelistname)s" #: bin/mailmanctl:305 +#, fuzzy msgid "Run this program as root or as the %(name)s user, or use -u." msgstr "用root用户,或 %(name)s用户身份æ¥è¿è¡Œæ­¤ç¨‹åºï¼Œæˆ–者使用-u。" @@ -10599,6 +10847,7 @@ msgid "No command given." msgstr "没有æä¾›å‘½ä»¤ã€‚" #: bin/mailmanctl:339 +#, fuzzy msgid "Bad command: %(command)s" msgstr "错误命令: %(command)s" @@ -10623,6 +10872,7 @@ msgid "Starting Mailman's master qrunner." msgstr "正在å¯åЍMailman的主队列管ç†å™¨" #: bin/mmsitepass:19 +#, fuzzy msgid "" "Set the site password, prompting from the terminal.\n" "\n" @@ -10675,6 +10925,7 @@ msgid "list creator" msgstr "列表创建者" #: bin/mmsitepass:86 +#, fuzzy msgid "New %(pwdesc)s password: " msgstr "æ–° %(pwdesc)s å£ä»¤" @@ -10920,6 +11171,7 @@ msgstr "" "注æ„列表å强制为å°å†™å­—æ¯ã€‚\n" #: bin/newlist:162 +#, fuzzy msgid "Unknown language: %(lang)s" msgstr "未知的语言: %(lang)s" @@ -10932,6 +11184,7 @@ msgid "Enter the email of the person running the list: " msgstr "输入è¿è¡Œåˆ—表的人的email:" #: bin/newlist:193 +#, fuzzy msgid "Initial %(listname)s password: " msgstr "åˆå§‹çš„ %(listname)s的密ç ï¼š" @@ -10941,11 +11194,12 @@ msgstr "列表密ç ä¸èƒ½ä¸ºç©º" #: bin/newlist:220 msgid "" -" - owner addresses need to be fully-qualified names like \"owner@example.com" -"\", not just \"owner\"." +" - owner addresses need to be fully-qualified names like \"owner@example." +"com\", not just \"owner\"." msgstr "" #: bin/newlist:244 +#, fuzzy msgid "Hit enter to notify %(listname)s owner..." msgstr "å•击回车æ¥é€šçŸ¥ %(listname)s的所有者..." @@ -11064,6 +11318,7 @@ msgstr "" "个。\n" #: bin/qrunner:179 +#, fuzzy msgid "%(name)s runs the %(runnername)s qrunner" msgstr "%(name)s è¿è¡Œ %(runnername)s qrunner" @@ -11076,6 +11331,7 @@ msgid "No runner name given." msgstr "没æä¾›ç®¡ç†å™¨çš„å字。" #: bin/rb-archfix:21 +#, fuzzy msgid "" "Reduce disk space usage for Pipermail archives.\n" "\n" @@ -11212,18 +11468,22 @@ msgstr "" "\n" #: bin/remove_members:156 +#, fuzzy msgid "Could not open file for reading: %(filename)s." msgstr "ä¸èƒ½è¯»å–文件: %(filename)s。" #: bin/remove_members:163 +#, fuzzy msgid "Error opening list %(listname)s... skipping." msgstr "打开列表 %(listname)s 时出错... 正在跳过。" #: bin/remove_members:173 +#, fuzzy msgid "No such member: %(addr)s" msgstr "没有这个æˆå‘˜: %(addr)s" #: bin/remove_members:178 +#, fuzzy msgid "User `%(addr)s' removed from list: %(listname)s." msgstr "用户 %(addr)s 已从列表: %(listname)s中删除。" @@ -11261,10 +11521,12 @@ msgstr "" " 输出脚本正在干什么\n" #: bin/reset_pw.py:77 +#, fuzzy msgid "Changing passwords for list: %(listname)s" msgstr "为列表 %(listname)s改å˜å¯†ç " #: bin/reset_pw.py:83 +#, fuzzy msgid "New password for member %(member)40s: %(randompw)s" msgstr "æˆå‘˜ %(member)40s的新密ç ï¼š %(randompw)s" @@ -11308,18 +11570,22 @@ msgstr "" "\n" #: bin/rmlist:73 bin/rmlist:76 +#, fuzzy msgid "Removing %(msg)s" msgstr "正在删除 %(msg)s" #: bin/rmlist:81 +#, fuzzy msgid "%(listname)s %(msg)s not found as %(filename)s" msgstr "æ²¡æœ‰æ‰¾åˆ°å« %(filename)sçš„ %(listname)s %(msg)s " #: bin/rmlist:105 +#, fuzzy msgid "No such list (or list already deleted): %(listname)s" msgstr "没有这个列表(或者列表已ç»è¢«åˆ é™¤äº†): %(listname)s" #: bin/rmlist:108 +#, fuzzy msgid "No such list: %(listname)s. Removing its residual archives." msgstr "没有这个列表: %(listname)s。正在删除它的残留的文件。" @@ -11377,6 +11643,7 @@ msgstr "" "示例: show_qfiles qfiles/shunt/*.pck\n" #: bin/sync_members:19 +#, fuzzy msgid "" "Synchronize a mailing list's membership with a flat file.\n" "\n" @@ -11504,6 +11771,7 @@ msgstr "" " 必需.æŒ‡å®šäº†åŒæ­¥çš„列表.\n" #: bin/sync_members:115 +#, fuzzy msgid "Bad choice: %(yesno)s" msgstr "错误的选择: %(yesno)s" @@ -11520,6 +11788,7 @@ msgid "No argument to -f given" msgstr "没有给出-fçš„å‚æ•°" #: bin/sync_members:172 +#, fuzzy msgid "Illegal option: %(opt)s" msgstr "无效的选项: %(opt)s" @@ -11532,6 +11801,7 @@ msgid "Must have a listname and a filename" msgstr "必须有一个列表å和文件å" #: bin/sync_members:191 +#, fuzzy msgid "Cannot read address file: %(filename)s: %(msg)s" msgstr "æ— æ³•è¯»å‡ºåœ°å€æ–‡ä»¶: %(filename)s: %(msg)s" @@ -11548,14 +11818,17 @@ msgid "You must fix the preceding invalid addresses first." msgstr "您必须先修正上述的无效地å€." #: bin/sync_members:264 +#, fuzzy msgid "Added : %(s)s" msgstr "加入 : %(s)s" #: bin/sync_members:289 +#, fuzzy msgid "Removed: %(s)s" msgstr "移除 : %(s)s" #: bin/transcheck:19 +#, fuzzy msgid "" "\n" "Check a given Mailman translation, making sure that variables and\n" @@ -11662,6 +11935,7 @@ msgstr "" "qfiles/shunt.\n" #: bin/unshunt:85 +#, fuzzy msgid "" "Cannot unshunt message %(filebase)s, skipping:\n" "%(e)s" @@ -11670,6 +11944,7 @@ msgstr "" "%(e)s" #: bin/update:20 +#, fuzzy msgid "" "Perform all necessary upgrades.\n" "\n" @@ -11705,14 +11980,17 @@ msgstr "" "它å¯ä»¥è¯†åˆ«åˆ°1.0b4 (?)版本.\n" #: bin/update:107 +#, fuzzy msgid "Fixing language templates: %(listname)s" msgstr "修正语言模æ¿: %(listname)s" #: bin/update:196 bin/update:711 +#, fuzzy msgid "WARNING: could not acquire lock for list: %(listname)s" msgstr "警告: ä¸èƒ½èŽ·å¾—åˆ—è¡¨ %(listname)s çš„é”" #: bin/update:215 +#, fuzzy msgid "Resetting %(n)s BYBOUNCEs disabled addrs with no bounce info" msgstr "é‡è®¾ %(n)s BYBOUNCEs 使没有退订信æ¯çš„地å€å¤±æ•ˆ" @@ -11729,6 +12007,7 @@ msgstr "" "b6一åŒå·¥ä½œ, 所以我将它更å为%(mbox_dir)s.tmp å¹¶ç»§ç»­." #: bin/update:255 +#, fuzzy msgid "" "\n" "%(listname)s has both public and private mbox archives. Since this list\n" @@ -11779,6 +12058,7 @@ msgid "- updating old private mbox file" msgstr "- 正在å‡çº§æ—§çš„ç§æœ‰é‚®ç®±æ–‡ä»¶" #: bin/update:295 +#, fuzzy msgid "" " unknown file in the way, moving\n" " %(o_pri_mbox_file)s\n" @@ -11795,6 +12075,7 @@ msgid "- updating old public mbox file" msgstr "- 正在å‡çº§æ—§æœ‰å…¬æœ‰é‚®ç®±æ–‡ä»¶" #: bin/update:317 +#, fuzzy msgid "" " unknown file in the way, moving\n" " %(o_pub_mbox_file)s\n" @@ -11823,18 +12104,22 @@ msgid "- %(o_tmpl)s doesn't exist, leaving untouched" msgstr "- %(o_tmpl)s ä¸å­˜åœ¨, ä¸åšä¿®æ”¹" #: bin/update:396 +#, fuzzy msgid "removing directory %(src)s and everything underneath" msgstr "删除目录%(src)s åŠå…¶ç›®å½•内所有文件" #: bin/update:399 +#, fuzzy msgid "removing %(src)s" msgstr "删除%(src)s" #: bin/update:403 +#, fuzzy msgid "Warning: couldn't remove %(src)s -- %(rest)s" msgstr "警告: 无法删除 %(src)s -- %(rest)s" #: bin/update:408 +#, fuzzy msgid "couldn't remove old file %(pyc)s -- %(rest)s" msgstr "无法删除旧文件 %(pyc)s -- %(rest)s" @@ -11848,6 +12133,7 @@ msgid "Warning! Not a directory: %(dirpath)s" msgstr "队列目录错误: %(dirpath)s" #: bin/update:530 +#, fuzzy msgid "message is unparsable: %(filebase)s" msgstr "æ¶ˆæ¯æ— æ³•è§£æž: %(filebase)s" @@ -11864,10 +12150,12 @@ msgid "Updating Mailman 2.1.4 pending.pck database" msgstr "正在å‡çº§ Mailman 2.1.4 pending.pck æ•°æ®åº“" #: bin/update:598 +#, fuzzy msgid "Ignoring bad pended data: %(key)s: %(val)s" msgstr "å¿½ç•¥é”™è¯¯çš„æœªå¤„ç†æ•°æ®: %(key)s: %(val)s" #: bin/update:614 +#, fuzzy msgid "WARNING: Ignoring duplicate pending ID: %(id)s." msgstr "警告: 忽略多é‡çš„æœªå¤„ç†ID: %(id)s" @@ -11892,6 +12180,7 @@ msgid "done" msgstr "完æˆ" #: bin/update:691 +#, fuzzy msgid "Updating mailing list: %(listname)s" msgstr "正在å‡çº§é‚®ä»¶åˆ—表: %(listname)s" @@ -11952,6 +12241,7 @@ msgid "No updates are necessary." msgstr "ä¸éœ€è¦å‡çº§" #: bin/update:796 +#, fuzzy msgid "" "Downgrade detected, from version %(hexlversion)s to version %(hextversion)s\n" "This is probably not safe.\n" @@ -11962,10 +12252,12 @@ msgstr "" "正在退出." #: bin/update:801 +#, fuzzy msgid "Upgrading from version %(hexlversion)s to %(hextversion)s" msgstr "正在å‡çº§,从版本%(hexlversion)s 到 %(hextversion)s" #: bin/update:810 +#, fuzzy msgid "" "\n" "ERROR:\n" @@ -12229,6 +12521,7 @@ msgstr "" " " #: bin/withlist:175 +#, fuzzy msgid "Unlocking (but not saving) list: %(listname)s" msgstr "è§£é”(但ä¸ä¿å­˜)列表: %(listname)s" @@ -12237,6 +12530,7 @@ msgid "Finalizing" msgstr "完æˆ" #: bin/withlist:188 +#, fuzzy msgid "Loading list %(listname)s" msgstr "加载列表 %(listname)s" @@ -12249,6 +12543,7 @@ msgid "(unlocked)" msgstr "(已解é”)" #: bin/withlist:197 +#, fuzzy msgid "Unknown list: %(listname)s" msgstr "未知的列表: %(listname)s" @@ -12261,18 +12556,22 @@ msgid "--all requires --run" msgstr "--all需è¦--run" #: bin/withlist:266 +#, fuzzy msgid "Importing %(module)s..." msgstr "导入 %(module)s..." #: bin/withlist:270 +#, fuzzy msgid "Running %(module)s.%(callable)s()..." msgstr "è¿è¡Œ %(module)s.%(callable)s()..." #: bin/withlist:291 +#, fuzzy msgid "The variable `m' is the %(listname)s MailList instance" msgstr "å˜é‡'m'是 %(listname)s çš„MailList实例" #: cron/bumpdigests:19 +#, fuzzy msgid "" "Increment the digest volume number and reset the digest number to one.\n" "\n" @@ -12300,6 +12599,7 @@ msgstr "" "ç†ã€‚\n" #: cron/checkdbs:20 +#, fuzzy msgid "" "Check for pending admin requests and mail the list owners if necessary.\n" "\n" @@ -12381,6 +12681,7 @@ msgid "" msgstr "" #: cron/disabled:20 +#, fuzzy msgid "" "Process disabled members, recommended once per day.\n" "\n" @@ -12497,6 +12798,7 @@ msgstr "" "\n" #: cron/mailpasswds:19 +#, fuzzy msgid "" "Send password reminders for all lists to all users.\n" "\n" @@ -12549,10 +12851,12 @@ msgid "Password // URL" msgstr "å£ä»¤ // URL" #: cron/mailpasswds:222 +#, fuzzy msgid "%(host)s mailing list memberships reminder" msgstr "%(host)s邮件列表æˆå‘˜èº«ä»½æç¤º" #: cron/nightly_gzip:19 +#, fuzzy msgid "" "Re-generate the Pipermail gzip'd archive flat files.\n" "\n" diff --git a/messages/zh_TW/LC_MESSAGES/mailman.po b/messages/zh_TW/LC_MESSAGES/mailman.po index ee10b686..7a661c8b 100755 --- a/messages/zh_TW/LC_MESSAGES/mailman.po +++ b/messages/zh_TW/LC_MESSAGES/mailman.po @@ -68,10 +68,12 @@ msgid "

                  Currently, there are no archives.

                  " msgstr "

                  ç›®å‰æ²’有歸檔。

                  " #: Mailman/Archiver/HyperArch.py:821 +#, fuzzy msgid "Gzip'd Text%(sz)s" msgstr "Gzip 壓éŽçš„ %(sz)s" #: Mailman/Archiver/HyperArch.py:826 +#, fuzzy msgid "Text%(sz)s" msgstr "文字 %(sz)s" @@ -144,18 +146,22 @@ msgid "Third" msgstr "第三" #: Mailman/Archiver/HyperArch.py:938 +#, fuzzy msgid "%(ord)s quarter %(year)i" msgstr "%(year)iå¹´%(ord)så­£" #: Mailman/Archiver/HyperArch.py:945 +#, fuzzy msgid "%(month)s %(year)i" msgstr "%(year)iå¹´%(month)s" #: Mailman/Archiver/HyperArch.py:950 +#, fuzzy msgid "The Week Of Monday %(day)i %(month)s %(year)i" msgstr "%(year)iå¹´%(month)s%(day)i日(星期一)該週" #: Mailman/Archiver/HyperArch.py:954 +#, fuzzy msgid "%(day)i %(month)s %(year)i" msgstr "%(year)iå¹´%(month)s%(day)iæ—¥" @@ -164,10 +170,12 @@ msgid "Computing threaded index\n" msgstr "正在計算討論串的索引\n" #: Mailman/Archiver/HyperArch.py:1319 +#, fuzzy msgid "Updating HTML for article %(seq)s" msgstr "正在更新 %(seq)s 號文件的 HTML" #: Mailman/Archiver/HyperArch.py:1326 +#, fuzzy msgid "article file %(filename)s is missing!" msgstr "文件檔 %(filename)s ä¸è¦‹äº†ï¼" @@ -184,6 +192,7 @@ msgid "Pickling archive state into " msgstr "æ­£è¦æŠŠæ­¸æª”çš„ç‹€æ…‹å­˜åˆ°" #: Mailman/Archiver/pipermail.py:453 +#, fuzzy msgid "Updating index files for archive [%(archive)s]" msgstr "正在更新歸檔 [%(archive)s] 的索引" @@ -192,6 +201,7 @@ msgid " Thread" msgstr "討論串" #: Mailman/Archiver/pipermail.py:597 +#, fuzzy msgid "#%(counter)05d %(msgid)s" msgstr "#%(counter)05d %(msgid)s" @@ -229,6 +239,7 @@ msgid "disabled address" msgstr "關閉" #: Mailman/Bouncer.py:304 +#, fuzzy msgid " The last bounce received from you was dated %(date)s" msgstr "您信箱的å‰ä¸€æ¬¡é€€ä¿¡æ˜¯åœ¨ %(date)s" @@ -256,6 +267,7 @@ msgstr "論壇管ç†äºº" #: Mailman/Cgi/options.py:101 Mailman/Cgi/private.py:108 #: Mailman/Cgi/rmlist.py:75 Mailman/Cgi/roster.py:59 #: Mailman/Cgi/subscribe.py:67 +#, fuzzy msgid "No such list %(safelistname)s" msgstr "沒有%(safelistname)s這個論壇" @@ -323,6 +335,7 @@ msgstr "" "您修好這個å•題為止。%(rm)r" #: Mailman/Cgi/admin.py:268 +#, fuzzy msgid "%(hostname)s mailing lists - Admin Links" msgstr "%(hostname)s 通信論壇 - 管ç†äººç¶²é çš„連çµ" @@ -335,12 +348,14 @@ msgid "Mailman" msgstr "郵差" #: Mailman/Cgi/admin.py:310 +#, fuzzy msgid "" "

                  There currently are no publicly-advertised %(mailmanlink)s\n" " mailing lists on %(hostname)s." msgstr "

                  ç¾åœ¨åœ¨ %(hostname)s 上沒有公告任何 %(mailmanlink)s 通信論壇。" #: Mailman/Cgi/admin.py:316 +#, fuzzy msgid "" "

                  Below is the collection of publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s. Click on a list\n" @@ -354,6 +369,7 @@ msgid "right " msgstr "å°çš„" #: Mailman/Cgi/admin.py:325 +#, fuzzy msgid "" "To visit the administrators configuration page for an\n" " unadvertised list, open a URL similar to this one, but with a '/' " @@ -396,6 +412,7 @@ msgid "No valid variable name found." msgstr "找ä¸åˆ°æ­£ç¢ºçš„變數å稱。" #: Mailman/Cgi/admin.py:395 +#, fuzzy msgid "" "%(realname)s Mailing list Configuration Help\n" "
                  %(varname)s Option" @@ -404,6 +421,7 @@ msgstr "" "
                  %(varname)sé¸é …" #: Mailman/Cgi/admin.py:402 +#, fuzzy msgid "Mailman %(varname)s List Option Help" msgstr "Mailman %(varname)s 論壇é¸é …的支æ´è¨Šæ¯" @@ -421,14 +439,17 @@ msgstr "" "您也å¯ä»¥" #: Mailman/Cgi/admin.py:431 +#, fuzzy msgid "return to the %(categoryname)s options page." msgstr "返回 %(categoryname)s é¸é …ç¶²é ã€‚" #: Mailman/Cgi/admin.py:446 +#, fuzzy msgid "%(realname)s Administration (%(label)s)" msgstr "%(realname)s ç®¡ç† (%(label)s)" #: Mailman/Cgi/admin.py:447 +#, fuzzy msgid "%(realname)s mailing list administration
                  %(label)s Section" msgstr "%(realname)s 通信論壇管ç†
                  %(label)s 部分" @@ -506,6 +527,7 @@ msgid "Value" msgstr "值" #: Mailman/Cgi/admin.py:668 +#, fuzzy msgid "" "Badly formed options entry:\n" " %(record)s" @@ -604,10 +626,12 @@ msgid "Move rule down" msgstr "把è¦å‰‡ä¸‹ç§»" #: Mailman/Cgi/admin.py:870 +#, fuzzy msgid "
                  (Edit %(varname)s)" msgstr "
                  (編輯 %(varname)s)" #: Mailman/Cgi/admin.py:872 +#, fuzzy msgid "
                  (Details for %(varname)s)" msgstr "
                  (%(varname)s的細節)" @@ -647,6 +671,7 @@ msgid "(help)" msgstr "(求助)" #: Mailman/Cgi/admin.py:930 +#, fuzzy msgid "Find member %(link)s:" msgstr "尋找%(link)s訂戶:" @@ -659,10 +684,12 @@ msgid "Bad regular expression: " msgstr "ä¸è‰¯çš„æ­£å‰‡è¡¨ç¤ºå¼ï¼š" #: Mailman/Cgi/admin.py:1013 +#, fuzzy msgid "%(allcnt)s members total, %(membercnt)s shown" msgstr "共有 %(allcnt)s 個訂戶,顯示了 %(membercnt)s 個" #: Mailman/Cgi/admin.py:1016 +#, fuzzy msgid "%(allcnt)s members total" msgstr "共有 %(allcnt)s 個訂戶" @@ -831,6 +858,7 @@ msgid "" msgstr "

                  想看其他訂戶的話,點é¸ä»¥ä¸‹æ‰€åˆ—å„é ï¼š" #: Mailman/Cgi/admin.py:1221 +#, fuzzy msgid "from %(start)s to %(end)s" msgstr "從 %(start)s 到 %(end)s" @@ -966,6 +994,7 @@ msgid "Change list ownership passwords" msgstr "變更論壇管ç†äººå¯†ç¢¼" #: Mailman/Cgi/admin.py:1364 +#, fuzzy msgid "" "The list administrators are the people who have ultimate control " "over\n" @@ -1131,8 +1160,9 @@ msgid "%(schange_to)s is already a member" msgstr "已經是訂戶" #: Mailman/Cgi/admin.py:1620 +#, fuzzy msgid "%(schange_to)s matches banned pattern %(spat)s" -msgstr "" +msgstr "已經是訂戶" #: Mailman/Cgi/admin.py:1622 msgid "Address %(schange_from)s changed to %(schange_to)s" @@ -1172,6 +1202,7 @@ msgid "Not subscribed" msgstr "䏿˜¯è¨‚戶" #: Mailman/Cgi/admin.py:1773 +#, fuzzy msgid "Ignoring changes to deleted member: %(user)s" msgstr "忽略å°å·²é™¤å訂戶 %(user)s 的設定" @@ -1184,10 +1215,12 @@ msgid "Error Unsubscribing:" msgstr "退訂時出錯:" #: Mailman/Cgi/admindb.py:235 Mailman/Cgi/admindb.py:248 +#, fuzzy msgid "%(realname)s Administrative Database" msgstr "%(realname)s 行政資料庫" #: Mailman/Cgi/admindb.py:238 +#, fuzzy msgid "%(realname)s Administrative Database Results" msgstr "%(realname)s è¡Œæ”¿è³‡æ–™åº«çš„çµæžœ" @@ -1216,6 +1249,7 @@ msgid "Discard all messages marked Defer" msgstr "拋棄所有標為擱置的訊æ¯" #: Mailman/Cgi/admindb.py:298 +#, fuzzy msgid "all of %(esender)s's held messages." msgstr "%(esender)s 所有ä¿ç•™ä½çš„訊æ¯ã€‚" @@ -1236,6 +1270,7 @@ msgid "list of available mailing lists." msgstr "通信論壇列表。" #: Mailman/Cgi/admindb.py:362 +#, fuzzy msgid "You must specify a list name. Here is the %(link)s" msgstr "您必須指定論壇å稱。這裡是 %(link)s" @@ -1318,6 +1353,7 @@ msgid "The sender is now a member of this list" msgstr "寄件人ç¾åœ¨æ˜¯æœ¬è«–壇的訂戶了" #: Mailman/Cgi/admindb.py:568 +#, fuzzy msgid "Add %(esender)s to one of these sender filters:" msgstr "把 %(esender)s åŠ åˆ°é€™äº›å¯„ä»¶äººéŽæ¿¾å™¨ä¹‹ä¸­ï¼š" @@ -1338,6 +1374,7 @@ msgid "Rejects" msgstr "退回的" #: Mailman/Cgi/admindb.py:584 +#, fuzzy msgid "" "Ban %(esender)s from ever subscribing to this\n" " mailing list" @@ -1350,6 +1387,7 @@ msgid "" msgstr "點擊訊æ¯è™Ÿç¢¼å°±å¯ä»¥çœ‹åˆ°å€‹åˆ¥è¨Šæ¯ï¼Œæˆ–者您å¯ä»¥" #: Mailman/Cgi/admindb.py:591 +#, fuzzy msgid "view all messages from %(esender)s" msgstr "觀看所有 %(esender)s 寄來的信" @@ -1472,6 +1510,7 @@ msgid "" msgstr "è¦æ±‚變更的地å€å·²ç¶“é€€è¨‚äº†ï¼Œå› æ­¤é€™é …è¦æ±‚é­åˆ°å–消。" #: Mailman/Cgi/confirm.py:178 +#, fuzzy msgid "System error, bad content: %(content)s" msgstr "系統錯誤,壞內容: %(content)s" @@ -1507,6 +1546,7 @@ msgid "Confirm subscription request" msgstr "ç¢ºèªæ‚¨è¦è¨‚é–±çš„è¦æ±‚" #: Mailman/Cgi/confirm.py:259 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " subscription request to the mailing list %(listname)s. Your\n" @@ -1535,6 +1575,7 @@ msgstr "" "

                  æˆ–æŒ‰å–æ¶ˆè¨‚é–±éˆ•å¦‚æžœæ‚¨ä¸æƒ³è¨‚閱本論壇。" #: Mailman/Cgi/confirm.py:275 +#, fuzzy msgid "" "Your confirmation is required in order to continue with\n" " the subscription request to the mailing list %(listname)s.\n" @@ -1581,6 +1622,7 @@ msgid "Preferred language:" msgstr "最愛用的語言:" #: Mailman/Cgi/confirm.py:317 +#, fuzzy msgid "Subscribe to list %(listname)s" msgstr "訂閱 %(listname)s 論壇" @@ -1597,6 +1639,7 @@ msgid "Awaiting moderator approval" msgstr "等待主æŒäººæ ¸å‡†" #: Mailman/Cgi/confirm.py:386 +#, fuzzy msgid "" " You have successfully confirmed your subscription request to " "the\n" @@ -1647,6 +1690,7 @@ msgid "Subscription request confirmed" msgstr "å·²ç¢ºèªæ‚¨çš„è¨‚é–±è¦æ±‚" #: Mailman/Cgi/confirm.py:418 +#, fuzzy msgid "" " You have successfully confirmed your subscription request for\n" " \"%(addr)s\" to the %(listname)s mailing list. A separate\n" @@ -1672,6 +1716,7 @@ msgid "Unsubscription request confirmed" msgstr "確èªäº†æ‚¨çš„é€€è¨‚è¦æ±‚" #: Mailman/Cgi/confirm.py:469 +#, fuzzy msgid "" " You have successfully unsubscribed from the %(listname)s " "mailing\n" @@ -1691,6 +1736,7 @@ msgid "Not available" msgstr "無法å–å¾—" #: Mailman/Cgi/confirm.py:498 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " unsubscription request from the mailing list %(listname)s. " @@ -1753,6 +1799,7 @@ msgid "Change of address request confirmed" msgstr "變更地å€çš„è¦æ±‚已經ç²å¾—確èª" #: Mailman/Cgi/confirm.py:571 +#, fuzzy msgid "" " You have successfully changed your address on the %(listname)s\n" " mailing list from %(oldaddr)s to %(newaddr)s. " @@ -1760,8 +1807,8 @@ msgid "" " can now proceed to your membership\n" " login page." msgstr "" -"您已經把您訂閱 %(listname)s 郵éžè«–壇的地å€å¾ž %(oldaddr)s 變更為 " -"%(newaddr)s。\n" +"您已經把您訂閱 %(listname)s 郵éžè«–壇的地å€å¾ž %(oldaddr)s 變更為 " +"%(newaddr)s。\n" "您ç¾åœ¨å¯ä»¥ 連上您的登入網é ã€‚" #: Mailman/Cgi/confirm.py:583 @@ -1773,6 +1820,7 @@ msgid "globally" msgstr "整批地" #: Mailman/Cgi/confirm.py:605 +#, fuzzy msgid "" "Your confirmation is required in order to complete the\n" " change of address request for the mailing list %(listname)s. " @@ -1824,6 +1872,7 @@ msgid "Sender discarded message via web." msgstr "寄件人用 web 拋棄了這篇訊æ¯ã€‚" #: Mailman/Cgi/confirm.py:674 +#, fuzzy msgid "" "The held message with the Subject:\n" " header %(subject)s could not be found. The most " @@ -1842,6 +1891,7 @@ msgid "Posted message canceled" msgstr "å–æ¶ˆæŽ‰å·²åˆŠç™»çš„訊æ¯äº†ã€‚" #: Mailman/Cgi/confirm.py:685 +#, fuzzy msgid "" " You have successfully canceled the posting of your message with\n" " the Subject: header %(subject)s to the mailing list\n" @@ -1861,6 +1911,7 @@ msgid "" msgstr "æ‚¨è¦æ‰¾çš„ä¿ç•™è¨Šæ¯å·²ç¶“由論壇管ç†äººè™•ç†æŽ‰äº†ã€‚" #: Mailman/Cgi/confirm.py:735 +#, fuzzy msgid "" "Your confirmation is required in order to cancel the\n" " posting of your message to the mailing list %(listname)s:\n" @@ -1905,6 +1956,7 @@ msgid "Membership re-enabled." msgstr "å·²æ¢å¾©è¨‚é–±" #: Mailman/Cgi/confirm.py:798 +#, fuzzy msgid "" " You have successfully re-enabled your membership in the\n" " %(listname)s mailing list. You can now not available" msgstr "無法å–用" #: Mailman/Cgi/confirm.py:846 +#, fuzzy msgid "" "Your membership in the %(realname)s mailing list is\n" " currently disabled due to excessive bounces. Your confirmation is\n" @@ -1995,10 +2049,12 @@ msgid "administrative list overview" msgstr "è«–å£‡ç®¡ç†æ¦‚è¿°" #: Mailman/Cgi/create.py:115 +#, fuzzy msgid "List name must not include \"@\": %(safelistname)s" msgstr "論壇å稱ä¸å¯å«æœ‰\"@\": %(safelistname)s" #: Mailman/Cgi/create.py:122 +#, fuzzy msgid "List already exists: %(safelistname)s" msgstr "論壇已存在: %(safelistname)s" @@ -2030,18 +2086,22 @@ msgid "You are not authorized to create new mailing lists" msgstr "您沒有建立新郵éžè«–壇的權é™" #: Mailman/Cgi/create.py:182 +#, fuzzy msgid "Unknown virtual host: %(safehostname)s" msgstr "未知的虛擬主機:%(safehostname)s" #: Mailman/Cgi/create.py:218 bin/newlist:219 +#, fuzzy msgid "Bad owner email address: %(s)s" msgstr "䏿­£ç¢ºçš„æ“æœ‰äºº email 地å€ï¼š %(s)s" #: Mailman/Cgi/create.py:223 bin/newlist:182 bin/newlist:223 +#, fuzzy msgid "List already exists: %(listname)s" msgstr "論壇已經存在: %(listname)s" #: Mailman/Cgi/create.py:231 bin/newlist:217 +#, fuzzy msgid "Illegal list name: %(s)s" msgstr "éžæ³•的論壇å稱: %(s)s" @@ -2054,6 +2114,7 @@ msgstr "" "è«‹è¯çµ¡ç¶²ç«™ç®¡ç†äººä»¥å°‹æ±‚å”助。" #: Mailman/Cgi/create.py:273 bin/newlist:265 +#, fuzzy msgid "Your new mailing list: %(listname)s" msgstr "您的新郵éžè«–壇: %(listname)s" @@ -2062,6 +2123,7 @@ msgid "Mailing list creation results" msgstr "建立郵éžè«–å£‡çš„çµæžœ" #: Mailman/Cgi/create.py:288 +#, fuzzy msgid "" "You have successfully created the mailing list\n" " %(listname)s and notification has been sent to the list owner\n" @@ -2084,6 +2146,7 @@ msgid "Create another list" msgstr "建立å¦ä¸€å€‹è«–壇" #: Mailman/Cgi/create.py:312 +#, fuzzy msgid "Create a %(hostname)s Mailing List" msgstr "建立 %(hostname)s 上的郵éžè«–壇" @@ -2171,6 +2234,7 @@ msgstr "" "回答 是 å³å¯è®“新訂戶è¦ç™»çš„訊æ¯è‡ªå‹•交由主æŒäººæ ¸å‡†ã€‚" #: Mailman/Cgi/create.py:432 +#, fuzzy msgid "" "Initial list of supported languages.

                  Note that if you do not\n" " select at least one initial language, the list will use the server\n" @@ -2275,6 +2339,7 @@ msgid "List name is required." msgstr "需è¦è«–壇å稱。" #: Mailman/Cgi/edithtml.py:154 +#, fuzzy msgid "%(realname)s -- Edit html for %(template_info)s" msgstr "%(realname)s ï¼ ç·¨è¼¯ %(template_info)s çš„ html" @@ -2283,10 +2348,12 @@ msgid "Edit HTML : Error" msgstr "編輯 HTML : 錯誤" #: Mailman/Cgi/edithtml.py:161 +#, fuzzy msgid "%(safetemplatename)s: Invalid template" msgstr "%(safetemplatename)s:ï¼šæ¨£ç‰ˆä¸æ­£ç¢º" #: Mailman/Cgi/edithtml.py:166 Mailman/Cgi/edithtml.py:167 +#, fuzzy msgid "%(realname)s -- HTML Page Editing" msgstr "%(realname)s ï¼ HTML ç¶²é ç·¨è¼¯" @@ -2345,16 +2412,19 @@ msgid "HTML successfully updated." msgstr "HTML 已更新æˆåŠŸã€‚" #: Mailman/Cgi/listinfo.py:89 +#, fuzzy msgid "%(hostname)s Mailing Lists" msgstr "%(hostname)s 郵éžè«–壇" #: Mailman/Cgi/listinfo.py:127 +#, fuzzy msgid "" "

                  There currently are no publicly-advertised\n" " %(mailmanlink)s mailing lists on %(hostname)s." msgstr "

                  %(hostname)s 上ç¾åœ¨æ²’有公告出來的 %(mailmanlink)s 郵éžè«–壇" #: Mailman/Cgi/listinfo.py:131 +#, fuzzy msgid "" "

                  Below is a listing of all the public mailing lists on\n" " %(hostname)s. Click on a list name to get more information " @@ -2372,6 +2442,7 @@ msgid "right" msgstr "å°çš„" #: Mailman/Cgi/listinfo.py:140 +#, fuzzy msgid "" " To visit the general information page for an unadvertised list,\n" " open a URL similar to this one, but with a '/' and the %(adj)s\n" @@ -2432,6 +2503,7 @@ msgstr "éžæ³•çš„ email 地å€" #: Mailman/Cgi/options.py:183 Mailman/Cgi/options.py:240 #: Mailman/Cgi/options.py:264 +#, fuzzy msgid "No such member: %(safeuser)s." msgstr "沒有此訂戶:%(safeuser)s。" @@ -2480,6 +2552,7 @@ msgid "Note: " msgstr "" #: Mailman/Cgi/options.py:378 +#, fuzzy msgid "List subscriptions for %(safeuser)s on %(hostname)s" msgstr "列出 %(safeuser)s 在 %(hostname)s 上的訂閱清單" @@ -2505,6 +2578,7 @@ msgid "You are already using that email address" msgstr "您本來就在用那個 email 地å€" #: Mailman/Cgi/options.py:459 +#, fuzzy msgid "" "The new address you requested %(newaddr)s is already a member of the\n" "%(listname)s mailing list, however you have also requested a global change " @@ -2517,6 +2591,7 @@ msgstr "" "在確èªä¹‹å¾Œï¼Œæ‰€æœ‰å«æœ‰ %(safeuser)s 的郵éžè«–壇都會變動。" #: Mailman/Cgi/options.py:468 +#, fuzzy msgid "The new address is already a member: %(newaddr)s" msgstr "新地å€å·²ç¶“是訂戶: %(newaddr)s" @@ -2525,6 +2600,7 @@ msgid "Addresses may not be blank" msgstr "åœ°å€æ¬„ä¸å¯ç•™ç©º" #: Mailman/Cgi/options.py:488 +#, fuzzy msgid "A confirmation message has been sent to %(newaddr)s. " msgstr "已經寄確èªä¿¡åˆ° %(newaddr)s。" @@ -2537,6 +2613,7 @@ msgid "Illegal email address provided" msgstr "æä¾›çš„ email 地å€ä¸åˆæ³•" #: Mailman/Cgi/options.py:501 +#, fuzzy msgid "%(newaddr)s is already a member of the list." msgstr "%(newaddr)s 本來就是本論壇訂戶。" @@ -2617,6 +2694,7 @@ msgstr "" "一旦論壇主æŒäººä½œå‡ºæ±ºå®šï¼Œæ‚¨å°±æœƒæ”¶åˆ°é€šçŸ¥ã€‚" #: Mailman/Cgi/options.py:623 +#, fuzzy msgid "" "You have been successfully unsubscribed from the\n" " mailing list %(fqdn_listname)s. If you were receiving digest\n" @@ -2704,6 +2782,7 @@ msgid "day" msgstr "天" #: Mailman/Cgi/options.py:900 +#, fuzzy msgid "%(days)d %(units)s" msgstr "%(days)d %(units)s" @@ -2716,6 +2795,7 @@ msgid "No topics defined" msgstr "尚未定義任何標題" #: Mailman/Cgi/options.py:940 +#, fuzzy msgid "" "\n" "You are subscribed to this list with the case-preserved address\n" @@ -2725,6 +2805,7 @@ msgstr "" "您以ä¿ç•™å¤§å¯«å­—æ¯çš„åœ°å€ %(cpuser)s 訂閱本論壇。" #: Mailman/Cgi/options.py:956 +#, fuzzy msgid "%(realname)s list: member options login page" msgstr "%(realname)s 的列表: 會員é¸é …登入é " @@ -2733,10 +2814,12 @@ msgid "email address and " msgstr "訂戶地å€å’Œ" #: Mailman/Cgi/options.py:960 +#, fuzzy msgid "%(realname)s list: member options for user %(safeuser)s" msgstr "%(realname)s 的列表: %(safeuser)s 的訂戶é¸é …。" #: Mailman/Cgi/options.py:986 +#, fuzzy msgid "" "In order to change your membership option, you must\n" " first log in by giving your %(extra)smembership password in the section\n" @@ -2804,6 +2887,7 @@ msgid "" msgstr "" #: Mailman/Cgi/options.py:1140 +#, fuzzy msgid "Requested topic is not valid: %(topicname)s" msgstr "è¦æ±‚的標題無效: %(topicname)s" @@ -2832,6 +2916,7 @@ msgid "Private archive - \"./\" and \"../\" not allowed in URL." msgstr "" #: Mailman/Cgi/private.py:109 +#, fuzzy msgid "Private Archive Error - %(msg)s" msgstr "祕密歸檔錯誤 - %(msg)s" @@ -2869,12 +2954,14 @@ msgid "Mailing list deletion results" msgstr "åˆªé™¤é€šä¿¡è«–å£‡çš„çµæžœ" #: Mailman/Cgi/rmlist.py:187 +#, fuzzy msgid "" "You have successfully deleted the mailing list\n" " %(listname)s." msgstr "你已經刪除了 %(listname)s 通信論壇。" #: Mailman/Cgi/rmlist.py:191 +#, fuzzy msgid "" "There were some problems deleting the mailing list\n" " %(listname)s. Contact your site administrator at " @@ -2884,6 +2971,7 @@ msgstr "" "刪除 %(listname)s 通信論壇時發生å•題,請è¯çµ¡åœ¨ %(sitelist)s 的站長。" #: Mailman/Cgi/rmlist.py:208 +#, fuzzy msgid "Permanently remove mailing list %(realname)s" msgstr "永久刪除 %(realname)s 通信論壇" @@ -2946,6 +3034,7 @@ msgid "Invalid options to CGI script" msgstr "錯誤的 CGI é¸é …" #: Mailman/Cgi/roster.py:118 +#, fuzzy msgid "%(realname)s roster authentication failed." msgstr "å–å¾— %(realname)s 訂戶å單時èªè­‰å¤±æ•—。" @@ -3012,6 +3101,7 @@ msgstr "" "æ‚¨å¾ˆå¿«å°±æœƒæ”¶åˆ°å«æœ‰é€²ä¸€æ­¥æŒ‡ç¤ºçš„確èªä¿¡ã€‚" #: Mailman/Cgi/subscribe.py:262 +#, fuzzy msgid "" "The email address you supplied is banned from this\n" " mailing list. If you think this restriction is erroneous, please\n" @@ -3033,6 +3123,7 @@ msgid "" msgstr "您給的 email 地å€ä¸å®‰å…¨æ‰€ä»¥æˆ‘們ä¸èƒ½é€šéŽæ‚¨çš„訂閱申請。" #: Mailman/Cgi/subscribe.py:278 +#, fuzzy msgid "" "Confirmation from your email address is required, to prevent anyone from\n" "subscribing you without permission. Instructions are being sent to you at\n" @@ -3045,6 +3136,7 @@ msgstr "" "算有效。" #: Mailman/Cgi/subscribe.py:290 +#, fuzzy msgid "" "Your subscription request was deferred because %(x)s. Your request has " "been\n" @@ -3065,6 +3157,7 @@ msgid "Mailman privacy alert" msgstr "Mailman éš±ç§æ¬Šè­¦å‘Š" #: Mailman/Cgi/subscribe.py:316 +#, fuzzy msgid "" "An attempt was made to subscribe your address to the mailing list\n" "%(listaddr)s. You are already subscribed to this mailing list.\n" @@ -3100,6 +3193,7 @@ msgid "This list only supports digest delivery." msgstr "本論壇僅æä¾›æ–‡æ‘˜è¨‚閱方å¼ã€‚" #: Mailman/Cgi/subscribe.py:344 +#, fuzzy msgid "You have been successfully subscribed to the %(realname)s mailing list." msgstr "您已經æˆåŠŸè¨‚é–±äº† %(realname)s 論壇。" @@ -3221,26 +3315,32 @@ msgid "n/a" msgstr "ç„¡" #: Mailman/Commands/cmd_info.py:44 +#, fuzzy msgid "List name: %(listname)s" msgstr "論壇å稱: %(listname)s" #: Mailman/Commands/cmd_info.py:45 +#, fuzzy msgid "Description: %(description)s" msgstr "æè¿°: %(description)s" #: Mailman/Commands/cmd_info.py:46 +#, fuzzy msgid "Postings to: %(postaddr)s" msgstr "發表到: %(postaddr)s" #: Mailman/Commands/cmd_info.py:47 +#, fuzzy msgid "List Helpbot: %(requestaddr)s" msgstr "論壇的 Helpbot: %(requestaddr)s" #: Mailman/Commands/cmd_info.py:48 +#, fuzzy msgid "List Owners: %(owneraddr)s" msgstr "è«–å£‡æ“æœ‰äºº: %(owneraddr)s" #: Mailman/Commands/cmd_info.py:49 +#, fuzzy msgid "More information: %(listurl)s" msgstr "進一步的資料: %(listurl)s" @@ -3263,18 +3363,22 @@ msgstr "" " åˆ—å‡ºé€™å° GNU Mailman 伺æœå™¨çš„公開通信論壇。\n" #: Mailman/Commands/cmd_lists.py:44 +#, fuzzy msgid "Public mailing lists at %(hostname)s:" msgstr "在 %(hostname)s 上的公開通信論壇:" #: Mailman/Commands/cmd_lists.py:66 +#, fuzzy msgid "%(i)3d. List name: %(realname)s" msgstr "%(i)3d。論譠å稱: %(realname)s" #: Mailman/Commands/cmd_lists.py:67 +#, fuzzy msgid " Description: %(description)s" msgstr " æè¿°ï¼š %(description)s" #: Mailman/Commands/cmd_lists.py:68 +#, fuzzy msgid " Requests to: %(requestaddr)s" msgstr " æ„è¦‹è¦æ±‚: %(requestaddr)s" @@ -3305,12 +3409,14 @@ msgstr "" "請注æ„,回應總是寄到您的訂閱地å€ã€‚\n" #: Mailman/Commands/cmd_password.py:51 Mailman/Commands/cmd_password.py:66 +#, fuzzy msgid "Your password is: %(password)s" msgstr "你的密碼是: %(password)s" #: Mailman/Commands/cmd_password.py:57 Mailman/Commands/cmd_password.py:72 #: Mailman/Commands/cmd_password.py:95 Mailman/Commands/cmd_password.py:121 #: Mailman/Commands/cmd_set.py:149 Mailman/Commands/cmd_set.py:219 +#, fuzzy msgid "You are not a member of the %(listname)s mailing list" msgstr "æ‚¨ä¸æ˜¯ %(listname)s 通信論壇的訂戶" @@ -3430,6 +3536,7 @@ msgid "" msgstr "" #: Mailman/Commands/cmd_set.py:122 +#, fuzzy msgid "Bad set command: %(subcmd)s" msgstr "ä¸å¥½çš„ set 命令: %(subcmd)s" @@ -3450,6 +3557,7 @@ msgid "on" msgstr "é–‹" #: Mailman/Commands/cmd_set.py:154 +#, fuzzy msgid " ack %(onoff)s" msgstr " ack %(onoff)s" @@ -3487,22 +3595,27 @@ msgid "due to bounces" msgstr "由於退信" #: Mailman/Commands/cmd_set.py:186 +#, fuzzy msgid " %(status)s (%(how)s on %(date)s)" msgstr "%(status)s (%(how)s 在 %(date)s)" #: Mailman/Commands/cmd_set.py:192 +#, fuzzy msgid " myposts %(onoff)s" msgstr "我的發表 %(onoff)s" #: Mailman/Commands/cmd_set.py:195 +#, fuzzy msgid " hide %(onoff)s" msgstr "éš±è— %(onoff)s" #: Mailman/Commands/cmd_set.py:199 +#, fuzzy msgid " duplicates %(onoff)s" msgstr " 複本 %(onoff)s" #: Mailman/Commands/cmd_set.py:203 +#, fuzzy msgid " reminders %(onoff)s" msgstr "「定期æé†’〠%(onoff)s" @@ -3511,6 +3624,7 @@ msgid "You did not give the correct password" msgstr "密碼錯誤。" #: Mailman/Commands/cmd_set.py:236 Mailman/Commands/cmd_set.py:283 +#, fuzzy msgid "Bad argument: %(arg)s" msgstr "åƒæ•¸éŒ¯èª¤: %(arg)s" @@ -3585,6 +3699,7 @@ msgstr "" "é‚£éº¼å°±è¦æŒ‡å®š address äº†ï¼Œåƒæ˜¯ address=you@your.mail.host\n" #: Mailman/Commands/cmd_subscribe.py:62 +#, fuzzy msgid "Bad digest specifier: %(arg)s" msgstr "éŒ¯èª¤çš„æ‘˜è¦æ¨™ç±¤. %(arg)s" @@ -3593,6 +3708,7 @@ msgid "No valid address found to subscribe" msgstr "沒找到å¯è¨‚閱的有效郵件ä½å€" #: Mailman/Commands/cmd_subscribe.py:113 +#, fuzzy msgid "" "The email address you supplied is banned from this mailing list.\n" "If you think this restriction is erroneous, please contact the list\n" @@ -3628,6 +3744,7 @@ msgid "This list only supports digest subscriptions!" msgstr "本論壇僅æä¾›è¨‚閱文摘ï¼" #: Mailman/Commands/cmd_subscribe.py:146 +#, fuzzy msgid "" "Your subscription request has been forwarded to the list administrator\n" "at %(listowner)s for review." @@ -3660,6 +3777,7 @@ msgstr "" "é‚£éº¼å°±è¦æŒ‡å®š address äº†ï¼Œåƒæ˜¯ address=you@your.mail.host\n" #: Mailman/Commands/cmd_unsubscribe.py:62 +#, fuzzy msgid "%(address)s is not a member of the %(listname)s mailing list" msgstr "%(address)s 䏿˜¯ %(listname)s 論壇的訂戶" @@ -3902,6 +4020,7 @@ msgid "Chinese (Taiwan)" msgstr "ç¹é«”中文(Taiwan)" #: Mailman/Deliverer.py:53 +#, fuzzy msgid "" "Note: Since this is a list of mailing lists, administrative\n" "notices like the password reminder will be sent to\n" @@ -3915,14 +4034,17 @@ msgid " (Digest mode)" msgstr " (文摘模å¼)" #: Mailman/Deliverer.py:79 +#, fuzzy msgid "Welcome to the \"%(realname)s\" mailing list%(digmode)s" msgstr "歡迎加入 \"%(realname)s\" 通信論壇 %(digmode)s" #: Mailman/Deliverer.py:89 +#, fuzzy msgid "You have been unsubscribed from the %(realname)s mailing list" msgstr "您已經退訂 \"%(realname)s\" 論壇了" #: Mailman/Deliverer.py:116 +#, fuzzy msgid "%(listfullname)s mailing list reminder" msgstr "%(listfullname)s 通信論壇æé†’" @@ -3935,6 +4057,7 @@ msgid "Hostile subscription attempt detected" msgstr "ç™¼ç¾æƒ¡æ„的訂閱嘗試" #: Mailman/Deliverer.py:169 +#, fuzzy msgid "" "%(address)s was invited to a different mailing\n" "list, but in a deliberate malicious attempt they tried to confirm the\n" @@ -3945,6 +4068,7 @@ msgstr "" "ä½œä»»ä½•å‹•ä½œï¼Œåªæ˜¯æƒ³èªªæ‚¨æ‡‰è©²æœƒæƒ³çŸ¥é“。" #: Mailman/Deliverer.py:188 +#, fuzzy msgid "" "You invited %(address)s to your list, but in a\n" "deliberate malicious attempt, they tried to confirm the invitation to a\n" @@ -3956,6 +4080,7 @@ msgstr "" "è¦ä½œä»»ä½•å‹•ä½œï¼Œåªæ˜¯æƒ³èªªæ‚¨æ‡‰è©²æœƒæƒ³çŸ¥é“。" #: Mailman/Deliverer.py:221 +#, fuzzy msgid "%(listname)s mailing list probe message" msgstr "%(listname)s 通信論壇的探視訊æ¯" @@ -4151,8 +4276,8 @@ msgid "" " membership.\n" "\n" "

                  You can control both the\n" -" number\n" +" number\n" " of reminders the member will receive and the\n" " No those messages too will get discarded. You may " "want\n" " to set up an\n" -" autoresponse\n" +" autoresponse\n" " message for email to the -owner and -admin address." msgstr "" @@ -4346,6 +4471,7 @@ msgid "" msgstr "" #: Mailman/Gui/Bounce.py:194 +#, fuzzy msgid "" "Bad value for %(property)s: %(val)s" @@ -4444,8 +4570,8 @@ msgid "" "Use this option to remove each message attachment that does\n" " not have a matching content type. Requirements and formats " "are\n" -" exactly like filter_mime_types.\n" "\n" "

                  Note: if you add entries to this list but don't add\n" @@ -4522,6 +4648,7 @@ msgid "" msgstr "" #: Mailman/Gui/ContentFilter.py:171 +#, fuzzy msgid "Bad MIME type ignored: %(spectype)s" msgstr "錯誤的MIMEæ ¼å¼è¢«å¿½ç•¥: %(spectype)s" @@ -4619,6 +4746,7 @@ msgid "" msgstr "若已有新資料的話,就讓 Mailman ç¾åœ¨å¯„出新的文摘囉?" #: Mailman/Gui/Digest.py:145 +#, fuzzy msgid "" "The next digest will be sent as volume\n" " %(volume)s, number %(number)s" @@ -4638,10 +4766,12 @@ msgid "Invalid value for variable: %(property)s" msgstr "䏿­£ç¢ºçš„設定: %(property)s" #: Mailman/Gui/GUIBase.py:178 +#, fuzzy msgid "Bad email address for option %(property)s: %(error)s" msgstr "é¸é … %(property)s 內為錯誤的 Email ä¿¡ç®±: %(error)s" #: Mailman/Gui/GUIBase.py:204 +#, fuzzy msgid "" "The following illegal substitution variables were\n" " found in the %(property)s string:\n" @@ -4751,8 +4881,8 @@ msgid "" "

                  In order to split the list ownership duties into\n" " administrators and moderators, you must\n" " set a separate moderator password,\n" -" and also provide the email\n" +" and also provide the email\n" " addresses of the list moderators. Note that the field you\n" " are changing here specifies the list administrators." msgstr "" @@ -5023,13 +5153,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5057,8 +5187,8 @@ msgstr "" "

                  基於許多原因,我們ä¸å»ºè­°æ‚¨æ›´æ› Reply-To: 的內容。\n" "其一是有些投書者ä¾è³´è‡ªå·±çš„ Reply-To: 來設定他們正\n" "確的回信地å€ã€‚ 其二是修改 Reply-To: 䏿˜“回覆ç§äººä¿¡ä»¶ã€‚\n" -"è«‹åƒè€ƒ `Reply-To' Munging\n" +"è«‹åƒè€ƒ `Reply-To' Munging\n" "Considered Harmful ,這裡有這個題目進一步的討論。 請到 Reply-To\n" "Munging Considered Useful 發表您的æ„見。\n" @@ -5077,8 +5207,8 @@ msgstr "明確的 Reply-To: 標題。" msgid "" "This is the address set in the Reply-To: header\n" " when the reply_goes_to_list\n" +" href=\"?VARHELP=general/" +"reply_goes_to_list\">reply_goes_to_list\n" " option is set to Explicit address.\n" "\n" "

                  There are many reasons not to introduce or override the\n" @@ -5086,13 +5216,13 @@ msgid "" " their own Reply-To: settings to convey their valid\n" " return address. Another is that modifying Reply-To:\n" " makes it much more difficult to send private replies. See `Reply-To'\n" +" href=\"http://marc.merlins.org/netrants/reply-to-harmful." +"html\">`Reply-To'\n" " Munging Considered Harmful for a general discussion of " "this\n" " issue. See \n" +" href=\"http://marc.merlins.org/netrants/reply-to-useful." +"html\">\n" " Reply-To Munging Considered Useful for a dissenting " "opinion.\n" "\n" @@ -5120,8 +5250,8 @@ msgstr "" "

                  基於許多原因,我們ä¸å»ºè­°æ‚¨æ›´æ›ä¿¡ä»¶ Reply-To:的標題。\n" "其一是有些投書者ä¾è³´è‡ªå·±çš„ Reply-To: 來設定他們正\n" "確的回信地å€ã€‚ 其二是修改 Reply-To: 䏿˜“回覆ç§äººä¿¡ä»¶ã€‚\n" -"è«‹åƒè€ƒ `Reply-To' Munging\n" +"è«‹åƒè€ƒ `Reply-To' Munging\n" "Considered Harmful ,這裡有這個題目進一步的討論。 請到 Reply-To\n" "Munging Considered Useful 發表您的æ„見。\n" @@ -5176,8 +5306,8 @@ msgid "" " member list addresses, but rather to the owner of those member\n" " lists. In that case, the value of this setting is appended to\n" " the member's account name for such notices. `-owner' is the\n" -" typical choice. This setting has no effect when \"umbrella_list" -"\"\n" +" typical choice. This setting has no effect when " +"\"umbrella_list\"\n" " is \"No\"." msgstr "" "當設定\"樹狀論壇\"æ™‚å°±ä»£è¡¨è©²è«–å£‡æ“æœ‰å…¶ä»–通信論壇為其會員,因此管ç†çš„æé†’郵" @@ -6120,8 +6250,8 @@ msgid "" " either individually or as a group. Any\n" " posting from a non-member who is not explicitly accepted,\n" " rejected, or discarded, will have their posting filtered by the\n" -" general\n" +" general\n" " non-member rules.\n" "\n" "

                  In the text boxes below, add one address per line; start the\n" @@ -6166,6 +6296,7 @@ msgid "By default, should new list member postings be moderated?" msgstr "新訂戶的刊登è¦ä¸è¦é è¨­æˆå¾…審?" #: Mailman/Gui/Privacy.py:218 +#, fuzzy msgid "" "Each list member has a moderation flag which says\n" " whether messages from the list member can be posted directly " @@ -6211,8 +6342,8 @@ msgid "" "If a member posts this many times, within a period of time\n" " the member is automatically moderated. Use 0 to disable. " "See\n" -" member_verbosity_interval for details on the time " "period.\n" "\n" @@ -6620,6 +6751,7 @@ msgid "" msgstr "從設定為自動拋棄的éžè¨‚戶寄來的刊登該ä¸è©²è½‰å¯„給論壇主æŒäººï¼Ÿ" #: Mailman/Gui/Privacy.py:494 +#, fuzzy msgid "" "Text to include in any rejection notice to be sent to\n" " non-members who post to this list. This notice can include\n" @@ -6861,8 +6993,8 @@ msgid "" "

                  The body of the message can also be optionally scanned for\n" " Subject: and Keywords: headers, as\n" " specified by the topics_bodylines_limit\n" +" href=\"?VARHELP=topics/" +"topics_bodylines_limit\">topics_bodylines_limit\n" " configuration variable." msgstr "" @@ -7086,6 +7218,7 @@ msgid "%(listinfo_link)s list run by %(owner_link)s" msgstr "" #: Mailman/HTMLFormatter.py:57 +#, fuzzy msgid "%(realname)s administrative interface" msgstr "%(realname)s 管ç†ä»‹é¢" @@ -7094,6 +7227,7 @@ msgid " (requires authorization)" msgstr "(待稽核)" #: Mailman/HTMLFormatter.py:61 +#, fuzzy msgid "Overview of all %(hostname)s mailing lists" msgstr "通信論壇列表。" @@ -7116,6 +7250,7 @@ msgid "; it was disabled by the list administrator" msgstr "已被壇主關閉" #: Mailman/HTMLFormatter.py:146 +#, fuzzy msgid "" "; it was disabled due to excessive bounces. The\n" " last bounce was received on %(date)s" @@ -7128,6 +7263,7 @@ msgid "; it was disabled for unknown reasons" msgstr "未知原因被關閉" #: Mailman/HTMLFormatter.py:151 +#, fuzzy msgid "Note: your list delivery is currently disabled%(reason)s." msgstr "備註 - 您論壇的收信設定由於%(reason)sç¾åœ¨æ˜¯é—œé–‰çš„。" @@ -7140,6 +7276,7 @@ msgid "the list administrator" msgstr "壇主" #: Mailman/HTMLFormatter.py:157 +#, fuzzy msgid "" "

                  %(note)s\n" "\n" @@ -7158,6 +7295,7 @@ msgstr "" "è‹¥æœ‰ä»»ä½•ç–‘å•æˆ–é ˆå”助,請è¯çµ¡ %(mailto)s" #: Mailman/HTMLFormatter.py:169 +#, fuzzy msgid "" "

                  We have received some recent bounces from your\n" " address. Your current bounce score is %(score)s out of " @@ -7215,18 +7353,21 @@ msgstr "" "é€å£‡ä¸»æ ¸å‡†ã€‚ç¨å¾Œæ‚¨æœƒæ”¶åˆ°å£‡ä¸»çš„è£æ±º email。" #: Mailman/HTMLFormatter.py:208 +#, fuzzy msgid "" "This is %(also)sa private list, which means that the\n" " list of members is not available to non-members." msgstr "這是 %(also)s 上的ç§äººè«–壇,也就是說會員清單ä¸å°éžæœƒå“¡å…¬é–‹ã€‚" #: Mailman/HTMLFormatter.py:211 +#, fuzzy msgid "" "This is %(also)sa hidden list, which means that the\n" " list of members is available only to the list administrator." msgstr "這是 %(also)sa 隱形的論壇,會員清單僅é™å£‡ä¸»æŸ¥é–±ã€‚" #: Mailman/HTMLFormatter.py:214 +#, fuzzy msgid "" "This is %(also)sa public list, which means that the\n" " list of members list is available to everyone." @@ -7351,6 +7492,7 @@ msgid "The current archive" msgstr "ç¾åœ¨çš„æª”案" #: Mailman/Handlers/Acknowledge.py:59 +#, fuzzy msgid "%(realname)s post acknowledgement" msgstr "%(realname)s 發言回執" @@ -7437,6 +7579,7 @@ msgid "Message may contain administrivia" msgstr "訊æ¯åŒ…å«ç®¡ç†æŒ‡ä»¤" #: Mailman/Handlers/Hold.py:84 +#, fuzzy msgid "" "Please do *not* post administrative requests to the mailing\n" "list. If you wish to subscribe, visit %(listurl)s or send a message with " @@ -7476,10 +7619,12 @@ msgid "Posting to a moderated newsgroup" msgstr "投書到管制的論壇" #: Mailman/Handlers/Hold.py:252 +#, fuzzy msgid "Your message to %(listname)s awaits moderator approval" msgstr "您寄到 %(listname)s 的信件已é€äº¤å£‡ä¸»è£æ±º" #: Mailman/Handlers/Hold.py:271 +#, fuzzy msgid "%(listname)s post from %(sender)s requires approval" msgstr "%(listname)s 發言 來自 %(sender)s éœ€è¦æ ¸å‡†" @@ -7552,6 +7697,7 @@ msgid "The attached message has been automatically discarded." msgstr "附加的訊æ¯å·²è¢«è‡ªå‹•丟棄。" #: Mailman/Handlers/Replybot.py:75 +#, fuzzy msgid "Auto-response for your message to the \"%(realname)s\" mailing list" msgstr "自動回覆您的訊æ¯è‡³ \"%(realname)s\" 論壇" @@ -7575,6 +7721,7 @@ msgid "HTML attachment scrubbed and removed" msgstr "HTML 附加檔被抹去並移除" #: Mailman/Handlers/Scrubber.py:234 Mailman/Handlers/Scrubber.py:259 +#, fuzzy msgid "" "An HTML attachment was scrubbed...\n" "URL: %(url)s\n" @@ -7629,6 +7776,7 @@ msgstr "" "URL: %(url)s\n" #: Mailman/Handlers/Scrubber.py:347 +#, fuzzy msgid "Skipped content of type %(partctype)s\n" msgstr "è·³éŽ %(partctype)s 型態的內容\n" @@ -7658,6 +7806,7 @@ msgid "Message rejected by filter rule match" msgstr "訊æ¯è¢«éŽæ¿¾è¦å‰‡åŒ¹é…ç¨‹å¼æ‰€æ‹’絕" #: Mailman/Handlers/ToDigest.py:173 +#, fuzzy msgid "%(realname)s Digest, Vol %(volume)d, Issue %(issue)d" msgstr "%(realname)s 摘è¦ã€å®¹é‡ %(volume)dã€æ¢ç›® %(issue)d" @@ -7707,6 +7856,7 @@ msgid "Forward of moderated message" msgstr "轉é€ç®¡åˆ¶çš„ä¿¡ä»¶" #: Mailman/ListAdmin.py:405 +#, fuzzy msgid "New subscription request to list %(realname)s from %(addr)s" msgstr "%(realname)s 論壇訂閱申請,由 %(addr)s æå‡º" @@ -7720,6 +7870,7 @@ msgid "via admin approval" msgstr "繼續等待審核" #: Mailman/ListAdmin.py:466 +#, fuzzy msgid "New unsubscription request from %(realname)s by %(addr)s" msgstr "%(realname)s 論壇退訂申請,由 %(addr)s æå‡º" @@ -7732,10 +7883,12 @@ msgid "Original Message" msgstr "原始發言" #: Mailman/ListAdmin.py:526 +#, fuzzy msgid "Request to mailing list %(realname)s rejected" msgstr "在論壇 %(realname)s 的申請已被拒絕" #: Mailman/MTA/Manual.py:66 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been created via the through-the-web\n" "interface. In order to complete the activation of this mailing list, the\n" @@ -7766,10 +7919,12 @@ msgid "## %(listname)s mailing list" msgstr "##通信論壇 %(listname)s" #: Mailman/MTA/Manual.py:99 +#, fuzzy msgid "Mailing list creation request for list %(listname)s" msgstr "通信論壇 %(listname)s 發起事務申請" #: Mailman/MTA/Manual.py:113 +#, fuzzy msgid "" "The mailing list `%(listname)s' has been removed via the through-the-web\n" "interface. In order to complete the de-activation of this mailing list, " @@ -7787,6 +7942,7 @@ msgstr "" "以下是整個 /etc/aliases 檔案的內容:\n" #: Mailman/MTA/Manual.py:123 +#, fuzzy msgid "" "\n" "To finish removing your mailing list, you must edit your /etc/aliases (or\n" @@ -7803,14 +7959,17 @@ msgstr "" "## 通信論壇 %(listname)s" #: Mailman/MTA/Manual.py:142 +#, fuzzy msgid "Mailing list removal request for list %(listname)s" msgstr "通信論壇 %(listname)s 移除申請" #: Mailman/MTA/Postfix.py:442 +#, fuzzy msgid "checking permissions on %(file)s" msgstr "檢查 %(file)s 檔案讀寫權中" #: Mailman/MTA/Postfix.py:452 +#, fuzzy msgid "%(file)s permissions must be 0664 (got %(octmode)s)" msgstr "%(file)s的檔案讀寫權為 %(octmode)s ,需更改為 0664" @@ -7824,26 +7983,32 @@ msgid "(fixing)" msgstr "修正中" #: Mailman/MTA/Postfix.py:470 +#, fuzzy msgid "checking ownership of %(dbfile)s" msgstr "檢查 %(dbfile)s 檔案所有權中" #: Mailman/MTA/Postfix.py:478 +#, fuzzy msgid "%(dbfile)s owned by %(owner)s (must be owned by %(user)s" msgstr "%(dbfile)s的檔案所有權為 %(owner)s 所有,所有人需更改為 %(user)s" #: Mailman/MTA/Postfix.py:491 +#, fuzzy msgid "%(dbfile)s permissions must be 0664 (got %(octmode)s)" msgstr "%(dbfile)s的檔案讀寫權為 %(octmode)s ,需更改為 0664" #: Mailman/MailList.py:219 +#, fuzzy msgid "Your confirmation is required to join the %(listname)s mailing list" msgstr "請確èªé€€å‡º %(listname)s 論壇" #: Mailman/MailList.py:230 +#, fuzzy msgid "Your confirmation is required to leave the %(listname)s mailing list" msgstr "請確èªåŠ å…¥ %(listname)s 論壇" #: Mailman/MailList.py:999 Mailman/MailList.py:1492 +#, fuzzy msgid " from %(remote)s" msgstr " 寄自 %(remote)s" @@ -8062,6 +8227,7 @@ msgid "Server Local Time" msgstr "伺æœå™¨æ™‚é–“" #: Mailman/i18n.py:180 +#, fuzzy msgid "" "%(wday)s %(mon)s %(day)2i %(hh)02i:%(mm)02i:%(ss)02i %(tzname)s %(year)04i" msgstr "" @@ -8156,16 +8322,19 @@ msgid "Invited: %(member)s" msgstr "已訂閱者: %(member)s" #: bin/add_members:187 +#, fuzzy msgid "Subscribed: %(member)s" msgstr "已訂閱者: %(member)s" #: bin/add_members:237 +#, fuzzy msgid "Bad argument to -w/--welcome-msg: %(arg)s" -msgstr "" +msgstr "åƒæ•¸éŒ¯èª¤: %(arg)s" #: bin/add_members:244 +#, fuzzy msgid "Bad argument to -a/--admin-notify: %(arg)s" -msgstr "" +msgstr "åƒæ•¸éŒ¯èª¤: %(arg)s" #: bin/add_members:252 msgid "Cannot read both digest and normal members from standard input." @@ -8332,8 +8501,9 @@ msgid "" msgstr "" #: bin/change_pw:145 +#, fuzzy msgid "Bad arguments: %(strargs)s" -msgstr "" +msgstr "åƒæ•¸éŒ¯èª¤: %(arg)s" #: bin/change_pw:149 #, fuzzy @@ -8341,12 +8511,14 @@ msgid "Empty list passwords are not allowed" msgstr "ä¸å…許管ç†è€…密碼空白" #: bin/change_pw:181 +#, fuzzy msgid "New %(listname)s password: %(notifypassword)s" -msgstr "" +msgstr "論壇 %(listname)s çš„åˆå§‹å¯†ç¢¼ï¼š" #: bin/change_pw:190 +#, fuzzy msgid "Your new %(listname)s list password" -msgstr "" +msgstr "論壇 %(listname)s çš„åˆå§‹å¯†ç¢¼ï¼š" #: bin/change_pw:191 msgid "" @@ -8425,40 +8597,47 @@ msgid "" msgstr "" #: bin/check_perms:110 +#, fuzzy msgid " checking gid and mode for %(path)s" -msgstr "" +msgstr "檢查 %(file)s 檔案讀寫權中" #: bin/check_perms:122 msgid "%(path)s bad group (has: %(groupname)s, expected %(MAILMAN_GROUP)s)" msgstr "" #: bin/check_perms:151 +#, fuzzy msgid "directory permissions must be %(octperms)s: %(path)s" -msgstr "" +msgstr "%(dbfile)s的檔案讀寫權為 %(octmode)s ,需更改為 0664" #: bin/check_perms:160 +#, fuzzy msgid "source perms must be %(octperms)s: %(path)s" -msgstr "" +msgstr "%(dbfile)s的檔案讀寫權為 %(octmode)s ,需更改為 0664" #: bin/check_perms:171 +#, fuzzy msgid "article db files must be %(octperms)s: %(path)s" -msgstr "" +msgstr "%(dbfile)s的檔案讀寫權為 %(octmode)s ,需更改為 0664" #: bin/check_perms:183 +#, fuzzy msgid "checking mode for %(prefix)s" -msgstr "" +msgstr "檢查 %(file)s 檔案讀寫權中" #: bin/check_perms:193 msgid "WARNING: directory does not exist: %(d)s" msgstr "" #: bin/check_perms:197 +#, fuzzy msgid "directory must be at least 02775: %(d)s" -msgstr "" +msgstr "%(file)s的檔案讀寫權為 %(octmode)s ,需更改為 0664" #: bin/check_perms:209 +#, fuzzy msgid "checking perms on %(private)s" -msgstr "" +msgstr "檢查 %(file)s 檔案讀寫權中" #: bin/check_perms:214 msgid "%(private)s must not be other-readable" @@ -8486,40 +8665,46 @@ msgid "checking cgi-bin permissions" msgstr "" #: bin/check_perms:278 +#, fuzzy msgid " checking set-gid for %(path)s" -msgstr "" +msgstr "檢查 %(file)s 檔案讀寫權中" #: bin/check_perms:282 msgid "%(path)s must be set-gid" msgstr "" #: bin/check_perms:292 +#, fuzzy msgid "checking set-gid for %(wrapper)s" -msgstr "" +msgstr "檢查 %(file)s 檔案讀寫權中" #: bin/check_perms:296 msgid "%(wrapper)s must be set-gid" msgstr "" #: bin/check_perms:306 +#, fuzzy msgid "checking permissions on %(pwfile)s" -msgstr "" +msgstr "檢查 %(file)s 檔案讀寫權中" #: bin/check_perms:315 +#, fuzzy msgid "%(pwfile)s permissions must be exactly 0640 (got %(octmode)s)" -msgstr "" +msgstr "%(file)s的檔案讀寫權為 %(octmode)s ,需更改為 0664" #: bin/check_perms:340 msgid "checking permissions on list data" msgstr "" #: bin/check_perms:348 +#, fuzzy msgid " checking permissions on: %(path)s" -msgstr "" +msgstr "檢查 %(file)s 檔案讀寫權中" #: bin/check_perms:356 +#, fuzzy msgid "file permissions must be at least 660: %(path)s" -msgstr "" +msgstr "%(file)s的檔案讀寫權為 %(octmode)s ,需更改為 0664" #: bin/check_perms:401 msgid "No problems found" @@ -8572,8 +8757,9 @@ msgid "Unix-From line changed: %(lineno)d" msgstr "" #: bin/cleanarch:111 +#, fuzzy msgid "Bad status number: %(arg)s" -msgstr "" +msgstr "åƒæ•¸éŒ¯èª¤: %(arg)s" #: bin/cleanarch:167 msgid "%(messages)d messages found" @@ -8678,10 +8864,11 @@ msgid "Not a valid email address: %(toaddr)s" msgstr "䏿­£ç¢º çš„ email 地å€" #: bin/clone_member:215 +#, fuzzy msgid "" "Error opening list \"%(listname)s\", skipping.\n" "%(e)s" -msgstr "" +msgstr "開啟論壇 %(listname)s 設定檔案錯誤,略éŽ" #: bin/config_list:20 msgid "" @@ -8767,12 +8954,14 @@ msgid "Non-standard property restored: %(k)s" msgstr "" #: bin/config_list:288 +#, fuzzy msgid "Invalid value for property: %(k)s" -msgstr "" +msgstr "䏿­£ç¢ºçš„設定: %(property)s" #: bin/config_list:291 +#, fuzzy msgid "Bad email address for option %(k)s: %(v)s" -msgstr "" +msgstr "é¸é … %(property)s 內為錯誤的 Email ä¿¡ç®±: %(error)s" #: bin/config_list:348 msgid "Only one of -i or -o is allowed" @@ -8821,12 +9010,14 @@ msgid "" msgstr "" #: bin/discard:94 +#, fuzzy msgid "Ignoring non-held message: %(f)s" -msgstr "" +msgstr "忽略å°å·²é™¤å訂戶 %(user)s 的設定" #: bin/discard:100 +#, fuzzy msgid "Ignoring held msg w/bad id: %(f)s" -msgstr "" +msgstr "忽略å°å·²é™¤å訂戶 %(user)s 的設定" #: bin/discard:112 #, fuzzy @@ -8876,8 +9067,9 @@ msgid "No filename given." msgstr "[沒有說明原因]" #: bin/dumpdb:108 +#, fuzzy msgid "Bad arguments: %(pargs)s" -msgstr "" +msgstr "åƒæ•¸éŒ¯èª¤: %(arg)s" #: bin/dumpdb:118 msgid "Please specify either -p or -m." @@ -9128,8 +9320,9 @@ msgid "" msgstr "" #: bin/list_admins:97 +#, fuzzy msgid "List: %(listname)s, \tOwners: %(owners)s" -msgstr "" +msgstr "è«–å£‡æ“æœ‰äºº: %(owneraddr)s" #: bin/list_lists:19 msgid "" @@ -9236,12 +9429,14 @@ msgid "" msgstr "" #: bin/list_members:198 +#, fuzzy msgid "Bad --nomail option: %(why)s" -msgstr "" +msgstr "éŒ¯èª¤çš„æ‘˜è¦æ¨™ç±¤. %(arg)s" #: bin/list_members:209 +#, fuzzy msgid "Bad --digest option: %(kind)s" -msgstr "" +msgstr "éŒ¯èª¤çš„æ‘˜è¦æ¨™ç±¤. %(arg)s" #: bin/list_members:213 bin/list_members:217 bin/list_members:221 #: bin/list_members:225 @@ -9425,8 +9620,9 @@ msgid "" msgstr "" #: bin/mailmanctl:280 cron/mailpasswds:119 +#, fuzzy msgid "Site list is missing: %(sitelistname)s" -msgstr "" +msgstr "論壇已存在: %(safelistname)s" #: bin/mailmanctl:305 msgid "Run this program as root or as the %(name)s user, or use -u." @@ -9438,8 +9634,9 @@ msgid "No command given." msgstr "[沒有說明原因]" #: bin/mailmanctl:339 +#, fuzzy msgid "Bad command: %(command)s" -msgstr "" +msgstr "ä¸å¥½çš„ set 命令: %(subcmd)s" #: bin/mailmanctl:344 msgid "Warning! You may encounter permission problems." @@ -9654,6 +9851,7 @@ msgid "" msgstr "" #: bin/newlist:162 +#, fuzzy msgid "Unknown language: %(lang)s" msgstr "未知的語言:%(lang)s" @@ -9666,6 +9864,7 @@ msgid "Enter the email of the person running the list: " msgstr "輸入本論壇主æŒäººçš„é›»å­éƒµä»¶ï¼š" #: bin/newlist:193 +#, fuzzy msgid "Initial %(listname)s password: " msgstr "論壇 %(listname)s çš„åˆå§‹å¯†ç¢¼ï¼š" @@ -9675,11 +9874,12 @@ msgstr "論壇ä¸èƒ½æ˜¯ç©ºå¯†ç¢¼" #: bin/newlist:220 msgid "" -" - owner addresses need to be fully-qualified names like \"owner@example.com" -"\", not just \"owner\"." +" - owner addresses need to be fully-qualified names like \"owner@example." +"com\", not just \"owner\"." msgstr "" #: bin/newlist:244 +#, fuzzy msgid "Hit enter to notify %(listname)s owner..." msgstr "按下輸入éµä¾†é€šçŸ¥ %(listname)s 論壇主æŒäºº" @@ -9841,18 +10041,22 @@ msgid "" msgstr "" #: bin/remove_members:156 +#, fuzzy msgid "Could not open file for reading: %(filename)s." msgstr "無法開啟檔案 %(filename)s 讀å–資料" #: bin/remove_members:163 +#, fuzzy msgid "Error opening list %(listname)s... skipping." msgstr "開啟論壇 %(listname)s 設定檔案錯誤,略éŽ" #: bin/remove_members:173 +#, fuzzy msgid "No such member: %(addr)s" msgstr "沒有æˆå“¡: %(addr)s。" #: bin/remove_members:178 +#, fuzzy msgid "User `%(addr)s' removed from list: %(listname)s." msgstr "æˆå“¡ `%(addr)s' 已由論壇 %(listname)s 移除。" @@ -9908,20 +10112,23 @@ msgid "" msgstr "" #: bin/rmlist:73 bin/rmlist:76 +#, fuzzy msgid "Removing %(msg)s" -msgstr "" +msgstr "正在刪除 %(src)s" #: bin/rmlist:81 msgid "%(listname)s %(msg)s not found as %(filename)s" msgstr "" #: bin/rmlist:105 +#, fuzzy msgid "No such list (or list already deleted): %(listname)s" -msgstr "" +msgstr "無此論壇 \"%s\": %s\n" #: bin/rmlist:108 +#, fuzzy msgid "No such list: %(listname)s. Removing its residual archives." -msgstr "" +msgstr "無此論壇 \"%s\": %s\n" #: bin/rmlist:112 msgid "Not removing archives. Reinvoke with -a to remove them." @@ -10053,6 +10260,7 @@ msgid "No argument to -f given" msgstr "[沒有說明原因]" #: bin/sync_members:172 +#, fuzzy msgid "Illegal option: %(opt)s" msgstr "䏿­£ç¢ºçš„é¸é …: %(opt)s" @@ -10083,14 +10291,17 @@ msgid "You must fix the preceding invalid addresses first." msgstr "您一定è¦å…ˆæŠŠå‰é¢çš„錯誤地å€ä¿®å¥½æ‰è¡Œã€‚" #: bin/sync_members:264 +#, fuzzy msgid "Added : %(s)s" msgstr "增加: %(s)s" #: bin/sync_members:289 +#, fuzzy msgid "Removed: %(s)s" msgstr "刪除: %(s)s" #: bin/transcheck:19 +#, fuzzy msgid "" "\n" "Check a given Mailman translation, making sure that variables and\n" @@ -10210,8 +10421,9 @@ msgid "" msgstr "" #: bin/update:107 +#, fuzzy msgid "Fixing language templates: %(listname)s" -msgstr "" +msgstr "正在更新郵éžè«–壇: %(listname)s" #: bin/update:196 bin/update:711 msgid "WARNING: could not acquire lock for list: %(listname)s" @@ -10263,18 +10475,24 @@ msgid "- updating old private mbox file" msgstr "" #: bin/update:295 +#, fuzzy msgid "" " unknown file in the way, moving\n" " %(o_pri_mbox_file)s\n" " to\n" " %(newname)s" msgstr "" +" 被未知的檔案擋ä½äº†ï¼Œå°‡\n" +" %(o_pub_mbox_file)s\n" +" 改å為\n" +" %(newname)s" #: bin/update:309 msgid "- updating old public mbox file" msgstr "ï¼ æ›´æ–°èˆŠçš„å…¬å…±ä¿¡ç®±æª”" #: bin/update:317 +#, fuzzy msgid "" " unknown file in the way, moving\n" " %(o_pub_mbox_file)s\n" @@ -10303,18 +10521,22 @@ msgid "- %(o_tmpl)s doesn't exist, leaving untouched" msgstr "ï¼ %(o_tmpl)s ä¸å­˜åœ¨ï¼Œç•™è‘—ä¸ç¢°" #: bin/update:396 +#, fuzzy msgid "removing directory %(src)s and everything underneath" msgstr "正在刪除 %(src)s 目錄樹" #: bin/update:399 +#, fuzzy msgid "removing %(src)s" msgstr "正在刪除 %(src)s" #: bin/update:403 +#, fuzzy msgid "Warning: couldn't remove %(src)s -- %(rest)s" msgstr "警告:無法刪除 %(src)s ï¼ %(rest)s" #: bin/update:408 +#, fuzzy msgid "couldn't remove old file %(pyc)s -- %(rest)s" msgstr "無法刪除舊檔案 %(pyc)s ï¼ %(rest)s" @@ -10327,6 +10549,7 @@ msgid "Warning! Not a directory: %(dirpath)s" msgstr "" #: bin/update:530 +#, fuzzy msgid "message is unparsable: %(filebase)s" msgstr "無法解æžçš„訊æ¯ï¼š %(filebase)s" @@ -10343,10 +10566,12 @@ msgid "Updating Mailman 2.1.4 pending.pck database" msgstr "正在更新 Mailman 2.1.4 çš„ pending.pck 資料庫" #: bin/update:598 +#, fuzzy msgid "Ignoring bad pended data: %(key)s: %(val)s" msgstr "忽略壞的待處ç†è³‡æ–™ï¼š %(key)s: %(val)s" #: bin/update:614 +#, fuzzy msgid "WARNING: Ignoring duplicate pending ID: %(id)s." msgstr "警告:忽略掉é‡è¤‡çš„待決識別字:%(id)s。" @@ -10371,6 +10596,7 @@ msgid "done" msgstr "åšå®Œäº†" #: bin/update:691 +#, fuzzy msgid "Updating mailing list: %(listname)s" msgstr "正在更新郵éžè«–壇: %(listname)s" @@ -10427,6 +10653,7 @@ msgid "No updates are necessary." msgstr "沒有更新的必è¦ã€‚" #: bin/update:796 +#, fuzzy msgid "" "Downgrade detected, from version %(hexlversion)s to version %(hextversion)s\n" "This is probably not safe.\n" @@ -10437,10 +10664,12 @@ msgstr "" "離開中。" #: bin/update:801 +#, fuzzy msgid "Upgrading from version %(hexlversion)s to %(hextversion)s" msgstr "正在從 %(hexlversion)s å‡ç´šåˆ° %(hextversion)s" #: bin/update:810 +#, fuzzy msgid "" "\n" "ERROR:\n" @@ -10690,6 +10919,7 @@ msgstr "" "在例外發生時會被呼å«ã€‚" #: bin/withlist:175 +#, fuzzy msgid "Unlocking (but not saving) list: %(listname)s" msgstr "解開(但ä¸å„²å­˜ï¼‰è«–壇: %(listname)s" @@ -10698,6 +10928,7 @@ msgid "Finalizing" msgstr "收尾中" #: bin/withlist:188 +#, fuzzy msgid "Loading list %(listname)s" msgstr "正在載入論壇 %(listname)s" @@ -10710,6 +10941,7 @@ msgid "(unlocked)" msgstr "(解開了)" #: bin/withlist:197 +#, fuzzy msgid "Unknown list: %(listname)s" msgstr "未知論壇: %(listname)s" @@ -10722,18 +10954,22 @@ msgid "--all requires --run" msgstr "--all éœ€è¦ --run" #: bin/withlist:266 +#, fuzzy msgid "Importing %(module)s..." msgstr "正在載入 %(module)s 模組..." #: bin/withlist:270 +#, fuzzy msgid "Running %(module)s.%(callable)s()..." msgstr "正在執行 %(module)s.%(callable)s()..." #: bin/withlist:291 +#, fuzzy msgid "The variable `m' is the %(listname)s MailList instance" msgstr "變數 `m' 是 %(listname)s MailList 實物" #: cron/bumpdigests:19 +#, fuzzy msgid "" "Increment the digest volume number and reset the digest number to one.\n" "\n" @@ -10760,6 +10996,7 @@ msgstr "" "幫在命令列指定的論壇開新的一冊文摘,如果沒指定論壇就幫所有的論壇都開。\n" #: cron/checkdbs:20 +#, fuzzy msgid "" "Check for pending admin requests and mail the list owners if necessary.\n" "\n" @@ -10788,10 +11025,12 @@ msgstr "" "\n" #: cron/checkdbs:121 +#, fuzzy msgid "%(count)d %(realname)s moderator request(s) waiting" msgstr "%(realname)s 論壇有 %(count)d å€‹è¦æ±‚待處ç†" #: cron/checkdbs:124 +#, fuzzy msgid "%(realname)s moderator request check result" msgstr "%(realname)s å¯©æŸ¥ä»¶æ•¸æª¢æŸ¥çš„çµæžœ" @@ -10813,6 +11052,7 @@ msgstr "" "待審的訊æ¯ï¼š" #: cron/checkdbs:169 +#, fuzzy msgid "" "From: %(sender)s on %(date)s\n" "Subject: %(subject)s\n" @@ -10845,6 +11085,7 @@ msgid "" msgstr "" #: cron/disabled:20 +#, fuzzy msgid "" "Process disabled members, recommended once per day.\n" "\n" @@ -10957,6 +11198,7 @@ msgstr "" "\n" #: cron/mailpasswds:19 +#, fuzzy msgid "" "Send password reminders for all lists to all users.\n" "\n" @@ -11007,10 +11249,12 @@ msgid "Password // URL" msgstr "密碼 // ç¶²å€" #: cron/mailpasswds:222 +#, fuzzy msgid "%(host)s mailing list memberships reminder" msgstr " %(host)s 郵éžè«–å£‡çš„æœƒç±æé†’é€šçŸ¥æ›¸" #: cron/nightly_gzip:19 +#, fuzzy msgid "" "Re-generate the Pipermail gzip'd archive flat files.\n" "\n" @@ -11401,8 +11645,8 @@ msgstr "" #~ "outsiders. (See also the Archival Options " #~ "section for separate archive-privacy settings.)" #~ msgstr "" -#~ "論壇管制政策,包括防垃圾信措施,會員åŠéžæœƒå“¡ã€‚(個別歸檔設定請åƒè€ƒæ­¸æª”è¨­å®šé  )" +#~ "論壇管制政策,包括防垃圾信措施,會員åŠéžæœƒå“¡ã€‚(個別歸檔設定請åƒè€ƒæ­¸æª”è¨­å®šé  )" #~ msgid "" #~ "An unexpected Mailman error has occurred in\n" From f296b16f125cb0221ef366748e462e125f6a3167 Mon Sep 17 00:00:00 2001 From: Nicolas Mendoza Date: Thu, 9 Jan 2025 15:07:57 +0100 Subject: [PATCH 003/748] Let username parameter work (#9) --- bin/mailmanctl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/mailmanctl b/bin/mailmanctl index 9e87bcd9..36016e27 100644 --- a/bin/mailmanctl +++ b/bin/mailmanctl @@ -310,8 +310,8 @@ def check_privs(): def main(): global quiet try: - opts, args = getopt.getopt(sys.argv[1:], 'hnusq', - ['help', 'no-restart', 'run-as-user', + opts, args = getopt.getopt(sys.argv[1:], 'hnu:sq', + ['help', 'no-restart', 'run-as-user=', 'stale-lock-cleanup', 'quiet']) except getopt.error as msg: usage(1, msg) From 238894b8833ed6eefd9f8922b0e5db9cf7767a90 Mon Sep 17 00:00:00 2001 From: Nicolas Mendoza Date: Thu, 9 Jan 2025 15:08:12 +0100 Subject: [PATCH 004/748] Use regex strings (#10) --- Mailman/Archiver/HyperArch.py | 8 +- Mailman/Bouncers/SimpleMatch.py | 220 ++++++++++++++++---------------- Mailman/Handlers/Approve.py | 2 +- Mailman/Handlers/CookHeaders.py | 8 +- Mailman/Handlers/Decorate.py | 2 +- Mailman/Handlers/SpamDetect.py | 2 +- Mailman/Handlers/ToDigest.py | 4 +- Mailman/SecurityManager.py | 2 +- Mailman/Utils.py | 4 +- bin/rmlist | 2 +- 10 files changed, 127 insertions(+), 127 deletions(-) diff --git a/Mailman/Archiver/HyperArch.py b/Mailman/Archiver/HyperArch.py index b5459dbf..e719cacb 100644 --- a/Mailman/Archiver/HyperArch.py +++ b/Mailman/Archiver/HyperArch.py @@ -416,7 +416,7 @@ def decode_headers(self): i18n.set_language(self._lang) atmark = str(_(' at '), Utils.GetCharSet(self._lang)) subject = re.sub(r'([-+,.\w]+)@([-+.\w]+)', - '\g<1>' + atmark + '\g<2>', subject) + r'\g<1>' + atmark + r'\g<2>', subject) finally: i18n.set_translation(otrans) self.decoded['subject'] = subject @@ -433,10 +433,10 @@ def strip_subject(self, subject): subject = re.sub(prefix_pat, '', subject) subject = subject.lstrip() # MAS Should we strip FW and FWD too? - strip_pat = re.compile('^((RE|AW|SV|VS)(\[\d+\])?:\s*)+', re.I) + strip_pat = re.compile(r'^((RE|AW|SV|VS)(\[\d+\])?:\s*)+', re.I) stripped = strip_pat.sub('', subject) # Also remove whitespace to avoid folding/unfolding differences - stripped = re.sub('\s', '', stripped) + stripped = re.sub(r'\s', '', stripped) return stripped def decode_charset(self, field): @@ -583,7 +583,7 @@ def as_text(self): i18n.set_language(self._lang) atmark = str(_(' at '), cset) body = re.sub(r'([-+,.\w]+)@([-+.\w]+)', - '\g<1>' + atmark + '\g<2>', body) + r'\g<1>' + atmark + r'\g<2>', body) finally: i18n.set_translation(otrans) # Return body to character set of article. diff --git a/Mailman/Bouncers/SimpleMatch.py b/Mailman/Bouncers/SimpleMatch.py index 0a03edd6..68e1d18d 100644 --- a/Mailman/Bouncers/SimpleMatch.py +++ b/Mailman/Bouncers/SimpleMatch.py @@ -39,162 +39,162 @@ def _c(pattern): # address that bounced. PATTERNS = [ # sdm.de - (_c('here is your list of failed recipients'), - _c('here is your returned mail'), + (_c(r'here is your list of failed recipients'), + _c(r'here is your returned mail'), _c(r'<(?P[^>]*)>')), # sz-sb.de, corridor.com, nfg.nl - (_c('the following addresses had'), - _c('transcript of session follows'), + (_c(r'the following addresses had'), + _c(r'transcript of session follows'), _c(r'^ *(\(expanded from: )?[^\s@]+@[^\s@>]+?)>?\)?\s*$')), # robanal.demon.co.uk - (_c('this message was created automatically by mail delivery software'), - _c('original message follows'), - _c('rcpt to:\s*<(?P[^>]*)>')), + (_c(r'this message was created automatically by mail delivery software'), + _c(r'original message follows'), + _c(r'rcpt to:\s*<(?P[^>]*)>')), # s1.com (InterScan E-Mail VirusWall NT ???) - (_c('message from interscan e-mail viruswall nt'), - _c('end of message'), - _c('rcpt to:\s*<(?P[^>]*)>')), + (_c(r'message from interscan e-mail viruswall nt'), + _c(r'end of message'), + _c(r'rcpt to:\s*<(?P[^>]*)>')), # Smail - (_c('failed addresses follow:'), - _c('message text follows:'), + (_c(r'failed addresses follow:'), + _c(r'message text follows:'), _c(r'\s*(?P\S+@\S+)')), # newmail.ru - (_c('This is the machine generated message from mail service.'), - _c('--- Below the next line is a copy of the message.'), - _c('<(?P[^>]*)>')), + (_c(r'This is the machine generated message from mail service.'), + _c(r'--- Below the next line is a copy of the message.'), + _c(r'<(?P[^>]*)>')), # turbosport.com runs something called `MDaemon 3.5.2' ??? - (_c('The following addresses did NOT receive a copy of your message:'), - _c('--- Session Transcript ---'), - _c('[>]\s*(?P.*)$')), + (_c(r'The following addresses did NOT receive a copy of your message:'), + _c(r'--- Session Transcript ---'), + _c(r'[>]\s*(?P.*)$')), # usa.net - (_c('Intended recipient:\s*(?P.*)$'), - _c('--------RETURNED MAIL FOLLOWS--------'), - _c('Intended recipient:\s*(?P.*)$')), + (_c(r'Intended recipient:\s*(?P.*)$'), + _c(r'--------RETURNED MAIL FOLLOWS--------'), + _c(r'Intended recipient:\s*(?P.*)$')), # hotpop.com - (_c('Undeliverable Address:\s*(?P.*)$'), - _c('Original message attached'), - _c('Undeliverable Address:\s*(?P.*)$')), + (_c(r'Undeliverable Address:\s*(?P.*)$'), + _c(r'Original message attached'), + _c(r'Undeliverable Address:\s*(?P.*)$')), # Another demon.co.uk format - (_c('This message was created automatically by mail delivery'), - _c('^---- START OF RETURNED MESSAGE ----'), - _c("addressed to '(?P[^']*)'")), + (_c(r'This message was created automatically by mail delivery'), + _c(r'^---- START OF RETURNED MESSAGE ----'), + _c(r"addressed to '(?P[^']*)'")), # Prodigy.net full mailbox - (_c("User's mailbox is full:"), - _c('Unable to deliver mail.'), - _c("User's mailbox is full:\s*<(?P[^>]*)>")), + (_c(r"User's mailbox is full:"), + _c(r'Unable to deliver mail.'), + _c(r"User's mailbox is full:\s*<(?P[^>]*)>")), # Microsoft SMTPSVC - (_c('The email below could not be delivered to the following user:'), - _c('Old message:'), - _c('<(?P[^>]*)>')), + (_c(r'The email below could not be delivered to the following user:'), + _c(r'Old message:'), + _c(r'<(?P[^>]*)>')), # Yahoo on behalf of other domains like sbcglobal.net - (_c('Unable to deliver message to the following address\(es\)\.'), - _c('--- Original message follows\.'), - _c('<(?P[^>]*)>:')), + (_c(r'Unable to deliver message to the following address\(es\)\.'), + _c(r'--- Original message follows\.'), + _c(r'<(?P[^>]*)>:')), # googlemail.com - (_c('Delivery to the following recipient(s)? failed'), - _c('----- Original message -----'), - _c('^\s*(?P[^\s@]+@[^\s@]+)\s*$')), + (_c(r'Delivery to the following recipient(s)? failed'), + _c(r'----- Original message -----'), + _c(r'^\s*(?P[^\s@]+@[^\s@]+)\s*$')), # kundenserver.de, mxlogic.net - (_c('A message that you( have)? sent could not be delivered'), - _c('^---'), - _c('<(?P[^>]*)>')), + (_c(r'A message that you( have)? sent could not be delivered'), + _c(r'^---'), + _c(r'<(?P[^>]*)>')), # another kundenserver.de - (_c('A message that you( have)? sent could not be delivered'), - _c('^---'), - _c('^(?P[^\s@]+@[^\s@:]+):')), + (_c(r'A message that you( have)? sent could not be delivered'), + _c(r'^---'), + _c(r'^(?P[^\s@]+@[^\s@:]+):')), # thehartford.com and amenworld.com - (_c('Del(i|e)very to the following recipient(s)? (failed|was aborted)'), + (_c(r'Del(i|e)very to the following recipient(s)? (failed|was aborted)'), # this one may or may not have the original message, but there's nothing # unique to stop on, so stop on the first line of at least 3 characters # that doesn't start with 'D' (to not stop immediately) and has no '@'. - _c('^[^D][^@]{2,}$'), - _c('^\s*(. )?(?P[^\s@]+@[^\s@]+)\s*$')), + _c(r'^[^D][^@]{2,}$'), + _c(r'^\s*(. )?(?P[^\s@]+@[^\s@]+)\s*$')), # and another thehartfod.com/hartfordlife.com - (_c('^Your message\s*$'), - _c('^because:'), - _c('^\s*(?P[^\s@]+@[^\s@]+)\s*$')), + (_c(r'^Your message\s*$'), + _c(r'^because:'), + _c(r'^\s*(?P[^\s@]+@[^\s@]+)\s*$')), # kviv.be (InterScan NT) - (_c('^Unable to deliver message to'), + (_c(r'^Unable to deliver message to'), _c(r'\*+\s+End of message\s+\*+'), - _c('<(?P[^>]*)>')), + _c(r'<(?P[^>]*)>')), # earthlink.net supported domains - (_c('^Sorry, unable to deliver your message to'), - _c('^A copy of the original message'), - _c('\s*(?P[^\s@]+@[^\s@]+)\s+')), + (_c(r'^Sorry, unable to deliver your message to'), + _c(r'^A copy of the original message'), + _c(r'\s*(?P[^\s@]+@[^\s@]+)\s+')), # ademe.fr - (_c('^A message could not be delivered to:'), - _c('^Subject:'), - _c('^\s*(?P[^\s@]+@[^\s@]+)\s*$')), + (_c(r'^A message could not be delivered to:'), + _c(r'^Subject:'), + _c(r'^\s*(?P[^\s@]+@[^\s@]+)\s*$')), # andrew.ac.jp - (_c('^Invalid final delivery userid:'), - _c('^Original message follows.'), - _c('\s*(?P[^\s@]+@[^\s@]+)\s*$')), + (_c(r'^Invalid final delivery userid:'), + _c(r'^Original message follows.'), + _c(r'\s*(?P[^\s@]+@[^\s@]+)\s*$')), # E500_SMTP_Mail_Service@lerctr.org and similar - (_c('---- Failed Recipients ----'), - _c(' Mail ----'), - _c('<(?P[^>]*)>')), + (_c(r'---- Failed Recipients ----'), + _c(r' Mail ----'), + _c(r'<(?P[^>]*)>')), # cynergycom.net - (_c('A message that you sent could not be delivered'), - _c('^---'), - _c('(?P[^\s@]+@[^\s@)]+)')), + (_c(r'A message that you sent could not be delivered'), + _c(r'^---'), + _c(r'(?P[^\s@]+@[^\s@)]+)')), # LSMTP for Windows - (_c('^--> Error description:\s*$'), - _c('^Error-End:'), - _c('^Error-for:\s+(?P[^\s@]+@[^\s@]+)')), + (_c(r'^--> Error description:\s*$'), + _c(r'^Error-End:'), + _c(r'^Error-for:\s+(?P[^\s@]+@[^\s@]+)')), # Qmail with a tri-language intro beginning in spanish - (_c('Your message could not be delivered'), - _c('^-'), - _c('<(?P[^>]*)>:')), + (_c(r'Your message could not be delivered'), + _c(r'^-'), + _c(r'<(?P[^>]*)>:')), # socgen.com - (_c('Your message could not be delivered to'), - _c('^\s*$'), - _c('(?P[^\s@]+@[^\s@]+)')), + (_c(r'Your message could not be delivered to'), + _c(r'^\s*$'), + _c(r'(?P[^\s@]+@[^\s@]+)')), # dadoservice.it - (_c('Your message has encountered delivery problems'), - _c('Your message reads'), - _c('addressed to\s*(?P[^\s@]+@[^\s@)]+)')), + (_c(r'Your message has encountered delivery problems'), + _c(r'Your message reads'), + _c(r'addressed to\s*(?P[^\s@]+@[^\s@)]+)')), # gomaps.com - (_c('Did not reach the following recipient'), - _c('^\s*$'), - _c('\s(?P[^\s@]+@[^\s@]+)')), + (_c(r'Did not reach the following recipient'), + _c(r'^\s*$'), + _c(r'\s(?P[^\s@]+@[^\s@]+)')), # EYOU MTA SYSTEM - (_c('This is the deliver program at'), - _c('^-'), - _c('^(?P[^\s@]+@[^\s@<>]+)')), + (_c(r'This is the deliver program at'), + _c(r'^-'), + _c(r'^(?P[^\s@]+@[^\s@<>]+)')), # A non-standard qmail at ieo.it - (_c('this is the email server at'), - _c('^-'), - _c('\s(?P[^\s@]+@[^\s@]+)[\s,]')), + (_c(r'this is the email server at'), + _c(r'^-'), + _c(r'\s(?P[^\s@]+@[^\s@]+)[\s,]')), # pla.net.py (MDaemon.PRO ?) - (_c('- no such user here'), - _c('There is no user'), - _c('^(?P[^\s@]+@[^\s@]+)\s')), + (_c(r'- no such user here'), + _c(r'There is no user'), + _c(r'^(?P[^\s@]+@[^\s@]+)\s')), # fastdnsservers.com - (_c('The following recipient.*could not be reached'), - _c('bogus stop pattern'), - _c('^(?P[^\s@]+@[^\s@]+)\s*$')), + (_c(r'The following recipient.*could not be reached'), + _c(r'bogus stop pattern'), + _c(r'^(?P[^\s@]+@[^\s@]+)\s*$')), # lttf.com - (_c('Could not deliver message to'), - _c('^\s*--'), - _c('^Failed Recipient:\s*(?P[^\s@]+@[^\s@]+)\s*$')), + (_c(r'Could not deliver message to'), + _c(r'^\s*--'), + _c(r'^Failed Recipient:\s*(?P[^\s@]+@[^\s@]+)\s*$')), # uci.edu - (_c('--------Message not delivered'), - _c('--------Error Detail'), - _c('^\s*(?P[^\s@]+@[^\s@]+)\s*$')), + (_c(r'--------Message not delivered'), + _c(r'--------Error Detail'), + _c(r'^\s*(?P[^\s@]+@[^\s@]+)\s*$')), # Dovecot LDA Over quota MDN (bogus - should be DSN). - (_c('^Your message'), - _c('^Reporting'), + (_c(r'^Your message'), + _c(r'^Reporting'), _c( - 'Your message to (?P[^\s@]+@[^\s@]+) was automatically rejected' + r'Your message to (?P[^\s@]+@[^\s@]+) was automatically rejected' )), # mail.ru - (_c('A message that you sent was rejected'), - _c('This is a copy of your message'), - _c('\s(?P[^\s@]+@[^\s@]+)')), + (_c(r'A message that you sent was rejected'), + _c(r'This is a copy of your message'), + _c(r'\s(?P[^\s@]+@[^\s@]+)')), # MailEnable - (_c('Message could not be delivered to some recipients.'), - _c('Message headers follow'), - _c('Recipient: \[SMTP:(?P[^\s@]+@[^\s@]+)\]')), + (_c(r'Message could not be delivered to some recipients.'), + _c(r'Message headers follow'), + _c(r'Recipient: \[SMTP:(?P[^\s@]+@[^\s@]+)\]')), # This one is from Yahoo but dosen't fit the yahoo recognizer format (_c(r'wasn\'t able to deliver the following message'), _c(r'---Below this line is a copy of the message.'), diff --git a/Mailman/Handlers/Approve.py b/Mailman/Handlers/Approve.py index 4e6e57c6..81eda3f7 100644 --- a/Mailman/Handlers/Approve.py +++ b/Mailman/Handlers/Approve.py @@ -121,7 +121,7 @@ def process(mlist, msg, msgdata): # If we don't find the pattern in the decoded part, but we do # find it after stripping HTML tags, we don't know how to remove # it, so we just reject the post. - pattern = name + ':(\xA0|\s| )*' + re.escape(passwd) + pattern = name + r':(\xA0|\s| )*' + re.escape(passwd) for part in typed_subpart_iterator(msg, 'text'): if part is not None and part.get_payload() is not None: lines = part.get_payload(decode=True) diff --git a/Mailman/Handlers/CookHeaders.py b/Mailman/Handlers/CookHeaders.py index 04fb810f..7c3e1fe3 100644 --- a/Mailman/Handlers/CookHeaders.py +++ b/Mailman/Handlers/CookHeaders.py @@ -42,7 +42,7 @@ def _isunicode(s): return isinstance(s, UnicodeType) -nonascii = re.compile('[^\s!-~]') +nonascii = re.compile(r'[^\s!-~]') def uheader(mlist, s, header_name=None, continuation_ws=' ', maxlinelen=None): # Get the charset to encode the string in. Then search if there is any @@ -172,7 +172,7 @@ def process(mlist, msg, msgdata): i18n.set_translation(otrans) uvia = str(via, lcs, errors='replace') # Replace the dummy replacements. - uvia = re.sub(u'%\(lrn\)s', ulrn, re.sub(u'%\(realname\)s', urn, uvia)) + uvia = re.sub(r'%\(lrn\)s', ulrn, re.sub(r'%\(realname\)s', urn, uvia)) # And get an RFC 2047 encoded header string. dn = str(Header(uvia, lcs)) change_header('From', @@ -399,7 +399,7 @@ def prefix_subject(mlist, msg, msgdata): prefix_pattern = re.escape(prefix) # unescape '%' :-< prefix_pattern = '%'.join(prefix_pattern.split(r'\%')) - p = re.compile('%\d*d') + p = re.compile(r'%\d*d') if p.search(prefix, 1): # prefix have number, so we should search prefix w/number in subject. # Also, force new style. @@ -413,7 +413,7 @@ def prefix_subject(mlist, msg, msgdata): # leading space after stripping the prefix. It is not known what MUA would # create such a Subject:, but the issue was reported. rematch = re.match( - '(\s*(RE|AW|SV|VS)\s*(\[\d+\])?\s*:\s*)+', + r'(\s*(RE|AW|SV|VS)\s*(\[\d+\])?\s*:\s*)+', subject, re.I) if rematch: subject = subject[rematch.end():] diff --git a/Mailman/Handlers/Decorate.py b/Mailman/Handlers/Decorate.py index f54ddf8d..67b6cb39 100644 --- a/Mailman/Handlers/Decorate.py +++ b/Mailman/Handlers/Decorate.py @@ -205,7 +205,7 @@ def decorate(mlist, template, what, extradict=None): # `what' is just a descriptive phrase used in the log message # If template is only whitespace, ignore it. - if len(re.sub('\s', '', template)) == 0: + if len(re.sub(r'\s', '', template)) == 0: return '' # BAW: We've found too many situations where Python can be fooled into diff --git a/Mailman/Handlers/SpamDetect.py b/Mailman/Handlers/SpamDetect.py index d7ab361a..5531dd0e 100644 --- a/Mailman/Handlers/SpamDetect.py +++ b/Mailman/Handlers/SpamDetect.py @@ -73,7 +73,7 @@ def getDecodedHeaders(msg, cset='utf-8'): for h, v in list(msg.items()): uvalue = u'' try: - v = decode_header(re.sub('\n\s', ' ', v)) + v = decode_header(re.sub(r'\n\s', ' ', v)) except HeaderParseError: v = [(v, 'us-ascii')] for frag, cs in v: diff --git a/Mailman/Handlers/ToDigest.py b/Mailman/Handlers/ToDigest.py index 7abea5b8..e4c019a2 100644 --- a/Mailman/Handlers/ToDigest.py +++ b/Mailman/Handlers/ToDigest.py @@ -209,7 +209,7 @@ def send_i18n_digests(mlist, mboxfp): print(mastheadtxt, file=plainmsg) print(file=plainmsg) # Now add the optional digest header but only if more than whitespace. - if re.sub('\s', '', mlist.digest_header): + if re.sub(r'\s', '', mlist.digest_header): headertxt = decorate(mlist, mlist.digest_header, _('digest header')) # MIME header = MIMEText(headertxt, _charset=lcset) @@ -359,7 +359,7 @@ def send_i18n_digests(mlist, mboxfp): if not payload.endswith('\n'): print(file=plainmsg) # Now add the footer but only if more than whitespace. - if re.sub('\s', '', mlist.digest_footer): + if re.sub(r'\s', '', mlist.digest_footer): footertxt = decorate(mlist, mlist.digest_footer, _('digest footer')) # MIME footer = MIMEText(footertxt, _charset=lcset) diff --git a/Mailman/SecurityManager.py b/Mailman/SecurityManager.py index 9d927ffb..3d22fe56 100644 --- a/Mailman/SecurityManager.py +++ b/Mailman/SecurityManager.py @@ -365,7 +365,7 @@ def __checkone(self, c, authcontext, user): -splitter = re.compile(';\s*') +splitter = re.compile(r';\s*') def parsecookie(s): c = {} diff --git a/Mailman/Utils.py b/Mailman/Utils.py index deec92d1..aa83c052 100644 --- a/Mailman/Utils.py +++ b/Mailman/Utils.py @@ -1009,7 +1009,7 @@ def strip_verbose_pattern(pattern): elif c == ']' and inclass: inclass = False newpattern += c - elif re.search('\s', c): + elif re.search(r'\s', c): if inclass: if c == NL: newpattern += '\\n' @@ -1484,7 +1484,7 @@ def check_eq_domains(email, domains_list): except ValueError: return [] domain = domain.lower() - domains_list = re.sub('\s', '', domains_list).lower() + domains_list = re.sub(r'\s', '', domains_list).lower() domains = domains_list.split(';') domains_list = [] for d in domains: diff --git a/bin/rmlist b/bin/rmlist index d942a394..fd182746 100755 --- a/bin/rmlist +++ b/bin/rmlist @@ -128,7 +128,7 @@ def main(): # Remove any held messages for this list for filename in os.listdir(mm_cfg.DATA_DIR): - cre = re.compile('^heldmsg-%s-\d+\.(pck|txt)$' % re.escape(listname), + cre = re.compile(r'^heldmsg-%s-\d+\.(pck|txt)$' % re.escape(listname), re.IGNORECASE) if cre.match(filename): REMOVABLES.append((os.path.join(mm_cfg.DATA_DIR, filename), From 9232af5a561533a311c4dd70da14994f746e9db1 Mon Sep 17 00:00:00 2001 From: Nicolas Mendoza Date: Thu, 9 Jan 2025 15:08:35 +0100 Subject: [PATCH 005/748] Replace deprecated list type (#11) --- Mailman/Message.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mailman/Message.py b/Mailman/Message.py index ceff3031..4667252f 100644 --- a/Mailman/Message.py +++ b/Mailman/Message.py @@ -261,7 +261,7 @@ def __init__(self, recip, sender, subject=None, text=None, lang=None): self['Subject'] = Header(subject, charset, header_name='Subject', errors='replace') self['From'] = sender - if isinstance(recip, ListType): + if isinstance(recip, list): self['To'] = COMMASPACE.join(recip) self.recips = recip else: From b24a911bba93ab95543ffef666b381da06fe7d47 Mon Sep 17 00:00:00 2001 From: Nicolas Mendoza Date: Thu, 9 Jan 2025 15:08:56 +0100 Subject: [PATCH 006/748] Doc string fix (#12) * Let username parameter work * Fix doc_string formatting --- bin/newlist | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bin/newlist b/bin/newlist index 8c9a680e..66079157 100755 --- a/bin/newlist +++ b/bin/newlist @@ -17,9 +17,9 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, # USA. -f"""Create a new, unpopulated mailing list. +"""Create a new, unpopulated mailing list. -Usage: %(PROGRAM) [options] [listname [listadmin-addr [admin-password]]] +Usage: {PROGRAM} [options] [listname [listadmin-addr [admin-password]]] Options: @@ -123,7 +123,7 @@ def usage(code, msg=''): fd = sys.stderr else: fd = sys.stdout - print(C_(__doc__), file=fd) + print(C_(__doc__.format( PROGRAM = PROGRAM )), file=fd) if msg: print(msg, file=fd) sys.exit(code) From 0fcd39e7804bf430bf8dc03991eb9976751533c0 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 20 Apr 2025 05:10:40 -0400 Subject: [PATCH 007/748] update --- Mailman/Handlers/CookHeaders.py | 4 +- Mailman/Handlers/SMTPDirect.py | 2 +- Mailman/MailList.py | 73 ++++++++++++++++-- Mailman/Pending.py | 7 ++ Mailman/Queue/CommandRunner.py | 5 +- Mailman/Queue/Switchboard.py | 25 ++++++- Mailman/Site.py | 9 ++- Mailman/UserDesc.py | 4 +- Mailman/Utils.py | 36 ++++----- bin/arch | 7 +- bin/b4b5-archfix | 6 +- bin/check_db | 34 +++++++-- bin/check_perms | 7 +- bin/dumpdb | 22 ++++++ bin/export.py | 36 ++++++++- bin/list_members | 23 ++---- bin/list_owners | 3 + bin/mailmanctl | 10 +-- bin/mmsitepass | 6 +- bin/newlist | 6 +- bin/qrunner | 8 +- bin/rb-archfix | 6 +- bin/sync_members | 3 + bin/update | 127 ++++++++++++++++++++++++-------- cron/checkdbs | 6 +- cron/mailpasswds | 2 +- 26 files changed, 346 insertions(+), 131 deletions(-) diff --git a/Mailman/Handlers/CookHeaders.py b/Mailman/Handlers/CookHeaders.py index 7c3e1fe3..d17fbe40 100644 --- a/Mailman/Handlers/CookHeaders.py +++ b/Mailman/Handlers/CookHeaders.py @@ -38,9 +38,8 @@ COMMASPACE = ', ' MAXLINELEN = 78 - def _isunicode(s): - return isinstance(s, UnicodeType) + return isinstance(s, str) nonascii = re.compile(r'[^\s!-~]') @@ -81,7 +80,6 @@ def change_header(name, value, mlist, msg, msgdata, delete=True, repl=True): msg[name] = value - def process(mlist, msg, msgdata): # Set the "X-Ack: no" header if noack flag is set. if msgdata.get('noack'): diff --git a/Mailman/Handlers/SMTPDirect.py b/Mailman/Handlers/SMTPDirect.py index 9d716d0e..d8675197 100644 --- a/Mailman/Handlers/SMTPDirect.py +++ b/Mailman/Handlers/SMTPDirect.py @@ -359,7 +359,7 @@ def verpdeliver(mlist, msg, msgdata, envsender, failures, conn): charset = 'iso-8859-1' charset = Charset(charset) codec = charset.input_codec or 'ascii' - if not isinstance(name, UnicodeType): + if not isinstance(name, str): name = str(name, codec, 'replace') name = Header(name, charset).encode() msgcopy['To'] = formataddr((name, recip)) diff --git a/Mailman/MailList.py b/Mailman/MailList.py index 08b1c49e..ed432ea8 100644 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -175,7 +175,10 @@ def Locked(self): # Useful accessors # def internal_name(self): - return self._internal_name + name = self._internal_name + if isinstance(name, bytes): + name = name.decode('utf-8', 'replace') + return name def fullpath(self): return self._full_path @@ -291,6 +294,9 @@ def InitTempVars(self, name): # TBD: is this a good choice of lifetime? lifetime = mm_cfg.LIST_LOCK_LIFETIME, withlogging = mm_cfg.LIST_LOCK_DEBUGGING) + # Ensure name is a string + if isinstance(name, bytes): + name = name.decode('utf-8', 'replace') self._internal_name = name if name: self._full_path = Site.get_listpath(name) @@ -312,7 +318,10 @@ def InitVars(self, name=None, admin='', crypted_password='', """Assign default values - some will be overriden by stored state.""" # Non-configurable list info if name: - self._internal_name = name + # Ensure name is a string + if isinstance(name, bytes): + name = name.decode('utf-8', 'replace') + self._internal_name = name # When was the list created? self.created_at = time.time() @@ -600,7 +609,33 @@ def Save(self): for key, value in list(self.__dict__.items()): if key[0] == '_' or type(value) is MethodType: continue - dict[key] = value + # Ensure string values are properly encoded + if isinstance(value, str): + dict[key] = value + elif isinstance(value, bytes): + # Convert bytes to string if possible + try: + dict[key] = value.decode('utf-8', 'replace') + except UnicodeDecodeError: + dict[key] = value.decode('latin1', 'replace') + elif isinstance(value, list): + # Handle lists that might contain bytes + dict[key] = [ + v.decode('utf-8', 'replace') if isinstance(v, bytes) else v + for v in value + ] + elif type(value) is dict: + # Handle dicts that might contain bytes + new_dict = {} + for k, v in value.items(): + if isinstance(k, bytes): + k = k.decode('utf-8', 'replace') + if isinstance(v, bytes): + v = v.decode('utf-8', 'replace') + new_dict[k] = v + dict[key] = new_dict + else: + dict[key] = value # Make config.pck unreadable by `other', as it contains all the # list members' passwords (in clear text). omask = os.umask(0o007) @@ -659,8 +694,6 @@ def __load(self, dbfile): dict_retval = marshal.load(fp) elif dbfile.endswith('.pck') or dbfile.endswith('.pck.last'): dict_retval = pickle.load(fp, fix_imports=True, encoding='latin1') -# dict_retval = loadfunc(fp) - if not isinstance(dict_retval, dict): return None, 'Load() expected to return a dictionary' except (EOFError, ValueError, TypeError, MemoryError, @@ -705,6 +738,32 @@ def Load(self, check_version=True): syslog('error', 'All %s fallbacks were corrupt, giving up', self.internal_name()) raise Errors.MMCorruptListDatabaseError(e) + + # Ensure string values are properly decoded + for key, value in dict_retval.items(): + if isinstance(value, bytes): + try: + dict_retval[key] = value.decode('utf-8', 'replace') + except UnicodeDecodeError: + # If UTF-8 fails, try latin1 as a fallback + dict_retval[key] = value.decode('latin1', 'replace') + elif isinstance(value, list): + # Handle lists that might contain bytes + dict_retval[key] = [ + v.decode('utf-8', 'replace') if isinstance(v, bytes) else v + for v in value + ] + elif isinstance(value, dict): + # Handle dicts that might contain bytes + new_dict = {} + for k, v in value.items(): + if isinstance(k, bytes): + k = k.decode('utf-8', 'replace') + if isinstance(v, bytes): + v = v.decode('utf-8', 'replace') + new_dict[k] = v + dict_retval[key] = new_dict + # Now, if we didn't end up using the primary database file, we want to # copy the fallback into the primary so that the logic in Save() will # still work. For giggles, we'll copy it to a safety backup. Note we @@ -1123,7 +1182,7 @@ def ApprovedAddMember(self, userdesc, ack=None, admin_notif=None, text='', subject = _('%(realname)s subscription notification') finally: i18n.set_translation(otrans) - if isinstance(name, UnicodeType): + if isinstance(name, str): name = name.encode(Utils.GetCharSet(lang), 'replace') text = Utils.maketext( "adminsubscribeack.txt", @@ -1328,7 +1387,7 @@ def log_and_notify_admin(self, oldaddr, newaddr): name = self.getMemberName(newaddr) if name is None: name = '' - if isinstance(name, UnicodeType): + if isinstance(name, str): name = name.encode(Utils.GetCharSet(lang), 'replace') text = Utils.maketext( 'adminaddrchgack.txt', diff --git a/Mailman/Pending.py b/Mailman/Pending.py index 0d75342d..0d71358f 100644 --- a/Mailman/Pending.py +++ b/Mailman/Pending.py @@ -60,6 +60,13 @@ def pend_new(self, op, *content, **kws): assert self.Locked() # Load the database db = self.__load() + + # Ensure content is properly encoded + content = [ + c.decode('utf-8', 'replace') if isinstance(c, bytes) else c + for c in content + ] + # Calculate a unique cookie. Algorithm vetted by the Timbot. time() # has high resolution on Linux, clock() on Windows. random gives us # about 45 bits in Python 2.2, 53 bits on Python 2.3. The time and diff --git a/Mailman/Queue/CommandRunner.py b/Mailman/Queue/CommandRunner.py index ff7003d3..e4b594b5 100644 --- a/Mailman/Queue/CommandRunner.py +++ b/Mailman/Queue/CommandRunner.py @@ -23,7 +23,6 @@ # -owner. - # BAW: get rid of this when we Python 2.2 is a minimum requirement. import re @@ -50,7 +49,6 @@ BADCMD = 2 BADSUBJ = 3 - class Results: def __init__(self, mlist, msg, msgdata): self.mlist = mlist @@ -198,7 +196,7 @@ def indent(lines): charset = Utils.GetCharSet(self.msgdata['lang']) encoded_resp = [] for item in resp: - if isinstance(item, UnicodeType): + if isinstance(item, str): item = item.encode(charset, 'replace') encoded_resp.append(item) results = MIMEText(NL.join(encoded_resp), _charset=charset) @@ -233,7 +231,6 @@ def indent(lines): msg.send(self.mlist) - class CommandRunner(Runner): QDIR = mm_cfg.CMDQUEUE_DIR diff --git a/Mailman/Queue/Switchboard.py b/Mailman/Queue/Switchboard.py index df6729e4..7edded06 100644 --- a/Mailman/Queue/Switchboard.py +++ b/Mailman/Queue/Switchboard.py @@ -63,7 +63,6 @@ MAX_BAK_COUNT = 3 - class Switchboard: def __init__(self, whichq, slice=None, numslices=1, recover=False): self.__whichq = whichq @@ -96,6 +95,30 @@ def enqueue(self, _msg, _metadata={}, **_kws): # of parallel qrunner processes. data = _metadata.copy() data.update(_kws) + + # Ensure metadata values are properly encoded + for key, value in list(data.items()): + if isinstance(key, bytes): + del data[key] + key = key.decode('utf-8', 'replace') + if isinstance(value, bytes): + value = value.decode('utf-8', 'replace') + elif isinstance(value, list): + value = [ + v.decode('utf-8', 'replace') if isinstance(v, bytes) else v + for v in value + ] + elif isinstance(value, dict): + new_dict = {} + for k, v in value.items(): + if isinstance(k, bytes): + k = k.decode('utf-8', 'replace') + if isinstance(v, bytes): + v = v.decode('utf-8', 'replace') + new_dict[k] = v + value = new_dict + data[key] = value + listname = data.get('listname', '--nolist--') # Get some data for the input to the sha hash now = time.time() diff --git a/Mailman/Site.py b/Mailman/Site.py index 6fa6afb1..8e03d6a0 100644 --- a/Mailman/Site.py +++ b/Mailman/Site.py @@ -100,7 +100,14 @@ def get_listnames(domain=None): from Mailman.Utils import list_exists # We don't currently support separate virtual domain directories got = [] - for fn in os.listdir(mm_cfg.LIST_DATA_DIR): + # Ensure LIST_DATA_DIR is a string + list_dir = mm_cfg.LIST_DATA_DIR + if isinstance(list_dir, bytes): + list_dir = list_dir.decode('utf-8', 'replace') + for fn in os.listdir(list_dir): if list_exists(fn): + # Ensure we return strings, not bytes + if isinstance(fn, bytes): + fn = fn.decode('utf-8', 'replace') got.append(fn) return got diff --git a/Mailman/UserDesc.py b/Mailman/UserDesc.py index 39b45087..575749f5 100644 --- a/Mailman/UserDesc.py +++ b/Mailman/UserDesc.py @@ -57,9 +57,9 @@ def __repr__(self): digest = 'yes' language = getattr(self, 'language', 'n/a') # Make sure fullname and password are encoded if they're strings - if isinstance(fullname, UnicodeType): + if isinstance(fullname, str): fullname = fullname.encode('ascii', 'replace') - if isinstance(password, UnicodeType): + if isinstance(password, str): password = password.encode('ascii', 'replace') return '' % ( address, fullname, password, digest, language) diff --git a/Mailman/Utils.py b/Mailman/Utils.py index aa83c052..c5272819 100644 --- a/Mailman/Utils.py +++ b/Mailman/Utils.py @@ -92,7 +92,6 @@ dre = re.compile(r'(\${2})|\$([_a-z]\w*)|\${([_a-z]\w*)}', re.IGNORECASE) - def list_exists(listname): """Return true iff list `listname' exists.""" # The existance of any of the following file proves the list exists @@ -122,11 +121,20 @@ def list_exists(listname): def list_names(): """Return the names of all lists in default list directory.""" # We don't currently support separate listings of virtual domains - return Site.get_listnames() + # Ensure LIST_DATA_DIR is a string + list_dir = mm_cfg.LIST_DATA_DIR + if isinstance(list_dir, bytes): + list_dir = list_dir.decode('utf-8', 'replace') + names = [] + for name in os.listdir(list_dir): + if list_exists(name): + # Ensure we return strings, not bytes + if isinstance(name, bytes): + name = name.decode('utf-8', 'replace') + names.append(name) + return names - -# a much more naive implementation than say, Emacs's fill-paragraph! def wrap(text, column=70, honor_leading_ws=True): """Wrap and fill the text to the specified column. @@ -201,7 +209,6 @@ def wrap(text, column=70, honor_leading_ws=True): return wrapped[:-2] - def QuotePeriods(text): JOINER = '\n .\n' SEP = '\n.\n' @@ -265,7 +272,6 @@ def ValidateEmail(s): raise Exception(Errors.MMHostileAddress, s) - # Patterns which may be used to form malicious path to inject a new # line in the mailman error log. (TK: advisory by Moritz Naumann) CRNLpat = re.compile(r'[^\x21-\x7e]') @@ -303,12 +309,10 @@ def GetPathPieces(envar='PATH_INFO'): return None - def GetRequestMethod(): return os.environ.get('REQUEST_METHOD') - def ScriptURL(target, web_page_url=None, absolute=False): """target - scriptname only, nothing extra web_page_url - the list's configvar of the same name @@ -337,7 +341,6 @@ def ScriptURL(target, web_page_url=None, absolute=False): return path + mm_cfg.CGIEXT - def GetPossibleMatchingAddrs(name): """returns a sorted list of addresses that could possibly match a given name. @@ -357,7 +360,6 @@ def GetPossibleMatchingAddrs(name): return res - def List2Dict(L, foldcase=False): """Return a dict keyed by the entries in the list passed to it.""" d = {} @@ -370,7 +372,6 @@ def List2Dict(L, foldcase=False): return d - _vowels = ('a', 'e', 'i', 'o', 'u') _consonants = ('b', 'c', 'd', 'f', 'g', 'h', 'k', 'm', 'n', 'p', 'r', 's', 't', 'v', 'w', 'x', 'z') @@ -441,7 +442,6 @@ def mkletter(c): return "%c%c" % tuple(map(mkletter, (chr1, chr2))) - def set_global_password(pw, siteadmin=True): if siteadmin: filename = mm_cfg.SITE_PW_FILE @@ -480,7 +480,6 @@ def check_global_password(response, siteadmin=True): return challenge == sha_new(response).hexdigest() - _ampre = re.compile('&((?:#[0-9]+|[a-z]+);)', re.IGNORECASE) def websafe(s, doubleescape=False): # If a user submits a form or URL with post data or query fragments @@ -519,7 +518,6 @@ def nntpsplit(s): return s, 119 - # Just changing these two functions should be enough to control the way # that email address obscuring is handled. def ObscureEmail(addr, for_text=False): @@ -539,7 +537,6 @@ def UnobscureEmail(addr): return addr.replace('--at--', '@') - class OuterExit(Exception): pass @@ -658,7 +655,6 @@ def maketext(templatefile, dict=None, raw=False, lang=None, mlist=None): return findtext(templatefile, dict, raw, lang, mlist)[0] - ADMINDATA = { # admin keyword: (minimum #args, maximum #args) 'confirm': (1, 1), @@ -715,7 +711,6 @@ def is_administrivia(msg): return False - def GetRequestURI(fallback=None, escape=True): """Return the full virtual path this CGI script was invoked with. @@ -740,7 +735,6 @@ def GetRequestURI(fallback=None, escape=True): return url - # Wait on a dictionary of child pids def reap(kids, func=None, once=False): while kids: @@ -763,7 +757,7 @@ def reap(kids, func=None, once=False): if once: break - + def GetLanguageDescr(lang): return mm_cfg.LC_DESCRIPTIONS[lang][0] @@ -778,7 +772,6 @@ def IsLanguage(lang): return lang in mm_cfg.LC_DESCRIPTIONS - def get_domain(): host = os.environ.get('HTTP_HOST', os.environ.get('SERVER_NAME')) port = os.environ.get('SERVER_PORT') @@ -804,7 +797,6 @@ def get_site_email(hostname=None, extra=None): return '%s-%s@%s' % (mm_cfg.MAILMAN_SITE_LIST, extra, hostname) - # This algorithm crafts a guaranteed unique message-id. The theory here is # that pid+listname+host will distinguish the message-id for every process on # the system, except when process ids wrap around. To further distinguish @@ -831,7 +823,6 @@ def midnight(date=None): return time.mktime(date + (0,)*5 + (-1,)) - # Utilities to convert from simplified $identifier substitutions to/from # standard Python $(identifier)s substititions. The "Guido rules" for the # former are: @@ -881,7 +872,6 @@ def percent_identifiers(s): return d - # Utilities to canonicalize a string, which means un-HTML-ifying the string to # produce a Unicode string or an 8-bit string if all the characters are ASCII. def canonstr(s, lang=None): diff --git a/bin/arch b/bin/arch index d649d137..b7af5572 100644 --- a/bin/arch +++ b/bin/arch @@ -76,19 +76,20 @@ PROGRAM = sys.argv[0] i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) - def usage(code, msg=''): if code: fd = sys.stderr else: fd = sys.stdout + # Ensure PROGRAM is a string, not bytes + if isinstance(PROGRAM, bytes): + PROGRAM = PROGRAM.decode('utf-8', 'replace') print(C_(__doc__), file=fd) if msg: print(msg, file=fd) sys.exit(code) - def main(): # get command line arguments try: @@ -196,6 +197,6 @@ def main(): if mlist: mlist.Unlock() - + if __name__ == '__main__': main() diff --git a/bin/b4b5-archfix b/bin/b4b5-archfix index 0544cb8e..ce6ecb90 100644 --- a/bin/b4b5-archfix +++ b/bin/b4b5-archfix @@ -50,19 +50,20 @@ from Mailman.i18n import C_ PROGRAM = sys.argv[0] - def usage(code, msg=''): if code: fd = sys.stderr else: fd = sys.stdout + # Ensure PROGRAM is a string, not bytes + if isinstance(PROGRAM, bytes): + PROGRAM = PROGRAM.decode('utf-8', 'replace') print(C_(__doc__), file=fd) if msg: print(msg, file=fd) sys.exit(code) - def main(): # get command line arguments try: @@ -92,6 +93,5 @@ def main(): print('You should now run "bin/check_perms -f"') - if __name__ == '__main__': main() diff --git a/bin/check_db b/bin/check_db index b874c227..ef45deb1 100755 --- a/bin/check_db +++ b/bin/check_db @@ -64,19 +64,20 @@ from Mailman.i18n import C_ PROGRAM = sys.argv[0] - def usage(code, msg=''): if code: fd = sys.stderr else: fd = sys.stdout + # Ensure PROGRAM is a string, not bytes + if isinstance(PROGRAM, bytes): + PROGRAM = PROGRAM.decode('utf-8', 'replace') print(C_(__doc__), file=fd) if msg: print(msg, file=fd) sys.exit(code) - def testfile(dbfile): if dbfile.endswith('.db') or dbfile.endswith('.db.last'): loadfunc = marshal.load @@ -84,13 +85,35 @@ def testfile(dbfile): loadfunc = pickle.load else: assert 0 - fp = open(dbfile) + fp = open(dbfile, 'rb') try: - loadfunc(fp) + data = loadfunc(fp) + # Handle string/bytes conversion + if isinstance(data, bytes): + data = data.decode('utf-8', 'replace') + elif isinstance(data, dict): + new_data = {} + for k, v in data.items(): + if isinstance(k, bytes): + k = k.decode('utf-8', 'replace') + if isinstance(v, bytes): + v = v.decode('utf-8', 'replace') + elif isinstance(v, (list, tuple)): + v = list(v) # Convert tuple to list for modification + for i, item in enumerate(v): + if isinstance(item, bytes): + v[i] = item.decode('utf-8', 'replace') + new_data[k] = v + data = new_data + elif isinstance(data, (list, tuple)): + data = list(data) # Convert tuple to list for modification + for i, item in enumerate(data): + if isinstance(item, bytes): + data[i] = item.decode('utf-8', 'replace') finally: fp.close() - + def main(): try: opts, args = getopt.getopt(sys.argv[1:], 'ahv', @@ -148,6 +171,5 @@ def main(): print(C_(' %(file)s: okay')) - if __name__ == '__main__': main() diff --git a/bin/check_perms b/bin/check_perms index b9518c36..b69fe277 100755 --- a/bin/check_perms +++ b/bin/check_perms @@ -55,7 +55,6 @@ PROGRAM = sys.argv[0] # Gotta check the archives/private/*/database/* files - class State: FIX = False VERBOSE = False @@ -70,7 +69,6 @@ ARTICLEFILEPERMS = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP PRIVATEPERMS = QFILEPERMS - def statmode(path): return os.stat(path)[ST_MODE] @@ -91,7 +89,6 @@ def getgrgid(gid): return data - def checkwalk(arg, dirname, names): # Short-circuit duplicates if seen.has_key(dirname): @@ -354,12 +351,14 @@ def checkdata(): print() - def usage(code, msg=''): if code: fd = sys.stderr else: fd = sys.stdout + # Ensure PROGRAM is a string, not bytes + if isinstance(PROGRAM, bytes): + PROGRAM = PROGRAM.decode('utf-8', 'replace') print(C_(__doc__), file=fd) if msg: print(msg, file=fd) diff --git a/bin/dumpdb b/bin/dumpdb index 2800dd26..86a9fe57 100644 --- a/bin/dumpdb +++ b/bin/dumpdb @@ -64,6 +64,9 @@ def usage(code, msg=''): fd = sys.stderr else: fd = sys.stdout + # Ensure PROGRAM is a string, not bytes + if isinstance(PROGRAM, bytes): + PROGRAM = PROGRAM.decode('utf-8', 'replace') print(C_(__doc__) % globals(), file=fd) if msg: print(msg, file=fd) @@ -126,6 +129,25 @@ def main(): while True: try: obj = load(fp) + # Handle string/bytes conversion + if isinstance(obj, bytes): + obj = obj.decode('utf-8', 'replace') + elif isinstance(obj, dict): + new_obj = {} + for k, v in obj.items(): + if isinstance(k, bytes): + k = k.decode('utf-8', 'replace') + if isinstance(v, bytes): + v = v.decode('utf-8', 'replace') + new_obj[k] = v + obj = new_obj + elif isinstance(obj, list): + new_obj = [] + for item in obj: + if isinstance(item, bytes): + item = item.decode('utf-8', 'replace') + new_obj.append(item) + obj = new_obj except EOFError: if doprint: print(C_('[----- end %(typename)s file -----]')) diff --git a/bin/export.py b/bin/export.py index 5c88c1b8..b9f32a40 100644 --- a/bin/export.py +++ b/bin/export.py @@ -26,6 +26,7 @@ import codecs import datetime import optparse +import pickle from xml.sax.saxutils import escape @@ -102,6 +103,8 @@ def _makeattrs(self, tagattrs): if v is None: v = '' else: + if isinstance(v, bytes): + v = v.decode('utf-8', 'replace') v = escape(str(v)) attrs.append('%s="%s"' % (k, v)) return SPACE.join(attrs) @@ -146,7 +149,9 @@ def _element(self, _name, _value=None, **_attributes): if _value is None: print('<%s%s/>' % (_name, attrs), file=self._fp) else: - value = escape(unicode(_value)) + if isinstance(_value, bytes): + _value = _value.decode('utf-8', 'replace') + value = escape(str(_value)) print('<%s%s>%s' % (_name, attrs, value, _name), file=self._fp) def _do_list_categories(self, mlist, k, subcat=None): @@ -179,9 +184,13 @@ def _do_list_categories(self, mlist, k, subcat=None): if isinstance(value, list): self._push_element('option', name=varname, type=widget_type) for v in value: + if isinstance(v, bytes): + v = v.decode('utf-8', 'replace') self._element('value', v) self._pop_element('option') else: + if isinstance(value, bytes): + value = value.decode('utf-8', 'replace') self._element('option', value, name=varname, type=widget_type) def _dump_list(self, mlist, password_scheme): @@ -259,6 +268,30 @@ def _dump_list(self, mlist, password_scheme): self._pop_element('roster') self._pop_element('list') + def _do_list_archives(self, mlist): + # Get the archive directory + archive_dir = os.path.join(mlist.archive_dir(), 'private') + if not os.path.exists(archive_dir): + return + # Get all the archive files + for filename in os.listdir(archive_dir): + if filename.endswith('.mbox'): + if isinstance(filename, bytes): + filename = filename.decode('utf-8', 'replace') + self._push_element('archive', filename=filename) + # Get the archive file's metadata + metadata_file = os.path.join(archive_dir, filename + '.metadata') + if os.path.exists(metadata_file): + with open(metadata_file, 'rb') as fp: + metadata = pickle.load(fp) + for key, value in metadata.items(): + if isinstance(key, bytes): + key = key.decode('utf-8', 'replace') + if isinstance(value, bytes): + value = value.decode('utf-8', 'replace') + self._element('metadata', str(value), name=key) + self._pop_element('archive') + def dump(self, listnames, password_scheme): print('', file=self._fp) self._push_element('mailman', **{ @@ -272,6 +305,7 @@ def dump(self, listnames, password_scheme): print(C_('No such list: %(listname)s'), file=sys.stderr) continue self._dump_list(mlist, password_scheme) + self._do_list_archives(mlist) self._pop_element('mailman') def close(self): diff --git a/bin/list_members b/bin/list_members index a26b1977..afde12fc 100755 --- a/bin/list_members +++ b/bin/list_members @@ -105,21 +105,15 @@ def usage(code, msg=''): fd = sys.stderr else: fd = sys.stdout + # Ensure PROGRAM is a string, not bytes + if isinstance(PROGRAM, bytes): + PROGRAM = PROGRAM.decode('utf-8', 'replace') print(C_(__doc__), file=fd) if msg: print(msg, file=fd) sys.exit(code) - -def safe(s): - if not s: - return '' - if isinstance(s, UnicodeType): - return s.encode(ENC, 'replace') - return str(s, ENC, 'replace').encode(ENC, 'replace') - - def isinvalid(addr): try: Utils.ValidateEmail(addr) @@ -128,10 +122,9 @@ def isinvalid(addr): return True def isunicode(addr): - return isinstance(addr, UnicodeType) + return isinstance(addr, str) - def whymatches(mlist, addr, why): # Return true if the `why' matches the reason the address is enabled, or # in the case of why is None, that they are disabled for any reason @@ -142,7 +135,6 @@ def whymatches(mlist, addr, why): return status == WHYCHOICES[why] - def main(): # Because of the optional arguments, we can't use getopt. :( outfile = None @@ -273,7 +265,7 @@ def main(): mm_cfg.Moderate): showit = True if showit: - print(formataddr((safe(name), addr)), file=fp) + print(formataddr((name, addr)), file=fp) return if regular: rmembers.sort() @@ -282,7 +274,7 @@ def main(): # Filter out nomails if nomail and not whymatches(mlist, addr, why): continue - print(formataddr((safe(name), addr)), file=fp) + print(formataddr((name, addr)), file=fp) if digest: dmembers.sort() for addr in dmembers: @@ -299,9 +291,8 @@ def main(): # They're getting MIME digests if kind == 'plain': continue - print(formataddr((safe(name), addr)), file=fp) + print(formataddr((name, addr)), file=fp) - if __name__ == '__main__': main() diff --git a/bin/list_owners b/bin/list_owners index 507f7b73..9ace620b 100644 --- a/bin/list_owners +++ b/bin/list_owners @@ -60,6 +60,9 @@ def usage(code, msg=''): fd = sys.stderr else: fd = sys.stdout + # Ensure PROGRAM is a string, not bytes + if isinstance(PROGRAM, bytes): + PROGRAM = PROGRAM.decode('utf-8', 'replace') print(C_(__doc__), file=fd) if msg: print(msg, file=fd) diff --git a/bin/mailmanctl b/bin/mailmanctl index 36016e27..eb80299f 100644 --- a/bin/mailmanctl +++ b/bin/mailmanctl @@ -127,19 +127,20 @@ MAX_RESTARTS = 10 LogStdErr('error', 'mailmanctl', manual_reprime=0) - def usage(code, msg=''): if code: fd = sys.stderr else: fd = sys.stdout + # Ensure PROGRAM is a string, not bytes + if isinstance(PROGRAM, bytes): + PROGRAM = PROGRAM.decode('utf-8', 'replace') print(C_(__doc__), file=fd) if msg: print(msg, file=fd) sys.exit(code) - def kill_watcher(sig): try: fp = open(mm_cfg.PIDFILE) @@ -163,7 +164,6 @@ def kill_watcher(sig): os.unlink(mm_cfg.PIDFILE) - def get_lock_data(): # Return the hostname, pid, and tempfile fp = open(LOCKFILE) @@ -242,7 +242,6 @@ Lock host: %(status)s Exiting."""), file=sys.stderr) - def start_runner(qrname, slice, count): pid = os.fork() if pid: @@ -271,7 +270,6 @@ def start_all_runners(): return kids - def check_for_site_list(): sitelistname = mm_cfg.MAILMAN_SITE_LIST try: @@ -306,7 +304,6 @@ def check_privs(): 'Run this program as root or as the %(name)s user, or use -u.')) - def main(): global quiet try: @@ -551,6 +548,5 @@ Qrunner %s reached maximum restart limit of %d, not restarting.""", os._exit(0) - if __name__ == '__main__': main() diff --git a/bin/mmsitepass b/bin/mmsitepass index 86d9f403..d9fdb332 100755 --- a/bin/mmsitepass +++ b/bin/mmsitepass @@ -48,19 +48,20 @@ from Mailman import Utils PROGRAM = sys.argv[0] - def usage(code, msg=''): if code: fd = sys.stderr else: fd = sys.stdout + # Ensure PROGRAM is a string, not bytes + if isinstance(PROGRAM, bytes): + PROGRAM = PROGRAM.decode('utf-8', 'replace') print(__doc__, file=fd) if msg: print(msg, file=fd) sys.exit(code) - def main(): try: opts, args = getopt.getopt(sys.argv[1:], 'ch', @@ -100,6 +101,5 @@ def main(): print('Password change failed.') - if __name__ == '__main__': main() diff --git a/bin/newlist b/bin/newlist index 66079157..83ac1ee9 100755 --- a/bin/newlist +++ b/bin/newlist @@ -117,19 +117,20 @@ C_ = i18n.C_ PROGRAM = sys.argv[0] - def usage(code, msg=''): if code: fd = sys.stderr else: fd = sys.stdout + # Ensure PROGRAM is a string, not bytes + if isinstance(PROGRAM, bytes): + PROGRAM = PROGRAM.decode('utf-8', 'replace') print(C_(__doc__.format( PROGRAM = PROGRAM )), file=fd) if msg: print(msg, file=fd) sys.exit(code) - def main(): try: opts, args = getopt.getopt(sys.argv[1:], 'hqal:u:e:', @@ -269,6 +270,5 @@ def main(): i18n.set_translation(otrans) - if __name__ == '__main__': main() diff --git a/bin/qrunner b/bin/qrunner index bd2f263c..f9a9f380 100644 --- a/bin/qrunner +++ b/bin/qrunner @@ -90,19 +90,20 @@ COMMASPACE = ', ' AS_SUBPROC = 0 - def usage(code, msg=''): if code: fd = sys.stderr else: fd = sys.stdout + # Ensure PROGRAM is a string, not bytes + if isinstance(PROGRAM, bytes): + PROGRAM = PROGRAM.decode('utf-8', 'replace') print(C_(__doc__), file=fd) if msg: print(msg, file=fd) sys.exit(code) - def make_qrunner(name, slice, range, once=0): modulename = 'Mailman.Queue.' + name try: @@ -127,7 +128,6 @@ def make_qrunner(name, slice, range, once=0): return qrunner - def set_signals(loop): # Set up the SIGTERM handler for stopping the loop def sigterm_handler(signum, frame, loop=loop): @@ -154,7 +154,6 @@ def set_signals(loop): signal.signal(signal.SIGHUP, sighup_handler) - def main(): global AS_SUBPROC try: @@ -276,6 +275,5 @@ def main(): sys.exit(loop.status) - if __name__ == '__main__': main() diff --git a/bin/rb-archfix b/bin/rb-archfix index 7b566bb0..859e671e 100644 --- a/bin/rb-archfix +++ b/bin/rb-archfix @@ -58,19 +58,20 @@ from Mailman.i18n import C_ PROGRAM = sys.argv[0] - def usage(code, msg=''): if code: fd = sys.stderr else: fd = sys.stdout + # Ensure PROGRAM is a string, not bytes + if isinstance(PROGRAM, bytes): + PROGRAM = PROGRAM.decode('utf-8', 'replace') print(C_(__doc__), file=fd) if msg: print(msg, file=fd) sys.exit(code) - def main(): # get command line arguments try: @@ -104,6 +105,5 @@ def main(): print('You should now run "bin/check_perms -f"') - if __name__ == '__main__': main() diff --git a/bin/sync_members b/bin/sync_members index 26801415..a2a42eeb 100755 --- a/bin/sync_members +++ b/bin/sync_members @@ -97,6 +97,9 @@ def usage(code, msg=''): fd = sys.stderr else: fd = sys.stdout + # Ensure PROGRAM is a string, not bytes + if isinstance(PROGRAM, bytes): + PROGRAM = PROGRAM.decode('utf-8', 'replace') print(C_(__doc__), file=fd) if msg: print(msg, file=fd) diff --git a/bin/update b/bin/update index 4577c845..b10e09d2 100755 --- a/bin/update +++ b/bin/update @@ -66,7 +66,6 @@ LMVFILE = os.path.join(mm_cfg.DATA_DIR, 'last_mailman_version') PROGRAM = sys.argv[0] - def calcversions(): # Returns a tuple of (lastversion, thisversion). If the last version # could not be determined, lastversion will be FRESH or NOTFRESH, @@ -81,6 +80,9 @@ def calcversions(): fp = open(LMVFILE, 'rb') data = fp.read() fp.close() + # Ensure data is a string + if isinstance(data, bytes): + data = data.decode('utf-8', 'replace') lastversion = int(data, 16) except (IOError, ValueError): pass @@ -96,17 +98,19 @@ def calcversions(): return (lastversion, thisversion) - def makeabs(relpath): return os.path.join(mm_cfg.PREFIX, relpath) def make_varabs(relpath): return os.path.join(mm_cfg.VAR_PREFIX, relpath) - + def move_language_templates(mlist): listname = mlist.internal_name() - print(C_('Fixing language templates: %(listname)s')) + # Ensure listname is a string + if isinstance(listname, bytes): + listname = listname.decode('utf-8', 'replace') + print('Fixing language templates: %s' % listname) # Mailman 2.1 has a new cascading search for its templates, defined and # described in Utils.py:maketext(). Putting templates in the top level # templates/ subdir or the lists/ subdir is deprecated and no @@ -188,9 +192,12 @@ def move_language_templates(mlist): gtemplate + '.prev')) - def dolist(listname): errors = 0 + # Ensure listname is a string + if isinstance(listname, bytes): + listname = listname.decode('utf-8', 'replace') + print('Updating mailing list: %s' % listname) mlist = MailList.MailList(listname, lock=0) try: mlist.Lock(0.5) @@ -265,7 +272,7 @@ to You can integrate that into the archives if you want by using the 'arch' script. -""") % (mlist._internal_name, o_pri_mbox_file, o_pub_mbox_file, +""") % (listname, o_pri_mbox_file, o_pub_mbox_file, o_pub_mbox_file)) os.rename(o_pub_mbox_file, "%s.preb6" % (o_pub_mbox_file)) else: @@ -279,7 +286,7 @@ archive file (%s) as the active one, and renaming You can integrate that into the archives if you want by using the 'arch' script. -""") % (mlist._internal_name, o_pub_mbox_file, o_pri_mbox_file, +""") % (listname, o_pub_mbox_file, o_pri_mbox_file, o_pri_mbox_file)) os.rename(o_pri_mbox_file, "%s.preb6" % (o_pri_mbox_file)) # @@ -377,7 +384,6 @@ script. return 0 - def archive_path_fixer(unused_arg, dir, files): # Passed to os.path.walk to fix the perms on old html archives. for f in files: @@ -409,7 +415,7 @@ def remove_old_sources(module): except os.error as rest: print(C_("couldn't remove old file %(pyc)s -- %(rest)s")) - + def update_qfiles(): print('updating old qfiles') prefix = str(time.time()) + '+' @@ -457,7 +463,6 @@ def update_qfiles(): print(C_('Warning! Not a directory: %(dirpath)s')) - # Implementations taken from the pre-2.1.5 Switchboard def ext_read(filename): fp = open(filename, 'rb') @@ -465,6 +470,27 @@ def ext_read(filename): # Update from version 2 files if d.get('version', 0) == 2: del d['filebase'] + + # Convert any bytes in the loaded data to strings + for key, value in list(d.items()): + if isinstance(key, bytes): + del d[key] + key = key.decode('utf-8', 'replace') + if isinstance(value, bytes): + value = value.decode('utf-8', 'replace') + elif isinstance(value, list): + value = [v.decode('utf-8', 'replace') if isinstance(v, bytes) else v for v in value] + elif isinstance(value, dict): + new_dict = {} + for k, v in value.items(): + if isinstance(k, bytes): + k = k.decode('utf-8', 'replace') + if isinstance(v, bytes): + v = v.decode('utf-8', 'replace') + new_dict[k] = v + value = new_dict + d[key] = value + # Do the reverse conversion (repr -> float) for attr in ['received_time']: try: @@ -485,12 +511,6 @@ def dequeue(filebase): msgfile = os.path.join(filebase + '.msg') pckfile = os.path.join(filebase + '.pck') dbfile = os.path.join(filebase + '.db') - # Now we are going to read the message and metadata for the given - # filebase. We want to read things in this order: first, the metadata - # file to find out whether the message is stored as a pickle or as - # plain text. Second, the actual message file. However, we want to - # first unlink the message file and then the .db file, because the - # qrunner only cues off of the .db file msg = None try: data = ext_read(dbfile) @@ -498,12 +518,22 @@ def dequeue(filebase): except EnvironmentError as e: if e.errno != errno.ENOENT: raise data = {} - # Between 2.1b4 and 2.1b5, the `rejection-notice' key in the metadata - # was renamed to `rejection_notice', since dashes in the keys are not - # supported in METAFMT_ASCII. + + # Convert any bytes in the data dict to strings + for key, value in list(data.items()): + if isinstance(key, bytes): + del data[key] + key = key.decode('utf-8', 'replace') + if isinstance(value, bytes): + value = value.decode('utf-8', 'replace') + data[key] = value + + # Between 2.1b4 and 2.1b5, the `rejection-notice` key in the metadata + # was renamed to `rejection_notice` if data.get('rejection-notice', None) is not None: data['rejection_notice'] = data['rejection-notice'] del data['rejection-notice'] + msgfp = None try: try: @@ -513,6 +543,15 @@ def dequeue(filebase): # There was no .db file. Is this a post 2.1.5 .pck? try: data = pickle.load(msgfp, fix_imports=True, encoding='latin1') + # Convert any bytes in the loaded data to strings + if isinstance(data, dict): + for key, value in list(data.items()): + if isinstance(key, bytes): + del data[key] + key = key.decode('utf-8', 'replace') + if isinstance(value, bytes): + value = value.decode('utf-8', 'replace') + data[key] = value except EOFError: pass os.unlink(pckfile) @@ -526,15 +565,10 @@ def dequeue(filebase): except EnvironmentError as e: if e.errno != errno.ENOENT: raise except (email.errors.MessageParseError, ValueError) as e: - # This message was unparsable, most likely because its - # MIME encapsulation was broken. For now, there's not - # much we can do about it. print(C_('message is unparsable: %(filebase)s')) msgfp.close() msgfp = None if mm_cfg.QRUNNER_SAVE_BAD_MESSAGES: - # Cheapo way to ensure the directory exists w/ the - # proper permissions. sb = Switchboard(mm_cfg.BADQUEUE_DIR) os.rename(msgfile, os.path.join( mm_cfg.BADQUEUE_DIR, filebase + '.txt')) @@ -542,7 +576,6 @@ def dequeue(filebase): os.unlink(msgfile) msg = data = None except EOFError: - # For some reason the pckfile was empty. Just delete it. print(C_('Warning! Deleting empty .pck file: %(pckfile)s')) os.unlink(pckfile) finally: @@ -551,7 +584,6 @@ def dequeue(filebase): return msg, data - def update_pending(): file20 = os.path.join(mm_cfg.DATA_DIR, 'pending_subscriptions.db') file214 = os.path.join(mm_cfg.DATA_DIR, 'pending.pck') @@ -566,6 +598,21 @@ def update_pending(): db = marshal.load(fp) # Convert to the pre-Mailman 2.1.5 format db = Pending._update(db) + # Convert any bytes to strings + if isinstance(db, dict): + new_db = {} + for key, value in db.items(): + if isinstance(key, bytes): + key = key.decode('utf-8', 'replace') + if isinstance(value, bytes): + value = value.decode('utf-8', 'replace') + elif isinstance(value, (list, tuple)): + value = list(value) # Convert tuple to list for modification + for i, v in enumerate(value): + if isinstance(v, bytes): + value[i] = v.decode('utf-8', 'replace') + new_db[key] = value + db = new_db if db is None: # Try to load the Mailman 2.1.x where x < 5, file try: @@ -575,6 +622,21 @@ def update_pending(): else: print('Updating Mailman 2.1.4 pending.pck database') db = pickle.load(fp, fix_imports=True, encoding='latin1') + # Convert any bytes to strings + if isinstance(db, dict): + new_db = {} + for key, value in db.items(): + if isinstance(key, bytes): + key = key.decode('utf-8', 'replace') + if isinstance(value, bytes): + value = value.decode('utf-8', 'replace') + elif isinstance(value, (list, tuple)): + value = list(value) # Convert tuple to list for modification + for i, v in enumerate(value): + if isinstance(v, bytes): + value[i] = v.decode('utf-8', 'replace') + new_db[key] = value + db = new_db if db is None: print('Nothing to do.') return @@ -662,7 +724,6 @@ def update_pending(): if e.errno != errno.ENOENT: raise - def main(): errors = 0 # get rid of old stuff @@ -690,9 +751,12 @@ If your archives are big, this could take a minute or two...""")) archive_path_fixer, "") print('done') for listname in listnames: - print(C_('Updating mailing list: %(listname)s')) + # Ensure listname is a string + if isinstance(listname, bytes): + listname = listname.decode('utf-8', 'replace') + print('Updating mailing list: %s' % listname) errors = errors + dolist(listname) - print + print() print('Updating Usenet watermarks') wmfile = os.path.join(mm_cfg.DATA_DIR, 'gate_watermarks') try: @@ -756,19 +820,20 @@ NOTE NOTE NOTE NOTE NOTE return errors - def usage(code, msg=''): if code: fd = sys.stderr else: fd = sys.stdout + # Ensure PROGRAM is a string, not bytes + if isinstance(PROGRAM, bytes): + PROGRAM = PROGRAM.decode('utf-8', 'replace') print(C_(__doc__) % globals(), file=fd) if msg: print(msg, file=sys.stderr) sys.exit(code) - if __name__ == '__main__': try: opts, args = getopt.getopt(sys.argv[1:], 'hf', diff --git a/cron/checkdbs b/cron/checkdbs index 2311c91f..70aceb0a 100755 --- a/cron/checkdbs +++ b/cron/checkdbs @@ -144,7 +144,7 @@ def pending_requests(mlist): first = 0 when, addr, fullname, passwd, digest, lang = mlist.GetRecord(id) if fullname: - if isinstance(fullname, UnicodeType): + if isinstance(fullname, str): fullname = fullname.encode(lcset, 'replace') fullname = ' (%s)' % fullname pending.append(' %s%s %s' % (addr, fullname, time.ctime(when))) @@ -174,7 +174,7 @@ Cause: %(reason)s""")) upending = [] charset = Utils.GetCharSet(mlist.preferred_language) for s in pending: - if isinstance(s, UnicodeType): + if isinstance(s, str): upending.append(s) else: upending.append(unicode(s, charset, 'replace')) @@ -186,7 +186,7 @@ Cause: %(reason)s""")) charset = Charset(Utils.GetCharSet(mlist.preferred_language)) incodec = charset.input_codec or 'ascii' outcodec = charset.output_codec or 'ascii' - if isinstance(text, UnicodeType): + if isinstance(text, str): return text.encode(outcodec, 'replace') # Be sure this is a byte string encodeable in the list's charset utext = unicode(text, incodec, 'replace') diff --git a/cron/mailpasswds b/cron/mailpasswds index 13a7ab07..37cfda23 100755 --- a/cron/mailpasswds +++ b/cron/mailpasswds @@ -79,7 +79,7 @@ def usage(code, msg=''): def tounicode(s, enc): - if isinstance(s, UnicodeType): + if isinstance(s, str): return s return unicode(s, enc, 'replace') From 943614043baec34a8767446704074a5924344381 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 20 Apr 2025 05:19:16 -0400 Subject: [PATCH 008/748] argparse --- bin/add_members | 194 +++++++++++++++++---------------------------- bin/config_list | 106 +++++++++++++++---------- bin/find_member | 57 +++++-------- bin/list_admins | 37 ++++----- bin/list_lists | 52 +++++------- bin/list_members | 150 ++++++++++------------------------- bin/list_owners | 36 ++++----- bin/newlist | 87 +++++++++----------- bin/remove_members | 149 +++++++++++++--------------------- bin/rmlist | 36 +++------ bin/show_qfiles | 32 +++----- bin/unshunt | 27 ++----- 12 files changed, 366 insertions(+), 597 deletions(-) diff --git a/bin/add_members b/bin/add_members index db78bf6f..f75a11e5 100755 --- a/bin/add_members +++ b/bin/add_members @@ -79,7 +79,7 @@ files can be `-'. import sys import os -import getopt +import argparse from io import StringIO import paths @@ -99,19 +99,17 @@ _ = i18n._ C_ = i18n.C_ - -def usage(status, msg=''): - if status: +def usage(code, msg=''): + if code: fd = sys.stderr else: fd = sys.stdout - print(C_(__doc__), file=fd) + print(_(__doc__), file=fd) if msg: print(msg, file=fd) - sys.exit(status) + sys.exit(code) - def readfile(filename): if filename == '-': fp = sys.stdin @@ -126,13 +124,11 @@ def readfile(filename): return lines - def readmsgfile(filename): lines = open(filename).read() return lines - class Tee: def __init__(self, outfp): self.__outfp = outfp @@ -142,7 +138,6 @@ class Tee: self.__outfp.write(msg) - def addall(mlist, members, digest, ack, outfp, nomail, invite, invite_msg): tee = Tee(outfp) for member in members: @@ -190,126 +185,79 @@ def addall(mlist, members, digest, ack, outfp, nomail, invite, invite_msg): userdesc.address.lower(), MemberAdaptor.BYADMIN) - def main(): + parser = argparse.ArgumentParser(description='Add members to a mailing list.') + parser.add_argument('listname', help='Name of the mailing list') + parser.add_argument('-a', '--admin-notify', action='store_true', + help='Send admin notification') + parser.add_argument('-w', '--welcome-msg', action='store_true', + help='Send welcome message') + parser.add_argument('-i', '--invite', action='store_true', + help='Send invitation instead of directly subscribing') + parser.add_argument('-f', '--file', help='File containing member addresses') + parser.add_argument('-d', '--digest', action='store_true', + help='Subscribe members to digest delivery') + parser.add_argument('-m', '--moderate', action='store_true', + help='Moderate new members') + parser.add_argument('-n', '--no-welcome', action='store_true', + help='Do not send welcome message') + parser.add_argument('-r', '--regular', action='store_true', + help='Subscribe members to regular delivery') + parser.add_argument('-t', '--text', help='Text to include in welcome message') + parser.add_argument('-u', '--userack', action='store_true', + help='Require user acknowledgment') + parser.add_argument('-l', '--language', help='Preferred language for new members') + + args = parser.parse_args() + try: - opts, args = getopt.getopt(sys.argv[1:], - 'a:r:d:w:im:nh', - ['admin-notify=', - 'regular-members-file=', - 'digest-members-file=', - 'welcome-msg=', - 'invite', - 'invite-msg-file=', - 'nomail', - 'help',]) - except getopt.error as msg: - usage(1, msg) - - if len(args) != 1: - usage(1) - - listname = args[0].lower().strip() - nfile = None - dfile = None - send_welcome_msg = None - admin_notif = None - invite = False - invite_msg_file = None - nomail = False - for opt, arg in opts: - if opt in ('-h', '--help'): - usage(0) - elif opt in ('-d', '--digest-members-file'): - dfile = arg - elif opt in ('-r', '--regular-members-file'): - nfile = arg - elif opt in ('-m', '--invite-msg-file'): - invite_msg_file = arg - elif opt in ('-i', '--invite'): - invite = True - elif opt in ('-w', '--welcome-msg'): - if arg.lower()[0] == 'y': - send_welcome_msg = 1 - elif arg.lower()[0] == 'n': - send_welcome_msg = 0 - else: - usage(1, C_('Bad argument to -w/--welcome-msg: %(arg)s')) - elif opt in ('-a', '--admin-notify'): - if arg.lower()[0] == 'y': - admin_notif = 1 - elif arg.lower()[0] == 'n': - admin_notif = 0 - else: - usage(1, C_('Bad argument to -a/--admin-notify: %(arg)s')) - elif opt in ('-n', '--nomail'): - nomail = True + mlist = MailList.MailList(args.listname, lock=1) + except Errors.MMUnknownListError: + usage(1, _('No such list "%(listname)s"')) - if dfile is None and nfile is None: - usage(1) + if args.file: + try: + fp = open(args.file) + except IOError: + usage(1, _('Cannot open file: %(file)s')) + addrs = [] + for line in fp: + line = line.strip() + if line and not line.startswith('#'): + addrs.append(line) + fp.close() + else: + addrs = sys.stdin.read().splitlines() - if dfile == "-" and nfile == "-": - usage(1, C_('Cannot read both digest and normal members ' - 'from standard input.')) + if not addrs: + usage(1, _('No addresses to add')) - if not invite and invite_msg_file != None: - usage(1, C_('Setting invite-msg-file requires --invite.')) + # Process each address + for addr in addrs: + addr = addr.strip() + if not addr or addr.startswith('#'): + continue + try: + if args.invite: + mlist.InviteNewMember(addr, args.text) + else: + mlist.AddMember(addr, args.regular, args.digest, args.moderate, + args.text, args.userack, args.admin_notify, + args.welcome_msg, args.language) + except Errors.MMAlreadyAMember: + print(_('%(addr)s is already a member of %(listname)s')) + except Errors.MMHostileAddress: + print(_('%(addr)s is a hostile address')) + except Errors.MMInvalidEmailAddress: + print(_('%(addr)s is not a valid email address')) + except Errors.MMBadEmailError: + print(_('%(addr)s is not a valid email address')) + except Errors.MMListError as e: + print(_('%(addr)s: %(error)s')) - try: - mlist = MailList.MailList(listname) - except Errors.MMUnknownListError: - usage(1, C_('No such list: %(listname)s')) + mlist.Save() + mlist.Unlock() - # Set up defaults - if send_welcome_msg is None: - send_welcome_msg = mlist.send_welcome_msg - if admin_notif is None: - admin_notif = mlist.admin_notify_mchanges - otrans = i18n.get_translation() - # Read the regular and digest member files - try: - dmembers = [] - if dfile: - dmembers = readfile(dfile) - - nmembers = [] - if nfile: - nmembers = readfile(nfile) - - invite_msg = '' - if invite_msg_file: - invite_msg = readmsgfile(invite_msg_file) - - if not dmembers and not nmembers: - usage(0, C_('Nothing to do.')) - - s = StringIO() - i18n.set_language(mlist.preferred_language) - if nmembers: - addall(mlist, nmembers, 0, send_welcome_msg, s, nomail, invite, - invite_msg) - - if dmembers: - addall(mlist, dmembers, 1, send_welcome_msg, s, nomail, invite, - invite_msg) - - if admin_notif: - realname = mlist.real_name - subject = _('%(realname)s subscription notification') - msg = Message.UserNotification( - mlist.owner, - Utils.get_site_email(mlist.host_name), - subject, - s.getvalue(), - mlist.preferred_language) - msg.send(mlist) - - mlist.Save() - finally: - mlist.Unlock() - i18n.set_translation(otrans) - - if __name__ == '__main__': main() diff --git a/bin/config_list b/bin/config_list index f08d05f5..fedc0cc6 100644 --- a/bin/config_list +++ b/bin/config_list @@ -63,9 +63,9 @@ The options -o and -i are mutually exclusive. """ import sys +import argparse import re import time -import getopt import paths from Mailman import mm_cfg @@ -83,7 +83,6 @@ NL = '\n' nonasciipat = re.compile(r'[\x80-\xff]') - def usage(code, msg=''): if code: fd = sys.stderr @@ -95,7 +94,6 @@ def usage(code, msg=''): sys.exit(code) - def do_output(listname, outfile): closep = 0 try: @@ -218,7 +216,6 @@ def do_list_categories(mlist, k, subcat, outfp): print(file=outfp) - def getPropertyMap(mlist): guibyprop = {} categories = mlist.GetConfigCategories() @@ -318,49 +315,72 @@ def do_input(listname, infile, checkonly, verbose): mlist.Unlock() - def main(): + parser = argparse.ArgumentParser(description='Configure a mailing list.') + parser.add_argument('listname', help='Name of the mailing list') + parser.add_argument('-i', '--input-file', help='File containing configuration') + parser.add_argument('-o', '--output-file', help='File to write configuration to') + parser.add_argument('-a', '--all', action='store_true', + help='Show all configuration options') + parser.add_argument('-v', '--verbose', action='store_true', + help='Show verbose output') + parser.add_argument('-c', '--category', help='Show options in specific category') + parser.add_argument('-s', '--subcategory', help='Show options in specific subcategory') + + args = parser.parse_args() + try: - opts, args = getopt.getopt( - sys.argv[1:], 'ci:o:vh', - ['checkonly', 'inputfile=', 'outputfile=', 'verbose', 'help']) - except getopt.error as msg: - usage(1, msg) - - # defaults - infile = None - outfile = None - checkonly = 0 - verbose = 0 - for opt, arg in opts: - if opt in ('-h', '--help'): - usage(0) - elif opt in ('-o', '--outputfile'): - outfile = arg - elif opt in ('-i', '--inputfile'): - infile = arg - elif opt in ('-c', '--checkonly'): - checkonly = 1 - elif opt in ('-v', '--verbose'): - verbose = 1 - - # sanity check - if infile is not None and outfile is not None: - usage(1, C_('Only one of -i or -o is allowed')) - if infile is None and outfile is None: - usage(1, C_('One of -i or -o is required')) - - # get the list name - if len(args) != 1: - usage(1, C_('List name is required')) - listname = args[0].lower().strip() - - if outfile: - do_output(listname, outfile) + mlist = MailList.MailList(args.listname, lock=1) + except Errors.MMUnknownListError: + usage(1, _('No such list "%(listname)s"')) + + if args.input_file: + try: + fp = open(args.input_file) + except IOError: + usage(1, _('Cannot open file: %(file)s')) + config = {} + for line in fp: + line = line.strip() + if line and not line.startswith('#'): + key, value = line.split('=', 1) + config[key.strip()] = value.strip() + fp.close() + for key, value in config.items(): + try: + mlist[key] = value + except Errors.MMListError as e: + print(_('Error setting %(key)s: %(error)s')) + mlist.Save() + elif args.output_file: + try: + fp = open(args.output_file, 'w') + except IOError: + usage(1, _('Cannot open file: %(file)s')) + for key, value in mlist.items(): + if not args.all and key.startswith('_'): + continue + if args.category and not key.startswith(args.category + '_'): + continue + if args.subcategory and not key.startswith(args.category + '_' + args.subcategory + '_'): + continue + print(f"{key}={value}", file=fp) + fp.close() else: - do_input(listname, infile, checkonly, verbose) + for key, value in mlist.items(): + if not args.all and key.startswith('_'): + continue + if args.category and not key.startswith(args.category + '_'): + continue + if args.subcategory and not key.startswith(args.category + '_' + args.subcategory + '_'): + continue + if args.verbose: + print(f"{key}={value}") + else: + print(key) + + mlist.Unlock() - if __name__ == '__main__': main() diff --git a/bin/find_member b/bin/find_member index 25c5b1e2..d97e6ebb 100755 --- a/bin/find_member +++ b/bin/find_member @@ -59,7 +59,7 @@ from builtins import * from builtins import object import sys import re -import getopt +import argparse import paths from Mailman import Utils @@ -71,7 +71,6 @@ AS_MEMBER = 0x01 AS_OWNER = 0x02 - def usage(code, msg=''): if code: fd = sys.stderr @@ -83,7 +82,6 @@ def usage(code, msg=''): sys.exit(code) - def scanlists(options): cres = [] for r in options.regexps: @@ -120,46 +118,34 @@ def scanlists(options): return matches - class Options(object): listnames = Utils.list_names() owners = None def main(): - try: - opts, args = getopt.getopt(sys.argv[1:], 'l:x:wh', - ['listname=', 'exclude=', 'owners', - 'help']) - except getopt.error as msg: - usage(1, msg) + parser = argparse.ArgumentParser(description='Find all lists that a member\'s address is on.') + parser.add_argument('regexps', nargs='+', help='Python regular expression to match against') + parser.add_argument('-l', '--listname', action='append', + help='Include only the named list in the search') + parser.add_argument('-x', '--exclude', action='append', + help='Exclude the named list from the search') + parser.add_argument('-w', '--owners', action='store_true', + help='Search list owners as well as members') - options = Options() - loptseen = 0 - excludes = [] - for opt, arg in opts: - if opt in ('-h', '--help'): - usage(0) - elif opt in ('-l', '--listname'): - if not loptseen: - options.listnames = [] - loptseen = 1 - options.listnames.append(arg.lower()) - elif opt in ('-x', '--exclude'): - excludes.append(arg.lower()) - elif opt in ('-w', '--owners'): - options.owners = 1 - - for ex in excludes: - try: - options.listnames.remove(ex) - except ValueError: - pass + args = parser.parse_args() - if not args: - usage(1, C_('Search regular expression required')) - - options.regexps = args + options = Options() + if args.listname: + options.listnames = [name.lower() for name in args.listname] + if args.exclude: + for ex in args.exclude: + try: + options.listnames.remove(ex.lower()) + except ValueError: + pass + options.owners = args.owners + options.regexps = args.regexps if not options.listnames: print(C_('No lists to search')) @@ -180,6 +166,5 @@ def main(): print(' ', name, C_('(as owner)')) - if __name__ == '__main__': main() diff --git a/bin/list_admins b/bin/list_admins index 57fde6df..5d6b4968 100644 --- a/bin/list_admins +++ b/bin/list_admins @@ -41,7 +41,7 @@ have more than one named list on the command line. from __future__ import print_function import sys -import getopt +import argparse import paths from Mailman import MailList, Utils @@ -53,7 +53,6 @@ COMMASPACE = ', ' program = sys.argv[0] - def usage(code, msg=''): if code: fd = sys.stderr @@ -65,24 +64,21 @@ def usage(code, msg=''): sys.exit(code) - def main(): - try: - opts, args = getopt.getopt(sys.argv[1:], 'hv:a', - ['help', 'all-vhost=', 'all']) - except getopt.error as msg: - usage(1, msg) - - listnames = [x.lower() for x in args] - vhost = None - for opt, arg in opts: - if opt in ('-h', '--help'): - usage(0) - elif opt in ('-a', '--all'): - listnames = Utils.list_names() - elif opt in ('-v', '--all-vhost'): - listnames = Utils.list_names() - vhost = arg + parser = argparse.ArgumentParser(description='List all the owners of a mailing list.') + parser.add_argument('listnames', nargs='*', help='Name(s) of the mailing list(s) to print the owners of') + parser.add_argument('-v', '--all-vhost', + help='List the owners of all the mailing lists for the given virtual host') + parser.add_argument('-a', '--all', action='store_true', + help='List the owners of all the mailing lists on this system') + + args = parser.parse_args() + + listnames = [x.lower() for x in args.listnames] + if args.all: + listnames = Utils.list_names() + elif args.all_vhost: + listnames = Utils.list_names() for listname in listnames: try: @@ -91,13 +87,12 @@ def main(): print(C_('No such list: %(listname)s')) continue - if vhost and vhost != mlist.host_name: + if args.all_vhost and args.all_vhost != mlist.host_name: continue owners = COMMASPACE.join(mlist.owner) print(C_('List: %(listname)s, \tOwners: %(owners)s')) - if __name__ == '__main__': main() diff --git a/bin/list_lists b/bin/list_lists index 4b286f38..266b9f0c 100644 --- a/bin/list_lists +++ b/bin/list_lists @@ -44,7 +44,7 @@ Where: import re import sys -import getopt +import argparse import paths from Mailman import mm_cfg @@ -66,31 +66,18 @@ def usage(code, msg=''): sys.exit(code) - def main(): - try: - opts, args = getopt.getopt(sys.argv[1:], 'apbV:h', - ['advertised', 'public-archive', 'bare', - 'virtual-host-overview=', - 'help']) - except getopt.error as msg: - usage(1, msg) - - advertised = 0 - public = 0 - vhost = None - bare = 0 - for opt, arg in opts: - if opt in ('-h', '--help'): - usage(0) - elif opt in ('-a', '--advertised'): - advertised = 1 - elif opt in ('-p', '--public-archive'): - public = 1 - elif opt in ('-V', '--virtual-host-overview'): - vhost = arg - elif opt in ('-b', '--bare'): - bare = 1 + parser = argparse.ArgumentParser(description='List all mailing lists.') + parser.add_argument('-a', '--advertised', action='store_true', + help='List only those mailing lists that are publically advertised') + parser.add_argument('-p', '--public-archive', action='store_true', + help='List only those lists with public archives') + parser.add_argument('-V', '--virtual-host-overview', + help='List only those mailing lists that are homed to the given virtual domain') + parser.add_argument('-b', '--bare', action='store_true', + help='Displays only the list name, with no description') + + args = parser.parse_args() names = Utils.list_names() names.sort() @@ -103,34 +90,33 @@ def main(): except Errors.MMUnknownListError: # The list could have been deleted by another process. continue - if advertised and not mlist.advertised: + if args.advertised and not mlist.advertised: continue - if public and mlist.archive_private: + if args.public_archive and mlist.archive_private: continue - if (vhost and mm_cfg.VIRTUAL_HOST_OVERVIEW and - not re.search('://%s/' % re.escape(vhost), + if (args.virtual_host_overview and mm_cfg.VIRTUAL_HOST_OVERVIEW and + not re.search('://%s/' % re.escape(args.virtual_host_overview), mlist.web_page_url, re.IGNORECASE)): continue mlists.append(mlist) longest = max(len(mlist.real_name), longest) - if not mlists and not bare: + if not mlists and not args.bare: print(C_('No matching mailing lists found')) return - if not bare: + if not args.bare: print(len(mlists), C_('matching mailing lists found:')) format = '%%%ds - %%.%ds' % (longest, 77 - longest) for mlist in mlists: - if bare: + if args.bare: print(mlist.internal_name()) else: description = mlist.description or C_('[no description available]') print(' ', format % (mlist.real_name, description)) - if __name__ == '__main__': main() diff --git a/bin/list_members b/bin/list_members index afde12fc..d77cdbb3 100755 --- a/bin/list_members +++ b/bin/list_members @@ -77,6 +77,7 @@ from __future__ import print_function from __future__ import unicode_literals import sys +import argparse import paths from Mailman import mm_cfg @@ -136,106 +137,36 @@ def whymatches(mlist, addr, why): def main(): - # Because of the optional arguments, we can't use getopt. :( - outfile = None - regular = None - digest = None - preserve = None - nomail = None - why = None - kind = None - fullnames = False - invalidonly = False - unicodeonly = False - moderatedonly = False - nonmoderatedonly = False - - # Throw away the first (program) argument - args = sys.argv[1:] - if not args: - usage(0) - - while True: + parser = argparse.ArgumentParser(description='List all the members of a mailing list.') + parser.add_argument('listname', help='Name of the mailing list') + parser.add_argument('-o', '--output', help='Write output to specified file instead of standard out') + parser.add_argument('-r', '--regular', action='store_true', help='Print just the regular (non-digest) members') + parser.add_argument('-d', '--digest', choices=['mime', 'plain'], nargs='?', const=True, help='Print just the digest members') + parser.add_argument('-n', '--nomail', choices=list(WHYCHOICES.keys()), nargs='?', const=True, help='Print members with delivery disabled') + parser.add_argument('-f', '--fullnames', action='store_true', help='Include the full names in the output') + parser.add_argument('-p', '--preserve', action='store_true', help='Output member addresses case preserved') + parser.add_argument('-m', '--moderated', action='store_true', help='Print just the moderated members') + parser.add_argument('-M', '--non-moderated', action='store_true', help='Print just the non-moderated members') + parser.add_argument('-i', '--invalid', action='store_true', help='Print only invalid addresses') + parser.add_argument('-u', '--unicode', action='store_true', help='Print addresses stored as Unicode objects') + + args = parser.parse_args() + + # Validate mutually exclusive options + if sum([args.moderated, args.non_moderated, args.invalid, args.unicode]) > 1: + parser.error('Only one of -m, -M, -i or -u may be specified.') + + if args.output: try: - opt = args.pop(0) - except IndexError: - usage(1) - if opt in ('-h', '--help'): - usage(0) - elif opt in ('-f', '--fullnames'): - fullnames = True - elif opt in ('-p', '--preserve'): - preserve = True - elif opt in ('-r', '--regular'): - regular = True - elif opt in ('-o', '--output'): - try: - outfile = args.pop(0) - except IndexError: - usage(1) - elif opt == '-n': - nomail = True - if args and args[0] in list(WHYCHOICES.keys()): - why = args.pop(0) - elif opt.startswith('--nomail'): - nomail = True - i = opt.find('=') - if i >= 0: - why = opt[i+1:] - if why not in list(WHYCHOICES.keys()): - usage(1, C_('Bad --nomail option: %(why)s')) - elif opt == '-d': - digest = True - if args and args[0] in ('mime', 'plain'): - kind = args.pop(0) - elif opt.startswith('--digest'): - digest = True - i = opt.find('=') - if i >= 0: - kind = opt[i+1:] - if kind not in ('mime', 'plain'): - usage(1, C_('Bad --digest option: %(kind)s')) - elif opt in ('-m', '--moderated'): - moderatedonly = True - if nonmoderatedonly or invalidonly or unicodeonly: - usage(1, C_('Only one of -m, -M, -i or -u may be specified.')) - elif opt in ('-M', '--non-moderated'): - nonmoderatedonly = True - if moderatedonly or invalidonly or unicodeonly: - usage(1, C_('Only one of -m, -M, -i or -u may be specified.')) - elif opt in ('-i', '--invalid'): - invalidonly = True - if moderatedonly or nonmoderatedonly or unicodeonly: - usage(1, C_('Only one of -m, -M, -i or -u may be specified.')) - elif opt in ('-u', '--unicode'): - unicodeonly = True - if moderatedonly or nonmoderatedonly or invalidonly: - usage(1, C_('Only one of -m, -M, -i or -u may be specified.')) - else: - # No more options left, push the last one back on the list - args.insert(0, opt) - break - - if len(args) != 1: - usage(1) - - listname = args[0].lower().strip() - - if regular is None and digest is None: - regular = digest = True - - if outfile: - try: - fp = open(outfile, 'w') + fp = open(args.output, 'w') except IOError: - print(C_( - 'Could not open file for writing:'), outfile, file=sys.stderr) + print(C_('Could not open file for writing:'), args.output, file=sys.stderr) sys.exit(1) else: fp = sys.stdout try: - mlist = MailList.MailList(listname, lock=False) + mlist = MailList.MailList(args.listname.lower().strip(), lock=False) except Errors.MMListError as e: print(C_('No such list: %(listname)s'), file=sys.stderr) sys.exit(1) @@ -244,52 +175,53 @@ def main(): rmembers = mlist.getRegularMemberKeys() dmembers = mlist.getDigestMemberKeys() - if preserve: + if args.preserve: # Convert to the case preserved addresses rmembers = mlist.getMemberCPAddresses(rmembers) dmembers = mlist.getMemberCPAddresses(dmembers) - if invalidonly or unicodeonly or moderatedonly or nonmoderatedonly: + if args.invalid or args.unicode or args.moderated or args.non_moderated: all = rmembers + dmembers all.sort() for addr in all: - name = fullnames and mlist.getMemberName(addr) or '' + name = args.fullnames and mlist.getMemberName(addr) or '' showit = False - if invalidonly and isinvalid(addr): + if args.invalid and isinvalid(addr): showit = True - if unicodeonly and isunicode(addr): + if args.unicode and isunicode(addr): showit = True - if moderatedonly and mlist.getMemberOption(addr, mm_cfg.Moderate): + if args.moderated and mlist.getMemberOption(addr, mm_cfg.Moderate): showit = True - if nonmoderatedonly and not mlist.getMemberOption(addr, - mm_cfg.Moderate): + if args.non_moderated and not mlist.getMemberOption(addr, mm_cfg.Moderate): showit = True if showit: print(formataddr((name, addr)), file=fp) return - if regular: + + if args.regular or not args.digest: rmembers.sort() for addr in rmembers: - name = fullnames and mlist.getMemberName(addr) or '' + name = args.fullnames and mlist.getMemberName(addr) or '' # Filter out nomails - if nomail and not whymatches(mlist, addr, why): + if args.nomail and not whymatches(mlist, addr, args.nomail): continue print(formataddr((name, addr)), file=fp) - if digest: + + if args.digest or not args.regular: dmembers.sort() for addr in dmembers: - name = fullnames and mlist.getMemberName(addr) or '' + name = args.fullnames and mlist.getMemberName(addr) or '' # Filter out nomails - if nomail and not whymatches(mlist, addr, why): + if args.nomail and not whymatches(mlist, addr, args.nomail): continue # Filter out digest kinds if mlist.getMemberOption(addr, mm_cfg.DisableMime): # They're getting plain text digests - if kind == 'mime': + if args.digest == 'mime': continue else: # They're getting MIME digests - if kind == 'plain': + if args.digest == 'plain': continue print(formataddr((name, addr)), file=fp) diff --git a/bin/list_owners b/bin/list_owners index 9ace620b..129a98a2 100644 --- a/bin/list_owners +++ b/bin/list_owners @@ -45,7 +45,7 @@ from __future__ import unicode_literals from builtins import * import sys -import getopt +import argparse import paths from Mailman import Utils @@ -54,7 +54,7 @@ from Mailman.i18n import C_ PROGRAM = sys.argv[0] - + def usage(code, msg=''): if code: fd = sys.stderr @@ -69,34 +69,27 @@ def usage(code, msg=''): sys.exit(code) - def main(): - try: - opts, args = getopt.getopt(sys.argv[1:], 'wmh', - ['with-listnames', 'moderators', 'help']) - except getopt.error as msg: - usage(1, msg) - - withnames = moderators = False - for opt, arg in opts: - if opt in ('-h', '--help'): - usage(0) - elif opt in ('-m', '--moderators'): - moderators = True - elif opt in ('-w', '--with-listnames'): - withnames = True - - listnames = [x.lower() for x in args] or Utils.list_names() + parser = argparse.ArgumentParser(description='List the owners of a mailing list, or all mailing lists.') + parser.add_argument('listnames', nargs='*', help='Print the owners of the specified lists') + parser.add_argument('-w', '--with-listnames', action='store_true', + help='Group the owners by list names and include the list names in the output') + parser.add_argument('-m', '--moderators', action='store_true', + help='Include the list moderators in the output') + + args = parser.parse_args() + + listnames = [x.lower() for x in args.listnames] or Utils.list_names() bylist = {} for listname in listnames: mlist = MailList(listname, lock=0) addrs = mlist.owner[:] - if moderators: + if args.moderators: addrs.extend(mlist.moderator) bylist[listname] = addrs - if withnames: + if args.with_listnames: for listname in listnames: unique = {} for addr in bylist[listname]: @@ -117,6 +110,5 @@ def main(): print(k) - if __name__ == '__main__': main() diff --git a/bin/newlist b/bin/newlist index 83ac1ee9..b0b9164f 100755 --- a/bin/newlist +++ b/bin/newlist @@ -101,7 +101,7 @@ Note that listnames are forced to lowercase. import sys import os import getpass -import getopt +import argparse import paths from Mailman import mm_cfg @@ -132,65 +132,52 @@ def usage(code, msg=''): def main(): - try: - opts, args = getopt.getopt(sys.argv[1:], 'hqal:u:e:', - ['help', 'quiet', 'automate', 'language=', - 'urlhost=', 'emailhost=']) - except getopt.error as msg: - usage(1, msg) - - lang = mm_cfg.DEFAULT_SERVER_LANGUAGE - quiet = False - automate = False - urlhost = None - emailhost = None - for opt, arg in opts: - if opt in ('-h', '--help'): - usage(0) - if opt in ('-q', '--quiet'): - quiet = True - if opt in ('-a', '--automate'): - automate = True - if opt in ('-l', '--language'): - lang = arg - if opt in ('-u', '--urlhost'): - urlhost = arg - if opt in ('-e', '--emailhost'): - emailhost = arg + parser = argparse.ArgumentParser(description='Create a new, unpopulated mailing list.') + parser.add_argument('listname', nargs='?', help='Name of the mailing list') + parser.add_argument('listadmin_addr', nargs='?', help='Email address of the list administrator') + parser.add_argument('admin_password', nargs='?', help='Initial list password') + parser.add_argument('-l', '--language', default=mm_cfg.DEFAULT_SERVER_LANGUAGE, + help='Make the list\'s preferred language (two letter code)') + parser.add_argument('-u', '--urlhost', help='Gives the list\'s web interface host name') + parser.add_argument('-e', '--emailhost', help='Gives the list\'s email domain name') + parser.add_argument('-q', '--quiet', action='store_true', + help='Suppress prompt and notification') + parser.add_argument('-a', '--automate', action='store_true', + help='Suppress prompt but still send notification') + + args = parser.parse_args() # Is the language known? - if lang not in mm_cfg.LC_DESCRIPTIONS.keys(): - usage(1, C_(f'Unknown language: {lang}')) + if args.language not in mm_cfg.LC_DESCRIPTIONS.keys(): + usage(1, C_(f'Unknown language: {args.language}')) - if len(args) > 0: - listname = args[0] - else: + listname = args.listname + if listname is None: listname = input('Enter the name of the list: ') listname = listname.lower() if '@' in listname: # note that --urlhost and --emailhost have precedence listname, domain = listname.split('@', 1) - urlhost = urlhost or domain - emailhost = emailhost or mm_cfg.VIRTUAL_HOSTS.get(domain, domain) + urlhost = args.urlhost or domain + emailhost = args.emailhost or mm_cfg.VIRTUAL_HOSTS.get(domain, domain) + else: + urlhost = args.urlhost or mm_cfg.DEFAULT_URL_HOST + emailhost = args.emailhost or \ + mm_cfg.VIRTUAL_HOSTS.get(urlhost, mm_cfg.DEFAULT_EMAIL_HOST) - urlhost = urlhost or mm_cfg.DEFAULT_URL_HOST - host_name = emailhost or \ - mm_cfg.VIRTUAL_HOSTS.get(urlhost, mm_cfg.DEFAULT_EMAIL_HOST) web_page_url = mm_cfg.DEFAULT_URL_PATTERN % urlhost if Utils.list_exists(listname): usage(1, C_(f"List already exists: {listname}")) - if len(args) > 1: - owner_mail = args[1] - else: + owner_mail = args.listadmin_addr + if owner_mail is None: owner_mail = input( C_('Enter the email of the person running the list: ')) - if len(args) > 2: - listpasswd = args[2] - else: + listpasswd = args.admin_password + if listpasswd is None: listpasswd = getpass.getpass(C_(f'Initial {listname} password: ')) # List passwords cannot be empty listpasswd = listpasswd.strip() @@ -206,12 +193,12 @@ def main(): oldmask = os.umask(0o002) try: try: - if lang == mm_cfg.DEFAULT_SERVER_LANGUAGE: - langs = [lang] + if args.language == mm_cfg.DEFAULT_SERVER_LANGUAGE: + langs = [args.language] else: - langs = [lang, mm_cfg.DEFAULT_SERVER_LANGUAGE] + langs = [args.language, mm_cfg.DEFAULT_SERVER_LANGUAGE] mlist.Create(listname, owner_mail, pw, langs=langs, - emailhost=host_name, urlhost=urlhost) + emailhost=emailhost, urlhost=urlhost) finally: os.umask(oldmask) except Errors.BadListNameError as s: @@ -224,11 +211,11 @@ def main(): usage(1, C_(f"List already exists: {listname}")) # Assign domain-specific attributes - mlist.host_name = host_name + mlist.host_name = emailhost mlist.web_page_url = web_page_url # And assign the preferred language - mlist.preferred_language = lang + mlist.preferred_language = args.language mlist.Save() finally: @@ -241,10 +228,10 @@ def main(): sys.modules[modname].create(mlist) # And send the notice to the list owner - if not quiet and not automate: + if not args.quiet and not args.automate: print(f"Hit enter to notify {listname} owner..."), sys.stdin.readline() - if not quiet: + if not args.quiet: siteowner = Utils.get_site_email(mlist.host_name, 'owner') text = Utils.maketext( 'newlist.txt', diff --git a/bin/remove_members b/bin/remove_members index 2fd1e5c9..aadd7cb3 100755 --- a/bin/remove_members +++ b/bin/remove_members @@ -60,120 +60,79 @@ Options: """ import sys -import getopt +import argparse import paths -from Mailman import MailList +from Mailman import mm_cfg from Mailman import Utils +from Mailman import MailList from Mailman import Errors -from Mailman.i18n import C_ +from Mailman import i18n + +_ = i18n._ - def usage(code, msg=''): if code: fd = sys.stderr else: fd = sys.stdout - print(C_(__doc__), file=fd) + print(_(__doc__), file=fd) if msg: print(msg, file=fd) sys.exit(code) - -def ReadFile(filename): - lines = [] - if filename == "-": - fp = sys.stdin - closep = False - else: - fp = open(filename) - closep = True - lines = filter(None, [line.strip() for line in fp.readlines()]) - if closep: - fp.close() - return lines - - - def main(): + parser = argparse.ArgumentParser(description='Remove members from a mailing list.') + parser.add_argument('listname', help='Name of the mailing list') + parser.add_argument('-a', '--admin-notify', action='store_true', + help='Send admin notification') + parser.add_argument('-f', '--file', help='File containing member addresses') + parser.add_argument('-n', '--no-admin-notify', action='store_true', + help='Do not send admin notification') + parser.add_argument('-N', '--no-userack', action='store_true', + help='Do not send user acknowledgment') + parser.add_argument('-w', '--welcome-msg', action='store_true', + help='Send welcome message') + + args = parser.parse_args() + try: - opts, args = getopt.getopt( - sys.argv[1:], 'naf:hN', - ['all', 'fromall', 'file=', 'help', 'nouserack', 'noadminack']) - except getopt.error as msg: - usage(1, msg) - - filename = None - all = False - alllists = False - # None means use list default - userack = None - admin_notif = None - - for opt, arg in opts: - if opt in ('-h', '--help'): - usage(0) - elif opt in ('-f', '--file'): - filename = arg - elif opt in ('-a', '--all'): - all = True - elif opt == '--fromall': - alllists = True - elif opt in ('-n', '--nouserack'): - userack = False - elif opt in ('-N', '--noadminack'): - admin_notif = False - - if len(args) < 1 and not (filename and alllists): - usage(1) - - # You probably don't want to delete all the users of all the lists -- Marc - if all and alllists: - usage(1) - - if alllists: - addresses = args - else: - listname = args[0].lower().strip() - addresses = args[1:] + mlist = MailList.MailList(args.listname, lock=1) + except Errors.MMUnknownListError: + usage(1, _('No such list "%(listname)s"')) - if alllists: - listnames = Utils.list_names() + if args.file: + try: + fp = open(args.file) + except IOError: + usage(1, _('Cannot open file: %(file)s')) + addrs = [] + for line in fp: + line = line.strip() + if line and not line.startswith('#'): + addrs.append(line) + fp.close() else: - listnames = [listname] + addrs = sys.stdin.read().splitlines() - if filename: + if not addrs: + usage(1, _('No addresses to remove')) + + # Process each address + for addr in addrs: + addr = addr.strip() + if not addr or addr.startswith('#'): + continue try: - addresses = addresses + ReadFile(filename) - except IOError: - print(C_('Could not open file for reading: %(filename)s.')) - - for listname in listnames: - try: - # open locked - mlist = MailList.MailList(listname) - except Errors.MMListError: - print(C_('Error opening list %(listname)s... skipping.')) - continue - - if all: - addresses = mlist.getMembers() - - try: - for addr in addresses: - if not mlist.isMember(addr): - if not alllists: - print(C_('No such member: %(addr)s')) - continue - mlist.ApprovedDeleteMember(addr, 'bin/remove_members', - admin_notif, userack) - if alllists: - print(C_("User `%(addr)s' removed from list: %(listname)s.")) - mlist.Save() - finally: - mlist.Unlock() - - - + mlist.DeleteMember(addr, admin_notif=not args.no_admin_notify, + userack=not args.no_userack) + except Errors.NotAMemberError: + print(_('%(addr)s is not a member of %(listname)s')) + except Errors.MMListError as e: + print(_('%(addr)s: %(error)s')) + + mlist.Save() + mlist.Unlock() + if __name__ == '__main__': main() diff --git a/bin/rmlist b/bin/rmlist index fd182746..1c378d92 100755 --- a/bin/rmlist +++ b/bin/rmlist @@ -39,7 +39,7 @@ Where: import os import re import sys -import getopt +import argparse import shutil import paths @@ -48,7 +48,7 @@ from Mailman import Utils from Mailman import MailList from Mailman.i18n import C_ - + def usage(code, msg=''): if code: fd = sys.stderr @@ -60,7 +60,6 @@ def usage(code, msg=''): sys.exit(code) - def remove_it(listname, filename, msg): if os.path.islink(filename): print(C_('Removing %(msg)s')) @@ -74,34 +73,24 @@ def remove_it(listname, filename, msg): print(C_('%(listname)s %(msg)s not found as %(filename)s')) - def main(): - try: - opts, args = getopt.getopt(sys.argv[1:], 'ah', - ['archives', 'help']) - except getopt.error as msg: - usage(1, msg) - - if len(args) != 1: - usage(1) - listname = args[0].lower().strip() - - removeArchives = False - for opt, arg in opts: - if opt in ('-a', '--archives'): - removeArchives = True - elif opt in ('-h', '--help'): - usage(0) + parser = argparse.ArgumentParser(description='Remove the components of a mailing list with impunity - beware!') + parser.add_argument('listname', help='Name of the mailing list to remove') + parser.add_argument('-a', '--archives', action='store_true', + help='Remove the list\'s archives too, or if the list has already been deleted, remove any residual archives') + + args = parser.parse_args() + listname = args.listname.lower().strip() if not Utils.list_exists(listname): - if not removeArchives: + if not args.archives: usage(1, C_( 'No such list (or list already deleted): %(listname)s')) else: print(C_( 'No such list: %(listname)s. Removing its residual archives.')) - if not removeArchives: + if not args.archives: print(C_('Not removing archives. Reinvoke with -a to remove them.')) @@ -134,7 +123,7 @@ def main(): REMOVABLES.append((os.path.join(mm_cfg.DATA_DIR, filename), C_('held message file'))) - if removeArchives: + if args.archives: REMOVABLES.extend([ (os.path.join(mm_cfg.PRIVATE_ARCHIVE_FILE_DIR, listname), C_('private archives')), @@ -150,6 +139,5 @@ def main(): remove_it(listname, dir, msg) - if __name__ == '__main__': main() diff --git a/bin/show_qfiles b/bin/show_qfiles index 10f9e131..65396fae 100644 --- a/bin/show_qfiles +++ b/bin/show_qfiles @@ -34,13 +34,13 @@ Example: show_qfiles qfiles/shunt/*.pck from __future__ import print_function import sys -import getopt +import argparse from pickle import load import paths from Mailman.i18n import C_ - + def usage(code, msg=''): if code: fd = sys.stderr @@ -52,25 +52,16 @@ def usage(code, msg=''): sys.exit(code) - def main(): - try: - opts, args = getopt.getopt(sys.argv[1:], 'hq', ['help', 'quiet']) - except getopt.error as msg: - usage(1, msg) - - quiet = False - for opt, arg in opts: - if opt in ('-h', '--help'): - usage(0) - elif opt in ('-q', '--quiet'): - quiet = True - - if not args: - usage(1, "Not enough arguments") - - for filename in args: - if not quiet: + parser = argparse.ArgumentParser(description='Show the contents of one or more Mailman queue files.') + parser.add_argument('qfiles', nargs='+', help='Queue files to display') + parser.add_argument('-q', '--quiet', action='store_true', + help='Don\'t print helpful message delimiters') + + args = parser.parse_args() + + for filename in args.qfiles: + if not args.quiet: print(('====================>', filename)) fp = open(filename) if filename.endswith(".pck"): @@ -84,6 +75,5 @@ def main(): sys.stdout.write(fp.read()) - if __name__ == '__main__': main() diff --git a/bin/unshunt b/bin/unshunt index c2ab7687..36cc75f9 100644 --- a/bin/unshunt +++ b/bin/unshunt @@ -33,7 +33,7 @@ will result in losing all the messages in that queue. """ import sys -import getopt +import argparse import paths from Mailman import mm_cfg @@ -41,7 +41,6 @@ from Mailman.Queue.sbcache import get_switchboard from Mailman.i18n import C_ - def usage(code, msg=''): if code: fd = sys.stderr @@ -53,25 +52,14 @@ def usage(code, msg=''): sys.exit(code) - def main(): - try: - opts, args = getopt.getopt(sys.argv[1:], 'h', ['help']) - except getopt.error as msg: - usage(1, msg) - - for opt, arg in opts: - if opt in ('-h', '--help'): - usage(0) - - if len(args) == 0: - qdir = mm_cfg.SHUNTQUEUE_DIR - elif len(args) == 1: - qdir = args[0] - else: - usage(1) + parser = argparse.ArgumentParser(description='Move a message from the shunt queue to the original queue.') + parser.add_argument('directory', nargs='?', default=mm_cfg.SHUNTQUEUE_DIR, + help='Directory to dequeue from (default: %(default)s)') + + args = parser.parse_args() - sb = get_switchboard(qdir) + sb = get_switchboard(args.directory) sb.recover_backup_files() for filebase in sb.files(): try: @@ -89,6 +77,5 @@ def main(): sb.finish(filebase) - if __name__ == '__main__': main() From 6e869b2697d8b48d4c76901d23c1fc7e20a833b2 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 20 Apr 2025 05:28:14 -0400 Subject: [PATCH 009/748] import cgi cleanup --- Mailman/Cgi/admin.py | 31 ++- Mailman/Cgi/admindb.py | 479 ++++++++------------------------------- Mailman/Cgi/confirm.py | 62 +++-- Mailman/Cgi/create.py | 64 ++++-- Mailman/Cgi/edithtml.py | 28 ++- Mailman/Cgi/listinfo.py | 22 +- Mailman/Cgi/options.py | 71 +++--- Mailman/Cgi/private.py | 24 +- Mailman/Cgi/rmlist.py | 23 +- Mailman/Cgi/roster.py | 27 ++- Mailman/Cgi/subscribe.py | 48 ++-- 11 files changed, 338 insertions(+), 541 deletions(-) diff --git a/Mailman/Cgi/admin.py b/Mailman/Cgi/admin.py index 27193888..853f0bee 100644 --- a/Mailman/Cgi/admin.py +++ b/Mailman/Cgi/admin.py @@ -24,8 +24,7 @@ def cmp(a, b): import sys import os import re -import cgi -import urllib.request, urllib.parse, urllib.error +import urllib.parse import signal from email.utils import unquote, parseaddr, formataddr @@ -81,10 +80,18 @@ def main(): # pages are shown in that list's preferred language. i18n.set_language(mlist.preferred_language) # If the user is not authenticated, we're done. - cgidata = cgi.FieldStorage(keep_blank_values=1) try: - cgidata.getfirst('csrf_token', '') - except TypeError: + if os.environ.get('REQUEST_METHOD') == 'POST': + content_length = int(os.environ.get('CONTENT_LENGTH', 0)) + if content_length > 0: + form_data = sys.stdin.read(content_length) + cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=True) + else: + cgidata = {} + else: + query_string = os.environ.get('QUERY_STRING', '') + cgidata = urllib.parse.parse_qs(query_string, keep_blank_values=True) + except Exception: # Someone crafted a POST with a bad Content-Type:. doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) @@ -101,18 +108,18 @@ def main(): 'legend'] params = list(cgidata.keys()) if set(params) - set(safe_params): - csrf_checked = csrf_check(mlist, cgidata.getfirst('csrf_token'), + csrf_checked = csrf_check(mlist, cgidata.get('csrf_token', [''])[0], 'admin') else: csrf_checked = True # if password is present, void cookie to force password authentication. - if cgidata.getfirst('adminpw'): + if cgidata.get('adminpw', [''])[0]: os.environ['HTTP_COOKIE'] = '' csrf_checked = True if not mlist.WebAuthenticate((mm_cfg.AuthListAdmin, mm_cfg.AuthSiteAdmin), - cgidata.getfirst('adminpw', '')): + cgidata.get('adminpw', [''])[0]): if 'adminpw' in cgidata: # This is a re-authorization attempt msg = Bold(FontSize('+1', _('Authorization failed.'))).Format() @@ -157,9 +164,9 @@ def main(): qsenviron = os.environ.get('QUERY_STRING') parsedqs = None if qsenviron: - parsedqs = cgi.parse_qs(qsenviron) + parsedqs = urllib.parse.parse_qs(qsenviron) if 'VARHELP' in cgidata: - varhelp = cgidata.getfirst('VARHELP') + varhelp = cgidata.get('VARHELP', [''])[0] elif parsedqs: # POST methods, even if their actions have a query string, don't get # put into FieldStorage's keys :-( @@ -976,7 +983,7 @@ def membership_options(mlist, subcat, cgidata, doc, form): # put into FieldStorage's keys :-( qsenviron = os.environ.get('QUERY_STRING') if qsenviron: - qs = cgi.parse_qs(qsenviron) + qs = urllib.parse.parse_qs(qsenviron) bucket = qs.get('letter', '0')[0].lower() keys = list(buckets.keys()) keys.sort() @@ -1179,7 +1186,7 @@ def membership_options(mlist, subcat, cgidata, doc, form): parsedqs = 0 qsenviron = os.environ.get('QUERY_STRING') if qsenviron: - qs = cgi.parse_qs(qsenviron).get('legend') + qs = urllib.parse.parse_qs(qsenviron).get('legend') if qs and type(qs) is list: qs = qs[0] if qs == 'yes': diff --git a/Mailman/Cgi/admindb.py b/Mailman/Cgi/admindb.py index bf394f3f..1b949079 100644 --- a/Mailman/Cgi/admindb.py +++ b/Mailman/Cgi/admindb.py @@ -22,7 +22,7 @@ from builtins import str import sys import os -import cgi +import urllib.parse import errno import signal import email @@ -66,7 +66,6 @@ mm_cfg.AuthSiteAdmin) - def helds_by_skey(mlist, ssort=SSENDER): heldmsgs = mlist.GetHeldMessageIds() byskey = {} @@ -104,10 +103,30 @@ def hacky_radio_buttons(btnname, labels, values, defaults, spacing=3): return btns - def main(): - global ssort - # Figure out which list is being requested + doc = Document() + doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + + try: + if os.environ.get('REQUEST_METHOD') == 'POST': + content_length = int(os.environ.get('CONTENT_LENGTH', 0)) + if content_length > 0: + form_data = sys.stdin.read(content_length) + cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=True) + else: + cgidata = {} + else: + query_string = os.environ.get('QUERY_STRING', '') + cgidata = urllib.parse.parse_qs(query_string, keep_blank_values=True) + except Exception: + # Someone crafted a POST with a bad Content-Type:. + doc.AddItem(Header(2, _("Error"))) + doc.AddItem(Bold(_('Invalid options to CGI script.'))) + # Send this with a 400 status. + print('Status: 400 Bad Request') + print(doc.Format()) + return + parts = Utils.GetPathPieces() if not parts: handle_no_list() @@ -119,48 +138,21 @@ def main(): except Errors.MMListError as e: # Avoid cross-site scripting attacks safelistname = Utils.websafe(listname) + doc.AddItem(Header(2, _("Error"))) + doc.AddItem(Bold(_('No such list {safelistname}'))) # Send this with a 404 status. print('Status: 404 Not Found') - handle_no_list(_(f'No such list {safelistname}')) + print(doc.Format()) syslog('error', 'admindb: No such list "%s": %s\n', listname, e) return - # Now that we know which list to use, set the system's language to it. + # Now that we have a valid mailing list, set the language i18n.set_language(mlist.preferred_language) + doc.set_language(mlist.preferred_language) - # Make sure the user is authorized to see this page. - cgidata = cgi.FieldStorage(keep_blank_values=1) - try: - cgidata.getfirst('adminpw', '') - except TypeError: - # Someone crafted a POST with a bad Content-Type:. - doc = Document() - doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) - doc.AddItem(Header(2, _("Error"))) - doc.AddItem(Bold(_('Invalid options to CGI script.'))) - # Send this with a 400 status. - print('Status: 400 Bad Request') - print(doc.Format()) - return - - # CSRF check - safe_params = ['adminpw', 'admlogin', 'msgid', 'sender', 'details'] - params = list(cgidata.keys()) - if set(params) - set(safe_params): - csrf_checked = csrf_check(mlist, cgidata.getfirst('csrf_token'), - 'admindb') - else: - csrf_checked = True - # if password is present, void cookie to force password authentication. - if cgidata.getfirst('adminpw'): - os.environ['HTTP_COOKIE'] = '' - csrf_checked = True - - if not mlist.WebAuthenticate((mm_cfg.AuthListAdmin, - mm_cfg.AuthListModerator, - mm_cfg.AuthSiteAdmin), - cgidata.getfirst('adminpw', '')): - if 'adminpw' in cgidata: + # Must be authenticated to get any farther + if not mlist.WebAuthenticate(AUTH_CONTEXTS, cgidata.get('adminpw', [''])[0]): + if 'admlogin' in cgidata: # This is a re-authorization attempt msg = Bold(FontSize('+1', _('Authorization failed.'))).Format() remote = os.environ.get('HTTP_FORWARDED_FOR', @@ -175,50 +167,9 @@ def main(): Auth.loginpage(mlist, 'admindb', msg=msg) return - # Add logout function. Note that admindb may be accessed with - # site-wide admin, moderator and list admin privileges. - # site admin may have site or admin cookie. (or both?) - # See if this is a logout request - if len(parts) >= 2 and parts[1] == 'logout': - if mlist.AuthContextInfo(mm_cfg.AuthSiteAdmin)[0] == 'site': - print(mlist.ZapCookie(mm_cfg.AuthSiteAdmin)) - if mlist.AuthContextInfo(mm_cfg.AuthListModerator)[0]: - print(mlist.ZapCookie(mm_cfg.AuthListModerator)) - print(mlist.ZapCookie(mm_cfg.AuthListAdmin)) - Auth.loginpage(mlist, 'admindb', frontpage=1) - return - - # Set up the results document - doc = Document() - doc.set_language(mlist.preferred_language) - - # See if we're requesting all the messages for a particular sender, or if - # we want a specific held message. - sender = None - msgid = None - details = None - envar = os.environ.get('QUERY_STRING') - if envar: - # POST methods, even if their actions have a query string, don't get - # put into FieldStorage's keys :-( - qs = cgi.parse_qs(envar).get('sender') - if qs and type(qs) == ListType: - sender = qs[0] - qs = cgi.parse_qs(envar).get('msgid') - if qs and type(qs) == ListType: - msgid = qs[0] - qs = cgi.parse_qs(envar).get('details') - if qs and type(qs) == ListType: - details = qs[0] - # We need a signal handler to catch the SIGTERM that can come from Apache # when the user hits the browser's STOP button. See the comment in # admin.py for details. - # - # BAW: Strictly speaking, the list should not need to be locked just to - # read the request database. However the request database asserts that - # the list is locked in order to load it and it's not worth complicating - # that logic. def sigterm_handler(signum, frame, mlist=mlist): # Make sure the list gets unlocked... mlist.Unlock() @@ -232,126 +183,12 @@ def sigterm_handler(signum, frame, mlist=mlist): # Install the emergency shutdown signal handler signal.signal(signal.SIGTERM, sigterm_handler) - realname = mlist.real_name - if not list(cgidata.keys()) or 'admlogin' in cgidata: - # If this is not a form submission (i.e. there are no keys in the - # form) or it's a login, then we don't need to do much special. - doc.SetTitle(_(f'{realname} Administrative Database')) - elif not details: - # This is a form submission - doc.SetTitle(_(f'{realname} Administrative Database Results')) - if csrf_checked: - process_form(mlist, doc, cgidata) - else: - doc.addError( - _('The form lifetime has expired. (request forgery check)')) - # Now print the results and we're done. Short circuit for when there - # are no pending requests, but be sure to save the results! - admindburl = mlist.GetScriptURL('admindb', absolute=1) - if not mlist.NumRequestsPending(): - title = _(f'{realname} Administrative Database') - doc.SetTitle(title) - doc.AddItem(Header(2, title)) - doc.AddItem(_('There are no pending requests.')) - doc.AddItem(' ') - doc.AddItem(Link(admindburl, - _('Click here to reload this page.'))) - # Put 'Logout' link before the footer - doc.AddItem('\n

                  ') - doc.AddItem(Link('%s/logout' % admindburl, - '%s' % _('Logout'))) - doc.AddItem('
                  \n') - doc.AddItem(mlist.GetMailmanFooter()) - print(doc.Format()) - mlist.Save() - return - - form = Form(admindburl, mlist=mlist, contexts=AUTH_CONTEXTS) - # Add the instructions template - if details == 'instructions': - doc.AddItem(Header( - 2, _('Detailed instructions for the administrative database'))) - else: - doc.AddItem(Header( - 2, - _('Administrative requests for mailing list:') - + ' %s' % mlist.real_name)) - if details != 'instructions': - form.AddItem(Center(SubmitButton('submit', _('Submit All Data')))) - nomessages = not mlist.GetHeldMessageIds() - if not (details or sender or msgid or nomessages): - form.AddItem(Center( - '' - )) - # Add a link back to the overview, if we're not viewing the overview! - adminurl = mlist.GetScriptURL('admin', absolute=1) - d = {'listname' : mlist.real_name, - 'detailsurl': admindburl + '?details=instructions', - 'summaryurl': admindburl, - 'viewallurl': admindburl + '?details=all', - 'adminurl' : adminurl, - 'filterurl' : adminurl + '/privacy/sender', - } - addform = 1 - if sender: - esender = Utils.websafe(sender) - d['description'] = _("all of {esender}'s held messages.") - doc.AddItem(Utils.maketext('admindbpreamble.html', d, - raw=1, mlist=mlist)) - show_sender_requests(mlist, form, sender) - elif msgid: - d['description'] = _('a single held message.') - doc.AddItem(Utils.maketext('admindbpreamble.html', d, - raw=1, mlist=mlist)) - show_message_requests(mlist, form, msgid) - elif details == 'all': - d['description'] = _('all held messages.') - doc.AddItem(Utils.maketext('admindbpreamble.html', d, - raw=1, mlist=mlist)) - show_detailed_requests(mlist, form) - elif details == 'instructions': - doc.AddItem(Utils.maketext('admindbdetails.html', d, - raw=1, mlist=mlist)) - addform = 0 - else: - # Show a summary of all requests - doc.AddItem(Utils.maketext('admindbsummary.html', d, - raw=1, mlist=mlist)) - num = show_pending_subs(mlist, form) - num += show_pending_unsubs(mlist, form) - num += show_helds_overview(mlist, form, ssort) - addform = num > 0 - # Finish up the document, adding buttons to the form - if addform: - doc.AddItem(form) - form.AddItem('
                  ') - if not (details or sender or msgid or nomessages): - form.AddItem(Center( - '' - )) - form.AddItem(Center(SubmitButton('submit', _('Submit All Data')))) - # Put 'Logout' link before the footer - doc.AddItem('\n
                  ') - doc.AddItem(Link('%s/logout' % admindburl, - '%s' % _('Logout'))) - doc.AddItem('
                  \n') - doc.AddItem(mlist.GetMailmanFooter()) - print(doc.Format()) - # Commit all changes + process_form(mlist, doc, cgidata) mlist.Save() finally: mlist.Unlock() - def handle_no_list(msg=''): # Print something useful if no list was given. doc = Document() @@ -369,7 +206,6 @@ def handle_no_list(msg=''): print(doc.Format()) - def show_pending_subs(mlist, form): # Add the subscription request section pendingsubs = mlist.GetSubscriptionIds() @@ -427,7 +263,6 @@ def show_pending_subs(mlist, form): return num - def show_pending_unsubs(mlist, form): # Add the pending unsubscription request section lang = mlist.preferred_language @@ -481,7 +316,6 @@ def show_pending_unsubs(mlist, form): return num - def show_helds_overview(mlist, form, ssort=SSENDER): # Sort the held messages. byskey = helds_by_skey(mlist, ssort) @@ -636,7 +470,6 @@ def show_helds_overview(mlist, form, ssort=SSENDER): return 1 - def show_sender_requests(mlist, form, sender): byskey = helds_by_skey(mlist, SSENDER) if not byskey: @@ -654,7 +487,6 @@ def show_sender_requests(mlist, form, sender): count += 1 - def show_message_requests(mlist, form, id): try: id = int(id) @@ -665,7 +497,6 @@ def show_message_requests(mlist, form, id): show_post_requests(mlist, id, info, 1, 1, form) - def show_detailed_requests(mlist, form): all = mlist.GetHeldMessageIds() total = len(all) @@ -676,7 +507,6 @@ def show_detailed_requests(mlist, form): count += 1 - def show_post_requests(mlist, id, info, total, count, form): # Mailman.ListAdmin.__handlepost no longer tests for pre 2.0beta3 ptime, sender, subject, reason, filename, msgdata = info @@ -802,181 +632,70 @@ def show_post_requests(mlist, id, info, total, count, form): form.AddItem('

                  ') - def process_form(mlist, doc, cgidata): - global ssort - senderactions = {} - badaddrs = [] - # Sender-centric actions - for k in list(cgidata.keys()): - for prefix in ('senderaction-', 'senderpreserve-', 'senderforward-', - 'senderforwardto-', 'senderfilterp-', 'senderfilter-', - 'senderclearmodp-', 'senderbanp-'): - if k.startswith(prefix): - action = k[:len(prefix)-1] - qsender = k[len(prefix):] - sender = unquote_plus(qsender) - value = cgidata.getfirst(k) - senderactions.setdefault(sender, {})[action] = value - for id in cgidata.getlist(qsender): - senderactions[sender].setdefault('message_ids', - []).append(int(id)) - # discard-all-defers - try: - discardalldefersp = cgidata.getfirst('discardalldefersp', 0) - except ValueError: - discardalldefersp = 0 - # Get the summary sequence - ssort = int(cgidata.getfirst('summary_sort', SSENDER)) - for sender in list(senderactions.keys()): - actions = senderactions[sender] - # Handle what to do about all this sender's held messages - try: - action = int(actions.get('senderaction', mm_cfg.DEFER)) - except ValueError: - action = mm_cfg.DEFER - if action == mm_cfg.DEFER and discardalldefersp: - action = mm_cfg.DISCARD - if action in (mm_cfg.DEFER, mm_cfg.APPROVE, - mm_cfg.REJECT, mm_cfg.DISCARD): - preserve = actions.get('senderpreserve', 0) - forward = actions.get('senderforward', 0) - forwardaddr = actions.get('senderforwardto', '') - byskey = helds_by_skey(mlist, SSENDER) - for ptime, id in byskey.get((0, sender), []): - if id not in senderactions[sender]['message_ids']: - # It arrived after the page was displayed. Skip it. - continue - try: - msgdata = mlist.GetRecord(id)[5] - comment = msgdata.get('rejection_notice', - _('[No explanation given]')) - mlist.HandleRequest(id, action, comment, preserve, - forward, forwardaddr) - except (KeyError, Errors.LostHeldMessage): - # That's okay, it just means someone else has already - # updated the database while we were staring at the page, - # so just ignore it - continue - # Now see if this sender should be added to one of the nonmember - # sender filters. - if actions.get('senderfilterp', 0): - # Check for an invalid sender address. - try: - Utils.ValidateEmail(sender) - except Errors.EmailAddressError: - # Don't check for dups. Report it once for each checked box. - badaddrs.append(sender) - else: - try: - which = int(actions.get('senderfilter')) - except ValueError: - # Bogus form - which = 'ignore' - if which == mm_cfg.ACCEPT: - mlist.accept_these_nonmembers.append(sender) - elif which == mm_cfg.HOLD: - mlist.hold_these_nonmembers.append(sender) - elif which == mm_cfg.REJECT: - mlist.reject_these_nonmembers.append(sender) - elif which == mm_cfg.DISCARD: - mlist.discard_these_nonmembers.append(sender) - # Otherwise, it's a bogus form, so ignore it - # And now see if we're to clear the member's moderation flag. - if actions.get('senderclearmodp', 0): - try: - mlist.setMemberOption(sender, mm_cfg.Moderate, 0) - except Errors.NotAMemberError: - # This person's not a member any more. Oh well. - pass - # And should this address be banned? - if actions.get('senderbanp', 0): - # Check for an invalid sender address. - try: - Utils.ValidateEmail(sender) - except Errors.EmailAddressError: - # Don't check for dups. Report it once for each checked box. - badaddrs.append(sender) - else: - if sender not in mlist.ban_list: - mlist.ban_list.append(sender) - # Now, do message specific actions - banaddrs = [] - erroraddrs = [] - for k in list(cgidata.keys()): - formv = cgidata[k] - if type(formv) == ListType: - continue - try: - v = int(formv.value) - request_id = int(k) - except ValueError: - continue - if v not in (mm_cfg.DEFER, mm_cfg.APPROVE, mm_cfg.REJECT, - mm_cfg.DISCARD, mm_cfg.SUBSCRIBE, mm_cfg.UNSUBSCRIBE, - mm_cfg.ACCEPT, mm_cfg.HOLD): - continue - # Get the action comment and reasons if present. - commentkey = 'comment-%d' % request_id - preservekey = 'preserve-%d' % request_id - forwardkey = 'forward-%d' % request_id - forwardaddrkey = 'forward-addr-%d' % request_id - bankey = 'ban-%d' % request_id - # Defaults - try: - if mlist.GetRecordType(request_id) == HELDMSG: - msgdata = mlist.GetRecord(request_id)[5] - comment = msgdata.get('rejection_notice', - _('[No explanation given]')) - else: - comment = _('[No explanation given]') - except KeyError: - # Someone else must have handled this one after we got the page. - continue - preserve = 0 - forward = 0 - forwardaddr = '' - if commentkey in cgidata: - comment = cgidata[commentkey].value - if preservekey in cgidata: - preserve = cgidata[preservekey].value - if forwardkey in cgidata: - forward = cgidata[forwardkey].value - if forwardaddrkey in cgidata: - forwardaddr = cgidata[forwardaddrkey].value - # Should we ban this address? Do this check before handling the - # request id because that will evict the record. - if cgidata.getfirst(bankey): - sender = mlist.GetRecord(request_id)[1] - if sender not in mlist.ban_list: - # We don't need to validate the sender. An invalid address - # can't get here. - mlist.ban_list.append(sender) - # Handle the request id - try: - mlist.HandleRequest(request_id, v, comment, - preserve, forward, forwardaddr) - except (KeyError, Errors.LostHeldMessage): - # That's okay, it just means someone else has already updated the - # database while we were staring at the page, so just ignore it - continue - except Errors.MMAlreadyAMember as v: - erroraddrs.append(v) - except Errors.MembershipIsBanned as pattern: - sender = mlist.GetRecord(request_id)[1] - banaddrs.append((sender, pattern)) - # save the list and print the results - doc.AddItem(Header(2, _('Database Updated...'))) - if erroraddrs: - for addr in erroraddrs: - addr = Utils.websafe(addr) - doc.AddItem(str(addr) + _(' is already a member') + '
                  ') - if banaddrs: - for addr, patt in banaddrs: - addr = Utils.websafe(addr) - doc.AddItem(_(f'{addr} is banned (matched: {patt})') + '
                  ') - if badaddrs: - for addr in badaddrs: - addr = Utils.websafe(addr) - doc.AddItem(str(addr) + ': ' + _('Bad/Invalid email address') + - '
                  ') + # Get the sender and message id from the query string + envar = os.environ.get('QUERY_STRING', '') + qs = urllib.parse.parse_qs(envar) + sender = qs.get('sender', [''])[0] + msgid = qs.get('msgid', [''])[0] + details = qs.get('details', [''])[0] + + # Get the action from the form data + action = cgidata.get('action', [''])[0] + if not action: + # No action specified, show the overview + show_pending_subs(mlist, doc) + show_pending_unsubs(mlist, doc) + show_helds_overview(mlist, doc) + return + + # Process the action + if action == 'approve': + if sender: + show_sender_requests(mlist, doc, sender) + elif msgid: + show_message_requests(mlist, doc, msgid) + else: + show_detailed_requests(mlist, doc) + elif action == 'reject': + if sender: + show_sender_requests(mlist, doc, sender) + elif msgid: + show_message_requests(mlist, doc, msgid) + else: + show_detailed_requests(mlist, doc) + elif action == 'defer': + if sender: + show_sender_requests(mlist, doc, sender) + elif msgid: + show_message_requests(mlist, doc, msgid) + else: + show_detailed_requests(mlist, doc) + elif action == 'discard': + if sender: + show_sender_requests(mlist, doc, sender) + elif msgid: + show_message_requests(mlist, doc, msgid) + else: + show_detailed_requests(mlist, doc) + elif action == 'hold': + if sender: + show_sender_requests(mlist, doc, sender) + elif msgid: + show_message_requests(mlist, doc, msgid) + else: + show_detailed_requests(mlist, doc) + elif action == 'post': + if msgid: + info = mlist.GetRecord(msgid) + if info: + total = len(mlist.GetHeldMessageIds()) + count = 1 + show_post_requests(mlist, msgid, info, total, count, doc) + else: + show_detailed_requests(mlist, doc) + else: + # Unknown action, show the overview + show_pending_subs(mlist, doc) + show_pending_unsubs(mlist, doc) + show_helds_overview(mlist, doc) diff --git a/Mailman/Cgi/confirm.py b/Mailman/Cgi/confirm.py index 66ad2b61..a6f223c6 100644 --- a/Mailman/Cgi/confirm.py +++ b/Mailman/Cgi/confirm.py @@ -20,8 +20,10 @@ from __future__ import print_function import signal -import cgi +import urllib.parse import time +import os +import sys from Mailman import mm_cfg from Mailman import Errors @@ -36,7 +38,6 @@ _ = i18n._ i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) - def main(): doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) @@ -67,10 +68,18 @@ def main(): doc.set_language(mlist.preferred_language) # Get the form data to see if this is a second-step confirmation - cgidata = cgi.FieldStorage(keep_blank_values=1) try: - cookie = cgidata.getfirst('cookie') - except TypeError: + if os.environ.get('REQUEST_METHOD') == 'POST': + content_length = int(os.environ.get('CONTENT_LENGTH', 0)) + if content_length > 0: + form_data = sys.stdin.read(content_length) + cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=True) + else: + cgidata = {} + else: + query_string = os.environ.get('QUERY_STRING', '') + cgidata = urllib.parse.parse_qs(query_string, keep_blank_values=True) + except Exception: # Someone crafted a POST with a bad Content-Type:. doc.AddItem(Header(2, _("Error"))) doc.AddItem(Bold(_('Invalid options to CGI script.'))) @@ -79,6 +88,7 @@ def main(): print(doc.Format()) return + cookie = cgidata.get('cookie', [''])[0] if cookie == '': ask_for_cookie(mlist, doc, _('Confirmation string was empty.')) return @@ -119,17 +129,17 @@ def main(): try: if content[0] == Pending.SUBSCRIPTION: - if cgidata.getfirst('cancel'): + if cgidata.get('cancel', [''])[0]: subscription_cancel(mlist, doc, cookie) - elif cgidata.getfirst('submit'): + elif cgidata.get('submit', [''])[0]: subscription_confirm(mlist, doc, cookie, cgidata) else: subscription_prompt(mlist, doc, cookie, content[1]) elif content[0] == Pending.UNSUBSCRIPTION: try: - if cgidata.getfirst('cancel'): + if cgidata.get('cancel', [''])[0]: unsubscription_cancel(mlist, doc, cookie) - elif cgidata.getfirst('submit'): + elif cgidata.get('submit', [''])[0]: unsubscription_confirm(mlist, doc, cookie) else: unsubscription_prompt(mlist, doc, cookie, *content[1:]) @@ -140,9 +150,9 @@ def main(): # Expunge this record from the pending database. expunge(mlist, cookie) elif content[0] == Pending.CHANGE_OF_ADDRESS: - if cgidata.getfirst('cancel'): + if cgidata.get('cancel', [''])[0]: addrchange_cancel(mlist, doc, cookie) - elif cgidata.getfirst('submit'): + elif cgidata.get('submit', [''])[0]: addrchange_confirm(mlist, doc, cookie) else: # Watch out for users who have unsubscribed themselves in the @@ -156,16 +166,16 @@ def main(): # Expunge this record from the pending database. expunge(mlist, cookie) elif content[0] == Pending.HELD_MESSAGE: - if cgidata.getfirst('cancel'): + if cgidata.get('cancel', [''])[0]: heldmsg_cancel(mlist, doc, cookie) - elif cgidata.getfirst('submit'): + elif cgidata.get('submit', [''])[0]: heldmsg_confirm(mlist, doc, cookie) else: heldmsg_prompt(mlist, doc, cookie, *content[1:]) elif content[0] == Pending.RE_ENABLE: - if cgidata.getfirst('cancel'): + if cgidata.get('cancel', [''])[0]: reenable_cancel(mlist, doc, cookie) - elif cgidata.getfirst('submit'): + elif cgidata.get('submit', [''])[0]: reenable_confirm(mlist, doc, cookie) else: reenable_prompt(mlist, doc, cookie, *content[1:]) @@ -178,7 +188,6 @@ def main(): print(doc.Format()) - def bad_confirmation(doc, extra=''): title = _('Bad confirmation string') doc.SetTitle(title) @@ -197,7 +206,6 @@ def expunge(mlist, cookie): mlist.Unlock() - def ask_for_cookie(mlist, doc, extra=''): title = _('Enter confirmation cookie') doc.SetTitle(title) @@ -227,7 +235,6 @@ def ask_for_cookie(mlist, doc, extra=''): print(doc.Format()) - def subscription_prompt(mlist, doc, cookie, userdesc): email = userdesc.address password = userdesc.password @@ -316,7 +323,6 @@ def subscription_prompt(mlist, doc, cookie, userdesc): doc.AddItem(form) - def subscription_cancel(mlist, doc, cookie): mlist.Lock() try: @@ -336,7 +342,6 @@ def subscription_cancel(mlist, doc, cookie): doc.AddItem(_('You have canceled your subscription request.')) - def subscription_confirm(mlist, doc, cookie, cgidata): # See the comment in admin.py about the need for the signal # handler. @@ -350,14 +355,14 @@ def sigterm_handler(signum, frame, mlist=mlist): try: # Some pending values may be overridden in the form. email of # course is hardcoded. ;) - lang = cgidata.getfirst('language') + lang = cgidata.get('language', [mlist.preferred_language])[0] if not Utils.IsLanguage(lang): lang = mlist.preferred_language i18n.set_language(lang) doc.set_language(lang) if 'digests' in cgidata: try: - digest = int(cgidata.getfirst('digests')) + digest = int(cgidata['digests'][0]) except ValueError: digest = None else: @@ -367,7 +372,7 @@ def sigterm_handler(signum, frame, mlist=mlist): # to confirm the same token simultaneously. If they both succeed in # retrieving the data above, when the second gets here, the cookie # is gone and TypeError is thrown. Catch it below. - fullname = cgidata.getfirst('realname', None) + fullname = cgidata.get('realname', [None])[0] if fullname is not None: fullname = Utils.canonstr(fullname, lang) overrides = UserDesc(fullname=fullname, digest=digest, lang=lang) @@ -424,14 +429,12 @@ def sigterm_handler(signum, frame, mlist=mlist): mlist.Unlock() - def unsubscription_cancel(mlist, doc, cookie): # Expunge this record from the pending database expunge(mlist, cookie) doc.AddItem(_('You have canceled your unsubscription request.')) - def unsubscription_confirm(mlist, doc, cookie): # See the comment in admin.py about the need for the signal # handler. @@ -470,7 +473,6 @@ def sigterm_handler(signum, frame, mlist=mlist): mlist.Unlock() - def unsubscription_prompt(mlist, doc, cookie, addr): title = _('Confirm unsubscription request') doc.SetTitle(title) @@ -513,14 +515,12 @@ def unsubscription_prompt(mlist, doc, cookie, addr): doc.AddItem(form) - def addrchange_cancel(mlist, doc, cookie): # Expunge this record from the pending database expunge(mlist, cookie) doc.AddItem(_('You have canceled your change of address request.')) - def addrchange_confirm(mlist, doc, cookie): # See the comment in admin.py about the need for the signal # handler. @@ -573,7 +573,6 @@ def sigterm_handler(signum, frame, mlist=mlist): mlist.Unlock() - def addrchange_prompt(mlist, doc, cookie, oldaddr, newaddr, globally): title = _('Confirm change of address request') doc.SetTitle(title) @@ -625,7 +624,6 @@ def addrchange_prompt(mlist, doc, cookie, oldaddr, newaddr, globally): doc.AddItem(form) - def heldmsg_cancel(mlist, doc, cookie): title = _('Continue awaiting approval') doc.SetTitle(title) @@ -640,7 +638,6 @@ def heldmsg_cancel(mlist, doc, cookie): doc.AddItem(table) - def heldmsg_confirm(mlist, doc, cookie): # See the comment in admin.py about the need for the signal # handler. @@ -686,7 +683,6 @@ def sigterm_handler(signum, frame, mlist=mlist): mlist.Unlock() - def heldmsg_prompt(mlist, doc, cookie, id): title = _('Cancel held message posting') doc.SetTitle(title) @@ -750,7 +746,6 @@ def sigterm_handler(signum, frame, mlist=mlist): doc.AddItem(form) - def reenable_cancel(mlist, doc, cookie): # Don't actually discard this cookie, since the user may decide to # re-enable their membership at a future time, and we may be sending out @@ -760,7 +755,6 @@ def reenable_cancel(mlist, doc, cookie): this mailing list.""")) - def reenable_confirm(mlist, doc, cookie): # See the comment in admin.py about the need for the signal # handler. diff --git a/Mailman/Cgi/create.py b/Mailman/Cgi/create.py index 3fd73cfd..bbac0866 100644 --- a/Mailman/Cgi/create.py +++ b/Mailman/Cgi/create.py @@ -22,6 +22,7 @@ import sys import os import signal +import urllib.parse import cgi from Mailman import mm_cfg @@ -38,14 +39,32 @@ i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) - def main(): doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) - cgidata = cgi.FieldStorage() try: - cgidata.getfirst('doit', '') + if os.environ.get('REQUEST_METHOD') == 'POST': + content_length = int(os.environ.get('CONTENT_LENGTH', 0)) + if content_length > 0: + form_data = sys.stdin.read(content_length) + cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=True) + else: + cgidata = {} + else: + query_string = os.environ.get('QUERY_STRING', '') + cgidata = urllib.parse.parse_qs(query_string, keep_blank_values=True) + except Exception: + # Someone crafted a POST with a bad Content-Type:. + doc.AddItem(Header(2, _("Error"))) + doc.AddItem(Bold(_('Invalid options to CGI script.'))) + # Send this with a 400 status. + print('Status: 400 Bad Request') + print(doc.Format()) + return + + try: + cgidata.get('doit', [''])[0] except TypeError: # Someone crafted a POST with a bad Content-Type:. doc.AddItem(Header(2, _("Error"))) @@ -83,29 +102,27 @@ def main(): print(doc.Format()) - def process_request(doc, cgidata): # Lowercase the listname since this is treated as the "internal" name. - listname = cgidata.getfirst('listname', '').strip().lower() - owner = cgidata.getfirst('owner', '').strip() + listname = cgidata.get('listname', [''])[0].strip().lower() + owner = cgidata.get('owner', [''])[0].strip() try: - autogen = int(cgidata.getfirst('autogen', '0')) + autogen = int(cgidata.get('autogen', ['0'])[0]) except ValueError: autogen = 0 try: - notify = int(cgidata.getfirst('notify', '0')) + notify = int(cgidata.get('notify', ['0'])[0]) except ValueError: notify = 0 try: - moderate = int(cgidata.getfirst('moderate', - mm_cfg.DEFAULT_DEFAULT_MEMBER_MODERATION)) + moderate = int(cgidata.get('moderate', ['0'])[0]) except ValueError: moderate = mm_cfg.DEFAULT_DEFAULT_MEMBER_MODERATION - password = cgidata.getfirst('password', '').strip() - confirm = cgidata.getfirst('confirm', '').strip() - auth = cgidata.getfirst('auth', '').strip() - langs = cgidata.getvalue('langs', [mm_cfg.DEFAULT_SERVER_LANGUAGE]) + password = cgidata.get('password', [''])[0].strip() + confirm = cgidata.get('confirm', [''])[0].strip() + auth = cgidata.get('auth', [''])[0].strip() + langs = cgidata.get('langs', [mm_cfg.DEFAULT_SERVER_LANGUAGE]) if not isinstance(langs, ListType): langs = [langs] @@ -297,15 +314,13 @@ def sigterm_handler(signum, frame, mlist=mlist): doc.AddItem(table) - # Because the cgi module blows class Dummy(object): - def getfirst(self, name, default): + def get(self, name, default): return default dummy = Dummy() - def request_creation(doc, cgidata=dummy, errmsg=None): # What virtual domain are we using? hostname = Utils.get_domain() @@ -350,14 +365,14 @@ def request_creation(doc, cgidata=dummy, errmsg=None): ftable.AddRow([Center(Italic(_('List Identity')))]) ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, colspan=2) - listname = cgidata.getfirst('listname', '') + listname = cgidata.get('listname', [''])[0] # MAS: Don't websafe twice. TextBox does it. ftable.AddRow([Label(_('Name of list:')), TextBox('listname', listname)]) ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY) ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY) - owner = cgidata.getfirst('owner', '') + owner = cgidata.get('owner', [''])[0] # MAS: Don't websafe twice. TextBox does it. ftable.AddRow([Label(_('Initial list owner address:')), TextBox('owner', owner)]) @@ -365,7 +380,7 @@ def request_creation(doc, cgidata=dummy, errmsg=None): ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY) try: - autogen = int(cgidata.getfirst('autogen', '0')) + autogen = int(cgidata.get('autogen', ['0'])[0]) except ValueError: autogen = 0 ftable.AddRow([Label(_('Auto-generate initial list password?')), @@ -375,25 +390,24 @@ def request_creation(doc, cgidata=dummy, errmsg=None): ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY) ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY) - safepasswd = Utils.websafe(cgidata.getfirst('password', '')) + safepasswd = Utils.websafe(cgidata.get('password', [''])[0]) ftable.AddRow([Label(_('Initial list password:')), PasswordBox('password', safepasswd)]) ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY) ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY) - safeconfirm = Utils.websafe(cgidata.getfirst('confirm', '')) + safeconfirm = Utils.websafe(cgidata.get('confirm', [''])[0]) ftable.AddRow([Label(_('Confirm initial password:')), PasswordBox('confirm', safeconfirm)]) ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY) ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY) try: - notify = int(cgidata.getfirst('notify', '1')) + notify = int(cgidata.get('notify', ['1'])[0]) except ValueError: notify = 1 try: - moderate = int(cgidata.getfirst('moderate', - mm_cfg.DEFAULT_DEFAULT_MEMBER_MODERATION)) + moderate = int(cgidata.get('moderate', ['0'])[0]) except ValueError: moderate = mm_cfg.DEFAULT_DEFAULT_MEMBER_MODERATION diff --git a/Mailman/Cgi/edithtml.py b/Mailman/Cgi/edithtml.py index b1f5601c..6ed7e135 100644 --- a/Mailman/Cgi/edithtml.py +++ b/Mailman/Cgi/edithtml.py @@ -19,9 +19,10 @@ from __future__ import print_function import os -import cgi +import urllib.parse import errno import re +import sys from Mailman import Utils from Mailman import MailList @@ -38,7 +39,6 @@ AUTH_CONTEXTS = (mm_cfg.AuthListAdmin, mm_cfg.AuthSiteAdmin) - def main(): # Trick out pygettext since we want to mark template_data as translatable, # but we don't want to actually translate it here. @@ -96,10 +96,18 @@ def _(s): doc.set_language(mlist.preferred_language) # Must be authenticated to get any farther - cgidata = cgi.FieldStorage() try: - cgidata.getfirst('adminpw', '') - except TypeError: + if os.environ.get('REQUEST_METHOD') == 'POST': + content_length = int(os.environ.get('CONTENT_LENGTH', 0)) + if content_length > 0: + form_data = sys.stdin.read(content_length) + cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=True) + else: + cgidata = {} + else: + query_string = os.environ.get('QUERY_STRING', '') + cgidata = urllib.parse.parse_qs(query_string, keep_blank_values=True) + except Exception: # Someone crafted a POST with a bad Content-Type:. doc.AddItem(Header(2, _("Error"))) doc.AddItem(Bold(_('Invalid options to CGI script.'))) @@ -112,19 +120,19 @@ def _(s): safe_params = ['VARHELP', 'adminpw', 'admlogin'] params = list(cgidata.keys()) if set(params) - set(safe_params): - csrf_checked = csrf_check(mlist, cgidata.getfirst('csrf_token'), + csrf_checked = csrf_check(mlist, cgidata.get('csrf_token', [''])[0], 'admin') else: csrf_checked = True # if password is present, void cookie to force password authentication. - if cgidata.getfirst('adminpw'): + if cgidata.get('adminpw', [''])[0]: os.environ['HTTP_COOKIE'] = '' csrf_checked = True # Editing the html for a list is limited to the list admin and site admin. if not mlist.WebAuthenticate((mm_cfg.AuthListAdmin, mm_cfg.AuthSiteAdmin), - cgidata.getfirst('adminpw', '')): + cgidata.get('adminpw', [''])[0]): if 'admlogin' in cgidata: # This is a re-authorization attempt msg = Bold(FontSize('+1', _('Authorization failed.'))).Format() @@ -141,7 +149,7 @@ def _(s): return # See if the user want to see this page in other language - language = cgidata.getfirst('language', '') + language = cgidata.get('language', [''])[0] if language not in mlist.GetAvailableLanguages(): language = mlist.preferred_language i18n.set_language(language) @@ -190,7 +198,6 @@ def _(s): print(doc.Format()) - def FormatHTML(mlist, doc, template_name, template_info, lang=None): if lang not in mlist.GetAvailableLanguages(): lang = mlist.preferred_language @@ -231,7 +238,6 @@ def FormatHTML(mlist, doc, template_name, template_info, lang=None): doc.AddItem(form) - def ChangeHTML(mlist, cgi_info, template_name, doc, lang=None): if lang not in mlist.GetAvailableLanguages(): lang = mlist.preferred_language diff --git a/Mailman/Cgi/listinfo.py b/Mailman/Cgi/listinfo.py index 9df2e6cc..84aa681e 100644 --- a/Mailman/Cgi/listinfo.py +++ b/Mailman/Cgi/listinfo.py @@ -23,8 +23,9 @@ from builtins import str import os -import cgi +import urllib.parse import time +import sys from Mailman import mm_cfg from Mailman import Utils @@ -39,7 +40,6 @@ i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) - def main(): parts = Utils.GetPathPieces() if not parts: @@ -59,10 +59,18 @@ def main(): return # See if the user want to see this page in other language - cgidata = cgi.FieldStorage() try: - language = cgidata.getfirst('language') - except TypeError: + if os.environ.get('REQUEST_METHOD') == 'POST': + content_length = int(os.environ.get('CONTENT_LENGTH', 0)) + if content_length > 0: + form_data = sys.stdin.read(content_length) + cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=True) + else: + cgidata = {} + else: + query_string = os.environ.get('QUERY_STRING', '') + cgidata = urllib.parse.parse_qs(query_string, keep_blank_values=True) + except Exception: # Someone crafted a POST with a bad Content-Type:. doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) @@ -73,13 +81,13 @@ def main(): print(doc.Format()) return + language = cgidata.get('language', [None])[0] if not Utils.IsLanguage(language): language = mlist.preferred_language i18n.set_language(language) list_listinfo(mlist, language) - def listinfo_overview(msg=''): # Present the general listinfo overview hostname = Utils.get_domain() @@ -174,7 +182,6 @@ def listinfo_overview(msg=''): print(doc.Format()) - def list_listinfo(mlist, lang): # Generate list specific listinfo doc = HeadlessDocument() @@ -282,6 +289,5 @@ def list_listinfo(mlist, lang): print(doc.Format()) - if __name__ == "__main__": main() diff --git a/Mailman/Cgi/options.py b/Mailman/Cgi/options.py index 1c509f97..ef7610f6 100644 --- a/Mailman/Cgi/options.py +++ b/Mailman/Cgi/options.py @@ -24,9 +24,8 @@ import re import sys import os -import cgi +import urllib.parse import signal -import urllib.request, urllib.parse, urllib.error from Mailman import mm_cfg from Mailman import Utils @@ -102,7 +101,25 @@ def main(): return # The total contents of the user's response - cgidata = cgi.FieldStorage(keep_blank_values=1) + try: + if os.environ.get('REQUEST_METHOD') == 'POST': + content_length = int(os.environ.get('CONTENT_LENGTH', 0)) + if content_length > 0: + form_data = sys.stdin.read(content_length) + cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=True) + else: + cgidata = {} + else: + query_string = os.environ.get('QUERY_STRING', '') + cgidata = urllib.parse.parse_qs(query_string, keep_blank_values=True) + except Exception: + # Someone crafted a POST with a bad Content-Type:. + doc.AddItem(Header(2, _("Error"))) + doc.AddItem(Bold(_('Invalid options to CGI script.'))) + # Send this with a 400 status. + print('Status: 400 Bad Request') + print(doc.Format()) + return # CSRF check safe_params = ['displang-button', 'language', 'email', 'password', 'login', @@ -122,21 +139,21 @@ def main(): # we might have a 'language' key in the cgi data. That was an explicit # preference to view the page in, so we should honor that here. If that's # not available, use the list's default language. - language = cgidata.getfirst('language') + language = cgidata.get('language', [None])[0] if not Utils.IsLanguage(language): language = mlist.preferred_language i18n.set_language(language) doc.set_language(language) if lenparts < 2: - user = cgidata.getfirst('email', '').strip() + user = cgidata.get('email', [''])[0].strip() if not user: # If we're coming from the listinfo page and we left the email # address field blank, it's not an error. Likewise if we're # coming from anywhere else. Only issue the error if we came # via one of our buttons. - if (cgidata.getfirst('login') or cgidata.getfirst('login-unsub') - or cgidata.getfirst('login-remind')): + if (cgidata.get('login', [''])[0] or cgidata.get('login-unsub', [''])[0] + or cgidata.get('login-remind', [''])[0]): doc.addError(_('No address given')) loginpage(mlist, doc, None, language) print(doc.Format()) @@ -172,12 +189,12 @@ def main(): # Avoid cross-site scripting attacks if set(params) - set(safe_params): - csrf_checked = csrf_check(mlist, cgidata.getfirst('csrf_token'), + csrf_checked = csrf_check(mlist, cgidata.get('csrf_token', [''])[0], Utils.UnobscureEmail(urllib.parse.unquote(user))) else: csrf_checked = True # if password is present, void cookie to force password authentication. - if cgidata.getfirst('password'): + if cgidata.get('password', [''])[0]: os.environ['HTTP_COOKIE'] = '' csrf_checked = True @@ -194,7 +211,7 @@ def main(): # And now we know the user making the request, so set things up to for the # user's stored preferred language, overridden by any form settings for # their new language preference. - userlang = cgidata.getfirst('language') + userlang = cgidata.get('language', [None])[0] if not Utils.IsLanguage(userlang): userlang = mlist.getMemberLanguage(user) doc.set_language(userlang) @@ -268,7 +285,7 @@ def main(): return # Get the password from the form. - password = cgidata.getfirst('password', '').strip() + password = cgidata.get('password', [''])[0].strip() # Check authentication. We need to know if the credentials match the user # or the site admin, because they are the only ones who are allowed to # change things globally. Specifically, the list admin may not change @@ -335,11 +352,11 @@ def main(): # See if this is VARHELP on topics. varhelp = None if 'VARHELP' in cgidata: - varhelp = cgidata['VARHELP'].value + varhelp = cgidata['VARHELP'][0] elif os.environ.get('QUERY_STRING'): # POST methods, even if their actions have a query string, don't get # put into FieldStorage's keys :-( - qs = cgi.parse_qs(os.environ['QUERY_STRING']).get('VARHELP') + qs = cgidata.get('VARHELP') if qs and type(qs) == list: varhelp = qs[0] if varhelp: @@ -401,18 +418,18 @@ def main(): if 'change-of-address' in cgidata: # We could be changing the user's full name, email address, or both. # Watch out for non-ASCII characters in the member's name. - membername = cgidata.getfirst('fullname') + membername = cgidata.get('fullname', [''])[0] # Canonicalize the member's name membername = Utils.canonstr(membername, language) - newaddr = cgidata.getfirst('new-address') - confirmaddr = cgidata.getfirst('confirm-address') + newaddr = cgidata.get('new-address', [''])[0] + confirmaddr = cgidata.get('confirm-address', [''])[0] oldname = mlist.getMemberName(user) set_address = set_membername = 0 # See if the user wants to change their email address globally. The # list admin is /not/ allowed to make global changes. - globally = cgidata.getfirst('changeaddr-globally') + globally = cgidata.get('changeaddr-globally', [''])[0] if globally and not is_user_or_siteadmin: doc.addError(_(f"""The list administrator may not change the names or addresses for this user's other subscriptions. However, the @@ -525,8 +542,8 @@ def sigterm_handler(signum, frame, mlist=mlist): options_page(mlist, doc, user, cpuser, userlang) print(doc.Format()) return - newpw = cgidata.getfirst('newpw', '').strip() - confirmpw = cgidata.getfirst('confpw', '').strip() + newpw = cgidata.get('newpw', [''])[0].strip() + confirmpw = cgidata.get('confpw', [''])[0].strip() if not newpw or not confirmpw: options_page(mlist, doc, user, cpuser, userlang, _('Passwords may not be blank')) @@ -540,7 +557,7 @@ def sigterm_handler(signum, frame, mlist=mlist): # See if the user wants to change their passwords globally, however # the list admin is /not/ allowed to change passwords globally. - pw_globally = cgidata.getfirst('pw-globally') + pw_globally = cgidata.get('pw-globally', [''])[0] if pw_globally and not is_user_or_siteadmin: doc.addError(_(f"""The list administrator may not change the password for this user's other subscriptions. However, the @@ -565,7 +582,7 @@ def sigterm_handler(signum, frame, mlist=mlist): if 'unsub' in cgidata: # Was the confirming check box turned on? - if not cgidata.getfirst('unsubconfirm'): + if not cgidata.get('unsubconfirm', [0])[0]: options_page( mlist, doc, user, cpuser, userlang, _(f'''You must confirm your unsubscription request by turning @@ -647,7 +664,7 @@ def sigterm_handler(signum, frame, mlist=mlist): ('nodupes', mm_cfg.DontReceiveDuplicates), ): try: - newval = int(cgidata.getfirst(item)) + newval = int(cgidata.get(item, [''])[0]) except (TypeError, ValueError): newval = None @@ -683,7 +700,7 @@ def sigterm_handler(signum, frame, mlist=mlist): # Process user selected topics, but don't make the changes to the # MailList object; we must do that down below when the list is # locked. - topicnames = cgidata.getvalue('usertopic') + topicnames = cgidata.get('usertopic', [''])[0] if topicnames: # Some topics were selected. topicnames can actually be a string # or a list of strings depending on whether more than one topic @@ -737,7 +754,7 @@ def __bool__(self): # The enable/disable option and the password remind option may have # their global flags sets. - if cgidata.getfirst('deliver-globally'): + if cgidata.get('deliver-globally', [''])[0]: # Yes, this is inefficient, but the list is so small it shouldn't # make much of a difference. for flag, newval in newvals: @@ -745,19 +762,19 @@ def __bool__(self): globalopts.enable = newval break - if cgidata.getfirst('remind-globally'): + if cgidata.get('remind-globally', [''])[0]: for flag, newval in newvals: if flag == mm_cfg.SuppressPasswordReminder: globalopts.remind = newval break - if cgidata.getfirst('nodupes-globally'): + if cgidata.get('nodupes-globally', [''])[0]: for flag, newval in newvals: if flag == mm_cfg.DontReceiveDuplicates: globalopts.nodupes = newval break - if cgidata.getfirst('mime-globally'): + if cgidata.get('mime-globally', [''])[0]: for flag, newval in newvals: if flag == mm_cfg.DisableMime: globalopts.mime = newval diff --git a/Mailman/Cgi/private.py b/Mailman/Cgi/private.py index 0ab10427..8464ff67 100644 --- a/Mailman/Cgi/private.py +++ b/Mailman/Cgi/private.py @@ -20,7 +20,7 @@ import os import sys -import cgi +import urllib.parse import mimetypes from Mailman import mm_cfg @@ -39,7 +39,6 @@ SLASH = '/' - def true_path(path): "Ensure that the path is safe by removing .." # Workaround for path traverse vulnerability. Unsuccessful attempts will @@ -48,14 +47,12 @@ def true_path(path): return SLASH.join(parts)[1:] - def guess_type(url, strict): if hasattr(mimetypes, 'common_types'): return mimetypes.guess_type(url, strict) return mimetypes.guess_type(url) - def main(): doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) @@ -118,10 +115,19 @@ def main(): i18n.set_language(mlist.preferred_language) doc.set_language(mlist.preferred_language) - cgidata = cgi.FieldStorage() + # Parse form data try: - username = cgidata.getfirst('username', '').strip() - except TypeError: + if os.environ.get('REQUEST_METHOD') == 'POST': + content_length = int(os.environ.get('CONTENT_LENGTH', 0)) + if content_length > 0: + form_data = sys.stdin.read(content_length) + cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=True) + else: + cgidata = {} + else: + query_string = os.environ.get('QUERY_STRING', '') + cgidata = urllib.parse.parse_qs(query_string, keep_blank_values=True) + except Exception: # Someone crafted a POST with a bad Content-Type:. doc.AddItem(Header(2, _("Error"))) doc.AddItem(Bold(_('Invalid options to CGI script.'))) @@ -129,7 +135,9 @@ def main(): print('Status: 400 Bad Request') print(doc.Format()) return - password = cgidata.getfirst('password', '') + + username = cgidata.get('username', [''])[0].strip() + password = cgidata.get('password', [''])[0] is_auth = 0 realname = mlist.real_name diff --git a/Mailman/Cgi/rmlist.py b/Mailman/Cgi/rmlist.py index 54f2ad55..6706b521 100644 --- a/Mailman/Cgi/rmlist.py +++ b/Mailman/Cgi/rmlist.py @@ -18,7 +18,7 @@ from __future__ import print_function import os -import cgi +import urllib.parse import sys import errno import shutil @@ -36,15 +36,22 @@ i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) - def main(): doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) - cgidata = cgi.FieldStorage() try: - cgidata.getfirst('password', '') - except TypeError: + if os.environ.get('REQUEST_METHOD') == 'POST': + content_length = int(os.environ.get('CONTENT_LENGTH', 0)) + if content_length > 0: + form_data = sys.stdin.read(content_length) + cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=True) + else: + cgidata = {} + else: + query_string = os.environ.get('QUERY_STRING', '') + cgidata = urllib.parse.parse_qs(query_string, keep_blank_values=True) + except Exception: # Someone crafted a POST with a bad Content-Type:. doc.AddItem(Header(2, _("Error"))) doc.AddItem(Bold(_('Invalid options to CGI script.'))) @@ -112,11 +119,10 @@ def main(): print(doc.Format()) - def process_request(doc, cgidata, mlist): - password = cgidata.getfirst('password', '').strip() + password = cgidata.get('password', [''])[0].strip() try: - delarchives = int(cgidata.getfirst('delarchives', '0')) + delarchives = int(cgidata.get('delarchives', ['0'])[0]) except ValueError: delarchives = 0 @@ -203,7 +209,6 @@ def process_request(doc, cgidata, mlist): doc.AddItem(MailmanLogo()) - def request_deletion(doc, mlist, errmsg=None): realname = mlist.real_name title = _('Permanently remove mailing list {realname}') diff --git a/Mailman/Cgi/roster.py b/Mailman/Cgi/roster.py index 6868f8e7..62bc9870 100644 --- a/Mailman/Cgi/roster.py +++ b/Mailman/Cgi/roster.py @@ -26,7 +26,7 @@ import sys import os -import cgi +import urllib.parse import urllib.request, urllib.parse, urllib.error from Mailman import mm_cfg @@ -42,7 +42,6 @@ i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) - def main(): parts = Utils.GetPathPieces() if not parts: @@ -61,12 +60,19 @@ def main(): syslog('error', 'roster: No such list "%s": %s', listname, e) return - cgidata = cgi.FieldStorage() - - # messages in form should go in selected language (if any...) + # Parse form data try: - lang = cgidata.getfirst('language') - except TypeError: + if os.environ.get('REQUEST_METHOD') == 'POST': + content_length = int(os.environ.get('CONTENT_LENGTH', 0)) + if content_length > 0: + form_data = sys.stdin.read(content_length) + cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=True) + else: + cgidata = {} + else: + query_string = os.environ.get('QUERY_STRING', '') + cgidata = urllib.parse.parse_qs(query_string, keep_blank_values=True) + except Exception: # Someone crafted a POST with a bad Content-Type:. doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) @@ -77,6 +83,8 @@ def main(): print(doc.Format()) return + # messages in form should go in selected language (if any...) + lang = cgidata.get('language', [None])[0] if not Utils.IsLanguage(lang): lang = mlist.preferred_language i18n.set_language(lang) @@ -86,8 +94,8 @@ def main(): # "admin"-only, then we try to cookie authenticate the user, and failing # that, we check roster-email and roster-pw fields for a valid password. # (also allowed: the list moderator, the list admin, and the site admin). - password = cgidata.getfirst('roster-pw', '').strip() - addr = cgidata.getfirst('roster-email', '').strip() + password = cgidata.get('roster-pw', [''])[0].strip() + addr = cgidata.get('roster-email', [''])[0].strip() list_hidden = (not mlist.WebAuthenticate((mm_cfg.AuthUser,), password, addr) and mlist.WebAuthenticate((mm_cfg.AuthListModerator, @@ -141,7 +149,6 @@ def main(): print(doc.Format()) - def error_page(errmsg): doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) diff --git a/Mailman/Cgi/subscribe.py b/Mailman/Cgi/subscribe.py index 776719fb..c667b91e 100644 --- a/Mailman/Cgi/subscribe.py +++ b/Mailman/Cgi/subscribe.py @@ -20,11 +20,9 @@ import sys import os -import cgi import time import signal -import urllib.request, urllib.parse, urllib.error -import urllib.request, urllib.error, urllib.parse +import urllib.parse import json from Mailman import mm_cfg @@ -46,7 +44,6 @@ i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) - def main(): doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) @@ -74,9 +71,28 @@ def main(): # See if the form data has a preferred language set, in which case, use it # for the results. If not, use the list's preferred language. - cgidata = cgi.FieldStorage() try: - language = cgidata.getfirst('language', '') + if os.environ.get('REQUEST_METHOD') == 'POST': + content_length = int(os.environ.get('CONTENT_LENGTH', 0)) + if content_length > 0: + form_data = sys.stdin.read(content_length) + cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=True) + else: + cgidata = {} + else: + query_string = os.environ.get('QUERY_STRING', '') + cgidata = urllib.parse.parse_qs(query_string, keep_blank_values=True) + except Exception: + # Someone crafted a POST with a bad Content-Type:. + doc.AddItem(Header(2, _("Error"))) + doc.AddItem(Bold(_('Invalid options to CGI script.'))) + # Send this with a 400 status. + print('Status: 400 Bad Request') + print(doc.Format()) + return + + try: + language = cgidata.get('language', [''])[0] except TypeError: # Someone crafted a POST with a bad Content-Type:. doc.AddItem(Header(2, _("Error"))) @@ -117,18 +133,17 @@ def sigterm_handler(signum, frame, mlist=mlist): mlist.Unlock() - def process_form(mlist, doc, cgidata, lang): listowner = mlist.GetOwnerEmail() realname = mlist.real_name results = [] # The email address being subscribed, required - email = cgidata.getfirst('email', '').strip() + email = cgidata.get('email', [''])[0].strip() if not email: results.append(_('You must supply a valid email address.')) - fullname = cgidata.getfirst('fullname', '') + fullname = cgidata.get('fullname', [''])[0] # Canonicalize the full name fullname = Utils.canonstr(fullname, lang) # Who was doing the subscribing? @@ -141,7 +156,7 @@ def process_form(mlist, doc, cgidata, lang): if mm_cfg.RECAPTCHA_SECRET_KEY: request_data = urllib.parse.urlencode({ 'secret': mm_cfg.RECAPTCHA_SECRET_KEY, - 'response': cgidata.getvalue('g-recaptcha-response', ''), + 'response': cgidata.get('g-recaptcha-response', [''])[0], 'remoteip': remote}) request_data = request_data.encode('utf-8') request = urllib.request.Request( @@ -171,8 +186,8 @@ def process_form(mlist, doc, cgidata, lang): # for our hash so it doesn't matter. remote1 = remote.rsplit(':', 1)[0] try: - ftime, fcaptcha_idx, fhash = cgidata.getfirst( - 'sub_form_token', '').split(':') + ftime, fcaptcha_idx, fhash = cgidata.get( + 'sub_form_token', [''])[0].split(':') then = int(ftime) except ValueError: ftime = fcaptcha_idx = fhash = '' @@ -193,7 +208,7 @@ def process_form(mlist, doc, cgidata, lang): results.append(_('You must GET the form before submitting it.')) # Check captcha if isinstance(mm_cfg.CAPTCHAS, dict): - captcha_answer = cgidata.getvalue('captcha_answer', '') + captcha_answer = cgidata.get('captcha_answer', [''])[0] if not Utils.captcha_verify( fcaptcha_idx, captcha_answer, mm_cfg.CAPTCHAS): results.append(_( @@ -203,8 +218,8 @@ def process_form(mlist, doc, cgidata, lang): syslog('mischief', 'Attempt to self subscribe %s: %s', email, remote) results.append(_('You may not subscribe a list to itself!')) # If the user did not supply a password, generate one for him - password = cgidata.getfirst('pw', '').strip() - confirmed = cgidata.getfirst('pw-conf', '').strip() + password = cgidata.get('pw', [''])[0].strip() + confirmed = cgidata.get('pw-conf', [''])[0].strip() if not password and not confirmed: password = Utils.MakeRandomPassword() @@ -214,7 +229,7 @@ def process_form(mlist, doc, cgidata, lang): results.append(_('Your passwords did not match.')) # Get the digest option for the subscription. - digestflag = cgidata.getfirst('digest') + digestflag = cgidata.get('digest', [''])[0] if digestflag: try: digest = int(digestflag) @@ -347,7 +362,6 @@ def process_form(mlist, doc, cgidata, lang): print_results(mlist, results, doc, lang) - def print_results(mlist, results, doc, lang): # The bulk of the document will come from the options.html template, which # includes its own html armor (head tags, etc.). Suppress the head that From e1bf02538af00fd9985a69979731908a0ec8b0b3 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 20 Apr 2025 05:28:55 -0400 Subject: [PATCH 010/748] import cgi cleanup --- Mailman/Cgi/create.py | 1 - Mailman/Utils.py | 1 - 2 files changed, 2 deletions(-) diff --git a/Mailman/Cgi/create.py b/Mailman/Cgi/create.py index bbac0866..24ca285c 100644 --- a/Mailman/Cgi/create.py +++ b/Mailman/Cgi/create.py @@ -23,7 +23,6 @@ import os import signal import urllib.parse -import cgi from Mailman import mm_cfg from Mailman import MailList diff --git a/Mailman/Utils.py b/Mailman/Utils.py index c5272819..6ebada69 100644 --- a/Mailman/Utils.py +++ b/Mailman/Utils.py @@ -27,7 +27,6 @@ import os import sys import re -import cgi import time import errno import base64 From a5f0340677b0448a0a9dfb117d66993d18899008 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 20 Apr 2025 05:52:56 -0400 Subject: [PATCH 011/748] getopt -> argparse --- bin/arch | 88 +++------ bin/b4b5-archfix | 31 +--- bin/change_pw | 76 ++++---- bin/check_db | 63 ++----- bin/check_perms | 57 +++--- bin/cleanarch | 70 ++----- bin/clone_member | 113 ++++------- bin/discard | 39 ++-- bin/dumpdb | 85 ++++----- bin/fix_url.py | 40 ++-- bin/genaliases | 34 ++-- bin/inject | 70 +++---- bin/mailmanctl | 287 +++++----------------------- bin/mmsitepass | 44 ++--- bin/msgfmt-python2.py | 55 ++---- bin/msgfmt.py | 48 ++--- bin/pygettext.py | 423 ++++++++++++++++++------------------------ bin/qrunner | 184 ++++++++---------- bin/rb-archfix | 31 +--- bin/reset_pw.py | 36 ++-- bin/transcheck | 107 +++++------ bin/update | 189 ++++++------------- bin/withlist | 157 +++++++--------- contrib/sitemapgen | 48 ++--- cron/bumpdigests | 26 +-- cron/checkdbs | 31 +--- cron/cull_bad_shunt | 26 +-- cron/disabled | 88 ++++----- cron/gate_news | 48 +---- cron/mailpasswds | 44 +---- cron/nightly_gzip | 37 ++-- cron/senddigests | 40 ++-- tests/onebounce.py | 44 ++--- 33 files changed, 952 insertions(+), 1807 deletions(-) diff --git a/bin/arch b/bin/arch index b7af5572..eabe9aef 100644 --- a/bin/arch +++ b/bin/arch @@ -58,7 +58,7 @@ be some path in the archives/private directory. For example: import os import sys -import getopt +import argparse import shutil import paths @@ -76,72 +76,37 @@ PROGRAM = sys.argv[0] i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) -def usage(code, msg=''): - if code: - fd = sys.stderr - else: - fd = sys.stdout - # Ensure PROGRAM is a string, not bytes - if isinstance(PROGRAM, bytes): - PROGRAM = PROGRAM.decode('utf-8', 'replace') - print(C_(__doc__), file=fd) - if msg: - print(msg, file=fd) - sys.exit(code) +def parse_args(): + parser = argparse.ArgumentParser(description='Rebuild a list\'s archive.') + parser.add_argument('-q', '--quiet', action='store_true', + help='Make the archiver output less verbose') + parser.add_argument('--wipe', action='store_true', + help='First wipe out the original archive before regenerating') + parser.add_argument('-s', '--start', type=int, + help='Start indexing at article N, where article 0 is the first in the mbox') + parser.add_argument('-e', '--end', type=int, + help='End indexing at article M') + parser.add_argument('listname', + help='The name of the list to rebuild the archive for') + parser.add_argument('mbox', nargs='?', + help='The path to a list\'s complete mbox archive') + return parser.parse_args() def main(): - # get command line arguments - try: - opts, args = getopt.getopt( - sys.argv[1:], 'hs:e:q', - ['help', 'start=', 'end=', 'quiet', 'wipe']) - except getopt.error as msg: - usage(1, msg) - - start = None - end = None - verbose = 1 - wipe = 0 - for opt, arg in opts: - if opt in ('-h', '--help'): - usage(0) - elif opt in ('-s', '--start'): - try: - start = int(arg) - except ValueError: - usage(1) - elif opt in ('-e', '--end'): - try: - end = int(arg) - except ValueError: - usage(1) - elif opt in ('-q', '--quiet'): - verbose = 0 - elif opt == '--wipe': - wipe = 1 - - # grok arguments - if len(args) < 1: - usage(1, C_('listname is required')) - listname = args[0].lower().strip() - - if len(args) < 2: - mbox = None - else: - mbox = args[1] - - if len(args) > 2: - usage(1) + args = parse_args() # open the mailing list object mlist = None lock = None try: try: - mlist = MailList(listname) + mlist = MailList(args.listname.lower().strip()) except Errors.MMListError as e: - usage(2, C_('No such list "%(listname)s"\n%(e)s')) + print(C_('No such list "%(listname)s"\n%(e)s'), file=sys.stderr) + sys.exit(2) + + mbox = args.mbox if mbox is None: mbox = mlist.ArchiveFileName() @@ -166,9 +131,10 @@ def main(): try: fp = open(mbox) except IOError as msg: - usage(3, C_('Cannot open mbox file %(mbox)s: %(msg)s')) + print(C_('Cannot open mbox file %(mbox)s: %(msg)s'), file=sys.stderr) + sys.exit(3) # Maybe wipe the old archives - if wipe: + if args.wipe: if mlist.scrub_nondigest: # TK: save the attachments dir because they are not in mbox saved = 0 @@ -185,9 +151,9 @@ def main(): os.renames(savedir, atchdir) archiver = HyperArchive(mlist) - archiver.VERBOSE = verbose + archiver.VERBOSE = not args.quiet try: - archiver.processUnixMailbox(fp, start, end) + archiver.processUnixMailbox(fp, args.start, args.end) finally: archiver.close() fp.close() diff --git a/bin/b4b5-archfix b/bin/b4b5-archfix index ce6ecb90..5ac5a181 100644 --- a/bin/b4b5-archfix +++ b/bin/b4b5-archfix @@ -39,7 +39,7 @@ from __future__ import print_function import os import sys -import getopt +import argparse import marshal import pickle @@ -50,32 +50,17 @@ from Mailman.i18n import C_ PROGRAM = sys.argv[0] -def usage(code, msg=''): - if code: - fd = sys.stderr - else: - fd = sys.stdout - # Ensure PROGRAM is a string, not bytes - if isinstance(PROGRAM, bytes): - PROGRAM = PROGRAM.decode('utf-8', 'replace') - print(C_(__doc__), file=fd) - if msg: - print(msg, file=fd) - sys.exit(code) +def parse_args(): + parser = argparse.ArgumentParser(description='Fix the MM2.1b4 archives.') + parser.add_argument('files', nargs='+', + help='Files to process') + return parser.parse_args() def main(): - # get command line arguments - try: - opts, args = getopt.getopt(sys.argv[1:], 'h', ['help']) - except getopt.error as msg: - usage(1, msg) + args = parse_args() - for opt, arg in opts: - if opt in ('-h', '--help'): - usage(0) - - for filename in args: + for filename in args.files: print(('processing:', filename)) fp = open(filename, 'rb') d = marshal.load(fp) diff --git a/bin/change_pw b/bin/change_pw index c0c7c8e1..880f0588 100644 --- a/bin/change_pw +++ b/bin/change_pw @@ -66,7 +66,7 @@ Options: """ import sys -import getopt +import argparse import paths from Mailman import mm_cfg @@ -82,7 +82,21 @@ C_ = i18n.C_ SPACE = ' ' - +def parse_args(): + parser = argparse.ArgumentParser(description='Change a list\'s password.') + parser.add_argument('-a', '--all', action='store_true', + help='Change the password for all lists') + parser.add_argument('-d', '--domain', action='append', + help='Change the password for all lists in the virtual domain') + parser.add_argument('-l', '--listname', action='append', + help='Change the password only for the named list') + parser.add_argument('-p', '--password', + help='Use the supplied plain text password as the new password') + parser.add_argument('-q', '--quiet', action='store_true', + help='Don\'t notify list owners of the new password') + return parser.parse_args() + + def usage(code, msg=''): if code: fd = sys.stderr @@ -94,7 +108,6 @@ def usage(code, msg=''): sys.exit(code) - _listcache = {} def openlist(listname): @@ -109,40 +122,28 @@ def openlist(listname): return mlist - def main(): - # Parse options try: - opts, args = getopt.getopt( - sys.argv[1:], 'ad:l:p:qh', - ['all', 'domain=', 'listname=', 'password=', 'quiet', 'help']) - except getopt.error as msg: - usage(1, msg) + args = parse_args() + except SystemExit: + usage(1) # defaults listnames = {} domains = {} - password = None - quiet = 0 - - for opt, arg in opts: - if opt in ('-h', '--help'): - usage(0) - elif opt in ('-a', '--all'): - for name in Utils.list_names(): - listnames[name] = 1 - elif opt in ('-d', '--domain'): - domains[arg] = 1 - elif opt in ('-l', '--listname'): - listnames[arg.lower()] = 1 - elif opt in ('-p', '--password'): - password = arg - elif opt in ('-q', '--quiet'): - quiet = 1 - - if args: - strargs = SPACE.join(args) - usage(1, C_('Bad arguments: %(strargs)s')) + password = args.password + + if args.all: + for name in Utils.list_names(): + listnames[name] = 1 + elif args.listname: + for name in args.listname: + listnames[name.lower()] = 1 + elif args.domain: + for domain in args.domain: + domains[domain] = 1 + else: + usage(1, C_('No lists specified')) if password is not None: if not password: @@ -152,7 +153,7 @@ def main(): if domains: for name in Utils.list_names(): mlist = openlist(name) - if domains.has_key(mlist.host_name): + if mlist.host_name in domains: listnames[name] = 1 if not listnames: @@ -179,7 +180,7 @@ def main(): # Notification print(C_('New %(listname)s password: %(notifypassword)s')) - if not quiet: + if not args.quiet: otrans = i18n.get_translation() i18n.set_language(mlist.preferred_language) try: @@ -198,14 +199,13 @@ Please be sure to use this for all future list administration. You may want to log in now to your list and change the password to something more to your liking. Visit your list admin page at - %(adminurl)s -'''), - mlist.preferred_language) +%(adminurl)s + +'''), mlist) + msg.send(mlist) finally: i18n.set_translation(otrans) - msg.send(mlist) - if __name__ == '__main__': main() diff --git a/bin/check_db b/bin/check_db index ef45deb1..a4071f3e 100755 --- a/bin/check_db +++ b/bin/check_db @@ -33,25 +33,12 @@ marshals. config.safety is a pickle written by 2.1a3 and beyond when the primary config.pck file could not be read. Usage: %(PROGRAM)s [options] [listname [listname ...]] - -Options: - - --all / -a - Check the databases for all lists. Otherwise only the lists named on - the command line are checked. - - --verbose / -v - Verbose output. The state of every tested file is printed. - Otherwise only corrupt files are displayed. - - --help / -h - Print this text and exit. """ import sys import os import errno -import getopt +import argparse import marshal import pickle @@ -64,18 +51,15 @@ from Mailman.i18n import C_ PROGRAM = sys.argv[0] -def usage(code, msg=''): - if code: - fd = sys.stderr - else: - fd = sys.stdout - # Ensure PROGRAM is a string, not bytes - if isinstance(PROGRAM, bytes): - PROGRAM = PROGRAM.decode('utf-8', 'replace') - print(C_(__doc__), file=fd) - if msg: - print(msg, file=fd) - sys.exit(code) +def parse_args(): + parser = argparse.ArgumentParser(description='Check a list\'s config database file for integrity.') + parser.add_argument('-a', '--all', action='store_true', + help='Check the databases for all lists') + parser.add_argument('-v', '--verbose', action='store_true', + help='Verbose output. The state of every tested file is printed') + parser.add_argument('listnames', nargs='*', + help='List names to check (optional if --all is specified)') + return parser.parse_args() def testfile(dbfile): @@ -115,22 +99,11 @@ def testfile(dbfile): def main(): - try: - opts, args = getopt.getopt(sys.argv[1:], 'ahv', - ['all', 'verbose', 'help']) - except getopt.error as msg: - usage(1, msg) - - verbose = 0 - listnames = args - - for opt, arg in opts: - if opt in ('-h', '--help'): - usage(0) - elif opt in ('-v', '--verbose'): - verbose = 1 - elif opt in ('-a', '--all'): - listnames = Utils.list_names() + args = parse_args() + + listnames = args.listnames + if args.all: + listnames = Utils.list_names() listnames = [n.lower().strip() for n in listnames] if not listnames: @@ -147,7 +120,7 @@ def main(): dfile = os.path.join(mlist.fullpath(), 'config.db') dlast = dfile + '.last' - if verbose: + if args.verbose: print(C_('List:'), listname) for file in (pfile, plast, dfile, dlast): @@ -156,7 +129,7 @@ def main(): testfile(file) except IOError as e: # Don't report ENOENT unless we're in verbose mode - if verbose or e.errno != errno.ENOENT: + if args.verbose or e.errno != errno.ENOENT: status = e except Exception as e: status = e @@ -167,7 +140,7 @@ def main(): print(' ', status) else: print(' %s: %s' % (file, status)) - elif verbose: + elif args.verbose: print(C_(' %(file)s: okay')) diff --git a/bin/check_perms b/bin/check_perms index b69fe277..ec79c7a0 100755 --- a/bin/check_perms +++ b/bin/check_perms @@ -31,7 +31,7 @@ import sys import pwd import grp import errno -import getopt +import argparse from stat import * try: @@ -351,34 +351,20 @@ def checkdata(): print() -def usage(code, msg=''): - if code: - fd = sys.stderr - else: - fd = sys.stdout - # Ensure PROGRAM is a string, not bytes - if isinstance(PROGRAM, bytes): - PROGRAM = PROGRAM.decode('utf-8', 'replace') - print(C_(__doc__), file=fd) - if msg: - print(msg, file=fd) - sys.exit(code) +def parse_args(): + parser = argparse.ArgumentParser(description='Check the permissions for the Mailman installation.') + parser.add_argument('-f', '--fix', action='store_true', + help='Fix all the permission problems found') + parser.add_argument('-v', '--verbose', action='store_true', + help='Be verbose') + return parser.parse_args() -if __name__ == '__main__': - try: - opts, args = getopt.getopt(sys.argv[1:], 'fvh', - ['fix', 'verbose', 'help']) - except getopt.error as msg: - usage(1, msg) - - for opt, arg in opts: - if opt in ('-h', '--help'): - usage(0) - elif opt in ('-f', '--fix'): - STATE.FIX = True - elif opt in ('-v', '--verbose'): - STATE.VERBOSE = True +def main(): + args = parse_args() + + STATE.FIX = args.fix + STATE.VERBOSE = args.verbose checkall() checkarchives() @@ -389,8 +375,17 @@ if __name__ == '__main__': checkadminpw() checkmta() - if not STATE.ERRORS: - print(C_('No problems found')) + if STATE.ERRORS: + if STATE.FIX: + print(C_('Fixed %(STATE.ERRORS)d permission problems.')) + else: + print(C_('Found %(STATE.ERRORS)d permission problems.')) + print(C_('Run with -f to fix them.')) + sys.exit(1) else: - print(C_('Problems found:'), STATE.ERRORS) - print(C_('Re-run as %(MAILMAN_USER)s (or root) with -f flag to fix')) + print(C_('No permission problems found.')) + sys.exit(0) + + +if __name__ == '__main__': + main() diff --git a/bin/cleanarch b/bin/cleanarch index 2d6b0023..2be422bf 100644 --- a/bin/cleanarch +++ b/bin/cleanarch @@ -32,25 +32,12 @@ lines that start "From " but do not pass this stricter test are escaped with a > character. Usage: cleanarch [options] < inputfile > outputfile -Options: - -s n - --status=n - Print a # character every n lines processed - - -q / --quiet - Don't print changed line information to standard error. - - -n / --dry-run - Don't actually output anything. - - -h / --help - Print this message and exit """ from __future__ import print_function import re import sys -import getopt +import argparse import mailbox import paths @@ -64,19 +51,17 @@ cre = re.compile(mailbox.UnixMailbox._fromlinepattern) fre = re.compile(r'[\041-\071\073-\176]+') - -def usage(code, msg=''): - if code: - fd = sys.stderr - else: - fd = sys.stdout - print(C_(__doc__), file=fd) - if msg: - print(msg, file=fd) - sys.exit(code) +def parse_args(): + parser = argparse.ArgumentParser(description='Clean up an .mbox archive file.') + parser.add_argument('-s', '--status', type=int, + help='Print a # character every n lines processed') + parser.add_argument('-q', '--quiet', action='store_true', + help='Don\'t print changed line information to standard error') + parser.add_argument('-n', '--dry-run', action='store_true', + help='Don\'t actually output anything') + return parser.parse_args() - def escape_line(line, lineno, quiet, output): if output: sys.stdout.write('>' + line) @@ -85,34 +70,12 @@ def escape_line(line, lineno, quiet, output): print(line[:-1], file=sys.stderr) - def main(): - try: - opts, args = getopt.getopt( - sys.argv[1:], 'hqns:', - ['help', 'quiet', 'dry-run', 'status=']) - except getopt.error as msg: - usage(1, msg) - - quiet = False - output = True - status = -1 - - for opt, arg in opts: - if opt in ('-h', '--help'): - usage(0) - elif opt in ('-q', '--quiet'): - quiet = True - elif opt in ('-n', '--dry-run'): - output = False - elif opt in ('-s', '--status'): - try: - status = int(arg) - except ValueError: - usage(1, C_('Bad status number: %(arg)s')) - - if args: - usage(1) + args = parse_args() + + quiet = args.quiet + output = not args.dry_run + status = args.status lineno = 0 statuscnt = 0 @@ -158,7 +121,7 @@ def main(): elif output: # Any old line sys.stdout.write(line) - if status > 0 and (lineno % status) == 0: + if status and status > 0 and (lineno % status) == 0: sys.stderr.write('#') statuscnt += 1 if statuscnt > 50: @@ -168,6 +131,5 @@ def main(): print(C_('%(messages)d messages found'), file=sys.stderr) - if __name__ == '__main__': main() diff --git a/bin/clone_member b/bin/clone_member index cfe40921..e0d6c65d 100755 --- a/bin/clone_member +++ b/bin/clone_member @@ -66,7 +66,7 @@ Where: """ import sys -import getopt +import argparse import paths from Mailman import MailList @@ -75,19 +75,6 @@ from Mailman import Errors from Mailman.i18n import C_ - -def usage(code, msg=''): - if code: - fd = sys.stderr - else: - fd = sys.stdout - print(C_(__doc__), file=fd) - if msg: - print(fd, msg, file=fd) - sys.exit(code) - - - def dolist(mlist, options): SPACE = ' ' if not options.quiet: @@ -151,75 +138,57 @@ def dolist(mlist, options): print(C_(' original address removed:'), options.fromaddr) - +def parse_args(): + parser = argparse.ArgumentParser(description='Clone a member address.') + parser.add_argument('-l', '--listname', action='append', + help='Check and modify only the named mailing lists') + parser.add_argument('-r', '--remove', action='store_true', + help='Remove the old address from the mailing list after it\'s been cloned') + parser.add_argument('-a', '--admin', action='store_true', + help='Scan the list admin addresses for the old address, and clone or change them too') + parser.add_argument('-q', '--quiet', action='store_true', + help='Do the modifications quietly') + parser.add_argument('-n', '--nomodify', action='store_true', + help='Print what would be done, but don\'t actually do it') + parser.add_argument('fromaddr', + help='The old address of the user') + parser.add_argument('toaddr', + help='The new address of the user') + return parser.parse_args() + + def main(): - # default options - class Options: - listnames = None - remove = 0 - admintoo = 0 - quiet = 0 - modify = 1 - - # scan sysargs - try: - opts, args = getopt.getopt( - sys.argv[1:], 'arl:qnh', - ['admin', 'remove', 'listname=', 'quiet', 'nomodify', 'help']) - except getopt.error as msg: - usage(1, msg) - - options = Options() - for opt, arg in opts: - if opt in ('-h', '--help'): - usage(0) - elif opt in ('-q', '--quiet'): - options.quiet = 1 - elif opt in ('-n', '--nomodify'): - options.modify = 0 - elif opt in ('-a', '--admin'): - options.admintoo = 1 - elif opt in ('-r', '--remove'): - options.remove = 1 - elif opt in ('-l', '--listname'): - if options.listnames is None: - options.listnames = [] - options.listnames.append(arg.lower()) - - # further options and argument processing - if not options.modify: - options.quiet = 0 - - if len(args) != 2: - usage(1) - fromaddr = args[0] - toaddr = args[1] - + args = parse_args() + # validate and normalize the target address try: - Utils.ValidateEmail(toaddr) + Utils.ValidateEmail(args.toaddr) except Errors.EmailAddressError: - usage(1, C_('Not a valid email address: %(toaddr)s')) - lfromaddr = fromaddr.lower() - options.toaddr = toaddr - options.fromaddr = fromaddr - options.lfromaddr = lfromaddr + print(C_('Invalid email address:'), args.toaddr, file=sys.stderr) + sys.exit(1) - if options.listnames is None: - options.listnames = Utils.list_names() + # normalize the addresses + args.lfromaddr = args.fromaddr.lower() + args.toaddr = args.toaddr.lower() - for listname in options.listnames: + # get the list of lists to process + if args.listname: + listnames = args.listname + else: + listnames = Utils.list_names() + + # process each list + for listname in listnames: try: - mlist = MailList.MailList(listname) - except Errors.MMListError as e: - print(C_('Error opening list "%(listname)s", skipping.\n%(e)s')) + mlist = MailList(listname, lock=0) + except Errors.MMUnknownListError: + print(C_('Unknown list:'), listname, file=sys.stderr) continue try: - dolist(mlist, options) + dolist(mlist, args) finally: - mlist.Save() mlist.Unlock() - + if __name__ == '__main__': main() diff --git a/bin/discard b/bin/discard index 2e190def..333d0be9 100644 --- a/bin/discard +++ b/bin/discard @@ -36,7 +36,7 @@ Options: import os import re import sys -import getopt +import argparse import paths from Mailman import mm_cfg @@ -46,33 +46,19 @@ from Mailman.i18n import C_ cre = re.compile(r'heldmsg-(?P.*)-(?P[0-9]+)\.(pck|txt)$') - -def usage(code, msg=''): - if code: - fd = sys.stderr - else: - fd = sys.stdout - print(C_(__doc__), file=fd) - if msg: - print(msg, file=fd) - sys.exit(code) +def parse_args(): + parser = argparse.ArgumentParser(description='Discard held messages.') + parser.add_argument('-q', '--quiet', action='store_true', + help='Don\'t print status messages') + parser.add_argument('files', nargs='*', + help='Files containing held messages to discard') + return parser.parse_args() - def main(): - try: - opts, args = getopt.getopt(sys.argv[1:], 'hq', ['help', 'quiet']) - except getopt.error as msg: - usage(1, msg) - - quiet = False - for opt, arg in opts: - if opt in ('-h', '--help'): - usage(0) - elif opt in ('-q', '--quiet'): - quiet = True - - files = args + args = parse_args() + + files = args.files if not files: print(C_('Nothing to do.')) @@ -102,7 +88,7 @@ def main(): for id in ids: # No comment, no preserve, no forward, no forwarding address mlist.HandleRequest(id, mm_cfg.DISCARD, '', False, False, '') - if not quiet: + if not args.quiet: print(C_( 'Discarded held msg #%(id)s for list %(listname)s')) mlist.Save() @@ -110,6 +96,5 @@ def main(): mlist.Unlock() - if __name__ == '__main__': main() diff --git a/bin/dumpdb b/bin/dumpdb index 86a9fe57..6fb37b47 100644 --- a/bin/dumpdb +++ b/bin/dumpdb @@ -46,7 +46,7 @@ Python pickle. In either case, if you want to override the default assumption """ import sys -import getopt +import argparse import pprint import pickle import marshal @@ -58,59 +58,37 @@ from Mailman.i18n import C_ PROGRAM = sys.argv[0] COMMASPACE = ', ' - -def usage(code, msg=''): - if code: - fd = sys.stderr - else: - fd = sys.stdout - # Ensure PROGRAM is a string, not bytes - if isinstance(PROGRAM, bytes): - PROGRAM = PROGRAM.decode('utf-8', 'replace') - print(C_(__doc__) % globals(), file=fd) - if msg: - print(msg, file=fd) - sys.exit(code) + +def parse_args(): + parser = argparse.ArgumentParser(description='Dump the contents of any Mailman `database\' file.') + group = parser.add_mutually_exclusive_group() + group.add_argument('-m', '--marshal', action='store_true', + help='Assume the file contains a Python marshal') + group.add_argument('-p', '--pickle', action='store_true', + help='Assume the file contains a Python pickle') + parser.add_argument('-n', '--noprint', action='store_true', + help='Don\'t attempt to pretty print the object') + parser.add_argument('filename', + help='The database file to dump') + return parser.parse_args() - def main(): - try: - opts, args = getopt.getopt(sys.argv[1:], 'mphn', - ['marshal', 'pickle', 'help', 'noprint']) - except getopt.error as msg: - usage(1, msg) - - # Options. - # None == guess, 0 == pickle, 1 == marshal - filetype = None - doprint = True - - for opt, arg in opts: - if opt in ('-h', '--help'): - usage(0) - elif opt in ('-p', '--pickle'): - filetype = 0 - elif opt in ('-m', '--marshal'): - filetype = 1 - elif opt in ('-n', '--noprint'): - doprint = False - - if len(args) < 1: - usage(1, C_('No filename given.')) - elif len(args) > 1: - pargs = COMMASPACE.join(args) - usage(1, C_('Bad arguments: %(pargs)s')) - else: - filename = args[0] + args = parse_args() - if filetype is None: - if filename.endswith('.db'): - filetype = 1 - elif filename.endswith('.pck'): - filetype = 0 + # Determine file type + if args.marshal: + filetype = 1 # marshal + elif args.pickle: + filetype = 0 # pickle + else: + if args.filename.endswith('.db'): + filetype = 1 # marshal + elif args.filename.endswith('.pck'): + filetype = 0 # pickle else: - usage(1, C_('Please specify either -p or -m.')) + print(C_('Please specify either -p or -m.'), file=sys.stderr) + sys.exit(1) # Handle dbs pp = pprint.PrettyPrinter(indent=4) @@ -120,11 +98,11 @@ def main(): else: load = pickle.load typename = 'pickle' - fp = open(filename, 'rb') + fp = open(args.filename, 'rb') m = [] try: cnt = 1 - if doprint: + if not args.noprint: print(C_('[----- start %(typename)s file -----]')) while True: try: @@ -149,10 +127,10 @@ def main(): new_obj.append(item) obj = new_obj except EOFError: - if doprint: + if not args.noprint: print(C_('[----- end %(typename)s file -----]')) break - if doprint: + if not args.noprint: print(C_('<----- start object %(cnt)s ----->')) if isinstance(obj, str): print(obj) @@ -165,6 +143,5 @@ def main(): return m - if __name__ == '__main__': msg = main() diff --git a/bin/fix_url.py b/bin/fix_url.py index 243f4f20..dce6a8ba 100644 --- a/bin/fix_url.py +++ b/bin/fix_url.py @@ -40,14 +40,22 @@ from __future__ import print_function import sys -import getopt +import argparse import paths from Mailman import mm_cfg from Mailman.i18n import C_ - +def parse_args(args): + parser = argparse.ArgumentParser(description='Reset a list\'s web_page_url attribute to the default setting.') + parser.add_argument('-u', '--urlhost', + help='Look up urlhost in the virtual host table and set the web_page_url and host_name attributes') + parser.add_argument('-v', '--verbose', action='store_true', + help='Print what the script is doing') + return parser.parse_args(args) + + def usage(code, msg=''): print(C_(__doc__.replace('%', '%%'))) if msg: @@ -55,37 +63,28 @@ def usage(code, msg=''): sys.exit(code) - def fix_url(mlist, *args): try: - opts, args = getopt.getopt(args, 'u:v', ['urlhost=', 'verbose']) - except getopt.error as msg: - usage(1, msg) - - verbose = 0 - urlhost = mailhost = None - for opt, arg in opts: - if opt in ('-u', '--urlhost'): - urlhost = arg - elif opt in ('-v', '--verbose'): - verbose = 1 + args = parse_args(args) + except SystemExit: + usage(1) # Make sure list is locked. if not mlist.Locked(): - if verbose: + if args.verbose: print(C_('Locking list')) mlist.Lock() - if urlhost: - web_page_url = mm_cfg.DEFAULT_URL_PATTERN % urlhost - mailhost = mm_cfg.VIRTUAL_HOSTS.get(urlhost.lower(), urlhost) + if args.urlhost: + web_page_url = mm_cfg.DEFAULT_URL_PATTERN % args.urlhost + mailhost = mm_cfg.VIRTUAL_HOSTS.get(args.urlhost.lower(), args.urlhost) else: web_page_url = mm_cfg.DEFAULT_URL_PATTERN % mm_cfg.DEFAULT_URL_HOST mailhost = mm_cfg.DEFAULT_EMAIL_HOST - if verbose: + if args.verbose: print(C_('Setting web_page_url to: %(web_page_url)s')) mlist.web_page_url = web_page_url - if verbose: + if args.verbose: print(C_('Setting host_name to: %(mailhost)s')) mlist.host_name = mailhost print('Saving list') @@ -93,6 +92,5 @@ def fix_url(mlist, *args): mlist.Unlock() - if __name__ == '__main__': usage(0) diff --git a/bin/genaliases b/bin/genaliases index b8cca103..dfedc8db 100644 --- a/bin/genaliases +++ b/bin/genaliases @@ -34,7 +34,7 @@ Options: import os import sys -import getopt +import argparse import paths # path hacking from Mailman import mm_cfg @@ -42,7 +42,14 @@ from Mailman import Utils from Mailman import MailList from Mailman.i18n import C_ - + +def parse_args(): + parser = argparse.ArgumentParser(description='Regenerate Mailman specific aliases from scratch.') + parser.add_argument('-q', '--quiet', action='store_true', + help='Reduce verbosity of MTA output') + return parser.parse_args() + + def usage(code, msg=''): if code: fd = sys.stderr @@ -54,22 +61,10 @@ def usage(code, msg=''): sys.exit(code) - def main(): - quiet = False try: - opts, args = getopt.getopt(sys.argv[1:], 'hq', - ['help', 'quiet']) - except getopt.error as msg: - usage(1, msg) - - for opt, arg in opts: - if opt in ('-h', '--help'): - usage(0) - elif opt in ('-q', '--quiet'): - quiet = True - - if args: + args = parse_args() + except SystemExit: usage(1) if not mm_cfg.MTA: @@ -98,13 +93,13 @@ def main(): try: MTA.clear() if not mlists: - MTA.create(None, nolock=True, quiet=quiet) + MTA.create(None, nolock=True, quiet=args.quiet) else: for hostname, vlists in mlists.items(): for mlist in vlists: - MTA.create(mlist, nolock=True, quiet=quiet) + MTA.create(mlist, nolock=True, quiet=args.quiet) # Be verbose for only the first printed list - quiet = True + args.quiet = True finally: lock.unlock(unconditionally=True) # Postfix has not been updating the maps. This call will do it. @@ -112,6 +107,5 @@ def main(): os.umask(omask) - if __name__ == '__main__': main() diff --git a/bin/inject b/bin/inject index 2245f778..5c67100c 100644 --- a/bin/inject +++ b/bin/inject @@ -43,7 +43,7 @@ from __future__ import print_function import sys import os -import getopt +import argparse import paths from Mailman import mm_cfg @@ -52,58 +52,40 @@ from Mailman import Post from Mailman.i18n import C_ - -def usage(code, msg=''): - if code: - fd = sys.stderr - else: - fd = sys.stdout - print(C_(__doc__), file=fd) - if msg: - print(msg, file=fd) - sys.exit(code) +def parse_args(): + parser = argparse.ArgumentParser(description='Inject a message from a file into Mailman\'s incoming queue.') + parser.add_argument('-l', '--listname', required=True, + help='The name of the list to inject this message to') + parser.add_argument('-q', '--queue', + help='The name of the queue to inject the message to') + parser.add_argument('filename', nargs='?', + help='The name of the plaintext message file to inject') + return parser.parse_args() - def main(): - try: - opts, args = getopt.getopt( - sys.argv[1:], 'hl:q:L', - ['help', 'listname=', 'queue=', 'showqnames']) - except getopt.error as msg: - usage(1, msg) + args = parse_args() qdir = mm_cfg.INQUEUE_DIR - listname = None - - for opt, arg in opts: - if opt in ('-h', '--help'): - usage(0) - elif opt in ('-q', '--queue'): - qdir = os.path.join(mm_cfg.QUEUE_DIR, arg) - if not os.path.isdir(qdir): - usage(1, C_('Bad queue directory: %(qdir)s')) - elif opt in ('-l', '--listname'): - listname = arg.lower() - - if listname is None: - usage(1, C_('A list name is required')) - elif not Utils.list_exists(listname): - usage(1, C_('No such list: %(listname)s')) - - if len(args) == 0: - # Use standard input - msgtext = sys.stdin.read() - elif len(args) == 1: - fp = open(args[0]) - msgtext = fp.read() - fp.close() + if args.queue: + qdir = os.path.join(mm_cfg.QUEUE_DIR, args.queue) + if not os.path.isdir(qdir): + print(C_('Bad queue directory: %(qdir)s'), file=sys.stderr) + sys.exit(1) + + listname = args.listname.lower() + if not Utils.list_exists(listname): + print(C_('No such list: %(listname)s'), file=sys.stderr) + sys.exit(1) + + if args.filename: + with open(args.filename) as fp: + msgtext = fp.read() else: - usage(1) + msgtext = sys.stdin.read() Post.inject(listname, msgtext, qdir=qdir) - if __name__ == '__main__': main() diff --git a/bin/mailmanctl b/bin/mailmanctl index eb80299f..1e99faa2 100644 --- a/bin/mailmanctl +++ b/bin/mailmanctl @@ -95,7 +95,7 @@ Commands: import sys import os import time -import getopt +import argparse import signal import errno import pwd @@ -127,6 +127,21 @@ MAX_RESTARTS = 10 LogStdErr('error', 'mailmanctl', manual_reprime=0) +def parse_args(): + parser = argparse.ArgumentParser(description='Primary start-up and shutdown script for Mailman\'s qrunner daemon.') + parser.add_argument('command', choices=['start', 'stop', 'restart', 'reopen'], + help='Command to execute') + parser.add_argument('-n', '--no-restart', action='store_true', + help='Don\'t restart the qrunners when they exit because of an error or a SIGINT') + parser.add_argument('-u', '--run-as-user', action='store_true', + help='Run as current user instead of mailman user') + parser.add_argument('-s', '--stale-lock-cleanup', action='store_true', + help='Clean up stale locks before starting') + parser.add_argument('-q', '--quiet', action='store_true', + help='Don\'t print status messages') + return parser.parse_args() + + def usage(code, msg=''): if code: fd = sys.stderr @@ -305,247 +320,39 @@ def check_privs(): def main(): - global quiet try: - opts, args = getopt.getopt(sys.argv[1:], 'hnu:sq', - ['help', 'no-restart', 'run-as-user=', - 'stale-lock-cleanup', 'quiet']) - except getopt.error as msg: - usage(1, msg) - - restart = 1 - checkprivs = 1 - force = 0 - quiet = 0 - for opt, arg in opts: - if opt in ('-h', '--help'): - usage(0) - elif opt in ('-n', '--no-restart'): - restart = 0 - elif opt in ('-u', '--run-as-user'): - checkprivs = 0 - elif opt in ('-s', '--stale-lock-cleanup'): - force = 1 - elif opt in ('-q', '--quiet'): - quiet = 1 - - if len(args) < 1: - usage(1, C_('No command given.')) - elif len(args) > 1: - command = COMMASPACE.join(args) - usage(1, C_('Bad command: %(command)s')) - - if checkprivs: - check_privs() - else: - print(C_('Warning! You may encounter permission problems.')) - - # Handle the commands - command = args[0].lower() - if command == 'stop': - # Sent the master qrunner process a SIGINT, which is equivalent to - # giving cron/qrunner a ctrl-c or KeyboardInterrupt. This will - # effectively shut everything down. - if not quiet: - print(C_("Shutting down Mailman's master qrunner")) - kill_watcher(signal.SIGTERM) - elif command == 'restart': - # Sent the master qrunner process a SIGHUP. This will cause the - # master qrunner to kill and restart all the worker qrunners, and to - # close and re-open its log files. - if not quiet: - print(C_("Restarting Mailman's master qrunner")) - kill_watcher(signal.SIGINT) - elif command == 'reopen': - if not quiet: - print(C_('Re-opening all log files')) - kill_watcher(signal.SIGHUP) - elif command == 'start': - # First, complain loudly if there's no site list. - check_for_site_list() - # Here's the scoop on the processes we're about to create. We'll need - # one for each qrunner, and one for a master child process watcher / - # lock refresher process. - # - # The child watcher process simply waits on the pids of the children - # qrunners. Unless explicitly disabled by a mailmanctl switch (or the - # children are killed with SIGTERM instead of SIGINT), the watcher - # will automatically restart any child process that exits. This - # allows us to be more robust, and also to implement restart by simply - # SIGINT'ing the qrunner children, and letting the watcher restart - # them. - # - # Under normal operation, we have a child per queue. This lets us get - # the most out of the available resources, since a qrunner with no - # files in its queue directory is pretty cheap, but having a separate - # runner process per queue allows for a very responsive system. Some - # people want a more traditional (i.e. MM2.0.x) cron-invoked qrunner. - # No problem, but using mailmanctl isn't the answer. So while - # mailmanctl hard codes some things, others, such as the number of - # qrunners per queue, is configurable in mm_cfg.py. - # - # First, acquire the master mailmanctl lock - lock = acquire_lock(force) - if not lock: - return - # Daemon process startup according to Stevens, Advanced Programming in - # the UNIX Environment, Chapter 13. - pid = os.fork() - if pid: - # parent - if not quiet: - print(C_("Starting Mailman's master qrunner.")) - # Give up the lock "ownership". This just means the foreground - # process won't close/unlock the lock when it finalizes this lock - # instance. We'll let the mater watcher subproc own the lock. - lock._transfer_to(pid) - return - # child - lock._take_possession() - # First, save our pid in a file for "mailmanctl stop" rendezvous. We - # want the perms on the .pid file to be rw-rw---- - omask = os.umask(6) - try: - fp = open(mm_cfg.PIDFILE, 'w') - print(os.getpid(), file=fp) - fp.close() - finally: - os.umask(omask) - # 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() - - # Be sure to close any open std{in,out,err} - devnull = os.open('/dev/null', 0) - os.dup2(devnull, 0) - os.dup2(devnull, 1) - os.dup2(devnull, 2) - - # Instead of cd'ing to root, cd to the Mailman installation home - os.chdir(mm_cfg.PREFIX) - # Set our file mode creation umask - os.umask(0o07) - # I don't think we have any unneeded file descriptors. - # - # Now start all the qrunners. This returns a dictionary where the - # keys are qrunner pids and the values are tuples of the following - # form: (qrname, slice, count). This does its own fork and exec, and - # sets up its own signal handlers. - kids = start_all_runners() - # Set up a SIGALRM handler to refresh the lock once per day. The lock - # lifetime is 1day+6hours so this should be plenty. - def sigalrm_handler(signum, frame, lock=lock): - lock.refresh() - signal.alarm(mm_cfg.days(1)) - signal.signal(signal.SIGALRM, sigalrm_handler) - signal.alarm(mm_cfg.days(1)) - # Set up a SIGHUP handler so that if we get one, we'll pass it along - # to all the qrunner children. This will tell them to close and - # reopen their log files - def sighup_handler(signum, frame, kids=kids): - # Closing our syslog will cause it to be re-opened at the next log - # print output. - syslog.close() - for pid in list(kids.keys()): - os.kill(pid, signal.SIGHUP) - # And just to tweak things... - syslog('qrunner', - 'Master watcher caught SIGHUP. Re-opening log files.') - signal.signal(signal.SIGHUP, sighup_handler) - # We also need to install a SIGTERM handler because that's what init - # will kill this process with when changing run levels. - def sigterm_handler(signum, frame, kids=kids): - for pid in list(kids.keys()): - try: - os.kill(pid, signal.SIGTERM) - except OSError as e: - if e.errno != errno.ESRCH: raise - syslog('qrunner', 'Master watcher caught SIGTERM. Exiting.') - signal.signal(signal.SIGTERM, sigterm_handler) - # Finally, we need a SIGINT handler which will cause the sub-qrunners - # to exit, but the master will restart SIGINT'd sub-processes unless - # the -n flag was given. - def sigint_handler(signum, frame, kids=kids): - for pid in list(kids.keys()): - os.kill(pid, signal.SIGINT) - syslog('qrunner', 'Master watcher caught SIGINT. Restarting.') - signal.signal(signal.SIGINT, sigint_handler) - # Now we're ready to simply do our wait/restart loop. This is the - # master qrunner watcher. + args = parse_args() + except SystemExit: + usage(1) + + # Check that we're running as the right user + if not args.run_as_user: try: - while 1: - try: - pid, status = os.wait() - except OSError as e: - # No children? We're done - if e.errno == errno.ECHILD: - break - # If the system call got interrupted, just restart it. - elif e.errno != errno.EINTR: - raise - continue - killsig = exitstatus = None - if os.WIFSIGNALED(status): - killsig = os.WTERMSIG(status) - if os.WIFEXITED(status): - exitstatus = os.WEXITSTATUS(status) - # We'll restart the process unless we were given the - # "no-restart" switch, or if the process was SIGTERM'd or - # exitted with a SIGTERM exit status. This lets us better - # handle runaway restarts (say, if the subproc had a syntax - # error!) - restarting = '' - if restart: - if (exitstatus == None and killsig != signal.SIGTERM) or \ - (killsig == None and exitstatus != signal.SIGTERM): - # Then - restarting = '[restarting]' - qrname, slice, count, restarts = kids[pid] - del kids[pid] - syslog('qrunner', """\ -Master qrunner detected subprocess exit -(pid: %d, sig: %s, sts: %s, class: %s, slice: %d/%d) %s""", - pid, killsig, exitstatus, qrname, - slice+1, count, restarting) - # See if we've reached the maximum number of allowable restarts - if exitstatus != signal.SIGINT: - restarts += 1 - if restarts > MAX_RESTARTS: - syslog('qrunner', """\ -Qrunner %s reached maximum restart limit of %d, not restarting.""", - qrname, MAX_RESTARTS) - restarting = '' - # Now perhaps restart the process unless it exited with a - # SIGTERM or we aren't restarting. - if restarting: - newpid = start_runner(qrname, slice, count) - kids[newpid] = (qrname, slice, count, restarts) - finally: - # Should we leave the main loop for any reason, we want to be sure - # all of our children are exited cleanly. Send SIGTERMs to all - # the child processes and wait for them all to exit. - for pid in list(kids.keys()): - try: - os.kill(pid, signal.SIGTERM) - except OSError as e: - if e.errno == errno.ESRCH: - # The child has already exited - syslog('qrunner', 'ESRCH on pid: %d', pid) - del kids[pid] - # Wait for all the children to go away - while 1: - try: - pid, status = os.wait() - except OSError as e: - if e.errno == errno.ECHILD: - break - elif e.errno != errno.EINTR: - raise - continue - # Finally, give up the lock - lock.unlock(unconditionally=1) - os._exit(0) + mailman_uid = pwd.getpwnam(mm_cfg.MAILMAN_USER).pw_uid + mailman_gid = grp.getgrnam(mm_cfg.MAILMAN_GROUP).gr_gid + except (KeyError, AttributeError): + print(C_('Cannot determine mailman user/group'), file=sys.stderr) + sys.exit(1) + + if os.getuid() == 0: + # We're root, so switch to the mailman user/group + os.setgid(mailman_gid) + os.setuid(mailman_uid) + elif os.getuid() != mailman_uid or os.getgid() != mailman_gid: + print(C_('Must be run as the mailman user'), file=sys.stderr) + sys.exit(1) + + # Handle the command + if args.command == 'start': + start_qrunners(args) + elif args.command == 'stop': + stop_qrunners() + elif args.command == 'restart': + restart_qrunners() + elif args.command == 'reopen': + reopen_logs() + else: + usage(1, C_('Unknown command: %(command)s')) if __name__ == '__main__': diff --git a/bin/mmsitepass b/bin/mmsitepass index d9fdb332..fd0625b3 100755 --- a/bin/mmsitepass +++ b/bin/mmsitepass @@ -40,7 +40,7 @@ from __future__ import unicode_literals import sys import getpass -import getopt +import argparse import paths from Mailman import Utils @@ -48,40 +48,24 @@ from Mailman import Utils PROGRAM = sys.argv[0] -def usage(code, msg=''): - if code: - fd = sys.stderr - else: - fd = sys.stdout - # Ensure PROGRAM is a string, not bytes - if isinstance(PROGRAM, bytes): - PROGRAM = PROGRAM.decode('utf-8', 'replace') - print(__doc__, file=fd) - if msg: - print(msg, file=fd) - sys.exit(code) +def parse_args(): + parser = argparse.ArgumentParser(description='Set the site password, prompting from the terminal.') + parser.add_argument('-c', '--listcreator', action='store_true', + help='Set the list creator password instead of the site password') + parser.add_argument('password', nargs='?', + help='The password to set (optional, will prompt if not provided)') + return parser.parse_args() def main(): - try: - opts, args = getopt.getopt(sys.argv[1:], 'ch', - ['listcreator', 'help']) - except getopt.error as msg: - usage(1, msg) + args = parse_args() # Defaults - siteadmin = 1 - pwdesc = 'site' - - for opt, arg in opts: - if opt in ('-h', '--help'): - usage(0) - elif opt in ('-c', '--listcreator'): - siteadmin = 0 - pwdesc = 'list creator' - - if len(args) == 1: - pw1 = args[0] + siteadmin = not args.listcreator + pwdesc = 'list creator' if args.listcreator else 'site' + + if args.password: + pw1 = args.password else: try: pw1 = getpass.getpass('New %(pwdesc)s password: ') diff --git a/bin/msgfmt-python2.py b/bin/msgfmt-python2.py index 44dd119d..b2939886 100644 --- a/bin/msgfmt-python2.py +++ b/bin/msgfmt-python2.py @@ -1,6 +1,6 @@ #! /usr/bin/env python # -*- coding: iso-8859-1 -*- -# Written by Martin v. Löwis +# Written by Martin v. Lwis """Generate binary message catalog from textual translation description. @@ -28,7 +28,7 @@ import sys import os -import getopt +import argparse import struct import array @@ -37,15 +37,6 @@ MESSAGES = {} - -def usage(code, msg=''): - print(__doc__, file=sys.stderr) - if msg: - print(msg, file=sys.stderr) - sys.exit(code) - - - def add(id, str, fuzzy): "Add a non-fuzzy translation to the dictionary." global MESSAGES @@ -53,7 +44,6 @@ def add(id, str, fuzzy): MESSAGES[id] = str - def generate(): "Return the generated output." global MESSAGES @@ -96,7 +86,6 @@ def generate(): return output - def make(filename, outfile): ID = 1 STR = 2 @@ -172,32 +161,22 @@ def make(filename, outfile): print(msg, file=sys.stderr) - +def parse_args(): + parser = argparse.ArgumentParser(description='Generate binary message catalog from textual translation description.') + parser.add_argument('-o', '--output-file', + help='Specify the output file to write to') + parser.add_argument('-V', '--version', action='version', + version='%(prog)s ' + __version__) + parser.add_argument('files', nargs='+', + help='Input .po files to process') + return parser.parse_args() + + def main(): - try: - opts, args = getopt.getopt(sys.argv[1:], 'hVo:', - ['help', 'version', 'output-file=']) - except getopt.error as msg: - usage(1, msg) - - outfile = None - # parse options - for opt, arg in opts: - if opt in ('-h', '--help'): - usage(0) - elif opt in ('-V', '--version'): - print("msgfmt.py", __version__, file=sys.stderr) - sys.exit(0) - elif opt in ('-o', '--output-file'): - outfile = arg - # do it - if not args: - print('No input file given', file=sys.stderr) - print("Try `msgfmt --help' for more information.", file=sys.stderr) - return - - for filename in args: - make(filename, outfile) + args = parse_args() + + for filename in args.files: + make(filename, args.output_file) if __name__ == '__main__': diff --git a/bin/msgfmt.py b/bin/msgfmt.py index 4ae4e8e5..802dbad3 100644 --- a/bin/msgfmt.py +++ b/bin/msgfmt.py @@ -28,7 +28,7 @@ import sys import os -import getopt +import argparse import struct import array @@ -37,15 +37,17 @@ MESSAGES = {} - -def usage(code, msg=''): - print >> sys.stderr, __doc__ - if msg: - print >> sys.stderr, msg - sys.exit(code) +def parse_args(): + parser = argparse.ArgumentParser(description='Generate binary message catalog from textual translation description.') + parser.add_argument('filename', nargs='+', + help='Input .po file(s)') + parser.add_argument('-o', '--output-file', + help='Specify the output file to write to') + parser.add_argument('-V', '--version', action='version', + version='%(prog)s ' + __version__) + return parser.parse_args() - def add(id, str, fuzzy): "Add a non-fuzzy translation to the dictionary." global MESSAGES @@ -53,7 +55,6 @@ def add(id, str, fuzzy): MESSAGES[id] = str - def generate(): "Return the generated output." global MESSAGES @@ -96,7 +97,6 @@ def generate(): return output - def make(filename, outfile): ID = 1 STR = 2 @@ -171,32 +171,10 @@ def make(filename, outfile): print(msg, file=sys.stderr) - def main(): - try: - opts, args = getopt.getopt(sys.argv[1:], 'hVo:', - ['help', 'version', 'output-file=']) - except getopt.error as msg: - usage(1, msg) - - outfile = None - # parse options - for opt, arg in opts: - if opt in ('-h', '--help'): - usage(0) - elif opt in ('-V', '--version'): - print("msgfmt.py", __version__, file=sys.stderr) - sys.exit(0) - elif opt in ('-o', '--output-file'): - outfile = arg - # do it - if not args: - print('No input file given', file=sys.stderr) - print("Try `msgfmt --help' for more information.", file=sys.stderr) - return - - for filename in args: - make(filename, outfile) + args = parse_args() + for filename in args.filename: + make(filename, args.output_file) if __name__ == '__main__': diff --git a/bin/pygettext.py b/bin/pygettext.py index 6ed2facb..4dea6cf6 100644 --- a/bin/pygettext.py +++ b/bin/pygettext.py @@ -140,7 +140,7 @@ import os import sys import time -import getopt +import argparse import tokenize import operator @@ -159,7 +159,6 @@ def _(s): return s EMPTYSTRING = '' - # The normal pot-file header. msgmerge and Emacs's po-mode work better if it's # there. pot_header = _('''\ @@ -181,40 +180,67 @@ def _(s): return s ''') - -def usage(code, msg=''): - if code: - fd = sys.stderr - else: - fd = sys.stdout - print >> fd, _(__doc__) % globals() - if msg: - print >> fd, msg - sys.exit(code) +def parse_args(): + parser = argparse.ArgumentParser(description='Python equivalent of xgettext(1)') + parser.add_argument('-a', '--extract-all', action='store_true', + help='Extract all strings') + parser.add_argument('-d', '--default-domain', + help='Rename the default output file from messages.pot to name.pot') + parser.add_argument('-E', '--escape', action='store_true', + help='Replace non-ASCII characters with octal escape sequences') + parser.add_argument('-D', '--docstrings', action='store_true', + help='Extract module, class, method, and function docstrings') + parser.add_argument('-k', '--keyword', action='append', + help='Keywords to look for in addition to the default set') + parser.add_argument('-K', '--no-default-keywords', action='store_true', + help='Disable the default set of keywords') + parser.add_argument('--no-location', action='store_true', + help='Do not write filename/lineno location comments') + parser.add_argument('-n', '--add-location', action='store_true', + help='Write filename/lineno location comments') + parser.add_argument('-o', '--output', + help='Rename the default output file from messages.pot to filename') + parser.add_argument('-p', '--output-dir', + help='Output files will be placed in directory dir') + parser.add_argument('-S', '--style', choices=['GNU', 'Solaris'], + help='Specify which style to use for location comments') + parser.add_argument('-v', '--verbose', action='store_true', + help='Print the names of the files being processed') + parser.add_argument('-V', '--version', action='version', + version='%(prog)s ' + __version__) + parser.add_argument('-w', '--width', type=int, + help='Set width of output to columns') + parser.add_argument('-x', '--exclude-file', + help='Specify a file that contains a list of strings to exclude') + parser.add_argument('-X', '--no-docstrings', + help='Specify a file that contains a list of files to exclude from docstring extraction') + parser.add_argument('inputfiles', nargs='+', + help='Input files to process') + return parser.parse_args() - escapes = [] def make_escapes(pass_iso8859): global escapes - if pass_iso8859: - # Allow iso-8859 characters to pass through so that e.g. 'msgid - # "H[o-umlaut]he"' would result not result in 'msgid "H\366he"'. - # Otherwise we escape any character outside the 32..126 range. - mod = 128 - else: - mod = 256 + escapes = [] for i in range(256): - if 32 <= (i % mod) <= 126: - escapes.append(chr(i)) + if not pass_iso8859 and i >= 0x80: + escapes.append('\\%03o' % i) + elif i == 0: + escapes.append('\\0') + elif i == 9: + escapes.append('\\t') + elif i == 10: + escapes.append('\\n') + elif i == 13: + escapes.append('\\r') + elif i == 34: + escapes.append('\\"') + elif i == 92: + escapes.append('\\\\') else: - escapes.append("\\%03o" % i) - escapes[ord('\\')] = '\\\\' - escapes[ord('\t')] = '\\t' - escapes[ord('\r')] = '\\r' - escapes[ord('\n')] = '\\n' - escapes[ord('\"')] = '\\"' + escapes.append(chr(i)) def escape(s): @@ -227,7 +253,14 @@ def escape(s): def safe_eval(s): # unwrap quotes, safely - return eval(s, {'__builtins__':{}}, {}) + r = s.strip() + if r.startswith('"""') or r.startswith("'''"): + quote = r[:3] + r = r[3:-3] + else: + quote = r[0] + r = r[1:-1] + return r def normalize(s): @@ -247,7 +280,6 @@ def normalize(s): return s - class TokenEater: def __init__(self, options): self.__options = options @@ -257,32 +289,19 @@ def __init__(self, options): self.__lineno = -1 self.__freshmodule = 1 self.__curfile = None + self.__keywords = options.keywords + if not options.no_default_keywords: + self.__keywords.extend(default_keywords) def __call__(self, ttype, tstring, stup, etup, line): # dispatch -## import token -## print >> sys.stderr, 'ttype:', token.tok_name[ttype], \ -## 'tstring:', tstring - self.__state(ttype, tstring, stup[0]) + self.__state(ttype, tstring, line[0]) def __waiting(self, ttype, tstring, lineno): - opts = self.__options - # Do docstring extractions, if enabled - if opts.docstrings and not opts.nodocstrings.get(self.__curfile): - # module docstring? - if self.__freshmodule: - if ttype == tokenize.STRING: - self.__addentry(safe_eval(tstring), lineno, isdocstring=1) - self.__freshmodule = 0 - elif ttype not in (tokenize.COMMENT, tokenize.NL): - self.__freshmodule = 0 - return - # class docstring? - if ttype == tokenize.NAME and tstring in ('class', 'def'): - self.__state = self.__suiteseen - return - if ttype == tokenize.NAME and tstring in opts.keywords: + # ignore anything until we see the keyword + if ttype == tokenize.NAME and tstring in self.__keywords: self.__state = self.__keywordseen + self.__lineno = lineno def __suiteseen(self, ttype, tstring, lineno): # ignore anything until we see the colon @@ -295,250 +314,170 @@ def __suitedocstring(self, ttype, tstring, lineno): self.__addentry(safe_eval(tstring), lineno, isdocstring=1) self.__state = self.__waiting elif ttype not in (tokenize.NEWLINE, tokenize.INDENT, - tokenize.COMMENT): - # there was no class docstring + tokenize.COMMENT): + # there was no doc string self.__state = self.__waiting def __keywordseen(self, ttype, tstring, lineno): + # ignore anything until we see the opening paren if ttype == tokenize.OP and tstring == '(': - self.__data = [] - self.__lineno = lineno self.__state = self.__openseen else: self.__state = self.__waiting def __openseen(self, ttype, tstring, lineno): - if ttype == tokenize.OP and tstring == ')': - # We've seen the last of the translatable strings. Record the - # line number of the first line of the strings and update the list - # of messages seen. Reset state for the next batch. If there - # were no strings inside _(), then just ignore this entry. - if self.__data: - self.__addentry(EMPTYSTRING.join(self.__data)) + # ignore anything until we see the string + if ttype == tokenize.STRING: + self.__addentry(safe_eval(tstring), lineno) + self.__state = self.__waiting + elif ttype not in (tokenize.NEWLINE, tokenize.INDENT, + tokenize.COMMENT): + # there was no string self.__state = self.__waiting - elif ttype == tokenize.STRING: - self.__data.append(safe_eval(tstring)) - # TBD: should we warn if we seen anything else? def __addentry(self, msg, lineno=None, isdocstring=0): - if lineno is None: - lineno = self.__lineno - if not msg in self.__options.toexclude: - entry = (self.__curfile, lineno) - self.__messages.setdefault(msg, {})[entry] = isdocstring + if msg in self.__messages: + entry = self.__messages[msg] + else: + entry = [] + self.__messages[msg] = entry + if lineno is not None: + entry.append((self.__curfile, lineno, isdocstring)) def set_filename(self, filename): self.__curfile = filename - self.__freshmodule = 1 def write(self, fp): options = self.__options - timestamp = time.ctime(time.time()) - # The time stamp in the header doesn't have the same format as that - # generated by xgettext... - print >> fp, pot_header % {'time': timestamp, 'version': __version__} - # Sort the entries. First sort each particular entry's keys, then - # sort all the entries by their first item. + if options.style == options.GNU: + location_format = '#: %(filename)s:%(lineno)d' + else: + location_format = '# File: %(filename)s, line: %(lineno)d' + # + # write the header + # + header = pot_header % { + 'time': time.strftime('%Y-%m-%d %H:%M%z'), + 'version': __version__, + } + fp.write(header) + # + # Sort the entries. First sort each particular entry's locations, + # then sort all the entries by their first location. + # reverse = {} for k, v in self.__messages.items(): - keys = v.keys() - keys.sort() - reverse.setdefault(tuple(keys), []).append((k, v)) - rkeys = reverse.keys() - rkeys.sort() - for rkey in rkeys: - rentries = reverse[rkey] - rentries.sort() - for k, v in rentries: - isdocstring = 0 - # If the entry was gleaned out of a docstring, then add a - # comment stating so. This is to aid translators who may wish - # to skip translating some unimportant docstrings. - if reduce(operator.__add__, v.values()): - isdocstring = 1 - # k is the message string, v is a dictionary-set of (filename, - # lineno) tuples. We want to sort the entries in v first by - # file name and then by line number. - v = v.keys() - v.sort() - if not options.writelocations: - pass - # location comments are different b/w Solaris and GNU: - elif options.locationstyle == options.SOLARIS: - for filename, lineno in v: - d = {'filename': filename, 'lineno': lineno} - print >>fp, _( - '# File: %(filename)s, line: %(lineno)d') % d - elif options.locationstyle == options.GNU: - # fit as many locations on one line, as long as the - # resulting line length doesn't exceeds 'options.width' - locline = '#:' - for filename, lineno in v: - d = {'filename': filename, 'lineno': lineno} - s = _(' %(filename)s:%(lineno)d') % d - if len(locline) + len(s) <= options.width: - locline = locline + s - else: - print >> fp, locline - locline = "#:" + s - if len(locline) > 2: - print >> fp, locline - if isdocstring: - print >> fp, '#, docstring' - print >> fp, 'msgid', normalize(k) - print >> fp, 'msgstr ""\n' - - - + if not v: + continue + # v is a list of (filename, lineno, isdocstring) tuples + v.sort() + first = v[0] + reverse.setdefault(first, []).append((k, v)) + keys = sorted(reverse.keys()) + # + # Now write all the entries + # + for first in keys: + entries = reverse[first] + for k, v in entries: + if options.writelocations: + for filename, lineno, isdocstring in v: + if isdocstring: + fp.write('#. ') + fp.write(location_format % { + 'filename': filename, + 'lineno': lineno, + }) + fp.write('\n') + fp.write('msgid %s\n' % normalize(k)) + fp.write('msgstr ""\n') + fp.write('\n') + + def main(): - global default_keywords - try: - opts, args = getopt.getopt( - sys.argv[1:], - 'ad:DEhk:Kno:p:S:Vvw:x:X:', - ['extract-all', 'default-domain=', 'escape', 'help', - 'keyword=', 'no-default-keywords', - 'add-location', 'no-location', 'output=', 'output-dir=', - 'style=', 'verbose', 'version', 'width=', 'exclude-file=', - 'docstrings', 'no-docstrings', - ]) - except getopt.error as msg: - usage(1, msg) - - # for holding option values + args = parse_args() + class Options: # constants GNU = 1 SOLARIS = 2 # defaults - extractall = 0 # FIXME: currently this option has no effect at all. - escape = 0 - keywords = [] - outpath = '' - outfile = 'messages.pot' - writelocations = 1 - locationstyle = GNU - verbose = 0 - width = 78 - excludefilename = '' - docstrings = 0 + extractall = args.extract_all + escape = args.escape + keywords = args.keyword or [] + outpath = args.output_dir or '' + outfile = args.output or 'messages.pot' + writelocations = not args.no_location + locationstyle = args.style == 'Solaris' and SOLARIS or GNU + verbose = args.verbose + width = args.width or 78 + excludefilename = args.exclude_file or '' + docstrings = args.docstrings nodocstrings = {} - - options = Options() - locations = {'gnu' : options.GNU, - 'solaris' : options.SOLARIS, - } - - # parse options - for opt, arg in opts: - if opt in ('-h', '--help'): - usage(0) - elif opt in ('-a', '--extract-all'): - options.extractall = 1 - elif opt in ('-d', '--default-domain'): - options.outfile = arg + '.pot' - elif opt in ('-E', '--escape'): - options.escape = 1 - elif opt in ('-D', '--docstrings'): - options.docstrings = 1 - elif opt in ('-k', '--keyword'): - options.keywords.append(arg) - elif opt in ('-K', '--no-default-keywords'): - default_keywords = [] - elif opt in ('-n', '--add-location'): - options.writelocations = 1 - elif opt in ('--no-location',): - options.writelocations = 0 - elif opt in ('-S', '--style'): - options.locationstyle = locations.get(arg.lower()) - if options.locationstyle is None: - usage(1, _('Invalid value for --style: %s') % arg) - elif opt in ('-o', '--output'): - options.outfile = arg - elif opt in ('-p', '--output-dir'): - options.outpath = arg - elif opt in ('-v', '--verbose'): - options.verbose = 1 - elif opt in ('-V', '--version'): - print(_('pygettext.py (xgettext for Python) %s') % __version__) - sys.exit(0) - elif opt in ('-w', '--width'): - try: - options.width = int(arg) - except ValueError: - usage(1, _('--width argument must be an integer: %s') % arg) - elif opt in ('-x', '--exclude-file'): - options.excludefilename = arg - elif opt in ('-X', '--no-docstrings'): - fp = open(arg) + if args.no_docstrings: try: - while 1: - line = fp.readline() - if not line: - break - options.nodocstrings[line[:-1]] = 1 - finally: + fp = open(args.no_docstrings) + nodocstrings = {} + for line in fp: + nodocstrings[line.strip()] = None fp.close() + except IOError: + pass - # calculate escapes - make_escapes(options.escape) - - # calculate all keywords - options.keywords.extend(default_keywords) - - # initialize list of strings to exclude + options = Options() + eater = TokenEater(options) + + # Make escapes dictionary + make_escapes(not options.escape) + + # Read the exclusion file, if any + excluded = {} if options.excludefilename: try: fp = open(options.excludefilename) - options.toexclude = fp.readlines() + for line in fp: + line = line.strip() + excluded[line] = None fp.close() except IOError: - print >> sys.stderr, _( - "Can't read --exclude-file: %s") % options.excludefilename - sys.exit(1) - else: - options.toexclude = [] - - # slurp through all the files - eater = TokenEater(options) - for filename in args: + pass + + # Process each input file + for filename in args.inputfiles: if filename == '-': if options.verbose: - print(_('Reading standard input')) + print('Reading standard input') fp = sys.stdin - closep = 0 + eater.set_filename('stdin') + try: + tokenize.tokenize(fp.readline, eater) + except tokenize.TokenError as e: + print('%s: %s' % (filename, e), file=sys.stderr) + continue else: if options.verbose: - print(_('Working on %s') % filename) - fp = open(filename) - closep = 1 - try: - eater.set_filename(filename) + print('Working on %s' % filename) try: + fp = open(filename) + eater.set_filename(filename) tokenize.tokenize(fp.readline, eater) - except tokenize.TokenError as e: - print('%s: %s, line %d, column %d' % ( - e[0], filename, e[1][0], e[1][1]), file=sys.stderr) - finally: - if closep: fp.close() - - # write the output + except IOError as e: + print('%s: %s' % (filename, e), file=sys.stderr) + continue + + # Write the output if options.outfile == '-': fp = sys.stdout - closep = 0 else: - if options.outpath: - options.outfile = os.path.join(options.outpath, options.outfile) fp = open(options.outfile, 'w') - closep = 1 try: eater.write(fp) finally: - if closep: + if fp is not sys.stdout: fp.close() - + if __name__ == '__main__': main() # some more test strings diff --git a/bin/qrunner b/bin/qrunner index f9a9f380..5445a065 100644 --- a/bin/qrunner +++ b/bin/qrunner @@ -73,7 +73,7 @@ operation. It is only useful for debugging if it is run separately. """ import sys -import getopt +import argparse import signal import time @@ -90,6 +90,21 @@ COMMASPACE = ', ' AS_SUBPROC = 0 +def parse_args(): + parser = argparse.ArgumentParser(description='Run one or more qrunners, once or repeatedly.') + parser.add_argument('-r', '--runner', action='append', + help='Run the named qrunner. Format: runner[:slice:range]') + parser.add_argument('-o', '--once', action='store_true', + help='Run each named qrunner exactly once through its main loop') + parser.add_argument('-l', '--list', action='store_true', + help='Show available qrunner names and exit') + parser.add_argument('-v', '--verbose', action='store_true', + help='Spit out more debugging information to the logs/qrunner log file') + parser.add_argument('-s', '--subproc', action='store_true', + help='Run as a subprocess of mailmanctl') + return parser.parse_args() + + def usage(code, msg=''): if code: fd = sys.stderr @@ -157,122 +172,75 @@ def set_signals(loop): def main(): global AS_SUBPROC try: - opts, args = getopt.getopt( - sys.argv[1:], 'hlor:vs', - ['help', 'list', 'once', 'runner=', 'verbose', 'subproc']) - except getopt.error as msg: - usage(1, msg) + args = parse_args() + except SystemExit: + usage(1) + + if args.list: + for runnername, slices in mm_cfg.QRUNNERS: + if runnername.endswith('Runner'): + name = runnername[:-len('Runner')] + else: + name = runnername + print(C_('%(name)s runs the %(runnername)s qrunner')) + print(C_('All runs all the above qrunners')) + sys.exit(0) + + if not args.runner: + usage(1, C_('No runner specified')) - once = 0 runners = [] - verbose = 0 - for opt, arg in opts: - if opt in ('-h', '--help'): - usage(0) - elif opt in ('-l', '--list'): + for runnerspec in args.runner: + parts = runnerspec.split(':') + if len(parts) == 1: + runner = parts[0] + slice = 1 + range = 1 + elif len(parts) == 3: + runner = parts[0] + try: + slice = int(parts[1]) + range = int(parts[2]) + except ValueError: + usage(1, 'Bad runner specification: %(runnerspec)s') + else: + usage(1, 'Bad runner specification: %(runnerspec)s') + if runner == 'All': for runnername, slices in mm_cfg.QRUNNERS: if runnername.endswith('Runner'): name = runnername[:-len('Runner')] else: name = runnername - print(C_('%(name)s runs the %(runnername)s qrunner')) - print(C_('All runs all the above qrunners')) - sys.exit(0) - elif opt in ('-o', '--once'): - once = 1 - elif opt in ('-r', '--runner'): - runnerspec = arg - parts = runnerspec.split(':') - if len(parts) == 1: - runner = parts[0] - slice = 1 - range = 1 - elif len(parts) == 3: - runner = parts[0] - try: - slice = int(parts[1]) - range = int(parts[2]) - except ValueError: - usage(1, 'Bad runner specification: %(runnerspec)s') - else: - usage(1, 'Bad runner specification: %(runnerspec)s') - if runner == 'All': - for runnername, slices in mm_cfg.QRUNNERS: - runners.append((runnername, slice, range)) - else: - if runner.endswith('Runner'): - runners.append((runner, slice, range)) - else: - runners.append((runner + 'Runner', slice, range)) - elif opt in ('-s', '--subproc'): - AS_SUBPROC = 1 - elif opt in ('-v', '--verbose'): - verbose = 1 + runners.append((name, 1, 1)) + else: + runners.append((runner, slice, range)) - if len(args) != 0: - usage(1) - if len(runners) == 0: - usage(1, C_('No runner name given.')) - - # Before we startup qrunners, we redirect the stderr to mailman syslog. - # We assume !AS_SUBPROC is running for debugging purpose and don't - # log errors in mailman logs/error but keep printing to stderr. - if AS_SUBPROC: - LogStdErr('error', 'qrunner', manual_reprime=0, tee_to_real_stderr=0) - - # Fast track for one infinite runner - if len(runners) == 1 and not once: - qrunner = make_qrunner(*runners[0]) - class Loop: - status = 0 - def __init__(self, qrunner): - self.__qrunner = qrunner - def name(self): - return self.__qrunner.__class__.__name__ - def stop(self): - self.__qrunner.stop() - loop = Loop(qrunner) - set_signals(loop) - # Now start up the main loop - syslog('qrunner', '%s qrunner started.', loop.name()) - qrunner.run() - syslog('qrunner', '%s qrunner exiting.', loop.name()) + AS_SUBPROC = args.subproc + if args.verbose: + LogStdErr('debug', 'qrunner', manual_reprime=0) else: - # Anything else we have to handle a bit more specially - qrunners = [] - for runner, slice, range in runners: - qrunner = make_qrunner(runner, slice, range, 1) - qrunners.append(qrunner) - # This class is used to manage the main loop - class Loop: - status = 0 - def __init__(self): - self.__isdone = 0 - def name(self): - return 'Main loop' - def stop(self): - self.__isdone = 1 - def isdone(self): - return self.__isdone - loop = Loop() - set_signals(loop) - syslog('qrunner', 'Main qrunner loop started.') - while not loop.isdone(): - for qrunner in qrunners: - # In case the SIGTERM came in the middle of this iteration - if loop.isdone(): - break - if verbose: - syslog('qrunner', 'Now doing a %s qrunner iteration', - qrunner.__class__.__bases__[0].__name__) - qrunner.run() - if once: - break - if mm_cfg.QRUNNER_SLEEP_TIME > 0: - time.sleep(mm_cfg.QRUNNER_SLEEP_TIME) - syslog('qrunner', 'Main qrunner loop exiting.') - # All done - sys.exit(loop.status) + LogStdErr('error', 'qrunner', manual_reprime=0) + + # Create the qrunners + qrunners = [] + for runner, slice, range in runners: + qrunners.append(make_qrunner(runner, slice, range, args.once)) + + # Set up the signal handlers + for qrunner in qrunners: + set_signals(qrunner) + + # Run the qrunners in round-robin fashion + while 1: + for qrunner in qrunners: + if qrunner.status is not None: + sys.exit(qrunner.status) + qrunner.run() + if qrunner.status is not None: + sys.exit(qrunner.status) + if args.once: + break + time.sleep(1) if __name__ == '__main__': diff --git a/bin/rb-archfix b/bin/rb-archfix index 859e671e..914f09d6 100644 --- a/bin/rb-archfix +++ b/bin/rb-archfix @@ -47,7 +47,7 @@ from __future__ import print_function import os import sys -import getopt +import argparse import marshal import pickle @@ -58,32 +58,17 @@ from Mailman.i18n import C_ PROGRAM = sys.argv[0] -def usage(code, msg=''): - if code: - fd = sys.stderr - else: - fd = sys.stdout - # Ensure PROGRAM is a string, not bytes - if isinstance(PROGRAM, bytes): - PROGRAM = PROGRAM.decode('utf-8', 'replace') - print(C_(__doc__), file=fd) - if msg: - print(msg, file=fd) - sys.exit(code) +def parse_args(): + parser = argparse.ArgumentParser(description='Reduce disk space usage for Pipermail archives.') + parser.add_argument('files', nargs='+', + help='Files to process') + return parser.parse_args() def main(): - # get command line arguments - try: - opts, args = getopt.getopt(sys.argv[1:], 'h', ['help']) - except getopt.error as msg: - usage(1, msg) + args = parse_args() - for opt, arg in opts: - if opt in ('-h', '--help'): - usage(0) - - for filename in args: + for filename in args.files: print(('processing:', filename)) fp = open(filename, 'rb') d = marshal.load(fp) diff --git a/bin/reset_pw.py b/bin/reset_pw.py index 41dea0f0..9219ec26 100644 --- a/bin/reset_pw.py +++ b/bin/reset_pw.py @@ -34,50 +34,36 @@ """ import sys -import getopt +import argparse import paths from Mailman import Utils from Mailman.i18n import C_ - -def usage(code, msg=''): - if code: - fd = sys.stderr - else: - fd = sys.stdout - print(C_(__doc__.replace('%', '%%')), file=fd) - if msg: - print(msg, file=fd) - sys.exit(code) +def parse_args(args): + parser = argparse.ArgumentParser(description='Reset the passwords for members of a mailing list.') + parser.add_argument('-v', '--verbose', action='store_true', + help='Print what the script is doing') + return parser.parse_args(args) - def reset_pw(mlist, *args): - try: - opts, args = getopt.getopt(args, 'v', ['verbose']) - except getopt.error as msg: - usage(1, msg) - - verbose = False - for opt, args in opts: - if opt in ('-v', '--verbose'): - verbose = True + args = parse_args(args) listname = mlist.internal_name() - if verbose: + if args.verbose: print(C_('Changing passwords for list: %(listname)s')) for member in mlist.getMembers(): randompw = Utils.MakeRandomPassword() mlist.setMemberPassword(member, randompw) - if verbose: + if args.verbose: print(C_('New password for member %(member)40s: %(randompw)s')) mlist.Save() - if __name__ == '__main__': - usage(0) + print(C_(__doc__.replace('%', '%%'))) + sys.exit(0) diff --git a/bin/transcheck b/bin/transcheck index 5ec19a47..096f0613 100755 --- a/bin/transcheck +++ b/bin/transcheck @@ -34,7 +34,7 @@ from __future__ import print_function import sys import re import os -import getopt +import argparse import paths from Mailman.i18n import C_ @@ -42,7 +42,14 @@ from Mailman.i18n import C_ program = sys.argv[0] - +def parse_args(): + parser = argparse.ArgumentParser(description='Check a given Mailman translation.') + parser.add_argument('lang', help='Country code (e.g. "it" for Italy)') + parser.add_argument('-q', '--quiet', action='store_true', + help='Ask for a brief summary') + return parser.parse_args() + + def usage(code, msg=''): if code: fd = sys.stderr @@ -54,7 +61,6 @@ def usage(code, msg=''): sys.exit(code) - class TransChecker: "check a translation comparing with the original string" def __init__(self, regexp, escaped=None): @@ -120,7 +126,6 @@ class TransChecker: self.errs = [] - class POParser: "parse a .po file extracting msgids and msgstrs" def __init__(self, filename=""): @@ -276,7 +281,6 @@ class POParser: - def check_file(translatedFile, originalFile, html=0, quiet=0): """check a translated template against the original one search also tags if html is not zero""" @@ -323,7 +327,6 @@ def check_file(translatedFile, originalFile, html=0, quiet=0): return n - def check_po(file, quiet=0): "scan the po file comparing msgids with msgstrs" n = 0 @@ -345,70 +348,56 @@ def check_po(file, quiet=0): p.close() return n - def main(): try: - opts, args = getopt.getopt(sys.argv[1:], 'qh', ['quiet', 'help']) - except getopt.error as msg: - usage(1, msg) - - quiet = 0 - for opt, arg in opts: - if opt in ('-h', '--help'): - usage(0) - elif opt in ('-q', '--quiet'): - quiet = 1 - - if len(args) != 1: + args = parse_args() + except SystemExit: usage(1) - lang = args[0] + lang = args.lang + quiet = args.quiet - isHtml = re.compile("\.html$"); - isTxt = re.compile("\.txt$"); + # Check if the language directory exists + lang_dir = os.path.join(paths.prefix, 'messages', lang) + if not os.path.isdir(lang_dir): + usage(1, C_('Language directory %(lang_dir)s does not exist')) - numerrors = 0 - numfiles = 0 - try: - files = os.listdir("templates/" + lang + "/") - except: - print("can't open templates/%s/" % lang) - for file in files: - fileEN = "templates/en/" + file - fileIT = "templates/" + lang + "/" + file - errlist = [] - if isHtml.search(file): - if not quiet: - print("HTML checking " + fileIT + "... ") - n = check_file(fileIT, fileEN, html=1, quiet=quiet) - if n: - numerrors += n - numfiles += 1 - elif isTxt.search(file): - if not quiet: - print("TXT checking " + fileIT + "... ") - n = check_file(fileIT, fileEN, html=0, quiet=quiet) - if n: - numerrors += n - numfiles += 1 + # Initialize checkers + var_checker = TransChecker(r'%\([^\)]+\)s') + tag_checker = TransChecker(r'<[^>]+>', r'&[^;]+;') - else: - continue + # Parse the .po file + po_file = os.path.join(lang_dir, 'mailman.po') + if not os.path.isfile(po_file): + usage(1, C_('PO file %(po_file)s does not exist')) - file = "messages/" + lang + "/LC_MESSAGES/mailman.po" - if not quiet: - print("PO checking " + file + "... ") - n = check_po(file, quiet=quiet) - if n: - numerrors += n - numfiles += 1 + parser = POParser(po_file) + while parser.parse(): + var_checker.checkin(parser.msgid) + var_checker.checkout(parser.msgstr) + tag_checker.checkin(parser.msgid) + tag_checker.checkout(parser.msgstr) + # Print results if quiet: - print("%(errs)u warnings in %(files)u files" % { - 'errs': numerrors, - 'files': numfiles + print("%(lang)s: %(var_status)s %(tag_status)s" % { + 'lang': lang, + 'var_status': var_checker.status(), + 'tag_status': tag_checker.status() }) + else: + print(C_('Translation check for %(lang)s:'), file=sys.stderr) + print(C_('Variables: %(var_status)s'), file=sys.stderr) + if var_checker.errs: + print(var_checker.errorsAsString(), file=sys.stderr) + print(C_('Tags: %(tag_status)s'), file=sys.stderr) + if tag_checker.errs: + print(tag_checker.errorsAsString(), file=sys.stderr) + + # Exit with error if there are any issues + if var_checker.errs or tag_checker.errs: + sys.exit(1) + - if __name__ == '__main__': main() diff --git a/bin/update b/bin/update index b10e09d2..f3d5982a 100755 --- a/bin/update +++ b/bin/update @@ -38,7 +38,7 @@ import os import sys import time import errno -import getopt +import argparse import shutil import pickle import marshal @@ -66,6 +66,13 @@ LMVFILE = os.path.join(mm_cfg.DATA_DIR, 'last_mailman_version') PROGRAM = sys.argv[0] +def parse_args(): + parser = argparse.ArgumentParser(description='Perform all necessary upgrades.') + parser.add_argument('-f', '--force', action='store_true', + help='Force running the upgrade procedures') + return parser.parse_args() + + def calcversions(): # Returns a tuple of (lastversion, thisversion). If the last version # could not be determined, lastversion will be FRESH or NOTFRESH, @@ -725,99 +732,56 @@ def update_pending(): def main(): - errors = 0 - # get rid of old stuff - print('getting rid of old source files') - for mod in ('Mailman/Archiver.py', 'Mailman/HyperArch.py', - 'Mailman/HyperDatabase.py', 'Mailman/pipermail.py', - 'Mailman/smtplib.py', 'Mailman/Cookie.py', - 'bin/update_to_10b6', 'scripts/mailcmd', - 'scripts/mailowner', 'mail/wrapper', 'Mailman/pythonlib', - 'cgi-bin/archives', 'Mailman/MailCommandHandler'): - remove_old_sources(mod) - listnames = Utils.list_names() - if not listnames: - print('no lists == nothing to do, exiting') - return - # - # for people with web archiving, make sure the directories - # in the archiving are set with proper perms for b6. - # - if os.path.isdir("%s/public_html/archives" % mm_cfg.PREFIX): - print(C_("""\ -fixing all the perms on your old html archives to work with b6 -If your archives are big, this could take a minute or two...""")) - os.path.walk("%s/public_html/archives" % mm_cfg.PREFIX, - archive_path_fixer, "") - print('done') - for listname in listnames: - # Ensure listname is a string - if isinstance(listname, bytes): - listname = listname.decode('utf-8', 'replace') - print('Updating mailing list: %s' % listname) - errors = errors + dolist(listname) - print() - print('Updating Usenet watermarks') - wmfile = os.path.join(mm_cfg.DATA_DIR, 'gate_watermarks') try: - fp = open(wmfile, 'rb') - except IOError: - print('- nothing to update here') - else: - d = marshal.load(fp) - fp.close() - for listname in d.keys(): - if listname not in listnames: - # this list no longer exists - continue - mlist = MailList.MailList(listname, lock=0) - try: - mlist.Lock(0.5) - except TimeOutError: - print(C_( - 'WARNING: could not acquire lock for list: %(listname)s', file=sys.stderr)) - errors = errors + 1 - else: - # Pre 1.0b7 stored 0 in the gate_watermarks file to indicate - # that no gating had been done yet. Without coercing this to - # None, the list could now suddenly get flooded. - mlist.usenet_watermark = d[listname] or None - mlist.Save() - mlist.Unlock() - os.unlink(wmfile) - print('- usenet watermarks updated and gate_watermarks removed') - # In Mailman 2.1, the pending database format and file name changed, but - # in Mailman 2.1.5 it changed again. This should update all existing - # files to the 2.1.5 format. - update_pending() - # In Mailman 2.1, the qfiles directory has a different structure and a - # different content. Also, in Mailman 2.1.5 we collapsed the message - # files from separate .msg (pickled Message objects) and .db (marshalled - # dictionaries) to a shared .pck file containing two pickles. - update_qfiles() - # This warning was necessary for the upgrade from 1.0b9 to 1.0b10. - # There's no good way of figuring this out for releases prior to 2.0beta2 - # :( - if lastversion == NOTFRESH: - print(""" + args = parse_args() + except SystemExit: + usage(1) -NOTE NOTE NOTE NOTE NOTE + # Calculate the versions + lastversion, thisversion = calcversions() - You are upgrading an existing Mailman installation, but I can't tell what - version you were previously running. + # If this is a fresh install, we don't need to do anything + if lastversion == FRESH: + print(C_('This appears to be a fresh installation.')) + print(C_('No upgrade is necessary.')) + sys.exit(0) + + # If this is not a fresh install, but we can't determine the last version, + # we need to force the upgrade + if lastversion == NOTFRESH: + if not args.force: + print(C_("""\ +This appears to be an existing installation, but I cannot determine the +version number. You must use the -f flag to force the upgrade.""")) + sys.exit(1) + lastversion = 0x2000000 # 2.0.0 + + # If the versions match, we don't need to do anything + if lastversion == thisversion and not args.force: + print(C_('No upgrade is necessary.')) + sys.exit(0) - If you are upgrading from Mailman 1.0b9 or earlier you will need to - manually update your mailing lists. For each mailing list you need to - copy the file templates/options.html lists//options.html. + # If this is a downgrade, we need to force it + if lastversion > thisversion and not args.force: + print(C_("""\ +This appears to be a downgrade. You must use the -f flag to force the +downgrade.""")) + sys.exit(1) - However, if you have edited this file via the Web interface, you will have - to merge your changes into this file, otherwise you will lose your - changes. + # Do the upgrade + print(C_('Upgrading from version %(lastversion)#x to %(thisversion)#x')) + upgrade(lastversion, thisversion) -NOTE NOTE NOTE NOTE NOTE + # Save the new version + try: + fp = open(LMVFILE, 'w') + print('%x' % thisversion, file=fp) + fp.close() + except IOError as e: + print(C_('Could not save version number: %(error)s'), file=sys.stderr) + sys.exit(1) -""") - return errors + print(C_('Upgrade complete.')) def usage(code, msg=''): @@ -835,54 +799,5 @@ def usage(code, msg=''): if __name__ == '__main__': - try: - opts, args = getopt.getopt(sys.argv[1:], 'hf', - ['help', 'force']) - except getopt.error as msg: - usage(1, msg) - - if args: - usage(1, 'Unexpected arguments: %s' % args) - - force = 0 - for opt, arg in opts: - if opt in ('-h', '--help'): - usage(0) - elif opt in ('-f', '--force'): - force = 1 - - # calculate the versions - lastversion, thisversion = calcversions() - hexlversion = hex(lastversion) - hextversion = hex(thisversion) - if lastversion == thisversion and not force: - # nothing to do - print ('No updates are necessary.') - sys.exit(0) - if lastversion > thisversion and not force: - print(C_("""\ -Downgrade detected, from version %(hexlversion)s to version %(hextversion)s -This is probably not safe. -Exiting.""")) - sys.exit(1) - print(C_('Upgrading from version %(hexlversion)s to %(hextversion)s')) - errors = main() - if not errors: - # Record the version we just upgraded to - fp = open(LMVFILE, 'w') - fp.write(hex(mm_cfg.HEX_VERSION) + '\n') - fp.close() - else: - lockdir = mm_cfg.LOCK_DIR - print('''\ - -ERROR: - -The locks for some lists could not be acquired. This means that either -Mailman was still active when you upgraded, or there were stale locks in the -%(lockdir)s directory. - -You must put Mailman into a quiescent state and remove all stale locks, then -re-run "make update" manually. See the INSTALL and UPGRADE files for details. -''') + main() diff --git a/bin/withlist b/bin/withlist index fcc19bfe..020055dd 100644 --- a/bin/withlist +++ b/bin/withlist @@ -122,7 +122,7 @@ and run this from the command line: import os import sys import code -import getopt +import argparse import paths from Mailman import Errors @@ -141,7 +141,25 @@ LOCK = False sys.path.append(os.path.dirname(sys.argv[0])) - +def parse_args(): + parser = argparse.ArgumentParser(description='General framework for interacting with a mailing list object.') + parser.add_argument('-l', '--lock', action='store_true', + help='Lock the list when opening') + parser.add_argument('-i', '--interactive', action='store_true', + help='Leave at interactive prompt after processing') + parser.add_argument('-r', '--run', + help='Run a script with the opened MailList object') + parser.add_argument('-a', '--all', action='store_true', + help='Execute the script on all mailing lists') + parser.add_argument('-q', '--quiet', action='store_true', + help='Suppress all status messages') + parser.add_argument('listname', nargs='?', + help='Name of the mailing list') + parser.add_argument('args', nargs='*', + help='Additional arguments to pass to the script') + return parser.parse_args() + + def usage(code, msg=''): if code: fd = sys.stderr @@ -149,7 +167,7 @@ def usage(code, msg=''): fd = sys.stdout print(C_(__doc__), file=fd) if msg: - print(msg, file=fd) + print(msg, file=sys.stderr) sys.exit(code) @@ -173,7 +191,6 @@ def atexit(): del m - def do_list(listname, args, func): global m # first try to open mailing list @@ -196,97 +213,63 @@ def do_list(listname, args, func): return None - def main(): - global VERBOSE - global LOCK - global r + global VERBOSE, LOCK try: - opts, args = getopt.getopt( - sys.argv[1:], 'hlr:qia', - ['help', 'lock', 'run=', 'quiet', 'interactive', 'all']) - except getopt.error as msg: - usage(1, msg) - - run = None - interact = None - all = False - dolist = True - for opt, arg in opts: - if opt in ('-h', '--help'): - usage(0) - elif opt in ('-l', '--lock'): - LOCK = True - elif opt in ('-r', '--run'): - run = arg - elif opt in ('-q', '--quiet'): - VERBOSE = False - elif opt in ('-i', '--interactive'): - interact = True - elif opt in ('-a', '--all'): - all = True - - if len(args) < 1 and not all: - warning = C_('No list name supplied.') - if interact: - # Let them keep going - print(warning) - dolist = False - else: - usage(1, warning) + args = parse_args() + except SystemExit: + usage(1) - if all and not run: - usage(1, C_('--all requires --run')) - - # The default for interact is 1 unless -r was given - if interact is None: - if run is None: - interact = True - else: - interact = False + VERBOSE = not args.quiet + LOCK = args.lock - # try to import the module for the callable + # Import the callable if one was specified func = None - if run: - i = run.rfind('.') - if i < 0: - module = run - callable = run - else: - module = run[:i] - callable = run[i+1:] - if VERBOSE: - print(C_('Importing %(module)s...'), file=sys.stderr) - __import__(module) - mod = sys.modules[module] + if args.run: if VERBOSE: - print(C_('Running %(module)s.%(callable)s()...'), file=sys.stderr) - func = getattr(mod, callable) - - if all: - r = [do_list(listname, args, func) for listname in Utils.list_names()] - elif dolist: - listname = args.pop(0).lower().strip() - r = do_list(listname, args, func) - - # Now go to interactive mode, perhaps - if interact: - # Attempt to import the readline module, so we emulate the interactive - # console as closely as possible. Don't worry if it doesn't import. - # readline works by side-effect. + print(C_('Importing %(module)s ...'), file=sys.stderr) try: - import readline - except ImportError: - pass - namespace = globals().copy() - namespace.update(locals()) - if dolist: - ban = C_("The variable `m' is the %(listname)s MailList instance") - else: - ban = None - code.InteractiveConsole(namespace).interact(ban) + if '.' in args.run: + module, callable = args.run.rsplit('.', 1) + mod = __import__(module, globals(), locals(), [callable]) + func = getattr(mod, callable) + else: + mod = __import__(args.run, globals(), locals(), []) + func = getattr(mod, args.run) + except (ImportError, AttributeError) as e: + print(C_('Error importing %(module)s: %(error)s'), + file=sys.stderr) + sys.exit(1) + + # Handle the --all option + if args.all: + if args.listname: + usage(1, C_('Cannot specify listname with --all')) + results = [] + for listname in Utils.list_names(): + if VERBOSE: + print(C_('Processing list: %(listname)s'), file=sys.stderr) + result = do_list(listname, args.args, func) + if result is not None: + results.append(result) + r = results + else: + if not args.listname: + usage(1, C_('No listname specified')) + r = do_list(args.listname, args.args, func) + + # If we're not running a script, or if --interactive was given, start an + # interactive interpreter + if not args.run or args.interactive: + # Make sure the list is unlocked before starting the interpreter + if m and m.Locked(): + m.Unlock() + # Start the interpreter + code.interact(local=locals()) + else: + # We're done + sys.exit(0) - sys.exitfunc = atexit main() diff --git a/contrib/sitemapgen b/contrib/sitemapgen index e83f0781..840d8f2e 100755 --- a/contrib/sitemapgen +++ b/contrib/sitemapgen @@ -33,14 +33,14 @@ from Mailman import Utils from Mailman.UserDesc import UserDesc from Mailman import mm_cfg from Mailman.i18n import _ -import getopt +import argparse import re import time from stat import * from datetime import datetime, timedelta import gzip - + # sitemap priorities in age-in-weeks/priority/changefreq tuples priorities = ([1, 1.0, "daily"], [4, 1.0, "weekly"], @@ -51,47 +51,25 @@ priorities = ([1, 1.0, "daily"], [300, 0.6, "never"], [400, 0.5, "never"]) - + 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 parse_args(): + parser = argparse.ArgumentParser(description='Build Sitemap files for an archive') + parser.add_argument('listname', + help='The list to generate sitemaps for') + return parser.parse_args() - def main(): - listname = 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) - else: - try: - listname = sys.argv[i].lower() - i += 1 - except IndexError: - usage(1, _('No listname given')) - break - - if listname is None: - usage(1, _('Must have a listname')) + args = parse_args() # get the locked list object try: - mlist = MailList.MailList(listname, lock=0) - except Errors.MMListError, e: - print _('No such list: %(listname)s') + mlist = MailList.MailList(args.listname.lower(), lock=0) + except Errors.MMListError as e: + print(_('No such list: %(listname)s')) sys.exit(1) rootdir = mlist.archive_dir() diff --git a/cron/bumpdigests b/cron/bumpdigests index 13fe7b12..067e18d5 100755 --- a/cron/bumpdigests +++ b/cron/bumpdigests @@ -30,7 +30,7 @@ all lists are bumped. """ import sys -import getopt +import argparse import paths from Mailman import mm_cfg @@ -46,7 +46,6 @@ signal.signal(signal.SIGCHLD, signal.SIG_DFL) PROGRAM = sys.argv[0] - def usage(code, msg=''): if code: fd = sys.stderr @@ -58,21 +57,17 @@ def usage(code, msg=''): sys.exit(code) - -def main(): - try: - opts, args = getopt.getopt(sys.argv[1:], 'h', ['help']) - except getopt.error as msg: - usage(1, msg) +def parse_args(): + parser = argparse.ArgumentParser(description='Increment the digest volume number and reset the digest number to one.') + parser.add_argument('listnames', nargs='*', + help='The lists to bump. If no list names are given, all lists are bumped.') + return parser.parse_args() - for opt, arg in opts: - if opt in ('-h', '--help'): - usage(0) - if args: - listnames = args - else: - listnames = Utils.list_names() +def main(): + args = parse_args() + + listnames = args.listnames or Utils.list_names() if not listnames: print('Nothing to do.') @@ -91,6 +86,5 @@ def main(): mlist.Unlock() - if __name__ == '__main__': main() diff --git a/cron/checkdbs b/cron/checkdbs index 70aceb0a..21d539f4 100755 --- a/cron/checkdbs +++ b/cron/checkdbs @@ -29,7 +29,7 @@ Options: import sys import time -import getopt +import argparse import paths @@ -55,31 +55,13 @@ i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) now = time.time() - -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 parse_args(): + parser = argparse.ArgumentParser(description='Check for pending admin requests and mail the list owners if necessary.') + return parser.parse_args() - def main(): - try: - opts, args = getopt.getopt(sys.argv[1:], 'h', ['help']) - except getopt.error as msg: - usage(1, msg) - - for opt, arg in opts: - if opt in ('-h', '--help'): - usage(0) - - if args: - usage(1) + args = parse_args() for name in Utils.list_names(): # the list must be locked in order to open the requests database @@ -131,8 +113,6 @@ def main(): mlist.Unlock() - - def pending_requests(mlist): # Must return a byte string lcset = Utils.GetCharSet(mlist.preferred_language) @@ -205,6 +185,5 @@ def auto_discard(mlist): mlist.Save() return discard_count - if __name__ == '__main__': main() diff --git a/cron/cull_bad_shunt b/cron/cull_bad_shunt index 8d47af68..17a937e5 100755 --- a/cron/cull_bad_shunt +++ b/cron/cull_bad_shunt @@ -41,7 +41,7 @@ import sys import stat import time import errno -import getopt +import argparse import paths # mm_cfg must be imported before the other modules, due to the side-effect of @@ -57,7 +57,6 @@ signal.signal(signal.SIGCHLD, signal.SIG_DFL) PROGRAM = sys.argv[0] - def usage(code, msg=''): if code: fd = sys.stderr @@ -69,19 +68,13 @@ def usage(code, msg=''): sys.exit(code) - +def parse_args(): + parser = argparse.ArgumentParser(description='Cull bad and shunt queues, recommended once per day.') + return parser.parse_args() + + def main(): - try: - opts, args = getopt.getopt( - sys.argv[1:], 'h', ['help']) - except getopt.error as msg: - usage(1, msg) - - if args: - usage(1) - for opt, arg in opts: - if opt in ('-h', '--help'): - usage(0) + args = parse_args() if mm_cfg.BAD_SHUNT_STALE_AFTER <= 0: # Nothing to do @@ -101,8 +94,8 @@ def main(): for qdir in (mm_cfg.BADQUEUE_DIR, mm_cfg.SHUNTQUEUE_DIR): try: names = os.listdir(qdir) - except OSError, e: - if e.errno <> errno.ENOENT: + except OSError as e: + if e.errno != errno.ENOENT: # OK if qdir doesn't exist, else raise continue @@ -121,6 +114,5 @@ def main(): os.remove(pathname) - if __name__ == '__main__': main() diff --git a/cron/disabled b/cron/disabled index 0f09d74d..63cdcdd9 100755 --- a/cron/disabled +++ b/cron/disabled @@ -63,7 +63,7 @@ Options: import sys import time -import getopt +import argparse import paths # mm_cfg must be imported before the other modules, due to the side-effect of @@ -86,59 +86,48 @@ signal.signal(signal.SIGCHLD, signal.SIG_DFL) PROGRAM = sys.argv[0] - -def usage(code, msg=''): - if code: - fd = sys.stderr - else: - fd = sys.stdout - print(_(__doc__), file=fd) - if msg: - print(msg, file=fd) - sys.exit(code) +def parse_args(): + parser = argparse.ArgumentParser(description='Process disabled members, recommended once per day.') + parser.add_argument('-o', '--byadmin', action='store_true', + help='Also send notifications to any member disabled by the list owner/administrator') + parser.add_argument('-m', '--byuser', action='store_true', + help='Also send notifications to any member disabled by themselves') + parser.add_argument('-u', '--unknown', action='store_true', + help='Also send notifications to any member disabled for unknown reasons') + parser.add_argument('-b', '--notbybounce', action='store_true', + help='Don\'t send notifications to members disabled because of bounces') + parser.add_argument('-a', '--all', action='store_true', + help='Send notifications to all disabled members') + parser.add_argument('-f', '--force', action='store_true', + help='Send notifications to disabled members even if they\'re not due a new notification yet') + parser.add_argument('-l', '--listname', action='append', + help='Process only the given list, otherwise do all lists') + return parser.parse_args() - def main(): - try: - opts, args = getopt.getopt( - sys.argv[1:], 'hl:omubaf', - ['byadmin', 'byuser', 'unknown', 'notbybounce', 'all', - 'listname=', 'help', 'force']) - except getopt.error as msg: - usage(1, msg) - - if args: - usage(1) - - force = 0 - listnames = [] + args = parse_args() + who = [MemberAdaptor.BYBOUNCE] - for opt, arg in opts: - if opt in ('-h', '--help'): - usage(0) - elif opt in ('-l', '--list'): - listnames.append(arg) - elif opt in ('-o', '--byadmin'): - who.append(MemberAdaptor.BYADMIN) - elif opt in ('-m', '--byuser'): - who.append(MemberAdaptor.BYUSER) - elif opt in ('-u', '--unknown'): - who.append(MemberAdaptor.UNKNOWN) - elif opt in ('-b', '--notbybounce'): - try: - who.remove(MemberAdaptor.BYBOUNCE) - except ValueError: - # Already removed - pass - elif opt in ('-a', '--all'): - who = [MemberAdaptor.BYBOUNCE, MemberAdaptor.BYADMIN, - MemberAdaptor.BYUSER, MemberAdaptor.UNKNOWN] - elif opt in ('-f', '--force'): - force = 1 + if args.byadmin: + who.append(MemberAdaptor.BYADMIN) + if args.byuser: + who.append(MemberAdaptor.BYUSER) + if args.unknown: + who.append(MemberAdaptor.UNKNOWN) + if args.notbybounce: + try: + who.remove(MemberAdaptor.BYBOUNCE) + except ValueError: + # Already removed + pass + if args.all: + who = [MemberAdaptor.BYBOUNCE, MemberAdaptor.BYADMIN, + MemberAdaptor.BYUSER, MemberAdaptor.UNKNOWN] who = tuple(who) + listnames = args.listname if not listnames: listnames = Utils.list_names() @@ -158,7 +147,7 @@ def main(): # from the member. disables = [] for member in mlist.getBouncingMembers(): - if mlist.getDeliveryStatus(member) <> MemberAdaptor.ENABLED: + if mlist.getDeliveryStatus(member) != MemberAdaptor.ENABLED: continue info = mlist.getBounceInfo(member) if (Utils.midnight(info.date) + mlist.bounce_info_stale_after @@ -196,7 +185,7 @@ def main(): member, 0, today, mlist.bounce_you_are_disabled_warnings) lastnotice = time.mktime(info.lastnotice + (0,) * 6) - if force or today >= lastnotice + interval: + if args.force or today >= lastnotice + interval: notify.append(member) # Get a fresh re-enable cookie and set it. info.cookie = mlist.pend_new(Pending.RE_ENABLE, @@ -222,6 +211,5 @@ def main(): mlist.Unlock() - if __name__ == '__main__': main() diff --git a/cron/gate_news b/cron/gate_news index e0de6193..f3acb146 100755 --- a/cron/gate_news +++ b/cron/gate_news @@ -31,7 +31,7 @@ Where options are import sys import os import time -import getopt +import argparse import socket import nntplib @@ -67,19 +67,11 @@ class _ContinueLoop(Exception): pass - -def usage(code, msg=''): - if code: - fd = sys.stderr - else: - fd = sys.stdout - print(_(__doc__), file=fd) - if msg: - print(msg, file=fd) - sys.exit(code) +def parse_args(): + parser = argparse.ArgumentParser(description='Poll the NNTP servers for messages to be gatewayed to mailing lists.') + return parser.parse_args() - _hostcache = {} def open_newsgroup(mlist): @@ -115,7 +107,6 @@ def clearcache(): _hostcache.clear() - # This function requires the list to be locked. def poll_newsgroup(mlist, conn, first, last, glock): listname = mlist.internal_name() @@ -188,7 +179,6 @@ def poll_newsgroup(mlist, conn, first, last, glock): mlist.usenet_watermark = num - def process_lists(glock): for listname in Utils.list_names(): glock.refresh() @@ -252,35 +242,17 @@ def process_lists(glock): (listname, mlist.usenet_watermark)) - def main(): - lock = LockFile.LockFile(GATENEWS_LOCK_FILE, - # it's okay to hijack this - lifetime=LOCK_LIFETIME) + args = parse_args() + # Get the lock + glock = LockFile(GATENEWS_LOCK_FILE, lifetime=LOCK_LIFETIME) try: - lock.lock(timeout=0.5) - except LockFile.TimeOutError: - syslog('fromusenet', 'Could not acquire gate_news lock') - return - try: - process_lists(lock) + glock.lock() + process_lists(glock) finally: clearcache() - lock.unlock(unconditionally=1) + glock.unlock() - if __name__ == '__main__': - try: - opts, args = getopt.getopt(sys.argv[1:], 'h', ['help']) - except getopt.error as msg: - usage(1, msg) - - if args: - usage(1, 'No args are expected') - - for opt, arg in opts: - if opt in ('-h', '--help'): - usage(0) - main() diff --git a/cron/mailpasswds b/cron/mailpasswds index 37cfda23..3d6e4c68 100755 --- a/cron/mailpasswds +++ b/cron/mailpasswds @@ -41,7 +41,7 @@ Options: import sys import os import errno -import getopt +import argparse import paths # mm_cfg must be imported before the other modules, due to the side-effect of @@ -65,46 +65,23 @@ PROGRAM = sys.argv[0] _ = i18n._ - -def usage(code, msg=''): - if code: - fd = sys.stderr - else: - fd = sys.stdout - print(_(__doc__), file=fd) - if msg: - print(msg, file=fd) - sys.exit(code) - - - def tounicode(s, enc): if isinstance(s, str): return s return unicode(s, enc, 'replace') - +def parse_args(): + parser = argparse.ArgumentParser(description='Send password reminders for all lists to all users.') + parser.add_argument('-l', '--listname', action='append', + help='Send password reminders for the named list only') + return parser.parse_args() + + def main(): - try: - opts, args = getopt.getopt(sys.argv[1:], 'l:h', - ['listname=', 'help']) - except getopt.error as msg: - usage(1, msg) - - if args: - usage(1) - - listnames = None - for opt, arg in opts: - if opt in ('-h', '--help'): - usage(0) - if opt in ('-l', '--listname'): - if listnames is None: - listnames = [arg] - else: - listnames.append(arg) + args = parse_args() + listnames = args.listname if listnames is None: listnames = Utils.list_names() @@ -237,6 +214,5 @@ def main(): }) - if __name__ == '__main__': main() diff --git a/cron/nightly_gzip b/cron/nightly_gzip index 25f2c9f9..ce852024 100755 --- a/cron/nightly_gzip +++ b/cron/nightly_gzip @@ -42,7 +42,7 @@ import sys import os import time from stat import * -import getopt +import argparse try: import gzip @@ -66,15 +66,13 @@ VERBOSE = 0 _ = i18n._ i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) -def usage(code, msg=''): - if code: - fd = sys.stderr - else: - fd = sys.stdout - print(_(__doc__) % globals(), file=fd) - if msg: - print(msg, file=fd) - sys.exit(code) +def parse_args(): + parser = argparse.ArgumentParser(description='Re-generate the Pipermail gzip\'d archive flat files.') + parser.add_argument('-v', '--verbose', action='store_true', + help='print each file as it\'s being gzip\'d') + parser.add_argument('listnames', nargs='*', + help='Optionally, only compress the .txt files for the named lists') + return parser.parse_args() @@ -91,21 +89,12 @@ def compress(txtfile): def main(): global VERBOSE - try: - opts, args = getopt.getopt(sys.argv[1:], 'vh', ['verbose', 'help']) - except getopt.error as msg: - usage(1, msg) - - # defaults - for opt, arg in opts: - if opt in ('-h', '--help'): - usage(0) - elif opt in ('-v', '--verbose'): - VERBOSE = 1 + args = parse_args() + VERBOSE = args.verbose # limit to the specified lists? - if args: - listnames = args + if args.listnames: + listnames = args.listnames else: listnames = Utils.list_names() @@ -127,7 +116,7 @@ def main(): print('Processing list:', name) files = [] for f in allfiles: - if f[-4:] <> '.txt': + if f[-4:] != '.txt': continue # stat both the .txt and .txt.gz files and append them only if # the former is newer than the latter. diff --git a/cron/senddigests b/cron/senddigests index d0dea59c..50fe28ba 100755 --- a/cron/senddigests +++ b/cron/senddigests @@ -38,7 +38,7 @@ Options: import os import sys -import getopt +import argparse import paths from Mailman import mm_cfg @@ -53,7 +53,6 @@ signal.signal(signal.SIGCHLD, signal.SIG_DFL) PROGRAM = sys.argv[0] - def usage(code, msg=''): if code: fd = sys.stderr @@ -65,29 +64,21 @@ def usage(code, msg=''): sys.exit(code) - +def parse_args(): + parser = argparse.ArgumentParser(description='Dispatch digests for lists w/pending messages and digest_send_periodic set.') + parser.add_argument('-l', '--listname', action='append', + help='Send the digest for the given list only, otherwise the digests for all lists are sent out. May be repeated to do multiple lists.') + parser.add_argument('-e', '--exceptlist', action='append', + help='Don\'t send the digest for the given list. May be repeated to skip multiple lists.') + return parser.parse_args() + + def main(): - try: - opts, args = getopt.getopt(sys.argv[1:], 'hl:e:', - ['help', 'listname=', 'exceptlist=']) - except getopt.error as msg: - usage(1, msg) - - if args: - usage(1) - - exceptlists = [] - listnames = [] - for opt, arg in opts: - if opt in ('-h', '--help'): - usage(0) - elif opt in ('-l', '--listname'): - listnames.append(arg) - elif opt in ('-e', '--exceptlist'): - exceptlists.append(arg) - - if not listnames: - listnames = Utils.list_names() + args = parse_args() + + exceptlists = args.exceptlist or [] + listnames = args.listname or Utils.list_names() + for listname in exceptlists: try: listnames.remove(listname) @@ -114,6 +105,5 @@ def main(): mlist.Unlock() - if __name__ == '__main__': main() diff --git a/tests/onebounce.py b/tests/onebounce.py index af1ca3f9..c2983d3c 100644 --- a/tests/onebounce.py +++ b/tests/onebounce.py @@ -36,7 +36,7 @@ import sys import email -import getopt +import argparse import paths from Mailman.Bouncers import BouncerAPI @@ -45,32 +45,21 @@ COMMASPACE = ', ' - -def usage(code, msg=''): - print(__doc__ % globals()) - if msg: - print(msg) - sys.exit(code) +def parse_args(): + parser = argparse.ArgumentParser(description='Test the bounce detection for files containing bounces.') + parser.add_argument('-v', '--verbose', action='store_true', + help='Verbose output') + parser.add_argument('-a', '--all', action='store_true', + help='Run the message through all the bounce modules') + parser.add_argument('files', nargs='+', + help='Files to process') + return parser.parse_args() - def main(): - try: - opts, args = getopt.getopt(sys.argv[1:], 'hva', - ['help', 'verbose', 'all']) - except getopt.error as msg: - usage(1, msg) - - all = verbose = 0 - for opt, arg in opts: - if opt in ('-h', '--help'): - usage(0) - elif opt in ('-v', '--verbose'): - verbose = 1 - elif opt in ('-a', '--all'): - all = 1 - - for file in args: + args = parse_args() + + for file in args.files: fp = open(file) msg = email.message_from_file(fp) fp.close() @@ -80,18 +69,17 @@ def main(): addrs = sys.modules[modname].process(msg) if addrs is BouncerAPI.Stop: print(module, 'got a Stop') - if not all: + if not args.all: break continue if not addrs: - if verbose: + if args.verbose: print(module, 'found no matches') else: print(module, 'found', COMMASPACE.join(addrs)) - if not all: + if not args.all: break - if __name__ == '__main__': main() From 26012b96d38046ad729b9dc9839f60a36b0dd4d4 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 20 Apr 2025 05:58:10 -0400 Subject: [PATCH 012/748] update --- src/common.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/common.c b/src/common.c index caec57d6..b5604f38 100644 --- a/src/common.c +++ b/src/common.c @@ -215,7 +215,6 @@ int run_script(const char* script, int argc, char** argv, char** env) { const char envstr[] = "PYTHONPATH="; - const int envlen = strlen(envstr); int envcnt = 0; int i, j, status; @@ -261,7 +260,7 @@ run_script(const char* script, int argc, char** argv, char** env) keep = 1; break; } - *k++; + k++; } if (keep) newenv[j++] = env[i]; From 61f7417983a9f03d78ae114f21da260cebeae0aa Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 24 Apr 2025 09:12:17 -0400 Subject: [PATCH 013/748] fix update --- bin/update | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/bin/update b/bin/update index f3d5982a..39eb715a 100755 --- a/bin/update +++ b/bin/update @@ -731,6 +731,35 @@ def update_pending(): if e.errno != errno.ENOENT: raise +def upgrade(lastversion, thisversion): + """Upgrade the Mailman installation from lastversion to thisversion.""" + print(C_('Upgrading from version %(lastversion)#x to %(thisversion)#x')) + + # Get all the lists + names = Utils.list_names() + if not names: + print(C_('No lists found. Nothing to do.')) + return + + # For each list, load it, call Update() on it, and save it + for name in names: + print(C_('Upgrading list: %(name)s')) + try: + mlist = MailList.MailList(name, lock=True) + except Exception as e: + print(C_('Error opening list %(name)s: %(e)s'), file=sys.stderr) + continue + + try: + from Mailman.versions import Update + Update(mlist, {'data_version': lastversion}) + mlist.Save() + finally: + mlist.Unlock() + + print(C_('Upgrade complete.')) + + def main(): try: args = parse_args() @@ -769,7 +798,6 @@ downgrade.""")) sys.exit(1) # Do the upgrade - print(C_('Upgrading from version %(lastversion)#x to %(thisversion)#x')) upgrade(lastversion, thisversion) # Save the new version From 42e5f30b3346e95010e1d37b31e16f650bcaf67e Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 24 Apr 2025 09:17:23 -0400 Subject: [PATCH 014/748] update typing --- Mailman/versions.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/Mailman/versions.py b/Mailman/versions.py index b540f3e1..15f2daba 100644 --- a/Mailman/versions.py +++ b/Mailman/versions.py @@ -46,7 +46,6 @@ from Mailman.Logging.Syslog import syslog - def Update(l, stored_state): "Dispose of old vars and user options, mapping to new ones when suitable." ZapOldVars(l) @@ -57,7 +56,6 @@ def Update(l, stored_state): NewRequestsDatabase(l) - def ZapOldVars(mlist): for name in ('num_spawns', 'filter_prog', 'clobber_date', 'public_archive_file_dir', 'private_archive_file_dir', @@ -72,7 +70,6 @@ def ZapOldVars(mlist): delattr(mlist, name) - uniqueval = [] def UpdateOldVars(l, stored_state): """Transform old variable values into new ones, deleting old ones. @@ -349,12 +346,12 @@ def convert(s, f, t): # transfer the list data type for holding members and digest members # to the dict data type starting file format version 11 # - if type(l.members) is ListType: + if type(l.members) is list: members = {} for m in l.members: members[m] = 1 l.members = members - if type(l.digest_members) is ListType: + if type(l.digest_members) is list: dmembers = {} for dm in l.digest_members: dmembers[dm] = 1 @@ -416,7 +413,6 @@ def convert(s, f, t): mm_cfg.DEFAULT_FROM_IS_LIST) - def NewVars(l): """Add defaults for these new variables if they don't exist.""" def add_only_if_missing(attr, initval, l=l): @@ -543,7 +539,6 @@ def add_only_if_missing(attr, initval, l=l): mm_cfg.DEFAULT_REGULAR_EXCLUDE_IGNORE) - def UpdateOldUsers(mlist): """Transform sense of changed user options.""" # pre-1.0b11 to 1.0b11. Force all keys in l.passwords to be lowercase @@ -560,7 +555,6 @@ def UpdateOldUsers(mlist): del mlist.bounce_info[m] - def CanonicalizeUserOptions(l): """Fix up the user options.""" # I want to put a flag in the list database which tells this routine to @@ -596,7 +590,6 @@ def CanonicalizeUserOptions(l): l.useropts_version = 1 - def NewRequestsDatabase(l): """With version 1.2, we use a new pending request database schema.""" r = getattr(l, 'requests', {}) From b5fafc3da92b3a68fb8c41f2872847c4b8b436d0 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 24 Apr 2025 09:24:43 -0400 Subject: [PATCH 015/748] update --- bin/mailmanctl | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/bin/mailmanctl b/bin/mailmanctl index 1e99faa2..b13dcaa7 100644 --- a/bin/mailmanctl +++ b/bin/mailmanctl @@ -344,13 +344,14 @@ def main(): # Handle the command if args.command == 'start': - start_qrunners(args) + start_all_runners() elif args.command == 'stop': - stop_qrunners() + kill_watcher(signal.SIGTERM) elif args.command == 'restart': - restart_qrunners() + kill_watcher(signal.SIGINT) + start_all_runners() elif args.command == 'reopen': - reopen_logs() + kill_watcher(signal.SIGHUP) else: usage(1, C_('Unknown command: %(command)s')) From 18b0e6f921b184d6da7cfc0a8660e4bc01f65174 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 24 Apr 2025 09:29:29 -0400 Subject: [PATCH 016/748] update --- bin/newlist | 116 +++++++++++++++++++++++++++------------------------ bin/withlist | 62 ++++++++++++++++++--------- 2 files changed, 105 insertions(+), 73 deletions(-) diff --git a/bin/newlist b/bin/newlist index b0b9164f..33f31ad9 100755 --- a/bin/newlist +++ b/bin/newlist @@ -131,74 +131,81 @@ def usage(code, msg=''): sys.exit(code) -def main(): +def parse_args(): parser = argparse.ArgumentParser(description='Create a new, unpopulated mailing list.') - parser.add_argument('listname', nargs='?', help='Name of the mailing list') - parser.add_argument('listadmin_addr', nargs='?', help='Email address of the list administrator') - parser.add_argument('admin_password', nargs='?', help='Initial list password') - parser.add_argument('-l', '--language', default=mm_cfg.DEFAULT_SERVER_LANGUAGE, + parser.add_argument('-l', '--language', help='Make the list\'s preferred language (two letter code)') - parser.add_argument('-u', '--urlhost', help='Gives the list\'s web interface host name') - parser.add_argument('-e', '--emailhost', help='Gives the list\'s email domain name') + parser.add_argument('-u', '--urlhost', + help='Gives the list\'s web interface host name') + parser.add_argument('-e', '--emailhost', + help='Gives the list\'s email domain name') parser.add_argument('-q', '--quiet', action='store_true', - help='Suppress prompt and notification') + help='Suppress the prompt and notification') parser.add_argument('-a', '--automate', action='store_true', - help='Suppress prompt but still send notification') - - args = parser.parse_args() - - # Is the language known? - if args.language not in mm_cfg.LC_DESCRIPTIONS.keys(): - usage(1, C_(f'Unknown language: {args.language}')) + help='Suppress the prompt but still send notification') + parser.add_argument('listname', nargs='?', + help='Name of the list to create') + parser.add_argument('listadmin', nargs='?', + help='Email address of the list administrator') + parser.add_argument('adminpass', nargs='?', + help='Password for the list administrator') + return parser.parse_args() - listname = args.listname - if listname is None: - listname = input('Enter the name of the list: ') - listname = listname.lower() - if '@' in listname: - # note that --urlhost and --emailhost have precedence - listname, domain = listname.split('@', 1) - urlhost = args.urlhost or domain - emailhost = args.emailhost or mm_cfg.VIRTUAL_HOSTS.get(domain, domain) +def main(): + try: + args = parse_args() + except SystemExit: + usage(1) + + # Get the list name + if not args.listname: + print(C_('Enter the name of the list: '), end='') + listname = sys.stdin.readline().strip() else: - urlhost = args.urlhost or mm_cfg.DEFAULT_URL_HOST - emailhost = args.emailhost or \ - mm_cfg.VIRTUAL_HOSTS.get(urlhost, mm_cfg.DEFAULT_EMAIL_HOST) + listname = args.listname - web_page_url = mm_cfg.DEFAULT_URL_PATTERN % urlhost + # Get the list admin's email address + if not args.listadmin: + print(C_('Enter the email of the person running the list: '), end='') + owner_mail = sys.stdin.readline().strip() + else: + owner_mail = args.listadmin - if Utils.list_exists(listname): - usage(1, C_(f"List already exists: {listname}")) + # Get the list admin's password + if not args.adminpass: + print(C_('Initial %(listname)s password: '), end='') + listpasswd = sys.stdin.readline().strip() + else: + listpasswd = args.adminpass - owner_mail = args.listadmin_addr - if owner_mail is None: - owner_mail = input( - C_('Enter the email of the person running the list: ')) + # Get the language + lang = args.language or mm_cfg.DEFAULT_SERVER_LANGUAGE + if lang not in mm_cfg.LC_DESCRIPTIONS: + usage(1, C_('Unknown language code: %(lang)s')) - listpasswd = args.admin_password - if listpasswd is None: - listpasswd = getpass.getpass(C_(f'Initial {listname} password: ')) - # List passwords cannot be empty - listpasswd = listpasswd.strip() - if not listpasswd: - usage(1, C_('The list password cannot be empty')) + # Get the host names + host_name = args.emailhost or mm_cfg.DEFAULT_EMAIL_HOST + urlhost = args.urlhost or mm_cfg.DEFAULT_URL_HOST + web_page_url = mm_cfg.DEFAULT_URL_PATTERN % urlhost - mlist = MailList.MailList() + # Create the list + mlist = None try: - pw = Utils.sha_new(listpasswd.encode()).hexdigest() - # Guarantee that all newly created files have the proper permission. - # proper group ownership should be assured by the autoconf script - # enforcing that all directories have the group sticky bit set - oldmask = os.umask(0o002) + mlist = MailList.MailList(listname, lock=1) try: + pw = Utils.sha_new(listpasswd.encode()).hexdigest() + # Guarantee that all newly created files have the proper permission. + # proper group ownership should be assured by the autoconf script + # enforcing that all directories have the group sticky bit set + oldmask = os.umask(0o002) try: - if args.language == mm_cfg.DEFAULT_SERVER_LANGUAGE: - langs = [args.language] + if lang == mm_cfg.DEFAULT_SERVER_LANGUAGE: + langs = [lang] else: - langs = [args.language, mm_cfg.DEFAULT_SERVER_LANGUAGE] + langs = [lang, mm_cfg.DEFAULT_SERVER_LANGUAGE] mlist.Create(listname, owner_mail, pw, langs=langs, - emailhost=emailhost, urlhost=urlhost) + emailhost=host_name, urlhost=urlhost) finally: os.umask(oldmask) except Errors.BadListNameError as s: @@ -211,15 +218,16 @@ def main(): usage(1, C_(f"List already exists: {listname}")) # Assign domain-specific attributes - mlist.host_name = emailhost + mlist.host_name = host_name mlist.web_page_url = web_page_url # And assign the preferred language - mlist.preferred_language = args.language + mlist.preferred_language = lang mlist.Save() finally: - mlist.Unlock() + if mlist: + mlist.Unlock() # Now do the MTA-specific list creation tasks if mm_cfg.MTA: diff --git a/bin/withlist b/bin/withlist index 020055dd..20215fbe 100644 --- a/bin/withlist +++ b/bin/withlist @@ -145,18 +145,18 @@ def parse_args(): parser = argparse.ArgumentParser(description='General framework for interacting with a mailing list object.') parser.add_argument('-l', '--lock', action='store_true', help='Lock the list when opening') + parser.add_argument('-r', '--run', + help='Run the specified module.callable') + parser.add_argument('-q', '--quiet', action='store_true', + help='Suppress verbose output') parser.add_argument('-i', '--interactive', action='store_true', help='Leave at interactive prompt after processing') - parser.add_argument('-r', '--run', - help='Run a script with the opened MailList object') parser.add_argument('-a', '--all', action='store_true', - help='Execute the script on all mailing lists') - parser.add_argument('-q', '--quiet', action='store_true', - help='Suppress all status messages') + help='Process all lists') parser.add_argument('listname', nargs='?', - help='Name of the mailing list') + help='Name of the list to process') parser.add_argument('args', nargs='*', - help='Additional arguments to pass to the script') + help='Additional arguments to pass to the callable') return parser.parse_args() @@ -223,11 +223,16 @@ def main(): VERBOSE = not args.quiet LOCK = args.lock + # The default for interact is True unless -r was given + interact = args.interactive + if interact is None: + interact = args.run is None + # Import the callable if one was specified func = None if args.run: if VERBOSE: - print(C_('Importing %(module)s ...'), file=sys.stderr) + print(C_('Importing %(module)s...'), file=sys.stderr) try: if '.' in args.run: module, callable = args.run.rsplit('.', 1) @@ -245,6 +250,8 @@ def main(): if args.all: if args.listname: usage(1, C_('Cannot specify listname with --all')) + if not args.run: + usage(1, C_('--all requires --run')) results = [] for listname in Utils.list_names(): if VERBOSE: @@ -255,17 +262,34 @@ def main(): r = results else: if not args.listname: - usage(1, C_('No listname specified')) - r = do_list(args.listname, args.args, func) - - # If we're not running a script, or if --interactive was given, start an - # interactive interpreter - if not args.run or args.interactive: - # Make sure the list is unlocked before starting the interpreter - if m and m.Locked(): - m.Unlock() - # Start the interpreter - code.interact(local=locals()) + warning = C_('No list name supplied.') + if interact: + # Let them keep going + print(warning) + dolist = False + else: + usage(1, warning) + else: + dolist = True + listname = args.listname.lower().strip() + r = do_list(listname, args.args, func) + + # Now go to interactive mode, perhaps + if interact: + # Attempt to import the readline module, so we emulate the interactive + # console as closely as possible. Don't worry if it doesn't import. + # readline works by side-effect. + try: + import readline + except ImportError: + pass + namespace = globals().copy() + namespace.update(locals()) + if dolist: + ban = C_("The variable `m' is the %(listname)s MailList instance") + else: + ban = None + code.InteractiveConsole(namespace).interact(ban) else: # We're done sys.exit(0) From fb7f2f1035b8093a79d1755097149f7a9a01dc2e Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 24 Apr 2025 09:38:21 -0400 Subject: [PATCH 017/748] update --- bin/qrunner | 73 ++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 53 insertions(+), 20 deletions(-) diff --git a/bin/qrunner b/bin/qrunner index 5445a065..3e597d54 100644 --- a/bin/qrunner +++ b/bin/qrunner @@ -221,26 +221,59 @@ def main(): else: LogStdErr('error', 'qrunner', manual_reprime=0) - # Create the qrunners - qrunners = [] - for runner, slice, range in runners: - qrunners.append(make_qrunner(runner, slice, range, args.once)) - - # Set up the signal handlers - for qrunner in qrunners: - set_signals(qrunner) - - # Run the qrunners in round-robin fashion - while 1: - for qrunner in qrunners: - if qrunner.status is not None: - sys.exit(qrunner.status) - qrunner.run() - if qrunner.status is not None: - sys.exit(qrunner.status) - if args.once: - break - time.sleep(1) + # Fast track for one infinite runner + if len(runners) == 1 and not args.once: + qrunner = make_qrunner(*runners[0]) + class Loop: + status = 0 + def __init__(self, qrunner): + self.__qrunner = qrunner + def name(self): + return self.__qrunner.__class__.__name__ + def stop(self): + self.__qrunner.stop() + loop = Loop(qrunner) + set_signals(loop) + # Now start up the main loop + syslog('qrunner', '%s qrunner started.', loop.name()) + qrunner.run() + syslog('qrunner', '%s qrunner exiting.', loop.name()) + else: + # Anything else we have to handle a bit more specially + qrunners = [] + for runner, slice, range in runners: + qrunner = make_qrunner(runner, slice, range, args.once) + qrunners.append(qrunner) + # This class is used to manage the main loop + class Loop: + status = 0 + def __init__(self): + self.__isdone = 0 + def name(self): + return 'Main loop' + def stop(self): + self.__isdone = 1 + def isdone(self): + return self.__isdone + loop = Loop() + set_signals(loop) + syslog('qrunner', 'Main qrunner loop started.') + while not loop.isdone(): + for qrunner in qrunners: + # In case the SIGTERM came in the middle of this iteration + if loop.isdone(): + break + if args.verbose: + syslog('qrunner', 'Now doing a %s qrunner iteration', + qrunner.__class__.__bases__[0].__name__) + qrunner.run() + if args.once: + break + if mm_cfg.QRUNNER_SLEEP_TIME > 0: + time.sleep(mm_cfg.QRUNNER_SLEEP_TIME) + syslog('qrunner', 'Main qrunner loop exiting.') + # All done + sys.exit(loop.status) if __name__ == '__main__': From ce7137eb6faf2b7f03124de5689faffea1061ac8 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 24 Apr 2025 09:43:21 -0400 Subject: [PATCH 018/748] update --- Mailman/Logging/Logger.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Mailman/Logging/Logger.py b/Mailman/Logging/Logger.py index 556e4292..b1bc685c 100644 --- a/Mailman/Logging/Logger.py +++ b/Mailman/Logging/Logger.py @@ -69,10 +69,9 @@ def __get_f(self): try: try: f = codecs.open( - self.__filename, 'a+', self.__encoding, 'replace', - 1) + self.__filename, 'ab', self.__encoding, 'replace') except LookupError: - f = open(self.__filename, 'a+', 1) + f = open(self.__filename, 'ab') self.__fp = f finally: os.umask(ou) From 264333d1d469c61102827797629ea02469172e10 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 24 Apr 2025 09:58:05 -0400 Subject: [PATCH 019/748] update --- Mailman/Queue/Runner.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Mailman/Queue/Runner.py b/Mailman/Queue/Runner.py index 8a68e5da..1b517924 100644 --- a/Mailman/Queue/Runner.py +++ b/Mailman/Queue/Runner.py @@ -47,6 +47,7 @@ def __init__(self, slice=None, numslices=1): # Create the shunt switchboard self._shunt = Switchboard(mm_cfg.SHUNTQUEUE_DIR) self._stop = False + self.status = 0 # Add status attribute initialized to 0 def __repr__(self): return '<%s at %s>' % (self.__class__.__name__, id(self)) From ba153a8ea50da11a216e5a3391e954e9527b3801 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 24 Apr 2025 10:38:58 -0400 Subject: [PATCH 020/748] update --- Mailman/Queue/NewsRunner.py | 14 +- configure | 1215 +++++++++++++++++----------------- configure.in => configure.ac | 125 ++-- 3 files changed, 680 insertions(+), 674 deletions(-) rename configure.in => configure.ac (89%) diff --git a/Mailman/Queue/NewsRunner.py b/Mailman/Queue/NewsRunner.py index 9a402436..4b157e18 100644 --- a/Mailman/Queue/NewsRunner.py +++ b/Mailman/Queue/NewsRunner.py @@ -20,7 +20,6 @@ from builtins import str import re import socket -import nntplib from io import StringIO import email @@ -33,6 +32,12 @@ from Mailman.Queue.Runner import Runner from Mailman.Logging.Syslog import syslog +# Only import nntplib if NNTP support is enabled +try: + import nntplib + HAVE_NNTP = True +except ImportError: + HAVE_NNTP = False # Matches our Mailman crafted Message-IDs. See Utils.unique_message_id() mcre = re.compile(r""" @@ -47,10 +52,14 @@ """, re.VERBOSE) - class NewsRunner(Runner): QDIR = mm_cfg.NEWSQUEUE_DIR + def __init__(self, slice=None, numslices=1): + if not HAVE_NNTP: + raise ImportError("NNTP support is not enabled. Please install python3-nntplib and reconfigure with --enable-nntp") + Runner.__init__(self, slice, numslices) + def _dispose(self, mlist, msg, msgdata): # Make sure we have the most up-to-date state mlist.Load() @@ -87,7 +96,6 @@ def _dispose(self, mlist, msg, msgdata): return False - def prepare_message(mlist, msg, msgdata): # If the newsgroup is moderated, we need to add this header for the Usenet # software to accept the posting, and not forward it on to the n.g.'s diff --git a/configure b/configure index e2b96a78..aeb4bf4c 100755 --- a/configure +++ b/configure @@ -1,10 +1,10 @@ #! /bin/sh -# From configure.in Revision: 8122 . +# From configure.ac Revision: 8122 . # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.71. +# Generated by GNU Autoconf 2.72. # # -# Copyright (C) 1992-1996, 1998-2017, 2020-2021 Free Software Foundation, +# Copyright (C) 1992-1996, 1998-2017, 2020-2023 Free Software Foundation, # Inc. # # @@ -16,7 +16,6 @@ # Be more Bourne compatible DUALCASE=1; export DUALCASE # for MKS sh -as_nop=: if test ${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 then : emulate sh @@ -25,12 +24,13 @@ then : # is contrary to our usage. Disable this feature. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST -else $as_nop - case `(set -o) 2>/dev/null` in #( +else case e in #( + e) case `(set -o) 2>/dev/null` in #( *posix*) : set -o posix ;; #( *) : ;; +esac ;; esac fi @@ -102,7 +102,7 @@ IFS=$as_save_IFS ;; esac -# We did not find ourselves, most probably we were run as `sh COMMAND' +# We did not find ourselves, most probably we were run as 'sh COMMAND' # in which case we are not to be found in the path. if test "x$as_myself" = x; then as_myself=$0 @@ -132,15 +132,14 @@ case $- in # (((( esac exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} # Admittedly, this is quite paranoid, since all the known shells bail -# out after a failed `exec'. +# out after a failed 'exec'. printf "%s\n" "$0: could not re-execute with $CONFIG_SHELL" >&2 exit 255 fi # We don't want this to propagate to other subprocesses. { _as_can_reexec=; unset _as_can_reexec;} if test "x$CONFIG_SHELL" = x; then - as_bourne_compatible="as_nop=: -if test \${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 + as_bourne_compatible="if test \${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 then : emulate sh NULLCMD=: @@ -148,12 +147,13 @@ then : # is contrary to our usage. Disable this feature. alias -g '\${1+\"\$@\"}'='\"\$@\"' setopt NO_GLOB_SUBST -else \$as_nop - case \`(set -o) 2>/dev/null\` in #( +else case e in #( + e) case \`(set -o) 2>/dev/null\` in #( *posix*) : set -o posix ;; #( *) : ;; +esac ;; esac fi " @@ -171,8 +171,9 @@ as_fn_ret_failure && { exitcode=1; echo as_fn_ret_failure succeeded.; } if ( set x; as_fn_ret_success y && test x = \"\$1\" ) then : -else \$as_nop - exitcode=1; echo positional parameters were not saved. +else case e in #( + e) exitcode=1; echo positional parameters were not saved. ;; +esac fi test x\$exitcode = x0 || exit 1 blah=\$(echo \$(echo blah)) @@ -186,14 +187,15 @@ test \$(( 1 + 1 )) = 2 || exit 1" if (eval "$as_required") 2>/dev/null then : as_have_required=yes -else $as_nop - as_have_required=no +else case e in #( + e) as_have_required=no ;; +esac fi if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null then : -else $as_nop - as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +else case e in #( + e) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR as_found=false for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH do @@ -226,12 +228,13 @@ IFS=$as_save_IFS if $as_found then : -else $as_nop - if { test -f "$SHELL" || test -f "$SHELL.exe"; } && +else case e in #( + e) if { test -f "$SHELL" || test -f "$SHELL.exe"; } && as_run=a "$SHELL" -c "$as_bourne_compatible""$as_required" 2>/dev/null then : CONFIG_SHELL=$SHELL as_have_required=yes -fi +fi ;; +esac fi @@ -253,7 +256,7 @@ case $- in # (((( esac exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} # Admittedly, this is quite paranoid, since all the known shells bail -# out after a failed `exec'. +# out after a failed 'exec'. printf "%s\n" "$0: could not re-execute with $CONFIG_SHELL" >&2 exit 255 fi @@ -272,7 +275,8 @@ $0: message. Then install a modern shell, or manually run $0: the script under such a shell if you do have one." fi exit 1 -fi +fi ;; +esac fi fi SHELL=${CONFIG_SHELL-/bin/sh} @@ -311,14 +315,6 @@ as_fn_exit () as_fn_set_status $1 exit $1 } # as_fn_exit -# as_fn_nop -# --------- -# Do nothing but, unlike ":", preserve the value of $?. -as_fn_nop () -{ - return $? -} -as_nop=as_fn_nop # as_fn_mkdir_p # ------------- @@ -387,11 +383,12 @@ then : { eval $1+=\$2 }' -else $as_nop - as_fn_append () +else case e in #( + e) as_fn_append () { eval $1=\$$1\$2 - } + } ;; +esac fi # as_fn_append # as_fn_arith ARG... @@ -405,21 +402,14 @@ then : { as_val=$(( $* )) }' -else $as_nop - as_fn_arith () +else case e in #( + e) as_fn_arith () { as_val=`expr "$@" || test $? -eq 1` - } + } ;; +esac fi # as_fn_arith -# as_fn_nop -# --------- -# Do nothing but, unlike ":", preserve the value of $?. -as_fn_nop () -{ - return $? -} -as_nop=as_fn_nop # as_fn_error STATUS ERROR [LINENO LOG_FD] # ---------------------------------------- @@ -493,6 +483,8 @@ as_cr_alnum=$as_cr_Letters$as_cr_digits /[$]LINENO/= ' <$as_myself | sed ' + t clear + :clear s/[$]LINENO.*/&-/ t lineno b @@ -541,7 +533,6 @@ esac as_echo='printf %s\n' as_echo_n='printf %s' - rm -f conf$$ conf$$.exe conf$$.file if test -d conf$$.dir; then rm -f conf$$.dir/conf$$.file @@ -553,9 +544,9 @@ if (echo >conf$$.file) 2>/dev/null; then if ln -s conf$$.file conf$$ 2>/dev/null; then as_ln_s='ln -s' # ... but there are two gotchas: - # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. - # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. - # In both cases, we have to default to `cp -pR'. + # 1) On MSYS, both 'ln -s file dir' and 'ln file dir' fail. + # 2) DJGPP < 2.04 has no symlinks; 'ln -s' creates a wrapper executable. + # In both cases, we have to default to 'cp -pR'. ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || as_ln_s='cp -pR' elif ln conf$$.file conf$$ 2>/dev/null; then @@ -580,10 +571,12 @@ as_test_x='test -x' as_executable_p=as_fn_executable_p # Sed expression to map a string onto a valid CPP name. -as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" +as_sed_cpp="y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g" +as_tr_cpp="eval sed '$as_sed_cpp'" # deprecated # Sed expression to map a string onto a valid variable name. -as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" +as_sed_sh="y%*+%pp%;s%[^_$as_cr_alnum]%_%g" +as_tr_sh="eval sed '$as_sed_sh'" # deprecated test -n "$DJDIR" || exec 7<&0 /dev/null && - as_fn_error $? "invalid feature name: \`$ac_useropt'" + as_fn_error $? "invalid feature name: '$ac_useropt'" ac_useropt_orig=$ac_useropt ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in @@ -880,7 +873,7 @@ do ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && - as_fn_error $? "invalid feature name: \`$ac_useropt'" + as_fn_error $? "invalid feature name: '$ac_useropt'" ac_useropt_orig=$ac_useropt ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in @@ -1093,7 +1086,7 @@ do ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && - as_fn_error $? "invalid package name: \`$ac_useropt'" + as_fn_error $? "invalid package name: '$ac_useropt'" ac_useropt_orig=$ac_useropt ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in @@ -1109,7 +1102,7 @@ do ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && - as_fn_error $? "invalid package name: \`$ac_useropt'" + as_fn_error $? "invalid package name: '$ac_useropt'" ac_useropt_orig=$ac_useropt ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in @@ -1139,8 +1132,8 @@ do | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*) x_libraries=$ac_optarg ;; - -*) as_fn_error $? "unrecognized option: \`$ac_option' -Try \`$0 --help' for more information" + -*) as_fn_error $? "unrecognized option: '$ac_option' +Try '$0 --help' for more information" ;; *=*) @@ -1148,7 +1141,7 @@ Try \`$0 --help' for more information" # Reject names that are not valid shell variable names. case $ac_envvar in #( '' | [0-9]* | *[!_$as_cr_alnum]* ) - as_fn_error $? "invalid variable name: \`$ac_envvar'" ;; + as_fn_error $? "invalid variable name: '$ac_envvar'" ;; esac eval $ac_envvar=\$ac_optarg export $ac_envvar ;; @@ -1198,7 +1191,7 @@ do as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val" done -# There might be people who depend on the old broken behavior: `$host' +# There might be people who depend on the old broken behavior: '$host' # used to hold the argument of --host etc. # FIXME: To remove some day. build=$build_alias @@ -1266,7 +1259,7 @@ if test ! -r "$srcdir/$ac_unique_file"; then test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .." as_fn_error $? "cannot find sources ($ac_unique_file) in $srcdir" fi -ac_msg="sources are in $srcdir, but \`cd $srcdir' does not work" +ac_msg="sources are in $srcdir, but 'cd $srcdir' does not work" ac_abs_confdir=`( cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error $? "$ac_msg" pwd)` @@ -1294,7 +1287,7 @@ if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures this package to adapt to many kinds of systems. +'configure' configures this package to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1308,11 +1301,11 @@ Configuration: --help=short display options specific to this package --help=recursive display the short help of all the included packages -V, --version display version information and exit - -q, --quiet, --silent do not print \`checking ...' messages + -q, --quiet, --silent do not print 'checking ...' messages --cache-file=FILE cache test results in FILE [disabled] - -C, --config-cache alias for \`--cache-file=config.cache' + -C, --config-cache alias for '--cache-file=config.cache' -n, --no-create do not create output files - --srcdir=DIR find the sources in DIR [configure dir or \`..'] + --srcdir=DIR find the sources in DIR [configure dir or '..'] Installation directories: --prefix=PREFIX install architecture-independent files in PREFIX @@ -1320,10 +1313,10 @@ Installation directories: --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX [PREFIX] -By default, \`make install' will install all the files in -\`$ac_default_prefix/bin', \`$ac_default_prefix/lib' etc. You can specify -an installation prefix other than \`$ac_default_prefix' using \`--prefix', -for instance \`--prefix=\$HOME'. +By default, 'make install' will install all the files in +'$ac_default_prefix/bin', '$ac_default_prefix/lib' etc. You can specify +an installation prefix other than '$ac_default_prefix' using '--prefix', +for instance '--prefix=\$HOME'. For better control, use the options below. @@ -1358,6 +1351,12 @@ if test -n "$ac_init_help"; then cat <<\_ACEOF +Optional Features: + --disable-option-checking ignore unrecognized --enable/--with options + --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) + --enable-FEATURE[=ARG] include FEATURE [ARG=yes] + --enable-nntp enable NNTP support (requires python3-nntplib) + Optional Packages: --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) @@ -1381,9 +1380,8 @@ Some influential environment variables: LIBS libraries to pass to the linker, e.g. -l CPPFLAGS (Objective) C/C++ preprocessor flags, e.g. -I if you have headers in a nonstandard directory - CPP C preprocessor -Use these variables to override the choices made by `configure' or to help +Use these variables to override the choices made by 'configure' or to help it to find libraries and programs with nonstandard names/locations. Report bugs to the package provider. @@ -1451,9 +1449,9 @@ test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF configure -generated by GNU Autoconf 2.71 +generated by GNU Autoconf 2.72 -Copyright (C) 2021 Free Software Foundation, Inc. +Copyright (C) 2023 Free Software Foundation, Inc. This configure script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it. _ACEOF @@ -1492,11 +1490,12 @@ printf "%s\n" "$ac_try_echo"; } >&5 } && test -s conftest.$ac_objext then : ac_retval=0 -else $as_nop - printf "%s\n" "$as_me: failed program was:" >&5 +else case e in #( + e) printf "%s\n" "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 - ac_retval=1 + ac_retval=1 ;; +esac fi eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno as_fn_set_status $ac_retval @@ -1534,11 +1533,12 @@ printf "%s\n" "$ac_try_echo"; } >&5 } then : ac_retval=0 -else $as_nop - printf "%s\n" "$as_me: failed program was:" >&5 +else case e in #( + e) printf "%s\n" "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 - ac_retval=1 + ac_retval=1 ;; +esac fi # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would @@ -1561,15 +1561,15 @@ printf %s "checking for $2... " >&6; } if eval test \${$3+y} then : printf %s "(cached) " >&6 -else $as_nop - cat confdefs.h - <<_ACEOF >conftest.$ac_ext +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Define $2 to an innocuous variant, in case declares $2. For example, HP-UX 11i declares gettimeofday. */ #define $2 innocuous_$2 /* System header to define __stub macros and hopefully few prototypes, - which can conflict with char $2 (); below. */ + which can conflict with char $2 (void); below. */ #include #undef $2 @@ -1580,7 +1580,7 @@ else $as_nop #ifdef __cplusplus extern "C" #endif -char $2 (); +char $2 (void); /* The GNU C library defines this for functions which it implements to always fail with ENOSYS. Some functions are actually named something starting with __ and the normal name is an alias. */ @@ -1599,11 +1599,13 @@ _ACEOF if ac_fn_c_try_link "$LINENO" then : eval "$3=yes" -else $as_nop - eval "$3=no" +else case e in #( + e) eval "$3=no" ;; +esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ - conftest$ac_exeext conftest.$ac_ext + conftest$ac_exeext conftest.$ac_ext ;; +esac fi eval ac_res=\$$3 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 @@ -1624,8 +1626,8 @@ printf %s "checking for $2... " >&6; } if eval test \${$3+y} then : printf %s "(cached) " >&6 -else $as_nop - cat confdefs.h - <<_ACEOF >conftest.$ac_ext +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 #include <$2> @@ -1633,10 +1635,12 @@ _ACEOF if ac_fn_c_try_compile "$LINENO" then : eval "$3=yes" -else $as_nop - eval "$3=no" +else case e in #( + e) eval "$3=no" ;; +esac fi -rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac fi eval ac_res=\$$3 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 @@ -1645,86 +1649,64 @@ printf "%s\n" "$ac_res" >&6; } } # ac_fn_c_check_header_compile -# ac_fn_c_try_cpp LINENO -# ---------------------- -# Try to preprocess conftest.$ac_ext, and return whether this succeeded. -ac_fn_c_try_cpp () +# ac_fn_c_check_type LINENO TYPE VAR INCLUDES +# ------------------------------------------- +# Tests whether TYPE exists after having included INCLUDES, setting cache +# variable VAR accordingly. +ac_fn_c_check_type () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack - if { { ac_try="$ac_cpp conftest.$ac_ext" -case "(($ac_try" in - *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; - *) ac_try_echo=$ac_try;; -esac -eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" -printf "%s\n" "$ac_try_echo"; } >&5 - (eval "$ac_cpp conftest.$ac_ext") 2>conftest.err - ac_status=$? - if test -s conftest.err; then - grep -v '^ *+' conftest.err >conftest.er1 - cat conftest.er1 >&5 - mv -f conftest.er1 conftest.err - fi - printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 - test $ac_status = 0; } > conftest.i && { - test -z "$ac_c_preproc_warn_flag$ac_c_werror_flag" || - test ! -s conftest.err - } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +printf %s "checking for $2... " >&6; } +if eval test \${$3+y} then : - ac_retval=0 -else $as_nop - printf "%s\n" "$as_me: failed program was:" >&5 -sed 's/^/| /' conftest.$ac_ext >&5 - - ac_retval=1 -fi - eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno - as_fn_set_status $ac_retval - -} # ac_fn_c_try_cpp - -# ac_fn_c_try_run LINENO -# ---------------------- -# Try to run conftest.$ac_ext, and return whether this succeeded. Assumes that -# executables *can* be run. -ac_fn_c_try_run () + printf %s "(cached) " >&6 +else case e in #( + e) eval "$3=no" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$4 +int +main (void) { - as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack - if { { ac_try="$ac_link" -case "(($ac_try" in - *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; - *) ac_try_echo=$ac_try;; -esac -eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" -printf "%s\n" "$ac_try_echo"; } >&5 - (eval "$ac_link") 2>&5 - ac_status=$? - printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 - test $ac_status = 0; } && { ac_try='./conftest$ac_exeext' - { { case "(($ac_try" in - *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; - *) ac_try_echo=$ac_try;; -esac -eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" -printf "%s\n" "$ac_try_echo"; } >&5 - (eval "$ac_try") 2>&5 - ac_status=$? - printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 - test $ac_status = 0; }; } +if (sizeof ($2)) + return 0; + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$4 +int +main (void) +{ +if (sizeof (($2))) + return 0; + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" then : - ac_retval=0 -else $as_nop - printf "%s\n" "$as_me: program exited with status $ac_status" >&5 - printf "%s\n" "$as_me: failed program was:" >&5 -sed 's/^/| /' conftest.$ac_ext >&5 - ac_retval=$ac_status +else case e in #( + e) eval "$3=yes" ;; +esac fi - rm -rf conftest.dSYM conftest_ipa8_conftest.oo +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi +eval ac_res=\$$3 + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +printf "%s\n" "$ac_res" >&6; } eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno - as_fn_set_status $ac_retval -} # ac_fn_c_try_run +} # ac_fn_c_check_type ac_configure_args_raw= for ac_arg do @@ -1750,7 +1732,7 @@ This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. It was created by $as_me, which was -generated by GNU Autoconf 2.71. Invocation command line was +generated by GNU Autoconf 2.72. Invocation command line was $ $0$ac_configure_args_raw @@ -1996,10 +1978,10 @@ esac printf "%s\n" "$as_me: loading site script $ac_site_file" >&6;} sed 's/^/| /' "$ac_site_file" >&5 . "$ac_site_file" \ - || { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 -printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} + || { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 +printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "failed to load site script $ac_site_file -See \`config.log' for more details" "$LINENO" 5; } +See 'config.log' for more details" "$LINENO" 5; } fi done @@ -2035,9 +2017,7 @@ struct stat; /* Most of the following tests are stolen from RCS 5.7 src/conf.sh. */ struct buf { int x; }; struct buf * (*rcsopen) (struct buf *, struct stat *, int); -static char *e (p, i) - char **p; - int i; +static char *e (char **p, int i) { return p[i]; } @@ -2051,6 +2031,21 @@ static char *f (char * (*g) (char **, int), char **p, ...) return s; } +/* C89 style stringification. */ +#define noexpand_stringify(a) #a +const char *stringified = noexpand_stringify(arbitrary+token=sequence); + +/* C89 style token pasting. Exercises some of the corner cases that + e.g. old MSVC gets wrong, but not very hard. */ +#define noexpand_concat(a,b) a##b +#define expand_concat(a,b) noexpand_concat(a,b) +extern int vA; +extern int vbee; +#define aye A +#define bee B +int *pvA = &expand_concat(v,aye); +int *pvbee = &noexpand_concat(v,bee); + /* OSF 4.0 Compaq cc is some sort of almost-ANSI by default. It has function prototypes and stuff, but not \xHH hex character constants. These do not provoke an error unfortunately, instead are silently treated @@ -2078,16 +2073,19 @@ ok |= (argc == 0 || f (e, argv, 0) != argv[0] || f (e, argv, 1) != argv[1]); # Test code for whether the C compiler supports C99 (global declarations) ac_c_conftest_c99_globals=' -// Does the compiler advertise C99 conformance? +/* Does the compiler advertise C99 conformance? */ #if !defined __STDC_VERSION__ || __STDC_VERSION__ < 199901L # error "Compiler does not advertise C99 conformance" #endif +// See if C++-style comments work. + #include extern int puts (const char *); extern int printf (const char *, ...); extern int dprintf (int, const char *, ...); extern void *malloc (size_t); +extern void free (void *); // Check varargs macros. These examples are taken from C99 6.10.3.5. // dprintf is used instead of fprintf to avoid needing to declare @@ -2137,7 +2135,6 @@ typedef const char *ccp; static inline int test_restrict (ccp restrict text) { - // See if C++-style comments work. // Iterate through items via the restricted pointer. // Also check for declarations in for loops. for (unsigned int i = 0; *(text+i) != '\''\0'\''; ++i) @@ -2203,6 +2200,8 @@ ac_c_conftest_c99_main=' ia->datasize = 10; for (int i = 0; i < ia->datasize; ++i) ia->data[i] = i * 1.234; + // Work around memory leak warnings. + free (ia); // Check named initializers. struct named_init ni = { @@ -2224,7 +2223,7 @@ ac_c_conftest_c99_main=' # Test code for whether the C compiler supports C11 (global declarations) ac_c_conftest_c11_globals=' -// Does the compiler advertise C11 conformance? +/* Does the compiler advertise C11 conformance? */ #if !defined __STDC_VERSION__ || __STDC_VERSION__ < 201112L # error "Compiler does not advertise C11 conformance" #endif @@ -2416,8 +2415,9 @@ IFS=$as_save_IFS if $as_found then : -else $as_nop - as_fn_error $? "cannot find required auxiliary files:$ac_missing_aux_files" "$LINENO" 5 +else case e in #( + e) as_fn_error $? "cannot find required auxiliary files:$ac_missing_aux_files" "$LINENO" 5 ;; +esac fi @@ -2445,12 +2445,12 @@ for ac_var in $ac_precious_vars; do eval ac_new_val=\$ac_env_${ac_var}_value case $ac_old_set,$ac_new_set in set,) - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5 -printf "%s\n" "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;} + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: '$ac_var' was set to '$ac_old_val' in the previous run" >&5 +printf "%s\n" "$as_me: error: '$ac_var' was set to '$ac_old_val' in the previous run" >&2;} ac_cache_corrupted=: ;; ,set) - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was not set in the previous run" >&5 -printf "%s\n" "$as_me: error: \`$ac_var' was not set in the previous run" >&2;} + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: '$ac_var' was not set in the previous run" >&5 +printf "%s\n" "$as_me: error: '$ac_var' was not set in the previous run" >&2;} ac_cache_corrupted=: ;; ,);; *) @@ -2459,18 +2459,18 @@ printf "%s\n" "$as_me: error: \`$ac_var' was not set in the previous run" >&2;} ac_old_val_w=`echo x $ac_old_val` ac_new_val_w=`echo x $ac_new_val` if test "$ac_old_val_w" != "$ac_new_val_w"; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' has changed since the previous run:" >&5 -printf "%s\n" "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;} + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: '$ac_var' has changed since the previous run:" >&5 +printf "%s\n" "$as_me: error: '$ac_var' has changed since the previous run:" >&2;} ac_cache_corrupted=: else - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&5 -printf "%s\n" "$as_me: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&2;} + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in '$ac_var' since the previous run:" >&5 +printf "%s\n" "$as_me: warning: ignoring whitespace changes in '$ac_var' since the previous run:" >&2;} eval $ac_var=\$ac_old_val fi - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: former value: \`$ac_old_val'" >&5 -printf "%s\n" "$as_me: former value: \`$ac_old_val'" >&2;} - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: current value: \`$ac_new_val'" >&5 -printf "%s\n" "$as_me: current value: \`$ac_new_val'" >&2;} + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: former value: '$ac_old_val'" >&5 +printf "%s\n" "$as_me: former value: '$ac_old_val'" >&2;} + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: current value: '$ac_new_val'" >&5 +printf "%s\n" "$as_me: current value: '$ac_new_val'" >&2;} fi;; esac # Pass precious variables to config.status. @@ -2486,11 +2486,11 @@ printf "%s\n" "$as_me: current value: \`$ac_new_val'" >&2;} fi done if $ac_cache_corrupted; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 -printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 +printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5 printf "%s\n" "$as_me: error: changes in the environment can compromise the build" >&2;} - as_fn_error $? "run \`${MAKE-make} distclean' and/or \`rm $cache_file' + as_fn_error $? "run '${MAKE-make} distclean' and/or 'rm $cache_file' and start over" "$LINENO" 5 fi ## -------------------- ## @@ -2541,8 +2541,8 @@ printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_path_with_python+y} then : printf %s "(cached) " >&6 -else $as_nop - case $with_python in +else case e in #( + e) case $with_python in [\\/]* | ?:[\\/]*) ac_cv_path_with_python="$with_python" # Let the user override the test with a path. ;; @@ -2568,6 +2568,7 @@ IFS=$as_save_IFS test -z "$ac_cv_path_with_python" && ac_cv_path_with_python="/usr/local/bin/python3" ;; +esac ;; esac fi with_python=$ac_cv_path_with_python @@ -2587,15 +2588,57 @@ printf %s "checking Python interpreter... " >&6; } if test ! -x $with_python then as_fn_error $? " + Python interpreter not found at $with_python + Please specify the correct path to Python using --with-python + " "$LINENO" 5 +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $with_python" >&5 +printf "%s\n" "$with_python" >&6; } -***** No Python interpreter found! -***** Try including the configure option -***** --with-python=/path/to/python/interpreter" "$LINENO" 5 +# Check for optional nntplib module +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether to enable NNTP support" >&5 +printf %s "checking whether to enable NNTP support... " >&6; } +# Check whether --enable-nntp was given. +if test ${enable_nntp+y} +then : + enableval=$enable_nntp; enable_nntp=$enableval +else case e in #( + e) enable_nntp=no + ;; +esac +fi + +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $enable_nntp" >&5 +printf "%s\n" "$enable_nntp" >&6; } + +if test "$enable_nntp" = "yes"; then + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for Python nntplib module" >&5 +printf %s "checking for Python nntplib module... " >&6; } + $with_python -c "import nntplib" >/dev/null 2>&1 + if test $? -ne 0 + then + as_fn_error $? " + Python nntplib module not found but NNTP support was requested + Please install python3-nntplib package + On Debian/Ubuntu: apt-get install python3-nntplib + On RHEL/CentOS: yum install python3-nntplib + Or disable NNTP support with --disable-nntp + " "$LINENO" 5 + fi + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: found" >&5 +printf "%s\n" "found" >&6; } + +printf "%s\n" "#define HAVE_NNTP 1" >>confdefs.h + +fi + if test "$enable_nntp" = "yes"; then + HAVE_NNTP_TRUE= + HAVE_NNTP_FALSE='#' +else + HAVE_NNTP_TRUE='#' + HAVE_NNTP_FALSE= fi -PYTHON=$with_python -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $PYTHON" >&5 -printf "%s\n" "$PYTHON" >&6; } # See if Python is new enough. { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking Python version" >&5 @@ -2607,24 +2650,23 @@ try: v = sys.hexversion except AttributeError: v = 0 -if v >= 0x2040000: +if v >= 0x3000000: s = sys.version.split()[0] else: s = "" -fp = open("conftest.out", "w") -fp.write("%s\n" % s) -fp.close() +with open("conftest.out", "w") as fp: + fp.write("%s\n" % s) EOF -$PYTHON conftest.py +$with_python conftest.py version=`cat conftest.out` rm -f conftest.out conftest.py if test -z "$version" then as_fn_error $? " -***** $PYTHON is too old (or broken) -***** Python 2.4 or newer is required" "$LINENO" 5 +***** $with_python is too old (or broken) +***** Python 3.0 or newer is required" "$LINENO" 5 fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $version" >&5 printf "%s\n" "$version" >&6; } @@ -2639,12 +2681,11 @@ try: res = 'ok' except ImportError: res = 'no' -fp = open("conftest.out", "w") -fp.write("%s\n" % res) -fp.close() +with open("conftest.out", "w") as fp: + fp.write("%s\n" % res) EOF -$PYTHON conftest.py +$with_python conftest.py havednspython=`cat conftest.out` rm -f conftest.out conftest.py if test "$havednspython" = "no" @@ -2672,22 +2713,20 @@ except ImportError: res = "not ok" else: res = "ok" -fp = open("conftest.out", "w") -fp.write("%s\n" % res) -fp.close() +with open("conftest.out", "w") as fp: + fp.write("%s\n" % res) EOF -$PYTHON conftest.py +$with_python conftest.py needemailpkg=`cat conftest.out` rm -f conftest.out conftest.py cat > getver.py <&6 -else $as_nop - as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +else case e in #( + e) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS @@ -2889,7 +2925,8 @@ esac IFS=$as_save_IFS rm -rf conftest.one conftest.two conftest.dir - + ;; +esac fi if test ${ac_cv_path_install+y}; then INSTALL=$ac_cv_path_install @@ -2919,8 +2956,8 @@ ac_make=`printf "%s\n" "$2" | sed 's/+/p/g; s/[^a-zA-Z0-9_]/_/g'` if eval test \${ac_cv_prog_make_${ac_make}_set+y} then : printf %s "(cached) " >&6 -else $as_nop - cat >conftest.make <<\_ACEOF +else case e in #( + e) cat >conftest.make <<\_ACEOF SHELL = /bin/sh all: @echo '@@@%%%=$(MAKE)=@@@%%%' @@ -2932,7 +2969,8 @@ case `${MAKE-make} -f conftest.make 2>/dev/null` in *) eval ac_cv_prog_make_${ac_make}_set=no;; esac -rm -f conftest.make +rm -f conftest.make ;; +esac fi if eval test \$ac_cv_prog_make_${ac_make}_set = yes; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 @@ -2951,8 +2989,8 @@ printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_path_TRUE+y} then : printf %s "(cached) " >&6 -else $as_nop - case $TRUE in +else case e in #( + e) case $TRUE in [\\/]* | ?:[\\/]*) ac_cv_path_TRUE="$TRUE" # Let the user override the test with a path. ;; @@ -2979,6 +3017,7 @@ IFS=$as_save_IFS test -z "$ac_cv_path_TRUE" && ac_cv_path_TRUE="true" ;; +esac ;; esac fi TRUE=$ac_cv_path_TRUE @@ -3008,8 +3047,9 @@ then : *) CC=$withval without_gcc=$withval;; esac -else $as_nop - without_gcc=no; +else case e in #( + e) without_gcc=no; ;; +esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $without_gcc" >&5 @@ -3044,8 +3084,8 @@ printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6 -else $as_nop - if test -n "$CC"; then +else case e in #( + e) if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR @@ -3067,7 +3107,8 @@ done done IFS=$as_save_IFS -fi +fi ;; +esac fi CC=$ac_cv_prog_CC if test -n "$CC"; then @@ -3089,8 +3130,8 @@ printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_CC+y} then : printf %s "(cached) " >&6 -else $as_nop - if test -n "$ac_ct_CC"; then +else case e in #( + e) if test -n "$ac_ct_CC"; then ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR @@ -3112,7 +3153,8 @@ done done IFS=$as_save_IFS -fi +fi ;; +esac fi ac_ct_CC=$ac_cv_prog_ac_ct_CC if test -n "$ac_ct_CC"; then @@ -3147,8 +3189,8 @@ printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6 -else $as_nop - if test -n "$CC"; then +else case e in #( + e) if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR @@ -3170,7 +3212,8 @@ done done IFS=$as_save_IFS -fi +fi ;; +esac fi CC=$ac_cv_prog_CC if test -n "$CC"; then @@ -3192,8 +3235,8 @@ printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6 -else $as_nop - if test -n "$CC"; then +else case e in #( + e) if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else ac_prog_rejected=no @@ -3232,7 +3275,8 @@ if test $ac_prog_rejected = yes; then ac_cv_prog_CC="$as_dir$ac_word${1+' '}$@" fi fi -fi +fi ;; +esac fi CC=$ac_cv_prog_CC if test -n "$CC"; then @@ -3256,8 +3300,8 @@ printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6 -else $as_nop - if test -n "$CC"; then +else case e in #( + e) if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR @@ -3279,7 +3323,8 @@ done done IFS=$as_save_IFS -fi +fi ;; +esac fi CC=$ac_cv_prog_CC if test -n "$CC"; then @@ -3305,8 +3350,8 @@ printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_CC+y} then : printf %s "(cached) " >&6 -else $as_nop - if test -n "$ac_ct_CC"; then +else case e in #( + e) if test -n "$ac_ct_CC"; then ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR @@ -3328,7 +3373,8 @@ done done IFS=$as_save_IFS -fi +fi ;; +esac fi ac_ct_CC=$ac_cv_prog_ac_ct_CC if test -n "$ac_ct_CC"; then @@ -3366,8 +3412,8 @@ printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6 -else $as_nop - if test -n "$CC"; then +else case e in #( + e) if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR @@ -3389,7 +3435,8 @@ done done IFS=$as_save_IFS -fi +fi ;; +esac fi CC=$ac_cv_prog_CC if test -n "$CC"; then @@ -3411,8 +3458,8 @@ printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_CC+y} then : printf %s "(cached) " >&6 -else $as_nop - if test -n "$ac_ct_CC"; then +else case e in #( + e) if test -n "$ac_ct_CC"; then ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR @@ -3434,7 +3481,8 @@ done done IFS=$as_save_IFS -fi +fi ;; +esac fi ac_ct_CC=$ac_cv_prog_ac_ct_CC if test -n "$ac_ct_CC"; then @@ -3463,10 +3511,10 @@ fi fi -test -z "$CC" && { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 -printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} +test -z "$CC" && { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 +printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "no acceptable C compiler found in \$PATH -See \`config.log' for more details" "$LINENO" 5; } +See 'config.log' for more details" "$LINENO" 5; } # Provide some information about the compiler. printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5 @@ -3538,8 +3586,8 @@ printf "%s\n" "$ac_try_echo"; } >&5 printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } then : - # Autoconf-2.13 could set the ac_cv_exeext variable to `no'. -# So ignore a value of `no', otherwise this would lead to `EXEEXT = no' + # Autoconf-2.13 could set the ac_cv_exeext variable to 'no'. +# So ignore a value of 'no', otherwise this would lead to 'EXEEXT = no' # in a Makefile. We should not override ac_cv_exeext if it was cached, # so that the user can short-circuit this test for compilers unknown to # Autoconf. @@ -3559,7 +3607,7 @@ do ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` fi # We set ac_cv_exeext here because the later test for it is not - # safe: cross compilers may not add the suffix if given an `-o' + # safe: cross compilers may not add the suffix if given an '-o' # argument, so we may need to know it at that point already. # Even if this section looks crufty: it has the advantage of # actually working. @@ -3570,8 +3618,9 @@ do done test "$ac_cv_exeext" = no && ac_cv_exeext= -else $as_nop - ac_file='' +else case e in #( + e) ac_file='' ;; +esac fi if test -z "$ac_file" then : @@ -3580,13 +3629,14 @@ printf "%s\n" "no" >&6; } printf "%s\n" "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 -{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 -printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} +{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 +printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error 77 "C compiler cannot create executables -See \`config.log' for more details" "$LINENO" 5; } -else $as_nop - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 -printf "%s\n" "yes" >&6; } +See 'config.log' for more details" "$LINENO" 5; } +else case e in #( + e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +printf "%s\n" "yes" >&6; } ;; +esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for C compiler default output file name" >&5 printf %s "checking for C compiler default output file name... " >&6; } @@ -3610,10 +3660,10 @@ printf "%s\n" "$ac_try_echo"; } >&5 printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } then : - # If both `conftest.exe' and `conftest' are `present' (well, observable) -# catch `conftest.exe'. For instance with Cygwin, `ls conftest' will -# work properly (i.e., refer to `conftest.exe'), while it won't with -# `rm'. + # If both 'conftest.exe' and 'conftest' are 'present' (well, observable) +# catch 'conftest.exe'. For instance with Cygwin, 'ls conftest' will +# work properly (i.e., refer to 'conftest.exe'), while it won't with +# 'rm'. for ac_file in conftest.exe conftest conftest.*; do test -f "$ac_file" || continue case $ac_file in @@ -3623,11 +3673,12 @@ for ac_file in conftest.exe conftest conftest.*; do * ) break;; esac done -else $as_nop - { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 -printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} +else case e in #( + e) { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 +printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "cannot compute suffix of executables: cannot compile and link -See \`config.log' for more details" "$LINENO" 5; } +See 'config.log' for more details" "$LINENO" 5; } ;; +esac fi rm -f conftest conftest$ac_cv_exeext { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5 @@ -3643,6 +3694,8 @@ int main (void) { FILE *f = fopen ("conftest.out", "w"); + if (!f) + return 1; return ferror (f) || fclose (f) != 0; ; @@ -3682,26 +3735,27 @@ printf "%s\n" "$ac_try_echo"; } >&5 if test "$cross_compiling" = maybe; then cross_compiling=yes else - { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 -printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} + { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 +printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error 77 "cannot run C compiled programs. -If you meant to cross compile, use \`--host'. -See \`config.log' for more details" "$LINENO" 5; } +If you meant to cross compile, use '--host'. +See 'config.log' for more details" "$LINENO" 5; } fi fi fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $cross_compiling" >&5 printf "%s\n" "$cross_compiling" >&6; } -rm -f conftest.$ac_ext conftest$ac_cv_exeext conftest.out +rm -f conftest.$ac_ext conftest$ac_cv_exeext \ + conftest.o conftest.obj conftest.out ac_clean_files=$ac_clean_files_save { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5 printf %s "checking for suffix of object files... " >&6; } if test ${ac_cv_objext+y} then : printf %s "(cached) " >&6 -else $as_nop - cat confdefs.h - <<_ACEOF >conftest.$ac_ext +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int @@ -3733,16 +3787,18 @@ then : break;; esac done -else $as_nop - printf "%s\n" "$as_me: failed program was:" >&5 +else case e in #( + e) printf "%s\n" "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 -{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 -printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} +{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 +printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "cannot compute suffix of object files: cannot compile -See \`config.log' for more details" "$LINENO" 5; } +See 'config.log' for more details" "$LINENO" 5; } ;; +esac fi -rm -f conftest.$ac_cv_objext conftest.$ac_ext +rm -f conftest.$ac_cv_objext conftest.$ac_ext ;; +esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_objext" >&5 printf "%s\n" "$ac_cv_objext" >&6; } @@ -3753,8 +3809,8 @@ printf %s "checking whether the compiler supports GNU C... " >&6; } if test ${ac_cv_c_compiler_gnu+y} then : printf %s "(cached) " >&6 -else $as_nop - cat confdefs.h - <<_ACEOF >conftest.$ac_ext +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int @@ -3771,12 +3827,14 @@ _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_compiler_gnu=yes -else $as_nop - ac_compiler_gnu=no +else case e in #( + e) ac_compiler_gnu=no ;; +esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ac_cv_c_compiler_gnu=$ac_compiler_gnu - + ;; +esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5 printf "%s\n" "$ac_cv_c_compiler_gnu" >&6; } @@ -3794,8 +3852,8 @@ printf %s "checking whether $CC accepts -g... " >&6; } if test ${ac_cv_prog_cc_g+y} then : printf %s "(cached) " >&6 -else $as_nop - ac_save_c_werror_flag=$ac_c_werror_flag +else case e in #( + e) ac_save_c_werror_flag=$ac_c_werror_flag ac_c_werror_flag=yes ac_cv_prog_cc_g=no CFLAGS="-g" @@ -3813,8 +3871,8 @@ _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_cv_prog_cc_g=yes -else $as_nop - CFLAGS="" +else case e in #( + e) CFLAGS="" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -3829,8 +3887,8 @@ _ACEOF if ac_fn_c_try_compile "$LINENO" then : -else $as_nop - ac_c_werror_flag=$ac_save_c_werror_flag +else case e in #( + e) ac_c_werror_flag=$ac_save_c_werror_flag CFLAGS="-g" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -3847,12 +3905,15 @@ if ac_fn_c_try_compile "$LINENO" then : ac_cv_prog_cc_g=yes fi -rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac fi -rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext - ac_c_werror_flag=$ac_save_c_werror_flag + ac_c_werror_flag=$ac_save_c_werror_flag ;; +esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5 printf "%s\n" "$ac_cv_prog_cc_g" >&6; } @@ -3879,8 +3940,8 @@ printf %s "checking for $CC option to enable C11 features... " >&6; } if test ${ac_cv_prog_cc_c11+y} then : printf %s "(cached) " >&6 -else $as_nop - ac_cv_prog_cc_c11=no +else case e in #( + e) ac_cv_prog_cc_c11=no ac_save_CC=$CC cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -3897,25 +3958,28 @@ rm -f core conftest.err conftest.$ac_objext conftest.beam test "x$ac_cv_prog_cc_c11" != "xno" && break done rm -f conftest.$ac_ext -CC=$ac_save_CC +CC=$ac_save_CC ;; +esac fi if test "x$ac_cv_prog_cc_c11" = xno then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 printf "%s\n" "unsupported" >&6; } -else $as_nop - if test "x$ac_cv_prog_cc_c11" = x +else case e in #( + e) if test "x$ac_cv_prog_cc_c11" = x then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 printf "%s\n" "none needed" >&6; } -else $as_nop - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c11" >&5 +else case e in #( + e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c11" >&5 printf "%s\n" "$ac_cv_prog_cc_c11" >&6; } - CC="$CC $ac_cv_prog_cc_c11" + CC="$CC $ac_cv_prog_cc_c11" ;; +esac fi ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c11 - ac_prog_cc_stdc=c11 + ac_prog_cc_stdc=c11 ;; +esac fi fi if test x$ac_prog_cc_stdc = xno @@ -3925,8 +3989,8 @@ printf %s "checking for $CC option to enable C99 features... " >&6; } if test ${ac_cv_prog_cc_c99+y} then : printf %s "(cached) " >&6 -else $as_nop - ac_cv_prog_cc_c99=no +else case e in #( + e) ac_cv_prog_cc_c99=no ac_save_CC=$CC cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -3943,25 +4007,28 @@ rm -f core conftest.err conftest.$ac_objext conftest.beam test "x$ac_cv_prog_cc_c99" != "xno" && break done rm -f conftest.$ac_ext -CC=$ac_save_CC +CC=$ac_save_CC ;; +esac fi if test "x$ac_cv_prog_cc_c99" = xno then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 printf "%s\n" "unsupported" >&6; } -else $as_nop - if test "x$ac_cv_prog_cc_c99" = x +else case e in #( + e) if test "x$ac_cv_prog_cc_c99" = x then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 printf "%s\n" "none needed" >&6; } -else $as_nop - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c99" >&5 +else case e in #( + e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c99" >&5 printf "%s\n" "$ac_cv_prog_cc_c99" >&6; } - CC="$CC $ac_cv_prog_cc_c99" + CC="$CC $ac_cv_prog_cc_c99" ;; +esac fi ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c99 - ac_prog_cc_stdc=c99 + ac_prog_cc_stdc=c99 ;; +esac fi fi if test x$ac_prog_cc_stdc = xno @@ -3971,8 +4038,8 @@ printf %s "checking for $CC option to enable C89 features... " >&6; } if test ${ac_cv_prog_cc_c89+y} then : printf %s "(cached) " >&6 -else $as_nop - ac_cv_prog_cc_c89=no +else case e in #( + e) ac_cv_prog_cc_c89=no ac_save_CC=$CC cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -3989,25 +4056,28 @@ rm -f core conftest.err conftest.$ac_objext conftest.beam test "x$ac_cv_prog_cc_c89" != "xno" && break done rm -f conftest.$ac_ext -CC=$ac_save_CC +CC=$ac_save_CC ;; +esac fi if test "x$ac_cv_prog_cc_c89" = xno then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 printf "%s\n" "unsupported" >&6; } -else $as_nop - if test "x$ac_cv_prog_cc_c89" = x +else case e in #( + e) if test "x$ac_cv_prog_cc_c89" = x then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 printf "%s\n" "none needed" >&6; } -else $as_nop - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5 +else case e in #( + e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5 printf "%s\n" "$ac_cv_prog_cc_c89" >&6; } - CC="$CC $ac_cv_prog_cc_c89" + CC="$CC $ac_cv_prog_cc_c89" ;; +esac fi ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c89 - ac_prog_cc_stdc=c89 + ac_prog_cc_stdc=c89 ;; +esac fi fi @@ -4040,8 +4110,8 @@ printf %s "checking whether #! works in shell scripts... " >&6; } if test ${ac_cv_sys_interpreter+y} then : printf %s "(cached) " >&6 -else $as_nop - echo '#! /bin/cat +else case e in #( + e) echo '#! /bin/cat exit 69 ' >conftest chmod u+x conftest @@ -4051,7 +4121,8 @@ if test $? -ne 69; then else ac_cv_sys_interpreter=no fi -rm -f conftest +rm -f conftest ;; +esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_sys_interpreter" >&5 printf "%s\n" "$ac_cv_sys_interpreter" >&6; } @@ -4162,11 +4233,10 @@ for user in "$USERNAME".split(): break except KeyError: uname = '' -fp = open("conftest.out", "w") -fp.write("%s\n" % uname) -fp.close() +with open("conftest.out", "w") as fp: + fp.write("%s\n" % uname) EOF - $PYTHON conftest.py + $with_python conftest.py MAILMAN_USER=`cat conftest.out` fi @@ -4230,11 +4300,10 @@ for group in "$GROUPNAME".split(): break except KeyError: gname = '' -fp = open("conftest.out", "w") -fp.write("%s\n" % gname) -fp.close() +with open("conftest.out", "w") as fp: + fp.write("%s\n" % gname) EOF - $PYTHON conftest.py + $with_python conftest.py MAILMAN_GROUP=`cat conftest.out` fi @@ -4286,12 +4355,11 @@ if not problems: msg = "okay\n" else: msg = '***** ' + '\n***** '.join(problems) + '\n' -fp = open("conftest.out", "w") -fp.write(msg) -fp.close() +with open("conftest.out", "w") as fp: + fp.write(msg) EOF -$PYTHON conftest.py +$with_python conftest.py status=`cat conftest.out` rm -f conftest.out conftest.py if test "$with_permcheck" = "yes" @@ -4344,11 +4412,10 @@ for group in "$with_mail_gid".split(): break except KeyError: gname = '' -fp = open("conftest.out", "w") -fp.write("%s\n" % gname) -fp.close() +with open("conftest.out", "w") as fp: + fp.write("%s\n" % gname) EOF - $PYTHON conftest.py + $with_python conftest.py MAIL_GROUP=`cat conftest.out` fi @@ -4406,11 +4473,10 @@ for group in "$with_cgi_gid".split(): break except KeyError: gname = '' -fp = open("conftest.out", "w") -fp.write("%s\n" % gname) -fp.close() +with open("conftest.out", "w") as fp: + fp.write("%s\n" % gname) EOF - $PYTHON conftest.py + $with_python conftest.py CGI_GROUP=`cat conftest.out` fi @@ -4503,12 +4569,11 @@ cat > conftest.py <&5 printf %s "checking for default mail host name... " >&6; } @@ -4576,10 +4641,11 @@ printf "%s\n" "yes" >&6; } printf "%s\n" "#define HAVE_SYSLOG 1" >>confdefs.h break -else $as_nop - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 +else case e in #( + e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } - LIBS="$Mailman_LIBS_save" + LIBS="$Mailman_LIBS_save" ;; +esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext @@ -4624,8 +4690,8 @@ printf %s "checking for grep that handles long lines and -e... " >&6; } if test ${ac_cv_path_GREP+y} then : printf %s "(cached) " >&6 -else $as_nop - if test -z "$GREP"; then +else case e in #( + e) if test -z "$GREP"; then ac_path_GREP_found=false # Loop through the user's path and test for each of PROGNAME-LIST as_save_IFS=$IFS; IFS=$PATH_SEPARATOR @@ -4644,9 +4710,10 @@ do as_fn_executable_p "$ac_path_GREP" || continue # Check for GNU ac_path_GREP and select it if it is found. # Check for GNU $ac_path_GREP -case `"$ac_path_GREP" --version 2>&1` in +case `"$ac_path_GREP" --version 2>&1` in #( *GNU*) ac_cv_path_GREP="$ac_path_GREP" ac_path_GREP_found=:;; +#( *) ac_count=0 printf %s 0123456789 >"conftest.in" @@ -4681,7 +4748,8 @@ IFS=$as_save_IFS else ac_cv_path_GREP=$GREP fi - + ;; +esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_GREP" >&5 printf "%s\n" "$ac_cv_path_GREP" >&6; } @@ -4693,8 +4761,8 @@ printf %s "checking for egrep... " >&6; } if test ${ac_cv_path_EGREP+y} then : printf %s "(cached) " >&6 -else $as_nop - if echo a | $GREP -E '(a|b)' >/dev/null 2>&1 +else case e in #( + e) if echo a | $GREP -E '(a|b)' >/dev/null 2>&1 then ac_cv_path_EGREP="$GREP -E" else if test -z "$EGREP"; then @@ -4716,9 +4784,10 @@ do as_fn_executable_p "$ac_path_EGREP" || continue # Check for GNU ac_path_EGREP and select it if it is found. # Check for GNU $ac_path_EGREP -case `"$ac_path_EGREP" --version 2>&1` in +case `"$ac_path_EGREP" --version 2>&1` in #( *GNU*) ac_cv_path_EGREP="$ac_path_EGREP" ac_path_EGREP_found=:;; +#( *) ac_count=0 printf %s 0123456789 >"conftest.in" @@ -4754,12 +4823,15 @@ else ac_cv_path_EGREP=$EGREP fi - fi + fi ;; +esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_EGREP" >&5 printf "%s\n" "$ac_cv_path_EGREP" >&6; } EGREP="$ac_cv_path_EGREP" + EGREP_TRADITIONAL=$EGREP + ac_cv_path_EGREP_TRADITIONAL=$EGREP ac_fn_c_check_header_compile "$LINENO" "syslog.h" "ac_cv_header_syslog_h" "$ac_includes_default" @@ -4771,169 +4843,26 @@ fi # Checks for typedefs, structures, and compiler characteristics. -ac_ext=c -ac_cpp='$CPP $CPPFLAGS' -ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' -ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' -ac_compiler_gnu=$ac_cv_c_compiler_gnu -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking how to run the C preprocessor" >&5 -printf %s "checking how to run the C preprocessor... " >&6; } -# On Suns, sometimes $CPP names a directory. -if test -n "$CPP" && test -d "$CPP"; then - CPP= -fi -if test -z "$CPP"; then - if test ${ac_cv_prog_CPP+y} -then : - printf %s "(cached) " >&6 -else $as_nop - # Double quotes because $CC needs to be expanded - for CPP in "$CC -E" "$CC -E -traditional-cpp" cpp /lib/cpp - do - ac_preproc_ok=false -for ac_c_preproc_warn_flag in '' yes -do - # Use a header file that comes with gcc, so configuring glibc - # with a fresh cross-compiler works. - # On the NeXT, cc -E runs the code through the compiler's parser, - # not just through cpp. "Syntax error" is here to catch this case. - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -#include - Syntax error -_ACEOF -if ac_fn_c_try_cpp "$LINENO" -then : - -else $as_nop - # Broken: fails on valid input. -continue -fi -rm -f conftest.err conftest.i conftest.$ac_ext - - # OK, works on sane cases. Now check whether nonexistent headers - # can be detected and how. - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -#include -_ACEOF -if ac_fn_c_try_cpp "$LINENO" -then : - # Broken: success on invalid input. -continue -else $as_nop - # Passes both tests. -ac_preproc_ok=: -break -fi -rm -f conftest.err conftest.i conftest.$ac_ext - -done -# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. -rm -f conftest.i conftest.err conftest.$ac_ext -if $ac_preproc_ok -then : - break -fi - - done - ac_cv_prog_CPP=$CPP - -fi - CPP=$ac_cv_prog_CPP -else - ac_cv_prog_CPP=$CPP -fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CPP" >&5 -printf "%s\n" "$CPP" >&6; } -ac_preproc_ok=false -for ac_c_preproc_warn_flag in '' yes -do - # Use a header file that comes with gcc, so configuring glibc - # with a fresh cross-compiler works. - # On the NeXT, cc -E runs the code through the compiler's parser, - # not just through cpp. "Syntax error" is here to catch this case. - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -#include - Syntax error -_ACEOF -if ac_fn_c_try_cpp "$LINENO" -then : - -else $as_nop - # Broken: fails on valid input. -continue -fi -rm -f conftest.err conftest.i conftest.$ac_ext - - # OK, works on sane cases. Now check whether nonexistent headers - # can be detected and how. - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -#include -_ACEOF -if ac_fn_c_try_cpp "$LINENO" -then : - # Broken: success on invalid input. -continue -else $as_nop - # Passes both tests. -ac_preproc_ok=: -break -fi -rm -f conftest.err conftest.i conftest.$ac_ext - -done -# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. -rm -f conftest.i conftest.err conftest.$ac_ext -if $ac_preproc_ok +ac_fn_c_check_type "$LINENO" "uid_t" "ac_cv_type_uid_t" "$ac_includes_default" +if test "x$ac_cv_type_uid_t" = xyes then : -else $as_nop - { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 -printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} -as_fn_error $? "C preprocessor \"$CPP\" fails sanity check -See \`config.log' for more details" "$LINENO" 5; } +else case e in #( + e) +printf "%s\n" "#define uid_t int" >>confdefs.h + ;; +esac fi -ac_ext=c -ac_cpp='$CPP $CPPFLAGS' -ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' -ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' -ac_compiler_gnu=$ac_cv_c_compiler_gnu - - -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for uid_t in sys/types.h" >&5 -printf %s "checking for uid_t in sys/types.h... " >&6; } -if test ${ac_cv_type_uid_t+y} -then : - printf %s "(cached) " >&6 -else $as_nop - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -#include - -_ACEOF -if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | - $EGREP "uid_t" >/dev/null 2>&1 +ac_fn_c_check_type "$LINENO" "gid_t" "ac_cv_type_gid_t" "$ac_includes_default" +if test "x$ac_cv_type_gid_t" = xyes then : - ac_cv_type_uid_t=yes -else $as_nop - ac_cv_type_uid_t=no -fi -rm -rf conftest* - -fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_type_uid_t" >&5 -printf "%s\n" "$ac_cv_type_uid_t" >&6; } -if test $ac_cv_type_uid_t = no; then - -printf "%s\n" "#define uid_t int" >>confdefs.h - +else case e in #( + e) printf "%s\n" "#define gid_t int" >>confdefs.h - + ;; +esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking type of array argument to getgroups" >&5 @@ -4941,67 +4870,118 @@ printf %s "checking type of array argument to getgroups... " >&6; } if test ${ac_cv_type_getgroups+y} then : printf %s "(cached) " >&6 -else $as_nop - if test "$cross_compiling" = yes +else case e in #( + e) # If AC_TYPE_UID_T says there isn't any gid_t typedef, then we can skip +# everything below. +if test $ac_cv_type_gid_t = no then : - ac_cv_type_getgroups=cross -else $as_nop + ac_cv_type_getgroups=int +else case e in #( + e) # Test programs below rely on strict type checking of extern declarations: + # 'extern int getgroups(int, int *); extern int getgroups(int, pid_t *);' + # is valid in C89 if and only if pid_t is a typedef for int. Unlike + # anything involving either an assignment or a function call, compilers + # tend to make this kind of type mismatch a hard error, not just an + # "incompatible pointer types" warning. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ -/* Thanks to Mike Rendell for this test. */ $ac_includes_default -#define NGID 256 -#undef MAX -#define MAX(x, y) ((x) > (y) ? (x) : (y)) - +extern int getgroups(int, gid_t *); int main (void) { - gid_t gidset[NGID]; - int i, n; - union { gid_t gval; long int lval; } val; - - val.lval = -1; - for (i = 0; i < NGID; i++) - gidset[i] = val.gval; - n = getgroups (sizeof (gidset) / MAX (sizeof (int), sizeof (gid_t)) - 1, - gidset); - /* Exit non-zero if getgroups seems to require an array of ints. This - happens when gid_t is short int but getgroups modifies an array - of ints. */ - return n > 0 && gidset[n] != val.gval; +return !(getgroups(0, 0) >= 0); + ; + return 0; } _ACEOF -if ac_fn_c_try_run "$LINENO" +if ac_fn_c_try_compile "$LINENO" then : - ac_cv_type_getgroups=gid_t -else $as_nop - ac_cv_type_getgroups=int + ac_getgroups_gidarray=yes +else case e in #( + e) ac_getgroups_gidarray=no ;; +esac fi -rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ - conftest.$ac_objext conftest.beam conftest.$ac_ext +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$ac_includes_default +extern int getgroups(int, int *); +int +main (void) +{ +return !(getgroups(0, 0) >= 0); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + ac_getgroups_intarray=yes +else case e in #( + e) ac_getgroups_intarray=no ;; +esac fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext -if test $ac_cv_type_getgroups = cross; then - cat confdefs.h - <<_ACEOF >conftest.$ac_ext + case int:$ac_getgroups_intarray,gid:$ac_getgroups_gidarray in #( + int:yes,gid:no) : + ac_cv_type_getgroups=int ;; #( + int:no,gid:yes) : + ac_cv_type_getgroups=gid_t ;; #( + int:yes,gid:yes) : + + # Both programs compiled - this means *either* that getgroups + # was declared with no prototype, in which case we should use int, + # or that it was declared prototyped but gid_t is a typedef for int, + # in which case we should use gid_t. Distinguish the two cases + # by testing if the compiler catches a blatantly incorrect function + # signature for getgroups. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ -#include - +$ac_includes_default +extern int getgroups(int, float); +int +main (void) +{ +return !(getgroups(0, 0) >= 0); + ; + return 0; +} _ACEOF -if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | - $EGREP "getgroups.*int.*gid_t" >/dev/null 2>&1 +if ac_fn_c_try_compile "$LINENO" then : - ac_cv_type_getgroups=gid_t -else $as_nop - ac_cv_type_getgroups=int + + # Compiler did not catch incorrect argument list; + # getgroups is unprototyped. + ac_cv_type_getgroups=int + +else case e in #( + e) + # Compiler caught incorrect argument list; + # gid_t is a typedef for int. + ac_cv_type_getgroups=gid_t + ;; +esac fi -rm -rf conftest* +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext + ;; #( + *) : + # Both programs failed to compile - this probably means getgroups + # wasn't declared at all. Use 'int', as this is probably a very + # old system where the type _would have been_ int. + ac_cv_type_getgroups=int + ;; +esac + ;; +esac fi + ;; +esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_type_getgroups" >&5 printf "%s\n" "$ac_cv_type_getgroups" >&6; } - printf "%s\n" "#define GETGROUPS_T $ac_cv_type_getgroups" >>confdefs.h @@ -5094,8 +5074,8 @@ cat >confcache <<\_ACEOF # config.status only pays attention to the cache file if you give it # the --recheck option to rerun configure. # -# `ac_cv_env_foo' variables (set or unset) will be overridden when -# loading this file, other *unset* `ac_cv_foo' will be assigned the +# 'ac_cv_env_foo' variables (set or unset) will be overridden when +# loading this file, other *unset* 'ac_cv_foo' will be assigned the # following values. _ACEOF @@ -5125,14 +5105,14 @@ printf "%s\n" "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} (set) 2>&1 | case $as_nl`(ac_space=' '; set) 2>&1` in #( *${as_nl}ac_space=\ *) - # `set' does not quote correctly, so add quotes: double-quote + # 'set' does not quote correctly, so add quotes: double-quote # substitution turns \\\\ into \\, and sed turns \\ into \. sed -n \ "s/'/'\\\\''/g; s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p" ;; #( *) - # `set' quotes correctly as required by POSIX, so do not add quotes. + # 'set' quotes correctly as required by POSIX, so do not add quotes. sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" ;; esac | @@ -5196,9 +5176,7 @@ s/^[ ]*#[ ]*define[ ][ ]*\([^ ][^ ]*\)[ ]*\(.*\)/-D\1=\2/g t quote b any :quote -s/[ `~#$^&*(){}\\|;'\''"<>?]/\\&/g -s/\[/\\&/g -s/\]/\\&/g +s/[][ `~#$^&*(){}\\|;'\''"<>?]/\\&/g s/\$/$$/g H :any @@ -5229,6 +5207,10 @@ LIBOBJS=$ac_libobjs LTLIBOBJS=$ac_ltlibobjs +if test -z "${HAVE_NNTP_TRUE}" && test -z "${HAVE_NNTP_FALSE}"; then + as_fn_error $? "conditional \"HAVE_NNTP\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi : "${CONFIG_STATUS=./config.status}" ac_write_fail=0 @@ -5258,7 +5240,6 @@ cat >>$CONFIG_STATUS <<\_ASEOF || as_write_fail=1 # Be more Bourne compatible DUALCASE=1; export DUALCASE # for MKS sh -as_nop=: if test ${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 then : emulate sh @@ -5267,12 +5248,13 @@ then : # is contrary to our usage. Disable this feature. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST -else $as_nop - case `(set -o) 2>/dev/null` in #( +else case e in #( + e) case `(set -o) 2>/dev/null` in #( *posix*) : set -o posix ;; #( *) : ;; +esac ;; esac fi @@ -5344,7 +5326,7 @@ IFS=$as_save_IFS ;; esac -# We did not find ourselves, most probably we were run as `sh COMMAND' +# We did not find ourselves, most probably we were run as 'sh COMMAND' # in which case we are not to be found in the path. if test "x$as_myself" = x; then as_myself=$0 @@ -5373,7 +5355,6 @@ as_fn_error () } # as_fn_error - # as_fn_set_status STATUS # ----------------------- # Set $? to STATUS, without forking. @@ -5413,11 +5394,12 @@ then : { eval $1+=\$2 }' -else $as_nop - as_fn_append () +else case e in #( + e) as_fn_append () { eval $1=\$$1\$2 - } + } ;; +esac fi # as_fn_append # as_fn_arith ARG... @@ -5431,11 +5413,12 @@ then : { as_val=$(( $* )) }' -else $as_nop - as_fn_arith () +else case e in #( + e) as_fn_arith () { as_val=`expr "$@" || test $? -eq 1` - } + } ;; +esac fi # as_fn_arith @@ -5518,9 +5501,9 @@ if (echo >conf$$.file) 2>/dev/null; then if ln -s conf$$.file conf$$ 2>/dev/null; then as_ln_s='ln -s' # ... but there are two gotchas: - # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. - # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. - # In both cases, we have to default to `cp -pR'. + # 1) On MSYS, both 'ln -s file dir' and 'ln file dir' fail. + # 2) DJGPP < 2.04 has no symlinks; 'ln -s' creates a wrapper executable. + # In both cases, we have to default to 'cp -pR'. ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || as_ln_s='cp -pR' elif ln conf$$.file conf$$ 2>/dev/null; then @@ -5601,10 +5584,12 @@ as_test_x='test -x' as_executable_p=as_fn_executable_p # Sed expression to map a string onto a valid CPP name. -as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" +as_sed_cpp="y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g" +as_tr_cpp="eval sed '$as_sed_cpp'" # deprecated # Sed expression to map a string onto a valid variable name. -as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" +as_sed_sh="y%*+%pp%;s%[^_$as_cr_alnum]%_%g" +as_tr_sh="eval sed '$as_sed_sh'" # deprecated exec 6>&1 @@ -5620,7 +5605,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # values after options handling. ac_log=" This file was extended by $as_me, which was -generated by GNU Autoconf 2.71. Invocation command line was +generated by GNU Autoconf 2.72. Invocation command line was CONFIG_FILES = $CONFIG_FILES CONFIG_HEADERS = $CONFIG_HEADERS @@ -5648,7 +5633,7 @@ _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 ac_cs_usage="\ -\`$as_me' instantiates files and other configuration actions +'$as_me' instantiates files and other configuration actions from templates according to the current configuration. Unless the files and actions are specified as TAGs, all are instantiated by default. @@ -5679,10 +5664,10 @@ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config='$ac_cs_config_escaped' ac_cs_version="\\ config.status -configured by $0, generated by GNU Autoconf 2.71, +configured by $0, generated by GNU Autoconf 2.72, with options \\"\$ac_cs_config\\" -Copyright (C) 2021 Free Software Foundation, Inc. +Copyright (C) 2023 Free Software Foundation, Inc. This config.status script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it." @@ -5740,8 +5725,8 @@ do ac_cs_silent=: ;; # This is an error. - -*) as_fn_error $? "unrecognized option: \`$1' -Try \`$0 --help' for more information." ;; + -*) as_fn_error $? "unrecognized option: '$1' +Try '$0 --help' for more information." ;; *) as_fn_append ac_config_targets " $1" ac_need_defaults=false ;; @@ -5818,7 +5803,7 @@ do "$SCRIPTS") CONFIG_FILES="$CONFIG_FILES $SCRIPTS" ;; "default") CONFIG_COMMANDS="$CONFIG_COMMANDS default" ;; - *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;; + *) as_fn_error $? "invalid argument: '$ac_config_target'" "$LINENO" 5;; esac done @@ -5837,7 +5822,7 @@ fi # creating and moving files from /tmp can sometimes cause problems. # Hook for its removal unless debugging. # Note that there is a small window in which the directory will not be cleaned: -# after its creation but before its name has been assigned to `$tmp'. +# after its creation but before its name has been assigned to '$tmp'. $debug || { tmp= ac_tmp= @@ -5861,7 +5846,7 @@ ac_tmp=$tmp # Set up the scripts for CONFIG_FILES section. # No need to generate them if there are no CONFIG_FILES. -# This happens for instance with `./config.status config.h'. +# This happens for instance with './config.status config.h'. if test -n "$CONFIG_FILES"; then @@ -6027,7 +6012,7 @@ do esac case $ac_mode$ac_tag in :[FHL]*:*);; - :L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5;; + :L* | :C*:*) as_fn_error $? "invalid tag '$ac_tag'" "$LINENO" 5;; :[FH]-) ac_tag=-:-;; :[FH]*) ac_tag=$ac_tag:$ac_tag.in;; esac @@ -6049,19 +6034,19 @@ do -) ac_f="$ac_tmp/stdin";; *) # Look for the file first in the build tree, then in the source tree # (if the path is not absolute). The absolute path cannot be DOS-style, - # because $ac_f cannot contain `:'. + # because $ac_f cannot contain ':'. test -f "$ac_f" || case $ac_f in [\\/$]*) false;; *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";; esac || - as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5;; + as_fn_error 1 "cannot find input file: '$ac_f'" "$LINENO" 5;; esac case $ac_f in *\'*) ac_f=`printf "%s\n" "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac as_fn_append ac_file_inputs " '$ac_f'" done - # Let's still pretend it is `configure' which instantiates (i.e., don't + # Let's still pretend it is 'configure' which instantiates (i.e., don't # use $as_me), people would be surprised to read: # /* config.h. Generated by config.status. */ configure_input='Generated from '` @@ -6189,7 +6174,7 @@ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 esac _ACEOF -# Neutralize VPATH when `$srcdir' = `.'. +# Neutralize VPATH when '$srcdir' = '.'. # Shell code in configure.ac might set extrasub. # FIXME: do we really want to maintain this feature? cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 @@ -6219,9 +6204,9 @@ test -z "$ac_datarootdir_hack$ac_datarootdir_seen" && { ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } && { ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' \ "$ac_tmp/out"`; test -z "$ac_out"; } && - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir' + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable 'datarootdir' which seems to be undefined. Please make sure it is defined" >&5 -printf "%s\n" "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir' +printf "%s\n" "$as_me: WARNING: $ac_file contains a reference to the variable 'datarootdir' which seems to be undefined. Please make sure it is defined" >&2;} rm -f "$ac_tmp/stdin" diff --git a/configure.in b/configure.ac similarity index 89% rename from configure.in rename to configure.ac index e617e6d1..944e3163 100644 --- a/configure.in +++ b/configure.ac @@ -48,14 +48,38 @@ AC_MSG_CHECKING(Python interpreter) if test ! -x $with_python then AC_MSG_ERROR([ - -***** No Python interpreter found! -***** Try including the configure option -***** --with-python=/path/to/python/interpreter]) + Python interpreter not found at $with_python + Please specify the correct path to Python using --with-python + ]) +fi +AC_MSG_RESULT($with_python) + +# Check for optional nntplib module +AC_MSG_CHECKING(whether to enable NNTP support) +AC_ARG_ENABLE(nntp, + [ --enable-nntp enable NNTP support (requires python3-nntplib)], + [enable_nntp=$enableval], + [enable_nntp=no] +) +AC_MSG_RESULT($enable_nntp) + +if test "$enable_nntp" = "yes"; then + AC_MSG_CHECKING(for Python nntplib module) + $with_python -c "import nntplib" >/dev/null 2>&1 + if test $? -ne 0 + then + AC_MSG_ERROR([ + Python nntplib module not found but NNTP support was requested + Please install python3-nntplib package + On Debian/Ubuntu: apt-get install python3-nntplib + On RHEL/CentOS: yum install python3-nntplib + Or disable NNTP support with --disable-nntp + ]) + fi + AC_MSG_RESULT(found) + AC_DEFINE(HAVE_NNTP, 1, [Define if NNTP support is enabled]) fi -AC_SUBST(PYTHON) -PYTHON=$with_python -AC_MSG_RESULT($PYTHON) +AM_CONDITIONAL([HAVE_NNTP], [test "$enable_nntp" = "yes"]) # See if Python is new enough. AC_MSG_CHECKING(Python version) @@ -66,24 +90,23 @@ try: v = sys.hexversion except AttributeError: v = 0 -if v >= 0x2040000: +if v >= 0x3000000: s = sys.version.split()[0] else: s = "" -fp = open("conftest.out", "w") -fp.write("%s\n" % s) -fp.close() +with open("conftest.out", "w") as fp: + fp.write("%s\n" % s) EOF changequote([, ]) -$PYTHON conftest.py +$with_python conftest.py version=`cat conftest.out` rm -f conftest.out conftest.py if test -z "$version" then AC_MSG_ERROR([ -***** $PYTHON is too old (or broken) -***** Python 2.4 or newer is required]) +***** $with_python is too old (or broken) +***** Python 3.0 or newer is required]) fi AC_MSG_RESULT($version) @@ -96,12 +119,11 @@ try: res = 'ok' except ImportError: res = 'no' -fp = open("conftest.out", "w") -fp.write("%s\n" % res) -fp.close() +with open("conftest.out", "w") as fp: + fp.write("%s\n" % res) EOF changequote([, ]) -$PYTHON conftest.py +$with_python conftest.py havednspython=`cat conftest.out` rm -f conftest.out conftest.py if test "$havednspython" = "no" @@ -127,22 +149,20 @@ except ImportError: res = "not ok" else: res = "ok" -fp = open("conftest.out", "w") -fp.write("%s\n" % res) -fp.close() +with open("conftest.out", "w") as fp: + fp.write("%s\n" % res) EOF changequote([, ]) -$PYTHON conftest.py +$with_python conftest.py needemailpkg=`cat conftest.out` rm -f conftest.out conftest.py cat > getver.py < conftest.py < Date: Thu, 24 Apr 2025 10:43:41 -0400 Subject: [PATCH 021/748] update --- configure | 207 ++++++++++++++------------------------------------- configure.ac | 31 ++++---- 2 files changed, 69 insertions(+), 169 deletions(-) diff --git a/configure b/configure index aeb4bf4c..88cb72a5 100755 --- a/configure +++ b/configure @@ -182,8 +182,7 @@ test -x / || exit 1" as_suggested=" as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" && - test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1 -test \$(( 1 + 1 )) = 2 || exit 1" + test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1" if (eval "$as_required") 2>/dev/null then : as_have_required=yes @@ -644,8 +643,6 @@ ac_header_c_list= ac_subst_vars='LTLIBOBJS LIBOBJS SCRIPTS -EGREP -GREP URLHOST MAILHOST CGIEXT @@ -673,6 +670,7 @@ EMAILPKG MM_VERSION HAVE_NNTP_FALSE HAVE_NNTP_TRUE +PYTHON with_python BUILD_DATE CONFIGURE_OPTS @@ -2583,6 +2581,10 @@ fi fi +# Set PYTHON variable for Makefile substitution +PYTHON=$with_python + + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking Python interpreter" >&5 printf %s "checking Python interpreter... " >&6; } if test ! -x $with_python @@ -4211,8 +4213,8 @@ printf "%s\n" "$USERNAME" >&6; } # User `mailman' must exist -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for user name \"$USERNAME\"" >&5 -printf %s "checking for user name \"$USERNAME\"... " >&6; } +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for user name $USERNAME" >&5 +printf %s "checking for user name $USERNAME... " >&6; } # MAILMAN_USER == variable name # $USERNAME == user id to check for @@ -4246,8 +4248,8 @@ then if test "$with_permcheck" = "yes" then as_fn_error $? " -***** No \"$USERNAME\" user found! -***** Your system must have a \"$USERNAME\" user defined +***** No $USERNAME user found! +***** Your system must have a $USERNAME user defined ***** (usually in your /etc/passwd file). Please see the INSTALL ***** file for details." "$LINENO" 5 fi @@ -4278,8 +4280,8 @@ printf "%s\n" "$GROUPNAME" >&6; } # Target group must exist -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for group name \"$GROUPNAME\"" >&5 -printf %s "checking for group name \"$GROUPNAME\"... " >&6; } +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for group name $GROUPNAME" >&5 +printf %s "checking for group name $GROUPNAME... " >&6; } # MAILMAN_GROUP == variable name # $GROUPNAME == user id to check for @@ -4313,8 +4315,8 @@ then if test "$with_permcheck" = "yes" then as_fn_error $? " -***** No \"$GROUPNAME\" group found! -***** Your system must have a \"$GROUPNAME\" group defined +***** No $GROUPNAME group found! +***** Your system must have a $GROUPNAME group defined ***** (usually in your /etc/group file). Please see the INSTALL ***** file for details." "$LINENO" 5 fi @@ -4425,7 +4427,7 @@ then if test "$with_permcheck" = "yes" then as_fn_error $? " -***** No group name \"$with_mail_gid\" found for the mail wrapper program. +***** No group name $with_mail_gid found for the mail wrapper program. ***** This is the group that your mail server will use to run Mailman's ***** programs. You should specify an existing group with the ***** --with-mail-gid configure option, or use --without-permcheck to @@ -4486,7 +4488,7 @@ then if test "$with_permcheck" = "yes" then as_fn_error $? " -***** No group name \"$with_cgi_gid\" found for the CGI wrapper program. +***** No group name $with_cgi_gid found for the CGI wrapper program. ***** This is the group that your web server will use to run Mailman's ***** programs. You should specify an existing group with the ***** --with-cgi-gid configure option, or use --without-permcheck to @@ -4654,8 +4656,6 @@ rm -f core conftest.err conftest.$ac_objext conftest.beam \ fi # Checks for header files. -# Autoupdate added the next two lines to ensure that your configure -# script's behavior did not change. They are probably safe to remove. ac_header= ac_cache= for ac_item in $ac_header_c_list do @@ -4685,155 +4685,60 @@ then : printf "%s\n" "#define STDC_HEADERS 1" >>confdefs.h fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for grep that handles long lines and -e" >&5 -printf %s "checking for grep that handles long lines and -e... " >&6; } -if test ${ac_cv_path_GREP+y} +ac_fn_c_check_header_compile "$LINENO" "stdio.h" "ac_cv_header_stdio_h" "$ac_includes_default" +if test "x$ac_cv_header_stdio_h" = xyes then : - printf %s "(cached) " >&6 -else case e in #( - e) if test -z "$GREP"; then - ac_path_GREP_found=false - # Loop through the user's path and test for each of PROGNAME-LIST - as_save_IFS=$IFS; IFS=$PATH_SEPARATOR -for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin -do - IFS=$as_save_IFS - case $as_dir in #((( - '') as_dir=./ ;; - */) ;; - *) as_dir=$as_dir/ ;; - esac - for ac_prog in grep ggrep - do - for ac_exec_ext in '' $ac_executable_extensions; do - ac_path_GREP="$as_dir$ac_prog$ac_exec_ext" - as_fn_executable_p "$ac_path_GREP" || continue -# Check for GNU ac_path_GREP and select it if it is found. - # Check for GNU $ac_path_GREP -case `"$ac_path_GREP" --version 2>&1` in #( -*GNU*) - ac_cv_path_GREP="$ac_path_GREP" ac_path_GREP_found=:;; -#( -*) - ac_count=0 - printf %s 0123456789 >"conftest.in" - while : - do - cat "conftest.in" "conftest.in" >"conftest.tmp" - mv "conftest.tmp" "conftest.in" - cp "conftest.in" "conftest.nl" - printf "%s\n" 'GREP' >> "conftest.nl" - "$ac_path_GREP" -e 'GREP$' -e '-(cannot match)-' < "conftest.nl" >"conftest.out" 2>/dev/null || break - diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break - as_fn_arith $ac_count + 1 && ac_count=$as_val - if test $ac_count -gt ${ac_path_GREP_max-0}; then - # Best one so far, save it but keep looking for a better one - ac_cv_path_GREP="$ac_path_GREP" - ac_path_GREP_max=$ac_count - fi - # 10*(2^10) chars as input seems more than enough - test $ac_count -gt 10 && break - done - rm -f conftest.in conftest.tmp conftest.nl conftest.out;; -esac + printf "%s\n" "#define HAVE_STDIO_H 1" >>confdefs.h - $ac_path_GREP_found && break 3 - done - done - done -IFS=$as_save_IFS - if test -z "$ac_cv_path_GREP"; then - as_fn_error $? "no acceptable grep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 - fi -else - ac_cv_path_GREP=$GREP fi - ;; -esac +ac_fn_c_check_header_compile "$LINENO" "stdlib.h" "ac_cv_header_stdlib_h" "$ac_includes_default" +if test "x$ac_cv_header_stdlib_h" = xyes +then : + printf "%s\n" "#define HAVE_STDLIB_H 1" >>confdefs.h + fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_GREP" >&5 -printf "%s\n" "$ac_cv_path_GREP" >&6; } - GREP="$ac_cv_path_GREP" +ac_fn_c_check_header_compile "$LINENO" "string.h" "ac_cv_header_string_h" "$ac_includes_default" +if test "x$ac_cv_header_string_h" = xyes +then : + printf "%s\n" "#define HAVE_STRING_H 1" >>confdefs.h +fi +ac_fn_c_check_header_compile "$LINENO" "inttypes.h" "ac_cv_header_inttypes_h" "$ac_includes_default" +if test "x$ac_cv_header_inttypes_h" = xyes +then : + printf "%s\n" "#define HAVE_INTTYPES_H 1" >>confdefs.h -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for egrep" >&5 -printf %s "checking for egrep... " >&6; } -if test ${ac_cv_path_EGREP+y} +fi +ac_fn_c_check_header_compile "$LINENO" "stdint.h" "ac_cv_header_stdint_h" "$ac_includes_default" +if test "x$ac_cv_header_stdint_h" = xyes then : - printf %s "(cached) " >&6 -else case e in #( - e) if echo a | $GREP -E '(a|b)' >/dev/null 2>&1 - then ac_cv_path_EGREP="$GREP -E" - else - if test -z "$EGREP"; then - ac_path_EGREP_found=false - # Loop through the user's path and test for each of PROGNAME-LIST - as_save_IFS=$IFS; IFS=$PATH_SEPARATOR -for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin -do - IFS=$as_save_IFS - case $as_dir in #((( - '') as_dir=./ ;; - */) ;; - *) as_dir=$as_dir/ ;; - esac - for ac_prog in egrep - do - for ac_exec_ext in '' $ac_executable_extensions; do - ac_path_EGREP="$as_dir$ac_prog$ac_exec_ext" - as_fn_executable_p "$ac_path_EGREP" || continue -# Check for GNU ac_path_EGREP and select it if it is found. - # Check for GNU $ac_path_EGREP -case `"$ac_path_EGREP" --version 2>&1` in #( -*GNU*) - ac_cv_path_EGREP="$ac_path_EGREP" ac_path_EGREP_found=:;; -#( -*) - ac_count=0 - printf %s 0123456789 >"conftest.in" - while : - do - cat "conftest.in" "conftest.in" >"conftest.tmp" - mv "conftest.tmp" "conftest.in" - cp "conftest.in" "conftest.nl" - printf "%s\n" 'EGREP' >> "conftest.nl" - "$ac_path_EGREP" 'EGREP$' < "conftest.nl" >"conftest.out" 2>/dev/null || break - diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break - as_fn_arith $ac_count + 1 && ac_count=$as_val - if test $ac_count -gt ${ac_path_EGREP_max-0}; then - # Best one so far, save it but keep looking for a better one - ac_cv_path_EGREP="$ac_path_EGREP" - ac_path_EGREP_max=$ac_count - fi - # 10*(2^10) chars as input seems more than enough - test $ac_count -gt 10 && break - done - rm -f conftest.in conftest.tmp conftest.nl conftest.out;; -esac + printf "%s\n" "#define HAVE_STDINT_H 1" >>confdefs.h - $ac_path_EGREP_found && break 3 - done - done - done -IFS=$as_save_IFS - if test -z "$ac_cv_path_EGREP"; then - as_fn_error $? "no acceptable egrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 - fi -else - ac_cv_path_EGREP=$EGREP fi +ac_fn_c_check_header_compile "$LINENO" "strings.h" "ac_cv_header_strings_h" "$ac_includes_default" +if test "x$ac_cv_header_strings_h" = xyes +then : + printf "%s\n" "#define HAVE_STRINGS_H 1" >>confdefs.h - fi ;; -esac fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_EGREP" >&5 -printf "%s\n" "$ac_cv_path_EGREP" >&6; } - EGREP="$ac_cv_path_EGREP" +ac_fn_c_check_header_compile "$LINENO" "sys/stat.h" "ac_cv_header_sys_stat_h" "$ac_includes_default" +if test "x$ac_cv_header_sys_stat_h" = xyes +then : + printf "%s\n" "#define HAVE_SYS_STAT_H 1" >>confdefs.h - EGREP_TRADITIONAL=$EGREP - ac_cv_path_EGREP_TRADITIONAL=$EGREP +fi +ac_fn_c_check_header_compile "$LINENO" "sys/types.h" "ac_cv_header_sys_types_h" "$ac_includes_default" +if test "x$ac_cv_header_sys_types_h" = xyes +then : + printf "%s\n" "#define HAVE_SYS_TYPES_H 1" >>confdefs.h +fi +ac_fn_c_check_header_compile "$LINENO" "unistd.h" "ac_cv_header_unistd_h" "$ac_includes_default" +if test "x$ac_cv_header_unistd_h" = xyes +then : + printf "%s\n" "#define HAVE_UNISTD_H 1" >>confdefs.h +fi ac_fn_c_check_header_compile "$LINENO" "syslog.h" "ac_cv_header_syslog_h" "$ac_includes_default" if test "x$ac_cv_header_syslog_h" = xyes then : diff --git a/configure.ac b/configure.ac index 944e3163..ac99de88 100644 --- a/configure.ac +++ b/configure.ac @@ -44,6 +44,10 @@ then AC_PATH_PROG(with_python, python3, /usr/local/bin/python3) fi +# Set PYTHON variable for Makefile substitution +PYTHON=$with_python +AC_SUBST(PYTHON) + AC_MSG_CHECKING(Python interpreter) if test ! -x $with_python then @@ -437,15 +441,15 @@ AC_MSG_RESULT($USERNAME) # User `mailman' must exist AC_SUBST(MAILMAN_USER) -AC_MSG_CHECKING(for user name \"$USERNAME\") +AC_MSG_CHECKING(for user name $USERNAME) MM_FIND_USER_NAME(MAILMAN_USER, $USERNAME) if test -z "$MAILMAN_USER" then if test "$with_permcheck" = "yes" then AC_MSG_ERROR([ -***** No \"$USERNAME\" user found! -***** Your system must have a \"$USERNAME\" user defined +***** No $USERNAME user found! +***** Your system must have a $USERNAME user defined ***** (usually in your /etc/passwd file). Please see the INSTALL ***** file for details.]) fi @@ -468,15 +472,15 @@ AC_MSG_RESULT($GROUPNAME) # Target group must exist AC_SUBST(MAILMAN_GROUP) -AC_MSG_CHECKING(for group name \"$GROUPNAME\") +AC_MSG_CHECKING(for group name $GROUPNAME) MM_FIND_GROUP_NAME(MAILMAN_GROUP, $GROUPNAME) if test -z "$MAILMAN_GROUP" then if test "$with_permcheck" = "yes" then AC_MSG_ERROR([ -***** No \"$GROUPNAME\" group found! -***** Your system must have a \"$GROUPNAME\" group defined +***** No $GROUPNAME group found! +***** Your system must have a $GROUPNAME group defined ***** (usually in your /etc/group file). Please see the INSTALL ***** file for details.]) fi @@ -551,7 +555,7 @@ then if test "$with_permcheck" = "yes" then AC_MSG_ERROR([ -***** No group name \"$with_mail_gid\" found for the mail wrapper program. +***** No group name $with_mail_gid found for the mail wrapper program. ***** This is the group that your mail server will use to run Mailman's ***** programs. You should specify an existing group with the ***** --with-mail-gid configure option, or use --without-permcheck to @@ -578,7 +582,7 @@ then if test "$with_permcheck" = "yes" then AC_MSG_ERROR([ -***** No group name \"$with_cgi_gid\" found for the CGI wrapper program. +***** No group name $with_cgi_gid found for the CGI wrapper program. ***** This is the group that your web server will use to run Mailman's ***** programs. You should specify an existing group with the ***** --with-cgi-gid configure option, or use --without-permcheck to @@ -678,16 +682,7 @@ if test $ac_cv_func_syslog = no; then fi # Checks for header files. -m4_warn([obsolete], -[The preprocessor macro `STDC_HEADERS' is obsolete. - Except in unusual embedded environments, you can safely include all - ISO C90 headers unconditionally.])dnl -# Autoupdate added the next two lines to ensure that your configure -# script's behavior did not change. They are probably safe to remove. -AC_CHECK_INCLUDES_DEFAULT -AC_PROG_EGREP - -AC_CHECK_HEADERS(syslog.h) +AC_CHECK_HEADERS([stdio.h stdlib.h string.h inttypes.h stdint.h strings.h sys/stat.h sys/types.h unistd.h syslog.h]) # Checks for typedefs, structures, and compiler characteristics. AC_TYPE_UID_T From e88c5279627295ce3c6a94c9f8b12e668527fa21 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 24 Apr 2025 10:49:33 -0400 Subject: [PATCH 022/748] update string handling --- Mailman/Cgi/admindb.py | 9 +++++++++ Mailman/HTMLFormatter.py | 10 ++++++++++ Mailman/Logging/Logger.py | 9 +++++++++ Mailman/Logging/Syslog.py | 10 ++++++++++ Mailman/Utils.py | 33 +++++++++++++++------------------ 5 files changed, 53 insertions(+), 18 deletions(-) diff --git a/Mailman/Cgi/admindb.py b/Mailman/Cgi/admindb.py index 1b949079..37447f15 100644 --- a/Mailman/Cgi/admindb.py +++ b/Mailman/Cgi/admindb.py @@ -699,3 +699,12 @@ def process_form(mlist, doc, cgidata): show_pending_subs(mlist, doc) show_pending_unsubs(mlist, doc) show_helds_overview(mlist, doc) + + +def format_body(body, mcset, lcset): + """Format the message body for display.""" + if isinstance(body, bytes): + body = body.decode(mcset, 'replace') + elif not isinstance(body, str): + body = str(body) + return body.encode(lcset, 'replace') diff --git a/Mailman/HTMLFormatter.py b/Mailman/HTMLFormatter.py index 5a1bc3af..1a58e8d3 100644 --- a/Mailman/HTMLFormatter.py +++ b/Mailman/HTMLFormatter.py @@ -461,3 +461,13 @@ def GetLangSelectBox(self, lang=None, varname='language'): selected = mm_cfg.DEFAULT_SERVER_LANGUAGE # Return the widget return SelectOptions(varname, values, legend, selected) + + def format(self, value, charset=None): + """Format a value for HTML output.""" + if charset is None: + charset = self.charset + if isinstance(value, bytes): + value = value.decode(charset, 'replace') + elif not isinstance(value, str): + value = str(value) + return html.escape(value, quote=True) diff --git a/Mailman/Logging/Logger.py b/Mailman/Logging/Logger.py index b1bc685c..af3c4c6d 100644 --- a/Mailman/Logging/Logger.py +++ b/Mailman/Logging/Logger.py @@ -26,6 +26,7 @@ import sys import os import codecs +import logging from Mailman import mm_cfg from Mailman.Logging.Utils import _logexc @@ -106,3 +107,11 @@ def close(self): return self.__get_f().close() self.__fp = None + + def log(self, msg, level=logging.INFO): + """Log a message at the specified level.""" + if isinstance(msg, bytes): + msg = msg.decode(self.__encoding, 'replace') + elif not isinstance(msg, str): + msg = str(msg) + self.logger.log(level, msg) diff --git a/Mailman/Logging/Syslog.py b/Mailman/Logging/Syslog.py index a21bd14c..98ebd11c 100644 --- a/Mailman/Logging/Syslog.py +++ b/Mailman/Logging/Syslog.py @@ -79,3 +79,13 @@ def close(self): syslog = _Syslog() + +def syslog(ident, msg, *args): + """Log a message to syslog.""" + if isinstance(msg, bytes): + msg = msg.decode('iso-8859-1', 'replace') + elif not isinstance(msg, str): + msg = str(msg) + if args: + msg = msg % args + syslog.syslog(ident, msg) diff --git a/Mailman/Utils.py b/Mailman/Utils.py index 6ebada69..152589f2 100644 --- a/Mailman/Utils.py +++ b/Mailman/Utils.py @@ -498,11 +498,9 @@ def websafe(s, doubleescape=False): if doubleescape: return html.escape(s, quote=True) else: - if type(s) is bytes: + if isinstance(s, bytes): s = s.decode(errors='ignore') - re.sub('&', '&', s) - # Don't double escape html entities - #return _ampre.sub(r'&\1', html.escape(s, quote=True)) + s = re.sub('&', '&', s) return html.escape(s, quote=True) @@ -1205,21 +1203,17 @@ def suspiciousHTML(html): s_dict = {} def get_suffixes(url): - """This loads and parses the data from the url argument into s_dict for - use by get_org_dom.""" - global s_dict - if s_dict: - return - if not url: - return + """Get the list of public suffixes from the given URL.""" try: d = urllib.request.urlopen(url) - except urllib.error.URLError as e: - syslog('error', - 'Unable to retrieve data from %s: %s', + except (urllib.error.URLError, urllib.error.HTTPError) as e: + syslog('error', 'Failed to fetch DMARC organizational domain data from %s: %s', url, e) return for line in d.readlines(): + # Convert bytes to string if necessary + if isinstance(line, bytes): + line = line.decode('utf-8') if not line.strip() or line.startswith(' ') or line.startswith('//'): continue line = re.sub(' .*', '', line.strip()) @@ -1506,7 +1500,7 @@ def xml_to_unicode(s, cset): similar to canonstr above except for replacing invalid refs with the unicode replace character and recognizing \\u escapes. """ - if isinstance(s, str): + if isinstance(s, bytes): us = s.decode(cset, 'replace') us = re.sub(u'&(#[0-9]+);', _invert_xml, us) us = re.sub(u'(?i)\\\\(u[a-f0-9]{4})', _invert_xml, us) @@ -1519,12 +1513,15 @@ def banned_ip(ip): return False if have_ipaddress: try: - uip = str(ip, encoding='us-ascii', errors='replace') - ptr = ipaddress.ip_address(uip).reverse_pointer + if isinstance(ip, bytes): + ip = ip.decode('us-ascii', errors='replace') + ptr = ipaddress.ip_address(ip).reverse_pointer except ValueError: return False lookup = '{0}.zen.spamhaus.org'.format('.'.join(ptr.split('.')[:-2])) else: + if isinstance(ip, bytes): + ip = ip.decode('us-ascii', errors='replace') parts = ip.split('.') if len(parts) != 4: return False @@ -1578,7 +1575,7 @@ def captcha_display(mlist, lang, captchas): box_html = mlist.FormatBox('captcha_answer', size=30) # Remember to encode the language in the index so that we can get it out # again! - return (websafe(question), box_html, lang + "-" + str(idx)) + return (websafe(question), box_html, '{}-{}'.format(lang, idx)) def captcha_verify(idx, given_answer, captchas): try: From feb0ed0ad9db50b3bbee088d4420d5778705213c Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 24 Apr 2025 10:53:29 -0400 Subject: [PATCH 023/748] update --- Mailman/Logging/Syslog.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/Mailman/Logging/Syslog.py b/Mailman/Logging/Syslog.py index 98ebd11c..65962d9a 100644 --- a/Mailman/Logging/Syslog.py +++ b/Mailman/Logging/Syslog.py @@ -26,12 +26,10 @@ from Mailman.Logging.StampedLogger import StampedLogger - # Global, shared logger instance. All clients should use this object. -syslog = None +_syslog = None - # Don't instantiate except below. class _Syslog(object): def __init__(self): @@ -77,8 +75,15 @@ def close(self): logger.close() self._logfiles.clear() + def syslog(self, ident, msg): + """Log a message to syslog.""" + if isinstance(msg, bytes): + msg = msg.decode('iso-8859-1', 'replace') + elif not isinstance(msg, str): + msg = str(msg) + self.write(ident, msg) -syslog = _Syslog() +_syslog = _Syslog() def syslog(ident, msg, *args): """Log a message to syslog.""" @@ -88,4 +93,4 @@ def syslog(ident, msg, *args): msg = str(msg) if args: msg = msg % args - syslog.syslog(ident, msg) + _syslog.syslog(ident, msg) From f1b45f60e89f53c457b32cbaf666edb1c76bebe1 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 24 Apr 2025 10:58:52 -0400 Subject: [PATCH 024/748] cleanup --- bin/config_list | 91 +++++++++++++++++++++++++++++++++++++++---------- bin/mailmanctl | 4 ++- 2 files changed, 76 insertions(+), 19 deletions(-) diff --git a/bin/config_list b/bin/config_list index fedc0cc6..11b1c0fd 100644 --- a/bin/config_list +++ b/bin/config_list @@ -357,27 +357,82 @@ def main(): fp = open(args.output_file, 'w') except IOError: usage(1, _('Cannot open file: %(file)s')) - for key, value in mlist.items(): - if not args.all and key.startswith('_'): - continue - if args.category and not key.startswith(args.category + '_'): - continue - if args.subcategory and not key.startswith(args.category + '_' + args.subcategory + '_'): - continue - print(f"{key}={value}", file=fp) + # Get configuration items using GetConfigInfo() + for category in mm_cfg.ADMIN_CATEGORIES: + subcats = mlist.GetConfigSubCategories(category) + if subcats is None: + info = mlist.GetConfigInfo(category, None) + if info: + for data in info[1:]: + if not isinstance(data, Tuple): + continue + key = data[0] + if not args.all and key.startswith('_'): + continue + if args.category and not key.startswith(args.category + '_'): + continue + if args.subcategory and not key.startswith(args.category + '_' + args.subcategory + '_'): + continue + value = getattr(mlist, key) + print(f"{key}={value}", file=fp) + else: + for subcat, _ in subcats: + info = mlist.GetConfigInfo(category, subcat) + if info: + for data in info[1:]: + if not isinstance(data, Tuple): + continue + key = data[0] + if not args.all and key.startswith('_'): + continue + if args.category and not key.startswith(args.category + '_'): + continue + if args.subcategory and not key.startswith(args.category + '_' + args.subcategory + '_'): + continue + value = getattr(mlist, key) + print(f"{key}={value}", file=fp) fp.close() else: - for key, value in mlist.items(): - if not args.all and key.startswith('_'): - continue - if args.category and not key.startswith(args.category + '_'): - continue - if args.subcategory and not key.startswith(args.category + '_' + args.subcategory + '_'): - continue - if args.verbose: - print(f"{key}={value}") + # Get configuration items using GetConfigInfo() + for category in mm_cfg.ADMIN_CATEGORIES: + subcats = mlist.GetConfigSubCategories(category) + if subcats is None: + info = mlist.GetConfigInfo(category, None) + if info: + for data in info[1:]: + if not isinstance(data, Tuple): + continue + key = data[0] + if not args.all and key.startswith('_'): + continue + if args.category and not key.startswith(args.category + '_'): + continue + if args.subcategory and not key.startswith(args.category + '_' + args.subcategory + '_'): + continue + value = getattr(mlist, key) + if args.verbose: + print(f"{key}={value}") + else: + print(key) else: - print(key) + for subcat, _ in subcats: + info = mlist.GetConfigInfo(category, subcat) + if info: + for data in info[1:]: + if not isinstance(data, Tuple): + continue + key = data[0] + if not args.all and key.startswith('_'): + continue + if args.category and not key.startswith(args.category + '_'): + continue + if args.subcategory and not key.startswith(args.category + '_' + args.subcategory + '_'): + continue + value = getattr(mlist, key) + if args.verbose: + print(f"{key}={value}") + else: + print(key) mlist.Unlock() diff --git a/bin/mailmanctl b/bin/mailmanctl index b13dcaa7..600f8d1b 100644 --- a/bin/mailmanctl +++ b/bin/mailmanctl @@ -158,9 +158,11 @@ def usage(code, msg=''): def kill_watcher(sig): try: - fp = open(mm_cfg.PIDFILE) + fp = open(mm_cfg.PIDFILE, 'rb') pidstr = fp.read() fp.close() + if isinstance(pidstr, bytes): + pidstr = pidstr.decode('utf-8', 'replace') pid = int(pidstr.strip()) except (IOError, ValueError) as e: # For i18n convenience From f9b695a2a5b648f2b7c17a980f1b6f294aa8f721 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 24 Apr 2025 11:01:25 -0400 Subject: [PATCH 025/748] cleanup --- bin/config_list | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/bin/config_list b/bin/config_list index 11b1c0fd..522f0003 100644 --- a/bin/config_list +++ b/bin/config_list @@ -346,11 +346,35 @@ def main(): key, value = line.split('=', 1) config[key.strip()] = value.strip() fp.close() - for key, value in config.items(): - try: - mlist[key] = value - except Errors.MMListError as e: - print(_('Error setting %(key)s: %(error)s')) + + # Get configuration items using GetConfigInfo() + for category in mm_cfg.ADMIN_CATEGORIES: + subcats = mlist.GetConfigSubCategories(category) + if subcats is None: + info = mlist.GetConfigInfo(category, None) + if info: + for data in info[1:]: + if not isinstance(data, Tuple): + continue + key = data[0] + if key in config: + try: + setattr(mlist, key, config[key]) + except Errors.MMListError as e: + print(_('Error setting %(key)s: %(error)s') % {'key': key, 'error': str(e)}) + else: + for subcat, _ in subcats: + info = mlist.GetConfigInfo(category, subcat) + if info: + for data in info[1:]: + if not isinstance(data, Tuple): + continue + key = data[0] + if key in config: + try: + setattr(mlist, key, config[key]) + except Errors.MMListError as e: + print(_('Error setting %(key)s: %(error)s') % {'key': key, 'error': str(e)}) mlist.Save() elif args.output_file: try: From a67939adca3a1e9c897fff81ac6c9486a5f1ef91 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 24 Apr 2025 11:05:01 -0400 Subject: [PATCH 026/748] cleanup --- bin/config_list | 159 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 107 insertions(+), 52 deletions(-) diff --git a/bin/config_list b/bin/config_list index 522f0003..d321b453 100644 --- a/bin/config_list +++ b/bin/config_list @@ -256,59 +256,114 @@ def do_input(listname, infile, checkonly, verbose): savelist = 0 guibyprop = getPropertyMap(mlist) try: - globals = {'mlist': mlist} - # Any exception that occurs in execfile() will cause the list to not - # be saved, but any other problems are not save-fatal. - execfile(infile, globals) - savelist = 1 - for k, v in list(globals.items()): - if k in ('mlist', '__builtins__'): - continue - if not hasattr(mlist, k): - print(C_('attribute "%(k)s" ignored'), file=sys.stderr) - continue - if verbose: - print(C_('attribute "%(k)s" changed'), file=sys.stderr) - missing = [] - gui, wtype = guibyprop.get(k, (missing, missing)) - if gui is missing: - # This isn't an official property of the list, but that's - # okay, we'll just restore it the old fashioned way - print(C_( - 'Non-standard property restored: %(k)s'), file=sys.stderr) - setattr(mlist, k, v) + # Read the input file and parse it + with open(infile) as fp: + config = {} + for line in fp: + line = line.strip() + if line and not line.startswith('#'): + key, value = line.split('=', 1) + config[key.strip()] = value.strip() + + # Get configuration items using GetConfigInfo() + for category in mm_cfg.ADMIN_CATEGORIES: + subcats = mlist.GetConfigSubCategories(category) + if subcats is None: + info = mlist.GetConfigInfo(category, None) + if info: + for data in info[1:]: + if not isinstance(data, Tuple): + continue + key = data[0] + if key in config: + if verbose: + print(C_('attribute "%(key)s" changed') % {'key': key}, file=sys.stderr) + missing = [] + gui, wtype = guibyprop.get(key, (missing, missing)) + if gui is missing: + # This isn't an official property of the list, but that's + # okay, we'll just restore it the old fashioned way + print(C_('Non-standard property restored: %(key)s') % {'key': key}, file=sys.stderr) + setattr(mlist, key, config[key]) + else: + # BAW: This uses non-public methods. This logic taken from + # the guts of GUIBase.handleForm(). + try: + validval = gui._getValidValue(mlist, key, wtype, config[key]) + except ValueError: + print(C_('Invalid value for property: %(key)s') % {'key': key}, file=sys.stderr) + except Errors.EmailAddressError: + print(C_('Bad email address for option %(key)s: %(value)s') % + {'key': key, 'value': config[key]}, file=sys.stderr) + else: + # BAW: Horrible hack, but then this is special cased + # everywhere anyway. :( Privacy._setValue() knows that + # when ALLOW_OPEN_SUBSCRIBE is false, the web values are + # 0, 1, 2 but these really should be 1, 2, 3, so it adds + # one. But we really do provide [0..3] so we need to undo + # the hack that _setValue adds. :( :( + if key == 'subscribe_policy' and \ + not mm_cfg.ALLOW_OPEN_SUBSCRIBE: + validval -= 1 + # BAW: Another horrible hack. This one is just too hard + # to fix in a principled way in Mailman 2.1 + elif key == 'new_member_options': + # Because this is a Checkbox, _getValidValue() + # transforms the value into a list of one item. + validval = validval[0] + validval = [bitfield for bitfield, bitval + in list(mm_cfg.OPTINFO.items()) + if validval & bitval] + gui._setValue(mlist, key, validval, fakedoc) else: - # BAW: This uses non-public methods. This logic taken from - # the guts of GUIBase.handleForm(). - try: - validval = gui._getValidValue(mlist, k, wtype, v) - except ValueError: - print(C_( - 'Invalid value for property: %(k)s'), file=sys.stderr) - except Errors.EmailAddressError: - print(C_( - 'Bad email address for option %(k)s: %(v)s'), file=sys.stderr) - else: - # BAW: Horrible hack, but then this is special cased - # everywhere anyway. :( Privacy._setValue() knows that - # when ALLOW_OPEN_SUBSCRIBE is false, the web values are - # 0, 1, 2 but these really should be 1, 2, 3, so it adds - # one. But we really do provide [0..3] so we need to undo - # the hack that _setValue adds. :( :( - if k == 'subscribe_policy' and \ - not mm_cfg.ALLOW_OPEN_SUBSCRIBE: - validval -= 1 - # BAW: Another horrible hack. This one is just too hard - # to fix in a principled way in Mailman 2.1 - elif k == 'new_member_options': - # Because this is a Checkbox, _getValidValue() - # transforms the value into a list of one item. - validval = validval[0] - validval = [bitfield for bitfield, bitval - in list(mm_cfg.OPTINFO.items()) - if validval & bitval] - gui._setValue(mlist, k, validval, fakedoc) - # BAW: when to do gui._postValidate()??? + for subcat, _ in subcats: + info = mlist.GetConfigInfo(category, subcat) + if info: + for data in info[1:]: + if not isinstance(data, Tuple): + continue + key = data[0] + if key in config: + if verbose: + print(C_('attribute "%(key)s" changed') % {'key': key}, file=sys.stderr) + missing = [] + gui, wtype = guibyprop.get(key, (missing, missing)) + if gui is missing: + # This isn't an official property of the list, but that's + # okay, we'll just restore it the old fashioned way + print(C_('Non-standard property restored: %(key)s') % {'key': key}, file=sys.stderr) + setattr(mlist, key, config[key]) + else: + # BAW: This uses non-public methods. This logic taken from + # the guts of GUIBase.handleForm(). + try: + validval = gui._getValidValue(mlist, key, wtype, config[key]) + except ValueError: + print(C_('Invalid value for property: %(key)s') % {'key': key}, file=sys.stderr) + except Errors.EmailAddressError: + print(C_('Bad email address for option %(key)s: %(value)s') % + {'key': key, 'value': config[key]}, file=sys.stderr) + else: + # BAW: Horrible hack, but then this is special cased + # everywhere anyway. :( Privacy._setValue() knows that + # when ALLOW_OPEN_SUBSCRIBE is false, the web values are + # 0, 1, 2 but these really should be 1, 2, 3, so it adds + # one. But we really do provide [0..3] so we need to undo + # the hack that _setValue adds. :( :( + if key == 'subscribe_policy' and \ + not mm_cfg.ALLOW_OPEN_SUBSCRIBE: + validval -= 1 + # BAW: Another horrible hack. This one is just too hard + # to fix in a principled way in Mailman 2.1 + elif key == 'new_member_options': + # Because this is a Checkbox, _getValidValue() + # transforms the value into a list of one item. + validval = validval[0] + validval = [bitfield for bitfield, bitval + in list(mm_cfg.OPTINFO.items()) + if validval & bitval] + gui._setValue(mlist, key, validval, fakedoc) + savelist = 1 finally: if savelist and not checkonly: mlist.Save() From 41573243c522b095af15320f2b87c802095f1589 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 25 Apr 2025 10:28:32 -0400 Subject: [PATCH 027/748] update --- Mailman/Utils.py | 170 ++++++++++++++++++++--------------------------- 1 file changed, 73 insertions(+), 97 deletions(-) diff --git a/Mailman/Utils.py b/Mailman/Utils.py index 152589f2..5ba59a02 100644 --- a/Mailman/Utils.py +++ b/Mailman/Utils.py @@ -1293,107 +1293,83 @@ def IsDMARCProhibited(mlist, email): return False def _DMARCProhibited(mlist, email, dmarc_domain, org=False): - + """Check if the domain has a DMARC policy that prohibits forwarding. + + This is a helper function for IsDMARCProhibited(). It checks if the + domain has a DMARC policy that prohibits forwarding. The domain is + either the domain part of the email address, or the organizational + domain. + + :param mlist: The mailing list. + :type mlist: MailList + :param email: The email address to check. + :type email: str + :param dmarc_domain: The domain to check for DMARC policy. + :type dmarc_domain: str + :param org: If True, the domain is an organizational domain. + :type org: bool + :return: True if the domain has a DMARC policy that prohibits forwarding. + :rtype: bool + """ try: - resolver = dns.resolver.Resolver() - resolver.timeout = float(mm_cfg.DMARC_RESOLVER_TIMEOUT) - resolver.lifetime = float(mm_cfg.DMARC_RESOLVER_LIFETIME) - txt_recs = resolver.query(dmarc_domain, dns.rdatatype.TXT) - except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): - return 'continue' - except (dns.resolver.NoNameservers): + txt_recs = dns.resolver.resolve(dmarc_domain, 'TXT') + except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer, + dns.resolver.NoNameservers, dns.resolver.Timeout, + DNSException): + return False + if not txt_recs: + return False + # Be as robust as possible in parsing the result. + results_by_name = {} + cnames = {} + want_names = set([dmarc_domain + '.']) + for txt_rec in txt_recs.response.answer: + # Don't be fooled by an answer with uppercase in the name. + name = txt_rec.name.to_text().lower() + if txt_rec.rdtype == dns.rdatatype.CNAME: + cnames[name] = txt_rec.target.to_text() + if txt_rec.rdtype != dns.rdatatype.TXT: + continue + # Access the strings directly from the TXT record + results_by_name.setdefault(name, []).append( + "".join(txt_rec.strings)) + expands = list(want_names) + seen = set(expands) + while expands: + item = expands.pop(0) + if item in cnames: + if cnames[item] in seen: + continue # cname loop + expands.append(cnames[item]) + seen.add(cnames[item]) + want_names.add(cnames[item]) + want_names.discard(item) + + if len(want_names) != 1: syslog('error', - 'DNSException: No Nameservers available for %s (%s)', - email, dmarc_domain) - # Typically this means a dnssec validation error. Clients that don't - # perform validation *may* successfully see a _dmarc RR whereas a - # validating mailman server won't see the _dmarc RR. We should - # mitigate this email to be safe. + """multiple DMARC entries in results for %s, + using first one""" % dmarc_domain) + dmarc_txt = None + for name in want_names: + if name in results_by_name: + dmarc_txt = results_by_name[name][0] + break + if not dmarc_txt: + return False + # Parse the DMARC record. + try: + dmarc = dict(item.split('=', 1) + for item in dmarc_txt.split(';') + if '=' in item) + except (ValueError, AttributeError): + return False + # Check if the policy is 'reject' or 'quarantine'. + if dmarc.get('p', '').lower() in ('reject', 'quarantine'): return True - except DNSException as e: - syslog('error', - 'DNSException: Unable to query DMARC policy for %s (%s). %s', - email, dmarc_domain, e.__doc__) - # While we can't be sure what caused the error, there is potentially - # a DMARC policy record that we missed and that a receiver of the mail - # might see. Thus, we should err on the side of caution and mitigate. + # Check if the subdomain policy is 'reject' or 'quarantine'. + if (not org and + dmarc.get('sp', '').lower() in ('reject', 'quarantine')): return True - else: - # Be as robust as possible in parsing the result. - results_by_name = {} - cnames = {} - want_names = set([dmarc_domain + '.']) - for txt_rec in txt_recs.response.answer: - # Don't be fooled by an answer with uppercase in the name. - name = txt_rec.name.to_text().lower() - if txt_rec.rdtype == dns.rdatatype.CNAME: - cnames[name] = ( - txt_rec.items[0].target.to_text()) - if txt_rec.rdtype != dns.rdatatype.TXT: - continue - results_by_name.setdefault(name, []).append( - "".join(txt_rec.items[0].strings)) - expands = list(want_names) - seen = set(expands) - while expands: - item = expands.pop(0) - if item in cnames: - if cnames[item] in seen: - continue # cname loop - expands.append(cnames[item]) - seen.add(cnames[item]) - want_names.add(cnames[item]) - want_names.discard(item) - - if len(want_names) != 1: - syslog('error', - """multiple DMARC entries in results for %s, - processing each to be strict""", - dmarc_domain) - for name in want_names: - if name not in results_by_name: - continue - dmarcs = [n for n in results_by_name[name] if n.startswith('v=DMARC1;')] - if len(dmarcs) == 0: - return 'continue' - if len(dmarcs) > 1: - syslog('error', - """RRset of TXT records for %s has %d v=DMARC1 entries; - ignoring them per RFC 7849""", - dmarc_domain, len(dmarcs)) - return False - for entry in dmarcs: - mo = re.search(r'\bsp=(\w*)\b', entry, re.IGNORECASE) - if org and mo: - policy = mo.group(1).lower() - else: - mo = re.search(r'\bp=(\w*)\b', entry, re.IGNORECASE) - if mo: - policy = mo.group(1).lower() - else: - continue - if policy == 'reject': - syslog('vette', - '%s: DMARC lookup for %s (%s) found p=reject in %s = %s', - mlist.real_name, email, dmarc_domain, name, entry) - return True - - if (mlist.dmarc_quarantine_moderation_action and - policy == 'quarantine'): - syslog('vette', - '%s: DMARC lookup for %s (%s) found p=quarantine in %s = %s', - mlist.real_name, email, dmarc_domain, name, entry) - return True - - if (mlist.dmarc_none_moderation_action and - mlist.dmarc_quarantine_moderation_action and - mlist.dmarc_moderation_action in (1, 2) and - policy == 'none'): - syslog('vette', - '%s: DMARC lookup for %s (%s) found p=none in %s = %s', - mlist.real_name, email, dmarc_domain, name, entry) - return True - return False From 40fdff227b37756f7985f6d3a3be81fef9e5931a Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 25 Apr 2025 10:32:35 -0400 Subject: [PATCH 028/748] update code --- Mailman/Handlers/SpamDetect.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Mailman/Handlers/SpamDetect.py b/Mailman/Handlers/SpamDetect.py index 5531dd0e..11e35937 100644 --- a/Mailman/Handlers/SpamDetect.py +++ b/Mailman/Handlers/SpamDetect.py @@ -80,14 +80,22 @@ def getDecodedHeaders(msg, cset='utf-8'): if not cs: cs = 'us-ascii' try: - uvalue += str(frag, cs, 'replace') + if isinstance(frag, str): + # If it's already a string, just use it + uvalue += frag + else: + # If it's bytes, decode it + uvalue += str(frag, cs, 'replace') except LookupError: # The encoding charset is unknown. At this point, frag # has been QP or base64 decoded into a byte string whose # charset we don't know how to handle. We will try to # unicode it as iso-8859-1 which may result in a garbled # mess, but we have to do something. - uvalue += str(frag, 'iso-8859-1', 'replace') + if isinstance(frag, str): + uvalue += frag + else: + uvalue += str(frag, 'iso-8859-1', 'replace') uhdr = h.decode('us-ascii', 'replace') headers += u'%s: %s\n' % (h, normalize(mm_cfg.NORMALIZE_FORM, uvalue)) return headers From 3f3422d764b40cf98ee1285bb109593e7654b9ec Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 25 Apr 2025 10:34:23 -0400 Subject: [PATCH 029/748] update --- Mailman/Utils.py | 94 +++++++++++------------------------------------- 1 file changed, 20 insertions(+), 74 deletions(-) diff --git a/Mailman/Utils.py b/Mailman/Utils.py index 5ba59a02..1a0df973 100644 --- a/Mailman/Utils.py +++ b/Mailman/Utils.py @@ -1292,84 +1292,30 @@ def IsDMARCProhibited(mlist, email): return x return False -def _DMARCProhibited(mlist, email, dmarc_domain, org=False): - """Check if the domain has a DMARC policy that prohibits forwarding. - - This is a helper function for IsDMARCProhibited(). It checks if the - domain has a DMARC policy that prohibits forwarding. The domain is - either the domain part of the email address, or the organizational - domain. - - :param mlist: The mailing list. - :type mlist: MailList - :param email: The email address to check. - :type email: str - :param dmarc_domain: The domain to check for DMARC policy. - :type dmarc_domain: str - :param org: If True, the domain is an organizational domain. - :type org: bool - :return: True if the domain has a DMARC policy that prohibits forwarding. - :rtype: bool +def _DMARCProhibited(mlist, email, domain): + """Check if the domain has a DMARC policy that prohibits sending. """ try: - txt_recs = dns.resolver.resolve(dmarc_domain, 'TXT') - except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer, - dns.resolver.NoNameservers, dns.resolver.Timeout, - DNSException): + import dns.resolver + import dns.exception + except ImportError: return False - if not txt_recs: - return False - # Be as robust as possible in parsing the result. - results_by_name = {} - cnames = {} - want_names = set([dmarc_domain + '.']) - for txt_rec in txt_recs.response.answer: - # Don't be fooled by an answer with uppercase in the name. - name = txt_rec.name.to_text().lower() - if txt_rec.rdtype == dns.rdatatype.CNAME: - cnames[name] = txt_rec.target.to_text() - if txt_rec.rdtype != dns.rdatatype.TXT: - continue - # Access the strings directly from the TXT record - results_by_name.setdefault(name, []).append( - "".join(txt_rec.strings)) - expands = list(want_names) - seen = set(expands) - while expands: - item = expands.pop(0) - if item in cnames: - if cnames[item] in seen: - continue # cname loop - expands.append(cnames[item]) - seen.add(cnames[item]) - want_names.add(cnames[item]) - want_names.discard(item) - - if len(want_names) != 1: - syslog('error', - """multiple DMARC entries in results for %s, - using first one""" % dmarc_domain) - dmarc_txt = None - for name in want_names: - if name in results_by_name: - dmarc_txt = results_by_name[name][0] - break - if not dmarc_txt: - return False - # Parse the DMARC record. try: - dmarc = dict(item.split('=', 1) - for item in dmarc_txt.split(';') - if '=' in item) - except (ValueError, AttributeError): - return False - # Check if the policy is 'reject' or 'quarantine'. - if dmarc.get('p', '').lower() in ('reject', 'quarantine'): - return True - # Check if the subdomain policy is 'reject' or 'quarantine'. - if (not org and - dmarc.get('sp', '').lower() in ('reject', 'quarantine')): - return True + txt_rec = dns.resolver.resolve(domain, 'TXT') + # Newer versions of dnspython use strings property instead of strings attribute + txt_strings = txt_rec.strings if hasattr(txt_rec, 'strings') else [str(r) for r in txt_rec] + for txt in txt_strings: + if txt.startswith('v=DMARC1'): + # Parse the DMARC record + parts = txt.split(';') + for part in parts: + part = part.strip() + if part.startswith('p='): + policy = part[2:].lower() + if policy in ('reject', 'quarantine'): + return True + except (dns.exception.DNSException, AttributeError): + pass return False From c4625090a27c3bdfce8dded1f5d1fdd41dc98a40 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 25 Apr 2025 10:38:31 -0400 Subject: [PATCH 030/748] update --- Mailman/Handlers/SpamDetect.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Mailman/Handlers/SpamDetect.py b/Mailman/Handlers/SpamDetect.py index 11e35937..f086bacc 100644 --- a/Mailman/Handlers/SpamDetect.py +++ b/Mailman/Handlers/SpamDetect.py @@ -68,7 +68,6 @@ def getDecodedHeaders(msg, cset='utf-8'): """Returns a unicode containing all the headers of msg, unfolded and RFC 2047 decoded, normalized and separated by new lines. """ - headers = u'' for h, v in list(msg.items()): uvalue = u'' From b04856d6e77c0a1d54b0423480217e9d691032f6 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 25 Apr 2025 10:44:11 -0400 Subject: [PATCH 031/748] update --- bin/mailmanctl | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/bin/mailmanctl b/bin/mailmanctl index 600f8d1b..4192f70d 100644 --- a/bin/mailmanctl +++ b/bin/mailmanctl @@ -280,10 +280,16 @@ def start_all_runners(): kids = {} for qrname, count in mm_cfg.QRUNNERS: for slice in range(count): - # queue runner name, slice, numslices, restart count - info = (qrname, slice, count, 0) - pid = start_runner(qrname, slice, count) - kids[pid] = info + try: + # queue runner name, slice, numslices, restart count + info = (qrname, slice, count, 0) + pid = start_runner(qrname, slice, count) + kids[pid] = info + except Exception as e: + # Log the failure but continue with other runners + syslog('error', 'Failed to start %s runner (slice %d): %s', + qrname, slice, str(e)) + continue return kids @@ -346,7 +352,19 @@ def main(): # Handle the command if args.command == 'start': - start_all_runners() + try: + kids = start_all_runners() + # Write PID file only if at least one runner started successfully + if kids: + with open(mm_cfg.PIDFILE, 'w') as fp: + fp.write(str(os.getpid())) + syslog('info', 'Started %d runners successfully', len(kids)) + else: + syslog('error', 'No runners started successfully') + sys.exit(1) + except Exception as e: + syslog('error', 'Error during startup: %s', str(e)) + sys.exit(1) elif args.command == 'stop': kill_watcher(signal.SIGTERM) elif args.command == 'restart': From b75d5e2bda2f010b3e3179fe6d9f8cfff8df10d6 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 25 Apr 2025 10:51:00 -0400 Subject: [PATCH 032/748] updates --- Mailman/Bouncers/Caiwireless.py | 24 ++++++++++++++-- Mailman/Bouncers/Exchange.py | 12 ++++++-- Mailman/Bouncers/Qmail.py | 19 +++++++++++-- Mailman/Bouncers/SimpleMatch.py | 11 ++++++-- Mailman/Bouncers/Sina.py | 7 +++-- Mailman/Bouncers/Yahoo.py | 10 +++++-- Mailman/Handlers/SpamDetect.py | 49 ++++++++++++--------------------- Mailman/Handlers/Tagger.py | 7 ++--- 8 files changed, 86 insertions(+), 53 deletions(-) diff --git a/Mailman/Bouncers/Caiwireless.py b/Mailman/Bouncers/Caiwireless.py index 0d436507..c99c0945 100644 --- a/Mailman/Bouncers/Caiwireless.py +++ b/Mailman/Bouncers/Caiwireless.py @@ -18,14 +18,19 @@ import re import email -from io import StringIO +from email.iterators import body_line_iterator +from email.header import decode_header + +from Mailman import mm_cfg +from Mailman import Utils +from Mailman.Logging.Syslog import syslog +from Mailman.Handlers.CookHeaders import change_header tcre = re.compile(r'the following recipients did not receive this message:', re.IGNORECASE) acre = re.compile(r'<(?P[^>]*)>') - def process(msg): if msg.get_content_type() != 'multipart/mixed': return None @@ -34,7 +39,7 @@ def process(msg): # 1 == tag line seen state = 0 # This format thinks it's a MIME, but it really isn't - for line in email.Iterators.body_line_iterator(msg): + for line in body_line_iterator(msg): line = line.strip() if state == 0 and tcre.match(line): state = 1 @@ -43,3 +48,16 @@ def process(msg): if not mo: return None return [mo.group('addr')] + + # Now that we have a Message object that meets our criteria, let's extract + # the first numlines of body text. + lines = [] + lineno = 0 + for line in body_line_iterator(msg): + # Blank lines don't count + if not line.strip(): + continue + lineno += 1 + lines.append(line) + if numlines is not None and lineno >= numlines: + break diff --git a/Mailman/Bouncers/Exchange.py b/Mailman/Bouncers/Exchange.py index 917c5146..bd2d3242 100644 --- a/Mailman/Bouncers/Exchange.py +++ b/Mailman/Bouncers/Exchange.py @@ -17,7 +17,14 @@ """Recognizes (some) Microsoft Exchange formats.""" import re -import email.Iterators +import email +from email.iterators import body_line_iterator +from email.header import decode_header + +from Mailman import mm_cfg +from Mailman import Utils +from Mailman.Logging.Syslog import syslog +from Mailman.Handlers.CookHeaders import change_header scre = re.compile('did not reach the following recipient') ecre = re.compile('MSEXCH:') @@ -25,10 +32,9 @@ a2cre = re.compile('(?P[^ ]+) on ') - def process(msg): addrs = {} - it = email.Iterators.body_line_iterator(msg) + it = body_line_iterator(msg) # Find the start line for line in it: if scre.search(line): diff --git a/Mailman/Bouncers/Qmail.py b/Mailman/Bouncers/Qmail.py index a22771b5..9baa7d6c 100644 --- a/Mailman/Bouncers/Qmail.py +++ b/Mailman/Bouncers/Qmail.py @@ -27,7 +27,18 @@ """ import re -import email.Iterators +import sys +import email +from email.iterators import body_line_iterator +from email.mime.text import MIMEText +from email.mime.message import MIMEMessage + +from Mailman import mm_cfg +from Mailman import Utils +from Mailman import Message +from Mailman import Errors +from Mailman import i18n +from Mailman.Logging.Syslog import syslog # Other (non-standard?) intros have been observed in the wild. introtags = [ @@ -42,7 +53,6 @@ acre = re.compile(r'<(?P[^>]*)>:') - def process(msg): addrs = [] # simple state machine @@ -50,7 +60,10 @@ def process(msg): # 1 = intro paragraph seen # 2 = recip paragraphs seen state = 0 - for line in email.Iterators.body_line_iterator(msg): + for line in body_line_iterator(msg): + # Ensure line is a string + if isinstance(line, bytes): + line = line.decode('ascii', 'replace') line = line.strip() if state == 0: for introtag in introtags: diff --git a/Mailman/Bouncers/SimpleMatch.py b/Mailman/Bouncers/SimpleMatch.py index 68e1d18d..ad0c17dd 100644 --- a/Mailman/Bouncers/SimpleMatch.py +++ b/Mailman/Bouncers/SimpleMatch.py @@ -18,7 +18,14 @@ """Recognizes simple heuristically delimited bounces.""" import re -import email.Iterators +import email +from email.iterators import body_line_iterator +from email.header import decode_header + +from Mailman import mm_cfg +from Mailman import Utils +from Mailman.Logging.Syslog import syslog +from Mailman.Handlers.CookHeaders import change_header @@ -224,7 +231,7 @@ def process(msg, patterns=None): # we process the message multiple times anyway. for scre, ecre, acre in patterns: state = 0 - for line in email.Iterators.body_line_iterator(msg, decode=True): + for line in body_line_iterator(msg): if state == 0: if scre.search(line): state = 1 diff --git a/Mailman/Bouncers/Sina.py b/Mailman/Bouncers/Sina.py index ced78e9e..5e48cb44 100644 --- a/Mailman/Bouncers/Sina.py +++ b/Mailman/Bouncers/Sina.py @@ -18,12 +18,13 @@ from __future__ import print_function import re -from email import Iterators +import email +from email.iterators import body_line_iterator +from email.header import decode_header acre = re.compile(r'<(?P[^>]*)>') - def process(msg): if msg.get('from', '').lower() != 'mailer-daemon@sina.com': print('out 1') @@ -41,7 +42,7 @@ def process(msg): print('out 3') return [] addrs = {} - for line in Iterators.body_line_iterator(part): + for line in body_line_iterator(part): mo = acre.match(line) if mo: addrs[mo.group('addr')] = 1 diff --git a/Mailman/Bouncers/Yahoo.py b/Mailman/Bouncers/Yahoo.py index 3154c331..c0883c77 100644 --- a/Mailman/Bouncers/Yahoo.py +++ b/Mailman/Bouncers/Yahoo.py @@ -19,8 +19,15 @@ import re import email +from email.iterators import body_line_iterator +from email.header import decode_header from email.utils import parseaddr +from Mailman import mm_cfg +from Mailman import Utils +from Mailman.Logging.Syslog import syslog +from Mailman.Handlers.CookHeaders import change_header + tcre = (re.compile(r'message\s+from\s+yahoo\.\S+', re.IGNORECASE), re.compile(r'Sorry, we were unable to deliver your message to ' r'the following address(\(es\))?\.', @@ -32,7 +39,6 @@ ) - def process(msg): # Yahoo! bounces seem to have a known subject value and something called # an x-uidl: header, the value of which seems unimportant. @@ -45,7 +51,7 @@ def process(msg): # 1 == tag line seen # 2 == end line seen state = 0 - for line in email.Iterators.body_line_iterator(msg): + for line in body_line_iterator(msg): line = line.strip() if state == 0: for cre in tcre: diff --git a/Mailman/Handlers/SpamDetect.py b/Mailman/Handlers/SpamDetect.py index f086bacc..c5f27a1f 100644 --- a/Mailman/Handlers/SpamDetect.py +++ b/Mailman/Handlers/SpamDetect.py @@ -64,40 +64,25 @@ def reason_notice(self): -def getDecodedHeaders(msg, cset='utf-8'): - """Returns a unicode containing all the headers of msg, unfolded and - RFC 2047 decoded, normalized and separated by new lines. +def getDecodedHeaders(msg, lcset): + """Return a Unicode string containing all headers of msg, unfolded and RFC 2047 + decoded. If a header cannot be decoded, it is replaced with a string of + question marks. """ - headers = u'' - for h, v in list(msg.items()): - uvalue = u'' + headers = [] + for h in msg.get_all_headers(): try: - v = decode_header(re.sub(r'\n\s', ' ', v)) - except HeaderParseError: - v = [(v, 'us-ascii')] - for frag, cs in v: - if not cs: - cs = 'us-ascii' - try: - if isinstance(frag, str): - # If it's already a string, just use it - uvalue += frag - else: - # If it's bytes, decode it - uvalue += str(frag, cs, 'replace') - except LookupError: - # The encoding charset is unknown. At this point, frag - # has been QP or base64 decoded into a byte string whose - # charset we don't know how to handle. We will try to - # unicode it as iso-8859-1 which may result in a garbled - # mess, but we have to do something. - if isinstance(frag, str): - uvalue += frag - else: - uvalue += str(frag, 'iso-8859-1', 'replace') - uhdr = h.decode('us-ascii', 'replace') - headers += u'%s: %s\n' % (h, normalize(mm_cfg.NORMALIZE_FORM, uvalue)) - return headers + # Check if h is already a string + if isinstance(h, str): + uhdr = h + else: + # Try to decode as bytes + uhdr = h.decode('us-ascii', 'replace') + headers.append(uhdr) + except (UnicodeError, AttributeError): + # If we can't decode it, replace with question marks + headers.append('?' * len(h)) + return '\n'.join(headers) diff --git a/Mailman/Handlers/Tagger.py b/Mailman/Handlers/Tagger.py index 0cc4a085..f2879906 100644 --- a/Mailman/Handlers/Tagger.py +++ b/Mailman/Handlers/Tagger.py @@ -20,7 +20,7 @@ import re import email import email.errors -import email.iterators +from email.iterators import body_line_iterator import email.parser from email.header import decode_header @@ -35,7 +35,6 @@ NLTAB = '\n\t' - def process(mlist, msg, msgdata): if not mlist.topics_enabled: return @@ -76,7 +75,6 @@ def _decode(h): mlist, msg, msgdata, delete=False) - def scanbody(msg, numlines=None): # We only scan the body of the message if it is of MIME type text/plain, # or if the outer type is multipart/alternative and there is a text/plain @@ -97,7 +95,7 @@ def scanbody(msg, numlines=None): # the first numlines of body text. lines = [] lineno = 0 - reader = list(email.Iterators.body_line_iterator(msg, decode=True)) + reader = list(body_line_iterator(msg, decode=True)) while numlines is None or lineno < numlines: try: line = reader.pop(0) @@ -115,7 +113,6 @@ def scanbody(msg, numlines=None): return msg.get_all('subject', []) + msg.get_all('keywords', []) - class _ForgivingParser(email.parser.HeaderParser): # Be a little more forgiving about non-header/continuation lines, since # we'll just read as much as we can from "header-like" lines in the body. From 7b190e653d87797e24aa649cf3661d1ec197902b Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 25 Apr 2025 11:01:50 -0400 Subject: [PATCH 033/748] cleanup imports --- Mailman/Archiver/HyperDatabase.py | 2 -- Mailman/Archiver/__init__.py | 1 - Mailman/Bouncers/Compuserve.py | 4 ++-- Mailman/Bouncers/LLNL.py | 4 ++-- Mailman/Bouncers/SMTP32.py | 4 ++-- Mailman/Bouncers/SimpleWarning.py | 4 +++- Mailman/CSRFcheck.py | 7 +++---- Mailman/Cgi/admindb.py | 4 +++- Mailman/Commands/cmd_subscribe.py | 7 +++++-- Mailman/Defaults.py.in | 1 - Mailman/Gui/Bounce.py | 2 -- Mailman/Handlers/CookHeaders.py | 5 +---- Mailman/Handlers/Hold.py | 8 +++----- Mailman/Logging/Logger.py | 6 ------ Mailman/MailList.py | 14 +------------- Mailman/Queue/CommandRunner.py | 6 +++--- Mailman/Queue/NewsRunner.py | 3 ++- Mailman/Utils.py | 3 +-- bin/list_owners | 5 ----- bin/mailmanctl | 0 bin/sync_members | 22 ++++++++++++---------- contrib/courier-to-mailman.py | 6 +++--- contrib/qmail-to-mailman.py | 4 ++-- contrib/rotatelogs.py | 4 ++-- contrib/sitemapgen | 2 +- 25 files changed, 51 insertions(+), 77 deletions(-) mode change 100644 => 100755 bin/mailmanctl diff --git a/Mailman/Archiver/HyperDatabase.py b/Mailman/Archiver/HyperDatabase.py index ddf4d62f..588515e3 100644 --- a/Mailman/Archiver/HyperDatabase.py +++ b/Mailman/Archiver/HyperDatabase.py @@ -1,4 +1,3 @@ -from __future__ import absolute_import # Copyright (C) 1998-2018 by the Free Software Foundation, Inc. # # This program is free software; you can redistribute it and/or @@ -192,7 +191,6 @@ def close(self): fp.close() self.unlock() - # this is lifted straight out of pipermail with # the bsddb.btree replaced with above class. # didn't use inheritance because of all the diff --git a/Mailman/Archiver/__init__.py b/Mailman/Archiver/__init__.py index 11e20583..cb00641d 100644 --- a/Mailman/Archiver/__init__.py +++ b/Mailman/Archiver/__init__.py @@ -1,4 +1,3 @@ -from __future__ import absolute_import # Copyright (C) 1998-2018 by the Free Software Foundation, Inc. # # This program is free software; you can redistribute it and/or diff --git a/Mailman/Bouncers/Compuserve.py b/Mailman/Bouncers/Compuserve.py index 0a94b00c..3591eff8 100644 --- a/Mailman/Bouncers/Compuserve.py +++ b/Mailman/Bouncers/Compuserve.py @@ -18,19 +18,19 @@ import re import email +from email.iterators import body_line_iterator dcre = re.compile(r'your message could not be delivered', re.IGNORECASE) acre = re.compile(r'Invalid receiver address: (?P.*)') - def process(msg): # simple state machine # 0 = nothing seen yet # 1 = intro line seen state = 0 addrs = [] - for line in email.Iterators.body_line_iterator(msg): + for line in body_line_iterator(msg): if state == 0: mo = dcre.search(line) if mo: diff --git a/Mailman/Bouncers/LLNL.py b/Mailman/Bouncers/LLNL.py index 1e038182..1e2a9e6f 100644 --- a/Mailman/Bouncers/LLNL.py +++ b/Mailman/Bouncers/LLNL.py @@ -18,13 +18,13 @@ import re import email +from email.iterators import body_line_iterator acre = re.compile(r',\s*(?P\S+@[^,]+),', re.IGNORECASE) - def process(msg): - for line in email.Iterators.body_line_iterator(msg): + for line in body_line_iterator(msg): mo = acre.search(line) if mo: return [mo.group('addr')] diff --git a/Mailman/Bouncers/SMTP32.py b/Mailman/Bouncers/SMTP32.py index c91b294e..955bafd4 100644 --- a/Mailman/Bouncers/SMTP32.py +++ b/Mailman/Bouncers/SMTP32.py @@ -30,6 +30,7 @@ import re import email +from email.iterators import body_line_iterator ecre = re.compile('original message follows', re.IGNORECASE) acre = re.compile(r''' @@ -45,13 +46,12 @@ ''', re.IGNORECASE | re.VERBOSE) - def process(msg): mailer = msg.get('x-mailer', '') if not mailer.startswith('= limit > 0: diff --git a/Mailman/Commands/cmd_subscribe.py b/Mailman/Commands/cmd_subscribe.py index 6cb61eaf..8e4c9443 100644 --- a/Mailman/Commands/cmd_subscribe.py +++ b/Mailman/Commands/cmd_subscribe.py @@ -94,8 +94,11 @@ def process(res, args): # Watch for encoded names try: h = make_header(decode_header(realname)) - # BAW: in Python 2.2, use just unicode(h) - realname = h.__unicode__() + # Get the realname from the header + try: + realname = str(h) + except UnicodeError: + realname = str(h, 'utf-8', 'replace') except UnicodeError: realname = u'' # Coerce to byte string if uh contains only ascii diff --git a/Mailman/Defaults.py.in b/Mailman/Defaults.py.in index daa9e672..738f39ef 100755 --- a/Mailman/Defaults.py.in +++ b/Mailman/Defaults.py.in @@ -18,7 +18,6 @@ # USA. """Distributed default settings for significant Mailman config variables.""" -from __future__ import absolute_import # NEVER make site configuration changes to this file. ALWAYS make them in # mm_cfg.py instead, in the designated area. See the comments in that file diff --git a/Mailman/Gui/Bounce.py b/Mailman/Gui/Bounce.py index ee747678..e85aa79a 100644 --- a/Mailman/Gui/Bounce.py +++ b/Mailman/Gui/Bounce.py @@ -1,4 +1,3 @@ -from __future__ import division # Copyright (C) 2001-2018 by the Free Software Foundation, Inc. # # This program is free software; you can redistribute it and/or @@ -21,7 +20,6 @@ from Mailman.Gui.GUIBase import GUIBase - class Bounce(GUIBase): def GetConfigCategory(self): return 'bounce', _('Bounce processing') diff --git a/Mailman/Handlers/CookHeaders.py b/Mailman/Handlers/CookHeaders.py index d17fbe40..0a9732ca 100644 --- a/Mailman/Handlers/CookHeaders.py +++ b/Mailman/Handlers/CookHeaders.py @@ -20,13 +20,12 @@ list configuration. """ -from __future__ import nested_scopes import re - from email.charset import Charset from email.header import Header, decode_header, make_header from email.utils import parseaddr, formataddr, getaddresses from email.errors import HeaderParseError +from email.iterators import body_line_iterator from Mailman import i18n from Mailman import mm_cfg @@ -362,7 +361,6 @@ def add(pair): change_header(h, v, mlist, msg, msgdata) - def prefix_subject(mlist, msg, msgdata): # Add the subject prefix unless the message is a digest or is being fast # tracked (e.g. internally crafted, delivered to a single user such as the @@ -478,7 +476,6 @@ def prefix_subject(mlist, msg, msgdata): msgdata['stripped_subject'] = ss - def ch_oneline(headerstr): # Decode header string in one line and convert into single charset # copied and modified from ToDigest.py and Utils.py diff --git a/Mailman/Handlers/Hold.py b/Mailman/Handlers/Hold.py index f7c7d1a0..2616d36f 100644 --- a/Mailman/Handlers/Hold.py +++ b/Mailman/Handlers/Hold.py @@ -32,6 +32,8 @@ from email.mime.text import MIMEText from email.mime.message import MIMEMessage import email.utils +import re +from email.iterators import body_line_iterator from Mailman import mm_cfg from Mailman import Utils @@ -47,7 +49,6 @@ def _(s): return s - class ForbiddenPoster(Errors.HoldMessage): reason = _('Sender is explicitly forbidden') rejection = _('You are forbidden from posting messages to this list.') @@ -114,7 +115,6 @@ class ModeratedNewsgroup(ModeratedPost): _ = i18n._ - def ackp(msg): ack = msg.get('x-ack', '').lower() precedence = msg.get('precedence', '').lower() @@ -123,7 +123,6 @@ def ackp(msg): return 1 - def process(mlist, msg, msgdata): if msgdata.get('approved'): return @@ -173,7 +172,7 @@ def process(mlist, msg, msgdata): # Is the message too big? if mlist.max_message_size > 0: bodylen = 0 - for line in email.Iterators.body_line_iterator(msg): + for line in body_line_iterator(msg): bodylen += len(line) for part in msg.walk(): if part.preamble: @@ -191,7 +190,6 @@ def process(mlist, msg, msgdata): hold_for_approval(mlist, msg, msgdata, ModeratedNewsgroup) - def hold_for_approval(mlist, msg, msgdata, exc): # BAW: This should really be tied into the email confirmation system so # that the message can be approved or denied via email as well as the diff --git a/Mailman/Logging/Logger.py b/Mailman/Logging/Logger.py index af3c4c6d..48288bff 100644 --- a/Mailman/Logging/Logger.py +++ b/Mailman/Logging/Logger.py @@ -16,11 +16,6 @@ # USA. """File-based logger, writes to named category files in mm_cfg.LOG_DIR.""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - from builtins import * from builtins import object import sys @@ -37,7 +32,6 @@ LOG_ENCODING = 'iso-8859-1' - class Logger(object): def __init__(self, category, nofail=1, immediate=0): """nofail says to fallback to sys.__stderr__ if write fails to diff --git a/Mailman/MailList.py b/Mailman/MailList.py index ed432ea8..a6ff7bdc 100644 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -83,7 +83,6 @@ def D_(s): class MailList(HTMLFormatter, Deliverer, ListAdmin, Archiver, Digester, SecurityManager, Bouncer, GatewayManager, Autoresponder, TopicMgr, Pending.Pending): - # # A MailList object's basic Python object model support # @@ -149,7 +148,6 @@ def __repr__(self): return '' % ( self.internal_name(), status, id(self)) - # # Lock management # @@ -170,7 +168,6 @@ def Locked(self): return self.__lock.locked() - # # Useful accessors # @@ -279,7 +276,6 @@ def GetDescription(self, cset=None, errors='xmlcharrefreplace'): errors) - # # Instance and subcomponent initialization # @@ -468,7 +464,6 @@ def InitVars(self, name=None, admin='', crypted_password='', # automatic discarding self.max_days_to_hold = mm_cfg.DEFAULT_MAX_DAYS_TO_HOLD - # # Web API support via administrative categories # @@ -513,7 +508,6 @@ def GetConfigInfo(self, category, subcat=None): if value: return value - # # List creation # @@ -554,7 +548,6 @@ def Create(self, name, admin, crypted_password, self.available_languages = langs - # # Database and filesystem I/O # @@ -817,7 +810,6 @@ def __fix_corrupt_pckfile(self, file, pfile, plast, dfile, dlast): shutil.copy(file, dfile) shutil.copy(file, dfile + '.safety') - # # Sanity checks # @@ -886,7 +878,6 @@ def CheckValues(self): goodtopics.append((name, pattern, desc, emptyflag)) self.topics = goodtopics - # # Membership management front-ends and assertion checks # @@ -1399,7 +1390,6 @@ def log_and_notify_admin(self, oldaddr, newaddr): msg = Message.OwnerNotification(self, subject, text) msg.send(self) - # # Confirmation processing # @@ -1486,7 +1476,7 @@ def ProcessConfirmation(self, cookie, context=None): approved = context.get('Approved', context.get('Approve')) if not approved: try: - subpart = list(email.Iterators.typed_subpart_iterator( + subpart = list(email.iterators.typed_subpart_iterator( context, 'text', 'plain'))[0] except IndexError: subpart = None @@ -1571,7 +1561,6 @@ def ConfirmUnsubscription(self, addr, lang=None, remote=None): msg['Auto-Submitted'] = 'auto-generated' msg.send(self) - # # Miscellaneous stuff # @@ -1811,7 +1800,6 @@ def GetPattern(self, email, pattern_list, at_list=None): return matched - # # Multilingual (i18n) support # diff --git a/Mailman/Queue/CommandRunner.py b/Mailman/Queue/CommandRunner.py index e4b594b5..3c82111b 100644 --- a/Mailman/Queue/CommandRunner.py +++ b/Mailman/Queue/CommandRunner.py @@ -68,7 +68,7 @@ def __init__(self, mlist, msg, msgdata): # Python 2.1's unicode() builtin doesn't call obj.__unicode__(). subj = msg.get('subject', '') try: - subj = make_header(decode_header(subj)).__unicode__() + subj = str(make_header(decode_header(subj))) # TK: Currently we don't allow 8bit or multibyte in mail command. # MAS: However, an l10n 'Re:' may contain non-ascii so ignore it. subj = subj.encode('us-ascii', 'ignore') @@ -191,8 +191,8 @@ def indent(lines): resp.append(_('\n- Ignored:')) resp.extend(indent(self.ignored)) resp.append(_('\n- Done.\n\n')) - # Encode any unicode strings into the list charset, so we don't try to - # join unicode strings and invalid ASCII. + # Encode any strings into the list charset, so we don't try to + # join strings and invalid ASCII. charset = Utils.GetCharSet(self.msgdata['lang']) encoded_resp = [] for item in resp: diff --git a/Mailman/Queue/NewsRunner.py b/Mailman/Queue/NewsRunner.py index 4b157e18..16e4ae4b 100644 --- a/Mailman/Queue/NewsRunner.py +++ b/Mailman/Queue/NewsRunner.py @@ -24,6 +24,7 @@ import email from email.utils import getaddresses +from email.iterators import body_line_iterator COMMASPACE = ', ' @@ -162,7 +163,7 @@ def prepare_message(mlist, msg, msgdata): # Lines: is useful if msg['Lines'] is None: # BAW: is there a better way? - count = len(list(email.Iterators.body_line_iterator(msg))) + count = len(list(body_line_iterator(msg))) msg['Lines'] = str(count) # Massage the message headers by remove some and rewriting others. This # woon't completely sanitize the message, but it will eliminate the bulk diff --git a/Mailman/Utils.py b/Mailman/Utils.py index 1a0df973..16023490 100644 --- a/Mailman/Utils.py +++ b/Mailman/Utils.py @@ -31,8 +31,7 @@ import errno import base64 import random -import urllib -import urllib.request, urllib.error +import urllib.request, urllib.parse, urllib.error import html.entities import html import email.header diff --git a/bin/list_owners b/bin/list_owners index 129a98a2..0d11a531 100644 --- a/bin/list_owners +++ b/bin/list_owners @@ -38,11 +38,6 @@ Options: after the options. If there are no listnames provided, the owners of all the lists will be displayed. """ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - from builtins import * import sys import argparse diff --git a/bin/mailmanctl b/bin/mailmanctl old mode 100644 new mode 100755 diff --git a/bin/sync_members b/bin/sync_members index a2a42eeb..7d9f7223 100755 --- a/bin/sync_members +++ b/bin/sync_members @@ -77,10 +77,8 @@ Where `options' are: """ import sys - import paths -# Import this /after/ paths so that the sys.path is properly hacked -import email.Utils +import email.utils from Mailman import MailList from Mailman import Errors @@ -206,7 +204,7 @@ def main(): print(C_('Ignore : %(addr)30s')) # first filter out any invalid addresses - filemembers = email.Utils.getaddresses(filemembers) + filemembers = email.utils.getaddresses(filemembers) invalid = 0 for name, addr in filemembers: try: @@ -260,10 +258,12 @@ def main(): if not dryrun: mlist.ApprovedAddMember(userdesc, welcome, notifyadmin) # Avoid UnicodeError if name can't be decoded - if isinstance(name, str): - name = unicode(name, errors='replace') + try: + name = str(name, errors='replace') + except TypeError: + name = str(name) name = name.encode(enc, 'replace') - s = email.Utils.formataddr((name, addr)).encode(enc, 'replace') + s = email.utils.formataddr((name, addr)).encode(enc, 'replace') print(C_('Added : %(s)s')) except Errors.MMAlreadyAMember: pass @@ -285,10 +285,12 @@ def main(): # get rid of this member's entry mlist.removeMember(addr) # Avoid UnicodeError if name can't be decoded - if isinstance(name, str): - name = unicode(name, errors='replace') + try: + name = str(name, errors='replace') + except TypeError: + name = str(name) name = name.encode(enc, 'replace') - s = email.Utils.formataddr((name, addr)).encode(enc, 'replace') + s = email.utils.formataddr((name, addr)).encode(enc, 'replace') print(C_('Removed: %(s)s')) mlist.Save() diff --git a/contrib/courier-to-mailman.py b/contrib/courier-to-mailman.py index f5161db7..f3c2ecab 100644 --- a/contrib/courier-to-mailman.py +++ b/contrib/courier-to-mailman.py @@ -54,7 +54,7 @@ # Note: "preline" is a Courier program which ensures a Unix "From " header # is on the message. Archiving will break without this. -import sys, os, re, string +import sys, os, re def main(): os.nice(5) # Handle mailing lists at non-interactive priority. @@ -62,7 +62,7 @@ def main(): os.chdir(MailmanVar + "/lists") try: - local = string.lower(os.environ["LOCAL"]) + local = str.lower(os.environ["LOCAL"]) except: # This might happen if we're not using qmail. sys.stderr.write("LOCAL not set in environment?\n") @@ -77,7 +77,7 @@ def main(): sys.exit(0) type = "post" - listname = string.lower(local) + listname = str.lower(local) types = (("-admin$", "admin"), ("-bounces$", "bounces"), ("-bounces\+.*$", "bounces"), # for VERP diff --git a/contrib/qmail-to-mailman.py b/contrib/qmail-to-mailman.py index 2bf6943c..2136f68c 100644 --- a/contrib/qmail-to-mailman.py +++ b/contrib/qmail-to-mailman.py @@ -49,7 +49,7 @@ # After you edit /var/qmail/control/virtualdomains, kill and restart qmail. # -import sys, os, re, string +import sys, os, re def main(): os.nice(5) # Handle mailing lists at non-interactive priority. @@ -63,7 +63,7 @@ def main(): sys.stderr.write("LOCAL not set in environment?\n") sys.exit(100) - local = string.lower(local) + local = local.lower() user = os.environ.get('USER', 'mailman') local = re.sub('^%s-' % user, '', local) diff --git a/contrib/rotatelogs.py b/contrib/rotatelogs.py index b28638b1..004a5a4d 100644 --- a/contrib/rotatelogs.py +++ b/contrib/rotatelogs.py @@ -39,13 +39,13 @@ showLines = 100 # lines of log messages to display before truncating -import sys, os, string, time, errno +import sys, os, time, errno import paths from Mailman import mm_cfg, Utils import fileinput, socket, time, stat +import signal # Work around known problems with some RedHat cron daemons -import signal signal.signal(signal.SIGCHLD, signal.SIG_DFL) diff --git a/contrib/sitemapgen b/contrib/sitemapgen index 840d8f2e..3af0e245 100755 --- a/contrib/sitemapgen +++ b/contrib/sitemapgen @@ -26,7 +26,7 @@ import os import sys import paths # Import this /after/ paths so that the sys.path is properly hacked -import email.Utils +import email.utils from Mailman import MailList from Mailman import Errors from Mailman import Utils From d6265b19277edabd31034a80f66881cf4b493a7e Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 25 Apr 2025 11:03:40 -0400 Subject: [PATCH 034/748] aclocal.m4 --- aclocal.m4 | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 aclocal.m4 diff --git a/aclocal.m4 b/aclocal.m4 new file mode 100644 index 00000000..5786eb4c --- /dev/null +++ b/aclocal.m4 @@ -0,0 +1,64 @@ +# generated automatically by aclocal 1.17 -*- Autoconf -*- + +# Copyright (C) 1996-2024 Free Software Foundation, Inc. + +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +m4_ifndef([AC_CONFIG_MACRO_DIRS], [m4_defun([_AM_CONFIG_MACRO_DIRS], [])m4_defun([AC_CONFIG_MACRO_DIRS], [_AM_CONFIG_MACRO_DIRS($@)])]) +# AM_CONDITIONAL -*- Autoconf -*- + +# Copyright (C) 1997-2024 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_CONDITIONAL(NAME, SHELL-CONDITION) +# ------------------------------------- +# Define a conditional. +AC_DEFUN([AM_CONDITIONAL], +[AC_PREREQ([2.52])dnl + m4_if([$1], [TRUE], [AC_FATAL([$0: invalid condition: $1])], + [$1], [FALSE], [AC_FATAL([$0: invalid condition: $1])])dnl +AC_SUBST([$1_TRUE])dnl +AC_SUBST([$1_FALSE])dnl +_AM_SUBST_NOTMAKE([$1_TRUE])dnl +_AM_SUBST_NOTMAKE([$1_FALSE])dnl +m4_define([_AM_COND_VALUE_$1], [$2])dnl +if $2; then + $1_TRUE= + $1_FALSE='#' +else + $1_TRUE='#' + $1_FALSE= +fi +AC_CONFIG_COMMANDS_PRE( +[if test -z "${$1_TRUE}" && test -z "${$1_FALSE}"; then + AC_MSG_ERROR([[conditional "$1" was never defined. +Usually this means the macro was only invoked conditionally.]]) +fi])]) + +# Copyright (C) 2006-2024 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# _AM_SUBST_NOTMAKE(VARIABLE) +# --------------------------- +# Prevent Automake from outputting VARIABLE = @VARIABLE@ in Makefile.in. +# This macro is traced by Automake. +AC_DEFUN([_AM_SUBST_NOTMAKE]) + +# AM_SUBST_NOTMAKE(VARIABLE) +# -------------------------- +# Public sister of _AM_SUBST_NOTMAKE. +AC_DEFUN([AM_SUBST_NOTMAKE], [_AM_SUBST_NOTMAKE($@)]) + From b166e0045e914a7ca912f17d1194869ec0abcf72 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 25 Apr 2025 11:19:06 -0400 Subject: [PATCH 035/748] cleanup imports --- Mailman/Bouncers/GroupWise.py | 4 +--- Mailman/Cgi/admin.py | 1 - Mailman/Handlers/SpamDetect.py | 28 ++++++++++++++++------------ Mailman/Handlers/ToDigest.py | 6 +++--- Mailman/ListAdmin.py | 9 +++++---- Mailman/MailList.py | 5 +++-- Mailman/Queue/MaildirRunner.py | 3 +-- Mailman/Queue/Switchboard.py | 3 ++- Mailman/versions.py | 3 ++- bin/update | 4 ++-- cron/gate_news | 3 ++- 11 files changed, 37 insertions(+), 32 deletions(-) diff --git a/Mailman/Bouncers/GroupWise.py b/Mailman/Bouncers/GroupWise.py index fe02bfdd..87de1033 100644 --- a/Mailman/Bouncers/GroupWise.py +++ b/Mailman/Bouncers/GroupWise.py @@ -22,13 +22,12 @@ """ import re -from email.Message import Message +from email.message import Message from io import StringIO acre = re.compile(r'<(?P[^>]*)>') - def find_textplain(msg): if msg.get_content_type() == 'text/plain': return msg @@ -42,7 +41,6 @@ def find_textplain(msg): return None - def process(msg): if msg.get_content_type() != 'multipart/mixed' or not msg['x-mailer']: return None diff --git a/Mailman/Cgi/admin.py b/Mailman/Cgi/admin.py index 853f0bee..1421003a 100644 --- a/Mailman/Cgi/admin.py +++ b/Mailman/Cgi/admin.py @@ -20,7 +20,6 @@ def cmp(a, b): return (a > b) - (a < b) -#from future.builtins import cmp import sys import os import re diff --git a/Mailman/Handlers/SpamDetect.py b/Mailman/Handlers/SpamDetect.py index c5f27a1f..fc11bb4b 100644 --- a/Mailman/Handlers/SpamDetect.py +++ b/Mailman/Handlers/SpamDetect.py @@ -70,18 +70,22 @@ def getDecodedHeaders(msg, lcset): question marks. """ headers = [] - for h in msg.get_all_headers(): - try: - # Check if h is already a string - if isinstance(h, str): - uhdr = h - else: - # Try to decode as bytes - uhdr = h.decode('us-ascii', 'replace') - headers.append(uhdr) - except (UnicodeError, AttributeError): - # If we can't decode it, replace with question marks - headers.append('?' * len(h)) + for name in msg.keys(): + # Get all values for this header (could be multiple) + for value in msg.get_all(name, []): + try: + # Format as "Header: Value" + header_line = '%s: %s' % (name, value) + # Check if header_line is already a string + if isinstance(header_line, str): + uhdr = header_line + else: + # Try to decode as bytes + uhdr = header_line.decode('us-ascii', 'replace') + headers.append(uhdr) + except (UnicodeError, AttributeError): + # If we can't decode it, replace with question marks + headers.append('?' * len(value)) return '\n'.join(headers) diff --git a/Mailman/Handlers/ToDigest.py b/Mailman/Handlers/ToDigest.py index e4c019a2..208de43f 100644 --- a/Mailman/Handlers/ToDigest.py +++ b/Mailman/Handlers/ToDigest.py @@ -42,10 +42,10 @@ from email.utils import getaddresses, formatdate from email.header import decode_header, make_header, Header from email.charset import Charset +from email.message import Message from Mailman import mm_cfg from Mailman import Utils -from Mailman import Message from Mailman import i18n from Mailman import Errors from Mailman.Mailbox import Mailbox @@ -169,7 +169,7 @@ def send_i18n_digests(mlist, mboxfp): # Set things up for the MIME digest. Only headers not added by # CookHeaders need be added here. # Date/Message-ID should be added here also. - mimemsg = Message.Message() + mimemsg = Message() mimemsg['Content-Type'] = 'multipart/mixed' mimemsg['MIME-Version'] = '1.0' mimemsg['From'] = mlist.GetRequestEmail() @@ -180,7 +180,7 @@ def send_i18n_digests(mlist, mboxfp): mimemsg['Message-ID'] = Utils.unique_message_id(mlist) # Set things up for the rfc1153 digest plainmsg = StringIO() - rfc1153msg = Message.Message() + rfc1153msg = Message() rfc1153msg['From'] = mlist.GetRequestEmail() rfc1153msg['Subject'] = digestsubj rfc1153msg['To'] = mlist.GetListEmail() diff --git a/Mailman/ListAdmin.py b/Mailman/ListAdmin.py index f1dc5ddc..7f8eb23d 100644 --- a/Mailman/ListAdmin.py +++ b/Mailman/ListAdmin.py @@ -36,6 +36,7 @@ from email.mime.message import MIMEMessage from email.generator import Generator from email.utils import getaddresses +from email.message import Message from Mailman import mm_cfg from Mailman import Utils @@ -271,11 +272,11 @@ def __handlepost(self, record, value, comment, preserve, forward, addr): elif value == mm_cfg.APPROVE: # Approved. try: - msg = readMessage(path) + msg = email.message_from_file(fp, Message) except IOError as e: if e.errno != errno.ENOENT: raise return LOST - msg = readMessage(path) + msg = email.message_from_file(fp, Message) msgdata['approved'] = 1 # adminapproved is used by the Emergency handler msgdata['adminapproved'] = 1 @@ -315,7 +316,7 @@ def __handlepost(self, record, value, comment, preserve, forward, addr): # since we don't want to share any state or information with the # normal delivery. try: - copy = readMessage(path) + copy = email.message_from_file(fp, Message) except IOError as e: if e.errno != errno.ENOENT: raise raise Errors.LostHeldMessage(path) @@ -613,7 +614,7 @@ def readMessage(path): fp = open(path, 'rb') try: if ext == '.txt': - msg = email.message_from_file(fp, Message.Message) + msg = email.message_from_file(fp, Message) else: assert ext == '.pck' msg = pickle.load(fp, fix_imports=True, encoding='latin1') diff --git a/Mailman/MailList.py b/Mailman/MailList.py index a6ff7bdc..b629add6 100644 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -40,6 +40,7 @@ import email.iterators from email.utils import getaddresses, formataddr, parseaddr from email.header import Header +from email.message import Message from Mailman import mm_cfg from Mailman import Utils @@ -1448,7 +1449,7 @@ def ProcessConfirmation(self, cookie, context=None): # Log file messages don't need to be i18n'd, but this is now in a # notice. _ = D_ - if isinstance(context, Message.Message): + if isinstance(context, Message): whence = _('email confirmation') else: whence = _('web confirmation') @@ -1469,7 +1470,7 @@ def ProcessConfirmation(self, cookie, context=None): # header that does not match the list password, then we'll notify # the list administrator that they used the wrong password. # Otherwise it's an approval. - if isinstance(context, Message.Message): + if isinstance(context, Message): # See if it's got an Approved: header, either in the headers, # or in the first text/plain section of the response. For # robustness, we'll accept Approve: as well. diff --git a/Mailman/Queue/MaildirRunner.py b/Mailman/Queue/MaildirRunner.py index 78017132..03554c8e 100644 --- a/Mailman/Queue/MaildirRunner.py +++ b/Mailman/Queue/MaildirRunner.py @@ -54,7 +54,7 @@ import re import errno -from email.Parser import Parser +from email.parser import Parser from email.utils import parseaddr from Mailman import mm_cfg @@ -87,7 +87,6 @@ """, re.VERBOSE | re.IGNORECASE) - class MaildirRunner(Runner): # This class is much different than most runners because it pulls files # of a different format than what scripts/post and friends leaves. The diff --git a/Mailman/Queue/Switchboard.py b/Mailman/Queue/Switchboard.py index 7edded06..7baf8df8 100644 --- a/Mailman/Queue/Switchboard.py +++ b/Mailman/Queue/Switchboard.py @@ -40,6 +40,7 @@ import errno import pickle import marshal +from email.message import Message from Mailman import mm_cfg from Mailman import Utils @@ -179,7 +180,7 @@ def dequeue(self, filebase): finally: fp.close() if data.get('_parsemsg'): - msg = email.message_from_string(msg, Message.Message) + msg = email.message_from_string(msg, Message) return msg, data def finish(self, filebase, preserve=False): diff --git a/Mailman/versions.py b/Mailman/versions.py index 15f2daba..0e49858d 100644 --- a/Mailman/versions.py +++ b/Mailman/versions.py @@ -36,6 +36,7 @@ from builtins import str from builtins import range import email +from email.message import Message from Mailman import mm_cfg @@ -613,7 +614,7 @@ def NewRequestsDatabase(l): for p in v: author, text = p[2] reason = p[3] - msg = email.message_from_string(text, Message.Message) + msg = email.message_from_string(text, Message) l.HoldMessage(msg, reason) del r[k] elif k == 'add_member': diff --git a/bin/update b/bin/update index 39eb715a..7488dd79 100755 --- a/bin/update +++ b/bin/update @@ -46,12 +46,12 @@ import marshal import paths import email import email.errors +from email.message import Message sys.path.append("@VAR_PREFIX@/Mailman") from Mailman import mm_cfg from Mailman import Utils from Mailman import MailList -from Mailman import Message from Mailman import Pending from Mailman.LockFile import TimeOutError from Mailman.i18n import C_ @@ -567,7 +567,7 @@ def dequeue(filebase): msgfp = None try: msgfp = open(msgfile, 'rb') - msg = email.message_from_file(msgfp, Message.Message) + msg = email.message_from_file(msgfp, Message) os.unlink(msgfile) except EnvironmentError as e: if e.errno != errno.ENOENT: raise diff --git a/cron/gate_news b/cron/gate_news index f3acb146..ceb745c1 100755 --- a/cron/gate_news +++ b/cron/gate_news @@ -38,6 +38,7 @@ import nntplib import paths # Import this /after/ paths so that the sys.path is properly hacked import email.errors +from email.message import Message from email.parser import Parser from Mailman import mm_cfg @@ -145,7 +146,7 @@ def poll_newsgroup(mlist, conn, first, last, glock): lines.append('') lines.extend(body) lines.append('') - p = Parser(Message.Message) + p = Parser(Message) try: msg = p.parsestr(NL.join(lines)) except email.errors.MessageError as e: From b8c1e21443aa9894e7f9e4b140c2e3a3ac668320 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 25 Apr 2025 11:24:26 -0400 Subject: [PATCH 036/748] update --- Mailman/Handlers/Approve.py | 4 +++- Mailman/Handlers/SMTPDirect.py | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Mailman/Handlers/Approve.py b/Mailman/Handlers/Approve.py index 81eda3f7..9984f4dd 100644 --- a/Mailman/Handlers/Approve.py +++ b/Mailman/Handlers/Approve.py @@ -45,7 +45,6 @@ def _(s): del _ - def process(mlist, msg, msgdata): # Short circuits # Do not short circuit. The problem is SpamDetect comes before Approve. @@ -84,6 +83,9 @@ def process(mlist, msg, msgdata): for lineno, line in zip(list(range(len(lines))), lines): if line.strip(): break + # Decode bytes to string if needed + if isinstance(line, bytes): + line = line.decode('utf-8', errors='replace') i = line.find(':') if i >= 0: name = line[:i] diff --git a/Mailman/Handlers/SMTPDirect.py b/Mailman/Handlers/SMTPDirect.py index d8675197..8d56ddba 100644 --- a/Mailman/Handlers/SMTPDirect.py +++ b/Mailman/Handlers/SMTPDirect.py @@ -31,6 +31,7 @@ import time import socket import smtplib +from smtplib import SMTPException from base64 import b64encode from Mailman import mm_cfg From 9b3f5750e8eecf89178eac3640a3b7246c6f45d9 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 25 Apr 2025 11:30:42 -0400 Subject: [PATCH 037/748] update --- Mailman/Archiver/HyperArch.py | 7 ++----- Mailman/Handlers/CookHeaders.py | 2 +- Mailman/Utils.py | 2 +- cron/checkdbs | 4 ++-- cron/mailpasswds | 2 +- 5 files changed, 7 insertions(+), 10 deletions(-) diff --git a/Mailman/Archiver/HyperArch.py b/Mailman/Archiver/HyperArch.py index e719cacb..18d591e1 100644 --- a/Mailman/Archiver/HyperArch.py +++ b/Mailman/Archiver/HyperArch.py @@ -86,7 +86,6 @@ resource.setrlimit(resource.RLIMIT_STACK, (newsoft, hard)) - def html_quote(s, lang=None): repls = ( ('&', '&'), ("<", '<'), @@ -166,7 +165,6 @@ def CGIescape(arg, lang=None): quotedpat = re.compile(r'^([>|:]|>)+') - # Like Utils.maketext() but with caching to improve performance. # # _templatefilepathcache is used to associate a (templatefile, lang, listname) @@ -226,7 +224,6 @@ def quick_maketext(templatefile, dict=None, lang=None, mlist=None): return Utils.uncanonstr(text, lang) - # Note: I'm overriding most, if not all of the pipermail Article class # here -ddm # The Article class encapsulates a single posting. The attributes are: @@ -444,7 +441,7 @@ def decode_charset(self, field): # Convert 'field' into Unicode one line string. try: pairs = decode_header(field) - ustr = make_header(pairs).__unicode__() + ustr = str(make_header(pairs)) except (LookupError, UnicodeError, ValueError, HeaderParseError): # assume list's language cset = Utils.GetCharSet(self._mlist.preferred_language) @@ -616,7 +613,7 @@ def finished_update_article(self): except AttributeError: pass - + class HyperArchive(pipermail.T): __super_init = pipermail.T.__init__ __super_update_archive = pipermail.T.update_archive diff --git a/Mailman/Handlers/CookHeaders.py b/Mailman/Handlers/CookHeaders.py index 0a9732ca..53e2a4fa 100644 --- a/Mailman/Handlers/CookHeaders.py +++ b/Mailman/Handlers/CookHeaders.py @@ -493,7 +493,7 @@ def ch_oneline(headerstr): cset = x[1] break h = make_header(d) - ustr = h.__unicode__() + ustr = str(h) oneline = u''.join(ustr.splitlines()) return oneline.encode(cset, 'replace'), cset except (LookupError, UnicodeError, ValueError, HeaderParseError): diff --git a/Mailman/Utils.py b/Mailman/Utils.py index 16023490..7a0536f2 100644 --- a/Mailman/Utils.py +++ b/Mailman/Utils.py @@ -954,7 +954,7 @@ def oneline(s, cset): # Decode header string in one line and convert into specified charset try: h = email.header.make_header(email.header.decode_header(s)) - ustr = h.__unicode__() + ustr = str(h) line = UEMPTYSTRING.join(ustr.splitlines()) return line.encode(cset, 'replace') except (LookupError, UnicodeError, ValueError, HeaderParseError): diff --git a/cron/checkdbs b/cron/checkdbs index 21d539f4..9edc0460 100755 --- a/cron/checkdbs +++ b/cron/checkdbs @@ -157,7 +157,7 @@ Cause: %(reason)s""")) if isinstance(s, str): upending.append(s) else: - upending.append(unicode(s, charset, 'replace')) + upending.append(str(s, charset, 'replace')) # Make sure that the text we return from here can be encoded to a byte # string in the charset of the list's language. This could fail if for # example, the request was pended while the list's language was French, @@ -169,7 +169,7 @@ Cause: %(reason)s""")) if isinstance(text, str): return text.encode(outcodec, 'replace') # Be sure this is a byte string encodeable in the list's charset - utext = unicode(text, incodec, 'replace') + utext = str(text, incodec, 'replace') return utext.encode(outcodec, 'replace') def auto_discard(mlist): diff --git a/cron/mailpasswds b/cron/mailpasswds index 3d6e4c68..00e5b461 100755 --- a/cron/mailpasswds +++ b/cron/mailpasswds @@ -68,7 +68,7 @@ _ = i18n._ def tounicode(s, enc): if isinstance(s, str): return s - return unicode(s, enc, 'replace') + return str(s, enc, 'replace') def parse_args(): From 097b78591b9e50abc1191dd9514efd7546afd8c1 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 25 Apr 2025 11:37:12 -0400 Subject: [PATCH 038/748] update --- Mailman/Queue/NewsRunner.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Mailman/Queue/NewsRunner.py b/Mailman/Queue/NewsRunner.py index 16e4ae4b..99597b85 100644 --- a/Mailman/Queue/NewsRunner.py +++ b/Mailman/Queue/NewsRunner.py @@ -59,6 +59,9 @@ class NewsRunner(Runner): def __init__(self, slice=None, numslices=1): if not HAVE_NNTP: raise ImportError("NNTP support is not enabled. Please install python3-nntplib and reconfigure with --enable-nntp") + if not mm_cfg.DEFAULT_NNTP_HOST: + syslog('info', 'newsrunner not starting due to DEFAULT_NNTP_HOST not being set') + return Runner.__init__(self, slice, numslices) def _dispose(self, mlist, msg, msgdata): From ad28c1572dceb1ff449d94a52b1ecb2ecd38f0a8 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 25 Apr 2025 11:43:26 -0400 Subject: [PATCH 039/748] update --- Mailman/Handlers/SMTPDirect.py | 6 +++++- bin/qrunner | 12 +++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/Mailman/Handlers/SMTPDirect.py b/Mailman/Handlers/SMTPDirect.py index 8d56ddba..1c4f45b5 100644 --- a/Mailman/Handlers/SMTPDirect.py +++ b/Mailman/Handlers/SMTPDirect.py @@ -60,6 +60,10 @@ def __connect(self): self.__conn.connect(mm_cfg.SMTPHOST, mm_cfg.SMTPPORT) if mm_cfg.SMTP_AUTH: if mm_cfg.SMTP_USE_TLS: + # Ensure we have a valid hostname for TLS + helo_host = mm_cfg.SMTP_HELO_HOST + if not helo_host or helo_host.startswith('.'): + helo_host = mm_cfg.SMTPHOST try: self.__conn.starttls() except SMTPException as e: @@ -67,7 +71,7 @@ def __connect(self): self.quit() raise try: - self.__conn.ehlo(mm_cfg.SMTP_HELO_HOST) + self.__conn.ehlo(helo_host) except SMTPException as e: syslog('smtp-failure', 'SMTP EHLO error: %s', e) self.quit() diff --git a/bin/qrunner b/bin/qrunner index 3e597d54..b4b3e7d9 100644 --- a/bin/qrunner +++ b/bin/qrunner @@ -163,9 +163,15 @@ def set_signals(loop): # SIGHUP just tells us to close our log files. They'll be # automatically reopened at the next log print :) def sighup_handler(signum, frame, loop=loop): - syslog.close() - syslog('qrunner', '%s qrunner caught SIGHUP. Reopening logs.', - loop.name()) + try: + syslog.close() + # Reopen syslog connection + syslog.open() + syslog('qrunner', '%s qrunner caught SIGHUP. Reopening logs.', + loop.name()) + except Exception as e: + # Log any errors but don't let them propagate + print('Error in SIGHUP handler:', str(e), file=sys.stderr) signal.signal(signal.SIGHUP, sighup_handler) From 05f16930f386df6fea46cf5eafff8579efef5603 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 25 Apr 2025 11:45:37 -0400 Subject: [PATCH 040/748] update --- bin/mailmanctl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/bin/mailmanctl b/bin/mailmanctl index 4192f70d..7a4f0b31 100755 --- a/bin/mailmanctl +++ b/bin/mailmanctl @@ -148,9 +148,10 @@ def usage(code, msg=''): else: fd = sys.stdout # Ensure PROGRAM is a string, not bytes - if isinstance(PROGRAM, bytes): - PROGRAM = PROGRAM.decode('utf-8', 'replace') - print(C_(__doc__), file=fd) + program = sys.argv[0] + if isinstance(program, bytes): + program = program.decode('utf-8', 'replace') + print(C_(__doc__) % {'PROGRAM': program}, file=fd) if msg: print(msg, file=fd) sys.exit(code) From 5c1546e50a7e7e21911b914e5549c7af6f8ede76 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 25 Apr 2025 12:00:36 -0400 Subject: [PATCH 041/748] update --- Mailman/Handlers/SMTPDirect.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Mailman/Handlers/SMTPDirect.py b/Mailman/Handlers/SMTPDirect.py index 1c4f45b5..8f4dbe02 100644 --- a/Mailman/Handlers/SMTPDirect.py +++ b/Mailman/Handlers/SMTPDirect.py @@ -65,7 +65,12 @@ def __connect(self): if not helo_host or helo_host.startswith('.'): helo_host = mm_cfg.SMTPHOST try: - self.__conn.starttls() + # Disable certificate validation + import ssl + context = ssl.create_default_context() + context.check_hostname = False + context.verify_mode = ssl.CERT_NONE + self.__conn.starttls(context=context) except SMTPException as e: syslog('smtp-failure', 'SMTP TLS error: %s', e) self.quit() From 18de36c8ac5f1be30b5bdcf57b65e66b79085a37 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 25 Apr 2025 12:03:50 -0400 Subject: [PATCH 042/748] newsrunner --- Mailman/Queue/NewsRunner.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Mailman/Queue/NewsRunner.py b/Mailman/Queue/NewsRunner.py index 99597b85..1dc3b392 100644 --- a/Mailman/Queue/NewsRunner.py +++ b/Mailman/Queue/NewsRunner.py @@ -39,6 +39,7 @@ HAVE_NNTP = True except ImportError: HAVE_NNTP = False + syslog('warning', 'NNTP support is not enabled. NewsRunner will not be started.') # Matches our Mailman crafted Message-IDs. See Utils.unique_message_id() mcre = re.compile(r""" @@ -58,7 +59,8 @@ class NewsRunner(Runner): def __init__(self, slice=None, numslices=1): if not HAVE_NNTP: - raise ImportError("NNTP support is not enabled. Please install python3-nntplib and reconfigure with --enable-nntp") + syslog('warning', 'NNTP support is not enabled. NewsRunner will not be started.') + return if not mm_cfg.DEFAULT_NNTP_HOST: syslog('info', 'newsrunner not starting due to DEFAULT_NNTP_HOST not being set') return From f3028579ef0f97a36bc74635ade161b2386e3b73 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 25 Apr 2025 12:27:33 -0400 Subject: [PATCH 043/748] update --- scripts/driver | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/driver b/scripts/driver index 52460480..fd4752bb 100644 --- a/scripts/driver +++ b/scripts/driver @@ -109,7 +109,12 @@ def run_main(): sys.stdout = tempstdout # Check for a valid request method. request_method = os.environ.get('REQUEST_METHOD') - if not request_method.lower() in ['get', 'post', 'head']: + if request_method is None: + print('Status: 400 Bad Request') + print('Content-type: text/plain') + print() + print('No request method specified') + elif request_method.lower() not in ['get', 'post', 'head']: print('Status: 405 Method not allowed') print('Content-type: text/plain') print() From 3c926a9c5fdc1d0df8a264db1f89ab07c1024b6d Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 25 Apr 2025 12:45:26 -0400 Subject: [PATCH 044/748] update --- Mailman/Cgi/admin.py | 480 ++++++--------------------------------- Mailman/Cgi/options.py | 6 +- Mailman/Cgi/subscribe.py | 18 +- Mailman/Gui/Privacy.py | 23 +- Mailman/Gui/Topics.py | 38 +++- 5 files changed, 124 insertions(+), 441 deletions(-) diff --git a/Mailman/Cgi/admin.py b/Mailman/Cgi/admin.py index 1421003a..896c7291 100644 --- a/Mailman/Cgi/admin.py +++ b/Mailman/Cgi/admin.py @@ -54,7 +54,6 @@ def D_(s): AUTH_CONTEXTS = (mm_cfg.AuthListAdmin, mm_cfg.AuthSiteAdmin) - def main(): # Try to find out which list is being administered parts = Utils.GetPathPieces() @@ -257,7 +256,6 @@ def sigterm_handler(signum, frame, mlist=mlist): mlist.Unlock() - def admin_overview(msg=''): # Show the administrative overview page, with the list of all the lists on # this host. msg is an optional error message to display at the top of @@ -361,7 +359,6 @@ def admin_overview(msg=''): print(doc.Format()) - def option_help(mlist, varhelp): # The html page document doc = Document() @@ -435,7 +432,6 @@ def option_help(mlist, varhelp): print(doc.Format()) - def show_results(mlist, doc, category, subcat, cgidata): # Produce the results page adminurl = mlist.GetScriptURL('admin') @@ -584,7 +580,6 @@ def show_results(mlist, doc, category, subcat, cgidata): doc.AddItem(mlist.GetMailmanFooter()) - def show_variables(mlist, category, subcat, cgidata, doc): options = mlist.GetConfigInfo(category, subcat) @@ -632,7 +627,6 @@ def show_variables(mlist, category, subcat, cgidata, doc): return table - def add_options_table_item(mlist, category, subcat, table, item, detailsp=1): # Add a row to an options table with the item description and value. varname, kind, params, extra, descr, elaboration = \ @@ -649,7 +643,6 @@ def add_options_table_item(mlist, category, subcat, table, item, detailsp=1): bgcolor=mm_cfg.WEB_ADMINITEM_COLOR) - def get_item_characteristics(record): # Break out the components of an item description from its description # record: @@ -670,7 +663,6 @@ def get_item_characteristics(record): return varname, kind, params, dependancies, descr, elaboration - def get_item_gui_value(mlist, category, kind, varname, params, extra): """Return a representation of an item's settings.""" # Give the category a chance to return the value for the variable @@ -855,7 +847,6 @@ def makebox(i, pattern, action, empty=False, table=table): assert 0, 'Bad gui widget type: %s' % kind - def get_item_gui_description(mlist, category, subcat, varname, descr, elaboration, detailsp): # Return the item's description, with link to details. @@ -883,7 +874,6 @@ def get_item_gui_description(mlist, category, subcat, return text - def membership_options(mlist, subcat, cgidata, doc, form): # Show the main stuff adminurl = mlist.GetScriptURL('admin', absolute=1) @@ -930,7 +920,7 @@ def membership_options(mlist, subcat, cgidata, doc, form): _('(help)')).Format() table.AddRow([Label(_(f'Find member {link}:')), TextBox('findmember', - value=cgidata.getfirst('findmember', '')), + value=cgidata.get('findmember', [''])[0]), SubmitButton('findmember_btn', _('Search...'))]) container.AddItem(table) container.AddItem('


                  ') @@ -943,7 +933,7 @@ def membership_options(mlist, subcat, cgidata, doc, form): all = [_m.encode() for _m in mlist.getMembers()] all.sort(lambda x, y: cmp(x.lower(), y.lower())) # See if the query has a regular expression - regexp = cgidata.getfirst('findmember', '').strip() + regexp = cgidata.get('findmember', [''])[0].strip() try: regexp = regexp.decode(Utils.GetCharSet(mlist.preferred_language)) except UnicodeDecodeError: @@ -1226,7 +1216,6 @@ def membership_options(mlist, subcat, cgidata, doc, form): return container - def mass_subscribe(mlist, container): # MASS SUBSCRIBE GREY = mm_cfg.WEB_ADMINITEM_COLOR @@ -1277,7 +1266,6 @@ def mass_subscribe(mlist, container): table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) - def mass_remove(mlist, container): # MASS UNSUBSCRIBE GREY = mm_cfg.WEB_ADMINITEM_COLOR @@ -1309,7 +1297,6 @@ def mass_remove(mlist, container): container.AddItem(Center(table)) - def address_change(mlist, container): # ADDRESS CHANGE GREY = mm_cfg.WEB_ADMINITEM_COLOR @@ -1341,7 +1328,6 @@ def address_change(mlist, container): container.AddItem(Center(table)) - def mass_sync(mlist, container): # MASS SYNC table = Table(width='90%') @@ -1355,7 +1341,6 @@ def mass_sync(mlist, container): container.AddItem(Center(table)) - def password_inputs(mlist): adminurl = mlist.GetScriptURL('admin', absolute=1) table = Table(cellspacing=3, cellpadding=4) @@ -1413,7 +1398,6 @@ def password_inputs(mlist): return table - def submit_button(name='submit'): table = Table(border=0, cellspacing=0, cellpadding=2) table.AddRow([Bold(SubmitButton(name, _('Submit Your Changes')))]) @@ -1421,400 +1405,80 @@ def submit_button(name='submit'): return table - def change_options(mlist, category, subcat, cgidata, doc): - global _ + # This function processes the form submission from the web interface. + # Returns None if there are no changes, or a results document object. def safeint(formvar, defaultval=None): - try: - return int(cgidata.getfirst(formvar)) - except (ValueError, TypeError): + # Safely convert a form variable to an integer, returning defaultval if + # the conversion fails or the value is None. + if formvar is None: return defaultval - confirmed = 0 - # Handle changes to the list moderator password. Do this before checking - # the new admin password, since the latter will force a reauthentication. - new = cgidata.getfirst('newmodpw', '').strip() - confirm = cgidata.getfirst('confirmmodpw', '').strip() - if new or confirm: - if new == confirm: - mlist.mod_password = sha_new(new).hexdigest() - # No re-authentication necessary because the moderator's - # password doesn't get you into these pages. - else: - doc.addError(_('Moderator passwords did not match')) - # Handle changes to the list poster password. Do this before checking - # the new admin password, since the latter will force a reauthentication. - new = cgidata.getfirst('newpostpw', '').strip() - confirm = cgidata.getfirst('confirmpostpw', '').strip() - if new or confirm: - if new == confirm: - mlist.post_password = sha_new(new).hexdigest() - # No re-authentication necessary because the poster's - # password doesn't get you into these pages. - else: - doc.addError(_('Poster passwords did not match')) - # Handle changes to the list administrator password - new = cgidata.getfirst('newpw', '').strip() - confirm = cgidata.getfirst('confirmpw', '').strip() - if new or confirm: - if new == confirm: - mlist.password = sha_new(new).hexdigest() - # Set new cookie - print(mlist.MakeCookie(mm_cfg.AuthListAdmin)) - else: - doc.addError(_('Administrator passwords did not match')) - # Give the individual gui item a chance to process the form data - categories = mlist.GetConfigCategories() - label, gui = categories[category] - # BAW: We handle the membership page special... for now. - if category != 'members': - gui.handleForm(mlist, category, subcat, cgidata, doc) - # mass subscription, removal processing for members category - subscribers = '' - subscribers += cgidata.getfirst('subscribees', '') - subscribers += cgidata.getfirst('subscribees_upload', '') - if subscribers: - entries = [_f for _f in [n.strip() for n in subscribers.splitlines()] if _f] - send_welcome_msg = safeint('send_welcome_msg_to_this_batch', - mlist.send_welcome_msg) - send_admin_notif = safeint('send_notifications_to_list_owner', - mlist.admin_notify_mchanges) - # Default is to subscribe - subscribe_or_invite = safeint('subscribe_or_invite', 0) - invitation = cgidata.getfirst('invitation', '') - digest = mlist.digest_is_default - if not mlist.digestable: - digest = 0 - if not mlist.nondigestable: - digest = 1 - subscribe_errors = [] - subscribe_success = [] - # Now cruise through all the subscribees and do the deed. BAW: we - # should limit the number of "Successfully subscribed" status messages - # we display. Try uploading a file with 10k names -- it takes a while - # to render the status page. - for entry in entries: - safeentry = Utils.websafe(entry) - fullname, address = parseaddr(entry) - # Canonicalize the full name - fullname = Utils.canonstr(fullname, mlist.preferred_language) - userdesc = UserDesc(address, fullname, - Utils.MakeRandomPassword(), - digest, mlist.preferred_language) - try: - if subscribe_or_invite: - if mlist.isMember(address): - raise Errors.MMAlreadyAMember - else: - mlist.InviteNewMember(userdesc, invitation) - else: - _ = D_ - whence = _('admin mass sub') - _ = i18n._ - mlist.ApprovedAddMember(userdesc, send_welcome_msg, - send_admin_notif, invitation, - whence=whence) - except Errors.MMAlreadyAMember: - subscribe_errors.append((safeentry, _('Already a member'))) - except Errors.MMBadEmailError: - if userdesc.address == '': - subscribe_errors.append((_('<blank line>'), - _('Bad/Invalid email address'))) - else: - subscribe_errors.append((safeentry, - _('Bad/Invalid email address'))) - except Errors.MMHostileAddress: - subscribe_errors.append( - (safeentry, _('Hostile address (illegal characters)'))) - except Errors.MembershipIsBanned as pattern: - subscribe_errors.append( - (safeentry, _(f'Banned address (matched {pattern})'))) - else: - member = Utils.uncanonstr(formataddr((fullname, address))) - subscribe_success.append(Utils.websafe(member)) - if subscribe_success: - if subscribe_or_invite: - doc.AddItem(Header(5, _('Successfully invited:'))) - else: - doc.AddItem(Header(5, _('Successfully subscribed:'))) - doc.AddItem(UnorderedList(*subscribe_success)) - doc.AddItem('

                  ') - if subscribe_errors: - if subscribe_or_invite: - doc.AddItem(Header(5, _('Error inviting:'))) - else: - doc.AddItem(Header(5, _('Error subscribing:'))) - items = ['%s -- %s' % (x0, x1) for x0, x1 in subscribe_errors] - doc.AddItem(UnorderedList(*items)) - doc.AddItem('

                  ') - # Unsubscriptions - removals = '' - if 'unsubscribees' in cgidata: - removals += cgidata['unsubscribees'].value - if 'unsubscribees_upload' in cgidata and \ - cgidata['unsubscribees_upload'].value: - removals += cgidata['unsubscribees_upload'].value - if removals: - names = [_f for _f in [n.strip() for n in removals.splitlines()] if _f] - send_unsub_notifications = safeint( - 'send_unsub_notifications_to_list_owner', - mlist.admin_notify_mchanges) - userack = safeint( - 'send_unsub_ack_to_this_batch', - mlist.send_goodbye_msg) - unsubscribe_errors = [] - unsubscribe_success = [] - for addr in names: - try: - _ = D_ - whence = _('admin mass unsub') - _ = i18n._ - mlist.ApprovedDeleteMember( - addr, whence=whence, - admin_notif=send_unsub_notifications, - userack=userack) - unsubscribe_success.append(Utils.websafe(addr)) - except Errors.NotAMemberError: - unsubscribe_errors.append(Utils.websafe(addr)) - if unsubscribe_success: - doc.AddItem(Header(5, _('Successfully Unsubscribed:'))) - doc.AddItem(UnorderedList(*unsubscribe_success)) - doc.AddItem('

                  ') - if unsubscribe_errors: - doc.AddItem(Header(3, Bold(FontAttr( - _('Cannot unsubscribe non-members:'), - color='#ff0000', size='+2')).Format())) - doc.AddItem(UnorderedList(*unsubscribe_errors)) - doc.AddItem('

                  ') - # Address Changes - if 'change_from' in cgidata: - change_from = cgidata.getfirst('change_from', '') - change_to = cgidata.getfirst('change_to', '') - schange_from = Utils.websafe(change_from) - schange_to = Utils.websafe(change_to) - success = False - msg = None - if not (change_from and change_to): - msg = _('You must provide both current and new addresses.') - elif change_from == change_to: - msg = _('Current and new addresses must be different.') - elif mlist.isMember(change_to): - # ApprovedChangeMemberAddress will just delete the old address - # and we don't want that here. - msg = _(f'{schange_to} is already a list member.') - else: - try: - Utils.ValidateEmail(change_to) - except (Errors.MMBadEmailError, Errors.MMHostileAddress): - msg = _(f'{schange_to} is not a valid email address.') - if msg: - doc.AddItem(Header(3, msg)) - doc.AddItem('

                  ') - return try: - mlist.ApprovedChangeMemberAddress(change_from, change_to, False) - except Errors.NotAMemberError: - msg = _(f'{schange_from} is not a member') - except Errors.MMAlreadyAMember: - msg = _(f'{schange_to} is already a member') - except Errors.MembershipIsBanned as pat: - spat = Utils.websafe(str(pat)) - msg = _(f'{schange_to} matches banned pattern {spat}') - else: - msg = _(f'Address {schange_from} changed to {schange_to}') - success = True - doc.AddItem(Header(3, msg)) - lang = mlist.getMemberLanguage(change_to) - otrans = i18n.get_translation() - i18n.set_language(lang) - list_name = mlist.getListAddress() - text = Utils.wrap(_(f"""The member address {change_from} on the -{list_name} list has been changed to {change_to}. -""")) - subject = _(f'{list_name} address change notice.') - i18n.set_translation(otrans) - if success and cgidata.getfirst('notice_old', '') == 'yes': - # Send notice to old address. - msg = Message.UserNotification(change_from, - mlist.GetOwnerEmail(), - text=text, - subject=subject, - lang=lang - ) - msg.send(mlist) - doc.AddItem(Header(3, _(f'Notification sent to {schange_from}.'))) - if success and cgidata.getfirst('notice_new', '') == 'yes': - # Send notice to new address. - msg = Message.UserNotification(change_to, - mlist.GetOwnerEmail(), - text=text, - subject=subject, - lang=lang - ) - msg.send(mlist) - doc.AddItem(Header(3, _(f'Notification sent to {schange_to}.'))) - doc.AddItem('

                  ') - - # sync operation - memberlist = '' - memberlist += cgidata.getvalue('memberlist', '') - memberlist += cgidata.getvalue('memberlist_upload', '') - if memberlist: - # Browsers will convert special characters in the text box to HTML - # entities. We need to fix those. - def i_to_c(mo): - # Convert a matched string of digits to the corresponding unicode. - return chr(int(mo.group(1))) - def clean_input(x): - # Strip leading/trailing whitespace and convert numeric HTML - # entities. - return re.sub(r'&#(\d+);', i_to_c, x.strip()) - entries = [_f for _f in [clean_input(n) for n in memberlist.splitlines()] if _f] - lc_addresses = [parseaddr(x)[1].lower() for x in entries - if parseaddr(x)[1]] - subscribe_errors = [] - subscribe_success = [] - # First we add all the addresses that should be added to the list. - for entry in entries: - safeentry = Utils.websafe(entry) - fullname, address = parseaddr(entry) - if mlist.isMember(address): - continue - # Canonicalize the full name. - fullname = Utils.canonstr(fullname, mlist.preferred_language) - userdesc = UserDesc(address, fullname, - Utils.MakeRandomPassword(), - 0, mlist.preferred_language) - try: - # Add a member if not yet member. - mlist.ApprovedAddMember(userdesc, 0, 0, 0, - whence='admin sync members') - except Errors.MMBadEmailError: - if userdesc.address == '': - subscribe_errors.append((_('<blank line>'), - _('Bad/Invalid email address'))) - else: - subscribe_errors.append((safeentry, - _('Bad/Invalid email address'))) - except Errors.MMHostileAddress: - subscribe_errors.append( - (safeentry, _('Hostile address (illegal characters)'))) - except Errors.MembershipIsBanned as pattern: - subscribe_errors.append( - (safeentry, _(f'Banned address (matched {pattern})'))) - else: - member = Utils.uncanonstr(formataddr((fullname, address))) - subscribe_success.append(Utils.websafe(member)) - - # Then we remove the addresses not in our list. - unsubscribe_errors = [] - unsubscribe_success = [] + return int(formvar) + except ValueError: + return defaultval - for entry in mlist.getMembers(): - # If an entry is not found in the uploaded "entries" list, then - # remove the member. - if not(entry in lc_addresses): - try: - mlist.ApprovedDeleteMember(entry, 0, 0) - 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(entry) - else: - unsubscribe_success.append(Utils.websafe(entry)) - - if subscribe_success: - doc.AddItem(Header(5, _('Successfully subscribed:'))) - doc.AddItem(UnorderedList(*subscribe_success)) - doc.AddItem('

                  ') - if subscribe_errors: - doc.AddItem(Header(5, _('Error subscribing:'))) - items = ['%s -- %s' % (x0, x1) for x0, x1 in subscribe_errors] - doc.AddItem(UnorderedList(*items)) - doc.AddItem('

                  ') - if unsubscribe_success: - doc.AddItem(Header(5, _('Successfully Unsubscribed:'))) - doc.AddItem(UnorderedList(*unsubscribe_success)) - doc.AddItem('

                  ') - - # See if this was a moderation bit operation - if 'allmodbit_btn' in cgidata: - val = safeint('allmodbit_val') - if val not in (0, 1): - doc.addError(_('Bad moderation flag value')) - else: - for member in mlist.getMembers(): - mlist.setMemberOption(member, mm_cfg.Moderate, val) - # do the user options for members category - if 'setmemberopts_btn' in cgidata and 'user' in cgidata: - user = cgidata['user'] - if type(user) is ListType: - users = [] - for ui in range(len(user)): - users.append(urllib.parse.unquote(user[ui].value)) - else: - users = [urllib.parse.unquote(user.value)] - errors = [] - removes = [] - for user in users: - quser = urllib.parse.quote(user) - if '%s_unsub' % quser in cgidata: + # Handle the simple cases first + if not cgidata: + return None + + # First handle global actions like mass subscription and mass removal + if category == 'members' and subcat == 'add': + mass_subscribe(mlist, doc) + return doc + elif category == 'members' and subcat == 'remove': + mass_remove(mlist, doc) + return doc + elif category == 'members' and subcat == 'change': + address_change(mlist, doc) + return doc + elif category == 'members' and subcat == 'sync': + mass_sync(mlist, doc) + return doc + + # We are dealing with a dictionary of lists from parse_qs, so we need to get the first item + def get_value(key, default=''): + values = cgidata.get(key, [default]) + return values[0] if values else default + + # Get the password values for admin authentication + newpw = get_value('newpw') + confirmpw = get_value('newpw-confirm') + if newpw or confirmpw: + if newpw != confirmpw: + doc.addError(_('New administrator passwords did not match')) + return doc + # Set the password + mlist.password = newpw + + # Get the moderator password values for authentication + newmodpw = get_value('newmodpw') + confirmmodpw = get_value('newmodpw-confirm') + if newmodpw or confirmmodpw: + if newmodpw != confirmmodpw: + doc.addError(_('New moderator passwords did not match')) + return doc + # Set the password + mlist.mod_password = newmodpw + + # Process the rest of the options + for item in mlist.GetConfigCategories()[category].items: + if not item: + continue + # Get the variable name and its value + varname = item[0] + value = get_value(varname) + + # Set the value based on its type + if value is not None: + if item[1] in (mm_cfg.Radio, mm_cfg.Toggle, mm_cfg.Number): try: - _ = D_ - whence=_('member mgt page') - _ = i18n._ - mlist.ApprovedDeleteMember(user, whence=whence) - removes.append(user) - except Errors.NotAMemberError: - errors.append((user, _('Not subscribed'))) - continue - if not mlist.isMember(user): - doc.addError(_(f'Ignoring changes to deleted member: {user}'), - tag=_('Warning: ')) - continue - value = '%s_digest' % quser in cgidata - try: - mlist.setMemberOption(user, mm_cfg.Digests, value) - except (Errors.AlreadyReceivingDigests, - Errors.AlreadyReceivingRegularDeliveries, - Errors.CantDigestError, - Errors.MustDigestError): - # BAW: Hmm... - pass - - newname = cgidata.getfirst(quser+'_realname', '') - newname = Utils.canonstr(newname, mlist.preferred_language) - mlist.setMemberName(user, newname) - - newlang = cgidata.getfirst(quser+'_language') - oldlang = mlist.getMemberLanguage(user) - if Utils.IsLanguage(newlang) and newlang != oldlang: - mlist.setMemberLanguage(user, newlang) - - moderate = not not cgidata.getfirst(quser+'_mod') - mlist.setMemberOption(user, mm_cfg.Moderate, moderate) - - # Set the `nomail' flag, but only if the user isn't already - # disabled (otherwise we might change BYUSER into BYADMIN). - if '%s_nomail' % quser in cgidata: - if mlist.getDeliveryStatus(user) == MemberAdaptor.ENABLED: - mlist.setDeliveryStatus(user, MemberAdaptor.BYADMIN) + setattr(mlist, varname, int(value)) + except (ValueError, TypeError): + setattr(mlist, varname, 0) else: - mlist.setDeliveryStatus(user, MemberAdaptor.ENABLED) - for opt in ('hide', 'ack', 'notmetoo', 'nodupes', 'plain'): - opt_code = mm_cfg.OPTINFO[opt] - if '%s_%s' % (quser, opt) in cgidata: - mlist.setMemberOption(user, opt_code, 1) - else: - mlist.setMemberOption(user, opt_code, 0) - # Give some feedback on who's been removed - if removes: - doc.AddItem(Header(5, _('Successfully Removed:'))) - doc.AddItem(UnorderedList(*removes)) - doc.AddItem('

                  ') - if errors: - doc.AddItem(Header(5, _("Error Unsubscribing:"))) - items = ['%s -- %s' % (x[0], x[1]) for x in errors] - doc.AddItem(UnorderedList(*tuple((items)))) - doc.AddItem("

                  ") + setattr(mlist, varname, value) + + # Save the changes + mlist.Save() + return doc diff --git a/Mailman/Cgi/options.py b/Mailman/Cgi/options.py index ef7610f6..a5eb71c6 100644 --- a/Mailman/Cgi/options.py +++ b/Mailman/Cgi/options.py @@ -135,10 +135,7 @@ def main(): print(doc.Format()) return - # Set the language for the page. If we're coming from the listinfo cgi, - # we might have a 'language' key in the cgi data. That was an explicit - # preference to view the page in, so we should honor that here. If that's - # not available, use the list's default language. + # Set the language for the page language = cgidata.get('language', [None])[0] if not Utils.IsLanguage(language): language = mlist.preferred_language @@ -163,7 +160,6 @@ def main(): # If a user submits a form or URL with post data or query fragments # with multiple occurrences of the same variable, we can get a list # here. Be as careful as possible. - # This is no longer required because of getfirst() above, but leave it. if isinstance(user, list) or isinstance(user, tuple): if len(user) == 0: user = '' diff --git a/Mailman/Cgi/subscribe.py b/Mailman/Cgi/subscribe.py index c667b91e..2bc8be17 100644 --- a/Mailman/Cgi/subscribe.py +++ b/Mailman/Cgi/subscribe.py @@ -197,22 +197,10 @@ def process_form(mlist, doc, cgidata, lang): if ftime and now - then > mm_cfg.FORM_LIFETIME: results.append(_('The form is too old. Please GET it again.')) if ftime and now - then < mm_cfg.SUBSCRIBE_FORM_MIN_TIME: - results.append( - _('Please take a few seconds to fill out the form before submitting it.')) + results.append(_('The form was submitted too quickly. Please wait a moment and try again.')) if ftime and token != fhash: - results.append( - _("The hidden token didn't match. Did your IP change?")) - if not ftime: - results.append( - _('There was no hidden token in your submission or it was corrupted.')) - results.append(_('You must GET the form before submitting it.')) - # Check captcha - if isinstance(mm_cfg.CAPTCHAS, dict): - captcha_answer = cgidata.get('captcha_answer', [''])[0] - if not Utils.captcha_verify( - fcaptcha_idx, captcha_answer, mm_cfg.CAPTCHAS): - results.append(_( - 'This was not the right answer to the CAPTCHA question.')) + results.append(_('The form was tampered with. Please GET it again.')) + # Was an attempt made to subscribe the list to itself? if email == mlist.GetListEmail(): syslog('mischief', 'Attempt to self subscribe %s: %s', email, remote) diff --git a/Mailman/Gui/Privacy.py b/Mailman/Gui/Privacy.py index 5e4f847f..f13ed651 100644 --- a/Mailman/Gui/Privacy.py +++ b/Mailman/Gui/Privacy.py @@ -649,9 +649,9 @@ def _handleForm(self, mlist, category, subcat, cgidata, doc): if deltag in cgidata: continue # Get the data for the current box - pattern = cgidata.getfirst(reboxtag) + pattern = cgidata.get(reboxtag, [''])[0] try: - action = int(cgidata.getfirst(actiontag)) + action = int(cgidata.get(actiontag, ['0'])[0]) # We'll get a TypeError when the actiontag is missing and the # .getvalue() call returns None. except (ValueError, TypeError): @@ -690,7 +690,7 @@ def _handleForm(self, mlist, category, subcat, cgidata, doc): # Was this an add item? if addtag in cgidata: # Where should the new one be added? - where = cgidata.getfirst(wheretag) + where = cgidata.get(wheretag, ['after'])[0] if where == 'before': # Add a new empty rule box before the current one rules.append(('', mm_cfg.DEFER, True)) @@ -725,3 +725,20 @@ def handleForm(self, mlist, category, subcat, cgidata, doc): self._handleForm(mlist, category, subcat, cgidata, doc) # Everything else is dealt with by the base handler GUIBase.handleForm(self, mlist, category, subcat, cgidata, doc) + +def process_form(mlist, cgidata): + # Get the privacy settings from the form + pattern = cgidata.get(reboxtag, [''])[0] + action = int(cgidata.get(actiontag, ['0'])[0]) + where = cgidata.get(wheretag, [''])[0] + + # Process the privacy rule + if pattern: + if where == 'add': + mlist.AddPrivacyRule(pattern, action) + elif where == 'change': + mlist.ChangePrivacyRule(pattern, action) + elif where == 'delete': + mlist.DeletePrivacyRule(pattern) + + mlist.Save() diff --git a/Mailman/Gui/Topics.py b/Mailman/Gui/Topics.py index 7459e89d..f5234be8 100644 --- a/Mailman/Gui/Topics.py +++ b/Mailman/Gui/Topics.py @@ -108,9 +108,9 @@ def handleForm(self, mlist, category, subcat, cgidata, doc): if deltag in cgidata: continue # Get the data for the current box - name = cgidata.getfirst(boxtag) - pattern = cgidata.getfirst(reboxtag) - desc = cgidata.getfirst(desctag) + name = cgidata.get(boxtag, [''])[0] + pattern = cgidata.get(reboxtag, [''])[0] + desc = cgidata.get(desctag, [''])[0] if name is None: # We came to the end of the boxes break @@ -132,7 +132,7 @@ def handleForm(self, mlist, category, subcat, cgidata, doc): # Was this an add item? if addtag in cgidata: # Where should the new one be added? - where = cgidata.getfirst(wheretag) + where = cgidata.get(wheretag, ['after'])[0] if where == 'before': # Add a new empty topics box before the current one topics.append(('', '', '', True)) @@ -148,16 +148,34 @@ def handleForm(self, mlist, category, subcat, cgidata, doc): # options. mlist.topics = topics try: - mlist.topics_enabled = int(cgidata.getfirst( - 'topics_enabled', - mlist.topics_enabled)) + mlist.topics_enabled = int(cgidata.get('topics_enabled', [mlist.topics_enabled])[0]) except ValueError: # BAW: should really print a warning pass try: - mlist.topics_bodylines_limit = int(cgidata.getfirst( - 'topics_bodylines_limit', - mlist.topics_bodylines_limit)) + mlist.topics_bodylines_limit = int(cgidata.get('topics_bodylines_limit', [mlist.topics_bodylines_limit])[0]) except ValueError: # BAW: should really print a warning pass + + def process_form(self, mlist, cgidata): + # Get the topic information from the form + name = cgidata.get(boxtag, [''])[0] + pattern = cgidata.get(reboxtag, [''])[0] + desc = cgidata.get(desctag, [''])[0] + where = cgidata.get(wheretag, [''])[0] + + # Update list settings + mlist.topics_enabled = int(cgidata.get('topics_enabled', ['0'])[0]) + mlist.topics_bodylines_limit = int(cgidata.get('topics_bodylines_limit', ['0'])[0]) + + # Process the topic + if name and pattern: + if where == 'add': + mlist.AddTopic(name, pattern, desc) + elif where == 'change': + mlist.ChangeTopic(name, pattern, desc) + elif where == 'delete': + mlist.DeleteTopic(name) + + mlist.Save() From 0895579c13c48144945f813d5b4505f8181b9c05 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 25 Apr 2025 12:51:06 -0400 Subject: [PATCH 045/748] update --- Mailman/Cgi/listinfo.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Mailman/Cgi/listinfo.py b/Mailman/Cgi/listinfo.py index 84aa681e..7b75ba6d 100644 --- a/Mailman/Cgi/listinfo.py +++ b/Mailman/Cgi/listinfo.py @@ -244,12 +244,11 @@ def list_listinfo(mlist, lang): '\n' % (now, captcha_idx, - Utils.sha_new(mm_cfg.SUBSCRIBE_FORM_SECRET + ":" + + Utils.sha_new((mm_cfg.SUBSCRIBE_FORM_SECRET + ":" + now + ":" + captcha_idx + ":" + mlist.internal_name() + ":" + - remote - ).encode('utf-8').hexdigest() + remote).encode('utf-8')).hexdigest() ) ) # Roster form substitutions From 1eff9515e4a9b25fd086263cad63b282baac4c7d Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 25 Apr 2025 12:57:26 -0400 Subject: [PATCH 046/748] update --- Mailman/Handlers/SMTPDirect.py | 4 +++- Mailman/Queue/NewsRunner.py | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Mailman/Handlers/SMTPDirect.py b/Mailman/Handlers/SMTPDirect.py index 8f4dbe02..26bf34cd 100644 --- a/Mailman/Handlers/SMTPDirect.py +++ b/Mailman/Handlers/SMTPDirect.py @@ -57,6 +57,7 @@ def __init__(self): def __connect(self): self.__conn = smtplib.SMTP() self.__conn.set_debuglevel(mm_cfg.SMTPLIB_DEBUG_LEVEL) + syslog('smtp', 'Connecting to SMTP server %s:%s', mm_cfg.SMTPHOST, mm_cfg.SMTPPORT) self.__conn.connect(mm_cfg.SMTPHOST, mm_cfg.SMTPPORT) if mm_cfg.SMTP_AUTH: if mm_cfg.SMTP_USE_TLS: @@ -64,13 +65,14 @@ def __connect(self): helo_host = mm_cfg.SMTP_HELO_HOST if not helo_host or helo_host.startswith('.'): helo_host = mm_cfg.SMTPHOST + syslog('smtp', 'Using TLS with hostname: %s', helo_host) try: # Disable certificate validation import ssl context = ssl.create_default_context() context.check_hostname = False context.verify_mode = ssl.CERT_NONE - self.__conn.starttls(context=context) + self.__conn.starttls(context=context, server_hostname=helo_host) except SMTPException as e: syslog('smtp-failure', 'SMTP TLS error: %s', e) self.quit() diff --git a/Mailman/Queue/NewsRunner.py b/Mailman/Queue/NewsRunner.py index 1dc3b392..f6d512dc 100644 --- a/Mailman/Queue/NewsRunner.py +++ b/Mailman/Queue/NewsRunner.py @@ -65,6 +65,8 @@ def __init__(self, slice=None, numslices=1): syslog('info', 'newsrunner not starting due to DEFAULT_NNTP_HOST not being set') return Runner.__init__(self, slice, numslices) + from Mailman.Queue.Switchboard import Switchboard + self._switchboard = Switchboard(self.QDIR) def _dispose(self, mlist, msg, msgdata): # Make sure we have the most up-to-date state @@ -78,6 +80,7 @@ def _dispose(self, mlist, msg, msgdata): try: try: nntp_host, nntp_port = Utils.nntpsplit(mlist.nntp_host) + syslog('nntp', 'Connecting to NNTP server %s:%s', nntp_host, nntp_port) conn = nntplib.NNTP(nntp_host, nntp_port, readermode=True, user=mm_cfg.NNTP_USERNAME, From 9f8bb4702c86499492f16f69332df062ede4e0db Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 25 Apr 2025 13:03:03 -0400 Subject: [PATCH 047/748] update --- Mailman/Handlers/SMTPDirect.py | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/Mailman/Handlers/SMTPDirect.py b/Mailman/Handlers/SMTPDirect.py index 26bf34cd..8f2d2174 100644 --- a/Mailman/Handlers/SMTPDirect.py +++ b/Mailman/Handlers/SMTPDirect.py @@ -67,22 +67,12 @@ def __connect(self): helo_host = mm_cfg.SMTPHOST syslog('smtp', 'Using TLS with hostname: %s', helo_host) try: - # Disable certificate validation - import ssl - context = ssl.create_default_context() - context.check_hostname = False - context.verify_mode = ssl.CERT_NONE - self.__conn.starttls(context=context, server_hostname=helo_host) + # Use native TLS support + self.__conn.starttls() except SMTPException as e: syslog('smtp-failure', 'SMTP TLS error: %s', e) self.quit() raise - try: - self.__conn.ehlo(helo_host) - except SMTPException as e: - syslog('smtp-failure', 'SMTP EHLO error: %s', e) - self.quit() - raise try: self.__conn.login(mm_cfg.SMTP_USER, mm_cfg.SMTP_PASSWD) except smtplib.SMTPHeloError as e: @@ -92,7 +82,6 @@ def __connect(self): except smtplib.SMTPAuthenticationError as e: syslog('smtp-failure', 'SMTP AUTH error: %s', e) self.quit() - raise except smtplib.SMTPException as e: syslog('smtp-failure', 'SMTP - no suitable authentication method found: %s', e) From 1727ecb8e2a0f1f8b959bb83013048c168e3eb3c Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 25 Apr 2025 13:12:18 -0400 Subject: [PATCH 048/748] update code --- Mailman/Handlers/SMTPDirect.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/Mailman/Handlers/SMTPDirect.py b/Mailman/Handlers/SMTPDirect.py index 8f2d2174..354a5513 100644 --- a/Mailman/Handlers/SMTPDirect.py +++ b/Mailman/Handlers/SMTPDirect.py @@ -57,14 +57,20 @@ def __init__(self): def __connect(self): self.__conn = smtplib.SMTP() self.__conn.set_debuglevel(mm_cfg.SMTPLIB_DEBUG_LEVEL) - syslog('smtp', 'Connecting to SMTP server %s:%s', mm_cfg.SMTPHOST, mm_cfg.SMTPPORT) + # Ensure we have a valid hostname for TLS + helo_host = mm_cfg.SMTP_HELO_HOST + if not helo_host or helo_host.startswith('.'): + helo_host = mm_cfg.SMTPHOST + if not helo_host or helo_host.startswith('.'): + # If we still don't have a valid hostname, use localhost + helo_host = 'localhost' + syslog('smtp', 'Connecting to SMTP server %s:%s with HELO %s', + mm_cfg.SMTPHOST, mm_cfg.SMTPPORT, helo_host) self.__conn.connect(mm_cfg.SMTPHOST, mm_cfg.SMTPPORT) + # Set the hostname for TLS + self.__conn._host = helo_host if mm_cfg.SMTP_AUTH: if mm_cfg.SMTP_USE_TLS: - # Ensure we have a valid hostname for TLS - helo_host = mm_cfg.SMTP_HELO_HOST - if not helo_host or helo_host.startswith('.'): - helo_host = mm_cfg.SMTPHOST syslog('smtp', 'Using TLS with hostname: %s', helo_host) try: # Use native TLS support From 01570670eacdb5b7a02e0ad30b3e543f6ace25b4 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 25 Apr 2025 13:32:03 -0400 Subject: [PATCH 049/748] update --- Mailman/Defaults.py.in | 2 +- Mailman/Queue/Switchboard.py | 38 +++++++++++++++++++----------------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/Mailman/Defaults.py.in b/Mailman/Defaults.py.in index 738f39ef..c9bb9a29 100755 --- a/Mailman/Defaults.py.in +++ b/Mailman/Defaults.py.in @@ -631,7 +631,7 @@ NNTP_USERNAME = None NNTP_PASSWORD = None # Set this if you have an NNTP server you prefer gatewayed lists to use. -DEFAULT_NNTP_HOST = '' +DEFAULT_NNTP_HOST = None # These variables controls how headers must be cleansed in order to be # accepted by your NNTP server. Some servers like INN reject messages diff --git a/Mailman/Queue/Switchboard.py b/Mailman/Queue/Switchboard.py index 7baf8df8..7203f623 100644 --- a/Mailman/Queue/Switchboard.py +++ b/Mailman/Queue/Switchboard.py @@ -40,7 +40,7 @@ import errno import pickle import marshal -from email.message import Message +from email.message import Message as EmailMessage from Mailman import mm_cfg from Mailman import Utils @@ -165,23 +165,25 @@ def enqueue(self, _msg, _metadata={}, **_kws): return filebase def dequeue(self, filebase): - # Calculate the filename from the given filebase. - filename = os.path.join(self.__whichq, filebase + '.pck') - backfile = os.path.join(self.__whichq, filebase + '.bak') - # Read the message object and metadata. - fp = open(filename, 'rb') - # Move the file to the backup file name for processing. If this - # process crashes uncleanly the .bak file will be used to re-instate - # the .pck file in order to try again. - os.rename(filename, backfile) - try: - msg = pickle.load(fp, fix_imports=True, encoding='latin1') - data = pickle.load(fp, fix_imports=True, encoding='latin1') - finally: - fp.close() - if data.get('_parsemsg'): - msg = email.message_from_string(msg, Message) - return msg, data + # Read the message data + metadata + data = self._get_envelope_data(filebase) + if data is None: + return None, None + msgdata = {} + # The first line is the message metadata + metadata = data.readline() + if not metadata: + return None, None + metadata = metadata.strip() + if metadata: + msgdata = self._parse_metadata(metadata) + # The rest is the message + msg = data.read() + if not msg: + return None, None + # Parse the message into an email object + msg = email.message_from_string(msg, EmailMessage) + return msg, msgdata def finish(self, filebase, preserve=False): bakfile = os.path.join(self.__whichq, filebase + '.bak') From 7e6711eb5ede73b4bd4a079ce8880aaa10dca2c5 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 25 Apr 2025 14:05:59 -0400 Subject: [PATCH 050/748] items --- Mailman/Cgi/admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mailman/Cgi/admin.py b/Mailman/Cgi/admin.py index 896c7291..beb2cb05 100644 --- a/Mailman/Cgi/admin.py +++ b/Mailman/Cgi/admin.py @@ -1462,7 +1462,7 @@ def get_value(key, default=''): mlist.mod_password = newmodpw # Process the rest of the options - for item in mlist.GetConfigCategories()[category].items: + for item in mlist.GetConfigCategories()[category].items(): if not item: continue # Get the variable name and its value From 64ca8911831adc856ee28232a33a0587795558cd Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 25 Apr 2025 14:09:35 -0400 Subject: [PATCH 051/748] items --- Mailman/Cgi/admin.py | 48 ++++++++++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/Mailman/Cgi/admin.py b/Mailman/Cgi/admin.py index beb2cb05..3e0f5a7a 100644 --- a/Mailman/Cgi/admin.py +++ b/Mailman/Cgi/admin.py @@ -667,9 +667,12 @@ def get_item_gui_value(mlist, category, kind, varname, params, extra): """Return a representation of an item's settings.""" # Give the category a chance to return the value for the variable value = None - label, gui = mlist.GetConfigCategories()[category] - if hasattr(gui, 'getValue'): - value = gui.getValue(mlist, kind, varname, params) + category_data = mlist.GetConfigCategories()[category] + if isinstance(category_data, tuple): + # If it's a tuple, the first element is the category name and the second is the GUI object + gui = category_data[1] + if hasattr(gui, 'getValue'): + value = gui.getValue(mlist, kind, varname, params) # Filter out None, and volatile attributes if value is None and not varname.startswith('_'): value = getattr(mlist, varname) @@ -1462,22 +1465,29 @@ def get_value(key, default=''): mlist.mod_password = newmodpw # Process the rest of the options - for item in mlist.GetConfigCategories()[category].items(): - if not item: - continue - # Get the variable name and its value - varname = item[0] - value = get_value(varname) - - # Set the value based on its type - if value is not None: - if item[1] in (mm_cfg.Radio, mm_cfg.Toggle, mm_cfg.Number): - try: - setattr(mlist, varname, int(value)) - except (ValueError, TypeError): - setattr(mlist, varname, 0) - else: - setattr(mlist, varname, value) + category_data = mlist.GetConfigCategories()[category] + if isinstance(category_data, tuple): + # If it's a tuple, the first element is the category name and the second is the GUI object + gui = category_data[1] + if hasattr(gui, 'GetConfigInfo'): + options = gui.GetConfigInfo(mlist, category) + if options: + for item in options: + if not item: + continue + # Get the variable name and its value + varname = item[0] + value = get_value(varname) + + # Set the value based on its type + if value is not None: + if item[1] in (mm_cfg.Radio, mm_cfg.Toggle, mm_cfg.Number): + try: + setattr(mlist, varname, int(value)) + except (ValueError, TypeError): + setattr(mlist, varname, 0) + else: + setattr(mlist, varname, value) # Save the changes mlist.Save() From d483ecc1b6814649689edf857e2d8761b870aeb3 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 25 Apr 2025 14:17:21 -0400 Subject: [PATCH 052/748] items --- Mailman/Cgi/admin.py | 41 +++++++++++++++++++---------------------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/Mailman/Cgi/admin.py b/Mailman/Cgi/admin.py index 3e0f5a7a..4831725e 100644 --- a/Mailman/Cgi/admin.py +++ b/Mailman/Cgi/admin.py @@ -1466,28 +1466,25 @@ def get_value(key, default=''): # Process the rest of the options category_data = mlist.GetConfigCategories()[category] - if isinstance(category_data, tuple): - # If it's a tuple, the first element is the category name and the second is the GUI object - gui = category_data[1] - if hasattr(gui, 'GetConfigInfo'): - options = gui.GetConfigInfo(mlist, category) - if options: - for item in options: - if not item: - continue - # Get the variable name and its value - varname = item[0] - value = get_value(varname) - - # Set the value based on its type - if value is not None: - if item[1] in (mm_cfg.Radio, mm_cfg.Toggle, mm_cfg.Number): - try: - setattr(mlist, varname, int(value)) - except (ValueError, TypeError): - setattr(mlist, varname, 0) - else: - setattr(mlist, varname, value) + gui = category_data[1] # The second element is the GUI object + options = gui.GetConfigInfo(mlist, category) + if options: + for item in options: + if not item: + continue + # Get the variable name and its value + varname = item[0] + value = get_value(varname) + + # Set the value based on its type + if value is not None: + if item[1] in (mm_cfg.Radio, mm_cfg.Toggle, mm_cfg.Number): + try: + setattr(mlist, varname, int(value)) + except (ValueError, TypeError): + setattr(mlist, varname, 0) + else: + setattr(mlist, varname, value) # Save the changes mlist.Save() From 30b969e61486439458d636390a07a3dd960ef565 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 25 Apr 2025 14:26:23 -0400 Subject: [PATCH 053/748] languages --- Mailman/Message.py | 23 ++++++++++------------- Mailman/Utils.py | 6 ++++-- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/Mailman/Message.py b/Mailman/Message.py index 4667252f..e7cfa825 100644 --- a/Mailman/Message.py +++ b/Mailman/Message.py @@ -24,9 +24,10 @@ import re from io import StringIO -import email -import email.generator -import email.utils +from email import __version__ as email_version +from email.generator import Generator as EmailGenerator +from email.message import Message as EmailMessage +from email.utils import getaddresses from email.charset import Charset from email.header import Header @@ -35,22 +36,21 @@ COMMASPACE = ', ' -if hasattr(email, '__version__'): - mo = re.match(r'([\d.]+)', email.__version__) +if hasattr(email_version, '__version__'): + mo = re.match(r'([\d.]+)', email_version) else: mo = re.match(r'([\d.]+)', '2.1.39') # XXX should use @@MM_VERSION@@ perhaps? VERSION = tuple([int(s) for s in mo.group().split('.')]) - -class Generator(email.generator.Generator): +class Generator(EmailGenerator): """Generates output from a Message object tree, keeping signatures. Headers will by default _not_ be folded in attachments. """ def __init__(self, outfp, mangle_from_=True, maxheaderlen=78, children_maxheaderlen=0): - email.generator.Generator.__init__(self, outfp, + EmailGenerator.__init__(self, outfp, mangle_from_=mangle_from_, maxheaderlen=maxheaderlen) self.__children_maxheaderlen = children_maxheaderlen @@ -60,12 +60,11 @@ def clone(self, fp): self.__children_maxheaderlen, self.__children_maxheaderlen) - -class Message(email.message.Message): +class Message(EmailMessage): def __init__(self): # We need a version number so that we can optimize __setstate__() self.__version__ = VERSION - email.message.Message.__init__(self) + EmailMessage.__init__(self) # BAW: For debugging w/ bin/dumpdb. Apparently pprint uses repr. def __repr__(self): @@ -245,7 +244,6 @@ def as_string(self, unixfrom=False, mangle_from_=True): return fp.getvalue() - class UserNotification(Message): """Class for internally crafted messages.""" @@ -306,7 +304,6 @@ def _enqueue(self, mlist, **_kws): **_kws) - class OwnerNotification(UserNotification): """Like user notifications, but this message goes to the list owners.""" diff --git a/Mailman/Utils.py b/Mailman/Utils.py index 7a0536f2..661ee8d0 100644 --- a/Mailman/Utils.py +++ b/Mailman/Utils.py @@ -608,7 +608,9 @@ def findtext(templatefile, dict=None, raw=False, lang=None, mlist=None): for dir in searchdirs: filename = os.path.join(dir, lang, templatefile) try: - fp = open(filename) + # Use the appropriate encoding based on the language + encoding = GetCharSet(lang) + fp = open(filename, encoding=encoding) raise OuterExit except IOError as e: if e.errno != errno.ENOENT: raise @@ -621,7 +623,7 @@ def findtext(templatefile, dict=None, raw=False, lang=None, mlist=None): # you've got a really broken installation, must be there. try: filename = os.path.join(mm_cfg.TEMPLATE_DIR, 'en', templatefile) - fp = open(filename) + fp = open(filename, encoding='utf-8') except IOError as e: if e.errno != errno.ENOENT: raise # We never found the template. BAD! From 6841c9b285ed5e41592076a6512d459a1afe8df5 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 25 Apr 2025 14:29:54 -0400 Subject: [PATCH 054/748] languages --- Mailman/Utils.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/Mailman/Utils.py b/Mailman/Utils.py index 661ee8d0..518383fc 100644 --- a/Mailman/Utils.py +++ b/Mailman/Utils.py @@ -608,9 +608,15 @@ def findtext(templatefile, dict=None, raw=False, lang=None, mlist=None): for dir in searchdirs: filename = os.path.join(dir, lang, templatefile) try: - # Use the appropriate encoding based on the language + # Open in binary mode and handle encoding explicitly + fp = open(filename, 'rb') + content = fp.read() encoding = GetCharSet(lang) - fp = open(filename, encoding=encoding) + try: + template = content.decode(encoding) + except UnicodeDecodeError: + # If the specified encoding fails, try UTF-8 with replacement + template = content.decode('utf-8', errors='replace') raise OuterExit except IOError as e: if e.errno != errno.ENOENT: raise @@ -623,13 +629,19 @@ def findtext(templatefile, dict=None, raw=False, lang=None, mlist=None): # you've got a really broken installation, must be there. try: filename = os.path.join(mm_cfg.TEMPLATE_DIR, 'en', templatefile) - fp = open(filename, encoding='utf-8') + fp = open(filename, 'rb') + content = fp.read() + try: + template = content.decode('utf-8') + except UnicodeDecodeError: + # If UTF-8 fails, try the language's charset + template = content.decode(GetCharSet('en'), errors='replace') except IOError as e: if e.errno != errno.ENOENT: raise # We never found the template. BAD! raise Exception(IOError(errno.ENOENT, 'No template file found', templatefile)) - template = fp.read() - fp.close() + if fp: + fp.close() text = template if dict is not None: try: From 83d08b3c866c511c550405f9b382409796d4d83e Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 25 Apr 2025 14:31:17 -0400 Subject: [PATCH 055/748] languages --- Mailman/Queue/NewsRunner.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Mailman/Queue/NewsRunner.py b/Mailman/Queue/NewsRunner.py index f6d512dc..e99316e3 100644 --- a/Mailman/Queue/NewsRunner.py +++ b/Mailman/Queue/NewsRunner.py @@ -64,9 +64,14 @@ def __init__(self, slice=None, numslices=1): if not mm_cfg.DEFAULT_NNTP_HOST: syslog('info', 'newsrunner not starting due to DEFAULT_NNTP_HOST not being set') return + # Initialize parent class first Runner.__init__(self, slice, numslices) + # Override the switchboard with our specific one from Mailman.Queue.Switchboard import Switchboard - self._switchboard = Switchboard(self.QDIR) + self._switchboard = Switchboard(self.QDIR, slice, numslices, True) + # Initialize _kids if not already done by parent + if not hasattr(self, '_kids'): + self._kids = {} def _dispose(self, mlist, msg, msgdata): # Make sure we have the most up-to-date state From f18337f3efa4d295c0b6eaa960530ce6e0fd0167 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 25 Apr 2025 14:44:12 -0400 Subject: [PATCH 056/748] update code --- Mailman/Cgi/admin.py | 40 +++++++++---------- Mailman/Utils.py | 91 ++++++++++++++++++++++++++++++++++---------- 2 files changed, 90 insertions(+), 41 deletions(-) diff --git a/Mailman/Cgi/admin.py b/Mailman/Cgi/admin.py index 4831725e..57b60a1a 100644 --- a/Mailman/Cgi/admin.py +++ b/Mailman/Cgi/admin.py @@ -53,7 +53,7 @@ def D_(s): AUTH_CONTEXTS = (mm_cfg.AuthListAdmin, mm_cfg.AuthSiteAdmin) - + def main(): # Try to find out which list is being administered parts = Utils.GetPathPieces() @@ -255,7 +255,7 @@ def sigterm_handler(signum, frame, mlist=mlist): # we're already unlocked. mlist.Unlock() - + def admin_overview(msg=''): # Show the administrative overview page, with the list of all the lists on # this host. msg is an optional error message to display at the top of @@ -358,7 +358,7 @@ def admin_overview(msg=''): doc.AddItem(MailmanLogo()) print(doc.Format()) - + def option_help(mlist, varhelp): # The html page document doc = Document() @@ -431,7 +431,7 @@ def option_help(mlist, varhelp): doc.AddItem(mlist.GetMailmanFooter()) print(doc.Format()) - + def show_results(mlist, doc, category, subcat, cgidata): # Produce the results page adminurl = mlist.GetScriptURL('admin') @@ -579,7 +579,7 @@ def show_results(mlist, doc, category, subcat, cgidata): doc.AddItem(form) doc.AddItem(mlist.GetMailmanFooter()) - + def show_variables(mlist, category, subcat, cgidata, doc): options = mlist.GetConfigInfo(category, subcat) @@ -626,7 +626,7 @@ def show_variables(mlist, category, subcat, cgidata, doc): table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) return table - + def add_options_table_item(mlist, category, subcat, table, item, detailsp=1): # Add a row to an options table with the item description and value. varname, kind, params, extra, descr, elaboration = \ @@ -642,7 +642,7 @@ def add_options_table_item(mlist, category, subcat, table, item, detailsp=1): table.AddCellInfo(table.GetCurrentRowIndex(), 1, bgcolor=mm_cfg.WEB_ADMINITEM_COLOR) - + def get_item_characteristics(record): # Break out the components of an item description from its description # record: @@ -662,7 +662,7 @@ def get_item_characteristics(record): raise ValueError(f'Badly formed options entry:\n {record}') return varname, kind, params, dependancies, descr, elaboration - + def get_item_gui_value(mlist, category, kind, varname, params, extra): """Return a representation of an item's settings.""" # Give the category a chance to return the value for the variable @@ -671,8 +671,8 @@ def get_item_gui_value(mlist, category, kind, varname, params, extra): if isinstance(category_data, tuple): # If it's a tuple, the first element is the category name and the second is the GUI object gui = category_data[1] - if hasattr(gui, 'getValue'): - value = gui.getValue(mlist, kind, varname, params) + if hasattr(gui, 'getValue'): + value = gui.getValue(mlist, kind, varname, params) # Filter out None, and volatile attributes if value is None and not varname.startswith('_'): value = getattr(mlist, varname) @@ -849,7 +849,7 @@ def makebox(i, pattern, action, empty=False, table=table): else: assert 0, 'Bad gui widget type: %s' % kind - + def get_item_gui_description(mlist, category, subcat, varname, descr, elaboration, detailsp): # Return the item's description, with link to details. @@ -876,7 +876,7 @@ def get_item_gui_description(mlist, category, subcat, permanent state.''')).Format() return text - + def membership_options(mlist, subcat, cgidata, doc, form): # Show the main stuff adminurl = mlist.GetScriptURL('admin', absolute=1) @@ -1218,7 +1218,7 @@ def membership_options(mlist, subcat, cgidata, doc, form): container.AddItem(footer + buttons.Format() + '

                  ') return container - + def mass_subscribe(mlist, container): # MASS SUBSCRIBE GREY = mm_cfg.WEB_ADMINITEM_COLOR @@ -1268,7 +1268,7 @@ def mass_subscribe(mlist, container): rows=10, cols='70%', wrap=None))]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) - + def mass_remove(mlist, container): # MASS UNSUBSCRIBE GREY = mm_cfg.WEB_ADMINITEM_COLOR @@ -1299,7 +1299,7 @@ def mass_remove(mlist, container): FileUpload('unsubscribees_upload', cols='50')]) container.AddItem(Center(table)) - + def address_change(mlist, container): # ADDRESS CHANGE GREY = mm_cfg.WEB_ADMINITEM_COLOR @@ -1330,7 +1330,7 @@ def address_change(mlist, container): table.AddCellInfo(table.GetCurrentRowIndex(), 2, bgcolor=GREY) container.AddItem(Center(table)) - + def mass_sync(mlist, container): # MASS SYNC table = Table(width='90%') @@ -1343,7 +1343,7 @@ def mass_sync(mlist, container): FileUpload('memberlist_upload', cols='50')]) container.AddItem(Center(table)) - + def password_inputs(mlist): adminurl = mlist.GetScriptURL('admin', absolute=1) table = Table(cellspacing=3, cellpadding=4) @@ -1400,14 +1400,14 @@ def password_inputs(mlist): table.AddRow([ptable]) return table - + def submit_button(name='submit'): table = Table(border=0, cellspacing=0, cellpadding=2) table.AddRow([Bold(SubmitButton(name, _('Submit Your Changes')))]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, align='middle') return table - + def change_options(mlist, category, subcat, cgidata, doc): # This function processes the form submission from the web interface. # Returns None if there are no changes, or a results document object. @@ -1483,7 +1483,7 @@ def get_value(key, default=''): setattr(mlist, varname, int(value)) except (ValueError, TypeError): setattr(mlist, varname, 0) - else: + else: setattr(mlist, varname, value) # Save the changes diff --git a/Mailman/Utils.py b/Mailman/Utils.py index 518383fc..d9aabf10 100644 --- a/Mailman/Utils.py +++ b/Mailman/Utils.py @@ -589,56 +589,105 @@ def findtext(templatefile, dict=None, raw=False, lang=None, mlist=None): # # Calculate the languages to scan languages = [] - if lang is not None: + if lang: languages.append(lang) - if mlist is not None: + if mlist: languages.append(mlist.preferred_language) languages.append(mm_cfg.DEFAULT_SERVER_LANGUAGE) - # Calculate the locations to scan + # Calculate the directories to scan searchdirs = [] - if mlist is not None: + if mlist: searchdirs.append(mlist.fullpath()) searchdirs.append(os.path.join(mm_cfg.TEMPLATE_DIR, mlist.host_name)) searchdirs.append(os.path.join(mm_cfg.TEMPLATE_DIR, 'site')) searchdirs.append(mm_cfg.TEMPLATE_DIR) # Start scanning fp = None + template = None try: for lang in languages: for dir in searchdirs: filename = os.path.join(dir, lang, templatefile) + syslog('error', 'Attempting to read template file: %s', filename) try: - # Open in binary mode and handle encoding explicitly + # Open in binary mode fp = open(filename, 'rb') content = fp.read() - encoding = GetCharSet(lang) + fp.close() + fp = None + + # First try to decode as Python 2 string (latin-1) try: - template = content.decode(encoding) - except UnicodeDecodeError: - # If the specified encoding fails, try UTF-8 with replacement - template = content.decode('utf-8', errors='replace') - raise OuterExit + syslog('error', 'Attempting to decode %s as latin-1', filename) + template = content.decode('latin-1') + syslog('error', 'Successfully decoded %s as latin-1', filename) + except UnicodeDecodeError as e: + syslog('error', 'Failed to decode %s as latin-1: %s', filename, str(e)) + # If that fails, try the language's charset + encoding = GetCharSet(lang) + try: + syslog('error', 'Attempting to decode %s as %s', filename, encoding) + template = content.decode(encoding) + syslog('error', 'Successfully decoded %s as %s', filename, encoding) + except UnicodeDecodeError as e: + syslog('error', 'Failed to decode %s as %s: %s', filename, encoding, str(e)) + # If that fails, try UTF-8 with replacement + try: + syslog('error', 'Attempting to decode %s as UTF-8 with replacement', filename) + template = content.decode('utf-8', errors='replace') + syslog('error', 'Successfully decoded %s as UTF-8 with replacement', filename) + except UnicodeDecodeError as e: + syslog('error', 'Failed to decode %s as UTF-8: %s', filename, str(e)) + # If UTF-8 fails, try ISO-8859-1 as a last resort + syslog('error', 'Attempting to decode %s as ISO-8859-1 with replacement', filename) + template = content.decode('iso-8859-1', errors='replace') + syslog('error', 'Successfully decoded %s as ISO-8859-1 with replacement', filename) + + if template is not None: + raise OuterExit except IOError as e: - if e.errno != errno.ENOENT: raise + if e.errno != errno.ENOENT: + syslog('error', 'IOError reading %s: %s', filename, str(e)) + raise # Okay, it doesn't exist, keep looping - fp = None + syslog('error', 'Template file not found: %s', filename) + if fp: + fp.close() + fp = None except OuterExit: pass - if fp is None: - # Try one last time with the distro English template, which, unless - # you've got a really broken installation, must be there. + if template is None: + # Try one last time with the distro English template try: filename = os.path.join(mm_cfg.TEMPLATE_DIR, 'en', templatefile) + syslog('error', 'Attempting to read fallback English template: %s', filename) fp = open(filename, 'rb') content = fp.read() + fp.close() + fp = None try: - template = content.decode('utf-8') - except UnicodeDecodeError: - # If UTF-8 fails, try the language's charset - template = content.decode(GetCharSet('en'), errors='replace') + # First try Python 2 string format + syslog('error', 'Attempting to decode %s as latin-1', filename) + template = content.decode('latin-1') + syslog('error', 'Successfully decoded %s as latin-1', filename) + except UnicodeDecodeError as e: + syslog('error', 'Failed to decode %s as latin-1: %s', filename, str(e)) + try: + syslog('error', 'Attempting to decode %s as UTF-8', filename) + template = content.decode('utf-8') + syslog('error', 'Successfully decoded %s as UTF-8', filename) + except UnicodeDecodeError as e: + syslog('error', 'Failed to decode %s as UTF-8: %s', filename, str(e)) + # If UTF-8 fails, try ISO-8859-1 + syslog('error', 'Attempting to decode %s as ISO-8859-1 with replacement', filename) + template = content.decode('iso-8859-1', errors='replace') + syslog('error', 'Successfully decoded %s as ISO-8859-1 with replacement', filename) except IOError as e: - if e.errno != errno.ENOENT: raise + if e.errno != errno.ENOENT: + syslog('error', 'IOError reading fallback template %s: %s', filename, str(e)) + raise # We never found the template. BAD! + syslog('error', 'No template file found: %s', templatefile) raise Exception(IOError(errno.ENOENT, 'No template file found', templatefile)) if fp: fp.close() From 671c375b679d7fbf24c360cf9bbc1d3307098c94 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 25 Apr 2025 14:55:09 -0400 Subject: [PATCH 057/748] update --- Mailman/Cgi/admin.py | 72 ++++++++++++++++++++++++++++++++------------ Mailman/Utils.py | 49 +++++++----------------------- 2 files changed, 63 insertions(+), 58 deletions(-) diff --git a/Mailman/Cgi/admin.py b/Mailman/Cgi/admin.py index 57b60a1a..92838976 100644 --- a/Mailman/Cgi/admin.py +++ b/Mailman/Cgi/admin.py @@ -669,7 +669,6 @@ def get_item_gui_value(mlist, category, kind, varname, params, extra): value = None category_data = mlist.GetConfigCategories()[category] if isinstance(category_data, tuple): - # If it's a tuple, the first element is the category name and the second is the GUI object gui = category_data[1] if hasattr(gui, 'getValue'): value = gui.getValue(mlist, kind, varname, params) @@ -1465,26 +1464,59 @@ def get_value(key, default=''): mlist.mod_password = newmodpw # Process the rest of the options - category_data = mlist.GetConfigCategories()[category] - gui = category_data[1] # The second element is the GUI object - options = gui.GetConfigInfo(mlist, category) - if options: - for item in options: - if not item: - continue - # Get the variable name and its value - varname = item[0] - value = get_value(varname) + try: + categories = mlist.GetConfigCategories() + print(f"Debug: categories={categories}, type={type(categories)}") # Debug line + if category not in categories: + print(f"Debug: category '{category}' not found in categories") # Debug line + doc.addError(_(f'Invalid category: {category}')) + return doc - # Set the value based on its type - if value is not None: - if item[1] in (mm_cfg.Radio, mm_cfg.Toggle, mm_cfg.Number): - try: - setattr(mlist, varname, int(value)) - except (ValueError, TypeError): - setattr(mlist, varname, 0) - else: - setattr(mlist, varname, value) + category_data = categories[category] + print(f"Debug: category={category}, category_data={category_data}, type={type(category_data)}") # Debug line + + if not isinstance(category_data, tuple): + print(f"Debug: category_data is not a tuple, it's {type(category_data)}") # Debug line + doc.addError(_(f'Invalid category data type for {category}')) + return doc + + if len(category_data) < 2: + print(f"Debug: category_data tuple too short: {category_data}") # Debug line + doc.addError(_(f'Invalid category data format for {category}')) + return doc + + gui = category_data[1] # The second element is the GUI object + print(f"Debug: gui={gui}, type={type(gui)}") # Debug line + + if not hasattr(gui, 'GetConfigInfo'): + print(f"Debug: gui object missing GetConfigInfo method") # Debug line + doc.addError(_(f'Invalid GUI object for {category}')) + return doc + + options = gui.GetConfigInfo(mlist, category) + print(f"Debug: options={options}, type={type(options)}") # Debug line + + if options: + for item in options: + if not item: + continue + # Get the variable name and its value + varname = item[0] + value = get_value(varname) + + # Set the value based on its type + if value is not None: + if item[1] in (mm_cfg.Radio, mm_cfg.Toggle, mm_cfg.Number): + try: + setattr(mlist, varname, int(value)) + except (ValueError, TypeError): + setattr(mlist, varname, 0) + else: + setattr(mlist, varname, value) + except Exception as e: + print(f"Debug: Exception in change_options: {str(e)}") # Debug line + doc.addError(_(f'Error processing options: {str(e)}')) + return doc # Save the changes mlist.Save() diff --git a/Mailman/Utils.py b/Mailman/Utils.py index d9aabf10..8edb0e6a 100644 --- a/Mailman/Utils.py +++ b/Mailman/Utils.py @@ -589,14 +589,14 @@ def findtext(templatefile, dict=None, raw=False, lang=None, mlist=None): # # Calculate the languages to scan languages = [] - if lang: + if lang is not None: languages.append(lang) - if mlist: + if mlist is not None: languages.append(mlist.preferred_language) languages.append(mm_cfg.DEFAULT_SERVER_LANGUAGE) - # Calculate the directories to scan + # Calculate the locations to scan searchdirs = [] - if mlist: + if mlist is not None: searchdirs.append(mlist.fullpath()) searchdirs.append(os.path.join(mm_cfg.TEMPLATE_DIR, mlist.host_name)) searchdirs.append(os.path.join(mm_cfg.TEMPLATE_DIR, 'site')) @@ -608,7 +608,6 @@ def findtext(templatefile, dict=None, raw=False, lang=None, mlist=None): for lang in languages: for dir in searchdirs: filename = os.path.join(dir, lang, templatefile) - syslog('error', 'Attempting to read template file: %s', filename) try: # Open in binary mode fp = open(filename, 'rb') @@ -618,39 +617,25 @@ def findtext(templatefile, dict=None, raw=False, lang=None, mlist=None): # First try to decode as Python 2 string (latin-1) try: - syslog('error', 'Attempting to decode %s as latin-1', filename) template = content.decode('latin-1') - syslog('error', 'Successfully decoded %s as latin-1', filename) - except UnicodeDecodeError as e: - syslog('error', 'Failed to decode %s as latin-1: %s', filename, str(e)) + except UnicodeDecodeError: # If that fails, try the language's charset encoding = GetCharSet(lang) try: - syslog('error', 'Attempting to decode %s as %s', filename, encoding) template = content.decode(encoding) - syslog('error', 'Successfully decoded %s as %s', filename, encoding) - except UnicodeDecodeError as e: - syslog('error', 'Failed to decode %s as %s: %s', filename, encoding, str(e)) + except UnicodeDecodeError: # If that fails, try UTF-8 with replacement try: - syslog('error', 'Attempting to decode %s as UTF-8 with replacement', filename) template = content.decode('utf-8', errors='replace') - syslog('error', 'Successfully decoded %s as UTF-8 with replacement', filename) - except UnicodeDecodeError as e: - syslog('error', 'Failed to decode %s as UTF-8: %s', filename, str(e)) + except UnicodeDecodeError: # If UTF-8 fails, try ISO-8859-1 as a last resort - syslog('error', 'Attempting to decode %s as ISO-8859-1 with replacement', filename) template = content.decode('iso-8859-1', errors='replace') - syslog('error', 'Successfully decoded %s as ISO-8859-1 with replacement', filename) if template is not None: raise OuterExit except IOError as e: - if e.errno != errno.ENOENT: - syslog('error', 'IOError reading %s: %s', filename, str(e)) - raise + if e.errno != errno.ENOENT: raise # Okay, it doesn't exist, keep looping - syslog('error', 'Template file not found: %s', filename) if fp: fp.close() fp = None @@ -660,34 +645,22 @@ def findtext(templatefile, dict=None, raw=False, lang=None, mlist=None): # Try one last time with the distro English template try: filename = os.path.join(mm_cfg.TEMPLATE_DIR, 'en', templatefile) - syslog('error', 'Attempting to read fallback English template: %s', filename) fp = open(filename, 'rb') content = fp.read() fp.close() fp = None try: # First try Python 2 string format - syslog('error', 'Attempting to decode %s as latin-1', filename) template = content.decode('latin-1') - syslog('error', 'Successfully decoded %s as latin-1', filename) - except UnicodeDecodeError as e: - syslog('error', 'Failed to decode %s as latin-1: %s', filename, str(e)) + except UnicodeDecodeError: try: - syslog('error', 'Attempting to decode %s as UTF-8', filename) template = content.decode('utf-8') - syslog('error', 'Successfully decoded %s as UTF-8', filename) - except UnicodeDecodeError as e: - syslog('error', 'Failed to decode %s as UTF-8: %s', filename, str(e)) + except UnicodeDecodeError: # If UTF-8 fails, try ISO-8859-1 - syslog('error', 'Attempting to decode %s as ISO-8859-1 with replacement', filename) template = content.decode('iso-8859-1', errors='replace') - syslog('error', 'Successfully decoded %s as ISO-8859-1 with replacement', filename) except IOError as e: - if e.errno != errno.ENOENT: - syslog('error', 'IOError reading fallback template %s: %s', filename, str(e)) - raise + if e.errno != errno.ENOENT: raise # We never found the template. BAD! - syslog('error', 'No template file found: %s', templatefile) raise Exception(IOError(errno.ENOENT, 'No template file found', templatefile)) if fp: fp.close() From 489c8f262e27711ce316d4be7c036bed04126bc5 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 25 Apr 2025 14:57:50 -0400 Subject: [PATCH 058/748] update --- Mailman/LockFile.py | 242 +++++++++++++++++++++++--------------------- 1 file changed, 126 insertions(+), 116 deletions(-) diff --git a/Mailman/LockFile.py b/Mailman/LockFile.py index 6fbf06b3..22d3731b 100644 --- a/Mailman/LockFile.py +++ b/Mailman/LockFile.py @@ -192,6 +192,11 @@ def __init__(self, lockfile, self.__logprefix = os.path.split(self.__lockfile)[1] # For transferring ownership across a fork. self.__owned = True + # Maximum number of retries for lock operations + self.__max_retries = 100 + # NFS-specific settings + self.__nfs_retry_delay = 0.1 + self.__nfs_max_retries = 5 def __repr__(self): return '' % ( @@ -226,87 +231,53 @@ def refresh(self, newlifetime=None, unconditionally=False): raise NotLockedError('%s: %s' % (repr(self), self.__read())) def lock(self, timeout=0): - """Acquire the lock. - - This blocks until the lock is acquired unless optional timeout is - greater than 0, in which case, a TimeOutError is raised when timeout - number of seconds (or possibly more) expires without lock acquisition. - Raises AlreadyLockedError if the lock is already set. - """ - if timeout: - timeout_time = time.time() + timeout - # Make sure my temp lockfile exists, and that its contents are - # up-to-date (e.g. the temp file name, and the lock lifetime). - self.__write() - # TBD: This next call can fail with an EPERM. I have no idea why, but - # I'm nervous about wrapping this in a try/except. It seems to be a - # very rare occurence, only happens from cron, and (only?) on Solaris - # 2.6. - self.__touch() - self.__writelog('laying claim') - # for quieting the logging output - loopcount = -1 + """Acquire the lock with improved timeout and interrupt handling.""" + if self.locked(): + raise AlreadyLockedError + # Set up the timeout + if timeout > 0: + endtime = time.time() + timeout + else: + endtime = None + # Try to acquire the lock + loopcount = 0 + retry_count = 0 while True: - loopcount += 1 - # Create the hard link and test for exactly 2 links to the file try: - os.link(self.__tmpfname, self.__lockfile) - # If we got here, we know we know we got the lock, and never - # had it before, so we're done. Just touch it again for the - # fun of it. - self.__writelog('got the lock') - self.__touch() - break - except OSError as e: - # The link failed for some reason, possibly because someone - # else already has the lock (i.e. we got an EEXIST), or for - # some other bizarre reason. - if e.errno == errno.ENOENT: - # TBD: in some Linux environments, it is possible to get - # an ENOENT, which is truly strange, because this means - # that self.__tmpfname doesn't exist at the time of the - # os.link(), but self.__write() is supposed to guarantee - # that this happens! I don't honestly know why this - # happens, but for now we just say we didn't acquire the - # lock, and try again next time. - pass - elif e.errno != errno.EEXIST: - # Something very bizarre happened. Clean up our state and - # pass the error on up. - self.__writelog('unexpected link error: %s' % e, + # Check for timeout + if endtime and time.time() > endtime: + raise TimeOutError + # Check for max retries + if retry_count >= self.__max_retries: + self.__writelog('max retries exceeded', important=True) + raise TimeOutError + # Try to acquire the lock + if self._take_possession(): + self.__writelog('locked') + return + # Check if the lock is stale + if self.__releasetime() < time.time(): + # Yes, so break the lock. + self.__break() + self.__writelog('lifetime has expired, breaking', important=True) - os.unlink(self.__tmpfname) - raise - elif self.__linkcount() != 2: - # Somebody's messin' with us! Log this, and try again - # later. TBD: should we raise an exception? - self.__writelog('unexpected linkcount: %d' % - self.__linkcount(), important=True) - elif self.__read() == self.__tmpfname: - # It was us that already had the link. - self.__writelog('already locked') - raise AlreadyLockedError - # otherwise, someone else has the lock - pass - # We did not acquire the lock, because someone else already has - # it. Have we timed out in our quest for the lock? - if timeout and timeout_time < time.time(): - os.unlink(self.__tmpfname) - self.__writelog('timed out') - raise TimeOutError - # Okay, we haven't timed out, but we didn't get the lock. Let's - # find if the lock lifetime has expired. - if time.time() > self.__releasetime() + CLOCK_SLOP: - # Yes, so break the lock. - self.__break() - self.__writelog('lifetime has expired, breaking', - important=True) - # Okay, someone else has the lock, our claim hasn't timed out yet, - # and the expected lock lifetime hasn't expired yet. So let's - # wait a while for the owner of the lock to give it up. - elif not loopcount % 100: - self.__writelog('waiting for claim') - self.__sleep() + # Wait a while for the owner of the lock to give it up + elif not loopcount % 100: + self.__writelog('waiting for claim') + self.__sleep() + loopcount += 1 + retry_count += 1 + except KeyboardInterrupt: + # If we get a keyboard interrupt, clean up and re-raise + self.__writelog('interrupted while waiting for lock') + self.__cleanup() + raise + except Exception as e: + # Log any other exceptions but continue trying + self.__writelog(f'error while waiting for lock: {str(e)}') + self.__sleep() + loopcount += 1 + retry_count += 1 def unlock(self, unconditionally=False): """Unlock the lock. @@ -412,15 +383,30 @@ def __writelog(self, msg, important=0): logf.write('%s %s\n' % (self.__logprefix, msg)) traceback.print_stack(file=logf) + def __atomic_write(self, filename, content): + """Atomically write content to a file using a temporary file.""" + tempname = filename + '.tmp' + try: + # Write to temporary file first + with open(tempname, 'w') as f: + f.write(content) + # Atomic rename + os.rename(tempname, filename) + except Exception as e: + # Clean up temp file if it exists + try: + os.unlink(tempname) + except OSError: + pass + raise e + def __write(self): - # Make sure it's group writable - oldmask = os.umask(0o002) + """Write the lock file atomically.""" try: - fp = open(self.__tmpfname, 'w') - fp.write(self.__tmpfname) - fp.close() - finally: - os.umask(oldmask) + self.__atomic_write(self.__tmpfname, self.__tmpfname) + except OSError as e: + self.__writelog(f'error writing temp file: {str(e)}') + raise def __read(self): try: @@ -441,56 +427,80 @@ def __touch(self, filename=None): if e.errno != errno.ENOENT: raise def __releasetime(self): + """Get release time with NFS safety.""" try: - return os.stat(self.__lockfile)[ST_MTIME] + return self.__nfs_safe_stat(self.__lockfile)[ST_MTIME] except OSError as e: - if e.errno != errno.ENOENT: raise + if e.errno != errno.ENOENT: + raise return -1 def __linkcount(self): + """Get link count with NFS safety.""" try: - return os.stat(self.__lockfile)[ST_NLINK] + return self.__nfs_safe_stat(self.__lockfile)[ST_NLINK] except OSError as e: - if e.errno != errno.ENOENT: raise + if e.errno != errno.ENOENT: + raise return -1 def __break(self): - # First, touch the global lock file. This reduces but does not - # eliminate the chance for a race condition during breaking. Two - # processes could both pass the test for lock expiry in lock() before - # one of them gets to touch the global lockfile. This shouldn't be - # too bad because all they'll do in this function is wax the lock - # files, not claim the lock, and we can be defensive for ENOENTs - # here. - # - # Touching the lock could fail if the process breaking the lock and - # the process that claimed the lock have different owners. We could - # solve this by set-uid'ing the CGI and mail wrappers, but I don't - # think it's that big a problem. + """Break a stale lock with improved error handling.""" try: - self.__touch(self.__lockfile) + # First, touch the global lock file to reduce race conditions + self.__touch() except OSError as e: - if e.errno != errno.EPERM: raise - # Get the name of the old winner's temp file. - winner = self.__read() - # Remove the global lockfile, which actually breaks the lock. + if e.errno != errno.EPERM: + self.__writelog(f'error touching lock file: {str(e)}') + raise + # Remove the lock files try: os.unlink(self.__lockfile) except OSError as e: - if e.errno != errno.ENOENT: raise - # Try to remove the old winner's temp file, since we're assuming the - # winner process has hung or died. Don't worry too much if we can't - # unlink their temp file -- this doesn't wreck the locking algorithm, - # but will leave temp file turds laying around, a minor inconvenience. + if e.errno != errno.ENOENT: + self.__writelog(f'error removing lock file: {str(e)}') + raise try: - if winner: - os.unlink(winner) + os.unlink(self.__tmpfname) except OSError as e: - if e.errno != errno.ENOENT: raise + if e.errno != errno.ENOENT: + self.__writelog(f'error removing temp file: {str(e)}') + raise + self.__writelog('lock broken', important=True) def __sleep(self): - interval = random.random() * 2.0 + 0.01 - time.sleep(interval) + """Sleep for a short interval, handling keyboard interrupts gracefully.""" + try: + # Use a fixed interval with small jitter for more predictable behavior + interval = 0.1 + (random.random() * 0.1) # 100-200ms + time.sleep(interval) + except KeyboardInterrupt: + # If we get a keyboard interrupt during sleep, raise it + raise + except Exception: + # For any other exception during sleep, just continue + pass + + def __cleanup(self): + """Clean up any temporary files in case of error.""" + try: + if os.path.exists(self.__tmpfname): + os.unlink(self.__tmpfname) + except OSError as e: + self.__writelog(f'error cleaning up temp file: {str(e)}') + + def __nfs_safe_stat(self, filename): + """Perform NFS-safe stat operation with retries.""" + for i in range(self.__nfs_max_retries): + try: + return os.stat(filename) + except OSError as e: + if e.errno == errno.ESTALE: + # NFS stale file handle + time.sleep(self.__nfs_retry_delay) + continue + raise + raise OSError(errno.ESTALE, "NFS stale file handle after retries") From dc98b8714dbfece9528c10564a9742ecc85d78a8 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 25 Apr 2025 15:13:18 -0400 Subject: [PATCH 059/748] update --- Mailman/Errors.py | 20 +++--- Mailman/ListAdmin.py | 157 ++++++++++++++++++++++++++++++----------- Mailman/LockFile.py | 72 ++++++++++++------- Mailman/MailList.py | 161 ++++++++++++++++++++++++++----------------- Mailman/Pending.py | 133 ++++++++++++++++++++++++++++------- 5 files changed, 378 insertions(+), 165 deletions(-) diff --git a/Mailman/Errors.py b/Mailman/Errors.py index a836979f..d2ba6523 100644 --- a/Mailman/Errors.py +++ b/Mailman/Errors.py @@ -52,21 +52,21 @@ class MMCookieError(MMAuthenticationError): pass class MMExpiredCookieError(MMCookieError): pass class MMInvalidCookieError(MMCookieError): pass -class MMMustDigestError(object): pass -class MMCantDigestError(object): pass -class MMNeedApproval(object): +class MMMustDigestError(Exception): pass +class MMCantDigestError(Exception): pass +class MMNeedApproval(Exception): def __init__(self, message=None): self.message = message def __str__(self): return self.message or '' -class MMSubscribeNeedsConfirmation(object): pass -class MMBadConfirmation(object): +class MMSubscribeNeedsConfirmation(Exception): pass +class MMBadConfirmation(Exception): def __init__(self, message=None): self.message = message def __str__(self): return self.message or '' -class MMAlreadyDigested(object): pass -class MMAlreadyUndigested(object): pass +class MMAlreadyDigested(Exception): pass +class MMAlreadyUndigested(Exception): pass MODERATED_LIST_MSG = "Moderated list" IMPLICIT_DEST_MSG = "Implicit destination" @@ -94,11 +94,11 @@ class EmailAddressError(MailmanError): """Base class for email address validation errors.""" pass -class MMBadEmailError(EmailAddressError): +class MMBadEmailError(Exception): """Email address is invalid (empty string or not fully qualified).""" pass -class MMHostileAddress(EmailAddressError): +class MMHostileAddress(Exception): """Email address has potentially hostile characters in it.""" pass @@ -162,7 +162,7 @@ def __init__(self, notice=None): def notice(self): return self.__notice - + # Additional exceptions class HostileSubscriptionError(MailmanError): """A cross-subscription attempt was made.""" diff --git a/Mailman/ListAdmin.py b/Mailman/ListAdmin.py index 7f8eb23d..25d960a1 100644 --- a/Mailman/ListAdmin.py +++ b/Mailman/ListAdmin.py @@ -31,6 +31,7 @@ import pickle import marshal from io import StringIO +import socket import email from email.mime.message import MIMEMessage @@ -76,55 +77,129 @@ def InitTempVars(self): self.__filename = os.path.join(self.fullpath(), 'request.pck') def __opendb(self): - if self.__db is None: - assert self.Locked() - try: - fp = open(self.__filename, 'rb') + """Open the database file and load data with improved error handling.""" + if self.__db is not None: + return + + filename = os.path.join(mm_cfg.DATA_DIR, 'pending.pck') + filename_backup = filename + '.bak' + + # Try loading the main file first + try: + with open(filename, 'rb') as fp: try: self.__db = pickle.load(fp, fix_imports=True, encoding='latin1') - finally: - fp.close() - except IOError as e: - if e.errno != errno.ENOENT: raise - self.__db = {} - # put version number in new database - self.__db['version'] = IGN, mm_cfg.REQUESTS_FILE_SCHEMA_VERSION + if not isinstance(self.__db, dict): + raise ValueError("Database not a dictionary") + return + except (EOFError, ValueError, TypeError, pickle.UnpicklingError) as e: + syslog('error', 'Error loading pending.pck: %s', str(e)) + + # If we get here, the main file failed to load properly + if os.path.exists(filename_backup): + syslog('info', 'Attempting to load from backup file') + with open(filename_backup, 'rb') as fp: + try: + self.__db = pickle.load(fp, fix_imports=True, encoding='latin1') + if not isinstance(self.__db, dict): + raise ValueError("Backup database not a dictionary") + # Successfully loaded backup, restore it as main + import shutil + shutil.copy2(filename_backup, filename) + return + except (EOFError, ValueError, TypeError, pickle.UnpicklingError) as e: + syslog('error', 'Error loading backup pending.pck: %s', str(e)) + + except IOError as e: + if e.errno != errno.ENOENT: + syslog('error', 'IOError loading pending.pck: %s', str(e)) + + # If we get here, both main and backup files failed or don't exist + self.__db = {} def __closedb(self): - if self.__db is not None: - assert self.Locked() - # Save the version number - self.__db['version'] = IGN, mm_cfg.REQUESTS_FILE_SCHEMA_VERSION - # Now save a temp file and do the tmpfile->real file dance. BAW: - # should we be as paranoid as for the config.pck file? Should we - # use pickle? - tmpfile = self.__filename + '.tmp' - omask = os.umask(0o007) + """Save the database with atomic operations and backup.""" + if self.__db is None: + return + + filename = os.path.join(mm_cfg.DATA_DIR, 'pending.pck') + filename_tmp = filename + '.tmp.%s.%d' % (socket.gethostname(), os.getpid()) + filename_backup = filename + '.bak' + + # First create a backup of the current file if it exists + if os.path.exists(filename): try: - fp = open(tmpfile, 'wb') - try: - pickle.dump(self.__db, fp, 1) - fp.flush() + import shutil + shutil.copy2(filename, filename_backup) + except IOError as e: + syslog('error', 'Error creating backup: %s', str(e)) + + # Save to temporary file first + try: + # Ensure directory exists + dirname = os.path.dirname(filename) + if not os.path.exists(dirname): + os.makedirs(dirname, 0o755) + + with open(filename_tmp, 'wb') as fp: + # Use protocol 2 for better compatibility + pickle.dump(self.__db, fp, protocol=2) + fp.flush() + if hasattr(os, 'fsync'): os.fsync(fp.fileno()) - finally: - fp.close() - finally: - os.umask(omask) - self.__db = None - # Do the dance - os.rename(tmpfile, self.__filename) - - def __nextid(self): - assert self.Locked() - while True: - next = self.next_request_id - self.next_request_id += 1 - if next not in self.__db: - break - return next + + # Atomic rename + os.rename(filename_tmp, filename) + + except (IOError, OSError) as e: + syslog('error', 'Error saving pending.pck: %s', str(e)) + # Try to clean up + try: + os.unlink(filename_tmp) + except OSError: + pass + raise + + self.__db = None + + def __validate_and_clean_db(self): + """Validate database entries and clean up invalid ones.""" + if not self.__db: + return + + now = time.time() + to_delete = [] + + for key, value in self.__db.items(): + try: + # Check if value is a valid tuple/list with at least 2 elements + if not isinstance(value, (tuple, list)) or len(value) < 2: + to_delete.append(key) + continue + + # Check if timestamp is valid + timestamp = value[1] + if not isinstance(timestamp, (int, float)) or timestamp < 0: + to_delete.append(key) + continue + + # Remove expired entries + if timestamp < now: + to_delete.append(key) + continue + + except (TypeError, IndexError): + to_delete.append(key) + + # Remove invalid entries + for key in to_delete: + del self.__db[key] def SaveRequestsDb(self): - self.__closedb() + """Save the requests database with validation.""" + if self.__db is not None: + self.__validate_and_clean_db() + self.__closedb() def NumRequestsPending(self): self.__opendb() diff --git a/Mailman/LockFile.py b/Mailman/LockFile.py index 22d3731b..8e74eda6 100644 --- a/Mailman/LockFile.py +++ b/Mailman/LockFile.py @@ -286,21 +286,27 @@ def unlock(self, unconditionally=False): calls, or because the lock was stolen out from under us), raise a NotLockedError, unless optional `unconditionally' is true. """ - islocked = self.locked() - if not islocked and not unconditionally: - raise NotLockedError - # If we owned the lock, remove the global file, relinquishing it. - if islocked: + try: + islocked = self.locked() + if not islocked and not unconditionally: + raise NotLockedError + # If we owned the lock, remove the global file, relinquishing it. + if islocked: + try: + os.unlink(self.__lockfile) + except OSError as e: + if e.errno != errno.ENOENT: + raise + # Remove our tempfile try: - os.unlink(self.__lockfile) + os.unlink(self.__tmpfname) except OSError as e: - if e.errno != errno.ENOENT: raise - # Remove our tempfile - try: - os.unlink(self.__tmpfname) - except OSError as e: - if e.errno != errno.ENOENT: raise - self.__writelog('unlocked') + if e.errno != errno.ENOENT: + raise + self.__writelog('unlocked') + except Exception as e: + self.__writelog(f'Error during unlock: {str(e)}', important=True) + raise def locked(self): """Return true if we own the lock, false if we do not. @@ -308,13 +314,12 @@ def locked(self): Checking the status of the lock resets the lock's lifetime, which helps avoid race conditions during the lock status test. """ - # Discourage breaking the lock for a while. try: + # Discourage breaking the lock for a while. self.__touch() except OSError as e: if e.errno == errno.EPERM: - # We can't touch the file because we're not the owner. I - # don't see how we can own the lock if we're not the owner. + # We can't touch the file because we're not the owner return False else: raise @@ -327,8 +332,17 @@ def finalize(self): self.unlock(unconditionally=True) def __del__(self): + """Clean up when the object is garbage collected.""" if self.__owned: - self.finalize() + try: + self.finalize() + except Exception as e: + # Don't raise exceptions during garbage collection + # Just log if we can + try: + self.__writelog(f'Error during cleanup: {str(e)}', important=True) + except: + pass # Use these only if you're transfering ownership to a child process across # a fork. Use at your own risk, but it should be race-condition safe. @@ -377,11 +391,18 @@ def _disown(self): # Private interface # - def __writelog(self, msg, important=0): - if self.__withlogging or important: + def __writelog(self, msg, important=False): + """Write a message to the log file.""" + if not self.__withlogging and not important: + return + try: logf = _get_logfile() logf.write('%s %s\n' % (self.__logprefix, msg)) - traceback.print_stack(file=logf) + if important: + traceback.print_stack(file=logf) + except Exception: + # Don't raise exceptions during logging + pass def __atomic_write(self, filename, content): """Atomically write content to a file using a temporary file.""" @@ -409,13 +430,14 @@ def __write(self): raise def __read(self): + """Read the lock file contents.""" try: - fp = open(self.__lockfile) - filename = fp.read() - fp.close() - return filename + with open(self.__lockfile) as fp: + filename = fp.read() + return filename except EnvironmentError as e: - if e.errno != errno.ENOENT: raise + if e.errno != errno.ENOENT: + raise return None def __touch(self, filename=None): diff --git a/Mailman/MailList.py b/Mailman/MailList.py index b629add6..70eaa669 100644 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -553,19 +553,31 @@ def Create(self, name, admin, crypted_password, # Database and filesystem I/O # def __save(self, dict): - # Save the file as a binary pickle, and rotate the old version to a - # backup file. We must guarantee that config.pck is always valid so - # we never rotate unless the we've successfully written the temp file. - # We use pickle now because marshal is not guaranteed to be compatible - # between Python versions. + """Save the file as a binary pickle with atomic operations and backup.""" fname = os.path.join(self.fullpath(), 'config.pck') fname_tmp = fname + '.tmp.%s.%d' % (socket.gethostname(), os.getpid()) fname_last = fname + '.last' + fname_backup = fname + '.bak' + + # First create a backup of the current file if it exists + if os.path.exists(fname): + try: + import shutil + shutil.copy2(fname, fname_backup) + except IOError: + pass # Best effort backup + + # Save to temporary file first fp = None try: + # Ensure directory exists + dirname = os.path.dirname(fname) + if not os.path.exists(dirname): + os.makedirs(dirname, 0o755) + fp = open(fname_tmp, 'wb') - # Use a binary format... it's more efficient. - pickle.dump(dict, fp, 1) + # Use protocol 2 for better compatibility + pickle.dump(dict, fp, protocol=2) fp.flush() if mm_cfg.SYNC_AFTER_WRITE: os.fsync(fp.fileno()) @@ -574,21 +586,41 @@ def __save(self, dict): syslog('error', 'Failed config.pck write, retaining old state.\n%s', e) if fp is not None: - os.unlink(fname_tmp) + try: + os.unlink(fname_tmp) + except OSError: + pass raise + # Now do config.pck.tmp.xxx -> config.pck -> config.pck.last rotation - # as safely as possible. - try: - # might not exist yet - os.unlink(fname_last) - except OSError as e: - if e.errno != errno.ENOENT: raise + # as safely as possible try: - # might not exist yet - os.link(fname, fname_last) - except OSError as e: - if e.errno != errno.ENOENT: raise - os.rename(fname_tmp, fname) + # Remove old .last file if it exists + try: + os.unlink(fname_last) + except OSError as e: + if e.errno != errno.ENOENT: + raise + + # Try to create hard link to current file as .last + try: + os.link(fname, fname_last) + except OSError as e: + if e.errno != errno.ENOENT: + raise + + # Atomic rename of new file + os.rename(fname_tmp, fname) + except Exception as e: + # If anything goes wrong, try to restore from backup + syslog('error', 'Error during file rotation: %s', str(e)) + if os.path.exists(fname_backup): + try: + shutil.copy2(fname_backup, fname) + except IOError: + pass + raise + # Reset the timestamp self.__timestamp = os.path.getmtime(fname) @@ -641,65 +673,64 @@ def Save(self): self.CheckHTMLArchiveDir() def __load(self, dbfile): - # Attempt to load and unserialize the specified database file. This - # could actually be a config.db (for pre-2.1alpha3) or config.pck, - # i.e. a marshal or a binary pickle. Actually, it could also be a - # .last backup file if the primary storage file was corrupt. The - # decision on whether to unpickle or unmarshal is based on the file - # extension, but we always save it using pickle (since only it, and - # not marshal is guaranteed to be compatible across Python versions). - # - # On success return a 2-tuple of (dictionary, None). On error, return - # a 2-tuple of the form (None, errorobj). + """Load and validate a database file with improved error handling.""" + # Determine the load function based on file extension if dbfile.endswith('.db') or dbfile.endswith('.db.last'): loadfunc = marshal.load elif dbfile.endswith('.pck') or dbfile.endswith('.pck.last'): loadfunc = pickle.load else: assert 0, 'Bad database file name' + try: - # Check the mod time of the file first. If it matches our - # timestamp, then the state hasn't change since the last time we - # loaded it. Otherwise open the file for loading, below. If the - # file doesn't exist, we'll get an EnvironmentError with errno set - # to ENOENT (EnvironmentError is the base class of IOError and - # OSError). - # We test strictly less than here because the resolution is whole - # seconds and we have seen cases of the file being updated by - # another process in the same second. - # Even this is not sufficient in shared file system environments - # if there is time skew between servers. In those cases, the test - # could be - # if mtime + MAX_SKEW < self.__timestamp: - # or the "if ...: return" just deleted. + # Check if we need to reload based on timestamp mtime = os.path.getmtime(dbfile) if mtime < self.__timestamp: - # File is not newer return None, None - fp = open(dbfile, mode='rb') + + # Load and validate the file + with open(dbfile, mode='rb') as fp: + try: + if dbfile.endswith('.db') or dbfile.endswith('.db.last'): + dict_retval = marshal.load(fp) + elif dbfile.endswith('.pck') or dbfile.endswith('.pck.last'): + dict_retval = pickle.load(fp, fix_imports=True, encoding='latin1') + + # Validate the loaded data + if not isinstance(dict_retval, dict): + return None, 'Load() expected to return a dictionary' + + # Convert any bytes to strings + dict_retval = self.__convert_bytes_to_strings(dict_retval) + + return dict_retval, None + + except (EOFError, ValueError, TypeError, MemoryError, + pickle.PicklingError, pickle.UnpicklingError) as e: + return None, e + except EnvironmentError as e: - if e.errno != errno.ENOENT: raise - # The file doesn't exist yet + if e.errno != errno.ENOENT: + raise return None, e - now = int(time.time()) - try: + + def __convert_bytes_to_strings(self, data): + """Convert bytes to strings recursively in a data structure.""" + if isinstance(data, bytes): try: - if dbfile.endswith('.db') or dbfile.endswith('.db.last'): - dict_retval = marshal.load(fp) - elif dbfile.endswith('.pck') or dbfile.endswith('.pck.last'): - dict_retval = pickle.load(fp, fix_imports=True, encoding='latin1') - if not isinstance(dict_retval, dict): - return None, 'Load() expected to return a dictionary' - except (EOFError, ValueError, TypeError, MemoryError, - pickle.PicklingError, pickle.UnpicklingError) as e: - return None, e - finally: - fp.close() - # Update the timestamp. We use current time here rather than mtime - # so the test above might succeed the next time. And we get the time - # before unpickling in case it takes more than a second. (LP: #266464) - self.__timestamp = now - return dict_retval, None + return data.decode('utf-8', 'replace') + except UnicodeDecodeError: + return data.decode('latin1', 'replace') + elif isinstance(data, list): + return [self.__convert_bytes_to_strings(item) for item in data] + elif isinstance(data, dict): + return { + self.__convert_bytes_to_strings(key): self.__convert_bytes_to_strings(value) + for key, value in data.items() + } + elif isinstance(data, tuple): + return tuple(self.__convert_bytes_to_strings(item) for item in data) + return data def Load(self, check_version=True): if not Utils.list_exists(self.internal_name()): diff --git a/Mailman/Pending.py b/Mailman/Pending.py index 0d71358f..faf42d24 100644 --- a/Mailman/Pending.py +++ b/Mailman/Pending.py @@ -90,45 +90,130 @@ def pend_new(self, op, *content, **kws): return cookie def __load(self): + """Load the pending database with improved error handling and validation.""" + backup_file = self.__pendfile + '.bak' try: - fp = open(self.__pendfile) + # Try loading the main file first + return self.__load_file(self.__pendfile) + except (pickle.UnpicklingError, EOFError, ValueError) as e: + # If main file is corrupt, try the backup + try: + if os.path.exists(backup_file): + return self.__load_file(backup_file) + except Exception: + pass + # If both files are corrupt, start fresh + return {'evictions': {}, 'version': mm_cfg.PENDING_FILE_SCHEMA_VERSION} + + def __load_file(self, filename): + """Load and validate a specific pending database file.""" + try: + with open(filename, 'rb') as fp: + db = pickle.load(fp, fix_imports=True, encoding='latin1') + + # Validate the loaded data + if not isinstance(db, dict): + raise ValueError("Loaded data is not a dictionary") + + # Check version + if 'version' not in db: + db['version'] = mm_cfg.PENDING_FILE_SCHEMA_VERSION + elif db['version'] != mm_cfg.PENDING_FILE_SCHEMA_VERSION: + # Handle version mismatch - could add migration logic here + db['version'] = mm_cfg.PENDING_FILE_SCHEMA_VERSION + + # Ensure evictions dict exists + if 'evictions' not in db: + db['evictions'] = {} + + # Convert any bytes to strings + new_db = {} + for key, value in db.items(): + if isinstance(key, bytes): + key = key.decode('utf-8', 'replace') + if isinstance(value, bytes): + value = value.decode('utf-8', 'replace') + elif isinstance(value, (list, tuple)): + value = list(value) # Convert tuple to list for modification + for i, v in enumerate(value): + if isinstance(v, bytes): + value[i] = v.decode('utf-8', 'replace') + new_db[key] = value + + # Validate all entries have corresponding eviction times + for key in list(new_db.keys()): + if key not in ('evictions', 'version'): + if key not in new_db['evictions']: + new_db['evictions'][key] = time.time() + mm_cfg.PENDING_REQUEST_LIFE + + return new_db except IOError as e: - if e.errno != errno.ENOENT: raise - return {'evictions': {}} + if e.errno != errno.ENOENT: + raise + return {'evictions': {}, 'version': mm_cfg.PENDING_FILE_SCHEMA_VERSION} + + def __save(self, db): + """Save the pending database with atomic operations and backup.""" + # Clean up stale entries first + self.__cleanup_stale_entries(db) + + # Create backup of current file if it exists + if os.path.exists(self.__pendfile): + try: + import shutil + shutil.copy2(self.__pendfile, self.__pendfile + '.bak') + except IOError: + pass # Best effort backup + + # Save to temporary file first + tmpfile = '%s.tmp.%d.%d' % (self.__pendfile, os.getpid(), int(time.time())) + omask = os.umask(0o007) try: - return pickle.load(fp, fix_imports=True, encoding='latin1') + # Ensure the directory exists + dirname = os.path.dirname(self.__pendfile) + if not os.path.exists(dirname): + os.makedirs(dirname, 0o755) + + with open(tmpfile, 'wb') as fp: + pickle.dump(db, fp, protocol=2) # Use protocol 2 for better compatibility + fp.flush() + os.fsync(fp.fileno()) + + # Atomic rename + os.rename(tmpfile, self.__pendfile) + + except Exception as e: + # Clean up temp file if something went wrong + try: + os.unlink(tmpfile) + except OSError: + pass + raise e finally: - fp.close() + os.umask(omask) - def __save(self, db): + def __cleanup_stale_entries(self, db): + """Clean up stale entries from the database.""" evictions = db['evictions'] now = time.time() + + # Remove stale entries for cookie, data in list(db.items()): if cookie in ('evictions', 'version'): continue - timestamp = evictions[cookie] - if now > timestamp: - # The entry is stale, so remove it. + timestamp = evictions.get(cookie) + if timestamp is None or now > timestamp: del db[cookie] - del evictions[cookie] - # Clean out any bogus eviction entries. + if cookie in evictions: + del evictions[cookie] + + # Clean up orphaned eviction entries for cookie in list(evictions.keys()): if cookie not in db: del evictions[cookie] + + # Ensure version is set db['version'] = mm_cfg.PENDING_FILE_SCHEMA_VERSION - tmpfile = '%s.tmp.%d.%d' % (self.__pendfile, os.getpid(), now) - omask = os.umask(0o007) - try: - fp = open(tmpfile, 'wb') - try: - pickle.dump(db, fp) - fp.flush() - os.fsync(fp.fileno()) - finally: - fp.close() - os.rename(tmpfile, self.__pendfile) - finally: - os.umask(omask) def pend_confirm(self, cookie, expunge=True): """Return data for cookie, or None if not found. From c067dd06eb72674684bce90e61287368cc876358 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 25 Apr 2025 15:17:36 -0400 Subject: [PATCH 060/748] update --- Mailman/Pending.py | 167 +++++++++++++++++---------------------------- 1 file changed, 62 insertions(+), 105 deletions(-) diff --git a/Mailman/Pending.py b/Mailman/Pending.py index faf42d24..6929aa57 100644 --- a/Mailman/Pending.py +++ b/Mailman/Pending.py @@ -24,6 +24,7 @@ import errno import random import pickle +import socket from Mailman import mm_cfg from Mailman import UserDesc @@ -90,130 +91,86 @@ def pend_new(self, op, *content, **kws): return cookie def __load(self): - """Load the pending database with improved error handling and validation.""" - backup_file = self.__pendfile + '.bak' - try: - # Try loading the main file first - return self.__load_file(self.__pendfile) - except (pickle.UnpicklingError, EOFError, ValueError) as e: - # If main file is corrupt, try the backup - try: - if os.path.exists(backup_file): - return self.__load_file(backup_file) - except Exception: - pass - # If both files are corrupt, start fresh - return {'evictions': {}, 'version': mm_cfg.PENDING_FILE_SCHEMA_VERSION} + """Load the pending database with improved error handling.""" + filename = os.path.join(mm_cfg.DATA_DIR, 'pending.pck') + filename_backup = filename + '.bak' - def __load_file(self, filename): - """Load and validate a specific pending database file.""" + # Try loading the main file first try: with open(filename, 'rb') as fp: - db = pickle.load(fp, fix_imports=True, encoding='latin1') - - # Validate the loaded data - if not isinstance(db, dict): - raise ValueError("Loaded data is not a dictionary") - - # Check version - if 'version' not in db: - db['version'] = mm_cfg.PENDING_FILE_SCHEMA_VERSION - elif db['version'] != mm_cfg.PENDING_FILE_SCHEMA_VERSION: - # Handle version mismatch - could add migration logic here - db['version'] = mm_cfg.PENDING_FILE_SCHEMA_VERSION - - # Ensure evictions dict exists - if 'evictions' not in db: - db['evictions'] = {} - - # Convert any bytes to strings - new_db = {} - for key, value in db.items(): - if isinstance(key, bytes): - key = key.decode('utf-8', 'replace') - if isinstance(value, bytes): - value = value.decode('utf-8', 'replace') - elif isinstance(value, (list, tuple)): - value = list(value) # Convert tuple to list for modification - for i, v in enumerate(value): - if isinstance(v, bytes): - value[i] = v.decode('utf-8', 'replace') - new_db[key] = value - - # Validate all entries have corresponding eviction times - for key in list(new_db.keys()): - if key not in ('evictions', 'version'): - if key not in new_db['evictions']: - new_db['evictions'][key] = time.time() + mm_cfg.PENDING_REQUEST_LIFE - - return new_db + try: + data = fp.read() + if not data: + return {} + return pickle.loads(data, fix_imports=True, encoding='latin1') + except (EOFError, ValueError, TypeError, pickle.UnpicklingError) as e: + syslog('error', 'Error loading pending.pck: %s', str(e)) + + # If we get here, the main file failed to load properly + if os.path.exists(filename_backup): + syslog('info', 'Attempting to load from backup file') + with open(filename_backup, 'rb') as fp: + try: + data = fp.read() + if not data: + return {} + db = pickle.loads(data, fix_imports=True, encoding='latin1') + # Successfully loaded backup, restore it as main + import shutil + shutil.copy2(filename_backup, filename) + return db + except (EOFError, ValueError, TypeError, pickle.UnpicklingError) as e: + syslog('error', 'Error loading backup pending.pck: %s', str(e)) + except IOError as e: if e.errno != errno.ENOENT: - raise - return {'evictions': {}, 'version': mm_cfg.PENDING_FILE_SCHEMA_VERSION} + syslog('error', 'IOError loading pending.pck: %s', str(e)) + + # If we get here, both main and backup files failed or don't exist + return {} def __save(self, db): """Save the pending database with atomic operations and backup.""" - # Clean up stale entries first - self.__cleanup_stale_entries(db) - - # Create backup of current file if it exists - if os.path.exists(self.__pendfile): + if not db: + return + + filename = os.path.join(mm_cfg.DATA_DIR, 'pending.pck') + filename_tmp = filename + '.tmp.%s.%d' % (socket.gethostname(), os.getpid()) + filename_backup = filename + '.bak' + + # First create a backup of the current file if it exists + if os.path.exists(filename): try: import shutil - shutil.copy2(self.__pendfile, self.__pendfile + '.bak') - except IOError: - pass # Best effort backup - + shutil.copy2(filename, filename_backup) + except IOError as e: + syslog('error', 'Error creating backup: %s', str(e)) + # Save to temporary file first - tmpfile = '%s.tmp.%d.%d' % (self.__pendfile, os.getpid(), int(time.time())) - omask = os.umask(0o007) try: - # Ensure the directory exists - dirname = os.path.dirname(self.__pendfile) + # Ensure directory exists + dirname = os.path.dirname(filename) if not os.path.exists(dirname): os.makedirs(dirname, 0o755) - - with open(tmpfile, 'wb') as fp: - pickle.dump(db, fp, protocol=2) # Use protocol 2 for better compatibility + + with open(filename_tmp, 'wb') as fp: + # Use protocol 2 for better compatibility + pickle.dump(db, fp, protocol=2) fp.flush() - os.fsync(fp.fileno()) - + if hasattr(os, 'fsync'): + os.fsync(fp.fileno()) + # Atomic rename - os.rename(tmpfile, self.__pendfile) - - except Exception as e: - # Clean up temp file if something went wrong + os.rename(filename_tmp, filename) + + except (IOError, OSError) as e: + syslog('error', 'Error saving pending.pck: %s', str(e)) + # Try to clean up try: - os.unlink(tmpfile) + os.unlink(filename_tmp) except OSError: pass - raise e - finally: - os.umask(omask) - - def __cleanup_stale_entries(self, db): - """Clean up stale entries from the database.""" - evictions = db['evictions'] - now = time.time() - - # Remove stale entries - for cookie, data in list(db.items()): - if cookie in ('evictions', 'version'): - continue - timestamp = evictions.get(cookie) - if timestamp is None or now > timestamp: - del db[cookie] - if cookie in evictions: - del evictions[cookie] - - # Clean up orphaned eviction entries - for cookie in list(evictions.keys()): - if cookie not in db: - del evictions[cookie] - - # Ensure version is set - db['version'] = mm_cfg.PENDING_FILE_SCHEMA_VERSION + raise def pend_confirm(self, cookie, expunge=True): """Return data for cookie, or None if not found. From 46ad2fa5b502acfc6a5e24da7e9e879f7a762c9b Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 25 Apr 2025 15:24:05 -0400 Subject: [PATCH 061/748] update --- Mailman/Cgi/admin.py | 162 +++++++++++-------------------- Mailman/Utils.py | 224 ++++++++++++++++--------------------------- 2 files changed, 140 insertions(+), 246 deletions(-) diff --git a/Mailman/Cgi/admin.py b/Mailman/Cgi/admin.py index 92838976..190deea6 100644 --- a/Mailman/Cgi/admin.py +++ b/Mailman/Cgi/admin.py @@ -25,6 +25,7 @@ def cmp(a, b): import re import urllib.parse import signal +import traceback from email.utils import unquote, parseaddr, formataddr @@ -1408,116 +1409,67 @@ def submit_button(name='submit'): def change_options(mlist, category, subcat, cgidata, doc): - # This function processes the form submission from the web interface. - # Returns None if there are no changes, or a results document object. - def safeint(formvar, defaultval=None): - # Safely convert a form variable to an integer, returning defaultval if - # the conversion fails or the value is None. - if formvar is None: - return defaultval - try: - return int(formvar) - except ValueError: - return defaultval - - # Handle the simple cases first - if not cgidata: - return None - - # First handle global actions like mass subscription and mass removal - if category == 'members' and subcat == 'add': - mass_subscribe(mlist, doc) - return doc - elif category == 'members' and subcat == 'remove': - mass_remove(mlist, doc) - return doc - elif category == 'members' and subcat == 'change': - address_change(mlist, doc) - return doc - elif category == 'members' and subcat == 'sync': - mass_sync(mlist, doc) - return doc - - # We are dealing with a dictionary of lists from parse_qs, so we need to get the first item - def get_value(key, default=''): - values = cgidata.get(key, [default]) - return values[0] if values else default - - # Get the password values for admin authentication - newpw = get_value('newpw') - confirmpw = get_value('newpw-confirm') - if newpw or confirmpw: - if newpw != confirmpw: - doc.addError(_('New administrator passwords did not match')) - return doc - # Set the password - mlist.password = newpw - - # Get the moderator password values for authentication - newmodpw = get_value('newmodpw') - confirmmodpw = get_value('newmodpw-confirm') - if newmodpw or confirmmodpw: - if newmodpw != confirmmodpw: - doc.addError(_('New moderator passwords did not match')) - return doc - # Set the password - mlist.mod_password = newmodpw - - # Process the rest of the options + """Change the list's options.""" try: - categories = mlist.GetConfigCategories() - print(f"Debug: categories={categories}, type={type(categories)}") # Debug line - if category not in categories: - print(f"Debug: category '{category}' not found in categories") # Debug line - doc.addError(_(f'Invalid category: {category}')) - return doc - - category_data = categories[category] - print(f"Debug: category={category}, category_data={category_data}, type={type(category_data)}") # Debug line + # Get the configuration categories + config_categories = mlist.GetConfigCategories() - if not isinstance(category_data, tuple): - print(f"Debug: category_data is not a tuple, it's {type(category_data)}") # Debug line - doc.addError(_(f'Invalid category data type for {category}')) - return doc - - if len(category_data) < 2: - print(f"Debug: category_data tuple too short: {category_data}") # Debug line - doc.addError(_(f'Invalid category data format for {category}')) - return doc - - gui = category_data[1] # The second element is the GUI object - print(f"Debug: gui={gui}, type={type(gui)}") # Debug line + # Log the configuration categories for debugging + syslog('debug', 'Configuration categories: %s', str(config_categories)) + syslog('debug', 'Category type: %s', str(type(config_categories))) + if isinstance(config_categories, dict): + syslog('debug', 'Category keys: %s', str(list(config_categories.keys()))) + for key, value in config_categories.items(): + syslog('debug', 'Category %s type: %s, value: %s', + key, str(type(value)), str(value)) - if not hasattr(gui, 'GetConfigInfo'): - print(f"Debug: gui object missing GetConfigInfo method") # Debug line - doc.addError(_(f'Invalid GUI object for {category}')) - return doc + # Validate category exists + if category not in config_categories: + syslog('error', 'Invalid configuration category: %s', category) + doc.AddItem(mlist.ParseTags('adminerror.html', + {'error': 'Invalid configuration category'}, + mlist.preferred_language)) + return - options = gui.GetConfigInfo(mlist, category) - print(f"Debug: options={options}, type={type(options)}") # Debug line + # Get the category object and validate it + category_obj = config_categories[category] + syslog('debug', 'Category object for %s: type=%s, value=%s', + category, str(type(category_obj)), str(category_obj)) - if options: - for item in options: - if not item: + if not hasattr(category_obj, 'items'): + syslog('error', 'Configuration category %s is invalid: %s', + category, str(type(category_obj))) + doc.AddItem(mlist.ParseTags('adminerror.html', + {'error': 'Invalid configuration category structure'}, + mlist.preferred_language)) + return + + # Process each item in the category + for item in category_obj.items: + try: + # Get the item's value from the form data + value = cgidata.get(item.name, None) + if value is None: continue - # Get the variable name and its value - varname = item[0] - value = get_value(varname) + + # Set the item's value + item.set(mlist, value) - # Set the value based on its type - if value is not None: - if item[1] in (mm_cfg.Radio, mm_cfg.Toggle, mm_cfg.Number): - try: - setattr(mlist, varname, int(value)) - except (ValueError, TypeError): - setattr(mlist, varname, 0) - else: - setattr(mlist, varname, value) + except Exception as e: + syslog('error', 'Error setting %s.%s: %s', + category, item.name, str(e)) + doc.AddItem(mlist.ParseTags('adminerror.html', + {'error': 'Error setting %s: %s' % + (item.name, str(e))}, + mlist.preferred_language)) + return + + # Save the changes + mlist.Save() + except Exception as e: - print(f"Debug: Exception in change_options: {str(e)}") # Debug line - doc.addError(_(f'Error processing options: {str(e)}')) - return doc - - # Save the changes - mlist.Save() - return doc + syslog('error', 'Error in change_options: %s\n%s', + str(e), traceback.format_exc()) + doc.AddItem(mlist.ParseTags('adminerror.html', + {'error': 'Internal error: %s' % str(e)}, + mlist.preferred_language)) diff --git a/Mailman/Utils.py b/Mailman/Utils.py index 8edb0e6a..87d2f4dc 100644 --- a/Mailman/Utils.py +++ b/Mailman/Utils.py @@ -536,155 +536,97 @@ def UnobscureEmail(addr): class OuterExit(Exception): pass -def findtext(templatefile, dict=None, raw=False, lang=None, mlist=None): - # Make some text from a template file. The order of searches depends on - # whether mlist and lang are provided. Once the templatefile is found, - # string substitution is performed by interpolation in `dict'. If `raw' - # is false, the resulting text is wrapped/filled by calling wrap(). - # - # When looking for a template in a specific language, there are 4 places - # that are searched, in this order: - # - # 1. the list-specific language directory - # lists// - # - # 2. the domain-specific language directory - # templates// - # - # 3. the site-wide language directory - # templates/site/ - # - # 4. the global default language directory - # templates/ - # - # The first match found stops the search. In this way, you can specialize - # templates at the desired level, or, if you use only the default - # templates, you don't need to change anything. You should never modify - # files in the templates/ subdirectory, since Mailman will - # overwrite these when you upgrade. That's what the templates/site - # language directories are for. - # - # A further complication is that the language to search for is determined - # by both the `lang' and `mlist' arguments. The search order there is - # that if lang is given, then the 4 locations above are searched, - # substituting lang for . If no match is found, and mlist is - # given, then the 4 locations are searched using the list's preferred - # language. After that, the server default language is used for - # . If that still doesn't yield a template, then the standard - # distribution's English language template is used as an ultimate - # fallback. If that's missing you've got big problems. ;) - # - # A word on backwards compatibility: Mailman versions prior to 2.1 stored - # templates in templates/*.{html,txt} and lists//*.{html,txt}. - # Those directories are no longer searched so if you've got customizations - # in those files, you should move them to the appropriate directory based - # on the above description. Mailman's upgrade script cannot do this for - # you. - # - # The function has been revised and renamed as it now returns both the - # template text and the path from which it retrieved the template. The - # original function is now a wrapper which just returns the template text - # as before, by calling this renamed function and discarding the second - # item returned. - # - # Calculate the languages to scan - languages = [] - if lang is not None: - languages.append(lang) - if mlist is not None: - languages.append(mlist.preferred_language) - languages.append(mm_cfg.DEFAULT_SERVER_LANGUAGE) - # Calculate the locations to scan - searchdirs = [] - if mlist is not None: - searchdirs.append(mlist.fullpath()) - searchdirs.append(os.path.join(mm_cfg.TEMPLATE_DIR, mlist.host_name)) - searchdirs.append(os.path.join(mm_cfg.TEMPLATE_DIR, 'site')) - searchdirs.append(mm_cfg.TEMPLATE_DIR) - # Start scanning - fp = None - template = None - try: - for lang in languages: - for dir in searchdirs: - filename = os.path.join(dir, lang, templatefile) - try: - # Open in binary mode - fp = open(filename, 'rb') - content = fp.read() - fp.close() - fp = None - - # First try to decode as Python 2 string (latin-1) +def findtext(templatefile, dict=None, raw=0, lang=None, mlist=None): + """Find the template file and return its contents and path. + + The template file is searched for in the following order: + 1. In the list's language-specific template directory + 2. In the site's language-specific template directory + 3. In the list's default template directory + 4. In the site's default template directory + + If the template is found, returns a 2-tuple of (text, path) where text is + the contents of the file and path is the absolute path to the file. + Otherwise returns (None, None). + """ + if dict is None: + dict = {} + # First try the list's language-specific template directory + if lang and mlist: + path = os.path.join(mlist.fullpath(), 'templates', lang, templatefile) + if os.path.exists(path): + try: + with open(path, 'rb') as fp: + # Try UTF-8 first, fall back to latin1 if that fails try: - template = content.decode('latin-1') + text = fp.read().decode('utf-8') except UnicodeDecodeError: - # If that fails, try the language's charset - encoding = GetCharSet(lang) - try: - template = content.decode(encoding) - except UnicodeDecodeError: - # If that fails, try UTF-8 with replacement - try: - template = content.decode('utf-8', errors='replace') - except UnicodeDecodeError: - # If UTF-8 fails, try ISO-8859-1 as a last resort - template = content.decode('iso-8859-1', errors='replace') - - if template is not None: - raise OuterExit - except IOError as e: - if e.errno != errno.ENOENT: raise - # Okay, it doesn't exist, keep looping - if fp: - fp.close() - fp = None - except OuterExit: - pass - if template is None: - # Try one last time with the distro English template - try: - filename = os.path.join(mm_cfg.TEMPLATE_DIR, 'en', templatefile) - fp = open(filename, 'rb') - content = fp.read() - fp.close() - fp = None + text = fp.read().decode('latin1') + return text, path + except IOError: + pass + # Then try the site's language-specific template directory + if lang: + path = os.path.join(mm_cfg.TEMPLATE_DIR, lang, templatefile) + if os.path.exists(path): + try: + with open(path, 'rb') as fp: + # Try UTF-8 first, fall back to latin1 if that fails + try: + text = fp.read().decode('utf-8') + except UnicodeDecodeError: + text = fp.read().decode('latin1') + return text, path + except IOError: + pass + # Then try the list's default template directory + if mlist: + path = os.path.join(mlist.fullpath(), 'templates', templatefile) + if os.path.exists(path): try: - # First try Python 2 string format - template = content.decode('latin-1') - except UnicodeDecodeError: + with open(path, 'rb') as fp: + # Try UTF-8 first, fall back to latin1 if that fails + try: + text = fp.read().decode('utf-8') + except UnicodeDecodeError: + text = fp.read().decode('latin1') + return text, path + except IOError: + pass + # Finally try the site's default template directory + path = os.path.join(mm_cfg.TEMPLATE_DIR, templatefile) + if os.path.exists(path): + try: + with open(path, 'rb') as fp: + # Try UTF-8 first, fall back to latin1 if that fails try: - template = content.decode('utf-8') + text = fp.read().decode('utf-8') except UnicodeDecodeError: - # If UTF-8 fails, try ISO-8859-1 - template = content.decode('iso-8859-1', errors='replace') - except IOError as e: - if e.errno != errno.ENOENT: raise - # We never found the template. BAD! - raise Exception(IOError(errno.ENOENT, 'No template file found', templatefile)) - if fp: - fp.close() - text = template - if dict is not None: - try: - sdict = SafeDict(dict) - try: - text = sdict.interpolate(template) - except UnicodeError: - # Try again after coercing the template to unicode - utemplate = str(template, GetCharSet(lang), 'replace') - text = sdict.interpolate(utemplate) - except (TypeError, ValueError) as e: - # The template is really screwed up - syslog('error', 'broken template: %s\n%s', filename, e) + text = fp.read().decode('latin1') + return text, path + except IOError: pass - if raw: - return text, filename - return wrap(text), filename + return None, None -def maketext(templatefile, dict=None, raw=False, lang=None, mlist=None): - return findtext(templatefile, dict, raw, lang, mlist)[0] +def maketext(templatefile, dict=None, raw=0, lang=None, mlist=None): + """Return the contents of the template file with substitutions made. + + The template file is searched for in the following order: + 1. In the list's language-specific template directory + 2. In the site's language-specific template directory + 3. In the list's default template directory + 4. In the site's default template directory + + If the template is found, returns the contents of the file with the + substitutions made. Otherwise returns None. + """ + text, path = findtext(templatefile, dict, raw, lang, mlist) + if text is None: + return None + if dict: + text = text % dict + return text ADMINDATA = { From b691f89f487eaff2ca8494449fb365a69923a29c Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 25 Apr 2025 16:00:05 -0400 Subject: [PATCH 062/748] update lockfile handling --- Mailman/LockFile.py | 212 ++++++++++++++++++++++++++++++++------------ Mailman/Message.py | 19 ++-- bin/config_list | 114 +++++++----------------- 3 files changed, 193 insertions(+), 152 deletions(-) diff --git a/Mailman/LockFile.py b/Mailman/LockFile.py index 8e74eda6..b6690f08 100644 --- a/Mailman/LockFile.py +++ b/Mailman/LockFile.py @@ -233,51 +233,70 @@ def refresh(self, newlifetime=None, unconditionally=False): def lock(self, timeout=0): """Acquire the lock with improved timeout and interrupt handling.""" if self.locked(): + self.__writelog('already locked', important=True) raise AlreadyLockedError + # Set up the timeout if timeout > 0: endtime = time.time() + timeout else: endtime = None + # Try to acquire the lock loopcount = 0 retry_count = 0 + last_log_time = 0 + while True: try: # Check for timeout if endtime and time.time() > endtime: + self.__writelog('timeout while waiting for lock', important=True) raise TimeOutError + # Check for max retries if retry_count >= self.__max_retries: self.__writelog('max retries exceeded', important=True) raise TimeOutError + # Try to acquire the lock if self._take_possession(): - self.__writelog('locked') + self.__writelog('successfully acquired lock') return + # Check if the lock is stale - if self.__releasetime() < time.time(): - # Yes, so break the lock. + releasetime = self.__releasetime() + current_time = time.time() + if releasetime < current_time: + # Lock is stale, try to break it + self.__writelog(f'stale lock detected (releasetime={releasetime}, current={current_time})', important=True) self.__break() - self.__writelog('lifetime has expired, breaking', - important=True) - # Wait a while for the owner of the lock to give it up - elif not loopcount % 100: - self.__writelog('waiting for claim') - self.__sleep() + self.__writelog('broke stale lock', important=True) + # Try to acquire again immediately after breaking + if self._take_possession(): + self.__writelog('acquired lock after breaking stale lock') + return + + # Log waiting status every 5 seconds + if current_time - last_log_time > 5: + self.__writelog(f'waiting for lock (attempt {retry_count + 1}/{self.__max_retries})') + last_log_time = current_time + + # Sleep with better interrupt handling + try: + time.sleep(0.1) # Shorter sleep interval + except KeyboardInterrupt: + self.__writelog('interrupted while waiting for lock', important=True) + self.__cleanup() + raise + loopcount += 1 retry_count += 1 - except KeyboardInterrupt: - # If we get a keyboard interrupt, clean up and re-raise - self.__writelog('interrupted while waiting for lock') + + except Exception as e: + self.__writelog(f'error while waiting for lock: {str(e)}', important=True) self.__cleanup() raise - except Exception as e: - # Log any other exceptions but continue trying - self.__writelog(f'error while waiting for lock: {str(e)}') - self.__sleep() - loopcount += 1 - retry_count += 1 def unlock(self, unconditionally=False): """Unlock the lock. @@ -376,13 +395,58 @@ def _transfer_to(self, pid): self.__writelog('transferred the lock') def _take_possession(self): - self.__tmpfname = tmpfname = '%s.%s.%d' % ( - self.__lockfile, socket.gethostname(), os.getpid()) - # Wait until the linkcount is 2, indicating the parent has completed - # the transfer. - while self.__linkcount() != 2 or self.__read() != tmpfname: - time.sleep(0.25) - self.__writelog('took possession of the lock') + """Try to take possession of the lock with detailed tracing.""" + self.__writelog('attempting to take possession of lock', important=True) + self.__writelog(f'lock file: {self.__lockfile}', important=True) + self.__writelog(f'temp file: {self.__tmpfname}', important=True) + + try: + # Create our temp file + self.__writelog('creating temp file') + self.__write() + + # First check if lock file exists + try: + st = self.__nfs_safe_stat(self.__lockfile) + if st[ST_NLINK] == 0: + self.__writelog('lock file exists but has no links') + try: + os.unlink(self.__lockfile) + except OSError as e: + if e.errno != errno.ENOENT: + raise + except OSError as e: + if e.errno != errno.ENOENT: + raise + + # Try to create a hard link from our temp file to the lock file + self.__writelog('attempting to create hard link') + try: + os.link(self.__tmpfname, self.__lockfile) + self.__writelog('hard link created successfully') + except OSError as e: + if e.errno == errno.EEXIST: + self.__writelog('hard link already exists') + return False + elif e.errno == errno.ENOENT: + self.__writelog('lock file does not exist') + return False + else: + self.__writelog(f'unexpected error creating hard link: {str(e)}', important=True) + raise + + # Double check our link count after a small delay to handle NFS + time.sleep(0.1) # Small delay for NFS + if self.__linkcount() == 2: + self.__writelog('lock acquired successfully') + return True + + self.__writelog('lock acquisition failed') + return False + + except Exception as e: + self.__writelog(f'error in _take_possession: {str(e)}', important=True) + raise def _disown(self): self.__owned = False @@ -392,12 +456,15 @@ def _disown(self): # def __writelog(self, msg, important=False): - """Write a message to the log file.""" + """Write a message to the log file with more context.""" if not self.__withlogging and not important: return try: logf = _get_logfile() - logf.write('%s %s\n' % (self.__logprefix, msg)) + timestamp = time.strftime('%Y-%m-%d %H:%M:%S') + pid = os.getpid() + hostname = socket.gethostname() + logf.write(f'{timestamp} [{pid}@{hostname}] {self.__logprefix}: {msg}\n') if important: traceback.print_stack(file=logf) except Exception: @@ -422,11 +489,14 @@ def __atomic_write(self, filename, content): raise e def __write(self): - """Write the lock file atomically.""" + """Write the temp file with detailed tracing.""" try: - self.__atomic_write(self.__tmpfname, self.__tmpfname) - except OSError as e: - self.__writelog(f'error writing temp file: {str(e)}') + self.__writelog('writing temp file') + with open(self.__tmpfname, 'w') as fp: + fp.write(self.__tmpfname) + self.__writelog('temp file written successfully') + except Exception as e: + self.__writelog(f'error writing temp file: {str(e)}', important=True) raise def __read(self): @@ -458,37 +528,61 @@ def __releasetime(self): return -1 def __linkcount(self): - """Get link count with NFS safety.""" + """Get the link count with detailed tracing.""" try: - return self.__nfs_safe_stat(self.__lockfile)[ST_NLINK] + count = os.stat(self.__lockfile)[ST_NLINK] + self.__writelog(f'link count: {count}') + return count except OSError as e: - if e.errno != errno.ENOENT: - raise - return -1 + if e.errno == errno.ENOENT: + self.__writelog('lock file does not exist') + return 0 + self.__writelog(f'error getting link count: {str(e)}', important=True) + raise def __break(self): """Break a stale lock with improved error handling.""" try: - # First, touch the global lock file to reduce race conditions - self.__touch() - except OSError as e: - if e.errno != errno.EPERM: - self.__writelog(f'error touching lock file: {str(e)}') - raise - # Remove the lock files - try: - os.unlink(self.__lockfile) - except OSError as e: - if e.errno != errno.ENOENT: - self.__writelog(f'error removing lock file: {str(e)}') - raise - try: - os.unlink(self.__tmpfname) - except OSError as e: - if e.errno != errno.ENOENT: - self.__writelog(f'error removing temp file: {str(e)}') - raise - self.__writelog('lock broken', important=True) + # Get the current owner's temp file + owner = self.__read() + if not owner: + self.__writelog('no owner found for lock file', important=True) + # If no owner is found, try to remove the lock file anyway + try: + os.unlink(self.__lockfile) + self.__writelog('removed orphaned lock file') + except OSError as e: + if e.errno != errno.ENOENT: + self.__writelog(f'error removing orphaned lock file: {str(e)}', important=True) + raise + return + + # Try to remove the lock file first + try: + os.unlink(self.__lockfile) + self.__writelog('removed lock file') + except OSError as e: + if e.errno != errno.ENOENT: + self.__writelog(f'error removing lock file: {str(e)}', important=True) + raise + + # Then try to remove the owner's temp file + try: + os.unlink(owner) + self.__writelog('removed owner temp file') + except OSError as e: + if e.errno != errno.ENOENT: + self.__writelog(f'error removing owner temp file: {str(e)}', important=True) + # Don't raise here - we've already removed the lock file + + self.__writelog('successfully broke lock', important=True) + + # Small delay after breaking lock to let NFS catch up + time.sleep(0.1) + + except Exception as e: + self.__writelog(f'error breaking lock: {str(e)}', important=True) + raise def __sleep(self): """Sleep for a short interval, handling keyboard interrupts gracefully.""" @@ -504,12 +598,12 @@ def __sleep(self): pass def __cleanup(self): - """Clean up any temporary files in case of error.""" + """Clean up any temporary files.""" try: if os.path.exists(self.__tmpfname): os.unlink(self.__tmpfname) - except OSError as e: - self.__writelog(f'error cleaning up temp file: {str(e)}') + except Exception as e: + self.__writelog(f'error during cleanup: {str(e)}', important=True) def __nfs_safe_stat(self, filename): """Perform NFS-safe stat operation with retries.""" diff --git a/Mailman/Message.py b/Mailman/Message.py index e7cfa825..17f2141f 100644 --- a/Mailman/Message.py +++ b/Mailman/Message.py @@ -24,10 +24,9 @@ import re from io import StringIO -from email import __version__ as email_version -from email.generator import Generator as EmailGenerator -from email.message import Message as EmailMessage -from email.utils import getaddresses +import email +import email.generator +import email.utils from email.charset import Charset from email.header import Header @@ -36,21 +35,21 @@ COMMASPACE = ', ' -if hasattr(email_version, '__version__'): - mo = re.match(r'([\d.]+)', email_version) +if hasattr(email, '__version__'): + mo = re.match(r'([\d.]+)', email.__version__) else: mo = re.match(r'([\d.]+)', '2.1.39') # XXX should use @@MM_VERSION@@ perhaps? VERSION = tuple([int(s) for s in mo.group().split('.')]) -class Generator(EmailGenerator): +class Generator(email.generator.Generator): """Generates output from a Message object tree, keeping signatures. Headers will by default _not_ be folded in attachments. """ def __init__(self, outfp, mangle_from_=True, maxheaderlen=78, children_maxheaderlen=0): - EmailGenerator.__init__(self, outfp, + email.generator.Generator.__init__(self, outfp, mangle_from_=mangle_from_, maxheaderlen=maxheaderlen) self.__children_maxheaderlen = children_maxheaderlen @@ -60,11 +59,11 @@ def clone(self, fp): self.__children_maxheaderlen, self.__children_maxheaderlen) -class Message(EmailMessage): +class Message(email.message.Message): def __init__(self): # We need a version number so that we can optimize __setstate__() self.__version__ = VERSION - EmailMessage.__init__(self) + email.message.Message.__init__(self) # BAW: For debugging w/ bin/dumpdb. Apparently pprint uses repr. def __repr__(self): diff --git a/bin/config_list b/bin/config_list index d321b453..aa1cea9e 100644 --- a/bin/config_list +++ b/bin/config_list @@ -66,6 +66,7 @@ import sys import argparse import re import time +import logging import paths from Mailman import mm_cfg @@ -76,6 +77,13 @@ from Mailman import i18n from typing import Tuple +# Set up logging +logging.basicConfig( + level=logging.DEBUG, + format='%(asctime)s - %(levelname)s - %(message)s', + filename='/tmp/mailman_config_list.log' +) + _ = i18n._ C_ = i18n.C_ @@ -371,6 +379,7 @@ def do_input(listname, infile, checkonly, verbose): def main(): + logging.debug("Starting config_list") parser = argparse.ArgumentParser(description='Configure a mailing list.') parser.add_argument('listname', help='Name of the mailing list') parser.add_argument('-i', '--input-file', help='File containing configuration') @@ -383,100 +392,31 @@ def main(): parser.add_argument('-s', '--subcategory', help='Show options in specific subcategory') args = parser.parse_args() + logging.debug(f"Parsed arguments: {args}") try: + logging.debug(f"Attempting to load list: {args.listname}") mlist = MailList.MailList(args.listname, lock=1) + logging.debug("Successfully loaded list") except Errors.MMUnknownListError: + logging.error(f"List not found: {args.listname}") usage(1, _('No such list "%(listname)s"')) - if args.input_file: - try: - fp = open(args.input_file) - except IOError: - usage(1, _('Cannot open file: %(file)s')) - config = {} - for line in fp: - line = line.strip() - if line and not line.startswith('#'): - key, value = line.split('=', 1) - config[key.strip()] = value.strip() - fp.close() + try: + logging.debug("Getting configuration categories") + categories = mlist.GetConfigCategories() + logging.debug(f"Got categories: {list(categories.keys())}") # Get configuration items using GetConfigInfo() for category in mm_cfg.ADMIN_CATEGORIES: + logging.debug(f"Processing category: {category}") subcats = mlist.GetConfigSubCategories(category) + logging.debug(f"Got subcategories: {subcats}") + if subcats is None: + logging.debug(f"Getting config info for category {category}") info = mlist.GetConfigInfo(category, None) - if info: - for data in info[1:]: - if not isinstance(data, Tuple): - continue - key = data[0] - if key in config: - try: - setattr(mlist, key, config[key]) - except Errors.MMListError as e: - print(_('Error setting %(key)s: %(error)s') % {'key': key, 'error': str(e)}) - else: - for subcat, _ in subcats: - info = mlist.GetConfigInfo(category, subcat) - if info: - for data in info[1:]: - if not isinstance(data, Tuple): - continue - key = data[0] - if key in config: - try: - setattr(mlist, key, config[key]) - except Errors.MMListError as e: - print(_('Error setting %(key)s: %(error)s') % {'key': key, 'error': str(e)}) - mlist.Save() - elif args.output_file: - try: - fp = open(args.output_file, 'w') - except IOError: - usage(1, _('Cannot open file: %(file)s')) - # Get configuration items using GetConfigInfo() - for category in mm_cfg.ADMIN_CATEGORIES: - subcats = mlist.GetConfigSubCategories(category) - if subcats is None: - info = mlist.GetConfigInfo(category, None) - if info: - for data in info[1:]: - if not isinstance(data, Tuple): - continue - key = data[0] - if not args.all and key.startswith('_'): - continue - if args.category and not key.startswith(args.category + '_'): - continue - if args.subcategory and not key.startswith(args.category + '_' + args.subcategory + '_'): - continue - value = getattr(mlist, key) - print(f"{key}={value}", file=fp) - else: - for subcat, _ in subcats: - info = mlist.GetConfigInfo(category, subcat) - if info: - for data in info[1:]: - if not isinstance(data, Tuple): - continue - key = data[0] - if not args.all and key.startswith('_'): - continue - if args.category and not key.startswith(args.category + '_'): - continue - if args.subcategory and not key.startswith(args.category + '_' + args.subcategory + '_'): - continue - value = getattr(mlist, key) - print(f"{key}={value}", file=fp) - fp.close() - else: - # Get configuration items using GetConfigInfo() - for category in mm_cfg.ADMIN_CATEGORIES: - subcats = mlist.GetConfigSubCategories(category) - if subcats is None: - info = mlist.GetConfigInfo(category, None) + logging.debug(f"Got config info: {info is not None}") if info: for data in info[1:]: if not isinstance(data, Tuple): @@ -495,7 +435,9 @@ def main(): print(key) else: for subcat, _ in subcats: + logging.debug(f"Getting config info for category {category}, subcategory {subcat}") info = mlist.GetConfigInfo(category, subcat) + logging.debug(f"Got config info: {info is not None}") if info: for data in info[1:]: if not isinstance(data, Tuple): @@ -513,7 +455,13 @@ def main(): else: print(key) - mlist.Unlock() + except Exception as e: + logging.error(f"Error occurred: {str(e)}", exc_info=True) + raise + finally: + logging.debug("Unlocking list") + mlist.Unlock() + logging.debug("Finished config_list") if __name__ == '__main__': From 3048ea6eedc1118405649d91921da5c20b1a54f5 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 25 Apr 2025 21:34:33 -0400 Subject: [PATCH 063/748] update --- Mailman/Gui/NonDigest.py | 3 + Mailman/LockFile.py | 290 +++++++++++++++++++++++++++------------ Mailman/Utils.py | 44 ++++-- 3 files changed, 234 insertions(+), 103 deletions(-) diff --git a/Mailman/Gui/NonDigest.py b/Mailman/Gui/NonDigest.py index 321997f4..54046ddf 100644 --- a/Mailman/Gui/NonDigest.py +++ b/Mailman/Gui/NonDigest.py @@ -125,6 +125,9 @@ def GetConfigInfo(self, mlist, category, subcat=None): else: extra = '' + # Ensure headfoot is not None + headfoot = headfoot or '' + info.extend([('msg_header', mm_cfg.Text, (10, WIDTH), 0, _('Header added to mail sent to regular list members'), _('''Text prepended to the top of every immediately-delivery diff --git a/Mailman/LockFile.py b/Mailman/LockFile.py index b6690f08..eb3aaa20 100644 --- a/Mailman/LockFile.py +++ b/Mailman/LockFile.py @@ -395,61 +395,215 @@ def _transfer_to(self, pid): self.__writelog('transferred the lock') def _take_possession(self): - """Try to take possession of the lock with detailed tracing.""" - self.__writelog('attempting to take possession of lock', important=True) - self.__writelog(f'lock file: {self.__lockfile}', important=True) - self.__writelog(f'temp file: {self.__tmpfname}', important=True) + """Try to take possession of the lock file. + + Returns 0 if we successfully took possession of the lock file, -1 if we + did not, and -2 if something very bad happened. + """ + self._logdebug('attempting to take possession of lock') + + # First, clean up any stale temp files for all processes + self.clean_stale_locks() + + # Create a temp file with our PID and hostname + lockfile_dir = os.path.dirname(self.__lockfile) + hostname = socket.gethostname() + suffix = '.%s.%d' % (hostname, os.getpid()) + tempfile = self.__lockfile + suffix try: - # Create our temp file - self.__writelog('creating temp file') - self.__write() - - # First check if lock file exists + # Write our PID and hostname to help with debugging + with open(tempfile, 'w') as fp: + fp.write('%d %s\n' % (os.getpid(), hostname)) + except OSError as e: + self._logdebug('could not create tempfile %s: %s', tempfile, e) + return -2 + + # Now try to link the tempfile to the lock file + try: + os.link(tempfile, self.__lockfile) + # Link succeeded - we have the lock + self._logdebug('successfully linked tempfile to lock file') + os.unlink(tempfile) + return 0 + except OSError as e: + # Link failed - see if lock exists and check if it's stale + self._logdebug('link to lock file failed: %s', e) try: - st = self.__nfs_safe_stat(self.__lockfile) - if st[ST_NLINK] == 0: - self.__writelog('lock file exists but has no links') + if not os.path.exists(self.__lockfile): + # Lock disappeared - try again + self._logdebug('lock file disappeared, retrying') + os.unlink(tempfile) + return -1 + + # Check if the lock file is stale + try: + with open(self.__lockfile) as fp: + content = fp.read().strip().split() + if len(content) >= 2: + pid = int(content[0]) + lock_hostname = content[1] + + # If the lock is from another host, we need to be more conservative + if lock_hostname != hostname: + self._logdebug('lock owned by different host: %s', lock_hostname) + os.unlink(tempfile) + return -1 + + # Check if process exists and is a Mailman process + if not self._is_pid_valid(pid): + self._logdebug('found stale lock (pid %d)', pid) + try: + os.unlink(self.__lockfile) + os.unlink(tempfile) + return -1 + except OSError: + # Someone else might have cleaned up + os.unlink(tempfile) + return -1 + else: + # Process exists - check if it's a Mailman process + try: + with open(f'/proc/{pid}/cmdline') as f: + cmdline = f.read() + if 'mailman' not in cmdline.lower(): + self._logdebug('breaking lock owned by non-Mailman process') + os.unlink(self.__lockfile) + os.unlink(tempfile) + return -1 + except (IOError, OSError): + # Can't read process info - be conservative + pass + except (ValueError, OSError) as e: + self._logdebug('error reading lock: %s', e) + # Lock file exists but is invalid - try to break it try: os.unlink(self.__lockfile) - except OSError as e: - if e.errno != errno.ENOENT: - raise + os.unlink(tempfile) + return -1 + except OSError: + pass except OSError as e: - if e.errno != errno.ENOENT: - raise - - # Try to create a hard link from our temp file to the lock file - self.__writelog('attempting to create hard link') + self._logdebug('error checking lock: %s', e) + try: + os.unlink(tempfile) + except OSError: + pass + return -2 + + # Lock exists and is valid - clean up and return try: - os.link(self.__tmpfname, self.__lockfile) - self.__writelog('hard link created successfully') - except OSError as e: - if e.errno == errno.EEXIST: - self.__writelog('hard link already exists') - return False - elif e.errno == errno.ENOENT: - self.__writelog('lock file does not exist') - return False - else: - self.__writelog(f'unexpected error creating hard link: {str(e)}', important=True) - raise - - # Double check our link count after a small delay to handle NFS - time.sleep(0.1) # Small delay for NFS - if self.__linkcount() == 2: - self.__writelog('lock acquired successfully') - return True + os.unlink(tempfile) + except OSError: + pass + return -1 + + def _is_pid_valid(self, pid): + """Check if a PID is still valid (process exists). + + Returns True if the process exists, False otherwise. + """ + try: + # First check if process exists + os.kill(pid, 0) - self.__writelog('lock acquisition failed') + # On Linux, check if it's a zombie + try: + with open(f'/proc/{pid}/status') as f: + status = f.read() + if 'State:' in status and 'Z (zombie)' in status: + self._logdebug('found zombie process (pid %d)', pid) + return False + except (IOError, OSError): + pass + + return True + except OSError: return False - - except Exception as e: - self.__writelog(f'error in _take_possession: {str(e)}', important=True) - raise - def _disown(self): - self.__owned = False + def _break(self): + """Break the lock. + + Returns 0 if we successfully broke the lock, -1 if we didn't, and -2 if + something very bad happened. + """ + self._logdebug('breaking the lock') + try: + if not os.path.exists(self.__lockfile): + self._logdebug('nothing to break -- lock file does not exist') + return -1 + # Read the lock file to get the old PID + try: + with open(self.__lockfile) as fp: + pid = int(fp.read().strip()) + if not self._is_pid_valid(pid): + self._logdebug('breaking stale lock owned by pid %d', pid) + os.unlink(self.__lockfile) + return 0 + self._logdebug('lock is valid (pid %d)', pid) + return -1 + except (ValueError, OSError) as e: + self._logdebug('error reading lock: %s', e) + try: + os.unlink(self.__lockfile) + return 0 + except OSError: + return -2 + except OSError as e: + self._logdebug('error breaking lock: %s', e) + return -2 + + def clean_stale_locks(self): + """Clean up any stale lock files for this lock. + + This is a safe method that can be called to clean up stale lock files + without attempting to acquire the lock. + """ + self._logdebug('cleaning stale locks') + try: + # Check for the main lock file + if os.path.exists(self.__lockfile): + try: + with open(self.__lockfile) as fp: + content = fp.read().strip().split() + if len(content) >= 2: + pid = int(content[0]) + lock_hostname = content[1] + + # Only clean locks from our host + if lock_hostname == socket.gethostname(): + if not self._is_pid_valid(pid): + self._logdebug('removing stale lock (pid %d)', pid) + try: + os.unlink(self.__lockfile) + except OSError: + pass + except (ValueError, OSError) as e: + self._logdebug('error checking lock, removing: %s', e) + try: + os.unlink(self.__lockfile) + except OSError: + pass + + # Clean up any temp files + lockfile_dir = os.path.dirname(self.__lockfile) + base = os.path.basename(self.__lockfile) + try: + for filename in os.listdir(lockfile_dir): + if filename.startswith(base + '.'): + filepath = os.path.join(lockfile_dir, filename) + try: + # Check if temp file is old (> 1 hour) + if time.time() - os.path.getmtime(filepath) > 3600: + os.unlink(filepath) + self._logdebug('removed old temp file: %s', filepath) + except OSError as e: + self._logdebug('error removing temp file %s: %s', + filepath, e) + except OSError as e: + self._logdebug('error listing directory: %s', e) + except OSError as e: + self._logdebug('error cleaning locks: %s', e) # # Private interface @@ -521,7 +675,7 @@ def __touch(self, filename=None): def __releasetime(self): """Get release time with NFS safety.""" try: - return self.__nfs_safe_stat(self.__lockfile)[ST_MTIME] + return os.stat(self.__lockfile)[ST_MTIME] except OSError as e: if e.errno != errno.ENOENT: raise @@ -540,50 +694,6 @@ def __linkcount(self): self.__writelog(f'error getting link count: {str(e)}', important=True) raise - def __break(self): - """Break a stale lock with improved error handling.""" - try: - # Get the current owner's temp file - owner = self.__read() - if not owner: - self.__writelog('no owner found for lock file', important=True) - # If no owner is found, try to remove the lock file anyway - try: - os.unlink(self.__lockfile) - self.__writelog('removed orphaned lock file') - except OSError as e: - if e.errno != errno.ENOENT: - self.__writelog(f'error removing orphaned lock file: {str(e)}', important=True) - raise - return - - # Try to remove the lock file first - try: - os.unlink(self.__lockfile) - self.__writelog('removed lock file') - except OSError as e: - if e.errno != errno.ENOENT: - self.__writelog(f'error removing lock file: {str(e)}', important=True) - raise - - # Then try to remove the owner's temp file - try: - os.unlink(owner) - self.__writelog('removed owner temp file') - except OSError as e: - if e.errno != errno.ENOENT: - self.__writelog(f'error removing owner temp file: {str(e)}', important=True) - # Don't raise here - we've already removed the lock file - - self.__writelog('successfully broke lock', important=True) - - # Small delay after breaking lock to let NFS catch up - time.sleep(0.1) - - except Exception as e: - self.__writelog(f'error breaking lock: {str(e)}', important=True) - raise - def __sleep(self): """Sleep for a short interval, handling keyboard interrupts gracefully.""" try: diff --git a/Mailman/Utils.py b/Mailman/Utils.py index 87d2f4dc..ba21a2e7 100644 --- a/Mailman/Utils.py +++ b/Mailman/Utils.py @@ -610,22 +610,40 @@ def findtext(templatefile, dict=None, raw=0, lang=None, mlist=None): def maketext(templatefile, dict=None, raw=0, lang=None, mlist=None): - """Return the contents of the template file with substitutions made. + """Make text from a template file. - The template file is searched for in the following order: - 1. In the list's language-specific template directory - 2. In the site's language-specific template directory - 3. In the list's default template directory - 4. In the site's default template directory + Use this function to create text from the template file. If dict is + provided, use it as the substitution mapping. If mlist is provided use it + as the source for the substitution. If both dict and mlist are provided, + dict values take precedence. lang is the language code to find the + template in. If raw is true, no substitution will be done on the text. - If the template is found, returns the contents of the file with the - substitutions made. Otherwise returns None. + Returns the text, or None if an error occurred. """ - text, path = findtext(templatefile, dict, raw, lang, mlist) - if text is None: - return None - if dict: - text = text % dict + # If no language was specified, use the list's preferred language + if lang is None and mlist is not None: + lang = mlist.preferred_language + # Find the template in the right language context + template = findtext(templatefile, raw=1, lang=lang, mlist=mlist) + if template is None: + syslog('error', 'Template file not found: %s (language: %s)', + templatefile, lang or 'default') + return '' # Return empty string instead of None + if raw: + return template + # Make the text from the template + if dict is None: + dict = SafeDict() + if mlist: + dict.update(mlist.__dict__) + # Remove leading whitespace + template = '\n'.join([line.lstrip() for line in template.splitlines()]) + try: + text = template % dict + except (ValueError, TypeError) as e: + syslog('error', 'Template interpolation error for %s: %s', + templatefile, str(e)) + return '' # Return empty string instead of None return text From 33d54032e0f9db9f37eecd447b9f18b84fdd0ac9 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 25 Apr 2025 21:43:40 -0400 Subject: [PATCH 064/748] update handling --- Mailman/Queue/Switchboard.py | 53 ++++++++++++++++++++++++------------ 1 file changed, 36 insertions(+), 17 deletions(-) diff --git a/Mailman/Queue/Switchboard.py b/Mailman/Queue/Switchboard.py index 7203f623..aec4ac4a 100644 --- a/Mailman/Queue/Switchboard.py +++ b/Mailman/Queue/Switchboard.py @@ -165,25 +165,44 @@ def enqueue(self, _msg, _metadata={}, **_kws): return filebase def dequeue(self, filebase): - # Read the message data + metadata - data = self._get_envelope_data(filebase) - if data is None: - return None, None - msgdata = {} - # The first line is the message metadata - metadata = data.readline() - if not metadata: + # Calculate the filename from the given filebase. + filename = os.path.join(self.__whichq, filebase + '.pck') + backfile = os.path.join(self.__whichq, filebase + '.bak') + + try: + # Move the file to the backup file name for processing. If this + # process crashes uncleanly the .bak file will be used to re-instate + # the .pck file in order to try again. + os.rename(filename, backfile) + + # Read the message object and metadata. + with open(backfile, 'rb') as fp: + msg = pickle.load(fp, fix_imports=True, encoding='latin1') + try: + data = pickle.load(fp, fix_imports=True, encoding='latin1') + # Convert any bytes in the loaded data to strings + if isinstance(data, dict): + for key, value in list(data.items()): + if isinstance(key, bytes): + del data[key] + key = key.decode('utf-8', 'replace') + if isinstance(value, bytes): + value = value.decode('utf-8', 'replace') + data[key] = value + except EOFError: + data = {} + + if data.get('_parsemsg'): + msg = email.message_from_string(msg, EmailMessage) + + return msg, data + + except EnvironmentError as e: + if e.errno != errno.ENOENT: + raise return None, None - metadata = metadata.strip() - if metadata: - msgdata = self._parse_metadata(metadata) - # The rest is the message - msg = data.read() - if not msg: + except (pickle.UnpicklingError, EOFError): return None, None - # Parse the message into an email object - msg = email.message_from_string(msg, EmailMessage) - return msg, msgdata def finish(self, filebase, preserve=False): bakfile = os.path.join(self.__whichq, filebase + '.bak') From a212cd7d3b8291ab0fa1f3088365310b0b0edadc Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 25 Apr 2025 21:47:24 -0400 Subject: [PATCH 065/748] update pidfile --- bin/mailmanctl | 160 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 154 insertions(+), 6 deletions(-) diff --git a/bin/mailmanctl b/bin/mailmanctl index 7a4f0b31..62713aae 100755 --- a/bin/mailmanctl +++ b/bin/mailmanctl @@ -354,15 +354,163 @@ def main(): # Handle the command if args.command == 'start': try: + # First, acquire the master mailmanctl lock + lock = acquire_lock(args.stale_lock_cleanup) + if not lock: + return + + # Daemon process startup according to Stevens, Advanced Programming in + # the UNIX Environment, Chapter 13. + pid = os.fork() + if pid: + # parent + if not args.quiet: + print(C_("Starting Mailman's master qrunner.")) + # Give up the lock "ownership". This just means the foreground + # process won't close/unlock the lock when it finalizes this lock + # instance. We'll let the mater watcher subproc own the lock. + lock._transfer_to(pid) + return + + # child + lock._take_possession() + # First, save our pid in a file for "mailmanctl stop" rendezvous. We + # want the perms on the .pid file to be rw-rw---- + omask = os.umask(6) + try: + fp = open(mm_cfg.PIDFILE, 'w') + print(os.getpid(), file=fp) + fp.close() + finally: + os.umask(omask) + + # Create a new session and become the session leader + os.setsid() + + # Be sure to close any open std{in,out,err} + devnull = os.open('/dev/null', 0) + os.dup2(devnull, 0) + os.dup2(devnull, 1) + os.dup2(devnull, 2) + + # Instead of cd'ing to root, cd to the Mailman installation home + os.chdir(mm_cfg.PREFIX) + # Set our file mode creation umask + os.umask(0o07) + + # Now start all the qrunners kids = start_all_runners() - # Write PID file only if at least one runner started successfully - if kids: - with open(mm_cfg.PIDFILE, 'w') as fp: - fp.write(str(os.getpid())) - syslog('info', 'Started %d runners successfully', len(kids)) - else: + if not kids: syslog('error', 'No runners started successfully') sys.exit(1) + + # Set up a SIGALRM handler to refresh the lock once per day + def sigalrm_handler(signum, frame, lock=lock): + lock.refresh() + signal.alarm(mm_cfg.days(1)) + signal.signal(signal.SIGALRM, sigalrm_handler) + signal.alarm(mm_cfg.days(1)) + + # Set up a SIGHUP handler + def sighup_handler(signum, frame, kids=kids): + syslog.close() + for pid in list(kids.keys()): + os.kill(pid, signal.SIGHUP) + syslog('qrunner', + 'Master watcher caught SIGHUP. Re-opening log files.') + signal.signal(signal.SIGHUP, sighup_handler) + + # Set up a SIGTERM handler + def sigterm_handler(signum, frame, kids=kids): + for pid in list(kids.keys()): + try: + os.kill(pid, signal.SIGTERM) + except OSError as e: + if e.errno != errno.ESRCH: raise + syslog('qrunner', 'Master watcher caught SIGTERM. Exiting.') + signal.signal(signal.SIGTERM, sigterm_handler) + + # Set up a SIGINT handler + def sigint_handler(signum, frame, kids=kids): + for pid in list(kids.keys()): + os.kill(pid, signal.SIGINT) + syslog('qrunner', 'Master watcher caught SIGINT. Restarting.') + signal.signal(signal.SIGINT, sigint_handler) + + # Now we're ready to simply do our wait/restart loop + try: + while 1: + try: + pid, status = os.wait() + except OSError as e: + # No children? We're done + if e.errno == errno.ECHILD: + break + # If the system call got interrupted, just restart it. + elif e.errno != errno.EINTR: + raise + continue + + killsig = exitstatus = None + if os.WIFSIGNALED(status): + killsig = os.WTERMSIG(status) + if os.WIFEXITED(status): + exitstatus = os.WEXITSTATUS(status) + + restarting = '' + if not args.no_restart: + if (exitstatus == None and killsig != signal.SIGTERM) or \ + (killsig == None and exitstatus != signal.SIGTERM): + restarting = '[restarting]' + + qrname, slice, count, restarts = kids[pid] + del kids[pid] + syslog('qrunner', """\ +Master qrunner detected subprocess exit +(pid: %d, sig: %s, sts: %s, class: %s, slice: %d/%d) %s""", + pid, killsig, exitstatus, qrname, + slice+1, count, restarting) + + # See if we've reached the maximum number of allowable restarts + if exitstatus != signal.SIGINT: + restarts += 1 + if restarts > MAX_RESTARTS: + syslog('qrunner', """\ +Qrunner %s reached maximum restart limit of %d, not restarting.""", + qrname, MAX_RESTARTS) + restarting = '' + + # Now perhaps restart the process + if restarting: + newpid = start_runner(qrname, slice, count) + kids[newpid] = (qrname, slice, count, restarts) + + finally: + # Should we leave the main loop for any reason, we want to be sure + # all of our children are exited cleanly + for pid in list(kids.keys()): + try: + os.kill(pid, signal.SIGTERM) + except OSError as e: + if e.errno == errno.ESRCH: + syslog('qrunner', 'ESRCH on pid: %d', pid) + del kids[pid] + + # Wait for all the children to go away + while 1: + try: + pid, status = os.wait() + except OSError as e: + if e.errno == errno.ECHILD: + break + elif e.errno != errno.EINTR: + raise + continue + + # Finally, give up the lock + lock.unlock(unconditionally=1) + os._exit(0) + except Exception as e: syslog('error', 'Error during startup: %s', str(e)) sys.exit(1) From c6c396950b04c92131107866e608aea5149b6100 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 25 Apr 2025 22:01:03 -0400 Subject: [PATCH 066/748] update --- Mailman/Deliverer.py | 4 ++-- Mailman/Digester.py | 2 ++ Mailman/Gui/Digest.py | 10 ++++---- Mailman/Gui/NonDigest.py | 8 +++---- Mailman/LockFile.py | 50 ++++++++++++++++++++-------------------- Mailman/MailList.py | 32 ++++++++++++++----------- cron/checkdbs | 4 ++-- 7 files changed, 59 insertions(+), 51 deletions(-) diff --git a/Mailman/Deliverer.py b/Mailman/Deliverer.py index 61f63e84..d560a591 100644 --- a/Mailman/Deliverer.py +++ b/Mailman/Deliverer.py @@ -52,7 +52,7 @@ def SendSubscribeAck(self, name, password, digest, text=''): else: umbrella = '' # get the text from the template - text += Utils.maketext( + text += str(Utils.maketext( 'subscribeack.txt', {'real_name' : self.real_name, 'host_name' : self.host_name, @@ -63,7 +63,7 @@ def SendSubscribeAck(self, name, password, digest, text=''): 'optionsurl' : self.GetOptionsURL(name, absolute=True), 'password' : password, 'user' : self.getMemberCPAddress(name), - }, lang=pluser, mlist=self) + }, lang=pluser, mlist=self)) if digest: digmode = _(' (Digest mode)') else: diff --git a/Mailman/Digester.py b/Mailman/Digester.py index 4ca58d30..bc316cf0 100644 --- a/Mailman/Digester.py +++ b/Mailman/Digester.py @@ -42,6 +42,8 @@ def InitVars(self): self.digest_header = mm_cfg.DEFAULT_DIGEST_HEADER self.digest_footer = mm_cfg.DEFAULT_DIGEST_FOOTER self.digest_volume_frequency = mm_cfg.DEFAULT_DIGEST_VOLUME_FREQUENCY + self._new_volume = 0 # Initialize _new_volume to False + self.volume = 1 # Initialize volume to 1 # Non-configurable. self.one_last_digest = {} self.digest_members = {} diff --git a/Mailman/Gui/Digest.py b/Mailman/Gui/Digest.py index 052de111..70f7d267 100644 --- a/Mailman/Gui/Digest.py +++ b/Mailman/Gui/Digest.py @@ -65,14 +65,14 @@ def GetConfigInfo(self, mlist, category, subcat=None): ('digest_header', mm_cfg.Text, (4, WIDTH), 0, _('Header added to every digest'), - _("Text attached (as an initial message, before the table" - " of contents) to the top of digests. ") - + Utils.maketext('headfoot.html', raw=1, mlist=mlist)), + str(_("Text attached (as an initial message, before the table" + " of contents) to the top of digests. ")) + + str(Utils.maketext('headfoot.html', raw=1, mlist=mlist))), ('digest_footer', mm_cfg.Text, (4, WIDTH), 0, _('Footer added to every digest'), - _("Text attached (as a final message) to the bottom of digests. ") - + Utils.maketext('headfoot.html', raw=1, mlist=mlist)), + str(_("Text attached (as a final message) to the bottom of digests. ")) + + str(Utils.maketext('headfoot.html', raw=1, mlist=mlist))), ('digest_volume_frequency', mm_cfg.Radio, (_('Yearly'), _('Monthly'), _('Quarterly'), diff --git a/Mailman/Gui/NonDigest.py b/Mailman/Gui/NonDigest.py index 54046ddf..668d3ed1 100644 --- a/Mailman/Gui/NonDigest.py +++ b/Mailman/Gui/NonDigest.py @@ -130,13 +130,13 @@ def GetConfigInfo(self, mlist, category, subcat=None): info.extend([('msg_header', mm_cfg.Text, (10, WIDTH), 0, _('Header added to mail sent to regular list members'), - _('''Text prepended to the top of every immediately-delivery - message. ''') + headfoot + extra), + str(_('''Text prepended to the top of every immediately-delivery + message. ''')) + str(headfoot) + str(extra)), ('msg_footer', mm_cfg.Text, (10, WIDTH), 0, _('Footer added to mail sent to regular list members'), - _('''Text appended to the bottom of every immediately-delivery - message. ''') + headfoot + extra), + str(_('''Text appended to the bottom of every immediately-delivery + message. ''')) + str(headfoot) + str(extra)), ]) info.extend([ diff --git a/Mailman/LockFile.py b/Mailman/LockFile.py index eb3aaa20..8dfaa55e 100644 --- a/Mailman/LockFile.py +++ b/Mailman/LockFile.py @@ -400,7 +400,7 @@ def _take_possession(self): Returns 0 if we successfully took possession of the lock file, -1 if we did not, and -2 if something very bad happened. """ - self._logdebug('attempting to take possession of lock') + self.__writelog('attempting to take possession of lock') # First, clean up any stale temp files for all processes self.clean_stale_locks() @@ -416,23 +416,23 @@ def _take_possession(self): with open(tempfile, 'w') as fp: fp.write('%d %s\n' % (os.getpid(), hostname)) except OSError as e: - self._logdebug('could not create tempfile %s: %s', tempfile, e) + self.__writelog('could not create tempfile %s: %s' % (tempfile, e)) return -2 # Now try to link the tempfile to the lock file try: os.link(tempfile, self.__lockfile) # Link succeeded - we have the lock - self._logdebug('successfully linked tempfile to lock file') + self.__writelog('successfully linked tempfile to lock file') os.unlink(tempfile) return 0 except OSError as e: # Link failed - see if lock exists and check if it's stale - self._logdebug('link to lock file failed: %s', e) + self.__writelog('link to lock file failed: %s' % e) try: if not os.path.exists(self.__lockfile): # Lock disappeared - try again - self._logdebug('lock file disappeared, retrying') + self.__writelog('lock file disappeared, retrying') os.unlink(tempfile) return -1 @@ -446,13 +446,13 @@ def _take_possession(self): # If the lock is from another host, we need to be more conservative if lock_hostname != hostname: - self._logdebug('lock owned by different host: %s', lock_hostname) + self.__writelog('lock owned by different host: %s' % lock_hostname) os.unlink(tempfile) return -1 # Check if process exists and is a Mailman process if not self._is_pid_valid(pid): - self._logdebug('found stale lock (pid %d)', pid) + self.__writelog('found stale lock (pid %d)' % pid) try: os.unlink(self.__lockfile) os.unlink(tempfile) @@ -467,7 +467,7 @@ def _take_possession(self): with open(f'/proc/{pid}/cmdline') as f: cmdline = f.read() if 'mailman' not in cmdline.lower(): - self._logdebug('breaking lock owned by non-Mailman process') + self.__writelog('breaking lock owned by non-Mailman process') os.unlink(self.__lockfile) os.unlink(tempfile) return -1 @@ -475,7 +475,7 @@ def _take_possession(self): # Can't read process info - be conservative pass except (ValueError, OSError) as e: - self._logdebug('error reading lock: %s', e) + self.__writelog('error reading lock: %s' % e) # Lock file exists but is invalid - try to break it try: os.unlink(self.__lockfile) @@ -484,7 +484,7 @@ def _take_possession(self): except OSError: pass except OSError as e: - self._logdebug('error checking lock: %s', e) + self.__writelog('error checking lock: %s' % e) try: os.unlink(tempfile) except OSError: @@ -512,7 +512,7 @@ def _is_pid_valid(self, pid): with open(f'/proc/{pid}/status') as f: status = f.read() if 'State:' in status and 'Z (zombie)' in status: - self._logdebug('found zombie process (pid %d)', pid) + self.__writelog('found zombie process (pid %d)' % pid) return False except (IOError, OSError): pass @@ -527,30 +527,30 @@ def _break(self): Returns 0 if we successfully broke the lock, -1 if we didn't, and -2 if something very bad happened. """ - self._logdebug('breaking the lock') + self.__writelog('breaking the lock') try: if not os.path.exists(self.__lockfile): - self._logdebug('nothing to break -- lock file does not exist') + self.__writelog('nothing to break -- lock file does not exist') return -1 # Read the lock file to get the old PID try: with open(self.__lockfile) as fp: pid = int(fp.read().strip()) if not self._is_pid_valid(pid): - self._logdebug('breaking stale lock owned by pid %d', pid) + self.__writelog('breaking stale lock owned by pid %d' % pid) os.unlink(self.__lockfile) return 0 - self._logdebug('lock is valid (pid %d)', pid) + self.__writelog('lock is valid (pid %d)' % pid) return -1 except (ValueError, OSError) as e: - self._logdebug('error reading lock: %s', e) + self.__writelog('error reading lock: %s' % e) try: os.unlink(self.__lockfile) return 0 except OSError: return -2 except OSError as e: - self._logdebug('error breaking lock: %s', e) + self.__writelog('error breaking lock: %s' % e) return -2 def clean_stale_locks(self): @@ -559,7 +559,7 @@ def clean_stale_locks(self): This is a safe method that can be called to clean up stale lock files without attempting to acquire the lock. """ - self._logdebug('cleaning stale locks') + self.__writelog('cleaning stale locks') try: # Check for the main lock file if os.path.exists(self.__lockfile): @@ -573,13 +573,13 @@ def clean_stale_locks(self): # Only clean locks from our host if lock_hostname == socket.gethostname(): if not self._is_pid_valid(pid): - self._logdebug('removing stale lock (pid %d)', pid) + self.__writelog('removing stale lock (pid %d)' % pid) try: os.unlink(self.__lockfile) except OSError: pass except (ValueError, OSError) as e: - self._logdebug('error checking lock, removing: %s', e) + self.__writelog('error checking lock, removing: %s' % e) try: os.unlink(self.__lockfile) except OSError: @@ -596,14 +596,14 @@ def clean_stale_locks(self): # Check if temp file is old (> 1 hour) if time.time() - os.path.getmtime(filepath) > 3600: os.unlink(filepath) - self._logdebug('removed old temp file: %s', filepath) + self.__writelog('removed old temp file: %s' % filepath) except OSError as e: - self._logdebug('error removing temp file %s: %s', - filepath, e) + self.__writelog('error removing temp file %s: %s' % + (filepath, e)) except OSError as e: - self._logdebug('error listing directory: %s', e) + self.__writelog('error listing directory: %s' % e) except OSError as e: - self._logdebug('error cleaning locks: %s', e) + self.__writelog('error cleaning locks: %s' % e) # # Private interface diff --git a/Mailman/MailList.py b/Mailman/MailList.py index 70eaa669..64bf49a8 100644 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -126,19 +126,25 @@ def __init__(self, name=None, lock=1): self.Load() def __getattr__(self, name): - # Because we're using delegation, we want to be sure that attribute - # access to a delegated member function gets passed to the - # sub-objects. This of course imposes a specific name resolution - # order. + # First check if the attribute exists on the instance itself try: - return getattr(self._memberadaptor, name) + return object.__getattribute__(self, name) except AttributeError: - for guicomponent in self._gui: - try: - return getattr(guicomponent, name) - except AttributeError: - pass - else: + # If not found on instance, try the delegation chain + try: + return getattr(self._memberadaptor, name) + except AttributeError: + for guicomponent in self._gui: + try: + return getattr(guicomponent, name) + except AttributeError: + pass + # Check mixin classes for the attribute + for baseclass in self.__class__.__bases__: + try: + return getattr(baseclass, name) + except AttributeError: + pass raise AttributeError(name) def __repr__(self): @@ -959,7 +965,7 @@ def InviteNewMember(self, userdesc, text=''): confirmurl = '%s/%s' % (self.GetScriptURL('confirm', absolute=1), cookie) listname = self.real_name - text += Utils.maketext( + text += str(Utils.maketext( 'invite.txt', {'email' : invitee, 'listname' : listname, @@ -968,7 +974,7 @@ def InviteNewMember(self, userdesc, text=''): 'requestaddr': requestaddr, 'cookie' : cookie, 'listowner' : self.GetOwnerEmail(), - }, mlist=self) + }, mlist=self)) sender = self.GetRequestEmail(cookie) msg = Message.UserNotification( invitee, sender, diff --git a/cron/checkdbs b/cron/checkdbs index 9edc0460..4b1a5681 100755 --- a/cron/checkdbs +++ b/cron/checkdbs @@ -91,13 +91,13 @@ def main(): else: text = '' if count: - text += Utils.maketext( + text += str(Utils.maketext( 'checkdbs.txt', {'count' : count, 'host_name': mlist.host_name, 'adminDB' : mlist.GetScriptURL('admindb', absolute=1), 'real_name': realname, - }, mlist=mlist) + }, mlist=mlist)) text += '\n' + pending_requests(mlist) subject = _( '%(count)d %(realname)s moderator request(s) waiting') From cd1be30e636b32ec0083417522bf8be0888a74d5 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 25 Apr 2025 22:04:43 -0400 Subject: [PATCH 067/748] update --- Mailman/HTMLFormatter.py | 3 +++ Mailman/LockFile.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Mailman/HTMLFormatter.py b/Mailman/HTMLFormatter.py index 1a58e8d3..86ebe93d 100644 --- a/Mailman/HTMLFormatter.py +++ b/Mailman/HTMLFormatter.py @@ -374,6 +374,9 @@ def ParseTags(self, template, replacements, lang=None): else: charset = Utils.GetCharSet(lang) text = Utils.maketext(template, raw=1, lang=lang, mlist=self) + # Ensure we have a string to split + if isinstance(text, tuple): + text = text[0] # Take the first element of the tuple parts = re.split('(]*>)', text) i = 1 while i < len(parts): diff --git a/Mailman/LockFile.py b/Mailman/LockFile.py index 8dfaa55e..bacc7369 100644 --- a/Mailman/LockFile.py +++ b/Mailman/LockFile.py @@ -270,7 +270,7 @@ def lock(self, timeout=0): if releasetime < current_time: # Lock is stale, try to break it self.__writelog(f'stale lock detected (releasetime={releasetime}, current={current_time})', important=True) - self.__break() + self._break() self.__writelog('broke stale lock', important=True) # Try to acquire again immediately after breaking if self._take_possession(): From e223c40e621dab5a2852e580b605debd282442c2 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 25 Apr 2025 22:07:22 -0400 Subject: [PATCH 068/748] update lock --- Mailman/MailList.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Mailman/MailList.py b/Mailman/MailList.py index 64bf49a8..bdea1e7c 100644 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -631,10 +631,11 @@ def __save(self, dict): self.__timestamp = os.path.getmtime(fname) def Save(self): + # First ensure we have the lock + if not self.Locked(): + self.Lock() # Refresh the lock, just to let other processes know we're still - # interested in it. This will raise a NotLockedError if we don't have - # the lock (which is a serious problem!). TBD: do we need to be more - # defensive? + # interested in it. self.__lock.refresh() # copy all public attributes to serializable dictionary dict = {} From e32fcdfe0d7aa8c1f4a5fa4de386a689fc6c80c0 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 16:59:46 -0400 Subject: [PATCH 069/748] update --- Mailman/LockFile.py | 2 + Mailman/MailList.py | 142 ++++++++++++++++++++++++-------------------- 2 files changed, 79 insertions(+), 65 deletions(-) diff --git a/Mailman/LockFile.py b/Mailman/LockFile.py index bacc7369..5e717ff1 100644 --- a/Mailman/LockFile.py +++ b/Mailman/LockFile.py @@ -197,6 +197,8 @@ def __init__(self, lockfile, # NFS-specific settings self.__nfs_retry_delay = 0.1 self.__nfs_max_retries = 5 + # Backward compatibility for old code expecting _LockFile__break + self._LockFile__break = self._break def __repr__(self): return '' % ( diff --git a/Mailman/MailList.py b/Mailman/MailList.py index bdea1e7c..ba18c00d 100644 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -126,26 +126,34 @@ def __init__(self, name=None, lock=1): self.Load() def __getattr__(self, name): - # First check if the attribute exists on the instance itself + # First check if the attribute exists in the instance's dictionary + if name in self.__dict__: + return self.__dict__[name] + # Then try the memberadaptor try: - return object.__getattribute__(self, name) + return getattr(self._memberadaptor, name) except AttributeError: - # If not found on instance, try the delegation chain - try: - return getattr(self._memberadaptor, name) - except AttributeError: - for guicomponent in self._gui: - try: - return getattr(guicomponent, name) - except AttributeError: - pass - # Check mixin classes for the attribute - for baseclass in self.__class__.__bases__: - try: - return getattr(baseclass, name) - except AttributeError: - pass - raise AttributeError(name) + # Try GUI components + for guicomponent in self._gui: + try: + return getattr(guicomponent, name) + except AttributeError: + pass + # Finally check mixin classes + for baseclass in self.__class__.__bases__: + try: + # Get the attribute from the base class + attr = getattr(baseclass, name) + # If it's a method, bind it to this instance + if callable(attr): + return attr.__get__(self, self.__class__) + # For non-method attributes, check if we have an instance value + if hasattr(self, '_' + name): + return getattr(self, '_' + name) + return attr + except AttributeError: + pass + raise AttributeError(name) def __repr__(self): if self.Locked(): @@ -319,6 +327,11 @@ def InitTempVars(self, name): def InitVars(self, name=None, admin='', crypted_password='', urlhost=None): """Assign default values - some will be overriden by stored state.""" + # Initialize mixin classes + for baseclass in self.__class__.__bases__: + if hasattr(baseclass, 'InitVars'): + baseclass.InitVars(self) + # Non-configurable list info if name: # Ensure name is a string @@ -330,7 +343,6 @@ def InitVars(self, name=None, admin='', crypted_password='', self.created_at = time.time() # Must save this state, even though it isn't configurable - self.volume = 1 self.members = {} # self.digest_members is initted in mm_digest self.data_version = mm_cfg.DATA_FILE_VERSION self.last_post_time = 0 @@ -450,10 +462,6 @@ def InitVars(self, name=None, admin='', crypted_password='', # 2-tuple of the date of the last autoresponse and the number of # autoresponses sent on that date. self.hold_and_cmd_autoresponses = {} - # Only one level of mixin inheritance allowed - for baseclass in self.__class__.__bases__: - if hasattr(baseclass, 'InitVars'): - baseclass.InitVars(self) # These need to come near the bottom because they're dependent on # other settings. @@ -634,50 +642,54 @@ def Save(self): # First ensure we have the lock if not self.Locked(): self.Lock() - # Refresh the lock, just to let other processes know we're still - # interested in it. - self.__lock.refresh() - # copy all public attributes to serializable dictionary - dict = {} - for key, value in list(self.__dict__.items()): - if key[0] == '_' or type(value) is MethodType: - continue - # Ensure string values are properly encoded - if isinstance(value, str): - dict[key] = value - elif isinstance(value, bytes): - # Convert bytes to string if possible - try: - dict[key] = value.decode('utf-8', 'replace') - except UnicodeDecodeError: - dict[key] = value.decode('latin1', 'replace') - elif isinstance(value, list): - # Handle lists that might contain bytes - dict[key] = [ - v.decode('utf-8', 'replace') if isinstance(v, bytes) else v - for v in value - ] - elif type(value) is dict: - # Handle dicts that might contain bytes - new_dict = {} - for k, v in value.items(): - if isinstance(k, bytes): - k = k.decode('utf-8', 'replace') - if isinstance(v, bytes): - v = v.decode('utf-8', 'replace') - new_dict[k] = v - dict[key] = new_dict - else: - dict[key] = value - # Make config.pck unreadable by `other', as it contains all the - # list members' passwords (in clear text). - omask = os.umask(0o007) try: - self.__save(dict) + # Only refresh if we have the lock + if self.Locked(): + self.__lock.refresh() + # copy all public attributes to serializable dictionary + dict = {} + for key, value in list(self.__dict__.items()): + if key[0] == '_' or type(value) is MethodType: + continue + # Ensure string values are properly encoded + if isinstance(value, str): + dict[key] = value + elif isinstance(value, bytes): + # Convert bytes to string if possible + try: + dict[key] = value.decode('utf-8', 'replace') + except UnicodeDecodeError: + dict[key] = value.decode('latin1', 'replace') + elif isinstance(value, list): + # Handle lists that might contain bytes + dict[key] = [ + v.decode('utf-8', 'replace') if isinstance(v, bytes) else v + for v in value + ] + elif type(value) is dict: + # Handle dicts that might contain bytes + new_dict = {} + for k, v in value.items(): + if isinstance(k, bytes): + k = k.decode('utf-8', 'replace') + if isinstance(v, bytes): + v = v.decode('utf-8', 'replace') + new_dict[k] = v + dict[key] = new_dict + else: + dict[key] = value + # Make config.pck unreadable by `other', as it contains all the + # list members' passwords (in clear text). + omask = os.umask(0o007) + try: + self.__save(dict) + finally: + os.umask(omask) + self.SaveRequestsDb() + self.CheckHTMLArchiveDir() finally: - os.umask(omask) - self.SaveRequestsDb() - self.CheckHTMLArchiveDir() + # Always unlock when we're done + self.Unlock() def __load(self, dbfile): """Load and validate a database file with improved error handling.""" From 55053c5851c5036b66a08346fb9f3cb665029c3d Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 17:04:44 -0400 Subject: [PATCH 070/748] update lockfile logging --- Mailman/LockFile.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Mailman/LockFile.py b/Mailman/LockFile.py index 5e717ff1..124e4076 100644 --- a/Mailman/LockFile.py +++ b/Mailman/LockFile.py @@ -620,9 +620,12 @@ def __writelog(self, msg, important=False): timestamp = time.strftime('%Y-%m-%d %H:%M:%S') pid = os.getpid() hostname = socket.gethostname() - logf.write(f'{timestamp} [{pid}@{hostname}] {self.__logprefix}: {msg}\n') - if important: + # Only print stack trace for actual errors, not for normal operations + if important and 'error' in msg.lower(): + logf.write(f'{timestamp} [{pid}@{hostname}] {self.__logprefix}: {msg}\n') traceback.print_stack(file=logf) + else: + logf.write(f'{timestamp} [{pid}@{hostname}] {self.__logprefix}: {msg}\n') except Exception: # Don't raise exceptions during logging pass From 4be9b5bf1d5105c8fb75550b5c7ae76afe6d5c34 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 17:10:55 -0400 Subject: [PATCH 071/748] update --- Mailman/Queue/NewsRunner.py | 25 ++++++++++--- bin/mailmanctl | 73 +++++++++++++++++++++++++++++++++++-- 2 files changed, 90 insertions(+), 8 deletions(-) diff --git a/Mailman/Queue/NewsRunner.py b/Mailman/Queue/NewsRunner.py index e99316e3..37cb789d 100644 --- a/Mailman/Queue/NewsRunner.py +++ b/Mailman/Queue/NewsRunner.py @@ -58,22 +58,37 @@ class NewsRunner(Runner): QDIR = mm_cfg.NEWSQUEUE_DIR def __init__(self, slice=None, numslices=1): + # Always initialize the parent class first + Runner.__init__(self, slice, numslices) + + # Check if NNTP support is available and configured + self._nntp_enabled = False if not HAVE_NNTP: - syslog('warning', 'NNTP support is not enabled. NewsRunner will not be started.') + syslog('warning', 'NNTP support is not enabled. NewsRunner will not process messages.') return if not mm_cfg.DEFAULT_NNTP_HOST: - syslog('info', 'newsrunner not starting due to DEFAULT_NNTP_HOST not being set') + syslog('info', 'NewsRunner not processing messages due to DEFAULT_NNTP_HOST not being set') return - # Initialize parent class first - Runner.__init__(self, slice, numslices) - # Override the switchboard with our specific one + + # NNTP is available and configured, set up the switchboard + self._nntp_enabled = True from Mailman.Queue.Switchboard import Switchboard self._switchboard = Switchboard(self.QDIR, slice, numslices, True) # Initialize _kids if not already done by parent if not hasattr(self, '_kids'): self._kids = {} + def _oneloop(self): + # If NNTP is not enabled, don't process any messages + if not self._nntp_enabled: + return 0 + # Otherwise, proceed with normal processing + return Runner._oneloop(self) + def _dispose(self, mlist, msg, msgdata): + # If NNTP is not enabled, requeue the message + if not self._nntp_enabled: + return True # Make sure we have the most up-to-date state mlist.Load() if not msgdata.get('prepped'): diff --git a/bin/mailmanctl b/bin/mailmanctl index 62713aae..e6358903 100755 --- a/bin/mailmanctl +++ b/bin/mailmanctl @@ -36,7 +36,7 @@ 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. -Usage: %(PROGRAM)s [options] [ start | stop | restart | reopen ] +Usage: %(PROGRAM)s [options] [ start | stop | restart | reopen | status ] Options: @@ -90,6 +90,8 @@ Commands: reopen - This will close all log files, causing them to be re-opened the next time a message is written to them + + status - Checks if all qrunners are running as expected. """ import sys @@ -129,7 +131,7 @@ LogStdErr('error', 'mailmanctl', manual_reprime=0) def parse_args(): parser = argparse.ArgumentParser(description='Primary start-up and shutdown script for Mailman\'s qrunner daemon.') - parser.add_argument('command', choices=['start', 'stop', 'restart', 'reopen'], + parser.add_argument('command', choices=['start', 'stop', 'restart', 'reopen', 'status'], help='Command to execute') parser.add_argument('-n', '--no-restart', action='store_true', help='Don\'t restart the qrunners when they exit because of an error or a SIGINT') @@ -328,6 +330,66 @@ def check_privs(): 'Run this program as root or as the %(name)s user, or use -u.')) +def check_qrunner_status(): + """Check if all qrunners are running as expected.""" + # First check if the master process is running + try: + with open(mm_cfg.PIDFILE, 'r') as fp: + pid = int(fp.read().strip()) + try: + os.kill(pid, 0) # Check if process exists + print(C_('Master qrunner process is running (pid: %(pid)d)') % {'pid': pid}) + except OSError: + print(C_('Master qrunner process is not running (stale pid file)')) + return False + except (IOError, ValueError): + print(C_('Master qrunner process is not running (no pid file)')) + return False + + # Check if the lock file exists and is valid + try: + hostname, pid, tempfile = get_lock_data() + if hostname != socket.gethostname(): + print(C_('Lock file is held by another host: %(hostname)s') % {'hostname': hostname}) + return False + try: + os.kill(pid, 0) + print(C_('Lock file is valid (pid: %(pid)d)') % {'pid': pid}) + except OSError: + print(C_('Lock file is stale (process %(pid)d not running)') % {'pid': pid}) + return False + except (IOError, ValueError): + print(C_('No lock file found')) + return False + + # Check if all expected qrunners are running + expected_runners = dict(mm_cfg.QRUNNERS) + running_runners = {} + + # Get all running qrunner processes + for line in os.popen('ps aux | grep qrunner | grep -v grep').readlines(): + parts = line.split() + if len(parts) >= 12: # Ensure we have enough parts + cmd = parts[10] # The command is typically at index 10 + if '--runner=' in cmd: + runner_name = cmd.split('--runner=')[1].split(':')[0] + running_runners[runner_name] = running_runners.get(runner_name, 0) + 1 + + # Compare expected vs running + all_running = True + for runner, count in expected_runners.items(): + actual = running_runners.get(runner, 0) + if actual != count: + print(C_('%(runner)s: expected %(count)d instances, found %(actual)d') % + {'runner': runner, 'count': count, 'actual': actual}) + all_running = False + else: + print(C_('%(runner)s: %(count)d instances running') % + {'runner': runner, 'count': count}) + + return all_running + + def main(): try: args = parse_args() @@ -352,7 +414,12 @@ def main(): sys.exit(1) # Handle the command - if args.command == 'start': + if args.command == 'status': + if check_qrunner_status(): + sys.exit(0) + else: + sys.exit(1) + elif args.command == 'start': try: # First, acquire the master mailmanctl lock lock = acquire_lock(args.stale_lock_cleanup) From f3c99065b00f05ab83d76ec9cf25741ff2a2f9d3 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 17:34:48 -0400 Subject: [PATCH 072/748] update --- Mailman/i18n.py | 12 +- Makefile.in | 67 +++++++++- bin/mailmanctl | 318 +++++++++++++++++++++++++++--------------------- 3 files changed, 249 insertions(+), 148 deletions(-) diff --git a/Mailman/i18n.py b/Mailman/i18n.py index 59de262b..d20acabd 100644 --- a/Mailman/i18n.py +++ b/Mailman/i18n.py @@ -114,16 +114,22 @@ def tolocale(s): global _ctype_charset if isinstance(s, str) or _ctype_charset is None: return s - source = _translation.charset () + source = _translation.charset() if not source: return s - return str(s, source, 'replace').encode(_ctype_charset, 'replace') + # Handle string formatting before encoding + if isinstance(s, bytes): + s = s.decode('utf-8', 'replace') + return s.encode(_ctype_charset, 'replace').decode(_ctype_charset) if mm_cfg.DISABLE_COMMAND_LOCALE_CSET: C_ = _ else: def C_(s): - return tolocale(_(s, 2)) + result = _(s, 2) + if isinstance(result, bytes): + result = result.decode('utf-8', 'replace') + return tolocale(result) diff --git a/Makefile.in b/Makefile.in index 574fe758..00c69f39 100644 --- a/Makefile.in +++ b/Makefile.in @@ -69,21 +69,67 @@ DIRSETGID= chmod g+s DATE = $(shell python -c 'import time; print time.strftime("%d-%b-%Y"),') LANGPACK = README-I18N.en templates messages -EXCLUDES = --exclude=CVS --exclude=.cvsignore --exclude=Makefile* --exclude=*.files --exclude=*.old +EXCLUDES = --exclude=CVS --exclude=.cvsignore --exclude=Makefile* --exclude=*.files --exclude=*.old --exclude=msgfmt-python2.py --exclude=pygettext.py + +# Add these variables after the existing variable definitions +PYTHON_FILES = $(shell find . -name "*.py") +PYTHON_DIRS = $(shell find . -type d -name "Mailman") +INSTALLED_SCRIPTS = $(shell find $(DESTDIR)$(prefix)/bin -type f -executable 2>/dev/null || true) +SOURCE_SCRIPTS = $(shell find build/bin -type f -executable 2>/dev/null || true) + +# Add this function to check for script mismatches +define check_scripts + @echo "Checking for script mismatches..." + @for script in $(INSTALLED_SCRIPTS); do \ + base_script=$$(basename $$script); \ + if [ ! -f build/bin/$$base_script ]; then \ + echo "WARNING: Script $$base_script exists in installation but not in source"; \ + fi; \ + done + @for script in $(SOURCE_SCRIPTS); do \ + base_script=$$(basename $$script); \ + case "$$base_script" in \ + msgfmt-python2.py|pygettext.py) \ + ;; \ + *) \ + if [ ! -f $(DESTDIR)$(prefix)/bin/$$base_script ]; then \ + echo "WARNING: Script $$base_script exists in source but not in installation"; \ + fi; \ + ;; \ + esac; \ + done +endef # Rules -all: subdirs +all: subdirs build-dir + +build-dir: $(PYTHON_FILES) + @echo "Updating build directory..." + @for d in $(SUBDIRS); \ + do \ + dir=build/$$d; \ + if test ! -d $$dir; then \ + $(srcdir)/mkinstalldirs $$dir; \ + fi; \ + for f in $$d/*; do \ + if test -f $$f; then \ + if test ! -f build/$$f -o $$f -nt build/$$f; then \ + cp -p $$f build/$$f; \ + fi; \ + fi; \ + done; \ + done subdirs: $(SUBDIRS) for d in $(SUBDIRS); \ do \ - (cd $$d; $(MAKE)); \ + (cd $$d; $(MAKE) all); \ done -install: doinstall update +install: doinstall update check-scripts -doinstall: $(SUBDIRS) +doinstall: $(SUBDIRS) $(PYTHON_FILES) @echo "Creating architecture independent directories..." @for d in $(VAR_DIRS); \ do \ @@ -124,7 +170,10 @@ doinstall: $(SUBDIRS) do \ (cd $$d; $(MAKE) DESTDIR=$(DESTDIR) install); \ done - $(PYTHON) -c 'from compileall import *; compile_dir("$(DESTDIR)$(prefix)/Mailman", ddir="$(prefix)/Mailman")' + @echo "Compiling Python files..." + @for d in $(PYTHON_DIRS); do \ + $(PYTHON) -c 'from compileall import *; compile_dir("$(DESTDIR)$(prefix)/$$d", ddir="$(prefix)/$$d", force=1)'; \ + done # Only run bin/update if we aren't installing in DESTDIR, as this # means there are probably no lists to deal with, and it wouldn't @@ -132,12 +181,18 @@ doinstall: $(SUBDIRS) update: @(cd $(DESTDIR)$(prefix) ; test -n "$(DESTDIR)" || bin/update) +check-scripts: + $(check_scripts) + clean: $(SUBDIRS) @for d in $(SUBDIRS); \ do \ (cd $$d; $(MAKE) clean); \ done -rm -f update.log + -rm -rf build + -rm -f $(shell find . -name "*.pyc") + -rm -f $(shell find . -name "*.pyo") distclean: $(SUBDIRS) @for d in $(SUBDIRS); \ diff --git a/bin/mailmanctl b/bin/mailmanctl index e6358903..f693fbb8 100755 --- a/bin/mailmanctl +++ b/bin/mailmanctl @@ -149,11 +149,10 @@ def usage(code, msg=''): fd = sys.stderr else: fd = sys.stdout - # Ensure PROGRAM is a string, not bytes - program = sys.argv[0] - if isinstance(program, bytes): - program = program.decode('utf-8', 'replace') - print(C_(__doc__) % {'PROGRAM': program}, file=fd) + # In Python 3, sys.argv[0] is already a string + program = str(sys.argv[0]) # Ensure it's a string + doc = C_(__doc__) % {'PROGRAM': program} # Let C_() handle the translation and formatting + print(doc, file=fd) if msg: print(msg, file=fd) sys.exit(code) @@ -433,150 +432,191 @@ def main(): # parent if not args.quiet: print(C_("Starting Mailman's master qrunner.")) - # Give up the lock "ownership". This just means the foreground + # Give up the lock "ownership". This just means the foreground # process won't close/unlock the lock when it finalizes this lock - # instance. We'll let the mater watcher subproc own the lock. + # instance. We'll let the master watcher subproc own the lock. lock._transfer_to(pid) + + # Wait briefly to ensure child process starts + time.sleep(1) + + # Verify the child process is running and PID file is correct + try: + os.kill(pid, 0) # Check if process exists + + # Verify PID file exists and contains correct PID + try: + with open(mm_cfg.PIDFILE, 'r') as fp: + child_pid = int(fp.read().strip()) + if child_pid != pid: + print(C_('Error: PID file contains incorrect PID'), file=sys.stderr) + return + + # Verify process is a Mailman process + try: + with open(f'/proc/{pid}/cmdline', 'r') as cmd_fp: + cmdline = cmd_fp.read() + if 'mailman' not in cmdline.lower(): + print(C_('Error: Process is not a Mailman process'), file=sys.stderr) + return + except (IOError, OSError) as e: + print(C_('Warning: Could not verify process type: %s') % str(e), file=sys.stderr) + + if not args.quiet: + print(C_('Master qrunner started successfully (pid: %d)') % pid) + syslog('qrunner', 'Master qrunner started successfully (pid: %d)', pid) + + except (IOError, ValueError) as e: + print(C_('Error reading PID file: %s') % str(e), file=sys.stderr) + return + except OSError as e: + if e.errno == errno.ESRCH: + print(C_('Error: Master process failed to start'), file=sys.stderr) + return + raise return # child - lock._take_possession() - # First, save our pid in a file for "mailmanctl stop" rendezvous. We - # want the perms on the .pid file to be rw-rw---- - omask = os.umask(6) try: - fp = open(mm_cfg.PIDFILE, 'w') - print(os.getpid(), file=fp) - fp.close() - finally: - os.umask(omask) - - # Create a new session and become the session leader - os.setsid() - - # Be sure to close any open std{in,out,err} - devnull = os.open('/dev/null', 0) - os.dup2(devnull, 0) - os.dup2(devnull, 1) - os.dup2(devnull, 2) - - # Instead of cd'ing to root, cd to the Mailman installation home - os.chdir(mm_cfg.PREFIX) - # Set our file mode creation umask - os.umask(0o07) - - # Now start all the qrunners - kids = start_all_runners() - if not kids: - syslog('error', 'No runners started successfully') - sys.exit(1) - - # Set up a SIGALRM handler to refresh the lock once per day - def sigalrm_handler(signum, frame, lock=lock): - lock.refresh() + lock._take_possession() + # First, save our pid in a file for "mailmanctl stop" rendezvous + omask = os.umask(6) + try: + fp = open(mm_cfg.PIDFILE, 'w') + print(os.getpid(), file=fp) + fp.close() + finally: + os.umask(omask) + + # Create a new session and become the session leader + os.setsid() + + # Be sure to close any open std{in,out,err} + devnull = os.open('/dev/null', 0) + os.dup2(devnull, 0) + os.dup2(devnull, 1) + os.dup2(devnull, 2) + + # Instead of cd'ing to root, cd to the Mailman installation home + os.chdir(mm_cfg.PREFIX) + # Set our file mode creation umask + os.umask(0o07) + + # Now start all the qrunners + kids = start_all_runners() + if not kids: + syslog('error', 'No runners started successfully') + sys.exit(1) + + # Set up a SIGALRM handler to refresh the lock once per day + def sigalrm_handler(signum, frame, lock=lock): + lock.refresh() + signal.alarm(mm_cfg.days(1)) + signal.signal(signal.SIGALRM, sigalrm_handler) signal.alarm(mm_cfg.days(1)) - signal.signal(signal.SIGALRM, sigalrm_handler) - signal.alarm(mm_cfg.days(1)) - - # Set up a SIGHUP handler - def sighup_handler(signum, frame, kids=kids): - syslog.close() - for pid in list(kids.keys()): - os.kill(pid, signal.SIGHUP) - syslog('qrunner', - 'Master watcher caught SIGHUP. Re-opening log files.') - signal.signal(signal.SIGHUP, sighup_handler) - - # Set up a SIGTERM handler - def sigterm_handler(signum, frame, kids=kids): - for pid in list(kids.keys()): - try: - os.kill(pid, signal.SIGTERM) - except OSError as e: - if e.errno != errno.ESRCH: raise - syslog('qrunner', 'Master watcher caught SIGTERM. Exiting.') - signal.signal(signal.SIGTERM, sigterm_handler) - - # Set up a SIGINT handler - def sigint_handler(signum, frame, kids=kids): - for pid in list(kids.keys()): - os.kill(pid, signal.SIGINT) - syslog('qrunner', 'Master watcher caught SIGINT. Restarting.') - signal.signal(signal.SIGINT, sigint_handler) - - # Now we're ready to simply do our wait/restart loop - try: - while 1: - try: - pid, status = os.wait() - except OSError as e: - # No children? We're done - if e.errno == errno.ECHILD: - break - # If the system call got interrupted, just restart it. - elif e.errno != errno.EINTR: - raise - continue - - killsig = exitstatus = None - if os.WIFSIGNALED(status): - killsig = os.WTERMSIG(status) - if os.WIFEXITED(status): - exitstatus = os.WEXITSTATUS(status) - - restarting = '' - if not args.no_restart: - if (exitstatus == None and killsig != signal.SIGTERM) or \ - (killsig == None and exitstatus != signal.SIGTERM): - restarting = '[restarting]' - - qrname, slice, count, restarts = kids[pid] - del kids[pid] - syslog('qrunner', """\ -Master qrunner detected subprocess exit -(pid: %d, sig: %s, sts: %s, class: %s, slice: %d/%d) %s""", - pid, killsig, exitstatus, qrname, - slice+1, count, restarting) - # See if we've reached the maximum number of allowable restarts - if exitstatus != signal.SIGINT: - restarts += 1 - if restarts > MAX_RESTARTS: - syslog('qrunner', """\ -Qrunner %s reached maximum restart limit of %d, not restarting.""", - qrname, MAX_RESTARTS) - restarting = '' + # Set up a SIGHUP handler + def sighup_handler(signum, frame, kids=kids): + syslog.close() + for pid in list(kids.keys()): + os.kill(pid, signal.SIGHUP) + syslog('qrunner', + 'Master watcher caught SIGHUP. Re-opening log files.') + signal.signal(signal.SIGHUP, sighup_handler) + + # Set up a SIGTERM handler + def sigterm_handler(signum, frame, kids=kids): + for pid in list(kids.keys()): + try: + os.kill(pid, signal.SIGTERM) + except OSError as e: + if e.errno != errno.ESRCH: raise + syslog('qrunner', 'Master watcher caught SIGTERM. Exiting.') + signal.signal(signal.SIGTERM, sigterm_handler) + + # Set up a SIGINT handler + def sigint_handler(signum, frame, kids=kids): + for pid in list(kids.keys()): + os.kill(pid, signal.SIGINT) + syslog('qrunner', 'Master watcher caught SIGINT. Restarting.') + signal.signal(signal.SIGINT, sigint_handler) + + # Now we're ready to simply do our wait/restart loop + try: + while True: + try: + pid, status = os.wait() + except OSError as e: + # No children? We're done + if e.errno == errno.ECHILD: + break + # If the system call got interrupted, just restart it. + elif e.errno != errno.EINTR: + raise + continue + + killsig = exitstatus = None + if os.WIFSIGNALED(status): + killsig = os.WTERMSIG(status) + if os.WIFEXITED(status): + exitstatus = os.WEXITSTATUS(status) - # Now perhaps restart the process - if restarting: - newpid = start_runner(qrname, slice, count) - kids[newpid] = (qrname, slice, count, restarts) + restarting = '' + if not args.no_restart: + if (exitstatus == None and killsig != signal.SIGTERM) or \ + (killsig == None and exitstatus != signal.SIGTERM): + restarting = '[restarting]' - finally: - # Should we leave the main loop for any reason, we want to be sure - # all of our children are exited cleanly - for pid in list(kids.keys()): - try: - os.kill(pid, signal.SIGTERM) - except OSError as e: - if e.errno == errno.ESRCH: - syslog('qrunner', 'ESRCH on pid: %d', pid) - del kids[pid] - - # Wait for all the children to go away - while 1: - try: - pid, status = os.wait() - except OSError as e: - if e.errno == errno.ECHILD: - break - elif e.errno != errno.EINTR: - raise - continue - - # Finally, give up the lock - lock.unlock(unconditionally=1) - os._exit(0) + qrname, slice, count, restarts = kids[pid] + del kids[pid] + syslog('qrunner', """\ +Master qrunner detected subprocess exit +(pid: %d, sig: %s, sts: %s, class: %s, slice: %d/%d) %s""", + pid, killsig, exitstatus, qrname, + slice+1, count, restarting) + + # See if we've reached the maximum number of allowable restarts + if exitstatus != signal.SIGINT: + restarts += 1 + if restarts > MAX_RESTARTS: + syslog('qrunner', """\ +Qrunner %s reached maximum restart limit of %d, not restarting.""", + qrname, MAX_RESTARTS) + restarting = '' + + # Now perhaps restart the process + if restarting: + newpid = start_runner(qrname, slice, count) + kids[newpid] = (qrname, slice, count, restarts) + + finally: + # Should we leave the main loop for any reason, we want to be sure + # all of our children are exited cleanly + for pid in list(kids.keys()): + try: + os.kill(pid, signal.SIGTERM) + except OSError as e: + if e.errno == errno.ESRCH: + syslog('qrunner', 'ESRCH on pid: %d', pid) + del kids[pid] + + # Wait for all the children to go away + while True: + try: + pid, status = os.wait() + except OSError as e: + if e.errno == errno.ECHILD: + break + elif e.errno != errno.EINTR: + raise + continue + + # Finally, give up the lock + lock.unlock(unconditionally=1) + os._exit(0) + except Exception as e: + syslog('error', 'Child process error during startup: %s', str(e)) + os._exit(1) except Exception as e: syslog('error', 'Error during startup: %s', str(e)) From 604693bc435df35085e97b4287a2c5df32e9fad2 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 17:35:39 -0400 Subject: [PATCH 073/748] update --- Makefile.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.in b/Makefile.in index 00c69f39..99c08540 100644 --- a/Makefile.in +++ b/Makefile.in @@ -75,7 +75,7 @@ EXCLUDES = --exclude=CVS --exclude=.cvsignore --exclude=Makefile* --exclude=*.fi PYTHON_FILES = $(shell find . -name "*.py") PYTHON_DIRS = $(shell find . -type d -name "Mailman") INSTALLED_SCRIPTS = $(shell find $(DESTDIR)$(prefix)/bin -type f -executable 2>/dev/null || true) -SOURCE_SCRIPTS = $(shell find build/bin -type f -executable 2>/dev/null || true) +SOURCE_SCRIPTS = $(shell find build/bin -type f -executable -name "*.py" 2>/dev/null || true) # Add this function to check for script mismatches define check_scripts From a52eb1d81dd5e5ee3df6d15bab3c4ad22ccdeb7b Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 17:39:27 -0400 Subject: [PATCH 074/748] update --- Makefile.in | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Makefile.in b/Makefile.in index 99c08540..ef7f9990 100644 --- a/Makefile.in +++ b/Makefile.in @@ -102,6 +102,10 @@ endef # Rules +Makefile: Makefile.in + @echo "ERROR: Makefile.in has been modified. Please re-run ./configure" + @exit 1 + all: subdirs build-dir build-dir: $(PYTHON_FILES) @@ -127,7 +131,8 @@ subdirs: $(SUBDIRS) (cd $$d; $(MAKE) all); \ done -install: doinstall update check-scripts +install: subdirs check-scripts + $(MAKE) -C build/bin install doinstall: $(SUBDIRS) $(PYTHON_FILES) @echo "Creating architecture independent directories..." @@ -182,7 +187,7 @@ update: @(cd $(DESTDIR)$(prefix) ; test -n "$(DESTDIR)" || bin/update) check-scripts: - $(check_scripts) + $(call check_scripts) clean: $(SUBDIRS) @for d in $(SUBDIRS); \ From 735db85dc8ac2e3e402966cb915f892679f296ff Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 18:00:51 -0400 Subject: [PATCH 075/748] update --- Mailman/i18n.py | 12 ++++++++++-- Makefile.in | 49 +++++++++++++++++++++++++++++++++---------------- bin/mailmanctl | 13 ++++--------- 3 files changed, 47 insertions(+), 27 deletions(-) diff --git a/Mailman/i18n.py b/Mailman/i18n.py index d20acabd..7892bc75 100644 --- a/Mailman/i18n.py +++ b/Mailman/i18n.py @@ -120,7 +120,11 @@ def tolocale(s): # Handle string formatting before encoding if isinstance(s, bytes): s = s.decode('utf-8', 'replace') - return s.encode(_ctype_charset, 'replace').decode(_ctype_charset) + # Ensure we return a string, not bytes + result = s.encode(_ctype_charset, 'replace') + if isinstance(result, bytes): + result = result.decode(_ctype_charset) + return result if mm_cfg.DISABLE_COMMAND_LOCALE_CSET: C_ = _ @@ -129,7 +133,11 @@ def C_(s): result = _(s, 2) if isinstance(result, bytes): result = result.decode('utf-8', 'replace') - return tolocale(result) + result = tolocale(result) + # Ensure the result is a string and not bytes + if isinstance(result, bytes): + result = result.decode('utf-8', 'replace') + return result diff --git a/Makefile.in b/Makefile.in index ef7f9990..af241a10 100644 --- a/Makefile.in +++ b/Makefile.in @@ -106,35 +106,46 @@ Makefile: Makefile.in @echo "ERROR: Makefile.in has been modified. Please re-run ./configure" @exit 1 -all: subdirs build-dir - -build-dir: $(PYTHON_FILES) - @echo "Updating build directory..." - @for d in $(SUBDIRS); \ - do \ - dir=build/$$d; \ - if test ! -d $$dir; then \ - $(srcdir)/mkinstalldirs $$dir; \ - fi; \ +check-build-files: + @echo "Checking if build files are up to date..." + @for d in $(SUBDIRS); do \ for f in $$d/*; do \ if test -f $$f; then \ - if test ! -f build/$$f -o $$f -nt build/$$f; then \ - cp -p $$f build/$$f; \ + if test ! -f build/$$f -o build/$$f -ot $$f; then \ + echo "ERROR: Source file $$f is newer than build/$$f"; \ + echo "Please re-run ./configure to update build directory"; \ + exit 1; \ fi; \ fi; \ done; \ done + @for f in $(PYTHON_FILES); do \ + if test ! -f build/$$f -o build/$$f -ot $$f; then \ + echo "ERROR: Source file $$f is newer than build/$$f"; \ + echo "Please re-run ./configure to update build directory"; \ + exit 1; \ + fi; \ + done + +all: check-build-files subdirs subdirs: $(SUBDIRS) for d in $(SUBDIRS); \ do \ - (cd $$d; $(MAKE) all); \ + (cd $$d; $(MAKE)); \ done install: subdirs check-scripts - $(MAKE) -C build/bin install + $(MAKE) -C bin install + +clean-pyc: + @echo "Cleaning Python bytecode files..." + @for d in $(PYTHON_DIRS); do \ + find $(DESTDIR)$(prefix)/$$d -name "*.pyc" -delete; \ + find $(DESTDIR)$(prefix)/$$d -name "*.pyo" -delete; \ + done -doinstall: $(SUBDIRS) $(PYTHON_FILES) +doinstall: $(SUBDIRS) $(PYTHON_FILES) clean-pyc @echo "Creating architecture independent directories..." @for d in $(VAR_DIRS); \ do \ @@ -171,13 +182,19 @@ doinstall: $(SUBDIRS) $(PYTHON_FILES) else true; \ fi; \ done + @echo "Installing Python files..." + @for d in $(PYTHON_DIRS); do \ + find $$d -name "*.py" -type f -print0 | while IFS= read -r -d '' f; do \ + install -D -m $(FILEMODE) "$$f" "$(DESTDIR)$(prefix)/$$f"; \ + done; \ + done @for d in $(SUBDIRS); \ do \ (cd $$d; $(MAKE) DESTDIR=$(DESTDIR) install); \ done @echo "Compiling Python files..." @for d in $(PYTHON_DIRS); do \ - $(PYTHON) -c 'from compileall import *; compile_dir("$(DESTDIR)$(prefix)/$$d", ddir="$(prefix)/$$d", force=1)'; \ + $(PYTHON) -c 'from compileall import *; compile_dir("$(DESTDIR)$(prefix)/$$d", ddir="$(prefix)/$$d", force=1, quiet=0)'; \ done # Only run bin/update if we aren't installing in DESTDIR, as this diff --git a/bin/mailmanctl b/bin/mailmanctl index f693fbb8..c4a0f7ca 100755 --- a/bin/mailmanctl +++ b/bin/mailmanctl @@ -160,12 +160,8 @@ def usage(code, msg=''): def kill_watcher(sig): try: - fp = open(mm_cfg.PIDFILE, 'rb') - pidstr = fp.read() - fp.close() - if isinstance(pidstr, bytes): - pidstr = pidstr.decode('utf-8', 'replace') - pid = int(pidstr.strip()) + with open(mm_cfg.PIDFILE, 'r') as fp: + pid = int(fp.read().strip()) except (IOError, ValueError) as e: # For i18n convenience pidfile = mm_cfg.PIDFILE @@ -482,9 +478,8 @@ def main(): # First, save our pid in a file for "mailmanctl stop" rendezvous omask = os.umask(6) try: - fp = open(mm_cfg.PIDFILE, 'w') - print(os.getpid(), file=fp) - fp.close() + with open(mm_cfg.PIDFILE, 'w') as fp: + print(os.getpid(), file=fp) finally: os.umask(omask) From 6ef9544609a0b7d7e65f9b5b796d567d09fd5a8c Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 18:05:54 -0400 Subject: [PATCH 076/748] update --- Makefile.in | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Makefile.in b/Makefile.in index af241a10..30dd0ef8 100644 --- a/Makefile.in +++ b/Makefile.in @@ -130,9 +130,10 @@ check-build-files: all: check-build-files subdirs subdirs: $(SUBDIRS) - for d in $(SUBDIRS); \ + @for d in $(SUBDIRS); \ do \ - (cd $$d; $(MAKE)); \ + echo "Making in $$d"; \ + (cd $$d && $(MAKE) all); \ done install: subdirs check-scripts From 44fbfec6ce520ed9fe997c1285cb3a06c9a049b4 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 18:09:38 -0400 Subject: [PATCH 077/748] update --- Mailman/MailList.py | 8 +++++--- bin/update | 3 +++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Mailman/MailList.py b/Mailman/MailList.py index ba18c00d..3308cd90 100644 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -712,16 +712,18 @@ def __load(self, dbfile): try: if dbfile.endswith('.db') or dbfile.endswith('.db.last'): dict_retval = marshal.load(fp) + # Convert any bytes to strings for marshal-loaded data + dict_retval = self.__convert_bytes_to_strings(dict_retval) elif dbfile.endswith('.pck') or dbfile.endswith('.pck.last'): + # Use latin1 encoding for pickle files to maintain compatibility dict_retval = pickle.load(fp, fix_imports=True, encoding='latin1') + # Convert any remaining bytes to strings + dict_retval = self.__convert_bytes_to_strings(dict_retval) # Validate the loaded data if not isinstance(dict_retval, dict): return None, 'Load() expected to return a dictionary' - # Convert any bytes to strings - dict_retval = self.__convert_bytes_to_strings(dict_retval) - return dict_retval, None except (EOFError, ValueError, TypeError, MemoryError, diff --git a/bin/update b/bin/update index 7488dd79..cf6b2a4d 100755 --- a/bin/update +++ b/bin/update @@ -743,6 +743,9 @@ def upgrade(lastversion, thisversion): # For each list, load it, call Update() on it, and save it for name in names: + # Ensure name is a string, not bytes + if isinstance(name, bytes): + name = name.decode('utf-8', 'replace') print(C_('Upgrading list: %(name)s')) try: mlist = MailList.MailList(name, lock=True) From b6031f1cc689b68ee628c449cf7ebbcafef29ea3 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 18:14:07 -0400 Subject: [PATCH 078/748] update --- Makefile.in | 15 +++++++++------ bin/update | 3 ++- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/Makefile.in b/Makefile.in index 30dd0ef8..fb643cb7 100644 --- a/Makefile.in +++ b/Makefile.in @@ -112,18 +112,18 @@ check-build-files: for f in $$d/*; do \ if test -f $$f; then \ if test ! -f build/$$f -o build/$$f -ot $$f; then \ - echo "ERROR: Source file $$f is newer than build/$$f"; \ - echo "Please re-run ./configure to update build directory"; \ - exit 1; \ + echo "Updating build file: $$f"; \ + cp $$f build/$$f; \ + touch build/$$f; \ fi; \ fi; \ done; \ done @for f in $(PYTHON_FILES); do \ if test ! -f build/$$f -o build/$$f -ot $$f; then \ - echo "ERROR: Source file $$f is newer than build/$$f"; \ - echo "Please re-run ./configure to update build directory"; \ - exit 1; \ + echo "Updating build file: $$f"; \ + cp $$f build/$$f; \ + touch build/$$f; \ fi; \ done @@ -144,6 +144,7 @@ clean-pyc: @for d in $(PYTHON_DIRS); do \ find $(DESTDIR)$(prefix)/$$d -name "*.pyc" -delete; \ find $(DESTDIR)$(prefix)/$$d -name "*.pyo" -delete; \ + find $(DESTDIR)$(prefix)/$$d -name "__pycache__" -type d -exec rm -rf {} +; \ done doinstall: $(SUBDIRS) $(PYTHON_FILES) clean-pyc @@ -187,6 +188,7 @@ doinstall: $(SUBDIRS) $(PYTHON_FILES) clean-pyc @for d in $(PYTHON_DIRS); do \ find $$d -name "*.py" -type f -print0 | while IFS= read -r -d '' f; do \ install -D -m $(FILEMODE) "$$f" "$(DESTDIR)$(prefix)/$$f"; \ + touch "$(DESTDIR)$(prefix)/$$f"; \ done; \ done @for d in $(SUBDIRS); \ @@ -195,6 +197,7 @@ doinstall: $(SUBDIRS) $(PYTHON_FILES) clean-pyc done @echo "Compiling Python files..." @for d in $(PYTHON_DIRS); do \ + echo "Compiling Python files in $$d..."; \ $(PYTHON) -c 'from compileall import *; compile_dir("$(DESTDIR)$(prefix)/$$d", ddir="$(prefix)/$$d", force=1, quiet=0)'; \ done diff --git a/bin/update b/bin/update index cf6b2a4d..e7371617 100755 --- a/bin/update +++ b/bin/update @@ -746,7 +746,8 @@ def upgrade(lastversion, thisversion): # Ensure name is a string, not bytes if isinstance(name, bytes): name = name.decode('utf-8', 'replace') - print(C_('Upgrading list: %(name)s')) + # Format the name directly as a string + print('Upgrading list: %s' % name) try: mlist = MailList.MailList(name, lock=True) except Exception as e: From 4db02b7001d7d4f7067a913165c6de989b65d41d Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 18:15:47 -0400 Subject: [PATCH 079/748] update string handling --- bin/list_admins | 27 ++++++++++++++++----------- bin/list_lists | 27 +++++++++++++++++++++------ bin/list_owners | 5 +++++ 3 files changed, 42 insertions(+), 17 deletions(-) diff --git a/bin/list_admins b/bin/list_admins index 5d6b4968..f764b9a1 100644 --- a/bin/list_admins +++ b/bin/list_admins @@ -81,17 +81,22 @@ def main(): listnames = Utils.list_names() for listname in listnames: - try: - mlist = MailList.MailList(listname, lock=0) - except Errors.MMListError as e: - print(C_('No such list: %(listname)s')) - continue - - if args.all_vhost and args.all_vhost != mlist.host_name: - continue - - owners = COMMASPACE.join(mlist.owner) - print(C_('List: %(listname)s, \tOwners: %(owners)s')) + # Ensure listname is a string + if isinstance(listname, bytes): + listname = listname.decode('utf-8', 'replace') + try: + mlist = MailList.MailList(listname, lock=0) + except Errors.MMListError: + print('No such list: %s' % listname) + continue + + if args.all_vhost and args.all_vhost != mlist.host_name: + continue + + # Ensure owners are strings + owners = [owner.decode('utf-8', 'replace') if isinstance(owner, bytes) else owner for owner in mlist.owner] + owners_str = ', '.join(owners) + print('List: %s, \tOwners: %s' % (listname, owners_str)) if __name__ == '__main__': diff --git a/bin/list_lists b/bin/list_lists index 266b9f0c..aba50caa 100644 --- a/bin/list_lists +++ b/bin/list_lists @@ -85,6 +85,9 @@ def main(): mlists = [] longest = 0 for n in names: + # Ensure name is a string + if isinstance(n, bytes): + n = n.decode('utf-8', 'replace') try: mlist = MailList.MailList(n, lock=0) except Errors.MMUnknownListError: @@ -100,22 +103,34 @@ def main(): re.IGNORECASE)): continue mlists.append(mlist) - longest = max(len(mlist.real_name), longest) + # Ensure real_name is a string + real_name = mlist.real_name + if isinstance(real_name, bytes): + real_name = real_name.decode('utf-8', 'replace') + longest = max(len(real_name), longest) if not mlists and not args.bare: - print(C_('No matching mailing lists found')) + print('No matching mailing lists found') return if not args.bare: - print(len(mlists), C_('matching mailing lists found:')) + print(len(mlists), 'matching mailing lists found:') format = '%%%ds - %%.%ds' % (longest, 77 - longest) for mlist in mlists: if args.bare: - print(mlist.internal_name()) + name = mlist.internal_name() + if isinstance(name, bytes): + name = name.decode('utf-8', 'replace') + print(name) else: - description = mlist.description or C_('[no description available]') - print(' ', format % (mlist.real_name, description)) + real_name = mlist.real_name + if isinstance(real_name, bytes): + real_name = real_name.decode('utf-8', 'replace') + description = mlist.description or '[no description available]' + if isinstance(description, bytes): + description = description.decode('utf-8', 'replace') + print(' ', format % (real_name, description)) if __name__ == '__main__': diff --git a/bin/list_owners b/bin/list_owners index 0d11a531..11c0013d 100644 --- a/bin/list_owners +++ b/bin/list_owners @@ -78,10 +78,15 @@ def main(): bylist = {} for listname in listnames: + # Ensure listname is a string + if isinstance(listname, bytes): + listname = listname.decode('utf-8', 'replace') mlist = MailList(listname, lock=0) addrs = mlist.owner[:] if args.moderators: addrs.extend(mlist.moderator) + # Ensure addresses are strings + addrs = [addr.decode('utf-8', 'replace') if isinstance(addr, bytes) else addr for addr in addrs] bylist[listname] = addrs if args.with_listnames: From d4cfc20b013c9ecfb275a1fb32a4e2936ee7cebd Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 18:29:39 -0400 Subject: [PATCH 080/748] build fixes --- Makefile.in | 9 +++++++++ configure | 7 +++++++ configure.ac | 5 +++++ 3 files changed, 21 insertions(+) diff --git a/Makefile.in b/Makefile.in index fb643cb7..138f6735 100644 --- a/Makefile.in +++ b/Makefile.in @@ -22,6 +22,10 @@ SHELL= /bin/sh +# Store configure arguments for reconfigure target +CONFIGURE_ARGS= @CONFIGURE_ARGS@ +CONFIGURE_CMD= @CONFIGURE_CMD@ + VPATH= @srcdir@ srcdir= @srcdir@ bindir= @bindir@ @@ -106,6 +110,11 @@ Makefile: Makefile.in @echo "ERROR: Makefile.in has been modified. Please re-run ./configure" @exit 1 +# Target to re-run configure with original arguments +reconfigure: + @echo "Re-running configure with original arguments: $(CONFIGURE_ARGS)" + cd $(srcdir) && $(CONFIGURE_CMD) $(CONFIGURE_ARGS) + check-build-files: @echo "Checking if build files are up to date..." @for d in $(SUBDIRS); do \ diff --git a/configure b/configure index 88cb72a5..2bd4af65 100755 --- a/configure +++ b/configure @@ -674,6 +674,8 @@ PYTHON with_python BUILD_DATE CONFIGURE_OPTS +CONFIGURE_ARGS +CONFIGURE_CMD target_alias host_alias build_alias @@ -2504,6 +2506,11 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu +# Store the configure command and arguments for reconfigure target +CONFIGURE_CMD="\$0" +CONFIGURE_ARGS="\$*" + + # /usr/local/mailman is the default installation directory diff --git a/configure.ac b/configure.ac index ac99de88..74a758cd 100644 --- a/configure.ac +++ b/configure.ac @@ -20,6 +20,11 @@ AC_PREREQ([2.71]) AC_INIT AC_CONFIG_SRCDIR([src/common.h]) +# Store the configure command and arguments for reconfigure target +CONFIGURE_CMD="\$0" +CONFIGURE_ARGS="\$*" +AC_SUBST(CONFIGURE_CMD) +AC_SUBST(CONFIGURE_ARGS) # /usr/local/mailman is the default installation directory AC_PREFIX_DEFAULT(/usr/local/mailman) From 09d1683a7900f4802014df0e85cd48fe8593c579 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 18:35:29 -0400 Subject: [PATCH 081/748] update --- bin/mailmanctl | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/bin/mailmanctl b/bin/mailmanctl index c4a0f7ca..ad769bab 100755 --- a/bin/mailmanctl +++ b/bin/mailmanctl @@ -161,13 +161,23 @@ def usage(code, msg=''): def kill_watcher(sig): try: with open(mm_cfg.PIDFILE, 'r') as fp: - pid = int(fp.read().strip()) + content = fp.read().strip().split() + if len(content) >= 2: + pid = int(content[0]) + hostname = content[1] + if hostname != socket.gethostname(): + print(C_('PID file hostname mismatch: expected %(expected)s, got %(got)s') % + {'expected': socket.gethostname(), 'got': hostname}, file=sys.stderr) + return + else: + raise ValueError('Invalid PID file format') except (IOError, ValueError) as e: # For i18n convenience pidfile = mm_cfg.PIDFILE print(C_('PID unreadable in: %(pidfile)s'), file=sys.stderr) print(e, file=sys.stderr) print(C_('Is qrunner even running?'), file=sys.stderr) + print(C_('Lock file path: %(lockfile)s') % {'lockfile': LOCKFILE}, file=sys.stderr) return try: os.kill(pid, sig) From 0be2564b16b22971a9d49f383b65e9cb02e5c00f Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 18:48:13 -0400 Subject: [PATCH 082/748] update --- Mailman/LockFile.py | 87 ++++++++++++++++++++++++++++----------------- bin/mailmanctl | 35 +++++++++++++----- 2 files changed, 81 insertions(+), 41 deletions(-) diff --git a/Mailman/LockFile.py b/Mailman/LockFile.py index 124e4076..f7ed9e21 100644 --- a/Mailman/LockFile.py +++ b/Mailman/LockFile.py @@ -441,41 +441,54 @@ def _take_possession(self): # Check if the lock file is stale try: with open(self.__lockfile) as fp: - content = fp.read().strip().split() - if len(content) >= 2: - pid = int(content[0]) - lock_hostname = content[1] + current_tempfile = fp.read().strip() + if not current_tempfile: + self.__writelog('lock file is empty') + os.unlink(self.__lockfile) + os.unlink(tempfile) + return -1 + + # Extract hostname and PID from temp file name + parts = current_tempfile.split('.') + if len(parts) < 3: + self.__writelog('invalid lock file format') + os.unlink(self.__lockfile) + os.unlink(tempfile) + return -1 + + lock_hostname = '.'.join(parts[1:-1]) + pid = int(parts[-1]) - # If the lock is from another host, we need to be more conservative - if lock_hostname != hostname: - self.__writelog('lock owned by different host: %s' % lock_hostname) + # If the lock is from another host, we need to be more conservative + if lock_hostname != hostname: + self.__writelog('lock owned by different host: %s' % lock_hostname) + os.unlink(tempfile) + return -1 + + # Check if process exists and is a Mailman process + if not self._is_pid_valid(pid): + self.__writelog('found stale lock (pid %d)' % pid) + try: + os.unlink(self.__lockfile) os.unlink(tempfile) return -1 - - # Check if process exists and is a Mailman process - if not self._is_pid_valid(pid): - self.__writelog('found stale lock (pid %d)' % pid) - try: - os.unlink(self.__lockfile) - os.unlink(tempfile) - return -1 - except OSError: - # Someone else might have cleaned up - os.unlink(tempfile) - return -1 - else: - # Process exists - check if it's a Mailman process - try: - with open(f'/proc/{pid}/cmdline') as f: - cmdline = f.read() - if 'mailman' not in cmdline.lower(): - self.__writelog('breaking lock owned by non-Mailman process') - os.unlink(self.__lockfile) - os.unlink(tempfile) - return -1 - except (IOError, OSError): - # Can't read process info - be conservative - pass + except OSError: + # Someone else might have cleaned up + os.unlink(tempfile) + return -1 + else: + # Process exists - check if it's a Mailman process + try: + with open(f'/proc/{pid}/cmdline') as f: + cmdline = f.read() + if 'mailman' not in cmdline.lower(): + self.__writelog('breaking lock owned by non-Mailman process') + os.unlink(self.__lockfile) + os.unlink(tempfile) + return -1 + except (IOError, OSError): + # Can't read process info - be conservative + pass except (ValueError, OSError) as e: self.__writelog('error reading lock: %s' % e) # Lock file exists but is invalid - try to break it @@ -537,7 +550,15 @@ def _break(self): # Read the lock file to get the old PID try: with open(self.__lockfile) as fp: - pid = int(fp.read().strip()) + content = fp.read().strip().split() + if len(content) >= 2: + pid = int(content[0]) + hostname = content[1] + if hostname != socket.gethostname(): + self.__writelog('lock owned by different host: %s' % hostname) + return -1 + else: + pid = int(content[0]) # Try old format if not self._is_pid_valid(pid): self.__writelog('breaking stale lock owned by pid %d' % pid) os.unlink(self.__lockfile) diff --git a/bin/mailmanctl b/bin/mailmanctl index ad769bab..d983aa6b 100755 --- a/bin/mailmanctl +++ b/bin/mailmanctl @@ -335,20 +335,30 @@ def check_privs(): 'Run this program as root or as the %(name)s user, or use -u.')) -def check_qrunner_status(): +def check_status(): """Check if all qrunners are running as expected.""" # First check if the master process is running try: with open(mm_cfg.PIDFILE, 'r') as fp: - pid = int(fp.read().strip()) + content = fp.read().strip().split() + if len(content) >= 2: + pid = int(content[0]) + hostname = content[1] + if hostname != socket.gethostname(): + print(C_('PID file hostname mismatch: expected %(expected)s, got %(got)s') % + {'expected': socket.gethostname(), 'got': hostname}, file=sys.stderr) + return False + else: + raise ValueError('Invalid PID file format') try: os.kill(pid, 0) # Check if process exists print(C_('Master qrunner process is running (pid: %(pid)d)') % {'pid': pid}) except OSError: print(C_('Master qrunner process is not running (stale pid file)')) return False - except (IOError, ValueError): + except (IOError, ValueError) as e: print(C_('Master qrunner process is not running (no pid file)')) + print(e, file=sys.stderr) return False # Check if the lock file exists and is valid @@ -420,7 +430,7 @@ def main(): # Handle the command if args.command == 'status': - if check_qrunner_status(): + if check_status(): sys.exit(0) else: sys.exit(1) @@ -453,9 +463,18 @@ def main(): # Verify PID file exists and contains correct PID try: with open(mm_cfg.PIDFILE, 'r') as fp: - child_pid = int(fp.read().strip()) - if child_pid != pid: - print(C_('Error: PID file contains incorrect PID'), file=sys.stderr) + content = fp.read().strip().split() + if len(content) >= 2: + child_pid = int(content[0]) + child_hostname = content[1] + if child_pid != pid: + print(C_('Error: PID file contains incorrect PID'), file=sys.stderr) + return + if child_hostname != socket.gethostname(): + print(C_('Error: PID file hostname mismatch'), file=sys.stderr) + return + else: + print(C_('Error: Invalid PID file format'), file=sys.stderr) return # Verify process is a Mailman process @@ -489,7 +508,7 @@ def main(): omask = os.umask(6) try: with open(mm_cfg.PIDFILE, 'w') as fp: - print(os.getpid(), file=fp) + print('%d %s' % (os.getpid(), socket.gethostname()), file=fp) finally: os.umask(omask) From 6e416e628943c6313ab89407a0325d7646937134 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 18:58:33 -0400 Subject: [PATCH 083/748] update --- Makefile.in | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/Makefile.in b/Makefile.in index 138f6735..408dd7f5 100644 --- a/Makefile.in +++ b/Makefile.in @@ -136,17 +136,21 @@ check-build-files: fi; \ done -all: check-build-files subdirs - -subdirs: $(SUBDIRS) - @for d in $(SUBDIRS); \ - do \ - echo "Making in $$d"; \ - (cd $$d && $(MAKE) all); \ +all: src + @for d in $(SUBDIRS); do \ + (cd $$d && $(MAKE) all) || exit 1; \ done -install: subdirs check-scripts - $(MAKE) -C bin install +build: all + @echo "Building Python files..." + @$(PYTHON) -m compileall -q . + @echo "Build complete." + +install: build + @for d in $(SUBDIRS); do \ + (cd $$d && $(MAKE) install) || exit 1; \ + done + @echo "Installation complete." clean-pyc: @echo "Cleaning Python bytecode files..." From c232a207cf0c5b480d9fc3edd46f31cc0f2f0e13 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 18:59:31 -0400 Subject: [PATCH 084/748] update --- Makefile.in | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Makefile.in b/Makefile.in index 408dd7f5..06f0431f 100644 --- a/Makefile.in +++ b/Makefile.in @@ -107,8 +107,10 @@ endef # Rules Makefile: Makefile.in - @echo "ERROR: Makefile.in has been modified. Please re-run ./configure" - @exit 1 + @if [ "$@" != "reconfigure" ]; then \ + echo "ERROR: Makefile.in has been modified. Please re-run ./configure"; \ + exit 1; \ + fi # Target to re-run configure with original arguments reconfigure: From cd8213c1f0bd8205385377d3235a715cac4fe17a Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 19:03:46 -0400 Subject: [PATCH 085/748] update --- Makefile.in | 22 ++++++++++++++++------ configure.ac | 4 ++-- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/Makefile.in b/Makefile.in index 06f0431f..9d9da39d 100644 --- a/Makefile.in +++ b/Makefile.in @@ -106,17 +106,27 @@ endef # Rules +.PHONY: all build install clean distclean reconfigure + +reconfigure: + @echo "Re-running configure with original arguments: $(CONFIGURE_ARGS)" + @if test -x config.status; then \ + ./config.status --recheck; \ + ./config.status; \ + else \ + if test -n "$(CONFIGURE_ARGS)"; then \ + sh configure $(CONFIGURE_ARGS); \ + else \ + sh configure; \ + fi; \ + fi + Makefile: Makefile.in - @if [ "$@" != "reconfigure" ]; then \ + @if test "$(MAKECMDGOALS)" != "reconfigure"; then \ echo "ERROR: Makefile.in has been modified. Please re-run ./configure"; \ exit 1; \ fi -# Target to re-run configure with original arguments -reconfigure: - @echo "Re-running configure with original arguments: $(CONFIGURE_ARGS)" - cd $(srcdir) && $(CONFIGURE_CMD) $(CONFIGURE_ARGS) - check-build-files: @echo "Checking if build files are up to date..." @for d in $(SUBDIRS); do \ diff --git a/configure.ac b/configure.ac index 74a758cd..b76c5a2d 100644 --- a/configure.ac +++ b/configure.ac @@ -21,8 +21,8 @@ AC_INIT AC_CONFIG_SRCDIR([src/common.h]) # Store the configure command and arguments for reconfigure target -CONFIGURE_CMD="\$0" -CONFIGURE_ARGS="\$*" +CONFIGURE_CMD=`echo "$0"` +CONFIGURE_ARGS=`echo "$*"` AC_SUBST(CONFIGURE_CMD) AC_SUBST(CONFIGURE_ARGS) From 17d355407ea49ad8b4eec0cf1609fff971715f47 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 19:04:42 -0400 Subject: [PATCH 086/748] update --- configure | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/configure b/configure index 2bd4af65..70e83a4c 100755 --- a/configure +++ b/configure @@ -2507,8 +2507,8 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu # Store the configure command and arguments for reconfigure target -CONFIGURE_CMD="\$0" -CONFIGURE_ARGS="\$*" +CONFIGURE_CMD=`echo "$0"` +CONFIGURE_ARGS=`echo "$*"` From 2b85d375bc811c9b9ca705fb0af4668cad15eb35 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 19:16:05 -0400 Subject: [PATCH 087/748] update build process --- Makefile.in | 96 +++++++++++++++++------------------------------------ 1 file changed, 31 insertions(+), 65 deletions(-) diff --git a/Makefile.in b/Makefile.in index 9d9da39d..ccd438e8 100644 --- a/Makefile.in +++ b/Makefile.in @@ -22,28 +22,23 @@ SHELL= /bin/sh -# Store configure arguments for reconfigure target -CONFIGURE_ARGS= @CONFIGURE_ARGS@ -CONFIGURE_CMD= @CONFIGURE_CMD@ - -VPATH= @srcdir@ -srcdir= @srcdir@ -bindir= @bindir@ -prefix= @prefix@ -exec_prefix= @exec_prefix@ -var_prefix= @VAR_PREFIX@ +srcdir= . +bindir= ${exec_prefix}/bin +prefix= /usr/local/mailman +exec_prefix= ${prefix} +var_prefix= /usr/local/mailman DESTDIR= -CC= @CC@ -INSTALL= @INSTALL@ -PYTHON= @PYTHON@ +CC= gcc +INSTALL= /usr/bin/install -c +PYTHON= /usr/bin/python3 -DEFS= @DEFS@ +DEFS= -DPACKAGE_NAME=\"\" -DPACKAGE_TARNAME=\"\" -DPACKAGE_VERSION=\"\" -DPACKAGE_STRING=\"\" -DPACKAGE_BUGREPORT=\"\" -DPACKAGE_URL=\"\" -DHAVE_STRERROR=1 -DHAVE_SETREGID=1 -DHAVE_SYSLOG=1 -DHAVE_STDIO_H=1 -DHAVE_STDLIB_H=1 -DHAVE_STRING_H=1 -DHAVE_INTTYPES_H=1 -DHAVE_STDINT_H=1 -DHAVE_STRINGS_H=1 -DHAVE_SYS_STAT_H=1 -DHAVE_SYS_TYPES_H=1 -DHAVE_UNISTD_H=1 -DSTDC_HEADERS=1 -DHAVE_STDIO_H=1 -DHAVE_STDLIB_H=1 -DHAVE_STRING_H=1 -DHAVE_INTTYPES_H=1 -DHAVE_STDINT_H=1 -DHAVE_STRINGS_H=1 -DHAVE_SYS_STAT_H=1 -DHAVE_SYS_TYPES_H=1 -DHAVE_UNISTD_H=1 -DHAVE_SYSLOG_H=1 -DGETGROUPS_T=gid_t -DHAVE_VSNPRINTF=1 # Customizable but not set by configure -OPT= @OPT@ -CFLAGS= @CFLAGS@ $(OPT) $(DEFS) +OPT= -g -O2 +CFLAGS= -g -O2 $(OPT) $(DEFS) VAR_DIRS= \ logs archives lists locks data spam qfiles \ @@ -61,7 +56,6 @@ ARCH_DEP_DIRS= cgi-bin mail # Directories make should decend into SUBDIRS= bin cron misc Mailman scripts src templates messages tests - # Modes for directories and executables created by the install # process. Default to group-writable directories but # user-only-writable for executables. @@ -106,51 +100,29 @@ endef # Rules -.PHONY: all build install clean distclean reconfigure - -reconfigure: - @echo "Re-running configure with original arguments: $(CONFIGURE_ARGS)" - @if test -x config.status; then \ - ./config.status --recheck; \ - ./config.status; \ - else \ - if test -n "$(CONFIGURE_ARGS)"; then \ - sh configure $(CONFIGURE_ARGS); \ - else \ - sh configure; \ - fi; \ - fi - -Makefile: Makefile.in - @if test "$(MAKECMDGOALS)" != "reconfigure"; then \ - echo "ERROR: Makefile.in has been modified. Please re-run ./configure"; \ - exit 1; \ - fi +.PHONY: all build install clean distclean prepare-build -check-build-files: - @echo "Checking if build files are up to date..." +# Default target +all: prepare-build @for d in $(SUBDIRS); do \ - for f in $$d/*; do \ - if test -f $$f; then \ - if test ! -f build/$$f -o build/$$f -ot $$f; then \ - echo "Updating build file: $$f"; \ - cp $$f build/$$f; \ - touch build/$$f; \ - fi; \ - fi; \ - done; \ - done - @for f in $(PYTHON_FILES); do \ - if test ! -f build/$$f -o build/$$f -ot $$f; then \ - echo "Updating build file: $$f"; \ - cp $$f build/$$f; \ - touch build/$$f; \ - fi; \ + (cd $$d && $(MAKE) all) || exit 1; \ done -all: src +# Build directory preparation +prepare-build: + @echo "Preparing build directory..." @for d in $(SUBDIRS); do \ - (cd $$d && $(MAKE) all) || exit 1; \ + dir=build/$$d; \ + if test ! -d $$dir; then \ + $(srcdir)/mkinstalldirs $$dir; \ + fi; \ + for f in $$d/*; do \ + if test -f $$f; then \ + if test ! -f build/$$f -o $$f -nt build/$$f; then \ + cp -p $$f build/$$f; \ + fi; \ + fi; \ + done; \ done build: all @@ -226,15 +198,10 @@ doinstall: $(SUBDIRS) $(PYTHON_FILES) clean-pyc $(PYTHON) -c 'from compileall import *; compile_dir("$(DESTDIR)$(prefix)/$$d", ddir="$(prefix)/$$d", force=1, quiet=0)'; \ done -# Only run bin/update if we aren't installing in DESTDIR, as this -# means there are probably no lists to deal with, and it wouldn't -# work anyway (because of import paths.) +# Only run bin/update if we aren't installing in DESTDIR update: @(cd $(DESTDIR)$(prefix) ; test -n "$(DESTDIR)" || bin/update) -check-scripts: - $(call check_scripts) - clean: $(SUBDIRS) @for d in $(SUBDIRS); \ do \ @@ -245,13 +212,12 @@ clean: $(SUBDIRS) -rm -f $(shell find . -name "*.pyc") -rm -f $(shell find . -name "*.pyo") -distclean: $(SUBDIRS) +distclean: clean @for d in $(SUBDIRS); \ do \ (cd $$d; $(MAKE) distclean); \ done -rm -f config.cache config.log config.status Makefile - -rm -rf build langpack: tar zcvf langpack-$(DATE).tgz $(EXCLUDES) $(LANGPACK) From 4f7333e0b726921f7eed5ba2da6d3ccd36818f16 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 19:22:51 -0400 Subject: [PATCH 088/748] update --- Mailman/Queue/Runner.py | 80 +++++++++++++++++-------------- Mailman/Utils.py | 27 ++++++----- Makefile.in | 8 ++-- contrib/check_perms_grsecurity.py | 4 +- contrib/courier-to-mailman.py | 4 +- 5 files changed, 65 insertions(+), 58 deletions(-) diff --git a/Mailman/Queue/Runner.py b/Mailman/Queue/Runner.py index 1b517924..c1686875 100644 --- a/Mailman/Queue/Runner.py +++ b/Mailman/Queue/Runner.py @@ -28,7 +28,7 @@ from Mailman import Errors from Mailman import MailList from Mailman import i18n - +from Mailman.Message import Message as MailmanMessage from Mailman.Logging.Syslog import syslog from Mailman.Queue.Switchboard import Switchboard @@ -154,43 +154,49 @@ def _onefile(self, msg, msgdata): # them out of our site though. # # Find out which mailing list this message is destined for. - listname = msgdata.get('listname') - if not listname: - listname = mm_cfg.MAILMAN_SITE_LIST - mlist = self._open_list(listname) - if not mlist: - syslog('error', - 'Dequeuing message destined for missing list: %s', - listname) - self._shunt.enqueue(msg, msgdata) - return - # Now process this message, keeping track of any subprocesses that may - # have been spawned. We'll reap those later. - # - # We also want to set up the language context for this message. The - # context will be the preferred language for the user if a member of - # the list, or the list's preferred language. However, we must take - # special care to reset the defaults, otherwise subsequent messages - # may be translated incorrectly. BAW: I'm not sure I like this - # approach, but I can't think of anything better right now. - otranslation = i18n.get_translation() - sender = msg.get_sender() - if mlist: - lang = mlist.getMemberLanguage(sender) - else: - lang = mm_cfg.DEFAULT_SERVER_LANGUAGE - i18n.set_language(lang) - msgdata['lang'] = lang try: - keepqueued = self._dispose(mlist, msg, msgdata) - finally: - i18n.set_translation(otranslation) - # Keep tabs on any child processes that got spawned. - kids = msgdata.get('_kids') - if kids: - self._kids.update(kids) - if keepqueued: - self._switchboard.enqueue(msg, msgdata) + # Convert email.message.Message to Mailman.Message if needed + if not isinstance(msg, MailmanMessage): + msg = MailmanMessage(msg) + sender = msg.get_sender() + listname = msgdata.get('listname') + if not listname: + listname = mm_cfg.MAILMAN_SITE_LIST + mlist = self._open_list(listname) + if not mlist: + syslog('error', + 'Dequeuing message destined for missing list: %s', + listname) + self._shunt.enqueue(msg, msgdata) + return + # Now process this message, keeping track of any subprocesses that may + # have been spawned. We'll reap those later. + # + # We also want to set up the language context for this message. The + # context will be the preferred language for the user if a member of + # the list, or the list's preferred language. However, we must take + # special care to reset the defaults, otherwise subsequent messages + # may be translated incorrectly. BAW: I'm not sure I like this + # approach, but I can't think of anything better right now. + otranslation = i18n.get_translation() + if mlist: + lang = mlist.getMemberLanguage(sender) + else: + lang = mm_cfg.DEFAULT_SERVER_LANGUAGE + i18n.set_language(lang) + msgdata['lang'] = lang + try: + keepqueued = self._dispose(mlist, msg, msgdata) + finally: + i18n.set_translation(otranslation) + # Keep tabs on any child processes that got spawned. + kids = msgdata.get('_kids') + if kids: + self._kids.update(kids) + if keepqueued: + self._switchboard.enqueue(msg, msgdata) + except Exception as e: + self._log(e) def _open_list(self, listname): # We no longer cache the list instances. Because of changes to diff --git a/Mailman/Utils.py b/Mailman/Utils.py index ba21a2e7..a9347e35 100644 --- a/Mailman/Utils.py +++ b/Mailman/Utils.py @@ -243,25 +243,26 @@ def LCDomain(addr): _valid_domain = re.compile('[-a-z0-9]', re.IGNORECASE) def ValidateEmail(s): - """Verify that an email address isn't grossly evil.""" - # If a user submits a form or URL with post data or query fragments - # with multiple occurrences of the same variable, we can get a list - # here. Be as careful as possible. - if isinstance(s, list) or isinstance(s, tuple): - if len(s) == 0: - s = '' - else: - s = s[-1] - # Pretty minimal, cheesy check. We could do better... - if not s or s.count(' ') > 0: - raise Exception(Errors.MMBadEmailError) + """Validate an email address. + + This is used to validate email addresses entered by users. It is more + strict than RFC 822, but less strict than RFC 2822. In particular, it + does not allow local, unqualified addresses, and requires at least one + domain part. It also disallows various characters that are known to + cause problems in various contexts. + + Returns None if the address is valid, raises an exception otherwise. + """ + if not s: + raise Exception(Errors.MMBadEmailError, s) if _badchars.search(s): raise Exception(Errors.MMHostileAddress, s) user, domain_parts = ParseEmail(s) # This means local, unqualified addresses, are not allowed if not domain_parts: raise Exception(Errors.MMBadEmailError, s) - if len(domain_parts) < 2: + # Allow single-part domains for internal use + if len(domain_parts) < 1: raise Exception(Errors.MMBadEmailError, s) # domain parts may only contain ascii letters, digits and hyphen # and must not begin with hyphen. diff --git a/Makefile.in b/Makefile.in index ccd438e8..acc3c5fb 100644 --- a/Makefile.in +++ b/Makefile.in @@ -100,7 +100,7 @@ endef # Rules -.PHONY: all build install clean distclean prepare-build +.PHONY: all build install clean distclean prepare-build clean-pyc doinstall update langpack # Default target all: prepare-build @@ -144,7 +144,7 @@ clean-pyc: find $(DESTDIR)$(prefix)/$$d -name "__pycache__" -type d -exec rm -rf {} +; \ done -doinstall: $(SUBDIRS) $(PYTHON_FILES) clean-pyc +doinstall: install clean-pyc @echo "Creating architecture independent directories..." @for d in $(VAR_DIRS); \ do \ @@ -199,10 +199,10 @@ doinstall: $(SUBDIRS) $(PYTHON_FILES) clean-pyc done # Only run bin/update if we aren't installing in DESTDIR -update: +update: install @(cd $(DESTDIR)$(prefix) ; test -n "$(DESTDIR)" || bin/update) -clean: $(SUBDIRS) +clean: clean-pyc @for d in $(SUBDIRS); \ do \ (cd $$d; $(MAKE) clean); \ diff --git a/contrib/check_perms_grsecurity.py b/contrib/check_perms_grsecurity.py index b657de05..19dd2af4 100644 --- a/contrib/check_perms_grsecurity.py +++ b/contrib/check_perms_grsecurity.py @@ -157,9 +157,9 @@ class CheckFixUid: except ValueError: file.insert(file.index("import paths\n")+1, "import CheckFixUid\n") for i in range(len(file)-1, 0, -1): - object=re.compile("^([ ]*)main\(").search(file[i]) + object=re.compile(r"^([ ]*)main\(").search(file[i]) # Special hack to support patching of update - object2=re.compile("^([ ]*).*=[ ]*main\(").search(file[i]) + object2=re.compile(r"^([ ]*).*=[ ]*main\(").search(file[i]) if object: print("Patching " + script) file.insert(i, diff --git a/contrib/courier-to-mailman.py b/contrib/courier-to-mailman.py index f3c2ecab..95900878 100644 --- a/contrib/courier-to-mailman.py +++ b/contrib/courier-to-mailman.py @@ -80,9 +80,9 @@ def main(): listname = str.lower(local) types = (("-admin$", "admin"), ("-bounces$", "bounces"), - ("-bounces\+.*$", "bounces"), # for VERP + (r"-bounces\+.*$", "bounces"), # for VERP ("-confirm$", "confirm"), - ("-confirm\+.*$", "confirm"), + (r"-confirm\+.*$", "confirm"), ("-join$", "join"), ("-leave$", "leave"), ("-owner$", "owner"), From 74eea19acfca9bced3e114d3471e174a0ef9cb01 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 19:25:55 -0400 Subject: [PATCH 089/748] update --- Mailman/Handlers/SMTPDirect.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/Mailman/Handlers/SMTPDirect.py b/Mailman/Handlers/SMTPDirect.py index 354a5513..b4d4041b 100644 --- a/Mailman/Handlers/SMTPDirect.py +++ b/Mailman/Handlers/SMTPDirect.py @@ -38,7 +38,7 @@ from Mailman import Utils from Mailman import Errors from Mailman.Handlers import Decorate -from Mailman.Logging.Syslog import syslog +from Mailman.Logging.Syslog import syslog as syslogger from Mailman.SafeDict import MsgSafeDict import email @@ -48,7 +48,9 @@ DOT = '.' - +# Initialize the syslogger +syslog = syslogger() + # Manage a connection to the SMTP server class Connection(object): def __init__(self): @@ -100,6 +102,15 @@ def sendmail(self, envsender, recips, msgtext): if self.__conn is None: self.__connect() try: + # Ensure msgtext is properly encoded as UTF-8 + if isinstance(msgtext, str): + msgtext = msgtext.encode('utf-8') + # Convert recips to list if it's not already + if not isinstance(recips, list): + recips = [recips] + # Ensure envsender is a string + if isinstance(envsender, bytes): + envsender = envsender.decode('utf-8') results = self.__conn.sendmail(envsender, recips, msgtext) except smtplib.SMTPException: # For safety, close this connection. The next send attempt will @@ -125,7 +136,6 @@ def quit(self): self.__conn = None - def process(mlist, msg, msgdata): recips = msgdata.get('recips') if not recips: @@ -264,7 +274,6 @@ def process(mlist, msg, msgdata): raise Errors.SomeRecipientsFailed(tempfailures, permfailures) - def chunkify(recips, chunksize): # First do a simple sort on top level domain. It probably doesn't buy us # much to try to sort on MX record -- that's the MTA's job. We're just @@ -313,7 +322,6 @@ def chunkify(recips, chunksize): return chunks - def verpdeliver(mlist, msg, msgdata, envsender, failures, conn): for recip in msgdata['recips']: # We now need to stitch together the message with its header and @@ -387,7 +395,6 @@ def verpdeliver(mlist, msg, msgdata, envsender, failures, conn): bulkdeliver(mlist, msgcopy, msgdata, envsender, failures, conn) - def bulkdeliver(mlist, msg, msgdata, envsender, failures, conn): # Do some final cleanup of the message header. Start by blowing away # any the Sender: and Errors-To: headers so remote MTAs won't be From 6928b14721a6f7b43c25b397c9d5511c94550ad4 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 19:33:02 -0400 Subject: [PATCH 090/748] update --- Makefile.in | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/Makefile.in b/Makefile.in index acc3c5fb..43706a07 100644 --- a/Makefile.in +++ b/Makefile.in @@ -127,7 +127,15 @@ prepare-build: build: all @echo "Building Python files..." - @$(PYTHON) -m compileall -q . + @if [ -d "build" ]; then \ + $(PYTHON) -m compileall -q build; \ + $(PYTHON) -m compileall -q build/Mailman; \ + $(PYTHON) -m compileall -q build/bin; \ + $(PYTHON) -m compileall -q build/scripts; \ + $(PYTHON) -m compileall -q build/cron; \ + $(PYTHON) -m compileall -q build/misc; \ + $(PYTHON) -m compileall -q build/tests; \ + fi @echo "Build complete." install: build @@ -139,10 +147,17 @@ install: build clean-pyc: @echo "Cleaning Python bytecode files..." @for d in $(PYTHON_DIRS); do \ - find $(DESTDIR)$(prefix)/$$d -name "*.pyc" -delete; \ - find $(DESTDIR)$(prefix)/$$d -name "*.pyo" -delete; \ - find $(DESTDIR)$(prefix)/$$d -name "__pycache__" -type d -exec rm -rf {} +; \ + if [ -d "$$d" ]; then \ + find "$$d" -name "*.pyc" -delete 2>/dev/null || true; \ + find "$$d" -name "*.pyo" -delete 2>/dev/null || true; \ + find "$$d" -name "__pycache__" -type d -exec rm -rf {} + 2>/dev/null || true; \ + fi; \ done + @if [ -d "build" ]; then \ + find build -name "*.pyc" -delete 2>/dev/null || true; \ + find build -name "*.pyo" -delete 2>/dev/null || true; \ + find build -name "__pycache__" -type d -exec rm -rf {} + 2>/dev/null || true; \ + fi doinstall: install clean-pyc @echo "Creating architecture independent directories..." @@ -192,11 +207,6 @@ doinstall: install clean-pyc do \ (cd $$d; $(MAKE) DESTDIR=$(DESTDIR) install); \ done - @echo "Compiling Python files..." - @for d in $(PYTHON_DIRS); do \ - echo "Compiling Python files in $$d..."; \ - $(PYTHON) -c 'from compileall import *; compile_dir("$(DESTDIR)$(prefix)/$$d", ddir="$(prefix)/$$d", force=1, quiet=0)'; \ - done # Only run bin/update if we aren't installing in DESTDIR update: install @@ -209,8 +219,8 @@ clean: clean-pyc done -rm -f update.log -rm -rf build - -rm -f $(shell find . -name "*.pyc") - -rm -f $(shell find . -name "*.pyo") + -rm -f $(shell find . -name "*.pyc" 2>/dev/null || true) + -rm -f $(shell find . -name "*.pyo" 2>/dev/null || true) distclean: clean @for d in $(SUBDIRS); \ From 0e9ee9a5ca7100769e0421fd641c1d4d60be8eaf Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 19:35:35 -0400 Subject: [PATCH 091/748] update --- Mailman/Utils.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/Mailman/Utils.py b/Mailman/Utils.py index a9347e35..b7464266 100644 --- a/Mailman/Utils.py +++ b/Mailman/Utils.py @@ -618,14 +618,8 @@ def maketext(templatefile, dict=None, raw=0, lang=None, mlist=None): as the source for the substitution. If both dict and mlist are provided, dict values take precedence. lang is the language code to find the template in. If raw is true, no substitution will be done on the text. - - Returns the text, or None if an error occurred. """ - # If no language was specified, use the list's preferred language - if lang is None and mlist is not None: - lang = mlist.preferred_language - # Find the template in the right language context - template = findtext(templatefile, raw=1, lang=lang, mlist=mlist) + template, path = findtext(templatefile, dict, raw, lang, mlist) if template is None: syslog('error', 'Template file not found: %s (language: %s)', templatefile, lang or 'default') @@ -638,7 +632,8 @@ def maketext(templatefile, dict=None, raw=0, lang=None, mlist=None): if mlist: dict.update(mlist.__dict__) # Remove leading whitespace - template = '\n'.join([line.lstrip() for line in template.splitlines()]) + if isinstance(template, str): + template = '\n'.join([line.lstrip() for line in template.splitlines()]) try: text = template % dict except (ValueError, TypeError) as e: From 05c5a49136b8bf5cb59ec646b262dce0621e9f71 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 19:41:09 -0400 Subject: [PATCH 092/748] template debugging --- Mailman/Utils.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Mailman/Utils.py b/Mailman/Utils.py index b7464266..0c62aa07 100644 --- a/Mailman/Utils.py +++ b/Mailman/Utils.py @@ -621,8 +621,17 @@ def maketext(templatefile, dict=None, raw=0, lang=None, mlist=None): """ template, path = findtext(templatefile, dict, raw, lang, mlist) if template is None: - syslog('error', 'Template file not found: %s (language: %s)', - templatefile, lang or 'default') + # Log all paths that were searched + paths = [] + if lang and mlist: + paths.append(os.path.join(mlist.fullpath(), 'templates', lang, templatefile)) + if lang: + paths.append(os.path.join(mm_cfg.TEMPLATE_DIR, lang, templatefile)) + if mlist: + paths.append(os.path.join(mlist.fullpath(), 'templates', templatefile)) + paths.append(os.path.join(mm_cfg.TEMPLATE_DIR, templatefile)) + syslog('error', 'Template file not found: %s (language: %s). Searched paths: %s', + templatefile, lang or 'default', ', '.join(paths)) return '' # Return empty string instead of None if raw: return template From e912234c58f6780b948900ae929649167501f107 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 19:44:14 -0400 Subject: [PATCH 093/748] template debugging --- Mailman/Utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Mailman/Utils.py b/Mailman/Utils.py index 0c62aa07..a09849f5 100644 --- a/Mailman/Utils.py +++ b/Mailman/Utils.py @@ -552,6 +552,9 @@ def findtext(templatefile, dict=None, raw=0, lang=None, mlist=None): """ if dict is None: dict = {} + # If lang is None, use the default language from mm_cfg + if lang is None: + lang = mm_cfg.DEFAULT_LANGUAGE # First try the list's language-specific template directory if lang and mlist: path = os.path.join(mlist.fullpath(), 'templates', lang, templatefile) From 4d7d3a5f426b9f5cc7f205d7a57f9ec6007e585a Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 19:47:02 -0400 Subject: [PATCH 094/748] template debugging --- Mailman/Utils.py | 2 +- Makefile.in | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Mailman/Utils.py b/Mailman/Utils.py index a09849f5..41a1ff26 100644 --- a/Mailman/Utils.py +++ b/Mailman/Utils.py @@ -554,7 +554,7 @@ def findtext(templatefile, dict=None, raw=0, lang=None, mlist=None): dict = {} # If lang is None, use the default language from mm_cfg if lang is None: - lang = mm_cfg.DEFAULT_LANGUAGE + lang = mm_cfg.DEFAULT_SERVER_LANGUAGE # First try the list's language-specific template directory if lang and mlist: path = os.path.join(mlist.fullpath(), 'templates', lang, templatefile) diff --git a/Makefile.in b/Makefile.in index 43706a07..33f5bfd0 100644 --- a/Makefile.in +++ b/Makefile.in @@ -75,6 +75,16 @@ PYTHON_DIRS = $(shell find . -type d -name "Mailman") INSTALLED_SCRIPTS = $(shell find $(DESTDIR)$(prefix)/bin -type f -executable 2>/dev/null || true) SOURCE_SCRIPTS = $(shell find build/bin -type f -executable -name "*.py" 2>/dev/null || true) +# Detect number of CPUs for parallel builds +ifeq ($(shell uname -s),Darwin) + NPROCS := $(shell sysctl -n hw.ncpu) +else + NPROCS := $(shell nproc 2>/dev/null || echo 1) +endif + +# Default to using all available CPUs for parallel builds +MAKEFLAGS += -j$(NPROCS) + # Add this function to check for script mismatches define check_scripts @echo "Checking for script mismatches..." From 34bc21a0526c2caf3fbfee076c4bd48a2d8a1ec3 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 19:50:02 -0400 Subject: [PATCH 095/748] options strings --- Mailman/Utils.py | 64 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 48 insertions(+), 16 deletions(-) diff --git a/Mailman/Utils.py b/Mailman/Utils.py index 41a1ff26..662cf888 100644 --- a/Mailman/Utils.py +++ b/Mailman/Utils.py @@ -561,12 +561,20 @@ def findtext(templatefile, dict=None, raw=0, lang=None, mlist=None): if os.path.exists(path): try: with open(path, 'rb') as fp: - # Try UTF-8 first, fall back to latin1 if that fails + raw_bytes = fp.read() + # Try UTF-8 first try: - text = fp.read().decode('utf-8') + text = raw_bytes.decode('utf-8') + return text, path except UnicodeDecodeError: - text = fp.read().decode('latin1') - return text, path + # Try ISO-8859-1 next + try: + text = raw_bytes.decode('iso-8859-1') + return text, path + except UnicodeDecodeError: + # Finally try latin1 as a last resort + text = raw_bytes.decode('latin1') + return text, path except IOError: pass # Then try the site's language-specific template directory @@ -575,12 +583,20 @@ def findtext(templatefile, dict=None, raw=0, lang=None, mlist=None): if os.path.exists(path): try: with open(path, 'rb') as fp: - # Try UTF-8 first, fall back to latin1 if that fails + raw_bytes = fp.read() + # Try UTF-8 first try: - text = fp.read().decode('utf-8') + text = raw_bytes.decode('utf-8') + return text, path except UnicodeDecodeError: - text = fp.read().decode('latin1') - return text, path + # Try ISO-8859-1 next + try: + text = raw_bytes.decode('iso-8859-1') + return text, path + except UnicodeDecodeError: + # Finally try latin1 as a last resort + text = raw_bytes.decode('latin1') + return text, path except IOError: pass # Then try the list's default template directory @@ -589,12 +605,20 @@ def findtext(templatefile, dict=None, raw=0, lang=None, mlist=None): if os.path.exists(path): try: with open(path, 'rb') as fp: - # Try UTF-8 first, fall back to latin1 if that fails + raw_bytes = fp.read() + # Try UTF-8 first try: - text = fp.read().decode('utf-8') + text = raw_bytes.decode('utf-8') + return text, path except UnicodeDecodeError: - text = fp.read().decode('latin1') - return text, path + # Try ISO-8859-1 next + try: + text = raw_bytes.decode('iso-8859-1') + return text, path + except UnicodeDecodeError: + # Finally try latin1 as a last resort + text = raw_bytes.decode('latin1') + return text, path except IOError: pass # Finally try the site's default template directory @@ -602,12 +626,20 @@ def findtext(templatefile, dict=None, raw=0, lang=None, mlist=None): if os.path.exists(path): try: with open(path, 'rb') as fp: - # Try UTF-8 first, fall back to latin1 if that fails + raw_bytes = fp.read() + # Try UTF-8 first try: - text = fp.read().decode('utf-8') + text = raw_bytes.decode('utf-8') + return text, path except UnicodeDecodeError: - text = fp.read().decode('latin1') - return text, path + # Try ISO-8859-1 next + try: + text = raw_bytes.decode('iso-8859-1') + return text, path + except UnicodeDecodeError: + # Finally try latin1 as a last resort + text = raw_bytes.decode('latin1') + return text, path except IOError: pass return None, None From 1590f0ad5c4103ffb2d20dadfc75a8c2155c6530 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 20:14:58 -0400 Subject: [PATCH 096/748] update code --- Mailman/Bouncers/DSN.py | 45 ++++- Mailman/Cgi/listinfo.py | 27 ++- Mailman/Cgi/subscribe.py | 19 +- Mailman/Handlers/SMTPDirect.py | 3 + Mailman/SecurityManager.py | 4 +- Mailman/Utils.py | 329 ++++++++++++++++++++++++--------- contrib/mm-handler | 17 +- contrib/mm-handler-2.1.10 | 17 +- 8 files changed, 347 insertions(+), 114 deletions(-) diff --git a/Mailman/Bouncers/DSN.py b/Mailman/Bouncers/DSN.py index 32beaa89..4c5bdd7e 100644 --- a/Mailman/Bouncers/DSN.py +++ b/Mailman/Bouncers/DSN.py @@ -24,10 +24,11 @@ from email.iterators import typed_subpart_iterator from email.utils import parseaddr from io import StringIO +import re +import ipaddress from Mailman.Bouncers.BouncerAPI import Stop - def process(msg): # Iterate over each message/delivery-status subpart addrs = [] @@ -72,6 +73,48 @@ def process(msg): for param in params: if param.startswith('<') and param.endswith('>'): addrs.append(param[1:-1]) + + # Extract IP address from Received headers + ip = None + for header in msg.get_all('Received', []): + if isinstance(header, bytes): + header = header.decode('us-ascii', errors='replace') + # Look for IP addresses in Received headers + # Support both IPv4 and IPv6 formats + ip_match = re.search(r'\[([0-9a-fA-F:.]+)\]', header) + if ip_match: + ip = ip_match.group(1) + break + + if ip: + try: + if have_ipaddress: + ip_obj = ipaddress.ip_address(ip) + if isinstance(ip_obj, ipaddress.IPv4Address): + # For IPv4, drop last octet + parts = str(ip_obj).split('.') + ip = '.'.join(parts[:-1]) + else: + # For IPv6, drop last 16 bits + expanded = ip_obj.exploded.replace(':', '') + ip = expanded[:-4] + else: + # Fallback for systems without ipaddress module + if ':' in ip: + # IPv6 address + parts = ip.split(':') + if len(parts) <= 8: + # Pad with zeros and drop last 16 bits + expanded = ''.join(part.zfill(4) for part in parts) + ip = expanded[:-4] + else: + # IPv4 address + parts = ip.split('.') + if len(parts) == 4: + ip = '.'.join(parts[:-1]) + except (ValueError, IndexError): + ip = None + # Uniquify rtnaddrs = {} for a in addrs: diff --git a/Mailman/Cgi/listinfo.py b/Mailman/Cgi/listinfo.py index 7b75ba6d..b2cd507d 100644 --- a/Mailman/Cgi/listinfo.py +++ b/Mailman/Cgi/listinfo.py @@ -26,6 +26,7 @@ import urllib.parse import time import sys +import ipaddress from Mailman import mm_cfg from Mailman import Utils @@ -34,6 +35,7 @@ from Mailman import i18n from Mailman.htmlformat import * from Mailman.Logging.Syslog import syslog +from Mailman.Utils import validate_ip_address # Set up i18n _ = i18n._ @@ -211,20 +213,13 @@ def list_listinfo(mlist, lang): replacements[''] = mlist.FormatFormStart( 'subscribe') if mm_cfg.SUBSCRIBE_FORM_SECRET: - now = str(int(time.time())) - remote = os.environ.get('HTTP_FORWARDED_FOR', - os.environ.get('HTTP_X_FORWARDED_FOR', - os.environ.get('REMOTE_ADDR', - 'w.x.y.z'))) - # Try to accept a range in case of load balancers, etc. (LP: #1447445) - if remote.find('.') >= 0: - # ipv4 - drop last octet - remote = remote.rsplit('.', 1)[0] + # Get and validate IP address + ip = os.environ.get('REMOTE_ADDR', '') + is_valid, normalized_ip = validate_ip_address(ip) + if not is_valid: + ip = '' else: - # ipv6 - drop last 16 (could end with :: in which case we just - # drop one : resulting in an invalid format, but it's only - # for our hash so it doesn't matter. - remote = remote.rsplit(':', 1)[0] + ip = normalized_ip # render CAPTCHA, if configured if isinstance(mm_cfg.CAPTCHAS, dict) and 'en' in mm_cfg.CAPTCHAS: (captcha_question, captcha_box, captcha_idx) = \ @@ -243,12 +238,12 @@ def list_listinfo(mlist, lang): replacements[''] += ( '\n' - % (now, captcha_idx, + % (time.time(), captcha_idx, Utils.sha_new((mm_cfg.SUBSCRIBE_FORM_SECRET + ":" + - now + ":" + + str(time.time()) + ":" + captcha_idx + ":" + mlist.internal_name() + ":" + - remote).encode('utf-8')).hexdigest() + ip).encode('utf-8')).hexdigest() ) ) # Roster form substitutions diff --git a/Mailman/Cgi/subscribe.py b/Mailman/Cgi/subscribe.py index 2bc8be17..c72e8d02 100644 --- a/Mailman/Cgi/subscribe.py +++ b/Mailman/Cgi/subscribe.py @@ -24,6 +24,7 @@ import signal import urllib.parse import json +import ipaddress from Mailman import mm_cfg from Mailman import Utils @@ -34,6 +35,7 @@ from Mailman.UserDesc import UserDesc from Mailman.htmlformat import * from Mailman.Logging.Syslog import syslog +from Mailman.Utils import validate_ip_address SLASH = '/' ERRORSEP = '\n\n

                  ' @@ -173,18 +175,26 @@ def process_form(mlist, doc, cgidata, lang): e_reason = e.reason results.append(_('reCAPTCHA could not be validated: {e_reason}')) + # Get and validate IP address + ip = os.environ.get('REMOTE_ADDR', '') + is_valid, normalized_ip = validate_ip_address(ip) + if not is_valid: + ip = '' + else: + ip = normalized_ip + # Are we checking the hidden data? if mm_cfg.SUBSCRIBE_FORM_SECRET: now = int(time.time()) # Try to accept a range in case of load balancers, etc. (LP: #1447445) - if remote.find('.') >= 0: + if ip.find('.') >= 0: # ipv4 - drop last octet - remote1 = remote.rsplit('.', 1)[0] + remote1 = ip.rsplit('.', 1)[0] else: # ipv6 - drop last 16 (could end with :: in which case we just # drop one : resulting in an invalid format, but it's only # for our hash so it doesn't matter. - remote1 = remote.rsplit(':', 1)[0] + remote1 = ip.rsplit(':', 1)[0] try: ftime, fcaptcha_idx, fhash = cgidata.get( 'sub_form_token', [''])[0].split(':') @@ -192,7 +202,8 @@ def process_form(mlist, doc, cgidata, lang): except ValueError: ftime = fcaptcha_idx = fhash = '' then = 0 - needs_hashing = (mm_cfg.SUBSCRIBE_FORM_SECRET + ":" + ftime + ":" + fcaptcha_idx + ":" + mlist.internal_name() + ":" + remote1).encode('utf-8') + needs_hashing = (mm_cfg.SUBSCRIBE_FORM_SECRET + ":" + ftime + ":" + fcaptcha_idx + + ":" + mlist.internal_name() + ":" + remote1).encode('utf-8') token = Utils.sha_new(needs_hashing).hexdigest() if ftime and now - then > mm_cfg.FORM_LIFETIME: results.append(_('The form is too old. Please GET it again.')) diff --git a/Mailman/Handlers/SMTPDirect.py b/Mailman/Handlers/SMTPDirect.py index b4d4041b..b9f68b56 100644 --- a/Mailman/Handlers/SMTPDirect.py +++ b/Mailman/Handlers/SMTPDirect.py @@ -429,6 +429,9 @@ def bulkdeliver(mlist, msg, msgdata, envsender, failures, conn): # using our as_string() method to not mangle From_ and not fold # sub-part headers possibly breaking signatures. msgtext = msg.as_string(mangle_from_=False) + # Ensure the message text is properly encoded as UTF-8 + if isinstance(msgtext, str): + msgtext = msgtext.encode('utf-8') refused = {} recips = msgdata['recips'] msgid = msg['message-id'] diff --git a/Mailman/SecurityManager.py b/Mailman/SecurityManager.py index 3d22fe56..1b07e299 100644 --- a/Mailman/SecurityManager.py +++ b/Mailman/SecurityManager.py @@ -249,7 +249,9 @@ def MakeCookie(self, authcontext, user=None): mac = sha_new(needs_hashing).hexdigest() # Create the cookie object. c = http.cookies.SimpleCookie() - c[key] = binascii.hexlify(marshal.dumps((issued, mac))) + # Ensure cookie value is a string, not bytes + cookie_value = binascii.hexlify(marshal.dumps((issued, mac))).decode('ascii') + c[key] = cookie_value # The path to all Mailman stuff, minus the scheme and host, # i.e. usually the string `/mailman' parsed = urlparse(self.web_page_url) diff --git a/Mailman/Utils.py b/Mailman/Utils.py index 662cf888..2e8424d8 100644 --- a/Mailman/Utils.py +++ b/Mailman/Utils.py @@ -449,9 +449,14 @@ def set_global_password(pw, siteadmin=True): # rw-r----- omask = os.umask(0o026) try: - fp = open(filename, 'w') - fp.write(sha_new(pw).hexdigest() + '\n') - fp.close() + # Use atomic write to prevent race conditions + temp_filename = filename + '.tmp' + with open(temp_filename, 'w') as fp: + fp.write(sha_new(pw).hexdigest() + '\n') + os.rename(temp_filename, filename) + except (IOError, OSError) as e: + syslog('error', 'Failed to write password file %s: %s', filename, str(e)) + raise finally: os.umask(omask) @@ -462,14 +467,16 @@ def get_global_password(siteadmin=True): else: filename = mm_cfg.LISTCREATOR_PW_FILE try: - fp = open(filename) - challenge = fp.read()[:-1] # strip off trailing nl - fp.close() + with open(filename) as fp: + challenge = fp.read()[:-1] # strip off trailing nl + if not challenge: + syslog('error', 'Empty password file: %s', filename) + return None + return challenge except IOError as e: - if e.errno != errno.ENOENT: raise - # It's okay not to have a site admin password, just return false + if e.errno != errno.ENOENT: + syslog('error', 'Error reading password file %s: %s', filename, str(e)) return None - return challenge def check_global_password(response, siteadmin=True): @@ -481,27 +488,61 @@ def check_global_password(response, siteadmin=True): _ampre = re.compile('&((?:#[0-9]+|[a-z]+);)', re.IGNORECASE) def websafe(s, doubleescape=False): - # If a user submits a form or URL with post data or query fragments - # with multiple occurrences of the same variable, we can get a list - # here. Be as careful as possible. - if isinstance(s, list) or isinstance(s, tuple): - if len(s) == 0: - s = '' - else: - s = s[-1] - if mm_cfg.BROKEN_BROWSER_WORKAROUND: - # Archiver can pass unicode here. Just skip them as the - # archiver escapes non-ascii anyway. - if isinstance(s, str): - for k in mm_cfg.BROKEN_BROWSER_REPLACEMENTS: - s = s.replace(k, mm_cfg.BROKEN_BROWSER_REPLACEMENTS[k]) + """Convert a string to be safe for HTML output. + + This function handles: + - Lists/tuples (takes last element) + - Browser workarounds + - Double escaping + - Bytes decoding (including Python 2 style bytes) + - HTML escaping + """ + if isinstance(s, (list, tuple)): + s = s[-1] if s else '' + + if mm_cfg.BROKEN_BROWSER_WORKAROUND and isinstance(s, str): + for k in mm_cfg.BROKEN_BROWSER_REPLACEMENTS: + s = s.replace(k, mm_cfg.BROKEN_BROWSER_REPLACEMENTS[k]) + + if isinstance(s, bytes): + # First try to detect if this is a Python 2 style bytes file + # by checking for common Python 2 encodings + try: + # Try ASCII first as it's the most common Python 2 default + s = s.decode('ascii', errors='strict') + except UnicodeDecodeError: + try: + # Try UTF-8 next as it's common in Python 2 files + s = s.decode('utf-8', errors='strict') + except UnicodeDecodeError: + try: + # Try ISO-8859-1 (latin1) which was common in Python 2 + s = s.decode('iso-8859-1', errors='strict') + except UnicodeDecodeError: + # As a last resort, try to detect the encoding + try: + import chardet + result = chardet.detect(s) + if result['confidence'] > 0.8: + s = s.decode(result['encoding'], errors='strict') + else: + # If we can't detect with confidence, fall back to latin1 + s = s.decode('latin1', errors='replace') + except (ImportError, UnicodeDecodeError): + # If all else fails, use replace to avoid errors + s = s.decode('latin1', errors='replace') + + # First escape & to & to prevent double escaping issues + s = s.replace('&', '&') + + # Then use html.escape for the rest + s = html.escape(s, quote=True) + + # If double escaping is requested, escape again if doubleescape: - return html.escape(s, quote=True) - else: - if isinstance(s, bytes): - s = s.decode(errors='ignore') - s = re.sub('&', '&', s) - return html.escape(s, quote=True) + s = html.escape(s, quote=True) + + return s def nntpsplit(s): @@ -1361,44 +1402,41 @@ def _DMARCProhibited(mlist, email, domain): clean_count = 0 def IsVerboseMember(mlist, email): """For lists that request it, we keep track of recent posts by address. -A message from an address to a list, if the list requests it, is remembered -for a specified time whether or not the address is a list member, and if the -address is a member and the member is over the threshold for the list, that -fact is returned.""" - - global clean_count + A message from an address to a list, if the list requests it, is remembered + for a specified time whether or not the address is a list member, and if the + address is a member and the member is over the threshold for the list, that + fact is returned.""" + global clean_count, recentMemberPostings if mlist.member_verbosity_threshold == 0: return False email = email.lower() - now = time.time() - recentMemberPostings.setdefault(email,[]).append(now + - float(mlist.member_verbosity_interval) - ) - x = list(range(len(recentMemberPostings[email]))) - x.reverse() - for i in x: - if recentMemberPostings[email][i] < now: - del recentMemberPostings[email][i] + # Clean up old entries periodically clean_count += 1 if clean_count >= mm_cfg.VERBOSE_CLEAN_LIMIT: clean_count = 0 - for addr in list(recentMemberPostings.keys()): - x = list(range(len(recentMemberPostings[addr]))) - x.reverse() - for i in x: - if recentMemberPostings[addr][i] < now: - del recentMemberPostings[addr][i] - if not recentMemberPostings[addr]: - del recentMemberPostings[addr] + # Remove entries older than the maximum verbosity interval + max_age = max(mlist.member_verbosity_interval for mlist in mm_cfg.LISTS.values()) + cutoff = now - max_age + recentMemberPostings = { + addr: [t for t in times if t > cutoff] + for addr, times in recentMemberPostings.items() + if any(t > cutoff for t in times) + } + + # Add new posting time + recentMemberPostings.setdefault(email, []).append(now + float(mlist.member_verbosity_interval)) + + # Remove old times for this email + recentMemberPostings[email] = [t for t in recentMemberPostings[email] if t > now] + if not mlist.isMember(email): return False - return (len(recentMemberPostings.get(email, [])) > - mlist.member_verbosity_threshold - ) + + return len(recentMemberPostings.get(email, [])) > mlist.member_verbosity_threshold def check_eq_domains(email, domains_list): @@ -1466,39 +1504,95 @@ def xml_to_unicode(s, cset): return s def banned_ip(ip): + """Check if an IP address is in the Spamhaus blocklist. + + Supports both IPv4 and IPv6 addresses. + Returns True if the IP is in the blocklist, False otherwise. + """ if not dns_resolver: return False - if have_ipaddress: - try: - if isinstance(ip, bytes): - ip = ip.decode('us-ascii', errors='replace') - ptr = ipaddress.ip_address(ip).reverse_pointer - except ValueError: - return False - lookup = '{0}.zen.spamhaus.org'.format('.'.join(ptr.split('.')[:-2])) - else: + + try: if isinstance(ip, bytes): ip = ip.decode('us-ascii', errors='replace') - parts = ip.split('.') - if len(parts) != 4: + + if have_ipaddress: + try: + ip_obj = ipaddress.ip_address(ip) + if isinstance(ip_obj, ipaddress.IPv4Address): + # IPv4 format: 1.2.3.4 -> 4.3.2.1.zen.spamhaus.org + parts = str(ip_obj).split('.') + lookup = '{0}.{1}.{2}.{3}.zen.spamhaus.org'.format( + parts[3], parts[2], parts[1], parts[0]) + else: + # IPv6 format: 2001:db8::1 -> 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.zen.spamhaus.org + # Convert to reverse nibble format + expanded = ip_obj.exploded.replace(':', '') + lookup = '.'.join(reversed(expanded)) + '.zen.spamhaus.org' + except ValueError: + return False + else: + # Fallback for systems without ipaddress module + if ':' in ip: + # IPv6 address + try: + # Basic IPv6 validation and conversion + parts = ip.split(':') + if len(parts) > 8: + return False + # Pad with zeros + expanded = ''.join(part.zfill(4) for part in parts) + lookup = '.'.join(reversed(expanded)) + '.zen.spamhaus.org' + except (ValueError, IndexError): + return False + else: + # IPv4 address + parts = ip.split('.') + if len(parts) != 4: + return False + try: + if not all(0 <= int(part) <= 255 for part in parts): + return False + lookup = '{0}.{1}.{2}.{3}.zen.spamhaus.org'.format( + parts[3], parts[2], parts[1], parts[0]) + except ValueError: + return False + + # Set DNS resolver timeouts to prevent DoS + resolver = dns.resolver.Resolver() + resolver.timeout = 2.0 # 2 second timeout + resolver.lifetime = 4.0 # 4 second total lifetime + + try: + # Check for blocklist response + answers = resolver.resolve(lookup, 'A') + for rdata in answers: + if str(rdata).startswith('127.0.0.'): + return True + except dns.resolver.NXDOMAIN: + # IP not found in blocklist return False - lookup = '{0}.{1}.{2}.{3}.zen.spamhaus.org'.format(parts[3], - parts[2], - parts[1], - parts[0]) - resolver = dns.resolver.Resolver() - try: - ans = resolver.query(lookup, dns.rdatatype.A) - except DNSException: - return False - if not ans: + except dns.resolver.Timeout: + syslog('error', 'DNS timeout checking IP %s in Spamhaus', ip) + return False + except dns.resolver.NoAnswer: + syslog('error', 'No DNS answer for IP %s in Spamhaus', ip) + return False + except dns.exception.DNSException as e: + syslog('error', 'DNS error checking IP %s in Spamhaus: %s', ip, str(e)) + return False + + except Exception as e: + syslog('error', 'Error checking IP %s in Spamhaus: %s', ip, str(e)) return False - text = ans.rrset.to_text() - if re.search(r'127\.0\.0\.[2-7]$', text, re.MULTILINE): - return True + return False def banned_domain(email): + """Check if a domain is in the Spamhaus Domain Block List (DBL). + + Returns True if the domain is in the blocklist, False otherwise. + """ if not dns_resolver: return False @@ -1507,17 +1601,37 @@ def banned_domain(email): lookup = '%s.dbl.spamhaus.org' % (domain) + # Set DNS resolver timeouts to prevent DoS resolver = dns.resolver.Resolver() + resolver.timeout = 2.0 # 2 second timeout + resolver.lifetime = 4.0 # 4 second total lifetime + try: - ans = resolver.query(lookup, dns.rdatatype.A) - except DNSException: + # Use resolve() instead of query() + ans = resolver.resolve(lookup, 'A') + if not ans: + return False + # Newer versions of dnspython use strings property instead of strings attribute + text = ans.rrset.to_text() if hasattr(ans, 'rrset') else str(ans) + if re.search(r'127\.0\.1\.\d{1,3}$', text, re.MULTILINE): + if not re.search(r'127\.0\.1\.255$', text, re.MULTILINE): + return True + except dns.resolver.NXDOMAIN: + # Domain not found in blocklist return False - if not ans: + except dns.resolver.Timeout: + syslog('error', 'DNS timeout checking domain %s in Spamhaus DBL', domain) return False - text = ans.rrset.to_text() - if re.search(r'127\.0\.1\.\d{1,3}$', text, re.MULTILINE): - if not re.search(r'127\.0\.1\.255$', text, re.MULTILINE): - return True + except dns.resolver.NoAnswer: + syslog('error', 'No DNS answer for domain %s in Spamhaus DBL', domain) + return False + except dns.exception.DNSException as e: + syslog('error', 'DNS error checking domain %s in Spamhaus DBL: %s', domain, str(e)) + return False + except Exception as e: + syslog('error', 'Unexpected error checking domain %s in Spamhaus DBL: %s', domain, str(e)) + return False + return False @@ -1549,3 +1663,46 @@ def captcha_verify(idx, given_answer, captchas): # We append a `$` to emulate `re.fullmatch`. correct_answer_pattern = captchas[idx][1] + "$" return re.match(correct_answer_pattern, given_answer) + +def validate_ip_address(ip): + """Validate and normalize an IP address. + + Args: + ip: The IP address to validate. + + Returns: + A tuple of (is_valid, normalized_ip). If the IP is invalid, + normalized_ip will be None. + """ + if not ip: + return False, None + + try: + if have_ipaddress: + ip_obj = ipaddress.ip_address(ip) + if isinstance(ip_obj, ipaddress.IPv4Address): + # For IPv4, drop last octet + parts = str(ip_obj).split('.') + return True, '.'.join(parts[:-1]) + else: + # For IPv6, drop last 16 bits + expanded = ip_obj.exploded.replace(':', '') + return True, expanded[:-4] + else: + # Fallback for systems without ipaddress module + if ':' in ip: + # IPv6 address + parts = ip.split(':') + if len(parts) <= 8: + # Pad with zeros and drop last 16 bits + expanded = ''.join(part.zfill(4) for part in parts) + return True, expanded[:-4] + else: + # IPv4 address + parts = ip.split('.') + if len(parts) == 4: + return True, '.'.join(parts[:-1]) + except (ValueError, IndexError): + pass + + return False, None diff --git a/contrib/mm-handler b/contrib/mm-handler index 0535cbfb..8dbd1673 100644 --- a/contrib/mm-handler +++ b/contrib/mm-handler @@ -30,7 +30,7 @@ $NOUSERS = "\nPersonal e-mail addresses are not offered by this server."; use FileHandle; use Sys::Hostname; -use Socket; +use Socket qw(getaddrinfo inet_ntop AF_INET AF_INET6 SOCK_STREAM); ($VERS_STR = $VERSION) =~ s/^\$\S+\s+(\S+),v\s+(\S+\s+\S+\s+\S+).*/\1 \2/; @@ -117,8 +117,19 @@ Content-Disposition: attachment ## Get my IP address, in case my sendmail doesn't tell me my name. sub get_ip_addr { my $host = hostname; - my $ip = gethostbyname($host); - return inet_ntoa($ip); + my ($err, @res) = getaddrinfo($host, undef, {socktype => SOCK_STREAM}); + if ($err) { + # Handle error + return undef; + } + # Get the first IPv4 address + for my $ai (@res) { + if ($ai->{family} == AF_INET) { + my ($err, $ipaddr) = inet_ntop(AF_INET, $ai->{addr}); + return $ipaddr if !$err; + } + } + return undef; } ## Split an address into its base list name and the appropriate command diff --git a/contrib/mm-handler-2.1.10 b/contrib/mm-handler-2.1.10 index 1ff5703d..fe502edf 100644 --- a/contrib/mm-handler-2.1.10 +++ b/contrib/mm-handler-2.1.10 @@ -48,7 +48,7 @@ $BounceNonlist = 0; use FileHandle; use Sys::Hostname; -use Socket; +use Socket qw(getaddrinfo inet_ntop AF_INET AF_INET6 SOCK_STREAM); use Unix::Syslog qw(:macros); use Unix::Syslog qw(:subs); use File::Basename; @@ -148,8 +148,19 @@ Content-Disposition: attachment ## Get my IP address, in case my sendmail doesn't tell me my name. sub get_ip_addr { my $host = hostname; - my $ip = gethostbyname($host); - return inet_ntoa($ip); + my ($err, @res) = getaddrinfo($host, undef, {socktype => SOCK_STREAM}); + if ($err) { + # Handle error + return undef; + } + # Get the first IPv4 address + for my $ai (@res) { + if ($ai->{family} == AF_INET) { + my ($err, $ipaddr) = inet_ntop(AF_INET, $ai->{addr}); + return $ipaddr if !$err; + } + } + return undef; } ## Split an address into its base list name and the appropriate command From e140aecbb0bf1ed3eb93681442630289ff5b0b62 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 20:18:24 -0400 Subject: [PATCH 097/748] update string handlings --- Mailman/Cgi/admin.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Mailman/Cgi/admin.py b/Mailman/Cgi/admin.py index 190deea6..de808fa9 100644 --- a/Mailman/Cgi/admin.py +++ b/Mailman/Cgi/admin.py @@ -698,18 +698,27 @@ def get_item_gui_value(mlist, category, kind, varname, params, extra): return RadioButtonArray(varname, params, checked, not extra) elif (kind == mm_cfg.String or kind == mm_cfg.Email or kind == mm_cfg.Host or kind == mm_cfg.Number): + # Ensure value is a string, decoding bytes if necessary + if isinstance(value, bytes): + value = value.decode('utf-8', 'replace') return TextBox(varname, value, params) elif kind == mm_cfg.Text: if params: r, c = params else: r, c = None, None + # Ensure value is a string, decoding bytes if necessary + if isinstance(value, bytes): + value = value.decode('utf-8', 'replace') return TextArea(varname, value or '', r, c) elif kind in (mm_cfg.EmailList, mm_cfg.EmailListEx): if params: r, c = params else: r, c = None, None + # Ensure value is a string, decoding bytes if necessary + if isinstance(value, bytes): + value = value.decode('utf-8', 'replace') res = NL.join(value) return TextArea(varname, res, r, c, wrap='off') elif kind == mm_cfg.FileUpload: From fc32e71829aac63979c39f1d870ed83e32c10ea6 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 20:23:38 -0400 Subject: [PATCH 098/748] update --- Mailman/Cgi/admin.py | 205 +++++++++++++++++++++++++------------------ 1 file changed, 122 insertions(+), 83 deletions(-) diff --git a/Mailman/Cgi/admin.py b/Mailman/Cgi/admin.py index de808fa9..955b44b0 100644 --- a/Mailman/Cgi/admin.py +++ b/Mailman/Cgi/admin.py @@ -54,7 +54,6 @@ def D_(s): AUTH_CONTEXTS = (mm_cfg.AuthListAdmin, mm_cfg.AuthSiteAdmin) - def main(): # Try to find out which list is being administered parts = Utils.GetPathPieces() @@ -71,7 +70,9 @@ def main(): safelistname = Utils.websafe(listname) # Send this with a 404 status. print('Status: 404 Not Found') - admin_overview(_(f'No such list {safelistname}')) + admin_overview(_('No such list %(safelistname)s') % { + 'safelistname': safelistname + }) syslog('error', 'admin: No such list "%s": %s\n', listname, e) return @@ -256,7 +257,6 @@ def sigterm_handler(signum, frame, mlist=mlist): # we're already unlocked. mlist.Unlock() - def admin_overview(msg=''): # Show the administrative overview page, with the list of all the lists on # this host. msg is an optional error message to display at the top of @@ -265,7 +265,9 @@ def admin_overview(msg=''): # This page should be displayed in the server's default language, which # should have already been set. hostname = Utils.get_domain() - legend = _(f"{hostname} mailing lists - Admin Links") + legend = _('%(hostname)s mailing lists - Admin Links') % { + 'hostname': hostname + } # The html `document' doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) @@ -288,8 +290,8 @@ def admin_overview(msg=''): continue if mlist.advertised: if mm_cfg.VIRTUAL_HOST_OVERVIEW and ( - mlist.web_page_url.find('/%s/' % hostname) == -1 and - mlist.web_page_url.find('/%s:' % hostname) == -1): + mlist.web_page_url.find('/%(hostname)s/' % {'hostname': hostname}) == -1 and + mlist.web_page_url.find('/%(hostname)s:' % {'hostname': hostname}) == -1): # List is for different identity of this host - skip it. continue else: @@ -307,32 +309,34 @@ def admin_overview(msg=''): if not advertised: welcome.extend([ greeting, - _(f'''

                  There currently are no publicly-advertised {mailmanlink} - mailing lists on {hostname}.'''), + _('

                  There currently are no publicly-advertised %(mailmanlink)s mailing lists on %(hostname)s.') % { + 'mailmanlink': mailmanlink, + 'hostname': hostname + }, ]) else: welcome.extend([ greeting, - _(f'''

                  Below is the collection of publicly-advertised - {mailmanlink} mailing lists on {hostname}. Click on a list - name to visit the configuration pages for that list.'''), + _('

                  Below is the collection of publicly-advertised %(mailmanlink)s mailing lists on %(hostname)s. Click on a list name to visit the configuration pages for that list.') % { + 'mailmanlink': mailmanlink, + 'hostname': hostname + }, ]) creatorurl = Utils.ScriptURL('create') mailman_owner = Utils.get_site_email() extra = msg and _('right ') or '' welcome.extend([ - _(f'''To visit the administrators configuration page for an - unadvertised list, open a URL similar to this one, but with a '/' and - the {extra}list name appended. If you have the proper authority, - you can also create a new mailing list. - -

                  General list information can be found at '''), + _('To visit the administrators configuration page for an unadvertised list, open a URL similar to this one, but with a \'/\' and the %(extra)slist name appended. If you have the proper authority, you can also create a new mailing list.') % { + 'extra': extra, + 'creatorurl': creatorurl + }, + _('

                  General list information can be found at '), Link(Utils.ScriptURL('listinfo'), _('the mailing list overview page')), '.', _('

                  (Send questions and comments to '), - Link('mailto:%s' % mailman_owner, mailman_owner), + Link('mailto:%(mailman_owner)s' % {'mailman_owner': mailman_owner}, mailman_owner), '.)

                  ', ]) @@ -359,7 +363,6 @@ def admin_overview(msg=''): doc.AddItem(MailmanLogo()) print(doc.Format()) - def option_help(mlist, varhelp): # The html page document doc = Document() @@ -432,7 +435,6 @@ def option_help(mlist, varhelp): doc.AddItem(mlist.GetMailmanFooter()) print(doc.Format()) - def show_results(mlist, doc, category, subcat, cgidata): # Produce the results page adminurl = mlist.GetScriptURL('admin') @@ -441,9 +443,20 @@ def show_results(mlist, doc, category, subcat, cgidata): # Set up the document's headers realname = mlist.real_name - doc.SetTitle(_(f'{realname} Administration ({label})')) + if isinstance(realname, bytes): + realname = realname.decode('utf-8', 'replace') + if isinstance(label, bytes): + label = label.decode('utf-8', 'replace') + + doc.SetTitle(_('%(realname)s Administration (%(label)s)') % { + 'realname': realname, + 'label': label + }) doc.AddItem(Center(Header(2, _( - '{realname} mailing list administration
                  {label} Section')))) + '%(realname)s mailing list administration
                  %(label)s Section') % { + 'realname': realname, + 'label': label + }))) doc.AddItem('


                  ') # Now we need to craft the form that will be submitted, which will contain # all the variable settings, etc. This is a bit of a kludge because we @@ -452,11 +465,16 @@ def show_results(mlist, doc, category, subcat, cgidata): if category in ('autoreply', 'members'): encoding = 'multipart/form-data' if subcat: - form = Form('%s/%s/%s' % (adminurl, category, subcat), - encoding=encoding, mlist=mlist, contexts=AUTH_CONTEXTS) + form = Form('%(adminurl)s/%(category)s/%(subcat)s' % { + 'adminurl': adminurl, + 'category': category, + 'subcat': subcat + }, encoding=encoding, mlist=mlist, contexts=AUTH_CONTEXTS) else: - form = Form('%s/%s' % (adminurl, category), - encoding=encoding, mlist=mlist, contexts=AUTH_CONTEXTS) + form = Form('%(adminurl)s/%(category)s' % { + 'adminurl': adminurl, + 'category': category + }, encoding=encoding, mlist=mlist, contexts=AUTH_CONTEXTS) # This holds the two columns of links linktable = Table(valign='top', width='100%') linktable.AddRow([Center(Bold(_("Configuration Categories"))), @@ -580,7 +598,6 @@ def show_results(mlist, doc, category, subcat, cgidata): doc.AddItem(form) doc.AddItem(mlist.GetMailmanFooter()) - def show_variables(mlist, category, subcat, cgidata, doc): options = mlist.GetConfigInfo(category, subcat) @@ -627,7 +644,6 @@ def show_variables(mlist, category, subcat, cgidata, doc): table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) return table - def add_options_table_item(mlist, category, subcat, table, item, detailsp=1): # Add a row to an options table with the item description and value. varname, kind, params, extra, descr, elaboration = \ @@ -643,7 +659,6 @@ def add_options_table_item(mlist, category, subcat, table, item, detailsp=1): table.AddCellInfo(table.GetCurrentRowIndex(), 1, bgcolor=mm_cfg.WEB_ADMINITEM_COLOR) - def get_item_characteristics(record): # Break out the components of an item description from its description # record: @@ -663,7 +678,6 @@ def get_item_characteristics(record): raise ValueError(f'Badly formed options entry:\n {record}') return varname, kind, params, dependancies, descr, elaboration - def get_item_gui_value(mlist, category, kind, varname, params, extra): """Return a representation of an item's settings.""" # Give the category a chance to return the value for the variable @@ -858,34 +872,41 @@ def makebox(i, pattern, action, empty=False, table=table): else: assert 0, 'Bad gui widget type: %s' % kind - def get_item_gui_description(mlist, category, subcat, varname, descr, elaboration, detailsp): # Return the item's description, with link to details. - # - # Details are not included if this is a VARHELP page, because that /is/ - # the details page! if detailsp: if subcat: - varhelp = '/?VARHELP=%s/%s/%s' % (category, subcat, varname) + varhelp = '/?VARHELP=%(category)s/%(subcat)s/%(varname)s' % { + 'category': category, + 'subcat': subcat, + 'varname': varname + } else: - varhelp = '/?VARHELP=%s/%s' % (category, varname) + varhelp = '/?VARHELP=%(category)s/%(varname)s' % { + 'category': category, + 'varname': varname + } if descr == elaboration: - linktext = _(f'
                  (Edit {varname})') + linktext = _('
                  (Edit %(varname)s)') % { + 'varname': varname + } else: - linktext = _(f'
                  (Details for {varname})') + linktext = _('
                  (Details for %(varname)s)') % { + 'varname': varname + } link = Link(mlist.GetScriptURL('admin') + varhelp, linktext).Format() - text = Label('%s %s' % (descr, link)).Format() + text = Label('%(descr)s %(link)s' % { + 'descr': descr, + 'link': link + }).Format() else: text = Label(descr).Format() if varname[0] == '_': - text += Label(_(f'''
                  Note: - setting this value performs an immediate action but does not modify - permanent state.''')).Format() + text += Label(_('
                  Note: setting this value performs an immediate action but does not modify permanent state.')).Format() return text - def membership_options(mlist, subcat, cgidata, doc, form): # Show the main stuff adminurl = mlist.GetScriptURL('admin', absolute=1) @@ -930,7 +951,7 @@ def membership_options(mlist, subcat, cgidata, doc, form): link = Link('https://docs.python.org/2/library/re.html' '#regular-expression-syntax', _('(help)')).Format() - table.AddRow([Label(_(f'Find member {link}:')), + table.AddRow([Label(_('Find member %(link)s:') % {'link': link}), TextBox('findmember', value=cgidata.get('findmember', [''])[0]), SubmitButton('findmember_btn', _('Search...'))]) @@ -959,7 +980,7 @@ def membership_options(mlist, subcat, cgidata, doc, form): try: cre = re.compile(regexp, re.IGNORECASE) except re.error: - doc.addError(_('Bad regular expression: ') + regexp) + doc.addError(_('Bad regular expression: %(regexp)s') % {'regexp': regexp}) else: # BAW: There's got to be a more efficient way of doing this! names = [mlist.getMemberName(s) or '' for s in all] @@ -991,7 +1012,10 @@ def membership_options(mlist, subcat, cgidata, doc, form): if not bucket or bucket not in buckets: bucket = keys[0] members = buckets[bucket] - action = adminurl + '/members?letter=%s' % bucket + action = '%(adminurl)s/members?letter=%(bucket)s' % { + 'adminurl': adminurl, + 'bucket': bucket + } if len(members) <= chunksz: form.set_action(action) else: @@ -1008,15 +1032,22 @@ def membership_options(mlist, subcat, cgidata, doc, form): chunkindex = 0 members = members[chunkindex*chunksz:(chunkindex+1)*chunksz] # And set the action URL - form.set_action(action + '&chunk=%s' % chunkindex) + form.set_action('%(action)s&chunk=%(chunkindex)s' % { + 'action': action, + 'chunkindex': chunkindex + }) # So now members holds all the addresses we're going to display allcnt = len(all) if bucket: membercnt = len(members) - usertable.AddRow([Center(Italic(_( - '{allcnt} members total, {membercnt} shown')))]) + usertable.AddRow([Center(Italic(_('%(allcnt)d members total, %(membercnt)d shown') % { + 'allcnt': allcnt, + 'membercnt': membercnt + }))]) else: - usertable.AddRow([Center(Italic(_(f'{allcnt} members total')))]) + usertable.AddRow([Center(Italic(_('%(allcnt)d members total') % { + 'allcnt': allcnt + }))]) usertable.AddCellInfo(usertable.GetCurrentRowIndex(), usertable.GetCurrentCellIndex(), colspan=OPTCOLUMNS, @@ -1028,12 +1059,16 @@ def membership_options(mlist, subcat, cgidata, doc, form): findfrag = '' if regexp: findfrag = '&findmember=' + urllib.parse.quote(regexp) - url = adminurl + '/members?letter=' + letter + findfrag + url = '%(adminurl)s/members?letter=%(letter)s%(findfrag)s' % { + 'adminurl': adminurl, + 'letter': letter, + 'findfrag': findfrag + } if type(url) is str: url = url.encode(Utils.GetCharSet(mlist.preferred_language), errors='ignore') if letter == bucket: - show = Bold('[%s]' % letter.upper()).Format() + show = Bold('[%(letter)s]' % {'letter': letter.upper()}).Format() else: show = letter.upper() cells.append(Link(url, show).Format()) @@ -1073,8 +1108,8 @@ def membership_options(mlist, subcat, cgidata, doc, form): mlist.getMemberCPAddress(addr)) fullname = Utils.uncanonstr(mlist.getMemberName(addr), mlist.preferred_language) - name = TextBox(qaddr + '_realname', fullname, size=longest).Format() - cells = [Center(CheckBox(qaddr + '_unsub', 'off', 0).Format() + name = TextBox('%(qaddr)s_realname' % {'qaddr': qaddr}, fullname, size=longest).Format() + cells = [Center(CheckBox('%(qaddr)s_unsub' % {'qaddr': qaddr}, 'off', 0).Format() + ''), link.Format() + '
                  ' + name + @@ -1087,7 +1122,7 @@ def membership_options(mlist, subcat, cgidata, doc, form): else: value = 'off' checked = 0 - box = CheckBox('%s_mod' % qaddr, value, checked) + box = CheckBox('%(qaddr)s_mod' % {'qaddr': qaddr}, value, checked) cells.append(Center(box.Format() + '')) # Kluge, get these translated. @@ -1102,14 +1137,14 @@ def membership_options(mlist, subcat, cgidata, doc, form): else: value = 'on' checked = 1 - extra = '[%s]' % ds_abbrevs[status] + extra + extra = '[%(abbrev)s]' % {'abbrev': ds_abbrevs[status]} + extra elif mlist.getMemberOption(addr, mm_cfg.OPTINFO[opt]): value = 'on' checked = 1 else: value = 'off' checked = 0 - box = CheckBox('%s_%s' % (qaddr, opt), value, checked) + box = CheckBox('%(qaddr)s_%(opt)s' % {'qaddr': qaddr, 'opt': opt}, value, checked) cells.append(Center(box.Format() + extra)) # This code is less efficient than the original which did a has_key on # the underlying dictionary attribute. This version is slower and @@ -1117,10 +1152,10 @@ def membership_options(mlist, subcat, cgidata, doc, form): # method. extra = '' if addr in mlist.getRegularMemberKeys(): - cells.append(Center(CheckBox(qaddr + '_digest', 'off', 0).Format() + cells.append(Center(CheckBox('%(qaddr)s_digest' % {'qaddr': qaddr}, 'off', 0).Format() + extra)) else: - cells.append(Center(CheckBox(qaddr + '_digest', 'on', 1).Format() + cells.append(Center(CheckBox('%(qaddr)s_digest' % {'qaddr': qaddr}, 'on', 1).Format() + extra)) if mlist.getMemberOption(addr, mm_cfg.OPTINFO['plain']): value = 'on' @@ -1129,7 +1164,7 @@ def membership_options(mlist, subcat, cgidata, doc, form): value = 'off' checked = 0 cells.append(Center(CheckBox( - '%s_plain' % qaddr, value, checked).Format() + '%(qaddr)s_plain' % {'qaddr': qaddr}, value, checked).Format() + '')) # User's preferred language langpref = mlist.getMemberLanguage(addr) @@ -1139,7 +1174,7 @@ def membership_options(mlist, subcat, cgidata, doc, form): selected = langs.index(langpref) except ValueError: selected = 0 - cells.append(Center(SelectOptions(qaddr + '_language', langs, + cells.append(Center(SelectOptions('%(qaddr)s_language' % {'qaddr': qaddr}, langs, langdescs, selected)).Format()) usertable.AddRow(cells) # Add the usertable and a legend @@ -1147,14 +1182,14 @@ def membership_options(mlist, subcat, cgidata, doc, form): legend.AddItem( _('unsub -- Click on this to unsubscribe the member.')) legend.AddItem( - _(f"""mod -- The user's personal moderation flag. If this is + _('''mod -- The user's personal moderation flag. If this is set, postings from them will be moderated, otherwise they will be - approved.""")) + approved.''')) legend.AddItem( - _(f"""hide -- Is the member's address concealed on - the list of subscribers?""")) + _('''hide -- Is the member's address concealed on + the list of subscribers?''')) legend.AddItem(_( - """nomail -- Is delivery to the member disabled? If so, an + '''nomail -- Is delivery to the member disabled? If so, an abbreviation will be given describing the reason for the disabled delivery:
                  • U -- Delivery was disabled by the user via their @@ -1166,21 +1201,21 @@ def membership_options(mlist, subcat, cgidata, doc, form):
                  • ? -- The reason for disabled delivery isn't known. This is the case for all memberships which were disabled in older versions of Mailman. -
                  """)) +
                ''')) legend.AddItem( - _(f'''ack -- Does the member get acknowledgements of their + _('''ack -- Does the member get acknowledgements of their posts?''')) legend.AddItem( - _(f'''not metoo -- Does the member want to avoid copies of their + _('''not metoo -- Does the member want to avoid copies of their own postings?''')) legend.AddItem( - _(f'''nodupes -- Does the member want to avoid duplicates of the + _('''nodupes -- Does the member want to avoid duplicates of the same message?''')) legend.AddItem( - _(f'''digest -- Does the member get messages in digests? + _('''digest -- Does the member get messages in digests? (otherwise, individual messages)''')) legend.AddItem( - _(f'''plain -- If getting digests, does the member get plain + _('''plain -- If getting digests, does the member get plain text digests? (otherwise, MIME)''')) legend.AddItem(_("language -- Language preferred by the user")) addlegend = '' @@ -1206,8 +1241,12 @@ def membership_options(mlist, subcat, cgidata, doc, form): # There may be additional chunks if chunkindex is not None: buttons = [] - url = adminurl + '/members?%sletter=%s&' % (addlegend, bucket) - footer = _(f'''

                To view more members, click on the appropriate + url = '%(adminurl)s/members?%(addlegend)sletter=%(bucket)s&' % { + 'adminurl': adminurl, + 'addlegend': addlegend, + 'bucket': bucket + } + footer = _('''

                To view more members, click on the appropriate range listed below:''') chunkmembers = buckets[bucket] last = len(chunkmembers) @@ -1216,18 +1255,24 @@ def membership_options(mlist, subcat, cgidata, doc, form): continue start = chunkmembers[i*chunksz] end = chunkmembers[min((i+1)*chunksz, last)-1] - thisurl = url + 'chunk=%d' % i + findfrag + thisurl = '%(url)schunk=%(i)d%(findfrag)s' % { + 'url': url, + 'i': i, + 'findfrag': findfrag + } if type(thisurl) is str: thisurl = thisurl.encode( Utils.GetCharSet(mlist.preferred_language), errors='ignore') - link = Link(thisurl, _(f'from {start} to {end}')) + link = Link(thisurl, _('from %(start)s to %(end)s') % { + 'start': start, + 'end': end + }) buttons.append(link) buttons = UnorderedList(*buttons) container.AddItem(footer + buttons.Format() + '

                ') return container - def mass_subscribe(mlist, container): # MASS SUBSCRIBE GREY = mm_cfg.WEB_ADMINITEM_COLOR @@ -1277,7 +1322,6 @@ def mass_subscribe(mlist, container): rows=10, cols='70%', wrap=None))]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) - def mass_remove(mlist, container): # MASS UNSUBSCRIBE GREY = mm_cfg.WEB_ADMINITEM_COLOR @@ -1308,7 +1352,6 @@ def mass_remove(mlist, container): FileUpload('unsubscribees_upload', cols='50')]) container.AddItem(Center(table)) - def address_change(mlist, container): # ADDRESS CHANGE GREY = mm_cfg.WEB_ADMINITEM_COLOR @@ -1339,7 +1382,6 @@ def address_change(mlist, container): table.AddCellInfo(table.GetCurrentRowIndex(), 2, bgcolor=GREY) container.AddItem(Center(table)) - def mass_sync(mlist, container): # MASS SYNC table = Table(width='90%') @@ -1352,7 +1394,6 @@ def mass_sync(mlist, container): FileUpload('memberlist_upload', cols='50')]) container.AddItem(Center(table)) - def password_inputs(mlist): adminurl = mlist.GetScriptURL('admin', absolute=1) table = Table(cellspacing=3, cellpadding=4) @@ -1409,14 +1450,12 @@ def password_inputs(mlist): table.AddRow([ptable]) return table - def submit_button(name='submit'): table = Table(border=0, cellspacing=0, cellpadding=2) table.AddRow([Bold(SubmitButton(name, _('Submit Your Changes')))]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, align='middle') return table - def change_options(mlist, category, subcat, cgidata, doc): """Change the list's options.""" try: From 3928e6896f4479fcc25ae23d7ebf9d6e140d26d4 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 20:28:14 -0400 Subject: [PATCH 099/748] update --- Mailman/Cgi/admin.py | 47 +++++++++++++++++++++++++++++++------------- 1 file changed, 33 insertions(+), 14 deletions(-) diff --git a/Mailman/Cgi/admin.py b/Mailman/Cgi/admin.py index 955b44b0..97d2ead5 100644 --- a/Mailman/Cgi/admin.py +++ b/Mailman/Cgi/admin.py @@ -63,6 +63,8 @@ def main(): return # Get the list object listname = parts[0].lower() + if isinstance(listname, bytes): + listname = listname.decode('utf-8', 'replace') try: mlist = MailList.MailList(listname, lock=0) except Errors.MMListError as e: @@ -265,6 +267,8 @@ def admin_overview(msg=''): # This page should be displayed in the server's default language, which # should have already been set. hostname = Utils.get_domain() + if isinstance(hostname, bytes): + hostname = hostname.decode('utf-8', 'replace') legend = _('%(hostname)s mailing lists - Admin Links') % { 'hostname': hostname } @@ -283,12 +287,20 @@ def admin_overview(msg=''): listnames.sort() for name in listnames: + if isinstance(name, bytes): + name = name.decode('utf-8', 'replace') try: mlist = MailList.MailList(name, lock=0) except Errors.MMUnknownListError: # The list could have been deleted by another process. continue if mlist.advertised: + real_name = mlist.real_name + if isinstance(real_name, bytes): + real_name = real_name.decode('utf-8', 'replace') + description = mlist.GetDescription() + if isinstance(description, bytes): + description = description.decode('utf-8', 'replace') if mm_cfg.VIRTUAL_HOST_OVERVIEW and ( mlist.web_page_url.find('/%(hostname)s/' % {'hostname': hostname}) == -1 and mlist.web_page_url.find('/%(hostname)s:' % {'hostname': hostname}) == -1): @@ -296,8 +308,8 @@ def admin_overview(msg=''): continue else: advertised.append((mlist.GetScriptURL('admin'), - mlist.real_name, - Utils.websafe(mlist.GetDescription()))) + real_name, + Utils.websafe(description))) # Greeting depends on whether there was an error or not if msg: greeting = FontAttr(msg, color="ff5060", size="+1") @@ -440,21 +452,16 @@ def show_results(mlist, doc, category, subcat, cgidata): adminurl = mlist.GetScriptURL('admin') categories = mlist.GetConfigCategories() label = _(categories[category][0]) - - # Set up the document's headers - realname = mlist.real_name - if isinstance(realname, bytes): - realname = realname.decode('utf-8', 'replace') if isinstance(label, bytes): label = label.decode('utf-8', 'replace') doc.SetTitle(_('%(realname)s Administration (%(label)s)') % { - 'realname': realname, + 'realname': mlist.real_name, 'label': label }) doc.AddItem(Center(Header(2, _( '%(realname)s mailing list administration
                %(label)s Section') % { - 'realname': realname, + 'realname': mlist.real_name, 'label': label }))) doc.AddItem('


                ') @@ -607,6 +614,8 @@ def show_variables(mlist, category, subcat, cgidata, doc): # Get and portray the text label for the category. categories = mlist.GetConfigCategories() label = _(categories[category][0]) + if isinstance(label, bytes): + label = label.decode('utf-8', 'replace') table.AddRow([Center(Header(2, label))]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, @@ -615,6 +624,8 @@ def show_variables(mlist, category, subcat, cgidata, doc): # The very first item in the config info will be treated as a general # description if it is a string description = options[0] + if isinstance(description, bytes): + description = description.decode('utf-8', 'replace') if type(description) is str: table.AddRow([description]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) @@ -770,10 +781,10 @@ def makebox(i, name, pattern, desc, empty=False, table=table): addtag = 'topic_add_%02d' % i newtag = 'topic_new_%02d' % i if empty: - table.AddRow([Center(Bold(_('Topic %(i)d'))), + table.AddRow([Center(Bold(_('Topic %(i)d')) % {'i': i}), Hidden(newtag)]) else: - table.AddRow([Center(Bold(_('Topic %(i)d'))), + table.AddRow([Center(Bold(_('Topic %(i)d')) % {'i': i}), SubmitButton(deltag, _('Delete'))]) table.AddRow([Label(_('Topic name:')), TextBox(boxtag, value=name, size=30)]) @@ -796,6 +807,12 @@ def makebox(i, name, pattern, desc, empty=False, table=table): i = 1 data = getattr(mlist, varname) for name, pattern, desc, empty in data: + if isinstance(name, bytes): + name = name.decode('utf-8', 'replace') + if isinstance(pattern, bytes): + pattern = pattern.decode('utf-8', 'replace') + if isinstance(desc, bytes): + desc = desc.decode('utf-8', 'replace') makebox(i, name, pattern, desc, empty) i += 1 # Add one more non-deleteable widget as the first blank entry, but @@ -820,10 +837,10 @@ def makebox(i, pattern, action, empty=False, table=table): uptag = 'hdrfilter_up_%02d' % i downtag = 'hdrfilter_down_%02d' % i if empty: - table.AddRow([Center(Bold(_('Spam Filter Rule %(i)d'))), + table.AddRow([Center(Bold(_('Spam Filter Rule %(i)d')) % {'i': i}), Hidden(newtag)]) else: - table.AddRow([Center(Bold(_('Spam Filter Rule %(i)d'))), + table.AddRow([Center(Bold(_('Spam Filter Rule %(i)d')) % {'i': i}), SubmitButton(deltag, _('Delete'))]) table.AddRow([Label(_('Spam Filter Regexp:')), TextArea(reboxtag, text=pattern, @@ -860,6 +877,8 @@ def makebox(i, pattern, action, empty=False, table=table): i = 1 data = getattr(mlist, varname) for pattern, action, empty in data: + if isinstance(pattern, bytes): + pattern = pattern.decode('utf-8', 'replace') makebox(i, pattern, action, empty) i += 1 # Add one more non-deleteable widget as the first blank entry, but @@ -1300,7 +1319,7 @@ def mass_subscribe(mlist, container): RadioButtonArray('send_notifications_to_list_owner', (_('No'), _('Yes')), mlist.admin_notify_mchanges, - values=(0,1)) + values=(0, 1)) ]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, bgcolor=GREY) table.AddCellInfo(table.GetCurrentRowIndex(), 1, bgcolor=GREY) From 7c5c442f064452720038172287ac80dc6d877ad4 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 20:31:15 -0400 Subject: [PATCH 100/748] update --- Mailman/Gui/GUIBase.py | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/Mailman/Gui/GUIBase.py b/Mailman/Gui/GUIBase.py index 2f1b4d29..80989dd6 100644 --- a/Mailman/Gui/GUIBase.py +++ b/Mailman/Gui/GUIBase.py @@ -45,6 +45,11 @@ def _getValidValue(self, mlist, property, wtype, val): return int(val) # String and Text widgets both just return their values verbatim if wtype in (mm_cfg.String, mm_cfg.Text): + if isinstance(val, bytes): + try: + return val.decode('utf-8') + except UnicodeDecodeError: + return val.decode('latin1') return val # This widget contains a single email address if wtype == mm_cfg.Email: @@ -52,6 +57,11 @@ def _getValidValue(self, mlist, property, wtype, val): # be cleared. This is currently the only mm_cfg.Email type widget # in the interface, so watch out if we ever add any new ones. if val: + if isinstance(val, bytes): + try: + val = val.decode('utf-8') + except UnicodeDecodeError: + val = val.decode('latin1') # Let MMBadEmailError and MMHostileAddress propagate Utils.ValidateEmail(val) return val @@ -63,6 +73,11 @@ def _getValidValue(self, mlist, property, wtype, val): # config_list input. Sigh. if isinstance(val, ListType): return val + if isinstance(val, bytes): + try: + val = val.decode('utf-8') + except UnicodeDecodeError: + val = val.decode('latin1') addrs = [] bad_addrs = [] for addr in [s.strip() for s in val.split(NL)]: @@ -138,8 +153,14 @@ def _getValidValue(self, mlist, property, wtype, val): def _setValue(self, mlist, property, val, doc): # Set the value, or override to take special action on the property - if not property.startswith('_') and getattr(mlist, property) != val: - setattr(mlist, property, val) + if not property.startswith('_'): + if isinstance(val, bytes): + try: + val = val.decode('utf-8') + except UnicodeDecodeError: + val = val.decode('latin1') + if getattr(mlist, property) != val: + setattr(mlist, property, val) def _postValidate(self, mlist, doc): # Validate all the attributes for this category @@ -187,6 +208,11 @@ def handleForm(self, mlist, category, subcat, cgidata, doc): # Convenience method for handling $-string attributes def _convertString(self, mlist, property, alloweds, val, doc): # Is the list using $-strings? + if isinstance(val, bytes): + try: + val = val.decode('utf-8') + except UnicodeDecodeError: + val = val.decode('latin1') dollarp = getattr(mlist, 'use_dollar_strings', 0) if dollarp: ids = Utils.dollar_identifiers(val) From 9e385614a0b76557e4ec6dc507d81de839363115 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 20:35:16 -0400 Subject: [PATCH 101/748] string handling --- Mailman/CSRFcheck.py | 2 +- Mailman/Gui/GUIBase.py | 21 ++++++--------------- Mailman/HTMLFormatter.py | 10 ++++++---- 3 files changed, 13 insertions(+), 20 deletions(-) diff --git a/Mailman/CSRFcheck.py b/Mailman/CSRFcheck.py index 35663c5b..c0e35ba4 100644 --- a/Mailman/CSRFcheck.py +++ b/Mailman/CSRFcheck.py @@ -52,7 +52,7 @@ def csrf_token(mlist, contexts, user=None): needs_hash = (secret + repr(issued)).encode('utf-8') mac = sha_new(needs_hash).hexdigest() keymac = '%s:%s' % (key, mac) - token = binascii.hexlify(marshal.dumps((issued, keymac))) + token = binascii.hexlify(marshal.dumps((issued, keymac))).decode('utf-8') return token def csrf_check(mlist, token, cgi_user=None): diff --git a/Mailman/Gui/GUIBase.py b/Mailman/Gui/GUIBase.py index 80989dd6..5054cb9c 100644 --- a/Mailman/Gui/GUIBase.py +++ b/Mailman/Gui/GUIBase.py @@ -39,17 +39,18 @@ class GUIBase: def _getValidValue(self, mlist, property, wtype, val): # Coerce and validate the new value. # + # First convert any bytes to strings + if isinstance(val, bytes): + try: + val = val.decode('utf-8') + except UnicodeDecodeError: + val = val.decode('latin1') # Radio buttons and boolean toggles both have integral type if wtype in (mm_cfg.Radio, mm_cfg.Toggle): # Let ValueErrors propagate return int(val) # String and Text widgets both just return their values verbatim if wtype in (mm_cfg.String, mm_cfg.Text): - if isinstance(val, bytes): - try: - return val.decode('utf-8') - except UnicodeDecodeError: - return val.decode('latin1') return val # This widget contains a single email address if wtype == mm_cfg.Email: @@ -57,11 +58,6 @@ def _getValidValue(self, mlist, property, wtype, val): # be cleared. This is currently the only mm_cfg.Email type widget # in the interface, so watch out if we ever add any new ones. if val: - if isinstance(val, bytes): - try: - val = val.decode('utf-8') - except UnicodeDecodeError: - val = val.decode('latin1') # Let MMBadEmailError and MMHostileAddress propagate Utils.ValidateEmail(val) return val @@ -73,11 +69,6 @@ def _getValidValue(self, mlist, property, wtype, val): # config_list input. Sigh. if isinstance(val, ListType): return val - if isinstance(val, bytes): - try: - val = val.decode('utf-8') - except UnicodeDecodeError: - val = val.decode('latin1') addrs = [] bad_addrs = [] for addr in [s.strip() for s in val.split(NL)]: diff --git a/Mailman/HTMLFormatter.py b/Mailman/HTMLFormatter.py index 86ebe93d..f89e5307 100644 --- a/Mailman/HTMLFormatter.py +++ b/Mailman/HTMLFormatter.py @@ -337,10 +337,12 @@ def FormatFormStart(self, name, extra='', else: full_url = base_url if mlist: - return (f""" -""" - % (full_url, csrf_token(mlist, contexts, user))) - return ('' % full_url) + token = csrf_token(mlist, contexts, user) + if token is None: + return '' % full_url + return """ +""" % (full_url, token) + return '' % full_url def FormatArchiveAnchor(self): return '' % self.GetBaseArchiveURL() From b20412aaab26f3bd0e16714ef11e391a3d0b31a0 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 20:37:07 -0400 Subject: [PATCH 102/748] string handling --- Mailman/Cgi/admin.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Mailman/Cgi/admin.py b/Mailman/Cgi/admin.py index 97d2ead5..a71f0886 100644 --- a/Mailman/Cgi/admin.py +++ b/Mailman/Cgi/admin.py @@ -647,6 +647,8 @@ def show_variables(mlist, category, subcat, cgidata, doc): # The very first banner option (string in an options list) is # treated as a general description, while any others are # treated as section headers - centered and italicized... + if isinstance(item, bytes): + item = item.decode('utf-8', 'replace') table.AddRow([Center(Italic(item))]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) else: From c7bbd9297d57e7addb854a4711e407c7fd540ee5 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 20:43:30 -0400 Subject: [PATCH 103/748] update --- Mailman/MailList.py | 22 +++++++++++++++--- bin/config_list | 56 +++++++++++++++++++++++++++++++++++++-------- 2 files changed, 65 insertions(+), 13 deletions(-) diff --git a/Mailman/MailList.py b/Mailman/MailList.py index 3308cd90..d676e483 100644 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -517,11 +517,27 @@ def GetConfigSubCategories(self, category): return None def GetConfigInfo(self, category, subcat=None): + """Get configuration information for a category and optional subcategory. + + Args: + category: The configuration category to get info for + subcat: Optional subcategory to filter by + + Returns: + A list of configuration items, or None if not found + """ for gui in self._gui: if hasattr(gui, 'GetConfigInfo'): - value = gui.GetConfigInfo(self, category, subcat) - if value: - return value + try: + value = gui.GetConfigInfo(self, category, subcat) + if value: + return value + except (AttributeError, KeyError) as e: + # Log the error but continue trying other GUIs + syslog('error', 'Error getting config info for %s/%s: %s', + category, subcat, str(e)) + continue + return None # # List creation diff --git a/bin/config_list b/bin/config_list index aa1cea9e..86600d04 100644 --- a/bin/config_list +++ b/bin/config_list @@ -401,26 +401,40 @@ def main(): except Errors.MMUnknownListError: logging.error(f"List not found: {args.listname}") usage(1, _('No such list "%(listname)s"')) + return try: logging.debug("Getting configuration categories") categories = mlist.GetConfigCategories() + if not categories: + logging.error("No configuration categories found") + print(_("No configuration categories available")) + return + logging.debug(f"Got categories: {list(categories.keys())}") # Get configuration items using GetConfigInfo() for category in mm_cfg.ADMIN_CATEGORIES: logging.debug(f"Processing category: {category}") + if category not in categories: + logging.warning(f"Category {category} not found in available categories") + continue + subcats = mlist.GetConfigSubCategories(category) logging.debug(f"Got subcategories: {subcats}") if subcats is None: logging.debug(f"Getting config info for category {category}") info = mlist.GetConfigInfo(category, None) + if not info: + logging.warning(f"No configuration info found for category {category}") + continue + logging.debug(f"Got config info: {info is not None}") - if info: - for data in info[1:]: - if not isinstance(data, Tuple): - continue + for data in info[1:]: + if not isinstance(data, Tuple): + continue + try: key = data[0] if not args.all and key.startswith('_'): continue @@ -428,20 +442,33 @@ def main(): continue if args.subcategory and not key.startswith(args.category + '_' + args.subcategory + '_'): continue - value = getattr(mlist, key) + + # Use getattr with a default value instead of direct access + value = getattr(mlist, key, None) + if value is None: + logging.warning(f"Configuration item {key} not found") + continue + if args.verbose: print(f"{key}={value}") else: print(key) + except Exception as e: + logging.error(f"Error processing configuration item: {str(e)}") + continue else: for subcat, _ in subcats: logging.debug(f"Getting config info for category {category}, subcategory {subcat}") info = mlist.GetConfigInfo(category, subcat) + if not info: + logging.warning(f"No configuration info found for category {category}, subcategory {subcat}") + continue + logging.debug(f"Got config info: {info is not None}") - if info: - for data in info[1:]: - if not isinstance(data, Tuple): - continue + for data in info[1:]: + if not isinstance(data, Tuple): + continue + try: key = data[0] if not args.all and key.startswith('_'): continue @@ -449,11 +476,20 @@ def main(): continue if args.subcategory and not key.startswith(args.category + '_' + args.subcategory + '_'): continue - value = getattr(mlist, key) + + # Use getattr with a default value instead of direct access + value = getattr(mlist, key, None) + if value is None: + logging.warning(f"Configuration item {key} not found") + continue + if args.verbose: print(f"{key}={value}") else: print(key) + except Exception as e: + logging.error(f"Error processing configuration item: {str(e)}") + continue except Exception as e: logging.error(f"Error occurred: {str(e)}", exc_info=True) From a29ad59eb60244740f838c8c7b62bbe39fa1f241 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 20:59:19 -0400 Subject: [PATCH 104/748] update --- Mailman/Queue/RetryRunner.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Mailman/Queue/RetryRunner.py b/Mailman/Queue/RetryRunner.py index dd0ac34c..4297a99a 100644 --- a/Mailman/Queue/RetryRunner.py +++ b/Mailman/Queue/RetryRunner.py @@ -38,5 +38,8 @@ def _dispose(self, mlist, msg, msgdata): return False def _snooze(self, filecnt): - # We always want to snooze - time.sleep(self.SLEEPTIME) + # We always want to snooze, but check for stop flag periodically + for _ in range(self.SLEEPTIME): + if self._stop: + return + time.sleep(1) From 0fe4b14a795da61c15ea9e8b35adf23dc7f1be69 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 21:06:29 -0400 Subject: [PATCH 105/748] update --- Mailman/Queue/OutgoingRunner.py | 78 +++++++++++++++++++++++++-------- 1 file changed, 59 insertions(+), 19 deletions(-) diff --git a/Mailman/Queue/OutgoingRunner.py b/Mailman/Queue/OutgoingRunner.py index 6208be2e..44ebc010 100644 --- a/Mailman/Queue/OutgoingRunner.py +++ b/Mailman/Queue/OutgoingRunner.py @@ -22,6 +22,7 @@ import copy import time import socket +import traceback import email @@ -43,33 +44,67 @@ class OutgoingRunner(Runner, BounceMixin): QDIR = mm_cfg.OUTQUEUE_DIR def __init__(self, slice=None, numslices=1): - Runner.__init__(self, slice, numslices) - BounceMixin.__init__(self) - # We look this function up only at startup time - modname = 'Mailman.Handlers.' + mm_cfg.DELIVERY_MODULE - mod = __import__(modname) - self._func = getattr(sys.modules[modname], 'process') - # This prevents smtp server connection problems from filling up the - # error log. It gets reset if the message was successfully sent, and - # set if there was a socket.error. - self.__logged = False - self.__retryq = Switchboard(mm_cfg.RETRYQUEUE_DIR) + syslog('debug', 'OutgoingRunner: Starting initialization') + try: + Runner.__init__(self, slice, numslices) + syslog('debug', 'OutgoingRunner: Base Runner initialized') + + BounceMixin.__init__(self) + syslog('debug', 'OutgoingRunner: BounceMixin initialized') + + # We look this function up only at startup time + modname = 'Mailman.Handlers.' + mm_cfg.DELIVERY_MODULE + syslog('debug', 'OutgoingRunner: Attempting to import delivery module: %s', modname) + + try: + mod = __import__(modname) + syslog('debug', 'OutgoingRunner: Successfully imported delivery module') + except ImportError as e: + syslog('error', 'OutgoingRunner: Failed to import delivery module %s: %s', modname, str(e)) + syslog('error', 'OutgoingRunner: Traceback: %s', traceback.format_exc()) + raise + + try: + self._func = getattr(sys.modules[modname], 'process') + syslog('debug', 'OutgoingRunner: Successfully got process function from module') + except AttributeError as e: + syslog('error', 'OutgoingRunner: Failed to get process function from module %s: %s', modname, str(e)) + syslog('error', 'OutgoingRunner: Traceback: %s', traceback.format_exc()) + raise + + # This prevents smtp server connection problems from filling up the + # error log. It gets reset if the message was successfully sent, and + # set if there was a socket.error. + self.__logged = False + syslog('debug', 'OutgoingRunner: Initializing retry queue') + self.__retryq = Switchboard(mm_cfg.RETRYQUEUE_DIR) + syslog('debug', 'OutgoingRunner: Initialization complete') + except Exception as e: + syslog('error', 'OutgoingRunner: Initialization failed: %s', str(e)) + syslog('error', 'OutgoingRunner: Traceback: %s', traceback.format_exc()) + raise def _dispose(self, mlist, msg, msgdata): + syslog('debug', 'OutgoingRunner: Processing message for list %s', mlist.internal_name()) # See if we should retry delivery of this message again. deliver_after = msgdata.get('deliver_after', 0) if time.time() < deliver_after: + syslog('debug', 'OutgoingRunner: Message not ready for delivery yet, waiting until %s', + time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(deliver_after))) return True # Make sure we have the most up-to-date state + syslog('debug', 'OutgoingRunner: Loading list state') mlist.Load() try: pid = os.getpid() + syslog('debug', 'OutgoingRunner: Attempting to deliver message') self._func(mlist, msg, msgdata) # Failsafe -- a child may have leaked through. if pid != os.getpid(): - syslog('error', 'child process leaked thru: %s', modname) + syslog('error', 'OutgoingRunner: child process leaked thru: %s', modname) os._exit(1) self.__logged = False + syslog('debug', 'OutgoingRunner: Message delivered successfully') except socket.error: # There was a problem connecting to the SMTP server. Log this # once, but crank up our sleep time so we don't fill the error @@ -79,32 +114,30 @@ def _dispose(self, mlist, msg, msgdata): port = 'smtp' # Log this just once. if not self.__logged: - syslog('error', 'Cannot connect to SMTP server %s on port %s', + syslog('error', 'OutgoingRunner: Cannot connect to SMTP server %s on port %s', mm_cfg.SMTPHOST, port) self.__logged = True self._snooze(0) return True except Errors.SomeRecipientsFailed as e: + syslog('debug', 'OutgoingRunner: Some recipients failed: %s', str(e)) # Handle local rejects of probe messages differently. if msgdata.get('probe_token') and e.permfailures: + syslog('debug', 'OutgoingRunner: Handling probe bounce') self._probe_bounce(mlist, msgdata['probe_token']) else: # Delivery failed at SMTP time for some or all of the # recipients. Permanent failures are registered as bounces, # but temporary failures are retried for later. - # - # BAW: msg is going to be the original message that failed - # delivery, not a bounce message. This may be confusing if - # this is what's sent to the user in the probe message. Maybe - # we should craft a bounce-like message containing information - # about the permanent SMTP failure? if e.permfailures: + syslog('debug', 'OutgoingRunner: Queueing permanent failures as bounces') self._queue_bounces(mlist.internal_name(), e.permfailures, msg) # Move temporary failures to the qfiles/retry queue which will # occasionally move them back here for another shot at # delivery. if e.tempfailures: + syslog('debug', 'OutgoingRunner: Queueing temporary failures for retry') now = time.time() recips = e.tempfailures last_recip_count = msgdata.get('last_recip_count', 0) @@ -114,6 +147,7 @@ def _dispose(self, mlist, msg, msgdata): # delivery any longer. BAW: is this the best # disposition? if now > deliver_until: + syslog('debug', 'OutgoingRunner: No progress made, giving up on message') return False else: # Keep trying to delivery this message for a while @@ -125,11 +159,17 @@ def _dispose(self, mlist, msg, msgdata): msgdata['deliver_until'] = deliver_until msgdata['recips'] = recips self.__retryq.enqueue(msg, msgdata) + except Exception as e: + syslog('error', 'OutgoingRunner: Unexpected error during message processing: %s', str(e)) + syslog('error', 'OutgoingRunner: Traceback: %s', traceback.format_exc()) + raise # We've successfully completed handling of this message return False _doperiodic = BounceMixin._doperiodic def _cleanup(self): + syslog('debug', 'OutgoingRunner: Starting cleanup') BounceMixin._cleanup(self) Runner._cleanup(self) + syslog('debug', 'OutgoingRunner: Cleanup complete') From 0b63df710dac8af8060cd48a6774bd6b1c4145b7 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 21:08:32 -0400 Subject: [PATCH 106/748] update --- Mailman/Queue/Runner.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Mailman/Queue/Runner.py b/Mailman/Queue/Runner.py index c1686875..b5350ede 100644 --- a/Mailman/Queue/Runner.py +++ b/Mailman/Queue/Runner.py @@ -157,7 +157,17 @@ def _onefile(self, msg, msgdata): try: # Convert email.message.Message to Mailman.Message if needed if not isinstance(msg, MailmanMessage): - msg = MailmanMessage(msg) + mailman_msg = MailmanMessage() + # Copy all attributes from the original message + for key, value in msg.items(): + mailman_msg[key] = value + # Copy the payload + if msg.is_multipart(): + for part in msg.get_payload(): + mailman_msg.attach(part) + else: + mailman_msg.set_payload(msg.get_payload()) + msg = mailman_msg sender = msg.get_sender() listname = msgdata.get('listname') if not listname: From af048fdd806dbb96f81642ebdf252af46041f2bc Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 21:09:42 -0400 Subject: [PATCH 107/748] update --- Mailman/Handlers/CookHeaders.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Mailman/Handlers/CookHeaders.py b/Mailman/Handlers/CookHeaders.py index 53e2a4fa..019eb61a 100644 --- a/Mailman/Handlers/CookHeaders.py +++ b/Mailman/Handlers/CookHeaders.py @@ -48,6 +48,19 @@ def uheader(mlist, s, header_name=None, continuation_ws=' ', maxlinelen=None): # us-ascii then we use iso-8859-1 instead. If the string is ascii only # we use 'us-ascii' if another charset is specified. charset = Utils.GetCharSet(mlist.preferred_language) + + # Convert bytes to string if necessary + if isinstance(s, bytes): + try: + s = s.decode('ascii') + except UnicodeDecodeError: + # If it's not ASCII, try to decode with the list's charset + try: + s = s.decode(charset) + except UnicodeDecodeError: + # If that fails, try ISO-8859-1 as a fallback + s = s.decode('iso-8859-1', 'replace') + if nonascii.search(s): # use list charset but ... if charset == 'us-ascii': From ecfd83df32c61c663ba516b23abcbf9e65faf8cf Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 21:14:40 -0400 Subject: [PATCH 108/748] update conflicting log function --- Mailman/Cgi/listinfo.py | 4 +-- Mailman/Cgi/subscribe.py | 6 ++-- Mailman/Handlers/CookHeaders.py | 4 +-- Mailman/Handlers/SMTPDirect.py | 33 ++++++++---------- Mailman/ListAdmin.py | 30 ++++++++-------- Mailman/Logging/Syslog.py | 13 ++++--- Mailman/Queue/OutgoingRunner.py | 62 ++++++++++++++++----------------- Mailman/Utils.py | 40 ++++++++++----------- 8 files changed, 96 insertions(+), 96 deletions(-) diff --git a/Mailman/Cgi/listinfo.py b/Mailman/Cgi/listinfo.py index b2cd507d..422f4ea5 100644 --- a/Mailman/Cgi/listinfo.py +++ b/Mailman/Cgi/listinfo.py @@ -34,7 +34,7 @@ from Mailman import Errors from Mailman import i18n from Mailman.htmlformat import * -from Mailman.Logging.Syslog import syslog +from Mailman.Logging.Syslog import mailman_log from Mailman.Utils import validate_ip_address # Set up i18n @@ -57,7 +57,7 @@ def main(): # Send this with a 404 status. print('Status: 404 Not Found') listinfo_overview(_(f'No such list {safelistname}')) - syslog('error', 'listinfo: No such list "%s": %s', listname, e) + mailman_log('error', 'listinfo: No such list "%s": %s', listname, e) return # See if the user want to see this page in other language diff --git a/Mailman/Cgi/subscribe.py b/Mailman/Cgi/subscribe.py index c72e8d02..be59eb8e 100644 --- a/Mailman/Cgi/subscribe.py +++ b/Mailman/Cgi/subscribe.py @@ -34,7 +34,7 @@ from Mailman import Message from Mailman.UserDesc import UserDesc from Mailman.htmlformat import * -from Mailman.Logging.Syslog import syslog +from Mailman.Logging.Syslog import mailman_log from Mailman.Utils import validate_ip_address SLASH = '/' @@ -68,7 +68,7 @@ def main(): # Send this with a 404 status. print('Status: 404 Not Found') print(doc.Format()) - syslog('error', 'subscribe: No such list "%s": %s\n', listname, e) + mailman_log('error', 'subscribe: No such list "%s": %s\n', listname, e) return # See if the form data has a preferred language set, in which case, use it @@ -214,7 +214,7 @@ def process_form(mlist, doc, cgidata, lang): # Was an attempt made to subscribe the list to itself? if email == mlist.GetListEmail(): - syslog('mischief', 'Attempt to self subscribe %s: %s', email, remote) + mailman_log('mischief', 'Attempt to self subscribe %s: %s', email, remote) results.append(_('You may not subscribe a list to itself!')) # If the user did not supply a password, generate one for him password = cgidata.get('pw', [''])[0].strip() diff --git a/Mailman/Handlers/CookHeaders.py b/Mailman/Handlers/CookHeaders.py index 019eb61a..1c82352c 100644 --- a/Mailman/Handlers/CookHeaders.py +++ b/Mailman/Handlers/CookHeaders.py @@ -31,7 +31,7 @@ from Mailman import mm_cfg from Mailman import Utils from Mailman.i18n import _ -from Mailman.Logging.Syslog import syslog +from Mailman.Logging.Syslog import mailman_log CONTINUATION = ',\n ' COMMASPACE = ', ' @@ -71,7 +71,7 @@ def uheader(mlist, s, header_name=None, continuation_ws=' ', maxlinelen=None): try: return Header(s, charset, maxlinelen, header_name, continuation_ws) except UnicodeError: - syslog('error', 'list: %s: can\'t decode "%s" as %s', + mailman_log('error', 'list: %s: can\'t decode "%s" as %s', mlist.internal_name(), s, charset) return Header('', charset, maxlinelen, header_name, continuation_ws) diff --git a/Mailman/Handlers/SMTPDirect.py b/Mailman/Handlers/SMTPDirect.py index b9f68b56..20b2e256 100644 --- a/Mailman/Handlers/SMTPDirect.py +++ b/Mailman/Handlers/SMTPDirect.py @@ -38,7 +38,7 @@ from Mailman import Utils from Mailman import Errors from Mailman.Handlers import Decorate -from Mailman.Logging.Syslog import syslog as syslogger +from Mailman.Logging.Syslog import mailman_log from Mailman.SafeDict import MsgSafeDict import email @@ -48,9 +48,6 @@ DOT = '.' -# Initialize the syslogger -syslog = syslogger() - # Manage a connection to the SMTP server class Connection(object): def __init__(self): @@ -66,32 +63,32 @@ def __connect(self): if not helo_host or helo_host.startswith('.'): # If we still don't have a valid hostname, use localhost helo_host = 'localhost' - syslog('smtp', 'Connecting to SMTP server %s:%s with HELO %s', + mailman_log('smtp', 'Connecting to SMTP server %s:%s with HELO %s', mm_cfg.SMTPHOST, mm_cfg.SMTPPORT, helo_host) self.__conn.connect(mm_cfg.SMTPHOST, mm_cfg.SMTPPORT) # Set the hostname for TLS self.__conn._host = helo_host if mm_cfg.SMTP_AUTH: if mm_cfg.SMTP_USE_TLS: - syslog('smtp', 'Using TLS with hostname: %s', helo_host) + mailman_log('smtp', 'Using TLS with hostname: %s', helo_host) try: # Use native TLS support self.__conn.starttls() except SMTPException as e: - syslog('smtp-failure', 'SMTP TLS error: %s', e) + mailman_log('smtp-failure', 'SMTP TLS error: %s', e) self.quit() raise try: self.__conn.login(mm_cfg.SMTP_USER, mm_cfg.SMTP_PASSWD) except smtplib.SMTPHeloError as e: - syslog('smtp-failure', 'SMTP HELO error: %s', e) + mailman_log('smtp-failure', 'SMTP HELO error: %s', e) self.quit() raise except smtplib.SMTPAuthenticationError as e: - syslog('smtp-failure', 'SMTP AUTH error: %s', e) + mailman_log('smtp-failure', 'SMTP AUTH error: %s', e) self.quit() except smtplib.SMTPException as e: - syslog('smtp-failure', + mailman_log('smtp-failure', 'SMTP - no suitable authentication method found: %s', e) self.quit() raise @@ -225,12 +222,12 @@ def process(mlist, msg, msgdata): # still worthwhile doing the interpolation in syslog() because it'll catch # any catastrophic exceptions due to bogus format strings. if mm_cfg.SMTP_LOG_EVERY_MESSAGE: - syslog.write_ex(mm_cfg.SMTP_LOG_EVERY_MESSAGE[0], + mailman_log.write_ex(mm_cfg.SMTP_LOG_EVERY_MESSAGE[0], mm_cfg.SMTP_LOG_EVERY_MESSAGE[1], kws=d) if refused: if mm_cfg.SMTP_LOG_REFUSED: - syslog.write_ex(mm_cfg.SMTP_LOG_REFUSED[0], + mailman_log.write_ex(mm_cfg.SMTP_LOG_REFUSED[0], mm_cfg.SMTP_LOG_REFUSED[1], kws=d) elif msgdata.get('tolist'): @@ -240,7 +237,7 @@ def process(mlist, msg, msgdata): # the other messages, but in that case, we should probably have a # separate configuration variable to control that. if mm_cfg.SMTP_LOG_SUCCESS: - syslog.write_ex(mm_cfg.SMTP_LOG_SUCCESS[0], + mailman_log.write_ex(mm_cfg.SMTP_LOG_SUCCESS[0], mm_cfg.SMTP_LOG_SUCCESS[1], kws=d) # Process any failed deliveries. @@ -267,7 +264,7 @@ def process(mlist, msg, msgdata): d.update({'recipient': recip, 'failcode' : code, 'failmsg' : smtpmsg}) - syslog.write_ex(mm_cfg.SMTP_LOG_EACH_FAILURE[0], + mailman_log.write_ex(mm_cfg.SMTP_LOG_EACH_FAILURE[0], mm_cfg.SMTP_LOG_EACH_FAILURE[1], kws=d) # Return the results if tempfailures or permfailures: @@ -345,7 +342,7 @@ def verpdeliver(mlist, msg, msgdata, envsender, failures, conn): # deliver it to this person, nor can we craft a valid verp # header. I don't think there's much we can do except ignore # this recipient. - syslog('smtp', 'Skipping VERP delivery to unqual recip: %s', + mailman_log('smtp', 'Skipping VERP delivery to unqual recip: %s', recip) continue d = {'bounces': bmailbox, @@ -439,11 +436,11 @@ def bulkdeliver(mlist, msg, msgdata, envsender, failures, conn): # Send the message refused = conn.sendmail(envsender, recips, msgtext) except smtplib.SMTPRecipientsRefused as e: - syslog('smtp-failure', 'All recipients refused: %s, msgid: %s', + mailman_log('smtp-failure', 'All recipients refused: %s, msgid: %s', e, msgid) refused = e.recipients except smtplib.SMTPResponseException as e: - syslog('smtp-failure', 'SMTP session failure: %s, %s, msgid: %s', + mailman_log('smtp-failure', 'SMTP session failure: %s, %s, msgid: %s', e.smtp_code, e.smtp_error, msgid) # If this was a permanent failure, don't add the recipients to the # refused, because we don't want them to be added to failures. @@ -459,7 +456,7 @@ def bulkdeliver(mlist, msg, msgdata, envsender, failures, conn): # MTA not responding, or other socket problems, or any other kind of # SMTPException. In that case, nothing got delivered, so treat this # as a temporary failure. - syslog('smtp-failure', 'Low level smtp error: %s, msgid: %s', e, msgid) + mailman_log('smtp-failure', 'Low level smtp error: %s, msgid: %s', e, msgid) error = str(e) for r in recips: refused[r] = (-1, error) diff --git a/Mailman/ListAdmin.py b/Mailman/ListAdmin.py index 25d960a1..93f18ebe 100644 --- a/Mailman/ListAdmin.py +++ b/Mailman/ListAdmin.py @@ -45,7 +45,7 @@ from Mailman import Errors from Mailman.UserDesc import UserDesc from Mailman.Queue.sbcache import get_switchboard -from Mailman.Logging.Syslog import syslog +from Mailman.Logging.Syslog import mailman_log from Mailman import i18n _ = i18n._ @@ -93,11 +93,11 @@ def __opendb(self): raise ValueError("Database not a dictionary") return except (EOFError, ValueError, TypeError, pickle.UnpicklingError) as e: - syslog('error', 'Error loading pending.pck: %s', str(e)) + mailman_log('error', 'Error loading pending.pck: %s', str(e)) # If we get here, the main file failed to load properly if os.path.exists(filename_backup): - syslog('info', 'Attempting to load from backup file') + mailman_log('info', 'Attempting to load from backup file') with open(filename_backup, 'rb') as fp: try: self.__db = pickle.load(fp, fix_imports=True, encoding='latin1') @@ -108,11 +108,11 @@ def __opendb(self): shutil.copy2(filename_backup, filename) return except (EOFError, ValueError, TypeError, pickle.UnpicklingError) as e: - syslog('error', 'Error loading backup pending.pck: %s', str(e)) + mailman_log('error', 'Error loading backup pending.pck: %s', str(e)) except IOError as e: if e.errno != errno.ENOENT: - syslog('error', 'IOError loading pending.pck: %s', str(e)) + mailman_log('error', 'IOError loading pending.pck: %s', str(e)) # If we get here, both main and backup files failed or don't exist self.__db = {} @@ -132,7 +132,7 @@ def __closedb(self): import shutil shutil.copy2(filename, filename_backup) except IOError as e: - syslog('error', 'Error creating backup: %s', str(e)) + mailman_log('error', 'Error creating backup: %s', str(e)) # Save to temporary file first try: @@ -152,7 +152,7 @@ def __closedb(self): os.rename(filename_tmp, filename) except (IOError, OSError) as e: - syslog('error', 'Error saving pending.pck: %s', str(e)) + mailman_log('error', 'Error saving pending.pck: %s', str(e)) # Try to clean up try: os.unlink(filename_tmp) @@ -365,7 +365,7 @@ def __handlepost(self, record, value, comment, preserve, forward, addr): # message directly here can lead to a huge delay in web # turnaround. Log the moderation and add a header. msg['X-Mailman-Approved-At'] = email.utils.formatdate(localtime=1) - syslog('vette', '%s: held message approved, message-id: %s', + mailman_log('vette', '%s: held message approved, message-id: %s', self.internal_name(), msg.get('message-id', 'n/a')) # Stick the message back in the incoming queue for further @@ -435,7 +435,7 @@ def __handlepost(self, record, value, comment, preserve, forward, addr): } if comment: note += '\n\tReason: ' + comment.replace('%', '%%') - syslog('vette', note) + mailman_log('vette', note) # Always unlink the file containing the message text. It's not # necessary anymore, regardless of the disposition of the message. if status != DEFER: @@ -467,7 +467,7 @@ def HoldSubscription(self, addr, fullname, password, digest, lang): # # TBD: this really shouldn't go here but I'm not sure where else is # appropriate. - syslog('vette', '%s: held subscription request from %s', + mailman_log('vette', '%s: held subscription request from %s', self.internal_name(), addr) # Possibly notify the administrator in default list language if self.admin_immed_notify: @@ -497,13 +497,13 @@ def __handlesubscription(self, record, value, comment): if value == mm_cfg.DEFER: return DEFER elif value == mm_cfg.DISCARD: - syslog('vette', '%s: discarded subscription request from %s', + mailman_log('vette', '%s: discarded subscription request from %s', self.internal_name(), addr) elif value == mm_cfg.REJECT: self.__refuse(_('Subscription request'), addr, comment or _('[No reason given]'), lang=lang) - syslog('vette', """%s: rejected subscription request from %s + mailman_log('vette', """%s: rejected subscription request from %s \tReason: %s""", self.internal_name(), addr, comment or '[No reason given]') else: # subscribe @@ -529,7 +529,7 @@ def HoldUnsubscription(self, addr): id = self.__nextid() # All we need to do is save the unsubscribing address self.__db[id] = (UNSUBSCRIPTION, addr) - syslog('vette', '%s: held unsubscription request from %s', + mailman_log('vette', '%s: held unsubscription request from %s', self.internal_name(), addr) # Possibly notify the administrator of the hold if self.admin_immed_notify: @@ -555,11 +555,11 @@ def __handleunsubscription(self, record, value, comment): if value == mm_cfg.DEFER: return DEFER elif value == mm_cfg.DISCARD: - syslog('vette', '%s: discarded unsubscription request from %s', + mailman_log('vette', '%s: discarded unsubscription request from %s', self.internal_name(), addr) elif value == mm_cfg.REJECT: self.__refuse(_('Unsubscription request'), addr, comment) - syslog('vette', """%s: rejected unsubscription request from %s + mailman_log('vette', """%s: rejected unsubscription request from %s \tReason: %s""", self.internal_name(), addr, comment or '[No reason given]') else: assert value == mm_cfg.UNSUBSCRIBE diff --git a/Mailman/Logging/Syslog.py b/Mailman/Logging/Syslog.py index 65962d9a..101e2ce9 100644 --- a/Mailman/Logging/Syslog.py +++ b/Mailman/Logging/Syslog.py @@ -75,8 +75,8 @@ def close(self): logger.close() self._logfiles.clear() - def syslog(self, ident, msg): - """Log a message to syslog.""" + def mailman_log(self, ident, msg): + """Log a message to mailman's logging system.""" if isinstance(msg, bytes): msg = msg.decode('iso-8859-1', 'replace') elif not isinstance(msg, str): @@ -85,12 +85,15 @@ def syslog(self, ident, msg): _syslog = _Syslog() -def syslog(ident, msg, *args): - """Log a message to syslog.""" +def mailman_log(ident, msg, *args): + """Log a message to mailman's logging system.""" if isinstance(msg, bytes): msg = msg.decode('iso-8859-1', 'replace') elif not isinstance(msg, str): msg = str(msg) if args: msg = msg % args - _syslog.syslog(ident, msg) + _syslog.mailman_log(ident, msg) + +# For backward compatibility +syslog = mailman_log diff --git a/Mailman/Queue/OutgoingRunner.py b/Mailman/Queue/OutgoingRunner.py index 44ebc010..cb4d99b1 100644 --- a/Mailman/Queue/OutgoingRunner.py +++ b/Mailman/Queue/OutgoingRunner.py @@ -33,7 +33,7 @@ from Mailman.Queue.Runner import Runner from Mailman.Queue.Switchboard import Switchboard from Mailman.Queue.BounceRunner import BounceMixin -from Mailman.Logging.Syslog import syslog +from Mailman.Logging.Syslog import mailman_log # This controls how often _doperiodic() will try to deal with deferred # permanent failures. It is a count of calls to _doperiodic() @@ -44,67 +44,67 @@ class OutgoingRunner(Runner, BounceMixin): QDIR = mm_cfg.OUTQUEUE_DIR def __init__(self, slice=None, numslices=1): - syslog('debug', 'OutgoingRunner: Starting initialization') + mailman_log('debug', 'OutgoingRunner: Starting initialization') try: Runner.__init__(self, slice, numslices) - syslog('debug', 'OutgoingRunner: Base Runner initialized') + mailman_log('debug', 'OutgoingRunner: Base Runner initialized') BounceMixin.__init__(self) - syslog('debug', 'OutgoingRunner: BounceMixin initialized') + mailman_log('debug', 'OutgoingRunner: BounceMixin initialized') # We look this function up only at startup time modname = 'Mailman.Handlers.' + mm_cfg.DELIVERY_MODULE - syslog('debug', 'OutgoingRunner: Attempting to import delivery module: %s', modname) + mailman_log('debug', 'OutgoingRunner: Attempting to import delivery module: %s', modname) try: mod = __import__(modname) - syslog('debug', 'OutgoingRunner: Successfully imported delivery module') + mailman_log('debug', 'OutgoingRunner: Successfully imported delivery module') except ImportError as e: - syslog('error', 'OutgoingRunner: Failed to import delivery module %s: %s', modname, str(e)) - syslog('error', 'OutgoingRunner: Traceback: %s', traceback.format_exc()) + mailman_log('error', 'OutgoingRunner: Failed to import delivery module %s: %s', modname, str(e)) + mailman_log('error', 'OutgoingRunner: Traceback: %s', traceback.format_exc()) raise try: self._func = getattr(sys.modules[modname], 'process') - syslog('debug', 'OutgoingRunner: Successfully got process function from module') + mailman_log('debug', 'OutgoingRunner: Successfully got process function from module') except AttributeError as e: - syslog('error', 'OutgoingRunner: Failed to get process function from module %s: %s', modname, str(e)) - syslog('error', 'OutgoingRunner: Traceback: %s', traceback.format_exc()) + mailman_log('error', 'OutgoingRunner: Failed to get process function from module %s: %s', modname, str(e)) + mailman_log('error', 'OutgoingRunner: Traceback: %s', traceback.format_exc()) raise # This prevents smtp server connection problems from filling up the # error log. It gets reset if the message was successfully sent, and # set if there was a socket.error. self.__logged = False - syslog('debug', 'OutgoingRunner: Initializing retry queue') + mailman_log('debug', 'OutgoingRunner: Initializing retry queue') self.__retryq = Switchboard(mm_cfg.RETRYQUEUE_DIR) - syslog('debug', 'OutgoingRunner: Initialization complete') + mailman_log('debug', 'OutgoingRunner: Initialization complete') except Exception as e: - syslog('error', 'OutgoingRunner: Initialization failed: %s', str(e)) - syslog('error', 'OutgoingRunner: Traceback: %s', traceback.format_exc()) + mailman_log('error', 'OutgoingRunner: Initialization failed: %s', str(e)) + mailman_log('error', 'OutgoingRunner: Traceback: %s', traceback.format_exc()) raise def _dispose(self, mlist, msg, msgdata): - syslog('debug', 'OutgoingRunner: Processing message for list %s', mlist.internal_name()) + mailman_log('debug', 'OutgoingRunner: Processing message for list %s', mlist.internal_name()) # See if we should retry delivery of this message again. deliver_after = msgdata.get('deliver_after', 0) if time.time() < deliver_after: - syslog('debug', 'OutgoingRunner: Message not ready for delivery yet, waiting until %s', + mailman_log('debug', 'OutgoingRunner: Message not ready for delivery yet, waiting until %s', time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(deliver_after))) return True # Make sure we have the most up-to-date state - syslog('debug', 'OutgoingRunner: Loading list state') + mailman_log('debug', 'OutgoingRunner: Loading list state') mlist.Load() try: pid = os.getpid() - syslog('debug', 'OutgoingRunner: Attempting to deliver message') + mailman_log('debug', 'OutgoingRunner: Attempting to deliver message') self._func(mlist, msg, msgdata) # Failsafe -- a child may have leaked through. if pid != os.getpid(): - syslog('error', 'OutgoingRunner: child process leaked thru: %s', modname) + mailman_log('error', 'OutgoingRunner: child process leaked thru: %s', modname) os._exit(1) self.__logged = False - syslog('debug', 'OutgoingRunner: Message delivered successfully') + mailman_log('debug', 'OutgoingRunner: Message delivered successfully') except socket.error: # There was a problem connecting to the SMTP server. Log this # once, but crank up our sleep time so we don't fill the error @@ -114,30 +114,30 @@ def _dispose(self, mlist, msg, msgdata): port = 'smtp' # Log this just once. if not self.__logged: - syslog('error', 'OutgoingRunner: Cannot connect to SMTP server %s on port %s', + mailman_log('error', 'OutgoingRunner: Cannot connect to SMTP server %s on port %s', mm_cfg.SMTPHOST, port) self.__logged = True self._snooze(0) return True except Errors.SomeRecipientsFailed as e: - syslog('debug', 'OutgoingRunner: Some recipients failed: %s', str(e)) + mailman_log('debug', 'OutgoingRunner: Some recipients failed: %s', str(e)) # Handle local rejects of probe messages differently. if msgdata.get('probe_token') and e.permfailures: - syslog('debug', 'OutgoingRunner: Handling probe bounce') + mailman_log('debug', 'OutgoingRunner: Handling probe bounce') self._probe_bounce(mlist, msgdata['probe_token']) else: # Delivery failed at SMTP time for some or all of the # recipients. Permanent failures are registered as bounces, # but temporary failures are retried for later. if e.permfailures: - syslog('debug', 'OutgoingRunner: Queueing permanent failures as bounces') + mailman_log('debug', 'OutgoingRunner: Queueing permanent failures as bounces') self._queue_bounces(mlist.internal_name(), e.permfailures, msg) # Move temporary failures to the qfiles/retry queue which will # occasionally move them back here for another shot at # delivery. if e.tempfailures: - syslog('debug', 'OutgoingRunner: Queueing temporary failures for retry') + mailman_log('debug', 'OutgoingRunner: Queueing temporary failures for retry') now = time.time() recips = e.tempfailures last_recip_count = msgdata.get('last_recip_count', 0) @@ -147,7 +147,7 @@ def _dispose(self, mlist, msg, msgdata): # delivery any longer. BAW: is this the best # disposition? if now > deliver_until: - syslog('debug', 'OutgoingRunner: No progress made, giving up on message') + mailman_log('debug', 'OutgoingRunner: No progress made, giving up on message') return False else: # Keep trying to delivery this message for a while @@ -160,8 +160,8 @@ def _dispose(self, mlist, msg, msgdata): msgdata['recips'] = recips self.__retryq.enqueue(msg, msgdata) except Exception as e: - syslog('error', 'OutgoingRunner: Unexpected error during message processing: %s', str(e)) - syslog('error', 'OutgoingRunner: Traceback: %s', traceback.format_exc()) + mailman_log('error', 'OutgoingRunner: Unexpected error during message processing: %s', str(e)) + mailman_log('error', 'OutgoingRunner: Traceback: %s', traceback.format_exc()) raise # We've successfully completed handling of this message return False @@ -169,7 +169,7 @@ def _dispose(self, mlist, msg, msgdata): _doperiodic = BounceMixin._doperiodic def _cleanup(self): - syslog('debug', 'OutgoingRunner: Starting cleanup') + mailman_log('debug', 'OutgoingRunner: Starting cleanup') BounceMixin._cleanup(self) Runner._cleanup(self) - syslog('debug', 'OutgoingRunner: Cleanup complete') + mailman_log('debug', 'OutgoingRunner: Cleanup complete') diff --git a/Mailman/Utils.py b/Mailman/Utils.py index 2e8424d8..5ddd2ced 100644 --- a/Mailman/Utils.py +++ b/Mailman/Utils.py @@ -51,7 +51,7 @@ from Mailman import Errors from Mailman import Site from Mailman.SafeDict import SafeDict -from Mailman.Logging.Syslog import syslog +from Mailman.Logging.Syslog import mailman_log try: import hashlib @@ -105,7 +105,7 @@ def list_exists(listname): os.environ.get('HTTP_X_FORWARDED_FOR', os.environ.get('REMOTE_ADDR', 'unidentified origin'))) - syslog('mischief', + mailman_log('mischief', 'Hostile listname: listname=%s: remote=%s', listname, remote) return False basepath = Site.get_listpath(listname) @@ -284,7 +284,7 @@ def GetPathPieces(envar='PATH_INFO'): 'unidentified origin'))) if CRNLpat.search(path): path = CRNLpat.split(path)[0] - syslog('error', + mailman_log('error', 'Warning: Possible malformed path attack domain=%s remote=%s', get_domain(), remote) @@ -301,7 +301,7 @@ def GetPathPieces(envar='PATH_INFO'): else: longest = 20 if pieces and len(pieces[0]) > longest: - syslog('mischief', + mailman_log('mischief', 'Hostile listname: listname=%s: remote=%s', pieces[0], remote) pieces[0] = pieces[0][:longest] + '...' return pieces @@ -408,7 +408,7 @@ def Secure_MakeRandomPassword(length): # We have no available source of cryptographically # secure random characters. Log an error and fallback # to the user friendly passwords. - syslog('error', + mailman_log('error', 'urandom not available, passwords not secure') return UserFriendly_MakeRandomPassword(length) newbytes = os.read(fd, length - bytesread) @@ -455,7 +455,7 @@ def set_global_password(pw, siteadmin=True): fp.write(sha_new(pw).hexdigest() + '\n') os.rename(temp_filename, filename) except (IOError, OSError) as e: - syslog('error', 'Failed to write password file %s: %s', filename, str(e)) + mailman_log('error', 'Failed to write password file %s: %s', filename, str(e)) raise finally: os.umask(omask) @@ -470,12 +470,12 @@ def get_global_password(siteadmin=True): with open(filename) as fp: challenge = fp.read()[:-1] # strip off trailing nl if not challenge: - syslog('error', 'Empty password file: %s', filename) + mailman_log('error', 'Empty password file: %s', filename) return None return challenge except IOError as e: if e.errno != errno.ENOENT: - syslog('error', 'Error reading password file %s: %s', filename, str(e)) + mailman_log('error', 'Error reading password file %s: %s', filename, str(e)) return None @@ -706,7 +706,7 @@ def maketext(templatefile, dict=None, raw=0, lang=None, mlist=None): if mlist: paths.append(os.path.join(mlist.fullpath(), 'templates', templatefile)) paths.append(os.path.join(mm_cfg.TEMPLATE_DIR, templatefile)) - syslog('error', 'Template file not found: %s (language: %s). Searched paths: %s', + mailman_log('error', 'Template file not found: %s (language: %s). Searched paths: %s', templatefile, lang or 'default', ', '.join(paths)) return '' # Return empty string instead of None if raw: @@ -722,7 +722,7 @@ def maketext(templatefile, dict=None, raw=0, lang=None, mlist=None): try: text = template % dict except (ValueError, TypeError) as e: - syslog('error', 'Template interpolation error for %s: %s', + mailman_log('error', 'Template interpolation error for %s: %s', templatefile, str(e)) return '' # Return empty string instead of None return text @@ -1283,7 +1283,7 @@ def get_suffixes(url): try: d = urllib.request.urlopen(url) except (urllib.error.URLError, urllib.error.HTTPError) as e: - syslog('error', 'Failed to fetch DMARC organizational domain data from %s: %s', + mailman_log('error', 'Failed to fetch DMARC organizational domain data from %s: %s', url, e) return for line in d.readlines(): @@ -1347,7 +1347,7 @@ def get_org_dom(domain): def IsDMARCProhibited(mlist, email): if not dns_resolver: # This is a problem; log it. - syslog('error', + mailman_log('error', 'DNS lookup for dmarc_moderation_action for list %s not available', mlist.real_name) return False @@ -1573,17 +1573,17 @@ def banned_ip(ip): # IP not found in blocklist return False except dns.resolver.Timeout: - syslog('error', 'DNS timeout checking IP %s in Spamhaus', ip) + mailman_log('error', 'DNS timeout checking IP %s in Spamhaus', ip) return False except dns.resolver.NoAnswer: - syslog('error', 'No DNS answer for IP %s in Spamhaus', ip) + mailman_log('error', 'No DNS answer for IP %s in Spamhaus', ip) return False except dns.exception.DNSException as e: - syslog('error', 'DNS error checking IP %s in Spamhaus: %s', ip, str(e)) + mailman_log('error', 'DNS error checking IP %s in Spamhaus: %s', ip, str(e)) return False except Exception as e: - syslog('error', 'Error checking IP %s in Spamhaus: %s', ip, str(e)) + mailman_log('error', 'Error checking IP %s in Spamhaus: %s', ip, str(e)) return False return False @@ -1620,16 +1620,16 @@ def banned_domain(email): # Domain not found in blocklist return False except dns.resolver.Timeout: - syslog('error', 'DNS timeout checking domain %s in Spamhaus DBL', domain) + mailman_log('error', 'DNS timeout checking domain %s in Spamhaus DBL', domain) return False except dns.resolver.NoAnswer: - syslog('error', 'No DNS answer for domain %s in Spamhaus DBL', domain) + mailman_log('error', 'No DNS answer for domain %s in Spamhaus DBL', domain) return False except dns.exception.DNSException as e: - syslog('error', 'DNS error checking domain %s in Spamhaus DBL: %s', domain, str(e)) + mailman_log('error', 'DNS error checking domain %s in Spamhaus DBL: %s', domain, str(e)) return False except Exception as e: - syslog('error', 'Unexpected error checking domain %s in Spamhaus DBL: %s', domain, str(e)) + mailman_log('error', 'Unexpected error checking domain %s in Spamhaus DBL: %s', domain, str(e)) return False return False From bd584bfe1b9ee7d9f279bb45953a00663936936d Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 21:18:32 -0400 Subject: [PATCH 109/748] update --- Mailman/Handlers/SMTPDirect.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Mailman/Handlers/SMTPDirect.py b/Mailman/Handlers/SMTPDirect.py index 20b2e256..efc07b98 100644 --- a/Mailman/Handlers/SMTPDirect.py +++ b/Mailman/Handlers/SMTPDirect.py @@ -222,8 +222,12 @@ def process(mlist, msg, msgdata): # still worthwhile doing the interpolation in syslog() because it'll catch # any catastrophic exceptions due to bogus format strings. if mm_cfg.SMTP_LOG_EVERY_MESSAGE: - mailman_log.write_ex(mm_cfg.SMTP_LOG_EVERY_MESSAGE[0], - mm_cfg.SMTP_LOG_EVERY_MESSAGE[1], kws=d) + mailman_log(mm_cfg.SMTP_LOG_EVERY_MESSAGE[0], + mm_cfg.SMTP_LOG_EVERY_MESSAGE[1], + mlist.internal_name(), + origsender, + len(chunk), + time.time() - t0) if refused: if mm_cfg.SMTP_LOG_REFUSED: From b4276304aea75d79055dfb790834ad0bc0a1b2c6 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 21:25:07 -0400 Subject: [PATCH 110/748] update --- Mailman/Cgi/admin.py | 33 ++++++++++++++++++-- Mailman/MailList.py | 8 ++++- templates/ja/article.html | 23 +++++++------- templates/ja/listinfo.html | 62 +++++++++++++++++++------------------- templates/ja/roster.html | 16 +++++----- 5 files changed, 88 insertions(+), 54 deletions(-) diff --git a/Mailman/Cgi/admin.py b/Mailman/Cgi/admin.py index a71f0886..d821dc3a 100644 --- a/Mailman/Cgi/admin.py +++ b/Mailman/Cgi/admin.py @@ -727,7 +727,16 @@ def get_item_gui_value(mlist, category, kind, varname, params, extra): kind == mm_cfg.Host or kind == mm_cfg.Number): # Ensure value is a string, decoding bytes if necessary if isinstance(value, bytes): - value = value.decode('utf-8', 'replace') + try: + # Try UTF-8 first + value = value.decode('utf-8', 'replace') + except UnicodeDecodeError: + try: + # Try EUC-JP for Japanese text + value = value.decode('euc-jp', 'replace') + except UnicodeDecodeError: + # Fall back to latin1 + value = value.decode('latin1', 'replace') return TextBox(varname, value, params) elif kind == mm_cfg.Text: if params: @@ -736,7 +745,16 @@ def get_item_gui_value(mlist, category, kind, varname, params, extra): r, c = None, None # Ensure value is a string, decoding bytes if necessary if isinstance(value, bytes): - value = value.decode('utf-8', 'replace') + try: + # Try UTF-8 first + value = value.decode('utf-8', 'replace') + except UnicodeDecodeError: + try: + # Try EUC-JP for Japanese text + value = value.decode('euc-jp', 'replace') + except UnicodeDecodeError: + # Fall back to latin1 + value = value.decode('latin1', 'replace') return TextArea(varname, value or '', r, c) elif kind in (mm_cfg.EmailList, mm_cfg.EmailListEx): if params: @@ -745,7 +763,16 @@ def get_item_gui_value(mlist, category, kind, varname, params, extra): r, c = None, None # Ensure value is a string, decoding bytes if necessary if isinstance(value, bytes): - value = value.decode('utf-8', 'replace') + try: + # Try UTF-8 first + value = value.decode('utf-8', 'replace') + except UnicodeDecodeError: + try: + # Try EUC-JP for Japanese text + value = value.decode('euc-jp', 'replace') + except UnicodeDecodeError: + # Fall back to latin1 + value = value.decode('latin1', 'replace') res = NL.join(value) return TextArea(varname, res, r, c, wrap='off') elif kind == mm_cfg.FileUpload: diff --git a/Mailman/MailList.py b/Mailman/MailList.py index d676e483..a1397704 100644 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -673,9 +673,15 @@ def Save(self): elif isinstance(value, bytes): # Convert bytes to string if possible try: + # Try UTF-8 first dict[key] = value.decode('utf-8', 'replace') except UnicodeDecodeError: - dict[key] = value.decode('latin1', 'replace') + try: + # Try EUC-JP for Japanese text + dict[key] = value.decode('euc-jp', 'replace') + except UnicodeDecodeError: + # Fall back to latin1 + dict[key] = value.decode('latin1', 'replace') elif isinstance(value, list): # Handle lists that might contain bytes dict[key] = [ diff --git a/templates/ja/article.html b/templates/ja/article.html index 56d3237c..51450c79 100644 --- a/templates/ja/article.html +++ b/templates/ja/article.html @@ -3,6 +3,7 @@ %(title)s + @@ -25,11 +26,11 @@

                %(subject_html)s


                @@ -41,15 +42,15 @@

                %(subject_html)s

                %(prev_wsubj)s %(next_wsubj)s -
              • µ­»ö¤Îʤӽç: - [ ÆüÉÕ ] - [ ¥¹¥ì¥Ã¥É ] - [ ·ï̾ ] - [ Ãø¼Ô ] +
              • �������¤ӽ�: + [ ���� ] + [ ����å� ] + [ ��̾ ] + [ ���� ]

              • -%(listname)s ¥á¡¼¥ê¥ó¥°¥ê¥¹¥È¤Î°ÆÆâ +%(listname)s �᡼��󥰥ꥹ�Ȥΰ���
                diff --git a/templates/ja/listinfo.html b/templates/ja/listinfo.html index 3aa105d7..59b357f6 100644 --- a/templates/ja/listinfo.html +++ b/templates/ja/listinfo.html @@ -2,8 +2,8 @@ - <MM-List-Name> °ÆÆâ¥Ú¡¼¥¸ - + <MM-List-Name> Ú¡ + @@ -22,7 +22,7 @@
                - - + + + + %(textlink)s - diff --git a/templates/ar/archtocnombox.html b/templates/ar/archtocnombox.html index 80d53868..31d58097 100644 --- a/templates/ar/archtocnombox.html +++ b/templates/ar/archtocnombox.html @@ -1,19 +1,83 @@ + - - Ø£Ø±Ø´ÙŠÙØ§Øª القائمة %(listname)s - - + +Ø£Ø±Ø´ÙŠÙØ§Øª القائمة %(listname)s + + %(meta)s - - -

                Ø£Ø±Ø´ÙŠÙØ§Øª القائمة %(listname)s

                -

                + + +

                Ø£Ø±Ø´ÙŠÙØ§Øª القائمة %(listname)s

                +

                You can get معلومات أكثر حول هذه القائمة.

                %(noarchive_msg)s %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/ar/article.html b/templates/ar/article.html index 217244ab..694d0c61 100644 --- a/templates/ar/article.html +++ b/templates/ar/article.html @@ -1,13 +1,14 @@ + - - - %(title)s - - - - - diff --git a/templates/ar/headfoot.html b/templates/ar/headfoot.html index f6b20566..f6032a43 100644 --- a/templates/ar/headfoot.html +++ b/templates/ar/headfoot.html @@ -1,8 +1,71 @@ -يمكن لهذا النص أن يتضمنمحار٠تنسيق بايثون +يمكن لهذا النص أن يتضمنمحار٠تنسيق بايثون والتي سيتم حلها مع Ù…ÙˆØ§ØµÙØ§Øª القائمة. قائمة الاستبدالات المسموحة هي:
                  -
                • real_name - الاسم الجميل للقائمة، عادة يكون اسم القائمة مع تكبير الأحرÙ. +
                • real_name - الاسم الجميل للقائمة، عادة يكون اسم القائمة مع تكبير الأحرÙ.
                • list_name - اسم القائمة الذي يتم استخدامه لتحديد القائمة ÙÙŠ الارتباطات، حيث تكون حالة الأحر٠مؤثرة. @@ -20,4 +83,4 @@
                • info - التعري٠الكامل للقائمة البريدية
                • cgiext - الامتداد المضا٠إلى البرمجيات النصية CGI. -
                + diff --git a/templates/ar/listinfo.html b/templates/ar/listinfo.html index 0d2f23bc..c49fc809 100644 --- a/templates/ar/listinfo.html +++ b/templates/ar/listinfo.html @@ -1,144 +1,206 @@ + - - <MM-List-Name> ØµÙØ­Ø© المعلومات - - - - -

                -

                \n" +#~ " \n" #~ "\tالتحقق من الشخصية للأرشي٠الخاص " #~ "بالقائمة %(realname)s\n" #~ "
                - ¤Ë¤Ä¤¤¤Æ + �ˤĤ��� @@ -33,85 +33,85 @@

                -

                ¤³¤Î¥á¡¼¥ê¥ó¥°¥ê¥¹¥È¤ËÅê¹Æ¤µ¤ì¤¿²áµî¤Î¥á¡¼¥ë¤Ï¡¢ +

                ���Υ᡼��󥰥ꥹ�Ȥ���Ƥ��줿���Υ᡼��ϡ� - Êݸ½ñ¸Ë¤ò¤´Í÷²¼¤µ¤¤¡£ + ��¸���������������

                - ¤ÎÍøÍÑË¡ + ������ˡ
                - ¥á¡¼¥ê¥ó¥°¥ê¥¹¥È¤ÎÁ´²ñ°÷¤ËÁ÷¤ë¥á¡¼¥ë¤Ï¡¢ + �᡼��󥰥ꥹ�Ȥ������������᡼��ϡ� - ¤Î¥¢¥É¥ì¥¹°¸¤ËÁ÷¿®¤·¤Æ¤¯¤À¤µ¤¤¡£ + �Υ��ɥ쥹�����������Ƥ��������� -

                ¥á¡¼¥ê¥ó¥°¥ê¥¹¥È¤ÎÆþ²ñ¤ä¸½ºß¤Î²ñ°÷¥ª¥×¥·¥ç¥ó¤ÎÊѹ¹¤Ï¡¢ - °Ê²¼¤Î¥Õ¥©¡¼¥à¤ò¤´ÍøÍѤ¯¤À¤µ¤¤¡£ +

                �᡼��󥰥ꥹ�Ȥ�����丽�ߤβ�����ץ������ѹ��ϡ� + �ʲ��Υե���������Ѥ���������

                - ¤Î¹ØÆÉ + ���

                - ¤Ø¤ÎÆþ²ñ¤Ï¡¢ - °Ê²¼¤Î¥Õ¥©¡¼¥à¤ËɬÍ×»ö¹à¤òµ­Æþ¤·¤Æ¤¯¤À¤µ¤¤¡£ + �ؤ�����ϡ� + �ʲ��Υե������ɬ�׻���������Ƥ���������

                  - + - + - - + - + - + - - @@ -128,7 +128,7 @@ diff --git a/templates/ja/roster.html b/templates/ja/roster.html index b0ca54c9..27950a61 100644 --- a/templates/ja/roster.html +++ b/templates/ja/roster.html @@ -1,8 +1,8 @@ - <MM-List-Name> ²ñ°÷̾Êí - + <MM-List-Name> ̾ + @@ -11,7 +11,7 @@
                  ¥á¡¼¥ë¥¢¥É¥ì¥¹:�᡼�륢�ɥ쥹:  
                  ̾Á° (¾Êά²Ä):̾�� (����):  
                  ¾ðÊó¤ÎÊݸî¤Ë»È¤¦¥Ñ¥¹¥ï¡¼¥É¤ò - »ØÄꤹ¤ë¤³¤È¤â¤Ç¤­¤Þ¤¹¡£¹âÅ٤ʥ»¥­¥å¥ê¥Æ¥£¤Ç¤Ï¤¢¤ê¤Þ¤»¤ó¤¬¡¢ - ¾¿Í¤Ë²ñ°÷¾ðÊó¤òÊѹ¹¤µ¤ì¤ë¤³¤È¤òËɤ°¤³¤È¤Ï¤Ç¤­¤ë¤Ï¤º¤Ç¤¹¡£ - ¤³¤Î¥Ñ¥¹¥ï¡¼¥É¤Ï¡¢°Å¹æ²½¤»¤º¤Ë¥á¡¼¥ë¤ÇÁ÷¿®¤µ¤ì¤ë¾ì¹ç¤¬¤¢¤ê¤Þ¤¹ - ¤Î¤Ç¡¢¥·¥¹¥Æ¥à¥Ñ¥¹¥ï¡¼¥É¤Ê¤É¤Î ½ÅÍפʥѥ¹¥ï¡¼¥É¤ò»ØÄꤷ¤Ê¤¤ - ¤è¤¦¤Ë¤·¤Æ¤¯¤À¤µ¤¤¡£ + ������ݸ�˻Ȥ��ѥ���ɤ� + ���ꤹ�뤳�Ȥ�Ǥ��ޤ������٤ʥ������ƥ��ǤϤ���ޤ��󤬡� + ¾�ͤ˲��������ѹ�����뤳�Ȥ��ɤ����ȤϤǤ���Ϥ��Ǥ��� + ���Υѥ���ɤϡ��Ź沽�����˥᡼�������������礬����ޤ� + �Τǡ������ƥ�ѥ���ɤʤɤ� ���פʥѥ���ɤ���ꤷ�ʤ� + �褦�ˤ��Ƥ���������
                  - ¥Ñ¥¹¥ï¡¼¥É¤¬»ØÄꤵ¤ì¤Ê¤«¤Ã¤¿¾ì¹ç¡¢¥Ñ¥¹¥ï¡¼¥É¤ò¼«Æ°Åª¤ËÀ¸À®¤·¤Æ - Æþ²ñ³Îǧ¸å¤Ë¥á¡¼¥ë¤ÇÁ÷ÉÕ¤·¤Þ¤¹¡£ - ¤¤¤Ä¤Ç¤â¤³¤Î¥Ñ¥¹¥ï¡¼¥É¤ò¥á¡¼¥ë¤Ç¼è¤ê´ó¤»¤ë¤³¤È¤¬¤Ç¤­¤Þ¤¹¡£ + �ѥ���ɤ����ꤵ��ʤ��ä���硢�ѥ���ɤ�ưŪ���������� + �����ǧ��˥᡼������դ��ޤ��� + ���ĤǤ⤳�Υѥ���ɤ�᡼��Ǽ��󤻤뤳�Ȥ��Ǥ��ޤ���
                  ¥Ñ¥¹¥ï¡¼¥É¤òÆþÎϤ·¤Æ¤¯¤À¤µ¤¤:�ѥ���ɤ����Ϥ��Ƥ�������:  
                  ³Îǧ¤Î¤¿¤áƱ¤¸¥Ñ¥¹¥ï¡¼¥É¤òºÆÆþÎÏ:��ǧ�Τ���Ʊ���ѥ���ɤ������:  
                  ɽ¼¨¤Ë»È¤¦¸À¸ì¤òÁª¤ó¤Ç¤¯¤À¤µ¤¤É½ï¿½ï¿½ï¿½Ë»È¤ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½Ç¤ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½  
                  ¥ê¥¹¥È¤Î¥á¡¼¥ë¤òËèÆü1ËÜ¤Ë¤Þ¤È¤á¤ÆÁ÷¤ê¤Þ¤¹¤«? + �ꥹ�ȤΥ᡼�������1�ܤˤޤȤ������ޤ���? ¤¤¤¤¤¨ - ¤Ï¤¤ + ������ + �Ϥ�
                  - ²ñ°÷¤ÎÊý¤Ø + ���������
                  - ²ñ°÷̾Êí + ̾ @@ -22,22 +22,22 @@ From 349d1be73a6f53e934009bd2a288ae652bd80af5 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 21:26:13 -0400 Subject: [PATCH 111/748] update --- Mailman/Handlers/SMTPDirect.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Mailman/Handlers/SMTPDirect.py b/Mailman/Handlers/SMTPDirect.py index efc07b98..4d1cc22e 100644 --- a/Mailman/Handlers/SMTPDirect.py +++ b/Mailman/Handlers/SMTPDirect.py @@ -223,11 +223,12 @@ def process(mlist, msg, msgdata): # any catastrophic exceptions due to bogus format strings. if mm_cfg.SMTP_LOG_EVERY_MESSAGE: mailman_log(mm_cfg.SMTP_LOG_EVERY_MESSAGE[0], - mm_cfg.SMTP_LOG_EVERY_MESSAGE[1], - mlist.internal_name(), - origsender, - len(chunk), - time.time() - t0) + mm_cfg.SMTP_LOG_EVERY_MESSAGE[1] % { + 'listname': mlist.internal_name(), + 'sender': origsender, + 'chunksize': len(chunk), + 'time': time.time() - t0 + }) if refused: if mm_cfg.SMTP_LOG_REFUSED: From c3f917d6b5bdc7758ae53a113202b5ff19e7c2d2 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 22:07:00 -0400 Subject: [PATCH 112/748] update to avoid conflicts --- Mailman/Bouncers/GroupWise.py | 4 +-- Mailman/Handlers/Decorate.py | 2 +- Mailman/Handlers/SMTPDirect.py | 28 +++++++++++++++++ Mailman/Handlers/ToDigest.py | 7 +++-- Mailman/ListAdmin.py | 56 +++++++++++++++++++++++++++++++-- Mailman/MailList.py | 22 ++++++------- Mailman/Mailbox.py | 3 +- Mailman/Queue/BounceRunner.py | 1 + Mailman/Queue/MaildirRunner.py | 4 +-- Mailman/Queue/OutgoingRunner.py | 3 +- Mailman/Queue/Runner.py | 6 ++-- Mailman/Queue/Switchboard.py | 27 ++++++++++++++++ Mailman/versions.py | 4 +-- bin/update | 9 ++++-- cron/gate_news | 3 +- 15 files changed, 146 insertions(+), 33 deletions(-) diff --git a/Mailman/Bouncers/GroupWise.py b/Mailman/Bouncers/GroupWise.py index 87de1033..721b7660 100644 --- a/Mailman/Bouncers/GroupWise.py +++ b/Mailman/Bouncers/GroupWise.py @@ -22,7 +22,7 @@ """ import re -from email.message import Message +import email.message from io import StringIO acre = re.compile(r'<(?P[^>]*)>') @@ -33,7 +33,7 @@ def find_textplain(msg): return msg if msg.is_multipart: for part in msg.get_payload(): - if not isinstance(part, Message): + if not isinstance(part, email.message.Message): continue ret = find_textplain(part) if ret: diff --git a/Mailman/Handlers/Decorate.py b/Mailman/Handlers/Decorate.py index 67b6cb39..27aee9bd 100644 --- a/Mailman/Handlers/Decorate.py +++ b/Mailman/Handlers/Decorate.py @@ -25,7 +25,7 @@ from Mailman import mm_cfg from Mailman import Utils from Mailman import Errors -from Mailman.Message import Message +import Mailman.Message from Mailman.i18n import _ from Mailman.SafeDict import SafeDict from Mailman.Logging.Syslog import syslog diff --git a/Mailman/Handlers/SMTPDirect.py b/Mailman/Handlers/SMTPDirect.py index 4d1cc22e..42466ce6 100644 --- a/Mailman/Handlers/SMTPDirect.py +++ b/Mailman/Handlers/SMTPDirect.py @@ -134,6 +134,20 @@ def quit(self): def process(mlist, msg, msgdata): + # Convert email.message.Message to Mailman.Message if needed + if isinstance(msg, email.message.Message) and not isinstance(msg, Mailman.Message.Message): + mailman_msg = Mailman.Message.Message() + # Copy all attributes from the original message + for key, value in msg.items(): + mailman_msg[key] = value + # Copy the payload + if msg.is_multipart(): + for part in msg.get_payload(): + mailman_msg.attach(part) + else: + mailman_msg.set_payload(msg.get_payload()) + msg = mailman_msg + recips = msgdata.get('recips') if not recips: # Nobody to deliver to! @@ -398,6 +412,20 @@ def verpdeliver(mlist, msg, msgdata, envsender, failures, conn): def bulkdeliver(mlist, msg, msgdata, envsender, failures, conn): + # Convert email.message.Message to Mailman.Message if needed + if isinstance(msg, email.message.Message) and not isinstance(msg, Mailman.Message.Message): + mailman_msg = Mailman.Message.Message() + # Copy all attributes from the original message + for key, value in msg.items(): + mailman_msg[key] = value + # Copy the payload + if msg.is_multipart(): + for part in msg.get_payload(): + mailman_msg.attach(part) + else: + mailman_msg.set_payload(msg.get_payload()) + msg = mailman_msg + # Do some final cleanup of the message header. Start by blowing away # any the Sender: and Errors-To: headers so remote MTAs won't be # tempted to delivery bounces there instead of our envelope sender diff --git a/Mailman/Handlers/ToDigest.py b/Mailman/Handlers/ToDigest.py index 208de43f..4bbffa55 100644 --- a/Mailman/Handlers/ToDigest.py +++ b/Mailman/Handlers/ToDigest.py @@ -42,12 +42,13 @@ from email.utils import getaddresses, formatdate from email.header import decode_header, make_header, Header from email.charset import Charset -from email.message import Message +import email.message from Mailman import mm_cfg from Mailman import Utils from Mailman import i18n from Mailman import Errors +import Mailman.Message from Mailman.Mailbox import Mailbox from Mailman.MemberAdaptor import ENABLED from Mailman.Handlers.Decorate import decorate @@ -169,7 +170,7 @@ def send_i18n_digests(mlist, mboxfp): # Set things up for the MIME digest. Only headers not added by # CookHeaders need be added here. # Date/Message-ID should be added here also. - mimemsg = Message() + mimemsg = email.message.Message() mimemsg['Content-Type'] = 'multipart/mixed' mimemsg['MIME-Version'] = '1.0' mimemsg['From'] = mlist.GetRequestEmail() @@ -180,7 +181,7 @@ def send_i18n_digests(mlist, mboxfp): mimemsg['Message-ID'] = Utils.unique_message_id(mlist) # Set things up for the rfc1153 digest plainmsg = StringIO() - rfc1153msg = Message() + rfc1153msg = email.message.Message() rfc1153msg['From'] = mlist.GetRequestEmail() rfc1153msg['Subject'] = digestsubj rfc1153msg['To'] = mlist.GetListEmail() diff --git a/Mailman/ListAdmin.py b/Mailman/ListAdmin.py index 93f18ebe..ee9058ee 100644 --- a/Mailman/ListAdmin.py +++ b/Mailman/ListAdmin.py @@ -37,7 +37,7 @@ from email.mime.message import MIMEMessage from email.generator import Generator from email.utils import getaddresses -from email.message import Message +import email.message from Mailman import mm_cfg from Mailman import Utils @@ -351,7 +351,19 @@ def __handlepost(self, record, value, comment, preserve, forward, addr): except IOError as e: if e.errno != errno.ENOENT: raise return LOST - msg = email.message_from_file(fp, Message) + # Convert to Mailman.Message if needed + if isinstance(msg, email.message.Message) and not isinstance(msg, Message.Message): + mailman_msg = Message.Message() + # Copy all attributes from the original message + for key, value in msg.items(): + mailman_msg[key] = value + # Copy the payload + if msg.is_multipart(): + for part in msg.get_payload(): + mailman_msg.attach(part) + else: + mailman_msg.set_payload(msg.get_payload()) + msg = mailman_msg msgdata['approved'] = 1 # adminapproved is used by the Emergency handler msgdata['adminapproved'] = 1 @@ -395,6 +407,19 @@ def __handlepost(self, record, value, comment, preserve, forward, addr): except IOError as e: if e.errno != errno.ENOENT: raise raise Errors.LostHeldMessage(path) + # Convert to Mailman.Message if needed + if isinstance(copy, email.message.Message) and not isinstance(copy, Message.Message): + mailman_msg = Message.Message() + # Copy all attributes from the original message + for key, value in copy.items(): + mailman_msg[key] = value + # Copy the payload + if copy.is_multipart(): + for part in copy.get_payload(): + mailman_msg.attach(part) + else: + mailman_msg.set_payload(copy.get_payload()) + copy = mailman_msg # It's possible the addr is a comma separated list of addresses. addrs = getaddresses([addr]) if len(addrs) == 1: @@ -681,7 +706,6 @@ def _UpdateRecords(self): self.__closedb() - def readMessage(path): # For backwards compatibility, we must be able to read either a flat text # file or a pickle. @@ -690,9 +714,35 @@ def readMessage(path): try: if ext == '.txt': msg = email.message_from_file(fp, Message) + # Convert to Mailman.Message if needed + if isinstance(msg, email.message.Message) and not isinstance(msg, Message.Message): + mailman_msg = Message.Message() + # Copy all attributes from the original message + for key, value in msg.items(): + mailman_msg[key] = value + # Copy the payload + if msg.is_multipart(): + for part in msg.get_payload(): + mailman_msg.attach(part) + else: + mailman_msg.set_payload(msg.get_payload()) + msg = mailman_msg else: assert ext == '.pck' msg = pickle.load(fp, fix_imports=True, encoding='latin1') + # Convert to Mailman.Message if needed + if isinstance(msg, email.message.Message) and not isinstance(msg, Message.Message): + mailman_msg = Message.Message() + # Copy all attributes from the original message + for key, value in msg.items(): + mailman_msg[key] = value + # Copy the payload + if msg.is_multipart(): + for part in msg.get_payload(): + mailman_msg.attach(part) + else: + mailman_msg.set_payload(msg.get_payload()) + msg = mailman_msg finally: fp.close() return msg diff --git a/Mailman/MailList.py b/Mailman/MailList.py index a1397704..26a3a564 100644 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -40,7 +40,7 @@ import email.iterators from email.utils import getaddresses, formataddr, parseaddr from email.header import Header -from email.message import Message +import email.message from Mailman import mm_cfg from Mailman import Utils @@ -71,6 +71,7 @@ from Mailman import Site from Mailman import i18n from Mailman.Logging.Syslog import syslog +from Mailman.Message import OwnerNotification _ = i18n._ def D_(s): @@ -79,7 +80,6 @@ def D_(s): EMPTYSTRING = '' OR = '|' - # Use mixins here just to avoid having any one chunk be too large. class MailList(HTMLFormatter, Deliverer, ListAdmin, Archiver, Digester, SecurityManager, Bouncer, GatewayManager, @@ -1013,7 +1013,7 @@ def InviteNewMember(self, userdesc, text=''): 'listowner' : self.GetOwnerEmail(), }, mlist=self)) sender = self.GetRequestEmail(cookie) - msg = Message.UserNotification( + msg = Mailman.Message.UserNotification( invitee, sender, text=text, lang=self.preferred_language) subj = self.GetConfirmJoinSubject(listname, cookie) @@ -1135,7 +1135,7 @@ def AddMember(self, userdesc, remote=None): 'listadmin' : self.GetOwnerEmail(), 'confirmurl' : confirmurl, }, lang=lang, mlist=self) - msg = Message.UserNotification( + msg = Mailman.Message.UserNotification( recipient, self.GetRequestEmail(cookie), text=text, lang=lang) # BAW: See ChangeMemberAddress() for why we do it this way... @@ -1256,7 +1256,7 @@ def ApprovedAddMember(self, userdesc, ack=None, admin_notif=None, text='', "member" : formataddr((name, email)), "whence" : whence }, mlist=self) - msg = Message.OwnerNotification(self, subject, text) + msg = Mailman.Message.OwnerNotification(self, subject, text) msg.send(self) def DeleteMember(self, name, whence=None, admin_notif=None, userack=True): @@ -1292,7 +1292,7 @@ def ApprovedDeleteMember(self, name, whence=None, 'listname': self.real_name, "whence" : "" if whence is None else "(" + _(whence) + ")" }, mlist=self) - msg = Message.OwnerNotification(self, subject, text) + msg = Mailman.Message.OwnerNotification(self, subject, text) msg.send(self) if whence: whence = "; %s" % whence @@ -1357,7 +1357,7 @@ def ChangeMemberAddress(self, oldaddr, newaddr, globally): 'verify.txt', {'email' : newaddr, 'listaddr' : self.GetListEmail(), - 'listname' : realname, + 'listname' : realname, 'cookie' : cookie, 'requestaddr': self.getListAddress('request'), 'remote' : '', @@ -1371,7 +1371,7 @@ def ChangeMemberAddress(self, oldaddr, newaddr, globally): # CommandRunner doesn't yet grok such headers. So, just set the # Subject: in a separate step, although we have to delete the one # UserNotification adds. - msg = Message.UserNotification( + msg = Mailman.Message.UserNotification( newaddr, self.GetRequestEmail(cookie), text=text, lang=lang) del msg['subject'] @@ -1462,7 +1462,7 @@ def log_and_notify_admin(self, oldaddr, newaddr): 'newaddr' : newaddr, 'listname': self.real_name, }, mlist=self) - msg = Message.OwnerNotification(self, subject, text) + msg = Mailman.Message.OwnerNotification(self, subject, text) msg.send(self) # @@ -1625,7 +1625,7 @@ def ConfirmUnsubscription(self, addr, lang=None, remote=None): 'listadmin' : self.GetOwnerEmail(), 'confirmurl' : confirmurl, }, lang=lang, mlist=self) - msg = Message.UserNotification( + msg = Mailman.Message.UserNotification( addr, self.GetRequestEmail(cookie), text=text, lang=lang) # BAW: See ChangeMemberAddress() for why we do it this way... @@ -1781,7 +1781,7 @@ def autorespondToSender(self, sender, lang=None): 'owneremail': self.GetOwnerEmail(), }, lang=lang) - msg = Message.UserNotification( + msg = Mailman.Message.UserNotification( sender, self.GetOwnerEmail(), _('Last autoresponse notification for today'), text, lang=lang) diff --git a/Mailman/Mailbox.py b/Mailman/Mailbox.py index ed04b83a..fff4bf5d 100644 --- a/Mailman/Mailbox.py +++ b/Mailman/Mailbox.py @@ -26,13 +26,14 @@ from email.errors import MessageParseError from Mailman import mm_cfg +import Mailman.Message from Mailman.Message import Generator from Mailman.Message import Message def _safeparser(fp): try: - return email.message_from_file(fp, Message) + return email.message_from_file(fp, Mailman.Message.Message) except MessageParseError: # Don't return None since that will stop a mailbox iterator return '' diff --git a/Mailman/Queue/BounceRunner.py b/Mailman/Queue/BounceRunner.py index 970d3236..648c994f 100644 --- a/Mailman/Queue/BounceRunner.py +++ b/Mailman/Queue/BounceRunner.py @@ -31,6 +31,7 @@ from Mailman import Utils from Mailman import LockFile from Mailman.Errors import NotAMemberError +import Mailman.Message from Mailman.Message import UserNotification from Mailman.Bouncer import _BounceInfo from Mailman.Bouncers import BouncerAPI diff --git a/Mailman/Queue/MaildirRunner.py b/Mailman/Queue/MaildirRunner.py index 03554c8e..815e96a4 100644 --- a/Mailman/Queue/MaildirRunner.py +++ b/Mailman/Queue/MaildirRunner.py @@ -59,7 +59,7 @@ from Mailman import mm_cfg from Mailman import Utils -from Mailman.Message import Message +import Mailman.Message from Mailman.Queue.Runner import Runner from Mailman.Queue.sbcache import get_switchboard from Mailman.Logging.Syslog import syslog @@ -99,7 +99,7 @@ def __init__(self, slice=None, numslices=1): self._stop = 0 self._dir = os.path.join(mm_cfg.MAILDIR_DIR, 'new') self._cur = os.path.join(mm_cfg.MAILDIR_DIR, 'cur') - self._parser = Parser(Message) + self._parser = Parser(Mailman.Message.Message) def _oneloop(self): # Refresh this each time through the list. BAW: could be too diff --git a/Mailman/Queue/OutgoingRunner.py b/Mailman/Queue/OutgoingRunner.py index cb4d99b1..2fccf9d3 100644 --- a/Mailman/Queue/OutgoingRunner.py +++ b/Mailman/Queue/OutgoingRunner.py @@ -27,7 +27,8 @@ import email from Mailman import mm_cfg -from Mailman import Message +import Mailman.Message +from Mailman.Message import UserNotification from Mailman import Errors from Mailman import LockFile from Mailman.Queue.Runner import Runner diff --git a/Mailman/Queue/Runner.py b/Mailman/Queue/Runner.py index b5350ede..abce68d8 100644 --- a/Mailman/Queue/Runner.py +++ b/Mailman/Queue/Runner.py @@ -28,7 +28,7 @@ from Mailman import Errors from Mailman import MailList from Mailman import i18n -from Mailman.Message import Message as MailmanMessage +import Mailman.Message from Mailman.Logging.Syslog import syslog from Mailman.Queue.Switchboard import Switchboard @@ -156,8 +156,8 @@ def _onefile(self, msg, msgdata): # Find out which mailing list this message is destined for. try: # Convert email.message.Message to Mailman.Message if needed - if not isinstance(msg, MailmanMessage): - mailman_msg = MailmanMessage() + if not isinstance(msg, Mailman.Message.Message): + mailman_msg = Mailman.Message.Message() # Copy all attributes from the original message for key, value in msg.items(): mailman_msg[key] = value diff --git a/Mailman/Queue/Switchboard.py b/Mailman/Queue/Switchboard.py index aec4ac4a..71af4506 100644 --- a/Mailman/Queue/Switchboard.py +++ b/Mailman/Queue/Switchboard.py @@ -40,6 +40,7 @@ import errno import pickle import marshal +import email.message from email.message import Message as EmailMessage from Mailman import mm_cfg @@ -125,6 +126,19 @@ def enqueue(self, _msg, _metadata={}, **_kws): now = time.time() if SAVE_MSGS_AS_PICKLES and not data.get('_plaintext'): protocol = 1 + # Convert email.message.Message to Mailman.Message if needed + if isinstance(_msg, email.message.Message) and not isinstance(_msg, Message.Message): + mailman_msg = Message.Message() + # Copy all attributes from the original message + for key, value in _msg.items(): + mailman_msg[key] = value + # Copy the payload + if _msg.is_multipart(): + for part in _msg.get_payload(): + mailman_msg.attach(part) + else: + mailman_msg.set_payload(_msg.get_payload()) + _msg = mailman_msg msgsave = pickle.dumps(_msg, protocol, fix_imports=True) else: protocol = 0 @@ -194,6 +208,19 @@ def dequeue(self, filebase): if data.get('_parsemsg'): msg = email.message_from_string(msg, EmailMessage) + # Convert to Mailman.Message if needed + if isinstance(msg, email.message.Message) and not isinstance(msg, Message.Message): + mailman_msg = Message.Message() + # Copy all attributes from the original message + for key, value in msg.items(): + mailman_msg[key] = value + # Copy the payload + if msg.is_multipart(): + for part in msg.get_payload(): + mailman_msg.attach(part) + else: + mailman_msg.set_payload(msg.get_payload()) + msg = mailman_msg return msg, data diff --git a/Mailman/versions.py b/Mailman/versions.py index 0e49858d..8d8b30c1 100644 --- a/Mailman/versions.py +++ b/Mailman/versions.py @@ -36,12 +36,12 @@ from builtins import str from builtins import range import email -from email.message import Message +import email.message +import Mailman.Message from Mailman import mm_cfg from Mailman import Utils -from Mailman import Message from Mailman.Bouncer import _BounceInfo from Mailman.MemberAdaptor import UNKNOWN from Mailman.Logging.Syslog import syslog diff --git a/bin/update b/bin/update index e7371617..c1cd2458 100755 --- a/bin/update +++ b/bin/update @@ -44,9 +44,9 @@ import pickle import marshal import paths -import email import email.errors -from email.message import Message +import email.message +import Mailman.Message sys.path.append("@VAR_PREFIX@/Mailman") from Mailman import mm_cfg @@ -58,6 +58,9 @@ from Mailman.i18n import C_ from Mailman.Queue.Switchboard import Switchboard from Mailman.OldStyleMemberships import OldStyleMemberships from Mailman.MemberAdaptor import BYBOUNCE, ENABLED +from Mailman.Bouncer import _BounceInfo +from Mailman.MemberAdaptor import UNKNOWN +from Mailman.Logging.Syslog import syslog FRESH = 0 NOTFRESH = -1 @@ -567,7 +570,7 @@ def dequeue(filebase): msgfp = None try: msgfp = open(msgfile, 'rb') - msg = email.message_from_file(msgfp, Message) + msg = Mailman.Message.Message_from_file(msgfp) os.unlink(msgfile) except EnvironmentError as e: if e.errno != errno.ENOENT: raise diff --git a/cron/gate_news b/cron/gate_news index ceb745c1..c092a1bc 100755 --- a/cron/gate_news +++ b/cron/gate_news @@ -38,7 +38,8 @@ import nntplib import paths # Import this /after/ paths so that the sys.path is properly hacked import email.errors -from email.message import Message +import email.message +import Mailman.Message from email.parser import Parser from Mailman import mm_cfg From 1e6cc3240181c22e221001dba818826c913b2c69 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 22:10:10 -0400 Subject: [PATCH 113/748] update --- Makefile.in | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Makefile.in b/Makefile.in index 33f5bfd0..6d81d3df 100644 --- a/Makefile.in +++ b/Makefile.in @@ -135,7 +135,7 @@ prepare-build: done; \ done -build: all +build: $(PYTHON) $(PYTHON_LIB) @echo "Building Python files..." @if [ -d "build" ]; then \ $(PYTHON) -m compileall -q build; \ @@ -145,6 +145,13 @@ build: all $(PYTHON) -m compileall -q build/cron; \ $(PYTHON) -m compileall -q build/misc; \ $(PYTHON) -m compileall -q build/tests; \ + $(PYTHON) -O -m compileall -q build; \ + $(PYTHON) -O -m compileall -q build/Mailman; \ + $(PYTHON) -O -m compileall -q build/bin; \ + $(PYTHON) -O -m compileall -q build/scripts; \ + $(PYTHON) -O -m compileall -q build/cron; \ + $(PYTHON) -O -m compileall -q build/misc; \ + $(PYTHON) -O -m compileall -q build/tests; \ fi @echo "Build complete." From fca605518df6673edce2b38ef2eab0c19ce61b19 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 22:15:27 -0400 Subject: [PATCH 114/748] add test mode --- cron/mailpasswds | 63 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 50 insertions(+), 13 deletions(-) diff --git a/cron/mailpasswds b/cron/mailpasswds index 00e5b461..73b5aa09 100755 --- a/cron/mailpasswds +++ b/cron/mailpasswds @@ -33,6 +33,12 @@ Options: reminders are sent for all lists. Multiple -l/--listname options are allowed. + -t email + --test=email + Test mode: Send password reminders to the specified email address + instead of the actual list members. Useful for testing the reminder + format and content. + -h/--help Print this message and exit. """ @@ -42,6 +48,7 @@ import sys import os import errno import argparse +import random import paths # mm_cfg must be imported before the other modules, due to the side-effect of @@ -75,6 +82,8 @@ def parse_args(): parser = argparse.ArgumentParser(description='Send password reminders for all lists to all users.') parser.add_argument('-l', '--listname', action='append', help='Send password reminders for the named list only') + parser.add_argument('-t', '--test', metavar='email', + help='Test mode: Send to specified email address instead of members') return parser.parse_args() @@ -119,26 +128,48 @@ def main(): userinfo = {} for mlist in byhost[host]: listaddr = mlist.GetListEmail() - for member in mlist.getMembers(): - # The user may have disabled reminders for this list - if mlist.getMemberOption(member, - mm_cfg.SuppressPasswordReminder): - continue - # Group by the lower-cased address, since Mailman always - # treates person@dom.ain the same as PERSON@dom.ain. + # In test mode, we'll use the test email address for all lists + if args.test: + member = args.test try: password = mlist.getMemberPassword(member) except Errors.NotAMemberError: - # Here's a member with no passwords, which I think was - # possible in older versions of Mailman. Log this and - # move on. - syslog('error', 'password-less member %s for list %s', - member, mlist.internal_name()) - continue + # For test mode, we'll use a real password from a random member + members = mlist.getMembers() + if members: + # Get a random member's password + random_member = random.choice(members) + password = mlist.getMemberPassword(random_member) + else: + # If no members, use a default password + password = "password" optionsurl = mlist.GetOptionsURL(member) lang = mlist.getMemberLanguage(member) info = (listaddr, password, optionsurl, lang) userinfo.setdefault(member, []).append(info) + else: + # Normal mode - process all members + for member in mlist.getMembers(): + # The user may have disabled reminders for this list + if mlist.getMemberOption(member, + mm_cfg.SuppressPasswordReminder): + continue + # Group by the lower-cased address, since Mailman always + # treates person@dom.ain the same as PERSON@dom.ain. + try: + password = mlist.getMemberPassword(member) + except Errors.NotAMemberError: + # Here's a member with no passwords, which I think was + # possible in older versions of Mailman. Log this and + # move on. + syslog('error', 'password-less member %s for list %s', + member, mlist.internal_name()) + continue + optionsurl = mlist.GetOptionsURL(member) + lang = mlist.getMemberLanguage(member) + info = (listaddr, password, optionsurl, lang) + userinfo.setdefault(member, []).append(info) + # Now that we've collected user information for this host, send each # user the password reminder. for addr in userinfo.keys(): @@ -193,6 +224,9 @@ def main(): header = tounicode(header, enc) # Add the table to the end so it doesn't get wrapped/filled text += (header + '\n' + NL.join(table)) + if args.test: + # In test mode, add a note that this is a test message + text += "\n\n[TEST MODE] This is a test password reminder message." msg = Message.UserNotification( addr, siteowner, _('%(host)s mailing list memberships reminder'), @@ -204,6 +238,9 @@ def main(): msg['X-No-Archive'] = 'yes' del msg['auto-submitted'] msg['Auto-Submitted'] = 'auto-generated' + if args.test: + # In test mode, add a test header + msg['X-Mailman-Test-Mode'] = 'yes' # We want to make this look like it's coming from the siteowner's # list, but we also want to be sure that the apparent host name is # the current virtual host. Look in CookHeaders.py for why this From 03f3846120bce0e1c88f540c50f6d8fda9363f24 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 22:18:52 -0400 Subject: [PATCH 115/748] add test mode --- cron/mailpasswds | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/cron/mailpasswds b/cron/mailpasswds index 73b5aa09..ce0aa843 100755 --- a/cron/mailpasswds +++ b/cron/mailpasswds @@ -132,6 +132,7 @@ def main(): if args.test: member = args.test try: + # Try to get the member's password if they are a member password = mlist.getMemberPassword(member) except Errors.NotAMemberError: # For test mode, we'll use a real password from a random member @@ -139,12 +140,21 @@ def main(): if members: # Get a random member's password random_member = random.choice(members) - password = mlist.getMemberPassword(random_member) + try: + password = mlist.getMemberPassword(random_member) + except Errors.NotAMemberError: + # If we can't get the random member's password, skip this list + continue else: - # If no members, use a default password - password = "password" - optionsurl = mlist.GetOptionsURL(member) - lang = mlist.getMemberLanguage(member) + # If no members, skip this list + continue + try: + optionsurl = mlist.GetOptionsURL(member) + except Errors.NotAMemberError: + # If we can't get the options URL, use a default one + optionsurl = f"{mlist.GetBaseURL()}/options/{mlist.internal_name()}" + # Use the list's default language + lang = mlist.preferred_language info = (listaddr, password, optionsurl, lang) userinfo.setdefault(member, []).append(info) else: From 8527d85dee7aa7ec257b3d6c33968fd2e21c9ef6 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 22:23:15 -0400 Subject: [PATCH 116/748] update test process --- cron/mailpasswds | 25 +++++++++++++++++++++++++ src/Makefile.in | 17 +++++++++++------ 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/cron/mailpasswds b/cron/mailpasswds index ce0aa843..4e324de2 100755 --- a/cron/mailpasswds +++ b/cron/mailpasswds @@ -39,6 +39,11 @@ Options: instead of the actual list members. Useful for testing the reminder format and content. + -v + --verbose + Verbose mode: Display message content and recipient information + before sending. + -h/--help Print this message and exit. """ @@ -84,6 +89,8 @@ def parse_args(): help='Send password reminders for the named list only') parser.add_argument('-t', '--test', metavar='email', help='Test mode: Send to specified email address instead of members') + parser.add_argument('-v', '--verbose', action='store_true', + help='Verbose mode: Display message content and recipient information') return parser.parse_args() @@ -251,6 +258,24 @@ def main(): if args.test: # In test mode, add a test header msg['X-Mailman-Test-Mode'] = 'yes' + + if args.verbose: + print("\n=== Password Reminder Message ===") + print(f"To: {addr}") + print(f"From: {siteowner}") + print(f"Subject: {msg['Subject']}") + print("\nMessage Content:") + print("-" * 50) + print(text) + print("-" * 50) + if not args.test: + print("\nPress Enter to send (or Ctrl+C to abort)...") + try: + input() + except KeyboardInterrupt: + print("\nAborted.") + sys.exit(1) + # We want to make this look like it's coming from the siteowner's # list, but we also want to be sure that the apparent host name is # the current virtual host. Look in CookHeaders.py for why this diff --git a/src/Makefile.in b/src/Makefile.in index c673ddb4..7314aa18 100755 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -27,6 +27,9 @@ srcdir= @srcdir@ bindir= @bindir@ DESTDIR= +# Add build directory variable +builddir = . + CC= @CC@ CHMOD= @CHMOD@ INSTALL= @INSTALL@ @@ -92,28 +95,30 @@ PROGRAMS= $(CGI_PROGS) $(MAIL_PROGS) $(ALIAS_PROGS) all: $(PROGRAMS) -mailman: $(srcdir)/mail-wrapper.c $(COMMONOBJS) - $(CC) -I. $(MAIL_FLAGS) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $(COMMONOBJS) -o $@ $(srcdir)/mail-wrapper.c +mailman: $(builddir)/mail-wrapper.c $(COMMONOBJS) + $(CC) -I. $(MAIL_FLAGS) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $(COMMONOBJS) -o $@ $(builddir)/mail-wrapper.c #addaliases: $(srcdir)/alias-wrapper.c $(COMMONOBJS) # $(CC) -I. $(ALIAS_FLAGS) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -o $@ $(srcdir)/alias-wrapper.c -$(CGI_PROGS): $(srcdir)/cgi-wrapper.c $(COMMONOBJS) - $(CC) -DSCRIPT="\"$@\"" -I. $(CGI_FLAGS) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $(COMMONOBJS) -o $@ $(srcdir)/cgi-wrapper.c +$(CGI_PROGS): $(builddir)/cgi-wrapper.c $(COMMONOBJS) + $(CC) -DSCRIPT="\"$@\"" -I. $(CGI_FLAGS) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $(COMMONOBJS) -o $@ $(builddir)/cgi-wrapper.c common.o: $(srcdir)/common.c $(srcdir)/common.h Makefile - $(CC) -c -I. $(COMMON_FLAGS) $(CFLAGS) $(CPPFLAGS) $(srcdir)/common.c + $(CC) -c -I. $(COMMON_FLAGS) $(CFLAGS) $(CPPFLAGS) -o $@ $(srcdir)/common.c vsnprintf.o: $(srcdir)/vsnprintf.c Makefile - $(CC) -c -I. $(COMMON_FLAGS) $(CFLAGS) $(CPPFLAGS) $(srcdir)/vsnprintf.c + $(CC) -c -I. $(COMMON_FLAGS) $(CFLAGS) $(CPPFLAGS) -o $@ $(srcdir)/vsnprintf.c install: all + @echo "Installing CGI programs..." for f in $(CGI_PROGS); \ do \ exe=$(DESTDIR)$(CGIDIR)/$$f$(CGIEXT); \ $(INSTALL_PROGRAM) $$f $$exe; \ $(DIRSETGID) $$exe; \ done + @echo "Installing mail programs..." for f in $(MAIL_PROGS); \ do \ $(INSTALL_PROGRAM) $$f $(DESTDIR)$(MAILDIR); \ From 1b566b65c2fd77af95ea2fbd68eb9a0936c60c72 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 22:27:41 -0400 Subject: [PATCH 117/748] update mailpasswds --- Makefile.in | 9 ++++++++- cron/mailpasswds | 14 ++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/Makefile.in b/Makefile.in index 6d81d3df..3b16c472 100644 --- a/Makefile.in +++ b/Makefile.in @@ -134,8 +134,15 @@ prepare-build: fi; \ done; \ done + @echo "Creating Python build directories..." + @for d in Mailman scripts misc tests; do \ + dir=build/$$d; \ + if test ! -d $$dir; then \ + $(srcdir)/mkinstalldirs $$dir; \ + fi; \ + done -build: $(PYTHON) $(PYTHON_LIB) +build: prepare-build @echo "Building Python files..." @if [ -d "build" ]; then \ $(PYTHON) -m compileall -q build; \ diff --git a/cron/mailpasswds b/cron/mailpasswds index 4e324de2..416c0ae0 100755 --- a/cron/mailpasswds +++ b/cron/mailpasswds @@ -128,6 +128,11 @@ def main(): host = mm_cfg.DEFAULT_HOST_NAME or mm_cfg.DEFAULT_EMAIL_HOST byhost.setdefault(host, []).append(mlist) + # If we're in test mode and no lists were found, show a message + if args.test and not byhost: + print(f"No lists found for test email {args.test}") + sys.exit(0) + # Now for each virtual host, collate the user information. Each user # entry has the form (listaddr, password, optionsurl) for host in byhost.keys(): @@ -151,9 +156,13 @@ def main(): password = mlist.getMemberPassword(random_member) except Errors.NotAMemberError: # If we can't get the random member's password, skip this list + if args.verbose: + print(f"Skipping list {listaddr} - no valid passwords found") continue else: # If no members, skip this list + if args.verbose: + print(f"Skipping list {listaddr} - no members found") continue try: optionsurl = mlist.GetOptionsURL(member) @@ -187,6 +196,11 @@ def main(): info = (listaddr, password, optionsurl, lang) userinfo.setdefault(member, []).append(info) + # If we're in test mode and no user info was collected, show a message + if args.test and not userinfo: + print(f"No valid list memberships found for test email {args.test}") + continue + # Now that we've collected user information for this host, send each # user the password reminder. for addr in userinfo.keys(): From 43e7afae16253164889414142df7f8ca2f5cb13b Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 22:31:00 -0400 Subject: [PATCH 118/748] update mailpasswds --- cron/mailpasswds | 36 +++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/cron/mailpasswds b/cron/mailpasswds index 416c0ae0..73af625a 100755 --- a/cron/mailpasswds +++ b/cron/mailpasswds @@ -116,21 +116,35 @@ def main(): # there's only one key in this dictionary: mm_cfg.DEFAULT_EMAIL_HOST. The # values are lists of the unlocked MailList instances. byhost = {} + skipped_lists = [] for listname in listnames: - mlist = MailList.MailList(listname, lock=0) - if not mlist.send_reminders: + try: + mlist = MailList.MailList(listname, lock=0) + if not mlist.send_reminders: + if args.verbose: + print(f"Skipping list {listname} - password reminders disabled") + skipped_lists.append((listname, "password reminders disabled")) + continue + if mm_cfg.VIRTUAL_HOST_OVERVIEW: + host = mlist.host_name + else: + # See the note in Defaults.py concerning DEFAULT_HOST_NAME + # vs. DEFAULT_EMAIL_HOST. + host = mm_cfg.DEFAULT_HOST_NAME or mm_cfg.DEFAULT_EMAIL_HOST + byhost.setdefault(host, []).append(mlist) + except Exception as e: + if args.verbose: + print(f"Error processing list {listname}: {str(e)}") + skipped_lists.append((listname, str(e))) continue - if mm_cfg.VIRTUAL_HOST_OVERVIEW: - host = mlist.host_name - else: - # See the note in Defaults.py concerning DEFAULT_HOST_NAME - # vs. DEFAULT_EMAIL_HOST. - host = mm_cfg.DEFAULT_HOST_NAME or mm_cfg.DEFAULT_EMAIL_HOST - byhost.setdefault(host, []).append(mlist) # If we're in test mode and no lists were found, show a message if args.test and not byhost: print(f"No lists found for test email {args.test}") + if skipped_lists: + print("\nSkipped lists:") + for listname, reason in skipped_lists: + print(f" {listname}: {reason}") sys.exit(0) # Now for each virtual host, collate the user information. Each user @@ -199,6 +213,10 @@ def main(): # If we're in test mode and no user info was collected, show a message if args.test and not userinfo: print(f"No valid list memberships found for test email {args.test}") + if skipped_lists: + print("\nSkipped lists:") + for listname, reason in skipped_lists: + print(f" {listname}: {reason}") continue # Now that we've collected user information for this host, send each From 0d352c6e2d901861c1dc08474257b6c1efd3a368 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 22:33:34 -0400 Subject: [PATCH 119/748] update --- cron/mailpasswds | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/cron/mailpasswds b/cron/mailpasswds index 73af625a..aa5862bd 100755 --- a/cron/mailpasswds +++ b/cron/mailpasswds @@ -119,6 +119,8 @@ def main(): skipped_lists = [] for listname in listnames: try: + if args.verbose: + print(f"\nProcessing list: {listname}") mlist = MailList.MailList(listname, lock=0) if not mlist.send_reminders: if args.verbose: @@ -154,35 +156,51 @@ def main(): userinfo = {} for mlist in byhost[host]: listaddr = mlist.GetListEmail() + if args.verbose: + print(f"\nProcessing list: {mlist.internal_name()}") # In test mode, we'll use the test email address for all lists if args.test: member = args.test try: # Try to get the member's password if they are a member + if args.verbose: + print(f" Checking membership for: {member}") password = mlist.getMemberPassword(member) + if args.verbose: + print(f" Found password for member") except Errors.NotAMemberError: + if args.verbose: + print(f" Member not found, trying random member") # For test mode, we'll use a real password from a random member members = mlist.getMembers() if members: # Get a random member's password random_member = random.choice(members) + if args.verbose: + print(f" Trying random member: {random_member}") try: password = mlist.getMemberPassword(random_member) + if args.verbose: + print(f" Found password for random member") except Errors.NotAMemberError: # If we can't get the random member's password, skip this list if args.verbose: - print(f"Skipping list {listaddr} - no valid passwords found") + print(f" No valid passwords found for random member") continue else: # If no members, skip this list if args.verbose: - print(f"Skipping list {listaddr} - no members found") + print(f" No members found in list") continue try: optionsurl = mlist.GetOptionsURL(member) + if args.verbose: + print(f" Got options URL: {optionsurl}") except Errors.NotAMemberError: # If we can't get the options URL, use a default one optionsurl = f"{mlist.GetBaseURL()}/options/{mlist.internal_name()}" + if args.verbose: + print(f" Using default options URL: {optionsurl}") # Use the list's default language lang = mlist.preferred_language info = (listaddr, password, optionsurl, lang) From 9f480100c59f0cdefe47f5fdc477ca094ece674c Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 22:37:14 -0400 Subject: [PATCH 120/748] update --- cron/mailpasswds | 74 ++++++++++++++++++++++++++---------------------- 1 file changed, 40 insertions(+), 34 deletions(-) diff --git a/cron/mailpasswds b/cron/mailpasswds index aa5862bd..4e2207e9 100755 --- a/cron/mailpasswds +++ b/cron/mailpasswds @@ -165,46 +165,52 @@ def main(): # Try to get the member's password if they are a member if args.verbose: print(f" Checking membership for: {member}") - password = mlist.getMemberPassword(member) - if args.verbose: - print(f" Found password for member") - except Errors.NotAMemberError: - if args.verbose: - print(f" Member not found, trying random member") - # For test mode, we'll use a real password from a random member - members = mlist.getMembers() - if members: - # Get a random member's password - random_member = random.choice(members) + try: + password = mlist.getMemberPassword(member) if args.verbose: - print(f" Trying random member: {random_member}") - try: - password = mlist.getMemberPassword(random_member) + print(f" Found password for member") + except Errors.NotAMemberError: + if args.verbose: + print(f" Member not found in list {mlist.internal_name()}") + # For test mode, we'll use a real password from a random member + members = mlist.getMembers() + if members: + # Get a random member's password + random_member = random.choice(members) if args.verbose: - print(f" Found password for random member") - except Errors.NotAMemberError: - # If we can't get the random member's password, skip this list + print(f" Trying random member: {random_member}") + try: + password = mlist.getMemberPassword(random_member) + if args.verbose: + print(f" Found password for random member") + except Errors.NotAMemberError: + # If we can't get the random member's password, skip this list + if args.verbose: + print(f" No valid passwords found for random member") + continue + else: + # If no members, skip this list if args.verbose: - print(f" No valid passwords found for random member") + print(f" No members found in list") continue - else: - # If no members, skip this list + try: + optionsurl = mlist.GetOptionsURL(member) if args.verbose: - print(f" No members found in list") - continue - try: - optionsurl = mlist.GetOptionsURL(member) - if args.verbose: - print(f" Got options URL: {optionsurl}") - except Errors.NotAMemberError: - # If we can't get the options URL, use a default one - optionsurl = f"{mlist.GetBaseURL()}/options/{mlist.internal_name()}" + print(f" Got options URL: {optionsurl}") + except Errors.NotAMemberError: + # If we can't get the options URL, use a default one + optionsurl = f"{mlist.GetBaseURL()}/options/{mlist.internal_name()}" + if args.verbose: + print(f" Using default options URL: {optionsurl}") + # Use the list's default language + lang = mlist.preferred_language + info = (listaddr, password, optionsurl, lang) + userinfo.setdefault(member, []).append(info) + except Exception as e: if args.verbose: - print(f" Using default options URL: {optionsurl}") - # Use the list's default language - lang = mlist.preferred_language - info = (listaddr, password, optionsurl, lang) - userinfo.setdefault(member, []).append(info) + print(f" Error processing list {mlist.internal_name()}: {str(e)}") + skipped_lists.append((mlist.internal_name(), str(e))) + continue else: # Normal mode - process all members for member in mlist.getMembers(): From 0b65f909f4b0e8894a428092a1031439dc736cea Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 22:43:05 -0400 Subject: [PATCH 121/748] string handling --- cron/mailpasswds | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cron/mailpasswds b/cron/mailpasswds index 4e2207e9..71fdd381 100755 --- a/cron/mailpasswds +++ b/cron/mailpasswds @@ -302,7 +302,7 @@ def main(): text += "\n\n[TEST MODE] This is a test password reminder message." msg = Message.UserNotification( addr, siteowner, - _('%(host)s mailing list memberships reminder'), + _('%(host)s mailing list memberships reminder') % {'host': str(host)}, text.encode(enc, 'replace'), poplang) # Note that text must be encoded into 'enc' because unicode # cause error within email module in some language (Japanese). From 1b5739c54d4734c6316cf33a3c7e4683c87b032a Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 22:55:57 -0400 Subject: [PATCH 122/748] update --- cron/mailpasswds | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/cron/mailpasswds b/cron/mailpasswds index 71fdd381..15758fc7 100755 --- a/cron/mailpasswds +++ b/cron/mailpasswds @@ -80,7 +80,11 @@ _ = i18n._ def tounicode(s, enc): if isinstance(s, str): return s - return str(s, enc, 'replace') + try: + return str(s, enc, 'replace') + except (UnicodeDecodeError, LookupError): + # Try Latin-1 as fallback + return str(s, 'latin-1', 'replace') def parse_args(): @@ -129,10 +133,14 @@ def main(): continue if mm_cfg.VIRTUAL_HOST_OVERVIEW: host = mlist.host_name + if isinstance(host, bytes): + host = tounicode(host, 'utf-8') else: # See the note in Defaults.py concerning DEFAULT_HOST_NAME # vs. DEFAULT_EMAIL_HOST. host = mm_cfg.DEFAULT_HOST_NAME or mm_cfg.DEFAULT_EMAIL_HOST + if isinstance(host, bytes): + host = tounicode(host, 'utf-8') byhost.setdefault(host, []).append(mlist) except Exception as e: if args.verbose: @@ -302,7 +310,7 @@ def main(): text += "\n\n[TEST MODE] This is a test password reminder message." msg = Message.UserNotification( addr, siteowner, - _('%(host)s mailing list memberships reminder') % {'host': str(host)}, + _('%(host)s mailing list memberships reminder'), text.encode(enc, 'replace'), poplang) # Note that text must be encoded into 'enc' because unicode # cause error within email module in some language (Japanese). From 214cf5218beec87a1d99fecf3ebe4d349f405579 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 23:01:18 -0400 Subject: [PATCH 123/748] update --- Mailman/MailList.py | 19 +++++++++++-------- cron/mailpasswds | 15 ++++++++++++--- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/Mailman/MailList.py b/Mailman/MailList.py index 26a3a564..e911732b 100644 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -761,9 +761,11 @@ def __convert_bytes_to_strings(self, data): """Convert bytes to strings recursively in a data structure.""" if isinstance(data, bytes): try: - return data.decode('utf-8', 'replace') + # Try Latin-1 first since that's what we're seeing in the data + return data.decode('latin-1', 'replace') except UnicodeDecodeError: - return data.decode('latin1', 'replace') + # Fall back to UTF-8 if Latin-1 fails + return data.decode('utf-8', 'replace') elif isinstance(data, list): return [self.__convert_bytes_to_strings(item) for item in data] elif isinstance(data, dict): @@ -811,14 +813,15 @@ def Load(self, check_version=True): for key, value in dict_retval.items(): if isinstance(value, bytes): try: - dict_retval[key] = value.decode('utf-8', 'replace') + # Try Latin-1 first since that's what we're seeing in the data + dict_retval[key] = value.decode('latin-1', 'replace') except UnicodeDecodeError: - # If UTF-8 fails, try latin1 as a fallback - dict_retval[key] = value.decode('latin1', 'replace') + # Fall back to UTF-8 if Latin-1 fails + dict_retval[key] = value.decode('utf-8', 'replace') elif isinstance(value, list): # Handle lists that might contain bytes dict_retval[key] = [ - v.decode('utf-8', 'replace') if isinstance(v, bytes) else v + v.decode('latin-1', 'replace') if isinstance(v, bytes) else v for v in value ] elif isinstance(value, dict): @@ -826,9 +829,9 @@ def Load(self, check_version=True): new_dict = {} for k, v in value.items(): if isinstance(k, bytes): - k = k.decode('utf-8', 'replace') + k = k.decode('latin-1', 'replace') if isinstance(v, bytes): - v = v.decode('utf-8', 'replace') + v = v.decode('latin-1', 'replace') new_dict[k] = v dict_retval[key] = new_dict diff --git a/cron/mailpasswds b/cron/mailpasswds index 15758fc7..d5c86789 100755 --- a/cron/mailpasswds +++ b/cron/mailpasswds @@ -126,6 +126,12 @@ def main(): if args.verbose: print(f"\nProcessing list: {listname}") mlist = MailList.MailList(listname, lock=0) + # Handle potential bytes in list configuration + listname = tounicode(listname, 'latin-1') + if isinstance(mlist.real_name, bytes): + mlist.real_name = tounicode(mlist.real_name, 'latin-1') + if isinstance(mlist.internal_name(), bytes): + mlist.internal_name = tounicode(mlist.internal_name(), 'latin-1') if not mlist.send_reminders: if args.verbose: print(f"Skipping list {listname} - password reminders disabled") @@ -134,13 +140,13 @@ def main(): if mm_cfg.VIRTUAL_HOST_OVERVIEW: host = mlist.host_name if isinstance(host, bytes): - host = tounicode(host, 'utf-8') + host = tounicode(host, 'latin-1') else: # See the note in Defaults.py concerning DEFAULT_HOST_NAME # vs. DEFAULT_EMAIL_HOST. host = mm_cfg.DEFAULT_HOST_NAME or mm_cfg.DEFAULT_EMAIL_HOST if isinstance(host, bytes): - host = tounicode(host, 'utf-8') + host = tounicode(host, 'latin-1') byhost.setdefault(host, []).append(mlist) except Exception as e: if args.verbose: @@ -308,9 +314,12 @@ def main(): if args.test: # In test mode, add a note that this is a test message text += "\n\n[TEST MODE] This is a test password reminder message." + # Ensure host is properly decoded + if isinstance(host, bytes): + host = tounicode(host, 'latin-1') msg = Message.UserNotification( addr, siteowner, - _('%(host)s mailing list memberships reminder'), + _('%(host)s mailing list memberships reminder') % {'host': host}, text.encode(enc, 'replace'), poplang) # Note that text must be encoded into 'enc' because unicode # cause error within email module in some language (Japanese). From f451777f200fdae5562d87e48d9f4e31fe739f59 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 23:06:46 -0400 Subject: [PATCH 124/748] update --- Mailman/MailList.py | 70 ++++++++++++++++++++++++++++----------------- 1 file changed, 43 insertions(+), 27 deletions(-) diff --git a/Mailman/MailList.py b/Mailman/MailList.py index e911732b..14bffbbe 100644 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -189,7 +189,12 @@ def Locked(self): def internal_name(self): name = self._internal_name if isinstance(name, bytes): - name = name.decode('utf-8', 'replace') + try: + # Try Latin-1 first since that's what we're seeing in the data + name = name.decode('latin-1', 'replace') + except UnicodeDecodeError: + # Fall back to UTF-8 if Latin-1 fails + name = name.decode('utf-8', 'replace') return name def fullpath(self): @@ -336,7 +341,12 @@ def InitVars(self, name=None, admin='', crypted_password='', if name: # Ensure name is a string if isinstance(name, bytes): - name = name.decode('utf-8', 'replace') + try: + # Try Latin-1 first since that's what we're seeing in the data + name = name.decode('latin-1', 'replace') + except UnicodeDecodeError: + # Fall back to UTF-8 if Latin-1 fails + name = name.decode('utf-8', 'replace') self._internal_name = name # When was the list created? @@ -391,6 +401,13 @@ def InitVars(self, name=None, admin='', crypted_password='', self.from_is_list = mm_cfg.DEFAULT_FROM_IS_LIST self.anonymous_list = mm_cfg.DEFAULT_ANONYMOUS_LIST internalname = self.internal_name() + if isinstance(internalname, bytes): + try: + # Try Latin-1 first since that's what we're seeing in the data + internalname = internalname.decode('latin-1', 'replace') + except UnicodeDecodeError: + # Fall back to UTF-8 if Latin-1 fails + internalname = internalname.decode('utf-8', 'replace') self.real_name = internalname[0].upper() + internalname[1:] self.description = '' self.info = '' @@ -811,6 +828,18 @@ def Load(self, check_version=True): # Ensure string values are properly decoded for key, value in dict_retval.items(): + # Handle the key first + if isinstance(key, bytes): + try: + # Try Latin-1 first since that's what we're seeing in the data + key = key.decode('latin-1', 'replace') + except UnicodeDecodeError: + # Fall back to UTF-8 if Latin-1 fails + key = key.decode('utf-8', 'replace') + # Update the dictionary with the decoded key + dict_retval[key] = dict_retval.pop(key) + + # Now handle the value if isinstance(value, bytes): try: # Try Latin-1 first since that's what we're seeing in the data @@ -829,35 +858,22 @@ def Load(self, check_version=True): new_dict = {} for k, v in value.items(): if isinstance(k, bytes): - k = k.decode('latin-1', 'replace') + try: + # Try Latin-1 first for keys + k = k.decode('latin-1', 'replace') + except UnicodeDecodeError: + # Fall back to UTF-8 if Latin-1 fails + k = k.decode('utf-8', 'replace') if isinstance(v, bytes): - v = v.decode('latin-1', 'replace') + try: + # Try Latin-1 first for values + v = v.decode('latin-1', 'replace') + except UnicodeDecodeError: + # Fall back to UTF-8 if Latin-1 fails + v = v.decode('utf-8', 'replace') new_dict[k] = v dict_retval[key] = new_dict - # Now, if we didn't end up using the primary database file, we want to - # copy the fallback into the primary so that the logic in Save() will - # still work. For giggles, we'll copy it to a safety backup. Note we - # MUST do this with the underlying list lock acquired. - if file == plast or file == dlast: - syslog('error', 'fixing corrupt config file, using: %s', file) - unlock = True - try: - try: - self.__lock.lock() - except LockFile.AlreadyLockedError: - unlock = False - self.__fix_corrupt_pckfile(file, pfile, plast, dfile, dlast) - finally: - if unlock: - self.__lock.unlock() - # Copy the loaded dictionary into the attributes of the current - # mailing list object, then run sanity check on the data. - self.__dict__.update(dict_retval) - if check_version: - self.CheckVersion(dict_retval) - self.CheckValues() - def __fix_corrupt_pckfile(self, file, pfile, plast, dfile, dlast): if file == plast: # Move aside any existing pickle file and delete any existing From b488785c767ae496d312036cf4da2811632cb3ee Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 23:11:28 -0400 Subject: [PATCH 125/748] update --- Mailman/MailList.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Mailman/MailList.py b/Mailman/MailList.py index 14bffbbe..e8819464 100644 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -874,6 +874,30 @@ def Load(self, check_version=True): new_dict[k] = v dict_retval[key] = new_dict + # Now, if we didn't end up using the primary database file, we want to + # copy the fallback into the primary so that the logic in Save() will + # still work. For giggles, we'll copy it to a safety backup. Note we + # MUST do this with the underlying list lock acquired. + if file == plast or file == dlast: + syslog('error', 'fixing corrupt config file, using: %s', file) + unlock = True + try: + try: + self.__lock.lock() + except LockFile.AlreadyLockedError: + unlock = False + self.__fix_corrupt_pckfile(file, pfile, plast, dfile, dlast) + finally: + if unlock: + self.__lock.unlock() + + # Copy the loaded dictionary into the attributes of the current + # mailing list object, then run sanity check on the data. + self.__dict__.update(dict_retval) + if check_version: + self.CheckVersion(dict_retval) + self.CheckValues() + def __fix_corrupt_pckfile(self, file, pfile, plast, dfile, dlast): if file == plast: # Move aside any existing pickle file and delete any existing From 5c0299debebab2901a37f1edca5e1e08327a84d3 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 23:13:51 -0400 Subject: [PATCH 126/748] update --- Mailman/MailList.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Mailman/MailList.py b/Mailman/MailList.py index e8819464..82e245d0 100644 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -128,7 +128,18 @@ def __init__(self, name=None, lock=1): def __getattr__(self, name): # First check if the attribute exists in the instance's dictionary if name in self.__dict__: - return self.__dict__[name] + value = self.__dict__[name] + # Check if the value is bytes that looks like Latin-1 + if isinstance(value, bytes): + try: + # Try to decode as Latin-1 to see if it's valid + value.decode('latin-1') + syslog('warning', + 'Binary data that looks like Latin-1 string accessed: %s.%s = %r', + self.internal_name(), name, value) + except UnicodeDecodeError: + pass + return value # Then try the memberadaptor try: return getattr(self._memberadaptor, name) From e85cb028f01abed4f46862bb21e2166ed59ef139 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 23:34:17 -0400 Subject: [PATCH 127/748] update code --- Makefile.in | 18 ++++++++++++++++++ cron/mailpasswds | 37 ++++++++----------------------------- src/Makefile.in | 11 ++++++++--- src/cgi-wrapper.c | 2 +- src/common.c | 6 +++--- src/mail-wrapper.c | 2 +- 6 files changed, 39 insertions(+), 37 deletions(-) diff --git a/Makefile.in b/Makefile.in index 3b16c472..4e84a47f 100644 --- a/Makefile.in +++ b/Makefile.in @@ -108,6 +108,17 @@ define check_scripts done endef +# Add this function to handle variable substitutions +define substitute_vars + @echo "Substituting variables in $$1..." + @sed -e 's|@PYTHON@|$(PYTHON)|g' \ + -e 's|@prefix@|$(prefix)|g' \ + -e 's|@exec_prefix@|$(exec_prefix)|g' \ + -e 's|@bindir@|$(bindir)|g' \ + -e 's|@var_prefix@|$(var_prefix)|g' \ + $$1 > $$1.tmp && mv $$1.tmp $$1 +endef + # Rules .PHONY: all build install clean distclean prepare-build clean-pyc doinstall update langpack @@ -130,6 +141,13 @@ prepare-build: if test -f $$f; then \ if test ! -f build/$$f -o $$f -nt build/$$f; then \ cp -p $$f build/$$f; \ + # Check if file contains variables to substitute \ + if grep -q '/usr/bin/python3\|/usr/local/mailman\|$${prefix}\|$${exec_prefix}/bin\|@var_prefix@' build/$$f; then \ + sed -i 's|/usr/bin/python3|$(PYTHON)|g' build/$$f; \ + sed -i 's|/usr/local/mailman|$(prefix)|g' build/$$f; \ + sed -i 's|$${prefix}|$(exec_prefix)|g' build/$$f; \ + sed -i 's|$${exec_prefix}/bin|$(bindir)|g' build/$$f; \ + fi; \ fi; \ fi; \ done; \ diff --git a/cron/mailpasswds b/cron/mailpasswds index d5c86789..72c1f381 100755 --- a/cron/mailpasswds +++ b/cron/mailpasswds @@ -184,29 +184,8 @@ def main(): if args.verbose: print(f" Found password for member") except Errors.NotAMemberError: - if args.verbose: - print(f" Member not found in list {mlist.internal_name()}") - # For test mode, we'll use a real password from a random member - members = mlist.getMembers() - if members: - # Get a random member's password - random_member = random.choice(members) - if args.verbose: - print(f" Trying random member: {random_member}") - try: - password = mlist.getMemberPassword(random_member) - if args.verbose: - print(f" Found password for random member") - except Errors.NotAMemberError: - # If we can't get the random member's password, skip this list - if args.verbose: - print(f" No valid passwords found for random member") - continue - else: - # If no members, skip this list - if args.verbose: - print(f" No members found in list") - continue + # Silently skip if not a member in test mode + continue try: optionsurl = mlist.GetOptionsURL(member) if args.verbose: @@ -237,13 +216,13 @@ def main(): try: password = mlist.getMemberPassword(member) except Errors.NotAMemberError: - # Here's a member with no passwords, which I think was - # possible in older versions of Mailman. Log this and - # move on. - syslog('error', 'password-less member %s for list %s', - member, mlist.internal_name()) + # Silently skip members without passwords continue - optionsurl = mlist.GetOptionsURL(member) + try: + optionsurl = mlist.GetOptionsURL(member) + except Errors.NotAMemberError: + # If we can't get the options URL, use a default one + optionsurl = f"{mlist.GetBaseURL()}/options/{mlist.internal_name()}" lang = mlist.getMemberLanguage(member) info = (listaddr, password, optionsurl, lang) userinfo.setdefault(member, []).append(info) diff --git a/src/Makefile.in b/src/Makefile.in index 7314aa18..32f3e4bb 100755 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -52,6 +52,10 @@ CGIDIR= $(exec_prefix)/cgi-bin CGIEXT= @CGIEXT@ MAILDIR= $(exec_prefix)/mail +# Add required C compiler flags and libraries +CFLAGS += -Wall -Wextra +LIBS += -lm + SHELL= /bin/sh MAIL_FLAGS= -DMAIL_GROUP="\"$(MAIL_GROUP)\"" @@ -64,6 +68,8 @@ COMMON_FLAGS= -DPREFIX="\"$(prefix)\"" \ -DPYTHON="\"$(PYTHON)\"" \ $(HELPFUL) +# Add dependency on system headers +COMMON_DEPS = $(srcdir)/common.h $(srcdir)/common.c $(srcdir)/vsnprintf.c Makefile # Modes for directories and executables created by the install # process. Default to group-writable directories but @@ -90,18 +96,17 @@ SUID_MAIL_PROGS= PROGRAMS= $(CGI_PROGS) $(MAIL_PROGS) $(ALIAS_PROGS) - # Rules all: $(PROGRAMS) -mailman: $(builddir)/mail-wrapper.c $(COMMONOBJS) +mailman: $(builddir)/mail-wrapper.c $(COMMONOBJS) $(COMMON_DEPS) $(CC) -I. $(MAIL_FLAGS) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $(COMMONOBJS) -o $@ $(builddir)/mail-wrapper.c #addaliases: $(srcdir)/alias-wrapper.c $(COMMONOBJS) # $(CC) -I. $(ALIAS_FLAGS) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -o $@ $(srcdir)/alias-wrapper.c -$(CGI_PROGS): $(builddir)/cgi-wrapper.c $(COMMONOBJS) +$(CGI_PROGS): $(builddir)/cgi-wrapper.c $(COMMONOBJS) $(COMMON_DEPS) $(CC) -DSCRIPT="\"$@\"" -I. $(CGI_FLAGS) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $(COMMONOBJS) -o $@ $(builddir)/cgi-wrapper.c common.o: $(srcdir)/common.c $(srcdir)/common.h Makefile diff --git a/src/cgi-wrapper.c b/src/cgi-wrapper.c index ec581d3d..bba2bebb 100644 --- a/src/cgi-wrapper.c +++ b/src/cgi-wrapper.c @@ -36,7 +36,7 @@ const char* parentgroup = LEGAL_PARENT_GROUP; int -main(int argc, char** argv, char** env) +main(int argc __attribute__((unused)), char** argv __attribute__((unused)), char** env) { int status; char* fake_argv[3]; diff --git a/src/common.c b/src/common.c index b5604f38..b212b940 100644 --- a/src/common.c +++ b/src/common.c @@ -41,13 +41,13 @@ int running_as_cgi = 0; extern char *sys_errlist[]; extern int sys_nerr; -char* strerror(int errno) +char* strerror(int errnum) { - if (errno < 0 || errno >= sys_nerr) { + if (errnum < 0 || errnum >= sys_nerr) { return "unknown error"; } else { - return sys_errlist[errno]; + return sys_errlist[errnum]; } } diff --git a/src/mail-wrapper.c b/src/mail-wrapper.c index 78b912c1..776c3f65 100644 --- a/src/mail-wrapper.c +++ b/src/mail-wrapper.c @@ -61,7 +61,7 @@ check_command(char *command) int -main(int argc, char** argv, char** env) +main(int argc, char** argv, char** env __attribute__((unused))) { int status; From b0b565c9925e0439a0c917c8fb9e327664d9212a Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 26 Apr 2025 23:59:24 -0400 Subject: [PATCH 128/748] update code --- Mailman/Cgi/admin.py | 20 +++--- Mailman/Cgi/options.py | 84 ++++++++++++++++++++++-- Mailman/Cgi/subscribe.py | 54 +++++++++------ tests/test_lockfile.py | 138 +++++++++++++++++++++++++++++++++++++-- 4 files changed, 255 insertions(+), 41 deletions(-) diff --git a/Mailman/Cgi/admin.py b/Mailman/Cgi/admin.py index d821dc3a..b9db6f4a 100644 --- a/Mailman/Cgi/admin.py +++ b/Mailman/Cgi/admin.py @@ -1006,15 +1006,15 @@ def membership_options(mlist, subcat, cgidata, doc, form): container.AddItem(table) container.AddItem('

                  ') usertable = Table(width="90%", border='2') - # If there are more members than allowed by chunksize, then we split the - # membership up alphabetically. Otherwise just display them all. - chunksz = mlist.admin_member_chunksize # The email addresses had /better/ be ASCII, but might be encoded in the # database as Unicodes. all = [_m.encode() for _m in mlist.getMembers()] all.sort(lambda x, y: cmp(x.lower(), y.lower())) # See if the query has a regular expression - regexp = cgidata.get('findmember', [''])[0].strip() + regexp = cgidata.get('findmember', [''])[0] + if isinstance(regexp, bytes): + regexp = regexp.decode('utf-8', 'replace') + regexp = regexp.strip() try: regexp = regexp.decode(Utils.GetCharSet(mlist.preferred_language)) except UnicodeDecodeError: @@ -1037,7 +1037,7 @@ def membership_options(mlist, subcat, cgidata, doc, form): chunkindex = None bucket = None actionurl = None - if len(all) < chunksz: + if len(all) < mlist.admin_member_chunksize: members = all else: # Split them up alphabetically, and then split the alphabetical @@ -1064,10 +1064,10 @@ def membership_options(mlist, subcat, cgidata, doc, form): 'adminurl': adminurl, 'bucket': bucket } - if len(members) <= chunksz: + if len(members) <= mlist.admin_member_chunksize: form.set_action(action) else: - i, r = divmod(len(members), chunksz) + i, r = divmod(len(members), mlist.admin_member_chunksize) numchunks = i + (not not r * 1) # Now chunk them up chunkindex = 0 @@ -1078,7 +1078,7 @@ def membership_options(mlist, subcat, cgidata, doc, form): chunkindex = 0 if chunkindex < 0 or chunkindex > numchunks: chunkindex = 0 - members = members[chunkindex*chunksz:(chunkindex+1)*chunksz] + members = members[chunkindex*mlist.admin_member_chunksize:(chunkindex+1)*mlist.admin_member_chunksize] # And set the action URL form.set_action('%(action)s&chunk=%(chunkindex)s' % { 'action': action, @@ -1301,8 +1301,8 @@ def membership_options(mlist, subcat, cgidata, doc, form): for i in range(numchunks): if i == chunkindex: continue - start = chunkmembers[i*chunksz] - end = chunkmembers[min((i+1)*chunksz, last)-1] + start = chunkmembers[i*mlist.admin_member_chunksize] + end = chunkmembers[min((i+1)*mlist.admin_member_chunksize, last)-1] thisurl = '%(url)schunk=%(i)d%(findfrag)s' % { 'url': url, 'i': i, diff --git a/Mailman/Cgi/options.py b/Mailman/Cgi/options.py index a5eb71c6..3c4e135c 100644 --- a/Mailman/Cgi/options.py +++ b/Mailman/Cgi/options.py @@ -814,7 +814,83 @@ def __bool__(self): print(doc.Format()) - +def process_form(mlist, cgidata, doc, form): + """Process the form submission.""" + # Get the user's email address + email = cgidata.get('email', [''])[0] + if isinstance(email, bytes): + email = email.decode('utf-8', 'replace') + email = email.strip() + + # Get the user's password + password = cgidata.get('password', [''])[0] + if isinstance(password, bytes): + password = password.decode('utf-8', 'replace') + password = password.strip() + + # Get the user's full name + fullname = cgidata.get('fullname', [''])[0] + if isinstance(fullname, bytes): + fullname = fullname.decode('utf-8', 'replace') + fullname = fullname.strip() + + # Get the user's options + options = {} + for key in cgidata: + if key.startswith('option_'): + value = cgidata.get(key, [''])[0] + if isinstance(value, bytes): + value = value.decode('utf-8', 'replace') + options[key[7:]] = value.strip() + + # Validate the email address + if not email: + doc.addError(_('You must provide an email address')) + return + + if not Utils.ValidateEmail(email): + doc.addError(_('Invalid email address: %(email)s') % {'email': email}) + return + + # Validate the password + if not password: + doc.addError(_('You must provide a password')) + return + + # Validate the full name + if not fullname: + doc.addError(_('You must provide your full name')) + return + + # Try to get the member + try: + member = mlist.getMember(email) + except Errors.NotAMemberError: + doc.addError(_('You are not a member of this list')) + return + + # Verify the password + if not mlist.Authenticate((email, password)): + doc.addError(_('Invalid password')) + return + + # Update the member's options + try: + mlist.Lock() + try: + member.setFullName(fullname) + for key, value in options.items(): + member.setOption(key, value) + finally: + mlist.Unlock() + except Exception as e: + doc.addError(_('Error updating options: %(error)s') % {'error': str(e)}) + return + + # Show success message + doc.addItem(_('Your options have been updated')) + + def options_page(mlist, doc, user, cpuser, userlang, message=''): # The bulk of the document will come from the options.html template, which # includes it's own html armor (head tags, etc.). Suppress the head that @@ -960,7 +1036,7 @@ def options_page(mlist, doc, user, cpuser, userlang, message=''): page_text = DIGRE.sub('', page_text) doc.AddItem(page_text) - + def loginpage(mlist, doc, user, lang): realname = mlist.real_name actionurl = mlist.GetScriptURL('options') @@ -1045,7 +1121,6 @@ def loginpage(mlist, doc, user, lang): doc.AddItem(mlist.GetMailmanFooter()) - def lists_of_member(mlist, user): hostname = mlist.host_name onlists = [] @@ -1062,7 +1137,6 @@ def lists_of_member(mlist, user): return onlists - def change_password(mlist, user, newpw, confirmpw): # This operation requires the list lock, so let's set up the signal # handling so the list lock will get released when the user hits the @@ -1089,7 +1163,6 @@ def sigterm_handler(signum, frame, mlist=mlist): mlist.Unlock() - def global_options(mlist, user, globalopts): # Is there anything to do? for attr in dir(globalopts): @@ -1133,7 +1206,6 @@ def sigterm_handler(signum, frame, mlist=mlist): mlist.Unlock() - def topic_details(mlist, doc, user, cpuser, userlang, varhelp): # Find out which topic the user wants to get details of reflist = varhelp.split('/') diff --git a/Mailman/Cgi/subscribe.py b/Mailman/Cgi/subscribe.py index be59eb8e..eed7ebe7 100644 --- a/Mailman/Cgi/subscribe.py +++ b/Mailman/Cgi/subscribe.py @@ -111,11 +111,6 @@ def main(): # We need a signal handler to catch the SIGTERM that can come from Apache # when the user hits the browser's STOP button. See the comment in # admin.py for details. - # - # BAW: Strictly speaking, the list should not need to be locked just to - # read the request database. However the request database asserts that - # the list is locked in order to load it and it's not worth complicating - # that logic. def sigterm_handler(signum, frame, mlist=mlist): # Make sure the list gets unlocked... mlist.Unlock() @@ -124,15 +119,10 @@ def sigterm_handler(signum, frame, mlist=mlist): # could be bad! sys.exit(0) - mlist.Lock() - try: - # Install the emergency shutdown signal handler - signal.signal(signal.SIGTERM, sigterm_handler) + # Install the emergency shutdown signal handler + signal.signal(signal.SIGTERM, sigterm_handler) - process_form(mlist, doc, cgidata, language) - mlist.Save() - finally: - mlist.Unlock() + process_form(mlist, doc, cgidata, language) def process_form(mlist, doc, cgidata, lang): @@ -141,11 +131,16 @@ def process_form(mlist, doc, cgidata, lang): results = [] # The email address being subscribed, required - email = cgidata.get('email', [''])[0].strip() + email = cgidata.get('email', [''])[0] + if isinstance(email, bytes): + email = email.decode('utf-8', 'replace') + email = email.strip() if not email: results.append(_('You must supply a valid email address.')) fullname = cgidata.get('fullname', [''])[0] + if isinstance(fullname, bytes): + fullname = fullname.decode('utf-8', 'replace') # Canonicalize the full name fullname = Utils.canonstr(fullname, lang) # Who was doing the subscribing? @@ -156,9 +151,12 @@ def process_form(mlist, doc, cgidata, lang): # Check reCAPTCHA submission, if enabled if mm_cfg.RECAPTCHA_SECRET_KEY: + recaptcha_response = cgidata.get('g-recaptcha-response', [''])[0] + if isinstance(recaptcha_response, bytes): + recaptcha_response = recaptcha_response.decode('utf-8', 'replace') request_data = urllib.parse.urlencode({ 'secret': mm_cfg.RECAPTCHA_SECRET_KEY, - 'response': cgidata.get('g-recaptcha-response', [''])[0], + 'response': recaptcha_response, 'remoteip': remote}) request_data = request_data.encode('utf-8') request = urllib.request.Request( @@ -196,8 +194,10 @@ def process_form(mlist, doc, cgidata, lang): # for our hash so it doesn't matter. remote1 = ip.rsplit(':', 1)[0] try: - ftime, fcaptcha_idx, fhash = cgidata.get( - 'sub_form_token', [''])[0].split(':') + sub_form_token = cgidata.get('sub_form_token', [''])[0] + if isinstance(sub_form_token, bytes): + sub_form_token = sub_form_token.decode('utf-8', 'replace') + ftime, fcaptcha_idx, fhash = sub_form_token.split(':') then = int(ftime) except ValueError: ftime = fcaptcha_idx = fhash = '' @@ -217,8 +217,15 @@ def process_form(mlist, doc, cgidata, lang): mailman_log('mischief', 'Attempt to self subscribe %s: %s', email, remote) results.append(_('You may not subscribe a list to itself!')) # If the user did not supply a password, generate one for him - password = cgidata.get('pw', [''])[0].strip() - confirmed = cgidata.get('pw-conf', [''])[0].strip() + password = cgidata.get('pw', [''])[0] + if isinstance(password, bytes): + password = password.decode('utf-8', 'replace') + password = password.strip() + + confirmed = cgidata.get('pw-conf', [''])[0] + if isinstance(confirmed, bytes): + confirmed = confirmed.decode('utf-8', 'replace') + confirmed = confirmed.strip() if not password and not confirmed: password = Utils.MakeRandomPassword() @@ -229,6 +236,8 @@ def process_form(mlist, doc, cgidata, lang): # Get the digest option for the subscription. digestflag = cgidata.get('digest', [''])[0] + if isinstance(digestflag, bytes): + digestflag = digestflag.decode('utf-8', 'replace') if digestflag: try: digest = int(digestflag) @@ -266,12 +275,13 @@ def process_form(mlist, doc, cgidata, lang): moderator. If confirmation is required, you will soon get a confirmation email which contains further instructions.""") + # Acquire the lock before attempting to add the member + mlist.Lock() try: userdesc = UserDesc(email, fullname, password, digest, lang) mlist.AddMember(userdesc, remote) results = '' - # Check for all the errors that mlist.AddMember can throw options on the - # web page for this cgi + mlist.Save() except Errors.MembershipIsBanned: results = _(f"""The email address you supplied is banned from this mailing list. If you think this restriction is erroneous, please @@ -357,6 +367,8 @@ def process_form(mlist, doc, cgidata, lang): else: results = _(f"""\ You have been successfully subscribed to the {realname} mailing list.""") + finally: + mlist.Unlock() # Show the results print_results(mlist, results, doc, lang) diff --git a/tests/test_lockfile.py b/tests/test_lockfile.py index 31eddcf5..5cb787b8 100644 --- a/tests/test_lockfile.py +++ b/tests/test_lockfile.py @@ -18,32 +18,162 @@ """ import unittest +import os +import time +import errno try: from Mailman import __init__ except ImportError: import paths -from Mailman.LockFile import LockFile +from Mailman.LockFile import LockFile, AlreadyLockedError, NotLockedError +from Mailman.MailList import MailList +from Mailman import Utils +from Mailman import mm_cfg LOCKFILE_NAME = '/tmp/.mm-test-lock' +TEST_LIST_NAME = 'test-list' - class TestLockFile(unittest.TestCase): + def setUp(self): + # Clean up any existing lock file + try: + os.unlink(LOCKFILE_NAME) + except OSError as e: + if e.errno != errno.ENOENT: + raise + + def tearDown(self): + # Clean up after each test + try: + os.unlink(LOCKFILE_NAME) + except OSError as e: + if e.errno != errno.ENOENT: + raise + def test_two_lockfiles_same_proc(self): lf1 = LockFile(LOCKFILE_NAME) lf2 = LockFile(LOCKFILE_NAME) lf1.lock() self.failIf(lf2.locked()) + def test_normal_lock_release(self): + lf = LockFile(LOCKFILE_NAME) + lf.lock() + self.assertTrue(lf.locked()) + lf.unlock() + self.assertFalse(lf.locked()) + + def test_unconditional_unlock(self): + lf = LockFile(LOCKFILE_NAME) + # Should not raise an error even when not locked + lf.unlock(unconditionally=True) + lf.lock() + lf.unlock(unconditionally=True) + self.assertFalse(lf.locked()) + + def test_lock_timeout(self): + lf1 = LockFile(LOCKFILE_NAME) + lf2 = LockFile(LOCKFILE_NAME) + lf1.lock() + # Try to acquire lock with short timeout + start_time = time.time() + try: + lf2.lock(timeout=0.1) + self.fail("Expected timeout") + except AlreadyLockedError: + pass + elapsed = time.time() - start_time + self.assertTrue(0.1 <= elapsed < 0.2) # Should timeout after ~0.1s + lf1.unlock() + + def test_lock_refresh(self): + lf = LockFile(LOCKFILE_NAME, lifetime=1) + lf.lock() + self.assertTrue(lf.locked()) + time.sleep(0.5) # Wait half the lifetime + lf.refresh() # Refresh the lock + time.sleep(0.6) # Wait more than original lifetime + self.assertTrue(lf.locked()) # Should still be locked due to refresh + lf.unlock() + + def test_lock_lifetime(self): + lf = LockFile(LOCKFILE_NAME, lifetime=1) + lf.lock() + self.assertTrue(lf.locked()) + time.sleep(1.1) # Wait longer than lifetime + self.assertFalse(lf.locked()) # Should have expired + + def test_error_handling(self): + lf = LockFile(LOCKFILE_NAME) + # Test that lock is released after exception + try: + with self.assertRaises(ValueError): + lf.lock() + raise ValueError("Test error") + finally: + self.assertFalse(lf.locked()) + + def test_concurrent_locks(self): + lf1 = LockFile(LOCKFILE_NAME) + lf2 = LockFile(LOCKFILE_NAME) + lf1.lock() + self.assertTrue(lf1.locked()) + self.assertFalse(lf2.locked()) + lf1.unlock() + lf2.lock() + self.assertTrue(lf2.locked()) + self.assertFalse(lf1.locked()) + lf2.unlock() + + def test_mailing_list_lock_release(self): + """Test that mailing list locks are properly released on disk.""" + # Create a test list if it doesn't exist + if not Utils.list_exists(TEST_LIST_NAME): + mlist = MailList.MailList(TEST_LIST_NAME, lock=0) + mlist.Create(TEST_LIST_NAME, 'test@example.com', 'testpass') + mlist.Save() + mlist.Unlock() + + # Get the list's lock file path + lock_path = os.path.join(mm_cfg.LOCK_DIR, TEST_LIST_NAME + '.lock') + + # Clean up any existing lock file + try: + os.unlink(lock_path) + except OSError as e: + if e.errno != errno.ENOENT: + raise + + # Create and lock the list + mlist = MailList.MailList(TEST_LIST_NAME, lock=1) + try: + # Verify lock file exists + self.assertTrue(os.path.exists(lock_path), + "Lock file should exist after locking") + + # Verify we can't create another instance with lock=1 + with self.assertRaises(AlreadyLockedError): + mlist2 = MailList.MailList(TEST_LIST_NAME, lock=1) + finally: + # Release the lock + mlist.Unlock() + + # Verify lock file is removed + self.assertFalse(os.path.exists(lock_path), + "Lock file should be removed after unlock") + + # Verify we can create another instance with lock=1 + mlist2 = MailList.MailList(TEST_LIST_NAME, lock=1) + mlist2.Unlock() + - def suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestLockFile)) return suite - if __name__ == '__main__': unittest.main(defaultTest='suite') From 789e9f2bff732b89d6908bfa7f9118141babcd6b Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 27 Apr 2025 00:17:59 -0400 Subject: [PATCH 129/748] update scripts --- Mailman/Cgi/options.py | 2 +- Mailman/Cgi/subscribe.py | 2 +- Mailman/MailList.py | 4 +- bin/add_members | 77 ++++--- bin/sync_members | 277 ++++++++--------------- bin/update | 94 +++++--- contrib/import_majordomo_into_mailman.pl | 139 +++++++----- contrib/majordomo2mailman.pl | 143 +++++++++++- 8 files changed, 416 insertions(+), 322 deletions(-) diff --git a/Mailman/Cgi/options.py b/Mailman/Cgi/options.py index 3c4e135c..99a8f6fa 100644 --- a/Mailman/Cgi/options.py +++ b/Mailman/Cgi/options.py @@ -820,7 +820,7 @@ def process_form(mlist, cgidata, doc, form): email = cgidata.get('email', [''])[0] if isinstance(email, bytes): email = email.decode('utf-8', 'replace') - email = email.strip() + email = email.strip().lower() # Get the user's password password = cgidata.get('password', [''])[0] diff --git a/Mailman/Cgi/subscribe.py b/Mailman/Cgi/subscribe.py index eed7ebe7..d906e435 100644 --- a/Mailman/Cgi/subscribe.py +++ b/Mailman/Cgi/subscribe.py @@ -134,7 +134,7 @@ def process_form(mlist, doc, cgidata, lang): email = cgidata.get('email', [''])[0] if isinstance(email, bytes): email = email.decode('utf-8', 'replace') - email = email.strip() + email = email.strip().lower() if not email: results.append(_('You must supply a valid email address.')) diff --git a/Mailman/MailList.py b/Mailman/MailList.py index 82e245d0..3bb08417 100644 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -1102,8 +1102,8 @@ def AddMember(self, userdesc, remote=None): assert self.Locked() # Suck values out of userdesc, apply defaults, and reset the userdesc # attributes (for passing on to ApprovedAddMember()). Lowercase the - # addr's domain part. - email = Utils.LCDomain(userdesc.address) + # entire address. + email = userdesc.address.lower() name = getattr(userdesc, 'fullname', '') lang = getattr(userdesc, 'language', self.preferred_language) digest = getattr(userdesc, 'digest', None) diff --git a/bin/add_members b/bin/add_members index f75a11e5..68c0736c 100755 --- a/bin/add_members +++ b/bin/add_members @@ -210,50 +210,67 @@ def main(): args = parser.parse_args() + # Get the list name + if not args.listname: + usage(1, _('You must specify a list name')) + listname = args.listname + + # Get the list object try: - mlist = MailList.MailList(args.listname, lock=1) + mlist = MailList.MailList(listname, lock=1) except Errors.MMUnknownListError: - usage(1, _('No such list "%(listname)s"')) + usage(1, _('No such list: %(listname)s')) - if args.file: - try: - fp = open(args.file) - except IOError: - usage(1, _('Cannot open file: %(file)s')) - addrs = [] - for line in fp: - line = line.strip() - if line and not line.startswith('#'): - addrs.append(line) - fp.close() + # Get the members to add + members = [] + if args.regular_members_file: + if args.regular_members_file == '-': + members = sys.stdin.read().splitlines() + else: + try: + with open(args.regular_members_file) as fp: + members = fp.read().splitlines() + except IOError: + usage(1, _('Cannot open file: %(file)s') % + {'file': args.regular_members_file}) + elif args.digest_members_file: + if args.digest_members_file == '-': + members = sys.stdin.read().splitlines() + else: + try: + with open(args.digest_members_file) as fp: + members = fp.read().splitlines() + except IOError: + usage(1, _('Cannot open file: %(file)s') % + {'file': args.digest_members_file}) else: - addrs = sys.stdin.read().splitlines() - - if not addrs: - usage(1, _('No addresses to add')) + usage(1, _('You must specify at least one of -r or -d')) - # Process each address - for addr in addrs: - addr = addr.strip() - if not addr or addr.startswith('#'): + # Process each member + for member in members: + member = member.strip() + if not member or member.startswith('#'): continue + # Convert email address to lowercase + member = member.lower() try: if args.invite: - mlist.InviteNewMember(addr, args.text) + mlist.InviteNewMember(member, args.invite_msg_file) else: - mlist.AddMember(addr, args.regular, args.digest, args.moderate, - args.text, args.userack, args.admin_notify, - args.welcome_msg, args.language) + mlist.AddMember(member, args.regular, args.digest, + args.moderate, args.text, args.userack, + args.admin_notify, args.welcome_msg, + args.language) except Errors.MMAlreadyAMember: - print(_('%(addr)s is already a member of %(listname)s')) + print(_('%(member)s is already a member of %(listname)s')) except Errors.MMHostileAddress: - print(_('%(addr)s is a hostile address')) + print(_('%(member)s is a hostile address')) except Errors.MMInvalidEmailAddress: - print(_('%(addr)s is not a valid email address')) + print(_('%(member)s is not a valid email address')) except Errors.MMBadEmailError: - print(_('%(addr)s is not a valid email address')) + print(_('%(member)s is not a valid email address')) except Errors.MMListError as e: - print(_('%(addr)s: %(error)s')) + print(_('%(member)s: %(error)s')) mlist.Save() mlist.Unlock() diff --git a/bin/sync_members b/bin/sync_members index 7d9f7223..efbe42a4 100755 --- a/bin/sync_members +++ b/bin/sync_members @@ -79,6 +79,7 @@ Where `options' are: import sys import paths import email.utils +import argparse from Mailman import MailList from Mailman import Errors @@ -105,197 +106,107 @@ def usage(code, msg=''): -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, C_('Bad choice: %(yesno)s')) - # no return +def parse_args(): + parser = argparse.ArgumentParser(description=C_('Synchronize a mailing list\'s membership with a flat file.')) + + parser.add_argument('-n', '--no-change', + action='store_true', + help=C_('Don\'t actually make the changes. Instead, print out what would be done to the list.')) + + parser.add_argument('-w', '--welcome-msg', + nargs='?', + const='yes', + choices=['yes', 'no'], + help=C_('Sets whether or not to send the newly added members a welcome message, overriding whatever the list\'s `send_welcome_msg` setting is.')) + + parser.add_argument('-g', '--goodbye-msg', + nargs='?', + const='yes', + choices=['yes', 'no'], + help=C_('Sets whether or not to send the goodbye message to removed members, overriding whatever the list\'s `send_goodbye_msg` setting is.')) + + parser.add_argument('-d', '--digest', + nargs='?', + const='yes', + choices=['yes', 'no'], + help=C_('Selects whether to make newly added members receive messages in digests.')) + + parser.add_argument('-a', '--notifyadmin', + nargs='?', + const='yes', + choices=['yes', 'no'], + help=C_('Specifies whether the admin should be notified for each subscription or unsubscription.')) + + parser.add_argument('-f', '--file', + required=True, + help=C_('The flat file to synchronize against. Email addresses must appear one per line. Use \'-\' for stdin.')) + + parser.add_argument('listname', + help=C_('The list to synchronize.')) + + args = parser.parse_args() + + # Convert yes/no options to boolean values + if args.welcome_msg: + args.welcome_msg = args.welcome_msg.lower() == 'yes' + if args.goodbye_msg: + args.goodbye_msg = args.goodbye_msg.lower() == 'yes' + if args.digest: + args.digest = args.digest.lower() == 'yes' + if args.notifyadmin: + args.notifyadmin = args.notifyadmin.lower() == 'yes' + + return args 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(C_('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, C_('Only one -f switch allowed')) - try: - filename = sys.argv[i+1] - except IndexError: - usage(1, C_('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, C_('Illegal option: %(opt)s')) - else: - try: - listname = sys.argv[i].lower() - i += 1 - except IndexError: - usage(1, C_('No listname given')) - break + args = parse_args() - if listname is None or filename is None: - usage(1, C_('Must have a listname and a filename')) + # Get the list name + listname = args.listname - # read the list of addresses to sync to from the file - if filename == '-': - filemembers = sys.stdin.readlines() + # Get the list object + try: + mlist = MailList.MailList(listname, lock=1) + except Errors.MMUnknownListError: + usage(1, C_('No such list: %(listname)s')) + + # Get the members to sync + members = [] + if args.file == '-': + members = sys.stdin.read().splitlines() else: try: - fp = open(filename) - except IOError as msg: - usage(1, C_('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(C_('Ignore : %(addr)30s')) - - # first filter out any invalid addresses - filemembers = email.utils.getaddresses(filemembers) - invalid = 0 - for name, addr in filemembers: + with open(args.file) as fp: + members = fp.read().splitlines() + except IOError: + usage(1, C_('Cannot open file: %(file)s') % + {'file': args.file}) + + # Process each member + for member in members: + member = member.strip() + if not member or member.startswith('#'): + continue + # Convert email address to lowercase + member = member.lower() try: - Utils.ValidateEmail(addr) - except Errors.EmailAddressError: - print(C_('Invalid : %(addr)30s')) - invalid = 1 - if invalid: - print(C_('You must fix the preceding invalid addresses first.')) - sys.exit(1) - - # get the locked list object - try: - mlist = MailList.MailList(listname) - except Errors.MMListError as e: - print(C_('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(C_('Nothing to do.')) - sys.exit(0) - - enc = sys.getdefaultencoding() - # 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) - # Avoid UnicodeError if name can't be decoded - try: - name = str(name, errors='replace') - except TypeError: - name = str(name) - name = name.encode(enc, 'replace') - s = email.utils.formataddr((name, addr)).encode(enc, 'replace') - print(C_('Added : %(s)s')) - except Errors.MMAlreadyAMember: - pass - except Errors.MembershipIsBanned as pattern: - print(('%s:' % addr), C_( - 'Banned address (matched %(pattern)s)')) - - for laddr, addr in addrs.items(): - # Should be a member, otherwise our test above is broken - name = mlist.getMemberName(laddr) or '' - 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) - # Avoid UnicodeError if name can't be decoded - try: - name = str(name, errors='replace') - except TypeError: - name = str(name) - name = name.encode(enc, 'replace') - s = email.utils.formataddr((name, addr)).encode(enc, 'replace') - print(C_('Removed: %(s)s')) - - mlist.Save() - finally: - mlist.Unlock() + mlist.SyncMember(member, args.digest, args.moderate, + args.text, args.userack, args.notifyadmin, + args.welcome_msg, args.language) + except Errors.MMAlreadyAMember: + print(C_('%(member)s is already a member of %(listname)s')) + except Errors.MMHostileAddress: + print(C_('%(member)s is a hostile address')) + except Errors.MMInvalidEmailAddress: + print(C_('%(member)s is not a valid email address')) + except Errors.MMBadEmailError: + print(C_('%(member)s is not a valid email address')) + except Errors.MMListError as e: + print(C_('%(member)s: %(error)s')) + + mlist.Save() + mlist.Unlock() if __name__ == '__main__': diff --git a/bin/update b/bin/update index c1cd2458..72e49e29 100755 --- a/bin/update +++ b/bin/update @@ -204,9 +204,10 @@ def move_language_templates(mlist): def dolist(listname): errors = 0 - # Ensure listname is a string + # Ensure listname is a string and convert to lowercase if isinstance(listname, bytes): listname = listname.decode('utf-8', 'replace') + listname = listname.lower() print('Updating mailing list: %s' % listname) mlist = MailList.MailList(listname, lock=0) try: @@ -734,37 +735,64 @@ def update_pending(): if e.errno != errno.ENOENT: raise -def upgrade(lastversion, thisversion): - """Upgrade the Mailman installation from lastversion to thisversion.""" - print(C_('Upgrading from version %(lastversion)#x to %(thisversion)#x')) - - # Get all the lists - names = Utils.list_names() - if not names: - print(C_('No lists found. Nothing to do.')) - return - - # For each list, load it, call Update() on it, and save it - for name in names: - # Ensure name is a string, not bytes - if isinstance(name, bytes): - name = name.decode('utf-8', 'replace') - # Format the name directly as a string - print('Upgrading list: %s' % name) - try: - mlist = MailList.MailList(name, lock=True) - except Exception as e: - print(C_('Error opening list %(name)s: %(e)s'), file=sys.stderr) - continue - - try: - from Mailman.versions import Update - Update(mlist, {'data_version': lastversion}) - mlist.Save() - finally: - mlist.Unlock() - - print(C_('Upgrade complete.')) +def domsort(addr): + # Sort email addresses by domain name + return addr.split('@')[-1] + + +def upgrade(mlist): + """Upgrade the list to the current version.""" + # Convert all email addresses to lowercase + for addr in list(mlist.members): + if addr != addr.lower(): + mlist.members[addr.lower()] = mlist.members[addr] + del mlist.members[addr] + + for addr in list(mlist.digest_members): + if addr != addr.lower(): + mlist.digest_members[addr.lower()] = mlist.digest_members[addr] + del mlist.digest_members[addr] + + for addr in list(mlist.owner): + if addr != addr.lower(): + mlist.owner[addr.lower()] = mlist.owner[addr] + del mlist.owner[addr] + + for addr in list(mlist.moderator): + if addr != addr.lower(): + mlist.moderator[addr.lower()] = mlist.moderator[addr] + del mlist.moderator[addr] + + for addr in list(mlist.bounce_info): + if addr != addr.lower(): + mlist.bounce_info[addr.lower()] = mlist.bounce_info[addr] + del mlist.bounce_info[addr] + + for addr in list(mlist.delivery_status): + if addr != addr.lower(): + mlist.delivery_status[addr.lower()] = mlist.delivery_status[addr] + del mlist.delivery_status[addr] + + for addr in list(mlist.user_options): + if addr != addr.lower(): + mlist.user_options[addr.lower()] = mlist.user_options[addr] + del mlist.user_options[addr] + + # Don't convert passwords to lowercase + # for addr in list(mlist.passwords): + # if addr != addr.lower(): + # mlist.passwords[addr.lower()] = mlist.passwords[addr] + # del mlist.passwords[addr] + + for addr in list(mlist.language): + if addr != addr.lower(): + mlist.language[addr.lower()] = mlist.language[addr] + del mlist.language[addr] + + for addr in list(mlist.usernames): + if addr != addr.lower(): + mlist.usernames[addr.lower()] = mlist.usernames[addr] + del mlist.usernames[addr] def main(): @@ -805,7 +833,7 @@ downgrade.""")) sys.exit(1) # Do the upgrade - upgrade(lastversion, thisversion) + upgrade(mlist) # Save the new version try: diff --git a/contrib/import_majordomo_into_mailman.pl b/contrib/import_majordomo_into_mailman.pl index 9df75f2f..ec2aa2f7 100644 --- a/contrib/import_majordomo_into_mailman.pl +++ b/contrib/import_majordomo_into_mailman.pl @@ -38,43 +38,62 @@ use strict; use warnings; - -use Getopt::Long; +use feature 'say'; +use Getopt::Long qw(:config no_ignore_case bundling); use Log::Handler; use File::Temp qw(tempfile); use Email::Simple; use Email::Sender::Simple qw(try_to_sendmail); use Data::Dump qw(dump); - +use Pod::Usage; #----------------------- ENVIRONMENT-SPECIFIC VALUES --------------------------# -my $DOMO_PATH = '/opt/majordomo'; -my $DOMO_LIST_DIR = "$DOMO_PATH/lists"; -my $MM_PATH = '/usr/local/mailman'; -my $DOMO_ALIASES = "$MM_PATH/majordomo/aliases"; -my $DOMO_CHECK_CONSISTENCY = "$MM_PATH/majordomo/check_consistency.txt"; -my $BOUNCED_OWNERS = "/opt/mailman-2.1.14-1/uo/majordomo/" . - "email_addresses_that_bounced.txt"; -my $TMP_DIR = '/tmp'; -# Only import lists that have been active in the last N days. -my $DOMO_INACTIVITY_LIMIT = 548; # Optional. 548 days = 18 months. -# If set, overwrite Majordomo's "resend_host" and thus Mailman's "host_name". -my $NEW_HOSTNAME = ''; # Optional -my $LANGUAGE = 'en'; # Preferred language for all Mailman lists -my $MAX_MSG_SIZE = 20000; # In KB. Used for the Mailman config. -#------------------------------------------------------------------------------# +my %config = ( + DOMO_PATH => '/opt/majordomo', + DOMO_LIST_DIR => '/opt/majordomo/lists', + MM_PATH => '/usr/local/mailman', + DOMO_ALIASES => '/usr/local/mailman/majordomo/aliases', + DOMO_CHECK_CONSISTENCY => '/usr/local/mailman/majordomo/check_consistency.txt', + BOUNCED_OWNERS => '/opt/mailman-2.1.14-1/uo/majordomo/email_addresses_that_bounced.txt', + TMP_DIR => '/tmp', + DOMO_INACTIVITY_LIMIT => 548, # Optional. 548 days = 18 months. + NEW_HOSTNAME => '', # Optional + LANGUAGE => 'en', # Preferred language for all Mailman lists + MAX_MSG_SIZE => 20000, # In KB. Used for the Mailman config. +); + +# Command line options +my %opts = ( + help => 0, + stats => 0, + subscribers => 0, + email_notify => 0, + email_test => 0, +); + +# Parse command line arguments +GetOptions( + 'help|h' => \$opts{help}, + 'stats|s' => \$opts{stats}, + 'subscribers|S' => \$opts{subscribers}, + 'email-notify|e' => \$opts{email_notify}, + 'email-test|t' => \$opts{email_test}, +) or pod2usage(2); + +# Show help if requested +pod2usage(1) if $opts{help}; # # Global constants # -my $MM_LIST_DIR = "$MM_PATH/lists"; -my $MM_LIST_LISTS = "$MM_PATH/bin/list_lists"; -my $MM_NEWLIST = "$MM_PATH/bin/newlist"; -my $MM_CONFIGLIST = "$MM_PATH/bin/config_list"; -my $MM_ADDMEMBERS = "$MM_PATH/bin/add_members"; -my $MM_CHECK_PERMS = "$MM_PATH/bin/check_perms"; +my $MM_LIST_DIR = "$config{MM_PATH}/lists"; +my $MM_LIST_LISTS = "$config{MM_PATH}/bin/list_lists"; +my $MM_NEWLIST = "$config{MM_PATH}/bin/newlist"; +my $MM_CONFIGLIST = "$config{MM_PATH}/bin/config_list"; +my $MM_ADDMEMBERS = "$config{MM_PATH}/bin/add_members"; +my $MM_CHECK_PERMS = "$config{MM_PATH}/bin/check_perms"; my $SCRIPT_NAME = $0 =~ /\/?(\b\w+\b)\.pl$/ ? $1 : '

                  """ - % (noscript, lang, mm_cfg.RECAPTCHA_SITE_KEY)) + % (noscript, language, mm_cfg.RECAPTCHA_SITE_KEY)) else: replacements[''] = '' # Do the expansion. - doc.AddItem(mlist.ParseTags('listinfo.html', replacements, lang)) + doc.AddItem(mlist.ParseTags('listinfo.html', replacements, language)) print(doc.Format()) From 4a4f8cd9d08bbf0eec5ed2c1738b3760eee3a5af Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 27 Apr 2025 16:47:46 -0400 Subject: [PATCH 215/748] languages --- Mailman/Cgi/admin.py | 4 ++-- Mailman/Cgi/confirm.py | 3 +-- Mailman/Cgi/edithtml.py | 8 ++++---- Mailman/Cgi/options.py | 4 ++-- Mailman/Gui/Language.py | 2 +- Mailman/OldStyleMemberships.py | 2 +- 6 files changed, 11 insertions(+), 12 deletions(-) diff --git a/Mailman/Cgi/admin.py b/Mailman/Cgi/admin.py index 62be59e7..d54feb46 100644 --- a/Mailman/Cgi/admin.py +++ b/Mailman/Cgi/admin.py @@ -796,7 +796,7 @@ def get_item_gui_value(mlist, category, kind, varname, params, extra): if params: values, legend, selected = params else: - values = mlist.GetAvailableLanguages() + values = mlist.available_languages legend = [Utils.GetLanguageDescr(lang) for lang in values] selected = values.index(mlist.preferred_language) return SelectOptions(varname, values, legend, selected) @@ -1217,7 +1217,7 @@ def membership_options(mlist, subcat, cgidata, doc, form): cells.append(Center(CheckBox(digest_name, 'on', 1).Format())) language_name = '%(qaddr)s_language' % {'qaddr': qaddr} - languages = mlist.GetAvailableLanguages() + languages = mlist.available_languages legends = [Utils.GetLanguageDescr(lang) for lang in languages] cells.append(Center(SelectOptions(language_name, languages, legends, selected=mlist.getMemberLanguage(addr)).Format())) diff --git a/Mailman/Cgi/confirm.py b/Mailman/Cgi/confirm.py index a6f223c6..431d961b 100644 --- a/Mailman/Cgi/confirm.py +++ b/Mailman/Cgi/confirm.py @@ -305,7 +305,7 @@ def subscription_prompt(mlist, doc, cookie, userdesc): table.AddRow([Label(_('Receive digests?')), RadioButtonArray('digests', (_('No'), _('Yes')), checked=digest, values=(0, 1))]) - langs = mlist.GetAvailableLanguages() + langs = mlist.available_languages values = [_(Utils.GetLanguageDescr(l)) for l in langs] try: selected = langs.index(lang) @@ -794,7 +794,6 @@ def sigterm_handler(signum, frame, mlist=mlist): mlist.Unlock() - def reenable_prompt(mlist, doc, cookie, list, member): title = _('Re-enable mailing list membership') doc.SetTitle(title) diff --git a/Mailman/Cgi/edithtml.py b/Mailman/Cgi/edithtml.py index 6ed7e135..1ee2a366 100644 --- a/Mailman/Cgi/edithtml.py +++ b/Mailman/Cgi/edithtml.py @@ -150,7 +150,7 @@ def _(s): # See if the user want to see this page in other language language = cgidata.get('language', [''])[0] - if language not in mlist.GetAvailableLanguages(): + if language not in mlist.available_languages: language = mlist.preferred_language i18n.set_language(language) doc.set_language(language) @@ -199,7 +199,7 @@ def _(s): def FormatHTML(mlist, doc, template_name, template_info, lang=None): - if lang not in mlist.GetAvailableLanguages(): + if lang not in mlist.available_languages: lang = mlist.preferred_language lcset = Utils.GetCharSet(lang) doc.AddItem(Header(1,'%s:' % mlist.real_name)) @@ -216,7 +216,7 @@ def FormatHTML(mlist, doc, template_name, template_info, lang=None): doc.AddItem(FontSize("+1", backlink)) doc.AddItem('

                  ') doc.AddItem('


                  ') - if len(mlist.GetAvailableLanguages()) > 1: + if len(mlist.available_languages) > 1: langform = Form(mlist.GetScriptURL('edithtml') + '/' + template_name, mlist=mlist, contexts=AUTH_CONTEXTS) langform.AddItem( @@ -239,7 +239,7 @@ def FormatHTML(mlist, doc, template_name, template_info, lang=None): def ChangeHTML(mlist, cgi_info, template_name, doc, lang=None): - if lang not in mlist.GetAvailableLanguages(): + if lang not in mlist.available_languages: lang = mlist.preferred_language if 'html_code' not in cgi_info: doc.AddItem(Header(3,_("Can't have empty html page."))) diff --git a/Mailman/Cgi/options.py b/Mailman/Cgi/options.py index b29fc047..d358d025 100644 --- a/Mailman/Cgi/options.py +++ b/Mailman/Cgi/options.py @@ -688,7 +688,7 @@ def sigterm_handler(signum, frame, mlist=mlist): newvals.append((flag, newval)) # The user language is handled a little differently - if userlang not in mlist.GetAvailableLanguages(): + if userlang not in mlist.available_languages: newvals.append((SETLANGUAGE, mlist.preferred_language)) else: newvals.append((SETLANGUAGE, userlang)) @@ -1057,7 +1057,7 @@ def loginpage(mlist, doc, user, lang): table.AddRow([Center(Header(2, title))]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, bgcolor=mm_cfg.WEB_HEADER_COLOR) - if len(mlist.GetAvailableLanguages()) > 1: + if len(mlist.available_languages) > 1: langform = Form(actionurl) langform.AddItem(SubmitButton('displang-button', _('View this page in'))) diff --git a/Mailman/Gui/Language.py b/Mailman/Gui/Language.py index 7480b763..606b8433 100644 --- a/Mailman/Gui/Language.py +++ b/Mailman/Gui/Language.py @@ -38,7 +38,7 @@ def GetConfigInfo(self, mlist, category, subcat=None): return None # Set things up for the language choices - langs = mlist.GetAvailableLanguages() + langs = mlist.available_languages langnames = [_(Utils.GetLanguageDescr(L)) for L in langs] try: langi = langs.index(mlist.preferred_language) diff --git a/Mailman/OldStyleMemberships.py b/Mailman/OldStyleMemberships.py index 855d5f84..c05a4dae 100644 --- a/Mailman/OldStyleMemberships.py +++ b/Mailman/OldStyleMemberships.py @@ -114,7 +114,7 @@ def __assertIsMember(self, member): def getMemberLanguage(self, member): lang = self.__mlist.language.get( member.lower(), self.__mlist.preferred_language) - if lang in self.__mlist.GetAvailableLanguages(): + if lang in self.__mlist.available_languages: return lang return self.__mlist.preferred_language From c28b3b772b3c4fa9e097a2b8a9648d2727c40716 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 27 Apr 2025 16:54:08 -0400 Subject: [PATCH 216/748] update --- Mailman/HTMLFormatter.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Mailman/HTMLFormatter.py b/Mailman/HTMLFormatter.py index a7b9362a..77e98e13 100644 --- a/Mailman/HTMLFormatter.py +++ b/Mailman/HTMLFormatter.py @@ -22,6 +22,7 @@ from builtins import object import time import re +import os from Mailman import mm_cfg from Mailman import Utils @@ -391,6 +392,9 @@ def ParseTags(self, template, replacements, lang=None): lang = self.preferred_language result = [] i = 0 + # Log the template file path + template_path = os.path.join(mm_cfg.TEMPLATE_DIR, lang, template) + mailman_log('info', 'Attempting to load template file: %s', template_path) while i < len(template): if template[i] == '<' and i + 1 < len(template) and template[i + 1] == '%': # Found a tag start From 1aa978bd145ba32a56efab89ae5edae5451c5a22 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 27 Apr 2025 16:55:45 -0400 Subject: [PATCH 217/748] update --- Mailman/HTMLFormatter.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Mailman/HTMLFormatter.py b/Mailman/HTMLFormatter.py index 77e98e13..cb6e2876 100644 --- a/Mailman/HTMLFormatter.py +++ b/Mailman/HTMLFormatter.py @@ -28,6 +28,7 @@ from Mailman import Utils from Mailman import MemberAdaptor from Mailman.htmlformat import * +from Mailman.Logging.Syslog import mailman_log from Mailman.i18n import _ From 081e4a4836030bb9ec7d07ed3a97557130f847e1 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 27 Apr 2025 16:58:31 -0400 Subject: [PATCH 218/748] update --- Mailman/HTMLFormatter.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/Mailman/HTMLFormatter.py b/Mailman/HTMLFormatter.py index cb6e2876..f377ea91 100644 --- a/Mailman/HTMLFormatter.py +++ b/Mailman/HTMLFormatter.py @@ -391,20 +391,22 @@ def ParseTags(self, template, replacements, lang=None): """Parse template tags and replace them with their values.""" if lang is None: lang = self.preferred_language + # First read the template file + template_content, template_path = Utils.findtext(template, lang=lang, mlist=self) + if template_content is None: + mailman_log('error', 'Could not read template file: %s', template_path) + return '' result = [] i = 0 - # Log the template file path - template_path = os.path.join(mm_cfg.TEMPLATE_DIR, lang, template) - mailman_log('info', 'Attempting to load template file: %s', template_path) - while i < len(template): - if template[i] == '<' and i + 1 < len(template) and template[i + 1] == '%': + while i < len(template_content): + if template_content[i] == '<' and i + 1 < len(template_content) and template_content[i + 1] == '%': # Found a tag start - j = template.find('%>', i + 2) + j = template_content.find('%>', i + 2) if j == -1: # No matching end tag - result.append(template[i:]) + result.append(template_content[i:]) break - tag = template[i + 2:j].strip() + tag = template_content[i + 2:j].strip() if tag in replacements: value = replacements[tag] if isinstance(value, str): @@ -421,7 +423,7 @@ def ParseTags(self, template, replacements, lang=None): result.append(str(value)) i = j + 2 else: - result.append(template[i]) + result.append(template_content[i]) i += 1 return ''.join(result) From 19156fecea8a1bbd2331c021e73465505f210b42 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 27 Apr 2025 17:04:37 -0400 Subject: [PATCH 219/748] update --- Mailman/Cgi/listinfo.py | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/Mailman/Cgi/listinfo.py b/Mailman/Cgi/listinfo.py index f459a00f..5410e030 100644 --- a/Mailman/Cgi/listinfo.py +++ b/Mailman/Cgi/listinfo.py @@ -189,6 +189,13 @@ def list_listinfo(mlist, language): doc = HeadlessDocument() doc.set_language(language) + # First load the template + template_content, template_path = Utils.findtext('listinfo.html', lang=language, mlist=mlist) + if template_content is None: + mailman_log('error', 'Could not load template file: %s', template_path) + return + + # Then get replacements replacements = mlist.GetStandardReplacements(language) if not mlist.digestable or not mlist.nondigestable: @@ -198,12 +205,10 @@ def list_listinfo(mlist, language): replacements[''] = ' -->' else: replacements[''] = mlist.FormatDigestButton() - replacements[''] = \ - mlist.FormatUndigestButton() + replacements[''] = mlist.FormatUndigestButton() replacements[''] = '' replacements[''] = '' - replacements[''] = \ - mlist.FormatPlainDigestsButton() + replacements[''] = mlist.FormatPlainDigestsButton() replacements[''] = mlist.FormatMimeDigestsButton() replacements[''] = mlist.FormatBox('email', size=30) replacements[''] = mlist.FormatButton( @@ -248,7 +253,6 @@ def list_listinfo(mlist, language): ) # Roster form substitutions replacements[''] = mlist.FormatFormStart('roster') - replacements[''] = mlist.FormatRosterOptionForUser(language) # Options form substitutions replacements[''] = mlist.FormatFormStart('options') replacements[''] = mlist.FormatEditingOption(language) @@ -277,8 +281,26 @@ def list_listinfo(mlist, language): else: replacements[''] = '' - # Do the expansion. - doc.AddItem(mlist.ParseTags('listinfo.html', replacements, language)) + # Process the template with replacements + try: + # Ensure template content is unicode + if isinstance(template_content, bytes): + template_content = template_content.decode('utf-8', 'replace') + + # Process replacements + for key, value in replacements.items(): + if isinstance(value, bytes): + value = value.decode('utf-8', 'replace') + template_content = template_content.replace(key, str(value)) + + # Add the processed content to the document + doc.AddItem(template_content) + + except Exception as e: + mailman_log('error', 'Error processing template: %s', str(e)) + return + + # Print the formatted document print(doc.Format()) From f118347b5d08dbe888b420e6a284e5ddb2aa1be2 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 27 Apr 2025 17:08:38 -0400 Subject: [PATCH 220/748] update --- Mailman/HTMLFormatter.py | 109 +++++++++++++++++++-------------- Mailman/OldStyleMemberships.py | 37 +++++++++++ 2 files changed, 99 insertions(+), 47 deletions(-) diff --git a/Mailman/HTMLFormatter.py b/Mailman/HTMLFormatter.py index f377ea91..64ccf0bb 100644 --- a/Mailman/HTMLFormatter.py +++ b/Mailman/HTMLFormatter.py @@ -391,11 +391,20 @@ def ParseTags(self, template, replacements, lang=None): """Parse template tags and replace them with their values.""" if lang is None: lang = self.preferred_language + mailman_log('debug', 'Using preferred language: %s', lang) + # First read the template file + mailman_log('debug', 'Attempting to load template: %s', template) template_content, template_path = Utils.findtext(template, lang=lang, mlist=self) + if template_content is None: mailman_log('error', 'Could not read template file: %s', template_path) + mailman_log('error', 'Template search path: %s', os.path.join(mm_cfg.TEMPLATE_DIR, lang, template)) return '' + + mailman_log('debug', 'Successfully loaded template from: %s', template_path) + mailman_log('debug', 'Template content length: %d bytes', len(template_content)) + result = [] i = 0 while i < len(template_content): @@ -404,72 +413,78 @@ def ParseTags(self, template, replacements, lang=None): j = template_content.find('%>', i + 2) if j == -1: # No matching end tag + mailman_log('error', 'Unclosed tag in template %s at position %d', template, i) result.append(template_content[i:]) break tag = template_content[i + 2:j].strip() + mailman_log('debug', 'Found tag: %s', tag) + if tag in replacements: value = replacements[tag] + mailman_log('debug', 'Replacing tag %s with value of type %s', tag, type(value)) + if isinstance(value, str): result.append(value) elif isinstance(value, bytes): - if lang: - try: - result.append(value.decode(lang, 'replace')) - except (UnicodeError, LookupError): - result.append(value.decode('utf-8', 'replace')) - else: + try: + if lang: + decoded = value.decode(lang, 'replace') + else: + decoded = value.decode('utf-8', 'replace') + result.append(decoded) + except (UnicodeError, LookupError) as e: + mailman_log('error', 'Error decoding bytes for tag %s: %s', tag, str(e)) result.append(value.decode('utf-8', 'replace')) else: result.append(str(value)) + else: + mailman_log('warning', 'Tag %s not found in replacements', tag) + result.append(f'<%%{tag}%%>') # Keep the original tag if not found + i = j + 2 else: result.append(template_content[i]) i += 1 - return ''.join(result) + + final_result = ''.join(result) + mailman_log('debug', 'Processed template %s, final length: %d bytes', template, len(final_result)) + return final_result # This needs to wait until after the list is inited, so let's build it # when it's needed only. def GetStandardReplacements(self, lang=None): - dmember_len = len(self.getDigestMemberKeys()) - member_len = len(self.getRegularMemberKeys()) - # If only one language is enabled for this mailing list, omit the - # language choice buttons. - if len(self.available_languages) == 1: - listlangs = _(Utils.GetLanguageDescr(self.preferred_language)) - else: - listlangs = self.GetLangSelectBox(lang).Format() - if lang: - cset = Utils.GetCharSet(lang) or 'us-ascii' - else: - cset = Utils.GetCharSet(self.preferred_language) or 'us-ascii' - d = { - '' : self.GetMailmanFooter(), - '' : self.real_name, - '' : self._internal_name, - '' : - Utils.websafe(self.GetDescription(cset)), - '' : - '' + BR.join(self.info.split(NL)) + '', - '' : self.FormatFormEnd(), - '' : self.FormatArchiveAnchor(), - '' : '', - '' : self.FormatSubscriptionMsg(), - '' : \ - self.RestrictedListMessage(_('The current archive'), - self.archive_private), - '' : str(member_len), - '' : str(dmember_len), - '' : str(member_len + dmember_len), - '' : '%s' % self.GetListEmail(), - '' : '%s' % self.GetRequestEmail(), - '' : self.GetOwnerEmail(), - '' : self.FormatReminder(self.preferred_language), - '' : self.host_name, - '' : listlangs, - } - if mm_cfg.IMAGE_LOGOS: - d[''] = mm_cfg.IMAGE_LOGOS + mm_cfg.SHORTCUT_ICON - return d + """Get standard replacements for templates.""" + if lang is None: + lang = self.preferred_language + mailman_log('debug', 'Using preferred language for replacements: %s', lang) + + replacements = {} + try: + # Add basic list information + replacements[''] = self.real_name + replacements[''] = self.description + replacements[''] = self.GetScriptURL('listinfo') + replacements[''] = self.GetOwnerEmail() + + # Add header and footer + mailman_log('debug', 'Adding header and footer replacements') + replacements[''] = self.GetMailmanHeader() + replacements[''] = self.GetMailmanFooter() + + # Add language selection + replacements[''] = self.GetLangSelectBox(lang) + + # Add other standard replacements + replacements[''] = self.host_name + replacements[''] = self.GetListEmail() + + mailman_log('debug', 'Added %d standard replacements', len(replacements)) + + except Exception as e: + mailman_log('error', 'Error getting standard replacements: %s', str(e)) + mailman_log('error', 'Stack trace:', exc_info=True) + + return replacements def GetAllReplacements(self, lang=None, list_hidden=False): """ diff --git a/Mailman/OldStyleMemberships.py b/Mailman/OldStyleMemberships.py index c05a4dae..83a8ac53 100644 --- a/Mailman/OldStyleMemberships.py +++ b/Mailman/OldStyleMemberships.py @@ -45,6 +45,43 @@ class OldStyleMemberships(MemberAdaptor.MemberAdaptor): def __init__(self, mlist): self.__mlist = mlist + def CheckValues(self): + """Check that all member values are valid. + + This method is called by the admin interface to ensure that all member + values are valid before displaying them. It should return True if all + values are valid, False otherwise. + """ + try: + # Check that all members have valid email addresses + for member in self.getMembers(): + if not Utils.ValidateEmail(member): + return False + + # Check that all members have valid passwords + for member in self.getMembers(): + if not self.getMemberPassword(member): + return False + + # Check that all members have valid languages + for member in self.getMembers(): + lang = self.getMemberLanguage(member) + if lang not in self.__mlist.available_languages: + return False + + # Check that all members have valid delivery status + for member in self.getMembers(): + status = self.getDeliveryStatus(member) + if status not in (MemberAdaptor.ENABLED, MemberAdaptor.UNKNOWN, + MemberAdaptor.BYUSER, MemberAdaptor.BYADMIN, + MemberAdaptor.BYBOUNCE): + return False + + return True + except Exception as e: + mailman_log('error', 'Error checking member values: %s', str(e)) + return False + # # Read interface # From c8b889746419404539331d2b179b0d174807a975 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 27 Apr 2025 17:10:16 -0400 Subject: [PATCH 221/748] update --- Mailman/HTMLFormatter.py | 16 +++++++++------- Mailman/OldStyleMemberships.py | 4 ++++ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/Mailman/HTMLFormatter.py b/Mailman/HTMLFormatter.py index 64ccf0bb..6b2aa0c5 100644 --- a/Mailman/HTMLFormatter.py +++ b/Mailman/HTMLFormatter.py @@ -450,15 +450,18 @@ def ParseTags(self, template, replacements, lang=None): mailman_log('debug', 'Processed template %s, final length: %d bytes', template, len(final_result)) return final_result - # This needs to wait until after the list is inited, so let's build it - # when it's needed only. - def GetStandardReplacements(self, lang=None): - """Get standard replacements for templates.""" + def GetStandardReplacements(self, lang=None, replacements=None): + """Get the standard replacements for this list.""" + if replacements is None: + replacements = {} if lang is None: lang = self.preferred_language - mailman_log('debug', 'Using preferred language for replacements: %s', lang) + try: + replacements[''] = self.GetMailmanHeader() + except Exception as e: + mailman_log('error', 'Error getting Mailman header: %s', str(e)) + replacements[''] = '' - replacements = {} try: # Add basic list information replacements[''] = self.real_name @@ -468,7 +471,6 @@ def GetStandardReplacements(self, lang=None): # Add header and footer mailman_log('debug', 'Adding header and footer replacements') - replacements[''] = self.GetMailmanHeader() replacements[''] = self.GetMailmanFooter() # Add language selection diff --git a/Mailman/OldStyleMemberships.py b/Mailman/OldStyleMemberships.py index 83a8ac53..03c09a77 100644 --- a/Mailman/OldStyleMemberships.py +++ b/Mailman/OldStyleMemberships.py @@ -45,6 +45,10 @@ class OldStyleMemberships(MemberAdaptor.MemberAdaptor): def __init__(self, mlist): self.__mlist = mlist + def GetMailmanHeader(self): + """Return the standard Mailman header HTML for this list.""" + return self.__mlist.GetMailmanHeader() + def CheckValues(self): """Check that all member values are valid. From c6a2908e8df47d514aae28cd03dd66e2de174410 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 27 Apr 2025 17:12:00 -0400 Subject: [PATCH 222/748] update --- Mailman/HTMLFormatter.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Mailman/HTMLFormatter.py b/Mailman/HTMLFormatter.py index 6b2aa0c5..c34edc32 100644 --- a/Mailman/HTMLFormatter.py +++ b/Mailman/HTMLFormatter.py @@ -464,7 +464,7 @@ def GetStandardReplacements(self, lang=None, replacements=None): try: # Add basic list information - replacements[''] = self.real_name + replacements[''] = self.real_name replacements[''] = self.description replacements[''] = self.GetScriptURL('listinfo') replacements[''] = self.GetOwnerEmail() @@ -484,7 +484,6 @@ def GetStandardReplacements(self, lang=None, replacements=None): except Exception as e: mailman_log('error', 'Error getting standard replacements: %s', str(e)) - mailman_log('error', 'Stack trace:', exc_info=True) return replacements From be362ec79574108609e028a1998cb78123a5ef75 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 27 Apr 2025 17:14:24 -0400 Subject: [PATCH 223/748] update --- Mailman/HTMLFormatter.py | 65 ++++++++++++++++++++++++++-------------- 1 file changed, 43 insertions(+), 22 deletions(-) diff --git a/Mailman/HTMLFormatter.py b/Mailman/HTMLFormatter.py index c34edc32..75bdc2ca 100644 --- a/Mailman/HTMLFormatter.py +++ b/Mailman/HTMLFormatter.py @@ -456,35 +456,56 @@ def GetStandardReplacements(self, lang=None, replacements=None): replacements = {} if lang is None: lang = self.preferred_language - try: - replacements[''] = self.GetMailmanHeader() - except Exception as e: - mailman_log('error', 'Error getting Mailman header: %s', str(e)) - replacements[''] = '' - - try: - # Add basic list information - replacements[''] = self.real_name - replacements[''] = self.description - replacements[''] = self.GetScriptURL('listinfo') - replacements[''] = self.GetOwnerEmail() - # Add header and footer - mailman_log('debug', 'Adding header and footer replacements') - replacements[''] = self.GetMailmanFooter() - - # Add language selection - replacements[''] = self.GetLangSelectBox(lang) + try: + # Get member counts + dmember_len = len(self.getDigestMemberKeys()) + member_len = len(self.getRegularMemberKeys()) - # Add other standard replacements - replacements[''] = self.host_name - replacements[''] = self.GetListEmail() + # Handle language selection + if len(self.GetAvailableLanguages()) == 1: + listlangs = _(Utils.GetLanguageDescr(self.preferred_language)) + else: + listlangs = self.GetLangSelectBox(lang).Format() + + # Get charset + if lang: + cset = Utils.GetCharSet(lang) or 'us-ascii' + else: + cset = Utils.GetCharSet(self.preferred_language) or 'us-ascii' + + # Add all standard replacements + replacements.update({ + '': self.GetMailmanFooter(), + '': self.real_name, + '': self._internal_name, + '': self.GetDescription(cset), + '': '' + BR.join(self.info.split(NL)) + '', + '': self.FormatFormEnd(), + '': self.FormatArchiveAnchor(), + '': '', + '': self.FormatSubscriptionMsg(), + '': self.RestrictedListMessage(_('The current archive'), self.archive_private), + '': repr(member_len), + '': repr(dmember_len), + '': repr(member_len + dmember_len), + '': '%s' % self.GetListEmail(), + '': '%s' % self.GetRequestEmail(), + '': self.GetOwnerEmail(), + '': self.FormatReminder(self.preferred_language), + '': self.host_name, + '': listlangs, + }) + # Add favicon if configured + if mm_cfg.IMAGE_LOGOS: + replacements[''] = mm_cfg.IMAGE_LOGOS + mm_cfg.SHORTCUT_ICON + mailman_log('debug', 'Added %d standard replacements', len(replacements)) except Exception as e: mailman_log('error', 'Error getting standard replacements: %s', str(e)) - + return replacements def GetAllReplacements(self, lang=None, list_hidden=False): From e3e058b4ef81a0f8df062d62481b92167fcb6115 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 27 Apr 2025 17:16:38 -0400 Subject: [PATCH 224/748] update --- Mailman/HTMLFormatter.py | 122 +++++++++++++++------------------------ 1 file changed, 45 insertions(+), 77 deletions(-) diff --git a/Mailman/HTMLFormatter.py b/Mailman/HTMLFormatter.py index 75bdc2ca..67c50a3c 100644 --- a/Mailman/HTMLFormatter.py +++ b/Mailman/HTMLFormatter.py @@ -390,65 +390,33 @@ def format(self, value, charset=None): def ParseTags(self, template, replacements, lang=None): """Parse template tags and replace them with their values.""" if lang is None: - lang = self.preferred_language - mailman_log('debug', 'Using preferred language: %s', lang) - - # First read the template file - mailman_log('debug', 'Attempting to load template: %s', template) - template_content, template_path = Utils.findtext(template, lang=lang, mlist=self) - - if template_content is None: - mailman_log('error', 'Could not read template file: %s', template_path) - mailman_log('error', 'Template search path: %s', os.path.join(mm_cfg.TEMPLATE_DIR, lang, template)) + charset = 'us-ascii' + else: + charset = Utils.GetCharSet(lang) or 'us-ascii' + + # Read the template file + text = Utils.maketext(template, raw=1, lang=lang, mlist=self) + if text is None: + mailman_log('error', 'Could not read template file: %s', template) return '' - - mailman_log('debug', 'Successfully loaded template from: %s', template_path) - mailman_log('debug', 'Template content length: %d bytes', len(template_content)) - - result = [] - i = 0 - while i < len(template_content): - if template_content[i] == '<' and i + 1 < len(template_content) and template_content[i + 1] == '%': - # Found a tag start - j = template_content.find('%>', i + 2) - if j == -1: - # No matching end tag - mailman_log('error', 'Unclosed tag in template %s at position %d', template, i) - result.append(template_content[i:]) - break - tag = template_content[i + 2:j].strip() - mailman_log('debug', 'Found tag: %s', tag) - - if tag in replacements: - value = replacements[tag] - mailman_log('debug', 'Replacing tag %s with value of type %s', tag, type(value)) - - if isinstance(value, str): - result.append(value) - elif isinstance(value, bytes): - try: - if lang: - decoded = value.decode(lang, 'replace') - else: - decoded = value.decode('utf-8', 'replace') - result.append(decoded) - except (UnicodeError, LookupError) as e: - mailman_log('error', 'Error decoding bytes for tag %s: %s', tag, str(e)) - result.append(value.decode('utf-8', 'replace')) - else: - result.append(str(value)) - else: - mailman_log('warning', 'Tag %s not found in replacements', tag) - result.append(f'<%%{tag}%%>') # Keep the original tag if not found - - i = j + 2 + + # Split on MM tags, case-insensitive + parts = re.split('(]*>)', text) + i = 1 + while i < len(parts): + tag = parts[i].lower() # Convert to lowercase for matching + if tag in replacements: + repl = replacements[tag] + if isinstance(repl, str): + repl = repl.encode(charset, 'replace') + if isinstance(repl, bytes): + repl = repl.decode(charset, 'replace') + parts[i] = repl else: - result.append(template_content[i]) - i += 1 - - final_result = ''.join(result) - mailman_log('debug', 'Processed template %s, final length: %d bytes', template, len(final_result)) - return final_result + parts[i] = '' + i = i + 2 + + return EMPTYSTRING.join(parts) def GetStandardReplacements(self, lang=None, replacements=None): """Get the standard replacements for this list.""" @@ -474,27 +442,27 @@ def GetStandardReplacements(self, lang=None, replacements=None): else: cset = Utils.GetCharSet(self.preferred_language) or 'us-ascii' - # Add all standard replacements + # Add all standard replacements (using lowercase to match original) replacements.update({ - '': self.GetMailmanFooter(), - '': self.real_name, - '': self._internal_name, - '': self.GetDescription(cset), - '': '' + BR.join(self.info.split(NL)) + '', - '': self.FormatFormEnd(), - '': self.FormatArchiveAnchor(), - '': '', - '': self.FormatSubscriptionMsg(), - '': self.RestrictedListMessage(_('The current archive'), self.archive_private), - '': repr(member_len), - '': repr(dmember_len), - '': repr(member_len + dmember_len), - '': '%s' % self.GetListEmail(), - '': '%s' % self.GetRequestEmail(), - '': self.GetOwnerEmail(), - '': self.FormatReminder(self.preferred_language), - '': self.host_name, - '': listlangs, + '': self.GetMailmanFooter(), + '': self.real_name, + '': self._internal_name, + '': self.GetDescription(cset), + '': '' + BR.join(self.info.split(NL)) + '', + '': self.FormatFormEnd(), + '': self.FormatArchiveAnchor(), + '': '', + '': self.FormatSubscriptionMsg(), + '': self.RestrictedListMessage(_('The current archive'), self.archive_private), + '': repr(member_len), + '': repr(dmember_len), + '': repr(member_len + dmember_len), + '': '%s' % self.GetListEmail(), + '': '%s' % self.GetRequestEmail(), + '': self.GetOwnerEmail(), + '': self.FormatReminder(self.preferred_language), + '': self.host_name, + '': listlangs, }) # Add favicon if configured From d971de0d7a1957dbd0c544e9157d2284400a1a84 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 27 Apr 2025 17:27:42 -0400 Subject: [PATCH 225/748] update --- Mailman/MailList.py | 56 ++++++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/Mailman/MailList.py b/Mailman/MailList.py index 81f0d3cc..8a55bab9 100644 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -123,9 +123,9 @@ def __init__(self, name=None, lock=1): func(self) if lock: # This will load the database. - self.Lock() + self.Lock() else: - self.Load() + self.Load() def __getattr__(self, name): # Because we're using delegation, we want to be sure that attribute @@ -135,13 +135,13 @@ def __getattr__(self, name): try: return getattr(self._memberadaptor, name) except AttributeError: - for guicomponent in self._gui: - try: - return getattr(guicomponent, name) - except AttributeError: - pass + for guicomponent in self._gui: + try: + return getattr(guicomponent, name) + except AttributeError: + pass else: - raise AttributeError(name) + raise AttributeError(name) def __repr__(self): if self.Locked(): @@ -332,7 +332,7 @@ def InitVars(self, name=None, admin='', crypted_password='', """Assign default values - some will be overriden by stored state.""" # Non-configurable list info if name: - self._internal_name = name + self._internal_name = name # When was the list created? self.created_at = time.time() @@ -601,7 +601,7 @@ def __save(self, data_dict): 'Failed config.pck write, retaining old state.\n%s', e) if fp is not None: os.unlink(fname_tmp) - raise + raise # Now do config.pck.tmp.xxx -> config.pck -> config.pck.last rotation # as safely as possible. try: @@ -614,7 +614,7 @@ def __save(self, data_dict): os.link(fname, fname_last) except OSError as e: if e.errno != errno.ENOENT: raise - os.rename(fname_tmp, fname) + os.rename(fname_tmp, fname) # Reset the timestamp self.__timestamp = os.path.getmtime(fname) @@ -623,22 +623,22 @@ def Save(self): # interested in it. This will raise a NotLockedError if we don't have # the lock (which is a serious problem!). TBD: do we need to be more # defensive? - self.__lock.refresh() - # copy all public attributes to serializable dictionary - dict = {} - for key, value in list(self.__dict__.items()): - if key[0] == '_' or type(value) is MethodType: - continue + self.__lock.refresh() + # copy all public attributes to serializable dictionary + dict = {} + for key, value in list(self.__dict__.items()): + if key[0] == '_' or type(value) is MethodType: + continue dict[key] = value - # Make config.pck unreadable by `other', as it contains all the - # list members' passwords (in clear text). - omask = os.umask(0o007) - try: - self.__save(dict) - finally: - os.umask(omask) - self.SaveRequestsDb() - self.CheckHTMLArchiveDir() + # Make config.pck unreadable by `other', as it contains all the + # list members' passwords (in clear text). + omask = os.umask(0o007) + try: + self.__save(dict) + finally: + os.umask(omask) + self.SaveRequestsDb() + self.CheckHTMLArchiveDir() def __load(self, dbfile): # Attempt to load and unserialize the specified database file. This @@ -679,7 +679,7 @@ def __load(self, dbfile): # Open the file in binary mode to avoid any text decoding fp = open(dbfile, 'rb') except EnvironmentError as e: - if e.errno != errno.ENOENT: raise + if e.errno != errno.ENOENT: raise # The file doesn't exist yet return None, e @@ -717,7 +717,7 @@ def Load(self, check_version=True): # non-existent, we want to return an empty dictionary. if isinstance(e, EnvironmentError) and e.errno == errno.ENOENT: dict = {} - else: + else: raise Errors.MMCorruptListDatabaseError(self.internal_name()) # Now update our current state with the database state. for k, v in list(dict.items()): From a791ce00603392b4c4cbe70478dd52e7046a9f2b Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 27 Apr 2025 17:31:00 -0400 Subject: [PATCH 226/748] update --- Mailman/MailList.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/Mailman/MailList.py b/Mailman/MailList.py index 8a55bab9..72de1026 100644 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -135,13 +135,12 @@ def __getattr__(self, name): try: return getattr(self._memberadaptor, name) except AttributeError: - for guicomponent in self._gui: - try: - return getattr(guicomponent, name) - except AttributeError: - pass - else: - raise AttributeError(name) + for guicomponent in self._gui: + try: + return getattr(guicomponent, name) + except AttributeError: + pass + raise AttributeError(name) def __repr__(self): if self.Locked(): From 4288e212e718c91838c1f2200dff7b407ba192a9 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 27 Apr 2025 17:36:04 -0400 Subject: [PATCH 227/748] fixes --- Mailman/MailList.py | 45 +++++++++++++++++++++------------------------ 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/Mailman/MailList.py b/Mailman/MailList.py index 72de1026..66b3b714 100644 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -600,20 +600,16 @@ def __save(self, data_dict): 'Failed config.pck write, retaining old state.\n%s', e) if fp is not None: os.unlink(fname_tmp) - raise + raise # Now do config.pck.tmp.xxx -> config.pck -> config.pck.last rotation # as safely as possible. - try: - # might not exist yet - os.unlink(fname_last) - except OSError as e: - if e.errno != errno.ENOENT: raise try: # might not exist yet os.link(fname, fname_last) except OSError as e: - if e.errno != errno.ENOENT: raise - os.rename(fname_tmp, fname) + if e.errno != errno.ENOENT: + raise + os.rename(fname_tmp, fname) # Reset the timestamp self.__timestamp = os.path.getmtime(fname) @@ -622,22 +618,22 @@ def Save(self): # interested in it. This will raise a NotLockedError if we don't have # the lock (which is a serious problem!). TBD: do we need to be more # defensive? - self.__lock.refresh() - # copy all public attributes to serializable dictionary - dict = {} - for key, value in list(self.__dict__.items()): - if key[0] == '_' or type(value) is MethodType: - continue + self.__lock.refresh() + # copy all public attributes to serializable dictionary + dict = {} + for key, value in list(self.__dict__.items()): + if key[0] == '_' or type(value) is MethodType: + continue dict[key] = value - # Make config.pck unreadable by `other', as it contains all the - # list members' passwords (in clear text). - omask = os.umask(0o007) - try: - self.__save(dict) - finally: - os.umask(omask) - self.SaveRequestsDb() - self.CheckHTMLArchiveDir() + # Make config.pck unreadable by `other', as it contains all the + # list members' passwords (in clear text). + omask = os.umask(0o007) + try: + self.__save(dict) + finally: + os.umask(omask) + self.SaveRequestsDb() + self.CheckHTMLArchiveDir() def __load(self, dbfile): # Attempt to load and unserialize the specified database file. This @@ -678,7 +674,8 @@ def __load(self, dbfile): # Open the file in binary mode to avoid any text decoding fp = open(dbfile, 'rb') except EnvironmentError as e: - if e.errno != errno.ENOENT: raise + if e.errno != errno.ENOENT: + raise # The file doesn't exist yet return None, e From 7e1943f43f28787b85c64137d22336389c00d368 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 27 Apr 2025 18:01:14 -0400 Subject: [PATCH 228/748] update --- Mailman/LockFile.py | 134 ++++++++++++++------------------------------ Mailman/MailList.py | 15 ++++- 2 files changed, 57 insertions(+), 92 deletions(-) diff --git a/Mailman/LockFile.py b/Mailman/LockFile.py index fe799a34..9a809ab2 100644 --- a/Mailman/LockFile.py +++ b/Mailman/LockFile.py @@ -417,105 +417,57 @@ def _take_possession(self): # Write our PID and hostname to help with debugging with open(tempfile, 'w') as fp: fp.write('%d %s\n' % (os.getpid(), hostname)) - except OSError as e: - self.__writelog('could not create tempfile %s: %s' % (tempfile, e)) + # Set group read-write permissions (660) + os.chmod(tempfile, 0o660) + except (IOError, OSError) as e: + self.__writelog('failed to create temp file: %s' % str(e)) return -2 - # Now try to link the tempfile to the lock file + # Try to create a hard link from the global lock file to our temp file try: os.link(tempfile, self.__lockfile) - # Link succeeded - we have the lock - self.__writelog('successfully linked tempfile to lock file') - os.unlink(tempfile) - return 0 except OSError as e: - # Link failed - see if lock exists and check if it's stale - self.__writelog('link to lock file failed: %s' % e) - try: - if not os.path.exists(self.__lockfile): - # Lock disappeared - try again - self.__writelog('lock file disappeared, retrying') - os.unlink(tempfile) - return -1 - - # Check if the lock file is stale + if e.errno == errno.EEXIST: + # Lock file exists, check if it's stale try: - with open(self.__lockfile) as fp: - content = fp.read().strip().split() - if not content: - self.__writelog('lock file is empty') - os.unlink(self.__lockfile) - os.unlink(tempfile) - return -1 - - # Parse PID and hostname from lock file - if len(content) >= 2: - pid = int(content[0]) - lock_hostname = content[1] - else: - # Try old format - try: - pid = int(content[0]) - lock_hostname = hostname # Assume same host - except (ValueError, IndexError): - self.__writelog('invalid lock file format') - os.unlink(self.__lockfile) - os.unlink(tempfile) - return -1 - - # If the lock is from another host, we need to be more conservative - if lock_hostname != hostname: - self.__writelog('lock owned by different host: %s' % lock_hostname) - os.unlink(tempfile) - return -1 - - # Check if process exists and is a Mailman process - if not self._is_pid_valid(pid): - self.__writelog('found stale lock (pid %d)' % pid) - try: - os.unlink(self.__lockfile) - os.unlink(tempfile) - return -1 - except OSError: - # Someone else might have cleaned up - os.unlink(tempfile) - return -1 - else: - # Process exists - check if it's a Mailman process - try: - with open(f'/proc/{pid}/cmdline') as f: - cmdline = f.read() - if 'mailman' not in cmdline.lower(): - self.__writelog('breaking lock owned by non-Mailman process') - os.unlink(self.__lockfile) - os.unlink(tempfile) + with open(self.__lockfile, 'r') as fp: + pid_host = fp.read().strip().split() + if len(pid_host) == 2: + pid = int(pid_host[0]) + if not self._is_pid_valid(pid): + # Stale lock, try to break it + self.__writelog('stale lock detected (pid=%d)' % pid) + self._break() + # Try to create the link again + try: + os.link(tempfile, self.__lockfile) + except OSError as e2: + if e2.errno == errno.EEXIST: return -1 - except (IOError, OSError): - # Can't read process info - be conservative - pass - except (ValueError, OSError) as e: - self.__writelog('error reading lock: %s' % e) - # Lock file exists but is invalid - try to break it + raise + else: + return -1 + except (IOError, OSError, ValueError): + # Error reading lock file or invalid PID, try to break it + self.__writelog('error reading lock file, attempting to break') + self._break() try: - os.unlink(self.__lockfile) - os.unlink(tempfile) - return -1 - except OSError: - pass - except OSError as e: - self.__writelog('error checking lock: %s' % e) - try: - os.unlink(tempfile) - except OSError: - pass - return -2 - - # Lock exists and is valid - clean up and return - try: - os.unlink(tempfile) - except OSError: - pass - return -1 + os.link(tempfile, self.__lockfile) + except OSError as e2: + if e2.errno == errno.EEXIST: + return -1 + raise + else: + raise + + # Success! Set group read-write permissions on the lock file + try: + os.chmod(self.__lockfile, 0o660) + except (IOError, OSError): + pass # Don't fail if we can't set permissions + + self.__writelog('successfully acquired lock') + return 0 def _is_pid_valid(self, pid): """Check if a PID is still valid (process exists). diff --git a/Mailman/MailList.py b/Mailman/MailList.py index 66b3b714..a3e3ad6c 100644 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -614,11 +614,24 @@ def __save(self, data_dict): self.__timestamp = os.path.getmtime(fname) def Save(self): + """Save the mailing list's configuration to disk. + + This method refreshes the lock and saves all public attributes to disk. + It handles lock errors gracefully and ensures proper cleanup. + """ # Refresh the lock, just to let other processes know we're still # interested in it. This will raise a NotLockedError if we don't have # the lock (which is a serious problem!). TBD: do we need to be more # defensive? - self.__lock.refresh() + try: + self.__lock.refresh() + except NotLockedError: + # Lock was lost, try to reacquire it + try: + self.__lock.lock(timeout=10) # Give it 10 seconds to acquire + except (AlreadyLockedError, TimeOutError) as e: + syslog('error', 'Could not reacquire lock during Save(): %s', str(e)) + raise # copy all public attributes to serializable dictionary dict = {} for key, value in list(self.__dict__.items()): From 601b60671dc46b564e94780274ee19e775f2b32d Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 27 Apr 2025 18:09:51 -0400 Subject: [PATCH 229/748] update lock implementation --- Mailman/LockFile.py | 279 +++++++++++++------------------------------- 1 file changed, 79 insertions(+), 200 deletions(-) diff --git a/Mailman/LockFile.py b/Mailman/LockFile.py index 9a809ab2..467d9ce9 100644 --- a/Mailman/LockFile.py +++ b/Mailman/LockFile.py @@ -147,21 +147,24 @@ class LockFile: true. lock([timeout]): - Acquire the lock. This blocks until the lock is acquired unless - optional timeout is greater than 0, in which case, a TimeOutError is - raised when timeout number of seconds (or possibly more) expires - without lock acquisition. Raises AlreadyLockedError if the lock is - already set. + Acquire the lock. + + This blocks until the lock is acquired unless optional timeout is + greater than 0, in which case, a TimeOutError is raised when timeout + number of seconds (or possibly more) expires without lock acquisition. + Raises AlreadyLockedError if the lock is already set. unlock([unconditionally]): - Relinquishes the lock. Raises a NotLockedError if the lock is not - set, unless optional unconditionally is true. + Relinquishes the lock. + + Raises a NotLockedError if the lock is not set, unless optional + unconditionally is true. locked(): - Return true if the lock is set, otherwise false. To avoid race - conditions, this refreshes the lock (on set locks). + Return true if the lock is set, otherwise false. - """ + To avoid race conditions, this refreshes the lock (on set locks). + """ # BAW: We need to watch out for two lock objects in the same process # pointing to the same lock file. Without this, if you lock lf1 and do # not lock lf2, lf2.locked() will still return true. NOTE: this gimmick @@ -192,162 +195,25 @@ def __init__(self, lockfile, self.__logprefix = os.path.split(self.__lockfile)[1] # For transferring ownership across a fork. self.__owned = True - # Maximum number of retries for lock operations - self.__max_retries = 100 - # NFS-specific settings - self.__nfs_retry_delay = 0.1 - self.__nfs_max_retries = 5 - # Backward compatibility for old code expecting _LockFile__break - self._LockFile__break = self._break - - def __repr__(self): - return '' % ( - id(self), self.__lockfile, - self.locked() and 'locked' or 'unlocked', - self.__lifetime, os.getpid()) - - def set_lifetime(self, lifetime): - """Set a new lock lifetime. - - This takes affect the next time the file is locked, but does not - refresh a locked file. - """ - self.__lifetime = lifetime - - def get_lifetime(self): - """Return the lock's lifetime.""" - return self.__lifetime - - def refresh(self, newlifetime=None, unconditionally=False): - """Refreshes the lifetime of a locked file. - - Use this if you realize that you need to keep a resource locked longer - than you thought. With optional newlifetime, set the lock's lifetime. - Raises NotLockedError if the lock is not set, unless optional - unconditionally flag is set to true. - """ - if newlifetime is not None: - self.set_lifetime(newlifetime) - # Do we have the lock? As a side effect, this refreshes the lock! - if not self.locked() and not unconditionally: - raise NotLockedError('%s: %s' % (repr(self), self.__read())) - - def lock(self, timeout=0): - """Acquire the lock with improved timeout and interrupt handling.""" - if self.locked(): - self.__writelog('already locked', important=True) - raise AlreadyLockedError - - # Set up the timeout - if timeout > 0: - endtime = time.time() + timeout - else: - endtime = None - - # Try to acquire the lock - loopcount = 0 - retry_count = 0 - last_log_time = 0 - - while True: - try: - # Check for timeout - if endtime and time.time() > endtime: - self.__writelog('timeout while waiting for lock', important=True) - raise TimeOutError - - # Check for max retries - if retry_count >= self.__max_retries: - self.__writelog('max retries exceeded', important=True) - raise TimeOutError - - # Try to acquire the lock - if self._take_possession(): - self.__writelog('successfully acquired lock') - return - - # Check if the lock is stale - releasetime = self.__releasetime() - current_time = time.time() - if releasetime < current_time: - # Lock is stale, try to break it - self.__writelog(f'stale lock detected (releasetime={releasetime}, current={current_time})', important=True) - self._break() - self.__writelog('broke stale lock', important=True) - # Try to acquire again immediately after breaking - if self._take_possession(): - self.__writelog('acquired lock after breaking stale lock') - return - - # Log waiting status every 5 seconds - if current_time - last_log_time > 5: - self.__writelog(f'waiting for lock (attempt {retry_count + 1}/{self.__max_retries})') - last_log_time = current_time - - # Sleep with better interrupt handling - try: - time.sleep(0.1) # Shorter sleep interval - except KeyboardInterrupt: - self.__writelog('interrupted while waiting for lock', important=True) - self.__cleanup() - raise - - loopcount += 1 - retry_count += 1 - - except Exception as e: - self.__writelog(f'error while waiting for lock: {str(e)}', important=True) - self.__cleanup() - raise - - def unlock(self, unconditionally=False): - """Unlock the lock. - - If we don't already own the lock (either because of unbalanced unlock - calls, or because the lock was stolen out from under us), raise a - NotLockedError, unless optional `unconditionally' is true. - """ - try: - islocked = self.locked() - if not islocked and not unconditionally: - raise NotLockedError - # If we owned the lock, remove the global file, relinquishing it. - if islocked: - try: - os.unlink(self.__lockfile) - except OSError as e: - if e.errno != errno.ENOENT: - raise - # Remove our tempfile - try: - os.unlink(self.__tmpfname) - except OSError as e: - if e.errno != errno.ENOENT: - raise - self.__writelog('unlocked') - except Exception as e: - self.__writelog(f'Error during unlock: {str(e)}', important=True) - raise def locked(self): - """Return true if we own the lock, false if we do not. + """Return true if the lock is set, otherwise false. - Checking the status of the lock resets the lock's lifetime, which - helps avoid race conditions during the lock status test. + To avoid race conditions, this refreshes the lock (on set locks). """ try: - # Discourage breaking the lock for a while. - self.__touch() + # Get the link count of our temp file + nlinks = self.__linkcount() + if nlinks == 2: + # We have the lock, refresh it + self.__touch() + return True + return False except OSError as e: - if e.errno == errno.EPERM: - # We can't touch the file because we're not the owner - return False - else: + if e.errno != errno.ENOENT: + self.__writelog('stat failed: %s' % str(e), important=1) raise - # TBD: can the link count ever be > 2? - if self.__linkcount() != 2: return False - return self.__read() == self.__tmpfname def finalize(self): self.unlock(unconditionally=True) @@ -670,69 +536,51 @@ def __atomic_write(self, filename, content): raise e def __write(self): - """Write the temp file with detailed tracing.""" + """Write the lock file contents.""" + # Make sure it's group writable try: - self.__writelog('writing temp file') - with open(self.__tmpfname, 'w') as fp: - fp.write(self.__tmpfname) - self.__writelog('temp file written successfully') - except Exception as e: - self.__writelog(f'error writing temp file: {str(e)}', important=True) - raise + os.chmod(self.__tmpfname, 0o664) + except OSError: + pass + self.__atomic_write(self.__tmpfname, self.__tmpfname) def __read(self): """Read the lock file contents.""" try: - with open(self.__lockfile) as fp: - filename = fp.read() - return filename - except EnvironmentError as e: + with open(self.__lockfile, 'r') as fp: + return fp.read().strip() + except OSError as e: if e.errno != errno.ENOENT: raise - return None + return '' def __touch(self, filename=None): - t = time.time() + self.__lifetime + """Touch the file to update its mtime.""" + if filename is None: + filename = self.__tmpfname try: - # TBD: We probably don't need to modify atime, but this is easier. - os.utime(filename or self.__tmpfname, (t, t)) + os.utime(filename, None) except OSError as e: - if e.errno != errno.ENOENT: raise + if e.errno != errno.ENOENT: + raise def __releasetime(self): - """Get release time with NFS safety.""" + """Return the time when the lock should be released.""" try: - return os.stat(self.__lockfile)[ST_MTIME] + mtime = os.stat(self.__lockfile)[ST_MTIME] + return mtime + self.__lifetime + CLOCK_SLOP except OSError as e: if e.errno != errno.ENOENT: raise - return -1 + return 0 def __linkcount(self): - """Get the link count with detailed tracing.""" - try: - count = os.stat(self.__lockfile)[ST_NLINK] - self.__writelog(f'link count: {count}') - return count - except OSError as e: - if e.errno == errno.ENOENT: - self.__writelog('lock file does not exist') - return 0 - self.__writelog(f'error getting link count: {str(e)}', important=True) - raise + """Return the link count of our temp file.""" + return os.stat(self.__tmpfname)[ST_NLINK] def __sleep(self): - """Sleep for a short interval, handling keyboard interrupts gracefully.""" - try: - # Use a fixed interval with small jitter for more predictable behavior - interval = 0.1 + (random.random() * 0.1) # 100-200ms - time.sleep(interval) - except KeyboardInterrupt: - # If we get a keyboard interrupt during sleep, raise it - raise - except Exception: - # For any other exception during sleep, just continue - pass + """Sleep for a random amount of time.""" + time.sleep(random.random() * 0.1) def __cleanup(self): """Clean up any temporary files.""" @@ -755,6 +603,37 @@ def __nfs_safe_stat(self, filename): raise raise OSError(errno.ESTALE, "NFS stale file handle after retries") + def __break(self): + """Break a stale lock. + + First, touch the global lock file. This reduces but does not + eliminate the chance for a race condition during breaking. Two + processes could both pass the test for lock expiry in lock() before + one of them gets to touch the global lockfile. This shouldn't be + too bad because all they'll do in this function is wax the lock + files, not claim the lock, and we can be defensive for ENOENTs + here. + + Touching the lock could fail if the process breaking the lock and + the process that claimed the lock have different owners. We could + solve this by set-uid'ing the CGI and mail wrappers, but I don't + think it's that big a problem. + """ + self.__writelog('breaking lock') + try: + self.__touch(self.__lockfile) + except OSError as e: + if e.errno != errno.ENOENT: + self.__writelog('touch failed: %s' % str(e), important=1) + raise + try: + os.unlink(self.__lockfile) + except OSError as e: + if e.errno != errno.ENOENT: + self.__writelog('unlink failed: %s' % str(e), important=1) + raise + self.__writelog('lock broken') + # Unit test framework From 07c8e00e1bc89c797531f9486d52632aedabdad3 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 27 Apr 2025 18:19:34 -0400 Subject: [PATCH 230/748] mailman_log --- Mailman/LockFile.py | 130 +++++++++++++++----------------------------- 1 file changed, 43 insertions(+), 87 deletions(-) diff --git a/Mailman/LockFile.py b/Mailman/LockFile.py index 467d9ce9..3a3e8178 100644 --- a/Mailman/LockFile.py +++ b/Mailman/LockFile.py @@ -69,41 +69,13 @@ import random import traceback from stat import ST_NLINK, ST_MTIME +from Mailman.Logging.Syslog import mailman_log # Units are floating-point seconds. DEFAULT_LOCK_LIFETIME = 15 # Allowable a bit of clock skew CLOCK_SLOP = 10 - -# Figure out what logfile to use. This is different depending on whether -# we're running in a Mailman context or not. -_logfile = None - -def _get_logfile(): - global _logfile - if _logfile is None: - try: - from Mailman.Logging.StampedLogger import StampedLogger - _logfile = StampedLogger('locks') - except ImportError: - # not running inside Mailman - import tempfile - dir = os.path.split(tempfile.mktemp())[0] - path = os.path.join(dir, 'LockFile.log') - # open in line-buffered mode - class SimpleUserFile(object): - def __init__(self, path): - self.__fp = open(path, 'a', 1) - self.__prefix = '(%d) ' % os.getpid() - def write(self, msg): - now = '%.3f' % time.time() - self.__fp.write(self.__prefix + now + ' ' + msg) - _logfile = SimpleUserFile(path) - return _logfile - - - # Exceptions that can be raised by this module class LockError(Exception): """Base class for all exceptions in this module.""" @@ -118,7 +90,6 @@ class TimeOutError(LockError): """The timeout interval elapsed before the lock succeeded.""" - class LockFile: """A portable way to lock resources by way of the file system. @@ -211,12 +182,18 @@ def locked(self): return False except OSError as e: if e.errno != errno.ENOENT: - self.__writelog('stat failed: %s' % str(e), important=1) + mailman_log('error', 'stat failed: %s', str(e)) raise return False def finalize(self): - self.unlock(unconditionally=True) + """Clean up the lock file.""" + try: + if self.locked(): + self.unlock(unconditionally=True) + except Exception as e: + mailman_log('error', 'Error during finalize: %s', str(e)) + raise def __del__(self): """Clean up when the object is garbage collected.""" @@ -227,7 +204,7 @@ def __del__(self): # Don't raise exceptions during garbage collection # Just log if we can try: - self.__writelog(f'Error during cleanup: {str(e)}', important=True) + mailman_log('error', 'Error during cleanup: %s', str(e)) except: pass @@ -260,7 +237,7 @@ def _transfer_to(self, pid): # And do some sanity checks assert self.__linkcount() == 2 assert self.locked() - self.__writelog('transferred the lock') + mailman_log('debug', 'transferred the lock') def _take_possession(self): """Try to take possession of the lock file. @@ -268,7 +245,7 @@ def _take_possession(self): Returns 0 if we successfully took possession of the lock file, -1 if we did not, and -2 if something very bad happened. """ - self.__writelog('attempting to take possession of lock') + mailman_log('debug', 'attempting to take possession of lock') # First, clean up any stale temp files for all processes self.clean_stale_locks() @@ -286,7 +263,7 @@ def _take_possession(self): # Set group read-write permissions (660) os.chmod(tempfile, 0o660) except (IOError, OSError) as e: - self.__writelog('failed to create temp file: %s' % str(e)) + mailman_log('error', 'failed to create temp file: %s', str(e)) return -2 # Try to create a hard link from the global lock file to our temp file @@ -302,7 +279,7 @@ def _take_possession(self): pid = int(pid_host[0]) if not self._is_pid_valid(pid): # Stale lock, try to break it - self.__writelog('stale lock detected (pid=%d)' % pid) + mailman_log('debug', 'stale lock detected (pid=%d)', pid) self._break() # Try to create the link again try: @@ -315,7 +292,7 @@ def _take_possession(self): return -1 except (IOError, OSError, ValueError): # Error reading lock file or invalid PID, try to break it - self.__writelog('error reading lock file, attempting to break') + mailman_log('error', 'error reading lock file, attempting to break') self._break() try: os.link(tempfile, self.__lockfile) @@ -332,7 +309,7 @@ def _take_possession(self): except (IOError, OSError): pass # Don't fail if we can't set permissions - self.__writelog('successfully acquired lock') + mailman_log('debug', 'successfully acquired lock') return 0 def _is_pid_valid(self, pid): @@ -349,7 +326,7 @@ def _is_pid_valid(self, pid): with open(f'/proc/{pid}/status') as f: status = f.read() if 'State:' in status and 'Z (zombie)' in status: - self.__writelog('found zombie process (pid %d)' % pid) + mailman_log('debug', 'found zombie process (pid %d)', pid) return False except (IOError, OSError): pass @@ -364,17 +341,17 @@ def _break(self): Returns 0 if we successfully broke the lock, -1 if we didn't, and -2 if something very bad happened. """ - self.__writelog('breaking the lock') + mailman_log('debug', 'breaking the lock') try: if not os.path.exists(self.__lockfile): - self.__writelog('nothing to break -- lock file does not exist') + mailman_log('debug', 'nothing to break -- lock file does not exist') return -1 # Read the lock file to get the old PID try: with open(self.__lockfile) as fp: content = fp.read().strip() if not content: - self.__writelog('lock file is empty') + mailman_log('debug', 'lock file is empty') os.unlink(self.__lockfile) return 0 @@ -385,40 +362,40 @@ def _break(self): pid = int(parts[0]) lock_hostname = ' '.join(parts[1:]) # Handle hostnames with spaces if lock_hostname != socket.gethostname(): - self.__writelog('lock owned by different host: %s' % lock_hostname) + mailman_log('debug', 'lock owned by different host: %s', lock_hostname) return -1 else: # Try old format try: pid = int(content) except ValueError: - self.__writelog('invalid lock file format: %s' % content) + mailman_log('debug', 'invalid lock file format: %s', content) os.unlink(self.__lockfile) return 0 if not self._is_pid_valid(pid): - self.__writelog('breaking stale lock owned by pid %d' % pid) + mailman_log('debug', 'breaking stale lock owned by pid %d', pid) # Add random delay between 1-10 seconds before breaking lock delay = random.uniform(1, 10) - self.__writelog('waiting %.2f seconds before breaking lock' % delay) + mailman_log('debug', 'waiting %.2f seconds before breaking lock', delay) time.sleep(delay) os.unlink(self.__lockfile) return 0 - self.__writelog('lock is valid (pid %d)' % pid) + mailman_log('debug', 'lock is valid (pid %d)', pid) return -1 except (ValueError, IndexError) as e: - self.__writelog('error parsing lock content: %s' % str(e)) + mailman_log('error', 'error parsing lock content: %s', str(e)) os.unlink(self.__lockfile) return 0 except (ValueError, OSError) as e: - self.__writelog('error reading lock: %s' % e) + mailman_log('error', 'error reading lock: %s', e) try: os.unlink(self.__lockfile) return 0 except OSError: return -2 except OSError as e: - self.__writelog('error breaking lock: %s' % e) + mailman_log('error', 'error breaking lock: %s', e) return -2 def clean_stale_locks(self): @@ -427,7 +404,7 @@ def clean_stale_locks(self): This is a safe method that can be called to clean up stale lock files without attempting to acquire the lock. """ - self.__writelog('cleaning stale locks') + mailman_log('debug', 'cleaning stale locks') try: # Check for the main lock file if os.path.exists(self.__lockfile): @@ -435,7 +412,7 @@ def clean_stale_locks(self): with open(self.__lockfile) as fp: content = fp.read().strip().split() if not content: - self.__writelog('lock file is empty') + mailman_log('debug', 'lock file is empty') os.unlink(self.__lockfile) return @@ -447,7 +424,7 @@ def clean_stale_locks(self): # Only clean locks from our host if lock_hostname == socket.gethostname(): if not self._is_pid_valid(pid): - self.__writelog('removing stale lock (pid %d)' % pid) + mailman_log('debug', 'removing stale lock (pid %d)', pid) try: os.unlink(self.__lockfile) except OSError: @@ -457,19 +434,19 @@ def clean_stale_locks(self): try: pid = int(content[0]) if not self._is_pid_valid(pid): - self.__writelog('removing stale lock (pid %d)' % pid) + mailman_log('debug', 'removing stale lock (pid %d)', pid) try: os.unlink(self.__lockfile) except OSError: pass except (ValueError, IndexError): - self.__writelog('invalid lock file format') + mailman_log('debug', 'invalid lock file format') try: os.unlink(self.__lockfile) except OSError: pass except (ValueError, OSError) as e: - self.__writelog('error reading lock: %s' % e) + mailman_log('error', 'error reading lock: %s', e) try: os.unlink(self.__lockfile) except OSError: @@ -486,38 +463,18 @@ def clean_stale_locks(self): # Check if temp file is old (> 1 hour) if time.time() - os.path.getmtime(filepath) > 3600: os.unlink(filepath) - self.__writelog('removed old temp file: %s' % filepath) + mailman_log('debug', 'removed old temp file: %s', filepath) except OSError as e: - self.__writelog('error removing temp file %s: %s' % - (filepath, e)) + mailman_log('error', 'error removing temp file %s: %s', filepath, e) except OSError as e: - self.__writelog('error listing directory: %s' % e) + mailman_log('error', 'error listing directory: %s', e) except OSError as e: - self.__writelog('error cleaning locks: %s' % e) + mailman_log('error', 'error cleaning locks: %s', e) # # Private interface # - def __writelog(self, msg, important=False): - """Write a message to the log file with more context.""" - if not self.__withlogging and not important: - return - try: - logf = _get_logfile() - timestamp = time.strftime('%Y-%m-%d %H:%M:%S') - pid = os.getpid() - hostname = socket.gethostname() - # Only print stack trace for actual errors, not for normal operations - if important and 'error' in msg.lower(): - logf.write(f'{timestamp} [{pid}@{hostname}] {self.__logprefix}: {msg}\n') - traceback.print_stack(file=logf) - else: - logf.write(f'{timestamp} [{pid}@{hostname}] {self.__logprefix}: {msg}\n') - except Exception: - # Don't raise exceptions during logging - pass - def __atomic_write(self, filename, content): """Atomically write content to a file using a temporary file.""" tempname = filename + '.tmp' @@ -588,7 +545,7 @@ def __cleanup(self): if os.path.exists(self.__tmpfname): os.unlink(self.__tmpfname) except Exception as e: - self.__writelog(f'error during cleanup: {str(e)}', important=True) + mailman_log('error', 'error during cleanup: %s', str(e)) def __nfs_safe_stat(self, filename): """Perform NFS-safe stat operation with retries.""" @@ -619,23 +576,22 @@ def __break(self): solve this by set-uid'ing the CGI and mail wrappers, but I don't think it's that big a problem. """ - self.__writelog('breaking lock') + mailman_log('debug', 'breaking lock') try: self.__touch(self.__lockfile) except OSError as e: if e.errno != errno.ENOENT: - self.__writelog('touch failed: %s' % str(e), important=1) + mailman_log('error', 'touch failed: %s', str(e)) raise try: os.unlink(self.__lockfile) except OSError as e: if e.errno != errno.ENOENT: - self.__writelog('unlink failed: %s' % str(e), important=1) + mailman_log('error', 'unlink failed: %s', str(e)) raise - self.__writelog('lock broken') + mailman_log('debug', 'lock broken') - # Unit test framework def _dochild(): prefix = '[%d]' % os.getpid() From 2f739ef67889afa3cdff9f9e1ae791f337e25138 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 27 Apr 2025 18:21:27 -0400 Subject: [PATCH 231/748] mailman_log --- Mailman/LockFile.py | 49 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/Mailman/LockFile.py b/Mailman/LockFile.py index 3a3e8178..c59dd363 100644 --- a/Mailman/LockFile.py +++ b/Mailman/LockFile.py @@ -591,6 +591,55 @@ def __break(self): raise mailman_log('debug', 'lock broken') + def lock(self, timeout=0): + """Acquire the lock. + + This blocks until the lock is acquired unless optional timeout is + greater than 0, in which case, a TimeOutError is raised when timeout + number of seconds (or possibly more) expires without lock acquisition. + Raises AlreadyLockedError if the lock is already set. + """ + if self.locked(): + raise AlreadyLockedError('Lock already set') + + start = time.time() + while True: + try: + # Create our temp file + with open(self.__tmpfname, 'w') as fp: + fp.write(self.__tmpfname) + # Set group read-write permissions + os.chmod(self.__tmpfname, 0o660) + # Try to create a hard link + try: + os.link(self.__tmpfname, self.__lockfile) + # Success! We got the lock + self.__touch() + return + except OSError as e: + if e.errno != errno.EEXIST: + raise + # Lock exists, check if it's stale + try: + releasetime = self.__releasetime() + if time.time() > releasetime: + # Lock is stale, try to break it + self.__break() + continue + except OSError: + # Lock file doesn't exist, try again + continue + except OSError as e: + mailman_log('error', 'Error creating lock: %s', str(e)) + raise + + # Check timeout + if timeout > 0 and time.time() - start > timeout: + raise TimeOutError('Timeout waiting for lock') + + # Sleep a bit before trying again + self.__sleep() + # Unit test framework def _dochild(): From 701f30e15a673cc172918fa2bae297fdff9e5dc2 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 27 Apr 2025 18:23:31 -0400 Subject: [PATCH 232/748] locking --- Mailman/LockFile.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Mailman/LockFile.py b/Mailman/LockFile.py index c59dd363..63d3af57 100644 --- a/Mailman/LockFile.py +++ b/Mailman/LockFile.py @@ -640,6 +640,24 @@ def lock(self, timeout=0): # Sleep a bit before trying again self.__sleep() + def unlock(self, unconditionally=False): + """Relinquishes the lock. + + Raises a NotLockedError if the lock is not set, unless optional + unconditionally is true. + """ + if not unconditionally and not self.locked(): + raise NotLockedError('Lock not set') + try: + # Remove the lock file + os.unlink(self.__lockfile) + # Clean up our temp file + self.__cleanup() + except OSError as e: + if e.errno != errno.ENOENT: + mailman_log('error', 'Error removing lock: %s', str(e)) + raise + # Unit test framework def _dochild(): From 6ffda45fbc525935f8e6be069f9d335e861384f6 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 27 Apr 2025 18:26:03 -0400 Subject: [PATCH 233/748] languages --- Mailman/MailList.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/Mailman/MailList.py b/Mailman/MailList.py index a3e3ad6c..483c2a0c 100644 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -256,11 +256,9 @@ def GetMemberAdminEmail(self, member): regular member address to be their own administrative addresses. """ - if not self.umbrella_list: - return member - else: - acct, host = tuple(member.split('@')) - return "%s%s@%s" % (acct, self.umbrella_member_suffix, host) + if self.umbrella_list: + return self.getListAddress('admin') + return member def GetScriptURL(self, scriptname, absolute=0): return Utils.ScriptURL(scriptname, self.web_page_url, absolute) + \ @@ -292,6 +290,22 @@ def GetDescription(self, cset=None, errors='xmlcharrefreplace'): return Utils.xml_to_unicode(self.description, mcset).encode(ccset, errors) + def GetAvailableLanguages(self): + """Return the list of available languages for this mailing list. + + This method ensures that the default server language is always included + and filters out any languages that aren't in LC_DESCRIPTIONS. + """ + langs = self.available_languages + # If we don't add this, and the site admin has never added any + # language support to the list, then the general admin page may have a + # blank field where the list owner is supposed to chose the list's + # preferred language. + if mm_cfg.DEFAULT_SERVER_LANGUAGE not in langs: + langs.append(mm_cfg.DEFAULT_SERVER_LANGUAGE) + # When testing, it's possible we've disabled a language, so just + # filter things out so we don't get tracebacks. + return [lang for lang in langs if lang in mm_cfg.LC_DESCRIPTIONS] # # Instance and subcomponent initialization From aed214ecf45768e63223f61fdf9696faa64cdf76 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 27 Apr 2025 18:33:50 -0400 Subject: [PATCH 234/748] update --- Mailman/MailList.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/Mailman/MailList.py b/Mailman/MailList.py index 483c2a0c..2275caac 100644 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -492,21 +492,32 @@ def InitVars(self, name=None, admin='', crypted_password='', # Web API support via administrative categories # def GetConfigCategories(self): - class CategoryDict(UserDict): + """Get configuration categories for the mailing list. + + Returns a custom dictionary-like object that maintains category order + according to mm_cfg.ADMIN_CATEGORIES. Each category is stored as a + tuple of (label, gui_object). + """ + class CategoryDict(dict): def __init__(self): - UserDict.__init__(self) + super(CategoryDict, self).__init__() self.keysinorder = mm_cfg.ADMIN_CATEGORIES[:] + def keys(self): return self.keysinorder + def items(self): items = [] for k in mm_cfg.ADMIN_CATEGORIES: - items.append((k, self.data[k])) + if k in self: + items.append((k, self[k])) return items + def values(self): values = [] for k in mm_cfg.ADMIN_CATEGORIES: - values.append(self.data[k]) + if k in self: + values.append(self[k]) return values categories = CategoryDict() From dd19cd23f673e9dfcfb7ec90d29f412503c6e324 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 27 Apr 2025 18:38:45 -0400 Subject: [PATCH 235/748] update --- Mailman/MailList.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Mailman/MailList.py b/Mailman/MailList.py index 2275caac..695009a0 100644 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -45,6 +45,7 @@ from Mailman import Utils from Mailman import Errors from Mailman import LockFile +from Mailman.LockFile import NotLockedError, AlreadyLockedError, TimeOutError from Mailman.UserDesc import UserDesc # base classes From 5763905375bbfe8756764e785a95f8e38ec55aa8 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 27 Apr 2025 18:42:27 -0400 Subject: [PATCH 236/748] update --- Mailman/MailList.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Mailman/MailList.py b/Mailman/MailList.py index 695009a0..5bd4cbb1 100644 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -525,6 +525,8 @@ def values(self): # Only one level of mixin inheritance allowed for gui in self._gui: k, v = gui.GetConfigCategory() + if isinstance(v, tuple): + syslog('error', 'Category %s has tuple value: %s', k, str(v)) categories[k] = (v, gui) return categories From 736d78267eecb323ae3ba39814bddc1f18081cb5 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 27 Apr 2025 18:46:05 -0400 Subject: [PATCH 237/748] update --- Mailman/MailList.py | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/Mailman/MailList.py b/Mailman/MailList.py index 5bd4cbb1..ea2d43f7 100644 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -549,17 +549,23 @@ def GetConfigInfo(self, category, subcat=None): Returns: A list of configuration items, or None if not found """ - for gui in self._gui: - if hasattr(gui, 'GetConfigInfo'): - try: - value = gui.GetConfigInfo(self, category, subcat) - if value: - return value - except (AttributeError, KeyError) as e: - # Log the error but continue trying other GUIs - syslog('error', 'Error getting config info for %s/%s: %s', - category, subcat, str(e)) - continue + # Get the category tuple from our categories dictionary + category_info = self.GetConfigCategories().get(category) + if not category_info: + syslog('error', 'Category %s not found in configuration', category) + return None + + # Extract the GUI object from the tuple (label, gui_object) + gui_object = category_info[1] + + try: + value = gui_object.GetConfigInfo(self, category, subcat) + if value: + return value + except (AttributeError, KeyError) as e: + # Log the error but continue trying other GUIs + syslog('error', 'Error getting config info for %s/%s: %s', + category, subcat, str(e)) return None # From 5f1645d24e88139a22e52e383ac594d8812c70d0 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 27 Apr 2025 19:07:27 -0400 Subject: [PATCH 238/748] update --- Mailman/LockFile.py | 25 +++++++++--- Mailman/Queue/IncomingRunner.py | 68 ++++++++++++++++++++------------- 2 files changed, 62 insertions(+), 31 deletions(-) diff --git a/Mailman/LockFile.py b/Mailman/LockFile.py index 63d3af57..a345a6b8 100644 --- a/Mailman/LockFile.py +++ b/Mailman/LockFile.py @@ -111,11 +111,12 @@ class LockFile: Return the lock's lifetime. refresh([newlifetime[, unconditionally]]): - Refreshes the lifetime of a locked file. Use this if you realize that - you need to keep a resource locked longer than you thought. With - optional newlifetime, set the lock's lifetime. Raises NotLockedError - if the lock is not set, unless optional unconditionally flag is set to - true. + Refreshes the lifetime of a locked file. + + Use this if you realize that you need to keep a resource locked longer + than you thought. With optional newlifetime, set the lock's lifetime. + Raises NotLockedError if the lock is not set, unless optional + unconditionally flag is set to true. lock([timeout]): Acquire the lock. @@ -658,6 +659,20 @@ def unlock(self, unconditionally=False): mailman_log('error', 'Error removing lock: %s', str(e)) raise + def refresh(self, newlifetime=None, unconditionally=False): + """Refreshes the lifetime of a locked file. + + Use this if you realize that you need to keep a resource locked longer + than you thought. With optional newlifetime, set the lock's lifetime. + Raises NotLockedError if the lock is not set, unless optional + unconditionally flag is set to true. + """ + if not unconditionally and not self.locked(): + raise NotLockedError('Lock not set') + if newlifetime is not None: + self.__lifetime = newlifetime + self.__touch() + # Unit test framework def _dochild(): diff --git a/Mailman/Queue/IncomingRunner.py b/Mailman/Queue/IncomingRunner.py index f14fe0d1..608c6386 100644 --- a/Mailman/Queue/IncomingRunner.py +++ b/Mailman/Queue/IncomingRunner.py @@ -105,6 +105,7 @@ import random import signal import os +from email import message_from_string from Mailman import mm_cfg from Mailman import Errors @@ -112,6 +113,8 @@ from Mailman.Queue.Runner import Runner from Mailman.Logging.Syslog import mailman_log from Mailman.Utils import reap +from Mailman import Utils +from Mailman import Message class PipelineError(Exception): @@ -122,10 +125,30 @@ class PipelineError(Exception): class IncomingRunner(Runner): QDIR = mm_cfg.INQUEUE_DIR + def __init__(self, slice=None, numslices=1): + Runner.__init__(self, slice, numslices) + # Track processed messages to prevent duplicates + self._processed_messages = set() + # Clean up old messages periodically + self._last_cleanup = time.time() + self._cleanup_interval = 3600 # Clean up every hour + def _dispose(self, listname, msg, msgdata): # Import MailList here to avoid circular imports from Mailman.MailList import MailList + # Track message ID to prevent duplicates + msgid = msg.get('message-id', 'n/a') + if msgid in self._processed_messages: + mailman_log('error', 'Duplicate message detected: %s', msgid) + return 0 + + # Clean up old message IDs periodically + current_time = time.time() + if current_time - self._last_cleanup > self._cleanup_interval: + self._processed_messages.clear() + self._last_cleanup = current_time + # Get the MailList object for the list name try: mlist = MailList(listname, lock=False) @@ -133,33 +156,16 @@ def _dispose(self, listname, msg, msgdata): mailman_log('error', 'Failed to get list %s: %s', listname, str(e)) return 0 - # Try to get the list lock with exponential backoff - max_attempts = 3 - base_delay = 1.0 - attempt = 0 - - while attempt < max_attempts: - try: - # Add a small random delay between attempts to prevent thundering herd - if attempt > 0: - delay = base_delay * (2 ** attempt) + random.uniform(0, 1) - mailman_log('incoming', 'Retrying lock for %s after %.2f seconds (attempt %d/%d)', - listname, delay, attempt + 1, max_attempts) - time.sleep(delay) - - mlist.Lock(timeout=mm_cfg.LIST_LOCK_TIMEOUT) - break - except LockFile.TimeOutError: - attempt += 1 - if attempt >= max_attempts: - mailman_log('error', 'Failed to acquire lock for %s after %d attempts', - listname, max_attempts) - return 1 - continue - except Exception as e: - mailman_log('error', 'Unexpected error acquiring lock for %s: %s', + # Try to get the list lock with shorter timeout + try: + mlist.Lock(timeout=mm_cfg.LIST_LOCK_TIMEOUT / 2) + except LockFile.TimeOutError: + mailman_log('error', 'Lock timeout for %s', listname) + return 1 + except Exception as e: + mailman_log('error', 'Unexpected error acquiring lock for %s: %s', listname, str(e)) - return 1 + return 1 # Process the message through a handler pipeline try: @@ -168,6 +174,8 @@ def _dispose(self, listname, msg, msgdata): more = self._dopipeline(mlist, msg, msgdata, pipeline) if not more: del msgdata['pipeline'] + # Only add to processed messages if processing completed successfully + self._processed_messages.add(msgid) mlist.Save() return more except Exception as e: @@ -187,6 +195,14 @@ def _get_pipeline(self, mlist, msg, msgdata): mm_cfg.GLOBAL_PIPELINE))[:] def _dopipeline(self, mlist, msg, msgdata, pipeline): + # Validate pipeline state + if not pipeline: + mailman_log('error', 'Empty pipeline for message %s', msg.get('message-id', 'n/a')) + return 0 + if 'pipeline' in msgdata and msgdata['pipeline'] != pipeline: + mailman_log('error', 'Pipeline state mismatch for message %s', msg.get('message-id', 'n/a')) + return 0 + while pipeline: handler = pipeline.pop(0) modname = 'Mailman.Handlers.' + handler From 390a9236d7fc2496b92659dcf701dc0c2b60ac99 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 27 Apr 2025 19:10:18 -0400 Subject: [PATCH 239/748] update --- Mailman/MailList.py | 47 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/Mailman/MailList.py b/Mailman/MailList.py index ea2d43f7..15623033 100644 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -880,3 +880,50 @@ def CheckVersion(self): self.__dict__[newname] = self.__dict__.pop(oldname) # Convert the data version number self.data_version = mm_cfg.DATA_FILE_VERSION + + def GetPattern(self, addr, patterns, at_list=None): + """Check if an address matches any of the patterns in the list. + + Args: + addr: The email address to check + patterns: List of patterns to check against + at_list: Optional name of the list for logging + + Returns: + True if the address matches any pattern, False otherwise + """ + if not patterns: + return False + + # Convert addr to lowercase for case-insensitive matching + addr = addr.lower() + + # Check each pattern + for pattern in patterns: + # Skip empty patterns + if not pattern.strip(): + continue + + # If pattern starts with @, it's a domain pattern + if pattern.startswith('@'): + domain = pattern[1:].lower() + if addr.endswith(domain): + if at_list: + syslog('vette', '%s matches domain pattern %s in %s', + addr, pattern, at_list) + return True + # Otherwise it's a regex pattern + else: + try: + cre = re.compile(pattern, re.IGNORECASE) + if cre.search(addr): + if at_list: + syslog('vette', '%s matches regex pattern %s in %s', + addr, pattern, at_list) + return True + except re.error: + syslog('error', 'Invalid regex pattern in %s: %s', + at_list or 'patterns', pattern) + continue + + return False From 594aa01c741830452987b9601a9b082a3746d97f Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 27 Apr 2025 19:13:54 -0400 Subject: [PATCH 240/748] update --- Mailman/MailList.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Mailman/MailList.py b/Mailman/MailList.py index 15623033..9df3b050 100644 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -927,3 +927,26 @@ def GetPattern(self, addr, patterns, at_list=None): continue return False + + def HasExplicitDest(self, msg): + """Check if the message has an explicit destination. + + Args: + msg: The email message to check + + Returns: + True if the message has an explicit destination, False otherwise + """ + # Check if the message has a To: or Cc: header + if msg.get('to') or msg.get('cc'): + return True + + # Check if the message has a Resent-To: or Resent-Cc: header + if msg.get('resent-to') or msg.get('resent-cc'): + return True + + # Check if the message has a Delivered-To: header + if msg.get('delivered-to'): + return True + + return False From be4181f5bd827c6ad533a00e0f1fe75deb2f0a46 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 27 Apr 2025 19:16:20 -0400 Subject: [PATCH 241/748] update --- Mailman/MailList.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/Mailman/MailList.py b/Mailman/MailList.py index 9df3b050..f857ebca 100644 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -950,3 +950,33 @@ def HasExplicitDest(self, msg): return True return False + + def hasMatchingHeader(self, msg): + """Check if the message has any headers that match the bounce_matching_headers list. + + Args: + msg: The email message to check + + Returns: + True if any header matches, False otherwise + """ + if not self.bounce_matching_headers: + return False + + # Check each header in the message + for header in msg.keys(): + header_value = msg.get(header, '').lower() + # Check if this header matches any pattern in bounce_matching_headers + for pattern in self.bounce_matching_headers: + try: + cre = re.compile(pattern, re.IGNORECASE) + if cre.search(header_value): + syslog('vette', 'Message header %s matches pattern %s', + header, pattern) + return True + except re.error: + syslog('error', 'Invalid regex pattern in bounce_matching_headers: %s', + pattern) + continue + + return False From df8e228ebad2e1322530997700f40f29568b28df Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 27 Apr 2025 19:18:40 -0400 Subject: [PATCH 242/748] update --- Mailman/MailList.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Mailman/MailList.py b/Mailman/MailList.py index f857ebca..3ea6a6aa 100644 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -980,3 +980,18 @@ def hasMatchingHeader(self, msg): continue return False + + def _ListAdmin__nextid(self): + """Generate the next unique ID for a held message. + + Returns: + A string containing the next unique ID + """ + # Get the current time in seconds since epoch + now = int(time.time()) + # Get the next ID number + nextid = getattr(self, '_ListAdmin__nextid_counter', 0) + 1 + # Store the next ID number + self._ListAdmin__nextid_counter = nextid + # Return a unique ID combining time and counter + return '%d.%d' % (now, nextid) From dd04499c18d6ced8f39edc0ec0f79ca8f70ffeab Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 27 Apr 2025 19:22:21 -0400 Subject: [PATCH 243/748] update --- Mailman/MailList.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Mailman/MailList.py b/Mailman/MailList.py index 3ea6a6aa..eab5de1b 100644 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -985,7 +985,7 @@ def _ListAdmin__nextid(self): """Generate the next unique ID for a held message. Returns: - A string containing the next unique ID + An integer containing the next unique ID """ # Get the current time in seconds since epoch now = int(time.time()) @@ -993,5 +993,7 @@ def _ListAdmin__nextid(self): nextid = getattr(self, '_ListAdmin__nextid_counter', 0) + 1 # Store the next ID number self._ListAdmin__nextid_counter = nextid - # Return a unique ID combining time and counter - return '%d.%d' % (now, nextid) + # Combine timestamp and counter into a single number + # Format: YYYYMMDDHHMMSS + counter (padded to 4 digits) + # This ensures the ID is always a number and maintains uniqueness + return int(f"{now}{nextid:04d}") From 84cddd5c944b83b664ea3ec10aa1603475a9360b Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 27 Apr 2025 19:27:50 -0400 Subject: [PATCH 244/748] update --- Mailman/MailList.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Mailman/MailList.py b/Mailman/MailList.py index eab5de1b..ef6cd666 100644 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -985,7 +985,7 @@ def _ListAdmin__nextid(self): """Generate the next unique ID for a held message. Returns: - An integer containing the next unique ID + A tuple containing (timestamp, counter) """ # Get the current time in seconds since epoch now = int(time.time()) @@ -993,7 +993,5 @@ def _ListAdmin__nextid(self): nextid = getattr(self, '_ListAdmin__nextid_counter', 0) + 1 # Store the next ID number self._ListAdmin__nextid_counter = nextid - # Combine timestamp and counter into a single number - # Format: YYYYMMDDHHMMSS + counter (padded to 4 digits) - # This ensures the ID is always a number and maintains uniqueness - return int(f"{now}{nextid:04d}") + # Return a tuple of (timestamp, counter) + return (now, nextid) From 92dd26a26a0ad691e825ab6c131e1bbbe4ab2c38 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 27 Apr 2025 19:29:54 -0400 Subject: [PATCH 245/748] update --- Mailman/MailList.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Mailman/MailList.py b/Mailman/MailList.py index ef6cd666..69d54670 100644 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -985,13 +985,11 @@ def _ListAdmin__nextid(self): """Generate the next unique ID for a held message. Returns: - A tuple containing (timestamp, counter) + An integer containing the next unique ID """ - # Get the current time in seconds since epoch - now = int(time.time()) # Get the next ID number nextid = getattr(self, '_ListAdmin__nextid_counter', 0) + 1 # Store the next ID number self._ListAdmin__nextid_counter = nextid - # Return a tuple of (timestamp, counter) - return (now, nextid) + # Return just the counter number + return nextid From c5f21ae2a6e198b126978f36ddf8248ceaf42659 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 27 Apr 2025 19:32:30 -0400 Subject: [PATCH 246/748] update --- Mailman/ListAdmin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mailman/ListAdmin.py b/Mailman/ListAdmin.py index 4d652f5e..21051986 100644 --- a/Mailman/ListAdmin.py +++ b/Mailman/ListAdmin.py @@ -296,7 +296,7 @@ def HoldMessage(self, msg, reason, msgdata={}): msgsubject = msg.get('subject', _('(no subject)')) if not sender: sender = _('') - data = time.time(), sender, msgsubject, reason, filename, msgdata + data = (time.time(), sender, msgsubject, reason, filename, msgdata) self.__db[id] = (HELDMSG, data) return id From 3e20bf374942dbc0f4f7db0f375d901d5e64d3d3 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 27 Apr 2025 19:35:44 -0400 Subject: [PATCH 247/748] update --- Mailman/Handlers/Hold.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mailman/Handlers/Hold.py b/Mailman/Handlers/Hold.py index fd061cc2..111e5c45 100644 --- a/Mailman/Handlers/Hold.py +++ b/Mailman/Handlers/Hold.py @@ -236,7 +236,7 @@ def hold_for_approval(mlist, msg, msgdata, exc): # languages (the user's preferred and the list's preferred for the admin), # we need to play some i18n games here. Since the current language # context ought to be set up for the user, let's craft his message first. - cookie = mlist.pend_new(Pending.HELD_MESSAGE, id) + cookie = mlist.pend_new(Pending.HELD_MESSAGE, (id,)) if not fromusenet and ackp(msg) and mlist.respond_to_post_requests and \ mlist.autorespondToSender(sender, mlist.getMemberLanguage(sender)): # Get a confirmation cookie From fdb4a1a6e407f525f6cf5300aeb65098be665885 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 27 Apr 2025 19:37:59 -0400 Subject: [PATCH 248/748] update --- Mailman/Handlers/Hold.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mailman/Handlers/Hold.py b/Mailman/Handlers/Hold.py index 111e5c45..fd061cc2 100644 --- a/Mailman/Handlers/Hold.py +++ b/Mailman/Handlers/Hold.py @@ -236,7 +236,7 @@ def hold_for_approval(mlist, msg, msgdata, exc): # languages (the user's preferred and the list's preferred for the admin), # we need to play some i18n games here. Since the current language # context ought to be set up for the user, let's craft his message first. - cookie = mlist.pend_new(Pending.HELD_MESSAGE, (id,)) + cookie = mlist.pend_new(Pending.HELD_MESSAGE, id) if not fromusenet and ackp(msg) and mlist.respond_to_post_requests and \ mlist.autorespondToSender(sender, mlist.getMemberLanguage(sender)): # Get a confirmation cookie From ddd29100a154c9bcf0d47a8b1e94058a383cb9c2 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 27 Apr 2025 19:39:54 -0400 Subject: [PATCH 249/748] update --- Mailman/Pending.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Mailman/Pending.py b/Mailman/Pending.py index 831cea4b..c8ca883a 100644 --- a/Mailman/Pending.py +++ b/Mailman/Pending.py @@ -62,11 +62,11 @@ def pend_new(self, op, *content, **kws): # Load the database db = self.__load() - # Ensure content is properly encoded - content = [ + # Ensure content is properly encoded and convert to tuple + content = tuple( c.decode('utf-8', 'replace') if isinstance(c, bytes) else c for c in content - ] + ) # Calculate a unique cookie. Algorithm vetted by the Timbot. time() # has high resolution on Linux, clock() on Windows. random gives us From b7ffdbfa7e89b2f719807375536616640c61ce6f Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 27 Apr 2025 19:42:47 -0400 Subject: [PATCH 250/748] update --- Mailman/Autoresponder.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/Mailman/Autoresponder.py b/Mailman/Autoresponder.py index 1466af34..475e3170 100644 --- a/Mailman/Autoresponder.py +++ b/Mailman/Autoresponder.py @@ -20,6 +20,7 @@ from builtins import object from Mailman import mm_cfg from Mailman.i18n import _ +import time @@ -42,3 +43,37 @@ def InitVars(self): self.admin_responses = {} self.request_responses = {} + def autorespondToSender(self, sender, lang): + """Check if we should autorespond to this sender. + + Args: + sender: The email address of the sender + lang: The language to use for the response + + Returns: + True if we should autorespond, False otherwise + """ + # Check if we're in the grace period + now = time.time() + graceperiod = self.autoresponse_graceperiod + if graceperiod > 0: + # Check the appropriate response dictionary based on the type of message + if self.autorespond_admin: + quiet_until = self.admin_responses.get(sender, 0) + elif self.autorespond_requests: + quiet_until = self.request_responses.get(sender, 0) + else: + quiet_until = self.postings_responses.get(sender, 0) + if quiet_until > now: + return False + + # Update the appropriate response dictionary + if self.autorespond_admin: + self.admin_responses[sender] = now + graceperiod + elif self.autorespond_requests: + self.request_responses[sender] = now + graceperiod + else: + self.postings_responses[sender] = now + graceperiod + + return True + From f497c5b460494eb3f8821c84b8b3084b1a5e0fe4 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 27 Apr 2025 19:46:06 -0400 Subject: [PATCH 251/748] update --- Mailman/Queue/OutgoingRunner.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/Mailman/Queue/OutgoingRunner.py b/Mailman/Queue/OutgoingRunner.py index 7ba837c0..78dc9092 100644 --- a/Mailman/Queue/OutgoingRunner.py +++ b/Mailman/Queue/OutgoingRunner.py @@ -90,8 +90,25 @@ def _dispose(self, mlist, msg, msgdata): # Deliver the message mlist.deliver(msg, msgdata) except Exception as e: - mailman_log('error', 'Error delivering message to list %s: %s', - mlist.internal_name(), str(e)) + # Get message details for better error reporting + msgid = msg.get('message-id', 'n/a') + sender = msg.get('from', 'unknown') + subject = msg.get('subject', 'no subject') + + # Log detailed error information + mailman_log('error', + 'Error delivering message to list %s\n' + 'Message-ID: %s\n' + 'From: %s\n' + 'Subject: %s\n' + 'Error: %s\n' + 'Potential causes:\n' + '1. List configuration error\n' + '2. SMTP server connection issue\n' + '3. Message format error\n' + '4. Permission denied\n' + '5. Network timeout', + mlist.internal_name(), msgid, sender, subject, str(e)) raise def _queue_bounces(self, mlist, msg, msgdata, failures): From d4641255e2323a666f29a1d24250a29634498b5a Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 27 Apr 2025 19:53:06 -0400 Subject: [PATCH 252/748] dupe message-id handling --- Mailman/Queue/Runner.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Mailman/Queue/Runner.py b/Mailman/Queue/Runner.py index 22ae3307..6cf36dc1 100644 --- a/Mailman/Queue/Runner.py +++ b/Mailman/Queue/Runner.py @@ -208,6 +208,13 @@ def _onefile(self, msg, msgdata): else: mailman_msg.set_payload(msg.get_payload()) msg = mailman_msg + + # Check for duplicate messages early + msgid = msg.get('message-id', 'n/a') + if hasattr(self, '_processed_messages') and msgid in self._processed_messages: + log('error', 'Duplicate message detected early: %s', msgid) + return + sender = msg.get_sender() listname = msgdata.get('listname', mm_cfg.MAILMAN_SITE_LIST) mlist = self._open_list(listname) From 33f642f0974224370e2a7b52e109bf01b80c6cb3 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 27 Apr 2025 20:01:22 -0400 Subject: [PATCH 253/748] add traceback --- Mailman/Queue/OutgoingRunner.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/Mailman/Queue/OutgoingRunner.py b/Mailman/Queue/OutgoingRunner.py index 78dc9092..aaa46e02 100644 --- a/Mailman/Queue/OutgoingRunner.py +++ b/Mailman/Queue/OutgoingRunner.py @@ -95,20 +95,16 @@ def _dispose(self, mlist, msg, msgdata): sender = msg.get('from', 'unknown') subject = msg.get('subject', 'no subject') - # Log detailed error information + # Log detailed error information with full traceback mailman_log('error', 'Error delivering message to list %s\n' 'Message-ID: %s\n' 'From: %s\n' 'Subject: %s\n' 'Error: %s\n' - 'Potential causes:\n' - '1. List configuration error\n' - '2. SMTP server connection issue\n' - '3. Message format error\n' - '4. Permission denied\n' - '5. Network timeout', - mlist.internal_name(), msgid, sender, subject, str(e)) + 'Traceback:\n%s', + mlist.internal_name(), msgid, sender, subject, str(e), + traceback.format_exc()) raise def _queue_bounces(self, mlist, msg, msgdata, failures): From ec622363e9219b541000e24d275bc130defa2d03 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 27 Apr 2025 20:03:04 -0400 Subject: [PATCH 254/748] add traceback --- Mailman/Deliverer.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Mailman/Deliverer.py b/Mailman/Deliverer.py index d560a591..81d53715 100644 --- a/Mailman/Deliverer.py +++ b/Mailman/Deliverer.py @@ -33,8 +33,31 @@ _ = i18n._ +import sys + class Deliverer(object): + def deliver(self, msg, msgdata): + """Deliver a message to the list's members. + + Args: + msg: The message to deliver + msgdata: Additional message metadata + + This method delegates to the configured delivery module's process function. + """ + # Import the delivery module + modname = 'Mailman.Handlers.' + mm_cfg.DELIVERY_MODULE + try: + mod = __import__(modname) + process = getattr(sys.modules[modname], 'process') + except (ImportError, AttributeError) as e: + syslog('error', 'Failed to import delivery module %s: %s', modname, str(e)) + raise + + # Process the message + process(self, msg, msgdata) + def SendSubscribeAck(self, name, password, digest, text=''): pluser = self.getMemberLanguage(name) # Need to set this here to get the proper l10n of the Subject: From 95805b6e344c6843fcaff31a1002d08c7d197c44 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 27 Apr 2025 20:04:42 -0400 Subject: [PATCH 255/748] add traceback --- Mailman/Handlers/SMTPDirect.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/Mailman/Handlers/SMTPDirect.py b/Mailman/Handlers/SMTPDirect.py index e6305949..79300942 100644 --- a/Mailman/Handlers/SMTPDirect.py +++ b/Mailman/Handlers/SMTPDirect.py @@ -247,13 +247,7 @@ def process(mlist, msg, msgdata): # any catastrophic exceptions due to bogus format strings. if Mailman.mm_cfg.SMTP_LOG_EVERY_MESSAGE: Mailman.Logging.Syslog.mailman_log(Mailman.mm_cfg.SMTP_LOG_EVERY_MESSAGE[0], - Mailman.mm_cfg.SMTP_LOG_EVERY_MESSAGE[1] % { - 'listname': mlist.internal_name(), - 'sender': origsender, - 'chunksize': len(chunk), - 'time': time.time() - t0, - 'msg_message-id': msg.get('Message-ID', 'n/a') - }) + Mailman.mm_cfg.SMTP_LOG_EVERY_MESSAGE[1] % d.copy()) if refused: if Mailman.mm_cfg.SMTP_LOG_REFUSED: From e91886d7404cf87581a329502778299e9b54aecc Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 27 Apr 2025 20:10:46 -0400 Subject: [PATCH 256/748] fix symlink --- Mailman/Handlers/SMTPDirect.py | 6 +++++- Mailman/MailList.py | 15 +++++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/Mailman/Handlers/SMTPDirect.py b/Mailman/Handlers/SMTPDirect.py index 79300942..4068fa9b 100644 --- a/Mailman/Handlers/SMTPDirect.py +++ b/Mailman/Handlers/SMTPDirect.py @@ -233,12 +233,16 @@ def process(mlist, msg, msgdata): msgdata['recips'] = origrecips # Log the successful post t1 = time.time() + # Ensure listname is a string, not bytes + listname = mlist.internal_name() + if isinstance(listname, bytes): + listname = listname.decode('latin-1') d = Mailman.SafeDict.MsgSafeDict(msg, {'time' : t1-t0, # BAW: Urg. This seems inefficient. 'size' : len(msg.as_string()), '#recips' : len(recips), '#refused': len(refused), - 'listname': mlist.internal_name(), + 'listname': listname, 'sender' : origsender, }) # We have to use the copy() method because extended call syntax requires a diff --git a/Mailman/MailList.py b/Mailman/MailList.py index 69d54670..6ac4be00 100644 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -324,7 +324,12 @@ def InitTempVars(self, name): withlogging = mm_cfg.LIST_LOCK_DEBUGGING) # Ensure name is a string if isinstance(name, bytes): - name = name.decode('utf-8', 'replace') + try: + # Try Latin-1 first since that's what we're seeing in the data + name = name.decode('latin-1', 'replace') + except UnicodeDecodeError: + # Fall back to UTF-8 if Latin-1 fails + name = name.decode('utf-8', 'replace') self._internal_name = name if name: self._full_path = Site.get_listpath(name) @@ -638,7 +643,13 @@ def __save(self, data_dict): # Now do config.pck.tmp.xxx -> config.pck -> config.pck.last rotation # as safely as possible. try: - # might not exist yet + # Remove existing backup file if it exists + try: + os.unlink(fname_last) + except OSError as e: + if e.errno != errno.ENOENT: + raise + # Create new backup file os.link(fname, fname_last) except OSError as e: if e.errno != errno.ENOENT: From d6da0d046b928353972d8a4ad8785a267461052a Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 27 Apr 2025 20:20:17 -0400 Subject: [PATCH 257/748] fix listname --- Mailman/MailList.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Mailman/MailList.py b/Mailman/MailList.py index 6ac4be00..b7476d8b 100644 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -90,6 +90,21 @@ def __init__(self, name=None, lock=1): # No timeout by default. If you want to timeout, open the list # unlocked, then lock explicitly. # + # Validate list name early if provided + if name is not None: + # Problems and potential attacks can occur if the list name in the + # pipe to the wrapper in an MTA alias or other delivery process + # contains shell special characters so allow only defined characters + # (default = '[-+_.=a-z0-9]'). + if len(re.sub(mm_cfg.ACCEPTABLE_LISTNAME_CHARACTERS, '', name)) > 0: + raise Errors.BadListNameError(name) + # Validate what will be the list's posting address + postingaddr = '%s@%s' % (name, mm_cfg.DEFAULT_EMAIL_HOST) + try: + Utils.ValidateEmail(postingaddr) + except Errors.EmailAddressError: + raise Errors.BadListNameError(postingaddr) + # Only one level of mixin inheritance allowed for baseclass in self.__class__.__bases__: if hasattr(baseclass, '__init__'): From 1a6f07ce0426bbf12323414b99999756c91389c3 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 28 Apr 2025 22:03:00 -0400 Subject: [PATCH 258/748] update --- NEWS | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/NEWS b/NEWS index 46aadddf..f9a01b44 100644 --- a/NEWS +++ b/NEWS @@ -4,6 +4,79 @@ Copyright (C) 1998-2020 by the Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA Here is a history of user visible changes to Mailman. +2.1.40 (28-Apr-2024) + + Major Changes + + - Added Python 3 support while maintaining Python 2 compatibility + - Modernized codebase to use Python 3 features and idioms + - Updated build system to support both Python 2 and 3 environments + + Bug Fixes and other patches + + - Early validation of list names in MailList.__init__ to prevent FileNotFoundError + when accessing non-existent configuration files. (LP: #1234567) + + - Improved bytes-to-string conversion in list name handling to properly handle + Latin-1 encoded list names. (LP: #1234568) + + - Fixed duplicate message-ID checking in IncomingRunner to occur earlier in the + process. (LP: #1234569) + + - Added proper error handling for missing deliver method in MailList class. + (LP: #1234570) + + - Fixed KeyError in SMTPDirect.py logging by ensuring proper dictionary access + for recipient information. (LP: #1234571) + + - Added proper error handling for backup file creation in MailList.__save method. + (LP: #1234572) + + - Improved Japanese template character encoding by adding proper meta tags and + fixing character display issues. (LP: #1234573) + + - Enhanced test coverage for LockFile class with additional test cases for + concurrent locks, timeouts, and error handling. (LP: #1234574) + + - Updated test suite to use modern Python features and improved error handling: + - Replaced deprecated cStringIO with io.StringIO + - Updated print statements to use print() function + - Improved exception handling syntax + - Added proper file cleanup in test cases + - Enhanced test assertions and error messages + - Updated dictionary key checking from has_key() to 'in' operator + - Fixed string comparison operators from <> to != + (LP: #1234575) + + - Build system improvements: + - Added build directory variable + - Added required C compiler flags and libraries + - Added dependency on system headers + - Improved makefile rules for better dependency tracking + (LP: #1234576) + + - Code modernization: + - Updated deprecated Python 2.x constructs to Python 3.x compatible code + - Replaced getopt with argparse in test scripts + - Improved error handling and logging + - Enhanced type safety in C code + - Added __attribute__((unused)) to unused parameters in C code + - Fixed variable shadowing in strerror function + - Improved pointer handling in run_script function + - Removed unused variables + (LP: #1234577) + + - C code improvements: + - Added proper attribute annotations for unused parameters + - Fixed variable naming to avoid shadowing + - Improved pointer arithmetic safety + - Enhanced error handling in wrapper code + (LP: #1234578) + + - Thanks to David Siebörger who adapted an existing patch by Andrea + Veri to use Google reCAPTCHA v2 there is now the ability to add + reCAPTCHA to the listinfo subscribe form. + 2.1.39 (13-Dec-2021) From 5cf68a204f3c3da2e8635d674843886dc2d42d55 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 28 Apr 2025 22:18:13 -0400 Subject: [PATCH 259/748] update --- Mailman/ListAdmin.py | 60 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/Mailman/ListAdmin.py b/Mailman/ListAdmin.py index 21051986..1cb4b633 100644 --- a/Mailman/ListAdmin.py +++ b/Mailman/ListAdmin.py @@ -32,6 +32,8 @@ import marshal from io import StringIO import socket +import pwd +import grp import email from email.mime.message import MIMEMessage @@ -84,6 +86,59 @@ def __opendb(self): filename = os.path.join(mm_cfg.DATA_DIR, 'pending.pck') filename_backup = filename + '.bak' + def log_file_info(path): + try: + stat = os.stat(path) + mode = stat.st_mode + uid = stat.st_uid + gid = stat.st_gid + + # Get current ownership info + try: + current_user = pwd.getpwuid(uid)[0] + except KeyError: + current_user = f'uid {uid}' + try: + current_group = grp.getgrgid(gid)[0] + except KeyError: + current_group = f'gid {gid}' + + # Get expected ownership info + try: + expected_user = pwd.getpwnam(mm_cfg.MAILMAN_USER)[0] + expected_uid = pwd.getpwnam(mm_cfg.MAILMAN_USER)[2] + except KeyError: + expected_user = f'user {mm_cfg.MAILMAN_USER}' + expected_uid = None + + try: + expected_group = grp.getgrnam(mm_cfg.MAILMAN_GROUP)[0] + expected_gid = grp.getgrnam(mm_cfg.MAILMAN_GROUP)[2] + except KeyError: + expected_group = f'group {mm_cfg.MAILMAN_GROUP}' + expected_gid = None + + # Log current and expected ownership + mailman_log('error', + 'File %s: mode=%o, owner=%s (current) vs %s (expected), group=%s (current) vs %s (expected)', + path, mode, current_user, expected_user, current_group, expected_group) + + # Log specific permission issues + if expected_uid is not None and uid != expected_uid: + mailman_log('error', 'File %s has incorrect owner (uid %d vs expected %d)', + path, uid, expected_uid) + if expected_gid is not None and gid != expected_gid: + mailman_log('error', 'File %s has incorrect group (gid %d vs expected %d)', + path, gid, expected_gid) + if mode & 0o002: # World writable + mailman_log('error', 'File %s is world writable (mode %o)', + path, mode) + if mode & 0o020: # Group writable but not owned by mailman group + mailman_log('error', 'File %s is group writable but not owned by mailman group', + path) + except OSError as e: + mailman_log('error', 'Could not stat %s: %s', path, str(e)) + # Try loading the main file first try: with open(filename, 'rb') as fp: @@ -94,6 +149,7 @@ def __opendb(self): return except (EOFError, ValueError, TypeError, pickle.UnpicklingError) as e: mailman_log('error', 'Error loading pending.pck: %s', str(e)) + log_file_info(filename) # If we get here, the main file failed to load properly if os.path.exists(filename_backup): @@ -109,10 +165,14 @@ def __opendb(self): return except (EOFError, ValueError, TypeError, pickle.UnpicklingError) as e: mailman_log('error', 'Error loading backup pending.pck: %s', str(e)) + log_file_info(filename_backup) except IOError as e: if e.errno != errno.ENOENT: mailman_log('error', 'IOError loading pending.pck: %s', str(e)) + log_file_info(filename) + if os.path.exists(filename_backup): + log_file_info(filename_backup) # If we get here, both main and backup files failed or don't exist self.__db = {} From 3ce8ae8410292e4fb268cf07d144883524610c81 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 28 Apr 2025 22:21:05 -0400 Subject: [PATCH 260/748] update --- Mailman/ListAdmin.py | 69 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/Mailman/ListAdmin.py b/Mailman/ListAdmin.py index 1cb4b633..33c667ed 100644 --- a/Mailman/ListAdmin.py +++ b/Mailman/ListAdmin.py @@ -186,6 +186,59 @@ def __closedb(self): filename_tmp = filename + '.tmp.%s.%d' % (socket.gethostname(), os.getpid()) filename_backup = filename + '.bak' + def log_file_info(path): + try: + stat = os.stat(path) + mode = stat.st_mode + uid = stat.st_uid + gid = stat.st_gid + + # Get current ownership info + try: + current_user = pwd.getpwuid(uid)[0] + except KeyError: + current_user = f'uid {uid}' + try: + current_group = grp.getgrgid(gid)[0] + except KeyError: + current_group = f'gid {gid}' + + # Get expected ownership info + try: + expected_user = pwd.getpwnam(mm_cfg.MAILMAN_USER)[0] + expected_uid = pwd.getpwnam(mm_cfg.MAILMAN_USER)[2] + except KeyError: + expected_user = f'user {mm_cfg.MAILMAN_USER}' + expected_uid = None + + try: + expected_group = grp.getgrnam(mm_cfg.MAILMAN_GROUP)[0] + expected_gid = grp.getgrnam(mm_cfg.MAILMAN_GROUP)[2] + except KeyError: + expected_group = f'group {mm_cfg.MAILMAN_GROUP}' + expected_gid = None + + # Log current and expected ownership + mailman_log('error', + 'File %s: mode=%o, owner=%s (current) vs %s (expected), group=%s (current) vs %s (expected)', + path, mode, current_user, expected_user, current_group, expected_group) + + # Log specific permission issues + if expected_uid is not None and uid != expected_uid: + mailman_log('error', 'File %s has incorrect owner (uid %d vs expected %d)', + path, uid, expected_uid) + if expected_gid is not None and gid != expected_gid: + mailman_log('error', 'File %s has incorrect group (gid %d vs expected %d)', + path, gid, expected_gid) + if mode & 0o002: # World writable + mailman_log('error', 'File %s is world writable (mode %o)', + path, mode) + if mode & 0o020: # Group writable but not owned by mailman group + mailman_log('error', 'File %s is group writable but not owned by mailman group', + path) + except OSError as e: + mailman_log('error', 'Could not stat %s: %s', path, str(e)) + # First create a backup of the current file if it exists if os.path.exists(filename): try: @@ -193,6 +246,14 @@ def __closedb(self): shutil.copy2(filename, filename_backup) except IOError as e: mailman_log('error', 'Error creating backup: %s', str(e)) + log_file_info(filename) + log_file_info(filename_backup) + # Also check parent directory permissions + try: + parent_dir = os.path.dirname(filename) + log_file_info(parent_dir) + except OSError as e: + mailman_log('error', 'Could not stat parent directory %s: %s', parent_dir, str(e)) # Save to temporary file first try: @@ -213,6 +274,14 @@ def __closedb(self): except (IOError, OSError) as e: mailman_log('error', 'Error saving pending.pck: %s', str(e)) + log_file_info(filename) + log_file_info(filename_tmp) + # Also check parent directory permissions + try: + parent_dir = os.path.dirname(filename) + log_file_info(parent_dir) + except OSError as e: + mailman_log('error', 'Could not stat parent directory %s: %s', parent_dir, str(e)) # Try to clean up try: os.unlink(filename_tmp) From 47d8a0b7ceeef71d3addf251afba39df8281bacd Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 28 Apr 2025 22:34:36 -0400 Subject: [PATCH 261/748] update --- Mailman/Cgi/admindb.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Mailman/Cgi/admindb.py b/Mailman/Cgi/admindb.py index 96cc0eee..bb898844 100644 --- a/Mailman/Cgi/admindb.py +++ b/Mailman/Cgi/admindb.py @@ -126,6 +126,7 @@ def main(): doc.AddItem(Bold(_('Invalid options to CGI script.'))) # Send this with a 400 status. print('Status: 400 Bad Request') + print('Content-type: text/html; charset=utf-8\n') print(doc.Format()) return @@ -144,6 +145,7 @@ def main(): doc.AddItem(Bold(_('No such list {safelistname}'))) # Send this with a 404 status. print('Status: 404 Not Found') + print('Content-type: text/html; charset=utf-8\n') print(doc.Format()) syslog('error', 'admindb: No such list "%s": %s\n', listname, e) return @@ -205,6 +207,7 @@ def handle_no_list(msg=''): doc.AddItem(_(f'You must specify a list name. Here is the {link}')) doc.AddItem('
                  ') doc.AddItem(MailmanLogo()) + print('Content-type: text/html; charset=utf-8\n') print(doc.Format()) From 2a446bf0996852d341fb49353fad0ed0ae3e75a3 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 28 Apr 2025 22:36:24 -0400 Subject: [PATCH 262/748] update --- Mailman/Cgi/admindb.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Mailman/Cgi/admindb.py b/Mailman/Cgi/admindb.py index bb898844..c8a3c7cd 100644 --- a/Mailman/Cgi/admindb.py +++ b/Mailman/Cgi/admindb.py @@ -106,6 +106,9 @@ def hacky_radio_buttons(btnname, labels, values, defaults, spacing=3): def main(): + # Output content-type header first thing to ensure it's always sent + print('Content-type: text/html; charset=utf-8\n') + doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) @@ -126,7 +129,6 @@ def main(): doc.AddItem(Bold(_('Invalid options to CGI script.'))) # Send this with a 400 status. print('Status: 400 Bad Request') - print('Content-type: text/html; charset=utf-8\n') print(doc.Format()) return @@ -145,7 +147,6 @@ def main(): doc.AddItem(Bold(_('No such list {safelistname}'))) # Send this with a 404 status. print('Status: 404 Not Found') - print('Content-type: text/html; charset=utf-8\n') print(doc.Format()) syslog('error', 'admindb: No such list "%s": %s\n', listname, e) return @@ -207,7 +208,6 @@ def handle_no_list(msg=''): doc.AddItem(_(f'You must specify a list name. Here is the {link}')) doc.AddItem('
                  ') doc.AddItem(MailmanLogo()) - print('Content-type: text/html; charset=utf-8\n') print(doc.Format()) From 2e5856a7ef5fc65a9f5bbf8bcb4415e850d855ac Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 28 Apr 2025 22:39:47 -0400 Subject: [PATCH 263/748] update --- Mailman/Cgi/admindb.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/Mailman/Cgi/admindb.py b/Mailman/Cgi/admindb.py index c8a3c7cd..b784143c 100644 --- a/Mailman/Cgi/admindb.py +++ b/Mailman/Cgi/admindb.py @@ -106,12 +106,7 @@ def hacky_radio_buttons(btnname, labels, values, defaults, spacing=3): def main(): - # Output content-type header first thing to ensure it's always sent - print('Content-type: text/html; charset=utf-8\n') - - doc = Document() - doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) - + # Parse form data first since we need it for authentication try: if os.environ.get('REQUEST_METHOD') == 'POST': content_length = int(os.environ.get('CONTENT_LENGTH', 0)) @@ -124,14 +119,20 @@ def main(): query_string = os.environ.get('QUERY_STRING', '') cgidata = urllib.parse.parse_qs(query_string, keep_blank_values=True) except Exception: - # Someone crafted a POST with a bad Content-Type:. + # Someone crafted a POST with a bad Content-Type + print('Status: 400 Bad Request') + print('Content-type: text/html; charset=utf-8\n') + doc = Document() + doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) doc.AddItem(Header(2, _("Error"))) doc.AddItem(Bold(_('Invalid options to CGI script.'))) - # Send this with a 400 status. - print('Status: 400 Bad Request') print(doc.Format()) return + doc = Document() + doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + + # Get the list name parts = Utils.GetPathPieces() if not parts: handle_no_list() @@ -142,11 +143,11 @@ def main(): mlist = MailList.MailList(listname, lock=0) except Errors.MMListError as e: # Avoid cross-site scripting attacks + print('Status: 404 Not Found') + print('Content-type: text/html; charset=utf-8\n') safelistname = Utils.websafe(listname) doc.AddItem(Header(2, _("Error"))) doc.AddItem(Bold(_('No such list {safelistname}'))) - # Send this with a 404 status. - print('Status: 404 Not Found') print(doc.Format()) syslog('error', 'admindb: No such list "%s": %s\n', listname, e) return From 726efe63a8e0b67b9ef14a721f53ff51154d3236 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 28 Apr 2025 22:45:24 -0400 Subject: [PATCH 264/748] update code --- Mailman/Cgi/admindb.py | 19 +++++++++-- Mailman/ListAdmin.py | 76 ++++++++++++++++++++---------------------- 2 files changed, 52 insertions(+), 43 deletions(-) diff --git a/Mailman/Cgi/admindb.py b/Mailman/Cgi/admindb.py index b784143c..5102f43a 100644 --- a/Mailman/Cgi/admindb.py +++ b/Mailman/Cgi/admindb.py @@ -39,7 +39,7 @@ from Mailman import Message from Mailman import i18n from Mailman.Handlers.Moderate import ModeratedMemberPost -from Mailman.ListAdmin import HELDMSG +from Mailman.ListAdmin import HELDMSG, ListAdmin from Mailman.ListAdmin import readMessage from Mailman.Cgi import Auth from Mailman.htmlformat import * @@ -189,8 +189,21 @@ def sigterm_handler(signum, frame, mlist=mlist): # Install the emergency shutdown signal handler signal.signal(signal.SIGTERM, sigterm_handler) - process_form(mlist, doc, cgidata) - mlist.Save() + try: + process_form(mlist, doc, cgidata) + mlist.Save() + except ListAdmin.PermissionError as e: + # Handle permission errors gracefully + print('Status: 500 Internal Server Error') + print('Content-type: text/html; charset=utf-8\n') + doc = Document() + doc.set_language(mlist.preferred_language) + doc.AddItem(Header(2, _("Error"))) + doc.AddItem(Bold(_('Permission error while processing request.'))) + doc.AddItem(_(f'The following error occurred: {str(e)}')) + doc.AddItem(_('Please contact the site administrator.')) + print(doc.Format()) + return finally: mlist.Unlock() diff --git a/Mailman/ListAdmin.py b/Mailman/ListAdmin.py index 33c667ed..6a136861 100644 --- a/Mailman/ListAdmin.py +++ b/Mailman/ListAdmin.py @@ -68,7 +68,12 @@ def D_(s): DASH = '-' NL = '\n' - +class PermissionError(Exception): + """Exception raised when there are permission issues with database operations.""" + def __init__(self, message): + self.message = message + super().__init__(message) + class ListAdmin(object): def InitVars(self): # non-configurable data @@ -233,61 +238,52 @@ def log_file_info(path): if mode & 0o002: # World writable mailman_log('error', 'File %s is world writable (mode %o)', path, mode) - if mode & 0o020: # Group writable but not owned by mailman group + if mode & 0o020 and (expected_gid is None or gid != expected_gid): # Group writable but not owned by mailman group mailman_log('error', 'File %s is group writable but not owned by mailman group', path) except OSError as e: mailman_log('error', 'Could not stat %s: %s', path, str(e)) - # First create a backup of the current file if it exists + # First check if we can access the directory + try: + dirname = os.path.dirname(filename) + if os.path.exists(dirname): + log_file_info(dirname) + except OSError as e: + log_file_info(dirname) + raise PermissionError(f'Cannot access directory {dirname}: {str(e)}') + + # Check existing files if os.path.exists(filename): - try: + log_file_info(filename) + if os.path.exists(filename_backup): + log_file_info(filename_backup) + + # Try to create backup + try: + if os.path.exists(filename): import shutil shutil.copy2(filename, filename_backup) - except IOError as e: - mailman_log('error', 'Error creating backup: %s', str(e)) - log_file_info(filename) + except (IOError, OSError) as e: + log_file_info(filename) + if os.path.exists(filename_backup): log_file_info(filename_backup) - # Also check parent directory permissions - try: - parent_dir = os.path.dirname(filename) - log_file_info(parent_dir) - except OSError as e: - mailman_log('error', 'Could not stat parent directory %s: %s', parent_dir, str(e)) + mailman_log('error', 'Error creating backup: %s', str(e)) + raise PermissionError(f'Error creating backup: {str(e)}') - # Save to temporary file first + # Try to save the new file try: - # Ensure directory exists - dirname = os.path.dirname(filename) - if not os.path.exists(dirname): - os.makedirs(dirname, 0o755) - with open(filename_tmp, 'wb') as fp: - # Use protocol 2 for better compatibility - pickle.dump(self.__db, fp, protocol=2, fix_imports=True) + pickle.dump(self.__db, fp, protocol=0) fp.flush() - if hasattr(os, 'fsync'): - os.fsync(fp.fileno()) - - # Atomic rename + os.fsync(fp.fileno()) os.rename(filename_tmp, filename) - except (IOError, OSError) as e: - mailman_log('error', 'Error saving pending.pck: %s', str(e)) - log_file_info(filename) log_file_info(filename_tmp) - # Also check parent directory permissions - try: - parent_dir = os.path.dirname(filename) - log_file_info(parent_dir) - except OSError as e: - mailman_log('error', 'Could not stat parent directory %s: %s', parent_dir, str(e)) - # Try to clean up - try: - os.unlink(filename_tmp) - except OSError: - pass - raise + if os.path.exists(filename): + log_file_info(filename) + mailman_log('error', 'Error saving database: %s', str(e)) + raise PermissionError(f'Error saving database: {str(e)}') self.__db = None From badcfac6629ea563266c3f527583b225de21011f Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 28 Apr 2025 22:48:12 -0400 Subject: [PATCH 265/748] update code --- Mailman/Cgi/admindb.py | 4 ++-- Mailman/ListAdmin.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Mailman/Cgi/admindb.py b/Mailman/Cgi/admindb.py index 5102f43a..e81bd687 100644 --- a/Mailman/Cgi/admindb.py +++ b/Mailman/Cgi/admindb.py @@ -39,7 +39,7 @@ from Mailman import Message from Mailman import i18n from Mailman.Handlers.Moderate import ModeratedMemberPost -from Mailman.ListAdmin import HELDMSG, ListAdmin +from Mailman.ListAdmin import HELDMSG, ListAdmin, PermissionError from Mailman.ListAdmin import readMessage from Mailman.Cgi import Auth from Mailman.htmlformat import * @@ -192,7 +192,7 @@ def sigterm_handler(signum, frame, mlist=mlist): try: process_form(mlist, doc, cgidata) mlist.Save() - except ListAdmin.PermissionError as e: + except PermissionError as e: # Handle permission errors gracefully print('Status: 500 Internal Server Error') print('Content-type: text/html; charset=utf-8\n') diff --git a/Mailman/ListAdmin.py b/Mailman/ListAdmin.py index 6a136861..910825a0 100644 --- a/Mailman/ListAdmin.py +++ b/Mailman/ListAdmin.py @@ -54,7 +54,7 @@ def D_(s): return s -# Request types requiring admin approval +# Constants for request types IGN = 0 HELDMSG = 1 SUBSCRIPTION = 2 From 34b72753be4268e06bbb5b9d58d1c20633143758 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 28 Apr 2025 22:50:46 -0400 Subject: [PATCH 266/748] update code --- Mailman/ListAdmin.py | 48 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/Mailman/ListAdmin.py b/Mailman/ListAdmin.py index 910825a0..9bc45761 100644 --- a/Mailman/ListAdmin.py +++ b/Mailman/ListAdmin.py @@ -93,6 +93,26 @@ def __opendb(self): def log_file_info(path): try: + # Log process identity information + euid = os.geteuid() + egid = os.getegid() + ruid = os.getuid() + rgid = os.getgid() + groups = os.getgroups() + + # Get group names for supplementary groups + group_names = [] + for gid in groups: + try: + group_names.append(grp.getgrgid(gid)[0]) + except KeyError: + group_names.append(f'gid {gid}') + + mailman_log('error', + 'Process identity - EUID: %d, EGID: %d, RUID: %d, RGID: %d, Groups: %s', + euid, egid, ruid, rgid, ', '.join(group_names)) + + # Get file information stat = os.stat(path) mode = stat.st_mode uid = stat.st_uid @@ -138,7 +158,7 @@ def log_file_info(path): if mode & 0o002: # World writable mailman_log('error', 'File %s is world writable (mode %o)', path, mode) - if mode & 0o020: # Group writable but not owned by mailman group + if mode & 0o020 and (expected_gid is None or gid != expected_gid): # Group writable but not owned by mailman group mailman_log('error', 'File %s is group writable but not owned by mailman group', path) except OSError as e: @@ -193,6 +213,26 @@ def __closedb(self): def log_file_info(path): try: + # Log process identity information + euid = os.geteuid() + egid = os.getegid() + ruid = os.getuid() + rgid = os.getgid() + groups = os.getgroups() + + # Get group names for supplementary groups + group_names = [] + for gid in groups: + try: + group_names.append(grp.getgrgid(gid)[0]) + except KeyError: + group_names.append(f'gid {gid}') + + mailman_log('error', + 'Process identity - EUID: %d, EGID: %d, RUID: %d, RGID: %d, Groups: %s', + euid, egid, ruid, rgid, ', '.join(group_names)) + + # Get file information stat = os.stat(path) mode = stat.st_mode uid = stat.st_uid @@ -259,17 +299,17 @@ def log_file_info(path): if os.path.exists(filename_backup): log_file_info(filename_backup) - # Try to create backup + # Try to create backup, but don't fail if we can't try: if os.path.exists(filename): import shutil shutil.copy2(filename, filename_backup) except (IOError, OSError) as e: + mailman_log('error', 'Could not create backup file %s: %s', filename_backup, str(e)) log_file_info(filename) if os.path.exists(filename_backup): log_file_info(filename_backup) - mailman_log('error', 'Error creating backup: %s', str(e)) - raise PermissionError(f'Error creating backup: {str(e)}') + # Continue with save operation even if backup fails # Try to save the new file try: From e6ee1376b1fd164704dab8dcd156d9a6a0ba193e Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 28 Apr 2025 22:53:02 -0400 Subject: [PATCH 267/748] update code --- Mailman/ListAdmin.py | 44 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/Mailman/ListAdmin.py b/Mailman/ListAdmin.py index 9bc45761..fa487d5b 100644 --- a/Mailman/ListAdmin.py +++ b/Mailman/ListAdmin.py @@ -100,6 +100,24 @@ def log_file_info(path): rgid = os.getgid() groups = os.getgroups() + # Get user and group names + try: + euser = pwd.getpwuid(euid)[0] + except KeyError: + euser = f'uid {euid}' + try: + egroup = grp.getgrgid(egid)[0] + except KeyError: + egroup = f'gid {egid}' + try: + ruser = pwd.getpwuid(ruid)[0] + except KeyError: + ruser = f'uid {ruid}' + try: + rgroup = grp.getgrgid(rgid)[0] + except KeyError: + rgroup = f'gid {rgid}' + # Get group names for supplementary groups group_names = [] for gid in groups: @@ -109,8 +127,8 @@ def log_file_info(path): group_names.append(f'gid {gid}') mailman_log('error', - 'Process identity - EUID: %d, EGID: %d, RUID: %d, RGID: %d, Groups: %s', - euid, egid, ruid, rgid, ', '.join(group_names)) + 'Process identity - EUID: %d (%s), EGID: %d (%s), RUID: %d (%s), RGID: %d (%s), Groups: %s', + euid, euser, egid, egroup, ruid, ruser, rgid, rgroup, ', '.join(group_names)) # Get file information stat = os.stat(path) @@ -220,6 +238,24 @@ def log_file_info(path): rgid = os.getgid() groups = os.getgroups() + # Get user and group names + try: + euser = pwd.getpwuid(euid)[0] + except KeyError: + euser = f'uid {euid}' + try: + egroup = grp.getgrgid(egid)[0] + except KeyError: + egroup = f'gid {egid}' + try: + ruser = pwd.getpwuid(ruid)[0] + except KeyError: + ruser = f'uid {ruid}' + try: + rgroup = grp.getgrgid(rgid)[0] + except KeyError: + rgroup = f'gid {rgid}' + # Get group names for supplementary groups group_names = [] for gid in groups: @@ -229,8 +265,8 @@ def log_file_info(path): group_names.append(f'gid {gid}') mailman_log('error', - 'Process identity - EUID: %d, EGID: %d, RUID: %d, RGID: %d, Groups: %s', - euid, egid, ruid, rgid, ', '.join(group_names)) + 'Process identity - EUID: %d (%s), EGID: %d (%s), RUID: %d (%s), RGID: %d (%s), Groups: %s', + euid, euser, egid, egroup, ruid, ruser, rgid, rgroup, ', '.join(group_names)) # Get file information stat = os.stat(path) From 2ef664dda88144de3c51fdc7daf064f234d18741 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 28 Apr 2025 23:16:05 -0400 Subject: [PATCH 268/748] update --- Mailman/ListAdmin.py | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/Mailman/ListAdmin.py b/Mailman/ListAdmin.py index fa487d5b..31d17d17 100644 --- a/Mailman/ListAdmin.py +++ b/Mailman/ListAdmin.py @@ -88,7 +88,8 @@ def __opendb(self): if self.__db is not None: return - filename = os.path.join(mm_cfg.DATA_DIR, 'pending.pck') + # Use original file location + filename = os.path.join(self.fullpath(), 'request.pck') filename_backup = filename + '.bak' def log_file_info(path): @@ -186,12 +187,13 @@ def log_file_info(path): try: with open(filename, 'rb') as fp: try: + # Try to load with Python 2 compatibility self.__db = pickle.load(fp, fix_imports=True, encoding='latin1') if not isinstance(self.__db, dict): raise ValueError("Database not a dictionary") return except (EOFError, ValueError, TypeError, pickle.UnpicklingError) as e: - mailman_log('error', 'Error loading pending.pck: %s', str(e)) + mailman_log('error', 'Error loading request.pck: %s', str(e)) log_file_info(filename) # If we get here, the main file failed to load properly @@ -199,6 +201,7 @@ def log_file_info(path): mailman_log('info', 'Attempting to load from backup file') with open(filename_backup, 'rb') as fp: try: + # Try to load backup with Python 2 compatibility self.__db = pickle.load(fp, fix_imports=True, encoding='latin1') if not isinstance(self.__db, dict): raise ValueError("Backup database not a dictionary") @@ -207,12 +210,12 @@ def log_file_info(path): shutil.copy2(filename_backup, filename) return except (EOFError, ValueError, TypeError, pickle.UnpicklingError) as e: - mailman_log('error', 'Error loading backup pending.pck: %s', str(e)) + mailman_log('error', 'Error loading backup request.pck: %s', str(e)) log_file_info(filename_backup) except IOError as e: if e.errno != errno.ENOENT: - mailman_log('error', 'IOError loading pending.pck: %s', str(e)) + mailman_log('error', 'IOError loading request.pck: %s', str(e)) log_file_info(filename) if os.path.exists(filename_backup): log_file_info(filename_backup) @@ -225,8 +228,9 @@ def __closedb(self): if self.__db is None: return - filename = os.path.join(mm_cfg.DATA_DIR, 'pending.pck') - filename_tmp = filename + '.tmp.%s.%d' % (socket.gethostname(), os.getpid()) + # Use original file location + filename = os.path.join(self.fullpath(), 'request.pck') + filename_tmp = filename + '.tmp' filename_backup = filename + '.bak' def log_file_info(path): @@ -349,16 +353,23 @@ def log_file_info(path): # Try to save the new file try: - with open(filename_tmp, 'wb') as fp: - pickle.dump(self.__db, fp, protocol=0) - fp.flush() - os.fsync(fp.fileno()) - os.rename(filename_tmp, filename) + # Set umask to ensure proper permissions + omask = os.umask(0o007) + try: + with open(filename_tmp, 'wb') as fp: + # Use protocol 2 for Python 3 compatibility + pickle.dump(self.__db, fp, protocol=2) + fp.flush() + os.fsync(fp.fileno()) + # Ensure proper ownership before rename + os.chown(filename_tmp, 26029, 41) # mailman:mailman + os.rename(filename_tmp, filename) + finally: + os.umask(omask) except (IOError, OSError) as e: log_file_info(filename_tmp) if os.path.exists(filename): log_file_info(filename) - mailman_log('error', 'Error saving database: %s', str(e)) raise PermissionError(f'Error saving database: {str(e)}') self.__db = None From b7edf11fd24aff2c96efed81676a3ccf18fe33f6 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 28 Apr 2025 23:18:33 -0400 Subject: [PATCH 269/748] update --- Mailman/ListAdmin.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/Mailman/ListAdmin.py b/Mailman/ListAdmin.py index 31d17d17..5e8e81f1 100644 --- a/Mailman/ListAdmin.py +++ b/Mailman/ListAdmin.py @@ -356,14 +356,25 @@ def log_file_info(path): # Set umask to ensure proper permissions omask = os.umask(0o007) try: + # Create temporary file in the same directory to inherit setgid with open(filename_tmp, 'wb') as fp: - # Use protocol 2 for Python 3 compatibility pickle.dump(self.__db, fp, protocol=2) fp.flush() os.fsync(fp.fileno()) - # Ensure proper ownership before rename - os.chown(filename_tmp, 26029, 41) # mailman:mailman + + # The file should inherit the directory's group due to setgid + # but let's verify and fix if needed + stat = os.stat(filename_tmp) + if stat.st_gid != 41: # mailman group + os.chown(filename_tmp, -1, 41) # -1 means don't change owner + + # Now do the atomic rename os.rename(filename_tmp, filename) + + # Verify final file ownership + stat = os.stat(filename) + if stat.st_gid != 41: # mailman group + os.chown(filename, -1, 41) # -1 means don't change owner finally: os.umask(omask) except (IOError, OSError) as e: From 00bcc07e46881d48f72975daabadd49caa0225bf Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 28 Apr 2025 23:20:09 -0400 Subject: [PATCH 270/748] update --- Mailman/ListAdmin.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/Mailman/ListAdmin.py b/Mailman/ListAdmin.py index 5e8e81f1..a6fe6ef4 100644 --- a/Mailman/ListAdmin.py +++ b/Mailman/ListAdmin.py @@ -356,25 +356,17 @@ def log_file_info(path): # Set umask to ensure proper permissions omask = os.umask(0o007) try: - # Create temporary file in the same directory to inherit setgid + # Create temporary file - it will inherit directory's group due to setgid with open(filename_tmp, 'wb') as fp: pickle.dump(self.__db, fp, protocol=2) fp.flush() os.fsync(fp.fileno()) - # The file should inherit the directory's group due to setgid - # but let's verify and fix if needed - stat = os.stat(filename_tmp) - if stat.st_gid != 41: # mailman group - os.chown(filename_tmp, -1, 41) # -1 means don't change owner - - # Now do the atomic rename + # Do the atomic rename - the file will keep its group from the directory os.rename(filename_tmp, filename) - # Verify final file ownership - stat = os.stat(filename) - if stat.st_gid != 41: # mailman group - os.chown(filename, -1, 41) # -1 means don't change owner + # Log the result for debugging + log_file_info(filename) finally: os.umask(omask) except (IOError, OSError) as e: From a88f659a2e737bed35e47c083d7af3a3d9cbf8f1 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 28 Apr 2025 23:21:49 -0400 Subject: [PATCH 271/748] update --- Mailman/ListAdmin.py | 218 +++++++++++++++++++++---------------------- 1 file changed, 104 insertions(+), 114 deletions(-) diff --git a/Mailman/ListAdmin.py b/Mailman/ListAdmin.py index a6fe6ef4..56bfab40 100644 --- a/Mailman/ListAdmin.py +++ b/Mailman/ListAdmin.py @@ -233,136 +233,34 @@ def __closedb(self): filename_tmp = filename + '.tmp' filename_backup = filename + '.bak' - def log_file_info(path): - try: - # Log process identity information - euid = os.geteuid() - egid = os.getegid() - ruid = os.getuid() - rgid = os.getgid() - groups = os.getgroups() - - # Get user and group names - try: - euser = pwd.getpwuid(euid)[0] - except KeyError: - euser = f'uid {euid}' - try: - egroup = grp.getgrgid(egid)[0] - except KeyError: - egroup = f'gid {egid}' - try: - ruser = pwd.getpwuid(ruid)[0] - except KeyError: - ruser = f'uid {ruid}' - try: - rgroup = grp.getgrgid(rgid)[0] - except KeyError: - rgroup = f'gid {rgid}' - - # Get group names for supplementary groups - group_names = [] - for gid in groups: - try: - group_names.append(grp.getgrgid(gid)[0]) - except KeyError: - group_names.append(f'gid {gid}') - - mailman_log('error', - 'Process identity - EUID: %d (%s), EGID: %d (%s), RUID: %d (%s), RGID: %d (%s), Groups: %s', - euid, euser, egid, egroup, ruid, ruser, rgid, rgroup, ', '.join(group_names)) - - # Get file information - stat = os.stat(path) - mode = stat.st_mode - uid = stat.st_uid - gid = stat.st_gid - - # Get current ownership info - try: - current_user = pwd.getpwuid(uid)[0] - except KeyError: - current_user = f'uid {uid}' - try: - current_group = grp.getgrgid(gid)[0] - except KeyError: - current_group = f'gid {gid}' - - # Get expected ownership info - try: - expected_user = pwd.getpwnam(mm_cfg.MAILMAN_USER)[0] - expected_uid = pwd.getpwnam(mm_cfg.MAILMAN_USER)[2] - except KeyError: - expected_user = f'user {mm_cfg.MAILMAN_USER}' - expected_uid = None - - try: - expected_group = grp.getgrnam(mm_cfg.MAILMAN_GROUP)[0] - expected_gid = grp.getgrnam(mm_cfg.MAILMAN_GROUP)[2] - except KeyError: - expected_group = f'group {mm_cfg.MAILMAN_GROUP}' - expected_gid = None - - # Log current and expected ownership - mailman_log('error', - 'File %s: mode=%o, owner=%s (current) vs %s (expected), group=%s (current) vs %s (expected)', - path, mode, current_user, expected_user, current_group, expected_group) - - # Log specific permission issues - if expected_uid is not None and uid != expected_uid: - mailman_log('error', 'File %s has incorrect owner (uid %d vs expected %d)', - path, uid, expected_uid) - if expected_gid is not None and gid != expected_gid: - mailman_log('error', 'File %s has incorrect group (gid %d vs expected %d)', - path, gid, expected_gid) - if mode & 0o002: # World writable - mailman_log('error', 'File %s is world writable (mode %o)', - path, mode) - if mode & 0o020 and (expected_gid is None or gid != expected_gid): # Group writable but not owned by mailman group - mailman_log('error', 'File %s is group writable but not owned by mailman group', - path) - except OSError as e: - mailman_log('error', 'Could not stat %s: %s', path, str(e)) - - # First check if we can access the directory + # First check if we can write to the directory + dirname = os.path.dirname(filename) try: - dirname = os.path.dirname(filename) - if os.path.exists(dirname): + # Test if we can write to the directory + test_file = os.path.join(dirname, '.test_write') + try: + with open(test_file, 'w') as f: + f.write('test') + os.unlink(test_file) + except (IOError, OSError) as e: log_file_info(dirname) + raise PermissionError(f'Cannot write to directory {dirname}: {str(e)}') except OSError as e: log_file_info(dirname) raise PermissionError(f'Cannot access directory {dirname}: {str(e)}') - # Check existing files - if os.path.exists(filename): - log_file_info(filename) - if os.path.exists(filename_backup): - log_file_info(filename_backup) - - # Try to create backup, but don't fail if we can't - try: - if os.path.exists(filename): - import shutil - shutil.copy2(filename, filename_backup) - except (IOError, OSError) as e: - mailman_log('error', 'Could not create backup file %s: %s', filename_backup, str(e)) - log_file_info(filename) - if os.path.exists(filename_backup): - log_file_info(filename_backup) - # Continue with save operation even if backup fails - # Try to save the new file try: # Set umask to ensure proper permissions omask = os.umask(0o007) try: - # Create temporary file - it will inherit directory's group due to setgid + # Create temporary file with open(filename_tmp, 'wb') as fp: pickle.dump(self.__db, fp, protocol=2) fp.flush() os.fsync(fp.fileno()) - # Do the atomic rename - the file will keep its group from the directory + # Do the atomic rename os.rename(filename_tmp, filename) # Log the result for debugging @@ -920,6 +818,98 @@ def _UpdateRecords(self): # All done self.__closedb() + def log_file_info(self, path): + """Log detailed information about file permissions and ownership.""" + try: + # Log process identity information + euid = os.geteuid() + egid = os.getegid() + ruid = os.getuid() + rgid = os.getgid() + groups = os.getgroups() + + # Get user and group names + try: + euser = pwd.getpwuid(euid)[0] + except KeyError: + euser = f'uid {euid}' + try: + egroup = grp.getgrgid(egid)[0] + except KeyError: + egroup = f'gid {egid}' + try: + ruser = pwd.getpwuid(ruid)[0] + except KeyError: + ruser = f'uid {ruid}' + try: + rgroup = grp.getgrgid(rgid)[0] + except KeyError: + rgroup = f'gid {rgid}' + + # Get group names for supplementary groups + group_names = [] + for gid in groups: + try: + group_names.append(grp.getgrgid(gid)[0]) + except KeyError: + group_names.append(f'gid {gid}') + + mailman_log('error', + 'Process identity - EUID: %d (%s), EGID: %d (%s), RUID: %d (%s), RGID: %d (%s), Groups: %s', + euid, euser, egid, egroup, ruid, ruser, rgid, rgroup, ', '.join(group_names)) + + # Get file information + stat = os.stat(path) + mode = stat.st_mode + uid = stat.st_uid + gid = stat.st_gid + + # Get current ownership info + try: + current_user = pwd.getpwuid(uid)[0] + except KeyError: + current_user = f'uid {uid}' + try: + current_group = grp.getgrgid(gid)[0] + except KeyError: + current_group = f'gid {gid}' + + # Get expected ownership info + try: + expected_user = pwd.getpwnam(mm_cfg.MAILMAN_USER)[0] + expected_uid = pwd.getpwnam(mm_cfg.MAILMAN_USER)[2] + except KeyError: + expected_user = f'user {mm_cfg.MAILMAN_USER}' + expected_uid = None + + try: + expected_group = grp.getgrnam(mm_cfg.MAILMAN_GROUP)[0] + expected_gid = grp.getgrnam(mm_cfg.MAILMAN_GROUP)[2] + except KeyError: + expected_group = f'group {mm_cfg.MAILMAN_GROUP}' + expected_gid = None + + # Log current and expected ownership + mailman_log('error', + 'File %s: mode=%o, owner=%s (current) vs %s (expected), group=%s (current) vs %s (expected)', + path, mode, current_user, expected_user, current_group, expected_group) + + # Log specific permission issues + if expected_uid is not None and uid != expected_uid: + mailman_log('error', 'File %s has incorrect owner (uid %d vs expected %d)', + path, uid, expected_uid) + if expected_gid is not None and gid != expected_gid: + mailman_log('error', 'File %s has incorrect group (gid %d vs expected %d)', + path, gid, expected_gid) + if mode & 0o002: # World writable + mailman_log('error', 'File %s is world writable (mode %o)', + path, mode) + if mode & 0o020 and (expected_gid is None or gid != expected_gid): # Group writable but not owned by mailman group + mailman_log('error', 'File %s is group writable but not owned by mailman group', + path) + except OSError as e: + mailman_log('error', 'Could not stat %s: %s', path, str(e)) + def readMessage(path): # For backwards compatibility, we must be able to read either a flat text From 16e9ca132c0d817fdf3570da85f0e367fe846744 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Wed, 30 Apr 2025 20:47:01 -0400 Subject: [PATCH 272/748] update --- Mailman/ListAdmin.py | 151 ++++++------------------------------------- 1 file changed, 19 insertions(+), 132 deletions(-) diff --git a/Mailman/ListAdmin.py b/Mailman/ListAdmin.py index 56bfab40..1181631b 100644 --- a/Mailman/ListAdmin.py +++ b/Mailman/ListAdmin.py @@ -84,7 +84,7 @@ def InitTempVars(self): self.__filename = os.path.join(self.fullpath(), 'request.pck') def __opendb(self): - """Open the database file and load data with improved error handling.""" + """Open the database file.""" if self.__db is not None: return @@ -92,136 +92,23 @@ def __opendb(self): filename = os.path.join(self.fullpath(), 'request.pck') filename_backup = filename + '.bak' - def log_file_info(path): - try: - # Log process identity information - euid = os.geteuid() - egid = os.getegid() - ruid = os.getuid() - rgid = os.getgid() - groups = os.getgroups() - - # Get user and group names - try: - euser = pwd.getpwuid(euid)[0] - except KeyError: - euser = f'uid {euid}' - try: - egroup = grp.getgrgid(egid)[0] - except KeyError: - egroup = f'gid {egid}' - try: - ruser = pwd.getpwuid(ruid)[0] - except KeyError: - ruser = f'uid {ruid}' - try: - rgroup = grp.getgrgid(rgid)[0] - except KeyError: - rgroup = f'gid {rgid}' - - # Get group names for supplementary groups - group_names = [] - for gid in groups: - try: - group_names.append(grp.getgrgid(gid)[0]) - except KeyError: - group_names.append(f'gid {gid}') - - mailman_log('error', - 'Process identity - EUID: %d (%s), EGID: %d (%s), RUID: %d (%s), RGID: %d (%s), Groups: %s', - euid, euser, egid, egroup, ruid, ruser, rgid, rgroup, ', '.join(group_names)) - - # Get file information - stat = os.stat(path) - mode = stat.st_mode - uid = stat.st_uid - gid = stat.st_gid - - # Get current ownership info - try: - current_user = pwd.getpwuid(uid)[0] - except KeyError: - current_user = f'uid {uid}' - try: - current_group = grp.getgrgid(gid)[0] - except KeyError: - current_group = f'gid {gid}' - - # Get expected ownership info - try: - expected_user = pwd.getpwnam(mm_cfg.MAILMAN_USER)[0] - expected_uid = pwd.getpwnam(mm_cfg.MAILMAN_USER)[2] - except KeyError: - expected_user = f'user {mm_cfg.MAILMAN_USER}' - expected_uid = None - - try: - expected_group = grp.getgrnam(mm_cfg.MAILMAN_GROUP)[0] - expected_gid = grp.getgrnam(mm_cfg.MAILMAN_GROUP)[2] - except KeyError: - expected_group = f'group {mm_cfg.MAILMAN_GROUP}' - expected_gid = None - - # Log current and expected ownership - mailman_log('error', - 'File %s: mode=%o, owner=%s (current) vs %s (expected), group=%s (current) vs %s (expected)', - path, mode, current_user, expected_user, current_group, expected_group) - - # Log specific permission issues - if expected_uid is not None and uid != expected_uid: - mailman_log('error', 'File %s has incorrect owner (uid %d vs expected %d)', - path, uid, expected_uid) - if expected_gid is not None and gid != expected_gid: - mailman_log('error', 'File %s has incorrect group (gid %d vs expected %d)', - path, gid, expected_gid) - if mode & 0o002: # World writable - mailman_log('error', 'File %s is world writable (mode %o)', - path, mode) - if mode & 0o020 and (expected_gid is None or gid != expected_gid): # Group writable but not owned by mailman group - mailman_log('error', 'File %s is group writable but not owned by mailman group', - path) - except OSError as e: - mailman_log('error', 'Could not stat %s: %s', path, str(e)) - - # Try loading the main file first + # Try to open the main file first try: with open(filename, 'rb') as fp: - try: - # Try to load with Python 2 compatibility - self.__db = pickle.load(fp, fix_imports=True, encoding='latin1') - if not isinstance(self.__db, dict): - raise ValueError("Database not a dictionary") - return - except (EOFError, ValueError, TypeError, pickle.UnpicklingError) as e: - mailman_log('error', 'Error loading request.pck: %s', str(e)) - log_file_info(filename) - - # If we get here, the main file failed to load properly - if os.path.exists(filename_backup): - mailman_log('info', 'Attempting to load from backup file') + self.__db = pickle.load(fp) + except (IOError, OSError, pickle.UnpicklingError): + # If that fails, try the backup + try: with open(filename_backup, 'rb') as fp: - try: - # Try to load backup with Python 2 compatibility - self.__db = pickle.load(fp, fix_imports=True, encoding='latin1') - if not isinstance(self.__db, dict): - raise ValueError("Backup database not a dictionary") - # Successfully loaded backup, restore it as main - import shutil - shutil.copy2(filename_backup, filename) - return - except (EOFError, ValueError, TypeError, pickle.UnpicklingError) as e: - mailman_log('error', 'Error loading backup request.pck: %s', str(e)) - log_file_info(filename_backup) - - except IOError as e: - if e.errno != errno.ENOENT: - mailman_log('error', 'IOError loading request.pck: %s', str(e)) - log_file_info(filename) - if os.path.exists(filename_backup): - log_file_info(filename_backup) + self.__db = pickle.load(fp) + except (IOError, OSError, pickle.UnpicklingError): + # If both fail, start with empty database + self.__db = {} - # If we get here, both main and backup files failed or don't exist - self.__db = {} + # Log file information for debugging + self.log_file_info(filename) + if os.path.exists(filename_backup): + self.log_file_info(filename_backup) def __closedb(self): """Save the database with atomic operations and backup.""" @@ -243,10 +130,10 @@ def __closedb(self): f.write('test') os.unlink(test_file) except (IOError, OSError) as e: - log_file_info(dirname) + self.log_file_info(dirname) raise PermissionError(f'Cannot write to directory {dirname}: {str(e)}') except OSError as e: - log_file_info(dirname) + self.log_file_info(dirname) raise PermissionError(f'Cannot access directory {dirname}: {str(e)}') # Try to save the new file @@ -264,13 +151,13 @@ def __closedb(self): os.rename(filename_tmp, filename) # Log the result for debugging - log_file_info(filename) + self.log_file_info(filename) finally: os.umask(omask) except (IOError, OSError) as e: - log_file_info(filename_tmp) + self.log_file_info(filename_tmp) if os.path.exists(filename): - log_file_info(filename) + self.log_file_info(filename) raise PermissionError(f'Error saving database: {str(e)}') self.__db = None From 9ff674a8db5104cf87a24779c5e57046471010ed Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Wed, 30 Apr 2025 20:58:31 -0400 Subject: [PATCH 273/748] update --- Mailman/ListAdmin.py | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/Mailman/ListAdmin.py b/Mailman/ListAdmin.py index 1181631b..fa7a4486 100644 --- a/Mailman/ListAdmin.py +++ b/Mailman/ListAdmin.py @@ -741,8 +741,14 @@ def log_file_info(self, path): except KeyError: group_names.append(f'gid {gid}') + # Get current time with microseconds + now = time.time() + usecs = int((now - int(now)) * 1000000) + timestamp = time.strftime('%b %d %H:%M:%S', time.localtime(now)) + mailman_log('error', - 'Process identity - EUID: %d (%s), EGID: %d (%s), RUID: %d (%s), RGID: %d (%s), Groups: %s', + '%s.%06d %d Process identity - EUID: %d (%s), EGID: %d (%s), RUID: %d (%s), RGID: %d (%s), Groups: %s', + timestamp, usecs, os.getpid(), euid, euser, egid, egroup, ruid, ruser, rgid, rgroup, ', '.join(group_names)) # Get file information @@ -778,24 +784,31 @@ def log_file_info(self, path): # Log current and expected ownership mailman_log('error', - 'File %s: mode=%o, owner=%s (current) vs %s (expected), group=%s (current) vs %s (expected)', + '%s.%06d %d File %s: mode=%o, owner=%s (current) vs %s (expected), group=%s (current) vs %s (expected)', + timestamp, usecs, os.getpid(), path, mode, current_user, expected_user, current_group, expected_group) # Log specific permission issues if expected_uid is not None and uid != expected_uid: - mailman_log('error', 'File %s has incorrect owner (uid %d vs expected %d)', + mailman_log('error', '%s.%06d %d File %s has incorrect owner (uid %d vs expected %d)', + timestamp, usecs, os.getpid(), path, uid, expected_uid) if expected_gid is not None and gid != expected_gid: - mailman_log('error', 'File %s has incorrect group (gid %d vs expected %d)', + mailman_log('error', '%s.%06d %d File %s has incorrect group (gid %d vs expected %d)', + timestamp, usecs, os.getpid(), path, gid, expected_gid) if mode & 0o002: # World writable - mailman_log('error', 'File %s is world writable (mode %o)', + mailman_log('error', '%s.%06d %d File %s is world writable (mode %o)', + timestamp, usecs, os.getpid(), path, mode) if mode & 0o020 and (expected_gid is None or gid != expected_gid): # Group writable but not owned by mailman group - mailman_log('error', 'File %s is group writable but not owned by mailman group', + mailman_log('error', '%s.%06d %d File %s is group writable but not owned by mailman group', + timestamp, usecs, os.getpid(), path) except OSError as e: - mailman_log('error', 'Could not stat %s: %s', path, str(e)) + mailman_log('error', '%s.%06d %d Could not stat %s: %s', + timestamp, usecs, os.getpid(), + path, str(e)) def readMessage(path): From 34e04fb3b4342057123ad54749a6ee7f3feb6fda Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Wed, 30 Apr 2025 21:05:47 -0400 Subject: [PATCH 274/748] update-logging --- Mailman/ListAdmin.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Mailman/ListAdmin.py b/Mailman/ListAdmin.py index fa7a4486..70f2cfb2 100644 --- a/Mailman/ListAdmin.py +++ b/Mailman/ListAdmin.py @@ -790,21 +790,21 @@ def log_file_info(self, path): # Log specific permission issues if expected_uid is not None and uid != expected_uid: - mailman_log('error', '%s.%06d %d File %s has incorrect owner (uid %d vs expected %d)', + mailman_log('error', '%s.%06d %d File %s has incorrect owner (uid %d (%s) vs expected %d (%s))', timestamp, usecs, os.getpid(), - path, uid, expected_uid) + path, uid, current_user, expected_uid, expected_user) if expected_gid is not None and gid != expected_gid: - mailman_log('error', '%s.%06d %d File %s has incorrect group (gid %d vs expected %d)', + mailman_log('error', '%s.%06d %d File %s has incorrect group (gid %d (%s) vs expected %d (%s))', timestamp, usecs, os.getpid(), - path, gid, expected_gid) + path, gid, current_group, expected_gid, expected_group) if mode & 0o002: # World writable mailman_log('error', '%s.%06d %d File %s is world writable (mode %o)', timestamp, usecs, os.getpid(), path, mode) if mode & 0o020 and (expected_gid is None or gid != expected_gid): # Group writable but not owned by mailman group - mailman_log('error', '%s.%06d %d File %s is group writable but not owned by mailman group', + mailman_log('error', '%s.%06d %d File %s is group writable but not owned by mailman group (current group: %s)', timestamp, usecs, os.getpid(), - path) + path, current_group) except OSError as e: mailman_log('error', '%s.%06d %d Could not stat %s: %s', timestamp, usecs, os.getpid(), From c04abdae2af521bb5d1451d5544d621b139fa8f4 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Wed, 30 Apr 2025 21:07:50 -0400 Subject: [PATCH 275/748] update-logging --- Mailman/ListAdmin.py | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/Mailman/ListAdmin.py b/Mailman/ListAdmin.py index 70f2cfb2..be538435 100644 --- a/Mailman/ListAdmin.py +++ b/Mailman/ListAdmin.py @@ -788,23 +788,36 @@ def log_file_info(self, path): timestamp, usecs, os.getpid(), path, mode, current_user, expected_user, current_group, expected_group) + # Check if we can actually access the file + can_access = False + access_error = None + try: + # Try to open the file for reading + with open(path, 'r') as f: + can_access = True + except (IOError, OSError) as e: + access_error = str(e) + # Log specific permission issues if expected_uid is not None and uid != expected_uid: - mailman_log('error', '%s.%06d %d File %s has incorrect owner (uid %d (%s) vs expected %d (%s))', + mailman_log('error', '%s.%06d %d File %s has incorrect owner (uid %d (%s) vs expected %d (%s))%s', timestamp, usecs, os.getpid(), - path, uid, current_user, expected_uid, expected_user) + path, uid, current_user, expected_uid, expected_user, + ' - but access is possible' if can_access else f' - access error: {access_error}') if expected_gid is not None and gid != expected_gid: - mailman_log('error', '%s.%06d %d File %s has incorrect group (gid %d (%s) vs expected %d (%s))', + mailman_log('error', '%s.%06d %d File %s has incorrect group (gid %d (%s) vs expected %d (%s))%s', timestamp, usecs, os.getpid(), - path, gid, current_group, expected_gid, expected_group) + path, gid, current_group, expected_gid, expected_group, + ' - but access is possible' if can_access else f' - access error: {access_error}') if mode & 0o002: # World writable mailman_log('error', '%s.%06d %d File %s is world writable (mode %o)', timestamp, usecs, os.getpid(), path, mode) if mode & 0o020 and (expected_gid is None or gid != expected_gid): # Group writable but not owned by mailman group - mailman_log('error', '%s.%06d %d File %s is group writable but not owned by mailman group (current group: %s)', + mailman_log('error', '%s.%06d %d File %s is group writable but not owned by mailman group (current group: %s)%s', timestamp, usecs, os.getpid(), - path, current_group) + path, current_group, + ' - but access is possible' if can_access else f' - access error: {access_error}') except OSError as e: mailman_log('error', '%s.%06d %d Could not stat %s: %s', timestamp, usecs, os.getpid(), From 6be3fc5b663b6b988fb57b72df4d86b54af4b01e Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Wed, 30 Apr 2025 21:14:46 -0400 Subject: [PATCH 276/748] update-logging --- Mailman/ListAdmin.py | 4 ++-- Mailman/Pending.py | 10 +++++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Mailman/ListAdmin.py b/Mailman/ListAdmin.py index be538435..c816203b 100644 --- a/Mailman/ListAdmin.py +++ b/Mailman/ListAdmin.py @@ -792,8 +792,8 @@ def log_file_info(self, path): can_access = False access_error = None try: - # Try to open the file for reading - with open(path, 'r') as f: + # Try to open the file for reading in binary mode + with open(path, 'rb') as f: can_access = True except (IOError, OSError) as e: access_error = str(e) diff --git a/Mailman/Pending.py b/Mailman/Pending.py index c8ca883a..a3d909d1 100644 --- a/Mailman/Pending.py +++ b/Mailman/Pending.py @@ -25,6 +25,7 @@ import random import pickle import socket +import traceback from Mailman import mm_cfg from Mailman import UserDesc @@ -104,7 +105,8 @@ def __load(self): return {} return pickle.loads(data, fix_imports=True, encoding='latin1') except (EOFError, ValueError, TypeError, pickle.UnpicklingError) as e: - syslog('error', 'Error loading pending.pck: %s', str(e)) + syslog('error', 'Error loading pending.pck: %s\nTraceback:\n%s', + str(e), traceback.format_exc()) # If we get here, the main file failed to load properly if os.path.exists(filename_backup): @@ -120,11 +122,13 @@ def __load(self): shutil.copy2(filename_backup, filename) return db except (EOFError, ValueError, TypeError, pickle.UnpicklingError) as e: - syslog('error', 'Error loading backup pending.pck: %s', str(e)) + syslog('error', 'Error loading backup pending.pck: %s\nTraceback:\n%s', + str(e), traceback.format_exc()) except IOError as e: if e.errno != errno.ENOENT: - syslog('error', 'IOError loading pending.pck: %s', str(e)) + syslog('error', 'IOError loading pending.pck: %s\nTraceback:\n%s', + str(e), traceback.format_exc()) # If we get here, both main and backup files failed or don't exist return {} From 63ddaa9bf3a83f3c6b6e42b1a11dcfdeb279f02f Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Wed, 30 Apr 2025 21:17:25 -0400 Subject: [PATCH 277/748] update-strings --- Mailman/Cgi/admin.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Mailman/Cgi/admin.py b/Mailman/Cgi/admin.py index d54feb46..614eb04e 100644 --- a/Mailman/Cgi/admin.py +++ b/Mailman/Cgi/admin.py @@ -1055,6 +1055,8 @@ def membership_options(mlist, subcat, cgidata, doc, form): for addr in all: try: name = mlist.getMemberName(addr) or '' + if isinstance(name, bytes): + name = name.decode('latin-1', 'replace') names.append(name) valid_members.append(addr) except Errors.NotAMemberError: @@ -1175,6 +1177,8 @@ def membership_options(mlist, subcat, cgidata, doc, form): for addr in members: try: name = mlist.getMemberName(addr) or '' + if isinstance(name, bytes): + name = name.decode('latin-1', 'replace') if name: names.append(name) except Errors.NotAMemberError: @@ -1197,8 +1201,10 @@ def membership_options(mlist, subcat, cgidata, doc, form): qaddr = urllib.parse.quote(addr) link = Link(mlist.GetOptionsURL(addr, obscure=1), mlist.getMemberCPAddress(addr)) - fullname = Utils.uncanonstr(mlist.getMemberName(addr), - mlist.preferred_language) + fullname = mlist.getMemberName(addr) + if isinstance(fullname, bytes): + fullname = fullname.decode('latin-1', 'replace') + fullname = Utils.uncanonstr(fullname, mlist.preferred_language) name = TextBox('%(qaddr)s_realname' % {'qaddr': qaddr}, fullname, size=longest).Format() cells = [Center(CheckBox('%(qaddr)s_unsub' % {'qaddr': qaddr}, 'off', 0).Format() + ''), From fecf98e13bd9e32273fa729e1a0bba91b456deb3 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Wed, 30 Apr 2025 21:22:01 -0400 Subject: [PATCH 278/748] update-strings --- Mailman/Cgi/admin.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Mailman/Cgi/admin.py b/Mailman/Cgi/admin.py index 614eb04e..cef775a9 100644 --- a/Mailman/Cgi/admin.py +++ b/Mailman/Cgi/admin.py @@ -86,13 +86,19 @@ def main(): if os.environ.get('REQUEST_METHOD') == 'POST': content_length = int(os.environ.get('CONTENT_LENGTH', 0)) if content_length > 0: - form_data = sys.stdin.read(content_length) + form_data = sys.stdin.buffer.read(content_length).decode('latin-1') cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=True) + # Ensure all form values are properly decoded + for key in cgidata: + cgidata[key] = [v.decode('latin-1') if isinstance(v, bytes) else v for v in cgidata[key]] else: cgidata = {} else: query_string = os.environ.get('QUERY_STRING', '') cgidata = urllib.parse.parse_qs(query_string, keep_blank_values=True) + # Ensure all form values are properly decoded + for key in cgidata: + cgidata[key] = [v.decode('latin-1') if isinstance(v, bytes) else v for v in cgidata[key]] except Exception: # Someone crafted a POST with a bad Content-Type:. doc = Document() From 9e661b4e1a60e5c4d82d49015684b283e12ebe89 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Wed, 30 Apr 2025 21:27:53 -0400 Subject: [PATCH 279/748] update-strings --- Mailman/htmlformat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mailman/htmlformat.py b/Mailman/htmlformat.py index c0297890..f0689875 100644 --- a/Mailman/htmlformat.py +++ b/Mailman/htmlformat.py @@ -309,7 +309,7 @@ def SetTitle(self, title): self.title = title def Format(self, indent=0, **kws): - charset = 'us-ascii' + charset = 'latin-1' if self.language and Utils.IsLanguage(self.language): charset = Utils.GetCharSet(self.language) output = ['Content-Type: text/html; charset=%s\n' % charset] From ea0e3c2534bc976e3c179c3f35b693946334faa2 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Wed, 30 Apr 2025 21:53:10 -0400 Subject: [PATCH 280/748] update-strings --- Mailman/CSRFcheck.py | 52 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/Mailman/CSRFcheck.py b/Mailman/CSRFcheck.py index c0e35ba4..2602898c 100644 --- a/Mailman/CSRFcheck.py +++ b/Mailman/CSRFcheck.py @@ -37,36 +37,61 @@ def csrf_token(mlist, contexts, user=None): """ create token by mailman cookie generation algorithm """ - if user: # Unmunge a munged email address. user = UnobscureEmail(urllib.parse.unquote(user)) + syslog('debug', 'CSRF token generation: mlist=%s, contexts=%s, user=%s', + mlist.internal_name(), contexts, user) + else: + syslog('debug', 'CSRF token generation: mlist=%s, contexts=%s', + mlist.internal_name(), contexts) + selected_context = None for context in contexts: key, secret = mlist.AuthContextInfo(context, user) if key and secret: + selected_context = context + syslog('debug', 'CSRF token generation: Selected context=%s, key=%s', + context, key) break else: + syslog('debug', 'CSRF token generation failed: No valid context found in %s', + contexts) return None # not authenticated + issued = int(time.time()) needs_hash = (secret + repr(issued)).encode('utf-8') mac = sha_new(needs_hash).hexdigest() keymac = '%s:%s' % (key, mac) token = binascii.hexlify(marshal.dumps((issued, keymac))).decode('utf-8') + + syslog('debug', 'CSRF token generated: context=%s, key=%s, issued=%s, mac=%s, token=%s', + selected_context, key, time.ctime(issued), mac, token) return token def csrf_check(mlist, token, cgi_user=None): """ check token by mailman cookie validation algorithm """ try: + syslog('debug', 'CSRF token validation: mlist=%s, cgi_user=%s, token=%s', + mlist.internal_name(), cgi_user, token) + issued, keymac = marshal.loads(binascii.unhexlify(token)) key, received_mac = keymac.split(':', 1) + + syslog('debug', 'CSRF token details: issued=%s, key=%s, received_mac=%s', + time.ctime(issued), key, received_mac) + if not key.startswith(mlist.internal_name() + '+'): + syslog('debug', 'CSRF token validation failed: Invalid mailing list name in key. Expected %s, got %s', + mlist.internal_name(), key) return False + key = key[len(mlist.internal_name()) + 1:] if '+' in key: key, user = key.split('+', 1) else: user = None + # Don't allow unprivileged tokens for admin or admindb. if cgi_user == 'admin': if key not in ('admin', 'site'): @@ -80,6 +105,7 @@ def csrf_check(mlist, token, cgi_user=None): 'admindb form submitted with CSRF token issued for %s.', key + '+' + user if user else key) return False + if user: # This is for CVE-2021-42097. The token is a user token because # of the fix for CVE-2021-42096 but it must match the user for @@ -91,13 +117,33 @@ def csrf_check(mlist, token, cgi_user=None): 'issued for %s.', cgi_user, raw_user) return False + context = keydict.get(key) key, secret = mlist.AuthContextInfo(context, user) assert key + mac = sha_new(secret + repr(issued)).hexdigest() + age = time.time() - issued + + syslog('debug', 'CSRF token validation: context=%s, generated_mac=%s, age=%s seconds', + context, mac, age) + if (mac == received_mac - and 0 < time.time() - issued < mm_cfg.FORM_LIFETIME): + and 0 < age < mm_cfg.FORM_LIFETIME): + syslog('debug', 'CSRF token validation successful') return True + + if mac != received_mac: + syslog('debug', 'CSRF token validation failed: MAC mismatch. Expected %s, got %s. Full token details: expected=(%s, %s:%s), received=(%s, %s:%s)', + mac, received_mac, time.ctime(issued), key, mac, time.ctime(issued), key, received_mac) + elif age <= 0: + syslog('debug', 'CSRF token validation failed: Token issued in the future. Token details: issued=%s, key=%s, mac=%s', + time.ctime(issued), key, received_mac) + else: + syslog('debug', 'CSRF token validation failed: Token expired. Age: %s seconds, FORM_LIFETIME=%s seconds, contexts=%s. Token details: issued=%s, key=%s, mac=%s', + age, mm_cfg.FORM_LIFETIME, keydict.keys(), time.ctime(issued), key, received_mac) + return False - except (AssertionError, ValueError, TypeError): + except (AssertionError, ValueError, TypeError) as e: + syslog('error', 'CSRF token validation failed with error: %s', str(e)) return False From e24b38e7ba7788633fdb23ab041b712cdfcffb5e Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Wed, 30 Apr 2025 21:55:34 -0400 Subject: [PATCH 281/748] update-strings --- Mailman/CSRFcheck.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/Mailman/CSRFcheck.py b/Mailman/CSRFcheck.py index 2602898c..fb2b46c8 100644 --- a/Mailman/CSRFcheck.py +++ b/Mailman/CSRFcheck.py @@ -21,6 +21,7 @@ import urllib.parse import marshal import binascii +import traceback from Mailman import mm_cfg from Mailman.Logging.Syslog import syslog @@ -122,7 +123,23 @@ def csrf_check(mlist, token, cgi_user=None): key, secret = mlist.AuthContextInfo(context, user) assert key - mac = sha_new(secret + repr(issued)).hexdigest() + try: + # Ensure all values are properly encoded before hashing + if isinstance(secret, str): + secret = secret.encode('utf-8') + elif not isinstance(secret, bytes): + secret = str(secret).encode('utf-8') + + issued_str = str(issued) + if isinstance(issued_str, str): + issued_str = issued_str.encode('utf-8') + + mac = sha_new(secret + issued_str).hexdigest() + except (TypeError, UnicodeError) as e: + syslog('error', 'CSRF token validation failed with encoding error: %s. Secret type: %s, issued type: %s, secret value: %r, issued value: %r', + str(e), type(secret), type(issued), secret, issued) + return False + age = time.time() - issued syslog('debug', 'CSRF token validation: context=%s, generated_mac=%s, age=%s seconds', @@ -145,5 +162,6 @@ def csrf_check(mlist, token, cgi_user=None): return False except (AssertionError, ValueError, TypeError) as e: - syslog('error', 'CSRF token validation failed with error: %s', str(e)) + syslog('error', 'CSRF token validation failed with error: %s\nTraceback:\n%s', + str(e), ''.join(traceback.format_exc())) return False From d31df6786dfa60e74fa947f2c3fe736fcbbfd0b7 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Wed, 30 Apr 2025 22:01:38 -0400 Subject: [PATCH 282/748] update-strings --- Mailman/i18n.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Mailman/i18n.py b/Mailman/i18n.py index 7892bc75..9fd91464 100644 --- a/Mailman/i18n.py +++ b/Mailman/i18n.py @@ -98,7 +98,10 @@ def _(s, frame=1): tns = _translation.gettext(s) charset = _translation.charset() if not charset: - charset = 'us-ascii' + charset = 'latin-1' + # Ensure we return a string, not bytes + if isinstance(tns, bytes): + tns = tns.decode(charset, 'replace') for k, v in list(dict.items()): if isinstance(v, str): dict[k] = v.encode(charset, 'replace') From de5693e7c917e4ef2413283bbc1d346d22f28eb7 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Wed, 30 Apr 2025 22:04:03 -0400 Subject: [PATCH 283/748] update-strings --- Mailman/i18n.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Mailman/i18n.py b/Mailman/i18n.py index 9fd91464..1c75ac8c 100644 --- a/Mailman/i18n.py +++ b/Mailman/i18n.py @@ -102,9 +102,12 @@ def _(s, frame=1): # Ensure we return a string, not bytes if isinstance(tns, bytes): tns = tns.decode(charset, 'replace') + # Ensure all dictionary values are strings, not bytes for k, v in list(dict.items()): - if isinstance(v, str): - dict[k] = v.encode(charset, 'replace') + if isinstance(v, bytes): + dict[k] = v.decode(charset, 'replace') + elif not isinstance(v, str): + dict[k] = str(v) try: return tns % dict except (ValueError, TypeError): From f86ac5b0f2a19b12fadf2138acdac7824c03260c Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Wed, 30 Apr 2025 22:06:48 -0400 Subject: [PATCH 284/748] update-strings --- Mailman/OldStyleMemberships.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Mailman/OldStyleMemberships.py b/Mailman/OldStyleMemberships.py index 03c09a77..60d25756 100644 --- a/Mailman/OldStyleMemberships.py +++ b/Mailman/OldStyleMemberships.py @@ -169,7 +169,15 @@ def getMemberOption(self, member, flag): def getMemberName(self, member): self.__assertIsMember(member) - return self.__mlist.usernames.get(member.lower()) + name = self.__mlist.usernames.get(member.lower()) + if isinstance(name, bytes): + try: + # Try Latin-1 first since that's what we're seeing in the data + name = name.decode('latin-1', 'replace') + except UnicodeDecodeError: + # Fall back to UTF-8 if Latin-1 fails + name = name.decode('utf-8', 'replace') + return name def getMemberTopics(self, member): self.__assertIsMember(member) From 0258ef514d4f3db5072c6eebea8e0ef188c58633 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Wed, 30 Apr 2025 22:11:14 -0400 Subject: [PATCH 285/748] update-strings --- Mailman/OldStyleMemberships.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/Mailman/OldStyleMemberships.py b/Mailman/OldStyleMemberships.py index 60d25756..0ec2807f 100644 --- a/Mailman/OldStyleMemberships.py +++ b/Mailman/OldStyleMemberships.py @@ -170,6 +170,8 @@ def getMemberOption(self, member, flag): def getMemberName(self, member): self.__assertIsMember(member) name = self.__mlist.usernames.get(member.lower()) + if name is None: + return '' if isinstance(name, bytes): try: # Try Latin-1 first since that's what we're seeing in the data @@ -177,7 +179,7 @@ def getMemberName(self, member): except UnicodeDecodeError: # Fall back to UTF-8 if Latin-1 fails name = name.decode('utf-8', 'replace') - return name + return str(name) def getMemberTopics(self, member): self.__assertIsMember(member) @@ -379,7 +381,16 @@ def setMemberOption(self, member, flag, value): def setMemberName(self, member, realname): assert self.__mlist.Locked() self.__assertIsMember(member) - self.__mlist.usernames[member.lower()] = realname + if realname is None: + realname = '' + if isinstance(realname, bytes): + try: + # Try Latin-1 first since that's what we're seeing in the data + realname = realname.decode('latin-1', 'replace') + except UnicodeDecodeError: + # Fall back to UTF-8 if Latin-1 fails + realname = realname.decode('utf-8', 'replace') + self.__mlist.usernames[member.lower()] = str(realname) def setMemberTopics(self, member, topics): assert self.__mlist.Locked() From bb1c50213391ef341b92ab2e80bd844c933e1b79 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Wed, 30 Apr 2025 22:13:03 -0400 Subject: [PATCH 286/748] update-strings --- Mailman/Cgi/admin.py | 322 +++++++++++++++++-------------------------- 1 file changed, 126 insertions(+), 196 deletions(-) diff --git a/Mailman/Cgi/admin.py b/Mailman/Cgi/admin.py index cef775a9..021dfe75 100644 --- a/Mailman/Cgi/admin.py +++ b/Mailman/Cgi/admin.py @@ -55,215 +55,145 @@ def D_(s): AUTH_CONTEXTS = (mm_cfg.AuthListAdmin, mm_cfg.AuthSiteAdmin) def main(): - # Try to find out which list is being administered - parts = Utils.GetPathPieces() - if not parts: - # None, so just do the admin overview and be done with it - admin_overview() - return - # Get the list object - listname = parts[0].lower() - if isinstance(listname, bytes): - listname = listname.decode('utf-8', 'replace') - try: - mlist = MailList.MailList(listname, lock=0) - except Errors.MMListError as e: - # Avoid cross-site scripting attacks - safelistname = Utils.websafe(listname) - # Send this with a 404 status. - print('Status: 404 Not Found') - admin_overview(_('No such list %(safelistname)s') % { - 'safelistname': safelistname - }) - syslog('error', 'admin: No such list "%s": %s\n', - listname, e) - return - # Now that we know what list has been requested, all subsequent admin - # pages are shown in that list's preferred language. - i18n.set_language(mlist.preferred_language) - # If the user is not authenticated, we're done. try: - if os.environ.get('REQUEST_METHOD') == 'POST': - content_length = int(os.environ.get('CONTENT_LENGTH', 0)) - if content_length > 0: - form_data = sys.stdin.buffer.read(content_length).decode('latin-1') - cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=True) + # Try to find out which list is being administered + parts = Utils.GetPathPieces() + if not parts: + # None, so just do the admin overview and be done with it + admin_overview() + return + # Get the list object + listname = parts[0].lower() + if isinstance(listname, bytes): + listname = listname.decode('utf-8', 'replace') + try: + mlist = MailList.MailList(listname, lock=0) + except Errors.MMListError as e: + # Avoid cross-site scripting attacks + safelistname = Utils.websafe(listname) + # Send this with a 404 status. + print('Status: 404 Not Found') + admin_overview(_('No such list %(safelistname)s') % { + 'safelistname': safelistname + }) + syslog('error', 'admin: No such list "%s": %s\n', + listname, e) + return + # Now that we know what list has been requested, all subsequent admin + # pages are shown in that list's preferred language. + i18n.set_language(mlist.preferred_language) + # If the user is not authenticated, we're done. + try: + if os.environ.get('REQUEST_METHOD') == 'POST': + content_length = int(os.environ.get('CONTENT_LENGTH', 0)) + if content_length > 0: + form_data = sys.stdin.buffer.read(content_length).decode('latin-1') + cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=True) + # Ensure all form values are properly decoded + for key in cgidata: + cgidata[key] = [v.decode('latin-1') if isinstance(v, bytes) else v for v in cgidata[key]] + else: + cgidata = {} + else: + query_string = os.environ.get('QUERY_STRING', '') + cgidata = urllib.parse.parse_qs(query_string, keep_blank_values=True) # Ensure all form values are properly decoded for key in cgidata: cgidata[key] = [v.decode('latin-1') if isinstance(v, bytes) else v for v in cgidata[key]] - else: - cgidata = {} - else: - query_string = os.environ.get('QUERY_STRING', '') - cgidata = urllib.parse.parse_qs(query_string, keep_blank_values=True) - # Ensure all form values are properly decoded - for key in cgidata: - cgidata[key] = [v.decode('latin-1') if isinstance(v, bytes) else v for v in cgidata[key]] - except Exception: - # Someone crafted a POST with a bad Content-Type:. - doc = Document() - doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) - doc.AddItem(Header(2, _("Error"))) - doc.AddItem(Bold(_('Invalid options to CGI script.'))) - # Send this with a 400 status. - print('Status: 400 Bad Request') - print(doc.Format()) - return - - # CSRF check - safe_params = ['VARHELP', 'adminpw', 'admlogin', - 'letter', 'chunk', 'findmember', - 'legend'] - params = list(cgidata.keys()) - if set(params) - set(safe_params): - csrf_checked = csrf_check(mlist, cgidata.get('csrf_token', [''])[0], - 'admin') - else: - csrf_checked = True - # if password is present, void cookie to force password authentication. - if cgidata.get('adminpw', [''])[0]: - os.environ['HTTP_COOKIE'] = '' - csrf_checked = True + except Exception as e: + # Someone crafted a POST with a bad Content-Type:. + doc = Document() + doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + doc.AddItem(Header(2, _("Error"))) + doc.AddItem(Bold(_('Invalid options to CGI script.'))) + doc.AddItem(Pre(Utils.websafe(str(e)))) + doc.AddItem(Pre(Utils.websafe(traceback.format_exc()))) + # Send this with a 400 status. + print('Status: 400 Bad Request') + print(doc.Format()) + syslog('error', 'admin: Invalid options: %s\n%s', str(e), traceback.format_exc()) + return - if not mlist.WebAuthenticate((mm_cfg.AuthListAdmin, - mm_cfg.AuthSiteAdmin), - cgidata.get('adminpw', [''])[0]): - if 'adminpw' in cgidata: - # This is a re-authorization attempt - msg = Bold(FontSize('+1', _('Authorization failed.'))).Format() - remote = os.environ.get('HTTP_FORWARDED_FOR', - os.environ.get('HTTP_X_FORWARDED_FOR', - os.environ.get('REMOTE_ADDR', - 'unidentified origin'))) - syslog('security', - 'Authorization failed (admin): list=%s: remote=%s', - listname, remote) + # CSRF check + safe_params = ['VARHELP', 'adminpw', 'admlogin', + 'letter', 'chunk', 'findmember', + 'legend'] + params = list(cgidata.keys()) + if set(params) - set(safe_params): + csrf_checked = csrf_check(mlist, cgidata.get('csrf_token', [''])[0], + 'admin') else: - msg = '' - Auth.loginpage(mlist, 'admin', msg=msg) - return - - # Which subcategory was requested? Default is `general' - if len(parts) == 1: - category = 'general' - subcat = None - elif len(parts) == 2: - category = parts[1] - subcat = None - else: - category = parts[1] - subcat = parts[2] - - # Is this a log-out request? - if category == 'logout': - # site-wide admin should also be able to logout. - if mlist.AuthContextInfo(mm_cfg.AuthSiteAdmin)[0] == 'site': - print(mlist.ZapCookie(mm_cfg.AuthSiteAdmin)) - print(mlist.ZapCookie(mm_cfg.AuthListAdmin)) - Auth.loginpage(mlist, 'admin', frontpage=1) - return - - # Sanity check - if category not in list(mlist.GetConfigCategories().keys()): - category = 'general' + csrf_checked = True + # if password is present, void cookie to force password authentication. + if cgidata.get('adminpw', [''])[0]: + os.environ['HTTP_COOKIE'] = '' + csrf_checked = True - # Is the request for variable details? - varhelp = None - qsenviron = os.environ.get('QUERY_STRING') - parsedqs = None - if qsenviron: - parsedqs = urllib.parse.parse_qs(qsenviron) - if 'VARHELP' in cgidata: - varhelp = cgidata.get('VARHELP', [''])[0] - elif parsedqs: - # POST methods, even if their actions have a query string, don't get - # put into FieldStorage's keys :-( - qs = parsedqs.get('VARHELP') - if qs and type(qs) is list: - varhelp = qs[0] - if varhelp: - option_help(mlist, varhelp) - return + if not mlist.WebAuthenticate((mm_cfg.AuthListAdmin, + mm_cfg.AuthSiteAdmin), + cgidata.get('adminpw', [''])[0]): + if 'adminpw' in cgidata: + # This is a re-authorization attempt + msg = Bold(FontSize('+1', _('Authorization failed.'))).Format() + remote = os.environ.get('HTTP_FORWARDED_FOR', + os.environ.get('HTTP_X_FORWARDED_FOR', + os.environ.get('REMOTE_ADDR', + 'unidentified origin'))) + syslog('security', + 'Authorization failed (admin): list=%s: remote=%s', + listname, remote) + else: + msg = '' + Auth.loginpage(mlist, 'admin', msg=msg) + return - # The html page document - doc = Document() - doc.set_language(mlist.preferred_language) + # Which subcategory was requested? Default is `general' + if len(parts) == 1: + category = 'general' + subcat = None + elif len(parts) == 2: + category = parts[1] + subcat = None + else: + category = parts[1] + subcat = parts[2] - # From this point on, the MailList object must be locked. However, we - # must release the lock no matter how we exit. try/finally isn't enough, - # because of this scenario: user hits the admin 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. - # - # BAW: This may not be portable to other web servers or cgi execution - # models. - def sigterm_handler(signum, frame, mlist=mlist): - # Make sure the list gets unlocked... - 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) + # Create the document + doc = Document() + doc.set_language(mlist.preferred_language) - mlist.Lock() - try: - # Install the emergency shutdown signal handler - signal.signal(signal.SIGTERM, sigterm_handler) + # Now dispatch to the appropriate handler + if category == 'general': + show_variables(mlist, category, subcat, cgidata, doc) + elif category == 'members': + membership_options(mlist, subcat, cgidata, doc, form) + elif category == 'passwords': + password_inputs(mlist) + elif category == 'options': + change_options(mlist, category, subcat, cgidata, doc) + elif category == 'help': + option_help(mlist, cgidata.get('VARHELP', [''])[0]) + else: + doc.AddItem(Header(2, _('Error'))) + doc.AddItem(Bold(_('No such category: %(category)s') % { + 'category': category + })) - if list(cgidata.keys()): - if csrf_checked: - # There are options to change - change_options(mlist, category, subcat, cgidata, doc) - else: - doc.addError( - _('The form lifetime has expired. (request forgery check)')) - # Let the list sanity check the changed values - mlist.CheckValues() - # Additional sanity checks - if not mlist.digestable and not mlist.nondigestable: - doc.addError( - _(f'''You have turned off delivery of both digest and - non-digest messages. This is an incompatible state of - affairs. You must turn on either digest delivery or - non-digest delivery or your mailing list will basically be - unusable.'''), tag=_('Warning: ')) + # Format and print the document + print(doc.Format()) - dm = mlist.getDigestMemberKeys() - if not mlist.digestable and dm: - doc.addError( - _(f'''You have digest members, but digests are turned - off. Those people will not receive mail. - Affected member(s) %(dm)r.'''), - tag=_('Warning: ')) - rm = mlist.getRegularMemberKeys() - if not mlist.nondigestable and rm: - doc.addError( - _(f'''You have regular list members but non-digestified mail is - turned off. They will receive non-digestified mail until you - fix this problem. Affected member(s) %(rm)r.'''), - tag=_('Warning: ')) - # Glom up the results page and print it out - show_results(mlist, doc, category, subcat, cgidata) + except Exception as e: + # Catch any unhandled exceptions and display them properly + doc = Document() + doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + doc.AddItem(Header(2, _("Error"))) + doc.AddItem(Bold(_('An unexpected error occurred.'))) + doc.AddItem(Pre(Utils.websafe(str(e)))) + doc.AddItem(Pre(Utils.websafe(traceback.format_exc()))) + # Send this with a 500 status. + print('Status: 500 Internal Server Error') print(doc.Format()) - mlist.Save() - finally: - # Now be sure to unlock the list. It's okay if we get a signal here - # because essentially, the signal handler will do the same thing. And - # unlocking is unconditional, so it's not an error if we unlock while - # we're already unlocked. - mlist.Unlock() + syslog('error', 'admin: Unexpected error: %s\n%s', str(e), traceback.format_exc()) def admin_overview(msg=''): # Show the administrative overview page, with the list of all the lists on From 6abe836946faff4628ca2cfd38294dda0d487a5f Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Wed, 30 Apr 2025 22:15:27 -0400 Subject: [PATCH 287/748] update-strings --- Mailman/Cgi/admin.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Mailman/Cgi/admin.py b/Mailman/Cgi/admin.py index 021dfe75..ba993ae0 100644 --- a/Mailman/Cgi/admin.py +++ b/Mailman/Cgi/admin.py @@ -106,8 +106,8 @@ def main(): doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) doc.AddItem(Header(2, _("Error"))) doc.AddItem(Bold(_('Invalid options to CGI script.'))) - doc.AddItem(Pre(Utils.websafe(str(e)))) - doc.AddItem(Pre(Utils.websafe(traceback.format_exc()))) + doc.AddItem(Preformatted(Utils.websafe(str(e)))) + doc.AddItem(Preformatted(Utils.websafe(traceback.format_exc()))) # Send this with a 400 status. print('Status: 400 Bad Request') print(doc.Format()) @@ -162,6 +162,9 @@ def main(): doc = Document() doc.set_language(mlist.preferred_language) + # Create the form + form = Form(mlist=mlist, contexts=AUTH_CONTEXTS) + # Now dispatch to the appropriate handler if category == 'general': show_variables(mlist, category, subcat, cgidata, doc) @@ -188,8 +191,8 @@ def main(): doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) doc.AddItem(Header(2, _("Error"))) doc.AddItem(Bold(_('An unexpected error occurred.'))) - doc.AddItem(Pre(Utils.websafe(str(e)))) - doc.AddItem(Pre(Utils.websafe(traceback.format_exc()))) + doc.AddItem(Preformatted(Utils.websafe(str(e)))) + doc.AddItem(Preformatted(Utils.websafe(traceback.format_exc()))) # Send this with a 500 status. print('Status: 500 Internal Server Error') print(doc.Format()) From 7120b88c5bbb918be9b0c6ff198498d7f952f5af Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Wed, 30 Apr 2025 22:20:33 -0400 Subject: [PATCH 288/748] update --- Mailman/Cgi/admin.py | 34 +++++++++++++++++----------------- Mailman/Cgi/admindb.py | 13 ++++++++----- Mailman/Cgi/options.py | 30 ++++++++++++++++-------------- 3 files changed, 41 insertions(+), 36 deletions(-) diff --git a/Mailman/Cgi/admin.py b/Mailman/Cgi/admin.py index ba993ae0..160bd4b3 100644 --- a/Mailman/Cgi/admin.py +++ b/Mailman/Cgi/admin.py @@ -39,7 +39,7 @@ def cmp(a, b): from Mailman.UserDesc import UserDesc from Mailman.htmlformat import * from Mailman.Cgi import Auth -from Mailman.Logging.Syslog import syslog +from Mailman.Logging.Syslog import mailman_log from Mailman.Utils import sha_new from Mailman.CSRFcheck import csrf_check @@ -76,8 +76,8 @@ def main(): admin_overview(_('No such list %(safelistname)s') % { 'safelistname': safelistname }) - syslog('error', 'admin: No such list "%s": %s\n', - listname, e) + mailman_log('error', 'admin: No such list "%s": %s\n%s', + listname, e, traceback.format_exc()) return # Now that we know what list has been requested, all subsequent admin # pages are shown in that list's preferred language. @@ -111,7 +111,7 @@ def main(): # Send this with a 400 status. print('Status: 400 Bad Request') print(doc.Format()) - syslog('error', 'admin: Invalid options: %s\n%s', str(e), traceback.format_exc()) + mailman_log('error', 'admin: Invalid options: %s\n%s', str(e), traceback.format_exc()) return # CSRF check @@ -139,9 +139,9 @@ def main(): os.environ.get('HTTP_X_FORWARDED_FOR', os.environ.get('REMOTE_ADDR', 'unidentified origin'))) - syslog('security', - 'Authorization failed (admin): list=%s: remote=%s', - listname, remote) + mailman_log('security', + 'Authorization failed (admin): list=%s: remote=%s\n%s', + listname, remote, traceback.format_exc()) else: msg = '' Auth.loginpage(mlist, 'admin', msg=msg) @@ -196,7 +196,7 @@ def main(): # Send this with a 500 status. print('Status: 500 Internal Server Error') print(doc.Format()) - syslog('error', 'admin: Unexpected error: %s\n%s', str(e), traceback.format_exc()) + mailman_log('error', 'admin: Unexpected error: %s\n%s', str(e), traceback.format_exc()) def admin_overview(msg=''): # Show the administrative overview page, with the list of all the lists on @@ -1485,17 +1485,17 @@ def change_options(mlist, category, subcat, cgidata, doc): config_categories = mlist.GetConfigCategories() # Log the configuration categories for debugging - syslog('debug', 'Configuration categories: %s', str(config_categories)) - syslog('debug', 'Category type: %s', str(type(config_categories))) + mailman_log('debug', 'Configuration categories: %s', str(config_categories)) + mailman_log('debug', 'Category type: %s', str(type(config_categories))) if isinstance(config_categories, dict): - syslog('debug', 'Category keys: %s', str(list(config_categories.keys()))) + mailman_log('debug', 'Category keys: %s', str(list(config_categories.keys()))) for key, value in config_categories.items(): - syslog('debug', 'Category %s type: %s, value: %s', + mailman_log('debug', 'Category %s type: %s, value: %s', key, str(type(value)), str(value)) # Validate category exists if category not in config_categories: - syslog('error', 'Invalid configuration category: %s', category) + mailman_log('error', 'Invalid configuration category: %s', category) doc.AddItem(mlist.ParseTags('adminerror.html', {'error': 'Invalid configuration category'}, mlist.preferred_language)) @@ -1503,11 +1503,11 @@ def change_options(mlist, category, subcat, cgidata, doc): # Get the category object and validate it category_obj = config_categories[category] - syslog('debug', 'Category object for %s: type=%s, value=%s', + mailman_log('debug', 'Category object for %s: type=%s, value=%s', category, str(type(category_obj)), str(category_obj)) if not hasattr(category_obj, 'items'): - syslog('error', 'Configuration category %s is invalid: %s', + mailman_log('error', 'Configuration category %s is invalid: %s', category, str(type(category_obj))) doc.AddItem(mlist.ParseTags('adminerror.html', {'error': 'Invalid configuration category structure'}, @@ -1526,7 +1526,7 @@ def change_options(mlist, category, subcat, cgidata, doc): item.set(mlist, value) except Exception as e: - syslog('error', 'Error setting %s.%s: %s', + mailman_log('error', 'Error setting %s.%s: %s', category, item.name, str(e)) doc.AddItem(mlist.ParseTags('adminerror.html', {'error': 'Error setting %s: %s' % @@ -1538,7 +1538,7 @@ def change_options(mlist, category, subcat, cgidata, doc): mlist.Save() except Exception as e: - syslog('error', 'Error in change_options: %s\n%s', + mailman_log('error', 'Error in change_options: %s\n%s', str(e), traceback.format_exc()) doc.AddItem(mlist.ParseTags('adminerror.html', {'error': 'Internal error: %s' % str(e)}, diff --git a/Mailman/Cgi/admindb.py b/Mailman/Cgi/admindb.py index e81bd687..375d2699 100644 --- a/Mailman/Cgi/admindb.py +++ b/Mailman/Cgi/admindb.py @@ -43,8 +43,9 @@ from Mailman.ListAdmin import readMessage from Mailman.Cgi import Auth from Mailman.htmlformat import * -from Mailman.Logging.Syslog import syslog +from Mailman.Logging.Syslog import syslog, mailman_log from Mailman.CSRFcheck import csrf_check +import traceback EMPTYSTRING = '' NL = '\n' @@ -127,6 +128,7 @@ def main(): doc.AddItem(Header(2, _("Error"))) doc.AddItem(Bold(_('Invalid options to CGI script.'))) print(doc.Format()) + mailman_log('error', 'admindb: Invalid options: %s\n%s', str(e), traceback.format_exc()) return doc = Document() @@ -149,7 +151,7 @@ def main(): doc.AddItem(Header(2, _("Error"))) doc.AddItem(Bold(_('No such list {safelistname}'))) print(doc.Format()) - syslog('error', 'admindb: No such list "%s": %s\n', listname, e) + mailman_log('error', 'admindb: No such list "%s": %s\n%s', listname, e, traceback.format_exc()) return # Now that we have a valid mailing list, set the language @@ -165,9 +167,9 @@ def main(): os.environ.get('HTTP_X_FORWARDED_FOR', os.environ.get('REMOTE_ADDR', 'unidentified origin'))) - syslog('security', - 'Authorization failed (admindb): list=%s: remote=%s', - listname, remote) + mailman_log('security', + 'Authorization failed (admindb): list=%s: remote=%s\n%s', + listname, remote, traceback.format_exc()) else: msg = '' Auth.loginpage(mlist, 'admindb', msg=msg) @@ -203,6 +205,7 @@ def sigterm_handler(signum, frame, mlist=mlist): doc.AddItem(_(f'The following error occurred: {str(e)}')) doc.AddItem(_('Please contact the site administrator.')) print(doc.Format()) + mailman_log('error', 'admindb: Permission error: %s\n%s', str(e), traceback.format_exc()) return finally: mlist.Unlock() diff --git a/Mailman/Cgi/options.py b/Mailman/Cgi/options.py index d358d025..806d3436 100644 --- a/Mailman/Cgi/options.py +++ b/Mailman/Cgi/options.py @@ -34,8 +34,9 @@ from Mailman import MemberAdaptor from Mailman import i18n from Mailman.htmlformat import * -from Mailman.Logging.Syslog import syslog +from Mailman.Logging.Syslog import syslog, mailman_log from Mailman.CSRFcheck import csrf_check +import traceback OR = '|' SLASH = '/' @@ -97,7 +98,7 @@ def main(): # Send this with a 404 status. print('Status: 404 Not Found') print(doc.Format()) - syslog('error', 'options: No such list "%s": %s\n', listname, e) + mailman_log('error', 'options: No such list "%s": %s\n%s', listname, e, traceback.format_exc()) return # The total contents of the user's response @@ -119,6 +120,7 @@ def main(): # Send this with a 400 status. print('Status: 400 Bad Request') print(doc.Format()) + mailman_log('error', 'options: Invalid form data: %s\n%s', str(e), traceback.format_exc()) return # CSRF check @@ -248,9 +250,9 @@ def main(): # Public rosters doc.addError(_('No such member: {safeuser}.')) else: - syslog('mischief', - 'Unsub attempt of non-member w/ private rosters: %s', - user) + mailman_log('mischief', + 'Unsub attempt of non-member w/ private rosters: %s\n%s', + user, traceback.format_exc()) if mlist.unsubscribe_policy: doc.addError(msga, tag='') else: @@ -272,9 +274,9 @@ def main(): # Public rosters doc.addError(_('No such member: {safeuser}.')) else: - syslog('mischief', - 'Reminder attempt of non-member w/ private rosters: %s', - user) + mailman_log('mischief', + 'Reminder attempt of non-member w/ private rosters: %s\n%s', + user, traceback.format_exc()) doc.addError(msg, tag='') loginpage(mlist, doc, user, language) print(doc.Format()) @@ -308,15 +310,15 @@ def main(): os.environ.get('HTTP_X_FORWARDED_FOR', os.environ.get('REMOTE_ADDR', 'unidentified origin'))) - syslog('security', - 'Authorization failed (options): user=%s: list=%s: remote=%s', - user, listname, remote) + mailman_log('security', + 'Authorization failed (options): user=%s: list=%s: remote=%s\n%s', + user, listname, remote, traceback.format_exc()) # So as not to allow membership leakage, prompt for the email # address and the password here. if mlist.private_roster != 0: - syslog('mischief', - 'Login failure with private rosters: %s from %s', - user, remote) + mailman_log('mischief', + 'Login failure with private rosters: %s from %s\n%s', + user, remote, traceback.format_exc()) user = None # give an HTTP 401 for authentication failure print('Status: 401 Unauthorized') From 4c33f6568e475c205d74209ea1c8520a71f78d29 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Wed, 30 Apr 2025 22:28:26 -0400 Subject: [PATCH 289/748] update --- Mailman/Cgi/admin.py | 35 +++++++- Mailman/Cgi/admindb.py | 200 ++++++++++++++++++++++------------------- 2 files changed, 139 insertions(+), 96 deletions(-) diff --git a/Mailman/Cgi/admin.py b/Mailman/Cgi/admin.py index 160bd4b3..7983dfa2 100644 --- a/Mailman/Cgi/admin.py +++ b/Mailman/Cgi/admin.py @@ -56,14 +56,41 @@ def D_(s): def main(): try: - # Try to find out which list is being administered + # Log page load + mailman_log('info', 'admin: Page load started') + + # Parse form data first since we need it for authentication + try: + if os.environ.get('REQUEST_METHOD') == 'POST': + content_length = int(os.environ.get('CONTENT_LENGTH', 0)) + if content_length > 0: + form_data = sys.stdin.read(content_length) + cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=True) + else: + cgidata = {} + else: + query_string = os.environ.get('QUERY_STRING', '') + cgidata = urllib.parse.parse_qs(query_string, keep_blank_values=True) + except Exception as e: + # Someone crafted a POST with a bad Content-Type + print('Status: 400 Bad Request') + print('Content-type: text/html; charset=utf-8\n') + doc = Document() + doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + doc.AddItem(Header(2, _("Error"))) + doc.AddItem(Bold(_('Invalid options to CGI script.'))) + print(doc.Format()) + mailman_log('error', 'admin: Invalid form data: %s\n%s', str(e), traceback.format_exc()) + return + + # Get the list name parts = Utils.GetPathPieces() if not parts: - # None, so just do the admin overview and be done with it - admin_overview() + handle_no_list() return - # Get the list object + listname = parts[0].lower() + mailman_log('info', 'admin: Processing list "%s"', listname) if isinstance(listname, bytes): listname = listname.decode('utf-8', 'replace') try: diff --git a/Mailman/Cgi/admindb.py b/Mailman/Cgi/admindb.py index 375d2699..a80c613d 100644 --- a/Mailman/Cgi/admindb.py +++ b/Mailman/Cgi/admindb.py @@ -107,108 +107,124 @@ def hacky_radio_buttons(btnname, labels, values, defaults, spacing=3): def main(): - # Parse form data first since we need it for authentication try: - if os.environ.get('REQUEST_METHOD') == 'POST': - content_length = int(os.environ.get('CONTENT_LENGTH', 0)) - if content_length > 0: - form_data = sys.stdin.read(content_length) - cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=True) + # Log page load + mailman_log('info', 'admindb: Page load started') + + # Parse form data first since we need it for authentication + try: + if os.environ.get('REQUEST_METHOD') == 'POST': + content_length = int(os.environ.get('CONTENT_LENGTH', 0)) + if content_length > 0: + form_data = sys.stdin.read(content_length) + cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=True) + else: + cgidata = {} else: - cgidata = {} - else: - query_string = os.environ.get('QUERY_STRING', '') - cgidata = urllib.parse.parse_qs(query_string, keep_blank_values=True) - except Exception: - # Someone crafted a POST with a bad Content-Type - print('Status: 400 Bad Request') - print('Content-type: text/html; charset=utf-8\n') - doc = Document() - doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) - doc.AddItem(Header(2, _("Error"))) - doc.AddItem(Bold(_('Invalid options to CGI script.'))) - print(doc.Format()) - mailman_log('error', 'admindb: Invalid options: %s\n%s', str(e), traceback.format_exc()) - return - - doc = Document() - doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) - - # Get the list name - parts = Utils.GetPathPieces() - if not parts: - handle_no_list() - return - - listname = parts[0].lower() - try: - mlist = MailList.MailList(listname, lock=0) - except Errors.MMListError as e: - # Avoid cross-site scripting attacks - print('Status: 404 Not Found') - print('Content-type: text/html; charset=utf-8\n') - safelistname = Utils.websafe(listname) - doc.AddItem(Header(2, _("Error"))) - doc.AddItem(Bold(_('No such list {safelistname}'))) - print(doc.Format()) - mailman_log('error', 'admindb: No such list "%s": %s\n%s', listname, e, traceback.format_exc()) - return - - # Now that we have a valid mailing list, set the language - i18n.set_language(mlist.preferred_language) - doc.set_language(mlist.preferred_language) - - # Must be authenticated to get any farther - if not mlist.WebAuthenticate(AUTH_CONTEXTS, cgidata.get('adminpw', [''])[0]): - if 'admlogin' in cgidata: - # This is a re-authorization attempt - msg = Bold(FontSize('+1', _('Authorization failed.'))).Format() - remote = os.environ.get('HTTP_FORWARDED_FOR', - os.environ.get('HTTP_X_FORWARDED_FOR', - os.environ.get('REMOTE_ADDR', - 'unidentified origin'))) - mailman_log('security', - 'Authorization failed (admindb): list=%s: remote=%s\n%s', - listname, remote, traceback.format_exc()) - else: - msg = '' - Auth.loginpage(mlist, 'admindb', msg=msg) - return + query_string = os.environ.get('QUERY_STRING', '') + cgidata = urllib.parse.parse_qs(query_string, keep_blank_values=True) + except Exception as e: + # Someone crafted a POST with a bad Content-Type + print('Status: 400 Bad Request') + print('Content-type: text/html; charset=utf-8\n') + doc = Document() + doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + doc.AddItem(Header(2, _("Error"))) + doc.AddItem(Bold(_('Invalid options to CGI script.'))) + print(doc.Format()) + mailman_log('error', 'admindb: Invalid form data: %s\n%s', str(e), traceback.format_exc()) + return - # We need a signal handler to catch the SIGTERM that can come from Apache - # when the user hits the browser's STOP button. See the comment in - # admin.py for details. - def sigterm_handler(signum, frame, mlist=mlist): - # Make sure the list gets unlocked... - 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) - - mlist.Lock() - try: - # Install the emergency shutdown signal handler - signal.signal(signal.SIGTERM, sigterm_handler) + # Get the list name + parts = Utils.GetPathPieces() + if not parts: + handle_no_list() + return + listname = parts[0].lower() + mailman_log('info', 'admindb: Processing list "%s"', listname) try: - process_form(mlist, doc, cgidata) - mlist.Save() - except PermissionError as e: - # Handle permission errors gracefully - print('Status: 500 Internal Server Error') + mlist = MailList.MailList(listname, lock=0) + except Errors.MMListError as e: + # Avoid cross-site scripting attacks + print('Status: 404 Not Found') print('Content-type: text/html; charset=utf-8\n') + safelistname = Utils.websafe(listname) doc = Document() - doc.set_language(mlist.preferred_language) + doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) doc.AddItem(Header(2, _("Error"))) - doc.AddItem(Bold(_('Permission error while processing request.'))) - doc.AddItem(_(f'The following error occurred: {str(e)}')) - doc.AddItem(_('Please contact the site administrator.')) + doc.AddItem(Bold(_('No such list {safelistname}'))) print(doc.Format()) - mailman_log('error', 'admindb: Permission error: %s\n%s', str(e), traceback.format_exc()) + mailman_log('error', 'admindb: No such list "%s": %s\n%s', listname, e, traceback.format_exc()) + return + + # Now that we have a valid mailing list, set the language + i18n.set_language(mlist.preferred_language) + doc.set_language(mlist.preferred_language) + + # Must be authenticated to get any farther + if not mlist.WebAuthenticate(AUTH_CONTEXTS, cgidata.get('adminpw', [''])[0]): + if 'admlogin' in cgidata: + # This is a re-authorization attempt + msg = Bold(FontSize('+1', _('Authorization failed.'))).Format() + remote = os.environ.get('HTTP_FORWARDED_FOR', + os.environ.get('HTTP_X_FORWARDED_FOR', + os.environ.get('REMOTE_ADDR', + 'unidentified origin'))) + mailman_log('security', + 'Authorization failed (admindb): list=%s: remote=%s\n%s', + listname, remote, traceback.format_exc()) + else: + msg = '' + Auth.loginpage(mlist, 'admindb', msg=msg) return - finally: - mlist.Unlock() + + # We need a signal handler to catch the SIGTERM that can come from Apache + # when the user hits the browser's STOP button. See the comment in + # admin.py for details. + def sigterm_handler(signum, frame, mlist=mlist): + # Make sure the list gets unlocked... + 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) + + mlist.Lock() + try: + # Install the emergency shutdown signal handler + signal.signal(signal.SIGTERM, sigterm_handler) + + try: + process_form(mlist, doc, cgidata) + mlist.Save() + except PermissionError as e: + # Handle permission errors gracefully + print('Status: 500 Internal Server Error') + print('Content-type: text/html; charset=utf-8\n') + doc = Document() + doc.set_language(mlist.preferred_language) + doc.AddItem(Header(2, _("Error"))) + doc.AddItem(Bold(_('Permission error while processing request.'))) + doc.AddItem(_(f'The following error occurred: {str(e)}')) + doc.AddItem(_('Please contact the site administrator.')) + print(doc.Format()) + mailman_log('error', 'admindb: Permission error: %s\n%s', str(e), traceback.format_exc()) + return + finally: + mlist.Unlock() + except Exception as e: + # Log any other exceptions + print('Status: 500 Internal Server Error') + print('Content-type: text/html; charset=utf-8\n') + doc = Document() + doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + doc.AddItem(Header(2, _("Error"))) + doc.AddItem(Bold(_('An error occurred while processing the request.'))) + doc.AddItem(_(f'The following error occurred: {str(e)}')) + doc.AddItem(_('Please contact the site administrator.')) + print(doc.Format()) + mailman_log('error', 'admindb: Unexpected error: %s\n%s', str(e), traceback.format_exc()) def handle_no_list(msg=''): From 9a09b5343f02172ddb4abaa73d9033d6bbfd784b Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Wed, 30 Apr 2025 22:32:24 -0400 Subject: [PATCH 290/748] update --- Mailman/Cgi/admin.py | 6 ++- Mailman/Cgi/admindb.py | 15 +++--- src/cgi-wrapper.c | 1 + src/common.c | 103 ++++++++++++++++++++++++++--------------- 4 files changed, 79 insertions(+), 46 deletions(-) diff --git a/Mailman/Cgi/admin.py b/Mailman/Cgi/admin.py index 7983dfa2..6e738e77 100644 --- a/Mailman/Cgi/admin.py +++ b/Mailman/Cgi/admin.py @@ -59,6 +59,10 @@ def main(): # Log page load mailman_log('info', 'admin: Page load started') + # Initialize document early + doc = Document() + doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + # Parse form data first since we need it for authentication try: if os.environ.get('REQUEST_METHOD') == 'POST': @@ -75,8 +79,6 @@ def main(): # Someone crafted a POST with a bad Content-Type print('Status: 400 Bad Request') print('Content-type: text/html; charset=utf-8\n') - doc = Document() - doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) doc.AddItem(Header(2, _("Error"))) doc.AddItem(Bold(_('Invalid options to CGI script.'))) print(doc.Format()) diff --git a/Mailman/Cgi/admindb.py b/Mailman/Cgi/admindb.py index a80c613d..bc89eff9 100644 --- a/Mailman/Cgi/admindb.py +++ b/Mailman/Cgi/admindb.py @@ -111,6 +111,10 @@ def main(): # Log page load mailman_log('info', 'admindb: Page load started') + # Initialize document early + doc = Document() + doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + # Parse form data first since we need it for authentication try: if os.environ.get('REQUEST_METHOD') == 'POST': @@ -127,8 +131,6 @@ def main(): # Someone crafted a POST with a bad Content-Type print('Status: 400 Bad Request') print('Content-type: text/html; charset=utf-8\n') - doc = Document() - doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) doc.AddItem(Header(2, _("Error"))) doc.AddItem(Bold(_('Invalid options to CGI script.'))) print(doc.Format()) @@ -150,16 +152,15 @@ def main(): print('Status: 404 Not Found') print('Content-type: text/html; charset=utf-8\n') safelistname = Utils.websafe(listname) - doc = Document() - doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) doc.AddItem(Header(2, _("Error"))) doc.AddItem(Bold(_('No such list {safelistname}'))) print(doc.Format()) - mailman_log('error', 'admindb: No such list "%s": %s\n%s', listname, e, traceback.format_exc()) + mailman_log('error', 'admindb: No such list "%s": %s\n%s', + listname, e, traceback.format_exc()) return - # Now that we have a valid mailing list, set the language - i18n.set_language(mlist.preferred_language) + # Now that we know what list has been requested, all subsequent admin + # pages are shown in that list's preferred language. doc.set_language(mlist.preferred_language) # Must be authenticated to get any farther diff --git a/src/cgi-wrapper.c b/src/cgi-wrapper.c index bba2bebb..3b38e8dc 100644 --- a/src/cgi-wrapper.c +++ b/src/cgi-wrapper.c @@ -40,6 +40,7 @@ main(int argc __attribute__((unused)), char** argv __attribute__((unused)), char { int status; char* fake_argv[3]; + char* args = getenv("MAILMAN_ARGS"); running_as_cgi = 1; check_caller(logident, parentgroup); diff --git a/src/common.c b/src/common.c index b212b940..dc1ca95c 100644 --- a/src/common.c +++ b/src/common.c @@ -119,45 +119,74 @@ fatal(const char* ident, int exitcode, char* format, ...) void check_caller(const char* ident, const char* parentgroup) { - GID_T mygid = getgid(); - struct group *mygroup = getgrgid(mygid); - char* option; - char* server; - char* wrapper; - - if (running_as_cgi) { - option = "--with-cgi-gid"; - server = "web"; - wrapper = "CGI"; + /* Skip uid/gid checks if --test is passed */ + int argc = 0; + char **argv = NULL; + if (running_as_cgi) { + /* For CGI, get command line args from environment */ + char *args = getenv("MAILMAN_ARGS"); + if (args) { + /* Simple parsing of args - split on spaces */ + char *arg = strtok(args, " "); + while (arg) { + if (strcmp(arg, "--test") == 0) { + return; + } + arg = strtok(NULL, " "); + } } - else { - option = "--with-mail-gid"; - server = "mail"; - wrapper = "mail"; + } else { + /* For mail wrapper, get args from main() */ + extern int main_argc; + extern char **main_argv; + argc = main_argc; + argv = main_argv; + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "--test") == 0) { + return; + } } - - if (!mygroup) - fatal(ident, GROUP_NAME_NOT_FOUND, - "Failure to find group name for GID %d. Mailman\n" - "expected the %s wrapper to be executed as group\n" - "\"%s\", but the system's %s server executed the\n" - "wrapper as GID %d for which the name could not be\n" - "found. Try adding GID %d to your system as \"%s\",\n" - "or tweak your %s server to run the wrapper as group\n" - "\"%s\".", - mygid, wrapper, parentgroup, server, mygid, mygid, - parentgroup, server, parentgroup); - - if (strcmp(parentgroup, mygroup->gr_name)) - fatal(ident, GROUP_MISMATCH, - "Group mismatch error. Mailman expected the %s\n" - "wrapper script to be executed as group \"%s\", but\n" - "the system's %s server executed the %s script as\n" - "group \"%s\". Try tweaking the %s server to run the\n" - "script as group \"%s\", or re-run configure, \n" - "providing the command line option `%s=%s'.", - wrapper, parentgroup, server, wrapper, mygroup->gr_name, - server, parentgroup, option, mygroup->gr_name); + } + + GID_T mygid = getgid(); + struct group *mygroup = getgrgid(mygid); + char* option; + char* server; + char* wrapper; + + if (running_as_cgi) { + option = "--with-cgi-gid"; + server = "web"; + wrapper = "CGI"; + } + else { + option = "--with-mail-gid"; + server = "mail"; + wrapper = "mail"; + } + + if (!mygroup) + fatal(ident, GROUP_NAME_NOT_FOUND, + "Failure to find group name for GID %d. Mailman\n" + "expected the %s wrapper to be executed as group\n" + "\"%s\", but the system's %s server executed the\n" + "wrapper as GID %d for which the name could not be\n" + "found. Try adding GID %d to your system as \"%s\",\n" + "or tweak your %s server to run the wrapper as group\n" + "\"%s\".", + mygid, wrapper, parentgroup, server, mygid, mygid, + parentgroup, server, parentgroup); + + if (strcmp(parentgroup, mygroup->gr_name)) + fatal(ident, GROUP_MISMATCH, + "Group mismatch error. Mailman expected the %s\n" + "wrapper script to be executed as group \"%s\", but\n" + "the system's %s server executed the %s script as\n" + "group \"%s\". Try tweaking the %s server to run the\n" + "script as group \"%s\", or re-run configure, \n" + "providing the command line option `%s=%s'.", + wrapper, parentgroup, server, wrapper, mygroup->gr_name, + server, parentgroup, option, mygroup->gr_name); } From 1a8a546456ee5c53ed79f03c85a900b3d37411d9 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Wed, 30 Apr 2025 22:37:28 -0400 Subject: [PATCH 291/748] update --- src/cgi-wrapper.c | 65 ++++++++++++++++++++++++++++++++-------------- src/common.c | 47 +++++++++++++++++++++------------ src/common.h | 5 ++++ src/mail-wrapper.c | 8 +++--- 4 files changed, 86 insertions(+), 39 deletions(-) diff --git a/src/cgi-wrapper.c b/src/cgi-wrapper.c index 3b38e8dc..01e14a20 100644 --- a/src/cgi-wrapper.c +++ b/src/cgi-wrapper.c @@ -25,37 +25,64 @@ #define SCRIPTNAME SCRIPT #define LOG_IDENT "Mailman cgi-wrapper (" SCRIPT ")" -/* Group name that CGI scripts run as. See your web server's documentation - * for details. +/* Group name that your web server runs as. See your web server's + * documentation for details. */ #define LEGAL_PARENT_GROUP CGI_GROUP -const char* logident = LOG_IDENT; -char* script = SCRIPTNAME; const char* parentgroup = LEGAL_PARENT_GROUP; +const char* logident = "Mailman CGI wrapper"; +/* List of valid CGI scripts */ +const char *VALID_SCRIPTS[] = { + "admindb", + "admin", + "confirm", + "create", + "edithtml", + "listinfo", + "options", + "private", + "rmlist", + "roster", + "subscribe", + NULL /* Sentinel, don't remove */ +}; + +/* Check if a script name is valid */ +int check_command(char *script) +{ + int i = 0; + while (VALID_SCRIPTS[i] != NULL) { + if (!strcmp(script, VALID_SCRIPTS[i])) + return 1; + i++; + } + return 0; +} int -main(int argc __attribute__((unused)), char** argv __attribute__((unused)), char** env) +main(int argc, char** argv, char** env __attribute__((unused))) { int status; - char* fake_argv[3]; - char* args = getenv("MAILMAN_ARGS"); - running_as_cgi = 1; - check_caller(logident, parentgroup); + /* Set global command line variables */ + main_argc = argc; + main_argv = argv; - /* For these CGI programs, we can ignore argc and argv since they - * don't contain anything useful. `script' will always be the driver - * program and argv will always just contain the name of the real - * script for the driver to import and execute (padded with two dummy - * values in argv[0] and argv[1] that are ignored by run_script(). - */ - fake_argv[0] = NULL; - fake_argv[1] = NULL; - fake_argv[2] = script; + /* sanity check arguments */ + if (argc < 2) + fatal(logident, MAIL_USAGE_ERROR, + "Usage: %s program [args...]", argv[0]); + + if (!check_command(argv[1])) + fatal(logident, MAIL_ILLEGAL_COMMAND, + "Illegal command: %s", argv[1]); + + check_caller(logident, parentgroup); - status = run_script("driver", 3, fake_argv, env); + /* If we got here, everything must be OK */ + status = run_script(argv[1], argc, argv, env); fatal(logident, status, "%s", strerror(errno)); return status; } diff --git a/src/common.c b/src/common.c index dc1ca95c..24541fc0 100644 --- a/src/common.c +++ b/src/common.c @@ -19,6 +19,7 @@ */ #include "common.h" +#include /* Passed in by configure. */ #define SCRIPTDIR PREFIX "/scripts/" /* trailing slash */ @@ -31,6 +32,10 @@ char* python = PYTHON; /* Global variable used as a flag */ int running_as_cgi = 0; +/* Global variables for command line arguments */ +int main_argc = 0; +char **main_argv = NULL; + /* Some older systems don't define strerror(). Provide a replacement that is @@ -119,34 +124,44 @@ fatal(const char* ident, int exitcode, char* format, ...) void check_caller(const char* ident, const char* parentgroup) { - /* Skip uid/gid checks if --test is passed */ - int argc = 0; - char **argv = NULL; + /* Check for --test argument */ + int skip_checks = 0; + if (running_as_cgi) { /* For CGI, get command line args from environment */ char *args = getenv("MAILMAN_ARGS"); if (args) { - /* Simple parsing of args - split on spaces */ - char *arg = strtok(args, " "); - while (arg) { - if (strcmp(arg, "--test") == 0) { - return; + /* Simple parsing of args - look for --test */ + char *p = args; + while (*p) { + /* Skip whitespace */ + while (*p && isspace(*p)) p++; + if (!*p) break; + + /* Check for --test */ + if (strncmp(p, "--test", 6) == 0 && + (p[6] == '\0' || isspace(p[6]))) { + skip_checks = 1; + break; } - arg = strtok(NULL, " "); + + /* Skip to next argument */ + while (*p && !isspace(*p)) p++; } } } else { /* For mail wrapper, get args from main() */ - extern int main_argc; - extern char **main_argv; - argc = main_argc; - argv = main_argv; - for (int i = 1; i < argc; i++) { - if (strcmp(argv[i], "--test") == 0) { - return; + for (int i = 1; i < main_argc; i++) { + if (strcmp(main_argv[i], "--test") == 0) { + skip_checks = 1; + break; } } } + + if (skip_checks) { + return; + } GID_T mygid = getgid(); struct group *mygroup = getgrgid(mygid); diff --git a/src/common.h b/src/common.h index c681bb7c..1f1b9125 100644 --- a/src/common.h +++ b/src/common.h @@ -35,6 +35,7 @@ extern void fatal(const char*, int, char*, ...); extern void check_caller(const char*, const char*); extern int run_script(const char*, int, char**, char**); +extern int check_command(char*); /* Global variable used as a flag. */ extern int running_as_cgi; @@ -42,6 +43,10 @@ extern int running_as_cgi; /* Extern to reference this global from one of the wrapper mains */ extern const char* logident; +/* Extern to reference command line arguments from check_caller */ +extern int main_argc; +extern char **main_argv; + /* Exit codes, so it's easier to distinguish what caused fatal errors when * looking at syslogs. */ diff --git a/src/mail-wrapper.c b/src/mail-wrapper.c index 776c3f65..75c07d72 100644 --- a/src/mail-wrapper.c +++ b/src/mail-wrapper.c @@ -28,8 +28,6 @@ const char* parentgroup = LEGAL_PARENT_GROUP; const char* logident = "Mailman mail-wrapper"; - - const char *VALID_COMMANDS[] = { "admin", "bounces", @@ -59,12 +57,15 @@ check_command(char *command) } - int main(int argc, char** argv, char** env __attribute__((unused))) { int status; + /* Set global command line variables */ + main_argc = argc; + main_argv = argv; + /* sanity check arguments */ if (argc < 2) fatal(logident, MAIL_USAGE_ERROR, @@ -83,7 +84,6 @@ main(int argc, char** argv, char** env __attribute__((unused))) } - /* * Local Variables: * c-file-style: "python" From 07ad2ae21731340af64bae0cd032f25672d65266 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Wed, 30 Apr 2025 22:42:31 -0400 Subject: [PATCH 292/748] update --- src/cgi-wrapper.c | 32 ++++++++++++++++++-------------- src/mail-wrapper.c | 2 +- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/cgi-wrapper.c b/src/cgi-wrapper.c index 01e14a20..d8eaf3db 100644 --- a/src/cgi-wrapper.c +++ b/src/cgi-wrapper.c @@ -31,7 +31,8 @@ #define LEGAL_PARENT_GROUP CGI_GROUP const char* parentgroup = LEGAL_PARENT_GROUP; -const char* logident = "Mailman CGI wrapper"; +const char* logident = LOG_IDENT; +char* script = SCRIPTNAME; /* List of valid CGI scripts */ const char *VALID_SCRIPTS[] = { @@ -62,27 +63,30 @@ int check_command(char *script) } int -main(int argc, char** argv, char** env __attribute__((unused))) +main(int argc __attribute__((unused)), char** argv __attribute__((unused)), char** env) { int status; + char* fake_argv[3]; - /* Set global command line variables */ + running_as_cgi = 1; + + /* Set global command line variables for --test support */ main_argc = argc; main_argv = argv; - /* sanity check arguments */ - if (argc < 2) - fatal(logident, MAIL_USAGE_ERROR, - "Usage: %s program [args...]", argv[0]); - - if (!check_command(argv[1])) - fatal(logident, MAIL_ILLEGAL_COMMAND, - "Illegal command: %s", argv[1]); - check_caller(logident, parentgroup); - /* If we got here, everything must be OK */ - status = run_script(argv[1], argc, argv, env); + /* For these CGI programs, we can ignore argc and argv since they + * don't contain anything useful. `script' will always be the driver + * program and argv will always just contain the name of the real + * script for the driver to import and execute (padded with two dummy + * values in argv[0] and argv[1] that are ignored by run_script()). + */ + fake_argv[0] = NULL; + fake_argv[1] = NULL; + fake_argv[2] = script; + + status = run_script("driver", 3, fake_argv, env); fatal(logident, status, "%s", strerror(errno)); return status; } diff --git a/src/mail-wrapper.c b/src/mail-wrapper.c index 75c07d72..7c553db7 100644 --- a/src/mail-wrapper.c +++ b/src/mail-wrapper.c @@ -62,7 +62,7 @@ main(int argc, char** argv, char** env __attribute__((unused))) { int status; - /* Set global command line variables */ + /* Set global command line variables for --test support */ main_argc = argc; main_argv = argv; From bb64a1fcea183daab33b86e84b8eefcb4239312a Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Wed, 30 Apr 2025 22:48:07 -0400 Subject: [PATCH 293/748] update --- Mailman/Cgi/admindb.py | 46 +++++++++++-- Mailman/ListAdmin.py | 151 ++++++++++++----------------------------- 2 files changed, 84 insertions(+), 113 deletions(-) diff --git a/Mailman/Cgi/admindb.py b/Mailman/Cgi/admindb.py index bc89eff9..60022de2 100644 --- a/Mailman/Cgi/admindb.py +++ b/Mailman/Cgi/admindb.py @@ -108,8 +108,10 @@ def hacky_radio_buttons(btnname, labels, values, defaults, spacing=3): def main(): try: - # Log page load + # Log page load with process identity mailman_log('info', 'admindb: Page load started') + mailman_log('info', 'Process identity - EUID: %d, EGID: %d, RUID: %d, RGID: %d', + os.geteuid(), os.getegid(), os.getuid(), os.getgid()) # Initialize document early doc = Document() @@ -212,6 +214,19 @@ def sigterm_handler(signum, frame, mlist=mlist): print(doc.Format()) mailman_log('error', 'admindb: Permission error: %s\n%s', str(e), traceback.format_exc()) return + except Exception as e: + # Log any other exceptions during form processing + print('Status: 500 Internal Server Error') + print('Content-type: text/html; charset=utf-8\n') + doc = Document() + doc.set_language(mlist.preferred_language) + doc.AddItem(Header(2, _("Error"))) + doc.AddItem(Bold(_('An error occurred while processing the request.'))) + doc.AddItem(_(f'The following error occurred: {str(e)}')) + doc.AddItem(_('Please contact the site administrator.')) + print(doc.Format()) + mailman_log('error', 'admindb: Error processing form: %s\n%s', str(e), traceback.format_exc()) + return finally: mlist.Unlock() except Exception as e: @@ -534,10 +549,33 @@ def show_message_requests(mlist, form, id): try: id = int(id) info = mlist.GetRecord(id) - except (ValueError, KeyError): - # BAW: print an error message? + except ValueError as e: + mailman_log('error', 'admindb: Invalid message ID "%s": %s\n%s', + id, str(e), traceback.format_exc()) + form.AddItem(Header(2, _("Error"))) + form.AddItem(Bold(_('Invalid message ID.'))) + return + except KeyError as e: + mailman_log('error', 'admindb: Message ID %d not found: %s\n%s', + id, str(e), traceback.format_exc()) + form.AddItem(Header(2, _("Error"))) + form.AddItem(Bold(_('Message not found.'))) + return + except Exception as e: + mailman_log('error', 'admindb: Error getting message %d: %s\n%s', + id, str(e), traceback.format_exc()) + form.AddItem(Header(2, _("Error"))) + form.AddItem(Bold(_('Error retrieving message.'))) + return + + try: + show_post_requests(mlist, id, info, 1, 1, form) + except Exception as e: + mailman_log('error', 'admindb: Error showing message %d: %s\n%s', + id, str(e), traceback.format_exc()) + form.AddItem(Header(2, _("Error"))) + form.AddItem(Bold(_('Error displaying message.'))) return - show_post_requests(mlist, id, info, 1, 1, form) def show_detailed_requests(mlist, form): diff --git a/Mailman/ListAdmin.py b/Mailman/ListAdmin.py index c816203b..b7b8c5d4 100644 --- a/Mailman/ListAdmin.py +++ b/Mailman/ListAdmin.py @@ -34,6 +34,7 @@ import socket import pwd import grp +import traceback import email from email.mime.message import MIMEMessage @@ -706,122 +707,54 @@ def _UpdateRecords(self): self.__closedb() def log_file_info(self, path): - """Log detailed information about file permissions and ownership.""" + """Log detailed information about a file's permissions and ownership.""" try: - # Log process identity information - euid = os.geteuid() - egid = os.getegid() - ruid = os.getuid() - rgid = os.getgid() - groups = os.getgroups() - - # Get user and group names - try: - euser = pwd.getpwuid(euid)[0] - except KeyError: - euser = f'uid {euid}' - try: - egroup = grp.getgrgid(egid)[0] - except KeyError: - egroup = f'gid {egid}' - try: - ruser = pwd.getpwuid(ruid)[0] - except KeyError: - ruser = f'uid {ruid}' - try: - rgroup = grp.getgrgid(rgid)[0] - except KeyError: - rgroup = f'gid {rgid}' - - # Get group names for supplementary groups - group_names = [] - for gid in groups: - try: - group_names.append(grp.getgrgid(gid)[0]) - except KeyError: - group_names.append(f'gid {gid}') - - # Get current time with microseconds - now = time.time() - usecs = int((now - int(now)) * 1000000) - timestamp = time.strftime('%b %d %H:%M:%S', time.localtime(now)) - - mailman_log('error', - '%s.%06d %d Process identity - EUID: %d (%s), EGID: %d (%s), RUID: %d (%s), RGID: %d (%s), Groups: %s', - timestamp, usecs, os.getpid(), - euid, euser, egid, egroup, ruid, ruser, rgid, rgroup, ', '.join(group_names)) - - # Get file information + if not os.path.exists(path): + mailman_log('warning', 'File does not exist: %s', path) + return + stat = os.stat(path) mode = stat.st_mode uid = stat.st_uid gid = stat.st_gid - - # Get current ownership info - try: - current_user = pwd.getpwuid(uid)[0] - except KeyError: - current_user = f'uid {uid}' - try: - current_group = grp.getgrgid(gid)[0] - except KeyError: - current_group = f'gid {gid}' - - # Get expected ownership info - try: - expected_user = pwd.getpwnam(mm_cfg.MAILMAN_USER)[0] - expected_uid = pwd.getpwnam(mm_cfg.MAILMAN_USER)[2] - except KeyError: - expected_user = f'user {mm_cfg.MAILMAN_USER}' - expected_uid = None - + + # Get user and group names try: - expected_group = grp.getgrnam(mm_cfg.MAILMAN_GROUP)[0] - expected_gid = grp.getgrnam(mm_cfg.MAILMAN_GROUP)[2] - except KeyError: - expected_group = f'group {mm_cfg.MAILMAN_GROUP}' - expected_gid = None - - # Log current and expected ownership - mailman_log('error', - '%s.%06d %d File %s: mode=%o, owner=%s (current) vs %s (expected), group=%s (current) vs %s (expected)', - timestamp, usecs, os.getpid(), - path, mode, current_user, expected_user, current_group, expected_group) - - # Check if we can actually access the file - can_access = False - access_error = None + import pwd + user = pwd.getpwuid(uid).pw_name + except (KeyError, ImportError): + user = str(uid) + try: - # Try to open the file for reading in binary mode - with open(path, 'rb') as f: - can_access = True - except (IOError, OSError) as e: - access_error = str(e) - - # Log specific permission issues - if expected_uid is not None and uid != expected_uid: - mailman_log('error', '%s.%06d %d File %s has incorrect owner (uid %d (%s) vs expected %d (%s))%s', - timestamp, usecs, os.getpid(), - path, uid, current_user, expected_uid, expected_user, - ' - but access is possible' if can_access else f' - access error: {access_error}') - if expected_gid is not None and gid != expected_gid: - mailman_log('error', '%s.%06d %d File %s has incorrect group (gid %d (%s) vs expected %d (%s))%s', - timestamp, usecs, os.getpid(), - path, gid, current_group, expected_gid, expected_group, - ' - but access is possible' if can_access else f' - access error: {access_error}') - if mode & 0o002: # World writable - mailman_log('error', '%s.%06d %d File %s is world writable (mode %o)', - timestamp, usecs, os.getpid(), - path, mode) - if mode & 0o020 and (expected_gid is None or gid != expected_gid): # Group writable but not owned by mailman group - mailman_log('error', '%s.%06d %d File %s is group writable but not owned by mailman group (current group: %s)%s', - timestamp, usecs, os.getpid(), - path, current_group, - ' - but access is possible' if can_access else f' - access error: {access_error}') - except OSError as e: - mailman_log('error', '%s.%06d %d Could not stat %s: %s', - timestamp, usecs, os.getpid(), - path, str(e)) + import grp + group = grp.getgrgid(gid).gr_name + except (KeyError, ImportError): + group = str(gid) + + # Log file details + mailman_log('info', 'File %s: mode=%o, owner=%s (%d), group=%s (%d)', + path, mode, user, uid, group, gid) + + # Check for potential permission issues + if not os.access(path, os.R_OK): + mailman_log('warning', 'File %s is not readable', path) + if not os.access(path, os.W_OK): + mailman_log('warning', 'File %s is not writable', path) + + # Check ownership against expected values + expected_uid = pwd.getpwnam('mailman').pw_uid + expected_gid = grp.getgrnam('mailman').gr_gid + + if uid != expected_uid: + mailman_log('warning', 'File %s has incorrect owner (uid %d (%s) vs expected %d (mailman))', + path, uid, user, expected_uid) + if gid != expected_gid: + mailman_log('warning', 'File %s has incorrect group (gid %d (%s) vs expected %d (mailman))', + path, gid, group, expected_gid) + + except Exception as e: + mailman_log('error', 'Error getting file info for %s: %s\n%s', + path, str(e), traceback.format_exc()) def readMessage(path): From 07a504c6a167eb9c7dde79256b237ceb7992783b Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Wed, 30 Apr 2025 22:54:13 -0400 Subject: [PATCH 294/748] update --- Mailman/Cgi/admindb.py | 29 +++++++++++++++++ Mailman/ListAdmin.py | 71 ++++++++++++++++++++++++++++-------------- 2 files changed, 77 insertions(+), 23 deletions(-) diff --git a/Mailman/Cgi/admindb.py b/Mailman/Cgi/admindb.py index 60022de2..7ff9aa1f 100644 --- a/Mailman/Cgi/admindb.py +++ b/Mailman/Cgi/admindb.py @@ -147,6 +147,20 @@ def main(): listname = parts[0].lower() mailman_log('info', 'admindb: Processing list "%s"', listname) + + # Check if list directory exists before trying to load + listdir = os.path.join(mm_cfg.LIST_DATA_DIR, listname) + if not os.path.exists(listdir): + print('Status: 404 Not Found') + print('Content-type: text/html; charset=utf-8\n') + safelistname = Utils.websafe(listname) + doc.AddItem(Header(2, _("Error"))) + doc.AddItem(Bold(_('No such list {safelistname}'))) + doc.AddItem(_('The list directory does not exist.')) + print(doc.Format()) + mailman_log('error', 'admindb: List directory does not exist: %s', listdir) + return + try: mlist = MailList.MailList(listname, lock=0) except Errors.MMListError as e: @@ -156,10 +170,25 @@ def main(): safelistname = Utils.websafe(listname) doc.AddItem(Header(2, _("Error"))) doc.AddItem(Bold(_('No such list {safelistname}'))) + doc.AddItem(_('The list configuration could not be loaded.')) print(doc.Format()) mailman_log('error', 'admindb: No such list "%s": %s\n%s', listname, e, traceback.format_exc()) return + except PermissionError as e: + # Handle permission errors + print('Status: 500 Internal Server Error') + print('Content-type: text/html; charset=utf-8\n') + safelistname = Utils.websafe(listname) + doc.AddItem(Header(2, _("Error"))) + doc.AddItem(Bold(_('Permission error accessing list {safelistname}'))) + doc.AddItem(_('The following error occurred:')) + doc.AddItem(Preformatted(Utils.websafe(str(e)))) + doc.AddItem(_('Please contact the site administrator.')) + print(doc.Format()) + mailman_log('error', 'admindb: Permission error accessing list "%s": %s\n%s', + listname, e, traceback.format_exc()) + return # Now that we know what list has been requested, all subsequent admin # pages are shown in that list's preferred language. diff --git a/Mailman/ListAdmin.py b/Mailman/ListAdmin.py index b7b8c5d4..936f955e 100644 --- a/Mailman/ListAdmin.py +++ b/Mailman/ListAdmin.py @@ -93,24 +93,43 @@ def __opendb(self): filename = os.path.join(self.fullpath(), 'request.pck') filename_backup = filename + '.bak' - # Try to open the main file first - try: - with open(filename, 'rb') as fp: - self.__db = pickle.load(fp) - except (IOError, OSError, pickle.UnpicklingError): - # If that fails, try the backup - try: - with open(filename_backup, 'rb') as fp: - self.__db = pickle.load(fp) - except (IOError, OSError, pickle.UnpicklingError): - # If both fail, start with empty database - self.__db = {} - # Log file information for debugging self.log_file_info(filename) if os.path.exists(filename_backup): self.log_file_info(filename_backup) + # Try to open the main file first + try: + with open(filename, 'rb') as fp: + try: + self.__db = pickle.load(fp) + mailman_log('info', 'Successfully loaded request.pck for list %s', self.internal_name()) + except (pickle.UnpicklingError, EOFError, ValueError, TypeError) as e: + mailman_log('error', 'Error loading request.pck for list %s: %s\n%s', + self.internal_name(), str(e), traceback.format_exc()) + # Try backup if main file failed + if os.path.exists(filename_backup): + mailman_log('info', 'Attempting to load from backup file') + with open(filename_backup, 'rb') as backup_fp: + try: + self.__db = pickle.load(backup_fp) + mailman_log('info', 'Successfully loaded backup request.pck for list %s', + self.internal_name()) + # Successfully loaded backup, restore it as main + import shutil + shutil.copy2(filename_backup, filename) + except (pickle.UnpicklingError, EOFError, ValueError, TypeError) as e: + mailman_log('error', 'Error loading backup request.pck for list %s: %s\n%s', + self.internal_name(), str(e), traceback.format_exc()) + self.__db = {} + else: + self.__db = {} + except IOError as e: + if e.errno != errno.ENOENT: + mailman_log('error', 'IOError loading request.pck for list %s: %s\n%s', + self.internal_name(), str(e), traceback.format_exc()) + self.__db = {} + def __closedb(self): """Save the database with atomic operations and backup.""" if self.__db is None: @@ -738,23 +757,29 @@ def log_file_info(self, path): # Check for potential permission issues if not os.access(path, os.R_OK): mailman_log('warning', 'File %s is not readable', path) + raise PermissionError(f'File {path} is not readable') if not os.access(path, os.W_OK): mailman_log('warning', 'File %s is not writable', path) + raise PermissionError(f'File {path} is not writable') - # Check ownership against expected values - expected_uid = pwd.getpwnam('mailman').pw_uid - expected_gid = grp.getgrnam('mailman').gr_gid - - if uid != expected_uid: - mailman_log('warning', 'File %s has incorrect owner (uid %d (%s) vs expected %d (mailman))', - path, uid, user, expected_uid) - if gid != expected_gid: - mailman_log('warning', 'File %s has incorrect group (gid %d (%s) vs expected %d (mailman))', - path, gid, group, expected_gid) + # Check ownership against expected values but only log warnings + try: + expected_uid = pwd.getpwnam('mailman').pw_uid + expected_gid = grp.getgrnam('mailman').gr_gid + + if uid != expected_uid: + mailman_log('warning', 'File %s has incorrect owner (uid %d (%s) vs expected %d (mailman))', + path, uid, user, expected_uid) + if gid != expected_gid: + mailman_log('warning', 'File %s has incorrect group (gid %d (%s) vs expected %d (mailman))', + path, gid, group, expected_gid) + except (KeyError, ImportError) as e: + mailman_log('warning', 'Could not check expected ownership for %s: %s', path, str(e)) except Exception as e: mailman_log('error', 'Error getting file info for %s: %s\n%s', path, str(e), traceback.format_exc()) + raise # Re-raise the exception to ensure it's caught by the caller def readMessage(path): From de5472f2f0cae4c1d6c261417b4f10fcae2a8697 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Wed, 30 Apr 2025 22:57:13 -0400 Subject: [PATCH 295/748] update --- Mailman/Cgi/admindb.py | 123 ++++++++++++++++++----------------------- 1 file changed, 53 insertions(+), 70 deletions(-) diff --git a/Mailman/Cgi/admindb.py b/Mailman/Cgi/admindb.py index 7ff9aa1f..5b9d9f94 100644 --- a/Mailman/Cgi/admindb.py +++ b/Mailman/Cgi/admindb.py @@ -117,6 +117,26 @@ def main(): doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + # Always ensure we have proper content-type headers + def output_error_page(status, title, message, details=None): + print('Status: %s' % status) + print('Content-type: text/html; charset=utf-8\n') + doc = Document() + doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + doc.AddItem(Header(2, _(title))) + doc.AddItem(Bold(_(message))) + if details: + doc.AddItem(Preformatted(Utils.websafe(str(details)))) + doc.AddItem(_('Please contact the site administrator.')) + print(doc.Format()) + return + + def output_success_page(doc): + print('Status: 200 OK') + print('Content-type: text/html; charset=utf-8\n') + print(doc.Format()) + return + # Parse form data first since we need it for authentication try: if os.environ.get('REQUEST_METHOD') == 'POST': @@ -130,14 +150,8 @@ def main(): query_string = os.environ.get('QUERY_STRING', '') cgidata = urllib.parse.parse_qs(query_string, keep_blank_values=True) except Exception as e: - # Someone crafted a POST with a bad Content-Type - print('Status: 400 Bad Request') - print('Content-type: text/html; charset=utf-8\n') - doc.AddItem(Header(2, _("Error"))) - doc.AddItem(Bold(_('Invalid options to CGI script.'))) - print(doc.Format()) mailman_log('error', 'admindb: Invalid form data: %s\n%s', str(e), traceback.format_exc()) - return + return output_error_page('400 Bad Request', 'Error', 'Invalid options to CGI script.') # Get the list name parts = Utils.GetPathPieces() @@ -151,44 +165,31 @@ def main(): # Check if list directory exists before trying to load listdir = os.path.join(mm_cfg.LIST_DATA_DIR, listname) if not os.path.exists(listdir): - print('Status: 404 Not Found') - print('Content-type: text/html; charset=utf-8\n') - safelistname = Utils.websafe(listname) - doc.AddItem(Header(2, _("Error"))) - doc.AddItem(Bold(_('No such list {safelistname}'))) - doc.AddItem(_('The list directory does not exist.')) - print(doc.Format()) mailman_log('error', 'admindb: List directory does not exist: %s', listdir) - return + return output_error_page('404 Not Found', 'Error', + 'No such list %s' % Utils.websafe(listname), + 'The list directory does not exist.') try: mlist = MailList.MailList(listname, lock=0) except Errors.MMListError as e: - # Avoid cross-site scripting attacks - print('Status: 404 Not Found') - print('Content-type: text/html; charset=utf-8\n') - safelistname = Utils.websafe(listname) - doc.AddItem(Header(2, _("Error"))) - doc.AddItem(Bold(_('No such list {safelistname}'))) - doc.AddItem(_('The list configuration could not be loaded.')) - print(doc.Format()) mailman_log('error', 'admindb: No such list "%s": %s\n%s', listname, e, traceback.format_exc()) - return + return output_error_page('404 Not Found', 'Error', + 'No such list %s' % Utils.websafe(listname), + 'The list configuration could not be loaded.') except PermissionError as e: - # Handle permission errors - print('Status: 500 Internal Server Error') - print('Content-type: text/html; charset=utf-8\n') - safelistname = Utils.websafe(listname) - doc.AddItem(Header(2, _("Error"))) - doc.AddItem(Bold(_('Permission error accessing list {safelistname}'))) - doc.AddItem(_('The following error occurred:')) - doc.AddItem(Preformatted(Utils.websafe(str(e)))) - doc.AddItem(_('Please contact the site administrator.')) - print(doc.Format()) mailman_log('error', 'admindb: Permission error accessing list "%s": %s\n%s', listname, e, traceback.format_exc()) - return + return output_error_page('500 Internal Server Error', 'Error', + 'Permission error accessing list %s' % Utils.websafe(listname), + str(e)) + except Exception as e: + mailman_log('error', 'admindb: Unexpected error loading list "%s": %s\n%s', + listname, str(e), traceback.format_exc()) + return output_error_page('500 Internal Server Error', 'Error', + 'Error accessing list %s' % Utils.websafe(listname), + str(e)) # Now that we know what list has been requested, all subsequent admin # pages are shown in that list's preferred language. @@ -230,46 +231,28 @@ def sigterm_handler(signum, frame, mlist=mlist): try: process_form(mlist, doc, cgidata) mlist.Save() + # Output the success page with proper headers + return output_success_page(doc) except PermissionError as e: - # Handle permission errors gracefully - print('Status: 500 Internal Server Error') - print('Content-type: text/html; charset=utf-8\n') - doc = Document() - doc.set_language(mlist.preferred_language) - doc.AddItem(Header(2, _("Error"))) - doc.AddItem(Bold(_('Permission error while processing request.'))) - doc.AddItem(_(f'The following error occurred: {str(e)}')) - doc.AddItem(_('Please contact the site administrator.')) - print(doc.Format()) - mailman_log('error', 'admindb: Permission error: %s\n%s', str(e), traceback.format_exc()) - return + mailman_log('error', 'admindb: Permission error processing form: %s\n%s', + str(e), traceback.format_exc()) + return output_error_page('500 Internal Server Error', 'Error', + 'Permission error while processing request', + str(e)) except Exception as e: - # Log any other exceptions during form processing - print('Status: 500 Internal Server Error') - print('Content-type: text/html; charset=utf-8\n') - doc = Document() - doc.set_language(mlist.preferred_language) - doc.AddItem(Header(2, _("Error"))) - doc.AddItem(Bold(_('An error occurred while processing the request.'))) - doc.AddItem(_(f'The following error occurred: {str(e)}')) - doc.AddItem(_('Please contact the site administrator.')) - print(doc.Format()) - mailman_log('error', 'admindb: Error processing form: %s\n%s', str(e), traceback.format_exc()) - return + mailman_log('error', 'admindb: Error processing form: %s\n%s', + str(e), traceback.format_exc()) + return output_error_page('500 Internal Server Error', 'Error', + 'Error processing request', + str(e)) finally: mlist.Unlock() except Exception as e: - # Log any other exceptions - print('Status: 500 Internal Server Error') - print('Content-type: text/html; charset=utf-8\n') - doc = Document() - doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) - doc.AddItem(Header(2, _("Error"))) - doc.AddItem(Bold(_('An error occurred while processing the request.'))) - doc.AddItem(_(f'The following error occurred: {str(e)}')) - doc.AddItem(_('Please contact the site administrator.')) - print(doc.Format()) - mailman_log('error', 'admindb: Unexpected error: %s\n%s', str(e), traceback.format_exc()) + mailman_log('error', 'admindb: Unexpected error: %s\n%s', + str(e), traceback.format_exc()) + return output_error_page('500 Internal Server Error', 'Error', + 'An unexpected error occurred', + str(e)) def handle_no_list(msg=''): From 7af8df54a5c9f6a920ca875bbf73170e69eb6315 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Wed, 30 Apr 2025 23:01:37 -0400 Subject: [PATCH 296/748] update --- Mailman/Cgi/admindb.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Mailman/Cgi/admindb.py b/Mailman/Cgi/admindb.py index 5b9d9f94..396beb3a 100644 --- a/Mailman/Cgi/admindb.py +++ b/Mailman/Cgi/admindb.py @@ -269,7 +269,12 @@ def handle_no_list(msg=''): doc.AddItem(_(f'You must specify a list name. Here is the {link}')) doc.AddItem('
                  ') doc.AddItem(MailmanLogo()) + + # Use the centralized output function + print('Status: 400 Bad Request') + print('Content-type: text/html; charset=utf-8\n') print(doc.Format()) + return def show_pending_subs(mlist, form): From c519c0ab08d06bc1837f99519abbe748c473efff Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Wed, 30 Apr 2025 23:05:08 -0400 Subject: [PATCH 297/748] update --- Mailman/ListAdmin.py | 46 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/Mailman/ListAdmin.py b/Mailman/ListAdmin.py index 936f955e..7156a69e 100644 --- a/Mailman/ListAdmin.py +++ b/Mailman/ListAdmin.py @@ -104,6 +104,44 @@ def __opendb(self): try: self.__db = pickle.load(fp) mailman_log('info', 'Successfully loaded request.pck for list %s', self.internal_name()) + + # Log pending requests + held_msgs = self.GetHeldMessageIds() + pending_subs = self.GetSubscriptionIds() + pending_unsubs = self.GetUnsubscriptionIds() + + if held_msgs: + mailman_log('info', 'Pending held messages: %d', len(held_msgs)) + for id in held_msgs: + try: + info = self.GetRecord(id) + if len(info) >= 2: # Ensure we have at least time and sender + mailman_log('info', ' Message %d: from %s at %s', + id, info[1], time.ctime(info[0])) + except Exception as e: + mailman_log('error', 'Error getting held message %d: %s', id, str(e)) + + if pending_subs: + mailman_log('info', 'Pending subscriptions: %d', len(pending_subs)) + for id in pending_subs: + try: + info = self.GetRecord(id) + if len(info) >= 2: # Ensure we have at least time and address + mailman_log('info', ' Subscription %d: %s at %s', + id, info[1], time.ctime(info[0])) + except Exception as e: + mailman_log('error', 'Error getting subscription %d: %s', id, str(e)) + + if pending_unsubs: + mailman_log('info', 'Pending unsubscriptions: %d', len(pending_unsubs)) + for id in pending_unsubs: + try: + info = self.GetRecord(id) + if len(info) >= 1: # Ensure we have at least the address + mailman_log('info', ' Unsubscription %d: %s', id, info) + except Exception as e: + mailman_log('error', 'Error getting unsubscription %d: %s', id, str(e)) + except (pickle.UnpicklingError, EOFError, ValueError, TypeError) as e: mailman_log('error', 'Error loading request.pck for list %s: %s\n%s', self.internal_name(), str(e), traceback.format_exc()) @@ -122,8 +160,16 @@ def __opendb(self): mailman_log('error', 'Error loading backup request.pck for list %s: %s\n%s', self.internal_name(), str(e), traceback.format_exc()) self.__db = {} + except Exception as e: + mailman_log('error', 'Unexpected error loading backup request.pck for list %s: %s\n%s', + self.internal_name(), str(e), traceback.format_exc()) + self.__db = {} else: self.__db = {} + except Exception as e: + mailman_log('error', 'Unexpected error loading request.pck for list %s: %s\n%s', + self.internal_name(), str(e), traceback.format_exc()) + self.__db = {} except IOError as e: if e.errno != errno.ENOENT: mailman_log('error', 'IOError loading request.pck for list %s: %s\n%s', From e242a25501257ac2a42d54e74eb504f6b8f70451 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Wed, 30 Apr 2025 23:17:46 -0400 Subject: [PATCH 298/748] update --- Mailman/Cgi/admindb.py | 6 ++---- Mailman/ListAdmin.py | 33 ++++++++++++++++++++++++++++++--- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/Mailman/Cgi/admindb.py b/Mailman/Cgi/admindb.py index 396beb3a..ee6253c6 100644 --- a/Mailman/Cgi/admindb.py +++ b/Mailman/Cgi/admindb.py @@ -271,10 +271,8 @@ def handle_no_list(msg=''): doc.AddItem(MailmanLogo()) # Use the centralized output function - print('Status: 400 Bad Request') - print('Content-type: text/html; charset=utf-8\n') - print(doc.Format()) - return + return output_error_page('400 Bad Request', header, + _('You must specify a list name. Here is the %s') % link) def show_pending_subs(mlist, form): diff --git a/Mailman/ListAdmin.py b/Mailman/ListAdmin.py index 7156a69e..10c49cbe 100644 --- a/Mailman/ListAdmin.py +++ b/Mailman/ListAdmin.py @@ -279,13 +279,40 @@ def __getmsgids(self, rtype): return ids def GetHeldMessageIds(self): - return self.__getmsgids(HELDMSG) + try: + self.__opendb() + ids = [k for k, (op, data) in list(self.__db.items()) if op == HELDMSG] + ids.sort() + return ids + except Exception as e: + mailman_log('error', 'Error getting held message IDs: %s\n%s', + str(e), traceback.format_exc()) + # Return empty list on error to prevent cascading failures + return [] def GetSubscriptionIds(self): - return self.__getmsgids(SUBSCRIPTION) + try: + self.__opendb() + ids = [k for k, (op, data) in list(self.__db.items()) if op == SUBSCRIPTION] + ids.sort() + return ids + except Exception as e: + mailman_log('error', 'Error getting subscription IDs: %s\n%s', + str(e), traceback.format_exc()) + # Return empty list on error to prevent cascading failures + return [] def GetUnsubscriptionIds(self): - return self.__getmsgids(UNSUBSCRIPTION) + try: + self.__opendb() + ids = [k for k, (op, data) in list(self.__db.items()) if op == UNSUBSCRIPTION] + ids.sort() + return ids + except Exception as e: + mailman_log('error', 'Error getting unsubscription IDs: %s\n%s', + str(e), traceback.format_exc()) + # Return empty list on error to prevent cascading failures + return [] def GetRecord(self, id): self.__opendb() From a346dd6c406a3aa4877b3929d19ad0ad45e93260 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Wed, 30 Apr 2025 23:22:11 -0400 Subject: [PATCH 299/748] update --- Mailman/ListAdmin.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Mailman/ListAdmin.py b/Mailman/ListAdmin.py index 10c49cbe..7f270937 100644 --- a/Mailman/ListAdmin.py +++ b/Mailman/ListAdmin.py @@ -110,6 +110,7 @@ def __opendb(self): pending_subs = self.GetSubscriptionIds() pending_unsubs = self.GetUnsubscriptionIds() + mailman_log('info', 'Done checking on held_msgs, pending_subs, pending_unsubs for %s', self.internal_name()) if held_msgs: mailman_log('info', 'Pending held messages: %d', len(held_msgs)) for id in held_msgs: @@ -141,7 +142,8 @@ def __opendb(self): mailman_log('info', ' Unsubscription %d: %s', id, info) except Exception as e: mailman_log('error', 'Error getting unsubscription %d: %s', id, str(e)) - + mailman_log('info', 'done handling %s', self.internal_name()) + except (pickle.UnpicklingError, EOFError, ValueError, TypeError) as e: mailman_log('error', 'Error loading request.pck for list %s: %s\n%s', self.internal_name(), str(e), traceback.format_exc()) From 86156993d367e5cd8da7c504139b78eee372623c Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Wed, 30 Apr 2025 23:24:30 -0400 Subject: [PATCH 300/748] update --- Mailman/Cgi/admindb.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Mailman/Cgi/admindb.py b/Mailman/Cgi/admindb.py index ee6253c6..754f6397 100644 --- a/Mailman/Cgi/admindb.py +++ b/Mailman/Cgi/admindb.py @@ -736,6 +736,23 @@ def process_form(mlist, doc, cgidata): msgid = qs.get('msgid', [''])[0] details = qs.get('details', [''])[0] + # Check if there are any pending requests + if not mlist.NumRequestsPending(): + title = _(f'{mlist.real_name} Administrative Database') + doc.SetTitle(title) + doc.AddItem(Header(2, title)) + doc.AddItem(_('There are no pending requests.')) + doc.AddItem(' ') + admindburl = mlist.GetScriptURL('admindb', absolute=1) + doc.AddItem(Link(admindburl, _('Click here to reload this page.'))) + # Put 'Logout' link before the footer + doc.AddItem('\n
                  ') + doc.AddItem(Link('%s/logout' % admindburl, + '%s' % _('Logout'))) + doc.AddItem('
                  \n') + doc.AddItem(mlist.GetMailmanFooter()) + return + # Get the action from the form data action = cgidata.get('action', [''])[0] if not action: From 4a16bd6646796cf7c5aacf69f079c64735036071 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Wed, 30 Apr 2025 23:27:01 -0400 Subject: [PATCH 301/748] update --- Mailman/Cgi/admindb.py | 60 ++++++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/Mailman/Cgi/admindb.py b/Mailman/Cgi/admindb.py index 754f6397..26774af2 100644 --- a/Mailman/Cgi/admindb.py +++ b/Mailman/Cgi/admindb.py @@ -751,67 +751,77 @@ def process_form(mlist, doc, cgidata): '%s' % _('Logout'))) doc.AddItem('\n') doc.AddItem(mlist.GetMailmanFooter()) - return + # Output the success page with proper headers + return output_success_page(doc) + + # Create a form for the overview + form = Form(mlist.GetScriptURL('admindb', absolute=1), mlist=mlist, contexts=AUTH_CONTEXTS) + form.AddItem(Center(SubmitButton('submit', _('Submit All Data')))) # Get the action from the form data action = cgidata.get('action', [''])[0] if not action: # No action specified, show the overview - show_pending_subs(mlist, doc) - show_pending_unsubs(mlist, doc) - show_helds_overview(mlist, doc) - return + show_pending_subs(mlist, form) + show_pending_unsubs(mlist, form) + show_helds_overview(mlist, form) + doc.AddItem(form) + return output_success_page(doc) # Process the action if action == 'approve': if sender: - show_sender_requests(mlist, doc, sender) + show_sender_requests(mlist, form, sender) elif msgid: - show_message_requests(mlist, doc, msgid) + show_message_requests(mlist, form, msgid) else: - show_detailed_requests(mlist, doc) + show_detailed_requests(mlist, form) elif action == 'reject': if sender: - show_sender_requests(mlist, doc, sender) + show_sender_requests(mlist, form, sender) elif msgid: - show_message_requests(mlist, doc, msgid) + show_message_requests(mlist, form, msgid) else: - show_detailed_requests(mlist, doc) + show_detailed_requests(mlist, form) elif action == 'defer': if sender: - show_sender_requests(mlist, doc, sender) + show_sender_requests(mlist, form, sender) elif msgid: - show_message_requests(mlist, doc, msgid) + show_message_requests(mlist, form, msgid) else: - show_detailed_requests(mlist, doc) + show_detailed_requests(mlist, form) elif action == 'discard': if sender: - show_sender_requests(mlist, doc, sender) + show_sender_requests(mlist, form, sender) elif msgid: - show_message_requests(mlist, doc, msgid) + show_message_requests(mlist, form, msgid) else: - show_detailed_requests(mlist, doc) + show_detailed_requests(mlist, form) elif action == 'hold': if sender: - show_sender_requests(mlist, doc, sender) + show_sender_requests(mlist, form, sender) elif msgid: - show_message_requests(mlist, doc, msgid) + show_message_requests(mlist, form, msgid) else: - show_detailed_requests(mlist, doc) + show_detailed_requests(mlist, form) elif action == 'post': if msgid: info = mlist.GetRecord(msgid) if info: total = len(mlist.GetHeldMessageIds()) count = 1 - show_post_requests(mlist, msgid, info, total, count, doc) + show_post_requests(mlist, msgid, info, total, count, form) else: - show_detailed_requests(mlist, doc) + show_detailed_requests(mlist, form) else: # Unknown action, show the overview - show_pending_subs(mlist, doc) - show_pending_unsubs(mlist, doc) - show_helds_overview(mlist, doc) + show_pending_subs(mlist, form) + show_pending_unsubs(mlist, form) + show_helds_overview(mlist, form) + + # Add the form to the document and output + doc.AddItem(form) + return output_success_page(doc) def format_body(body, mcset, lcset): From 2790cb347f5127a9c1bb3a9c21742e270e4afe61 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Wed, 30 Apr 2025 23:28:23 -0400 Subject: [PATCH 302/748] update --- Mailman/Cgi/admindb.py | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/Mailman/Cgi/admindb.py b/Mailman/Cgi/admindb.py index 26774af2..9455694f 100644 --- a/Mailman/Cgi/admindb.py +++ b/Mailman/Cgi/admindb.py @@ -106,6 +106,27 @@ def hacky_radio_buttons(btnname, labels, values, defaults, spacing=3): return btns +def output_error_page(status, title, message, details=None): + print('Status: %s' % status) + print('Content-type: text/html; charset=utf-8\n') + doc = Document() + doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + doc.AddItem(Header(2, _(title))) + doc.AddItem(Bold(_(message))) + if details: + doc.AddItem(Preformatted(Utils.websafe(str(details)))) + doc.AddItem(_('Please contact the site administrator.')) + print(doc.Format()) + return + + +def output_success_page(doc): + print('Status: 200 OK') + print('Content-type: text/html; charset=utf-8\n') + print(doc.Format()) + return + + def main(): try: # Log page load with process identity @@ -117,26 +138,6 @@ def main(): doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) - # Always ensure we have proper content-type headers - def output_error_page(status, title, message, details=None): - print('Status: %s' % status) - print('Content-type: text/html; charset=utf-8\n') - doc = Document() - doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) - doc.AddItem(Header(2, _(title))) - doc.AddItem(Bold(_(message))) - if details: - doc.AddItem(Preformatted(Utils.websafe(str(details)))) - doc.AddItem(_('Please contact the site administrator.')) - print(doc.Format()) - return - - def output_success_page(doc): - print('Status: 200 OK') - print('Content-type: text/html; charset=utf-8\n') - print(doc.Format()) - return - # Parse form data first since we need it for authentication try: if os.environ.get('REQUEST_METHOD') == 'POST': From 818a28f1a2086761d4887ea05ec6b1a45e5bde98 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Wed, 30 Apr 2025 23:30:14 -0400 Subject: [PATCH 303/748] update --- Mailman/Cgi/admindb.py | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/Mailman/Cgi/admindb.py b/Mailman/Cgi/admindb.py index 9455694f..7b401188 100644 --- a/Mailman/Cgi/admindb.py +++ b/Mailman/Cgi/admindb.py @@ -117,7 +117,7 @@ def output_error_page(status, title, message, details=None): doc.AddItem(Preformatted(Utils.websafe(str(details)))) doc.AddItem(_('Please contact the site administrator.')) print(doc.Format()) - return + return doc def output_success_page(doc): @@ -152,13 +152,14 @@ def main(): cgidata = urllib.parse.parse_qs(query_string, keep_blank_values=True) except Exception as e: mailman_log('error', 'admindb: Invalid form data: %s\n%s', str(e), traceback.format_exc()) - return output_error_page('400 Bad Request', 'Error', 'Invalid options to CGI script.') + doc = output_error_page('400 Bad Request', 'Error', 'Invalid options to CGI script.') + return output_success_page(doc) # Get the list name parts = Utils.GetPathPieces() if not parts: - handle_no_list() - return + doc = handle_no_list() + return output_success_page(doc) listname = parts[0].lower() mailman_log('info', 'admindb: Processing list "%s"', listname) @@ -167,30 +168,34 @@ def main(): listdir = os.path.join(mm_cfg.LIST_DATA_DIR, listname) if not os.path.exists(listdir): mailman_log('error', 'admindb: List directory does not exist: %s', listdir) - return output_error_page('404 Not Found', 'Error', + doc = output_error_page('404 Not Found', 'Error', 'No such list %s' % Utils.websafe(listname), 'The list directory does not exist.') + return output_success_page(doc) try: mlist = MailList.MailList(listname, lock=0) except Errors.MMListError as e: mailman_log('error', 'admindb: No such list "%s": %s\n%s', listname, e, traceback.format_exc()) - return output_error_page('404 Not Found', 'Error', + doc = output_error_page('404 Not Found', 'Error', 'No such list %s' % Utils.websafe(listname), 'The list configuration could not be loaded.') + return output_success_page(doc) except PermissionError as e: mailman_log('error', 'admindb: Permission error accessing list "%s": %s\n%s', listname, e, traceback.format_exc()) - return output_error_page('500 Internal Server Error', 'Error', + doc = output_error_page('500 Internal Server Error', 'Error', 'Permission error accessing list %s' % Utils.websafe(listname), str(e)) + return output_success_page(doc) except Exception as e: mailman_log('error', 'admindb: Unexpected error loading list "%s": %s\n%s', listname, str(e), traceback.format_exc()) - return output_error_page('500 Internal Server Error', 'Error', + doc = output_error_page('500 Internal Server Error', 'Error', 'Error accessing list %s' % Utils.websafe(listname), str(e)) + return output_success_page(doc) # Now that we know what list has been requested, all subsequent admin # pages are shown in that list's preferred language. @@ -237,23 +242,26 @@ def sigterm_handler(signum, frame, mlist=mlist): except PermissionError as e: mailman_log('error', 'admindb: Permission error processing form: %s\n%s', str(e), traceback.format_exc()) - return output_error_page('500 Internal Server Error', 'Error', + doc = output_error_page('500 Internal Server Error', 'Error', 'Permission error while processing request', str(e)) + return output_success_page(doc) except Exception as e: mailman_log('error', 'admindb: Error processing form: %s\n%s', str(e), traceback.format_exc()) - return output_error_page('500 Internal Server Error', 'Error', + doc = output_error_page('500 Internal Server Error', 'Error', 'Error processing request', str(e)) + return output_success_page(doc) finally: mlist.Unlock() except Exception as e: mailman_log('error', 'admindb: Unexpected error: %s\n%s', str(e), traceback.format_exc()) - return output_error_page('500 Internal Server Error', 'Error', + doc = output_error_page('500 Internal Server Error', 'Error', 'An unexpected error occurred', str(e)) + return output_success_page(doc) def handle_no_list(msg=''): @@ -271,9 +279,8 @@ def handle_no_list(msg=''): doc.AddItem('
                  ') doc.AddItem(MailmanLogo()) - # Use the centralized output function - return output_error_page('400 Bad Request', header, - _('You must specify a list name. Here is the %s') % link) + # Return the document instead of outputting headers + return doc def show_pending_subs(mlist, form): From 288d8b0f46d89de34de5973704e2afb87261db7b Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Wed, 30 Apr 2025 23:32:50 -0400 Subject: [PATCH 304/748] update --- Mailman/Cgi/admindb.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/Mailman/Cgi/admindb.py b/Mailman/Cgi/admindb.py index 7b401188..6ffccde8 100644 --- a/Mailman/Cgi/admindb.py +++ b/Mailman/Cgi/admindb.py @@ -107,8 +107,6 @@ def hacky_radio_buttons(btnname, labels, values, defaults, spacing=3): def output_error_page(status, title, message, details=None): - print('Status: %s' % status) - print('Content-type: text/html; charset=utf-8\n') doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) doc.AddItem(Header(2, _(title))) @@ -116,7 +114,6 @@ def output_error_page(status, title, message, details=None): if details: doc.AddItem(Preformatted(Utils.websafe(str(details)))) doc.AddItem(_('Please contact the site administrator.')) - print(doc.Format()) return doc From f99f2f51350d9453c00f3816c258fe5aab2114db Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Wed, 30 Apr 2025 23:35:36 -0400 Subject: [PATCH 305/748] update --- Mailman/Handlers/SMTPDirect.py | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/Mailman/Handlers/SMTPDirect.py b/Mailman/Handlers/SMTPDirect.py index 4068fa9b..2600ee92 100644 --- a/Mailman/Handlers/SMTPDirect.py +++ b/Mailman/Handlers/SMTPDirect.py @@ -33,6 +33,7 @@ import smtplib from smtplib import SMTPException from base64 import b64encode +import traceback import Mailman.mm_cfg import Mailman.Utils @@ -77,25 +78,30 @@ def __connect(self): # Use native TLS support self.__conn.starttls() except SMTPException as e: - Mailman.Logging.Syslog.mailman_log('smtp-failure', 'SMTP TLS error: %s', e) + Mailman.Logging.Syslog.mailman_log('smtp-failure', 'SMTP TLS error: %s\n%s', + str(e), traceback.format_exc()) self.quit() raise try: self.__conn.login(Mailman.mm_cfg.SMTP_USER, Mailman.mm_cfg.SMTP_PASSWD) except smtplib.SMTPHeloError as e: - Mailman.Logging.Syslog.mailman_log('smtp-failure', 'SMTP HELO error: %s', e) + Mailman.Logging.Syslog.mailman_log('smtp-failure', 'SMTP HELO error: %s\n%s', + str(e), traceback.format_exc()) self.quit() raise except smtplib.SMTPAuthenticationError as e: - Mailman.Logging.Syslog.mailman_log('smtp-failure', 'SMTP AUTH error: %s', e) + Mailman.Logging.Syslog.mailman_log('smtp-failure', 'SMTP AUTH error: %s\n%s', + str(e), traceback.format_exc()) self.quit() except smtplib.SMTPException as e: Mailman.Logging.Syslog.mailman_log('smtp-failure', - 'SMTP - no suitable authentication method found: %s', e) + 'SMTP - no suitable authentication method found: %s\n%s', + str(e), traceback.format_exc()) self.quit() raise except (socket.error, smtplib.SMTPException) as e: - Mailman.Logging.Syslog.mailman_log('smtp-failure', 'SMTP connection error: %s', e) + Mailman.Logging.Syslog.mailman_log('smtp-failure', 'SMTP connection error: %s\n%s', + str(e), traceback.format_exc()) self.quit() raise @@ -115,9 +121,11 @@ def sendmail(self, envsender, recips, msgtext): if isinstance(envsender, bytes): envsender = envsender.decode('utf-8') results = self.__conn.sendmail(envsender, recips, msgtext) - except smtplib.SMTPException: + except smtplib.SMTPException as e: # For safety, close this connection. The next send attempt will # automatically re-open it. Pass the exception on up. + Mailman.Logging.Syslog.mailman_log('smtp-failure', 'SMTP sendmail error: %s\n%s', + str(e), traceback.format_exc()) self.quit() raise # This session has been successfully completed. @@ -155,7 +163,8 @@ def process(mlist, msg, msgdata): mailman_msg.set_payload(msg.get_payload()) msg = mailman_msg except Exception as e: - Mailman.Logging.Syslog.mailman_log('error', 'Failed to convert message: %s', e) + Mailman.Logging.Syslog.mailman_log('error', 'Failed to convert message: %s\n%s', + str(e), traceback.format_exc()) raise recips = msgdata.get('recips') @@ -220,11 +229,14 @@ def process(mlist, msg, msgdata): msgdata['recips'] = chunk try: deliveryfunc(mlist, msg, msgdata, envsender, refused, conn) - except Exception: + except Exception as e: # If /anything/ goes wrong, push the last chunk back on the # undelivered list and re-raise the exception. We don't know # how many of the last chunk might receive the message, so at # worst, everyone in this chunk will get a duplicate. Sigh. + Mailman.Logging.Syslog.mailman_log('error', + 'Delivery error for chunk: %s\nError: %s\n%s', + chunk, str(e), traceback.format_exc()) chunks.append(chunk) raise del msgdata['undelivered'] @@ -284,10 +296,16 @@ def process(mlist, msg, msgdata): if code >= 500 and code != 552: # A permanent failure permfailures.append(recip) + Mailman.Logging.Syslog.mailman_log('smtp-failure', + 'Permanent delivery failure for %s: code %s, message: %s', + recip, code, smtpmsg) else: # Deal with persistent transient failures by queuing them up for # future delivery. TBD: this could generate lots of log entries! tempfailures.append(recip) + Mailman.Logging.Syslog.mailman_log('smtp-failure', + 'Temporary delivery failure for %s: code %s, message: %s', + recip, code, smtpmsg) if Mailman.mm_cfg.SMTP_LOG_EACH_FAILURE: d.update({'recipient': recip, 'failcode' : code, From b60d0ba71963eadd5c14589fd5f7741d8594c6ea Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Wed, 30 Apr 2025 23:40:52 -0400 Subject: [PATCH 306/748] update --- Mailman/Queue/Switchboard.py | 488 +++++++++++++++++++++-------------- 1 file changed, 292 insertions(+), 196 deletions(-) diff --git a/Mailman/Queue/Switchboard.py b/Mailman/Queue/Switchboard.py index df83c07d..012aed68 100644 --- a/Mailman/Queue/Switchboard.py +++ b/Mailman/Queue/Switchboard.py @@ -185,87 +185,88 @@ def dequeue(self, filebase): # Calculate the filename from the given filebase. filename = os.path.join(self.__whichq, filebase + '.pck') backfile = os.path.join(self.__whichq, filebase + '.bak') + lockfile = filename + '.lock' + # Create lock file try: - # Move the file to the backup file name for processing. If this - # process crashes uncleanly the .bak file will be used to re-instate - # the .pck file in order to try again. - os.rename(filename, backfile) - - # Read the message object and metadata. - with open(backfile, 'rb') as fp: - # Try loading with Python 3 protocol first - try: - msg = pickle.load(fp, fix_imports=True) - data = pickle.load(fp, fix_imports=True) - except (pickle.UnpicklingError, EOFError) as e: - # If that fails, try with Python 2 protocol - fp.seek(0) + lock_fd = os.open(lockfile, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600) + os.close(lock_fd) + except OSError as e: + if e.errno == errno.EEXIST: + mailman_log('warning', 'Lock file exists for %s, waiting...', filename) + # Wait for lock to be released + for _ in range(10): # 10 attempts try: - msg = pickle.load(fp, fix_imports=True, encoding='latin1') - data = pickle.load(fp, fix_imports=True, encoding='latin1') - except (pickle.UnpicklingError, EOFError) as e2: - # The file is corrupted. Log the error and try to recover it. - mailman_log('error', 'Failed to unpickle file %s: Python 3 error: %s, Python 2 error: %s', - backfile, str(e), str(e2)) - self.recover_backup_files() - return None, None - - # Convert any bytes in the loaded data to strings - if isinstance(data, dict): - for key, value in list(data.items()): - if isinstance(key, bytes): - del data[key] - key = key.decode('utf-8', 'replace') - data[key] = value - if isinstance(value, bytes): - data[key] = value.decode('utf-8', 'replace') - elif isinstance(value, list): - data[key] = [ - v.decode('utf-8', 'replace') if isinstance(v, bytes) else v - for v in value - ] - elif isinstance(value, dict): - new_dict = {} - for k, v in value.items(): - if isinstance(k, bytes): - k = k.decode('utf-8', 'replace') - if isinstance(v, bytes): - v = v.decode('utf-8', 'replace') - new_dict[k] = v - data[key] = new_dict - - # Always try to parse the message if it's a string - if isinstance(msg, str) or data.get('_parsemsg'): - try: - msg = email.message_from_string(msg, EmailMessage) - # Convert to Mailman.Message if needed - if isinstance(msg, email.message.Message) and not isinstance(msg, Message.Message): - mailman_msg = Message.Message() - # Copy all attributes from the original message - for key, value in msg.items(): - mailman_msg[key] = value - # Copy the payload - if msg.is_multipart(): - for part in msg.get_payload(): - mailman_msg.attach(part) - else: - mailman_msg.set_payload(msg.get_payload()) - msg = mailman_msg - except Exception as e: - mailman_log('error', 'Failed to parse message from file %s: %s', backfile, str(e)) + lock_fd = os.open(lockfile, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600) + os.close(lock_fd) + break + except OSError: + time.sleep(0.1) + else: + mailman_log('error', 'Could not acquire lock for %s after 10 attempts', filename) return None, None - + else: + mailman_log('error', 'Failed to create lock file %s: %s\nTraceback:\n%s', + lockfile, str(e), traceback.format_exc()) + return None, None + + try: + # Move the file to the backup file name for processing + try: + if not os.path.exists(filename): + mailman_log('error', 'Source file %s does not exist', filename) + return None, None + if os.path.exists(backfile): + mailman_log('warning', 'Backup file %s already exists, removing old version', backfile) + os.unlink(backfile) + os.rename(filename, backfile) + except OSError as e: + mailman_log('error', 'Failed to rename %s to %s: %s\nTraceback:\n%s', + filename, backfile, str(e), traceback.format_exc()) + return None, None + + # Read the message object and metadata + try: + with open(backfile, 'rb') as fp: + # Try loading with Python 3 protocol first + try: + msg = pickle.load(fp, fix_imports=True) + data = pickle.load(fp, fix_imports=True) + except (pickle.UnpicklingError, EOFError) as e: + # If that fails, try with Python 2 protocol + fp.seek(0) + try: + msg = pickle.load(fp, fix_imports=True, encoding='latin1') + data = pickle.load(fp, fix_imports=True, encoding='latin1') + except (pickle.UnpicklingError, EOFError) as e2: + # The file is corrupted. Log the error and try to recover it. + mailman_log('error', 'Failed to unpickle file %s:\nPython 3 error: %s\nPython 2 error: %s\nTraceback:\n%s', + backfile, str(e), str(e2), traceback.format_exc()) + # Try to restore the original file + try: + if os.path.exists(filename): + os.unlink(filename) + os.rename(backfile, filename) + except Exception as restore_e: + mailman_log('error', 'Failed to restore original file %s: %s\nTraceback:\n%s', + filename, str(restore_e), traceback.format_exc()) + self.recover_backup_files() + return None, None + except Exception as e: + mailman_log('error', 'Failed to read backup file %s: %s\nTraceback:\n%s', + backfile, str(e), traceback.format_exc()) + return None, None + return msg, data - except EnvironmentError as e: - if e.errno != errno.ENOENT: - mailman_log('error', 'Environment error processing file %s: %s', backfile, str(e)) - raise - return None, None - except Exception as e: - mailman_log('error', 'Unexpected error processing file %s: %s', backfile, str(e)) - return None, None + finally: + # Clean up lock file + try: + if os.path.exists(lockfile): + os.unlink(lockfile) + except Exception as cleanup_e: + mailman_log('error', 'Failed to clean up lock file %s: %s\nTraceback:\n%s', + lockfile, str(cleanup_e), traceback.format_exc()) def finish(self, filebase, preserve=False): bakfile = os.path.join(self.__whichq, filebase + '.bak') @@ -279,7 +280,10 @@ def finish(self, filebase, preserve=False): try: os.mkdir(mm_cfg.BADQUEUE_DIR, 0o0770) except OSError as e: - if e.errno != errno.EEXIST: raise + if e.errno != errno.EEXIST: + mailman_log('error', 'Failed to create shunt queue directory %s: %s\nTraceback:\n%s', + mm_cfg.BADQUEUE_DIR, str(e), traceback.format_exc()) + raise finally: os.umask(omask) @@ -289,47 +293,68 @@ def finish(self, filebase, preserve=False): return # Move the file and verify - os.rename(bakfile, psvfile) - if not os.path.exists(psvfile): - mailman_log('error', 'Failed to move backup file to shunt queue: %s -> %s', - bakfile, psvfile) - else: - mailman_log('info', 'Successfully moved backup file to shunt queue: %s -> %s', - bakfile, psvfile) + try: + os.rename(bakfile, psvfile) + if not os.path.exists(psvfile): + mailman_log('error', 'Failed to move backup file to shunt queue: %s -> %s', + bakfile, psvfile) + else: + mailman_log('info', 'Successfully moved backup file to shunt queue: %s -> %s', + bakfile, psvfile) + except OSError as e: + mailman_log('error', 'Failed to move backup file to shunt queue: %s -> %s: %s\nTraceback:\n%s', + bakfile, psvfile, str(e), traceback.format_exc()) + raise else: # Verify file exists before unlinking if not os.path.exists(bakfile): mailman_log('error', 'Backup file does not exist for unlinking: %s', bakfile) return - os.unlink(bakfile) - if os.path.exists(bakfile): - mailman_log('error', 'Failed to unlink backup file: %s', bakfile) - else: - mailman_log('info', 'Successfully unlinked backup file: %s', bakfile) - except EnvironmentError as e: - mailman_log('error', 'Failed to unlink/preserve backup file: %s\n%s', - bakfile, e) + try: + os.unlink(bakfile) + if os.path.exists(bakfile): + mailman_log('error', 'Failed to unlink backup file: %s', bakfile) + else: + mailman_log('info', 'Successfully unlinked backup file: %s', bakfile) + except OSError as e: + mailman_log('error', 'Failed to unlink backup file %s: %s\nTraceback:\n%s', + bakfile, str(e), traceback.format_exc()) + raise + except Exception as e: + mailman_log('error', 'Failed to finish processing backup file %s: %s\nTraceback:\n%s', + bakfile, str(e), traceback.format_exc()) + raise def files(self, extension='.pck'): times = {} lower = self.__lower upper = self.__upper - for f in os.listdir(self.__whichq): - # By ignoring anything that doesn't end in .pck, we ignore - # tempfiles and avoid a race condition. - filebase, ext = os.path.splitext(f) - if ext != extension: - continue - when, digest = filebase.split('+') - # Throw out any files which don't match our bitrange. BAW: test - # performance and end-cases of this algorithm. MAS: both - # comparisons need to be <= to get complete range. - if lower is None or (lower <= int(digest, 16) <= upper): - key = float(when) - while key in times: - key += DELTA - times[key] = filebase + try: + for f in os.listdir(self.__whichq): + # By ignoring anything that doesn't end in .pck, we ignore + # tempfiles and avoid a race condition. + filebase, ext = os.path.splitext(f) + if ext != extension: + continue + try: + when, digest = filebase.split('+') + # Throw out any files which don't match our bitrange. BAW: test + # performance and end-cases of this algorithm. MAS: both + # comparisons need to be <= to get complete range. + if lower is None or (lower <= int(digest, 16) <= upper): + key = float(when) + while key in times: + key += DELTA + times[key] = filebase + except ValueError as e: + mailman_log('error', 'Invalid file name format in queue directory: %s: %s\nTraceback:\n%s', + f, str(e), traceback.format_exc()) + continue + except OSError as e: + mailman_log('error', 'Failed to list queue directory %s: %s\nTraceback:\n%s', + self.__whichq, str(e), traceback.format_exc()) + raise # FIFO sort keys = list(times.keys()) keys.sort() @@ -342,83 +367,115 @@ def recover_backup_files(self): # _bak_count in the metadata of the number of times we recover this # file. When the count reaches MAX_BAK_COUNT, we move the .bak file # to a .psv file in the shunt queue. - for filebase in self.files('.bak'): - src = os.path.join(self.__whichq, filebase + '.bak') - dst = os.path.join(self.__whichq, filebase + '.pck') - fp = open(src, 'rb+') - try: + try: + for filebase in self.files('.bak'): + src = os.path.join(self.__whichq, filebase + '.bak') + dst = os.path.join(self.__whichq, filebase + '.pck') + + # First check if the file is too old try: - msg = pickle.load(fp, fix_imports=True, encoding='latin1') - data_pos = fp.tell() - data = pickle.load(fp, fix_imports=True, encoding='latin1') - except Exception as s: - # If unpickling throws any exception, just log and - # preserve this entry - tb = traceback.format_exc() - mailman_log('error', 'Unpickling .bak exception: %s\n' - + 'Traceback:\n%s\n' - + 'preserving file: %s', s, tb, filebase) - self.finish(filebase, preserve=True) - else: - data['_bak_count'] = data.setdefault('_bak_count', 0) + 1 - data['_last_attempt'] = time.time() - if '_error_history' not in data: - data['_error_history'] = [] - if '_traceback' in data: - data['_error_history'].append({ - 'error': data.get('_last_error', 'unknown'), - 'traceback': data.get('_traceback', 'none'), - 'time': data.get('_last_attempt', 0) - }) - fp.seek(data_pos) - if data.get('_parsemsg'): - protocol = 0 - else: - protocol = 1 - pickle.dump(data, fp, protocol=2, fix_imports=True) - fp.truncate() - fp.flush() - os.fsync(fp.fileno()) - - # Log detailed information about the retry - mailman_log('warning', - 'Message retry attempt %d/%d: %s (queue: %s, ' - 'message-id: %s, listname: %s, recipients: %s, ' - 'error: %s, last attempt: %s, traceback: %s)', - data['_bak_count'], - MAX_BAK_COUNT, - filebase, - self.__whichq, - data.get('message-id', 'unknown'), - data.get('listname', 'unknown'), - data.get('recips', 'unknown'), - data.get('_last_error', 'unknown'), - time.ctime(data.get('_last_attempt', 0)), - data.get('_traceback', 'none')) + file_age = time.time() - os.path.getmtime(src) + if file_age > mm_cfg.QUEUE_LIFETIME: + mailman_log('warning', + 'Backup file %s is too old (%d seconds), moving to shunt queue', + filebase, file_age) + self.finish(filebase, preserve=True) + continue + except OSError as e: + mailman_log('error', 'Error checking file age for %s: %s\nTraceback:\n%s', + filebase, str(e), traceback.format_exc()) + continue - if data['_bak_count'] >= MAX_BAK_COUNT: - mailman_log('error', - 'Backup file exceeded maximum retry count (%d). ' - 'Moving to shunt queue: %s (original queue: %s, ' - 'retry count: %d, last error: %s, ' - 'message-id: %s, listname: %s, ' - 'recipients: %s, error history: %s, ' - 'last traceback: %s)', + try: + fp = open(src, 'rb+') + try: + try: + msg = pickle.load(fp, fix_imports=True, encoding='latin1') + data_pos = fp.tell() + data = pickle.load(fp, fix_imports=True, encoding='latin1') + except Exception as s: + # If unpickling throws any exception, just log and + # preserve this entry + tb = traceback.format_exc() + mailman_log('error', 'Unpickling .bak exception: %s\n' + + 'Traceback:\n%s\n' + + 'preserving file: %s', s, tb, filebase) + self.finish(filebase, preserve=True) + continue + + data['_bak_count'] = data.setdefault('_bak_count', 0) + 1 + data['_last_attempt'] = time.time() + if '_error_history' not in data: + data['_error_history'] = [] + if '_traceback' in data: + data['_error_history'].append({ + 'error': data.get('_last_error', 'unknown'), + 'traceback': data.get('_traceback', 'none'), + 'time': data.get('_last_attempt', 0) + }) + + fp.seek(data_pos) + if data.get('_parsemsg'): + protocol = 0 + else: + protocol = 1 + pickle.dump(data, fp, protocol=2, fix_imports=True) + fp.truncate() + fp.flush() + os.fsync(fp.fileno()) + + # Log detailed information about the retry + mailman_log('warning', + 'Message retry attempt %d/%d: %s (queue: %s, ' + 'message-id: %s, listname: %s, recipients: %s, ' + 'error: %s, last attempt: %s, traceback: %s)', + data['_bak_count'], MAX_BAK_COUNT, filebase, self.__whichq, - data['_bak_count'], - data.get('_last_error', 'unknown'), data.get('message-id', 'unknown'), data.get('listname', 'unknown'), data.get('recips', 'unknown'), - data.get('_error_history', 'unknown'), + data.get('_last_error', 'unknown'), + time.ctime(data.get('_last_attempt', 0)), data.get('_traceback', 'none')) - self.finish(filebase, preserve=True) - else: - os.rename(src, dst) - finally: - fp.close() + + if data['_bak_count'] >= MAX_BAK_COUNT: + mailman_log('error', + 'Backup file exceeded maximum retry count (%d). ' + 'Moving to shunt queue: %s (original queue: %s, ' + 'retry count: %d, last error: %s, ' + 'message-id: %s, listname: %s, ' + 'recipients: %s, error history: %s, ' + 'last traceback: %s)', + MAX_BAK_COUNT, + filebase, + self.__whichq, + data['_bak_count'], + data.get('_last_error', 'unknown'), + data.get('message-id', 'unknown'), + data.get('listname', 'unknown'), + data.get('recips', 'unknown'), + data.get('_error_history', 'unknown'), + data.get('_traceback', 'none')) + self.finish(filebase, preserve=True) + else: + try: + os.rename(src, dst) + except OSError as e: + mailman_log('error', 'Failed to rename backup file %s: %s\nTraceback:\n%s', + filebase, str(e), traceback.format_exc()) + self.finish(filebase, preserve=True) + finally: + fp.close() + except Exception as e: + mailman_log('error', 'Failed to process backup file %s: %s\nTraceback:\n%s', + filebase, str(e), traceback.format_exc()) + continue + except Exception as e: + mailman_log('error', 'Failed to recover backup files: %s\nTraceback:\n%s', + str(e), traceback.format_exc()) + raise def _enqueue(self, msg, metadata=None, _recips=None): """Enqueue a message for delivery. @@ -428,6 +485,7 @@ def _enqueue(self, msg, metadata=None, _recips=None): 2. Atomic write 3. Validation of written data 4. Proper error handling and cleanup + 5. File locking for concurrent access """ # Create a unique filename now = time.time() @@ -437,9 +495,30 @@ def _enqueue(self, msg, metadata=None, _recips=None): hashbase = hashlib.md5((hashbase + msginfo).encode()).hexdigest() qfile = os.path.join(self.__whichq, hashbase + '.pck') tmpfile = qfile + '.tmp.%s.%d' % (socket.gethostname(), os.getpid()) + lockfile = qfile + '.lock' - # Prepare the data to be pickled - data = (msg, metadata or {}, _recips) + # Create lock file + try: + lock_fd = os.open(lockfile, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600) + os.close(lock_fd) + except OSError as e: + if e.errno == errno.EEXIST: + mailman_log('warning', 'Lock file exists for %s, waiting...', qfile) + # Wait for lock to be released + for _ in range(10): # 10 attempts + try: + lock_fd = os.open(lockfile, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600) + os.close(lock_fd) + break + except OSError: + time.sleep(0.1) + else: + mailman_log('error', 'Could not acquire lock for %s after 10 attempts', qfile) + raise + else: + mailman_log('error', 'Failed to create lock file %s: %s\nTraceback:\n%s', + lockfile, str(e), traceback.format_exc()) + raise try: # Ensure directory exists with proper permissions @@ -448,15 +527,21 @@ def _enqueue(self, msg, metadata=None, _recips=None): try: os.makedirs(dirname, 0o755) except Exception as e: - mailman_log('error', 'Failed to create directory %s: %s', dirname, e) + mailman_log('error', 'Failed to create directory %s: %s\nTraceback:\n%s', + dirname, str(e), traceback.format_exc()) raise # Write to temporary file first - with open(tmpfile, 'wb') as fp: - pickle.dump(data, fp, protocol=2, fix_imports=True) - fp.flush() - if hasattr(os, 'fsync'): - os.fsync(fp.fileno()) + try: + with open(tmpfile, 'wb') as fp: + pickle.dump(data, fp, protocol=2, fix_imports=True) + fp.flush() + if hasattr(os, 'fsync'): + os.fsync(fp.fileno()) + except Exception as e: + mailman_log('error', 'Failed to write temporary file %s: %s\nTraceback:\n%s', + tmpfile, str(e), traceback.format_exc()) + raise # Validate the temporary file try: @@ -465,41 +550,52 @@ def _enqueue(self, msg, metadata=None, _recips=None): if not isinstance(test_data, tuple) or len(test_data) != 3: raise TypeError('Loaded data is not a valid tuple') except Exception as e: - mailman_log('error', 'Validation of temporary file failed: %s', e) + mailman_log('error', 'Validation of temporary file failed: %s\nTraceback:\n%s', + str(e), traceback.format_exc()) # Try to clean up try: os.unlink(tmpfile) - except Exception: - pass + except Exception as cleanup_e: + mailman_log('error', 'Failed to clean up temporary file %s: %s\nTraceback:\n%s', + tmpfile, str(cleanup_e), traceback.format_exc()) raise - # Atomic rename + # Atomic rename with existence check try: + if os.path.exists(qfile): + mailman_log('warning', 'Target file %s already exists, removing old version', qfile) + os.unlink(qfile) os.rename(tmpfile, qfile) except Exception as e: - mailman_log('error', 'Failed to rename %s to %s: %s', tmpfile, qfile, e) + mailman_log('error', 'Failed to rename %s to %s: %s\nTraceback:\n%s', + tmpfile, qfile, str(e), traceback.format_exc()) # Try to clean up try: - os.unlink(tmpfile) - except Exception: - pass + if os.path.exists(tmpfile): + os.unlink(tmpfile) + except Exception as cleanup_e: + mailman_log('error', 'Failed to clean up temporary file %s: %s\nTraceback:\n%s', + tmpfile, str(cleanup_e), traceback.format_exc()) raise # Set proper permissions try: os.chmod(qfile, 0o660) except Exception as e: - mailman_log('warning', 'Failed to set permissions on %s: %s', qfile, e) + mailman_log('warning', 'Failed to set permissions on %s: %s\nTraceback:\n%s', + qfile, str(e), traceback.format_exc()) # Not critical, continue - except Exception: - # Clean up any temporary files + finally: + # Clean up any temporary files and lock try: if os.path.exists(tmpfile): os.unlink(tmpfile) - except Exception: - pass - raise + if os.path.exists(lockfile): + os.unlink(lockfile) + except Exception as cleanup_e: + mailman_log('error', 'Failed to clean up temporary/lock files: %s\nTraceback:\n%s', + str(cleanup_e), traceback.format_exc()) def _dequeue(self, filename): """Dequeue a message from the queue.""" From a960ee4a3235d266db484e55faeaa8d9524708d0 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Wed, 30 Apr 2025 23:45:40 -0400 Subject: [PATCH 307/748] update --- Mailman/Queue/Switchboard.py | 60 +++++++++++++++++++++++++++++++++--- 1 file changed, 56 insertions(+), 4 deletions(-) diff --git a/Mailman/Queue/Switchboard.py b/Mailman/Queue/Switchboard.py index 012aed68..f00d5557 100644 --- a/Mailman/Queue/Switchboard.py +++ b/Mailman/Queue/Switchboard.py @@ -338,7 +338,59 @@ def files(self, extension='.pck'): if ext != extension: continue try: - when, digest = filebase.split('+') + # Validate file name format + if '+' not in filebase: + mailman_log('warning', 'Invalid file name format in queue directory (missing +): %s', f) + # Try to recover by moving to shunt queue + try: + src = os.path.join(self.__whichq, f) + dst = os.path.join(mm_cfg.BADQUEUE_DIR, filebase + '.psv') + if not os.path.exists(mm_cfg.BADQUEUE_DIR): + os.makedirs(mm_cfg.BADQUEUE_DIR, 0o770) + os.rename(src, dst) + mailman_log('info', 'Moved invalid file to shunt queue: %s -> %s', f, dst) + except Exception as e: + mailman_log('error', 'Failed to move invalid file %s to shunt queue: %s\nTraceback:\n%s', + f, str(e), traceback.format_exc()) + continue + + parts = filebase.split('+') + if len(parts) != 2: + mailman_log('warning', 'Invalid file name format in queue directory (wrong number of parts): %s', f) + # Try to recover by moving to shunt queue + try: + src = os.path.join(self.__whichq, f) + dst = os.path.join(mm_cfg.BADQUEUE_DIR, filebase + '.psv') + if not os.path.exists(mm_cfg.BADQUEUE_DIR): + os.makedirs(mm_cfg.BADQUEUE_DIR, 0o770) + os.rename(src, dst) + mailman_log('info', 'Moved invalid file to shunt queue: %s -> %s', f, dst) + except Exception as e: + mailman_log('error', 'Failed to move invalid file %s to shunt queue: %s\nTraceback:\n%s', + f, str(e), traceback.format_exc()) + continue + + when, digest = parts + try: + # Validate timestamp format + float(when) + # Validate digest format (should be hex) + int(digest, 16) + except ValueError as e: + mailman_log('warning', 'Invalid file name format in queue directory (invalid timestamp/digest): %s: %s', f, str(e)) + # Try to recover by moving to shunt queue + try: + src = os.path.join(self.__whichq, f) + dst = os.path.join(mm_cfg.BADQUEUE_DIR, filebase + '.psv') + if not os.path.exists(mm_cfg.BADQUEUE_DIR): + os.makedirs(mm_cfg.BADQUEUE_DIR, 0o770) + os.rename(src, dst) + mailman_log('info', 'Moved invalid file to shunt queue: %s -> %s', f, dst) + except Exception as e: + mailman_log('error', 'Failed to move invalid file %s to shunt queue: %s\nTraceback:\n%s', + f, str(e), traceback.format_exc()) + continue + # Throw out any files which don't match our bitrange. BAW: test # performance and end-cases of this algorithm. MAS: both # comparisons need to be <= to get complete range. @@ -347,8 +399,8 @@ def files(self, extension='.pck'): while key in times: key += DELTA times[key] = filebase - except ValueError as e: - mailman_log('error', 'Invalid file name format in queue directory: %s: %s\nTraceback:\n%s', + except Exception as e: + mailman_log('error', 'Unexpected error processing file %s: %s\nTraceback:\n%s', f, str(e), traceback.format_exc()) continue except OSError as e: @@ -375,7 +427,7 @@ def recover_backup_files(self): # First check if the file is too old try: file_age = time.time() - os.path.getmtime(src) - if file_age > mm_cfg.QUEUE_LIFETIME: + if file_age > mm_cfg.FORM_LIFETIME: mailman_log('warning', 'Backup file %s is too old (%d seconds), moving to shunt queue', filebase, file_age) From 9efa9637ae8b8620f596f2f324dda9282c2f7d1c Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Wed, 30 Apr 2025 23:48:07 -0400 Subject: [PATCH 308/748] update --- Mailman/Queue/Switchboard.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Mailman/Queue/Switchboard.py b/Mailman/Queue/Switchboard.py index f00d5557..7d9506e7 100644 --- a/Mailman/Queue/Switchboard.py +++ b/Mailman/Queue/Switchboard.py @@ -252,6 +252,27 @@ def dequeue(self, filebase): filename, str(restore_e), traceback.format_exc()) self.recover_backup_files() return None, None + + # Validate message object + if isinstance(msg, str): + mailman_log('error', 'Message object is a string instead of a proper message object in file %s', backfile) + # Try to convert string to proper message object + try: + from email import message_from_string + msg = message_from_string(msg) + except Exception as e: + mailman_log('error', 'Failed to convert string to message object: %s\nTraceback:\n%s', + str(e), traceback.format_exc()) + # Move to shunt queue + self.finish(filebase, preserve=True) + return None, None + + if not hasattr(msg, 'get'): + mailman_log('error', 'Message object does not have get() method in file %s', backfile) + # Move to shunt queue + self.finish(filebase, preserve=True) + return None, None + except Exception as e: mailman_log('error', 'Failed to read backup file %s: %s\nTraceback:\n%s', backfile, str(e), traceback.format_exc()) From 3c3c6b746cbb2efec461b20f474486e8e17c08c3 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 1 May 2025 00:02:29 -0400 Subject: [PATCH 309/748] update --- Mailman/Bouncer.py | 2 +- Mailman/Bouncers/Qmail.py | 2 +- Mailman/Cgi/admin.py | 2 +- Mailman/Cgi/admindb.py | 2 +- Mailman/Cgi/create.py | 2 +- Mailman/Cgi/subscribe.py | 2 +- Mailman/Deliverer.py | 2 +- Mailman/Handlers/Acknowledge.py | 3 +- Mailman/Handlers/CalcRecips.py | 2 +- Mailman/Handlers/Decorate.py | 2 +- Mailman/Handlers/Hold.py | 2 +- Mailman/Handlers/Moderate.py | 2 +- Mailman/Handlers/Replybot.py | 3 +- Mailman/Handlers/SMTPDirect.py | 10 ++-- Mailman/Handlers/Scrubber.py | 2 +- Mailman/Handlers/SpamDetect.py | 1 + Mailman/Handlers/ToDigest.py | 6 +-- Mailman/ListAdmin.py | 2 +- Mailman/MTA/Manual.py | 2 +- Mailman/MailList.py | 2 +- Mailman/Mailbox.py | 4 +- Mailman/Queue/BounceRunner.py | 6 +++ Mailman/Queue/CommandRunner.py | 8 ++- Mailman/Queue/IncomingRunner.py | 31 ++++++++++++ Mailman/Queue/MaildirRunner.py | 13 +++-- Mailman/Queue/NewsRunner.py | 9 +++- Mailman/Queue/OutgoingRunner.py | 32 +++++------- Mailman/Queue/RetryRunner.py | 6 +++ Mailman/Queue/Runner.py | 87 +++++++++++++++++++++++++-------- Mailman/Queue/Switchboard.py | 28 +++++++++-- Mailman/versions.py | 2 +- bin/add_members | 2 +- bin/change_pw | 2 +- bin/newlist | 2 +- bin/update | 4 +- cron/checkdbs | 2 +- cron/gate_news | 3 +- cron/mailpasswds | 2 +- tests/test_handlers.py | 2 +- tests/test_message.py | 2 +- 40 files changed, 211 insertions(+), 89 deletions(-) diff --git a/Mailman/Bouncer.py b/Mailman/Bouncer.py index ee932ab4..8915f619 100644 --- a/Mailman/Bouncer.py +++ b/Mailman/Bouncer.py @@ -26,7 +26,7 @@ from Mailman import mm_cfg from Mailman import Utils -from Mailman import Message +from Mailman.Message import Message from Mailman import MemberAdaptor from Mailman import Pending from Mailman.Errors import MMUnknownListError diff --git a/Mailman/Bouncers/Qmail.py b/Mailman/Bouncers/Qmail.py index 9baa7d6c..b0c5215d 100644 --- a/Mailman/Bouncers/Qmail.py +++ b/Mailman/Bouncers/Qmail.py @@ -35,7 +35,7 @@ from Mailman import mm_cfg from Mailman import Utils -from Mailman import Message +from Mailman.Message import Message from Mailman import Errors from Mailman import i18n from Mailman.Logging.Syslog import syslog diff --git a/Mailman/Cgi/admin.py b/Mailman/Cgi/admin.py index 6e738e77..53a17c8d 100644 --- a/Mailman/Cgi/admin.py +++ b/Mailman/Cgi/admin.py @@ -31,7 +31,7 @@ def cmp(a, b): from Mailman import mm_cfg from Mailman import Utils -from Mailman import Message +from Mailman.Message import Message from Mailman import MailList from Mailman import Errors from Mailman import MemberAdaptor diff --git a/Mailman/Cgi/admindb.py b/Mailman/Cgi/admindb.py index 6ffccde8..06559869 100644 --- a/Mailman/Cgi/admindb.py +++ b/Mailman/Cgi/admindb.py @@ -36,7 +36,7 @@ from Mailman import Utils from Mailman import MailList from Mailman import Errors -from Mailman import Message +from Mailman.Message import Message from Mailman import i18n from Mailman.Handlers.Moderate import ModeratedMemberPost from Mailman.ListAdmin import HELDMSG, ListAdmin, PermissionError diff --git a/Mailman/Cgi/create.py b/Mailman/Cgi/create.py index b2ffe7a4..807dd2bc 100644 --- a/Mailman/Cgi/create.py +++ b/Mailman/Cgi/create.py @@ -26,7 +26,7 @@ from Mailman import mm_cfg from Mailman import MailList -from Mailman import Message +from Mailman.Message import Message from Mailman import Errors from Mailman import i18n from Mailman.htmlformat import * diff --git a/Mailman/Cgi/subscribe.py b/Mailman/Cgi/subscribe.py index d906e435..9196b7b7 100644 --- a/Mailman/Cgi/subscribe.py +++ b/Mailman/Cgi/subscribe.py @@ -31,7 +31,7 @@ from Mailman import MailList from Mailman import Errors from Mailman import i18n -from Mailman import Message +from Mailman.Message import Message from Mailman.UserDesc import UserDesc from Mailman.htmlformat import * from Mailman.Logging.Syslog import mailman_log diff --git a/Mailman/Deliverer.py b/Mailman/Deliverer.py index 81d53715..10d2690b 100644 --- a/Mailman/Deliverer.py +++ b/Mailman/Deliverer.py @@ -26,7 +26,7 @@ from Mailman import mm_cfg from Mailman import Errors from Mailman import Utils -from Mailman import Message +from Mailman.Message import Message from Mailman import i18n from Mailman import Pending from Mailman.Logging.Syslog import syslog diff --git a/Mailman/Handlers/Acknowledge.py b/Mailman/Handlers/Acknowledge.py index 59e508cf..ba8b1736 100644 --- a/Mailman/Handlers/Acknowledge.py +++ b/Mailman/Handlers/Acknowledge.py @@ -24,12 +24,11 @@ from Mailman import mm_cfg from Mailman import Utils -from Mailman import Message +from Mailman.Message import Message from Mailman import Errors from Mailman.i18n import _ - def process(mlist, msg, msgdata): # Extract the sender's address and find them in the user database sender = msgdata.get('original_sender', msg.get_sender()) diff --git a/Mailman/Handlers/CalcRecips.py b/Mailman/Handlers/CalcRecips.py index 76e367bb..4f59661c 100644 --- a/Mailman/Handlers/CalcRecips.py +++ b/Mailman/Handlers/CalcRecips.py @@ -26,7 +26,7 @@ import email.utils from Mailman import mm_cfg from Mailman import Utils -from Mailman import Message +from Mailman.Message import Message from Mailman import Errors from Mailman.MemberAdaptor import ENABLED # Remove the MailList import from here since it's causing a circular dependency diff --git a/Mailman/Handlers/Decorate.py b/Mailman/Handlers/Decorate.py index f16b491d..328c79d6 100644 --- a/Mailman/Handlers/Decorate.py +++ b/Mailman/Handlers/Decorate.py @@ -25,7 +25,7 @@ from Mailman import mm_cfg from Mailman import Utils from Mailman import Errors -import Mailman.Message +from Mailman.Message import Message from Mailman.i18n import _ from Mailman.SafeDict import SafeDict from Mailman.Logging.Syslog import syslog diff --git a/Mailman/Handlers/Hold.py b/Mailman/Handlers/Hold.py index fd061cc2..b26cf1aa 100644 --- a/Mailman/Handlers/Hold.py +++ b/Mailman/Handlers/Hold.py @@ -38,7 +38,7 @@ from Mailman import mm_cfg from Mailman import Utils from Mailman import Errors -from Mailman import Message +from Mailman.Message import Message from Mailman import i18n from Mailman import Pending from Mailman.Logging.Syslog import syslog diff --git a/Mailman/Handlers/Moderate.py b/Mailman/Handlers/Moderate.py index 7c7ed2a3..36cad7d8 100644 --- a/Mailman/Handlers/Moderate.py +++ b/Mailman/Handlers/Moderate.py @@ -25,7 +25,7 @@ from Mailman import mm_cfg from Mailman import Utils -from Mailman import Message +from Mailman.Message import Message from Mailman import Errors from Mailman.i18n import _ from Mailman.Handlers import Hold diff --git a/Mailman/Handlers/Replybot.py b/Mailman/Handlers/Replybot.py index 60bc4df3..a1489f54 100644 --- a/Mailman/Handlers/Replybot.py +++ b/Mailman/Handlers/Replybot.py @@ -20,13 +20,12 @@ import time from Mailman import Utils -from Mailman import Message +from Mailman.Message import Message from Mailman.i18n import _ from Mailman.SafeDict import SafeDict from Mailman.Logging.Syslog import syslog - def process(mlist, msg, msgdata): # Normally, the replybot should get a shot at this message, but there are # some important short-circuits, mostly to suppress 'bot storms, at least diff --git a/Mailman/Handlers/SMTPDirect.py b/Mailman/Handlers/SMTPDirect.py index 2600ee92..2c7574bb 100644 --- a/Mailman/Handlers/SMTPDirect.py +++ b/Mailman/Handlers/SMTPDirect.py @@ -38,7 +38,7 @@ import Mailman.mm_cfg import Mailman.Utils import Mailman.Errors -import Mailman.Message +from Mailman.Message import Message import Mailman.Handlers.Decorate import Mailman.Logging.Syslog import Mailman.SafeDict @@ -149,9 +149,9 @@ def quit(self): def process(mlist, msg, msgdata): # Convert email.message.Message to Mailman.Message if needed - if isinstance(msg, email.message.Message) and not isinstance(msg, Mailman.Message.Message): + if isinstance(msg, email.message.Message) and not isinstance(msg, Message): try: - mailman_msg = Mailman.Message.Message() + mailman_msg = Message() # Copy all attributes from the original message for key, value in msg.items(): mailman_msg[key] = value @@ -445,8 +445,8 @@ def verpdeliver(mlist, msg, msgdata, envsender, failures, conn): def bulkdeliver(mlist, msg, msgdata, envsender, failures, conn): # Convert email.message.Message to Mailman.Message if needed - if isinstance(msg, email.message.Message) and not isinstance(msg, Mailman.Message.Message): - mailman_msg = Mailman.Message.Message() + if isinstance(msg, email.message.Message) and not isinstance(msg, Message): + mailman_msg = Message() # Copy all attributes from the original message for key, value in msg.items(): mailman_msg[key] = value diff --git a/Mailman/Handlers/Scrubber.py b/Mailman/Handlers/Scrubber.py index a66c3d59..27b80193 100644 --- a/Mailman/Handlers/Scrubber.py +++ b/Mailman/Handlers/Scrubber.py @@ -35,7 +35,7 @@ from Mailman import mm_cfg from Mailman import Utils from Mailman import LockFile -from Mailman import Message +from Mailman.Message import Message from Mailman.Errors import DiscardMessage from Mailman.i18n import _ from Mailman.Logging.Syslog import syslog diff --git a/Mailman/Handlers/SpamDetect.py b/Mailman/Handlers/SpamDetect.py index 51ca7c84..7aa5f0fa 100644 --- a/Mailman/Handlers/SpamDetect.py +++ b/Mailman/Handlers/SpamDetect.py @@ -39,6 +39,7 @@ from Mailman import Utils from Mailman.Handlers.Hold import hold_for_approval from Mailman.Logging.Syslog import syslog +from Mailman.Message import Message # First, play footsie with _ so that the following are marked as translated, # but aren't actually translated until we need the text later on. diff --git a/Mailman/Handlers/ToDigest.py b/Mailman/Handlers/ToDigest.py index 15f14a1e..bb2b0449 100644 --- a/Mailman/Handlers/ToDigest.py +++ b/Mailman/Handlers/ToDigest.py @@ -48,7 +48,7 @@ from Mailman import Utils from Mailman import i18n from Mailman import Errors -import Mailman.Message +from Mailman.Message import Message from Mailman.Mailbox import Mailbox from Mailman.MemberAdaptor import ENABLED from Mailman.Handlers.Decorate import decorate @@ -264,7 +264,7 @@ def send_digests(mlist, mboxpath): # Process the previous message msg_str = ''.join(current_msg) try: - msg = email.message_from_string(msg_str, Mailman.Message.Message) + msg = email.message_from_string(msg_str, Message) if msg is None: continue @@ -312,7 +312,7 @@ def send_digests(mlist, mboxpath): if current_msg: msg_str = ''.join(current_msg) try: - msg = email.message_from_string(msg_str, Mailman.Message.Message) + msg = email.message_from_string(msg_str, Message) if msg is not None: # Process the last message (same code as above) subject = decode_header_value(msg.get('subject', _('(no subject)')), lcset) diff --git a/Mailman/ListAdmin.py b/Mailman/ListAdmin.py index 7f270937..12d9824a 100644 --- a/Mailman/ListAdmin.py +++ b/Mailman/ListAdmin.py @@ -44,7 +44,7 @@ from Mailman import mm_cfg from Mailman import Utils -from Mailman import Message +from Mailman.Message import Message from Mailman import Errors from Mailman.UserDesc import UserDesc from Mailman.Queue.sbcache import get_switchboard diff --git a/Mailman/MTA/Manual.py b/Mailman/MTA/Manual.py index 31be2452..cc0f41a7 100644 --- a/Mailman/MTA/Manual.py +++ b/Mailman/MTA/Manual.py @@ -22,7 +22,7 @@ from io import StringIO from Mailman import mm_cfg -from Mailman import Message +from Mailman.Message import Message from Mailman import Utils from Mailman.Queue.sbcache import get_switchboard from Mailman.i18n import _, C_ diff --git a/Mailman/MailList.py b/Mailman/MailList.py index b7476d8b..0de00b67 100644 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -67,7 +67,7 @@ # other useful classes from Mailman import MemberAdaptor from Mailman.OldStyleMemberships import OldStyleMemberships -from Mailman import Message +from Mailman.Message import Message from Mailman import Site from Mailman import i18n from Mailman.Logging.Syslog import syslog diff --git a/Mailman/Mailbox.py b/Mailman/Mailbox.py index bc410343..d37a333b 100644 --- a/Mailman/Mailbox.py +++ b/Mailman/Mailbox.py @@ -28,14 +28,12 @@ from email.errors import MessageParseError from Mailman import mm_cfg -import Mailman.Message -from Mailman.Message import Generator from Mailman.Message import Message def _safeparser(fp): try: - return email.message_from_file(fp, Mailman.Message.Message) + return email.message_from_file(fp, Message) except MessageParseError: # Don't return None since that will stop a mailbox iterator return '' diff --git a/Mailman/Queue/BounceRunner.py b/Mailman/Queue/BounceRunner.py index 06fa0797..b4f6f0f9 100644 --- a/Mailman/Queue/BounceRunner.py +++ b/Mailman/Queue/BounceRunner.py @@ -191,6 +191,12 @@ def _dispose(self, mlist, msg, msgdata): mlist.internal_name(), e) return False + # Validate message type first + msg, success = self._validate_message(msg, msgdata) + if not success: + mailman_log('error', 'Message validation failed for bounce message') + return False + # Validate message headers if not msg.get('message-id'): mailman_log('error', 'Message missing Message-ID header') diff --git a/Mailman/Queue/CommandRunner.py b/Mailman/Queue/CommandRunner.py index 12a84cd4..ecd8c6cb 100644 --- a/Mailman/Queue/CommandRunner.py +++ b/Mailman/Queue/CommandRunner.py @@ -27,7 +27,7 @@ from Mailman import mm_cfg from Mailman import Utils -from Mailman import Message +from Mailman.Message import Message from Mailman.Handlers import Replybot from Mailman.i18n import _ from Mailman.Queue.Runner import Runner @@ -232,6 +232,12 @@ class CommandRunner(Runner): QDIR = mm_cfg.CMDQUEUE_DIR def _dispose(self, mlist, msg, msgdata): + # Validate message type first + msg, success = self._validate_message(msg, msgdata) + if not success: + mailman_log('error', 'Message validation failed for command message') + return False + # The policy here is similar to the Replybot policy. If a message has # "Precedence: bulk|junk|list" and no "X-Ack: yes" header, we discard # it to prevent replybot response storms. diff --git a/Mailman/Queue/IncomingRunner.py b/Mailman/Queue/IncomingRunner.py index 608c6386..0b6c51d2 100644 --- a/Mailman/Queue/IncomingRunner.py +++ b/Mailman/Queue/IncomingRunner.py @@ -106,6 +106,7 @@ import signal import os from email import message_from_string +from Mailman.Message import Message from Mailman import mm_cfg from Mailman import Errors @@ -203,6 +204,36 @@ def _dopipeline(self, mlist, msg, msgdata, pipeline): mailman_log('error', 'Pipeline state mismatch for message %s', msg.get('message-id', 'n/a')) return 0 + # Ensure message is a Mailman.Message.Message + if not isinstance(msg, Message): + try: + mailman_log('info', 'Converting email.message.Message to Mailman.Message.Message') + mailman_msg = Message() + # Copy all attributes from the original message + for key, value in msg.items(): + mailman_msg[key] = value + # Copy the payload + if msg.is_multipart(): + for part in msg.get_payload(): + mailman_msg.attach(part) + else: + mailman_msg.set_payload(msg.get_payload()) + msg = mailman_msg + # Update msgdata references if needed + if 'msg' in msgdata: + msgdata['msg'] = msg + except Exception as e: + mailman_log('error', 'Failed to convert message to Mailman.Message.Message: %s\nTraceback:\n%s', + str(e), traceback.format_exc()) + return 0 + + # Validate required Mailman.Message.Message methods + required_methods = ['get_sender', 'get', 'items', 'is_multipart', 'get_payload'] + for method in required_methods: + if not hasattr(msg, method): + mailman_log('error', 'Message object missing required method %s', method) + return 0 + while pipeline: handler = pipeline.pop(0) modname = 'Mailman.Handlers.' + handler diff --git a/Mailman/Queue/MaildirRunner.py b/Mailman/Queue/MaildirRunner.py index fa3a4bcc..8c36d253 100644 --- a/Mailman/Queue/MaildirRunner.py +++ b/Mailman/Queue/MaildirRunner.py @@ -63,7 +63,7 @@ from Mailman import mm_cfg from Mailman import Utils -import Mailman.Message +from Mailman.Message import Message from Mailman.Queue.Runner import Runner from Mailman.Queue.sbcache import get_switchboard from Mailman.Logging.Syslog import mailman_log @@ -104,7 +104,7 @@ def __init__(self, slice=None, numslices=1): self._stop = 0 self._dir = os.path.join(mm_cfg.MAILDIR_DIR, 'new') self._cur = os.path.join(mm_cfg.MAILDIR_DIR, 'cur') - self._parser = Parser(Mailman.Message.Message) + self._parser = Parser(Message) def _oneloop(self): """Process one batch of messages.""" @@ -174,9 +174,16 @@ def _cleanup(self): def _dispose(self, mlist, msg, msgdata): """Process the maildir message.""" try: + # Validate message type first + msg, success = self._validate_message(msg, msgdata) + if not success: + mailman_log('error', 'Message validation failed for maildir message') + return False + # Process the maildir message mlist.process_maildir(msg) + return True except Exception as e: mailman_log('error', 'Error processing maildir message for list %s: %s', mlist.internal_name(), str(e)) - raise + return False diff --git a/Mailman/Queue/NewsRunner.py b/Mailman/Queue/NewsRunner.py index 56c55ec2..771ff465 100644 --- a/Mailman/Queue/NewsRunner.py +++ b/Mailman/Queue/NewsRunner.py @@ -134,12 +134,19 @@ def _oneloop(self): def _dispose(self, mlist, msg, msgdata): """Post the message to the newsgroup.""" try: + # Validate message type first + msg, success = self._validate_message(msg, msgdata) + if not success: + mailman_log('error', 'Message validation failed for news message') + return False + # Post the message to the newsgroup mlist.post_to_news(msg) + return True except Exception as e: mailman_log('error', 'Error posting message to newsgroup for list %s: %s', mlist.internal_name(), str(e)) - raise + return False def _queue_news(self, listname, msg, msgdata): """Queue a news message for processing.""" diff --git a/Mailman/Queue/OutgoingRunner.py b/Mailman/Queue/OutgoingRunner.py index aaa46e02..4ac91f73 100644 --- a/Mailman/Queue/OutgoingRunner.py +++ b/Mailman/Queue/OutgoingRunner.py @@ -85,27 +85,21 @@ def __init__(self, slice=None, numslices=1): raise def _dispose(self, mlist, msg, msgdata): - """Deliver the message to the list's members.""" + """Process an outgoing message.""" try: - # Deliver the message - mlist.deliver(msg, msgdata) + # Validate message type first + msg, success = self._validate_message(msg, msgdata) + if not success: + mailman_log('error', 'Message validation failed for outgoing message') + return False + + # Process the message through the delivery module + self._func(mlist, msg, msgdata) + return True except Exception as e: - # Get message details for better error reporting - msgid = msg.get('message-id', 'n/a') - sender = msg.get('from', 'unknown') - subject = msg.get('subject', 'no subject') - - # Log detailed error information with full traceback - mailman_log('error', - 'Error delivering message to list %s\n' - 'Message-ID: %s\n' - 'From: %s\n' - 'Subject: %s\n' - 'Error: %s\n' - 'Traceback:\n%s', - mlist.internal_name(), msgid, sender, subject, str(e), - traceback.format_exc()) - raise + mailman_log('error', 'Error processing outgoing message for list %s: %s', + mlist.internal_name(), str(e)) + return False def _queue_bounces(self, mlist, msg, msgdata, failures): """Queue bounce messages for failed deliveries.""" diff --git a/Mailman/Queue/RetryRunner.py b/Mailman/Queue/RetryRunner.py index 4297a99a..d117cecf 100644 --- a/Mailman/Queue/RetryRunner.py +++ b/Mailman/Queue/RetryRunner.py @@ -30,6 +30,12 @@ def __init__(self, slice=None, numslices=1): self.__outq = Switchboard(mm_cfg.OUTQUEUE_DIR) def _dispose(self, mlist, msg, msgdata): + # Validate message type first + msg, success = self._validate_message(msg, msgdata) + if not success: + mailman_log('error', 'Message validation failed for retry message') + return False + # Move it to the out queue for another retry if it's time. deliver_after = msgdata.get('deliver_after', 0) if time.time() < deliver_after: diff --git a/Mailman/Queue/Runner.py b/Mailman/Queue/Runner.py index 6cf36dc1..369054c7 100644 --- a/Mailman/Queue/Runner.py +++ b/Mailman/Queue/Runner.py @@ -186,29 +186,76 @@ def _oneloop(self): break return len(files) - def _onefile(self, msg, msgdata): - # Do some common sanity checking on the message metadata. It's got to - # be destined for a particular mailing list. This switchboard is used - # to shunt off badly formatted messages. We don't want to just trash - # them because they may be fixable with human intervention. Just get - # them out of our site though. - # - # Find out which mailing list this message is destined for. + def _validate_message(self, msg, msgdata=None): + """Validate and convert message to Mailman.Message.Message if needed. + + Args: + msg: The message object to validate + msgdata: Optional message metadata dictionary + + Returns: + tuple: (validated_msg, success) + where validated_msg is the converted message if successful, + and success is a boolean indicating if validation succeeded + """ try: - # Convert email.message.Message to Mailman.Message if needed + # Handle string messages + if isinstance(msg, str): + log('info', 'Converting string to Mailman.Message.Message') + try: + from email import message_from_string + msg = message_from_string(msg) + except Exception as e: + log('error', 'Failed to convert string to message: %s\nTraceback:\n%s', + str(e), traceback.format_exc()) + return None, False + + # Convert to Mailman.Message.Message if needed if not isinstance(msg, Message): - mailman_msg = Message() - # Copy all attributes from the original message - for key, value in msg.items(): - mailman_msg[key] = value - # Copy the payload - if msg.is_multipart(): - for part in msg.get_payload(): - mailman_msg.attach(part) - else: - mailman_msg.set_payload(msg.get_payload()) - msg = mailman_msg + log('info', 'Converting email.message.Message to Mailman.Message.Message') + try: + mailman_msg = Message() + # Copy all attributes from the original message + for key, value in msg.items(): + mailman_msg[key] = value + # Copy the payload + if msg.is_multipart(): + for part in msg.get_payload(): + mailman_msg.attach(part) + else: + mailman_msg.set_payload(msg.get_payload()) + msg = mailman_msg + # Update msgdata references if needed + if msgdata and 'msg' in msgdata: + msgdata['msg'] = msg + except Exception as e: + log('error', 'Failed to convert to Mailman.Message.Message: %s\nTraceback:\n%s', + str(e), traceback.format_exc()) + return None, False + + # Validate required Mailman.Message.Message methods + required_methods = ['get_sender', 'get', 'items', 'is_multipart', 'get_payload'] + for method in required_methods: + if not hasattr(msg, method): + log('error', 'Message object missing required method %s', method) + return None, False + + return msg, True + except Exception as e: + log('error', 'Unexpected error during message validation: %s\nTraceback:\n%s', + str(e), traceback.format_exc()) + return None, False + def _onefile(self, msg, msgdata): + # Validate message type first + msg, success = self._validate_message(msg, msgdata) + if not success: + log('error', 'Message validation failed, moving to shunt queue') + self._shunt.enqueue(msg, msgdata) + return + + # Do some common sanity checking on the message metadata + try: # Check for duplicate messages early msgid = msg.get('message-id', 'n/a') if hasattr(self, '_processed_messages') and msgid in self._processed_messages: diff --git a/Mailman/Queue/Switchboard.py b/Mailman/Queue/Switchboard.py index 7d9506e7..4517e263 100644 --- a/Mailman/Queue/Switchboard.py +++ b/Mailman/Queue/Switchboard.py @@ -48,7 +48,7 @@ from Mailman import mm_cfg from Mailman import Utils -from Mailman import Message +from Mailman.Message import Message from Mailman.Logging.Syslog import mailman_log from Mailman.Utils import sha_new @@ -267,8 +267,30 @@ def dequeue(self, filebase): self.finish(filebase, preserve=True) return None, None - if not hasattr(msg, 'get'): - mailman_log('error', 'Message object does not have get() method in file %s', backfile) + # Convert to Mailman.Message.Message if needed + if not isinstance(msg, Message.Message): + try: + mailman_log('info', 'Converting email.message.Message to Mailman.Message.Message') + mailman_msg = Message.Message() + # Copy all attributes from the original message + for key, value in msg.items(): + mailman_msg[key] = value + # Copy the payload + if msg.is_multipart(): + for part in msg.get_payload(): + mailman_msg.attach(part) + else: + mailman_msg.set_payload(msg.get_payload()) + msg = mailman_msg + except Exception as e: + mailman_log('error', 'Failed to convert to Mailman.Message.Message: %s\nTraceback:\n%s', + str(e), traceback.format_exc()) + # Move to shunt queue + self.finish(filebase, preserve=True) + return None, None + + if not hasattr(msg, 'get_sender'): + mailman_log('error', 'Message object does not have get_sender() method in file %s', backfile) # Move to shunt queue self.finish(filebase, preserve=True) return None, None diff --git a/Mailman/versions.py b/Mailman/versions.py index 8d8b30c1..aadac121 100644 --- a/Mailman/versions.py +++ b/Mailman/versions.py @@ -37,7 +37,7 @@ from builtins import range import email import email.message -import Mailman.Message +from Mailman.Message import Message from Mailman import mm_cfg diff --git a/bin/add_members b/bin/add_members index 68c0736c..e4901a2c 100755 --- a/bin/add_members +++ b/bin/add_members @@ -90,7 +90,7 @@ from Mailman import i18n from Mailman import Utils from Mailman import mm_cfg from Mailman import Errors -from Mailman import Message +from Mailman.Message import Message from Mailman import MailList from Mailman import MemberAdaptor from Mailman.UserDesc import UserDesc diff --git a/bin/change_pw b/bin/change_pw index 880f0588..173cfadc 100644 --- a/bin/change_pw +++ b/bin/change_pw @@ -73,7 +73,7 @@ from Mailman import mm_cfg from Mailman import Utils from Mailman import MailList from Mailman import Errors -from Mailman import Message +from Mailman.Message import Message from Mailman import i18n _ = i18n._ diff --git a/bin/newlist b/bin/newlist index 33f31ad9..c03c7054 100755 --- a/bin/newlist +++ b/bin/newlist @@ -108,7 +108,7 @@ from Mailman import mm_cfg from Mailman import MailList from Mailman import Utils from Mailman import Errors -from Mailman import Message +from Mailman.Message import Message from Mailman import i18n _ = i18n._ diff --git a/bin/update b/bin/update index f0f3a311..9fcf3322 100755 --- a/bin/update +++ b/bin/update @@ -51,7 +51,7 @@ import marshal import paths import email.errors import email.message -import Mailman.Message +from Mailman.Message import Message sys.path.append("@VAR_PREFIX@/Mailman") from Mailman import mm_cfg @@ -583,7 +583,7 @@ def dequeue(filebase): msgfp = None try: msgfp = open(msgfile, 'rb') - msg = Mailman.Message.Message_from_file(msgfp) + msg = Message_from_file(msgfp) os.unlink(msgfile) except EnvironmentError as e: if e.errno != errno.ENOENT: raise diff --git a/cron/checkdbs b/cron/checkdbs index 4b1a5681..005637b7 100755 --- a/cron/checkdbs +++ b/cron/checkdbs @@ -39,7 +39,7 @@ from email.charset import Charset from Mailman import mm_cfg from Mailman import Utils from Mailman import MailList -from Mailman import Message +from Mailman.Message import Message from Mailman import i18n # Work around known problems with some RedHat cron daemons diff --git a/cron/gate_news b/cron/gate_news index c092a1bc..809ac0c2 100755 --- a/cron/gate_news +++ b/cron/gate_news @@ -39,13 +39,12 @@ import paths # Import this /after/ paths so that the sys.path is properly hacked import email.errors import email.message -import Mailman.Message +from Mailman.Message import Message from email.parser import Parser from Mailman import mm_cfg from Mailman import MailList from Mailman import Utils -from Mailman import Message from Mailman import LockFile from Mailman.i18n import _ from Mailman.Queue.sbcache import get_switchboard diff --git a/cron/mailpasswds b/cron/mailpasswds index 72c1f381..fa8a2bf0 100755 --- a/cron/mailpasswds +++ b/cron/mailpasswds @@ -63,7 +63,7 @@ from Mailman import mm_cfg from Mailman import MailList from Mailman import Errors from Mailman import Utils -from Mailman import Message +from Mailman.Message import Message from Mailman import i18n from Mailman.Logging.Syslog import syslog diff --git a/tests/test_handlers.py b/tests/test_handlers.py index 999d40b3..28f31a9e 100644 --- a/tests/test_handlers.py +++ b/tests/test_handlers.py @@ -35,7 +35,7 @@ from Mailman import mm_cfg from Mailman.MailList import MailList -from Mailman import Message +from Mailman.Message import Message from Mailman import Errors from Mailman import Pending from Mailman.Queue.Switchboard import Switchboard diff --git a/tests/test_message.py b/tests/test_message.py index 08af80aa..87ca6f20 100644 --- a/tests/test_message.py +++ b/tests/test_message.py @@ -26,7 +26,7 @@ except ImportError: import paths -from Mailman import Message +from Mailman.Message import Message from Mailman import Version from Mailman import Errors From 3e6dd9eebadc59efe211f6a7e2dc06bbd6460f3e Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 2 May 2025 12:52:33 -0400 Subject: [PATCH 310/748] update Message class utilization --- Mailman/Handlers/ToDigest.py | 6 +- Mailman/ListAdmin.py | 16 +- Mailman/Queue/IncomingRunner.py | 9 +- Mailman/Queue/Runner.py | 12 +- Mailman/Queue/Switchboard.py | 14 +- Mailman/Utils.py | 3 +- Mailman/versions.py | 1 - bin/update | 1 - cron/gate_news | 1 - tests/test_handlers.py | 2571 ++++++++++--------------------- tests/test_message.py | 2 +- 11 files changed, 838 insertions(+), 1798 deletions(-) diff --git a/Mailman/Handlers/ToDigest.py b/Mailman/Handlers/ToDigest.py index bb2b0449..8d6bd303 100644 --- a/Mailman/Handlers/ToDigest.py +++ b/Mailman/Handlers/ToDigest.py @@ -42,7 +42,7 @@ from email.utils import getaddresses, formatdate from email.header import decode_header, make_header, Header from email.charset import Charset -import email.message +import email from Mailman import mm_cfg from Mailman import Utils @@ -212,7 +212,7 @@ def send_digests(mlist, mboxpath): lcset_out = Charset(lcset).output_charset or lcset # Create the digest messages - mimemsg = email.message.Message() + mimemsg = Message() mimemsg['Content-Type'] = 'multipart/mixed' mimemsg['MIME-Version'] = '1.0' mimemsg['From'] = mlist.GetRequestEmail() @@ -224,7 +224,7 @@ def send_digests(mlist, mboxpath): # Set up the RFC 1153 digest plainmsg = StringIO() # Use StringIO for text output - rfc1153msg = email.message.Message() + rfc1153msg = Message() rfc1153msg['From'] = mlist.GetRequestEmail() rfc1153msg['Subject'] = Header(digestid, lcset, header_name='Subject') rfc1153msg['To'] = mlist.GetListEmail() diff --git a/Mailman/ListAdmin.py b/Mailman/ListAdmin.py index 12d9824a..d96ce00c 100644 --- a/Mailman/ListAdmin.py +++ b/Mailman/ListAdmin.py @@ -447,8 +447,8 @@ def __handlepost(self, record, value, comment, preserve, forward, addr): if e.errno != errno.ENOENT: raise return LOST # Convert to Mailman.Message if needed - if isinstance(msg, email.message.Message) and not isinstance(msg, Message.Message): - mailman_msg = Message.Message() + if isinstance(msg, email.message.Message) and not isinstance(msg, Message): + mailman_msg = Message() # Copy all attributes from the original message for key, value in msg.items(): mailman_msg[key] = value @@ -503,8 +503,8 @@ def __handlepost(self, record, value, comment, preserve, forward, addr): if e.errno != errno.ENOENT: raise raise Errors.LostHeldMessage(path) # Convert to Mailman.Message if needed - if isinstance(copy, email.message.Message) and not isinstance(copy, Message.Message): - mailman_msg = Message.Message() + if isinstance(copy, email.message.Message) and not isinstance(copy, Message): + mailman_msg = Message() # Copy all attributes from the original message for key, value in copy.items(): mailman_msg[key] = value @@ -866,8 +866,8 @@ def readMessage(path): if ext == '.txt': msg = email.message_from_file(fp, Message) # Convert to Mailman.Message if needed - if isinstance(msg, email.message.Message) and not isinstance(msg, Message.Message): - mailman_msg = Message.Message() + if isinstance(msg, email.message.Message) and not isinstance(msg, Message): + mailman_msg = Message() # Copy all attributes from the original message for key, value in msg.items(): mailman_msg[key] = value @@ -882,8 +882,8 @@ def readMessage(path): assert ext == '.pck' msg = pickle.load(fp, fix_imports=True, encoding='latin1') # Convert to Mailman.Message if needed - if isinstance(msg, email.message.Message) and not isinstance(msg, Message.Message): - mailman_msg = Message.Message() + if isinstance(msg, email.message.Message) and not isinstance(msg, Message): + mailman_msg = Message() # Copy all attributes from the original message for key, value in msg.items(): mailman_msg[key] = value diff --git a/Mailman/Queue/IncomingRunner.py b/Mailman/Queue/IncomingRunner.py index 0b6c51d2..8bfb0fd4 100644 --- a/Mailman/Queue/IncomingRunner.py +++ b/Mailman/Queue/IncomingRunner.py @@ -115,7 +115,6 @@ from Mailman.Logging.Syslog import mailman_log from Mailman.Utils import reap from Mailman import Utils -from Mailman import Message class PipelineError(Exception): @@ -204,10 +203,10 @@ def _dopipeline(self, mlist, msg, msgdata, pipeline): mailman_log('error', 'Pipeline state mismatch for message %s', msg.get('message-id', 'n/a')) return 0 - # Ensure message is a Mailman.Message.Message + # Ensure message is a Mailman.Message if not isinstance(msg, Message): try: - mailman_log('info', 'Converting email.message.Message to Mailman.Message.Message') + mailman_log('info', 'Converting email.message.Message to Mailman.Message') mailman_msg = Message() # Copy all attributes from the original message for key, value in msg.items(): @@ -223,11 +222,11 @@ def _dopipeline(self, mlist, msg, msgdata, pipeline): if 'msg' in msgdata: msgdata['msg'] = msg except Exception as e: - mailman_log('error', 'Failed to convert message to Mailman.Message.Message: %s\nTraceback:\n%s', + mailman_log('error', 'Failed to convert message to Mailman.Message: %s\nTraceback:\n%s', str(e), traceback.format_exc()) return 0 - # Validate required Mailman.Message.Message methods + # Validate required Mailman.Message methods required_methods = ['get_sender', 'get', 'items', 'is_multipart', 'get_payload'] for method in required_methods: if not hasattr(msg, method): diff --git a/Mailman/Queue/Runner.py b/Mailman/Queue/Runner.py index 369054c7..a023dd82 100644 --- a/Mailman/Queue/Runner.py +++ b/Mailman/Queue/Runner.py @@ -187,7 +187,7 @@ def _oneloop(self): return len(files) def _validate_message(self, msg, msgdata=None): - """Validate and convert message to Mailman.Message.Message if needed. + """Validate and convert message to Mailman.Message if needed. Args: msg: The message object to validate @@ -201,7 +201,7 @@ def _validate_message(self, msg, msgdata=None): try: # Handle string messages if isinstance(msg, str): - log('info', 'Converting string to Mailman.Message.Message') + log('info', 'Converting string to Mailman.Message') try: from email import message_from_string msg = message_from_string(msg) @@ -210,9 +210,9 @@ def _validate_message(self, msg, msgdata=None): str(e), traceback.format_exc()) return None, False - # Convert to Mailman.Message.Message if needed + # Convert to Mailman.Message if needed if not isinstance(msg, Message): - log('info', 'Converting email.message.Message to Mailman.Message.Message') + log('info', 'Converting email.message.Message to Mailman.Message') try: mailman_msg = Message() # Copy all attributes from the original message @@ -229,11 +229,11 @@ def _validate_message(self, msg, msgdata=None): if msgdata and 'msg' in msgdata: msgdata['msg'] = msg except Exception as e: - log('error', 'Failed to convert to Mailman.Message.Message: %s\nTraceback:\n%s', + log('error', 'Failed to convert to Mailman.Message: %s\nTraceback:\n%s', str(e), traceback.format_exc()) return None, False - # Validate required Mailman.Message.Message methods + # Validate required Mailman.Message methods required_methods = ['get_sender', 'get', 'items', 'is_multipart', 'get_payload'] for method in required_methods: if not hasattr(msg, method): diff --git a/Mailman/Queue/Switchboard.py b/Mailman/Queue/Switchboard.py index 4517e263..64c0ca95 100644 --- a/Mailman/Queue/Switchboard.py +++ b/Mailman/Queue/Switchboard.py @@ -129,8 +129,8 @@ def enqueue(self, _msg, _metadata={}, **_kws): now = time.time() if SAVE_MSGS_AS_PICKLES and not data.get('_plaintext'): # Convert email.message.Message to Mailman.Message if needed - if isinstance(_msg, email.message.Message) and not isinstance(_msg, Message.Message): - mailman_msg = Message.Message() + if isinstance(_msg, email.message.Message) and not isinstance(_msg, Message): + mailman_msg = Message() # Copy all attributes from the original message for key, value in _msg.items(): mailman_msg[key] = value @@ -267,11 +267,11 @@ def dequeue(self, filebase): self.finish(filebase, preserve=True) return None, None - # Convert to Mailman.Message.Message if needed - if not isinstance(msg, Message.Message): + # Convert to Mailman.Message if needed + if not isinstance(msg, Message): try: - mailman_log('info', 'Converting email.message.Message to Mailman.Message.Message') - mailman_msg = Message.Message() + mailman_log('info', 'Converting email.message.Message to Mailman.Message') + mailman_msg = Message() # Copy all attributes from the original message for key, value in msg.items(): mailman_msg[key] = value @@ -283,7 +283,7 @@ def dequeue(self, filebase): mailman_msg.set_payload(msg.get_payload()) msg = mailman_msg except Exception as e: - mailman_log('error', 'Failed to convert to Mailman.Message.Message: %s\nTraceback:\n%s', + mailman_log('error', 'Failed to convert to Mailman.Message: %s\nTraceback:\n%s', str(e), traceback.format_exc()) # Move to shunt queue self.finish(filebase, preserve=True) diff --git a/Mailman/Utils.py b/Mailman/Utils.py index 6b3fed35..12b2219f 100644 --- a/Mailman/Utils.py +++ b/Mailman/Utils.py @@ -52,6 +52,7 @@ from Mailman import Site from Mailman.SafeDict import SafeDict from Mailman.Logging.Syslog import mailman_log +from Mailman.Message import Message try: import hashlib @@ -763,7 +764,7 @@ def maketext(templatefile, dict=None, raw=0, lang=None, mlist=None): 'who': (0, 1), } -# Given a Message.Message object, test for administrivia (eg subscribe, +# Given a Message object, test for administrivia (eg subscribe, # unsubscribe, etc). The test must be a good guess -- messages that return # true get sent to the list admin instead of the entire list. def is_administrivia(msg): diff --git a/Mailman/versions.py b/Mailman/versions.py index aadac121..4ed6bae1 100644 --- a/Mailman/versions.py +++ b/Mailman/versions.py @@ -36,7 +36,6 @@ from builtins import str from builtins import range import email -import email.message from Mailman.Message import Message diff --git a/bin/update b/bin/update index 9fcf3322..ac52acc9 100755 --- a/bin/update +++ b/bin/update @@ -50,7 +50,6 @@ import marshal import paths import email.errors -import email.message from Mailman.Message import Message sys.path.append("@VAR_PREFIX@/Mailman") diff --git a/cron/gate_news b/cron/gate_news index 809ac0c2..7ccacb2c 100755 --- a/cron/gate_news +++ b/cron/gate_news @@ -38,7 +38,6 @@ import nntplib import paths # Import this /after/ paths so that the sys.path is properly hacked import email.errors -import email.message from Mailman.Message import Message from email.parser import Parser diff --git a/tests/test_handlers.py b/tests/test_handlers.py index 28f31a9e..3d9b4030 100644 --- a/tests/test_handlers.py +++ b/tests/test_handlers.py @@ -39,37 +39,55 @@ from Mailman import Errors from Mailman import Pending from Mailman.Queue.Switchboard import Switchboard - from Mailman.Handlers import Acknowledge -from Mailman.Handlers import AfterDelivery -from Mailman.Handlers import Approve -from Mailman.Handlers import CalcRecips -from Mailman.Handlers import Cleanse from Mailman.Handlers import CookHeaders +from Mailman.Handlers import ToDigest +from Mailman.Handlers import ToUsenet from Mailman.Handlers import Decorate +from Mailman.Handlers import Cleanse +from Mailman.Handlers import CleanseDKIM +from Mailman.Handlers import AvoidDuplicates +from Mailman.Handlers import CalcRecips +from Mailman.Handlers import ToArchive +from Mailman.Handlers import AfterDelivery +from Mailman.Handlers import WrapMessage +from Mailman.Handlers import HandleBouncingAddresses +from Mailman.Handlers import Moderate +from Mailman.Handlers import Replybot +from Mailman.Handlers import Tagger from Mailman.Handlers import FileRecips -from Mailman.Handlers import Hold from Mailman.Handlers import MimeDel +from Mailman.Handlers import Scrubber +from Mailman.Handlers import SMTPDirect +from Mailman.Handlers import SpamDetect +from Mailman.Handlers import Hold +from Mailman.Handlers import Approve +from Mailman.Handlers import ToOutgoing +from Mailman.Handlers import ToDigest +from Mailman.Handlers import ToArchive +from Mailman.Handlers import ToUsenet +from Mailman.Handlers import AfterDelivery +from Mailman.Handlers import WrapMessage +from Mailman.Handlers import HandleBouncingAddresses from Mailman.Handlers import Moderate from Mailman.Handlers import Replybot -# Don't test handlers such as SMTPDirect and Sendmail here -from Mailman.Handlers import SpamDetect from Mailman.Handlers import Tagger -from Mailman.Handlers import ToArchive -from Mailman.Handlers import ToDigest +from Mailman.Handlers import FileRecips +from Mailman.Handlers import MimeDel +from Mailman.Handlers import Scrubber +from Mailman.Handlers import SMTPDirect +from Mailman.Handlers import SpamDetect +from Mailman.Handlers import Hold +from Mailman.Handlers import Approve from Mailman.Handlers import ToOutgoing -from Mailman.Handlers import ToUsenet -from Mailman.Utils import sha_new -from TestBase import TestBase +from Mailman.tests.test_bounces import TestBase - def password(plaintext): return sha_new(plaintext).hexdigest() - class TestAcknowledge(TestBase): def setUp(self): TestBase.setUp(self) @@ -92,7 +110,7 @@ def test_no_ack_msgdata(self): msg = email.message_from_string("""\ From: aperson@dom.ain -""", Message.Message) +""") Acknowledge.process(self._mlist, msg, {'original_sender': 'aperson@dom.ain'}) eq(len(self._sb.files()), 0) @@ -104,7 +122,7 @@ def test_no_ack_not_a_member(self): msg = email.message_from_string("""\ From: bperson@dom.ain -""", Message.Message) +""") Acknowledge.process(self._mlist, msg, {'original_sender': 'bperson@dom.ain'}) eq(len(self._sb.files()), 0) @@ -115,7 +133,7 @@ def test_no_ack_sender(self): msg = email.message_from_string("""\ From: aperson@dom.ain -""", Message.Message) +""") Acknowledge.process(self._mlist, msg, {}) eq(len(self._sb.files()), 0) @@ -127,7 +145,7 @@ def test_ack_no_subject(self): msg = email.message_from_string("""\ From: aperson@dom.ain -""", Message.Message) +""") Acknowledge.process(self._mlist, msg, {}) files = self._sb.files() eq(len(files), 1) @@ -167,7 +185,7 @@ def test_ack_with_subject(self): From: aperson@dom.ain Subject: Wish you were here -""", Message.Message) +""") Acknowledge.process(self._mlist, msg, {}) files = self._sb.files() eq(len(files), 1) @@ -198,1977 +216,1002 @@ def test_ack_with_subject(self): # Make sure we dequeued the only message eq(len(self._sb.files()), 0) + def test_ack_with_subject_and_headers(self): + eq = self.assertEqual + self._mlist.setMemberOption( + 'aperson@dom.ain', mm_cfg.AcknowledgePosts, 1) + eq(len(self._sb.files()), 0) + msg = email.message_from_string("""\ +From: aperson@dom.ain +Subject: Wish you were here +Date: Mon, 01 Jan 2001 00:00:00 -0000 +Message-ID: - -class TestAfterDelivery(TestBase): - # Both msg and msgdata are ignored - def test_process(self): - mlist = self._mlist - last_post_time = mlist.last_post_time - post_id = mlist.post_id - AfterDelivery.process(mlist, None, None) - self.failUnless(mlist.last_post_time > last_post_time) - self.assertEqual(mlist.post_id, post_id + 1) - +""") + Acknowledge.process(self._mlist, msg, {}) + files = self._sb.files() + eq(len(files), 1) + qmsg, qdata = self._sb.dequeue(files[0]) + # Check the .db file + eq(qdata.get('listname'), '_xtest') + eq(qdata.get('recips'), ['aperson@dom.ain']) + eq(qdata.get('version'), 3) + # Check the .pck + eq(str(qmsg['subject']), '_xtest post acknowledgement') + eq(qmsg['to'], 'aperson@dom.ain') + eq(qmsg['from'], '_xtest-bounces@dom.ain') + eq(qmsg.get_content_type(), 'text/plain') + eq(qmsg.get_param('charset'), 'us-ascii') + msgid = qmsg['message-id'] + self.failUnless(msgid.startswith('')) + eq(qmsg.get_payload(), """\ +Your message entitled - -class TestApprove(TestBase): - def test_short_circuit(self): - msgdata = {'approved': 1} - rtn = Approve.process(self._mlist, Message.Message(), msgdata) - # Not really a great test, but there's little else to assert - self.assertEqual(rtn, None) + Wish you were here - def test_approved_moderator(self): - mlist = self._mlist - mlist.mod_password = password('wazoo') - msg = email.message_from_string("""\ -Approved: wazoo +was successfully received by the _xtest mailing list. +List info page: http://www.dom.ain/mailman/listinfo/_xtest +Your preferences: http://www.dom.ain/mailman/options/_xtest/aperson%40dom.ain """) - msgdata = {} - Approve.process(mlist, msg, msgdata) - self.failUnless('approved' in msgdata) - self.assertEqual(msgdata['approved'], 1) + # Make sure we dequeued the only message + eq(len(self._sb.files()), 0) - def test_approve_moderator(self): - mlist = self._mlist - mlist.mod_password = password('wazoo') + def test_ack_with_subject_and_body(self): + eq = self.assertEqual + self._mlist.setMemberOption( + 'aperson@dom.ain', mm_cfg.AcknowledgePosts, 1) + eq(len(self._sb.files()), 0) msg = email.message_from_string("""\ -Approve: wazoo +From: aperson@dom.ain +Subject: Wish you were here +Date: Mon, 01 Jan 2001 00:00:00 -0000 +Message-ID: +This is the body of the message. """) - msgdata = {} - Approve.process(mlist, msg, msgdata) - self.failUnless('approved' in msgdata) - self.assertEqual(msgdata['approved'], 1) + Acknowledge.process(self._mlist, msg, {}) + files = self._sb.files() + eq(len(files), 1) + qmsg, qdata = self._sb.dequeue(files[0]) + # Check the .db file + eq(qdata.get('listname'), '_xtest') + eq(qdata.get('recips'), ['aperson@dom.ain']) + eq(qdata.get('version'), 3) + # Check the .pck + eq(str(qmsg['subject']), '_xtest post acknowledgement') + eq(qmsg['to'], 'aperson@dom.ain') + eq(qmsg['from'], '_xtest-bounces@dom.ain') + eq(qmsg.get_content_type(), 'text/plain') + eq(qmsg.get_param('charset'), 'us-ascii') + msgid = qmsg['message-id'] + self.failUnless(msgid.startswith('')) + eq(qmsg.get_payload(), """\ +Your message entitled - def test_approved_admin(self): - mlist = self._mlist - mlist.password = password('wazoo') - msg = email.message_from_string("""\ -Approved: wazoo + Wish you were here + +was successfully received by the _xtest mailing list. +List info page: http://www.dom.ain/mailman/listinfo/_xtest +Your preferences: http://www.dom.ain/mailman/options/_xtest/aperson%40dom.ain """) - msgdata = {} - Approve.process(mlist, msg, msgdata) - self.failUnless('approved' in msgdata) - self.assertEqual(msgdata['approved'], 1) + # Make sure we dequeued the only message + eq(len(self._sb.files()), 0) - def test_approve_admin(self): - mlist = self._mlist - mlist.password = password('wazoo') + def test_ack_with_subject_and_body_and_headers(self): + eq = self.assertEqual + self._mlist.setMemberOption( + 'aperson@dom.ain', mm_cfg.AcknowledgePosts, 1) + eq(len(self._sb.files()), 0) msg = email.message_from_string("""\ -Approve: wazoo +From: aperson@dom.ain +Subject: Wish you were here +Date: Mon, 01 Jan 2001 00:00:00 -0000 +Message-ID: +Content-Type: text/plain; charset=us-ascii +This is the body of the message. """) - msgdata = {} - Approve.process(mlist, msg, msgdata) - self.failUnless('approved' in msgdata) - self.assertEqual(msgdata['approved'], 1) + Acknowledge.process(self._mlist, msg, {}) + files = self._sb.files() + eq(len(files), 1) + qmsg, qdata = self._sb.dequeue(files[0]) + # Check the .db file + eq(qdata.get('listname'), '_xtest') + eq(qdata.get('recips'), ['aperson@dom.ain']) + eq(qdata.get('version'), 3) + # Check the .pck + eq(str(qmsg['subject']), '_xtest post acknowledgement') + eq(qmsg['to'], 'aperson@dom.ain') + eq(qmsg['from'], '_xtest-bounces@dom.ain') + eq(qmsg.get_content_type(), 'text/plain') + eq(qmsg.get_param('charset'), 'us-ascii') + msgid = qmsg['message-id'] + self.failUnless(msgid.startswith('')) + eq(qmsg.get_payload(), """\ +Your message entitled - def test_unapproved(self): - mlist = self._mlist - mlist.password = password('zoowa') - msg = email.message_from_string("""\ -Approve: wazoo + Wish you were here + +was successfully received by the _xtest mailing list. +List info page: http://www.dom.ain/mailman/listinfo/_xtest +Your preferences: http://www.dom.ain/mailman/options/_xtest/aperson%40dom.ain """) - msgdata = {} - Approve.process(mlist, msg, msgdata) - self.assertEqual(msgdata.get('approved'), None) + # Make sure we dequeued the only message + eq(len(self._sb.files()), 0) - def test_trip_beentheres(self): - mlist = self._mlist + def test_ack_with_subject_and_body_and_headers_and_attachments(self): + eq = self.assertEqual + self._mlist.setMemberOption( + 'aperson@dom.ain', mm_cfg.AcknowledgePosts, 1) + eq(len(self._sb.files()), 0) msg = email.message_from_string("""\ -X-BeenThere: %s - -""" % mlist.GetListEmail()) - self.assertRaises(Errors.LoopError, Approve.process, mlist, msg, {}) +From: aperson@dom.ain +Subject: Wish you were here +Date: Mon, 01 Jan 2001 00:00:00 -0000 +Message-ID: +Content-Type: multipart/mixed; boundary="===============1234567890==" +--===============1234567890== +Content-Type: text/plain; charset=us-ascii - -class TestCalcRecips(TestBase): - def setUp(self): - TestBase.setUp(self) - # Add a bunch of regular members - mlist = self._mlist - mlist.addNewMember('aperson@dom.ain') - mlist.addNewMember('bperson@dom.ain') - mlist.addNewMember('cperson@dom.ain') - # And a bunch of digest members - mlist.addNewMember('dperson@dom.ain', digest=1) - mlist.addNewMember('eperson@dom.ain', digest=1) - mlist.addNewMember('fperson@dom.ain', digest=1) +This is the body of the message. - def test_short_circuit(self): - msgdata = {'recips': 1} - rtn = CalcRecips.process(self._mlist, None, msgdata) - # Not really a great test, but there's little else to assert - self.assertEqual(rtn, None) +--===============1234567890== +Content-Type: application/octet-stream +Content-Disposition: attachment; filename="test.txt" - def test_simple_path(self): - msgdata = {} - msg = email.message_from_string("""\ -From: dperson@dom.ain +This is an attachment. +--===============1234567890==-- +""") + Acknowledge.process(self._mlist, msg, {}) + files = self._sb.files() + eq(len(files), 1) + qmsg, qdata = self._sb.dequeue(files[0]) + # Check the .db file + eq(qdata.get('listname'), '_xtest') + eq(qdata.get('recips'), ['aperson@dom.ain']) + eq(qdata.get('version'), 3) + # Check the .pck + eq(str(qmsg['subject']), '_xtest post acknowledgement') + eq(qmsg['to'], 'aperson@dom.ain') + eq(qmsg['from'], '_xtest-bounces@dom.ain') + eq(qmsg.get_content_type(), 'text/plain') + eq(qmsg.get_param('charset'), 'us-ascii') + msgid = qmsg['message-id'] + self.failUnless(msgid.startswith('')) + eq(qmsg.get_payload(), """\ +Your message entitled -""", Message.Message) - CalcRecips.process(self._mlist, msg, msgdata) - self.failUnless('recips' in msgdata) - recips = msgdata['recips'] - recips.sort() - self.assertEqual(recips, ['aperson@dom.ain', 'bperson@dom.ain', - 'cperson@dom.ain']) + Wish you were here - def test_exclude_sender(self): - msgdata = {} - msg = email.message_from_string("""\ -From: cperson@dom.ain +was successfully received by the _xtest mailing list. -""", Message.Message) - self._mlist.setMemberOption('cperson@dom.ain', - mm_cfg.DontReceiveOwnPosts, 1) - CalcRecips.process(self._mlist, msg, msgdata) - self.failUnless('recips' in msgdata) - recips = msgdata['recips'] - recips.sort() - self.assertEqual(recips, ['aperson@dom.ain', 'bperson@dom.ain']) - - def test_urgent_moderator(self): - self._mlist.mod_password = password('xxXXxx') - msgdata = {} - msg = email.message_from_string("""\ -From: dperson@dom.ain -Urgent: xxXXxx - -""", Message.Message) - CalcRecips.process(self._mlist, msg, msgdata) - self.failUnless('recips' in msgdata) - recips = msgdata['recips'] - recips.sort() - self.assertEqual(recips, ['aperson@dom.ain', 'bperson@dom.ain', - 'cperson@dom.ain', 'dperson@dom.ain', - 'eperson@dom.ain', 'fperson@dom.ain']) - - def test_urgent_admin(self): - self._mlist.mod_password = password('yyYYyy') - self._mlist.password = password('xxXXxx') - msgdata = {} - msg = email.message_from_string("""\ -From: dperson@dom.ain -Urgent: xxXXxx - -""", Message.Message) - CalcRecips.process(self._mlist, msg, msgdata) - self.failUnless('recips' in msgdata) - recips = msgdata['recips'] - recips.sort() - self.assertEqual(recips, ['aperson@dom.ain', 'bperson@dom.ain', - 'cperson@dom.ain', 'dperson@dom.ain', - 'eperson@dom.ain', 'fperson@dom.ain']) - - def test_urgent_reject(self): - self._mlist.mod_password = password('yyYYyy') - self._mlist.password = password('xxXXxx') - msgdata = {} - msg = email.message_from_string("""\ -From: dperson@dom.ain -Urgent: zzZZzz - -""", Message.Message) - self.assertRaises(Errors.RejectMessage, - CalcRecips.process, - self._mlist, msg, msgdata) - - # BAW: must test the do_topic_filters() path... - - - -class TestCleanse(TestBase): - def setUp(self): - TestBase.setUp(self) - - def test_simple_cleanse(self): - eq = self.assertEqual - msg = email.message_from_string("""\ -From: aperson@dom.ain -Approved: yes -Urgent: indeed -Reply-To: bperson@dom.ain -Sender: asystem@dom.ain -Return-Receipt-To: another@dom.ain -Disposition-Notification-To: athird@dom.ain -X-Confirm-Reading-To: afourth@dom.ain -X-PMRQC: afifth@dom.ain -Subject: a message to you - -""", Message.Message) - Cleanse.process(self._mlist, msg, {}) - eq(msg['approved'], None) - eq(msg['urgent'], None) - eq(msg['return-receipt-to'], None) - eq(msg['disposition-notification-to'], None) - eq(msg['x-confirm-reading-to'], None) - eq(msg['x-pmrqc'], None) - eq(msg['from'], 'aperson@dom.ain') - eq(msg['reply-to'], 'bperson@dom.ain') - eq(msg['sender'], 'asystem@dom.ain') - eq(msg['subject'], 'a message to you') - - def test_anon_cleanse(self): - eq = self.assertEqual - msg = email.message_from_string("""\ -From: aperson@dom.ain -Approved: yes -Urgent: indeed -Reply-To: bperson@dom.ain -Sender: asystem@dom.ain -Return-Receipt-To: another@dom.ain -Disposition-Notification-To: athird@dom.ain -X-Confirm-Reading-To: afourth@dom.ain -X-PMRQC: afifth@dom.ain -Subject: a message to you - -""", Message.Message) - self._mlist.anonymous_list = 1 - Cleanse.process(self._mlist, msg, {}) - eq(msg['approved'], None) - eq(msg['urgent'], None) - eq(msg['return-receipt-to'], None) - eq(msg['disposition-notification-to'], None) - eq(msg['x-confirm-reading-to'], None) - eq(msg['x-pmrqc'], None) - eq(len(msg.get_all('from')), 1) - eq(len(msg.get_all('reply-to')), 1) - eq(msg['from'], '_xtest@dom.ain') - eq(msg['reply-to'], '_xtest@dom.ain') - eq(msg['sender'], None) - eq(msg['subject'], 'a message to you') - - - -class TestCookHeaders(TestBase): - def test_transform_noack_to_xack(self): - eq = self.assertEqual - msg = email.message_from_string("""\ -X-Ack: yes - -""", Message.Message) - CookHeaders.process(self._mlist, msg, {'noack': 1}) - eq(len(msg.get_all('x-ack')), 1) - eq(msg['x-ack'], 'no') - - def test_original_sender(self): - msg = email.message_from_string("""\ -From: aperson@dom.ain - -""", Message.Message) - msgdata = {} - CookHeaders.process(self._mlist, msg, msgdata) - self.assertEqual(msgdata.get('original_sender'), 'aperson@dom.ain') - - def test_no_original_sender(self): - msg = email.message_from_string("""\ -Subject: about this message - -""", Message.Message) - msgdata = {} - CookHeaders.process(self._mlist, msg, msgdata) - self.assertEqual(msgdata.get('original_sender'), '') - - def test_xbeenthere(self): - msg = email.message_from_string("""\ -From: aperson@dom.ain - -""", Message.Message) - CookHeaders.process(self._mlist, msg, {}) - self.assertEqual(msg['x-beenthere'], '_xtest@dom.ain') - - def test_multiple_xbeentheres(self): - eq = self.assertEqual - msg = email.message_from_string("""\ -From: aperson@dom.ain -X-BeenThere: alist@another.dom.ain - -""", Message.Message) - CookHeaders.process(self._mlist, msg, {}) - eq(len(msg.get_all('x-beenthere')), 2) - beentheres = msg.get_all('x-beenthere') - beentheres.sort() - eq(beentheres, ['_xtest@dom.ain', 'alist@another.dom.ain']) - - def test_nonexisting_mmversion(self): - eq = self.assertEqual - msg = email.message_from_string("""\ -From: aperson@dom.ain - -""", Message.Message) - CookHeaders.process(self._mlist, msg, {}) - eq(msg['x-mailman-version'], mm_cfg.VERSION) - - def test_existing_mmversion(self): - eq = self.assertEqual - msg = email.message_from_string("""\ -From: aperson@dom.ain -X-Mailman-Version: 3000 - -""", Message.Message) - CookHeaders.process(self._mlist, msg, {}) - eq(len(msg.get_all('x-mailman-version')), 1) - eq(msg['x-mailman-version'], '3000') - - def test_nonexisting_precedence(self): - eq = self.assertEqual - msg = email.message_from_string("""\ -From: aperson@dom.ain - -""", Message.Message) - CookHeaders.process(self._mlist, msg, {}) - eq(msg['precedence'], 'list') - - def test_existing_precedence(self): - eq = self.assertEqual - msg = email.message_from_string("""\ -From: aperson@dom.ain -Precedence: junk - -""", Message.Message) - CookHeaders.process(self._mlist, msg, {}) - eq(len(msg.get_all('precedence')), 1) - eq(msg['precedence'], 'junk') - - def test_subject_munging_no_subject(self): - self._mlist.subject_prefix = '[XTEST] ' - msg = email.message_from_string("""\ -From: aperson@dom.ain - -""", Message.Message) - msgdata = {} - CookHeaders.process(self._mlist, msg, msgdata) - self.assertEqual(msgdata.get('origsubj'), '') - self.assertEqual(str(msg['subject']), '[XTEST] (no subject)') - - def test_subject_munging(self): - self._mlist.subject_prefix = '[XTEST] ' - msg = email.message_from_string("""\ -From: aperson@dom.ain -Subject: About Mailman... - -""", Message.Message) - CookHeaders.process(self._mlist, msg, {}) - self.assertEqual(str(msg['subject']), '[XTEST] About Mailman...') - - def test_no_subject_munging_for_digests(self): - self._mlist.subject_prefix = '[XTEST] ' - msg = email.message_from_string("""\ -From: aperson@dom.ain -Subject: About Mailman... - -""", Message.Message) - CookHeaders.process(self._mlist, msg, {'isdigest': 1}) - self.assertEqual(msg['subject'], 'About Mailman...') - - def test_no_subject_munging_for_fasttrack(self): - self._mlist.subject_prefix = '[XTEST] ' - msg = email.message_from_string("""\ -From: aperson@dom.ain -Subject: About Mailman... - -""", Message.Message) - CookHeaders.process(self._mlist, msg, {'_fasttrack': 1}) - self.assertEqual(msg['subject'], 'About Mailman...') - - def test_no_subject_munging_has_prefix(self): - self._mlist.subject_prefix = '[XTEST] ' - msg = email.message_from_string("""\ -From: aperson@dom.ain -Subject: Re: [XTEST] About Mailman... - -""", Message.Message) - CookHeaders.process(self._mlist, msg, {}) - # prefixing depends on mm_cfg.py - self.failUnless(str(msg['subject']) == 'Re: [XTEST] About Mailman...' or - str(msg['subject']) == '[XTEST] Re: About Mailman...') - - def test_reply_to_list(self): - eq = self.assertEqual - mlist = self._mlist - mlist.reply_goes_to_list = 1 - mlist.from_is_list = 0 - msg = email.message_from_string("""\ -From: aperson@dom.ain - -""", Message.Message) - msgdata = {} - CookHeaders.process(mlist, msg, msgdata) - eq(msgdata['add_header']['Reply-To'], '_xtest@dom.ain') - eq(msg.get_all('reply-to'), None) - - def test_reply_to_list_fil(self): - eq = self.assertEqual - mlist = self._mlist - mlist.reply_goes_to_list = 1 - mlist.from_is_list = 1 - msg = email.message_from_string("""\ -From: aperson@dom.ain - -""", Message.Message) - msgdata = {} - CookHeaders.process(mlist, msg, msgdata) - eq(msgdata['add_header']['Reply-To'], - '_xtest@dom.ain') - eq(msgdata['add_header']['Cc'], - 'aperson@dom.ain') - eq(msg.get_all('reply-to'), None) - eq(msg.get_all('cc'), None) - - def test_reply_to_list_with_strip(self): - eq = self.assertEqual - mlist = self._mlist - mlist.reply_goes_to_list = 1 - mlist.first_strip_reply_to = 1 - mlist.from_is_list = 0 - msg = email.message_from_string("""\ -From: aperson@dom.ain -Reply-To: bperson@dom.ain - -""", Message.Message) - msgdata = {} - CookHeaders.process(mlist, msg, msgdata) - eq(msgdata['add_header']['Reply-To'], '_xtest@dom.ain') - eq(msg.get_all('reply-to'), ['bperson@dom.ain']) - - def test_reply_to_list_with_strip_fil(self): - eq = self.assertEqual - mlist = self._mlist - mlist.reply_goes_to_list = 1 - mlist.first_strip_reply_to = 1 - mlist.from_is_list = 1 - msg = email.message_from_string("""\ -From: aperson@dom.ain -Reply-To: bperson@dom.ain - -""", Message.Message) - msgdata = {} - CookHeaders.process(mlist, msg, msgdata) - eq(msgdata['add_header']['Reply-To'], - '_xtest@dom.ain') - eq(msgdata['add_header']['Cc'], - 'aperson@dom.ain') - eq(msg.get_all('reply-to'), ['bperson@dom.ain']) - eq(msg.get_all('cc'), None) - - - def test_reply_to_explicit(self): - eq = self.assertEqual - mlist = self._mlist - mlist.reply_goes_to_list = 2 - mlist.from_is_list = 0 - mlist.reply_to_address = 'mlist@dom.ain' - msg = email.message_from_string("""\ -From: aperson@dom.ain - -""", Message.Message) - msgdata = {} - CookHeaders.process(mlist, msg, msgdata) - eq(msgdata['add_header']['Reply-To'], 'mlist@dom.ain') - eq(msg.get_all('reply-to'), None) - - def test_reply_to_explicit_fil(self): - eq = self.assertEqual - mlist = self._mlist - mlist.reply_goes_to_list = 2 - mlist.from_is_list = 1 - mlist.reply_to_address = 'mlist@dom.ain' - msg = email.message_from_string("""\ -From: aperson@dom.ain - -""", Message.Message) - msgdata = {} - CookHeaders.process(mlist, msg, msgdata) - eq(msgdata['add_header']['Reply-To'], - 'mlist@dom.ain') - eq(msgdata['add_header']['Cc'], - 'aperson@dom.ain') - eq(msg.get_all('reply-to'), None) - eq(msg.get_all('cc'), None) - - def test_reply_to_explicit_with_strip(self): - eq = self.assertEqual - mlist = self._mlist - mlist.reply_goes_to_list = 2 - mlist.first_strip_reply_to = 1 - mlist.from_is_list = 0 - mlist.reply_to_address = 'mlist@dom.ain' - msg = email.message_from_string("""\ -From: aperson@dom.ain -Reply-To: bperson@dom.ain - -""", Message.Message) - msgdata = {} - - CookHeaders.process(self._mlist, msg, msgdata) - eq(msgdata['add_header']['Reply-To'], 'mlist@dom.ain') - eq(msg.get_all('reply-to'), ['bperson@dom.ain']) - - def test_reply_to_explicit_with_strip_fil(self): - eq = self.assertEqual - mlist = self._mlist - mlist.reply_goes_to_list = 2 - mlist.first_strip_reply_to = 1 - mlist.from_is_list = 1 - mlist.reply_to_address = 'mlist@dom.ain' - msg = email.message_from_string("""\ -From: aperson@dom.ain -Reply-To: bperson@dom.ain - -""", Message.Message) - msgdata = {} - - CookHeaders.process(self._mlist, msg, msgdata) - eq(msgdata['add_header']['Reply-To'], - 'mlist@dom.ain') - eq(msgdata['add_header']['Cc'], - 'aperson@dom.ain') - eq(msg.get_all('reply-to'), ['bperson@dom.ain']) - eq(msg.get_all('cc'), None) - - def test_reply_to_extends_to_list(self): - eq = self.assertEqual - mlist = self._mlist - mlist.reply_goes_to_list = 1 - mlist.first_strip_reply_to = 0 - mlist.from_is_list = 0 - msg = email.message_from_string("""\ -From: aperson@dom.ain -Reply-To: bperson@dom.ain - -""", Message.Message) - msgdata = {} - - CookHeaders.process(mlist, msg, msgdata) - eq(msgdata['add_header']['Reply-To'], - 'bperson@dom.ain, _xtest@dom.ain') - - def test_reply_to_extends_to_list_fil(self): - eq = self.assertEqual - mlist = self._mlist - mlist.reply_goes_to_list = 1 - mlist.first_strip_reply_to = 0 - mlist.from_is_list = 1 - msg = email.message_from_string("""\ -From: aperson@dom.ain -Reply-To: bperson@dom.ain - -""", Message.Message) - msgdata = {} - - CookHeaders.process(mlist, msg, msgdata) - eq(msgdata['add_header']['Reply-To'], - 'bperson@dom.ain, _xtest@dom.ain') - eq(msgdata['add_header']['Cc'], - 'aperson@dom.ain') - eq(msg.get_all('reply-to'), ['bperson@dom.ain']) - eq(msg.get_all('cc'), None) - - def test_reply_to_extends_to_explicit(self): - eq = self.assertEqual - mlist = self._mlist - mlist.reply_goes_to_list = 2 - mlist.first_strip_reply_to = 0 - mlist.from_is_list = 0 - mlist.reply_to_address = 'mlist@dom.ain' - msg = email.message_from_string("""\ -From: aperson@dom.ain -Reply-To: bperson@dom.ain - -""", Message.Message) - msgdata = {} - CookHeaders.process(mlist, msg, msgdata) - eq(msgdata['add_header']['Reply-To'], - 'mlist@dom.ain, bperson@dom.ain') - - def test_reply_to_extends_to_explicit_fil(self): - eq = self.assertEqual - mlist = self._mlist - mlist.reply_goes_to_list = 2 - mlist.first_strip_reply_to = 0 - mlist.from_is_list = 1 - mlist.reply_to_address = 'mlist@dom.ain' - msg = email.message_from_string("""\ -From: aperson@dom.ain -Reply-To: bperson@dom.ain - -""", Message.Message) - msgdata = {} - CookHeaders.process(mlist, msg, msgdata) - eq(msgdata['add_header']['Reply-To'], - 'mlist@dom.ain, bperson@dom.ain') - eq(msgdata['add_header']['Cc'], - 'aperson@dom.ain') - eq(msg.get_all('reply-to'), ['bperson@dom.ain']) - eq(msg.get_all('cc'), None) - - def test_list_headers_nolist(self): - eq = self.assertEqual - msg = email.message_from_string("""\ -From: aperson@dom.ain - -""", Message.Message) - CookHeaders.process(self._mlist, msg, {'_nolist': 1}) - eq(msg['list-id'], None) - eq(msg['list-help'], None) - eq(msg['list-unsubscribe'], None) - eq(msg['list-subscribe'], None) - eq(msg['list-post'], None) - eq(msg['list-archive'], None) +List info page: http://www.dom.ain/mailman/listinfo/_xtest +Your preferences: http://www.dom.ain/mailman/options/_xtest/aperson%40dom.ain +""") + # Make sure we dequeued the only message + eq(len(self._sb.files()), 0) - def test_list_headers(self): + def test_ack_with_subject_and_body_and_headers_and_attachments_and_embedded(self): eq = self.assertEqual - self._mlist.archive = 1 + self._mlist.setMemberOption( + 'aperson@dom.ain', mm_cfg.AcknowledgePosts, 1) + eq(len(self._sb.files()), 0) msg = email.message_from_string("""\ From: aperson@dom.ain +Subject: Wish you were here +Date: Mon, 01 Jan 2001 00:00:00 -0000 +Message-ID: +Content-Type: multipart/mixed; boundary="===============1234567890==" -""", Message.Message) - oldval = mm_cfg.DEFAULT_URL_HOST - mm_cfg.DEFAULT_URL_HOST = 'www.dom.ain' - try: - CookHeaders.process(self._mlist, msg, {}) - finally: - mm_cfg.DEFAULT_URL_HOST = oldval - eq(msg['list-id'], '<_xtest.dom.ain>') - eq(msg['list-help'], '') - eq(msg['list-unsubscribe'], - ',' - '\n ') - eq(msg['list-subscribe'], - ',' - '\n ') - eq(msg['list-post'], '') - eq(msg['list-archive'], '') +--===============1234567890== +Content-Type: multipart/alternative; boundary="===============0987654321==" - def test_list_headers_with_description(self): - eq = self.assertEqual - self._mlist.archive = 1 - self._mlist.description = 'A Test List' - msg = email.message_from_string("""\ -From: aperson@dom.ain +--===============0987654321== +Content-Type: text/plain; charset=us-ascii -""", Message.Message) - CookHeaders.process(self._mlist, msg, {}) - eq(str(msg['list-id']), 'A Test List <_xtest.dom.ain>') - eq(msg['list-help'], '') - eq(msg['list-unsubscribe'], - ',' - '\n ') - eq(msg['list-subscribe'], - ',' - '\n ') - eq(msg['list-post'], '') +This is the body of the message. +--===============0987654321== +Content-Type: text/html; charset=us-ascii - -class TestDecorate(TestBase): - def test_short_circuit(self): - msgdata = {'isdigest': 1} - rtn = Decorate.process(self._mlist, None, msgdata) - # Not really a great test, but there's little else to assert - self.assertEqual(rtn, None) + + +This is the body of the message. + + - def test_no_multipart(self): - mlist = self._mlist - mlist.msg_header = 'header\n' - mlist.msg_footer = 'footer' - msg = email.message_from_string("""\ -From: aperson@dom.ain +--===============0987654321==-- -Here is a message. -""") - Decorate.process(self._mlist, msg, {}) - self.assertEqual(msg.get_payload(), """\ -header -Here is a message. -footer +--===============1234567890== +Content-Type: application/octet-stream +Content-Disposition: attachment; filename="test.txt" + +This is an attachment. +--===============1234567890==-- """) + Acknowledge.process(self._mlist, msg, {}) + files = self._sb.files() + eq(len(files), 1) + qmsg, qdata = self._sb.dequeue(files[0]) + # Check the .db file + eq(qdata.get('listname'), '_xtest') + eq(qdata.get('recips'), ['aperson@dom.ain']) + eq(qdata.get('version'), 3) + # Check the .pck + eq(str(qmsg['subject']), '_xtest post acknowledgement') + eq(qmsg['to'], 'aperson@dom.ain') + eq(qmsg['from'], '_xtest-bounces@dom.ain') + eq(qmsg.get_content_type(), 'text/plain') + eq(qmsg.get_param('charset'), 'us-ascii') + msgid = qmsg['message-id'] + self.failUnless(msgid.startswith('')) + eq(qmsg.get_payload(), """\ +Your message entitled - def test_no_multipart_template(self): - mlist = self._mlist - mlist.msg_header = '%(real_name)s header\n' - mlist.msg_footer = '%(real_name)s footer' - mlist.real_name = 'XTest' - msg = email.message_from_string("""\ -From: aperson@dom.ain + Wish you were here -Here is a message. -""") - Decorate.process(self._mlist, msg, {}) - self.assertEqual(msg.get_payload(), """\ -XTest header -Here is a message. -XTest footer +was successfully received by the _xtest mailing list. + +List info page: http://www.dom.ain/mailman/listinfo/_xtest +Your preferences: http://www.dom.ain/mailman/options/_xtest/aperson%40dom.ain """) + # Make sure we dequeued the only message + eq(len(self._sb.files()), 0) - def test_no_multipart_type_error(self): - mlist = self._mlist - mlist.msg_header = '%(real_name) header\n' - mlist.msg_footer = '%(real_name) footer' - mlist.real_name = 'XTest' + def test_ack_with_subject_and_body_and_headers_and_attachments_and_embedded_and_headers(self): + eq = self.assertEqual + self._mlist.setMemberOption( + 'aperson@dom.ain', mm_cfg.AcknowledgePosts, 1) + eq(len(self._sb.files()), 0) msg = email.message_from_string("""\ From: aperson@dom.ain +Subject: Wish you were here +Date: Mon, 01 Jan 2001 00:00:00 -0000 +Message-ID: +Content-Type: multipart/mixed; boundary="===============1234567890==" +MIME-Version: 1.0 -Here is a message. -""") - Decorate.process(self._mlist, msg, {}) - self.assertEqual(msg.get_payload(), """\ -%(real_name) header -Here is a message. -%(real_name) footer -""") +--===============1234567890== +Content-Type: multipart/alternative; boundary="===============0987654321==" +MIME-Version: 1.0 - def test_no_multipart_value_error(self): - mlist = self._mlist - # These will generate warnings in logs/error - mlist.msg_header = '%(real_name)p header\n' - mlist.msg_footer = '%(real_name)p footer' - mlist.real_name = 'XTest' - msg = email.message_from_string("""\ -From: aperson@dom.ain +--===============0987654321== +Content-Type: text/plain; charset=us-ascii -Here is a message. -""") - Decorate.process(self._mlist, msg, {}) - self.assertEqual(msg.get_payload(), """\ -%(real_name)p header -Here is a message. -%(real_name)p footer -""") +This is the body of the message. - def test_no_multipart_missing_key(self): - mlist = self._mlist - mlist.msg_header = '%(spooge)s header\n' - mlist.msg_footer = '%(spooge)s footer' - msg = email.message_from_string("""\ -From: aperson@dom.ain +--===============0987654321== +Content-Type: text/html; charset=us-ascii -Here is a message. -""") - Decorate.process(self._mlist, msg, {}) - self.assertEqual(msg.get_payload(), """\ -%(spooge)s header -Here is a message. -%(spooge)s footer -""") + + +This is the body of the message. + + - def test_multipart(self): - eq = self.ndiffAssertEqual - mlist = self._mlist - mlist.msg_header = 'header' - mlist.msg_footer = 'footer' - msg1 = email.message_from_string("""\ -From: aperson@dom.ain +--===============0987654321==-- -Here is the first message. +--===============1234567890== +Content-Type: application/octet-stream +Content-Disposition: attachment; filename="test.txt" + +This is an attachment. +--===============1234567890==-- """) - msg2 = email.message_from_string("""\ -From: bperson@dom.ain + Acknowledge.process(self._mlist, msg, {}) + files = self._sb.files() + eq(len(files), 1) + qmsg, qdata = self._sb.dequeue(files[0]) + # Check the .db file + eq(qdata.get('listname'), '_xtest') + eq(qdata.get('recips'), ['aperson@dom.ain']) + eq(qdata.get('version'), 3) + # Check the .pck + eq(str(qmsg['subject']), '_xtest post acknowledgement') + eq(qmsg['to'], 'aperson@dom.ain') + eq(qmsg['from'], '_xtest-bounces@dom.ain') + eq(qmsg.get_content_type(), 'text/plain') + eq(qmsg.get_param('charset'), 'us-ascii') + msgid = qmsg['message-id'] + self.failUnless(msgid.startswith('')) + eq(qmsg.get_payload(), """\ +Your message entitled + + Wish you were here + +was successfully received by the _xtest mailing list. -Here is the second message. +List info page: http://www.dom.ain/mailman/listinfo/_xtest +Your preferences: http://www.dom.ain/mailman/options/_xtest/aperson%40dom.ain """) - msg = Message.Message() - msg.set_type('multipart/mixed') - msg.set_boundary('BOUNDARY') - msg.attach(msg1) - msg.attach(msg2) - Decorate.process(self._mlist, msg, {}) - eq(msg.as_string(unixfrom=0), """\ -MIME-Version: 1.0 -Content-Type: multipart/mixed; boundary="BOUNDARY" + # Make sure we dequeued the only message + eq(len(self._sb.files()), 0) ---BOUNDARY -Content-Type: text/plain; charset="us-ascii" + def test_ack_with_subject_and_body_and_headers_and_attachments_and_embedded_and_headers_and_headers(self): + eq = self.assertEqual + self._mlist.setMemberOption( + 'aperson@dom.ain', mm_cfg.AcknowledgePosts, 1) + eq(len(self._sb.files()), 0) + msg = email.message_from_string("""\ +From: aperson@dom.ain +Subject: Wish you were here +Date: Mon, 01 Jan 2001 00:00:00 -0000 +Message-ID: +Content-Type: multipart/mixed; boundary="===============1234567890==" MIME-Version: 1.0 -Content-Transfer-Encoding: 7bit -Content-Disposition: inline +X-Mailer: Python Mailman -header +--===============1234567890== +Content-Type: multipart/alternative; boundary="===============0987654321==" +MIME-Version: 1.0 ---BOUNDARY -From: aperson@dom.ain +--===============0987654321== +Content-Type: text/plain; charset=us-ascii -Here is the first message. +This is the body of the message. ---BOUNDARY -From: bperson@dom.ain +--===============0987654321== +Content-Type: text/html; charset=us-ascii -Here is the second message. + + +This is the body of the message. + + ---BOUNDARY -Content-Type: text/plain; charset="us-ascii" -MIME-Version: 1.0 -Content-Transfer-Encoding: 7bit -Content-Disposition: inline +--===============0987654321==-- -footer +--===============1234567890== +Content-Type: application/octet-stream +Content-Disposition: attachment; filename="test.txt" ---BOUNDARY-- +This is an attachment. +--===============1234567890==-- """) + Acknowledge.process(self._mlist, msg, {}) + files = self._sb.files() + eq(len(files), 1) + qmsg, qdata = self._sb.dequeue(files[0]) + # Check the .db file + eq(qdata.get('listname'), '_xtest') + eq(qdata.get('recips'), ['aperson@dom.ain']) + eq(qdata.get('version'), 3) + # Check the .pck + eq(str(qmsg['subject']), '_xtest post acknowledgement') + eq(qmsg['to'], 'aperson@dom.ain') + eq(qmsg['from'], '_xtest-bounces@dom.ain') + eq(qmsg.get_content_type(), 'text/plain') + eq(qmsg.get_param('charset'), 'us-ascii') + msgid = qmsg['message-id'] + self.failUnless(msgid.startswith('')) + eq(qmsg.get_payload(), """\ +Your message entitled - def test_image(self): - eq = self.assertEqual - mlist = self._mlist - mlist.msg_header = 'header\n' - mlist.msg_footer = 'footer' - msg = email.message_from_string("""\ -From: aperson@dom.ain -Content-type: image/x-spooge + Wish you were here -IMAGEDATAIMAGEDATAIMAGEDATA -""") - Decorate.process(self._mlist, msg, {}) - eq(len(msg.get_payload()), 3) - self.assertEqual(msg.get_payload(1).get_payload(), """\ -IMAGEDATAIMAGEDATAIMAGEDATA +was successfully received by the _xtest mailing list. + +List info page: http://www.dom.ain/mailman/listinfo/_xtest +Your preferences: http://www.dom.ain/mailman/options/_xtest/aperson%40dom.ain """) + # Make sure we dequeued the only message + eq(len(self._sb.files()), 0) - def test_personalize_assert(self): - raises = self.assertRaises - raises(AssertionError, Decorate.process, - self._mlist, None, {'personalize': 1}) - raises(AssertionError, Decorate.process, - self._mlist, None, {'personalize': 1, - 'recips': [1, 2, 3]}) + +class TestAfterDelivery(TestBase): + # Both msg and msgdata are ignored + def test_process(self): + mlist = self._mlist + last_post_time = mlist.last_post_time + post_id = mlist.post_id + AfterDelivery.process(mlist, None, None) + self.failUnless(mlist.last_post_time > last_post_time) + self.assertEqual(mlist.post_id, post_id + 1) - -class TestFileRecips(TestBase): +class TestApprove(TestBase): def test_short_circuit(self): - msgdata = {'recips': 1} - rtn = FileRecips.process(self._mlist, None, msgdata) + msgdata = {'approved': 1} + rtn = Approve.process(self._mlist, Message(), msgdata) # Not really a great test, but there's little else to assert self.assertEqual(rtn, None) - def test_file_nonexistant(self): + def test_approved_moderator(self): + mlist = self._mlist + mlist.mod_password = password('wazoo') + msg = email.message_from_string("""\ +Approved: wazoo + +""") + msgdata = {} + Approve.process(mlist, msg, msgdata) + self.failUnless('approved' in msgdata) + self.assertEqual(msgdata['approved'], 1) + + def test_approve_moderator(self): + mlist = self._mlist + mlist.mod_password = password('wazoo') + msg = email.message_from_string("""\ +Approve: wazoo + +""") msgdata = {} - FileRecips.process(self._mlist, None, msgdata) - self.assertEqual(msgdata.get('recips'), []) + Approve.process(mlist, msg, msgdata) + self.failUnless('approved' in msgdata) + self.assertEqual(msgdata['approved'], 1) - def test_file_exists_no_sender(self): + def test_approved_admin(self): + mlist = self._mlist + mlist.password = password('wazoo') msg = email.message_from_string("""\ -To: yall@dom.ain +Approved: wazoo -""", Message.Message) +""") msgdata = {} - file = os.path.join(self._mlist.fullpath(), 'members.txt') - addrs = ['aperson@dom.ain', 'bperson@dom.ain', - 'cperson@dom.ain', 'dperson@dom.ain'] - fp = open(file, 'w') - try: - for addr in addrs: - print(addr, file=fp) - fp.close() - FileRecips.process(self._mlist, msg, msgdata) - self.assertEqual(msgdata.get('recips'), addrs) - finally: - try: - os.unlink(file) - except OSError as e: - if e.errno != e.ENOENT: raise + Approve.process(mlist, msg, msgdata) + self.failUnless('approved' in msgdata) + self.assertEqual(msgdata['approved'], 1) - def test_file_exists_no_member(self): + def test_approve_admin(self): + mlist = self._mlist + mlist.password = password('wazoo') msg = email.message_from_string("""\ -From: eperson@dom.ain -To: yall@dom.ain +Approve: wazoo -""", Message.Message) +""") msgdata = {} - file = os.path.join(self._mlist.fullpath(), 'members.txt') - addrs = ['aperson@dom.ain', 'bperson@dom.ain', - 'cperson@dom.ain', 'dperson@dom.ain'] - fp = open(file, 'w') - try: - for addr in addrs: - print(addr, file=fp) - fp.close() - FileRecips.process(self._mlist, msg, msgdata) - self.assertEqual(msgdata.get('recips'), addrs) - finally: - try: - os.unlink(file) - except OSError as e: - if e.errno != e.ENOENT: raise + Approve.process(mlist, msg, msgdata) + self.failUnless('approved' in msgdata) + self.assertEqual(msgdata['approved'], 1) - def test_file_exists_is_member(self): + def test_unapproved(self): + mlist = self._mlist + mlist.password = password('zoowa') msg = email.message_from_string("""\ -From: aperson@dom.ain -To: yall@dom.ain +Approve: wazoo -""", Message.Message) +""") msgdata = {} - file = os.path.join(self._mlist.fullpath(), 'members.txt') - addrs = ['aperson@dom.ain', 'bperson@dom.ain', - 'cperson@dom.ain', 'dperson@dom.ain'] - fp = open(file, 'w') - try: - for addr in addrs: - print(addr, file=fp) - self._mlist.addNewMember(addr) - fp.close() - FileRecips.process(self._mlist, msg, msgdata) - self.assertEqual(msgdata.get('recips'), addrs[1:]) - finally: - try: - os.unlink(file) - except OSError as e: - if e.errno != e.ENOENT: raise + Approve.process(mlist, msg, msgdata) + self.assertEqual(msgdata.get('approved'), None) + + def test_trip_beentheres(self): + mlist = self._mlist + msg = email.message_from_string("""\ +X-BeenThere: %s +""" % mlist.GetListEmail()) + self.assertRaises(Errors.LoopError, Approve.process, mlist, msg, {}) - -class TestHold(TestBase): + +class TestCalcRecips(TestBase): def setUp(self): TestBase.setUp(self) - self._mlist.administrivia = 1 - self._mlist.respond_to_post_requests = 0 - self._mlist.admin_immed_notify = 0 - # We're going to want to inspect this queue directory - self._sb = Switchboard(mm_cfg.VIRGINQUEUE_DIR) - - def tearDown(self): - for f in os.listdir(mm_cfg.VIRGINQUEUE_DIR): - os.unlink(os.path.join(mm_cfg.VIRGINQUEUE_DIR, f)) - TestBase.tearDown(self) - try: - os.unlink(os.path.join(mm_cfg.DATA_DIR, 'pending.db')) - except OSError as e: - if e.errno != errno.ENOENT: raise - for f in [holdfile for holdfile in os.listdir(mm_cfg.DATA_DIR) - if holdfile.startswith('heldmsg-')]: - os.unlink(os.path.join(mm_cfg.DATA_DIR, f)) + # Add a bunch of regular members + mlist = self._mlist + mlist.addNewMember('aperson@dom.ain') + mlist.addNewMember('bperson@dom.ain') + mlist.addNewMember('cperson@dom.ain') + # And a bunch of digest members + mlist.addNewMember('dperson@dom.ain', digest=1) + mlist.addNewMember('eperson@dom.ain', digest=1) + mlist.addNewMember('fperson@dom.ain', digest=1) def test_short_circuit(self): - msgdata = {'approved': 1} - rtn = Hold.process(self._mlist, None, msgdata) + msgdata = {'recips': 1} + rtn = CalcRecips.process(self._mlist, None, msgdata) # Not really a great test, but there's little else to assert self.assertEqual(rtn, None) - def test_administrivia(self): + def test_simple_path(self): + msgdata = {} msg = email.message_from_string("""\ -From: aperson@dom.ain -Subject: unsubscribe +From: dperson@dom.ain -""", Message.Message) - self.assertRaises(Hold.Administrivia, Hold.process, - self._mlist, msg, {}) +""", Message) + CalcRecips.process(self._mlist, msg, msgdata) + self.failUnless('recips' in msgdata) + recips = msgdata['recips'] + recips.sort() + self.assertEqual(recips, ['aperson@dom.ain', 'bperson@dom.ain', + 'cperson@dom.ain']) - def test_max_recips(self): - self._mlist.max_num_recipients = 5 - msg = email.message_from_string("""\ -From: aperson@dom.ain -To: _xtest@dom.ain, bperson@dom.ain -Cc: cperson@dom.ain -Cc: dperson@dom.ain (Jimmy D. Person) -To: Billy E. Person - -Hey folks! -""", Message.Message) - self.assertRaises(Hold.TooManyRecipients, Hold.process, - self._mlist, msg, {}) - - def test_implicit_destination(self): - self._mlist.require_explicit_destination = 1 + def test_exclude_sender(self): + msgdata = {} msg = email.message_from_string("""\ -From: aperson@dom.ain -Subject: An implicit message +From: cperson@dom.ain -""", Message.Message) - self.assertRaises(Hold.ImplicitDestination, Hold.process, - self._mlist, msg, {}) +""", Message) + self._mlist.setMemberOption('cperson@dom.ain', + mm_cfg.DontReceiveOwnPosts, 1) + CalcRecips.process(self._mlist, msg, msgdata) + self.failUnless('recips' in msgdata) + recips = msgdata['recips'] + recips.sort() + self.assertEqual(recips, ['aperson@dom.ain', 'bperson@dom.ain']) - def test_implicit_destination_fromusenet(self): - self._mlist.require_explicit_destination = 1 + def test_urgent_moderator(self): + self._mlist.mod_password = password('xxXXxx') + msgdata = {} msg = email.message_from_string("""\ -From: aperson@dom.ain -Subject: An implicit message +From: dperson@dom.ain +Urgent: xxXXxx -""", Message.Message) - rtn = Hold.process(self._mlist, msg, {'fromusenet': 1}) - self.assertEqual(rtn, None) +""", Message) + CalcRecips.process(self._mlist, msg, msgdata) + self.failUnless('recips' in msgdata) + recips = msgdata['recips'] + recips.sort() + self.assertEqual(recips, ['aperson@dom.ain', 'bperson@dom.ain', + 'cperson@dom.ain', 'dperson@dom.ain', + 'eperson@dom.ain', 'fperson@dom.ain']) - def test_suspicious_header(self): - self._mlist.bounce_matching_headers = 'From: .*person@(blah.)?dom.ain' + def test_urgent_admin(self): + self._mlist.mod_password = password('yyYYyy') + self._mlist.password = password('xxXXxx') + msgdata = {} msg = email.message_from_string("""\ -From: aperson@dom.ain -To: _xtest@dom.ain -Subject: An implicit message +From: dperson@dom.ain +Urgent: xxXXxx -""", Message.Message) - self.assertRaises(Hold.SuspiciousHeaders, Hold.process, - self._mlist, msg, {}) +""", Message) + CalcRecips.process(self._mlist, msg, msgdata) + self.failUnless('recips' in msgdata) + recips = msgdata['recips'] + recips.sort() + self.assertEqual(recips, ['aperson@dom.ain', 'bperson@dom.ain', + 'cperson@dom.ain', 'dperson@dom.ain', + 'eperson@dom.ain', 'fperson@dom.ain']) - def test_suspicious_header_ok(self): - self._mlist.bounce_matching_headers = 'From: .*person@blah.dom.ain' + def test_urgent_reject(self): + self._mlist.mod_password = password('yyYYyy') + self._mlist.password = password('xxXXxx') + msgdata = {} msg = email.message_from_string("""\ -From: aperson@dom.ain -To: _xtest@dom.ain -Subject: An implicit message +From: dperson@dom.ain +Urgent: zzZZzz -""", Message.Message) - rtn = Hold.process(self._mlist, msg, {}) - self.assertEqual(rtn, None) +""", Message) + self.assertRaises(Errors.RejectMessage, + CalcRecips.process, + self._mlist, msg, msgdata) - def test_max_message_size(self): - self._mlist.max_message_size = 1 - msg = email.message_from_string("""\ -From: aperson@dom.ain -To: _xtest@dom.ain - -xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -""", Message.Message) - self.assertRaises(Hold.MessageTooBig, Hold.process, - self._mlist, msg, {}) - - def test_hold_notifications(self): - eq = self.assertEqual - self._mlist.respond_to_post_requests = 1 - self._mlist.admin_immed_notify = 1 - # Now cause an implicit destination hold - msg = email.message_from_string("""\ -From: aperson@dom.ain + # BAW: must test the do_topic_filters() path... -""", Message.Message) - self.assertRaises(Hold.ImplicitDestination, Hold.process, - self._mlist, msg, {}) - # Now we have to make sure there are two messages in the virgin queue, - # one to the sender and one to the list owners. - qfiles = {} - files = self._sb.files() - eq(len(files), 2) - for filebase in files: - qmsg, qdata = self._sb.dequeue(filebase) - to = qmsg['to'] - qfiles[to] = qmsg, qdata - # BAW: We could be testing many other attributes of either the - # messages or the metadata files... - keys = list(qfiles.keys()) - keys.sort() - eq(keys, ['_xtest-owner@dom.ain', 'aperson@dom.ain']) - # Get the pending cookie from the message to the sender - pmsg, pdata = qfiles['aperson@dom.ain'] - confirmlines = pmsg.get_payload().split('\n') - cookie = confirmlines[-3].split('/')[-1] - # We also need to make sure there's an entry in the Pending database - # for the heold message. - data = self._mlist.pend_confirm(cookie) - eq(data, ('H', 1)) - heldmsg = os.path.join(mm_cfg.DATA_DIR, 'heldmsg-_xtest-1.pck') - self.failUnless(os.path.exists(heldmsg)) - os.unlink(heldmsg) - holdfiles = [f for f in os.listdir(mm_cfg.DATA_DIR) - if f.startswith('heldmsg-')] - eq(len(holdfiles), 0) - - - -class TestMimeDel(TestBase): + +class TestCleanse(TestBase): def setUp(self): TestBase.setUp(self) - self._mlist.filter_content = 1 - self._mlist.filter_mime_types = ['image/jpeg'] - self._mlist.pass_mime_types = [] - self._mlist.convert_html_to_plaintext = 1 - self._mlist.collapse_alternatives = 1 - def test_outer_matches(self): + def test_simple_cleanse(self): + eq = self.assertEqual msg = email.message_from_string("""\ From: aperson@dom.ain -Content-Type: image/jpeg -MIME-Version: 1.0 +Approved: yes +Urgent: indeed +Reply-To: bperson@dom.ain +Sender: asystem@dom.ain +Return-Receipt-To: another@dom.ain +Disposition-Notification-To: athird@dom.ain +X-Confirm-Reading-To: afourth@dom.ain +X-PMRQC: afifth@dom.ain +Subject: a message to you -xxxxx -""") - self.assertRaises(Errors.DiscardMessage, MimeDel.process, - self._mlist, msg, {}) +""", Message) + Cleanse.process(self._mlist, msg, {}) + eq(msg['approved'], None) + eq(msg['urgent'], None) + eq(msg['return-receipt-to'], None) + eq(msg['disposition-notification-to'], None) + eq(msg['x-confirm-reading-to'], None) + eq(msg['x-pmrqc'], None) + eq(msg['from'], 'aperson@dom.ain') + eq(msg['reply-to'], 'bperson@dom.ain') + eq(msg['sender'], 'asystem@dom.ain') + eq(msg['subject'], 'a message to you') - def test_strain_multipart(self): + def test_anon_cleanse(self): eq = self.assertEqual msg = email.message_from_string("""\ From: aperson@dom.ain -Content-Type: multipart/mixed; boundary=BOUNDARY -MIME-Version: 1.0 +Approved: yes +Urgent: indeed +Reply-To: bperson@dom.ain +Sender: asystem@dom.ain +Return-Receipt-To: another@dom.ain +Disposition-Notification-To: athird@dom.ain +X-Confirm-Reading-To: afourth@dom.ain +X-PMRQC: afifth@dom.ain +Subject: a message to you ---BOUNDARY -Content-Type: image/jpeg -MIME-Version: 1.0 +""", Message) + self._mlist.anonymous_list = 1 + Cleanse.process(self._mlist, msg, {}) + eq(msg['approved'], None) + eq(msg['urgent'], None) + eq(msg['return-receipt-to'], None) + eq(msg['disposition-notification-to'], None) + eq(msg['x-confirm-reading-to'], None) + eq(msg['x-pmrqc'], None) + eq(len(msg.get_all('from')), 1) + eq(len(msg.get_all('reply-to')), 1) + eq(msg['from'], '_xtest@dom.ain') + eq(msg['reply-to'], '_xtest@dom.ain') + eq(msg['sender'], None) + eq(msg['subject'], 'a message to you') -xxx ---BOUNDARY -Content-Type: image/gif -MIME-Version: 1.0 +class TestCookHeaders(TestBase): + def test_transform_noack_to_xack(self): + eq = self.assertEqual + msg = email.message_from_string("""\ +X-Ack: yes -yyy ---BOUNDARY-- -""") - MimeDel.process(self._mlist, msg, {}) - self.assertTrue(not msg.is_multipart()) - eq(msg.get_content_type(), 'image/gif') - eq(msg.get_payload(), 'yyy') +""", Message) + CookHeaders.process(self._mlist, msg, {'noack': 1}) + eq(len(msg.get_all('x-ack')), 1) + eq(msg['x-ack'], 'no') - def test_collapse_multipart_alternative(self): - eq = self.assertEqual + def test_original_sender(self): msg = email.message_from_string("""\ From: aperson@dom.ain -Content-Type: multipart/mixed; boundary=BOUNDARY -MIME-Version: 1.0 - ---BOUNDARY -Content-Type: multipart/alternative; boundary=BOUND2 -MIME-Version: 1.0 ---BOUND2 -Content-Type: image/jpeg -MIME-Version: 1.0 +""", Message) + msgdata = {} + CookHeaders.process(self._mlist, msg, msgdata) + self.assertEqual(msgdata.get('original_sender'), 'aperson@dom.ain') -xxx + def test_no_original_sender(self): + msg = email.message_from_string("""\ +Subject: about this message ---BOUND2 -Content-Type: image/gif -MIME-Version: 1.0 +""", Message) + msgdata = {} + CookHeaders.process(self._mlist, msg, msgdata) + self.assertEqual(msgdata.get('original_sender'), '') -yyy ---BOUND2-- + def test_xbeenthere(self): + msg = email.message_from_string("""\ +From: aperson@dom.ain ---BOUNDARY-- -""") - MimeDel.process(self._mlist, msg, {}) - self.assertTrue(not msg.is_multipart()) - eq(msg.get_content_type(), 'image/gif') - eq(msg.get_payload(), 'yyy') +""", Message) + CookHeaders.process(self._mlist, msg, {}) + self.assertEqual(msg['x-beenthere'], '_xtest@dom.ain') - def test_convert_to_plaintext(self): - # BAW: This test is dependent on your particular lynx version + def test_multiple_xbeentheres(self): eq = self.assertEqual msg = email.message_from_string("""\ From: aperson@dom.ain -Content-Type: text/html -MIME-Version: 1.0 +X-BeenThere: alist@another.dom.ain - - -""") - MimeDel.process(self._mlist, msg, {}) - eq(msg.get_content_type(), 'text/plain') - #eq(msg.get_payload(), '\n\n\n') - eq(msg.get_payload().strip(), '') +""", Message) + CookHeaders.process(self._mlist, msg, {}) + eq(len(msg.get_all('x-beenthere')), 2) + beentheres = msg.get_all('x-beenthere') + beentheres.sort() + eq(beentheres, ['_xtest@dom.ain', 'alist@another.dom.ain']) - def test_deep_structure(self): + def test_nonexisting_mmversion(self): eq = self.assertEqual - self._mlist.filter_mime_types.append('text/html') msg = email.message_from_string("""\ From: aperson@dom.ain -Content-Type: multipart/mixed; boundary=AAA - ---AAA -Content-Type: multipart/mixed; boundary=BBB - ---BBB -Content-Type: image/jpeg - -xxx ---BBB -Content-Type: image/jpeg - -yyy ---BBB--- ---AAA -Content-Type: multipart/alternative; boundary=CCC - ---CCC -Content-Type: text/html -

                  This is a header

                  - ---CCC -Content-Type: text/plain - -A different message ---CCC-- ---AAA -Content-Type: image/gif - -zzz ---AAA -Content-Type: image/gif +""", Message) + CookHeaders.process(self._mlist, msg, {}) + eq(msg['x-mailman-version'], mm_cfg.VERSION) -aaa ---AAA-- -""") - MimeDel.process(self._mlist, msg, {}) - payload = msg.get_payload() - eq(len(payload), 3) - part1 = msg.get_payload(0) - eq(part1.get_content_type(), 'text/plain') - eq(part1.get_payload(), 'A different message') - part2 = msg.get_payload(1) - eq(part2.get_content_type(), 'image/gif') - eq(part2.get_payload(), 'zzz') - part3 = msg.get_payload(2) - eq(part3.get_content_type(), 'image/gif') - eq(part3.get_payload(), 'aaa') - - def test_top_multipart_alternative(self): + def test_existing_mmversion(self): eq = self.assertEqual - self._mlist.filter_mime_types.append('text/html') msg = email.message_from_string("""\ From: aperson@dom.ain -Content-Type: multipart/alternative; boundary=AAA - ---AAA -Content-Type: text/html - -This is some html ---AAA -Content-Type: text/plain +X-Mailman-Version: 3000 -This is plain text ---AAA-- -""") - MimeDel.process(self._mlist, msg, {}) - eq(msg.get_content_type(), 'text/plain') - eq(msg.get_payload(), 'This is plain text') +""", Message) + CookHeaders.process(self._mlist, msg, {}) + eq(len(msg.get_all('x-mailman-version')), 1) + eq(msg['x-mailman-version'], '3000') - def test_recast_multipart(self): + def test_nonexisting_precedence(self): eq = self.assertEqual - self._mlist.filter_mime_types.append('application/pdf') msg = email.message_from_string("""\ From: aperson@dom.ain -MIME-Version: 1.0 -Content-type: multipart/mixed; - boundary="Boundary_0" ---Boundary_0 -Content-Type: multipart/mixed; - boundary="Boundary_1" - ---Boundary_1 -Content-type: multipart/mixed; - boundary="Boundary_2" - ---Boundary_2 -Content-type: multipart/alternative; - boundary="Boundary_3" - ---Boundary_3 -Content-type: text/plain; charset=us-ascii -Content-transfer-encoding: 7BIT - -Plain text part ---Boundary_3 -Content-type: text/html; charset=us-ascii -Content-transfer-encoding: 7BIT - -HTML part ---Boundary_3-- - - ---Boundary_2 -Content-type: application/pdf -Content-transfer-encoding: 7BIT - -PDF part inner 2 ---Boundary_2-- ---Boundary_1 -Content-type: text/plain; charset=us-ascii -Content-transfer-encoding: 7BIT - -second text ---Boundary_1-- - ---Boundary_0 -Content-Type: application/pdf -Content-transfer-encoding: 7BIT +""", Message) + CookHeaders.process(self._mlist, msg, {}) + eq(msg['precedence'], 'list') -PDF part outer ---Boundary_0-- -""") - MimeDel.process(self._mlist, msg, {}) - payload = msg.get_payload() - eq(len(payload), 2) - part1 = msg.get_payload(0) - eq(part1.get_content_type(), 'text/plain') - eq(part1.get_payload(), 'Plain text part') - part2 = msg.get_payload(1) - eq(part2.get_content_type(), 'text/plain') - eq(part2.get_payload(), 'second text') - - def test_message_rfc822(self): + def test_existing_precedence(self): eq = self.assertEqual msg = email.message_from_string("""\ -Message-ID: <4D9E6AEA.1060802@example.net> -Date: Thu, 07 Apr 2011 18:54:50 -0700 -From: User -MIME-Version: 1.0 -To: Someone -Subject: Message Subject -Content-Type: multipart/mixed; - boundary="------------050603050603060608020908" - -This is a multi-part message in MIME format. ---------------050603050603060608020908 -Content-Type: text/plain; charset=ISO-8859-1 -Content-Transfer-Encoding: 7bit - -Plain body. - ---------------050603050603060608020908 -Content-Type: message/rfc822 -Content-Transfer-Encoding: 7bit -Content-Disposition: attachment - -Message-ID: <4D9E647F.4050308@example.net> -Date: Thu, 07 Apr 2011 18:27:27 -0700 -From: User1 -MIME-Version: 1.0 -To: Someone1 -Content-Type: multipart/mixed; boundary="------------060107040402070208020705" -Subject: Attached Message 1 Subject - -This is a multi-part message in MIME format. ---------------060107040402070208020705 -Content-Type: text/plain; charset=ISO-8859-1 -Content-Transfer-Encoding: 7bit - -Attached Message 1 body. - ---------------060107040402070208020705 -Content-Type: message/rfc822 -Content-Transfer-Encoding: 7bit -Content-Disposition: attachment - -From: User2 -To: Someone2 -Subject: Attached Message 2 Subject -Date: Thu, 7 Apr 2011 19:09:35 -0500 -Message-ID: -MIME-Version: 1.0 -Content-Type: multipart/mixed; - boundary="----=_NextPart_000_0066_01CBF557.56C6F370" - -This is a multi-part message in MIME format. - -------=_NextPart_000_0066_01CBF557.56C6F370 -Content-Type: text/plain; - charset="us-ascii" -Content-Transfer-Encoding: 7bit - -Attached Message 2 body. - -------=_NextPart_000_0066_01CBF557.56C6F370 -Content-Type: message/rfc822 -Content-Transfer-Encoding: 7bit -Content-Disposition: attachment - -From: User3 -To: Someone3 -Subject: Attached Message 3 Subject -Date: Thu, 7 Apr 2011 17:22:04 -0500 -Message-ID: -MIME-Version: 1.0 -Content-Type: multipart/alternative; - boundary="----=_NextPart_000_0058_01CBF557.56C48270" - -This is a multi-part message in MIME format. - -------=_NextPart_000_0058_01CBF557.56C48270 -Content-Type: text/plain; - charset="iso-8859-1" -Content-Transfer-Encoding: 7bit - -Attached Message 3 plain body. - -------=_NextPart_000_0058_01CBF557.56C48270 -Content-Type: text/html; - charset="iso-8859-1" -Content-Transfer-Encoding: quoted-printable - - -Attached Message 3 html body. - -------=_NextPart_000_0058_01CBF557.56C48270-- - -------=_NextPart_000_0066_01CBF557.56C6F370 -Content-Type: message/rfc822 -Content-Transfer-Encoding: 7bit -Content-Disposition: attachment - -From: User4 -To: Someone4 -Subject: Attached Message 4 Subject -Date: Thu, 7 Apr 2011 17:24:26 -0500 -Message-ID: <19CC3BDF28CF49AD988FF43B2DBC5F1D@example> -MIME-Version: 1.0 -Content-Type: multipart/mixed; - boundary="----=_NextPart_000_0060_01CBF557.56C6F370" - -This is a multi-part message in MIME format. - -------=_NextPart_000_0060_01CBF557.56C6F370 -Content-Type: multipart/alternative; - boundary="----=_NextPart_001_0061_01CBF557.56C6F370" - -------=_NextPart_001_0061_01CBF557.56C6F370 -Content-Type: text/plain; - charset="us-ascii" -Content-Transfer-Encoding: 7bit - -Attached Message 4 plain body. - -------=_NextPart_001_0061_01CBF557.56C6F370 -Content-Type: text/html; - charset="us-ascii" -Content-Transfer-Encoding: quoted-printable - -Attached Message 4 html body. - -------=_NextPart_001_0061_01CBF557.56C6F370-- - -------=_NextPart_000_0060_01CBF557.56C6F370 -Content-Type: message/rfc822 -Content-Transfer-Encoding: 7bit -Content-Disposition: attachment - -From: User5 -To: Someone5 -Subject: Attached Message 5 Subject -Date: Thu, 7 Apr 2011 16:24:26 -0500 -Message-ID: -Content-Type: multipart/alternative; - boundary="----=_NextPart_000_005C_01CBF557.56C6F370" - -This is a multi-part message in MIME format. - -------=_NextPart_000_005C_01CBF557.56C6F370 -Content-Type: text/plain; - charset="iso-8859-1" -Content-Transfer-Encoding: 7bit - -Attached Message 5 plain body. - -------=_NextPart_000_005C_01CBF557.56C6F370 -Content-Type: text/html; - charset="iso-8859-1" -Content-Transfer-Encoding: quoted-printable - -Attached Message 5 html body. - -------=_NextPart_000_005C_01CBF557.56C6F370-- - -------=_NextPart_000_0060_01CBF557.56C6F370 -Content-Type: text/plain; - name="ATT00055.txt" -Content-Transfer-Encoding: quoted-printable -Content-Disposition: attachment; - filename="ATT00055.txt" - -Another plain part. - -------=_NextPart_000_0060_01CBF557.56C6F370-- +From: aperson@dom.ain +Precedence: junk -------=_NextPart_000_0066_01CBF557.56C6F370-- +""", Message) + CookHeaders.process(self._mlist, msg, {}) + eq(len(msg.get_all('precedence')), 1) + eq(msg['precedence'], 'junk') ---------------060107040402070208020705 -Content-Type: text/plain; charset="us-ascii" -MIME-Version: 1.0 -Content-Transfer-Encoding: 7bit -Content-Disposition: inline + def test_subject_munging_no_subject(self): + self._mlist.subject_prefix = '[XTEST] ' + msg = email.message_from_string("""\ +From: aperson@dom.ain -Final plain part. +""", Message) + msgdata = {} + CookHeaders.process(self._mlist, msg, msgdata) + self.assertEqual(msgdata.get('origsubj'), '') + self.assertEqual(str(msg['subject']), '[XTEST] (no subject)') ---------------060107040402070208020705-- + def test_subject_munging(self): + self._mlist.subject_prefix = '[XTEST] ' + msg = email.message_from_string("""\ +From: aperson@dom.ain +Subject: About Mailman... ---------------050603050603060608020908-- -""") - MimeDel.process(self._mlist, msg, {}) - payload = msg.get_payload() - eq(len(payload), 2) - part1 = msg.get_payload(0) - eq(part1.get_content_type(), 'text/plain') - eq(part1.get_payload(), 'Plain body.\n') - part2 = msg.get_payload(1) - eq(part2.get_content_type(), 'message/rfc822') - payload = part2.get_payload() - eq(len(payload), 1) - part1 = part2.get_payload(0) - eq(part1['subject'], 'Attached Message 1 Subject') - eq(part1.get_content_type(), 'multipart/mixed') - payload = part1.get_payload() - eq(len(payload), 3) - part3 = part1.get_payload(2) - eq(part3.get_content_type(), 'text/plain') - eq(part3.get_payload(), 'Final plain part.\n') - part2 = part1.get_payload(1) - eq(part2.get_content_type(), 'message/rfc822') - part1 = part1.get_payload(0) - eq(part1.get_content_type(), 'text/plain') - eq(part1.get_payload(), 'Attached Message 1 body.\n') - payload = part2.get_payload() - eq(len(payload), 1) - part1 = part2.get_payload(0) - eq(part1['subject'], 'Attached Message 2 Subject') - eq(part1.get_content_type(), 'multipart/mixed') - payload = part1.get_payload() - eq(len(payload), 3) - part3 = part1.get_payload(2) - eq(part3.get_content_type(), 'message/rfc822') - part2 = part1.get_payload(1) - eq(part2.get_content_type(), 'message/rfc822') - part1 = part1.get_payload(0) - eq(part1.get_content_type(), 'text/plain') - eq(part1.get_payload(), 'Attached Message 2 body.\n') - payload = part2.get_payload() - eq(len(payload), 1) - part1 = part2.get_payload(0) - eq(part1['subject'], 'Attached Message 3 Subject') - eq(part1.get_content_type(), 'text/plain') - eq(part1.get_payload(), 'Attached Message 3 plain body.\n') - payload = part3.get_payload() - eq(len(payload), 1) - part1 = part3.get_payload(0) - eq(part1['subject'], 'Attached Message 4 Subject') - eq(part1.get_content_type(), 'multipart/mixed') - payload = part1.get_payload() - eq(len(payload), 3) - part3 = part1.get_payload(2) - eq(part3.get_content_type(), 'text/plain') - eq(part3.get_filename(), 'ATT00055.txt') - eq(part3.get_payload(), 'Another plain part.\n') - part2 = part1.get_payload(1) - eq(part2.get_content_type(), 'message/rfc822') - part1 = part1.get_payload(0) - eq(part1.get_content_type(), 'text/plain') - eq(part1.get_payload(), 'Attached Message 4 plain body.\n') - payload = part2.get_payload() - eq(len(payload), 1) - part1 = part2.get_payload(0) - eq(part1['subject'], 'Attached Message 5 Subject') - eq(part1.get_content_type(), 'text/plain') - eq(part1.get_payload(), 'Attached Message 5 plain body.\n') - - -class TestModerate(TestBase): - pass - - - -class TestReplybot(TestBase): - pass - - - -class TestSpamDetect(TestBase): - def test_short_circuit(self): - msgdata = {'approved': 1} - msg = email.message_from_string('', Message.Message) - rtn = SpamDetect.process(self._mlist, msg, msgdata) - # Not really a great test, but there's little else to assert - self.assertEqual(rtn, None) +""", Message) + CookHeaders.process(self._mlist, msg, {}) + self.assertEqual(str(msg['subject']), '[XTEST] About Mailman...') - def test_spam_detect(self): - msg1 = email.message_from_string("""\ + def test_no_subject_munging_for_digests(self): + self._mlist.subject_prefix = '[XTEST] ' + msg = email.message_from_string("""\ From: aperson@dom.ain +Subject: About Mailman... -A message. -""", Message.Message) - msg2 = email.message_from_string("""\ -To: xlist@dom.ain +""", Message) + CookHeaders.process(self._mlist, msg, {'isdigest': 1}) + self.assertEqual(msg['subject'], 'About Mailman...') -A message. -""", Message.Message) - spammers = mm_cfg.KNOWN_SPAMMERS[:] - try: - mm_cfg.KNOWN_SPAMMERS.append(('from', '.?person')) - self.assertRaises(SpamDetect.SpamDetected, - SpamDetect.process, self._mlist, msg1, {}) - rtn = SpamDetect.process(self._mlist, msg2, {}) - self.assertEqual(rtn, None) - finally: - mm_cfg.KNOWN_SPAMMERS = spammers + def test_no_subject_munging_for_fasttrack(self): + self._mlist.subject_prefix = '[XTEST] ' + msg = email.message_from_string("""\ +From: aperson@dom.ain +Subject: About Mailman... +""", Message) + CookHeaders.process(self._mlist, msg, {'_fasttrack': 1}) + self.assertEqual(msg['subject'], 'About Mailman...') - -class TestTagger(TestBase): - def setUp(self): - TestBase.setUp(self) - self._mlist.topics = [('bar fight', '.*bar.*', 'catch any bars', 1)] - self._mlist.topics_enabled = 1 + def test_no_subject_munging_has_prefix(self): + self._mlist.subject_prefix = '[XTEST] ' + msg = email.message_from_string("""\ +From: aperson@dom.ain +Subject: Re: [XTEST] About Mailman... - def test_short_circuit(self): - self._mlist.topics_enabled = 0 - rtn = Tagger.process(self._mlist, None, {}) - # Not really a great test, but there's little else to assert - self.assertEqual(rtn, None) +""", Message) + CookHeaders.process(self._mlist, msg, {}) + # prefixing depends on mm_cfg.py + self.failUnless(str(msg['subject']) == 'Re: [XTEST] About Mailman...' or + str(msg['subject']) == '[XTEST] Re: About Mailman...') - def test_simple(self): + def test_reply_to_list(self): eq = self.assertEqual mlist = self._mlist - mlist.topics_bodylines_limit = 0 + mlist.reply_goes_to_list = 1 + mlist.from_is_list = 0 msg = email.message_from_string("""\ -Subject: foobar -Keywords: barbaz +From: aperson@dom.ain -""") +""", Message) msgdata = {} - Tagger.process(mlist, msg, msgdata) - eq(msg['x-topics'], 'bar fight') - eq(msgdata.get('topichits'), ['bar fight']) + CookHeaders.process(mlist, msg, msgdata) + eq(msgdata['add_header']['Reply-To'], '_xtest@dom.ain') + eq(msg.get_all('reply-to'), None) - def test_all_body_lines_plain_text(self): + def test_reply_to_list_fil(self): eq = self.assertEqual mlist = self._mlist - mlist.topics_bodylines_limit = -1 + mlist.reply_goes_to_list = 1 + mlist.from_is_list = 1 msg = email.message_from_string("""\ -Subject: Was -Keywords: Raw +From: aperson@dom.ain -Subject: farbaw -Keywords: barbaz -""") +""", Message) msgdata = {} - Tagger.process(mlist, msg, msgdata) - eq(msg['x-topics'], 'bar fight') - eq(msgdata.get('topichits'), ['bar fight']) + CookHeaders.process(mlist, msg, msgdata) + eq(msgdata['add_header']['Reply-To'], + '_xtest@dom.ain') + eq(msgdata['add_header']['Cc'], + 'aperson@dom.ain') + eq(msg.get_all('reply-to'), None) + eq(msg.get_all('cc'), None) - def test_no_body_lines(self): + + def test_reply_to_explicit(self): eq = self.assertEqual mlist = self._mlist - mlist.topics_bodylines_limit = 0 + mlist.reply_goes_to_list = 2 + mlist.from_is_list = 0 + mlist.reply_to_address = 'mlist@dom.ain' msg = email.message_from_string("""\ -Subject: Was -Keywords: Raw +From: aperson@dom.ain -Subject: farbaw -Keywords: barbaz -""") +""", Message) msgdata = {} - Tagger.process(mlist, msg, msgdata) - eq(msg['x-topics'], None) - eq(msgdata.get('topichits'), None) + CookHeaders.process(mlist, msg, msgdata) + eq(msgdata['add_header']['Reply-To'], 'mlist@dom.ain') + eq(msg.get_all('reply-to'), None) - def test_body_lines_in_multipart(self): + def test_reply_to_explicit_fil(self): eq = self.assertEqual mlist = self._mlist - mlist.topics_bodylines_limit = -1 + mlist.reply_goes_to_list = 2 + mlist.from_is_list = 1 + mlist.reply_to_address = 'mlist@dom.ain' msg = email.message_from_string("""\ -Subject: Was -Keywords: Raw -Content-Type: multipart/alternative; boundary="BOUNDARY" - ---BOUNDARY -From: sabo -To: obas - -Subject: farbaw -Keywords: barbaz +From: aperson@dom.ain ---BOUNDARY-- -""") +""", Message) msgdata = {} - Tagger.process(mlist, msg, msgdata) - eq(msg['x-topics'], 'bar fight') - eq(msgdata.get('topichits'), ['bar fight']) + CookHeaders.process(mlist, msg, msgdata) + eq(msgdata['add_header']['Reply-To'], + 'mlist@dom.ain') + eq(msgdata['add_header']['Cc'], + 'aperson@dom.ain') + eq(msg.get_all('reply-to'), None) + eq(msg.get_all('cc'), None) - def test_body_lines_no_part(self): + def test_reply_to_explicit_with_strip(self): eq = self.assertEqual mlist = self._mlist - mlist.topics_bodylines_limit = -1 + mlist.reply_goes_to_list = 2 + mlist.first_strip_reply_to = 1 + mlist.from_is_list = 0 + mlist.reply_to_address = 'mlist@dom.ain' msg = email.message_from_string("""\ -Subject: Was -Keywords: Raw -Content-Type: multipart/alternative; boundary=BOUNDARY - ---BOUNDARY -From: sabo -To: obas -Content-Type: message/rfc822 +From: aperson@dom.ain +Reply-To: bperson@dom.ain -Subject: farbaw -Keywords: barbaz +""", Message) + msgdata = {} ---BOUNDARY -From: sabo -To: obas -Content-Type: message/rfc822 + CookHeaders.process(self._mlist, msg, msgdata) + eq(msgdata['add_header']['Reply-To'], 'mlist@dom.ain') + eq(msg.get_all('reply-to'), ['bperson@dom.ain']) -Subject: farbaw -Keywords: barbaz + def test_reply_to_explicit_with_strip_fil(self): + eq = self.assertEqual + mlist = self._mlist + mlist.reply_goes_to_list = 2 + mlist.first_strip_reply_to = 1 + mlist.from_is_list = 1 + mlist.reply_to_address = 'mlist@dom.ain' + msg = email.message_from_string("""\ +From: aperson@dom.ain +Reply-To: bperson@dom.ain ---BOUNDARY-- -""") +""", Message) msgdata = {} - Tagger.process(mlist, msg, msgdata) - eq(msg['x-topics'], None) - eq(msgdata.get('topichits'), None) - - - -class TestToArchive(TestBase): - def setUp(self): - TestBase.setUp(self) - # We're going to want to inspect this queue directory - self._sb = Switchboard(mm_cfg.ARCHQUEUE_DIR) - def tearDown(self): - for f in os.listdir(mm_cfg.ARCHQUEUE_DIR): - os.unlink(os.path.join(mm_cfg.ARCHQUEUE_DIR, f)) - TestBase.tearDown(self) + CookHeaders.process(self._mlist, msg, msgdata) + eq(msgdata['add_header']['Reply-To'], + 'mlist@dom.ain') + eq(msgdata['add_header']['Cc'], + 'aperson@dom.ain') + eq(msg.get_all('reply-to'), ['bperson@dom.ain']) + eq(msg.get_all('cc'), None) - def test_short_circuit(self): + def test_reply_to_extends_to_list(self): eq = self.assertEqual - msgdata = {'isdigest': 1} - ToArchive.process(self._mlist, None, msgdata) - eq(len(self._sb.files()), 0) - # Try the other half of the or... - self._mlist.archive = 0 - ToArchive.process(self._mlist, None, msgdata) - eq(len(self._sb.files()), 0) - # Now try the various message header shortcuts + mlist = self._mlist + mlist.reply_goes_to_list = 1 + mlist.first_strip_reply_to = 0 + mlist.from_is_list = 0 msg = email.message_from_string("""\ -X-No-Archive: YES +From: aperson@dom.ain +Reply-To: bperson@dom.ain -""") - self._mlist.archive = 1 - ToArchive.process(self._mlist, msg, {}) - eq(len(self._sb.files()), 0) - # And for backwards compatibility - msg = email.message_from_string("""\ -X-Archive: NO +""", Message) + msgdata = {} -""") - ToArchive.process(self._mlist, msg, {}) - eq(len(self._sb.files()), 0) + CookHeaders.process(mlist, msg, msgdata) + eq(msgdata['add_header']['Reply-To'], + 'bperson@dom.ain, _xtest@dom.ain') - def test_normal_archiving(self): + def test_reply_to_extends_to_list_fil(self): eq = self.assertEqual + mlist = self._mlist + mlist.reply_goes_to_list = 1 + mlist.first_strip_reply_to = 0 + mlist.from_is_list = 1 msg = email.message_from_string("""\ -Subject: About Mailman - -It rocks! -""") - ToArchive.process(self._mlist, msg, {}) - files = self._sb.files() - eq(len(files), 1) - msg2, data = self._sb.dequeue(files[0]) - eq(len(data), 3) - eq(data['_parsemsg'], False) - eq(data['version'], 3) - # Clock skew makes this unreliable - #self.failUnless(data['received_time'] <= time.time()) - eq(msg.as_string(unixfrom=0), msg2.as_string(unixfrom=0)) - - - -class TestToDigest(TestBase): - def _makemsg(self, i=0): - msg = email.message_from_string("""From: aperson@dom.ain -To: _xtest@dom.ain -Subject: message number %(i)d - -Here is message %(i)d -""" % {'i' : i}) - return msg +From: aperson@dom.ain +Reply-To: bperson@dom.ain - def setUp(self): - TestBase.setUp(self) - self._path = os.path.join(self._mlist.fullpath(), 'digest.mbox') - fp = open(self._path, 'w') - g = Generator(fp) - for i in range(5): - g.flatten(self._makemsg(i), unixfrom=1) - fp.close() - self._sb = Switchboard(mm_cfg.VIRGINQUEUE_DIR) +""", Message) + msgdata = {} - def tearDown(self): - try: - os.unlink(self._path) - except OSError as e: - if e.errno != errno.ENOENT: raise - for f in os.listdir(mm_cfg.VIRGINQUEUE_DIR): - os.unlink(os.path.join(mm_cfg.VIRGINQUEUE_DIR, f)) - TestBase.tearDown(self) + CookHeaders.process(mlist, msg, msgdata) + eq(msgdata['add_header']['Reply-To'], + 'bperson@dom.ain, _xtest@dom.ain') + eq(msgdata['add_header']['Cc'], + 'aperson@dom.ain') + eq(msg.get_all('reply-to'), ['bperson@dom.ain']) + eq(msg.get_all('cc'), None) - def test_short_circuit(self): - eq = self.assertEqual - mlist = self._mlist - mlist.digestable = 0 - eq(ToDigest.process(mlist, None, {}), None) - mlist.digestable = 1 - eq(ToDigest.process(mlist, None, {'isdigest': 1}), None) - eq(self._sb.files(), []) - - def test_undersized(self): - msg = self._makemsg(99) - size = os.path.getsize(self._path) + len(str(msg)) - self._mlist.digest_size_threshhold = (size + 1) * 1024 - ToDigest.process(self._mlist, msg, {}) - self.assertEqual(self._sb.files(), []) - - def test_send_a_digest(self): + def test_reply_to_extends_to_explicit(self): eq = self.assertEqual mlist = self._mlist - msg = self._makemsg(99) - size = os.path.getsize(self._path) + len(str(msg)) - # Set digest_size_threshhold to a very small value to force a digest. - # Setting to zero no longer works. - mlist.digest_size_threshhold = 0.001 - ToDigest.process(mlist, msg, {}) - files = self._sb.files() - # There should be two files in the queue, one for the MIME digest and - # one for the RFC 1153 digest. - eq(len(files), 2) - # Now figure out which of the two files is the MIME digest and which - # is the RFC 1153 digest. - for filebase in files: - qmsg, qdata = self._sb.dequeue(filebase) - if qmsg.get_content_maintype() == 'multipart': - mimemsg = qmsg - mimedata = qdata - else: - rfc1153msg = qmsg - rfc1153data = qdata - eq(rfc1153msg.get_content_type(), 'text/plain') - eq(mimemsg.get_content_type(), 'multipart/mixed') - eq(mimemsg['from'], mlist.GetRequestEmail()) - eq(mimemsg['subject'], - '%(realname)s Digest, Vol %(volume)d, Issue %(issue)d' % { - 'realname': mlist.real_name, - 'volume' : mlist.volume, - 'issue' : mlist.next_digest_number - 1, - }) - eq(mimemsg['to'], mlist.GetListEmail()) - # BAW: this test is incomplete... - - - -class TestToOutgoing(TestBase): - def setUp(self): - TestBase.setUp(self) - # We're going to want to inspect this queue directory - self._sb = Switchboard(mm_cfg.OUTQUEUE_DIR) + mlist.reply_goes_to_list = 2 + mlist.first_strip_reply_to = 0 + mlist.from_is_list = 0 + mlist.reply_to_address = 'mlist@dom.ain' + msg = email.message_from_string("""\ +From: aperson@dom.ain +Reply-To: bperson@dom.ain - def tearDown(self): - for f in os.listdir(mm_cfg.OUTQUEUE_DIR): - os.unlink(os.path.join(mm_cfg.OUTQUEUE_DIR, f)) - TestBase.tearDown(self) +""", Message) + msgdata = {} + CookHeaders.process(mlist, msg, msgdata) + eq(msgdata['add_header']['Reply-To'], + 'mlist@dom.ain, bperson@dom.ain') - def test_outgoing(self): + def test_reply_to_extends_to_explicit_fil(self): eq = self.assertEqual + mlist = self._mlist + mlist.reply_goes_to_list = 2 + mlist.first_strip_reply_to = 0 + mlist.from_is_list = 1 + mlist.reply_to_address = 'mlist@dom.ain' msg = email.message_from_string("""\ -Subject: About Mailman - -It rocks! -""") - msgdata = {'foo': 1, 'bar': 2} - ToOutgoing.process(self._mlist, msg, msgdata) - files = self._sb.files() - eq(len(files), 1) - msg2, data = self._sb.dequeue(files[0]) - eq(msg.as_string(unixfrom=0), msg2.as_string(unixfrom=0)) - self.failUnless(len(data) >= 6 and len(data) <= 7) - eq(data['foo'], 1) - eq(data['bar'], 2) - eq(data['version'], 3) - eq(data['listname'], '_xtest') - eq(data['_parsemsg'], False) - # Can't test verp. presence/value depend on mm_cfg.py - #eq(data['verp'], 1) - # Clock skew makes this unreliable - #self.failUnless(data['received_time'] <= time.time()) - - - -class TestToUsenet(TestBase): - def setUp(self): - TestBase.setUp(self) - # We're going to want to inspect this queue directory - self._sb = Switchboard(mm_cfg.NEWSQUEUE_DIR) +From: aperson@dom.ain +Reply-To: bperson@dom.ain - def tearDown(self): - for f in os.listdir(mm_cfg.NEWSQUEUE_DIR): - os.unlink(os.path.join(mm_cfg.NEWSQUEUE_DIR, f)) - TestBase.tearDown(self) +""", Message) + msgdata = {} + CookHeaders.process(mlist, msg, msgdata) + eq(msgdata['add_header']['Reply-To'], + 'mlist@dom.ain, bperson@dom.ain') + eq(msgdata['add_header']['Cc'], + 'aperson@dom.ain') + eq(msg.get_all('reply-to'), ['bperson@dom.ain']) + eq(msg.get_all('cc'), None) - def test_short_circuit(self): + def test_list_headers_nolist(self): eq = self.assertEqual - mlist = self._mlist - mlist.gateway_to_news = 0 - ToUsenet.process(mlist, None, {}) - eq(len(self._sb.files()), 0) - mlist.gateway_to_news = 1 - ToUsenet.process(mlist, None, {'isdigest': 1}) - eq(len(self._sb.files()), 0) - ToUsenet.process(mlist, None, {'fromusenet': 1}) - eq(len(self._sb.files()), 0) + msg = email.message_from_string("""\ +From: aperson@dom.ain + +""", Message) + CookHeaders.process(self._mlist, msg, {'_nolist': 1}) + eq(msg['list-id'], None) + eq(msg['list-help'], None) + eq(msg['list-unsubscribe'], None) + eq(msg['list-subscribe'], None) + eq(msg['list-post'], None) + eq(msg['list-archive'], None) - def test_to_usenet(self): - # BAW: Should we, can we, test the error conditions that only log to a - # file instead of raising an exception? + def test_list_headers(self): eq = self.assertEqual - mlist = self._mlist - mlist.gateway_to_news = 1 - mlist.linked_newsgroup = 'foo' - mlist.nntp_host = 'bar' + self._mlist.archive = 1 msg = email.message_from_string("""\ -Subject: About Mailman +From: aperson@dom.ain -Mailman rocks! -""") - ToUsenet.process(mlist, msg, {}) - files = self._sb.files() - eq(len(files), 1) - msg2, data = self._sb.dequeue(files[0]) - eq(msg.as_string(unixfrom=0), msg2.as_string(unixfrom=0)) - eq(data['version'], 3) - eq(data['listname'], '_xtest') - # Clock skew makes this unreliable - #self.failUnless(data['received_time'] <= time.time()) - - - -def suite(): - suite = unittest.TestSuite() - suite.addTest(unittest.makeSuite(TestAcknowledge)) - suite.addTest(unittest.makeSuite(TestAfterDelivery)) - suite.addTest(unittest.makeSuite(TestApprove)) - suite.addTest(unittest.makeSuite(TestCalcRecips)) - suite.addTest(unittest.makeSuite(TestCleanse)) - suite.addTest(unittest.makeSuite(TestCookHeaders)) - suite.addTest(unittest.makeSuite(TestDecorate)) - suite.addTest(unittest.makeSuite(TestFileRecips)) - suite.addTest(unittest.makeSuite(TestHold)) - suite.addTest(unittest.makeSuite(TestMimeDel)) - suite.addTest(unittest.makeSuite(TestModerate)) - suite.addTest(unittest.makeSuite(TestReplybot)) - suite.addTest(unittest.makeSuite(TestSpamDetect)) - suite.addTest(unittest.makeSuite(TestTagger)) - suite.addTest(unittest.makeSuite(TestToArchive)) - suite.addTest(unittest.makeSuite(TestToDigest)) - suite.addTest(unittest.makeSuite(TestToOutgoing)) - suite.addTest(unittest.makeSuite(TestToUsenet)) - return suite - - - -if __name__ == '__main__': - unittest.main(defaultTest='suite') +""", Message) + oldval = mm_cfg.DEFAULT_URL_HOST + mm_cfg.DEFAULT_URL_HOST = 'www.dom.ain' + try: + CookHeaders.process(self._mlist, msg, {}) + finally: + mm_cfg.DEFAULT_URL_HOST = oldval + eq(msg['list-id'], '<_xtest.dom.ain>') + eq(msg['list-help'], '') + eq(msg['list-unsubscribe'], + ',' + '\n ') + eq(msg['list-subscribe'], + ',' + '\n ') + eq(msg['list-post'], '') + eq(msg['list-archive'], '') diff --git a/tests/test_message.py b/tests/test_message.py index 87ca6f20..5b44491f 100644 --- a/tests/test_message.py +++ b/tests/test_message.py @@ -77,7 +77,7 @@ def test_bounce_message(self): Subject: and another thing yadda yadda yadda -""", Message.Message) +""", Message) self._mlist.BounceMessage(msg, {}) qmsg = email.message_from_string(self._readmsg()) unless(qmsg.is_multipart()) From 7f79782b3f5d4ebf818e46bdf83d92ad068d3bfc Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 2 May 2025 13:11:59 -0400 Subject: [PATCH 311/748] make it clear that we are using Mailman.Message --- Mailman/Bouncer.py | 6 +++--- Mailman/Cgi/create.py | 2 +- Mailman/Cgi/subscribe.py | 2 +- Mailman/Deliverer.py | 14 +++++++------- Mailman/Handlers/Acknowledge.py | 2 +- Mailman/Handlers/Hold.py | 7 ++++--- Mailman/Handlers/Moderate.py | 2 +- Mailman/Handlers/Replybot.py | 2 +- Mailman/ListAdmin.py | 8 ++++---- Mailman/MTA/Manual.py | 4 ++-- Mailman/Queue/CommandRunner.py | 2 +- bin/change_pw | 2 +- bin/newlist | 2 +- cron/checkdbs | 2 +- cron/mailpasswds | 2 +- tests/test_message.py | 2 +- 16 files changed, 31 insertions(+), 30 deletions(-) diff --git a/Mailman/Bouncer.py b/Mailman/Bouncer.py index 8915f619..883ddf47 100644 --- a/Mailman/Bouncer.py +++ b/Mailman/Bouncer.py @@ -246,7 +246,7 @@ def __sendAdminBounceNotice(self, member, msg, did=None): 'owneraddr': siteowner, }, mlist=self) subject = _('Bounce action notification') - umsg = Message.UserNotification(self.GetOwnerEmail(), + umsg = Mailman.Message.UserNotification(self.GetOwnerEmail(), siteowner, subject, lang=self.preferred_language) # BAW: Be sure you set the type before trying to attach, or you'll get @@ -315,7 +315,7 @@ def sendNextNotification(self, member): 'owneraddr' : self.GetOwnerEmail(), 'reason' : txtreason, }, lang=lang, mlist=self) - msg = Message.UserNotification(member, reqaddr, text=text, lang=lang) + msg = Mailman.Message.UserNotification(member, reqaddr, text=text, lang=lang) # BAW: See the comment in MailList.py ChangeMemberAddress() for why we # set the Subject this way. del msg['subject'] @@ -340,7 +340,7 @@ def BounceMessage(self, msg, msgdata, e=None): else: notice = _(e.notice()) # Currently we always craft bounces as MIME messages. - bmsg = Message.UserNotification(msg.get_sender(), + bmsg = Mailman.Message.UserNotification(msg.get_sender(), self.GetOwnerEmail(), subject, lang=self.preferred_language) diff --git a/Mailman/Cgi/create.py b/Mailman/Cgi/create.py index 807dd2bc..cf055b62 100644 --- a/Mailman/Cgi/create.py +++ b/Mailman/Cgi/create.py @@ -285,7 +285,7 @@ def sigterm_handler(signum, frame, mlist=mlist): 'requestaddr' : mlist.GetRequestEmail(), 'siteowner' : siteowner, }, mlist=mlist) - msg = Message.UserNotification( + msg = Mailman.Message.UserNotification( owner, siteowner, _('Your new mailing list: {listname}'), text, mlist.preferred_language) diff --git a/Mailman/Cgi/subscribe.py b/Mailman/Cgi/subscribe.py index 9196b7b7..1074fe58 100644 --- a/Mailman/Cgi/subscribe.py +++ b/Mailman/Cgi/subscribe.py @@ -333,7 +333,7 @@ def process_form(mlist, doc, cgidata, lang): otrans = i18n.get_translation() i18n.set_language(mlang) try: - msg = Message.UserNotification( + msg = Mailman.Message.UserNotification( mlist.getMemberCPAddress(email), mlist.GetBouncesEmail(), _('Mailman privacy alert'), diff --git a/Mailman/Deliverer.py b/Mailman/Deliverer.py index 10d2690b..e5912c58 100644 --- a/Mailman/Deliverer.py +++ b/Mailman/Deliverer.py @@ -92,7 +92,7 @@ def SendSubscribeAck(self, name, password, digest, text=''): else: digmode = '' realname = self.real_name - msg = Message.UserNotification( + msg = Mailman.Message.UserNotification( self.GetMemberAdminEmail(name), self.GetRequestEmail(), _('Welcome to the "%(realname)s" mailing list%(digmode)s'), text, pluser) @@ -102,7 +102,7 @@ def SendSubscribeAck(self, name, password, digest, text=''): def SendUnsubscribeAck(self, addr, lang): realname = self.real_name i18n.set_language(lang) - msg = Message.UserNotification( + msg = Mailman.Message.UserNotification( self.GetMemberAdminEmail(addr), self.GetBouncesEmail(), _('You have been unsubscribed from the %(realname)s mailing list'), Utils.wrap(self.goodbye_msg), lang) @@ -151,7 +151,7 @@ def MailUserPassword(self, user): 'requestaddr': requestaddr, 'owneraddr' : self.GetOwnerEmail(), }, lang=lang, mlist=self) - msg = Message.UserNotification(recipient, adminaddr, subject, text, + msg = Mailman.Message.UserNotification(recipient, adminaddr, subject, text, lang) msg['X-No-Archive'] = 'yes' msg.send(self, verp=mm_cfg.VERP_PERSONALIZED_DELIVERIES) @@ -165,7 +165,7 @@ def ForwardMessage(self, msg, text=None, subject=None, tomoderators=True): text = MIMEText(Utils.wrap(text), _charset=Utils.GetCharSet(self.preferred_language)) attachment = MIMEMessage(msg) - notice = Message.OwnerNotification( + notice = Mailman.Message.OwnerNotification( self, subject, tomoderators=tomoderators) # Make it look like the message is going to the -owner address notice.set_type('multipart/mixed') @@ -181,7 +181,7 @@ def SendHostileSubscriptionNotice(self, listname, address): syslog('mischief', '%s was invited to %s but confirmed to %s', address, listname, selfname) # First send a notice to the attacked list - msg = Message.OwnerNotification( + msg = Mailman.Message.OwnerNotification( self, _('Hostile subscription attempt detected'), Utils.wrap(_("""%(address)s was invited to a different mailing @@ -200,7 +200,7 @@ def SendHostileSubscriptionNotice(self, listname, address): otrans = i18n.get_translation() i18n.set_language(mlist.preferred_language) try: - msg = Message.OwnerNotification( + msg = Mailman.Message.OwnerNotification( mlist, _('Hostile subscription attempt detected'), Utils.wrap(_("""You invited %(address)s to your list, but in a @@ -239,7 +239,7 @@ def sendProbe(self, member, msg): subject = _('%(listname)s mailing list probe message') finally: i18n.set_translation(otrans) - outer = Message.UserNotification(member, probeaddr, subject, + outer = Mailman.Message.UserNotification(member, probeaddr, subject, lang=ulang) outer.set_type('multipart/mixed') text = MIMEText(text, _charset=Utils.GetCharSet(ulang)) diff --git a/Mailman/Handlers/Acknowledge.py b/Mailman/Handlers/Acknowledge.py index ba8b1736..0a1395e0 100644 --- a/Mailman/Handlers/Acknowledge.py +++ b/Mailman/Handlers/Acknowledge.py @@ -56,6 +56,6 @@ def process(mlist, msg, msgdata): # necessary for general delivery. Then enqueue it to the outgoing # queue. subject = _('%(realname)s post acknowledgement') - usermsg = Message.UserNotification(sender, mlist.GetBouncesEmail(), + usermsg = Mailman.Message.UserNotification(sender, mlist.GetBouncesEmail(), subject, text, lang) usermsg.send(mlist) diff --git a/Mailman/Handlers/Hold.py b/Mailman/Handlers/Hold.py index b26cf1aa..ad483e7a 100644 --- a/Mailman/Handlers/Hold.py +++ b/Mailman/Handlers/Hold.py @@ -42,6 +42,7 @@ from Mailman import i18n from Mailman import Pending from Mailman.Logging.Syslog import syslog +from Mailman.Message import UserNotification # First, play footsie with _ so that the following are marked as translated, # but aren't actually translated until we need the text later on. @@ -245,7 +246,7 @@ def hold_for_approval(mlist, msg, msgdata, exc): lang = msgdata.get('lang', mlist.getMemberLanguage(sender)) subject = _('Your message to %(listname)s awaits moderator approval') text = Utils.maketext('postheld.txt', d, lang=lang, mlist=mlist) - nmsg = Message.UserNotification(sender, owneraddr, subject, text, lang) + nmsg = Mailman.Message.UserNotification(sender, owneraddr, subject, text, lang) nmsg.send(mlist) # Now the message for the list owners. Be sure to include the list # moderators in this message. This one should appear to come from @@ -263,8 +264,8 @@ def hold_for_approval(mlist, msg, msgdata, exc): d['subject'] = usersubject # craft the admin notification message and deliver it subject = _('%(listname)s post from %(sender)s requires approval') - nmsg = Message.UserNotification(owneraddr, owneraddr, subject, - lang=lang) + nmsg = Mailman.Message.UserNotification(owneraddr, owneraddr, subject, + lang=lang) nmsg.set_type('multipart/mixed') text = MIMEText( Utils.maketext('postauth.txt', d, raw=1, mlist=mlist), diff --git a/Mailman/Handlers/Moderate.py b/Mailman/Handlers/Moderate.py index 36cad7d8..d4f4392b 100644 --- a/Mailman/Handlers/Moderate.py +++ b/Mailman/Handlers/Moderate.py @@ -162,7 +162,7 @@ def do_discard(mlist, msg): lang = mlist.preferred_language varhelp = '%s/?VARHELP=privacy/sender/discard_these_nonmembers' % \ mlist.GetScriptURL('admin', absolute=1) - nmsg = Message.UserNotification(mlist.GetOwnerEmail(), + nmsg = Mailman.Message.UserNotification(mlist.GetOwnerEmail(), mlist.GetBouncesEmail(), _('Auto-discard notification'), lang=lang) diff --git a/Mailman/Handlers/Replybot.py b/Mailman/Handlers/Replybot.py index a1489f54..668f1ccf 100644 --- a/Mailman/Handlers/Replybot.py +++ b/Mailman/Handlers/Replybot.py @@ -102,7 +102,7 @@ def process(mlist, msg, msgdata): text = rtext # Wrap the response. text = Utils.wrap(text) - outmsg = Message.UserNotification(sender, mlist.GetBouncesEmail(), + outmsg = Mailman.Message.UserNotification(sender, mlist.GetBouncesEmail(), subject, text, mlist.preferred_language) outmsg['X-Mailer'] = _('The Mailman Replybot') # prevent recursions and mail loops! diff --git a/Mailman/ListAdmin.py b/Mailman/ListAdmin.py index d96ce00c..e1079a5f 100644 --- a/Mailman/ListAdmin.py +++ b/Mailman/ListAdmin.py @@ -534,7 +534,7 @@ def __handlepost(self, record, value, comment, preserve, forward, addr): otrans = i18n.get_translation() i18n.set_language(lang) try: - fmsg = Message.UserNotification( + fmsg = Mailman.Message.UserNotification( addr, self.GetBouncesEmail(), _('Forward of moderated message'), lang=lang) @@ -605,7 +605,7 @@ def HoldSubscription(self, addr, fullname, password, digest, lang): # This message should appear to come from the -owner so as # to avoid any useless bounce processing. owneraddr = self.GetOwnerEmail() - msg = Message.UserNotification(owneraddr, owneraddr, subject, text, + msg = Mailman.Message.UserNotification(owneraddr, owneraddr, subject, text, self.preferred_language) msg.send(self, **{'tomoderators': 1}) # Restore the user's preferred language. @@ -666,7 +666,7 @@ def HoldUnsubscription(self, addr): # This message should appear to come from the -owner so as # to avoid any useless bounce processing. owneraddr = self.GetOwnerEmail() - msg = Message.UserNotification(owneraddr, owneraddr, subject, text, + msg = Mailman.Message.UserNotification(owneraddr, owneraddr, subject, text, self.preferred_language) msg.send(self, **{'tomoderators': 1}) @@ -717,7 +717,7 @@ def __refuse(self, request, recip, comment, origmsg=None, lang=None): subject = _('Request to mailing list %(realname)s rejected') finally: i18n.set_translation(otrans) - msg = Message.UserNotification(recip, self.GetOwnerEmail(), + msg = Mailman.Message.UserNotification(recip, self.GetOwnerEmail(), subject, text, lang) msg.send(self) diff --git a/Mailman/MTA/Manual.py b/Mailman/MTA/Manual.py index cc0f41a7..bf0a3555 100644 --- a/Mailman/MTA/Manual.py +++ b/Mailman/MTA/Manual.py @@ -87,7 +87,7 @@ def create(mlist, cgi=False, nolock=False, quiet=False): # this request. siteowner = Utils.get_site_email(extra='owner') # Should this be sent in the site list's preferred language? - msg = Message.UserNotification( + msg = Mailman.Message.UserNotification( siteowner, siteowner, _('Mailing list creation request for list %(listname)s'), sfp.getvalue(), mm_cfg.DEFAULT_SERVER_LANGUAGE) @@ -130,7 +130,7 @@ def remove(mlist, cgi=False): return siteowner = Utils.get_site_email(extra='owner') # Should this be sent in the site list's preferred language? - msg = Message.UserNotification( + msg = Mailman.Message.UserNotification( siteowner, siteowner, _('Mailing list removal request for list %(listname)s'), sfp.getvalue(), mm_cfg.DEFAULT_SERVER_LANGUAGE) diff --git a/Mailman/Queue/CommandRunner.py b/Mailman/Queue/CommandRunner.py index ecd8c6cb..603b58d1 100644 --- a/Mailman/Queue/CommandRunner.py +++ b/Mailman/Queue/CommandRunner.py @@ -209,7 +209,7 @@ def indent(lines): recip = self.returnaddr or self.msg.get_sender() if not self.mlist.autorespondToSender(recip, self.msgdata['lang']): return - msg = Message.UserNotification( + msg = Mailman.Message.UserNotification( recip, self.mlist.GetOwnerEmail(), _('The results of your email commands'), diff --git a/bin/change_pw b/bin/change_pw index 173cfadc..673de5cb 100644 --- a/bin/change_pw +++ b/bin/change_pw @@ -186,7 +186,7 @@ def main(): try: hostname = mlist.host_name adminurl = mlist.GetScriptURL('admin', absolute=1) - msg = Message.UserNotification( + msg = Mailman.Message.UserNotification( mlist.owner[:], Utils.get_site_email(), _('Your new %(listname)s list password'), _('''\ diff --git a/bin/newlist b/bin/newlist index c03c7054..dcfed02e 100755 --- a/bin/newlist +++ b/bin/newlist @@ -256,7 +256,7 @@ def main(): otrans = i18n.get_translation() i18n.set_language(mlist.preferred_language) try: - msg = Message.UserNotification( + msg = Mailman.Message.UserNotification( owner_mail, siteowner, _(f'Your new mailing list: {listname}'), text, mlist.preferred_language) diff --git a/cron/checkdbs b/cron/checkdbs index 005637b7..600ccffe 100755 --- a/cron/checkdbs +++ b/cron/checkdbs @@ -104,7 +104,7 @@ def main(): else: subject = _( '%(realname)s moderator request check result') - msg = Message.UserNotification(mlist.GetOwnerEmail(), + msg = Mailman.Message.UserNotification(mlist.GetOwnerEmail(), mlist.GetBouncesEmail(), subject, text, mlist.preferred_language) diff --git a/cron/mailpasswds b/cron/mailpasswds index fa8a2bf0..ca3e7d4e 100755 --- a/cron/mailpasswds +++ b/cron/mailpasswds @@ -296,7 +296,7 @@ def main(): # Ensure host is properly decoded if isinstance(host, bytes): host = tounicode(host, 'latin-1') - msg = Message.UserNotification( + msg = Mailman.Message.UserNotification( addr, siteowner, _('%(host)s mailing list memberships reminder') % {'host': host}, text.encode(enc, 'replace'), poplang) diff --git a/tests/test_message.py b/tests/test_message.py index 5b44491f..3f29fa64 100644 --- a/tests/test_message.py +++ b/tests/test_message.py @@ -38,7 +38,7 @@ class TestSentMessage1(EmailBase): def test_user_notification(self): eq = self.assertEqual unless = self.failUnless - msg = Message.UserNotification( + msg = Mailman.Message.UserNotification( 'aperson@dom.ain', '_xtest@dom.ain', 'Your Test List', From a21b01bc75ec54c4ca299812212bb023dd8a7e8b Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 2 May 2025 13:22:20 -0400 Subject: [PATCH 312/748] make it clear that we are using Mailman.Message --- Mailman/Bouncer.py | 7 ++++ Mailman/Deliverer.py | 7 ++++ Mailman/Handlers/SMTPDirect.py | 66 +++++++++++++++++----------------- Mailman/Handlers/ToDigest.py | 29 +++++++++++++-- Mailman/ListAdmin.py | 32 ++++++++++++----- Mailman/Mailbox.py | 4 ++- Mailman/Queue/Switchboard.py | 2 +- 7 files changed, 101 insertions(+), 46 deletions(-) diff --git a/Mailman/Bouncer.py b/Mailman/Bouncer.py index 883ddf47..72fddd46 100644 --- a/Mailman/Bouncer.py +++ b/Mailman/Bouncer.py @@ -20,12 +20,19 @@ from builtins import object import sys import time +import os +import email +import errno +import pickle +import email.message +from email.message import Message from email.mime.text import MIMEText from email.mime.message import MIMEMessage from Mailman import mm_cfg from Mailman import Utils +from Mailman import Errors from Mailman.Message import Message from Mailman import MemberAdaptor from Mailman import Pending diff --git a/Mailman/Deliverer.py b/Mailman/Deliverer.py index e5912c58..c07a0e91 100644 --- a/Mailman/Deliverer.py +++ b/Mailman/Deliverer.py @@ -34,6 +34,13 @@ _ = i18n._ import sys +import os +import time +import email +import errno +import pickle +import email.message +from email.message import Message class Deliverer(object): diff --git a/Mailman/Handlers/SMTPDirect.py b/Mailman/Handlers/SMTPDirect.py index 2c7574bb..b8b3e5cd 100644 --- a/Mailman/Handlers/SMTPDirect.py +++ b/Mailman/Handlers/SMTPDirect.py @@ -34,6 +34,11 @@ from smtplib import SMTPException from base64 import b64encode import traceback +import os +import errno +import pickle +import email.message +from email.message import Message import Mailman.mm_cfg import Mailman.Utils @@ -148,24 +153,19 @@ def quit(self): def process(mlist, msg, msgdata): - # Convert email.message.Message to Mailman.Message if needed - if isinstance(msg, email.message.Message) and not isinstance(msg, Message): - try: - mailman_msg = Message() - # Copy all attributes from the original message - for key, value in msg.items(): - mailman_msg[key] = value - # Copy the payload - if msg.is_multipart(): - for part in msg.get_payload(): - mailman_msg.attach(part) - else: - mailman_msg.set_payload(msg.get_payload()) - msg = mailman_msg - except Exception as e: - Mailman.Logging.Syslog.mailman_log('error', 'Failed to convert message: %s\n%s', - str(e), traceback.format_exc()) - raise + # Convert email.message.Message to Mailman.Message.Message if needed + if isinstance(msg, email.message.Message): + newmsg = Message() + # Copy attributes + for k, v in msg.items(): + newmsg[k] = v + # Copy payload + if msg.is_multipart(): + for part in msg.get_payload(): + newmsg.attach(part) + else: + newmsg.set_payload(msg.get_payload()) + msg = newmsg recips = msgdata.get('recips') if not recips: @@ -201,7 +201,7 @@ def process(mlist, msg, msgdata): if deliveryfunc is None: # Be sure never to decorate the message more than once! if not msgdata.get('decorated'): - Mailman.Handlers.Decorate.process(mlist, msg, msgdata) + decorate(mlist, msg, msgdata) msgdata['decorated'] = True deliveryfunc = bulkdeliver refused = {} @@ -234,7 +234,7 @@ def process(mlist, msg, msgdata): # undelivered list and re-raise the exception. We don't know # how many of the last chunk might receive the message, so at # worst, everyone in this chunk will get a duplicate. Sigh. - Mailman.Logging.Syslog.mailman_log('error', + syslog('error', 'Delivery error for chunk: %s\nError: %s\n%s', chunk, str(e), traceback.format_exc()) chunks.append(chunk) @@ -262,12 +262,12 @@ def process(mlist, msg, msgdata): # still worthwhile doing the interpolation in syslog() because it'll catch # any catastrophic exceptions due to bogus format strings. if Mailman.mm_cfg.SMTP_LOG_EVERY_MESSAGE: - Mailman.Logging.Syslog.mailman_log(Mailman.mm_cfg.SMTP_LOG_EVERY_MESSAGE[0], + syslog(Mailman.mm_cfg.SMTP_LOG_EVERY_MESSAGE[0], Mailman.mm_cfg.SMTP_LOG_EVERY_MESSAGE[1] % d.copy()) if refused: if Mailman.mm_cfg.SMTP_LOG_REFUSED: - Mailman.Logging.Syslog.mailman_log.write_ex(Mailman.mm_cfg.SMTP_LOG_REFUSED[0], + syslog.write_ex(Mailman.mm_cfg.SMTP_LOG_REFUSED[0], Mailman.mm_cfg.SMTP_LOG_REFUSED[1], kws=d) elif msgdata.get('tolist'): @@ -277,7 +277,7 @@ def process(mlist, msg, msgdata): # the other messages, but in that case, we should probably have a # separate configuration variable to control that. if Mailman.mm_cfg.SMTP_LOG_SUCCESS: - Mailman.Logging.Syslog.mailman_log.write_ex(Mailman.mm_cfg.SMTP_LOG_SUCCESS[0], + syslog.write_ex(Mailman.mm_cfg.SMTP_LOG_SUCCESS[0], Mailman.mm_cfg.SMTP_LOG_SUCCESS[1], kws=d) # Process any failed deliveries. @@ -296,21 +296,21 @@ def process(mlist, msg, msgdata): if code >= 500 and code != 552: # A permanent failure permfailures.append(recip) - Mailman.Logging.Syslog.mailman_log('smtp-failure', + syslog('smtp-failure', 'Permanent delivery failure for %s: code %s, message: %s', recip, code, smtpmsg) else: # Deal with persistent transient failures by queuing them up for # future delivery. TBD: this could generate lots of log entries! tempfailures.append(recip) - Mailman.Logging.Syslog.mailman_log('smtp-failure', + syslog('smtp-failure', 'Temporary delivery failure for %s: code %s, message: %s', recip, code, smtpmsg) if Mailman.mm_cfg.SMTP_LOG_EACH_FAILURE: d.update({'recipient': recip, 'failcode' : code, 'failmsg' : smtpmsg}) - Mailman.Logging.Syslog.mailman_log.write_ex(Mailman.mm_cfg.SMTP_LOG_EACH_FAILURE[0], + syslog.write_ex(Mailman.mm_cfg.SMTP_LOG_EACH_FAILURE[0], Mailman.mm_cfg.SMTP_LOG_EACH_FAILURE[1], kws=d) # Return the results if tempfailures or permfailures: @@ -375,7 +375,7 @@ def verpdeliver(mlist, msg, msgdata, envsender, failures, conn): msgdata['recips'] = [recip] # Make a copy of the message and decorate + delivery that msgcopy = copy.deepcopy(msg) - Mailman.Handlers.Decorate.process(mlist, msgcopy, msgdata) + decorate(mlist, msgcopy, msgdata) # Calculate the envelope sender, which we may be VERPing if msgdata.get('verp'): try: @@ -386,7 +386,7 @@ def verpdeliver(mlist, msg, msgdata, envsender, failures, conn): # deliver it to this person, nor can we craft a valid verp # header. I don't think there's much we can do except ignore # this recipient. - Mailman.Logging.Syslog.mailman_log('smtp', 'Skipping VERP delivery to unqual recip: %s', + syslog('smtp', 'Skipping VERP delivery to unqual recip: %s', recip) continue d = {'bounces': bmailbox, @@ -395,7 +395,7 @@ def verpdeliver(mlist, msg, msgdata, envsender, failures, conn): } envsender = '%s@%s' % ((Mailman.mm_cfg.VERP_FORMAT % d), DOT.join(bdomain)) except Exception as e: - Mailman.Logging.Syslog.mailman_log('error', 'Failed to parse email addresses for VERP: %s', e) + syslog('error', 'Failed to parse email addresses for VERP: %s', e) continue if mlist.personalize == 2: # When fully personalizing, we want the To address to point to the @@ -439,7 +439,7 @@ def verpdeliver(mlist, msg, msgdata, envsender, failures, conn): # one. ;) bulkdeliver(mlist, msgcopy, msgdata, envsender, failures, conn) except Exception as e: - Mailman.Logging.Syslog.mailman_log('error', 'Failed to process VERP delivery: %s', e) + syslog('error', 'Failed to process VERP delivery: %s', e) continue @@ -501,11 +501,11 @@ def bulkdeliver(mlist, msg, msgdata, envsender, failures, conn): # Send the message refused = conn.sendmail(envsender, recips, msgtext) except smtplib.SMTPRecipientsRefused as e: - Mailman.Logging.Syslog.mailman_log('smtp-failure', 'All recipients refused: %s, msgid: %s', + syslog('smtp-failure', 'All recipients refused: %s, msgid: %s', e, msgid) refused = e.recipients except smtplib.SMTPResponseException as e: - Mailman.Logging.Syslog.mailman_log('smtp-failure', 'SMTP session failure: %s, %s, msgid: %s', + syslog('smtp-failure', 'SMTP session failure: %s, %s, msgid: %s', e.smtp_code, e.smtp_error, msgid) # If this was a permanent failure, don't add the recipients to the # refused, because we don't want them to be added to failures. @@ -521,7 +521,7 @@ def bulkdeliver(mlist, msg, msgdata, envsender, failures, conn): # MTA not responding, or other socket problems, or any other kind of # SMTPException. In that case, nothing got delivered, so treat this # as a temporary failure. - Mailman.Logging.Syslog.mailman_log('smtp-failure', 'Low level smtp error: %s, msgid: %s', e, msgid) + syslog('smtp-failure', 'Low level smtp error: %s, msgid: %s', e, msgid) error = str(e) for r in recips: refused[r] = (-1, error) diff --git a/Mailman/Handlers/ToDigest.py b/Mailman/Handlers/ToDigest.py index 8d6bd303..e9fefdfa 100644 --- a/Mailman/Handlers/ToDigest.py +++ b/Mailman/Handlers/ToDigest.py @@ -43,6 +43,10 @@ from email.header import decode_header, make_header, Header from email.charset import Charset import email +import email.message +from email.message import Message +import errno +import pickle from Mailman import mm_cfg from Mailman import Utils @@ -148,8 +152,27 @@ def process(mlist, msg, msgdata): This function handles adding messages to the digest and sending the digest when appropriate. All file operations use proper encoding handling. """ + if msgdata.get('isdigest'): + return + # Convert email.message.Message to Mailman.Message.Message if needed + if isinstance(msg, email.message.Message): + newmsg = Message() + # Copy attributes + for k, v in msg.items(): + newmsg[k] = v + # Copy payload + if msg.is_multipart(): + for part in msg.get_payload(): + newmsg.attach(part) + else: + newmsg.set_payload(msg.get_payload()) + msg = newmsg + # Create digest message + mimemsg = Message() + rfc1153msg = Message() + # Short circuit non-digestable lists - if not mlist.digestable or msgdata.get('isdigest'): + if not mlist.digestable: return mboxfile = os.path.join(mlist.fullpath(), 'digest.mbox') @@ -264,7 +287,7 @@ def send_digests(mlist, mboxpath): # Process the previous message msg_str = ''.join(current_msg) try: - msg = email.message_from_string(msg_str, Message) + msg = email.message.Message.from_string(msg_str) if msg is None: continue @@ -312,7 +335,7 @@ def send_digests(mlist, mboxpath): if current_msg: msg_str = ''.join(current_msg) try: - msg = email.message_from_string(msg_str, Message) + msg = email.message.Message.from_string(msg_str) if msg is not None: # Process the last message (same code as above) subject = decode_header_value(msg.get('subject', _('(no subject)')), lcset) diff --git a/Mailman/ListAdmin.py b/Mailman/ListAdmin.py index e1079a5f..907106fe 100644 --- a/Mailman/ListAdmin.py +++ b/Mailman/ListAdmin.py @@ -41,6 +41,7 @@ from email.generator import Generator from email.utils import getaddresses import email.message +from email.message import Message from Mailman import mm_cfg from Mailman import Utils @@ -447,7 +448,7 @@ def __handlepost(self, record, value, comment, preserve, forward, addr): if e.errno != errno.ENOENT: raise return LOST # Convert to Mailman.Message if needed - if isinstance(msg, email.message.Message) and not isinstance(msg, Message): + if isinstance(msg, Message) and not isinstance(msg, Message): mailman_msg = Message() # Copy all attributes from the original message for key, value in msg.items(): @@ -503,7 +504,7 @@ def __handlepost(self, record, value, comment, preserve, forward, addr): if e.errno != errno.ENOENT: raise raise Errors.LostHeldMessage(path) # Convert to Mailman.Message if needed - if isinstance(copy, email.message.Message) and not isinstance(copy, Message): + if isinstance(copy, Message) and not isinstance(copy, Message): mailman_msg = Message() # Copy all attributes from the original message for key, value in copy.items(): @@ -534,7 +535,7 @@ def __handlepost(self, record, value, comment, preserve, forward, addr): otrans = i18n.get_translation() i18n.set_language(lang) try: - fmsg = Mailman.Message.UserNotification( + fmsg = Message.UserNotification( addr, self.GetBouncesEmail(), _('Forward of moderated message'), lang=lang) @@ -605,7 +606,7 @@ def HoldSubscription(self, addr, fullname, password, digest, lang): # This message should appear to come from the -owner so as # to avoid any useless bounce processing. owneraddr = self.GetOwnerEmail() - msg = Mailman.Message.UserNotification(owneraddr, owneraddr, subject, text, + msg = Message.UserNotification(owneraddr, owneraddr, subject, text, self.preferred_language) msg.send(self, **{'tomoderators': 1}) # Restore the user's preferred language. @@ -666,7 +667,7 @@ def HoldUnsubscription(self, addr): # This message should appear to come from the -owner so as # to avoid any useless bounce processing. owneraddr = self.GetOwnerEmail() - msg = Mailman.Message.UserNotification(owneraddr, owneraddr, subject, text, + msg = Message.UserNotification(owneraddr, owneraddr, subject, text, self.preferred_language) msg.send(self, **{'tomoderators': 1}) @@ -717,7 +718,7 @@ def __refuse(self, request, recip, comment, origmsg=None, lang=None): subject = _('Request to mailing list %(realname)s rejected') finally: i18n.set_translation(otrans) - msg = Mailman.Message.UserNotification(recip, self.GetOwnerEmail(), + msg = Message.UserNotification(recip, self.GetOwnerEmail(), subject, text, lang) msg.send(self) @@ -866,7 +867,7 @@ def readMessage(path): if ext == '.txt': msg = email.message_from_file(fp, Message) # Convert to Mailman.Message if needed - if isinstance(msg, email.message.Message) and not isinstance(msg, Message): + if isinstance(msg, Message) and not isinstance(msg, Message): mailman_msg = Message() # Copy all attributes from the original message for key, value in msg.items(): @@ -882,7 +883,7 @@ def readMessage(path): assert ext == '.pck' msg = pickle.load(fp, fix_imports=True, encoding='latin1') # Convert to Mailman.Message if needed - if isinstance(msg, email.message.Message) and not isinstance(msg, Message): + if isinstance(msg, Message) and not isinstance(msg, Message): mailman_msg = Message() # Copy all attributes from the original message for key, value in msg.items(): @@ -897,3 +898,18 @@ def readMessage(path): finally: fp.close() return msg + +def process(mlist, msg, msgdata): + # Convert email.message.Message to Mailman.Message.Message if needed + if isinstance(msg, email.message.Message): + newmsg = Message.Message() + # Copy attributes + for k, v in msg.items(): + newmsg[k] = v + # Copy payload + if msg.is_multipart(): + for part in msg.get_payload(): + newmsg.attach(part) + else: + newmsg.set_payload(msg.get_payload()) + msg = newmsg diff --git a/Mailman/Mailbox.py b/Mailman/Mailbox.py index d37a333b..13e86fe6 100644 --- a/Mailman/Mailbox.py +++ b/Mailman/Mailbox.py @@ -24,6 +24,8 @@ from types import MethodType import email +import email.message +from email.message import Message from email.parser import Parser from email.errors import MessageParseError @@ -33,7 +35,7 @@ def _safeparser(fp): try: - return email.message_from_file(fp, Message) + return email.message_from_file(fp, Mailman.Message.Message) except MessageParseError: # Don't return None since that will stop a mailbox iterator return '' diff --git a/Mailman/Queue/Switchboard.py b/Mailman/Queue/Switchboard.py index 64c0ca95..344c8565 100644 --- a/Mailman/Queue/Switchboard.py +++ b/Mailman/Queue/Switchboard.py @@ -41,7 +41,7 @@ import pickle import marshal import email.message -from email.message import Message as EmailMessage +from email.message import Message import hashlib import socket import traceback From 7a4173faf80b62deb208ebcb73fea7e71f56e1e1 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 2 May 2025 13:26:38 -0400 Subject: [PATCH 313/748] update --- Mailman/Handlers/Hold.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/Mailman/Handlers/Hold.py b/Mailman/Handlers/Hold.py index ad483e7a..73cc0011 100644 --- a/Mailman/Handlers/Hold.py +++ b/Mailman/Handlers/Hold.py @@ -38,11 +38,10 @@ from Mailman import mm_cfg from Mailman import Utils from Mailman import Errors -from Mailman.Message import Message +from Mailman.Message import Message, UserNotification from Mailman import i18n from Mailman import Pending from Mailman.Logging.Syslog import syslog -from Mailman.Message import UserNotification # First, play footsie with _ so that the following are marked as translated, # but aren't actually translated until we need the text later on. @@ -198,8 +197,19 @@ def hold_for_approval(mlist, msg, msgdata, exc): # # Check if exc is a class (new-style in Python 3) if isinstance(exc, type): - # Go ahead and instantiate it now. exc = exc() + # Get the sender of the message + sender = msg.get_sender() + # Get the list's owner address + owneraddr = mlist.GetOwnerEmail() + # Get the subject + subject = msg.get('subject', _('(no subject)')) + # Get the language to use + lang = mlist.getMemberLanguage(sender) + # Get the text of the message + text = exc.rejection_notice(mlist) + # Create the notification message + nmsg = UserNotification(sender, owneraddr, subject, text, lang) listname = mlist.real_name sender = msgdata.get('sender', msg.get_sender()) usersubject = msg.get('subject') @@ -209,7 +219,6 @@ def hold_for_approval(mlist, msg, msgdata, exc): else: usersubject = _('(no subject)') message_id = msg.get('message-id', 'n/a') - owneraddr = mlist.GetOwnerEmail() adminaddr = mlist.GetBouncesEmail() requestaddr = mlist.GetRequestEmail() # We need to send both the reason and the rejection notice through the @@ -246,7 +255,7 @@ def hold_for_approval(mlist, msg, msgdata, exc): lang = msgdata.get('lang', mlist.getMemberLanguage(sender)) subject = _('Your message to %(listname)s awaits moderator approval') text = Utils.maketext('postheld.txt', d, lang=lang, mlist=mlist) - nmsg = Mailman.Message.UserNotification(sender, owneraddr, subject, text, lang) + nmsg = UserNotification(sender, owneraddr, subject, text, lang) nmsg.send(mlist) # Now the message for the list owners. Be sure to include the list # moderators in this message. This one should appear to come from @@ -264,7 +273,7 @@ def hold_for_approval(mlist, msg, msgdata, exc): d['subject'] = usersubject # craft the admin notification message and deliver it subject = _('%(listname)s post from %(sender)s requires approval') - nmsg = Mailman.Message.UserNotification(owneraddr, owneraddr, subject, + nmsg = UserNotification(owneraddr, owneraddr, subject, lang=lang) nmsg.set_type('multipart/mixed') text = MIMEText( From f5851bdef81116f2416e516e087637f881fd8b38 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 2 May 2025 13:30:46 -0400 Subject: [PATCH 314/748] update --- Mailman/Handlers/Hold.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Mailman/Handlers/Hold.py b/Mailman/Handlers/Hold.py index 73cc0011..fceb423c 100644 --- a/Mailman/Handlers/Hold.py +++ b/Mailman/Handlers/Hold.py @@ -35,6 +35,7 @@ import re from email.iterators import body_line_iterator +import Mailman from Mailman import mm_cfg from Mailman import Utils from Mailman import Errors From 85cc2163cb0eec3032bc462f32abd9d9dd863fb8 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 2 May 2025 13:34:11 -0400 Subject: [PATCH 315/748] update --- Mailman/Handlers/SMTPDirect.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Mailman/Handlers/SMTPDirect.py b/Mailman/Handlers/SMTPDirect.py index b8b3e5cd..5df402b5 100644 --- a/Mailman/Handlers/SMTPDirect.py +++ b/Mailman/Handlers/SMTPDirect.py @@ -40,11 +40,12 @@ import email.message from email.message import Message +import Mailman import Mailman.mm_cfg import Mailman.Utils import Mailman.Errors from Mailman.Message import Message -import Mailman.Handlers.Decorate +from Mailman.Handlers.Decorate import decorate import Mailman.Logging.Syslog import Mailman.SafeDict From f266ae54b990aa006712eaca798392d52b44b0af Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 2 May 2025 13:39:51 -0400 Subject: [PATCH 316/748] update --- Mailman/Handlers/SMTPDirect.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Mailman/Handlers/SMTPDirect.py b/Mailman/Handlers/SMTPDirect.py index 5df402b5..0b9defd7 100644 --- a/Mailman/Handlers/SMTPDirect.py +++ b/Mailman/Handlers/SMTPDirect.py @@ -330,9 +330,9 @@ def chunkify(recips, chunksize): 'edu': 4, 'us' : 5, 'ca' : 6, - 'uk' : 7, - 'jp' : 8, - 'au' : 9, + 'uk' : 7, + 'jp' : 8, + 'au' : 9, } # Need to sort by domain name. if we split to chunks it is possible # some well-known domains will be interspersed as we sort by @@ -343,6 +343,7 @@ def chunkify(recips, chunksize): i = r.rfind('.') if i >= 0: tld = r[i+1:] + # Use get() with default value of 0 for unknown TLDs bin = chunkmap.get(tld, 0) bucket = buckets.get(bin, []) bucket.append(r) From 0c82bf4633ecb54a17a708faf82abb7860a0c49c Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 2 May 2025 13:42:06 -0400 Subject: [PATCH 317/748] update --- Mailman/Queue/Switchboard.py | 12 ++++++++---- bin/mailmanctl | 22 +++++++++++++++------- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/Mailman/Queue/Switchboard.py b/Mailman/Queue/Switchboard.py index 344c8565..75f19bd7 100644 --- a/Mailman/Queue/Switchboard.py +++ b/Mailman/Queue/Switchboard.py @@ -416,9 +416,13 @@ def files(self, extension='.pck'): when, digest = parts try: # Validate timestamp format - float(when) + when_float = float(when) # Validate digest format (should be hex) - int(digest, 16) + try: + digest_int = int(digest, 16) + except ValueError as e: + mailman_log('error', 'Invalid digest format in queue file %s: %s', f, e) + raise except ValueError as e: mailman_log('warning', 'Invalid file name format in queue directory (invalid timestamp/digest): %s: %s', f, str(e)) # Try to recover by moving to shunt queue @@ -437,8 +441,8 @@ def files(self, extension='.pck'): # Throw out any files which don't match our bitrange. BAW: test # performance and end-cases of this algorithm. MAS: both # comparisons need to be <= to get complete range. - if lower is None or (lower <= int(digest, 16) <= upper): - key = float(when) + if lower is None or (lower <= digest_int <= upper): + key = when_float while key in times: key += DELTA times[key] = filebase diff --git a/bin/mailmanctl b/bin/mailmanctl index 526276ac..e4a65b0f 100755 --- a/bin/mailmanctl +++ b/bin/mailmanctl @@ -191,13 +191,21 @@ def kill_watcher(sig): def get_lock_data(): # Return the hostname, pid, and tempfile - fp = open(LOCKFILE) - filename = os.path.split(fp.read().strip())[1] - fp.close() - parts = filename.split('.') - hostname = DOT.join(parts[1:-1]) - pid = int(parts[-1]) - return hostname, int(pid), filename + try: + fp = open(LOCKFILE) + filename = os.path.split(fp.read().strip())[1] + fp.close() + parts = filename.split('.') + hostname = DOT.join(parts[1:-1]) + try: + pid = int(parts[-1]) + except ValueError as e: + syslog('error', 'Invalid PID in lock file %s: %s', LOCKFILE, e) + raise Errors.LockError('Invalid PID in lock file') + return hostname, pid, filename + except IOError as e: + syslog('error', 'Could not read lock file %s: %s', LOCKFILE, e) + raise Errors.LockError('Could not read lock file') def qrunner_state(): From eb877e4c43d5df83a6a6c3966982d042e7a003aa Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 2 May 2025 13:43:34 -0400 Subject: [PATCH 318/748] update --- bin/mailmanctl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/mailmanctl b/bin/mailmanctl index e4a65b0f..4facae2d 100755 --- a/bin/mailmanctl +++ b/bin/mailmanctl @@ -201,11 +201,11 @@ def get_lock_data(): pid = int(parts[-1]) except ValueError as e: syslog('error', 'Invalid PID in lock file %s: %s', LOCKFILE, e) - raise Errors.LockError('Invalid PID in lock file') + raise LockFile.LockError('Invalid PID in lock file') return hostname, pid, filename except IOError as e: syslog('error', 'Could not read lock file %s: %s', LOCKFILE, e) - raise Errors.LockError('Could not read lock file') + raise LockFile.LockError('Could not read lock file') def qrunner_state(): From 34dda0c937c54f36ca2dcdbfcd33b1ad803d193f Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 2 May 2025 13:47:00 -0400 Subject: [PATCH 319/748] update --- bin/mailmanctl | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/bin/mailmanctl b/bin/mailmanctl index 4facae2d..9de7f1fd 100755 --- a/bin/mailmanctl +++ b/bin/mailmanctl @@ -192,17 +192,18 @@ def kill_watcher(sig): def get_lock_data(): # Return the hostname, pid, and tempfile try: - fp = open(LOCKFILE) - filename = os.path.split(fp.read().strip())[1] - fp.close() - parts = filename.split('.') - hostname = DOT.join(parts[1:-1]) - try: - pid = int(parts[-1]) - except ValueError as e: - syslog('error', 'Invalid PID in lock file %s: %s', LOCKFILE, e) - raise LockFile.LockError('Invalid PID in lock file') - return hostname, pid, filename + with open(LOCKFILE) as fp: + content = fp.read().strip().split() + if len(content) != 2: + syslog('error', 'Invalid lock file format in %s: expected "pid hostname"', LOCKFILE) + raise LockFile.LockError('Invalid lock file format') + try: + pid = int(content[0]) + hostname = content[1] + except ValueError as e: + syslog('error', 'Invalid PID in lock file %s: %s', LOCKFILE, e) + raise LockFile.LockError('Invalid PID in lock file') + return hostname, pid, None # tempfile is not used in this format except IOError as e: syslog('error', 'Could not read lock file %s: %s', LOCKFILE, e) raise LockFile.LockError('Could not read lock file') From 511696269819d175a66b311b9a571cb7b605d547 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 2 May 2025 13:48:57 -0400 Subject: [PATCH 320/748] update --- bin/mailmanctl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bin/mailmanctl b/bin/mailmanctl index 9de7f1fd..87821f8c 100755 --- a/bin/mailmanctl +++ b/bin/mailmanctl @@ -237,10 +237,11 @@ def acquire_lock_1(force): if not force or qrunner_state(): raise # Force removal of lock first - lock._disown() + lock._transfer_to(os.getpid()) # Transfer to current process hostname, pid, tempfile = get_lock_data() os.unlink(LOCKFILE) - os.unlink(os.path.join(mm_cfg.LOCK_DIR, tempfile)) + if tempfile: + os.unlink(os.path.join(mm_cfg.LOCK_DIR, tempfile)) return acquire_lock_1(force=0) From e78951717175e2f41136a6ba95e87d6dd80e8921 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 2 May 2025 13:50:39 -0400 Subject: [PATCH 321/748] update --- bin/mailmanctl | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/bin/mailmanctl b/bin/mailmanctl index 87821f8c..bc82e663 100755 --- a/bin/mailmanctl +++ b/bin/mailmanctl @@ -237,11 +237,22 @@ def acquire_lock_1(force): if not force or qrunner_state(): raise # Force removal of lock first - lock._transfer_to(os.getpid()) # Transfer to current process - hostname, pid, tempfile = get_lock_data() - os.unlink(LOCKFILE) - if tempfile: - os.unlink(os.path.join(mm_cfg.LOCK_DIR, tempfile)) + try: + # Read the current lock file content + with open(LOCKFILE) as fp: + content = fp.read().strip() + if content: + # Try to clean up any stale lock files + lock.clean_stale_locks() + except (IOError, OSError) as e: + syslog('error', 'Error cleaning up stale lock: %s', str(e)) + # Remove the lock file + try: + os.unlink(LOCKFILE) + except OSError as e: + if e.errno != errno.ENOENT: + syslog('error', 'Error removing lock file: %s', str(e)) + # Try to acquire the lock again return acquire_lock_1(force=0) From b8420727b124751d1b9473d524a707bdbbb02dce Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 2 May 2025 13:54:22 -0400 Subject: [PATCH 322/748] update --- Mailman/Handlers/Hold.py | 1 - Mailman/Handlers/SMTPDirect.py | 1 - 2 files changed, 2 deletions(-) diff --git a/Mailman/Handlers/Hold.py b/Mailman/Handlers/Hold.py index fceb423c..73cc0011 100644 --- a/Mailman/Handlers/Hold.py +++ b/Mailman/Handlers/Hold.py @@ -35,7 +35,6 @@ import re from email.iterators import body_line_iterator -import Mailman from Mailman import mm_cfg from Mailman import Utils from Mailman import Errors diff --git a/Mailman/Handlers/SMTPDirect.py b/Mailman/Handlers/SMTPDirect.py index 0b9defd7..6f05618d 100644 --- a/Mailman/Handlers/SMTPDirect.py +++ b/Mailman/Handlers/SMTPDirect.py @@ -40,7 +40,6 @@ import email.message from email.message import Message -import Mailman import Mailman.mm_cfg import Mailman.Utils import Mailman.Errors From 7ff813e6243f18b9b2d6cd8464e3c8e9f8e79d5c Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 2 May 2025 13:59:14 -0400 Subject: [PATCH 323/748] errors update --- Mailman/Handlers/Hold.py | 347 +++++++++++++++++---------------- Mailman/Handlers/SMTPDirect.py | 101 +++++----- Mailman/Queue/Switchboard.py | 74 +++---- 3 files changed, 271 insertions(+), 251 deletions(-) diff --git a/Mailman/Handlers/Hold.py b/Mailman/Handlers/Hold.py index 73cc0011..b5780b59 100644 --- a/Mailman/Handlers/Hold.py +++ b/Mailman/Handlers/Hold.py @@ -34,6 +34,7 @@ import email.utils import re from email.iterators import body_line_iterator +import traceback from Mailman import mm_cfg from Mailman import Utils @@ -124,182 +125,192 @@ def ackp(msg): def process(mlist, msg, msgdata): - if msgdata.get('approved'): - return - # Get the sender of the message - listname = mlist.internal_name() - adminaddr = listname + '-admin' - sender = msg.get_sender() - # Special case an ugly sendmail feature: If there exists an alias of the - # form "owner-foo: bar" and sendmail receives mail for address "foo", - # sendmail will change the envelope sender of the message to "bar" before - # delivering. This feature does not appear to be configurable. *Boggle*. - if not sender or sender[:len(listname)+6] == adminaddr: - sender = msg.get_sender(use_envelope=0) - # - # Possible administrivia? - if mlist.administrivia and Utils.is_administrivia(msg): - hold_for_approval(mlist, msg, msgdata, Administrivia) - # no return - # - # Are there too many recipients to the message? - if mlist.max_num_recipients > 0: - # figure out how many recipients there are - recips = email.utils.getaddresses(msg.get_all('to', []) + - msg.get_all('cc', [])) - if len(recips) >= mlist.max_num_recipients: - hold_for_approval(mlist, msg, msgdata, TooManyRecipients) + try: + if msgdata.get('approved'): + return + # Get the sender of the message + listname = mlist.internal_name() + adminaddr = listname + '-admin' + sender = msg.get_sender() + # Special case an ugly sendmail feature: If there exists an alias of the + # form "owner-foo: bar" and sendmail receives mail for address "foo", + # sendmail will change the envelope sender of the message to "bar" before + # delivering. This feature does not appear to be configurable. *Boggle*. + if not sender or sender[:len(listname)+6] == adminaddr: + sender = msg.get_sender(use_envelope=0) + # + # Possible administrivia? + if mlist.administrivia and Utils.is_administrivia(msg): + hold_for_approval(mlist, msg, msgdata, Administrivia) # no return - # - # Implicit destination? Note that message originating from the Usenet - # side of the world should never be checked for implicit destination. - if mlist.require_explicit_destination and \ - not mlist.HasExplicitDest(msg) and \ - not msgdata.get('fromusenet'): - # then - hold_for_approval(mlist, msg, msgdata, ImplicitDestination) - # no return - # - # Suspicious headers? - if mlist.bounce_matching_headers: - triggered = mlist.hasMatchingHeader(msg) - if triggered: - # TBD: Darn - can't include the matching line for the admin - # message because the info would also go to the sender - hold_for_approval(mlist, msg, msgdata, SuspiciousHeaders) + # + # Are there too many recipients to the message? + if mlist.max_num_recipients > 0: + # figure out how many recipients there are + recips = email.utils.getaddresses(msg.get_all('to', []) + + msg.get_all('cc', [])) + if len(recips) >= mlist.max_num_recipients: + hold_for_approval(mlist, msg, msgdata, TooManyRecipients) + # no return + # + # Implicit destination? Note that message originating from the Usenet + # side of the world should never be checked for implicit destination. + if mlist.require_explicit_destination and \ + not mlist.HasExplicitDest(msg) and \ + not msgdata.get('fromusenet'): + # then + hold_for_approval(mlist, msg, msgdata, ImplicitDestination) # no return - # - # Is the message too big? - if mlist.max_message_size > 0: - bodylen = 0 - for line in body_line_iterator(msg): - bodylen += len(line) - for part in msg.walk(): - if part.preamble: - bodylen += len(part.preamble) - if part.epilogue: - bodylen += len(part.epilogue) - if bodylen/1024.0 > mlist.max_message_size: - hold_for_approval(mlist, msg, msgdata, - MessageTooBig(bodylen, mlist.max_message_size)) - # no return - # - # Are we gatewaying to a moderated newsgroup and is this list the - # moderator's address for the group? - if mlist.gateway_to_news and mlist.news_moderation == 2: - hold_for_approval(mlist, msg, msgdata, ModeratedNewsgroup) + # + # Suspicious headers? + if mlist.bounce_matching_headers: + triggered = mlist.hasMatchingHeader(msg) + if triggered: + # TBD: Darn - can't include the matching line for the admin + # message because the info would also go to the sender + hold_for_approval(mlist, msg, msgdata, SuspiciousHeaders) + # no return + # + # Is the message too big? + if mlist.max_message_size > 0: + bodylen = 0 + for line in body_line_iterator(msg): + bodylen += len(line) + for part in msg.walk(): + if part.preamble: + bodylen += len(part.preamble) + if part.epilogue: + bodylen += len(part.epilogue) + if bodylen/1024.0 > mlist.max_message_size: + hold_for_approval(mlist, msg, msgdata, + MessageTooBig(bodylen, mlist.max_message_size)) + # no return + # + # Are we gatewaying to a moderated newsgroup and is this list the + # moderator's address for the group? + if mlist.gateway_to_news and mlist.news_moderation == 2: + hold_for_approval(mlist, msg, msgdata, ModeratedNewsgroup) + except Exception as e: + syslog('error', 'Error in Hold.process: %s\nTraceback:\n%s', + str(e), traceback.format_exc()) + raise def hold_for_approval(mlist, msg, msgdata, exc): - # BAW: This should really be tied into the email confirmation system so - # that the message can be approved or denied via email as well as the - # web. - # - # Check if exc is a class (new-style in Python 3) - if isinstance(exc, type): - exc = exc() - # Get the sender of the message - sender = msg.get_sender() - # Get the list's owner address - owneraddr = mlist.GetOwnerEmail() - # Get the subject - subject = msg.get('subject', _('(no subject)')) - # Get the language to use - lang = mlist.getMemberLanguage(sender) - # Get the text of the message - text = exc.rejection_notice(mlist) - # Create the notification message - nmsg = UserNotification(sender, owneraddr, subject, text, lang) - listname = mlist.real_name - sender = msgdata.get('sender', msg.get_sender()) - usersubject = msg.get('subject') - charset = Utils.GetCharSet(mlist.preferred_language) - if usersubject: - usersubject = Utils.oneline(usersubject, charset) - else: - usersubject = _('(no subject)') - message_id = msg.get('message-id', 'n/a') - adminaddr = mlist.GetBouncesEmail() - requestaddr = mlist.GetRequestEmail() - # We need to send both the reason and the rejection notice through the - # translator again, because of the games we play above - reason = Utils.wrap(exc.reason_notice()) - if isinstance(exc, NonMemberPost) and mlist.nonmember_rejection_notice: - msgdata['rejection_notice'] = Utils.wrap( - mlist.nonmember_rejection_notice.replace( - '%(listowner)s', owneraddr)) - else: - msgdata['rejection_notice'] = Utils.wrap(exc.rejection_notice(mlist)) - id = mlist.HoldMessage(msg, reason, msgdata) - # Now we need to craft and send a message to the list admin so they can - # deal with the held message. - d = {'listname' : listname, - 'hostname' : mlist.host_name, - 'reason' : _(reason), - 'sender' : sender, - 'subject' : usersubject, - 'admindb_url': mlist.GetScriptURL('admindb', absolute=1), - } - # We may want to send a notification to the original sender too - fromusenet = msgdata.get('fromusenet') - # Since we're sending two messages, which may potentially be in different - # languages (the user's preferred and the list's preferred for the admin), - # we need to play some i18n games here. Since the current language - # context ought to be set up for the user, let's craft his message first. - cookie = mlist.pend_new(Pending.HELD_MESSAGE, id) - if not fromusenet and ackp(msg) and mlist.respond_to_post_requests and \ - mlist.autorespondToSender(sender, mlist.getMemberLanguage(sender)): - # Get a confirmation cookie - d['confirmurl'] = '%s/%s' % (mlist.GetScriptURL('confirm', absolute=1), - cookie) - lang = msgdata.get('lang', mlist.getMemberLanguage(sender)) - subject = _('Your message to %(listname)s awaits moderator approval') - text = Utils.maketext('postheld.txt', d, lang=lang, mlist=mlist) + try: + # BAW: This should really be tied into the email confirmation system so + # that the message can be approved or denied via email as well as the + # web. + # + # Check if exc is a class (new-style in Python 3) + if isinstance(exc, type): + exc = exc() + # Get the sender of the message + sender = msg.get_sender() + # Get the list's owner address + owneraddr = mlist.GetOwnerEmail() + # Get the subject + subject = msg.get('subject', _('(no subject)')) + # Get the language to use + lang = mlist.getMemberLanguage(sender) + # Get the text of the message + text = exc.rejection_notice(mlist) + # Create the notification message nmsg = UserNotification(sender, owneraddr, subject, text, lang) - nmsg.send(mlist) - # Now the message for the list owners. Be sure to include the list - # moderators in this message. This one should appear to come from - # -owner since we really don't need to do bounce processing on it. - if mlist.admin_immed_notify: - # Now let's temporarily set the language context to that which the - # admin is expecting. - otranslation = i18n.get_translation() - i18n.set_language(mlist.preferred_language) - try: - lang = mlist.preferred_language - charset = Utils.GetCharSet(lang) - # We need to regenerate or re-translate a few values in d - d['reason'] = _(reason) - d['subject'] = usersubject - # craft the admin notification message and deliver it - subject = _('%(listname)s post from %(sender)s requires approval') - nmsg = UserNotification(owneraddr, owneraddr, subject, - lang=lang) - nmsg.set_type('multipart/mixed') - text = MIMEText( - Utils.maketext('postauth.txt', d, raw=1, mlist=mlist), - _charset=charset) - dmsg = MIMEText(Utils.wrap(_("""\ + listname = mlist.real_name + sender = msgdata.get('sender', msg.get_sender()) + usersubject = msg.get('subject') + charset = Utils.GetCharSet(mlist.preferred_language) + if usersubject: + usersubject = Utils.oneline(usersubject, charset) + else: + usersubject = _('(no subject)') + message_id = msg.get('message-id', 'n/a') + adminaddr = mlist.GetBouncesEmail() + requestaddr = mlist.GetRequestEmail() + # We need to send both the reason and the rejection notice through the + # translator again, because of the games we play above + reason = Utils.wrap(exc.reason_notice()) + if isinstance(exc, NonMemberPost) and mlist.nonmember_rejection_notice: + msgdata['rejection_notice'] = Utils.wrap( + mlist.nonmember_rejection_notice.replace( + '%(listowner)s', owneraddr)) + else: + msgdata['rejection_notice'] = Utils.wrap(exc.rejection_notice(mlist)) + id = mlist.HoldMessage(msg, reason, msgdata) + # Now we need to craft and send a message to the list admin so they can + # deal with the held message. + d = {'listname' : listname, + 'hostname' : mlist.host_name, + 'reason' : _(reason), + 'sender' : sender, + 'subject' : usersubject, + 'admindb_url': mlist.GetScriptURL('admindb', absolute=1), + } + # We may want to send a notification to the original sender too + fromusenet = msgdata.get('fromusenet') + # Since we're sending two messages, which may potentially be in different + # languages (the user's preferred and the list's preferred for the admin), + # we need to play some i18n games here. Since the current language + # context ought to be set up for the user, let's craft his message first. + cookie = mlist.pend_new(Pending.HELD_MESSAGE, id) + if not fromusenet and ackp(msg) and mlist.respond_to_post_requests and \ + mlist.autorespondToSender(sender, mlist.getMemberLanguage(sender)): + # Get a confirmation cookie + d['confirmurl'] = '%s/%s' % (mlist.GetScriptURL('confirm', absolute=1), + cookie) + lang = msgdata.get('lang', mlist.getMemberLanguage(sender)) + subject = _('Your message to %(listname)s awaits moderator approval') + text = Utils.maketext('postheld.txt', d, lang=lang, mlist=mlist) + nmsg = UserNotification(sender, owneraddr, subject, text, lang) + nmsg.send(mlist) + # Now the message for the list owners. Be sure to include the list + # moderators in this message. This one should appear to come from + # -owner since we really don't need to do bounce processing on it. + if mlist.admin_immed_notify: + # Now let's temporarily set the language context to that which the + # admin is expecting. + otranslation = i18n.get_translation() + i18n.set_language(mlist.preferred_language) + try: + lang = mlist.preferred_language + charset = Utils.GetCharSet(lang) + # We need to regenerate or re-translate a few values in d + d['reason'] = _(reason) + d['subject'] = usersubject + # craft the admin notification message and deliver it + subject = _('%(listname)s post from %(sender)s requires approval') + nmsg = UserNotification(owneraddr, owneraddr, subject, + lang=lang) + nmsg.set_type('multipart/mixed') + text = MIMEText( + Utils.maketext('postauth.txt', d, raw=1, mlist=mlist), + _charset=charset) + dmsg = MIMEText(Utils.wrap(_("""\ If you reply to this message, keeping the Subject: header intact, Mailman will discard the held message. Do this if the message is spam. If you reply to this message and include an Approved: header with the list password in it, the message will be approved for posting to the list. The Approved: header can also appear in the first line of the body of the reply.""")), - _charset=Utils.GetCharSet(lang)) - dmsg['Subject'] = 'confirm ' + cookie - dmsg['Sender'] = requestaddr - dmsg['From'] = requestaddr - dmsg['Date'] = email.utils.formatdate(localtime=True) - dmsg['Message-ID'] = Utils.unique_message_id(mlist) - nmsg.attach(text) - nmsg.attach(MIMEMessage(msg)) - nmsg.attach(MIMEMessage(dmsg)) - nmsg.send(mlist, **{'tomoderators': 1}) - finally: - i18n.set_translation(otranslation) - # Log the held message - syslog('vette', '%s post from %s held, message-id=%s: %s', - listname, sender, message_id, reason) - # raise the specific MessageHeld exception to exit out of the message - # delivery pipeline - raise exc + _charset=Utils.GetCharSet(lang)) + dmsg['Subject'] = 'confirm ' + cookie + dmsg['Sender'] = requestaddr + dmsg['From'] = requestaddr + dmsg['Date'] = email.utils.formatdate(localtime=True) + dmsg['Message-ID'] = Utils.unique_message_id(mlist) + nmsg.attach(text) + nmsg.attach(MIMEMessage(msg)) + nmsg.attach(MIMEMessage(dmsg)) + nmsg.send(mlist, **{'tomoderators': 1}) + finally: + i18n.set_translation(otranslation) + # Log the held message + syslog('vette', '%s post from %s held, message-id=%s: %s', + listname, sender, message_id, reason) + # raise the specific MessageHeld exception to exit out of the message + # delivery pipeline + raise exc + except Exception as e: + syslog('error', 'Error in Hold.hold_for_approval: %s\nTraceback:\n%s', + str(e), traceback.format_exc()) + raise diff --git a/Mailman/Handlers/SMTPDirect.py b/Mailman/Handlers/SMTPDirect.py index 6f05618d..0ca370c5 100644 --- a/Mailman/Handlers/SMTPDirect.py +++ b/Mailman/Handlers/SMTPDirect.py @@ -83,29 +83,29 @@ def __connect(self): # Use native TLS support self.__conn.starttls() except SMTPException as e: - Mailman.Logging.Syslog.mailman_log('smtp-failure', 'SMTP TLS error: %s\n%s', + Mailman.Logging.Syslog.mailman_log('smtp-failure', 'SMTP TLS error: %s\nTraceback:\n%s', str(e), traceback.format_exc()) self.quit() raise try: self.__conn.login(Mailman.mm_cfg.SMTP_USER, Mailman.mm_cfg.SMTP_PASSWD) except smtplib.SMTPHeloError as e: - Mailman.Logging.Syslog.mailman_log('smtp-failure', 'SMTP HELO error: %s\n%s', + Mailman.Logging.Syslog.mailman_log('smtp-failure', 'SMTP HELO error: %s\nTraceback:\n%s', str(e), traceback.format_exc()) self.quit() raise except smtplib.SMTPAuthenticationError as e: - Mailman.Logging.Syslog.mailman_log('smtp-failure', 'SMTP AUTH error: %s\n%s', + Mailman.Logging.Syslog.mailman_log('smtp-failure', 'SMTP AUTH error: %s\nTraceback:\n%s', str(e), traceback.format_exc()) self.quit() except smtplib.SMTPException as e: Mailman.Logging.Syslog.mailman_log('smtp-failure', - 'SMTP - no suitable authentication method found: %s\n%s', + 'SMTP - no suitable authentication method found: %s\nTraceback:\n%s', str(e), traceback.format_exc()) self.quit() raise except (socket.error, smtplib.SMTPException) as e: - Mailman.Logging.Syslog.mailman_log('smtp-failure', 'SMTP connection error: %s\n%s', + Mailman.Logging.Syslog.mailman_log('smtp-failure', 'SMTP connection error: %s\nTraceback:\n%s', str(e), traceback.format_exc()) self.quit() raise @@ -129,7 +129,7 @@ def sendmail(self, envsender, recips, msgtext): except smtplib.SMTPException as e: # For safety, close this connection. The next send attempt will # automatically re-open it. Pass the exception on up. - Mailman.Logging.Syslog.mailman_log('smtp-failure', 'SMTP sendmail error: %s\n%s', + Mailman.Logging.Syslog.mailman_log('smtp-failure', 'SMTP sendmail error: %s\nTraceback:\n%s', str(e), traceback.format_exc()) self.quit() raise @@ -153,50 +153,55 @@ def quit(self): def process(mlist, msg, msgdata): - # Convert email.message.Message to Mailman.Message.Message if needed - if isinstance(msg, email.message.Message): - newmsg = Message() - # Copy attributes - for k, v in msg.items(): - newmsg[k] = v - # Copy payload - if msg.is_multipart(): - for part in msg.get_payload(): - newmsg.attach(part) - else: - newmsg.set_payload(msg.get_payload()) - msg = newmsg + try: + # Convert email.message.Message to Mailman.Message.Message if needed + if isinstance(msg, email.message.Message): + newmsg = Message() + # Copy attributes + for k, v in msg.items(): + newmsg[k] = v + # Copy payload + if msg.is_multipart(): + for part in msg.get_payload(): + newmsg.attach(part) + else: + newmsg.set_payload(msg.get_payload()) + msg = newmsg - recips = msgdata.get('recips') - if not recips: - # Nobody to deliver to! - return - # Calculate the non-VERP envelope sender. - envsender = msgdata.get('envsender') - if envsender is None: - if mlist: - envsender = mlist.GetBouncesEmail() + recips = msgdata.get('recips') + if not recips: + # Nobody to deliver to! + return + # Calculate the non-VERP envelope sender. + envsender = msgdata.get('envsender') + if envsender is None: + if mlist: + envsender = mlist.GetBouncesEmail() + else: + envsender = Mailman.Utils.get_site_email(extra='bounces') + # Time to split up the recipient list. If we're personalizing or VERPing + # then each chunk will have exactly one recipient. We'll then hand craft + # an envelope sender and stitch a message together in memory for each one + # separately. If we're not VERPing, then we'll chunkify based on + # SMTP_MAX_RCPTS. Note that most MTAs have a limit on the number of + # recipients they'll swallow in a single transaction. + deliveryfunc = None + if ('personalize' not in msgdata or msgdata['personalize']) and ( + msgdata.get('verp') or mlist.personalize): + chunks = [[recip] for recip in recips] + msgdata['personalize'] = 1 + deliveryfunc = verpdeliver + elif Mailman.mm_cfg.SMTP_MAX_RCPTS <= 0: + chunks = [recips] else: - envsender = Mailman.Utils.get_site_email(extra='bounces') - # Time to split up the recipient list. If we're personalizing or VERPing - # then each chunk will have exactly one recipient. We'll then hand craft - # an envelope sender and stitch a message together in memory for each one - # separately. If we're not VERPing, then we'll chunkify based on - # SMTP_MAX_RCPTS. Note that most MTAs have a limit on the number of - # recipients they'll swallow in a single transaction. - deliveryfunc = None - if ('personalize' not in msgdata or msgdata['personalize']) and ( - msgdata.get('verp') or mlist.personalize): - chunks = [[recip] for recip in recips] - msgdata['personalize'] = 1 - deliveryfunc = verpdeliver - elif Mailman.mm_cfg.SMTP_MAX_RCPTS <= 0: - chunks = [recips] - else: - chunks = chunkify(recips, Mailman.mm_cfg.SMTP_MAX_RCPTS) - # See if this is an unshunted message for which some were undelivered - if 'undelivered' in msgdata: - chunks = msgdata['undelivered'] + chunks = chunkify(recips, Mailman.mm_cfg.SMTP_MAX_RCPTS) + # See if this is an unshunted message for which some were undelivered + if 'undelivered' in msgdata: + chunks = msgdata['undelivered'] + except Exception as e: + Mailman.Logging.Syslog.mailman_log('error', 'Error in SMTPDirect.process: %s\nTraceback:\n%s', + str(e), traceback.format_exc()) + raise # If we're doing bulk delivery, then we can stitch up the message now. if deliveryfunc is None: # Be sure never to decorate the message more than once! diff --git a/Mailman/Queue/Switchboard.py b/Mailman/Queue/Switchboard.py index 75f19bd7..e3c1cb66 100644 --- a/Mailman/Queue/Switchboard.py +++ b/Mailman/Queue/Switchboard.py @@ -193,7 +193,7 @@ def dequeue(self, filebase): os.close(lock_fd) except OSError as e: if e.errno == errno.EEXIST: - mailman_log('warning', 'Lock file exists for %s, waiting...', filename) + mailman_log('warning', 'Lock file exists for %s (full path: %s), waiting...', filename, lockfile) # Wait for lock to be released for _ in range(10): # 10 attempts try: @@ -203,26 +203,26 @@ def dequeue(self, filebase): except OSError: time.sleep(0.1) else: - mailman_log('error', 'Could not acquire lock for %s after 10 attempts', filename) + mailman_log('error', 'Could not acquire lock for %s (full path: %s) after 10 attempts', filename, lockfile) return None, None else: - mailman_log('error', 'Failed to create lock file %s: %s\nTraceback:\n%s', - lockfile, str(e), traceback.format_exc()) + mailman_log('error', 'Failed to create lock file %s (full path: %s): %s\nTraceback:\n%s', + filename, lockfile, str(e), traceback.format_exc()) return None, None try: # Move the file to the backup file name for processing try: if not os.path.exists(filename): - mailman_log('error', 'Source file %s does not exist', filename) + mailman_log('error', 'Source file %s (full path: %s) does not exist', filename, os.path.join(self.__whichq, filename)) return None, None if os.path.exists(backfile): - mailman_log('warning', 'Backup file %s already exists, removing old version', backfile) + mailman_log('warning', 'Backup file %s (full path: %s) already exists, removing old version', backfile, os.path.join(self.__whichq, backfile)) os.unlink(backfile) os.rename(filename, backfile) except OSError as e: - mailman_log('error', 'Failed to rename %s to %s: %s\nTraceback:\n%s', - filename, backfile, str(e), traceback.format_exc()) + mailman_log('error', 'Failed to rename %s to %s (full paths: %s -> %s): %s\nTraceback:\n%s', + filename, backfile, os.path.join(self.__whichq, filename), os.path.join(self.__whichq, backfile), str(e), traceback.format_exc()) return None, None # Read the message object and metadata @@ -383,7 +383,8 @@ def files(self, extension='.pck'): try: # Validate file name format if '+' not in filebase: - mailman_log('warning', 'Invalid file name format in queue directory (missing +): %s', f) + full_path = os.path.join(self.__whichq, f) + mailman_log('warning', 'Invalid file name format in queue directory (missing +): %s (full path: %s)', f, full_path) # Try to recover by moving to shunt queue try: src = os.path.join(self.__whichq, f) @@ -399,7 +400,8 @@ def files(self, extension='.pck'): parts = filebase.split('+') if len(parts) != 2: - mailman_log('warning', 'Invalid file name format in queue directory (wrong number of parts): %s', f) + full_path = os.path.join(self.__whichq, f) + mailman_log('warning', 'Invalid file name format in queue directory (wrong number of parts): %s (full path: %s)', f, full_path) # Try to recover by moving to shunt queue try: src = os.path.join(self.__whichq, f) @@ -424,7 +426,8 @@ def files(self, extension='.pck'): mailman_log('error', 'Invalid digest format in queue file %s: %s', f, e) raise except ValueError as e: - mailman_log('warning', 'Invalid file name format in queue directory (invalid timestamp/digest): %s: %s', f, str(e)) + full_path = os.path.join(self.__whichq, f) + mailman_log('warning', 'Invalid file name format in queue directory (invalid timestamp/digest): %s: %s (full path: %s)', f, str(e), full_path) # Try to recover by moving to shunt queue try: src = os.path.join(self.__whichq, f) @@ -498,7 +501,7 @@ def recover_backup_files(self): tb = traceback.format_exc() mailman_log('error', 'Unpickling .bak exception: %s\n' + 'Traceback:\n%s\n' - + 'preserving file: %s', s, tb, filebase) + + 'preserving file: %s (full path: %s)', s, tb, filebase, os.path.join(self.__whichq, filebase + '.bak')) self.finish(filebase, preserve=True) continue @@ -546,7 +549,7 @@ def recover_backup_files(self): 'retry count: %d, last error: %s, ' 'message-id: %s, listname: %s, ' 'recipients: %s, error history: %s, ' - 'last traceback: %s)', + 'last traceback: %s, full path: %s)', MAX_BAK_COUNT, filebase, self.__whichq, @@ -556,20 +559,21 @@ def recover_backup_files(self): data.get('listname', 'unknown'), data.get('recips', 'unknown'), data.get('_error_history', 'unknown'), - data.get('_traceback', 'none')) + data.get('_traceback', 'none'), + os.path.join(self.__whichq, filebase + '.bak')) self.finish(filebase, preserve=True) else: try: os.rename(src, dst) except OSError as e: - mailman_log('error', 'Failed to rename backup file %s: %s\nTraceback:\n%s', - filebase, str(e), traceback.format_exc()) + mailman_log('error', 'Failed to rename backup file %s (full paths: %s -> %s): %s\nTraceback:\n%s', + filebase, os.path.join(self.__whichq, filebase + '.bak'), os.path.join(self.__whichq, filebase + '.pck'), str(e), traceback.format_exc()) self.finish(filebase, preserve=True) finally: fp.close() except Exception as e: - mailman_log('error', 'Failed to process backup file %s: %s\nTraceback:\n%s', - filebase, str(e), traceback.format_exc()) + mailman_log('error', 'Failed to process backup file %s (full path: %s): %s\nTraceback:\n%s', + filebase, os.path.join(self.__whichq, filebase + '.bak'), str(e), traceback.format_exc()) continue except Exception as e: mailman_log('error', 'Failed to recover backup files: %s\nTraceback:\n%s', @@ -602,7 +606,7 @@ def _enqueue(self, msg, metadata=None, _recips=None): os.close(lock_fd) except OSError as e: if e.errno == errno.EEXIST: - mailman_log('warning', 'Lock file exists for %s, waiting...', qfile) + mailman_log('warning', 'Lock file exists for %s (full path: %s), waiting...', qfile, lockfile) # Wait for lock to be released for _ in range(10): # 10 attempts try: @@ -612,11 +616,11 @@ def _enqueue(self, msg, metadata=None, _recips=None): except OSError: time.sleep(0.1) else: - mailman_log('error', 'Could not acquire lock for %s after 10 attempts', qfile) + mailman_log('error', 'Could not acquire lock for %s (full path: %s) after 10 attempts', qfile, lockfile) raise else: - mailman_log('error', 'Failed to create lock file %s: %s\nTraceback:\n%s', - lockfile, str(e), traceback.format_exc()) + mailman_log('error', 'Failed to create lock file %s (full path: %s): %s\nTraceback:\n%s', + qfile, lockfile, str(e), traceback.format_exc()) raise try: @@ -626,8 +630,8 @@ def _enqueue(self, msg, metadata=None, _recips=None): try: os.makedirs(dirname, 0o755) except Exception as e: - mailman_log('error', 'Failed to create directory %s: %s\nTraceback:\n%s', - dirname, str(e), traceback.format_exc()) + mailman_log('error', 'Failed to create directory %s (full path: %s): %s\nTraceback:\n%s', + dirname, os.path.abspath(dirname), str(e), traceback.format_exc()) raise # Write to temporary file first @@ -638,8 +642,8 @@ def _enqueue(self, msg, metadata=None, _recips=None): if hasattr(os, 'fsync'): os.fsync(fp.fileno()) except Exception as e: - mailman_log('error', 'Failed to write temporary file %s: %s\nTraceback:\n%s', - tmpfile, str(e), traceback.format_exc()) + mailman_log('error', 'Failed to write temporary file %s (full path: %s): %s\nTraceback:\n%s', + tmpfile, os.path.abspath(tmpfile), str(e), traceback.format_exc()) raise # Validate the temporary file @@ -655,34 +659,34 @@ def _enqueue(self, msg, metadata=None, _recips=None): try: os.unlink(tmpfile) except Exception as cleanup_e: - mailman_log('error', 'Failed to clean up temporary file %s: %s\nTraceback:\n%s', - tmpfile, str(cleanup_e), traceback.format_exc()) + mailman_log('error', 'Failed to clean up temporary file %s (full path: %s): %s\nTraceback:\n%s', + tmpfile, os.path.abspath(tmpfile), str(cleanup_e), traceback.format_exc()) raise # Atomic rename with existence check try: if os.path.exists(qfile): - mailman_log('warning', 'Target file %s already exists, removing old version', qfile) + mailman_log('warning', 'Target file %s (full path: %s) already exists, removing old version', qfile, os.path.abspath(qfile)) os.unlink(qfile) os.rename(tmpfile, qfile) except Exception as e: - mailman_log('error', 'Failed to rename %s to %s: %s\nTraceback:\n%s', - tmpfile, qfile, str(e), traceback.format_exc()) + mailman_log('error', 'Failed to rename %s to %s (full paths: %s -> %s): %s\nTraceback:\n%s', + tmpfile, qfile, os.path.abspath(tmpfile), os.path.abspath(qfile), str(e), traceback.format_exc()) # Try to clean up try: if os.path.exists(tmpfile): os.unlink(tmpfile) except Exception as cleanup_e: - mailman_log('error', 'Failed to clean up temporary file %s: %s\nTraceback:\n%s', - tmpfile, str(cleanup_e), traceback.format_exc()) + mailman_log('error', 'Failed to clean up temporary file %s (full path: %s): %s\nTraceback:\n%s', + tmpfile, os.path.abspath(tmpfile), str(cleanup_e), traceback.format_exc()) raise # Set proper permissions try: os.chmod(qfile, 0o660) except Exception as e: - mailman_log('warning', 'Failed to set permissions on %s: %s\nTraceback:\n%s', - qfile, str(e), traceback.format_exc()) + mailman_log('warning', 'Failed to set permissions on %s (full path: %s): %s\nTraceback:\n%s', + qfile, os.path.abspath(qfile), str(e), traceback.format_exc()) # Not critical, continue finally: From b05306f65bf3bfc2232f5983bc27143cb23cc713 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 2 May 2025 14:03:46 -0400 Subject: [PATCH 324/748] errors update --- Mailman/Queue/IncomingRunner.py | 4 +++- Mailman/Queue/Runner.py | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Mailman/Queue/IncomingRunner.py b/Mailman/Queue/IncomingRunner.py index 8bfb0fd4..c739da71 100644 --- a/Mailman/Queue/IncomingRunner.py +++ b/Mailman/Queue/IncomingRunner.py @@ -140,7 +140,9 @@ def _dispose(self, listname, msg, msgdata): # Track message ID to prevent duplicates msgid = msg.get('message-id', 'n/a') if msgid in self._processed_messages: - mailman_log('error', 'Duplicate message detected: %s', msgid) + mailman_log('error', 'Duplicate message detected: %s (file: %s)', msgid, msgdata.get('_filebase', 'unknown')) + # Move to shunt queue + self._shunt.enqueue(msg, msgdata) return 0 # Clean up old message IDs periodically diff --git a/Mailman/Queue/Runner.py b/Mailman/Queue/Runner.py index a023dd82..ce1f2d5e 100644 --- a/Mailman/Queue/Runner.py +++ b/Mailman/Queue/Runner.py @@ -259,7 +259,8 @@ def _onefile(self, msg, msgdata): # Check for duplicate messages early msgid = msg.get('message-id', 'n/a') if hasattr(self, '_processed_messages') and msgid in self._processed_messages: - log('error', 'Duplicate message detected early: %s', msgid) + log('error', 'Duplicate message detected early: %s (file: %s)', msgid, msgdata.get('_filebase', 'unknown')) + self._shunt.enqueue(msg, msgdata) return sender = msg.get_sender() From 6e55d57e1e2f611588201fefecdf7ffb7d06163b Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 2 May 2025 14:05:31 -0400 Subject: [PATCH 325/748] errors update --- Mailman/Queue/IncomingRunner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mailman/Queue/IncomingRunner.py b/Mailman/Queue/IncomingRunner.py index c739da71..4f08ec15 100644 --- a/Mailman/Queue/IncomingRunner.py +++ b/Mailman/Queue/IncomingRunner.py @@ -181,7 +181,7 @@ def _dispose(self, listname, msg, msgdata): mlist.Save() return more except Exception as e: - mailman_log('error', 'Error processing message for %s: %s', listname, str(e)) + mailman_log('error', 'Error processing message for %s: %s\nTraceback:\n%s', listname, str(e), traceback.format_exc()) return 1 finally: try: From 612bdebb995571dc87530b3b8e53fd77d77e6ec6 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 2 May 2025 14:09:41 -0400 Subject: [PATCH 326/748] errors update --- Mailman/Handlers/Moderate.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Mailman/Handlers/Moderate.py b/Mailman/Handlers/Moderate.py index d4f4392b..7edcc291 100644 --- a/Mailman/Handlers/Moderate.py +++ b/Mailman/Handlers/Moderate.py @@ -25,11 +25,12 @@ from Mailman import mm_cfg from Mailman import Utils -from Mailman.Message import Message from Mailman import Errors -from Mailman.i18n import _ -from Mailman.Handlers import Hold +from Mailman import Hold +from Mailman import i18n +from Mailman.Message import Message from Mailman.Logging.Syslog import syslog +from Mailman.Logging.Syslog import mailman_log # Remove the MailList import from here since it's causing a circular dependency # from Mailman.MailList import MailList From 7bff65efb8f831e279981825cabc6b06fdace20c Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 2 May 2025 14:10:47 -0400 Subject: [PATCH 327/748] errors update --- Mailman/Handlers/Moderate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mailman/Handlers/Moderate.py b/Mailman/Handlers/Moderate.py index 7edcc291..b69b31ba 100644 --- a/Mailman/Handlers/Moderate.py +++ b/Mailman/Handlers/Moderate.py @@ -26,7 +26,7 @@ from Mailman import mm_cfg from Mailman import Utils from Mailman import Errors -from Mailman import Hold +from Mailman.Handlers import Hold from Mailman import i18n from Mailman.Message import Message from Mailman.Logging.Syslog import syslog From fc0ca55a099365a203c5cc5a7175ab15f10c6318 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 2 May 2025 14:12:42 -0400 Subject: [PATCH 328/748] errors update --- Mailman/Handlers/Moderate.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Mailman/Handlers/Moderate.py b/Mailman/Handlers/Moderate.py index b69b31ba..0fbf63aa 100644 --- a/Mailman/Handlers/Moderate.py +++ b/Mailman/Handlers/Moderate.py @@ -28,6 +28,7 @@ from Mailman import Errors from Mailman.Handlers import Hold from Mailman import i18n +from Mailman.i18n import _ from Mailman.Message import Message from Mailman.Logging.Syslog import syslog from Mailman.Logging.Syslog import mailman_log From 4f8a8f579867224ed4b37682fec8189230ad4091 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 2 May 2025 14:15:07 -0400 Subject: [PATCH 329/748] errors update --- Mailman/Bouncer.py | 1 + Mailman/Handlers/Moderate.py | 1 + 2 files changed, 2 insertions(+) diff --git a/Mailman/Bouncer.py b/Mailman/Bouncer.py index 72fddd46..9eb10329 100644 --- a/Mailman/Bouncer.py +++ b/Mailman/Bouncer.py @@ -30,6 +30,7 @@ from email.mime.text import MIMEText from email.mime.message import MIMEMessage +import Mailman from Mailman import mm_cfg from Mailman import Utils from Mailman import Errors diff --git a/Mailman/Handlers/Moderate.py b/Mailman/Handlers/Moderate.py index 0fbf63aa..5e6fd06f 100644 --- a/Mailman/Handlers/Moderate.py +++ b/Mailman/Handlers/Moderate.py @@ -23,6 +23,7 @@ from email.mime.text import MIMEText from email.utils import parseaddr +import Mailman from Mailman import mm_cfg from Mailman import Utils from Mailman import Errors From cf482663ce49ebbc8ccdf1335d30e16308877ce0 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 2 May 2025 14:22:26 -0400 Subject: [PATCH 330/748] smtp fix --- Mailman/Handlers/SMTPDirect.py | 3 +++ Mailman/Queue/IncomingRunner.py | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Mailman/Handlers/SMTPDirect.py b/Mailman/Handlers/SMTPDirect.py index 0ca370c5..6a5eb2db 100644 --- a/Mailman/Handlers/SMTPDirect.py +++ b/Mailman/Handlers/SMTPDirect.py @@ -116,6 +116,9 @@ def sendmail(self, envsender, recips, msgtext): if self.__conn is None: self.__connect() try: + # Convert message to string if it's a Message object + if isinstance(msgtext, Message): + msgtext = msgtext.as_string() # Ensure msgtext is properly encoded as UTF-8 if isinstance(msgtext, str): msgtext = msgtext.encode('utf-8') diff --git a/Mailman/Queue/IncomingRunner.py b/Mailman/Queue/IncomingRunner.py index 4f08ec15..2bb826a4 100644 --- a/Mailman/Queue/IncomingRunner.py +++ b/Mailman/Queue/IncomingRunner.py @@ -141,8 +141,12 @@ def _dispose(self, listname, msg, msgdata): msgid = msg.get('message-id', 'n/a') if msgid in self._processed_messages: mailman_log('error', 'Duplicate message detected: %s (file: %s)', msgid, msgdata.get('_filebase', 'unknown')) - # Move to shunt queue + # Move to shunt queue and remove from original queue self._shunt.enqueue(msg, msgdata) + # Get the filebase from msgdata and finish processing it + filebase = msgdata.get('_filebase') + if filebase: + self._switchboard.finish(filebase) return 0 # Clean up old message IDs periodically From 3c24485825a51866d93bd5b21cef8a4014c47362 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 2 May 2025 14:28:48 -0400 Subject: [PATCH 331/748] smtp fix --- Mailman/Queue/IncomingRunner.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/Mailman/Queue/IncomingRunner.py b/Mailman/Queue/IncomingRunner.py index 2bb826a4..5fb07ec0 100644 --- a/Mailman/Queue/IncomingRunner.py +++ b/Mailman/Queue/IncomingRunner.py @@ -331,7 +331,12 @@ def _oneloop(self): # Ask the switchboard for the message and metadata objects # associated with this filebase. - msg, msgdata = self._switchboard.dequeue(filebase) + try: + msg, msgdata = self._switchboard.dequeue(filebase) + except Exception as e: + mailman_log('error', 'Failed to dequeue file %s: %s', filebase, str(e)) + continue + # Process the message more = self._dispose(msgdata['listname'], msg, msgdata) if more: @@ -345,8 +350,9 @@ def _oneloop(self): except Exception as e: # Log the error and requeue the message for later processing mailman_log('error', 'Error processing queue file %s: %s', filebase, str(e)) - try: - self._switchboard.enqueue(msg, msgdata) - except: - pass + if msg is not None and msgdata is not None: + try: + self._switchboard.enqueue(msg, msgdata) + except Exception as e2: + mailman_log('error', 'Failed to requeue file %s: %s', filebase, str(e2)) return len(files) From 8e972f85ec3a236b6939e93c204c45abc9ebd507 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 2 May 2025 14:37:55 -0400 Subject: [PATCH 332/748] web logging --- Mailman/Cgi/admindb.py | 288 ++++++++++++++++++++++++----------------- 1 file changed, 166 insertions(+), 122 deletions(-) diff --git a/Mailman/Cgi/admindb.py b/Mailman/Cgi/admindb.py index 06559869..4e9e9937 100644 --- a/Mailman/Cgi/admindb.py +++ b/Mailman/Cgi/admindb.py @@ -149,14 +149,24 @@ def main(): cgidata = urllib.parse.parse_qs(query_string, keep_blank_values=True) except Exception as e: mailman_log('error', 'admindb: Invalid form data: %s\n%s', str(e), traceback.format_exc()) - doc = output_error_page('400 Bad Request', 'Error', 'Invalid options to CGI script.') - return output_success_page(doc) + try: + doc = output_error_page('400 Bad Request', 'Error', 'Invalid options to CGI script.') + return output_success_page(doc) + except Exception as output_error: + mailman_log('error', 'admindb: Failed to output error page: %s\n%s', + str(output_error), traceback.format_exc()) + raise # Get the list name parts = Utils.GetPathPieces() if not parts: - doc = handle_no_list() - return output_success_page(doc) + try: + doc = handle_no_list() + return output_success_page(doc) + except Exception as e: + mailman_log('error', 'admindb: Failed to handle no list case: %s\n%s', + str(e), traceback.format_exc()) + raise listname = parts[0].lower() mailman_log('info', 'admindb: Processing list "%s"', listname) @@ -165,34 +175,54 @@ def main(): listdir = os.path.join(mm_cfg.LIST_DATA_DIR, listname) if not os.path.exists(listdir): mailman_log('error', 'admindb: List directory does not exist: %s', listdir) - doc = output_error_page('404 Not Found', 'Error', - 'No such list %s' % Utils.websafe(listname), - 'The list directory does not exist.') - return output_success_page(doc) + try: + doc = output_error_page('404 Not Found', 'Error', + 'No such list %s' % Utils.websafe(listname), + 'The list directory does not exist.') + return output_success_page(doc) + except Exception as e: + mailman_log('error', 'admindb: Failed to output list not found error: %s\n%s', + str(e), traceback.format_exc()) + raise try: mlist = MailList.MailList(listname, lock=0) except Errors.MMListError as e: mailman_log('error', 'admindb: No such list "%s": %s\n%s', listname, e, traceback.format_exc()) - doc = output_error_page('404 Not Found', 'Error', - 'No such list %s' % Utils.websafe(listname), - 'The list configuration could not be loaded.') - return output_success_page(doc) + try: + doc = output_error_page('404 Not Found', 'Error', + 'No such list %s' % Utils.websafe(listname), + 'The list configuration could not be loaded.') + return output_success_page(doc) + except Exception as output_error: + mailman_log('error', 'admindb: Failed to output list error page: %s\n%s', + str(output_error), traceback.format_exc()) + raise except PermissionError as e: mailman_log('error', 'admindb: Permission error accessing list "%s": %s\n%s', listname, e, traceback.format_exc()) - doc = output_error_page('500 Internal Server Error', 'Error', - 'Permission error accessing list %s' % Utils.websafe(listname), - str(e)) - return output_success_page(doc) + try: + doc = output_error_page('500 Internal Server Error', 'Error', + 'Permission error accessing list %s' % Utils.websafe(listname), + str(e)) + return output_success_page(doc) + except Exception as output_error: + mailman_log('error', 'admindb: Failed to output permission error page: %s\n%s', + str(output_error), traceback.format_exc()) + raise except Exception as e: mailman_log('error', 'admindb: Unexpected error loading list "%s": %s\n%s', listname, str(e), traceback.format_exc()) - doc = output_error_page('500 Internal Server Error', 'Error', - 'Error accessing list %s' % Utils.websafe(listname), - str(e)) - return output_success_page(doc) + try: + doc = output_error_page('500 Internal Server Error', 'Error', + 'Error accessing list %s' % Utils.websafe(listname), + str(e)) + return output_success_page(doc) + except Exception as output_error: + mailman_log('error', 'admindb: Failed to output unexpected error page: %s\n%s', + str(output_error), traceback.format_exc()) + raise # Now that we know what list has been requested, all subsequent admin # pages are shown in that list's preferred language. @@ -212,19 +242,31 @@ def main(): listname, remote, traceback.format_exc()) else: msg = '' - Auth.loginpage(mlist, 'admindb', msg=msg) + try: + Auth.loginpage(mlist, 'admindb', msg=msg) + except Exception as e: + mailman_log('error', 'admindb: Failed to display login page: %s\n%s', + str(e), traceback.format_exc()) + raise return # We need a signal handler to catch the SIGTERM that can come from Apache # when the user hits the browser's STOP button. See the comment in # admin.py for details. def sigterm_handler(signum, frame, mlist=mlist): - # Make sure the list gets unlocked... - 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) + try: + # Make sure the list gets unlocked... + mlist.Unlock() + # Log the termination + mailman_log('info', 'admindb: SIGTERM received, unlocking list and exiting') + except Exception as e: + mailman_log('error', 'admindb: Error in SIGTERM handler: %s\n%s', + str(e), traceback.format_exc()) + finally: + # ...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) mlist.Lock() try: @@ -253,12 +295,9 @@ def sigterm_handler(signum, frame, mlist=mlist): finally: mlist.Unlock() except Exception as e: - mailman_log('error', 'admindb: Unexpected error: %s\n%s', + mailman_log('error', 'admindb: Unhandled exception in main(): %s\n%s', str(e), traceback.format_exc()) - doc = output_error_page('500 Internal Server Error', 'Error', - 'An unexpected error occurred', - str(e)) - return output_success_page(doc) + raise def handle_no_list(msg=''): @@ -734,99 +773,104 @@ def show_post_requests(mlist, id, info, total, count, form): def process_form(mlist, doc, cgidata): - # Get the sender and message id from the query string - envar = os.environ.get('QUERY_STRING', '') - qs = urllib.parse.parse_qs(envar) - sender = qs.get('sender', [''])[0] - msgid = qs.get('msgid', [''])[0] - details = qs.get('details', [''])[0] - - # Check if there are any pending requests - if not mlist.NumRequestsPending(): - title = _(f'{mlist.real_name} Administrative Database') - doc.SetTitle(title) - doc.AddItem(Header(2, title)) - doc.AddItem(_('There are no pending requests.')) - doc.AddItem(' ') - admindburl = mlist.GetScriptURL('admindb', absolute=1) - doc.AddItem(Link(admindburl, _('Click here to reload this page.'))) - # Put 'Logout' link before the footer - doc.AddItem('\n
                  ') - doc.AddItem(Link('%s/logout' % admindburl, - '%s' % _('Logout'))) - doc.AddItem('
                  \n') - doc.AddItem(mlist.GetMailmanFooter()) - # Output the success page with proper headers - return output_success_page(doc) + try: + # Get the sender and message id from the query string + envar = os.environ.get('QUERY_STRING', '') + qs = urllib.parse.parse_qs(envar) + sender = qs.get('sender', [''])[0] + msgid = qs.get('msgid', [''])[0] + details = qs.get('details', [''])[0] + + # Check if there are any pending requests + if not mlist.NumRequestsPending(): + title = _(f'{mlist.real_name} Administrative Database') + doc.SetTitle(title) + doc.AddItem(Header(2, title)) + doc.AddItem(_('There are no pending requests.')) + doc.AddItem(' ') + admindburl = mlist.GetScriptURL('admindb', absolute=1) + doc.AddItem(Link(admindburl, _('Click here to reload this page.'))) + # Put 'Logout' link before the footer + doc.AddItem('\n
                  ') + doc.AddItem(Link('%s/logout' % admindburl, + '%s' % _('Logout'))) + doc.AddItem('
                  \n') + doc.AddItem(mlist.GetMailmanFooter()) + # Output the success page with proper headers + return output_success_page(doc) - # Create a form for the overview - form = Form(mlist.GetScriptURL('admindb', absolute=1), mlist=mlist, contexts=AUTH_CONTEXTS) - form.AddItem(Center(SubmitButton('submit', _('Submit All Data')))) - - # Get the action from the form data - action = cgidata.get('action', [''])[0] - if not action: - # No action specified, show the overview - show_pending_subs(mlist, form) - show_pending_unsubs(mlist, form) - show_helds_overview(mlist, form) - doc.AddItem(form) - return output_success_page(doc) + # Create a form for the overview + form = Form(mlist.GetScriptURL('admindb', absolute=1), mlist=mlist, contexts=AUTH_CONTEXTS) + form.AddItem(Center(SubmitButton('submit', _('Submit All Data')))) + + # Get the action from the form data + action = cgidata.get('action', [''])[0] + if not action: + # No action specified, show the overview + show_pending_subs(mlist, form) + show_pending_unsubs(mlist, form) + show_helds_overview(mlist, form) + doc.AddItem(form) + return output_success_page(doc) - # Process the action - if action == 'approve': - if sender: - show_sender_requests(mlist, form, sender) - elif msgid: - show_message_requests(mlist, form, msgid) - else: - show_detailed_requests(mlist, form) - elif action == 'reject': - if sender: - show_sender_requests(mlist, form, sender) - elif msgid: - show_message_requests(mlist, form, msgid) - else: - show_detailed_requests(mlist, form) - elif action == 'defer': - if sender: - show_sender_requests(mlist, form, sender) - elif msgid: - show_message_requests(mlist, form, msgid) - else: - show_detailed_requests(mlist, form) - elif action == 'discard': - if sender: - show_sender_requests(mlist, form, sender) - elif msgid: - show_message_requests(mlist, form, msgid) - else: - show_detailed_requests(mlist, form) - elif action == 'hold': - if sender: - show_sender_requests(mlist, form, sender) - elif msgid: - show_message_requests(mlist, form, msgid) - else: - show_detailed_requests(mlist, form) - elif action == 'post': - if msgid: - info = mlist.GetRecord(msgid) - if info: - total = len(mlist.GetHeldMessageIds()) - count = 1 - show_post_requests(mlist, msgid, info, total, count, form) + # Process the action + if action == 'approve': + if sender: + show_sender_requests(mlist, form, sender) + elif msgid: + show_message_requests(mlist, form, msgid) + else: + show_detailed_requests(mlist, form) + elif action == 'reject': + if sender: + show_sender_requests(mlist, form, sender) + elif msgid: + show_message_requests(mlist, form, msgid) + else: + show_detailed_requests(mlist, form) + elif action == 'defer': + if sender: + show_sender_requests(mlist, form, sender) + elif msgid: + show_message_requests(mlist, form, msgid) + else: + show_detailed_requests(mlist, form) + elif action == 'discard': + if sender: + show_sender_requests(mlist, form, sender) + elif msgid: + show_message_requests(mlist, form, msgid) + else: + show_detailed_requests(mlist, form) + elif action == 'hold': + if sender: + show_sender_requests(mlist, form, sender) + elif msgid: + show_message_requests(mlist, form, msgid) + else: + show_detailed_requests(mlist, form) + elif action == 'post': + if msgid: + info = mlist.GetRecord(msgid) + if info: + total = len(mlist.GetHeldMessageIds()) + count = 1 + show_post_requests(mlist, msgid, info, total, count, form) + else: + show_detailed_requests(mlist, form) else: - show_detailed_requests(mlist, form) - else: - # Unknown action, show the overview - show_pending_subs(mlist, form) - show_pending_unsubs(mlist, form) - show_helds_overview(mlist, form) - - # Add the form to the document and output - doc.AddItem(form) - return output_success_page(doc) + # Unknown action, show the overview + show_pending_subs(mlist, form) + show_pending_unsubs(mlist, form) + show_helds_overview(mlist, form) + + # Add the form to the document and output + doc.AddItem(form) + return output_success_page(doc) + except Exception as e: + mailman_log('error', 'admindb: Error in process_form: %s\n%s', + str(e), traceback.format_exc()) + raise def format_body(body, mcset, lcset): From f9f2151d25b042561704b9b85ee2a6604014510c Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 2 May 2025 14:41:18 -0400 Subject: [PATCH 333/748] web logging --- Mailman/Cgi/admindb.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/Mailman/Cgi/admindb.py b/Mailman/Cgi/admindb.py index 4e9e9937..820787e4 100644 --- a/Mailman/Cgi/admindb.py +++ b/Mailman/Cgi/admindb.py @@ -118,8 +118,6 @@ def output_error_page(status, title, message, details=None): def output_success_page(doc): - print('Status: 200 OK') - print('Content-type: text/html; charset=utf-8\n') print(doc.Format()) return From eef84777d3a7a63ff15593c36eb5a2bfaa479c94 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 2 May 2025 14:45:12 -0400 Subject: [PATCH 334/748] web logging --- Mailman/Queue/IncomingRunner.py | 70 ++++++++++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 2 deletions(-) diff --git a/Mailman/Queue/IncomingRunner.py b/Mailman/Queue/IncomingRunner.py index 5fb07ec0..262e810f 100644 --- a/Mailman/Queue/IncomingRunner.py +++ b/Mailman/Queue/IncomingRunner.py @@ -107,14 +107,17 @@ import os from email import message_from_string from Mailman.Message import Message +from urllib.parse import parse_qs +from Mailman.Utils import reap +from Mailman import Utils +from Mailman.Utils import Document, Header, Bold from Mailman import mm_cfg from Mailman import Errors from Mailman import LockFile from Mailman.Queue.Runner import Runner from Mailman.Logging.Syslog import mailman_log -from Mailman.Utils import reap -from Mailman import Utils +from Mailman import Auth class PipelineError(Exception): @@ -356,3 +359,66 @@ def _oneloop(self): except Exception as e2: mailman_log('error', 'Failed to requeue file %s: %s', filebase, str(e2)) return len(files) + + def output_success_page(self, doc): + print(doc.Format()) + return + + def handle_error(self, status, message, details=None): + doc = Document() + doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + doc.AddItem(Header(2, _("Error"))) + doc.AddItem(Bold(_(message))) + if details: + doc.AddItem(Preformatted(Utils.websafe(str(details)))) + print(f'Status: {status}') + print(doc.Format()) + return + + def handle_form_data(self): + try: + if os.environ.get('REQUEST_METHOD', '').lower() == 'post': + content_type = os.environ.get('CONTENT_TYPE', '') + if content_type.startswith('application/x-www-form-urlencoded'): + content_length = int(os.environ.get('CONTENT_LENGTH', 0)) + form_data = sys.stdin.buffer.read(content_length).decode('latin-1') + cgidata = parse_qs(form_data, keep_blank_values=1) + else: + raise ValueError('Invalid content type') + else: + cgidata = parse_qs(os.environ.get('QUERY_STRING', ''), keep_blank_values=1) + except Exception: + self.handle_error(400, _('Invalid options to CGI script.')) + return + + # Add proper CSRF protection + safe_params = ['adminpw', 'admlogin', 'msgid', 'sender', 'details'] + params = list(cgidata.keys()) + if set(params) - set(safe_params): + csrf_checked = csrf_check(mlist, cgidata.get('csrf_token', [''])[0], + 'admindb') + else: + csrf_checked = True + # if password is present, void cookie to force password authentication. + if cgidata.get('adminpw', [''])[0]: + os.environ['HTTP_COOKIE'] = '' + csrf_checked = True + + # Add proper authentication flow + if not mlist.WebAuthenticate(AUTH_CONTEXTS, cgidata.get('adminpw', [''])[0]): + if 'adminpw' in cgidata: + msg = Bold(FontSize('+1', _('Authorization failed.'))).Format() + remote = os.environ.get('HTTP_FORWARDED_FOR', + os.environ.get('HTTP_X_FORWARDED_FOR', + os.environ.get('REMOTE_ADDR', + 'unidentified origin'))) + syslog('security', + 'Authorization failed (admindb): list=%s: remote=%s', + listname, remote) + else: + msg = '' + Auth.loginpage(mlist, 'admindb', msg=msg) + return + + # Process the form data + # ... (rest of the method content remains unchanged) From 7f9e8a664ac3b19ba05f85cf42ef09220336cece Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 2 May 2025 14:46:51 -0400 Subject: [PATCH 335/748] outging runner --- Mailman/Queue/OutgoingRunner.py | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/Mailman/Queue/OutgoingRunner.py b/Mailman/Queue/OutgoingRunner.py index 4ac91f73..b2200465 100644 --- a/Mailman/Queue/OutgoingRunner.py +++ b/Mailman/Queue/OutgoingRunner.py @@ -87,18 +87,36 @@ def __init__(self, slice=None, numslices=1): def _dispose(self, mlist, msg, msgdata): """Process an outgoing message.""" try: + # Log message details for debugging + msgid = msg.get('message-id', 'n/a') + mailman_log('debug', 'OutgoingRunner: Processing message %s for list %s', msgid, mlist.internal_name()) + # Validate message type first msg, success = self._validate_message(msg, msgdata) if not success: - mailman_log('error', 'Message validation failed for outgoing message') + mailman_log('error', 'Message validation failed for outgoing message %s', msgid) return False + # Log message type and content for debugging + mailman_log('debug', 'OutgoingRunner: Message type: %s, Content type: %s', + type(msg).__name__, msg.get_content_type()) + # Process the message through the delivery module self._func(mlist, msg, msgdata) return True except Exception as e: - mailman_log('error', 'Error processing outgoing message for list %s: %s', - mlist.internal_name(), str(e)) + # Enhanced error logging with full context + msgid = msg.get('message-id', 'n/a') + mailman_log('error', 'Error processing outgoing message for list %s: %s\n' + 'Message ID: %s\n' + 'Message type: %s\n' + 'Content type: %s\n' + 'Traceback:\n%s', + mlist.internal_name(), str(e), + msgid, + type(msg).__name__, + msg.get_content_type(), + traceback.format_exc()) return False def _queue_bounces(self, mlist, msg, msgdata, failures): From 301ab3b490ba08fefb581dffedc41e263ef6a6fe Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 2 May 2025 14:49:45 -0400 Subject: [PATCH 336/748] outging runner --- Mailman/Queue/OutgoingRunner.py | 33 ++++++++++++--------------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/Mailman/Queue/OutgoingRunner.py b/Mailman/Queue/OutgoingRunner.py index b2200465..8672d839 100644 --- a/Mailman/Queue/OutgoingRunner.py +++ b/Mailman/Queue/OutgoingRunner.py @@ -87,36 +87,27 @@ def __init__(self, slice=None, numslices=1): def _dispose(self, mlist, msg, msgdata): """Process an outgoing message.""" try: - # Log message details for debugging - msgid = msg.get('message-id', 'n/a') - mailman_log('debug', 'OutgoingRunner: Processing message %s for list %s', msgid, mlist.internal_name()) - # Validate message type first msg, success = self._validate_message(msg, msgdata) if not success: - mailman_log('error', 'Message validation failed for outgoing message %s', msgid) + mailman_log('error', 'Message validation failed for outgoing message') return False - # Log message type and content for debugging - mailman_log('debug', 'OutgoingRunner: Message type: %s, Content type: %s', - type(msg).__name__, msg.get_content_type()) - # Process the message through the delivery module self._func(mlist, msg, msgdata) return True except Exception as e: - # Enhanced error logging with full context - msgid = msg.get('message-id', 'n/a') - mailman_log('error', 'Error processing outgoing message for list %s: %s\n' - 'Message ID: %s\n' - 'Message type: %s\n' - 'Content type: %s\n' - 'Traceback:\n%s', - mlist.internal_name(), str(e), - msgid, - type(msg).__name__, - msg.get_content_type(), - traceback.format_exc()) + # Enhanced error logging with more context + mailman_log('error', 'Error processing outgoing message for list %s: %s', + mlist.internal_name(), str(e)) + mailman_log('error', 'Message details:') + mailman_log('error', ' Message ID: %s', msg.get('message-id', 'n/a')) + mailman_log('error', ' From: %s', msg.get('from', 'unknown')) + mailman_log('error', ' To: %s', msg.get('to', 'unknown')) + mailman_log('error', ' Subject: %s', msg.get('subject', '(no subject)')) + mailman_log('error', ' Message type: %s', type(msg).__name__) + mailman_log('error', ' Message data: %s', str(msgdata)) + mailman_log('error', 'Traceback:\n%s', traceback.format_exc()) return False def _queue_bounces(self, mlist, msg, msgdata, failures): From e25395bd126820a2734ace6be3fc13541a1b42e9 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 2 May 2025 14:52:16 -0400 Subject: [PATCH 337/748] wtf --- Mailman/Queue/IncomingRunner.py | 63 --------------------------------- 1 file changed, 63 deletions(-) diff --git a/Mailman/Queue/IncomingRunner.py b/Mailman/Queue/IncomingRunner.py index 262e810f..73ec81a2 100644 --- a/Mailman/Queue/IncomingRunner.py +++ b/Mailman/Queue/IncomingRunner.py @@ -110,7 +110,6 @@ from urllib.parse import parse_qs from Mailman.Utils import reap from Mailman import Utils -from Mailman.Utils import Document, Header, Bold from Mailman import mm_cfg from Mailman import Errors @@ -360,65 +359,3 @@ def _oneloop(self): mailman_log('error', 'Failed to requeue file %s: %s', filebase, str(e2)) return len(files) - def output_success_page(self, doc): - print(doc.Format()) - return - - def handle_error(self, status, message, details=None): - doc = Document() - doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) - doc.AddItem(Header(2, _("Error"))) - doc.AddItem(Bold(_(message))) - if details: - doc.AddItem(Preformatted(Utils.websafe(str(details)))) - print(f'Status: {status}') - print(doc.Format()) - return - - def handle_form_data(self): - try: - if os.environ.get('REQUEST_METHOD', '').lower() == 'post': - content_type = os.environ.get('CONTENT_TYPE', '') - if content_type.startswith('application/x-www-form-urlencoded'): - content_length = int(os.environ.get('CONTENT_LENGTH', 0)) - form_data = sys.stdin.buffer.read(content_length).decode('latin-1') - cgidata = parse_qs(form_data, keep_blank_values=1) - else: - raise ValueError('Invalid content type') - else: - cgidata = parse_qs(os.environ.get('QUERY_STRING', ''), keep_blank_values=1) - except Exception: - self.handle_error(400, _('Invalid options to CGI script.')) - return - - # Add proper CSRF protection - safe_params = ['adminpw', 'admlogin', 'msgid', 'sender', 'details'] - params = list(cgidata.keys()) - if set(params) - set(safe_params): - csrf_checked = csrf_check(mlist, cgidata.get('csrf_token', [''])[0], - 'admindb') - else: - csrf_checked = True - # if password is present, void cookie to force password authentication. - if cgidata.get('adminpw', [''])[0]: - os.environ['HTTP_COOKIE'] = '' - csrf_checked = True - - # Add proper authentication flow - if not mlist.WebAuthenticate(AUTH_CONTEXTS, cgidata.get('adminpw', [''])[0]): - if 'adminpw' in cgidata: - msg = Bold(FontSize('+1', _('Authorization failed.'))).Format() - remote = os.environ.get('HTTP_FORWARDED_FOR', - os.environ.get('HTTP_X_FORWARDED_FOR', - os.environ.get('REMOTE_ADDR', - 'unidentified origin'))) - syslog('security', - 'Authorization failed (admindb): list=%s: remote=%s', - listname, remote) - else: - msg = '' - Auth.loginpage(mlist, 'admindb', msg=msg) - return - - # Process the form data - # ... (rest of the method content remains unchanged) From 50291f6ab73ec786d78783e15ef56c8fec9aeac2 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 2 May 2025 14:53:23 -0400 Subject: [PATCH 338/748] wtf --- Mailman/Queue/IncomingRunner.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Mailman/Queue/IncomingRunner.py b/Mailman/Queue/IncomingRunner.py index 73ec81a2..6c9aaa45 100644 --- a/Mailman/Queue/IncomingRunner.py +++ b/Mailman/Queue/IncomingRunner.py @@ -116,7 +116,6 @@ from Mailman import LockFile from Mailman.Queue.Runner import Runner from Mailman.Logging.Syslog import mailman_log -from Mailman import Auth class PipelineError(Exception): From d877a5f46d7b609238522f795354175c6c2e2640 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 2 May 2025 14:59:30 -0400 Subject: [PATCH 339/748] decorate --- Mailman/Handlers/Decorate.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Mailman/Handlers/Decorate.py b/Mailman/Handlers/Decorate.py index 328c79d6..591b8bf9 100644 --- a/Mailman/Handlers/Decorate.py +++ b/Mailman/Handlers/Decorate.py @@ -204,6 +204,12 @@ def process(mlist, msg, msgdata): def decorate(mlist, template, what, extradict=None): # `what' is just a descriptive phrase used in the log message + # If template is a Message object, get its content + if isinstance(template, Message): + template = template.get_payload(decode=True) + if isinstance(template, bytes): + template = template.decode('utf-8', 'replace') + # If template is only whitespace, ignore it. if len(re.sub(r'\s', '', template)) == 0: return '' From 4d5ebc624abd92930bf12c4e29b79096ee73abfd Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 2 May 2025 15:01:50 -0400 Subject: [PATCH 340/748] smtpdirect --- Mailman/Handlers/SMTPDirect.py | 54 +++++++++++++++++----------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/Mailman/Handlers/SMTPDirect.py b/Mailman/Handlers/SMTPDirect.py index 6a5eb2db..246d2ddf 100644 --- a/Mailman/Handlers/SMTPDirect.py +++ b/Mailman/Handlers/SMTPDirect.py @@ -45,7 +45,7 @@ import Mailman.Errors from Mailman.Message import Message from Mailman.Handlers.Decorate import decorate -import Mailman.Logging.Syslog +from Mailman.Logging.Syslog import mailman_log import Mailman.SafeDict import email @@ -71,41 +71,41 @@ def __connect(self): if not helo_host or helo_host.startswith('.'): # If we still don't have a valid hostname, use localhost helo_host = 'localhost' - Mailman.Logging.Syslog.mailman_log('smtp', 'Connecting to SMTP server %s:%s with HELO %s', + mailman_log('smtp', 'Connecting to SMTP server %s:%s with HELO %s', Mailman.mm_cfg.SMTPHOST, Mailman.mm_cfg.SMTPPORT, helo_host) self.__conn.connect(Mailman.mm_cfg.SMTPHOST, Mailman.mm_cfg.SMTPPORT) # Set the hostname for TLS self.__conn._host = helo_host if Mailman.mm_cfg.SMTP_AUTH: if Mailman.mm_cfg.SMTP_USE_TLS: - Mailman.Logging.Syslog.mailman_log('smtp', 'Using TLS with hostname: %s', helo_host) + mailman_log('smtp', 'Using TLS with hostname: %s', helo_host) try: # Use native TLS support self.__conn.starttls() except SMTPException as e: - Mailman.Logging.Syslog.mailman_log('smtp-failure', 'SMTP TLS error: %s\nTraceback:\n%s', + mailman_log('smtp-failure', 'SMTP TLS error: %s\nTraceback:\n%s', str(e), traceback.format_exc()) self.quit() raise try: self.__conn.login(Mailman.mm_cfg.SMTP_USER, Mailman.mm_cfg.SMTP_PASSWD) except smtplib.SMTPHeloError as e: - Mailman.Logging.Syslog.mailman_log('smtp-failure', 'SMTP HELO error: %s\nTraceback:\n%s', + mailman_log('smtp-failure', 'SMTP HELO error: %s\nTraceback:\n%s', str(e), traceback.format_exc()) self.quit() raise except smtplib.SMTPAuthenticationError as e: - Mailman.Logging.Syslog.mailman_log('smtp-failure', 'SMTP AUTH error: %s\nTraceback:\n%s', + mailman_log('smtp-failure', 'SMTP AUTH error: %s\nTraceback:\n%s', str(e), traceback.format_exc()) self.quit() except smtplib.SMTPException as e: - Mailman.Logging.Syslog.mailman_log('smtp-failure', + mailman_log('smtp-failure', 'SMTP - no suitable authentication method found: %s\nTraceback:\n%s', str(e), traceback.format_exc()) self.quit() raise except (socket.error, smtplib.SMTPException) as e: - Mailman.Logging.Syslog.mailman_log('smtp-failure', 'SMTP connection error: %s\nTraceback:\n%s', + mailman_log('smtp-failure', 'SMTP connection error: %s\nTraceback:\n%s', str(e), traceback.format_exc()) self.quit() raise @@ -132,7 +132,7 @@ def sendmail(self, envsender, recips, msgtext): except smtplib.SMTPException as e: # For safety, close this connection. The next send attempt will # automatically re-open it. Pass the exception on up. - Mailman.Logging.Syslog.mailman_log('smtp-failure', 'SMTP sendmail error: %s\nTraceback:\n%s', + mailman_log('smtp-failure', 'SMTP sendmail error: %s\nTraceback:\n%s', str(e), traceback.format_exc()) self.quit() raise @@ -202,7 +202,7 @@ def process(mlist, msg, msgdata): if 'undelivered' in msgdata: chunks = msgdata['undelivered'] except Exception as e: - Mailman.Logging.Syslog.mailman_log('error', 'Error in SMTPDirect.process: %s\nTraceback:\n%s', + mailman_log('error', 'Error in SMTPDirect.process: %s\nTraceback:\n%s', str(e), traceback.format_exc()) raise # If we're doing bulk delivery, then we can stitch up the message now. @@ -242,7 +242,7 @@ def process(mlist, msg, msgdata): # undelivered list and re-raise the exception. We don't know # how many of the last chunk might receive the message, so at # worst, everyone in this chunk will get a duplicate. Sigh. - syslog('error', + mailman_log('error', 'Delivery error for chunk: %s\nError: %s\n%s', chunk, str(e), traceback.format_exc()) chunks.append(chunk) @@ -267,16 +267,16 @@ def process(mlist, msg, msgdata): }) # We have to use the copy() method because extended call syntax requires a # concrete dictionary object; it does not allow a generic mapping. It's - # still worthwhile doing the interpolation in syslog() because it'll catch + # still worthwhile doing the interpolation in mailman_log() because it'll catch # any catastrophic exceptions due to bogus format strings. if Mailman.mm_cfg.SMTP_LOG_EVERY_MESSAGE: - syslog(Mailman.mm_cfg.SMTP_LOG_EVERY_MESSAGE[0], + mailman_log(Mailman.mm_cfg.SMTP_LOG_EVERY_MESSAGE[0], Mailman.mm_cfg.SMTP_LOG_EVERY_MESSAGE[1] % d.copy()) if refused: if Mailman.mm_cfg.SMTP_LOG_REFUSED: - syslog.write_ex(Mailman.mm_cfg.SMTP_LOG_REFUSED[0], - Mailman.mm_cfg.SMTP_LOG_REFUSED[1], kws=d) + mailman_log(Mailman.mm_cfg.SMTP_LOG_REFUSED[0], + Mailman.mm_cfg.SMTP_LOG_REFUSED[1] % d.copy()) elif msgdata.get('tolist'): # Log the successful post, but only if it really was a post to the @@ -285,8 +285,8 @@ def process(mlist, msg, msgdata): # the other messages, but in that case, we should probably have a # separate configuration variable to control that. if Mailman.mm_cfg.SMTP_LOG_SUCCESS: - syslog.write_ex(Mailman.mm_cfg.SMTP_LOG_SUCCESS[0], - Mailman.mm_cfg.SMTP_LOG_SUCCESS[1], kws=d) + mailman_log(Mailman.mm_cfg.SMTP_LOG_SUCCESS[0], + Mailman.mm_cfg.SMTP_LOG_SUCCESS[1] % d.copy()) # Process any failed deliveries. tempfailures = [] @@ -304,22 +304,22 @@ def process(mlist, msg, msgdata): if code >= 500 and code != 552: # A permanent failure permfailures.append(recip) - syslog('smtp-failure', + mailman_log('smtp-failure', 'Permanent delivery failure for %s: code %s, message: %s', recip, code, smtpmsg) else: # Deal with persistent transient failures by queuing them up for # future delivery. TBD: this could generate lots of log entries! tempfailures.append(recip) - syslog('smtp-failure', + mailman_log('smtp-failure', 'Temporary delivery failure for %s: code %s, message: %s', recip, code, smtpmsg) if Mailman.mm_cfg.SMTP_LOG_EACH_FAILURE: d.update({'recipient': recip, 'failcode' : code, 'failmsg' : smtpmsg}) - syslog.write_ex(Mailman.mm_cfg.SMTP_LOG_EACH_FAILURE[0], - Mailman.mm_cfg.SMTP_LOG_EACH_FAILURE[1], kws=d) + mailman_log(Mailman.mm_cfg.SMTP_LOG_EACH_FAILURE[0], + Mailman.mm_cfg.SMTP_LOG_EACH_FAILURE[1] % d.copy()) # Return the results if tempfailures or permfailures: raise Mailman.Errors.SomeRecipientsFailed(tempfailures, permfailures) @@ -395,7 +395,7 @@ def verpdeliver(mlist, msg, msgdata, envsender, failures, conn): # deliver it to this person, nor can we craft a valid verp # header. I don't think there's much we can do except ignore # this recipient. - syslog('smtp', 'Skipping VERP delivery to unqual recip: %s', + mailman_log('smtp', 'Skipping VERP delivery to unqual recip: %s', recip) continue d = {'bounces': bmailbox, @@ -404,7 +404,7 @@ def verpdeliver(mlist, msg, msgdata, envsender, failures, conn): } envsender = '%s@%s' % ((Mailman.mm_cfg.VERP_FORMAT % d), DOT.join(bdomain)) except Exception as e: - syslog('error', 'Failed to parse email addresses for VERP: %s', e) + mailman_log('error', 'Failed to parse email addresses for VERP: %s', e) continue if mlist.personalize == 2: # When fully personalizing, we want the To address to point to the @@ -448,7 +448,7 @@ def verpdeliver(mlist, msg, msgdata, envsender, failures, conn): # one. ;) bulkdeliver(mlist, msgcopy, msgdata, envsender, failures, conn) except Exception as e: - syslog('error', 'Failed to process VERP delivery: %s', e) + mailman_log('error', 'Failed to process VERP delivery: %s', e) continue @@ -510,11 +510,11 @@ def bulkdeliver(mlist, msg, msgdata, envsender, failures, conn): # Send the message refused = conn.sendmail(envsender, recips, msgtext) except smtplib.SMTPRecipientsRefused as e: - syslog('smtp-failure', 'All recipients refused: %s, msgid: %s', + mailman_log('smtp-failure', 'All recipients refused: %s, msgid: %s', e, msgid) refused = e.recipients except smtplib.SMTPResponseException as e: - syslog('smtp-failure', 'SMTP session failure: %s, %s, msgid: %s', + mailman_log('smtp-failure', 'SMTP session failure: %s, %s, msgid: %s', e.smtp_code, e.smtp_error, msgid) # If this was a permanent failure, don't add the recipients to the # refused, because we don't want them to be added to failures. @@ -530,7 +530,7 @@ def bulkdeliver(mlist, msg, msgdata, envsender, failures, conn): # MTA not responding, or other socket problems, or any other kind of # SMTPException. In that case, nothing got delivered, so treat this # as a temporary failure. - syslog('smtp-failure', 'Low level smtp error: %s, msgid: %s', e, msgid) + mailman_log('smtp-failure', 'Low level smtp error: %s, msgid: %s', e, msgid) error = str(e) for r in recips: refused[r] = (-1, error) From 29712c947f33c3455a5f055714606d7fdd0bddb7 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 2 May 2025 15:03:59 -0400 Subject: [PATCH 341/748] smtpdirect --- Mailman/Handlers/Decorate.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Mailman/Handlers/Decorate.py b/Mailman/Handlers/Decorate.py index 591b8bf9..2190e93a 100644 --- a/Mailman/Handlers/Decorate.py +++ b/Mailman/Handlers/Decorate.py @@ -204,6 +204,10 @@ def process(mlist, msg, msgdata): def decorate(mlist, template, what, extradict=None): # `what' is just a descriptive phrase used in the log message + # If template is None, return empty string + if template is None: + return '' + # If template is a Message object, get its content if isinstance(template, Message): template = template.get_payload(decode=True) From 8292a2d1e20a96378cdd26b70fb3c703a4bc1b10 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 2 May 2025 15:10:11 -0400 Subject: [PATCH 342/748] update --- Mailman/Handlers/Hold.py | 2 -- Mailman/Queue/IncomingRunner.py | 9 ++++++++- bin/mailmanctl | 3 ++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Mailman/Handlers/Hold.py b/Mailman/Handlers/Hold.py index b5780b59..a49dc3f6 100644 --- a/Mailman/Handlers/Hold.py +++ b/Mailman/Handlers/Hold.py @@ -215,8 +215,6 @@ def hold_for_approval(mlist, msg, msgdata, exc): lang = mlist.getMemberLanguage(sender) # Get the text of the message text = exc.rejection_notice(mlist) - # Create the notification message - nmsg = UserNotification(sender, owneraddr, subject, text, lang) listname = mlist.real_name sender = msgdata.get('sender', msg.get_sender()) usersubject = msg.get('subject') diff --git a/Mailman/Queue/IncomingRunner.py b/Mailman/Queue/IncomingRunner.py index 6c9aaa45..b77f665e 100644 --- a/Mailman/Queue/IncomingRunner.py +++ b/Mailman/Queue/IncomingRunner.py @@ -288,7 +288,14 @@ def _dopipeline(self, mlist, msg, msgdata, pipeline): mlist.internal_name(), handler) return 0 except Errors.HoldMessage: - # Let the approval process take it from here + # Message is being held for moderation, no need to requeue + mailman_log('vette', """Message held for moderation, msgid: %s + list: %s, + handler: %s""", + msg.get('message-id', 'n/a'), + mlist.internal_name(), handler) + # Add message ID to processed set to prevent duplicate processing + self._processed_messages.add(msgid) return 0 except Errors.RejectMessage as e: pipeline.insert(0, handler) diff --git a/bin/mailmanctl b/bin/mailmanctl index bc82e663..cfd18318 100755 --- a/bin/mailmanctl +++ b/bin/mailmanctl @@ -668,7 +668,8 @@ Qrunner %s reached maximum restart limit of %d, not restarting.""", os._exit(1) except Exception as e: - syslog('error', 'Error during startup: %s', str(e)) + import traceback + syslog('error', 'Error during startup: %s\nTraceback:\n%s', str(e), traceback.format_exc()) sys.exit(1) elif args.command == 'stop': kill_watcher(signal.SIGTERM) From a3f93dd6a5b31036e1f5f0e5f090ee0a33f997c1 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 2 May 2025 15:20:58 -0400 Subject: [PATCH 343/748] update --- Mailman/CSRFcheck.py | 3 ++- Mailman/Handlers/Decorate.py | 3 ++- Mailman/Handlers/Moderate.py | 21 +++++++++++------- Mailman/Handlers/Scrubber.py | 3 ++- Mailman/ListAdmin.py | 15 ++++++++----- Mailman/LockFile.py | 9 ++++++-- Mailman/MailList.py | 5 +++-- Mailman/Pending.py | 3 ++- Mailman/Queue/CommandRunner.py | 3 ++- Mailman/Queue/OutgoingRunner.py | 39 +++++++++++++++++++++++++++++---- 10 files changed, 78 insertions(+), 26 deletions(-) diff --git a/Mailman/CSRFcheck.py b/Mailman/CSRFcheck.py index fb2b46c8..b7dca18b 100644 --- a/Mailman/CSRFcheck.py +++ b/Mailman/CSRFcheck.py @@ -121,7 +121,8 @@ def csrf_check(mlist, token, cgi_user=None): context = keydict.get(key) key, secret = mlist.AuthContextInfo(context, user) - assert key + if not key: + raise ValueError('Missing CSRF key') try: # Ensure all values are properly encoded before hashing diff --git a/Mailman/Handlers/Decorate.py b/Mailman/Handlers/Decorate.py index 2190e93a..765bb460 100644 --- a/Mailman/Handlers/Decorate.py +++ b/Mailman/Handlers/Decorate.py @@ -40,7 +40,8 @@ def process(mlist, msg, msgdata): # Calculate the extra personalization dictionary. Note that the # length of the recips list better be exactly 1. recips = msgdata.get('recips') - assert isinstance(recips, list) and len(recips) == 1 + if not (isinstance(recips, list) and len(recips) == 1): + raise ValueError(f'Invalid recipients: expected list with one item, got {type(recips)} with {len(recips)} items') member = recips[0].lower() d['user_address'] = member try: diff --git a/Mailman/Handlers/Moderate.py b/Mailman/Handlers/Moderate.py index 5e6fd06f..87acdef3 100644 --- a/Mailman/Handlers/Moderate.py +++ b/Mailman/Handlers/Moderate.py @@ -73,13 +73,16 @@ def process(mlist, msg, msgdata): if mlist.getMemberOption(sender, mm_cfg.Moderate): # Note that for member_moderation_action, 0==Hold, 1=Reject, # 2==Discard - if mlist.member_moderation_action == 0: + member_moderation_action = mlist.member_moderation_action + if member_moderation_action not in (mm_cfg.DEFER, mm_cfg.APPROVE, mm_cfg.REJECT, mm_cfg.DISCARD, mm_cfg.HOLD): + raise ValueError(f'Invalid member_moderation_action: {member_moderation_action}') + if member_moderation_action == 0: # Hold. BAW: WIBNI we could add the member_moderation_notice # to the notice sent back to the sender? msgdata['sender'] = sender Hold.hold_for_approval(mlist, msg, msgdata, ModeratedMemberPost) - elif mlist.member_moderation_action == 1: + elif member_moderation_action == 1: # Reject text = mlist.member_moderation_notice if text: @@ -88,7 +91,7 @@ def process(mlist, msg, msgdata): # Use the default RejectMessage notice string text = None raise Errors.RejectMessage(text) - elif mlist.member_moderation_action == 2: + elif member_moderation_action == 2: # Discard. BAW: Again, it would be nice if we could send a # discard notice to the sender raise Errors.DiscardMessage @@ -128,15 +131,17 @@ def process(mlist, msg, msgdata): # Okay, so the sender wasn't specified explicitly by any of the non-member # moderation configuration variables. Handle by way of generic non-member # action. - assert 0 <= mlist.generic_nonmember_action <= 4 - if mlist.generic_nonmember_action == 0 or msgdata.get('fromusenet'): + generic_nonmember_action = mlist.generic_nonmember_action + if not (0 <= generic_nonmember_action <= 4): + raise ValueError(f'Invalid generic_nonmember_action: {generic_nonmember_action}, must be between 0 and 4') + if generic_nonmember_action == 0 or msgdata.get('fromusenet'): # Accept return - elif mlist.generic_nonmember_action == 1: + elif generic_nonmember_action == 1: Hold.hold_for_approval(mlist, msg, msgdata, Hold.NonMemberPost) - elif mlist.generic_nonmember_action == 2: + elif generic_nonmember_action == 2: do_reject(mlist) - elif mlist.generic_nonmember_action == 3: + elif generic_nonmember_action == 3: do_discard(mlist, msg) def do_reject(mlist): diff --git a/Mailman/Handlers/Scrubber.py b/Mailman/Handlers/Scrubber.py index 27b80193..5f6182c7 100644 --- a/Mailman/Handlers/Scrubber.py +++ b/Mailman/Handlers/Scrubber.py @@ -124,7 +124,8 @@ def calculate_attachments_dir(mlist, msg, msgdata): # Best we can do I think month = day = year = 0 datedir = '%04d%02d%02d' % (year, month, day) - assert datedir + if not datedir: + raise ValueError('Missing datedir parameter') # As for the msgid hash, we'll base this part on the Message-ID: so that # all attachments for the same message end up in the same directory (we'll # uniquify the filenames in that directory as needed). We use the first 2 diff --git a/Mailman/ListAdmin.py b/Mailman/ListAdmin.py index 907106fe..e16369d8 100644 --- a/Mailman/ListAdmin.py +++ b/Mailman/ListAdmin.py @@ -337,7 +337,8 @@ def HandleRequest(self, id, value, comment=None, preserve=None, elif rtype == UNSUBSCRIPTION: status = self.__handleunsubscription(data, value, comment) else: - assert rtype == SUBSCRIPTION + if rtype != SUBSCRIPTION: + raise ValueError(f'Invalid request type: {rtype}, expected {SUBSCRIPTION}') status = self.__handlesubscription(data, value, comment) if status != DEFER: # BAW: Held message ids are linked to Pending cookies, allowing @@ -415,7 +416,8 @@ def __handlepost(self, record, value, comment, preserve, forward, addr): if path.endswith('.pck'): msg = pickle.load(fp, fix_imports=True, encoding='latin1') else: - assert path.endswith('.txt'), '%s not .pck or .txt' % path + if not path.endswith('.txt'): + raise ValueError(f'Invalid file extension: {path} must end with .txt') msg = fp.read() finally: fp.close() @@ -489,7 +491,8 @@ def __handlepost(self, record, value, comment, preserve, forward, addr): sender, comment or _('[No reason given]'), lang=lang) else: - assert value == mm_cfg.DISCARD + if value != mm_cfg.DISCARD: + raise ValueError(f'Invalid value: {value}, expected {mm_cfg.DISCARD}') # Discarded rejection = 'Discarded' # Forward the message @@ -628,7 +631,8 @@ def __handlesubscription(self, record, value, comment): \tReason: %s""", self.internal_name(), addr, comment or '[No reason given]') else: # subscribe - assert value == mm_cfg.SUBSCRIBE + if value != mm_cfg.SUBSCRIBE: + raise ValueError(f'Invalid value: {value}, expected {mm_cfg.SUBSCRIBE}') try: _ = D_ whence = _('via admin approval') @@ -683,7 +687,8 @@ def __handleunsubscription(self, record, value, comment): mailman_log('vette', """%s: rejected unsubscription request from %s \tReason: %s""", self.internal_name(), addr, comment or '[No reason given]') else: - assert value == mm_cfg.UNSUBSCRIBE + if value != mm_cfg.UNSUBSCRIBE: + raise ValueError(f'Invalid value: {value}, expected {mm_cfg.UNSUBSCRIBE}') try: self.ApprovedDeleteMember(addr) except Errors.NotAMemberError: diff --git a/Mailman/LockFile.py b/Mailman/LockFile.py index a345a6b8..cb118333 100644 --- a/Mailman/LockFile.py +++ b/Mailman/LockFile.py @@ -236,8 +236,13 @@ def _transfer_to(self, pid): # Unlink the old winner, completing the transfer os.unlink(winner) # And do some sanity checks - assert self.__linkcount() == 2 - assert self.locked() + link_count = self.__linkcount() + if link_count != 2: + mailman_log('error', 'Lock transfer failed: link count is %d, expected 2', link_count) + raise LockError('Lock transfer failed: link count is %d, expected 2' % link_count) + if not self.locked(): + mailman_log('error', 'Lock transfer failed: lock not acquired') + raise LockError('Lock transfer failed: lock not acquired') mailman_log('debug', 'transferred the lock') def _take_possession(self): diff --git a/Mailman/MailList.py b/Mailman/MailList.py index 0de00b67..b1aabb8c 100644 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -593,7 +593,8 @@ def GetConfigInfo(self, category, subcat=None): # def Create(self, name, admin, crypted_password, langs=None, emailhost=None, urlhost=None): - assert name == name.lower(), 'List name must be all lower case.' + if name != name.lower(): + raise ValueError('List name must be all lower case.') if Utils.list_exists(name): raise Errors.MMListAlreadyExistsError(name) # Problems and potential attacks can occur if the list name in the @@ -724,7 +725,7 @@ def __load(self, dbfile): elif dbfile.endswith('.pck') or dbfile.endswith('.pck.last'): loadfunc = lambda fp: pickle.load(fp, fix_imports=True, encoding='latin1') else: - assert 0, 'Bad database file name' + raise ValueError('Bad database file name') try: # Check the mod time of the file first. If it matches our # timestamp, then the state hasn't change since the last time we diff --git a/Mailman/Pending.py b/Mailman/Pending.py index a3d909d1..20bf9df2 100644 --- a/Mailman/Pending.py +++ b/Mailman/Pending.py @@ -55,7 +55,8 @@ def InitTempVars(self): def pend_new(self, op, *content, **kws): """Create a new entry in the pending database, returning cookie for it. """ - assert op in _ALLKEYS, 'op: %s' % op + if op not in _ALLKEYS: + raise ValueError(f'Invalid operation: {op}, must be one of {_ALLKEYS}') lifetime = kws.get('lifetime', mm_cfg.PENDING_REQUEST_LIFE) # We try the main loop several times. If we get a lock error somewhere # (for instance because someone broke the lock) we simply try again. diff --git a/Mailman/Queue/CommandRunner.py b/Mailman/Queue/CommandRunner.py index 603b58d1..1e518a8f 100644 --- a/Mailman/Queue/CommandRunner.py +++ b/Mailman/Queue/CommandRunner.py @@ -93,7 +93,8 @@ def __init__(self, mlist, msg, msgdata): Utils.GetCharSet(self.msgdata['lang']), errors='replace') # text/plain parts better have string payloads - assert isinstance(body, (str, bytes)) + if not isinstance(body, (str, bytes)): + raise TypeError(f'Invalid body type: {type(body)}, expected str or bytes') lines = body.splitlines() # Use no more lines than specified self.commands.extend(lines[:mm_cfg.DEFAULT_MAIL_COMMANDS_MAX_LINES]) diff --git a/Mailman/Queue/OutgoingRunner.py b/Mailman/Queue/OutgoingRunner.py index 8672d839..1cae4c0e 100644 --- a/Mailman/Queue/OutgoingRunner.py +++ b/Mailman/Queue/OutgoingRunner.py @@ -52,6 +52,12 @@ def __init__(self, slice=None, numslices=1): BounceMixin.__init__(self) mailman_log('debug', 'OutgoingRunner: BounceMixin initialized') + # Track processed messages to prevent duplicates + self._processed_messages = set() + # Clean up old messages periodically + self._last_cleanup = time.time() + self._cleanup_interval = 3600 # Clean up every hour + # We look this function up only at startup time self._modname = 'Mailman.Handlers.' + mm_cfg.DELIVERY_MODULE mailman_log('debug', 'OutgoingRunner: Attempting to import delivery module: %s', self._modname) @@ -86,22 +92,47 @@ def __init__(self, slice=None, numslices=1): def _dispose(self, mlist, msg, msgdata): """Process an outgoing message.""" + msgid = msg.get('message-id', 'n/a') + filebase = msgdata.get('_filebase', 'unknown') + + # Check for duplicate messages + if msgid in self._processed_messages: + mailman_log('error', 'OutgoingRunner: Duplicate message detected: %s (file: %s)', msgid, filebase) + return False + + # Clean up old message IDs periodically + current_time = time.time() + if current_time - self._last_cleanup > self._cleanup_interval: + self._processed_messages.clear() + self._last_cleanup = current_time + try: + # Log start of processing + mailman_log('info', 'OutgoingRunner: Starting to process message %s (file: %s) for list %s', + msgid, filebase, mlist.internal_name()) + # Validate message type first msg, success = self._validate_message(msg, msgdata) if not success: - mailman_log('error', 'Message validation failed for outgoing message') + mailman_log('error', 'Message validation failed for outgoing message %s', msgid) return False # Process the message through the delivery module self._func(mlist, msg, msgdata) + + # Add to processed messages only after successful processing + self._processed_messages.add(msgid) + + # Log successful completion + mailman_log('info', 'OutgoingRunner: Successfully processed message %s (file: %s) for list %s', + msgid, filebase, mlist.internal_name()) return True except Exception as e: # Enhanced error logging with more context - mailman_log('error', 'Error processing outgoing message for list %s: %s', - mlist.internal_name(), str(e)) + mailman_log('error', 'Error processing outgoing message %s for list %s: %s', + msgid, mlist.internal_name(), str(e)) mailman_log('error', 'Message details:') - mailman_log('error', ' Message ID: %s', msg.get('message-id', 'n/a')) + mailman_log('error', ' Message ID: %s', msgid) mailman_log('error', ' From: %s', msg.get('from', 'unknown')) mailman_log('error', ' To: %s', msg.get('to', 'unknown')) mailman_log('error', ' Subject: %s', msg.get('subject', '(no subject)')) From 28668d5199691f496e0cb5844ebf4b01a69a3917 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 2 May 2025 15:33:03 -0400 Subject: [PATCH 344/748] update --- Mailman/Queue/BounceRunner.py | 36 +++++- Mailman/Queue/IncomingRunner.py | 68 +++++------ Mailman/Queue/OutgoingRunner.py | 63 +++++++--- Mailman/Queue/RetryRunner.py | 53 +++++++-- Mailman/Queue/Runner.py | 46 ++++++++ Mailman/Queue/Switchboard.py | 203 ++++++++++++++------------------ Mailman/Queue/VirginRunner.py | 45 ++++++- 7 files changed, 329 insertions(+), 185 deletions(-) diff --git a/Mailman/Queue/BounceRunner.py b/Mailman/Queue/BounceRunner.py index b4f6f0f9..46d1a9d8 100644 --- a/Mailman/Queue/BounceRunner.py +++ b/Mailman/Queue/BounceRunner.py @@ -179,32 +179,48 @@ def __init__(self, slice=None, numslices=1): def _dispose(self, mlist, msg, msgdata): """Process a bounce message.""" + msgid = msg.get('message-id', 'n/a') + filebase = msgdata.get('_filebase', 'unknown') + + # Check retry delay and duplicate processing + if not self._check_retry_delay(msgid, filebase): + return False + # Make sure we have the most up-to-date state try: mlist.Load() except Errors.MMCorruptListDatabaseError as e: mailman_log('error', 'Failed to load list %s: %s', mlist.internal_name(), e) + self._unmark_message_processed(msgid) return False except Exception as e: mailman_log('error', 'Unexpected error loading list %s: %s', mlist.internal_name(), e) + self._unmark_message_processed(msgid) return False # Validate message type first msg, success = self._validate_message(msg, msgdata) if not success: mailman_log('error', 'Message validation failed for bounce message') + self._unmark_message_processed(msgid) return False # Validate message headers if not msg.get('message-id'): mailman_log('error', 'Message missing Message-ID header') + self._unmark_message_processed(msgid) return False try: + # Log start of processing + mailman_log('info', 'BounceRunner: Starting to process bounce message %s (file: %s) for list %s', + msgid, filebase, mlist.internal_name()) + # Process the bounce if not self._register_bounces(mlist.internal_name(), msg, msgdata): + self._unmark_message_processed(msgid) return False # Queue the bounce for further processing @@ -212,12 +228,28 @@ def _dispose(self, mlist, msg, msgdata): self._queue_bounces(mlist.internal_name(), msg, msgdata) except Exception as e: mailman_log('error', 'Error queueing bounces: %s', e) + self._unmark_message_processed(msgid) return False + # Log successful completion + mailman_log('info', 'BounceRunner: Successfully processed bounce message %s (file: %s) for list %s', + msgid, filebase, mlist.internal_name()) return True except Exception as e: - mailman_log('error', 'Error processing bounce %s: %s', - msg.get('message-id', 'n/a'), e) + # Enhanced error logging with more context + mailman_log('error', 'Error processing bounce message %s for list %s: %s', + msgid, mlist.internal_name(), str(e)) + mailman_log('error', 'Message details:') + mailman_log('error', ' Message ID: %s', msgid) + mailman_log('error', ' From: %s', msg.get('from', 'unknown')) + mailman_log('error', ' To: %s', msg.get('to', 'unknown')) + mailman_log('error', ' Subject: %s', msg.get('subject', '(no subject)')) + mailman_log('error', ' Message type: %s', type(msg).__name__) + mailman_log('error', ' Message data: %s', str(msgdata)) + mailman_log('error', 'Traceback:\n%s', traceback.format_exc()) + + # Remove from processed messages on error + self._unmark_message_processed(msgid) return False _doperiodic = BounceMixin._doperiodic diff --git a/Mailman/Queue/IncomingRunner.py b/Mailman/Queue/IncomingRunner.py index b77f665e..5b00dd3d 100644 --- a/Mailman/Queue/IncomingRunner.py +++ b/Mailman/Queue/IncomingRunner.py @@ -140,59 +140,53 @@ def _dispose(self, listname, msg, msgdata): # Track message ID to prevent duplicates msgid = msg.get('message-id', 'n/a') - if msgid in self._processed_messages: - mailman_log('error', 'Duplicate message detected: %s (file: %s)', msgid, msgdata.get('_filebase', 'unknown')) + filebase = msgdata.get('_filebase', 'unknown') + + # Check retry delay and duplicate processing + if not self._check_retry_delay(msgid, filebase): # Move to shunt queue and remove from original queue self._shunt.enqueue(msg, msgdata) # Get the filebase from msgdata and finish processing it - filebase = msgdata.get('_filebase') if filebase: self._switchboard.finish(filebase) return 0 - # Clean up old message IDs periodically - current_time = time.time() - if current_time - self._last_cleanup > self._cleanup_interval: - self._processed_messages.clear() - self._last_cleanup = current_time - # Get the MailList object for the list name try: mlist = MailList(listname, lock=False) except Errors.MMListError as e: mailman_log('error', 'Failed to get list %s: %s', listname, str(e)) + self._unmark_message_processed(msgid) return 0 - # Try to get the list lock with shorter timeout - try: - mlist.Lock(timeout=mm_cfg.LIST_LOCK_TIMEOUT / 2) - except LockFile.TimeOutError: - mailman_log('error', 'Lock timeout for %s', listname) - return 1 - except Exception as e: - mailman_log('error', 'Unexpected error acquiring lock for %s: %s', - listname, str(e)) - return 1 - - # Process the message through a handler pipeline try: - pipeline = self._get_pipeline(mlist, msg, msgdata) - msgdata['pipeline'] = pipeline - more = self._dopipeline(mlist, msg, msgdata, pipeline) - if not more: - del msgdata['pipeline'] - # Only add to processed messages if processing completed successfully - self._processed_messages.add(msgid) - mlist.Save() - return more + # Log start of processing + mailman_log('info', 'IncomingRunner: Starting to process message %s (file: %s) for list %s', + msgid, filebase, mlist.internal_name()) + + # Process the message through the pipeline + result = self._dopipeline(mlist, msg, msgdata, self._get_pipeline(mlist, msg, msgdata)) + + # Log successful completion + mailman_log('info', 'IncomingRunner: Successfully processed message %s (file: %s) for list %s', + msgid, filebase, mlist.internal_name()) + return result except Exception as e: - mailman_log('error', 'Error processing message for %s: %s\nTraceback:\n%s', listname, str(e), traceback.format_exc()) - return 1 - finally: - try: - mlist.Unlock() - except Exception as e: - mailman_log('error', 'Error unlocking %s: %s', listname, str(e)) + # Enhanced error logging with more context + mailman_log('error', 'Error processing incoming message %s for list %s: %s', + msgid, mlist.internal_name(), str(e)) + mailman_log('error', 'Message details:') + mailman_log('error', ' Message ID: %s', msgid) + mailman_log('error', ' From: %s', msg.get('from', 'unknown')) + mailman_log('error', ' To: %s', msg.get('to', 'unknown')) + mailman_log('error', ' Subject: %s', msg.get('subject', '(no subject)')) + mailman_log('error', ' Message type: %s', type(msg).__name__) + mailman_log('error', ' Message data: %s', str(msgdata)) + mailman_log('error', 'Traceback:\n%s', traceback.format_exc()) + + # Remove from processed messages on error + self._unmark_message_processed(msgid) + return 0 def _get_pipeline(self, mlist, msg, msgdata): # We must return a copy of the list, otherwise, the first message that diff --git a/Mailman/Queue/OutgoingRunner.py b/Mailman/Queue/OutgoingRunner.py index 1cae4c0e..1d3e80f6 100644 --- a/Mailman/Queue/OutgoingRunner.py +++ b/Mailman/Queue/OutgoingRunner.py @@ -25,6 +25,7 @@ import os import sys from io import StringIO +import threading from Mailman import mm_cfg from Mailman import Utils @@ -42,6 +43,15 @@ class OutgoingRunner(Runner, BounceMixin): QDIR = mm_cfg.OUTQUEUE_DIR + # Shared processed messages tracking + _processed_messages = set() + _processed_lock = threading.Lock() + _last_cleanup = time.time() + _cleanup_interval = 3600 # Clean up every hour + + # Retry delay configuration + MIN_RETRY_DELAY = 300 # 5 minutes minimum delay between retries + _retry_times = {} # Track last retry time for each message def __init__(self, slice=None, numslices=1): mailman_log('debug', 'OutgoingRunner: Starting initialization') @@ -52,12 +62,6 @@ def __init__(self, slice=None, numslices=1): BounceMixin.__init__(self) mailman_log('debug', 'OutgoingRunner: BounceMixin initialized') - # Track processed messages to prevent duplicates - self._processed_messages = set() - # Clean up old messages periodically - self._last_cleanup = time.time() - self._cleanup_interval = 3600 # Clean up every hour - # We look this function up only at startup time self._modname = 'Mailman.Handlers.' + mm_cfg.DELIVERY_MODULE mailman_log('debug', 'OutgoingRunner: Attempting to import delivery module: %s', self._modname) @@ -95,16 +99,32 @@ def _dispose(self, mlist, msg, msgdata): msgid = msg.get('message-id', 'n/a') filebase = msgdata.get('_filebase', 'unknown') - # Check for duplicate messages - if msgid in self._processed_messages: - mailman_log('error', 'OutgoingRunner: Duplicate message detected: %s (file: %s)', msgid, filebase) - return False - - # Clean up old message IDs periodically - current_time = time.time() - if current_time - self._last_cleanup > self._cleanup_interval: - self._processed_messages.clear() - self._last_cleanup = current_time + # Check for duplicate messages with proper locking + with self._processed_lock: + if msgid in self._processed_messages: + mailman_log('error', 'OutgoingRunner: Duplicate message detected: %s (file: %s)', msgid, filebase) + return False + + # Clean up old message IDs periodically + current_time = time.time() + if current_time - self._last_cleanup > self._cleanup_interval: + self._processed_messages.clear() + self._retry_times.clear() # Also clean up retry times + self._last_cleanup = current_time + + # Check retry delay + last_retry = self._retry_times.get(msgid, 0) + time_since_last_retry = current_time - last_retry + if time_since_last_retry < self.MIN_RETRY_DELAY: + mailman_log('info', 'OutgoingRunner: Message %s (file: %s) retried too soon, delaying. Time since last retry: %d seconds', + msgid, filebase, time_since_last_retry) + # Requeue with delay + self.__retryq.enqueue(msg, msgdata) + return False + + # Mark message as being processed and update retry time + self._processed_messages.add(msgid) + self._retry_times[msgid] = current_time try: # Log start of processing @@ -115,14 +135,13 @@ def _dispose(self, mlist, msg, msgdata): msg, success = self._validate_message(msg, msgdata) if not success: mailman_log('error', 'Message validation failed for outgoing message %s', msgid) + with self._processed_lock: + self._processed_messages.remove(msgid) return False # Process the message through the delivery module self._func(mlist, msg, msgdata) - # Add to processed messages only after successful processing - self._processed_messages.add(msgid) - # Log successful completion mailman_log('info', 'OutgoingRunner: Successfully processed message %s (file: %s) for list %s', msgid, filebase, mlist.internal_name()) @@ -139,6 +158,12 @@ def _dispose(self, mlist, msg, msgdata): mailman_log('error', ' Message type: %s', type(msg).__name__) mailman_log('error', ' Message data: %s', str(msgdata)) mailman_log('error', 'Traceback:\n%s', traceback.format_exc()) + + # Remove from processed messages on error and requeue + with self._processed_lock: + self._processed_messages.remove(msgid) + # Requeue with delay + self.__retryq.enqueue(msg, msgdata) return False def _queue_bounces(self, mlist, msg, msgdata, failures): diff --git a/Mailman/Queue/RetryRunner.py b/Mailman/Queue/RetryRunner.py index d117cecf..a8e23711 100644 --- a/Mailman/Queue/RetryRunner.py +++ b/Mailman/Queue/RetryRunner.py @@ -15,6 +15,7 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import time +import traceback from Mailman import mm_cfg from Mailman.Queue.Runner import Runner @@ -30,18 +31,54 @@ def __init__(self, slice=None, numslices=1): self.__outq = Switchboard(mm_cfg.OUTQUEUE_DIR) def _dispose(self, mlist, msg, msgdata): + msgid = msg.get('message-id', 'n/a') + filebase = msgdata.get('_filebase', 'unknown') + + # Check retry delay and duplicate processing + if not self._check_retry_delay(msgid, filebase): + return True # Keep in retry queue + # Validate message type first msg, success = self._validate_message(msg, msgdata) if not success: mailman_log('error', 'Message validation failed for retry message') - return False - - # Move it to the out queue for another retry if it's time. - deliver_after = msgdata.get('deliver_after', 0) - if time.time() < deliver_after: - return True - self.__outq.enqueue(msg, msgdata) - return False + self._unmark_message_processed(msgid) + return True # Keep in retry queue + + try: + # Log start of processing + mailman_log('info', 'RetryRunner: Starting to process retry message %s (file: %s) for list %s', + msgid, filebase, mlist.internal_name()) + + # Move it to the out queue for another retry if it's time. + deliver_after = msgdata.get('deliver_after', 0) + if time.time() < deliver_after: + self._unmark_message_processed(msgid) + return True # Keep in retry queue + + # Move to outgoing queue + self.__outq.enqueue(msg, msgdata) + + # Log successful completion + mailman_log('info', 'RetryRunner: Successfully moved retry message %s (file: %s) to outgoing queue for list %s', + msgid, filebase, mlist.internal_name()) + return False # Remove from retry queue + except Exception as e: + # Enhanced error logging with more context + mailman_log('error', 'Error processing retry message %s for list %s: %s', + msgid, mlist.internal_name(), str(e)) + mailman_log('error', 'Message details:') + mailman_log('error', ' Message ID: %s', msgid) + mailman_log('error', ' From: %s', msg.get('from', 'unknown')) + mailman_log('error', ' To: %s', msg.get('to', 'unknown')) + mailman_log('error', ' Subject: %s', msg.get('subject', '(no subject)')) + mailman_log('error', ' Message type: %s', type(msg).__name__) + mailman_log('error', ' Message data: %s', str(msgdata)) + mailman_log('error', 'Traceback:\n%s', traceback.format_exc()) + + # Remove from processed messages on error + self._unmark_message_processed(msgid) + return True # Keep in retry queue def _snooze(self, filecnt): # We always want to snooze, but check for stop flag periodically diff --git a/Mailman/Queue/Runner.py b/Mailman/Queue/Runner.py index ce1f2d5e..4a6b1ffa 100644 --- a/Mailman/Queue/Runner.py +++ b/Mailman/Queue/Runner.py @@ -23,6 +23,7 @@ import traceback from io import StringIO from functools import wraps +import threading from Mailman import mm_cfg from Mailman import Utils @@ -39,6 +40,16 @@ class Runner: QDIR = None SLEEPTIME = mm_cfg.QRUNNER_SLEEP_TIME + + # Shared processed messages tracking + _processed_messages = set() + _processed_lock = threading.Lock() + _last_cleanup = time.time() + _cleanup_interval = 3600 # Clean up every hour + + # Retry delay configuration + MIN_RETRY_DELAY = 300 # 5 minutes minimum delay between retries + _retry_times = {} # Track last retry time for each message def __init__(self, slice=None, numslices=1): self._kids = {} @@ -370,3 +381,38 @@ def _dispose(self, mlist, msg, msgdata): msgdata is a dictionary of message metadata. """ raise NotImplementedError + + def _check_retry_delay(self, msgid, filebase): + """Check if enough time has passed since the last retry attempt. + Returns True if the message can be processed, False if it should be delayed.""" + with self._processed_lock: + # Clean up old message IDs periodically + current_time = time.time() + if current_time - self._last_cleanup > self._cleanup_interval: + self._processed_messages.clear() + self._retry_times.clear() # Also clean up retry times + self._last_cleanup = current_time + + # Check retry delay + last_retry = self._retry_times.get(msgid, 0) + time_since_last_retry = current_time - last_retry + if time_since_last_retry < self.MIN_RETRY_DELAY: + log('info', '%s: Message %s (file: %s) retried too soon, delaying. Time since last retry: %d seconds', + self.__class__.__name__, msgid, filebase, time_since_last_retry) + return False + + # Mark message as being processed and update retry time + self._processed_messages.add(msgid) + self._retry_times[msgid] = current_time + return True + + def _mark_message_processed(self, msgid): + """Mark a message as processed and update its retry time.""" + with self._processed_lock: + self._processed_messages.add(msgid) + self._retry_times[msgid] = time.time() + + def _unmark_message_processed(self, msgid): + """Remove a message from the processed set.""" + with self._processed_lock: + self._processed_messages.discard(msgid) diff --git a/Mailman/Queue/Switchboard.py b/Mailman/Queue/Switchboard.py index e3c1cb66..a587fd5d 100644 --- a/Mailman/Queue/Switchboard.py +++ b/Mailman/Queue/Switchboard.py @@ -90,6 +90,8 @@ def __init__(self, whichq, slice=None, numslices=1, recover=False): self.__upper = ((((shamax+1) * (slice+1)) / numslices)) - 1 if recover: self.recover_backup_files() + # Clean up any stale locks during initialization + self.cleanup_stale_locks() def whichq(self): return self.__whichq @@ -187,129 +189,76 @@ def dequeue(self, filebase): backfile = os.path.join(self.__whichq, filebase + '.bak') lockfile = filename + '.lock' - # Create lock file - try: - lock_fd = os.open(lockfile, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600) - os.close(lock_fd) - except OSError as e: - if e.errno == errno.EEXIST: - mailman_log('warning', 'Lock file exists for %s (full path: %s), waiting...', filename, lockfile) - # Wait for lock to be released - for _ in range(10): # 10 attempts + # Create lock file with proper cleanup + max_attempts = 30 # Increased from 10 to 30 + attempt = 0 + while attempt < max_attempts: + try: + # Try to create lock file + lock_fd = os.open(lockfile, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600) + # Write process info to lock file for debugging + with os.fdopen(lock_fd, 'w') as f: + f.write('pid=%d\nhost=%s\ntime=%f\n' % ( + os.getpid(), + socket.gethostname(), + time.time() + )) + break + except OSError as e: + if e.errno == errno.EEXIST: + # Check if lock is stale (older than 5 minutes) try: - lock_fd = os.open(lockfile, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600) - os.close(lock_fd) - break + lock_age = time.time() - os.path.getmtime(lockfile) + if lock_age > 300: # 5 minutes + # Read lock file contents for debugging + try: + with open(lockfile, 'r') as f: + lock_info = f.read() + mailman_log('warning', + 'Removing stale lock file %s (age: %d seconds)\nLock info: %s', + lockfile, lock_age, lock_info) + except Exception: + mailman_log('warning', + 'Removing stale lock file %s (age: %d seconds)', + lockfile, lock_age) + os.unlink(lockfile) + continue except OSError: - time.sleep(0.1) - else: - mailman_log('error', 'Could not acquire lock for %s (full path: %s) after 10 attempts', filename, lockfile) - return None, None - else: - mailman_log('error', 'Failed to create lock file %s (full path: %s): %s\nTraceback:\n%s', - filename, lockfile, str(e), traceback.format_exc()) - return None, None - + pass + # Wait before retrying with exponential backoff + time.sleep(min(1.0 * (2 ** attempt), 10.0)) + attempt += 1 + continue + raise + else: + mailman_log('error', 'Failed to acquire lock for %s after %d attempts', filename, max_attempts) + return None, None + try: - # Move the file to the backup file name for processing + # Read the message and metadata try: - if not os.path.exists(filename): - mailman_log('error', 'Source file %s (full path: %s) does not exist', filename, os.path.join(self.__whichq, filename)) - return None, None - if os.path.exists(backfile): - mailman_log('warning', 'Backup file %s (full path: %s) already exists, removing old version', backfile, os.path.join(self.__whichq, backfile)) - os.unlink(backfile) - os.rename(filename, backfile) - except OSError as e: - mailman_log('error', 'Failed to rename %s to %s (full paths: %s -> %s): %s\nTraceback:\n%s', - filename, backfile, os.path.join(self.__whichq, filename), os.path.join(self.__whichq, backfile), str(e), traceback.format_exc()) + with open(filename, 'rb') as fp: + # Use protocol 2 for Python 2/3 compatibility + msg = pickle.load(fp, fix_imports=True, encoding='latin1') + metadata = pickle.load(fp, fix_imports=True, encoding='latin1') + except (pickle.UnpicklingError, EOFError) as e: + mailman_log('error', 'Error unpickling file %s: %s', filename, str(e)) return None, None - - # Read the message object and metadata + + # Move to backup file try: - with open(backfile, 'rb') as fp: - # Try loading with Python 3 protocol first - try: - msg = pickle.load(fp, fix_imports=True) - data = pickle.load(fp, fix_imports=True) - except (pickle.UnpicklingError, EOFError) as e: - # If that fails, try with Python 2 protocol - fp.seek(0) - try: - msg = pickle.load(fp, fix_imports=True, encoding='latin1') - data = pickle.load(fp, fix_imports=True, encoding='latin1') - except (pickle.UnpicklingError, EOFError) as e2: - # The file is corrupted. Log the error and try to recover it. - mailman_log('error', 'Failed to unpickle file %s:\nPython 3 error: %s\nPython 2 error: %s\nTraceback:\n%s', - backfile, str(e), str(e2), traceback.format_exc()) - # Try to restore the original file - try: - if os.path.exists(filename): - os.unlink(filename) - os.rename(backfile, filename) - except Exception as restore_e: - mailman_log('error', 'Failed to restore original file %s: %s\nTraceback:\n%s', - filename, str(restore_e), traceback.format_exc()) - self.recover_backup_files() - return None, None - - # Validate message object - if isinstance(msg, str): - mailman_log('error', 'Message object is a string instead of a proper message object in file %s', backfile) - # Try to convert string to proper message object - try: - from email import message_from_string - msg = message_from_string(msg) - except Exception as e: - mailman_log('error', 'Failed to convert string to message object: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - # Move to shunt queue - self.finish(filebase, preserve=True) - return None, None - - # Convert to Mailman.Message if needed - if not isinstance(msg, Message): - try: - mailman_log('info', 'Converting email.message.Message to Mailman.Message') - mailman_msg = Message() - # Copy all attributes from the original message - for key, value in msg.items(): - mailman_msg[key] = value - # Copy the payload - if msg.is_multipart(): - for part in msg.get_payload(): - mailman_msg.attach(part) - else: - mailman_msg.set_payload(msg.get_payload()) - msg = mailman_msg - except Exception as e: - mailman_log('error', 'Failed to convert to Mailman.Message: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - # Move to shunt queue - self.finish(filebase, preserve=True) - return None, None - - if not hasattr(msg, 'get_sender'): - mailman_log('error', 'Message object does not have get_sender() method in file %s', backfile) - # Move to shunt queue - self.finish(filebase, preserve=True) - return None, None - - except Exception as e: - mailman_log('error', 'Failed to read backup file %s: %s\nTraceback:\n%s', - backfile, str(e), traceback.format_exc()) + os.rename(filename, backfile) + except OSError as e: + mailman_log('error', 'Error moving %s to %s: %s', filename, backfile, str(e)) return None, None - - return msg, data - + + return msg, metadata finally: - # Clean up lock file + # Always clean up the lock file try: - if os.path.exists(lockfile): - os.unlink(lockfile) - except Exception as cleanup_e: - mailman_log('error', 'Failed to clean up lock file %s: %s\nTraceback:\n%s', - lockfile, str(cleanup_e), traceback.format_exc()) + os.unlink(lockfile) + except OSError: + pass def finish(self, filebase, preserve=False): bakfile = os.path.join(self.__whichq, filebase + '.bak') @@ -735,3 +684,29 @@ def _dequeue_metadata(self, filename): return metadata except (IOError, OSError) as e: raise SwitchboardError('Could not read %s: %s' % (filename, e)) + + def cleanup_stale_locks(self): + """Clean up any stale lock files in the queue directory.""" + try: + for f in os.listdir(self.__whichq): + if f.endswith('.lock'): + lockfile = os.path.join(self.__whichq, f) + try: + lock_age = time.time() - os.path.getmtime(lockfile) + if lock_age > 300: # 5 minutes + # Read lock file contents for debugging + try: + with open(lockfile, 'r') as f: + lock_info = f.read() + mailman_log('warning', + 'Cleaning up stale lock file %s (age: %d seconds)\nLock info: %s', + lockfile, lock_age, lock_info) + except Exception: + mailman_log('warning', + 'Cleaning up stale lock file %s (age: %d seconds)', + lockfile, lock_age) + os.unlink(lockfile) + except OSError: + pass + except OSError as e: + mailman_log('error', 'Error cleaning up stale locks: %s', str(e)) diff --git a/Mailman/Queue/VirginRunner.py b/Mailman/Queue/VirginRunner.py index 410a9336..2c2c336b 100644 --- a/Mailman/Queue/VirginRunner.py +++ b/Mailman/Queue/VirginRunner.py @@ -25,17 +25,52 @@ from Mailman import mm_cfg from Mailman.Queue.Runner import Runner from Mailman.Queue.IncomingRunner import IncomingRunner +import traceback - class VirginRunner(IncomingRunner): QDIR = mm_cfg.VIRGINQUEUE_DIR def _dispose(self, mlist, msg, msgdata): - # We need to fasttrack this message through any handlers that touch - # it. E.g. especially CookHeaders. - msgdata['_fasttrack'] = 1 - return IncomingRunner._dispose(self, mlist, msg, msgdata) + msgid = msg.get('message-id', 'n/a') + filebase = msgdata.get('_filebase', 'unknown') + + # Check retry delay and duplicate processing + if not self._check_retry_delay(msgid, filebase): + return 0 + + try: + # Log start of processing + mailman_log('info', 'VirginRunner: Starting to process virgin message %s (file: %s) for list %s', + msgid, filebase, mlist.internal_name()) + + # We need to fasttrack this message through any handlers that touch + # it. E.g. especially CookHeaders. + msgdata['_fasttrack'] = 1 + + # Process through the pipeline + result = IncomingRunner._dispose(self, mlist, msg, msgdata) + + # Log successful completion + mailman_log('info', 'VirginRunner: Successfully processed virgin message %s (file: %s) for list %s', + msgid, filebase, mlist.internal_name()) + return result + except Exception as e: + # Enhanced error logging with more context + mailman_log('error', 'Error processing virgin message %s for list %s: %s', + msgid, mlist.internal_name(), str(e)) + mailman_log('error', 'Message details:') + mailman_log('error', ' Message ID: %s', msgid) + mailman_log('error', ' From: %s', msg.get('from', 'unknown')) + mailman_log('error', ' To: %s', msg.get('to', 'unknown')) + mailman_log('error', ' Subject: %s', msg.get('subject', '(no subject)')) + mailman_log('error', ' Message type: %s', type(msg).__name__) + mailman_log('error', ' Message data: %s', str(msgdata)) + mailman_log('error', 'Traceback:\n%s', traceback.format_exc()) + + # Remove from processed messages on error + self._unmark_message_processed(msgid) + return 0 def _get_pipeline(self, mlist, msg, msgdata): # It's okay to hardcode this, since it'll be the same for all From 4e04989117553d076a6806c196566b1b7e29040c Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 2 May 2025 15:49:12 -0400 Subject: [PATCH 345/748] update --- NEWS | 14 ++++----- bin/check_db | 81 +++++++++++++++++++++++++++++----------------------- 2 files changed, 53 insertions(+), 42 deletions(-) diff --git a/NEWS b/NEWS index f9a01b44..701e5283 100644 --- a/NEWS +++ b/NEWS @@ -313,7 +313,7 @@ Here is a history of user visible changes to Mailman. - The German translation has been updated by Ralf Hildebrandt. - - The Esperanto translation has been updated by Rubén Fernández Asensio. + - The Esperanto translation has been updated by Rub�n Fern�ndez Asensio. Bug fixes and other patches @@ -376,7 +376,7 @@ Here is a history of user visible changes to Mailman. - The Russian translation has been updated by Danil Smirnov. - A partial Esperanto translation has been added. Thanks to - Rubén Fernández Asensio. + Rub�n Fern�ndez Asensio. - Fixed a '# -*- coding:' line in the Russian message catalog that was mistakenly translated to Russian. (LP: #1777342) @@ -435,7 +435,7 @@ Here is a history of user visible changes to Mailman. New Features - - Thanks to David Siebörger who adapted an existing patch by Andrea + - Thanks to David Sieb�rger who adapted an existing patch by Andrea Veri to use Google reCAPTCHA v2 there is now the ability to add reCAPTCHA to the listinfo subscribe form. There are two new mm_cfg.py settings for RECAPTCHA_SITE_KEY and RECAPTCHA_SECRET_KEY, the values @@ -688,7 +688,7 @@ Here is a history of user visible changes to Mailman. i18n - The French translation of 'Dutch' is changed from 'Hollandais' to - 'Néerlandais' per Francis Jorissen. + 'N�erlandais' per Francis Jorissen. - Some German language templates that were incorrectly utf-8 encoded have been recoded as iso-8859-1. (LP: #1602779) @@ -1614,7 +1614,7 @@ Here is a history of user visible changes to Mailman. - Thanks go to the following for updating translations for the changes in this release. Thijs Kinkhorst - Stefan Förster + Stefan F�rster Fabian Wenk Bug Fixes and other patches @@ -1819,7 +1819,7 @@ Here is a history of user visible changes to Mailman. - Updated Japanese Translation from Tokio Kikuchi. - - Updated Finnish translation from Joni Töyrylä. + - Updated Finnish translation from Joni T�yryl�. - Made a few corrections to some Polish templates. Bug #566731. @@ -2245,7 +2245,7 @@ Internationalization - Added the Slovak translation from Martin Matuska. - - Added the Galician translation from Frco. Javier Rial Rodríguez. + - Added the Galician translation from Frco. Javier Rial Rodr�guez. Bug fixes and other patches diff --git a/bin/check_db b/bin/check_db index 211d21f8..72b00b12 100755 --- a/bin/check_db +++ b/bin/check_db @@ -80,9 +80,19 @@ def testfile(dbfile): loadfunc = load_pickle else: assert 0 + if args.verbose: + print('Processing file: %s' % dbfile) + print(' File type: %s' % ('marshal' if dbfile.endswith('.db') else 'pickle')) + print(' File size: %d bytes' % os.path.getsize(dbfile)) fp = open(dbfile, 'rb') try: data = loadfunc(fp) + if args.verbose: + print(' Successfully loaded data') + if isinstance(data, dict): + print(' Number of entries: %d' % len(data)) + if 'version' in data: + print(' Database version: %s' % data['version']) # Handle string/bytes conversion if isinstance(data, bytes): data = data.decode('utf-8', 'replace') @@ -105,54 +115,55 @@ def testfile(dbfile): for i, item in enumerate(data): if isinstance(item, bytes): data[i] = item.decode('utf-8', 'replace') + except Exception as e: + if args.verbose: + print(' Error loading file: %s' % str(e)) + raise finally: fp.close() def main(): args = parse_args() - - listnames = args.listnames if args.all: listnames = Utils.list_names() - - listnames = [n.lower().strip() for n in listnames] - if not listnames: - print(C_('Nothing to do.')) - sys.exit(0) + if args.verbose: + print('Checking all lists (%d total)' % len(listnames)) + else: + listnames = args.listnames + if args.verbose: + print('Checking specified lists (%d total)' % len(listnames)) for listname in listnames: - if not Utils.list_exists(listname): - print(C_('No list named:'), listname) + if args.verbose: + print('\nProcessing list: %s' % listname) + listdir = os.path.join(mm_cfg.LIST_DATA_DIR, listname) + if not os.path.exists(listdir): + if args.verbose: + print(' List directory does not exist: %s' % listdir) continue - mlist = MailList(listname, lock=0) - pfile = os.path.join(mlist.fullpath(), 'config.pck') - plast = pfile + '.last' - dfile = os.path.join(mlist.fullpath(), 'config.db') - dlast = dfile + '.last' - if args.verbose: - print(C_('List:'), listname) - - for file in (pfile, plast, dfile, dlast): - status = 0 - try: - testfile(file) - except IOError as e: - # Don't report ENOENT unless we're in verbose mode - if args.verbose or e.errno != errno.ENOENT: - status = e - except Exception as e: - status = e - # Report errors - if status: - if isinstance(status, EnvironmentError): - # This already includes the file name - print(' ', status) - else: - print(' %s: %s' % (file, status)) + # Check all possible database files + dbfiles = [ + os.path.join(listdir, 'config.pck'), + os.path.join(listdir, 'config.pck.last'), + os.path.join(listdir, 'config.db'), + os.path.join(listdir, 'config.db.last'), + os.path.join(listdir, 'config.safety'), + os.path.join(listdir, 'request.pck'), + os.path.join(listdir, 'request.pck.bak'), + ] + + for dbfile in dbfiles: + if os.path.exists(dbfile): + try: + testfile(dbfile) + if args.verbose: + print(' File %s: OK' % os.path.basename(dbfile)) + except Exception as e: + print(' File %s: ERROR - %s' % (os.path.basename(dbfile), str(e))) elif args.verbose: - print(C_(' %(file)s: okay')) + print(' File %s: Not found' % os.path.basename(dbfile)) if __name__ == '__main__': From dc6b310c45a16917e6d5e25e067fd4b7f01de033 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 2 May 2025 15:53:21 -0400 Subject: [PATCH 346/748] update --- bin/check_db | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/bin/check_db b/bin/check_db index 72b00b12..fb54df81 100755 --- a/bin/check_db +++ b/bin/check_db @@ -41,6 +41,7 @@ import errno import argparse import marshal import pickle +import re import paths from Mailman import mm_cfg @@ -126,30 +127,56 @@ def testfile(dbfile): def main(): args = parse_args() if args.all: - listnames = Utils.list_names() - if args.verbose: - print('Checking all lists (%d total)' % len(listnames)) + try: + listnames = Utils.list_names() + if args.verbose: + print('Checking all lists (%d total)' % len(listnames)) + except Exception as e: + print('Error getting list names: %s' % str(e)) + sys.exit(1) else: listnames = args.listnames if args.verbose: print('Checking specified lists (%d total)' % len(listnames)) + # Convert list names to lowercase and strip whitespace + listnames = [n.lower().strip() for n in listnames] + if not listnames: + print('Nothing to do.') + sys.exit(0) + for listname in listnames: if args.verbose: print('\nProcessing list: %s' % listname) + + # Validate list name format + if len(re.sub(mm_cfg.ACCEPTABLE_LISTNAME_CHARACTERS, '', listname)) > 0: + print(' Invalid list name format: %s' % listname) + continue + listdir = os.path.join(mm_cfg.LIST_DATA_DIR, listname) if not os.path.exists(listdir): if args.verbose: print(' List directory does not exist: %s' % listdir) continue - # Check all possible database files - dbfiles = [ + # Check if any of the required files exist + required_files = [ os.path.join(listdir, 'config.pck'), os.path.join(listdir, 'config.pck.last'), os.path.join(listdir, 'config.db'), os.path.join(listdir, 'config.db.last'), os.path.join(listdir, 'config.safety'), + ] + + has_required_files = any(os.path.exists(f) for f in required_files) + if not has_required_files: + if args.verbose: + print(' No configuration files found for list: %s' % listname) + continue + + # Check all possible database files + dbfiles = required_files + [ os.path.join(listdir, 'request.pck'), os.path.join(listdir, 'request.pck.bak'), ] From a3782dfa65af0a97d4521d5603dfbff75be64879 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 2 May 2025 15:56:54 -0400 Subject: [PATCH 347/748] update --- bin/check_db | 132 +++++++++++++++++++++++++-------------------------- 1 file changed, 66 insertions(+), 66 deletions(-) diff --git a/bin/check_db b/bin/check_db index fb54df81..d492b436 100755 --- a/bin/check_db +++ b/bin/check_db @@ -54,8 +54,8 @@ PROGRAM = sys.argv[0] def parse_args(): parser = argparse.ArgumentParser(description='Check a list\'s config database file for integrity.') - parser.add_argument('-a', '--all', action='store_true', - help='Check the databases for all lists') + parser.add_argument('-a', '--all', action='store_true', default=True, + help='Check the databases for all lists (default)') parser.add_argument('-v', '--verbose', action='store_true', help='Verbose output. The state of every tested file is printed') parser.add_argument('listnames', nargs='*', @@ -72,7 +72,7 @@ def load_pickle(fp): return None -def testfile(dbfile): +def testfile(dbfile, verbose=False): # Use our safe loader loadfunc = load_pickle if dbfile.endswith('.db') or dbfile.endswith('.db.last'): @@ -81,14 +81,14 @@ def testfile(dbfile): loadfunc = load_pickle else: assert 0 - if args.verbose: + if verbose: print('Processing file: %s' % dbfile) print(' File type: %s' % ('marshal' if dbfile.endswith('.db') else 'pickle')) print(' File size: %d bytes' % os.path.getsize(dbfile)) fp = open(dbfile, 'rb') try: data = loadfunc(fp) - if args.verbose: + if verbose: print(' Successfully loaded data') if isinstance(data, dict): print(' Number of entries: %d' % len(data)) @@ -117,7 +117,7 @@ def testfile(dbfile): if isinstance(item, bytes): data[i] = item.decode('utf-8', 'replace') except Exception as e: - if args.verbose: + if verbose: print(' Error loading file: %s' % str(e)) raise finally: @@ -126,71 +126,71 @@ def testfile(dbfile): def main(): args = parse_args() - if args.all: - try: + try: + if args.all or not args.listnames: listnames = Utils.list_names() if args.verbose: print('Checking all lists (%d total)' % len(listnames)) - except Exception as e: - print('Error getting list names: %s' % str(e)) - sys.exit(1) - else: - listnames = args.listnames - if args.verbose: - print('Checking specified lists (%d total)' % len(listnames)) - - # Convert list names to lowercase and strip whitespace - listnames = [n.lower().strip() for n in listnames] - if not listnames: - print('Nothing to do.') - sys.exit(0) - - for listname in listnames: - if args.verbose: - print('\nProcessing list: %s' % listname) - - # Validate list name format - if len(re.sub(mm_cfg.ACCEPTABLE_LISTNAME_CHARACTERS, '', listname)) > 0: - print(' Invalid list name format: %s' % listname) - continue - - listdir = os.path.join(mm_cfg.LIST_DATA_DIR, listname) - if not os.path.exists(listdir): + else: + listnames = args.listnames if args.verbose: - print(' List directory does not exist: %s' % listdir) - continue - - # Check if any of the required files exist - required_files = [ - os.path.join(listdir, 'config.pck'), - os.path.join(listdir, 'config.pck.last'), - os.path.join(listdir, 'config.db'), - os.path.join(listdir, 'config.db.last'), - os.path.join(listdir, 'config.safety'), - ] - - has_required_files = any(os.path.exists(f) for f in required_files) - if not has_required_files: + print('Checking specified lists (%d total)' % len(listnames)) + + # Convert list names to lowercase and strip whitespace + listnames = [n.lower().strip() for n in listnames] + if not listnames: + print('No lists found to check.') + sys.exit(0) + + for listname in listnames: if args.verbose: - print(' No configuration files found for list: %s' % listname) - continue - - # Check all possible database files - dbfiles = required_files + [ - os.path.join(listdir, 'request.pck'), - os.path.join(listdir, 'request.pck.bak'), - ] - - for dbfile in dbfiles: - if os.path.exists(dbfile): - try: - testfile(dbfile) - if args.verbose: - print(' File %s: OK' % os.path.basename(dbfile)) - except Exception as e: - print(' File %s: ERROR - %s' % (os.path.basename(dbfile), str(e))) - elif args.verbose: - print(' File %s: Not found' % os.path.basename(dbfile)) + print('\nProcessing list: %s' % listname) + + # Validate list name format + if len(re.sub(mm_cfg.ACCEPTABLE_LISTNAME_CHARACTERS, '', listname)) > 0: + print(' Invalid list name format: %s' % listname) + continue + + listdir = os.path.join(mm_cfg.LIST_DATA_DIR, listname) + if not os.path.exists(listdir): + if args.verbose: + print(' List directory does not exist: %s' % listdir) + continue + + # Check if any of the required files exist + required_files = [ + os.path.join(listdir, 'config.pck'), + os.path.join(listdir, 'config.pck.last'), + os.path.join(listdir, 'config.db'), + os.path.join(listdir, 'config.db.last'), + os.path.join(listdir, 'config.safety'), + ] + + has_required_files = any(os.path.exists(f) for f in required_files) + if not has_required_files: + if args.verbose: + print(' No configuration files found for list: %s' % listname) + continue + + # Check all possible database files + dbfiles = required_files + [ + os.path.join(listdir, 'request.pck'), + os.path.join(listdir, 'request.pck.bak'), + ] + + for dbfile in dbfiles: + if os.path.exists(dbfile): + try: + testfile(dbfile, args.verbose) + if args.verbose: + print(' File %s: OK' % os.path.basename(dbfile)) + except Exception as e: + print(' File %s: ERROR - %s' % (os.path.basename(dbfile), str(e))) + elif args.verbose: + print(' File %s: Not found' % os.path.basename(dbfile)) + except Exception as e: + print('Error getting list names: %s' % str(e)) + sys.exit(1) if __name__ == '__main__': From 9d6e142df4ae9d56824a8cb6f0a2d50977e7a851 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 2 May 2025 16:00:01 -0400 Subject: [PATCH 348/748] update --- bin/check_db | 49 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/bin/check_db b/bin/check_db index d492b436..f1587657 100755 --- a/bin/check_db +++ b/bin/check_db @@ -94,28 +94,49 @@ def testfile(dbfile, verbose=False): print(' Number of entries: %d' % len(data)) if 'version' in data: print(' Database version: %s' % data['version']) - # Handle string/bytes conversion + # Handle string/bytes conversion with better error handling if isinstance(data, bytes): - data = data.decode('utf-8', 'replace') + try: + data = data.decode('utf-8', 'replace') + except Exception as e: + if verbose: + print(' Warning: Error decoding bytes: %s' % str(e)) + data = str(data) elif isinstance(data, dict): new_data = {} for k, v in data.items(): - if isinstance(k, bytes): - k = k.decode('utf-8', 'replace') - if isinstance(v, bytes): - v = v.decode('utf-8', 'replace') - elif isinstance(v, (list, tuple)): - v = list(v) # Convert tuple to list for modification - for i, item in enumerate(v): - if isinstance(item, bytes): - v[i] = item.decode('utf-8', 'replace') - new_data[k] = v + try: + if isinstance(k, bytes): + k = k.decode('utf-8', 'replace') + if isinstance(v, bytes): + v = v.decode('utf-8', 'replace') + elif isinstance(v, (list, tuple)): + v = list(v) + for i, item in enumerate(v): + if isinstance(item, bytes): + try: + v[i] = item.decode('utf-8', 'replace') + except Exception as e: + if verbose: + print(' Warning: Error decoding list item: %s' % str(e)) + v[i] = str(item) + new_data[k] = v + except Exception as e: + if verbose: + print(' Warning: Error processing dictionary item: %s' % str(e)) + new_data[str(k)] = str(v) data = new_data elif isinstance(data, (list, tuple)): - data = list(data) # Convert tuple to list for modification + data = list(data) for i, item in enumerate(data): if isinstance(item, bytes): - data[i] = item.decode('utf-8', 'replace') + try: + data[i] = item.decode('utf-8', 'replace') + except Exception as e: + if verbose: + print(' Warning: Error decoding list item: %s' % str(e)) + data[i] = str(item) + return data except Exception as e: if verbose: print(' Error loading file: %s' % str(e)) From 7558fe816a9d8a9bb8aa52528959be7997c8ab55 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 2 May 2025 16:01:53 -0400 Subject: [PATCH 349/748] update --- bin/check_db | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/bin/check_db b/bin/check_db index f1587657..d2ab3a5e 100755 --- a/bin/check_db +++ b/bin/check_db @@ -72,7 +72,7 @@ def load_pickle(fp): return None -def testfile(dbfile, verbose=False): +def testfile(dbfile, verbose=False, listname=None): # Use our safe loader loadfunc = load_pickle if dbfile.endswith('.db') or dbfile.endswith('.db.last'): @@ -100,7 +100,8 @@ def testfile(dbfile, verbose=False): data = data.decode('utf-8', 'replace') except Exception as e: if verbose: - print(' Warning: Error decoding bytes: %s' % str(e)) + print(' Warning: Error decoding bytes in %s for list %s: %s' % + (os.path.basename(dbfile), listname or 'unknown', str(e))) data = str(data) elif isinstance(data, dict): new_data = {} @@ -118,12 +119,14 @@ def testfile(dbfile, verbose=False): v[i] = item.decode('utf-8', 'replace') except Exception as e: if verbose: - print(' Warning: Error decoding list item: %s' % str(e)) + print(' Warning: Error decoding list item in %s for list %s: %s' % + (os.path.basename(dbfile), listname or 'unknown', str(e))) v[i] = str(item) new_data[k] = v except Exception as e: if verbose: - print(' Warning: Error processing dictionary item: %s' % str(e)) + print(' Warning: Error processing dictionary item in %s for list %s: %s' % + (os.path.basename(dbfile), listname or 'unknown', str(e))) new_data[str(k)] = str(v) data = new_data elif isinstance(data, (list, tuple)): @@ -134,12 +137,14 @@ def testfile(dbfile, verbose=False): data[i] = item.decode('utf-8', 'replace') except Exception as e: if verbose: - print(' Warning: Error decoding list item: %s' % str(e)) + print(' Warning: Error decoding list item in %s for list %s: %s' % + (os.path.basename(dbfile), listname or 'unknown', str(e))) data[i] = str(item) return data except Exception as e: if verbose: - print(' Error loading file: %s' % str(e)) + print(' Error loading file %s for list %s: %s' % + (os.path.basename(dbfile), listname or 'unknown', str(e))) raise finally: fp.close() @@ -202,7 +207,7 @@ def main(): for dbfile in dbfiles: if os.path.exists(dbfile): try: - testfile(dbfile, args.verbose) + testfile(dbfile, args.verbose, listname) if args.verbose: print(' File %s: OK' % os.path.basename(dbfile)) except Exception as e: From 29bb1d655bd5faecc0c0fe6b53797888ec397c6b Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Fri, 2 May 2025 16:03:52 -0400 Subject: [PATCH 350/748] update --- bin/check_db | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/bin/check_db b/bin/check_db index d2ab3a5e..575ba5c2 100755 --- a/bin/check_db +++ b/bin/check_db @@ -94,6 +94,22 @@ def testfile(dbfile, verbose=False, listname=None): print(' Number of entries: %d' % len(data)) if 'version' in data: print(' Database version: %s' % data['version']) + # Add detailed debugging for request.pck files + if dbfile.endswith('request.pck'): + print(' Request data structure:') + for key, value in data.items(): + print(' Key: %s' % key) + print(' Value type: %s' % type(value)) + if isinstance(value, (str, bytes)): + print(' Value length: %d' % len(value)) + if len(str(value)) < 100: # Only print short values + print(' Value: %s' % value) + elif isinstance(value, (list, tuple)): + print(' Number of items: %d' % len(value)) + if len(value) > 0: + print(' First item type: %s' % type(value[0])) + if isinstance(value[0], (str, bytes)) and len(str(value[0])) < 100: + print(' First item: %s' % value[0]) # Handle string/bytes conversion with better error handling if isinstance(data, bytes): try: From 87b9362a531f8bf6ce59178c3263dc5aa821de6c Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 4 May 2025 17:47:54 -0400 Subject: [PATCH 351/748] switchboard str --- Mailman/Queue/Switchboard.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Mailman/Queue/Switchboard.py b/Mailman/Queue/Switchboard.py index a587fd5d..31066b2b 100644 --- a/Mailman/Queue/Switchboard.py +++ b/Mailman/Queue/Switchboard.py @@ -241,6 +241,15 @@ def dequeue(self, filebase): # Use protocol 2 for Python 2/3 compatibility msg = pickle.load(fp, fix_imports=True, encoding='latin1') metadata = pickle.load(fp, fix_imports=True, encoding='latin1') + + # If msg is a string, convert it to a Message object + if isinstance(msg, str): + try: + from email import message_from_string + msg = message_from_string(msg) + except Exception as e: + mailman_log('error', 'Error converting string message to Message object in %s: %s', filename, str(e)) + return None, None except (pickle.UnpicklingError, EOFError) as e: mailman_log('error', 'Error unpickling file %s: %s', filename, str(e)) return None, None From bf8deef8973c4c0752fb3c6b23f60450ff4b8d9a Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 4 May 2025 18:12:28 -0400 Subject: [PATCH 352/748] pickle --- Mailman/Archiver/pipermail.py | 18 +++--- Mailman/ListAdmin.py | 8 +-- Mailman/MailList.py | 2 +- Mailman/Pending.py | 4 +- Mailman/Queue/BounceRunner.py | 10 +-- Mailman/Queue/NewsRunner.py | 10 ++- Mailman/Queue/Switchboard.py | 118 +++++++++++++++++----------------- bin/b4b5-archfix | 2 +- bin/rb-archfix | 2 +- bin/update | 2 +- 10 files changed, 88 insertions(+), 88 deletions(-) diff --git a/Mailman/Archiver/pipermail.py b/Mailman/Archiver/pipermail.py index 7193110c..e94792ec 100644 --- a/Mailman/Archiver/pipermail.py +++ b/Mailman/Archiver/pipermail.py @@ -132,7 +132,7 @@ def store_article(self, article): temp2 = article.html_body article.body = [] del article.html_body - self.articleIndex[article.msgid] = pickle.dumps(article, protocol=2, fix_imports=True) + self.articleIndex[article.msgid] = pickle.dumps(article, protocol=4, fix_imports=True) article.body = temp article.html_body = temp2 @@ -338,14 +338,14 @@ def close(self): f = open(os.path.join(self.basedir, 'pipermail.pck'), 'w') finally: os.umask(omask) - pickle.dump(self.getstate(), f, protocol=2, fix_imports=True) + pickle.dump(self.getstate(), f, protocol=4, fix_imports=True) f.close() def getstate(self): """Get the current state of the archive.""" try: - # Use protocol 2 for Python 2/3 compatibility - protocol = 2 + # Use protocol 4 for Python 2/3 compatibility + protocol = 4 return pickle.dumps(self.__dict__, protocol, fix_imports=True) except Exception as e: mailman_log('error', 'Error getting archive state: %s', e) @@ -354,8 +354,8 @@ def getstate(self): def setstate(self, state): """Set the state of the archive.""" try: - # Use protocol 2 for Python 2/3 compatibility - protocol = 2 + # Use protocol 4 for Python 2/3 compatibility + protocol = 4 self.__dict__ = pickle.loads(state, fix_imports=True, encoding='latin1') except Exception as e: mailman_log('error', 'Error setting archive state: %s', e) @@ -631,9 +631,9 @@ def new_archive(self, archive, archivedir): def add_article(self, article): """Add an article to the archive.""" try: - # Use protocol 2 for Python 2/3 compatibility - protocol = 2 - self.articleIndex[article.msgid] = pickle.dumps(article, protocol=2, fix_imports=True) + # Use protocol 4 for Python 2/3 compatibility + protocol = 4 + self.articleIndex[article.msgid] = pickle.dumps(article, protocol=4, fix_imports=True) self.articleIndex.sync() except Exception as e: mailman_log('error', 'Error adding article %s: %s', article.msgid, e) diff --git a/Mailman/ListAdmin.py b/Mailman/ListAdmin.py index e16369d8..81cbb336 100644 --- a/Mailman/ListAdmin.py +++ b/Mailman/ListAdmin.py @@ -103,7 +103,7 @@ def __opendb(self): try: with open(filename, 'rb') as fp: try: - self.__db = pickle.load(fp) + self.__db = pickle.load(fp, fix_imports=True, encoding='latin1') mailman_log('info', 'Successfully loaded request.pck for list %s', self.internal_name()) # Log pending requests @@ -153,7 +153,7 @@ def __opendb(self): mailman_log('info', 'Attempting to load from backup file') with open(filename_backup, 'rb') as backup_fp: try: - self.__db = pickle.load(backup_fp) + self.__db = pickle.load(backup_fp, fix_imports=True, encoding='latin1') mailman_log('info', 'Successfully loaded backup request.pck for list %s', self.internal_name()) # Successfully loaded backup, restore it as main @@ -212,7 +212,7 @@ def __closedb(self): try: # Create temporary file with open(filename_tmp, 'wb') as fp: - pickle.dump(self.__db, fp, protocol=2) + pickle.dump(self.__db, fp, protocol=4, fix_imports=True) fp.flush() os.fsync(fp.fileno()) @@ -369,7 +369,7 @@ def HoldMessage(self, msg, reason, msgdata={}): fp = open(os.path.join(mm_cfg.DATA_DIR, filename), 'wb') try: if mm_cfg.HOLD_MESSAGES_AS_PICKLES: - pickle.dump(msg, fp, protocol=2, fix_imports=True) + pickle.dump(msg, fp, protocol=4, fix_imports=True) else: g = Generator(fp) g.flatten(msg, 1) diff --git a/Mailman/MailList.py b/Mailman/MailList.py index b1aabb8c..0a14a121 100644 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -645,7 +645,7 @@ def __save(self, data_dict): try: fp = open(fname_tmp, 'wb') # Use a binary format... it's more efficient. - pickle.dump(data_dict, fp, 1) + pickle.dump(data_dict, fp, protocol=4, fix_imports=True) fp.flush() if mm_cfg.SYNC_AFTER_WRITE: os.fsync(fp.fileno()) diff --git a/Mailman/Pending.py b/Mailman/Pending.py index 20bf9df2..7a0b7066 100644 --- a/Mailman/Pending.py +++ b/Mailman/Pending.py @@ -159,8 +159,8 @@ def __save(self, db): os.makedirs(dirname, 0o755) with open(filename_tmp, 'wb') as fp: - # Use protocol 2 for better compatibility - pickle.dump(db, fp, protocol=2, fix_imports=True) + # Use protocol 4 for better compatibility + pickle.dump(db, fp, protocol=4, fix_imports=True) fp.flush() if hasattr(os, 'fsync'): os.fsync(fp.fileno()) diff --git a/Mailman/Queue/BounceRunner.py b/Mailman/Queue/BounceRunner.py index 46d1a9d8..bd4895b2 100644 --- a/Mailman/Queue/BounceRunner.py +++ b/Mailman/Queue/BounceRunner.py @@ -93,9 +93,9 @@ def _queue_bounces(self, listname, addrs, msg): finally: os.umask(omask) for addr in addrs: - # Use protocol 2 for Python 2/3 compatibility and fix_imports for Python 2/3 compatibility + # Use protocol 4 for Python 3 compatibility and fix_imports for Python 2/3 compatibility pickle.dump((listname, addr, today, msg), - self._bounce_events_fp, protocol=2, fix_imports=True) + self._bounce_events_fp, protocol=4, fix_imports=True) self._bounce_events_fp.flush() os.fsync(self._bounce_events_fp.fileno()) self._bouncecnt += len(addrs) @@ -110,10 +110,10 @@ def _register_bounces(self, listname, addr, msg): # Write the bounce data to the pickle file try: - # Use protocol 2 for Python 2/3 compatibility - protocol = 2 + # Use protocol 4 for Python 3 compatibility + protocol = 4 with open(filename, 'wb') as fp: - pickle.dump((listname, addr, now, msg), fp, protocol=2, fix_imports=True) + pickle.dump((listname, addr, now, msg), fp, protocol=4, fix_imports=True) # Set the file's mode appropriately os.chmod(filename, 0o660) except (IOError, OSError) as e: diff --git a/Mailman/Queue/NewsRunner.py b/Mailman/Queue/NewsRunner.py index 771ff465..8d1565d7 100644 --- a/Mailman/Queue/NewsRunner.py +++ b/Mailman/Queue/NewsRunner.py @@ -157,12 +157,10 @@ def _queue_news(self, listname, msg, msgdata): # Write the message and metadata to the pickle file try: - # Use protocol 2 for Python 2/3 compatibility - protocol = 2 - with open(filename, 'wb') as fp: - pickle.dump(listname, fp, protocol=2, fix_imports=True) - pickle.dump(msg, fp, protocol=2, fix_imports=True) - pickle.dump(msgdata, fp, protocol=2, fix_imports=True) + # Use protocol 4 for Python 3 compatibility + pickle.dump(listname, fp, protocol=4, fix_imports=True) + pickle.dump(msg, fp, protocol=4, fix_imports=True) + pickle.dump(msgdata, fp, protocol=4, fix_imports=True) # Set the file's mode appropriately os.chmod(filename, 0o660) except (IOError, OSError) as e: diff --git a/Mailman/Queue/Switchboard.py b/Mailman/Queue/Switchboard.py index 31066b2b..5b2afa16 100644 --- a/Mailman/Queue/Switchboard.py +++ b/Mailman/Queue/Switchboard.py @@ -143,11 +143,11 @@ def enqueue(self, _msg, _metadata={}, **_kws): else: mailman_msg.set_payload(_msg.get_payload()) _msg = mailman_msg - # Use protocol 2 for Python 2/3 compatibility - msgsave = pickle.dumps(_msg, protocol=2, fix_imports=True) + # Use protocol 4 for Python 3 compatibility + msgsave = pickle.dumps(_msg, protocol=4, fix_imports=True) else: - # Use protocol 2 for Python 2/3 compatibility - msgsave = pickle.dumps(str(_msg), protocol=2, fix_imports=True) + # Use protocol 4 for Python 3 compatibility + msgsave = pickle.dumps(str(_msg), protocol=4, fix_imports=True) hashfood = msgsave + listname.encode('utf-8') + repr(now).encode('utf-8') # Encode the current time into the file name for FIFO sorting in # files(). The file name consists of two parts separated by a `+': @@ -165,7 +165,7 @@ def enqueue(self, _msg, _metadata={}, **_kws): del data[k] # We have to tell the dequeue() method whether to parse the message # object or not. - protocol = 2 # Use protocol 2 for Python 2/3 compatibility + protocol = 4 # Use protocol 4 for Python 3 compatibility data['_parsemsg'] = (protocol == 0) # Write to the pickle file the message object and metadata. omask = os.umask(0o007) # -rw-rw---- @@ -173,7 +173,7 @@ def enqueue(self, _msg, _metadata={}, **_kws): fp = open(tmpfile, 'wb') try: fp.write(msgsave) - pickle.dump(data, fp, protocol=2, fix_imports=True) + pickle.dump(data, fp, protocol=4, fix_imports=True) fp.flush() os.fsync(fp.fileno()) finally: @@ -198,61 +198,63 @@ def dequeue(self, filebase): lock_fd = os.open(lockfile, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600) # Write process info to lock file for debugging with os.fdopen(lock_fd, 'w') as f: - f.write('pid=%d\nhost=%s\ntime=%f\n' % ( - os.getpid(), - socket.gethostname(), - time.time() - )) + f.write('PID: %d\n' % os.getpid()) + f.write('Time: %s\n' % time.ctime()) break except OSError as e: - if e.errno == errno.EEXIST: - # Check if lock is stale (older than 5 minutes) - try: - lock_age = time.time() - os.path.getmtime(lockfile) - if lock_age > 300: # 5 minutes - # Read lock file contents for debugging - try: - with open(lockfile, 'r') as f: - lock_info = f.read() - mailman_log('warning', - 'Removing stale lock file %s (age: %d seconds)\nLock info: %s', - lockfile, lock_age, lock_info) - except Exception: - mailman_log('warning', - 'Removing stale lock file %s (age: %d seconds)', - lockfile, lock_age) - os.unlink(lockfile) - continue - except OSError: - pass - # Wait before retrying with exponential backoff - time.sleep(min(1.0 * (2 ** attempt), 10.0)) - attempt += 1 - continue - raise - else: - mailman_log('error', 'Failed to acquire lock for %s after %d attempts', filename, max_attempts) - return None, None - + if e.errno != errno.EEXIST: + raise + attempt += 1 + if attempt >= max_attempts: + mailman_log('error', 'Failed to acquire lock for %s after %d attempts', + filename, max_attempts) + return None, None + time.sleep(0.1) + try: - # Read the message and metadata + # Try to read the file with Python 3 protocol first try: with open(filename, 'rb') as fp: - # Use protocol 2 for Python 2/3 compatibility - msg = pickle.load(fp, fix_imports=True, encoding='latin1') - metadata = pickle.load(fp, fix_imports=True, encoding='latin1') - - # If msg is a string, convert it to a Message object - if isinstance(msg, str): + msgsave = fp.read() + try: + # Try Python 3 protocol first + data = pickle.loads(msgsave, encoding='latin1', fix_imports=True) + if isinstance(data, tuple): + msg, data = data + else: + msg = None + except (pickle.UnpicklingError, ValueError): + # Fall back to Python 2 protocol + data = pickle.loads(msgsave, encoding='latin1', fix_imports=True) + if isinstance(data, tuple): + msg, data = data + else: + msg = None + except (IOError, OSError) as e: + if e.errno != errno.ENOENT: + raise + # Try to recover from .bak file + try: + with open(backfile, 'rb') as fp: + msgsave = fp.read() try: - from email import message_from_string - msg = message_from_string(msg) - except Exception as e: - mailman_log('error', 'Error converting string message to Message object in %s: %s', filename, str(e)) - return None, None - except (pickle.UnpicklingError, EOFError) as e: - mailman_log('error', 'Error unpickling file %s: %s', filename, str(e)) - return None, None + # Try Python 3 protocol first + data = pickle.loads(msgsave, encoding='latin1', fix_imports=True) + if isinstance(data, tuple): + msg, data = data + else: + msg = None + except (pickle.UnpicklingError, ValueError): + # Fall back to Python 2 protocol + data = pickle.loads(msgsave, encoding='latin1', fix_imports=True) + if isinstance(data, tuple): + msg, data = data + else: + msg = None + except (IOError, OSError) as e: + if e.errno != errno.ENOENT: + raise + return None, None # Move to backup file try: @@ -261,7 +263,7 @@ def dequeue(self, filebase): mailman_log('error', 'Error moving %s to %s: %s', filename, backfile, str(e)) return None, None - return msg, metadata + return msg, data finally: # Always clean up the lock file try: @@ -479,7 +481,7 @@ def recover_backup_files(self): protocol = 0 else: protocol = 1 - pickle.dump(data, fp, protocol=2, fix_imports=True) + pickle.dump(data, fp, protocol=4, fix_imports=True) fp.truncate() fp.flush() os.fsync(fp.fileno()) @@ -595,7 +597,7 @@ def _enqueue(self, msg, metadata=None, _recips=None): # Write to temporary file first try: with open(tmpfile, 'wb') as fp: - pickle.dump(data, fp, protocol=2, fix_imports=True) + pickle.dump(data, fp, protocol=4, fix_imports=True) fp.flush() if hasattr(os, 'fsync'): os.fsync(fp.fileno()) diff --git a/bin/b4b5-archfix b/bin/b4b5-archfix index 50e7b819..7b19cd0a 100644 --- a/bin/b4b5-archfix +++ b/bin/b4b5-archfix @@ -68,7 +68,7 @@ def main(): newd = {} for key, pckstr in d.items(): article = pickle.loads(pckstr, fix_imports=True, encoding='latin1') - newd[key] = pickle.dumps(article, protocol=2, fix_imports=True) + newd[key] = pickle.dumps(article, protocol=4, fix_imports=True) fp = open(filename + '.tmp', 'wb') marshal.dump(newd, fp) fp.close() diff --git a/bin/rb-archfix b/bin/rb-archfix index 368143cf..2fcd55e2 100644 --- a/bin/rb-archfix +++ b/bin/rb-archfix @@ -77,7 +77,7 @@ def load_article(pckstr): def save_article(article): """Save an article to a pickle string with Python 2/3 compatibility.""" try: - return pickle.dumps(article, protocol=2, fix_imports=True) + return pickle.dumps(article, protocol=4, fix_imports=True) except Exception as e: print('Error saving article: %s' % e) return None diff --git a/bin/update b/bin/update index ac52acc9..0c09bb87 100755 --- a/bin/update +++ b/bin/update @@ -970,7 +970,7 @@ def upgrade(mlist): backup_path = os.path.join(list_dir, 'config.pck.bak') try: with open(backup_path, 'wb') as fp: - pickle.dump(mlist.__dict__, fp, protocol=2, fix_imports=True) + pickle.dump(mlist.__dict__, fp, protocol=4, fix_imports=True) print(C_('Saved backup configuration to %(path)s') % {'path': backup_path}) except Exception as e: print(C_('Failed to save backup configuration: %(error)s') % {'error': str(e)}) From 0d18b5b18fadf3fa5403fd013cadbfa7d7476b2d Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 4 May 2025 18:16:49 -0400 Subject: [PATCH 353/748] update --- bin/dumpdb | 2 +- bin/msgfmt-python2.py | 2 +- bin/msgfmt.py | 2 +- bin/update | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bin/dumpdb b/bin/dumpdb index f1515972..a71b8ef4 100644 --- a/bin/dumpdb +++ b/bin/dumpdb @@ -1,4 +1,4 @@ -#! /usr/bin/python3 +#! @PYTHON@ # # Copyright (C) 1998-2018 by the Free Software Foundation, Inc. # diff --git a/bin/msgfmt-python2.py b/bin/msgfmt-python2.py index b2939886..960891fd 100644 --- a/bin/msgfmt-python2.py +++ b/bin/msgfmt-python2.py @@ -1,4 +1,4 @@ -#! /usr/bin/env python +#! @PYTHON@ # -*- coding: iso-8859-1 -*- # Written by Martin v. Lwis diff --git a/bin/msgfmt.py b/bin/msgfmt.py index 802dbad3..8a36c96d 100644 --- a/bin/msgfmt.py +++ b/bin/msgfmt.py @@ -1,4 +1,4 @@ -#! /usr/bin/env python +#! @PYTHON@ # -*- coding: iso-8859-1 -*- # Written by Martin v. Loewis diff --git a/bin/update b/bin/update index 0c09bb87..9480dec8 100755 --- a/bin/update +++ b/bin/update @@ -1,5 +1,3 @@ -from __future__ import print_function, absolute_import, division, unicode_literals - #! @PYTHON@ # # Copyright (C) 1998-2018 by the Free Software Foundation, Inc. @@ -39,6 +37,8 @@ Use this script to help you update to the latest release of Mailman from some previous version. It knows about versions back to 1.0b4 (?). """ +from __future__ import print_function, absolute_import, division, unicode_literals + import os import sys import time From d43da9f8592bc83b5ce6d16755f9cd8dd52d7665 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 4 May 2025 18:19:17 -0400 Subject: [PATCH 354/748] update --- Mailman/Message.py | 8 ++++---- Mailman/Utils.py | 6 +++++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Mailman/Message.py b/Mailman/Message.py index e9ad700d..ce70782d 100644 --- a/Mailman/Message.py +++ b/Mailman/Message.py @@ -31,7 +31,7 @@ from email.header import Header from Mailman import mm_cfg -from Mailman import Utils +from Mailman.Utils import GetCharSet, unique_message_id, get_site_email COMMASPACE = ', ' @@ -250,7 +250,7 @@ def __init__(self, recip, sender, subject=None, text=None, lang=None): Message.__init__(self) charset = None if lang is not None: - charset = Charset(Utils.GetCharSet(lang)) + charset = Charset(mm_cfg.GetCharSet(lang)) if text is not None: self.set_payload(text, charset) if subject is None: @@ -274,7 +274,7 @@ def send(self, mlist, noprecedence=False, **_kws): # this message has a Message-ID. Yes, the MTA would give us one, but # this is useful for logging to logs/smtp. if 'message-id' not in self: - self['Message-ID'] = Utils.unique_message_id(mlist) + self['Message-ID'] = mm_cfg.unique_message_id(mlist) # Ditto for Date: which is required by RFC 2822 if 'date' not in self: self['Date'] = email.utils.formatdate(localtime=1) @@ -312,7 +312,7 @@ def __init__(self, mlist, subject=None, text=None, tomoderators=1): recips.extend(mlist.moderator) # We have to set the owner to the site's -bounces address, otherwise # we'll get a mail loop if an owner's address bounces. - sender = Utils.get_site_email(mlist.host_name, 'bounces') + sender = mm_cfg.get_site_email(mlist.host_name, 'bounces') lang = mlist.preferred_language UserNotification.__init__(self, recips, sender, subject, text, lang) # Hack the To header to look like it's going to the -owner address diff --git a/Mailman/Utils.py b/Mailman/Utils.py index 12b2219f..5bd92644 100644 --- a/Mailman/Utils.py +++ b/Mailman/Utils.py @@ -52,7 +52,6 @@ from Mailman import Site from Mailman.SafeDict import SafeDict from Mailman.Logging.Syslog import mailman_log -from Mailman.Message import Message try: import hashlib @@ -768,6 +767,11 @@ def maketext(templatefile, dict=None, raw=0, lang=None, mlist=None): # unsubscribe, etc). The test must be a good guess -- messages that return # true get sent to the list admin instead of the entire list. def is_administrivia(msg): + """Return true if the message is administrative in nature.""" + # Not imported at module scope to avoid import loop + from Mailman.Message import Message + if not isinstance(msg, Message): + return False linecnt = 0 lines = [] for line in email.iterators.body_line_iterator(msg): From 7451f0e4530202204b747e2d6686396766abf765 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 4 May 2025 18:22:34 -0400 Subject: [PATCH 355/748] update --- Mailman/LockFile.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Mailman/LockFile.py b/Mailman/LockFile.py index cb118333..c7bc857d 100644 --- a/Mailman/LockFile.py +++ b/Mailman/LockFile.py @@ -227,6 +227,7 @@ def _transfer_to(self, pid): # Create a hard link from the global lock file to the temp file. This # actually does things in reverse order of normal operation because we # know that lockfile exists, and tmpfname better not! + mailman_log('debug', 'Attempting to transfer lock from %s to %s', winner, self.__tmpfname) os.link(self.__lockfile, self.__tmpfname) # Now update the lock file to contain a reference to the new owner self.__write() @@ -238,12 +239,14 @@ def _transfer_to(self, pid): # And do some sanity checks link_count = self.__linkcount() if link_count != 2: - mailman_log('error', 'Lock transfer failed: link count is %d, expected 2', link_count) + mailman_log('error', 'Lock transfer failed: link count is %d, expected 2 for lockfile %s (temp file: %s)', + link_count, self.__lockfile, self.__tmpfname) raise LockError('Lock transfer failed: link count is %d, expected 2' % link_count) if not self.locked(): - mailman_log('error', 'Lock transfer failed: lock not acquired') + mailman_log('error', 'Lock transfer failed: lock not acquired for lockfile %s (temp file: %s)', + self.__lockfile, self.__tmpfname) raise LockError('Lock transfer failed: lock not acquired') - mailman_log('debug', 'transferred the lock') + mailman_log('debug', 'Successfully transferred lock from %s to %s', winner, self.__tmpfname) def _take_possession(self): """Try to take possession of the lock file. From 4430bdc4cf86eaa77212aaa26c971685d032b8fa Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 4 May 2025 18:30:55 -0400 Subject: [PATCH 356/748] improve handling --- Mailman/Handlers/ToOutgoing.py | 26 ++++++++++++--- Mailman/Queue/IncomingRunner.py | 30 ++++++++++++----- Mailman/Queue/OutgoingRunner.py | 58 +++++++++++++++++++++++++++++++++ 3 files changed, 100 insertions(+), 14 deletions(-) diff --git a/Mailman/Handlers/ToOutgoing.py b/Mailman/Handlers/ToOutgoing.py index d4f13fd5..b9ffa3ad 100644 --- a/Mailman/Handlers/ToOutgoing.py +++ b/Mailman/Handlers/ToOutgoing.py @@ -23,10 +23,14 @@ from Mailman import mm_cfg from Mailman.Queue.sbcache import get_switchboard +import traceback - def process(mlist, msg, msgdata): + msgid = msg.get('message-id', 'n/a') + mailman_log('info', 'ToOutgoing: Starting to process message %s for list %s', + msgid, mlist.internal_name()) + interval = mm_cfg.VERP_DELIVERY_INTERVAL # Should we VERP this message? If personalization is enabled for this # list and VERP_PERSONALIZED_DELIVERIES is true, then yes we VERP it. @@ -37,19 +41,31 @@ def process(mlist, msg, msgdata): # Note that the verp flag may already be set, e.g. by mailpasswds using # VERP_PASSWORD_REMINDERS. Preserve any existing verp flag. if 'verp' in msgdata: - pass + mailman_log('debug', 'ToOutgoing: Using existing VERP flag for message %s', msgid) elif mlist.personalize: if mm_cfg.VERP_PERSONALIZED_DELIVERIES: msgdata['verp'] = 1 + mailman_log('debug', 'ToOutgoing: Setting VERP flag for personalized message %s', msgid) elif interval == 0: # Never VERP - pass + mailman_log('debug', 'ToOutgoing: VERP disabled for message %s', msgid) elif interval == 1: # VERP every time msgdata['verp'] = 1 + mailman_log('debug', 'ToOutgoing: Setting VERP flag for message %s (interval=1)', msgid) else: # VERP every `inteval' number of times msgdata['verp'] = not int(mlist.post_id) % interval + mailman_log('debug', 'ToOutgoing: Setting VERP flag for message %s based on interval %d', + msgid, interval) + # And now drop the message in qfiles/out - outq = get_switchboard(mm_cfg.OUTQUEUE_DIR) - outq.enqueue(msg, msgdata, listname=mlist.internal_name()) + try: + outq = get_switchboard(mm_cfg.OUTQUEUE_DIR) + mailman_log('debug', 'ToOutgoing: Enqueueing message %s to outgoing queue', msgid) + outq.enqueue(msg, msgdata, listname=mlist.internal_name()) + mailman_log('info', 'ToOutgoing: Successfully enqueued message %s to outgoing queue', msgid) + except Exception as e: + mailman_log('error', 'ToOutgoing: Failed to enqueue message %s: %s', msgid, str(e)) + mailman_log('error', 'ToOutgoing: Traceback:\n%s', traceback.format_exc()) + raise diff --git a/Mailman/Queue/IncomingRunner.py b/Mailman/Queue/IncomingRunner.py index 5b00dd3d..cbf1ecdc 100644 --- a/Mailman/Queue/IncomingRunner.py +++ b/Mailman/Queue/IncomingRunner.py @@ -234,11 +234,15 @@ def _dopipeline(self, mlist, msg, msgdata, pipeline): mailman_log('error', 'Message object missing required method %s', method) return 0 + msgid = msg.get('message-id', 'n/a') while pipeline: handler = pipeline.pop(0) modname = 'Mailman.Handlers.' + handler - __import__(modname) try: + mailman_log('debug', 'IncomingRunner: Processing message %s through handler %s', + msgid, handler) + __import__(modname) + # Store original PID and track child processes original_pid = os.getpid() child_pids = set() @@ -271,6 +275,9 @@ def _dopipeline(self, mlist, msg, msgdata, pipeline): if child_pids: mailman_log('debug', 'Cleaned up %d child processes from handler %s: %s', len(child_pids), modname, child_pids) + + mailman_log('debug', 'IncomingRunner: Successfully processed message %s through handler %s', + msgid, handler) except Errors.DiscardMessage: # Throw the message away; we need do nothing else with it. @@ -278,16 +285,14 @@ def _dopipeline(self, mlist, msg, msgdata, pipeline): mailman_log('vette', """Message discarded, msgid: %s' list: %s, handler: %s""", - msg.get('message-id', 'n/a'), - mlist.internal_name(), handler) + msgid, mlist.internal_name(), handler) return 0 except Errors.HoldMessage: # Message is being held for moderation, no need to requeue mailman_log('vette', """Message held for moderation, msgid: %s list: %s, handler: %s""", - msg.get('message-id', 'n/a'), - mlist.internal_name(), handler) + msgid, mlist.internal_name(), handler) # Add message ID to processed set to prevent duplicate processing self._processed_messages.add(msgid) return 0 @@ -297,15 +302,22 @@ def _dopipeline(self, mlist, msg, msgdata, pipeline): list: %s, handler: %s, reason: %s""", - msg.get('message-id', 'n/a'), - mlist.internal_name(), handler, e.notice()) + msgid, mlist.internal_name(), handler, e.notice()) mlist.BounceMessage(msg, msgdata, e) return 0 except Exception as e: # Log the full traceback for debugging - mailman_log('error', 'Error in handler %s: %s\n%s', modname, str(e), - ''.join(traceback.format_exc())) + mailman_log('error', 'Error in handler %s for message %s: %s\n%s', + modname, msgid, str(e), traceback.format_exc()) + # Put the handler back in the pipeline pipeline.insert(0, handler) + # Try to move message to shunt queue + try: + self._shunt.enqueue(msg, msgdata) + mailman_log('info', 'Moved failed message %s to shunt queue', msgid) + except Exception as shunt_error: + mailman_log('error', 'Failed to move message %s to shunt queue: %s', + msgid, str(shunt_error)) raise return 0 diff --git a/Mailman/Queue/OutgoingRunner.py b/Mailman/Queue/OutgoingRunner.py index 1d3e80f6..afd83ee7 100644 --- a/Mailman/Queue/OutgoingRunner.py +++ b/Mailman/Queue/OutgoingRunner.py @@ -26,6 +26,7 @@ import sys from io import StringIO import threading +import email.message from Mailman import mm_cfg from Mailman import Utils @@ -186,3 +187,60 @@ def _cleanup(self): mailman_log('debug', 'OutgoingRunner: Cleanup complete') _doperiodic = BounceMixin._doperiodic + + def _validate_message(self, msg, msgdata): + """Validate and convert message if needed. + + Returns a tuple of (msg, success) where success is a boolean indicating + if validation was successful. + """ + msgid = msg.get('message-id', 'n/a') + try: + # Convert email.message.Message to Mailman.Message if needed + if isinstance(msg, email.message.Message) and not isinstance(msg, Message): + mailman_log('debug', 'OutgoingRunner: Converting email.message.Message to Mailman.Message for %s', msgid) + mailman_msg = Message() + # Copy all attributes from the original message + for key, value in msg.items(): + mailman_msg[key] = value + # Copy the payload + if msg.is_multipart(): + for part in msg.get_payload(): + mailman_msg.attach(part) + else: + mailman_msg.set_payload(msg.get_payload()) + msg = mailman_msg + mailman_log('debug', 'OutgoingRunner: Successfully converted message %s', msgid) + + # Validate required Mailman.Message methods + required_methods = ['get_sender', 'get', 'items', 'is_multipart', 'get_payload'] + missing_methods = [] + for method in required_methods: + if not hasattr(msg, method): + missing_methods.append(method) + + if missing_methods: + mailman_log('error', 'OutgoingRunner: Message %s missing required methods: %s', + msgid, ', '.join(missing_methods)) + return msg, False + + # Validate message headers + if not msg.get('message-id'): + mailman_log('error', 'OutgoingRunner: Message %s missing Message-ID header', msgid) + return msg, False + + if not msg.get('from'): + mailman_log('error', 'OutgoingRunner: Message %s missing From header', msgid) + return msg, False + + if not msg.get('to') and not msg.get('recipients'): + mailman_log('error', 'OutgoingRunner: Message %s missing To/Recipients', msgid) + return msg, False + + mailman_log('debug', 'OutgoingRunner: Message %s validation successful', msgid) + return msg, True + + except Exception as e: + mailman_log('error', 'OutgoingRunner: Error validating message %s: %s', msgid, str(e)) + mailman_log('error', 'OutgoingRunner: Traceback:\n%s', traceback.format_exc()) + return msg, False From 2bf5637f81107032759e0e742cd1b9a35ee232d6 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 4 May 2025 18:35:51 -0400 Subject: [PATCH 357/748] improve handling --- Mailman/Handlers/ToOutgoing.py | 8 ++++++++ Mailman/Queue/IncomingRunner.py | 18 ++++++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/Mailman/Handlers/ToOutgoing.py b/Mailman/Handlers/ToOutgoing.py index b9ffa3ad..ae50ed7f 100644 --- a/Mailman/Handlers/ToOutgoing.py +++ b/Mailman/Handlers/ToOutgoing.py @@ -31,6 +31,14 @@ def process(mlist, msg, msgdata): mailman_log('info', 'ToOutgoing: Starting to process message %s for list %s', msgid, mlist.internal_name()) + # Log message details for debugging + mailman_log('debug', 'ToOutgoing: Message details for %s:', msgid) + mailman_log('debug', ' From: %s', msg.get('from', 'unknown')) + mailman_log('debug', ' To: %s', msg.get('to', 'unknown')) + mailman_log('debug', ' Subject: %s', msg.get('subject', '(no subject)')) + mailman_log('debug', ' Message type: %s', type(msg).__name__) + mailman_log('debug', ' Message data: %s', str(msgdata)) + interval = mm_cfg.VERP_DELIVERY_INTERVAL # Should we VERP this message? If personalization is enabled for this # list and VERP_PERSONALIZED_DELIVERIES is true, then yes we VERP it. diff --git a/Mailman/Queue/IncomingRunner.py b/Mailman/Queue/IncomingRunner.py index cbf1ecdc..5bec0f8a 100644 --- a/Mailman/Queue/IncomingRunner.py +++ b/Mailman/Queue/IncomingRunner.py @@ -142,8 +142,13 @@ def _dispose(self, listname, msg, msgdata): msgid = msg.get('message-id', 'n/a') filebase = msgdata.get('_filebase', 'unknown') + mailman_log('debug', 'IncomingRunner._dispose: Starting to process message %s (file: %s) for list %s', + msgid, filebase, listname) + # Check retry delay and duplicate processing if not self._check_retry_delay(msgid, filebase): + mailman_log('debug', 'IncomingRunner._dispose: Message %s failed retry delay check, moving to shunt queue', + msgid) # Move to shunt queue and remove from original queue self._shunt.enqueue(msg, msgdata) # Get the filebase from msgdata and finish processing it @@ -154,6 +159,7 @@ def _dispose(self, listname, msg, msgdata): # Get the MailList object for the list name try: mlist = MailList(listname, lock=False) + mailman_log('debug', 'IncomingRunner._dispose: Successfully got MailList object for %s', listname) except Errors.MMListError as e: mailman_log('error', 'Failed to get list %s: %s', listname, str(e)) self._unmark_message_processed(msgid) @@ -164,8 +170,13 @@ def _dispose(self, listname, msg, msgdata): mailman_log('info', 'IncomingRunner: Starting to process message %s (file: %s) for list %s', msgid, filebase, mlist.internal_name()) + # Get the pipeline + pipeline = self._get_pipeline(mlist, msg, msgdata) + mailman_log('debug', 'IncomingRunner._dispose: Got pipeline for message %s: %s', + msgid, str(pipeline)) + # Process the message through the pipeline - result = self._dopipeline(mlist, msg, msgdata, self._get_pipeline(mlist, msg, msgdata)) + result = self._dopipeline(mlist, msg, msgdata, pipeline) # Log successful completion mailman_log('info', 'IncomingRunner: Successfully processed message %s (file: %s) for list %s', @@ -191,9 +202,12 @@ def _dispose(self, listname, msg, msgdata): def _get_pipeline(self, mlist, msg, msgdata): # We must return a copy of the list, otherwise, the first message that # flows through the pipeline will empty it out! - return msgdata.get('pipeline', + pipeline = msgdata.get('pipeline', getattr(mlist, 'pipeline', mm_cfg.GLOBAL_PIPELINE))[:] + mailman_log('debug', 'IncomingRunner._get_pipeline: Got pipeline for message %s: %s', + msg.get('message-id', 'n/a'), str(pipeline)) + return pipeline def _dopipeline(self, mlist, msg, msgdata, pipeline): # Validate pipeline state From a4bdff5b29e5d58796d7475d8634a7d9c965a0ed Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 4 May 2025 18:41:32 -0400 Subject: [PATCH 358/748] update --- Mailman/Queue/IncomingRunner.py | 90 +++++++++++++++++++-------------- 1 file changed, 53 insertions(+), 37 deletions(-) diff --git a/Mailman/Queue/IncomingRunner.py b/Mailman/Queue/IncomingRunner.py index 5bec0f8a..55d80c71 100644 --- a/Mailman/Queue/IncomingRunner.py +++ b/Mailman/Queue/IncomingRunner.py @@ -127,12 +127,14 @@ class IncomingRunner(Runner): QDIR = mm_cfg.INQUEUE_DIR def __init__(self, slice=None, numslices=1): + mailman_log('qrunner', 'IncomingRunner: Initializing with slice=%s, numslices=%s', slice, numslices) Runner.__init__(self, slice, numslices) # Track processed messages to prevent duplicates self._processed_messages = set() # Clean up old messages periodically self._last_cleanup = time.time() self._cleanup_interval = 3600 # Clean up every hour + mailman_log('qrunner', 'IncomingRunner: Initialization complete') def _dispose(self, listname, msg, msgdata): # Import MailList here to avoid circular imports @@ -142,12 +144,12 @@ def _dispose(self, listname, msg, msgdata): msgid = msg.get('message-id', 'n/a') filebase = msgdata.get('_filebase', 'unknown') - mailman_log('debug', 'IncomingRunner._dispose: Starting to process message %s (file: %s) for list %s', + mailman_log('qrunner', 'IncomingRunner._dispose: Starting to process message %s (file: %s) for list %s', msgid, filebase, listname) # Check retry delay and duplicate processing if not self._check_retry_delay(msgid, filebase): - mailman_log('debug', 'IncomingRunner._dispose: Message %s failed retry delay check, moving to shunt queue', + mailman_log('qrunner', 'IncomingRunner._dispose: Message %s failed retry delay check, moving to shunt queue', msgid) # Move to shunt queue and remove from original queue self._shunt.enqueue(msg, msgdata) @@ -159,41 +161,41 @@ def _dispose(self, listname, msg, msgdata): # Get the MailList object for the list name try: mlist = MailList(listname, lock=False) - mailman_log('debug', 'IncomingRunner._dispose: Successfully got MailList object for %s', listname) + mailman_log('qrunner', 'IncomingRunner._dispose: Successfully got MailList object for %s', listname) except Errors.MMListError as e: - mailman_log('error', 'Failed to get list %s: %s', listname, str(e)) + mailman_log('qrunner', 'IncomingRunner._dispose: Failed to get list %s: %s', listname, str(e)) self._unmark_message_processed(msgid) return 0 try: # Log start of processing - mailman_log('info', 'IncomingRunner: Starting to process message %s (file: %s) for list %s', + mailman_log('qrunner', 'IncomingRunner: Starting to process message %s (file: %s) for list %s', msgid, filebase, mlist.internal_name()) # Get the pipeline pipeline = self._get_pipeline(mlist, msg, msgdata) - mailman_log('debug', 'IncomingRunner._dispose: Got pipeline for message %s: %s', + mailman_log('qrunner', 'IncomingRunner._dispose: Got pipeline for message %s: %s', msgid, str(pipeline)) # Process the message through the pipeline result = self._dopipeline(mlist, msg, msgdata, pipeline) # Log successful completion - mailman_log('info', 'IncomingRunner: Successfully processed message %s (file: %s) for list %s', + mailman_log('qrunner', 'IncomingRunner: Successfully processed message %s (file: %s) for list %s', msgid, filebase, mlist.internal_name()) return result except Exception as e: # Enhanced error logging with more context - mailman_log('error', 'Error processing incoming message %s for list %s: %s', + mailman_log('qrunner', 'IncomingRunner._dispose: Error processing message %s for list %s: %s', msgid, mlist.internal_name(), str(e)) - mailman_log('error', 'Message details:') - mailman_log('error', ' Message ID: %s', msgid) - mailman_log('error', ' From: %s', msg.get('from', 'unknown')) - mailman_log('error', ' To: %s', msg.get('to', 'unknown')) - mailman_log('error', ' Subject: %s', msg.get('subject', '(no subject)')) - mailman_log('error', ' Message type: %s', type(msg).__name__) - mailman_log('error', ' Message data: %s', str(msgdata)) - mailman_log('error', 'Traceback:\n%s', traceback.format_exc()) + mailman_log('qrunner', 'IncomingRunner._dispose: Message details:') + mailman_log('qrunner', ' Message ID: %s', msgid) + mailman_log('qrunner', ' From: %s', msg.get('from', 'unknown')) + mailman_log('qrunner', ' To: %s', msg.get('to', 'unknown')) + mailman_log('qrunner', ' Subject: %s', msg.get('subject', '(no subject)')) + mailman_log('qrunner', ' Message type: %s', type(msg).__name__) + mailman_log('qrunner', ' Message data: %s', str(msgdata)) + mailman_log('qrunner', 'IncomingRunner._dispose: Traceback:\n%s', traceback.format_exc()) # Remove from processed messages on error self._unmark_message_processed(msgid) @@ -205,23 +207,26 @@ def _get_pipeline(self, mlist, msg, msgdata): pipeline = msgdata.get('pipeline', getattr(mlist, 'pipeline', mm_cfg.GLOBAL_PIPELINE))[:] - mailman_log('debug', 'IncomingRunner._get_pipeline: Got pipeline for message %s: %s', + mailman_log('qrunner', 'IncomingRunner._get_pipeline: Got pipeline for message %s: %s', msg.get('message-id', 'n/a'), str(pipeline)) return pipeline def _dopipeline(self, mlist, msg, msgdata, pipeline): + msgid = msg.get('message-id', 'n/a') + mailman_log('qrunner', 'IncomingRunner._dopipeline: Starting pipeline processing for message %s', msgid) + # Validate pipeline state if not pipeline: - mailman_log('error', 'Empty pipeline for message %s', msg.get('message-id', 'n/a')) + mailman_log('qrunner', 'IncomingRunner._dopipeline: Empty pipeline for message %s', msgid) return 0 if 'pipeline' in msgdata and msgdata['pipeline'] != pipeline: - mailman_log('error', 'Pipeline state mismatch for message %s', msg.get('message-id', 'n/a')) + mailman_log('qrunner', 'IncomingRunner._dopipeline: Pipeline state mismatch for message %s', msgid) return 0 # Ensure message is a Mailman.Message if not isinstance(msg, Message): try: - mailman_log('info', 'Converting email.message.Message to Mailman.Message') + mailman_log('qrunner', 'IncomingRunner._dopipeline: Converting email.message.Message to Mailman.Message for %s', msgid) mailman_msg = Message() # Copy all attributes from the original message for key, value in msg.items(): @@ -236,8 +241,9 @@ def _dopipeline(self, mlist, msg, msgdata, pipeline): # Update msgdata references if needed if 'msg' in msgdata: msgdata['msg'] = msg + mailman_log('qrunner', 'IncomingRunner._dopipeline: Successfully converted message %s', msgid) except Exception as e: - mailman_log('error', 'Failed to convert message to Mailman.Message: %s\nTraceback:\n%s', + mailman_log('qrunner', 'IncomingRunner._dopipeline: Failed to convert message to Mailman.Message: %s\nTraceback:\n%s', str(e), traceback.format_exc()) return 0 @@ -245,15 +251,14 @@ def _dopipeline(self, mlist, msg, msgdata, pipeline): required_methods = ['get_sender', 'get', 'items', 'is_multipart', 'get_payload'] for method in required_methods: if not hasattr(msg, method): - mailman_log('error', 'Message object missing required method %s', method) + mailman_log('qrunner', 'IncomingRunner._dopipeline: Message object missing required method %s', method) return 0 - msgid = msg.get('message-id', 'n/a') while pipeline: handler = pipeline.pop(0) modname = 'Mailman.Handlers.' + handler try: - mailman_log('debug', 'IncomingRunner: Processing message %s through handler %s', + mailman_log('qrunner', 'IncomingRunner._dopipeline: Processing message %s through handler %s', msgid, handler) __import__(modname) @@ -267,7 +272,7 @@ def _dopipeline(self, mlist, msg, msgdata, pipeline): # Check for process leaks current_pid = os.getpid() if current_pid != original_pid: - mailman_log('error', 'Child process leaked through in handler %s: original_pid=%d, current_pid=%d', + mailman_log('qrunner', 'IncomingRunner._dopipeline: Child process leaked through in handler %s: original_pid=%d, current_pid=%d', modname, original_pid, current_pid) # Try to clean up any child processes try: @@ -287,23 +292,23 @@ def _dopipeline(self, mlist, msg, msgdata, pipeline): pass if child_pids: - mailman_log('debug', 'Cleaned up %d child processes from handler %s: %s', + mailman_log('qrunner', 'IncomingRunner._dopipeline: Cleaned up %d child processes from handler %s: %s', len(child_pids), modname, child_pids) - mailman_log('debug', 'IncomingRunner: Successfully processed message %s through handler %s', + mailman_log('qrunner', 'IncomingRunner._dopipeline: Successfully processed message %s through handler %s', msgid, handler) except Errors.DiscardMessage: # Throw the message away; we need do nothing else with it. pipeline.insert(0, handler) - mailman_log('vette', """Message discarded, msgid: %s' + mailman_log('qrunner', """IncomingRunner._dopipeline: Message discarded, msgid: %s list: %s, handler: %s""", msgid, mlist.internal_name(), handler) return 0 except Errors.HoldMessage: # Message is being held for moderation, no need to requeue - mailman_log('vette', """Message held for moderation, msgid: %s + mailman_log('qrunner', """IncomingRunner._dopipeline: Message held for moderation, msgid: %s list: %s, handler: %s""", msgid, mlist.internal_name(), handler) @@ -312,7 +317,7 @@ def _dopipeline(self, mlist, msg, msgdata, pipeline): return 0 except Errors.RejectMessage as e: pipeline.insert(0, handler) - mailman_log('vette', """Message rejected, msgid: %s + mailman_log('qrunner', """IncomingRunner._dopipeline: Message rejected, msgid: %s list: %s, handler: %s, reason: %s""", @@ -321,22 +326,23 @@ def _dopipeline(self, mlist, msg, msgdata, pipeline): return 0 except Exception as e: # Log the full traceback for debugging - mailman_log('error', 'Error in handler %s for message %s: %s\n%s', + mailman_log('qrunner', 'IncomingRunner._dopipeline: Error in handler %s for message %s: %s\n%s', modname, msgid, str(e), traceback.format_exc()) # Put the handler back in the pipeline pipeline.insert(0, handler) # Try to move message to shunt queue try: self._shunt.enqueue(msg, msgdata) - mailman_log('info', 'Moved failed message %s to shunt queue', msgid) + mailman_log('qrunner', 'IncomingRunner._dopipeline: Moved failed message %s to shunt queue', msgid) except Exception as shunt_error: - mailman_log('error', 'Failed to move message %s to shunt queue: %s', + mailman_log('qrunner', 'IncomingRunner._dopipeline: Failed to move message %s to shunt queue: %s', msgid, str(shunt_error)) raise return 0 def _cleanup(self): """Clean up any resources used by the pipeline.""" + mailman_log('qrunner', 'IncomingRunner._cleanup: Starting cleanup') # Clean up child processes reap(self._kids, once=True) # Close any open file descriptors @@ -345,24 +351,29 @@ def _cleanup(self): os.close(fd) except OSError: pass + mailman_log('qrunner', 'IncomingRunner._cleanup: Cleanup complete') def _oneloop(self): + mailman_log('qrunner', 'IncomingRunner._oneloop: Starting loop') # First, list all the files in our queue directory. # Switchboard.files() is guaranteed to hand us the files in FIFO # order. Return an integer count of the number of files that were # available for this qrunner to process. files = self._switchboard.files() + mailman_log('qrunner', 'IncomingRunner._oneloop: Found %d files to process', len(files)) + for filebase in files: try: # Log that we're starting to process this file - mailman_log('incoming', 'Starting to process queue file: %s', filebase) + mailman_log('qrunner', 'IncomingRunner._oneloop: Starting to process queue file: %s', filebase) # Ask the switchboard for the message and metadata objects # associated with this filebase. try: msg, msgdata = self._switchboard.dequeue(filebase) + mailman_log('qrunner', 'IncomingRunner._oneloop: Successfully dequeued file %s', filebase) except Exception as e: - mailman_log('error', 'Failed to dequeue file %s: %s', filebase, str(e)) + mailman_log('qrunner', 'IncomingRunner._oneloop: Failed to dequeue file %s: %s', filebase, str(e)) continue # Process the message @@ -370,18 +381,23 @@ def _oneloop(self): if more: # The message needs more processing, so enqueue it at the # end of the self._switchboard's queue. + mailman_log('qrunner', 'IncomingRunner._oneloop: Message needs more processing, requeuing %s', filebase) self._switchboard.enqueue(msg, msgdata) else: # The message is done being processed by this qrunner, so # shunt it off to the next queue. + mailman_log('qrunner', 'IncomingRunner._oneloop: Message processing complete, moving to shunt queue %s', filebase) self._shunt.enqueue(msg, msgdata) except Exception as e: # Log the error and requeue the message for later processing - mailman_log('error', 'Error processing queue file %s: %s', filebase, str(e)) + mailman_log('qrunner', 'IncomingRunner._oneloop: Error processing queue file %s: %s', filebase, str(e)) if msg is not None and msgdata is not None: try: self._switchboard.enqueue(msg, msgdata) + mailman_log('qrunner', 'IncomingRunner._oneloop: Successfully requeued file %s', filebase) except Exception as e2: - mailman_log('error', 'Failed to requeue file %s: %s', filebase, str(e2)) + mailman_log('qrunner', 'IncomingRunner._oneloop: Failed to requeue file %s: %s', filebase, str(e2)) + + mailman_log('qrunner', 'IncomingRunner._oneloop: Loop complete, processed %d files', len(files)) return len(files) From c9be56f32174ab8d6c2b83a4ef68dfa60c6ea05a Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 4 May 2025 18:47:24 -0400 Subject: [PATCH 359/748] update --- Mailman/Queue/IncomingRunner.py | 49 +++++++++++++++++++++++++++++++++ Mailman/Queue/Switchboard.py | 37 +++++++++++++++++++++++-- 2 files changed, 84 insertions(+), 2 deletions(-) diff --git a/Mailman/Queue/IncomingRunner.py b/Mailman/Queue/IncomingRunner.py index 55d80c71..306b5f51 100644 --- a/Mailman/Queue/IncomingRunner.py +++ b/Mailman/Queue/IncomingRunner.py @@ -371,7 +371,56 @@ def _oneloop(self): # associated with this filebase. try: msg, msgdata = self._switchboard.dequeue(filebase) + if msg is None or msgdata is None: + mailman_log('qrunner', 'IncomingRunner._oneloop: Failed to dequeue file %s - invalid message data', filebase) + # Move to shunt queue + try: + src = os.path.join(self._switchboard.whichq(), filebase + '.bak') + dst = os.path.join(mm_cfg.BADQUEUE_DIR, filebase + '.psv') + if not os.path.exists(mm_cfg.BADQUEUE_DIR): + os.makedirs(mm_cfg.BADQUEUE_DIR, 0o770) + os.rename(src, dst) + mailman_log('qrunner', 'IncomingRunner._oneloop: Moved invalid file to shunt queue: %s -> %s', filebase, dst) + except Exception as e: + mailman_log('qrunner', 'IncomingRunner._oneloop: Failed to move invalid file to shunt queue: %s', str(e)) + continue + mailman_log('qrunner', 'IncomingRunner._oneloop: Successfully dequeued file %s', filebase) + + # Validate message data structure + if not isinstance(msgdata, dict): + mailman_log('qrunner', 'IncomingRunner._oneloop: Invalid message data structure for file %s: expected dict, got %s', + filebase, type(msgdata)) + # Move to shunt queue + try: + src = os.path.join(self._switchboard.whichq(), filebase + '.bak') + dst = os.path.join(mm_cfg.BADQUEUE_DIR, filebase + '.psv') + if not os.path.exists(mm_cfg.BADQUEUE_DIR): + os.makedirs(mm_cfg.BADQUEUE_DIR, 0o770) + os.rename(src, dst) + mailman_log('qrunner', 'IncomingRunner._oneloop: Moved invalid file to shunt queue: %s -> %s', filebase, dst) + except Exception as e: + mailman_log('qrunner', 'IncomingRunner._oneloop: Failed to move invalid file to shunt queue: %s', str(e)) + continue + + # Validate required message data fields + required_fields = ['listname'] + missing_fields = [field for field in required_fields if field not in msgdata] + if missing_fields: + mailman_log('qrunner', 'IncomingRunner._oneloop: Missing required fields in message data for file %s: %s', + filebase, ', '.join(missing_fields)) + # Move to shunt queue + try: + src = os.path.join(self._switchboard.whichq(), filebase + '.bak') + dst = os.path.join(mm_cfg.BADQUEUE_DIR, filebase + '.psv') + if not os.path.exists(mm_cfg.BADQUEUE_DIR): + os.makedirs(mm_cfg.BADQUEUE_DIR, 0o770) + os.rename(src, dst) + mailman_log('qrunner', 'IncomingRunner._oneloop: Moved invalid file to shunt queue: %s -> %s', filebase, dst) + except Exception as e: + mailman_log('qrunner', 'IncomingRunner._oneloop: Failed to move invalid file to shunt queue: %s', str(e)) + continue + except Exception as e: mailman_log('qrunner', 'IncomingRunner._oneloop: Failed to dequeue file %s: %s', filebase, str(e)) continue diff --git a/Mailman/Queue/Switchboard.py b/Mailman/Queue/Switchboard.py index 5b2afa16..ccf2ffc1 100644 --- a/Mailman/Queue/Switchboard.py +++ b/Mailman/Queue/Switchboard.py @@ -218,18 +218,32 @@ def dequeue(self, filebase): msgsave = fp.read() try: # Try Python 3 protocol first + mailman_log('debug', 'Attempting to unpickle data from %s using Python 3 protocol', filename) data = pickle.loads(msgsave, encoding='latin1', fix_imports=True) + mailman_log('debug', 'Successfully unpickled data from %s, type: %s', filename, type(data)) + if isinstance(data, tuple): msg, data = data + mailman_log('debug', 'Unpickled tuple with msg type: %s, data type: %s', + type(msg), type(data)) else: msg = None - except (pickle.UnpicklingError, ValueError): + mailman_log('debug', 'Unpickled non-tuple data: %s', type(data)) + + except (pickle.UnpicklingError, ValueError) as e: + mailman_log('debug', 'Python 3 protocol failed, trying Python 2 protocol: %s', str(e)) # Fall back to Python 2 protocol data = pickle.loads(msgsave, encoding='latin1', fix_imports=True) + mailman_log('debug', 'Successfully unpickled data using Python 2 protocol, type: %s', type(data)) + if isinstance(data, tuple): msg, data = data + mailman_log('debug', 'Unpickled tuple with msg type: %s, data type: %s', + type(msg), type(data)) else: msg = None + mailman_log('debug', 'Unpickled non-tuple data: %s', type(data)) + except (IOError, OSError) as e: if e.errno != errno.ENOENT: raise @@ -239,18 +253,32 @@ def dequeue(self, filebase): msgsave = fp.read() try: # Try Python 3 protocol first + mailman_log('debug', 'Attempting to unpickle data from backup file %s using Python 3 protocol', backfile) data = pickle.loads(msgsave, encoding='latin1', fix_imports=True) + mailman_log('debug', 'Successfully unpickled data from backup file, type: %s', type(data)) + if isinstance(data, tuple): msg, data = data + mailman_log('debug', 'Unpickled tuple from backup with msg type: %s, data type: %s', + type(msg), type(data)) else: msg = None - except (pickle.UnpicklingError, ValueError): + mailman_log('debug', 'Unpickled non-tuple data from backup: %s', type(data)) + + except (pickle.UnpicklingError, ValueError) as e: + mailman_log('debug', 'Python 3 protocol failed on backup, trying Python 2 protocol: %s', str(e)) # Fall back to Python 2 protocol data = pickle.loads(msgsave, encoding='latin1', fix_imports=True) + mailman_log('debug', 'Successfully unpickled data from backup using Python 2 protocol, type: %s', type(data)) + if isinstance(data, tuple): msg, data = data + mailman_log('debug', 'Unpickled tuple from backup with msg type: %s, data type: %s', + type(msg), type(data)) else: msg = None + mailman_log('debug', 'Unpickled non-tuple data from backup: %s', type(data)) + except (IOError, OSError) as e: if e.errno != errno.ENOENT: raise @@ -263,6 +291,11 @@ def dequeue(self, filebase): mailman_log('error', 'Error moving %s to %s: %s', filename, backfile, str(e)) return None, None + # Validate data structure before returning + if not isinstance(data, dict): + mailman_log('error', 'Invalid data structure in %s: expected dict, got %s', filename, type(data)) + return None, None + return msg, data finally: # Always clean up the lock file From 104c35813cacec3a88ccfb058095c603f5124287 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 4 May 2025 18:52:51 -0400 Subject: [PATCH 360/748] pickle fixes --- Mailman/Queue/Switchboard.py | 59 ++++++++++++++++++++++-------------- 1 file changed, 37 insertions(+), 22 deletions(-) diff --git a/Mailman/Queue/Switchboard.py b/Mailman/Queue/Switchboard.py index ccf2ffc1..d3652f3a 100644 --- a/Mailman/Queue/Switchboard.py +++ b/Mailman/Queue/Switchboard.py @@ -146,8 +146,14 @@ def enqueue(self, _msg, _metadata={}, **_kws): # Use protocol 4 for Python 3 compatibility msgsave = pickle.dumps(_msg, protocol=4, fix_imports=True) else: + # Convert string to Mailman.Message if needed + if isinstance(_msg, str): + mailman_msg = Message() + mailman_msg.set_payload(_msg) + _msg = mailman_msg # Use protocol 4 for Python 3 compatibility - msgsave = pickle.dumps(str(_msg), protocol=4, fix_imports=True) + msgsave = pickle.dumps(_msg, protocol=4, fix_imports=True) + hashfood = msgsave + listname.encode('utf-8') + repr(now).encode('utf-8') # Encode the current time into the file name for FIFO sorting in # files(). The file name consists of two parts separated by a `+': @@ -172,8 +178,8 @@ def enqueue(self, _msg, _metadata={}, **_kws): try: fp = open(tmpfile, 'wb') try: - fp.write(msgsave) - pickle.dump(data, fp, protocol=4, fix_imports=True) + # Write the tuple of (message, metadata) + pickle.dump((_msg, data), fp, protocol=4, fix_imports=True) fp.flush() os.fsync(fp.fileno()) finally: @@ -189,6 +195,9 @@ def dequeue(self, filebase): backfile = os.path.join(self.__whichq, filebase + '.bak') lockfile = filename + '.lock' + mailman_log('debug', 'Switchboard.dequeue: Starting to dequeue file %s', filebase) + mailman_log('debug', 'Switchboard.dequeue: Full paths - filename: %s, backfile: %s', filename, backfile) + # Create lock file with proper cleanup max_attempts = 30 # Increased from 10 to 30 attempt = 0 @@ -216,91 +225,97 @@ def dequeue(self, filebase): try: with open(filename, 'rb') as fp: msgsave = fp.read() + mailman_log('debug', 'Switchboard.dequeue: Read %d bytes from %s', len(msgsave), filename) try: # Try Python 3 protocol first - mailman_log('debug', 'Attempting to unpickle data from %s using Python 3 protocol', filename) + mailman_log('debug', 'Switchboard.dequeue: Attempting to unpickle data from %s using Python 3 protocol', filename) data = pickle.loads(msgsave, encoding='latin1', fix_imports=True) - mailman_log('debug', 'Successfully unpickled data from %s, type: %s', filename, type(data)) + mailman_log('debug', 'Switchboard.dequeue: Successfully unpickled data from %s, type: %s', filename, type(data)) if isinstance(data, tuple): msg, data = data - mailman_log('debug', 'Unpickled tuple with msg type: %s, data type: %s', + mailman_log('debug', 'Switchboard.dequeue: Unpickled tuple with msg type: %s, data type: %s', type(msg), type(data)) else: msg = None - mailman_log('debug', 'Unpickled non-tuple data: %s', type(data)) + mailman_log('debug', 'Switchboard.dequeue: Unpickled non-tuple data: %s', type(data)) except (pickle.UnpicklingError, ValueError) as e: - mailman_log('debug', 'Python 3 protocol failed, trying Python 2 protocol: %s', str(e)) + mailman_log('debug', 'Switchboard.dequeue: Python 3 protocol failed, trying Python 2 protocol: %s', str(e)) # Fall back to Python 2 protocol data = pickle.loads(msgsave, encoding='latin1', fix_imports=True) - mailman_log('debug', 'Successfully unpickled data using Python 2 protocol, type: %s', type(data)) + mailman_log('debug', 'Switchboard.dequeue: Successfully unpickled data using Python 2 protocol, type: %s', type(data)) if isinstance(data, tuple): msg, data = data - mailman_log('debug', 'Unpickled tuple with msg type: %s, data type: %s', + mailman_log('debug', 'Switchboard.dequeue: Unpickled tuple with msg type: %s, data type: %s', type(msg), type(data)) - else: msg = None - mailman_log('debug', 'Unpickled non-tuple data: %s', type(data)) + mailman_log('debug', 'Switchboard.dequeue: Unpickled non-tuple data: %s', type(data)) except (IOError, OSError) as e: if e.errno != errno.ENOENT: raise + mailman_log('debug', 'Switchboard.dequeue: Primary file not found, trying backup file') # Try to recover from .bak file try: with open(backfile, 'rb') as fp: msgsave = fp.read() + mailman_log('debug', 'Switchboard.dequeue: Read %d bytes from backup file %s', len(msgsave), backfile) try: # Try Python 3 protocol first - mailman_log('debug', 'Attempting to unpickle data from backup file %s using Python 3 protocol', backfile) + mailman_log('debug', 'Switchboard.dequeue: Attempting to unpickle data from backup file %s using Python 3 protocol', backfile) data = pickle.loads(msgsave, encoding='latin1', fix_imports=True) - mailman_log('debug', 'Successfully unpickled data from backup file, type: %s', type(data)) + mailman_log('debug', 'Switchboard.dequeue: Successfully unpickled data from backup file, type: %s', type(data)) if isinstance(data, tuple): msg, data = data - mailman_log('debug', 'Unpickled tuple from backup with msg type: %s, data type: %s', + mailman_log('debug', 'Switchboard.dequeue: Unpickled tuple from backup with msg type: %s, data type: %s', type(msg), type(data)) else: msg = None - mailman_log('debug', 'Unpickled non-tuple data from backup: %s', type(data)) + mailman_log('debug', 'Switchboard.dequeue: Unpickled non-tuple data from backup: %s', type(data)) except (pickle.UnpicklingError, ValueError) as e: - mailman_log('debug', 'Python 3 protocol failed on backup, trying Python 2 protocol: %s', str(e)) + mailman_log('debug', 'Switchboard.dequeue: Python 3 protocol failed on backup, trying Python 2 protocol: %s', str(e)) # Fall back to Python 2 protocol data = pickle.loads(msgsave, encoding='latin1', fix_imports=True) - mailman_log('debug', 'Successfully unpickled data from backup using Python 2 protocol, type: %s', type(data)) + mailman_log('debug', 'Switchboard.dequeue: Successfully unpickled data from backup using Python 2 protocol, type: %s', type(data)) if isinstance(data, tuple): msg, data = data - mailman_log('debug', 'Unpickled tuple from backup with msg type: %s, data type: %s', + mailman_log('debug', 'Switchboard.dequeue: Unpickled tuple from backup with msg type: %s, data type: %s', type(msg), type(data)) else: msg = None - mailman_log('debug', 'Unpickled non-tuple data from backup: %s', type(data)) + mailman_log('debug', 'Switchboard.dequeue: Unpickled non-tuple data from backup: %s', type(data)) except (IOError, OSError) as e: if e.errno != errno.ENOENT: raise + mailman_log('error', 'Switchboard.dequeue: Both primary and backup files not found for %s', filebase) return None, None # Move to backup file try: os.rename(filename, backfile) + mailman_log('debug', 'Switchboard.dequeue: Successfully moved %s to %s', filename, backfile) except OSError as e: - mailman_log('error', 'Error moving %s to %s: %s', filename, backfile, str(e)) + mailman_log('error', 'Switchboard.dequeue: Error moving %s to %s: %s', filename, backfile, str(e)) return None, None # Validate data structure before returning if not isinstance(data, dict): - mailman_log('error', 'Invalid data structure in %s: expected dict, got %s', filename, type(data)) + mailman_log('error', 'Switchboard.dequeue: Invalid data structure in %s: expected dict, got %s', filename, type(data)) return None, None + mailman_log('debug', 'Switchboard.dequeue: Successfully dequeued file %s', filebase) return msg, data finally: # Always clean up the lock file try: os.unlink(lockfile) + mailman_log('debug', 'Switchboard.dequeue: Cleaned up lock file %s', lockfile) except OSError: pass From 9f430dfafa669877421ebd9f94a6d2777b7cc278 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 4 May 2025 18:54:50 -0400 Subject: [PATCH 361/748] pickle fixes --- Mailman/Handlers/ToOutgoing.py | 58 ++++++++++------------------------ 1 file changed, 16 insertions(+), 42 deletions(-) diff --git a/Mailman/Handlers/ToOutgoing.py b/Mailman/Handlers/ToOutgoing.py index ae50ed7f..a9fa7910 100644 --- a/Mailman/Handlers/ToOutgoing.py +++ b/Mailman/Handlers/ToOutgoing.py @@ -24,56 +24,30 @@ from Mailman import mm_cfg from Mailman.Queue.sbcache import get_switchboard import traceback +from Mailman.Logging.Syslog import mailman_log def process(mlist, msg, msgdata): - msgid = msg.get('message-id', 'n/a') - mailman_log('info', 'ToOutgoing: Starting to process message %s for list %s', - msgid, mlist.internal_name()) + """Process the message by moving it to the outgoing queue.""" + # Log the start of processing + mailman_log('info', 'ToOutgoing: Starting to process message %s for list %s', + msg.get('message-id', 'n/a'), mlist.internal_name()) - # Log message details for debugging - mailman_log('debug', 'ToOutgoing: Message details for %s:', msgid) + # Get the outgoing queue + outgoingq = mlist.outgoingq + + # Log message details + mailman_log('debug', 'ToOutgoing: Message details:') + mailman_log('debug', ' Message ID: %s', msg.get('message-id', 'n/a')) mailman_log('debug', ' From: %s', msg.get('from', 'unknown')) mailman_log('debug', ' To: %s', msg.get('to', 'unknown')) mailman_log('debug', ' Subject: %s', msg.get('subject', '(no subject)')) mailman_log('debug', ' Message type: %s', type(msg).__name__) mailman_log('debug', ' Message data: %s', str(msgdata)) - interval = mm_cfg.VERP_DELIVERY_INTERVAL - # Should we VERP this message? If personalization is enabled for this - # list and VERP_PERSONALIZED_DELIVERIES is true, then yes we VERP it. - # Also, if personalization is /not/ enabled, but VERP_DELIVERY_INTERVAL is - # set (and we've hit this interval), then again, this message should be - # VERPed. Otherwise, no. - # - # Note that the verp flag may already be set, e.g. by mailpasswds using - # VERP_PASSWORD_REMINDERS. Preserve any existing verp flag. - if 'verp' in msgdata: - mailman_log('debug', 'ToOutgoing: Using existing VERP flag for message %s', msgid) - elif mlist.personalize: - if mm_cfg.VERP_PERSONALIZED_DELIVERIES: - msgdata['verp'] = 1 - mailman_log('debug', 'ToOutgoing: Setting VERP flag for personalized message %s', msgid) - elif interval == 0: - # Never VERP - mailman_log('debug', 'ToOutgoing: VERP disabled for message %s', msgid) - elif interval == 1: - # VERP every time - msgdata['verp'] = 1 - mailman_log('debug', 'ToOutgoing: Setting VERP flag for message %s (interval=1)', msgid) - else: - # VERP every `inteval' number of times - msgdata['verp'] = not int(mlist.post_id) % interval - mailman_log('debug', 'ToOutgoing: Setting VERP flag for message %s based on interval %d', - msgid, interval) + # Add the message to the outgoing queue + outgoingq.enqueue(msg, msgdata) - # And now drop the message in qfiles/out - try: - outq = get_switchboard(mm_cfg.OUTQUEUE_DIR) - mailman_log('debug', 'ToOutgoing: Enqueueing message %s to outgoing queue', msgid) - outq.enqueue(msg, msgdata, listname=mlist.internal_name()) - mailman_log('info', 'ToOutgoing: Successfully enqueued message %s to outgoing queue', msgid) - except Exception as e: - mailman_log('error', 'ToOutgoing: Failed to enqueue message %s: %s', msgid, str(e)) - mailman_log('error', 'ToOutgoing: Traceback:\n%s', traceback.format_exc()) - raise + # Log successful completion + mailman_log('info', 'ToOutgoing: Successfully queued message %s for list %s', + msg.get('message-id', 'n/a'), mlist.internal_name()) From 9334408ee4dca9afec8a5ab38f3fb1d4f3b9a628 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 4 May 2025 18:56:41 -0400 Subject: [PATCH 362/748] pickle fixes --- Mailman/Handlers/ToOutgoing.py | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/Mailman/Handlers/ToOutgoing.py b/Mailman/Handlers/ToOutgoing.py index a9fa7910..26e0b92d 100644 --- a/Mailman/Handlers/ToOutgoing.py +++ b/Mailman/Handlers/ToOutgoing.py @@ -29,25 +29,35 @@ def process(mlist, msg, msgdata): """Process the message by moving it to the outgoing queue.""" + msgid = msg.get('message-id', 'n/a') + # Log the start of processing mailman_log('info', 'ToOutgoing: Starting to process message %s for list %s', - msg.get('message-id', 'n/a'), mlist.internal_name()) - - # Get the outgoing queue - outgoingq = mlist.outgoingq + msgid, mlist.internal_name()) # Log message details mailman_log('debug', 'ToOutgoing: Message details:') - mailman_log('debug', ' Message ID: %s', msg.get('message-id', 'n/a')) + mailman_log('debug', ' Message ID: %s', msgid) mailman_log('debug', ' From: %s', msg.get('from', 'unknown')) mailman_log('debug', ' To: %s', msg.get('to', 'unknown')) mailman_log('debug', ' Subject: %s', msg.get('subject', '(no subject)')) mailman_log('debug', ' Message type: %s', type(msg).__name__) mailman_log('debug', ' Message data: %s', str(msgdata)) - # Add the message to the outgoing queue - outgoingq.enqueue(msg, msgdata) + # Get the outgoing queue + try: + outgoingq = get_switchboard(mm_cfg.OUTQUEUE_DIR) + mailman_log('debug', 'ToOutgoing: Got outgoing queue for message %s', msgid) + except Exception as e: + mailman_log('error', 'ToOutgoing: Failed to get outgoing queue for message %s: %s', msgid, str(e)) + raise - # Log successful completion - mailman_log('info', 'ToOutgoing: Successfully queued message %s for list %s', - msg.get('message-id', 'n/a'), mlist.internal_name()) + # Add the message to the outgoing queue + try: + outgoingq.enqueue(msg, msgdata, listname=mlist.internal_name()) + mailman_log('info', 'ToOutgoing: Successfully queued message %s for list %s', + msgid, mlist.internal_name()) + except Exception as e: + mailman_log('error', 'ToOutgoing: Failed to enqueue message %s: %s', msgid, str(e)) + mailman_log('error', 'ToOutgoing: Traceback:\n%s', traceback.format_exc()) + raise From b26466d96875666dcc97f6d8aa09804a6619e68c Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 4 May 2025 19:03:00 -0400 Subject: [PATCH 363/748] fixes --- Mailman/Pending.py | 51 +++++++++++++++------------------------------- 1 file changed, 16 insertions(+), 35 deletions(-) diff --git a/Mailman/Pending.py b/Mailman/Pending.py index 7a0b7066..cd157ca4 100644 --- a/Mailman/Pending.py +++ b/Mailman/Pending.py @@ -52,44 +52,25 @@ class Pending(object): def InitTempVars(self): self.__pendfile = os.path.join(self.fullpath(), 'pending.pck') - def pend_new(self, op, *content, **kws): - """Create a new entry in the pending database, returning cookie for it. + def pend_new(self, operation, data=None): + """Add a new pending request to the list. + + :param operation: The operation to perform. + :type operation: string + :param data: The data associated with the operation. + :type data: any + :return: The cookie for the pending request. + :rtype: string """ - if op not in _ALLKEYS: - raise ValueError(f'Invalid operation: {op}, must be one of {_ALLKEYS}') - lifetime = kws.get('lifetime', mm_cfg.PENDING_REQUEST_LIFE) - # We try the main loop several times. If we get a lock error somewhere - # (for instance because someone broke the lock) we simply try again. - assert self.Locked() - # Load the database - db = self.__load() + # Make sure we have a lock + assert self._locked, 'List must be locked before pending operations' - # Ensure content is properly encoded and convert to tuple - content = tuple( - c.decode('utf-8', 'replace') if isinstance(c, bytes) else c - for c in content - ) + # Generate a unique cookie + cookie = Utils.unique_message_id(mlist=self) + + # Store the pending request + self._pending[cookie] = (operation, data) - # Calculate a unique cookie. Algorithm vetted by the Timbot. time() - # has high resolution on Linux, clock() on Windows. random gives us - # about 45 bits in Python 2.2, 53 bits on Python 2.3. The time and - # clock values basically help obscure the random number generator, as - # does the hash calculation. The integral parts of the time values - # are discarded because they're the most predictable bits. - while True: - now = time.time() - x = random.random() + now % 1.0 - cookie = sha_new(repr(x).encode()).hexdigest() - # We'll never get a duplicate, but we'll be anal about checking - # anyway. - if cookie not in db: - break - # Store the content, plus the time in the future when this entry will - # be evicted from the database, due to staleness. - db[cookie] = (op,) + content - evictions = db.setdefault('evictions', {}) - evictions[cookie] = now + lifetime - self.__save(db) return cookie def __load(self): From e7d0d891220999ce5aa85a5586f5b514d2faa90b Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 4 May 2025 19:07:38 -0400 Subject: [PATCH 364/748] fixes --- Mailman/Deliverer.py | 7 +++---- Mailman/MailList.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/Mailman/Deliverer.py b/Mailman/Deliverer.py index c07a0e91..e88e39b5 100644 --- a/Mailman/Deliverer.py +++ b/Mailman/Deliverer.py @@ -143,10 +143,9 @@ def MailUserPassword(self, user): lang = self.getMemberLanguage(user) cset = Utils.GetCharSet(lang) password = self.getMemberPassword(user) - # TK: Make unprintables to ? - # The list owner should allow users to set language options if they - # want to use non-us-ascii characters in password and send it back. - password = str(password, cset, 'replace').encode(cset, 'replace') + # Handle password encoding properly for Python 3 + if isinstance(password, bytes): + password = password.decode(cset, 'replace') # get the text from the template text = Utils.maketext( 'userpass.txt', diff --git a/Mailman/MailList.py b/Mailman/MailList.py index 0a14a121..f5dbe0e4 100644 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -1020,3 +1020,45 @@ def _ListAdmin__nextid(self): self._ListAdmin__nextid_counter = nextid # Return just the counter number return nextid + + def ConfirmUnsubscription(self, addr, lang=None, remote=None): + """Confirm an unsubscription request. + + :param addr: The address to unsubscribe. + :type addr: string + :param lang: The language to use for the confirmation message. + :type lang: string + :param remote: The remote address making the request. + :type remote: string + :raises: MMAlreadyPending if there's already a pending request + """ + # Make sure we have a lock + assert self._locked, 'List must be locked before pending operations' + + # Get the member's language if not specified + if lang is None: + lang = self.getMemberLanguage(addr) + + # Create a pending request + cookie = self.pend_new(Pending.UNSUBSCRIPTION, addr) + + # Craft the confirmation message + d = { + 'listname': self.real_name, + 'email': addr, + 'listaddr': self.GetListEmail(), + 'remote': remote and f'from {remote}' or '', + 'confirmurl': '%s/%s' % (self.GetScriptURL('confirm', absolute=1), cookie), + 'requestaddr': self.GetRequestEmail(cookie), + 'cookie': cookie, + 'listadmin': self.GetOwnerEmail(), + } + + # Send the confirmation message + subject = self.GetConfirmLeaveSubject(self.real_name, cookie) + text = Utils.maketext('unsub.txt', d, lang=lang, mlist=self) + msg = Message.UserNotification(addr, self.GetRequestEmail(cookie), + subject, text, lang) + msg.send(self) + + return cookie From 9ed3e41595a2e25f538b41c4cb90805c6725b9ba Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 4 May 2025 19:09:49 -0400 Subject: [PATCH 365/748] fixes --- Mailman/Pending.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mailman/Pending.py b/Mailman/Pending.py index cd157ca4..a9d8a326 100644 --- a/Mailman/Pending.py +++ b/Mailman/Pending.py @@ -63,7 +63,7 @@ def pend_new(self, operation, data=None): :rtype: string """ # Make sure we have a lock - assert self._locked, 'List must be locked before pending operations' + assert self.Locked(), 'List must be locked before pending operations' # Generate a unique cookie cookie = Utils.unique_message_id(mlist=self) From 833e2dad6b5f5507ce0b04fecb1de2e040ed9a3b Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 4 May 2025 19:11:38 -0400 Subject: [PATCH 366/748] fixes --- Mailman/Handlers/Hold.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Mailman/Handlers/Hold.py b/Mailman/Handlers/Hold.py index a49dc3f6..3f797e19 100644 --- a/Mailman/Handlers/Hold.py +++ b/Mailman/Handlers/Hold.py @@ -251,7 +251,17 @@ def hold_for_approval(mlist, msg, msgdata, exc): # languages (the user's preferred and the list's preferred for the admin), # we need to play some i18n games here. Since the current language # context ought to be set up for the user, let's craft his message first. - cookie = mlist.pend_new(Pending.HELD_MESSAGE, id) + + # Ensure the list is locked before calling pend_new + if not mlist.Locked(): + mlist.Lock() + try: + cookie = mlist.pend_new(Pending.HELD_MESSAGE, id) + finally: + mlist.Unlock() + else: + cookie = mlist.pend_new(Pending.HELD_MESSAGE, id) + if not fromusenet and ackp(msg) and mlist.respond_to_post_requests and \ mlist.autorespondToSender(sender, mlist.getMemberLanguage(sender)): # Get a confirmation cookie From db545c4d6ea76d9749f7e7e59a7a8cb7c2207928 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 4 May 2025 19:14:13 -0400 Subject: [PATCH 367/748] fixes --- Mailman/Pending.py | 1 + Mailman/Queue/Switchboard.py | 181 +++++++++++++++++++---------------- 2 files changed, 101 insertions(+), 81 deletions(-) diff --git a/Mailman/Pending.py b/Mailman/Pending.py index a9d8a326..def8ac63 100644 --- a/Mailman/Pending.py +++ b/Mailman/Pending.py @@ -29,6 +29,7 @@ from Mailman import mm_cfg from Mailman import UserDesc +from Mailman import Utils from Mailman.Utils import sha_new # Types of pending records diff --git a/Mailman/Queue/Switchboard.py b/Mailman/Queue/Switchboard.py index d3652f3a..45724efb 100644 --- a/Mailman/Queue/Switchboard.py +++ b/Mailman/Queue/Switchboard.py @@ -471,21 +471,23 @@ def files(self, extension='.pck'): return [times[k] for k in keys] def recover_backup_files(self): - # Move all .bak files in our slice to .pck. It's impossible for both - # to exist at the same time, so the move is enough to ensure that our - # normal dequeuing process will handle them. We keep count in - # _bak_count in the metadata of the number of times we recover this - # file. When the count reaches MAX_BAK_COUNT, we move the .bak file - # to a .psv file in the shunt queue. + """Move all .bak files in our slice to .pck. + + This method implements a robust recovery mechanism with: + 1. Proper error handling for corrupted files + 2. Validation of backup file contents + 3. Detailed logging of recovery attempts + 4. Safe file operations with atomic moves + """ try: for filebase in self.files('.bak'): src = os.path.join(self.__whichq, filebase + '.bak') dst = os.path.join(self.__whichq, filebase + '.pck') - # First check if the file is too old + # Check if the backup file is too old try: file_age = time.time() - os.path.getmtime(src) - if file_age > mm_cfg.FORM_LIFETIME: + if file_age > mm_cfg.BACKUP_FILE_MAX_AGE: mailman_log('warning', 'Backup file %s is too old (%d seconds), moving to shunt queue', filebase, file_age) @@ -497,88 +499,105 @@ def recover_backup_files(self): continue try: - fp = open(src, 'rb+') - try: + # First try to validate the backup file + with open(src, 'rb') as fp: try: - msg = pickle.load(fp, fix_imports=True, encoding='latin1') - data_pos = fp.tell() - data = pickle.load(fp, fix_imports=True, encoding='latin1') - except Exception as s: - # If unpickling throws any exception, just log and - # preserve this entry - tb = traceback.format_exc() - mailman_log('error', 'Unpickling .bak exception: %s\n' - + 'Traceback:\n%s\n' - + 'preserving file: %s (full path: %s)', s, tb, filebase, os.path.join(self.__whichq, filebase + '.bak')) - self.finish(filebase, preserve=True) - continue - - data['_bak_count'] = data.setdefault('_bak_count', 0) + 1 - data['_last_attempt'] = time.time() - if '_error_history' not in data: - data['_error_history'] = [] - if '_traceback' in data: - data['_error_history'].append({ - 'error': data.get('_last_error', 'unknown'), - 'traceback': data.get('_traceback', 'none'), - 'time': data.get('_last_attempt', 0) - }) + # Try to read the entire file first to check for EOF + content = fp.read() + if not content: + raise EOFError('Empty backup file') - fp.seek(data_pos) - if data.get('_parsemsg'): - protocol = 0 - else: - protocol = 1 - pickle.dump(data, fp, protocol=4, fix_imports=True) - fp.truncate() - fp.flush() - os.fsync(fp.fileno()) - - # Log detailed information about the retry - mailman_log('warning', - 'Message retry attempt %d/%d: %s (queue: %s, ' - 'message-id: %s, listname: %s, recipients: %s, ' - 'error: %s, last attempt: %s, traceback: %s)', - data['_bak_count'], - MAX_BAK_COUNT, - filebase, - self.__whichq, - data.get('message-id', 'unknown'), - data.get('listname', 'unknown'), - data.get('recips', 'unknown'), - data.get('_last_error', 'unknown'), - time.ctime(data.get('_last_attempt', 0)), - data.get('_traceback', 'none')) - - if data['_bak_count'] >= MAX_BAK_COUNT: - mailman_log('error', - 'Backup file exceeded maximum retry count (%d). ' - 'Moving to shunt queue: %s (original queue: %s, ' - 'retry count: %d, last error: %s, ' - 'message-id: %s, listname: %s, ' - 'recipients: %s, error history: %s, ' - 'last traceback: %s, full path: %s)', + # Create a BytesIO object to read from the content + from io import BytesIO + fp = BytesIO(content) + + try: + msg = pickle.load(fp, fix_imports=True, encoding='latin1') + data_pos = fp.tell() + data = pickle.load(fp, fix_imports=True, encoding='latin1') + except (EOFError, pickle.UnpicklingError) as e: + mailman_log('error', 'Corrupted backup file %s: %s\nTraceback:\n%s', + filebase, str(e), traceback.format_exc()) + self.finish(filebase, preserve=True) + continue + + # Validate the unpickled data + if not isinstance(data, dict): + raise TypeError('Invalid data format in backup file') + + # Update metadata + data['_bak_count'] = data.setdefault('_bak_count', 0) + 1 + data['_last_attempt'] = time.time() + if '_error_history' not in data: + data['_error_history'] = [] + if '_traceback' in data: + data['_error_history'].append({ + 'error': data.get('_last_error', 'unknown'), + 'traceback': data.get('_traceback', 'none'), + 'time': data.get('_last_attempt', 0) + }) + + # Write the updated data back + with open(src, 'wb') as out_fp: + if data.get('_parsemsg'): + protocol = 0 + else: + protocol = 1 + pickle.dump(data, out_fp, protocol=4, fix_imports=True) + out_fp.flush() + if hasattr(os, 'fsync'): + os.fsync(out_fp.fileno()) + + # Log detailed information about the retry + mailman_log('warning', + 'Message retry attempt %d/%d: %s (queue: %s, ' + 'message-id: %s, listname: %s, recipients: %s, ' + 'error: %s, last attempt: %s, traceback: %s)', + data['_bak_count'], MAX_BAK_COUNT, filebase, self.__whichq, - data['_bak_count'], - data.get('_last_error', 'unknown'), data.get('message-id', 'unknown'), data.get('listname', 'unknown'), data.get('recips', 'unknown'), - data.get('_error_history', 'unknown'), - data.get('_traceback', 'none'), - os.path.join(self.__whichq, filebase + '.bak')) - self.finish(filebase, preserve=True) - else: - try: - os.rename(src, dst) - except OSError as e: - mailman_log('error', 'Failed to rename backup file %s (full paths: %s -> %s): %s\nTraceback:\n%s', - filebase, os.path.join(self.__whichq, filebase + '.bak'), os.path.join(self.__whichq, filebase + '.pck'), str(e), traceback.format_exc()) + data.get('_last_error', 'unknown'), + time.ctime(data.get('_last_attempt', 0)), + data.get('_traceback', 'none')) + + if data['_bak_count'] >= MAX_BAK_COUNT: + mailman_log('error', + 'Backup file exceeded maximum retry count (%d). ' + 'Moving to shunt queue: %s (original queue: %s, ' + 'retry count: %d, last error: %s, ' + 'message-id: %s, listname: %s, ' + 'recipients: %s, error history: %s, ' + 'last traceback: %s, full path: %s)', + MAX_BAK_COUNT, + filebase, + self.__whichq, + data['_bak_count'], + data.get('_last_error', 'unknown'), + data.get('message-id', 'unknown'), + data.get('listname', 'unknown'), + data.get('recips', 'unknown'), + data.get('_error_history', 'unknown'), + data.get('_traceback', 'none'), + os.path.join(self.__whichq, filebase + '.bak')) self.finish(filebase, preserve=True) - finally: - fp.close() + else: + try: + os.rename(src, dst) + except OSError as e: + mailman_log('error', 'Failed to rename backup file %s (full paths: %s -> %s): %s\nTraceback:\n%s', + filebase, os.path.join(self.__whichq, filebase + '.bak'), os.path.join(self.__whichq, filebase + '.pck'), str(e), traceback.format_exc()) + self.finish(filebase, preserve=True) + + except Exception as e: + mailman_log('error', 'Failed to process backup file %s (full path: %s): %s\nTraceback:\n%s', + filebase, os.path.join(self.__whichq, filebase + '.bak'), str(e), traceback.format_exc()) + self.finish(filebase, preserve=True) + continue + except Exception as e: mailman_log('error', 'Failed to process backup file %s (full path: %s): %s\nTraceback:\n%s', filebase, os.path.join(self.__whichq, filebase + '.bak'), str(e), traceback.format_exc()) From cc035c47e6462fd19a4f2b1dba350c84d5205f35 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 12 May 2025 19:13:40 -0400 Subject: [PATCH 368/748] update --- Mailman/Pending.py | 1 + Mailman/Queue/OutgoingRunner.py | 220 ++++++++++++++++++++++---------- Mailman/Queue/Switchboard.py | 2 +- 3 files changed, 155 insertions(+), 68 deletions(-) diff --git a/Mailman/Pending.py b/Mailman/Pending.py index def8ac63..d13f0724 100644 --- a/Mailman/Pending.py +++ b/Mailman/Pending.py @@ -26,6 +26,7 @@ import pickle import socket import traceback +import signal from Mailman import mm_cfg from Mailman import UserDesc diff --git a/Mailman/Queue/OutgoingRunner.py b/Mailman/Queue/OutgoingRunner.py index afd83ee7..47ef2205 100644 --- a/Mailman/Queue/OutgoingRunner.py +++ b/Mailman/Queue/OutgoingRunner.py @@ -44,14 +44,17 @@ class OutgoingRunner(Runner, BounceMixin): QDIR = mm_cfg.OUTQUEUE_DIR - # Shared processed messages tracking + # Shared processed messages tracking with size limits _processed_messages = set() _processed_lock = threading.Lock() _last_cleanup = time.time() _cleanup_interval = 3600 # Clean up every hour + _max_processed_messages = 10000 + _max_retry_times = 10000 - # Retry delay configuration + # Retry configuration MIN_RETRY_DELAY = 300 # 5 minutes minimum delay between retries + MAX_RETRIES = 5 # Maximum number of retry attempts _retry_times = {} # Track last retry time for each message def __init__(self, slice=None, numslices=1): @@ -95,12 +98,128 @@ def __init__(self, slice=None, numslices=1): mailman_log('error', 'OutgoingRunner: Traceback: %s', traceback.format_exc()) raise + def _cleanup_old_messages(self): + """Clean up old message tracking data.""" + with self._processed_lock: + if len(self._processed_messages) > self._max_processed_messages: + self._processed_messages.clear() + if len(self._retry_times) > self._max_retry_times: + self._retry_times.clear() + self._last_cleanup = time.time() + + def _cleanup_resources(self, msg, msgdata): + """Clean up any temporary resources.""" + try: + if msgdata and '_tempfile' in msgdata: + os.unlink(msgdata['_tempfile']) + except Exception as e: + mailman_log('error', 'Error cleaning up resources: %s', str(e)) + + def _get_smtp_connection(self): + """Get a new SMTP connection with proper configuration.""" + try: + conn = smtplib.SMTP(mm_cfg.SMTPHOST, mm_cfg.SMTPPORT, timeout=30) + if mm_cfg.SMTP_USE_TLS: + conn.starttls() + return conn + except Exception as e: + mailman_log('error', 'SMTP connection failed: %s', str(e)) + return None + + def _handle_smtp_error(self, e, mlist, msg, msgdata): + """Handle SMTP errors with appropriate recovery.""" + if isinstance(e, smtplib.SMTPServerDisconnected): + # Server disconnected, try to reconnect + return self._retry_with_new_connection(mlist, msg, msgdata) + elif isinstance(e, smtplib.SMTPRecipientsRefused): + # Recipient refused, queue bounce + self._queue_bounces(mlist, msg, msgdata, e.recipients) + return False + + def _retry_with_new_connection(self, mlist, msg, msgdata): + """Retry message delivery with a new SMTP connection.""" + try: + conn = self._get_smtp_connection() + if conn: + return self._func(mlist, msg, msgdata, conn) + except Exception as e: + mailman_log('error', 'Retry with new connection failed: %s', str(e)) + return False + + def _convert_message(self, msg): + """Convert email.message.Message to Mailman.Message with proper handling of nested messages.""" + if isinstance(msg, email.message.Message): + mailman_msg = Message() + for key, value in msg.items(): + mailman_msg[key] = value + if msg.is_multipart(): + for part in msg.get_payload(): + mailman_msg.attach(self._convert_message(part)) + else: + mailman_msg.set_payload(msg.get_payload()) + return mailman_msg + return msg + + def _validate_message(self, msg, msgdata): + """Validate and convert message if needed. + + Returns a tuple of (msg, success) where success is a boolean indicating + if validation was successful. + """ + msgid = msg.get('message-id', 'n/a') + try: + # Check message size + if len(str(msg)) > mm_cfg.MAX_MESSAGE_SIZE: + mailman_log('error', 'Message too large: %d bytes', len(str(msg))) + return msg, False + + # Convert message if needed + msg = self._convert_message(msg) + + # Validate required Mailman.Message methods + required_methods = ['get_sender', 'get', 'items', 'is_multipart', 'get_payload'] + missing_methods = [] + for method in required_methods: + if not hasattr(msg, method): + missing_methods.append(method) + + if missing_methods: + mailman_log('error', 'OutgoingRunner: Message %s missing required methods: %s', + msgid, ', '.join(missing_methods)) + return msg, False + + # Validate message headers + if not msg.get('message-id'): + mailman_log('error', 'OutgoingRunner: Message %s missing Message-ID header', msgid) + return msg, False + + if not msg.get('from'): + mailman_log('error', 'OutgoingRunner: Message %s missing From header', msgid) + return msg, False + + if not msg.get('to') and not msg.get('recipients'): + mailman_log('error', 'OutgoingRunner: Message %s missing To/Recipients', msgid) + return msg, False + + mailman_log('debug', 'OutgoingRunner: Message %s validation successful', msgid) + return msg, True + + except Exception as e: + mailman_log('error', 'OutgoingRunner: Error validating message %s: %s', msgid, str(e)) + mailman_log('error', 'OutgoingRunner: Traceback:\n%s', traceback.format_exc()) + return msg, False + def _dispose(self, mlist, msg, msgdata): """Process an outgoing message.""" msgid = msg.get('message-id', 'n/a') filebase = msgdata.get('_filebase', 'unknown') - # Check for duplicate messages with proper locking + # Check retry count + retry_count = msgdata.get('_retry_count', 0) + if retry_count >= self.MAX_RETRIES: + mailman_log('error', 'Message %s exceeded maximum retries', msgid) + return False + with self._processed_lock: if msgid in self._processed_messages: mailman_log('error', 'OutgoingRunner: Duplicate message detected: %s (file: %s)', msgid, filebase) @@ -109,9 +228,7 @@ def _dispose(self, mlist, msg, msgdata): # Clean up old message IDs periodically current_time = time.time() if current_time - self._last_cleanup > self._cleanup_interval: - self._processed_messages.clear() - self._retry_times.clear() # Also clean up retry times - self._last_cleanup = current_time + self._cleanup_old_messages() # Check retry delay last_retry = self._retry_times.get(msgid, 0) @@ -141,7 +258,10 @@ def _dispose(self, mlist, msg, msgdata): return False # Process the message through the delivery module - self._func(mlist, msg, msgdata) + try: + self._func(mlist, msg, msgdata) + except smtplib.SMTPException as e: + return self._handle_smtp_error(e, mlist, msg, msgdata) # Log successful completion mailman_log('info', 'OutgoingRunner: Successfully processed message %s (file: %s) for list %s', @@ -163,15 +283,20 @@ def _dispose(self, mlist, msg, msgdata): # Remove from processed messages on error and requeue with self._processed_lock: self._processed_messages.remove(msgid) - # Requeue with delay + # Update retry count and requeue + msgdata['_retry_count'] = retry_count + 1 self.__retryq.enqueue(msg, msgdata) return False + finally: + self._cleanup_resources(msg, msgdata) def _queue_bounces(self, mlist, msg, msgdata, failures): """Queue bounce messages for failed deliveries.""" msgid = msg.get('message-id', 'n/a') try: for recip, code, errmsg in failures: + if not self._validate_bounce(recip, code, errmsg): + continue mailman_log('error', 'OutgoingRunner: Delivery failure for msgid: %s - Recipient: %s, Code: %s, Error: %s', msgid, recip, code, errmsg) BounceMixin._queue_bounce(self, mlist, msg, recip, code, errmsg) @@ -179,68 +304,29 @@ def _queue_bounces(self, mlist, msg, msgdata, failures): mailman_log('error', 'OutgoingRunner: Error queueing bounce for msgid: %s - %s', msgid, str(e)) mailman_log('error', 'OutgoingRunner: Traceback: %s', traceback.format_exc()) + def _validate_bounce(self, recip, code, errmsg): + """Validate bounce message data.""" + try: + if not recip or not isinstance(recip, str): + return False + if not code or not isinstance(code, (int, str)): + return False + if not errmsg or not isinstance(errmsg, str): + return False + return True + except Exception: + return False + def _cleanup(self): """Clean up resources.""" mailman_log('debug', 'OutgoingRunner: Starting cleanup') - BounceMixin._cleanup(self) - Runner._cleanup(self) + try: + BounceMixin._cleanup(self) + Runner._cleanup(self) + self._cleanup_old_messages() + self._cleanup_resources(None, {}) + except Exception as e: + mailman_log('error', 'Cleanup failed: %s', str(e)) mailman_log('debug', 'OutgoingRunner: Cleanup complete') _doperiodic = BounceMixin._doperiodic - - def _validate_message(self, msg, msgdata): - """Validate and convert message if needed. - - Returns a tuple of (msg, success) where success is a boolean indicating - if validation was successful. - """ - msgid = msg.get('message-id', 'n/a') - try: - # Convert email.message.Message to Mailman.Message if needed - if isinstance(msg, email.message.Message) and not isinstance(msg, Message): - mailman_log('debug', 'OutgoingRunner: Converting email.message.Message to Mailman.Message for %s', msgid) - mailman_msg = Message() - # Copy all attributes from the original message - for key, value in msg.items(): - mailman_msg[key] = value - # Copy the payload - if msg.is_multipart(): - for part in msg.get_payload(): - mailman_msg.attach(part) - else: - mailman_msg.set_payload(msg.get_payload()) - msg = mailman_msg - mailman_log('debug', 'OutgoingRunner: Successfully converted message %s', msgid) - - # Validate required Mailman.Message methods - required_methods = ['get_sender', 'get', 'items', 'is_multipart', 'get_payload'] - missing_methods = [] - for method in required_methods: - if not hasattr(msg, method): - missing_methods.append(method) - - if missing_methods: - mailman_log('error', 'OutgoingRunner: Message %s missing required methods: %s', - msgid, ', '.join(missing_methods)) - return msg, False - - # Validate message headers - if not msg.get('message-id'): - mailman_log('error', 'OutgoingRunner: Message %s missing Message-ID header', msgid) - return msg, False - - if not msg.get('from'): - mailman_log('error', 'OutgoingRunner: Message %s missing From header', msgid) - return msg, False - - if not msg.get('to') and not msg.get('recipients'): - mailman_log('error', 'OutgoingRunner: Message %s missing To/Recipients', msgid) - return msg, False - - mailman_log('debug', 'OutgoingRunner: Message %s validation successful', msgid) - return msg, True - - except Exception as e: - mailman_log('error', 'OutgoingRunner: Error validating message %s: %s', msgid, str(e)) - mailman_log('error', 'OutgoingRunner: Traceback:\n%s', traceback.format_exc()) - return msg, False diff --git a/Mailman/Queue/Switchboard.py b/Mailman/Queue/Switchboard.py index 45724efb..598e1213 100644 --- a/Mailman/Queue/Switchboard.py +++ b/Mailman/Queue/Switchboard.py @@ -487,7 +487,7 @@ def recover_backup_files(self): # Check if the backup file is too old try: file_age = time.time() - os.path.getmtime(src) - if file_age > mm_cfg.BACKUP_FILE_MAX_AGE: + if file_age > mm_cfg.FORM_LIFETIME: mailman_log('warning', 'Backup file %s is too old (%d seconds), moving to shunt queue', filebase, file_age) From 4598b95280bfdb28caaba5832022a00e9f67737d Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 12 May 2025 19:18:35 -0400 Subject: [PATCH 369/748] update --- Mailman/Queue/BounceRunner.py | 184 +++++++++++++--------- Mailman/Queue/CommandRunner.py | 176 ++++++++++++++++----- Mailman/Queue/IncomingRunner.py | 270 ++++++++++++++++---------------- 3 files changed, 381 insertions(+), 249 deletions(-) diff --git a/Mailman/Queue/BounceRunner.py b/Mailman/Queue/BounceRunner.py index bd4895b2..f1e4ae84 100644 --- a/Mailman/Queue/BounceRunner.py +++ b/Mailman/Queue/BounceRunner.py @@ -25,10 +25,11 @@ import email from email.utils import getaddresses from email.iterators import body_line_iterator - from email.mime.text import MIMEText from email.mime.message import MIMEMessage from email.utils import parseaddr +import threading +import traceback from Mailman import mm_cfg from Mailman import Utils @@ -59,49 +60,102 @@ def __init__(self): # Then it truncates the file and continues on. We don't need to lock # the bounce event file because bounce qrunners are single threaded # and each creates a uniquely named file to contain the events. - # - # XXX When Python 2.3 is minimal require, we can use the new - # tempfile.TemporaryFile() function. - # - # XXX We used to classify bounces to the site list as bounce events - # for every list, but this caused severe problems. Here's the - # scenario: aperson@example.com is a member of 4 lists, and a list - # owner of the foo list. example.com has an aggressive spam filter - # which rejects any message that is spam or contains spam as an - # attachment. Now, a spambot sends a piece of spam to the foo list, - # but since that spambot is not a member, the list holds the message - # for approval, and sends a notification to aperson@example.com as - # list owner. That notification contains a copy of the spam. Now - # example.com rejects the message, causing a bounce to be sent to the - # site list's bounce address. The bounce runner would then dutifully - # register a bounce for all 4 lists that aperson@example.com was a - # member of, and eventually that person would get disabled on all - # their lists. So now we ignore site list bounces. Ce La Vie for - # password reminder bounces. self._bounce_events_file = os.path.join( mm_cfg.DATA_DIR, 'bounce-events-%05d.pck' % os.getpid()) self._bounce_events_fp = None self._bouncecnt = 0 self._nextaction = time.time() + mm_cfg.REGISTER_BOUNCES_EVERY + self._max_bounce_size = 1024 * 1024 # 1MB max bounce size + self._max_bounce_age = 7 * 24 * 3600 # 7 days max bounce age + self._bounce_lock = threading.Lock() + self._last_cleanup = time.time() + self._cleanup_interval = 3600 # Clean up every hour + + def _cleanup_old_bounces(self): + """Clean up old bounce files.""" + try: + current_time = time.time() + if current_time - self._last_cleanup < self._cleanup_interval: + return + + with self._bounce_lock: + # Clean up old bounce event files + for filename in os.listdir(mm_cfg.DATA_DIR): + if filename.startswith('bounce-events-') and filename.endswith('.pck'): + filepath = os.path.join(mm_cfg.DATA_DIR, filename) + try: + if current_time - os.path.getmtime(filepath) > self._max_bounce_age: + os.unlink(filepath) + except OSError: + pass + + # Clean up old bounce queue files + for filename in os.listdir(mm_cfg.BOUNCEQUEUE_DIR): + if filename.endswith('.pck'): + filepath = os.path.join(mm_cfg.BOUNCEQUEUE_DIR, filename) + try: + if current_time - os.path.getmtime(filepath) > self._max_bounce_age: + os.unlink(filepath) + except OSError: + pass + + self._last_cleanup = current_time + except Exception as e: + mailman_log('error', 'Error cleaning up old bounces: %s', str(e)) + + def _validate_bounce(self, msg): + """Validate bounce message format.""" + try: + # Check required headers + if not msg.get('from'): + return False + if not msg.get('to'): + return False + if not msg.get('message-id'): + return False + + # Check message size + if len(str(msg)) > self._max_bounce_size: + return False + + # Check for valid bounce format + if not msg.get('content-type', '').startswith('message/delivery-status'): + return False + + return True + except Exception: + return False def _queue_bounces(self, listname, addrs, msg): + """Queue bounce messages with proper validation and error handling.""" + if not self._validate_bounce(msg): + mailman_log('error', 'Invalid bounce message format') + return False + today = time.localtime()[:3] - if self._bounce_events_fp is None: - omask = os.umask(0o006) - try: - self._bounce_events_fp = open(self._bounce_events_file, 'ab') - finally: - os.umask(omask) - for addr in addrs: - # Use protocol 4 for Python 3 compatibility and fix_imports for Python 2/3 compatibility - pickle.dump((listname, addr, today, msg), - self._bounce_events_fp, protocol=4, fix_imports=True) - self._bounce_events_fp.flush() - os.fsync(self._bounce_events_fp.fileno()) - self._bouncecnt += len(addrs) + try: + with self._bounce_lock: + if self._bounce_events_fp is None: + omask = os.umask(0o006) + try: + self._bounce_events_fp = open(self._bounce_events_file, 'ab') + finally: + os.umask(omask) + + for addr in addrs: + # Use protocol 4 for Python 3 compatibility + pickle.dump((listname, addr, today, msg), + self._bounce_events_fp, protocol=4, fix_imports=True) + self._bounce_events_fp.flush() + os.fsync(self._bounce_events_fp.fileno()) + self._bouncecnt += len(addrs) + return True + except Exception as e: + mailman_log('error', 'Error queueing bounces: %s', str(e)) + return False def _register_bounces(self, listname, addr, msg): - """Register a bounce for a member.""" + """Register a bounce for a member with proper error handling.""" try: # Create a unique filename now = time.time() @@ -110,53 +164,60 @@ def _register_bounces(self, listname, addr, msg): # Write the bounce data to the pickle file try: - # Use protocol 4 for Python 3 compatibility - protocol = 4 with open(filename, 'wb') as fp: pickle.dump((listname, addr, now, msg), fp, protocol=4, fix_imports=True) - # Set the file's mode appropriately os.chmod(filename, 0o660) + return True except (IOError, OSError) as e: try: os.unlink(filename) except (IOError, OSError): pass - raise SwitchboardError('Could not save bounce to %s: %s' % - (filename, e)) + mailman_log('error', 'Could not save bounce to %s: %s', filename, e) + return False except Exception as e: mailman_log('error', 'Error registering bounce: %s', e) return False def _cleanup(self): - if self._bouncecnt > 0: - self._register_bounces() + """Clean up resources.""" + try: + if self._bounce_events_fp is not None: + self._bounce_events_fp.close() + self._bounce_events_fp = None + if self._bouncecnt > 0: + self._register_bounces() + self._cleanup_old_bounces() + except Exception as e: + mailman_log('error', 'Error in bounce cleanup: %s', str(e)) def _doperiodic(self): + """Periodic cleanup and processing.""" now = time.time() if self._nextaction > now or self._bouncecnt == 0: return # Let's go ahead and register the bounces we've got stored up self._nextaction = now + mm_cfg.REGISTER_BOUNCES_EVERY self._register_bounces() + self._cleanup_old_bounces() def _probe_bounce(self, mlist, token): + """Process a probe bounce with proper error handling.""" locked = mlist.Locked() if not locked: mlist.Lock() try: op, addr, bmsg = mlist.pend_confirm(token) - # For Python 2.4 compatibility we need an inner try because - # try: ... except: ... finally: requires Python 2.5+ try: info = mlist.getBounceInfo(addr) if not info: # info was deleted before probe bounce was received. # Just create a new info. info = _BounceInfo(addr, - 0.0, - time.localtime()[:3], - mlist.bounce_you_are_disabled_warnings - ) + 0.0, + time.localtime()[:3], + mlist.bounce_you_are_disabled_warnings + ) mlist.disableBouncingMember(addr, info, bmsg) # Only save the list if we're unlocking it if not locked: @@ -165,6 +226,8 @@ def _probe_bounce(self, mlist, token): # Member was removed before probe bounce returned. # Just ignore it. pass + except Exception as e: + mailman_log('error', 'Error processing probe bounce: %s', str(e)) finally: if not locked: mlist.Unlock() @@ -178,12 +241,13 @@ def __init__(self, slice=None, numslices=1): BounceMixin.__init__(self) def _dispose(self, mlist, msg, msgdata): - """Process a bounce message.""" + """Process a bounce message with proper validation and error handling.""" msgid = msg.get('message-id', 'n/a') filebase = msgdata.get('_filebase', 'unknown') - # Check retry delay and duplicate processing - if not self._check_retry_delay(msgid, filebase): + # Validate bounce message + if not self._validate_bounce(msg): + mailman_log('error', 'Invalid bounce message format for %s', msgid) return False # Make sure we have the most up-to-date state @@ -192,25 +256,10 @@ def _dispose(self, mlist, msg, msgdata): except Errors.MMCorruptListDatabaseError as e: mailman_log('error', 'Failed to load list %s: %s', mlist.internal_name(), e) - self._unmark_message_processed(msgid) return False except Exception as e: mailman_log('error', 'Unexpected error loading list %s: %s', mlist.internal_name(), e) - self._unmark_message_processed(msgid) - return False - - # Validate message type first - msg, success = self._validate_message(msg, msgdata) - if not success: - mailman_log('error', 'Message validation failed for bounce message') - self._unmark_message_processed(msgid) - return False - - # Validate message headers - if not msg.get('message-id'): - mailman_log('error', 'Message missing Message-ID header') - self._unmark_message_processed(msgid) return False try: @@ -220,7 +269,6 @@ def _dispose(self, mlist, msg, msgdata): # Process the bounce if not self._register_bounces(mlist.internal_name(), msg, msgdata): - self._unmark_message_processed(msgid) return False # Queue the bounce for further processing @@ -228,7 +276,6 @@ def _dispose(self, mlist, msg, msgdata): self._queue_bounces(mlist.internal_name(), msg, msgdata) except Exception as e: mailman_log('error', 'Error queueing bounces: %s', e) - self._unmark_message_processed(msgid) return False # Log successful completion @@ -247,9 +294,6 @@ def _dispose(self, mlist, msg, msgdata): mailman_log('error', ' Message type: %s', type(msg).__name__) mailman_log('error', ' Message data: %s', str(msgdata)) mailman_log('error', 'Traceback:\n%s', traceback.format_exc()) - - # Remove from processed messages on error - self._unmark_message_processed(msgid) return False _doperiodic = BounceMixin._doperiodic diff --git a/Mailman/Queue/CommandRunner.py b/Mailman/Queue/CommandRunner.py index 1e518a8f..564241f3 100644 --- a/Mailman/Queue/CommandRunner.py +++ b/Mailman/Queue/CommandRunner.py @@ -24,6 +24,10 @@ import re import sys +import time +import threading +import traceback +import os from Mailman import mm_cfg from Mailman import Utils @@ -231,12 +235,101 @@ def indent(lines): class CommandRunner(Runner): QDIR = mm_cfg.CMDQUEUE_DIR + + def __init__(self, slice=None, numslices=1): + Runner.__init__(self, slice, numslices) + # Rate limiting + self._command_times = {} + self._command_lock = threading.Lock() + self._max_commands_per_hour = 100 + self._command_window = 3600 # 1 hour + # Cleanup + self._last_cleanup = time.time() + self._cleanup_interval = 3600 # Clean up every hour + self._max_command_age = 7 * 24 * 3600 # 7 days max command age + + def _cleanup_old_commands(self): + """Clean up old command files.""" + try: + current_time = time.time() + if current_time - self._last_cleanup < self._cleanup_interval: + return + + with self._command_lock: + # Clean up old command files + for filename in os.listdir(self.QDIR): + if filename.endswith('.pck'): + filepath = os.path.join(self.QDIR, filename) + try: + if current_time - os.path.getmtime(filepath) > self._max_command_age: + os.unlink(filepath) + except OSError: + pass + + # Clean up old command times + cutoff = current_time - self._command_window + self._command_times = {k: v for k, v in self._command_times.items() + if v > cutoff} + + self._last_cleanup = current_time + except Exception as e: + syslog('error', 'Error cleaning up old commands: %s', str(e)) + + def _check_rate_limit(self, sender): + """Check if sender has exceeded rate limit.""" + with self._command_lock: + current_time = time.time() + # Clean up old entries + cutoff = current_time - self._command_window + self._command_times = {k: v for k, v in self._command_times.items() + if v > cutoff} + + # Count commands in window + count = sum(1 for t in self._command_times.values() if t > cutoff) + if count >= self._max_commands_per_hour: + return False + + # Add new command + self._command_times[sender] = current_time + return True + + def _validate_command(self, msg, msgdata): + """Validate command message format.""" + try: + # Check required headers + if not msg.get('from'): + return False + if not msg.get('to'): + return False + if not msg.get('message-id'): + return False + + # Check message size + if len(str(msg)) > mm_cfg.MAX_MESSAGE_SIZE: + return False + + # Check for valid command format + if not msg.get('content-type', '').startswith('text/plain'): + return False + + return True + except Exception: + return False def _dispose(self, mlist, msg, msgdata): - # Validate message type first - msg, success = self._validate_message(msg, msgdata) - if not success: - mailman_log('error', 'Message validation failed for command message') + """Process a command message with proper validation and rate limiting.""" + msgid = msg.get('message-id', 'n/a') + filebase = msgdata.get('_filebase', 'unknown') + sender = msg.get_sender() + + # Validate command message + if not self._validate_command(msg, msgdata): + syslog('error', 'Invalid command message format for %s', msgid) + return False + + # Check rate limit + if not self._check_rate_limit(sender): + syslog('error', 'Rate limit exceeded for sender %s', sender) return False # The policy here is similar to the Replybot policy. If a message has @@ -248,44 +341,43 @@ def _dispose(self, mlist, msg, msgdata): syslog('vette', 'Precedence: %s message discarded by: %s', precedence, mlist.GetRequestEmail()) return False - # Do replybot for commands - mlist.Load() - Replybot.process(mlist, msg, msgdata) - if mlist.autorespond_requests == 1: - syslog('vette', 'replied and discard') - # w/discard - return False - # Now craft the response - res = Results(mlist, msg, msgdata) - # BAW: Not all the functions of this qrunner require the list to be - # locked. Still, it's more convenient to lock it here and now and - # deal with lock failures in one place. + try: - mlist.Lock(timeout=mm_cfg.LIST_LOCK_TIMEOUT) - except LockFile.TimeOutError: - # Oh well, try again later + # Log start of processing + syslog('info', 'CommandRunner: Starting to process command message %s (file: %s) for list %s', + msgid, filebase, mlist.internal_name()) + + # Process the command + results = Results(mlist, msg, msgdata) + ret = results.process() + + # Send response if needed + if ret != BADCMD: + results.send_response() + + # Log successful completion + syslog('info', 'CommandRunner: Successfully processed command message %s (file: %s) for list %s', + msgid, filebase, mlist.internal_name()) return True - # This message will have been delivered to one of mylist-request, - # mylist-join, or mylist-leave, and the message metadata will contain - # a key to which one was used. - try: - ret = BADCMD - if msgdata.get('torequest'): - ret = res.process() - elif msgdata.get('tojoin'): - ret = res.do_command('join') - elif msgdata.get('toleave'): - ret = res.do_command('leave') - elif msgdata.get('toconfirm'): - mo = re.match(mm_cfg.VERP_CONFIRM_REGEXP, msg.get('to', '')) - if mo: - ret = res.do_command('confirm', (mo.group('cookie'),)) - if ret == BADCMD and mm_cfg.DISCARD_MESSAGE_WITH_NO_COMMAND: - syslog('vette', - 'No command, message discarded, msgid: %s', - msg.get('message-id', 'n/a')) - else: - res.send_response() - mlist.Save() + except Exception as e: + # Enhanced error logging with more context + syslog('error', 'Error processing command message %s for list %s: %s', + msgid, mlist.internal_name(), str(e)) + syslog('error', 'Message details:') + syslog('error', ' Message ID: %s', msgid) + syslog('error', ' From: %s', msg.get('from', 'unknown')) + syslog('error', ' To: %s', msg.get('to', 'unknown')) + syslog('error', ' Subject: %s', msg.get('subject', '(no subject)')) + syslog('error', ' Message type: %s', type(msg).__name__) + syslog('error', ' Message data: %s', str(msgdata)) + syslog('error', 'Traceback:\n%s', traceback.format_exc()) + return False finally: - mlist.Unlock() + self._cleanup_old_commands() + + def _cleanup(self): + """Clean up resources.""" + try: + self._cleanup_old_commands() + except Exception as e: + syslog('error', 'Error in command cleanup: %s', str(e)) diff --git a/Mailman/Queue/IncomingRunner.py b/Mailman/Queue/IncomingRunner.py index 306b5f51..dd0b474c 100644 --- a/Mailman/Queue/IncomingRunner.py +++ b/Mailman/Queue/IncomingRunner.py @@ -104,7 +104,7 @@ from io import StringIO import random import signal -import os +import threading from email import message_from_string from Mailman.Message import Message from urllib.parse import parse_qs @@ -129,43 +129,118 @@ class IncomingRunner(Runner): def __init__(self, slice=None, numslices=1): mailman_log('qrunner', 'IncomingRunner: Initializing with slice=%s, numslices=%s', slice, numslices) Runner.__init__(self, slice, numslices) - # Track processed messages to prevent duplicates - self._processed_messages = set() - # Clean up old messages periodically + # Rate limiting + self._message_times = {} + self._message_lock = threading.Lock() + self._max_messages_per_hour = 1000 + self._message_window = 3600 # 1 hour + # Cleanup self._last_cleanup = time.time() self._cleanup_interval = 3600 # Clean up every hour + self._max_message_age = 7 * 24 * 3600 # 7 days max message age mailman_log('qrunner', 'IncomingRunner: Initialization complete') + def _cleanup_old_messages(self): + """Clean up old message files.""" + try: + current_time = time.time() + if current_time - self._last_cleanup < self._cleanup_interval: + return + + with self._message_lock: + # Clean up old message files + for filename in os.listdir(self.QDIR): + if filename.endswith('.pck'): + filepath = os.path.join(self.QDIR, filename) + try: + if current_time - os.path.getmtime(filepath) > self._max_message_age: + os.unlink(filepath) + except OSError: + pass + + # Clean up old message times + cutoff = current_time - self._message_window + self._message_times = {k: v for k, v in self._message_times.items() + if v > cutoff} + + self._last_cleanup = current_time + except Exception as e: + mailman_log('error', 'Error cleaning up old messages: %s', str(e)) + + def _check_rate_limit(self, sender): + """Check if sender has exceeded rate limit.""" + with self._message_lock: + current_time = time.time() + # Clean up old entries + cutoff = current_time - self._message_window + self._message_times = {k: v for k, v in self._message_times.items() + if v > cutoff} + + # Count messages in window + count = sum(1 for t in self._message_times.values() if t > cutoff) + if count >= self._max_messages_per_hour: + return False + + # Add new message + self._message_times[sender] = current_time + return True + + def _validate_message(self, msg, msgdata): + """Validate message format.""" + try: + # Check required headers + if not msg.get('from'): + return False + if not msg.get('to'): + return False + if not msg.get('message-id'): + return False + + # Check message size + if len(str(msg)) > mm_cfg.MAX_MESSAGE_SIZE: + return False + + # Check for valid message format + if not msg.get('content-type', '').startswith(('text/', 'multipart/')): + return False + + return True + except Exception: + return False + def _dispose(self, listname, msg, msgdata): + """Process an incoming message with proper validation and rate limiting.""" # Import MailList here to avoid circular imports from Mailman.MailList import MailList # Track message ID to prevent duplicates msgid = msg.get('message-id', 'n/a') filebase = msgdata.get('_filebase', 'unknown') + sender = msg.get_sender() mailman_log('qrunner', 'IncomingRunner._dispose: Starting to process message %s (file: %s) for list %s', msgid, filebase, listname) - # Check retry delay and duplicate processing - if not self._check_retry_delay(msgid, filebase): - mailman_log('qrunner', 'IncomingRunner._dispose: Message %s failed retry delay check, moving to shunt queue', - msgid) - # Move to shunt queue and remove from original queue - self._shunt.enqueue(msg, msgdata) - # Get the filebase from msgdata and finish processing it - if filebase: - self._switchboard.finish(filebase) - return 0 + # Validate message + if not self._validate_message(msg, msgdata): + mailman_log('error', 'Invalid message format for %s', msgid) + return False + + # Check rate limit + if not self._check_rate_limit(sender): + mailman_log('error', 'Rate limit exceeded for sender %s', sender) + return False # Get the MailList object for the list name try: mlist = MailList(listname, lock=False) mailman_log('qrunner', 'IncomingRunner._dispose: Successfully got MailList object for %s', listname) except Errors.MMListError as e: - mailman_log('qrunner', 'IncomingRunner._dispose: Failed to get list %s: %s', listname, str(e)) - self._unmark_message_processed(msgid) - return 0 + mailman_log('error', 'Failed to get list %s: %s', listname, str(e)) + return False + except Exception as e: + mailman_log('error', 'Unexpected error loading list %s: %s', listname, str(e)) + return False try: # Log start of processing @@ -186,22 +261,22 @@ def _dispose(self, listname, msg, msgdata): return result except Exception as e: # Enhanced error logging with more context - mailman_log('qrunner', 'IncomingRunner._dispose: Error processing message %s for list %s: %s', + mailman_log('error', 'Error processing message %s for list %s: %s', msgid, mlist.internal_name(), str(e)) - mailman_log('qrunner', 'IncomingRunner._dispose: Message details:') - mailman_log('qrunner', ' Message ID: %s', msgid) - mailman_log('qrunner', ' From: %s', msg.get('from', 'unknown')) - mailman_log('qrunner', ' To: %s', msg.get('to', 'unknown')) - mailman_log('qrunner', ' Subject: %s', msg.get('subject', '(no subject)')) - mailman_log('qrunner', ' Message type: %s', type(msg).__name__) - mailman_log('qrunner', ' Message data: %s', str(msgdata)) - mailman_log('qrunner', 'IncomingRunner._dispose: Traceback:\n%s', traceback.format_exc()) - - # Remove from processed messages on error - self._unmark_message_processed(msgid) - return 0 + mailman_log('error', 'Message details:') + mailman_log('error', ' Message ID: %s', msgid) + mailman_log('error', ' From: %s', msg.get('from', 'unknown')) + mailman_log('error', ' To: %s', msg.get('to', 'unknown')) + mailman_log('error', ' Subject: %s', msg.get('subject', '(no subject)')) + mailman_log('error', ' Message type: %s', type(msg).__name__) + mailman_log('error', ' Message data: %s', str(msgdata)) + mailman_log('error', 'Traceback:\n%s', traceback.format_exc()) + return False + finally: + self._cleanup_old_messages() def _get_pipeline(self, mlist, msg, msgdata): + """Get the pipeline for message processing.""" # We must return a copy of the list, otherwise, the first message that # flows through the pipeline will empty it out! pipeline = msgdata.get('pipeline', @@ -212,21 +287,22 @@ def _get_pipeline(self, mlist, msg, msgdata): return pipeline def _dopipeline(self, mlist, msg, msgdata, pipeline): + """Process message through pipeline with proper error handling.""" msgid = msg.get('message-id', 'n/a') mailman_log('qrunner', 'IncomingRunner._dopipeline: Starting pipeline processing for message %s', msgid) # Validate pipeline state if not pipeline: - mailman_log('qrunner', 'IncomingRunner._dopipeline: Empty pipeline for message %s', msgid) + mailman_log('error', 'Empty pipeline for message %s', msgid) return 0 if 'pipeline' in msgdata and msgdata['pipeline'] != pipeline: - mailman_log('qrunner', 'IncomingRunner._dopipeline: Pipeline state mismatch for message %s', msgid) + mailman_log('error', 'Pipeline state mismatch for message %s', msgid) return 0 # Ensure message is a Mailman.Message if not isinstance(msg, Message): try: - mailman_log('qrunner', 'IncomingRunner._dopipeline: Converting email.message.Message to Mailman.Message for %s', msgid) + mailman_log('qrunner', 'Converting email.message.Message to Mailman.Message for %s', msgid) mailman_msg = Message() # Copy all attributes from the original message for key, value in msg.items(): @@ -241,117 +317,37 @@ def _dopipeline(self, mlist, msg, msgdata, pipeline): # Update msgdata references if needed if 'msg' in msgdata: msgdata['msg'] = msg - mailman_log('qrunner', 'IncomingRunner._dopipeline: Successfully converted message %s', msgid) + mailman_log('qrunner', 'Successfully converted message %s', msgid) except Exception as e: - mailman_log('qrunner', 'IncomingRunner._dopipeline: Failed to convert message to Mailman.Message: %s\nTraceback:\n%s', + mailman_log('error', 'Failed to convert message to Mailman.Message: %s\nTraceback:\n%s', str(e), traceback.format_exc()) return 0 - # Validate required Mailman.Message methods - required_methods = ['get_sender', 'get', 'items', 'is_multipart', 'get_payload'] - for method in required_methods: - if not hasattr(msg, method): - mailman_log('qrunner', 'IncomingRunner._dopipeline: Message object missing required method %s', method) - return 0 - - while pipeline: - handler = pipeline.pop(0) - modname = 'Mailman.Handlers.' + handler - try: - mailman_log('qrunner', 'IncomingRunner._dopipeline: Processing message %s through handler %s', - msgid, handler) - __import__(modname) - - # Store original PID and track child processes - original_pid = os.getpid() - child_pids = set() - - # Process the message - sys.modules[modname].process(mlist, msg, msgdata) - - # Check for process leaks - current_pid = os.getpid() - if current_pid != original_pid: - mailman_log('qrunner', 'IncomingRunner._dopipeline: Child process leaked through in handler %s: original_pid=%d, current_pid=%d', - modname, original_pid, current_pid) - # Try to clean up any child processes - try: - os.kill(original_pid, signal.SIGTERM) - except: - pass - os._exit(1) - - # Clean up any child processes - try: - while True: - pid, status = os.waitpid(-1, os.WNOHANG) - if pid == 0: - break - child_pids.add(pid) - except ChildProcessError: - pass - - if child_pids: - mailman_log('qrunner', 'IncomingRunner._dopipeline: Cleaned up %d child processes from handler %s: %s', - len(child_pids), modname, child_pids) - - mailman_log('qrunner', 'IncomingRunner._dopipeline: Successfully processed message %s through handler %s', - msgid, handler) - - except Errors.DiscardMessage: - # Throw the message away; we need do nothing else with it. - pipeline.insert(0, handler) - mailman_log('qrunner', """IncomingRunner._dopipeline: Message discarded, msgid: %s - list: %s, - handler: %s""", - msgid, mlist.internal_name(), handler) - return 0 - except Errors.HoldMessage: - # Message is being held for moderation, no need to requeue - mailman_log('qrunner', """IncomingRunner._dopipeline: Message held for moderation, msgid: %s - list: %s, - handler: %s""", - msgid, mlist.internal_name(), handler) - # Add message ID to processed set to prevent duplicate processing - self._processed_messages.add(msgid) - return 0 - except Errors.RejectMessage as e: - pipeline.insert(0, handler) - mailman_log('qrunner', """IncomingRunner._dopipeline: Message rejected, msgid: %s - list: %s, - handler: %s, - reason: %s""", - msgid, mlist.internal_name(), handler, e.notice()) - mlist.BounceMessage(msg, msgdata, e) - return 0 - except Exception as e: - # Log the full traceback for debugging - mailman_log('qrunner', 'IncomingRunner._dopipeline: Error in handler %s for message %s: %s\n%s', - modname, msgid, str(e), traceback.format_exc()) - # Put the handler back in the pipeline - pipeline.insert(0, handler) - # Try to move message to shunt queue + # Process through pipeline + try: + for handler in pipeline: try: - self._shunt.enqueue(msg, msgdata) - mailman_log('qrunner', 'IncomingRunner._dopipeline: Moved failed message %s to shunt queue', msgid) - except Exception as shunt_error: - mailman_log('qrunner', 'IncomingRunner._dopipeline: Failed to move message %s to shunt queue: %s', - msgid, str(shunt_error)) - raise - return 0 + handler.process(mlist, msg, msgdata) + except Exception as e: + mailman_log('error', 'Handler %s failed for message %s: %s', + handler.__name__, msgid, str(e)) + raise PipelineError(str(e)) + return 1 + except PipelineError as e: + mailman_log('error', 'Pipeline processing failed for message %s: %s', + msgid, str(e)) + return 0 + except Exception as e: + mailman_log('error', 'Unexpected error in pipeline for message %s: %s', + msgid, str(e)) + return 0 def _cleanup(self): - """Clean up any resources used by the pipeline.""" - mailman_log('qrunner', 'IncomingRunner._cleanup: Starting cleanup') - # Clean up child processes - reap(self._kids, once=True) - # Close any open file descriptors - for fd in range(3, 1024): # Skip stdin, stdout, stderr - try: - os.close(fd) - except OSError: - pass - mailman_log('qrunner', 'IncomingRunner._cleanup: Cleanup complete') + """Clean up resources.""" + try: + self._cleanup_old_messages() + except Exception as e: + mailman_log('error', 'Error in message cleanup: %s', str(e)) def _oneloop(self): mailman_log('qrunner', 'IncomingRunner._oneloop: Starting loop') From 38cd6325109c96a9bf71d28f045ae45fdc075c52 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 12 May 2025 19:30:55 -0400 Subject: [PATCH 370/748] update admin debugs --- Mailman/Cgi/admin.py | 53 +++++++++++++++++++------------------------- 1 file changed, 23 insertions(+), 30 deletions(-) diff --git a/Mailman/Cgi/admin.py b/Mailman/Cgi/admin.py index 53a17c8d..8a9e326d 100644 --- a/Mailman/Cgi/admin.py +++ b/Mailman/Cgi/admin.py @@ -58,13 +58,13 @@ def main(): try: # Log page load mailman_log('info', 'admin: Page load started') - + print("DEBUG: Entered main()", flush=True) # Initialize document early doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) - # Parse form data first since we need it for authentication try: + print("DEBUG: Parsing form data", flush=True) if os.environ.get('REQUEST_METHOD') == 'POST': content_length = int(os.environ.get('CONTENT_LENGTH', 0)) if content_length > 0: @@ -75,8 +75,8 @@ def main(): else: query_string = os.environ.get('QUERY_STRING', '') cgidata = urllib.parse.parse_qs(query_string, keep_blank_values=True) + print(f"DEBUG: cgidata after parse: {cgidata}", flush=True) except Exception as e: - # Someone crafted a POST with a bad Content-Type print('Status: 400 Bad Request') print('Content-type: text/html; charset=utf-8\n') doc.AddItem(Header(2, _("Error"))) @@ -84,23 +84,22 @@ def main(): print(doc.Format()) mailman_log('error', 'admin: Invalid form data: %s\n%s', str(e), traceback.format_exc()) return - # Get the list name parts = Utils.GetPathPieces() + print(f"DEBUG: Path parts: {parts}", flush=True) if not parts: handle_no_list() return - listname = parts[0].lower() mailman_log('info', 'admin: Processing list "%s"', listname) + print(f"DEBUG: List name: {listname}", flush=True) if isinstance(listname, bytes): listname = listname.decode('utf-8', 'replace') try: mlist = MailList.MailList(listname, lock=0) + print("DEBUG: Loaded MailList", flush=True) except Errors.MMListError as e: - # Avoid cross-site scripting attacks safelistname = Utils.websafe(listname) - # Send this with a 404 status. print('Status: 404 Not Found') admin_overview(_('No such list %(safelistname)s') % { 'safelistname': safelistname @@ -108,17 +107,15 @@ def main(): mailman_log('error', 'admin: No such list "%s": %s\n%s', listname, e, traceback.format_exc()) return - # Now that we know what list has been requested, all subsequent admin - # pages are shown in that list's preferred language. i18n.set_language(mlist.preferred_language) # If the user is not authenticated, we're done. try: + print("DEBUG: Checking authentication", flush=True) if os.environ.get('REQUEST_METHOD') == 'POST': content_length = int(os.environ.get('CONTENT_LENGTH', 0)) if content_length > 0: form_data = sys.stdin.buffer.read(content_length).decode('latin-1') cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=True) - # Ensure all form values are properly decoded for key in cgidata: cgidata[key] = [v.decode('latin-1') if isinstance(v, bytes) else v for v in cgidata[key]] else: @@ -126,23 +123,20 @@ def main(): else: query_string = os.environ.get('QUERY_STRING', '') cgidata = urllib.parse.parse_qs(query_string, keep_blank_values=True) - # Ensure all form values are properly decoded for key in cgidata: cgidata[key] = [v.decode('latin-1') if isinstance(v, bytes) else v for v in cgidata[key]] + print(f"DEBUG: cgidata before auth: {cgidata}", flush=True) except Exception as e: - # Someone crafted a POST with a bad Content-Type:. doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) doc.AddItem(Header(2, _("Error"))) doc.AddItem(Bold(_('Invalid options to CGI script.'))) doc.AddItem(Preformatted(Utils.websafe(str(e)))) doc.AddItem(Preformatted(Utils.websafe(traceback.format_exc()))) - # Send this with a 400 status. print('Status: 400 Bad Request') print(doc.Format()) mailman_log('error', 'admin: Invalid options: %s\n%s', str(e), traceback.format_exc()) return - # CSRF check safe_params = ['VARHELP', 'adminpw', 'admlogin', 'letter', 'chunk', 'findmember', @@ -153,16 +147,22 @@ def main(): 'admin') else: csrf_checked = True - # if password is present, void cookie to force password authentication. if cgidata.get('adminpw', [''])[0]: os.environ['HTTP_COOKIE'] = '' csrf_checked = True - - if not mlist.WebAuthenticate((mm_cfg.AuthListAdmin, + try: + print("DEBUG: Calling WebAuthenticate", flush=True) + auth_result = mlist.WebAuthenticate((mm_cfg.AuthListAdmin, mm_cfg.AuthSiteAdmin), - cgidata.get('adminpw', [''])[0]): + cgidata.get('adminpw', [''])[0]) + print(f"DEBUG: WebAuthenticate result: {auth_result}", flush=True) + except Exception as e: + mailman_log('error', 'admin: Exception during WebAuthenticate: %s\n%s', str(e), traceback.format_exc()) + print("DEBUG: Exception during WebAuthenticate", flush=True) + raise + if not auth_result: + print("DEBUG: Not authenticated, calling loginpage", flush=True) if 'adminpw' in cgidata: - # This is a re-authorization attempt msg = Bold(FontSize('+1', _('Authorization failed.'))).Format() remote = os.environ.get('HTTP_FORWARDED_FOR', os.environ.get('HTTP_X_FORWARDED_FOR', @@ -174,8 +174,9 @@ def main(): else: msg = '' Auth.loginpage(mlist, 'admin', msg=msg) + print("DEBUG: Called Auth.loginpage", flush=True) return - + print("DEBUG: Authenticated, proceeding to admin page", flush=True) # Which subcategory was requested? Default is `general' if len(parts) == 1: category = 'general' @@ -186,14 +187,10 @@ def main(): else: category = parts[1] subcat = parts[2] - - # Create the document doc = Document() doc.set_language(mlist.preferred_language) - - # Create the form form = Form(mlist=mlist, contexts=AUTH_CONTEXTS) - + print(f"DEBUG: category={category}, subcat={subcat}", flush=True) # Now dispatch to the appropriate handler if category == 'general': show_variables(mlist, category, subcat, cgidata, doc) @@ -210,19 +207,15 @@ def main(): doc.AddItem(Bold(_('No such category: %(category)s') % { 'category': category })) - - # Format and print the document + print("DEBUG: About to print doc.Format()", flush=True) print(doc.Format()) - except Exception as e: - # Catch any unhandled exceptions and display them properly doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) doc.AddItem(Header(2, _("Error"))) doc.AddItem(Bold(_('An unexpected error occurred.'))) doc.AddItem(Preformatted(Utils.websafe(str(e)))) doc.AddItem(Preformatted(Utils.websafe(traceback.format_exc()))) - # Send this with a 500 status. print('Status: 500 Internal Server Error') print(doc.Format()) mailman_log('error', 'admin: Unexpected error: %s\n%s', str(e), traceback.format_exc()) From 824be77a834336daa07cb6eb32403d0d109c2d8c Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 12 May 2025 19:35:44 -0400 Subject: [PATCH 371/748] update --- Mailman/Queue/BounceRunner.py | 184 +++++++++------------- Mailman/Queue/CommandRunner.py | 176 +++++---------------- Mailman/Queue/IncomingRunner.py | 270 ++++++++++++++++---------------- 3 files changed, 249 insertions(+), 381 deletions(-) diff --git a/Mailman/Queue/BounceRunner.py b/Mailman/Queue/BounceRunner.py index f1e4ae84..bd4895b2 100644 --- a/Mailman/Queue/BounceRunner.py +++ b/Mailman/Queue/BounceRunner.py @@ -25,11 +25,10 @@ import email from email.utils import getaddresses from email.iterators import body_line_iterator + from email.mime.text import MIMEText from email.mime.message import MIMEMessage from email.utils import parseaddr -import threading -import traceback from Mailman import mm_cfg from Mailman import Utils @@ -60,102 +59,49 @@ def __init__(self): # Then it truncates the file and continues on. We don't need to lock # the bounce event file because bounce qrunners are single threaded # and each creates a uniquely named file to contain the events. + # + # XXX When Python 2.3 is minimal require, we can use the new + # tempfile.TemporaryFile() function. + # + # XXX We used to classify bounces to the site list as bounce events + # for every list, but this caused severe problems. Here's the + # scenario: aperson@example.com is a member of 4 lists, and a list + # owner of the foo list. example.com has an aggressive spam filter + # which rejects any message that is spam or contains spam as an + # attachment. Now, a spambot sends a piece of spam to the foo list, + # but since that spambot is not a member, the list holds the message + # for approval, and sends a notification to aperson@example.com as + # list owner. That notification contains a copy of the spam. Now + # example.com rejects the message, causing a bounce to be sent to the + # site list's bounce address. The bounce runner would then dutifully + # register a bounce for all 4 lists that aperson@example.com was a + # member of, and eventually that person would get disabled on all + # their lists. So now we ignore site list bounces. Ce La Vie for + # password reminder bounces. self._bounce_events_file = os.path.join( mm_cfg.DATA_DIR, 'bounce-events-%05d.pck' % os.getpid()) self._bounce_events_fp = None self._bouncecnt = 0 self._nextaction = time.time() + mm_cfg.REGISTER_BOUNCES_EVERY - self._max_bounce_size = 1024 * 1024 # 1MB max bounce size - self._max_bounce_age = 7 * 24 * 3600 # 7 days max bounce age - self._bounce_lock = threading.Lock() - self._last_cleanup = time.time() - self._cleanup_interval = 3600 # Clean up every hour - - def _cleanup_old_bounces(self): - """Clean up old bounce files.""" - try: - current_time = time.time() - if current_time - self._last_cleanup < self._cleanup_interval: - return - - with self._bounce_lock: - # Clean up old bounce event files - for filename in os.listdir(mm_cfg.DATA_DIR): - if filename.startswith('bounce-events-') and filename.endswith('.pck'): - filepath = os.path.join(mm_cfg.DATA_DIR, filename) - try: - if current_time - os.path.getmtime(filepath) > self._max_bounce_age: - os.unlink(filepath) - except OSError: - pass - - # Clean up old bounce queue files - for filename in os.listdir(mm_cfg.BOUNCEQUEUE_DIR): - if filename.endswith('.pck'): - filepath = os.path.join(mm_cfg.BOUNCEQUEUE_DIR, filename) - try: - if current_time - os.path.getmtime(filepath) > self._max_bounce_age: - os.unlink(filepath) - except OSError: - pass - - self._last_cleanup = current_time - except Exception as e: - mailman_log('error', 'Error cleaning up old bounces: %s', str(e)) - - def _validate_bounce(self, msg): - """Validate bounce message format.""" - try: - # Check required headers - if not msg.get('from'): - return False - if not msg.get('to'): - return False - if not msg.get('message-id'): - return False - - # Check message size - if len(str(msg)) > self._max_bounce_size: - return False - - # Check for valid bounce format - if not msg.get('content-type', '').startswith('message/delivery-status'): - return False - - return True - except Exception: - return False def _queue_bounces(self, listname, addrs, msg): - """Queue bounce messages with proper validation and error handling.""" - if not self._validate_bounce(msg): - mailman_log('error', 'Invalid bounce message format') - return False - today = time.localtime()[:3] - try: - with self._bounce_lock: - if self._bounce_events_fp is None: - omask = os.umask(0o006) - try: - self._bounce_events_fp = open(self._bounce_events_file, 'ab') - finally: - os.umask(omask) - - for addr in addrs: - # Use protocol 4 for Python 3 compatibility - pickle.dump((listname, addr, today, msg), - self._bounce_events_fp, protocol=4, fix_imports=True) - self._bounce_events_fp.flush() - os.fsync(self._bounce_events_fp.fileno()) - self._bouncecnt += len(addrs) - return True - except Exception as e: - mailman_log('error', 'Error queueing bounces: %s', str(e)) - return False + if self._bounce_events_fp is None: + omask = os.umask(0o006) + try: + self._bounce_events_fp = open(self._bounce_events_file, 'ab') + finally: + os.umask(omask) + for addr in addrs: + # Use protocol 4 for Python 3 compatibility and fix_imports for Python 2/3 compatibility + pickle.dump((listname, addr, today, msg), + self._bounce_events_fp, protocol=4, fix_imports=True) + self._bounce_events_fp.flush() + os.fsync(self._bounce_events_fp.fileno()) + self._bouncecnt += len(addrs) def _register_bounces(self, listname, addr, msg): - """Register a bounce for a member with proper error handling.""" + """Register a bounce for a member.""" try: # Create a unique filename now = time.time() @@ -164,60 +110,53 @@ def _register_bounces(self, listname, addr, msg): # Write the bounce data to the pickle file try: + # Use protocol 4 for Python 3 compatibility + protocol = 4 with open(filename, 'wb') as fp: pickle.dump((listname, addr, now, msg), fp, protocol=4, fix_imports=True) + # Set the file's mode appropriately os.chmod(filename, 0o660) - return True except (IOError, OSError) as e: try: os.unlink(filename) except (IOError, OSError): pass - mailman_log('error', 'Could not save bounce to %s: %s', filename, e) - return False + raise SwitchboardError('Could not save bounce to %s: %s' % + (filename, e)) except Exception as e: mailman_log('error', 'Error registering bounce: %s', e) return False def _cleanup(self): - """Clean up resources.""" - try: - if self._bounce_events_fp is not None: - self._bounce_events_fp.close() - self._bounce_events_fp = None - if self._bouncecnt > 0: - self._register_bounces() - self._cleanup_old_bounces() - except Exception as e: - mailman_log('error', 'Error in bounce cleanup: %s', str(e)) + if self._bouncecnt > 0: + self._register_bounces() def _doperiodic(self): - """Periodic cleanup and processing.""" now = time.time() if self._nextaction > now or self._bouncecnt == 0: return # Let's go ahead and register the bounces we've got stored up self._nextaction = now + mm_cfg.REGISTER_BOUNCES_EVERY self._register_bounces() - self._cleanup_old_bounces() def _probe_bounce(self, mlist, token): - """Process a probe bounce with proper error handling.""" locked = mlist.Locked() if not locked: mlist.Lock() try: op, addr, bmsg = mlist.pend_confirm(token) + # For Python 2.4 compatibility we need an inner try because + # try: ... except: ... finally: requires Python 2.5+ try: info = mlist.getBounceInfo(addr) if not info: # info was deleted before probe bounce was received. # Just create a new info. info = _BounceInfo(addr, - 0.0, - time.localtime()[:3], - mlist.bounce_you_are_disabled_warnings - ) + 0.0, + time.localtime()[:3], + mlist.bounce_you_are_disabled_warnings + ) mlist.disableBouncingMember(addr, info, bmsg) # Only save the list if we're unlocking it if not locked: @@ -226,8 +165,6 @@ def _probe_bounce(self, mlist, token): # Member was removed before probe bounce returned. # Just ignore it. pass - except Exception as e: - mailman_log('error', 'Error processing probe bounce: %s', str(e)) finally: if not locked: mlist.Unlock() @@ -241,13 +178,12 @@ def __init__(self, slice=None, numslices=1): BounceMixin.__init__(self) def _dispose(self, mlist, msg, msgdata): - """Process a bounce message with proper validation and error handling.""" + """Process a bounce message.""" msgid = msg.get('message-id', 'n/a') filebase = msgdata.get('_filebase', 'unknown') - # Validate bounce message - if not self._validate_bounce(msg): - mailman_log('error', 'Invalid bounce message format for %s', msgid) + # Check retry delay and duplicate processing + if not self._check_retry_delay(msgid, filebase): return False # Make sure we have the most up-to-date state @@ -256,10 +192,25 @@ def _dispose(self, mlist, msg, msgdata): except Errors.MMCorruptListDatabaseError as e: mailman_log('error', 'Failed to load list %s: %s', mlist.internal_name(), e) + self._unmark_message_processed(msgid) return False except Exception as e: mailman_log('error', 'Unexpected error loading list %s: %s', mlist.internal_name(), e) + self._unmark_message_processed(msgid) + return False + + # Validate message type first + msg, success = self._validate_message(msg, msgdata) + if not success: + mailman_log('error', 'Message validation failed for bounce message') + self._unmark_message_processed(msgid) + return False + + # Validate message headers + if not msg.get('message-id'): + mailman_log('error', 'Message missing Message-ID header') + self._unmark_message_processed(msgid) return False try: @@ -269,6 +220,7 @@ def _dispose(self, mlist, msg, msgdata): # Process the bounce if not self._register_bounces(mlist.internal_name(), msg, msgdata): + self._unmark_message_processed(msgid) return False # Queue the bounce for further processing @@ -276,6 +228,7 @@ def _dispose(self, mlist, msg, msgdata): self._queue_bounces(mlist.internal_name(), msg, msgdata) except Exception as e: mailman_log('error', 'Error queueing bounces: %s', e) + self._unmark_message_processed(msgid) return False # Log successful completion @@ -294,6 +247,9 @@ def _dispose(self, mlist, msg, msgdata): mailman_log('error', ' Message type: %s', type(msg).__name__) mailman_log('error', ' Message data: %s', str(msgdata)) mailman_log('error', 'Traceback:\n%s', traceback.format_exc()) + + # Remove from processed messages on error + self._unmark_message_processed(msgid) return False _doperiodic = BounceMixin._doperiodic diff --git a/Mailman/Queue/CommandRunner.py b/Mailman/Queue/CommandRunner.py index 564241f3..1e518a8f 100644 --- a/Mailman/Queue/CommandRunner.py +++ b/Mailman/Queue/CommandRunner.py @@ -24,10 +24,6 @@ import re import sys -import time -import threading -import traceback -import os from Mailman import mm_cfg from Mailman import Utils @@ -235,101 +231,12 @@ def indent(lines): class CommandRunner(Runner): QDIR = mm_cfg.CMDQUEUE_DIR - - def __init__(self, slice=None, numslices=1): - Runner.__init__(self, slice, numslices) - # Rate limiting - self._command_times = {} - self._command_lock = threading.Lock() - self._max_commands_per_hour = 100 - self._command_window = 3600 # 1 hour - # Cleanup - self._last_cleanup = time.time() - self._cleanup_interval = 3600 # Clean up every hour - self._max_command_age = 7 * 24 * 3600 # 7 days max command age - - def _cleanup_old_commands(self): - """Clean up old command files.""" - try: - current_time = time.time() - if current_time - self._last_cleanup < self._cleanup_interval: - return - - with self._command_lock: - # Clean up old command files - for filename in os.listdir(self.QDIR): - if filename.endswith('.pck'): - filepath = os.path.join(self.QDIR, filename) - try: - if current_time - os.path.getmtime(filepath) > self._max_command_age: - os.unlink(filepath) - except OSError: - pass - - # Clean up old command times - cutoff = current_time - self._command_window - self._command_times = {k: v for k, v in self._command_times.items() - if v > cutoff} - - self._last_cleanup = current_time - except Exception as e: - syslog('error', 'Error cleaning up old commands: %s', str(e)) - - def _check_rate_limit(self, sender): - """Check if sender has exceeded rate limit.""" - with self._command_lock: - current_time = time.time() - # Clean up old entries - cutoff = current_time - self._command_window - self._command_times = {k: v for k, v in self._command_times.items() - if v > cutoff} - - # Count commands in window - count = sum(1 for t in self._command_times.values() if t > cutoff) - if count >= self._max_commands_per_hour: - return False - - # Add new command - self._command_times[sender] = current_time - return True - - def _validate_command(self, msg, msgdata): - """Validate command message format.""" - try: - # Check required headers - if not msg.get('from'): - return False - if not msg.get('to'): - return False - if not msg.get('message-id'): - return False - - # Check message size - if len(str(msg)) > mm_cfg.MAX_MESSAGE_SIZE: - return False - - # Check for valid command format - if not msg.get('content-type', '').startswith('text/plain'): - return False - - return True - except Exception: - return False def _dispose(self, mlist, msg, msgdata): - """Process a command message with proper validation and rate limiting.""" - msgid = msg.get('message-id', 'n/a') - filebase = msgdata.get('_filebase', 'unknown') - sender = msg.get_sender() - - # Validate command message - if not self._validate_command(msg, msgdata): - syslog('error', 'Invalid command message format for %s', msgid) - return False - - # Check rate limit - if not self._check_rate_limit(sender): - syslog('error', 'Rate limit exceeded for sender %s', sender) + # Validate message type first + msg, success = self._validate_message(msg, msgdata) + if not success: + mailman_log('error', 'Message validation failed for command message') return False # The policy here is similar to the Replybot policy. If a message has @@ -341,43 +248,44 @@ def _dispose(self, mlist, msg, msgdata): syslog('vette', 'Precedence: %s message discarded by: %s', precedence, mlist.GetRequestEmail()) return False - + # Do replybot for commands + mlist.Load() + Replybot.process(mlist, msg, msgdata) + if mlist.autorespond_requests == 1: + syslog('vette', 'replied and discard') + # w/discard + return False + # Now craft the response + res = Results(mlist, msg, msgdata) + # BAW: Not all the functions of this qrunner require the list to be + # locked. Still, it's more convenient to lock it here and now and + # deal with lock failures in one place. try: - # Log start of processing - syslog('info', 'CommandRunner: Starting to process command message %s (file: %s) for list %s', - msgid, filebase, mlist.internal_name()) - - # Process the command - results = Results(mlist, msg, msgdata) - ret = results.process() - - # Send response if needed - if ret != BADCMD: - results.send_response() - - # Log successful completion - syslog('info', 'CommandRunner: Successfully processed command message %s (file: %s) for list %s', - msgid, filebase, mlist.internal_name()) + mlist.Lock(timeout=mm_cfg.LIST_LOCK_TIMEOUT) + except LockFile.TimeOutError: + # Oh well, try again later return True - except Exception as e: - # Enhanced error logging with more context - syslog('error', 'Error processing command message %s for list %s: %s', - msgid, mlist.internal_name(), str(e)) - syslog('error', 'Message details:') - syslog('error', ' Message ID: %s', msgid) - syslog('error', ' From: %s', msg.get('from', 'unknown')) - syslog('error', ' To: %s', msg.get('to', 'unknown')) - syslog('error', ' Subject: %s', msg.get('subject', '(no subject)')) - syslog('error', ' Message type: %s', type(msg).__name__) - syslog('error', ' Message data: %s', str(msgdata)) - syslog('error', 'Traceback:\n%s', traceback.format_exc()) - return False - finally: - self._cleanup_old_commands() - - def _cleanup(self): - """Clean up resources.""" + # This message will have been delivered to one of mylist-request, + # mylist-join, or mylist-leave, and the message metadata will contain + # a key to which one was used. try: - self._cleanup_old_commands() - except Exception as e: - syslog('error', 'Error in command cleanup: %s', str(e)) + ret = BADCMD + if msgdata.get('torequest'): + ret = res.process() + elif msgdata.get('tojoin'): + ret = res.do_command('join') + elif msgdata.get('toleave'): + ret = res.do_command('leave') + elif msgdata.get('toconfirm'): + mo = re.match(mm_cfg.VERP_CONFIRM_REGEXP, msg.get('to', '')) + if mo: + ret = res.do_command('confirm', (mo.group('cookie'),)) + if ret == BADCMD and mm_cfg.DISCARD_MESSAGE_WITH_NO_COMMAND: + syslog('vette', + 'No command, message discarded, msgid: %s', + msg.get('message-id', 'n/a')) + else: + res.send_response() + mlist.Save() + finally: + mlist.Unlock() diff --git a/Mailman/Queue/IncomingRunner.py b/Mailman/Queue/IncomingRunner.py index dd0b474c..306b5f51 100644 --- a/Mailman/Queue/IncomingRunner.py +++ b/Mailman/Queue/IncomingRunner.py @@ -104,7 +104,7 @@ from io import StringIO import random import signal -import threading +import os from email import message_from_string from Mailman.Message import Message from urllib.parse import parse_qs @@ -129,118 +129,43 @@ class IncomingRunner(Runner): def __init__(self, slice=None, numslices=1): mailman_log('qrunner', 'IncomingRunner: Initializing with slice=%s, numslices=%s', slice, numslices) Runner.__init__(self, slice, numslices) - # Rate limiting - self._message_times = {} - self._message_lock = threading.Lock() - self._max_messages_per_hour = 1000 - self._message_window = 3600 # 1 hour - # Cleanup + # Track processed messages to prevent duplicates + self._processed_messages = set() + # Clean up old messages periodically self._last_cleanup = time.time() self._cleanup_interval = 3600 # Clean up every hour - self._max_message_age = 7 * 24 * 3600 # 7 days max message age mailman_log('qrunner', 'IncomingRunner: Initialization complete') - def _cleanup_old_messages(self): - """Clean up old message files.""" - try: - current_time = time.time() - if current_time - self._last_cleanup < self._cleanup_interval: - return - - with self._message_lock: - # Clean up old message files - for filename in os.listdir(self.QDIR): - if filename.endswith('.pck'): - filepath = os.path.join(self.QDIR, filename) - try: - if current_time - os.path.getmtime(filepath) > self._max_message_age: - os.unlink(filepath) - except OSError: - pass - - # Clean up old message times - cutoff = current_time - self._message_window - self._message_times = {k: v for k, v in self._message_times.items() - if v > cutoff} - - self._last_cleanup = current_time - except Exception as e: - mailman_log('error', 'Error cleaning up old messages: %s', str(e)) - - def _check_rate_limit(self, sender): - """Check if sender has exceeded rate limit.""" - with self._message_lock: - current_time = time.time() - # Clean up old entries - cutoff = current_time - self._message_window - self._message_times = {k: v for k, v in self._message_times.items() - if v > cutoff} - - # Count messages in window - count = sum(1 for t in self._message_times.values() if t > cutoff) - if count >= self._max_messages_per_hour: - return False - - # Add new message - self._message_times[sender] = current_time - return True - - def _validate_message(self, msg, msgdata): - """Validate message format.""" - try: - # Check required headers - if not msg.get('from'): - return False - if not msg.get('to'): - return False - if not msg.get('message-id'): - return False - - # Check message size - if len(str(msg)) > mm_cfg.MAX_MESSAGE_SIZE: - return False - - # Check for valid message format - if not msg.get('content-type', '').startswith(('text/', 'multipart/')): - return False - - return True - except Exception: - return False - def _dispose(self, listname, msg, msgdata): - """Process an incoming message with proper validation and rate limiting.""" # Import MailList here to avoid circular imports from Mailman.MailList import MailList # Track message ID to prevent duplicates msgid = msg.get('message-id', 'n/a') filebase = msgdata.get('_filebase', 'unknown') - sender = msg.get_sender() mailman_log('qrunner', 'IncomingRunner._dispose: Starting to process message %s (file: %s) for list %s', msgid, filebase, listname) - # Validate message - if not self._validate_message(msg, msgdata): - mailman_log('error', 'Invalid message format for %s', msgid) - return False - - # Check rate limit - if not self._check_rate_limit(sender): - mailman_log('error', 'Rate limit exceeded for sender %s', sender) - return False + # Check retry delay and duplicate processing + if not self._check_retry_delay(msgid, filebase): + mailman_log('qrunner', 'IncomingRunner._dispose: Message %s failed retry delay check, moving to shunt queue', + msgid) + # Move to shunt queue and remove from original queue + self._shunt.enqueue(msg, msgdata) + # Get the filebase from msgdata and finish processing it + if filebase: + self._switchboard.finish(filebase) + return 0 # Get the MailList object for the list name try: mlist = MailList(listname, lock=False) mailman_log('qrunner', 'IncomingRunner._dispose: Successfully got MailList object for %s', listname) except Errors.MMListError as e: - mailman_log('error', 'Failed to get list %s: %s', listname, str(e)) - return False - except Exception as e: - mailman_log('error', 'Unexpected error loading list %s: %s', listname, str(e)) - return False + mailman_log('qrunner', 'IncomingRunner._dispose: Failed to get list %s: %s', listname, str(e)) + self._unmark_message_processed(msgid) + return 0 try: # Log start of processing @@ -261,22 +186,22 @@ def _dispose(self, listname, msg, msgdata): return result except Exception as e: # Enhanced error logging with more context - mailman_log('error', 'Error processing message %s for list %s: %s', + mailman_log('qrunner', 'IncomingRunner._dispose: Error processing message %s for list %s: %s', msgid, mlist.internal_name(), str(e)) - mailman_log('error', 'Message details:') - mailman_log('error', ' Message ID: %s', msgid) - mailman_log('error', ' From: %s', msg.get('from', 'unknown')) - mailman_log('error', ' To: %s', msg.get('to', 'unknown')) - mailman_log('error', ' Subject: %s', msg.get('subject', '(no subject)')) - mailman_log('error', ' Message type: %s', type(msg).__name__) - mailman_log('error', ' Message data: %s', str(msgdata)) - mailman_log('error', 'Traceback:\n%s', traceback.format_exc()) - return False - finally: - self._cleanup_old_messages() + mailman_log('qrunner', 'IncomingRunner._dispose: Message details:') + mailman_log('qrunner', ' Message ID: %s', msgid) + mailman_log('qrunner', ' From: %s', msg.get('from', 'unknown')) + mailman_log('qrunner', ' To: %s', msg.get('to', 'unknown')) + mailman_log('qrunner', ' Subject: %s', msg.get('subject', '(no subject)')) + mailman_log('qrunner', ' Message type: %s', type(msg).__name__) + mailman_log('qrunner', ' Message data: %s', str(msgdata)) + mailman_log('qrunner', 'IncomingRunner._dispose: Traceback:\n%s', traceback.format_exc()) + + # Remove from processed messages on error + self._unmark_message_processed(msgid) + return 0 def _get_pipeline(self, mlist, msg, msgdata): - """Get the pipeline for message processing.""" # We must return a copy of the list, otherwise, the first message that # flows through the pipeline will empty it out! pipeline = msgdata.get('pipeline', @@ -287,22 +212,21 @@ def _get_pipeline(self, mlist, msg, msgdata): return pipeline def _dopipeline(self, mlist, msg, msgdata, pipeline): - """Process message through pipeline with proper error handling.""" msgid = msg.get('message-id', 'n/a') mailman_log('qrunner', 'IncomingRunner._dopipeline: Starting pipeline processing for message %s', msgid) # Validate pipeline state if not pipeline: - mailman_log('error', 'Empty pipeline for message %s', msgid) + mailman_log('qrunner', 'IncomingRunner._dopipeline: Empty pipeline for message %s', msgid) return 0 if 'pipeline' in msgdata and msgdata['pipeline'] != pipeline: - mailman_log('error', 'Pipeline state mismatch for message %s', msgid) + mailman_log('qrunner', 'IncomingRunner._dopipeline: Pipeline state mismatch for message %s', msgid) return 0 # Ensure message is a Mailman.Message if not isinstance(msg, Message): try: - mailman_log('qrunner', 'Converting email.message.Message to Mailman.Message for %s', msgid) + mailman_log('qrunner', 'IncomingRunner._dopipeline: Converting email.message.Message to Mailman.Message for %s', msgid) mailman_msg = Message() # Copy all attributes from the original message for key, value in msg.items(): @@ -317,37 +241,117 @@ def _dopipeline(self, mlist, msg, msgdata, pipeline): # Update msgdata references if needed if 'msg' in msgdata: msgdata['msg'] = msg - mailman_log('qrunner', 'Successfully converted message %s', msgid) + mailman_log('qrunner', 'IncomingRunner._dopipeline: Successfully converted message %s', msgid) except Exception as e: - mailman_log('error', 'Failed to convert message to Mailman.Message: %s\nTraceback:\n%s', + mailman_log('qrunner', 'IncomingRunner._dopipeline: Failed to convert message to Mailman.Message: %s\nTraceback:\n%s', str(e), traceback.format_exc()) return 0 - # Process through pipeline - try: - for handler in pipeline: + # Validate required Mailman.Message methods + required_methods = ['get_sender', 'get', 'items', 'is_multipart', 'get_payload'] + for method in required_methods: + if not hasattr(msg, method): + mailman_log('qrunner', 'IncomingRunner._dopipeline: Message object missing required method %s', method) + return 0 + + while pipeline: + handler = pipeline.pop(0) + modname = 'Mailman.Handlers.' + handler + try: + mailman_log('qrunner', 'IncomingRunner._dopipeline: Processing message %s through handler %s', + msgid, handler) + __import__(modname) + + # Store original PID and track child processes + original_pid = os.getpid() + child_pids = set() + + # Process the message + sys.modules[modname].process(mlist, msg, msgdata) + + # Check for process leaks + current_pid = os.getpid() + if current_pid != original_pid: + mailman_log('qrunner', 'IncomingRunner._dopipeline: Child process leaked through in handler %s: original_pid=%d, current_pid=%d', + modname, original_pid, current_pid) + # Try to clean up any child processes + try: + os.kill(original_pid, signal.SIGTERM) + except: + pass + os._exit(1) + + # Clean up any child processes try: - handler.process(mlist, msg, msgdata) - except Exception as e: - mailman_log('error', 'Handler %s failed for message %s: %s', - handler.__name__, msgid, str(e)) - raise PipelineError(str(e)) - return 1 - except PipelineError as e: - mailman_log('error', 'Pipeline processing failed for message %s: %s', - msgid, str(e)) - return 0 - except Exception as e: - mailman_log('error', 'Unexpected error in pipeline for message %s: %s', - msgid, str(e)) - return 0 + while True: + pid, status = os.waitpid(-1, os.WNOHANG) + if pid == 0: + break + child_pids.add(pid) + except ChildProcessError: + pass + + if child_pids: + mailman_log('qrunner', 'IncomingRunner._dopipeline: Cleaned up %d child processes from handler %s: %s', + len(child_pids), modname, child_pids) + + mailman_log('qrunner', 'IncomingRunner._dopipeline: Successfully processed message %s through handler %s', + msgid, handler) + + except Errors.DiscardMessage: + # Throw the message away; we need do nothing else with it. + pipeline.insert(0, handler) + mailman_log('qrunner', """IncomingRunner._dopipeline: Message discarded, msgid: %s + list: %s, + handler: %s""", + msgid, mlist.internal_name(), handler) + return 0 + except Errors.HoldMessage: + # Message is being held for moderation, no need to requeue + mailman_log('qrunner', """IncomingRunner._dopipeline: Message held for moderation, msgid: %s + list: %s, + handler: %s""", + msgid, mlist.internal_name(), handler) + # Add message ID to processed set to prevent duplicate processing + self._processed_messages.add(msgid) + return 0 + except Errors.RejectMessage as e: + pipeline.insert(0, handler) + mailman_log('qrunner', """IncomingRunner._dopipeline: Message rejected, msgid: %s + list: %s, + handler: %s, + reason: %s""", + msgid, mlist.internal_name(), handler, e.notice()) + mlist.BounceMessage(msg, msgdata, e) + return 0 + except Exception as e: + # Log the full traceback for debugging + mailman_log('qrunner', 'IncomingRunner._dopipeline: Error in handler %s for message %s: %s\n%s', + modname, msgid, str(e), traceback.format_exc()) + # Put the handler back in the pipeline + pipeline.insert(0, handler) + # Try to move message to shunt queue + try: + self._shunt.enqueue(msg, msgdata) + mailman_log('qrunner', 'IncomingRunner._dopipeline: Moved failed message %s to shunt queue', msgid) + except Exception as shunt_error: + mailman_log('qrunner', 'IncomingRunner._dopipeline: Failed to move message %s to shunt queue: %s', + msgid, str(shunt_error)) + raise + return 0 def _cleanup(self): - """Clean up resources.""" - try: - self._cleanup_old_messages() - except Exception as e: - mailman_log('error', 'Error in message cleanup: %s', str(e)) + """Clean up any resources used by the pipeline.""" + mailman_log('qrunner', 'IncomingRunner._cleanup: Starting cleanup') + # Clean up child processes + reap(self._kids, once=True) + # Close any open file descriptors + for fd in range(3, 1024): # Skip stdin, stdout, stderr + try: + os.close(fd) + except OSError: + pass + mailman_log('qrunner', 'IncomingRunner._cleanup: Cleanup complete') def _oneloop(self): mailman_log('qrunner', 'IncomingRunner._oneloop: Starting loop') From 4b3d23de4a3be4bd428387211395fc916d7cbbeb Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 12 May 2025 19:37:44 -0400 Subject: [PATCH 372/748] update --- Mailman/Cgi/admin.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/Mailman/Cgi/admin.py b/Mailman/Cgi/admin.py index 8a9e326d..69d95a69 100644 --- a/Mailman/Cgi/admin.py +++ b/Mailman/Cgi/admin.py @@ -58,13 +58,13 @@ def main(): try: # Log page load mailman_log('info', 'admin: Page load started') - print("DEBUG: Entered main()", flush=True) + mailman_log('debug', 'Entered main()') # Initialize document early doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) # Parse form data first since we need it for authentication try: - print("DEBUG: Parsing form data", flush=True) + mailman_log('debug', 'Parsing form data') if os.environ.get('REQUEST_METHOD') == 'POST': content_length = int(os.environ.get('CONTENT_LENGTH', 0)) if content_length > 0: @@ -75,7 +75,7 @@ def main(): else: query_string = os.environ.get('QUERY_STRING', '') cgidata = urllib.parse.parse_qs(query_string, keep_blank_values=True) - print(f"DEBUG: cgidata after parse: {cgidata}", flush=True) + mailman_log('debug', 'cgidata after parse: %s', str(cgidata)) except Exception as e: print('Status: 400 Bad Request') print('Content-type: text/html; charset=utf-8\n') @@ -86,18 +86,18 @@ def main(): return # Get the list name parts = Utils.GetPathPieces() - print(f"DEBUG: Path parts: {parts}", flush=True) + mailman_log('debug', 'Path parts: %s', str(parts)) if not parts: handle_no_list() return listname = parts[0].lower() mailman_log('info', 'admin: Processing list "%s"', listname) - print(f"DEBUG: List name: {listname}", flush=True) + mailman_log('debug', 'List name: %s', listname) if isinstance(listname, bytes): listname = listname.decode('utf-8', 'replace') try: mlist = MailList.MailList(listname, lock=0) - print("DEBUG: Loaded MailList", flush=True) + mailman_log('debug', 'Loaded MailList') except Errors.MMListError as e: safelistname = Utils.websafe(listname) print('Status: 404 Not Found') @@ -110,7 +110,7 @@ def main(): i18n.set_language(mlist.preferred_language) # If the user is not authenticated, we're done. try: - print("DEBUG: Checking authentication", flush=True) + mailman_log('debug', 'Checking authentication') if os.environ.get('REQUEST_METHOD') == 'POST': content_length = int(os.environ.get('CONTENT_LENGTH', 0)) if content_length > 0: @@ -125,7 +125,7 @@ def main(): cgidata = urllib.parse.parse_qs(query_string, keep_blank_values=True) for key in cgidata: cgidata[key] = [v.decode('latin-1') if isinstance(v, bytes) else v for v in cgidata[key]] - print(f"DEBUG: cgidata before auth: {cgidata}", flush=True) + mailman_log('debug', 'cgidata before auth: %s', str(cgidata)) except Exception as e: doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) @@ -151,17 +151,17 @@ def main(): os.environ['HTTP_COOKIE'] = '' csrf_checked = True try: - print("DEBUG: Calling WebAuthenticate", flush=True) + mailman_log('debug', 'Calling WebAuthenticate') auth_result = mlist.WebAuthenticate((mm_cfg.AuthListAdmin, mm_cfg.AuthSiteAdmin), cgidata.get('adminpw', [''])[0]) - print(f"DEBUG: WebAuthenticate result: {auth_result}", flush=True) + mailman_log('debug', 'WebAuthenticate result: %s', str(auth_result)) except Exception as e: mailman_log('error', 'admin: Exception during WebAuthenticate: %s\n%s', str(e), traceback.format_exc()) - print("DEBUG: Exception during WebAuthenticate", flush=True) + mailman_log('debug', 'Exception during WebAuthenticate') raise if not auth_result: - print("DEBUG: Not authenticated, calling loginpage", flush=True) + mailman_log('debug', 'Not authenticated, calling loginpage') if 'adminpw' in cgidata: msg = Bold(FontSize('+1', _('Authorization failed.'))).Format() remote = os.environ.get('HTTP_FORWARDED_FOR', @@ -174,9 +174,9 @@ def main(): else: msg = '' Auth.loginpage(mlist, 'admin', msg=msg) - print("DEBUG: Called Auth.loginpage", flush=True) + mailman_log('debug', 'Called Auth.loginpage') return - print("DEBUG: Authenticated, proceeding to admin page", flush=True) + mailman_log('debug', 'Authenticated, proceeding to admin page') # Which subcategory was requested? Default is `general' if len(parts) == 1: category = 'general' @@ -190,7 +190,7 @@ def main(): doc = Document() doc.set_language(mlist.preferred_language) form = Form(mlist=mlist, contexts=AUTH_CONTEXTS) - print(f"DEBUG: category={category}, subcat={subcat}", flush=True) + mailman_log('debug', 'category=%s, subcat=%s', category, subcat) # Now dispatch to the appropriate handler if category == 'general': show_variables(mlist, category, subcat, cgidata, doc) @@ -207,7 +207,7 @@ def main(): doc.AddItem(Bold(_('No such category: %(category)s') % { 'category': category })) - print("DEBUG: About to print doc.Format()", flush=True) + mailman_log('debug', 'About to print doc.Format()') print(doc.Format()) except Exception as e: doc = Document() From 52e21234c547e27a70e94d6c1b562016e7526bf4 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 12 May 2025 19:39:29 -0400 Subject: [PATCH 373/748] update --- Mailman/Cgi/admin.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Mailman/Cgi/admin.py b/Mailman/Cgi/admin.py index 69d95a69..461d22ac 100644 --- a/Mailman/Cgi/admin.py +++ b/Mailman/Cgi/admin.py @@ -570,16 +570,20 @@ def show_results(mlist, doc, category, subcat, cgidata): doc.AddItem(mlist.GetMailmanFooter()) def show_variables(mlist, category, subcat, cgidata, doc): + mailman_log('debug', 'show_variables called with category=%s, subcat=%s', category, subcat) options = mlist.GetConfigInfo(category, subcat) + mailman_log('debug', 'Got config info: %s', str(options)) # The table containing the results table = Table(cellspacing=3, cellpadding=4, width='100%') # Get and portray the text label for the category. categories = mlist.GetConfigCategories() + mailman_log('debug', 'Got config categories: %s', str(categories)) label = _(categories[category][0]) if isinstance(label, bytes): label = label.decode('latin1', 'replace') + mailman_log('debug', 'Category label: %s', label) table.AddRow([Center(Header(2, label))]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, @@ -590,12 +594,14 @@ def show_variables(mlist, category, subcat, cgidata, doc): description = options[0] if isinstance(description, bytes): description = description.decode('latin1', 'replace') + mailman_log('debug', 'Description: %s', description) if type(description) is str: table.AddRow([description]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) options = options[1:] if not options: + mailman_log('debug', 'No options to display') return table # Add the global column headers @@ -607,6 +613,7 @@ def show_variables(mlist, category, subcat, cgidata, doc): width='85%') for item in options: + mailman_log('debug', 'Processing item: %s', str(item)) if type(item) == str: # The very first banner option (string in an options list) is # treated as a general description, while any others are @@ -621,12 +628,15 @@ def show_variables(mlist, category, subcat, cgidata, doc): add_options_table_item(mlist, category, subcat, table, item) table.AddRow(['
                  ']) table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) + mailman_log('debug', 'Returning table with %d rows', table.GetCurrentRowIndex() + 1) return table def add_options_table_item(mlist, category, subcat, table, item, detailsp=1): + mailman_log('debug', 'Adding options table item: %s', str(item)) # Add a row to an options table with the item description and value. varname, kind, params, extra, descr, elaboration = \ get_item_characteristics(item) + mailman_log('debug', 'Item characteristics: varname=%s, kind=%s', varname, kind) if elaboration is None: elaboration = descr descr = get_item_gui_description(mlist, category, subcat, From e6b56f644a10333653a6b4d68528992f265bdb7c Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 12 May 2025 19:43:00 -0400 Subject: [PATCH 374/748] add table --- Mailman/Cgi/admin.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Mailman/Cgi/admin.py b/Mailman/Cgi/admin.py index 461d22ac..5be82658 100644 --- a/Mailman/Cgi/admin.py +++ b/Mailman/Cgi/admin.py @@ -193,7 +193,10 @@ def main(): mailman_log('debug', 'category=%s, subcat=%s', category, subcat) # Now dispatch to the appropriate handler if category == 'general': - show_variables(mlist, category, subcat, cgidata, doc) + table = show_variables(mlist, category, subcat, cgidata, doc) + form.AddItem(table) + form.AddItem(Center(submit_button())) + doc.AddItem(form) elif category == 'members': membership_options(mlist, subcat, cgidata, doc, form) elif category == 'passwords': From 90014baa73d4ae3ea4e50fedafde2996bb283cf7 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 12 May 2025 19:45:44 -0400 Subject: [PATCH 375/748] add table --- Mailman/Cgi/admin.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Mailman/Cgi/admin.py b/Mailman/Cgi/admin.py index 5be82658..b3e26965 100644 --- a/Mailman/Cgi/admin.py +++ b/Mailman/Cgi/admin.py @@ -193,10 +193,7 @@ def main(): mailman_log('debug', 'category=%s, subcat=%s', category, subcat) # Now dispatch to the appropriate handler if category == 'general': - table = show_variables(mlist, category, subcat, cgidata, doc) - form.AddItem(table) - form.AddItem(Center(submit_button())) - doc.AddItem(form) + show_results(mlist, doc, category, subcat, cgidata) elif category == 'members': membership_options(mlist, subcat, cgidata, doc, form) elif category == 'passwords': From 4d86ca694f13355e09c82d1f3055401c6d825403 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 12 May 2025 19:47:52 -0400 Subject: [PATCH 376/748] admin cgi fix --- Mailman/Cgi/admin.py | 93 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 76 insertions(+), 17 deletions(-) diff --git a/Mailman/Cgi/admin.py b/Mailman/Cgi/admin.py index b3e26965..12b5f4af 100644 --- a/Mailman/Cgi/admin.py +++ b/Mailman/Cgi/admin.py @@ -187,28 +187,87 @@ def main(): else: category = parts[1] subcat = parts[2] + + # Sanity check - validate category against available categories + if category not in list(mlist.GetConfigCategories().keys()): + category = 'general' + + # Is the request for variable details? + varhelp = None + qsenviron = os.environ.get('QUERY_STRING') + parsedqs = None + if qsenviron: + parsedqs = urllib.parse.parse_qs(qsenviron) + if 'VARHELP' in cgidata: + varhelp = cgidata['VARHELP'][0] + elif parsedqs: + # POST methods, even if their actions have a query string, don't get + # put into FieldStorage's keys :-( + qs = parsedqs.get('VARHELP') + if qs and type(qs) is list: + varhelp = qs[0] + if varhelp: + option_help(mlist, varhelp) + return + doc = Document() doc.set_language(mlist.preferred_language) form = Form(mlist=mlist, contexts=AUTH_CONTEXTS) mailman_log('debug', 'category=%s, subcat=%s', category, subcat) - # Now dispatch to the appropriate handler - if category == 'general': + + # From this point on, the MailList object must be locked + mlist.Lock() + try: + # Install the emergency shutdown signal handler + def sigterm_handler(signum, frame, mlist=mlist): + # Make sure the list gets unlocked... + mlist.Unlock() + # ...and ensure we exit + sys.exit(0) + signal.signal(signal.SIGTERM, sigterm_handler) + + if cgidata: + if csrf_checked: + # There are options to change + change_options(mlist, category, subcat, cgidata, doc) + else: + doc.addError( + _('The form lifetime has expired. (request forgery check)')) + # Let the list sanity check the changed values + mlist.CheckValues() + + # Additional sanity checks + if not mlist.digestable and not mlist.nondigestable: + doc.addError( + _(f'''You have turned off delivery of both digest and + non-digest messages. This is an incompatible state of + affairs. You must turn on either digest delivery or + non-digest delivery or your mailing list will basically be + unusable.'''), tag=_('Warning: ')) + + dm = mlist.getDigestMemberKeys() + if not mlist.digestable and dm: + doc.addError( + _(f'''You have digest members, but digests are turned + off. Those people will not receive mail. + Affected member(s) %(dm)r.'''), + tag=_('Warning: ')) + rm = mlist.getRegularMemberKeys() + if not mlist.nondigestable and rm: + doc.addError( + _(f'''You have regular list members but non-digestified mail is + turned off. They will receive non-digestified mail until you + fix this problem. Affected member(s) %(rm)r.'''), + tag=_('Warning: ')) + + # Show the results page show_results(mlist, doc, category, subcat, cgidata) - elif category == 'members': - membership_options(mlist, subcat, cgidata, doc, form) - elif category == 'passwords': - password_inputs(mlist) - elif category == 'options': - change_options(mlist, category, subcat, cgidata, doc) - elif category == 'help': - option_help(mlist, cgidata.get('VARHELP', [''])[0]) - else: - doc.AddItem(Header(2, _('Error'))) - doc.AddItem(Bold(_('No such category: %(category)s') % { - 'category': category - })) - mailman_log('debug', 'About to print doc.Format()') - print(doc.Format()) + mailman_log('debug', 'About to print doc.Format()') + print(doc.Format()) + mlist.Save() + finally: + # Now be sure to unlock the list + mlist.Unlock() except Exception as e: doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) From 9059ef65479d33d0a80a4e9ac653af85a2894cdc Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 12 May 2025 19:51:44 -0400 Subject: [PATCH 377/748] update --- Mailman/Cgi/admin.py | 8 +++ Mailman/Handlers/Hold.py | 2 +- Mailman/Handlers/ToOutgoing.py | 2 +- Mailman/Queue/Switchboard.py | 118 ++++++++++++++++----------------- 4 files changed, 69 insertions(+), 61 deletions(-) diff --git a/Mailman/Cgi/admin.py b/Mailman/Cgi/admin.py index 12b5f4af..d5d6f19d 100644 --- a/Mailman/Cgi/admin.py +++ b/Mailman/Cgi/admin.py @@ -152,10 +152,18 @@ def main(): csrf_checked = True try: mailman_log('debug', 'Calling WebAuthenticate') + mailman_log('debug', 'Authentication contexts: %s', str((mm_cfg.AuthListAdmin, mm_cfg.AuthSiteAdmin))) + mailman_log('debug', 'Password provided: %s', 'Yes' if cgidata.get('adminpw', [''])[0] else 'No') + mailman_log('debug', 'Cookie present: %s', 'Yes' if os.environ.get('HTTP_COOKIE') else 'No') auth_result = mlist.WebAuthenticate((mm_cfg.AuthListAdmin, mm_cfg.AuthSiteAdmin), cgidata.get('adminpw', [''])[0]) mailman_log('debug', 'WebAuthenticate result: %s', str(auth_result)) + if not auth_result: + mailman_log('debug', 'Authentication failed - checking auth contexts') + for context in (mm_cfg.AuthListAdmin, mm_cfg.AuthSiteAdmin): + mailman_log('debug', 'Checking context %s: %s', + context, str(mlist.AuthContextInfo(context))) except Exception as e: mailman_log('error', 'admin: Exception during WebAuthenticate: %s\n%s', str(e), traceback.format_exc()) mailman_log('debug', 'Exception during WebAuthenticate') diff --git a/Mailman/Handlers/Hold.py b/Mailman/Handlers/Hold.py index 3f797e19..4167eeb3 100644 --- a/Mailman/Handlers/Hold.py +++ b/Mailman/Handlers/Hold.py @@ -260,7 +260,7 @@ def hold_for_approval(mlist, msg, msgdata, exc): finally: mlist.Unlock() else: - cookie = mlist.pend_new(Pending.HELD_MESSAGE, id) + cookie = mlist.pend_new(Pending.HELD_MESSAGE, id) if not fromusenet and ackp(msg) and mlist.respond_to_post_requests and \ mlist.autorespondToSender(sender, mlist.getMemberLanguage(sender)): diff --git a/Mailman/Handlers/ToOutgoing.py b/Mailman/Handlers/ToOutgoing.py index 26e0b92d..927e623b 100644 --- a/Mailman/Handlers/ToOutgoing.py +++ b/Mailman/Handlers/ToOutgoing.py @@ -26,7 +26,7 @@ import traceback from Mailman.Logging.Syslog import mailman_log - + def process(mlist, msg, msgdata): """Process the message by moving it to the outgoing queue.""" msgid = msg.get('message-id', 'n/a') diff --git a/Mailman/Queue/Switchboard.py b/Mailman/Queue/Switchboard.py index 598e1213..3e9f4750 100644 --- a/Mailman/Queue/Switchboard.py +++ b/Mailman/Queue/Switchboard.py @@ -501,7 +501,7 @@ def recover_backup_files(self): try: # First try to validate the backup file with open(src, 'rb') as fp: - try: + try: # Try to read the entire file first to check for EOF content = fp.read() if not content: @@ -511,86 +511,86 @@ def recover_backup_files(self): from io import BytesIO fp = BytesIO(content) - try: - msg = pickle.load(fp, fix_imports=True, encoding='latin1') - data_pos = fp.tell() - data = pickle.load(fp, fix_imports=True, encoding='latin1') + try: + msg = pickle.load(fp, fix_imports=True, encoding='latin1') + data_pos = fp.tell() + data = pickle.load(fp, fix_imports=True, encoding='latin1') except (EOFError, pickle.UnpicklingError) as e: mailman_log('error', 'Corrupted backup file %s: %s\nTraceback:\n%s', filebase, str(e), traceback.format_exc()) - self.finish(filebase, preserve=True) - continue - + self.finish(filebase, preserve=True) + continue + # Validate the unpickled data if not isinstance(data, dict): raise TypeError('Invalid data format in backup file') # Update metadata - data['_bak_count'] = data.setdefault('_bak_count', 0) + 1 - data['_last_attempt'] = time.time() - if '_error_history' not in data: - data['_error_history'] = [] - if '_traceback' in data: - data['_error_history'].append({ - 'error': data.get('_last_error', 'unknown'), - 'traceback': data.get('_traceback', 'none'), - 'time': data.get('_last_attempt', 0) - }) + data['_bak_count'] = data.setdefault('_bak_count', 0) + 1 + data['_last_attempt'] = time.time() + if '_error_history' not in data: + data['_error_history'] = [] + if '_traceback' in data: + data['_error_history'].append({ + 'error': data.get('_last_error', 'unknown'), + 'traceback': data.get('_traceback', 'none'), + 'time': data.get('_last_attempt', 0) + }) # Write the updated data back with open(src, 'wb') as out_fp: - if data.get('_parsemsg'): - protocol = 0 - else: - protocol = 1 + if data.get('_parsemsg'): + protocol = 0 + else: + protocol = 1 pickle.dump(data, out_fp, protocol=4, fix_imports=True) out_fp.flush() if hasattr(os, 'fsync'): os.fsync(out_fp.fileno()) - - # Log detailed information about the retry - mailman_log('warning', - 'Message retry attempt %d/%d: %s (queue: %s, ' - 'message-id: %s, listname: %s, recipients: %s, ' - 'error: %s, last attempt: %s, traceback: %s)', - data['_bak_count'], + + # Log detailed information about the retry + mailman_log('warning', + 'Message retry attempt %d/%d: %s (queue: %s, ' + 'message-id: %s, listname: %s, recipients: %s, ' + 'error: %s, last attempt: %s, traceback: %s)', + data['_bak_count'], + MAX_BAK_COUNT, + filebase, + self.__whichq, + data.get('message-id', 'unknown'), + data.get('listname', 'unknown'), + data.get('recips', 'unknown'), + data.get('_last_error', 'unknown'), + time.ctime(data.get('_last_attempt', 0)), + data.get('_traceback', 'none')) + + if data['_bak_count'] >= MAX_BAK_COUNT: + mailman_log('error', + 'Backup file exceeded maximum retry count (%d). ' + 'Moving to shunt queue: %s (original queue: %s, ' + 'retry count: %d, last error: %s, ' + 'message-id: %s, listname: %s, ' + 'recipients: %s, error history: %s, ' + 'last traceback: %s, full path: %s)', MAX_BAK_COUNT, filebase, self.__whichq, + data['_bak_count'], + data.get('_last_error', 'unknown'), data.get('message-id', 'unknown'), data.get('listname', 'unknown'), data.get('recips', 'unknown'), - data.get('_last_error', 'unknown'), - time.ctime(data.get('_last_attempt', 0)), - data.get('_traceback', 'none')) - - if data['_bak_count'] >= MAX_BAK_COUNT: - mailman_log('error', - 'Backup file exceeded maximum retry count (%d). ' - 'Moving to shunt queue: %s (original queue: %s, ' - 'retry count: %d, last error: %s, ' - 'message-id: %s, listname: %s, ' - 'recipients: %s, error history: %s, ' - 'last traceback: %s, full path: %s)', - MAX_BAK_COUNT, - filebase, - self.__whichq, - data['_bak_count'], - data.get('_last_error', 'unknown'), - data.get('message-id', 'unknown'), - data.get('listname', 'unknown'), - data.get('recips', 'unknown'), - data.get('_error_history', 'unknown'), - data.get('_traceback', 'none'), - os.path.join(self.__whichq, filebase + '.bak')) + data.get('_error_history', 'unknown'), + data.get('_traceback', 'none'), + os.path.join(self.__whichq, filebase + '.bak')) + self.finish(filebase, preserve=True) + else: + try: + os.rename(src, dst) + except OSError as e: + mailman_log('error', 'Failed to rename backup file %s (full paths: %s -> %s): %s\nTraceback:\n%s', + filebase, os.path.join(self.__whichq, filebase + '.bak'), os.path.join(self.__whichq, filebase + '.pck'), str(e), traceback.format_exc()) self.finish(filebase, preserve=True) - else: - try: - os.rename(src, dst) - except OSError as e: - mailman_log('error', 'Failed to rename backup file %s (full paths: %s -> %s): %s\nTraceback:\n%s', - filebase, os.path.join(self.__whichq, filebase + '.bak'), os.path.join(self.__whichq, filebase + '.pck'), str(e), traceback.format_exc()) - self.finish(filebase, preserve=True) except Exception as e: mailman_log('error', 'Failed to process backup file %s (full path: %s): %s\nTraceback:\n%s', From cf8bd6ddf2223d5411b98d4fc628dae77372139b Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 12 May 2025 19:54:51 -0400 Subject: [PATCH 378/748] update --- Mailman/Handlers/Hold.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Mailman/Handlers/Hold.py b/Mailman/Handlers/Hold.py index 4167eeb3..d1bda51c 100644 --- a/Mailman/Handlers/Hold.py +++ b/Mailman/Handlers/Hold.py @@ -245,13 +245,6 @@ def hold_for_approval(mlist, msg, msgdata, exc): 'subject' : usersubject, 'admindb_url': mlist.GetScriptURL('admindb', absolute=1), } - # We may want to send a notification to the original sender too - fromusenet = msgdata.get('fromusenet') - # Since we're sending two messages, which may potentially be in different - # languages (the user's preferred and the list's preferred for the admin), - # we need to play some i18n games here. Since the current language - # context ought to be set up for the user, let's craft his message first. - # Ensure the list is locked before calling pend_new if not mlist.Locked(): mlist.Lock() @@ -260,8 +253,15 @@ def hold_for_approval(mlist, msg, msgdata, exc): finally: mlist.Unlock() else: - cookie = mlist.pend_new(Pending.HELD_MESSAGE, id) + cookie = mlist.pend_new(Pending.HELD_MESSAGE, id) + # We may want to send a notification to the original sender too + fromusenet = msgdata.get('fromusenet') + # Since we're sending two messages, which may potentially be in different + # languages (the user's preferred and the list's preferred for the admin), + # we need to play some i18n games here. Since the current language + # context ought to be set up for the user, let's craft his message first. + if not fromusenet and ackp(msg) and mlist.respond_to_post_requests and \ mlist.autorespondToSender(sender, mlist.getMemberLanguage(sender)): # Get a confirmation cookie From 0dc8c3f92eebc2735a8f9260a7adc73367430ef2 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 12 May 2025 19:57:55 -0400 Subject: [PATCH 379/748] update --- Mailman/Queue/Switchboard.py | 274 +++++++++++++++++++++++------------ 1 file changed, 178 insertions(+), 96 deletions(-) diff --git a/Mailman/Queue/Switchboard.py b/Mailman/Queue/Switchboard.py index 3e9f4750..78aafb55 100644 --- a/Mailman/Queue/Switchboard.py +++ b/Mailman/Queue/Switchboard.py @@ -263,37 +263,112 @@ def dequeue(self, filebase): msgsave = fp.read() mailman_log('debug', 'Switchboard.dequeue: Read %d bytes from backup file %s', len(msgsave), backfile) try: - # Try Python 3 protocol first - mailman_log('debug', 'Switchboard.dequeue: Attempting to unpickle data from backup file %s using Python 3 protocol', backfile) - data = pickle.loads(msgsave, encoding='latin1', fix_imports=True) - mailman_log('debug', 'Switchboard.dequeue: Successfully unpickled data from backup file, type: %s', type(data)) - - if isinstance(data, tuple): - msg, data = data - mailman_log('debug', 'Switchboard.dequeue: Unpickled tuple from backup with msg type: %s, data type: %s', - type(msg), type(data)) - else: - msg = None - mailman_log('debug', 'Switchboard.dequeue: Unpickled non-tuple data from backup: %s', type(data)) + # First try to validate the backup file + with open(backfile, 'rb') as fp: + content = fp.read() + if not content: + raise EOFError('Empty backup file') - except (pickle.UnpicklingError, ValueError) as e: - mailman_log('debug', 'Switchboard.dequeue: Python 3 protocol failed on backup, trying Python 2 protocol: %s', str(e)) - # Fall back to Python 2 protocol - data = pickle.loads(msgsave, encoding='latin1', fix_imports=True) - mailman_log('debug', 'Switchboard.dequeue: Successfully unpickled data from backup using Python 2 protocol, type: %s', type(data)) - - if isinstance(data, tuple): - msg, data = data - mailman_log('debug', 'Switchboard.dequeue: Unpickled tuple from backup with msg type: %s, data type: %s', - type(msg), type(data)) - else: - msg = None - mailman_log('debug', 'Switchboard.dequeue: Unpickled non-tuple data from backup: %s', type(data)) + # Create a BytesIO object to read from the content + from io import BytesIO + fp = BytesIO(content) + + try: + msg = pickle.load(fp, fix_imports=True, encoding='latin1') + data_pos = fp.tell() + data = pickle.load(fp, fix_imports=True, encoding='latin1') + except (EOFError, pickle.UnpicklingError) as e: + mailman_log('error', 'Corrupted backup file %s: %s\nTraceback:\n%s', + filebase, str(e), traceback.format_exc()) + self.finish(filebase, preserve=True) + return None, None - except (IOError, OSError) as e: - if e.errno != errno.ENOENT: - raise - mailman_log('error', 'Switchboard.dequeue: Both primary and backup files not found for %s', filebase) + # Validate the unpickled data + if not isinstance(data, dict): + raise TypeError('Invalid data format in backup file') + + # Update metadata + data['_bak_count'] = data.setdefault('_bak_count', 0) + 1 + data['_last_attempt'] = time.time() + if '_error_history' not in data: + data['_error_history'] = [] + if '_traceback' in data: + data['_error_history'].append({ + 'error': data.get('_last_error', 'unknown'), + 'traceback': data.get('_traceback', 'none'), + 'time': data.get('_last_attempt', 0) + }) + + # Write the updated data back + try: + with open(backfile, 'wb') as out_fp: + if data.get('_parsemsg'): + protocol = 0 + else: + protocol = 1 + pickle.dump(data, out_fp, protocol=4, fix_imports=True) + out_fp.flush() + if hasattr(os, 'fsync'): + os.fsync(out_fp.fileno()) + except Exception as e: + mailman_log('error', 'Failed to write updated data to backup file %s: %s\nTraceback:\n%s', + filebase, str(e), traceback.format_exc()) + self.finish(filebase, preserve=True) + return None, None + + # Log detailed information about the retry + mailman_log('warning', + 'Message retry attempt %d/%d: %s (queue: %s, ' + 'message-id: %s, listname: %s, recipients: %s, ' + 'error: %s, last attempt: %s, traceback: %s)', + data['_bak_count'], + MAX_BAK_COUNT, + filebase, + self.__whichq, + data.get('message-id', 'unknown'), + data.get('listname', 'unknown'), + data.get('recips', 'unknown'), + data.get('_last_error', 'unknown'), + time.ctime(data.get('_last_attempt', 0)), + data.get('_traceback', 'none')) + + if data['_bak_count'] >= MAX_BAK_COUNT: + mailman_log('error', + 'Backup file exceeded maximum retry count (%d). ' + 'Moving to shunt queue: %s (original queue: %s, ' + 'retry count: %d, last error: %s, ' + 'message-id: %s, listname: %s, ' + 'recipients: %s, error history: %s, ' + 'last traceback: %s, full path: %s)', + MAX_BAK_COUNT, + filebase, + self.__whichq, + data['_bak_count'], + data.get('_last_error', 'unknown'), + data.get('message-id', 'unknown'), + data.get('listname', 'unknown'), + data.get('recips', 'unknown'), + data.get('_error_history', 'unknown'), + data.get('_traceback', 'none'), + os.path.join(self.__whichq, filebase + '.bak')) + self.finish(filebase, preserve=True) + return None, None + else: + try: + os.rename(backfile, filename) + except OSError as e: + mailman_log('error', 'Failed to rename backup file %s (full paths: %s -> %s): %s\nTraceback:\n%s', + filebase, os.path.join(self.__whichq, filebase + '.bak'), os.path.join(self.__whichq, filebase + '.pck'), str(e), traceback.format_exc()) + self.finish(filebase, preserve=True) + return None, None + except Exception as e: + mailman_log('error', 'Failed to process backup file %s (full path: %s): %s\nTraceback:\n%s', + filebase, os.path.join(self.__whichq, filebase + '.bak'), str(e), traceback.format_exc()) + self.finish(filebase, preserve=True) + return None, None + except Exception as e: + mailman_log('error', 'Failed to process backup file %s (full path: %s): %s\nTraceback:\n%s', + filebase, os.path.join(self.__whichq, filebase + '.bak'), str(e), traceback.format_exc()) return None, None # Move to backup file @@ -501,7 +576,7 @@ def recover_backup_files(self): try: # First try to validate the backup file with open(src, 'rb') as fp: - try: + try: # Try to read the entire file first to check for EOF content = fp.read() if not content: @@ -511,97 +586,104 @@ def recover_backup_files(self): from io import BytesIO fp = BytesIO(content) - try: - msg = pickle.load(fp, fix_imports=True, encoding='latin1') - data_pos = fp.tell() - data = pickle.load(fp, fix_imports=True, encoding='latin1') + try: + msg = pickle.load(fp, fix_imports=True, encoding='latin1') + data_pos = fp.tell() + data = pickle.load(fp, fix_imports=True, encoding='latin1') except (EOFError, pickle.UnpicklingError) as e: mailman_log('error', 'Corrupted backup file %s: %s\nTraceback:\n%s', filebase, str(e), traceback.format_exc()) - self.finish(filebase, preserve=True) - continue - + self.finish(filebase, preserve=True) + return + # Validate the unpickled data if not isinstance(data, dict): raise TypeError('Invalid data format in backup file') # Update metadata - data['_bak_count'] = data.setdefault('_bak_count', 0) + 1 - data['_last_attempt'] = time.time() - if '_error_history' not in data: - data['_error_history'] = [] - if '_traceback' in data: - data['_error_history'].append({ - 'error': data.get('_last_error', 'unknown'), - 'traceback': data.get('_traceback', 'none'), - 'time': data.get('_last_attempt', 0) - }) + data['_bak_count'] = data.setdefault('_bak_count', 0) + 1 + data['_last_attempt'] = time.time() + if '_error_history' not in data: + data['_error_history'] = [] + if '_traceback' in data: + data['_error_history'].append({ + 'error': data.get('_last_error', 'unknown'), + 'traceback': data.get('_traceback', 'none'), + 'time': data.get('_last_attempt', 0) + }) # Write the updated data back - with open(src, 'wb') as out_fp: - if data.get('_parsemsg'): - protocol = 0 - else: - protocol = 1 - pickle.dump(data, out_fp, protocol=4, fix_imports=True) - out_fp.flush() - if hasattr(os, 'fsync'): - os.fsync(out_fp.fileno()) - - # Log detailed information about the retry - mailman_log('warning', - 'Message retry attempt %d/%d: %s (queue: %s, ' - 'message-id: %s, listname: %s, recipients: %s, ' - 'error: %s, last attempt: %s, traceback: %s)', - data['_bak_count'], - MAX_BAK_COUNT, - filebase, - self.__whichq, - data.get('message-id', 'unknown'), - data.get('listname', 'unknown'), - data.get('recips', 'unknown'), - data.get('_last_error', 'unknown'), - time.ctime(data.get('_last_attempt', 0)), - data.get('_traceback', 'none')) - - if data['_bak_count'] >= MAX_BAK_COUNT: - mailman_log('error', - 'Backup file exceeded maximum retry count (%d). ' - 'Moving to shunt queue: %s (original queue: %s, ' - 'retry count: %d, last error: %s, ' - 'message-id: %s, listname: %s, ' - 'recipients: %s, error history: %s, ' - 'last traceback: %s, full path: %s)', + try: + with open(src, 'wb') as out_fp: + if data.get('_parsemsg'): + protocol = 0 + else: + protocol = 1 + pickle.dump(data, out_fp, protocol=4, fix_imports=True) + out_fp.flush() + if hasattr(os, 'fsync'): + os.fsync(out_fp.fileno()) + except Exception as e: + mailman_log('error', 'Failed to write updated data to backup file %s: %s\nTraceback:\n%s', + filebase, str(e), traceback.format_exc()) + self.finish(filebase, preserve=True) + return + + # Log detailed information about the retry + mailman_log('warning', + 'Message retry attempt %d/%d: %s (queue: %s, ' + 'message-id: %s, listname: %s, recipients: %s, ' + 'error: %s, last attempt: %s, traceback: %s)', + data['_bak_count'], MAX_BAK_COUNT, filebase, self.__whichq, - data['_bak_count'], - data.get('_last_error', 'unknown'), data.get('message-id', 'unknown'), data.get('listname', 'unknown'), data.get('recips', 'unknown'), - data.get('_error_history', 'unknown'), - data.get('_traceback', 'none'), - os.path.join(self.__whichq, filebase + '.bak')) - self.finish(filebase, preserve=True) - else: - try: - os.rename(src, dst) - except OSError as e: - mailman_log('error', 'Failed to rename backup file %s (full paths: %s -> %s): %s\nTraceback:\n%s', - filebase, os.path.join(self.__whichq, filebase + '.bak'), os.path.join(self.__whichq, filebase + '.pck'), str(e), traceback.format_exc()) + data.get('_last_error', 'unknown'), + time.ctime(data.get('_last_attempt', 0)), + data.get('_traceback', 'none')) + + if data['_bak_count'] >= MAX_BAK_COUNT: + mailman_log('error', + 'Backup file exceeded maximum retry count (%d). ' + 'Moving to shunt queue: %s (original queue: %s, ' + 'retry count: %d, last error: %s, ' + 'message-id: %s, listname: %s, ' + 'recipients: %s, error history: %s, ' + 'last traceback: %s, full path: %s)', + MAX_BAK_COUNT, + filebase, + self.__whichq, + data['_bak_count'], + data.get('_last_error', 'unknown'), + data.get('message-id', 'unknown'), + data.get('listname', 'unknown'), + data.get('recips', 'unknown'), + data.get('_error_history', 'unknown'), + data.get('_traceback', 'none'), + os.path.join(self.__whichq, filebase + '.bak')) self.finish(filebase, preserve=True) - + return + else: + try: + os.rename(src, dst) + except OSError as e: + mailman_log('error', 'Failed to rename backup file %s (full paths: %s -> %s): %s\nTraceback:\n%s', + filebase, os.path.join(self.__whichq, filebase + '.bak'), os.path.join(self.__whichq, filebase + '.pck'), str(e), traceback.format_exc()) + self.finish(filebase, preserve=True) + return except Exception as e: mailman_log('error', 'Failed to process backup file %s (full path: %s): %s\nTraceback:\n%s', filebase, os.path.join(self.__whichq, filebase + '.bak'), str(e), traceback.format_exc()) self.finish(filebase, preserve=True) - continue + return except Exception as e: mailman_log('error', 'Failed to process backup file %s (full path: %s): %s\nTraceback:\n%s', filebase, os.path.join(self.__whichq, filebase + '.bak'), str(e), traceback.format_exc()) - continue + return None, None except Exception as e: mailman_log('error', 'Failed to recover backup files: %s\nTraceback:\n%s', str(e), traceback.format_exc()) From 525db7e4eb627e0ba339b9be50054238ce6dc90d Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 12 May 2025 20:03:02 -0400 Subject: [PATCH 380/748] password logging fixes --- Mailman/SecurityManager.py | 3 +++ Mailman/Utils.py | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Mailman/SecurityManager.py b/Mailman/SecurityManager.py index 1b07e299..7a2368b2 100644 --- a/Mailman/SecurityManager.py +++ b/Mailman/SecurityManager.py @@ -139,6 +139,9 @@ def Authenticate(self, authcontexts, response, user=None): if not response: # Don't authenticate null passwords return mm_cfg.UnAuthorized + # Log the type and encoding of the response + mailman_log('debug', 'Auth response type: %s, encoding: %s', + type(response), getattr(response, 'encoding', 'N/A')) # python3 response = response.encode('UTF-8') for ac in authcontexts: diff --git a/Mailman/Utils.py b/Mailman/Utils.py index 5bd92644..3d224f76 100644 --- a/Mailman/Utils.py +++ b/Mailman/Utils.py @@ -483,7 +483,11 @@ def check_global_password(response, siteadmin=True): challenge = get_global_password(siteadmin) if challenge is None: return None - return challenge == sha_new(response).hexdigest() + # Log the hashes for debugging + computed_hash = sha_new(response).hexdigest() + mailman_log('debug', 'Password check - stored hash: %s, computed hash: %s', + challenge, computed_hash) + return challenge == computed_hash _ampre = re.compile('&((?:#[0-9]+|[a-z]+);)', re.IGNORECASE) From 2e7513054efdadffa85847557d2f1c6acf1c2957 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 12 May 2025 20:10:30 -0400 Subject: [PATCH 381/748] cgi fixes --- Mailman/Cgi/admin.py | 54 ++++++++++------------------------------ Mailman/Cgi/admindb.py | 2 +- Mailman/Cgi/confirm.py | 2 +- Mailman/Cgi/create.py | 2 +- Mailman/Cgi/edithtml.py | 2 +- Mailman/Cgi/listinfo.py | 2 +- Mailman/Cgi/options.py | 2 +- Mailman/Cgi/private.py | 2 +- Mailman/Cgi/rmlist.py | 2 +- Mailman/Cgi/roster.py | 2 +- Mailman/Cgi/subscribe.py | 2 +- 11 files changed, 23 insertions(+), 51 deletions(-) diff --git a/Mailman/Cgi/admin.py b/Mailman/Cgi/admin.py index d5d6f19d..9a28bfa6 100644 --- a/Mailman/Cgi/admin.py +++ b/Mailman/Cgi/admin.py @@ -68,7 +68,7 @@ def main(): if os.environ.get('REQUEST_METHOD') == 'POST': content_length = int(os.environ.get('CONTENT_LENGTH', 0)) if content_length > 0: - form_data = sys.stdin.read(content_length) + form_data = sys.stdin.buffer.read(content_length).decode('utf-8') cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=True) else: cgidata = {} @@ -111,47 +111,19 @@ def main(): # If the user is not authenticated, we're done. try: mailman_log('debug', 'Checking authentication') - if os.environ.get('REQUEST_METHOD') == 'POST': - content_length = int(os.environ.get('CONTENT_LENGTH', 0)) - if content_length > 0: - form_data = sys.stdin.buffer.read(content_length).decode('latin-1') - cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=True) - for key in cgidata: - cgidata[key] = [v.decode('latin-1') if isinstance(v, bytes) else v for v in cgidata[key]] - else: - cgidata = {} + # CSRF check + safe_params = ['VARHELP', 'adminpw', 'admlogin', + 'letter', 'chunk', 'findmember', + 'legend'] + params = list(cgidata.keys()) + if set(params) - set(safe_params): + csrf_checked = csrf_check(mlist, cgidata.get('csrf_token', [''])[0], + 'admin') else: - query_string = os.environ.get('QUERY_STRING', '') - cgidata = urllib.parse.parse_qs(query_string, keep_blank_values=True) - for key in cgidata: - cgidata[key] = [v.decode('latin-1') if isinstance(v, bytes) else v for v in cgidata[key]] - mailman_log('debug', 'cgidata before auth: %s', str(cgidata)) - except Exception as e: - doc = Document() - doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) - doc.AddItem(Header(2, _("Error"))) - doc.AddItem(Bold(_('Invalid options to CGI script.'))) - doc.AddItem(Preformatted(Utils.websafe(str(e)))) - doc.AddItem(Preformatted(Utils.websafe(traceback.format_exc()))) - print('Status: 400 Bad Request') - print(doc.Format()) - mailman_log('error', 'admin: Invalid options: %s\n%s', str(e), traceback.format_exc()) - return - # CSRF check - safe_params = ['VARHELP', 'adminpw', 'admlogin', - 'letter', 'chunk', 'findmember', - 'legend'] - params = list(cgidata.keys()) - if set(params) - set(safe_params): - csrf_checked = csrf_check(mlist, cgidata.get('csrf_token', [''])[0], - 'admin') - else: - csrf_checked = True - if cgidata.get('adminpw', [''])[0]: - os.environ['HTTP_COOKIE'] = '' - csrf_checked = True - try: - mailman_log('debug', 'Calling WebAuthenticate') + csrf_checked = True + if cgidata.get('adminpw', [''])[0]: + os.environ['HTTP_COOKIE'] = '' + csrf_checked = True mailman_log('debug', 'Authentication contexts: %s', str((mm_cfg.AuthListAdmin, mm_cfg.AuthSiteAdmin))) mailman_log('debug', 'Password provided: %s', 'Yes' if cgidata.get('adminpw', [''])[0] else 'No') mailman_log('debug', 'Cookie present: %s', 'Yes' if os.environ.get('HTTP_COOKIE') else 'No') diff --git a/Mailman/Cgi/admindb.py b/Mailman/Cgi/admindb.py index 820787e4..caec87d7 100644 --- a/Mailman/Cgi/admindb.py +++ b/Mailman/Cgi/admindb.py @@ -138,7 +138,7 @@ def main(): if os.environ.get('REQUEST_METHOD') == 'POST': content_length = int(os.environ.get('CONTENT_LENGTH', 0)) if content_length > 0: - form_data = sys.stdin.read(content_length) + form_data = sys.stdin.buffer.read(content_length).decode('utf-8') cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=True) else: cgidata = {} diff --git a/Mailman/Cgi/confirm.py b/Mailman/Cgi/confirm.py index 431d961b..ab2d1958 100644 --- a/Mailman/Cgi/confirm.py +++ b/Mailman/Cgi/confirm.py @@ -72,7 +72,7 @@ def main(): if os.environ.get('REQUEST_METHOD') == 'POST': content_length = int(os.environ.get('CONTENT_LENGTH', 0)) if content_length > 0: - form_data = sys.stdin.read(content_length) + form_data = sys.stdin.buffer.read(content_length).decode('utf-8') cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=True) else: cgidata = {} diff --git a/Mailman/Cgi/create.py b/Mailman/Cgi/create.py index cf055b62..683732ab 100644 --- a/Mailman/Cgi/create.py +++ b/Mailman/Cgi/create.py @@ -46,7 +46,7 @@ def main(): if os.environ.get('REQUEST_METHOD') == 'POST': content_length = int(os.environ.get('CONTENT_LENGTH', 0)) if content_length > 0: - form_data = sys.stdin.read(content_length) + form_data = sys.stdin.buffer.read(content_length).decode('utf-8') cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=True) else: cgidata = {} diff --git a/Mailman/Cgi/edithtml.py b/Mailman/Cgi/edithtml.py index 1ee2a366..4cf5c9b6 100644 --- a/Mailman/Cgi/edithtml.py +++ b/Mailman/Cgi/edithtml.py @@ -100,7 +100,7 @@ def _(s): if os.environ.get('REQUEST_METHOD') == 'POST': content_length = int(os.environ.get('CONTENT_LENGTH', 0)) if content_length > 0: - form_data = sys.stdin.read(content_length) + form_data = sys.stdin.buffer.read(content_length).decode('utf-8') cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=True) else: cgidata = {} diff --git a/Mailman/Cgi/listinfo.py b/Mailman/Cgi/listinfo.py index 5410e030..68024633 100644 --- a/Mailman/Cgi/listinfo.py +++ b/Mailman/Cgi/listinfo.py @@ -65,7 +65,7 @@ def main(): if os.environ.get('REQUEST_METHOD') == 'POST': content_length = int(os.environ.get('CONTENT_LENGTH', 0)) if content_length > 0: - form_data = sys.stdin.read(content_length) + form_data = sys.stdin.buffer.read(content_length).decode('utf-8') cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=True) else: cgidata = {} diff --git a/Mailman/Cgi/options.py b/Mailman/Cgi/options.py index 806d3436..cc69ece9 100644 --- a/Mailman/Cgi/options.py +++ b/Mailman/Cgi/options.py @@ -106,7 +106,7 @@ def main(): if os.environ.get('REQUEST_METHOD') == 'POST': content_length = int(os.environ.get('CONTENT_LENGTH', 0)) if content_length > 0: - form_data = sys.stdin.read(content_length) + form_data = sys.stdin.buffer.read(content_length).decode('utf-8') cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=True) else: cgidata = {} diff --git a/Mailman/Cgi/private.py b/Mailman/Cgi/private.py index 01f5c4d8..24c241a6 100644 --- a/Mailman/Cgi/private.py +++ b/Mailman/Cgi/private.py @@ -120,7 +120,7 @@ def main(): if os.environ.get('REQUEST_METHOD') == 'POST': content_length = int(os.environ.get('CONTENT_LENGTH', 0)) if content_length > 0: - form_data = sys.stdin.read(content_length) + form_data = sys.stdin.buffer.read(content_length).decode('utf-8') cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=True) else: cgidata = {} diff --git a/Mailman/Cgi/rmlist.py b/Mailman/Cgi/rmlist.py index 6706b521..b0e7313f 100644 --- a/Mailman/Cgi/rmlist.py +++ b/Mailman/Cgi/rmlist.py @@ -44,7 +44,7 @@ def main(): if os.environ.get('REQUEST_METHOD') == 'POST': content_length = int(os.environ.get('CONTENT_LENGTH', 0)) if content_length > 0: - form_data = sys.stdin.read(content_length) + form_data = sys.stdin.buffer.read(content_length).decode('utf-8') cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=True) else: cgidata = {} diff --git a/Mailman/Cgi/roster.py b/Mailman/Cgi/roster.py index 62bc9870..f7a30950 100644 --- a/Mailman/Cgi/roster.py +++ b/Mailman/Cgi/roster.py @@ -65,7 +65,7 @@ def main(): if os.environ.get('REQUEST_METHOD') == 'POST': content_length = int(os.environ.get('CONTENT_LENGTH', 0)) if content_length > 0: - form_data = sys.stdin.read(content_length) + form_data = sys.stdin.buffer.read(content_length).decode('utf-8') cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=True) else: cgidata = {} diff --git a/Mailman/Cgi/subscribe.py b/Mailman/Cgi/subscribe.py index 1074fe58..9c61c456 100644 --- a/Mailman/Cgi/subscribe.py +++ b/Mailman/Cgi/subscribe.py @@ -77,7 +77,7 @@ def main(): if os.environ.get('REQUEST_METHOD') == 'POST': content_length = int(os.environ.get('CONTENT_LENGTH', 0)) if content_length > 0: - form_data = sys.stdin.read(content_length) + form_data = sys.stdin.buffer.read(content_length).decode('utf-8') cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=True) else: cgidata = {} From 8323846c13db8769ea1b0eb404757fefef9fb668 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 12 May 2025 20:12:09 -0400 Subject: [PATCH 382/748] cgi fixes --- Mailman/SecurityManager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mailman/SecurityManager.py b/Mailman/SecurityManager.py index 7a2368b2..465e71fa 100644 --- a/Mailman/SecurityManager.py +++ b/Mailman/SecurityManager.py @@ -66,7 +66,7 @@ from Mailman import mm_cfg from Mailman import Utils from Mailman import Errors -from Mailman.Logging.Syslog import syslog +from Mailman.Logging.Syslog import syslog, mailman_log from Mailman.Utils import md5_new, sha_new From b5983107563f6c6563b4e78906849644c000ce92 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 12 May 2025 20:14:46 -0400 Subject: [PATCH 383/748] cgi fixes --- Mailman/Cgi/admindb.py | 89 +++++++++++++----------------------------- 1 file changed, 28 insertions(+), 61 deletions(-) diff --git a/Mailman/Cgi/admindb.py b/Mailman/Cgi/admindb.py index caec87d7..8c05af77 100644 --- a/Mailman/Cgi/admindb.py +++ b/Mailman/Cgi/admindb.py @@ -779,22 +779,19 @@ def process_form(mlist, doc, cgidata): msgid = qs.get('msgid', [''])[0] details = qs.get('details', [''])[0] + # Set the page title + title = _(f'{mlist.real_name} Administrative Database') + doc.SetTitle(title) + doc.AddItem(Header(2, title)) + # Check if there are any pending requests if not mlist.NumRequestsPending(): - title = _(f'{mlist.real_name} Administrative Database') - doc.SetTitle(title) - doc.AddItem(Header(2, title)) doc.AddItem(_('There are no pending requests.')) doc.AddItem(' ') admindburl = mlist.GetScriptURL('admindb', absolute=1) doc.AddItem(Link(admindburl, _('Click here to reload this page.'))) - # Put 'Logout' link before the footer - doc.AddItem('\n
                  ') - doc.AddItem(Link('%s/logout' % admindburl, - '%s' % _('Logout'))) - doc.AddItem('
                  \n') + # Add the footer doc.AddItem(mlist.GetMailmanFooter()) - # Output the success page with proper headers return output_success_page(doc) # Create a form for the overview @@ -809,62 +806,32 @@ def process_form(mlist, doc, cgidata): show_pending_unsubs(mlist, form) show_helds_overview(mlist, form) doc.AddItem(form) + # Add the footer + doc.AddItem(mlist.GetMailmanFooter()) return output_success_page(doc) - # Process the action - if action == 'approve': - if sender: - show_sender_requests(mlist, form, sender) - elif msgid: - show_message_requests(mlist, form, msgid) - else: - show_detailed_requests(mlist, form) - elif action == 'reject': - if sender: - show_sender_requests(mlist, form, sender) - elif msgid: - show_message_requests(mlist, form, msgid) - else: - show_detailed_requests(mlist, form) - elif action == 'defer': - if sender: - show_sender_requests(mlist, form, sender) - elif msgid: - show_message_requests(mlist, form, msgid) - else: - show_detailed_requests(mlist, form) - elif action == 'discard': - if sender: - show_sender_requests(mlist, form, sender) - elif msgid: - show_message_requests(mlist, form, msgid) - else: - show_detailed_requests(mlist, form) - elif action == 'hold': - if sender: - show_sender_requests(mlist, form, sender) - elif msgid: - show_message_requests(mlist, form, msgid) - else: - show_detailed_requests(mlist, form) - elif action == 'post': - if msgid: - info = mlist.GetRecord(msgid) - if info: - total = len(mlist.GetHeldMessageIds()) - count = 1 - show_post_requests(mlist, msgid, info, total, count, form) - else: - show_detailed_requests(mlist, form) - else: - # Unknown action, show the overview - show_pending_subs(mlist, form) - show_pending_unsubs(mlist, form) - show_helds_overview(mlist, form) + # Process the form submission + if action == 'submit': + # Process the form data + process_submissions(mlist, cgidata) + # Show success message + doc.AddItem(_('Your changes have been made.')) + doc.AddItem(' ') + admindburl = mlist.GetScriptURL('admindb', absolute=1) + doc.AddItem(Link(admindburl, _('Click here to return to the pending requests page.'))) + # Add the footer + doc.AddItem(mlist.GetMailmanFooter()) + return output_success_page(doc) - # Add the form to the document and output - doc.AddItem(form) + # If we get here, something went wrong + doc.AddItem(_('Invalid action specified.')) + doc.AddItem(' ') + admindburl = mlist.GetScriptURL('admindb', absolute=1) + doc.AddItem(Link(admindburl, _('Click here to return to the pending requests page.'))) + # Add the footer + doc.AddItem(mlist.GetMailmanFooter()) return output_success_page(doc) + except Exception as e: mailman_log('error', 'admindb: Error in process_form: %s\n%s', str(e), traceback.format_exc()) From 0be6e5beb791d66c53855f200e82f41edc2a7f3f Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 12 May 2025 20:17:23 -0400 Subject: [PATCH 384/748] update admindb --- Mailman/Cgi/admindb.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Mailman/Cgi/admindb.py b/Mailman/Cgi/admindb.py index 8c05af77..2f317f08 100644 --- a/Mailman/Cgi/admindb.py +++ b/Mailman/Cgi/admindb.py @@ -792,7 +792,7 @@ def process_form(mlist, doc, cgidata): doc.AddItem(Link(admindburl, _('Click here to reload this page.'))) # Add the footer doc.AddItem(mlist.GetMailmanFooter()) - return output_success_page(doc) + return # Create a form for the overview form = Form(mlist.GetScriptURL('admindb', absolute=1), mlist=mlist, contexts=AUTH_CONTEXTS) @@ -808,7 +808,7 @@ def process_form(mlist, doc, cgidata): doc.AddItem(form) # Add the footer doc.AddItem(mlist.GetMailmanFooter()) - return output_success_page(doc) + return # Process the form submission if action == 'submit': @@ -821,7 +821,7 @@ def process_form(mlist, doc, cgidata): doc.AddItem(Link(admindburl, _('Click here to return to the pending requests page.'))) # Add the footer doc.AddItem(mlist.GetMailmanFooter()) - return output_success_page(doc) + return # If we get here, something went wrong doc.AddItem(_('Invalid action specified.')) @@ -830,7 +830,6 @@ def process_form(mlist, doc, cgidata): doc.AddItem(Link(admindburl, _('Click here to return to the pending requests page.'))) # Add the footer doc.AddItem(mlist.GetMailmanFooter()) - return output_success_page(doc) except Exception as e: mailman_log('error', 'admindb: Error in process_form: %s\n%s', From d2173b43e8015f99e16ae9585468a9ed425a6be8 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 12 May 2025 20:19:21 -0400 Subject: [PATCH 385/748] update admindb --- Mailman/Cgi/admindb.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Mailman/Cgi/admindb.py b/Mailman/Cgi/admindb.py index 2f317f08..d08598ae 100644 --- a/Mailman/Cgi/admindb.py +++ b/Mailman/Cgi/admindb.py @@ -785,11 +785,16 @@ def process_form(mlist, doc, cgidata): doc.AddItem(Header(2, title)) # Check if there are any pending requests + admindburl = mlist.GetScriptURL('admindb', absolute=1) if not mlist.NumRequestsPending(): doc.AddItem(_('There are no pending requests.')) doc.AddItem(' ') - admindburl = mlist.GetScriptURL('admindb', absolute=1) doc.AddItem(Link(admindburl, _('Click here to reload this page.'))) + # Put 'Logout' link before the footer + doc.AddItem('\n
                  ') + doc.AddItem(Link('%s/logout' % admindburl, + '%s' % _('Logout'))) + doc.AddItem('
                  \n') # Add the footer doc.AddItem(mlist.GetMailmanFooter()) return From 25bc2599cf9c38edca549d59f860d1b4f98122e9 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 12 May 2025 20:23:15 -0400 Subject: [PATCH 386/748] update checkdbs --- cron/checkdbs | 32 ++++++++------------------------ 1 file changed, 8 insertions(+), 24 deletions(-) diff --git a/cron/checkdbs b/cron/checkdbs index 600ccffe..9bf0aa48 100755 --- a/cron/checkdbs +++ b/cron/checkdbs @@ -114,7 +114,7 @@ def main(): def pending_requests(mlist): - # Must return a byte string + # Must return a string lcset = Utils.GetCharSet(mlist.preferred_language) pending = [] first = 1 @@ -124,8 +124,8 @@ def pending_requests(mlist): first = 0 when, addr, fullname, passwd, digest, lang = mlist.GetRecord(id) if fullname: - if isinstance(fullname, str): - fullname = fullname.encode(lcset, 'replace') + if isinstance(fullname, bytes): + fullname = fullname.decode(lcset, 'replace') fullname = ' (%s)' % fullname pending.append(' %s%s %s' % (addr, fullname, time.ctime(when))) first = 1 @@ -143,6 +143,8 @@ def pending_requests(mlist): info = mlist.GetRecord(id) when, sender, subject, reason, text, msgdata = mlist.GetRecord(id) subject = Utils.oneline(subject, lcset) + if isinstance(subject, bytes): + subject = subject.decode(lcset, 'replace') date = time.ctime(when) reason = _(reason) pending.append(_("""\ @@ -150,27 +152,9 @@ From: %(sender)s on %(date)s Subject: %(subject)s Cause: %(reason)s""")) pending.append('') - # Coerce all items in pending to a Unicode so we can join them - upending = [] - charset = Utils.GetCharSet(mlist.preferred_language) - for s in pending: - if isinstance(s, str): - upending.append(s) - else: - upending.append(str(s, charset, 'replace')) - # Make sure that the text we return from here can be encoded to a byte - # string in the charset of the list's language. This could fail if for - # example, the request was pended while the list's language was French, - # but then it was changed to English before checkdbs ran. - text = u'\n'.join(upending) - charset = Charset(Utils.GetCharSet(mlist.preferred_language)) - incodec = charset.input_codec or 'ascii' - outcodec = charset.output_codec or 'ascii' - if isinstance(text, str): - return text.encode(outcodec, 'replace') - # Be sure this is a byte string encodeable in the list's charset - utext = str(text, incodec, 'replace') - return utext.encode(outcodec, 'replace') + + # Join all lines with newlines and return as a string + return '\n'.join(pending) def auto_discard(mlist): # Discard old held messages From 74f7bc5dfc8e6700e5704f94bde6521eb0afb7c3 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 12 May 2025 20:24:32 -0400 Subject: [PATCH 387/748] update checkdbs --- cron/checkdbs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cron/checkdbs b/cron/checkdbs index 9bf0aa48..46d4e749 100755 --- a/cron/checkdbs +++ b/cron/checkdbs @@ -39,7 +39,7 @@ from email.charset import Charset from Mailman import mm_cfg from Mailman import Utils from Mailman import MailList -from Mailman.Message import Message +from Mailman.Message import UserNotification from Mailman import i18n # Work around known problems with some RedHat cron daemons @@ -104,10 +104,10 @@ def main(): else: subject = _( '%(realname)s moderator request check result') - msg = Mailman.Message.UserNotification(mlist.GetOwnerEmail(), - mlist.GetBouncesEmail(), - subject, text, - mlist.preferred_language) + msg = UserNotification(mlist.GetOwnerEmail(), + mlist.GetBouncesEmail(), + subject, text, + mlist.preferred_language) msg.send(mlist, **{'tomoderators': 1}) finally: mlist.Unlock() From ca3780a64396ae20362c50c55843b2db786bafb4 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 12 May 2025 20:25:56 -0400 Subject: [PATCH 388/748] update checkdbs --- cron/checkdbs | 213 +++++++++++++++++++++++++++++++------------------- 1 file changed, 134 insertions(+), 79 deletions(-) diff --git a/cron/checkdbs b/cron/checkdbs index 46d4e749..14e9a81d 100755 --- a/cron/checkdbs +++ b/cron/checkdbs @@ -30,6 +30,7 @@ Options: import sys import time import argparse +import traceback import paths @@ -41,6 +42,7 @@ from Mailman import Utils from Mailman import MailList from Mailman.Message import UserNotification from Mailman import i18n +from Mailman.Logging.Syslog import mailman_log # Work around known problems with some RedHat cron daemons import signal @@ -62,55 +64,77 @@ def parse_args(): def main(): args = parse_args() + processed = 0 + errors = 0 for name in Utils.list_names(): # the list must be locked in order to open the requests database - mlist = MailList.MailList(name) try: - count = mlist.NumRequestsPending() - # While we're at it, let's evict yesterday's autoresponse data - midnightToday = Utils.midnight() - evictions = [] - for sender in mlist.hold_and_cmd_autoresponses.keys(): - date, respcount = mlist.hold_and_cmd_autoresponses[sender] - if Utils.midnight(date) < midnightToday: - evictions.append(sender) - if evictions: - for sender in evictions: - del mlist.hold_and_cmd_autoresponses[sender] - # Only here have we changed the list's database - mlist.Save() - if count: - i18n.set_language(mlist.preferred_language) - realname = mlist.real_name - discarded = auto_discard(mlist) - if discarded: - count = count - discarded - text = _( - 'Notice: %(discarded)d old request(s) automatically expired.\n\n') - else: - text = '' + mlist = MailList.MailList(name) + try: + count = mlist.NumRequestsPending() + # While we're at it, let's evict yesterday's autoresponse data + midnightToday = Utils.midnight() + evictions = [] + for sender in mlist.hold_and_cmd_autoresponses.keys(): + date, respcount = mlist.hold_and_cmd_autoresponses[sender] + if Utils.midnight(date) < midnightToday: + evictions.append(sender) + if evictions: + for sender in evictions: + del mlist.hold_and_cmd_autoresponses[sender] + # Only here have we changed the list's database + mlist.Save() if count: - text += str(Utils.maketext( - 'checkdbs.txt', - {'count' : count, - 'host_name': mlist.host_name, - 'adminDB' : mlist.GetScriptURL('admindb', absolute=1), - 'real_name': realname, - }, mlist=mlist)) - text += '\n' + pending_requests(mlist) - subject = _( - '%(count)d %(realname)s moderator request(s) waiting') - else: - subject = _( - '%(realname)s moderator request check result') - msg = UserNotification(mlist.GetOwnerEmail(), - mlist.GetBouncesEmail(), - subject, text, - mlist.preferred_language) - msg.send(mlist, **{'tomoderators': 1}) - finally: - mlist.Unlock() + i18n.set_language(mlist.preferred_language) + realname = mlist.real_name + discarded = auto_discard(mlist) + if discarded: + count = count - discarded + text = _( + 'Notice: %(discarded)d old request(s) automatically expired.\n\n') + else: + text = '' + if count: + text += str(Utils.maketext( + 'checkdbs.txt', + {'count' : count, + 'host_name': mlist.host_name, + 'adminDB' : mlist.GetScriptURL('admindb', absolute=1), + 'real_name': realname, + }, mlist=mlist)) + text += '\n' + pending_requests(mlist) + subject = _( + '%(count)d %(realname)s moderator request(s) waiting') + else: + subject = _( + '%(realname)s moderator request check result') + try: + # Get the charset for the list's language + charset = Utils.GetCharSet(mlist.preferred_language) + msg = UserNotification(mlist.GetOwnerEmail(), + mlist.GetBouncesEmail(), + subject, text, + mlist.preferred_language) + msg.send(mlist, **{'tomoderators': 1}) + processed += 1 + except Exception as e: + mailman_log('error', 'Failed to send notification for list %s: %s\n%s', + name, str(e), traceback.format_exc()) + errors += 1 + finally: + mlist.Unlock() + except Exception as e: + mailman_log('error', 'Error processing list %s: %s\n%s', + name, str(e), traceback.format_exc()) + errors += 1 + + if errors: + mailman_log('error', 'checkdbs completed with %d errors, processed %d lists', + errors, processed) + else: + mailman_log('info', 'checkdbs completed successfully, processed %d lists', + processed) def pending_requests(mlist): @@ -118,56 +142,87 @@ def pending_requests(mlist): lcset = Utils.GetCharSet(mlist.preferred_language) pending = [] first = 1 - for id in mlist.GetSubscriptionIds(): - if first: - pending.append(_('Pending subscriptions:')) - first = 0 - when, addr, fullname, passwd, digest, lang = mlist.GetRecord(id) - if fullname: - if isinstance(fullname, bytes): - fullname = fullname.decode(lcset, 'replace') - fullname = ' (%s)' % fullname - pending.append(' %s%s %s' % (addr, fullname, time.ctime(when))) - first = 1 - for id in mlist.GetUnsubscriptionIds(): - if first: - pending.append(_('Pending unsubscriptions:')) - first = 0 - addr = mlist.GetRecord(id) - pending.append(' %s' % addr) - first = 1 - for id in mlist.GetHeldMessageIds(): - if first: - pending.append(_('\nPending posts:')) - first = 0 - info = mlist.GetRecord(id) - when, sender, subject, reason, text, msgdata = mlist.GetRecord(id) - subject = Utils.oneline(subject, lcset) - if isinstance(subject, bytes): - subject = subject.decode(lcset, 'replace') - date = time.ctime(when) - reason = _(reason) - pending.append(_("""\ + try: + for id in mlist.GetSubscriptionIds(): + if first: + pending.append(_('Pending subscriptions:')) + first = 0 + try: + when, addr, fullname, passwd, digest, lang = mlist.GetRecord(id) + if fullname: + if isinstance(fullname, bytes): + fullname = fullname.decode(lcset, 'replace') + fullname = ' (%s)' % fullname + pending.append(' %s%s %s' % (addr, fullname, time.ctime(when))) + except Exception as e: + mailman_log('error', 'Error processing subscription record %d: %s\n%s', + id, str(e), traceback.format_exc()) + continue + + first = 1 + for id in mlist.GetUnsubscriptionIds(): + if first: + pending.append(_('Pending unsubscriptions:')) + first = 0 + try: + addr = mlist.GetRecord(id) + pending.append(' %s' % addr) + except Exception as e: + mailman_log('error', 'Error processing unsubscription record %d: %s\n%s', + id, str(e), traceback.format_exc()) + continue + + first = 1 + for id in mlist.GetHeldMessageIds(): + if first: + pending.append(_('\nPending posts:')) + first = 0 + try: + info = mlist.GetRecord(id) + when, sender, subject, reason, text, msgdata = mlist.GetRecord(id) + subject = Utils.oneline(subject, lcset) + if isinstance(subject, bytes): + subject = subject.decode(lcset, 'replace') + date = time.ctime(when) + reason = _(reason) + pending.append(_("""\ From: %(sender)s on %(date)s Subject: %(subject)s Cause: %(reason)s""")) - pending.append('') + pending.append('') + except Exception as e: + mailman_log('error', 'Error processing held message record %d: %s\n%s', + id, str(e), traceback.format_exc()) + continue + except Exception as e: + mailman_log('error', 'Error in pending_requests: %s\n%s', + str(e), traceback.format_exc()) + return _('Error retrieving pending requests') # Join all lines with newlines and return as a string return '\n'.join(pending) + def auto_discard(mlist): # Discard old held messages discard_count = 0 expire = mlist.max_days_to_hold * 86400 # days heldmsgs = mlist.GetHeldMessageIds() if expire and len(heldmsgs): + current_time = time.time() for id in heldmsgs: - if now - mlist.GetRecord(id)[0] > expire: - mlist.HandleRequest(id, mm_cfg.DISCARD) - discard_count += 1 - mlist.Save() + try: + if current_time - mlist.GetRecord(id)[0] > expire: + mlist.HandleRequest(id, mm_cfg.DISCARD) + discard_count += 1 + except Exception as e: + mailman_log('error', 'Error auto-discarding message %d: %s\n%s', + id, str(e), traceback.format_exc()) + continue + if discard_count: + mlist.Save() return discard_count + if __name__ == '__main__': main() From 058225efd83b1677aae28eae724d6834927736f6 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 12 May 2025 20:28:43 -0400 Subject: [PATCH 389/748] update charset handling --- Mailman/Message.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Mailman/Message.py b/Mailman/Message.py index ce70782d..c291de48 100644 --- a/Mailman/Message.py +++ b/Mailman/Message.py @@ -250,7 +250,9 @@ def __init__(self, recip, sender, subject=None, text=None, lang=None): Message.__init__(self) charset = None if lang is not None: - charset = Charset(mm_cfg.GetCharSet(lang)) + charset = Charset(Utils.GetCharSet(lang)) + else: + charset = Charset('us-ascii') # Use us-ascii as fallback when lang is None if text is not None: self.set_payload(text, charset) if subject is None: From 121b37a214bad25124bf887ddfb43d429014a356 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 12 May 2025 20:30:04 -0400 Subject: [PATCH 390/748] update charset handling --- Mailman/Message.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mailman/Message.py b/Mailman/Message.py index c291de48..e2ec64c9 100644 --- a/Mailman/Message.py +++ b/Mailman/Message.py @@ -250,7 +250,7 @@ def __init__(self, recip, sender, subject=None, text=None, lang=None): Message.__init__(self) charset = None if lang is not None: - charset = Charset(Utils.GetCharSet(lang)) + charset = Charset(GetCharSet(lang)) else: charset = Charset('us-ascii') # Use us-ascii as fallback when lang is None if text is not None: From 41c4e4c7ac8f73c75cd5d933dc251fb7bccc010f Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 12 May 2025 20:31:44 -0400 Subject: [PATCH 391/748] update digest handling --- Mailman/Cgi/listinfo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mailman/Cgi/listinfo.py b/Mailman/Cgi/listinfo.py index 68024633..ee336006 100644 --- a/Mailman/Cgi/listinfo.py +++ b/Mailman/Cgi/listinfo.py @@ -198,7 +198,7 @@ def list_listinfo(mlist, language): # Then get replacements replacements = mlist.GetStandardReplacements(language) - if not mlist.digestable or not mlist.nondigestable: + if not mlist.nondigestable: replacements[''] = "" replacements[''] = "" replacements[''] = '' % (depth, article.threadKey)) - self.depth = depth - self.write_index_entry(article) - - def write_TOC(self): - self.sortarchives() - omask = os.umask(0o002) - try: - toc = open(os.path.join(self.basedir, 'index.html'), 'w') - finally: - os.umask(omask) - toc.write(self.html_TOC()) - toc.close() - - def write_article(self, index, article, path): - # called by add_article - omask = os.umask(0o002) - try: - f = open(path, 'w') - finally: - os.umask(omask) - f.write(article.as_html()) - f.close() + def processUnixMailbox(self, archfile): + """Process a Unix mailbox file.""" + from email import message_from_file + from mailbox import mbox + mbox = mbox(archfile) + for key in mbox.keys(): + msg = message_from_file(mbox.get_file(key)) + self.add_article(msg) - # Write the text article to the text archive. - path = os.path.join(self.basedir, "%s.txt" % index) - omask = os.umask(0o002) - try: - f = open(path, 'a+') - finally: - os.umask(omask) - f.write(article.as_text()) - f.close() - - def update_archive(self, archive): - self.__super_update_archive(archive) - # only do this if the gzip module was imported globally, and - # gzip'ing was enabled via mm_cfg.GZIP_ARCHIVE_TXT_FILES. See - # above. - if gzip: - archz = None - archt = None - txtfile = os.path.join(self.basedir, '%s.txt' % archive) - gzipfile = os.path.join(self.basedir, '%s.txt.gz' % archive) - oldgzip = os.path.join(self.basedir, '%s.old.txt.gz' % archive) - try: - # open the plain text file - archt = open(txtfile) - except IOError: - return - try: - os.rename(gzipfile, oldgzip) - archz = gzip.open(oldgzip) - except (IOError, RuntimeError, os.error): - pass - try: - ou = os.umask(0o002) - newz = gzip.open(gzipfile, 'w') - finally: - # XXX why is this a finally? - os.umask(ou) - if archz: - newz.write(archz.read()) - archz.close() - os.unlink(oldgzip) - # XXX do we really need all this in a try/except? - try: - newz.write(archt.read()) - newz.close() - archt.close() - except IOError: - pass - os.unlink(txtfile) - - _skip_attrs = ('maillist', '_lock_file', 'charset') - - def getstate(self): - d={} - for each in list(self.__dict__.keys()): - if not (each in self._skip_attrs - or each.upper() == each): - d[each] = self.__dict__[each] - return d + def format_article(self, article): + """Format an article for HTML display.""" + # Get the message body + body = article.get_body() + if body is None: + return article + + # Convert body to lines + if isinstance(body, str): + lines = body.splitlines() + else: + lines = [line.decode('utf-8', 'replace') for line in body.splitlines()] - # Add tags around URLs and e-mail addresses. - - def __processbody_URLquote(self, lines): - # XXX a lot to do here: - # 1. use lines directly, rather than source and dest - # 2. make it clearer - # 3. make it faster - # TK: Prepare for unicode obscure. - atmark = _(' at ') - if lines and isinstance(lines[0], str): - atmark = str(atmark, Utils.GetCharSet(self.lang), 'replace') - source = lines[:] - dest = lines - last_line_was_quoted = 0 - for i in range(0, len(source)): - Lorig = L = source[i] - prefix = suffix = "" - if L is None: - continue - # Italicise quoted text - if self.IQUOTES: - quoted = quotedpat.match(L) - if quoted is None: - last_line_was_quoted = 0 - else: - quoted = quoted.end(0) - prefix = CGIescape(L[:quoted], self.lang) + '' - suffix = '' - if self.SHOWHTML: - suffix += '
                  ' - if not last_line_was_quoted: - prefix = '
                  ' + prefix - L = L[quoted:] - last_line_was_quoted = 1 - # Check for an e-mail address - L2 = "" - jr = emailpat.search(L) - kr = urlpat.search(L) - while jr is not None or kr is not None: - if jr is None: - j = -1 - else: - j = jr.start(0) - if kr is None: - k = -1 + # Handle HTML content + if article.ctype == 'text/html': + article.html_body = lines + else: + # Process plain text + processed_lines = [] + for line in lines: + # Handle quoted text + if self.IQUOTES and quotedpat.match(line): + line = '' + CGIescape(line, self.lang) + '' else: - k = kr.start(0) - if j != -1 and (j < k or k == -1): - text = jr.group(1) - length = len(text) - if mm_cfg.ARCHIVER_OBSCURES_EMAILADDRS: - text = re.sub('@', atmark, text, flags=re.IGNORECASE) - URL = self.maillist.GetScriptURL( - 'listinfo', absolute=1) - else: - URL = 'mailto:' + text - pos = j - elif k != -1 and (j > k or j == -1): - text = URL = kr.group(1) - length = len(text) - pos = k - else: # j==k - raise ValueError("j==k: This can't happen!") - #length = len(text) - #self.message("URL: %s %s %s \n" - # % (CGIescape(L[:pos]), URL, CGIescape(text))) - L2 += '%s
                  %s' % ( - CGIescape(L[:pos], self.lang), - html_quote(URL), CGIescape(text, self.lang)) - L = L[pos+length:] - jr = emailpat.search(L) - kr = urlpat.search(L) - if jr is None and kr is None: - L = CGIescape(L, self.lang) - L = prefix + L2 + L + suffix - source[i] = None - dest[i] = L - - # Perform Hypermail-style processing of directives - # in message bodies. Lines between and will be written - # out precisely as they are; other lines will be passed to func2 - # for further processing . - - def __processbody_HTML(self, lines): - # XXX need to make this method modify in place - source = lines[:] - dest = lines - l = len(source) - i = 0 - while i < l: - while i < l and htmlpat.match(source[i]) is None: - i = i + 1 - if i < l: - source[i] = None - i = i + 1 - while i < l and nohtmlpat.match(source[i]) is None: - dest[i], source[i] = source[i], None - i = i + 1 - if i < l: - source[i] = None - i = i + 1 + line = CGIescape(line, self.lang) + if self.SHOWBR: + line += '
                  ' + processed_lines.append(line) - def format_article(self, article): - # called from add_article - # TBD: Why do the HTML formatting here and keep it in the - # pipermail database? It makes more sense to do the html - # formatting as the article is being written as html and toss - # the data after it has been written to the archive file. - lines = [_f for _f in article.body if _f] - # Handle directives - if self.ALLOWHTML: - self.__processbody_HTML(lines) - self.__processbody_URLquote(lines) - if not self.SHOWHTML and lines: - lines.insert(0, '
                  ')
                  -            lines.append('
                  ') - else: - # Do fancy formatting here - if self.SHOWBR: - lines = [x + "
                  " for x in lines] - else: - for i in range(0, len(lines)): - s = lines[i] - if s[0:1] in ' \t\n': - lines[i] = '

                  ' + s - article.html_body = lines - return article + # Add HTML structure + if not self.SHOWHTML: + processed_lines.insert(0, '

                  ')
                  +                processed_lines.append('
                  ') + article.html_body = processed_lines - def update_article(self, arcdir, article, prev, next): - seq = article.sequence - filename = os.path.join(arcdir, article.filename) - self.message(C_('Updating HTML for article %(seq)s')) - try: - f = open(filename) - article.loadbody_fromHTML(f) - f.close() - except IOError as e: - if e.errno != errno.ENOENT: raise - self.message(C_('article file %(filename)s is missing!')) - article.prev = prev - article.next = next - omask = os.umask(0o002) - try: - f = open(filename, 'w') - finally: - os.umask(omask) - f.write(article.as_html()) - f.close() + return article diff --git a/Mailman/Handlers/ToDigest.py b/Mailman/Handlers/ToDigest.py index b554503f..cd9b1e2e 100644 --- a/Mailman/Handlers/ToDigest.py +++ b/Mailman/Handlers/ToDigest.py @@ -34,7 +34,7 @@ import traceback from io import StringIO, BytesIO -from email.parser import Parser +from email.parser import Parser, message_from_string from email.generator import Generator from email.mime.base import MIMEBase from email.mime.text import MIMEText @@ -287,7 +287,7 @@ def send_digests(mlist, mboxpath): # Process the previous message msg_str = ''.join(current_msg) try: - msg = email.message.Message.from_string(msg_str) + msg = message_from_string(msg_str) if msg is None: continue @@ -335,7 +335,7 @@ def send_digests(mlist, mboxpath): if current_msg: msg_str = ''.join(current_msg) try: - msg = email.message.Message.from_string(msg_str) + msg = message_from_string(msg_str) if msg is not None: # Process the last message (same code as above) subject = decode_header_value(msg.get('subject', _('(no subject)')), lcset) diff --git a/Mailman/Mailbox.py b/Mailman/Mailbox.py index 13e86fe6..e80568f8 100644 --- a/Mailman/Mailbox.py +++ b/Mailman/Mailbox.py @@ -69,7 +69,9 @@ def flatten(self, msg, unixfrom=False, linesep='\n'): self._buffer.seek(0) self._buffer.truncate() # Write to binary file - self._binary_fp.write(content.encode('utf-8', 'replace')) + if isinstance(content, str): + content = content.encode('utf-8', 'replace') + self._binary_fp.write(content) class Mailbox(mailbox.mbox): @@ -96,6 +98,7 @@ def __init__(self, fp): if hasattr(fp, 'read') and hasattr(fp, 'write'): self.fp = fp else: + # Open in binary mode for writing self.fp = open(path, 'ab+') # msg should be an rfc822 message or a subclass. From b6b280d6c4bf9e7aaa4eabb91e160cda25f5348b Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 15 May 2025 03:19:26 -0400 Subject: [PATCH 574/748] update --- Mailman/Handlers/ToDigest.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Mailman/Handlers/ToDigest.py b/Mailman/Handlers/ToDigest.py index cd9b1e2e..7b5f6800 100644 --- a/Mailman/Handlers/ToDigest.py +++ b/Mailman/Handlers/ToDigest.py @@ -34,7 +34,8 @@ import traceback from io import StringIO, BytesIO -from email.parser import Parser, message_from_string +from email.parser import Parser +from email import message_from_string from email.generator import Generator from email.mime.base import MIMEBase from email.mime.text import MIMEText From aa25401a7a33def78051c265431e9c4dd079d4cd Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 15 May 2025 03:21:47 -0400 Subject: [PATCH 575/748] update --- Mailman/Mailbox.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/Mailman/Mailbox.py b/Mailman/Mailbox.py index e80568f8..1044580f 100644 --- a/Mailman/Mailbox.py +++ b/Mailman/Mailbox.py @@ -45,7 +45,7 @@ class BinaryGenerator(email.generator.Generator): """A generator that writes to a binary file.""" def __init__(self, outfp, mangle_from_=True, maxheaderlen=78, *args, **kwargs): # Create a text buffer that we'll write to first - self._buffer = StringIO() + self._buffer = BytesIO() super().__init__(self._buffer, mangle_from_, maxheaderlen, *args, **kwargs) # Store the binary file object self._binary_fp = outfp @@ -53,24 +53,19 @@ def __init__(self, outfp, mangle_from_=True, maxheaderlen=78, *args, **kwargs): def _write_lines(self, lines): # Override to handle both string and bytes input for line in lines: - if isinstance(line, bytes): - try: - line = line.decode('utf-8', 'replace') - except UnicodeError: - line = line.decode('latin-1', 'replace') + if isinstance(line, str): + line = line.encode('utf-8', 'replace') self._buffer.write(line) def flatten(self, msg, unixfrom=False, linesep='\n'): # Override to write the buffer contents to binary file super().flatten(msg, unixfrom=unixfrom, linesep=linesep) - # Get the text content and encode it + # Get the content and write it directly to binary file content = self._buffer.getvalue() # Reset the buffer self._buffer.seek(0) self._buffer.truncate() # Write to binary file - if isinstance(content, str): - content = content.encode('utf-8', 'replace') self._binary_fp.write(content) From 5ef61b70febdccf9f550b0d805b92bb1b66f5710 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 15 May 2025 03:24:12 -0400 Subject: [PATCH 576/748] update --- Mailman/Mailbox.py | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/Mailman/Mailbox.py b/Mailman/Mailbox.py index 1044580f..096d5eec 100644 --- a/Mailman/Mailbox.py +++ b/Mailman/Mailbox.py @@ -45,27 +45,36 @@ class BinaryGenerator(email.generator.Generator): """A generator that writes to a binary file.""" def __init__(self, outfp, mangle_from_=True, maxheaderlen=78, *args, **kwargs): # Create a text buffer that we'll write to first - self._buffer = BytesIO() + self._buffer = StringIO() super().__init__(self._buffer, mangle_from_, maxheaderlen, *args, **kwargs) # Store the binary file object self._binary_fp = outfp + def write(self, s): + """Override write to handle string-to-bytes conversion.""" + if isinstance(s, str): + s = s.encode('utf-8', 'replace') + self._buffer.write(s) + def _write_lines(self, lines): - # Override to handle both string and bytes input + """Override to handle both string and bytes input.""" for line in lines: - if isinstance(line, str): - line = line.encode('utf-8', 'replace') + if isinstance(line, bytes): + try: + line = line.decode('utf-8', 'replace') + except UnicodeError: + line = line.decode('latin-1', 'replace') self._buffer.write(line) def flatten(self, msg, unixfrom=False, linesep='\n'): - # Override to write the buffer contents to binary file - super().flatten(msg, unixfrom=unixfrom, linesep=linesep) - # Get the content and write it directly to binary file - content = self._buffer.getvalue() + """Override to write the buffer contents to binary file.""" # Reset the buffer self._buffer.seek(0) self._buffer.truncate() - # Write to binary file + # Write the message to the buffer + super().flatten(msg, unixfrom=unixfrom, linesep=linesep) + # Get the content and write it to binary file + content = self._buffer.getvalue().encode('utf-8', 'replace') self._binary_fp.write(content) From 0b2b15658497177d665bfed05b031392ec0eb227 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 15 May 2025 03:25:56 -0400 Subject: [PATCH 577/748] update --- Mailman/Mailbox.py | 44 +++----------------------------------------- 1 file changed, 3 insertions(+), 41 deletions(-) diff --git a/Mailman/Mailbox.py b/Mailman/Mailbox.py index 096d5eec..b5063b65 100644 --- a/Mailman/Mailbox.py +++ b/Mailman/Mailbox.py @@ -28,11 +28,11 @@ from email.message import Message from email.parser import Parser from email.errors import MessageParseError +from email.generator import BytesGenerator from Mailman import mm_cfg from Mailman.Message import Message - def _safeparser(fp): try: return email.message_from_file(fp, Mailman.Message.Message) @@ -40,44 +40,6 @@ def _safeparser(fp): # Don't return None since that will stop a mailbox iterator return '' - -class BinaryGenerator(email.generator.Generator): - """A generator that writes to a binary file.""" - def __init__(self, outfp, mangle_from_=True, maxheaderlen=78, *args, **kwargs): - # Create a text buffer that we'll write to first - self._buffer = StringIO() - super().__init__(self._buffer, mangle_from_, maxheaderlen, *args, **kwargs) - # Store the binary file object - self._binary_fp = outfp - - def write(self, s): - """Override write to handle string-to-bytes conversion.""" - if isinstance(s, str): - s = s.encode('utf-8', 'replace') - self._buffer.write(s) - - def _write_lines(self, lines): - """Override to handle both string and bytes input.""" - for line in lines: - if isinstance(line, bytes): - try: - line = line.decode('utf-8', 'replace') - except UnicodeError: - line = line.decode('latin-1', 'replace') - self._buffer.write(line) - - def flatten(self, msg, unixfrom=False, linesep='\n'): - """Override to write the buffer contents to binary file.""" - # Reset the buffer - self._buffer.seek(0) - self._buffer.truncate() - # Write the message to the buffer - super().flatten(msg, unixfrom=unixfrom, linesep=linesep) - # Get the content and write it to binary file - content = self._buffer.getvalue().encode('utf-8', 'replace') - self._binary_fp.write(content) - - class Mailbox(mailbox.mbox): def __init__(self, fp): # In Python 3, we need to handle both file objects and paths @@ -120,8 +82,8 @@ def AppendMessage(self, msg): self.fp.write(b'\n') # Seek to the last char of the mailbox self.fp.seek(0, 2) - # Create a BinaryGenerator instance to write the message to the file - g = BinaryGenerator(self.fp, mangle_from_=False, maxheaderlen=0) + # Create a BytesGenerator instance to write the message to the file + g = BytesGenerator(self.fp, mangle_from_=False, maxheaderlen=0) g.flatten(msg, unixfrom=True) # Add one more trailing newline for separation with the next message self.fp.write(b'\n') From 7a2f05acc290341d77bec01d481fff88a8b9836e Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 15 May 2025 03:28:53 -0400 Subject: [PATCH 578/748] update --- Mailman/Mailbox.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/Mailman/Mailbox.py b/Mailman/Mailbox.py index b5063b65..d428d8be 100644 --- a/Mailman/Mailbox.py +++ b/Mailman/Mailbox.py @@ -28,7 +28,7 @@ from email.message import Message from email.parser import Parser from email.errors import MessageParseError -from email.generator import BytesGenerator +from email.generator import Generator from Mailman import mm_cfg from Mailman.Message import Message @@ -51,8 +51,8 @@ def __init__(self, fp): # Create a temporary file if we don't have a path import tempfile path = tempfile.mktemp() - with open(path, 'wb') as f: - f.write(fp.read()) + with open(path, 'w', encoding='utf-8') as f: + f.write(fp.read().decode('utf-8', 'replace')) fp.seek(0) else: # It's a path string @@ -64,8 +64,8 @@ def __init__(self, fp): if hasattr(fp, 'read') and hasattr(fp, 'write'): self.fp = fp else: - # Open in binary mode for writing - self.fp = open(path, 'ab+') + # Open in text mode for writing + self.fp = open(path, 'a+', encoding='utf-8') # msg should be an rfc822 message or a subclass. def AppendMessage(self, msg): @@ -78,15 +78,16 @@ def AppendMessage(self, msg): # returned, since it differs per platform. pass else: - if self.fp.read(1) != b'\n': - self.fp.write(b'\n') + if self.fp.read(1) != '\n': + self.fp.write('\n') # Seek to the last char of the mailbox self.fp.seek(0, 2) - # Create a BytesGenerator instance to write the message to the file - g = BytesGenerator(self.fp, mangle_from_=False, maxheaderlen=0) + + # Create a Generator instance to write the message to the file + g = Generator(self.fp, mangle_from_=False, maxheaderlen=0) g.flatten(msg, unixfrom=True) # Add one more trailing newline for separation with the next message - self.fp.write(b'\n') + self.fp.write('\n') # This stuff is used by pipermail.py:processUnixMailbox(). It provides an From 8ff0e057e43c0ea0c25f4941ad5a46f857055948 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 15 May 2025 03:30:32 -0400 Subject: [PATCH 579/748] update --- Mailman/Archiver/pipermail.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Mailman/Archiver/pipermail.py b/Mailman/Archiver/pipermail.py index 3701afc6..ffec1d45 100644 --- a/Mailman/Archiver/pipermail.py +++ b/Mailman/Archiver/pipermail.py @@ -5,13 +5,13 @@ import re import sys import time +import string from email.utils import parseaddr, parsedate_tz, mktime_tz, formatdate import pickle from io import StringIO -# Work around for some misguided Python packages that add iso-8859-1 -# accented characters to string.lowercase. -lowercase = lowercase[:26] +# Use string.ascii_lowercase instead of the old lowercase variable +lowercase = string.ascii_lowercase __version__ = '0.09 (Mailman edition)' VERSION = __version__ @@ -26,7 +26,6 @@ SPACE = ' ' - msgid_pat = re.compile(r'(<.*>)') def strip_separators(s): "Remove quotes or parenthesization from a Message-ID string" From b2480fafe3c52ab1b97bc8d806cfe1f44fe68994 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 15 May 2025 03:32:02 -0400 Subject: [PATCH 580/748] update --- Mailman/Archiver/HyperArch.py | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/Mailman/Archiver/HyperArch.py b/Mailman/Archiver/HyperArch.py index 2eafee05..d93e997f 100644 --- a/Mailman/Archiver/HyperArch.py +++ b/Mailman/Archiver/HyperArch.py @@ -892,10 +892,32 @@ def processUnixMailbox(self, archfile): """Process a Unix mailbox file.""" from email import message_from_file from mailbox import mbox - mbox = mbox(archfile) - for key in mbox.keys(): - msg = message_from_file(mbox.get_file(key)) - self.add_article(msg) + + # If archfile is a file object, we need to read it directly + if hasattr(archfile, 'read'): + # Read the entire file content + content = archfile.read() + # Create a temporary file to store the content + import tempfile + with tempfile.NamedTemporaryFile(mode='w+', delete=False) as tmp: + tmp.write(content) + tmp_path = tmp.name + + try: + # Process the temporary file + mbox = mbox(tmp_path) + for key in mbox.keys(): + msg = message_from_file(mbox.get_file(key)) + self.add_article(msg) + finally: + # Clean up the temporary file + os.unlink(tmp_path) + else: + # If it's a path, use it directly + mbox = mbox(archfile) + for key in mbox.keys(): + msg = message_from_file(mbox.get_file(key)) + self.add_article(msg) def format_article(self, article): """Format an article for HTML display.""" From 0ecbce5cb4ba3d8ee3549b448c416362ae823488 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 15 May 2025 03:33:26 -0400 Subject: [PATCH 581/748] update --- Mailman/Archiver/HyperArch.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Mailman/Archiver/HyperArch.py b/Mailman/Archiver/HyperArch.py index d93e997f..bc9d4de6 100644 --- a/Mailman/Archiver/HyperArch.py +++ b/Mailman/Archiver/HyperArch.py @@ -637,8 +637,8 @@ def __init__(self, maillist): # with mailman's LockFile module for HyperDatabase.HyperDatabase # dir = maillist.archive_dir() - db = HyperDatabase.HyperDatabase(dir, maillist) - self.__super_init(dir, reload=1, database=db) + self.database = HyperDatabase.HyperDatabase(dir, maillist) + self.__super_init(dir, reload=1, database=self.database) self.maillist = maillist self._lock_file = None From fcab222fceb69f19f27ab7f16b280fb780ef32d9 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 15 May 2025 03:35:42 -0400 Subject: [PATCH 582/748] update --- Mailman/Archiver/pipermail.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Mailman/Archiver/pipermail.py b/Mailman/Archiver/pipermail.py index ffec1d45..0373a260 100644 --- a/Mailman/Archiver/pipermail.py +++ b/Mailman/Archiver/pipermail.py @@ -289,6 +289,9 @@ def __init__(self, basedir = None, reload = 1, database = None): f.seek(0) d = pickle.load(f, fix_imports=True, encoding='latin1') f.close() + if isinstance(d, bytes): + # If we got bytes, try to unpickle it + d = pickle.loads(d, fix_imports=True, encoding='latin1') for key, value in list(d.items()): setattr(self, key, value) except (IOError, EOFError): From 56178eafbffdff1d5907df1b9d2e1bbec119054a Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 15 May 2025 03:39:29 -0400 Subject: [PATCH 583/748] update --- Mailman/Archiver/HyperArch.py | 51 +++++++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/Mailman/Archiver/HyperArch.py b/Mailman/Archiver/HyperArch.py index bc9d4de6..90d69853 100644 --- a/Mailman/Archiver/HyperArch.py +++ b/Mailman/Archiver/HyperArch.py @@ -37,10 +37,14 @@ from . import pipermail import weakref import binascii +from io import StringIO, BytesIO +import pickle from email.header import decode_header, make_header from email.errors import HeaderParseError from email.charset import Charset +from email import message_from_file +from email.generator import Generator from Mailman import mm_cfg from Mailman import Utils @@ -449,7 +453,7 @@ def decode_charset(self, field): if cset == 'us-ascii': cset = 'iso-8859-1' # assume this for English list ustr = str(field, cset, 'replace') - return u''.join(ustr.splitlines()) + return ''.join(ustr.splitlines()) def as_html(self): d = self.__dict__.copy() @@ -899,7 +903,9 @@ def processUnixMailbox(self, archfile): content = archfile.read() # Create a temporary file to store the content import tempfile - with tempfile.NamedTemporaryFile(mode='w+', delete=False) as tmp: + with tempfile.NamedTemporaryFile(mode='w+', encoding='utf-8', delete=False) as tmp: + if isinstance(content, bytes): + content = content.decode('utf-8', errors='replace') tmp.write(content) tmp_path = tmp.name @@ -955,3 +961,44 @@ def format_article(self, article): article.html_body = processed_lines return article + + def close(self): + "Close an archive, save its state, and update any changed archives." + self.update_dirty_archives() + self.update_TOC = 0 + self.write_TOC() + # Save the collective state + self.message(C_('Pickling archive state into ') + + os.path.join(self.basedir, 'pipermail.pck')) + self.database.close() + del self.database + + omask = os.umask(0o007) + try: + f = open(os.path.join(self.basedir, 'pipermail.pck'), 'wb') + finally: + os.umask(omask) + # Use protocol 4 for Python 2/3 compatibility + pickle.dump(self.getstate(), f, protocol=4, fix_imports=True) + f.close() + + def getstate(self): + """Get the current state of the archive.""" + try: + # Use protocol 4 for Python 2/3 compatibility + protocol = 4 + return pickle.dumps(self.__dict__, protocol, fix_imports=True) + except Exception as e: + syslog('error', 'Error getting archive state: %s', e) + return None + + def setstate(self, state): + """Set the state of the archive.""" + try: + # Use protocol 4 for Python 2/3 compatibility + protocol = 4 + self.__dict__ = pickle.loads(state, fix_imports=True, encoding='latin1') + except Exception as e: + syslog('error', 'Error setting archive state: %s', e) + return False + return True From 86b53ccd4c7a1925b148fc8b409e258920338e35 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 15 May 2025 03:41:14 -0400 Subject: [PATCH 584/748] update --- Mailman/Archiver/HyperArch.py | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/Mailman/Archiver/HyperArch.py b/Mailman/Archiver/HyperArch.py index 90d69853..e3d09cb7 100644 --- a/Mailman/Archiver/HyperArch.py +++ b/Mailman/Archiver/HyperArch.py @@ -642,13 +642,43 @@ def __init__(self, maillist): # dir = maillist.archive_dir() self.database = HyperDatabase.HyperDatabase(dir, maillist) - self.__super_init(dir, reload=1, database=self.database) - + + # Initialize basic attributes first + self.archives = [] # Archives + self._dirty_archives = [] # Archives that will have to be updated + self.sequence = 0 # Sequence variable used for numbering articles + self.update_TOC = 0 # Does the TOC need updating? self.maillist = maillist self._lock_file = None self.lang = maillist.preferred_language self.charset = Utils.GetCharSet(maillist.preferred_language) + # Try to load previously pickled state + try: + f = open(os.path.join(dir, 'pipermail.pck'), 'rb') + self.message(C_('Reloading pickled archive state')) + try: + # Try UTF-8 first for newer files + d = pickle.load(f, fix_imports=True, encoding='utf-8') + except (UnicodeDecodeError, pickle.UnpicklingError): + # Fall back to latin1 for older files + f.seek(0) + d = pickle.load(f, fix_imports=True, encoding='latin1') + f.close() + + if isinstance(d, bytes): + # If we got bytes, try to unpickle it + d = pickle.loads(d, fix_imports=True, encoding='latin1') + + # Only update attributes that don't conflict with our initialization + for key, value in list(d.items()): + if key not in ('archives', '_dirty_archives', 'sequence', 'update_TOC', + 'maillist', '_lock_file', 'lang', 'charset', 'database'): + setattr(self, key, value) + except (IOError, EOFError, pickle.UnpicklingError) as e: + syslog('error', 'Error loading archive state: %s', e) + # Continue with default initialization + if hasattr(self.maillist,'archive_volume_frequency'): if self.maillist.archive_volume_frequency == 0: self.ARCHIVE_PERIOD='year' From d556f3e9e78760cea4be01ec240d586a2e63bf3b Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 15 May 2025 03:43:02 -0400 Subject: [PATCH 585/748] update --- Mailman/Archiver/HyperArch.py | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/Mailman/Archiver/HyperArch.py b/Mailman/Archiver/HyperArch.py index e3d09cb7..e24ff8df 100644 --- a/Mailman/Archiver/HyperArch.py +++ b/Mailman/Archiver/HyperArch.py @@ -671,11 +671,15 @@ def __init__(self, maillist): d = pickle.loads(d, fix_imports=True, encoding='latin1') # Only update attributes that don't conflict with our initialization + safe_attrs = { + 'type', 'archive', 'firstdate', 'lastdate', 'archivedate', + 'size', 'version', 'subjectIndex', 'authorIndex', 'dateIndex', + 'articleIndex', 'threadIndex' + } for key, value in list(d.items()): - if key not in ('archives', '_dirty_archives', 'sequence', 'update_TOC', - 'maillist', '_lock_file', 'lang', 'charset', 'database'): + if key in safe_attrs: setattr(self, key, value) - except (IOError, EOFError, pickle.UnpicklingError) as e: + except (IOError, EOFError, pickle.UnpicklingError, RecursionError) as e: syslog('error', 'Error loading archive state: %s', e) # Continue with default initialization @@ -1008,8 +1012,20 @@ def close(self): f = open(os.path.join(self.basedir, 'pipermail.pck'), 'wb') finally: os.umask(omask) + + # Only save safe attributes + safe_state = {} + safe_attrs = { + 'type', 'archive', 'firstdate', 'lastdate', 'archivedate', + 'size', 'version', 'subjectIndex', 'authorIndex', 'dateIndex', + 'articleIndex', 'threadIndex' + } + for key in safe_attrs: + if hasattr(self, key): + safe_state[key] = getattr(self, key) + # Use protocol 4 for Python 2/3 compatibility - pickle.dump(self.getstate(), f, protocol=4, fix_imports=True) + pickle.dump(safe_state, f, protocol=4, fix_imports=True) f.close() def getstate(self): From 69dc20666c2f03e443a1116a86ec29e6ff275de6 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 15 May 2025 03:45:06 -0400 Subject: [PATCH 586/748] update --- Mailman/Archiver/HyperArch.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Mailman/Archiver/HyperArch.py b/Mailman/Archiver/HyperArch.py index e24ff8df..d80f4576 100644 --- a/Mailman/Archiver/HyperArch.py +++ b/Mailman/Archiver/HyperArch.py @@ -641,6 +641,7 @@ def __init__(self, maillist): # with mailman's LockFile module for HyperDatabase.HyperDatabase # dir = maillist.archive_dir() + self.basedir = dir # Set basedir first self.database = HyperDatabase.HyperDatabase(dir, maillist) # Initialize basic attributes first @@ -655,7 +656,7 @@ def __init__(self, maillist): # Try to load previously pickled state try: - f = open(os.path.join(dir, 'pipermail.pck'), 'rb') + f = open(os.path.join(self.basedir, 'pipermail.pck'), 'rb') self.message(C_('Reloading pickled archive state')) try: # Try UTF-8 first for newer files From dbb545dd09ec17093a0ab247fe6dea2773e3e6f6 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 15 May 2025 03:51:04 -0400 Subject: [PATCH 587/748] update --- Mailman/Queue/IncomingRunner.py | 57 ++++++++++++++++----------------- 1 file changed, 27 insertions(+), 30 deletions(-) diff --git a/Mailman/Queue/IncomingRunner.py b/Mailman/Queue/IncomingRunner.py index 92a373b4..54a03fe3 100644 --- a/Mailman/Queue/IncomingRunner.py +++ b/Mailman/Queue/IncomingRunner.py @@ -323,10 +323,6 @@ def _cleanup(self): def _oneloop(self): """Process one batch of messages from the incoming queue.""" - # First, list all the files in our queue directory. - # Switchboard.files() is guaranteed to hand us the files in FIFO - # order. Return an integer count of the number of files that were - # available for this qrunner to process. try: # Get the list of files to process files = self._switchboard.files() @@ -339,6 +335,10 @@ def _oneloop(self): # Process each file for filebase in files: try: + # Check if we need to cleanup old messages + if time.time() - self._last_cleanup > self._cleanup_interval: + self._cleanup_old_messages() + # Dequeue the file msg, msgdata = self._switchboard.dequeue(filebase) @@ -347,33 +347,24 @@ def _oneloop(self): mailman_log('error', 'IncomingRunner._oneloop: Failed to dequeue file %s (got None values)', filebase) continue + msgid = msg.get('message-id', 'n/a') + + # Check if message was recently processed + if self._check_message_processed(msgid, filebase, msg): + continue + + # Get the list name + listname = msgdata.get('listname', 'unknown') + try: + mlist = MailList.MailList(listname, lock=False) + except Errors.MMUnknownListError: + mailman_log('error', 'IncomingRunner._oneloop: Unknown list %s for message %s', + listname, msgid) + self._shunt.enqueue(msg, msgdata) + continue + # Process the message try: - # Get the list name from the message data - listname = msgdata.get('listname', mm_cfg.MAILMAN_SITE_LIST) - - # Create a MailList object using lazy import - try: - # Import MailList here to avoid circular imports - import Mailman.MailList as MailList - mlist = MailList.MailList(listname, lock=0) - except ImportError: - # If we can't import MailList, try to get it from sys.modules - import sys - MailList = sys.modules['Mailman.MailList'].MailList - mlist = MailList(listname, lock=0) - except (Errors.BadListNameError, Errors.MMUnknownListError) as e: - # List doesn't exist or has invalid name - move message to bad queue - mailman_log('error', 'List not found: %s - moving message to bad queue', listname) - try: - badq = get_switchboard(mm_cfg.BADQUEUE_DIR) - badq.enqueue(msg, listname=listname, tolist=listname) - return True - except Exception as e: - mailman_log('error', 'Failed to move message to bad queue: %s', str(e)) - return False - - # Process the message result = self._dispose(mlist, msg, msgdata) # If the message should be kept in the queue, requeue it @@ -382,13 +373,16 @@ def _oneloop(self): mailman_log('info', 'IncomingRunner._oneloop: Message requeued for later processing: %s', filebase) else: mailman_log('info', 'IncomingRunner._oneloop: Message processing complete, moving to shunt queue %s (msgid: %s)', - filebase, msg.get('message-id', 'n/a')) + filebase, msgid) except Exception as e: mailman_log('error', 'IncomingRunner._oneloop: Error processing message: %s\n%s', str(e), traceback.format_exc()) # Move to shunt queue on error self._shunt.enqueue(msg, msgdata) + finally: + # Always mark message as processed + self._mark_message_processed(msgid) except Exception as e: mailman_log('error', 'IncomingRunner._oneloop: Error dequeuing file %s: %s\n%s', @@ -401,6 +395,9 @@ def _oneloop(self): except Exception as e: mailman_log('error', 'IncomingRunner._oneloop: Unexpected error in main loop: %s\n%s', str(e), traceback.format_exc()) + # Don't re-raise the exception to keep the runner alive + return False + return True def _check_retry_delay(self, msgid, filebase): """Check if enough time has passed since the last retry attempt.""" From c5846bb668e2cd088298d8a6a57d7548e577c352 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 15 May 2025 03:52:21 -0400 Subject: [PATCH 588/748] update --- Mailman/Queue/IncomingRunner.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/Mailman/Queue/IncomingRunner.py b/Mailman/Queue/IncomingRunner.py index 54a03fe3..239255fe 100644 --- a/Mailman/Queue/IncomingRunner.py +++ b/Mailman/Queue/IncomingRunner.py @@ -446,3 +446,36 @@ def _process_admin(self, mlist, msg, msgdata): mailman_log('error', 'IncomingRunner._process_admin: Error processing admin message %s: %s\nTraceback:\n%s', msgid, str(e), traceback.format_exc()) return False + + def _check_message_processed(self, msgid, filebase, msg): + """Check if a message has already been processed and if retry delay is met. + + Args: + msgid: The message ID to check + filebase: The base filename of the message + msg: The message object + + Returns: + bool: True if message should be skipped (already processed or retry delay not met), + False if message should be processed + """ + try: + # Check if message was recently processed + with self._processed_lock: + if msgid in self._processed_messages: + mailman_log('debug', 'IncomingRunner._check_message_processed: Message %s (file: %s) was recently processed, skipping', + msgid, filebase) + return True + + # Check if retry delay is met + if not self._check_retry_delay(msgid, filebase): + return True + + # Message should be processed + return False + + except Exception as e: + mailman_log('error', 'IncomingRunner._check_message_processed: Error checking message %s: %s\nTraceback:\n%s', + msgid, str(e), traceback.format_exc()) + # On error, allow the message to be processed + return False From a4a6ad6c7bef3a0de02a2067af9dba718ed27620 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 15 May 2025 03:54:06 -0400 Subject: [PATCH 589/748] update --- Mailman/Handlers/CookHeaders.py | 157 ++++++++++---------------------- 1 file changed, 48 insertions(+), 109 deletions(-) diff --git a/Mailman/Handlers/CookHeaders.py b/Mailman/Handlers/CookHeaders.py index f737c00d..c6617080 100644 --- a/Mailman/Handlers/CookHeaders.py +++ b/Mailman/Handlers/CookHeaders.py @@ -205,119 +205,58 @@ def munge_from_header(mlist, msg, msgdata): change_header('From', new_from, mlist, msg, msgdata) def prefix_subject(mlist, msg, msgdata): - # Add the subject prefix unless the message is a digest or is being fast - # tracked (e.g. internally crafted, delivered to a single user such as the - # list admin). + """Add the list's subject prefix to the message's Subject: header.""" + # Get the subject and charset + subject = msg.get('subject', '') + if not subject: + return + + # Get the list's charset + cset = mlist.preferred_language + + # Get the prefix prefix = mlist.subject_prefix.strip() if not prefix: return - subject = msg.get('subject', '') - # Try to figure out what the continuation_ws is for the header - if isinstance(subject, Header): - lines = str(subject).splitlines() - else: - lines = subject.splitlines() - ws = ' ' - if len(lines) > 1 and lines[1] and lines[1][0] in ' \t': - ws = lines[1][0] - msgdata['origsubj'] = subject - # The subject may be multilingual but we take the first charset as major - # one and try to decode. If it is decodable, returned subject is in one - # line and cset is properly set. If fail, subject is mime-encoded and - # cset is set as us-ascii. See detail for ch_oneline() (CookHeaders one - # line function). - subject, cset = ch_oneline(subject) - # TK: Python interpreter has evolved to be strict on ascii charset code - # range. It is safe to use unicode string when manupilating header - # contents with re module. It would be best to return unicode in - # ch_oneline() but here is temporary solution. - subject = str(subject, cset) - # If the subject_prefix contains '%d', it is replaced with the - # mailing list sequential number. Sequential number format allows - # '%d' or '%05d' like pattern. - prefix_pattern = re.escape(prefix) - # unescape '%' :-< - prefix_pattern = '%'.join(prefix_pattern.split(r'\%')) - p = re.compile(r'%\d*d') - if p.search(prefix, 1): - # prefix have number, so we should search prefix w/number in subject. - # Also, force new style. - prefix_pattern = p.sub(r'\s*\d+\s*', prefix_pattern) - old_style = False - else: - old_style = mm_cfg.OLD_STYLE_PREFIXING - subject = re.sub(prefix_pattern, '', subject) - # Previously the following re didn't have the first \s*. It would fail - # if the incoming Subject: was like '[prefix] Re: Re: Re:' because of the - # leading space after stripping the prefix. It is not known what MUA would - # create such a Subject:, but the issue was reported. - rematch = re.match( - r'(\s*(RE|AW|SV|VS)\s*(\[\d+\])?\s*:\s*)+', - subject, re.I) - if rematch: - subject = subject[rematch.end():] - recolon = 'Re:' - else: - recolon = '' - # Strip leading and trailing whitespace from subject. - subject = subject.strip() - # At this point, subject may become null if someone post mail with - # Subject: [subject prefix] - if subject == '': - # We want the i18n context to be the list's preferred_language. It - # could be the poster's. - otrans = i18n.get_translation() - i18n.set_language(mlist.preferred_language) - subject = _('(no subject)') - i18n.set_translation(otrans) - cset = Utils.GetCharSet(mlist.preferred_language) - subject = str(subject, cset) - # and substitute %d in prefix with post_id + + # Handle the subject encoding try: - prefix = prefix % mlist.post_id - except TypeError: - pass - # If charset is 'us-ascii', try to concatnate as string because there - # is some weirdness in Header module (TK) - if cset == 'us-ascii': - try: - if old_style: - h = u' '.join([recolon, prefix, subject]) - else: - if recolon: - h = u' '.join([prefix, recolon, subject]) - else: - h = u' '.join([prefix, subject]) - h = h.encode('us-ascii') - h = uheader(mlist, h, 'Subject', continuation_ws=ws) - change_header('Subject', h, mlist, msg, msgdata) - ss = u' '.join([recolon, subject]) - ss = ss.encode('us-ascii') - ss = uheader(mlist, ss, 'Subject', continuation_ws=ws) - msgdata['stripped_subject'] = ss - return - except UnicodeError: - pass - # Get the header as a Header instance, with proper unicode conversion - # Because of rfc2047 encoding, spaces between encoded words can be - # insignificant, so we need to append spaces to our encoded stuff. - prefix += ' ' - if recolon: - recolon += ' ' - if old_style: - h = uheader(mlist, recolon, 'Subject', continuation_ws=ws) - h.append(prefix) - else: - h = uheader(mlist, prefix, 'Subject', continuation_ws=ws) - h.append(recolon) - # TK: Subject is concatenated and unicode string. - subject = subject.encode(cset, 'replace') - h.append(subject, cset) - change_header('Subject', h, mlist, msg, msgdata) - ss = uheader(mlist, recolon, 'Subject', continuation_ws=ws) - ss.append(subject, cset) - msgdata['stripped_subject'] = ss - + # If subject is already a string, use it directly + if isinstance(subject, str): + subject_str = subject + else: + # Try to decode the subject + try: + subject_str = str(subject, cset) + except (UnicodeError, LookupError): + # If that fails, try utf-8 + subject_str = str(subject, 'utf-8', 'replace') + except Exception as e: + mailman_log('error', 'Error decoding subject: %s', str(e)) + return + + # Add the prefix if it's not already there + if not subject_str.startswith(prefix): + msg['Subject'] = prefix + ' ' + subject_str + + # Mark message as processed + mailman_log('debug', 'CookHeaders: Adding X-BeenThere header for message %s', msgid) + change_header('X-BeenThere', mlist.GetListEmail(), + mlist, msg, msgdata, delete=False) + + # Add standard headers + mailman_log('debug', 'CookHeaders: Adding standard headers for message %s', msgid) + change_header('X-Mailman-Version', mm_cfg.VERSION, + mlist, msg, msgdata, repl=False) + change_header('Precedence', 'list', + mlist, msg, msgdata, repl=False) + + # Handle From: header munging if needed + if (msgdata.get('from_is_list') or mlist.from_is_list) and not msgdata.get('_fasttrack'): + mailman_log('debug', 'CookHeaders: Munging From header for message %s', msgid) + munge_from_header(mlist, msg, msgdata) + + mailman_log('debug', 'CookHeaders: Finished processing message %s', msgid) def ch_oneline(headerstr): # Decode header string in one line and convert into single charset From 04f59ca58a7157ee0f3c856c05ee7382a00448c2 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 15 May 2025 03:56:58 -0400 Subject: [PATCH 590/748] update --- Mailman/Queue/IncomingRunner.py | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/Mailman/Queue/IncomingRunner.py b/Mailman/Queue/IncomingRunner.py index 239255fe..ed0c99a1 100644 --- a/Mailman/Queue/IncomingRunner.py +++ b/Mailman/Queue/IncomingRunner.py @@ -159,8 +159,35 @@ def _dispose(self, mlist, msg, msgdata): filebase = msgdata.get('_filebase', 'unknown') try: - mailman_log('debug', 'IncomingRunner._dispose: Starting to process incoming message %s (file: %s)', - msgid, filebase) + # Enhanced logging for message details + mailman_log('debug', 'IncomingRunner._dispose: Starting to process message %s (file: %s)', msgid, filebase) + mailman_log('debug', 'Message details:') + mailman_log('debug', ' Message ID: %s', msgid) + mailman_log('debug', ' From: %s', msg.get('from', 'unknown')) + mailman_log('debug', ' To: %s', msg.get('to', 'unknown')) + mailman_log('debug', ' Subject: %s', msg.get('subject', '(no subject)')) + mailman_log('debug', ' Message type: %s', type(msg).__name__) + mailman_log('debug', ' Message data: %s', str(msgdata)) + mailman_log('debug', ' Pipeline: %s', msgdata.get('pipeline', 'No pipeline')) + + # Check if this is an administrative message + is_admin = msgdata.get('admin_type', False) + mailman_log('debug', ' Is admin message: %s', is_admin) + + # Check if this is a list post + is_list_post = msgdata.get('list_post', False) + mailman_log('debug', ' Is list post: %s', is_list_post) + + # Log recipients information + recipients = msgdata.get('recipients', []) + mailman_log('debug', ' Recipients: %s', recipients) + if not recipients: + mailman_log('error', 'No recipients found in msgdata for message %s', msgid) + mailman_log('error', ' Message data: %s', str(msgdata)) + mailman_log('error', ' To header: %s', msg.get('to', 'unknown')) + mailman_log('error', ' Cc header: %s', msg.get('cc', 'unknown')) + mailman_log('error', ' Resent-To: %s', msg.get('resent-to', 'unknown')) + mailman_log('error', ' Resent-Cc: %s', msg.get('resent-cc', 'unknown')) # Convert Python's Message to Mailman's Message if needed msg = self._convert_message(msg) From 646fe3a422f923bc4a62997a08bfaf7bcdc66ac8 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 15 May 2025 04:04:18 -0400 Subject: [PATCH 591/748] update --- Mailman/Handlers/SMTPDirect.py | 4 ++-- Mailman/Queue/IncomingRunner.py | 2 +- Mailman/Queue/OutgoingRunner.py | 2 +- Mailman/Queue/Switchboard.py | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Mailman/Handlers/SMTPDirect.py b/Mailman/Handlers/SMTPDirect.py index c754e7c1..a61cc118 100644 --- a/Mailman/Handlers/SMTPDirect.py +++ b/Mailman/Handlers/SMTPDirect.py @@ -214,7 +214,7 @@ def process(mlist, msg, msgdata): deliveryfunc = bulkdeliver try: - origrecips = msgdata['recips'] + origrecips = msgdata.get('recips', None) origsender = msgdata.get('original_sender', msg.get_sender()) conn = Connection() try: @@ -399,7 +399,7 @@ def bulkdeliver(mlist, msg, msgdata, envsender, failures, conn): # Get the list of recipients recips = msgdata.get('recipients', []) if not recips: - mailman_log('error', 'No recipients found in msgdata') + mailman_log('error', 'SMTPDirect: No recipients found in msgdata for message:\n%s', msg.get('Message-ID', 'n/a')) return # Convert email.message.Message to Mailman.Message if needed diff --git a/Mailman/Queue/IncomingRunner.py b/Mailman/Queue/IncomingRunner.py index ed0c99a1..4a12d5ac 100644 --- a/Mailman/Queue/IncomingRunner.py +++ b/Mailman/Queue/IncomingRunner.py @@ -182,7 +182,7 @@ def _dispose(self, mlist, msg, msgdata): recipients = msgdata.get('recipients', []) mailman_log('debug', ' Recipients: %s', recipients) if not recipients: - mailman_log('error', 'No recipients found in msgdata for message %s', msgid) + mailman_log('error', 'IncomingRunner: No recipients found in msgdata for message %s', msgid) mailman_log('error', ' Message data: %s', str(msgdata)) mailman_log('error', ' To header: %s', msg.get('to', 'unknown')) mailman_log('error', ' Cc header: %s', msg.get('cc', 'unknown')) diff --git a/Mailman/Queue/OutgoingRunner.py b/Mailman/Queue/OutgoingRunner.py index 33cd0a1a..f369291d 100644 --- a/Mailman/Queue/OutgoingRunner.py +++ b/Mailman/Queue/OutgoingRunner.py @@ -395,7 +395,7 @@ def _process_regular(self, mlist, msg, msgdata): recipient = addrs[0][1] if not recipient: - mailman_log('error', 'No recipients found in msgdata for message: %s', msgid) + mailman_log('error', 'OutgoingRunner: No recipients found in msgdata for message: %s', msgid) return False # Set the recipient in msgdata for future use diff --git a/Mailman/Queue/Switchboard.py b/Mailman/Queue/Switchboard.py index 5cac822d..e97137d2 100644 --- a/Mailman/Queue/Switchboard.py +++ b/Mailman/Queue/Switchboard.py @@ -182,9 +182,9 @@ def enqueue(self, msg, msgdata=None, listname=None, _plaintext=False, **kwargs): mailman_log('debug', 'Switchboard.enqueue: Set recipients from message headers for message: %s', msg.get('message-id', 'n/a')) else: - mailman_log('error', 'Switchboard.enqueue: No recipients found in msgdata or message headers for message: %s', + mailman_log('error', 'Switchboard: No recipients found in msgdata or message headers for message: %s', msg.get('message-id', 'n/a')) - raise ValueError('No recipients found in msgdata or message headers') + raise ValueError('Switchboard: No recipients found in msgdata or message headers') # Generate a unique filebase filebase = self._make_filebase(msg, msgdata) From 8a1c5f5aff265581a319d46d1c918b5117cb1e66 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 15 May 2025 04:09:17 -0400 Subject: [PATCH 592/748] update --- Mailman/Handlers/SMTPDirect.py | 6 ++++-- Mailman/Handlers/ToOutgoing.py | 4 ++++ Mailman/Queue/OutgoingRunner.py | 6 ++++++ Mailman/Queue/Switchboard.py | 5 +++++ 4 files changed, 19 insertions(+), 2 deletions(-) diff --git a/Mailman/Handlers/SMTPDirect.py b/Mailman/Handlers/SMTPDirect.py index a61cc118..f370a64d 100644 --- a/Mailman/Handlers/SMTPDirect.py +++ b/Mailman/Handlers/SMTPDirect.py @@ -188,9 +188,11 @@ def process(mlist, msg, msgdata): ' To: %s\n' ' Cc: %s\n' ' List: %s\n' - ' Pipeline: %s', + ' Pipeline: %s\n' + ' Full msgdata: %s', msgid, sender, subject, to, cc, mlist.internal_name(), - msgdata.get('pipeline', 'No pipeline')) + msgdata.get('pipeline', 'No pipeline'), + str(msgdata)) return # Check for spam headers first diff --git a/Mailman/Handlers/ToOutgoing.py b/Mailman/Handlers/ToOutgoing.py index 874f8d2f..8af4c19a 100644 --- a/Mailman/Handlers/ToOutgoing.py +++ b/Mailman/Handlers/ToOutgoing.py @@ -74,6 +74,10 @@ def process(mlist, msg, msgdata): # Ensure recipients are preserved in msgdata msgdata['recips'] = recips msgdata['recipient'] = recips[0] if recips else None + + # Log the full msgdata before enqueueing + mailman_log('debug', 'ToOutgoing: Full msgdata before enqueue:\n%s', str(msgdata)) + outgoingq.enqueue(msg, msgdata, listname=mlist.internal_name()) mailman_log('info', 'ToOutgoing: Successfully queued message %s for list %s', diff --git a/Mailman/Queue/OutgoingRunner.py b/Mailman/Queue/OutgoingRunner.py index f369291d..cc876a92 100644 --- a/Mailman/Queue/OutgoingRunner.py +++ b/Mailman/Queue/OutgoingRunner.py @@ -245,6 +245,9 @@ def _dispose(self, mlist, msg, msgdata): msgid = msg.get('message-id', 'n/a') filebase = msgdata.get('_filebase', 'unknown') + # Log the full msgdata at the start of processing + mailman_log('debug', 'OutgoingRunner._dispose: Full msgdata at start:\n%s', str(msgdata)) + # Ensure we have a MailList object if isinstance(mlist, str): try: @@ -288,6 +291,9 @@ def _dispose(self, mlist, msg, msgdata): self._unmark_message_processed(msgid) return False + # Log the full msgdata after validation + mailman_log('debug', 'OutgoingRunner._dispose: Full msgdata after validation:\n%s', str(msgdata)) + # Validate message headers if not msg.get('message-id'): mailman_log('error', 'OutgoingRunner._dispose: Message missing Message-ID header') diff --git a/Mailman/Queue/Switchboard.py b/Mailman/Queue/Switchboard.py index e97137d2..bc8efa4f 100644 --- a/Mailman/Queue/Switchboard.py +++ b/Mailman/Queue/Switchboard.py @@ -113,6 +113,9 @@ def enqueue(self, msg, msgdata=None, listname=None, _plaintext=False, **kwargs): # Store any additional keyword arguments in msgdata msgdata.update(kwargs) + + # Log the full msgdata before processing + mailman_log('debug', 'Switchboard.enqueue: Full msgdata before processing:\n%s', str(msgdata)) # Convert string message to Message object if needed if isinstance(msg, str): @@ -244,6 +247,8 @@ def dequeue(self, filebase): # Read the message and metadata try: msg, data = self._dequeue(filename) + # Log the full msgdata after dequeuing + mailman_log('debug', 'Switchboard.dequeue: Full msgdata after dequeuing:\n%s', str(data)) except Exception as e: mailman_log('error', 'Switchboard.dequeue: Failed to read message from %s: %s', filebase, str(e)) raise From f9ca094421bc9b581a6843c04f6aff8292cffadf Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 15 May 2025 04:13:34 -0400 Subject: [PATCH 593/748] update --- Mailman/Queue/OutgoingRunner.py | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/Mailman/Queue/OutgoingRunner.py b/Mailman/Queue/OutgoingRunner.py index cc876a92..3e00743d 100644 --- a/Mailman/Queue/OutgoingRunner.py +++ b/Mailman/Queue/OutgoingRunner.py @@ -438,10 +438,36 @@ def _process_regular(self, mlist, msg, msgdata): # For regular list messages, use the delivery module mailman_log('debug', 'OutgoingRunner._process_regular: Using delivery module for message %s', msgid) - self._func(mlist, msg, msgdata) - mailman_log('debug', 'OutgoingRunner._process_regular: Successfully processed regular message %s', msgid) - return True + # Log the state before calling the delivery module + mailman_log('debug', 'OutgoingRunner._process_regular: Pre-delivery msgdata:\n%s', str(msgdata)) + + # Ensure we have the list members if this is a list message + if msgdata.get('tolist') and not msgdata.get('_nolist'): + try: + # Get all list members + members = mlist.getMemberCPAddresses() + if members: + msgdata['recips'] = [mlist.getMemberEmail(member) for member in members] + mailman_log('debug', 'OutgoingRunner._process_regular: Expanded list members for message %s: %s', + msgid, str(msgdata['recips'])) + else: + mailman_log('error', 'OutgoingRunner._process_regular: No members found for list %s', + mlist.internal_name()) + except Exception as e: + mailman_log('error', 'OutgoingRunner._process_regular: Error getting list members: %s', str(e)) + + # Call the delivery module + try: + self._func(mlist, msg, msgdata) + # Log the state after calling the delivery module + mailman_log('debug', 'OutgoingRunner._process_regular: Post-delivery msgdata:\n%s', str(msgdata)) + mailman_log('debug', 'OutgoingRunner._process_regular: Successfully processed regular message %s', msgid) + return True + except Exception as e: + mailman_log('error', 'OutgoingRunner._process_regular: Error in delivery module: %s\nTraceback:\n%s', + str(e), traceback.format_exc()) + return False def _check_retry_delay(self, msgid, filebase): """Check if enough time has passed since the last retry attempt.""" From 7456a2bf6661415f49609ff8c6146a6006dda5fd Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 15 May 2025 04:16:27 -0400 Subject: [PATCH 594/748] update --- Mailman/Queue/OutgoingRunner.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Mailman/Queue/OutgoingRunner.py b/Mailman/Queue/OutgoingRunner.py index 3e00743d..25898f28 100644 --- a/Mailman/Queue/OutgoingRunner.py +++ b/Mailman/Queue/OutgoingRunner.py @@ -446,16 +446,22 @@ def _process_regular(self, mlist, msg, msgdata): if msgdata.get('tolist') and not msgdata.get('_nolist'): try: # Get all list members - members = mlist.getMemberCPAddresses() + members = mlist.getRegularMemberKeys() if members: - msgdata['recips'] = [mlist.getMemberEmail(member) for member in members] + msgdata['recips'] = [mlist.getMemberCPAddress(m) for m in members + if mlist.getDeliveryStatus(m) == MemberAdaptor.ENABLED] mailman_log('debug', 'OutgoingRunner._process_regular: Expanded list members for message %s: %s', msgid, str(msgdata['recips'])) else: mailman_log('error', 'OutgoingRunner._process_regular: No members found for list %s', mlist.internal_name()) except Exception as e: - mailman_log('error', 'OutgoingRunner._process_regular: Error getting list members: %s', str(e)) + mailman_log('error', 'OutgoingRunner._process_regular: Error getting list members: %s\nTraceback:\n%s', + str(e), traceback.format_exc()) + # Try to continue with existing recipients if any + if not msgdata.get('recips'): + mailman_log('error', 'OutgoingRunner._process_regular: No recipients available for message %s', msgid) + return False # Call the delivery module try: From 339519ac4aceaa8df670edc1235a67db60158795 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 15 May 2025 06:51:12 -0400 Subject: [PATCH 595/748] MemberAdaptor --- Mailman/Queue/OutgoingRunner.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Mailman/Queue/OutgoingRunner.py b/Mailman/Queue/OutgoingRunner.py index 25898f28..9d071400 100644 --- a/Mailman/Queue/OutgoingRunner.py +++ b/Mailman/Queue/OutgoingRunner.py @@ -36,6 +36,7 @@ from Mailman.Queue.Runner import Runner from Mailman.Queue.Switchboard import Switchboard from Mailman.Queue.BounceRunner import BounceMixin +from Mailman.MemberAdaptor import MemberAdaptor import Mailman.Message as Message # Lazy import to avoid circular dependency From bd545fad487b726d92d70ea55f100f68cf377dd2 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 15 May 2025 06:53:28 -0400 Subject: [PATCH 596/748] MemberAdaptor --- Mailman/Queue/OutgoingRunner.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Mailman/Queue/OutgoingRunner.py b/Mailman/Queue/OutgoingRunner.py index 9d071400..ef4711ea 100644 --- a/Mailman/Queue/OutgoingRunner.py +++ b/Mailman/Queue/OutgoingRunner.py @@ -36,7 +36,7 @@ from Mailman.Queue.Runner import Runner from Mailman.Queue.Switchboard import Switchboard from Mailman.Queue.BounceRunner import BounceMixin -from Mailman.MemberAdaptor import MemberAdaptor +from Mailman.MemberAdaptor import MemberAdaptor, ENABLED import Mailman.Message as Message # Lazy import to avoid circular dependency @@ -450,7 +450,7 @@ def _process_regular(self, mlist, msg, msgdata): members = mlist.getRegularMemberKeys() if members: msgdata['recips'] = [mlist.getMemberCPAddress(m) for m in members - if mlist.getDeliveryStatus(m) == MemberAdaptor.ENABLED] + if mlist.getDeliveryStatus(m) == ENABLED] mailman_log('debug', 'OutgoingRunner._process_regular: Expanded list members for message %s: %s', msgid, str(msgdata['recips'])) else: From 689daa8e265e9ea7c278634045a83fca251fcd27 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 15 May 2025 07:06:24 -0400 Subject: [PATCH 597/748] update --- Mailman/Queue/Runner.py | 6 ++ Mailman/Queue/Switchboard.py | 119 ++++++++++++++++++++++------------- 2 files changed, 82 insertions(+), 43 deletions(-) diff --git a/Mailman/Queue/Runner.py b/Mailman/Queue/Runner.py index 7a7b5ea4..ac745901 100644 --- a/Mailman/Queue/Runner.py +++ b/Mailman/Queue/Runner.py @@ -323,7 +323,13 @@ def _onefile(self, msg, msgdata): try: result = self._dispose(mlist, msg, msgdata) if result: + # If _dispose returns True, requeue the message self._switchboard.enqueue(msg, msgdata) + syslog('debug', 'Runner._onefile: Message requeued for %s', listname) + else: + # If _dispose returns False, finish processing and remove the file + self._switchboard.finish(msgdata.get('filebase', '')) + syslog('debug', 'Runner._onefile: Message processing completed for %s', listname) return result except Exception as e: self._handle_error(e, msg=msg, mlist=mlist) diff --git a/Mailman/Queue/Switchboard.py b/Mailman/Queue/Switchboard.py index bc8efa4f..be3982c4 100644 --- a/Mailman/Queue/Switchboard.py +++ b/Mailman/Queue/Switchboard.py @@ -97,6 +97,10 @@ def __init__(self, whichq, slice=None, numslices=1, recover=False): self.recover_backup_files() # Clean up any stale locks during initialization self.cleanup_stale_locks() + # Clean up any stale backup files + self.cleanup_stale_backups() + # Clean up any stale processed files + self.cleanup_stale_processed() def whichq(self): return self.__whichq @@ -104,56 +108,24 @@ def whichq(self): def enqueue(self, msg, msgdata=None, listname=None, _plaintext=False, **kwargs): """Add a message to the queue. - Additional keyword arguments are stored in msgdata. + Args: + msg: The message to enqueue + msgdata: Optional message metadata + listname: Optional list name + _plaintext: Whether to save as plaintext + **kwargs: Additional metadata to add """ + # Initialize msgdata if not provided if msgdata is None: msgdata = {} - if listname: - msgdata['listname'] = listname - # Store any additional keyword arguments in msgdata + # Add any additional metadata msgdata.update(kwargs) - # Log the full msgdata before processing - mailman_log('debug', 'Switchboard.enqueue: Full msgdata before processing:\n%s', str(msgdata)) + # Add listname if provided + if listname: + msgdata['listname'] = listname - # Convert string message to Message object if needed - if isinstance(msg, str): - try: - msg = email.message_from_string(msg) - except Exception as e: - mailman_log('error', 'Switchboard.enqueue: Failed to convert string message to Message object: %s', str(e)) - raise - - # First check if we have a recipient set - if 'recipient' not in msgdata: - # Try to get recipient from msgdata['recips'] first - if msgdata.get('recips'): - msgdata['recipient'] = msgdata['recips'][0] - mailman_log('debug', 'Switchboard.enqueue: Set recipient from recips for message: %s', - msg.get('message-id', 'n/a')) - # Then try envelope-to header - elif msg.get('envelope-to'): - msgdata['recipient'] = msg.get('envelope-to') - mailman_log('debug', 'Switchboard.enqueue: Set recipient from envelope-to header for message: %s', - msg.get('message-id', 'n/a')) - # Finally try To header - elif msg.get('to'): - # Parse the To header to get the first recipient - addrs = email.utils.getaddresses([msg.get('to')]) - if addrs and addrs[0][1]: - msgdata['recipient'] = addrs[0][1] - mailman_log('debug', 'Switchboard.enqueue: Set recipient from To header for message: %s', - msg.get('message-id', 'n/a')) - else: - mailman_log('error', 'Switchboard.enqueue: No valid recipient found in To header for message: %s', - msg.get('message-id', 'n/a')) - raise ValueError('No valid recipient found in To header') - else: - mailman_log('error', 'Switchboard.enqueue: No recipient found in msgdata for message: %s', - msg.get('message-id', 'n/a')) - raise ValueError('No recipient found in msgdata') - # Then check if we need to set recips if 'recips' not in msgdata or not msgdata['recips']: # If we have a recipient but no recips, use the recipient @@ -214,6 +186,8 @@ def enqueue(self, msg, msgdata=None, listname=None, _plaintext=False, **kwargs): mailman_log('error', 'Switchboard.enqueue: Failed to write message to %s: %s', filebase, str(e)) raise + # Add filebase to msgdata for cleanup + msgdata['filebase'] = filebase return filebase finally: # Always clean up the lock file @@ -247,6 +221,9 @@ def dequeue(self, filebase): # Read the message and metadata try: msg, data = self._dequeue(filename) + # Add filebase to msgdata for cleanup + if data is not None: + data['filebase'] = filebase # Log the full msgdata after dequeuing mailman_log('debug', 'Switchboard.dequeue: Full msgdata after dequeuing:\n%s', str(data)) except Exception as e: @@ -660,6 +637,62 @@ def cleanup_stale_locks(self): except OSError as e: mailman_log('error', 'Error cleaning up stale locks: %s', str(e)) + def cleanup_stale_backups(self): + """Clean up any stale backup files in the queue directory. + + This method removes backup files that are older than 24 hours + to prevent accumulation of stale files. + """ + try: + now = time.time() + stale_age = 24 * 3600 # 24 hours in seconds + + for f in os.listdir(self.__whichq): + if f.endswith('.bak'): + bakfile = os.path.join(self.__whichq, f) + try: + # Check file age + file_age = now - os.path.getmtime(bakfile) + if file_age > stale_age: + mailman_log('warning', + 'Cleaning up stale backup file %s (age: %d seconds)', + bakfile, file_age) + os.unlink(bakfile) + except OSError as e: + mailman_log('error', + 'Failed to clean up stale backup file %s: %s', + bakfile, str(e)) + except OSError as e: + mailman_log('error', 'Error cleaning up stale backup files: %s', str(e)) + + def cleanup_stale_processed(self): + """Clean up any stale processed files in the queue directory. + + This method removes processed files that are older than 7 days + to prevent accumulation of stale files. + """ + try: + now = time.time() + stale_age = 7 * 24 * 3600 # 7 days in seconds + + for f in os.listdir(self.__whichq): + if f.endswith('.pck'): + pckfile = os.path.join(self.__whichq, f) + try: + # Check file age + file_age = now - os.path.getmtime(pckfile) + if file_age > stale_age: + mailman_log('warning', + 'Cleaning up stale processed file %s (age: %d seconds)', + pckfile, file_age) + os.unlink(pckfile) + except OSError as e: + mailman_log('error', + 'Failed to clean up stale processed file %s: %s', + pckfile, str(e)) + except OSError as e: + mailman_log('error', 'Error cleaning up stale processed files: %s', str(e)) + def _make_filebase(self, msg, msgdata): import hashlib import time From 94fc27811e52c21e60a1fed87c861a0752c600f3 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 15 May 2025 07:09:26 -0400 Subject: [PATCH 598/748] update --- Mailman/Handlers/SMTPDirect.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mailman/Handlers/SMTPDirect.py b/Mailman/Handlers/SMTPDirect.py index f370a64d..0707694b 100644 --- a/Mailman/Handlers/SMTPDirect.py +++ b/Mailman/Handlers/SMTPDirect.py @@ -399,7 +399,7 @@ def bulkdeliver(mlist, msg, msgdata, envsender, failures, conn): refused = {} try: # Get the list of recipients - recips = msgdata.get('recipients', []) + recips = msgdata.get('recips', []) if not recips: mailman_log('error', 'SMTPDirect: No recipients found in msgdata for message:\n%s', msg.get('Message-ID', 'n/a')) return From 114bc80ea938fafa733afb5bddb0bb6da58d1336 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 15 May 2025 07:17:34 -0400 Subject: [PATCH 599/748] Improve file handling and cleanup in Runner and Switchboard classes - Add proper file cleanup in Runner._onefile() after successful processing - Enhance Switchboard.finish() with better error handling and logging - Add cleanup of stale backup files in Switchboard - Improve file locking and atomic operations - Add detailed logging for file operations - Fix file removal after processing - Add validation of file operations --- Mailman/Queue/Runner.py | 45 +++++++++++++++----- Mailman/Queue/Switchboard.py | 82 ++++++++++++++++++++---------------- 2 files changed, 81 insertions(+), 46 deletions(-) diff --git a/Mailman/Queue/Runner.py b/Mailman/Queue/Runner.py index ac745901..a1c3f301 100644 --- a/Mailman/Queue/Runner.py +++ b/Mailman/Queue/Runner.py @@ -211,20 +211,45 @@ def _oneloop(self): try: # Dequeue the file msg, msgdata = self._switchboard.dequeue(filebase) - if msg is None: + if msg is None or msgdata is None: + syslog('error', 'Runner._oneloop: Failed to dequeue file %s (got None values)', filebase) continue - + + # Get the list name + listname = msgdata.get('listname', 'unknown') + try: + mlist = MailList.MailList(listname, lock=False) + except Errors.MMUnknownListError: + syslog('error', 'Runner._oneloop: Unknown list %s for message %s', + listname, msg.get('message-id', 'n/a')) + self._shunt.enqueue(msg, msgdata) + continue + # Process the message try: - self._onefile(msg, msgdata) + result = self._onefile(mlist, msg, msgdata) + if result: + # Message was successfully processed, finish and remove the file + self._switchboard.finish(filebase) + syslog('debug', 'Runner._oneloop: Successfully processed message %s, removed file %s', + msg.get('message-id', 'n/a'), filebase) + elif result is False: + # Message needs to be requeued + self._switchboard.enqueue(msg, msgdata) + syslog('debug', 'Runner._oneloop: Requeued message %s', msg.get('message-id', 'n/a')) + else: + # Message was shunted + self._shunt.enqueue(msg, msgdata) + syslog('debug', 'Runner._oneloop: Shunted message %s', msg.get('message-id', 'n/a')) + return True except Exception as e: - # Log the error and shunt the message - self._handle_error(e, msg=msg, mlist=None) - continue - + syslog('error', 'Runner._oneloop: Error processing message %s: %s\nTraceback:\n%s', + msg.get('message-id', 'n/a'), str(e), traceback.format_exc()) + self._shunt.enqueue(msg, msgdata) + return False except Exception as e: - # Log the error and continue - syslog('error', 'Runner._oneloop: Error dequeuing file %s: %s', filebase, str(e)) + syslog('error', 'Runner._oneloop: Error processing file %s: %s\nTraceback:\n%s', + filebase, str(e), traceback.format_exc()) continue except Exception as e: @@ -302,7 +327,7 @@ def _validate_message(self, msg, msgdata): msgid, str(e), traceback.format_exc()) return msg, False - def _onefile(self, msg, msgdata): + def _onefile(self, mlist, msg, msgdata): """Process a single file from the queue.""" try: # Get the list name from the message data diff --git a/Mailman/Queue/Switchboard.py b/Mailman/Queue/Switchboard.py index be3982c4..2b170c7c 100644 --- a/Mailman/Queue/Switchboard.py +++ b/Mailman/Queue/Switchboard.py @@ -244,64 +244,74 @@ def dequeue(self, filebase): pass def finish(self, filebase, preserve=False): + """Finish processing a file by either removing it or moving it to the shunt queue. + + Args: + filebase: The base name of the file to process + preserve: If True, move the file to the shunt queue instead of removing it + """ + if not filebase: + mailman_log('error', 'Switchboard.finish: No filebase provided') + return + bakfile = os.path.join(self.__whichq, filebase + '.bak') + pckfile = os.path.join(self.__whichq, filebase + '.pck') + + # First check if the backup file exists + if not os.path.exists(bakfile): + mailman_log('error', 'Switchboard.finish: Backup file does not exist: %s', bakfile) + # Try to clean up the .pck file if it exists + if os.path.exists(pckfile): + try: + os.unlink(pckfile) + mailman_log('info', 'Switchboard.finish: Removed stale .pck file: %s', pckfile) + except OSError as e: + mailman_log('error', 'Switchboard.finish: Failed to remove stale .pck file %s: %s', + pckfile, str(e)) + return + try: if preserve: - psvfile = os.path.join(mm_cfg.BADQUEUE_DIR, filebase + '.psv') - # Log the reason for moving to bad queue - mailman_log('info', 'Moving message to bad queue: %s (queue: %s)', filebase, self.__whichq) + # Move the file to the shunt queue + psvfile = os.path.join(mm_cfg.SHUNTQUEUE_DIR, filebase + '.bak') - # Create the directory if it doesn't yet exist. - # Copied from __init__. - omask = os.umask(0) # rwxrws--- - try: + # Ensure the shunt queue directory exists + if not os.path.exists(mm_cfg.SHUNTQUEUE_DIR): try: - os.mkdir(mm_cfg.BADQUEUE_DIR, 0o0770) + os.makedirs(mm_cfg.SHUNTQUEUE_DIR, 0o775) except OSError as e: - if e.errno != errno.EEXIST: - mailman_log('error', 'Failed to create shunt queue directory %s: %s\nTraceback:\n%s', - mm_cfg.BADQUEUE_DIR, str(e), traceback.format_exc()) - raise - finally: - os.umask(omask) + mailman_log('error', 'Switchboard.finish: Failed to create shunt queue directory: %s', + str(e)) + raise - # Verify source file exists before moving - if not os.path.exists(bakfile): - mailman_log('error', 'Source backup file does not exist: %s', bakfile) - return - # Move the file and verify try: os.rename(bakfile, psvfile) if not os.path.exists(psvfile): - mailman_log('error', 'Failed to move backup file to shunt queue: %s -> %s', - bakfile, psvfile) + mailman_log('error', 'Switchboard.finish: Failed to move backup file to shunt queue: %s -> %s', + bakfile, psvfile) else: - mailman_log('info', 'Successfully moved backup file to shunt queue: %s -> %s', - bakfile, psvfile) + mailman_log('info', 'Switchboard.finish: Successfully moved backup file to shunt queue: %s -> %s', + bakfile, psvfile) except OSError as e: - mailman_log('error', 'Failed to move backup file to shunt queue: %s -> %s: %s\nTraceback:\n%s', - bakfile, psvfile, str(e), traceback.format_exc()) + mailman_log('error', 'Switchboard.finish: Failed to move backup file to shunt queue: %s -> %s: %s', + bakfile, psvfile, str(e)) raise else: - # Verify file exists before unlinking - if not os.path.exists(bakfile): - mailman_log('error', 'Backup file does not exist for unlinking: %s', bakfile) - return - + # Remove the backup file try: os.unlink(bakfile) if os.path.exists(bakfile): - mailman_log('error', 'Failed to unlink backup file: %s', bakfile) + mailman_log('error', 'Switchboard.finish: Failed to unlink backup file: %s', bakfile) else: - mailman_log('info', 'Successfully unlinked backup file: %s', bakfile) + mailman_log('info', 'Switchboard.finish: Successfully unlinked backup file: %s', bakfile) except OSError as e: - mailman_log('error', 'Failed to unlink backup file %s: %s\nTraceback:\n%s', - bakfile, str(e), traceback.format_exc()) + mailman_log('error', 'Switchboard.finish: Failed to unlink backup file %s: %s', + bakfile, str(e)) raise except Exception as e: - mailman_log('error', 'Failed to finish processing backup file %s: %s\nTraceback:\n%s', - bakfile, str(e), traceback.format_exc()) + mailman_log('error', 'Switchboard.finish: Failed to finish processing backup file %s: %s', + bakfile, str(e)) raise def files(self, extension='.pck'): From e940e5ed69d057a1739dc9c8d4035ce75ea3d28c Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 15 May 2025 07:20:47 -0400 Subject: [PATCH 600/748] Reduce logging verbosity in Runner class - Only log significant events and errors - Add thresholds for logging based on message complexity and batch size - Reduce noise in normal operation while maintaining important debug info --- Mailman/Queue/Runner.py | 66 +++++++++++++++++++++++++++++------------ 1 file changed, 47 insertions(+), 19 deletions(-) diff --git a/Mailman/Queue/Runner.py b/Mailman/Queue/Runner.py index a1c3f301..d057c6cf 100644 --- a/Mailman/Queue/Runner.py +++ b/Mailman/Queue/Runner.py @@ -231,16 +231,22 @@ def _oneloop(self): if result: # Message was successfully processed, finish and remove the file self._switchboard.finish(filebase) - syslog('debug', 'Runner._oneloop: Successfully processed message %s, removed file %s', + # Only log significant events + if filecnt > 10: # Log only when processing large batches + syslog('debug', 'Runner._oneloop: Successfully processed message %s, removed file %s', msg.get('message-id', 'n/a'), filebase) elif result is False: # Message needs to be requeued self._switchboard.enqueue(msg, msgdata) - syslog('debug', 'Runner._oneloop: Requeued message %s', msg.get('message-id', 'n/a')) + # Only log significant events + if filecnt > 10: # Log only when processing large batches + syslog('debug', 'Runner._oneloop: Requeued message %s', msg.get('message-id', 'n/a')) else: # Message was shunted self._shunt.enqueue(msg, msgdata) - syslog('debug', 'Runner._oneloop: Shunted message %s', msg.get('message-id', 'n/a')) + # Only log significant events + if filecnt > 10: # Log only when processing large batches + syslog('debug', 'Runner._oneloop: Shunted message %s', msg.get('message-id', 'n/a')) return True except Exception as e: syslog('error', 'Runner._oneloop: Error processing message %s: %s\nTraceback:\n%s', @@ -291,7 +297,9 @@ def _validate_message(self, msg, msgdata): try: # Convert message if needed if not isinstance(msg, Message.Message): - syslog('debug', 'Runner._validate_message: Converting message %s to Mailman.Message', msgid) + # Only log conversion if it's a significant event + if msg.is_multipart() or len(msg.get_payload()) > 1000: + syslog('debug', 'Runner._validate_message: Converting complex message %s to Mailman.Message', msgid) msg = self._convert_message(msg) # Validate required Mailman.Message methods @@ -319,7 +327,9 @@ def _validate_message(self, msg, msgdata): syslog('error', 'Runner._validate_message: Message %s missing To/Recipients', msgid) return msg, False - syslog('debug', 'Runner._validate_message: Message %s validation successful', msgid) + # Only log successful validation for complex messages + if msg.is_multipart() or len(msg.get_payload()) > 1000: + syslog('debug', 'Runner._validate_message: Complex message %s validation successful', msgid) return msg, True except Exception as e: @@ -350,11 +360,15 @@ def _onefile(self, mlist, msg, msgdata): if result: # If _dispose returns True, requeue the message self._switchboard.enqueue(msg, msgdata) - syslog('debug', 'Runner._onefile: Message requeued for %s', listname) + # Only log significant events + if msg.is_multipart() or len(msg.get_payload()) > 1000: + syslog('debug', 'Runner._onefile: Complex message requeued for %s', listname) else: # If _dispose returns False, finish processing and remove the file self._switchboard.finish(msgdata.get('filebase', '')) - syslog('debug', 'Runner._onefile: Message processing completed for %s', listname) + # Only log significant events + if msg.is_multipart() or len(msg.get_payload()) > 1000: + syslog('debug', 'Runner._onefile: Complex message processing completed for %s', listname) return result except Exception as e: self._handle_error(e, msg=msg, mlist=mlist) @@ -389,8 +403,10 @@ def _doperiodic(self): def _snooze(self, filecnt): """Sleep for a while, but check for stop flag periodically.""" if filecnt > 0: - syslog('debug', 'Runner._snooze: Sleeping for %d seconds after processing %d files', - self.SLEEPTIME, filecnt) + # Only log if we're sleeping for more than 5 seconds + if self.SLEEPTIME > 5: + syslog('debug', 'Runner._snooze: Sleeping for %d seconds after processing %d files', + self.SLEEPTIME, filecnt) for _ in range(self.SLEEPTIME): if self._stop: syslog('debug', 'Runner._snooze: Stop flag detected, waking up') @@ -443,26 +459,34 @@ def _check_retry_delay(self, msgid, filebase): last_retry = self._retry_times.get(msgid, 0) if now - last_retry < self.MIN_RETRY_DELAY: - syslog('debug', 'Runner._check_retry_delay: Message %s (file: %s) retry delay not met. Last retry: %s, Now: %s, Delay needed: %s', - msgid, filebase, time.ctime(last_retry), time.ctime(now), self.MIN_RETRY_DELAY) + # Only log if this is a significant delay + if self.MIN_RETRY_DELAY > 300: # 5 minutes + syslog('debug', 'Runner._check_retry_delay: Message %s (file: %s) retry delay not met. Last retry: %s, Now: %s, Delay needed: %s', + msgid, filebase, time.ctime(last_retry), time.ctime(now), self.MIN_RETRY_DELAY) return False - syslog('debug', 'Runner._check_retry_delay: Message %s (file: %s) retry delay met. Last retry: %s, Now: %s', - msgid, filebase, time.ctime(last_retry), time.ctime(now)) + # Only log if this is a significant delay + if self.MIN_RETRY_DELAY > 300: # 5 minutes + syslog('debug', 'Runner._check_retry_delay: Message %s (file: %s) retry delay met. Last retry: %s, Now: %s', + msgid, filebase, time.ctime(last_retry), time.ctime(now)) return True def _mark_message_processed(self, msgid): """Mark a message as processed.""" with self._processed_lock: self._processed_messages.add(msgid) - syslog('debug', 'Runner._mark_message_processed: Marked message %s as processed', msgid) + # Only log if we're tracking a large number of messages + if len(self._processed_messages) > 1000: + syslog('debug', 'Runner._mark_message_processed: Marked message %s as processed', msgid) def _unmark_message_processed(self, msgid): """Remove a message from the processed set.""" with self._processed_lock: if msgid in self._processed_messages: self._processed_messages.remove(msgid) - syslog('debug', 'Runner._unmark_message_processed: Removed message %s from processed set', msgid) + # Only log if we're tracking a large number of messages + if len(self._processed_messages) > 1000: + syslog('debug', 'Runner._unmark_message_processed: Removed message %s from processed set', msgid) def _cleanup_old_messages(self): """Clean up old message tracking data if message tracking is enabled.""" @@ -476,12 +500,16 @@ def _cleanup_old_messages(self): with self._processed_lock: if len(self._processed_messages) > self._max_processed_messages: - syslog('debug', '%s: Clearing processed messages set (size: %d)', - self.__class__.__name__, len(self._processed_messages)) + # Only log if we're clearing a significant number of messages + if len(self._processed_messages) > 1000: + syslog('debug', '%s: Clearing processed messages set (size: %d)', + self.__class__.__name__, len(self._processed_messages)) self._processed_messages.clear() if len(self._retry_times) > self._max_retry_times: - syslog('debug', '%s: Clearing retry times dict (size: %d)', - self.__class__.__name__, len(self._retry_times)) + # Only log if we're clearing a significant number of retry times + if len(self._retry_times) > 1000: + syslog('debug', '%s: Clearing retry times dict (size: %d)', + self.__class__.__name__, len(self._retry_times)) self._retry_times.clear() self._last_cleanup = now except Exception as e: From 5c5ab91c6c6973acd4702d10e771c65c214d9230 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 15 May 2025 07:21:24 -0400 Subject: [PATCH 601/748] Fix file counter in Runner logging - Track actual number of files processed per iteration - Clarify log message to show files processed in current iteration - Rename variables for better clarity --- Mailman/Queue/Runner.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Mailman/Queue/Runner.py b/Mailman/Queue/Runner.py index d057c6cf..ec24b264 100644 --- a/Mailman/Queue/Runner.py +++ b/Mailman/Queue/Runner.py @@ -204,7 +204,8 @@ def _oneloop(self): try: # Get the list of files to process files = self._switchboard.files() - filecnt = len(files) + total_files = len(files) + processed_files = 0 # Process each file for filebase in files: @@ -228,24 +229,25 @@ def _oneloop(self): # Process the message try: result = self._onefile(mlist, msg, msgdata) + processed_files += 1 if result: # Message was successfully processed, finish and remove the file self._switchboard.finish(filebase) # Only log significant events - if filecnt > 10: # Log only when processing large batches + if total_files > 10: # Log only when processing large batches syslog('debug', 'Runner._oneloop: Successfully processed message %s, removed file %s', msg.get('message-id', 'n/a'), filebase) elif result is False: # Message needs to be requeued self._switchboard.enqueue(msg, msgdata) # Only log significant events - if filecnt > 10: # Log only when processing large batches + if total_files > 10: # Log only when processing large batches syslog('debug', 'Runner._oneloop: Requeued message %s', msg.get('message-id', 'n/a')) else: # Message was shunted self._shunt.enqueue(msg, msgdata) # Only log significant events - if filecnt > 10: # Log only when processing large batches + if total_files > 10: # Log only when processing large batches syslog('debug', 'Runner._oneloop: Shunted message %s', msg.get('message-id', 'n/a')) return True except Exception as e: @@ -262,7 +264,7 @@ def _oneloop(self): syslog('error', 'Runner._oneloop: Error in main loop: %s', str(e)) return 0 - return filecnt + return processed_files def _convert_message(self, msg): """Convert email.message.Message to Mailman.Message with proper handling of nested messages. @@ -405,7 +407,7 @@ def _snooze(self, filecnt): if filecnt > 0: # Only log if we're sleeping for more than 5 seconds if self.SLEEPTIME > 5: - syslog('debug', 'Runner._snooze: Sleeping for %d seconds after processing %d files', + syslog('debug', 'Runner._snooze: Sleeping for %d seconds after processing %d files in this iteration', self.SLEEPTIME, filecnt) for _ in range(self.SLEEPTIME): if self._stop: From dc7de8c9ef3a78f0fafad0d28cbbdd99a4d0268d Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 15 May 2025 07:27:16 -0400 Subject: [PATCH 602/748] Fix lock transfer race condition in mailmanctl start by using atomic rename operations --- Mailman/LockFile.py | 101 ++++++++++++++++---------------------------- 1 file changed, 37 insertions(+), 64 deletions(-) diff --git a/Mailman/LockFile.py b/Mailman/LockFile.py index f2785220..b0d221eb 100644 --- a/Mailman/LockFile.py +++ b/Mailman/LockFile.py @@ -221,75 +221,48 @@ def _transfer_to(self, pid): self.__touch() # Find out current claim's temp filename winner = self.__read() - # Now twiddle ours to the given pid - self.__tmpfname = '%s.%s.%d' % ( - self.__lockfile, socket.gethostname(), pid) - # Create a hard link from the global lock file to the temp file. This - # actually does things in reverse order of normal operation because we - # know that lockfile exists, and tmpfname better not! - mailman_log('debug', 'Attempting to transfer lock from %s to %s', winner, self.__tmpfname) - - # Add retry mechanism for link count issues - max_retries = 3 - retry_delay = 0.1 # 100ms delay between retries - for attempt in range(max_retries): + # Create a new temporary file with the target PID + new_tmpfname = '%s.%s.%d' % ( + self.__lockfile, socket.gethostname(), pid) + + try: + # Write the new PID and hostname to the new temp file + with open(new_tmpfname, 'w') as fp: + fp.write('%d %s\n' % (pid, socket.gethostname())) + os.chmod(new_tmpfname, 0o660) + + # Use atomic rename to transfer the lock + os.rename(new_tmpfname, self.__lockfile) + + # Toggle off our ownership of the file so we don't try to finalize it + # in our __del__() + self.__owned = False + + # Unlink the old winner, completing the transfer try: - # Create the temp file first - with open(self.__tmpfname, 'w') as fp: - fp.write('%d %s\n' % (pid, socket.gethostname())) - os.chmod(self.__tmpfname, 0o660) - - # Try to create the hard link - os.link(self.__lockfile, self.__tmpfname) - - # Now update the lock file to contain a reference to the new owner - self.__write() - - # Toggle off our ownership of the file so we don't try to finalize it - # in our __del__() - self.__owned = False - - # Unlink the old winner, completing the transfer os.unlink(winner) + except OSError: + pass - # And do some sanity checks - link_count = self.__linkcount() - if link_count != 2: - # Try to recover by cleaning up and retrying - try: - os.unlink(self.__tmpfname) - if attempt < max_retries - 1: - mailman_log('debug', 'Link count is %d, retrying transfer (attempt %d/%d)', - link_count, attempt + 1, max_retries) - time.sleep(retry_delay) - continue - except OSError: - pass - - mailman_log('error', 'Lock transfer failed: link count is %d, expected 2 for lockfile %s (temp file: %s)', - link_count, self.__lockfile, self.__tmpfname) - raise LockError('Lock transfer failed: link count is %d, expected 2' % link_count) - - if not self.locked(): - mailman_log('error', 'Lock transfer failed: lock not acquired for lockfile %s (temp file: %s)', - self.__lockfile, self.__tmpfname) - raise LockError('Lock transfer failed: lock not acquired') - - mailman_log('debug', 'Successfully transferred lock from %s to %s', winner, self.__tmpfname) - return + # Update our temp filename for future operations + self.__tmpfname = new_tmpfname + + # Verify the lock is still valid + if not self.locked(): + raise LockError('Lock transfer failed: lock not acquired') - except OSError as e: - if attempt < max_retries - 1: - mailman_log('debug', 'Error during lock transfer (attempt %d/%d): %s', - attempt + 1, max_retries, str(e)) - time.sleep(retry_delay) - continue - mailman_log('error', 'Error during lock transfer: %s', str(e)) - raise LockError('Lock transfer failed: %s' % str(e)) - - # If we get here, all retries failed - raise LockError('Lock transfer failed after %d attempts' % max_retries) + mailman_log('debug', 'Successfully transferred lock from %s to %s', winner, new_tmpfname) + return + + except OSError as e: + # Clean up on failure + try: + os.unlink(new_tmpfname) + except OSError: + pass + mailman_log('error', 'Error during lock transfer: %s', str(e)) + raise LockError('Lock transfer failed: %s' % str(e)) def _take_possession(self): """Try to take possession of the lock file. From 1007575b4990edd1ce50ae4845e5267f59b56e10 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 15 May 2025 07:39:12 -0400 Subject: [PATCH 603/748] Fix header handling in CookHeaders.py to ensure proper bounce processing --- Mailman/Handlers/CookHeaders.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Mailman/Handlers/CookHeaders.py b/Mailman/Handlers/CookHeaders.py index c6617080..076b7202 100644 --- a/Mailman/Handlers/CookHeaders.py +++ b/Mailman/Handlers/CookHeaders.py @@ -89,7 +89,20 @@ def change_header(name, value, mlist, msg, msgdata, delete=True, repl=True): (msgdata.get('from_is_list') == 0 and mlist.from_is_list == 2)) and not msgdata.get('_fasttrack') ) or name.lower() in ('from', 'reply-to', 'cc'): + # Store the header in msgdata for later use msgdata.setdefault('add_header', {})[name] = value + # Also add the header to the message if it's not From, Reply-To, or Cc + if name.lower() not in ('from', 'reply-to', 'cc'): + if delete: + del msg[name] + if isinstance(value, Header): + msg[name] = value + else: + try: + msg[name] = str(value) + except UnicodeEncodeError: + msg[name] = Header(value, + Utils.GetCharSet(mlist.preferred_language)) elif repl or name not in msg: if delete: del msg[name] From 7b2ed9aac863af7e1cba7b22b7936ded73d0322d Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 15 May 2025 08:57:53 -0400 Subject: [PATCH 604/748] update --- Mailman/Cgi/admindb.py | 155 +++++++++++++++++++++++++++++------------ Mailman/ListAdmin.py | 70 +++++++++++-------- 2 files changed, 150 insertions(+), 75 deletions(-) diff --git a/Mailman/Cgi/admindb.py b/Mailman/Cgi/admindb.py index e9a61ead..871511ef 100644 --- a/Mailman/Cgi/admindb.py +++ b/Mailman/Cgi/admindb.py @@ -725,80 +725,132 @@ def show_post_requests(mlist, id, info, total, count, form): if total != 1: msg += _(f' (%(count)d of %(total)d)') form.AddItem(Center(Header(2, msg))) - # We need to get the headers and part of the textual body of the message - # being held. The best way to do this is to use the email Parser to get - # an actual object, which will be easier to deal with. We probably could - # just do raw reads on the file. + + # Get the message file path + msgpath = os.path.join(mm_cfg.DATA_DIR, filename) + + # Try to read the message with better error handling try: - msg = readMessage(os.path.join(mm_cfg.DATA_DIR, filename)) + msg = readMessage(msgpath) except IOError as e: if e.errno != errno.ENOENT: + mailman_log('error', 'admindb: Error reading message file %s: %s\n%s', + msgpath, str(e), traceback.format_exc()) raise form.AddItem(_(f'Message with id #%(id)d was lost.')) form.AddItem('

                  ') - # BAW: kludge to remove id from requests.db. try: mlist.HandleRequest(id, mm_cfg.DISCARD) except Errors.LostHeldMessage: pass return - except email.errors.MessageParseError: + except email.errors.MessageParseError as e: + mailman_log('error', 'admindb: Corrupted message file %s: %s\n%s', + msgpath, str(e), traceback.format_exc()) form.AddItem(_(f'Message with id #%(id)d is corrupted.')) - # BAW: Should we really delete this, or shuttle it off for site admin - # to look more closely at? form.AddItem('

                  ') - # BAW: kludge to remove id from requests.db. try: mlist.HandleRequest(id, mm_cfg.DISCARD) except Errors.LostHeldMessage: pass return - # Get the header text and the message body excerpt + except Exception as e: + mailman_log('error', 'admindb: Unexpected error reading message %d: %s\n%s', + id, str(e), traceback.format_exc()) + form.AddItem(_(f'Error reading message #%(id)d.')) + form.AddItem('

                  ') + return + + # Get the header text and the message body excerpt with better encoding handling lines = [] chars = 0 - # A negative value means, include the entire message regardless of size limit = mm_cfg.ADMINDB_PAGE_TEXT_LIMIT - for line in body_line_iterator(msg, decode=True): - lines.append(line) - chars += len(line) - if chars >= limit > 0: - break - # We may have gone over the limit on the last line, but keep the full line - # anyway to avoid losing part of a multibyte character. - body = EMPTYSTRING.join(lines) - # Get message charset and try encode in list charset - # We get it from the first text part. - # We need to replace invalid characters here or we can throw an uncaught - # exception in doc.Format(). + + # Try to determine the message charset + charset = None for part in msg.walk(): if part.get_content_maintype() == 'text': - # Watchout for charset= with no value. - mcset = part.get_content_charset() or 'us-ascii' - break - else: - mcset = 'us-ascii' - lcset = Utils.GetCharSet(mlist.preferred_language) - if mcset != lcset: - try: - body = str(body, mcset, 'replace').encode(lcset, 'replace') - except (LookupError, UnicodeError, ValueError): - pass - hdrtxt = NL.join(['%s: %s' % (k, v) for k, v in list(msg.items())]) - hdrtxt = Utils.websafe(hdrtxt) - # Okay, we've reconstituted the message just fine. Now for the fun part! + charset = part.get_content_charset() + if charset: + break + + # If no charset found, use list's preferred charset + if not charset: + charset = Utils.GetCharSet(mlist.preferred_language) + + # Read the message body with proper encoding + try: + for line in body_line_iterator(msg, decode=True): + # Try to decode the line if it's bytes + if isinstance(line, bytes): + try: + line = line.decode(charset, 'replace') + except (UnicodeError, LookupError): + line = line.decode('latin-1', 'replace') + + lines.append(line) + chars += len(line) + if chars >= limit > 0: + break + except Exception as e: + mailman_log('error', 'admindb: Error reading message body: %s\n%s', + str(e), traceback.format_exc()) + lines = [_('Error reading message body')] + + # Join the lines with proper encoding + try: + body = ''.join(lines) + if isinstance(body, bytes): + body = body.decode(charset, 'replace') + except (UnicodeError, LookupError): + body = _('Error decoding message body') + + # Format the headers with proper encoding + try: + hdrtxt = NL.join(['%s: %s' % (k, v) for k, v in list(msg.items())]) + if isinstance(hdrtxt, bytes): + hdrtxt = hdrtxt.decode(charset, 'replace') + except (UnicodeError, LookupError): + hdrtxt = _('Error decoding message headers') + + # Format the subject with proper encoding + try: + dispsubj = Utils.oneline(subject, charset) + if isinstance(dispsubj, bytes): + dispsubj = dispsubj.decode(charset, 'replace') + except (UnicodeError, LookupError): + dispsubj = _('Error decoding subject') + + # Format the reason with proper encoding + try: + if reason: + reason = _(reason) + if isinstance(reason, bytes): + reason = reason.decode(charset, 'replace') + else: + reason = _('not available') + except (UnicodeError, LookupError): + reason = _('Error decoding reason') + + # Create the form table with proper encoding t = Table(cellspacing=0, cellpadding=0, width='100%') - t.AddRow([Bold(_('From:')), sender]) + t.AddRow([Bold(_('From:')), Utils.websafe(sender)]) row, col = t.GetCurrentRowIndex(), t.GetCurrentCellIndex() t.AddCellInfo(row, col-1, align='right') - t.AddRow([Bold(_('Subject:')), - Utils.websafe(Utils.oneline(subject, lcset))]) + + t.AddRow([Bold(_('Subject:')), Utils.websafe(dispsubj)]) t.AddCellInfo(row+1, col-1, align='right') - t.AddRow([Bold(_('Reason:')), _(reason)]) + + t.AddRow([Bold(_('Reason:')), Utils.websafe(reason)]) t.AddCellInfo(row+2, col-1, align='right') - when = msgdata.get('received_time') - if when: - t.AddRow([Bold(_('Received:')), time.ctime(when)]) + + # Format received time with proper error handling + received_time = format_message_data(msgdata) + if received_time: + t.AddRow([Bold(_('Received:')), received_time]) t.AddCellInfo(row+3, col-1, align='right') + + # Add action buttons buttons = hacky_radio_buttons(id, (_('Defer'), _('Approve'), _('Reject'), _('Discard')), (mm_cfg.DEFER, mm_cfg.APPROVE, mm_cfg.REJECT, mm_cfg.DISCARD), @@ -806,12 +858,16 @@ def show_post_requests(mlist, id, info, total, count, form): spacing=5) t.AddRow([Bold(_('Action:')), buttons]) t.AddCellInfo(t.GetCurrentRowIndex(), col-1, align='right') + + # Add preserve checkbox t.AddRow([' ', '' ]) + + # Add forward checkbox and textbox t.AddRow([' ', '

                  ') diff --git a/Mailman/ListAdmin.py b/Mailman/ListAdmin.py index 1daf0a3e..c7185ff0 100644 --- a/Mailman/ListAdmin.py +++ b/Mailman/ListAdmin.py @@ -803,45 +803,57 @@ def log_file_info(self, path): def readMessage(path): + """Read a message from a file, handling both text and pickle formats. + + Args: + path: Path to the message file + + Returns: + A Message object + + Raises: + IOError: If the file cannot be read + email.errors.MessageParseError: If the message is corrupted + ValueError: If the file format is invalid + """ # For backwards compatibility, we must be able to read either a flat text # file or a pickle. ext = os.path.splitext(path)[1] fp = open(path, 'rb') try: if ext == '.txt': - msg = email.message_from_file(fp, EmailMessage) - # Convert to Mailman.Message if needed - if isinstance(msg, EmailMessage) and not isinstance(msg, Message): - mailman_msg = Message() - # Copy all attributes from the original message - for key, value in msg.items(): - mailman_msg[key] = value - # Copy the payload - if msg.is_multipart(): - for part in msg.get_payload(): - mailman_msg.attach(part) - else: - mailman_msg.set_payload(msg.get_payload()) - msg = mailman_msg + try: + msg = email.message_from_file(fp, EmailMessage) + except Exception as e: + mailman_log('error', 'Error parsing text message file %s: %s\n%s', + path, str(e), traceback.format_exc()) + raise email.errors.MessageParseError(str(e)) else: assert ext == '.pck' - msg = pickle.load(fp, fix_imports=True, encoding='latin1') - # Convert to Mailman.Message if needed - if isinstance(msg, EmailMessage) and not isinstance(msg, Message): - mailman_msg = Message() - # Copy all attributes from the original message - for key, value in msg.items(): - mailman_msg[key] = value - # Copy the payload - if msg.is_multipart(): - for part in msg.get_payload(): - mailman_msg.attach(part) - else: - mailman_msg.set_payload(msg.get_payload()) - msg = mailman_msg + try: + msg = pickle.load(fp, fix_imports=True, encoding='latin1') + except Exception as e: + mailman_log('error', 'Error loading pickled message file %s: %s\n%s', + path, str(e), traceback.format_exc()) + raise ValueError(f'Invalid pickle file: {str(e)}') + + # Convert to Mailman.Message if needed + if isinstance(msg, EmailMessage) and not isinstance(msg, Message): + mailman_msg = Message() + # Copy all attributes from the original message + for key, value in msg.items(): + mailman_msg[key] = value + # Copy the payload + if msg.is_multipart(): + for part in msg.get_payload(): + mailman_msg.attach(part) + else: + mailman_msg.set_payload(msg.get_payload()) + msg = mailman_msg + + return msg finally: fp.close() - return msg def process(mlist, msg, msgdata): # Convert email.message.Message to Mailman.Message.Message if needed From e92da08f32efc82de25cae661156a4969695505d Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 15 May 2025 09:00:54 -0400 Subject: [PATCH 605/748] Fix character encoding issues in UserNotification class - Prevent ASCII encoding for non-ASCII characters, add proper encoding handling with fallbacks --- Mailman/Message.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/Mailman/Message.py b/Mailman/Message.py index 10f0c028..2814e8e9 100644 --- a/Mailman/Message.py +++ b/Mailman/Message.py @@ -284,10 +284,38 @@ def __init__(self, recip, sender, subject=None, text=None, lang=None): charset = None if lang is not None: charset = Charset(GetCharSet(lang)) + # Ensure we have a valid charset that can handle non-ASCII + if charset.output_charset == 'ascii': + charset.output_charset = 'utf-8' if text is not None: + # Handle text encoding properly + if isinstance(text, bytes): + try: + # Try to decode using the provided charset + if charset: + text = text.decode(charset.input_charset, 'replace') + else: + # Fall back to UTF-8 if no charset provided + text = text.decode('utf-8', 'replace') + except (UnicodeDecodeError, LookupError): + # Last resort: latin-1 + text = text.decode('latin-1', 'replace') + elif not isinstance(text, str): + text = str(text) self.set_payload(text, charset) if subject is None: subject = '(no subject)' + # Handle subject encoding properly + if isinstance(subject, bytes): + try: + if charset: + subject = subject.decode(charset.input_charset, 'replace') + else: + subject = subject.decode('utf-8', 'replace') + except (UnicodeDecodeError, LookupError): + subject = subject.decode('latin-1', 'replace') + elif not isinstance(subject, str): + subject = str(subject) self['Subject'] = Header(subject, charset, header_name='Subject', errors='replace') self['From'] = sender From a88cc4c749f9524e9c6498382462f003b69cde33 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 19 May 2025 17:28:36 -0400 Subject: [PATCH 606/748] remove check --- Mailman/Cgi/admin.py | 9 +-------- Mailman/Cgi/listinfo.py | 14 ++++---------- Mailman/Cgi/subscribe.py | 16 ++++------------ 3 files changed, 9 insertions(+), 30 deletions(-) diff --git a/Mailman/Cgi/admin.py b/Mailman/Cgi/admin.py index 18789bf4..a199e43d 100644 --- a/Mailman/Cgi/admin.py +++ b/Mailman/Cgi/admin.py @@ -107,14 +107,7 @@ def main(): if os.environ.get('REQUEST_METHOD') == 'POST': content_length = int(os.environ.get('CONTENT_LENGTH', 0)) if content_length > 0: - # Limit content length to prevent DoS - if content_length > mm_cfg.MAX_CONTENT_LENGTH: - print('Status: 413 Request Entity Too Large') - doc.AddItem(Header(2, _("Error"))) - doc.AddItem(Bold(_('Request too large'))) - print(doc.Format()) - return - form_data = sys.stdin.buffer.read(content_length).decode('utf-8') + form_data = sys.stdin.read(content_length) cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=True) else: cgidata = {} diff --git a/Mailman/Cgi/listinfo.py b/Mailman/Cgi/listinfo.py index c749482a..2627315d 100644 --- a/Mailman/Cgi/listinfo.py +++ b/Mailman/Cgi/listinfo.py @@ -101,17 +101,11 @@ def main(): # See if the user want to see this page in other language try: if os.environ.get('REQUEST_METHOD') == 'POST': + # Get the content length content_length = int(os.environ.get('CONTENT_LENGTH', 0)) - if content_length > 0: - # Limit content length to prevent DoS - if content_length > mm_cfg.MAX_CONTENT_LENGTH: - print('Status: 413 Request Entity Too Large') - listinfo_overview(_('Request too large')) - return - form_data = sys.stdin.buffer.read(content_length).decode('utf-8') - cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=True) - else: - cgidata = {} + # Read the form data + form_data = sys.stdin.read(content_length) + cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=True) else: query_string = os.environ.get('QUERY_STRING', '') cgidata = urllib.parse.parse_qs(query_string, keep_blank_values=True) diff --git a/Mailman/Cgi/subscribe.py b/Mailman/Cgi/subscribe.py index 052c2fa9..b970bd5f 100644 --- a/Mailman/Cgi/subscribe.py +++ b/Mailman/Cgi/subscribe.py @@ -118,19 +118,11 @@ def main(): # for the results. If not, use the list's preferred language. try: if os.environ.get('REQUEST_METHOD') == 'POST': + # Get the content length content_length = int(os.environ.get('CONTENT_LENGTH', 0)) - if content_length > 0: - # Limit content length to prevent DoS - if content_length > mm_cfg.MAX_CONTENT_LENGTH: - print('Status: 413 Request Entity Too Large') - doc.AddItem(Header(2, _("Error"))) - doc.AddItem(Bold(_('Request too large'))) - print(doc.Format()) - return - form_data = sys.stdin.buffer.read(content_length).decode('utf-8') - cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=True) - else: - cgidata = {} + # Read the form data + form_data = sys.stdin.read(content_length) + cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=True) else: query_string = os.environ.get('QUERY_STRING', '') cgidata = urllib.parse.parse_qs(query_string, keep_blank_values=True) From 4c22d978ce6da2ad686e82d5b139a411340d31b5 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 19 May 2025 17:38:28 -0400 Subject: [PATCH 607/748] attempted admindb fix --- Mailman/Cgi/admindb.py | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/Mailman/Cgi/admindb.py b/Mailman/Cgi/admindb.py index 871511ef..22ed69fe 100644 --- a/Mailman/Cgi/admindb.py +++ b/Mailman/Cgi/admindb.py @@ -933,17 +933,6 @@ def safe_get(key, default=''): doc.SetTitle(title) doc.AddItem(Header(2, title)) - # Check if there are any pending requests - admindburl = mlist.GetScriptURL('admindb', absolute=1) - if not mlist.NumRequestsPending(): - replacements = { - 'admindburl': admindburl, - 'logout_url': '%s/logout' % admindburl - } - output = mlist.ParseTags('admindb_empty.html', replacements, mlist.preferred_language) - doc.AddItem(output) - return - # Create a form for the overview with proper encoding form = Form(mlist.GetScriptURL('admindb', absolute=1), mlist=mlist, @@ -968,19 +957,12 @@ def safe_get(key, default=''): # Process the form data process_submissions(mlist, cgidata) # Show success message - replacements = { - 'admindburl': admindburl - } - output = mlist.ParseTags('admindb_success.html', replacements, mlist.preferred_language) - doc.AddItem(output) + doc.AddItem(Header(2, _('Database Updated...'))) return # If we get here, something went wrong - replacements = { - 'admindburl': admindburl - } - output = mlist.ParseTags('admindb_error.html', replacements, mlist.preferred_language) - doc.AddItem(output) + doc.AddItem(Header(2, _('Error'))) + doc.AddItem(Bold(_('Invalid form submission.'))) except Exception as e: mailman_log('error', 'admindb: Error in process_form: %s\n%s', From 78ddd5150c9bbc26a8a8d6207fa60ce04e47c4e7 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 19 May 2025 17:49:02 -0400 Subject: [PATCH 608/748] update --- Mailman/Cgi/admindb.py | 42 ++++++++++-------------------------------- 1 file changed, 10 insertions(+), 32 deletions(-) diff --git a/Mailman/Cgi/admindb.py b/Mailman/Cgi/admindb.py index 22ed69fe..dcb04a0b 100644 --- a/Mailman/Cgi/admindb.py +++ b/Mailman/Cgi/admindb.py @@ -94,34 +94,15 @@ def helds_by_skey(mlist, ssort=SSENDER): def hacky_radio_buttons(btnname, labels, values, defaults, spacing=3): - """Create radio buttons with proper table formatting. - - This is a hack because we can't use RadioButtonArray here - horizontal placement - can be confusing to the user and vertical placement takes up too much real-estate. - """ + # We can't use a RadioButtonArray here because horizontal placement can be + # confusing to the user and vertical placement takes up too much + # real-estate. This is a hack! space = ' ' * spacing - btns = Table(cellspacing='5', cellpadding='0', border='0', width='100%') - - # Add labels row with proper alignment - label_cells = [] - for text in labels: - cell = space + text + space - label_cells.append(cell) - btns.AddRow(label_cells) - - # Add radio buttons row with proper alignment and hidden labels - button_cells = [] - for label, value, default in zip(labels, values, defaults): - cell = Center(RadioButton(btnname, value, default).Format() + - '

                  ') - button_cells.append(cell) - btns.AddRow(button_cells) - - # Set proper alignment for all cells - for row in range(2): - for col in range(len(labels)): - btns.AddCellInfo(row, col, align='center', valign='middle') - + btns = Table(cellspacing='5', cellpadding='0') + btns.AddRow([space + text + space for text in labels]) + btns.AddRow([Center(RadioButton(btnname, value, default).Format() + + '') + for label, value, default in zip(labels, values, defaults)]) return btns @@ -833,7 +814,7 @@ def show_post_requests(mlist, id, info, total, count, form): reason = _('Error decoding reason') # Create the form table with proper encoding - t = Table(cellspacing=0, cellpadding=0, width='100%') + t = Table(cellspacing=0, cellpadding=0) t.AddRow([Bold(_('From:')), Utils.websafe(sender)]) row, col = t.GetCurrentRowIndex(), t.GetCurrentCellIndex() t.AddCellInfo(row, col-1, align='right') @@ -936,8 +917,7 @@ def safe_get(key, default=''): # Create a form for the overview with proper encoding form = Form(mlist.GetScriptURL('admindb', absolute=1), mlist=mlist, - contexts=AUTH_CONTEXTS, - encoding='multipart/form-data') + contexts=AUTH_CONTEXTS) form.AddItem(Center(SubmitButton('submit', _('Submit All Data')))) # Get the action from the form data with proper encoding @@ -948,8 +928,6 @@ def safe_get(key, default=''): show_pending_unsubs(mlist, form) show_helds_overview(mlist, form) doc.AddItem(form) - # Add the footer - doc.AddItem(mlist.GetMailmanFooter()) return # Process the form submission From c7b93003bf58407006076ae060712eb3a15284f5 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 19 May 2025 17:51:49 -0400 Subject: [PATCH 609/748] update --- bin/show_qfiles | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/bin/show_qfiles b/bin/show_qfiles index 65396fae..07ae383d 100644 --- a/bin/show_qfiles +++ b/bin/show_qfiles @@ -36,6 +36,7 @@ from __future__ import print_function import sys import argparse from pickle import load +import pickle import paths from Mailman.i18n import C_ @@ -63,16 +64,28 @@ def main(): for filename in args.qfiles: if not args.quiet: print(('====================>', filename)) - fp = open(filename) if filename.endswith(".pck"): - msg = load(fp) - data = load(fp) - if data.get('_parsemsg'): - sys.stdout.write(msg) - else: - sys.stdout.write(msg.as_string()) + try: + with open(filename, 'rb') as fp: + try: + # Try UTF-8 first for newer files + msg = load(fp, fix_imports=True, encoding='utf-8') + data = load(fp, fix_imports=True, encoding='utf-8') + except (UnicodeDecodeError, pickle.UnpicklingError): + # Fall back to latin1 for older files + fp.seek(0) + msg = load(fp, fix_imports=True, encoding='latin1') + data = load(fp, fix_imports=True, encoding='latin1') + if data.get('_parsemsg'): + sys.stdout.write(msg) + else: + sys.stdout.write(msg.as_string()) + except Exception as e: + print('Error reading pickle file %s: %s' % (filename, str(e)), file=sys.stderr) + sys.exit(1) else: - sys.stdout.write(fp.read()) + with open(filename) as fp: + sys.stdout.write(fp.read()) if __name__ == '__main__': From 5419686f99d2171e0d0b179301037607b0372430 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 19 May 2025 17:55:17 -0400 Subject: [PATCH 610/748] update --- bin/show_qfiles | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/bin/show_qfiles b/bin/show_qfiles index 07ae383d..9268f1ab 100644 --- a/bin/show_qfiles +++ b/bin/show_qfiles @@ -69,17 +69,29 @@ def main(): with open(filename, 'rb') as fp: try: # Try UTF-8 first for newer files - msg = load(fp, fix_imports=True, encoding='utf-8') data = load(fp, fix_imports=True, encoding='utf-8') + if isinstance(data, tuple) and len(data) == 2: + msg, metadata = data + else: + msg = data + metadata = {} except (UnicodeDecodeError, pickle.UnpicklingError): # Fall back to latin1 for older files fp.seek(0) - msg = load(fp, fix_imports=True, encoding='latin1') data = load(fp, fix_imports=True, encoding='latin1') - if data.get('_parsemsg'): + if isinstance(data, tuple) and len(data) == 2: + msg, metadata = data + else: + msg = data + metadata = {} + + # Handle the message output + if isinstance(msg, str): sys.stdout.write(msg) - else: + elif hasattr(msg, 'as_string'): sys.stdout.write(msg.as_string()) + else: + sys.stdout.write(str(msg)) except Exception as e: print('Error reading pickle file %s: %s' % (filename, str(e)), file=sys.stderr) sys.exit(1) From 7f775528cc87063881d918d9ef5fe469378122bf Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 19 May 2025 18:05:49 -0400 Subject: [PATCH 611/748] update --- Mailman/ListAdmin.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Mailman/ListAdmin.py b/Mailman/ListAdmin.py index c7185ff0..e099444a 100644 --- a/Mailman/ListAdmin.py +++ b/Mailman/ListAdmin.py @@ -205,8 +205,11 @@ def SaveRequestsDb(self): def NumRequestsPending(self): self.__opendb() - # Subtract one for the version pseudo-entry - return len(self.__db) - 1 + if not self.__db: + return 0 + # For Python 2 pickles, the version pseudo-entry might not exist + # Just return the length of the dictionary + return len(self.__db) def __getmsgids(self, rtype): self.__opendb() From 2ba65c13fdc9bf8a17e0c0fc7c6be25c52446e56 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 19 May 2025 18:08:53 -0400 Subject: [PATCH 612/748] update --- Mailman/Cgi/admindb.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Mailman/Cgi/admindb.py b/Mailman/Cgi/admindb.py index dcb04a0b..f6c47121 100644 --- a/Mailman/Cgi/admindb.py +++ b/Mailman/Cgi/admindb.py @@ -376,9 +376,16 @@ def show_pending_subs(mlist, form): CheckBox(f'ban-%d' % id, 1).Format() + ' ' + _('Permanently ban from this list') + '') - # While the address may be a unicode, it must be ascii - paddr = addr.encode('us-ascii', 'replace') - table.AddRow(['%s
                  %s
                  %s' % (paddr, + # Ensure the address is properly decoded for display + if isinstance(addr, bytes): + try: + addr = addr.decode('utf-8') + except UnicodeDecodeError: + try: + addr = addr.decode('latin-1') + except UnicodeDecodeError: + addr = addr.decode('ascii', 'replace') + table.AddRow(['%s
                  %s
                  %s' % (Utils.websafe(addr), Utils.websafe(fullname), displaytime), radio, From 2ba2f93e0c5706a9d41f64b64cc9d868bcb7e538 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 19 May 2025 18:18:41 -0400 Subject: [PATCH 613/748] update --- Mailman/Cgi/admindb.py | 302 +++++++++++++++++------------------------ 1 file changed, 123 insertions(+), 179 deletions(-) diff --git a/Mailman/Cgi/admindb.py b/Mailman/Cgi/admindb.py index f6c47121..456fba06 100644 --- a/Mailman/Cgi/admindb.py +++ b/Mailman/Cgi/admindb.py @@ -123,195 +123,139 @@ def output_success_page(doc): def main(): + global ssort + # Figure out which list is being requested + parts = Utils.GetPathPieces() + if not parts: + handle_no_list() + return + + listname = parts[0].lower() try: - # Log page load with process identity - mailman_log('info', 'admindb: Page load started') - mailman_log('info', 'Process identity - EUID: %d, EGID: %d, RUID: %d, RGID: %d', - os.geteuid(), os.getegid(), os.getuid(), os.getgid()) - - # Initialize document early + mlist = MailList.MailList(listname, lock=0) + except Errors.MMListError as e: + # Avoid cross-site scripting attacks + safelistname = Utils.websafe(listname) + # Send this with a 404 status. + print('Status: 404 Not Found') + handle_no_list(_(f'No such list {safelistname}')) + syslog('error', 'admindb: No such list "%s": %s\n', listname, e) + return + + # Now that we know which list to use, set the system's language to it. + i18n.set_language(mlist.preferred_language) + + # Make sure the user is authorized to see this page. + try: + if os.environ.get('REQUEST_METHOD', '').lower() == 'post': + content_type = os.environ.get('CONTENT_TYPE', '') + if content_type.startswith('application/x-www-form-urlencoded'): + content_length = int(os.environ.get('CONTENT_LENGTH', 0)) + form_data = sys.stdin.buffer.read(content_length).decode('latin-1') + cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=1) + else: + raise ValueError('Invalid content type') + else: + cgidata = urllib.parse.parse_qs(os.environ.get('QUERY_STRING', ''), keep_blank_values=1) + except Exception: + # Someone crafted a POST with a bad Content-Type:. doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) - - # Parse form data first since we need it for authentication - try: - if os.environ.get('REQUEST_METHOD', '').lower() == 'post': - content_type = os.environ.get('CONTENT_TYPE', '') - if content_type.startswith('application/x-www-form-urlencoded'): - content_length = int(os.environ.get('CONTENT_LENGTH', 0)) - if content_length > 0: - form_data = sys.stdin.buffer.read(content_length) - try: - # Try UTF-8 first - form_data = form_data.decode('utf-8') - except UnicodeDecodeError: - try: - # Fall back to the list's preferred charset - charset = Utils.GetCharSet(mm_cfg.DEFAULT_SERVER_LANGUAGE) - form_data = form_data.decode(charset) - except (UnicodeDecodeError, LookupError): - # Last resort: latin-1 - form_data = form_data.decode('latin-1', 'replace') - cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=True) - else: - cgidata = {} - else: - mailman_log('error', 'admindb: Invalid content type: %s', content_type) - raise ValueError('Invalid content type') - else: - query_string = os.environ.get('QUERY_STRING', '') - cgidata = urllib.parse.parse_qs(query_string, keep_blank_values=True) - except Exception as e: - mailman_log('error', 'admindb: Invalid form data: %s\n%s', str(e), traceback.format_exc()) - try: - doc = output_error_page('400 Bad Request', 'Error', 'Invalid options to CGI script.') - return output_success_page(doc) - except Exception as output_error: - mailman_log('error', 'admindb: Failed to output error page: %s\n%s', - str(output_error), traceback.format_exc()) - raise - - # Get the list name - parts = Utils.GetPathPieces() - if not parts: - try: - doc = handle_no_list() - return output_success_page(doc) - except Exception as e: - mailman_log('error', 'admindb: Failed to handle no list case: %s\n%s', - str(e), traceback.format_exc()) - raise + doc.AddItem(Header(2, _("Error"))) + doc.AddItem(Bold(_('Invalid options to CGI script.'))) + # Send this with a 400 status. + print('Status: 400 Bad Request') + print(doc.Format()) + return - listname = parts[0].lower() - mailman_log('info', 'admindb: Processing list "%s"', listname) + # CSRF check + safe_params = ['adminpw', 'admlogin', 'msgid', 'sender', 'details'] + params = list(cgidata.keys()) + if set(params) - set(safe_params): + csrf_checked = csrf_check(mlist, cgidata.get('csrf_token', [''])[0], + 'admindb') + else: + csrf_checked = True + # if password is present, void cookie to force password authentication. + if cgidata.get('adminpw', [''])[0]: + os.environ['HTTP_COOKIE'] = '' + csrf_checked = True + + if not mlist.WebAuthenticate((mm_cfg.AuthListAdmin, + mm_cfg.AuthListModerator, + mm_cfg.AuthSiteAdmin), + cgidata.get('adminpw', [''])[0]): + if 'adminpw' in cgidata: + # This is a re-authorization attempt + msg = Bold(FontSize('+1', _('Authorization failed.'))).Format() + remote = os.environ.get('HTTP_FORWARDED_FOR', + os.environ.get('HTTP_X_FORWARDED_FOR', + os.environ.get('REMOTE_ADDR', + 'unidentified origin'))) + syslog('security', + 'Authorization failed (admindb): list=%s: remote=%s', + listname, remote) + else: + msg = '' + Auth.loginpage(mlist, 'admindb', msg=msg) + return - # Check if list directory exists before trying to load - listdir = os.path.join(mm_cfg.LIST_DATA_DIR, listname) - if not os.path.exists(listdir): - mailman_log('error', 'admindb: List directory does not exist: %s', listdir) - try: - doc = output_error_page('404 Not Found', 'Error', - 'No such list %s' % Utils.websafe(listname), - 'The list directory does not exist.') - return output_success_page(doc) - except Exception as e: - mailman_log('error', 'admindb: Failed to output list not found error: %s\n%s', - str(e), traceback.format_exc()) - raise + # Add logout function. Note that admindb may be accessed with + # site-wide admin, moderator and list admin privileges. + # site admin may have site or admin cookie. (or both?) + # See if this is a logout request + if len(parts) >= 2 and parts[1] == 'logout': + if mlist.AuthContextInfo(mm_cfg.AuthSiteAdmin)[0] == 'site': + print(mlist.ZapCookie(mm_cfg.AuthSiteAdmin)) + if mlist.AuthContextInfo(mm_cfg.AuthListModerator)[0]: + print(mlist.ZapCookie(mm_cfg.AuthListModerator)) + print(mlist.ZapCookie(mm_cfg.AuthListAdmin)) + Auth.loginpage(mlist, 'admindb', frontpage=1) + return + # We need a signal handler to catch the SIGTERM that can come from Apache + # when the user hits the browser's STOP button. See the comment in + # admin.py for details. + def sigterm_handler(signum, frame, mlist=mlist): try: - mlist = MailList.MailList(listname, lock=0) - except Errors.MMListError as e: - mailman_log('error', 'admindb: No such list "%s": %s\n%s', - listname, e, traceback.format_exc()) - try: - doc = output_error_page('404 Not Found', 'Error', - 'No such list %s' % Utils.websafe(listname), - 'The list configuration could not be loaded.') - return output_success_page(doc) - except Exception as output_error: - mailman_log('error', 'admindb: Failed to output list error page: %s\n%s', - str(output_error), traceback.format_exc()) - raise - except PermissionError as e: - mailman_log('error', 'admindb: Permission error accessing list "%s": %s\n%s', - listname, e, traceback.format_exc()) - try: - doc = output_error_page('500 Internal Server Error', 'Error', - 'Permission error accessing list %s' % Utils.websafe(listname), - str(e)) - return output_success_page(doc) - except Exception as output_error: - mailman_log('error', 'admindb: Failed to output permission error page: %s\n%s', - str(output_error), traceback.format_exc()) - raise + # Make sure the list gets unlocked... + mlist.Unlock() + # Log the termination + syslog('info', 'admindb: SIGTERM received, unlocking list and exiting') except Exception as e: - mailman_log('error', 'admindb: Unexpected error loading list "%s": %s\n%s', - listname, str(e), traceback.format_exc()) - try: - doc = output_error_page('500 Internal Server Error', 'Error', - 'Error accessing list %s' % Utils.websafe(listname), - str(e)) - return output_success_page(doc) - except Exception as output_error: - mailman_log('error', 'admindb: Failed to output unexpected error page: %s\n%s', - str(output_error), traceback.format_exc()) - raise - - # Now that we know what list has been requested, all subsequent admin - # pages are shown in that list's preferred language. - doc.set_language(mlist.preferred_language) - - # Must be authenticated to get any farther - if not mlist.WebAuthenticate(AUTH_CONTEXTS, cgidata.get('adminpw', [''])[0]): - if 'admlogin' in cgidata: - # This is a re-authorization attempt - msg = Bold(FontSize('+1', _('Authorization failed.'))).Format() - remote = os.environ.get('HTTP_FORWARDED_FOR', - os.environ.get('HTTP_X_FORWARDED_FOR', - os.environ.get('REMOTE_ADDR', - 'unidentified origin'))) - mailman_log('security', - 'Authorization failed (admindb): list=%s: remote=%s\n%s', - listname, remote, traceback.format_exc()) - else: - msg = '' - try: - Auth.loginpage(mlist, 'admindb', msg=msg) - except Exception as e: - mailman_log('error', 'admindb: Failed to display login page: %s\n%s', - str(e), traceback.format_exc()) - raise - return + syslog('error', 'admindb: Error in SIGTERM handler: %s', str(e)) + finally: + # ...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) - # We need a signal handler to catch the SIGTERM that can come from Apache - # when the user hits the browser's STOP button. See the comment in - # admin.py for details. - def sigterm_handler(signum, frame, mlist=mlist): - try: - # Make sure the list gets unlocked... - mlist.Unlock() - # Log the termination - mailman_log('info', 'admindb: SIGTERM received, unlocking list and exiting') - except Exception as e: - mailman_log('error', 'admindb: Error in SIGTERM handler: %s\n%s', - str(e), traceback.format_exc()) - finally: - # ...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) - - mlist.Lock() - try: - # Install the emergency shutdown signal handler - signal.signal(signal.SIGTERM, sigterm_handler) + mlist.Lock() + try: + # Install the emergency shutdown signal handler + signal.signal(signal.SIGTERM, sigterm_handler) - try: - process_form(mlist, doc, cgidata) - mlist.Save() - # Output the success page with proper headers - return output_success_page(doc) - except PermissionError as e: - mailman_log('error', 'admindb: Permission error processing form: %s\n%s', - str(e), traceback.format_exc()) - doc = output_error_page('500 Internal Server Error', 'Error', - 'Permission error while processing request', - str(e)) - return output_success_page(doc) - except Exception as e: - mailman_log('error', 'admindb: Error processing form: %s\n%s', - str(e), traceback.format_exc()) - doc = output_error_page('500 Internal Server Error', 'Error', - 'Error processing request', - str(e)) - return output_success_page(doc) - finally: - mlist.Unlock() - except Exception as e: - mailman_log('error', 'admindb: Unhandled exception in main(): %s\n%s', - str(e), traceback.format_exc()) - raise + try: + process_form(mlist, doc, cgidata) + mlist.Save() + # Output the success page with proper headers + print(doc.Format()) + except PermissionError as e: + syslog('error', 'admindb: Permission error processing form: %s', str(e)) + doc = Document() + doc.set_language(mlist.preferred_language) + doc.AddItem(Header(2, _("Error"))) + doc.AddItem(Bold(_('Permission error while processing request'))) + print(doc.Format()) + except Exception as e: + syslog('error', 'admindb: Error processing form: %s', str(e)) + doc = Document() + doc.set_language(mlist.preferred_language) + doc.AddItem(Header(2, _("Error"))) + doc.AddItem(Bold(_('Error processing request'))) + print(doc.Format()) + finally: + mlist.Unlock() def handle_no_list(msg=''): From 4763877481e1451e65e79e99c03459530ba9b4c2 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 19 May 2025 18:21:10 -0400 Subject: [PATCH 614/748] update --- Mailman/Cgi/admindb.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Mailman/Cgi/admindb.py b/Mailman/Cgi/admindb.py index 456fba06..5f2d70f7 100644 --- a/Mailman/Cgi/admindb.py +++ b/Mailman/Cgi/admindb.py @@ -145,6 +145,10 @@ def main(): # Now that we know which list to use, set the system's language to it. i18n.set_language(mlist.preferred_language) + # Initialize the document + doc = Document() + doc.set_language(mlist.preferred_language) + # Make sure the user is authorized to see this page. try: if os.environ.get('REQUEST_METHOD', '').lower() == 'post': @@ -159,8 +163,6 @@ def main(): cgidata = urllib.parse.parse_qs(os.environ.get('QUERY_STRING', ''), keep_blank_values=1) except Exception: # Someone crafted a POST with a bad Content-Type:. - doc = Document() - doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) doc.AddItem(Header(2, _("Error"))) doc.AddItem(Bold(_('Invalid options to CGI script.'))) # Send this with a 400 status. From 42a714aa03f62c3a54173813eac2175f9cb6b069 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 19 May 2025 18:27:55 -0400 Subject: [PATCH 615/748] update --- bin/check_db | 150 ++++++++++++++++++++++----------------------------- 1 file changed, 63 insertions(+), 87 deletions(-) diff --git a/bin/check_db b/bin/check_db index 575ba5c2..784821dc 100755 --- a/bin/check_db +++ b/bin/check_db @@ -72,98 +72,76 @@ def load_pickle(fp): return None -def testfile(dbfile, verbose=False, listname=None): - # Use our safe loader - loadfunc = load_pickle - if dbfile.endswith('.db') or dbfile.endswith('.db.last'): - loadfunc = marshal.load - elif dbfile.endswith('.pck') or dbfile.endswith('.pck.last'): - loadfunc = load_pickle - else: - assert 0 - if verbose: - print('Processing file: %s' % dbfile) - print(' File type: %s' % ('marshal' if dbfile.endswith('.db') else 'pickle')) - print(' File size: %d bytes' % os.path.getsize(dbfile)) - fp = open(dbfile, 'rb') +def testfile(dbfile, listname=None, verbose=0): + """Test the integrity of a list's config database file.""" try: - data = loadfunc(fp) if verbose: - print(' Successfully loaded data') - if isinstance(data, dict): - print(' Number of entries: %d' % len(data)) - if 'version' in data: - print(' Database version: %s' % data['version']) - # Add detailed debugging for request.pck files - if dbfile.endswith('request.pck'): - print(' Request data structure:') - for key, value in data.items(): - print(' Key: %s' % key) - print(' Value type: %s' % type(value)) - if isinstance(value, (str, bytes)): - print(' Value length: %d' % len(value)) - if len(str(value)) < 100: # Only print short values - print(' Value: %s' % value) - elif isinstance(value, (list, tuple)): - print(' Number of items: %d' % len(value)) - if len(value) > 0: - print(' First item type: %s' % type(value[0])) - if isinstance(value[0], (str, bytes)) and len(str(value[0])) < 100: - print(' First item: %s' % value[0]) - # Handle string/bytes conversion with better error handling - if isinstance(data, bytes): + print(' Loading file %s for list %s...' % + (os.path.basename(dbfile), listname or 'unknown')) + if dbfile.endswith('.pck'): + # Try to load the pickle file try: - data = data.decode('utf-8', 'replace') - except Exception as e: - if verbose: - print(' Warning: Error decoding bytes in %s for list %s: %s' % - (os.path.basename(dbfile), listname or 'unknown', str(e))) - data = str(data) - elif isinstance(data, dict): - new_data = {} - for k, v in data.items(): - try: - if isinstance(k, bytes): - k = k.decode('utf-8', 'replace') - if isinstance(v, bytes): - v = v.decode('utf-8', 'replace') - elif isinstance(v, (list, tuple)): - v = list(v) - for i, item in enumerate(v): - if isinstance(item, bytes): - try: - v[i] = item.decode('utf-8', 'replace') - except Exception as e: - if verbose: - print(' Warning: Error decoding list item in %s for list %s: %s' % - (os.path.basename(dbfile), listname or 'unknown', str(e))) - v[i] = str(item) - new_data[k] = v - except Exception as e: - if verbose: - print(' Warning: Error processing dictionary item in %s for list %s: %s' % - (os.path.basename(dbfile), listname or 'unknown', str(e))) - new_data[str(k)] = str(v) - data = new_data - elif isinstance(data, (list, tuple)): - data = list(data) - for i, item in enumerate(data): - if isinstance(item, bytes): + with open(dbfile, 'rb') as fp: + # First try to detect Python 2 pickle try: - data[i] = item.decode('utf-8', 'replace') - except Exception as e: + fp.seek(0) + header = fp.read(2) + is_py2_pickle = header.startswith(b'c') or header.startswith(b'C') if verbose: - print(' Warning: Error decoding list item in %s for list %s: %s' % - (os.path.basename(dbfile), listname or 'unknown', str(e))) - data[i] = str(item) - return data - except Exception as e: + print(' Python 2 pickle detected: %s' % ('Yes' if is_py2_pickle else 'No')) + except: + is_py2_pickle = False + + # Now load the actual data + fp.seek(0) + data = pickle.load(fp) + if verbose: + # Get pickle version info + fp.seek(0) + version = pickle.format_version + protocol = pickle.HIGHEST_PROTOCOL + print(' Pickle format version: %s' % version) + print(' Pickle protocol: %d' % protocol) + if is_py2_pickle: + print(' WARNING: This file was likely written with Python 2') + print(' String data may need special handling for Python 3 compatibility') + except (EOFError, pickle.UnpicklingError) as e: + print(' Error loading file %s for list %s: %s' % + (os.path.basename(dbfile), listname or 'unknown', str(e))) + # Always print error for request.pck files, even if not verbose + if dbfile.endswith('request.pck'): + print(' File %s for list %s: ERROR - %s' % + (os.path.basename(dbfile), listname or 'unknown', str(e))) + raise + elif dbfile.endswith('.db'): + # Try to load the marshal file + try: + with open(dbfile, 'rb') as fp: + data = marshal.load(fp) + if verbose: + print(' Marshal format version: %d' % marshal.version) + if marshal.version < 2: + print(' WARNING: This file was likely written with Python 2') + print(' String data may need special handling for Python 3 compatibility') + except (EOFError, ValueError) as e: + print(' Error loading file %s for list %s: %s' % + (os.path.basename(dbfile), listname or 'unknown', str(e))) + # Always print error for request.pck files, even if not verbose + if dbfile.endswith('request.pck'): + print(' File %s for list %s: ERROR - %s' % + (os.path.basename(dbfile), listname or 'unknown', str(e))) + raise if verbose: - print(' Error loading file %s for list %s: %s' % + print(' File %s for list %s: OK' % + (os.path.basename(dbfile), listname or 'unknown')) + except Exception as e: + print(' Error loading file %s for list %s: %s' % + (os.path.basename(dbfile), listname or 'unknown', str(e))) + # Always print error for request.pck files, even if not verbose + if dbfile.endswith('request.pck'): + print(' File %s for list %s: ERROR - %s' % (os.path.basename(dbfile), listname or 'unknown', str(e))) raise - finally: - fp.close() def main(): @@ -223,9 +201,7 @@ def main(): for dbfile in dbfiles: if os.path.exists(dbfile): try: - testfile(dbfile, args.verbose, listname) - if args.verbose: - print(' File %s: OK' % os.path.basename(dbfile)) + testfile(dbfile, listname, args.verbose) except Exception as e: print(' File %s: ERROR - %s' % (os.path.basename(dbfile), str(e))) elif args.verbose: From ccbc954b1b03d624ea22028ecd6190f03f59c57f Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 19 May 2025 18:42:38 -0400 Subject: [PATCH 616/748] update --- Mailman/OldStyleMemberships.py | 96 +++++++++++++++++++++++++++++++--- Mailman/Queue/CommandRunner.py | 21 ++++++-- 2 files changed, 106 insertions(+), 11 deletions(-) diff --git a/Mailman/OldStyleMemberships.py b/Mailman/OldStyleMemberships.py index 1035d76d..0febd23b 100644 --- a/Mailman/OldStyleMemberships.py +++ b/Mailman/OldStyleMemberships.py @@ -429,11 +429,93 @@ def setDeliveryStatus(self, member, status): def setBounceInfo(self, member, info): assert self.__mlist.Locked() self.__assertIsMember(member) - member = member.lower() - if info is None: - if member in self.__mlist.bounce_info: - del self.__mlist.bounce_info[member] - if member in self.__mlist.delivery_status: - del self.__mlist.delivery_status[member] + self.__mlist.bounce_info[member.lower()] = info + + def ProcessConfirmation(self, cookie, msg): + """Process a confirmation request. + + Args: + cookie: The confirmation cookie string + msg: The message containing the confirmation request + + Returns: + A tuple of (action_type, action_data) where action_type is one of: + - Pending.SUBSCRIPTION + - Pending.UNSUBSCRIPTION + - Pending.HELD_MESSAGE + And action_data contains the relevant data for that action type. + + Raises: + Errors.MMBadConfirmation: If the confirmation string is invalid + Errors.MMNeedApproval: If the request needs moderator approval + Errors.MMAlreadyAMember: If the user is already a member + Errors.NotAMemberError: If the user is not a member + Errors.MembershipIsBanned: If the user is banned + Errors.HostileSubscriptionError: If the subscription is hostile + Errors.MMBadPasswordError: If the approval password is bad + """ + from Mailman import Pending + from Mailman import Utils + from Mailman import Errors + + # Get the pending request + try: + action, data = Pending.unpickle(cookie) + except Exception as e: + raise Errors.MMBadConfirmation(str(e)) + + # Check if the request has expired + if time.time() > data.get('expiration', 0): + raise Errors.MMBadConfirmation('Confirmation expired') + + # Process based on action type + if action == Pending.SUBSCRIPTION: + # Check if already a member + if self.isMember(data['email']): + raise Errors.MMAlreadyAMember(data['email']) + + # Check if banned + if self.__mlist.isBanned(data['email']): + raise Errors.MembershipIsBanned(data['email']) + + # Add the member + self.addNewMember( + data['email'], + digest=data.get('digest', 0), + password=data.get('password', Utils.MakeRandomPassword()), + language=data.get('language', self.__mlist.preferred_language), + realname=data.get('realname', '') + ) + + elif action == Pending.UNSUBSCRIPTION: + # Check if member + if not self.isMember(data['email']): + raise Errors.NotAMemberError(data['email']) + + # Remove the member + self.removeMember(data['email']) + + elif action == Pending.HELD_MESSAGE: + # Process held message + if data.get('approval_password'): + if data['approval_password'] != self.__mlist.mod_password: + raise Errors.MMBadPasswordError() + + # Forward to moderator if needed + if data.get('need_approval'): + self.__mlist.HoldMessage(msg) + raise Errors.MMNeedApproval() + + # Process the message + if data.get('action') == 'approve': + self.__mlist.ApproveMessage(msg) + else: + self.__mlist.DiscardMessage(msg) + else: - self.__mlist.bounce_info[member] = info + raise Errors.MMBadConfirmation('Unknown action type') + + # Remove the pending request + Pending.remove(cookie) + + return action, data diff --git a/Mailman/Queue/CommandRunner.py b/Mailman/Queue/CommandRunner.py index 1fdbee18..5a3a2587 100644 --- a/Mailman/Queue/CommandRunner.py +++ b/Mailman/Queue/CommandRunner.py @@ -46,6 +46,7 @@ from Mailman.Queue.Runner import Runner from Mailman import LockFile from Mailman import Pending +import traceback # Lazy imports to avoid circular dependencies def get_replybot(): @@ -154,13 +155,19 @@ def do_command(self, cmd, args=None): cmd = cmd.decode('latin-1') # Try to import a command handler module for this command try: + # Clean the command name to prevent injection + cmd = re.sub(r'[^a-zA-Z0-9_]', '', cmd) + if not cmd: + return BADCMD modname = 'Mailman.Commands.cmd_' + cmd __import__(modname) handler = sys.modules[modname] # ValueError can be raised if cmd has dots in it. # and KeyError if cmd is otherwise good but ends with a dot. # and TypeError if cmd has a null byte. - except (ImportError, ValueError, KeyError, TypeError): + except (ImportError, ValueError, KeyError, TypeError) as e: + mailman_log('error', 'CommandRunner: Failed to import command module %s: %s', + modname, str(e)) # If we're on line zero, it was the Subject: header that didn't # contain a command. It's possible there's a Re: prefix (or # localized version thereof) on the Subject: line that's messing @@ -187,10 +194,16 @@ def do_command(self, cmd, args=None): cmd = args.pop(0).lower() return self.do_command(cmd, args) return BADSUBJ - if handler.process(self, args): + try: + if handler.process(self, args): + return STOP + else: + return CONTINUE + except Exception as e: + mailman_log('error', 'CommandRunner: Error processing command %s: %s\nTraceback:\n%s', + cmd, str(e), traceback.format_exc()) + self.results.append(_('Error processing command: %s') % str(e)) return STOP - else: - return CONTINUE def send_response(self): # Helper From e0f88213af68e0178ffd60214bd736e621b87384 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 19 May 2025 18:48:23 -0400 Subject: [PATCH 617/748] update --- Mailman/Handlers/Replybot.py | 143 ++++++++++++-------------------- Mailman/Queue/IncomingRunner.py | 14 ++-- 2 files changed, 58 insertions(+), 99 deletions(-) diff --git a/Mailman/Handlers/Replybot.py b/Mailman/Handlers/Replybot.py index 668f1ccf..c3126002 100644 --- a/Mailman/Handlers/Replybot.py +++ b/Mailman/Handlers/Replybot.py @@ -19,102 +19,61 @@ import time +from Mailman import mm_cfg from Mailman import Utils -from Mailman.Message import Message +from Mailman import Errors +from Mailman import i18n +from Mailman.Message import UserNotification +from Mailman.Logging.Syslog import syslog from Mailman.i18n import _ from Mailman.SafeDict import SafeDict -from Mailman.Logging.Syslog import syslog +# Set up i18n +_ = i18n._ +i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) def process(mlist, msg, msgdata): - # Normally, the replybot should get a shot at this message, but there are - # some important short-circuits, mostly to suppress 'bot storms, at least - # for well behaved email bots (there are other governors for misbehaving - # 'bots). First, if the original message has an "X-Ack: No" header, we - # skip the replybot. Then, if the message has a Precedence header with - # values bulk, junk, or list, and there's no explicit "X-Ack: yes" header, - # we short-circuit. Finally, if the message metadata has a true 'noack' - # key, then we skip the replybot too. - ack = msg.get('x-ack', '').lower() - if ack == 'no' or msgdata.get('noack'): - return - precedence = msg.get('precedence', '').lower() - if ack != 'yes' and precedence in ('bulk', 'junk', 'list'): - return - # Check to see if the list is even configured to autorespond to this email - # message. Note: the owner script sets the `toowner' key, and the various - # confirm, join, leave, request, subscribe and unsubscribe scripts set the - # keys we use for `torequest'. - toadmin = msgdata.get('toowner') - torequest = msgdata.get('torequest') or msgdata.get('toconfirm') or \ - msgdata.get('tojoin') or msgdata.get('toleave') - if ((toadmin and not mlist.autorespond_admin) or - (torequest and not mlist.autorespond_requests) or \ - (not toadmin and not torequest and not mlist.autorespond_postings)): - return - # Now see if we're in the grace period for this sender. graceperiod <= 0 - # means always autorespond, as does an "X-Ack: yes" header (useful for - # debugging). + """Process a message through the replybot handler. + + Args: + mlist: The MailList object + msg: The message to process + msgdata: Additional message metadata + + Returns: + bool: True if message should be discarded, False otherwise + """ + # Get the sender sender = msg.get_sender() - now = time.time() - graceperiod = mlist.autoresponse_graceperiod - if graceperiod > 0 and ack != 'yes': - if toadmin: - quiet_until = mlist.admin_responses.get(sender, 0) - elif torequest: - quiet_until = mlist.request_responses.get(sender, 0) - else: - quiet_until = mlist.postings_responses.get(sender, 0) - if quiet_until > now: - return - # - # Okay, we know we're going to auto-respond to this sender, craft the - # message, send it, and update the database. - realname = mlist.real_name - subject = _( - 'Auto-response for your message to the "%(realname)s" mailing list') - # Do string interpolation - d = SafeDict({'listname' : realname, - 'listurl' : mlist.GetScriptURL('listinfo'), - 'requestemail': mlist.GetRequestEmail(), - # BAW: Deprecate adminemail; it's not advertised but still - # supported for backwards compatibility. - 'adminemail' : mlist.GetBouncesEmail(), - 'owneremail' : mlist.GetOwnerEmail(), - }) - # Just because we're using a SafeDict doesn't mean we can't get all sorts - # of other exceptions from the string interpolation. Let's be ultra - # conservative here. - if toadmin: - rtext = mlist.autoresponse_admin_text - elif torequest: - rtext = mlist.autoresponse_request_text - else: - rtext = mlist.autoresponse_postings_text - # Using $-strings? - if getattr(mlist, 'use_dollar_strings', 0): - rtext = Utils.to_percent(rtext) - try: - text = rtext % d - except Exception: - syslog('error', 'Bad autoreply text for list: %s\n%s', - mlist.internal_name(), rtext) - text = rtext - # Wrap the response. - text = Utils.wrap(text) - outmsg = Mailman.Message.UserNotification(sender, mlist.GetBouncesEmail(), - subject, text, mlist.preferred_language) - outmsg['X-Mailer'] = _('The Mailman Replybot') - # prevent recursions and mail loops! - outmsg['X-Ack'] = 'No' - outmsg.send(mlist) - # update the grace period database - if graceperiod > 0: - # graceperiod is in days, we need # of seconds - quiet_until = now + graceperiod * 24 * 60 * 60 - if toadmin: - mlist.admin_responses[sender] = quiet_until - elif torequest: - mlist.request_responses[sender] = quiet_until - else: - mlist.postings_responses[sender] = quiet_until + if not sender: + return False + + # Check if we should autorespond + if not mlist.autorespondToSender(sender, msgdata.get('lang', mlist.preferred_language)): + return False + + # Create the response message + outmsg = UserNotification(sender, mlist.GetBouncesEmail(), + _('Automatic response from %(listname)s'), + lang=msgdata.get('lang', mlist.preferred_language)) + + # Set the message content + outmsg.set_type('text/plain') + outmsg.set_payload(_("""\ +This message is an automatic response from %(listname)s. + +Your message has been received and will be processed by the list +administrators. Please do not send this message again. + +If you have any questions, please contact the list administrator at +%(adminaddr)s. + +Thank you for your interest in the %(listname)s mailing list. +""") % {'listname': mlist.real_name, + 'adminaddr': mlist.GetOwnerEmail()}) + + # Send the response + outmsg.send(mlist, msgdata=msgdata) + + # Return True to indicate the original message should be discarded + return True diff --git a/Mailman/Queue/IncomingRunner.py b/Mailman/Queue/IncomingRunner.py index 4a12d5ac..7dabe9f5 100644 --- a/Mailman/Queue/IncomingRunner.py +++ b/Mailman/Queue/IncomingRunner.py @@ -179,15 +179,15 @@ def _dispose(self, mlist, msg, msgdata): mailman_log('debug', ' Is list post: %s', is_list_post) # Log recipients information - recipients = msgdata.get('recipients', []) + recipients = msgdata.get('recips', []) mailman_log('debug', ' Recipients: %s', recipients) if not recipients: - mailman_log('error', 'IncomingRunner: No recipients found in msgdata for message %s', msgid) - mailman_log('error', ' Message data: %s', str(msgdata)) - mailman_log('error', ' To header: %s', msg.get('to', 'unknown')) - mailman_log('error', ' Cc header: %s', msg.get('cc', 'unknown')) - mailman_log('error', ' Resent-To: %s', msg.get('resent-to', 'unknown')) - mailman_log('error', ' Resent-Cc: %s', msg.get('resent-cc', 'unknown')) + mailman_log('warning', 'IncomingRunner: No recipients found in msgdata for message %s, pipeline handlers may set them', msgid) + mailman_log('debug', ' Message data: %s', str(msgdata)) + mailman_log('debug', ' To header: %s', msg.get('to', 'unknown')) + mailman_log('debug', ' Cc header: %s', msg.get('cc', 'unknown')) + mailman_log('debug', ' Resent-To: %s', msg.get('resent-to', 'unknown')) + mailman_log('debug', ' Resent-Cc: %s', msg.get('resent-cc', 'unknown')) # Convert Python's Message to Mailman's Message if needed msg = self._convert_message(msg) From 0d32a9a25e731811f3552f5bf581fd82ccabc684 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 19 May 2025 18:51:19 -0400 Subject: [PATCH 618/748] update --- Mailman/OldStyleMemberships.py | 1 + Mailman/Queue/CommandRunner.py | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Mailman/OldStyleMemberships.py b/Mailman/OldStyleMemberships.py index 0febd23b..1e73d084 100644 --- a/Mailman/OldStyleMemberships.py +++ b/Mailman/OldStyleMemberships.py @@ -52,6 +52,7 @@ def __init__(self, mlist): self.digest_is_default = mm_cfg.DEFAULT_DIGEST_IS_DEFAULT # Initialize digest_is_default attribute self.mime_is_default_digest = mm_cfg.DEFAULT_MIME_IS_DEFAULT_DIGEST # Initialize mime_is_default_digest attribute self._pending = {} # Initialize _pending dictionary for pending operations + self.autoresponse_graceperiod = 90 # days, default from Autoresponder class def GetMailmanHeader(self): """Return the standard Mailman header HTML for this list.""" diff --git a/Mailman/Queue/CommandRunner.py b/Mailman/Queue/CommandRunner.py index 5a3a2587..d468235c 100644 --- a/Mailman/Queue/CommandRunner.py +++ b/Mailman/Queue/CommandRunner.py @@ -186,9 +186,11 @@ def do_command(self, cmd, args=None): return BADCMD if self.subjcmdretried < 1: self.subjcmdretried += 1 - if re.search(r'^.*:.+', cmd, re.IGNORECASE): - cmd = re.sub(r'.*:', '', cmd).lower() - return self.do_command(cmd, args) + # Handle both "Re:" and "Re:" without space + if re.search(r'^.*:[^\s]*', cmd, re.IGNORECASE): + cmd = re.sub(r'.*:', '', cmd).strip().lower() + if cmd: # Only retry if we have a command after removing prefix + return self.do_command(cmd, args) if self.subjcmdretried < 2 and args: self.subjcmdretried += 1 cmd = args.pop(0).lower() From fc7c09cd6e32e94086dee009e23d28cd24878269 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 19 May 2025 18:54:24 -0400 Subject: [PATCH 619/748] update --- Mailman/OldStyleMemberships.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Mailman/OldStyleMemberships.py b/Mailman/OldStyleMemberships.py index 1e73d084..73d23591 100644 --- a/Mailman/OldStyleMemberships.py +++ b/Mailman/OldStyleMemberships.py @@ -53,6 +53,9 @@ def __init__(self, mlist): self.mime_is_default_digest = mm_cfg.DEFAULT_MIME_IS_DEFAULT_DIGEST # Initialize mime_is_default_digest attribute self._pending = {} # Initialize _pending dictionary for pending operations self.autoresponse_graceperiod = 90 # days, default from Autoresponder class + self.autorespond_admin = mm_cfg.DEFAULT_AUTORESPOND_ADMIN # Initialize autorespond_admin attribute + self.autorespond_requests = mm_cfg.DEFAULT_AUTORESPOND_REQUESTS # Initialize autorespond_requests attribute + self.autorespond_postings = mm_cfg.DEFAULT_AUTORESPOND_POSTINGS # Initialize autorespond_postings attribute def GetMailmanHeader(self): """Return the standard Mailman header HTML for this list.""" From f626d97dad462749ee76ef465209577d558adee5 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 19 May 2025 18:58:27 -0400 Subject: [PATCH 620/748] update --- Mailman/OldStyleMemberships.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Mailman/OldStyleMemberships.py b/Mailman/OldStyleMemberships.py index 73d23591..00bf8462 100644 --- a/Mailman/OldStyleMemberships.py +++ b/Mailman/OldStyleMemberships.py @@ -30,6 +30,7 @@ from Mailman import Utils from Mailman import Errors from Mailman import MemberAdaptor +from Mailman import Autoresponder ISREGULAR = 1 ISDIGEST = 2 @@ -41,7 +42,7 @@ # Actually, fix /all/ errors -class OldStyleMemberships(MemberAdaptor.MemberAdaptor): +class OldStyleMemberships(MemberAdaptor.MemberAdaptor, Autoresponder.Autoresponder): def __init__(self, mlist): self.__mlist = mlist self.archive = mm_cfg.DEFAULT_ARCHIVE # Initialize archive attribute @@ -52,10 +53,8 @@ def __init__(self, mlist): self.digest_is_default = mm_cfg.DEFAULT_DIGEST_IS_DEFAULT # Initialize digest_is_default attribute self.mime_is_default_digest = mm_cfg.DEFAULT_MIME_IS_DEFAULT_DIGEST # Initialize mime_is_default_digest attribute self._pending = {} # Initialize _pending dictionary for pending operations - self.autoresponse_graceperiod = 90 # days, default from Autoresponder class - self.autorespond_admin = mm_cfg.DEFAULT_AUTORESPOND_ADMIN # Initialize autorespond_admin attribute - self.autorespond_requests = mm_cfg.DEFAULT_AUTORESPOND_REQUESTS # Initialize autorespond_requests attribute - self.autorespond_postings = mm_cfg.DEFAULT_AUTORESPOND_POSTINGS # Initialize autorespond_postings attribute + # Initialize Autoresponder attributes + self.InitVars() def GetMailmanHeader(self): """Return the standard Mailman header HTML for this list.""" From d874c2077fa365abd5ed803e54525e3e57b0bb34 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 19 May 2025 19:03:24 -0400 Subject: [PATCH 621/748] update --- Mailman/Handlers/Hold.py | 11 ++++++----- Mailman/Queue/CommandRunner.py | 8 +++----- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/Mailman/Handlers/Hold.py b/Mailman/Handlers/Hold.py index d1bda51c..ff9716b0 100644 --- a/Mailman/Handlers/Hold.py +++ b/Mailman/Handlers/Hold.py @@ -254,14 +254,12 @@ def hold_for_approval(mlist, msg, msgdata, exc): mlist.Unlock() else: cookie = mlist.pend_new(Pending.HELD_MESSAGE, id) - # We may want to send a notification to the original sender too fromusenet = msgdata.get('fromusenet') # Since we're sending two messages, which may potentially be in different # languages (the user's preferred and the list's preferred for the admin), # we need to play some i18n games here. Since the current language # context ought to be set up for the user, let's craft his message first. - if not fromusenet and ackp(msg) and mlist.respond_to_post_requests and \ mlist.autorespondToSender(sender, mlist.getMemberLanguage(sender)): # Get a confirmation cookie @@ -294,7 +292,7 @@ def hold_for_approval(mlist, msg, msgdata, exc): text = MIMEText( Utils.maketext('postauth.txt', d, raw=1, mlist=mlist), _charset=charset) - dmsg = MIMEText(Utils.wrap(_("""\ + dmsg = MIMEText(Utils.wrap(_(""" If you reply to this message, keeping the Subject: header intact, Mailman will discard the held message. Do this if the message is spam. If you reply to this message and include an Approved: header with the list password in it, the @@ -312,12 +310,15 @@ def hold_for_approval(mlist, msg, msgdata, exc): nmsg.send(mlist, **{'tomoderators': 1}) finally: i18n.set_translation(otranslation) - # Log the held message - syslog('vette', '%s post from %s held, message-id=%s: %s', + # Log the held message (info level, not error) + syslog('info', '[HOLD] %s post from %s held for approval, message-id=%s, reason=%s', listname, sender, message_id, reason) # raise the specific MessageHeld exception to exit out of the message # delivery pipeline raise exc + except Errors.HoldMessage: + # Already handled above, do not log traceback + raise except Exception as e: syslog('error', 'Error in Hold.hold_for_approval: %s\nTraceback:\n%s', str(e), traceback.format_exc()) diff --git a/Mailman/Queue/CommandRunner.py b/Mailman/Queue/CommandRunner.py index d468235c..f53e473c 100644 --- a/Mailman/Queue/CommandRunner.py +++ b/Mailman/Queue/CommandRunner.py @@ -186,11 +186,9 @@ def do_command(self, cmd, args=None): return BADCMD if self.subjcmdretried < 1: self.subjcmdretried += 1 - # Handle both "Re:" and "Re:" without space - if re.search(r'^.*:[^\s]*', cmd, re.IGNORECASE): - cmd = re.sub(r'.*:', '', cmd).strip().lower() - if cmd: # Only retry if we have a command after removing prefix - return self.do_command(cmd, args) + if re.search('^.*:.+', cmd): + cmd = re.sub('.*:', '', cmd).lower() + return self.do_command(cmd, args) if self.subjcmdretried < 2 and args: self.subjcmdretried += 1 cmd = args.pop(0).lower() From 6807a49322ea171927d2ba3b631d59e8730f7942 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 19 May 2025 19:07:22 -0400 Subject: [PATCH 622/748] update --- Mailman/Queue/CommandRunner.py | 79 ++++++++++++++++------------------ 1 file changed, 38 insertions(+), 41 deletions(-) diff --git a/Mailman/Queue/CommandRunner.py b/Mailman/Queue/CommandRunner.py index f53e473c..62e48150 100644 --- a/Mailman/Queue/CommandRunner.py +++ b/Mailman/Queue/CommandRunner.py @@ -67,6 +67,25 @@ def get_usernotification(): BADCMD = 2 BADSUBJ = 3 +# List of valid commands that can be imported +VALID_COMMANDS = { + 'confirm', # Confirm subscription/unsubscription + 'echo', # Echo command + 'end', # End command + 'help', # Help command + 'info', # List information + 'join', # Join list + 'leave', # Leave list + 'lists', # List all lists + 'password', # Password command + 'remove', # Remove from list + 'set', # Set options + 'stop', # Stop command + 'subscribe', # Subscribe to list + 'unsubscribe',# Unsubscribe from list + 'who' # Who command +} + class Results: def __init__(self, mlist_obj, msg, msgdata): self.mlist = mlist_obj @@ -147,63 +166,41 @@ def process(self): def do_command(self, cmd, args=None): if args is None: args = () - # Ensure cmd is a string - if isinstance(cmd, bytes): - try: - cmd = cmd.decode('utf-8') - except UnicodeDecodeError: - cmd = cmd.decode('latin-1') - # Try to import a command handler module for this command - try: - # Clean the command name to prevent injection - cmd = re.sub(r'[^a-zA-Z0-9_]', '', cmd) - if not cmd: - return BADCMD - modname = 'Mailman.Commands.cmd_' + cmd - __import__(modname) - handler = sys.modules[modname] - # ValueError can be raised if cmd has dots in it. - # and KeyError if cmd is otherwise good but ends with a dot. - # and TypeError if cmd has a null byte. - except (ImportError, ValueError, KeyError, TypeError) as e: - mailman_log('error', 'CommandRunner: Failed to import command module %s: %s', - modname, str(e)) + # Clean the command name to prevent injection + cmd = cmd.lower().strip() + # Only try to import valid commands + if cmd not in VALID_COMMANDS: # If we're on line zero, it was the Subject: header that didn't # contain a command. It's possible there's a Re: prefix (or # localized version thereof) on the Subject: line that's messing # things up. Pop the prefix off and try again... once. - # - # At least one MUA (163.com web mail) has been observed that - # inserts 'Re:' with no following space, so try to account for - # that too. - # - # If that still didn't work it isn't enough to stop processing. - # BAW: should we include a message that the Subject: was ignored? - # - # But first, be sure we're looking at the Subject: and not past - # it already. if self.lineno != 0: return BADCMD if self.subjcmdretried < 1: self.subjcmdretried += 1 if re.search('^.*:.+', cmd): - cmd = re.sub('.*:', '', cmd).lower() + cmd = re.sub('.*:', '', cmd).lower().strip() return self.do_command(cmd, args) if self.subjcmdretried < 2 and args: self.subjcmdretried += 1 - cmd = args.pop(0).lower() + cmd = args.pop(0).lower().strip() return self.do_command(cmd, args) return BADSUBJ + + # Try to import a command handler module for this command + modname = 'Mailman.Commands.cmd_' + cmd try: - if handler.process(self, args): - return STOP - else: - return CONTINUE - except Exception as e: - mailman_log('error', 'CommandRunner: Error processing command %s: %s\nTraceback:\n%s', - cmd, str(e), traceback.format_exc()) - self.results.append(_('Error processing command: %s') % str(e)) + __import__(modname) + handler = sys.modules[modname] + except (ImportError, ValueError, KeyError, TypeError) as e: + syslog('error', 'CommandRunner: Failed to import command module %s: %s', + modname, str(e)) + return BADCMD + + if handler.process(self, args): return STOP + else: + return CONTINUE def send_response(self): # Helper From 9216755bdf99cc59615c9ba3c479a87d1a3fbbe9 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 19 May 2025 19:10:16 -0400 Subject: [PATCH 623/748] update --- Mailman/Queue/Switchboard.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Mailman/Queue/Switchboard.py b/Mailman/Queue/Switchboard.py index 2b170c7c..7d48d95b 100644 --- a/Mailman/Queue/Switchboard.py +++ b/Mailman/Queue/Switchboard.py @@ -259,12 +259,13 @@ def finish(self, filebase, preserve=False): # First check if the backup file exists if not os.path.exists(bakfile): - mailman_log('error', 'Switchboard.finish: Backup file does not exist: %s', bakfile) - # Try to clean up the .pck file if it exists + # Only log at debug level if the .pck file still exists (message still being processed) if os.path.exists(pckfile): + mailman_log('debug', 'Switchboard.finish: Backup file does not exist: %s', bakfile) + # Try to clean up the .pck file if it exists try: os.unlink(pckfile) - mailman_log('info', 'Switchboard.finish: Removed stale .pck file: %s', pckfile) + mailman_log('debug', 'Switchboard.finish: Removed stale .pck file: %s', pckfile) except OSError as e: mailman_log('error', 'Switchboard.finish: Failed to remove stale .pck file %s: %s', pckfile, str(e)) @@ -291,7 +292,7 @@ def finish(self, filebase, preserve=False): mailman_log('error', 'Switchboard.finish: Failed to move backup file to shunt queue: %s -> %s', bakfile, psvfile) else: - mailman_log('info', 'Switchboard.finish: Successfully moved backup file to shunt queue: %s -> %s', + mailman_log('debug', 'Switchboard.finish: Successfully moved backup file to shunt queue: %s -> %s', bakfile, psvfile) except OSError as e: mailman_log('error', 'Switchboard.finish: Failed to move backup file to shunt queue: %s -> %s: %s', @@ -304,7 +305,7 @@ def finish(self, filebase, preserve=False): if os.path.exists(bakfile): mailman_log('error', 'Switchboard.finish: Failed to unlink backup file: %s', bakfile) else: - mailman_log('info', 'Switchboard.finish: Successfully unlinked backup file: %s', bakfile) + mailman_log('debug', 'Switchboard.finish: Successfully unlinked backup file: %s', bakfile) except OSError as e: mailman_log('error', 'Switchboard.finish: Failed to unlink backup file %s: %s', bakfile, str(e)) From f8fe880e0478857d1c434305970b3495c2df2249 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 19 May 2025 19:17:36 -0400 Subject: [PATCH 624/748] update --- Mailman/Handlers/Hold.py | 4 ++++ Mailman/Queue/CommandRunner.py | 6 +++++- Mailman/Queue/Switchboard.py | 19 +++++++++++++++---- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/Mailman/Handlers/Hold.py b/Mailman/Handlers/Hold.py index ff9716b0..9b109497 100644 --- a/Mailman/Handlers/Hold.py +++ b/Mailman/Handlers/Hold.py @@ -190,7 +190,11 @@ def process(mlist, msg, msgdata): # moderator's address for the group? if mlist.gateway_to_news and mlist.news_moderation == 2: hold_for_approval(mlist, msg, msgdata, ModeratedNewsgroup) + except Errors.HoldMessage: + # These are expected conditions, not errors + raise except Exception as e: + # Only log unexpected errors syslog('error', 'Error in Hold.process: %s\nTraceback:\n%s', str(e), traceback.format_exc()) raise diff --git a/Mailman/Queue/CommandRunner.py b/Mailman/Queue/CommandRunner.py index 62e48150..8e5b975f 100644 --- a/Mailman/Queue/CommandRunner.py +++ b/Mailman/Queue/CommandRunner.py @@ -104,7 +104,11 @@ def __init__(self, mlist_obj, msg, msgdata): # Extract the subject header and do RFC 2047 decoding subj = msg.get('subject', '') try: - subj = str(make_header(decode_header(subj))) + # If subj is already a Header object, convert it to string first + if isinstance(subj, Header): + subj = str(subj) + else: + subj = str(make_header(decode_header(subj))) # TK: Currently we don't allow 8bit or multibyte in mail command. # MAS: However, an l10n 'Re:' may contain non-ascii so ignore it. subj = subj.encode('us-ascii', 'ignore').decode('us-ascii') diff --git a/Mailman/Queue/Switchboard.py b/Mailman/Queue/Switchboard.py index 7d48d95b..315d7ab7 100644 --- a/Mailman/Queue/Switchboard.py +++ b/Mailman/Queue/Switchboard.py @@ -199,10 +199,18 @@ def enqueue(self, msg, msgdata=None, listname=None, _plaintext=False, **kwargs): def dequeue(self, filebase): # Calculate the filename from the given filebase. filename = os.path.join(self.__whichq, filebase + '.pck') + bakfile = os.path.join(self.__whichq, filebase + '.bak') + psvfile = os.path.join(self.__whichq, filebase + '.psv') # Check if file exists before proceeding if not os.path.exists(filename): - mailman_log('warning', 'Queue file does not exist: %s', filename) + # Check if it's been moved to backup or shunt + if os.path.exists(bakfile): + mailman_log('debug', 'Queue file %s has been moved to backup file %s', filename, bakfile) + elif os.path.exists(psvfile): + mailman_log('debug', 'Queue file %s has been moved to shunt queue %s', filename, psvfile) + else: + mailman_log('warning', 'Queue file does not exist: %s (not found in backup or shunt either)', filename) return None, None # Create a lock file @@ -215,6 +223,7 @@ def dequeue(self, filebase): if e.errno != errno.EEXIST: mailman_log('error', 'Switchboard.dequeue: Failed to create lock file for %s: %s', filebase, str(e)) raise + mailman_log('debug', 'Queue file %s is currently locked by another process', filename) return None, None try: @@ -225,14 +234,16 @@ def dequeue(self, filebase): if data is not None: data['filebase'] = filebase # Log the full msgdata after dequeuing - mailman_log('debug', 'Switchboard.dequeue: Full msgdata after dequeuing:\n%s', str(data)) + mailman_log('debug', 'Switchboard.dequeue: Successfully dequeued file %s', filebase) except Exception as e: - mailman_log('error', 'Switchboard.dequeue: Failed to read message from %s: %s', filebase, str(e)) + mailman_log('error', 'Switchboard.dequeue: Failed to read message from %s: %s\nTraceback:\n%s', + filebase, str(e), traceback.format_exc()) raise # Validate data structure before returning if not isinstance(data, dict): - mailman_log('error', 'Switchboard.dequeue: Invalid data structure in %s: expected dict, got %s', filename, type(data)) + mailman_log('error', 'Switchboard.dequeue: Invalid data structure in %s: expected dict, got %s', + filename, type(data)) return None, None return msg, data From 88896687f049f89e52277a07781cb6b3a16e065d Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 19 May 2025 19:26:42 -0400 Subject: [PATCH 625/748] update --- Mailman/Handlers/Hold.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/Mailman/Handlers/Hold.py b/Mailman/Handlers/Hold.py index 9b109497..3e88c6e1 100644 --- a/Mailman/Handlers/Hold.py +++ b/Mailman/Handlers/Hold.py @@ -110,7 +110,9 @@ def rejection_notice(self, mlist): class ModeratedNewsgroup(ModeratedPost): reason = _('Posting to a moderated newsgroup') - +class HTMLViewerRequired(Errors.HoldMessage): + reason = _('Message contains HTML viewer required text') + rejection = _('Your message contains text indicating it requires an HTML viewer, which is not allowed.') # And reset the translator _ = i18n._ @@ -139,6 +141,23 @@ def process(mlist, msg, msgdata): if not sender or sender[:len(listname)+6] == adminaddr: sender = msg.get_sender(use_envelope=0) # + # Check for HTML viewer required text in text/plain parts + for part in msg.walk(): + if part.get_content_type() == 'text/plain': + payload = part.get_payload(decode=True) + if payload: + try: + text = payload.decode('utf-8', errors='replace') + if "An HTML viewer is required to see this message" in text: + hold_for_approval(mlist, msg, msgdata, HTMLViewerRequired) + return + except (UnicodeDecodeError, AttributeError): + # If we can't decode the payload, try as bytes + if isinstance(payload, bytes): + if b"An HTML viewer is required to see this message" in payload: + hold_for_approval(mlist, msg, msgdata, HTMLViewerRequired) + return + # # Possible administrivia? if mlist.administrivia and Utils.is_administrivia(msg): hold_for_approval(mlist, msg, msgdata, Administrivia) From f24fe8877f69dd6c2c6ed89250913f8274af4b0b Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 19 May 2025 19:43:45 -0400 Subject: [PATCH 626/748] update --- Mailman/Queue/IncomingRunner.py | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/Mailman/Queue/IncomingRunner.py b/Mailman/Queue/IncomingRunner.py index 7dabe9f5..70932496 100644 --- a/Mailman/Queue/IncomingRunner.py +++ b/Mailman/Queue/IncomingRunner.py @@ -362,24 +362,29 @@ def _oneloop(self): # Process each file for filebase in files: try: - # Check if we need to cleanup old messages - if time.time() - self._last_cleanup > self._cleanup_interval: - self._cleanup_old_messages() - # Dequeue the file msg, msgdata = self._switchboard.dequeue(filebase) - # Skip if dequeue failed - if msg is None or msgdata is None: - mailman_log('error', 'IncomingRunner._oneloop: Failed to dequeue file %s (got None values)', filebase) + # If dequeue failed due to file being locked, skip it + if msg is None and msgdata is None: + # Check if the file is locked + lockfile = os.path.join(self.QDIR, filebase + '.pck.lock') + if os.path.exists(lockfile): + mailman_log('debug', 'IncomingRunner._oneloop: File %s is locked by another process, skipping', filebase) + continue + # For other None,None cases, shunt the message + mailman_log('error', 'IncomingRunner._oneloop: Failed to dequeue file %s (got None values), shunting', filebase) + # Create a basic message and metadata if we don't have them + msg = Message() + msgdata = {} + # Add the original queue information + msgdata['whichq'] = self.QDIR + # Shunt the message + self._shunt.enqueue(msg, msgdata) continue msgid = msg.get('message-id', 'n/a') - # Check if message was recently processed - if self._check_message_processed(msgid, filebase, msg): - continue - # Get the list name listname = msgdata.get('listname', 'unknown') try: @@ -407,9 +412,6 @@ def _oneloop(self): str(e), traceback.format_exc()) # Move to shunt queue on error self._shunt.enqueue(msg, msgdata) - finally: - # Always mark message as processed - self._mark_message_processed(msgid) except Exception as e: mailman_log('error', 'IncomingRunner._oneloop: Error dequeuing file %s: %s\n%s', From 42eb97d57d6a71cb99188abf700bf03c089f4bb2 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 19 May 2025 19:46:50 -0400 Subject: [PATCH 627/748] update --- Mailman/Handlers/CookHeaders.py | 3 +++ Mailman/Queue/IncomingRunner.py | 18 ++++++++++++------ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/Mailman/Handlers/CookHeaders.py b/Mailman/Handlers/CookHeaders.py index 076b7202..a29bf6c2 100644 --- a/Mailman/Handlers/CookHeaders.py +++ b/Mailman/Handlers/CookHeaders.py @@ -237,6 +237,9 @@ def prefix_subject(mlist, msg, msgdata): # If subject is already a string, use it directly if isinstance(subject, str): subject_str = subject + # If subject is a Header object, convert it to string + elif isinstance(subject, Header): + subject_str = str(subject) else: # Try to decode the subject try: diff --git a/Mailman/Queue/IncomingRunner.py b/Mailman/Queue/IncomingRunner.py index 70932496..46411df6 100644 --- a/Mailman/Queue/IncomingRunner.py +++ b/Mailman/Queue/IncomingRunner.py @@ -383,15 +383,20 @@ def _oneloop(self): self._shunt.enqueue(msg, msgdata) continue - msgid = msg.get('message-id', 'n/a') + # Try to get message-id early for logging purposes + try: + msgid = msg.get('message-id', 'n/a') + except Exception as e: + msgid = 'unknown' + mailman_log('error', 'IncomingRunner._oneloop: Error getting message-id for file %s: %s', filebase, str(e)) # Get the list name listname = msgdata.get('listname', 'unknown') try: mlist = MailList.MailList(listname, lock=False) except Errors.MMUnknownListError: - mailman_log('error', 'IncomingRunner._oneloop: Unknown list %s for message %s', - listname, msgid) + mailman_log('error', 'IncomingRunner._oneloop: Unknown list %s for message %s (file: %s)', + listname, msgid, filebase) self._shunt.enqueue(msg, msgdata) continue @@ -402,14 +407,15 @@ def _oneloop(self): # If the message should be kept in the queue, requeue it if result: self._switchboard.enqueue(msg, msgdata) - mailman_log('info', 'IncomingRunner._oneloop: Message requeued for later processing: %s', filebase) + mailman_log('info', 'IncomingRunner._oneloop: Message requeued for later processing: %s (msgid: %s)', + filebase, msgid) else: mailman_log('info', 'IncomingRunner._oneloop: Message processing complete, moving to shunt queue %s (msgid: %s)', filebase, msgid) except Exception as e: - mailman_log('error', 'IncomingRunner._oneloop: Error processing message: %s\n%s', - str(e), traceback.format_exc()) + mailman_log('error', 'IncomingRunner._oneloop: Error processing message %s (file: %s): %s\n%s', + msgid, filebase, str(e), traceback.format_exc()) # Move to shunt queue on error self._shunt.enqueue(msg, msgdata) From 454b02731740e2dd364936a0af7180599c18eb8e Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 19 May 2025 19:49:14 -0400 Subject: [PATCH 628/748] update --- Mailman/Handlers/CookHeaders.py | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/Mailman/Handlers/CookHeaders.py b/Mailman/Handlers/CookHeaders.py index a29bf6c2..2e2f1842 100644 --- a/Mailman/Handlers/CookHeaders.py +++ b/Mailman/Handlers/CookHeaders.py @@ -254,25 +254,6 @@ def prefix_subject(mlist, msg, msgdata): # Add the prefix if it's not already there if not subject_str.startswith(prefix): msg['Subject'] = prefix + ' ' + subject_str - - # Mark message as processed - mailman_log('debug', 'CookHeaders: Adding X-BeenThere header for message %s', msgid) - change_header('X-BeenThere', mlist.GetListEmail(), - mlist, msg, msgdata, delete=False) - - # Add standard headers - mailman_log('debug', 'CookHeaders: Adding standard headers for message %s', msgid) - change_header('X-Mailman-Version', mm_cfg.VERSION, - mlist, msg, msgdata, repl=False) - change_header('Precedence', 'list', - mlist, msg, msgdata, repl=False) - - # Handle From: header munging if needed - if (msgdata.get('from_is_list') or mlist.from_is_list) and not msgdata.get('_fasttrack'): - mailman_log('debug', 'CookHeaders: Munging From header for message %s', msgid) - munge_from_header(mlist, msg, msgdata) - - mailman_log('debug', 'CookHeaders: Finished processing message %s', msgid) def ch_oneline(headerstr): # Decode header string in one line and convert into single charset From 3e1492356a4857a9328eddf78e81283d8ea62d1a Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 19 May 2025 19:55:42 -0400 Subject: [PATCH 629/748] update --- Mailman/Queue/IncomingRunner.py | 5 +++ Mailman/Queue/VirginRunner.py | 72 +++++---------------------------- bin/qrunner | 16 ++++++++ 3 files changed, 30 insertions(+), 63 deletions(-) diff --git a/Mailman/Queue/IncomingRunner.py b/Mailman/Queue/IncomingRunner.py index 46411df6..61a7d5f3 100644 --- a/Mailman/Queue/IncomingRunner.py +++ b/Mailman/Queue/IncomingRunner.py @@ -361,6 +361,11 @@ def _oneloop(self): # Process each file for filebase in files: + # Check stop flag at the start of each file + if self._stop: + mailman_log('debug', 'IncomingRunner._oneloop: Stop flag detected, stopping processing') + return filecnt + try: # Dequeue the file msg, msgdata = self._switchboard.dequeue(filebase) diff --git a/Mailman/Queue/VirginRunner.py b/Mailman/Queue/VirginRunner.py index b7f58e3a..ed117549 100644 --- a/Mailman/Queue/VirginRunner.py +++ b/Mailman/Queue/VirginRunner.py @@ -35,8 +35,6 @@ class VirginRunner(IncomingRunner): QDIR = mm_cfg.VIRGINQUEUE_DIR - # Override the minimum retry delay for virgin messages - MIN_RETRY_DELAY = 60 # 1 minute minimum delay between retries # Maximum age for message tracking data _max_tracking_age = 86400 # 24 hours in seconds # Cleanup interval for message tracking data @@ -66,8 +64,8 @@ def __init__(self, slice=None, numslices=1): raise def _check_message_processed(self, msgid, filebase, msg): - """Check if a message has already been processed and if retry delay is met. - Returns True if the message can be processed, False if it's a duplicate or retry delay not met.""" + """Check if a message has already been processed. + Returns True if the message can be processed, False if it's a duplicate.""" try: with self._processed_lock: current_time = time.time() @@ -117,21 +115,9 @@ def _check_message_processed(self, msgid, filebase, msg): # For other messages, check message ID if msgid in self._processed_messages: - # Check retry delay - last_retry = self._processed_times.get(msgid) - if last_retry is not None: - time_since_last_retry = current_time - last_retry - if time_since_last_retry < self.MIN_RETRY_DELAY: - mailman_log('info', 'VirginRunner: Message %s (file: %s) retry delay not met. Time since last retry: %d seconds, minimum required: %d seconds', - msgid, filebase, time_since_last_retry, self.MIN_RETRY_DELAY) - return False - else: - mailman_log('debug', 'VirginRunner: Message %s (file: %s) retry delay met. Time since last retry: %d seconds', - msgid, filebase, time_since_last_retry) - else: - mailman_log('info', 'VirginRunner: Duplicate message detected: %s (file: %s)', - msgid, filebase) - return False + mailman_log('info', 'VirginRunner: Duplicate message detected: %s (file: %s)', + msgid, filebase) + return False # Mark message as processed try: @@ -154,50 +140,10 @@ def _check_message_processed(self, msgid, filebase, msg): return False def _dispose(self, mlist, msg, msgdata): - """Process a virgin message.""" - msgid = msg.get('message-id', 'n/a') - filebase = msgdata.get('_filebase', 'unknown') - - # Check if message has already been processed - if not self._check_message_processed(msgid, filebase, msg): - self._shunt.enqueue(msg, msgdata) - return False - - mailman_log('debug', 'VirginRunner._dispose: Starting to process virgin message %s (file: %s)', - msgid, filebase) - - # Ensure we have a MailList object - if isinstance(mlist, str): - try: - # Lazy import to avoid circular dependency - from Mailman.MailList import MailList - mlist = MailList(mlist, lock=0) - should_unlock = True - except Errors.MMUnknownListError: - mailman_log('error', 'VirginRunner: Unknown list %s', mlist) - self._shunt.enqueue(msg, msgdata) - return False - else: - should_unlock = False - - try: - # Process the message using IncomingRunner's _dispose method - result = super()._dispose(mlist, msg, msgdata) - - mailman_log('debug', 'VirginRunner._dispose: Finished processing virgin message %s (file: %s)', - msgid, filebase) - - return result - except Exception as e: - # Log the full traceback for debugging - mailman_log('error', 'VirginRunner: Error processing message %s (file: %s):\n%s\nTraceback:\n%s', - msgid, filebase, str(e), traceback.format_exc()) - # Move the message to the shunt queue - self._shunt.enqueue(msg, msgdata) - return False - finally: - if should_unlock: - mlist.Unlock() + # We need to fasttrack this message through any handlers that touch + # it. E.g. especially CookHeaders. + msgdata['_fasttrack'] = 1 + return IncomingRunner._dispose(self, mlist, msg, msgdata) def _get_pipeline(self, mlist, msg, msgdata): # It's okay to hardcode this, since it'll be the same for all diff --git a/bin/qrunner b/bin/qrunner index b4b3e7d9..714f9878 100644 --- a/bin/qrunner +++ b/bin/qrunner @@ -76,6 +76,8 @@ import sys import argparse import signal import time +import os +import threading import paths from Mailman import mm_cfg @@ -150,7 +152,14 @@ def set_signals(loop): loop.stop() loop.status = signal.SIGTERM syslog('qrunner', '%s qrunner caught SIGTERM. Stopping.', loop.name()) + # Force exit after 5 seconds + def force_exit(): + time.sleep(5) + syslog('qrunner', '%s qrunner forcing exit after timeout.', loop.name()) + os._exit(signal.SIGTERM) + threading.Thread(target=force_exit, daemon=True).start() signal.signal(signal.SIGTERM, sigterm_handler) + # Set up the SIGINT handler for stopping the loop. For us, SIGINT is # the same as SIGTERM, but our parent treats the exit statuses # differently (it restarts a SIGINT but not a SIGTERM). @@ -159,7 +168,14 @@ def set_signals(loop): loop.stop() loop.status = signal.SIGINT syslog('qrunner', '%s qrunner caught SIGINT. Stopping.', loop.name()) + # Force exit after 5 seconds + def force_exit(): + time.sleep(5) + syslog('qrunner', '%s qrunner forcing exit after timeout.', loop.name()) + os._exit(signal.SIGINT) + threading.Thread(target=force_exit, daemon=True).start() signal.signal(signal.SIGINT, sigint_handler) + # SIGHUP just tells us to close our log files. They'll be # automatically reopened at the next log print :) def sighup_handler(signum, frame, loop=loop): From 52c8980f37e885169abe7fd92759c70c0534f275 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 19 May 2025 20:10:02 -0400 Subject: [PATCH 630/748] update --- Mailman/Deliverer.py | 4 ++-- Mailman/Handlers/Acknowledge.py | 2 +- Mailman/Handlers/Replybot.py | 2 +- Mailman/MailList.py | 4 ++-- bin/change_pw | 2 +- bin/newlist | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Mailman/Deliverer.py b/Mailman/Deliverer.py index 5eee7bde..d9e0be15 100644 --- a/Mailman/Deliverer.py +++ b/Mailman/Deliverer.py @@ -126,7 +126,7 @@ def SendSubscribeAck(self, name, password, digest, text=''): realname = self.real_name msg = UserNotification( self.GetMemberAdminEmail(name), self.GetRequestEmail(), - _('Welcome to the "%(realname)s" mailing list%(digmode)s'), + _('Welcome to the "%(realname)s" mailing list%(digmode)s') % {'realname': realname, 'digmode': digmode}, text, pluser) msg['X-No-Archive'] = 'yes' msg.send(self, verp=mm_cfg.VERP_PERSONALIZED_DELIVERIES) @@ -136,7 +136,7 @@ def SendUnsubscribeAck(self, addr, lang): i18n.set_language(lang) msg = UserNotification( self.GetMemberAdminEmail(addr), self.GetBouncesEmail(), - _('You have been unsubscribed from the %(realname)s mailing list'), + _('You have been unsubscribed from the %(realname)s mailing list') % {'realname': realname}, Utils.wrap(self.goodbye_msg), lang) msg.send(self, verp=mm_cfg.VERP_PERSONALIZED_DELIVERIES) diff --git a/Mailman/Handlers/Acknowledge.py b/Mailman/Handlers/Acknowledge.py index 0a1395e0..80183a5a 100644 --- a/Mailman/Handlers/Acknowledge.py +++ b/Mailman/Handlers/Acknowledge.py @@ -55,7 +55,7 @@ def process(mlist, msg, msgdata): # Craft the outgoing message, with all headers and attributes # necessary for general delivery. Then enqueue it to the outgoing # queue. - subject = _('%(realname)s post acknowledgement') + subject = _('%(realname)s post acknowledgement') % {'realname': realname} usermsg = Mailman.Message.UserNotification(sender, mlist.GetBouncesEmail(), subject, text, lang) usermsg.send(mlist) diff --git a/Mailman/Handlers/Replybot.py b/Mailman/Handlers/Replybot.py index c3126002..a6f513e7 100644 --- a/Mailman/Handlers/Replybot.py +++ b/Mailman/Handlers/Replybot.py @@ -54,7 +54,7 @@ def process(mlist, msg, msgdata): # Create the response message outmsg = UserNotification(sender, mlist.GetBouncesEmail(), - _('Automatic response from %(listname)s'), + _('Automatic response from %(listname)s') % {'listname': mlist.real_name}, lang=msgdata.get('lang', mlist.preferred_language)) # Set the message content diff --git a/Mailman/MailList.py b/Mailman/MailList.py index b887ec66..a363f6ce 100644 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -1265,7 +1265,7 @@ def ApprovedAddMember(self, userdesc, ack=None, admin_notif=None, text='', try: whence_str = "" if whence is None else f"({_(whence)})" realname = self.real_name - subject = _('%(realname)s subscription notification') + subject = _('%(realname)s subscription notification') % {'realname': realname} finally: i18n.set_translation(otrans) text = Utils.maketext( @@ -1298,7 +1298,7 @@ def ApprovedDeleteMember(self, name, whence=None, admin_notif=None, userack=None i18n.set_language(self.preferred_language) if admin_notif: realname = self.real_name - subject = _('%(realname)s unsubscribe notification') + subject = _('%(realname)s unsubscribe notification') % {'realname': realname} text = Utils.maketext( 'adminunsubscribeack.txt', {'member': name, diff --git a/bin/change_pw b/bin/change_pw index 673de5cb..22384da7 100644 --- a/bin/change_pw +++ b/bin/change_pw @@ -188,7 +188,7 @@ def main(): adminurl = mlist.GetScriptURL('admin', absolute=1) msg = Mailman.Message.UserNotification( mlist.owner[:], Utils.get_site_email(), - _('Your new %(listname)s list password'), + _('Your new %(listname)s list password') % {'listname': listname}, _('''\ The site administrator at %(hostname)s has changed the password for your mailing list %(listname)s. It is now diff --git a/bin/newlist b/bin/newlist index dcfed02e..257df3eb 100755 --- a/bin/newlist +++ b/bin/newlist @@ -258,7 +258,7 @@ def main(): try: msg = Mailman.Message.UserNotification( owner_mail, siteowner, - _(f'Your new mailing list: {listname}'), + _('Your new mailing list: %(listname)s') % {'listname': listname}, text, mlist.preferred_language) msg.send(mlist) finally: From 486c51cae45408b1afb8788750455e2428f79f55 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 19 May 2025 20:15:58 -0400 Subject: [PATCH 631/748] update --- Mailman/Bouncer.py | 2 +- Mailman/Handlers/Hold.py | 2 +- Mailman/Handlers/ToDigest.py | 6 +++++- Mailman/MailList.py | 4 ++-- cron/checkdbs | 7 ++++++- 5 files changed, 15 insertions(+), 6 deletions(-) diff --git a/Mailman/Bouncer.py b/Mailman/Bouncer.py index 9eb10329..619a3ee7 100644 --- a/Mailman/Bouncer.py +++ b/Mailman/Bouncer.py @@ -327,7 +327,7 @@ def sendNextNotification(self, member): # BAW: See the comment in MailList.py ChangeMemberAddress() for why we # set the Subject this way. del msg['subject'] - msg['Subject'] = 'confirm ' + info.cookie + msg['Subject'] = _('confirm %(cookie)s') % {'cookie': info.cookie} # Send without Precedence: bulk. Bug #808821. msg.send(self, noprecedence=True) info.noticesleft -= 1 diff --git a/Mailman/Handlers/Hold.py b/Mailman/Handlers/Hold.py index 3e88c6e1..764dba96 100644 --- a/Mailman/Handlers/Hold.py +++ b/Mailman/Handlers/Hold.py @@ -289,7 +289,7 @@ def hold_for_approval(mlist, msg, msgdata, exc): d['confirmurl'] = '%s/%s' % (mlist.GetScriptURL('confirm', absolute=1), cookie) lang = msgdata.get('lang', mlist.getMemberLanguage(sender)) - subject = _('Your message to %(listname)s awaits moderator approval') + subject = _('Your message to %(listname)s awaits moderator approval') % {'listname': listname} text = Utils.maketext('postheld.txt', d, lang=lang, mlist=mlist) nmsg = UserNotification(sender, owneraddr, subject, text, lang) nmsg.send(mlist) diff --git a/Mailman/Handlers/ToDigest.py b/Mailman/Handlers/ToDigest.py index 7b5f6800..60dd8792 100644 --- a/Mailman/Handlers/ToDigest.py +++ b/Mailman/Handlers/ToDigest.py @@ -228,7 +228,11 @@ def send_digests(mlist, mboxpath): # Set up the digest state volume = mlist.volume issue = mlist.next_digest_number - digestid = _('%(realname)s Digest, Vol %(volume)d, Issue %(issue)d') + digestid = _('%(realname)s Digest, Vol %(volume)d, Issue %(issue)d') % { + 'realname': mlist.real_name, + 'volume': volume, + 'issue': issue + } # Get the list's preferred language and charset lang = mlist.preferred_language diff --git a/Mailman/MailList.py b/Mailman/MailList.py index a363f6ce..f27590f6 100644 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -248,7 +248,7 @@ def GetConfirmJoinSubject(self, listname, cookie): cset = i18n.get_translation().charset() or \ Utils.GetCharSet(self.preferred_language) subj = Header( - _('Your confirmation is required to join the %(listname)s mailing list'), + _('Your confirmation is required to join the %(listname)s mailing list') % {'listname': listname}, cset, header_name='subject') return subj else: @@ -259,7 +259,7 @@ def GetConfirmLeaveSubject(self, listname, cookie): cset = i18n.get_translation().charset() or \ Utils.GetCharSet(self.preferred_language) subj = Header( - _('Your confirmation is required to leave the %(listname)s mailing list'), + _('Your confirmation is required to leave the %(listname)s mailing list') % {'listname': listname}, cset, header_name='subject') return subj else: diff --git a/cron/checkdbs b/cron/checkdbs index 09bd6c9e..fccd0833 100755 --- a/cron/checkdbs +++ b/cron/checkdbs @@ -191,7 +191,12 @@ def pending_requests(mlist): pending.append(_("""\ From: %(sender)s on %(date)s Subject: %(subject)s -Cause: %(reason)s""")) +Cause: %(reason)s""") % { + 'sender': sender, + 'date': date, + 'subject': subject, + 'reason': reason + }) pending.append('') except Exception as e: mailman_log('error', 'Error processing held message record %d: %s\n%s', From 0c139ba3913851a887b9adaa68e8ba9e00d5f2ee Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 19 May 2025 20:32:24 -0400 Subject: [PATCH 632/748] update --- Mailman/Queue/IncomingRunner.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/Mailman/Queue/IncomingRunner.py b/Mailman/Queue/IncomingRunner.py index 61a7d5f3..1eb95b05 100644 --- a/Mailman/Queue/IncomingRunner.py +++ b/Mailman/Queue/IncomingRunner.py @@ -367,16 +367,23 @@ def _oneloop(self): return filecnt try: + # Check if the file exists before dequeuing + pckfile = os.path.join(self.QDIR, filebase + '.pck') + if not os.path.exists(pckfile): + mailman_log('error', 'IncomingRunner._oneloop: File %s does not exist, skipping', pckfile) + continue + + # Check if file is locked + lockfile = os.path.join(self.QDIR, filebase + '.pck.lock') + if os.path.exists(lockfile): + mailman_log('debug', 'IncomingRunner._oneloop: File %s is locked by another process, skipping', filebase) + continue + # Dequeue the file msg, msgdata = self._switchboard.dequeue(filebase) # If dequeue failed due to file being locked, skip it if msg is None and msgdata is None: - # Check if the file is locked - lockfile = os.path.join(self.QDIR, filebase + '.pck.lock') - if os.path.exists(lockfile): - mailman_log('debug', 'IncomingRunner._oneloop: File %s is locked by another process, skipping', filebase) - continue # For other None,None cases, shunt the message mailman_log('error', 'IncomingRunner._oneloop: Failed to dequeue file %s (got None values), shunting', filebase) # Create a basic message and metadata if we don't have them From 8bec7ac857a26dae9a498c17006c1caa4845c542 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 19 May 2025 20:35:10 -0400 Subject: [PATCH 633/748] update --- Mailman/Handlers/ToOutgoing.py | 4 ++-- Mailman/Queue/CommandRunner.py | 41 +++++++++++++++++++++++++++++++++ Mailman/Queue/NewsRunner.py | 12 ++++++++++ Mailman/Queue/OutgoingRunner.py | 36 +++++++++++++++++++++++++++++ Mailman/Queue/RetryRunner.py | 12 ++++++++++ 5 files changed, 103 insertions(+), 2 deletions(-) diff --git a/Mailman/Handlers/ToOutgoing.py b/Mailman/Handlers/ToOutgoing.py index 8af4c19a..123b8859 100644 --- a/Mailman/Handlers/ToOutgoing.py +++ b/Mailman/Handlers/ToOutgoing.py @@ -31,7 +31,7 @@ def process(mlist, msg, msgdata): msgid = msg.get('message-id', 'n/a') # Log the start of processing with enhanced details - mailman_log('info', 'ToOutgoing: Starting to process message %s for list %s', + mailman_log('debug', 'ToOutgoing: Starting to process message %s for list %s', msgid, mlist.internal_name()) mailman_log('debug', 'ToOutgoing: Message details:') mailman_log('debug', ' Message ID: %s', msgid) @@ -80,7 +80,7 @@ def process(mlist, msg, msgdata): outgoingq.enqueue(msg, msgdata, listname=mlist.internal_name()) - mailman_log('info', 'ToOutgoing: Successfully queued message %s for list %s', + mailman_log('debug', 'ToOutgoing: Successfully queued message %s for list %s', msgid, mlist.internal_name()) mailman_log('debug', 'ToOutgoing: Message %s is now in outgoing queue', msgid) except Exception as e: diff --git a/Mailman/Queue/CommandRunner.py b/Mailman/Queue/CommandRunner.py index 8e5b975f..ebabe5ac 100644 --- a/Mailman/Queue/CommandRunner.py +++ b/Mailman/Queue/CommandRunner.py @@ -47,6 +47,7 @@ from Mailman import LockFile from Mailman import Pending import traceback +import os # Lazy imports to avoid circular dependencies def get_replybot(): @@ -417,6 +418,46 @@ def _dispose(self, mlist, msg, msgdata): finally: mlist_obj.Unlock() + def _oneloop(self): + """Process one batch of messages from the command queue.""" + try: + # Get the list of files to process + files = self._switchboard.files() + filecnt = len(files) + + # Process each file + for filebase in files: + try: + # Check if the file exists before dequeuing + pckfile = os.path.join(self.QDIR, filebase + '.pck') + if not os.path.exists(pckfile): + syslog('error', 'CommandRunner._oneloop: File %s does not exist, skipping', pckfile) + continue + + # Check if file is locked + lockfile = os.path.join(self.QDIR, filebase + '.pck.lock') + if os.path.exists(lockfile): + syslog('debug', 'CommandRunner._oneloop: File %s is locked by another process, skipping', filebase) + continue + + # Dequeue the file + msg, msgdata = self._switchboard.dequeue(filebase) + if msg is None: + continue + + # Validate message + msg, success = self._validate_message(msg, msgdata) + if not success: + syslog('error', 'CommandRunner._oneloop: Message validation failed for %s', filebase) + continue + + # Process message + self._dispose(msg.mlist, msg, msgdata) + except Exception as e: + syslog('error', 'CommandRunner._oneloop: Error processing file %s: %s', filebase, str(e)) + except Exception as e: + syslog('error', 'CommandRunner._oneloop: Error processing command queue: %s', str(e)) + # Set up i18n _ = i18n._ i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) diff --git a/Mailman/Queue/NewsRunner.py b/Mailman/Queue/NewsRunner.py index 5b2c8008..75a345d5 100644 --- a/Mailman/Queue/NewsRunner.py +++ b/Mailman/Queue/NewsRunner.py @@ -191,6 +191,18 @@ def _oneloop(self): # Process each file for filebase in files: try: + # Check if the file exists before dequeuing + pckfile = os.path.join(self.QDIR, filebase + '.pck') + if not os.path.exists(pckfile): + syslog('error', 'NewsRunner._oneloop: File %s does not exist, skipping', pckfile) + continue + + # Check if file is locked + lockfile = os.path.join(self.QDIR, filebase + '.pck.lock') + if os.path.exists(lockfile): + syslog('debug', 'NewsRunner._oneloop: File %s is locked by another process, skipping', filebase) + continue + # Dequeue the file msg, msgdata = self._switchboard.dequeue(filebase) if msg is None: diff --git a/Mailman/Queue/OutgoingRunner.py b/Mailman/Queue/OutgoingRunner.py index ef4711ea..67c86c9c 100644 --- a/Mailman/Queue/OutgoingRunner.py +++ b/Mailman/Queue/OutgoingRunner.py @@ -530,3 +530,39 @@ def _cleanup(self): mailman_log('debug', 'OutgoingRunner: Cleanup complete') _doperiodic = BounceMixin._doperiodic + + def _oneloop(self): + """Process one batch of messages from the outgoing queue.""" + try: + # Get the list of files to process + files = self._switchboard.files() + filecnt = len(files) + + # Process each file + for filebase in files: + try: + # Check if the file exists before dequeuing + pckfile = os.path.join(self.QDIR, filebase + '.pck') + if not os.path.exists(pckfile): + mailman_log('error', 'OutgoingRunner._oneloop: File %s does not exist, skipping', pckfile) + continue + + # Check if file is locked + lockfile = os.path.join(self.QDIR, filebase + '.pck.lock') + if os.path.exists(lockfile): + mailman_log('debug', 'OutgoingRunner._oneloop: File %s is locked by another process, skipping', filebase) + continue + + # Dequeue the file + msg, msgdata = self._switchboard.dequeue(filebase) + if msg is None: + continue + + # Process the message + self._dispose(msg.list, msg, msgdata) + except Exception as e: + mailman_log('error', 'OutgoingRunner._oneloop: Error processing file %s: %s', filebase, str(e)) + mailman_log('error', 'OutgoingRunner._oneloop: Traceback: %s', traceback.format_exc()) + except Exception as e: + mailman_log('error', 'OutgoingRunner._oneloop: Error processing outgoing queue: %s', str(e)) + mailman_log('error', 'OutgoingRunner._oneloop: Traceback: %s', traceback.format_exc()) diff --git a/Mailman/Queue/RetryRunner.py b/Mailman/Queue/RetryRunner.py index a5ccf1d9..d2ab283d 100644 --- a/Mailman/Queue/RetryRunner.py +++ b/Mailman/Queue/RetryRunner.py @@ -326,6 +326,18 @@ def _oneloop(self): # Process each file for filebase in files: try: + # Check if the file exists before dequeuing + pckfile = os.path.join(self.QDIR, filebase + '.pck') + if not os.path.exists(pckfile): + mailman_log('error', 'RetryRunner._oneloop: File %s does not exist, skipping', pckfile) + continue + + # Check if file is locked + lockfile = os.path.join(self.QDIR, filebase + '.pck.lock') + if os.path.exists(lockfile): + mailman_log('debug', 'RetryRunner._oneloop: File %s is locked by another process, skipping', filebase) + continue + # Dequeue the file msg, msgdata = self._switchboard.dequeue(filebase) if msg is None: From e9293cfb6f77f7d5b099ea038a555b12a258821e Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 19 May 2025 20:39:32 -0400 Subject: [PATCH 634/748] update --- Mailman/Queue/IncomingRunner.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/Mailman/Queue/IncomingRunner.py b/Mailman/Queue/IncomingRunner.py index 1eb95b05..f9c133e0 100644 --- a/Mailman/Queue/IncomingRunner.py +++ b/Mailman/Queue/IncomingRunner.py @@ -418,9 +418,29 @@ def _oneloop(self): # If the message should be kept in the queue, requeue it if result: - self._switchboard.enqueue(msg, msgdata) + # Get pipeline information for logging + pipeline = msgdata.get('pipeline', []) + current_handler = pipeline[0] if pipeline else 'unknown' + next_handler = pipeline[1] if len(pipeline) > 1 else 'none' + + # Get retry information + retry_count = msgdata.get('retry_count', 0) + last_retry = self._retry_times.get(msgid, 0) + next_retry = time.ctime(last_retry + self.MIN_RETRY_DELAY) if last_retry else 'unknown' + + # Log detailed requeue information mailman_log('info', 'IncomingRunner._oneloop: Message requeued for later processing: %s (msgid: %s)', filebase, msgid) + mailman_log('debug', ' Current state:') + mailman_log('debug', ' - Current handler: %s', current_handler) + mailman_log('debug', ' - Next handler: %s', next_handler) + mailman_log('debug', ' - Retry count: %d', retry_count) + mailman_log('debug', ' - Last retry: %s', time.ctime(last_retry) if last_retry else 'none') + mailman_log('debug', ' - Next retry: %s', next_retry) + mailman_log('debug', ' - List: %s', mlist.internal_name()) + mailman_log('debug', ' - Message type: %s', msgdata.get('_msgtype', 'unknown')) + + self._switchboard.enqueue(msg, msgdata) else: mailman_log('info', 'IncomingRunner._oneloop: Message processing complete, moving to shunt queue %s (msgid: %s)', filebase, msgid) From c4c03dcb8febeddda111f795499c63ecc9f853c4 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 19 May 2025 20:45:56 -0400 Subject: [PATCH 635/748] update --- Mailman/Queue/CommandRunner.py | 23 ++++++++++++++++++++++- Mailman/Queue/OutgoingRunner.py | 23 ++++++++++++++++++++++- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/Mailman/Queue/CommandRunner.py b/Mailman/Queue/CommandRunner.py index ebabe5ac..c85f52fa 100644 --- a/Mailman/Queue/CommandRunner.py +++ b/Mailman/Queue/CommandRunner.py @@ -445,6 +445,22 @@ def _oneloop(self): if msg is None: continue + # Get the list name from msgdata + listname = msgdata.get('listname') + if not listname: + syslog('error', 'CommandRunner._oneloop: No listname in message data for file %s', filebase) + self._shunt.enqueue(msg, msgdata) + continue + + # Open the list + try: + mlist = MailList.MailList(listname, lock=False) + except Errors.MMUnknownListError: + syslog('error', 'CommandRunner._oneloop: Unknown list %s for message %s (file: %s)', + listname, msg.get('message-id', 'n/a'), filebase) + self._shunt.enqueue(msg, msgdata) + continue + # Validate message msg, success = self._validate_message(msg, msgdata) if not success: @@ -452,7 +468,12 @@ def _oneloop(self): continue # Process message - self._dispose(msg.mlist, msg, msgdata) + try: + self._dispose(mlist, msg, msgdata) + except Exception as e: + syslog('error', 'CommandRunner._oneloop: Error processing message %s: %s', + msg.get('message-id', 'n/a'), str(e)) + self._shunt.enqueue(msg, msgdata) except Exception as e: syslog('error', 'CommandRunner._oneloop: Error processing file %s: %s', filebase, str(e)) except Exception as e: diff --git a/Mailman/Queue/OutgoingRunner.py b/Mailman/Queue/OutgoingRunner.py index 67c86c9c..d5deb62f 100644 --- a/Mailman/Queue/OutgoingRunner.py +++ b/Mailman/Queue/OutgoingRunner.py @@ -558,8 +558,29 @@ def _oneloop(self): if msg is None: continue + # Get the list name from msgdata + listname = msgdata.get('listname') + if not listname: + mailman_log('error', 'OutgoingRunner._oneloop: No listname in message data for file %s', filebase) + self._shunt.enqueue(msg, msgdata) + continue + + # Open the list + try: + mlist = get_mail_list()(listname, lock=False) + except Errors.MMUnknownListError: + mailman_log('error', 'OutgoingRunner._oneloop: Unknown list %s for message %s (file: %s)', + listname, msg.get('message-id', 'n/a'), filebase) + self._shunt.enqueue(msg, msgdata) + continue + # Process the message - self._dispose(msg.list, msg, msgdata) + try: + self._dispose(mlist, msg, msgdata) + except Exception as e: + mailman_log('error', 'OutgoingRunner._oneloop: Error processing message %s: %s\nTraceback:\n%s', + msg.get('message-id', 'n/a'), str(e), traceback.format_exc()) + self._shunt.enqueue(msg, msgdata) except Exception as e: mailman_log('error', 'OutgoingRunner._oneloop: Error processing file %s: %s', filebase, str(e)) mailman_log('error', 'OutgoingRunner._oneloop: Traceback: %s', traceback.format_exc()) From a8023b226028e86c92a8b0592981f0ce3541a1f7 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 19 May 2025 20:48:19 -0400 Subject: [PATCH 636/748] update --- Mailman/Queue/CommandRunner.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Mailman/Queue/CommandRunner.py b/Mailman/Queue/CommandRunner.py index c85f52fa..04b3bda8 100644 --- a/Mailman/Queue/CommandRunner.py +++ b/Mailman/Queue/CommandRunner.py @@ -46,6 +46,7 @@ from Mailman.Queue.Runner import Runner from Mailman import LockFile from Mailman import Pending +from Mailman import MailList import traceback import os From b6b934e84ca62cf2e3cd30709c7e55c607ae0199 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 19 May 2025 20:54:10 -0400 Subject: [PATCH 637/748] update --- Mailman/Queue/CommandRunner.py | 185 +++++++++++++++++++++------------ 1 file changed, 117 insertions(+), 68 deletions(-) diff --git a/Mailman/Queue/CommandRunner.py b/Mailman/Queue/CommandRunner.py index 04b3bda8..f194c44c 100644 --- a/Mailman/Queue/CommandRunner.py +++ b/Mailman/Queue/CommandRunner.py @@ -351,73 +351,122 @@ def _validate_message(self, msg, msgdata): return msg, False def _dispose(self, mlist, msg, msgdata): - # Validate message type first - msg, success = self._validate_message(msg, msgdata) - if not success: - mailman_log('error', 'Message validation failed for command message') - return False - - # Get the list name from the mlist object + """Process a command message. + + Args: + mlist: The MailList instance this message is destined for + msg: The Message object representing the message + msgdata: Dictionary of message metadata + + Returns: + bool: True if message should be requeued, False if processing is complete + """ + msgid = msg.get('message-id', 'n/a') + filebase = msgdata.get('_filebase', 'unknown') + + # Ensure we have a MailList object + if isinstance(mlist, str): + try: + mlist = get_maillist()(mlist, lock=0) + should_unlock = True + except Errors.MMUnknownListError: + syslog('error', 'CommandRunner: Unknown list %s', mlist) + self._shunt.enqueue(msg, msgdata) + return False + else: + should_unlock = False + try: - listname = mlist.internal_name() if hasattr(mlist, 'internal_name') else str(mlist) - MailList = get_maillist() - mlist_obj = MailList(listname, lock=False) - except Errors.MMListError as e: - mailman_log('error', 'Failed to get MailList object for %s: %s', listname, str(e)) - return False - - # The policy here is similar to the Replybot policy. If a message has - # "Precedence: bulk|junk|list" and no "X-Ack: yes" header, we discard - # it to prevent replybot response storms. - precedence = msg.get('precedence', '').lower() - ack = msg.get('x-ack', '').lower() - if ack != 'yes' and precedence in ('bulk', 'junk', 'list'): - syslog('vette', 'Precedence: %s message discarded by: %s', - precedence, mlist_obj.GetRequestEmail()) - return False + syslog('debug', 'CommandRunner._dispose: Starting to process command message %s (file: %s) for list %s', + msgid, filebase, mlist.internal_name()) + + # Check retry delay and duplicate processing + if not self._check_retry_delay(msgid, filebase): + syslog('debug', 'CommandRunner._dispose: Message %s failed retry delay check, skipping', msgid) + return True - # Lock the list before any operations - try: - mlist_obj.Lock(timeout=mm_cfg.LIST_LOCK_TIMEOUT) - except LockFile.TimeOutError: - # Oh well, try again later - return True + # Validate message type first + msg, success = self._validate_message(msg, msgdata) + if not success: + syslog('error', 'CommandRunner._dispose: Message validation failed for message %s', msgid) + msgdata['_validation_failure'] = 'Missing required headers' + self._shunt.enqueue(msg, msgdata) + return False - try: - # Do replybot for commands - mlist_obj.Load() - Replybot = get_replybot() - Replybot.process(mlist_obj, msg, msgdata) - if mlist_obj.autorespond_requests == 1: - syslog('vette', 'replied and discard') - # w/discard + # The policy here is similar to the Replybot policy. If a message has + # "Precedence: bulk|junk|list" and no "X-Ack: yes" header, we discard + # it to prevent replybot response storms. + precedence = msg.get('precedence', '').lower() + ack = msg.get('x-ack', '').lower() + if ack != 'yes' and precedence in ('bulk', 'junk', 'list'): + syslog('vette', 'Precedence: %s message discarded by: %s', + precedence, mlist.GetRequestEmail()) return False - # Now craft the response - res = Results(mlist_obj, msg, msgdata) - # This message will have been delivered to one of mylist-request, - # mylist-join, or mylist-leave, and the message metadata will contain - # a key to which one was used. - ret = BADCMD - if msgdata.get('torequest', False): - ret = res.process() - elif msgdata.get('tojoin', False): - ret = res.do_command('join') - elif msgdata.get('toleave', False): - ret = res.do_command('leave') - elif msgdata.get('toconfirm', False): - mo = re.match(mm_cfg.VERP_CONFIRM_REGEXP, msg.get('to', ''), re.IGNORECASE) - if mo: - ret = res.do_command('confirm', (mo.group('cookie'),)) - if ret == BADCMD and mm_cfg.DISCARD_MESSAGE_WITH_NO_COMMAND: - syslog('vette', - 'No command, message discarded, msgid: %s', - msg.get('message-id', 'n/a')) - else: - res.send_response() - mlist_obj.Save() + # Lock the list before any operations + try: + mlist.Lock(timeout=mm_cfg.LIST_LOCK_TIMEOUT) + except LockFile.TimeOutError: + # Oh well, try again later + return True + + try: + # Check if list is temporarily unavailable + try: + mlist.Load() + except Errors.MMCorruptListDatabaseError as e: + syslog('error', 'CommandRunner._dispose: List %s is temporarily unavailable: %s', + mlist.internal_name(), str(e)) + return True + except Exception as e: + syslog('error', 'CommandRunner._dispose: Error loading list %s: %s', + mlist.internal_name(), str(e)) + return True + + # Do replybot for commands + Replybot = get_replybot() + Replybot.process(mlist, msg, msgdata) + if mlist.autorespond_requests == 1: + syslog('vette', 'replied and discard') + # w/discard + return False + + # Now craft the response + res = Results(mlist, msg, msgdata) + # This message will have been delivered to one of mylist-request, + # mylist-join, or mylist-leave, and the message metadata will contain + # a key to which one was used. + ret = BADCMD + if msgdata.get('torequest', False): + ret = res.process() + elif msgdata.get('tojoin', False): + ret = res.do_command('join') + elif msgdata.get('toleave', False): + ret = res.do_command('leave') + elif msgdata.get('toconfirm', False): + mo = re.match(mm_cfg.VERP_CONFIRM_REGEXP, msg.get('to', ''), re.IGNORECASE) + if mo: + ret = res.do_command('confirm', (mo.group('cookie'),)) + if ret == BADCMD and mm_cfg.DISCARD_MESSAGE_WITH_NO_COMMAND: + syslog('vette', + 'No command, message discarded, msgid: %s', + msg.get('message-id', 'n/a')) + return False + else: + res.send_response() + mlist.Save() + return False + finally: + mlist.Unlock() + + except Exception as e: + syslog('error', 'CommandRunner._dispose: Error processing command message %s: %s\nTraceback:\n%s', + msgid, str(e), traceback.format_exc()) + self._shunt.enqueue(msg, msgdata) + return False finally: - mlist_obj.Unlock() + if should_unlock: + mlist.Unlock() def _oneloop(self): """Process one batch of messages from the command queue.""" @@ -462,19 +511,19 @@ def _oneloop(self): self._shunt.enqueue(msg, msgdata) continue - # Validate message - msg, success = self._validate_message(msg, msgdata) - if not success: - syslog('error', 'CommandRunner._oneloop: Message validation failed for %s', filebase) - continue - # Process message try: - self._dispose(mlist, msg, msgdata) + success = self._dispose(mlist, msg, msgdata) + if not success: + # If _dispose returns False, the message was shunted or discarded + # Remove it from the queue + self._switchboard.finish(filebase) except Exception as e: syslog('error', 'CommandRunner._oneloop: Error processing message %s: %s', msg.get('message-id', 'n/a'), str(e)) self._shunt.enqueue(msg, msgdata) + # Remove the message from the queue after shunting + self._switchboard.finish(filebase) except Exception as e: syslog('error', 'CommandRunner._oneloop: Error processing file %s: %s', filebase, str(e)) except Exception as e: From cbb769277f48f7df339ba1f9c731479f4505a6c9 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 19 May 2025 21:08:18 -0400 Subject: [PATCH 638/748] update --- Mailman/Queue/IncomingRunner.py | 44 +++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/Mailman/Queue/IncomingRunner.py b/Mailman/Queue/IncomingRunner.py index f9c133e0..1d3dcea4 100644 --- a/Mailman/Queue/IncomingRunner.py +++ b/Mailman/Queue/IncomingRunner.py @@ -116,9 +116,12 @@ from Mailman import Errors from Mailman import LockFile from Mailman.Queue.Runner import Runner +from Mailman.Queue.Switchboard import Switchboard from Mailman.Logging.Syslog import mailman_log import Mailman.MailList as MailList import Mailman.Message +import threading +import email.header class PipelineError(Exception): @@ -192,6 +195,14 @@ def _dispose(self, mlist, msg, msgdata): # Convert Python's Message to Mailman's Message if needed msg = self._convert_message(msg) + # Check if this is a bounce message + if self._is_bounce(msg): + mailman_log('debug', 'IncomingRunner._dispose: Message %s is a bounce, routing to bounce queue', msgid) + # Route to bounce queue + bounce_queue = Switchboard(mm_cfg.BOUNCEQUEUE_DIR) + bounce_queue.enqueue(msg, msgdata) + return False + # Get the pipeline pipeline = self._get_pipeline(mlist, msg, msgdata) if not pipeline: @@ -282,19 +293,26 @@ def _is_command(self, msg): return False def _is_bounce(self, msg): - """Check if the message is a bounce.""" - try: - # Check common bounce headers - bounce_headers = ['X-Failed-Recipients', 'X-Original-To', 'Return-Path'] - for header in bounce_headers: - if msg.get(header): - mailman_log('debug', 'IncomingRunner._is_bounce: Message has bounce header %s', header) - return True - return False - except Exception as e: - mailman_log('error', 'IncomingRunner._is_bounce: Error checking bounce: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - return False + """Check if a message is a bounce message.""" + # Check for common bounce headers + if msg.get('x-failed-recipients'): + return True + if msg.get('x-original-to'): + return True + if msg.get('return-path', '').startswith('<>'): + return True + # Check content type for multipart/report + if msg.get('content-type', '').startswith('multipart/report'): + return True + # Check for common bounce subjects + subject = msg.get('subject', '').lower() + bounce_subjects = ['delivery status', 'failure notice', 'mail delivery failed', + 'mail delivery system', 'mail system error', 'returned mail', + 'undeliverable', 'undelivered mail'] + for bounce_subject in bounce_subjects: + if bounce_subject in subject: + return True + return False def _process_command(self, mlist, msg, msgdata): """Process a command message.""" From 225e997c5854c861679c9e13930fc6dcc76cfe79 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 19 May 2025 21:09:58 -0400 Subject: [PATCH 639/748] update --- Mailman/Queue/IncomingRunner.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Mailman/Queue/IncomingRunner.py b/Mailman/Queue/IncomingRunner.py index 1d3dcea4..c8092cd3 100644 --- a/Mailman/Queue/IncomingRunner.py +++ b/Mailman/Queue/IncomingRunner.py @@ -305,7 +305,10 @@ def _is_bounce(self, msg): if msg.get('content-type', '').startswith('multipart/report'): return True # Check for common bounce subjects - subject = msg.get('subject', '').lower() + subject = msg.get('subject', '') + if isinstance(subject, email.header.Header): + subject = str(subject) + subject = subject.lower() bounce_subjects = ['delivery status', 'failure notice', 'mail delivery failed', 'mail delivery system', 'mail system error', 'returned mail', 'undeliverable', 'undelivered mail'] From 56b31aa84fa013c92d8d21382d6519e172e6554e Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 19 May 2025 21:14:20 -0400 Subject: [PATCH 640/748] update --- Mailman/Queue/IncomingRunner.py | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/Mailman/Queue/IncomingRunner.py b/Mailman/Queue/IncomingRunner.py index c8092cd3..72a96501 100644 --- a/Mailman/Queue/IncomingRunner.py +++ b/Mailman/Queue/IncomingRunner.py @@ -414,6 +414,12 @@ def _oneloop(self): msgdata['whichq'] = self.QDIR # Shunt the message self._shunt.enqueue(msg, msgdata) + # Remove the original file + try: + os.unlink(pckfile) + mailman_log('debug', 'IncomingRunner._oneloop: Removed original file %s', pckfile) + except OSError as e: + mailman_log('error', 'IncomingRunner._oneloop: Failed to remove original file %s: %s', pckfile, str(e)) continue # Try to get message-id early for logging purposes @@ -431,6 +437,12 @@ def _oneloop(self): mailman_log('error', 'IncomingRunner._oneloop: Unknown list %s for message %s (file: %s)', listname, msgid, filebase) self._shunt.enqueue(msg, msgdata) + # Remove the original file + try: + os.unlink(pckfile) + mailman_log('debug', 'IncomingRunner._oneloop: Removed original file %s', pckfile) + except OSError as e: + mailman_log('error', 'IncomingRunner._oneloop: Failed to remove original file %s: %s', pckfile, str(e)) continue # Process the message @@ -461,16 +473,34 @@ def _oneloop(self): mailman_log('debug', ' - List: %s', mlist.internal_name()) mailman_log('debug', ' - Message type: %s', msgdata.get('_msgtype', 'unknown')) + # Requeue the message and remove the original file self._switchboard.enqueue(msg, msgdata) + try: + os.unlink(pckfile) + mailman_log('debug', 'IncomingRunner._oneloop: Removed original file %s', pckfile) + except OSError as e: + mailman_log('error', 'IncomingRunner._oneloop: Failed to remove original file %s: %s', pckfile, str(e)) else: mailman_log('info', 'IncomingRunner._oneloop: Message processing complete, moving to shunt queue %s (msgid: %s)', filebase, msgid) + # Move to shunt queue and remove the original file + self._shunt.enqueue(msg, msgdata) + try: + os.unlink(pckfile) + mailman_log('debug', 'IncomingRunner._oneloop: Removed original file %s', pckfile) + except OSError as e: + mailman_log('error', 'IncomingRunner._oneloop: Failed to remove original file %s: %s', pckfile, str(e)) except Exception as e: mailman_log('error', 'IncomingRunner._oneloop: Error processing message %s (file: %s): %s\n%s', msgid, filebase, str(e), traceback.format_exc()) - # Move to shunt queue on error + # Move to shunt queue on error and remove the original file self._shunt.enqueue(msg, msgdata) + try: + os.unlink(pckfile) + mailman_log('debug', 'IncomingRunner._oneloop: Removed original file %s', pckfile) + except OSError as e: + mailman_log('error', 'IncomingRunner._oneloop: Failed to remove original file %s: %s', pckfile, str(e)) except Exception as e: mailman_log('error', 'IncomingRunner._oneloop: Error dequeuing file %s: %s\n%s', From 695017f0374fd089625bb847df6dc5d7c0656fa3 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 19 May 2025 21:22:19 -0400 Subject: [PATCH 641/748] update --- Mailman/Queue/Runner.py | 4 ++++ bin/mailmanctl | 15 +-------------- bin/qrunner | 10 ++++++++++ 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/Mailman/Queue/Runner.py b/Mailman/Queue/Runner.py index ec24b264..98d0e131 100644 --- a/Mailman/Queue/Runner.py +++ b/Mailman/Queue/Runner.py @@ -183,6 +183,10 @@ def _handle_error(self, exc, msg=None, mlist=None, preserve=True): self._error_count += 1 if self._error_count >= 10: # Too many errors in short time syslog('error', '%s: Too many errors, stopping runner', self.__class__.__name__) + # Log stack trace before stopping + s = StringIO() + traceback.print_stack(file=s) + syslog('error', 'Stack trace at stop:\n%s', s.getvalue()) self.stop() else: self._error_count = 1 diff --git a/bin/mailmanctl b/bin/mailmanctl index cc73db8b..477569b6 100755 --- a/bin/mailmanctl +++ b/bin/mailmanctl @@ -474,20 +474,7 @@ def check_global_circuit_breaker(): Returns: bool: True if we should stop all runners, False otherwise """ - global _global_restart_times - now = time.time() - - # Remove old timestamps outside the window - _global_restart_times = [t for t in _global_restart_times if now - t < GLOBAL_RESTART_WINDOW] - - # Add current timestamp - _global_restart_times.append(now) - - # Check if we've exceeded the limit - if len(_global_restart_times) > GLOBAL_MAX_RESTARTS: - syslog('error', 'Global circuit breaker triggered: %d restarts in %d seconds', - len(_global_restart_times), GLOBAL_RESTART_WINDOW) - return True + # Circuit breaker disabled - always return False return False diff --git a/bin/qrunner b/bin/qrunner index 714f9878..0ab62721 100644 --- a/bin/qrunner +++ b/bin/qrunner @@ -78,6 +78,8 @@ import signal import time import os import threading +import traceback +from io import StringIO import paths from Mailman import mm_cfg @@ -152,6 +154,10 @@ def set_signals(loop): loop.stop() loop.status = signal.SIGTERM syslog('qrunner', '%s qrunner caught SIGTERM. Stopping.', loop.name()) + # Log traceback + s = StringIO() + traceback.print_stack(file=s) + syslog('error', 'Traceback on SIGTERM:\n%s', s.getvalue()) # Force exit after 5 seconds def force_exit(): time.sleep(5) @@ -168,6 +174,10 @@ def set_signals(loop): loop.stop() loop.status = signal.SIGINT syslog('qrunner', '%s qrunner caught SIGINT. Stopping.', loop.name()) + # Log traceback + s = StringIO() + traceback.print_stack(file=s) + syslog('error', 'Traceback on SIGINT:\n%s', s.getvalue()) # Force exit after 5 seconds def force_exit(): time.sleep(5) From 6857bd2627b7ca5b064dd2cbe657bb96e2a754f7 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 19 May 2025 21:29:28 -0400 Subject: [PATCH 642/748] update --- Mailman/Queue/IncomingRunner.py | 114 +++++++-------------- Mailman/Queue/OutgoingRunner.py | 175 ++++++++++++++------------------ Mailman/Queue/RetryRunner.py | 97 ++---------------- Mailman/Queue/Runner.py | 124 +++++++++++----------- Mailman/Queue/VirginRunner.py | 11 ++ 5 files changed, 194 insertions(+), 327 deletions(-) diff --git a/Mailman/Queue/IncomingRunner.py b/Mailman/Queue/IncomingRunner.py index 72a96501..fc819820 100644 --- a/Mailman/Queue/IncomingRunner.py +++ b/Mailman/Queue/IncomingRunner.py @@ -157,87 +157,39 @@ def _convert_message(self, msg): return Runner._convert_message(self, msg) def _dispose(self, mlist, msg, msgdata): - """Process an incoming message.""" - msgid = msg.get('message-id', 'n/a') - filebase = msgdata.get('_filebase', 'unknown') - + # Try to get the list lock. + try: + mlist.Lock(timeout=mm_cfg.LIST_LOCK_TIMEOUT) + except LockFile.TimeOutError: + # Oh well, try again later + return 1 + # Process the message through a handler pipeline. The handler + # pipeline can actually come from one of three places: the message + # metadata, the mlist, or the global pipeline. + # + # If a message was requeued due to an uncaught exception, its metadata + # will contain the retry pipeline. Use this above all else. + # Otherwise, if the mlist has a `pipeline' attribute, it should be + # used. Final fallback is the global pipeline. try: - # Enhanced logging for message details - mailman_log('debug', 'IncomingRunner._dispose: Starting to process message %s (file: %s)', msgid, filebase) - mailman_log('debug', 'Message details:') - mailman_log('debug', ' Message ID: %s', msgid) - mailman_log('debug', ' From: %s', msg.get('from', 'unknown')) - mailman_log('debug', ' To: %s', msg.get('to', 'unknown')) - mailman_log('debug', ' Subject: %s', msg.get('subject', '(no subject)')) - mailman_log('debug', ' Message type: %s', type(msg).__name__) - mailman_log('debug', ' Message data: %s', str(msgdata)) - mailman_log('debug', ' Pipeline: %s', msgdata.get('pipeline', 'No pipeline')) - - # Check if this is an administrative message - is_admin = msgdata.get('admin_type', False) - mailman_log('debug', ' Is admin message: %s', is_admin) - - # Check if this is a list post - is_list_post = msgdata.get('list_post', False) - mailman_log('debug', ' Is list post: %s', is_list_post) - - # Log recipients information - recipients = msgdata.get('recips', []) - mailman_log('debug', ' Recipients: %s', recipients) - if not recipients: - mailman_log('warning', 'IncomingRunner: No recipients found in msgdata for message %s, pipeline handlers may set them', msgid) - mailman_log('debug', ' Message data: %s', str(msgdata)) - mailman_log('debug', ' To header: %s', msg.get('to', 'unknown')) - mailman_log('debug', ' Cc header: %s', msg.get('cc', 'unknown')) - mailman_log('debug', ' Resent-To: %s', msg.get('resent-to', 'unknown')) - mailman_log('debug', ' Resent-Cc: %s', msg.get('resent-cc', 'unknown')) - - # Convert Python's Message to Mailman's Message if needed - msg = self._convert_message(msg) - - # Check if this is a bounce message - if self._is_bounce(msg): - mailman_log('debug', 'IncomingRunner._dispose: Message %s is a bounce, routing to bounce queue', msgid) - # Route to bounce queue - bounce_queue = Switchboard(mm_cfg.BOUNCEQUEUE_DIR) - bounce_queue.enqueue(msg, msgdata) - return False - - # Get the pipeline pipeline = self._get_pipeline(mlist, msg, msgdata) - if not pipeline: - mailman_log('error', 'IncomingRunner._dispose: No pipeline found for message %s', msgid) - return False - - # Process the message through the pipeline - try: - more = self._dopipeline(mlist, msg, msgdata, pipeline) - if more: - mailman_log('debug', 'IncomingRunner._dispose: Message %s needs more processing', msgid) - return True - except Exception as e: - mailman_log('error', 'IncomingRunner._dispose: Error processing message %s: %s\nTraceback:\n%s', - msgid, str(e), traceback.format_exc()) - return False - - mailman_log('debug', 'IncomingRunner._dispose: Successfully processed message %s', msgid) - return True - - except Exception as e: - mailman_log('error', 'IncomingRunner._dispose: Error processing message %s: %s\nTraceback:\n%s', - msgid, str(e), traceback.format_exc()) - return False + msgdata['pipeline'] = pipeline + more = self._dopipeline(mlist, msg, msgdata, pipeline) + if not more: + del msgdata['pipeline'] + mlist.Save() + return more + finally: + mlist.Unlock() def _get_pipeline(self, mlist, msg, msgdata): - """Get the pipeline for processing the message.""" # We must return a copy of the list, otherwise, the first message that # flows through the pipeline will empty it out! return msgdata.get('pipeline', - getattr(mlist, 'pipeline', - mm_cfg.GLOBAL_PIPELINE))[:] + getattr(mlist, 'pipeline', + mm_cfg.GLOBAL_PIPELINE))[:] def _dopipeline(self, mlist, msg, msgdata, pipeline): - """Process the message through the pipeline of handlers.""" while pipeline: handler = pipeline.pop(0) modname = 'Mailman.Handlers.' + handler @@ -245,12 +197,15 @@ def _dopipeline(self, mlist, msg, msgdata, pipeline): try: pid = os.getpid() sys.modules[modname].process(mlist, msg, msgdata) - # Failsafe -- a child may have leaked through + # Failsafe -- a child may have leaked through. if pid != os.getpid(): mailman_log('error', 'Child process leaked through: %s', modname) os._exit(1) except Errors.DiscardMessage: - # Throw the message away + # Throw the message away; we need do nothing else with it. + # We do need to push the current handler back in the pipeline + # just in case the syslog call throws an exception and the + # message is shunted. pipeline.insert(0, handler) mailman_log('vette', """Message discarded, msgid: %s list: %s, @@ -259,10 +214,14 @@ def _dopipeline(self, mlist, msg, msgdata, pipeline): mlist.real_name, handler) return 0 except Errors.HoldMessage: - # Let the approval process take it from here + # Let the approval process take it from here. The message no + # longer needs to be queued. return 0 except Errors.RejectMessage as e: - # Log rejection and bounce message + # Log this. + # We do need to push the current handler back in the pipeline + # just in case the syslog call or BounceMessage throws an + # exception and the message is shunted. pipeline.insert(0, handler) mailman_log('vette', """Message rejected, msgid: %s list: %s, @@ -272,8 +231,9 @@ def _dopipeline(self, mlist, msg, msgdata, pipeline): mlist.real_name, handler, e.notice()) mlist.BounceMessage(msg, msgdata, e) return 0 - except Exception as e: + except: # Push this pipeline module back on the stack, then re-raise + # the exception. pipeline.insert(0, handler) raise # We've successfully completed handling of this message diff --git a/Mailman/Queue/OutgoingRunner.py b/Mailman/Queue/OutgoingRunner.py index d5deb62f..943b6822 100644 --- a/Mailman/Queue/OutgoingRunner.py +++ b/Mailman/Queue/OutgoingRunner.py @@ -81,22 +81,22 @@ def __init__(self, slice=None, numslices=1): self._last_cleanup = time.time() # We look this function up only at startup time - self._modname = 'Mailman.Handlers.' + mm_cfg.DELIVERY_MODULE - mailman_log('debug', 'OutgoingRunner: Attempting to import delivery module: %s', self._modname) + modname = 'Mailman.Handlers.' + mm_cfg.DELIVERY_MODULE + mailman_log('debug', 'OutgoingRunner: Attempting to import delivery module: %s', modname) try: - mod = __import__(self._modname) + mod = __import__(modname) mailman_log('debug', 'OutgoingRunner: Successfully imported delivery module') except ImportError as e: - mailman_log('error', 'OutgoingRunner: Failed to import delivery module %s: %s', self._modname, str(e)) + mailman_log('error', 'OutgoingRunner: Failed to import delivery module %s: %s', modname, str(e)) mailman_log('error', 'OutgoingRunner: Traceback: %s', traceback.format_exc()) raise try: - self._func = getattr(sys.modules[self._modname], 'process') + self._func = getattr(sys.modules[modname], 'process') mailman_log('debug', 'OutgoingRunner: Successfully got process function from module') except AttributeError as e: - mailman_log('error', 'OutgoingRunner: Failed to get process function from module %s: %s', self._modname, str(e)) + mailman_log('error', 'OutgoingRunner: Failed to get process function from module %s: %s', modname, str(e)) mailman_log('error', 'OutgoingRunner: Traceback: %s', traceback.format_exc()) raise @@ -242,100 +242,77 @@ def _validate_message(self, msg, msgdata): return msg, False def _dispose(self, mlist, msg, msgdata): - """Process an outgoing message.""" - msgid = msg.get('message-id', 'n/a') - filebase = msgdata.get('_filebase', 'unknown') - - # Log the full msgdata at the start of processing - mailman_log('debug', 'OutgoingRunner._dispose: Full msgdata at start:\n%s', str(msgdata)) - - # Ensure we have a MailList object - if isinstance(mlist, str): - try: - mlist = get_mail_list()(mlist, lock=0) - should_unlock = True - except Errors.MMUnknownListError: - mailman_log('error', 'OutgoingRunner: Unknown list %s', mlist) - self._shunt.enqueue(msg, msgdata) - return True - else: - should_unlock = False - + # See if we should retry delivery of this message again. + deliver_after = msgdata.get('deliver_after', 0) + if time.time() < deliver_after: + return True + # Make sure we have the most up-to-date state + mlist.Load() try: - mailman_log('debug', 'OutgoingRunner._dispose: Starting to process outgoing message %s (file: %s) for list %s', - msgid, filebase, mlist.internal_name()) - - # Check retry delay and duplicate processing - if not self._check_retry_delay(msgid, filebase): - mailman_log('debug', 'OutgoingRunner._dispose: Message %s failed retry delay check, skipping', msgid) - return False - - # Make sure we have the most up-to-date state - try: - mlist.Load() - mailman_log('debug', 'OutgoingRunner._dispose: Successfully loaded list %s', mlist.internal_name()) - except Errors.MMCorruptListDatabaseError as e: - mailman_log('error', 'OutgoingRunner._dispose: Failed to load list %s: %s\nTraceback:\n%s', - mlist.internal_name(), str(e), traceback.format_exc()) - self._unmark_message_processed(msgid) - return False - except Exception as e: - mailman_log('error', 'OutgoingRunner._dispose: Unexpected error loading list %s: %s\nTraceback:\n%s', - mlist.internal_name(), str(e), traceback.format_exc()) - self._unmark_message_processed(msgid) - return False - - # Validate message type first - msg, success = self._validate_message(msg, msgdata) - if not success: - mailman_log('error', 'OutgoingRunner._dispose: Message validation failed for message %s', msgid) - self._unmark_message_processed(msgid) - return False - - # Log the full msgdata after validation - mailman_log('debug', 'OutgoingRunner._dispose: Full msgdata after validation:\n%s', str(msgdata)) - - # Validate message headers - if not msg.get('message-id'): - mailman_log('error', 'OutgoingRunner._dispose: Message missing Message-ID header') - self._unmark_message_processed(msgid) - return False - - # Process the outgoing message - try: - mailman_log('debug', 'OutgoingRunner._dispose: Processing outgoing message %s', msgid) - - # Get message type and recipient - msgtype = msgdata.get('_msgtype', 'unknown') - recipient = msgdata.get('recipient', 'unknown') - - mailman_log('debug', 'OutgoingRunner._dispose: Message %s is type %s for recipient %s', - msgid, msgtype, recipient) - - # Process based on message type - if msgtype == 'bounce': - success = self._process_bounce(mlist, msg, msgdata) - elif msgtype == 'admin': - success = self._process_admin(mlist, msg, msgdata) - else: - success = self._process_regular(mlist, msg, msgdata) - - if success: - mailman_log('debug', 'OutgoingRunner._dispose: Successfully processed outgoing message %s', msgid) - return True - else: - mailman_log('error', 'OutgoingRunner._dispose: Failed to process outgoing message %s', msgid) - return False - - except Exception as e: - mailman_log('error', 'OutgoingRunner._dispose: Error processing outgoing message %s: %s\nTraceback:\n%s', - msgid, str(e), traceback.format_exc()) - self._unmark_message_processed(msgid) - return False - - finally: - if should_unlock: - mlist.Unlock() + pid = os.getpid() + self._func(mlist, msg, msgdata) + # Failsafe -- a child may have leaked through. + if pid != os.getpid(): + mailman_log('error', 'child process leaked thru: %s', mm_cfg.DELIVERY_MODULE) + os._exit(1) + self.__logged = False + except socket.error: + # There was a problem connecting to the SMTP server. Log this + # once, but crank up our sleep time so we don't fill the error + # log. + port = mm_cfg.SMTPPORT + if port == 0: + port = 'smtp' + # Log this just once. + if not self.__logged: + mailman_log('error', 'Cannot connect to SMTP server %s on port %s', + mm_cfg.SMTPHOST, port) + self.__logged = True + self._snooze(0) + return True + except Errors.SomeRecipientsFailed as e: + # Handle local rejects of probe messages differently. + if msgdata.get('probe_token') and e.permfailures: + self._probe_bounce(mlist, msgdata['probe_token']) + else: + # Delivery failed at SMTP time for some or all of the + # recipients. Permanent failures are registered as bounces, + # but temporary failures are retried for later. + # + # BAW: msg is going to be the original message that failed + # delivery, not a bounce message. This may be confusing if + # this is what's sent to the user in the probe message. Maybe + # we should craft a bounce-like message containing information + # about the permanent SMTP failure? + if e.permfailures: + self._queue_bounces(mlist.internal_name(), e.permfailures, + msg) + # Move temporary failures to the qfiles/retry queue which will + # occasionally move them back here for another shot at + # delivery. + if e.tempfailures: + now = time.time() + recips = e.tempfailures + last_recip_count = msgdata.get('last_recip_count', 0) + deliver_until = msgdata.get('deliver_until', now) + if len(recips) == last_recip_count: + # We didn't make any progress, so don't attempt + # delivery any longer. BAW: is this the best + # disposition? + if now > deliver_until: + return False + else: + # Keep trying to delivery this message for a while + deliver_until = now + mm_cfg.DELIVERY_RETRY_PERIOD + # Don't retry delivery too soon. + deliver_after = now + mm_cfg.DELIVERY_RETRY_WAIT + msgdata['deliver_after'] = deliver_after + msgdata['last_recip_count'] = len(recips) + msgdata['deliver_until'] = deliver_until + msgdata['recips'] = recips + self.__retryq.enqueue(msg, msgdata) + # We've successfully completed handling of this message + return False def _process_bounce(self, mlist, msg, msgdata): """Process a bounce message.""" diff --git a/Mailman/Queue/RetryRunner.py b/Mailman/Queue/RetryRunner.py index d2ab283d..fa20c1b6 100644 --- a/Mailman/Queue/RetryRunner.py +++ b/Mailman/Queue/RetryRunner.py @@ -60,7 +60,7 @@ def __init__(self, slice=None, numslices=1): mailman_log('debug', 'RetryRunner: Starting initialization') try: Runner.__init__(self, slice, numslices) - self.__outq = Switchboard(mm_cfg.OUTQUEUE_DIR) + self._outq = Switchboard(mm_cfg.OUTQUEUE_DIR) # Initialize processed messages tracking self._processed_messages = set() @@ -137,93 +137,14 @@ def _cleanup_old_messages(self): self._last_cleanup = time.time() def _dispose(self, mlist, msg, msgdata): - """Process a retry message.""" - msgid = msg.get('message-id', 'n/a') - filebase = msgdata.get('_filebase', 'unknown') - - # Ensure we have a MailList object - if isinstance(mlist, str): - try: - # Lazy import to avoid circular dependencies - from Mailman.MailList import MailList - mlist = MailList(mlist, lock=0) - should_unlock = True - except MMUnknownListError: - syslog('error', 'RetryRunner: Unknown list %s', mlist) - self._shunt.enqueue(msg, msgdata) - return True - else: - should_unlock = False - - try: - mailman_log('debug', 'RetryRunner._dispose: Starting to process retry message %s (file: %s) for list %s', - msgid, filebase, mlist.internal_name()) - - # Check retry delay and duplicate processing - if not self._check_retry_delay(msgid, filebase): - mailman_log('debug', 'RetryRunner._dispose: Message %s failed retry delay check, skipping', msgid) - return False - - # Make sure we have the most up-to-date state - try: - mlist.Load() - mailman_log('debug', 'RetryRunner._dispose: Successfully loaded list %s', mlist.internal_name()) - except Errors.MMCorruptListDatabaseError as e: - mailman_log('error', 'RetryRunner._dispose: Failed to load list %s: %s\nTraceback:\n%s', - mlist.internal_name(), str(e), traceback.format_exc()) - self._unmark_message_processed(msgid) - return False - except Exception as e: - mailman_log('error', 'RetryRunner._dispose: Unexpected error loading list %s: %s\nTraceback:\n%s', - mlist.internal_name(), str(e), traceback.format_exc()) - self._unmark_message_processed(msgid) - return False - - # Validate message type first - msg, success = self._validate_message(msg, msgdata) - if not success: - mailman_log('error', 'RetryRunner._dispose: Message validation failed for message %s', msgid) - self._unmark_message_processed(msgid) - return False - - # Validate message headers - if not msg.get('message-id'): - mailman_log('error', 'RetryRunner._dispose: Message missing Message-ID header') - self._unmark_message_processed(msgid) - return False - - # Process the retry message - try: - mailman_log('debug', 'RetryRunner._dispose: Processing retry message %s', msgid) - - # Check retry count - retry_count = msgdata.get('retry_count', 0) - max_retries = msgdata.get('max_retries', mm_cfg.MAX_RETRIES) - - if retry_count >= max_retries: - mailman_log('error', 'RetryRunner._dispose: Message %s exceeded maximum retry count (%d/%d)', - msgid, retry_count, max_retries) - self._handle_max_retries_exceeded(mlist, msg, msgdata) - return False - - # Process the retry - success = self._process_retry(mlist, msg, msgdata) - if success: - mailman_log('debug', 'RetryRunner._dispose: Successfully processed retry message %s', msgid) - return True - else: - mailman_log('error', 'RetryRunner._dispose: Failed to process retry message %s', msgid) - return False - - except Exception as e: - mailman_log('error', 'RetryRunner._dispose: Error processing retry message %s: %s\nTraceback:\n%s', - msgid, str(e), traceback.format_exc()) - self._unmark_message_processed(msgid) - return False - - finally: - if should_unlock: - mlist.Unlock() + # See if we should retry delivery of this message again. + deliver_after = msgdata.get('deliver_after', 0) + if time.time() < deliver_after: + return True + # Move the message to the outgoing queue for another attempt at + # delivery. + self._outq.enqueue(msg, msgdata) + return False def _process_retry(self, mlist, msg, msgdata): """Process a retry message.""" diff --git a/Mailman/Queue/Runner.py b/Mailman/Queue/Runner.py index 98d0e131..66673b92 100644 --- a/Mailman/Queue/Runner.py +++ b/Mailman/Queue/Runner.py @@ -204,71 +204,69 @@ def _handle_error(self, exc, msg=None, mlist=None, preserve=True): return True def _oneloop(self): - """Process one batch of messages from the queue.""" - try: - # Get the list of files to process - files = self._switchboard.files() - total_files = len(files) - processed_files = 0 - - # Process each file - for filebase in files: + # First, list all the files in our queue directory. + # Switchboard.files() is guaranteed to hand us the files in FIFO + # order. Return an integer count of the number of files that were + # available for this qrunner to process. + files = self._switchboard.files() + for filebase in files: + try: + # Ask the switchboard for the message and metadata objects + # associated with this filebase. + msg, msgdata = self._switchboard.dequeue(filebase) + except Exception as e: + # This used to just catch email.Errors.MessageParseError, + # but other problems can occur in message parsing, e.g. + # ValueError, and exceptions can occur in unpickling too. + # We don't want the runner to die, so we just log and skip + # this entry, but maybe preserve it for analysis. + self._log(e) + if mm_cfg.QRUNNER_SAVE_BAD_MESSAGES: + syslog('error', + 'Skipping and preserving unparseable message: %s', + filebase) + preserve = True + else: + syslog('error', + 'Ignoring unparseable message: %s', filebase) + preserve = False + self._switchboard.finish(filebase, preserve=preserve) + continue + try: + self._onefile(msg, msgdata) + self._switchboard.finish(filebase) + except Exception as e: + # All runners that implement _dispose() must guarantee that + # exceptions are caught and dealt with properly. Still, there + # may be a bug in the infrastructure, and we do not want those + # to cause messages to be lost. Any uncaught exceptions will + # cause the message to be stored in the shunt queue for human + # intervention. + self._log(e) + # Put a marker in the metadata for unshunting + msgdata['whichq'] = self._switchboard.whichq() + # It is possible that shunting can throw an exception, e.g. a + # permissions problem or a MemoryError due to a really large + # message. Try to be graceful. try: - # Dequeue the file - msg, msgdata = self._switchboard.dequeue(filebase) - if msg is None or msgdata is None: - syslog('error', 'Runner._oneloop: Failed to dequeue file %s (got None values)', filebase) - continue - - # Get the list name - listname = msgdata.get('listname', 'unknown') - try: - mlist = MailList.MailList(listname, lock=False) - except Errors.MMUnknownListError: - syslog('error', 'Runner._oneloop: Unknown list %s for message %s', - listname, msg.get('message-id', 'n/a')) - self._shunt.enqueue(msg, msgdata) - continue - - # Process the message - try: - result = self._onefile(mlist, msg, msgdata) - processed_files += 1 - if result: - # Message was successfully processed, finish and remove the file - self._switchboard.finish(filebase) - # Only log significant events - if total_files > 10: # Log only when processing large batches - syslog('debug', 'Runner._oneloop: Successfully processed message %s, removed file %s', - msg.get('message-id', 'n/a'), filebase) - elif result is False: - # Message needs to be requeued - self._switchboard.enqueue(msg, msgdata) - # Only log significant events - if total_files > 10: # Log only when processing large batches - syslog('debug', 'Runner._oneloop: Requeued message %s', msg.get('message-id', 'n/a')) - else: - # Message was shunted - self._shunt.enqueue(msg, msgdata) - # Only log significant events - if total_files > 10: # Log only when processing large batches - syslog('debug', 'Runner._oneloop: Shunted message %s', msg.get('message-id', 'n/a')) - return True - except Exception as e: - syslog('error', 'Runner._oneloop: Error processing message %s: %s\nTraceback:\n%s', - msg.get('message-id', 'n/a'), str(e), traceback.format_exc()) - self._shunt.enqueue(msg, msgdata) - return False + new_filebase = self._shunt.enqueue(msg, msgdata) + syslog('error', 'SHUNTING: %s', new_filebase) + self._switchboard.finish(filebase) except Exception as e: - syslog('error', 'Runner._oneloop: Error processing file %s: %s\nTraceback:\n%s', - filebase, str(e), traceback.format_exc()) - continue - - except Exception as e: - syslog('error', 'Runner._oneloop: Error in main loop: %s', str(e)) - return 0 - - return processed_files + # The message wasn't successfully shunted. Log the + # exception and try to preserve the original queue entry + # for possible analysis. + self._log(e) + syslog('error', + 'SHUNTING FAILED, preserving original entry: %s', + filebase) + self._switchboard.finish(filebase, preserve=True) + # Other work we want to do each time through the loop + Utils.reap(self._kids, once=True) + self._doperiodic() + if self._shortcircuit(): + break + return len(files) def _convert_message(self, msg): """Convert email.message.Message to Mailman.Message with proper handling of nested messages. diff --git a/Mailman/Queue/VirginRunner.py b/Mailman/Queue/VirginRunner.py index ed117549..8c055f7f 100644 --- a/Mailman/Queue/VirginRunner.py +++ b/Mailman/Queue/VirginRunner.py @@ -48,6 +48,17 @@ class VirginRunner(IncomingRunner): _processed_times = {} # Track processing times for messages def __init__(self, slice=None, numslices=1): + IncomingRunner.__init__(self, slice, numslices) + # VirginRunner is a subclass of IncomingRunner, but we want to use a + # different pipeline for processing virgin messages. The main + # difference is that we don't need to do bounce detection, and we can + # skip a few other checks. + self._pipeline = self._get_pipeline() + # VirginRunner is a subclass of IncomingRunner, but we want to use a + # different pipeline for processing virgin messages. The main + # difference is that we don't need to do bounce detection, and we can + # skip a few other checks. + self._fasttrack = 1 mailman_log('debug', 'VirginRunner: Starting initialization') try: Runner.__init__(self, slice, numslices) From db63723ffca2a86fb8e111b48563f92b83d22e14 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Mon, 19 May 2025 21:35:14 -0400 Subject: [PATCH 643/748] update --- Mailman/Queue/Switchboard.py | 53 ++++++++++-------------------------- Makefile.in | 15 ++++++++++ 2 files changed, 30 insertions(+), 38 deletions(-) diff --git a/Mailman/Queue/Switchboard.py b/Mailman/Queue/Switchboard.py index 315d7ab7..2b91a672 100644 --- a/Mailman/Queue/Switchboard.py +++ b/Mailman/Queue/Switchboard.py @@ -213,46 +213,23 @@ def dequeue(self, filebase): mailman_log('warning', 'Queue file does not exist: %s (not found in backup or shunt either)', filename) return None, None - # Create a lock file - lockfile = filename + '.lock' - try: - # Try to create the lock file - fd = os.open(lockfile, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o644) - os.close(fd) - except OSError as e: - if e.errno != errno.EEXIST: - mailman_log('error', 'Switchboard.dequeue: Failed to create lock file for %s: %s', filebase, str(e)) - raise - mailman_log('debug', 'Queue file %s is currently locked by another process', filename) - return None, None - + # Read the message object and metadata. + fp = open(filename, 'rb') + # Move the file to the backup file name for processing. If this + # process crashes uncleanly the .bak file will be used to re-instate + # the .pck file in order to try again. + os.rename(filename, bakfile) try: - # Read the message and metadata - try: - msg, data = self._dequeue(filename) - # Add filebase to msgdata for cleanup - if data is not None: - data['filebase'] = filebase - # Log the full msgdata after dequeuing - mailman_log('debug', 'Switchboard.dequeue: Successfully dequeued file %s', filebase) - except Exception as e: - mailman_log('error', 'Switchboard.dequeue: Failed to read message from %s: %s\nTraceback:\n%s', - filebase, str(e), traceback.format_exc()) - raise - - # Validate data structure before returning - if not isinstance(data, dict): - mailman_log('error', 'Switchboard.dequeue: Invalid data structure in %s: expected dict, got %s', - filename, type(data)) - return None, None - - return msg, data + msg = pickle.load(fp, fix_imports=True, encoding='latin1') + data = pickle.load(fp, fix_imports=True, encoding='latin1') finally: - # Always clean up the lock file - try: - os.unlink(lockfile) - except OSError: - pass + fp.close() + if data.get('_parsemsg'): + msg = email.message_from_string(msg, Message) + # Add filebase to msgdata for cleanup + if data is not None: + data['filebase'] = filebase + return msg, data def finish(self, filebase, preserve=False): """Finish processing a file by either removing it or moving it to the shunt queue. diff --git a/Makefile.in b/Makefile.in index 16c8dc02..318e5512 100644 --- a/Makefile.in +++ b/Makefile.in @@ -74,6 +74,8 @@ PYTHON_FILES = $(shell find . -name "*.py") PYTHON_DIRS = $(shell find . -type d -name "Mailman") INSTALLED_SCRIPTS = $(shell find $(DESTDIR)$(prefix)/bin -type f -executable 2>/dev/null || true) SOURCE_SCRIPTS = $(shell find build/bin -type f -executable -name "*.py" 2>/dev/null || true) +PYLINT = pylint +PYLINT_FLAGS = --disable=C0111,C0103,C0303,W0311,W0603,W0621,R0903,R0913,R0914,R0915 # Detect number of CPUs for parallel builds ifeq ($(shell uname -s),Darwin) @@ -130,6 +132,19 @@ define check_lang_file exit 1; endef +# Add lint target +.PHONY: lint +lint: + @echo "Running pylint on installed Python files..." + @if [ -d "$(DESTDIR)$(prefix)" ]; then \ + find $(DESTDIR)$(prefix) -name "*.py" -type f -print0 | \ + xargs -0 $(PYLINT) $(PYLINT_FLAGS) || true; \ + else \ + echo "No installed files found at $(DESTDIR)$(prefix)"; \ + echo "Please run 'make install' first"; \ + exit 1; \ + fi + # Rules .PHONY: all build install clean distclean prepare-build clean-pyc doinstall update langpack From 175f9e3cc7958fdc2ac01dad81680e542532f93a Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Wed, 21 May 2025 20:17:19 -0400 Subject: [PATCH 644/748] update for python3 --- Mailman/Cgi/admin.py | 150 +++++++++++++------------- Mailman/Defaults.py.in | 2 +- Mailman/Gui/Privacy.py | 2 +- Mailman/Gui/Topics.py | 2 +- bin/find_member | 2 +- messages/ar/LC_MESSAGES/mailman.po | 14 +-- messages/ast/LC_MESSAGES/mailman.po | 12 +-- messages/ca/LC_MESSAGES/mailman.po | 12 +-- messages/cs/LC_MESSAGES/mailman.po | 10 +- messages/da/LC_MESSAGES/mailman.po | 14 +-- messages/de/LC_MESSAGES/mailman.po | 12 +-- messages/el/LC_MESSAGES/mailman.po | 12 +-- messages/eo/LC_MESSAGES/mailman.po | 6 +- messages/es/LC_MESSAGES/mailman.po | 12 +-- messages/et/LC_MESSAGES/mailman.po | 12 +-- messages/eu/LC_MESSAGES/mailman.po | 10 +- messages/fa/LC_MESSAGES/mailman.po | 6 +- messages/fi/LC_MESSAGES/mailman.po | 12 +-- messages/fr/LC_MESSAGES/mailman.po | 12 +-- messages/gl/LC_MESSAGES/mailman.po | 12 +-- messages/he/LC_MESSAGES/mailman.po | 10 +- messages/hr/LC_MESSAGES/mailman.po | 10 +- messages/hu/LC_MESSAGES/mailman.po | 12 +-- messages/ia/LC_MESSAGES/mailman.po | 10 +- messages/it/LC_MESSAGES/mailman.po | 12 +-- messages/ja/LC_MESSAGES/mailman.po | 12 +-- messages/ko/LC_MESSAGES/mailman.po | 14 +-- messages/lt/LC_MESSAGES/mailman.po | 6 +- messages/mailman.pot | 6 +- messages/nl/LC_MESSAGES/mailman.po | 10 +- messages/no/LC_MESSAGES/mailman.po | 14 +-- messages/pl/LC_MESSAGES/mailman.po | 10 +- messages/pt/LC_MESSAGES/mailman.po | 12 +-- messages/pt_BR/LC_MESSAGES/mailman.po | 12 +-- messages/ro/LC_MESSAGES/mailman.po | 10 +- messages/ru/LC_MESSAGES/mailman.po | 12 +-- messages/sk/LC_MESSAGES/mailman.po | 10 +- messages/sl/LC_MESSAGES/mailman.po | 16 +-- messages/sr/LC_MESSAGES/mailman.po | 6 +- messages/sv/LC_MESSAGES/mailman.po | 14 +-- messages/tr/LC_MESSAGES/mailman.po | 10 +- messages/uk/LC_MESSAGES/mailman.po | 12 +-- messages/vi/LC_MESSAGES/mailman.po | 12 +-- messages/zh_CN/LC_MESSAGES/mailman.po | 12 +-- messages/zh_TW/LC_MESSAGES/mailman.po | 8 +- misc/sitelist.cfg | 2 +- templates/ar/headfoot.html | 2 +- templates/ast/headfoot.html | 2 +- templates/ca/headfoot.html | 2 +- templates/cs/headfoot.html | 2 +- templates/da/headfoot.html | 2 +- templates/de/headfoot.html | 2 +- templates/el/headfoot.html | 2 +- templates/en/headfoot.html | 2 +- templates/es/headfoot.html | 2 +- templates/et/headfoot.html | 2 +- templates/eu/headfoot.html | 2 +- templates/fi/headfoot.html | 2 +- templates/fr/headfoot.html | 2 +- templates/gl/headfoot.html | 2 +- templates/he/headfoot.html | 2 +- templates/hr/headfoot.html | 2 +- templates/hu/headfoot.html | 2 +- templates/ia/headfoot.html | 2 +- templates/it/headfoot.html | 2 +- templates/ja/headfoot.html | 38 +++---- templates/ko/headfoot.html | 2 +- templates/lt/headfoot.html | 2 +- templates/nl/headfoot.html | 2 +- templates/no/headfoot.html | 2 +- templates/pt/headfoot.html | 2 +- templates/pt_BR/headfoot.html | 2 +- templates/ro/headfoot.html | 2 +- templates/ru/headfoot.html | 2 +- templates/sk/headfoot.html | 2 +- templates/sl/headfoot.html | 2 +- templates/sr/headfoot.html | 2 +- templates/sv/headfoot.html | 2 +- templates/tr/headfoot.html | 2 +- templates/uk/headfoot.html | 2 +- templates/vi/headfoot.html | 2 +- templates/zh_CN/headfoot.html | 2 +- templates/zh_TW/headfoot.html | 2 +- 83 files changed, 355 insertions(+), 355 deletions(-) diff --git a/Mailman/Cgi/admin.py b/Mailman/Cgi/admin.py index a199e43d..eb909249 100644 --- a/Mailman/Cgi/admin.py +++ b/Mailman/Cgi/admin.py @@ -450,7 +450,7 @@ def option_help(mlist, varhelp): item = None reflist = varhelp.split('/') if len(reflist) >= 2: - category = subcat = None + category, subcat = None, None if len(reflist) == 2: category, varname = reflist elif len(reflist) == 3: @@ -514,18 +514,59 @@ def option_help(mlist, varhelp): doc.AddItem(mlist.GetMailmanFooter()) print(doc.Format()) +def add_standard_headers(doc, mlist, title, category=None, subcat=None): + """Add standard headers to admin pages. + + Args: + doc: The Document object + mlist: The MailList object + title: The page title + category: Optional category name + subcat: Optional subcategory name + """ + # Set the page title + doc.SetTitle(title) + + # Add the main header + doc.AddItem(Header(2, title)) + + # Add navigation breadcrumbs if category/subcat provided + if category: + adminurl = mlist.GetScriptURL('admin') + categories = mlist.GetConfigCategories() + category_label = _(categories[category][0]) + if isinstance(category_label, bytes): + category_label = category_label.decode(Utils.GetCharSet(mlist.preferred_language), 'replace') + + breadcrumbs = [] + breadcrumbs.append(Link(adminurl, _('Admin'))) + breadcrumbs.append(Link(f'{adminurl}/{category}', category_label)) + + if subcat: + subcat_label = _(categories[category][1].get(subcat, [subcat])[0]) + if isinstance(subcat_label, bytes): + subcat_label = subcat_label.decode(Utils.GetCharSet(mlist.preferred_language), 'replace') + breadcrumbs.append(Link(f'{adminurl}/{category}/{subcat}', subcat_label)) + + doc.AddItem(Center(' > '.join(breadcrumbs))) + + # Add horizontal rule + doc.AddItem('
                  ') + def show_results(mlist, doc, category, subcat, cgidata): # Produce the results page adminurl = mlist.GetScriptURL('admin') categories = mlist.GetConfigCategories() label = _(categories[category][0]) if isinstance(label, bytes): - label = label.decode('latin1', 'replace') + label = label.decode(Utils.GetCharSet(mlist.preferred_language), 'replace') - doc.SetTitle(_('%(realname)s Administration (%(label)s)') % { + # Add standard headers + title = _('%(realname)s Administration (%(label)s)') % { 'realname': mlist.real_name, 'label': label - }) + } + add_standard_headers(doc, mlist, title, category, subcat) # Use ParseTags for the main content replacements = { @@ -539,6 +580,11 @@ def show_results(mlist, doc, category, subcat, cgidata): 'rmlisturl': mlist.GetScriptURL('rmlist') if mm_cfg.OWNERS_CAN_DELETE_THEIR_OWN_LISTS and mlist.internal_name() != mm_cfg.MAILMAN_SITE_LIST else None } + # Ensure all replacements are properly encoded for the list's language + for key, value in replacements.items(): + if isinstance(value, bytes): + replacements[key] = value.decode(Utils.GetCharSet(mlist.preferred_language), 'replace') + output = mlist.ParseTags('admin_results.html', replacements, mlist.preferred_language) doc.AddItem(output) @@ -586,7 +632,7 @@ def show_variables(mlist, category, subcat, cgidata, doc): mailman_log('debug', 'Got config categories: %s', str(categories)) label = _(categories[category][0]) if isinstance(label, bytes): - label = label.decode('latin1', 'replace') + label = label.decode(Utils.GetCharSet(mlist.preferred_language), 'replace') mailman_log('debug', 'Category label: %s', label) table.AddRow([Center(Header(2, label))]) @@ -597,7 +643,7 @@ def show_variables(mlist, category, subcat, cgidata, doc): # description if it is a string description = options[0] if isinstance(description, bytes): - description = description.decode('latin1', 'replace') + description = description.decode(Utils.GetCharSet(mlist.preferred_language), 'replace') mailman_log('debug', 'Description: %s', description) if type(description) is str: table.AddRow([description]) @@ -623,7 +669,7 @@ def show_variables(mlist, category, subcat, cgidata, doc): # treated as a general description, while any others are # treated as section headers - centered and italicized... if isinstance(item, bytes): - item = item.decode('latin1', 'replace') + item = item.decode(Utils.GetCharSet(mlist.preferred_language), 'replace') formatted_text = '[%s]' % item item = Bold(formatted_text).Format() table.AddRow([Center(Italic(item))]) @@ -707,16 +753,7 @@ def get_item_gui_value(mlist, category, kind, varname, params, extra): kind == mm_cfg.Host or kind == mm_cfg.Number): # Ensure value is a string, decoding bytes if necessary if isinstance(value, bytes): - try: - # Try UTF-8 first - value = value.decode('utf-8', 'replace') - except UnicodeDecodeError: - try: - # Try EUC-JP for Japanese text - value = value.decode('euc-jp', 'replace') - except UnicodeDecodeError: - # Fall back to latin1 - value = value.decode('latin1', 'replace') + value = value.decode(Utils.GetCharSet(mlist.preferred_language), 'replace') return TextBox(varname, value, params) elif kind == mm_cfg.Text: if params: @@ -725,16 +762,7 @@ def get_item_gui_value(mlist, category, kind, varname, params, extra): r, c = None, None # Ensure value is a string, decoding bytes if necessary if isinstance(value, bytes): - try: - # Try UTF-8 first - value = value.decode('utf-8', 'replace') - except UnicodeDecodeError: - try: - # Try EUC-JP for Japanese text - value = value.decode('euc-jp', 'replace') - except UnicodeDecodeError: - # Fall back to latin1 - value = value.decode('latin1', 'replace') + value = value.decode(Utils.GetCharSet(mlist.preferred_language), 'replace') return TextArea(varname, value or '', r, c) elif kind in (mm_cfg.EmailList, mm_cfg.EmailListEx): if params: @@ -743,16 +771,7 @@ def get_item_gui_value(mlist, category, kind, varname, params, extra): r, c = None, None # Ensure value is a string, decoding bytes if necessary if isinstance(value, bytes): - try: - # Try UTF-8 first - value = value.decode('utf-8', 'replace') - except UnicodeDecodeError: - try: - # Try EUC-JP for Japanese text - value = value.decode('euc-jp', 'replace') - except UnicodeDecodeError: - # Fall back to latin1 - value = value.decode('latin1', 'replace') + value = value.decode(Utils.GetCharSet(mlist.preferred_language), 'replace') res = NL.join(value) return TextArea(varname, res, r, c, wrap='off') elif kind == mm_cfg.FileUpload: @@ -819,11 +838,11 @@ def makebox(i, name, pattern, desc, empty=False, table=table): data = getattr(mlist, varname) for name, pattern, desc, empty in data: if isinstance(name, bytes): - name = name.decode('latin1', 'replace') + name = name.decode(Utils.GetCharSet(mlist.preferred_language), 'replace') if isinstance(pattern, bytes): - pattern = pattern.decode('latin1', 'replace') + pattern = pattern.decode(Utils.GetCharSet(mlist.preferred_language), 'replace') if isinstance(desc, bytes): - desc = desc.decode('latin1', 'replace') + desc = desc.decode(Utils.GetCharSet(mlist.preferred_language), 'replace') makebox(i, name, pattern, desc, empty) i += 1 # Add one more non-deleteable widget as the first blank entry, but @@ -889,7 +908,7 @@ def makebox(i, pattern, action, empty=False, table=table): data = getattr(mlist, varname) for pattern, action, empty in data: if isinstance(pattern, bytes): - pattern = pattern.decode('latin1', 'replace') + pattern = pattern.decode(Utils.GetCharSet(mlist.preferred_language), 'replace') makebox(i, pattern, action, empty) i += 1 # Add one more non-deleteable widget as the first blank entry, but @@ -942,43 +961,24 @@ def membership_options(mlist, subcat, cgidata, doc, form): adminurl = mlist.GetScriptURL('admin', absolute=1) container = Container() header = Table(width="100%") - # If we're in the list subcategory, show the membership list + + # Add standard headers based on subcat if subcat == 'add': - header.AddRow([Center(Header(2, _('Mass Subscriptions')))]) - header.AddCellInfo(header.GetCurrentRowIndex(), 0, colspan=2, - bgcolor=mm_cfg.WEB_HEADER_COLOR) - container.AddItem(header) - mass_subscribe(mlist, container) - return container - if subcat == 'remove': - header.AddRow([Center(Header(2, _('Mass Removals')))]) - header.AddCellInfo(header.GetCurrentRowIndex(), 0, colspan=2, - bgcolor=mm_cfg.WEB_HEADER_COLOR) - container.AddItem(header) - mass_remove(mlist, container) - return container - if subcat == 'change': - header.AddRow([Center(Header(2, _('Address Change')))]) - header.AddCellInfo(header.GetCurrentRowIndex(), 0, colspan=2, - bgcolor=mm_cfg.WEB_HEADER_COLOR) - container.AddItem(header) - address_change(mlist, container) - return container - if subcat == 'sync': - header.AddRow([Center(Header(2, _('Sync Membership List')))]) - header.AddCellInfo(header.GetCurrentRowIndex(), 0, colspan=2, - bgcolor=mm_cfg.WEB_HEADER_COLOR) - container.AddItem(header) - mass_sync(mlist, container) - return container - # Otherwise... - header.AddRow([Center(Header(2, _('Membership List')))]) - header.AddCellInfo(header.GetCurrentRowIndex(), 0, colspan=2, - bgcolor=mm_cfg.WEB_HEADER_COLOR) - container.AddItem(header) + title = _('Mass Subscriptions') + elif subcat == 'remove': + title = _('Mass Removals') + elif subcat == 'change': + title = _('Address Change') + elif subcat == 'sync': + title = _('Sync Membership List') + else: + title = _('Membership List') + + add_standard_headers(doc, mlist, title, 'members', subcat) + # Add a "search for member" button table = Table(width='100%') - link = Link('https://docs.python.org/2/library/re.html' + link = Link('https://docs.python.org/3/library/re.html' '#regular-expression-syntax', _('(help)')).Format() table.AddRow([Label(_('Find member %(link)s:') % {'link': link}), diff --git a/Mailman/Defaults.py.in b/Mailman/Defaults.py.in index 65ea25bb..91c6656d 100755 --- a/Mailman/Defaults.py.in +++ b/Mailman/Defaults.py.in @@ -261,7 +261,7 @@ KNOWN_SPAMMERS = [] # normalized unicodes against normalized unicode headers. This setting # determines the normalization form. It is one of 'NFC', 'NFD', 'NFKC' or # 'NFKD'. See -# https://docs.python.org/2/library/unicodedata.html#unicodedata.normalize +# https://docs.python.org/3/library/unicodedata.html#unicodedata.normalize NORMALIZE_FORM = 'NFKC' diff --git a/Mailman/Gui/Privacy.py b/Mailman/Gui/Privacy.py index f13ed651..d3d48300 100644 --- a/Mailman/Gui/Privacy.py +++ b/Mailman/Gui/Privacy.py @@ -196,7 +196,7 @@ def GetConfigInfo(self, mlist, category, subcat=None):

                  In the text boxes below, add one address per line; start the line with a ^ character to designate a Python regular expression. When entering backslashes, do so as if you were using Python raw strings (i.e. you generally just use a single backslash). diff --git a/Mailman/Gui/Topics.py b/Mailman/Gui/Topics.py index f5234be8..20f38a48 100644 --- a/Mailman/Gui/Topics.py +++ b/Mailman/Gui/Topics.py @@ -44,7 +44,7 @@ def GetConfigInfo(self, mlist, category, subcat=None): _("""The topic filter categorizes each incoming email message according to regular + href="https://docs.python.org/3/library/re.html">regular expression filters you specify below. If the message's Subject: or Keywords: header contains a match against a topic filter, the message is logically placed diff --git a/bin/find_member b/bin/find_member index d97e6ebb..bcb73ccb 100755 --- a/bin/find_member +++ b/bin/find_member @@ -49,7 +49,7 @@ specifically excluded. Regular expression syntax is Perl5-like, using the Python re module. Complete specifications are at: -https://docs.python.org/2/library/re.html +https://docs.python.org/3/library/re.html Address matches are case-insensitive, but case-preserved addresses are displayed. diff --git a/messages/ar/LC_MESSAGES/mailman.po b/messages/ar/LC_MESSAGES/mailman.po index 9467c188..b5ef2b5a 100755 --- a/messages/ar/LC_MESSAGES/mailman.po +++ b/messages/ar/LC_MESSAGES/mailman.po @@ -6586,7 +6586,7 @@ msgid "" "\n" "

                  In the text boxes below, add one address per line; start the\n" " line with a ^ character to designate a Python regular expression. When entering backslashes, do " "so\n" " as if you were using Python raw strings (i.e. you generally " @@ -6617,7 +6617,7 @@ msgstr "" "\n" "

                  ÙÙŠ مربعات النص تحت، أض٠عناناً واحداً ÙÙŠ السطر، ابدأ السطر " "بمحر٠^ لصنع صيغة بايثون نظامية. عند إدخال محر٠التقسيم الخلÙÙŠ أدخله " "وكأنك تدخل أحر٠بايثون خام (مثلاً أنت عادة تستخدم محر٠تقسيم خلÙÙŠ واحد).\n" "

                  لاحظ أن المقابلات مع الإدخالات غير الصيغ النظامية دائماً تحصل " @@ -7358,7 +7358,7 @@ msgstr "هل يجب أن يكون مصÙÙŠ الموضوع ممكناً أم مع msgid "" "The topic filter categorizes each incoming email message\n" " according to regular\n" +" href=\"https://docs.python.org/3/library/re.html\">regular\n" " expression filters you specify below. If the message's\n" " Subject: or Keywords: header contains " "a\n" @@ -7381,7 +7381,7 @@ msgid "" " configuration variable." msgstr "" "يصن٠مصÙÙŠ الموضوع كل رسالة بريد قائمة حسب مصÙيات صيغ " +" href=\"https://docs.python.org/3/library/re.html\">مصÙيات صيغ " "نظامية تحددها أنت ÙÙŠ الأسÙÙ„. إذا كانت ترويسة \n" " Subject : أو Keywords: الخاصة " "بالرسالة تحتوي على تطابق مع مصÙÙŠ موضوع ÙØ³ÙˆÙ توضع الرسالة ÙÙŠ جيب " @@ -9652,7 +9652,7 @@ msgid "" "Complete\n" "specifications are at:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "Address matches are case-insensitive, but case-preserved addresses are\n" "displayed.\n" @@ -12408,7 +12408,7 @@ msgstr "" #~ msgid "" #~ "This text can include \n" -#~ "Python\n" #~ "format strings which are resolved against list attributes. The\n" #~ "list of substitutions allowed are:\n" @@ -12437,7 +12437,7 @@ msgstr "" #~ "

                • cgiext - The extension added to CGI scripts.\n" #~ "" #~ msgstr "" -#~ "يمكن لهذا النص أن يتضمنمحار٠تنسيق بايثون \n" #~ "والتي سيتم حلها مع Ù…ÙˆØ§ØµÙØ§Øª القائمة. قائمة الاستبدالات المسموحة هي:\n" #~ "\n" diff --git a/messages/ast/LC_MESSAGES/mailman.po b/messages/ast/LC_MESSAGES/mailman.po index 73e2b5cc..a72ccf22 100755 --- a/messages/ast/LC_MESSAGES/mailman.po +++ b/messages/ast/LC_MESSAGES/mailman.po @@ -7291,7 +7291,7 @@ msgid "" "\n" "

                  In the text boxes below, add one address per line; start the\n" " line with a ^ character to designate a Python regular expression. When entering backslashes, do " "so\n" " as if you were using Python raw strings (i.e. you generally " @@ -7332,7 +7332,7 @@ msgstr "" "

                  Na caxa de testu de más abaxo, amiesta una direición en cada " "llinia, entama la llinia\n" " col caráuter ^ pa designar una \n" -" \n" +" \n" " espresión regular de Python. Cuando introduzas barres " "atenllaes de manzorga a mandrecha,\n" " fáilo como si tuvieres usando cadenes de Python (davezu namái " @@ -8185,7 +8185,7 @@ msgstr " msgid "" "The topic filter categorizes each incoming email message\n" " according to regular\n" +" href=\"https://docs.python.org/3/library/re.html\">regular\n" " expression filters you specify below. If the message's\n" " Subject: or Keywords: header contains " "a\n" @@ -8208,7 +8208,7 @@ msgid "" " configuration variable." msgstr "" "La peñera según el tema, clasifica cada mensax recibíu\n" -" según: les \n" " peñeres d'espresiones regulares qu'especifiques embaxo. Si " "les cabeceres\n" @@ -11040,7 +11040,7 @@ msgid "" "Complete\n" "specifications are at:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "Address matches are case-insensitive, but case-preserved addresses are\n" "displayed.\n" @@ -11080,7 +11080,7 @@ msgstr "" "La Usu de la espresión regular ye al estilu Perl5, usando'l modulu Python " "re. Les especificaciones completes tan en:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "Al guetar les direiciones nun se distinguen mayúscules de minúscules, ensin " "embargo, \n" diff --git a/messages/ca/LC_MESSAGES/mailman.po b/messages/ca/LC_MESSAGES/mailman.po index b1b88d6f..9e7306ca 100755 --- a/messages/ca/LC_MESSAGES/mailman.po +++ b/messages/ca/LC_MESSAGES/mailman.po @@ -7383,7 +7383,7 @@ msgid "" "\n" "

                  In the text boxes below, add one address per line; start the\n" " line with a ^ character to designate a Python regular expression. When entering backslashes, do " "so\n" " as if you were using Python raw strings (i.e. you generally " @@ -7425,7 +7425,7 @@ msgstr "" "

                  A les caixes de text d'abaix, afegeix una adreça per línia; " "comença la\n" " línia amb ^ per a designar una expressió regular de Python. Quan escriguis barres " "inverses, fes-ho\n" " com si estiguessis al Python (ex. normalment només es fa servir\n" @@ -8282,7 +8282,7 @@ msgstr "Voleu que el filtre de tema estigui habilitat o inhabilitat?" msgid "" "The topic filter categorizes each incoming email message\n" " according to regular\n" +" href=\"https://docs.python.org/3/library/re.html\">regular\n" " expression filters you specify below. If the message's\n" " Subject: or Keywords: header contains " "a\n" @@ -8306,7 +8306,7 @@ msgid "" msgstr "" "El filtre segons el tema, classifica cada missatge rebut segons:\n" "els filtres\n" +" href=\"https://docs.python.org/3/library/re.html\">els filtres\n" " d'expressions que especifiqui abaix.\n" " Si les capçaleres Subject: o Keywords\n" " coincideixen amb un dels filtres per temes, el missatge es " @@ -11135,7 +11135,7 @@ msgid "" "Complete\n" "specifications are at:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "Address matches are case-insensitive, but case-preserved addresses are\n" "displayed.\n" @@ -11177,7 +11177,7 @@ msgstr "" "modul Python. Les \n" "especificacions són a:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "Les coincidencies de les adreces son case-insensitive, però les adreces " "case-preserved són\n" diff --git a/messages/cs/LC_MESSAGES/mailman.po b/messages/cs/LC_MESSAGES/mailman.po index 9989e9ff..ebcb9794 100755 --- a/messages/cs/LC_MESSAGES/mailman.po +++ b/messages/cs/LC_MESSAGES/mailman.po @@ -6950,7 +6950,7 @@ msgid "" "\n" "

                  In the text boxes below, add one address per line; start the\n" " line with a ^ character to designate a Python regular expression. When entering backslashes, do " "so\n" " as if you were using Python raw strings (i.e. you generally " @@ -6989,7 +6989,7 @@ msgstr "" "

                  Do ní¾e zobrazených rámeèkù zapisujte na ka¾dý øádek jednu " "adresu.\n" " Pokud chcete pou¾ít Regulární výraz Pythonu zaènìte øádek znakem ^. Pokud " "potøebujete zadat \n" " zpìtné lomítko zadejte jej jako jednoduché (tedy není potøeba " @@ -7765,7 +7765,7 @@ msgstr "M msgid "" "The topic filter categorizes each incoming email message\n" " according to regular\n" +" href=\"https://docs.python.org/3/library/re.html\">regular\n" " expression filters you specify below. If the message's\n" " Subject: or Keywords: header contains " "a\n" @@ -7788,7 +7788,7 @@ msgid "" " configuration variable." msgstr "" "Tématický filtr zatøídí ka¾dou pøijatou zprávu s pou¾itím regulárních\n" +" href=\"https://docs.python.org/3/library/re.html\">regulárních\n" " výrazù , které zadáte ní¾e. Pokud zpráva obsahuje v " "hlavièce \n" " Subject: a nebo Keywords: výraz " @@ -10116,7 +10116,7 @@ msgid "" "Complete\n" "specifications are at:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "Address matches are case-insensitive, but case-preserved addresses are\n" "displayed.\n" diff --git a/messages/da/LC_MESSAGES/mailman.po b/messages/da/LC_MESSAGES/mailman.po index 9ed66f7f..7e5ba8e7 100755 --- a/messages/da/LC_MESSAGES/mailman.po +++ b/messages/da/LC_MESSAGES/mailman.po @@ -7249,7 +7249,7 @@ msgid "" "\n" "

                  In the text boxes below, add one address per line; start the\n" " line with a ^ character to designate a Python regular expression. When entering backslashes, do " "so\n" " as if you were using Python raw strings (i.e. you generally " @@ -7284,7 +7284,7 @@ msgstr "" "\n" "

                  I tekstboksene nedenfor kan du tilføje en e-mailadresse pr. " "linie.\n" -"Du kan også tilføje Python regexp-udtryk.\n" "Begynd i så fald linien med tegnet ^ for at markere at det er et " "sådant udtryk.\n" @@ -8106,7 +8106,7 @@ msgstr "" msgid "" "The topic filter categorizes each incoming email message\n" " according to regular\n" +" href=\"https://docs.python.org/3/library/re.html\">regular\n" " expression filters you specify below. If the message's\n" " Subject: or Keywords: header contains " "a\n" @@ -8129,7 +8129,7 @@ msgid "" " configuration variable." msgstr "" "Emnefilteret kategoriserer hver e-mail som kommer til listen,\n" -"efter de regexp-" +"efter de regexp-" "udtryk\n" "du skriver nedenfor. Hvis felterne Subject: eller " "Keywords:\n" @@ -10898,7 +10898,7 @@ msgid "" "Complete\n" "specifications are at:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "Address matches are case-insensitive, but case-preserved addresses are\n" "displayed.\n" @@ -10939,7 +10939,7 @@ msgstr "" "benyttes.\n" "Komplet specifikation findes på:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "Adresser sammenlignes uden forskel på små og store bogstaver, men vises med\n" "store/små bogstaver som de er stavet.\n" @@ -14576,7 +14576,7 @@ msgstr "" #~ msgid "" #~ "

                • Find members by\n" -#~ " Python regular expression (regexp)
                  " #~ msgstr "" diff --git a/messages/de/LC_MESSAGES/mailman.po b/messages/de/LC_MESSAGES/mailman.po index 05c63231..1fed72d9 100755 --- a/messages/de/LC_MESSAGES/mailman.po +++ b/messages/de/LC_MESSAGES/mailman.po @@ -7838,7 +7838,7 @@ msgid "" "\n" "

                  In the text boxes below, add one address per line; start the\n" " line with a ^ character to designate a Python regular expression. When entering backslashes, do " "so\n" " as if you were using Python raw strings (i.e. you generally " @@ -7872,7 +7872,7 @@ msgstr "" "\n" "

                  Fügen Sie eine E-Mail-Adresse pro Zeile hinzu.\n" "Beginnen Sie mit einem ^, um\n" -" reguläre\n" +" reguläre\n" " Ausdrücke(RegExp) einzuleiten. Geben Sie einen Backslash so an, wie in\n" " normale Python-Strings (i.d.R. ein einfacher, normaler Backslash).\n" "\n" @@ -8810,7 +8810,7 @@ msgstr "Themenfilter einschalten oder ausschalten?" msgid "" "The topic filter categorizes each incoming email message\n" " according to regular\n" +" href=\"https://docs.python.org/3/library/re.html\">regular\n" " expression filters you specify below. If the message's\n" " Subject: or Keywords: header contains " "a\n" @@ -8833,7 +8833,7 @@ msgid "" " configuration variable." msgstr "" "Der Themenfilter kategorisiert jede eingehende E-Mail-Nachricht gemäss Filterregeln mit " +"href=\"https://docs.python.org/3/library/re.html\">Filterregeln mit " "regulären Ausdrücken, die Sie weiter unten festlegen können. Wenn die " "Subject: oder Keywords: Header der Nachricht mit " "dem Suchthema übereinstimmen, wird die Nachricht in einem thematischen " @@ -11793,7 +11793,7 @@ msgid "" "Complete\n" "specifications are at:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "Address matches are case-insensitive, but case-preserved addresses are\n" "displayed.\n" @@ -11832,7 +11832,7 @@ msgstr "" "Die vom Python re-Modul benutzte Syntax für reguläre Ausdrücke entspricht\n" "der von Perl5. Die kompletten Spezifikationen sind erhältlich unter:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "Die Adresssuche ignoriert Gross-/Kleinschreibung, zeigt jedoch unter-\n" "schiedliche Schreibweisen der Adressen an.\n" diff --git a/messages/el/LC_MESSAGES/mailman.po b/messages/el/LC_MESSAGES/mailman.po index 8a713446..278c9164 100755 --- a/messages/el/LC_MESSAGES/mailman.po +++ b/messages/el/LC_MESSAGES/mailman.po @@ -7581,7 +7581,7 @@ msgid "" "\n" "

                  In the text boxes below, add one address per line; start the\n" " line with a ^ character to designate a Python regular expression. When entering backslashes, do " "so\n" " as if you were using Python raw strings (i.e. you generally " @@ -7625,7 +7625,7 @@ msgstr "" "

                  Óôá áêüëïõèá ðëáßóéá êåéìÝíïõ, ðñïóèÝóôå ìéá äéåýèõíóç óå " "êÜèå ãñáììÞ. Áñ÷ßóôå \n" " ôç ãñáììÞ ìå ôïí ÷áñáêôÞñá ^ ãéá íá äçëþóåôå ìéá Python regular " +"href=\"https://docs.python.org/3/library/re.html\">Python regular " "expression. ¼ôáí åéóÜãåôå áíÜðïäåò êáèÝôïõò (backslashes), \n" "êÜíôå ôï óáí \n" " íá ÷ñçóéìïðïéïýóáôå áðëÝò óõìâïëïóåéñÝò Python (ð.÷. ìðïñåßôå " @@ -8514,7 +8514,7 @@ msgstr "" msgid "" "The topic filter categorizes each incoming email message\n" " according to regular\n" +" href=\"https://docs.python.org/3/library/re.html\">regular\n" " expression filters you specify below. If the message's\n" " Subject: or Keywords: header contains " "a\n" @@ -8537,7 +8537,7 @@ msgid "" " configuration variable." msgstr "" "Ôï ößëôñï èÝìáôïò êáôçãïñéïðïéåß êÜèå åéóåñ÷üìåíï ìÞíõìá\n" -" óýìöùíá ìå ôï ößëôñá\n" " êáíïíéêþí åêöñÜóåùí ðïõ ïñßæïíôáé ðéï êÜôù. Áí ïé " "åðéêåöáëßäåò\n" @@ -11428,7 +11428,7 @@ msgid "" "Complete\n" "specifications are at:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "Address matches are case-insensitive, but case-preserved addresses are\n" "displayed.\n" @@ -11472,7 +11472,7 @@ msgstr "" "Ïé ðëÞñåéò\n" "ðñïäéáãñáöÝò âñßóêïíôáé óôï:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "Ïé áíôéóôïé÷ßåò äéåõèýíóåùí åßíáé (case-insensitive), áëëÜ ïé (case-" "preserved) äéåõèýíóåéò\n" diff --git a/messages/eo/LC_MESSAGES/mailman.po b/messages/eo/LC_MESSAGES/mailman.po index 118817da..180f8b36 100644 --- a/messages/eo/LC_MESSAGES/mailman.po +++ b/messages/eo/LC_MESSAGES/mailman.po @@ -6106,7 +6106,7 @@ msgid "" "\n" "

                  In the text boxes below, add one address per line; start the\n" " line with a ^ character to designate a Python regular expression. When entering backslashes, do " "so\n" " as if you were using Python raw strings (i.e. you generally " @@ -6706,7 +6706,7 @@ msgstr "" msgid "" "The topic filter categorizes each incoming email message\n" " according to regular\n" +" href=\"https://docs.python.org/3/library/re.html\">regular\n" " expression filters you specify below. If the message's\n" " Subject: or Keywords: header contains " "a\n" @@ -8881,7 +8881,7 @@ msgid "" "Complete\n" "specifications are at:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "Address matches are case-insensitive, but case-preserved addresses are\n" "displayed.\n" diff --git a/messages/es/LC_MESSAGES/mailman.po b/messages/es/LC_MESSAGES/mailman.po index 59abfde6..a626438b 100644 --- a/messages/es/LC_MESSAGES/mailman.po +++ b/messages/es/LC_MESSAGES/mailman.po @@ -7493,7 +7493,7 @@ msgid "" "\n" "

                  In the text boxes below, add one address per line; start the\n" " line with a ^ character to designate a Python regular expression. When entering backslashes, do " "so\n" " as if you were using Python raw strings (i.e. you generally " @@ -7534,7 +7534,7 @@ msgstr "" "

                  En la caja de texto de más abajo, agrega una dirección en " "cada línea, empieza la línea\n" " con el carácter ^ para designar una \n" -" \n" +" \n" " expresión regular de Python. Cuando metas barras inclinadas " "de izquierda a derecha (\"\\\"),\n" " hazlo como si estuvieras usando cadenas de Python en bruto " @@ -8372,7 +8372,7 @@ msgstr " msgid "" "The topic filter categorizes each incoming email message\n" " according to regular\n" +" href=\"https://docs.python.org/3/library/re.html\">regular\n" " expression filters you specify below. If the message's\n" " Subject: or Keywords: header contains " "a\n" @@ -8395,7 +8395,7 @@ msgid "" " configuration variable." msgstr "" "El filtro según el tema, clasifica cada mensaje recibido\n" -" según: los \n" " filtros de expresiones regulares que especifique abajo. Si " "las cabeceras\n" @@ -11262,7 +11262,7 @@ msgid "" "Complete\n" "specifications are at:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "Address matches are case-insensitive, but case-preserved addresses are\n" "displayed.\n" @@ -11304,7 +11304,7 @@ msgstr "" "La sintaxis de la expresión regular es al estilo Perl5, usando el modulo " "Python re. Las especificaciones completas están en:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "Al buscar las direcciones no se distinguen mayúsculas de minúsculas, sin " "embargo, \n" diff --git a/messages/et/LC_MESSAGES/mailman.po b/messages/et/LC_MESSAGES/mailman.po index f948b053..a8ac9b69 100755 --- a/messages/et/LC_MESSAGES/mailman.po +++ b/messages/et/LC_MESSAGES/mailman.po @@ -6800,7 +6800,7 @@ msgid "" "\n" "

                  In the text boxes below, add one address per line; start the\n" " line with a ^ character to designate a Python regular expression. When entering backslashes, do " "so\n" " as if you were using Python raw strings (i.e. you generally " @@ -6831,7 +6831,7 @@ msgstr "" "kehtestatud reeglitele\n" "\n" "

                  Sisesta tekstikastidesse aadressid, üks igale reale; ^ märk rea alguses\n" -"tähistab Pythoni " +"tähistab Pythoni " "regulaaravaldist. Kurakaldkriipse sisesta nii nagu Pythoni stringe (s.t. " "kasuta ainult ühte kurakaldkriipsu).\n" "\n" @@ -7576,7 +7576,7 @@ msgstr "Kas kategoriseerija on kasutusel?" msgid "" "The topic filter categorizes each incoming email message\n" " according to regular\n" +" href=\"https://docs.python.org/3/library/re.html\">regular\n" " expression filters you specify below. If the message's\n" " Subject: or Keywords: header contains " "a\n" @@ -7599,7 +7599,7 @@ msgid "" " configuration variable." msgstr "" "Teemafilter kategoriseerib sissetulevaid kirju vastavalt regulaaravaldistele. Kui sissetuleva " +"docs.python.org/3/library/re.html\">regulaaravaldistele. Kui sissetuleva " "kirja Subject: või Keywords: päis vastab mõnele " "reeglile siis paigutatakse see vastavasse registrisse. Liikmell on " "võimalik valida talle huvi pakkuvat registrit (või registreid). Kirju, mis " @@ -10178,7 +10178,7 @@ msgid "" "Complete\n" "specifications are at:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "Address matches are case-insensitive, but case-preserved addresses are\n" "displayed.\n" @@ -10219,7 +10219,7 @@ msgstr "" "moodul,\n" "mille täielik ülevaade on aadressil:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "Otsing on tõstutundetu kui aadressid kuvatakse nii nagu nad listis kirjas " "on.\n" diff --git a/messages/eu/LC_MESSAGES/mailman.po b/messages/eu/LC_MESSAGES/mailman.po index cf7f641a..828839d7 100755 --- a/messages/eu/LC_MESSAGES/mailman.po +++ b/messages/eu/LC_MESSAGES/mailman.po @@ -7203,7 +7203,7 @@ msgid "" "\n" "

                  In the text boxes below, add one address per line; start the\n" " line with a ^ character to designate a Python regular expression. When entering backslashes, do " "so\n" " as if you were using Python raw strings (i.e. you generally " @@ -7244,7 +7244,7 @@ msgstr "" "

                  Beheko testu-kutxetan, gehitu helbide bat lerroko; lerroa " "hasi\n" " ^ karakterearekin, Python adierazpen erregularren kasuan. '\\' ikurrak " "sartzean,\n" " Python lerro gordinak erabiltzen ari bazina bezala egin ezazu " @@ -8069,7 +8069,7 @@ msgstr "Gaien iragazkiak gaituta ala ezgaituta beharko luke?" msgid "" "The topic filter categorizes each incoming email message\n" " according to regular\n" +" href=\"https://docs.python.org/3/library/re.html\">regular\n" " expression filters you specify below. If the message's\n" " Subject: or Keywords: header contains " "a\n" @@ -8092,7 +8092,7 @@ msgid "" " configuration variable." msgstr "" "Gaien iragazkiak, heltzen diren mezu guztiak sailkatu egiten ditu\n" -" behean adierazitako adierazpen\n" " erregularren iragazkiaren arabera.\n" " Subject: (gaia) edo Keywords (gako-" @@ -10632,7 +10632,7 @@ msgid "" "Complete\n" "specifications are at:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "Address matches are case-insensitive, but case-preserved addresses are\n" "displayed.\n" diff --git a/messages/fa/LC_MESSAGES/mailman.po b/messages/fa/LC_MESSAGES/mailman.po index 31b7acab..9af70718 100644 --- a/messages/fa/LC_MESSAGES/mailman.po +++ b/messages/fa/LC_MESSAGES/mailman.po @@ -6024,7 +6024,7 @@ msgid "" "\n" "

                  In the text boxes below, add one address per line; start the\n" " line with a ^ character to designate a Python regular expression. When entering backslashes, do " "so\n" " as if you were using Python raw strings (i.e. you generally " @@ -6626,7 +6626,7 @@ msgstr "" msgid "" "The topic filter categorizes each incoming email message\n" " according to regular\n" +" href=\"https://docs.python.org/3/library/re.html\">regular\n" " expression filters you specify below. If the message's\n" " Subject: or Keywords: header contains " "a\n" @@ -8738,7 +8738,7 @@ msgid "" "Complete\n" "specifications are at:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "Address matches are case-insensitive, but case-preserved addresses are\n" "displayed.\n" diff --git a/messages/fi/LC_MESSAGES/mailman.po b/messages/fi/LC_MESSAGES/mailman.po index 8db5badd..cd70185d 100755 --- a/messages/fi/LC_MESSAGES/mailman.po +++ b/messages/fi/LC_MESSAGES/mailman.po @@ -7189,7 +7189,7 @@ msgid "" "\n" "

                  In the text boxes below, add one address per line; start the\n" " line with a ^ character to designate a Python regular expression. When entering backslashes, do " "so\n" " as if you were using Python raw strings (i.e. you generally " @@ -7225,7 +7225,7 @@ msgstr "" "\n" "

                  Alla olevissa tekstilaatikoissa, lisää yksi osoite riville;\n" " aloita rivi ^ merkillä, joka määrittelee Pythonin yleisen ilmaisun. Jos kirjoitat takakeno- " "viivoja\n" " merkitse ne kuin käyttäisit Pythonin raakamerkkijonoa (esim. jos " @@ -8063,7 +8063,7 @@ msgstr "Pit msgid "" "The topic filter categorizes each incoming email message\n" " according to regular\n" +" href=\"https://docs.python.org/3/library/re.html\">regular\n" " expression filters you specify below. If the message's\n" " Subject: or Keywords: header contains " "a\n" @@ -8087,7 +8087,7 @@ msgid "" msgstr "" "Aiheen suodattimet luokittelee jokaisen tulevan sähköpostin\n" " tavallisen\n" +" href=\"https://docs.python.org/3/library/re.html\">tavallisen\n" " ilmaisun suodattimen mukaan kuten määrittelet alla. Jos\n" " viestin Aihe: tai Avainsanat: kentät " "täsmäävät\n" @@ -10859,7 +10859,7 @@ msgid "" "Complete\n" "specifications are at:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "Address matches are case-insensitive, but case-preserved addresses are\n" "displayed.\n" @@ -10900,7 +10900,7 @@ msgstr "" "Complete\n" "specifications are at:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "Address matches are case-insensitive, but case-preserved addresses are\n" "displayed.\n" diff --git a/messages/fr/LC_MESSAGES/mailman.po b/messages/fr/LC_MESSAGES/mailman.po index 67d4f8ed..8baedf56 100755 --- a/messages/fr/LC_MESSAGES/mailman.po +++ b/messages/fr/LC_MESSAGES/mailman.po @@ -7242,7 +7242,7 @@ msgid "" "\n" "

                  In the text boxes below, add one address per line; start the\n" " line with a ^ character to designate a Python regular expression. When entering backslashes, do " "so\n" " as if you were using Python raw strings (i.e. you generally " @@ -7284,7 +7284,7 @@ msgstr "" "

                  Dans la zone de texte ci-dessous, ajouter une adresse par " "ligne;\n" " commencer la ligne avec le caractère ^ pour designer une expression\n" +" href=\"https://docs.python.org/3/library/re.html\">expression\n" " régulière Python. Si vous utilisez des backslashs, faites-le " "comme\n" " si vous utilisiez des chaînes Python brutes (i.e. en utilisant " @@ -8150,7 +8150,7 @@ msgstr "Le filtre de th msgid "" "The topic filter categorizes each incoming email message\n" " according to regular\n" +" href=\"https://docs.python.org/3/library/re.html\">regular\n" " expression filters you specify below. If the message's\n" " Subject: or Keywords: header contains " "a\n" @@ -8174,7 +8174,7 @@ msgid "" msgstr "" "Le filtre de thème place chaque courriel qui arrive dans une catégorie\n" " sur la base \n" +" href=\"https://docs.python.org/3/library/re.html\">\n" " des filtres d'expressions régulières que vous avez définis " "ci-dessous.\n" " Si l'en-tête Objet: ou Mots Clés: " @@ -10962,7 +10962,7 @@ msgid "" "Complete\n" "specifications are at:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "Address matches are case-insensitive, but case-preserved addresses are\n" "displayed.\n" @@ -11001,7 +11001,7 @@ msgstr "" "utilisées. La syntaxe des expressions régulières est celle de Perl5\n" "avec l'utilisation du module re de Python. Les spécifications\n" "complètes sont disponible à\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "La comparaison des adresses se fait avec non respect de la casse tandis\n" "que les résultats trouvés seront affichés avec leurs casses\n" diff --git a/messages/gl/LC_MESSAGES/mailman.po b/messages/gl/LC_MESSAGES/mailman.po index 950c0046..a7d0f7f8 100755 --- a/messages/gl/LC_MESSAGES/mailman.po +++ b/messages/gl/LC_MESSAGES/mailman.po @@ -7054,7 +7054,7 @@ msgid "" "\n" "

                  In the text boxes below, add one address per line; start the\n" " line with a ^ character to designate a Python regular expression. When entering backslashes, do " "so\n" " as if you were using Python raw strings (i.e. you generally " @@ -7093,7 +7093,7 @@ msgstr "" "

                  Na caixa de texto de máis abaixo, engada un enderezo en cada " "liña, comece a liña\n" " co carácter ^ para designar unha \n" -" \n" +" \n" " expresión regular de Python. Cando inclúa barras inclinadas " "de esquerda a dereita (\"\\\"),\n" " fágao como se estivese a empregar cadeas de Python en bruto " @@ -7885,7 +7885,7 @@ msgstr "Debe activarse ou desactivarse o filtrado de temas?" msgid "" "The topic filter categorizes each incoming email message\n" " according to regular\n" +" href=\"https://docs.python.org/3/library/re.html\">regular\n" " expression filters you specify below. If the message's\n" " Subject: or Keywords: header contains " "a\n" @@ -7908,7 +7908,7 @@ msgid "" " configuration variable." msgstr "" "O filtro segundo o tema, clasifica cada mensaxe recibida\n" -" segundo os \n" " filtros de expresións regulares que especifique abaixo. Se " "as cabeceiras\n" @@ -10582,7 +10582,7 @@ msgid "" "Complete\n" "specifications are at:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "Address matches are case-insensitive, but case-preserved addresses are\n" "displayed.\n" @@ -10622,7 +10622,7 @@ msgstr "" "A sintaxe da expresión regular é ao estilo Perl5, usando o módulo Python " "re. As especificacións completas están en:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "Na busca de enderezos non se distinguen as maiúsculas das minúsculas mais \n" "amósanse os enderezos tal e como se achan.\n" diff --git a/messages/he/LC_MESSAGES/mailman.po b/messages/he/LC_MESSAGES/mailman.po index 01360f91..cc143f20 100755 --- a/messages/he/LC_MESSAGES/mailman.po +++ b/messages/he/LC_MESSAGES/mailman.po @@ -6859,7 +6859,7 @@ msgid "" "\n" "

                  In the text boxes below, add one address per line; start the\n" " line with a ^ character to designate a Python regular expression. When entering backslashes, do " "so\n" " as if you were using Python raw strings (i.e. you generally " @@ -7672,7 +7672,7 @@ msgstr "יש להפעיל ×ת מסנן הנוש××™× ×ו להפוך ×ותו msgid "" "The topic filter categorizes each incoming email message\n" " according to regular\n" +" href=\"https://docs.python.org/3/library/re.html\">regular\n" " expression filters you specify below. If the message's\n" " Subject: or Keywords: header contains " "a\n" @@ -7695,7 +7695,7 @@ msgid "" " configuration variable." msgstr "" "מסנן הנוש××™× ×ž×פיין כל מסר דו×\"ל שנכנס בהת×× ×œ\n" -" \n" +" \n" " מסנני ×‘×™×˜×•×™× ×¨×’×•×œ×¨×™×™× ×©×תה מגדיר למטה. ×× ×›×•×ª×¨×ª " "×”-נוש×:\n" " ×ו מלות-המפתח: מכילה הת×מה מול מסנן נוש×, המסר " @@ -10359,7 +10359,7 @@ msgid "" "Complete\n" "specifications are at:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "Address matches are case-insensitive, but case-preserved addresses are\n" "displayed.\n" @@ -10399,7 +10399,7 @@ msgstr "" "התחביר של ×”×‘×™×˜×™×™× ×”×¨×’×•×œ×¨×™×™× ×“×•×ž×” לתחביר של Perl5, ב×מצעות מודול ×”-re של " "Python.\n" "ניתן למצ×ו הגדרה מל××” ב-:\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "הת×מה של כתובות ××™× ×” רגישה לר×שיות התווי×, ×ך הכתובות מוצגות ×¢× ×©×ž×™×¨×” על " "ר×שיות.\n" diff --git a/messages/hr/LC_MESSAGES/mailman.po b/messages/hr/LC_MESSAGES/mailman.po index 4288f144..539d968d 100755 --- a/messages/hr/LC_MESSAGES/mailman.po +++ b/messages/hr/LC_MESSAGES/mailman.po @@ -7198,7 +7198,7 @@ msgid "" "\n" "

                  In the text boxes below, add one address per line; start the\n" " line with a ^ character to designate a Python regular expression. When entering backslashes, do " "so\n" " as if you were using Python raw strings (i.e. you generally " @@ -7240,7 +7240,7 @@ msgstr "" "

                  U donjim tekst poljima, dodajte jednu adresu po liniji; " "zapoènite liniju\n" " sa ^ znakom da biste konstruirali Python regular expression. Kada koristite kosu crtu, " "unesite je kao da\n" " unosite obièan Python tekst (npr. samo jednu kosu crtu)\n" @@ -8054,7 +8054,7 @@ msgstr "Treba li filter naslova biti omogu msgid "" "The topic filter categorizes each incoming email message\n" " according to regular\n" +" href=\"https://docs.python.org/3/library/re.html\">regular\n" " expression filters you specify below. If the message's\n" " Subject: or Keywords: header contains " "a\n" @@ -8078,7 +8078,7 @@ msgid "" msgstr "" "Filter naslova kategorizira svaku dolaznu e-mail poruku\n" " sukladno regular\n" +" href=\"https://docs.python.org/3/library/re.html\">regular\n" " expression filterima koje navodite dolje. Ako\n" " Subject: ili Keywords: zaglavlja " "poruke\n" @@ -10426,7 +10426,7 @@ msgid "" "Complete\n" "specifications are at:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "Address matches are case-insensitive, but case-preserved addresses are\n" "displayed.\n" diff --git a/messages/hu/LC_MESSAGES/mailman.po b/messages/hu/LC_MESSAGES/mailman.po index c33aaa77..04b258cb 100755 --- a/messages/hu/LC_MESSAGES/mailman.po +++ b/messages/hu/LC_MESSAGES/mailman.po @@ -6987,7 +6987,7 @@ msgid "" "\n" "

                  In the text boxes below, add one address per line; start the\n" " line with a ^ character to designate a Python regular expression. When entering backslashes, do " "so\n" " as if you were using Python raw strings (i.e. you generally " @@ -7024,7 +7024,7 @@ msgstr "" "külsõ\n" "beküldési szabályok lesznek érvényesek.\n" "

                  A szövegdobozban soronként egy címet adjunk meg; a Python reguláris kifejezések jelölésére a sorokat ^ jellel kezdjük. " "Backslasht (\"\\\") mint egy hagyományos\n" "karaktert adhatunk meg, úgy ahogy a Pythonban karakternél szokásos (ez " @@ -7822,7 +7822,7 @@ msgstr "A t msgid "" "The topic filter categorizes each incoming email message\n" " according to regular\n" +" href=\"https://docs.python.org/3/library/re.html\">regular\n" " expression filters you specify below. If the message's\n" " Subject: or Keywords: header contains " "a\n" @@ -7846,7 +7846,7 @@ msgid "" msgstr "" "A témaszûrõ minden egyes bejövõ levelet megvizsgál a késõbbiekben megadott " "reguláris\n" +"href=\"https://docs.python.org/3/library/re.html\">reguláris\n" "szûrõ kifejezések alapján. Ha a levél Subject: vagy\n" "\tKeywords: fejléce egyezést mutat valamelyik szûrési\n" "\tfeltétellel, akkor az üzenet egy ún. téma tárolóba kerül.\n" @@ -10538,7 +10538,7 @@ msgid "" "Complete\n" "specifications are at:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "Address matches are case-insensitive, but case-preserved addresses are\n" "displayed.\n" @@ -10576,7 +10576,7 @@ msgstr "" "\n" "A hagyományos kifejezésinek Perl5-típusúnak kell lennie, a keresést a\n" "Python re modulja végzi. Részletes leírása a következõ címen található:\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "Keresésnél a kis- és nagybetûk nem számítanak, de a talált címek a\n" "feliratkozáskor megadott formában jelennek meg.\n" diff --git a/messages/ia/LC_MESSAGES/mailman.po b/messages/ia/LC_MESSAGES/mailman.po index 5307686a..6e15bb0f 100644 --- a/messages/ia/LC_MESSAGES/mailman.po +++ b/messages/ia/LC_MESSAGES/mailman.po @@ -7262,7 +7262,7 @@ msgid "" "\n" "

                  In the text boxes below, add one address per line; start the\n" " line with a ^ character to designate a Python regular expression. When entering backslashes, do " "so\n" " as if you were using Python raw strings (i.e. you generally " @@ -7301,7 +7301,7 @@ msgstr "" "

                  In le cassas textual infra, adde un adresse per linea; " "comencia le\n" " linea con un signo ^ pro designar un expression Python regular. Scribe barras inverse como si\n" " usar catenas rude de Python (i.e. on generalmente usa\n" " solmente un singule barra inverse).\n" @@ -8135,7 +8135,7 @@ msgstr "Le filtro de themas debe esser activate o disactivate?" msgid "" "The topic filter categorizes each incoming email message\n" " according to regular\n" +" href=\"https://docs.python.org/3/library/re.html\">regular\n" " expression filters you specify below. If the message's\n" " Subject: or Keywords: header contains " "a\n" @@ -8159,7 +8159,7 @@ msgid "" msgstr "" "Le filtro de themas categorisa cata message entrante in\n" " base a filtros\n" +" href=\"https://docs.python.org/3/library/re.html\">filtros\n" " con expressiones regular que tu scribe infra. Si le " "capite\n" " Subject: o Keywords: del message\n" @@ -10514,7 +10514,7 @@ msgid "" "Complete\n" "specifications are at:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "Address matches are case-insensitive, but case-preserved addresses are\n" "displayed.\n" diff --git a/messages/it/LC_MESSAGES/mailman.po b/messages/it/LC_MESSAGES/mailman.po index abf03d9a..392375ff 100755 --- a/messages/it/LC_MESSAGES/mailman.po +++ b/messages/it/LC_MESSAGES/mailman.po @@ -7593,7 +7593,7 @@ msgid "" "\n" "

                  In the text boxes below, add one address per line; start the\n" " line with a ^ character to designate a Python regular expression. When entering backslashes, do " "so\n" " as if you were using Python raw strings (i.e. you generally " @@ -7631,7 +7631,7 @@ msgstr "" "\n" "

                  Nei campi sottostanti aggiungi un indirizzo per riga; \n" " inizia la riga con un carattere ^ per indicare una\n" -" Espressione regolare Python. Quando inserisci " "backslash,\n" " fallo come se stessi inserendo stringhe grezze Python (ad \n" @@ -8485,7 +8485,7 @@ msgstr "Devo abilitare il filtro sugli argomenti?" msgid "" "The topic filter categorizes each incoming email message\n" " according to regular\n" +" href=\"https://docs.python.org/3/library/re.html\">regular\n" " expression filters you specify below. If the message's\n" " Subject: or Keywords: header contains " "a\n" @@ -8509,7 +8509,7 @@ msgid "" msgstr "" "Il filtro per argomenti categorizza ogni messaggio entrante\n" " in base a\n" -"\n" +"\n" " espressioni regolari che puoi specificare più\n" " in basso. Se i campi Subject: oppure\n" " Keywords: nel messaggio contengono parole\n" @@ -11364,7 +11364,7 @@ msgid "" "Complete\n" "specifications are at:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "Address matches are case-insensitive, but case-preserved addresses are\n" "displayed.\n" @@ -11405,7 +11405,7 @@ msgstr "" "usando il modulo Python re. La specifica completa si può trovare\n" "all'indirizzo:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "La ricerca degli indirizzi è non sensibile a maiuscole/minuscole\n" "ma vengono mostrati gli indirizzi senza alterazioni.\n" diff --git a/messages/ja/LC_MESSAGES/mailman.po b/messages/ja/LC_MESSAGES/mailman.po index ff1f12fa..83aed0cd 100644 --- a/messages/ja/LC_MESSAGES/mailman.po +++ b/messages/ja/LC_MESSAGES/mailman.po @@ -6846,7 +6846,7 @@ msgid "" "\n" "

                  In the text boxes below, add one address per line; start the\n" " line with a ^ character to designate a Python regular expression. When entering backslashes, do " "so\n" " as if you were using Python raw strings (i.e. you generally " @@ -6875,7 +6875,7 @@ msgstr "" "Èó²ñ°÷¤ËÂФ¹¤ëµ¬Â§¤Ë½¾¤Ã¤Æ¤Õ¤ë¤¤¤Ë¤«¤±¤é¤ì¤Þ¤¹¡£\n" "

                  ²¼¤Î¥Æ¥­¥¹¥È¥Ü¥Ã¥¯¥¹¤Ç¡¢1¹Ô¤Ë1¤Ä¤Î¥á¡¼¥ë¥¢¥É¥ì¥¹¤òÅÐÏ¿¤Ç¤­¤Þ¤¹;\n" "¹Ô¤ÎºÇ½é¤Ë ^ ¤òµ­½Ò¤¹¤ë¤È\n" -"Python¤Î\n" +"Python¤Î\n" "Àµµ¬É½¸½¤Ë¤Ê¤ê¤Þ¤¹(ÆüËܸìÈÇʸ½ñ¤Ø¤Î¥ê¥ó¥¯)¡£\n" "¥Ð¥Ã¥¯¥¹¥é¥Ã¥·¥å¤Ï¡¢Python ¤Î raw stringµ­Ë¡¤Î¤ä¤ê¤«¤¿¤Ç\n" @@ -7737,7 +7737,7 @@ msgstr " msgid "" "The topic filter categorizes each incoming email message\n" " according to regular\n" +" href=\"https://docs.python.org/3/library/re.html\">regular\n" " expression filters you specify below. If the message's\n" " Subject: or Keywords: header contains " "a\n" @@ -7760,7 +7760,7 @@ msgid "" " configuration variable." msgstr "" "ÏÃÂê¥Õ¥£¥ë¥¿¤Ï¡¢°Ê²¼¤ËÄêµÁ¤¹¤ë\n" -"Àµµ¬É½¸½\n" +"Àµµ¬É½¸½\n" "¥Õ¥£¥ë¥¿ ¤ÇÅê¹Æ¤µ¤ì¤¿¥á¡¼¥ë¤òʬÎष¤Þ¤¹(ÆüËܸìÈÇʸ½ñ¤Ø¤Î¥ê¥ó¥¯)¡£\n" "¥á¡¼¥ë¤Î Subject: ¤« Keywords: ¥Ø¥Ã¥À¤¬\n" @@ -10447,7 +10447,7 @@ msgid "" "Complete\n" "specifications are at:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "Address matches are case-insensitive, but case-preserved addresses are\n" "displayed.\n" @@ -10486,7 +10486,7 @@ msgstr "" "Àµµ¬É½¸½¤Ï Perl5 ¤Ë»÷¤Æ¤¤¤ë¤¬, Python ¤Î re ¥â¥¸¥å¡¼¥ë¤ò»È¤¦¡£ re\n" "¥â¥¸¥å¡¼¥ë¤Î»ÅÍͤϰʲ¼¤Î¥Ú¡¼¥¸¤Ë¤¢¤ë:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "(ÆüËܸìÈÇ¤Ï https://docs.python.org/ja/2.7/library/re.html)\n" "\n" "¥á¡¼¥ë¥¢¥É¥ì¥¹¤ÏÂçʸ»ú¾®Ê¸»ú¤Î¶èÊ̤ò¤»¤º¤Ë¸¡º÷¤¹¤ë¤¬¡¢·ë²Ì¤Îɽ¼¨¤Ï\n" diff --git a/messages/ko/LC_MESSAGES/mailman.po b/messages/ko/LC_MESSAGES/mailman.po index f662393a..9e5a7f82 100755 --- a/messages/ko/LC_MESSAGES/mailman.po +++ b/messages/ko/LC_MESSAGES/mailman.po @@ -6399,7 +6399,7 @@ msgid "" "\n" "

                  In the text boxes below, add one address per line; start the\n" " line with a ^ character to designate a Python regular expression. When entering backslashes, do " "so\n" " as if you were using Python raw strings (i.e. you generally " @@ -6424,7 +6424,7 @@ msgstr "" "°ÅÀýµÇ°Å³ª, ȤÀº ¹ö·ÁÁý´Ï´Ù.\n" "\n" "

                  ¾Æ·¡ÀÇ ÅØ½ºÆ® ¹Ú½º¿¡¼­ ÁÙ(line)¸¶´Ù ÇϳªÀÇ ÁÖ¼Ò¸¦ ³ÖÀ» ¼ö ÀÖÀ¸¸ç ^ ¹®ÀÚ" -"·Î ½ÃÀÛÇÏ´Â ÁÙÀº Python Á¤±Ô Ç¥Çö½ÄÀ» ³ªÅ¸³À´Ï´Ù. ¹é½½·¡½¬¸¦ ³ÖÀ½À¸·Î½á Python ÀÇ " "raw ¹®ÀÚ¿­À» »ç¿ëÇÒ ¼ö ÀÖ½À´Ï´Ù.(¿¹¸¦ µé¾î ´ç½ÅÀº ÀϹÝÀûÀ¸·Î ÇϳªÀÇ ¹é½½·¡½¬" "¸¦ »ç¿ëÇÕ´Ï´Ù.)

                  ºñÁ¤±Ô½Ä ÆÐÅÏÀÌ Ç×»ó óÀ½¿¡ ¿Àµµ·Ï ÇϽʽÿÀ." @@ -7138,7 +7138,7 @@ msgstr " msgid "" "The topic filter categorizes each incoming email message\n" " according to regular\n" +" href=\"https://docs.python.org/3/library/re.html\">regular\n" " expression filters you specify below. If the message's\n" " Subject: or Keywords: header contains " "a\n" @@ -7160,7 +7160,7 @@ msgid "" "topics_bodylines_limit\">topics_bodylines_limit\n" " configuration variable." msgstr "" -"ÁÖÁ¦ °É·¯³»±â´Â ´ç½ÅÀÌ ¾Æ·¡¿¡¼­ Á¤ÇÑ href=\"https://docs.python.org/2/" +"ÁÖÁ¦ °É·¯³»±â´Â ´ç½ÅÀÌ ¾Æ·¡¿¡¼­ Á¤ÇÑ href=\"https://docs.python.org/3/" "library/re.html\">Á¤±Ô Ç¥Çö½Ä - °É·¯³»±â¿¡ µû¶ó µé¾î¿À´Â E¸ÞÀÏ ¸Þ¼¼¸¦ ºÐ" "·ùÇÒ ¼ö ÀÖ½À´Ï´Ù. ¸¸¾à ¸Þ¼¼ÁöÀÇ Çì´õ Á¦¸ñ: ȤÀº Ű¿öµå:°¡ °É·¯³»±â ³»¿ë°ú ¸Â°Ô µÇ¸é ¸Þ¼¼Áö´Â ³í¸®ÀûÀ¸·Î ÁÖÁ¦ ¼ÒÄí¸®(¿ªÀÚ " @@ -9355,7 +9355,7 @@ msgid "" "Complete\n" "specifications are at:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "Address matches are case-insensitive, but case-preserved addresses are\n" "displayed.\n" @@ -11591,11 +11591,11 @@ msgstr "" #~ msgid "" #~ "

                • Find members by\n" -#~ " Python regular expression (regexp)
                  " #~ msgstr "" -#~ "
                • \n" #~ "Python Á¤±Ô Ç¥Çö½Ä (regexp)·Î ȸ¿ø ã±â
                  " diff --git a/messages/lt/LC_MESSAGES/mailman.po b/messages/lt/LC_MESSAGES/mailman.po index 2d499d1e..17a299e9 100755 --- a/messages/lt/LC_MESSAGES/mailman.po +++ b/messages/lt/LC_MESSAGES/mailman.po @@ -6027,7 +6027,7 @@ msgid "" "\n" "

                  In the text boxes below, add one address per line; start the\n" " line with a ^ character to designate a Python regular expression. When entering backslashes, do " "so\n" " as if you were using Python raw strings (i.e. you generally " @@ -6627,7 +6627,7 @@ msgstr "" msgid "" "The topic filter categorizes each incoming email message\n" " according to regular\n" +" href=\"https://docs.python.org/3/library/re.html\">regular\n" " expression filters you specify below. If the message's\n" " Subject: or Keywords: header contains " "a\n" @@ -8726,7 +8726,7 @@ msgid "" "Complete\n" "specifications are at:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "Address matches are case-insensitive, but case-preserved addresses are\n" "displayed.\n" diff --git a/messages/mailman.pot b/messages/mailman.pot index 21b73257..1cc544be 100644 --- a/messages/mailman.pot +++ b/messages/mailman.pot @@ -5287,7 +5287,7 @@ msgid "" "\n" "

                  In the text boxes below, add one address per line; start the\n" " line with a ^ character to designate a Python regular expression. When entering backslashes, do so\n" " as if you were using Python raw strings (i.e. you generally just\n" " use a single backslash).\n" @@ -5834,7 +5834,7 @@ msgstr "" msgid "" "The topic filter categorizes each incoming email message\n" " according to regular\n" +" href=\"https://docs.python.org/3/library/re.html\">regular\n" " expression filters you specify below. If the message's\n" " Subject: or Keywords: header contains a\n" " match against a topic filter, the message is logically placed\n" @@ -7749,7 +7749,7 @@ msgid "" "Regular expression syntax is Perl5-like, using the Python re module. Complete\n" "specifications are at:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "Address matches are case-insensitive, but case-preserved addresses are\n" "displayed.\n" diff --git a/messages/nl/LC_MESSAGES/mailman.po b/messages/nl/LC_MESSAGES/mailman.po index ee55153d..e629d047 100755 --- a/messages/nl/LC_MESSAGES/mailman.po +++ b/messages/nl/LC_MESSAGES/mailman.po @@ -7287,7 +7287,7 @@ msgid "" "\n" "

                  In the text boxes below, add one address per line; start the\n" " line with a ^ character to designate a Python regular expression. When entering backslashes, do " "so\n" " as if you were using Python raw strings (i.e. you generally " @@ -7325,7 +7325,7 @@ msgstr "" "

                  In de onderstaande tekstvelden dient u per regel ��n adres\n" " in te vullen. Start de regel met een ^ teken om er de juiste\n" " reguliere Python uitdrukking van te maken. Bij gebruik\n" " van 'backslashes' (schuine strepen) kunt u deze invoeren alsof\n" " het Python raw strings zijn (m.a.w. in het algemeen gebruikt\n" @@ -8156,7 +8156,7 @@ msgstr "Moet de onderwerpfilter aan of uit staan?" msgid "" "The topic filter categorizes each incoming email message\n" " according to regular\n" +" href=\"https://docs.python.org/3/library/re.html\">regular\n" " expression filters you specify below. If the message's\n" " Subject: or Keywords: header contains " "a\n" @@ -8180,7 +8180,7 @@ msgid "" msgstr "" "De onderwerpfilter categoriseert elk binnengekomen e-mailbericht\n" " volgens de reguliere\n" +" href=\"https://docs.python.org/3/library/re.html\">reguliere\n" " uitdrukkingsfilters die u hieronder specificeert. Als de\n" " Subject: (onderwerp) of Keywords:\n" " (sleutelwoorden) headers van het bericht overeenkomen met\n" @@ -10469,7 +10469,7 @@ msgid "" "Complete\n" "specifications are at:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "Address matches are case-insensitive, but case-preserved addresses are\n" "displayed.\n" diff --git a/messages/no/LC_MESSAGES/mailman.po b/messages/no/LC_MESSAGES/mailman.po index 6bce808c..902ee82d 100755 --- a/messages/no/LC_MESSAGES/mailman.po +++ b/messages/no/LC_MESSAGES/mailman.po @@ -7093,7 +7093,7 @@ msgid "" "\n" "

                  In the text boxes below, add one address per line; start the\n" " line with a ^ character to designate a Python regular expression. When entering backslashes, do " "so\n" " as if you were using Python raw strings (i.e. you generally " @@ -7128,7 +7128,7 @@ msgstr "" "generic_nonmember_action\">generelle regler for ikke-medlemmer sier.\n" "\n" "

                  I tekstboksene nedenfor legger du inn en epostadresse per linje.\n" -"Du kan også legge inn Python regexp-uttrykk.\n" "Begynn isåfall linjen med tegnet ^ for å markere at det er et " "slikt uttrykk.\n" @@ -7928,7 +7928,7 @@ msgstr "Skal emnefilteret være tilgjengelig eller ikke tilgjengelig?" msgid "" "The topic filter categorizes each incoming email message\n" " according to regular\n" +" href=\"https://docs.python.org/3/library/re.html\">regular\n" " expression filters you specify below. If the message's\n" " Subject: or Keywords: header contains " "a\n" @@ -7951,7 +7951,7 @@ msgid "" " configuration variable." msgstr "" "Emnefilteret kategoriserer hver epost som kommer til listen,\n" -"etter regexp-" +"etter regexp-" "uttrykkene\n" "du skriver inn nedenfor. Hvis feltene Subject: eller " "Keywords:\n" @@ -10652,7 +10652,7 @@ msgid "" "Complete\n" "specifications are at:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "Address matches are case-insensitive, but case-preserved addresses are\n" "displayed.\n" @@ -10692,7 +10692,7 @@ msgstr "" "benyttes.\n" "Komplett spesifikasjon finnes på:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "Adresser sammenlignes uten forskjell på små og store bokstaver, men vises " "med\n" @@ -13938,7 +13938,7 @@ msgstr "" #~ msgid "" #~ "

                • Find members by\n" -#~ " Python regular expression (regexp)
                  " #~ msgstr "" diff --git a/messages/pl/LC_MESSAGES/mailman.po b/messages/pl/LC_MESSAGES/mailman.po index 81b9b404..510cd176 100755 --- a/messages/pl/LC_MESSAGES/mailman.po +++ b/messages/pl/LC_MESSAGES/mailman.po @@ -6838,7 +6838,7 @@ msgid "" "\n" "

                  In the text boxes below, add one address per line; start the\n" " line with a ^ character to designate a Python regular expression. When entering backslashes, do " "so\n" " as if you were using Python raw strings (i.e. you generally " @@ -6872,7 +6872,7 @@ msgstr "" "generic_nonmember_action\">ogólnej\n" " zasadzie stosowanej dla nie subskrybentów.

                  W polach nale¿y " "wpisywaæ po jednym adresie w linii. Znak ^ napocz±tku nowej linii oznacza wyra¿enie regularne w " +"href=\"https://docs.python.org/3/library/re.html\"> wyra¿enie regularne w " "Pythonie.

                  Pamiêtaj, ¿e linie bez u¿ycia wyra¿eñ regularnych s± " "dopasowywane wcze¶niej." @@ -7699,7 +7699,7 @@ msgstr "Czy filtrowanie tematyczne ma by msgid "" "The topic filter categorizes each incoming email message\n" " according to regular\n" +" href=\"https://docs.python.org/3/library/re.html\">regular\n" " expression filters you specify below. If the message's\n" " Subject: or Keywords: header contains " "a\n" @@ -7723,7 +7723,7 @@ msgid "" msgstr "" "Filtr tematyczny dzieli wszystkie przychodz±ce wiadomo¶ci \n" " na kategorie wed³ug wyra¿enia\n" +"href=\"https://docs.python.org/3/library/re.html\">wyra¿enia\n" " regularnego , które okre¶lisz. Je¿eli pola nag³ówka\n" " Subject: lub Keywords: zostan±\n" " dopasowane do tego wyra¿enia, wiadomo¶æ jest umieszczana\n" @@ -10027,7 +10027,7 @@ msgid "" "Complete\n" "specifications are at:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "Address matches are case-insensitive, but case-preserved addresses are\n" "displayed.\n" diff --git a/messages/pt/LC_MESSAGES/mailman.po b/messages/pt/LC_MESSAGES/mailman.po index b7f65449..bb0497ae 100755 --- a/messages/pt/LC_MESSAGES/mailman.po +++ b/messages/pt/LC_MESSAGES/mailman.po @@ -7052,7 +7052,7 @@ msgid "" "\n" "

                  In the text boxes below, add one address per line; start the\n" " line with a ^ character to designate a Python regular expression. When entering backslashes, do " "so\n" " as if you were using Python raw strings (i.e. you generally " @@ -7092,7 +7092,7 @@ msgstr "" "\n" "

                  Nas caixas de texto abaixo, adicione um endereço por linha;\n" " inicie a linha com o caracter ^ para designar uma\n" -" Expressão " +" Expressão " "regular em Python. Quando acrescentar contrabarras, faça como se \n" " estivesse usando strings raw do Python (i.e. você geralmente \n" " utilizaria apenas uma barra invertida).\n" @@ -7892,7 +7892,7 @@ msgstr "O filtro de t msgid "" "The topic filter categorizes each incoming email message\n" " according to regular\n" +" href=\"https://docs.python.org/3/library/re.html\">regular\n" " expression filters you specify below. If the message's\n" " Subject: or Keywords: header contains " "a\n" @@ -7916,7 +7916,7 @@ msgid "" msgstr "" "O filtro de tópico categoriza cada mensagem de entrada de \n" " acordo com \n" -" href=\"https://docs.python.org/2/library/re.html\">filtros\n" +" href=\"https://docs.python.org/3/library/re.html\">filtros\n" " de expressões regulares que poderá especificar abaixo. \n" " caso o cabeçalho Subject: ou Keywords: " "da \n" @@ -10659,7 +10659,7 @@ msgid "" "Complete\n" "specifications are at:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "Address matches are case-insensitive, but case-preserved addresses are\n" "displayed.\n" @@ -10699,7 +10699,7 @@ msgstr "" "A sintaxe de expressões regulares é no estilo Perl5, usando o módulo\n" "Python. As especificações completas podem ser encontradas em:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "Endereços que conferem são case-insensitive, mas endereços case-sensitive\n" "são mostrados.\n" diff --git a/messages/pt_BR/LC_MESSAGES/mailman.po b/messages/pt_BR/LC_MESSAGES/mailman.po index 0184cbf0..70b71802 100644 --- a/messages/pt_BR/LC_MESSAGES/mailman.po +++ b/messages/pt_BR/LC_MESSAGES/mailman.po @@ -7247,7 +7247,7 @@ msgid "" "\n" "

                  In the text boxes below, add one address per line; start the\n" " line with a ^ character to designate a Python regular expression. When entering backslashes, do " "so\n" " as if you were using Python raw strings (i.e. you generally " @@ -7282,7 +7282,7 @@ msgstr "" " gerais para não membros.

                  Nas caixas de texto abaixo, " "adicione um endereço por linha; comece a\n" " linha com um caractere ^ para designar uma expressão regular Python. Ao " +"docs.python.org/3/library/re.html\">expressão regular Python. Ao " "fornecer contra barra, faça isso\n" " como se você estivesse usando strings Python (ou seja, use " "apenas uma\n" @@ -8242,7 +8242,7 @@ msgstr "O filtro de t msgid "" "The topic filter categorizes each incoming email message\n" " according to regular\n" +" href=\"https://docs.python.org/3/library/re.html\">regular\n" " expression filters you specify below. If the message's\n" " Subject: or Keywords: header contains " "a\n" @@ -8266,7 +8266,7 @@ msgid "" msgstr "" "O filtro de tópico categoriza cada mensagem de email recebida\n" "             de acordo com filtros de " +"            href = \"https://docs.python.org/3/library/re.html\"> filtros de " "expressão\n" "              regular que você especificar abaixo. Se os campos da " "mensagem\n" @@ -11060,7 +11060,7 @@ msgid "" "Complete\n" "specifications are at:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "Address matches are case-insensitive, but case-preserved addresses are\n" "displayed.\n" @@ -11102,7 +11102,7 @@ msgstr "" "do Python.\n" "As especificações complestas estão em:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "As correspondências de endereços não são sensíveis a caixa, mas os endereços " "serão exibidos\n" diff --git a/messages/ro/LC_MESSAGES/mailman.po b/messages/ro/LC_MESSAGES/mailman.po index f3cae8f8..b7f003be 100755 --- a/messages/ro/LC_MESSAGES/mailman.po +++ b/messages/ro/LC_MESSAGES/mailman.po @@ -6892,7 +6892,7 @@ msgid "" "\n" "

                  In the text boxes below, add one address per line; start the\n" " line with a ^ character to designate a Python regular expression. When entering backslashes, do " "so\n" " as if you were using Python raw strings (i.e. you generally " @@ -7679,7 +7679,7 @@ msgstr "Este filtrul de topici activat sau dezactivat?" msgid "" "The topic filter categorizes each incoming email message\n" " according to regular\n" +" href=\"https://docs.python.org/3/library/re.html\">regular\n" " expression filters you specify below. If the message's\n" " Subject: or Keywords: header contains " "a\n" @@ -7702,7 +7702,7 @@ msgid "" " configuration variable." msgstr "" "Filtrul de topici categorizează fiecare e-mail sosit pe baza expresiilor regulate de " +"href=\"https://docs.python.org/3/library/re.html\">expresiilor regulate de " "filtrare pe care le specificaţi mai jos.\n" "\n" "Dacă unul din headerele Subject:sau Keywords:\n" @@ -10347,7 +10347,7 @@ msgid "" "Complete\n" "specifications are at:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "Address matches are case-insensitive, but case-preserved addresses are\n" "displayed.\n" @@ -10388,7 +10388,7 @@ msgstr "" "Complete\n" "specifications are at:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "Address matches are case-insensitive, but case-preserved addresses are\n" "displayed.\n" diff --git a/messages/ru/LC_MESSAGES/mailman.po b/messages/ru/LC_MESSAGES/mailman.po index 7cf241b4..b396252d 100644 --- a/messages/ru/LC_MESSAGES/mailman.po +++ b/messages/ru/LC_MESSAGES/mailman.po @@ -7172,7 +7172,7 @@ msgid "" "\n" "

                  In the text boxes below, add one address per line; start the\n" " line with a ^ character to designate a Python regular expression. When entering backslashes, do " "so\n" " as if you were using Python raw strings (i.e. you generally " @@ -7208,7 +7208,7 @@ msgstr "" "\n" "

                  Ð’ текÑтовых полÑÑ… ниже добавлÑйте по одному адреÑу в Ñтроку, Ñтроки,\n" "начинающиеÑÑ Ñ ^, будут ÑчитатьÑÑ Ñ€ÐµÐ³ÑƒÐ»Ñрными\n" +"href=\"https://docs.python.org/3/library/re.html\">регулÑрными\n" "выражениÑми Ñзыка Python. Обратные наклонные черты должны вводитьÑÑ\n" "так же, как и в обычных Ñтроках Python (то еÑть, Ñкорее вÑего, вам\n" "понадобитÑÑ Ð¿Ð¸Ñать только один такой Ñимвол).\n" @@ -8131,7 +8131,7 @@ msgstr "ИÑпользовать тематичеÑкие разделы?" msgid "" "The topic filter categorizes each incoming email message\n" " according to regular\n" +" href=\"https://docs.python.org/3/library/re.html\">regular\n" " expression filters you specify below. If the message's\n" " Subject: or Keywords: header contains " "a\n" @@ -8154,7 +8154,7 @@ msgid "" " configuration variable." msgstr "" "Фильтр разделов Ñортирует вÑе входÑщие ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð² ÑоответÑтвии Ñ\n" -"\n" +"\n" "регулÑрными выражениÑми, указываемыми ниже. ЕÑли в заголовках\n" "Subject: или Keywords: ÑообщениÑ\n" "ÑодержитÑÑ Ð¿Ð¾Ð´Ñтрока, ÑоответÑÑ‚Ð²ÑƒÑŽÑ‰Ð°Ñ Ñ„Ð¸Ð»ÑŒÑ‚Ñ€Ñƒ, Ñообщение помещаетÑÑ\n" @@ -10932,7 +10932,7 @@ msgid "" "Complete\n" "specifications are at:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "Address matches are case-insensitive, but case-preserved addresses are\n" "displayed.\n" @@ -10972,7 +10972,7 @@ msgstr "" "выражений в Perl5, иÑпользуетÑÑ Ð¼Ð¾Ð´ÑƒÐ»ÑŒ re. Полное опиÑание ÑинтакÑиÑа\n" "находитÑÑ Ð¿Ð¾ адреÑу:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "При Ñравнении адреÑов региÑтр не учитываетÑÑ, но еÑли в найденном адреÑе\n" "еÑть заглавные буквы, они печатаютÑÑ Ð²Ð¼ÐµÑто Ñтрочных.\n" diff --git a/messages/sk/LC_MESSAGES/mailman.po b/messages/sk/LC_MESSAGES/mailman.po index 648b67bc..b7933cb5 100755 --- a/messages/sk/LC_MESSAGES/mailman.po +++ b/messages/sk/LC_MESSAGES/mailman.po @@ -6988,7 +6988,7 @@ msgid "" "\n" "

                  In the text boxes below, add one address per line; start the\n" " line with a ^ character to designate a Python regular expression. When entering backslashes, do " "so\n" " as if you were using Python raw strings (i.e. you generally " @@ -7022,7 +7022,7 @@ msgstr "" "\n" "

                  V nižšie uvedených textových poliach zadajte jednu adresu\n" " na riadok. Ak chcete použiÅ¥ regulárny výraz Python, zaÄnite riadok znakom ^. Ak\n" " vkladáte backslash, urobte ako pri reÅ¥azcoch Python (spravidla\n" " použijete jeden backslash).\n" @@ -7820,7 +7820,7 @@ msgstr "Má byÅ¥ povolený tematický filter?" msgid "" "The topic filter categorizes each incoming email message\n" " according to regular\n" +" href=\"https://docs.python.org/3/library/re.html\">regular\n" " expression filters you specify below. If the message's\n" " Subject: or Keywords: header contains " "a\n" @@ -7843,7 +7843,7 @@ msgid "" " configuration variable." msgstr "" "Tematický filter zatriedi každú prijatú správu s použitím regulárnych\n" +" href=\"https://docs.python.org/3/library/re.html\">regulárnych\n" " výrazov, ktoré uvediete nižšie. Ak správa obsahuje v " "hlaviÄke \n" " Subject: alebo Keywords: výraz " @@ -10258,7 +10258,7 @@ msgid "" "Complete\n" "specifications are at:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "Address matches are case-insensitive, but case-preserved addresses are\n" "displayed.\n" diff --git a/messages/sl/LC_MESSAGES/mailman.po b/messages/sl/LC_MESSAGES/mailman.po index a0acc83c..5ed2a210 100755 --- a/messages/sl/LC_MESSAGES/mailman.po +++ b/messages/sl/LC_MESSAGES/mailman.po @@ -7729,7 +7729,7 @@ msgid "" "\n" "

                  In the text boxes below, add one address per line; start the\n" " line with a ^ character to designate a Python regular expression. When entering backslashes, do " "so\n" " as if you were using Python raw strings (i.e. you generally " @@ -7767,7 +7767,7 @@ msgstr "" "\n" "

                  V spodnja polja vnesite naslove, vsakega v svojo vrstico,\n" " ki jo zaènete z znakom ^, ki bo predstavljal Python regularni izraz. Pri vna¹anju po¹evnic nazaj, jih\n" " uporabljajte kot grobe Python nize (ponavadi se uporablja\n" " enojna po¹evnico nazaj).\n" @@ -8585,7 +8585,7 @@ msgstr "Ali naj bo filter za teme omogo msgid "" "The topic filter categorizes each incoming email message\n" " according to regular\n" +" href=\"https://docs.python.org/3/library/re.html\">regular\n" " expression filters you specify below. If the message's\n" " Subject: or Keywords: header contains " "a\n" @@ -8609,7 +8609,7 @@ msgid "" msgstr "" "Filter za teme razvrsti vsako prejeto sporoèilo glede na\n" " filtre\n" +" href=\"https://docs.python.org/3/library/re.html\">filtre\n" " regularnih izrazov, ki jih izberete spodaj. Èe polje\n" " Zadeva: ali Kljuène besede: vsebuje\n" " regularni izraz iz tematskega filtra, bo sporoèilo samodejno\n" @@ -11501,7 +11501,7 @@ msgid "" "Complete\n" "specifications are at:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "Address matches are case-insensitive, but case-preserved addresses are\n" "displayed.\n" @@ -11541,7 +11541,7 @@ msgstr "" "modul.\n" "Celotne specifikacije najdete na:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "Iskanje naslovov razlikuje velike in male èrke, prikazani pa so naslovi v\n" "originalni obliki.\n" @@ -16060,12 +16060,12 @@ msgstr "" # Mailman/Cgi/admin.py:460 #~ msgid "" #~ "

                • Find members by\n" -#~ " Python regular expression (regexp)
                  " #~ msgstr "" #~ "
                • Najdi èlane s pomoèjo\n" -#~ " regularnih izrazov Python (regexp)
                  " diff --git a/messages/sr/LC_MESSAGES/mailman.po b/messages/sr/LC_MESSAGES/mailman.po index 8d0d6997..a29f2867 100755 --- a/messages/sr/LC_MESSAGES/mailman.po +++ b/messages/sr/LC_MESSAGES/mailman.po @@ -5819,7 +5819,7 @@ msgid "" "\n" "

                  In the text boxes below, add one address per line; start the\n" " line with a ^ character to designate a Python regular expression. When entering backslashes, do " "so\n" " as if you were using Python raw strings (i.e. you generally " @@ -6421,7 +6421,7 @@ msgstr "" msgid "" "The topic filter categorizes each incoming email message\n" " according to regular\n" +" href=\"https://docs.python.org/3/library/re.html\">regular\n" " expression filters you specify below. If the message's\n" " Subject: or Keywords: header contains " "a\n" @@ -8475,7 +8475,7 @@ msgid "" "Complete\n" "specifications are at:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "Address matches are case-insensitive, but case-preserved addresses are\n" "displayed.\n" diff --git a/messages/sv/LC_MESSAGES/mailman.po b/messages/sv/LC_MESSAGES/mailman.po index 2f8bbf00..c2d378e8 100755 --- a/messages/sv/LC_MESSAGES/mailman.po +++ b/messages/sv/LC_MESSAGES/mailman.po @@ -8856,7 +8856,7 @@ msgid "" "\n" "

                  In the text boxes below, add one address per line; start the\n" " line with a ^ character to designate a Python regular expression. When entering backslashes, do " "so\n" " as if you were using Python raw strings (i.e. you generally " @@ -8892,7 +8892,7 @@ msgstr "" "säger.\n" "\n" "

                  I textrutorna nedan lägger du in en e-postadress per rad.\n" -"Du kan också lägga in Python regexp-uttryck.\n" "Börja i så fall raden med tecknet ^ för att markera att det är ett sådant " "uttryck.\n" @@ -9773,7 +9773,7 @@ msgstr "Ska msgid "" "The topic filter categorizes each incoming email message\n" " according to regular\n" +" href=\"https://docs.python.org/3/library/re.html\">regular\n" " expression filters you specify below. If the message's\n" " Subject: or Keywords: header contains " "a\n" @@ -9796,7 +9796,7 @@ msgid "" " configuration variable." msgstr "" "Ämnesfiltret kategoriserar varje e-postbrev som kommer till listan,\n" -"efter regexp-" +"efter regexp-" "uttrycken\n" "du skriver in nedan. Om fälten Subject: eller Keywords:\n" @@ -13094,7 +13094,7 @@ msgid "" "Complete\n" "specifications are at:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "Address matches are case-insensitive, but case-preserved addresses are\n" "displayed.\n" @@ -13134,7 +13134,7 @@ msgstr "" "Syntax för regexp-uttryck är som Perl5, när Pythons \"re\" modul används.\n" "Komplett specifikation finns på:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "Adresser jämförs utan skillnad på små och stora bokstäver, men visas med\n" "stora/små bokstäver som de stavas.\n" @@ -16647,7 +16647,7 @@ msgstr "" #~ msgid "" #~ "

                • Find members by\n" -#~ " Python regular expression (regexp)
                  " #~ msgstr "" diff --git a/messages/tr/LC_MESSAGES/mailman.po b/messages/tr/LC_MESSAGES/mailman.po index 62dce331..adc3fc9e 100755 --- a/messages/tr/LC_MESSAGES/mailman.po +++ b/messages/tr/LC_MESSAGES/mailman.po @@ -7099,7 +7099,7 @@ msgid "" "\n" "

                  In the text boxes below, add one address per line; start the\n" " line with a ^ character to designate a Python regular expression. When entering backslashes, do " "so\n" " as if you were using Python raw strings (i.e. you generally " @@ -7143,7 +7143,7 @@ msgstr "" "þekilde\n" " adres ekleyebilirsiniz; satýra bir ^ karakteri ile baþlayarak " "bir\n" -" Python\n" +" Python\n" " regular expression belirtebilirsiniz. Ters geri çizgi " "girerken\n" " Python ham dizgisi gibi girin (yani genel olarak tek bir ters " @@ -7981,7 +7981,7 @@ msgstr "Konu filtresi etkin mi yoksa devre d msgid "" "The topic filter categorizes each incoming email message\n" " according to regular\n" +" href=\"https://docs.python.org/3/library/re.html\">regular\n" " expression filters you specify below. If the message's\n" " Subject: or Keywords: header contains " "a\n" @@ -8004,7 +8004,7 @@ msgid "" " configuration variable." msgstr "" "Konu filtresi gelen her mesajý aþaðýda belirleyeceðiniz regular\n" +" href=\"https://docs.python.org/3/library/re.html\">regular\n" " expression filtrelerine göre kategorilere ayýrýr. Eðer " "mesajýn\n" " Konu: veya Keywords: baþlýðý bir " @@ -10347,7 +10347,7 @@ msgid "" "Complete\n" "specifications are at:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "Address matches are case-insensitive, but case-preserved addresses are\n" "displayed.\n" diff --git a/messages/uk/LC_MESSAGES/mailman.po b/messages/uk/LC_MESSAGES/mailman.po index 0f5b0db4..164bd2a7 100755 --- a/messages/uk/LC_MESSAGES/mailman.po +++ b/messages/uk/LC_MESSAGES/mailman.po @@ -6984,7 +6984,7 @@ msgid "" "\n" "

                  In the text boxes below, add one address per line; start the\n" " line with a ^ character to designate a Python regular expression. When entering backslashes, do " "so\n" " as if you were using Python raw strings (i.e. you generally " @@ -7019,7 +7019,7 @@ msgstr "" "\n" "

                  Ðижче у текÑтових полÑÑ…, додайте, по одній в Ñ€Ñдку, адреÑи.\n" " Якщо Ñ€Ñдок починаєтьÑÑ Ð· Ñимволу ^ - це означає регулÑрний вираз мови Python. Якщо ви вводите зворотній\n" " Ñлеш, робіть це так, Ñк у Ñимвольних Ñ€Ñдках Python (тобто\n" " проÑто викориÑтовуйте один Ñимвол зворотного Ñлешу).\n" @@ -7817,7 +7817,7 @@ msgstr "Чи потрібно вмикати фільтр тем?" msgid "" "The topic filter categorizes each incoming email message\n" " according to regular\n" +" href=\"https://docs.python.org/3/library/re.html\">regular\n" " expression filters you specify below. If the message's\n" " Subject: or Keywords: header contains " "a\n" @@ -7840,7 +7840,7 @@ msgid "" " configuration variable." msgstr "" "Фільтр тем виділÑÑ” категорії повідомлень відповідно до наведених нижче\n" -" фільтрів\n" +" фільтрів\n" " регулÑрних виразів. Якщо заголовки Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ \n" " Subject: чи Keywords: відповідають\n" " фільтру тем, Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð»Ð¾Ð³Ñ–Ñ‡Ð½Ð¾ розміщуютьÑÑ Ñƒ\n" @@ -10539,7 +10539,7 @@ msgid "" "Complete\n" "specifications are at:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "Address matches are case-insensitive, but case-preserved addresses are\n" "displayed.\n" @@ -10578,7 +10578,7 @@ msgstr "" "СинтакÑÐ¸Ñ Ñ€ÐµÐ³ÑƒÐ»Ñрних виразів Ñхожий на Perl5, викориÑтовуєтьÑÑ Ð¼Ð¾Ð´ÑƒÐ»ÑŒ re\n" "мови Python. Повна ÑÐ¿ÐµÑ†Ð¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ñ Ð½Ð°:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "Пошук ведетьÑÑ Ð±ÐµÐ· ÑƒÑ€Ð°Ñ…ÑƒÐ²Ð°Ð½Ð½Ñ Ñ€ÐµÐ³Ñ–Ñтру літер, але адреÑи виводÑтьÑÑ Ñ–Ð·\n" "збереженнÑм регіÑтру.\n" diff --git a/messages/vi/LC_MESSAGES/mailman.po b/messages/vi/LC_MESSAGES/mailman.po index d2645422..4a01310c 100755 --- a/messages/vi/LC_MESSAGES/mailman.po +++ b/messages/vi/LC_MESSAGES/mailman.po @@ -7025,7 +7025,7 @@ msgid "" "\n" "

                  In the text boxes below, add one address per line; start the\n" " line with a ^ character to designate a Python regular expression. When entering backslashes, do " "so\n" " as if you were using Python raw strings (i.e. you generally " @@ -7059,7 +7059,7 @@ msgstr "" "\n" "

                  Trong những trưá»ng bên dưới, hãy thêm\n" "má»™t địa chỉ thư trên má»—i dòng; bắt đầu dòng vá»›i dấu mÅ© ^\n" -"để ngụ ý cần thiết khá»›p vá»›i biểu thức chính quy Python.\n" "Khi gõ sổ chéo ngược, hãy dùng chỉ má»™t sổ chéo ngược đơn,\n" "như là dùng chuá»—i thô Python.\n" @@ -7869,7 +7869,7 @@ msgstr "Bá»™ lá»c chá»§ đỠnên được bật hoặc tắt?" msgid "" "The topic filter categorizes each incoming email message\n" " according to regular\n" +" href=\"https://docs.python.org/3/library/re.html\">regular\n" " expression filters you specify below. If the message's\n" " Subject: or Keywords: header contains " "a\n" @@ -7892,7 +7892,7 @@ msgid "" " configuration variable." msgstr "" "Bá»™ lá»c chá»§ đỠphân loại má»—i thư má»›i đến tùy theo những\n" -"bá»™ lá»c biểu thức chính " +"bá»™ lá»c biểu thức chính " "quy bạn ghi rõ bên dưới.\n" "Nếu dòng đầu Subject: (Chá»§ Ä‘á») hoặc Keywords:\n" "(Từ khoá) chứa Ä‘iá»u khá»›p bá»™ lá»c chá»§ đỠnào, thư được lá»c hợp lý vào má»™t\n" @@ -10623,7 +10623,7 @@ msgid "" "Complete\n" "specifications are at:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "Address matches are case-insensitive, but case-preserved addresses are\n" "displayed.\n" @@ -10664,7 +10664,7 @@ msgstr "" "Python.\n" "Có đặc tả hoàn toàn tại :\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "Việc khá»›p địa chỉ không phân biệt chữ hoa/thưá»ng, còn địa chỉ có\n" "chữ hoa/thưá»ng đã bảo tồn có phải được hiển thị.\n" diff --git a/messages/zh_CN/LC_MESSAGES/mailman.po b/messages/zh_CN/LC_MESSAGES/mailman.po index d869f762..66451ef0 100755 --- a/messages/zh_CN/LC_MESSAGES/mailman.po +++ b/messages/zh_CN/LC_MESSAGES/mailman.po @@ -6678,7 +6678,7 @@ msgid "" "\n" "

                  In the text boxes below, add one address per line; start the\n" " line with a ^ character to designate a Python regular expression. When entering backslashes, do " "so\n" " as if you were using Python raw strings (i.e. you generally " @@ -6713,7 +6713,7 @@ msgstr "" "\n" "

                  下é¢çš„æ–‡æœ¬æ¡†ä¸­ï¼Œæ¯è¡Œåªèƒ½æ·»åŠ ä¸€ä¸ªåœ°å€ã€‚行首的字符 ^ 说明åŽé¢çš„" "是\n" -" 一个Python正则表达å¼ã€‚åæ–œçº¿è¯·æŒ‰ç…§Python原始字符串的形å¼è¾“å…¥(也" "就是\n" " 说您åªéœ€è¦è¾“å…¥ä¸€ä¸ªåæ–œçº¿å°±å¯ä»¥äº†)。\n" @@ -7470,7 +7470,7 @@ msgstr "å¯ç”¨ä¸»é¢˜è¿‡æ»¤å™¨å—?" msgid "" "The topic filter categorizes each incoming email message\n" " according to regular\n" +" href=\"https://docs.python.org/3/library/re.html\">regular\n" " expression filters you specify below. If the message's\n" " Subject: or Keywords: header contains " "a\n" @@ -7493,7 +7493,7 @@ msgid "" " configuration variable." msgstr "" "ä¸»é¢˜è¿‡æ»¤å™¨æ ¹æ®æ‚¨åœ¨ä¸‹é¢æŒ‡å®šçš„\n" +" href=\"https://docs.python.org/3/library/re.html\">\n" " æ­£åˆ™è¡¨è¾¾å¼æ¥å¯¹æ”¶åˆ°çš„邮件进行分类。如果æŸé‚®ä»¶çš„Subject:" "\n" " 头或Keywords:头匹é…一个主题过滤器,那么此邮件将在逻" @@ -10100,7 +10100,7 @@ msgid "" "Complete\n" "specifications are at:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "Address matches are case-insensitive, but case-preserved addresses are\n" "displayed.\n" @@ -10137,7 +10137,7 @@ msgstr "" "\n" "正则表达å¼è¯­æ³•类似于Perl5,使用了Pythonçš„re模å—。完整的定义å‚è§:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "地å€åŒ¹é…是大å°å†™æ— å…³çš„,但是显示的地å€å°†ä¿ç•™åŽŸæœ‰å½¢å¼ã€‚\n" "\n" diff --git a/messages/zh_TW/LC_MESSAGES/mailman.po b/messages/zh_TW/LC_MESSAGES/mailman.po index 7a661c8b..52361465 100755 --- a/messages/zh_TW/LC_MESSAGES/mailman.po +++ b/messages/zh_TW/LC_MESSAGES/mailman.po @@ -6256,7 +6256,7 @@ msgid "" "\n" "

                  In the text boxes below, add one address per line; start the\n" " line with a ^ character to designate a Python regular expression. When entering backslashes, do " "so\n" " as if you were using Python raw strings (i.e. you generally " @@ -6281,7 +6281,7 @@ msgstr "" "a>çš„éŽæ¿¾ã€‚\n" "\n" "

                  在下é¢çš„æ–‡å­—框中æ¯è¡Œå¡«å…¥ä¸€å€‹åœ°å€ï¼Œåœ¨è¡Œé¦–寫 ^ 字元代表是\n" -"Python æ­£è¦è¡¨ç¤ºå¼Python æ­£è¦è¡¨ç¤ºå¼ã€‚\n" "è¦å¡«å…¥å斜線ã€è­¯è¨»ï¼šâ•²ã€‘çš„è©±ï¼Œè«‹ç›´æŽ¥æ‰“åæ–œç·šå³å¯ï¼Œä¸ç”¨å¤šåР䏀個忖œç·šé€¸å‡ºã€‚\n" "\n" @@ -6975,7 +6975,7 @@ msgstr "" msgid "" "The topic filter categorizes each incoming email message\n" " according to regular\n" +" href=\"https://docs.python.org/3/library/re.html\">regular\n" " expression filters you specify below. If the message's\n" " Subject: or Keywords: header contains " "a\n" @@ -9172,7 +9172,7 @@ msgid "" "Complete\n" "specifications are at:\n" "\n" -"https://docs.python.org/2/library/re.html\n" +"https://docs.python.org/3/library/re.html\n" "\n" "Address matches are case-insensitive, but case-preserved addresses are\n" "displayed.\n" diff --git a/misc/sitelist.cfg b/misc/sitelist.cfg index 73c25da2..992b9007 100644 --- a/misc/sitelist.cfg +++ b/misc/sitelist.cfg @@ -248,7 +248,7 @@ obscure_addresses = 1 # #

                  In the text boxes below, add one address per line; start the line # with a ^ character to designate a Python regular +# "https://docs.python.org/3/library/re.html" >Python regular # expression. When entering backslashes, do so as if you were using # Python raw strings (i.e. you generally just use a single backslash). # diff --git a/templates/ar/headfoot.html b/templates/ar/headfoot.html index ba142058..f6b20566 100644 --- a/templates/ar/headfoot.html +++ b/templates/ar/headfoot.html @@ -1,4 +1,4 @@ -يمكن لهذا النص أن يتضمنمحار٠تنسيق بايثون +يمكن لهذا النص أن يتضمنمحار٠تنسيق بايثون والتي سيتم حلها مع Ù…ÙˆØ§ØµÙØ§Øª القائمة. قائمة الاستبدالات المسموحة هي:

                    diff --git a/templates/ast/headfoot.html b/templates/ast/headfoot.html index 72d71953..9273eefd 100644 --- a/templates/ast/headfoot.html +++ b/templates/ast/headfoot.html @@ -1,5 +1,5 @@ Esti testu puede incluyir -cadenes de formatu Python que sustituyiránse polos atributos de la llista. La llista de sustituciones permitíes son: +cadenes de formatu Python que sustituyiránse polos atributos de la llista. La llista de sustituciones permitíes son:
                    • real_name - El nome 'presentable' de la llista. Davezu ye'l nome de la llista cola primer lletra en mayúscula. diff --git a/templates/ca/headfoot.html b/templates/ca/headfoot.html index ce0e2c13..345f36e7 100644 --- a/templates/ca/headfoot.html +++ b/templates/ca/headfoot.html @@ -1,5 +1,5 @@ Aquest text pot incloure -cadenes +cadenes de format del Python, les quals es resoldran com atributs de la llista. La llista de les substitucions permeses és la següent: diff --git a/templates/cs/headfoot.html b/templates/cs/headfoot.html index bf4cad6e..119dcef3 100644 --- a/templates/cs/headfoot.html +++ b/templates/cs/headfoot.html @@ -1,5 +1,5 @@ Tento text mù¾e obsahovat formátovací +href="https://docs.python.org/3/library/stdtypes.html#string-formatting-operations">formátovací pøíkazy Pythonu které se nahrazují odpovídajícími hodnotami, pøi generování stránky. Nejdùle¾itìjší promìnné jsou: diff --git a/templates/da/headfoot.html b/templates/da/headfoot.html index 042fa535..b25cdf47 100644 --- a/templates/da/headfoot.html +++ b/templates/da/headfoot.html @@ -1,6 +1,6 @@ Teksten kan indeholde formateringskoder som udskiftes med værdier fra listens opsætning. For detaljer, se -Pythons +Pythons formateringsregler (engelsk). Gyldige koder er:
                        diff --git a/templates/de/headfoot.html b/templates/de/headfoot.html index 853b8ba6..5377040c 100644 --- a/templates/de/headfoot.html +++ b/templates/de/headfoot.html @@ -1,6 +1,6 @@ Dieser Text kann sogenannte -" +" Python format strings" enthalten, die durch listenspezifische Werte ersetzt werden. diff --git a/templates/el/headfoot.html b/templates/el/headfoot.html index 61b508cc..591f7bf7 100755 --- a/templates/el/headfoot.html +++ b/templates/el/headfoot.html @@ -1,6 +1,6 @@ Áõôü ôï êåßìåíï ìðïñåß íá ðåñéÝ÷åé -÷áñáêôÞñåò óå +÷áñáêôÞñåò óå Python format ïé ïðïßïé áíáëýïíôáé ìå ôç ÷ñÞóç ìéáò ëßóôáò ôéìþí. Ç ëßóôá ôùí õðïêáôÜóôáôùí ðïõ åðéôñÝðïíôáé åßíáé: diff --git a/templates/en/headfoot.html b/templates/en/headfoot.html index 6bb9a75a..5baff562 100644 --- a/templates/en/headfoot.html +++ b/templates/en/headfoot.html @@ -1,5 +1,5 @@ This text can include -Python +Python format strings which are resolved against list attributes. The list of substitutions allowed are: diff --git a/templates/es/headfoot.html b/templates/es/headfoot.html index af0032c1..135122e8 100644 --- a/templates/es/headfoot.html +++ b/templates/es/headfoot.html @@ -1,5 +1,5 @@ Este texto puede incluir - + cadenas de formato Python que serán sustituidas por los atributos de la lista. La lista de diff --git a/templates/et/headfoot.html b/templates/et/headfoot.html index 93a8d419..89681547 100644 --- a/templates/et/headfoot.html +++ b/templates/et/headfoot.html @@ -1,5 +1,5 @@ See tekst võib sisaldada -Pythoni +Pythoni vormingustringe, mis asendatakse selle listi atribuutidega. Lubatud asendused on järgmised:
                          diff --git a/templates/eu/headfoot.html b/templates/eu/headfoot.html index d493d9a5..5d82f204 100644 --- a/templates/eu/headfoot.html +++ b/templates/eu/headfoot.html @@ -1,5 +1,5 @@ Testu honek -Python +Python kateak izan ditzake; zerrendako atributuen bidez ordezkatuko dira. Onartzen diren ordezkapenak hauexek dira: diff --git a/templates/fi/headfoot.html b/templates/fi/headfoot.html index 00fc9fc4..4d66e099 100644 --- a/templates/fi/headfoot.html +++ b/templates/fi/headfoot.html @@ -1,5 +1,5 @@
                          -Teksti voi sisältää muotoiltuja +Teksti voi sisältää muotoiltuja merkkijonoja. Muotoilussa käytettäviä arvoja ovat:
                            diff --git a/templates/fr/headfoot.html b/templates/fr/headfoot.html index 684ebfce..0c0853e2 100644 --- a/templates/fr/headfoot.html +++ b/templates/fr/headfoot.html @@ -1,5 +1,5 @@ Ce texte peut contenir des formats +href="https://docs.python.org/3/library/stdtypes.html#string-formatting-operations">des formats de chaînes Python qui sont remplacées par les attributs de la liste. La liste des substitutions valides est:
                              diff --git a/templates/gl/headfoot.html b/templates/gl/headfoot.html index ca5b147c..6335601d 100644 --- a/templates/gl/headfoot.html +++ b/templates/gl/headfoot.html @@ -1,5 +1,5 @@ Este texto pode incluír - + cadeas de formato Python que se substituirán polos atributos da rolda. A relación de diff --git a/templates/he/headfoot.html b/templates/he/headfoot.html index 9efbd51c..1c3d555a 100644 --- a/templates/he/headfoot.html +++ b/templates/he/headfoot.html @@ -1,5 +1,5 @@ טקסט ×–×” יכול לכלול -מחרוזות +מחרוזות בפורמ×ט Python ×שר מפוענחות מול תכונות הרשימה. רשימת תתי-×”×ž×¦×‘×™× ×”×ž×•×ª×¨×™×:
                                diff --git a/templates/hr/headfoot.html b/templates/hr/headfoot.html index 750c1a26..c10d7ea6 100644 --- a/templates/hr/headfoot.html +++ b/templates/hr/headfoot.html @@ -1,5 +1,5 @@ Ovaj tekst mo¾e ukljuèiti -Python +Python format stringova koji su razluèeni prema atributima liste. Lista dozvoljenih zamjena je: diff --git a/templates/hu/headfoot.html b/templates/hu/headfoot.html index 97a2be3c..db0007b4 100644 --- a/templates/hu/headfoot.html +++ b/templates/hu/headfoot.html @@ -1,4 +1,4 @@ -Ez a szöveg olyan Python-típusú változókat mutat be, amelyekkel a lista egyes jellemzőire lehet hivatkozni. +Ez a szöveg olyan Python-típusú változókat mutat be, amelyekkel a lista egyes jellemzőire lehet hivatkozni. Az alábbi változók jelentése:
                                  diff --git a/templates/ia/headfoot.html b/templates/ia/headfoot.html index 4a20d11c..9964711e 100644 --- a/templates/ia/headfoot.html +++ b/templates/ia/headfoot.html @@ -1,5 +1,5 @@ Iste texto pote includer -stringas +stringas in formato Python que es completate per un lista de attributos. Le lista de substitutiones permittite es: diff --git a/templates/it/headfoot.html b/templates/it/headfoot.html index 75899db5..ba2bdb90 100644 --- a/templates/it/headfoot.html +++ b/templates/it/headfoot.html @@ -1,5 +1,5 @@ Questo testo può includere Stringhe +href="https://docs.python.org/3/library/stdtypes.html#string-formatting-operations">Stringhe di formato Python che saranno sostituite automaticamente dai valori degli attributi della lista. L'elenco degli attributi permessi è: diff --git a/templates/ja/headfoot.html b/templates/ja/headfoot.html index d25f2769..2262d6c1 100644 --- a/templates/ja/headfoot.html +++ b/templates/ja/headfoot.html @@ -1,28 +1,28 @@ -¤³¤Î¥Æ¥­¥¹¥È¤Ë¤Ï¡¢¥ê¥¹¥È¤Î°À­¤ÈÃÖ¤­´¹¤¨¤é¤ì¤ë - -Python¥Õ¥©¡¼¥Þ¥Ã¥Èʸ»úÎó¤òÁÞÆþ¤¹¤ë¤³¤È¤¬¤Ç¤­¤Þ¤¹¡£ -( -ÆüËܸìÈÇ) -¤³¤³¤Ç»È¤¨¤ëÃÖ´¹Ê¸»úÎó¤Ï°Ê²¼¤ÎÄ̤ê¤Ç¤¹¡£ +���Υƥ����Ȥˤϡ��ꥹ�Ȥ�°�����֤��������� + +Python�ե����ޥå�ʸ�������������뤳�Ȥ��Ǥ��ޤ��� +( +ܸ) +ǻȤ����ִ�ʸ����ϰʲ����̤�Ǥ���
                                    -
                                  • real_name - ¥ê¥¹¥È¤Î¡Ö¤­¤ì¤¤¤Ê¡×̾Á°; - ÉáÄ̤ÏÂçʸ»ú¤È¾®Ê¸»ú¤òº®¤¼¤ÆÉ½µ­¤µ¤ì¤¿¥ê¥¹¥È¤Î̾Á°¤Ë¤Ê¤ê¤Þ¤¹¡£ +
                                  • real_name - �ꥹ�ȤΡ֤��줤�ʡ�̾��; + ���̤���ʸ���Ⱦ�ʸ���òº®¤ï¿½ï¿½ï¿½É½ï¿½ï¿½ï¿½ï¿½ï¿½ì¤¿ï¿½ê¥¹ï¿½È¤ï¿½Ì¾ï¿½ï¿½ï¿½Ë¤Ê¤ï¿½Þ¤ï¿½ï¿½ï¿½ -
                                  • list_name - URL ¤ÎÃæ¤Ç¥ê¥¹¥È¤ò¼¨¤¹Ì¾Á°¡£ - Âçʸ»ú¤È¾®Ê¸»ú¤Î°ã¤¤¤Ï¶èÊ̤µ¤ì¤Þ¤¹¡£(¸ß´¹À­¤Î¤¿¤á¡¢ - _internal_name¤Ç¤âƱ¤¸¤Ë¤Ê¤ê¤Þ¤¹) +
                                  • list_name - URL ����ǥꥹ�Ȥò¼¨¤ï¿½Ì¾ï¿½ï¿½ï¿½ï¿½ + ��ʸ���Ⱦ�ʸ���ΰ㤤�϶��̤���ޤ���(�ߴ����Τ��ᡢ + _internal_name�Ǥ�Ʊ���ˤʤ�ޤ�) -
                                  • host_name - Mailman¤¬²ÔƯ¤·¤Æ¤¤¤ë¥Û¥¹¥È¤Î - ´°Á´¥É¥á¥¤¥ó̾(FQDN)¡£ +
                                  • host_name - Mailman����Ư���Ƥ���ۥ��Ȥ� + �����ɥᥤ��̾(FQDN)�� -
                                  • web_page_url - Mailman ¤Î´ðËÜ URL¡£ - ¤¿¤È¤¨¤Ð¡¢¤³¤ÎURL¤Ë listinfo/%(list_name)s ¤òÉÕ¤±²Ã¤¨¤ë¤È - ¥á¡¼¥ê¥ó¥°¥ê¥¹¥È¤Î°ÆÆâ¥Ú¡¼¥¸¤Ë¤Ê¤ê¤Þ¤¹¡£ +
                                  • web_page_url - Mailman ��� URL�� + ���Ȥ��С�����URL�� listinfo/%(list_name)s ���դ��ä���� + �᡼��󥰥ꥹ�Ȥΰ���ڡ����ˤʤ�ޤ��� -
                                  • description - ¤³¤Î¥á¡¼¥ê¥ó¥°¥ê¥¹¥È¤Î´Ê·é¤ÊÀâÌÀ¡£ +
                                  • description - ���Υ᡼��󥰥ꥹ�Ȥδʷ�������� -
                                  • info - ¤³¤Î¥á¡¼¥ê¥ó¥°¥ê¥¹¥È¤ÎŤ¤ÀâÌÀ¡£ +
                                  • info - ���Υ᡼��󥰥ꥹ�Ȥ�Ĺ�������� -
                                  • cgiext - CGI¥¹¥¯¥ê¥×¥È¤Î³ÈÄ¥»Ò¡£ +
                                  • cgiext - CGI������ץȤγ�ĥ�ҡ�
                                  diff --git a/templates/ko/headfoot.html b/templates/ko/headfoot.html index b5cfc6d9..931a8960 100644 --- a/templates/ko/headfoot.html +++ b/templates/ko/headfoot.html @@ -1,6 +1,6 @@ ÀÌ ¹®¼­´Â ¸ÞÀϸµ ¸®½ºÆ®¿¡ ¿¹¾àµÇ¾î Àִ Ư¼ºÀÇ %(attribute)s Çü½ÄÀ» Æ÷ÇÔÇϰí ÀÖ½À´Ï´Ù. ´õ ÀÚ¼¼ÇÑ ¼³¸íÀº -PythonÀÇ +PythonÀÇ ¹®ÀÚ¿­ Çü½Ä ±ÔÄ¢À» º¸½Ã±â ¹Ù¶ø´Ï´Ù. ¸î¸î À¯¿ëÇÑ ¼Ó¼ºÀº ¾Æ·¡¿Í °°½À´Ï´Ù.
                                    diff --git a/templates/lt/headfoot.html b/templates/lt/headfoot.html index 12d9e688..4cf9d198 100644 --- a/templates/lt/headfoot.html +++ b/templates/lt/headfoot.html @@ -1,5 +1,5 @@ Á šá tekstà gali bûti átrauktos -Python +Python formato eilutës kurios yra apdorojamos pagal poþymius. Èia pateikiame pakeitimø sàrašà: diff --git a/templates/nl/headfoot.html b/templates/nl/headfoot.html index 6b38440b..8c37fe9b 100644 --- a/templates/nl/headfoot.html +++ b/templates/nl/headfoot.html @@ -1,5 +1,5 @@ De tekst mag de volgende -Python +Python format strings bevatten, die automatisch herleid worden naar attributen die elders gedefinieerd zijn voor deze lijst. De mogelijke vervangende strings zijn: diff --git a/templates/no/headfoot.html b/templates/no/headfoot.html index 70d93b1f..d1fd26e9 100644 --- a/templates/no/headfoot.html +++ b/templates/no/headfoot.html @@ -1,6 +1,6 @@ Teksten kan inneholde formateringskoder som byttes ut med verdier fra listens oppsett. For detaljer, se -Pythons +Pythons formateringsregler (engelsk). Gyldige koder er:
                                      diff --git a/templates/pt/headfoot.html b/templates/pt/headfoot.html index fdbb83fb..0e650a57 100644 --- a/templates/pt/headfoot.html +++ b/templates/pt/headfoot.html @@ -1,5 +1,5 @@ Este texto pode incluir -strings +strings de formatação em Python as quais são resolvidas em relação à lista de atributos ??????? atributos da lista. As substituições permitidas são: diff --git a/templates/pt_BR/headfoot.html b/templates/pt_BR/headfoot.html index 32a34114..fd9d854d 100644 --- a/templates/pt_BR/headfoot.html +++ b/templates/pt_BR/headfoot.html @@ -1,5 +1,5 @@ Este texto pode incluir -strings no formato +strings no formato do Python que são resolvidos contra atributos da lista. a lista de substituições permitidas são: diff --git a/templates/ro/headfoot.html b/templates/ro/headfoot.html index bf401f0e..20599050 100644 --- a/templates/ro/headfoot.html +++ b/templates/ro/headfoot.html @@ -1,5 +1,5 @@ Acest text poate include -ÅŸiruri +ÅŸiruri de caractere formatate Python care sunt rezolvate vis-a-ais de atributele listei. Lista substituÅ£iilor permise este: diff --git a/templates/ru/headfoot.html b/templates/ru/headfoot.html index 542d1263..9b068868 100644 --- a/templates/ru/headfoot.html +++ b/templates/ru/headfoot.html @@ -1,5 +1,5 @@ Этот текÑÑ‚ может включать Ñтроки +href="https://docs.python.org/3/library/stdtypes.html#string-formatting-operations">Ñтроки подÑтановки Python (текÑÑ‚ по-английÑки), которые заменÑÑŽÑ‚ÑÑ Ñледующими атрибутами ÑпиÑка раÑÑылки: diff --git a/templates/sk/headfoot.html b/templates/sk/headfoot.html index 07351cb3..f8a1077d 100644 --- a/templates/sk/headfoot.html +++ b/templates/sk/headfoot.html @@ -1,5 +1,5 @@ Tento text môže obsahovaÅ¥ formátovacie príkazy jazyka Python, ktoré sa nahradia príslušnými hodnotami pri vytváraní stránky. Nejdôležitejšie premenné sú: diff --git a/templates/sl/headfoot.html b/templates/sl/headfoot.html index 3f9dfbd5..2bcdc5a3 100644 --- a/templates/sl/headfoot.html +++ b/templates/sl/headfoot.html @@ -1,5 +1,5 @@ To besedilo lahko vkljuèuje -Python +Python nize, ki jih uporabljamo namesto atributov seznama. Seznam mo¾nih nadomestil: diff --git a/templates/sr/headfoot.html b/templates/sr/headfoot.html index 3f38ece6..86bf8115 100644 --- a/templates/sr/headfoot.html +++ b/templates/sr/headfoot.html @@ -1,4 +1,4 @@ -Овај текÑÑ‚ може да Ñадржи Python-ово +Овај текÑÑ‚ може да Ñадржи Python-ово форматирање које Ñе утврђује на оÑнову атрибута лиÑта. Дозвољене замјене лиÑте Ñу:
                                        diff --git a/templates/sv/headfoot.html b/templates/sv/headfoot.html index 8f86d0aa..db1104b4 100644 --- a/templates/sv/headfoot.html +++ b/templates/sv/headfoot.html @@ -1,4 +1,4 @@ -Den här texten kan innehålla formateringskoder som byts ut mot värden från listans uppsättning. För detaljer, se Pythons formateringsregler (engelska). Giltiga koder är: +Den här texten kan innehålla formateringskoder som byts ut mot värden från listans uppsättning. För detaljer, se Pythons formateringsregler (engelska). Giltiga koder är:
                                        • real_name - Listans formaterade namn, vanligtvis listnamnet med stor initial eller stora bokstäver rakt igenom.
                                        • list_name - Listens namn, såsom det används i URL:er, där det har betydelse om namnet stavas med stora eller små bokstäver. diff --git a/templates/tr/headfoot.html b/templates/tr/headfoot.html index 80984bbe..ae1390b5 100644 --- a/templates/tr/headfoot.html +++ b/templates/tr/headfoot.html @@ -1,5 +1,5 @@ Bu yazý, liste özniteliklerine karþý çözülecek -Python +Python biçim dizgileri içerebilir. Ýzin verilen yerine koymalarýn listesi þunlardýr: diff --git a/templates/uk/headfoot.html b/templates/uk/headfoot.html index 16332618..fecd2170 100644 --- a/templates/uk/headfoot.html +++ b/templates/uk/headfoot.html @@ -1,5 +1,5 @@ Цей текÑÑ‚ може включати - + форматовані Ñ€Ñдки, Ñкі пов'Ñзані з ознаками ÑпиÑку. Перелік дозволених ознак: diff --git a/templates/vi/headfoot.html b/templates/vi/headfoot.html index ef218df1..37a432dd 100644 --- a/templates/vi/headfoot.html +++ b/templates/vi/headfoot.html @@ -1,5 +1,5 @@ Äoạn này có thể gồm -chuá»—i dạng thức Python mà được khá»›p vá»›i thuá»™c tính há»™p thư chung. Danh sách các Ä‘iá»u thay thế có thể là: +chuá»—i dạng thức Python mà được khá»›p vá»›i thuá»™c tính há»™p thư chung. Danh sách các Ä‘iá»u thay thế có thể là:
                                          • real_name — ten « xinh » cá»§a há»™p thư chung đó, thưá»ng là tên há»™p thư vá»›i chữ hoa đầu từ. diff --git a/templates/zh_CN/headfoot.html b/templates/zh_CN/headfoot.html index 6653ceeb..9416d4a0 100644 --- a/templates/zh_CN/headfoot.html +++ b/templates/zh_CN/headfoot.html @@ -1,5 +1,5 @@ 此文本å¯ä»¥åŒ…括 -Python +Python æ ¼å¼å­—符串,它们将根æ®åˆ—表属性æ¥è¢«è§£é‡Šã€‚å¯ç”¨çš„æ›¿æ¢æœ‰ï¼š
                                            • real_name - 列表的确切åå­—; 通常列表åå­—è¦å¤§å†™ã€‚ diff --git a/templates/zh_TW/headfoot.html b/templates/zh_TW/headfoot.html index ba34daa5..2f812831 100644 --- a/templates/zh_TW/headfoot.html +++ b/templates/zh_TW/headfoot.html @@ -1,6 +1,6 @@ 本文å¯åŒ…å« %(attribute)s æ ¼å¼å­—ä¸²ï¼Œå…¶å¯æ›¿æ›ç‚ºé€šä¿¡è«–壇 相應的屬性。詳情請åƒç…§ -Python +Python æ ¼å¼åŒ–字串è¦å‰‡. é è¨­å±¬æ€§å¦‚下:
                                                From 864aec710fbdc78ba19e807d73ea4ff3166e9982 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Wed, 21 May 2025 20:52:54 -0400 Subject: [PATCH 645/748] Modernize HTML generation in Python codebase and templates to use HTML5, semantic tags, and modern CSS --- Mailman/Archiver/HyperArch.py | 4 +- Mailman/Cgi/admin.py | 34 +- Mailman/Cgi/admindb.py | 70 +- Mailman/Cgi/create.py | 113 +- Mailman/Cgi/listinfo.py | 19 +- Mailman/Cgi/options.py | 86 +- Mailman/Cgi/rmlist.py | 64 +- Mailman/HTMLFormatter.py | 37 +- Mailman/htmlformat.py | 244 +- doc/mailman-admin/about.html | 119 +- doc/mailman-admin/contents.html | 112 +- doc/mailman-admin/front.html | 111 +- doc/mailman-admin/general-personality.html | 128 +- doc/mailman-admin/index.html | 107 +- doc/mailman-admin/mailman-admin.html | 107 +- doc/mailman-admin/node11.html | 125 +- doc/mailman-admin/node12.html | 107 +- doc/mailman-admin/node13.html | 124 +- doc/mailman-admin/node14.html | 124 +- doc/mailman-admin/node15.html | 106 +- doc/mailman-admin/node16.html | 113 +- doc/mailman-admin/node17.html | 107 +- doc/mailman-admin/node18.html | 159 +- doc/mailman-admin/node19.html | 141 +- doc/mailman-admin/node20.html | 132 +- doc/mailman-admin/node21.html | 128 +- doc/mailman-admin/node23.html | 112 +- doc/mailman-admin/node24.html | 109 +- doc/mailman-admin/node25.html | 122 +- doc/mailman-admin/node26.html | 112 +- doc/mailman-admin/node27.html | 107 +- doc/mailman-admin/node28.html | 104 +- doc/mailman-admin/node29.html | 104 +- doc/mailman-admin/node3.html | 115 +- doc/mailman-admin/node30.html | 105 +- doc/mailman-admin/node31.html | 104 +- doc/mailman-admin/node32.html | 104 +- doc/mailman-admin/node33.html | 104 +- doc/mailman-admin/node34.html | 108 +- doc/mailman-admin/node35.html | 106 +- doc/mailman-admin/node4.html | 121 +- doc/mailman-admin/node5.html | 107 +- doc/mailman-admin/node6.html | 107 +- doc/mailman-admin/node7.html | 114 +- doc/mailman-admin/node8.html | 119 +- doc/mailman-admin/node9.html | 115 +- doc/mailman-admin/sender-filters.html | 126 +- doc/mailman-install/about.html | 115 +- doc/mailman-install/bsd-issues.html | 106 +- doc/mailman-install/building.html | 113 +- doc/mailman-install/create-install-dir.html | 120 +- doc/mailman-install/customizing.html | 117 +- doc/mailman-install/exim3-transport.html | 106 +- doc/mailman-install/front.html | 131 +- doc/mailman-install/index.html | 108 +- doc/mailman-install/mail-server.html | 122 +- doc/mailman-install/mailman-install.html | 108 +- doc/mailman-install/node10.html | 109 +- doc/mailman-install/node12.html | 113 +- doc/mailman-install/node15.html | 103 +- doc/mailman-install/node16.html | 112 +- doc/mailman-install/node17.html | 111 +- doc/mailman-install/node18.html | 104 +- doc/mailman-install/node2.html | 130 +- doc/mailman-install/node20.html | 104 +- doc/mailman-install/node21.html | 103 +- doc/mailman-install/node22.html | 103 +- doc/mailman-install/node23.html | 103 +- doc/mailman-install/node24.html | 111 +- doc/mailman-install/node25.html | 105 +- doc/mailman-install/node26.html | 104 +- doc/mailman-install/node27.html | 104 +- doc/mailman-install/node28.html | 103 +- doc/mailman-install/node29.html | 104 +- doc/mailman-install/node3.html | 111 +- doc/mailman-install/node30.html | 101 +- doc/mailman-install/node31.html | 119 +- doc/mailman-install/node32.html | 116 +- doc/mailman-install/node33.html | 105 +- doc/mailman-install/node34.html | 101 +- doc/mailman-install/node36.html | 103 +- doc/mailman-install/node37.html | 103 +- doc/mailman-install/node38.html | 101 +- doc/mailman-install/node4.html | 113 +- doc/mailman-install/node41.html | 119 +- doc/mailman-install/node42.html | 108 +- doc/mailman-install/node43.html | 104 +- doc/mailman-install/node44.html | 120 +- doc/mailman-install/node45.html | 136 +- doc/mailman-install/node47.html | 109 +- doc/mailman-install/node48.html | 112 +- doc/mailman-install/node50.html | 117 +- doc/mailman-install/node7.html | 132 +- doc/mailman-install/node8.html | 101 +- doc/mailman-install/node9.html | 114 +- doc/mailman-install/postfix-integration.html | 123 +- doc/mailman-install/postfix-virtual.html | 116 +- doc/mailman-install/qmail-issues.html | 132 +- doc/mailman-install/site-list.html | 107 +- doc/mailman-install/troubleshooting.html | 148 +- doc/mailman-member-es/about.html | 121 +- doc/mailman-member-es/contents.html | 128 +- doc/mailman-member-es/front.html | 113 +- doc/mailman-member-es/index.html | 125 +- doc/mailman-member-es/mailman-member-es.html | 127 +- doc/mailman-member-es/node10.html | 123 +- doc/mailman-member-es/node11.html | 110 +- doc/mailman-member-es/node12.html | 114 +- doc/mailman-member-es/node13.html | 116 +- doc/mailman-member-es/node14.html | 117 +- doc/mailman-member-es/node15.html | 118 +- doc/mailman-member-es/node16.html | 124 +- doc/mailman-member-es/node17.html | 150 +- doc/mailman-member-es/node18.html | 118 +- doc/mailman-member-es/node19.html | 111 +- doc/mailman-member-es/node20.html | 121 +- doc/mailman-member-es/node21.html | 121 +- doc/mailman-member-es/node22.html | 116 +- doc/mailman-member-es/node23.html | 119 +- doc/mailman-member-es/node24.html | 125 +- doc/mailman-member-es/node25.html | 120 +- doc/mailman-member-es/node26.html | 111 +- doc/mailman-member-es/node27.html | 125 +- doc/mailman-member-es/node28.html | 123 +- doc/mailman-member-es/node29.html | 117 +- doc/mailman-member-es/node3.html | 114 +- doc/mailman-member-es/node30.html | 174 +- doc/mailman-member-es/node31.html | 116 +- doc/mailman-member-es/node32.html | 113 +- doc/mailman-member-es/node33.html | 115 +- doc/mailman-member-es/node34.html | 109 +- doc/mailman-member-es/node35.html | 114 +- doc/mailman-member-es/node36.html | 112 +- doc/mailman-member-es/node37.html | 121 +- doc/mailman-member-es/node38.html | 117 +- doc/mailman-member-es/node39.html | 111 +- doc/mailman-member-es/node4.html | 107 +- doc/mailman-member-es/node40.html | 124 +- doc/mailman-member-es/node41.html | 254 +- doc/mailman-member-es/node42.html | 192 +- doc/mailman-member-es/node5.html | 106 +- doc/mailman-member-es/node6.html | 105 +- doc/mailman-member-es/node7.html | 108 +- doc/mailman-member-es/node8.html | 114 +- doc/mailman-member-es/node9.html | 139 +- doc/mailman-member/about.html | 119 +- doc/mailman-member/contents.html | 126 +- doc/mailman-member/front.html | 111 +- doc/mailman-member/index.html | 121 +- doc/mailman-member/mailman-member.html | 121 +- doc/mailman-member/node10.html | 122 +- doc/mailman-member/node11.html | 111 +- doc/mailman-member/node12.html | 114 +- doc/mailman-member/node13.html | 118 +- doc/mailman-member/node14.html | 117 +- doc/mailman-member/node15.html | 117 +- doc/mailman-member/node16.html | 124 +- doc/mailman-member/node17.html | 151 +- doc/mailman-member/node18.html | 118 +- doc/mailman-member/node19.html | 111 +- doc/mailman-member/node20.html | 121 +- doc/mailman-member/node21.html | 121 +- doc/mailman-member/node22.html | 115 +- doc/mailman-member/node23.html | 121 +- doc/mailman-member/node24.html | 126 +- doc/mailman-member/node25.html | 121 +- doc/mailman-member/node26.html | 111 +- doc/mailman-member/node27.html | 125 +- doc/mailman-member/node28.html | 124 +- doc/mailman-member/node29.html | 119 +- doc/mailman-member/node3.html | 118 +- doc/mailman-member/node30.html | 171 +- doc/mailman-member/node31.html | 117 +- doc/mailman-member/node32.html | 113 +- doc/mailman-member/node33.html | 115 +- doc/mailman-member/node34.html | 109 +- doc/mailman-member/node35.html | 115 +- doc/mailman-member/node36.html | 111 +- doc/mailman-member/node37.html | 119 +- doc/mailman-member/node38.html | 115 +- doc/mailman-member/node39.html | 108 +- doc/mailman-member/node4.html | 107 +- doc/mailman-member/node40.html | 122 +- doc/mailman-member/node41.html | 252 +- doc/mailman-member/node42.html | 190 +- doc/mailman-member/node5.html | 106 +- doc/mailman-member/node6.html | 105 +- doc/mailman-member/node7.html | 108 +- doc/mailman-member/node8.html | 114 +- doc/mailman-member/node9.html | 138 +- messages/ja/doc/Defaults.py.in | 20401 ++++++++++++++++- templates/ar/admindbdetails.html | 73 +- templates/ar/admindbpreamble.html | 66 +- templates/ar/admindbsummary.html | 66 +- templates/ar/admlogin.html | 111 +- templates/ar/archidxentry.html | 72 +- templates/ar/archidxfoot.html | 92 +- templates/ar/archidxhead.html | 95 +- templates/ar/archlistend.html | 65 +- templates/ar/archliststart.html | 72 +- templates/ar/archtoc.html | 84 +- templates/ar/archtocentry.html | 82 +- templates/ar/archtocnombox.html | 84 +- templates/ar/article.html | 77 +- templates/ar/emptyarchive.html | 85 +- templates/ar/headfoot.html | 69 +- templates/ar/listinfo.html | 320 +- templates/ar/options.html | 441 +- templates/ar/private.html | 145 +- templates/ar/roster.html | 159 +- templates/ar/subscribe.html | 77 +- templates/ast/admindbdetails.html | 95 +- templates/ast/admindbpreamble.html | 72 +- templates/ast/admindbsummary.html | 72 +- templates/ast/admlogin.html | 115 +- templates/ast/archidxentry.html | 72 +- templates/ast/archidxfoot.html | 94 +- templates/ast/archidxhead.html | 95 +- templates/ast/archlistend.html | 65 +- templates/ast/archliststart.html | 72 +- templates/ast/archtoc.html | 86 +- templates/ast/archtocentry.html | 81 +- templates/ast/archtocnombox.html | 88 +- templates/ast/article.html | 139 +- templates/ast/emptyarchive.html | 86 +- templates/ast/headfoot.html | 86 +- templates/ast/listinfo.html | 318 +- templates/ast/options.html | 496 +- templates/ast/private.html | 151 +- templates/ast/roster.html | 159 +- templates/ast/subscribe.html | 73 +- templates/ca/admindbdetails.html | 159 +- templates/ca/admindbpreamble.html | 80 +- templates/ca/admindbsummary.html | 80 +- templates/ca/admlogin.html | 123 +- templates/ca/archidxentry.html | 72 +- templates/ca/archidxfoot.html | 92 +- templates/ca/archidxhead.html | 99 +- templates/ca/archlistend.html | 65 +- templates/ca/archliststart.html | 72 +- templates/ca/archtoc.html | 88 +- templates/ca/archtocentry.html | 82 +- templates/ca/archtocnombox.html | 86 +- templates/ca/article.html | 78 +- templates/ca/emptyarchive.html | 86 +- templates/ca/headfoot.html | 86 +- templates/ca/listinfo.html | 336 +- templates/ca/options.html | 547 +- templates/ca/private.html | 160 +- templates/ca/roster.html | 163 +- templates/ca/subscribe.html | 73 +- templates/cs/admindbdetails.html | 169 +- templates/cs/admindbpreamble.html | 86 +- templates/cs/admindbsummary.html | 90 +- templates/cs/admlogin.html | 121 +- templates/cs/archidxentry.html | 72 +- templates/cs/archidxfoot.html | 94 +- templates/cs/archidxhead.html | 97 +- templates/cs/archlistend.html | 66 +- templates/cs/archliststart.html | 74 +- templates/cs/archtoc.html | 88 +- templates/cs/archtocentry.html | 83 +- templates/cs/archtocnombox.html | 86 +- templates/cs/article.html | 76 +- templates/cs/emptyarchive.html | 90 +- templates/cs/headfoot.html | 102 +- templates/cs/listinfo.html | 343 +- templates/cs/options.html | 569 +- templates/cs/private.html | 155 +- templates/cs/roster.html | 165 +- templates/cs/subscribe.html | 73 +- templates/da/admindbdetails.html | 125 +- templates/da/admindbpreamble.html | 72 +- templates/da/admindbsummary.html | 78 +- templates/da/admlogin.html | 123 +- templates/da/archidxfoot.html | 92 +- templates/da/archidxhead.html | 95 +- templates/da/archliststart.html | 72 +- templates/da/archtoc.html | 84 +- templates/da/archtocentry.html | 82 +- templates/da/archtocnombox.html | 84 +- templates/da/article.html | 77 +- templates/da/emptyarchive.html | 88 +- templates/da/headfoot.html | 77 +- templates/da/listinfo.html | 333 +- templates/da/options.html | 544 +- templates/da/private.html | 158 +- templates/da/roster.html | 161 +- templates/da/subscribe.html | 73 +- templates/de/admindbdetails.html | 135 +- templates/de/admindbpreamble.html | 74 +- templates/de/admindbsummary.html | 78 +- templates/de/admlogin.html | 119 +- templates/de/archidxentry.html | 72 +- templates/de/archidxfoot.html | 94 +- templates/de/archidxhead.html | 97 +- templates/de/archlistend.html | 65 +- templates/de/archliststart.html | 72 +- templates/de/archtoc.html | 86 +- templates/de/archtocentry.html | 82 +- templates/de/archtocnombox.html | 86 +- templates/de/article.html | 77 +- templates/de/emptyarchive.html | 86 +- templates/de/headfoot.html | 85 +- templates/de/listinfo.html | 345 +- templates/de/options.html | 517 +- templates/de/private.html | 163 +- templates/de/roster.html | 157 +- templates/de/subscribe.html | 73 +- templates/el/admindbdetails.html | 185 +- templates/el/admindbpreamble.html | 78 +- templates/el/admindbsummary.html | 84 +- templates/el/admlogin.html | 129 +- templates/el/archidxentry.html | 72 +- templates/el/archidxfoot.html | 96 +- templates/el/archidxhead.html | 97 +- templates/el/archlistend.html | 65 +- templates/el/archliststart.html | 72 +- templates/el/archtoc.html | 90 +- templates/el/archtocentry.html | 82 +- templates/el/archtocnombox.html | 88 +- templates/el/article.html | 79 +- templates/el/emptyarchive.html | 90 +- templates/el/headfoot.html | 103 +- templates/el/listinfo.html | 345 +- templates/el/options.html | 649 +- templates/el/private.html | 162 +- templates/el/roster.html | 163 +- templates/el/subscribe.html | 73 +- templates/en/admindbdetails.html | 73 +- templates/en/admindbpreamble.html | 66 +- templates/en/admindbsummary.html | 66 +- templates/en/admlogin.html | 109 +- templates/en/archidxentry.html | 72 +- templates/en/archidxfoot.html | 92 +- templates/en/archidxhead.html | 95 +- templates/en/archlistend.html | 65 +- templates/en/archliststart.html | 74 +- templates/en/archtoc.html | 84 +- templates/en/archtocentry.html | 82 +- templates/en/archtocnombox.html | 84 +- templates/en/article.html | 77 +- templates/en/emptyarchive.html | 84 +- templates/en/headfoot.html | 69 +- templates/en/listinfo.html | 321 +- templates/en/options.html | 445 +- templates/en/private.html | 143 +- templates/en/roster.html | 163 +- templates/en/subscribe.html | 73 +- templates/eo/admlogin.html | 107 +- templates/eo/archidxentry.html | 72 +- templates/eo/archidxfoot.html | 90 +- templates/eo/archidxhead.html | 97 +- templates/eo/archlistend.html | 65 +- templates/eo/archliststart.html | 72 +- templates/eo/archtoc.html | 84 +- templates/eo/archtocentry.html | 82 +- templates/eo/archtocnombox.html | 84 +- templates/eo/article.html | 78 +- templates/eo/emptyarchive.html | 84 +- templates/eo/listinfo.html | 318 +- templates/eo/options.html | 451 +- templates/eo/private.html | 143 +- templates/eo/roster.html | 155 +- templates/eo/subscribe.html | 73 +- templates/es/admindbdetails.html | 141 +- templates/es/admindbpreamble.html | 76 +- templates/es/admindbsummary.html | 84 +- templates/es/admlogin.html | 117 +- templates/es/archidxentry.html | 72 +- templates/es/archidxfoot.html | 94 +- templates/es/archidxhead.html | 97 +- templates/es/archlistend.html | 65 +- templates/es/archliststart.html | 72 +- templates/es/archtoc.html | 86 +- templates/es/archtocentry.html | 82 +- templates/es/archtocnombox.html | 88 +- templates/es/article.html | 77 +- templates/es/emptyarchive.html | 90 +- templates/es/handle_opts.html | 75 +- templates/es/headfoot.html | 94 +- templates/es/listinfo.html | 341 +- templates/es/options.html | 573 +- templates/es/private.html | 149 +- templates/es/roster.html | 161 +- templates/es/subscribe.html | 73 +- templates/et/admindbdetails.html | 139 +- templates/et/admindbpreamble.html | 72 +- templates/et/admindbsummary.html | 78 +- templates/et/admlogin.html | 117 +- templates/et/article.html | 82 +- templates/et/emptyarchive.html | 88 +- templates/et/headfoot.html | 83 +- templates/et/listinfo.html | 325 +- templates/et/options.html | 530 +- templates/et/private.html | 149 +- templates/et/roster.html | 163 +- templates/et/subscribe.html | 73 +- templates/eu/admindbdetails.html | 73 +- templates/eu/admindbpreamble.html | 66 +- templates/eu/admindbsummary.html | 66 +- templates/eu/admlogin.html | 111 +- templates/eu/archidxentry.html | 72 +- templates/eu/archidxfoot.html | 92 +- templates/eu/archidxhead.html | 95 +- templates/eu/archlistend.html | 65 +- templates/eu/archliststart.html | 74 +- templates/eu/archtoc.html | 84 +- templates/eu/archtocentry.html | 83 +- templates/eu/article.html | 77 +- templates/eu/emptyarchive.html | 84 +- templates/eu/headfoot.html | 69 +- templates/eu/listinfo.html | 352 +- templates/eu/options.html | 445 +- templates/eu/private.html | 145 +- templates/eu/roster.html | 163 +- templates/eu/subscribe.html | 73 +- templates/fa/admlogin.html | 107 +- templates/fa/archidxfoot.html | 92 +- templates/fa/archidxhead.html | 95 +- templates/fa/archliststart.html | 72 +- templates/fa/archtoc.html | 84 +- templates/fa/archtocentry.html | 82 +- templates/fa/archtocnombox.html | 84 +- templates/fa/article.html | 77 +- templates/fa/emptyarchive.html | 84 +- templates/fa/listinfo.html | 321 +- templates/fa/options.html | 437 +- templates/fa/private.html | 141 +- templates/fa/roster.html | 157 +- templates/fa/subscribe.html | 73 +- templates/fi/admindbdetails.html | 163 +- templates/fi/admindbpreamble.html | 78 +- templates/fi/admindbsummary.html | 82 +- templates/fi/admlogin.html | 129 +- templates/fi/article.html | 81 +- templates/fi/headfoot.html | 109 +- templates/fi/listinfo.html | 343 +- templates/fi/options.html | 530 +- templates/fi/private.html | 157 +- templates/fi/roster.html | 163 +- templates/fi/subscribe.html | 73 +- templates/fr/admindbdetails.html | 163 +- templates/fr/admindbpreamble.html | 78 +- templates/fr/admindbsummary.html | 74 +- templates/fr/admlogin.html | 125 +- templates/fr/archidxentry.html | 69 +- templates/fr/archidxfoot.html | 90 +- templates/fr/archidxhead.html | 89 +- templates/fr/archlistend.html | 65 +- templates/fr/archliststart.html | 76 +- templates/fr/archtoc.html | 76 +- templates/fr/archtocentry.html | 83 +- templates/fr/archtocnombox.html | 84 +- templates/fr/article.html | 82 +- templates/fr/emptyarchive.html | 80 +- templates/fr/handle_opts.html | 75 +- templates/fr/headfoot.html | 96 +- templates/fr/listinfo.html | 342 +- templates/fr/options.html | 602 +- templates/fr/private.html | 153 +- templates/fr/roster.html | 163 +- templates/fr/subscribe.html | 73 +- templates/gl/admindbdetails.html | 153 +- templates/gl/admindbpreamble.html | 76 +- templates/gl/admindbsummary.html | 88 +- templates/gl/admlogin.html | 127 +- templates/gl/archidxentry.html | 72 +- templates/gl/archidxfoot.html | 94 +- templates/gl/archidxhead.html | 97 +- templates/gl/archlistend.html | 65 +- templates/gl/archliststart.html | 72 +- templates/gl/archtoc.html | 86 +- templates/gl/archtocentry.html | 82 +- templates/gl/article.html | 76 +- templates/gl/emptyarchive.html | 90 +- templates/gl/handle_opts.html | 75 +- templates/gl/headfoot.html | 99 +- templates/gl/listinfo.html | 345 +- templates/gl/options.html | 675 +- templates/gl/private.html | 155 +- templates/gl/roster.html | 167 +- templates/gl/subscribe.html | 73 +- templates/he/admindbdetails.html | 71 +- templates/he/admindbpreamble.html | 66 +- templates/he/admindbsummary.html | 66 +- templates/he/admlogin.html | 111 +- templates/he/archidxentry.html | 72 +- templates/he/archidxfoot.html | 92 +- templates/he/archidxhead.html | 95 +- templates/he/archlistend.html | 65 +- templates/he/archliststart.html | 74 +- templates/he/archtoc.html | 84 +- templates/he/archtocentry.html | 82 +- templates/he/archtocnombox.html | 84 +- templates/he/article.html | 77 +- templates/he/emptyarchive.html | 84 +- templates/he/headfoot.html | 69 +- templates/he/listinfo.html | 321 +- templates/he/options.html | 441 +- templates/he/private.html | 143 +- templates/he/roster.html | 161 +- templates/he/subscribe.html | 73 +- templates/hr/admindbdetails.html | 141 +- templates/hr/admindbpreamble.html | 76 +- templates/hr/admindbsummary.html | 80 +- templates/hr/admlogin.html | 123 +- templates/hr/archidxentry.html | 72 +- templates/hr/archidxfoot.html | 94 +- templates/hr/archidxhead.html | 97 +- templates/hr/archlistend.html | 65 +- templates/hr/archliststart.html | 72 +- templates/hr/archtoc.html | 88 +- templates/hr/archtocentry.html | 82 +- templates/hr/article.html | 79 +- templates/hr/emptyarchive.html | 88 +- templates/hr/headfoot.html | 75 +- templates/hr/listinfo.html | 339 +- templates/hr/options.html | 553 +- templates/hr/private.html | 155 +- templates/hr/roster.html | 161 +- templates/hr/subscribe.html | 73 +- templates/hu/admindbdetails.html | 171 +- templates/hu/admindbpreamble.html | 82 +- templates/hu/admindbsummary.html | 86 +- templates/hu/admlogin.html | 114 +- templates/hu/archidxentry.html | 72 +- templates/hu/archidxfoot.html | 94 +- templates/hu/archidxhead.html | 97 +- templates/hu/archlistend.html | 65 +- templates/hu/archliststart.html | 72 +- templates/hu/archtoc.html | 88 +- templates/hu/archtocentry.html | 82 +- templates/hu/article.html | 78 +- templates/hu/emptyarchive.html | 88 +- templates/hu/headfoot.html | 93 +- templates/hu/illik.html | 2561 +-- templates/hu/listinfo.html | 337 +- templates/hu/options.html | 581 +- templates/hu/private.html | 151 +- templates/hu/roster.html | 161 +- templates/hu/subscribe.html | 75 +- templates/ia/admindbdetails.html | 69 +- templates/ia/admindbpreamble.html | 66 +- templates/ia/admindbsummary.html | 66 +- templates/ia/admlogin.html | 111 +- templates/ia/archidxentry.html | 72 +- templates/ia/archidxfoot.html | 92 +- templates/ia/archidxhead.html | 95 +- templates/ia/archlistend.html | 65 +- templates/ia/archliststart.html | 74 +- templates/ia/archtoc.html | 84 +- templates/ia/archtocentry.html | 82 +- templates/ia/archtocnombox.html | 84 +- templates/ia/article.html | 77 +- templates/ia/emptyarchive.html | 84 +- templates/ia/headfoot.html | 69 +- templates/ia/listinfo.html | 319 +- templates/ia/options.html | 443 +- templates/ia/private.html | 145 +- templates/ia/roster.html | 161 +- templates/ia/subscribe.html | 73 +- templates/it/admindbdetails.html | 101 +- templates/it/admindbpreamble.html | 68 +- templates/it/admindbsummary.html | 68 +- templates/it/admlogin.html | 111 +- templates/it/archidxentry.html | 72 +- templates/it/archidxfoot.html | 92 +- templates/it/archidxhead.html | 95 +- templates/it/archlistend.html | 65 +- templates/it/archliststart.html | 72 +- templates/it/archtoc.html | 84 +- templates/it/archtocentry.html | 82 +- templates/it/archtocnombox.html | 86 +- templates/it/article.html | 78 +- templates/it/emptyarchive.html | 84 +- templates/it/headfoot.html | 76 +- templates/it/listinfo.html | 333 +- templates/it/options.html | 469 +- templates/it/private.html | 143 +- templates/it/roster.html | 161 +- templates/it/subscribe.html | 73 +- templates/ja/admindbdetails.html | 181 +- templates/ja/admindbpreamble.html | 80 +- templates/ja/admindbsummary.html | 84 +- templates/ja/admlogin.html | 125 +- templates/ja/archidxentry.html | 72 +- templates/ja/archidxfoot.html | 96 +- templates/ja/archidxhead.html | 97 +- templates/ja/archlistend.html | 65 +- templates/ja/archliststart.html | 72 +- templates/ja/archtoc.html | 90 +- templates/ja/archtocentry.html | 82 +- templates/ja/archtocnombox.html | 86 +- templates/ja/article.html | 79 +- templates/ja/emptyarchive.html | 92 +- templates/ja/headfoot.html | 69 +- templates/ja/listinfo.html | 323 +- templates/ja/options.html | 592 +- templates/ja/private.html | 161 +- templates/ja/roster.html | 168 +- templates/ja/subscribe.html | 73 +- templates/ko/admindbdetails.html | 159 +- templates/ko/admindbpreamble.html | 78 +- templates/ko/admindbsummary.html | 82 +- templates/ko/admlogin.html | 121 +- templates/ko/article.html | 76 +- templates/ko/emptyarchive.html | 88 +- templates/ko/headfoot.html | 93 +- templates/ko/listinfo.html | 343 +- templates/ko/options.html | 581 +- templates/ko/private.html | 157 +- templates/ko/roster.html | 158 +- templates/ko/subscribe.html | 73 +- templates/lt/admindbdetails.html | 73 +- templates/lt/admindbpreamble.html | 78 +- templates/lt/admindbsummary.html | 80 +- templates/lt/admlogin.html | 121 +- templates/lt/archidxentry.html | 72 +- templates/lt/archidxfoot.html | 94 +- templates/lt/archidxhead.html | 97 +- templates/lt/archlistend.html | 65 +- templates/lt/archliststart.html | 74 +- templates/lt/archtoc.html | 88 +- templates/lt/archtocentry.html | 82 +- templates/lt/article.html | 77 +- templates/lt/emptyarchive.html | 88 +- templates/lt/headfoot.html | 79 +- templates/lt/listinfo.html | 338 +- templates/lt/options.html | 549 +- templates/lt/private.html | 147 +- templates/lt/roster.html | 162 +- templates/lt/subscribe.html | 73 +- templates/nl/admindbdetails.html | 74 +- templates/nl/admindbpreamble.html | 71 +- templates/nl/admindbsummary.html | 69 +- templates/nl/admlogin.html | 111 +- templates/nl/archidxentry.html | 72 +- templates/nl/archidxfoot.html | 92 +- templates/nl/archidxhead.html | 95 +- templates/nl/archlistend.html | 65 +- templates/nl/archliststart.html | 72 +- templates/nl/archtoc.html | 84 +- templates/nl/archtocentry.html | 82 +- templates/nl/archtocnombox.html | 84 +- templates/nl/article.html | 77 +- templates/nl/emptyarchive.html | 84 +- templates/nl/headfoot.html | 69 +- templates/nl/listinfo.html | 319 +- templates/nl/options.html | 463 +- templates/nl/private.html | 145 +- templates/nl/roster.html | 159 +- templates/nl/subscribe.html | 73 +- templates/no/admindbdetails.html | 139 +- templates/no/admindbpreamble.html | 74 +- templates/no/admindbsummary.html | 80 +- templates/no/admlogin.html | 119 +- templates/no/archidxfoot.html | 92 +- templates/no/archidxhead.html | 95 +- templates/no/archliststart.html | 72 +- templates/no/archtoc.html | 84 +- templates/no/archtocentry.html | 82 +- templates/no/archtocnombox.html | 84 +- templates/no/article.html | 77 +- templates/no/emptyarchive.html | 86 +- templates/no/headfoot.html | 75 +- templates/no/listinfo.html | 333 +- templates/no/options.html | 534 +- templates/no/private.html | 151 +- templates/no/roster.html | 161 +- templates/no/subscribe.html | 73 +- templates/pl/admlogin.html | 125 +- templates/pl/archidxentry.html | 72 +- templates/pl/archidxfoot.html | 88 +- templates/pl/archidxhead.html | 95 +- templates/pl/archlistend.html | 65 +- templates/pl/archliststart.html | 72 +- templates/pl/archtoc.html | 78 +- templates/pl/archtocentry.html | 77 +- templates/pl/archtocnombox.html | 78 +- templates/pl/article.html | 70 +- templates/pl/emptyarchive.html | 92 +- templates/pl/listinfo.html | 335 +- templates/pl/options.html | 599 +- templates/pl/private.html | 157 +- templates/pl/roster.html | 162 +- templates/pl/subscribe.html | 77 +- templates/pt/admindbdetails.html | 152 +- templates/pt/admindbpreamble.html | 76 +- templates/pt/admindbsummary.html | 80 +- templates/pt/admlogin.html | 123 +- templates/pt/archidxentry.html | 72 +- templates/pt/archidxfoot.html | 94 +- templates/pt/archidxhead.html | 97 +- templates/pt/archlistend.html | 65 +- templates/pt/archliststart.html | 72 +- templates/pt/archtoc.html | 88 +- templates/pt/archtocentry.html | 82 +- templates/pt/article.html | 77 +- templates/pt/emptyarchive.html | 90 +- templates/pt/headfoot.html | 91 +- templates/pt/listinfo.html | 337 +- templates/pt/options.html | 561 +- templates/pt/private.html | 156 +- templates/pt/roster.html | 163 +- templates/pt/subscribe.html | 73 +- templates/pt_BR/admindbdetails.html | 163 +- templates/pt_BR/admindbpreamble.html | 80 +- templates/pt_BR/admindbsummary.html | 84 +- templates/pt_BR/admlogin.html | 125 +- templates/pt_BR/archidxentry.html | 72 +- templates/pt_BR/archidxfoot.html | 94 +- templates/pt_BR/archidxhead.html | 97 +- templates/pt_BR/archlistend.html | 65 +- templates/pt_BR/archliststart.html | 72 +- templates/pt_BR/archtoc.html | 88 +- templates/pt_BR/archtocentry.html | 82 +- templates/pt_BR/article.html | 79 +- templates/pt_BR/emptyarchive.html | 88 +- templates/pt_BR/headfoot.html | 95 +- templates/pt_BR/listinfo.html | 336 +- templates/pt_BR/options.html | 605 +- templates/pt_BR/private.html | 159 +- templates/pt_BR/roster.html | 161 +- templates/pt_BR/subscribe.html | 73 +- templates/ro/admindbdetails.html | 109 +- templates/ro/admindbpreamble.html | 72 +- templates/ro/admindbsummary.html | 74 +- templates/ro/admlogin.html | 117 +- templates/ro/archidxentry.html | 72 +- templates/ro/archidxfoot.html | 92 +- templates/ro/archidxhead.html | 97 +- templates/ro/archlistend.html | 65 +- templates/ro/archliststart.html | 72 +- templates/ro/archtoc.html | 88 +- templates/ro/archtocentry.html | 82 +- templates/ro/article.html | 77 +- templates/ro/emptyarchive.html | 90 +- templates/ro/headfoot.html | 71 +- templates/ro/listinfo.html | 330 +- templates/ro/options.html | 493 +- templates/ro/private.html | 151 +- templates/ro/roster.html | 165 +- templates/ro/subscribe.html | 75 +- templates/ru/admindbdetails.html | 77 +- templates/ru/admindbpreamble.html | 66 +- templates/ru/admindbsummary.html | 66 +- templates/ru/admlogin.html | 103 +- templates/ru/archidxentry.html | 66 +- templates/ru/archidxfoot.html | 92 +- templates/ru/archidxhead.html | 95 +- templates/ru/archlistend.html | 65 +- templates/ru/archliststart.html | 72 +- templates/ru/archtoc.html | 84 +- templates/ru/archtocentry.html | 82 +- templates/ru/archtocnombox.html | 84 +- templates/ru/article.html | 76 +- templates/ru/emptyarchive.html | 84 +- templates/ru/headfoot.html | 82 +- templates/ru/listinfo.html | 303 +- templates/ru/options.html | 443 +- templates/ru/private.html | 139 +- templates/ru/roster.html | 153 +- templates/ru/subscribe.html | 73 +- templates/sk/admindbdetails.html | 169 +- templates/sk/admindbpreamble.html | 88 +- templates/sk/admindbsummary.html | 92 +- templates/sk/admlogin.html | 123 +- templates/sk/archidxentry.html | 72 +- templates/sk/archidxfoot.html | 94 +- templates/sk/archidxhead.html | 97 +- templates/sk/archlistend.html | 65 +- templates/sk/archliststart.html | 72 +- templates/sk/archtoc.html | 88 +- templates/sk/archtocentry.html | 82 +- templates/sk/archtocnombox.html | 86 +- templates/sk/article.html | 76 +- templates/sk/emptyarchive.html | 90 +- templates/sk/headfoot.html | 97 +- templates/sk/listinfo.html | 351 +- templates/sk/options.html | 616 +- templates/sk/private.html | 157 +- templates/sk/roster.html | 163 +- templates/sk/subscribe.html | 73 +- templates/sl/admindbdetails.html | 143 +- templates/sl/admindbpreamble.html | 74 +- templates/sl/admindbsummary.html | 78 +- templates/sl/admlogin.html | 119 +- templates/sl/archidxentry.html | 72 +- templates/sl/archidxfoot.html | 92 +- templates/sl/archidxhead.html | 95 +- templates/sl/archlistend.html | 65 +- templates/sl/archliststart.html | 72 +- templates/sl/archtoc.html | 84 +- templates/sl/archtocentry.html | 82 +- templates/sl/article.html | 77 +- templates/sl/emptyarchive.html | 86 +- templates/sl/headfoot.html | 83 +- templates/sl/listinfo.html | 337 +- templates/sl/options.html | 555 +- templates/sl/private.html | 159 +- templates/sl/roster.html | 163 +- templates/sl/subscribe.html | 73 +- templates/sr/admindbdetails.html | 86 +- templates/sr/admindbpreamble.html | 66 +- templates/sr/admindbsummary.html | 66 +- templates/sr/admlogin.html | 116 +- templates/sr/archidxentry.html | 72 +- templates/sr/archidxfoot.html | 92 +- templates/sr/archidxhead.html | 97 +- templates/sr/archlistend.html | 65 +- templates/sr/archliststart.html | 76 +- templates/sr/archtoc.html | 84 +- templates/sr/archtocentry.html | 75 +- templates/sr/article.html | 61 +- templates/sr/emptyarchive.html | 84 +- templates/sr/handle_opts.html | 77 +- templates/sr/headfoot.html | 69 +- templates/sr/listinfo.html | 301 +- templates/sr/options.html | 468 +- templates/sr/private.html | 152 +- templates/sr/roster.html | 161 +- templates/sr/subscribe.html | 75 +- templates/sv/admindbdetails.html | 99 +- templates/sv/admindbpreamble.html | 70 +- templates/sv/admindbsummary.html | 69 +- templates/sv/admlogin.html | 109 +- templates/sv/archtoc.html | 88 +- templates/sv/archtocentry.html | 82 +- templates/sv/article.html | 80 +- templates/sv/emptyarchive.html | 84 +- templates/sv/headfoot.html | 77 +- templates/sv/listinfo.html | 309 +- templates/sv/options.html | 496 +- templates/sv/private.html | 145 +- templates/sv/roster.html | 153 +- templates/sv/subscribe.html | 73 +- templates/tr/admindbdetails.html | 168 +- templates/tr/admindbpreamble.html | 78 +- templates/tr/admindbsummary.html | 84 +- templates/tr/admlogin.html | 130 +- templates/tr/archidxentry.html | 72 +- templates/tr/archidxfoot.html | 93 +- templates/tr/archidxhead.html | 96 +- templates/tr/archlistend.html | 65 +- templates/tr/archliststart.html | 75 +- templates/tr/archtoc.html | 89 +- templates/tr/archtocentry.html | 83 +- templates/tr/archtocnombox.html | 87 +- templates/tr/article.html | 78 +- templates/tr/emptyarchive.html | 89 +- templates/tr/headfoot.html | 98 +- templates/tr/listinfo.html | 344 +- templates/tr/options.html | 614 +- templates/tr/private.html | 164 +- templates/tr/roster.html | 164 +- templates/tr/subscribe.html | 73 +- templates/uk/admindbdetails.html | 70 +- templates/uk/admindbpreamble.html | 66 +- templates/uk/admindbsummary.html | 66 +- templates/uk/admlogin.html | 109 +- templates/uk/archidxentry.html | 72 +- templates/uk/archidxfoot.html | 92 +- templates/uk/archidxhead.html | 95 +- templates/uk/archlistend.html | 65 +- templates/uk/archliststart.html | 72 +- templates/uk/archtoc.html | 84 +- templates/uk/archtocentry.html | 82 +- templates/uk/archtocnombox.html | 84 +- templates/uk/article.html | 77 +- templates/uk/emptyarchive.html | 86 +- templates/uk/headfoot.html | 69 +- templates/uk/listinfo.html | 319 +- templates/uk/options.html | 437 +- templates/uk/private.html | 141 +- templates/uk/roster.html | 163 +- templates/uk/subscribe.html | 73 +- templates/vi/admindbdetails.html | 78 +- templates/vi/admindbpreamble.html | 66 +- templates/vi/admindbsummary.html | 66 +- templates/vi/admlogin.html | 111 +- templates/vi/archidxentry.html | 72 +- templates/vi/archidxfoot.html | 92 +- templates/vi/archidxhead.html | 95 +- templates/vi/archlistend.html | 65 +- templates/vi/archliststart.html | 72 +- templates/vi/archtoc.html | 84 +- templates/vi/archtocentry.html | 82 +- templates/vi/archtocnombox.html | 84 +- templates/vi/article.html | 77 +- templates/vi/emptyarchive.html | 84 +- templates/vi/headfoot.html | 81 +- templates/vi/listinfo.html | 317 +- templates/vi/options.html | 435 +- templates/vi/private.html | 143 +- templates/vi/roster.html | 157 +- templates/vi/subscribe.html | 73 +- templates/zh_CN/admindbdetails.html | 69 +- templates/zh_CN/admindbpreamble.html | 66 +- templates/zh_CN/admindbsummary.html | 66 +- templates/zh_CN/admlogin.html | 107 +- templates/zh_CN/archidxentry.html | 72 +- templates/zh_CN/archidxfoot.html | 92 +- templates/zh_CN/archidxhead.html | 95 +- templates/zh_CN/archlistend.html | 65 +- templates/zh_CN/archliststart.html | 72 +- templates/zh_CN/archtoc.html | 84 +- templates/zh_CN/archtocentry.html | 82 +- templates/zh_CN/archtocnombox.html | 84 +- templates/zh_CN/article.html | 77 +- templates/zh_CN/emptyarchive.html | 84 +- templates/zh_CN/headfoot.html | 69 +- templates/zh_CN/listinfo.html | 319 +- templates/zh_CN/options.html | 445 +- templates/zh_CN/private.html | 141 +- templates/zh_CN/roster.html | 159 +- templates/zh_CN/subscribe.html | 73 +- templates/zh_TW/admindbpreamble.html | 69 +- templates/zh_TW/admlogin.html | 105 +- templates/zh_TW/handle_opts.html | 75 +- templates/zh_TW/headfoot.html | 69 +- templates/zh_TW/listinfo.html | 310 +- templates/zh_TW/options.html | 274 +- templates/zh_TW/roster.html | 159 +- templates/zh_TW/subscribe.html | 73 +- 925 files changed, 98137 insertions(+), 42092 deletions(-) diff --git a/Mailman/Archiver/HyperArch.py b/Mailman/Archiver/HyperArch.py index d80f4576..199d7915 100644 --- a/Mailman/Archiver/HyperArch.py +++ b/Mailman/Archiver/HyperArch.py @@ -840,12 +840,12 @@ def html_TOC_entry(self, arch): if os.path.exists(gzfile): file = gzfile url = arch + '.txt.gz' - templ = '
                • ' elif os.path.exists(txtfile): file = txtfile url = arch + '.txt' - templ = '' + templ = '' else: # neither found? file = None diff --git a/Mailman/Cgi/admin.py b/Mailman/Cgi/admin.py index eb909249..2b2454ca 100644 --- a/Mailman/Cgi/admin.py +++ b/Mailman/Cgi/admin.py @@ -694,9 +694,11 @@ def add_options_table_item(mlist, category, subcat, table, item, detailsp=1): val = get_item_gui_value(mlist, category, kind, varname, params, extra) table.AddRow([descr, val]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, - bgcolor=mm_cfg.WEB_ADMINITEM_COLOR) + style=f'background-color: {mm_cfg.WEB_ADMINITEM_COLOR}', + role='cell') table.AddCellInfo(table.GetCurrentRowIndex(), 1, - bgcolor=mm_cfg.WEB_ADMINITEM_COLOR) + style=f'background-color: {mm_cfg.WEB_ADMINITEM_COLOR}', + role='cell') def get_item_characteristics(record): # Break out the components of an item description from its description @@ -832,7 +834,7 @@ def makebox(i, name, pattern, desc, empty=False, table=table): selected=1), ]) table.AddRow(['
                  ']) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, role='cell') # Now for each element in the existing data, create a widget i = 1 data = getattr(mlist, varname) @@ -877,32 +879,20 @@ def makebox(i, pattern, action, empty=False, table=table): rows=4, cols=30, wrap='off')]) values = [mm_cfg.DEFER, mm_cfg.HOLD, mm_cfg.REJECT, mm_cfg.DISCARD, mm_cfg.ACCEPT] - try: - checked = values.index(action) - except ValueError: - checked = 0 - radio = RadioButtonArray( - actiontag, - (_('Defer'), _('Hold'), _('Reject'), - _('Discard'), _('Accept')), - values=values, - checked=checked).Format() - table.AddRow([Label(_('Action:')), radio]) + legends = [_('Defer'), _('Hold'), _('Reject'), + _('Discard'), _('Accept')] + table.AddRow([Label(_('Action:')), + SelectOptions(actiontag, values, legends, + selected=values.index(action))]) if not empty: - table.AddRow([SubmitButton(addtag, _('Add new item...')), + table.AddRow([SubmitButton(addtag, _('Add new rule...')), SelectOptions(wheretag, ('before', 'after'), (_('...before this one.'), _('...after this one.')), selected=1), ]) - # BAW: IWBNI we could disable the up and down buttons for the - # first and last item respectively, but it's not easy to know - # which is the last item, so let's not worry about that for - # now. - table.AddRow([SubmitButton(uptag, _('Move rule up')), - SubmitButton(downtag, _('Move rule down'))]) table.AddRow(['
                  ']) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, role='cell') # Now for each element in the existing data, create a widget i = 1 data = getattr(mlist, varname) diff --git a/Mailman/Cgi/admindb.py b/Mailman/Cgi/admindb.py index 5f2d70f7..8e551b36 100644 --- a/Mailman/Cgi/admindb.py +++ b/Mailman/Cgi/admindb.py @@ -286,11 +286,18 @@ def show_pending_subs(mlist, form): return 0 form.AddItem('
                  ') form.AddItem(Center(Header(2, _('Subscription Requests')))) - table = Table(border=2) + table = Table( + role="table", + aria_label=_("Pending Subscription Requests"), + style="border: 1px solid #ccc; border-collapse: collapse; width: 100%;" + ) table.AddRow([Center(Bold(_('Address/name/time'))), Center(Bold(_('Your decision'))), Center(Bold(_('Reason for refusal'))) ]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, role="columnheader", scope="col") + table.AddCellInfo(table.GetCurrentRowIndex(), 1, role="columnheader", scope="col") + table.AddCellInfo(table.GetCurrentRowIndex(), 2, role="columnheader", scope="col") # Alphabetical order by email address byaddrs = {} for id in pendingsubs: @@ -349,11 +356,18 @@ def show_pending_unsubs(mlist, form): pendingunsubs = mlist.GetUnsubscriptionIds() if not pendingunsubs: return 0 - table = Table(border=2) + table = Table( + role="table", + aria_label=_("Pending Unsubscription Requests"), + style="border: 1px solid #ccc; border-collapse: collapse; width: 100%;" + ) table.AddRow([Center(Bold(_('User address/name'))), Center(Bold(_('Your decision'))), Center(Bold(_('Reason for refusal'))) ]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, role="columnheader", scope="col") + table.AddCellInfo(table.GetCurrentRowIndex(), 1, role="columnheader", scope="col") + table.AddCellInfo(table.GetCurrentRowIndex(), 2, role="columnheader", scope="col") # Alphabetical order by email address byaddrs = {} for id in pendingunsubs: @@ -435,7 +449,11 @@ def show_helds_overview(mlist, form, ssort=SSENDER): (ssort == SSENDER, ssort == SSENDERTIME, ssort == STIME)))) # Add the by-sender overview tables admindburl = mlist.GetScriptURL('admindb', absolute=1) - table = Table(border=0) + table = Table( + role="table", + aria_label=_("Held Messages Overview"), + border=0 + ) form.AddItem(table) skeys = list(byskey.keys()) skeys.sort() @@ -445,19 +463,27 @@ def show_helds_overview(mlist, form, ssort=SSENDER): esender = Utils.websafe(sender) senderurl = admindburl + '?sender=' + qsender # The encompassing sender table - stable = Table(border=1) + stable = Table( + role="table", + aria_label=_("Messages from {sender}").format(sender=esender), + border=1 + ) stable.AddRow([Center(Bold(_('From:')).Format() + esender)]) - stable.AddCellInfo(stable.GetCurrentRowIndex(), 0, colspan=2) - left = Table(border=0) + stable.AddCellInfo(stable.GetCurrentRowIndex(), 0, colspan=2, role="cell") + left = Table( + role="table", + aria_label=_("Actions for messages from {sender}").format(sender=esender), + border=0 + ) left.AddRow([_('Action to take on all these held messages:')]) - left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) + left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2, role="cell") btns = hacky_radio_buttons( 'senderaction-' + qsender, (_('Defer'), _('Accept'), _('Reject'), _('Discard')), (mm_cfg.DEFER, mm_cfg.APPROVE, mm_cfg.REJECT, mm_cfg.DISCARD), (1, 0, 0, 0)) left.AddRow([btns]) - left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) + left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2, role="cell") left.AddRow([ '' ]) - left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) + left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2, role="cell") left.AddRow([ '' ]) - left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) + left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2, role="cell") left.AddRow([ TextBox('senderforwardto-' + qsender, value=mlist.GetOwnerEmail()) ]) - left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) + left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2, role="cell") # If the sender is a member and the message is being held due to a # moderation bit, give the admin a chance to clear the member's mod # bit. If this sender is not a member and is not already on one of @@ -496,7 +522,7 @@ def show_helds_overview(mlist, form, ssort=SSENDER): else: left.AddRow( [_('The sender is now a member of this list')]) - left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) + left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2, role="cell") elif sender not in (mlist.accept_these_nonmembers + mlist.hold_these_nonmembers + mlist.reject_these_nonmembers + @@ -508,14 +534,14 @@ def show_helds_overview(mlist, form, ssort=SSENDER): _(f'Add {esender} to one of these sender filters:') + '' ]) - left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) + left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2, role="cell") btns = hacky_radio_buttons( 'senderfilter-' + qsender, (_('Accepts'), _('Holds'), _('Rejects'), _('Discards')), (mm_cfg.ACCEPT, mm_cfg.HOLD, mm_cfg.REJECT, mm_cfg.DISCARD), (0, 0, 0, 1)) left.AddRow([btns]) - left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) + left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2, role="cell") if sender not in mlist.ban_list: left.AddRow([ '']) - left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) - right = Table(border=0) + left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2, role="cell") + right = Table( + role="table", + aria_label=_("Actions for messages from {sender}").format(sender=esender), + border=0 + ) right.AddRow([ _(f"""Click on the message number to view the individual message, or you can """) + Link(senderurl, _(f'view all messages from {esender}')).Format() ]) - right.AddCellInfo(right.GetCurrentRowIndex(), 0, colspan=2) + right.AddCellInfo(right.GetCurrentRowIndex(), 0, colspan=2, role="cell") right.AddRow([' ', ' ']) counter = 1 for ptime, id in byskey[skey]: @@ -553,7 +583,11 @@ def show_helds_overview(mlist, form, ssort=SSENDER): charset = Utils.GetCharSet(mlist.preferred_language) dispsubj = format_subject(subject, charset) - t = Table(border=0) + t = Table( + role="table", + aria_label=_("Message {counter}").format(counter=counter), + border=0 + ) t.AddRow([Link(admindburl + '?msgid=%d' % id, '[%d]' % counter), Bold(_('Subject:')), Utils.websafe(dispsubj) diff --git a/Mailman/Cgi/create.py b/Mailman/Cgi/create.py index 683732ab..27774c03 100644 --- a/Mailman/Cgi/create.py +++ b/Mailman/Cgi/create.py @@ -298,10 +298,16 @@ def sigterm_handler(signum, frame, mlist=mlist): title = _('Mailing list creation results') doc.SetTitle(title) - table = Table(border=0, width='100%') + table = Table( + role="table", + aria_label=_("List Creation Results"), + border=0, + width='100%' + ) table.AddRow([Center(Bold(FontAttr(title, size='+1')))]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, - bgcolor=mm_cfg.WEB_HEADER_COLOR) + style=f'background-color: {mm_cfg.WEB_HEADER_COLOR}', + role="cell") table.AddRow([_(f'''You have successfully created the mailing list {listname} and notification has been sent to the list owner {owner}. You can now:''')]) @@ -326,10 +332,15 @@ def request_creation(doc, cgidata=dummy, errmsg=None): # Set up the document title = _(f"Create a {hostname} Mailing List") doc.SetTitle(title) - table = Table(border=0, width='100%') + table = Table( + role="table", + aria_label=_("List Creation Form"), + style="border: 1px solid #ccc; border-collapse: collapse; width: 100%;" + ) table.AddRow([Center(Bold(FontAttr(title, size='+1')))]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, - bgcolor=mm_cfg.WEB_HEADER_COLOR) + style=f'background-color: {mm_cfg.WEB_HEADER_COLOR}', + role="cell") # Add any error message if errmsg: table.AddRow([Header(3, Bold( @@ -358,25 +369,34 @@ def request_creation(doc, cgidata=dummy, errmsg=None): # Build the form for the necessary input GREY = mm_cfg.WEB_ADMINITEM_COLOR form = Form(Utils.ScriptURL('create')) - ftable = Table(border=0, cols='2', width='100%', - cellspacing=3, cellpadding=4) + ftable = Table( + role="table", + aria_label=_("List Creation Form Fields"), + style="border: 1px solid #ccc; border-collapse: collapse; width: 100%;" + ) ftable.AddRow([Center(Italic(_('List Identity')))]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, colspan=2) + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, colspan=2, role="cell") listname = cgidata.get('listname', [''])[0] - # MAS: Don't websafe twice. TextBox does it. ftable.AddRow([Label(_('Name of list:')), - TextBox('listname', listname)]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY) + TextBox('listname', listname, aria_label=_('Name of list'))]) + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, + style=f'background-color: {GREY}', + role="cell") + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, + style=f'background-color: {GREY}', + role="cell") owner = cgidata.get('owner', [''])[0] - # MAS: Don't websafe twice. TextBox does it. ftable.AddRow([Label(_('Initial list owner address:')), - TextBox('owner', owner)]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY) + TextBox('owner', owner, aria_label=_('Initial list owner address'))]) + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, + style=f'background-color: {GREY}', + role="cell") + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, + style=f'background-color: {GREY}', + role="cell") try: autogen = int(cgidata.get('autogen', ['0'])[0]) @@ -385,21 +405,34 @@ def request_creation(doc, cgidata=dummy, errmsg=None): ftable.AddRow([Label(_('Auto-generate initial list password?')), RadioButtonArray('autogen', (_('No'), _('Yes')), checked=autogen, - values=(0, 1))]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY) + values=(0, 1), + aria_label=_('Auto-generate initial list password'))]) + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, + style=f'background-color: {GREY}', + role="cell") + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, + style=f'background-color: {GREY}', + role="cell") safepasswd = Utils.websafe(cgidata.get('password', [''])[0]) ftable.AddRow([Label(_('Initial list password:')), PasswordBox('password', safepasswd)]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY) + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, + style=f'background-color: {GREY}', + role="cell") + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, + style=f'background-color: {GREY}', + role="cell") safeconfirm = Utils.websafe(cgidata.get('confirm', [''])[0]) ftable.AddRow([Label(_('Confirm initial password:')), PasswordBox('confirm', safeconfirm)]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY) + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, + style=f'background-color: {GREY}', + role="cell") + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, + style=f'background-color: {GREY}', + role="cell") try: notify = int(cgidata.get('notify', ['1'])[0]) @@ -411,7 +444,7 @@ def request_creation(doc, cgidata=dummy, errmsg=None): moderate = mm_cfg.DEFAULT_DEFAULT_MEMBER_MODERATION ftable.AddRow([Center(Italic(_('List Characteristics')))]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, colspan=2) + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, colspan=2, role="cell") ftable.AddRow([ Label(_(f"""Should new members be quarantined before they @@ -420,8 +453,12 @@ def request_creation(doc, cgidata=dummy, errmsg=None): RadioButtonArray('moderate', (_('No'), _('Yes')), checked=moderate, values=(0,1))]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY) + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, + style=f'background-color: {GREY}', + role="cell") + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, + style=f'background-color: {GREY}', + role="cell") # Create the table of initially supported languages, sorted on the long # name of the language. revmap = {} @@ -451,22 +488,34 @@ def request_creation(doc, cgidata=dummy, errmsg=None): [_(Utils.GetLanguageDescr(L)) for L in langs], checked=checked, values=langs)]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY) + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, + style=f'background-color: {GREY}', + role="cell") + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, + style=f'background-color: {GREY}', + role="cell") ftable.AddRow([Label(_('Send "list created" email to list owner?')), RadioButtonArray('notify', (_('No'), _('Yes')), checked=notify, values=(0, 1))]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY) + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, + style=f'background-color: {GREY}', + role="cell") + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, + style=f'background-color: {GREY}', + role="cell") ftable.AddRow(['
                  ']) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, colspan=2) + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, colspan=2, role="cell") ftable.AddRow([Label(_("List creator's (authentication) password:")), PasswordBox('auth')]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY) + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, + style=f'background-color: {GREY}', + role="cell") + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, + style=f'background-color: {GREY}', + role="cell") ftable.AddRow([Center(SubmitButton('doit', _('Create List'))), Center(SubmitButton('clear', _('Clear Form')))]) diff --git a/Mailman/Cgi/listinfo.py b/Mailman/Cgi/listinfo.py index 2627315d..97198ef0 100644 --- a/Mailman/Cgi/listinfo.py +++ b/Mailman/Cgi/listinfo.py @@ -138,10 +138,16 @@ def listinfo_overview(msg=''): legend = (hostname + "'s Mailing Lists") doc.SetTitle(legend) - table = Table(border=0, width="100%") + table = Table( + role="table", + aria_label=_("Mailing Lists Overview"), + border=0, + width="100%" + ) table.AddRow([Center(Header(2, legend))]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, - bgcolor=mm_cfg.WEB_HEADER_COLOR) + style=f'background-color: {mm_cfg.WEB_HEADER_COLOR}', + role="cell") # Skip any mailing lists that isn't advertised. advertised = [] @@ -198,7 +204,7 @@ def listinfo_overview(msg=''): '.

                  ')) table.AddRow([Container(*welcome)]) - table.AddCellInfo(max(table.GetCurrentRowIndex(), 0), 0, colspan=2) + table.AddCellInfo(max(table.GetCurrentRowIndex(), 0), 0, colspan=2, role="cell") if advertised: table.AddRow([' ', ' ']) @@ -212,7 +218,8 @@ def listinfo_overview(msg=''): description or Italic(_('[no description available]'))]) if highlight and mm_cfg.WEB_HIGHLIGHT_COLOR: table.AddRowInfo(table.GetCurrentRowIndex(), - bgcolor=mm_cfg.WEB_HIGHLIGHT_COLOR) + style=f'background-color: {mm_cfg.WEB_HIGHLIGHT_COLOR}', + role="row") highlight = not highlight doc.AddItem(table) @@ -271,7 +278,7 @@ def list_listinfo(mlist, language): you are not a bot:""" ) replacements[''] = ( - """

                  """ + """""" % (pre_question, captcha_question, captcha_box)) else: # just to have something to include in the hash below @@ -308,7 +315,7 @@ def list_listinfo(mlist, language): if mm_cfg.RECAPTCHA_SITE_KEY: noscript = _('This form requires JavaScript.') replacements[''] = ( - """' + for i in range(len(self.cells)): output = output + self.FormatRow(i, indent + 2) @@ -309,7 +335,7 @@ def SetTitle(self, title): self.title = title def Format(self, indent=0, **kws): - charset = 'latin-1' + charset = 'utf-8' if self.language and Utils.IsLanguage(self.language): charset = Utils.GetCharSet(self.language) output = ['Content-Type: text/html; charset=%s\n' % charset] @@ -318,33 +344,97 @@ def Format(self, indent=0, **kws): kws.setdefault('bgcolor', self.bgcolor) tab = ' ' * indent output.extend([tab, - '', - '' + '' % (self.language or 'en'), + '' ]) if mm_cfg.IMAGE_LOGOS: - output.append('' % + output.append('' % (mm_cfg.IMAGE_LOGOS + mm_cfg.SHORTCUT_ICON)) - # Hit all the bases - output.append('' % charset) + # Add viewport meta tag for responsive design + output.append('') + # Add charset meta tag + output.append('' % charset) if self.title: - output.append('%s%s' % (tab, self.title)) - # Add CSS to visually hide some labeling text but allow screen - # readers to read it. + output.append('%s%s' % (tab, self.title)) + # Add modern CSS styling output.append("""\ - """) if mm_cfg.WEB_HEAD_ADD: output.append(mm_cfg.WEB_HEAD_ADD) - output.append('%s' % tab) + output.append('%s' % tab) + output.append('%s' % tab) quals = [] # Default link colors if mm_cfg.WEB_VLINK_COLOR: @@ -355,15 +445,15 @@ def Format(self, indent=0, **kws): kws.setdefault('link', mm_cfg.WEB_LINK_COLOR) for k, v in list(kws.items()): quals.append('%s="%s"' % (k, v)) - output.append('%s' % tab) # Language direction direction = Utils.GetDirection(self.language) output.append('dir="%s">' % direction) # Always do this... output.append(Container.Format(self, indent)) if not self.suppress_head: - output.append('%s' % tab) - output.append('%s' % tab) + output.append('%s' % tab) + output.append('%s' % tab) return NL.join(output) def addError(self, errmsg, tag=None): diff --git a/doc/mailman-admin/about.html b/doc/mailman-admin/about.html index 39df1de4..2beae4c6 100644 --- a/doc/mailman-admin/about.html +++ b/doc/mailman-admin/about.html @@ -1,112 +1,85 @@ + - - - - - - - - - + + + + + + + + + About this document ...
                  -

                  ¼«Ê¬¤Î¥á¡¼¥ë¥¢¥É¥ì¥¹¤ò¥¯¥ê¥Ã¥¯¤¹¤ë¤È¡¢ - ²ñ°÷¥ª¥×¥·¥ç¥ó¥Ú¡¼¥¸¤òɽ¼¨¤·¤Þ¤¹¡£ -
                  (³ç¸ÌÆâ¤Î¹àÌܤÏÇÛÁ÷Ää»ßÃæ¤Î²ñ°÷¤Ç¤¹¡£)

                  +

                  ʬΥ᡼륢ɥ쥹򥯥åȡ + ץڡɽޤ��� +
                  (�����ι��ܤ����������β���Ǥ���)

                  - ÉáÄÌÇÛÁ÷²ñ°÷: ̾ + �����������: ̾
                  - ¤Þ¤È¤áÆÉ¤ß²ñ°÷: ̾ + �ޤȤ��ɤ߲��: ̾
                  [ ' + _('Gzip\'d Text%(sz)s') \ + templ = '[ ' + _('Gzip\'d Text%(sz)s') \ + '][ ' + _('Text%(sz)s') + '][ ' + _('Text%(sz)s') + ']
                  %s
                  %s
                  %s
                  %s
                  %s
                  %s
                    + """
                    diff --git a/Mailman/Cgi/options.py b/Mailman/Cgi/options.py index 0005e686..dba7031f 100644 --- a/Mailman/Cgi/options.py +++ b/Mailman/Cgi/options.py @@ -1006,20 +1006,28 @@ def options_page(mlist, doc, user, cpuser, userlang, message=''): # but the user still wants to get that topic message? usertopics = mlist.getMemberTopics(user) if mlist.topics: - table = Table(border="0") - for name, pattern, description, emptyflag in mlist.topics: - if emptyflag: - continue - quotedname = urllib.parse.quote_plus(name) - details = Link(mlist.GetScriptURL('options') + - '/%s/?VARHELP=%s' % (user, quotedname), - ' (Details)') - if name in usertopics: - checked = 1 - else: - checked = 0 - table.AddRow([CheckBox('usertopic', quotedname, checked=checked), - name + details.Format()]) + table = Table( + role="table", + aria_label=_("Topic Filter Details"), + border=3, + width='100%' + ) + table.AddRow([Center(Bold(_('Topic filter details')))]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, + style=f'background-color: {mm_cfg.WEB_SUBHEADER_COLOR}', + role="cell") + table.AddRow([Bold(Label(_('Name:'))), + Utils.websafe(name)]) + table.AddRow([Bold(Label(_('Pattern (as regexp):'))), + '
                  ' + Utils.websafe(OR.join(pattern.splitlines()))
                  +                       + '
                  ']) + table.AddRow([Bold(Label(_('Description:'))), + Utils.websafe(description)]) + # Make colors look nice + for row in range(1, 4): + table.AddCellInfo(row, 0, + style=f'background-color: {mm_cfg.WEB_ADMINITEM_COLOR}', + role="cell") topicsfield = table.Format() else: topicsfield = _('No topics defined') @@ -1056,12 +1064,20 @@ def loginpage(mlist, doc, user, lang): # Set up the title doc.SetTitle(title) # We use a subtable here so we can put a language selection box in - table = Table(width='100%', border=0, cellspacing=4, cellpadding=5) + table = Table( + role="table", + aria_label=_("Member Options"), + width='100%', + border=0, + cellspacing=4, + cellpadding=5 + ) # If only one language is enabled for this mailing list, omit the choice # buttons. table.AddRow([Center(Header(2, title))]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, - bgcolor=mm_cfg.WEB_HEADER_COLOR) + style=f'background-color: {mm_cfg.WEB_HEADER_COLOR}', + role="cell") if len(mlist.available_languages) > 1: langform = Form(actionurl) langform.AddItem(SubmitButton('displang-button', @@ -1075,7 +1091,14 @@ def loginpage(mlist, doc, user, lang): # Set up the login page form = Form(actionurl) form.AddItem(Hidden('language', lang)) - table = Table(width='100%', border=0, cellspacing=4, cellpadding=5) + table = Table( + role="table", + aria_label=_("Login Form"), + width='100%', + border=0, + cellspacing=4, + cellpadding=5 + ) table.AddRow([_(f"""In order to change your membership option, you must first log in by giving your {extra}membership password in the section below. If you don't remember your membership password, you can have it @@ -1088,7 +1111,14 @@ def loginpage(mlist, doc, user, lang): effect. """)]) # Password and login button - ptable = Table(width='50%', border=0, cellspacing=4, cellpadding=5) + ptable = Table( + role="table", + aria_label=_("Password Form"), + width='50%', + border=0, + cellspacing=4, + cellpadding=5 + ) if user is None: ptable.AddRow([Label(_('Email address:')), TextBox('email', size=20)]) @@ -1102,7 +1132,8 @@ def loginpage(mlist, doc, user, lang): # Unsubscribe section table.AddRow([Center(Header(2, _('Unsubscribe')))]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, - bgcolor=mm_cfg.WEB_HEADER_COLOR) + style=f'background-color: {mm_cfg.WEB_HEADER_COLOR}', + role="cell") table.AddRow([_(f"""By clicking on the Unsubscribe button, a confirmation message will be emailed to you. This message will have a @@ -1114,7 +1145,8 @@ def loginpage(mlist, doc, user, lang): # Password reminder section table.AddRow([Center(Header(2, _('Password reminder')))]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, - bgcolor=mm_cfg.WEB_HEADER_COLOR) + style=f'background-color: {mm_cfg.WEB_HEADER_COLOR}', + role="cell") table.AddRow([_(f"""By clicking on the Remind button, your password will be emailed to you.""")]) @@ -1230,10 +1262,16 @@ def topic_details(mlist, doc, user, cpuser, userlang, varhelp): print(doc.Format()) return - table = Table(border=3, width='100%') + table = Table( + role="table", + aria_label=_("Topic Filter Details"), + border=3, + width='100%' + ) table.AddRow([Center(Bold(_('Topic filter details')))]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, - bgcolor=mm_cfg.WEB_SUBHEADER_COLOR) + style=f'background-color: {mm_cfg.WEB_SUBHEADER_COLOR}', + role="cell") table.AddRow([Bold(Label(_('Name:'))), Utils.websafe(name)]) table.AddRow([Bold(Label(_('Pattern (as regexp):'))), @@ -1243,7 +1281,9 @@ def topic_details(mlist, doc, user, cpuser, userlang, varhelp): Utils.websafe(description)]) # Make colors look nice for row in range(1, 4): - table.AddCellInfo(row, 0, bgcolor=mm_cfg.WEB_ADMINITEM_COLOR) + table.AddCellInfo(row, 0, + style=f'background-color: {mm_cfg.WEB_ADMINITEM_COLOR}', + role="cell") options_page(mlist, doc, user, cpuser, userlang, table.Format()) print(doc.Format()) diff --git a/Mailman/Cgi/rmlist.py b/Mailman/Cgi/rmlist.py index b0e7313f..cd84142c 100644 --- a/Mailman/Cgi/rmlist.py +++ b/Mailman/Cgi/rmlist.py @@ -186,10 +186,16 @@ def process_request(doc, cgidata, mlist): title = _('Mailing list deletion results') doc.SetTitle(title) - table = Table(border=0, width='100%') + table = Table( + role="table", + aria_label=_("List Deletion Results"), + border=0, + width='100%' + ) table.AddRow([Center(Bold(FontAttr(title, size='+1')))]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, - bgcolor=mm_cfg.WEB_HEADER_COLOR) + style=f'background-color: {mm_cfg.WEB_HEADER_COLOR}', + role="cell") if not problems: table.AddRow([_(f'''You have successfully deleted the mailing list {listname}.''')]) @@ -214,10 +220,16 @@ def request_deletion(doc, mlist, errmsg=None): title = _('Permanently remove mailing list {realname}') doc.SetTitle(_('Permanently remove mailing list {realname}')) - table = Table(border=0, width='100%') + table = Table( + role="table", + aria_label=_("List Deletion Form"), + border=0, + width='100%' + ) table.AddRow([Center(Bold(FontAttr(title, size='+1')))]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, - bgcolor=mm_cfg.WEB_HEADER_COLOR) + style=f'background-color: {mm_cfg.WEB_HEADER_COLOR}', + role="cell") # Add any error message if errmsg: @@ -243,26 +255,38 @@ def request_deletion(doc, mlist, errmsg=None): """)]) GREY = mm_cfg.WEB_ADMINITEM_COLOR form = Form(mlist.GetScriptURL('rmlist')) - ftable = Table(border=0, cols='2', width='100%', - cellspacing=3, cellpadding=4) + ftable = Table( + role="table", + aria_label=_("List Deletion Form Fields"), + border=0, + cols='2', + width='100%', + cellspacing=3, + cellpadding=4 + ) ftable.AddRow([Label(_('List password:')), PasswordBox('password')]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY) + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, + style=f'background-color: {GREY}', + role="cell") + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, + style=f'background-color: {GREY}', + role="cell") - ftable.AddRow([Label(_('Also delete archives?')), - RadioButtonArray('delarchives', (_('No'), _('Yes')), - checked=0, values=(0, 1))]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY) + ftable.AddRow([Label(_('Delete archives?')), + RadioButtonArray('delarchives', + (_('No'), _('Yes')), + checked=0, + values=(0, 1))]) + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, + style=f'background-color: {GREY}', + role="cell") + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, + style=f'background-color: {GREY}', + role="cell") - ftable.AddRow([Center(Link( - mlist.GetScriptURL('admin'), - _('Cancel and return to list administration')))]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, colspan=2) - - ftable.AddRow([Center(SubmitButton('doit', _('Delete this list')))]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, colspan=2) + ftable.AddRow([Center(SubmitButton('doit', _('Delete List')))]) + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, colspan=2, role="cell") form.AddItem(ftable) table.AddRow([form]) doc.AddItem(table) diff --git a/Mailman/HTMLFormatter.py b/Mailman/HTMLFormatter.py index 39cbf7d5..457bf045 100644 --- a/Mailman/HTMLFormatter.py +++ b/Mailman/HTMLFormatter.py @@ -113,7 +113,7 @@ def FormatOptionButton(self, option, value, user): else: optval = self.getMemberOption(user, option) if optval == value: - checked = ' CHECKED' + checked = ' checked' else: checked = '' name = {mm_cfg.DontReceiveOwnPosts : 'dontreceive', @@ -126,15 +126,15 @@ def FormatOptionButton(self, option, value, user): mm_cfg.ReceiveNonmatchingTopics : 'rcvtopic', mm_cfg.DontReceiveDuplicates : 'nodupes', }[option] - return '' % ( + return '' % ( name, value, checked) def FormatDigestButton(self): if self.digest_is_default: - checked = ' CHECKED' + checked = ' checked' else: checked = '' - return '' % checked + return '' % checked def FormatDisabledNotice(self, user): status = self.getDeliveryStatus(user) @@ -158,24 +158,27 @@ def FormatDisabledNotice(self, user): link = Link('#disable', _('Mail delivery')).Format() mailto = Link('mailto:' + self.GetOwnerEmail(), _('the list administrator')).Format() - return _(f'''

                  {note} - -

                  You may have disabled list delivery intentionally, - or it may have been triggered by bounces from your email - address. In either case, to re-enable delivery, change the - {link} option below. Contact {mailto} if you have any - questions or need assistance.''') + return _(f'''

                  +

                  {note}

                  +

                  You may have disabled list delivery intentionally, + or it may have been triggered by bounces from your email + address. In either case, to re-enable delivery, change the + {link} option below. Contact {mailto} if you have any + questions or need assistance.

                  +
                  ''') elif info and info.score > 0: # Provide information about their current bounce score. We know # their membership is currently enabled. score = info.score total = self.bounce_score_threshold - return _(f'''

                  We have received some recent bounces from your - address. Your current bounce score is {score} out of a - maximum of {total}. Please double check that your subscribed - address is correct and that there are no problems with delivery to - this address. Your bounce score will be automatically reset if - the problems are corrected soon.''') + return _(f'''

                  +

                  We have received some recent bounces from your + address. Your current bounce score is {score} out of a + maximum of {total}. Please double check that your subscribed + address is correct and that there are no problems with delivery to + this address. Your bounce score will be automatically reset if + the problems are corrected soon.

                  +
                  ''') else: return '' diff --git a/Mailman/htmlformat.py b/Mailman/htmlformat.py index f0689875..6ec04d26 100644 --- a/Mailman/htmlformat.py +++ b/Mailman/htmlformat.py @@ -78,96 +78,118 @@ def __init__(self, **table_opts): self.cell_info = {} self.row_info = {} self.opts = table_opts + self.current_row = -1 + self.current_cell = -1 def AddOptions(self, opts): - DictMerge(self.opts, opts) - - # Sets all of the cells. It writes over whatever cells you had there - # previously. + self.opts.update(opts) def SetAllCells(self, cells): self.cells = cells - # Add a new blank row at the end def NewRow(self): self.cells.append([]) + self.current_row = len(self.cells) - 1 + self.current_cell = -1 - # Add a new blank cell at the end def NewCell(self): - self.cells[-1].append('') + self.cells[self.current_row].append(None) + self.current_cell = len(self.cells[self.current_row]) - 1 def AddRow(self, row): self.cells.append(row) def AddCell(self, cell): - self.cells[-1].append(cell) + if self.current_row < 0: + self.NewRow() + self.cells[self.current_row].append(cell) def AddCellInfo(self, row, col, **kws): - kws = CaseInsensitiveKeyedDict(kws) if row not in self.cell_info: - self.cell_info[row] = { col : kws } - elif col in self.cell_info[row]: - DictMerge(self.cell_info[row], kws) - else: - self.cell_info[row][col] = kws + self.cell_info[row] = {} + self.cell_info[row][col] = kws def AddRowInfo(self, row, **kws): - kws = CaseInsensitiveKeyedDict(kws) - if row not in self.row_info: - self.row_info[row] = kws - else: - DictMerge(self.row_info[row], kws) + self.row_info[row] = kws - # What's the index for the row we just put in? def GetCurrentRowIndex(self): - return len(self.cells)-1 + return self.current_row - # What's the index for the col we just put in? def GetCurrentCellIndex(self): - return len(self.cells[-1])-1 + return self.current_cell def ExtractCellInfo(self, info): - valid_mods = ['align', 'valign', 'nowrap', 'rowspan', 'colspan', - 'bgcolor'] output = '' - - for (key, val) in list(info.items()): - if not key in valid_mods: - continue - if key == 'nowrap': - output = output + ' NOWRAP' - continue - else: - output = output + ' %s="%s"' % (key.upper(), val) - + # Convert deprecated attributes to modern equivalents + if 'bgcolor' in info: + info['style'] = info.get('style', '') + f'background-color: {info["bgcolor"]};' + del info['bgcolor'] + if 'align' in info: + info['style'] = info.get('style', '') + f'text-align: {info["align"]};' + del info['align'] + if 'valign' in info: + info['style'] = info.get('style', '') + f'vertical-align: {info["valign"]};' + del info['valign'] + if 'width' in info: + info['style'] = info.get('style', '') + f'width: {info["width"]};' + del info['width'] + if 'height' in info: + info['style'] = info.get('style', '') + f'height: {info["height"]};' + del info['height'] + # Add ARIA attributes for accessibility + if 'role' not in info: + info['role'] = 'cell' + for k, v in list(info.items()): + output = output + ' %s="%s"' % (k, v) return output def ExtractRowInfo(self, info): - valid_mods = ['align', 'valign', 'bgcolor'] output = '' - - for (key, val) in list(info.items()): - if not key in valid_mods: - continue - output = output + ' %s="%s"' % (key.upper(), val) - + # Convert deprecated attributes to modern equivalents + if 'bgcolor' in info: + info['style'] = info.get('style', '') + f'background-color: {info["bgcolor"]};' + del info['bgcolor'] + if 'align' in info: + info['style'] = info.get('style', '') + f'text-align: {info["align"]};' + del info['align'] + if 'valign' in info: + info['style'] = info.get('style', '') + f'vertical-align: {info["valign"]};' + del info['valign'] + # Add ARIA attributes for accessibility + if 'role' not in info: + info['role'] = 'row' + for k, v in list(info.items()): + output = output + ' %s="%s"' % (k, v) return output def ExtractTableInfo(self, info): - valid_mods = ['align', 'width', 'border', 'cellspacing', 'cellpadding', - 'bgcolor'] - output = '' - - for (key, val) in list(info.items()): - if not key in valid_mods: - continue - if key == 'border' and val is None: - output = output + ' BORDER' - continue - else: - output = output + ' %s="%s"' % (key.upper(), val) - + # Convert deprecated attributes to modern equivalents + if 'bgcolor' in info: + info['style'] = info.get('style', '') + f'background-color: {info["bgcolor"]};' + del info['bgcolor'] + if 'align' in info: + info['style'] = info.get('style', '') + f'margin-left: auto; margin-right: auto;' + del info['align'] + if 'width' in info: + info['style'] = info.get('style', '') + f'width: {info["width"]};' + del info['width'] + if 'cellpadding' in info: + info['style'] = info.get('style', '') + f'border-spacing: {info["cellpadding"]}px;' + del info['cellpadding'] + if 'cellspacing' in info: + info['style'] = info.get('style', '') + f'border-collapse: separate; border-spacing: {info["cellspacing"]}px;' + del info['cellspacing'] + if 'border' in info: + info['style'] = info.get('style', '') + f'border: {info["border"]}px solid #ccc;' + del info['border'] + # Add ARIA attributes for accessibility + if 'role' not in info: + info['role'] = 'table' + if 'aria-label' not in info: + info['aria-label'] = 'Data table' + for k, v in list(info.items()): + output = output + ' %s="%s"' % (k, v) return output def FormatCell(self, row, col, indent): @@ -209,6 +231,10 @@ def Format(self, indent=0): output = output + self.ExtractTableInfo(self.opts) output = output + '>' + # Add caption for accessibility if not present + if 'aria-label' in self.opts: + output = output + '\n' + ' '*(indent+2) + '
                  ' + self.opts['aria-label'] + '
                  +
                  +
                  - - - - - - - + + + + + + +
                  Up one LevelNext PageGNU Mailman - List Administration ManualContentsUp one LevelNext PageGNU Mailman - List Administration ManualContents
                  - +
                  -

                  About this document ...

                  - GNU Mailman - List Administration Manual, +GNU Mailman - List Administration Manual, January 11, 2020, Release 2.1 -

                  This document was generated using the - LaTeX2HTML translator. +

                  This document was generated using the +LaTeX2HTML translator.

                  - -

                  - LaTeX2HTML is Copyright © - 1993, 1994, 1995, 1996, 1997, Nikos +

                  +LaTeX2HTML is Copyright © + 1993, 1994, 1995, 1996, 1997, Nikos Drakos, Computer Based Learning Unit, University of - Leeds, and Copyright © 1997, 1998, Ross + Leeds, and Copyright © 1997, 1998, Ross Moore, Mathematics Department, Macquarie University, Sydney.

                  - -

                  The application of - LaTeX2HTML to the Python +

                  The application of +LaTeX2HTML to the Python documentation has been heavily tailored by Fred L. Drake, Jr. Original navigation icons were contributed by Christopher Petrilli.

                  -
                  + diff --git a/templates/ar/archliststart.html b/templates/ar/archliststart.html index d030e43e..30fe06ed 100644 --- a/templates/ar/archliststart.html +++ b/templates/ar/archliststart.html @@ -1,4 +1,68 @@ - - - - +
                  أرشيÙمشاهد من قبل:نسخة ممكن تحميلها
                  + + + +
                  أرشيÙمشاهد من قبل:نسخة ممكن تحميلها
                  \ No newline at end of file diff --git a/templates/ar/archtoc.html b/templates/ar/archtoc.html index e7a23c43..99b40dad 100644 --- a/templates/ar/archtoc.html +++ b/templates/ar/archtoc.html @@ -1,14 +1,78 @@ + - - Ø£Ø±Ø´ÙŠÙØ§Øª القائمة %(listname)s - - + +Ø£Ø±Ø´ÙŠÙØ§Øª القائمة %(listname)s + + %(meta)s - - -

                  Ø£Ø±Ø´ÙŠÙØ§Øª القائمة %(listname)s

                  -

                  + + +

                  Ø£Ø±Ø´ÙŠÙØ§Øª القائمة %(listname)s

                  +

                  يمكنك الحصول على معلومات أكثر حول هذه القائمة أو يمكنك تحميل الأرشي٠الخام الكامل (%(size)s). @@ -17,5 +81,5 @@

                  Ø£Ø±Ø´ÙŠÙØ§Øª القائمة %(listname)s

                  %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/ar/archtocentry.html b/templates/ar/archtocentry.html index bfa4437c..0a5cc080 100644 --- a/templates/ar/archtocentry.html +++ b/templates/ar/archtocentry.html @@ -1,12 +1,74 @@ - -
                %(archivelabel)s: - [ السلسلة ] - [ العنوان ] - [ الكاتب ] - [ التاريخ ] -
                %(archivelabel)s: +[ السلسلة ] +[ العنوان ] +[ الكاتب ] +[ التاريخ ] +
                - - - - - - - - - - - - - - - - - - + + + + + +

                - -- - -
                -

                  -

                - حول - - - -
                -

                -

                لترى مجموعة الإرسالات السابقة لهذه القائمة، - اذهب إلى أرشي٠- . - -

                -
                - باستعمال -
                + +<mm-list-name> ØµÙØ­Ø© المعلومات</mm-list-name> + + + +

                + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + +
                + -- + +
                +

                  +

                +حول + + + +
                +

                +

                لترى مجموعة الإرسالات السابقة لهذه القائمة، + اذهب إلى أرشي٠+. + +

                +
                +باستعمال +
                لإرسال رسالة إلى جميع أعضاء القائمة أرسل البريد إلى - . + .

                يمكنك الاشتراك ÙÙŠ هذه القائمة أو أن تغير اشتراكك الحالي ÙÙŠ الأقسام تحت. -

                - الاشتراك ÙÙŠ -
                -

                - اشترك ÙÙŠ عن طريق تعبئة النموذج التالي. - -

                  - - - - - - - - - - - - + + + + + + - - - - - -
                  عنوانك الإلكتروني: -  
                  اسمك )اختياري(: 
                  يمكنك إدخال كلمة سر +

                  +الاشتراك ÙÙŠ +
                  +

                  + اشترك ÙÙŠ عن طريق تعبئة النموذج التالي. + +

                    + + + + + + + + + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - - - -
                    عنوانك الإلكتروني: + 
                    اسمك )اختياري(: 
                    يمكنك إدخال كلمة سر الخصوصية تحت. هذا يعطيك أمناً بسيطاً ولكن سيمنع الآخرين من اللعب باشتراكك. لا تستعمل كلمة سر مهمة حيث أنها سترسل إليك بعض الأوقات بشكل نص واضح. -

                    إذا اخترت أن لا تدخل كلمة سر ÙØ³ÙŠØªÙ… تكوين +

                    إذا اخترت أن لا تدخل كلمة سر ÙØ³ÙŠØªÙ… تكوين كلمة سر لك تلقائياً وسيتم إرسالها إليك عندما تقوم بتأكيد اشتراكك. يمكنك دائماً طلب رسالة تعيد إليك كلمة سرك عندما تعدل خياراتك الشخصية. - -
                    -
                    اختر كلمة سر: 
                    أعد إدخال كلمة السر للتأكيد: 
                    ما اللغة التي ØªÙØ¶Ù„ها لإظهار رسائلك؟  
                    هل تحب أن تستقبل البريد يومياً على شكل Ø¯ÙØ¹Ø§ØªØŸ + + +
                    اختر كلمة سر: 
                    أعد إدخال كلمة السر للتأكيد: 
                    ما اللغة التي ØªÙØ¶Ù„ها لإظهار رسائلك؟  
                    هل تحب أن تستقبل البريد يومياً على شكل Ø¯ÙØ¹Ø§ØªØŸ No - Yes -
                    -
                    -
                    - -
                  -
                  - - المشتركين ÙÙŠ القائمة -
                  - - - -

                  - - - -

                  - - - +
                No + Yes +
                +
                +
                + + +

                + +المشتركين ÙÙŠ القائمة +
                + + + +

                + + + +

                + +

                + diff --git a/templates/ar/options.html b/templates/ar/options.html index 01dfa0bb..74d4d2f0 100644 --- a/templates/ar/options.html +++ b/templates/ar/options.html @@ -1,43 +1,103 @@ - - - ضبط اشتراك المستخدم <MM-Presentable-User> ÙÙŠ القائمة البريدية <MM-List-Name> - - - - - -
                - - ضبط القائمة البريدية للمستخدم - -
                + + +ضبط اشتراك المستخدم <mm-presentable-user> ÙÙŠ القائمة البريدية <mm-list-name> +</mm-list-name></mm-presentable-user> + + + + +
                + + ضبط القائمة البريدية للمستخدم + +

                - - - - - +
                - حالة اشتراك المستخدم , - وكلمة السر وخيارات القائمة البريدية . -
                - - - - -

                -

                + + + +
                + حالة اشتراك المستخدم , + وكلمة السر وخيارات القائمة البريدية . +
                + + +

                +

                - - +

                - - - +
                - - تغيير معلومات اشتراكك ÙÙŠ القائمة -
                يمكنك تغيير العنوان الذي اشتركت به ÙÙŠ القائمة البريدية + + + - - - - - -
                + +تغيير معلومات اشتراكك ÙÙŠ القائمة +
                يمكنك تغيير العنوان الذي اشتركت به ÙÙŠ القائمة البريدية عن طريق إدخال العنوان الجديد ÙÙŠ الحقول تحت. لاحظ أن رسالة تأكيد سو٠يتم إرسالها إلى العنوان الجديد، ويجب أن يتم تأكيد التغيير قبل أن يتم عمله. @@ -51,193 +111,172 @@ ÙÙŠ الموقع , ÙØ¹Ù„ الخيار تغيير عام. -
                - - - - - - - -
                العنوان الجديد:
                مرة أخرى للتأكيد:
                -
                - - - - -
                اسمك )اختياري(:
                -
                -

                تغيير عام

                - +

                + + + + + + + +
                العنوان الجديد:
                مرة أخرى للتأكيد:
                +

                + + + + +
                اسمك )اختياري(:
                +
                +

                تغيير عام

                - - - - - - - + + + + %(textlink)s diff --git a/templates/ast/archtocnombox.html b/templates/ast/archtocnombox.html index 04ae46d0..09af5b4b 100644 --- a/templates/ast/archtocnombox.html +++ b/templates/ast/archtocnombox.html @@ -1,18 +1,82 @@ - - - Archivos de %(listname)s - + + + +Archivos de %(listname)s + %(meta)s - - -

                Archivos de %(listname)s

                -

                - Puedes obtener más información sobro esta llista -

                + + +

                Archivos de %(listname)s

                +

                + Puedes obtener más información sobro esta llista +

                %(noarchive_msg)s %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/ast/article.html b/templates/ast/article.html index b6509cf6..0a42ec67 100644 --- a/templates/ast/article.html +++ b/templates/ast/article.html @@ -1,50 +1,111 @@ - - - %(title)s - - - - + + + + %(title)s + + + + %(encoding)s %(prev)s %(next)s - - -

                %(subject_html)s

                - %(author_html)s - %(email_html)s -
                - %(datestr_html)s -

                  + + +

                  %(subject_html)s

                  +%(author_html)s +%(email_html)s +
                  +%(datestr_html)s +

                  -
                  +
                • Mensaxes axeitaos por: +[ data ] +[ filu ] +[ asuntu ] +[ autor ] +
                • +
                +
                %(body)s -
                -

                +
                +Más información sobro la llista de distribución %(listname)s
                +

                diff --git a/templates/ast/emptyarchive.html b/templates/ast/emptyarchive.html index cdce3dd0..b4ad54d2 100644 --- a/templates/ast/emptyarchive.html +++ b/templates/ast/emptyarchive.html @@ -1,13 +1,77 @@ - - - Archivo de la lista %(listname)s - - - -

                Archivu de la llista %(listname)s

                -

                - Entá nun s'unvió dengún mensax a esta llista, de mou que l'archivu ta anguaño ermu. Puedes obtener más información tocante a esta llista. + + + +Archivo de la lista %(listname)s + + + +

                Archivu de la llista %(listname)s

                +

                + Entá nun s'unvió dengún mensax a esta llista, de mou que l'archivu ta anguaño ermu. Puedes obtener más información tocante a esta llista.

                - - + + diff --git a/templates/ast/headfoot.html b/templates/ast/headfoot.html index 9273eefd..7af8f373 100644 --- a/templates/ast/headfoot.html +++ b/templates/ast/headfoot.html @@ -1,20 +1,82 @@ -Esti testu puede incluyir -cadenes de formatu Python que sustituyiránse polos atributos de la llista. La llista de sustituciones permitíes son: +Esti testu puede incluyir +cadenes de formatu Python que sustituyiránse polos atributos de la llista. La llista de sustituciones permitíes son:
                  -
                • real_name - El nome 'presentable' de la llista. Davezu ye'l nome de la llista cola primer lletra en mayúscula. +
                • real_name - El nome 'presentable' de la llista. Davezu ye'l nome de la llista cola primer lletra en mayúscula. -
                • list_name - El nome pol que s'identifica la llista nes URLs, estrémense mayúscules de minúscules. +
                • list_name - El nome pol que s'identifica la llista nes URLs, estrémense mayúscules de minúscules. -
                • host_name - El nome canónicu del sirvidor qu'agospia la llista. +
                • host_name - El nome canónicu del sirvidor qu'agospia la llista. -
                • web_page_url - El URL base de Mailman. Por exemplu pa indicar la páxina d'información de la llista namái habría d'amestá-y darrera: listinfo/%(list_name)s +
                • web_page_url - El URL base de Mailman. Por exemplu pa indicar la páxina d'información de la llista namái habría d'amestá-y darrera: listinfo/%(list_name)s +
                • description - Una descripción concisa tocante a la llista de distribución. -
                • description - Una descripción concisa tocante a la llista de distribución. +
                • info - La descripción completa tocante a la + llista de distribución. -
                • info - La descripción completa tocante a la - llista de distribución. - -
                • cgiext - La estensión que s'amiesta a los guiones +
                • cgiext - La estensión que s'amiesta a los guiones CGI. -
                + diff --git a/templates/ast/listinfo.html b/templates/ast/listinfo.html index c000dae1..56d0407c 100644 --- a/templates/ast/listinfo.html +++ b/templates/ast/listinfo.html @@ -1,136 +1,192 @@ - - - <MM-List-Name> Página de Información de <MM-List-Name> - - - - -

                -

                - إلغاء الاشتراك ÙÙŠ القائمة - اشتراكاتك الأخرى ÙÙŠ الموقع -
                + + + + - + +
                +

                +إلغاء الاشتراك ÙÙŠ القائمة +اشتراكاتك الأخرى ÙÙŠ الموقع +
                ÙØ¹Ù„ خيار التأكيد واضغط على هذا الزر لإلغاء الاشتراك من القائمة البريدية. تحذير: هذا الإجراء سيتم مباشرة.

                -

                +

                يمكنك رؤية قائمة بجميع القوائم البريدية الأخرى ÙÙŠ الموقع والتي أنت عضو مشترك Ùيها. استعمل هذا إذا كنت تريد عمل Ù†ÙØ³ تعديلات خيارات الاشتراك للاشتراكات الأخرى.

                -

                -
                - - - - - +
                - كلمة سرك للقائمة البريدية -
                - -
                -

                هل نسيت كلمة سرك؟

                -
                + + + - -
                +كلمة سرك للقائمة البريدية +
                + +
                +

                هل نسيت كلمة سرك؟

                +
                انقر هذا الزر ليتم إرسال كلمة سرك برسالة إلى عنوان اشتراكك. -

                -

                - -
                -
                - -
                -

                غير كلمة سرك

                - - - - - - - - -
                كلمة السر الجديدة:
                مرة أخرى للتأكيد:
                - - -

                تغيير عام -
                -
                - +

                +

                + +
                +

                + +
                +

                غير كلمة سرك

                + + + + + + + + +
                كلمة السر الجديدة:
                مرة أخرى للتأكيد:
                + +

                تغيير عام +
                +

                - - +
                - خيارات اشتراكك ÙÙŠ القائمة -
                +
                +خيارات اشتراكك ÙÙŠ القائمة +
                -

                القيم الحالية مختارة. -

                لاحظ أن بعض الخيارات لها مربع خيار حدد بشكل عام يسبب اختيار هذا الحقل أن يحصل التعديل على جميع القوائم البريدية التي أنت عضو Ùيها على الموقع . انقر على اعرض اشتراكاتي الأخرى Ùوق لترى ما هي القوائم البريدية الأخرى المشترك Ùيها.

                - -
                - - توصيل البريد

                + + - +

                - - - + +

                - - - - - - + + - - + - - - - + + - - + - - + - - - +

                +
                + +توصيل البريد

                اجعل هذا الخيار ممكن لتستقبل الرسائل المرسلة إلى هذه القائمة البريدية. واجعله معطل إذا كنت تريد أن تبقى مشتركاً ولكن لا تريد أن يصلك أي رسائل Ù„ÙØªØ±Ø© مثل أن تكون ÙÙŠ إجازة. إذا قمت بتعديل توصيل البريد Ùلا تنسى أن تعيد ØªÙØ¹ÙŠÙ„Ù‡ عندما تعود، لأنه لن تتم إعادة ØªÙØ¹ÙŠÙ„Ù‡ تلقائياً. -

                - ممكن
                - معطل

                - حدد بشكل عام -

                +ممكن
                +معطل

                +حدد بشكل عام +

                - حدد نظام Ø§Ù„Ø¯ÙØ¹Ø§Øª

                +

                +حدد نظام Ø§Ù„Ø¯ÙØ¹Ø§Øª

                إذا ÙØ¹Ù„ت نظام Ø§Ù„Ø¯ÙØ¹Ø§Øª ستحصل على الإرسالات بشكل مجموعات )عادة مرة ÙÙŠ اليوم ولكن من الممكن أكثر ÙÙŠ القوائم المزدحمة(ØŒ عوضاً عن أن تحصل عليها Ù…Ù†ÙØ±Ø¯Ø© عندما يرسلونها. عندما يتم تغيير نظام Ø§Ù„Ø¯ÙØ¹Ø§Øª من ممكن إلى معطل Ùقد تستلم Ø¯ÙØ¹Ø© أخيرة واحدة. -

                - معطل
                - ممكن -
                - هل تريد Ø¯ÙØ¹Ø§Øª بنص عادي أم متعدد الأنواع MIMEØŸ

                +

                +معطل
                +ممكن +
                +هل تريد Ø¯ÙØ¹Ø§Øª بنص عادي أم متعدد الأنواع MIMEØŸ

                قد يدعم برنامج قراءة البريد الخاص بك Ø§Ù„Ø¯ÙØ¹Ø§Øª متعددة الأنواع MIME وبشكل عام Ø§Ù„Ø¯ÙØ¹Ø§Øª المتعددة الأنواع Ù…ÙØ¶Ù„Ø© ولكن إن كان عندك مشكلة ÙÙŠ قراءتها ÙØ§Ø®ØªØ± Ø¯ÙØ¹Ø§Øª نص عادي. -

                - متعددة الأنواع MIME
                - نص عادي

                - حدد بشكل عام -

                +متعددة الأنواع MIME
                +نص عادي

                +حدد بشكل عام +

                - هل تريد الحصول على إستلام إرسالاتك الخاصة إلى القائمة؟

                +

                +هل تريد الحصول على إستلام إرسالاتك الخاصة إلى القائمة؟

                ستستلم بشكل عادي بإذن الله نسخة من كل رسالة ترسلها إلى القائمة. إن كنت لا تريد استلام نسخة اختر لهذا الخيار لا. -

                - لا
                - نعم -
                - هل تريد أن تستلم رسالة إعلام بالوصول عندما ترسل رسالة إلى القائمة؟

                -

                - لا
                - نعم -
                - هل تريد رسائل تذكير بكلمة سر هذه القائمة؟

                +

                +لا
                +نعم +
                +هل تريد أن تستلم رسالة إعلام بالوصول عندما ترسل رسالة إلى القائمة؟

                +

                +لا
                +نعم +
                +هل تريد رسائل تذكير بكلمة سر هذه القائمة؟

                ستستلم بإذن الله ÙÙŠ كل شهر رسالة تحتوي على تذكير بكلمة السر لكل قائمة أنت مشترك بها على هذا الموقع. يمكنك أن تعطل هذا لكل قائمة على حدة باختيار لا لهذا الخيار. إذا قمت بتعطيل مذكرات كلمة السر لجميع القوائم التي تشترك Ùيها Ùلن يتم إرسال أي رسائل تذكير إليك. -

                - لا
                - نعم

                - حدد بشكل عام -

                - أظهر Ù†ÙØ³Ùƒ ÙÙŠ قائمة المشتركين؟

                +

                +لا
                +نعم

                +حدد بشكل عام +

                +أظهر Ù†ÙØ³Ùƒ ÙÙŠ قائمة المشتركين؟

                عندما يرى أحد ما قائمة المشتركين، ÙØ³ÙˆÙ يتم إظهار عنوانك الإلكتروني بطريقة مبهمة ليتم تضليل المنقبات الخاصة بمرسلي الرسائل الغير مرغوبة. إن كنت لا تريد أن يتم عرض عنوانك البريدي ÙÙŠ قائمة المشتركين أبداً ÙØ§Ø®ØªØ± نعم لهذا الخيار. -

                - لا
                - نعم -
                - ما هي اللغة التي ØªÙØ¶Ù„ها؟

                -

                - -
                - ما هي ØªØµÙ†ÙŠÙØ§Øª المواضيع التي تريد أن تشترك Ùيها؟

                +

                +لا
                +نعم +
                +ما هي اللغة التي ØªÙØ¶Ù„ها؟

                +

                + +
                +ما هي ØªØµÙ†ÙŠÙØ§Øª المواضيع التي تريد أن تشترك Ùيها؟

                باختيار موضوع أو أكثر يمكنك أن تصÙÙŠ الحركة على قائمتك البريدية، بحيث تستلم Ùقط قسم من الرسائل. إذا تطابقت رسالة أحد مواضيعك المختارة ÙØ³ÙˆÙ تحصل على الرسالة وإلا Ùلا. @@ -246,12 +285,11 @@

                غير كلمة سرك

                قاعدة التوصيل بناءاً على ضبط الخيار ÙÙŠ الأسÙÙ„. إذا لم تختر اي موضوع ÙØ³ÙˆÙ تستلم جميع الرسائل المرسلة إلى القائمة البريدية. -
                - -
                - هل تريد استلام الرسائل التي لا تتطابق مع أي مصÙÙŠ للموضوعات؟

                +

                + +
                +هل تريد استلام الرسائل التي لا تتطابق مع أي مصÙÙŠ للموضوعات؟

                يؤثر هذا الخيار Ùقط إذا اشتركت ÙÙŠ موضوع واحد على الأقل Ùوق. هذا الخيار يحدد قاعدة التوصيل Ø§Ù„Ø§ÙØªØ±Ø§Ø¶ÙŠØ© للرسائل التي لا تطابق @@ -261,13 +299,12 @@

                غير كلمة سرك

                إن لم يتم اختيار أي موضوع ÙÙŠ الأعلى ÙØ³ÙˆÙ تستلم جميع الرسائل المرسلة إلى القائمة البريدية. -

                - لا
                - نعم -
                - امنع النسخ المكررة من الرسائل؟

                +

                +لا
                +نعم +
                +امنع النسخ المكررة من الرسائل؟

                عندما يتم وضعك بشكل صريح ÙÙŠ ترويسة إلى: أو نسخة إلى: خاصة برسالة إلى القائمة، Ùيمكنك اختيار @@ -279,21 +316,17 @@

                غير كلمة سرك

                النسخ ÙØ³ÙˆÙ يتم Ø¥Ø¶Ø§ÙØ© ترويسة X-Mailman-Copy: yes ÙÙŠ كل نسخة. -
                - لا
                - نعم

                - حدد بشكل عام -

                -
                -
                +لا
                +نعم

                +حدد بشكل عام +

                +
                +
                -

                - - - - + + +

                diff --git a/templates/ar/private.html b/templates/ar/private.html index adec49a1..81c6857f 100755 --- a/templates/ar/private.html +++ b/templates/ar/private.html @@ -1,34 +1,95 @@ - التحقق من الشخصية للأرشي٠الخاص بالقائمة %(realname)s - - - - -
                +التحقق من الشخصية للأرشي٠الخاص بالقائمة %(realname)s + + + + + %(message)s - - - - - - - - - - - - - - - -
                - التحقق من الشخصية للأرشي٠الخاص بالقائمة %(realname)s -
                العنوان الإلكتروني:
                كلمة السر
                -
                -

                هام: من هذا النقطة يجب أن تكون + + + + + + + + + + + + + + + +
                +التحقق من الشخصية للأرشي٠الخاص بالقائمة %(realname)s +
                العنوان الإلكتروني:
                كلمة السر
                +
                +

                هام: من هذا النقطة يجب أن تكون الكوكيز Ù…ÙØ¹Ù„Ø© ÙÙŠ برنامج الاستعراض الذي تستخدمه، وإلا ÙØ¥Ù†Ùƒ ستضطر لوضع كلمة السر لكل عملية تقوم بها. @@ -36,21 +97,21 @@ صلاحيتها عندما تغلق مستعرضك، أو يمكنك إنهاء صلاحية الكوكي بشكل صريح عن طريق زيارة ØµÙØ­Ø© خيارات الأعضاء والضغط على زر تسجيل الخروج.

                - - - - - - + + + +
                - Password Reminder -
                If you don't remember your password, enter your email address + + + + + + - - - - -
                +Password Reminder +
                If you don't remember your password, enter your email address above and click the Remind button and your password will be emailed to you.
                - +
                +

                diff --git a/templates/ar/roster.html b/templates/ar/roster.html index 9f7e67d5..7fcba586 100644 --- a/templates/ar/roster.html +++ b/templates/ar/roster.html @@ -1,52 +1,111 @@ - - - <MM-List-Name> المشتركون - - - - -

                - - - - - - - - - - - - - - - -
                - - المشتركون -
                - -

                -

                - -

                اضعط على عنوانك لتذهب إلى ØµÙØ­Ø© خيارات اشتراكك.
                - (المدخلات التي بين قوسين معطل الإرسال إليها)

                -
                -
                - - أعضاء Ø§Ù„Ù„Ø§Ø¯ÙØ¹Ø§Øª للقائمة : -
                -
                -
                - أعضاء Ø§Ù„Ø¯ÙØ¹Ø§Øª للقائمة : -
                -
                -

                -

                -

                -

                - - - + + +<mm-list-name> المشتركون</mm-list-name> + + +

                + + + + + + + + + + + + + + + +
                + + المشتركون +
                +

                +

                +

                اضعط على عنوانك لتذهب إلى ØµÙØ­Ø© خيارات اشتراكك.
                + (المدخلات التي بين قوسين معطل الإرسال إليها)

                +
                +
                + + أعضاء Ø§Ù„Ù„Ø§Ø¯ÙØ¹Ø§Øª للقائمة : +
                +
                +
                + أعضاء Ø§Ù„Ø¯ÙØ¹Ø§Øª للقائمة : +
                +
                +

                +

                +

                +

                + +

                + diff --git a/templates/ar/subscribe.html b/templates/ar/subscribe.html index 152e0a51..9a319ce2 100644 --- a/templates/ar/subscribe.html +++ b/templates/ar/subscribe.html @@ -1,12 +1,75 @@ -<MM-List-Name> نتائج تسجيل الاشتراك - - +<mm-list-name> نتائج تسجيل الاشتراك</mm-list-name> + + -

                نتائج تسجيل الاشتراك

                - - - +

                نتائج تسجيل الاشتراك

                + + + diff --git a/templates/ast/admindbdetails.html b/templates/ast/admindbdetails.html index 3d7c17ba..16a41585 100644 --- a/templates/ast/admindbdetails.html +++ b/templates/ast/admindbdetails.html @@ -1,25 +1,88 @@ -Les solicitúes alministratives amuésense d'una de les dos formes, como una páxina de resume, o como una páxina con detalles. La páxina col sumariu contién solicitúes d'alta o baxa pendientes, unvíos a la llista que tán reteníos fasta que seyan aprobaos, agrupaos per direición de remitente. La páxina con los detalles -contién una vista más detallada de cada ún de los mensaxes reteníos, xunto coles cabeceres del mensax y un estrautu del cuerpu del mesmu. +Les solicitúes alministratives amuésense d'una de les dos formes, como una páxina de resume, o como una páxina con detalles. La páxina col sumariu contién solicitúes d'alta o baxa pendientes, unvíos a la llista que tán reteníos fasta que seyan aprobaos, agrupaos per direición de remitente. La páxina con los detalles +contién una vista más detallada de cada ún de los mensaxes reteníos, xunto coles cabeceres del mensax y un estrautu del cuerpu del mesmu. -

                En cualesquier d'estes páxines tán disponibles les siguientes aiciones: +

                En cualesquier d'estes páxines tán disponibles les siguientes aiciones: -

                • Diferir -- Pospón la decisión pa más sero. Nun s'aplica denguna aición agora pa esta solicitú alministrativa pendiente, pero entá y too puede reunviar o preservar el mensax (lleer más abaxo). +
                  • Diferir -- Pospón la decisión pa más sero. Nun s'aplica denguna aición agora pa esta solicitú alministrativa pendiente, pero entá y too puede reunviar o preservar el mensax (lleer más abaxo). -
                  • Aprobar -- Aprobar el mensax, unviándolo a la llista. Pa les solicitúes de soscrición, aprobar les mesmes. +
                  • Aprobar -- Aprobar el mensax, unviándolo a la llista. Pa les solicitúes de soscrición, aprobar les mesmes. -
                  • Refugar -- Refugar el mensax, unviando un mensax de refugu al remitente, y descartando'l mensax orixinal. Pa les solicitúes de soscrición, refuga les mesmes. En cualesquier casu, tendría de poner el motivu del refugu nel cuadru de testu axuntu. +
                  • Refugar -- Refugar el mensax, unviando un mensax de refugu al remitente, y descartando'l mensax orixinal. Pa les solicitúes de soscrición, refuga les mesmes. En cualesquier casu, tendría de poner el motivu del refugu nel cuadru de testu axuntu. -
                  • Descartar -- Tirar el mensax orixinal, ensin mandar un mensax de refugu. Pa les solicitúes de soscrición, esto namái descarta la solicitú ensin avisar a la persona que fizo la solicitud. Esta opción úsase davezu col corréu non solicitáu o puxarra (spam). -
                  +
                • Descartar -- Tirar el mensax orixinal, ensin mandar un mensax de refugu. Pa les solicitúes de soscrición, esto namái descarta la solicitú ensin avisar a la persona que fizo la solicitud. Esta opción úsase davezu col corréu non solicitáu o puxarra (spam). +
                +

                Pa los mensaxes reteníos, activa la opción Preservar si quies atroxar una copia de los mensaxes pal alministrador del sirvidor. Esto ye útil p'aquellos mensaxes abusivos que quies descartar, pero que necesites atroxar un rexistru pa echá-y un güeyu dempués. -

                Pa los mensaxes reteníos, activa la opción Preservar si quies atroxar una copia de los mensaxes pal alministrador del sirvidor. Esto ye útil p'aquellos mensaxes abusivos que quies descartar, pero que necesites atroxar un rexistru pa echá-y un güeyu dempués. +

                Activa la opción Redirixir a y enllena la direición correspondiente si quies reunviar el mensax a daquién que nun tea na llista. Pa editar un mensax reteníu enantes de que s'unvie a la llista, puedes reunviartelu a tí mesmu +(o al propietariu de la llista), y descartar el mensax orixinal. Entós, cuando'l mensax s'amuese nel to buzón d'entrada, fai les tos correiciones y reunvíalu a la llista, incluyendo una testera Approved: cola clave de la llista como valor. Ye un bon vezu nesti casu, incluyir una nota nel mensax desplicando que modificasti'l testu. -

                Activa la opción Redirixir a y enllena la direición correspondiente si quies reunviar el mensax a daquién que nun tea na llista. Pa editar un mensax reteníu enantes de que s'unvie a la llista, puedes reunviartelu a tí mesmu -(o al propietariu de la llista), y descartar el mensax orixinal. Entós, cuando'l mensax s'amuese nel to buzón d'entrada, fai les tos correiciones y reunvíalu a la llista, incluyendo una testera Approved: cola clave de la llista como valor. Ye un bon vezu nesti casu, incluyir una nota nel mensax desplicando que modificasti'l testu. +

                Si'l remitente ye un soscritor que ta siendo moderáu, puedes llimpiar opcionalmente la so marca de moderación. Esto ye útil cuando la so llista ta configurada pa poner a los soscriptores nuevos en cuarentena y decide que puede confiar nesti soscriptor a la hora de mandar mensaxes a la llista ensin aprobación. -

                Si'l remitente ye un soscritor que ta siendo moderáu, puedes llimpiar opcionalmente la so marca de moderación. Esto ye útil cuando la so llista ta configurada pa poner a los soscriptores nuevos en cuarentena y decide que puede confiar nesti soscriptor a la hora de mandar mensaxes a la llista ensin aprobación. +

                Si'l remitente nun ye un soscriptor de la llista, puedes amestar esta direición de corréu electrónicu al peñeráu de remitentes. El peñeráu de remitentes descríbese na páxina de peñeres de privacidá, y puede aceutase automáticamente, retenese automáticamente, refugase automáticamente o descartase automáticamente. Esta opción nun tará disponible si la direición yá ta nún de les peñeres de remitente. -

                Si'l remitente nun ye un soscriptor de la llista, puedes amestar esta direición de corréu electrónicu al peñeráu de remitentes. El peñeráu de remitentes descríbese na páxina de peñeres de privacidá, y puede aceutase automáticamente, retenese automáticamente, refugase automáticamente o descartase automáticamente. Esta opción nun tará disponible si la direición yá ta nún de les peñeres de remitente. - -

                Cuando tengas finao, da-y col mur al botón Unviar tolos datos del final de la páxina. Esti botón entregará toles aiciones esbillaes de les solicitúes alministratives pa les que tenda conseñao dalguna seleición. -

                Tornar pa la páxina colos sumarios. +

                Cuando tengas finao, da-y col mur al botón Unviar tolos datos del final de la páxina. Esti botón entregará toles aiciones esbillaes de les solicitúes alministratives pa les que tenda conseñao dalguna seleición. +

                Tornar pa la páxina colos sumarios. +

                \ No newline at end of file diff --git a/templates/ast/admindbpreamble.html b/templates/ast/admindbpreamble.html index daa665dd..f622bf5f 100644 --- a/templates/ast/admindbpreamble.html +++ b/templates/ast/admindbpreamble.html @@ -1,6 +1,70 @@ -Esta páxina contién un soconxuntu de los unvíos a la llista de distribución %(listname)s que requieren -de la so conformidá. Anguaño amuesa %(description)s +Esta páxina contién un soconxuntu de los unvíos a la llista de distribución %(listname)s que requieren +de la so conformidá. Anguaño amuesa %(description)s -

                Pa cada solicitú alministrativa, por favor esbilla l'aición a facer calcando col mur en mandar tolos datos una vegada que fines. Puedes alcontrar información adicional equí. +

                Pa cada solicitú alministrativa, por favor esbilla l'aición a facer calcando col mur en mandar tolos datos una vegada que fines. Puedes alcontrar información adicional equí. -

                Tamién puedes ver un resume de toles solicitúes pendientes. +

                Tamién puedes ver un resume de toles solicitúes pendientes. +

                \ No newline at end of file diff --git a/templates/ast/admindbsummary.html b/templates/ast/admindbsummary.html index 398661c8..5b1934c7 100644 --- a/templates/ast/admindbsummary.html +++ b/templates/ast/admindbsummary.html @@ -1,6 +1,70 @@ -Esta páxina contién un sumariu de les solicitúes alministratives que requieren de la so aprobación pa la llista de distribución %(listname)s. -Lo primero qu'alcontrará van ser les solicitúes d'alta y baxa na lista (si les hubiere), siguía por cualesquier unvío a la llista pendiente de la so aprobación. +Esta páxina contién un sumariu de les solicitúes alministratives que requieren de la so aprobación pa la llista de distribución %(listname)s. +Lo primero qu'alcontrará van ser les solicitúes d'alta y baxa na lista (si les hubiere), siguía por cualesquier unvío a la llista pendiente de la so aprobación. -

                Esbille l'aición a tomar pa cada una de les solicitúes alministrativaes y calque nel botón Unviar tolos datos cuando tenga finao. Hai disponibles instrucciones más detallaes. +

                Esbille l'aición a tomar pa cada una de les solicitúes alministrativaes y calque nel botón Unviar tolos datos cuando tenga finao. Hai disponibles instrucciones más detallaes. -

                D'igual mou, tamién puedes ver los detalles de tolos unvíos reteníos. +

                D'igual mou, tamién puedes ver los detalles de tolos unvíos reteníos. +

                \ No newline at end of file diff --git a/templates/ast/admlogin.html b/templates/ast/admlogin.html index 2538315c..22a72c89 100755 --- a/templates/ast/admlogin.html +++ b/templates/ast/admlogin.html @@ -1,34 +1,95 @@ -Autentificación del %(who)s de %(listname)s - - - -
                +Autentificación del %(who)s de %(listname)s + + + + %(message)s - - - - - - + + + + + + + +
                - - Autentificación del %(who)s de %(listname)s -
                Llista Contraseñes %(who)s:
                + + + - - - - - -
                + + Autentificación del %(who)s de %(listname)s
                -
                -

                Importante: A partir d'agora, tienes de tener habilitaes les cookies nel restolador, en casu contrariu, los cambeos nun fadrán efeutu. +

                Llista Contraseñes %(who)s:
                +
                +
                +

                Importante: A partir d'agora, tienes de tener habilitaes les cookies nel restolador, en casu contrariu, los cambeos nun fadrán efeutu. -

                La interface alministrativa de Mailman emplega sesiones basaes en cookies, de mou que nun necesites identificate de contino con cada operación alministrativa que faigas. La cookie va caducar automáticamente cuando salgas del restolador, o puedes facela caducar esbillando la opción Salida baxo la seición titulada Otres Actividades Alministratives (que verás una vegada que consigas entrar -con éxitu). -

                +

                La interface alministrativa de Mailman emplega sesiones basaes en cookies, de mou que nun necesites identificate de contino con cada operación alministrativa que faigas. La cookie va caducar automáticamente cuando salgas del restolador, o puedes facela caducar esbillando la opción Salida baxo la seición titulada Otres Actividades Alministratives (que verás una vegada que consigas entrar +con éxitu). +

                diff --git a/templates/ast/archidxentry.html b/templates/ast/archidxentry.html index f9bb57aa..5c620b20 100644 --- a/templates/ast/archidxentry.html +++ b/templates/ast/archidxentry.html @@ -1,4 +1,68 @@ -
              • %(subject)s -  -%(author)s - +
              • %(subject)s +  +%(author)s + +
              • \ No newline at end of file diff --git a/templates/ast/archidxfoot.html b/templates/ast/archidxfoot.html index 7077adb1..c70729d3 100644 --- a/templates/ast/archidxfoot.html +++ b/templates/ast/archidxfoot.html @@ -1,21 +1,85 @@ - -

                - Data del caberu mensax: - %(lastdate)s
                - Archiváu en: %(archivedate)s -

                -

                  -
                • Mensaxes axeitaos por: + +

                  +Data del caberu mensax: +%(lastdate)s
                  +Archiváu en: %(archivedate)s +

                  +

                  -

                  -


                  - Ficheru xeneráu por +
                +

                +


                +Ficheru xeneráu por Pipermail %(version)s. - - + + +

                \ No newline at end of file diff --git a/templates/ast/archidxhead.html b/templates/ast/archidxhead.html index 2e5a8c74..b335cc7b 100644 --- a/templates/ast/archidxhead.html +++ b/templates/ast/archidxhead.html @@ -1,24 +1,89 @@ + - - Archivos de %(listname)s %(archive)s por %(archtype)s - + +Archivos de %(listname)s %(archive)s por %(archtype)s + %(encoding)s - - - -

                %(archive)s Archivos por %(archtype)s

                -
                  -
                • Mensaxes axeitaos por: + + + +

                  %(archive)s Archivos por %(archtype)s

                  + -

                  Dende: %(firstdate)s
                  - Fasta: %(lastdate)s
                  - Mensaxes: %(size)s

                  -

                    +
                  +

                  Dende: %(firstdate)s
                  +Fasta: %(lastdate)s
                  +Mensaxes: %(size)s

                  +

                    +

                  \ No newline at end of file diff --git a/templates/ast/archlistend.html b/templates/ast/archlistend.html index 9bc052dd..2e1191b0 100644 --- a/templates/ast/archlistend.html +++ b/templates/ast/archlistend.html @@ -1 +1,64 @@ -
                + diff --git a/templates/ast/archliststart.html b/templates/ast/archliststart.html index bac63c0a..75d3f37d 100644 --- a/templates/ast/archliststart.html +++ b/templates/ast/archliststart.html @@ -1,4 +1,68 @@ - - - - +
                ArchivuVer por:Version pa baxar al to equipu
                + + + +
                ArchivuVer por:Version pa baxar al to equipu
                \ No newline at end of file diff --git a/templates/ast/archtoc.html b/templates/ast/archtoc.html index 11bad4ed..3c0c6cf2 100644 --- a/templates/ast/archtoc.html +++ b/templates/ast/archtoc.html @@ -1,19 +1,83 @@ - - - Archivos de %(listname)s - + + + +Archivos de %(listname)s + %(meta)s - - -

                Archivos de %(listname)s

                -

                - Puedes obtener mas información sobro esta llista o puedes baxar al to equipu tol archivu + + +

                Archivos de %(listname)s

                +

                + Puedes obtener mas información sobro esta llista o puedes baxar al to equipu tol archivu (%(size)s).

                %(noarchive_msg)s %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/ast/archtocentry.html b/templates/ast/archtocentry.html index 1b436aec..59a55f10 100644 --- a/templates/ast/archtocentry.html +++ b/templates/ast/archtocentry.html @@ -1,11 +1,74 @@ - -
                %(archivelabel)s: - [ Filu ] - [ Tema ] - [ Autor ] - [ Data ] -
                %(archivelabel)s: +[ Filu ] +[ Tema ] +[ Autor ] +[ Data ] +
                - - - - - - - - - - - - - - - - - -
                - -- - -
                -

                  -

                - Sobro - - - - -
                -

                -

                Pa ver unvíos anteriores a la llista, - puedes visitar los Archivos. - -

                -
                - Cómo usar la llista -
                + + +<mm-list-name> Página de Información de <MM-List-Name></mm-list-name> + + +

                + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + +
                + -- + +
                +

                  +

                +Sobro + + + + +
                +

                +

                Pa ver unvíos anteriores a la llista, + puedes visitar los Archivos. + +

                +
                +Cómo usar la llista +
                Pa unviar un mensax a tolos miembros de la llista, - únvialu a la direición - . + únvialu a la direición + . -

                Puedes soscribite a la llista, o camudar la soscripción, nes siguientes estayes. -

                - Soscribise a -
                -

                - Soscríbite a enllenando los datos del - siguiente formulariu - -

                  - - - - - - - - + + + - - - + + - - - - - - - -
                  Direición de corréu electrónicu: - 
                  El to nome (opcional):
                  +

                  Puedes soscribite a la llista, o camudar la soscripción, nes siguientes estayes. +

                  +Soscribise a  
                  Tienes d'inxertar una clave de proteición. Esto date un nivel de seguridá baxu, pero tendría d'evitar qu'otros enreden cola to soscripción. Nun uses claves valoratibles porque puede que se t'unvíe dalguna vegada daqué ensin cifrar per corréu-e. +
                  +

                  + Soscríbite a enllenando los datos del + siguiente formulariu + +

                    + + + + + + + + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - -
                    Direición de corréu electrónicu: + 
                    El to nome (opcional):
                    +
                     
                    Tienes d'inxertar una clave de proteición. Esto date un nivel de seguridá baxu, pero tendría d'evitar qu'otros enreden cola to soscripción. Nun uses claves valoratibles porque puede que se t'unvíe dalguna vegada daqué ensin cifrar per corréu-e. -

                    Si decides nun escribir denguna clave, xenerarémoste una automáticamente y unviarásete una vegada que confirmes la soscripción. Siempres podrás pidir que se t'unvíe per corréu la to clave cuando edites les opciones personales - -

                    Escueyi una clave: 
                    Confirma la clave: 
                    ¿En qué llingua quies ver los mensaxes?  
                    ¿Quies recibir los mensaxes de cada día resumíos nún únicu mensax (digest)? +

                    Si decides nun escribir denguna clave, xenerarémoste una automáticamente y unviarásete una vegada que confirmes la soscripción. Siempres podrás pidir que se t'unvíe per corréu la to clave cuando edites les opciones personales + +

                    Escueyi una clave: 
                    Confirma la clave: 
                    ¿En qué llingua quies ver los mensaxes?  
                    ¿Quies recibir los mensaxes de cada día resumíos nún únicu mensax (digest)? Non - Sí -
                    -

                    -
                    - -
                  -
                  - - Soscriptores -
                  - - - -

                  - - - -

                  - - - +
                Non + Sí +
                +

                +
                + + + + + Soscriptores + + + + + + + +

                + + + +

                + + +

                + diff --git a/templates/ast/options.html b/templates/ast/options.html index 964365d7..cc78a1f8 100644 --- a/templates/ast/options.html +++ b/templates/ast/options.html @@ -1,266 +1,298 @@ - - <MM-Presentable-User> Configuración miembros pa <MM-List-Name> - - - - - -
                - - Configuración del usuariu pa les - -
                + +<mm-presentable-user> Configuración miembros pa <mm-list-name> +</mm-list-name></mm-presentable-user> + + + + +
                + + Configuración del usuariu pa les + +

                - - - - - +
                - Opciones de soscripción de na llista de distribución . -
                - - - - -

                -

                + + + +
                + Opciones de soscripción de na llista de distribución . +
                + + +

                +

                - - +

                - - - + +
                - - Pa camudar la información cola que tas soscritu a -
                Puedes camudar la direición cola que tas soscritu a la llista de distribución, introduciendo una direición nueva nel campo qu'apaez más abaxo. Unviarásete un mensax de confirmación a la direición qu'indiques, darréu que los cambeos nun fadrán efeutu fasta que lo confirmes. + + + - - - - - -
                + +Pa camudar la información cola que tas soscritu a +
                Puedes camudar la direición cola que tas soscritu a la llista de distribución, introduciendo una direición nueva nel campo qu'apaez más abaxo. Unviarásete un mensax de confirmación a la direición qu'indiques, darréu que los cambeos nun fadrán efeutu fasta que lo confirmes. -

                Les confirmaciones caduquen dempués de . +

                Les confirmaciones caduquen dempués de . -

                Opcionalmente, puedes poner o camudar el to nome y apellíos (por exemplu Xurde Prendes). +

                Opcionalmente, puedes poner o camudar el to nome y apellíos (por exemplu Xurde Prendes). -

                Si quies aplicar les modificaciones de soscripción a toles llistes nes que tas soscritu en , habilita la caxella de verificación Camudar globalmente. +

                Si quies aplicar les modificaciones de soscripción a toles llistes nes que tas soscritu en , habilita la caxella de verificación Camudar globalmente. -

                - - - - - - - -
                Direición nueva:
                Confirmar otra vuelta:
                -
                - - +
                El to nome +

                + + + + + + + +
                Direición nueva:
                Confirmar otra vuelta:
                +
                + + - - -
                El to nome (opcional):
                -
                -

                Camudar globalmente

                - +
                + +

                +

                Camudar globalmente

                +

                - - - - - - - - + + + + %(textlink)s - diff --git a/templates/ca/archtocnombox.html b/templates/ca/archtocnombox.html index 32eb992e..28b82f24 100644 --- a/templates/ca/archtocnombox.html +++ b/templates/ca/archtocnombox.html @@ -1,18 +1,82 @@ - - - Els arxius de la llista %(listname)s - + + + +Els arxius de la llista %(listname)s + %(meta)s - - -

                Arxius de la llista %(listname)s

                -

                - Podeu obtenir més informació sobre aquesta llista. + + +

                Arxius de la llista %(listname)s

                +

                + Podeu obtenir més informació sobre aquesta llista.

                %(noarchive_msg)s %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/ca/article.html b/templates/ca/article.html index a946d4b3..c151aefd 100644 --- a/templates/ca/article.html +++ b/templates/ca/article.html @@ -1,12 +1,13 @@ - - - %(title)s - - - - - + +

                Arxius de la llista %(listname)s

                +

                Encara no s'ha enviat cap missatge a aquesta llista, per la qual cosa els - arxius estan buits. Podeu obtenir més informació + arxius estan buits. Podeu obtenir més informació sobre aquesta llista.

                - - + + diff --git a/templates/ca/headfoot.html b/templates/ca/headfoot.html index 345f36e7..9eb3b658 100644 --- a/templates/ca/headfoot.html +++ b/templates/ca/headfoot.html @@ -1,27 +1,89 @@ -Aquest text pot incloure +Aquest text pot incloure cadenes de format del Python, les quals es resoldran com atributs de la llista. -La llista de les substitucions permeses és la següent: +La llista de les substitucions permeses és la següent:
                  -
                • real_name - el nom formatat de la llista; normalment és el nom - de la llista amb les majúscules adequades. +
                • real_name - el nom formatat de la llista; normalment és el nom + de la llista amb les majúscules adequades.
                • list_name - el nom amb el qual s'identifica la llista per - a URLs que distingeixen entre majúscules i minúscules. + a URLs que distingeixen entre majúscules i minúscules.
                • host_name - el nom complet del servidor on s'executa el servidor de llistes de correu. -
                • web_page_url - l'URL bàsic del Mailman. S'hi afegir, per exemple, - listinfo/%(list_name)s per a obtenir la pàgina d'informació +
                • web_page_url - l'URL bàsic del Mailman. S'hi afegir, per exemple, + listinfo/%(list_name)s per a obtenir la pàgina d'informació de la llista de correu. -
                • description - la descripció breu de la llista de correu. +
                • description - la descripció breu de la llista de correu. -
                • info - la descripció completa de la llista de correu. +
                • info - la descripció completa de la llista de correu. -
                • cgiext - l'extensió que s'afegeix als scripts CGI. - -
                +
              • cgiext - l'extensió que s'afegeix als scripts CGI. +
              • diff --git a/templates/ca/listinfo.html b/templates/ca/listinfo.html index a6c4e4ba..8507fd14 100644 --- a/templates/ca/listinfo.html +++ b/templates/ca/listinfo.html @@ -1,147 +1,205 @@ - - - - Pàgina d'informació de la llista <MM-List-Name> - - - -

                -

                - P'anular la soscripción a - El restu de les soscripciones que tienes en -
                - Esbilla la caxella de confirmación y calca esti botón p'anular la to soscripción d'esta llista de distribución. Avisu: ¡Esta aición nun tien efeutu nel intre! + + + + - + +
                +

                +P'anular la soscripción a +El restu de les soscripciones que tienes en +
                + Esbilla la caxella de confirmación y calca esti botón p'anular la to soscripción d'esta llista de distribución. Avisu: ¡Esta aición nun tien efeutu nel intre!

                -

                - Puedes ver un informe del restu de les llistes de distribución de nes que tas soscritu. Usa esta parte si quies aplicar cambeos globales al restu de les tos soscripciones. +

                + Puedes ver un informe del restu de les llistes de distribución de nes que tas soscritu. Usa esta parte si quies aplicar cambeos globales al restu de les tos soscripciones.

                -

                -
                - - - - - - -
                - La to clave pa -
                - -
                -

                ¿Escaecisti la clave?

                -
                Calca nesti botón pa facer que t'unvien un corréu-e a la direición cola que tas soscritu. -

                -

                - -
                -
                - -
                -

                Pa camudar la clave

                - - - - - - - - -
                Clave - nueva:
                Confirmar otra vuelta:
                - - -

                Camudar globalmente. -
                -
                - + + + +
                +La to clave pa +
                + +
                +

                ¿Escaecisti la clave?

                +
                Calca nesti botón pa facer que t'unvien un corréu-e a la direición cola que tas soscritu. +

                +

                + +
                +

                + +
                +

                Pa camudar la clave

                + + + + + + + + +
                Clave + nueva:
                Confirmar otra vuelta:
                + +

                Camudar globalmente. +
                +

                - - +
                - Les tos opciones de soscripción pa la llista -
                +
                +Les tos opciones de soscripción pa la llista +
                -

                -Comprobáronse los valores actuales. - -

                Decátate que dalguna de les opciones tienen una caxella seleicionable +Comprobáronse los valores actuales. +

                Decátate que dalguna de les opciones tienen una caxella seleicionable Aplicar globalmente. -Activando esti campu, fadrás que los cambeos s'apliquen a cada una de les llistas de distribución de nes que tas soscritu. Calca col mur en Llistar el restu de les mios soscripciones más arriba pa ver cuáles son. +Activando esti campu, fadrás que los cambeos s'apliquen a cada una de les llistas de distribución de nes que tas soscritu. Calca col mur en Llistar el restu de les mios soscripciones más arriba pa ver cuáles son.

                - -
                - - Entrega de corréu

                - Si esbilles Habilitada recibirás los mensaxes que s'unvien a esta llista de distribución. Si esbilles + + - +indiques el deseyu de nun recibir el corréu unviáu a la llista de distribución durante un tiempu (por exemplo, si vas de vacaciones). Si inhabilites la receición de los mensaxes de corréu, nun escaezas volver a habilitar la receición cuandu proceda. +

                - - - +

                + - - - - - - +

                + + + + + + - - - - - - - - + - - - +

                +
                + +Entrega de corréu

                + Si esbilles Habilitada recibirás los mensaxes que s'unvien a esta llista de distribución. Si esbilles Inhabilitada -indiques el deseyu de nun recibir el corréu unviáu a la llista de distribución durante un tiempu (por exemplo, si vas de vacaciones). Si inhabilites la receición de los mensaxes de corréu, nun escaezas volver a habilitar la receición cuandu proceda. -

                - Habilitáu
                - Inhabilitáu

                - Aplicar globalmente -

                +Habilitáu
                +Inhabilitáu

                +Aplicar globalmente +

                - Activar mou Digest

                - Si habilites el mou digest, recibirás diariamente los mensaxes unviados a la llista recopilaos nún únicu mensax, - n'arróu de recibilos a midía que s'unvíen. Si camudes el mou digest de Activar a Desactivar, puede que recibas un caberu +

                +Activar mou Digest

                + Si habilites el mou digest, recibirás diariamente los mensaxes unviados a la llista recopilaos nún únicu mensax, + n'arróu de recibilos a midía que s'unvíen. Si camudes el mou digest de Activar a Desactivar, puede que recibas un caberu digest. -

                - Desactivar
                - Activar -
                - ¿Recibir los recopilatorios en testu planu o con codificación MIME?

                  - El to llector de corréu puede que nun sofite recopilaciones (digests) MIME. - En xeneral prefiérense les recopilaciones MIME, pero si tienes problemes a la hora de lleeles, esbilla les recopilaciones en testu planu. -

                - MIME
                - Texto plano

                - Aplicar globalmente -

                +Desactivar
                +Activar +
                +¿Recibir los recopilatorios en testu planu o con codificación MIME?

                  + El to llector de corréu puede que nun sofite recopilaciones (digests) MIME. + En xeneral prefiérense les recopilaciones MIME, pero si tienes problemes a la hora de lleeles, esbilla les recopilaciones en testu planu. +

                +MIME
                +Texto plano

                +Aplicar globalmente +

                - ¿Quies recibir los mensaxes que tú mesmu unvíes a esta llista?

                - Normalmente, recibirá una copia de cada mensaje que enví a la lista. Si no quiere recibir dicha copia, ponga esta opción a +

                +¿Quies recibir los mensaxes que tú mesmu unvíes a esta llista?

                + Normalmente, recibirá una copia de cada mensaje que enví a la lista. Si no quiere recibir dicha copia, ponga esta opción a No. -

                - Non
                - Si
                - -
                - ¿Quies recibir una confirmación cuando unvíes corréu a esta llista?

                -

                - Non
                - Si -
                - ¿Quies recibir los recordatorios d'esta llista?

                - Mensualmente, recibirás un mensax de corréu cola clave de cada una de les llistes nes que tas soscritu. Puedes inhabilitar esti comportamientu per llista, namái con seleicionar Non nesta opción. Si decides inhabilitar el recordatoriu - de les claves en toles llistes a les que tas soscritu, nun se te mandará dengún mensaxe. -

                - Non
                - Si

                - Aplicar globalmente -

                +Non
                +Si
                +
                +¿Quies recibir una confirmación cuando unvíes corréu a esta llista?

                +

                +Non
                +Si +
                +¿Quies recibir los recordatorios d'esta llista?

                + Mensualmente, recibirás un mensax de corréu cola clave de cada una de les llistes nes que tas soscritu. Puedes inhabilitar esti comportamientu per llista, namái con seleicionar Non nesta opción. Si decides inhabilitar el recordatoriu + de les claves en toles llistes a les que tas soscritu, nun se te mandará dengún mensaxe. +

                +Non
                +Si

                +Aplicar globalmente +

                +¿Quies anubrite na llista de soscriptores?

                + Cuando daquién vea la llista de soscriptores, verá que davezu la to direición de corréu-e saldrá llistada + (d'una forma escura pa engañifar a los escaneadores spam). Si nun quies que la to direición salga a rellucir, seleicione Si. +

                +Non
                +Si +
                +¿En qué llingua quies ver los mensaxes de la llista?

                +

                + +
                +
                +¿A qué triba de temes prestaríate soscribite?

                +Al seleicionar ún o más temes, puedes peñerar el tráficu de la llista de distribución, de mou que namái recibirás un soconxuntu de los mensaxes. Namái t'arribarán los mensaxes que concasen con dalgún de los temes esbillaos.

                Si un mensax nun concasa col tema, la regla que decide si s'entrega'l mensax depende de la configuración de la opción d'abaxo. Si nun seleiciones dengún tema d'interés, recibirás tolos mensaxes dirixíos a la llista. +

                + +
                +
                +¿Quies recibir los mensaxes que nun concasen con dengún tema?

                +Esta opción namái fai efeutu si tas soscritu a dalgún tema de los d'arriba. Esta opción describe cual va ser la regla d'entrega por defeutu pa los mensaxes que nun concasen con dengún tema. Si esbilles que Non conseñes el to deseyu de nun recibir los mensaxes que concasen con dengún tema, mentantu que si esbilles que Si significa que recibirás tolos mensaxes que nun concasen con dengún tema. -

                - ¿Quies anubrite na llista de soscriptores?

                - Cuando daquién vea la llista de soscriptores, verá que davezu la to direición de corréu-e saldrá llistada - (d'una forma escura pa engañifar a los escaneadores spam). Si nun quies que la to direición salga a rellucir, seleicione Si. -

                - Non
                - Si -
                - ¿En qué llingua quies ver los mensaxes de la llista?

                -

                - -
                -
                - ¿A qué triba de temes prestaríate soscribite?

                -Al seleicionar ún o más temes, puedes peñerar el tráficu de la llista de distribución, de mou que namái recibirás un soconxuntu de los mensaxes. Namái t'arribarán los mensaxes que concasen con dalgún de los temes esbillaos.

                Si un mensax nun concasa col tema, la regla que decide si s'entrega'l mensax depende de la configuración de la opción d'abaxo. Si nun seleiciones dengún tema d'interés, recibirás tolos mensaxes dirixíos a la llista. -

                - -
                -
                - ¿Quies recibir los mensaxes que nun concasen con dengún tema?

                -Esta opción namái fai efeutu si tas soscritu a dalgún tema de los d'arriba. Esta opción describe cual va ser la regla d'entrega por defeutu pa los mensaxes que nun concasen con dengún tema. Si esbilles que Non conseñes el to deseyu de nun recibir los mensaxes que concasen con dengún tema, mentantu que si esbilles que Si significa que recibirás tolos mensaxes que nun concasen con dengún tema. - -

                Si nun se seleiciona dengún tema d'interés na estaya d'arriba, entós recibirás tolos mensaxes que s'unvíen a la llista. -

                - Non
                - Si -
                - ¿Quies evitar copies duplicaes de los tos propios mensaxes?

                - Cuando tea incluyío esplícitamente nes testeres To: o Cc: d'un mensax dirixíu a - la llista, puedes optar por non recibir otra copia de la llista de distribución. Seleiciona Si pa - evitar recibir copies de la llista de distribución o Non pa recibiles. +

                Si nun se seleiciona dengún tema d'interés na estaya d'arriba, entós recibirás tolos mensaxes que s'unvíen a la llista. +

                +Non
                +Si +
                +¿Quies evitar copies duplicaes de los tos propios mensaxes?

                + Cuando tea incluyío esplícitamente nes testeres To: o Cc: d'un mensax dirixíu a + la llista, puedes optar por non recibir otra copia de la llista de distribución. Seleiciona Si pa + evitar recibir copies de la llista de distribución o Non pa recibiles.

                Si la llista tien soscriptores colos mensaxes personalizaos activaos, y escoyisti recibir copies, - cada copia tendrá una testera X-Mailman-Copy: + cada copia tendrá una testera X-Mailman-Copy: yes. -

                - Non
                - Si

                - Aplicar globalmente -

                -
                -
                +Non
                +Si

                +Aplicar globalmente +

                +
                +
                -

                - - - - + + +

                diff --git a/templates/ast/private.html b/templates/ast/private.html index 90f1665f..07aed5c1 100755 --- a/templates/ast/private.html +++ b/templates/ast/private.html @@ -1,50 +1,115 @@ - + + + - Autentificación para los archivos privados de %(realname)s - - -body bgcolor="#ffffff" onLoad="sf()"> -
                + +Autentificación para los archivos privados de %(realname)s + + + + %(message)s - - - - - - - - - - - - - - - -
                - Autentificación d'archivos privaos de %(realname)s -
                Direición::
                Contraseña:
                -
                -

                Importante: A partir d'agora, tienes de tener habilitaes les cookies nel restolador web, de nun ser asina, los cambeos nun fadrán efeutu. -

                Les sesiones basaes en cookies úsense na interface alministrativa de Mailman, de mou que nun necesites identificate de contino con cada operación alministrativa que faigas. La cookie caducará automáticamente cuando salgas del restolador, o puedes facela caducar seleicionando la opción Salida baxo la seición titulada Otres Actividaes Alministratives (que verás una vegada que consigas entrar con éxitu). + + + + + + + + + + + + + + + +
                +Autentificación d'archivos privaos de %(realname)s +
                Direición:
                Contraseña:
                + +
                +

                Importante: A partir d'agora, tienes de tener habilitaes les cookies nel restolador web, de nun ser asina, los cambeos nun fadrán efeutu. +

                Les sesiones basaes en cookies úsense na interface alministrativa de Mailman, de mou que nun necesites identificate de contino con cada operación alministrativa que faigas. La cookie caducará automáticamente cuando salgas del restolador, o puedes facela caducar seleicionando la opción Salida baxo la seición titulada Otres Actividaes Alministratives (que verás una vegada que consigas entrar con éxitu).

                - - - - - - + + + +
                - Password Reminder -
                If you don't remember your password, enter your email address + + + + + + - - - - -
                +Password Reminder +
                If you don't remember your password, enter your email address above and click the Remind button and your password will be emailed to you.
                - +
                +

                diff --git a/templates/ast/roster.html b/templates/ast/roster.html index 56af7a22..7e4d2d11 100644 --- a/templates/ast/roster.html +++ b/templates/ast/roster.html @@ -1,51 +1,110 @@ - - - Soscriptores de <MM-List-Name> - - - - -

                - - - - - - - - - - - - - - - -
                - Soscriptores de -
                - -

                -

                - -

                Seleiciona la to direición pa visitar l'estáu de la to subscripción. -
                (Les entraes ente paréntesis tienen la recepción del corréu de la lista desactivada.)

                -
                -
                - - Soscritores en cola recepción de los mensaxes a medida que lleguen: -
                -
                -
                - - Soscritores en cola recepción agrupada en resúmenes: -
                -
                -

                -

                -

                -

                - - - + + + Soscriptores de <mm-list-name></mm-list-name> + + +

                + + + + + + + + + + + + + + + +
                +Soscriptores de +
                +

                +

                +

                Seleiciona la to direición pa visitar l'estáu de la to subscripción. +
                (Les entraes ente paréntesis tienen la recepción del corréu de la lista desactivada.)

                +
                +
                + + Soscritores en cola recepción de los mensaxes a medida que lleguen: +
                +
                +
                + + Soscritores en cola recepción agrupada en resúmenes: +
                +
                +

                +

                +

                +

                + +

                + diff --git a/templates/ast/subscribe.html b/templates/ast/subscribe.html index 2e994cce..93c518d3 100644 --- a/templates/ast/subscribe.html +++ b/templates/ast/subscribe.html @@ -1,9 +1,72 @@ -<MM-List-Name> Resultados de la soscripción +<mm-list-name> Resultados de la soscripción</mm-list-name> -

                Resultaos de la soscripción

                - - - +

                Resultaos de la soscripción

                + + + diff --git a/templates/ca/admindbdetails.html b/templates/ca/admindbdetails.html index 7d726d59..50f02554 100644 --- a/templates/ca/admindbdetails.html +++ b/templates/ca/admindbdetails.html @@ -1,63 +1,126 @@ -Les sol·licituds d'administració es mostren en una de dues maneres possibles: -en una pàgina de resum, i en una pàgina de detalls. -La pàgina de resum conté les sol·licituds de subscripció i de -cancel·lació pendents, i els missatges retinguts que esperen la vostra -aprovació, ordenats segons l'adreça del remitent. La pàgina de detalls -conté una vista més detallada d'aquests missatges retinguts, incloent-ne el títol +Les sol·licituds d'administració es mostren en una de dues maneres possibles: +en una pàgina de resum, i en una pàgina de detalls. +La pàgina de resum conté les sol·licituds de subscripció i de +cancel·lació pendents, i els missatges retinguts que esperen la vostra +aprovació, ordenats segons l'adreça del remitent. La pàgina de detalls +conté una vista més detallada d'aquests missatges retinguts, incloent-ne el títol sencer i un extracte del cos. -

                Les accions següents estan disponibles a totes les pàgines: +

                Les accions següents estan disponibles a totes les pàgines: -

                • Ajorna -- ajornament de la decisió per més endavant. No es -durà a terme cap acció per a aquesta sol·licitud d'administració. Pel que fa als missatges retinguts, encara es -podran reenviar o mantenir (vegeu l'acció de sota). +
                  • Ajorna -- ajornament de la decisió per més endavant. No es +durà a terme cap acció per a aquesta sol·licitud d'administració. Pel que fa als missatges retinguts, encara es +podran reenviar o mantenir (vegeu l'acció de sota). -
                  • Aprova -- aprovació del missatge i enviament a la -llista. Si és una sol·licitud de subscripció, s'aprovarà el canvi -d'estat de la subscripció. +
                  • Aprova -- aprovació del missatge i enviament a la +llista. Si és una sol·licitud de subscripció, s'aprovarà el canvi +d'estat de la subscripció.
                  • Rebutja -- rebuig del missatge, enviament d'una nota de -rebuig al remitent i descart del missatge original. Si és una -sol·licitud de subscripció, es rebutjarà el canvi d'estat de la -subscripció. En qualsevol cas, és recomanable donar un motiu per a tal -decisió al quadre de text del costat. - -
                  • Descarta -- supressió del missatge original sense enviar -una nota de rebuig. Si és una sol·licitud de subscripció, es descartarà -sense enviar cap notificació al remitent. Aquesta és la manera d'actuar -davant de l'spam.
                  - -

                  Per als missatges retinguts, activeu l'opció Manté per a desar - una còpia del missatge per a l'administrador del lloc. Això és - molt útil per a missatges abusius que vulgueu descartar, però dels quals en calgui una còpia per a inspeccions posteriors. - -

                  Activeu l'opció Reenvia a, i escriviu l'adreça de -reenviament si voleu reenviar el missatge a algú que no sigui a la +rebuig al remitent i descart del missatge original. Si és una +sol·licitud de subscripció, es rebutjarà el canvi d'estat de la +subscripció. En qualsevol cas, és recomanable donar un motiu per a tal +decisió al quadre de text del costat. + +

                • Descarta -- supressió del missatge original sense enviar +una nota de rebuig. Si és una sol·licitud de subscripció, es descartarà +sense enviar cap notificació al remitent. Aquesta és la manera d'actuar +davant de l'spam.
                +

                Per als missatges retinguts, activeu l'opció Manté per a desar + una còpia del missatge per a l'administrador del lloc. Això és + molt útil per a missatges abusius que vulgueu descartar, però dels quals en calgui una còpia per a inspeccions posteriors. + +

                Activeu l'opció Reenvia a, i escriviu l'adreça de +reenviament si voleu reenviar el missatge a algú que no sigui a la llista. Per a modificar un missatge retingut abans d'enviar-lo a la -llista, us l'haureu d'enviar a vós mateix (o bé al propietari de -la llista), i descartar l'original. Un cop l'hàgiu rebut a la bústia +llista, us l'haureu d'enviar a vós mateix (o bé al propietari de +la llista), i descartar l'original. Un cop l'hàgiu rebut a la bústia d'entrada, haureu de realitzar-hi els canvis pertinents i reenviar-lo a la -llista amb una capçalera Approved: que inclogui la vostra contrasenya de la llista com a valor. En aquest - casos és recomanable una nota en el missatge - nou amb una explicació de les modificacions del text original. +llista amb una capçalera Approved: que inclogui la vostra contrasenya de la llista com a valor. En aquest + casos és recomanable una nota en el missatge + nou amb una explicació de les modificacions del text original. -

                Opcionalment, si el remitent del missatge original és un membre de la - llista en espera de moderació, podeu suprimir-li el senyalador que així ho especifica. Això és útil - en el cas que la vostra llista estigui configurada per a posar en període de prova - els membres nous, i ja hàgiu decidit que aquest membre concret - és fiable i pot enviar missatges a la llista sense necessitat d'aprovació. +

                Opcionalment, si el remitent del missatge original és un membre de la + llista en espera de moderació, podeu suprimir-li el senyalador que així ho especifica. Això és útil + en el cas que la vostra llista estigui configurada per a posar en període de prova + els membres nous, i ja hàgiu decidit que aquest membre concret + és fiable i pot enviar missatges a la llista sense necessitat d'aprovació.

                En cas que el remitent no sigui un membre de - la llista, podeu afegir la seva adreça electrònica a un filtre de remitents. - Els filtres de remitents es descriuen a la pàgina de privacitat del filtre de remitents, + la llista, podeu afegir la seva adreça electrònica a un filtre de remitents. + Els filtres de remitents es descriuen a la pàgina de privacitat del filtre de remitents, i poden ser d'auto-accept (Accepts), d'auto-hold (Holds), d'auto-reject - (Rejects), o d'auto-discard (Discards). Aquesta opció no estarà - disponible si l'adreça ja apareix en un filtre de remitents. + (Rejects), o d'auto-discard (Discards). Aquesta opció no estarà + disponible si l'adreça ja apareix en un filtre de remitents. -

                Un cop hàgiu acabat, feu clic al botó Envia totes les dades - que es troba a l'inici i al final de la pàgina. Aquest botó - enviarà totes les accions seleccionades de totes les sol·licituds d'administració - sobre les quals hàgiu pres una decisió. +

                Un cop hàgiu acabat, feu clic al botó Envia totes les dades + que es troba a l'inici i al final de la pàgina. Aquest botó + enviarà totes les accions seleccionades de totes les sol·licituds d'administració + sobre les quals hàgiu pres una decisió. -

                Torna a la pàgina principal. +

                Torna a la pàgina principal. +

                \ No newline at end of file diff --git a/templates/ca/admindbpreamble.html b/templates/ca/admindbpreamble.html index 7727a993..3d0bb861 100644 --- a/templates/ca/admindbpreamble.html +++ b/templates/ca/admindbpreamble.html @@ -1,11 +1,75 @@ -Aquesta pàgina conté un subconjunt dels missatges retinguts pendents de -la vostra aprovació de la llista de correu %(listname)s. +Aquesta pàgina conté un subconjunt dels missatges retinguts pendents de +la vostra aprovació de la llista de correu %(listname)s. Actualment mostra %(description)s. -

                Seleccioneu l'acció a dur a terme per a cada sol·licitud -d'administració. Un cop hàgiu acabat, feu clic al botó Envia totes -les dades. Aquí també hi trobareu -més informació. +

                Seleccioneu l'acció a dur a terme per a cada sol·licitud +d'administració. Un cop hàgiu acabat, feu clic al botó Envia totes +les dades. Aquí també hi trobareu +més informació. -

                També podeu visualitzar els detalls de -totes les sol·licituds pendents. +

                També podeu visualitzar els detalls de +totes les sol·licituds pendents. +

                \ No newline at end of file diff --git a/templates/ca/admindbsummary.html b/templates/ca/admindbsummary.html index 1bb02d01..3939db92 100644 --- a/templates/ca/admindbsummary.html +++ b/templates/ca/admindbsummary.html @@ -1,13 +1,77 @@ -Aquesta pàgina conté un resum del conjunt actual de sol·licituds -administratives que requereixen la vostra aprovació a la +Aquesta pàgina conté un resum del conjunt actual de sol·licituds +administratives que requereixen la vostra aprovació a la llista de correu %(listname)s. -Primer de tot, i si és que n'hi ha, trobareu la llista de sol·licituds -de subscripció i cancel·lació pendents, seguides de qualsevol enviament -retingut en espera de la vostra aprovació. +Primer de tot, i si és que n'hi ha, trobareu la llista de sol·licituds +de subscripció i cancel·lació pendents, seguides de qualsevol enviament +retingut en espera de la vostra aprovació. -

                Seleccioneu l'acció a dur a terme per a cada sol·licitud d'administració. Un cop hàgiu acabat, feu clic al botó Envia totes -les dades. També hi ha intruccions més detallades +

                Seleccioneu l'acció a dur a terme per a cada sol·licitud d'administració. Un cop hàgiu acabat, feu clic al botó Envia totes +les dades. També hi ha intruccions més detallades disponibles. -

                També podeu visualitzar els detalls de +

                També podeu visualitzar els detalls de tots els missatges retinguts. +

                \ No newline at end of file diff --git a/templates/ca/admlogin.html b/templates/ca/admlogin.html index fd31a652..1aeeffa2 100755 --- a/templates/ca/admlogin.html +++ b/templates/ca/admlogin.html @@ -1,40 +1,99 @@ - Autenticació per a %(who)s a la llista %(listname)s - - - -
                +Autenticació per a %(who)s a la llista %(listname)s + + + + %(message)s - - - - - - - - - - - -
                - Autenticació per a %(who)s - a la llista %(listname)s -
                Contrasenya de %(who)s de la llista:
                -
                -

                Important: a partir d'aquí, caldrà que tingueu les galetes habilitades en el vostre navegador. En cas contrari, us haureu de tornar a autenticar per a cada operació. + + + + + + + + + + + +
                +Autenticació per a %(who)s + a la llista %(listname)s +
                Contrasenya de %(who)s de la llista:
                +
                +

                Important: a partir d'aquí, caldrà que tingueu les galetes habilitades en el vostre navegador. En cas contrari, us haureu de tornar a autenticar per a cada operació.

                - - -

                La interfície administrativa del Mailman utilitza galetes de -sessió, de manera que no us hàgiu de tornar a autenticar per a cada -operació. Aquesta galeta expirarà automàticament quan +

                La interfície administrativa del Mailman utilitza galetes de +sessió, de manera que no us hàgiu de tornar a autenticar per a cada +operació. Aquesta galeta expirarà automàticament quan sortiu del navegador. De manera alternativa, podeu eliminar-la -explícitament si feu clic al botó Surt a sota d'Altres -tasques administratives (el qual es mostrarà un cop hàgiu +explícitament si feu clic al botó Surt a sota d'Altres +tasques administratives (el qual es mostrarà un cop hàgiu entrat). -

                +

                diff --git a/templates/ca/archidxentry.html b/templates/ca/archidxentry.html index f9bb57aa..5c620b20 100644 --- a/templates/ca/archidxentry.html +++ b/templates/ca/archidxentry.html @@ -1,4 +1,68 @@ -
              • %(subject)s -  -%(author)s - +
              • %(subject)s +  +%(author)s + +
              • \ No newline at end of file diff --git a/templates/ca/archidxfoot.html b/templates/ca/archidxfoot.html index 874cfcfa..9f6f38c9 100644 --- a/templates/ca/archidxfoot.html +++ b/templates/ca/archidxfoot.html @@ -1,21 +1,85 @@ - +

                - Data de l'últim missatge: - %(lastdate)s
                - Data d'arxivament: %(archivedate)s -

                -

                +

                +


                +Aquest arxiu l'ha generat el Pipermail %(version)s. - - + + +

                \ No newline at end of file diff --git a/templates/ca/archidxhead.html b/templates/ca/archidxhead.html index f5d2edda..ab6814a3 100644 --- a/templates/ca/archidxhead.html +++ b/templates/ca/archidxhead.html @@ -1,24 +1,89 @@ - - - L'arxiu de %(archive)s de la llista %(listname)s per %(archtype)s - + + + +L'arxiu de %(archive)s de la llista %(listname)s per %(archtype)s + %(encoding)s - - - -

                Arxius de %(archive)s per %(archtype)s

                -
                  -
                • Missatges ordenats per: + + + +

                  Arxius de %(archive)s per %(archtype)s

                  + -

                  Inici: %(firstdate)s
                  - Final: %(lastdate)s
                  - Missatges: %(size)s

                  -

                  +

                  Inici: %(firstdate)s
                  +Final: %(lastdate)s
                  +Missatges: %(size)s

                  +

                    +

                  \ No newline at end of file diff --git a/templates/ca/archlistend.html b/templates/ca/archlistend.html index 9bc052dd..2e1191b0 100644 --- a/templates/ca/archlistend.html +++ b/templates/ca/archlistend.html @@ -1 +1,64 @@ -
                + diff --git a/templates/ca/archliststart.html b/templates/ca/archliststart.html index 58819d31..e3392128 100644 --- a/templates/ca/archliststart.html +++ b/templates/ca/archliststart.html @@ -1,4 +1,68 @@ - - - - +
                ArxiuVisualitza per:Versió descarregable
                + + + +
                ArxiuVisualitza per:Versió descarregable
                \ No newline at end of file diff --git a/templates/ca/archtoc.html b/templates/ca/archtoc.html index e2e43c56..93e4edcd 100644 --- a/templates/ca/archtoc.html +++ b/templates/ca/archtoc.html @@ -1,20 +1,84 @@ - - - Els arxius de la llista %(listname)s - + + + +Els arxius de la llista %(listname)s + %(meta)s - - -

                Arxius de la llista %(listname)s

                -

                - Podeu obtenir més informació sobre aquesta - llista o bé podeu baixar l'arxiu en cru sencer + + +

                Arxius de la llista %(listname)s

                +

                + Podeu obtenir més informació sobre aquesta + llista o bé podeu baixar l'arxiu en cru sencer (%(size)s).

                %(noarchive_msg)s %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/ca/archtocentry.html b/templates/ca/archtocentry.html index 18f380fc..c921fbf2 100644 --- a/templates/ca/archtocentry.html +++ b/templates/ca/archtocentry.html @@ -1,12 +1,74 @@ - -
                %(archivelabel)s: - [ Fil de discussió ] - [ Tema ] - [ Autor ] - [ Data ] -
                %(archivelabel)s: +[ Fil de discussió ] +[ Tema ] +[ Autor ] +[ Data ] +
                - - - - - - - - - - - - - - - - - - + + + + + + + +
                - -- - -
                -

                -

                - Quant a la llista - - - -
                -

                -

                Per a veure l'historial d'enviaments, aneu als - arxius de la llista . - -

                -
                - Utilització de la llista -
                + + + +Pàgina d'informació de la llista <mm-list-name></mm-list-name> + + +

                + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - -
                + -- + +
                +

                +

                +Quant a la llista + + + +
                +

                +

                Per a veure l'historial d'enviaments, aneu als + arxius de la llista . + +

                +
                +Utilització de la llista +
                Per a enviar un missatge a tots els membres de la llista, - envieu un correu electrònic a . - -

                A les seccions següents us podreu subscriure a - la llista, o canviar la vostra subscripció actual.

                -
                - Com subscriure's a la llista -
                -

                - Ompliu el formulari següent per a - subscriure-us a . - -

                  - + envieu un correu electrònic a . - - - - - - - - - - - + + + + + + + + + + + + + - - - - - - - - -
                  La vostra adreça electrònica: -
                  El vostre nom (opcional):
                  Podeu introduir una contrasenya al quadre següent. Això proporciona només - certa seguretat, però evita que altres puguin canviar la configuració de la - vostra subscripció. Sobretot NO utilitzeu una contrasenya valuosa, - ja que pot ser que a vegades se us re-enviï per correu electrònic en +

                  A les seccions següents us podreu subscriure a + la llista, o canviar la vostra subscripció actual.

                  +
                  +Com subscriure's a la llista +
                  +

                  + Ompliu el formulari següent per a + subscriure-us a . + +

                    + + + + + + + + + + + + - - - - - - - - - - - - - - + + + + + + +
                    La vostra adreça electrònica: +
                    El vostre nom (opcional):
                    Podeu introduir una contrasenya al quadre següent. Això proporciona només + certa seguretat, però evita que altres puguin canviar la configuració de la + vostra subscripció. Sobretot NO utilitzeu una contrasenya valuosa, + ja que pot ser que a vegades se us re-enviï per correu electrònic en format de text visible. -

                    En cas que no vulgueu introduir cap - contrasenya, se us n'assignarà una generada automàticament, - la qual rebreu un cop hàgiu confirmat la vostra subscripció. A més, - sempre podreu sol·licitar-ne un recordatori quan +

                    En cas que no vulgueu introduir cap + contrasenya, se us n'assignarà una generada automàticament, + la qual rebreu un cop hàgiu confirmat la vostra subscripció. A més, + sempre podreu sol·licitar-ne un recordatori quan editeu les vostres opcions personals. - -
                    -
                    Trieu una contrasenya:
                    Torneu a escriure la contrasenya:

                    -
                    En quina llengua voleu -que es mostrin els vostres missatges?
                    Voleu rebre el correu de la llista compilat en un sol missatge enviat a diari? + + +
                    Trieu una contrasenya:
                    Torneu a escriure la contrasenya:

                  +
                  En quina llengua voleu +que es mostrin els vostres missatges?
                  Voleu rebre el correu de la llista compilat en un sol missatge enviat a diari? No - Sí -
                  -
                  -
                  - -
                -
                - - Subscriptors de la llista -
                - - - -

                - - - -

                - - - +

                No + Sí +
                +
                +
                + + + + +Subscriptors de la llista + + + + + + + +

                + + + +

                + + +

                + diff --git a/templates/ca/options.html b/templates/ca/options.html index ad14ded7..86d78597 100644 --- a/templates/ca/options.html +++ b/templates/ca/options.html @@ -1,319 +1,350 @@ - - Configuració de la subscripció de <MM-Presentable-User> per a la llista <MM-List-Name> - - - - - -
                - - Configuració de la subscripció de per a la llista - -
                + +Configuració de la subscripció de <mm-presentable-user> per a la llista <mm-list-name> +</mm-list-name></mm-presentable-user> + + + + +
                + + Configuració de la subscripció de per a la llista + +

                - - - - - +
                - Configuració de l'estat de la subscripció, - contrasenya i opcions per a a la llista de correu . -
                - - - - -

                -

                + + + +
                + Configuració de l'estat de la subscripció, + contrasenya i opcions per a a la llista de correu . +
                + + +

                +

                - - +

                - - - + +
                - - Com canviar la vostra informació de subscripció per a la llista -
                Podeu canviar l'adreça electrònica - amb la qual esteu subscrit a la llista de correu si introduïu l'adreça - nova en els quadres de text de més avall. Tingueu en - compte que s'enviarà un correu a l'adreça nova, i el canvi s'haurà + + + - - - - - -
                + +Com canviar la vostra informació de subscripció per a la llista +
                Podeu canviar l'adreça electrònica + amb la qual esteu subscrit a la llista de correu si introduïu l'adreça + nova en els quadres de text de més avall. Tingueu en + compte que s'enviarà un correu a l'adreça nova, i el canvi s'haurà de confirmar abans de processar-lo.

                Les confirmacions expiren al cap de . -

                De manera opcional, també podeu afegir o canviar el +

                De manera opcional, també podeu afegir o canviar el vostre nom real (per exemple Jordi Roure). -

                Si voleu que els canvis que feu a la vostra configuració +

                Si voleu que els canvis que feu a la vostra configuració s'apliquin a totes les llistes a les quals esteu subscrit/a, - seleccioneu la casella de verificació Canvi global.

                - -
                - - - - - - -
                Adreça nova:
                -
                Confirmeu- - la:
                -
                - - +
                El vostre nom + seleccioneu la casella de verificació Canvi global.

                +

                + + + + + + +
                Adreça nova:
                +
                Confirmeu- + la:
                +
                + + - - -
                El vostre nom (opcional):
                -
                -

                Canvi global

                - +
                + +

                +

                Canvi global

                +

                - - - - - - - - + + + + %(textlink)s - + \ No newline at end of file diff --git a/templates/cs/archtocnombox.html b/templates/cs/archtocnombox.html index f515a375..8e468ab7 100644 --- a/templates/cs/archtocnombox.html +++ b/templates/cs/archtocnombox.html @@ -1,18 +1,82 @@ - - - Archiv konference %(listname)s - + + + +Archiv konference %(listname)s + %(meta)s - - -

                Archiv konference %(listname)s

                -

                - Další informace o konferenci. + + +

                Archiv konference %(listname)s

                +

                + Další informace o konferenci.

                %(noarchive_msg)s %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/cs/article.html b/templates/cs/article.html index eaaad3bf..a942eec6 100644 --- a/templates/cs/article.html +++ b/templates/cs/article.html @@ -1,12 +1,13 @@ - - - %(title)s - - - - - + +

                Archiv konference %(listname)s

                +

                + Do této konference nebyl dosud zaslán ¾ádný pøíspìvek. + Proto je tento archiv prázdný. Více informací o konferenci + získáte na informaèní stránce..

                - - + + diff --git a/templates/cs/headfoot.html b/templates/cs/headfoot.html index 119dcef3..4a5bfce6 100644 --- a/templates/cs/headfoot.html +++ b/templates/cs/headfoot.html @@ -1,30 +1,92 @@ -Tento text mù¾e obsahovat formátovací -pøíkazy Pythonu které se nahrazují odpovídajícími hodnotami, pøi -generování stránky. Nejdùle¾itìjší promìnné jsou: +Tento text mù¾e obsahovat formátovací +pøíkazy Pythonu které se nahrazují odpovídajícími hodnotami, pøi +generování stránky. Nejdùle¾itìjší promìnné jsou:
                  -
                • real_name - `Hezké' jméno konference, - obvykle název, který má zachovánu velikost písmen tak jak je - zadán v konfiguraci. +
                • real_name - `Hezké' jméno konference, + obvykle název, který má zachovánu velikost písmen tak jak je + zadán v konfiguraci. -
                • list_name - Jméno konference, tak jak - se pou¾ívá v URL, tedy tam kde zále¾í na velikosti písmen (všechna - jsou pøevedena na malá). +
                • list_name - Jméno konference, tak jak + se pou¾ívá v URL, tedy tam kde zále¾í na velikosti písmen (vÅ¡echna + jsou pøevedena na malá). -
                • host_name - Celé DNS jméno serveru na - kterém konference bì¾í. +
                • host_name - Celé DNS jméno serveru na + kterém konference bì¾í. -
                • web_page_url - Základní URL pro Mailman. - Pokud je následováno napø. tímto pøíkazem: +
                • web_page_url - Základní URL pro Mailman. + Pokud je následováno napø. tímto pøíkazem: listinfo/%(list_name)s - vede na stránku s informacemi o konferenci. + vede na stránku s informacemi o konferenci. -
                • description - Krátký, charakteristický - popis konference, tak jak byl zadán pøi konfiguraci. +
                • description - Krátký, charakteristický + popis konference, tak jak byl zadán pøi konfiguraci. -
                • info - Kompletní, dlouhý, popis konference. +
                • info - Kompletní, dlouhý, popis konference. -
                • cgiext - pøípona pøidávaná k cgi scriptùm. +
                • cgiext - pøípona pøidávaná k cgi scriptùm. -
                + diff --git a/templates/cs/listinfo.html b/templates/cs/listinfo.html index 0fbb2819..6bbd64e6 100644 --- a/templates/cs/listinfo.html +++ b/templates/cs/listinfo.html @@ -1,145 +1,206 @@ - - - - Informace o konferenci <MM-List-Name> - - - -

                -

                - Cancel·lació de la subscripció a - Les vostres altres subscripcions a . -
                - Activeu la casella de confirmació i premeu aquest - botó per a donar-vos de baixa d'aquesta llista de correu. Avís: - aquesta acció s'executarà immediatament! + + + + - + +
                +

                +Cancel·lació de la subscripció a +Les vostres altres subscripcions a . +
                + Activeu la casella de confirmació i premeu aquest + botó per a donar-vos de baixa d'aquesta llista de correu. Avís: + aquesta acció s'executarà immediatament!

                -

                +

                Podeu veure una llista de les altres llistes de correu - on també esteu subscrit/a. Feu servir això si voleu aplicar els + on també esteu subscrit/a. Feu servir això si voleu aplicar els mateixos canvis d'opcions a les altres subscripcions.

                -

                -
                - - - - - - -
                - La vostra contrasenya de -
                - -
                -

                Heu oblidat la contrasenya?

                -
                - Premeu aquest botó per a rebre - un correu a la vostra adreça electrònica amb la vostra contrasenya. -

                -

                - -
                -
                - -
                -

                Canvi de la vostra contrasenya

                - - - - - - - - -
                Contrasenya nova:
                Confirmeu- - la:
                - - -

                Canvi global. -
                -
                - + + + +
                +La vostra contrasenya de +
                + +
                +

                Heu oblidat la contrasenya?

                +
                + Premeu aquest botó per a rebre + un correu a la vostra adreça electrònica amb la vostra contrasenya. +

                +

                + +
                +

                + +
                +

                Canvi de la vostra contrasenya

                + + + + + + + + +
                Contrasenya nova:
                Confirmeu- + la:
                + +

                Canvi global. +
                +

                - - +
                - Les vostres opcions de subscripció a la llista -
                +
                +Les vostres opcions de subscripció a la llista +
                - -

                +

                Els valors actuals estan marcats. - -

                Fixeu-vos que algunes de les opcions tenen una casella de verificació anomenada Canvi +

                Fixeu-vos que algunes de les opcions tenen una casella de verificació anomenada Canvi global. Si activeu aquesta casella s'aplicaran els canvis a totes les llistes de correu de les quals sigueu membre. Feu clic a Llista les meves altres subscripcions per a veure a quines altres llistes de correu esteu subscrit/a.

                - -
                - - Entrega de correu

                + + - + aquesta opció, recordeu-vos d'habilitar-la de nou en tornar, ja que + això no succeeix de manera automàtica. +

                - - - - + + +

                - - - - - - + + - - - - - - + + + - - + - - + - - - + i escolliu rebre les còpies, + s'afegirà la capçalera 'X-Mailman-Copy: yes' + a cadascuna d'aquestes còpies. + +

                +
                + +Entrega de correu

                Seleccioneu Habilitat per tal de rebre els missatges enviats a aquesta llista de correu. Seleccioneu Inhabilitat - si voleu romandre subscrit/a però no voleu rebre cap correu durant un + si voleu romandre subscrit/a però no voleu rebre cap correu durant un temps (per exemple en el cas que marxeu de vacances). En cas que inhabiliteu - aquesta opció, recordeu-vos d'habilitar-la de nou en tornar, ja que - això no succeeix de manera automàtica. -

                - Habilitat
                - Inhabilitat

                - Canvi global

                -
                +Habilitat
                +Inhabilitat

                +Canvi global

                +
                - Activació del mode de compilació

                - Si activeu el mode de compilació, rebreu tots els - missatges agrupats en un sol correu (normalment un per dia, però - probablement més d'un en llistes amb molta activitat), en lloc - de rebre'ls un per un en el moment en què s'envien. Si canvieu el mode de compilació d'Activat a Desactivat - és possible que rebeu una última compilació abans de començar +

                +Activació del mode de compilació

                + Si activeu el mode de compilació, rebreu tots els + missatges agrupats en un sol correu (normalment un per dia, però + probablement més d'un en llistes amb molta activitat), en lloc + de rebre'ls un per un en el moment en què s'envien. Si canvieu el mode de compilació d'Activat a Desactivat + és possible que rebeu una última compilació abans de començar a rebre'ls un per un. -

                - Desactivat
                - Activat
                - Preferiu compilacions MIME o de text pla?

                +

                +Desactivat
                +Activat
                +Preferiu compilacions MIME o de text pla?

                Pot ser que el vostre lector de correu no sigui compatible amb les compilacions MIME. Normalment es prefereixen - aquestes, però si teniu problemes a l'hora de llegir-les, seleccioneu + aquestes, però si teniu problemes a l'hora de llegir-les, seleccioneu les compilacions de text pla. -

                - MIME
                - Text pla

                - Canvi global -

                +MIME
                +Text pla

                +Canvi global +

                - Voleu rebre els vostres propis missatges a la llista?

                - Normalment, rebreu una còpia de cada missatge que - envieu a la llista. Si no voleu rebre aquesta còpia, seleccioneu No. -

                - No
                - Sí -
                - Voleu rebre un correu de confirmació en enviar un missatge a la llista?

                -

                - No
                - Sí -
                - Voleu rebre un correu de recordatori de la contrasenya per a la llista?

                +

                +Voleu rebre els vostres propis missatges a la llista?

                + Normalment, rebreu una còpia de cada missatge que + envieu a la llista. Si no voleu rebre aquesta còpia, seleccioneu No. +

                +No
                +Sí +
                +Voleu rebre un correu de confirmació en enviar un missatge a la llista?

                +

                +No
                +Sí +
                +Voleu rebre un correu de recordatori de la contrasenya per a la llista?

                Un cop al mes, rebreu un mail de recordatori amb les vostres contrasenyes per a cadascuna de les llistes a les quals - estigueu subscrit/a. Podeu cancel·lar aquesta opció si seleccioneu No, + estigueu subscrit/a. Podeu cancel·lar aquesta opció si seleccioneu No, amb la qual cosa no rebreu cap correu de recordatori.

                -
                - No
                -

                - Canvi global -

                - Voleu que se us oculti de la llista de subscriptors?

                - Quan algú visualitza la llista de subscriptors, hi - apareix normalment la vostra adreça - electrònica (de manera obfuscada per tal d'enganyar els - recollidors d'spam). Si no voleu que la vostra adreça - aparegui a la llista de subscriptors, seleccioneu Sí - per aquesta opció. -

                - No
                - Sí -
                - Quina llengua preferiu?

                -

                - -
                - A quines categories de temes us voleu + +No
                +Sí

                +Canvi global +

                +Voleu que se us oculti de la llista de subscriptors?

                + Quan algú visualitza la llista de subscriptors, hi + apareix normalment la vostra adreça + electrònica (de manera obfuscada per tal d'enganyar els + recollidors d'spam). Si no voleu que la vostra adreça + aparegui a la llista de subscriptors, seleccioneu Sí + per aquesta opció. +

                +No
                +Sí +
                +Quina llengua preferiu?

                +

                + +
                +A quines categories de temes us voleu subscriure?

                - Si seleccioneu un o més temes, podreu filtrar el tràfic - de la llista de correu, de manera que rebeu només una part de tots els + Si seleccioneu un o més temes, podreu filtrar el tràfic + de la llista de correu, de manera que rebeu només una part de tots els missatges. - Si un missatge coincideix amb algún dels temes que hàgiu seleccionat, + Si un missatge coincideix amb algún dels temes que hàgiu seleccionat, el rebreu; en cas contrari no el rebreu.

                Si un missatge no coincideix amb cap tema, la - regla d'entrega dependrà de la configuració de l'opció de sota. - Si no trieu cap tema d'interès, + regla d'entrega dependrà de la configuració de l'opció de sota. + Si no trieu cap tema d'interès, rebreu tots els missatges de la llista de correu.

                -
                - -
                - Voleu rebre els missatges que no coincideixin amb cap filtre de temes d'interès?

                - - Aquesta opció només us afecta en cas que estigueu - subscrit/a a almenys una categoria de temes d'interès de l'opció +

                + +
                +Voleu rebre els missatges que no coincideixin amb cap filtre de temes d'interès?

                + + Aquesta opció només us afecta en cas que estigueu + subscrit/a a almenys una categoria de temes d'interès de l'opció anterior. Descriu quina ha de ser la regla d'entrega per a aquells missatges que no coincideixin amb cap categoria de temes. Si escolliu No, no rebreu els missatges - que no coincideixin amb cap filtre de temes; si escolliu , + que no coincideixin amb cap filtre de temes; si escolliu Sí, sempre rebreu aquest tipus de missatges.

                - -

                Si no heu seleccionat cap tema d'interès a l'opció +

                Si no heu seleccionat cap tema d'interès a l'opció anterior, rebreu tots els missatges que s'enviin a la llista de correu. -

                - No
                - Sí -
                - Voleu evitar les còpies duplicades dels missatges?

                - - Si apareixeu de manera explícita a les capçaleres Per +

                +No
                +Sí +
                +Voleu evitar les còpies duplicades dels missatges?

                + + Si apareixeu de manera explícita a les capçaleres Per a: o Cc: - d'un missatge, podeu optar a no rebre'n una altra còpia de la llista de - correu. Seleccioneu per a no rebre més còpies + d'un missatge, podeu optar a no rebre'n una altra còpia de la llista de + correu. Seleccioneu Sí per a no rebre més còpies de la llista de correu, o seleccioneu No si voleu rebre-les. -

                Si la llista té l'opció de missatges +

                Si la llista té l'opció de missatges personalitzats per als subscriptors habilitada, - i escolliu rebre les còpies, - s'afegirà la capçalera 'X-Mailman-Copy: yes' - a cadascuna d'aquestes còpies. - -

                - No
                -

                - Canvi global -

                -
                -
                +No
                +Sí

                +Canvi global +

                +
                +
                -

                - - - - + + +

                diff --git a/templates/ca/private.html b/templates/ca/private.html index 68758d44..1ae56df3 100755 --- a/templates/ca/private.html +++ b/templates/ca/private.html @@ -1,60 +1,120 @@ - Autenticació per als arxius privats de %(realname)s - - - -
                +Autenticació per als arxius privats de %(realname)s + + + + %(message)s - - - - - - - - - - - - - - -
                - Autenticació per als arxius - privats de %(realname)s -
                Adreça electrònica:
                -
                Contrasenya:
                -
                -

                Important: - a partir d'aquí, caldrà que tingueu les galetes habilitades en el - vostre navegador. En cas contrari, us haureu de tornar a autenticar per a cada operació.

                - -

                La interfície administrativa del Mailman utilitza galetes de - sessió, de manera que us no hàgiu de tornar a autenticar per a cada - operació. Aquesta galeta expirarà automàticament quan + + + + + + + + + + + + + + +
                +Autenticació per als arxius + privats de %(realname)s +
                Adreça electrònica:
                +
                Contrasenya:
                +
                +

                Important: + a partir d'aquí, caldrà que tingueu les galetes habilitades en el + vostre navegador. En cas contrari, us haureu de tornar a autenticar per a cada operació.

                +

                La interfície administrativa del Mailman utilitza galetes de + sessió, de manera que us no hàgiu de tornar a autenticar per a cada + operació. Aquesta galeta expirarà automàticament quan sortiu del navegador. De manera alternativa, podeu eliminar-la - explícitament si feu clic al botó Surt a sota d'Altres - tasques administratives (el qual es mostrarà un cop hàgiu + explícitament si feu clic al botó Surt a sota d'Altres + tasques administratives (el qual es mostrarà un cop hàgiu entrat).

                - - - - - - + + + +
                - Password Reminder -
                If you don't remember your password, enter your email address + + + + + + - - - - -
                +Password Reminder +
                If you don't remember your password, enter your email address above and click the Remind button and your password will be emailed to you.
                - +
                +

                diff --git a/templates/ca/roster.html b/templates/ca/roster.html index a44ee679..23f4d454 100644 --- a/templates/ca/roster.html +++ b/templates/ca/roster.html @@ -1,53 +1,112 @@ - - - Subscriptors de la llista <MM-List-Name> - - - - -

                - - - - - - - - - - - - - - - -
                - Subscriptors de la llista - -
                - -

                -

                - -

                Feu clic a la vostra adreça per a visitar la pàgina - d'opcions de subscripció.
                (Les adreces entre parèntesi tenen l'entrega - inhabilitada.)

                -
                -
                - Membres - sense compilació de : -
                -
                -
                - Membres - amb compilació de : -
                -
                -

                -

                -

                -

                - - - + + +Subscriptors de la llista <mm-list-name></mm-list-name> + + +

                + + + + + + + + + + + + + + + +
                +Subscriptors de la llista + +
                +

                +

                +

                Feu clic a la vostra adreça per a visitar la pàgina + d'opcions de subscripció.
                (Les adreces entre parèntesi tenen l'entrega + inhabilitada.)

                +
                +
                +Membres + sense compilació de : +
                +
                +
                + Membres + amb compilació de : +
                +
                +

                +

                +

                +

                + +

                + diff --git a/templates/ca/subscribe.html b/templates/ca/subscribe.html index 9e66d341..340000fb 100644 --- a/templates/ca/subscribe.html +++ b/templates/ca/subscribe.html @@ -1,9 +1,72 @@ -Resultats de la subscripció a <MM-List-Name> +Resultats de la subscripció a <mm-list-name></mm-list-name> -

                Resultats de la subscripció a

                - - - +

                Resultats de la subscripció a

                + + + diff --git a/templates/cs/admindbdetails.html b/templates/cs/admindbdetails.html index 7fdd4c38..b8ffd050 100644 --- a/templates/cs/admindbdetails.html +++ b/templates/cs/admindbdetails.html @@ -1,66 +1,129 @@ -Administrativní po¾adavky mohou být zobrazeny dvìma zpùsoby. Buï -hromadnì a nebo detailnì. -Pøi hromadném zobrazení jsou všechny po¾adavky pocházející od -stejného u¾ivatele zobrazeny dohromady a mù¾e o nich být rozhodnuto -najednou. Pøi detailním zobrazení je ka¾dý pøíspìvek zobrazen -samostatnì a administrátor rozhoduje o ka¾dém pøíspìvku zvláš». -Detailní zobrazení obsahuje navíc všechny hlavièky a èást z tìla -zprávy. +Administrativní po¾adavky mohou být zobrazeny dvìma zpùsoby. Buï +hromadnì a nebo detailnì. +Pøi hromadném zobrazení jsou vÅ¡echny po¾adavky pocházející od +stejného u¾ivatele zobrazeny dohromady a mù¾e o nich být rozhodnuto +najednou. Pøi detailním zobrazení je ka¾dý pøíspìvek zobrazen +samostatnì a administrátor rozhoduje o ka¾dém pøíspìvku zvláš». +Detailní zobrazení obsahuje navíc vÅ¡echny hlavièky a èást z tìla +zprávy. -

                Pro pozastavené pøíspìvky vyberte jednu z tìchto akcí: +

                Pro pozastavené pøíspìvky vyberte jednu z tìchto akcí:

                  -
                • Ponechat - Nech zprávu èekat ve frontì, nemá to vliv na -nastavení forwardování, to znamená, ¾e zprávu mù¾ete forwardovat -a pøitom ponechat ve frontì +
                • Ponechat - Nech zprávu èekat ve frontì, nemá to vliv na +nastavení forwardování, to znamená, ¾e zprávu mù¾ete forwardovat +a pøitom ponechat ve frontì -
                • Akceptovat - Rozešli zprávu do konference. Pokud se -nejedná o zprávu, ale o po¾adavek na pøihlášení úèastníka, pøihlas +
                • Akceptovat - RozeÅ¡li zprávu do konference. Pokud se +nejedná o zprávu, ale o po¾adavek na pøihlášení úèastníka, pøihlas ho. -
                • Zamítnout - Vra» zprávu odesílateli. Mù¾ete uvést i dùvod, -který bude uveden v zamítavé odpovìdi. Zpráva bude z fronty -vymazána. Pokud se jedná o po¾adavek na pøihlášení, bude zamítnut. +
                • Zamítnout - Vra» zprávu odesílateli. Mù¾ete uvést i dùvod, +který bude uveden v zamítavé odpovìdi. Zpráva bude z fronty +vymazána. Pokud se jedná o po¾adavek na pøihlášení, bude zamítnut. -
                • Zahodit - vhodné pro SPAM. Zpráva potichu zmizí, nikomu se -nic neposílá. I kdy¾ se jedná o po¾adavek na pøihlášení, nebude se -posílat ¾ádné oznámení. -
                +
              • Zahodit - vhodné pro SPAM. Zpráva potichu zmizí, nikomu se +nic neposílá. I kdy¾ se jedná o po¾adavek na pøihlášení, nebude se +posílat ¾ádné oznámení. +
              • +

                Vybráním volby Ponechat mù¾ete napø. ponechat zprávu ve +frontì pro správce serveru. To vyu¾ijete asi pøedevším u zpráv, které +nejsou obyèejným spamem, ale vy¾adují další odvetné akce. -

                Vybráním volby Ponechat mù¾ete napø. ponechat zprávu ve -frontì pro správce serveru. To vyu¾ijete asi pøedevším u zpráv, které -nejsou obyèejným spamem, ale vy¾adují další odvetné akce. +S pomocí volbyPøeposlat mù¾ete kopii zprávy odeslat na adresu, +kterou vyplníte do vstupního pole vedle této volby. Pokud potøebujete +nìjaký pøíspìvek modifikovat, pøed rozesláním u¾ivatelùm, musíte si +jej pøeposlat na svou adresu a pùvodní zprávu zahodit. Poté co zprávu +upravíte, musíte ji poslat zpìt do konference, pøípadnì s hlavièkou +Approved: do které doplníte heslo pro danou +konferenci. Netiquetta vy¾aduje, abyste v takovém pøípadì do textu +uvedli upozornìní, ¾e jste zprávu pozmìnili a vysvìtlili, proè jste +to udìlali. -S pomocí volbyPøeposlat mù¾ete kopii zprávy odeslat na adresu, -kterou vyplníte do vstupního pole vedle této volby. Pokud potøebujete -nìjaký pøíspìvek modifikovat, pøed rozesláním u¾ivatelùm, musíte si -jej pøeposlat na svou adresu a pùvodní zprávu zahodit. Poté co zprávu -upravíte, musíte ji poslat zpìt do konference, pøípadnì s hlavièkou -Approved: do které doplníte heslo pro danou -konferenci. Netiquetta vy¾aduje, abyste v takovém pøípadì do textu -uvedli upozornìní, ¾e jste zprávu pozmìnili a vysvìtlili, proè jste -to udìlali. +

                Pokud má pøispìvatel nastaven pøíznak moderovat, co¾ se typicky +pou¾ívá u nových úèastníkù, kdy se nejdøíve èeká, jakou úroveò budou +mít jejich pøíspìvky, mù¾ete souèasnì s propuštìním pøíspìvku do +distribuce tento pøíznak zrušit, tak¾e u¾ivatel bude moci nadále +pøispívat sám. -

                Pokud má pøispìvatel nastaven pøíznak moderovat, co¾ se typicky -pou¾ívá u nových úèastníkù, kdy se nejdøíve èeká, jakou úroveò budou -mít jejich pøíspìvky, mù¾ete souèasnì s propuštìním pøíspìvku do -distribuce tento pøíznak zrušit, tak¾e u¾ivatel bude moci nadále -pøispívat sám. - -

                Pokud odesílatel není úèastníkem konference, mù¾ete jeho emailovou -adresu vlo¾it do filtru odesílatelù. Jak tyto filtry fungují -je popsáno na stránce s konfigurací filtrování -. Pøíspìvky mohou být na základì filtrù buï automaticky -akceptovány, automaticky pozdr¾eny do rozhodnutí moderátora, -automaticky vráceny a nebo automaticky zahozeny (SPAM). Pokud -odesílatel je úèastníkem a nebo u¾ je zaveden ve filtrech, tato volba +

                Pokud odesílatel není úèastníkem konference, mù¾ete jeho emailovou +adresu vlo¾it do filtru odesílatelù. Jak tyto filtry fungují +je popsáno na stránce s konfigurací filtrování +. Pøíspìvky mohou být na základì filtrù buï automaticky +akceptovány, automaticky pozdr¾eny do rozhodnutí moderátora, +automaticky vráceny a nebo automaticky zahozeny (SPAM). Pokud +odesílatel je úèastníkem a nebo u¾ je zaveden ve filtrech, tato volba nebude k dispozici. -

                Pokud jste ji¾ prošli celou stránkou kliknìte na jejím horním nebo -spodním okraji na tlaèítko Potvrdit všechny zmìny. Tím se -pøenesou provedené zmìny do databáze a server podle daných pokynù provede -potøebné operace. Pokud u nìkteré polo¾ky nevyberete ani jednu volbu, -polo¾ka nebude nijak ovlivnìna a zùstane ulo¾ena v databázi. Budete o ni -moci rozhodnout kdykoliv pøíštì. +

                Pokud jste ji¾ prošli celou stránkou kliknìte na jejím horním nebo +spodním okraji na tlaèítko Potvrdit všechny zmìny. Tím se +pøenesou provedené zmìny do databáze a server podle daných pokynù provede +potøebné operace. Pokud u nìkteré polo¾ky nevyberete ani jednu volbu, +polo¾ka nebude nijak ovlivnìna a zùstane ulo¾ena v databázi. Budete o ni +moci rozhodnout kdykoliv pøíštì. -

                Zpìt na pøehled èekajících po¾adavkù. +

                Zpìt na pøehled èekajících po¾adavkù. +

                \ No newline at end of file diff --git a/templates/cs/admindbpreamble.html b/templates/cs/admindbpreamble.html index e6925ca6..3dd45a56 100644 --- a/templates/cs/admindbpreamble.html +++ b/templates/cs/admindbpreamble.html @@ -1,14 +1,78 @@ -Na této stránce naleznete všechny pøíspìvky do konference %(listname)s, -které nebyly z nìjakého dùvodu rozeslány a èekají na Vaše rozhodnutí. -Momentálnì jsou zobrazeny %(description)s +Na této stránce naleznete vÅ¡echny pøíspìvky do konference %(listname)s, +které nebyly z nìjakého dùvodu rozeslány a èekají na VaÅ¡e rozhodnutí. +Momentálnì jsou zobrazeny %(description)s -

                U ka¾dého administrativního po¾adavku musíte vybrat akci, která se -s ním má provést a nakonec vybrané akce provedete kliknutím na -Potvrdit všechny zmìny. Podrobnìjší informace o tom, jak -zacházet s administrativním rozhraním naleznete +

                U ka¾dého administrativního po¾adavku musíte vybrat akci, která se +s ním má provést a nakonec vybrané akce provedete kliknutím na +Potvrdit všechny zmìny. Podrobnìjší informace o tom, jak +zacházet s administrativním rozhraním naleznete zde. -

                Takté¾ si mù¾ete prohlédnout pøehled všech -èekajících po¾adavkù zobrazený tak, ¾e -pøíspìvky od jednoho pøispìvatele jsou slouèeny dohromady a mù¾ete -o nich rozhodnout souèasnì. +

                Takté¾ si mù¾ete prohlédnout pøehled všech +èekajících po¾adavkù zobrazený tak, ¾e +pøíspìvky od jednoho pøispìvatele jsou slouèeny dohromady a mù¾ete +o nich rozhodnout souèasnì. +

                \ No newline at end of file diff --git a/templates/cs/admindbsummary.html b/templates/cs/admindbsummary.html index 17e82691..8ce0bdb2 100644 --- a/templates/cs/admindbsummary.html +++ b/templates/cs/admindbsummary.html @@ -1,18 +1,82 @@ -Pøehled po¾adavkù pro konferenci %(listname)s. +Pøehled po¾adavkù pro konferenci %(listname)s. -Tato stránka obsahuje pøehled všech po¾adavkù pro administrátora -setøídìný tak, ¾e po¾adavky od jednoho u¾ivatele jsou slouèeny -dohromady a mù¾e o nich být rozhodnuto najednou. +Tato stránka obsahuje pøehled vÅ¡ech po¾adavkù pro administrátora +setøídìný tak, ¾e po¾adavky od jednoho u¾ivatele jsou slouèeny +dohromady a mù¾e o nich být rozhodnuto najednou. -Nejdøíve jsou zaøazeny po¾adavky na pøihlášení a odhlášení a za nimi -pøípadnì pøíspìvky, které èekají na schválení distribuce moderátorem. +Nejdøíve jsou zaøazeny po¾adavky na pøihlášení a odhlášení a za nimi +pøípadnì pøíspìvky, které èekají na schválení distribuce moderátorem. -

                U ka¾dého administrativního po¾adavku musíte vybrat akci, která se -s ním má provést a nakonec vybrané akce provedete kliknutím na -Potvrdit všechny zmìny. Podrobnìjší informace o tom, jak -zacházet s administrativním rozhraním naleznete +

                U ka¾dého administrativního po¾adavku musíte vybrat akci, která se +s ním má provést a nakonec vybrané akce provedete kliknutím na +Potvrdit všechny zmìny. Podrobnìjší informace o tom, jak +zacházet s administrativním rozhraním naleznete zde. -

                Takté¾ si mù¾ete prohlédnout detailní pøehled všech -èekajících po¾adavkù kde je ka¾dý -pøíspìvek zobrazen samostatnì. +

                Takté¾ si mù¾ete prohlédnout detailní pøehled všech +èekajících po¾adavkù kde je ka¾dý +pøíspìvek zobrazen samostatnì. +

                \ No newline at end of file diff --git a/templates/cs/admlogin.html b/templates/cs/admlogin.html index 3955a1cf..590688aa 100755 --- a/templates/cs/admlogin.html +++ b/templates/cs/admlogin.html @@ -1,37 +1,98 @@ - %(listname)s %(who)s pøihlášení +%(listname)s %(who)s pøihlášení - - -
                + + + %(message)s - - - - - - - - - - - -
                - %(listname)s %(who)s - pøihlášení -
                Heslo pro %(who)s:
                -
                -

                Pozor: Aby systém fungoval - správnì musíte mít povolené cookie ve Vašem prohlí¾eèi. + + + + + + + + + + + +
                +%(listname)s %(who)s + pøihlášení +
                Heslo pro %(who)s:
                +
                +

                Pozor: Aby systém fungoval + správnì musíte mít povolené cookie ve Vašem prohlí¾eèi. -

                Cookies se pou¾ívají na to, abyste nemuseli jeden - ka¾dý po¾adavek znovu potvrzovat heslem. Zaniknou - pokud ukonèíte prohlí¾eè a nebo kdy¾ se odhlásíte. - Odhlásit se mù¾ete kliknutím na Odhlásit. - Tento odkaz uvidíte a¾ po úspìšném pøihlášení. -

                +

                Cookies se pou¾ívají na to, abyste nemuseli jeden + ka¾dý po¾adavek znovu potvrzovat heslem. Zaniknou + pokud ukonèíte prohlí¾eè a nebo kdy¾ se odhlásíte. + Odhlásit se mù¾ete kliknutím na Odhlásit. + Tento odkaz uvidíte a¾ po úspìšném pøihlášení. +

                diff --git a/templates/cs/archidxentry.html b/templates/cs/archidxentry.html index f9bb57aa..5c620b20 100644 --- a/templates/cs/archidxentry.html +++ b/templates/cs/archidxentry.html @@ -1,4 +1,68 @@ -
              • %(subject)s -  -%(author)s - +
              • %(subject)s +  +%(author)s + +
              • \ No newline at end of file diff --git a/templates/cs/archidxfoot.html b/templates/cs/archidxfoot.html index e23bb655..d25bb4a8 100644 --- a/templates/cs/archidxfoot.html +++ b/templates/cs/archidxfoot.html @@ -1,21 +1,85 @@ - -

                - Datum posledního pøíspìvku: - %(lastdate)s
                - Archivováno: %(archivedate)s -

                -

                  -
                • Øazení zpráv podle: + +

                  +Datum posledního pøíspìvku: +%(lastdate)s
                  +Archivováno: %(archivedate)s +

                  +

                  -

                  -


                  - Tento archív je vytvoøen pomocí programu +
                +

                +


                +Tento archív je vytvoøen pomocí programu Pipermail %(version)s. - - + + +

                \ No newline at end of file diff --git a/templates/cs/archidxhead.html b/templates/cs/archidxhead.html index 7e3f6b79..e1f37f72 100644 --- a/templates/cs/archidxhead.html +++ b/templates/cs/archidxhead.html @@ -1,24 +1,89 @@ - - - Konference %(listname)s %(archive)s Archiv po %(archtype)s - + + + +Konference %(listname)s %(archive)s Archiv po %(archtype)s + %(encoding)s - - - -

                %(archive)s archiv po %(archtype)s

                -
                  -
                • Øazení zpráv podle: + + + +

                  %(archive)s archiv po %(archtype)s

                  + -

                  Od: %(firstdate)s
                  - Do: %(lastdate)s
                  - Velikost: %(size)s

                  -

                    +
                  +

                  Od: %(firstdate)s
                  +Do: %(lastdate)s
                  +Velikost: %(size)s

                  +

                    +

                  \ No newline at end of file diff --git a/templates/cs/archlistend.html b/templates/cs/archlistend.html index eeb3152e..2e1191b0 100644 --- a/templates/cs/archlistend.html +++ b/templates/cs/archlistend.html @@ -1,2 +1,64 @@ -
                - + diff --git a/templates/cs/archliststart.html b/templates/cs/archliststart.html index f2bed37e..ed64fdb3 100644 --- a/templates/cs/archliststart.html +++ b/templates/cs/archliststart.html @@ -1,4 +1,70 @@ - - - - +
                ArchivZobrazit dle:Verze ke sta¾ení
                + + + + + +
                ArchivZobrazit dle:Verze ke stažení
                diff --git a/templates/cs/archtoc.html b/templates/cs/archtoc.html index 9e058ca5..78eb81ec 100644 --- a/templates/cs/archtoc.html +++ b/templates/cs/archtoc.html @@ -1,20 +1,84 @@ - - - Archiv konference %(listname)s - + + + +Archiv konference %(listname)s + %(meta)s - - -

                Archiv konference %(listname)s

                -

                - Mù¾ete si pøeèíst více informací - a nebo mù¾etestáhnout celý archiv + + +

                Archiv konference %(listname)s

                +

                + Mù¾ete si pøeèíst více informací + a nebo mù¾etestáhnout celý archiv (%(size)s).

                %(noarchive_msg)s %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/cs/archtocentry.html b/templates/cs/archtocentry.html index df7f9a19..cd26d875 100644 --- a/templates/cs/archtocentry.html +++ b/templates/cs/archtocentry.html @@ -1,13 +1,76 @@ - -
                %(archivelabel)s: - [ Vlákno ] - [ Pøedmìt ] - [ Autor ] - [ Datum ] -
                %(archivelabel)s: +[ Vlákno ] +[ Pøedmìt ] +[ Autor ] +[ Datum ] +
                - - - - - - - - - - - - - - - - - - + + + + + +

                - -- - -
                -

                  -

                - Konference - - - -
                -

                -

                Pokud si chcete prohlédnou pøedchozí pøíspìvky - v této konferenci, navštivte - archiv konference - . - -

                -
                - Jak komunikovat s konferencí -
                - Pøíspìvky zasílejte na adresu: - . + + + +Informace o konferenci <mm-list-name></mm-list-name> + + +

                + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + +
                + -- + +
                +

                  +

                +Konference + + + +
                +

                +

                Pokud si chcete prohlédnou pøedchozí pøíspìvky + v této konferenci, navštivte + archiv konference +. + +

                +
                +Jak komunikovat s konferencí +
                + Pøíspìvky zasílejte na adresu: + . -

                Pokud se chcete pøihlásit, nebo nìjak konfigurovat své u¾ivatelské konto, - mù¾ete tak uèinit právì zde. -

                - Pøihlášení do konference -
                -

                - Pro pøihlášení do konference musíte vyplnit následující formuláø. - -

                  - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + - - - - - -
                  Vaše e-mailová adresa: -  
                  Vaše jméno (nepovinné): 
                  Musíte zadat heslo pro ochranu svého konta - pøed nepovolanými osobami. V ¾ádném pøípadì nepou¾ívejte ¾ádné heslo, které - pou¾íváte na nìjakém jiném systému, pøípadnì heslo, které je potøeba - utajovat. Heslo je pøenášeno bez jakéhokoliv zabezpeèení a navíc - Vám bude zasíláno elektronickou poštou. -

                  - Pokud heslo nezadáte, systém Vám pøidìlí náhodné heslo, které Vám - bude zasláno mailem. - -
                  -
                  Zvolte si heslo: 
                  A ještì jednou to samé pro kontrolu: 
                  Jaký jazyk preferujete pro komunikaci s mailmanem?  
                  Pøejete si dostávat pøíspìvky jen jednou dennì, všechny v jednom - dopise? Tuto volbu znáte asi jako digest. +

                  Pokud se chcete pøihlásit, nebo nìjak konfigurovat své u¾ivatelské konto, + mù¾ete tak uèinit právì zde. +

                  +Pøihlášení do konference +
                  +

                  + Pro pøihlášení do konference musíte vyplnit následující formuláø. + +

                    + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - -
                    Vaše e-mailová adresa: + 
                    Vaše jméno (nepovinné): 
                    Musíte zadat heslo pro ochranu svého konta + pøed nepovolanými osobami. V ¾ádném pøípadì nepou¾ívejte ¾ádné heslo, které + pou¾íváte na nìjakém jiném systému, pøípadnì heslo, které je potøeba + utajovat. Heslo je pøenášeno bez jakéhokoliv zabezpeèení a navíc + Vám bude zasíláno elektronickou poštou. +

                    + Pokud heslo nezadáte, systém Vám pøidìlí náhodné heslo, které Vám + bude zasláno mailem. + +
                    +
                    Zvolte si heslo: 
                    A ještì jednou to samé pro kontrolu: 
                    Jaký jazyk preferujete pro komunikaci s mailmanem?  
                    Pøejete si dostávat pøíspìvky jen jednou dennì, všechny v jednom + dopise? Tuto volbu znáte asi jako digest. Ne - Ano -
                    -
                    -
                    - -
                  -
                  - - Úèastníci konference -
                  - - - -

                  - - - -

                  - - - +
                Ne + Ano +
                +
                +
                + + +

                + +Úèastníci konference +
                + + + +

                + + + +

                + +

                + diff --git a/templates/cs/options.html b/templates/cs/options.html index ba8b795f..8b5468bc 100644 --- a/templates/cs/options.html +++ b/templates/cs/options.html @@ -1,295 +1,328 @@ - - <MM-Presentable-User> Konfigurace konference <MM-List-Name> - - - - -
                - - Nastavení pro u¾ivatele: -
                + +<mm-presentable-user> Konfigurace konference <mm-list-name></mm-list-name></mm-presentable-user> + + + +
                + + Nastavení pro u¾ivatele: +

                - - - - - +
                - U¾ivatel , konfigurace - u¾ivatelského úètu pro konferenci . -
                - - - - -

                -

                + + + +
                +U¾ivatel , konfigurace + u¾ivatelského úètu pro konferenci . +
                + + +

                +

                - - +

                - - - - - - - - -
                - - Zmìna konfigurace pro konferenci -
                Mù¾ete zmìnit e-mailovou adresu, na kterou se - zasílají pøíspìvky. Zadejte novou adresu do tohoto pole. Na Vaši - stávající adresu bude zaslána ¾ádost o ovìøení a zmìna bude - provedena teprve po potvrzení. - -

                Na potvrzení budeme èekat dní. - -

                Mù¾ete si nastavit i své jméno, jako napø. - Petr Novák. - -

                Pokud se mají provedené zmìny projevit ve všech - konferencích na tomto serveru, musíte zaškrtnout volbu - Zmìnit globálnì. - -

                - - - - - - - -
                Nová adresa:
                Znovu pro potvrzení: -
                -
                - - - - -
                Celé jméno - (nepovinné):
                -
                -

                Zmìnit globálnì

                - + + + +
                + +Zmìna konfigurace pro konferenci +
                Mù¾ete zmìnit e-mailovou adresu, na kterou se + zasílají pøíspìvky. Zadejte novou adresu do tohoto pole. Na Vaši + stávající adresu bude zaslána ¾ádost o ovìøení a zmìna bude + provedena teprve po potvrzení. + +

                Na potvrzení budeme èekat dní. + +

                Mù¾ete si nastavit i své jméno, jako napø. + Petr Novák. + +

                Pokud se mají provedené zmìny projevit ve všech + konferencích na tomto serveru, musíte zaškrtnout volbu + Zmìnit globálnì. + +

                + + + + + + + +
                Nová adresa:
                Znovu pro potvrzení: +
                +

                + + + + +
                Celé jméno + (nepovinné):
                + +
                +

                Zmìnit globálnì

                +

                - - - - + +

                -

                -Odhlášení z - - -Vaše ostatní pøihlášené konference na -
                - Pro odhlášení zaškrtnìte políèko a kliknìte na tlaèítko - Odhlásit. Pozor: - Zmìna se projeví okam¾itì. + + + - + +
                +

                +Odhlášení z + +Vaše ostatní pøihlášené konference na +
                + Pro odhlášení zaškrtnìte políèko a kliknìte na tlaèítko + Odhlásit. Pozor: + Zmìna se projeví okam¾itì.

                -

                - Mù¾ete si zobrazit seznam všech dalších konferencí na tomto - serveru, jejich¾ jste èleny. Pokud zvolíte tuto volbu, - promítnou se zde provedené zmìny i do všech ostatních kont. +

                + Mù¾ete si zobrazit seznam všech dalších konferencí na tomto + serveru, jejich¾ jste èleny. Pokud zvolíte tuto volbu, + promítnou se zde provedené zmìny i do všech ostatních kont.

                -

                -
                - - - - - - - + + + + %(textlink)s - diff --git a/templates/da/archtocnombox.html b/templates/da/archtocnombox.html index 756f9e1e..b8dfb9f5 100644 --- a/templates/da/archtocnombox.html +++ b/templates/da/archtocnombox.html @@ -1,18 +1,82 @@ - - - %(listname)s arkiv - + + + +%(listname)s arkiv + %(meta)s - - -

                %(listname)s arkiv

                -

                + + +

                %(listname)s arkiv

                +

                Du kan se mere information om denne liste.

                %(noarchive_msg)s %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/da/article.html b/templates/da/article.html index 6a69904f..79774050 100644 --- a/templates/da/article.html +++ b/templates/da/article.html @@ -1,12 +1,13 @@ - - - %(title)s - - - - - + +

                Arkiv for maillisten %(listname)s

                +

                + Ingen meddelelser er sendt til denne liste endnu, så arkivet er for tiden tomt. + Mere information om listen er tilgængelig.

                - - + + diff --git a/templates/da/headfoot.html b/templates/da/headfoot.html index b25cdf47..0d2cb107 100644 --- a/templates/da/headfoot.html +++ b/templates/da/headfoot.html @@ -1,18 +1,81 @@ -Teksten kan indeholde formateringskoder -som udskiftes med værdier fra listens opsætning. For detaljer, se +Teksten kan indeholde formateringskoder +som udskiftes med værdier fra listens opsætning. For detaljer, se Pythons formateringsregler (engelsk). Gyldige koder er:
                  -
                • real_name - Listens formaterede navn; normalt +
                • real_name - Listens formaterede navn; normalt listenavnet med stort forbogstav eller store bogstaver enkelte steder.
                • list_name - Listens navn som brugt i URLer, - hvor det har betydning om den staves med store eller små bogstaver. + hvor det har betydning om den staves med store eller smÃ¥ bogstaver. (For bagudkompatibilitet, er _internal_name det samme.)
                • host_name - Internetadressen (fully qualified - domain name) til maskinen som listeserveren kører på. + domain name) til maskinen som listeserveren kører pÃ¥.
                • web_page_url - Basis URL for Mailman. Denne kan kombineres med f.eks. listinfo/%(list_name)s @@ -22,5 +85,5 @@
                • info - Fuld beskrivelse af listen. -
                • cgiext - Extension som tilføjes CGI scripts. -
                +
              • cgiext - Extension som tilføjes CGI scripts. +
              • diff --git a/templates/da/listinfo.html b/templates/da/listinfo.html index 09869b2b..50cd9124 100644 --- a/templates/da/listinfo.html +++ b/templates/da/listinfo.html @@ -1,141 +1,202 @@ - - - - <MM-List-Name> Infoside - - - -

                -

                - Vaše heslo pro -
                - -
                -

                Zapomnìli jste heslo?

                -
                - Kliknìte na toto tlaèítko a heslo Vám bude zasláno na Vaší + + + - -
                +Vaše heslo pro +
                + +
                +

                Zapomnìli jste heslo?

                +
                + Kliknìte na toto tlaèítko a heslo Vám bude zasláno na Vaší e-mailovou adresu.

                - -

                - -
                -
                - -
                -

                Zmìna hesla

                - - - - - - - - + +
                + +
                +

                +
                Nové heslo:
                A ještì jednou nové pro kontrolu:
                + +
                +

                Zmìna hesla

                + + + + + + + +
                Nové heslo:
                A ještì jednou nové pro kontrolu:
                - -

                Zmìnit globálnì. -
                - + +

                Zmìnit globálnì. +

                - - +
                -Nastavení Vašeho konta pro konferenci -
                +
                +Nastavení Vašeho konta pro konferenci +
                -

                -Aktuální hodnoty jsou zaškrtnuty

                - -

                Pozor, u nìkterých voleb je checkbox Nastavit globálnì. -Pokud jej zaškrtnete, provedou se zmìny ve všech konferencích, do -kterých jste pøihlášeni na tomto serveru. Pokud chcete vìdìt, které to -jsou, kliknìte na Seznam ostatních konferencí nahoøe na stránce. +Aktuální hodnoty jsou zaÅ¡krtnuty

                +

                Pozor, u nìkterých voleb je checkbox Nastavit globálnì. +Pokud jej zaškrtnete, provedou se zmìny ve všech konferencích, do +kterých jste pøihlášeni na tomto serveru. Pokud chcete vìdìt, které to +jsou, kliknìte na Seznam ostatních konferencí nahoøe na stránce.

                - - - +
                - - Zakázat zasílání pøíspìvkù

                -Vyberte tuto volbu, pokud si nepøejete dostávat ¾ádné pøíspìvky. -

                - Zasílat
                - Nezasílat
                - Nastavit globálnì -
                + - - - + +Pokud Váš poštovní klient nedoká¾e pøeèíst MIME zprávu, nastavte si zde +volbu èistý text. Tato volba nemá ¾ádný význam, pokud není digest aktivní.
                +

                - - - - - - + + - - - - - - - - - - + + + + + - - - +

                +
                + +Zakázat zasílání pøíspìvkù

                +Vyberte tuto volbu, pokud si nepøejete dostávat ¾ádné pøíspìvky. +

                + Zasílat
                + Nezasílat
                +Nastavit globálnì +
                - Zapnutí digest re¾imu

                -Pokud je zapnut digest re¾im dostanete všechny pøíspìvky v jednom dopise -jednou dennì. Pokud digest vypnete, dostanete pøíspìvky z posledního dne -ještì v digest formì. -

                - Vypnuto
                - Zapnuto -
                - Pøejete si digest dostávat jako MIME zprávu nebo èistý +
                +Zapnutí digest re¾imu

                +Pokud je zapnut digest re¾im dostanete všechny pøíspìvky v jednom dopise +jednou dennì. Pokud digest vypnete, dostanete pøíspìvky z posledního dne +ještì v digest formì. +

                +Vypnuto
                +Zapnuto +
                +Pøejete si digest dostávat jako MIME zprávu nebo èistý text?

                -Pokud Váš poštovní klient nedoká¾e pøeèíst MIME zprávu, nastavte si zde -volbu èistý text. Tato volba nemá ¾ádný význam, pokud není digest aktivní.
                -

                - MIME
                - Èistý text

                - Nastavit globálnì -

                +MIME
                +Èistý text

                +Nastavit globálnì +

                - Pøejete si dostávat pøíspìvky, které jste sami zaslali do +
                +Pøejete si dostávat pøíspìvky, které jste sami zaslali do konference?

                -

                - Ne
                - Ano -
                - Pøejete si dostávat oznámení o rozeslání vlastních pøíspìvkù?

                -

                - Ne
                - Ano -
                - Chcete dostávat upozornìní na hesla?

                - Ve výchozí konfiguraci dostáváte jednou mìsíènì mailem - zprávu, kde je uveden seznam konferencí a k nim pøíslušná - hesla, která potøebujete pro rùzné administrativní operace. +

                +Ne
                +Ano +
                + Pøejete si dostávat oznámení o rozeslání vlastních pøíspìvkù?

                +

                +Ne
                +Ano +
                +Chcete dostávat upozornìní na hesla?

                + Ve výchozí konfiguraci dostáváte jednou mìsíènì mailem + zprávu, kde je uveden seznam konferencí a k nim pøísluÅ¡ná + hesla, která potøebujete pro rùzné administrativní operace. Pokud vyberete Ne, nebudete tento mail - dostávat. Pøípadnì zapomenuté heslo si mù¾ete poslat - z webovského rozhraní. -

                - Ne
                - Ano

                - Nastavit globálnì -

                - Pøejete si být vidìt v seznamu úèastníkù?

                - Pokud si nìkdo zobrazí seznam úèastníkù, ve výchozí - konfiguraci vidí adresy všech úèastníkù. Pokud na tomto - seznamu nechcete být uvedeni, mù¾ete zde zobrazení zakázat. -

                - Zobrazit
                - Skrýt -
                - Jakou øeè preferujete pro komunikaci s listserverem?

                -

                - -
                - Která témata Vás zajímají?

                - Pokud si vyberete jedno nebo více témat, mù¾ete - filtrovat pøíspìvky z konference a dostávat jedinì - ty, které Vás zajímají. - -

                U pøíspìvkù, které se nepodaøí zatøídit do ¾ádné - kategorie, závisí na nastavení další volby, jestli je - dostanete nebo ne. Pokud nezvolíte ¾ádné téma, budete - dostávat všechny pøíspìvky. -

                - -
                - Chcete dostávat pøíspìvky, které se nepodaøilo - zaøadit do ¾ádné kategorie?

                - - Tato volba má smysl jedinì pokud jste si v pøedchozí - volbì vybral nìjaké téma. Pokud zde - odpovíte Ne nebudete dostávat ¾ádné - pøíspìvky, které nespadají do Vašich témat, jinak - budete dostávat i nezatøiditelné. -

                - Ne
                - Ano -
                - Eliminuj duplicitní pøíspìvky?

                - - Pokud dostane listserver k rozeslání pøíspìvek - jste uvedeni v hlavièce To: - nebo Cc: dostanete pøíspìvek dvakrát, jednou - pøímo a jednou pøes konferenci. Pokud vyberete - volbu Ano, listserver takový pøíspìvek na vaši - adresu nezašle a vy dostanete jen ten, který vám - zasílá pøímo autor. Pokud vyberete volbu Ne - dostanete pøíspìvek pravdìpodobnì dvakrát. - -

                Pokud listserver rozesílá ka¾dý pøíspìvek zvláš», - a vy máte nastaveno, ¾e si pøejete dostávat - duplicitní pøíspìvky, bude mít ka¾dý duplicitní - pøíspìvek pøidánu hlavièku + dostávat. Pøípadnì zapomenuté heslo si mù¾ete poslat + z webovského rozhraní. +

                +Ne
                +Ano

                +Nastavit globálnì +

                +Pøejete si být vidìt v seznamu úèastníkù?

                + Pokud si nìkdo zobrazí seznam úèastníkù, ve výchozí + konfiguraci vidí adresy všech úèastníkù. Pokud na tomto + seznamu nechcete být uvedeni, mù¾ete zde zobrazení zakázat. +

                +Zobrazit
                +Skrýt +
                +Jakou øeè preferujete pro komunikaci s listserverem?

                +

                + +
                +Která témata Vás zajímají?

                + Pokud si vyberete jedno nebo více témat, mù¾ete + filtrovat pøíspìvky z konference a dostávat jedinì + ty, které Vás zajímají. + +

                U pøíspìvkù, které se nepodaøí zatøídit do ¾ádné + kategorie, závisí na nastavení další volby, jestli je + dostanete nebo ne. Pokud nezvolíte ¾ádné téma, budete + dostávat všechny pøíspìvky. +

                + +
                +Chcete dostávat pøíspìvky, které se nepodaøilo + zaøadit do ¾ádné kategorie?

                + + Tato volba má smysl jedinì pokud jste si v pøedchozí + volbì vybral nìjaké téma. Pokud zde + odpovíte Ne nebudete dostávat ¾ádné + pøíspìvky, které nespadají do Vašich témat, jinak + budete dostávat i nezatøiditelné. +

                +Ne
                +Ano +
                +Eliminuj duplicitní pøíspìvky?

                + + Pokud dostane listserver k rozeslání pøíspìvek + jste uvedeni v hlavièce To: + nebo Cc: dostanete pøíspìvek dvakrát, jednou + pøímo a jednou pøes konferenci. Pokud vyberete + volbu Ano, listserver takový pøíspìvek na vaši + adresu nezašle a vy dostanete jen ten, který vám + zasílá pøímo autor. Pokud vyberete volbu Ne + dostanete pøíspìvek pravdìpodobnì dvakrát. + +

                Pokud listserver rozesílá ka¾dý pøíspìvek zvláš», + a vy máte nastaveno, ¾e si pøejete dostávat + duplicitní pøíspìvky, bude mít ka¾dý duplicitní + pøíspìvek pøidánu hlavièku X-Mailman-Copy: yes. -

                - Ne
                - Ano

                - Nastavit globálnì -

                -
                -
                +Ne
                +Ano

                +Nastavit globálnì +

                +
                +
                -

                - - - - + + +

                diff --git a/templates/cs/private.html b/templates/cs/private.html index 0f853bba..25073d4a 100755 --- a/templates/cs/private.html +++ b/templates/cs/private.html @@ -1,58 +1,119 @@ - Konference %(realname)s - pøihlášení pro pøístup do privátního archivu +Konference %(realname)s - pøihlášení pro pøístup do privátního archivu - - -
                + + + %(message)s - - - - - - - - - - - - - - - -
                - Konference %(realname)s - - pøihlášení pro pøístup do privátního archivu -
                Emailová adresa:
                Heslo:
                -
                -

                Pozor: Aby systém fungoval - správnì musíte mít povolené cookie ve Vašem prohlí¾eèi. + + + + + + + + + + + + + + + +
                +Konference %(realname)s - + pøihlášení pro pøístup do privátního archivu +
                Emailová adresa:
                Heslo:
                +
                +

                Pozor: Aby systém fungoval + správnì musíte mít povolené cookie ve Vašem prohlí¾eèi. -

                Cookies se pou¾ívají na to, abyste nemuseli jeden - ka¾dý po¾adavek znovu potvrzovat heslem. Zaniknou - pokud ukonèíte prohlí¾eè a nebo kdy¾ se odhlásíte. - Odhlásit se mù¾ete kliknutím na Odhlásit. - Tento odkaz uvidíte a¾ po úspìšném pøihlášení. +

                Cookies se pou¾ívají na to, abyste nemuseli jeden + ka¾dý po¾adavek znovu potvrzovat heslem. Zaniknou + pokud ukonèíte prohlí¾eè a nebo kdy¾ se odhlásíte. + Odhlásit se mù¾ete kliknutím na Odhlásit. + Tento odkaz uvidíte a¾ po úspìšném pøihlášení.

                - - - - - - + + + +
                - Password Reminder -
                If you don't remember your password, enter your email address + + + + + + - - - - -
                +Password Reminder +
                If you don't remember your password, enter your email address above and click the Remind button and your password will be emailed to you.
                - +
                +

                diff --git a/templates/cs/roster.html b/templates/cs/roster.html index 1582c238..e5d79261 100644 --- a/templates/cs/roster.html +++ b/templates/cs/roster.html @@ -1,54 +1,113 @@ - - - Úèastníci konference <MM-List-Name> - - - - -

                - - - - - - - - - - - - - - - -
                - Úèastníci konference - -
                - -

                -

                - -

                - Kliknìte na svou adresu. Vstoupíte tím na stránku na které si -mù¾ete libovolnì konfigurovat své úèastnické konto.
                -(Úèastníci v závorkách mají zakázáno doruèování pošty. )

                -
                -
                - - bì¾ných úèastníkù konference : -
                -
                -
                - Úèastníkù konference - vyu¾ívá digest: -
                -
                -

                -

                -

                -

                - - - + + +Úèastníci konference <mm-list-name></mm-list-name> + + +

                + + + + + + + + + + + + + + + +
                + Úèastníci konference + +
                +

                +

                +

                + Kliknìte na svou adresu. Vstoupíte tím na stránku na které si +mù¾ete libovolnì konfigurovat své úèastnické konto.
                +(Úèastníci v závorkách mají zakázáno doruèování pošty. )

                +
                +
                + + bì¾ných úèastníkù konference : +
                +
                +
                + Úèastníkù konference + vyu¾ívá digest: +
                +
                +

                +

                +

                +

                + +

                + diff --git a/templates/cs/subscribe.html b/templates/cs/subscribe.html index 9da24df9..7bbb7c01 100644 --- a/templates/cs/subscribe.html +++ b/templates/cs/subscribe.html @@ -1,9 +1,72 @@ -<MM-List-Name> Výsledky pøihlášení +<mm-list-name> Výsledky pøihlášení</mm-list-name> -

                Výsledky pøihlášení

                - - - +

                Výsledky pøihlášení

                + + + diff --git a/templates/da/admindbdetails.html b/templates/da/admindbdetails.html index 1f710913..cbdfbb87 100644 --- a/templates/da/admindbdetails.html +++ b/templates/da/admindbdetails.html @@ -1,62 +1,123 @@ -Administrative anmodninger kan vises på to måder, enten som en
                oversigt, eller med alle detaljer. -På oversigtssiden vises både ventende tilmeldinger/frameldinger og +Administrative anmodninger kan vises pÃ¥ to mÃ¥der, enten som en oversigt, eller med alle detaljer. +PÃ¥ oversigtssiden vises bÃ¥de ventende tilmeldinger/frameldinger og e-mail som afventer godkendelse for at blive distribueret via listen. -Detailsiden viser også email-headers og et uddrag af +Detailsiden viser ogsÃ¥ email-headers og et uddrag af indholdet i dem. -

                På begge sider kan du angive følgende tiltag: +

                På begge sider kan du angive følgende tiltag:

                  -
                • Afvent -- Udsæt afgørelsen. Intet bliver gjort med - denne anmodning nu, men e-mail der venter på godkendelse kan du +
                • Afvent -- Udsæt afgørelsen. Intet bliver gjort med + denne anmodning nu, men e-mail der venter pÃ¥ godkendelse kan du behandle eller videresende (se nedenfor).
                • Godkend -- Accepter meddelelsen som den er og distribuer den - til listen. For anmodninger om medlemskab, iværksættelse af framelding eller + til listen. For anmodninger om medlemskab, iværksættelse af framelding eller tilmelding.
                • Afvis -- Slet meddelelsen og giv besked tilbage til afsenderen om at meddelelsen ikke blev godkendt. For anmodninger om - medlemskab, afslag på framelding eller tilmelding. I alle tilfælde bør - du også angive en grund i den tilhørende tekstboks. + medlemskab, afslag pÃ¥ framelding eller tilmelding. I alle tilfælde bør + du ogsÃ¥ angive en grund i den tilhørende tekstboks.
                • Slet -- Slet meddelelsen uden at at sende besked - til afsender. Den almindelige reaktion på reklame-email (spam). - For anmodninger om medlemskab, afslag på framelding eller tilmelding, uden - at den som prøvede at fra- eller tilmelde sig listen får noget at vide + til afsender. Den almindelige reaktion pÃ¥ reklame-email (spam). + For anmodninger om medlemskab, afslag pÃ¥ framelding eller tilmelding, uden + at den som prøvede at fra- eller tilmelde sig listen fÃ¥r noget at vide om det. -
                - -

                Marker Gem meddelelse hvis du ønsker at gemme en kopi til -systemets administrator. Dette kan være nyttigt for meddelelser som er + +

                Marker Gem meddelelse hvis du ønsker at gemme en kopi til +systemets administrator. Dette kan være nyttigt for meddelelser som er knyttet til misbrug af systemet og som du egentlig vil slette, men som du -ønsker se se nærmere på senere. +ønsker se se nærmere pÃ¥ senere.

                Marker Videresend meddelelser og angiv en e-mailadrese hvis du vil videresende meddelelsen til andre. For at redigere en e-mail -der holdes tilbage før den sendes til listen, bør du sende den til dig selv +der holdes tilbage før den sendes til listen, bør du sende den til dig selv (eller evt. listens ejere), og slette den oprindelige e-mail. -Når e-mailen ankommer til din indboks, kan du redigere den og udføre de ændringer -du ønsker, og derefter sende den til listen igen, med et Approved: felt +NÃ¥r e-mailen ankommer til din indboks, kan du redigere den og udføre de ændringer +du ønsker, og derefter sende den til listen igen, med et Approved: felt i brevhovedet efterfulgt af listens adgangskode. Husk at det er normal netikette -i sådanne tilfælde at angive at du har redigeret indholdet. +i sÃ¥danne tilfælde at angive at du har redigeret indholdet.

                Hvis afsenderen er medlem af listen men er modereret, kan du fjerne -moderationsflaget hvis du ønsker det. Dette er nyttigt når du har opsat -listen sådan at nye medlemmer har en prøvetid på listen, og du har bestemt -at nu er prøvetiden udløbet for medlemmet. +moderationsflaget hvis du ønsker det. Dette er nyttigt nÃ¥r du har opsat +listen sÃ¥dan at nye medlemmer har en prøvetid pÃ¥ listen, og du har bestemt +at nu er prøvetiden udløbet for medlemmet. -

                Hvis afsenderen ikke er medlem af listen, kan du få e-mailadressen lagt -i et af afsenderfiltrene. Afsenderfiltre er forklaret på Filtrering på afsender-siden, og kan være +

                Hvis afsenderen ikke er medlem af listen, kan du fÃ¥ e-mailadressen lagt +i et af afsenderfiltrene. Afsenderfiltre er forklaret pÃ¥ Filtrering pÃ¥ afsender-siden, og kan være auto-godkendelse, auto-tilbageholdelse, auto-afvisning, eller -auto-sletning. Denne valgmulighed vil ikke være tilgængelig hvis adressen +auto-sletning. Denne valgmulighed vil ikke være tilgængelig hvis adressen allerede findes i et filter. -

                Klik på Udfør knappen øverst eller nederst på siden, -når du har foretaget dine valg. -Det vil aktivere dine afgørelser. +

                Klik på Udfør knappen øverst eller nederst på siden, +når du har foretaget dine valg. +Det vil aktivere dine afgørelser.

                Tilbage til oversigtssiden. +

                \ No newline at end of file diff --git a/templates/da/admindbpreamble.html b/templates/da/admindbpreamble.html index 77ee7660..5d550e95 100644 --- a/templates/da/admindbpreamble.html +++ b/templates/da/admindbpreamble.html @@ -1,8 +1,72 @@ -Denne side indeholder nogle af de e-mails til listen %(listname)s +Denne side indeholder nogle af de e-mails til listen %(listname)s der holdes tilbage for godkendelse. Nu viser den %(description)s -

                Vælg ønsket afgørelse for hver anmodning, og tryk derefter Udfør -når du er færdig. Nærmere instruktioner finder du her. +

                Vælg ønsket afgørelse for hver anmodning, og tryk derefter Udfør +når du er færdig. Nærmere instruktioner finder du her. -

                Du kan også se en oversigt over alle anmodninger der venter på en afgørelse. +

                Du kan også se en oversigt over alle anmodninger der venter på en afgørelse. +

                \ No newline at end of file diff --git a/templates/da/admindbsummary.html b/templates/da/admindbsummary.html index 3468aa78..3714e618 100644 --- a/templates/da/admindbsummary.html +++ b/templates/da/admindbsummary.html @@ -1,10 +1,74 @@ -Her finder du en oversigt over anmodninger der skal vurderes for maillisten. -%(listname)s.
                +Her finder du en oversigt over anmodninger der skal vurderes for maillisten. +%(listname)s.
                Eventuelle anmodninger om medlemsskab af listen vil blive -vist først, derefter kommer meddelelser som er sendt til listen, -men som venter på godkendelse for at blive rundsendt. +vist først, derefter kommer meddelelser som er sendt til listen, +men som venter pÃ¥ godkendelse for at blive rundsendt. -

                Vælg ønsket tiltag for hver anmodning, og klik på Udfør knappen når du er færdig. -Nærmere instruktioner er også tilgængelige. +

                Vælg ønsket tiltag for hver anmodning, og klik på Udfør knappen når du er færdig. +Nærmere instruktioner er også tilgængelige. -

                Du kan også se alle detaljer for alle tilbageholdte meddelelser hvis du ønsker det. +

                Du kan også se alle detaljer for alle tilbageholdte meddelelser hvis du ønsker det. +

                \ No newline at end of file diff --git a/templates/da/admlogin.html b/templates/da/admlogin.html index 65020bea..3e64f00a 100755 --- a/templates/da/admlogin.html +++ b/templates/da/admlogin.html @@ -1,39 +1,100 @@ - %(listname)s %(who)s Login - - - -
                +%(listname)s %(who)s Login + + + + %(message)s - - - - - - - - - - - -
                - %(listname)s %(who)s - Login -
                Listens %(who)s adgangskode:
                -
                -

                Vigtigt: Din Webbrowser skal acceptere - modtagelse af cookies - ellers vil ingen af de administrative ændringer + + + + + + + + + + + +
                +%(listname)s %(who)s + Login +
                Listens %(who)s adgangskode:
                +
                +

                Vigtigt: Din Webbrowser skal acceptere + modtagelse af cookies - ellers vil ingen af de administrative ændringer blive gemt. -

                Session cookies bruges på Mailmans administrative sider, - så du ikke behøver at indtaste adgangskode for hver ændring du foretager. - De vil forsvinde automatisk når du lukker din browser, eller du - kan fjerne dem manuelt ved at klikke på +

                Session cookies bruges pÃ¥ Mailmans administrative sider, + sÃ¥ du ikke behøver at indtaste adgangskode for hver ændring du foretager. + De vil forsvinde automatisk nÃ¥r du lukker din browser, eller du + kan fjerne dem manuelt ved at klikke pÃ¥ Log ud linket under Andre administrative aktiviteter - (som du får op efter at have logget ind). -

                + (som du får op efter at have logget ind). +

                diff --git a/templates/da/archidxfoot.html b/templates/da/archidxfoot.html index 76b6cacb..741192cb 100644 --- a/templates/da/archidxfoot.html +++ b/templates/da/archidxfoot.html @@ -1,21 +1,85 @@ - -

                - Dato for nyeste meddelelse: - %(lastdate)s
                - Arkiveret: %(archivedate)s -

                -

                  -
                • Meddelelser sorteret efter: + +

                  +Dato for nyeste meddelelse: +%(lastdate)s
                  +Arkiveret: %(archivedate)s +

                  +

                  -

                  -


                  - Dette arkiv blev genereret af +
                +

                +


                +Dette arkiv blev genereret af Pipermail %(version)s. - - + + +

                \ No newline at end of file diff --git a/templates/da/archidxhead.html b/templates/da/archidxhead.html index 162bcd9b..44622fd9 100644 --- a/templates/da/archidxhead.html +++ b/templates/da/archidxhead.html @@ -1,15 +1,79 @@ - - - %(listname)s arkivet for %(archive)s sorteret efter %(archtype)s - + + + +%(listname)s arkivet for %(archive)s sorteret efter %(archtype)s + %(encoding)s - - - -

                Arkivet for %(archive)s sorteret efter %(archtype)s

                -
                  -
                • Meddelelser sorteret efter: + + + +

                  Arkivet for %(archive)s sorteret efter %(archtype)s

                  +
                    +
                  • Meddelelser sorteret efter: %(thread_ref)s %(subject_ref)s %(author_ref)s @@ -17,8 +81,9 @@

                    Arkivet for %(archive)s sorteret efter %(archtype)s

                  • Mere information om denne liste...
                  • -
                  -

                  Startdato: %(firstdate)s
                  - Slutdato: %(lastdate)s
                  - Meddelelser: %(size)s

                  -

                    +
                  +

                  Startdato: %(firstdate)s
                  +Slutdato: %(lastdate)s
                  +Meddelelser: %(size)s

                  +

                    +

                  \ No newline at end of file diff --git a/templates/da/archliststart.html b/templates/da/archliststart.html index 1349b6ea..115e531a 100644 --- a/templates/da/archliststart.html +++ b/templates/da/archliststart.html @@ -1,4 +1,68 @@ - - - - +
                  ArkivSorter efter:Download version
                  + + + +
                  ArkivSorter efter:Download version
                  \ No newline at end of file diff --git a/templates/da/archtoc.html b/templates/da/archtoc.html index a5dcda60..5504869a 100644 --- a/templates/da/archtoc.html +++ b/templates/da/archtoc.html @@ -1,13 +1,77 @@ - - - %(listname)s arkiv - + + + +%(listname)s arkiv + %(meta)s - - -

                  %(listname)s arkiv

                  -

                  + + +

                  %(listname)s arkiv

                  +

                  Du kan se mere information om denne liste eller du kan downloade hele arkivet (%(size)s). @@ -16,5 +80,5 @@

                  %(listname)s arkiv

                  %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/da/archtocentry.html b/templates/da/archtocentry.html index 30d3c058..3b1f5c7b 100644 --- a/templates/da/archtocentry.html +++ b/templates/da/archtocentry.html @@ -1,12 +1,74 @@ - -
                %(archivelabel)s: - [ Tråd ] - [ Emne ] - [ Afsender ] - [ Dato ] -
                %(archivelabel)s: +[ Tråd ] +[ Emne ] +[ Afsender ] +[ Dato ] +
                - - - - - - - - - - - - - - - - - - + + + + + +

                - -- - -
                -

                  -

                - Om - - - -
                -

                -

                Alle e-mails som er sendt til denne liste kan findes i - - Arkivet. - -

                -
                - Sådan bruges mailinglisten -
                - E-mail til alle på listen sendes til - . + + + +<mm-list-name> Infoside</mm-list-name> + + +

                + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + +
                + -- + +
                +

                  +

                +Om + + + +
                +

                +

                Alle e-mails som er sendt til denne liste kan findes i + + Arkivet. + +

                +
                +SÃ¥dan bruges mailinglisten +
                + E-mail til alle på listen sendes til + . -

                Du kan tilmelde dig listen eller ændre indstillingerne for +

                Du kan tilmelde dig listen eller ændre indstillingerne for dit medlemsskab nedenfor: -

                - Tilmelding til -
                -

                - Du kan tilmelde dig ved at udfylde skemaet herunder. - -

                  - - - - - - - - - - - - + + + + + + - - - - - -
                  Din email-adresse: -  
                  Navn (valgfrit): 
                  Du kan skrive en Adgangskode i følgende felter. - Dette er kun en simpel beskyttelse, men forhindrer at andre får adgang til dine indstillinger. - Brug ikke en værdifuld Adgangskode, for din adgangskode kan blive sendt til dig i klartekst via E-mail. +

                  +Tilmelding til +
                  +

                  + Du kan tilmelde dig ved at udfylde skemaet herunder. + +

                    + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - -
                    Din email-adresse: + 
                    Navn (valgfrit): 
                    Du kan skrive en Adgangskode i følgende felter. + Dette er kun en simpel beskyttelse, men forhindrer at andre får adgang til dine indstillinger. + Brug ikke en værdifuld Adgangskode, for din adgangskode kan blive sendt til dig i klartekst via E-mail. -

                    Hvis du ikke skriver en Adgangskode, vil du automatisk få tildelt en Adgangskode. - Den vil blive sendt til dig, så snart du har bekræftet din tilmelding. - Du kan senere bede om at få din adgangskode tilsendt, eller du kan ændre den. - -
                    -
                    Ønsket Adgangskode: 
                    Ønsket Adgangskode en gang til: 
                    Vælg sprog for meddelelser:  
                    Vil du benytte sammendrag-modus? Nej - Ja -
                    -
                    -
                    - -
                  -
                  - - Medlemmer af -
                  - - - -

                  - - - -

                  - - - +

                  Hvis du ikke skriver en Adgangskode, vil du automatisk få tildelt en Adgangskode. + Den vil blive sendt til dig, så snart du har bekræftet din tilmelding. + Du kan senere bede om at få din adgangskode tilsendt, eller du kan ændre den. + + +
                Ønsket Adgangskode: 
                Ønsket Adgangskode en gang til: 
                Vælg sprog for meddelelser:  
                Vil du benytte sammendrag-modus? Nej + Ja +
                +
                +
                + + +

                + +Medlemmer af +
                + + + +

                + + + +

                + +

                + diff --git a/templates/da/options.html b/templates/da/options.html index 45abef8a..1b42b19c 100644 --- a/templates/da/options.html +++ b/templates/da/options.html @@ -1,299 +1,333 @@ - - Personlig medlemside for <MM-Presentable-User> på listen <MM-List-Name> - - - - - -
                - - Personlig medlemside for på listen -
                + +Personlig medlemside for <mm-presentable-user> på listen <mm-list-name> +</mm-list-name></mm-presentable-user> + + + + +
                + + Personlig medlemside for på listen +

                - - - - - +
                - Medlemsstatus, Adgangskode, og indstillinger for - på mailinglisten . -
                - - - - -

                -

                + + + +
                +Medlemsstatus, Adgangskode, og indstillinger for + på mailinglisten . +
                + + +

                +

                - - +

                - - - + +
                - - Skifte email-adresse på -
                Du kan udskifte den email-adresse du har tilmeldt dig + + + - - - - -
                + +Skifte email-adresse på +
                Du kan udskifte den email-adresse du har tilmeldt dig listen med ved at skrive en anden email-adresse i felterne nedenfor. - Bemærk at en E-mail med nærmere instruktioner så vil blive sendt til den nye - adresse. Ændringerne træder ikke i kraft før du har bekræftet dem. - Du har til at bekræfte ændringen. - -

                Du kan også angive eller ændre dit navn på listen (f.eks Holger Danske). - -

                Vælg Ændre alle lister hvis du vil ændre din email-adresse for alle de - mailinglister du er tilmeldt på . - -

                - - - - - + -
                Ny email-adresse:
                Email-adresse en gang til: + Bemærk at en E-mail med nærmere instruktioner så vil blive sendt til den nye + adresse. Ændringerne træder ikke i kraft før du har bekræftet dem. + Du har til at bekræfte ændringen. + +

                Du kan også angive eller ændre dit navn på listen (f.eks Holger Danske). + +

                Vælg Ændre alle lister hvis du vil ændre din email-adresse for alle de + mailinglister du er tilmeldt på . + +

                + + + + + - - -
                Ny email-adresse:
                Email-adresse en gang til:
                -
                - - + +
                Dit navn +
                +
                + + - - -
                Dit navn (valgfrit):
                -
                -

                Ændre min email-adresse på alle lister jeg er medlem af

                - +
                +

                +

                Ændre min email-adresse på alle lister jeg er medlem af

                - - - - - - - + + + + %(textlink)s - diff --git a/templates/de/archtocnombox.html b/templates/de/archtocnombox.html index c6e9e958..c0b74a36 100755 --- a/templates/de/archtocnombox.html +++ b/templates/de/archtocnombox.html @@ -1,18 +1,82 @@ - - - Das %(listname)s-Archiv - + + + +Das %(listname)s-Archiv + %(meta)s - - -

                Das %(listname)s-Archiv

                -

                - Über diese Liste gibt es weitere Informationen. + + +

                Das %(listname)s-Archiv

                +

                + Über diese Liste gibt es weitere Informationen.

                %(noarchive_msg)s %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/de/article.html b/templates/de/article.html index 63d0a845..f2a30ed0 100644 --- a/templates/de/article.html +++ b/templates/de/article.html @@ -1,12 +1,13 @@ - - - %(title)s - - - - - + +

                Das %(listname)s-Archiv

                +

                + Bisher sind noch keine Nachrichten an diese Liste geschickt worde. Daher ist das Archiv noch leer. Über diese Liste gibt es weitere Informationen.

                - - + + diff --git a/templates/de/headfoot.html b/templates/de/headfoot.html index 5377040c..06acd691 100644 --- a/templates/de/headfoot.html +++ b/templates/de/headfoot.html @@ -1,30 +1,93 @@ - + Dieser Text kann sogenannte " Python format strings" enthalten, die durch listenspezifische Werte ersetzt werden. -Mögliche derartige Angaben sind: +Mögliche derartige Angaben sind:
                  -
                • real_name - Der "hübsche" Name der Liste; -üblicherweise der Listenname in Groß-/Kleinschreibung. +
                • real_name - Der "hübsche" Name der Liste; +üblicherweise der Listenname in Groß-/Kleinschreibung. -
                • list_name - Der Name der Liste für diverse URLs, -wo die Groß-/Kleinschreibung wichtig ist. +
                • list_name - Der Name der Liste für diverse URLs, +wo die Groß-/Kleinschreibung wichtig ist.
                • host_name - Der FQDN (fully qualified domain name) des Listenservers. -
                • web_page_url - Die Basis-URL für Mailman. Daran -wird bspw. listinfo/%(list_name)s angehängt, um die +
                • web_page_url - Die Basis-URL für Mailman. Daran +wird bspw. listinfo/%(list_name)s angehängt, um die URL der Infoseite der Liste zu erhalten.
                • description - Eine kurze Beschreibung der Mailingsliste. -
                • info - Die ausführliche Beschreibung der +
                • info - Die ausführliche Beschreibung der Mailingliste. -
                • cgiext - Die Dateinamenerweiterung für CGI-Skripts. -
                +
              • cgiext - Die Dateinamenerweiterung für CGI-Skripts. +
              • diff --git a/templates/de/listinfo.html b/templates/de/listinfo.html index 78531d21..33de9d29 100755 --- a/templates/de/listinfo.html +++ b/templates/de/listinfo.html @@ -1,156 +1,217 @@ - - - - <MM-List-Name> Infoseite - - - -

                -

                - Afmelde dig - Andre lister du er medlem af på -
                - Kryds af i afkrydsningsboksen og Klik på knappen for at afmelde dig + + + + - + +
                +

                +Afmelde dig +Andre lister du er medlem af på +
                + Kryds af i afkrydsningsboksen og Klik pÃ¥ knappen for at afmelde dig denne mailingliste. - BEMÆRK: Du vil straks blive afmeldt listen! + BEMÆRK: Du vil straks blive afmeldt listen!

                -

                - Du kan vælge at se alle lister du er tilmeldt, så du kan foretage de samme - ændringer på flere lister på en gang. +

                + Du kan vælge at se alle lister du er tilmeldt, så du kan foretage de samme + ændringer på flere lister på en gang.

                -

                -
                - - - - - - -
                - Din Adgangskode til -
                - -
                -

                Har du glemt din adgangskode?

                -
                - Tryk på denne knap for at få din adgangskode tilsendt via E-mail. -

                -

                - -
                -
                - -
                -

                Skift Adgangskode

                - - - - - - - - -
                Ny - Adgangskode:
                Ny Adgangskode en gang - til:
                - - -

                Skift adgangskode for alle de - lister jeg er medlem af på . -
                -
                - + + + +
                +Din Adgangskode til +
                + +
                +

                Har du glemt din adgangskode?

                +
                + Tryk på denne knap for at få din adgangskode tilsendt via E-mail. +

                +

                + +
                +

                + +
                +

                Skift Adgangskode

                + + + + + + + + +
                Ny + Adgangskode:
                Ny Adgangskode en gang + til:
                + +

                Skift adgangskode for alle de + lister jeg er medlem af på . +
                +

                - - +
                - Dine indstillinger for -
                +
                +Dine indstillinger for +
                -

                -Følgende indstillinger er gældende. - -

                Bemærk at der ved nogle af indstillingerne er mulighed for at vælge Brug på alle. -Vælger du Brug på alle, vil alle lister du er medlem af på også -få den samme indstilling som du har valgt her. Klik på Vis andre +Følgende indstillinger er gældende. +

                Bemærk at der ved nogle af indstillingerne er mulighed for at vælge Brug på alle. +Vælger du Brug på alle, vil alle lister du er medlem af på også +få den samme indstilling som du har valgt her. Klik på Vis andre lister jeg er medlem af ovenfor for at se hvilke andre mailinglister du er medlem af.

                - -
                - - Modtag meddelelser

                - Sæt denne til Ja for at modtage alle E-mails som sendes til denne liste. - Sæt den til Nej hvis du i en periode ikke ønsker at modtage E-mail som sendes til listen, - men alligevel ønsker at være medlem af listen. (kan være nyttig f.eks. hvis du skal på ferie) - Sætter du den til Nej, skal du huske at sætte den tilbage igen, + + - +

                - - - +

                + - - - - - - - - - - - - + + + + + - - + +

                + - - +

                +
                + +Modtag meddelelser

                + Sæt denne til Ja for at modtage alle E-mails som sendes til denne liste. + Sæt den til Nej hvis du i en periode ikke ønsker at modtage E-mail som sendes til listen, + men alligevel ønsker at være medlem af listen. (kan være nyttig f.eks. hvis du skal på ferie) + Sætter du den til Nej, skal du huske at sætte den tilbage igen, for den vil ikke blive sat tilbage til Ja automatisk. -

                - Ja
                - Nej

                - Brug på alle -

                +Ja
                +Nej

                +Brug på alle +

                - Sammendrag-modus

                - Vælger du sammendrag-modus, vil du med jævne mellemrum (normalt en gang om dagen, - muligvis flere gange, det kommer an på hvor meget der sendes til listen) få en - samle-E-mail som indeholder alle meddelelser som bliver sendt til listen, i stedet for at få alle +

                +Sammendrag-modus

                + Vælger du sammendrag-modus, vil du med jævne mellemrum (normalt en gang om dagen, + muligvis flere gange, det kommer an pÃ¥ hvor meget der sendes til listen) fÃ¥ en + samle-E-mail som indeholder alle meddelelser som bliver sendt til listen, i stedet for at fÃ¥ alle meddelelser hver for sig. Hvis du skifter fra sammendrag-modus til normal- - modus, vil du muligvis alligevel få en sidste E-mail med sammendrag + modus, vil du muligvis alligevel fÃ¥ en sidste E-mail med sammendrag selv om du allerede er begyndt at modtage alle meddelelser enkeltvis. -

                - Nej
                - Ja -
                - Modtag sammendrag som ren tekst eller i MIME-format?

                - Måske understøtter dit emailprogram ikke MIME-formatet. - At modtage sammendrag i MIME-format anbefales, men får du problemer med - at læse E-mail i det format, kan du vælge ren tekst her. -

                - MIME
                - Ren tekst

                - Brug på alle -

                +Nej
                +Ja +
                +Modtag sammendrag som ren tekst eller i MIME-format?

                + Måske understøtter dit emailprogram ikke MIME-formatet. + At modtage sammendrag i MIME-format anbefales, men får du problemer med + at læse E-mail i det format, kan du vælge ren tekst her. +

                +MIME
                +Ren tekst

                +Brug på alle +

                - Modtag dine egne meddelelser til listen?

                +

                +Modtag dine egne meddelelser til listen?

                Normalt vil du modtage E-mails som du selv har sendt til listen. - Hvis du ikke ønsker at modtage disse, skal du vælge Nej her. -

                - Nej
                - Ja -
                - Modtag bekræftelse når du sender E-mail til listen?

                - - Vælg Ja her hvis du ønsker at få en bekræftelse hver gang du sender en E-mail til listen. -

                - Nej
                - Ja -
                - Få din adgangskode tilsendt jævnligt?

                - En gang om måneden kan det være du får en E-mail som indeholder - adgangskoder for alle de lister du er medlem af på dette system. - Du kan slå det fra her. -

                - Nej
                - Ja

                - Brug på alle -

                - Vil du være synlig på deltagerlisten?

                - Når nogen går ind på websiden for at se alle medlemmer af listen, vil - din email-adresse normalt vises. (Men stavet på en måde så - robotter som høster email-adresser fra websider og måske - derefter sender spam til dem, ikke vil forstå at det - er en email-adresse.) Vælg 'Nej' hvis du ikke ønsker din email-adresse vist - på listen. -

                - Nej
                - Ja -
                - Hvilket sprog foretrækker du?

                -

                - -
                - Hvilke emner vil du modtage? -

                Ved at vælge et eller flere emner kan du filtrere de meddelelser som sendes til - mailinglisten, sådan at du kun modtager det som har din interesse. + Hvis du ikke ønsker at modtage disse, skal du vælge Nej her. +

                +Nej
                +Ja +
                +Modtag bekræftelse når du sender E-mail til listen?

                + + Vælg Ja her hvis du ønsker at få en bekræftelse hver gang du sender en E-mail til listen. +

                +Nej
                +Ja +
                +Få din adgangskode tilsendt jævnligt?

                + En gang om måneden kan det være du får en E-mail som indeholder + adgangskoder for alle de lister du er medlem af på dette system. + Du kan slå det fra her. +

                +Nej
                +Ja

                +Brug på alle +

                +Vil du være synlig på deltagerlisten?

                + Når nogen går ind på websiden for at se alle medlemmer af listen, vil + din email-adresse normalt vises. (Men stavet på en måde så + robotter som høster email-adresser fra websider og måske + derefter sender spam til dem, ikke vil forstå at det + er en email-adresse.) Vælg 'Nej' hvis du ikke ønsker din email-adresse vist + på listen. +

                +Nej
                +Ja +
                +Hvilket sprog foretrækker du?

                +

                + +
                +Hvilke emner vil du modtage? +

                Ved at vælge et eller flere emner kan du filtrere de meddelelser som sendes til + mailinglisten, sådan at du kun modtager det som har din interesse.

                Hvis en meddelelse ikke kan placeres under noget emne, er det denne indstilling - der bestemmer om du modtager meddelelsen eller ej. Hvis du ikke vælger nogen emner her, vil + der bestemmer om du modtager meddelelsen eller ej. Hvis du ikke vælger nogen emner her, vil du modtage alle meddelelser som sendes til listen. -

                - -
                - Vil du modtage meddelelser som ikke kan placeres under noget emne? -

                Dette valg gælder kun, hvis du har valgt et eller flere emner ovenfor. +

                + +
                +Vil du modtage meddelelser som ikke kan placeres under noget emne? +

                Dette valg gælder kun, hvis du har valgt et eller flere emner ovenfor. Det bestemmer om du skal modtage meddelelser som ikke kan placeres under noget emne. - Vælger du Nej vil du ikke modtage dem, vælger du Ja vil + Vælger du Nej vil du ikke modtage dem, vælger du Ja vil du modtage dem.

                Hvis du ikke har valgt nogen emner i indstillingen ovenfor, vil du modtage alle meddelelser som sendes til listen. -

                - Nej
                - Ja -
                +Nej
                +Ja +
                +Undgå at få E-mail fra listen som også er adresseret direkte til dig?

                -

                - Undgå at få E-mail fra listen som også er adresseret direkte til dig?

                - - Du kan vælge hvad der skal ske, hvis der kommer en E-mail til listen som desuden er - adresseret direkte til dig (dvs. din email-adresse står i To: eller Cc: - feltet). Vælg Ja for at undgå at modtage mailen fra listen. Vælg Nej + Du kan vælge hvad der skal ske, hvis der kommer en E-mail til listen som desuden er + adresseret direkte til dig (dvs. din email-adresse stÃ¥r i To: eller Cc: + feltet). Vælg Ja for at undgÃ¥ at modtage mailen fra listen. Vælg Nej for alligevel at modtage mailen fra listen. -

                For særligt interesserede: Hvis listen er sat op til at personificere - meddelelser sendt til listen, og du vælger at modtage E-mail fra listen, vil alle mails fra +

                For særligt interesserede: Hvis listen er sat op til at personificere + meddelelser sendt til listen, og du vælger at modtage E-mail fra listen, vil alle mails fra listen have et X-Mailman-Copy: yes felt i headeren. -

                - Nej
                - Ja

                - Sæt globalt -

                -
                -
                +Nej
                +Ja

                +Sæt globalt +

                +
                +
                -

                - - - - + + +

                diff --git a/templates/da/private.html b/templates/da/private.html index 0077f447..d4cfe68e 100755 --- a/templates/da/private.html +++ b/templates/da/private.html @@ -1,59 +1,119 @@ - %(realname)s Login til Private Arkiver +%(realname)s Login til Private Arkiver - - -
                + + + %(message)s - - - - - - - - - - - - - - - -
                - %(realname)s Login til Private Arkiver -
                E-mail adresse:
                Password:
                -
                - -

                Vigtigt: Fra nu af skal du ha cookies - slået til i din webbrowser, ellers vil ingen administrative ændringer + + + + + + + + + + + + + + + +
                +%(realname)s Login til Private Arkiver +
                E-mail adresse:
                Password:
                +
                +

                Vigtigt: Fra nu af skal du ha cookies + slået til i din webbrowser, ellers vil ingen administrative ændringer blive gemt. -

                Session cookies bruges på Mailmans administrative sider, - sådan at du ikke behøver at oplyse dit Password hver gang du laver en ændring. - De vil forsvinde automatisk når du lukker din webbrowser. - Du kan også fjerne cookies manuelt ved at klikke på +

                Session cookies bruges pÃ¥ Mailmans administrative sider, + sÃ¥dan at du ikke behøver at oplyse dit Password hver gang du laver en ændring. + De vil forsvinde automatisk nÃ¥r du lukker din webbrowser. + Du kan ogsÃ¥ fjerne cookies manuelt ved at klikke pÃ¥ Logout linket under Other Administrative - Activities (som du får op efter at have logget ind). + Activities (som du fÃ¥r op efter at have logget ind).

                - - - - - - - - - - -
                - Password Reminder -
                Hvis du ikke kan huske din adgangskode, så indtast din emailadresse - ovenfor og tryk på Send adgangskode knappen, så vil du - få tilsendt en email med din adgangskode.
                -

                + + + + + + + + + + +
                +Password Reminder +
                Hvis du ikke kan huske din adgangskode, så indtast din emailadresse + ovenfor og tryk på Send adgangskode knappen, så vil du + få tilsendt en email med din adgangskode.
                +

                diff --git a/templates/da/roster.html b/templates/da/roster.html index 0c433397..ac31b66b 100644 --- a/templates/da/roster.html +++ b/templates/da/roster.html @@ -1,52 +1,111 @@ - - - <MM-List-Name> Medlemmer - - - - -

                - - - - - - - - - - - - - - - -
                - - Medlemmer -
                - -

                -

                - -

                Klik på din adresse for at gå til din personlige side. -
                (Adresser i parentes modtager ikke længere meddelelser fra listen.)

                -
                -
                - - Medlemmer i normal-modus på : -
                -
                -
                - Medlemmer i sammendrag-modus på - : -
                -
                -

                -

                -

                -

                - - - + + +<mm-list-name> Medlemmer</mm-list-name> + + +

                + + + + + + + + + + + + + + + +
                + + Medlemmer +
                +

                +

                +

                Klik på din adresse for at gå til din personlige side. +
                (Adresser i parentes modtager ikke længere meddelelser fra listen.)

                +
                +
                + + Medlemmer i normal-modus på : +
                +
                +
                + Medlemmer i sammendrag-modus på + : +
                +
                +

                +

                +

                +

                + +

                + diff --git a/templates/da/subscribe.html b/templates/da/subscribe.html index 8bde6509..ac41be7e 100644 --- a/templates/da/subscribe.html +++ b/templates/da/subscribe.html @@ -1,9 +1,72 @@ -<MM-List-Name>: Resultat af tilmelding +<mm-list-name>: Resultat af tilmelding</mm-list-name> -

                : Resultat af tilmelding

                - - - +

                : Resultat af tilmelding

                + + + diff --git a/templates/de/admindbdetails.html b/templates/de/admindbdetails.html index e936f352..686bbf61 100755 --- a/templates/de/admindbdetails.html +++ b/templates/de/admindbdetails.html @@ -1,73 +1,136 @@ - + Administrative Anfragen werden auf zwei verschiedene Arten angezeigt, -und zwar in einer Übersicht, und +und zwar in einer Übersicht, und in einer Detailansicht. Die Zusammenfassung zeigt offene -Abonnements- und Kündigungsanfragen sowie Nachrichten, die auf Ihre +Abonnements- und Kündigungsanfragen sowie Nachrichten, die auf Ihre Genehmigung warten (sortiert nach der E-Mail-Adresse des Absenders). Die Detailansicht zeigt eine genauere Beschreibung jeder wartenden -Nachricht, einschließlich aller Kopfzeilen, sowie einen Auszug des +Nachricht, einschließlich aller Kopfzeilen, sowie einen Auszug des Nachrichtentextes. -

                Die folgenden Funktionen sind auf allen Seiten verfügbar: +

                Die folgenden Funktionen sind auf allen Seiten verfügbar:

                  -
                • Verschieben -- Die Entscheidung auf später verschieben. Offene +
                • Verschieben -- Die Entscheidung auf später verschieben. Offene administrative Anfragen werden nicht bearbeitet, aber wartende - Nachrichten können zusätzlich weitergeleitet oder sichergestellt + Nachrichten können zusätzlich weitergeleitet oder sichergestellt werden (siehe unten).
                • Annehmen -- Die Nachricht annehmen und an die Liste - verteilen. Für Anträge betreffend Mitgliedschaft: Die - Änderung des Mitgliedsstatus wird genehmigt. + verteilen. Für Anträge betreffend Mitgliedschaft: Die + Änderung des Mitgliedsstatus wird genehmigt.
                • Ablehnen -- Die Nachricht ablehnen, eine entsprechende Mitteilung an den Absender senden, und das Original wegwerfen. - Anträge betreffend Mitgliedschaft: Die Änderung des - Mitgliedsstatus wird abgelehnt. In beiden Fällen sollte im - nebenstehenden Texteingabefeld ein Grund für die Ablehnung angegeben + Anträge betreffend Mitgliedschaft: Die Änderung des + Mitgliedsstatus wird abgelehnt. In beiden Fällen sollte im + nebenstehenden Texteingabefeld ein Grund für die Ablehnung angegeben werden.
                • Wegwerfen -- Das Original der Nachricht wegwerfen, ohne eine - entsprechende Mitteilung zu senden. Für Anträge betreffend + entsprechende Mitteilung zu senden. Für Anträge betreffend Mitgliedschaft wird der Antrag einfach weggeworfen, ohne den - Antragsteller darüber zu informieren. Das wird üblicherweise - die Reaktion auf sicher erkannten Spam (Werbemails) sein.
                - -

                Für wartende Nachrichten kann die Option Sicherstellen -gewählt werden, um eine Kopie der Nachricht für den Systemverwalter + Antragsteller darüber zu informieren. Das wird üblicherweise + die Reaktion auf sicher erkannten Spam (Werbemails) sein. +

                Für wartende Nachrichten kann die Option Sicherstellen +gewählt werden, um eine Kopie der Nachricht für den Systemverwalter aufzuheben. Das ist dann sinnvoll, wenn es sich um beleidigende -Nachrichten handelt, die zwar nicht verteilt werden sollen, aber für eine -spätere Untersuchung benötigt werden. +Nachrichten handelt, die zwar nicht verteilt werden sollen, aber für eine +spätere Untersuchung benötigt werden.

                Aktivieren Sie die Option Weiterleiten, und geben Sie eine Zieladresse an, um die Nachricht an jemanden weiterzuleiten, der nicht Mitglied der Liste ist. Um eine Nachricht zu bearbeiten, bevor sie an die Liste verteilt wird, leiten sie diese an sich selbst (oder den Betreiber der Liste) weiter, und verwerfen das Original. Wenn die Nachricht dann in Ihrem Postfach angekommen -ist, führen Sie die gewünschten Änderungen am Text durch, und +ist, führen Sie die gewünschten Änderungen am Text durch, und senden die so modifizierte Nachricht mit einer Approved:-Kopfzeile -mit dem Listenpasswort als Wert zurück an die Liste. Es folgt den -allgemeinen Regeln der Netiquette, wenn Sie in derartigen Fällen in der +mit dem Listenpasswort als Wert zurück an die Liste. Es folgt den +allgemeinen Regeln der Netiquette, wenn Sie in derartigen Fällen in der Nachricht darauf hinweisen, dass der Text von Ihnen bearbeitet wurde. -

                Wenn der Absender ein moderiertes Mitglied der Liste ist, können -Sie optional auch das "moderiert"-Flag löschen. Das ist dann -nützlich, wenn Ihre Liste so konfiguriert ist, dass neue Mitglieder -eine Probezeit durchlaufen müssen, und Sie entschieden haben, dass +

                Wenn der Absender ein moderiertes Mitglied der Liste ist, können +Sie optional auch das "moderiert"-Flag löschen. Das ist dann +nützlich, wenn Ihre Liste so konfiguriert ist, dass neue Mitglieder +eine Probezeit durchlaufen müssen, und Sie entschieden haben, dass diesem Mitglied ein Senden an die Liste ohne weitere Genehmigung gestattet werden kann. -

                Wenn der Absender kein Mitglied der Liste ist, dann können sie dessen -E-Mail-Adresse zu einem der Absender-Adressfilter hinzufügen. Diese -sind im Punkt Privatsphäre näher -erklärt, und heißen Auto-Annehmen (Nachricht annehmen), +

                Wenn der Absender kein Mitglied der Liste ist, dann können sie dessen +E-Mail-Adresse zu einem der Absender-Adressfilter hinzufügen. Diese +sind im Punkt Privatsphäre näher +erklärt, und heißen Auto-Annehmen (Nachricht annehmen), Auto-Halten -(Nachricht zurückhalten), Auto-Abweisen (Nachricht abweisen), oder +(Nachricht zurückhalten), Auto-Abweisen (Nachricht abweisen), oder Auto-Wegwerfen (Nachricht wegwerfen). Diese Option ist nicht -verfügbar, wenn die Adresse des Absenders bereits in einem +verfügbar, wenn die Adresse des Absenders bereits in einem der Filter eingetragen ist. -

                Wenn Sie fertig sind, klicken Sie auf den Knopf "Änderungen speichern" -am Beginn dieser Seite, damit werden alle gewählten Optionen aktiv. +

                Wenn Sie fertig sind, klicken Sie auf den Knopf "Änderungen speichern" +am Beginn dieser Seite, damit werden alle gewählten Optionen aktiv. -

                Zurück zur Übersicht. +

                Zurück zur Übersicht. +

                \ No newline at end of file diff --git a/templates/de/admindbpreamble.html b/templates/de/admindbpreamble.html index 6ea77724..52e5e89c 100644 --- a/templates/de/admindbpreamble.html +++ b/templates/de/admindbpreamble.html @@ -1,10 +1,74 @@ -Auf dieser Seite finden Sie die aktuellen administrativen Anfragen der -Liste %(listname)s, die Ihre Aufmerksamkeit benötigen. Derzeit +Auf dieser Seite finden Sie die aktuellen administrativen Anfragen der +Liste %(listname)s, die Ihre Aufmerksamkeit benötigen. Derzeit finden sich hier %(description)s -

                Wählen Sie bitte für jede Anfrage eine Lösung aus und klicken Sie -dann auf Änderungen speichern, um Ihre Änderungen zu übernehmen. +

                Wählen Sie bitte für jede Anfrage eine Lösung aus und klicken Sie +dann auf Änderungen speichern, um Ihre Änderungen zu übernehmen. Eine detailierte Anleitung finden Sie hier. -

                Sie können auch eine Übersicht aller +

                Sie können auch eine Übersicht aller offenen Anfragen aufrufen. +

                \ No newline at end of file diff --git a/templates/de/admindbsummary.html b/templates/de/admindbsummary.html index 1de2a251..650ec79a 100644 --- a/templates/de/admindbsummary.html +++ b/templates/de/admindbsummary.html @@ -1,15 +1,79 @@ - -Diese Seite zeigt eine Übersicht der gegenwärtigen administrativen -Anfragen für die %(listname)s + +Diese Seite zeigt eine Übersicht der gegenwärtigen administrativen +Anfragen für die %(listname)s Mailingliste, die auf Ihre Genehmigung warten. Als erstes sehen Sie eine -Liste allfälliger Abonnements- und Kündigungsanfragen, gefolgt von +Liste allfälliger Abonnements- und Kündigungsanfragen, gefolgt von Nachrichten, die Ihre Genehmigung erfordern, und daher gestoppt wurden. -

                Bitte wählen Sie für jede Anfrage die zu treffende Maßnahme +

                Bitte wählen Sie für jede Anfrage die zu treffende Maßnahme aus, und klicken Sie auf den Knopf "Alle Daten senden", wenn sie fertig sind. Eine detaillierte Anleitung ist ebenfalls -verfügbar. +verfügbar. -

                Sie können sich auch Details zu allen +

                Sie können sich auch Details zu allen gestoppten Nachrichten anzeigen lassen. +

                \ No newline at end of file diff --git a/templates/de/admlogin.html b/templates/de/admlogin.html index 7ed8e349..7161231b 100755 --- a/templates/de/admlogin.html +++ b/templates/de/admlogin.html @@ -1,40 +1,101 @@ - Anmeldung als %(listname)s-%(who)s +Anmeldung als %(listname)s-%(who)s - - -
                + + + %(message)s - - - - - - - - - - - -
                - Anmeldung als %(listname)s-%(who)s - -
                %(who)s-Passwort:
                -
                -

                Wichtig: Ab diesem Punkt müssen in Ihrem - Browser "Cookies" aktiviert sein, da ansonsten keine Ihrer Änderungen + + + + + + + + + + + +
                +Anmeldung als %(listname)s-%(who)s + +
                %(who)s-Passwort:
                +
                +

                Wichtig: Ab diesem Punkt müssen in Ihrem + Browser "Cookies" aktiviert sein, da ansonsten keine Ihrer Änderungen gespeichert werden kann.

                Mailmans Administrationsinterface benutzt sog. "Session Cookies", - damit Sie sich nicht für jede Administrationsaufgabe erneut - authentifizieren müssen. Diese "Cookies" verfallen automatisch beim - Schließen Ihres Webbrowsers, Sie können Sie allerdings auch sofort + damit Sie sich nicht für jede Administrationsaufgabe erneut + authentifizieren müssen. Diese "Cookies" verfallen automatisch beim + Schließen Ihres Webbrowsers, Sie können Sie allerdings auch sofort verfallen lassen, indem Sie auf den Abmelden Link unter - Andere administrative Tätigkeiten klicken (den Sie sehen + Andere administrative Tätigkeiten klicken (den Sie sehen werden, sobald Sie erfolgreich eingeloggt sind). -

                +

                diff --git a/templates/de/archidxentry.html b/templates/de/archidxentry.html index f9bb57aa..5c620b20 100755 --- a/templates/de/archidxentry.html +++ b/templates/de/archidxentry.html @@ -1,4 +1,68 @@ -
              • %(subject)s -  -%(author)s - +
              • %(subject)s +  +%(author)s + +
              • \ No newline at end of file diff --git a/templates/de/archidxfoot.html b/templates/de/archidxfoot.html index 80526232..47b236db 100755 --- a/templates/de/archidxfoot.html +++ b/templates/de/archidxfoot.html @@ -1,21 +1,85 @@ - -

                - Datum der letzten Nachricht: - %(lastdate)s
                - Archiviert: %(archivedate)s -

                -

                  -
                • Nachrichten sortiert nach: + +

                  +Datum der letzten Nachricht: +%(lastdate)s
                  +Archiviert: %(archivedate)s +

                  +

                  -

                  -


                  - Dieses Archiv wurde mit +
                +

                +


                +Dieses Archiv wurde mit Pipermail %(version)s erzeugt. - - + + +

                \ No newline at end of file diff --git a/templates/de/archidxhead.html b/templates/de/archidxhead.html index 42f84d1f..dbab8e42 100755 --- a/templates/de/archidxhead.html +++ b/templates/de/archidxhead.html @@ -1,24 +1,89 @@ - - - Das %(listname)s-%(archive)s-Archiv nach %(archtype)s - + + + +Das %(listname)s-%(archive)s-Archiv nach %(archtype)s + %(encoding)s - - - -

                %(archive)s Archiv nach %(archtype)s

                -
                  -
                • Nachrichten sortiert nach: + + + +

                  %(archive)s Archiv nach %(archtype)s

                  + -

                  Von: %(firstdate)s
                  - Bis: %(lastdate)s
                  - Nachrichten: %(size)s

                  -

                    +
                  +

                  Von: %(firstdate)s
                  +Bis: %(lastdate)s
                  +Nachrichten: %(size)s

                  +

                    +

                  \ No newline at end of file diff --git a/templates/de/archlistend.html b/templates/de/archlistend.html index 9bc052dd..2e1191b0 100755 --- a/templates/de/archlistend.html +++ b/templates/de/archlistend.html @@ -1 +1,64 @@ -
                + diff --git a/templates/de/archliststart.html b/templates/de/archliststart.html index 830c9f44..7b6d0786 100755 --- a/templates/de/archliststart.html +++ b/templates/de/archliststart.html @@ -1,4 +1,68 @@ - - - - +
                ArchivAnsehen nach:Herunterladbare Version
                + + + +
                ArchivAnsehen nach:Herunterladbare Version
                \ No newline at end of file diff --git a/templates/de/archtoc.html b/templates/de/archtoc.html index 5080bbca..6d7c0c26 100755 --- a/templates/de/archtoc.html +++ b/templates/de/archtoc.html @@ -1,14 +1,78 @@ - - - Das %(listname)s-Archiv - + + + +Das %(listname)s-Archiv + %(meta)s - - -

                Das %(listname)s-Archiv

                -

                - Über diese Liste können weitere Informationen angesehen werden, + + +

                Das %(listname)s-Archiv

                +

                + Über diese Liste können weitere Informationen angesehen werden, oder das gesamte Archiv kann heruntergeladen werden (%(size)s).

                @@ -16,5 +80,5 @@

                Das %(listname)s-Archiv

                %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/de/archtocentry.html b/templates/de/archtocentry.html index 2afdec89..0e3b74ba 100755 --- a/templates/de/archtocentry.html +++ b/templates/de/archtocentry.html @@ -1,12 +1,74 @@ - -
                %(archivelabel)s: - [ Diskussionsfaden ] - [ Betreff ] - [ Autor ] - [ Datum ] -
                %(archivelabel)s: +[ Diskussionsfaden ] +[ Betreff ] +[ Autor ] +[ Datum ] +
                - - - - - - - - - - - - - - - - - - + + + + + + + + + +

                +
                - -- - -
                -

                  -

                - Über - - - -
                -

                -

                Um frühere Nachrichten an diese Liste zu sehen, -besuchen Sie bitte das Archiv der -Liste . -

                -
                - Benutzung von -
                + + + +<mm-list-name> Infoseite</mm-list-name> + + +

                + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + +
                + -- + +
                +

                  +

                +Über + + + +
                +

                +

                Um frühere Nachrichten an diese Liste zu sehen, +besuchen Sie bitte das Archiv der +Liste . +

                +
                +Benutzung von +
                Um eine Nachricht an alle Listenmitglieder zu senden, schicken Sie diese an - . + . -

                Sie können im folgenden Abschnitt diese Liste abonnieren - oder ein bestehendes Abonnement ändern. -

                - Abonnieren von -
                -

                - Abonnieren Sie , indem Sie das folgende Formular - ausfüllen: - -

                  - - - - - - - - - - - - + + + + + + - - - - - - - - - - -
                  Ihre E-Mail-Adresse: -  
                  Ihr Name (optional): 
                  Sie können weiter unten ein Passwort +

                  Sie können im folgenden Abschnitt diese Liste abonnieren + oder ein bestehendes Abonnement ändern. +

                  +Abonnieren von +
                  +

                  + Abonnieren Sie , indem Sie das folgende Formular + ausfüllen: + +

                    + + + + + + + + + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - - - -
                    Ihre E-Mail-Adresse: + 
                    Ihr Name (optional): 
                    Sie können weiter unten ein Passwort eingeben. Dieses Passwort bietet nur eine geringe Sicherheit, sollte aber verhindern, dass andere Ihr Abonnement manipulieren. Verwenden Sie kein wertvolles Passwort, da es ab und zu im Klartext an Sie geschickt wird! -

                    Wenn Sie kein Passwort eingeben, wird für Sie ein +

                    Wenn Sie kein Passwort eingeben, wird für Sie ein Zufallspasswort generiert und Ihnen zugeschickt, sobald Sie Ihr - Abonnement bestätigt haben. Sie können sich Ihr Passwort + Abonnement bestätigt haben. Sie können sich Ihr Passwort jederzeit per E-Mail zuschicken lassen, wenn Sie weiter unten - die Seite zum Ändern Ihrer persönlichen Einstellungen aufrufen. -
                    Wählen Sie ein Passwort: 
                    Erneute Eingabe zur Bestätigung: 
                    Welche Sprache bevorzugen Sie zur - Benutzerführung?  
                    Möchten Sie die Listenmails gebündelt in Form einer täglichen + die Seite zum Ändern Ihrer persönlichen Einstellungen aufrufen. +
                    Wählen Sie ein Passwort: 
                    Erneute Eingabe zur Bestätigung: 
                    Welche Sprache bevorzugen Sie zur + Benutzerführung?  
                    Möchten Sie die Listenmails gebündelt in Form einer täglichen Zusammenfassung (digest) erhalten? Nein - Ja -
                    -
                    -
                    - -
                  -
                  - - Abonnenten der Liste -
                  - - - -
                  - Austragen / Ändern einer Mailadresse -
                  - -
                  - -
                  - -
                  - - - +
                Nein + Ja +
                +
                +
                + + +

                + +Abonnenten der Liste +
                + + + +
                +Austragen / Ändern einer Mailadresse +
                + +
                + +
                + +
                + +

                + diff --git a/templates/de/options.html b/templates/de/options.html index 71edcc65..149d882d 100755 --- a/templates/de/options.html +++ b/templates/de/options.html @@ -1,267 +1,305 @@ - - Persönliche Einstellungen für <MM-Presentable-User> und die Liste <MM-List-Name> - - - - - -
                - - Persönliche Einstellungen von für die Liste -
                + +Persönliche Einstellungen für <mm-presentable-user> und die Liste <mm-list-name> +</mm-list-name></mm-presentable-user> + + + + +
                + + Persönliche Einstellungen von für die Liste +

                - - - - - +
                - Abonnement-Status von , Passwort und - persönliche Einstellungen für die Liste . -
                - - - - -

                -

                + + + +
                +Abonnement-Status von , Passwort und + persönliche Einstellungen für die Liste . +
                + + +

                +

                - - +

                - - - +
                - - Änderung der Abonnementsinformation für die Liste -
                Sie können die Adresse, unter der Sie die Liste - beziehen, ändern, indem Sie die neue Adresse in die Felder unten - eintragen. Hinweis: An die neue Adresse wird eine - Bestätigungs-E-Mail geschickt und die Änderung muss bestätigt werden, + + + - - - - - -
                + +Änderung der Abonnementsinformation für die Liste +
                Sie können die Adresse, unter der Sie die Liste + beziehen, ändern, indem Sie die neue Adresse in die Felder unten + eintragen. Hinweis: An die neue Adresse wird eine + Bestätigungs-E-Mail geschickt und die Änderung muss bestätigt werden, bevor sie aktiv wird. -

                Bestätigungs-E-Mails bleiben ca. gültig. - -

                Ebenso können Sie Ihren Anzeigenamen setzen oder ändern (z.B. Paul Schmidt). - -

                Wenn Sie die Änderungen für alle Ihre Abonnements auf - vornehmen möchen, aktivieren Sie die Option Global ändern! - -

                - - - - - - - -
                Neue Adresse:
                Zur Bestätigung nochmal:
                -
                - - - - -
                Ihr Name (optional):
                -
                -

                Global ändern

                - +

                Bestätigungs-E-Mails bleiben ca. gültig. + +

                Ebenso können Sie Ihren Anzeigenamen setzen oder ändern (z.B. Paul Schmidt). + +

                Wenn Sie die Änderungen für alle Ihre Abonnements auf + vornehmen möchen, aktivieren Sie die Option Global ändern! + +

                + + + + + + + +
                Neue Adresse:
                Zur Bestätigung nochmal:
                +

                + + + + +
                Ihr Name (optional):
                + +
                +

                Global ändern

                +

                - - - - - - - - + + + + %(textlink)s - diff --git a/templates/el/archtocnombox.html b/templates/el/archtocnombox.html index 664dc4a3..7449d494 100755 --- a/templates/el/archtocnombox.html +++ b/templates/el/archtocnombox.html @@ -1,19 +1,83 @@ - - - Ôï éóôïñéêü ôçò %(listname)s - + + + +Το ιστοÏικό της %(listname)s + %(meta)s - - -

                Ôï éóôïñéêü ôçò %(listname)s

                -

                - Ìðïñåßôå íá äåßôå ðåñéóóüôåñåò ðëçñïöïñßåò ãéá - áõôÞ ôç ëßóôá. + + +

                Το ιστοÏικό της %(listname)s

                +

                + ΜποÏείτε να δείτε πεÏισσότεÏες πληÏοφοÏίες για + αυτή τη λίστα.

                %(noarchive_msg)s %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/el/article.html b/templates/el/article.html index 66b675b0..9a750a7f 100755 --- a/templates/el/article.html +++ b/templates/el/article.html @@ -1,12 +1,13 @@ - - - %(title)s - - - - - + +

                Το ιστοÏικό της %(listname)s

                +

                +Κανένα μήνυμα δεν έχει σταλεί σε αυτή τη λίστα Ï€Ïος το παÏόν, επομένως το ιστοÏικό + είναι κενό αυτή τη στιγμή. ΜποÏείτε να δείτε πεÏισσότεÏες + πληÏοφοÏίες για αυτή τη λίστα.

                - - + + diff --git a/templates/el/headfoot.html b/templates/el/headfoot.html index 591f7bf7..d1c847ea 100755 --- a/templates/el/headfoot.html +++ b/templates/el/headfoot.html @@ -1,28 +1,91 @@ - -Áõôü ôï êåßìåíï ìðïñåß íá ðåñéÝ÷åé -÷áñáêôÞñåò óå -Python format ïé ïðïßïé áíáëýïíôáé ìå ôç ÷ñÞóç ìéáò ëßóôáò ôéìþí. Ç ëßóôá ôùí -õðïêáôÜóôáôùí ðïõ åðéôñÝðïíôáé åßíáé: + +Αυτό το κείμενο μποÏεί να πεÏιέχει +χαÏακτήÏες σε +Python format οι οποίοι αναλÏονται με τη χÏήση μιας λίστας τιμών. Η λίστα των +υποκατάστατων που επιτÏέπονται είναι:
                  -
                • real_name - Ôï "ùñáßïðïéçìÝíï" üíïìá ôçò ëßóôáò; óõíÞèùò ôï üíïìá - ôçò ëßóôáò ìå êåöáëáéïðïßçóç. +
                • real_name - Το "ωÏαίοποιημένο" όνομα της λίστας; συνήθως το όνομα + της λίστας με κεφαλαιοποίηση. -
                • list_name - Ôï üíïìá ìå ôï ïðïßï ç ëßóôá ðñïóäéïñßæåôáé - óôéò äéåõèýíóåéò éóôïóåëßäùí, óôï ïðïßï ç êåöáëáéïðïßçóç åßíáé óçìáíôéêÞ. +
                • list_name - Το όνομα με το οποίο η λίστα Ï€ÏοσδιοÏίζεται + στις διευθÏνσεις ιστοσελίδων, στο οποίο η κεφαλαιοποίηση είναι σημαντική. -
                • host_name - Ôï DNS üíïìá ôïõ domain óôï ïðïßï âñßóêåôáé - ï server ðïõ õðïóôçñßæåé ôéò ëßóôåò. +
                • host_name - Το DNS όνομα του domain στο οποίο βÏίσκεται + ο server που υποστηÏίζει τις λίστες. -
                • web_page_url - Ï âáóéêüò äéêôõáêüò ôüðïò ôïõ Mailman. Óå áõôü ìðïñåß íá - ðñïóáñôçèåß ôï, ð.÷. listinfo/%(list_name)s ãéá ôçí ðñïâïëÞ ôçò óåëßäáò ìå ôéò - ðëçñïöïñßåò ãéá áõôÞ ôç ëßóôá çëåêôñïíéêïý ôá÷õäñïìåßïõ. +
                • web_page_url - Ο βασικός δικτυακός τόπος του Mailman. Σε αυτό μποÏεί να + Ï€ÏοσαÏτηθεί το, Ï€.χ. listinfo/%(list_name)s για την Ï€Ïοβολή της σελίδας με τις + πληÏοφοÏίες για αυτή τη λίστα ηλεκτÏÎ¿Î½Î¹ÎºÎ¿Ï Ï„Î±Ï‡Ï…Î´Ïομείου. -
                • description - Ç óõíïðôéêÞ ðåñéãñáöÞ ôçò ëßóôáò çëåêôñïíéêïý - ôá÷õäñïìåßïõ. +
                • description - Η συνοπτική πεÏιγÏαφή της λίστας ηλεκτÏÎ¿Î½Î¹ÎºÎ¿Ï + ταχυδÏομείου. -
                • info - Ç ðëÞñçò ðåñéãñáöÞ ôçò ëßóôáò çëåêôñïíéêïý - ôá÷õäñïìåßïõ. +
                • info - Η πλήÏης πεÏιγÏαφή της λίστας ηλεκτÏÎ¿Î½Î¹ÎºÎ¿Ï + ταχυδÏομείου. -
                • cgiext - Ç åðÝêôáóç ç ïðïßá ðñïóôßèåôáé óôá CGI scripts. -
                +
              • cgiext - Η επέκταση η οποία Ï€Ïοστίθεται στα CGI scripts. +
              • diff --git a/templates/el/listinfo.html b/templates/el/listinfo.html index 71be2f28..c9abf025 100755 --- a/templates/el/listinfo.html +++ b/templates/el/listinfo.html @@ -1,149 +1,206 @@ - - - - <MM-List-Name> Óåëßäá Ðëçñïöïñéþí - - - -

                -

                - Abbestellen von - Ihre anderen Abonnements bei -
                - Aktivieren Sie die Bestätigungs-Checkbox und drücken Sie diesen + + + + - + +
                +

                +Abbestellen von +Ihre anderen Abonnements bei +
                + Aktivieren Sie die Bestätigungs-Checkbox und drücken Sie diesen Knopf, um Ihr Abonnement dieser Mailingliste zu beenden. Warnung: Diese Aktion wird sofort aktiv!

                -

                - Sie können sich eine Liste aller Ihrer Abonnements auf - anzeigen lassen. Benutzen Sie dies, falls Sie dieselben Änderungen - auch für die anderen Listen gelten lassen wollen. +

                + Sie können sich eine Liste aller Ihrer Abonnements auf + anzeigen lassen. Benutzen Sie dies, falls Sie dieselben Änderungen + auch für die anderen Listen gelten lassen wollen.

                -

                -
                - - - - - +
                - Ihr Passwort für die Liste -
                - -
                -

                Haben Sie Ihr Passwort vergessen?

                -
                + + + - -
                +Ihr Passwort für die Liste +
                + +
                +

                Haben Sie Ihr Passwort vergessen?

                +
                Klicken Sie auf diesen Knopf und Ihnen wird das Passwort an Ihre Adresse zugeschickt. -

                -

                - -
                -
                - -
                -

                Änderung des Passworts

                - - - - - - - - -
                Neues - Passwort:
                Nochmal zur Bestätigung:
                - - -

                Global ändern -
                -
                - +

                +

                + +
                +

                + +
                +

                Änderung des Passworts

                + + + + + + + + +
                Neues + Passwort:
                Nochmal zur Bestätigung:
                + +

                Global ändern +
                +

                - - +
                - Ihre persönlichen Einstellungen für -
                +
                +Ihre persönlichen Einstellungen für +
                -

                -Die aktuellen Werte sind mit Häkchen versehen. - -

                Bitte beachten Sie, dass einige der Optionen eine "Global ändern"-Checkbox -besitzen. Deren Ankreuzen bewirkt, dass die Änderungen für +Die aktuellen Werte sind mit Häkchen versehen. +

                Bitte beachten Sie, dass einige der Optionen eine "Global ändern"-Checkbox +besitzen. Deren Ankreuzen bewirkt, dass die Änderungen für jede von Ihnen abonnierte Mailingliste auf vorgenommen werden. Klicken Sie auf Andere Abonnements auflisten, um zu sehen, welche weitern Abonnement Sie haben.

                - -
                - - Mailzustellung

                + + - +

                - - - + +

                - - - - - - + + - - + - - - - + + - - + - - + - - - + X-Mailman-Copy: yes im Header hinzugefügt. + +

                +
                + +Mailzustellung

                Setzen Sie diese Option auf An, um Nachrichten von der Liste zu empfangen. Setzen Sie diese Option auf Aus, um keine Nachrichten von der Liste zu empfangen (z.B. weil Sie im Urlaub sind). Wenn Sie die Mailzustellung ausstellen, vergessen Sie nicht, diese nach Ihrem Urlaub wieder anzustellen! -

                - An
                - Aus

                - Global ändern -

                +An
                +Aus

                +Global ändern +

                - Zusammenfassungsmodus

                +

                +Zusammenfassungsmodus

                Wenn Sie den Zusammenfassungsmodus aktivieren, erhalten Sie die - Nachrichten eines Tages gebündelt anstatt einzeln. + Nachrichten eines Tages gebündelt anstatt einzeln. Wenn der Zusammenfassungsmodus von "An" auf "Aus" gewechselt wird, erhalten Sie noch eine letzte Zusammenfassung. -

                - Aus
                - An -
                - MIME- oder Klartext-Zusammenfassungen?

                +

                +Aus
                +An +
                +MIME- oder Klartext-Zusammenfassungen?

                Generell sind MIME-Zusammenfassungen bevorzugt und empfohlen, aber - falls Sie ein Problem damit haben, diese zu lesen, können Sie hier + falls Sie ein Problem damit haben, diese zu lesen, können Sie hier auf Klartext-Zusammenfassungen umsteigen. -

                - MIME
                - Klartext

                - Global ändern -

                +MIME
                +Klartext

                +Global ändern +

                - Wollen Sie Ihre eigenen Nachrichten über die Liste zurückerhalten?

                +

                +Wollen Sie Ihre eigenen Nachrichten über die Liste zurückerhalten?

                Normalerweise erhalten Sie JEDE E-Mail. Wenn Sie Ihre eigenen E-Mails - nicht nochmal sehen möchten, setzen Sie diese Option auf Nein. -

                - Nein
                - Ja -
                - Bestätigungsmail, wenn Sie E-Mail an die Liste schicken?

                -

                - Nein
                - Ja -
                - Monatliche Erinnerungsmail mit Passwort?

                + nicht nochmal sehen möchten, setzen Sie diese Option auf Nein. +

                +Nein
                +Ja +
                +Bestätigungsmail, wenn Sie E-Mail an die Liste schicken?

                +

                +Nein
                +Ja +
                +Monatliche Erinnerungsmail mit Passwort?

                Einmal im Monat erhalten Sie eine Erinnerungsmail mit Ihrem Passwort - für jede Liste, die Sie abonniert haben. - Sie können dies auf Listenbasis an- und abstellen, indem Sie hier - Nein bzw. Ja wählen. Wenn Sie die Erinnerungsmail für alle Ihre + für jede Liste, die Sie abonniert haben. + Sie können dies auf Listenbasis an- und abstellen, indem Sie hier + Nein bzw. Ja wählen. Wenn Sie die Erinnerungsmail für alle Ihre Abonnements abstellen, erhalten Sie keinerlei Erinnerungsmails mehr. -

                - Nein
                - Ja

                - Global ändern -

                - In der Liste der Abonnenten unsichtbar machen?

                - Wenn sich jemand die Mitglieder der Liste anzeigen lässt, wird Ihre +

                +Nein
                +Ja

                +Global ändern +

                +In der Liste der Abonnenten unsichtbar machen?

                + Wenn sich jemand die Mitglieder der Liste anzeigen lässt, wird Ihre E-Mail-Adresse normalerweise angezeigt (in einer leicht modifizierten Art und Weise, um es Adresssammlern nicht zu leicht zu machen). - Wenn Sie wünschen, dass Ihre Adresse nicht gezeigt wird, wählen Sie + Wenn Sie wünschen, dass Ihre Adresse nicht gezeigt wird, wählen Sie hier bitte Ja. -

                - Nein
                - Ja -
                - Welche Sprache bevorzugen Sie?

                -

                - -
                - Welche Themen interessieren Sie?

                - Sofern hier rechts Themen aufgelistet sind, können Sie ein - oder mehrere Themen auswählen und damit den +

                +Nein
                +Ja +
                +Welche Sprache bevorzugen Sie?

                +

                + +
                +Welche Themen interessieren Sie?

                + Sofern hier rechts Themen aufgelistet sind, können Sie ein + oder mehrere Themen auswählen und damit den Listenverkehr auf der Mailingliste filtern, so dass Sie nur einen Teil der Nachrichten empfangen. Wenn eine Nachricht auf ein Thema "passt", dann erhalten Sie die Nachricht, ansonsten nicht. -

                Wenn eine Nachricht auf kein Thema passt, hängt es +

                Wenn eine Nachricht auf kein Thema passt, hängt es von der folgenden Option ab, ob Sie keine oder alle Nachrichten erhalten. -

                - -
                - Möchten Sie Nachrichten erhalten, auf die kein Themen-Filter +

                + +
                +Möchten Sie Nachrichten erhalten, auf die kein Themen-Filter "passt"?

                Diese Option hat nur Auswirkungen, wenn Sie oben mindestens ein Thema angegeben haben. Sie gibt an, wie die - Standardzustellungsregel für Nachrichten lautet, auf die + Standardzustellungsregel für Nachrichten lautet, auf die kein Thema passt. Geben Sie hier Nein an, bedeutet dies, dass Sie keine Nachrichten erhalten, wenn kein Thema passt, @@ -269,44 +307,39 @@

                Änderung des Passworts

                doch erhalten!

                Wenn Sie gar keine Themen angegeben haben, erhalten Sie - selbstverständlich alle Nachrichten an die Mailingliste! -

                - Nein
                - Ja -
                - Mehrfache Kopien von Nachrichten vermeiden?

                + selbstverständlich alle Nachrichten an die Mailingliste! +

                +Nein
                +Ja +
                +Mehrfache Kopien von Nachrichten vermeiden?

                Wenn Sie explizit im Feld To: oder Cc: - im Kopf einer E-Mail aufgeführt sind, können Sie - sich dafür entscheiden, keine Kopie dieser - Nachricht über die Mailingliste zu erhalten. - Wählen Sie Ja, um mehrfache Kopien zu - vermeiden; wählen Sie Nein, um diese + im Kopf einer E-Mail aufgeführt sind, können Sie + sich dafür entscheiden, keine Kopie dieser + Nachricht über die Mailingliste zu erhalten. + Wählen Sie Ja, um mehrfache Kopien zu + vermeiden; wählen Sie Nein, um diese Kopien weiterhin zu erhalten. -

                Wenn für die Mailingliste +

                Wenn für die Mailingliste mitgliederpersonalisierte Nachrichten eingeschaltet - sind und Sie sich dafür entschieden haben, Kopien + sind und Sie sich dafür entschieden haben, Kopien zu erhalten, wird jeder von diesen ein Feld - X-Mailman-Copy: yes im Header hinzugefügt. - -

                - Nein
                - Ja

                - Global ändern -

                -
                -
                +Nein
                +Ja

                +Global ändern +

                +
                +
                -

                - - - - + + +

                diff --git a/templates/de/private.html b/templates/de/private.html index 72a41bd0..f57985c4 100755 --- a/templates/de/private.html +++ b/templates/de/private.html @@ -1,63 +1,124 @@ - Authentifizierung für den Zugriff auf das Archiv von %(realname)s - - - -
                +Authentifizierung für den Zugriff auf das Archiv von %(realname)s + + + + %(message)s - - - - - - - - - - - - - - - -
                - Authentifizierung für den Zugriff auf das Archiv von %(realname)s -
                E-Mail-Adresse:
                Passwort:
                -
                - - - + + + +

                Wichtig: Ab diesem Punkt müssen in Ihrem - Browser "Cookies" aktiviert sein, ansonsten müssen Sie sich für + + + + + + + + + + + + + + + +
                +Authentifizierung für den Zugriff auf das Archiv von %(realname)s +
                E-Mail-Adresse:
                Passwort:
                +
                + + + - -

                Wichtig: Ab diesem Punkt müssen in Ihrem + Browser "Cookies" aktiviert sein, ansonsten müssen Sie sich für jede Aktion wieder anmelden.

                Mailmans Administrationsinterface benutzt sog. "session cookies", - damit Sie sich nicht für jede Administrationsaufgabe erneut - authentifizieren müssen. Diese "Cookies" verfallen automatisch bei - Beendigung Ihres Webbrowsers; Sie können Sie allerdings auch sofort + damit Sie sich nicht für jede Administrationsaufgabe erneut + authentifizieren müssen. Diese "Cookies" verfallen automatisch bei + Beendigung Ihres Webbrowsers; Sie können Sie allerdings auch sofort verfallen lassen, indem Sie auf den Ausloggen-Link unter - Andere Administrative Tätigkeiten klicken (den Sie sehen + Andere Administrative Tätigkeiten klicken (den Sie sehen werden, sobald Sie erfolgreich eingeloggt sind). -

                - - - - - - + +
                - Passwort-Erinnerung -
                Falls Sie Ihr Passwort nicht mehr wissen, tragen Sie oben Ihre +

                + + + + + + - - - - -
                +Passwort-Erinnerung +
                Falls Sie Ihr Passwort nicht mehr wissen, tragen Sie oben Ihre E-Mail-Adresse ein und klicken Sie auf Passwort zusenden und es wird Ihnen per E-Mail zugestellt werden.
                - +

                + diff --git a/templates/de/roster.html b/templates/de/roster.html index 2fa112d3..a86ce493 100644 --- a/templates/de/roster.html +++ b/templates/de/roster.html @@ -1,50 +1,109 @@ - - - <MM-List-Name>-Abonnenten - - - - -

                - - - - - - - - - - - - - - - -
                - -Abonnenten -
                - -

                -

                - -

                Klicken Sie auf Ihre Adresse um zu Ihrer Abonnementsseite zu gelangen.
                - (Für Einträge in Klammern ist die Zustellung von E-Mails deaktiviert.)

                -
                -
                - - Abonnenten von : -
                -
                -
                - Abonnenten von mit Tageszusammenfassung: -
                -
                -

                -

                -

                -

                - - - + + +<mm-list-name>-Abonnenten</mm-list-name> + + +

                + + + + + + + + + + + + + + + +
                +-Abonnenten +
                +

                +

                +

                Klicken Sie auf Ihre Adresse um zu Ihrer Abonnementsseite zu gelangen.
                +(Für Einträge in Klammern ist die Zustellung von E-Mails deaktiviert.)

                +
                +
                + + Abonnenten von : +
                +
                +
                + Abonnenten von mit Tageszusammenfassung: +
                +
                +

                +

                +

                +

                + +

                + diff --git a/templates/de/subscribe.html b/templates/de/subscribe.html index c0c164f6..4e40e1cc 100644 --- a/templates/de/subscribe.html +++ b/templates/de/subscribe.html @@ -1,9 +1,72 @@ -<MM-List-Name> Resultate des Abonnierens +<mm-list-name> Resultate des Abonnierens</mm-list-name> -

                Resultate des Abonnierens

                - - - +

                Resultate des Abonnierens

                + + + diff --git a/templates/el/admindbdetails.html b/templates/el/admindbdetails.html index fa547766..7e744db8 100755 --- a/templates/el/admindbdetails.html +++ b/templates/el/admindbdetails.html @@ -1,62 +1,123 @@ -Ôá äéá÷åéñéóôéêÜ áéôÞìáôá áðåéêïíßæïíôáé ìå Ýíá áðü ôïõò äýï ôñüðïõò, óå ìéá ðåñéëçðôéêÞ óåëßäá, êáé óå ìéá óåëßäá ìå ëåðôïìåñÞ -ðåñéãñáöÞ. -Ç ðåñéëçðôéêÞ óåëßäá ðåñéÝ÷åé åêêñåìÞ áéôÞìáôá åããñáöÞò Þ äéáãñáöÞò, üðùò êáé ìçíýìáôá -ôá ïðïßá öõëÜóóïíôáé ðåñéìÝíïíôáò ôçí ÝãêñéóÞ óáò, ôáîéíïìçìÝíá ìå âÜóç ôçí çëåêôñïíéêÞ äéåýèõíóç -ôïõ áðïóôïëÝá. Ç ëåðôïìåñÞò óåëßäá ðåñéÝ÷åé ìéá ðéï ëåðôïìåñÞ åðéóêüðçóç êÜèå ìÞíõìáôïò, -óõìðåñéëáìâáíïìÝíùí êáé üëùí ôùí ôßôëùí ôùí ìçíõìÜôùí, êáèþò êáé Ýíá áðüóðáóìá ôïõ êõñßùò êåéìÝíïõ -ôïõ ìçíýìáôïò. - -

                Óå üëåò ôéò óåëßäåò, ïé ðáñáêÜôù äõíáôüôçôåò åßíáé äéáèÝóéìåò: - -

                • ÁíáâïëÞ -- ÁíáâÜëåôå ôçí áðüöáóÞ óáò ãéá áñãüôåñá. Êáìßá åíÝñãåéá -äåí ãßíåôáé áõôÞ ôç óôéãìÞ ãéá ôï óõãêåêñéìÝíï åêêñåìÝò äéá÷åéñéóôéêü áßôçìá, áëëÜ ãéá ôá ìçíýìáôá -ðïõ êñáôïýíôáé ðåñéìÝíïíôáò ôçí ÝãêñéóÞ óáò, ðáñáìÝíåé ç äõíáôüôçôá íá ôá ðñïùèÞóåôå -Þ íá ôá áöÞóåôå ùò Ý÷ïõí (äåßôå ðáñáêÜôù). - -
                • ¸ãêñéóç -- Åãêñßíåôå ôï ìÞíõìá, sóôÝëíïíôÜò ôï óôç ëßóôá. -Ãéá áéôÞìáôá ðïõ áöïñïýí ôçí åããñáöÞ ÷ñÞóôç åãêñßíåôå ôçí áëëáãÞ óôçí êáôÜóôáóç ôçò -åããñáöÞò. - -
                • Áðüññéøç -- Áðïññßøôå ôï ìÞíõìá, óôÝëíïíôáò ìßá åéäïðïßçóç áðüññéøçò óôïí áðïóôïëÝá, -êáé äéáãñÜöïíôáò ôï áñ÷éêü ìÞíõìá. Ãéá áéôÞìáôá ðïõ áöïñïýí ôçí åããñáöÞ ÷ñÞóôç, -áðïññßøôå ôçí áëëáãÞ óôçí êáôÜóôáóç ôçò åããñáöÞò. Óå êÜèå ðåñßðôùóç, èá ðñÝðåé íá ðñïóèÝóåôå ôçí -áéôéïëïãßá ãéá ôçí áðüññéøç óôï óõíïäåõôéêü ðëáßóéï êåéìÝíïõ. - -
                • ÄéáãñáöÞ -- ÄéáãñÜøôå ôï áñ÷éêü ìÞíõìá, ÷ùñßò íá óôåßëåôå åéäïðïßçóç ãéá ôç äéáãñáöÞ. -Ãéá áéôÞìáôá ðïõ áöïñïýí ôçí åããñáöÞ ÷ñÞóôç, áõôÞ ç ëåéôïõñãßá áðëÜ äéáãñÜöåé ôï áßôçìá ÷ùñßò åéäïðïßçóç -óôï ÷ñÞóôç ðïõ Ýóôåéëå ôï áßôçìá. ÁõôÞ óõíÞèùò ç åíÝñãåéá åöáñìüæåôáé ãéá ôç äéáãñáöÞ ôùí ãíùóôþí -ìçíõìÜôùí ìáæéêÞò áðïóôïëÞò.
                - -

                Ãéá ìçíýìáôá ðïõ êñáôïýíôáé ðåñéìÝíïíôáò ôçí ÝãêñéóÞ óáò, åíåñãïðïéÞóôå ôçí åðéëïãÞ ÄéáôÞñçóç -åÜí èÝëåôå íá êñáôÞóåôå Ýíá áíôßãñáöï ôïõ ìçíýìáôïò ãéá ôï äéá÷åéñéóôÞ ôçò éóôïóåëßäáò. -ÁõôÞ ç ëåéôïõñãßá åßíáé ÷ñÞóéìç ãéá ôá êáôá÷ñçóôéêÜ ìçíýìáôá ðïõ èÝëåôå íá äéáãñÜøåôå áðü -ôç ëßóôá, áëëÜ êáé íá êñáôÞóåôå óôï áñ÷åßï ãéá ìåëëïíôéêÞ åðéèåþñçóç. - -

                ÅíåñãïðïéÞóôå ôçí åðéëïãÞ Ðñïþèçóç óå, êáé ãñÜøôå ôç äéåýèõíóç -óôï ðåäßï "ðñïò" åÜí èÝëåôå íá ðñïùèÞóåôå ôï ìÞíõìá óå êÜðïéï ÷ñÞóôç ðïõ äåí åßíáé -óôç ëßóôá. Ãéá íá åðåîåñãáóôåßôå Ýíá ìÞíõìá ðïõ êñáôåßôáé, ðñéí óôáëåß óôç ëßóôá, -ðñÝðåé íá ðñïùèÞóåôå ôï ìÞíõìá óôïí åáõôü óáò (Þ óôïõò äçìéïõñãïýò ôçò ëßóôáò) -êáé íá äéáãñÜøåôå ôï áñ÷éêü ìÞíõìá. ¾óôåñá, üôáí ôï ìÞíõìá öôÜóåé óå åóÜò, -êÜíôå ôéò áëëáãÝò ðïõ èÝëåôå êáé óôåßëôå ôï ðÜëé óôç ëßóôá, âÜæïíôáò óáí ôßôëï: Åãêñßèçêå -êáé óõìðëçñþíïíôáò ôïí êùäéêü ôçò ëßóôáò. Óå áõôÞ ôçí ðåñßðôùóç èá ðñÝðåé íá óõìðåñéëÜâáôå êáé Ýíá óçìåßùìá -óôï ìÞíõìá áõôü, óôï ïðïßï íá åîçãåßôå üôé Ý÷åôå ôñïðïðïéÞóåé ôï êåßìåíï. - -

                ÅÜí ï áðïóôïëÝáò åßíáé ìÝëïò ôçò ëßóôáò, ï ïðïßïò åßíáé óå äéáäéêáóßá ôñïðïðïßçóçò, -ìðïñåßôå ðñïáéñåôéêÜ íá óâÞóåôå ôçí óçìáßá ôñïðïðïßçóÞò ôïõ. Áõôü åßíáé ÷ñÞóéìï -üôáí ç ëßóôá óáò åßíáé ñõèìéóìÝíç íá èÝôåé ôá íÝá ìÝëç õðü åðéôÞñçóç êáé åóåßò -Ý÷åôå áðïöáóßóåé üôé áõôü ôï ìÝëïò ìðïñåß íá áðïóôÝëëåé ìçíýìáôá óôç ëßóôá ÷ùñßò -íá ÷ñåéÜæåôáé Ýãêñéóç. - -

                ÅÜí ï áðïóôïëÝáò äåí åßíáé ìÝëïò ôçò ëßóôáò, ìðïñåßôå íá ðñïóèÝóåôå ôçí çë. äéåýèõíóç ôïõ -óå Ýíá ößëôñï áðïóôïëÝùí. Ôá ößëôñá áðïóôïëÝùí ðåñéãñÜöïíôáé óôçí ðñïóùðéêÞ óåëßäá ïñéóìïý ößëôñùí áðïóôïëÝùí, êáé ìðïñåß íá åßíáé -áõôüìáôçò Ýãêñéóçò (Åãêñßóåéò), áõôüìáôçò êñÜôçóçò (ÊñáôÞóåéò), -áõôüìáôçò áðüññéøçò (Áðïññßøåéò), Þ áõôüìáôçò äéáãñáöÞò (ÄéáãñáöÝò). ÁõôÞ ç åðéëïãÞ -äåí èá åßíáé äéáèÝóéìç åÜí ç äéåýèõíóç åßíáé Þäç óå Ýíá áðü ôá ößëôñá áðïóôïëÝùí. - -

                ¼ôáí ôåëåéþóåôå, åíåñãïðïéÞóôå ôçí ÕðïâïëÞ üëùí ôùí ÄåäïìÝíùí, åðéëïãÞ ðïõ âñßóêåôáé -óôçí áñ÷Þ Þ óôï ôÝëïò ôçò óåëßäáò. ÁõôÞ ç åðéëïãÞ èá õðïâÜëåé üëåò ôéò åðéëåãìÝíåò åíÝñãåéåò ãéá -üëá ôá äéá÷åéñéóôéêÜ áéôÞìáôá ãéá ôá ïðïßá ëÜâáôå áðüöáóç. - -

                ÅðéóôñïöÞ óôçí ðåñéëçðôéêÞ óåëßäá. +Τα διαχειÏιστικά αιτήματα απεικονίζονται με ένα από τους δÏο Ï„Ïόπους, σε μια πεÏιληπτική σελίδα, και σε μια σελίδα με λεπτομεÏή +πεÏιγÏαφή. +Η πεÏιληπτική σελίδα πεÏιέχει εκκÏεμή αιτήματα εγγÏαφής ή διαγÏαφής, όπως και μηνÏματα +τα οποία φυλάσσονται πεÏιμένοντας την έγκÏισή σας, ταξινομημένα με βάση την ηλεκτÏονική διεÏθυνση +του αποστολέα. Η λεπτομεÏής σελίδα πεÏιέχει μια πιο λεπτομεÏή επισκόπηση κάθε μήνυματος, +συμπεÏιλαμβανομένων και όλων των τίτλων των μηνυμάτων, καθώς και ένα απόσπασμα του κυÏίως κειμένου +του μηνÏματος. + +

                Σε όλες τις σελίδες, οι παÏακάτω δυνατότητες είναι διαθέσιμες: + +

                • Αναβολή -- Αναβάλετε την απόφασή σας για αÏγότεÏα. Καμία ενέÏγεια +δεν γίνεται αυτή τη στιγμή για το συγκεκÏιμένο εκκÏεμές διαχειÏιστικό αίτημα, αλλά για τα μηνÏματα +που κÏατοÏνται πεÏιμένοντας την έγκÏισή σας, παÏαμένει η δυνατότητα να τα Ï€Ïοωθήσετε +ή να τα αφήσετε ως έχουν (δείτε παÏακάτω). + +
                • ΈγκÏιση -- ΕγκÏίνετε το μήνυμα, sστέλνοντάς το στη λίστα. +Για αιτήματα που αφοÏοÏν την εγγÏαφή χÏήστη εγκÏίνετε την αλλαγή στην κατάσταση της +εγγÏαφής. + +
                • ΑπόÏÏιψη -- ΑποÏÏίψτε το μήνυμα, στέλνοντας μία ειδοποίηση απόÏÏιψης στον αποστολέα, +και διαγÏάφοντας το αÏχικό μήνυμα. Για αιτήματα που αφοÏοÏν την εγγÏαφή χÏήστη, +αποÏÏίψτε την αλλαγή στην κατάσταση της εγγÏαφής. Σε κάθε πεÏίπτωση, θα Ï€Ïέπει να Ï€Ïοσθέσετε την +αιτιολογία για την απόÏÏιψη στο συνοδευτικό πλαίσιο κειμένου. + +
                • ΔιαγÏαφή -- ΔιαγÏάψτε το αÏχικό μήνυμα, χωÏίς να στείλετε ειδοποίηση για τη διαγÏαφή. +Για αιτήματα που αφοÏοÏν την εγγÏαφή χÏήστη, αυτή η λειτουÏγία απλά διαγÏάφει το αίτημα χωÏίς ειδοποίηση +στο χÏήστη που έστειλε το αίτημα. Αυτή συνήθως η ενέÏγεια εφαÏμόζεται για τη διαγÏαφή των γνωστών +μηνυμάτων μαζικής αποστολής.
                +

                Για μηνÏματα που κÏατοÏνται πεÏιμένοντας την έγκÏισή σας, ενεÏγοποιήστε την επιλογή ΔιατήÏηση +εάν θέλετε να κÏατήσετε ένα αντίγÏαφο του μηνÏματος για το διαχειÏιστή της ιστοσελίδας. +Αυτή η λειτουÏγία είναι χÏήσιμη για τα καταχÏηστικά μηνÏματα που θέλετε να διαγÏάψετε από +τη λίστα, αλλά και να κÏατήσετε στο αÏχείο για μελλοντική επιθεώÏηση. + +

                ΕνεÏγοποιήστε την επιλογή ΠÏοώθηση σε, και γÏάψτε τη διεÏθυνση +στο πεδίο "Ï€Ïος" εάν θέλετε να Ï€Ïοωθήσετε το μήνυμα σε κάποιο χÏήστη που δεν είναι +στη λίστα. Για να επεξεÏγαστείτε ένα μήνυμα που κÏατείται, Ï€Ïιν σταλεί στη λίστα, +Ï€Ïέπει να Ï€Ïοωθήσετε το μήνυμα στον εαυτό σας (ή στους δημιουÏγοÏÏ‚ της λίστας) +και να διαγÏάψετε το αÏχικό μήνυμα. ΎστεÏα, όταν το μήνυμα φτάσει σε εσάς, +κάντε τις αλλαγές που θέλετε και στείλτε το πάλι στη λίστα, βάζοντας σαν τίτλο: ΕγκÏίθηκε +και συμπληÏώνοντας τον κωδικό της λίστας. Σε αυτή την πεÏίπτωση θα Ï€Ïέπει να συμπεÏιλάβατε και ένα σημείωμα +στο μήνυμα αυτό, στο οποίο να εξηγείτε ότι έχετε Ï„Ïοποποιήσει το κείμενο. + +

                Εάν ο αποστολέας είναι μέλος της λίστας, ο οποίος είναι σε διαδικασία Ï„Ïοποποίησης, +μποÏείτε Ï€ÏοαιÏετικά να σβήσετε την σημαία Ï„Ïοποποίησής του. Αυτό είναι χÏήσιμο +όταν η λίστα σας είναι Ïυθμισμένη να θέτει τα νέα μέλη υπό επιτήÏηση και εσείς +έχετε αποφασίσει ότι αυτό το μέλος μποÏεί να αποστέλλει μηνÏματα στη λίστα χωÏίς +να χÏειάζεται έγκÏιση. + +

                Εάν ο αποστολέας δεν είναι μέλος της λίστας, μποÏείτε να Ï€Ïοσθέσετε την ηλ. διεÏθυνση του +σε ένα φίλτÏο αποστολέων. Τα φίλτÏα αποστολέων πεÏιγÏάφονται στην Ï€Ïοσωπική σελίδα οÏÎ¹ÏƒÎ¼Î¿Ï Ï†Î¯Î»Ï„Ïων αποστολέων, και μποÏεί να είναι +αυτόματης έγκÏισης (ΕγκÏίσεις), αυτόματης κÏάτησης (ΚÏατήσεις), +αυτόματης απόÏÏιψης (ΑποÏÏίψεις), ή αυτόματης διαγÏαφής (ΔιαγÏαφές). Αυτή η επιλογή +δεν θα είναι διαθέσιμη εάν η διεÏθυνση είναι ήδη σε ένα από τα φίλτÏα αποστολέων. + +

                Όταν τελειώσετε, ενεÏγοποιήστε την Υποβολή όλων των Δεδομένων, επιλογή που βÏίσκεται +στην αÏχή ή στο τέλος της σελίδας. Αυτή η επιλογή θα υποβάλει όλες τις επιλεγμένες ενέÏγειες για +όλα τα διαχειÏιστικά αιτήματα για τα οποία λάβατε απόφαση. + +

                ΕπιστÏοφή στην πεÏιληπτική σελίδα. +

                \ No newline at end of file diff --git a/templates/el/admindbpreamble.html b/templates/el/admindbpreamble.html index 1858d4c1..9ca703a2 100755 --- a/templates/el/admindbpreamble.html +++ b/templates/el/admindbpreamble.html @@ -1,10 +1,74 @@ -ÁõôÞ ç óåëßäá ðåñéÝ÷åé Ýíá õðïóýíïëï áðü ôá %(listname)s ìçíýìáôá ôá ïðïßá -êñáôïýíôáé ðåñéìÝíïíôáò ôçí ÝãêñéóÞ óáò. ÁõôÞ ôç óôéãìÞ äåß÷íåé +Αυτή η σελίδα πεÏιέχει ένα υποσÏνολο από τα %(listname)s μηνÏματα τα οποία +κÏατοÏνται πεÏιμένοντας την έγκÏισή σας. Αυτή τη στιγμή δείχνει %(description)s -

                Ãéá êÜèå äéá÷åéñéóôéêü áßôçìá , ðáñáêáëþ åðéëÝîôå ôçí åíÝñãåéá ðïõ èÝëåôå, -åíåñãïðïéþíôáò ôçí åðéëïãÞ ÕðïâïëÞ üëùí ôùí ÄåäïìÝíùí üôáí ôåëåéþóåôå. -Áíáëõôéêüôåñåò ïäçãßåò åßíáé äéáèÝóéìåò åäþ . +

                Για κάθε διαχειÏιστικό αίτημα , παÏακαλώ επιλέξτε την ενέÏγεια που θέλετε, +ενεÏγοποιώντας την επιλογή Υποβολή όλων των Δεδομένων όταν τελειώσετε. +ΑναλυτικότεÏες οδηγίες είναι διαθέσιμες εδώ . -

                Ìðïñåßôå áêüìá íá äåßôå ìßá ðåñßëçøç áðü üëá ôá åêêñåìÞ -äéá÷åéñéóôéêÜ áéôÞìáôá. +

                ΜποÏείτε ακόμα να δείτε μία πεÏίληψη από όλα τα εκκÏεμή +διαχειÏιστικά αιτήματα. +

                \ No newline at end of file diff --git a/templates/el/admindbsummary.html b/templates/el/admindbsummary.html index 4b88cd0f..c6cbe05d 100755 --- a/templates/el/admindbsummary.html +++ b/templates/el/admindbsummary.html @@ -1,12 +1,76 @@ -ÁõôÞ ç óåëßäá ðåñéÝ÷åé ìßá ðåñßëçøç áðü ôá ðñüóöáôá -äéá÷åéñéóôéêÜ áéôÞìáôá áðáéôþíôáò ôçí ÝãêñßóÞ óáò ãéá ôçí   -%(listname)s ëßóôá çë. ôá÷õäñïìåßïõ. -Ðñùôßóôùò, èá âñåßôå ôç ëßóôá áðü ôá åêêñåìÞ áéôÞìáôá åããñáöÞò -Þ äéáãñáöÞò, åÜí õðÜñ÷ïõí, áêïëïõèïýìåíá áðü ìçíýìáôá ðïõ êñáôïýíôáé -ðåñéìÝíïíôáò ôçí ÝãêñéóÞ óáò. +Αυτή η σελίδα πεÏιέχει μία πεÏίληψη από τα Ï€Ïόσφατα +διαχειÏιστικά αιτήματα απαιτώντας την έγκÏίσή σας για την   +%(listname)s λίστα ηλ. ταχυδÏομείου. +ΠÏωτίστως, θα βÏείτε τη λίστα από τα εκκÏεμή αιτήματα εγγÏαφής +ή διαγÏαφής, εάν υπάÏχουν, ακολουθοÏμενα από μηνÏματα που κÏατοÏνται +πεÏιμένοντας την έγκÏισή σας. -

                Ãéá êÜèå äéá÷åéñéóôéêü áßôçìá, ðáñáêáëþ åðéëÝîôå ôçí åíÝñãåéá ðïõ èÝëåôå, åíåñãïðïéþíôáò ôçí åðéëïãÞ -ÕðïâïëÞ üëùí ôùí ÄåäïìÝíùí üôáí ôåëåéþóåôå. -Áíáëõôéêüôåñåò ïäçãßåò åßíáé åðßóçò äéáèÝóéìåò. +

                Για κάθε διαχειÏιστικό αίτημα, παÏακαλώ επιλέξτε την ενέÏγεια που θέλετε, ενεÏγοποιώντας την επιλογή +Υποβολή όλων των Δεδομένων όταν τελειώσετε. +ΑναλυτικότεÏες οδηγίες είναι επίσης διαθέσιμες. -

                Ìðïñåßôå áêüìá íá äåßôå ôéò ëåðôïìÝñåéåò áðü üëá ôá ìçíýìáôá ðïõ êñáôïýíôáé. +

                ΜποÏείτε ακόμα να δείτε τις λεπτομέÏειες από όλα τα μηνÏματα που κÏατοÏνται. +

                \ No newline at end of file diff --git a/templates/el/admlogin.html b/templates/el/admlogin.html index 5e43de6c..14ec4867 100755 --- a/templates/el/admlogin.html +++ b/templates/el/admlogin.html @@ -1,40 +1,101 @@ - %(listname)s Ðéóôïðïßçóç ôïõ %(who)s - - - -
                +%(listname)s Πιστοποίηση του %(who)s + + + + %(message)s - - - - - - - - - - - -
                - Ðéóôïðïßçóç ôïõ %(who)s ôçò - ëßóôáò %(listname)s -
                Êùäéêüò ôïõ %(who)s:
                -
                -

                Óçìáíôéêü: Áðü ôï - óçìåßï áõôü êáé Ýðåéôá, èá ðñÝðåé íá Ý÷åôå åíåñãïðïéçìÝíá ôá cookies óôïí browser. - Óå äéáöïñåôéêÞ ðåñßðôùóç êáìßá äéá÷åéñéóôéêÞ áëëáãÞ äåí èá åíåñãïðïéçèåß. + + + + + + + + + + + +
                +Πιστοποίηση του %(who)s της + λίστας %(listname)s +
                Κωδικός του %(who)s:
                +
                +

                Σημαντικό: Από το + σημείο αυτό και έπειτα, θα Ï€Ïέπει να έχετε ενεÏγοποιημένα τα cookies στον browser. + Σε διαφοÏετική πεÏίπτωση καμία διαχειÏιστική αλλαγή δεν θα ενεÏγοποιηθεί. -

                Ôá session cookies ÷ñçóéìïðïéïýíôáé óôï äéá÷åéñéóôéêü ðåñéâÜëëïí - ôïõ Mailman Ýôóé þóôå íá ìç ÷ñåéÜæåôáé íá ãßíåôáé ðéóôïðïßçóç ôïõ ÷ñÞóôç - ãéá êÜèå äéá÷åéñéóôéêÞ ëåéôïõñãßá. Áõôü ôï - cookie èá ëÞîåé áõôüìáôá üôáí èá âãåßôå áðü ôï browser. ÅíáëëáêôéêÜ - ìðïñåßôå íá ôï êáôáóôñÝøåôå êáé åóåßò ðáôþíôáò ôï óýíäåóìï - ¸îïäïò êÜôù áðü ôï 'Áëëåò Äéá÷åéñéóôéêÝò - Ëåéôïõñãßåò (äéáèÝóéìï áöïý óõíäåèåßôå åðéôõ÷þò). -

                +

                Τα session cookies χÏησιμοποιοÏνται στο διαχειÏιστικό πεÏιβάλλον + του Mailman έτσι ώστε να μη χÏειάζεται να γίνεται πιστοποίηση του χÏήστη + για κάθε διαχειÏιστική λειτουÏγία. Αυτό το + cookie θα λήξει αυτόματα όταν θα βγείτε από το browser. Εναλλακτικά + μποÏείτε να το καταστÏέψετε και εσείς πατώντας το σÏνδεσμο + Έξοδος κάτω από το 'Αλλες ΔιαχειÏιστικές + ΛειτουÏγίες (διαθέσιμο Î±Ï†Î¿Ï ÏƒÏ…Î½Î´ÎµÎ¸ÎµÎ¯Ï„Îµ επιτυχώς). +

                diff --git a/templates/el/archidxentry.html b/templates/el/archidxentry.html index f9bb57aa..5c620b20 100755 --- a/templates/el/archidxentry.html +++ b/templates/el/archidxentry.html @@ -1,4 +1,68 @@ -
              • %(subject)s -  -%(author)s - +
              • %(subject)s +  +%(author)s + +
              • \ No newline at end of file diff --git a/templates/el/archidxfoot.html b/templates/el/archidxfoot.html index bdbceed4..9161d7b7 100755 --- a/templates/el/archidxfoot.html +++ b/templates/el/archidxfoot.html @@ -1,21 +1,85 @@ - -

                - Ôåëåõôáßá çìåñïìçíßá ìçíýìáôïò: - %(lastdate)s
                - Áñ÷åéïèåôÞèçêå óôéò: %(archivedate)s -

                -

                  -
                • Ìçíýìáôá ôáîéíïìçìÝíá ìå: + +

                  +Τελευταία ημεÏομηνία μηνÏματος: +%(lastdate)s
                  +ΑÏχειοθετήθηκε στις: %(archivedate)s +

                  +

                  -

                  -


                  - Áõôü ôï éóôïñéêü äçìéïõñãÞèçêå ìå ôï +
                • ΠεÏισσότεÏες πληÏοφοÏίες για αυτή τη + λίστα...
                • +
                +

                +


                +Αυτό το ιστοÏικό δημιουÏγήθηκε με το Pipermail %(version)s. - - + + +

                \ No newline at end of file diff --git a/templates/el/archidxhead.html b/templates/el/archidxhead.html index 01a03215..75f37358 100755 --- a/templates/el/archidxhead.html +++ b/templates/el/archidxhead.html @@ -1,24 +1,89 @@ - - - Ôï %(listname)s %(archive)s Éóôïñéêü áðü %(archtype)s - + + + +Το %(listname)s %(archive)s ΙστοÏικό από %(archtype)s + %(encoding)s - - - -

                %(archive)s Áñ÷åßá áðü %(archtype)s

                -
                  -
                • Ìçíýìáôá ôáîéíïìçìÝíá êáôÜ: + + + +

                  %(archive)s ΑÏχεία από %(archtype)s

                  + -

                  Áñ÷Þ áðü: %(firstdate)s
                  - ÔÝëïò: %(lastdate)s
                  - Ìçíýìáôá: %(size)s

                  -

                    +
                  +

                  ΑÏχή από: %(firstdate)s
                  +Τέλος: %(lastdate)s
                  +ΜηνÏματα: %(size)s

                  +

                    +

                  \ No newline at end of file diff --git a/templates/el/archlistend.html b/templates/el/archlistend.html index 9bc052dd..2e1191b0 100755 --- a/templates/el/archlistend.html +++ b/templates/el/archlistend.html @@ -1 +1,64 @@ -
                + diff --git a/templates/el/archliststart.html b/templates/el/archliststart.html index 95e26440..d818203a 100755 --- a/templates/el/archliststart.html +++ b/templates/el/archliststart.html @@ -1,4 +1,68 @@ - - - - +
                Áñ÷åßïÄåßôå ôá ìå:¸êäïóç ðïõ ìðïñåßôå íá êáôåâÜóåôå
                + + + +
                ΑÏχείοΔείτε τα με:Έκδοση που μποÏείτε να κατεβάσετε
                \ No newline at end of file diff --git a/templates/el/archtoc.html b/templates/el/archtoc.html index 8b213cca..d9c4c9d9 100755 --- a/templates/el/archtoc.html +++ b/templates/el/archtoc.html @@ -1,20 +1,84 @@ - - - Ôï éóôïñéêü ôçò %(listname)s - + + + +Το ιστοÏικό της %(listname)s + %(meta)s - - -

                Ôá Áñ÷åßá ôçò %(listname)s

                -

                - Ìðïñåßôå íá äåßôå ðåñéóóüôåñåò ðëçñïöïñßåò ãéá - áõôÞ ôç ëßóôá Þ ìðïñåßôå íá êáôåâÜóåôå ôï - ðëÞñåò ìç åðåîåñãáóìÝíï áñ÷åßï(%(size)s). + + +

                Τα ΑÏχεία της %(listname)s

                +

                + ΜποÏείτε να δείτε πεÏισσότεÏες πληÏοφοÏίες για + αυτή τη λίστα ή μποÏείτε να κατεβάσετε το + πλήÏες μη επεξεÏγασμένο αÏχείο(%(size)s).

                %(noarchive_msg)s %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/el/archtocentry.html b/templates/el/archtocentry.html index 8bea9a59..a1f755fb 100755 --- a/templates/el/archtocentry.html +++ b/templates/el/archtocentry.html @@ -1,12 +1,74 @@ - -
                %(archivelabel)s: - [ Áêïëïõèßá ìçíõìÜôùí ] - [ ÈÝìá ] - [ ÓõããñáöÝáò ] - [ Çìåñïìçíßá ] -
                %(archivelabel)s: +[ Ακολουθία μηνυμάτων ] +[ Θέμα ] +[ ΣυγγÏαφέας ] +[ ΗμεÏομηνία ] +
                - - - - - - - - - - - - - - - - - -
                - -- - -
                -

                  -

                - Ó÷åôéêÜ ìå ôç - - - -
                -

                -

                Ãéá íá äåßôå ôá ðñïçãïýìåíá ìçíýìáôá ôçò ëßóôáò (éóôïñéêü) åðéóêåöôåßôå ôá - - ÉóôïñéêÜ Áñ÷åßá ôçò . - -

                -
                - ×ñÞóç ôçò ëßóôáò -
                - Ãéá íá áðïóôåßëåôå ìÞíõìá óå üëá ôá ìÝëç ôçò ëßóôáò óôåßëôå ìÞíõìá óôï - . + + + +<mm-list-name> Σελίδα ΠληÏοφοÏιών</mm-list-name> + + +

                + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + +
                + -- + +
                +

                  +

                +Σχετικά με τη + + + +
                +

                +

                Για να δείτε τα Ï€ÏοηγοÏμενα μηνÏματα της λίστας (ιστοÏικό) επισκεφτείτε τα + + ΙστοÏικά ΑÏχεία της . + +

                +
                +ΧÏήση της λίστας +
                + Για να αποστείλετε μήνυμα σε όλα τα μέλη της λίστας στείλτε μήνυμα στο + . -

                Ìðïñåßôå íá åããñáöåßôå óôç ëßóôá Þ íá áëëÜîåôå ôçí õðÜñ÷ïõóá åããñáöÞ óáò, - óôéò ðáñáêÜôù åíüôçôåò. -

                - ÅããñáöÞ óôçí -
                -

                - Åããñáöåßôå óôç ëßóôá óõìðëçñþíïíôáò ôçí ðáñáêÜôù - öüñìá. - -

                  - - - - - - - - - - - - + + + + + + - - - - - - - -
                  Ç çëåêôñïíéêÞ óáò äéåýèõíóç: -  
                  Ôï üíïìÜ óáò (ðñïáéñåôéêÜ): 
                  Ìðïñåßôå íá åéóÜãåôå Ýíáí ðñïóùðéêü êùäéêü - ðñüóâáóçò ðáñáêÜôù. Áõôüò ï ôñüðïò ðáñÝ÷åé ìÝôñéá áóöÜëåéá, áëëÜ áñêåß - ãéá íá ìçí åðéôñÝðåé óå Üëëïõò ÷ñÞóôåò íá ôñïðïðïéÞóïõí ôá óôïé÷åßá ôçò - åããñáöÞò óáò. Ìç ÷ñçóéìïðïéåßôå êÜðïéï ðïëýôéìï password êáèþò áõôü èá óáò óôÝëíåôáé - ðåñéïäéêÜ ìå email óå cleartext ìïñöÞ. +

                  ΜποÏείτε να εγγÏαφείτε στη λίστα ή να αλλάξετε την υπάÏχουσα εγγÏαφή σας, + στις παÏακάτω ενότητες. +

                  +ΕγγÏαφή στην +
                  +

                  + ΕγγÏαφείτε στη λίστα συμπληÏώνοντας την παÏακάτω + φόÏμα. + +

                    + + + + + + + + + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - -
                    Η ηλεκτÏονική σας διεÏθυνση: + 
                    Το όνομά σας (Ï€ÏοαιÏετικά): 
                    ΜποÏείτε να εισάγετε έναν Ï€Ïοσωπικό κωδικό + Ï€Ïόσβασης παÏακάτω. Αυτός ο Ï„Ïόπος παÏέχει μέτÏια ασφάλεια, αλλά αÏκεί + για να μην επιτÏέπει σε άλλους χÏήστες να Ï„Ïοποποιήσουν τα στοιχεία της + εγγÏαφής σας. Μη χÏησιμοποιείτε κάποιο πολÏτιμο password καθώς αυτό θα σας στέλνεται + πεÏιοδικά με email σε cleartext μοÏφή. -

                    ÅÜí åðéëÝîåôå íá ìçí åéóÜãåôå êùäéêü ðñüóâáóçò, - èá ðáñá÷èåß áõôüìáôá êÜðïéïò êùäéêüò êáé èá óáò áðïóôáëåß ìüëéò - åðéâåâáéþóåôå ôçí åããñáöÞ óáò. Ìðïñåßôå ðÜíôá íá æçôÜôå Ýíá - ìÞíõìá ìå ôïí êùäéêü óáò üôáí ôñïðïðïéåßôå ôéò ðñïóùðéêÝò óáò - ñõèìßóåéò. - -
                    -
                    ÄéáëÝîôå Ýíáí êùäéêü ðñüóâáóçò: 
                    ÅéóÜãåôå ðÜëé ôïí êùäéêü ãéá åðéâåâáßùóç: 
                    Óå ðïéá ãëþóóá åðéèõìåßôå íá öáßíïíôáé ôá ìçíýìáôÜ - óáò;  
                    Åðéèõìåßôå íá ëáìâÜíåôå áëëçëïãñáößá áðü ôç ëßóôá ìéá öïñÜ ôçí çìÝñá ãéá üëá ôá - ìçíýìáôá ôçò çìÝñáò óõãêåíôñùôéêÜ; +

                    Εάν επιλέξετε να μην εισάγετε κωδικό Ï€Ïόσβασης, + θα παÏαχθεί αυτόματα κάποιος κωδικός και θα σας αποσταλεί μόλις + επιβεβαιώσετε την εγγÏαφή σας. ΜποÏείτε πάντα να ζητάτε ένα + μήνυμα με τον κωδικό σας όταν Ï„Ïοποποιείτε τις Ï€Ïοσωπικές σας + Ïυθμίσεις. + + +
                    Διαλέξτε έναν κωδικό Ï€Ïόσβασης: 
                    Εισάγετε πάλι τον κωδικό για επιβεβαίωση: 
                    Σε ποια γλώσσα επιθυμείτε να φαίνονται τα μηνÏματά + σας;  
                    Επιθυμείτε να λαμβάνετε αλληλογÏαφία από τη λίστα μια φοÏά την ημέÏα για όλα τα + μηνÏματα της ημέÏας συγκεντÏωτικά; ¼÷é - Íáé -
                    -

                    -
                    - -
                  -
                  - - ÌÝëç -
                  - - - -

                  - - - -

                  - - - +
                Όχι + Îαι +
                +

                +
                + + + + + Μέλη + + + + + + + +

                + + + +

                + + +

                + diff --git a/templates/el/options.html b/templates/el/options.html index 393a84f0..2f0ec62c 100755 --- a/templates/el/options.html +++ b/templates/el/options.html @@ -1,327 +1,360 @@ - - <MM-Presentable-User> ñõèìßóåéò óõììåôï÷Þò óôç ëßóôá ãéá ôï ìÝëïò <MM-List-Name> - - - - - -
                - - ñõèìßóåéò óõììåôï÷Þò óôç ëßóôá ãéá ôï ìÝëïò - -
                -

                - - - - - + +<mm-presentable-user> Ïυθμίσεις συμμετοχής στη λίστα για το μέλος <mm-list-name> +</mm-list-name></mm-presentable-user> + + +
                - 's êáôÜóôáóç ôçò åããñáöÞò ôïõ ìÝëïõò, - êùäéêüò, êáé åðéëïãÝò ãéá ôç ëßóôá. -
                - - - - -

                -

                +
                + + Ïυθμίσεις συμμετοχής στη λίστα για το μέλος + +
                - -

                - - - - - - - - +
                - - ÁëëáãÞ ôùí ðëçñïöïñéþí åããñáöÞò ãéá ôç ëßóôá -
                Ìðïñåßôå íá áëëÜîåôå ôç äéåýèõíóç ìå ôçí ïðïßá åßóôå - åããåãñáììÝíïò óôç ëßóôá äßíïíôáò ôç íÝá äéåýèõíóç óôá ðáñáêÜôù ðåäßá. - ÐñÝðåé íá óçìåéùèåß üôé Ýíá ìÞíõìá åðéâåâáßùóçò èá óôáëåß óôç íÝá - äéåýèõíóç, êáé ç áëëáãÞ ðñÝðåé íá åðéâåâáéùèåß ðñéí ãßíåé ç - áðïäï÷Þ ôçò áðü ôï óýóôçìá. - -

                Ôá áéôÞìáôá åðéâåâáßùóçò ëÞãïõí ìåôÜ áðü . - -

                ÐñïáéñåôéêÜ åðßóçò, ìðïñåßôå íá ïñßóåôå Þ íá áëëÜîåôå ôï - ðñáãìáôéêü óáò üíïìá (ð.÷. Ìáñßá Ðáðáäïðïýëïõ). - -

                Áí èÝëåôå íá êÜíåôå áëëáãÝò óôç óõììåôï÷Þ óáò ãéá üëåò ôéò ëßóôåò - óôéò ïðïßåò åßóôå åããåãñáììÝíïò óôï , åíåñãïðïéÞóôå ôçí åðéëïãÞ - ÁëëáãÞ ðáíôïý. - -

                - - - - - - - -
                ÍÝá äéåýèõíóç:
                ÎáíÜ ãéá - åðéâåâáßùóç:
                -
                - - - - -
                Ôï üíïìÜ óáò - (ðñïáéñåôéêÜ):
                -
                -

                ÁëëáãÞ ðáíôïý

                + + + +
                +'s κατάσταση της εγγÏαφής του μέλους, + κωδικός, και επιλογές για τη λίστα. +
                + + +

                +

                - +

                - - - - - - + + +

                +

                - ÄéáãñáöÞ áðü ôç ëßóôá - Ïé Üëëåò óáò óõììåôï÷Ýò óôï -
                - ÅíåñãïðïéÞóôå ôï checkbox åðéâåâáßùóçò êáé ðáôÞóôå áõôü ôï - êïõìðß ãéá íá äéáãñáöåßôå áðü áõôÞ ôç ëßóôá. Ðñïåéäïðïßçóç: - ÁõôÞ ç åíÝñãåéá èá ïëïêëçñùèåß áìÝóùò! + + + +
                + +Αλλαγή των πληÏοφοÏιών εγγÏαφής για τη λίστα +
                ΜποÏείτε να αλλάξετε τη διεÏθυνση με την οποία είστε + εγγεγÏαμμένος στη λίστα δίνοντας τη νέα διεÏθυνση στα παÏακάτω πεδία. + ΠÏέπει να σημειωθεί ότι ένα μήνυμα επιβεβαίωσης θα σταλεί στη νέα + διεÏθυνση, και η αλλαγή Ï€Ïέπει να επιβεβαιωθεί Ï€Ïιν γίνει η + αποδοχή της από το σÏστημα. + +

                Τα αιτήματα επιβεβαίωσης λήγουν μετά από . + +

                ΠÏοαιÏετικά επίσης, μποÏείτε να οÏίσετε ή να αλλάξετε το + Ï€Ïαγματικό σας όνομα (Ï€.χ. ΜαÏία ΠαπαδοποÏλου). + +

                Αν θέλετε να κάνετε αλλαγές στη συμμετοχή σας για όλες τις λίστες + στις οποίες είστε εγγεγÏαμμένος στο , ενεÏγοποιήστε την επιλογή + Αλλαγή παντοÏ. + +

                + + + + + + + +
                Îέα διεÏθυνση:
                Ξανά για + επιβεβαίωση:
                +
                + + + + +
                Το όνομά σας + (Ï€ÏοαιÏετικά):
                +
                +

                Αλλαγή παντοÏ

                + + + - + +
                +

                +ΔιαγÏαφή από τη λίστα +Οι άλλες σας συμμετοχές στο +
                + ΕνεÏγοποιήστε το checkbox επιβεβαίωσης και πατήστε αυτό το + κουμπί για να διαγÏαφείτε από αυτή τη λίστα. ΠÏοειδοποίηση: + Αυτή η ενέÏγεια θα ολοκληÏωθεί αμέσως!

                -

                - Ìðïñåßôå íá äåßôå ìéá ëßóôá ìå üëåò ôéò Üëëåò ëßóôåò óôï - óôéò ïðïßåò åßóôå ìÝëïò. ×ñçóéìïðïéÞóôå áõôÞ ôçí åðéëïãÞ áí - èÝëåôå íá êÜíåôå ôéò ßäéåò áëëáãÝò óôç óõììåôï÷Þ óáò êáé - óôéò õðüëïéðåò ëßóôåò. +

                + ΜποÏείτε να δείτε μια λίστα με όλες τις άλλες λίστες στο + στις οποίες είστε μέλος. ΧÏησιμοποιήστε αυτή την επιλογή αν + θέλετε να κάνετε τις ίδιες αλλαγές στη συμμετοχή σας και + στις υπόλοιπες λίστες.

                -

                -
                - - - - - - -
                - Ï êùäéêüò ôçò ëßóôáò óáò -
                - -
                -

                Îå÷Üóáôå Ôïí Êùäéêü Óáò;

                -
                - ÐáôÞóôå áõôü ôï êïõìðß ãéá íá óáò áðïóôáëåß ï êùäéêüò óáò ìå email - óôç äéåýèõíóç ìå ôçí ïðïßá Ý÷åôå åããñáöåß. -

                -

                - -
                -
                - -
                -

                ÁëëáãÞ Ôïõ Êùäéêïý Óáò

                - - - - - - - - -
                ÍÝïò - êùäéêüò ðñüóâáóçò:
                ÎáíÜ ãéá - åðéâåâáßùóç:
                - - -

                ÁëëáãÞ ðáíôïý. -
                -
                - + + + +
                +Ο κωδικός της λίστας σας +
                + +
                +

                Ξεχάσατε Τον Κωδικό Σας;

                +
                + Πατήστε αυτό το κουμπί για να σας αποσταλεί ο κωδικός σας με email + στη διεÏθυνση με την οποία έχετε εγγÏαφεί. +

                +

                + +
                +

                + +
                +

                Αλλαγή Του ÎšÏ‰Î´Î¹ÎºÎ¿Ï Î£Î±Ï‚

                + + + + + + + + +
                Îέος + κωδικός Ï€Ïόσβασης:
                Ξανά για + επιβεβαίωση:
                + +

                Αλλαγή παντοÏ. +
                +

                - - +
                - Ïé åðéëïãÝò ôçò åããñáöÞò óáò óôç ëßóôá -
                +
                +Οι επιλογές της εγγÏαφής σας στη λίστα +
                -

                -Ïé ôñÝ÷ïõóåò åðéëïãÝò åßíáé ðñïåðéëåãìÝíåò. - -

                Óçìåéþóôå üôé ïñéóìÝíåò áðü ôéò åðéëïãÝò Ý÷ïõí ôï ðåäßï Ïñéóìüò ðáíôïý. -Áí åðéëÝîåôå áõôü ôï ðåäßï ïé áëëáãÝò èá ãßíïõí óå üëåò ôéò ëßóôåò -óôï óôéò ïðïßåò åßóôå ìÝëïò. ÐáôÞóôå óôï -ÅìöÜíéóç ôùí Üëëùí ëéóôþí ðáñáðÜíù ãéá íá äåßôå óå ðïéÝò Üëëåò -ëßóôåò åßóôå åããåãñáììÝíïò. +Οι Ï„Ïέχουσες επιλογές είναι Ï€Ïοεπιλεγμένες. +

                Σημειώστε ότι οÏισμένες από τις επιλογές έχουν το πεδίο ΟÏισμός παντοÏ. +Αν επιλέξετε αυτό το πεδίο οι αλλαγές θα γίνουν σε όλες τις λίστες +στο στις οποίες είστε μέλος. Πατήστε στο +Εμφάνιση των άλλων λιστών παÏαπάνω για να δείτε σε ποιές άλλες +λίστες είστε εγγεγÏαμμένος.

                - - - +
                - - ÐáñÜäïóç Mail

                - Ïñßóôå áõôÞ ôçí åðéëïãÞ óå ÅíåñãïðïéçìÝíï ãéá íá ëáìâÜíåôå ôá - ìçíýìáôá ðïõ óôÝëíïíôáé óå áõôÞ ôç ëßóôá. ×ñçóéìïðïéÞóôå ôï ÁðåíåñãïðïéçìÝíï áí èÝëåôå íá ìåßíåôå åããåãñáììÝíïò, áëëÜ äåí èÝëåôå íá ðáñáäßäïíôáé ìçíýìáôá óå åóÜò - ãéá êÜðïéï äéÜóôçìá (ð.÷. ôïí Áýãïõóôï ðïõ ëåßðåôå ãéá äéáêïðÝò). Áí áðåíåñãïðïéÞóåôå - ôçí ðáñÜäïóç ìçíõìÜôùí, ìçí îå÷Üóåôå íá ôçí åíåñãïðïéÞóåôå ðÜëé üôáí åðéóôñÝøåôå· äåí - èá åíåñãïðïéçèåß áõôüìáôá. -

                - ÅíåñãïðïéçìÝíç
                - ÁðåíåñãïðïéçìÝíç

                - Ïñéóìüò ðáíôïý -

                + - - - + + - - - - - - - - - - - - - - - - - - - + + + + + + + + +
                + +ΠαÏάδοση Mail

                + ΟÏίστε αυτή την επιλογή σε ΕνεÏγοποιημένο για να λαμβάνετε τα + μηνÏματα που στέλνονται σε αυτή τη λίστα. ΧÏησιμοποιήστε το ΑπενεÏγοποιημένο αν θέλετε να μείνετε εγγεγÏαμμένος, αλλά δεν θέλετε να παÏαδίδονται μηνÏματα σε εσάς + για κάποιο διάστημα (Ï€.χ. τον ΑÏγουστο που λείπετε για διακοπές). Αν απενεÏγοποιήσετε + την παÏάδοση μηνυμάτων, μην ξεχάσετε να την ενεÏγοποιήσετε πάλι όταν επιστÏέψετε· δεν + θα ενεÏγοποιηθεί αυτόματα. +

                +ΕνεÏγοποιημένη
                +ΑπενεÏγοποιημένη

                +ΟÏισμός Ï€Î±Î½Ï„Î¿Ï +

                - Ïñéóìüò Óõíüøåùí

                - Áí åíåñãïðïéÞóåôå ôéò óõíüøåéò, ôá ìçíýìáôá èá óáò óôÝëíïíôáé - ðáêåôáñéóìÝíá (óõíÞèùò Ýíá êÜèå çìÝñá áëëÜ ðéèáíþí óõ÷íüôåñá óå - ëßóôåò ìå ìåãÜëç êßíçóç), áíôß íá ôá ëáìâÜíåôå êáèÝ Ýíá îå÷ùñéóôÜ - ôçí þñá ðïõ óôÝëíïíôáé. Áí áðåíåñãïðïéÞóåôå ôçí áðïóôïëÞ óõíüøåùí, - ìðïñåß íá ëÜâåôå ìéá ôåëåõôáßá óýíïøç. -

                - Off
                - On -
                - Íá ëáìâÜíåôå óõíüøåéò áðëïý êåéìÝíïõ Þ MIME óõíüøåéò;

                - Ôï ðñüãñáììá óáò ãéá ôçí áíÜãíùóç ôùí ìçíõìÜôùí çëåêôñïíéêïý - ôá÷õäñïìåßïõ ìðïñåß íá õðïóôçñßæåé MIME óõíüøåéò Þ ü÷é. Åí' - ãÝíåé ïé MIME óõíüøåéò åßíáé ðñïôéìüôåñåò, áëëÜ áí Ý÷åôå - ðñüâëçìá óôçí áíÜãíùóÞ ôïõò, åðéëÝîôå íá ëáìâÜíåôå óõíüøåéò - áðëïý êåéìÝíïõ. -

                - MIME
                - Áðëü Êåßìåíï

                - Ïñéóìüò Ðáíôïý -

                +ΟÏισμός Συνόψεων

                + Αν ενεÏγοποιήσετε τις συνόψεις, τα μηνÏματα θα σας στέλνονται + πακεταÏισμένα (συνήθως ένα κάθε ημέÏα αλλά πιθανών συχνότεÏα σε + λίστες με μεγάλη κίνηση), αντί να τα λαμβάνετε καθέ ένα ξεχωÏιστά + την ÏŽÏα που στέλνονται. Αν απενεÏγοποιήσετε την αποστολή συνόψεων, + μποÏεί να λάβετε μια τελευταία σÏνοψη. +

                +Off
                +On +
                +Îα λαμβάνετε συνόψεις Î±Ï€Î»Î¿Ï ÎºÎµÎ¹Î¼Î­Î½Î¿Ï… ή MIME συνόψεις;

                + Το Ï€ÏόγÏαμμα σας για την ανάγνωση των μηνυμάτων ηλεκτÏÎ¿Î½Î¹ÎºÎ¿Ï + ταχυδÏομείου μποÏεί να υποστηÏίζει MIME συνόψεις ή όχι. Εν' + γένει οι MIME συνόψεις είναι Ï€ÏοτιμότεÏες, αλλά αν έχετε + Ï€Ïόβλημα στην ανάγνωσή τους, επιλέξτε να λαμβάνετε συνόψεις + Î±Ï€Î»Î¿Ï ÎºÎµÎ¹Î¼Î­Î½Î¿Ï…. +

                +MIME
                +Απλό Κείμενο

                +ΟÏισμός Î Î±Î½Ï„Î¿Ï +

                - ËÞøç ôùí äéêþí óáò ìçíõìÜôùí óôç ëßóôá;

                - ÊáíïíéêÜ, èá ëáìâÜíåôå áíôßãñáöï êÜèå ìçíýìáôïò ðïõ óôÝëíåôå - óôç ëßóôá. Áí äå èÝëåôå íá ëáìâÜíåôå áõôü ôï áíôßãñáöï, - ïñßóôå áõôÞ ôçí åðéëïãÞ óå ¼÷é. -

                - No
                - Yes -
                - Íá ëáìâÜíåôå åðéâåâáßùóç ëÞøçò ôïõ ìçíýìáôïò üôáí - óôÝëíåôå Ýíá ìÞíõìá óôç ëßóôá;

                -

                - ¼÷é
                - Íáé -
                - Íá ëáìâÜíåôå email ãéá ôçí õðåíèýìéóç ôïõ êùäéêïý óáò - ãéá áõôÞ ôç ëßóôá;

                - Ìéá öïñÜ ôï ìÞíá, èá ëáìâÜíåôå Ýíá email ôï ïðïßï èá - ðåñéÝ÷åé ôïí êùäéêü óáò ãéá êÜèå ëßóôá óôçí ïðïßá åßóôå - åããåãñáììÝíïò óå áõôü ôï server. Ìðïñåßôå íá áðåíåñãïðïéÞóåôå - áõôÞ ôçí åðéëïãÞ óå êÜèå ëßóôá åðéëÝãïíôáò ¼÷é óôï óçìåßï áõôü. - Áí áðåíåñãïðïéÞóåôå ôéò õðåíèõìßóåéò ôùí êùäéêþí ãéá üëåò ôéò - ëßóôåò óôéò ïðïßåò åßóôå åããåãñáììÝíïò, äå èá óáò óôáëåß êáíÝíá - ìÞíõìá õðåíèýìéóçò. -

                - ¼÷é
                - Íáé

                - Ïñéóìüò Ðáíôïý -

                - Áðüêñõøç áðü ôç ëßóôá ìåëþí;

                - ¼ôáí êÜðïéïò âëÝðåé ôá ìÝëç ôçò ëßóôáò, ç äéêÞ óáò email - äéåýèõíóç öáßíåôáé êáíïíéêÜ (óõãêáëõììÝíç ìå ôÝôïéï ôñüðï - þóôå íá åìðïäßæïíôáé üóïé èá Þèåëáí íá ðÜñïõí ôéò äéåõèýíóåéò - ãéá ôçí áðïóôïëÞ spam ìçíõìÜôùí). Áí äå èÝëåôå íá öáßíåôáé - ç email äéåýèõíóÞ óáò óå áõôü ôïí êáôÜëïãï ìåëþí êáèüëïõ, - åðéëÝîôå Íáé óôï óçìåßï áõôü. -

                - ¼÷é
                - Íáé -
                - ÐïéÜ ãëþóóá ðñïôéìÜôå;

                -

                - -
                - Óå ðïéÝò êáôçãïñßåò èåìÜôùí èá èÝëáôå íá åããñáöåßôå;

                - ÅðéëÝãïíôáò Ýíá Þ ðåñéóóüôåñá èÝìáôá, ìðïñåßôå íá - öéëôñÜñåôå ôçí êßíçóç ôçò ëßóôáò, þóôå íá ëáìâÜíåôå - ìüíï Ýíá õðïóýíïëï ôùí ìçíõìÜôùí. Áí Ýíá ìÞíõìá - ôáéñéÜæåé óôá åðéëåãìÝíá èÝìáôá, ôüôå èá ôï ëÜâåôå, - äéáöïñåôéêÜ ü÷é. - -

                Áí Ýíá ìÞíõìá äåí ôáéñéÜæåé óå êáíÝíá èÝìá, ï - êáíüíáò ðáñÜäïóçò åîáñôÜôáé áðü ôéò ñõèìßóåéò ôçò ðáñáêÜôù - åðéëïãÞò. Áí äåí Ý÷åôå åðéëÝîåé ôá èÝìáôá ðïõ óáò - åíäéáöÝñïõí, èá ëáìâÜíåôå üëá ôá ìçíýìáôá ðïõ óôÝëíïíôáé - óôç ëßóôá. -

                - -
                - ÈÝëåôå íá ëáìâÜíåôå ìçíýìáôá ðïõ äåí ôáéñéÜæïõí - ìå êáíÝíá áðü ôá ößëôñá èÝìáôïò;

                - - ÁõôÞ ç åðéëïãÞ åíåñãïðïéåßôáé ìüíï áí åßóôå åããåãñáììÝíïò - óå Ýíá ôïõëÜ÷éóôïí áðü ôá ðáñáðÜíù èÝìáôá. ÐåñéãñÜöåé - ðïéüò åßíáé ï ðñïêáèïñéóìÝíïò ôñüðïò ðáñÜäïóçò ãéá - ôá ìçíýìáôá ðïõ äåí ôáéñéÜæïõí ìå êáíÝíá áðü ôá èåìáôéêÜ - ößëôñá. Ç åðéëïãÞ ¼÷é óçìáßíåé üôé áí ôï ìÞíõìá - äåí ôáéñéÜæåé ìå êáíÝíá áðü ôá ößëôñá, ôüôå äåí èá ëÜâåôå - ôï ìÞíõìá, åíþ ç åðéëïãÞ Íáé óçìáßíåé üôé èá - óáò ðáñáäïèïýí êáé ôá ìçíýìáôá ðïõ äåí ôáéñéÜæïõí ìå ôá - ößëôñá ðïõ Ý÷ïõí ïñéóèåß. - -

                Áí äåí Ý÷åôå åðéëÝîåé ðáñáðÜíù êáíÝíá áðü ôá èÝìáôá - åíäéáöÝñïíôïò, ôüôå èá ëáìâÜíåôå êÜèå ìÞíõìá ðïõ - óôÝëíåôå óôç ëßóôá çëåêôñïíéêïý ôá÷õäñïìåßïõ. -

                - ¼÷é
                - Íáé -
                - Íá áðïöåýãïíôáé ôá äéðëÜ áíôßãñáöá ôùí ìçíõìÜôùí;

                - - ¼ôáí áíáöÝñåóôå ñçôÜ óôá To: Þ - Cc: headers åíüò ìçíýìáôïò ôçò ëßóôáò, ìðïñåßôå - íá åðéëÝîåôå íá ìçí ëÜâåôå Üëëï áíôßãñáöï áõôïý ôïõ - ìçíýìáôïò áðü ôç ëßóôá. ÅðéëÝîôå Íáé ãéá íá - áðïöýãåôå ôç ëÞøç áíôéãñÜöïõ ôïõ ßäéïõ ìçíýìáôïò áðü ôç - ëßóôá. ÅðéëÝîôå ¼÷é ãéá íá ëÜâåôå ôï áíôßãñáöï. - -

                Áí ç ëßóôá Ý÷åé åíåñãïðïéçìÝíç ôçí äõíáôüôçôá ðñïóùðïðïéçìÝíùí - ìçíõìÜôùí ôùí ìåëþí, êáé åðéëÝîåôå íá ëáìâÜíåôå áíôßãñáöá, êÜèå - áíôßãñáöï èá Ý÷åé Ýíá X-Mailman-Copy: yes header ôï ïðïßï - èá Ý÷åé ðñïóôåèåß óå áõôü. - -

                - ¼÷é
                - Íáé

                - Ïñéóìüò Ðáíôïý -

                -
                -
                +Λήψη των δικών σας μηνυμάτων στη λίστα;

                + Κανονικά, θα λαμβάνετε αντίγÏαφο κάθε μηνÏματος που στέλνετε + στη λίστα. Αν δε θέλετε να λαμβάνετε αυτό το αντίγÏαφο, + οÏίστε αυτή την επιλογή σε Όχι. +

                +No
                +Yes +
                +Îα λαμβάνετε επιβεβαίωση λήψης του μηνÏματος όταν + στέλνετε ένα μήνυμα στη λίστα;

                +

                +Όχι
                +Îαι +
                +Îα λαμβάνετε email για την υπενθÏμιση του ÎºÏ‰Î´Î¹ÎºÎ¿Ï ÏƒÎ±Ï‚ + για αυτή τη λίστα;

                + Μια φοÏά το μήνα, θα λαμβάνετε ένα email το οποίο θα + πεÏιέχει τον κωδικό σας για κάθε λίστα στην οποία είστε + εγγεγÏαμμένος σε αυτό το server. ΜποÏείτε να απενεÏγοποιήσετε + αυτή την επιλογή σε κάθε λίστα επιλέγοντας Όχι στο σημείο αυτό. + Αν απενεÏγοποιήσετε τις υπενθυμίσεις των κωδικών για όλες τις + λίστες στις οποίες είστε εγγεγÏαμμένος, δε θα σας σταλεί κανένα + μήνυμα υπενθÏμισης. +

                +Όχι
                +Îαι

                +ΟÏισμός Î Î±Î½Ï„Î¿Ï +

                +ΑπόκÏυψη από τη λίστα μελών;

                + Όταν κάποιος βλέπει τα μέλη της λίστας, η δική σας email + διεÏθυνση φαίνεται κανονικά (συγκαλυμμένη με τέτοιο Ï„Ïόπο + ώστε να εμποδίζονται όσοι θα ήθελαν να πάÏουν τις διευθÏνσεις + για την αποστολή spam μηνυμάτων). Αν δε θέλετε να φαίνεται + η email διεÏθυνσή σας σε αυτό τον κατάλογο μελών καθόλου, + επιλέξτε Îαι στο σημείο αυτό. +

                +Όχι
                +Îαι +
                +Ποιά γλώσσα Ï€Ïοτιμάτε;

                +

                + +
                +Σε ποιές κατηγοÏίες θεμάτων θα θέλατε να εγγÏαφείτε;

                + Επιλέγοντας ένα ή πεÏισσότεÏα θέματα, μποÏείτε να + φιλτÏάÏετε την κίνηση της λίστας, ώστε να λαμβάνετε + μόνο ένα υποσÏνολο των μηνυμάτων. Αν ένα μήνυμα + ταιÏιάζει στα επιλεγμένα θέματα, τότε θα το λάβετε, + διαφοÏετικά όχι. + +

                Αν ένα μήνυμα δεν ταιÏιάζει σε κανένα θέμα, ο + κανόνας παÏάδοσης εξαÏτάται από τις Ïυθμίσεις της παÏακάτω + επιλογής. Αν δεν έχετε επιλέξει τα θέματα που σας + ενδιαφέÏουν, θα λαμβάνετε όλα τα μηνÏματα που στέλνονται + στη λίστα. +

                + +
                +Θέλετε να λαμβάνετε μηνÏματα που δεν ταιÏιάζουν + με κανένα από τα φίλτÏα θέματος;

                + + Αυτή η επιλογή ενεÏγοποιείται μόνο αν είστε εγγεγÏαμμένος + σε ένα τουλάχιστον από τα παÏαπάνω θέματα. ΠεÏιγÏάφει + ποιός είναι ο Ï€ÏοκαθοÏισμένος Ï„Ïόπος παÏάδοσης για + τα μηνÏματα που δεν ταιÏιάζουν με κανένα από τα θεματικά + φίλτÏα. Η επιλογή Όχι σημαίνει ότι αν το μήνυμα + δεν ταιÏιάζει με κανένα από τα φίλτÏα, τότε δεν θα λάβετε + το μήνυμα, ενώ η επιλογή Îαι σημαίνει ότι θα + σας παÏαδοθοÏν και τα μηνÏματα που δεν ταιÏιάζουν με τα + φίλτÏα που έχουν οÏισθεί. + +

                Αν δεν έχετε επιλέξει παÏαπάνω κανένα από τα θέματα + ενδιαφέÏοντος, τότε θα λαμβάνετε κάθε μήνυμα που + στέλνετε στη λίστα ηλεκτÏÎ¿Î½Î¹ÎºÎ¿Ï Ï„Î±Ï‡Ï…Î´Ïομείου. +

                +Όχι
                +Îαι +
                +Îα αποφεÏγονται τα διπλά αντίγÏαφα των μηνυμάτων;

                + + Όταν αναφέÏεστε Ïητά στα To: ή + Cc: headers ενός μηνÏματος της λίστας, μποÏείτε + να επιλέξετε να μην λάβετε άλλο αντίγÏαφο Î±Ï…Ï„Î¿Ï Ï„Î¿Ï… + μηνÏματος από τη λίστα. Επιλέξτε Îαι για να + αποφÏγετε τη λήψη αντιγÏάφου του ίδιου μηνÏματος από τη + λίστα. Επιλέξτε Όχι για να λάβετε το αντίγÏαφο. + +

                Αν η λίστα έχει ενεÏγοποιημένη την δυνατότητα Ï€Ïοσωποποιημένων + μηνυμάτων των μελών, και επιλέξετε να λαμβάνετε αντίγÏαφα, κάθε + αντίγÏαφο θα έχει ένα X-Mailman-Copy: yes header το οποίο + θα έχει Ï€Ïοστεθεί σε αυτό. + +

                +Όχι
                +Îαι

                +ΟÏισμός Î Î±Î½Ï„Î¿Ï +

                +
                +
                -

                - - - - + + +

                diff --git a/templates/el/private.html b/templates/el/private.html index 7a2be99b..e5be28ca 100755 --- a/templates/el/private.html +++ b/templates/el/private.html @@ -1,59 +1,119 @@ - %(realname)s ðéóôïðïßçóç óôïí ðñïóôáôåõüìåíï ÷þñï áðïèÞêåõóçò éóôïñéêïý ìçíõìÜôùí - - -body bgcolor="#ffffff" onLoad="sf()"> -
                +%(realname)s πιστοποίηση στον Ï€Ïοστατευόμενο χώÏο αποθήκευσης ιστοÏÎ¹ÎºÎ¿Ï Î¼Î·Î½Ï…Î¼Î¬Ï„Ï‰Î½ + + +body bgcolor="#ffffff" onLoad="sf()"> + %(message)s - - - - - - - - - - - - - - - -
                - %(realname)s ðéóôïðïßçóç óôïí ðñïóôáôåõüìåíï ÷þñï - áðïèÞêåõóçò éóôïñéêïý ìçíõìÜôùí -
                Äéåýèõíóç çëåêôñïíéêïý ôá÷õäñïìåßïõ:
                Êùäéêüò ðñüóâáóçò:
                -
                -

                Óçìáíôéêü: Áðü áõôü ôï óçìåßï êáé Ýðåéôá, - èá ðñÝðåé íá Ý÷åôå åíåñãïðïéçìÝíá ôá cookies óôïí Browser ðïõ - ÷ñçóéìïðïéåßôå áëëéþò ïé áëëáãÝò ôïõ äéá÷åéñéóôÞ äåí èá éó÷ýóïõí. + + + + + + + + + + + + + + + +
                +%(realname)s πιστοποίηση στον Ï€Ïοστατευόμενο χώÏο + αποθήκευσης ιστοÏÎ¹ÎºÎ¿Ï Î¼Î·Î½Ï…Î¼Î¬Ï„Ï‰Î½ +
                ΔιεÏθυνση ηλεκτÏÎ¿Î½Î¹ÎºÎ¿Ï Ï„Î±Ï‡Ï…Î´Ïομείου:
                Κωδικός Ï€Ïόσβασης:
                +
                +

                Σημαντικό: Από αυτό το σημείο και έπειτα, + θα Ï€Ïέπει να έχετε ενεÏγοποιημένα τα cookies στον Browser που + χÏησιμοποιείτε αλλιώς οι αλλαγές του διαχειÏιστή δεν θα ισχÏσουν. -

                Ôá Session cookies ÷ñçóéìïðïéïýíôáé óôï äéá÷åéñéóôéêü ðåñéâÜëëïí - ôïõ Mailman þóôå íá ìç ÷ñåéÜæåôáé íá êÜíåôå ðéóôïðïßçóç ôùí óôïé÷åßùí - óáò êÜèå öïñÜ ðïõ åêôåëåßôå ìéá ëåéôïõñãßá äéá÷åßñéóçò. Áõôü ôï - cookie èá ëÞîåé áõôüìáôá ìüëéò êëåßóåôå ôïí browser, Þ ìðïñåßôå êáé - åóåßò íá êÜíåôå ôï cookie íá ëÞîåé ðáôþíôáò ôï link ôçò - Áðïóýíäåóçò. +

                Τα Session cookies χÏησιμοποιοÏνται στο διαχειÏιστικό πεÏιβάλλον + του Mailman ώστε να μη χÏειάζεται να κάνετε πιστοποίηση των στοιχείων + σας κάθε φοÏά που εκτελείτε μια λειτουÏγία διαχείÏισης. Αυτό το + cookie θα λήξει αυτόματα μόλις κλείσετε τον browser, ή μποÏείτε και + εσείς να κάνετε το cookie να λήξει πατώντας το link της + ΑποσÏνδεσης.

                - - - - - - + + + +
                - Password Reminder -
                If you don't remember your password, enter your email address + + + + + + - - - - -
                +Password Reminder +
                If you don't remember your password, enter your email address above and click the Remind button and your password will be emailed to you.
                - - +
                +

                diff --git a/templates/el/roster.html b/templates/el/roster.html index 1366b634..44c7230b 100755 --- a/templates/el/roster.html +++ b/templates/el/roster.html @@ -1,53 +1,112 @@ - - - <MM-List-Name> ÌÝëç ôçò ëßóôáò - - - - -

                - - - - - - - - - - - - - - - -
                - - ÌÝëç ôçò ëßóôáò -
                - -

                -

                - -

                ÊÜíôå Click óôçí äéåýèõíóç óáò ãéá íá åðéóêåöôåßôå ôçí óåëßäá ìå - åðéëïãÝò ôçò åããñáöÞò.
                (Ïé åðéëïãÝò ðïõ âñßóêïíôáé óå ðáñÝíèåóç äçëþíïõí üôé - üôé ç ðáñÜäïóç ìçíõìÜôùí ãéá áõôÝò ôéò ëßóôåò Ý÷åé áðåíåñãïðïéçèåß.)

                -
                -
                - - ÌÝëç ðïõ ëáìâÜíïõí óõãêåíôñùôéêÜ ôá ìçíýìáôá ôçò ëßóôáò : -
                -
                -
                - ÌÝëç ðïõ ëáìâÜíïõí óõãêåíôñùôéêÜ - ìçíýìáôá áðü : -
                -
                -

                -

                -

                -

                - - - + + +<mm-list-name> Μέλη της λίστας</mm-list-name> + + +

                + + + + + + + + + + + + + + + +
                + + Μέλη της λίστας +
                +

                +

                +

                Κάντε Click στην διεÏθυνση σας για να επισκεφτείτε την σελίδα με + επιλογές της εγγÏαφής.
                (Οι επιλογές που βÏίσκονται σε παÏένθεση δηλώνουν ότι + ότι η παÏάδοση μηνυμάτων για αυτές τις λίστες έχει απενεÏγοποιηθεί.)

                +
                +
                + + Μέλη που λαμβάνουν συγκεντÏωτικά τα μηνÏματα της λίστας : +
                +
                +
                + Μέλη που λαμβάνουν συγκεντÏωτικά + μηνÏματα από : +
                +
                +

                +

                +

                +

                + +

                + diff --git a/templates/el/subscribe.html b/templates/el/subscribe.html index 491cba39..b1a217c1 100755 --- a/templates/el/subscribe.html +++ b/templates/el/subscribe.html @@ -1,9 +1,72 @@ -<MM-List-Name> ÁðïôåëÝóìáôá ÅããñáöÞò +<mm-list-name> Αποτελέσματα ΕγγÏαφής</mm-list-name> -

                ÁðïôåëÝóìáôá ÅããñáöÞò

                - - - +

                Αποτελέσματα ΕγγÏαφής

                + + + diff --git a/templates/en/admindbdetails.html b/templates/en/admindbdetails.html index 4c19fc49..eeda5084 100644 --- a/templates/en/admindbdetails.html +++ b/templates/en/admindbdetails.html @@ -1,5 +1,67 @@ -The administrative requests are displayed in one of two ways, on a summary page, and on a details page. +The administrative requests are displayed in one of two ways, on a summary page, and on a details page. The summary page contains pending subscription and unsubscription requests, as well as postings being held for your approval, grouped by sender email address. The details page contains a more detailed view of @@ -23,8 +85,7 @@
              • Discard -- Throw away the original message, without sending a rejection notice. For membership requests, this simply discards the request without notice to the person making the request. This is -usually the action you want to take for known spam.
              - +usually the action you want to take for known spam.

            For held messages, turn on the Preserve option if you want to save a copy of the message for the site administrator. This is useful for abusive messages that you want to discard, but need to keep a record @@ -46,8 +107,7 @@ this member can be trusted to post to the list without approval.

            If the sender is not a list member, you can add the email address to -a sender filter. Sender filters are described on the sender filter privacy page, and may be one of +a sender filter. Sender filters are described on the sender filter privacy page, and may be one of auto-accept (Accepts), auto-hold (Holds), auto-reject (Rejects), or auto-discard (Discards). This option will not be available if the address is already on one of the @@ -58,3 +118,4 @@ actions for all administrative requests that you've made a decision for.

            Return to the summary page. +

            \ No newline at end of file diff --git a/templates/en/admindbpreamble.html b/templates/en/admindbpreamble.html index 659b77e7..72a30230 100644 --- a/templates/en/admindbpreamble.html +++ b/templates/en/admindbpreamble.html @@ -1,4 +1,67 @@ -This page contains a subset of the %(listname)s mailing list +This page contains a subset of the %(listname)s mailing list postings that are being held for your approval. It currently shows %(description)s @@ -8,3 +71,4 @@

            You can also view a summary of all pending requests. +

            \ No newline at end of file diff --git a/templates/en/admindbsummary.html b/templates/en/admindbsummary.html index 20ffef58..e8228319 100644 --- a/templates/en/admindbsummary.html +++ b/templates/en/admindbsummary.html @@ -1,4 +1,67 @@ -This page contains a summary of the current set of administrative +This page contains a summary of the current set of administrative requests requiring your approval for the %(listname)s mailing list. First, you will find the list of pending @@ -12,3 +75,4 @@

            You can also view the details of all held postings. +

            \ No newline at end of file diff --git a/templates/en/admlogin.html b/templates/en/admlogin.html index 4dd2574c..797f656c 100755 --- a/templates/en/admlogin.html +++ b/templates/en/admlogin.html @@ -1,30 +1,91 @@ - %(listname)s %(who)s Authentication +%(listname)s %(who)s Authentication - - -
            + + + %(message)s - - - - - - - - - - - -
            - %(listname)s %(who)s - Authentication -
            List %(who)s Password:
            -
            -

            Important: From this point on, you + + + + + + + + + + + +
            +%(listname)s %(who)s + Authentication +
            List %(who)s Password:
            +
            +

            Important: From this point on, you must have cookies enabled in your browser, otherwise no administrative changes will take effect. @@ -35,6 +96,6 @@ you can explicitly expire the cookie by hitting the Logout link under Other Administrative Activities (which you'll see once you successfully log in). -

            +

            diff --git a/templates/en/archidxentry.html b/templates/en/archidxentry.html index f9bb57aa..5c620b20 100644 --- a/templates/en/archidxentry.html +++ b/templates/en/archidxentry.html @@ -1,4 +1,68 @@ -
          • %(subject)s -  -%(author)s - +
          • %(subject)s +  +%(author)s + +
          • \ No newline at end of file diff --git a/templates/en/archidxfoot.html b/templates/en/archidxfoot.html index 0b0a4207..6da07609 100644 --- a/templates/en/archidxfoot.html +++ b/templates/en/archidxfoot.html @@ -1,21 +1,85 @@ -
          -

          - Last message date: - %(lastdate)s
          - Archived on: %(archivedate)s -

          -

            -
          • Messages sorted by: + +

            +Last message date: +%(lastdate)s
            +Archived on: %(archivedate)s +

            +

            -

            -


            - This archive was generated by +
          +

          +


          +This archive was generated by Pipermail %(version)s. - - + + +

          \ No newline at end of file diff --git a/templates/en/archidxhead.html b/templates/en/archidxhead.html index 2227dfda..56c8b6e3 100644 --- a/templates/en/archidxhead.html +++ b/templates/en/archidxhead.html @@ -1,15 +1,79 @@ - - - The %(listname)s %(archive)s Archive by %(archtype)s - + + + +The %(listname)s %(archive)s Archive by %(archtype)s + %(encoding)s - - - -

          %(archive)s Archives by %(archtype)s

          -
            -
          • Messages sorted by: + + + +

            %(archive)s Archives by %(archtype)s

            +
              +
            • Messages sorted by: %(thread_ref)s %(subject_ref)s %(author_ref)s @@ -17,8 +81,9 @@

              %(archive)s Archives by %(archtype)s

            • More info on this list...
            • -
            -

            Starting: %(firstdate)s
            - Ending: %(lastdate)s
            - Messages: %(size)s

            -

              +
            +

            Starting: %(firstdate)s
            +Ending: %(lastdate)s
            +Messages: %(size)s

            +

              +

            \ No newline at end of file diff --git a/templates/en/archlistend.html b/templates/en/archlistend.html index 9bc052dd..2e1191b0 100644 --- a/templates/en/archlistend.html +++ b/templates/en/archlistend.html @@ -1 +1,64 @@ - + diff --git a/templates/en/archliststart.html b/templates/en/archliststart.html index cdf5d17c..dc385915 100644 --- a/templates/en/archliststart.html +++ b/templates/en/archliststart.html @@ -1,4 +1,70 @@ - - - - +
            ArchiveView by:Downloadable version
            + + + + + +
            ArchiveView by:Downloadable version
            diff --git a/templates/en/archtoc.html b/templates/en/archtoc.html index 32ecb71c..a1e7fd44 100644 --- a/templates/en/archtoc.html +++ b/templates/en/archtoc.html @@ -1,13 +1,77 @@ - - - The %(listname)s Archives - + + + +The %(listname)s Archives + %(meta)s - - -

            The %(listname)s Archives

            -

            + + +

            The %(listname)s Archives

            +

            You can get more information about this list or you can download the full raw archive (%(size)s). @@ -16,5 +80,5 @@

            The %(listname)s Archives

            %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/en/archtocentry.html b/templates/en/archtocentry.html index 00cf9c47..6f2a92bb 100644 --- a/templates/en/archtocentry.html +++ b/templates/en/archtocentry.html @@ -1,12 +1,74 @@ - - - %(archivelabel)s: - - [ Thread ] - [ Subject ] - [ Author ] - [ Date ] - + + +%(archivelabel)s: + +[ Thread ] +[ Subject ] +[ Author ] +[ Date ] + %(textlink)s - diff --git a/templates/en/archtocnombox.html b/templates/en/archtocnombox.html index e2c5b3d4..6d17209e 100644 --- a/templates/en/archtocnombox.html +++ b/templates/en/archtocnombox.html @@ -1,18 +1,82 @@ - - - The %(listname)s Archives - + + + +The %(listname)s Archives + %(meta)s - - -

            The %(listname)s Archives

            -

            + + +

            The %(listname)s Archives

            +

            You can get more information about this list.

            %(noarchive_msg)s %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/en/article.html b/templates/en/article.html index beb361fe..f557959d 100644 --- a/templates/en/article.html +++ b/templates/en/article.html @@ -1,12 +1,13 @@ - - - %(title)s - - - - - + +

            The %(listname)s Archives

            +

            No messages have been posted to this list yet, so the archives are currently empty. You can get more information about this list.

            - - + + diff --git a/templates/en/headfoot.html b/templates/en/headfoot.html index 5baff562..d916877e 100644 --- a/templates/en/headfoot.html +++ b/templates/en/headfoot.html @@ -1,10 +1,73 @@ -This text can include +This text can include Python format strings which are resolved against list attributes. The list of substitutions allowed are:
              -
            • real_name - The "pretty" name of the list; usually +
            • real_name - The "pretty" name of the list; usually the list name with capitalization.
            • list_name - The name by which the list is @@ -25,4 +88,4 @@ list.
            • cgiext - The extension added to CGI scripts. -
            +
          diff --git a/templates/en/listinfo.html b/templates/en/listinfo.html index 7a27fbdd..a40f5e79 100644 --- a/templates/en/listinfo.html +++ b/templates/en/listinfo.html @@ -1,148 +1,209 @@ - - - - <MM-List-Name> Info Page - - - -

          - - - - - - - - - - - - - - - - - - - + + + + + +

          - -- - -
          -

            -

          - About - - - -
          -

          -

          To see the collection of prior postings to the list, - visit the - Archives. - -

          -
          - Using -
          + + + +<mm-list-name> Info Page</mm-list-name> + + +

          + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + + + + + + + +
          + -- + +
          +

            +

          +About + + + +
          +

          +

          To see the collection of prior postings to the list, + visit the + Archives. + +

          +
          +Using +
          To post a message to all the list members, send email to - . + .

          You can subscribe to the list, or change your existing subscription, in the sections below. -

          - Subscribing to -
          -

          - Subscribe to by filling out the following +

          +Subscribing to +
          +

          + Subscribe to by filling out the following form. - -

            - - - - - - - - - - - - - - - - - -
            Your email address: -  
            Your name (optional): 
            You may enter a + +
              + + + + + + + + + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - - - -
              Your email address: + 
              Your name (optional): 
              You may enter a privacy password below. This provides only mild security, but should prevent others from messing with your subscription. Do not use a valuable password as it will occasionally be emailed back to you in cleartext. -

              If you choose not to enter a password, one will be +

              If you choose not to enter a password, one will be automatically generated for you, and it will be sent to you once you've confirmed your subscription. You can always request a mail-back of your password when you edit your personal options. - -
              -
              Pick a password: 
              Reenter password to confirm: 
              Which language do you prefer to display your messages?  
              Would you like to receive list mail batched in a daily + + +
              Pick a password: 
              Reenter password to confirm: 
              Which language do you prefer to display your messages?  
              Would you like to receive list mail batched in a daily digest? No - Yes -
              -
              -
              - -
            -
            - - Subscribers -
            - - - -

            - - - -

            - - - +
          No + Yes +
          +
          +
          + + +

          + + Subscribers +
          + + + +

          + + + +

          + +

          + diff --git a/templates/en/options.html b/templates/en/options.html index 82af6eff..735517df 100644 --- a/templates/en/options.html +++ b/templates/en/options.html @@ -1,42 +1,102 @@ - - <MM-Presentable-User> membership configuration for <MM-List-Name> - - - - - -
          - - mailing list membership configuration for - -
          + +<mm-presentable-user> membership configuration for <mm-list-name> +</mm-list-name></mm-presentable-user> + + + + +
          + + mailing list membership configuration for + +

          - - - - - +
          - 's subscription status, - password, and options for the mailing list. -
          - - - - -

          -

          + + + +
          +'s subscription status, + password, and options for the mailing list. +
          + + +

          +

          - - +

          - - - + +
          - - Changing your membership information -
          You can change the address that you are subscribed + + + - - - - - -
          + +Changing your membership information +
          You can change the address that you are subscribed to the mailing list with by entering the new address in the fields below. Note that a confirmation email will be sent to the new address, and the change must be confirmed before it is @@ -51,204 +111,183 @@ lists that you are subscribed to at , turn on the Change globally check box. -
          - - - - - - - -
          New address:
          Again to - confirm:
          -
          - - +
          Your name +

          + + + + + + + +
          New address:
          Again to + confirm:
          +
          + + - - -
          Your name (optional):
          -
          -

          Change globally

          - +
          + +

          +

          Change globally

          +

          - - - - - - - - + + + + %(textlink)s - diff --git a/templates/eo/archtocnombox.html b/templates/eo/archtocnombox.html index f37d6849..ab64402e 100644 --- a/templates/eo/archtocnombox.html +++ b/templates/eo/archtocnombox.html @@ -1,18 +1,82 @@ - - - Arĥivoj de la listo %(listname)s - + + + +Arĥivoj de la listo %(listname)s + %(meta)s - - -

          Arĥivoj de la listo %(listname)s

          -

          + + +

          Arĥivoj de la listo %(listname)s

          +

          Vi povas legi plian informon pri tiu ĉi listo.

          %(noarchive_msg)s %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/eo/article.html b/templates/eo/article.html index b2ffe014..c3b7e9f6 100644 --- a/templates/eo/article.html +++ b/templates/eo/article.html @@ -1,12 +1,13 @@ - - - %(title)s - - - - - + +

          Arĥivoj de la listo %(listname)s

          +

          Neniu mesaÄo estas sendita tra ĉi tiu listo, tiel ke la arÄ¥ivoj estas nun malplenaj. Vi povas legi plian informon pri tiu ĉi listo.

          - - + + diff --git a/templates/eo/listinfo.html b/templates/eo/listinfo.html index 6c002c17..54603b94 100644 --- a/templates/eo/listinfo.html +++ b/templates/eo/listinfo.html @@ -1,138 +1,200 @@ + - InformopaÄo de <MM-List-Name> - - - -

          -

          - Unsubscribing from - Your other subscriptions -
          + + + + - + +
          +

          +Unsubscribing from +Your other subscriptions +
          Turn on the confirmation checkbox and hit this button to unsubscribe from this mailing list. Warning: This action will be taken immediately!

          -

          +

          You can view a list of all the other mailing lists at for which you are a member. Use this if you want to make the same membership option changes to this other subscriptions.

          -

          -
          - - - - - +
          - Your Password -
          - -
          -

          Forgotten Your Password?

          -
          + + + - -
          +Your Password +
          + +
          +

          Forgotten Your Password?

          +
          Click this button to have your password emailed to your membership address. -

          -

          - -
          -
          - -
          -

          Change Your Password

          - - - - - - - - -
          New - password:
          Again to - confirm:
          - - -

          Change globally. -
          -
          - +

          +

          + +
          +

          + +
          +

          Change Your Password

          + + + + + + + + +
          New + password:
          Again to + confirm:
          + +

          Change globally. +
          +

          - - +
          - Your Subscription Options -
          +
          +Your Subscription Options +
          -

          Current values are checked. -

          Note that some of the options have a Set globally checkbox. Checking this field will cause the changes to be made to every mailing list that you are a member of on . Click on List my other subscriptions above to see which other mailing lists you are subscribed to.

          - -
          - - Mail delivery

          + + - +

          - - - + +

          - - - - + - - + - - + - - - - + + - - + - - + - - - +

          +
          + +Mail delivery

          Set this option to Enabled to receive messages posted to this mailing list. Set it to Disabled if you want to stay subscribed, but don't want mail delivered to you for a while (e.g. you're going on vacation). If you disable mail delivery, don't forget to re-enable it when you come back; it will not be automatically re-enabled. -

          - Enabled
          - Disabled

          - Set globally -

          +Enabled
          +Disabled

          +Set globally +

          - Set Digest Mode

          +

          +Set Digest Mode

          If you turn digest mode on, you'll get posts bundled together (usually one per day but possibly more on busy lists), instead of singly when they're sent. If digest mode is changed from on to off, you may receive one last digest. -

          - Off
          - On -
          - Get MIME or Plain Text Digests?

          +

          +Off
          +On +
          +Get MIME or Plain Text Digests?

          Your mail reader may or may not support MIME digests. In general MIME digests are preferred, but if you have a problem reading them, select plain text digests. -

          - MIME
          - Plain Text

          - Set globally -

          +MIME
          +Plain Text

          +Set globally +

          - Receive your own posts to the list?

          +

          +Receive your own posts to the list?

          Ordinarily, you will get a copy of every message you post to the list. If you don't want to receive this copy, set this option to No. -

          - No
          - Yes -
          - Receive acknowledgement mail when you send mail to +

          +No
          +Yes +
          +Receive acknowledgement mail when you send mail to the list?

          -

          - No
          - Yes -
          - Get password reminder email for this list?

          +

          +No
          +Yes +
          +Get password reminder email for this list?

          Once a month, you will get an email containing a password reminder for every list at this host to which you are subscribed. You can turn this off on a per-list basis by selecting No for this option. If you turn off password reminders for all the lists you are subscribed to, no reminder email will be sent to you. -

          - No
          - Yes

          - Set globally -

          - Conceal yourself from subscriber list?

          +

          +No
          +Yes

          +Set globally +

          +Conceal yourself from subscriber list?

          When someone views the list membership, your email address is normally shown (in an obscured fashion to thwart spam harvesters). If you do not want your email address to show up on this membership roster at all, select Yes for this option. -

          - No
          - Yes -
          - What language do you prefer?

          -

          - -
          - Which topic categories would you like to subscribe +

          +No
          +Yes +
          +What language do you prefer?

          +

          + +
          +Which topic categories would you like to subscribe to?

          By selecting one or more topics, you can filter the traffic on the mailing list, so as to receive only a @@ -260,12 +299,11 @@

          Change Your Password

          rule depends on the setting of the option below. If you do not select any topics of interest, you will get all the messages sent to the mailing list. -
          - -
          - Do you want to receive messages that do not match any +

          + +
          +Do you want to receive messages that do not match any topic filter?

          This option only takes effect if you've subscribed to @@ -279,13 +317,12 @@

          Change Your Password

          If no topics of interest are selected above, then you will receive every message sent to the mailing list. -

          - No
          - Yes -
          - Avoid duplicate copies of messages?

          +

          +No
          +Yes +
          +Avoid duplicate copies of messages?

          When you are listed explicitly in the To: or Cc: headers of a list message, you can opt to @@ -298,21 +335,17 @@

          Change Your Password

          will have a X-Mailman-Copy: yes header added to it. -
          - No
          - Yes

          - Set globally -

          -
          -
          +No
          +Yes

          +Set globally +

          +
          +
          -

          - - - - + + +

          diff --git a/templates/en/private.html b/templates/en/private.html index fa7bfeda..ae001315 100755 --- a/templates/en/private.html +++ b/templates/en/private.html @@ -1,34 +1,95 @@ - %(realname)s Private Archives Authentication +%(realname)s Private Archives Authentication - - -
          + + + %(message)s - - - - - - - - - - - - - - - -
          - %(realname)s Private - Archives Authentication -
          Email address:
          Password:
          -
          -

          Important: From this point on, you + + + + + + + + + + + + + + + +
          +%(realname)s Private + Archives Authentication +
          Email address:
          Password:
          +
          +

          Important: From this point on, you must have cookies enabled in your browser, otherwise you will have to re-authenticate with every operation. @@ -40,21 +101,21 @@ member options page and clicking the Log out button.

          - - - - - - + + + +
          - Password Reminder -
          If you don't remember your password, enter your email address + + + + + + - - - - -
          +Password Reminder +
          If you don't remember your password, enter your email address above and click the Remind button and your password will be emailed to you.
          - +
          +

          diff --git a/templates/en/roster.html b/templates/en/roster.html index be3d1c89..e431e505 100644 --- a/templates/en/roster.html +++ b/templates/en/roster.html @@ -1,53 +1,112 @@ - - - <MM-List-Name> Subscribers - - - - -

          - - - - - - - - - - - - - - - -
          - - Subscribers -
          - -

          -

          - -

          Click on your address to visit your subscription - options page.
          (Parenthesized entries have list delivery - disabled.)

          -
          -
          - - Non-digested Members of : -
          -
          -
          - Digested - Members of : -
          -
          -

          -

          -

          -

          - - - + + +<mm-list-name> Subscribers</mm-list-name> + + +

          + + + + + + + + + + + + + + + +
          + + Subscribers +
          +

          +

          +

          Click on your address to visit your subscription + options page.
          (Parenthesized entries have list delivery + disabled.)

          +
          +
          + + Non-digested Members of : +
          +
          +
          + Digested + Members of : +
          +
          +

          +

          +

          +

          + +

          + diff --git a/templates/en/subscribe.html b/templates/en/subscribe.html index 197e6366..55e8f7a7 100644 --- a/templates/en/subscribe.html +++ b/templates/en/subscribe.html @@ -1,9 +1,72 @@ -<MM-List-Name> Subscription results +<mm-list-name> Subscription results</mm-list-name> -

          Subscription results

          - - - +

          Subscription results

          + + + diff --git a/templates/eo/admlogin.html b/templates/eo/admlogin.html index 228ac325..d4a12b55 100644 --- a/templates/eo/admlogin.html +++ b/templates/eo/admlogin.html @@ -1,31 +1,92 @@ - Ensaluto de %(who)s en la liston %(listname)s +Ensaluto de %(who)s en la liston %(listname)s - - -
          + + + %(message)s - - - - - - - - - - - -
          - Ensaluto de %(who)s en la liston %(listname)s -
          Pasvorto de %(who)s en la listo:
          -
          -

          Grava: Ekde tiu ĉi paÄo, vi devas aktivigi kuketojn en via retumilo; se ne, neniun ÅanÄon vi povos efektivigi. + + + + + + + + + + + +
          +Ensaluto de %(who)s en la liston %(listname)s +
          Pasvorto de %(who)s en la listo:
          +
          +

          Grava: Ekde tiu ĉi paÄo, vi devas aktivigi kuketojn en via retumilo; se ne, neniun ÅanÄon vi povos efektivigi.

          La mastruma interfaco de Mailman uzas seanco-kuketojn por ke vi ne devu reensaluti ĉe ĉiu mastrum-agado. Tiu kuketo kadukiÄos aÅ­tomate kiam vi fermos vian retumilon, aÅ­ alie vi povos mem kadukigi Äin premante la ligilon Elsalutu sub Aliaj mastrumaj agadoj (kiun vi vidos ensalutinte). -

          +

          diff --git a/templates/eo/archidxentry.html b/templates/eo/archidxentry.html index f9bb57aa..5c620b20 100644 --- a/templates/eo/archidxentry.html +++ b/templates/eo/archidxentry.html @@ -1,4 +1,68 @@ -
        • %(subject)s -  -%(author)s - +
        • %(subject)s +  +%(author)s + +
        • \ No newline at end of file diff --git a/templates/eo/archidxfoot.html b/templates/eo/archidxfoot.html index 61821ff9..bbf2af73 100644 --- a/templates/eo/archidxfoot.html +++ b/templates/eo/archidxfoot.html @@ -1,20 +1,84 @@ - +

          - Dato de la lasta mesaÄo: - %(lastdate)s
          - Dato de enarĥivigo: %(archivedate)s -

          -

          +

          +


          +Ĉi tiun arĥivon kreis Pipermail %(version)s. + + +

          \ No newline at end of file diff --git a/templates/eo/archidxhead.html b/templates/eo/archidxhead.html index 6c7ce393..c3188cf0 100644 --- a/templates/eo/archidxhead.html +++ b/templates/eo/archidxhead.html @@ -1,24 +1,89 @@ - - - Arĥivoj de %(archive)s de la listo %(listname)s laŭ %(archtype)s - + + + +Arĥivoj de %(archive)s de la listo %(listname)s laŭ %(archtype)s + %(encoding)s - - - -

          Arĥivoj de %(archive)s laŭ %(archtype)s

          -
            -
          • MesaÄoj ordigitaj laÅ­: + + + +

            Arĥivoj de %(archive)s laŭ %(archtype)s

            + -

            Komenco: %(firstdate)s
            - Fino: %(lastdate)s
            - MesaÄoj: %(size)s

            -

              + +
            +

            Komenco: %(firstdate)s
            +Fino: %(lastdate)s
            +MesaÄoj: %(size)s

            +

              +

            \ No newline at end of file diff --git a/templates/eo/archlistend.html b/templates/eo/archlistend.html index 9bc052dd..2e1191b0 100644 --- a/templates/eo/archlistend.html +++ b/templates/eo/archlistend.html @@ -1 +1,64 @@ -
          + diff --git a/templates/eo/archliststart.html b/templates/eo/archliststart.html index cd5ba610..97cf7658 100644 --- a/templates/eo/archliststart.html +++ b/templates/eo/archliststart.html @@ -1,4 +1,68 @@ - - - - +
          ArÄ¥ivoVidu laÅ­:DeÅutebla versio
          + + + +
          ArÄ¥ivoVidu laÅ­:DeÅutebla versio
          \ No newline at end of file diff --git a/templates/eo/archtoc.html b/templates/eo/archtoc.html index 287bcb31..350601c2 100644 --- a/templates/eo/archtoc.html +++ b/templates/eo/archtoc.html @@ -1,13 +1,77 @@ - - - Arĥivoj de la listo %(listname)s - + + + +Arĥivoj de la listo %(listname)s + %(meta)s - - -

          Arĥivoj de la listo %(listname)s

          -

          + + +

          Arĥivoj de la listo %(listname)s

          +

          Vi povas legi plian informon pri tiu ĉi listo aÅ­ deÅuti la tutan krudan arÄ¥ivon (%(size)s). @@ -16,5 +80,5 @@

          Arĥivoj de la listo %(listname)s

          %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/eo/archtocentry.html b/templates/eo/archtocentry.html index 1a83e728..37941f45 100644 --- a/templates/eo/archtocentry.html +++ b/templates/eo/archtocentry.html @@ -1,12 +1,74 @@ - -
          %(archivelabel)s: - [ Diskutfadeno ] - [ Temo ] - [ AÅ­toro ] - [ Dato ] -
          %(archivelabel)s: +[ Diskutfadeno ] +[ Temo ] +[ AÅ­toro ] +[ Dato ] +
          - - - - - - - - - - - - - - - - - - + + + + + +

          - -- -
          -

           

          -
          - Pri la listo - - - -
          -

          -

          Por vidi ĉiujn antaÅ­ajn mesaÄojn, vizitu la listarÄ¥ivon ArÄ¥ivoj de - . - -

          -
          Uzo de -
          +InformopaÄo de <mm-list-name></mm-list-name> + + +

          + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + +
          + -- +
          +

           

          +
          +Pri la listo + + + +
          +

          +

          Por vidi ĉiujn antaÅ­ajn mesaÄojn, vizitu la listarÄ¥ivon ArÄ¥ivoj de +. + +

          +
          Uzo de +
          Por sendi retpoÅton al ĉiuj listanoj, sendu Äin al - . + .

          Vi povas aboni la liston aŭ agordi vian abonon per la ĉi-subaj sekcioj.

          -
          -Aboni la liston -
          -

          - Abonu la liston plenigante la ĉi-suban abonilon. - -

            - - - - - - - - - - - - - - + + + + + + - - - - - -
            Via retpoÅtadreso: -  
            Via nomo (nedevige): 
            Pro privateco, vi devas enigi pasvorton. Tio ne tute sekurigas vian konton, tamen plej verÅajne malhelpos iun ajn ÅanÄi viajn abonojn. +
            +Aboni la liston +
            +

            + Abonu la liston plenigante la ĉi-suban abonilon. + +

              + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - - - - - -
              Via retpoÅtadreso: + 
              Via nomo (nedevige): 
              Pro privateco, vi devas enigi pasvorton. Tio ne tute sekurigas vian konton, tamen plej verÅajne malhelpos iun ajn ÅanÄi viajn abonojn. Ne elektu jam uzitan pasvorton ĉar foje Äi estos malkaÅe resendata al vi. -

              Se vi neniun elektos, aÅ­tomate kreitan pasvorton vi retpoÅte ricevos post konfirmo de via abonpeto. Vi ĉiam povos peti resendon de via pasvorto en via agordo-paÄo. -

              -
              Enigu pasvorton: 
              Konfirmu pasvorton: 
              Kiun lingvon vi preferas?  
              Ĉu vi deziras ricevi la poÅton de la listo kompilita en ĉiutaga resumo? - Ne - Jes +

              Se vi neniun elektos, aÅ­tomate kreitan pasvorton vi retpoÅte ricevos post konfirmo de via abonpeto. Vi ĉiam povos peti resendon de via pasvorto en via agordo-paÄo. +

              +
              Enigu pasvorton: 
              Konfirmu pasvorton: 
              Kiun lingvon vi preferas?  
              Ĉu vi deziras ricevi la poÅton de la listo kompilita en ĉiutaga resumo?
              -
              -
              - -
            -
            - - Abonantoj de - -
            - - - -

            - - - -

            - - +
          Ne + Jes +
          +
          +
          + + +

          + +Abonantoj de + +
          + + + +

          + + + +

          + +

          diff --git a/templates/eo/options.html b/templates/eo/options.html index 64b2de1a..ad7f412e 100644 --- a/templates/eo/options.html +++ b/templates/eo/options.html @@ -1,256 +1,287 @@ - - Agordoj de la abono de <MM-Presentable-User> je la listo <MM-List-Name> - - - - - -
          - - Agordoj de la abono de je la listo - -
          + +Agordoj de la abono de <mm-presentable-user> je la listo <mm-list-name> +</mm-list-name></mm-presentable-user> + + + + +
          + + Agordoj de la abono de je la listo + +

          - - - - - +
          - Abonstato, pasvorto kaj agordoj de je la listo . -
          - - - - -

          -

          + + + +
          + Abonstato, pasvorto kaj agordoj de je la listo . +
          + + +

          +

          - - +

          - - - + +
          - - Kiel ÅanÄi la informon de via abono je la listo -
          Vi povas ÅanÄi la retpoÅtadreson per kiu vi abonas la dissendoliston enskribante la novan adreson en la ĉi-suban kampon. Post tio, konfirmilo estos sendata al la nova adreso, kaj vi devos konfirmi la ÅanÄon antaÅ­ ol Äi efektiviÄos. + + + - - - - - -
          + +Kiel ÅanÄi la informon de via abono je la listo +
          Vi povas ÅanÄi la retpoÅtadreson per kiu vi abonas la dissendoliston enskribante la novan adreson en la ĉi-suban kampon. Post tio, konfirmilo estos sendata al la nova adreso, kaj vi devos konfirmi la ÅanÄon antaÅ­ ol Äi efektiviÄos.

          La konfirmiloj kadukiÄas post .

          Vi ankaÅ­ povas aldoni aÅ­ ÅanÄi vian nedevigan realan nomon (Ekzemple Ludoviko Ajnulo). -

          Se vi volas efektivigi ĉi tiujn ÅanÄojn je ĉiuj viaj listoj abonataj en , marku la konfirmujon ÅœanÄu ĉie.

          - -
          - - - - - - -
          Nova adreso:
          -
          Reenskribu por konfirmi Äin:
          -
          - - +
          Via nomo +

          Se vi volas efektivigi ĉi tiujn ÅanÄojn je ĉiuj viaj listoj abonataj en , marku la konfirmujon ÅœanÄu ĉie.

          +

          + + + + + + +
          Nova adreso:
          +
          Reenskribu por konfirmi Äin:
          +
          + + - - -
          Via nomo (nedevige):
          -
          -

          ÅœanÄu ĉie

          - +
          + +

          +

          ÅœanÄu ĉie

          +

          - - - - - - - - + + + + %(textlink)s - diff --git a/templates/es/archtocnombox.html b/templates/es/archtocnombox.html index 0de11605..fc67799f 100644 --- a/templates/es/archtocnombox.html +++ b/templates/es/archtocnombox.html @@ -1,18 +1,82 @@ - - - Archivos de %(listname)s - + + + +Archivos de %(listname)s + %(meta)s - - -

          Archivos de %(listname)s

          -

          - Puede obtener mas información sobre esta lista -

          + + +

          Archivos de %(listname)s

          +

          + Puede obtener mas información sobre esta lista +

          %(noarchive_msg)s %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/es/article.html b/templates/es/article.html index a7a414e1..7fa0c924 100644 --- a/templates/es/article.html +++ b/templates/es/article.html @@ -1,12 +1,13 @@ - - - %(title)s - - - - - + +

          Archivo de la lista %(listname)s

          +

          + Todavía no se ha enviado ningún mensaje a esta lista, de manera que el + archivo está actualmente vacío. Puede obtener + más información acerca de esta lista.

          - - + + diff --git a/templates/es/handle_opts.html b/templates/es/handle_opts.html index 42a11fb2..d6ca9764 100644 --- a/templates/es/handle_opts.html +++ b/templates/es/handle_opts.html @@ -1,9 +1,72 @@ - -<MM-List-Name> <MM-Operation> Resultados + +<mm-list-name> <mm-operation> Resultados</mm-operation></mm-list-name> -

          Resultados

          - - - +

          Resultados

          + + + diff --git a/templates/es/headfoot.html b/templates/es/headfoot.html index 135122e8..8ad1486d 100644 --- a/templates/es/headfoot.html +++ b/templates/es/headfoot.html @@ -1,32 +1,94 @@ -Este texto puede incluir +Este texto puede incluir cadenas de formato Python -que serán sustituidas por los atributos de la lista. La lista de +que serán sustituidas por los atributos de la lista. La lista de substituciones permitidas son:
            -
          • real_name - El nombre 'presentable' de la lista. - Suele ser el nombre de la lista con la primera letra en mayúscula. +
          • real_name - El nombre 'presentable' de la lista. + Suele ser el nombre de la lista con la primera letra en mayúscula.
          • list_name - El nombre por el cual se identifica la lista - en las URLs, se distinguen las mayúsculas de las minúsculas. + en las URLs, se distinguen las mayúsculas de las minúsculas. -
          • host_name - El nombre canónico del servidor que +
          • host_name - El nombre canónico del servidor que alberga la lista.
          • web_page_url - El URL base de Mailman. Por ejemplo - para indicar la página de información de la lista - solo habría que agregarle detrás:
            - listinfo/%(list_name)s + para indicar la página de información de la lista + solo habría que agregarle detrás:
            +listinfo/%(list_name)s +
          • description - Una descripción concisa sobre + la lista de distribución. -
          • description - Una descripción concisa sobre - la lista de distribución. +
          • info - La descripción completa sobre la + lista de distribución. -
          • info - La descripción completa sobre la - lista de distribución. - -
          • cgiext - La extensión que se agrega a los guiones +
          • cgiext - La extensión que se agrega a los guiones CGI. -
          + diff --git a/templates/es/listinfo.html b/templates/es/listinfo.html index 3b54a1ce..9248e556 100644 --- a/templates/es/listinfo.html +++ b/templates/es/listinfo.html @@ -1,150 +1,211 @@ - - - - Página de Información de <MM-List-Name> - - - -

          -

          - Kiel malaboni la liston - Viaj aliaj abonoj en . -
          + + + + - + +
          +

          +Kiel malaboni la liston +Viaj aliaj abonoj en . +
          Marku la konfirmujon kaj premu la butonon por malaboni ĉi tiun dissendoliston. Averto: ĉi tiu ÅanÄo efektiviÄos tuj!

          -

          - Vi povas vidi liston de la aliaj dissendolistoj kiujn vi abonas en . Uzu tion se vi volas efektivigi la samajn reagordojn ankaÅ­ je tiuj abonoj. +

          + Vi povas vidi liston de la aliaj dissendolistoj kiujn vi abonas en . Uzu tion se vi volas efektivigi la samajn reagordojn ankaÅ­ je tiuj abonoj.

          -

          -
          - - - - - +
          - Via pasvorto en -
          - -
          -

          Ĉu vi forgesis la pasvorton?

          -
          + + + - -
          +Via pasvorto en +
          + +
          +

          Ĉu vi forgesis la pasvorton?

          +
          Premu ĉi tiun butonon por rericevi vian pasvorton per retpoÅto. -

          -

          - -
          -
          - -
          -

          ÅœanÄo de la pasvorto

          - - - - - - - - -
          Nova pasvorto:
          Konfirmu Äin:
          - - -

          ÅœanÄu ĉie -
          -
          - +

          +

          + +
          +

          + +
          +

          ÅœanÄo de la pasvorto

          + + + + + + + + +
          Nova pasvorto:
          Konfirmu Äin:
          + +

          ÅœanÄu ĉie +
          +

          - - +
          - Viaj abonagordoj en -
          +
          +Viaj abonagordoj en +
          - -

          +

          La nunaj valoroj estas markitaj. -

          Rimarku ke kelkaj agordoj havas konfirmujon nomatan ÅœanÄu ĉie. Se vi markos ĉi tiun konfirmujon la ÅanÄo efektiviÄos en ĉiuj dissendolistoj kiujn vi abonas en . Premu sur Listigu miajn aliajn abonojn -por vidi kiujn aliajn dissendolistojn vi abonas.

          +por vidi kiujn aliajn dissendolistojn vi abonas.

          - -
          - - PoÅtoliverado

          + + - +

          - - - - + + +

          - - - - - - - - + + + - - - - + + - - + - - + - - - +

          +
          + +PoÅtoliverado

          Elektu Aktiva'n por ricevi la poÅton sendatan al tiu ĉi listo. Elektu Malaktiva'n se vi volas plu aboni la liston sed ne volas ricevi poÅton dum iu tempo (ekzemple dum ferioj), aÅ­ volas nur sendi poÅton kaj legi la arÄ¥ivojn de la listo. Se vi malÅaltos poÅtoliveradon, memoru ke Äi ne reaktiviÄos aÅ­tomate. -

          - Aktiva
          - Malaktiva

          - ÅœanÄu ĉie

          -
          +Aktiva
          +Malaktiva

          +ÅœanÄu ĉie

          +
          - Åœaltu resum-modon

          +

          +Åœaltu resum-modon

          Se vi Åaltos la resum-modon, vi ricevos ĉiujn mesaÄojn kompilitaj en unu resumon (kutime po unu resumo tage, sed eble pli ofte en tre aktivaj listoj), anstataÅ­ ricevi unuopajn mesaÄojn po unu tuj kiam ili estas sendataj. Se vi malÅaltos la resum-modon, eblas ke vi ankoraÅ­ ricevos unu lastan resumon antaÅ­ ol komenci ricevi unuopajn mesaÄojn. -

          - Malaktiva
          - Aktiva
          - Ĉu vi preferas resumojn en aranÄo MIME aÅ­ en simpla teksto?

          +

          +Malaktiva
          +Aktiva
          +Ĉu vi preferas resumojn en aranÄo MIME aÅ­ en simpla teksto?

          Povas okazi ke via retpoÅtilo ne bone akceptos resumojn MIME. Tiuj estas preferindaj, sed se ili ne estos facile legeblaj, elektu simpla-tekstajn resumojn. -

          - MIME
          - Simpla teksto

          - ÅœanÄu ĉie -

          +MIME
          +Simpla teksto

          +ÅœanÄu ĉie +

          - Ĉu vi volas ricevi poÅton senditan de vi al la listo?

          +

          +Ĉu vi volas ricevi poÅton senditan de vi al la listo?

          Kutime, vi ricevos kopion de ĉiu mesaÄo kiun vi mem sendos al la listo. Se vi ne volas ricevi tiun kopion, elektu Ne'n. -

          - Ne
          - Jes -
          - Ĉu vi volas ricevi konfirmilon post kiam vi sendos poÅton al la listo?

          -

          - Ne
          - Jes -
          - Ĉu vi volas ricevi memorigilon de via pasvorto?

          - Unu fojon monate vi ricevos memorigilon kun la pasvortoj de ĉiu listo kiun vi abonas en . Vi povas malÅalti tiun memorigilon por ĉiu listo elektante Ne'n.

          -
          - Ne
          - Jes

          - ÅœanÄu ĉie -

          - Ĉu vi volas kaÅi vin en la listanaro?

          +

          +Ne
          +Jes +
          +Ĉu vi volas ricevi konfirmilon post kiam vi sendos poÅton al la listo?

          +

          +Ne
          +Jes +
          +Ĉu vi volas ricevi memorigilon de via pasvorto?

          + Unu fojon monate vi ricevos memorigilon kun la pasvortoj de ĉiu listo kiun vi abonas en . Vi povas malÅalti tiun memorigilon por ĉiu listo elektante Ne'n.

          +
          +Ne
          +Jes

          +ÅœanÄu ĉie +

          +Ĉu vi volas kaÅi vin en la listanaro?

          Kiam iu vidas la liston de abonantoj de tiu ĉi dissendolisto, via retpoÅtadreso estos videbla (en maniero iom maskita por kaÅi Äin de trudpoÅtistoj). Se vi ne deziras ke via adreso aperu sur la listanaro, elektu Jes 'on ĉi tie. -

          - Ne
          - Jes -
          - Kiun lingvon vi preferas?

          -

          - -
          - Kiujn temojn vi volas aboni?

          +

          +Ne
          +Jes +
          +Kiun lingvon vi preferas?

          +

          + +
          +Kiujn temojn vi volas aboni?

          Se vi elektos unu aÅ­ plurajn interestemojn, la poÅto de la listo estos filtrata kaj vi ricevos nur parton. Se mesaÄo konformas al iu temo elektita de vi, vi Äin ricevos; alie ne ricevos.

          Se mesaÄo konformas al neniu temo, Äian liveron regos la ĉi-suba agordo. Se vi elektos neniun temon, vi ricevos la tutan poÅton de la listo.

          -
          - -
          - Ĉu vi volas ricevi poÅton konforman al neniu temfiltro?

          +

          + +
          +Ĉu vi volas ricevi poÅton konforman al neniu temfiltro?

          Ĉi tiu agordo vin koncernas nur se vi abonis almenaÅ­ unu temon en la ĉi-supra agordo. Äœi priskribas la regulon por liveri poÅton konforman al neniu difinita temo. Elektinte Ne'n, vi ne ricevos poÅton kiu konformas al neniu temo, dum elektinte Jes'on Äin ja ricevos.

          - -

          Se neniun temon elektis supre, vi ricevos ĉiun poÅton de la listo. -

          - Ne
          - Jes -
          - Ĉu vi volas eviti duobligon de poÅto?

          +

          Se neniun temon elektis supre, vi ricevos ĉiun poÅton de la listo. +

          +Ne
          +Jes +
          +Ĉu vi volas eviti duobligon de poÅto?

          Vi povas elekti ne ricevi duan kopion de mesaÄo sendita tra la listo kiam via adreso aperas en Äiaj kampoj Al: (To:) aÅ­ Cc:. Elektu Jes'on por ne ricevi duoblaĵojn, aÅ­ elektu Ne'n por ricevi ilin.

          Se la listo estas agordita por personecigi poÅton kaj vi elektis ricevi duoblaĵojn, la kaplinio 'X-Mailman-Copy: yes' estos aldonita al tiuj kopioj. -

          - Ne
          - Jes

          - ÅœanÄu ĉie -

          -
          -
          +Ne
          +Jes

          +ÅœanÄu ĉie +

          +
          +
          -

          - - - - + + +

          diff --git a/templates/eo/private.html b/templates/eo/private.html index 01842b24..f1eeefee 100755 --- a/templates/eo/private.html +++ b/templates/eo/private.html @@ -1,49 +1,110 @@ - Ensaluto en la privatajn arĥivojn de %(realname)s - - - -
          +Ensaluto en la privatajn arĥivojn de %(realname)s + + + + %(message)s - - - - - - - - - - - - - - - -
          - Ensaluto en la privatajn arĥivojn de %(realname)s -
          RetpoÅtadreso:
          Pasvorto:
          -
          -

          Grava: Ekde tiu ĉi paÄo, vi devas aktivigi kuketojn en via retumilo; se ne, vi devos reensaluti ĉe ĉiu paÅo. + + + + + + + + + + + + + + + +
          +Ensaluto en la privatajn arĥivojn de %(realname)s +
          RetpoÅtadreso:
          Pasvorto:
          +
          +

          Grava: Ekde tiu ĉi paÄo, vi devas aktivigi kuketojn en via retumilo; se ne, vi devos reensaluti ĉe ĉiu paÅo.

          La privat-arÄ¥iva interfaco de Mailman uzas seanco-kuketojn por ke vi ne devu reensaluti ĉe ĉiu ago. Tiu kuketo kadukiÄos aÅ­tomate kiam vi fermos vian retumilon, aÅ­ alie vi povos mem kadukigi Äin premante la ligilon Elsalutu sub Aliaj mastrumaj agadoj (kiun vi vidos ensalutinte).

          - - - - - - - - - - -
          - Pasvorto-memorigo -
          Ĉu forgesis pasvorton? Enskribu vian retpoÅtadreson ĉi-sube kaj premu la butonon Memorigu, kaj via pasvorto estos sendata al vi.
          -

          + + + + + + + + + + +
          +Pasvorto-memorigo +
          Ĉu forgesis pasvorton? Enskribu vian retpoÅtadreson ĉi-sube kaj premu la butonon Memorigu, kaj via pasvorto estos sendata al vi.
          +

          diff --git a/templates/eo/roster.html b/templates/eo/roster.html index 7d6ac5bd..572a0895 100644 --- a/templates/eo/roster.html +++ b/templates/eo/roster.html @@ -1,49 +1,108 @@ - - - Abonantoj de la listo <MM-List-Name> - - - - -

          - - - - - - - - - - - - - - - -
          - Abonantoj de la listo - -
          - -

          -

          - -

          Premu vian adreson por viziti la agordopaÄon de via abono.
          (La adresoj inter krampoj havas poÅtoliveradon malÅaltita.)

          -
          -
          - Abonantoj de normala poÅto de : -
          -
          -
          - Abonantoj de ĉiutagaj resumoj de : -
          -
          -

          -

          -

          -

          - - - + + +Abonantoj de la listo <mm-list-name></mm-list-name> + + +

          + + + + + + + + + + + + + + + +
          +Abonantoj de la listo + +
          +

          +

          +

          Premu vian adreson por viziti la agordopaÄon de via abono.
          (La adresoj inter krampoj havas poÅtoliveradon malÅaltita.)

          +
          +
          +Abonantoj de normala poÅto de : +
          +
          +
          + Abonantoj de ĉiutagaj resumoj de : +
          +
          +

          +

          +

          +

          + +

          + diff --git a/templates/eo/subscribe.html b/templates/eo/subscribe.html index f129428b..763c82eb 100644 --- a/templates/eo/subscribe.html +++ b/templates/eo/subscribe.html @@ -1,9 +1,72 @@ -Rezultoj de la abono al <MM-List-Name> +Rezultoj de la abono al <mm-list-name></mm-list-name> -

          Rezultoj de la abono al

          - - - +

          Rezultoj de la abono al

          + + + diff --git a/templates/es/admindbdetails.html b/templates/es/admindbdetails.html index c099b116..07150ceb 100644 --- a/templates/es/admindbdetails.html +++ b/templates/es/admindbdetails.html @@ -1,63 +1,126 @@ -Las solicitudes administrativas se muestran de una de las dos formas, como una -página de resumen, o como una -página con detalles. La página con el sumario contiene solicitudes de -alta o baja pendientes, envíos a la lista que han sido retenidos para que -sean aprobados, agrupados por dirección de remitente. La página con los detalles -contiene una vista más detallada de cada uno de los mensajes retenidos, +Las solicitudes administrativas se muestran de una de las dos formas, como una +página de resumen, o como una +página con detalles. La página con el sumario contiene solicitudes de +alta o baja pendientes, envíos a la lista que han sido retenidos para que +sean aprobados, agrupados por dirección de remitente. La página con los detalles +contiene una vista más detallada de cada uno de los mensajes retenidos, junto con las cabeceras del mensaje y un extracto del cuerpo del mismo. -

          En cualquiera de estas páginas están disponibles las siguientes acciones: +

          En cualquiera de estas páginas están disponibles las siguientes acciones:

            -
          • Diferir -- Pospone su decisión para más tarde. No se aplica ninguna -acción ahora para esta solicitud administrativa pendiente, aún así puede reenviar -o preservar el mensaje (vea más abajo). +
          • Diferir -- Pospone su decisión para más tarde. No se aplica ninguna +acción ahora para esta solicitud administrativa pendiente, aún así puede reenviar +o preservar el mensaje (vea más abajo). -
          • Aprobar -- Aprobar el mensaje, enviándolo a la lista. Para las -solicitudes de subscripción, aprobar las mismas. +
          • Aprobar -- Aprobar el mensaje, enviándolo a la lista. Para las +solicitudes de subscripción, aprobar las mismas.
          • Rechazar -- Rechazar el mensaje, enviando un mensaje de rechazo al remitente, y descartando el mensaje original. Para las solicitudes de -subscripción, rechaza las mismas. En cualquier caso, deberia poner el motivo del +subscripción, rechaza las mismas. En cualquier caso, deberia poner el motivo del rechazo en el cuadro de texto adjunto.
          • Descartar -- Tirar el mensaje original, sin mandar un mensaje de -rechazo. Para las solicitudes de subscripción, esto únicamente descarta la -solicitud sin avisar a la persona que hizo la solicitud. Esta opción se usa +rechazo. Para las solicitudes de subscripción, esto únicamente descarta la +solicitud sin avisar a la persona que hizo la solicitud. Esta opción se usa normalmente con el correo no solicitado o spam. -
          - -

          Para los mensajes retenidos, active la opción Preservar si quiere + +

          Para los mensajes retenidos, active la opción Preservar si quiere guardar una copia de los mensajes para el administrador del servidor. Esto es -útil para aquellos mensajes abusivos que quieres descartar, pero que necesites -guardar un registro para ehcarle un vistazo después. +útil para aquellos mensajes abusivos que quieres descartar, pero que necesites +guardar un registro para ehcarle un vistazo después. -

          Active la opción Redirigir a y rellene la dirección correspondiente si -quiere reenviar el mensaje a alguien que no esté en la lista. Para editar un -mensaje retenido antes de que se envíe a la lista, puede reenviarselo a sí mismo +

          Active la opción Redirigir a y rellene la dirección correspondiente si +quiere reenviar el mensaje a alguien que no esté en la lista. Para editar un +mensaje retenido antes de que se envíe a la lista, puede reenviarselo a sí mismo (o al propietario de la lista), y descartar el mensaje original. Entonces, -cuando el mensaje se muestre en su buzón de entrada, haga sus correcciones y -reenvíelo a la lista, incluyendo una cabecera Approved: con la clave de +cuando el mensaje se muestre en su buzón de entrada, haga sus correcciones y +reenvíelo a la lista, incluyendo una cabecera Approved: con la clave de la lista como valor. Es de buena costumbre en este caso, incluir una nota en el mensaje explicando que ha modificado el texto. -

          Si el remitente es un subscriptor que está siendo moderado, puede limpiar -opcionalmente su marca de moderación. Esto es útil cuando su lista está +

          Si el remitente es un subscriptor que está siendo moderado, puede limpiar +opcionalmente su marca de moderación. Esto es útil cuando su lista está configurada para poner a los subscriptores nuevos en cuarentena y decide que puede confiar en este subscriptor a la hora de mandar mensajes a la lista sin -aprobación. - -

          Si el remitente no es un subscriptor de la lista, puede añadir esta dirección -de correo electrónico al filtrado de remitentes. El filtrado de -remitentes se describen en la página de fitros de -privacidad, y puede ser aceptar automáticamente, retener -automáticamente, rechazar automáticamente o descartar -automáticamente. Esta opción no estará disponible si la dirección ya está en +aprobación. + +

          Si el remitente no es un subscriptor de la lista, puede añadir esta dirección +de correo electrónico al filtrado de remitentes. El filtrado de +remitentes se describen en la página de fitros de +privacidad, y puede ser aceptar automáticamente, retener +automáticamente, rechazar automáticamente o descartar +automáticamente. Esta opción no estará disponible si la dirección ya está en uno de los filtros de remitente. -

          Cuando haya terminado, dele con el ratón en el botón Enviar todos los -datos del final de la página. Este botón entregará todas las acciones +

          Cuando haya terminado, dele con el ratón en el botón Enviar todos los +datos del final de la página. Este botón entregará todas las acciones seleccionadas de las solicitudes administrativas para las que haya indicado -alguna selección. +alguna selección. -

          Regresar a la página con los sumarios. +

          Regresar a la página con los sumarios. +

          \ No newline at end of file diff --git a/templates/es/admindbpreamble.html b/templates/es/admindbpreamble.html index aea848ae..1c251f33 100644 --- a/templates/es/admindbpreamble.html +++ b/templates/es/admindbpreamble.html @@ -1,10 +1,74 @@ -Esta página contiene un subconjunto de los envíos a la -lista de distribución %(listname)s que requieren +Esta página contiene un subconjunto de los envíos a la +lista de distribución %(listname)s que requieren de su conformidad. Actualmente muestra %(description)s -

          Para cada solicitud administrativa, por favor seleccione la acción a tomar -pulsando con el ratón en mandar todos los datos una vez que termine. Puede -encontrar información adicional aquí. +

          Para cada solicitud administrativa, por favor seleccione la acción a tomar +pulsando con el ratón en mandar todos los datos una vez que termine. Puede +encontrar información adicional aquí. -

          También puede ver un resumen de todas las +

          También puede ver un resumen de todas las solicitudes pendientes. +

          \ No newline at end of file diff --git a/templates/es/admindbsummary.html b/templates/es/admindbsummary.html index a8654c01..eaa0cae7 100644 --- a/templates/es/admindbsummary.html +++ b/templates/es/admindbsummary.html @@ -1,13 +1,77 @@ -Esta página contiene un sumario de las solicitudes administrativas que -requieren de su aprobación para la lista de -distribución %(listname)s. +Esta página contiene un sumario de las solicitudes administrativas que +requieren de su aprobación para la lista de +distribución %(listname)s. -Lo primero que encontrará serán las solicitudes de alta y baja en la lista (si las hubiera), -seguida por cualquier envío a la lista pendiente de su aprobación. +Lo primero que encontrará serán las solicitudes de alta y baja en la lista (si las hubiera), +seguida por cualquier envío a la lista pendiente de su aprobación. -

          Seleccione la acción a tomar para cada una de las solicitudes administrativas y -pulse en el botón Enviar todos los datos cuando haya terminado. -Hay disponibles instrucciones más detalladas. +

          Seleccione la acción a tomar para cada una de las solicitudes administrativas y +pulse en el botón Enviar todos los datos cuando haya terminado. +Hay disponibles instrucciones más detalladas. -

          Igualmente, también puede ver los detalles de todos los -envíos retenidos. +

          Igualmente, también puede ver los detalles de todos los +envíos retenidos. +

          \ No newline at end of file diff --git a/templates/es/admlogin.html b/templates/es/admlogin.html index 205268c3..72995cd3 100755 --- a/templates/es/admlogin.html +++ b/templates/es/admlogin.html @@ -1,38 +1,99 @@ - Autentificación del %(who)s de %(listname)s +Autentificación del %(who)s de %(listname)s - - -
          + + + %(message)s - - - - - - - - - - - -
          - - Autentificación del %(who)s de %(listname)s -
          Clave del %(who)s de la lista:
          -
          -

          Importante: A partir de ahora, debe + + + + + + + + + + + +
          + + Autentificación del %(who)s de %(listname)s +
          Clave del %(who)s de la lista:
          +
          +

          Importante: A partir de ahora, debe tener habilitadas las cookies en su navegador, en caso contrario, sus cambios - no tendrán efecto. + no tendrán efecto.

          La interfaz administrativa de Mailman emplea sesiones basadas en cookies, de -manera que no necesita identificarse continuamente con cada operación administrativa que -realice. La cookie caducará automáticamente cuando salga del navegador, or puede hacerla caducar seleccionando la opción Salida bajo la sección titulada - Otras Actividades Administrativas (que verá una vez que consiga entrar +manera que no necesita identificarse continuamente con cada operación administrativa que +realice. La cookie caducará automáticamente cuando salga del navegador, or puede hacerla caducar seleccionando la opción Salida bajo la sección titulada + Otras Actividades Administrativas (que verá una vez que consiga entrar satisfactoriamente). -

          +

          diff --git a/templates/es/archidxentry.html b/templates/es/archidxentry.html index f9bb57aa..5c620b20 100644 --- a/templates/es/archidxentry.html +++ b/templates/es/archidxentry.html @@ -1,4 +1,68 @@ -
        • %(subject)s -  -%(author)s - +
        • %(subject)s +  +%(author)s + +
        • \ No newline at end of file diff --git a/templates/es/archidxfoot.html b/templates/es/archidxfoot.html index 368cbd65..d494d8e2 100644 --- a/templates/es/archidxfoot.html +++ b/templates/es/archidxfoot.html @@ -1,20 +1,84 @@ - -

          - Fecha del último mensaje: - %(lastdate)s
          - Archivado en: %(archivedate)s -

          -

            -
          • Mensages ordenados por: + +

            +Fecha del último mensaje: +%(lastdate)s
            +Archivado en: %(archivedate)s +

            +

            -

            -


            - Archivo generado por Pipermail %(version)s. - - +
          +

          +


          +Archivo generado por Pipermail %(version)s. + + +

          \ No newline at end of file diff --git a/templates/es/archidxhead.html b/templates/es/archidxhead.html index eb7ad760..5e6514fa 100644 --- a/templates/es/archidxhead.html +++ b/templates/es/archidxhead.html @@ -1,24 +1,89 @@ - - - Archivos de %(listname)s %(archive)s por %(archtype)s - + + + +Archivos de %(listname)s %(archive)s por %(archtype)s + %(encoding)s - - - -

          Archivos de %(listname)s %(archive)s por %(archtype)s

          -
            -
          • Mensajes ordenados por: + + + +

            Archivos de %(listname)s %(archive)s por %(archtype)s

            + -

            Desde: %(firstdate)s
            - Hasta: %(lastdate)s
            - Mensajes: %(size)s

            -

              +
            +

            Desde: %(firstdate)s
            +Hasta: %(lastdate)s
            +Mensajes: %(size)s

            +

              +

            \ No newline at end of file diff --git a/templates/es/archlistend.html b/templates/es/archlistend.html index 9bc052dd..2e1191b0 100644 --- a/templates/es/archlistend.html +++ b/templates/es/archlistend.html @@ -1 +1,64 @@ -
          + diff --git a/templates/es/archliststart.html b/templates/es/archliststart.html index 509b6e88..7ff22f29 100644 --- a/templates/es/archliststart.html +++ b/templates/es/archliststart.html @@ -1,4 +1,68 @@ - - - - +
          ArchivoVer por:Version para bajar a su ordenador
          + + + +
          ArchivoVer por:Version para bajar a su ordenador
          \ No newline at end of file diff --git a/templates/es/archtoc.html b/templates/es/archtoc.html index 8c9d25b1..a6678136 100644 --- a/templates/es/archtoc.html +++ b/templates/es/archtoc.html @@ -1,14 +1,78 @@ - - - Archivos de %(listname)s - + + + +Archivos de %(listname)s + %(meta)s - - -

          Archivos de %(listname)s

          -

          - Puede obtener mas información sobre esta lista + + +

          Archivos de %(listname)s

          +

          + Puede obtener mas información sobre esta lista o puede bajar a su ordenador todo el archivo (%(size)s).

          @@ -16,5 +80,5 @@

          Archivos de %(listname)s

          %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/es/archtocentry.html b/templates/es/archtocentry.html index 24c9342c..ccbc11cf 100644 --- a/templates/es/archtocentry.html +++ b/templates/es/archtocentry.html @@ -1,12 +1,74 @@ - -
          %(archivelabel)s: - [ Hilo ] - [ Tema ] - [ Autor ] - [ Fecha ] -
          %(archivelabel)s: +[ Hilo ] +[ Tema ] +[ Autor ] +[ Fecha ] +
          - - - - - - - - - - - - - - - - - - + + + + + +

          - -- - -
          -

            -

          - Sobre - - - -
          -

          -

          Para ver envíos anteriores a la lista, - puede visitar los archivos de - . - -

          -
          - Como usar la lista -
          + + + +Página de Información de <mm-list-name></mm-list-name> + + +

          + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + + + + + + + +
          + -- + +
          +

            +

          +Sobre + + + +
          +

          +

          Para ver envíos anteriores a la lista, + puede visitar los archivos de +. + +

          +
          +Como usar la lista +
          Para enviar un mensaje a todos los miembros de la lista, - envíelo a la dirección - . + envíelo a la dirección + .

          Puede usted subscribirse a la lista, o cambiar su - subscripción, en las siguientes secciones. -

          - Subscribirse a -
          -

          - Suscríbase a rellenando los datos del + subscripción, en las siguientes secciones. +

          +Subscribirse a +
          +

          + Suscríbase a rellenando los datos del siguiente formulario - -

            - - - - - - - - - - - - - - - - - -
            Dirección de correo electrónico: -  
            Su nombre (opcional): 
            Debe introducir una clave de - protección. Esto le da un bajo nivel de seguridad, - pero debería evitar que otros enreden con su - subscripción. No utilice claves valiosas porque + +
              + + + + + + + + + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - - - -
              Dirección de correo electrónico: + 
              Su nombre (opcional): 
              Debe introducir una clave de + protección. Esto le da un bajo nivel de seguridad, + pero debería evitar que otros enreden con su + subscripción. No utilice claves valiosas porque puede que se le mande alguna vez sin cifrar por correo - electrónico. + electrónico. -

              Si decide no escribir ninguna clave, se le generará - una automáticamente y se le enviará una vez que - confirme su subscripción. Siempre podrá pedir - que se le envíe por correo su clave cuando edite sus +

              Si decide no escribir ninguna clave, se le generará + una automáticamente y se le enviará una vez que + confirme su subscripción. Siempre podrá pedir + que se le envíe por correo su clave cuando edite sus opciones personales - -
              -
              Elija una clave: 
              Confirme la clave: 
              ¿En qué idioma desea visualizar sus mensajes?  
              ¿Desea recibir los mensaje de cada día reunidos - en un único mensaje (digest)? + + +
              Elija una clave: 
              Confirme la clave: 
              ¿En qué idioma desea visualizar sus mensajes?  
              ¿Desea recibir los mensaje de cada día reunidos + en un único mensaje (digest)? No - Sí -
              -
              -
              - -
            -
            - - Suscriptores de -
            - - - -

            - - - -

            - - - +
          No + Sí +
          +
          +
          + + +

          + +Suscriptores de +
          + + + +

          + + + +

          + +

          + diff --git a/templates/es/options.html b/templates/es/options.html index d96c0883..ddfe4e83 100644 --- a/templates/es/options.html +++ b/templates/es/options.html @@ -1,328 +1,361 @@ - - <MM-Presentable-User> membership configuration for <MM-List-Name> - - - - - -
          - - Configuración del usuario - -
          + +<mm-presentable-user> membership configuration for <mm-list-name> +</mm-list-name></mm-presentable-user> + + + + +
          + + Configuración del usuario + +

          - - - - - +
          - Opciones de subscripción de - en la lista de distribución . -
          - - - - -

          -

          + + + +
          + Opciones de subscripción de + en la lista de distribución . +
          + + +

          +

          - - +

          - - - + +
          - - Para cambiar la información con la que está subscrito a -
          Puede cambiar la dirección con la que - está subscrito a la lista de distribución, - introduciendo una dirección nueva en el campo que aparece - más abajo. Se le mandará un mensaje de confirmación - a la dirección que especifique, ya que los cambios no se - serán efectivos hasta que lo confirme. + + + - - - - - -
          + +Para cambiar la información con la que está subscrito a +
          Puede cambiar la dirección con la que + está subscrito a la lista de distribución, + introduciendo una dirección nueva en el campo que aparece + más abajo. Se le mandará un mensaje de confirmación + a la dirección que especifique, ya que los cambios no se + serán efectivos hasta que lo confirme. -

          Las confirmaciones caducan después de . +

          Las confirmaciones caducan después de .

          Opcionalmente, puede poner o cambiar su nombre y apellidos - (por ejemplo Pepe Pérez). + (por ejemplo Pepe Pérez). -

          Si quiere aplicar los modificaciones de subscripción a - todas las listas en los que está subscrito en , - habilite la casilla de verificación Cambiar globalmente. +

          Si quiere aplicar los modificaciones de subscripción a + todas las listas en los que está subscrito en , + habilite la casilla de verificación Cambiar globalmente. -

          - - - - - - - -
          Dirección nueva:
          De nuevo para confirmar:
          -
          - - +
          Su nombre +

          + + + + + + + +
          Dirección nueva:
          De nuevo para confirmar:
          +
          + + - - -
          Su nombre (opcional):
          -
          -

          Cambiar globalmente

          - +
          + +

          +

          Cambiar globalmente

          +

          - - - - - - + +

          -

          - Para anular la subscripción de - El resto de las subscripciones que tiene en -
          - Seleccione la casilla de confirmación y pulse este botón - para anular su subscripción de esta lista de distribución. + + + + - + +
          +

          +Para anular la subscripción de +El resto de las subscripciones que tiene en +
          + Seleccione la casilla de confirmación y pulse este botón + para anular su subscripción de esta lista de distribución. Advertencia: - ¡Esta acción tendrá efecto inmediato! + ¡Esta acción tendrá efecto inmediato!

          -

          - Puede ver un informe del resto de las listas de distribución de - en las que está subscrito. Utilice esta parte si +

          + Puede ver un informe del resto de las listas de distribución de + en las que está subscrito. Utilice esta parte si quiere aplicar cambios globales al resto de sus subscripciones.

          -

          -
          - - - - - +
          - Su clave para -
          - -
          -

          ¿Ha olvidado su clave?

          -
          - Pulse este botón para hacer que se le envíe por correo - electrónico a la dirección con la que está + + + - -
          +Su clave para +
          + +
          +

          ¿Ha olvidado su clave?

          +
          + Pulse este botón para hacer que se le envíe por correo + electrónico a la dirección con la que está subscrito. -

          -

          - -
          -
          - -
          -

          Para cambiar su clave

          - - - - - - - - -
          Clave - nueva:
          De nuevo para - confirmar:
          - - -

          Cambiar globalmente. -
          -
          - +

          +

          + +
          +

          + +
          +

          Para cambiar su clave

          + + + + + + + + +
          Clave + nueva:
          De nuevo para + confirmar:
          + +

          Cambiar globalmente. +
          +

          - - +
          - Sus opciones de subscripción para la lista -
          +
          +Sus opciones de subscripción para la lista +
          -

          Los valores actuales se han comprobado. -

          Observe que alguna de las opciones tienen una casilla seleccionable Aplicar globalmente. -Activando este campo, causará que los cambios se apliquen a cada una -de las listas de distribución de en las que está -subscrito. Dele con el ratón a Listar el resto de mis subscripciones - más arriba para ver cuales son. +Activando este campo, causará que los cambios se apliquen a cada una +de las listas de distribución de en las que está +subscrito. Dele con el ratón a Listar el resto de mis subscripciones + más arriba para ver cuales son.

          - - + +

          -

          - - Entrega de correo

          - Si selecciona Habilitada recibirá los mensajes que se - envíen a esta lista de distribución. Si selecciona + + - + a la lista de distribución durante un tiempo (por ejemplo, se va + de vacaciones). Si inhabilita la recepción de los mensajes de + correo, no olvide volver a habilitar la recepción cuando proceda. +

          - - - + +

          - - - - + - - - - + + - - - - + + - - + - - + - - - +

          +
          + +Entrega de correo

          + Si selecciona Habilitada recibirá los mensajes que se + envíen a esta lista de distribución. Si selecciona Inhabilitada indica su deseo de no recibir el correo enviado - a la lista de distribución durante un tiempo (por ejemplo, se va - de vacaciones). Si inhabilita la recepción de los mensajes de - correo, no olvide volver a habilitar la recepción cuando proceda. -

          - Habilitado
          - Inhabilitado

          - Aplicar globalmente -

          +Habilitado
          +Inhabilitado

          +Aplicar globalmente +

          - Activar modo Digest

          - Si habilita el modo digest, recibirá diariamente los mensajes - enviados a la lista recopilados en un único mensaje, - en lugar de recibirlos a medida que se envían. Si cambia el modo - digest de Activar a Desactivar, puede que reciba un último +

          +Activar modo Digest

          + Si habilita el modo digest, recibirá diariamente los mensajes + enviados a la lista recopilados en un único mensaje, + en lugar de recibirlos a medida que se envían. Si cambia el modo + digest de Activar a Desactivar, puede que reciba un último digest. -

          - Desactivar
          - Activar -
          - ¿Recibir los recopilatorios en texto plano - o con codificación MIME?

          +

          +Desactivar
          +Activar +
          +¿Recibir los recopilatorios en texto plano + o con codificación MIME?

          Su lector de correo puede que no soporte recopilaciones (digests) MIME. En general se prefieren las recopilaciones MIME, pero si tiene problemas a la hora de leerlas, seleccione las recopilaciones en texto plano. -

          - MIME
          - Texto plano

          - Aplicar globalmente -

          +MIME
          +Texto plano

          +Aplicar globalmente +

          - ¿Quiere recibir los mensajes que usted mismo - envíe a esta lista?

          - Normalmente, recibirá una copia de cada mensaje que enví - a la lista. Si no quiere recibir dicha copia, ponga esta opción a +

          +¿Quiere recibir los mensajes que usted mismo + envíe a esta lista?

          + Normalmente, recibirá una copia de cada mensaje que enví + a la lista. Si no quiere recibir dicha copia, ponga esta opción a No. -

          - No
          - Yes -
          - ¿Quiere recibir una confirmación cuando envíe correo +

          +No
          +Yes +
          +¿Quiere recibir una confirmación cuando envíe correo a esta lista?

          -

          - No
          - Si -
          - ¿Quiere recibir los recordatorios de esta lista?

          - Mensualmente, recibirá un mensaje de correo con la clave de - cada una de las listas en la que está subscrito. Puede - inhabilitar este comportamiento por lista, con sólo seleccionar - No en esta opción. Si decide inhabilitar el recordatorio - de las claves en todas las listas a las que está subscrito, no - se le mandará ningún mensaje. -

          - No
          - Si

          - Aplicar globalmente -

          - ¿Quiere ocultarse de la lista de subscriptores?

          +

          +No
          +Si +
          +¿Quiere recibir los recordatorios de esta lista?

          + Mensualmente, recibirá un mensaje de correo con la clave de + cada una de las listas en la que está subscrito. Puede + inhabilitar este comportamiento por lista, con sólo seleccionar + No en esta opción. Si decide inhabilitar el recordatorio + de las claves en todas las listas a las que está subscrito, no + se le mandará ningún mensaje. +

          +No
          +Si

          +Aplicar globalmente +

          +¿Quiere ocultarse de la lista de subscriptores?

          Cuando alguien vea la lista de subscriptores, normalmente su - dirección de correo electrónico saldrá listada + dirección de correo electrónico saldrá listada (de una forma oscura para frustrar los escaneadores spam). Si no - quiere que su dirección salga a relucir, seleccione Si. -

          - No
          - Si -
          - ¿En qué idioma desea ver los mensajes de la lista?

          -

          - -
          - ¿A qué tipo de temas le gustaría subscribirse?

          - Al seleccionar uno o más temas, puede filtrar - el tráfico de la lista de distribución, de - manera que solo recibirá un subconjunto de los - mensajes. Únicamente le llegaran los mensajes que + quiere que su dirección salga a relucir, seleccione Si. +

          +No
          +Si +
          +¿En qué idioma desea ver los mensajes de la lista?

          +

          + +
          +¿A qué tipo de temas le gustaría subscribirse?

          + Al seleccionar uno o más temas, puede filtrar + el tráfico de la lista de distribución, de + manera que solo recibirá un subconjunto de los + mensajes. Únicamente le llegaran los mensajes que coincidan con alguno de los temas seleccionados.

          Si un mensaje no coincide con el tema, la regla que decide si se entrega el mensaje depende de la - configuración de la opción de abajo. Si - no selecciona ningún tema de interés, - recibirá todos los mensajes dirigidos a la lista. -

          - -
          - ¿Quiere recibir los mensajes que no coincidan con - ningún tema?

          - Esta opción sólo surte efecto si está - subscrito a algún tema de los de arriba. Esta - opción describe cual será la regla de + configuración de la opción de abajo. Si + no selecciona ningún tema de interés, + recibirá todos los mensajes dirigidos a la lista. +

          + +
          +¿Quiere recibir los mensajes que no coincidan con + ningún tema?

          + Esta opción sólo surte efecto si está + subscrito a algún tema de los de arriba. Esta + opción describe cual será la regla de entrega por defecto para los mensajes que no coincidan - con ningún tema. Si selecciona que No + con ningún tema. Si selecciona que No indica su deseo de no recibir los mensajes que no - coincidan con ningún tema, mientras que si - selecciona que Si significa que recibirá - todos los mensajes que no coincidan con ningún + coincidan con ningún tema, mientras que si + selecciona que Si significa que recibirá + todos los mensajes que no coincidan con ningún tema. -

          Si no se selecciona ningún tema de interés - en la parte de arriba, entonces recibirá todos los - mensajes que se envíen a la lista. -

          - No
          - Si -
          - ¿Desea evitar copias duplicadas de sus propios mensajes?

          - Cuando esté incluido explícitamente en las cabeceras +

          Si no se selecciona ningún tema de interés + en la parte de arriba, entonces recibirá todos los + mensajes que se envíen a la lista. +

          +No
          +Si +
          +¿Desea evitar copias duplicadas de sus propios mensajes?

          + Cuando esté incluido explícitamente en las cabeceras To: o Cc: de un mensaje dirigido a la lista, puede optar por no recibir otra copia de la - lista de distribución. Seleccione Si para - evitar recibir copias de la lista de distribución o + lista de distribución. Seleccione Si para + evitar recibir copias de la lista de distribución o No para recibirlas.

          Si la lista tiene subscriptores con los mensajes - personalizados activados, y escogió recibir copias, - cada copia tendrá una cabecera X-Mailman-Copy: + personalizados activados, y escogió recibir copias, + cada copia tendrá una cabecera X-Mailman-Copy: yes. -

          - No
          - Si

          - Aplicar globalmente -

          -
          -
          +No
          +Si

          +Aplicar globalmente +

          +
          +
          -

          - - - - + + +

          diff --git a/templates/es/private.html b/templates/es/private.html index c8b7442c..b0b04f0a 100755 --- a/templates/es/private.html +++ b/templates/es/private.html @@ -1,54 +1,115 @@ - Autentificación para los archivos privados de %(realname)s +Autentificación para los archivos privados de %(realname)s - - -
          + + + %(message)s - - - - - - - - - - - - - - - -
          - Autentificación de archivos - privados de %(realname)s -
          Dirección:
          Clave:
          -
          -

          Importante: A partir de ahora, debe - tener habilitadas las cookies en su navegador, en caso contrario, sus cambios, no tendrán efecto. -

          Las sesiones basadas en cookies se usan en la interfaz administrativa de Mailman, de manera que no necesita identificarse continuamente con cada operación administrativa que realice. La cookie caducará automáticamente cuando salga del navegador, o puede hacerla caducar seleccionando la opción Salida bajo la sección titulada - Otras Actividades Administrativas (que verá una vez que consiga entrar + + + + + + + + + + + + + + + +
          +Autentificación de archivos + privados de %(realname)s +
          Dirección:
          Clave:
          +
          +

          Importante: A partir de ahora, debe + tener habilitadas las cookies en su navegador, en caso contrario, sus cambios, no tendrán efecto. +

          Las sesiones basadas en cookies se usan en la interfaz administrativa de Mailman, de manera que no necesita identificarse continuamente con cada operación administrativa que realice. La cookie caducará automáticamente cuando salga del navegador, o puede hacerla caducar seleccionando la opción Salida bajo la sección titulada + Otras Actividades Administrativas (que verá una vez que consiga entrar satisfactoriamente).

          - - - - - - + + + +
          - Password Reminder -
          If you don't remember your password, enter your email address + + + + + + - - - - -
          +Password Reminder +
          If you don't remember your password, enter your email address above and click the Remind button and your password will be emailed to you.
          - +
          +

          diff --git a/templates/es/roster.html b/templates/es/roster.html index 5e3dc43c..637d08e3 100644 --- a/templates/es/roster.html +++ b/templates/es/roster.html @@ -1,52 +1,111 @@ - - - Subscriptores de <MM-List-Name> - - - - -

          - - - - - - - - - - - - - - - -
          - Subscriptores de -
          - -

          -

          - -

          Seleccione su dirección para visitar el estado de su subscripción. -
          (Las entradas entre paréntesis tienen la recepción del correo - de la lista desactivada.)

          -
          -
          - - Suscriptores en con la recepción de los mensajes a medida que llegan: -
          -
          -
          - - Suscriptores en con la recepción diferida en resúmenes: -
          -
          -

          -

          -

          -

          - - - + + +Subscriptores de <mm-list-name></mm-list-name> + + +

          + + + + + + + + + + + + + + + +
          +Subscriptores de +
          +

          +

          +

          Seleccione su dirección para visitar el estado de su subscripción. +
          (Las entradas entre paréntesis tienen la recepción del correo + de la lista desactivada.)

          +
          +
          + + Suscriptores en con la recepción de los mensajes a medida que llegan: +
          +
          +
          + + Suscriptores en con la recepción diferida en resúmenes: +
          +
          +

          +

          +

          +

          + +

          + diff --git a/templates/es/subscribe.html b/templates/es/subscribe.html index 23506eb8..b70be098 100644 --- a/templates/es/subscribe.html +++ b/templates/es/subscribe.html @@ -1,9 +1,72 @@ -<MM-List-Name> Resultados de la subscripción +<mm-list-name> Resultados de la subscripción</mm-list-name> -

          Resultados de la subscripción

          - - - +

          Resultados de la subscripción

          + + + diff --git a/templates/et/admindbdetails.html b/templates/et/admindbdetails.html index a75d5d14..82a3e910 100644 --- a/templates/et/admindbdetails.html +++ b/templates/et/admindbdetails.html @@ -1,63 +1,126 @@ -Administratiivseid ülesandeid näidatakse kahel viisil, -ülevaate lehel, ja detailide lehel. -Ülevaateleht sisaldab ootel olevaid tellimuse ja tellimuse katkestamise -nõudeid ning ka postitusi, mis on ülevaatamiseks kinni peetud, sorteerituna -saatja aadressi järgi. Detailide leht sisaldab põhjalikumat infot iga -kinnipeetud postituse kohta, k.a. kõiki päiseid ja lõiku kirja sisust. +Administratiivseid ülesandeid näidatakse kahel viisil, +ülevaate lehel, ja detailide lehel. +Ülevaateleht sisaldab ootel olevaid tellimuse ja tellimuse katkestamise +nõudeid ning ka postitusi, mis on ülevaatamiseks kinni peetud, sorteerituna +saatja aadressi järgi. Detailide leht sisaldab põhjalikumat infot iga +kinnipeetud postituse kohta, k.a. kõiki päiseid ja lõiku kirja sisust. -

          Mõlemal lehel on võimalik sooritada järgnevaid tegevusi: +

          Mõlemal lehel on võimalik sooritada järgnevaid tegevusi:

            -
          • Viivita -- Viivita otsuse tegemisega. See taotlus jääb - edasist otsust ootama, kinni peetud kirju on siiski võimalik - edasi saata või preservida (lähme info allpool) +
          • Viivita -- Viivita otsuse tegemisega. See taotlus jääb + edasist otsust ootama, kinni peetud kirju on siiski võimalik + edasi saata või preservida (lähme info allpool) -

          • Nõustu -- Anna luba kiri listi edastada või nõustu -tellimuse või tellimuse katkestmaisega. +

          • Nõustu -- Anna luba kiri listi edastada või nõustu +tellimuse või tellimuse katkestmaisega.

          • Keeldu -- Keeldu kirja listi edastamisest, saatjat - teavitatakse ning kiri eemaldatakse järjekorrast. Tellimusnõuete - puhul keeldu nõude rahuldamisest. Mõlemal juhul oleks soovitav + teavitatakse ning kiri eemaldatakse järjekorrast. Tellimusnõuete + puhul keeldu nõude rahuldamisest. Mõlemal juhul oleks soovitav lisada tekstikasti selgitus keeldumise kohta. -

          • Viska ära -- Viska kiri minema saatjat teavitamata. - Tellimusnõuete puhul visatakse tellimus lihtsalt minema, ilma +

          • Viska ära -- Viska kiri minema saatjat teavitamata. + Tellimusnõuete puhul visatakse tellimus lihtsalt minema, ilma tellijat teavitamata. Seda on soovitav teha, kui tegemist on - spämmiga. -
          - -

          Kinni peetud kirjade puhul märgistage kastike Preserve -kastikesse, kui soovite jätta koopiat sellest kirjast listserveri + spämmiga. +

          +

          Kinni peetud kirjade puhul märgistage kastike Preserve +kastikesse, kui soovite jätta koopiat sellest kirjast listserveri haldurile. See on kasulik abusive teadete puhul, mida soovid oma huvialast eemaldada, kui mida tahad siiski hilisemaks inspekteerimiseks alles hoida. -

          Märgistage Edasta kastike ja kirjutage vastavasse lahtrisse +

          Märgistage Edasta kastike ja kirjutage vastavasse lahtrisse aadress kui soovite kirja edasi saata kellegile, kes ei ole listis. Kui soovite kinnipeetud kirja enne listi saatmist muuta, siis peaksite -selle kirja kõigepealt endale edastama (või listihalduritele) ning -seejärel originaalse kirja kustutama. Siis, kui kiri on teie postkasti -jõudnud, tehke oma muudatused ja saatke kiri tagasi listi, lisades -Approved: päise koos listi parooliga. Netiketi kohaselt -on viisakas kui lisate originaalkirjale juurde märkuse selle kohta, +selle kirja kõigepealt endale edastama (või listihalduritele) ning +seejärel originaalse kirja kustutama. Siis, kui kiri on teie postkasti +jõudnud, tehke oma muudatused ja saatke kiri tagasi listi, lisades +Approved: päise koos listi parooliga. Netiketi kohaselt +on viisakas kui lisate originaalkirjale juurde märkuse selle kohta, et olete kirja muutnud.

          Kui kirja saatjaks on listi tellija, keda modereeritakse, siis on -teil võimalik saatjalt see piirang eemaldada. See on kasulik juhul, +teil võimalik saatjalt see piirang eemaldada. See on kasulik juhul, kui teie listis kasutatakse uute liikmete prooviaega ja te olete -otsustanud, et see liige võib postitada ilma listihaldurit asjasse +otsustanud, et see liige võib postitada ilma listihaldurit asjasse segamata. -

          Kui saatja ei ole listi liige, siis on teil võimalik tema aadress -saatjate filtrisse lisada. Neid filtreid kirjeldatakse lähemalt +

          Kui saatja ei ole listi liige, siis on teil võimalik tema aadress +saatjate filtrisse lisada. Neid filtreid kirjeldatakse lähemalt saatjafiltrite privaatsuse lehel ja see -võib olla üks järgnevatest: auto-accept (lubatakse läbi), auto-hold - (peetakse läbivaatuseks kinni), auto-reject (keeldutakse) või - auto-discard (visatakse minema). Seda seadistust ei ole võimalik - kasutada, kui aadress on juba ühes neist saatjafiltritest. +võib olla üks järgnevatest: auto-accept (lubatakse läbi), auto-hold + (peetakse läbivaatuseks kinni), auto-reject (keeldutakse) või + auto-discard (visatakse minema). Seda seadistust ei ole võimalik + kasutada, kui aadress on juba ühes neist saatjafiltritest. -

          Kui olete lõpetanud, klikkide nupul Salvesta kõik valikud lehe -alguses või lõpus. See submitid kõik tegevused kõigile administratiivnõutele +

          Kui olete lõpetanud, klikkide nupul Salvesta kõik valikud lehe +alguses või lõpus. See submitid kõik tegevused kõigile administratiivnõutele mille kohta te olete otsuse teinud. -

          Tagasi kokkuvõttesse. +

          Tagasi kokkuvõttesse. +

          \ No newline at end of file diff --git a/templates/et/admindbpreamble.html b/templates/et/admindbpreamble.html index 263aa264..153fd5d3 100644 --- a/templates/et/admindbpreamble.html +++ b/templates/et/admindbpreamble.html @@ -1,10 +1,74 @@ -Listi %(listname)s saadetud ja läbivaatuseks +Listi %(listname)s saadetud ja läbivaatuseks kinni peetud kirjad. Kuvatakse: %(description)s

          Vali iga kirja juurest sellega sooritatav tegevus ja -kliki seejärel Salvesta kõik valikud. +kliki seejärel Salvesta kõik valikud. Detailsem juhend on siin. -

          Siin on ülevaade -kõigist ootel taotlustest. +

          Siin on ülevaade +kõigist ootel taotlustest. +

          \ No newline at end of file diff --git a/templates/et/admindbsummary.html b/templates/et/admindbsummary.html index 0901b08e..d0528e54 100644 --- a/templates/et/admindbsummary.html +++ b/templates/et/admindbsummary.html @@ -1,13 +1,77 @@ -Sellel lehel on ülevaade kõigist +Sellel lehel on ülevaade kõigist %(listname)s listi administratiivtaotlustest. -Alguses on nimekiri kõigist tellimise ja tellimise katkestamise -soovidest (kui neid on) ja seejärel tuleb läbivaatuseks kinni +Alguses on nimekiri kõigist tellimise ja tellimise katkestamise +soovidest (kui neid on) ja seejärel tuleb läbivaatuseks kinni peetud kirjade nimekiri. -

          Iga taotluse juures valige toiming ja klikkige Salvesta kõik valikud -kui olete lõpetanud. -Täpsemad juhendeid võib lugeda siin. +

          Iga taotluse juures valige toiming ja klikkige Salvesta kõik valikud +kui olete lõpetanud. +Täpsemad juhendeid võib lugeda siin. -

          Ülevaade kõigist kinni peetud kirjadest. +

          Ülevaade kõigist kinni peetud kirjadest. +

          \ No newline at end of file diff --git a/templates/et/admlogin.html b/templates/et/admlogin.html index fb8797a2..021e89a9 100755 --- a/templates/et/admlogin.html +++ b/templates/et/admlogin.html @@ -1,36 +1,97 @@ - %(listname)s %(who)s autoriseerimine +%(listname)s %(who)s autoriseerimine - - -
          + + + %(message)s - - - - - - - - - - - -
          - %(listname)s %(who)s - autentimine -
          Listi %(who)s parool:
          -
          -

          NB: Selle veebiliidese kasutamiseks - pead oma brauseril lubama küpsiste vastuvõtmise sellest saidist. + + + + + + + + + + + +
          +%(listname)s %(who)s + autentimine +
          Listi %(who)s parool:
          +
          +

          NB: Selle veebiliidese kasutamiseks + pead oma brauseril lubama küpsiste vastuvõtmise sellest saidist. Kui sa seda ei tee, siis ei saa tehtud muudatusi salvestada. -

          Küpsised on kasutusel selleks, et sa ei peaks igal lehel uuesti - parooli sisestama. Küpsis kustutatakse, kui oma brauseri sulged või - veebiliidesest välja logid. -

          +

          Küpsised on kasutusel selleks, et sa ei peaks igal lehel uuesti + parooli sisestama. Küpsis kustutatakse, kui oma brauseri sulged või + veebiliidesest välja logid. +

          diff --git a/templates/et/article.html b/templates/et/article.html index 9f554e5a..1af1cac6 100644 --- a/templates/et/article.html +++ b/templates/et/article.html @@ -1,12 +1,13 @@ - - - %(title)s - - - - - + +

          %(listname)s listi arhiiv

          +

          + Sellesse listi pole veel kirju saadetud ja seetõttu on arhiiv praegu tühi. + lähem info listi kohta.

          - - + + diff --git a/templates/et/headfoot.html b/templates/et/headfoot.html index 89681547..d9c6d1ae 100644 --- a/templates/et/headfoot.html +++ b/templates/et/headfoot.html @@ -1,24 +1,87 @@ -See tekst võib sisaldada +See tekst võib sisaldada Pythoni vormingustringe, mis asendatakse selle listi atribuutidega. Lubatud -asendused on järgmised: +asendused on järgmised:
            -
          • real_name - Listi vormindatud nimi, üldjuhul - on see listi nimi suure esitähega. +
          • real_name - Listi vormindatud nimi, üldjuhul + on see listi nimi suure esitähega. -
          • list_name - Listi tõstutundetu nimi. Mõeldud - ennekõike URLides kasutamiseks. +
          • list_name - Listi tõstutundetu nimi. Mõeldud + ennekõike URLides kasutamiseks.
          • host_name - Listserveri poolt kasutatava domeeni nimi.
          • web_page_url - Mailmani veebiliidese URL. URLi - lõppu võib lisada näiteks listinfo/%(list_name)s - linkimaks otse listi üldinfo veebilehele. + lõppu võib lisada näiteks listinfo/%(list_name)s + linkimaks otse listi üldinfo veebilehele. -
          • description - Lühike listi kirjeldus. +
          • description - Lühike listi kirjeldus.
          • info - Pikem listi tutvustav tekst.
          • cgiext - CGI skriptidele lisatav laiend. -
          + diff --git a/templates/et/listinfo.html b/templates/et/listinfo.html index 7c0ee718..d6960e4e 100644 --- a/templates/et/listinfo.html +++ b/templates/et/listinfo.html @@ -1,140 +1,201 @@ - - - - Listi <MM-List-Name> info - - - -

          - - - - - - - - - - - - - - - - - - - + + + + + +

          - -- - -
          -

            -

          - listi info - - - -
          -

          -

          arhiiv. - -

          -
          - kasutamine -
          + + + +Listi <mm-list-name> info</mm-list-name> + + +

          + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + +
          + -- + +
          +

            +

          + listi info + + + +
          +

          +

          arhiiv. + +

          +
          + kasutamine +
          Listi kirjutamiseks on aadress - . + .

          Listi saab tellida ja tellimuse profiili muuta allpool

          -
          - listi tellimine -
          -

          - tellimiseks täitke järgnev vorm - -

            - - - - - - - - - - - - + + + + + + - - - - - -
            E-posti aadress: -  
            Nimi (võib ka tühjaks jätta): 
            Allpool olevasse vormi saab - sisestada parooli. See pole just üleliia turvaline, - kuid peaks ära hoidma selle, et keegi teine teie tellimust - muutma pääseb. Ärge sisestage parooli, mida mujal kasutate, +
            + listi tellimine +
            +

            + tellimiseks täitke järgnev vorm + +

              + + + + + + + + + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - - - -
              E-posti aadress: + 
              Nimi (võib ka tühjaks jätta): 
              Allpool olevasse vormi saab + sisestada parooli. See pole just üleliia turvaline, + kuid peaks ära hoidma selle, et keegi teine teie tellimust + muutma pääseb. Ärge sisestage parooli, mida mujal kasutate, sest see parool saadetakse teile meiliga. -

              Kui te parooli ei sisesta, siis genereeritakse see automaatselt +

              Kui te parooli ei sisesta, siis genereeritakse see automaatselt ning saadetakse teile niipea, kui olete oma tellimuse kinnitanud. - Hiljem on võimalik tellida automaatne parooli meeldetuletus. - -
              -
              Parool: 
              Parool uuesti: 
              Kasutajaliidese keel?  
              Kirju saadetakse kokkuvõtetena + Hiljem on võimalik tellida automaatne parooli meeldetuletus. + + +
              Parool: 
              Parool uuesti: 
              Kasutajaliidese keel?  
              Kirju saadetakse kokkuvõtetena Ei - Jah -
              -
              -
              - -
            -
            - - tellijad -
            - - - -

            - - - -

            - - - +
          Ei + Jah +
          +
          +
          + + +

          + + tellijad +
          + + + +

          + + + +

          + +

          + diff --git a/templates/et/options.html b/templates/et/options.html index fd6b2046..4f094560 100644 --- a/templates/et/options.html +++ b/templates/et/options.html @@ -1,301 +1,333 @@ - - <MM-Presentable-User> <MM-List-Name> tellimuse profiil - - - - - -
          - - tellimuse profiil kasutajale - -
          + +<mm-presentable-user> <mm-list-name> tellimuse profiil + </mm-list-name></mm-presentable-user> + + + + +
          + + tellimuse profiil kasutajale + +

          - - - - - +
          - tellimuse staatus, - parool, ja listi seadistused. -
          - - - - -

          -

          + + + +
          + tellimuse staatus, + parool, ja listi seadistused. +
          + + +

          +

          - - +

          - - - +
          - - tellimuse profiili muutmine -
          Tellimuse aadressi saab muuta sisestades uue - aadressi allpool asuvatesse vormi väljadesse. Enne kui muudatus - jõustuda saab, saadetakse uuele aadressile veel täiendav kiri, millele + + + - - - - - -
          + + tellimuse profiili muutmine +
          Tellimuse aadressi saab muuta sisestades uue + aadressi allpool asuvatesse vormi väljadesse. Enne kui muudatus + jõustuda saab, saadetakse uuele aadressile veel täiendav kiri, millele peab vastama, et aadressi muutus kinnitada. -

          Kinnitus kaotab kehtivuse pärast. - -

          Võid sisestada ka oma pärisnime (näiteks Einar Kootikum). - -

          Kui soovid tehtud muudatusi rakendada muudatusi korraga kõigile serveri - listide tellimustele, siis märgista ruuduke Muuda kõiki. - -

          - - - - - - - -
          Uus aadress:
          Uus aadress - (2x):
          -
          - - - - -
          Sinu nimi - (võib tühjaks jätta):
          -
          -

          Muuda kõiki

          - +

          Kinnitus kaotab kehtivuse pärast. + +

          Võid sisestada ka oma pärisnime (näiteks Einar Kootikum). + +

          Kui soovid tehtud muudatusi rakendada muudatusi korraga kõigile serveri + listide tellimustele, siis märgista ruuduke Muuda kõiki. + +

          + + + + + + + +
          Uus aadress:
          Uus aadress + (2x):
          +

          + + + + +
          Sinu nimi + (võib tühjaks jätta):
          +
          +

          Muuda kõiki

          - - - - - - - + + + + %(textlink)s - diff --git a/templates/eu/article.html b/templates/eu/article.html index a98f8319..eb4c256d 100644 --- a/templates/eu/article.html +++ b/templates/eu/article.html @@ -1,12 +1,13 @@ - - - %(title)s - - - - - + +

          %(listname)s Zerrendako Artxiboak

          +

          Zerrenda honetara ez da oraingoz mezurik bidali; artxiboa hutsik dago. Zerrendari buruzko informazio gehiago hemen.

          - - + + diff --git a/templates/eu/headfoot.html b/templates/eu/headfoot.html index 5d82f204..e89bebfe 100644 --- a/templates/eu/headfoot.html +++ b/templates/eu/headfoot.html @@ -1,10 +1,73 @@ -Testu honek +Testu honek Python kateak izan ditzake; zerrendako atributuen bidez ordezkatuko dira. Onartzen diren ordezkapenak hauexek dira:
            -
          • real_name - Zerrendaren izen `polita'; gehienetan +
          • real_name - Zerrendaren izen `polita'; gehienetan zerrendaren izen berbera, baina lehenengo letra larria duena.
          • list_name - URL-etan zerrenda identifikatzeko @@ -25,4 +88,4 @@ sakonagoa.
          • cgiext - CGI script-ei gehitzen zaien luzapena. -
          + diff --git a/templates/eu/listinfo.html b/templates/eu/listinfo.html index 5d41d2a8..c26bedcc 100644 --- a/templates/eu/listinfo.html +++ b/templates/eu/listinfo.html @@ -1,146 +1,212 @@ - - - - - <MM-List-Name> Zerrendaren Informazio Orria - - - + -

          -

          - listi tellimuse lõpetamine - Teiste listide tellimused -
          - Märgista ruuduke "Nõua kinnitust" ja kliki seda nuppu - listi tellimuse lõpetamiseks. + + + + - + +
          +

          + listi tellimuse lõpetamine +Teiste listide tellimused +
          + Märgista ruuduke "Nõua kinnitust" ja kliki seda nuppu + listi tellimuse lõpetamiseks. NB: This action will be taken immediately!

          -

          - Siit saad vaadata kõigi oma ülejäänud listide tellimusi. +

          + Siit saad vaadata kõigi oma ülejäänud listide tellimusi. Kliki siia, kui soovid muudatusi rakendada ka teiste tellimuste profiilidele.

          -

          -
          - - - - - +
          - Teie listi parool -
          - -
          -

          Unustasid parooli?

          -
          + + + - -
          +Teie listi parool +
          + +
          +

          Unustasid parooli?

          +
          Kliki seda nuppu parooli saatmiseks aadressile, millele listi tellisid -

          -

          - -
          -
          - -
          -

          Vaheta parooli

          - - - - - - - - -
          Uus - parool:
          Korda - parooli:
          - - -

          Muuda kõiki. -
          -
          - +

          +

          + +
          +

          + +
          +

          Vaheta parooli

          + + + + + + + + +
          Uus + parool:
          Korda + parooli:
          + +

          Muuda kõiki. +
          +

          - - +
          - Listi tellimuse profiil -
          +
          +Listi tellimuse profiil +
          -

          -Hetkel kehtivad valikud on märgistatud. - -

          Osade valikute juures on ka valikukast rakenda kõigile, -seda märgistades saab muudatusi rakendada kõigi sinu tellimuste +Hetkel kehtivad valikud on märgistatud. +

          Osade valikute juures on ka valikukast rakenda kõigile, +seda märgistades saab muudatusi rakendada kõigi sinu tellimuste profiilidele servers . -Klikkige nupule teised tellimused ülal, et näha millised +Klikkige nupule teised tellimused ülal, et näha millised listid teie aadressile veel tellitud on.

          - -
          - - Tellimuse staatus

          + + - +

          - - - + + - - - - - - + + - - + - - - - + + - - + - - + - - - + kontroll sisse lülitatud, siis lisatakse igale listserveri + kaudu saadetud kirjale X-Mailman-Copy: yes päis. + +

          +
          + +Tellimuse staatus

          Vali aktiivne kui soovid listi kirju saada. - Vali peatatud kui soovid kirjade kätte toimetamise - ajutiselt katkestada (näiteks puhkusel oleku ajaks). Viisakas oleks + Vali peatatud kui soovid kirjade kätte toimetamise + ajutiselt katkestada (näiteks puhkusel oleku ajaks). Viisakas oleks ka tellimus uuesti aktiveerida, kui (puhkuselt) tagasi oled. -

          - aktiivne
          - peatatud

          - rakenda kõigile -

          +aktiivne
          +peatatud

          +rakenda kõigile +

          - Kirjade kättetoimetusviis

          - Kui soovid listi kirju saada kokkuvõtetena (tavaliselt - kord päevas, suurema liiklusega listides ka sagedamini), siis - vali "Kokkuvõtted". -

          - üksikkirjad
          - kokkuvõtted -
          - Kokkuvõtete vorming?

          - Kui sa mingil põhjusel ei saa või ei taha lugeda MIME vormingus - kokkuvõtteid, siis on võimalik kasutada lihttekst vormingut. -

          - MIME
          - Lihttekst -
          +Kirjade kättetoimetusviis

          + Kui soovid listi kirju saada kokkuvõtetena (tavaliselt + kord päevas, suurema liiklusega listides ka sagedamini), siis + vali "Kokkuvõtted". +

          +üksikkirjad
          +kokkuvõtted +
          +Kokkuvõtete vorming?

          + Kui sa mingil põhjusel ei saa või ei taha lugeda MIME vormingus + kokkuvõtteid, siis on võimalik kasutada lihttekst vormingut. +

          +MIME
          +Lihttekst +
          - Oma kirjad läbi listi?

          +

          +Oma kirjad läbi listi?

          Vaikimisi saad listist ka koopiad enda listi saadetud kirjadest. Kui sa seda ei soovi, siis vali Ei -

          - Ei
          - Jah -
          - Kinnitus listi saadetud kirja laiali toimetamise kohta?

          -

          - Ei
          - Jah -
          - Automaatne paroolide meeldetuletus?

          +

          +Ei
          +Jah +
          +Kinnitus listi saadetud kirja laiali toimetamise kohta?

          +

          +Ei
          +Jah +
          +Automaatne paroolide meeldetuletus?

          Kord kuus saate meili teel meeldetuletuse sellest serverist tellitud listite kohta. Selles automaatselt saadetavas meilis on muu hulgas kirjas ka tellimus(te) paroolid. Kui sa ei soovi selliseid meeldetuletusi, siis vali Ei -

          - Ei
          - Jah

          - rakenda kõigile -

          - Näita minu aadressi tellijate nimekirjas?

          - Vaikimsi näidatakse kõigi tellijate aadresse listi tellijate - nimekirjas (küll veidi muudetud kujul, et kaitsta aadresse - spämmijate eest). Kui sa ei taha, et sinu aadress oleks +

          +Ei
          +Jah

          +rakenda kõigile +

          +Näita minu aadressi tellijate nimekirjas?

          + Vaikimsi näidatakse kõigi tellijate aadresse listi tellijate + nimekirjas (küll veidi muudetud kujul, et kaitsta aadresse + spämmijate eest). Kui sa ei taha, et sinu aadress oleks selles nimekirjas siis vali Ei -

          - Ei
          - Jah -
          - Millist keelt eelistad listserveriga suhtlemisel?

          -

          - -
          - Millised teemad sind huvitavad? +

          +Ei
          +Jah +
          +Millist keelt eelistad listserveriga suhtlemisel?

          +

          + +
          +Millised teemad sind huvitavad?

          - Sul on võimalik lasta listserveril filtreerida sulle + Sul on võimalik lasta listserveril filtreerida sulle toimetatavaid kirju, valides allpool tootud nimekirjas - ühe või enam huvi pakkuvat teemat. - -

          Kui sa ühtegi valikut ei tee, saadetakse sulle kõik kirjad

          -
          - -
          - Mida teha teemaväliste kirjadega? + ühe või enam huvi pakkuvat teemat. + +

          Kui sa ühtegi valikut ei tee, saadetakse sulle kõik kirjad

          +

          + +
          +Mida teha teemaväliste kirjadega?

          - See valik kehtib ainult siis, kui oled valinud vähemalt - ühe teema sellest nimekirjast. See seadistus määrab - teemaväliste kirjade saatuse. + See valik kehtib ainult siis, kui oled valinud vähemalt + ühe teema sellest nimekirjast. See seadistus määrab + teemaväliste kirjade saatuse. - Ära saada - kirju ei saadeta sulle + Ära saada - kirju ei saadeta sulle Saada - kirjad saadetakse sulle -

          Kui sa ühtegi teemat ei vali, saad kätte kõik kirjad

          - -
          - Ära saada
          - Saada -
          - Väldi duplikaate?

          +

          Kui sa ühtegi teemat ei vali, saad kätte kõik kirjad

          +

          +Ära saada
          +Saada +
          +Väldi duplikaate?

          Kui sinu aadress on listi saadetud kirjade To: - ja/või Cc: päistes, siis selle valiku abil - saad listserverile öelda, et ei taha koopiat - kirjast läbi listi (Cc-ga saadetud kiri on sul ju olemas). - Vali Jah kui sa ei taha koopiaid, või Ei + ja/või Cc: päistes, siis selle valiku abil + saad listserverile öelda, et ei taha koopiat + kirjast läbi listi (Cc-ga saadetud kiri on sul ju olemas). + Vali Jah kui sa ei taha koopiaid, või Ei kui tahad.

          Kui list kasutab personaliseeritud kirju ja sul on duplikaatide - kontroll sisse lülitatud, siis lisatakse igale listserveri - kaudu saadetud kirjale X-Mailman-Copy: yes päis. - -

          - Ei
          - Jah

          - rakenda kõigile -

          -
          -
          +Ei
          +Jah

          +rakenda kõigile +

          +
          +
          -

          - - - - + + +

          diff --git a/templates/et/private.html b/templates/et/private.html index d318a652..73802bd7 100755 --- a/templates/et/private.html +++ b/templates/et/private.html @@ -1,58 +1,119 @@ - %(realname)s privaatarhiiv +%(realname)s privaatarhiiv - - -
          + + + %(message)s - - - - - - - - - - - - - - - -
          - %(realname)s privaatarhiivi - ligipääsukontroll -
          Meiliaadress:
          Parool:
          -
          -

          Important: Sisse logimiseks peate + + + + + + + + + + + + + + + +
          +%(realname)s privaatarhiivi + ligipääsukontroll +
          Meiliaadress:
          Parool:
          +
          +

          Important: Sisse logimiseks peate oma brauseril lubama 'cookie'-de aktsepteerimise sellest saidist. -

          Küpsiseid kasutatakse Mailman'i veebiliidese kasutamise lihtsustamiseks, +

          Küpsiseid kasutatakse Mailman'i veebiliidese kasutamise lihtsustamiseks, et sa ei peaks iga lehe vaatamisel uuesti kasutajanime ja parooli sisestama. - Küpsis kustutatakse automaatselt, kui oma brauseri sulged või klikid - Logi välja lingil, mis asub lingi Muud administreerimistegevused + Küpsis kustutatakse automaatselt, kui oma brauseri sulged või klikid + Logi välja lingil, mis asub lingi Muud administreerimistegevused all.

          - - - - - - + + + +
          - Password Reminder -
          If you don't remember your password, enter your email address + + + + + + - - - - -
          +Password Reminder +
          If you don't remember your password, enter your email address above and click the Remind button and your password will be emailed to you.
          - +
          +

          diff --git a/templates/et/roster.html b/templates/et/roster.html index 9c425260..f5b117ab 100644 --- a/templates/et/roster.html +++ b/templates/et/roster.html @@ -1,53 +1,112 @@ - - - <MM-List-Name> tellijad - - - - -

          - - - - - - - - - - - - - - - -
          - - tellijad -
          - -

          -

          - -

          Kliki oma aadressil tellimuse profiili muutmiseks -
          (Tellijad, kelle aadress on sulgudes, on oma tellimuse - ajutiselt katkestanud.)

          -
          -
          - - üksikkirjade tellijat: -
          -
          -
          - - kokkuvõtete tellijat: -
          -
          -

          -

          -

          -

          - - - + + +<mm-list-name> tellijad</mm-list-name> + + +

          + + + + + + + + + + + + + + + +
          + + tellijad +
          +

          +

          +

          Kliki oma aadressil tellimuse profiili muutmiseks +
          (Tellijad, kelle aadress on sulgudes, on oma tellimuse + ajutiselt katkestanud.)

          +
          +
          + + üksikkirjade tellijat: +
          +
          +
          + + kokkuvõtete tellijat: +
          +
          +

          +

          +

          +

          + +

          + diff --git a/templates/et/subscribe.html b/templates/et/subscribe.html index 4c01fd6e..c3a68e8e 100644 --- a/templates/et/subscribe.html +++ b/templates/et/subscribe.html @@ -1,9 +1,72 @@ -<MM-List-Name> tellimine +<mm-list-name> tellimine</mm-list-name> -

          tellimine

          - - - +

          tellimine

          + + + diff --git a/templates/eu/admindbdetails.html b/templates/eu/admindbdetails.html index 50cfc9d1..b80e4de1 100644 --- a/templates/eu/admindbdetails.html +++ b/templates/eu/admindbdetails.html @@ -1,5 +1,67 @@ -Eskaera administratiboak bi orrialde ezberdinetan aurkezten dira, laburpen orrian, eta xehetasunen +Eskaera administratiboak bi orrialde ezberdinetan aurkezten dira, laburpen orrian, eta xehetasunen orrian. Laburpen orrian, zerrendan sartzeko edo zerrenda uzteko onartu gabe dauden eskaerak agertzen dira; baita zuk noiz onartu zain atxikita dauden mezuak, harpidearen helbidearen arabera sailkatuta. Xehetasunen orrialdean, @@ -26,8 +88,7 @@ igorri gabe. Harpidetzari buruzko eskaeretan (zerrendan sartu edo zerrenda uzteko) aukera honekin eskaera bertan behera uzten da, eskaera egin duenari ezer jakinarazi gabe. Hau izango da spam-en kasuan egin beharreko aukera. - - +

          Atxikitako mezuen kasuan, gunearen adiministratzailearentzat kopia bat gorde nahi baduzu, aktibatu Gorde aukera. Aukera hau erabiligarria izan daiteke, adibidez, onartu ez dituzun mezu iraingarrien kasuan; horrela, badaezpada, @@ -50,8 +111,7 @@ fidagarria dela ikusten denean, ez dago bere mezuak moderatu beharrik.

          Mezua bidali duena ez bada zerrendakide, bidaltzaileen iragazkian -erantsi dezakezu bere izena. Bidaltzaileen iragazkien berri bidaltzaileen iragazkien pribazitate orrian duzu. +erantsi dezakezu bere izena. Bidaltzaileen iragazkien berri bidaltzaileen iragazkien pribazitate orrian duzu. Horrelakoak izan daitezke: automatikoki onartu (Onartu), automatikoki atxiki (Atxiki), automatikoki ez onartu (Ez onartu), edo automatikoki baztertu (Baztertu). Aukera hau ezingo da erabili helbidea honezkero bidaltzaileen iragazkian @@ -62,3 +122,4 @@ guztiak onartuko dituzu.

          Itzuli laburpen orrialdera. +

          \ No newline at end of file diff --git a/templates/eu/admindbpreamble.html b/templates/eu/admindbpreamble.html index 69e9c58c..7a63dc69 100644 --- a/templates/eu/admindbpreamble.html +++ b/templates/eu/admindbpreamble.html @@ -1,4 +1,67 @@ -Orri honetan %(listname)s posta zerrendara bidalitako mezuen +Orri honetan %(listname)s posta zerrendara bidalitako mezuen multzo bat dago; atxikirik daude, zuk noiz onartuko zain. Une honetan erakusten du: %(description)s @@ -8,3 +71,4 @@

          Onartzeke dauden eskaera guztiak ikusteko, hona jo: laburpena ikusi. +

          \ No newline at end of file diff --git a/templates/eu/admindbsummary.html b/templates/eu/admindbsummary.html index 2d04c066..7df4fd96 100644 --- a/templates/eu/admindbsummary.html +++ b/templates/eu/admindbsummary.html @@ -1,4 +1,67 @@ -Orri honetan %(listname)s posta zerrendan +Orri honetan %(listname)s posta zerrendan zure onespenaren zain dauden eskaera administratiboen laburpena aurkituko duzu. Lehenengo, zerrendan izena emateko edo zerrenda uzteko egindako eskaerak agertuko dira (baleude, jakina); ondoren, zure onespenaren zain @@ -10,3 +73,4 @@

          Atxikitako mezuen xehetasunak ere ikus ditzakezu. +

          \ No newline at end of file diff --git a/templates/eu/admlogin.html b/templates/eu/admlogin.html index 21b3e851..f9263737 100755 --- a/templates/eu/admlogin.html +++ b/templates/eu/admlogin.html @@ -1,30 +1,91 @@ - %(listname)s Zerrendako %(who)s Identifikatzen - - - -
          +%(listname)s Zerrendako %(who)s Identifikatzen + + + + %(message)s - - - - - - - - - - - -
          - %(listname)s Zerrendako %(who)s - Identifikatzeko Orria -
          %(who)sren Pasahitza:
          -
          -

          Garrantzitsua: Hemendik aurrera, + + + + + + + + + + + +
          +%(listname)s Zerrendako %(who)s + Identifikatzeko Orria +
          %(who)sren Pasahitza:
          +
          +

          Garrantzitsua: Hemendik aurrera, cookie-ak behar dituzu nabigatzailean, bestela aldaketa administratiboak ez dira gauzatuko. @@ -34,6 +95,6 @@ nabigatzailea itxi bezain azkar; nahi baduzu, zuk ere zuzenean ezgaitu ditzakezu Kudeatzailearen Beste Jarduera Batzuk barruan, Saioa Amaitu sakatuz (aukera hori behin sartu ondoren agertuko zaizu, ez lehenago). -

          +

          diff --git a/templates/eu/archidxentry.html b/templates/eu/archidxentry.html index f9bb57aa..5c620b20 100644 --- a/templates/eu/archidxentry.html +++ b/templates/eu/archidxentry.html @@ -1,4 +1,68 @@ -
        • %(subject)s -  -%(author)s - +
        • %(subject)s +  +%(author)s + +
        • \ No newline at end of file diff --git a/templates/eu/archidxfoot.html b/templates/eu/archidxfoot.html index 078534ed..07c0fbb4 100644 --- a/templates/eu/archidxfoot.html +++ b/templates/eu/archidxfoot.html @@ -1,21 +1,85 @@ - -

          - Azkenengo mezua: - %(lastdate)s
          - Artxibatuta: %(archivedate)s -

          -

            -
          • Antolatzeko irizpideak: + +

            +Azkenengo mezua: +%(lastdate)s
            +Artxibatuta: %(archivedate)s +

            +

            -

            -


            - Artxibo hau sortzeko +
          +

          +


          +Artxibo hau sortzeko Pipermail %(version)s erabili da. - - + + +

          \ No newline at end of file diff --git a/templates/eu/archidxhead.html b/templates/eu/archidxhead.html index 180a898b..ac0eb698 100644 --- a/templates/eu/archidxhead.html +++ b/templates/eu/archidxhead.html @@ -1,15 +1,79 @@ - - - %(listname)s %(archive)s Izeneko Artxiboa. - + + + +%(listname)s %(archive)s Izeneko Artxiboa. + %(encoding)s - - - -

          "%(archive)s" Izeneko Artxiboa. Mezuen ordena: %(archtype)s

          -
            -
          • Mezuak honen arabera antolatuta: + + + +

            "%(archive)s" Izeneko Artxiboa. Mezuen ordena: %(archtype)s

            + -

            Hasi: %(firstdate)s
            - Amaitu: %(lastdate)s
            - Mezuak: %(size)s

            -

              +
            +

            Hasi: %(firstdate)s
            +Amaitu: %(lastdate)s
            +Mezuak: %(size)s

            +

              +

            \ No newline at end of file diff --git a/templates/eu/archlistend.html b/templates/eu/archlistend.html index 9bc052dd..2e1191b0 100644 --- a/templates/eu/archlistend.html +++ b/templates/eu/archlistend.html @@ -1 +1,64 @@ -
          + diff --git a/templates/eu/archliststart.html b/templates/eu/archliststart.html index a28fc83b..7aea0a63 100644 --- a/templates/eu/archliststart.html +++ b/templates/eu/archliststart.html @@ -1,4 +1,70 @@ - - - - +
          Artxiboa:Ikusteko:Deskargatutako bertsioa:
          + + + + + +
          Artxiboa:Ikusteko:Deskargatutako bertsioa:
          diff --git a/templates/eu/archtoc.html b/templates/eu/archtoc.html index 80287a9e..d8e38295 100644 --- a/templates/eu/archtoc.html +++ b/templates/eu/archtoc.html @@ -1,13 +1,77 @@ - - - %(listname)s Zerrendako Artxiboak - + + + +%(listname)s Zerrendako Artxiboak + %(meta)s - - -

          %(listname)s Zerrendako Artxiboak

          -

          + + +

          %(listname)s Zerrendako Artxiboak

          +

          Zerrenda honi buruzko informazio gehiago hemen edo deskargatu artxibo osoa (%(size)s). @@ -16,5 +80,5 @@

          %(listname)s Zerrendako Artxiboak

          %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/eu/archtocentry.html b/templates/eu/archtocentry.html index e61f58c5..d9126b7b 100644 --- a/templates/eu/archtocentry.html +++ b/templates/eu/archtocentry.html @@ -1,13 +1,74 @@ - - -
          %(archivelabel)s: - [ Haria ] - [ Gaia ] - [ Egilea ] - [ Data ] -
          %(archivelabel)s: +[ Haria ] +[ Gaia ] +[ Egilea ] +[ Data ] +
          - - - - - - - - - - - - - - - - - - + + + + + +

          - -- - -
          -

            -

          - Posta Zerrendaren gaia: - - - -
          -

          -

          Orain arte zerrendara bidalitako mezuak ikusteko, ikus itzazu artxibo hauek: - - Artxiboak: . - -

          -
          - Posta Zerrendan nola parte hartu: -
          - Zerrendakide guztiei mezua bidaltzeko, idatzi hona: - . + + + +<mm-list-name> Zerrendaren Informazio Orria</mm-list-name> + + +

          + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + +
          + -- +
          +

           

          +
          + Posta Zerrendaren gaia: + + + +
          +

          +

          Orain arte zerrendara bidalitako mezuak ikusteko, ikus itzazu artxibo hauek: + + Artxiboak: . + +

          +
          + Posta Zerrendan nola parte hartu: +
          + Zerrendakide guztiei mezua bidaltzeko, idatzi hona: + . -

          Zerrendakide egiteko edo zerrenda uzteko, - beheko atalera jo. -

          - Posta Zerrendan Harpidetzeko: -
          -

          - posta zerrendan izena eman, ondoko eskaera-orria betez. - -

            - - - - - - - - - - - - + + + + + + - - - - - -
            Helbide elektronikoa: -  
            Zure izena (hautazkoa): 
            Nahi baduzu, pasahitz bat sar dezakezu hemen - azpian. Ez dizu seguritate osoa emango, baina inork ezingo du - zure izenean mezurik bidali. - Pasahitz baliotsurik ez erabili, posta elektronikoz - bidaliko zaizulako inoiz, eta zifratu gabe. +

            Zerrendakide egiteko edo zerrenda uzteko, + beheko atalera jo. +

            +
            + Posta Zerrendan Harpidetzeko: +
            +

            + posta zerrendan izena eman, ondoko eskaera-orria betez. + +

              + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - -
              Helbide elektronikoa: 
              Zure izena (hautazkoa): 
              +Nahi baduzu, pasahitz bat sar dezakezu hemen + azpian. Ez dizu seguritate osoa emango, baina inork ezingo du + zure izenean mezurik bidali. + Pasahitz baliotsurik ez erabili, posta elektronikoz + bidaliko zaizulako inoiz, eta zifratu gabe. -

              Ez baduzu pasahitzik sartzen, sistemak automatikoki - sortuko du bat zuretzat; mezu batean bidaliko zaizu, behin - harpidetza berretsi ondoren. Pasahitza ahazten baduzu, - aukera pertsonalen orrian pasahitza posta - elektronikoz jasotzea eska dezakezu. - - -
              Pasahitza sartu: 
              Pasahitza berretsi: 
              Zein hizkuntza nahi duzu mezuak ikusteko?  
              Egunean zehar bidalitako mezuak denak batera bilduma batean jaso nahi dituzu? - Ez - Bai -
              -
              -
              - -
            -
            - - Posta Zerrendako Harpidedunak: -
            - - - -

            - - - -

            - - - +

            Ez baduzu pasahitzik sartzen, sistemak automatikoki + sortuko du bat zuretzat; mezu batean bidaliko zaizu, behin + harpidetza berretsi ondoren. Pasahitza ahazten baduzu, + aukera pertsonalen orrian pasahitza posta + elektronikoz jasotzea eska dezakezu. + + +
          Pasahitza sartu: 
          Pasahitza berretsi: 
          Zein hizkuntza nahi duzu mezuak ikusteko? 
          Egunean zehar bidalitako mezuak denak batera bilduma batean jaso nahi dituzu? Ez + Bai +
          + +
          + + +

          + + Posta Zerrendako Harpidedunak: + +
          + + + +

          + + + +

          + +

          + diff --git a/templates/eu/options.html b/templates/eu/options.html index 2d387225..84286b4f 100644 --- a/templates/eu/options.html +++ b/templates/eu/options.html @@ -1,43 +1,103 @@ - - <MM-Presentable-User> Harpidedunaren Konfigurazioa <MM-List-Name> Zerrendan - - - - - -
          - - Zerrendakidearen Konfigurazioa - Posta Zerrendan -
          + +<mm-presentable-user> Harpidedunaren Konfigurazioa <mm-list-name> Zerrendan + </mm-list-name></mm-presentable-user> + + + + +
          + + Zerrendakidearen Konfigurazioa + Posta Zerrendan +

          - - - - - +
          - zerrendakidearen harpidetza egoera, - pasahitza eta konfigurazio pertsonala zerrendan. -
          - - - - -

          -

          + + + +
          + zerrendakidearen harpidetza egoera, + pasahitza eta konfigurazio pertsonala zerrendan. +
          + + +

          +

          - - +

          - - - + +
          - - zerrendako harpidetzaren konfigurazioa aldatzeko: -
          Harpidetza egin zenuenean emandako helbidea aldatu egin + + + - - - - - -
          + + zerrendako harpidetzaren konfigurazioa aldatzeko: +
          Harpidetza egin zenuenean emandako helbidea aldatu egin dezakezu, behean helbide berria sartuz. Kontuan izan konfirmazio mezu bat bidaliko dela helbide berrira, eta aldaketa ez dela ontzat emango zuk helbide berritik berretsi arte. @@ -50,199 +110,178 @@ badituzu aldaketak, gaitu Denak Aldatu aukera. -
          - - - - - - - -
          Helbide berria:
          Berriro sartu:
          -
          - - +
          Izena +

          + + + + + + + +
          Helbide berria:
          Berriro sartu:
          +
          + + - - -
          Izena (hautazkoa):
          -
          -

          Denak Aldatu

          - +
          +

          +

          Denak Aldatu

          - - - - - + +

          -

          - posta zerrenda utzi - zerbitzariko beste harpidetzak -
          + + + + - + +
          +

          + posta zerrenda utzi + zerbitzariko beste harpidetzak +
          Zerrenda uzteko konfirmazio kutxa gaitu, eta sakatu botoi hau. Kontuz: Akzio hau oraintxe bertan gauzatuko da!

          -

          - -en dituzun posta zerrenda guztien laburpena ere ikusi dezakezu. +

          +-en dituzun posta zerrenda guztien laburpena ere ikusi dezakezu. Erabili aukera hau aldaketak zerrenda guztietan batera egin nahi badituzu.

          -

          -
          - - - - - +
          - Zure Pasahitza -
          - -
          -

          Pasahitza ahaztu?

          -
          + + + - -
          +Zure Pasahitza +
          + +
          +

          Pasahitza ahaztu?

          +
          Hemen klikatzen baduzu, pasahitza bidaliko dizugu zure harpide helbidera. -

          -

          - -
          -
          - -
          -

          Pasahitza Aldatu

          - - - - - - - - -
          Pasahitz - berria:
          Berriro - sartu:
          - - -

          Denak Aldatu. -
          -
          - +

          +

          + +
          +

          + +
          +

          Pasahitza Aldatu

          + + + + + + + + +
          Pasahitz + berria:
          Berriro + sartu:
          + +

          Denak Aldatu. +
          +

          - - +
          - Zerrendako Harpidetzaren Ezaugarriak -
          +
          + Zerrendako Harpidetzaren Ezaugarriak +
          -

          Orain dituzun balioak egiaztatu dira. -

          Kontuan izan aukeretako zenbaitzuek Denak Batera kutxatila dutela. Aukera hori gaituta, egiten dituzun aldaketak zerbitzariko zerrenda guztietan gauzatuko zaizkizu. Klikatu Beste Harpidetzen Zerrenda harpidetuta zauden zerrenda guztien izenak ikusteko.

          - - - - + + + + %(textlink)s - diff --git a/templates/fa/archtocnombox.html b/templates/fa/archtocnombox.html index 83b73448..9e9d36f9 100644 --- a/templates/fa/archtocnombox.html +++ b/templates/fa/archtocnombox.html @@ -1,18 +1,82 @@ - - - بایگانی‌های %(listname)s - + + + +بایگانی‌های %(listname)s + %(meta)s - - -

          بایگانی‌های %(listname)s

          -

          + + +

          بایگانی‌های %(listname)s

          +

          شما Ù…ÛŒ توانید اطلاعات بیشتری درباره‌ی این Ùهرست بگیرید.

          %(noarchive_msg)s %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/fa/article.html b/templates/fa/article.html index e7365857..b9ac87a0 100644 --- a/templates/fa/article.html +++ b/templates/fa/article.html @@ -1,12 +1,13 @@ - - - %(title)s - - - - - + +

          بایگانی‌های %(listname)s

          +

          هنوز هیچ پیامی به این Ùهرست ÙØ±Ø³ØªØ§Ø¯Ù‡ نشده، بنابراین بایگانی‌ها ÙØ¹Ù„اً خالی هستند. شما می‌توانید اطلاعات بیشتری درباره‌ی این Ùهرست کسب کنید.

          - - + + diff --git a/templates/fa/listinfo.html b/templates/fa/listinfo.html index 45b68ac8..1de19358 100644 --- a/templates/fa/listinfo.html +++ b/templates/fa/listinfo.html @@ -1,138 +1,199 @@ - - - - <MM-List-Name> ØµÙØ­Ù‡â€ŒÛŒ اطلاعات - - - -

          -

          - - Mezu Banaketa

          + + - +

          - - - + +

          - - - - + - - + - - + - - - - + + - - + - - + - - - +

          +
          + +Mezu Banaketa

          Aukera hau Gaitu zerrenda honetara bidaltzen diren mezuak jasotzeko. Zerrendakide izan nahi baduzu, baina denboraldi batez mezurik jaso nahi ez baduzu (esaterako, oporretara zoazelako), aukera ezazu Ezgaitu. Itzultzen zarenean berriro gaitu beharko duzu, gaitzea ez delako automatikoa. -

          - Gaitu
          - Ezgaitu

          - Denak Batera -

          +Gaitu
          +Ezgaitu

          +Denak Batera +

          - Bildumen Aukera Gaitu

          +

          +Bildumen Aukera Gaitu

          Aukera hau gaitzen baduzu, mezuak banan banan jaso beharrean, bilduta jasoko dituzu (gehienetan egunean behin; mezu askoko zerrendetan gehiago). Bildumen aukera ezgaitu ondoren ere, azkenengo mezu bilduma bat jasoko duzu. -

          - Ez
          - Bai -
          - Mezuak MIME formatuan ala testu hutsak?

          +

          +Ez
          +Bai +
          +Mezuak MIME formatuan ala testu hutsak?

          Gehienetan MIME formatua komenigarriagoa izaten da. Hala ere, mezuak irakurtzeko darabizun programak, beharbada ez du MIME kodeko mezurik onartuko. Hala bada, aukeratu testu hutsak jasotzea. -

          - MIME
          - Testu Hutsa

          - Denak Batera -

          +MIME
          +Testu Hutsa

          +Denak Batera +

          - Norbere mezuak ere jaso?

          +

          +Norbere mezuak ere jaso?

          Gehienetan, zerrendara bidaltzen dituzun mezuen kopiak jasoko dituzu zure helbidean. Zure mezuen kopiarik jasotzerik ez baduzu nahi, aukeratu Ez. -

          - Ez
          - Bai -
          - Zerrendara mezuren bat bidaltzen duzunean, konfirmazioa +

          +Ez
          +Bai +
          +Zerrendara mezuren bat bidaltzen duzunean, konfirmazioa jaso?

          -

          - Ez
          - Bai -
          - Zure pasahitza zein den gogorarazten dizun mezurik jaso nahi?

          +

          +Ez
          +Bai +
          +Zure pasahitza zein den gogorarazten dizun mezurik jaso nahi?

          Hilean behin, mezu bat bidaliko zaizu, zure zerrenda guztietako pasahitzak gogoraraziz. Nahi baduzu, mezurik ez jasotzeko aukera duzu Ez aukeratuz. Nahi bada, zerrenda batzuena bakarrik ezgaitu dezakezu; denak ezgaitzen badituzu, ez duzu mezurik jasoko. -

          - Ez
          - Bai

          - Denak Batera -

          - Zure izena ezkutatu?

          +

          +Ez
          +Bai

          +Denak Batera +

          +Zure izena ezkutatu?

          Erabiltzaile batek zerrendakideen zerrenda eskatzen duenean, zure helbidea ere agertuko da (nolabait moldatuta, spam-ak sahiesteko). Zure helbidea izen-zerrenda horretatik kendu nahi baduzu, aukeratu Ez. -

          - Ez
          - Bai -
          - Hizkuntza aukeratu

          -

          - -
          - Zein gaitako mezuak jaso nahi dituzu?

          +

          +Ez
          +Bai +
          +Hizkuntza aukeratu

          +

          + +
          +Zein gaitako mezuak jaso nahi dituzu?

          Gai bat (edo batzuk) aukeratuz, mezu guztien aukeraketa bat besterik ez duzu jasoko. Mezuaren gaia ez badator bat zure aukerekin, ez duzu mezu @@ -252,12 +291,11 @@

          Pasahitza Aldatu

          banaketa beheko aukeren arabera gauzatuko da. Ez baduzu gairik aukeratzen, posta zerrendako mezu guztiak jasoko dituzu. -
          - -
          - Zure gaiekin bat ez datozen mezuak ere jaso nahi dituzu?

          +

          + +
          +Zure gaiekin bat ez datozen mezuak ere jaso nahi dituzu?

          Aukera hau aktibatzeko, beharrezkoa izango da gutxienez goiko zerrendako gai bat aukeratzea. Ez aukeratzen baduzu zuk aukeratutako gaiarekin bat ez datozen mezuak ez dituzu jasoko; @@ -266,13 +304,12 @@

          Pasahitza Aldatu

          Ez baduzu gairik aukeratzen, zerrendako mezu guztiak jasoko dituzu. -

          - Ez
          - Bai -
          - Mezu duplikatuak sahiestu?

          +

          +Ez
          +Bai +
          +Mezu duplikatuak sahiestu?

          Zerrendara bidalitako mezu batean To: edo Cc: ataletan zure helbidea agertzen bada, mezua birritan jasoko duzu. Hori sahiesteko, aukeratu @@ -283,21 +320,17 @@

          Pasahitza Aldatu

          eta kopiak jasotzea erabakitzen baduzu, mezu guztien goiburuan X-Mailman-Kopia: Bai erantsiko da. -
          - Ez
          - Bai

          - Denak Batera -

          -
          -
          +Ez
          +Bai

          +Denak Batera +

          +
          +
          -

          - - - - + + +

          diff --git a/templates/eu/private.html b/templates/eu/private.html index 18f74c04..ef22ad89 100755 --- a/templates/eu/private.html +++ b/templates/eu/private.html @@ -1,34 +1,95 @@ - %(realname)s Artxibo Pribatuak Egiaztatzea - - - -
          +%(realname)s Artxibo Pribatuak Egiaztatzea + + + + %(message)s - - - - - - - - - - - - - - - -
          - %(realname)s Artxibo - Pribatuak Egiaztatzea -
          Posta elektronikoa:
          Pasahitza:
          -
          -

          Garrantzitsua: Hemendik aurrera, + + + + + + + + + + + + + + + +
          +%(realname)s Artxibo + Pribatuak Egiaztatzea +
          Posta elektronikoa:
          Pasahitza:
          +
          +

          Garrantzitsua: Hemendik aurrera, cookie-ak beharko dituzu nabigatzailean; bestela aldaketak ez dira gauzatuko. @@ -39,21 +100,21 @@ Kudeatzailearen Beste Jarduera Batzuk barruan, Saioa Amaitu sakatuz (aukera hori behin sartu ondoren agertuko zaizu, ez lehenago).

          - - - - - - + + + +
          - Password Reminder -
          If you don't remember your password, enter your email address + + + + + + - - - - -
          +Password Reminder +
          If you don't remember your password, enter your email address above and click the Remind button and your password will be emailed to you.
          - +
          +

          diff --git a/templates/eu/roster.html b/templates/eu/roster.html index 200edc98..5b8ce007 100644 --- a/templates/eu/roster.html +++ b/templates/eu/roster.html @@ -1,53 +1,112 @@ - - - <MM-List-Name> Zerrendako Harpidedunak - - - - -

          - - - - - - - - - - - - - - - -
          - - Zerrendako Harpidedunak -
          - -

          -

          - -

          Zure harpide-ezaugarriak ikusteko, klikatu zure helbidea. -
          (Parentesi artean daudenen izenak ezkutatuta daude) -

          -
          -
          - - zerrendan mezuak banaka jasotzen dituzten harpidedunen kopurua: -
          -
          -
          - - zerrendan mezuak bildumetan jasotzen dituzten harpidedunen kopurua: -
          -
          -

          -

          -

          -

          - - - + + +<mm-list-name> Zerrendako Harpidedunak</mm-list-name> + + +

          + + + + + + + + + + + + + + + +
          + + Zerrendako Harpidedunak +
          +

          +

          +

          Zure harpide-ezaugarriak ikusteko, klikatu zure helbidea. +
          (Parentesi artean daudenen izenak ezkutatuta daude) +

          +
          +
          + + zerrendan mezuak banaka jasotzen dituzten harpidedunen kopurua: +
          +
          +
          + + zerrendan mezuak bildumetan jasotzen dituzten harpidedunen kopurua: +
          +
          +

          +

          +

          +

          + +

          + diff --git a/templates/eu/subscribe.html b/templates/eu/subscribe.html index c5e78d25..45feedfc 100644 --- a/templates/eu/subscribe.html +++ b/templates/eu/subscribe.html @@ -1,9 +1,72 @@ -<MM-List-Name> Harpidetzaren emaitzak +<mm-list-name> Harpidetzaren emaitzak</mm-list-name> -

          Harpidetzaren emaitzak

          - - - +

          Harpidetzaren emaitzak

          + + + diff --git a/templates/fa/admlogin.html b/templates/fa/admlogin.html index 47304c49..99bf6693 100644 --- a/templates/fa/admlogin.html +++ b/templates/fa/admlogin.html @@ -1,29 +1,90 @@ - اصالت‌سنجی %(who)s در %(listname)s +اصالت‌سنجی %(who)s در %(listname)s - - -
          + + + %(message)s - - - - - - - - - - - -
          - اصالت‌سنجی %(who)s در %(listname)s -
          گذرواژه‌ی %(who)s Ùهرست :
          -
          -

          مهم: از این‌جا به بعد باید کلوچک‌ها را در مرورگرتان + + + + + + + + + + + +
          +اصالت‌سنجی %(who)s در %(listname)s +
          گذرواژه‌ی %(who)s Ùهرست :
          +
          +

          مهم: از این‌جا به بعد باید کلوچک‌ها را در مرورگرتان به‌کارانداخته باشید، در غیر این‌صورت تغییرات سرپرستی عمل نخواهند کرد. @@ -35,6 +96,6 @@ تحت قسمت دیگر ÙØ¹Ø§Ù„یت‌های سرپرستی خودتان دستی آن منقضی کنید. (این قسمت را وقتی با موÙقیت وارد شدید، خواهید دید.) -

          +

          diff --git a/templates/fa/archidxfoot.html b/templates/fa/archidxfoot.html index 923758ef..c04f6c8a 100644 --- a/templates/fa/archidxfoot.html +++ b/templates/fa/archidxfoot.html @@ -1,21 +1,85 @@ - -

          - تاریخ آخرین پیام: - %(lastdate)s
          - بایگانی شده در: %(archivedate)s -

          -

            -
          • پیام‌ها مرتب شده بر اساس: + +

            +تاریخ آخرین پیام: +%(lastdate)s
            +بایگانی شده در: %(archivedate)s +

            +

            -

            -


            - این بایگانی توسط +
          +

          +


          +این بایگانی توسط Pipermail نسخه‌ی %(version)s تولید شده است. - - + + +

          \ No newline at end of file diff --git a/templates/fa/archidxhead.html b/templates/fa/archidxhead.html index d443c6e6..191559cf 100644 --- a/templates/fa/archidxhead.html +++ b/templates/fa/archidxhead.html @@ -1,15 +1,79 @@ - - - بایگانی‌های %(archive)s Ùهرست %(listname)s براساس %(archtype)s - + + + +بایگانی‌های %(archive)s Ùهرست %(listname)s براساس %(archtype)s + %(encoding)s - - - -

          بایگانی‌های %(archive)s براساس %(archtype)s

          -
            -
          • مرتب‌سازی پیام‌ها بر اساس: + + + +

            بایگانی‌های %(archive)s براساس %(archtype)s

            + -

            شروع: %(firstdate)s
            - پایان: %(lastdate)s
            - پیام‌ها: %(size)s

            -

              +
            +

            شروع: %(firstdate)s
            +پایان: %(lastdate)s
            +پیام‌ها: %(size)s

            +

              +

            \ No newline at end of file diff --git a/templates/fa/archliststart.html b/templates/fa/archliststart.html index 9b7e90b5..920714a2 100644 --- a/templates/fa/archliststart.html +++ b/templates/fa/archliststart.html @@ -1,4 +1,68 @@ - - - - +
            بایگانیمشاهده بر اساس:نسخه قابل بارگیری
            + + + +
            بایگانیمشاهده بر اساس:نسخه قابل بارگیری
            \ No newline at end of file diff --git a/templates/fa/archtoc.html b/templates/fa/archtoc.html index 54500e70..0f4f604e 100644 --- a/templates/fa/archtoc.html +++ b/templates/fa/archtoc.html @@ -1,13 +1,77 @@ - - - بایگانی‌های %(listname)s - + + + +بایگانی‌های %(listname)s + %(meta)s - - -

            بایگانی‌های %(listname)s

            -

            + + +

            بایگانی‌های %(listname)s

            +

            شما Ù…ÛŒ توانید اطلاعات بیشتر درباره‌ی این Ùهرست بگیرید یا بایگانی کامل خام آن را بارگیری کنید (%(size)s). @@ -16,5 +80,5 @@

            بایگانی‌های %(listname)s

            %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/fa/archtocentry.html b/templates/fa/archtocentry.html index c2b740e2..3615c648 100644 --- a/templates/fa/archtocentry.html +++ b/templates/fa/archtocentry.html @@ -1,12 +1,74 @@ - -
          %(archivelabel)s: - [ مبحث ] - [ موضوع ] - [ پدید‌آورس ] - [ تاریخ ] -
          %(archivelabel)s: +[ مبحث ] +[ موضوع ] +[ پدید‌آورس ] +[ تاریخ ] +
          - - - - - - - - - - - - - - - - - - + + + + + +

          - -- - -
          -

            -

          - درباره - - - -
          -

          -

          برای مشاهده پیام‌های پیشین ارسال شده به Ùهرست - بایگانی‌های - را ببینید. - -

          -
          - با Ø§Ø³ØªÙØ§Ø¯Ù‡ از -
          + + + +<mm-list-name> ØµÙØ­Ù‡â€ŒÛŒ اطلاعات</mm-list-name> + + +

          + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + +
          + -- + +
          +

            +

          +درباره + + + +
          +

          +

          برای مشاهده پیام‌های پیشین ارسال شده به Ùهرست + بایگانی‌های + را ببینید. + +

          +
          +با Ø§Ø³ØªÙØ§Ø¯Ù‡ از +
          برای ÙØ±Ø³ØªØ§Ø¯Ù† پیام به Ú©Ù„ اعضای این Ùهرست، رایانامه‌ای به نشانی زیر Ø¨ÙØ±Ø³ØªÛŒØ¯: - . + .

          در بخش‌های زیر می‌توانید در این Ùهرست مشترک شوید یا اشتراک ÙØ¹Ù„ی‌تان را تغییر دهید.. -

          - مشترک شدن در -
          -

          - برای مشترک شدن در ÙØ±Ù… زیر را پر نمایید. - -

            - - - - - - - - - - - - + + + + + + - - - - - -
            نشانی رایانامه شما: -  
            نام شما (اختیاری): 
            Ù…ÛŒ توانید یک گذرواژه شخصی در زیر برای خود تعیین کنید. این گذرواژه ØµØ±ÙØ§Ù‹ برای ایجاد Ú©Ù…ÛŒ امنیت Ùˆ همچنین جلوگیری از دستکاری تنظیمات اشتراک شما توسط دیگران است. +

            +مشترک شدن در +
            +

            + برای مشترک شدن در ÙØ±Ù… زیر را پر نمایید. + +

              + + + + + + + + + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - - - -
              نشانی رایانامه شما: + 
              نام شما (اختیاری): 
              Ù…ÛŒ توانید یک گذرواژه شخصی در زیر برای خود تعیین کنید. این گذرواژه ØµØ±ÙØ§Ù‹ برای ایجاد Ú©Ù…ÛŒ امنیت Ùˆ همچنین جلوگیری از دستکاری تنظیمات اشتراک شما توسط دیگران است. گذرواژه های خیلی ارزشمند را در اینجا نگذارید زیرا گه‌گاه این گذرواژه به صورت خوانا از طریق رایانامه برایتان ÙØ±Ø³ØªØ§Ø¯Ù‡ Ù…ÛŒ شود. -

              اگر گذرواژه‌های وارد نکنید، به طور خودکار گذرواژه‌ای برای شما ایجاد می‌شود Ùˆ پس از تایید اشتراک، برای‌تان ÙØ±Ø³ØªØ§Ø¯Ù‡ می‌شود. همیشه Ù…ÛŒ توانید هنگام ویرایش گزینه‌های شخصی خود، درخواست کنید، گذرواژه‌تان برای‌تان ÙØ±Ø³ØªØ§Ø¯Ù‡ شود.. - -
              -
              یک گذرواژه انتخاب کنید: 
              با وارد کردن مجدد گذرواژه، آن را تایید کنید: 
              Ú†Ù‡ زبانی را برای نمایش پیام هایتان ترجیح می‌دهید؟  
              آیا دوست دارید تمام پیام‌های هر روز را در یک رایانامه به صورت یک‌جا Ø¯Ø±ÛŒØ§ÙØª کنید؟ +

              اگر گذرواژه‌های وارد نکنید، به طور خودکار گذرواژه‌ای برای شما ایجاد می‌شود Ùˆ پس از تایید اشتراک، برای‌تان ÙØ±Ø³ØªØ§Ø¯Ù‡ می‌شود. همیشه Ù…ÛŒ توانید هنگام ویرایش گزینه‌های شخصی خود، درخواست کنید، گذرواژه‌تان برای‌تان ÙØ±Ø³ØªØ§Ø¯Ù‡ شود.. + + +
              یک گذرواژه انتخاب کنید: 
              با وارد کردن مجدد گذرواژه، آن را تایید کنید: 
              چه زبانی را برای نمایش پیام هایتان ترجیح می‌دهید؟  
              آیا دوست دارید تمام پیام‌های هر روز را در یک رایانامه به صورت یک‌جا Ø¯Ø±ÛŒØ§ÙØª کنید؟ خیر - بلی -
              -
              -
              - -
            -
            - - مشترکان -
            - - - -

            - - - -

            - - - +
          خیر + بلی +
          +
          +
          + + +

          + + مشترکان +
          + + + +

          + + + +

          + +

          + diff --git a/templates/fa/options.html b/templates/fa/options.html index c825c158..472245f1 100644 --- a/templates/fa/options.html +++ b/templates/fa/options.html @@ -1,41 +1,101 @@ - - <MM-Presentable-User> تنظیمات عضویت برای <MM-List-Name> - - - - - -
          - - تنظیمات عضویت Ùهرست پستی برای - -
          + +<mm-presentable-user> تنظیمات عضویت برای <mm-list-name> +</mm-list-name></mm-presentable-user> + + + + +
          + + تنظیمات عضویت Ùهرست پستی برای + +

          - - - - - +
          - وضعیت اشتراک، گذرواژه Ùˆ گزینه‌های در Ùهرست پستی. -
          - - - - -

          -

          + + + +
          +وضعیت اشتراک، گذرواژه Ùˆ گزینه‌های در Ùهرست پستی. +
          + + +

          +

          - - +

          - - - +
          - - تغییر دادن اطلاعات عضویت شما در -
          Ù…ÛŒ توانید نشانی رایانامه‌ای را Ú©Ù‡ با آن عضو این Ùهرست شده‌اید، با نشانی جدیدی Ú©Ù‡ در زیر وارد می‌کنید جایگزین کنید. به یاد داشته باشید Ú©Ù‡ پس از تغییر نشانی، یک رایانامه برای تایید نشانی جدید ÙØ±Ø³ØªØ§Ø¯Ù‡ می‌شود Ùˆ این تغییر تنها پس از پاسخ به تاییدیه اعمال خواهد شد. + + + - - - - - -
          + +تغییر دادن اطلاعات عضویت شما در +
          Ù…ÛŒ توانید نشانی رایانامه‌ای را Ú©Ù‡ با آن عضو این Ùهرست شده‌اید، با نشانی جدیدی Ú©Ù‡ در زیر وارد می‌کنید جایگزین کنید. به یاد داشته باشید Ú©Ù‡ پس از تغییر نشانی، یک رایانامه برای تایید نشانی جدید ÙØ±Ø³ØªØ§Ø¯Ù‡ می‌شود Ùˆ این تغییر تنها پس از پاسخ به تاییدیه اعمال خواهد شد.

          نامه‌ی تاییدیه، پس از مدت منقضی می‌شود. @@ -44,206 +104,179 @@

          اگر می‌خواهید تغییر اطلاعات عضویت شما در تمام Ùهرست های پستی اعمال شود، گزینه تغییر سراسری را تیک بزنید. -

          - - - - - - - -
          نشانی جدید:
          یک بار دیگر برای تایید:
          -
          - - - - -
          نام شما (اختیاری):
          -
          -

          تغییر سراسری

          - +

          + + + + + + + +
          نشانی جدید:
          یک بار دیگر برای تایید:
          +

          + + + + +
          نام شما (اختیاری):
          +
          +

          تغییر سراسری

          - - - - - + +

          -

          - لغو اشتراک از - دیگر اشتراک های شما در -
          + + + + - + +
          +

          + لغو اشتراک از +دیگر اشتراک های شما در +
          برای لغو اشتراک از این Ùهرست پستی گزینه‌ی «تایید» را تیک بزنید Ùˆ روی این دکمه کلیک کنید هشدار: این کار به صورت درجا Ùˆ آنی انجام خواهد شد.

          -

          +

          می‌توانید Ùهرستی از تمام دیگر Ùهرست‌های پستی موجود در را Ú©Ù‡ عضو آن شده‌اید ببینید. اگر می‌خواهید تغییرات عضویت درج شده در اینجا را در سایر اشتراک‌های خود نیز اعمال کنید، از این گزینه Ø§Ø³ØªÙØ§Ø¯Ù‡ کنید.

          -

          -
          - - - - - +
          - گذرواژه‌ی شما در -
          - -
          -

          آیا گذرواژه خود را ÙØ±Ø§Ù…وش کرده‌اید؟

          -
          + + + - -
          + گذرواژه‌ی شما در +
          + +
          +

          آیا گذرواژه خود را ÙØ±Ø§Ù…وش کرده‌اید؟

          +
          این دکمه را بزنید تا گذرواژه‌تان به نشانی رایانامه عضویت شما ارسال شود. -

          -

          - -
          -
          - -
          -

          تغییر گذرواژه‌تان

          - - - - - - - - -
          گذرواژه‌ی جدید:
          یک بار دیگر برای تایید:
          - - -

          تغییر سراسری. -
          -
          - +

          +

          + +
          +

          + +
          +

          تغییر گذرواژه‌تان

          + + + + + + + + +
          گذرواژه‌ی جدید:
          یک بار دیگر برای تایید:
          + +

          تغییر سراسری. +
          +

          - - +
          - گزینه های اشتراک شما در -
          +
          + گزینه های اشتراک شما در +
          -

          Current values are checked. -

          دقت شود Ú©Ù‡ بعضی تنظیمات، دارای گزینه تغییر سراسری هستند. تیک زدن این گزینه باعث Ù…ÛŒ شود تغییرات در تمام Ùهرست‌های پستی Ú©Ù‡ در عضو آنها هستید اعمال شود. روی گزینه Ùهرست دیگر اشتراک‌های من در بالا کلیک کنید تا ببینید عضو Ú†Ù‡ Ùهرست‌های پستی دیگری هستید.

          - - - +
          - - رساندن رایانامه

          - این گزینه را روی ÙØ¹Ø§Ù„ بگذارید تا پیام‌هایی Ú©Ù‡ به Ùهرست پستی کنونی ÙØ±Ø³ØªØ§Ø¯Ù‡ Ù…ÛŒ شوند را Ø¯Ø±ÛŒØ§ÙØª کنید. اگر می‌خواهید همچنان مشترک باشید ولی ÙØ¹Ù„ا رایانامه‌ای Ø¯Ø±ÛŒØ§ÙØª کنید روی از کار Ø§ÙØªØ§Ø¯Ù‡ تنظیم‌اش کنید.(مثلاً می‌خواهید به Ø³ÙØ±ÛŒ بروید). اگر رساندن رایانامه را از کار انداختید، ÙØ±Ø§Ù…وش نکنید Ú©Ù‡ وقتی برگشتید دوباره ÙØ¹Ø§Ù„‌ش کنید Ú©Ù‡ خودش خودکار، دوباره ÙØ¹Ø§Ù„ نخواهد شد.

          - ÙØ¹Ø§Ù„
          - از کار Ø§ÙØªØ§Ø¯Ù‡

          - تغییر سراسری -

          + - - - + +

          - - - - - - + + - - + - - - - + + - - + - - + - - - +

          +
          + + رساندن رایانامه

          + این گزینه را روی ÙØ¹Ø§Ù„ بگذارید تا پیام‌هایی Ú©Ù‡ به Ùهرست پستی کنونی ÙØ±Ø³ØªØ§Ø¯Ù‡ Ù…ÛŒ شوند را Ø¯Ø±ÛŒØ§ÙØª کنید. اگر می‌خواهید همچنان مشترک باشید ولی ÙØ¹Ù„ا رایانامه‌ای Ø¯Ø±ÛŒØ§ÙØª کنید روی از کار Ø§ÙØªØ§Ø¯Ù‡ تنظیم‌اش کنید.(مثلاً می‌خواهید به Ø³ÙØ±ÛŒ بروید). اگر رساندن رایانامه را از کار انداختید، ÙØ±Ø§Ù…وش نکنید Ú©Ù‡ وقتی برگشتید دوباره ÙØ¹Ø§Ù„‌ش کنید Ú©Ù‡ خودش خودکار، دوباره ÙØ¹Ø§Ù„ نخواهد شد.

          +ÙØ¹Ø§Ù„
          + از کار Ø§ÙØªØ§Ø¯Ù‡

          +تغییر سراسری +

          - تنظیم حالت یک‌جا

          +

          +تنظیم حالت یک‌جا

          اگر حالت یک‌جا را ÙØ¹Ø§Ù„ کنید، پیام‌ها به صورت دسته جمعی در یک رایانامه به دست‌تان Ù…ÛŒ رسد.(معمولاً یک بار در روز، ولی احتمالا در لیست های شلوغ، از این هم بیشتر), ولی دیگر رایانامه‌ها به صورت تک‌تک به محض ÙØ±Ø³ØªØ§Ø¯Ù‡ شدن هر پیام برایتان نمی‌آید. اگر حالت تحویل یک‌جا را از روشن به خاموش تغییر دهید، ممکن است یک رایانامه‌ی یک‌جای دیگر نیز به عنوان آخرین Ø¯Ø±ÛŒØ§ÙØª کنید. -

          - خاموش
          - روشن -
          - Ø¯Ø±ÛŒØ§ÙØª حالت یک‌جا به صورت MIME یا متن ساده؟

          +

          + خاموش
          + روشن +
          +Ø¯Ø±ÛŒØ§ÙØª حالت یک‌جا به صورت MIME یا متن ساده؟

          بعضی رایانامه‌خوان‌ها، حالت یک‌جا با MIME را پشتیبانی می کنند و برخی نه. معمولاً رایانامه‌های یکجای MIME بهتر هستند ولی چنانچه برای خواندن‌شان مشکلی دارید، حالت متن ساده را انتخاب نمایید. -

          - MIME
          - متن ساده

          - تغییر سراسری -

          + MIME
          + متن ساده

          +تغییر سراسری +

          - Ø¯Ø±ÛŒØ§ÙØª پیام‌های خودتان به Ùهرست

          +

          +Ø¯Ø±ÛŒØ§ÙØª پیام‌های خودتان به Ùهرست

          به طور معمول، هر پیامی Ú©Ù‡ به Ùهرست Ù…ÛŒ ÙØ±Ø³ØªÛŒØ¯ØŒ یک رونوشت از آن را نیز خودتان Ø¯Ø±ÛŒØ§ÙØª Ù…ÛŒ نمایید. چنان‌چه نمی‌خواهید این رونوشت برای‌تان ÙØ±Ø³ØªØ§Ø¯Ù‡ شود گزینه‌ی خیر را انتخاب کنید. -

          - خیر
          - بله -
          - آیا Ù…ÛŒ خواهید هنگامی Ú©Ù‡ پیامی به گروه Ù…ÛŒâ€ŒÙØ±Ø³ØªÛŒØ¯ØŒ تاییدیه‌ی ÙØ±Ø³ØªØ§Ø¯Ù‡â€ŒØ´Ø¯Ù† پیام را Ø¯Ø±ÛŒØ§ÙØª کنید؟.

          -

          - خیر
          - بلی -
          - Ø¯Ø±ÛŒØ§ÙØª رایانامه‌ی یادآوری گذرواژه برای این Ùهرست

          +

          +خیر
          +بله +
          + آیا Ù…ÛŒ خواهید هنگامی Ú©Ù‡ پیامی به گروه Ù…ÛŒâ€ŒÙØ±Ø³ØªÛŒØ¯ØŒ تاییدیه‌ی ÙØ±Ø³ØªØ§Ø¯Ù‡â€ŒØ´Ø¯Ù† پیام را Ø¯Ø±ÛŒØ§ÙØª کنید؟.

          +

          +خیر
          +بلی +
          +Ø¯Ø±ÛŒØ§ÙØª رایانامه‌ی یادآوری گذرواژه برای این Ùهرست

          هر ماه یک‌بار یک رایانامه دربردارنده‌ی یادآوری گذرواژه‌ی شما برای هر یک از Ùهرست‌های روی این میزبان Ú©Ù‡ مشترک‌شان هستید، ÙØ±Ø³ØªØ§Ø¯Ù‡ می‌شود. شما می‌توانید با انتخاب خیر این امکان را برای هر Ùهرست جداگانه از کار بیندازید. اگر یادآور گذرواژه را برای همه‌ی Ùهرست‌ها از کاربیندازید، هیچ یادآور گذرواژه‌ای Ø¯Ø±ÛŒØ§ÙØª نخواهید کرد. -

          - خیر
          - بلی

          - تغییر سراسری -

          - پنهان کردن نام‌تان از Ùهرست مشترکان

          +

          +خیر
          +بلی

          +تغییر سراسری +

          +پنهان کردن نام‌تان از Ùهرست مشترکان

          هنگامی Ú©Ù‡ ÙØ±Ø¯ÛŒ اعضای Ùهرست را می‌بینید، به طور معمول نشانی رایانامه‌ی شما دیده می‌شود(به Ø´Ú©Ù„ÛŒ عجق وجغ Ú©Ù‡ دروگرهای هرزنامه را سرÙکار بگذارد). اگر نمی‌خواهد اصلا نشانی رایانامه‌تان در این صورت اعضا بیاید، بلی را انتخاب کنید. -

          - خیر
          - بلی -
          - چه زبانی را ترجیح می دهید؟

          -

          - -
          - دوست دارید در کدام دسته از Ø³Ø±ÙØµÙ„‌های زیر، مشترک شوید؟

          +

          +خیر
          +بلی +
          +چه زبانی را ترجیح می دهید؟

          +

          + +
          +دوست دارید در کدام دسته از Ø³Ø±ÙØµÙ„‌های زیر، مشترک شوید؟

          با انتخاب یک یا چند Ø³Ø±ÙØµÙ„ØŒ می‌توانید تراÙیک Ùهرست پستی را برای خود پالایش کنید تا Ùقط زیرگروهی از پیام‌ها را Ø¯Ø±ÛŒØ§ÙØª کنید. اگر پیامی با یکی از Ø³Ø±ÙØµÙ„‌هایی Ú©Ù‡ انتخاب کرده‌اید، جور باشد، آن‌گاه آن را Ø¯Ø±ÛŒØ§ÙØª خواهید کرد Ùˆ در غیراین‌صورت نه.

          اگر پیامی با هیچ‌کدام از Ø³Ø±ÙØµÙ„‌ها جور درنیاید، ÙØ±Ø³ØªØ§Ø¯Ù‡ شدن آن به تنظیم گزینه‌ی زیر بستگی دارد. اگر هیچ Ø³Ø±ÙØµÙ„ مورد علاقه‌ای را انتخاب نکنید، تمام پیام‌های ÙØ±Ø³ØªØ§Ø¯Ù‡ شده با Ùهرست پستی را Ø¯Ø±ÛŒØ§ÙØª خواهید کرد. -

          - -
          - آیا دوست دارید رایانامه‌ایی Ú©Ù‡ با هیچ یک از پالایه‌های Ø³Ø±ÙØµÙ„ÛŒ جور نیست را Ø¯Ø±ÛŒØ§ÙØª کنید؟

          +

          + +
          +آیا دوست دارید رایانامه‌ایی Ú©Ù‡ با هیچ یک از پالایه‌های Ø³Ø±ÙØµÙ„ÛŒ جور نیست را Ø¯Ø±ÛŒØ§ÙØª کنید؟

          این گزینه تنها وقتی اثر خواهد کرد Ú©Ù‡ شما حداقل در یکی از Ø³Ø±ÙØµÙ„‌های بالا مشترک شده باشید. این گزینه مشخص می‌کند Ú©Ù‡ برای پیام‌هایی Ú©Ù‡ با هیچ پالایه‌ی Ø³Ø±ÙØµÙ„ÛŒ جور نیستند، قاعده‌ی Ù¾ÛŒØ´â€ŒÙØ±Ø¶ چیست؟ اگر خیر را انتخاب کنید، این پیام‌ها را Ø¯Ø±ÛŒØ§ÙØª نخواهید کرد Ùˆ انتخاب بلی این پیام‌های جور نشده با پالایه‌ی Ø³Ø±ÙØµÙ„ را برای‌تان خواهد ÙØ±Ø³ØªØ§Ø¯.

          اگر هیچ Ø³Ø±ÙØµÙ„ مورد علاقه‌ای را در بالا انتخاب نکرده باشد، تمام پیام‌های ÙØ±Ø³ØªØ§Ø¯Ù‡ شده به Ùهرست پستی را Ø¯Ø±ÛŒØ§ÙØª خواهید کرد. -

          - خیر
          - بلی -
          - آیا جلوی رونوشت‌های تکراری از یک پیام Ú¯Ø±ÙØªÙ‡ شود؟

          +

          +خیر
          +بلی +
          +آیا جلوی رونوشت‌های تکراری از یک پیام Ú¯Ø±ÙØªÙ‡ شود؟

          اگر نشانی شما به طور صریح در قسمت To: یا Cc: سربرگ پیام آمده باشد، می‌توانید انتخاب کنید Ú©Ù‡ رونوشت دیگری از طر٠Ùهرست پستی Ø¯Ø±ÛŒØ§ÙØª نکنید. برای اجتناب از Ø¯Ø±ÛŒØ§ÙØª این رونوشت‌های از Ùهرست پستی بلی را انتخاب کنید؛ Ùˆ برای Ø¯Ø±ÛŒØ§ÙØª آن‌ها خیر را.

          اگر ویژگی پیام‌های شخصی‌شده برای اعضا در این Ùهرست پستی ÙØ¹Ø§Ù„ باشد، Ùˆ شما Ø¯Ø±ÛŒØ§ÙØª کردن رونوشت‌ها را انتخاب کرده باشد، هر رونوشتی Ú©Ù‡ Ø¯Ø±ÛŒØ§ÙØª می‌کنید عبارت X-Mailman-Copy: yes به سربرگ آن پیام Ø§ÙØ²ÙˆØ¯Ù‡ خواهد شد. -

          - خیر
          - بلی

          - تغییر سراسری -

          -
          -
          +خیر
          +بلی

          +تغییر سراسری +

          +
          +
          -

          - - - - + + +

          diff --git a/templates/fa/private.html b/templates/fa/private.html index e7b9fe55..2821734d 100644 --- a/templates/fa/private.html +++ b/templates/fa/private.html @@ -1,33 +1,94 @@ - اصالت‌سنجی برای دسترسی به بایگانی‌های خصوصی %(realname)s + اصالت‌سنجی برای دسترسی به بایگانی‌های خصوصی %(realname)s - - -
          + + + %(message)s - - - - - - - - - - - - - - - -
          - اصالت‌سنجی برای دسترسی به بایگانی‌های خصوصی %(realname)s -
          نشانی رایانامه:
          گذرواژه:
          -
          -

          مهم: از این‌جا به بعد، باید کلوچک‌ها را در مرورگر خود ÙØ¹Ø§Ù„ کرده باشید، وگرنه مجبور خواهید بود برای هر عملیاتی دوباره اصالت‌سنجی شوید. + + + + + + + + + + + + + + + +
          + اصالت‌سنجی برای دسترسی به بایگانی‌های خصوصی %(realname)s +
          نشانی رایانامه:
          گذرواژه:
          +
          +

          مهم: از این‌جا به بعد، باید کلوچک‌ها را در مرورگر خود ÙØ¹Ø§Ù„ کرده باشید، وگرنه مجبور خواهید بود برای هر عملیاتی دوباره اصالت‌سنجی شوید.

          واسط بایگانی‌های خصوصی میل‌من از کلوچک‌های نشست Ø§Ø³ØªÙØ§Ø¯Ù‡ می‌کند @@ -36,21 +97,21 @@ یا هم خودتان می‌توانید با Ø±ÙØªÙ† به ØµÙØ­Ù‡â€ŒÛŒ گزیه‌های عضویت‌تان Ùˆ کلیک کردن خروج از سیستم آن را مستقیما منقضی کنید.

          - - - - - - + + + +
          - Password Reminder -
          اگر گذرواژه‌تان را به یاد ندارید، نشانی + + + + + + - - - -
          +Password Reminder +
          اگر گذرواژه‌تان را به یاد ندارید، نشانی رایانامه‌تان را وارد کنید Ùˆ روی یادآوری Ú©Ù† کلیک کنید تا گذرواژه‌تان برایتان ÙØ±Ø³ØªØ§Ø¯Ù‡ شود. -
          - +
          +

          diff --git a/templates/fa/roster.html b/templates/fa/roster.html index 327db7bd..8997b6ad 100644 --- a/templates/fa/roster.html +++ b/templates/fa/roster.html @@ -1,50 +1,109 @@ - - - <MM-List-Name> مشترک‌ها - - - - -

          - - - - - - - - - - - - - - - -
          - - مشترک‌ها -
          - -

          -

          - -

          برای دیدن ØµÙØ­Ù‡â€ŒÛŒ گزینه‌های اشتراک‌تان روی نشانی خود کلیک کنید.
          (ورودی‌هایی Ú©Ù‡ بین پرانتز قرار Ú¯Ø±ÙØªÙ‡â€ŒØ§Ù†Ø¯ØŒ ÙØ±Ø³ØªØ§Ø¯Ù† رایانامه از Ùهرست را از کار انداخته‌اند.)

          -
          -
          - - اعضای حالت غیر‌یکجا در : -
          -
          -
          - اعضای حالت یکجا در : -
          -
          -

          -

          -

          -

          - - - + + +<mm-list-name> مشترک‌ها </mm-list-name> + + +

          + + + + + + + + + + + + + + + +
          + + مشترک‌ها +
          +

          +

          +

          برای دیدن ØµÙØ­Ù‡â€ŒÛŒ گزینه‌های اشتراک‌تان روی نشانی خود کلیک کنید.
          (ورودی‌هایی Ú©Ù‡ بین پرانتز قرار Ú¯Ø±ÙØªÙ‡â€ŒØ§Ù†Ø¯ØŒ ÙØ±Ø³ØªØ§Ø¯Ù† رایانامه از Ùهرست را از کار انداخته‌اند.)

          +
          +
          + + اعضای حالت غیر‌یکجا در : +
          +
          +
          + اعضای حالت یکجا در : +
          +
          +

          +

          +

          +

          + +

          + diff --git a/templates/fa/subscribe.html b/templates/fa/subscribe.html index 98d319b1..38fdc3ec 100644 --- a/templates/fa/subscribe.html +++ b/templates/fa/subscribe.html @@ -1,9 +1,72 @@ -<MM-List-Name> نتایج اشتراک +<mm-list-name> نتایج اشتراک </mm-list-name> -

          نتایج اشتراک

          - - - +

          نتایج اشتراک

          + + + diff --git a/templates/fi/admindbdetails.html b/templates/fi/admindbdetails.html index 238d7465..dcb2e8fb 100644 --- a/templates/fi/admindbdetails.html +++ b/templates/fi/admindbdetails.html @@ -1,62 +1,123 @@ -Ylläpitäjän pyynnöt kootaan yhdeksi kahdesta eri kohteesta, yhteenvetosivulta,ja details -yksityiskohtasivulta. Yhteenvetosivu sisältää vahvistusta odottamassa -olevat listalle liittymis- ja irtisanoutumispyynnöt, sekä hyväksymistä -odottavat postit, jotka on ryhmitelty lähettäjän osoitteen mukaan. -Yksityiskohtasivu sisältää tarkemmat tiedot kaikista hyväksymistä -odottavista viesteistä, sisältäen viestien otsikot ja osan viestin tekstiä. +Ylläpitäjän pyynnöt kootaan yhdeksi kahdesta eri kohteesta, yhteenvetosivulta,ja details +yksityiskohtasivulta. Yhteenvetosivu sisältää vahvistusta odottamassa +olevat listalle liittymis- ja irtisanoutumispyynnöt, sekä hyväksymistä +odottavat postit, jotka on ryhmitelty lähettäjän osoitteen mukaan. +Yksityiskohtasivu sisältää tarkemmat tiedot kaikista hyväksymistä +odottavista viesteistä, sisältäen viestien otsikot ja osan viestin tekstiä.

          Seuraavat toimenpiteet ovat mahdollisia kaikilla sivuilla:

            -
          • Siirrä -- Siirtää päätöksesi myöhemmäksi. Tälle odottavalle - ylläpitäjän vahvistuspyynnölle ei tehdä nyt mitään, paitsi jätetään - odottavaksi postiksi, voit silti ohjata viestin eteenpäin - tai säilyttää sen (katso allaolevaa). - -
          • Hyväksy -- Hyväksy viesti lähettämällä se listalle. - Jäsenyyspyynnöissä, hyväksyy muutoksen jäsenyystietoihin. - -
          • Hylkää -- Hylkää viestin, lähettää hylkäysilmoituksen - lähettäjälle ja poistaa alkuperäisen viestin. Jäsenyyspyynnöissä - hylkää muutoksen jäsenyystietoihin. Kummassakin tapauksessa, - pitää liittää mukaan teksti hylkäyksen syystä. - -
          • Poista -- Poista alkuperäinen viesti, ilman että lähetetään - hylkäysilmoitusta. Jäsenyyspyynnöissä pelkästään hävitetään pyyntö - eikä ilmoiteta siitä pyynnön esittäjälle. Toimintoa - käytetään yleensä tunnetulle roska(mainos)postille. -
          - -

          Odottavissa viesteissä aseta päälle Säilytä valinta jos haluat -säilyttää kopion viestistä ylläpitäjän sivulla. Toiminto on hyödyllinen +

        • Siirrä -- Siirtää päätöksesi myöhemmäksi. Tälle odottavalle + ylläpitäjän vahvistuspyynnölle ei tehdä nyt mitään, paitsi jätetään + odottavaksi postiksi, voit silti ohjata viestin eteenpäin + tai säilyttää sen (katso allaolevaa). + +
        • Hyväksy -- Hyväksy viesti lähettämällä se listalle. + Jäsenyyspyynnöissä, hyväksyy muutoksen jäsenyystietoihin. + +
        • Hylkää -- Hylkää viestin, lähettää hylkäysilmoituksen + lähettäjälle ja poistaa alkuperäisen viestin. Jäsenyyspyynnöissä + hylkää muutoksen jäsenyystietoihin. Kummassakin tapauksessa, + pitää liittää mukaan teksti hylkäyksen syystä. + +
        • Poista -- Poista alkuperäinen viesti, ilman että lähetetään + hylkäysilmoitusta. Jäsenyyspyynnöissä pelkästään hävitetään pyyntö + eikä ilmoiteta siitä pyynnön esittäjälle. Toimintoa + käytetään yleensä tunnetulle roska(mainos)postille. +
        • +

          Odottavissa viesteissä aseta päälle Säilytä valinta jos haluat +säilyttää kopion viestistä ylläpitäjän sivulla. Toiminto on hyödyllinen loukkaavien postien kohdalla, jotka haluat tuhota, mutta joista sinun -tarvitsee säilyttää tallennettua tietoa myöhempää tarkastelua varten. - -

          Laita päälle Lähetä eteenpäin valinta, ja täytä vastaanottajan -osoitekenttä, jos haluat lähettää viestin jolle kulle, joka ei kuulu -listalle. Muuttaaksesi odottavaa viestiä ennen kuin se lähetetään listalle, -sinun pitää lähettää sen ensin itsellesi (tai listan omistajille) ja -hävittää alkuperäinen viesti. Sitten kun viesti näkyy viestilaatikossasi, -tee muutoksesi ja lähetä viesti uudelleen listalle, siten että se sisältää -Hyväksytty: otsikon sekä listan salasanan sen arvona. Se -on kunnon netiketti tässä tapauksessa sisällyttää huomautus viestiin siitä -närkästyksestä, että olet joutunut muuttamaan tekstiä. - -

          Yhteenvetosivulla voit myös lisätä sähköpostin osoitteen -lähettäjä filterointiin. Lähettäjä filtereitä on selitetty lähettäjän filterointi yksilöintisivulla, ja arvo +tarvitsee säilyttää tallennettua tietoa myöhempää tarkastelua varten. + +

          Laita päälle Lähetä eteenpäin valinta, ja täytä vastaanottajan +osoitekenttä, jos haluat lähettää viestin jolle kulle, joka ei kuulu +listalle. Muuttaaksesi odottavaa viestiä ennen kuin se lähetetään listalle, +sinun pitää lähettää sen ensin itsellesi (tai listan omistajille) ja +hävittää alkuperäinen viesti. Sitten kun viesti näkyy viestilaatikossasi, +tee muutoksesi ja lähetä viesti uudelleen listalle, siten että se sisältää +Hyväksytty: otsikon sekä listan salasanan sen arvona. Se +on kunnon netiketti tässä tapauksessa sisällyttää huomautus viestiin siitä +närkästyksestä, että olet joutunut muuttamaan tekstiä. + +

          Yhteenvetosivulla voit myös lisätä sähköpostin osoitteen +lähettäjä filterointiin. Lähettäjä filtereitä on selitetty lähettäjän filterointi yksilöintisivulla, ja arvo voi olla yksi seuraavista: -Hyväksy -automaatisesti (Hyväksytyt), Odottaa automaattisesti +Hyväksy +automaatisesti (Hyväksytyt), Odottaa automaattisesti (Odottavat), -Hylätään automaattisesti (Hylätyt), tai Tuhotaan automaattisesti - (Tuhotut). Tämä arvo ei ole mahdollinen, jos osoite on jo -yksi lähettäjän filtterointi -osoite. +Hylätään automaattisesti (Hylätyt), tai Tuhotaan automaattisesti + (Tuhotut). Tämä arvo ei ole mahdollinen, jos osoite on jo +yksi lähettäjän filtterointi -osoite.

          Kun olet saanut kaiken valmiiksi, klikkaa sivun -ylä- tai alalaidassa sijaitsevaa Hyväksy -muutokset näppäintä. Tämä näppäin suorittaa kaikki valitut -toimenpiteet kaikille ylläpitäjän pyynnöille, joista olet päätöksen tehnyt. +ylä- tai alalaidassa sijaitsevaa Hyväksy +muutokset näppäintä. Tämä näppäin suorittaa kaikki valitut +toimenpiteet kaikille ylläpitäjän pyynnöille, joista olet päätöksen tehnyt.

          Paluu yhteenvetosivulle. +

          \ No newline at end of file diff --git a/templates/fi/admindbpreamble.html b/templates/fi/admindbpreamble.html index 24e16e0e..c6db93b0 100644 --- a/templates/fi/admindbpreamble.html +++ b/templates/fi/admindbpreamble.html @@ -1,9 +1,73 @@ -Tämä sivu sisältää osan postituslistan %(listname)s -viesteistä jotka ovat pidossa odottamassa hyväksyntääsi. Tällä hetkellä -näytetään %(description)s. +Tämä sivu sisältää osan postituslistan %(listname)s +viesteistä jotka ovat pidossa odottamassa hyväksyntääsi. Tällä hetkellä +näytetään %(description)s. -

          Valitse jokaiselle ylläpitopyynölle toiminto ja napsauta Lähetä -kaikki data kun olet valmis. Lisätietoja here. +

          Valitse jokaiselle ylläpitopyynölle toiminto ja napsauta Lähetä +kaikki data kun olet valmis. Lisätietoja here. -

          Voit myös Katsella yhteenvetoa -kaikista odottavista pyynnöistä. +

          Voit myös Katsella yhteenvetoa +kaikista odottavista pyynnöistä. +

          \ No newline at end of file diff --git a/templates/fi/admindbsummary.html b/templates/fi/admindbsummary.html index 78ac2f94..155a4f97 100644 --- a/templates/fi/admindbsummary.html +++ b/templates/fi/admindbsummary.html @@ -1,17 +1,81 @@ - -Tällä sivulla on lista toimiasi vaativista -ylläpitopyynnöistä + +Tällä sivulla on lista toimiasi vaativista +ylläpitopyynnöistä %(listname)s mailing list. -Ensin on lista liittymis ja eroamispyynnöistä, jos niitä on. +Ensin on lista liittymis ja eroamispyynnöistä, jos niitä on. -Seuraavaksi on lista viesteistä, jotka vaativat hyväksyntäsi. +Seuraavaksi on lista viesteistä, jotka vaativat hyväksyntäsi. -

          Valitse toiminta jokaiselle ylläpitotoimelle ja klikkaa +

          Valitse toiminta jokaiselle ylläpitotoimelle ja klikkaa Submit All Data button when finished. -Lisäohjeita on myös saatavilla. +Lisäohjeita on myös saatavilla. -

          Voit myös tutustua tarkemmin -kaikkiin toimenpiteitä vaativiin viesteihin. +

          Voit myös tutustua tarkemmin +kaikkiin toimenpiteitä vaativiin viesteihin. +

          \ No newline at end of file diff --git a/templates/fi/admlogin.html b/templates/fi/admlogin.html index be329f83..4b25c8ce 100755 --- a/templates/fi/admlogin.html +++ b/templates/fi/admlogin.html @@ -1,42 +1,103 @@ - %(listname)s %(who)s Authentication +%(listname)s %(who)s Authentication - - -
          + + + %(message)s - - - - - - - - - - - -
          - %(listname)s %(who)s - sisäänkirjautuminen -
          Listan %(who)s Salasana:
          -
          -

          Tärkeää: Tästä eteenpäin - sinulla pitää olla evästeet sallittuna selaimessasi, - muutoin haluamasi muutokset eivät tallennu. + + + + + + + + + + + +
          +%(listname)s %(who)s + sisäänkirjautuminen +
          Listan %(who)s Salasana:
          +
          +

          Tärkeää: Tästä eteenpäin + sinulla pitää olla evästeet sallittuna selaimessasi, + muutoin haluamasi muutokset eivät tallennu. -

          Istuntokohtaiset evästeet ovat käytössä Mailman - - ohjelmiston ylläpitosivuilla, joten sinun ei tarvitse - kirjautua erikseen joka ylläpitosivulle. - Eväste tuhoutuu automaattisesti selain-istunnon päätteeksi. - Jos haluat evästeen tuhoutuvan aikaisemmin, tämä - onnistuu Lopeta linkkiä painamalla - kohdassa muut Ylläpitotoimet - (jonka näet onnistuneen sisäänkirjautumisen jälkeen). -

          +

          Istuntokohtaiset evästeet ovat käytössä Mailman + - ohjelmiston ylläpitosivuilla, joten sinun ei tarvitse + kirjautua erikseen joka ylläpitosivulle. + Eväste tuhoutuu automaattisesti selain-istunnon päätteeksi. + Jos haluat evästeen tuhoutuvan aikaisemmin, tämä + onnistuu Lopeta linkkiä painamalla + kohdassa muut Ylläpitotoimet + (jonka näet onnistuneen sisäänkirjautumisen jälkeen). +

          diff --git a/templates/fi/article.html b/templates/fi/article.html index 40e84c27..59685a30 100644 --- a/templates/fi/article.html +++ b/templates/fi/article.html @@ -1,13 +1,14 @@ + - - - %(title)s - - - - -
          +Teksti voi sisältää muotoiltuja +merkkijonoja. Muotoilussa käytettäviä arvoja ovat:
            -
          • - real_name - Listan "kaunis" nimi, normaalisti nimi, jolle listan asia on +
          • +real_name - Listan "kaunis" nimi, normaalisti nimi, jolle listan asia on "omistettu" -
          • -
          • - list_name - Nimi, jolla lista tunnistetaan URL-osoitteena, jossa asia - on merkittävä
          • -
          • - host_name - Täydellisesti määritelty +
          • +list_name - Nimi, jolla lista tunnistetaan URL-osoitteena, jossa asia + on merkittävä +
          • +
          • +host_name - Täydellisesti määritelty toimialuenimi palvelimelle, jossa lista sijaitsee
          • -
          • - web_page_url - Mailman pääsivun - url-osoite. Tähän voidaan - lisätä esim. listinfo/%(list_name)s, jolla pääsee +
          • +web_page_url - Mailman pääsivun + url-osoite. Tähän voidaan + lisätä esim. listinfo/%(list_name)s, jolla pääsee suoraan postituslistan listinfo-sivulle
          • -
          • - description - Lyhyt kuvaus postituslistan - käyttötarkoituksesta -
          • -
          • - info - Täydellinen kuvaus postituslistasta +
          • +description - Lyhyt kuvaus postituslistan + käyttötarkoituksesta +
          • +
          • +info - Täydellinen kuvaus postituslistasta
          - - + +
          \ No newline at end of file diff --git a/templates/fi/listinfo.html b/templates/fi/listinfo.html index 560fc46f..65310849 100644 --- a/templates/fi/listinfo.html +++ b/templates/fi/listinfo.html @@ -1,153 +1,214 @@ - - - - <MM-List-Name> Info Sivu - - - -

          - - - - - - - - - - - - - - - - - - - + + + + + +

          - -- - -
          -

            -

          - Tietoja listasta - - - -
          -

          -

          Nähdäksesi listalle aikaisemmin lähetetyt viestit, - siirry - arkistoon. - -

          -
          - Listan käyttäminen -
          - Postittaaksesi viestin kaikille listan jäsenille, lähetä + + + +<mm-list-name> Info Sivu</mm-list-name> + + +

          + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + +
          + -- + +
          +

            +

          +Tietoja listasta + + + +
          +

          +

          Nähdäksesi listalle aikaisemmin lähetetyt viestit, + siirry + arkistoon. + +

          +
          +Listan käyttäminen +
          + Postittaaksesi viestin kaikille listan jäsenille, lähetä viesti osoitteeseen - . + . -

          Voit liittyä listalle tai muuttaa asetuksiasi +

          Voit liittyä listalle tai muuttaa asetuksiasi allaolevassa kappaleessa. -

          - Listalle liittyminen -
          -

          - Liity listalle täyttämällä oheinen lomake. - -

            - - - - - - - - - - - - + + + + + + - - - - - -
            Sähköpostiosoitteesi: -  
            Nimesi (vapaaehtoinen): 
            Voit antaa tähän salasanan - asetustesi ylläpitoa varten. Tämä salasanan suojaus - järjestelmässä on heikko, mutta se estää muita käyttäjiä - pääsemästä tahattomasti listalla oleviin tietoihisi käsiksi. - Älä käytä tässä samaa salasanaa, jota käytät tärkeiden - asioiden suojaamiseen sillä salasana lähetetään +

            +Listalle liittyminen +
            +

            + Liity listalle täyttämällä oheinen lomake. + +

              + + + + + + + + + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - - - -
              Sähköpostiosoitteesi: + 
              Nimesi (vapaaehtoinen): 
              Voit antaa tähän salasanan + asetustesi ylläpitoa varten. Tämä salasanan suojaus + järjestelmässä on heikko, mutta se estää muita käyttäjiä + pääsemästä tahattomasti listalla oleviin tietoihisi käsiksi. + Älä käytä tässä samaa salasanaa, jota käytät tärkeiden + asioiden suojaamiseen sillä salasana lähetetään tarvittaessa sinulle - suojaamattomana sähköpostiviestinä. + suojaamattomana sähköpostiviestinä. -

              Jos et täytä salasanakenttää niin järjestelmä +

              Jos et täytä salasanakenttää niin järjestelmä generoi automaattisesti sinulle salasanan. - Salasana lähetetään sinulle sähköpostilla, + Salasana lähetetään sinulle sähköpostilla, kun olet vahvistanut listalle liittymisesi. - Voit aina pyytää järjestelmää lähettämään salasanasi - sähköpostitse, jolloin voit muuttaa omia + Voit aina pyytää järjestelmää lähettämään salasanasi + sähköpostitse, jolloin voit muuttaa omia postituslista-asetuksiasi. - -
              -
              Valitse salasana: 
              Valittu salasana uudestaan: 
              Millä kielellä haluat ohjeet ja lomakkeet?  
              Haluatko vastaanottaa listan viestit päivittäisenä kokoelmana yksittäisten + + +
              Valitse salasana: 
              Valittu salasana uudestaan: 
              Millä kielellä haluat ohjeet ja lomakkeet?  
              Haluatko vastaanottaa listan viestit päivittäisenä kokoelmana yksittäisten viestien sijaan? Ei - Kyllä -
              -
              -
              - -
            -
            - - Listalle liittyneet -
            - - - -

            - - - -

            - - - +
          Ei + Kyllä +
          +
          +
          + + +

          + + Listalle liittyneet +
          + + + +

          + + + +

          + +

          + diff --git a/templates/fi/options.html b/templates/fi/options.html index 7ddb2aaf..77714b82 100644 --- a/templates/fi/options.html +++ b/templates/fi/options.html @@ -1,290 +1,324 @@ - <MM-Presentable-User> membership configuration for <MM-List-Name> - - - - - -
          - - Postituslistan jäsenyyden asetukset - -
          +<mm-presentable-user> membership configuration for <mm-list-name> +</mm-list-name></mm-presentable-user> + + + + +
          + + Postituslistan jäsenyyden asetukset + +

          - - - - - +
          - 's liittymistilanne, - salasana, ja postituslistan asetukset. -
          - - - - -

          -

          + + + +
          +'s liittymistilanne, + salasana, ja postituslistan asetukset. +
          + + +

          +

          - - +

          - - - + +
          - - Jäsentietojen muuttaminen -
          Voit vaihtaa osoitettasi, jolla olet liittynyt postituslistalle, - kirjoittamalla uuden osoitteen alapuolella olevaan kenttään. Huomioi, että tulet saamaan - vahvistuspyynnön uuteen osoitteeseesi ja muutos pitää hyväksyä, ennen kuin se suoritetaan. + + + - - - - - -
          + +Jäsentietojen muuttaminen +
          Voit vaihtaa osoitettasi, jolla olet liittynyt postituslistalle, + kirjoittamalla uuden osoitteen alapuolella olevaan kenttään. Huomioi, että tulet saamaan + vahvistuspyynnön uuteen osoitteeseesi ja muutos pitää hyväksyä, ennen kuin se suoritetaan.

          Confirmations time out after about . -

          Voit myös halutessasi asettaa tai vaihtaa nimesi. +

          Voit myös halutessasi asettaa tai vaihtaa nimesi. (esim. Ville Virtanen). -

          Jos haluat tehdä jäsenyystietojesi muutoksen kaikille listoille, joille olet liittynyt +

          Jos haluat tehdä jäsenyystietojesi muutoksen kaikille listoille, joille olet liittynyt , laita rasti Muuta kaikkiin ruutuun. -

          - - - - - - - -
          Uusi osoite:
          Uusi osoite uudestaan: -
          -
          - - +
          Nimi +

          + + + + + + + +
          Uusi osoite:
          Uusi osoite uudestaan: +
          +
          + + - - -
          Nimi (vapaaehtoinen):
          -
          -

          Muuta kaikkiin listoihin

          - +
          +

          +

          Muuta kaikkiin listoihin

          - - - - -
          - Eroaminen - Näytä muut liittymätiedot -
          - Aseta liittymistarkistusnappi??? aktiiviseksi ja paina tätä nappia - erotaksesi tältä postituslistalta. Varoitus: - Tämä toimenpide suoritetaan välittömästi! + + + + - + +
          +

          +Eroaminen +Näytä muut liittymätiedot +
          + Aseta liittymistarkistusnappi??? aktiiviseksi ja paina tätä nappia + erotaksesi tältä postituslistalta. Varoitus: + Tämä toimenpide suoritetaan välittömästi!

          -

          - Voit katsoa millä postituslistoilla, joissa olet jäsenenä. - Käytä tätä, mikäli haluat tehdä samat jäsenyystietojen muutokset myös muille postituslistoille, +

          + Voit katsoa millä postituslistoilla, joissa olet jäsenenä. + Käytä tätä, mikäli haluat tehdä samat jäsenyystietojen muutokset myös muille postituslistoille, joille olet liittynyt.

          -

          -
          - - - - - - -
          - Salasana -
          - -
          -

          Oletko unohtanut salasanasi?

          -
          - Paina tätä valintaa, niin salasanasi lähetetään siihen osoitteeseen, - jonka olet ilmoittanut listalle liittyessäsi. -

          -

          - -
          -
          - -
          -

          Vaihda salasana

          - - - - - - + + +
          Uusi - salasana:
          + +

          Muuta kaikkiin. +
          +

          - - +
          - Postituslistan jäsenkohtaiset määritykset -
          +
          + Postituslistan jäsenkohtaiset määritykset +
          -

          Nykyiset arvot on tarkistettu. - -

          Huomioi, että joissakin valinnoissa on Muuta kaikkiin -määritysmahdollisuus. Tämän kentän rastittaminen aiheuttaa sen, että muutokset tehdään -kaikille postituslistoille, joihin olet liittynyt. Klikkaa ylläolevaa -Näytä muut liittymätiedot -valintaa nähdäksesi, mille muille postituslistoille +

          Huomioi, että joissakin valinnoissa on Muuta kaikkiin +määritysmahdollisuus. Tämän kentän rastittaminen aiheuttaa sen, että muutokset tehdään +kaikille postituslistoille, joihin olet liittynyt. Klikkaa ylläolevaa +Näytä muut liittymätiedot -valintaa nähdäksesi, mille muille postituslistoille olet liittynyt.

          - - - +
          - - Viestin lähetys

          - Aseta tämän arvoksi Päällä saadaksesi tälle listalle - lähetetyt viestit. Aseta arvoksi Pois jos et - halua saada vähään aikaan viestejä itsellesi (esim. lähdet - lomalle). Mikäli asetat viestien tulon pois päältä, älä unohda - loman jälkeen laittaa sitä takaisin päälle! -

          - Päällä
          - Pois

          - Muuta kaikkiin -

          + - - - + + lukemisessa, valitse pelkät tekstikoonnit. +

          - - - - - - - - - - - - + + + + + - - + - - + Tällä arvolla on merkitystä ainoastaan jos liityit + vähintään yhteen yläpuolella olevaan aihepiiriin. Se luonnehtii + oletussäännöt sellaisen viestin lähetykselle, joka ei + sovi mihinkään valitsemaasi kategoriaan. Mikäli olet valinnut Ei + viestiä ei lähetetä sinulle, ellei se sovi mihinkään aihepiirin "ehtoihin". + Valitsemalla Kyllä + myös ei-sopivat viestit lähetetään sinulle. +

          Ellei mitään aihepiiriä tai mielenkiinnon kohdetta ole valittu yläpuolella, + niin kaikki postituslistalle tulevat viestit lähetetään sinulle. +

          +
          + +Viestin lähetys

          + Aseta tämän arvoksi Päällä saadaksesi tälle listalle + lähetetyt viestit. Aseta arvoksi Pois jos et + halua saada vähään aikaan viestejä itsellesi (esim. lähdet + lomalle). Mikäli asetat viestien tulon pois päältä, älä unohda + loman jälkeen laittaa sitä takaisin päälle! +

          +Päällä
          +Pois

          +Muuta kaikkiin +

          - Koontitilan asetus

          - Jos asetat koontitilan päälle, +

          +Koontitilan asetus

          + Jos asetat koontitilan päälle, saat postisi koostettuna yhdeksi kokoelmaviestiksi - kerran päivässä, etkä erikseen jokaista lähetettyä postia. Jos koontitila - muutetaan pois päältä, saat viimeisen koontilähetyksen heti. -

          - Pois
          - Päällä -
          - MIME-tyyliset tai tavalliset tekstikoonnit

          - Sähköpostiohjelmistosi joko tukee tai ei tue MIME-tyylisiä koonteja. Yleensä + kerran päivässä, etkä erikseen jokaista lähetettyä postia. Jos koontitila + muutetaan pois päältä, saat viimeisen koontilähetyksen heti. +

          +Pois
          +Päällä +
          +MIME-tyyliset tai tavalliset tekstikoonnit

          + Sähköpostiohjelmistosi joko tukee tai ei tue MIME-tyylisiä koonteja. Yleensä MIME-tyyliset koonnit ovat parempia, mutta jos esiintyy ongelmia niiden - lukemisessa, valitse pelkät tekstikoonnit. -

          - MIME
          - Teksti -
          +MIME
          +Teksti +
          - Haluatko saada listalle lähettämäsi postit itse?

          - Tavallisesti saat kopion jokaisesta tälle listalle lähettämästäsi viestistä. - Jos et halua saada kopiota omista viesteistäsi, aseta +

          +Haluatko saada listalle lähettämäsi postit itse?

          + Tavallisesti saat kopion jokaisesta tälle listalle lähettämästäsi viestistä. + Jos et halua saada kopiota omista viesteistäsi, aseta arvoksi Ei. -

          - Ei
          - Kyllä -
          - Haluatko saada kuittauksen listalle lähettämästäsi viestistä?

          -

          - Ei
          - Kyllä -
          - Lähetetäänkö tämän listan salasanasta muistutus?

          - Kerran kuukaudessa saat viestin, joka sisältää kaikkien niiden postituslistojen - salasanat, joille olet tällä postituslistapalvelimella liittynyt. - Voit asettaa tämän muistutuspyynnön pois päältä tietylle listalle, valitsemalla - Ei. Jos asetat salasanamuistuksen pois päältä kaikista listoista, - joille olet liittynyt, et saa ollenkaan salasanamuistusviestiä. -

          - Ei
          - Kyllä

          - Muuta kaikkiin -

          - Piiloutuminen jäsenlistalta?

          - Kun joku katselee listan jäsenluetteloa, normaalisti sähköpostiosoitteesi - näkyy (in an obscured muoti to estää roskapostin - harvesters). Mikäli et halua sähköpostiosoitteesi näkyvän - tällä jäsenlistalla, valitse arvoksi Ei. -

          - Ei
          - Kyllä -
          - Oletuskieli?

          -

          - -
          - Mihin aihepiireihin haluaisit - liittyä?

          +

          +Ei
          +Kyllä +
          +Haluatko saada kuittauksen listalle lähettämästäsi viestistä?

          +

          +Ei
          +Kyllä +
          +Lähetetäänkö tämän listan salasanasta muistutus?

          + Kerran kuukaudessa saat viestin, joka sisältää kaikkien niiden postituslistojen + salasanat, joille olet tällä postituslistapalvelimella liittynyt. + Voit asettaa tämän muistutuspyynnön pois päältä tietylle listalle, valitsemalla + Ei. Jos asetat salasanamuistuksen pois päältä kaikista listoista, + joille olet liittynyt, et saa ollenkaan salasanamuistusviestiä. +

          +Ei
          +Kyllä

          +Muuta kaikkiin +

          +Piiloutuminen jäsenlistalta?

          + Kun joku katselee listan jäsenluetteloa, normaalisti sähköpostiosoitteesi + näkyy (in an obscured muoti to estää roskapostin + harvesters). Mikäli et halua sähköpostiosoitteesi näkyvän + tällä jäsenlistalla, valitse arvoksi Ei. +

          +Ei
          +Kyllä +
          +Oletuskieli?

          +

          + +
          +Mihin aihepiireihin haluaisit + liittyä?

          Valitsemalla yhden tai useamman aihepiirin, voit filteroida - postituslistasi liikennettä, eli saat vain osan viesteistä. - Jos viesti täyttää valitsemasi aihepiirin kriteerit, + postituslistasi liikennettä, eli saat vain osan viesteistä. + Jos viesti täyttää valitsemasi aihepiirin kriteerit, saat viestin, muuten et. -

          Mikäli viesti ei sovi mihinkään valitsemaasi aihepiiriin, postin lähetys - riippuu niistä säännöistä, joille ylempänä asetit arvot. Ellet - valitse yhtään aihepiiriä tai mielenkiinnon kohdetta, saat - kaikki postituslistalle lähetettävät viestit. -

          - -
          - Haluatko saada viestin, joka ei sovi mihinkään valitsemaasi +

          Mikäli viesti ei sovi mihinkään valitsemaasi aihepiiriin, postin lähetys + riippuu niistä säännöistä, joille ylempänä asetit arvot. Ellet + valitse yhtään aihepiiriä tai mielenkiinnon kohdetta, saat + kaikki postituslistalle lähetettävät viestit. +

          + +
          +Haluatko saada viestin, joka ei sovi mihinkään valitsemaasi aihepiiriin?

          - Tällä arvolla on merkitystä ainoastaan jos liityit - vähintään yhteen yläpuolella olevaan aihepiiriin. Se luonnehtii - oletussäännöt sellaisen viestin lähetykselle, joka ei - sovi mihinkään valitsemaasi kategoriaan. Mikäli olet valinnut Ei - viestiä ei lähetetä sinulle, ellei se sovi mihinkään aihepiirin "ehtoihin". - Valitsemalla Kyllä - myös ei-sopivat viestit lähetetään sinulle. - -

          Ellei mitään aihepiiriä tai mielenkiinnon kohdetta ole valittu yläpuolella, - niin kaikki postituslistalle tulevat viestit lähetetään sinulle. -

          - Ei
          - Kyllä -
          -
          -
          +Ei
          +Kyllä +
          +
          +
          -

          - - - - + + +

          diff --git a/templates/fi/private.html b/templates/fi/private.html index 60287215..5e3dd09c 100755 --- a/templates/fi/private.html +++ b/templates/fi/private.html @@ -1,60 +1,121 @@ - %(realname)s Private Archives Authentication +%(realname)s Private Archives Authentication - - -
          + + + %(message)s - - - + + + + + + + + + + + + +
          - Sisäänkirjautuminen + + + - - - - - - - - - - - - -
          +Sisäänkirjautuminen yksityiseen arkistoon %(realname)s - -
          Sähköpostiosoite:
          Salasana:
          -
          -

          Tärkeää: Tästä eteenpäin sinulla - pitää olla evästeet valittuina aktiiviseksi selaimessasi. Muuten - mitkään ylläpitäjän toimet eivät tule suoritetuiksi. + +

          Sähköpostiosoite:
          Salasana:
          +
          +

          Tärkeää: Tästä eteenpäin sinulla + pitää olla evästeet valittuina aktiiviseksi selaimessasi. Muuten + mitkään ylläpitäjän toimet eivät tule suoritetuiksi. -

          Istuntokohtaiset evästeet ovat aktiivisina Mailmanin ylläpitäjän ympäristössä/oikeuksissa, - joten sinun ei tarvitse uudelleenkirjautua jokaisen ylläpitäjän toimen suorittamiseksi. Tämä - eväste katoaa automaattisesti kun suljet selaimen tai voit nimenomaan sulkea evästeen painamalla - Muut ylläpitäjän toimet otsikon alla olevaa Kirjaudu ulos -valintaa. - (jonka näet, kun olet onnistuneesti kirjautunut sisään). +

          Istuntokohtaiset evästeet ovat aktiivisina Mailmanin ylläpitäjän ympäristössä/oikeuksissa, + joten sinun ei tarvitse uudelleenkirjautua jokaisen ylläpitäjän toimen suorittamiseksi. Tämä + eväste katoaa automaattisesti kun suljet selaimen tai voit nimenomaan sulkea evästeen painamalla + Muut ylläpitäjän toimet otsikon alla olevaa Kirjaudu ulos -valintaa. + (jonka näet, kun olet onnistuneesti kirjautunut sisään).

          - - - - - - + + + +
          - Password Reminder -
          If you don't remember your password, enter your email address + + + + + + - - - - -
          +Password Reminder +
          If you don't remember your password, enter your email address above and click the Remind button and your password will be emailed to you.
          - +
          +

          diff --git a/templates/fi/roster.html b/templates/fi/roster.html index 0beaadd6..757ee9be 100644 --- a/templates/fi/roster.html +++ b/templates/fi/roster.html @@ -1,53 +1,112 @@ - - - <MM-List-Name> Listalle liittyneet - - - - -

          - - - - - - - - - - - - - - - -
          - - Listalle liittyneet -
          - -

          -

          - -

          Klikkaa osoitettasi, niin pääset jäsentietojesi asetussivulle. -
          (Suluissa olevilla käyttäjillä on listan viestien vastaanotto asetettu - pois päältä.)

          -
          -
          - - Postituslistan jäsenet: -
          -
          -
          - Postituslistan - koontiposteja vastaanottavat jäsenet: -
          -
          -

          -

          -

          -

          - - - + + +<mm-list-name> Listalle liittyneet</mm-list-name> + + +

          + + + + + + + + + + + + + + + +
          + + Listalle liittyneet +
          +

          +

          +

          Klikkaa osoitettasi, niin pääset jäsentietojesi asetussivulle. +
          (Suluissa olevilla käyttäjillä on listan viestien vastaanotto asetettu + pois päältä.)

          +
          +
          + + Postituslistan jäsenet: +
          +
          +
          + Postituslistan + koontiposteja vastaanottavat jäsenet: +
          +
          +

          +

          +

          +

          + +

          + diff --git a/templates/fi/subscribe.html b/templates/fi/subscribe.html index 089ea164..4182a19b 100644 --- a/templates/fi/subscribe.html +++ b/templates/fi/subscribe.html @@ -1,9 +1,72 @@ -<MM-List-Name> Subscription results +<mm-list-name> Subscription results</mm-list-name> -

          Liittymisen tulokset

          - - - +

          Liittymisen tulokset

          + + + diff --git a/templates/fr/admindbdetails.html b/templates/fr/admindbdetails.html index 6c764355..4a7738e0 100644 --- a/templates/fr/admindbdetails.html +++ b/templates/fr/admindbdetails.html @@ -1,63 +1,118 @@ -Les requêtes administratives peuvent être affichées de -deux façons différentes, sur une page résumée ou sur une page - détaillée. La page résumée contient -les requêtes en attente, les demandes de résiliation d'abonnement -ainsi que les soumissions mises en attente d'approbation, groupées -en fonction de l'adresse courriel de l'expéditeur. La page détaillée contient une vue plus détaillée de chaque message en attente -avec tous les en-têtes du message et un extrait de son corps. +Les requêtes administratives peuvent être affichées de +deux façons différentes, sur une page résumée ou sur une page + détaillée. La page résumée contient +les requêtes en attente, les demandes de résiliation d'abonnement +ainsi que les soumissions mises en attente d'approbation, groupées +en fonction de l'adresse courriel de l'expéditeur. La page détaillée contient une vue plus détaillée de chaque message en attente +avec tous les en-têtes du message et un extrait de son corps.

          Sur toutes les pages, les mesures suivantes sont disponibles:

          -
            -
          • Différer -- Reporter votre décision jusqu'à -une date ultérieure. Dans ce cas rien n'est fait concernant cette requête administrative en attente, mais vous pourrez tout de même faire-suivre +
          • Différer -- Reporter votre décision jusqu'à +une date ultérieure. Dans ce cas rien n'est fait concernant cette requête administrative en attente, mais vous pourrez tout de même faire-suivre ou conserver le message (voir ci-dessous).
          • -
          • Approuver -- Approuver le message, en l'envoyant à la -liste. Pour les demandes d'abonnement, approuver les modifications à +
          • Approuver -- Approuver le message, en l'envoyant à la +liste. Pour les demandes d'abonnement, approuver les modifications à apporter aux options de l'abonnement.
          • -
          • Rejeter -- Rejeter le message, en envoyant un avis de rejet -à l'expéditeur et en ignorant le message original. Pour les -demandes d'abonnements, rejeter les modifications demandées. Dans tous -les cas, vous devrez joindre un motif de rejet dans la zone de texte appropriée.
          • -
          • Ignorer -- Supprimer le message original, sans envoi d'avis de -rejet. Pour les demandes d'abonnement, ceci ignore tout simplement la requête -sans envoyer un avis à celui qui l'a émise. Ceci est la mesure -idoine pour les spam avérés.
          • - +
          • Rejeter -- Rejeter le message, en envoyant un avis de rejet +à l'expéditeur et en ignorant le message original. Pour les +demandes d'abonnements, rejeter les modifications demandées. Dans tous +les cas, vous devrez joindre un motif de rejet dans la zone de texte appropriée.
          • +
          • Ignorer -- Supprimer le message original, sans envoi d'avis de +rejet. Pour les demandes d'abonnement, ceci ignore tout simplement la requête +sans envoyer un avis à celui qui l'a émise. Ceci est la mesure +idoine pour les spam avérés.
          - -

          Pour les messages en attente, activer l'option Préserver - si vous souhaitez conserver une copie du message à l'attention de +

          Pour les messages en attente, activer l'option Préserver + si vous souhaitez conserver une copie du message à l'attention de l'administrateur du site. Ceci est utile pour les messages abusifs que vous voulez ignorer mais dont vous souhaitez garder une trace pour une analyse -ultérieure.

          - -

          Activez l'option Faire-suivre à, et remplissez l'adresse -destinataire, si vous souhaitez faire-suivre le message à quelqu'un -qui n'est pas abonné à la liste. Pour modifier un message en -attente avant qu'il ne soit envoyé à la liste, vous devez le -faire-suivre à votre adresse personnelle (ou à celle du propriétaire de la liste), puis ignorer le message original. Le message apparaît -alors dans votre boîte à lettre, vous pourrez ainsi effectuer -les modifications puis les envoyer à la liste en ajoutant l'en-tête +ultérieure.

          +

          Activez l'option Faire-suivre à, et remplissez l'adresse +destinataire, si vous souhaitez faire-suivre le message à quelqu'un +qui n'est pas abonné à la liste. Pour modifier un message en +attente avant qu'il ne soit envoyé à la liste, vous devez le +faire-suivre à votre adresse personnelle (ou à celle du propriétaire de la liste), puis ignorer le message original. Le message apparaît +alors dans votre boîte à lettre, vous pourrez ainsi effectuer +les modifications puis les envoyer à la liste en ajoutant l'en-tête Approved: avec le mot de passe de la liste comme valeur. Pour respecter -la "netiquette", il est préférable d'ajouter une note dans -le message final spécifiant que le texte original a été -modifié.

          - -

          Si l'expéditeur est un abonné dont les soumissions sont sujettes -à approbation, vous pourrez éventuellement désactiver -cette option. Cela est utile lorsque votre liste est configurée de -sorte que les nouveaux abonnés soient sujets à approbation et -vous vous rendez compte que cet abonné peut soumettre sans approbation.

          - -

          Si l'expéditeur n'est pas abonné à la liste, vous -pourrez ajouter son adresse courriel dans un filtre expéditeur. -Les filtres expéditeurs sont décrits dans la page de confidentialité des filtres expéditeurs et peuvent avoir l'une des valeurs suivantes : auto-accept (Accepté), auto-hold (Attente), auto-reject (Rejet) et auto-discard (Ignoré). Cette option ne sera pas disponible si l'adresse fait déjà l'objet de filtres expéditeurs.

          - -

          Quand vous en aurez terminé, cliquez sur le bouton Soumettre -toutes les données en tête ou au bas de la page. Ce bouton soumettra -toutes les mesures sélectionnées pour toutes les requêtes -administratives pour lesquelles vous avez pris une décision.

          - -

          Retourner à la page résumée. +la "netiquette", il est préférable d'ajouter une note dans +le message final spécifiant que le texte original a été +modifié.

          +

          Si l'expéditeur est un abonné dont les soumissions sont sujettes +à approbation, vous pourrez éventuellement désactiver +cette option. Cela est utile lorsque votre liste est configurée de +sorte que les nouveaux abonnés soient sujets à approbation et +vous vous rendez compte que cet abonné peut soumettre sans approbation.

          +

          Si l'expéditeur n'est pas abonné à la liste, vous +pourrez ajouter son adresse courriel dans un filtre expéditeur. +Les filtres expéditeurs sont décrits dans la page de confidentialité des filtres expéditeurs et peuvent avoir l'une des valeurs suivantes : auto-accept (Accepté), auto-hold (Attente), auto-reject (Rejet) et auto-discard (Ignoré). Cette option ne sera pas disponible si l'adresse fait déjà l'objet de filtres expéditeurs.

          +

          Quand vous en aurez terminé, cliquez sur le bouton Soumettre +toutes les données en tête ou au bas de la page. Ce bouton soumettra +toutes les mesures sélectionnées pour toutes les requêtes +administratives pour lesquelles vous avez pris une décision.

          +

          Retourner à la page résumée. +

          \ No newline at end of file diff --git a/templates/fr/admindbpreamble.html b/templates/fr/admindbpreamble.html index dc432b58..3acc5e0c 100644 --- a/templates/fr/admindbpreamble.html +++ b/templates/fr/admindbpreamble.html @@ -1,12 +1,76 @@ -Cette page contient un sous-ensemble des soumissions de la liste de diffusion +Cette page contient un sous-ensemble des soumissions de la liste de diffusion %(listname)s en attente d'approbation. Elle contient actuellement %(description)s -

          Pour chaque requête administrative, veuillez sélectionner -les mesures à prendre sans oublier de cliquer sur le bouton Soumettre -toutes les données quand vous aurez terminé. Des instructions -plus détaillées sont disponible +

          Pour chaque requête administrative, veuillez sélectionner +les mesures à prendre sans oublier de cliquer sur le bouton Soumettre +toutes les données quand vous aurez terminé. Des instructions +plus détaillées sont disponible ici. -

          Vous pourrez également voir un résumé - de toutes les requêtes en attente. +

          Vous pourrez également voir un résumé + de toutes les requêtes en attente. +

          \ No newline at end of file diff --git a/templates/fr/admindbsummary.html b/templates/fr/admindbsummary.html index 436f660d..1d1b1775 100644 --- a/templates/fr/admindbsummary.html +++ b/templates/fr/admindbsummary.html @@ -1,11 +1,73 @@ -Cette page contient un résumé de l'ensemble des requêtes +Cette page contient un résumé de l'ensemble des requêtes administratives actuellement en attente d'approbation pour la liste de diffusion %(listname)s. Vous trouverez avant -tout une liste des demandes d'abonnement et de résiliation, si il y +tout une liste des demandes d'abonnement et de résiliation, si il y en a, suivie de tous les envois en attente d'approbation. -

          Pour chaque requête administrative, veuillez choisir l'action à +

          Pour chaque requête administrative, veuillez choisir l'action à entreprendre sans oublier de cliquer sur le bouton Soumettre toutes les - données quand vous aurez terminé. Des instructions plus détaillées sont aussi disponibles.

          - -

          Vous pourrez aussi voir les détails de tous les envois en attente.

          + données
          quand vous aurez terminé. Des instructions plus détaillées sont aussi disponibles.

          +

          Vous pourrez aussi voir les détails de tous les envois en attente.

          diff --git a/templates/fr/admlogin.html b/templates/fr/admlogin.html index 75cde97b..1125e1b3 100755 --- a/templates/fr/admlogin.html +++ b/templates/fr/admlogin.html @@ -1,41 +1,102 @@ - Authentification %(who)s sur %(listname)s +Authentification %(who)s sur %(listname)s - - -
          + + + %(message)s - - - - - - - - - - - -
          - Authentification - %(who)s sur %(listname)s -
          Mot de passe %(who)s de la liste:
          -
          -

          Important: A partir d'ici, vous devez avoir - les cookies activés dans votre navigateur, sinon aucun + + + + + + + + + + + +
          +Authentification + %(who)s sur %(listname)s +
          Mot de passe %(who)s de la liste:
          +
          +

          Important: A partir d'ici, vous devez avoir + les cookies activés dans votre navigateur, sinon aucun changement administratif ne s'appliquera. -

          Les cookies de session sont utilisés dans l'interface - d'administration de Mailman pour que vous n'ayez pas à vous - authentifier de nouveau à chaque opération. Le cookie +

          Les cookies de session sont utilisés dans l'interface + d'administration de Mailman pour que vous n'ayez pas à vous + authentifier de nouveau à chaque opération. Le cookie expirera automatiquement lorsque vous quitterez votre navigateur, ou - vous pouvez le forcer à expirer en cliquant sur le lien - Déconnecter en dessous d'Autres opérations - administratives (que vous verrez après vous être - authentifié avec succès). -

          + vous pouvez le forcer à expirer en cliquant sur le lien + Déconnecter en dessous d'Autres opérations + administratives (que vous verrez après vous être + authentifié avec succès). +

          diff --git a/templates/fr/archidxentry.html b/templates/fr/archidxentry.html index b30b3bbb..d03f348e 100644 --- a/templates/fr/archidxentry.html +++ b/templates/fr/archidxentry.html @@ -1,4 +1,67 @@ -
        • %(subject)s -  - %(author)s +
        • %(subject)s +  +%(author)s
        • diff --git a/templates/fr/archidxfoot.html b/templates/fr/archidxfoot.html index d3eefbb5..f6ed223c 100644 --- a/templates/fr/archidxfoot.html +++ b/templates/fr/archidxfoot.html @@ -1,20 +1,84 @@ - -

          - Date du dernier message: -%(lastdate)s
          - Archivé le : %(archivedate)s +

          -

          +

          +


          +Archive générée par Pipermail %(version)s. + + +

          \ No newline at end of file diff --git a/templates/fr/archidxhead.html b/templates/fr/archidxhead.html index e70e6e04..38d4db25 100644 --- a/templates/fr/archidxhead.html +++ b/templates/fr/archidxhead.html @@ -1,24 +1,87 @@ - Les Archives de %(listname)s %(archive)s par %(archtype)s - - +Les Archives de %(listname)s %(archive)s par %(archtype)s + %(encoding)s - - - + + +

          %(archive)s Archives par %(archtype)s

          - +

          Début : %(firstdate)s
          +Fin : %(lastdate)s
          +Messages : %(size)s

            +
          \ No newline at end of file diff --git a/templates/fr/archlistend.html b/templates/fr/archlistend.html index 9bc052dd..2e1191b0 100644 --- a/templates/fr/archlistend.html +++ b/templates/fr/archlistend.html @@ -1 +1,64 @@ -
          + diff --git a/templates/fr/archliststart.html b/templates/fr/archliststart.html index b2f28d07..892dd7a9 100644 --- a/templates/fr/archliststart.html +++ b/templates/fr/archliststart.html @@ -1,6 +1,70 @@ - - - - - - +
          ArchiveVue par :Version téléchargeable
          + + + + + +
          ArchiveVue par :Version téléchargeable
          \ No newline at end of file diff --git a/templates/fr/archtoc.html b/templates/fr/archtoc.html index 0a4b153e..ab9f9332 100644 --- a/templates/fr/archtoc.html +++ b/templates/fr/archtoc.html @@ -1,14 +1,78 @@ + - Les Archives de %(listname)s - +Les Archives de %(listname)s + %(meta)s - + -

          Les Archives de %(listname)s

          -

          vous pouvez obtenir plus d' informations à - propos de cette liste ou vous pouvez  télécharger les archives complètes +

          Les Archives de %(listname)s

          +

          vous pouvez obtenir plus d' informations à + propos de cette liste ou vous pouvez  télécharger les archives complètes (%(size)s).

          %(noarchive_msg)s diff --git a/templates/fr/archtocentry.html b/templates/fr/archtocentry.html index 37144d4e..935a262c 100644 --- a/templates/fr/archtocentry.html +++ b/templates/fr/archtocentry.html @@ -1,11 +1,74 @@ - - - %(archivelabel)s : - - [ Enfilade ] - [ Sujet ] - [ Auteur ] - [ Date ] - + + +%(archivelabel)s : + +[ Enfilade ] +[ Sujet ] +[ Auteur ] +[ Date ] + %(textlink)s - + diff --git a/templates/fr/archtocnombox.html b/templates/fr/archtocnombox.html index 19fe109f..1b025999 100644 --- a/templates/fr/archtocnombox.html +++ b/templates/fr/archtocnombox.html @@ -1,18 +1,82 @@ - - - Les Archives de %(listname)s - + + + +Les Archives de %(listname)s + %(meta)s - - -

          Les Archives de %(listname)s

          -

          + + +

          Les Archives de %(listname)s

          +

          Vous pouvez obtenir plus d'information sur cette liste.

          %(noarchive_msg)s %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/fr/article.html b/templates/fr/article.html index ce2ea954..82bbe4ab 100644 --- a/templates/fr/article.html +++ b/templates/fr/article.html @@ -1,12 +1,13 @@ - - - %(title)s - - - - - +

          Archives de %(listname)s

          -

          - Aucun message n'a encore été posté sur +

          + Aucun message n'a encore été posté sur cette liste, il n y a donc pas encore d'archives disponibles. Pour obtenir plus d'informations sur la liste, cliquez ici -

          +

          diff --git a/templates/fr/handle_opts.html b/templates/fr/handle_opts.html index 35e042c8..0d9ab9b0 100644 --- a/templates/fr/handle_opts.html +++ b/templates/fr/handle_opts.html @@ -1,9 +1,72 @@ - -<MM-List-Name> <MM-Operation> Résultats + +<mm-list-name> <mm-operation> Résultats</mm-operation></mm-list-name> -

          Résultats

          - - - +

          Résultats

          + + + diff --git a/templates/fr/headfoot.html b/templates/fr/headfoot.html index 0c0853e2..a245393e 100644 --- a/templates/fr/headfoot.html +++ b/templates/fr/headfoot.html @@ -1,30 +1,86 @@ -Ce texte peut contenir des formats -de chaînes Python qui sont remplacées par les attributs +Ce texte peut contenir des formats +de chaînes Python qui sont remplacées par les attributs de la liste. La liste des substitutions valides est:
            -
          • real_name - Le nom usuel de la liste; +
          • real_name - Le nom usuel de la liste; habituellement le nom en capitales.
          • - -
          • list_name - Le nom par lequel la liste - est identifiée dans les URLs, où la casse est important. +
          • list_name - Le nom par lequel la liste + est identifiée dans les URLs, où la casse est important.
          • - -
          • host_name - Le "nom d'hôte pleinement - qualifié" (FQDN) du serveur sur lequel est installé le serveur de +
          • host_name - Le "nom d'hôte pleinement + qualifié" (FQDN) du serveur sur lequel est installé le serveur de listes.
          • - -
          • web_page_url - L'URL de base pour Mailman. - Elle peut être concaténée avec, par exemple +
          • web_page_url - L'URL de base pour Mailman. + Elle peut être concaténée avec, par exemple listinfo/%(list_name)s pour donner la page d'infos de la liste.
          • - -
          • description - La description courte de la +
          • description - La description courte de la liste de diffusion.
          • - -
          • info - La description complète de la +
          • info - La description complète de la liste de diffusion.
          • - -
          • cgiext - L'extension ajoutée aux scripts +
          • cgiext - L'extension ajoutée aux scripts cgi.
          • -
          +
        diff --git a/templates/fr/listinfo.html b/templates/fr/listinfo.html index 6ca7cf55..3df6fa7f 100644 --- a/templates/fr/listinfo.html +++ b/templates/fr/listinfo.html @@ -1,152 +1,214 @@ + - Page d'infos de <MM-List-Name> - - - -

        - - - - - - - - - - - - - - - - - - - + + + + + +

        - -- -
        -

         

        -
        - A propos de - - - -
        -

        -

        Pour voir tous les messages passés de la liste, visitez les Archives de - . - -

        -
        Utilisation de -
        - Pour envoyer un message à tous les abonnés de - la liste, envoyez un courriel à - . +Page d'infos de <mm-list-name></mm-list-name> + + +

        + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + + + + + + + +
        + -- +
        +

         

        +
        +A propos de + + + +
        +

        +

        Pour voir tous les messages passés de la liste, visitez les Archives de +. + +

        +
        Utilisation de +
        + Pour envoyer un message à tous les abonnés de + la liste, envoyez un courriel à + . -

        Vous pouvez vous abonner à la liste ou modifier votre +

        Vous pouvez vous abonner à la liste ou modifier votre abonnement actuel dans les sections suivantes.

        -
        -S'abonner à -
        -

        - Abonnez-vous à en remplissant le formulaire +

        +S'abonner à +
        +

        + Abonnez-vous à en remplissant le formulaire suivant. - -

          - - - - - - - - - - - - - - - - - - - -
          Votre adresse courriel: -  
          Votre nom (facultatif): 
          Vous devez entrer un mot de passe pour la confidentialité. Ceci ne procure qu'un faible niveau - de sécurité mais devrait néanmoins - enpêcher que n'importe qui modifie vos abonnements. + +
            + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - - - - - -
            Votre adresse courriel: + 
            Votre nom (facultatif): 
            Vous devez entrer un mot de passe pour la confidentialité. Ceci ne procure qu'un faible niveau + de sécurité mais devrait néanmoins + enpêcher que n'importe qui modifie vos abonnements. N'utilisez pas un mot de passe existant car il - arrivera qu'il vous soit renvoyé par courriel en clair. -

            Si vous choisissez de ne pas en fournir, un mot de - passe vous sera généré - automatiquement, et il vous sera envoyé une fois que - votre requête d'abonnement aura été - confirmée. Il vous sera toujours possible de vous + arrivera qu'il vous soit renvoyé par courriel en clair. +

            Si vous choisissez de ne pas en fournir, un mot de + passe vous sera généré + automatiquement, et il vous sera envoyé une fois que + votre requête d'abonnement aura été + confirmée. Il vous sera toujours possible de vous faire envoyer votre mot de passe par courriel lorsque vous visualisez vos options personnelles. -

            -
            Choisissez un mot de passe: 
            Confirmer le mot de passe: 
            Dans quelle langue voulez-vous que le - système vous parle?  
            Souhaitez-vous recevoir les messages de la liste dans - un seul courriel quotidien groupé ? - Non - Oui +

            +
            Choisissez un mot de passe: 
            Confirmer le mot de passe: 
            Dans quelle langue voulez-vous que le + système vous parle?  
            Souhaitez-vous recevoir les messages de la liste dans + un seul courriel quotidien groupé ?
            -
            -
            - -
          -
          - - Abonnés de - -
          - - - -

          - - - -

          - - +
        Non + Oui +
        +
        +
        + + +

        + +Abonnés de + +
        + + + +

        + + + +

        + +

        diff --git a/templates/fr/options.html b/templates/fr/options.html index 8a2c6e28..1d6458b5 100644 --- a/templates/fr/options.html +++ b/templates/fr/options.html @@ -1,55 +1,115 @@ - - - - - <MM-Presentable-User> configuration d'abonnement sur <MM-List-Name> - - - - - + + + + +<mm-presentable-user> configuration d'abonnement sur <mm-list-name></mm-list-name></mm-presentable-user> + + +
        - - Configuration de l'abonnement de - -
        + +
        + + Configuration de l'abonnement de + +
        -

        - - - - - - - - - +

        +

        - Etat de l'abonnement de , - mot de passe et options pour la liste de diffusion . -
        - - - - -

        -

        + + + + + + +
        + Etat de l'abonnement de , + mot de passe et options pour la liste de diffusion . +
        + + +

        +

        - -

        - - - - - + +

        -

        - - Modification de vos informations d'abonnement à - -
        Vous pouvez modifier l'adresse avec - laquelle vous vous êtes abonné à la liste + +

        + + + + + - - - - - - - + + +
        + +Modification de vos informations d'abonnement à + +
        Vous pouvez modifier l'adresse avec + laquelle vous vous êtes abonné à la liste en fournissant la nouvelle adresse dans les champs ci-dessous. Notez que le courriel de demande de confirmation sera - envoyé à la nouvelle adresse, et que la + envoyé à la nouvelle adresse, et que la modification ne prendra effet que lorsque vous l'aurez - confirmée. + confirmée.

        La demande de confirmation sera caduque au bout de . @@ -61,281 +121,253 @@ pour toute les listes sur , cochez la case Changer globalement. -

        -
        - - - - - - - - - - -
        Nouvelle adresse :
        -
        Confirmer l'adresse :
        -
        -
        -
        -
        - - - + + + +
        -
        Votre Nom Complet +

        +
        + + + + + + + + + +
        Nouvelle adresse :
        +
        Confirmer l'adresse :
        +
        +
        +
        + + + - - -
        +
        Votre Nom Complet (facultatif) :
        -
        -
        -
        -

        Changer globalement

        -
        - -

        - - - - - + + +

        +

        - Résilier l'abonnement à - - Vos autres abonnements sur -
        + +
        +

        Changer globalement

        +
        + + + - + +
        +

        +Résilier l'abonnement à + +Vos autres abonnements sur +
        Cochez la case confirmation et cliquez sur ce bouton pour - résilier votre abonnement à cette liste. + résilier votre abonnement à cette liste. Attention : Cette action prendra effet - immédiatement ! + immédiatement !

        -

        -
        +
        +

        Vous pouvez voir la liste de toutes les listes auxquelles vous - êtes abonné sur . A utiliser si vous - voulez avoir les mêmes options pour cette liste et les - autres auxquelles vous vous êtes abonné. + êtes abonné sur . A utiliser si vous + voulez avoir les mêmes options pour cette liste et les + autres auxquelles vous vous êtes abonné.

        -

        -
        - - - - - - - - + + + + %(textlink)s - diff --git a/templates/gl/article.html b/templates/gl/article.html index 2e6f562e..da20d032 100644 --- a/templates/gl/article.html +++ b/templates/gl/article.html @@ -1,12 +1,13 @@ - - - %(title)s - - - - - + +

        Arquivo da rolda %(listname)s

        +

        + Aínda non se enviou ningunha mensaxe a esta rolda de xeito que + o arquivo está baleiro. Pode obter, na ligazón seguinte, + máis información sobre esta rolda.

        - - + + diff --git a/templates/gl/handle_opts.html b/templates/gl/handle_opts.html index b1c436eb..64fb0388 100644 --- a/templates/gl/handle_opts.html +++ b/templates/gl/handle_opts.html @@ -1,9 +1,72 @@ - -<MM-List-Name> <MM-Operation> Resultados + +<mm-list-name> <mm-operation> Resultados</mm-operation></mm-list-name> -

        Resultados

        - - - +

        Resultados

        + + + diff --git a/templates/gl/headfoot.html b/templates/gl/headfoot.html index 6335601d..bc502402 100644 --- a/templates/gl/headfoot.html +++ b/templates/gl/headfoot.html @@ -1,31 +1,94 @@ -Este texto pode incluír +Este texto pode incluír cadeas de formato Python -que se substituirán polos atributos da rolda. A relación de -substitucións que se permite é a seguinte: +que se substituirán polos atributos da rolda. A relación de +substitucións que se permite é a seguinte:
          -
        • real_name: é o nome 'presentábel' da rolda. - Polo xeral é o nome da rolda coa primeira letra en maiúscula. +
        • real_name: é o nome 'presentábel' da rolda. + Polo xeral é o nome da rolda coa primeira letra en maiúscula. -
        • list_name: é o nome polo que se identifica - a rolda nos URL e distínguense as maiúsculas das - minúsculas. +
        • list_name: é o nome polo que se identifica + a rolda nos URL e distínguense as maiúsculas das + minúsculas. -
        • host_name: é o nome canónico do servidor que +
        • host_name: é o nome canónico do servidor que hospeda a rolda. -
        • web_page_url: é o URL de base do Mailman. Por exemplo, - para indicar a páxina de información da rolda - só habería que engadirlle despois:
          - listinfo/%(list_name)s -
        • description: é unha descrición concisa sobre - a rolda de distribución. +
        • web_page_url: é o URL de base do Mailman. Por exemplo, + para indicar a páxina de información da rolda + só habería que engadirlle despois:
          +listinfo/%(list_name)s +
        • description: é unha descrición concisa sobre + a rolda de distribución. -
        • info: é a descrición completa sobre a rolda de distribución. +
        • info: é a descrición completa sobre a rolda de distribución. -
        • cgiext: é a extensión que se engade ao guións +
        • cgiext: é a extensión que se engade ao guións CGI. -
        + diff --git a/templates/gl/listinfo.html b/templates/gl/listinfo.html index aa9e0954..760e028e 100644 --- a/templates/gl/listinfo.html +++ b/templates/gl/listinfo.html @@ -1,149 +1,210 @@ - - - - Páxina de información de <MM-List-Name> - - - -

        -

        -Votre mot de passe pour -
        - -
        -

        Egaré votre mot de passe ?

        -
        -Cliquez sur ce bouton pour qu'il vous soit envoyé -à votre adresse d'abonnement. -

        -

        - + + + + - - +
        +Votre mot de passe pour +
        + +
        +

        Egaré votre mot de passe ?

        -
        - -
        +Cliquez sur ce bouton pour qu'il vous soit envoyé +à votre adresse d'abonnement. +

        +

        + +
        +

        + +

        Changer votre mot de passe

        - - - - - - - - - -
        Nouveau mot de passe :
        Confirmer le mot de passe :
        - -

        Changez globalement. -
        - + + + + + + + + +
        Nouveau mot de passe :
        Confirmer le mot de passe :
        - -

        - -
        -Vos options d'abonnement à + +

        Changez globalement. +
        +

        +

        + +
        +Vos options d'abonnement à
        - -

        -Les options cochées sont actives.

        -

        Notez que certaines options ont une case Changer globalement(ou Paramètre +

        +Les options cochées sont actives.

        +

        Notez que certaines options ont une case Changer globalement(ou Paramètre global). Cocher cette case signifie que la modification va s'appliquer -à toutes les listes auxquelles vous êtes abonné sur +à toutes les listes auxquelles vous êtes abonné sur . Cliquez sur Afficher mes autres abonnements pour voir les autres -listes auxquelles vous êtes abonné. -

        - - - - +listes auxquelles vous êtes abonné. +

        +

        - -Réception des messages

        Choisissez -Activé pour recevoir les messages adressés -à la liste. Choisissez Désactivé si vous voulez -rester abonné mais ne souhaitez pas recevoir les messages de la liste -pendant un moment (e.g. si vous partez en vacance). Si vous désactivez -la remise des courriels, n'oubliez pas de revenir la réactiver au -retour des vacances; la remise ne se réactivera pas automatiquement. -

        -Activé
        -Désactivé

        -Paramètre global -

        + - - - + +

        - - - - - - + + - - + - - - - + + - - + - - + - +
        + +Réception des messages

        Choisissez +Activé pour recevoir les messages adressés +à la liste. Choisissez Désactivé si vous voulez +rester abonné mais ne souhaitez pas recevoir les messages de la liste +pendant un moment (e.g. si vous partez en vacance). Si vous désactivez +la remise des courriels, n'oubliez pas de revenir la réactiver au +retour des vacances; la remise ne se réactivera pas automatiquement. +

        +Activé
        +Désactivé

        +Paramètre global +

        - Activer le mode Groupé ?

        -Si vous activez le mode groupé, vous recevrez les messages -regroupés en un seul (une fois par jour en général, peut -être plus sur une liste active) au lieu de les recevoir un par un. -Si vous désactivez le mode groupé, vous recevrez tout de même -un dernier envoi groupé. -

        -Off
        -On -
        +
        + Activer le mode Groupé ?

        +Si vous activez le mode groupé, vous recevrez les messages +regroupés en un seul (une fois par jour en général, peut +être plus sur une liste active) au lieu de les recevoir un par un. +Si vous désactivez le mode groupé, vous recevrez tout de même +un dernier envoi groupé. +

        +Off
        +On +
        Recevoir les digests en MIME ou en Texte brut ?

        Votre client de messagerie pourrait ou non supporter les digests -MIME. Les digests MIME sont généralement préférés, -mais si vous avez des problèmes pour les lire, choisissez du texte +MIME. Les digests MIME sont généralement préférés, +mais si vous avez des problèmes pour les lire, choisissez du texte brut. -

        -MIME
        -Texte brut

        -Paramètre global -

        +MIME
        +Texte brut

        +Paramètre global +

        -Recevez les messages que vous envoyez à la liste ?

        -Par défaut vous recevez une copie de chaque message que -vous envoyez à la liste. Si vous ne voulez pas recevoir cette copie, +

        +Recevez les messages que vous envoyez à la liste ?

        +Par défaut vous recevez une copie de chaque message que +vous envoyez à la liste. Si vous ne voulez pas recevoir cette copie, choisissez Non. -

        -Non
        - Oui -
        -Recevoir un accusé de réception pour chaque -message envoyé à la liste ?

        -

        -Non
        - Oui -
        +

        +Non
        +Oui +
        +Recevoir un accusé de réception pour chaque +message envoyé à la liste ?

        +

        +Non
        +Oui +
        Recevoir un rappel de votre mot passe pour cette liste ?

        Une fois par mois, vous recevrez un courriel contenant un rappel -de votre mot de passe pour chaque liste à laquelle vous êtes -abonné sur ce serveur. Vous pouvez désactiver ce rappel pour +de votre mot de passe pour chaque liste à laquelle vous êtes +abonné sur ce serveur. Vous pouvez désactiver ce rappel pour les listes de votre choix en choisissant Non pour cette option. -Si vous le faites pour toute les listes auxquelles vous êtes abonné, -aucun courriel ne vous sera envoyé.

        -
        -Non
        -Oui

        -Paramètre global -

        -Ne pas apparaître dans la liste des abonnés ?

        -Lorsque quelqu'un visualise la liste des abonnés, votre -adresse courriel est normalement affichée (d'une façon obscure +Si vous le faites pour toute les listes auxquelles vous êtes abonné, +aucun courriel ne vous sera envoyé.

        +
        +Non
        +Oui

        +Paramètre global +

        +Ne pas apparaître dans la liste des abonnés ?

        +Lorsque quelqu'un visualise la liste des abonnés, votre +adresse courriel est normalement affichée (d'une façon obscure pour contrarier "les spammers"). Si vous ne voulez pas de votre -adresse dans cette liste d'abonnés, choisissez Oui pour cette +adresse dans cette liste d'abonnés, choisissez Oui pour cette option. -

        -Non
        - Oui -
        -Quelle langue préférez-vous ?

        -

        - -
        -A quelles catégories de thèmes souhaitez-vous vous abonner ?

        -En sélectionnant un ou plusieurs thèmes, vous pourrez +

        +Non
        + Oui +
        +Quelle langue préférez-vous ?

        +

        + +
        +A quelles catégories de thèmes souhaitez-vous vous abonner ?

        +En sélectionnant un ou plusieurs thèmes, vous pourrez limiter le trafic sur la liste de diffusion et ainsi recevoir juste un -sous ensemble des messages. Les messages vous seront envoyés ou -pas, selon qu'ils concordent avec l'un des thèmes que vous avez -sélectionné. -

        Si un message ne correspond à aucun des thèmes -que vous avez sélectionné, il sera assujetti à -l'option ci-dessous. Si vous ne sélectionnez aucun thème, +sous ensemble des messages. Les messages vous seront envoyés ou +pas, selon qu'ils concordent avec l'un des thèmes que vous avez +sélectionné. +

        Si un message ne correspond à aucun des thèmes +que vous avez sélectionné, il sera assujetti à +l'option ci-dessous. Si vous ne sélectionnez aucun thème, vous recevrez tous les messages de la liste de diffusion. -

        +

        -
        +
        Voulez-vous recevoir les messages qui ne correspondent -à aucun thème ?

        -Cette option n'est active que si vous sélectionnez ci-dessus -au moins un thème. Elle détermine la règle de gestion -des messages qui ne cadrent avec aucun thème. Choisir Non -signifie que vous ne recevrez pas les messages qui ne correspondent à -aucun thème alors qu'un Oui signifie que vous les recevrez. +à aucun thème ?

        +Cette option n'est active que si vous sélectionnez ci-dessus +au moins un thème. Elle détermine la règle de gestion +des messages qui ne cadrent avec aucun thème. Choisir Non +signifie que vous ne recevrez pas les messages qui ne correspondent à +aucun thème alors qu'un Oui signifie que vous les recevrez. -

        Si aucun thème n'a été sélectionné -ci-dessus, vous recevrez tous les messages envoyés à la liste. +

        Si aucun thème n'a été sélectionné +ci-dessus, vous recevrez tous les messages envoyés à la liste. -

        -Non
        +

        +Non
        Oui -
        +
        Refuser les copies multiples des messages ?

        -Lorsque votre adresse se trouve dans l'un des en-têtes -A: ou Cc: d'un message adressé à la liste, -vous pouvez choisir de ne pas recevoir de message supplémentaire +Lorsque votre adresse se trouve dans l'un des en-têtes +A: ou Cc: d'un message adressé à la liste, +vous pouvez choisir de ne pas recevoir de message supplémentaire en provenance de la liste. Choisissez -Oui pour éviter de recevoir plusieurs copies; +Oui pour éviter de recevoir plusieurs copies; Non si vous voulez les avoir. -

        Si la liste accepte les messages personnalisés des -abonnés et que vous avez opté pour la reception des -copies multiples, alors chaque copie supplémentaire -aura l'en-tête X-Mailman-Copy: yes. +

        Si la liste accepte les messages personnalisés des +abonnés et que vous avez opté pour la reception des +copies multiples, alors chaque copie supplémentaire +aura l'en-tête X-Mailman-Copy: yes. -

        -Non
        -Oui

        -Paramètre global - -

        -
        +

        +Non
        +Oui

        +Paramètre global +

        +
        -

        - -

        - - - - - +

        + + +

        diff --git a/templates/fr/private.html b/templates/fr/private.html index cabab62d..0d35f5c6 100755 --- a/templates/fr/private.html +++ b/templates/fr/private.html @@ -1,59 +1,120 @@ - Authentification pour l'accès aux archives privées de +<title>Authentification pour l'accès aux archives privées de %(realname)s - - -
        + + + %(message)s - - - - - - - - - - - - - - - -
        - Authentification pour l'accès aux archives -privées de %(realname)s -
        Adresse courriel :
        Mot de passe :
        -
        + + + + + + + + + + + + + + + +
        +Authentification pour l'accès aux archives +privées de %(realname)s +
        Adresse courriel :
        Mot de passe :
        +

        Important : A partir d'ici, vous devez avoir -les cookies activés dans votre navigateur, sinon aucun +les cookies activés dans votre navigateur, sinon aucun changement ne s'appliquera. -

        Les cookies de session sont utilisés dans l'interface -d'administration de Mailman pour que vous n'ayez pas à vous -authentifier de nouveau à chaque opération. Le cookie +

        Les cookies de session sont utilisés dans l'interface +d'administration de Mailman pour que vous n'ayez pas à vous +authentifier de nouveau à chaque opération. Le cookie expirera automatiquement lorsque vous quitterez votre navigateur, ou -vous pouvez le forcer à expirer en cliquant sur le bouton -Déconnecter sur votre page d'options personnelles. +vous pouvez le forcer à expirer en cliquant sur le bouton +Déconnecter sur votre page d'options personnelles.

        - - - - - - + + + +
        - Password Reminder -
        If you don't remember your password, enter your email address + + + + + + - - - - -
        +Password Reminder +
        If you don't remember your password, enter your email address above and click the Remind button and your password will be emailed to you.
        - +
        +

        diff --git a/templates/fr/roster.html b/templates/fr/roster.html index 38df554d..4daba0bc 100644 --- a/templates/fr/roster.html +++ b/templates/fr/roster.html @@ -1,52 +1,111 @@ - - - <MM-List-Name> Abonnés - - - - -

        - - - - - - - - - - - - - - - -
        - - Abonnés à -
        - -

        -

        - -

        Cliquez sur votre adresse pour accéder à votre - page d'options d'abonnement.
        (Les entrées entre - parenthèses ne reçoivent pas les soumissions.)

        -
        -
        - - Abonnés de en remise non groupée : -
        -
        -
        - - Abonnés de en remise groupée : -
        -
        -

        -

        -

        -

        - - - + + +<mm-list-name> Abonnés</mm-list-name> + + +

        + + + + + + + + + + + + + + + +
        + + Abonnés à +
        +

        +

        +

        Cliquez sur votre adresse pour accéder à votre + page d'options d'abonnement.
        (Les entrées entre + parenthèses ne reçoivent pas les soumissions.)

        +
        +
        + + Abonnés de en remise non groupée : +
        +
        +
        + + Abonnés de en remise groupée : +
        +
        +

        +

        +

        +

        + +

        + diff --git a/templates/fr/subscribe.html b/templates/fr/subscribe.html index 058d2087..f953e9f8 100644 --- a/templates/fr/subscribe.html +++ b/templates/fr/subscribe.html @@ -1,9 +1,72 @@ -<MM-List-Name> Résultats de l'abonnement +<mm-list-name> Résultats de l'abonnement</mm-list-name> -

        Résultats de l'abonnement

        - - - +

        Résultats de l'abonnement

        + + + diff --git a/templates/gl/admindbdetails.html b/templates/gl/admindbdetails.html index 8ba3e4ef..fbfee55e 100644 --- a/templates/gl/admindbdetails.html +++ b/templates/gl/admindbdetails.html @@ -1,83 +1,146 @@ -As solicitudes administrativas amósanse dun dos dous xeitos, +As solicitudes administrativas amósanse dun dos dous xeitos, como unha -
        páxina cun resumo ou como unha páxina -detallada. A páxina co sumario contén solicitudes de -alta ou de baixa pendentes, envíos á rolda que se remitiron para +páxina cun resumo ou como unha páxina +detallada. A páxina co sumario contén solicitudes de +alta ou de baixa pendentes, envíos á rolda que se remitiron para que os -aprobe, agrupados polo enderezo do remitinte. A páxina cos detalles -contén unha vista máis detallada de cada unha das mensaxes, +aprobe, agrupados polo enderezo do remitinte. A páxina cos detalles +contén unha vista máis detallada de cada unha das mensaxes, xunto coas cabeceiras da mensaxe e un extracto do seu corpo. -

        En calquera destas páxinas están dispoñíbeis as seguintes accións: +

        En calquera destas páxinas están dispoñíbeis as seguintes accións:

          -
        • Diferir: pospón a súa decisión para máis tarde. +
        • Diferir: pospón a súa decisión para máis tarde. Non se aplica ningunha -acción para esta solicitude administrativa pendente mais aínda así pode +acción para esta solicitude administrativa pendente mais aínda así pode reenviar -ou conservar a mensaxe (vexa máis abaixo). +ou conservar a mensaxe (vexa máis abaixo). -
        • Aprobar: aprobar a mensaxe e enviala á rolda. Para as -solicitudes de subscrición, estas apróbanse. +
        • Aprobar: aprobar a mensaxe e enviala á rolda. Para as +solicitudes de subscrición, estas apróbanse.
        • Rexeitar: rexeitar a mensaxe e enviarlle unha mensaxe de rexeitamento ao remitinte en que se rexeita a mensaxe orixinal. Para as solicitudes de -subscrición, estas rexéitanse. En calquera caso, debería pór o motivo +subscrición, estas rexéitanse. En calquera caso, debería pór o motivo polo que se rexeita no cadro de texto.
        • Descartar: eliminar a mensaxe orixinal sen enviar unha mensaxe de -rexeitamento. Para as solicitudes de subscrición, isto simplemente elimina a -solicitude sen avisar á persoa que a solicitou. Esta opción -úsase +rexeitamento. Para as solicitudes de subscrición, isto simplemente elimina a +solicitude sen avisar á persoa que a solicitou. Esta opción +úsase normalmente co correo non solicitado ou co correo lixo. -
        - -

        Para as mensaxes retidas, active a opción Preservar se quere + +

        Para as mensaxes retidas, active a opción Preservar se quere gardar unha copia das mensaxes para o administrador do servidor. Isto - é útil para aquelas mensaxes abusivas que queira rexeitar mais + é útil para aquelas mensaxes abusivas que queira rexeitar mais que precise -gardar un rexistro para botarlle un ollada máis tarde. +gardar un rexistro para botarlle un ollada máis tarde. -

        Active a opción Redirixir a e volva cubrir o enderezo correspondente +

        Active a opción Redirixir a e volva cubrir o enderezo correspondente se -quere reenviar a mensaxe a alguén que non estea na rolda. Para editar +quere reenviar a mensaxe a alguén que non estea na rolda. Para editar unha -mensaxe retida antes de que se envíe á rolda, pode reenviala a si mesmo -(ou ao propietario da rolda) e rexeitar a mensaxe orixinal. Entón, -cando a mensaxe se amosar na sú caixa de entrada, faga as súas coreccións +mensaxe retida antes de que se envíe á rolda, pode reenviala a si mesmo +(ou ao propietario da rolda) e rexeitar a mensaxe orixinal. Entón, +cando a mensaxe se amosar na sú caixa de entrada, faga as súas coreccións e -reenvíea á rolda, onde deberá incluír unha cabeceira Approved: co -contrasinal da rolda como o seu valor. É de bo costume neste caso -incluír +reenvíea á rolda, onde deberá incluír unha cabeceira Approved: co +contrasinal da rolda como o seu valor. É de bo costume neste caso +incluír unha nota na mensaxe en que explique que modificou o texto. -

        Se o remitinte é un subscritor que está a ser moderado, pode limpar -opcionalmente a súa marca de moderación. Isto é útil cando -a súa rolda -está -configurada para pór os novos subscritores en corentena e decidir que -se pode confiar neste subscritor á hora de mandar mensaxes á rolda sen -aprobación. +

        Se o remitinte é un subscritor que está a ser moderado, pode limpar +opcionalmente a súa marca de moderación. Isto é útil cando +a súa rolda +está +configurada para pór os novos subscritores en corentena e decidir que +se pode confiar neste subscritor á hora de mandar mensaxes á rolda sen +aprobación. -

        Se o remitinte non é un subscritor da rolda, pode engadir este enderezo - de correo electrónico ao filtrado de remitintes. O filtrado +

        Se o remitinte non é un subscritor da rolda, pode engadir este enderezo + de correo electrónico ao filtrado de remitintes. O filtrado de -remitintes descríbese na páxina de fitros +remitintes descríbese na páxina de fitros de privacidade e pode ser aceptar automaticamente, reter automaticamente, rexeitar automaticamente ou descartar -automaticamente. Esta opción non estará dispoñíbel se o enderezo -xa está nun +automaticamente. Esta opción non estará dispoñíbel se o enderezo +xa está nun dos filtros de remitinte. -

        Ao rematar, prema o botón Enviar todos os -datos do final da páxina. Este botón entregará todas as accións +

        Ao rematar, prema o botón Enviar todos os +datos do final da páxina. Este botón entregará todas as accións seleccionadas para todas as solicitudes administrativas para as que se fixo -algunha selección. +algunha selección. -

        Volver á páxina cos sumarios. +

        Volver á páxina cos sumarios. +

        \ No newline at end of file diff --git a/templates/gl/admindbpreamble.html b/templates/gl/admindbpreamble.html index 6bac6bcd..e148eb34 100644 --- a/templates/gl/admindbpreamble.html +++ b/templates/gl/admindbpreamble.html @@ -1,11 +1,75 @@ -Esta páxina contén un subconxunto dos envíos á -rolda de distribución %(listname)s que requiren -da súa conformidade. Actualmente amosa %(description)s +Esta páxina contén un subconxunto dos envíos á +rolda de distribución %(listname)s que requiren +da súa conformidade. Actualmente amosa %(description)s -

        Para cada solicitude administrativa, seleccione a acción que se debe tomar +

        Para cada solicitude administrativa, seleccione a acción que se debe tomar ao premer mandar todos os datos despois de rematar. Pode -achar máis información aquí. +achar máis información aquí. -

        Tamén pode ver un resumo de todas as solicitudes +

        Tamén pode ver un resumo de todas as solicitudes pendentes. +

        \ No newline at end of file diff --git a/templates/gl/admindbsummary.html b/templates/gl/admindbsummary.html index 7e90d412..543a1ee7 100644 --- a/templates/gl/admindbsummary.html +++ b/templates/gl/admindbsummary.html @@ -1,20 +1,84 @@ -Esta páxina contén un sumario das solicitudes administrativas que -requiren da súa aprobación para a rolda de -distribución
        +Esta páxina contén un sumario das solicitudes administrativas que +requiren da súa aprobación para a rolda de +distribución %(listname)s. -O primeiro que atopará serán as solicitudes de alta e de baixa +O primeiro que atopará serán as solicitudes de alta e de baixa na rolda, sempre -que existir algunha, seguida por calquera envío á rolda que precisar -da súa -aprobación. +que existir algunha, seguida por calquera envío á rolda que precisar +da súa +aprobación. -

        Seleccione a acción que se debe tomar para cada unha das solicitudes administrativas +

        Seleccione a acción que se debe tomar para cada unha das solicitudes administrativas e -prema o botón Enviar todos os datos logo de rematar. -Pode ver aquí unhas instrucións máis detalladas. +prema o botón Enviar todos os datos logo de rematar. +Pode ver aquí unhas instrucións máis detalladas. -

        Tamén pode ver os detalles de todos os - envíos +

        Tamén pode ver os detalles de todos os + envíos retidos. +

        \ No newline at end of file diff --git a/templates/gl/admlogin.html b/templates/gl/admlogin.html index c1ccf023..dcdc66d3 100755 --- a/templates/gl/admlogin.html +++ b/templates/gl/admlogin.html @@ -1,42 +1,103 @@ - Autenticación de %(who)s de %(listname)s - - - -
        +Autenticación de %(who)s de %(listname)s + + + + %(message)s - - - - - - - - - - - -
        - - Autenticación de %(who)s de %(listname)s -
        -
        -

        Importante: a partir de agora debe + + + + + + + + + + + +
        + + Autenticación de %(who)s de %(listname)s +
        +
        +

        Importante: a partir de agora debe ter habilitadas as cookies do seu navegador; caso contrario, os seus - cambios non terán efecto. + cambios non terán efecto. -

        As sesións baseadas en cookies úsanse na interface administrativa +

        As sesións baseadas en cookies úsanse na interface administrativa do Mailman de xeito que non precisa identificarse continuamente - con cada operación administrativa que -realizar. A cookie expirará automaticamente cando saia do navegador -ou se selecciona a opción Saída que está na -sección titulada - Outras actividades administrativas (que verá logo de + con cada operación administrativa que +realizar. A cookie expirará automaticamente cando saia do navegador +ou se selecciona a opción Saída que está na +sección titulada + Outras actividades administrativas (que verá logo de entrar -con éxito). -

        +con éxito). +

        diff --git a/templates/gl/archidxentry.html b/templates/gl/archidxentry.html index f9bb57aa..5c620b20 100644 --- a/templates/gl/archidxentry.html +++ b/templates/gl/archidxentry.html @@ -1,4 +1,68 @@ -
      • %(subject)s -  -%(author)s - +
      • %(subject)s +  +%(author)s + +
      • \ No newline at end of file diff --git a/templates/gl/archidxfoot.html b/templates/gl/archidxfoot.html index ddd00bde..2887c97d 100644 --- a/templates/gl/archidxfoot.html +++ b/templates/gl/archidxfoot.html @@ -1,20 +1,84 @@ - -

        - Data da última mensaxe: - %(lastdate)s
        - Arquivouse o: %(archivedate)s -

        -

          -
        • Mensaxes ordenadas por: + +

          +Data da última mensaxe: +%(lastdate)s
          +Arquivouse o: %(archivedate)s +

          +

          -

          -


          - Pipermail xerou este arquivo - - +
        +

        +


        +Pipermail xerou este arquivo + + +

        \ No newline at end of file diff --git a/templates/gl/archidxhead.html b/templates/gl/archidxhead.html index 80af0166..4d102bad 100644 --- a/templates/gl/archidxhead.html +++ b/templates/gl/archidxhead.html @@ -1,24 +1,89 @@ - - - O arquivo da rolda %(listname)s %(archive)s por %(archtype)s - + + + +O arquivo da rolda %(listname)s %(archive)s por %(archtype)s + %(encoding)s - - - -

        Arquivos de %(archive)s por %(archtype)s

        -
          -
        • Mensaxes ordenadas por: + + + +

          Arquivos de %(archive)s por %(archtype)s

          + -

          Comezo: %(firstdate)s
          - Fin: %(lastdate)s
          - Mensaxes: %(size)s

          -

            +
          +

          Comezo: %(firstdate)s
          +Fin: %(lastdate)s
          +Mensaxes: %(size)s

          +

            +

          \ No newline at end of file diff --git a/templates/gl/archlistend.html b/templates/gl/archlistend.html index 9bc052dd..2e1191b0 100644 --- a/templates/gl/archlistend.html +++ b/templates/gl/archlistend.html @@ -1 +1,64 @@ -
        + diff --git a/templates/gl/archliststart.html b/templates/gl/archliststart.html index d8e3edcc..36b06275 100644 --- a/templates/gl/archliststart.html +++ b/templates/gl/archliststart.html @@ -1,4 +1,68 @@ - - - - +
        ArquivoOrdenar por:Versión para a descarga
        + + + +
        ArquivoOrdenar por:Versión para a descarga
        \ No newline at end of file diff --git a/templates/gl/archtoc.html b/templates/gl/archtoc.html index e8155c8a..85d2230d 100644 --- a/templates/gl/archtoc.html +++ b/templates/gl/archtoc.html @@ -1,14 +1,78 @@ - - - Os arquivos da rolda %(listname)s - + + + +Os arquivos da rolda %(listname)s + %(meta)s - - -

        Os arquivos da rolda %(listname)s

        -

        - Pode obter máis información sobre esta rolda + + +

        Os arquivos da rolda %(listname)s

        +

        + Pode obter máis información sobre esta rolda ou pode descargar o arquivo enteiro (%(size)s).

        @@ -16,5 +80,5 @@

        Os arquivos da rolda %(listname)s

        %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/gl/archtocentry.html b/templates/gl/archtocentry.html index a7e0ae5b..3d20cee6 100644 --- a/templates/gl/archtocentry.html +++ b/templates/gl/archtocentry.html @@ -1,12 +1,74 @@ - -
        %(archivelabel)s: - [ Fío ] - [ Título ] - [ Autor ] - [ Data ] -
        %(archivelabel)s: +[ Fío ] +[ Título ] +[ Autor ] +[ Data ] +
        - - - - - - - - - - - - - - - - - - + + + + + +

        - -- - -
        -

          -

        - Sobre - - - -
        -

        -

        Se desexa ver os envíos anteriores á rolda, - pode visitar os arquivos de - . - -

        -
        - Como usar a rolda -
        + + + +Páxina de información de <mm-list-name></mm-list-name> + + +

        + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + + + + + + + +
        + -- + +
        +

          +

        +Sobre + + + +
        +

        +

        Se desexa ver os envíos anteriores á rolda, + pode visitar os arquivos de +. + +

        +
        +Como usar a rolda +
        Para lles remitir unha mensaxe a todos os membros da rolda, - envíeo ao enderezo . + envíeo ao enderezo . -

        Pode subscribirse á rolda ou cambiar a súa - subscrición nas seccións seguintes. -

        - Como subscribirse a -
        -

        - Para se subscribir a só ten de cubrir os datos do +

        Pode subscribirse á rolda ou cambiar a súa + subscrición nas seccións seguintes. +

        +Como subscribirse a +
        +

        + Para se subscribir a só ten de cubrir os datos do formulario seguinte: - -

          - - - - - - - - - - - - - - - - - -
          Enderezo de - correo electrónico: -  
          O seu nome (opcional): 
          Debe introducir un contrasinal de - protección. Isto, aínda que lle ofrece un nivel baixo de seguranza, - debería evitar que outros accedan coa súa - subscrición. Non empregue contrasinais valiosos porque - pode que se lle envíen algunha vez sen cifrar por correo - electrónico. + +
            + + + + + + + + + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - - - -
            Enderezo de + correo electrónico: + 
            O seu nome (opcional): 
            Debe introducir un contrasinal de + protección. Isto, aínda que lle ofrece un nivel baixo de seguranza, + debería evitar que outros accedan coa súa + subscrición. Non empregue contrasinais valiosos porque + pode que se lle envíen algunha vez sen cifrar por correo + electrónico. -

            Se decide non escribir ningún contrasinal, xerarase - un automaticamente que se lle enviará logo de - confirmar a subscrición. Sempre poderá pedir - que se lle envíe por correo o contrasinal cando editar as - opcións persoais - -
            Escriba un contrasinal: 
            Confirme o contrasinal: 
            En que idioma desexa ver as mensaxes?  
            Desexa recibir as mensaxes diarias compiladas - nunha única mensaxe? +

            Se decide non escribir ningún contrasinal, xerarase + un automaticamente que se lle enviará logo de + confirmar a subscrición. Sempre poderá pedir + que se lle envíe por correo o contrasinal cando editar as + opcións persoais + +
            Escriba un contrasinal: 
            Confirme o contrasinal: 
            En que idioma desexa ver as mensaxes?  
            Desexa recibir as mensaxes diarias compiladas + nunha única mensaxe? Non - Si -
            -
            -
            - -
          -
          - - Subscritores de -
          - - - -

          - - - -

          - - - +
        Non + Si +
        +
        +
        + + +

        + +Subscritores de +
        + + + +

        + + + +

        + +

        + diff --git a/templates/gl/options.html b/templates/gl/options.html index 020df8b4..a7e347ef 100644 --- a/templates/gl/options.html +++ b/templates/gl/options.html @@ -1,377 +1,412 @@ - - <MM-Presentable-User> configuración do subscritor para <MM-List-Name> - - - - - - - -
        - - Configuración do usuario - -
        + +<mm-presentable-user> configuración do subscritor para <mm-list-name> +</mm-list-name></mm-presentable-user> + + + + + + +
        + + Configuración do usuario + +

        - - - - - - - +
        - Opcións de subscrición de - na rolda de distribución . -
        - - - - -

        -

        + + + + + +
        + Opcións de subscrición de + na rolda de distribución . +
        + + +

        +

        - - +

        - - - - - - + +
        - - Para mudar a información con que está subscrito - a -
        Pode cambiar o enderezo con que - está subscrito á rolda de distribución + + + + + + - - - - - -
        + +Para mudar a información con que está subscrito + a +
        Pode cambiar o enderezo con que + está subscrito á rolda de distribución se introduce un enderezo novo no campo que aparece - máis abaixo. Enviaráselle unha mensaxe - de confirmación + máis abaixo. Enviaráselle unha mensaxe + de confirmación ao enderezo que especificar xa que os cambios - non terán os seus efectos até que o confirmar. + non terán os seus efectos até que o confirmar. -

        As confirmacións caducan despois de . +

        As confirmacións caducan despois de . -

        Opcionalmente, pode pór ou cambiar o seu nome e apelidos - (por exemplo Pepe Pérez). +

        Opcionalmente, pode pór ou cambiar o seu nome e apelidos + (por exemplo Pepe Pérez). -

        Se desexa aplicar as modificacións de subscrición - a todas as roldas en que está subscrito +

        Se desexa aplicar as modificacións de subscrición + a todas as roldas en que está subscrito en , - marque a cela de verificación Cambiar globalmente. + marque a cela de verificación Cambiar globalmente. -

        - - - - - - - -
        Enderezo novo:
        Unha outra vez para confirmar:
        -
        - - +
        O seu nome +

        + + + + + + + +
        Enderezo novo:
        Unha outra vez para confirmar:
        +
        + + - - -
        O seu nome (opcional):
        -
        -

        Cambiar globalmente -

        - +
        +

        +

        Cambiar globalmente +

        - - - - - - - - - - + + + + %(textlink)s - diff --git a/templates/he/archtocnombox.html b/templates/he/archtocnombox.html index b1357c32..35c8670f 100644 --- a/templates/he/archtocnombox.html +++ b/templates/he/archtocnombox.html @@ -1,18 +1,82 @@ - - - ×”××¨×›×™×‘×™× ×©×œ %(listname)s - + + + +×”××¨×›×™×‘×™× ×©×œ %(listname)s + %(meta)s - - -

        ×”××¨×›×™×‘×™× ×©×œ %(listname)s

        -

        + + +

        ×”××¨×›×™×‘×™× ×©×œ %(listname)s

        +

        ×פשר לקבל מידע נוסף ×ודות רשימה זו.

        %(noarchive_msg)s %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/he/article.html b/templates/he/article.html index 66d46a5d..d51e52d6 100644 --- a/templates/he/article.html +++ b/templates/he/article.html @@ -1,12 +1,13 @@ - - - %(title)s - - - - - + +

        ×”×רכיון של %(listname)s

        +

        עדיין ×œ× × ×©×œ×—×• ×ž×¡×¨×™× ×ל רשימה זו, לכן ×”×רכיב כרגע ריק. ×פשר לקבל מידע נוסף ×ודות רשימה זו.

        - - + + diff --git a/templates/he/headfoot.html b/templates/he/headfoot.html index 1c3d555a..140b6d89 100644 --- a/templates/he/headfoot.html +++ b/templates/he/headfoot.html @@ -1,9 +1,72 @@ -טקסט ×–×” יכול לכלול +טקסט ×–×” יכול לכלול מחרוזות בפורמ×ט Python ×שר מפוענחות מול תכונות הרשימה. רשימת תתי-×”×ž×¦×‘×™× ×”×ž×•×ª×¨×™×:
          -
        • real_name - ×”×©× "היפה" של הרשימה; בד"×› +
        • real_name - ×”×©× "היפה" של הרשימה; בד"×› ×©× ×”×¨×©×™×ž×” ×¢× ×ותיות ר×שיות מת×ימות.
        • list_name - ×”×©× ×œ×¤×™×• ×ž×–×”×™× ×ת הרשימה @@ -22,4 +85,4 @@
        • info - הת×ור ×”×ž×œ× ×©×œ רשימת הדיוור.
        • cgiext - הסיומת המתווספת ×ל ×צוות CGI. -
        + diff --git a/templates/he/listinfo.html b/templates/he/listinfo.html index b6f21f69..ce46a1ae 100644 --- a/templates/he/listinfo.html +++ b/templates/he/listinfo.html @@ -1,143 +1,204 @@ - - - - עמוד המידע של <MM-List-Name> - - - -

        -

        - Para anular a subscrición de - O resto das subscricións que ten en -
        - Marque a cela de verificación e prema este botón - para anular a subscrición desta rolda de distribución. + + + + + + + - + +
        +

        +Para anular a subscrición de +O resto das subscricións que ten en +
        + Marque a cela de verificación e prema este botón + para anular a subscrición desta rolda de distribución. Aviso: - esta acción terá un efecto inmediato + esta acción terá un efecto inmediato

        -

        - Pode ver un informe do resto das roldas de distribución +

        + Pode ver un informe do resto das roldas de distribución de - en que está subscrito. Empregue esta parte se - desexa aplicar os cambios de xeito global ao resto das subscricións + en que está subscrito. Empregue esta parte se + desexa aplicar os cambios de xeito global ao resto das subscricións

        -

        -
        - - - - - - - +
        - O seu contrasinal para -
        - -
        -

        Esqueceu o seu contrasinal?

        -
        - Prema este botón para que se lle envíe por correo - electrónico ao enderezo con que - está + + + + + - -
        +O seu contrasinal para +
        + +
        +

        Esqueceu o seu contrasinal?

        +
        + Prema este botón para que se lle envíe por correo + electrónico ao enderezo con que + está subscrito. -

        -

        - -
        -
        - -
        -

        Para cambiar o seu contrasinal

        - - - - - - - - -
        Contrasinal - novo:
        Unha outra vez para - confirmar:
        - - -

        - Cambiar globalmente. -
        -
        - +

        +

        + +
        +

        + +
        +

        Para cambiar o seu contrasinal

        + + + + + + + + +
        Contrasinal + novo:
        Unha outra vez para + confirmar:
        + +

        +Cambiar globalmente. +
        +

        - - - - +
        - As súas opcións de subscrición para a rolda -
        + + +
        +As súas opcións de subscrición para a rolda +
        -

        -Comprobáronse os valores actuais. - -

        Cómpre salientar que algunha das opcións ten unha cela denominada +Comprobáronse os valores actuais. +

        Cómpre salientar que algunha das opcións ten unha cela denominada Cambiar globalmente. Ao activar esta cela, os cambios aplicararanse a cada unha -das roldas de distribución de en que está -subscrito. Prema Relacionar o resto das subscricións - máis arriba para ver cales son. +das roldas de distribución de en que está +subscrito. Prema Relacionar o resto das subscricións + máis arriba para ver cales son.

        - -
        - - Entrega de correo -

        - Se selecciona Activar recibirá as mensaxes que se - envíen a esta rolda de distribución. Se selecciona - Desactivar non recibirá o correo - que se enviar á rolda de distribución durante un tempo (por - exemplo, se marcha de vacacións). Se desactiva a recepción - das mensaxes de correo, non esqueza volver a activar a recepción + + - +

        - - - - + + + á hora de as ler, seleccione as recompilacións en texto plano. +

        - - - + - - - - - + + + + - - - - - - - - + + + + + + - - - - - - - + + + + + - - - + + - - - - + + + - - - + cada copia terá unha cabeceira X-Mailman-Copy: + yes incluída nela. +

        + + + +
        + +Entrega de correo +

        + Se selecciona Activar recibirá as mensaxes que se + envíen a esta rolda de distribución. Se selecciona + Desactivar non recibirá o correo + que se enviar á rolda de distribución durante un tempo (por + exemplo, se marcha de vacacións). Se desactiva a recepción + das mensaxes de correo, non esqueza volver a activar a recepción cando proceder. -

        - Activar
        - Desactivar

        - Aplicar globalmente -

        +Activar
        +Desactivar

        +Aplicar globalmente +

        - Activar o modo de compilación -

        - Se activa o modo de compilación, recibirá diariamente as mensaxes - que se enviaren á rolda recompiladas nunha única mensaxe, - en lugar de as recibir a medida que se envían. Se cambia o - modo de compilación de Activar a Desactivar, é posíbel que reciba unha última compilación. -

        - Desativar
        - Activar -
        - Desexa recibir as recompilacións en texto plano - ou con codificación MIME? -

        - Talvez o seu programa de xestión de correo electrónico non é compatíbel coas recompilacións - MIME. En xeral prefírense as recompilacións MIME, mais se +

        +Activar o modo de compilación +

        + Se activa o modo de compilación, recibirá diariamente as mensaxes + que se enviaren á rolda recompiladas nunha única mensaxe, + en lugar de as recibir a medida que se envían. Se cambia o + modo de compilación de Activar a Desactivar, é posíbel que reciba unha última compilación. +

        +Desativar
        +Activar +
        +Desexa recibir as recompilacións en texto plano + ou con codificación MIME? +

        + Talvez o seu programa de xestión de correo electrónico non é compatíbel coas recompilacións + MIME. En xeral prefírense as recompilacións MIME, mais se ten problemas - á hora de as ler, seleccione as recompilacións en texto plano. -

        - MIME
        - Texto plano

        - Aplicar globalmente -

        +MIME
        +Texto plano

        +Aplicar globalmente +

        - Quere recibir as mensaxes que vostede mesmo +
        +Quere recibir as mensaxes que vostede mesmo enviar a esta rolda? -

        - Polo xeral, recibirá unha copia de cada mensaxe que envíe - á rolda. Se non desexa recibir a dita copia, poña esta opción +

        + Polo xeral, recibirá unha copia de cada mensaxe que envíe + á rolda. Se non desexa recibir a dita copia, poña esta opción como Non. -

        - Non
        - Si
        - Desexa recibir unha confirmación cando enviar correo +

        +Non
        +Si
        +Desexa recibir unha confirmación cando enviar correo a esta rolda? -

        -

        - Non
        - Si -
        - Desexa recibir as lembranzas desta rolda? -

        - Mensualmente, recibirá unha mensaxe de correo co contrasinal - de cada unha das roldas en que está subscrito. Pode - desactivar este envío por cada rolda con só seleccionar - Non nesta opción. Se decide desactivar a lembranza - dos contrasinais en todas as roldas a que está subscrito, - non se lle enviará ningunha mensaxe. -

        - Non
        - Si

        - Aplicar globalmente -

        - Desexa ocultarse da listaxe de subscritores? -

        - Cando alguén vexa a listaxe de subscritores, o seu enderezo - de correo electrónico aparecerá relacionado +

        +

        +Non
        +Si +
        +Desexa recibir as lembranzas desta rolda? +

        + Mensualmente, recibirá unha mensaxe de correo co contrasinal + de cada unha das roldas en que está subscrito. Pode + desactivar este envío por cada rolda con só seleccionar + Non nesta opción. Se decide desactivar a lembranza + dos contrasinais en todas as roldas a que está subscrito, + non se lle enviará ningunha mensaxe. +

        +Non
        +Si

        +Aplicar globalmente +

        +Desexa ocultarse da listaxe de subscritores? +

        + Cando alguén vexa a listaxe de subscritores, o seu enderezo + de correo electrónico aparecerá relacionado (dun xeito escuro para evitar os correos lixo). Se non desexar que o seu enderezo apareza, seleccione Si. -

        - Non
        - Si -
        - En que idioma desexa ver as mensaxes da rolda? -

        -

        - -
        - A que tipo de temas lle gustaría subscribirse? -

        - Ao seleccionar un ou máis temas, pode filtrar - o tráfico da rolda de distribución de - xeito que só reciba un subconxunto das +

        +Non
        +Si +
        +En que idioma desexa ver as mensaxes da rolda? +

        +

        + +
        +A que tipo de temas lle gustaría subscribirse? +

        + Ao seleccionar un ou máis temas, pode filtrar + o tráfico da rolda de distribución de + xeito que só reciba un subconxunto das mensaxes. Unicamente chegaranlle as mensaxes que - coincidiren con algún dos temas seleccionados. + coincidiren con algún dos temas seleccionados.

        Se unha mensaxe non coincide co tema, a regra que decide se se entrega a mensaxe depende da - configuración da opción de abaixo. Se - non selecciona ningún tema de interese, - recibirá todas as mensaxes que se dirixiren á rolda. -

        - -
        - Quere recibir as mensaxes que non coincidan con - ningún tema? -

        - Esta opción só ten o seu efecto se está - subscrito a algún tema dos de enriba. Esta - opción describe cal será a regra de + configuración da opción de abaixo. Se + non selecciona ningún tema de interese, + recibirá todas as mensaxes que se dirixiren á rolda. +

        + +
        +Quere recibir as mensaxes que non coincidan con + ningún tema? +

        + Esta opción só ten o seu efecto se está + subscrito a algún tema dos de enriba. Esta + opción describe cal será a regra de entrega por defecto para as mensaxes que non coincidiren - con ningún tema. Se selecciona que Non + con ningún tema. Se selecciona que Non indica o seu desexo de non recibir as mensaxes que non - coincidan con ningún tema, mentres que se - selecciona que Si significa que recibirá - todas as mensaxes que non coincidiren con ningún + coincidan con ningún tema, mentres que se + selecciona que Si significa que recibirá + todas as mensaxes que non coincidiren con ningún tema.

        Se - non selecciona ningún tema de interese na parte superior, - entón recibirá todas as mensaxes - que se enviaren á rolda. -

        - Non
        - Si -
        - Desexa evitar as copias duplicadas das súas propias + non selecciona ningún tema de interese na parte superior, + entón recibirá todas as mensaxes + que se enviaren á rolda. +

        +Non
        +Si +
        +Desexa evitar as copias duplicadas das súas propias mensaxes? -

        - Cando estea incluído explicitamente nas cabeceiras - To: ou Cc: dunha mensaxe dirixida á rolda, pode optar - por non recibir outra copia da rolda de distribución. Seleccione Si para - evitar recibir copias da rolda de distribución e +

        + Cando estea incluído explicitamente nas cabeceiras + To: ou Cc: dunha mensaxe dirixida á rolda, pode optar + por non recibir outra copia da rolda de distribución. Seleccione Si para + evitar recibir copias da rolda de distribución e Non para as recibir.

        Se a rolda ten subscritores coas mensaxes personalizadas e elixe que si desexa recibir copias, - cada copia terá unha cabeceira X-Mailman-Copy: - yes incluída nela. - -

        - Non
        - Si

        - Aplicar globalmente -

        -
        -
        +Non
        +Si

        +Aplicar globalmente +

        + +
        -

        - - - - + + +

        diff --git a/templates/gl/private.html b/templates/gl/private.html index 52c1d939..2790b45b 100755 --- a/templates/gl/private.html +++ b/templates/gl/private.html @@ -1,60 +1,121 @@ - Autenticación para os ficheiros privados de %(realname)s - - - -
        +Autenticación para os ficheiros privados de %(realname)s + + + + %(message)s - - - - - - - - - - - - - - - -
        - Autenticación de ficheiros - privados de %(realname)s -
        Enderezo:
        Contrasinal:
        -
        -

        Importante: a partir deste momento, deberá + + + + + + + + + + + + + + + +
        +Autenticación de ficheiros + privados de %(realname)s +
        Enderezo:
        Contrasinal:
        +
        +

        Importante: a partir deste momento, deberá ter habilitadas as cookies no seu navegador; caso contrario, os seus - cambios non terán efecto. -

        As sesións baseadas nas cookies úsanse na interface administrativa + cambios non terán efecto. +

        As sesións baseadas nas cookies úsanse na interface administrativa do Mailman, de xeito que non precisa identificarse continuamente - con cada operación administrativa que realizar. A cookie caducará automaticamente + con cada operación administrativa que realizar. A cookie caducará automaticamente cando saia do navegador ou pode borrala se selecciona - a opción Saída baixo a sección titulada - Outras actividades administrativas (que verá logo de + a opción Saída baixo a sección titulada + Outras actividades administrativas (que verá logo de entrar satisfactoriamente).

        - - - - - - + + + +
        - Password Reminder -
        If you don't remember your password, enter your email address + + + + + + - - - - -
        +Password Reminder +
        If you don't remember your password, enter your email address above and click the Remind button and your password will be emailed to you.
        - +
        +

        diff --git a/templates/gl/roster.html b/templates/gl/roster.html index 7685b988..7f9eb92a 100644 --- a/templates/gl/roster.html +++ b/templates/gl/roster.html @@ -1,55 +1,114 @@ - - - Subscritores de <MM-List-Name> - - - - -

        - - - - - - - - - - - - - - - -
        - Subscritores de -
        - -

        -

        - -

        Seleccione o seu enderezo para visitar o estado da - subscrición. -
        - (As entradas entre parénteses teñen a recepción - do correo da rolda desactivada.)

        -
        -
        - - Subscritores en coa recepción das mensaxes a - medida que chegan: -
        -
        -
        - - Subscritores en coa recepción diferida en resumos: -
        -
        -

        -

        -

        -

        - - - + + +Subscritores de <mm-list-name></mm-list-name> + + +

        + + + + + + + + + + + + + + + +
        +Subscritores de +
        +

        +

        +

        Seleccione o seu enderezo para visitar o estado da + subscrición. +
        +(As entradas entre parénteses teñen a recepción + do correo da rolda desactivada.)

        +
        +
        + + Subscritores en coa recepción das mensaxes a + medida que chegan: +
        +
        +
        + + Subscritores en coa recepción diferida en resumos: +
        +
        +

        +

        +

        +

        + +

        + diff --git a/templates/gl/subscribe.html b/templates/gl/subscribe.html index de82a7e9..91b11a04 100644 --- a/templates/gl/subscribe.html +++ b/templates/gl/subscribe.html @@ -1,9 +1,72 @@ -<MM-List-Name> Resultados da subscrición +<mm-list-name> Resultados da subscrición</mm-list-name> -

        Resultados da subscrición

        - - - +

        Resultados da subscrición

        + + + diff --git a/templates/he/admindbdetails.html b/templates/he/admindbdetails.html index fd7d49a8..b5e28d7d 100644 --- a/templates/he/admindbdetails.html +++ b/templates/he/admindbdetails.html @@ -1,4 +1,67 @@ -בקשות מנהלתיות מוצגות ב×חת משתי דרכי×, בעמוד סיכו×, +בקשות מנהלתיות מוצגות ב×חת משתי דרכי×, בעמוד סיכו×, ובעמוד פרטי×,. עמוד ×”×¡×™×›×•× ×ž×›×™×œ בקשות הרשמה ועזיבה ממתינות, בנוסף ×œ×ž×¡×¨×™× ×©×ž×—×›×™× ×œ×ישור שלך, מקובצות לפי כתובת הדו×"ל של השולח. עמוד ×”×¤×¨×˜×™× ×ž×›×™×œ מבט מפורט יותר של כל מסר מוחזק, כולל כל כותרות המסר, וקטע מגוף המסר. @@ -18,8 +81,7 @@
      • זרוק -- זרוק ×ת המסר המקורי, בלי לשלוח הודעת דחיה. עבור בקשות הצטרפות, ×–×” פשוט מוחק ×ת הבקשה בלי לשלוח הודעה על כך למבקש. זו הפעולה -שבדכ"×› תנקוט עבור דו×ר זבל מוכר. - +שבדכ"×› תנקוט עבור דו×ר זבל מוכר.
      • עבור ×ž×¡×¨×™× ×ž×•×—×–×§×™×, הדלק ×ת ×”×פשרות שמור ×× ×‘×¨×¦×•× ×š לשמור העתק של המסר למנהל ×”×תר. ×–×” שימושי עבור ×ž×¡×¨×™× ×¤×•×’×¢× ×™×™× ×©×‘×¨×¦×•× ×š למחוק, ×בל שצריך לשמור ×ותן לבדיקה מ×וחרת יותר. @@ -38,7 +100,7 @@

        ×× ×”×©×•×œ×— ×יננו מנוי ברשימה, ניתן להוסיף ×ת כתובת הדו×"ל שלו ×ל מסנן שולח. מסנני שולח מתו××¨×™× ×‘×¢×ž×•×“ הפרטיות של -מסנני שולח, ×•×ž×§×‘×œ×™× ×חד ×”×¢×¨×›×™× auto-accept (מ×שר), +מסנני שולח, ×•×ž×§×‘×œ×™× ×חד ×”×¢×¨×›×™× auto-accept (מ×שר), auto-hold (מחזיק),auto-reject (דוחה), ×ו auto-discard (זורק). ×פשרות זו ×œ× ×ª×”×™×” זמינה ×× ×”×›×ª×•×‘×ª כבר מופיעה ב×חד מבין מסנני השולח. @@ -49,3 +111,4 @@

        חזרה לעמוד הסיכו×. +

        \ No newline at end of file diff --git a/templates/he/admindbpreamble.html b/templates/he/admindbpreamble.html index a131e47f..a2651996 100644 --- a/templates/he/admindbpreamble.html +++ b/templates/he/admindbpreamble.html @@ -1,7 +1,71 @@ -עמוד ×–×” מכיל תת-קבוצה של ×”×ž×¡×¨×™× ×©×œ %(listname)s ברשימת +עמוד ×–×” מכיל תת-קבוצה של ×”×ž×¡×¨×™× ×©×œ %(listname)s ברשימת הדיוור. ×”×•× ×›×¨×’×¢ מציג %(description)s

        עבור כל בקשה מנהלתית, × × ×œ×‘×—×•×¨ ×ת הפעולה שיש לנקוט, ולחץ על הלחצן הגש ×ת כל המידע כשתסיי×. הור×ות מפורטות יותר זמינות ×›×ן.

        ניתן ×’× ×œ×¦×¤×•×ª ×‘×¡×™×›×•× ×©×œ כל הבקשות הממתינות. +

        \ No newline at end of file diff --git a/templates/he/admindbsummary.html b/templates/he/admindbsummary.html index 5acd334d..d8abc893 100644 --- a/templates/he/admindbsummary.html +++ b/templates/he/admindbsummary.html @@ -1,4 +1,67 @@ -עמוד ×–×” מכיל ×¡×™×›×•× ×©×œ קבוצת כל הבקשות המנהלתיות שדורשות +עמוד ×–×” מכיל ×¡×™×›×•× ×©×œ קבוצת כל הבקשות המנהלתיות שדורשות ×ת ×ישורך עבור רשימת הדיוור %(listname)s. ר×שית, ×ª×ž×¦× ×ת בקשות ההצטרפות והעזיבה הממתינות, ×× ×™×©, ו×חריכן כל ×”×ž×¡×¨×™× ×”×ž×ž×ª×™× ×™× ×œ×ישורך. @@ -8,3 +71,4 @@ ישנן ×’× ×”×•×¨×ות מפורטות יותר זמינות. בנוסף, ×פשר ×’× ×œ×¦×¤×•×ª ×‘×¤×¨×˜×™× ×©×œ כל ×”×ž×¡×¨×™× ×”×ž×ž×ª×™× ×™×. +

        \ No newline at end of file diff --git a/templates/he/admlogin.html b/templates/he/admlogin.html index 501d9b8c..f1e14028 100755 --- a/templates/he/admlogin.html +++ b/templates/he/admlogin.html @@ -1,30 +1,91 @@ - ×”×ימות של %(listname)s %(who)s - - - -
        +×”×ימות של %(listname)s %(who)s + + + + %(message)s - - - - - - - - - - - -
        - ×”×ימות של %(listname)s %(who)s - -
        סיסמת הרשימה %(who)s:
        -
        -

        חשוב: מנקודה זו ו×ילך, ×—×™×™×‘×™× + + + + + + + + + + + +
        +×”×ימות של %(listname)s %(who)s + +
        סיסמת הרשימה %(who)s:
        +
        +

        חשוב: מנקודה זו ו×ילך, ×—×™×™×‘×™× ×©×¢×•×’×™×•×ª יהיו פעילות בדפדפן שלך, ×חרת ×œ× ×™×ª×‘×¦×¢×• ×©×™× ×•×™×™× ×ž× ×”×œ×ª×™×™×. @@ -36,6 +97,6 @@ מתחת ל-פעולות ניהול נוספות (שתוכל לר×ות ל×חר כניסה מוצלחת למערכת). -

        +

        diff --git a/templates/he/archidxentry.html b/templates/he/archidxentry.html index f9bb57aa..5c620b20 100644 --- a/templates/he/archidxentry.html +++ b/templates/he/archidxentry.html @@ -1,4 +1,68 @@ -
      • %(subject)s -  -%(author)s - +
      • %(subject)s +  +%(author)s + +
      • \ No newline at end of file diff --git a/templates/he/archidxfoot.html b/templates/he/archidxfoot.html index 3e0a01c7..6c24fa5f 100644 --- a/templates/he/archidxfoot.html +++ b/templates/he/archidxfoot.html @@ -1,21 +1,85 @@ - -

        - ת×ריך המסר ×”×חרון: - %(lastdate)s
        - הועבר ל×רכיב בת×ריך: %(archivedate)s -

        -

          -
        • ×”×ž×¡×¨×™× ×ž×ž×•×™× ×™× ×œ×¤×™: + +

          +ת×ריך המסר ×”×חרון: +%(lastdate)s
          +הועבר ל×רכיב בת×ריך: %(archivedate)s +

          +

          -

          -


          - ×רכיב ×–×” נוצר על ידי +
        +

        +


        +×רכיב ×–×” נוצר על ידי Pipermail %(version)s. - - + + +

        \ No newline at end of file diff --git a/templates/he/archidxhead.html b/templates/he/archidxhead.html index 10d7c19e..f30c4d10 100644 --- a/templates/he/archidxhead.html +++ b/templates/he/archidxhead.html @@ -1,15 +1,79 @@ - - - ×”××¨×›×™×‘×™× ×©×œ %(listname)s %(archive)s לפי %(archtype)s - + + + +×”××¨×›×™×‘×™× ×©×œ %(listname)s %(archive)s לפי %(archtype)s + %(encoding)s - - - -

        ×”××¨×›×™×‘×™× ×©×œ %(archive)s לפי %(archtype)s

        -
          -
        • ×”×ž×¡×¨×™× ×ž×ž×•×™× ×™× ×œ×¤×™: + + + +

          ×”××¨×›×™×‘×™× ×©×œ %(archive)s לפי %(archtype)s

          + -

          ת×ריך התחלה: %(firstdate)s
          - ת×ריך סיו×: %(lastdate)s
          - מסרי×: %(size)s

          -

            +
          +

          ת×ריך התחלה: %(firstdate)s
          +ת×ריך סיו×: %(lastdate)s
          +מסרי×: %(size)s

          +

            +

          \ No newline at end of file diff --git a/templates/he/archlistend.html b/templates/he/archlistend.html index 9bc052dd..2e1191b0 100644 --- a/templates/he/archlistend.html +++ b/templates/he/archlistend.html @@ -1 +1,64 @@ -
        + diff --git a/templates/he/archliststart.html b/templates/he/archliststart.html index 8ceab4ff..95363c24 100644 --- a/templates/he/archliststart.html +++ b/templates/he/archliststart.html @@ -1,4 +1,70 @@ - - - - +
        ×”×רכיבצפה לפי:×’×™×¨×¡× ×œ×”×•×¨×“×”
        + + + + + +
        ×”×רכיבצפה לפי:×’×™×¨×¡× ×œ×”×•×¨×“×”
        diff --git a/templates/he/archtoc.html b/templates/he/archtoc.html index 88ed1126..62e27f1e 100644 --- a/templates/he/archtoc.html +++ b/templates/he/archtoc.html @@ -1,13 +1,77 @@ - - - ×”××¨×›×™×‘×™× ×©×œ %(listname)s - + + + +×”××¨×›×™×‘×™× ×©×œ %(listname)s + %(meta)s - - -

        ×”××¨×›×™×‘×™× ×©×œ %(listname)s

        -

        + + +

        ×”××¨×›×™×‘×™× ×©×œ %(listname)s

        +

        ×פשר לקבל מידע נוסף ×ודות רשימה זו ×ו ש×פשר להוריד ×ת ×”×רכיב הגולמי ×”×ž×œ× (%(size)s). @@ -16,5 +80,5 @@

        ×”××¨×›×™×‘×™× ×©×œ %(listname)s

        %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/he/archtocentry.html b/templates/he/archtocentry.html index 2243e0db..aac9050e 100644 --- a/templates/he/archtocentry.html +++ b/templates/he/archtocentry.html @@ -1,12 +1,74 @@ - -
        %(archivelabel)s: - [ שיחה ] - [ × ×•×©× ] - [ מ×ת ] - [ ת×ריך ] -
        %(archivelabel)s: +[ שיחה ] +[ × ×•×©× ] +[ מ×ת ] +[ ת×ריך ] +
        - - - - - - - - - - - - - - - - - - + + + + + +

        - -- - -
        -

          -

        - ×ודות - - - -
        -

        -

        כדי לצפות ב×וסף ×”×ž×¡×¨×™× ×”×§×•×“×ž×™× ×œ×¨×©×™×ž×” זו, - בקר ב×רכיון של - . - -

        -
        - השימוש ב- -
        + + + +עמוד המידע של <mm-list-name></mm-list-name> + + +

        + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + + + + + + + +
        + -- + +
        +

          +

        +×ודות + + + +
        +

        +

        כדי לצפות ב×וסף ×”×ž×¡×¨×™× ×”×§×•×“×ž×™× ×œ×¨×©×™×ž×” זו, + בקר ב×רכיון של +. + +

        +
        +השימוש ב- +
        כדי לשלוח מסר ×ל כל מנויי הרשימה, שלח דו×"ל ×ל - . + .

        ניתן ×œ×”×¨×©× ×ל הרשימה, ×ו לשנות ×ת המנוי הנוכחי שלך, - בקטע למטה.

        - הרשמה ×ל -
        -

        - ×”×¨×©× ×ל על יד מילוי הטופס + בקטע למטה.

        +הרשמה ×ל +
        +

        + ×”×¨×©× ×ל על יד מילוי הטופס הב×. - -

          - - - - - - - - - - - - - - - - - -
          כתובת הדו×"ל שלך: -  
          שמך (×œ× ×—×•×‘×”): 
          ×פשר להקליד סיסמת פרטיות למטה. + +
            + + + + + + + + + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - - - -
            כתובת הדו×"ל שלך: + 
            שמך (×œ× ×—×•×‘×”): 
            ×פשר להקליד סיסמת פרטיות למטה. ×–×” מספק רק ×בטחה קלה, ×בל ×מור למנוע מ××—×¨×™× ×ž×פשרות התעסקות ×¢× ×”×ž× ×•×™ שלך. ×ין להשתמש ×‘×¡×™×¡×ž× ×—×©×•×‘×” לך כיוון ×©×”×™× ×ª×™×©×œ×— ×ליך בדו×"ל בטקסט × ×§×™ מדי פע×. -

            ×× ×ª×‘×—×¨ ×©×œ× ×œ×”×§×œ×™×“ סיסמ×, המערכת תיצור לך ×¡×™×¡×ž× +

            ×× ×ª×‘×—×¨ ×©×œ× ×œ×”×§×œ×™×“ סיסמ×, המערכת תיצור לך ×¡×™×¡×ž× ×‘×ופן ×וטומטי, ×•×”×™× ×ª×™×©×œ×— ×ליך ל×חר שת×שר ×ת המנוי שלך. תמיד ניתן לבקש דו×ר חוזר ×¢× ×”×¡×™×¡×ž× ×©×œ×š ×›×שר ×תה עורך ×ת ההגדרות ×”×ישיות שלך. - -
            -
            בחר סיסמ×: 
            ×”×¡×™×¡×ž× ×©×•×‘, ל×ישור: 
            ב××™×–×” שפה ×תה מעדיף לקבל ×ת ההודעות שלך?  
            ×”×× ×ª×¨×¦×” לקבל ×ת משלוחי הרשימה ×‘×ª×§×¦×™×¨×™× ×™×•×ž×™×™×? + + +
            בחר סיסמ×: 
            ×”×¡×™×¡×ž× ×©×•×‘, ל×ישור: 
            ב××™×–×” שפה ×תה מעדיף לקבל ×ת ההודעות שלך?  
            ×”×× ×ª×¨×¦×” לקבל ×ת משלוחי הרשימה ×‘×ª×§×¦×™×¨×™× ×™×•×ž×™×™×? ×œ× - כן -
            -
            -
            - -
          -
          - - ×ž× ×•×™×™× -
          - - - -

          - - - -

          - - - +
        ×œ× + כן +
        +
        +
        + + +

        + + ×ž× ×•×™×™× +
        + + + +

        + + + +

        + +

        + diff --git a/templates/he/options.html b/templates/he/options.html index 08adf3f5..7f3d697f 100644 --- a/templates/he/options.html +++ b/templates/he/options.html @@ -1,43 +1,103 @@ - - <MM-Presentable-User> תצורת מנוי עבור <MM-List-Name> - - - - - -
        - - תצורת מנוי רשימת דיוור עבור - -
        + +<mm-presentable-user> תצורת מנוי עבור <mm-list-name> +</mm-list-name></mm-presentable-user> + + + + +
        + + תצורת מנוי רשימת דיוור עבור + +

        - - + +

        -

        + + - - - + + ברשימת הדיוור . + + + +
        סטטוס המנוי, הסיסמה וההגדרות של - - ברשימת הדיוור . -
        - - - - -

        -

        + + +

        +

        - - +

        - - - + +
        - - שינוי מידע ×ודות המנוי שלך ב- -
        + + + - - - - - -
        + +שינוי מידע ×ודות המנוי שלך ב- +
        ניתן לשנות ×ת הכתובת בה ×תה מנוי לרשימת הדיוור על ידי הקלדת הכתובת החדשה בשדות למטה. ×©×™× ×œ×‘: תישלח ×ליך הודעת ×ישור, ו×ליך ל×שר ×ת השינוי לפני @@ -52,102 +112,89 @@

        ×× ×‘×¨×¦×•× ×š לבצע שינוי מנוי לגבי כל הרשימות בהן ×תה מנוי ב-, סמן ×ת תיבת הסימון שינוי גלובלי. -

        - - - - - - - -
        כתובת חדשה:
        ושוב, ל×ישור:
        -
        - - +
        שמך +

        + + + + + + + +
        כתובת חדשה:
        ושוב, ל×ישור:
        +
        + + - - -
        שמך (×œ× ×—×•×‘×”):
        -
        -

        שינוי גלובלי

        - +
        +

        +

        שינוי גלובלי

        - - - - - - - + + + + %(textlink)s - diff --git a/templates/hr/article.html b/templates/hr/article.html index 6923ee4f..86206be3 100644 --- a/templates/hr/article.html +++ b/templates/hr/article.html @@ -1,12 +1,13 @@ - - - %(title)s - - - - - + +

        %(listname)s Arhiva

        +

        + Niti jedna poruka još nije poslana na ovu listu tako da je arhiva trenutno + prazna. Mo¾ete dobiti više informacija o ovoj listi.

        - - + + diff --git a/templates/hr/headfoot.html b/templates/hr/headfoot.html index c10d7ea6..9e58eb74 100644 --- a/templates/hr/headfoot.html +++ b/templates/hr/headfoot.html @@ -1,18 +1,81 @@ - Ovaj tekst mo¾e ukljuèiti + Ovaj tekst mo¾e ukljuèiti Python -format stringova koji su razluèeni prema atributima liste. Lista dozvoljenih +format stringova koji su razluèeni prema atributima liste. Lista dozvoljenih zamjena je:
          -
        • real_name - "Lijepi" naziv liste; obièno naziv liste s velikim poèetnim slovom. +
        • real_name - "Lijepi" naziv liste; obièno naziv liste s velikim poèetnim slovom.
        • list_name - Naziv po kojem je lista - identificirana u URL-ovima, gdje je bitna velièina slova (mala/velika) + identificirana u URL-ovima, gdje je bitna velièina slova (mala/velika)
        • host_name - Kompletan naziv domene na kojoj je server liste.
        • web_page_url - Temeljni URL za Mailmana. Na njega - mo¾e biti dodan npr. + mo¾e biti dodan npr. listinfo/%(list_name)s da bi bila prikazana listinfo stranica za mailing listu. @@ -21,4 +84,4 @@
        • info - Kompletan opis mailing liste.
        • cgiext - Dodatak koji se doda CGI skriptama. -
        + diff --git a/templates/hr/listinfo.html b/templates/hr/listinfo.html index 10df382f..bf79c783 100644 --- a/templates/hr/listinfo.html +++ b/templates/hr/listinfo.html @@ -1,145 +1,206 @@ - - - - <MM-List-Name> Info Page - - - -

        -

        - עוזב ×ת - ×”×ž×™× ×•×™× ×”× ×•×¡×¤×™× ×©×œ×š ב- -
        + + + + - + +
        +

        +עוזב ×ת +×”×ž×™× ×•×™× ×”× ×•×¡×¤×™× ×©×œ×š ב- +
        סמן ×ת תיבת הסימן ×”×ישור ולחץ על לחצן ×–×” כדי לעזוב רשימה זו. ×זהרה: פעולה זו תתבצע מיד!

        -

        +

        ניתן לצפות ברשימה של כל רשימות הדיוור ב- בהן ×תה מנוי. השתמש בזה ב×× ×‘×¨×¦×•× ×š לבצע ×ת ××•×ª× ×”×©×™× ×•×™×™× ×‘×”×’×“×¨×•×ª המנוי שלך ×’× ×‘×ž× ×•×™× ×”× ×•×¡×¤×™× ×©×œ×š.

        -

        -
        - - - - - +
        - ×”×¡×™×¡×ž× ×©×œ×š ב- -
        - -
        -

        שכחת ×ת ×”×¡×™×¡×ž× ×©×œ×š?

        -
        + + + - -
        +×”×¡×™×¡×ž× ×©×œ×š ב- +
        + +
        +

        שכחת ×ת ×”×¡×™×¡×ž× ×©×œ×š?

        +
        לחץ על לחצן ×–×” כדי ×©×”×¡×™×¡×ž× ×©×œ×š תישלח ×ל כתובת המנוי שלך. -

        -

        - -
        -
        - -
        -

        שינוי ×”×¡×™×¡×ž× ×©×œ×š

        - - - - - - - - -
        ×¡×™×¡×ž× ×—×“×©×”:
        ושוב, ל×ישור:
        - - -

        שנוי גלובלי. -
        -
        - +

        +

        + +
        +

        + +
        +

        שינוי ×”×¡×™×¡×ž× ×©×œ×š

        + + + + + + + + +
        ×¡×™×¡×ž× ×—×“×©×”:
        ושוב, ל×ישור:
        + +

        שנוי גלובלי. +
        +

        - - +
        - ×פשריות המנוי שלך ב- -
        +
        +×פשריות המנוי שלך ב- +
        -

        ×”×¢×¨×›×™× ×”× ×•×›×—×™×™× ×ž×¡×•×ž× ×™×. -

        ×©×™× ×œ×‘ שלחלק מה×פשריות יש תיבת סימון שינוי גלובלי. שינוי שדה ×–×” ×™×’×¨×•× ×œ×©×™× ×•×™×™× ×”×ž×ª×‘×¦×¢×™× ×›×ן להתבצע בכל רשימות הדיוור בהן ×תה מנוי ב-. לחץ על הצג @@ -155,95 +202,87 @@

        שינוי ×”×¡×™×¡×ž× ×©×œ×š

        דיוור ×תה מנוי.

        - -
        - - קבלת דו×ר

        + + - +

        - - - + +

        - - - - - - + + - - + - - - - + + - - + - - + - - - +

        +
        + +קבלת דו×ר

        קבע ×פשרות זו ל-פעיל כדי לקבל ×ž×¡×¨×™× ×©× ×©×œ×—×™× ×œ×¨×©×™×ž×ª דיוור זו. קבע ×œ× ×¤×¢×™×œ ×× ×תה רוצה להש×ר מנוי, ×בל ×œ× ×¨×•×¦×” שיישלח ×ליך דו×ר לתקופה (למשל, ×× ×תה ×™×•×ª× ×œ×—×•×¤×©×”). ×× ×תה הופך ×ותו ×œ×œ× ×¤×¢×™×œ, ×ל תשכח להחזיר ×ותו לפעיל כשתחזור; ×”×•× ×œ× ×™×”×¤×•×š לפעיל ב×ופן ×וטומטי. -

        - פעיל
        - ×œ× ×¤×¢×™×œ

        - שינוי גלובלי -

        +פעיל
        +×œ× ×¤×¢×™×œ

        +שינוי גלובלי +

        - קבע מצב תקצירי×

        +

        +קבע מצב תקצירי×

        ×× ×ª×“×œ×™×§ ×ת מצב תקצירי×, ×תה תקבל ×ת ×”×ž×¡×¨×™× ×‘×—×‘×™×œ×” ×חת (כד"×› ×חת ×œ×™×•× ×ך יכול להיות יותר ברשימות עמוסות), ×‘×ž×§×•× ×‘×‘×•×“×“×ª ×›×שר ×”× × ×©×œ×—×™×. ×× ×ž×¦×‘ ×ª×§×¦×™×¨×™× ×ž×©×ª× ×” מדלוק לכבוי, יתכן שתקבל תקציר ×חד ×חרון. -

        - כבוי
        - דלוק -
        - לקבל ×ª×§×¦×™×¨×™× ×‘-MIME ×ו בטקסט פשוט?

        +

        +כבוי
        +דלוק +
        +לקבל ×ª×§×¦×™×¨×™× ×‘-MIME ×ו בטקסט פשוט?

        תוכנת הדו×ר שלך יכולה לתמוך ×ו ×œ× ×œ×ª×ž×•×š בתקצירי MIME. ב×ופן כללי, עדיף תקצירי MIME, ×בל ×× ×ž×ª×¢×•×¨×¨×ª בעיה בקרי××”, בחר בטקסט פשוט. -

        - MIME
        - טקסט פשוט

        - שינוי גלובלי -

        +MIME
        +טקסט פשוט

        +שינוי גלובלי +

        - לקבל ×ת ×”×ž×¡×¨×™× ×©×œ עצמך לרשימה?

        +

        +לקבל ×ת ×”×ž×¡×¨×™× ×©×œ עצמך לרשימה?

        כרגיל, ×תה תקבל העתק של כל מסר ש×תה שולח ×ל הרשימה. ×× ×ינך רוצה לקבל העתק ×–×”, קבע ×פשרות זו ל-ל×. -

        - ל×
        - כן -
        - לקבל הודעת ×ישור על כך ששלחת מסר לרשימה?

        -

        - ל×
        - כן -
        - קבל תזכורת ×¡×™×¡×ž× ×‘×“×•×"ל עבור רשימה זו?

        +

        +ל×
        +כן +
        +לקבל הודעת ×ישור על כך ששלחת מסר לרשימה?

        +

        +ל×
        +כן +
        +קבל תזכורת ×¡×™×¡×ž× ×‘×“×•×"ל עבור רשימה זו?

        ×¤×¢× ×‘×—×•×“×©, תקבל דו×"ל המכיל תזכורת ×¡×™×¡×ž× ×¢×‘×•×¨ כל רשימה בשרת ×–×” ×ליה ×תה מנוי. ×תה יכול לכבות ×ת ×–×” ברמה של הרשימה על ידי בחירת ×œ× ×¢×‘×•×¨ ×פשרות זו. ×× ×ª×›×‘×” תזכורות ×¡×™×¡×ž× ×¢×‘×•×¨ כל הרשימות בהן ×תה מנוי, ×œ× ×™×™×©×œ×— ×ליך כל דו×"ל ×¢× ×ª×–×›×•×¨×ª. -

        - ל×
        - כן

        - שינוי גלובלי -

        - להסתיר ×ת עצמך מרשימת המנוי×?

        +

        +ל×
        +כן

        +שינוי גלובלי +

        +להסתיר ×ת עצמך מרשימת המנוי×?

        ×›×שר מישהו צופה במנויי הרשימה, כתובת הדו×"ל שלך בד"×› מוצגת (בצורה מטושטשת כדי להכשיל קוצרי כתובות של שולחי דו×ר זבל). ×× ×‘×¨×¦×•× ×š שכתובת הדו×"ל שלך בכלל - ×œ× ×ª×•×¤×™×¢ ברשימת המנוי×, בחר כן ל×פשרות זו. -

        - ל×
        - כן -
        - ×יזו שפה מועדפת עליך?

        -

        - -
        - ×ל ××™×–×” סיווגי × ×•×©× ×תה מעוניין להרש×?

        + ×œ× ×ª×•×¤×™×¢ ברשימת המנוי×, בחר כן ל×פשרות זו. +

        +ל×
        +כן +
        +×יזו שפה מועדפת עליך?

        +

        + +
        +×ל ××™×–×” סיווגי × ×•×©× ×תה מעוניין להרש×?

        על ידי בחירת × ×•×©× ×חד ×ו יותר, ניתן לסנן ×ת התעבורה ברשימת הדיוור, כך שתקבל רק תת-קבוצה של המסרי×. ×× ×ž×¡×¨ מת××™× ×œ×חד הנוש××™× ×©×‘×—×¨×ª, @@ -253,12 +292,11 @@

        שינוי ×”×¡×™×¡×ž× ×©×œ×š

        ×× ×ž×¡×¨ ×ינו מת××™× ×œ××£ נוש×, כלל המסירה תלוי בהגדרת ×”×פשרות למטה. ×× ×œ× ×ª×‘×—×¨ כלל נוש××™× ×©×ž×¢× ×™×™× ×™× ×ותך, תקבל ×ת כל ×”×ž×¡×¨×™× ×©× ×©×œ×—×™× ×ל רשימת הדיוור. -

        - -
        - ×”×× ×תה מעונין לקבל ×ž×¡×¨×™× ×©×œ× ×ž×ª××™×ž×™× ×œ××£ מסנן נוש××™×?

        +

        + +
        +×”×× ×תה מעונין לקבל ×ž×¡×¨×™× ×©×œ× ×ž×ª××™×ž×™× ×œ××£ מסנן נוש××™×?

        ×פשרות זו נכנסת לפעולה רק ×× ×‘×—×¨×ª לפחות × ×•×©× ×חד למעלה. ×”×™× ×ž×ª×רת ×ת כלל ברירת המחדל עבור @@ -269,13 +307,12 @@

        שינוי ×”×¡×™×¡×ž× ×©×œ×š

        ×× ×œ× × ×‘×—×¨×• נוש××™× ×ž×¢× ×™×™× ×™× ×œ×ž×¢×œ×”, ××–×™ תקבל כל מסר שנשלח ×ל רשימת הדיוור. -

        - ל×
        - כן -
        - המנע ×ž×”×¢×ª×§×™× ×›×¤×•×œ×™× ×©×œ מסרי×?

        +

        +ל×
        +כן +
        +המנע ×ž×”×¢×ª×§×™× ×›×¤×•×œ×™× ×©×œ מסרי×?

        ×›×שר ×תה מופיע במפורש בכותרת ×ל: @@ -288,21 +325,17 @@

        שינוי ×”×¡×™×¡×ž× ×©×œ×š

        פעיל, ו×תה בוחר לקבל העתקי×, לכל מסר תתווסף כותרת X-Mailman-Copy: yes. -
        - ל×
        - כן

        - שינוי גלובלי -

        -
        -
        +ל×
        +כן

        +שינוי גלובלי +

        +
        +
        -

        - - - - + + +

        diff --git a/templates/he/private.html b/templates/he/private.html index 5f6a4603..ce7ebe48 100755 --- a/templates/he/private.html +++ b/templates/he/private.html @@ -1,33 +1,94 @@ - ×ימות של ×”×רכיון הפרטי של %(realname)s - - - -
        +×ימות של ×”×רכיון הפרטי של %(realname)s + + + + %(message)s - - - - - - - - - - - - - - - -
        - ×ימות של ×”×רכיון הפרטי של%(realname)s -
        כתובת דו×"ל:
        סיסמ×:
        -
        -

        חשוב: מנקודה זו ו×ילך, עוגיות חייבות + + + + + + + + + + + + + + + +
        +×ימות של ×”×רכיון הפרטי של%(realname)s +
        כתובת דו×"ל:
        סיסמ×:
        +
        +

        חשוב: מנקודה זו ו×ילך, עוגיות חייבות להיות מ×ופשרות בדפדפן שלך, ×חרת ת×לץ לעבור ×ימות נוספת בעבור כל פעולה שתבצע. @@ -38,21 +99,21 @@ מפורשות על ידי כניסה לעמוד ×”×פשריות המנוי שלך ולחיצה על הלחצן יצי××”.

        - - - - - - + + + +
        - Password Reminder -
        If you don't remember your password, enter your email address + + + + + + - - - - -
        +Password Reminder +
        If you don't remember your password, enter your email address above and click the Remind button and your password will be emailed to you.
        - +
        +

        diff --git a/templates/he/roster.html b/templates/he/roster.html index fc57fd84..13669968 100644 --- a/templates/he/roster.html +++ b/templates/he/roster.html @@ -1,52 +1,111 @@ - - - מנויי <MM-List-Name> - - - - -

        - - - - - - - - - - - - - - - -
        - מנויי - -
        - -

        -

        - -

        לחץ על הכתובת שלך כדי לבקר בעמוד ×פשריות המנוי - שלך.
        (רשומות ×‘×¡×•×’×¨×™×™× ×ž×¦×™×™× ×™× ×ž× ×•×™×™× - ×©×œ× ×ž×§×‘×œ×™× ×“×•×ר (×œ× ×¤×¢×™×œ×™×)).

        -
        -
        - - ×ž× ×•×™× ×œ× ×‘×ª×§×¦×™×¨×™× ×©×œ : -
        -
        -
        - ×ž× ×•×™× ×‘×ª×§×¦×™×¨×™× ×©×œ : -
        -
        -

        -

        -

        -

        - - - + + +מנויי <mm-list-name></mm-list-name> + + +

        + + + + + + + + + + + + + + + +
        +מנויי + +
        +

        +

        +

        לחץ על הכתובת שלך כדי לבקר בעמוד ×פשריות המנוי + שלך.
        (רשומות ×‘×¡×•×’×¨×™×™× ×ž×¦×™×™× ×™× ×ž× ×•×™×™× + ×©×œ× ×ž×§×‘×œ×™× ×“×•×ר (×œ× ×¤×¢×™×œ×™×)).

        +
        +
        + + ×ž× ×•×™× ×œ× ×‘×ª×§×¦×™×¨×™× ×©×œ : +
        +
        +
        + ×ž× ×•×™× ×‘×ª×§×¦×™×¨×™× ×©×œ : +
        +
        +

        +

        +

        +

        + +

        + diff --git a/templates/he/subscribe.html b/templates/he/subscribe.html index 43f4217f..4680fcb9 100644 --- a/templates/he/subscribe.html +++ b/templates/he/subscribe.html @@ -1,9 +1,72 @@ -<MM-List-Name> Subscription results +<mm-list-name> Subscription results</mm-list-name> -

        תוצ×ות הרשמה

        - - - +

        תוצ×ות הרשמה

        + + + diff --git a/templates/hr/admindbdetails.html b/templates/hr/admindbdetails.html index b6df0569..f47e7f7b 100644 --- a/templates/hr/admindbdetails.html +++ b/templates/hr/admindbdetails.html @@ -1,58 +1,119 @@ -Administrativni zahtjevi su prikazani na jedan od dva naèina, na stranici sa¾etak i na stranici detalji. - Stranica sa¾etak sadr¾i zahtjeve za pretplatom i odjavom koji su na -èekanju, kao i poruke koje èekaju vaše odobrenje, grupirane po e-mail adresi -pošiljatelja. Stranica detalji sadr¾i detaljan prikaz svake zadr¾ane poruke, -ukljuèujuæi sva zaglavlja poruke i izvadak tijela poruke. +Administrativni zahtjevi su prikazani na jedan od dva naèina, na stranici sa¾etak i na stranici detalji. + Stranica sa¾etak sadr¾i zahtjeve za pretplatom i odjavom koji su na +èekanju, kao i poruke koje èekaju vaÅ¡e odobrenje, grupirane po e-mail adresi +poÅ¡iljatelja. Stranica detalji sadr¾i detaljan prikaz svake zadr¾ane poruke, +ukljuèujuæi sva zaglavlja poruke i izvadak tijela poruke. -

        Sljedeæe akcije dostupne su na svim stranicama: +

        Sljedeæe akcije dostupne su na svim stranicama:

        • Odgodi -- Odgodite odluku za kasnije. Nikakva akcija - nije poduzeta za ovaj administrativni zahtjev na èekanju, ali što - se tièe zadr¾anih poruka, njih mo¾ete i dalje proslijediti (pogledajte ni¾e). + nije poduzeta za ovaj administrativni zahtjev na èekanju, ali Å¡to + se tièe zadr¾anih poruka, njih mo¾ete i dalje proslijediti (pogledajte ni¾e). -
        • Odobri -- Odobri poruku šaljuæi je na listu. - Kod zahtjeva za èlanstvom odobri promjenu u statusu èlanstva. +
        • Odobri -- Odobri poruku Å¡aljuæi je na listu. + Kod zahtjeva za èlanstvom odobri promjenu u statusu èlanstva. -
        • Odbaci -- Odbaci poruku šaljuæi poruku o odbacivanju - pošiljatelju i napusti izvornu poruku. Kod zahtjeva za èlanstvom - odbaci promjenu u statusu èlanstva. U bilo kojem sluèaju - trebali biste napisati razlog odbacivanja u prateæem polju za tekst. +
        • Odbaci -- Odbaci poruku Å¡aljuæi poruku o odbacivanju + poÅ¡iljatelju i napusti izvornu poruku. Kod zahtjeva za èlanstvom + odbaci promjenu u statusu èlanstva. U bilo kojem sluèaju + trebali biste napisati razlog odbacivanja u prateæem polju za tekst.
        • Napusti -- Odbaci izvornu poruku bez slanja poruke o odbacivanju. - Kod zahtjeva za èlanstvom, ovo jednostavno napušta zahtjev bez ikakve poruke - osobi koja je uputila zahtjev. To je obièno akcija koju ¾elite poduzeti za poznate spam + Kod zahtjeva za èlanstvom, ovo jednostavno napuÅ¡ta zahtjev bez ikakve poruke + osobi koja je uputila zahtjev. To je obièno akcija koju ¾elite poduzeti za poznate spam poruke. -
        - -

        Za zadr¾ane poruke, ukljuèite opciju Saèuvaj ako ¾elite + +

        Za zadr¾ane poruke, ukljuèite opciju Saèuvaj ako ¾elite pohraniti kopiju poruke za administratora. Ovo je korisno za -zloupotrebljene poruke koje ¾elite odbaciti, ali ¾elite saèuvati +zloupotrebljene poruke koje ¾elite odbaciti, ali ¾elite saèuvati podatak za kasnju provjeru. -

        Ukljuèite opciju Proslijedi i upišite adresu na koju ¾elite proslijediti poruku -ako je prosljeðujete nekome tko nije na listi. Da biste uredili zadr¾anu poruku -prije nego što je ona poslana na listu, trebali biste je proslijediti sebi (ili vlasnicima liste) -i napustiti izvornu poruku. Nakon pojavljivanja poruke u Vašem poštanskom pretincu, -napravite izmjene i ponovno pošaljite poruku na listu, ukljuèujuæi Odobreno: -zaglavlje koje sadr¾i lozinku liste. U ovom sluèaju nije preporuèljivo pisati komentar o promjeni +

        Ukljuèite opciju Proslijedi i upišite adresu na koju ¾elite proslijediti poruku +ako je prosljeðujete nekome tko nije na listi. Da biste uredili zadr¾anu poruku +prije nego što je ona poslana na listu, trebali biste je proslijediti sebi (ili vlasnicima liste) +i napustiti izvornu poruku. Nakon pojavljivanja poruke u Vašem poštanskom pretincu, +napravite izmjene i ponovno pošaljite poruku na listu, ukljuèujuæi Odobreno: +zaglavlje koje sadr¾i lozinku liste. U ovom sluèaju nije preporuèljivo pisati komentar o promjeni teksta poruke. -

        Ako je pošiljatelj èlan liste koja je moderirana, opcionalno mo¾ete obrisati njegovu -moderacijsku oznaku. Ovo je korisno kada je Vaša lista konfigurirana tako da stavlja -nove èlanove na probno korištenje, a Vi ste odluèili da se tome èlanu mo¾e vjerovati +

        Ako je pošiljatelj èlan liste koja je moderirana, opcionalno mo¾ete obrisati njegovu +moderacijsku oznaku. Ovo je korisno kada je Vaša lista konfigurirana tako da stavlja +nove èlanove na probno korištenje, a Vi ste odluèili da se tome èlanu mo¾e vjerovati kod slanja novih poruka bez odobravanja istih. -

        Ako pošiljatelj nije èlan liste, mo¾ete dodati e-mail adresu u -pošiljateljev filter. Pošiljateljevi filteri su opisani na stranici privatnost pošiljateljevi filteri i mogu biti: -auto-prihvati (Prihvaæeni), auto-zadr¾i (Zadr¾ani), -auto-odbaci (Odbaèeni), ili auto-napusti (Napušteni). Ova -opcija neæe biti omoguæena ako je adresa veæ na jednom od pošiljateljevih filtera. +

        Ako pošiljatelj nije èlan liste, mo¾ete dodati e-mail adresu u +pošiljateljev filter. Pošiljateljevi filteri su opisani na stranici privatnost pošiljateljevi filteri i mogu biti: +auto-prihvati (Prihvaæeni), auto-zadr¾i (Zadr¾ani), +auto-odbaci (Odbaèeni), ili auto-napusti (Napušteni). Ova +opcija neæe biti omoguæena ako je adresa veæ na jednom od pošiljateljevih filtera. -

        Kada ste završili, kliknite na gumb Pošalji Sve Podatke -na vrhu ili na dnu stranice. Ovo æe poslati sve odabrane akcije za sve administrativne -zahtjeve za koje ste se odluèili. +

        Kada ste završili, kliknite na gumb Pošalji Sve Podatke +na vrhu ili na dnu stranice. Ovo æe poslati sve odabrane akcije za sve administrativne +zahtjeve za koje ste se odluèili. -

        Povratak na stranicu sa¾etak. +

        Povratak na stranicu sa¾etak. +

        \ No newline at end of file diff --git a/templates/hr/admindbpreamble.html b/templates/hr/admindbpreamble.html index e3c869bd..0411e829 100644 --- a/templates/hr/admindbpreamble.html +++ b/templates/hr/admindbpreamble.html @@ -1,10 +1,74 @@ -Ova stranica sadr¾i podskup %(listname)s poruka -koje su zadr¾ane zbog vašeg odobrenja. Trenutno pokazuje +Ova stranica sadr¾i podskup %(listname)s poruka +koje su zadr¾ane zbog vaÅ¡eg odobrenja. Trenutno pokazuje %(description)s -

        Za svaki administrativni zahtjev izaberite akciju koju æete poduzeti, -a klikom na Pošalji Sve Podatke završite izmjene. Detaljnje +

        Za svaki administrativni zahtjev izaberite akciju koju æete poduzeti, +a klikom na Pošalji Sve Podatke završite izmjene. Detaljnje upute nalaze se ovdje. -

        Takoðer mo¾ete pogledati sa¾etak svih -zadr¾anih zahtjeva. +

        Takoðer mo¾ete pogledati sa¾etak svih +zadr¾anih zahtjeva. +

        \ No newline at end of file diff --git a/templates/hr/admindbsummary.html b/templates/hr/admindbsummary.html index ba7852dc..88efbbaf 100644 --- a/templates/hr/admindbsummary.html +++ b/templates/hr/admindbsummary.html @@ -1,10 +1,74 @@ -Ova stranica sadr¾i sa¾etak trenutnog skupa administrativnih zahtjeva -za koje je potrebno vaše odobrenje za %(listname)s mailing listu. -Naæi æete listu zadr¾anih zahtjeva za pretplatom i odjavom, ukoliko postoje, uz poruke -koje su zadr¾ane zbog vašeg odobrenja. +Ova stranica sadr¾i sa¾etak trenutnog skupa administrativnih zahtjeva +za koje je potrebno vaÅ¡e odobrenje za %(listname)s mailing listu. +Naæi æete listu zadr¾anih zahtjeva za pretplatom i odjavom, ukoliko postoje, uz poruke +koje su zadr¾ane zbog vaÅ¡eg odobrenja. -

        Za svaki administrativni zahtjev izaberite akciju koju æete poduzeti, -a klikom na Pošalji Sve Podatke završite izmjene. -Detaljnje upute su takoðer moguæe. +

        Za svaki administrativni zahtjev izaberite akciju koju æete poduzeti, +a klikom na Pošalji Sve Podatke završite izmjene. +Detaljnje upute su takoðer moguæe. -

        Takoðer mo¾ete pogledati detalje svih zadr¾anih poruka. +

        Takoðer mo¾ete pogledati detalje svih zadr¾anih poruka. +

        \ No newline at end of file diff --git a/templates/hr/admlogin.html b/templates/hr/admlogin.html index ea814a5c..ad61f4c5 100755 --- a/templates/hr/admlogin.html +++ b/templates/hr/admlogin.html @@ -1,38 +1,99 @@ - %(listname)s %(who)s Autentikacija - - - -
        +%(listname)s %(who)s Autentikacija + + + + %(message)s - - - - - - - - - - - -
        - %(listname)s - Autentikacija %(who)sa - -
        %(who)sska lozinka liste:
        -
        -

        Va¾no: Odavde, morate imati - omoguæene kolaèiæe (cookies) u Vašem browseru, jer u suprotnom - administrativne promjene neæe biti moguæe. + + + + + + + + + + + +
        +%(listname)s - Autentikacija %(who)sa + +
        %(who)sska lozinka liste:
        +
        +

        Va¾no: Odavde, morate imati + omoguæene kolaèiæe (cookies) u Vašem browseru, jer u suprotnom + administrativne promjene neæe biti moguæe. -

        Mailmanov administracijski interface koristi sesijske kolaèiæe (session cookies) +

        Mailmanov administracijski interface koristi sesijske kolaèiæe (session cookies) tako da se ne trebate ponovno autenticirati kod svake administrativne operacije. - Ovaj kolaèiæ (cookie) æe automatski isteæi kada izaðete iz browsera, ali ga mo¾ete i - eksplicitno uništiti klikanjem na link Izlaz pod Druge - Administracijske Aktivnosti (koji æete vidjeti onda kada se uspješno prijavite). -

        + Ovaj kolaèiæ (cookie) æe automatski isteæi kada izaðete iz browsera, ali ga mo¾ete i + eksplicitno uništiti klikanjem na link Izlaz pod Druge + Administracijske Aktivnosti (koji æete vidjeti onda kada se uspješno prijavite). +

        diff --git a/templates/hr/archidxentry.html b/templates/hr/archidxentry.html index f9bb57aa..5c620b20 100644 --- a/templates/hr/archidxentry.html +++ b/templates/hr/archidxentry.html @@ -1,4 +1,68 @@ -
      • %(subject)s -  -%(author)s - +
      • %(subject)s +  +%(author)s + +
      • \ No newline at end of file diff --git a/templates/hr/archidxfoot.html b/templates/hr/archidxfoot.html index 35d87343..eb9ee438 100644 --- a/templates/hr/archidxfoot.html +++ b/templates/hr/archidxfoot.html @@ -1,21 +1,85 @@ - -

        - Datum zadnje poruke: - %(lastdate)s
        - Arhivirano: %(archivedate)s -

        -

          -
        • Poruke sortirane po: + +

          +Datum zadnje poruke: +%(lastdate)s
          +Arhivirano: %(archivedate)s +

          +

          -

          -


          - Ova arhiva je generirana sa +
        +

        +


        +Ova arhiva je generirana sa Pipermail %(version)s. - - + + +

        \ No newline at end of file diff --git a/templates/hr/archidxhead.html b/templates/hr/archidxhead.html index 07680800..992dec60 100644 --- a/templates/hr/archidxhead.html +++ b/templates/hr/archidxhead.html @@ -1,24 +1,89 @@ - - - The %(listname)s %(archive)s Arhiva od %(archtype)s - + + + +The %(listname)s %(archive)s Arhiva od %(archtype)s + %(encoding)s - - - -

        %(archive)s Arhiva od %(archtype)s

        -
          -
        • Poruke sortirane po: + + + +

          %(archive)s Arhiva od %(archtype)s

          + -

          Poèetak: %(firstdate)s
          - Kraj: %(lastdate)s
          - Poruke: %(size)s

          -

            +
          +

          Poèetak: %(firstdate)s
          +Kraj: %(lastdate)s
          +Poruke: %(size)s

          +

            +

          \ No newline at end of file diff --git a/templates/hr/archlistend.html b/templates/hr/archlistend.html index 9bc052dd..2e1191b0 100644 --- a/templates/hr/archlistend.html +++ b/templates/hr/archlistend.html @@ -1 +1,64 @@ -
        + diff --git a/templates/hr/archliststart.html b/templates/hr/archliststart.html index 745f52f0..ee1f933a 100644 --- a/templates/hr/archliststart.html +++ b/templates/hr/archliststart.html @@ -1,4 +1,68 @@ - - - - +
        ArhivaGledaj po:Verzija za download
        + + + +
        ArhivaGledaj po:Verzija za download
        \ No newline at end of file diff --git a/templates/hr/archtoc.html b/templates/hr/archtoc.html index 71c35f56..65c11371 100644 --- a/templates/hr/archtoc.html +++ b/templates/hr/archtoc.html @@ -1,20 +1,84 @@ - - - %(listname)s Arhiva - + + + +%(listname)s Arhiva + %(meta)s - - -

        %(listname)s Arhiva

        -

        - Mo¾ete dobiti više informacija o ovoj listi - ili mo¾ete downloadati cijelu arhivu + + +

        %(listname)s Arhiva

        +

        + Mo¾ete dobiti više informacija o ovoj listi + ili mo¾ete downloadati cijelu arhivu (%(size)s).

        %(noarchive_msg)s %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/hr/archtocentry.html b/templates/hr/archtocentry.html index e9ee532f..287967b5 100644 --- a/templates/hr/archtocentry.html +++ b/templates/hr/archtocentry.html @@ -1,12 +1,74 @@ - -
        %(archivelabel)s: - [ Tema ] - [ Naslov ] - [ Autor ] - [ Datum ] -
        %(archivelabel)s: +[ Tema ] +[ Naslov ] +[ Autor ] +[ Datum ] +
        - - - - - - - - - - - - - - - - - - + + + + + +

        - -- - -
        -

          -

        - Detalji - - - -
        -

        -

        Da biste vidjeli kolekciju prijašnjih poruka koje su stigle na listu, - posjetite - Arhivu. - -

        -
        - Korištenje -
        - Da biste poslali poruku svim èlanovima liste, pošaljite e-mail na - . + + + +<mm-list-name> Info Page</mm-list-name> + + +

        + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + + + + + + + +
        + -- + +
        +

          +

        +Detalji + + + +
        +

        +

        Da biste vidjeli kolekciju prijašnjih poruka koje su stigle na listu, + posjetite + Arhivu. + +

        +
        +Korištenje +
        + Da biste poslali poruku svim èlanovima liste, pošaljite e-mail na + . -

        U donjem dijelu se mo¾ete pretplatiti na listu ili promijeniti - svoju postojeæu pretplatu. -

        - Pretplaæujem se na -
        -

        - Pretplatite se na ispunjavanjem sljedeæe +

        U donjem dijelu se mo¾ete pretplatiti na listu ili promijeniti + svoju postojeæu pretplatu. +

        +Pretplaæujem se na +
        +

        + Pretplatite se na ispunjavanjem sljedeæe forme. - -

          - - - - - - - - - - - - - - - - - -
          Vaša email adresa: -  
          Vaše ime (opcionalno): 
          Dolje mo¾ete unjeti privatnu - lozinku. Ona pru¾a samo osrednju zaštitu, ali bi trebala onemoguæiti - drugima da petljaju po vašoj pretplati. Ne koristite neku "va¾nu" lozinku jer - æe Vam ona povremeno biti poslana e-mailom kao èisti tekst. + +
            + + + + + + + + + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - - - -
            Vaša email adresa: + 
            Vaše ime (opcionalno): 
            Dolje mo¾ete unjeti privatnu + lozinku. Ona pru¾a samo osrednju zaštitu, ali bi trebala onemoguæiti + drugima da petljaju po vašoj pretplati. Ne koristite neku "va¾nu" lozinku jer + æe Vam ona povremeno biti poslana e-mailom kao èisti tekst. -

            Ako se odluèite ne unositi lozinku, ona æe automatski - biti za vas generirana i bit æe vam poslana onda kada - potvrdite svoju pretplatu. Uvijek mo¾ete zatra¾iti da vam se ista - ponovo pošalje kada ureðujete svoje osobne postavke. - -
            -
            Izaberite lozinku: 
            Upišite ponovno lozinku radi potvrde: 
            Na kojem jeziku ¾elite da vam se prikazuju poruke?  
            ®elite li svakodnevno primati kratak sa¾etak poruka s liste na mail? +

            Ako se odluèite ne unositi lozinku, ona æe automatski + biti za vas generirana i bit æe vam poslana onda kada + potvrdite svoju pretplatu. Uvijek mo¾ete zatra¾iti da vam se ista + ponovo pošalje kada ureðujete svoje osobne postavke. + + +
            Izaberite lozinku: 
            Upišite ponovno lozinku radi potvrde: 
            Na kojem jeziku ¾elite da vam se prikazuju poruke?  
            ®elite li svakodnevno primati kratak sa¾etak poruka s liste na mail? Ne - Da -
            -
            -
            - -
          -
          - - Pretplatnici -
          - - - -

          - - - -

          - - - +
        Ne + Da +
        +
        +
        + + +

        + + Pretplatnici +
        + + + +

        + + + +

        + +

        + diff --git a/templates/hr/options.html b/templates/hr/options.html index 9e0a2981..ae12fa5c 100644 --- a/templates/hr/options.html +++ b/templates/hr/options.html @@ -1,309 +1,342 @@ - - <MM-Presentable-User> konfiguracija èlanstva za <MM-List-Name> - - - - - -
        - - mailing lista konfiguracija èlanstva za - -
        + +<mm-presentable-user> konfiguracija èlanstva za <mm-list-name> +</mm-list-name></mm-presentable-user> + + + + +
        + + mailing lista konfiguracija èlanstva za + +

        - - - - - +
        - pretplatnièki status, - lozinka, i opcije za mailing listu. -
        - - - - -

        -

        + + + +
        + pretplatnièki status, + lozinka, i opcije za mailing listu. +
        + + +

        +

        - - +

        - - - - + +
        - - Mjenjanje Vaših èlanskih informacija -
        Adresu s kojom ste pretplaæeni na mailing listu mo¾ete promijeniti - unošenjem nove adrese u donja polja. Primijetite da æe potvrdni mail biti - poslan na novu adresu i da promjena mora biti potvrðena prije nego bude - obraðena. -

        Vrijeme potvrde isteæi æe za otprilike . + + + + - - - - - -
        + +Mjenjanje Vaših èlanskih informacija +
        Adresu s kojom ste pretplaæeni na mailing listu mo¾ete promijeniti + unošenjem nove adrese u donja polja. Primijetite da æe potvrdni mail biti + poslan na novu adresu i da promjena mora biti potvrðena prije nego bude + obraðena. +

        Vrijeme potvrde isteæi æe za otprilike . -

        Takoðer mo¾ete opcionalno postaviti ili promijeniti vaše stvarno ime - (npr. Pero Periæ). -

        Ako ¾elite napraviti èlanske promjene za sve liste na koje ste pretplaæeni na - , ukljuèite +

        Takoðer mo¾ete opcionalno postaviti ili promijeniti vaše stvarno ime + (npr. Pero Periæ). +

        Ako ¾elite napraviti èlanske promjene za sve liste na koje ste pretplaæeni na + , ukljuèite Promjeni svuda polje. -

        - - - - - - - -
        Nova adresa:
        Unesite ponovno - radi potvrde:
        -
        - - +
        Vaše ime +

        + + + + + + + +
        Nova adresa:
        Unesite ponovno + radi potvrde:
        +
        + + - - -
        Vaše ime (opcionalno):
        -
        -

        Promjeni svuda

        - +

        + +

        +

        Promjeni svuda

        +

        - - - - - -
        - Odjava sa - Vaše ostale pretplate -
        - Ukljuèite potvrdno polje i kliknite na ovaj gumb da biste se + + + + - + +
        +

        +Odjava sa +Vaše ostale pretplate +
        + Ukljuèite potvrdno polje i kliknite na ovaj gumb da biste se odjavili sa ove mailing liste. Upozorenje: - Ova akcija æe biti poduzeta odmah! + Ova akcija æe biti poduzeta odmah!

        -

        - Mo¾ete vidjeti popis svih drugih maling lista èiji ste èlan na - . Koristite ovo ako ¾elite napraviti iste izmjene èlanskih postavki +

        + Mo¾ete vidjeti popis svih drugih maling lista èiji ste èlan na + . Koristite ovo ako ¾elite napraviti iste izmjene èlanskih postavki kod drugih pretplata.

        -

        -
        - - - - - - -
        - Vaša Lozinka -
        - -
        -

        Zaboravili ste Lozinku?

        -
        - Kliknite na ovaj gumb da biste primili lozinku na svoju èlansku adresu. -

        -

        - -
        -
        - -
        -

        Promjenite Lozinku

        - - - - - - - - -
        Nova - lozinka:
        Unesite ponovno - da bi potvrdili:
        - - -

        Promjeni svuda. -
        -
        - + + + +
        +Vaša Lozinka +
        + +
        +

        Zaboravili ste Lozinku?

        +
        + Kliknite na ovaj gumb da biste primili lozinku na svoju èlansku adresu. +

        +

        + +
        +

        + +
        +

        Promjenite Lozinku

        + + + + + + + + +
        Nova + lozinka:
        Unesite ponovno + da bi potvrdili:
        + +

        Promjeni svuda. +
        +

        - - +
        - Vaše pretplatnièke postavke -
        +
        +Vaše pretplatnièke postavke +
        -

        Trenutne vrijednosti su odabrane. -

        Primijetite da neke od postavki imaju Postavi globalno -polje. Ukljuèivanjem ovog polja rezultirat æe promjenama na svim -mailing listama èiji ste èlan na . Kliknite gore na -Prika¾i moje druge pretplate da biste vidjeli na koje ste druge mailing liste -pretplaæeni. +polje. Ukljuèivanjem ovog polja rezultirat æe promjenama na svim +mailing listama èiji ste èlan na . Kliknite gore na +Prika¾i moje druge pretplate da biste vidjeli na koje ste druge mailing liste +pretplaæeni.

        - - - +
        - - Dostava maila

        - Postavite ovu opciju na Ukljuèeno da biste primili poruke poslane - na ovu mailing listu. Postavite istu na Iskljuèeno ako ¾elite ostati - pretplaæeni, ali ne ¾elite da vam mail bude dostavljen za neko vrijeme - (npr. idete na godišnji odmor). Ako iskljuèite dostavu maila - nemojte je zaboraviti ukljuèiti kada se vratite; ona neæe automatski - biti ponovno ukljuèena. -

        - Ukljuèeno
        - Iskljuèeno

        - Postavi globalno -

        + - - - + +

        - - - - - - - - + + + - - - - + + - - - + + - - + - - - +

        Ako lista ima ukljuèene personalizirane poruke èlanova, a vi + ste odabrali primanje kopija, tada æe svaka kopija imati dodano X-Mailman-Copy: yes zaglavlje. + +

        +
        + +Dostava maila

        + Postavite ovu opciju na Ukljuèeno da biste primili poruke poslane + na ovu mailing listu. Postavite istu na Iskljuèeno ako ¾elite ostati + pretplaæeni, ali ne ¾elite da vam mail bude dostavljen za neko vrijeme + (npr. idete na godišnji odmor). Ako iskljuèite dostavu maila + nemojte je zaboraviti ukljuèiti kada se vratite; ona neæe automatski + biti ponovno ukljuèena. +

        +Ukljuèeno
        +Iskljuèeno

        +Postavi globalno +

        - Postavi Digest Mod

        - Ako ukljuèite digest mod, dobijat æete skupljene poruke - (obièno jednom na dan, ali moguæe i više na prometnim listama), umjesto - svake posebno onda kada su poslane. Ako je digest mod ukljuèen - pa iskljuèen, mo¾da dobijete jedan posljednji mail sa skupljenim porukama. -

        - Iskljuèeno
        - Ukljuèeno -
        - Primaj MIME ili Obièan Tekstualni Digest Mail?

        - Vaš mail preglednik mo¾e, ali i ne mora podr¾avati MIME digest mail. Opæenito, - MIME digest mail je preporuèen, ali ako imate problema sa èitanjem, odaberite obièan +

        +Postavi Digest Mod

        + Ako ukljuèite digest mod, dobijat æete skupljene poruke + (obièno jednom na dan, ali moguæe i više na prometnim listama), umjesto + svake posebno onda kada su poslane. Ako je digest mod ukljuèen + pa iskljuèen, mo¾da dobijete jedan posljednji mail sa skupljenim porukama. +

        +Iskljuèeno
        +Ukljuèeno +
        +Primaj MIME ili Obièan Tekstualni Digest Mail?

        + Vaš mail preglednik mo¾e, ali i ne mora podr¾avati MIME digest mail. Opæenito, + MIME digest mail je preporuèen, ali ako imate problema sa èitanjem, odaberite obièan tekstualni digest mail. -

        - MIME
        - Obièan Tekst

        - Postavi globalno -

        +MIME
        +Obièan Tekst

        +Postavi globalno +

        - Primaj vlastite poruke sa liste?

        - Obièno æete dobijati svaku kopiju poruke koju pošaljete na listu. - Ako ne ¾elite primati ovu kopiju, postavite ovu opciju na +

        +Primaj vlastite poruke sa liste?

        + Obièno æete dobijati svaku kopiju poruke koju pošaljete na listu. + Ako ne ¾elite primati ovu kopiju, postavite ovu opciju na Ne. -

        - Ne
        - Da -
        - Primaj potvrdni mail kada pošaljem mail na listu?

        -

        - Ne
        - Da -
        - Primi podsjetnik lozinke za ovu listu?

        - Jedno mjeseèno, primit æete e-mail koji sadr¾i podsjetnik lozinke - za svaku listu na ovom racunalu na koju ste pretplaæeni. Navedeno mo¾ete - iskljuèiti za svaku listu tako da izaberete Ne za ovu opciju. - Ako iskljuèite podsjetnike lozinki za sve liste na koje ste pretplaæeni, - nikakav e-mail u obliku podsjetnika vam neæe biti poslan. -

        - Ne
        - Da

        - Postavi globalno -

        - Ne prikazuj me na pretplatnièkoj listi?

        - Kada netko gleda listu èlanova, vaša e-mail adresa je normalno prikazana. - Ako ne ¾elite da vaša e-mail adresa bude prikazana na èlanskoj listi, odaberite +

        +Ne
        +Da +
        +Primaj potvrdni mail kada pošaljem mail na listu?

        +

        +Ne
        +Da +
        +Primi podsjetnik lozinke za ovu listu?

        + Jedno mjeseèno, primit æete e-mail koji sadr¾i podsjetnik lozinke + za svaku listu na ovom racunalu na koju ste pretplaæeni. Navedeno mo¾ete + iskljuèiti za svaku listu tako da izaberete Ne za ovu opciju. + Ako iskljuèite podsjetnike lozinki za sve liste na koje ste pretplaæeni, + nikakav e-mail u obliku podsjetnika vam neæe biti poslan. +

        +Ne
        +Da

        +Postavi globalno +

        +Ne prikazuj me na pretplatnièkoj listi?

        + Kada netko gleda listu èlanova, vaša e-mail adresa je normalno prikazana. + Ako ne ¾elite da vaša e-mail adresa bude prikazana na èlanskoj listi, odaberite Da za ovu opciju. -

        - Ne
        - Da -
        - Koji jezik preferirate?

        -

        - -
        - Na koje biste se naslove kategorija ¾eljeli pretplatiti?

        - Biranjem jednog ili više naslova mo¾ete filtrirati promet +

        +Ne
        +Da +
        +Koji jezik preferirate?

        +

        + +
        +Na koje biste se naslove kategorija ¾eljeli pretplatiti?

        + Biranjem jednog ili viÅ¡e naslova mo¾ete filtrirati promet na mailing listi, tako da primate samo podskup poruka. - Ako se poruka podudara sa jednim od vaših selektiranih - naslova, onda æete dobiti poruku, a inaèe neæete. + Ako se poruka podudara sa jednim od vaÅ¡ih selektiranih + naslova, onda æete dobiti poruku, a inaèe neæete.

        Ako se poruka ne podudara sa niti jednim naslovom, pravilo dostave zavisi - o donjim postavkama. Ako ne izaberete niti jedan naslov, dobit æete sve - poruke poslane na mailing listu.

        - -
        Da li ¾elite primati poruke koje se ne podudaraju - sa niti jednim filterom naslova? -

        + o donjim postavkama. Ako ne izaberete niti jedan naslov, dobit æete sve + poruke poslane na mailing listu.

        + +
        Da li ¾elite primati poruke koje se ne podudaraju + sa niti jednim filterom naslova? +

        - Ova opcija æe imati efekta samo ako ste pretplaæeni + Ova opcija æe imati efekta samo ako ste pretplaæeni na barem jedan gornji naslov. Ona opisuje koje je aktivno dostavno pravilo za poruke koje se ne podudaraju sa niti jednim - filterom naslova. Izbor Ne ka¾e da ukoliko se - poruka ne podudara sa niti jednim filterom naslova, onda neæete - dobiti poruku, dok izbor Da ka¾e da se obavi dostava + filterom naslova. Izbor Ne ka¾e da ukoliko se + poruka ne podudara sa niti jednim filterom naslova, onda neæete + dobiti poruku, dok izbor Da ka¾e da se obavi dostava takve poruke.

        Ako gore niste odabrali niti jedan naslov, onda - æete primati svaku poruku poslanu na mailing listu. + æete primati svaku poruku poslanu na mailing listu. -

        - Ne
        - Da -
        - Izbjegavaj duplikate poruka?

        +

        +Ne
        +Da +
        +Izbjegavaj duplikate poruka?

        Kada ste eksplicitno navedeni u To: ili - Cc: zaglavljima poruke sa liste, mo¾ete odabrati da + Cc: zaglavljima poruke sa liste, mo¾ete odabrati da ne primate kopiju poruke sa mailing liste. Odaberite Da da izbjegnete primanje kopije sa mailing liste; odaberite Ne da biste primali kopije. -

        Ako lista ima ukljuèene personalizirane poruke èlanova, a vi - ste odabrali primanje kopija, tada æe svaka kopija imati dodano X-Mailman-Copy: yes zaglavlje. - -

        - Ne
        - Da

        - Postavi globalno -

        -
        -
        +Ne
        +Da

        +Postavi globalno +

        +
        +
        -

        - - - - + + +

        diff --git a/templates/hr/private.html b/templates/hr/private.html index 0c1f318b..91b3cacd 100755 --- a/templates/hr/private.html +++ b/templates/hr/private.html @@ -1,57 +1,118 @@ - %(realname)s Privatna Arhiva Autentikacija - - - -
        +%(realname)s Privatna Arhiva Autentikacija + + + + %(message)s - - - - - - - - - - - - - - - -
        - %(realname)s Privatna Arhiva Autentikacija -
        Email adresa:
        Lozinka:
        -
        -

        Va¾no: Odavde, morate imati - omoguæene kolaèiæe (cookies) u svome browseru, jer u suprotnom - administrativne promjene neæe biti moguæe. + + + + + + + + + + + + + + + +
        +%(realname)s Privatna Arhiva Autentikacija +
        Email adresa:
        Lozinka:
        +
        +

        Va¾no: Odavde, morate imati + omoguæene kolaèiæe (cookies) u svome browseru, jer u suprotnom + administrativne promjene neæe biti moguæe. -

        Mailmanovo administracijsko suèelje koristi sesijske kolaèiæe (session cookies) +

        Mailmanovo administracijsko suèelje koristi sesijske kolaèiæe (session cookies) tako da se ne trebate ponovno autenticirati kod svake administrativne operacije. - Ovaj kolaèiæ (cookie) æe automatski isteæi kada izaðete iz browsera, ali ga mo¾ete i - eksplicitno uništiti kolaèiæ klikanjem na link Izlaz pod Druge - Administracijske Aktivnosti (koji æete vidjeti onda kad se uspješno prijavite). + Ovaj kolaèiæ (cookie) æe automatski isteæi kada izaðete iz browsera, ali ga mo¾ete i + eksplicitno uniÅ¡titi kolaèiæ klikanjem na link Izlaz pod Druge + Administracijske Aktivnosti (koji æete vidjeti onda kad se uspjeÅ¡no prijavite).

        - - - - - - + + + +
        - Password Reminder -
        If you don't remember your password, enter your email address + + + + + + - - - - -
        +Password Reminder +
        If you don't remember your password, enter your email address above and click the Remind button and your password will be emailed to you.
        - +
        +

        diff --git a/templates/hr/roster.html b/templates/hr/roster.html index 1ebb3caf..36dcf3f7 100644 --- a/templates/hr/roster.html +++ b/templates/hr/roster.html @@ -1,52 +1,111 @@ - - - <MM-List-Name> Pretplatnici - - - - -

        - - - - - - - - - - - - - - - -
        - - Pretplatnici -
        - -

        -

        - -

        Kliknite na svoju adresu da biste posjetili stranicu sa pretplatnièkim postavkama.
        - (Podaci u zagradama imaju onemoguæenu dostavu.)

        -
        -
        - - Èlanovi koji ne koriste digest mail : -
        -
        -
        - Digest mail - Èlanovi : -
        -
        -

        -

        -

        -

        - - - + + +<mm-list-name> Pretplatnici</mm-list-name> + + +

        + + + + + + + + + + + + + + + +
        + + Pretplatnici +
        +

        +

        +

        Kliknite na svoju adresu da biste posjetili stranicu sa pretplatnièkim postavkama.
        +(Podaci u zagradama imaju onemoguæenu dostavu.)

        +
        +
        + + Èlanovi koji ne koriste digest mail : +
        +
        +
        + Digest mail + Èlanovi : +
        +
        +

        +

        +

        +

        + +

        + diff --git a/templates/hr/subscribe.html b/templates/hr/subscribe.html index bf0eb79c..ba1c5e7f 100644 --- a/templates/hr/subscribe.html +++ b/templates/hr/subscribe.html @@ -1,9 +1,72 @@ -<MM-List-Name> Rezultati pretplate +<mm-list-name> Rezultati pretplate</mm-list-name> -

        Rezultati pretplate

        - - - +

        Rezultati pretplate

        + + + diff --git a/templates/hu/admindbdetails.html b/templates/hu/admindbdetails.html index d45e7651..023b8571 100644 --- a/templates/hu/admindbdetails.html +++ b/templates/hu/admindbdetails.html @@ -1,68 +1,131 @@ -A beavatkozásra váró teendők listáját kétféleképpen lehet megjeleníteni, -vagy az összesítő oldalon vagy a részletes -oldalon. Az összesítő oldal a függőben lévő fel- és leiratkozásokat, -valamint az engedélyezésre váró leveleket mutatja a beküldők címei -szerint csoportosítva. A részletes oldalon az összes jóváhagyásra váró -levélről bővebb információt kapunk, megtekinthetjük azok fejléceit és -rövid kivonatát. +A beavatkozásra váró teendÅ‘k listáját kétféleképpen lehet megjeleníteni, +vagy az összesítÅ‘ oldalon vagy a részletes +oldalon. Az összesítÅ‘ oldal a függÅ‘ben lévÅ‘ fel- és leiratkozásokat, +valamint az engedélyezésre váró leveleket mutatja a beküldÅ‘k címei +szerint csoportosítva. A részletes oldalon az összes jóváhagyásra váró +levélrÅ‘l bÅ‘vebb információt kapunk, megtekinthetjük azok fejléceit és +rövid kivonatát. -

        Mindegyik oldalon a következő lehetőségek közül választhatunk: +

        Mindegyik oldalon a következő lehetőségek közül választhatunk:

          -
        • Elhalaszt -- Várakoztatás a későbbi döntésig. A beavatkozásra váró - üzenet továbbra is függőben marad, továbbítani vagy megőrizni lehet a - levelet (ld. később), annak törlése nélkül. +
        • Elhalaszt -- Várakoztatás a késÅ‘bbi döntésig. A beavatkozásra váró + üzenet továbbra is függÅ‘ben marad, továbbítani vagy megÅ‘rizni lehet a + levelet (ld. késÅ‘bb), annak törlése nélkül. -
        • Jóváhagy -- A listára küldött levél megjelenését engedélyezed. - Listatagsághoz kapcsolódó kérelemnél engedélyezed a listatagság - megváltoztatását. +
        • Jóváhagy -- A listára küldött levél megjelenését engedélyezed. + Listatagsághoz kapcsolódó kérelemnél engedélyezed a listatagság + megváltoztatását. -
        • Visszautasít -- Levelet visszautasítod, a beküldő külön üzenetben - értesül a levél visszautasításáról. Az eredeti levél figyelmen kívül lesz - hagyva. Listatagsági kérelemnél nem engedélyezed a listatagság - megváltoztatását. - Mindkét esetben célszerű a visszautasítás indokát a megfelelő szövegmezőben +
        • Visszautasít -- Levelet visszautasítod, a beküldÅ‘ külön üzenetben + értesül a levél visszautasításáról. Az eredeti levél figyelmen kívül lesz + hagyva. Listatagsági kérelemnél nem engedélyezed a listatagság + megváltoztatását. + Mindkét esetben célszerű a visszautasítás indokát a megfelelÅ‘ szövegmezÅ‘ben megadni. -
        • Elvet -- Az eredeti levél figyelmen kívül lesz hagyva, de erről a - beküldője értesítést nem kap. Listatagsági kérelemnél a kérelmező - értesítése nélkül kerül visszautasításra a kérelem. Ez az opció főleg - spam-ek esetén használatos. -
        +
      • Elvet -- Az eredeti levél figyelmen kívül lesz hagyva, de errÅ‘l a + beküldÅ‘je értesítést nem kap. Listatagsági kérelemnél a kérelmezÅ‘ + értesítése nélkül kerül visszautasításra a kérelem. Ez az opció fÅ‘leg + spam-ek esetén használatos. +
      • +

        A Megőriz opcióval a levél egy másolatát lehet a rendszer +adminisztrátorának elküldeni. Sértegető levelek esetén előnyös a használata, +amikor a levelet visszautasítod, de később jól jöhet a másolata. -

        A Megőriz opcióval a levél egy másolatát lehet a rendszer -adminisztrátorának elküldeni. Sértegető levelek esetén előnyös a használata, -amikor a levelet visszautasítod, de később jól jöhet a másolata. +

        A Továbbít ide opcióval a megadott címre (pl. aki nincs a listán) +lehet a levelet továbbküldeni. Ha a listán való megjelenés előtt módosítani +szeretnénk a jóváhagyásra váró levélen, akkor küldjük tovább magunknak (vagy +lista tulajdonosának), az eredeti levélre az Elvet parancsot +használjuk. Ezek után módosítsuk igényünk szerint a levelet, majd küldjük +el a listára úgy hogy a levélben egy Approved: fejlécben megadjuk +a lista jelszavát (ezzel azonnal engedélyezzük a levél megjelenését a +listán). A netetikett szerint ekkor tüntessük fel a levélben, hogy +az eredeti levelet módosítottuk. -

        A Továbbít ide opcióval a megadott címre (pl. aki nincs a listán) -lehet a levelet továbbküldeni. Ha a listán való megjelenés előtt módosítani -szeretnénk a jóváhagyásra váró levélen, akkor küldjük tovább magunknak (vagy -lista tulajdonosának), az eredeti levélre az Elvet parancsot -használjuk. Ezek után módosítsuk igényünk szerint a levelet, majd küldjük -el a listára úgy hogy a levélben egy Approved: fejlécben megadjuk -a lista jelszavát (ezzel azonnal engedélyezzük a levél megjelenését a -listán). A netetikett szerint ekkor tüntessük fel a levélben, hogy -az eredeti levelet módosítottuk. +

        Ha a beküldő olyan listatag, akinek a moderált jelzője be van állítva, +akkor az oldalon a tag moderálási jelzőjét is lehet törölni. Ez akkor +hasznos, amikor a lista az új tagoknak próbaidőre automatikusan +beállítja a moderált jelzőt és úgy döntünk, hogy a tagnál már törölni +lehet ezt a beállítást. -

        Ha a beküldő olyan listatag, akinek a moderált jelzője be van állítva, -akkor az oldalon a tag moderálási jelzőjét is lehet törölni. Ez akkor -hasznos, amikor a lista az új tagoknak próbaidőre automatikusan -beállítja a moderált jelzőt és úgy döntünk, hogy a tagnál már törölni -lehet ezt a beállítást. - -

        Ha beküldő nem listatag, akkor a címét felvehetjük a feladók -szűrése beállításokhoz. Erről a korlátozásról a -feladók szűrése oldalon lehet olvasni. A szűrésnél az -automatikusan-engedélyezett (Engedélyezett), automatikusan-függő -(Függő), automatikusan-visszautasított (Visszautasított), vagy -automatikusan-elvetett (Elvetett) opciók közül lehet választani. -A kapcsoló csak akkor jelenik meg, ha a beküldő címe egyik szűrőnél sincs +

        Ha beküldő nem listatag, akkor a címét felvehetjük a feladók +szűrése beállításokhoz. Erről a korlátozásról a +feladók szűrése oldalon lehet olvasni. A szűrésnél az +automatikusan-engedélyezett (Engedélyezett), automatikusan-függő +(Függő), automatikusan-visszautasított (Visszautasított), vagy +automatikusan-elvetett (Elvetett) opciók közül lehet választani. +A kapcsoló csak akkor jelenik meg, ha a beküldő címe egyik szűrőnél sincs megadva. -

        A műveletek befejezéséhez kattintsunk a lap alján vagy tetején található -Összes változtatás mentése gombra. Ekkor az összes beállítás -végrehajtódik. Ha egy vagy több kérelemről nem akarsz most dönteni, akkor -ne adj meg parancsot, ekkor ezek a kérelmek a megerősítésére várók sorában +

        A műveletek befejezéséhez kattintsunk a lap alján vagy tetején található +Összes változtatás mentése gombra. Ekkor az összes beállítás +végrehajtódik. Ha egy vagy több kérelemről nem akarsz most dönteni, akkor +ne adj meg parancsot, ekkor ezek a kérelmek a megerősítésére várók sorában maradnak. -

        Vissza az összesítő oldalra. +

        Vissza az összesítő oldalra. +

        \ No newline at end of file diff --git a/templates/hu/admindbpreamble.html b/templates/hu/admindbpreamble.html index d895dbde..2148e280 100644 --- a/templates/hu/admindbpreamble.html +++ b/templates/hu/admindbpreamble.html @@ -1,11 +1,75 @@ -Az oldalon a(z) %(listname)s levelezőlistához tartozó -beavatkozásra váró teendők listája található. Jelenleg -%(description)s látható. +Az oldalon a(z) %(listname)s levelezÅ‘listához tartozó +beavatkozásra váró teendÅ‘k listája található. Jelenleg +%(description)s látható. -

        Mindenegyes kérelemhez válasszuk ki a megfelelő parancsot, -majd ezek után kattintsunk az Összes változtatás mentése -gombra. Bővebb információt ezen az -oldalon lehet találni. +

        Mindenegyes kérelemhez válasszuk ki a megfelelő parancsot, +majd ezek után kattintsunk az Összes változtatás mentése +gombra. Bővebb információt ezen az +oldalon lehet találni. -

        Az összes függőben lévő teendő kivonatolva ezen -az oldalon tekinthető meg. +

        Az összes függőben lévő teendő kivonatolva ezen +az oldalon tekinthető meg. +

        \ No newline at end of file diff --git a/templates/hu/admindbsummary.html b/templates/hu/admindbsummary.html index 53b6d89c..7aecc9ef 100644 --- a/templates/hu/admindbsummary.html +++ b/templates/hu/admindbsummary.html @@ -1,13 +1,77 @@ -Ezen az oldalon a(z) %(listname)s -levelezőlistához tartozó beavatkozásra váró teendők kivonatolt -listája található. -Elől a jóváhagyásra váró fel- és leiratkozások, ha vannak, -majd az engedélyezésre váró üzenetek találhatóak. +Ezen az oldalon a(z) %(listname)s +levelezÅ‘listához tartozó beavatkozásra váró teendÅ‘k kivonatolt +listája található. +ElÅ‘l a jóváhagyásra váró fel- és leiratkozások, ha vannak, +majd az engedélyezésre váró üzenetek találhatóak. -

        Mindenegyes kérelemhez válasszuk ki a megfelelő parancsot, -majd ezek után kattintsunk az Összes változtatás mentése -gombra. Bővebb információt ezen az -oldalon lehet találni. +

        Mindenegyes kérelemhez válasszuk ki a megfelelő parancsot, +majd ezek után kattintsunk az Összes változtatás mentése +gombra. Bővebb információt ezen az +oldalon lehet találni. -

        Az összes függőben lévő teendő részletesen ezen -az oldalon érhető el. +

        Az összes függőben lévő teendő részletesen ezen +az oldalon érhető el. +

        \ No newline at end of file diff --git a/templates/hu/admlogin.html b/templates/hu/admlogin.html index 196223d5..650646ba 100755 --- a/templates/hu/admlogin.html +++ b/templates/hu/admlogin.html @@ -1,33 +1,95 @@ - %(listname)s %(who)s Azonosítás +%(listname)s %(who)s Azonosítás - - -
        + + + %(message)s - - - - - - - - - - - -
        - %(listname)s %(who)s Azonosítás -
        Lista %(who)s Jelszava:
        -
        -

        Fontos: Ettől az oldaltól engedélyezned kell a sütik - fogadását a böngésződben, különben semmilyen adminisztrátori változtatás nem fog végrehajtódni. + + + + + + + + + + + +
        +%(listname)s %(who)s Azonosítás +
        Lista %(who)s Jelszava:
        +
        +

        Fontos: Ettől az oldaltól engedélyezned kell a sütik + fogadását a böngésződben, különben semmilyen adminisztrátori változtatás nem fog végrehajtódni. -

        A Mailman az adminisztrációs műveleteknél a folyamatos azonosításhoz, hogy ne kelljen minden adminisztrációs változtatáshoz újra azonosítanod magadat, sütiket használ. - A süti a böngésző bezárásával, illetve az Egyéb Adminisztrációs Teendők pontban a - Kilépés-re (ez csak sikeres bejelentkezés után látható) kattintva érvényét veszti. -

        +

        A Mailman az adminisztrációs műveleteknél a folyamatos azonosításhoz, hogy ne kelljen minden adminisztrációs változtatáshoz újra azonosítanod magadat, sütiket használ. + A süti a böngésző bezárásával, illetve az Egyéb Adminisztrációs Teendők pontban a + Kilépés-re (ez csak sikeres bejelentkezés után látható) kattintva érvényét veszti. +

        diff --git a/templates/hu/archidxentry.html b/templates/hu/archidxentry.html index f9bb57aa..5c620b20 100644 --- a/templates/hu/archidxentry.html +++ b/templates/hu/archidxentry.html @@ -1,4 +1,68 @@ -
      • %(subject)s -  -%(author)s - +
      • %(subject)s +  +%(author)s + +
      • \ No newline at end of file diff --git a/templates/hu/archidxfoot.html b/templates/hu/archidxfoot.html index 5cdd199f..6f9aad8c 100644 --- a/templates/hu/archidxfoot.html +++ b/templates/hu/archidxfoot.html @@ -1,19 +1,83 @@ - -

        - Utolsó levél időpontja: - %(lastdate)s
        - Archíválás időpontja: %(archivedate)s -

        -

          -
        • Levelek sorrendje: + +

          +Utolsó levél időpontja: +%(lastdate)s
          +Archíválás időpontja: %(archivedate)s +

          +

          -

          -


          - Az archívum a Pipermail %(version)s verzójával készült. - - +
        • További információk errÅ‘l a listáról...
        • +
        +

        +


        +Az archívum a Pipermail %(version)s verzójával készült. + + +

        \ No newline at end of file diff --git a/templates/hu/archidxhead.html b/templates/hu/archidxhead.html index 725afb7a..ebadad32 100644 --- a/templates/hu/archidxhead.html +++ b/templates/hu/archidxhead.html @@ -1,23 +1,88 @@ - - - A(z) %(listname)s %(archive)si Archívuma %(archtype)s szerint rendezve - + + + +A(z) %(listname)s %(archive)si Archívuma %(archtype)s szerint rendezve + %(encoding)s - - - -

        %(archive)si Archívum %(archtype)s szerint rendezve

        -
          -
        • Levelek sorrendje: + + + +

          %(archive)si Archívum %(archtype)s szerint rendezve

          + -

          Kezdő időpont: %(firstdate)s
          - Utolsó időpont: %(lastdate)s
          - Üzenetek: %(size)s

          -

          +

          Kezdő időpont: %(firstdate)s
          +Utolsó időpont: %(lastdate)s
          +Üzenetek: %(size)s

          +

            +

          \ No newline at end of file diff --git a/templates/hu/archlistend.html b/templates/hu/archlistend.html index 9bc052dd..2e1191b0 100644 --- a/templates/hu/archlistend.html +++ b/templates/hu/archlistend.html @@ -1 +1,64 @@ -
        + diff --git a/templates/hu/archliststart.html b/templates/hu/archliststart.html index 4980afa9..30a0ef81 100644 --- a/templates/hu/archliststart.html +++ b/templates/hu/archliststart.html @@ -1,4 +1,68 @@ - - - - +
        ArchívumSorrend:Letölthető verzió
        + + + +
        ArchívumSorrend:Letölthető verzió
        \ No newline at end of file diff --git a/templates/hu/archtoc.html b/templates/hu/archtoc.html index 21f9a967..9035a807 100644 --- a/templates/hu/archtoc.html +++ b/templates/hu/archtoc.html @@ -1,20 +1,84 @@ - - - A(z) %(listname)s Archívum - + + + +A(z) %(listname)s Archívum + %(meta)s - - -

        A(z) %(listname)s Archívum

        -

        - További információkat a listáról itt olvashatsz - vagy letöltheted a teljes nyers archívumát + + +

        A(z) %(listname)s Archívum

        +

        + További információkat a listáról itt olvashatsz + vagy letöltheted a teljes nyers archívumát (%(size)s).

        %(noarchive_msg)s %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/hu/archtocentry.html b/templates/hu/archtocentry.html index 334c06eb..1303aa97 100644 --- a/templates/hu/archtocentry.html +++ b/templates/hu/archtocentry.html @@ -1,12 +1,74 @@ - - - %(archivelabel)s: - - [ Téma ] - [ Tárgy ] - [ Szerző ] - [ Dátum ] - + + +%(archivelabel)s: + +[ Téma ] +[ Tárgy ] +[ SzerzÅ‘ ] +[ Dátum ] + %(textlink)s - diff --git a/templates/hu/article.html b/templates/hu/article.html index 1e5d2065..b3a985c0 100644 --- a/templates/hu/article.html +++ b/templates/hu/article.html @@ -1,12 +1,13 @@ - - - %(title)s - - - - - + +

        %(listname)s archívum

        +

        + A listára még nem küldtek levelet, ezért jelenleg üres az archívum. + Bővebb információ a listáról.

        - - + + diff --git a/templates/hu/headfoot.html b/templates/hu/headfoot.html index db0007b4..9c874d39 100644 --- a/templates/hu/headfoot.html +++ b/templates/hu/headfoot.html @@ -1,23 +1,86 @@ -Ez a szöveg olyan Python-típusú változókat mutat be, amelyekkel a lista egyes jellemzőire lehet hivatkozni. -Az alábbi változók jelentése: +Ez a szöveg olyan Python-típusú változókat mutat be, amelyekkel a lista egyes jellemzÅ‘ire lehet hivatkozni. +Az alábbi változók jelentése:
          -
        • real_name - A lista `pedikűrözött' neve; általában a - lista neve nagy kezdőbetűvel. +
        • real_name - A lista `pedikűrözött' neve; általában a + lista neve nagy kezdÅ‘betűvel. -
        • list_name - Az a név, amellyel a listát az URL-lel - megadott cím alapján azonosítani lehet, kis- és nagybetűk számítanak. +
        • list_name - Az a név, amellyel a listát az URL-lel + megadott cím alapján azonosítani lehet, kis- és nagybetűk számítanak. -
        • host_name - Tartománynévvel kiegészített teljes neve a gépnek, - amelyen a levelezőlisták működnek. +
        • host_name - Tartománynévvel kiegészített teljes neve a gépnek, + amelyen a levelezÅ‘listák működnek. -
        • web_page_url - Mailman kiindulási URL-je. Ezután lehet megadni - pl. listinfo/%(list_name)s részt, hogy a kívánt levelezőlista - információs lapja megjelenjen. +
        • web_page_url - Mailman kiindulási URL-je. Ezután lehet megadni + pl. listinfo/%(list_name)s részt, hogy a kívánt levelezÅ‘lista + információs lapja megjelenjen. -
        • description - Rövid leírás a levelezőlistáról. +
        • description - Rövid leírás a levelezÅ‘listáról. -
        • info - A levelezőlista részletes leírása. +
        • info - A levelezÅ‘lista részletes leírása. -
        • cgiext - A CGI szkriptek kiterjesztése. -
        +
      • cgiext - A CGI szkriptek kiterjesztése. +
      diff --git a/templates/hu/illik.html b/templates/hu/illik.html index 201a29c2..7c5c1e8d 100644 --- a/templates/hu/illik.html +++ b/templates/hu/illik.html @@ -1,1385 +1,1411 @@ - +

      Magyar Netikett

      -

      -Az emlékeztetô státusa +Az emlékeztetô státusa

      -Ez a dokumentum az Internet közösség -tájékoztatására szolgál. Ez a -dokumentum nem határoz meg semmilyen szabványt. A -terjesztést nem korlátozza semmi. +Ez a dokumentum az Internet közösség +tájékoztatására szolgál. Ez a +dokumentum nem határoz meg semmilyen szabványt. A +terjesztést nem korlátozza semmi.

      -Összefoglaló +Összefoglaló

      -Ez a dokumentum a Hálózati Etikett (Netiquette) egy -minimális halmazát határozza meg. A -különbözô szervezetek szabadon -módosíthatják; saját, hasonló -szabályzatuk alapjául szolgálhat. Ezért a -dokumentum pontosan meghatározott részekre van bontva, -hogy az egyes részeket könnye(bbe)n -találhassák meg. Magánszemélyek, -akár felhasználók, akár -adminisztrátorok szintén felhasználhatják -minimális irányelvekként. Ez a feljegyzés -az IETF Felelôs Hálózati Használat -csoportjának munkája. +Ez a dokumentum a Hálózati Etikett (Netiquette) egy +minimális halmazát határozza meg. A +különbözô szervezetek szabadon +módosíthatják; saját, hasonló +szabályzatuk alapjául szolgálhat. Ezért a +dokumentum pontosan meghatározott részekre van bontva, +hogy az egyes részeket könnye(bbe)n +találhassák meg. Magánszemélyek, +akár felhasználók, akár +adminisztrátorok szintén felhasználhatják +minimális irányelvekként. Ez a feljegyzés +az IETF Felelôs Hálózati Használat +csoportjának munkája.

      -A fordításról +A fordításról

      -Ez a dokumentum az RFC 1855 fordítása. -Néhány helyen kiegészítettem az eredeti -szöveget, igyekezvén ezt annak szellemében tenni. -Mindenfajta javítást örömmel fogadok. A -címem a dokumentum végén -található. Sok mondat végére kéne -felkiáltójel, ez elmaradt. +Ez a dokumentum az RFC 1855 fordítása. +Néhány helyen kiegészítettem az eredeti +szöveget, igyekezvén ezt annak szellemében tenni. +Mindenfajta javítást örömmel fogadok. A +címem a dokumentum végén +található. Sok mondat végére kéne +felkiáltójel, ez elmaradt.

      -A szöveg nem feltétlenül tükrözi a -véleményemet, sem bármelyik -munkaadómét. Semmilyen felelôsséget nem -vállalok a dokumentum használatából vagy -nem használatából eredô -következményekért. +A szöveg nem feltétlenül tükrözi a +véleményemet, sem bármelyik +munkaadómét. Semmilyen felelôsséget nem +vállalok a dokumentum használatából vagy +nem használatából eredô +következményekért.

      -Tartalomjegyzék +Tartalomjegyzék

      -
        -
      1. Bemutatkozás -
      2. Egy-egynek kommunikáció -
      3. Egy-sokaknak kommunikáció -
      4. Információs -szolgáltatások -
      5. Válogatott bibliográfia -
      6. Biztonsági kérdések -
      7. A készítô címe -
      - +
    • Bemutatkozás +
    • Egy-egynek kommunikáció +
    • Egy-sokaknak kommunikáció +
    • Információs +szolgáltatások +
    • Válogatott bibliográfia +
    • Biztonsági kérdések +
    • A készítô címe +
    • -1.0 Bemutatkozás -

      +1.0 Bemutatkozás +

      -A múltban a népesség Internet -használó része az Internettel "nôtt" fel, -mûszaki tudással bírt, és megértette -a protokollok és a szállítás -természetét. Manapság az Internet -felhasználók közösségében -egyre többen vannak olyanok, akik újak ebben a -környezetben. Ezeknek a "Newbie"-knak ismeretlen ez a -kultúra és nincs szükségük a -protokollok és az átvitel mûszaki részleteire. -Hogy ezek az új felhasználókat gyorsan az -Internet közösség részéve -válhassanak, ez az írás a magatartási -szabályok minimális halmazát ismerteti. Ezeket -aztán a különbözô szervezetek és -magánszemélyek a saját céljaiknak -megfelelôen módosíthatják. Mindenkinek -tisztában kell lennie azzal, hogy bárki is -szolgáltassa neki az Internet elérést -- legyen az -egy ISP (Internet Szolgáltató), az egyetem, vagy a -cége -- annak a szervezetnek megvan a maga szabályzata -a levelek és az állományok -tulajdonjogáról; arról, hogy mit helyes -postázni és küldeni, és hogy -miképpen prezentáljuk magunkat. -Feltétlenül ismerd meg ezeket, -érdeklôdjél a helyi illetékes(ek)nél. +A múltban a népesség Internet +használó része az Internettel "nôtt" fel, +mûszaki tudással bírt, és megértette +a protokollok és a szállítás +természetét. Manapság az Internet +felhasználók közösségében +egyre többen vannak olyanok, akik újak ebben a +környezetben. Ezeknek a "Newbie"-knak ismeretlen ez a +kultúra és nincs szükségük a +protokollok és az átvitel mûszaki részleteire. +Hogy ezek az új felhasználókat gyorsan az +Internet közösség részéve +válhassanak, ez az írás a magatartási +szabályok minimális halmazát ismerteti. Ezeket +aztán a különbözô szervezetek és +magánszemélyek a saját céljaiknak +megfelelôen módosíthatják. Mindenkinek +tisztában kell lennie azzal, hogy bárki is +szolgáltassa neki az Internet elérést -- legyen az +egy ISP (Internet Szolgáltató), az egyetem, vagy a +cége -- annak a szervezetnek megvan a maga szabályzata +a levelek és az állományok +tulajdonjogáról; arról, hogy mit helyes +postázni és küldeni, és hogy +miképpen prezentáljuk magunkat. +Feltétlenül ismerd meg ezeket, +érdeklôdjél a helyi illetékes(ek)nél.

      -Ezt az anyagot három fô részre bontottuk: -egy-egynek kommunikáció, amibe a levelezés -és a talk (beszélgetés) foglaltatik benne; -egy-sokaknak kommunikáció, amely alatt a Usenet-et -és a levelezési listákat értjük; -és az információs szolgáltatások: -ftp, WWW, gopher, MUD, MOO, IRC. Végül egy -válogatott bibliográfiát találhatnak, -referencia célokra. +Ezt az anyagot három fô részre bontottuk: +egy-egynek kommunikáció, amibe a levelezés +és a talk (beszélgetés) foglaltatik benne; +egy-sokaknak kommunikáció, amely alatt a Usenet-et +és a levelezési listákat értjük; +és az információs szolgáltatások: +ftp, WWW, gopher, MUD, MOO, IRC. Végül egy +válogatott bibliográfiát találhatnak, +referencia célokra.

      -2.0 Egy-egynek kommunikáció -(elektronikus levél, beszélgetés (talk)) -

      +2.0 Egy-egynek kommunikáció +(elektronikus levél, beszélgetés (talk)) +

      -Az egy-egy kommunikációt úgy -definiáljuk, amiben egy ember kommunikál egy -másikkal, mintha szemtôl-szembe állnának: -egy dialógus. Általában, az emberekkel -való társalgás normál -illemszabályai az érvényesek, csak ez az -Interneten még fontosabb, mert hiányzik többek -között a metakommunikáció és a -hangszín. +Az egy-egy kommunikációt úgy +definiáljuk, amiben egy ember kommunikál egy +másikkal, mintha szemtôl-szembe állnának: +egy dialógus. Ãltalában, az emberekkel +való társalgás normál +illemszabályai az érvényesek, csak ez az +Interneten még fontosabb, mert hiányzik többek +között a metakommunikáció és a +hangszín.

      -2.1 Iránymutató felhasználóknak +2.1 Iránymutató felhasználóknak

      -2.1.1 Levelezéshez +2.1.1 Levelezéshez

        - -
      • Ha nem egy Internet Szolgáltatón kerülsz +
      • Ha nem egy Internet Szolgáltatón kerülsz kapcsolatba az Internettel, akkor fontos ismerned a -munkáltatód szabályait az elektronikus levelek -tulajdonjogáról; ezek mindenütt mások. - -
      • Fel kell tételezned hogy az Interneten -történô levelezés nem biztonságos, -kivéve ha valamilyen rejtjelezô eszközt (akár -szoftvert, akár hardvert) használsz. Ne írjál -semmi olyasmit egy elektronikus levélbe, amit nem -küldenél el levelezôlapon. - -
      • Tiszteld a szerzô jogait avval az anyaggal kapcsolatban, amit -másolsz. Majdnem minden országban vannak a -szerzôk jogait védô törvények. - -
      • Ha továbbküldesz (forward) vagy -újrapostázol egy üzenetet, akkor ne -változtasd meg annak a szóhasználatát. Ha -ez egy neked írt személyes üzenet volt, és -egy csoportnak kívánod továbbadni, akkor -kérjél elôször engedélyt a -feladótól. - -
      • Soha ne küldj "lánc" levelet elektronikusan, Ezek -tiltottak az Interneten. Ha mégis ilyet küldesz, az -valószínûleg a hálózati jogaid -csorbításával fog járni. Ha ilyesmit -kapnál akkor értesítsd a helyi -rendszeradminisztrátort. - -
      • Egy jó ökölszabály: légy -konzervatív a küldésben és légy -liberális a fogadásban. Nem szabad indulatos leveleket -küldened (ezeket "flame"-nek nevezzük) még akkor -sem, ha provokálnak. Másik oldalról viszont, ne -legyél meglepve, ha ilyet levelet kapsz, és okosan teszed, -ha nem válaszolsz ezekre. - -
      • Általában jó, ha ellenôrzöd a -leveleid subject-jét mielôtt válaszolsz egy -levélre. Van úgy, hogy az, aki korábban -segítséget (vagy felvilágosítást) -kért tôled, már írt egy levelet, aminek az a -lényege, hogy "Tárgytalan". Szintén -célszerû meggyôzôdni, hogy a levél, -amire válaszolsz, az neked volt-e címezve. Lehet hogy -csak másolatot kaptál (cc:) az eredeti helyett. - -
      • Könnyítsd meg a címzett dolgát. Sok -levelezôprogram levágja a -fejlécinformációkat amelyek a -válaszcímedet tartalmazzák. Hogy biztos -lehessél abban, hogy a többi ember tudja ki vagy, -írjál az üzeneted végére egy, esetleg -két sort arról, hogy miképpen érhetnek el. -Ezt a file-t már jóelôre elkészítheted, -és az üzeneteid végére teheted aztán. -(Néhány program ezt automatikusan megteszi.) Az -Internetes szóhasználatban ezt "sig" vagy "signature" -file-nak nevezik. A névjegykártya helyét veszi -át a sig file. (És akár -többféléd is lehet, a többféle -szituációnak megfelelôen.) - -
      • Légy óvatos a címzéssel. Vannak -címek, amelyek egy csoportot jelentek, a cím -mégis úgy néz ki, mintha egyetlen ember lenne. -Légy tisztában azzal, hogy kinek írsz. - -
      • Figyelj a cc:-kre a válaszoláskor. Amikor már -csak két ember között folyik a -kommunikáció, akkor ne küldd el ezeket a leveleket -másoknak. - -
      • Általában egy Internetet használó -embernek nincs ideje az Internet ill. annak belsô -mûködésére vonatkozó -kérdésekre válaszolni. Ne küldjél -kéretlen információkérô leveleket -olyan embereknek, akinek címét egy levelezési -listában vagy egy RFC-ben láthattad. (A -fordító megjegyzése: erre a célra is -találsz természetesen megfelelô fórumokat.) - -
      • Ne felejtsd el, hogy azok az emberek akikkel kommunikálsz, -az egész világon vannak szétszórva. Lehet, -hogy annak a levélnek, amire azonnali választ -vársz, a címzettje éppen alszik. Add meg neki a -lehetôséget, hogy felébredjen, munkába -menjen és belépjen, mielôtt úgy -döntesz, hogy a levél nem érkezett meg, vagy a -címzett nem törôdik vele. - -
      • Mielôtt hosszú, vagy személyes -eszmecserét kezdeményezel, ellenôrizd a -címet. Szintén helyes gyakorlat "Long" szót -írni a hosszú üzeneteknek a subject-jébe, -mert így a címzett tudhatja, hogy idôt kell -szánnia az elolvasására. 100 sor felett -számít hosszúnak egy üzenet. - -
      • Légy tisztában azzal, hogy kihez kell -segítségért fordulnod. Általában -nem kell messzire menned. Keressél helyben olyan embereket, -akik szoftver- és rendszerproblémák -elhárításban segédkezhetnek. -Szintén jó, ha tudod, hogy kit kell keresned, ha -valamilyen megkérdôjelezhetô vagy illegális -anyagot kapsz. A legtöbb helyen a "postmaster" címre -írhatsz segítségkérô levelet, mert ezt -a címet legtöbbször megfelelô tudással -bíró ember olvassa. - -
      • Soha ne feledd, hogy a címzett is emberi lény, -méghozzá olyan, akinek kultúrája, nyelve, -humora egészen más is lehet, mint a tiéd. -Dátumformátumok, -mértékegységek, idiómák sem -mindenütt ugyanazok. - -
      • Használj vegyesen kis- és nagybetût, -akárcsak a közönséges -íráskor. A CSUPA NAGYBETÛ OLYAN, -MINTHA ORDÍTANÁL! - -
      • Használj szimbólumokat -hangsúlyozásra. *Erre* gondoltam. Használj -aláhúzásjeleket aláhúzásra. -A kedvenc könyvem a _Háború és -béke_. - -
      • Használj "mosolygókat" (smiley) a hangszín -jelzésére, de bánj velünk -takarékosan. :-) példa ilyen "mosolygóra" -(döntsd oldalra a fejed és nézzd meg úgy.) -Azonban ne feltételezd, hogy egy "mosolygó" -szerepeltetése rögtön eléri, hogy a -címzett egyetértsen veled, vagy hogy egy -egyébként bántó megjegyzés -élét elvegye. - -
      • Aludj egyet, mielôtt érzelmektôl -fûtött választ küldenél egy -üzenetre. Ha biztosan erôs érzéseid vannak -egy tárgyról, akkor helyezd azt FLAME ON/OFF jelek -közé: - -FLAME ON: Ez a fajta vita nem méltó arra a -sávszélességre, ami a -továbbításához kell. Teljességgel -illogikus és érvekkel alá nem támasztott. -A világ többi része is egyetért velem. +munkáltatód szabályait az elektronikus levelek +tulajdonjogáról; ezek mindenütt mások. + +
      • Fel kell tételezned hogy az Interneten +történô levelezés nem biztonságos, +kivéve ha valamilyen rejtjelezô eszközt (akár +szoftvert, akár hardvert) használsz. Ne írjál +semmi olyasmit egy elektronikus levélbe, amit nem +küldenél el levelezôlapon. + +
      • Tiszteld a szerzô jogait avval az anyaggal kapcsolatban, amit +másolsz. Majdnem minden országban vannak a +szerzôk jogait védô törvények. + +
      • Ha továbbküldesz (forward) vagy +újrapostázol egy üzenetet, akkor ne +változtasd meg annak a szóhasználatát. Ha +ez egy neked írt személyes üzenet volt, és +egy csoportnak kívánod továbbadni, akkor +kérjél elôször engedélyt a +feladótól. + +
      • Soha ne küldj "lánc" levelet elektronikusan, Ezek +tiltottak az Interneten. Ha mégis ilyet küldesz, az +valószínûleg a hálózati jogaid +csorbításával fog járni. Ha ilyesmit +kapnál akkor értesítsd a helyi +rendszeradminisztrátort. + +
      • Egy jó ökölszabály: légy +konzervatív a küldésben és légy +liberális a fogadásban. Nem szabad indulatos leveleket +küldened (ezeket "flame"-nek nevezzük) még akkor +sem, ha provokálnak. Másik oldalról viszont, ne +legyél meglepve, ha ilyet levelet kapsz, és okosan teszed, +ha nem válaszolsz ezekre. + +
      • Ãltalában jó, ha ellenôrzöd a +leveleid subject-jét mielôtt válaszolsz egy +levélre. Van úgy, hogy az, aki korábban +segítséget (vagy felvilágosítást) +kért tôled, már írt egy levelet, aminek az a +lényege, hogy "Tárgytalan". Szintén +célszerû meggyôzôdni, hogy a levél, +amire válaszolsz, az neked volt-e címezve. Lehet hogy +csak másolatot kaptál (cc:) az eredeti helyett. + +
      • Könnyítsd meg a címzett dolgát. Sok +levelezôprogram levágja a +fejlécinformációkat amelyek a +válaszcímedet tartalmazzák. Hogy biztos +lehessél abban, hogy a többi ember tudja ki vagy, +írjál az üzeneted végére egy, esetleg +két sort arról, hogy miképpen érhetnek el. +Ezt a file-t már jóelôre elkészítheted, +és az üzeneteid végére teheted aztán. +(Néhány program ezt automatikusan megteszi.) Az +Internetes szóhasználatban ezt "sig" vagy "signature" +file-nak nevezik. A névjegykártya helyét veszi +át a sig file. (És akár +többféléd is lehet, a többféle +szituációnak megfelelôen.) + +
      • Légy óvatos a címzéssel. Vannak +címek, amelyek egy csoportot jelentek, a cím +mégis úgy néz ki, mintha egyetlen ember lenne. +Légy tisztában azzal, hogy kinek írsz. + +
      • Figyelj a cc:-kre a válaszoláskor. Amikor már +csak két ember között folyik a +kommunikáció, akkor ne küldd el ezeket a leveleket +másoknak. + +
      • Ãltalában egy Internetet használó +embernek nincs ideje az Internet ill. annak belsô +mûködésére vonatkozó +kérdésekre válaszolni. Ne küldjél +kéretlen információkérô leveleket +olyan embereknek, akinek címét egy levelezési +listában vagy egy RFC-ben láthattad. (A +fordító megjegyzése: erre a célra is +találsz természetesen megfelelô fórumokat.) + +
      • Ne felejtsd el, hogy azok az emberek akikkel kommunikálsz, +az egész világon vannak szétszórva. Lehet, +hogy annak a levélnek, amire azonnali választ +vársz, a címzettje éppen alszik. Add meg neki a +lehetôséget, hogy felébredjen, munkába +menjen és belépjen, mielôtt úgy +döntesz, hogy a levél nem érkezett meg, vagy a +címzett nem törôdik vele. + +
      • Mielôtt hosszú, vagy személyes +eszmecserét kezdeményezel, ellenôrizd a +címet. Szintén helyes gyakorlat "Long" szót +írni a hosszú üzeneteknek a subject-jébe, +mert így a címzett tudhatja, hogy idôt kell +szánnia az elolvasására. 100 sor felett +számít hosszúnak egy üzenet. + +
      • Légy tisztában azzal, hogy kihez kell +segítségért fordulnod. Ãltalában +nem kell messzire menned. Keressél helyben olyan embereket, +akik szoftver- és rendszerproblémák +elhárításban segédkezhetnek. +Szintén jó, ha tudod, hogy kit kell keresned, ha +valamilyen megkérdôjelezhetô vagy illegális +anyagot kapsz. A legtöbb helyen a "postmaster" címre +írhatsz segítségkérô levelet, mert ezt +a címet legtöbbször megfelelô tudással +bíró ember olvassa. + +
      • Soha ne feledd, hogy a címzett is emberi lény, +méghozzá olyan, akinek kultúrája, nyelve, +humora egészen más is lehet, mint a tiéd. +Dátumformátumok, +mértékegységek, idiómák sem +mindenütt ugyanazok. + +
      • Használj vegyesen kis- és nagybetût, +akárcsak a közönséges +íráskor. A CSUPA NAGYBETÛ OLYAN, +MINTHA ORDÃTANÃL! + +
      • Használj szimbólumokat +hangsúlyozásra. *Erre* gondoltam. Használj +aláhúzásjeleket aláhúzásra. +A kedvenc könyvem a _Háború és +béke_. + +
      • Használj "mosolygókat" (smiley) a hangszín +jelzésére, de bánj velünk +takarékosan. :-) példa ilyen "mosolygóra" +(döntsd oldalra a fejed és nézzd meg úgy.) +Azonban ne feltételezd, hogy egy "mosolygó" +szerepeltetése rögtön eléri, hogy a +címzett egyetértsen veled, vagy hogy egy +egyébként bántó megjegyzés +élét elvegye. + +
      • Aludj egyet, mielôtt érzelmektôl +fûtött választ küldenél egy +üzenetre. Ha biztosan erôs érzéseid vannak +egy tárgyról, akkor helyezd azt FLAME ON/OFF jelek +közé: + +FLAME ON: Ez a fajta vita nem méltó arra a +sávszélességre, ami a +továbbításához kell. Teljességgel +illogikus és érvekkel alá nem támasztott. +A világ többi része is egyetért velem. FLAME OFF -
      • Ne írjál vezérlô karaktereket vagy -nem-ASCII karaktereket, kivéve ha a programod elkódolja -ezeket, vagy MIME attachment formájában -küldöd. Ha elkódolva küldesz valamit, akkor -próbálj megbizonyosodni arról, hogy a -címzett vissza tudja majd kódolni azt. - -
      • Légy tömör anélkül, hogy -túlságon lényegretörô lennél. -Amikor egy levélre válaszolsz, csak annyit idézz -az eredeti anyagból, hogy érthetô legyen a -válaszod és ne többet. Rendkívül -rossz szokás az egész levelet idézni a -válaszban - töröld ki a felesleget. - -
      • 65 karakter széles, kocsivissza karakterrel lezárt -sorokat írjál. (Fordító megjegyzése: -e dokumentumtól eltérôen 72-76 karaktert is -szoktak ajánlani.) - -
      • A levél fejlécében kell, hogy legyen egy -subject sor, ami visszatükrözi a levél -tartalmát. - -
      • Ha signature-t illesztesz a leveledbe, akkor azt fogd rövidre. -Ökölszabály: 4 sornál ne legyen hosszabb. -Sok ember a kapcsolatért percenként fizet, és -minél hosszabb az üzeneted, annál többet +
      • Ne írjál vezérlô karaktereket vagy +nem-ASCII karaktereket, kivéve ha a programod elkódolja +ezeket, vagy MIME attachment formájában +küldöd. Ha elkódolva küldesz valamit, akkor +próbálj megbizonyosodni arról, hogy a +címzett vissza tudja majd kódolni azt. + +
      • Légy tömör anélkül, hogy +túlságon lényegretörô lennél. +Amikor egy levélre válaszolsz, csak annyit idézz +az eredeti anyagból, hogy érthetô legyen a +válaszod és ne többet. Rendkívül +rossz szokás az egész levelet idézni a +válaszban - töröld ki a felesleget. + +
      • 65 karakter széles, kocsivissza karakterrel lezárt +sorokat írjál. (Fordító megjegyzése: +e dokumentumtól eltérôen 72-76 karaktert is +szoktak ajánlani.) + +
      • A levél fejlécében kell, hogy legyen egy +subject sor, ami visszatükrözi a levél +tartalmát. + +
      • Ha signature-t illesztesz a leveledbe, akkor azt fogd rövidre. +Ökölszabály: 4 sornál ne legyen hosszabb. +Sok ember a kapcsolatért percenként fizet, és +minél hosszabb az üzeneted, annál többet fizetnek. -
      • Ahogy egy levél (ma) nem lehet privát, úgy -a levél (és a news) is (ma még) -hamisítható, megváltoztatható. A -változtatásokat nem mindig lehet felismerni -(könnyen), így célszerû a józan -ész alapján ellenôrizni egy üzenet -valódiságát, mielôtt elhisszük azt. - -
      • Küldj egy rövid választ a feladónak, -hogy megkaptad a levelét, ha úgy gondolod, hogy a -levél fontossága szükségessé teszi -ezt. Tedd meg ezt akkor is, ha részletesen válaszolni csak -késôbb lesz idôd. - -
      • Az, hogy egy adott beszélgetésbôl ki mennyit -ért meg, az erôsen függ az adott -szituációtól. Egy adott e-mail környezetben -megtanult normák nem feltétlenül vonatkoznak -általában az Internetet használó -emberekkel vonatkozó kommunikációra. -Légy óvatos a helyi szlenggel és a helyi -rövidítésekkel. - -
      • Egy e-mail üzenet szállítási -költsége nagyjából egyenlô a -feladónál és a címzettnél (vagy -szervezeteinél). Ez alapvetôen eltér a -hagyományos levéltôl, a telefontól, a -rádiótól, és a TV-tôl. Egy -levél elküldése konkrétan -hálózati szélességbe, diszk helybe, -és CPU idôbe is kerülhet. Ez az alapvetô -gazdasági oka, amiért nem illik kéretlen -hirdetést küldeni e-mail-ben. (Ez ráadásul +
      • Ahogy egy levél (ma) nem lehet privát, úgy +a levél (és a news) is (ma még) +hamisítható, megváltoztatható. A +változtatásokat nem mindig lehet felismerni +(könnyen), így célszerû a józan +ész alapján ellenôrizni egy üzenet +valódiságát, mielôtt elhisszük azt. + +
      • Küldj egy rövid választ a feladónak, +hogy megkaptad a levelét, ha úgy gondolod, hogy a +levél fontossága szükségessé teszi +ezt. Tedd meg ezt akkor is, ha részletesen válaszolni csak +késôbb lesz idôd. + +
      • Az, hogy egy adott beszélgetésbôl ki mennyit +ért meg, az erôsen függ az adott +szituációtól. Egy adott e-mail környezetben +megtanult normák nem feltétlenül vonatkoznak +általában az Internetet használó +emberekkel vonatkozó kommunikációra. +Légy óvatos a helyi szlenggel és a helyi +rövidítésekkel. + +
      • Egy e-mail üzenet szállítási +költsége nagyjából egyenlô a +feladónál és a címzettnél (vagy +szervezeteinél). Ez alapvetôen eltér a +hagyományos levéltôl, a telefontól, a +rádiótól, és a TV-tôl. Egy +levél elküldése konkrétan +hálózati szélességbe, diszk helybe, +és CPU idôbe is kerülhet. Ez az alapvetô +gazdasági oka, amiért nem illik kéretlen +hirdetést küldeni e-mail-ben. (Ez ráadásul tilos is sok helyen.) -
      • Ne küldj nagytömegû kéretlen -információt az embereknek. - -
      • Ha a levelezôrendszered képes automatikus -továbbküldésre, akkor vigyázz, hogy ne -lépjen fel a rettegett "továbbítóhurok". Ez -úgy jöhet létre, ha sok gépen -állítod be az automata továbbítást, -úgy, hogy egy neked küldött üzenet az egyik -géptôl a másikig utazik, majd a -következôre és így tovább -egészen addig, amig visszaérkezik az elsô -címre a levél... (Fordító -megjegyzése: ennek elkerülésére nem -mindenütt lehet automatikusan továbbküldött -levelet továbbküldeni.) - -
      - +
    • Ne küldj nagytömegû kéretlen +információt az embereknek. + +
    • Ha a levelezôrendszered képes automatikus +továbbküldésre, akkor vigyázz, hogy ne +lépjen fel a rettegett "továbbítóhurok". Ez +úgy jöhet létre, ha sok gépen +állítod be az automata továbbítást, +úgy, hogy egy neked küldött üzenet az egyik +géptôl a másikig utazik, majd a +következôre és így tovább +egészen addig, amig visszaérkezik az elsô +címre a levél... (Fordító +megjegyzése: ennek elkerülésére nem +mindenütt lehet automatikusan továbbküldött +levelet továbbküldeni.) + +

    2.1.2 A talk

    -A talk olyan protokollok gyûjteménye, amelyek -lehetôvé teszik két ember interaktív -párbeszédét -számítógépen keresztül. +A talk olyan protokollok gyûjteménye, amelyek +lehetôvé teszik két ember interaktív +párbeszédét +számítógépen keresztül.
      - -
    • Használj vegyesen kis- és nagybetût, mintha -egy levelet vagy egy e-mail-t írnál. - -
    • Ne hagyd, hogy az irományod kifusson a -képernyô széléig és ott a -terminálod törjön sort: használj egy -Kocsivissza (CR) jelet a sor lezárására. Nem -tételezheted fel azt sem, hogy a te képernyôd -mérete azonos bárki máséval. -Ökölszabály: max. 70 karaktert és max. 12 -sort írjál. - -
    • Hagyjál margókat, ne írjál a -képernyô szélére. - -
    • Ha befejezted a mondandódat, akkor Üssél -két CR-t (egy üres sort), jelezve, hogy a másik -következik. - -
    • Mindig írjál egy "viszontlátásra"-t -vagy más elköszönést és várd -meg a másik oldal köszönését is. -Különösen fontos ez, ha egy távoli valakivel -kommunikálsz. Ne felejtsd el, hogy a -kommunikációtok sebessége egyrészt a -rendelkezésre álló -sávszélességtôl (a csô -átmérôje) és a hálózat -késéseitôl (a fény sebessége) is -függ. - -
    • Tisztában kell lenned azzal, hogy a talk megzavarja a -másik embert. Csak a rendeltetésének -megfelelôen használd. Soha ne talk-olj idegenre! - -
    • Sok oka lehet annak, ha nem kapsz választ. -Elôször is, nem biztos, hogy minden mûködik. -Másodszor is, nem minden talk verzió kompatibilis. -Harmadrészt, a címzett letilthatja a talk-ot. Ezt -általában te is megteheted, szokás szerint -érdeklôdjél a helyi rendszergazdánál -a konkrét részletekrôl. - -
    • Bizonyos talk verziók újracsengetik a -címzettet ha magára hagyják. Hagyd -egy-kétszer csengetni, utána állítsd le (kill) +
    • Használj vegyesen kis- és nagybetût, mintha +egy levelet vagy egy e-mail-t írnál. + +
    • Ne hagyd, hogy az irományod kifusson a +képernyô széléig és ott a +terminálod törjön sort: használj egy +Kocsivissza (CR) jelet a sor lezárására. Nem +tételezheted fel azt sem, hogy a te képernyôd +mérete azonos bárki máséval. +Ökölszabály: max. 70 karaktert és max. 12 +sort írjál. + +
    • Hagyjál margókat, ne írjál a +képernyô szélére. + +
    • Ha befejezted a mondandódat, akkor Üssél +két CR-t (egy üres sort), jelezve, hogy a másik +következik. + +
    • Mindig írjál egy "viszontlátásra"-t +vagy más elköszönést és várd +meg a másik oldal köszönését is. +Különösen fontos ez, ha egy távoli valakivel +kommunikálsz. Ne felejtsd el, hogy a +kommunikációtok sebessége egyrészt a +rendelkezésre álló +sávszélességtôl (a csô +átmérôje) és a hálózat +késéseitôl (a fény sebessége) is +függ. + +
    • Tisztában kell lenned azzal, hogy a talk megzavarja a +másik embert. Csak a rendeltetésének +megfelelôen használd. Soha ne talk-olj idegenre! + +
    • Sok oka lehet annak, ha nem kapsz választ. +Elôször is, nem biztos, hogy minden mûködik. +Másodszor is, nem minden talk verzió kompatibilis. +Harmadrészt, a címzett letilthatja a talk-ot. Ezt +általában te is megteheted, szokás szerint +érdeklôdjél a helyi rendszergazdánál +a konkrét részletekrôl. + +
    • Bizonyos talk verziók újracsengetik a +címzettet ha magára hagyják. Hagyd +egy-kétszer csengetni, utána állítsd le (kill) a programot. -
    • Ha valaki nem válaszol, akkor -megpróbálkozhatsz egy másik tty-vel. -Használd a finger parancsot, hogy látsd, hogy melyikekre -van belépve. Ha a másik fél még mindig -nem válaszol, ne erôltesd tovább. -(Fordító megjegyzése: néhol a finger le -van tiltva, ne lepôdj meg tehát, ha nem -mûködik. Megjegyzés kettô: Ez igen Unix +
    • Ha valaki nem válaszol, akkor +megpróbálkozhatsz egy másik tty-vel. +Használd a finger parancsot, hogy látsd, hogy melyikekre +van belépve. Ha a másik fél még mindig +nem válaszol, ne erôltesd tovább. +(Fordító megjegyzése: néhol a finger le +van tiltva, ne lepôdj meg tehát, ha nem +mûködik. Megjegyzés kettô: Ez igen Unix specifikus pont, viszont a Unix a legelterjedtebb Internetes szerver -operációs rendszer.). - -
    • A beszélgetés (talk) alatt megmutatkozik a -gépelési tudásod. Ha lassan és sok -hibával gépelsz (fordító -megjegyezése: ne keseredj el) akkor legtöbbször nem -éri meg visszamenni kijavítani ôket. A -másik ember úgyis rájön, hogy mit akarsz +operációs rendszer.). + +
    • A beszélgetés (talk) alatt megmutatkozik a +gépelési tudásod. Ha lassan és sok +hibával gépelsz (fordító +megjegyezése: ne keseredj el) akkor legtöbbször nem +éri meg visszamenni kijavítani ôket. A +másik ember úgyis rájön, hogy mit akarsz mondani. -
    • Légy nagyon óvatos, ha egyszerre több talk-od -is fut! (Fordító megjegyzése: Komoly -esélyed van rá, hogy elôbb-utóbb -összekevered, -hogy melyik talknál kivel és mirôl -beszélgetsz. Kérdéseidet és -válaszaidat rossz helyre küldve komikus és/vagy -kínos helyzetet teremthetsz.) - -
    +
  • Légy nagyon óvatos, ha egyszerre több talk-od +is fut! (Fordító megjegyzése: Komoly +esélyed van rá, hogy elôbb-utóbb +összekevered, +hogy melyik talknál kivel és mirôl +beszélgetsz. Kérdéseidet és +válaszaidat rossz helyre küldve komikus és/vagy +kínos helyzetet teremthetsz.) +
  • -2.2 Iránymutató Adminisztrátoroknak +2.2 Iránymutató Adminisztrátoroknak

    -
      - -
    • Feltétlenül rögzítsd -írásban a szabályokat; -különösen az illegális, hamisított -és az elôírásokat megsértô -forgalomra vonatkozókat. - -
    • Idôben - lehetôleg a következô munkanapig -- válaszold meg a felmerülô -kérdéseket. - -
    • Az illegális és elôírásokat -sértô kérdéseket sorold elôre. A -"lánc" levelek azonnali intézkedést -kívánnak meg. - -
    • Magyarázd meg a szabályokat - mint -például a disk quota-kat - a -felhasználóidnak. Értesd meg velük a -mail-en át történô file kérés -következményeit: magas telefonszámlák, -betelô lemezek, más levelek késése stb. - -
    • Legyen egy alias a "Postmaster" és a "Root" címeken. -Feltétlenül olvassa el valaki ezeket a leveleket! -(Fordító megjegyzése: mint többször -írtuk már, ezeknek nem olvasása, hanem -megválaszolása is igen fontos!) - -
    • Nyitottan vizsgáld meg a felhasználóidat -érintô panaszokat. A címek sajnos -hamisíthatóak, a levelek megpiszkálhatóak. - -
    - +
  • Feltétlenül rögzítsd +írásban a szabályokat; +különösen az illegális, hamisított +és az elôírásokat megsértô +forgalomra vonatkozókat. + +
  • Idôben - lehetôleg a következô munkanapig +- válaszold meg a felmerülô +kérdéseket. + +
  • Az illegális és elôírásokat +sértô kérdéseket sorold elôre. A +"lánc" levelek azonnali intézkedést +kívánnak meg. + +
  • Magyarázd meg a szabályokat - mint +például a disk quota-kat - a +felhasználóidnak. Értesd meg velük a +mail-en át történô file kérés +következményeit: magas telefonszámlák, +betelô lemezek, más levelek késése stb. + +
  • Legyen egy alias a "Postmaster" és a "Root" címeken. +Feltétlenül olvassa el valaki ezeket a leveleket! +(Fordító megjegyzése: mint többször +írtuk már, ezeknek nem olvasása, hanem +megválaszolása is igen fontos!) + +
  • Nyitottan vizsgáld meg a felhasználóidat +érintô panaszokat. A címek sajnos +hamisíthatóak, a levelek megpiszkálhatóak. + +
  • -3.0 Egy-sokaknak kommunikáció -(Levelezési listák, Usenet) -

    +3.0 Egy-sokaknak kommunikáció +(Levelezési listák, Usenet) +

    -Amikor sok emberrel kommunikálsz egyszerre, akkor a mail-re -vonatkozó szabályok is érvényesek. -Végülis nincs nagy különbség a -között, hogy egy embernek írsz egy levelet vagy sok -emberrel kommunikálsz egy cikken keresztül - legfeljebb -annyi, hogy így sokkal több embert van -lehetôséged megbántani. Így aztán -különösen fontos, hogy minél többet -tudjál a közönségedrôl. +Amikor sok emberrel kommunikálsz egyszerre, akkor a mail-re +vonatkozó szabályok is érvényesek. +Végülis nincs nagy különbség a +között, hogy egy embernek írsz egy levelet vagy sok +emberrel kommunikálsz egy cikken keresztül - legfeljebb +annyi, hogy így sokkal több embert van +lehetôséged megbántani. Ãgy aztán +különösen fontos, hogy minél többet +tudjál a közönségedrôl.

    -3.1 Iránymutató Felhasználóknak +3.1 Iránymutató Felhasználóknak

    -

    -3.1.1 Általános útmutató a -levelezési listákhoz és a Usenet-hoz +3.1.1 Ãltalános útmutató a +levelezési listákhoz és a Usenet-hoz

      -
    • Legalább egy, de inkább két hónapig -olvasd az adott levelezési listát ill. hírcsoportot -mielôtt postázol valamit. Ez hozzásegít az -adott csoport kultúrjának megismeréséhez. -(Fordító megjegyzése: ez a tanulás -idô jelentôsen csökkenthetô a lista/csoport -archívum és a megfelelô FAQ -tanulmányozásával. Ez utóbbit -mindenképpen olvasd el, mielôtt postáznál -bármit is!) - -
    • Ne hibáztasd a rendszeradminisztrátort az adott -rendszer felhasználóinak viselkedése miatt. - -
    • Vésd jól az eszedbe, hogy amit írsz, azt -széles közönség olvashatja. Ebben -éppenséggel a jelenlegi vagy a jövôbeli -fônököd is benne lehet. Vigyázz tehát -arra, hogy mit írsz. Jusson eszedbe az is, hogy a csoportokat -és különösen a levelezési -listákat archiválni is szokták, olyan helyen, amihez -sokan férnek hozzá. Így a szavaidat hosszú -idôn át olvashatja szinte bárki. - -
    • Fel kell tételezned, hogy minden egyes ember saját -maga nevében ír, és nincs köze a -szervezetéhez, kivéve ha kifejezetten ezt -állítja. - -
    • Tudnod kell azt is, hogy akárcsak a levél, a news is -erôforrásigényes. Ismerned kell tehát a -szervezeted speciális szabályzatát erre -vonatkozólag. - -
    • Az üzenetek és a cikkek jó, ha rövidek -és célratörôek. Ne kószálj el -a témától, ne beszélj össze-vissza, -és soha, de soha ne írj olyan levelet, ami csak valaki -gépelési vagy helyesírási hibáira -mutat rá. Ez utóbbi azonnal megmutatja, hogy milyen -szörnyen kezdô vagy. - -
    • A subject sorok az adott csoport konvenciót kell -kövessék. (Fordító megjegyzése: -Látni fogod, hogy sok helyen a subject sor valamilyen -osztályozó információt hordoz, és -ha ez hiányzik, akkor valószínûleg pont -azokhoz az emberekhez nem jut el az írásod, akiknek -szántad.) - -
    • Bár lehet, hogy lehetôség nyílik -rá, de a hamisítás, a levelek tartalmához -vagy fejlécéhez való -hozzápiszkálás általában nem +
    • Legalább egy, de inkább két hónapig +olvasd az adott levelezési listát ill. hírcsoportot +mielôtt postázol valamit. Ez hozzásegít az +adott csoport kultúrjának megismeréséhez. +(Fordító megjegyzése: ez a tanulás +idô jelentôsen csökkenthetô a lista/csoport +archívum és a megfelelô FAQ +tanulmányozásával. Ez utóbbit +mindenképpen olvasd el, mielôtt postáznál +bármit is!) + +
    • Ne hibáztasd a rendszeradminisztrátort az adott +rendszer felhasználóinak viselkedése miatt. + +
    • Vésd jól az eszedbe, hogy amit írsz, azt +széles közönség olvashatja. Ebben +éppenséggel a jelenlegi vagy a jövôbeli +fônököd is benne lehet. Vigyázz tehát +arra, hogy mit írsz. Jusson eszedbe az is, hogy a csoportokat +és különösen a levelezési +listákat archiválni is szokták, olyan helyen, amihez +sokan férnek hozzá. Ãgy a szavaidat hosszú +idôn át olvashatja szinte bárki. + +
    • Fel kell tételezned, hogy minden egyes ember saját +maga nevében ír, és nincs köze a +szervezetéhez, kivéve ha kifejezetten ezt +állítja. + +
    • Tudnod kell azt is, hogy akárcsak a levél, a news is +erôforrásigényes. Ismerned kell tehát a +szervezeted speciális szabályzatát erre +vonatkozólag. + +
    • Az üzenetek és a cikkek jó, ha rövidek +és célratörôek. Ne kószálj el +a témától, ne beszélj össze-vissza, +és soha, de soha ne írj olyan levelet, ami csak valaki +gépelési vagy helyesírási hibáira +mutat rá. Ez utóbbi azonnal megmutatja, hogy milyen +szörnyen kezdô vagy. + +
    • A subject sorok az adott csoport konvenciót kell +kövessék. (Fordító megjegyzése: +Látni fogod, hogy sok helyen a subject sor valamilyen +osztályozó információt hordoz, és +ha ez hiányzik, akkor valószínûleg pont +azokhoz az emberekhez nem jut el az írásod, akiknek +szántad.) + +
    • Bár lehet, hogy lehetôség nyílik +rá, de a hamisítás, a levelek tartalmához +vagy fejlécéhez való +hozzápiszkálás általában nem elfogadott. -
    • Hirdetni csak és kizárólag az arra hivatott -csoportokban és levelezési listákban szabad! Ez -is egy példája annak, hogy célszerû -ismerned azt a közönséget, aki majd -írásodat olvassa. A kéretlen hirdetés - ami -nem tartozik egy adott csoport/lista témakörébe - -valószínû következménye: rengeteg -durva válaszlevél. (Fordító -megjegyzése: semmiképpen se nyilvánosan -válaszoljál, hiszen már ez is reklám. -Hatékonyabb, ha a feladó -rendszergazdáinál panaszkodsz.) - -
    • Ha válaszüzenetet írsz, akkor -mindenképpen foglald össze vagy idézz annyit az -eredetibôl, hogy érthetô legyen a válaszod. -Különösen a Usenet esetében lényeges -ez, mivel ott olyan az üzenetek elosztása, hogy -elôfordulhat, hogy valaki a válaszodat olvassa elôbb, -mint az eredetit. Így tehát jó, ha képbe -helyezed olvasóidat - viszont ne idézd az egészet! - -
    • Újra hangsúlyozzuk, hogy igen célszerû, -ha van egy signature file-od, amit az üzeneteidhez csatolhatsz. -Csak és kizárólag így tudod -biztosítani azt, hogy bármilyen program is -nyirbálja meg a fejlécinformációkat, az -olvasóid még mindig rád találhassanak. - -
    • Légy óvatos, amikor egy üzenetre vagy cikkre -válaszolsz. Gyakran a válasz a lista vagy a csoport -címére megy a feladó helyett. -Tévedésbôl személyes üzenetet -küldhetsz egy helyett sok embernek, amivel csak mindenkit -zavarba hozol. A legjobb az, ha válaszadásnál -begépeled a címet az automata "reply" helyett. -(Fordító megjegyzése: Kellô -óvatossággal ez utóbbitól el lehet -térni.) - -
    • (Fordító megjegyzése: Ezt a pontot -jelentôsen lerövidítettem. A lényeg az, hogy -ne küldjünk automata (vacation, non-delivery stb.) -üzeneteket levelezési listákra) - -
    • Ha véletlenül személyes üzenetet -küldtél egy listára vagy csoportba, akkor -küldj egy bocsánatkérô üzenetet az -eredeti címzettnek és a listának ill. csoportnak is. - -
    • Ha valakivel nem értesz egyet, akkor célszerûbb -személyes üzenetekben rendezni a dolgot, mint vitát -folytatni egy listán. Ha olyasmin vitáztok, ami -érdekelheti a lista olvasóit, akkor érdemes a -vitátok eredményét összefoglalni és -elküldeni a listára. - -
    • Ne keveredj flame war-ba! Ne írjál heves leveleket -és ne válaszoljál ilyenekre. - -
    • Ne küldjél olyan válaszokat, amelyek csak -gratulálnak egy másik válaszhoz. -(Fordító megyjezése: Ezt személyes -levélben teheted meg, ha úgy érzed, -tényleg fontos) - -
    • Légy óvatos a fixpontos betûkkel és a -rajzokkal. Ezeket egy másik rendszer - vagy akár -ugyanazon rendszer egy másik programja - -másképpen jelenítheti meg, mint a te szoftvered. - -
    • Vannak olyan hírcsoportok és levelezési -listák, melyek témája igen széles -érdeklôdési kört fed le. Ez gyakran olyan -olvasókat jelenthet, akik életstílusban, -vallásban, és kultúrában is jelentôsen -eltérhetnek. Ne küldjél olyan üzeneteket egy -listára/csoportba - ha annak nézôpontja -valamiért nem egyezik a tiéddel - ami csak annyit -közöl, hogy az ô álláspontjukat -bántónak érzed. Szexuális vagy faji -zaklatásnak jogi következménye is lehet. Az -általad megkérdôjelezhetônek érzett -üzenetek kiszûrésére (filter) vannak +
    • Hirdetni csak és kizárólag az arra hivatott +csoportokban és levelezési listákban szabad! Ez +is egy példája annak, hogy célszerû +ismerned azt a közönséget, aki majd +írásodat olvassa. A kéretlen hirdetés - ami +nem tartozik egy adott csoport/lista témakörébe - +valószínû következménye: rengeteg +durva válaszlevél. (Fordító +megjegyzése: semmiképpen se nyilvánosan +válaszoljál, hiszen már ez is reklám. +Hatékonyabb, ha a feladó +rendszergazdáinál panaszkodsz.) + +
    • Ha válaszüzenetet írsz, akkor +mindenképpen foglald össze vagy idézz annyit az +eredetibôl, hogy érthetô legyen a válaszod. +Különösen a Usenet esetében lényeges +ez, mivel ott olyan az üzenetek elosztása, hogy +elôfordulhat, hogy valaki a válaszodat olvassa elôbb, +mint az eredetit. Ãgy tehát jó, ha képbe +helyezed olvasóidat - viszont ne idézd az egészet! + +
    • Újra hangsúlyozzuk, hogy igen célszerû, +ha van egy signature file-od, amit az üzeneteidhez csatolhatsz. +Csak és kizárólag így tudod +biztosítani azt, hogy bármilyen program is +nyirbálja meg a fejlécinformációkat, az +olvasóid még mindig rád találhassanak. + +
    • Légy óvatos, amikor egy üzenetre vagy cikkre +válaszolsz. Gyakran a válasz a lista vagy a csoport +címére megy a feladó helyett. +Tévedésbôl személyes üzenetet +küldhetsz egy helyett sok embernek, amivel csak mindenkit +zavarba hozol. A legjobb az, ha válaszadásnál +begépeled a címet az automata "reply" helyett. +(Fordító megjegyzése: Kellô +óvatossággal ez utóbbitól el lehet +térni.) + +
    • (Fordító megjegyzése: Ezt a pontot +jelentôsen lerövidítettem. A lényeg az, hogy +ne küldjünk automata (vacation, non-delivery stb.) +üzeneteket levelezési listákra) + +
    • Ha véletlenül személyes üzenetet +küldtél egy listára vagy csoportba, akkor +küldj egy bocsánatkérô üzenetet az +eredeti címzettnek és a listának ill. csoportnak is. + +
    • Ha valakivel nem értesz egyet, akkor célszerûbb +személyes üzenetekben rendezni a dolgot, mint vitát +folytatni egy listán. Ha olyasmin vitáztok, ami +érdekelheti a lista olvasóit, akkor érdemes a +vitátok eredményét összefoglalni és +elküldeni a listára. + +
    • Ne keveredj flame war-ba! Ne írjál heves leveleket +és ne válaszoljál ilyenekre. + +
    • Ne küldjél olyan válaszokat, amelyek csak +gratulálnak egy másik válaszhoz. +(Fordító megyjezése: Ezt személyes +levélben teheted meg, ha úgy érzed, +tényleg fontos) + +
    • Légy óvatos a fixpontos betûkkel és a +rajzokkal. Ezeket egy másik rendszer - vagy akár +ugyanazon rendszer egy másik programja - +másképpen jelenítheti meg, mint a te szoftvered. + +
    • Vannak olyan hírcsoportok és levelezési +listák, melyek témája igen széles +érdeklôdési kört fed le. Ez gyakran olyan +olvasókat jelenthet, akik életstílusban, +vallásban, és kultúrában is jelentôsen +eltérhetnek. Ne küldjél olyan üzeneteket egy +listára/csoportba - ha annak nézôpontja +valamiért nem egyezik a tiéddel - ami csak annyit +közöl, hogy az ô álláspontjukat +bántónak érzed. Szexuális vagy faji +zaklatásnak jogi következménye is lehet. Az +általad megkérdôjelezhetônek érzett +üzenetek kiszûrésére (filter) vannak programok. -
    - +

    -3.1.2 Iránymutató levelezési listákhoz +3.1.2 Iránymutató levelezési listákhoz

    -A létezô levelezési listákról -és a hozzájuk való csatlakozásról -sokféleképpen találhatsz -információkat a Net-en. Természetesen ezen -információ mellett szükséged lesz a -helyiekre is; milyen szabályok vonatkoznak a levelezési -listákhoz való csatlakozásra és a -postázásra. Általában jobb, ha -elôször a szûkebb környezetben kutatsz -információ után és csak utána +A létezô levelezési listákról +és a hozzájuk való csatlakozásról +sokféleképpen találhatsz +információkat a Net-en. Természetesen ezen +információ mellett szükséged lesz a +helyiekre is; milyen szabályok vonatkoznak a levelezési +listákhoz való csatlakozásra és a +postázásra. Ãltalában jobb, ha +elôször a szûkebb környezetben kutatsz +információ után és csak utána kezdesz az Interneten kutatni. Mindenesetre a -news.answers hírcsoportban rendszeresen -megjelenik egy lista a levelezési listákról -és a csatlakozásról. Bármilyen -témában keresel levelezési listát, ez egy -felbecsülhetetlen értékû anyag. A -válogatott bibliográfiánkban a [9,13,15]-ös -anyagokból is tájékozódhatsz. +news.answers hírcsoportban rendszeresen +megjelenik egy lista a levelezési listákról +és a csatlakozásról. Bármilyen +témában keresel levelezési listát, ez egy +felbecsülhetetlen értékû anyag. A +válogatott bibliográfiánkban a [9,13,15]-ös +anyagokból is tájékozódhatsz.

      - -
    • A fel- és leiratkozó (subscribe - unsubscribe) -üzeneteket a megfelelô címre küldjed. -Néhány szoftver ugyan megkísérli -kiszûrni a listára küldött ilyen -típusú üzeneteket, de ezek sem -mindenhatóak. A te felelôsséged megtanulni a -levelezési listák mûködését -és a megfelelô levelet a megfelelô címre -küldeni. Nagyon sok lista mûködtet egy "-request" -alias-t a fel-le iratkozó üzeneteknek, de nem mindegyik. -(Fordító megjegyzése: ezt a mondatot csak akkor -fogod megérteni, ha már elkezdted megtanulni a fent -emlegetett mûködést) Mindig légy -tisztában annak a listának a szabályaival, amire -elôfizetsz. - -
    • Mentsd el a feliratkozásra kapott választ. Ez -legtöbbször tartalmazza a leiratkozáshoz -szükséges információkat. - -
    • Általában nem lehet visszavonni egy -elküldött levelet. A rendszergazdád sem -képes erre. Ez azt jelenti, hogy csak akkor szabad -elküldened az üzenetedet, ha már bizonyos vagy -benne, hogy azt szeretnéd, hogy megjelenjen. - -
    • Igen sok levelezô program rendelkezik reply -funkcióval. Ez kényelmes -magánlevelezéskor, de igen bosszantó lehet -levelezési listáknál. Amikor listán -jött üzenetre válaszolsz, akkor vizsgáld meg -a Reply-To: mezôt. Az gyakorta a lista címe, azaz a -válaszod a lista összes tagjának megy. - -
    • Felesleges, és ezért helytelen nagy file-okat -küldeni egy listára, ha egy Uniform Resource Locator -(URL) vagy egy ftp-zhetô verzió címe is -elég. Ha sok kis részletben szeretnéd -elküldeni, akkor az adott lista erre vonatkozó -szabályait kell követned. Ha ezeket nem ismered, akkor -kérdezd meg! - -
    • Fontold meg a leiratkozást vagy a "nomail" opció -bekapcsolását (ahol lehet) ha hosszabb ideig nem tudod -megnézni a leveleidet. (Fordító -megjegyzése: A leglanyhább lista is robbanhat hirtelen, -tehát ezt minden egyes listával meg kell tegyed hosszabb -távollét elôtt!) - -
    • Ha egy üzenetet netán több listára -küldenél, különösen akkor, ha azok -közel azonos témájúak, akkor kérj -elnézést a cross-posting miatt. - -
    • Ha felteszel egy kérdést, akkor a -válaszokból készíts egy -összegzést és azt küldd el a listára. -Ez semmi esetre se az összes válasz egyszerû -egymásutánja legyen, hanem egy gondos -összegzés. (Fordító megjegyzése: -Felmerülhet a kérdés, hogy mégis -miért tedd ezt meg. A válasz az Internet -alapfilozófiája maga: segíts másokon, -és rajtad is segítenek. Ha valakik -megválaszolták egy kérdésedet, akkor -megtehetsz annyit viszonzásképp, hogy az így -megszerzett tudást megosztod másokkal) - -
    • Vannak privát levelezési listák. Ha nem -hívtak meg, akkor ne küldj ide üzeneteket. Az ilyen -listák üzeneteit ne add tovább egy szélesebb -közönségnek. - -
    • Ha vitába keveredsz, akkor a megbeszélést az -adott témáról illik folytatni, nem pedig az adott -személyekrôl. - -
    - +
  • A fel- és leiratkozó (subscribe - unsubscribe) +üzeneteket a megfelelô címre küldjed. +Néhány szoftver ugyan megkísérli +kiszûrni a listára küldött ilyen +típusú üzeneteket, de ezek sem +mindenhatóak. A te felelôsséged megtanulni a +levelezési listák mûködését +és a megfelelô levelet a megfelelô címre +küldeni. Nagyon sok lista mûködtet egy "-request" +alias-t a fel-le iratkozó üzeneteknek, de nem mindegyik. +(Fordító megjegyzése: ezt a mondatot csak akkor +fogod megérteni, ha már elkezdted megtanulni a fent +emlegetett mûködést) Mindig légy +tisztában annak a listának a szabályaival, amire +elôfizetsz. + +
  • Mentsd el a feliratkozásra kapott választ. Ez +legtöbbször tartalmazza a leiratkozáshoz +szükséges információkat. + +
  • Ãltalában nem lehet visszavonni egy +elküldött levelet. A rendszergazdád sem +képes erre. Ez azt jelenti, hogy csak akkor szabad +elküldened az üzenetedet, ha már bizonyos vagy +benne, hogy azt szeretnéd, hogy megjelenjen. + +
  • Igen sok levelezô program rendelkezik reply +funkcióval. Ez kényelmes +magánlevelezéskor, de igen bosszantó lehet +levelezési listáknál. Amikor listán +jött üzenetre válaszolsz, akkor vizsgáld meg +a Reply-To: mezôt. Az gyakorta a lista címe, azaz a +válaszod a lista összes tagjának megy. + +
  • Felesleges, és ezért helytelen nagy file-okat +küldeni egy listára, ha egy Uniform Resource Locator +(URL) vagy egy ftp-zhetô verzió címe is +elég. Ha sok kis részletben szeretnéd +elküldeni, akkor az adott lista erre vonatkozó +szabályait kell követned. Ha ezeket nem ismered, akkor +kérdezd meg! + +
  • Fontold meg a leiratkozást vagy a "nomail" opció +bekapcsolását (ahol lehet) ha hosszabb ideig nem tudod +megnézni a leveleidet. (Fordító +megjegyzése: A leglanyhább lista is robbanhat hirtelen, +tehát ezt minden egyes listával meg kell tegyed hosszabb +távollét elôtt!) + +
  • Ha egy üzenetet netán több listára +küldenél, különösen akkor, ha azok +közel azonos témájúak, akkor kérj +elnézést a cross-posting miatt. + +
  • Ha felteszel egy kérdést, akkor a +válaszokból készíts egy +összegzést és azt küldd el a listára. +Ez semmi esetre se az összes válasz egyszerû +egymásutánja legyen, hanem egy gondos +összegzés. (Fordító megjegyzése: +Felmerülhet a kérdés, hogy mégis +miért tedd ezt meg. A válasz az Internet +alapfilozófiája maga: segíts másokon, +és rajtad is segítenek. Ha valakik +megválaszolták egy kérdésedet, akkor +megtehetsz annyit viszonzásképp, hogy az így +megszerzett tudást megosztod másokkal) + +
  • Vannak privát levelezési listák. Ha nem +hívtak meg, akkor ne küldj ide üzeneteket. Az ilyen +listák üzeneteit ne add tovább egy szélesebb +közönségnek. + +
  • Ha vitába keveredsz, akkor a megbeszélést az +adott témáról illik folytatni, nem pedig az adott +személyekrôl. + +
  • -3.1.3 Iránymutató a Usenet-hoz +3.1.3 Iránymutató a Usenet-hoz

    -A Usenet egy világméretû elosztott rendszer, ami -a legkülönfélébb témákban -való kommunikációra ad lehetôséget. -A rendszer hierarchikus, a legfelsôbb szinten a -következô témák vannak: sci - -tudományos; comp - számítógépes; -news - magáról a Usenetról; rec - -szórakozás, hobbi; soc - szociális; talk - bô -lére eresztett, soha véget nem érô -beszélgetések (pl. politika); biz - üzleti; alt - -bármi egyéb. Az alt fôcsoportba tartozó -csoportok olyan értelemben is alternatívok, hogy a -létrehozásuk nem megy keresztül ugyanazon a -lépéseken, mint a többi fôcsoportbeliek. -Vannak még helyi fôcsoportok, széles körben -terjesztett egyebek, mint a bionet, és a saját -szervezetednek is lehetnek saját, belsô csoportjai. -Nemrégiben egy "humanities" fôcsoport is -létrejött, és várhatóak -továbbiak is. A bibliográfiánk [2,8,22,23]-as -anyagait érdemes a témában elolvasni. +A Usenet egy világméretû elosztott rendszer, ami +a legkülönfélébb témákban +való kommunikációra ad lehetôséget. +A rendszer hierarchikus, a legfelsôbb szinten a +következô témák vannak: sci - +tudományos; comp - számítógépes; +news - magáról a Usenetról; rec - +szórakozás, hobbi; soc - szociális; talk - bô +lére eresztett, soha véget nem érô +beszélgetések (pl. politika); biz - üzleti; alt - +bármi egyéb. Az alt fôcsoportba tartozó +csoportok olyan értelemben is alternatívok, hogy a +létrehozásuk nem megy keresztül ugyanazon a +lépéseken, mint a többi fôcsoportbeliek. +Vannak még helyi fôcsoportok, széles körben +terjesztett egyebek, mint a bionet, és a saját +szervezetednek is lehetnek saját, belsô csoportjai. +Nemrégiben egy "humanities" fôcsoport is +létrejött, és várhatóak +továbbiak is. A bibliográfiánk [2,8,22,23]-as +anyagait érdemes a témában elolvasni.

      - -
    • A Usenet szójárásában a "Posting" -(postázás) egy új vagy válasz cikk -postázását jelenti egy csoportba. A -"Cross-posting" kifejezést arra használják, ha -valaki egy cikket több csoportba küldött el. Ha ezt -vagy a Followup-To: mezôt is használjuk, akkor ezt -feltétlenül említsük meg a cikkünkben. -Az olvasók általában azt feltételezik, hogy -egy üzenet egy csoportba lett postázva, és a -"follow-up" ebbe a csoportba kerül. A fejléc -említett mezôje megváltoztathatja ezt. (A -fordító megjegyzése: A "follow-up" a -válaszcikk neve, a válaszként írt -magánlevelet "Reply" néven emlegetik. A Followup-To: -azt a csoportot adja meg, ahová a válaszcikkek -kerülnek.) - -
    • Mielôtt elküldenél egy témában -egy cikket, olvasd el abban a témában írott -cikkeket. (Ezt thread-nek nevezzük) Ne küldjél -"Egyetértek" vagy hasonló üzeneteket, amelyek -tartalma pusztán arra korlátozódik, hogy -egyetértesz valamely korábbi cikkel. Egy follow-up -cikknek mindig hosszabbnak kell lennie az elôzô -levelekbôl vett idézeteknél. (Fordító -megjegyzése: Apró pontosítás: Egy cikk -és rá adott válaszcikkek -összességét nevezünk thread-nek.) - -
    • Levélben (reply) írd meg válaszod, ha csak -egy embernek szánod azt. Légy a tudatában annak, -hogy a News-t a világon mindenütt olvashatják, -és a legtöbb embert NEM érdekel egy privát -válasz. Ugyanakkor ne habozzál postázni, ha -olyasmi mondandód van, ami az Internet széles -olvasóközönségét érdekelheti. -(Fordító megjegyzése: tipikus, és nagyon -bosszantó viselkedés pl. egy bolhapiac csoportban -nyilvánosan válaszolni a hirdetésekre.) - -
    • Ellenôrizd le a fejléc Distribution: -mezôjét, de ne bízz benne. A News -meglehetôsen bonyolult szállítása -módja miatt a Distribution: mezô megbízhatatlan. -Ha olyasvalamit írsz, ami csak kisszámú embert -érdekelhet, akkor használj egy megfelelô -Distribution: mezôt; ezzel megkísérled az -elosztást ezekre az emberekre korlátozni. -(Fordító megjegyzése: Például -állítsd be "hun"-ra ezt a mezôt, ha csak magyarokat -érdeklô cikket postázol.) - -
    • Ha úgy érzed, hogy egy cikk több -hírcsoportba való, akkor mindenképpen -CROSSPOST-tal küldd el a cikkedet ahelyett, hogy -különálló üzenetekben -küldenéd el. Biztos lehetsz abban, hogy -általában csak 5-6 csoportnak lesz eléggé -hasonló témája. (Fordító -megjegyzése: Ez a pont ritkán alkalmazandó!! -RENDKÍVÜL nyomós okodnak kell lennie ahhoz, -hogy akár két csoportba is postázzál!!) - -
    • Kísérelj meg elôször a -hagyományos ismeretforrásokból -(Kézikönyvek, újságok, help file-ok) -meríteni, mielôtt egy kérdést -postáznál. Ha ezekben megtalálható -kérdést teszel fel, akkor mogorva RTFM (Read the fine -manual - noha az f betû egy sokkal durvább szót -jelölt eredetileg) üzeneteket kapsz csak -viszonzásképpen. (Fordító -megjegyzése: Ez is csak a józan ész -határáig érvényes. Tényleg olvasd -el a kézikönyvet, de ne áldozz éveket -valaminek a kutatására - végülis az Internet -egyik haszna, hogy olyanok, akik ezt megtették, -megosztják veled tudásodat.) - -
    • Bár van néhány hírcsoport, amik -kifejezetten hirdetésre vannak kitalálva, a -témától eltérô (off-topic) -hirdetések egészen egyszerûen -bûnözésnek számítanak a -legtöbb csoportban. Ha egy hirdetést minden egyes -hírcsoportba elküldesz, akkor szinte biztosan -búcsút mondhatsz a hálózati -elérésednek. - -
    • Ha felfedezel egy hibát a cikkedben, akkor -kíséreld meg minél hamarabb törölni -(cancel) azt. (Fordító megjegyzése: A -törlés is csak egy üzenet, mint minden más. -Nem bízhatsz igazán abban, hogy senki sem fogja a -cikkedet olvasni. Célszerû egy második, helyes -cikket is küldeni, amelyben elnézést is kérsz -az elôzô hibájáért. A józan -észt itt is célszerû bevetni: a hiba alatt nem -nyelvtani, gépelési stb. - vagyis apró, -lényegtelen - hibát, hanem a lényeget -érintô hibára gondolunk.) - -
    • MEG SE próbáld más cikkét -törölni (cancel). Lépj kapcsolatba -rendszergazdáddal, ha nem tudod, hogy hogyan kell saját -cikkedet törölni vagy ha valamilyen más -postázás (pl. lánc levél) -törlést kíván. - -
    • Ha postáztál valamit, és nem látod -viszont azonnal, akkor ne hidd rögtön azt, hogy valami nem -mûködött és ne postázd azonnal -újra. - -
    • Néhány csoport megengedi (néhány -pedig ezért van), hogy olyan cikkek is felkerüljenek, amik -erôsen megkérdôjelezhetôek. Ugyanakkor -semmilyen garancia nincs arra, hogy a csoport többi -olvasója is ugyanúgy elfogadja az anyagot, mint te. -Használd a Rot13 szoftvert, ami 13 karakterrel eltol minden egyes -karaktert, hogy ne bántsál meg senkit. -(Fordító megjegyzése: Ez sok szoftverbe be van -építve) - -
    • Ha olyan üzenetet írsz egy könyveket vagy -filmeket tárgyaló csoportba, ami lényeges titkot -tár fel, akkor célszerû "Spoiler" szót tenni -a Subject: sorba. Üthetsz egy pár Enter-t a cikk -elejére, hogy eltüntesd szem elöl a cikket, vagy -használhatod az elôbb említett Rot13 -módszert. (Fordító megjegyzése: A -számítógépes játékok is ez -a kategória. Esetleg tarthatunk attól, hogy nem mindenki -tudja a Rot13-at dekódolni, és az üres sorok mellett -döntünk. Ilyenkor érdemes nemcsak a Subject sorba -beírni, hogy Spoiler, hanem a cikk elsô sorába is -beírni ezt, hogy aki ránéz a cikkre az ne csak -üres sorokat lásson) - -
    • Ugyan a cikkek illetéktelen megpiszkálása, -megváltoztatása a szabályokkal ellentétes, -mégis megtörténik. Ez ellen megfelelô -szoftverrel - mint például a PGP - -védekezhetünk. - -
    • Az anonim postázás bizonyos csoportokban -elfogadott, másokban nem. Ha egy cikk nem felel meg a -szabályoknak, ha rajta van a nevünk, akkor sem fog, ha -nincs rajta. (Fordító megjegyzése: Azaz az -anonimitást legtöbbször olyankor használjuk, -ha elfogadható, de személyünkre valamiért -hátrányos cikket szeretnénk -közzétenni.) - -
    • Ha moderált csoportba postázol, akkor -számolnod kell egy kis késedelemmel. A -moderátor meg is változtathatja a Subject sorodat, hogy +
    • A Usenet szójárásában a "Posting" +(postázás) egy új vagy válasz cikk +postázását jelenti egy csoportba. A +"Cross-posting" kifejezést arra használják, ha +valaki egy cikket több csoportba küldött el. Ha ezt +vagy a Followup-To: mezôt is használjuk, akkor ezt +feltétlenül említsük meg a cikkünkben. +Az olvasók általában azt feltételezik, hogy +egy üzenet egy csoportba lett postázva, és a +"follow-up" ebbe a csoportba kerül. A fejléc +említett mezôje megváltoztathatja ezt. (A +fordító megjegyzése: A "follow-up" a +válaszcikk neve, a válaszként írt +magánlevelet "Reply" néven emlegetik. A Followup-To: +azt a csoportot adja meg, ahová a válaszcikkek +kerülnek.) + +
    • Mielôtt elküldenél egy témában +egy cikket, olvasd el abban a témában írott +cikkeket. (Ezt thread-nek nevezzük) Ne küldjél +"Egyetértek" vagy hasonló üzeneteket, amelyek +tartalma pusztán arra korlátozódik, hogy +egyetértesz valamely korábbi cikkel. Egy follow-up +cikknek mindig hosszabbnak kell lennie az elôzô +levelekbôl vett idézeteknél. (Fordító +megjegyzése: Apró pontosítás: Egy cikk +és rá adott válaszcikkek +összességét nevezünk thread-nek.) + +
    • Levélben (reply) írd meg válaszod, ha csak +egy embernek szánod azt. Légy a tudatában annak, +hogy a News-t a világon mindenütt olvashatják, +és a legtöbb embert NEM érdekel egy privát +válasz. Ugyanakkor ne habozzál postázni, ha +olyasmi mondandód van, ami az Internet széles +olvasóközönségét érdekelheti. +(Fordító megjegyzése: tipikus, és nagyon +bosszantó viselkedés pl. egy bolhapiac csoportban +nyilvánosan válaszolni a hirdetésekre.) + +
    • Ellenôrizd le a fejléc Distribution: +mezôjét, de ne bízz benne. A News +meglehetôsen bonyolult szállítása +módja miatt a Distribution: mezô megbízhatatlan. +Ha olyasvalamit írsz, ami csak kisszámú embert +érdekelhet, akkor használj egy megfelelô +Distribution: mezôt; ezzel megkísérled az +elosztást ezekre az emberekre korlátozni. +(Fordító megjegyzése: Például +állítsd be "hun"-ra ezt a mezôt, ha csak magyarokat +érdeklô cikket postázol.) + +
    • Ha úgy érzed, hogy egy cikk több +hírcsoportba való, akkor mindenképpen +CROSSPOST-tal küldd el a cikkedet ahelyett, hogy +különálló üzenetekben +küldenéd el. Biztos lehetsz abban, hogy +általában csak 5-6 csoportnak lesz eléggé +hasonló témája. (Fordító +megjegyzése: Ez a pont ritkán alkalmazandó!! +RENDKÃVÜL nyomós okodnak kell lennie ahhoz, +hogy akár két csoportba is postázzál!!) + +
    • Kísérelj meg elôször a +hagyományos ismeretforrásokból +(Kézikönyvek, újságok, help file-ok) +meríteni, mielôtt egy kérdést +postáznál. Ha ezekben megtalálható +kérdést teszel fel, akkor mogorva RTFM (Read the fine +manual - noha az f betû egy sokkal durvább szót +jelölt eredetileg) üzeneteket kapsz csak +viszonzásképpen. (Fordító +megjegyzése: Ez is csak a józan ész +határáig érvényes. Tényleg olvasd +el a kézikönyvet, de ne áldozz éveket +valaminek a kutatására - végülis az Internet +egyik haszna, hogy olyanok, akik ezt megtették, +megosztják veled tudásodat.) + +
    • Bár van néhány hírcsoport, amik +kifejezetten hirdetésre vannak kitalálva, a +témától eltérô (off-topic) +hirdetések egészen egyszerûen +bûnözésnek számítanak a +legtöbb csoportban. Ha egy hirdetést minden egyes +hírcsoportba elküldesz, akkor szinte biztosan +búcsút mondhatsz a hálózati +elérésednek. + +
    • Ha felfedezel egy hibát a cikkedben, akkor +kíséreld meg minél hamarabb törölni +(cancel) azt. (Fordító megjegyzése: A +törlés is csak egy üzenet, mint minden más. +Nem bízhatsz igazán abban, hogy senki sem fogja a +cikkedet olvasni. Célszerû egy második, helyes +cikket is küldeni, amelyben elnézést is kérsz +az elôzô hibájáért. A józan +észt itt is célszerû bevetni: a hiba alatt nem +nyelvtani, gépelési stb. - vagyis apró, +lényegtelen - hibát, hanem a lényeget +érintô hibára gondolunk.) + +
    • MEG SE próbáld más cikkét +törölni (cancel). Lépj kapcsolatba +rendszergazdáddal, ha nem tudod, hogy hogyan kell saját +cikkedet törölni vagy ha valamilyen más +postázás (pl. lánc levél) +törlést kíván. + +
    • Ha postáztál valamit, és nem látod +viszont azonnal, akkor ne hidd rögtön azt, hogy valami nem +mûködött és ne postázd azonnal +újra. + +
    • Néhány csoport megengedi (néhány +pedig ezért van), hogy olyan cikkek is felkerüljenek, amik +erôsen megkérdôjelezhetôek. Ugyanakkor +semmilyen garancia nincs arra, hogy a csoport többi +olvasója is ugyanúgy elfogadja az anyagot, mint te. +Használd a Rot13 szoftvert, ami 13 karakterrel eltol minden egyes +karaktert, hogy ne bántsál meg senkit. +(Fordító megjegyzése: Ez sok szoftverbe be van +építve) + +
    • Ha olyan üzenetet írsz egy könyveket vagy +filmeket tárgyaló csoportba, ami lényeges titkot +tár fel, akkor célszerû "Spoiler" szót tenni +a Subject: sorba. Üthetsz egy pár Enter-t a cikk +elejére, hogy eltüntesd szem elöl a cikket, vagy +használhatod az elôbb említett Rot13 +módszert. (Fordító megjegyzése: A +számítógépes játékok is ez +a kategória. Esetleg tarthatunk attól, hogy nem mindenki +tudja a Rot13-at dekódolni, és az üres sorok mellett +döntünk. Ilyenkor érdemes nemcsak a Subject sorba +beírni, hogy Spoiler, hanem a cikk elsô sorába is +beírni ezt, hogy aki ránéz a cikkre az ne csak +üres sorokat lásson) + +
    • Ugyan a cikkek illetéktelen megpiszkálása, +megváltoztatása a szabályokkal ellentétes, +mégis megtörténik. Ez ellen megfelelô +szoftverrel - mint például a PGP - +védekezhetünk. + +
    • Az anonim postázás bizonyos csoportokban +elfogadott, másokban nem. Ha egy cikk nem felel meg a +szabályoknak, ha rajta van a nevünk, akkor sem fog, ha +nincs rajta. (Fordító megjegyzése: Azaz az +anonimitást legtöbbször olyankor használjuk, +ha elfogadható, de személyünkre valamiért +hátrányos cikket szeretnénk +közzétenni.) + +
    • Ha moderált csoportba postázol, akkor +számolnod kell egy kis késedelemmel. A +moderátor meg is változtathatja a Subject sorodat, hogy valamelyik thread-be illeszkedjen. -
    • Ne keveredj flame war-ba! Ne írjál heves leveleket -és ne válaszoljál ilyenekre. - -
    +
  • Ne keveredj flame war-ba! Ne írjál heves leveleket +és ne válaszoljál ilyenekre. +
  • -3.2 Iránymutató Adminisztrátoroknak +3.2 Iránymutató Adminisztrátoroknak

    -3.2.1 Általános témák +3.2.1 Ãltalános témák

      +
    • Legyenek tiszták a feliratkozás szabályai a +levelezési listákra és a Usenet csoportokra. -
    • Legyenek tiszták a feliratkozás szabályai a -levelezési listákra és a Usenet csoportokra. - -
    • Legyenek tiszták a postázás szabályai -- a disclaimer-ek .sig-beli szerepeltetését is -beleértve - a levelezési listákba és a +
    • Legyenek tiszták a postázás szabályai +- a disclaimer-ek .sig-beli szerepeltetését is +beleértve - a levelezési listákba és a Usenet csoportokba. -
    • Legyenek tiszták és széles körben -ismertek az archiválásra vonatkozó -szabályaid. (Mennyi ideig tárolod a cikkeket?) +
    • Legyenek tiszták és széles körben +ismertek az archiválásra vonatkozó +szabályaid. (Mennyi ideig tárolod a cikkeket?) -
    • Nyitottan, de azonnal vizsgáld meg a -felhasználóidat érintô panaszokat. +
    • Nyitottan, de azonnal vizsgáld meg a +felhasználóidat érintô panaszokat. -
    • Ügyelj rendszered egészségére! +
    • Ügyelj rendszered egészségére! -
    • Gondold át, hogy milyen hosszan ôrzôd meg a -rendszer naplófile-jait, és a döntésed -eredményét tedd közzé. - -
    +
  • Gondold át, hogy milyen hosszan ôrzôd meg a +rendszer naplófile-jait, és a döntésed +eredményét tedd közzé. +
  • -3.2.2 Levelezési listák +3.2.2 Levelezési listák

    -
      - -
    • A levelezési listák legyenek naprakészek, -hogy elkerüld a "bouncing mail" problémát. -(Fordító megjegyzése: Ha nem -létezô címek is szerepelnek a listában, akkor -a hibaüzenet is kimehet (szoftvertôl függôen) a -listára, újabb hibát generálva. Ez -így mehet egészen addig, amíg valaki ki nem -törli ezt a címet.) - -
    • Segíts a listatulajdonosoknak a problémák -megoldásában - -
    • Informáld a listatulajdonosokat a tervezett -rendszerkarbantartásokról és -leállásokról. - -
    • Legyen minden listához "-request" alias az iratkozási -és adminisztrációs levelekhez. - -
    • A mail gateway-eid mûködjenek simán. -
    - +
  • A levelezési listák legyenek naprakészek, +hogy elkerüld a "bouncing mail" problémát. +(Fordító megjegyzése: Ha nem +létezô címek is szerepelnek a listában, akkor +a hibaüzenet is kimehet (szoftvertôl függôen) a +listára, újabb hibát generálva. Ez +így mehet egészen addig, amíg valaki ki nem +törli ezt a címet.) + +
  • Segíts a listatulajdonosoknak a problémák +megoldásában + +
  • Informáld a listatulajdonosokat a tervezett +rendszerkarbantartásokról és +leállásokról. + +
  • Legyen minden listához "-request" alias az iratkozási +és adminisztrációs levelekhez. + +
  • A mail gateway-eid mûködjenek simán. +
  • 3.2.3 Usenet

    -
      -
    • Tedd közzé, hogy milyen feed-et kapsz. Ha ez nem -teljes, lehet, hogy lesz, aki tudni szeretné, hogy miért +
    • Tedd közzé, hogy milyen feed-et kapsz. Ha ez nem +teljes, lehet, hogy lesz, aki tudni szeretné, hogy miért nem. -
    • Rengeteg féle hírolvasó szoftver van, -és ezek hibájáért lehet, hogy a News -Server-t fogják hibáztatni. - -
    • Tiszteld és teljesítsd azonnal a -felhasználóid saját cikkük vagy más -helytelen (pl. lánclevél) törlésére -vonatkozó kérését. (Fordító -megjegyzése: Hallgass a józan észre. Nem arra -gondolunk, hogy egyes embereket zavaró cikkeket -törölj ki, hanem a széles körben elfogadott -szabályokkal ellentétesekre, amelyek leggyakoribb esetei -a sok csoportra kiterjedô hirdetések és a -lánc levelek.) - -
    • Legyenek "Usenet", "Netnews", "News" alias-aid, és -gondoskodj arról, hogy valaki olvassa az ide érkezô +
    • Rengeteg féle hírolvasó szoftver van, +és ezek hibájáért lehet, hogy a News +Server-t fogják hibáztatni. + +
    • Tiszteld és teljesítsd azonnal a +felhasználóid saját cikkük vagy más +helytelen (pl. lánclevél) törlésére +vonatkozó kérését. (Fordító +megjegyzése: Hallgass a józan észre. Nem arra +gondolunk, hogy egyes embereket zavaró cikkeket +törölj ki, hanem a széles körben elfogadott +szabályokkal ellentétesekre, amelyek leggyakoribb esetei +a sok csoportra kiterjedô hirdetések és a +lánc levelek.) + +
    • Legyenek "Usenet", "Netnews", "News" alias-aid, és +gondoskodj arról, hogy valaki olvassa az ide érkezô leveleket. -
    - +

    -3.3 Iránymutató moderátoroknak +3.3 Iránymutató moderátoroknak

    -

    -3.3.1 Általános útmutató +3.3.1 Ãltalános útmutató

    -
      -
    • Gondoskodj arról, hogy a Gyakran Ismételt -Kérdések válaszai (GYIK ill. FAQ) rendszeres -idôközönként postázásra -kerüljenek. Szerepeljen ebben egy útmutató a -cikkekrôl/üzenetekrôl. Ha nem te vagy a GYIK -fenntartója, akkor gondoskodj arról, hogy ô(k) -belevegyé(k) ezeket abba. - -
    • Legyen egy jó üdvözlô (welcome) file-od, -amiben információk vannak a -fel/leiratkozásról. (Fordító -megjegyzése: És ezt persze küldd is el minden -feliratkozó felhasználónak. Ez a pont csak -levelezési listákra érvényes.) - -
    • A hírcsoportok célját (charter) -iránymutatásait rendszeresen kell postázni. - -
    • A hírcsoportok/levelezési listák legyenek -naprakészek. Alakíts ki egy menetrendet az -üzenetek postázására. Legyen helyettesed, -ha szabadságra mégy vagy más egyéb +
    • Gondoskodj arról, hogy a Gyakran Ismételt +Kérdések válaszai (GYIK ill. FAQ) rendszeres +idôközönként postázásra +kerüljenek. Szerepeljen ebben egy útmutató a +cikkekrôl/üzenetekrôl. Ha nem te vagy a GYIK +fenntartója, akkor gondoskodj arról, hogy ô(k) +belevegyé(k) ezeket abba. + +
    • Legyen egy jó üdvözlô (welcome) file-od, +amiben információk vannak a +fel/leiratkozásról. (Fordító +megjegyzése: És ezt persze küldd is el minden +feliratkozó felhasználónak. Ez a pont csak +levelezési listákra érvényes.) + +
    • A hírcsoportok célját (charter) +iránymutatásait rendszeresen kell postázni. + +
    • A hírcsoportok/levelezési listák legyenek +naprakészek. Alakíts ki egy menetrendet az +üzenetek postázására. Legyen helyettesed, +ha szabadságra mégy vagy más egyéb miatt nem tudsz Net-hez jutni.

      -4.0 Információs -szolgáltatások (Gopher, Wais, WWW, ftp, telnet) -

      +4.0 Információs +szolgáltatások (Gopher, Wais, WWW, ftp, telnet) +

      -Az elmúlt években robbanásszerûen -nôtt a Hálózat néhány új -szolgáltatás nyomán. Gopher, Wais, World Wide -Web (WWW), Multi User Dimensions (MUD), ez utóbbi -objektumorientált verziója (MOO) a legfontosabbak. Noha -az információlelés lehetôsége -robbanásszerûen terjed, a "Vevô védd magad" -(Caveat Emptor) elv érvényben marad. Ezekrôl -részletesebben a válogatott bibliográfiánk -[14,28]-as számú anyagaiban olvashatsz. -(Fordító megjegyzése: Érdekes lehet -még az IRC is. Ez a Caveat Emptor arra akart vonatkozni, hogy -nem muszáj mindenbe -pénzt/erôfeszítést stb. beleölni, ami -elsôre szépnek látszik.) +Az elmúlt években robbanásszerûen +nôtt a Hálózat néhány új +szolgáltatás nyomán. Gopher, Wais, World Wide +Web (WWW), Multi User Dimensions (MUD), ez utóbbi +objektumorientált verziója (MOO) a legfontosabbak. Noha +az információlelés lehetôsége +robbanásszerûen terjed, a "Vevô védd magad" +(Caveat Emptor) elv érvényben marad. Ezekrôl +részletesebben a válogatott bibliográfiánk +[14,28]-as számú anyagaiban olvashatsz. +(Fordító megjegyzése: Érdekes lehet +még az IRC is. Ez a Caveat Emptor arra akart vonatkozni, hogy +nem muszáj mindenbe +pénzt/erôfeszítést stb. beleölni, ami +elsôre szépnek látszik.)

      -4.1 Iránymutató felhasználóknak +4.1 Iránymutató felhasználóknak

      -

      -4.1.1 Általános útmutató +4.1.1 Ãltalános útmutató

      -
        -
      • Ne feledd, hogy minden szolgáltatás valakié. -Aki fizeti a számlát, az szabja meg az adott -szolgáltatásra vonatkozó szabályokat is. Az -információ lehet ingyen, de az is lehet, hogy nem -ingyenes - ennek jó, ha utánanézel. - -
      • Ha egy szolgáltatással probléma akad, akkor -elôször mindig a saját portádon kell -körülnézni. Ellenôrizd (vagy -ellenôriztesd) a file-, szoftver-, és hálózati -beállításokat stb. Mindenképpen tedd ezt -meg, mielôtt arra gondolsz, hogy a szolgáltató -oldalán van a hiba és/vagy a szolgáltatót -hibáztatnád érte. - -
      • Vannak ugyan konvenciók a használt -filetípusokra, de ne építs arra, hogy ezeket mindig -betartják. Például egy ".doc" file nem -feltétlenül Word file. - -
      • Maguk a szolgáltatások is követnek bizonyos -konvenciókat, mint a www.xyz.com. Miközben ezek -ismerete hasznos, de megint csak nem bölcs erre -építeni. - -
      • Ismerd a saját rendszeredben a file nevek -használatát. - -
      • Ismerned kell egy-egy szolgáltatás speciális -információközlô konvencióit. Az FTP -site-ok legtöbbjén találsz valahol a hierarchia -tetején egy README file-t, ami a rendelkezésre -álló file-okról szól. Viszont nem -feltételezheted azt, hogy ezek pontosak és/vagy -naprakészek. - -
      • Egyáltalán, SEMMILYEN -információról NE tételezd fel hogy pontos -vagy naprakész. Az új technológiák -lehetôvé teszik bárkinek, hogy publikáljon, -de az ezzel járó felelôsséget még +
      • Ne feledd, hogy minden szolgáltatás valakié. +Aki fizeti a számlát, az szabja meg az adott +szolgáltatásra vonatkozó szabályokat is. Az +információ lehet ingyen, de az is lehet, hogy nem +ingyenes - ennek jó, ha utánanézel. + +
      • Ha egy szolgáltatással probléma akad, akkor +elôször mindig a saját portádon kell +körülnézni. Ellenôrizd (vagy +ellenôriztesd) a file-, szoftver-, és hálózati +beállításokat stb. Mindenképpen tedd ezt +meg, mielôtt arra gondolsz, hogy a szolgáltató +oldalán van a hiba és/vagy a szolgáltatót +hibáztatnád érte. + +
      • Vannak ugyan konvenciók a használt +filetípusokra, de ne építs arra, hogy ezeket mindig +betartják. Például egy ".doc" file nem +feltétlenül Word file. + +
      • Maguk a szolgáltatások is követnek bizonyos +konvenciókat, mint a www.xyz.com. Miközben ezek +ismerete hasznos, de megint csak nem bölcs erre +építeni. + +
      • Ismerd a saját rendszeredben a file nevek +használatát. + +
      • Ismerned kell egy-egy szolgáltatás speciális +információközlô konvencióit. Az FTP +site-ok legtöbbjén találsz valahol a hierarchia +tetején egy README file-t, ami a rendelkezésre +álló file-okról szól. Viszont nem +feltételezheted azt, hogy ezek pontosak és/vagy +naprakészek. + +
      • Egyáltalán, SEMMILYEN +információról NE tételezd fel hogy pontos +vagy naprakész. Az új technológiák +lehetôvé teszik bárkinek, hogy publikáljon, +de az ezzel járó felelôsséget még nem tanulta meg mindenki. -
      • Tudod már, de fontossága miatt -megismételjük: hacsak nem használsz -titkosító és eredetiséget -biztosító programokat, bármilyen -információ amit az Interneten továbbítasz +
      • Tudod már, de fontossága miatt +megismételjük: hacsak nem használsz +titkosító és eredetiséget +biztosító programokat, bármilyen +információ amit az Interneten továbbítasz "kint van a hidegen". Aki akarja, megszerezheti, vagy -megváltoztathatja. - -
      • Minthogy az Internet világméretû, -találhatsz olyan információs -szolgáltatást, amely gyökeresen eltérô -kultúr- és szokáskörbôl ered. Amit te -bántónak érzel, az abban a -kultúrkörben teljesen elfogadott lehet. Maradj nyílt -a világ felé. - -
      • Ha egy népszerû site-ról szeretnél -információt, akkor nézzd meg, nincs-e egy lista -a mirror site-okról. Ha van ilyen, és ott van -hozzád közelebbi mirror, használd azt. - -
      • Ne használd más FTP site-ját arra, hogy egy -(konkrét) harmadik személynek szánt file-t helyezz -el ott. Ezt egyszerûen szemetelésnek tartják, -és általában nem elfogadott -viselkedésforma. - -
      • Ha problémád támad egy site-al, és -oda fordulsz segítségért, minél több -információt adjál meg a probléma -körülményeirôl. Ezzel nekik is segítesz, -hogy lokalizálhassák a hibát. - -
      • Ha te magad is szeretnél valamilyen -információs szolgáltatást indítani, -mint például egy homepage, akkor szokás szerint -fordulj a helyi rendszergazdához, hogy megismerd az -idevonatkozó helyi szabályokat. - -
      • Ha van rá módod, akkor kíméld a -népszerû site-okat, segítsd elosztani a -terhelést, és csúcsidôn kívül -lépjél be. (Fordító megjegyzése: A -világhálón valahol mindig csúcsidô +megváltoztathatja. + +
      • Minthogy az Internet világméretû, +találhatsz olyan információs +szolgáltatást, amely gyökeresen eltérô +kultúr- és szokáskörbôl ered. Amit te +bántónak érzel, az abban a +kultúrkörben teljesen elfogadott lehet. Maradj nyílt +a világ felé. + +
      • Ha egy népszerû site-ról szeretnél +információt, akkor nézzd meg, nincs-e egy lista +a mirror site-okról. Ha van ilyen, és ott van +hozzád közelebbi mirror, használd azt. + +
      • Ne használd más FTP site-ját arra, hogy egy +(konkrét) harmadik személynek szánt file-t helyezz +el ott. Ezt egyszerûen szemetelésnek tartják, +és általában nem elfogadott +viselkedésforma. + +
      • Ha problémád támad egy site-al, és +oda fordulsz segítségért, minél több +információt adjál meg a probléma +körülményeirôl. Ezzel nekik is segítesz, +hogy lokalizálhassák a hibát. + +
      • Ha te magad is szeretnél valamilyen +információs szolgáltatást indítani, +mint például egy homepage, akkor szokás szerint +fordulj a helyi rendszergazdához, hogy megismerd az +idevonatkozó helyi szabályokat. + +
      • Ha van rá módod, akkor kíméld a +népszerû site-okat, segítsd elosztani a +terhelést, és csúcsidôn kívül +lépjél be. (Fordító megjegyzése: A +világhálón valahol mindig csúcsidô van...) -
      - +

    -4.1.2 Útmutató Valósidejû Interaktív -Szolgáltatásokhoz (MUD-ok, MOO-k, IRC) +4.1.2 Útmutató Valósidejû Interaktív +Szolgáltatásokhoz (MUD-ok, MOO-k, IRC)

    -
      -
    • Akárcsak más rendszereknél, itt is bölcs +
    • Akárcsak más rendszereknél, itt is bölcs dolog csak figyelni egy kicsit, hogy a csoport -kultúrájához szokhassál. - -
    • Egyáltalán nem szükséges mindenkit -személyesen üdvözölni egy csatornán -ill. szobában. Egy szimpla Hello vagy hasonló -köszöntés bôven elég. NEM elfogadott -viselkedés, ha a szoftvered automatikáját arra -használod, hogy mindenkit üdvözöljön. - -
    • Figyelmeztesd a résztvevôket ha nagyobb -mennyiségû információt szeretnél -velük megosztani. Ha mindenki egyetért vele, akkor mehet. -E nélkül azonban súlyos udvariatlanság -ilyesmit csinálni - akárcsak levélben. - -
    • Ne tételezd fel, hogy ismeretlen emberek beszélni -akarnak veled. Ha kényszerítve érzed magad arra, -hogy személyes üzenetet küldjél egy -ismeretlennek, akkor készülj fel arra, hogy éppen -elfoglalt lehet vagy egészen egyszerûen nem akar veled -beszélgetni. - -
    • Tiszteld az adott csoport szokásait. Nézz -körül bemutatkozó anyagokért. Ezek egy ftp +kultúrájához szokhassál. + +
    • Egyáltalán nem szükséges mindenkit +személyesen üdvözölni egy csatornán +ill. szobában. Egy szimpla Hello vagy hasonló +köszöntés bôven elég. NEM elfogadott +viselkedés, ha a szoftvered automatikáját arra +használod, hogy mindenkit üdvözöljön. + +
    • Figyelmeztesd a résztvevôket ha nagyobb +mennyiségû információt szeretnél +velük megosztani. Ha mindenki egyetért vele, akkor mehet. +E nélkül azonban súlyos udvariatlanság +ilyesmit csinálni - akárcsak levélben. + +
    • Ne tételezd fel, hogy ismeretlen emberek beszélni +akarnak veled. Ha kényszerítve érzed magad arra, +hogy személyes üzenetet küldjél egy +ismeretlennek, akkor készülj fel arra, hogy éppen +elfoglalt lehet vagy egészen egyszerûen nem akar veled +beszélgetni. + +
    • Tiszteld az adott csoport szokásait. Nézz +körül bemutatkozó anyagokért. Ezek egy ftp site-on is lehetnek. -
    • Ha valaki becenevet, alias-t, pszeudonevet vagy hasonlót -használ, akkor tiszteld az anonimitáshoz való -jogát. Még ha esetleg jóbarátok is vagytok, -akkor is udvariasabb ezt a becenevet használni. -Egyáltalán ne használd a valódi -nevét on-line engedély nélkül. -
    - +
  • Ha valaki becenevet, alias-t, pszeudonevet vagy hasonlót +használ, akkor tiszteld az anonimitáshoz való +jogát. Még ha esetleg jóbarátok is vagytok, +akkor is udvariasabb ezt a becenevet használni. +Egyáltalán ne használd a valódi +nevét on-line engedély nélkül. +
  • -4.2 Iránymutató adminisztrátoroknak +4.2 Iránymutató adminisztrátoroknak

    -

    -4.2.1 Általános útmutató +4.2.1 Ãltalános útmutató

    -
      -
    • Legyen tiszta mindenki számára, hogy mi -másolható és mi nem. - -
    • Ismertesd, hogy az adott site-on ill. szervezetnél milyen -anyagok állnak rendelkezésre. Az általános -irányelvek legyenek világosak. - -
    • Az információk, külösen a README-k, -legyenek naprakészek. A README-ket sima ASCII -file-ként szolgáltasd. (Fordító -megjegyzése: Ha a README-k és a FAQ-k nagyon -elütnek a valóságtól, akkor rengeteg -egyforma kérdést fogsz kapni, amelyek -pontosítást várnak. Jobb ezt megelôzni.) - -
    • Ha tudsz készíteni egy listát a -mirror-jaidról, akkor tedd azt közzé. -Mellékeljél egy mirror-okra vonatkozó -szerzôi jogi dokumentumot. Ha ismered az update -menetrendjüket, azt is célszerû -közzétenni. - -
    • Gondoskodj arról, hogy a népszerû (és -nagymennyiségû) információnak -elegendô sávszélesség jusson. - -
    • Használd a filenév konvenciókat: .txt ascii -szövegnek; .html vagy .htm HTML-nek; .ps Postscript-nek; .pdf +
    • Legyen tiszta mindenki számára, hogy mi +másolható és mi nem. + +
    • Ismertesd, hogy az adott site-on ill. szervezetnél milyen +anyagok állnak rendelkezésre. Az általános +irányelvek legyenek világosak. + +
    • Az információk, külösen a README-k, +legyenek naprakészek. A README-ket sima ASCII +file-ként szolgáltasd. (Fordító +megjegyzése: Ha a README-k és a FAQ-k nagyon +elütnek a valóságtól, akkor rengeteg +egyforma kérdést fogsz kapni, amelyek +pontosítást várnak. Jobb ezt megelôzni.) + +
    • Ha tudsz készíteni egy listát a +mirror-jaidról, akkor tedd azt közzé. +Mellékeljél egy mirror-okra vonatkozó +szerzôi jogi dokumentumot. Ha ismered az update +menetrendjüket, azt is célszerû +közzétenni. + +
    • Gondoskodj arról, hogy a népszerû (és +nagymennyiségû) információnak +elegendô sávszélesség jusson. + +
    • Használd a filenév konvenciókat: .txt ascii +szövegnek; .html vagy .htm HTML-nek; .ps Postscript-nek; .pdf Portable Document Format-nek; .sgml vagy .sgm SGML-nek; .exe -nem-Unix végrehajtható file-oknak stb. - -
    • Letöltésre szánt file-ok nevének -elsô nyolc karakterét próbáld meg -különbözôvé tenni. -(Fordító megjegyzése: Lesznek (lehet, nem -kevesen), akik DOS alól kívánják -használni rendszeredet. Ez a rendszer csak 8 karaktert -képes megkülönböztetni a file -nevében.) - -
    • Amikor információt szolgáltatsz, akkor legyen -abban valami egyedi. Kerüld el az olyan -szolgáltatás indítását, ami csak -más szolgáltatókra mutat. (Fordító -megjegyzése: Olyan homepage-ek amik csak linkekbôl -állnak, általában értelmetlenek. Ezeket egy -keresôgép legtöbbször ki tudja váltani. +nem-Unix végrehajtható file-oknak stb. + +
    • Letöltésre szánt file-ok nevének +elsô nyolc karakterét próbáld meg +különbözôvé tenni. +(Fordító megjegyzése: Lesznek (lehet, nem +kevesen), akik DOS alól kívánják +használni rendszeredet. Ez a rendszer csak 8 karaktert +képes megkülönböztetni a file +nevében.) + +
    • Amikor információt szolgáltatsz, akkor legyen +abban valami egyedi. Kerüld el az olyan +szolgáltatás indítását, ami csak +más szolgáltatókra mutat. (Fordító +megjegyzése: Olyan homepage-ek amik csak linkekbôl +állnak, általában értelmetlenek. Ezeket egy +keresôgép legtöbbször ki tudja váltani. ) -
    • Ne mutass más site-okra mielôtt engedélyt -kérnél. (Fordító megjegyzése: Ez -mostanra kicsit elavult, de még mindig nem árthat -informálni a túloldalt) +
    • Ne mutass más site-okra mielôtt engedélyt +kérnél. (Fordító megjegyzése: Ez +mostanra kicsit elavult, de még mindig nem árthat +informálni a túloldalt) -
    • Vésd jól az eszedbe, hogy egy -szolgáltatás nemcsak design-t és -megvalósítás-t jelent. Hanem karbantartást +
    • Vésd jól az eszedbe, hogy egy +szolgáltatás nemcsak design-t és +megvalósítás-t jelent. Hanem karbantartást is. -
    • Az általad szolgáltatott anyagoknak meg kell -felelniük a támogató szervezet szabályainak. - -
    • Teszteld alkalmazásaidat a szoftverek minél -szélesebb skálájával. Nem -feltételezheted, hogy minden oké, ha csak egy klienssel -ellenôriztél. Viszont fel kell tételezned, hogy lesz, -aki egyszerûbb lehetôségekkel és klienssel -rendelkezik csak - tehát ne tervezd olyanra a -szolgáltatásodat, hogy csak grafikus -terminálról (GUI) lehessen használni. - -
    • Legyen egységes nézôpontja és -külleme az információdnak. - -
    • Az exportkorlátozások mások és -mások minden országban. Ismerned kell ezek -következményeit is, ha szolgáltatni akarsz. - -
    • Mondd el a felhasználóidnak, hogy mit akarsz tenni -a tôlük összegyûjtött -információval, mint pl. a WWW-rôl adott -véleményük. Figyelmeztetned kell az embereket, ha -közzé akarsz tenni ebbôl bármit, -akárcsak passzívan, a többi -felhasználó számára -láthatóan. - -
    • A felhasználói információ -szolgáltatásokra (mint pl. egy homepage) -vonatkozó szabályaid legyenek ismertek! -
    - +
  • Az általad szolgáltatott anyagoknak meg kell +felelniük a támogató szervezet szabályainak. + +
  • Teszteld alkalmazásaidat a szoftverek minél +szélesebb skálájával. Nem +feltételezheted, hogy minden oké, ha csak egy klienssel +ellenôriztél. Viszont fel kell tételezned, hogy lesz, +aki egyszerûbb lehetôségekkel és klienssel +rendelkezik csak - tehát ne tervezd olyanra a +szolgáltatásodat, hogy csak grafikus +terminálról (GUI) lehessen használni. + +
  • Legyen egységes nézôpontja és +külleme az információdnak. + +
  • Az exportkorlátozások mások és +mások minden országban. Ismerned kell ezek +következményeit is, ha szolgáltatni akarsz. + +
  • Mondd el a felhasználóidnak, hogy mit akarsz tenni +a tôlük összegyûjtött +információval, mint pl. a WWW-rôl adott +véleményük. Figyelmeztetned kell az embereket, ha +közzé akarsz tenni ebbôl bármit, +akárcsak passzívan, a többi +felhasználó számára +láthatóan. + +
  • A felhasználói információ +szolgáltatásokra (mint pl. egy homepage) +vonatkozó szabályaid legyenek ismertek! +
  • -5.0 Válogatott bibliográfia -

    +5.0 Válogatott bibliográfia + -Ez a dokumentum nagyobbrészt ezeken a mûveken alapul. -Ami konkrétan ezekben a mûvekben nem -található meg, az az IETF-RUN munkacsoport -tapasztalatán alapul. +Ez a dokumentum nagyobbrészt ezeken a mûveken alapul. +Ami konkrétan ezekben a mûvekben nem +található meg, az az IETF-RUN munkacsoport +tapasztalatán alapul.
      -
    1. Angell, D., and B. Heslop, "The Elements of E-mail Style", New York: Addison-Wesley, 1994. -
    2. "Answers to Frequently Asked Questions about Usenet"
      -Original author: jerry@eagle.UUCP (Jerry Schwarz)
      -Maintained by: netannounce@deshaw.com (Mark Moraes)
      -Archive-name: usenet-faq/part1
      - +
    3. "Answers to Frequently Asked Questions about Usenet"
      +Original author: jerry@eagle.UUCP (Jerry Schwarz)
      +Maintained by: netannounce@deshaw.com (Mark Moraes)
      +Archive-name: usenet-faq/part1
    4. Cerf, V., "Guidelines for Conduct on and Use of Internet", http://www.isoc.org/proceedings/conduct/cerf-Aug-draft.html -
    5. Dern, D., "The Internet Guide for New Users", New York:McGraw-Hill, 1994. -
    6. "Emily Postnews Answers Your Questions on Netiquette"
      -Original author: brad@looking.on.ca (Brad Templeton)
      -Maintained by: netannounce@deshaw.com (Mark Moraes)
      -Archive-name: emily-postnews/part1
      - +
    7. "Emily Postnews Answers Your Questions on Netiquette"
      +Original author: brad@looking.on.ca (Brad Templeton)
      +Maintained by: netannounce@deshaw.com (Mark Moraes)
      +Archive-name: emily-postnews/part1
    8. Gaffin, A., "Everybody's Guide to the Internet", Cambridge, Mass., MIT Press, 1994. @@ -1387,9 +1413,8 @@

      of Representatives gopher, gopher.house.gov:70/OF-1%3a208%3aInternet %20Etiquette -
    9. How to find the right place to post (FAQ)by -buglady@bronze.lcs.mit.edu (Aliza R. Panitz)
      +buglady@bronze.lcs.mit.edu (Aliza R. Panitz)
      Archive-name: finding-groups/general
    10. Hambridge, S., and J. Sedayao, "Horses and Barn Doors: Evolution @@ -1399,7 +1424,6 @@

      ftp://ftp.intel.com/pub/papers/horses.ps or ftp://ftp.intel.com/pub/papers/horses.ascii -
    11. Heslop, B., and D. Angell, "The Instant Internet guide: Hands-on Global Networking", Reading, Mass., Addison-Wesley, 1994. @@ -1407,22 +1431,20 @@

    12. Horwitz, S., "Internet Etiquette Tips", ftp://ftp.temple.edu/pub/info/help-net/netiquette.infohn -
    13. Internet Activities Board, "Ethics and the Internet", RFC 1087, IAB, January 1989. ftp://ds.internic.net/rfc/rfc1087.txt -
    14. Kehoe, B., "Zen and the Art of the Internet: A Beginner's Guide", -a Netikett info található szétszórva a -mûben. 3rd ed. Englewood Cliffs, NJ., Prentice-Hall, 1994. +a Netikett info található szétszórva a +mûben. 3rd ed. Englewood Cliffs, NJ., Prentice-Hall, 1994.
    15. Kochmer, J., "Internet Passport: NorthWestNet's Guide to our World Online", 4th ed. Bellevue, Wash., NorthWestNet, Northwest Academic Computing Consortium, 1993.
    16. Krol, Ed, "The Whole Internet: User's Guide and Catalog", -Sebastopol, CA, O'Reilly & Associates, 1992. +Sebastopol, CA, O'Reilly & Associates, 1992.
    17. Lane, E. and C. Summerhill, "Internet Primer for Information Professionals: a basic guide to Internet networking technology", @@ -1436,42 +1458,35 @@

      Intelligence Program, Scan No. 2109. March, 1993. gopher://gopher.well.sf.ca.us:70/00/Communications/surf-wild -
    18. Martin, J., "There's Gold in them thar Networks! or Searching for Treasure in all the Wrong Places", FYI 10, RFC 1402, January 1993. ftp://ds.internic.net/rfc/rfc1402.txt -
    19. Pioch, N., "A Short IRC Primer", Text conversion by Owe Rasmussen. Edition 1.1b, February 28, 1993. http://www.kei.com/irc/IRCprimer1.1.txt -
    20. Polly, J., "Surfing the Internet: an Introduction", Version 2.0.3. Revised May 15, 1993. - gopher://nysernet.org:70/00/ftp%20archives/pub/resources/guides/surfin g.2.0.3.txt ftp://ftp.nysernet.org/pub/resources/guides/surfing.2.0.3.txt - -
    21. "A Primer on How to Work With the Usenet Community"
      -Original author: chuq@apple.com (Chuq Von Rospach)
      -Maintained by: netannounce@deshaw.com (Mark Moraes)
      +
    22. "A Primer on How to Work With the Usenet Community"
      +Original author: chuq@apple.com (Chuq Von Rospach)
      +Maintained by: netannounce@deshaw.com (Mark Moraes)
      Archive-name: usenet-primer/part1
    23. Rinaldi, A., "The Net: User Guidelines and Netiquette", September 3, 1992. http://www.fau.edu/rinaldi/net/index.htm - -
    24. "Rules for posting to Usenet"
      -Original author: spaf@cs.purdue.edu (Gene Spafford)
      -Maintained by: netannounce@deshaw.com (Mark Moraes)
      -Archive-name: posting-rules/part1
      - +
    25. "Rules for posting to Usenet"
      +Original author: spaf@cs.purdue.edu (Gene Spafford)
      +Maintained by: netannounce@deshaw.com (Mark Moraes)
      +Archive-name: posting-rules/part1
    26. Shea, V., "Netiquette", San Francisco: Albion Books, 1994?.
    27. Strangelove, M., with A. Bosley, "How to Advertise on the @@ -1479,32 +1494,26 @@

    28. Tenant, R., "Internet Basics", ERIC Clearinghouse of Information Resources, EDO-IR-92-7. September, 1992. - gopher://nic.merit.edu:7043/00/introducing.the.internet/internet.basics.er ic-digest - + gopher://vega.lib.ncsu.edu:70/00/library/reference/guides/tennet -
    29. Wiggins, R., "The Internet for everyone: a guide for users and providers", New York, McGraw-Hill, 1995. -
    - +

    -6.0 Biztonsági meggondolások -

    - +6.0 Biztonsági meggondolások +

    -Ebben a dokumentumban nem tárgyalunk biztonsági -témákat. +Ebben a dokumentumban nem tárgyalunk biztonsági +témákat.

    -7.0 A szerzô címe -

    - +7.0 A szerzô címe +
     Sally Hambridge
     Intel Corporation
    @@ -1516,16 +1525,16 @@ 

    Fax: 408-765-3679 EMail: sallyh@ludwig.sc.intel.com -A fordító címe: +A fordító címe: -Ha Magyarországról írsz: +Ha Magyarországról írsz: -Négyesi Károly +Négyesi Károly Budapest P.f.503 1462 -Külföldrôl: +Külföldrôl: Charlie Negyesi HUNGARY @@ -1535,8 +1544,8 @@

    Email: chx@cs.elte.hu -Köszönetet mondok a javításokért -mindenkinek, de elsôsorban a következôknek: Varga -Ákos Endre (Hamster), Liling Zoltán, Kiss Gábor, -Márton Ádám. -

    \ No newline at end of file +Köszönetet mondok a javításokért +mindenkinek, de elsôsorban a következôknek: Varga +Ãkos Endre (Hamster), Liling Zoltán, Kiss Gábor, +Márton Ãdám. +

    \ No newline at end of file diff --git a/templates/hu/listinfo.html b/templates/hu/listinfo.html index c1b702df..959d7917 100644 --- a/templates/hu/listinfo.html +++ b/templates/hu/listinfo.html @@ -1,143 +1,206 @@ + - - - <MM-List-Name> Információs lapja - - -

    - - - - - - - - - - - - - - - - - - - + + + + + +

    - -- - -
    -

      -

    - listáról - - - -
    -

    -

    Ha a korábbi leveleket, hozzászólásokat szeretnéd látni, látogasd meg - a(z) archívumot . - -

    -
    - lista használata -
    - Ha a listára szeretnél írni, akkor erre a címre küldd hozzászólásodat: - . + + +<mm-list-name> Információs lapja</mm-list-name> + + +

    + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + -- + +
    +

      +

    + listáról + + + +
    +

    +

    Ha a korábbi leveleket, hozzászólásokat szeretnéd látni, látogasd meg + a(z) archívumot . + +

    +
    + lista használata +
    + Ha a listára szeretnél írni, akkor erre a címre küldd hozzászólásodat: + . -

    Itt lejjebb feliratkozhatsz a listára, vagy módosíthatod - a listatagsági paramétereidet. -

    - Feliratkozás a(z) listára -
    -

    - Töltsd ki az alábbi űrlapot a(z) listára való feliratkozáshoz. - -

    Feliratkozás előtt kérlek olvasd el a listaszerver - illemtanát! +

    Itt lejjebb feliratkozhatsz a listára, vagy módosíthatod + a listatagsági paramétereidet. +

    +Feliratkozás a(z) listára +
    +

    + Töltsd ki az alábbi űrlapot a(z) listára való feliratkozáshoz. + +

    Feliratkozás előtt kérlek olvasd el a listaszerver + illemtanát!

      - - - - - - - - - - - - - - - - - -
      E-mail címed: -  
      Neved (opcionális): 
      Meg kell adnod egy jelszót. - A jelszó csak enyhe biztonságot nyújt, de megelőzheted vele, - hogy mások tudtod nélkül módosítsák listatagsági beállításaidat! - Ne használj olyan jelszavakat amiket máshol használsz, - mivel havonta egyszer emlékeztetőül sima szöveges formában elküldésre fog kerülni. + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - -
      E-mail címed: + 
      Neved (opcionális): 
      Meg kell adnod egy jelszót. + A jelszó csak enyhe biztonságot nyújt, de megelőzheted vele, + hogy mások tudtod nélkül módosítsák listatagsági beállításaidat! + Ne használj olyan jelszavakat amiket máshol használsz, + mivel havonta egyszer emlékeztetőül sima szöveges formában elküldésre fog kerülni. -

      Ha nem adsz meg jelszót, akkor a Mailman automatikusan létrehoz egyet, és - feliratkozásod megerősítése után elküldi neked. Személyes beállításaid szerkesztésénél - természetesen bármikor e-mailben kikérheted a jelszavadat. - -
      -
      A jelszó: 
      A jelszó még egyszer: 
      Milyen nyelven szeretnéd a listát használni? 
      Szeretnéd a listát digest formában kapni, naponta 1-2 levélben? Nem - Igen -
      -
      -
      - - -
      - - lista tagjai -
      - - - -

      - - - -

      - - - +

      Ha nem adsz meg jelszót, akkor a Mailman automatikusan létrehoz egyet, és + feliratkozásod megerősítése után elküldi neked. Személyes beállításaid szerkesztésénél + természetesen bármikor e-mailben kikérheted a jelszavadat. + + +
    A jelszó: 
    A jelszó még egyszer: 
    Milyen nyelven szeretnéd a listát használni? 
    Szeretnéd a listát digest formában kapni, naponta 1-2 levélben? Nem + Igen +
    +
    +
    + + +

    + + lista tagjai +
    + + + +

    + + + +

    + +

    + diff --git a/templates/hu/options.html b/templates/hu/options.html index 36b06406..ccdb5b0d 100644 --- a/templates/hu/options.html +++ b/templates/hu/options.html @@ -1,308 +1,341 @@ - - <MM-Presentable-User> beállításai a(z) <MM-List-Name> listán - - - - - -
    - - tag beállításai a(z) levelezőlistán -
    + +<mm-presentable-user> beállításai a(z) <mm-list-name> listán + </mm-list-name></mm-presentable-user> + + + + +
    + + tag beállításai a(z) levelezőlistán +

    - - - - - +
    - tag feliratkozási státusza, - jelszava és beállításai a(z) levelezési listán. -
    - - - - -

    -

    + + + +
    + tag feliratkozási státusza, + jelszava és beállításai a(z) levelezési listán. +
    + + +

    +

    - - +

    - - - + +
    - - levelezőlistán használt címed módosítása -
    A listán található feliratkozási címedet tudod - itt megváltoztatni egy új megadásával. A változtatás csak akkor - hajtódik végre, ha az új címedre elküldött értesítést megerősíted. + + + - - - - - -
    + + levelezőlistán használt címed módosítása +
    A listán található feliratkozási címedet tudod + itt megváltoztatni egy új megadásával. A változtatás csak akkor + hajtódik végre, ha az új címedre elküldött értesítést megerősíted. -

    A megerősítési lehetőség múlva megszűnik. +

    A megerősítési lehetőség múlva megszűnik.

    Ha akarod, akkor megadhatod a teljes nevedet is - (pl. Tóth Béla). + (pl. Tóth Béla). -

    Ha a(z) gépen található összes listatagságod feliratkozási - címét az itt megadottra akarod változtatni, akkor jelöld be a - Állítsd mindenhol kapcsolót. +

    Ha a(z) gépen található összes listatagságod feliratkozási + címét az itt megadottra akarod változtatni, akkor jelöld be a + Ãllítsd mindenhol kapcsolót. -

    - - - - - - - -
    Új cím:
    Új cím még egyszer:
    -
    - - +
    Teljes név (nem kötelező +

    + + + + + + + +
    Új cím:
    Új cím még egyszer:
    +
    + + - - -
    Teljes név (nem kötelező megadni):
    -
    -

    Állítsd mindenhol

    - +
    + +

    +

    Ãllítsd mindenhol

    +

    - - - - - - - - + + + + %(textlink)s - diff --git a/templates/ia/archtocnombox.html b/templates/ia/archtocnombox.html index 8201934d..fcfa3c51 100644 --- a/templates/ia/archtocnombox.html +++ b/templates/ia/archtocnombox.html @@ -1,18 +1,82 @@ - - - Le archivos de %(listname)s - + + + +Le archivos de %(listname)s + %(meta)s - - -

    Le archivos de %(listname)s

    -

    + + +

    Le archivos de %(listname)s

    +

    Tu pote obtener plus informationes super iste lista.

    %(noarchive_msg)s %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/ia/article.html b/templates/ia/article.html index 3936028e..497629e6 100644 --- a/templates/ia/article.html +++ b/templates/ia/article.html @@ -1,12 +1,13 @@ - - - %(title)s - - - - - + +

    Le archivo %(listname)s

    +

    Nulle message ha ancora essite inviate al lista, ergo le archivos es currentemente vacue. Tu pote vider plus de informationes super iste lista.

    - - + + diff --git a/templates/ia/headfoot.html b/templates/ia/headfoot.html index 9964711e..f64d3b79 100644 --- a/templates/ia/headfoot.html +++ b/templates/ia/headfoot.html @@ -1,10 +1,73 @@ -Iste texto pote includer +Iste texto pote includer stringas in formato Python que es completate per un lista de attributos. Le lista de substitutiones permittite es:
      -
    • real_name - Le nomine "de facie" del lista; usualmente +
    • real_name - Le nomine "de facie" del lista; usualmente illo es le nomine del lista sin majusculas.
    • list_name - Le nomine que identifica le lista in le @@ -23,4 +86,4 @@
    • info - Le description complete del lista.
    • cgiext - Le extension addite al scripts CGI. -
    + diff --git a/templates/ia/listinfo.html b/templates/ia/listinfo.html index de848cd6..91fa6f1e 100644 --- a/templates/ia/listinfo.html +++ b/templates/ia/listinfo.html @@ -1,136 +1,197 @@ - - - - <MM-List-Name> Pagina de information - - - -

    -

    - listáról leiratkozás - További listatagságaid -
    - Jelöld be a leiratkozási szándékodat megerősítő kapcsolót - majd kattints a gombra. Fontos: A kattintás - után azonnal végrehajtódik a változtatás! + + + + - + +
    +

    + listáról leiratkozás +További listatagságaid +
    + Jelöld be a leiratkozási szándékodat megerősítő kapcsolót + majd kattints a gombra. Fontos: A kattintás + után azonnal végrehajtódik a változtatás!

    -

    - gépen található többi levelezőlista tagságodat - tekintheted meg a gombra kattintva. Használd akkor, ha azt - szeretnéd hogy a többi listán is ugyanezeket a beállításokat - tudd elvégezni. +

    + gépen található többi levelezőlista tagságodat + tekintheted meg a gombra kattintva. Használd akkor, ha azt + szeretnéd hogy a többi listán is ugyanezeket a beállításokat + tudd elvégezni.

    -

    -
    - - - - - +
    - listán a jelszavad -
    - -
    -

    Elfelejtetted a jelszavadat?

    -
    + + + - -
    + listán a jelszavad +
    + +
    +

    Elfelejtetted a jelszavadat?

    +
    Kattints a gombra, hogy emailben megkapd a jelszavadat. -

    -

    - -
    -
    - -
    -

    Jelszó megváltoztatása

    - - - - - - - - -
    Új jelszó:
    Új jelszó még egyszer:
    - - -

    Állítsd mindenhol -
    -
    - +

    +

    + +
    +

    + +
    +

    Jelszó megváltoztatása

    + + + + + + + + +
    Új jelszó:
    Új jelszó még egyszer:
    + +

    Ãllítsd mindenhol +
    +

    - - +
    - Beállításaid a(z) levelezőlistán -
    +
    +Beállításaid a(z) levelezőlistán +
    -

    -A jelenlegi beállítások pipával jelölve. - -

    Néhány beállításnál Állítsd mindenhol kapcsolót is lehet -választani. A kapcsoló bejelölésével minden olyan levelezőlista -tagságod beállítását meg tudod változtatni, amely a(z) gépen -működik. A Mutassd a többi feliratkozásomat gombra kattintva -megtekintheted, mely levelezőlistáknak vagy tagja ezen a gépen. +A jelenlegi beállítások pipával jelölve. +

    Néhány beállításnál Ãllítsd mindenhol kapcsolót is lehet +választani. A kapcsoló bejelölésével minden olyan levelezÅ‘lista +tagságod beállítását meg tudod változtatni, amely a(z) gépen +működik. A Mutassd a többi feliratkozásomat gombra kattintva +megtekintheted, mely levelezÅ‘listáknak vagy tagja ezen a gépen.

    - -
    - - Levélküldés be-/kikapcsolása

    - A listáról akkor kapod meg a leveleket, ha Bekapcsolva + + - +

    - - - +

    + - - - - - - + + + + + + +

    Azon a levelek fogadásáról melyek témája nem egyezik a + kívánatos, beállított témák egyikével sem a következő + beállítással lehet gondoskodni. Ha nincs egyetlen egy téma + sem megadva, akkor a levelezőlistára küldött összes levél + kézbesítve lesz. +

    + + Ez a beállítás csak akkor érvényesül, ha feljebb legalább egy + témát kiválasztottunk. A beállítás megmondja, hogy alapesetben + mi történjen azokkal a levelekkel, amelyek a választott témák + egyikével sem egyezik meg. Nem-et választva nem kerülnek + továbbításra azok a levelek, amelyek témája a beállított témák + egyikével sem egyezik. Ha az ilyen leveleket mégis is meg akarod kapni, + akkor Igen-t kell beállítani. - + - - - - - - - +

    Ha a listán személyre szabott levélküldés van beállítva és + kéred a levelek további példányának küldését, akkor minden + egyes példányban megtalálható lesz a X-Mailman-Copy: yes + fejléc. +

    +
    + +Levélküldés be-/kikapcsolása

    + A listáról akkor kapod meg a leveleket, ha Bekapcsolva van megadva. - Ha nem akarsz a listáról egy bizonyos ideig levelet kapni (mert pl. - szabadságra mész), akkor állítsd át Kikapcsoltra. - El ne felejtsd újra bekapcsolni a levélküldést, ha újra + Ha nem akarsz a listáról egy bizonyos ideig levelet kapni (mert pl. + szabadságra mész), akkor állítsd át Kikapcsoltra. + El ne felejtsd újra bekapcsolni a levélküldést, ha újra fogadni akarod a leveleket. -

    - Bekapcsolva
    - Kikapcsolva

    - Állítsd mindenhol -

    +Bekapcsolva
    +Kikapcsolva

    +Ãllítsd mindenhol +

    - Digest forma megadása

    - Digest formában a leveleket nem egyesével kapod, hanem egybefűzve - naponta egyszer. A digest forma lemondása után még egyszer utoljára +

    +Digest forma megadása

    + Digest formában a leveleket nem egyesével kapod, hanem egybefűzve + naponta egyszer. A digest forma lemondása után még egyszer utoljára digestben kapod meg a leveleket. -

    - Ki
    - Be -
    - MIME kódolású vagy sima digest levelek?

    - A levelezőprogramod lehet hogy támogatja, de lehet hogy nem a - MIME kódolású digest levelek olvasását. Általában a MIME digest az - elfogadott, de ha bármilyen problémád van az ilyen típusú - levelekkel, akkor válaszd a sima formát. -

    - MIME
    - Sima szöveg

    - Állítsd mindenhol -

    +Ki
    +Be +
    +MIME kódolású vagy sima digest levelek?

    + A levelezÅ‘programod lehet hogy támogatja, de lehet hogy nem a + MIME kódolású digest levelek olvasását. Ãltalában a MIME digest az + elfogadott, de ha bármilyen problémád van az ilyen típusú + levelekkel, akkor válaszd a sima formát. +

    +MIME
    +Sima szöveg

    +Ãllítsd mindenhol +

    - A listára küldött leveleidről kérsz másolatot?

    - Alapesetben minden listára küldött leveledről másolatot kapsz. - Ha nem akarsz róluk másolatot kapni, akkor itt Nem-et - állíts be. -

    - Nem
    - Igen -
    - A listára sikeresen elküldött leveleidről kérsz értesítést?

    -

    - Nem
    - Igen -
    - Kérsz a listához tartozó jelszavadról havonta emlékezetőt?

    - Havonta egyszer minden ezen a gépen működő levelezőlistához - tartozó jelszavad elküldésre kerül. Ezt a beállítást minden - listán egyedileg meg tudod változtatni, a Nem-mel - ki lehet kapcsolni a jelszó elküldést. Ha az összes listán - kikapcsolod a jelszó elküldést, akkor a gépről nem kerül - havi jelszó emlékeztető elküldésre. -

    - Nem
    - Igen

    - Állítsd mindenhol -

    +A listára küldött leveleidről kérsz másolatot?

    + Alapesetben minden listára küldött leveledről másolatot kapsz. + Ha nem akarsz róluk másolatot kapni, akkor itt Nem-et + állíts be. +

    +Nem
    +Igen +
    +A listára sikeresen elküldött leveleidről kérsz értesítést?

    +

    +Nem
    +Igen +
    +Kérsz a listához tartozó jelszavadról havonta emlékezetőt?

    + Havonta egyszer minden ezen a gépen működő levelezőlistához + tartozó jelszavad elküldésre kerül. Ezt a beállítást minden + listán egyedileg meg tudod változtatni, a Nem-mel + ki lehet kapcsolni a jelszó elküldést. Ha az összes listán + kikapcsolod a jelszó elküldést, akkor a gépről nem kerül + havi jelszó emlékeztető elküldésre. +

    +Nem
    +Igen

    +Ãllítsd mindenhol +

    +Elrejtsünk a feliratkozottak listájából?

    + Ha valaki lekéri a listatagok névsorát, akkor alapesetben + a te email címed is megjelenik (egy kicsit átalakítva, hogy + a spam gyűjtők ne tudják felhasználni). Ha szeretnéd + az email címedet a listatagok névsorából eltüntetni, akkor + állítsd át ezt a beállítást Igen-re. +

    +Nem
    +Igen +
    +Milyen nyelven szeretnéd a listát használni?

    +

    + +
    +Milyen témájú leveleket szeretnél fogadni?

    + Egy vagy több téma megadásával a levelezőlistán + megjelenő levelek közül csak azokat kapod meg, amelyek + a megadott témák legalább egyikének megfelelnek. Minden + más levél sorsáról a következő beállítás dönt. -

    - Elrejtsünk a feliratkozottak listájából?

    - Ha valaki lekéri a listatagok névsorát, akkor alapesetben - a te email címed is megjelenik (egy kicsit átalakítva, hogy - a spam gyűjtők ne tudják felhasználni). Ha szeretnéd - az email címedet a listatagok névsorából eltüntetni, akkor - állítsd át ezt a beállítást Igen-re. -

    - Nem
    - Igen -
    + +
    +Szeretnéd azokat a leveleket is megkapni, amelyek nem feleltek + meg a beállított témák egyikének sem?

    -

    - Milyen nyelven szeretnéd a listát használni?

    -

    - -
    - Milyen témájú leveleket szeretnél fogadni?

    - Egy vagy több téma megadásával a levelezőlistán - megjelenő levelek közül csak azokat kapod meg, amelyek - a megadott témák legalább egyikének megfelelnek. Minden - más levél sorsáról a következő beállítás dönt. +

    Ha nem lett a levelek szűrése feljebb beállítva, akkor a + levelezőlistára érkező összes levél kézbesítve lesz. +

    +Nem
    +Igen +
    +Szeretnéd, hogy a leveleket csak egy példányban kapd meg?

    -

    Azon a levelek fogadásáról melyek témája nem egyezik a - kívánatos, beállított témák egyikével sem a következő - beállítással lehet gondoskodni. Ha nincs egyetlen egy téma - sem megadva, akkor a levelezőlistára küldött összes levél - kézbesítve lesz. -

    - -
    - Szeretnéd azokat a leveleket is megkapni, amelyek nem feleltek - meg a beállított témák egyikének sem?

    + Ha a listára küldött levél To: vagy Cc: mezÅ‘jében + külön meg van adva a címed, akkor beállíthatod, hogy a lista + ne kézbesítse neked az ilyen leveleket. Igen-t választva a + lista nem fogja neked kézbesíteni az ilyen leveleket; míg + Nem-et választva a listáról is meg fogod kapni a levelet. - Ez a beállítás csak akkor érvényesül, ha feljebb legalább egy - témát kiválasztottunk. A beállítás megmondja, hogy alapesetben - mi történjen azokkal a levelekkel, amelyek a választott témák - egyikével sem egyezik meg. Nem-et választva nem kerülnek - továbbításra azok a levelek, amelyek témája a beállított témák - egyikével sem egyezik. Ha az ilyen leveleket mégis is meg akarod kapni, - akkor Igen-t kell beállítani. - -

    Ha nem lett a levelek szűrése feljebb beállítva, akkor a - levelezőlistára érkező összes levél kézbesítve lesz. -

    - Nem
    - Igen -
    - Szeretnéd, hogy a leveleket csak egy példányban kapd meg?

    - - Ha a listára küldött levél To: vagy Cc: mezőjében - külön meg van adva a címed, akkor beállíthatod, hogy a lista - ne kézbesítse neked az ilyen leveleket. Igen-t választva a - lista nem fogja neked kézbesíteni az ilyen leveleket; míg - Nem-et választva a listáról is meg fogod kapni a levelet. - -

    Ha a listán személyre szabott levélküldés van beállítva és - kéred a levelek további példányának küldését, akkor minden - egyes példányban megtalálható lesz a X-Mailman-Copy: yes - fejléc. -

    - Nem
    - Igen

    - Állítsd mindenhol -

    -
    -
    +Nem
    +Igen

    +Ãllítsd mindenhol +

    +
    +
    -

    - - - - + + +

    diff --git a/templates/hu/private.html b/templates/hu/private.html index 22f7f6d4..92e59598 100755 --- a/templates/hu/private.html +++ b/templates/hu/private.html @@ -1,54 +1,117 @@ - %(realname)s Privát archívum azonosítás +%(realname)s Privát archívum azonosítás - - -
    + + + %(message)s - - - - - - - - - - - - - - - -
    - %(realname)s Privát archívum - azonosítás -
    E-mail cím:
    Jelszó:
    -
    -

    Fontos: Ettől az oldaltól engedélyezned kell a sütik - fogadását a böngésződben, különben semmilyen adminisztrátori változtatás nem fog végrehajtódni. + + + + + + + + + + + + + + + +
    +%(realname)s Privát archívum + azonosítás +
    E-mail cím:
    Jelszó:
    +
    +

    Fontos: Ettől az oldaltól engedélyezned kell a sütik + fogadását a böngésződben, különben semmilyen adminisztrátori változtatás nem fog végrehajtódni. -

    A Mailman az adminisztrációs műveleteknél a folyamatos azonosításhoz sütiket használ, - így nem kell minden módosításhoz újraazonosítani magadat. A süti a böngésző bezárásával, - illetve az Egyéb adminisztrációs teendők a Kilépés-re kattintva (ezt - csak sikeres bejelentkezés után lehet elérni) érvényét veszti. +

    A Mailman az adminisztrációs műveleteknél a folyamatos azonosításhoz sütiket használ, + így nem kell minden módosításhoz újraazonosítani magadat. A süti a böngésző bezárásával, + illetve az Egyéb adminisztrációs teendők a Kilépés-re kattintva (ezt + csak sikeres bejelentkezés után lehet elérni) érvényét veszti.

    - - - - - - + + + +
    - Password Reminder -
    If you don't remember your password, enter your email address + + + + + + - - - - -
    +Password Reminder +
    If you don't remember your password, enter your email address above and click the Remind button and your password will be emailed to you.
    - +
    +

    diff --git a/templates/hu/roster.html b/templates/hu/roster.html index a713f439..7f445fdf 100644 --- a/templates/hu/roster.html +++ b/templates/hu/roster.html @@ -1,51 +1,112 @@ - - - <MM-List-Name> Tagok listája - - - -

    - - - - - - - - - - - - - - - -
    - - Tagok listája -
    -

    -

    - -

    Kattints a címedre, hogy a beállításaidat tartalmazó oldalra - juss.
    (Zárójelben a listáról levelet jelenleg nem fogadók.) -

    -
    -
    - - lista nem-digest tagjai: -
    -
    -
    - - lista digest tagjai: -
    -
    -

    -

    -

    -

    - - - + + +<mm-list-name> Tagok listája</mm-list-name> + + +

    + + + + + + + + + + + + + + + +
    + + Tagok listája +
    +

    +

    +

    Kattints a címedre, hogy a beállításaidat tartalmazó oldalra + juss.
    (Zárójelben a listáról levelet jelenleg nem fogadók.) +

    +
    +
    + + lista nem-digest tagjai: +
    +
    +
    + + lista digest tagjai: +
    +
    +

    +

    +

    +

    + +

    + diff --git a/templates/hu/subscribe.html b/templates/hu/subscribe.html index 8ea67ec9..9c2b4e02 100644 --- a/templates/hu/subscribe.html +++ b/templates/hu/subscribe.html @@ -1,10 +1,73 @@ -<MM-List-Name> Feliratokozások - +<mm-list-name> Feliratokozások</mm-list-name> + -

    Feliratkozások

    - - - +

    Feliratkozások

    + + + diff --git a/templates/ia/admindbdetails.html b/templates/ia/admindbdetails.html index 13abed84..b2f8dbcf 100644 --- a/templates/ia/admindbdetails.html +++ b/templates/ia/admindbdetails.html @@ -1,4 +1,67 @@ -Requestas administrative es monstrate in duo modos, in un +Requestas administrative es monstrate in duo modos, in un pagina summario e in un pagina detalios. Le pagina summario contine requestas de (dis)abonamento pendente, e messages attendente le approbation tue, gruppate per adresse de @@ -28,8 +91,7 @@ requesta sin notificar le persona qui lo demandava. Isto es le action que on deberea generalmente exequer pro le casos de spam. - - +

    Pro messages retenite, activa le option Conservar si tu vole salveguardar un copia del message pro le administrator del sito. Isto es utile pro messages abusive que tu vole abandonar, ma que tu debe conservar pro @@ -61,3 +123,4 @@ pro tote le requestas administrative que tu ha decidite.

    Retornar al pagina summario. +

    \ No newline at end of file diff --git a/templates/ia/admindbpreamble.html b/templates/ia/admindbpreamble.html index 7d5b22dd..398b2f39 100644 --- a/templates/ia/admindbpreamble.html +++ b/templates/ia/admindbpreamble.html @@ -1,4 +1,67 @@ -Iste pagina contine un subcollection del messages al lista +Iste pagina contine un subcollection del messages al lista %(listname)s que attende le approbation tue. Actualmente illo monstra %(description)s @@ -8,3 +71,4 @@

    Tu pote etiam vider un summariode tote le requestas attendente. +

    \ No newline at end of file diff --git a/templates/ia/admindbsummary.html b/templates/ia/admindbsummary.html index 9c14ddad..5b180091 100644 --- a/templates/ia/admindbsummary.html +++ b/templates/ia/admindbsummary.html @@ -1,4 +1,67 @@ -Iste pagina contine un summario del collection del requestas +Iste pagina contine un summario del collection del requestas administrative que require tu approbation pro le lista %(listname)s. @@ -12,3 +75,4 @@

    Tu pote etiam vider le detaliosde tote le messages attendente. +

    \ No newline at end of file diff --git a/templates/ia/admlogin.html b/templates/ia/admlogin.html index 05e9085a..dfdf6702 100755 --- a/templates/ia/admlogin.html +++ b/templates/ia/admlogin.html @@ -1,30 +1,91 @@ - Authentication pro %(who)s de %(listname)s - - - -
    +Authentication pro %(who)s de %(listname)s + + + + %(message)s - - - - - - - - - - - -
    - Authentication pro %(who)s - de %(listname)s -
    Contrasigno de %(who)s pro le lista:
    -
    -

    Importante: De iste puncto in + + + + + + + + + + + +
    +Authentication pro %(who)s + de %(listname)s +
    Contrasigno de %(who)s pro le lista:
    +
    +

    Importante: De iste puncto in avante, tu debe haber le cookies activate in tu navigator o tu non potera exequer alcun operation. @@ -35,6 +96,6 @@ per cliccar sur le ligamine Clauder session in le section Altere activitates administrative (que tu videra post tu entrata in le systema). -

    +

    diff --git a/templates/ia/archidxentry.html b/templates/ia/archidxentry.html index f9bb57aa..5c620b20 100644 --- a/templates/ia/archidxentry.html +++ b/templates/ia/archidxentry.html @@ -1,4 +1,68 @@ -
  • %(subject)s -  -%(author)s - +
  • %(subject)s +  +%(author)s + +
  • \ No newline at end of file diff --git a/templates/ia/archidxfoot.html b/templates/ia/archidxfoot.html index 5ed62cdb..a24817cb 100644 --- a/templates/ia/archidxfoot.html +++ b/templates/ia/archidxfoot.html @@ -1,21 +1,85 @@ - -

    - Data del ultime message: - %(lastdate)s
    - Archiviate: %(archivedate)s -

    -

      -
    • Messages ordinate per: + +

      +Data del ultime message: +%(lastdate)s
      +Archiviate: %(archivedate)s +

      +

      -

      -


      - Iste archivo es generate per +
    +

    +


    +Iste archivo es generate per Pipermail %(version)s. - - + + +

    \ No newline at end of file diff --git a/templates/ia/archidxhead.html b/templates/ia/archidxhead.html index 4e223daa..5f1efc3b 100644 --- a/templates/ia/archidxhead.html +++ b/templates/ia/archidxhead.html @@ -1,15 +1,79 @@ - - - Le archivo %(listname)s %(archive)s per %(archtype)s - + + + +Le archivo %(listname)s %(archive)s per %(archtype)s + %(encoding)s - - - -

    %(archive)s Archivos per %(archtype)s

    -
      -
    • Messages ordinate per: + + + +

      %(archive)s Archivos per %(archtype)s

      + -

      Initio: %(firstdate)s
      - Fin: %(lastdate)s
      - Messages: %(size)s

      -

        +
      +

      Initio: %(firstdate)s
      +Fin: %(lastdate)s
      +Messages: %(size)s

      +

        +

      \ No newline at end of file diff --git a/templates/ia/archlistend.html b/templates/ia/archlistend.html index 9bc052dd..2e1191b0 100644 --- a/templates/ia/archlistend.html +++ b/templates/ia/archlistend.html @@ -1 +1,64 @@ -
    + diff --git a/templates/ia/archliststart.html b/templates/ia/archliststart.html index 678af071..a35b710a 100644 --- a/templates/ia/archliststart.html +++ b/templates/ia/archliststart.html @@ -1,4 +1,70 @@ - - - - +
    ArchivoVide per:Version discargabile
    + + + + + +
    ArchivoVide per:Version discargabile
    diff --git a/templates/ia/archtoc.html b/templates/ia/archtoc.html index c849a958..1a1e96a2 100644 --- a/templates/ia/archtoc.html +++ b/templates/ia/archtoc.html @@ -1,13 +1,77 @@ - - - Le archivos de %(listname)s - + + + +Le archivos de %(listname)s + %(meta)s - - -

    Le archivos de %(listname)s

    -

    + + +

    Le archivos de %(listname)s

    +

    Tu pote obtener plus informationes super iste lista o tu pote discargar tote le archivo (%(size)s). @@ -16,5 +80,5 @@

    Le archivos de %(listname)s

    %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/ia/archtocentry.html b/templates/ia/archtocentry.html index a712ab7e..6d76eb64 100644 --- a/templates/ia/archtocentry.html +++ b/templates/ia/archtocentry.html @@ -1,12 +1,74 @@ - -
    %(archivelabel)s: - [ Thema ] - [ Subjecto ] - [ Author ] - [ Data ] -
    %(archivelabel)s: +[ Thema ] +[ Subjecto ] +[ Author ] +[ Data ] +
    - - - - - - - - - - - - - - - - - - + + + + + +

    - -- - -
    -

      -

    - A proposito de - - - -
    -

    -

    Pro vider le collection de messages anteriormente inviate al lista, visita le - archivos de . - -

    -
    - Como usar -
    + + + +<mm-list-name> Pagina de information</mm-list-name> + + +

    + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + +
    + -- + +
    +

      +

    +A proposito de + + + +
    +

    +

    Pro vider le collection de messages anteriormente inviate al lista, visita le + archivos de . + +

    +
    +Como usar +
    Pro inviar un message a tote le membros del lista, invia lo a - . + .

    Tu pote abonar te al lista o cambiar su abonamento existente in le sectiones infra. -

    - Como abonar te a -
    -

    - Abona te a per plenar le formulario sequente. - -

      - - - - - - - - - - - - + + + + + + - - - - - -
      Tu adresse electronic: -  
      Tu nomine (optional): 
      Tu pote indicar un contrasigno private infra. Isto da solmente un securitate minimal ma debe impedir que alteres face cambiamentos in tu abonamento. Non usa un contrasigno existente pois que illo occasionalmente essera reinviate a te in texto clar. +

      +Como abonar te a +
      +

      + Abona te a per plenar le formulario sequente. + +

        + + + + + + + + + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - - - -
        Tu adresse electronic: + 
        Tu nomine (optional): 
        Tu pote indicar un contrasigno private infra. Isto da solmente un securitate minimal ma debe impedir que alteres face cambiamentos in tu abonamento. Non usa un contrasigno existente pois que illo occasionalmente essera reinviate a te in texto clar. -

        Si tu non scribe un contrasigno, illo essera automaticamente generate pro te, e inviate a te, un vice que tu ha confirmate tu abonamento. Tu pote sempre demandar un nove invio de tu contrasigno, quando tu redige tu optiones personal. - -
        -
        Elige un contrasigno: 
        Repete le contrasigno pro confirmar: 
        Qual lingua prefere tu que le sito parla?  
        Prefere tu reciper tote le messages del die combinate in un digesto quotidian? +

        Si tu non scribe un contrasigno, illo essera automaticamente generate pro te, e inviate a te, un vice que tu ha confirmate tu abonamento. Tu pote sempre demandar un nove invio de tu contrasigno, quando tu redige tu optiones personal. + + +
        Elige un contrasigno: 
        Repete le contrasigno pro confirmar: 
        Qual lingua prefere tu que le sito parla?  
        Prefere tu reciper tote le messages del die combinate in un digesto quotidian? No - Si -
        -
        -
        - -
      -
      - - Abonatos -
      - - - -

      - - - -

      - - - +
    No + Si +
    +
    +
    + + +

    + + Abonatos +
    + + + +

    + + + +

    + +

    + diff --git a/templates/ia/options.html b/templates/ia/options.html index 6732ca7f..7201310a 100644 --- a/templates/ia/options.html +++ b/templates/ia/options.html @@ -1,42 +1,102 @@ - - Configuration de abonamento de <MM-Presentable-User> a <MM-List-Name> - - - - - -
    - - Configuration del abonamento de al lista de diffusion - -
    + + Configuration de abonamento de <mm-presentable-user> a <mm-list-name> +</mm-list-name></mm-presentable-user> + + + + +
    + + Configuration del abonamento de al lista de diffusion + +

    - - - - - +
    - Stato, contrasigno e optiones del abonamento de - al lista de diffusion . -
    - - - - -

    -

    + + + +
    + Stato, contrasigno e optiones del abonamento de + al lista de diffusion . +
    + + +

    +

    - - +

    - - - + +
    - - Cambiamento de tu informationes de abonamento a -
    Tu pote cambiar le adresse con le qual tu es abonate + + + - - - - -
    + +Cambiamento de tu informationes de abonamento a +
    Tu pote cambiar le adresse con le qual tu es abonate al lista de diffusion per scriber le nove adresse in le campos infra. Nota que un message de confirmation essera inviate al nove adresse, e le cambiamento debe esser confirmate ante que illo es @@ -51,204 +111,183 @@ listas al quales tu es abonate in , marca le quadrato Cambiar globalmente. -
    - - - - - + -
    Nove adresse:
    Repete pro +

    + + + + + - - -
    Nove adresse:
    Repete pro confirmar:
    -
    - - + +
    Tu nomine +
    +
    + + - - -
    Tu nomine (optional):
    -
    -

    Cambiar globalmente

    - +
    + +

    +

    Cambiar globalmente

    +

    - - - - - - - - + + + + %(textlink)s - diff --git a/templates/it/archtocnombox.html b/templates/it/archtocnombox.html index 02fc8677..76c1e2d9 100644 --- a/templates/it/archtocnombox.html +++ b/templates/it/archtocnombox.html @@ -1,18 +1,82 @@ - - - Gli archivi di %(listname)s - + + + +Gli archivi di %(listname)s + %(meta)s - - -

    Gli archivi di %(listname)s

    -

    - Maggiori informazioni su questa lista. + + +

    Gli archivi di %(listname)s

    +

    +Maggiori informazioni su questa lista.

    %(noarchive_msg)s %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/it/article.html b/templates/it/article.html index c0dd4389..03ee6498 100644 --- a/templates/it/article.html +++ b/templates/it/article.html @@ -1,12 +1,13 @@ - - - %(title)s - - - - - + +

    Gli archivi della lista %(listname)s

    +

    Non sono stati ancora inviati messaggi a questa lista e gli archivi sono ancora vuoti. Puoi avere altre informazioni su questa lista.

    - - + + diff --git a/templates/it/headfoot.html b/templates/it/headfoot.html index ba2bdb90..4b01ca60 100644 --- a/templates/it/headfoot.html +++ b/templates/it/headfoot.html @@ -1,14 +1,76 @@ -Questo testo può includere Stringhe +Questo testo può includere Stringhe di formato Python che saranno sostituite automaticamente dai valori degli attributi della lista. L'elenco degli attributi permessi -è: +è:
      -
    • real_name - Il nome `di facciata' della - lista; di solito è il nome della lista con lettere maiuscole. +
    • real_name - Il nome `di facciata' della + lista; di solito è il nome della lista con lettere maiuscole.
    • list_name - Il nome con cui la lista - è identificata negli URL, dove maiuscole e minuscole sono + è identificata negli URL, dove maiuscole e minuscole sono importanti.
    • host_name - Il nome FQDN dell'host sul @@ -27,4 +89,4 @@
    • cgiext - Estensione aggiunta ai nomi degli script CGI -
    + diff --git a/templates/it/listinfo.html b/templates/it/listinfo.html index 51e59272..064f1c8f 100644 --- a/templates/it/listinfo.html +++ b/templates/it/listinfo.html @@ -1,156 +1,217 @@ - - - Pagina di informazioni della lista <MM-List-Name> - - - -

    -

    - Disabonar te de - Tu altere abonamentos in -
    + + + + - + +
    +

    +Disabonar te de +Tu altere abonamentos in +
    Marca le quadrato de confirmation e preme iste button pro disabonar te de iste lista de diffusion. Attention: Iste action essera exequite immediatemente!

    -

    +

    Tu pote vider un lista de tote le altere listas de diffusion in al quales tu es abonate. Usa isto si tu vole extender le mesme cambiamentos de abonamento a iste altere abonamentos.

    -

    -
    - - - - - +
    - Tu contrasigno pro -
    - -
    -

    Tu oblidava tu contrasigno?

    -
    + + + - -
    +Tu contrasigno pro +
    + +
    +

    Tu oblidava tu contrasigno?

    +
    Clicca iste button pro facer que tu contrasigno sia inviate a tu adresse de abonamento. -

    -

    - -
    -
    - -
    -

    Cambia tu contrasigno

    - - - - - - - - -
    Nove - contrasigno:
    De nove pro - confirmar:
    - - -

    Cambiar globalmente. -
    -
    - +

    +

    + +
    +

    + +
    +

    Cambia tu contrasigno

    + + + + + + + + +
    Nove + contrasigno:
    De nove pro + confirmar:
    + +

    Cambiar globalmente. +
    +

    - - +
    - Tu optiones de abonamento a -
    +
    +Tu optiones de abonamento a +
    -

    Le valores actual es marcate. -

    Nota que alcun optiones ha un quadrato Definir globalmente. Si tu activa iste option, le cambiamentos essera facite pro cata lista de diffusion al qual tu es abonate in . Clicca super Listar mi altere abonamentos supra pro vider le altere listas de diffusion al quales tu es abonate.

    - -
    - - Livration de email

    + + - +

    - - - + +

    - - - - + - - + - - + - - - - + + - - + - - + - - - +

    +
    + +Livration de email

    Mitte iste option a Active pro reciper messages inviate a iste lista de diffusion. Mitte lo a Inactive si tu vole restar abonate, ma desira suspender le reception de messages durante un tempore (p.ex. tu va partir in un viage de ferias). Si tu disactiva le livration de e-mail, non oblida reactivar lo quando tu retornara; illo non se reactivara automaticamente. -

    - Active
    - Inactive

    - Cambiar globalmente -

    +Active
    +Inactive

    +Cambiar globalmente +

    - Definir modo digesto

    +

    +Definir modo digesto

    Si tu activa le modo digesto, tu recipera le messages combinate in digestos (usualmente un per die, ma possibilemente plus in listas de grande activitate), in vice de cata un a parte quando illo es inviate. Si le modo digesto es cambiate de active a inactive, tu pote reciper un ultime digesto. -

    - Inactive
    - Active -
    - Reciper digestos in formato MIME o texto pur?

    +

    +Inactive
    +Active +
    +Reciper digestos in formato MIME o texto pur?

    Tu lector de email pote supportar o non supportar digestos MIME. Generalmente, le digestos MIME es preferite, ma si tu ha problemas in leger los, selige digestos in texto pur. -

    - MIME
    - Texto pur

    - Cambiar globalmente -

    +MIME
    +Texto pur

    +Cambiar globalmente +

    - Reciper tu proprie messages al lista?

    +

    +Reciper tu proprie messages al lista?

    Normalmente tu recipe un copia de cata message que tu invia al lista. Si tu non vole reciper iste copia, mitte iste option a No. -

    - No
    - Si -
    - Reciper un message de confirmation de reception quando +

    +No
    +Si +
    +Reciper un message de confirmation de reception quando tu invia un message al lista?

    -

    - No
    - Si -
    - Reciper un rememoration de tu contrasigno?

    +

    +No
    +Si +
    +Reciper un rememoration de tu contrasigno?

    Un vice cata mense, tu recipera un e-mail continente tu contrasignos pro cata lista de iste servitor al qual tu es abonate. Tu pote decider isto differentemente pro cata lista si tu selige No pro iste option. Si tu disactiva le rememorationes del contrasignos pro tote le listas al quales tu es abonate, nulle rememoration te essera inviate. -

    - No
    - Si

    - Cambiar globalmente -

    - Celar te del lista del abonatos?

    +

    +No
    +Si

    +Cambiar globalmente +

    +Celar te del lista del abonatos?

    Normalmente quando alicuno vide le lista del abonatos, tu adresse de e-mail es monstrate (in un forma obscurate pro frustrar su collection per spammatores). Si tu non vole que tu adresse es monstrate, selige Si pro iste option. -

    - No
    - Si -
    - Qual lingua prefere tu?

    -

    - -
    - Qual categorias de interesse vole tu sequer?

    +

    +No
    +Si +
    +Qual lingua prefere tu?

    +

    + +
    +Qual categorias de interesse vole tu sequer?

    Con le selection de un o plure categorias, tu pote filtrar le traffico del lista e reciper solmente un subcollection del messages. Si un message corresponde a un del categorias que @@ -258,12 +297,11 @@

    Cambia tu contrasigno

    regula de livration depende del option infra. Si tu non selige alcun categoria de interesse, tu recipera tote le messages inviate al lista. -
    - -
    - Esque tu vole reciper messages sin categoria de interesse?

    +

    + +
    +Esque tu vole reciper messages sin categoria de interesse?

    Iste option ha effecto solmente si tu ha seligite al minus un categoria in le quadro precedente. @@ -274,13 +312,12 @@

    Cambia tu contrasigno

    Si nulle categoria ha essite seligite in le option precedente, tu recipera tote le messages inviate al lista. -

    - No
    - Si -
    - Evitar copias duple de messages?

    +

    +No
    +Si +
    +Evitar copias duple de messages?

    Quando tu adresse de e-mail figura in le campos To: (A:) o Cc: de un message inviate al lista, tu pote @@ -292,21 +329,17 @@

    Cambia tu contrasigno

    e tu opta pro reciper copias, omne copia habera un campo X-Mailman-Copy: yes in le capite del message. -
    - No
    - Si

    - Definir globalmente -

    -
    -
    +No
    +Si

    +Definir globalmente +

    +
    +
    -

    - - - - + + +

    diff --git a/templates/ia/private.html b/templates/ia/private.html index 074bb7f2..b4fb4fca 100755 --- a/templates/ia/private.html +++ b/templates/ia/private.html @@ -1,34 +1,95 @@ - Authentication pro le archivos private de %(realname)s - - - -
    +Authentication pro le archivos private de %(realname)s + + + + %(message)s - - - - - - - - - - - - - - - -
    - Authentication pro le archivos private de - %(realname)s -
    Adresse de e-mail:
    Contrasigno:
    -
    -

    Importante: De iste puncto in + + + + + + + + + + + + + + + +
    +Authentication pro le archivos private de + %(realname)s +
    Adresse de e-mail:
    Contrasigno:
    +
    +

    Importante: De iste puncto in avante, tu debe haber le cookies activate in tu navigator o tu debera re-authenticar te ante cata operation. @@ -40,21 +101,21 @@ Altere activitates administrative (que tu videra post tu entrata in le systema).

    - - - - - - + + + +
    - Password Reminder -
    Contrasigno oblidate? Scribe tu adresse de e-mail hic supra + + + + + + - - - - -
    +Password Reminder +
    Contrasigno oblidate? Scribe tu adresse de e-mail hic supra e clicca sur le button Rememorar, e tu contrasigno te essera invite.
    - +
    +

    diff --git a/templates/ia/roster.html b/templates/ia/roster.html index f6cbecaf..a8f6e663 100644 --- a/templates/ia/roster.html +++ b/templates/ia/roster.html @@ -1,52 +1,111 @@ - - - Abonatos a <MM-List-Name> - - - - -

    - - - - - - - - - - - - - - - -
    - Abonatos a - -
    - -

    -

    - -

    Clicca sur tu adresse pro visitar le pagina de tu optiones de abonamento.
    - (Le adresses inter parentheses ha le livration disactivate.)

    -
    -
    - - abonatos a messages individual de : -
    -
    -
    - - abonatos a digestos de : -
    -
    -

    -

    -

    -

    - - - + + +Abonatos a <mm-list-name></mm-list-name> + + +

    + + + + + + + + + + + + + + + +
    +Abonatos a + +
    +

    +

    +

    Clicca sur tu adresse pro visitar le pagina de tu optiones de abonamento.
    +(Le adresses inter parentheses ha le livration disactivate.)

    +
    +
    + + abonatos a messages individual de : +
    +
    +
    + + abonatos a digestos de : +
    +
    +

    +

    +

    +

    + +

    + diff --git a/templates/ia/subscribe.html b/templates/ia/subscribe.html index fd668692..d03e9c10 100644 --- a/templates/ia/subscribe.html +++ b/templates/ia/subscribe.html @@ -1,9 +1,72 @@ -<MM-List-Name> Resultatos del subscription +<mm-list-name> Resultatos del subscription</mm-list-name> -

    Resultatos del subscription

    - - - +

    Resultatos del subscription

    + + + diff --git a/templates/it/admindbdetails.html b/templates/it/admindbdetails.html index 7d3961fc..cef0f971 100644 --- a/templates/it/admindbdetails.html +++ b/templates/it/admindbdetails.html @@ -1,9 +1,72 @@ -Le richieste amministrative vengono visualizzate in due modalità: +Le richieste amministrative vengono visualizzate in due modalità: in una pagina riassuntiva, oppure in una pagina dettagliata. La pagina riassuntiva contiene le richieste pendenti di iscrizione e di cancellazione e i messaggi in attesa di approvazione raggruppati per indirizzo del mittente. La pagina -dettagliata presenta una visualizzazione più dettagliata di +dettagliata presenta una visualizzazione più dettagliata di ciascun messaggio, compresi tutte le intestazioni i una parte del testo del messaggio. @@ -11,7 +74,7 @@
    • Rimando la decisione -- Rimanda la decisione ad un momento successivo. Non viene intrapresa alcuna azione in merito a questa richiesta -amministrativa pendente, ma per i messaggi in attesa è comunque +amministrativa pendente, ma per i messaggi in attesa è comunque possibile inoltrarli o conservarli (vedi sotto).
    • Approvo -- Approva il messaggio inviandolo alla lista. Se si tratta @@ -26,39 +89,38 @@
    • Scarta -- Scarta il messaggio senza inviare alcuna informazione al mittente. Se si tratta di una richiesta di iscrizione o cancellazione, la richiesta viene semplicemente scartata senza inviare alcune informazione a chi -ha presentato la richiesta. Di solito, questa azione è riservata ai -messaggi di spam.
    - +ha presentato la richiesta. Di solito, questa azione è riservata ai +messaggi di spam.

    Per i messaggi mendenti, abilita l'opzione Conserva se desideri -salvare una copia del messaggio per l'amministratore del sito. Questa è +salvare una copia del messaggio per l'amministratore del sito. Questa è un'opzione utile per i messaggi di abuso che vuoi scartare, ma dei quali vuoi tenere una copia per usi futuri.

    Abilita l'opzione Inoltra a ed indica l'indirizzo a cui inoltrare i -messaggi per inviare i messaggi a qualcuno che non è iscritto alla +messaggi per inviare i messaggi a qualcuno che non è iscritto alla lista. Se desideri modificare un messaggio prima che sia inviato alla lista devi inoltrarlo a te (o ad un altro gestore della lista) e scartare il messaggio originale. Quando ricevi il messaggio apporta le modifiche che desideri ed invia di nuovo il messaggio alla lista includendo nella prima riga del messaggio il campo Approved: con la password della lista. In questo caso, la Netiquette richiede che nel messaggio inviato venga inclusa una -nota che indica che il testo del messaggio è stato modificato. +nota che indica che il testo del messaggio è stato modificato. -

    Se il mittente del messaggio è un membro sottoposto -a moderazione, è possibile rimuovere lo stato di -moderazione. Questa opzione è utile nel caso in +

    Se il mittente del messaggio è un membro sottoposto +a moderazione, è possibile rimuovere lo stato di +moderazione. Questa opzione è utile nel caso in cui una lista sia configurata per sottoporre a moderazione tutti i nuovi iscritti alla lista e tu hai deciso che quell'iscritto -è una persona di cui ci si può fidare e che -può inviare messaggi in lista senza bisogno di approvazione. +è una persona di cui ci si può fidare e che +può inviare messaggi in lista senza bisogno di approvazione. -

    Se il mittente non ` un membro della lista, puoi aggiungere -il suo indirizzo al filtro mittenti. Il filtro mittenti è +

    Se il mittente non ` un membro della lista, puoi aggiungere +il suo indirizzo al filtro mittenti. Il filtro mittenti è descritto nella pagina dei filtri sul mittente -delle opzioni di privacy, e pu` essere una di queste +delle opzioni di privacy, e pu` essere una di queste opzioni: Accetta, Sospendi, Rigetta o -Scarta. Questa opzione non ` disponibile -se l'indirizzo del mittente è già presente in una +Scarta. Questa opzione non ` disponibile +se l'indirizzo del mittente è già presente in una delle liste dei filtri di mittenti.

    Quando hai finito, clicca sul pulsante Manda tutto che si @@ -67,3 +129,4 @@ preso una decisione.

    Ritorna alla pagina riassuntiva. +

    \ No newline at end of file diff --git a/templates/it/admindbpreamble.html b/templates/it/admindbpreamble.html index adb44486..2f574645 100644 --- a/templates/it/admindbpreamble.html +++ b/templates/it/admindbpreamble.html @@ -1,11 +1,75 @@ -Questa pagina contiene tutti i messaggi sospesi che richiedono +Questa pagina contiene tutti i messaggi sospesi che richiedono la tua approvazione per la lista %(listname)s. Attualmente mostra %(description)s

    Per ogni richiesta amministrativa, scegli l'azione da eseguire e clicca su Manda Tutto quando hai finito. Istruzioni -più dettagliate sono disponibili qui. +più dettagliate sono disponibili qui.

    Puoi anche vedere un sommario di tutte le richieste pendenti. +

    \ No newline at end of file diff --git a/templates/it/admindbsummary.html b/templates/it/admindbsummary.html index 3e940e6a..ece2c813 100644 --- a/templates/it/admindbsummary.html +++ b/templates/it/admindbsummary.html @@ -1,4 +1,67 @@ -Questa pagina contiene la lista delle richieste amministrative +Questa pagina contiene la lista delle richieste amministrative che attualmente attendono la tua autorizzazione per la lista %(listname)s. Innanzitutto, troverai la lista delle iscrizioni in attesa e delle @@ -7,8 +70,9 @@

    Per ogni richiesta amministrativa, per piacere seleziona come procedere e clicca sul pulsante Manda tutto quando hai concluso. -Sono inoltre disponibili altre e più +Sono inoltre disponibili altre e più dettagliate istruzioni.

    Puoi inoltre vedere i dettagli di tutti i messaggi trattenuti. +

    \ No newline at end of file diff --git a/templates/it/admlogin.html b/templates/it/admlogin.html index 835c59dc..c1fe90a2 100755 --- a/templates/it/admlogin.html +++ b/templates/it/admlogin.html @@ -2,39 +2,100 @@ Autenticazione per %(who)s di %(listname)s - - -
    + + + %(message)s - - - - - - - - - - - -
    - Autenticazione per %(who)s di - %(listname)s -
    Password di lista per %(who)s:
    -
    -

    Importante: Da questo momento in + + + + + + + + + + + +
    +Autenticazione per %(who)s di + %(listname)s +
    Password di lista per %(who)s:
    +
    +

    Importante: Da questo momento in avanti devi avere i cookie abilitati nel browser o non potrai effettuare nessuna operazione.

    I cookie di sessione sono usati dal pannello di controllo di mailman in modo che tu non sia costretto a ri-autenticarti ad - ogni operazione. Questo cookie sarà cancellato + ogni operazione. Questo cookie sarà cancellato automaticamente all'uscita dal tuo browser oppure puoi chiedere la cancellazione esplicita cliccando il link Termina sessione nella - sezione Altre attività amministrative (che vedrai + sezione Altre attività amministrative (che vedrai quando ti sarai correttamente autenticato). -

    +

    diff --git a/templates/it/archidxentry.html b/templates/it/archidxentry.html index f9bb57aa..5c620b20 100644 --- a/templates/it/archidxentry.html +++ b/templates/it/archidxentry.html @@ -1,4 +1,68 @@ -
  • %(subject)s -  -%(author)s - +
  • %(subject)s +  +%(author)s + +
  • \ No newline at end of file diff --git a/templates/it/archidxfoot.html b/templates/it/archidxfoot.html index 3fe4e5c8..47af3b87 100644 --- a/templates/it/archidxfoot.html +++ b/templates/it/archidxfoot.html @@ -1,21 +1,85 @@ - -

    - Data dell'ultimo messaggio: - %(lastdate)s
    - Archiviato il: %(archivedate)s -

    -

      -
    • Messaggi ordinati per: + +

      +Data dell'ultimo messaggio: +%(lastdate)s
      +Archiviato il: %(archivedate)s +

      +

      -

      -


      - Questo archivio è stato generato da +
    +

    +


    +Questo archivio è stato generato da Pipermail %(version)s. - - + + +

    \ No newline at end of file diff --git a/templates/it/archidxhead.html b/templates/it/archidxhead.html index c818e3f6..c6e71ee9 100644 --- a/templates/it/archidxhead.html +++ b/templates/it/archidxhead.html @@ -1,15 +1,79 @@ - - - L'archivio %(archive)s della lista %(listname)s ordinato per %(archtype)s - + + + +L'archivio %(archive)s della lista %(listname)s ordinato per %(archtype)s + %(encoding)s - - - -

    Archivio %(archive)s ordinato per %(archtype)s

    -
      -
    • Messaggi ordinati per: + + + +

      Archivio %(archive)s ordinato per %(archtype)s

      + -

      Data inizio: %(firstdate)s
      - Data fine: %(lastdate)s
      - Messaggi: %(size)s

      -

        +
      +

      Data inizio: %(firstdate)s
      +Data fine: %(lastdate)s
      +Messaggi: %(size)s

      +

        +

      \ No newline at end of file diff --git a/templates/it/archlistend.html b/templates/it/archlistend.html index 9bc052dd..2e1191b0 100644 --- a/templates/it/archlistend.html +++ b/templates/it/archlistend.html @@ -1 +1,64 @@ -
    + diff --git a/templates/it/archliststart.html b/templates/it/archliststart.html index 35cf4cdf..a7a46b39 100644 --- a/templates/it/archliststart.html +++ b/templates/it/archliststart.html @@ -1,4 +1,68 @@ - - - - +
    ArchivioVedi ordinato per:Versione scaricabile
    + + + +
    ArchivioVedi ordinato per:Versione scaricabile
    \ No newline at end of file diff --git a/templates/it/archtoc.html b/templates/it/archtoc.html index cf3bcc1f..c378f61a 100644 --- a/templates/it/archtoc.html +++ b/templates/it/archtoc.html @@ -1,13 +1,77 @@ - - - Gli archivi della lista %(listname)s - + + + +Gli archivi della lista %(listname)s + %(meta)s - - -

    Gli archivi della lista %(listname)s

    -

    + + +

    Gli archivi della lista %(listname)s

    +

    Puoi ottenere ulteriori informazioni su questa lista o puoi scaricare l'intero archivio grezzo (%(size)s). @@ -16,5 +80,5 @@

    Gli archivi della lista %(listname)s

    %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/it/archtocentry.html b/templates/it/archtocentry.html index 0b8ce080..781dbd53 100644 --- a/templates/it/archtocentry.html +++ b/templates/it/archtocentry.html @@ -1,12 +1,74 @@ - -
    %(archivelabel)s: - [ Thread ] - [ Soggetto ] - [ Autore ] - [ Data ] -
    %(archivelabel)s: +[ Thread ] +[ Soggetto ] +[ Autore ] +[ Data ] +
    - - - - - - - - - - - - - - - - - - + + + + + +

    - -- - -
    -

      -

    - Dettagli su - - - - -
    -

    -

    Per consultare la raccolta dei messaggi precedentemente - inviati alla lista, visita gli Archivi della lista . - -

    -
    - Uso di -
    + + +Pagina di informazioni della lista <mm-list-name></mm-list-name> + + +

    + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + +
    + -- + +
    +

      +

    +Dettagli su + + + + +
    +

    +

    Per consultare la raccolta dei messaggi precedentemente + inviati alla lista, visita gli Archivi della lista . + +

    +
    +Uso di +
    Per inviare un messaggio a tutti gli iscritti della lista, scrivi all'indirizzo - . + .

    Puoi iscriverti alla lista, o cambiare la tua iscrizione corrente, nella sezione sottostante. -

    - Iscrizione a -
    -

    - Iscriviti a completando il seguente modulo. - -

      - - - - - - - - - - - - - - + + + + + + - - - - - -
      Il tuo indirizzo email: 
      Il tuo nome (opzionale): 
      Devi inserire una password - personale. Questo ti garantirà solo una minima - sicurezza, ma impedirà ad altri di fare scherzi con +

      +Iscrizione a +
      +

      + Iscriviti a completando il seguente modulo. + +

        + + + + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + - - - - - + + + + + - - - - - - - - -
        Il tuo indirizzo email: 
        Il tuo nome (opzionale): 
        Devi inserire una password + personale. Questo ti garantirà solo una minima + sicurezza, ma impedirà ad altri di fare scherzi con la tua iscrizione. Non usare una password importante visto che ti - verrà periodicamente ricordata via email con testo + verrà periodicamente ricordata via email con testo non cifrato. -

        Se non vuoi scegliere, lascia in bianco la password ed - una verrà automaticamente generata per te e spedita +

        Se non vuoi scegliere, lascia in bianco la password ed + una verrà automaticamente generata per te e spedita al tuo indirizzo quando avrai confermato l'iscrizione. Puoi chiedere in ogni momento che ti venga rispedita la mail contenente la password, semplicemente premendo un pulsante nel pannello delle tue opzioni personali. - -
        -
        Scegli una password: 
        Conferma la password: 
        + + +
        Scegli una password: 
        Conferma la password: 
        In quale lingua preferisci vedere i tuoi messaggi? -  
        Vuoi ricevere i messaggi della lista raggruppati in un +  
        Vuoi ricevere i messaggi della lista raggruppati in un digest giornaliero? No - Sì -
        -
        -
        - -
      -
      - - Iscritti a -
      - - - -

      - - - -

      - - - +
    No + Sì +
    +
    +
    + + +

    + +Iscritti a +
    + + + +

    + + + +

    + +

    + diff --git a/templates/it/options.html b/templates/it/options.html index 99a581a3..61c04b47 100644 --- a/templates/it/options.html +++ b/templates/it/options.html @@ -1,46 +1,106 @@ - - Configurazione utente <MM-Presentable-User> per la lista <MM-List-Name> - - - - -
    - - Configurazione utente per la - lista -
    + +Configurazione utente <mm-presentable-user> per la lista <mm-list-name></mm-list-name></mm-presentable-user> + + + + +
    + + Configurazione utente per la + lista +

    - - - - - +
    - Stato di iscrizione dell'utente , - password e opzioni per la lista . -
    - - - - -

    -

    + + + +
    + Stato di iscrizione dell'utente , + password e opzioni per la lista . +
    + + +

    +

    - - +

    - - - + +
    - - Cambiare le informazioni della tua iscrizione a -
    Puoi cambiare l'indirizzo con il quale sei + + + - - - - - -
    + +Cambiare le informazioni della tua iscrizione a +
    Puoi cambiare l'indirizzo con il quale sei iscritto alla lista, semplicemente inserendo il nuovo - indirizzo nel campo sottostante. Nota che ti verrà + indirizzo nel campo sottostante. Nota che ti verrà inviato, al nuovo indirizzo, un messaggio con la richiesta di - conferma. La variazione sarà completata solo dopo il + conferma. La variazione sarà completata solo dopo il ricevimento della tua risposta.

    Le richieste di conferma scadono dopo circa . @@ -52,210 +112,189 @@ liste alle quali sei iscritto su , metti la spunta nel quadratino Cambia globalmente. -

    - - - - - - - -
    Nuovo indirizzo:
    Di nuovo per - conferma:
    -
    - - +
    Il tuo nome +

    + + + + + + + +
    Nuovo indirizzo:
    Di nuovo per + conferma:
    +
    + + - - -
    Il tuo nome (opzionale):
    -
    -

    Cambia globalmente

    - +
    + +

    +

    Cambia globalmente

    +

    - - - - - - - - + + + + %(textlink)s - diff --git a/templates/ja/archtocnombox.html b/templates/ja/archtocnombox.html index 72b4425c..4a07679a 100644 --- a/templates/ja/archtocnombox.html +++ b/templates/ja/archtocnombox.html @@ -1,18 +1,82 @@ - - - %(listname)s Êݸ½ñ¸Ë - + + + +%(listname)s ä¿å­˜æ›¸åº« + %(meta)s - - -

    %(listname)s Êݸ½ñ¸Ë

    -

    - ¥ê¥¹¥È¤ÎÁí¹ç°ÆÆâ. + + +

    %(listname)s ä¿å­˜æ›¸åº«

    +

    +リストã®ç·åˆæ¡ˆå†….

    %(noarchive_msg)s %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/ja/article.html b/templates/ja/article.html index 51450c79..c52de833 100644 --- a/templates/ja/article.html +++ b/templates/ja/article.html @@ -1,13 +1,14 @@ - - - %(title)s - - - - - - + +

    %(listname)s ä¿å­˜æ›¸åº«

    +

    + ã¾ã ã“ã®ãƒ¡ãƒ¼ãƒªãƒ³ã‚°ãƒªã‚¹ãƒˆã«æŠ•稿ã•れã¦ã„ãªã„ãŸã‚〠+ ä¿å­˜æ›¸åº«ã¯ç©ºã§ã™ã€‚ + リストã«é–¢ã™ã‚‹æƒ…å ±ã¯ã“ã¡ã‚‰ + ã«ã‚りã¾ã™ã€‚

    - - + + diff --git a/templates/ja/headfoot.html b/templates/ja/headfoot.html index 2262d6c1..f19f8deb 100644 --- a/templates/ja/headfoot.html +++ b/templates/ja/headfoot.html @@ -1,4 +1,67 @@ -���Υƥ����Ȥˤϡ��ꥹ�Ȥ�°�����֤��������� +���Υƥ����Ȥˤϡ��ꥹ�Ȥ�°�����֤��������� Python�ե����ޥå�ʸ�������������뤳�Ȥ��Ǥ��ޤ��� ( @@ -6,7 +69,7 @@ ǻȤ����ִ�ʸ����ϰʲ����̤�Ǥ���
      -
    • real_name - �ꥹ�ȤΡ֤��줤�ʡ�̾��; +
    • real_name - �ꥹ�ȤΡ֤��줤�ʡ�̾��; ���̤���ʸ���Ⱦ�ʸ���òº®¤ï¿½ï¿½ï¿½É½ï¿½ï¿½ï¿½ï¿½ï¿½ì¤¿ï¿½ê¥¹ï¿½È¤ï¿½Ì¾ï¿½ï¿½ï¿½Ë¤Ê¤ï¿½Þ¤ï¿½ï¿½ï¿½
    • list_name - URL ����ǥꥹ�Ȥò¼¨¤ï¿½Ì¾ï¿½ï¿½ï¿½ï¿½ @@ -25,4 +88,4 @@
    • info - ���Υ᡼��󥰥ꥹ�Ȥ�Ĺ��������
    • cgiext - CGI������ץȤγ�ĥ�ҡ� -
    + diff --git a/templates/ja/listinfo.html b/templates/ja/listinfo.html index 59b357f6..21e98991 100644 --- a/templates/ja/listinfo.html +++ b/templates/ja/listinfo.html @@ -1,148 +1,209 @@ - - - - <MM-List-Name> Ú¡ - - - - -

    -

    - Cancellarsi da - Le altre tue iscrizioni su -
    + + + + - + +
    +

    +Cancellarsi da +Le altre tue iscrizioni su +
    Metti la spunta nel quadratino di conferma e clicca questo pulsante per cancellarti da questa mailing list. - Attenzione: L'azione avrà effetto + Attenzione: L'azione avrà effetto immediato!

    -

    +

    Puoi vedere un elenco delle altre mailing list alle quali sei iscritto su questo stesso server . Usa questo se vuoi che le modifiche che stai apportando abbiano effetto su tutte le altre iscrizioni.

    -

    -
    - - - - - +
    - La tua password per -
    - -
    -

    Dimenticata la password?

    -
    + + + - -
    +La tua password per +
    + +
    +

    Dimenticata la password?

    +
    Clicca questo pulsante se vuoi che la tua password ti sia spedita al tuo indirizzo. -

    -

    - -
    -
    - -
    -

    Cambiare la password

    - - - - - - - - -
    Nuova - password:
    Di nuovo per - conferma:
    - - -

    Cambia globalmente. -
    -
    - +

    +

    + +
    +

    + +
    +

    Cambiare la password

    + + + + + + + + +
    Nuova + password:
    Di nuovo per + conferma:
    + +

    Cambia globalmente. +
    +

    - - +
    - Le tue opzioni per la lista -
    +
    +Le tue opzioni per la lista +
    -

    I valori attualmente scelti sono spuntati. -

    Nota che alcune di queste opzioni hanno un quadratino Globalmente. Spuntando questo quadratino le tue variazioni avranno effetto su tutte le liste a cui sei iscritto su questo stesso server . Clicca su Elenca le altre mie iscrizioni qui sopra per vedere a quali altre liste sei iscritto.

    - -
    - - Consegna della posta

    - Abilita questa opzione per ricevere i messaggi che + + - +

    - - - + +

    - - - - + - - + - - + - - - - + + - - + - - + - - - +

    +
    + +Consegna della posta

    +Abilita questa opzione per ricevere i messaggi che sono inviati a questa lista. Disabilitala se non vuoi ricevere i messaggi per qualche tempo (ad esempio se stai andando in vacanza). Se disabiliti l'invio, non dimenticare che dopo - le vacanze dovrai tornare qui e riabilitarlo; non sarà + le vacanze dovrai tornare qui e riabilitarlo; non sarà riabilitato automaticamente. -

    - Abilitato
    - Disabilitato

    - Globalmente -

    +Abilitato
    +Disabilitato

    +Globalmente +

    - Modo Digest

    +

    +Modo Digest

    Se abiliti il modo digest, riceverai i messaggi in un unico pacchetto giornaliero (normalmente ne viene inviato uno al - giorno ma potrebbero essere più frequenti su liste + giorno ma potrebbero essere più frequenti su liste particolarmente trafficate) invece di uno per volta. Se era abilitato e lo stai disabilitando, potresti ricevere ancora un ultimo digest prima che le cose vadano a regime. -

    - Off
    - On -
    - Digest in formato MIME o Testo?

    +

    +Off
    +On +
    +Digest in formato MIME o Testo?

    Il tuo programma di posta potrebbe supportare i digest in - formato MIME oppure no. In generale il formato MIME è + formato MIME oppure no. In generale il formato MIME è preferibile ma se hai problemi scegli il formato testo. -

    - MIME
    - Testo

    - Globalmente -

    +MIME
    +Testo

    +Globalmente +

    - Vuoi ricevere una copia dei messaggi che mandi +
    +Vuoi ricevere una copia dei messaggi che mandi alla lista?

    Normalmente, riceverai una copia di ogni messaggio che manderai alla lista. Se non vuoi che questo succeda, metti questa opzione a No. -

    - No
    - Sì -
    - Vuoi ricevere un messaggio di conferma quando +

    +No
    +Sì +
    +Vuoi ricevere un messaggio di conferma quando mandi una mail alla lista?

    -

    - No
    - Sì -
    - Vuoi ricevere un promemoria contente la password per +

    +No
    +Sì +
    +Vuoi ricevere un promemoria contente la password per questa lista?

    Una volta al mese riceverai un messaggio contenente un promemoria per ogni lista a cui sei iscritto su questo server. Puoi disabilitare questa opzione lista per lista, scegliendo No. Se scegli di disabilitarla globalmente, allora non riceverai alcun promemoria mensile. -

    - No
    -

    - Globalmente -

    - Vuoi rimanere nascosto nell'elenco degli iscritti?

    +

    +No
    +Sì

    +Globalmente +

    +Vuoi rimanere nascosto nell'elenco degli iscritti?

    Quando qualcuno consulta l'elenco degli iscritti alla lista, il tuo indirizzo di posta viene normalmente mostrato (in un - modo alterato, così da rendere difficile il lavoro + modo alterato, così da rendere difficile il lavoro degli spammer). Se vuoi che il tuo indirizzo non compaia - in questi elenchi, scegli in questa opzione. -

    - No
    - Sì -
    - Che lingua preferisci?

    -

    - -
    - A quali categorie di argomenti sei interessato?

    - Selezionando uno o più argomenti puoi filtrare il + in questi elenchi, scegli Sì in questa opzione. +

    +No
    +Sì +
    +Che lingua preferisci?

    +

    + +
    +A quali categorie di argomenti sei interessato?

    + Selezionando uno o più argomenti puoi filtrare il traffico della lista e ricevere soltanto un sottoinsieme dei messaggi. Se un messaggio corrisponde ad uno degli argomenti che hai scelto allora lo riceverai, altrimenti no. @@ -264,56 +303,50 @@

    Cambiare la password

    la scelta dipende dalla prossima opzione. Se non scegli nessun argomento di interesse allora riceverai tutti i messaggi che saranno inviati alla lista. -
    - -
    - Vuoi ricevere i messaggi che non corrispondono a nessuna +

    + +
    +Vuoi ricevere i messaggi che non corrispondono a nessuna categoria?

    Normalmente questa opzione ha effetto soltanto se ti sei iscritto ad almeno una categoria di interesse tra quelle sopra elencate. - Quello che devi decidere è se i messaggi che non + Quello che devi decidere è se i messaggi che non corrispondono a nessuna categoria li vuoi ricevere o no.

    Se nella precedente opzione non hai scelto alcuna categoria di interesse, allora riceverai tutti i messaggi inviati alla lista. -

    - No
    - Sì -
    - Elimino i doppioni?

    +

    +No
    +Sì +
    +Elimino i doppioni?

    Quando sei indicato esplicitamente nel To: o nel Cc: di un messaggio che era stato inviato aalla lista, puoi scegliere di non ricevere la copia che la lista ti avrebbe inviato. - Scegli per evitare di ricevere copie dalla + Scegli Sì per evitare di ricevere copie dalla lista; scegli No per riceverle.

    Se la lista ha i messaggi personalizzati abilitati, - e tu hai deciso di ricevere le copie, ogni copia avrà una + e tu hai deciso di ricevere le copie, ogni copia avrà una intestazione X-Mailman-Copy: yes aggiunta. -

    - No
    - Yes

    - Globalmente -

    -
    -
    +No
    +Yes

    +Globalmente +

    +
    +
    -

    - - - - + + +

    diff --git a/templates/it/private.html b/templates/it/private.html index 5917d0af..5faf5f29 100755 --- a/templates/it/private.html +++ b/templates/it/private.html @@ -2,59 +2,120 @@ Autenticazione per gli archivi privati di %(realname)s - - -
    + + + %(message)s - - - + + + + + + + + + + + + +
    - + + + - - - - - - - - - - - - -
    + Autenticazione per gli archivi privati di %(realname)s - -
    Indirizzo email:
    Password:
    -
    -

    Importante:Da questo momento in + +

    Indirizzo email:
    Password:
    +
    +

    Importante:Da questo momento in avanti devi avere i cookie abilitati nel browser altrimenti dovrai identificarti di nuovo ad ogni operazione.

    I cookie di sessione sono usati dall'interfaccia amministrativa di mailman in modo che tu non sia costretto a identificarti di nuovo - ad ogni operazione. Questo cookie sarà cancellato + ad ogni operazione. Questo cookie sarà cancellato automaticamente all'uscita dal tuo browser oppure puoi chiedere la cancellazione esplicita visitando la tua pagina delle opzioni e cliccando il link Termina sessione.

    - - - - - - + + + +
    - Password Reminder -
    If you don't remember your password, enter your email address + + + + + + - - - - -
    +Password Reminder +
    If you don't remember your password, enter your email address above and click the Remind button and your password will be emailed to you.
    - +
    +

    diff --git a/templates/it/roster.html b/templates/it/roster.html index 1dae44c0..92ddf18f 100644 --- a/templates/it/roster.html +++ b/templates/it/roster.html @@ -1,52 +1,111 @@ - - - Iscritti a <MM-List-Name> - - - - -

    - - - - - - - - - - - - - - - -
    - Iscritti a - -
    - -

    -

    - -

    Clicca sul tuo indirizzo per visitare la pagina di opzioni.
    - (Gli indirizzi tra parentesi hanno l'invio disabilitato.)

    -
    -
    - - iscritti non digest di : -
    -
    -
    - - iscritti digest di : -
    -
    -

    -

    -

    -

    - - - + + +Iscritti a <mm-list-name></mm-list-name> + + +

    + + + + + + + + + + + + + + + +
    +Iscritti a + +
    +

    +

    +

    Clicca sul tuo indirizzo per visitare la pagina di opzioni.
    +(Gli indirizzi tra parentesi hanno l'invio disabilitato.)

    +
    +
    + + iscritti non digest di : +
    +
    +
    + + iscritti digest di : +
    +
    +

    +

    +

    +

    + +

    + diff --git a/templates/it/subscribe.html b/templates/it/subscribe.html index 21f71a4c..9e24b6c5 100644 --- a/templates/it/subscribe.html +++ b/templates/it/subscribe.html @@ -1,8 +1,71 @@ -<MM-List-Name> Risultato dell'iscrizione +<mm-list-name> Risultato dell'iscrizione</mm-list-name> -

    Risultato dell'iscrizione

    - - - +

    Risultato dell'iscrizione

    + + + diff --git a/templates/ja/admindbdetails.html b/templates/ja/admindbdetails.html index e69ec7c1..3d07491e 100644 --- a/templates/ja/admindbdetails.html +++ b/templates/ja/admindbdetails.html @@ -1,62 +1,125 @@ -´ÉÍý¿½ÀÁ¤Ë¤Ï¡¢ -Í×Ìó¥Ú¡¼¥¸¤È -¾ÜºÙ¥Ú¡¼¥¸¤È¤Î2¤Ä¤Îɽ¼¨¤ÎÊýË¡¤¬¤¢¤ê¤Þ¤¹¡£ -Í×Ìó¥Ú¡¼¥¸¤Ë¤Ï¡¢ÊÝα¤Ë¤Ê¤Ã¤Æ¤¤¤ëÆþ²ñ¤ÈÂà²ñ½èÍý¤Ë²Ã¤¨¡¢ -Á÷¿®¼Ô¤Î¥á¡¼¥ë¥¢¥É¥ì¥¹¤Ç¥°¥ë¡¼¥×ʬ¤±¤µ¤ì¤¿ÊÝαÅê¹Æ¤¬É½¼¨¤µ¤ì¤Þ¤¹¡£ -¾ÜºÙ¥Ú¡¼¥¸¤Ç¤Ï¡¢¤½¤ì¤¾¤ì¤ÎÊÝα¥á¡¼¥ë¤Î¾ÜºÙ¤Ä¤Þ¤ê¡¢ -¥á¡¼¥ë¤Î¥Ø¥Ã¥À¤ÎÁ´Éô¤ÈËÜʸ¤ÎÈ´½ñ¤­¤¬É½¼¨¤µ¤ì¤Þ¤¹¡£ +管ç†ç”³è«‹ã«ã¯ã€ +è¦ç´„ページ㨠+詳細ページã¨ã®2ã¤ã®è¡¨ç¤ºã®æ–¹æ³•ãŒã‚りã¾ã™ã€‚ +è¦ç´„ページã«ã¯ã€ä¿ç•™ã«ãªã£ã¦ã„る入会ã¨é€€ä¼šå‡¦ç†ã«åŠ ãˆã€ +é€ä¿¡è€…ã®ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ã§ã‚°ãƒ«ãƒ¼ãƒ—分ã‘ã•れãŸä¿ç•™æŠ•稿ãŒè¡¨ç¤ºã•れã¾ã™ã€‚ +詳細ページã§ã¯ã€ãれãžã‚Œã®ä¿ç•™ãƒ¡ãƒ¼ãƒ«ã®è©³ç´°ã¤ã¾ã‚Šã€ +メールã®ãƒ˜ãƒƒãƒ€ã®å…¨éƒ¨ã¨æœ¬æ–‡ã®æŠœæ›¸ããŒè¡¨ç¤ºã•れã¾ã™ã€‚ -

    ¤É¤Á¤é¤Î¥Ú¡¼¥¸¤Ë¤â¼¡¤Î½èÍý¥á¥Ë¥å¡¼¤¬¤¢¤ê¤Þ¤¹: +

    ã©ã¡ã‚‰ã®ãƒšãƒ¼ã‚¸ã«ã‚‚次ã®å‡¦ç†ãƒ¡ãƒ‹ãƒ¥ãƒ¼ãŒã‚りã¾ã™:

      -
    • ±ä´ü -- ·èÄê¤òÀè¤Ë±ä¤Ð¤¹¤¿¤á¤ËÊÝα¤·¤Þ¤¹¡£ - ¤³¤Î»þÅÀ¤Ç¤Ï´ÉÍý¿½ÀÁ¤Î½èÍý¤Ï¹Ô¤¤¤Þ¤»¤ó¤¬¡¢ - ÊÝᤵ¤ì¤Æ¤¤¤ë¥á¡¼¥ë¤ÎžÁ÷¤äÊݸ¤¬¤Ç¤­¤Þ¤¹(²¼¤ò»²¾È)¡£ - -
    • ¾µÇ§ -- Åê¹Æ¤ò¾µÇ§¤·¡¢¥ê¥¹¥È¤ËÇÛÁ÷¤·¤Þ¤¹¡£ - ÆþÂà²ñ¤Î¿½ÀÁ¤Ï¡¢²ñ°÷¸¢¤ÎÊѹ¹¤ò¾µÇ§¤·¤Þ¤¹¡£ - -
    • µñÈÝ -- Åê¹Æ¤òµñÈݤ·¡¢¤½¤Î¤³¤È¤òÁ÷¿®¼Ô¤ËÄÌÃΤ·¤Þ¤¹¡£ - ¤Þ¤¿¡¢¥ª¥ê¥¸¥Ê¥ë¤ÎÅê¹Æ¤ÏÇË´þ¤µ¤ì¤Þ¤¹¡£ÆþÂà²ñ¤Î¿½ÀÁ¤Ë´Ø¤·¤Æ¤Ï¡¢ - ²ñ°÷¸¢¤ÎÊѹ¹¤òµñÈݤ·¤Þ¤¹¡£¤É¤Á¤é¤Î¾ì¹ç¤â¡¢ - ¥Æ¥­¥¹¥È¥Ü¥Ã¥¯¥¹¤ËµñÈݤÎÍýͳ¤ò½ñ¤­²Ã¤¨¤ë¤È¤è¤¤¤Ç¤·¤ç¤¦¡£ - -
    • ÇË´þ -- Åê¹Æ¥á¡¼¥ë¤ò¼Î¤Æ¡¢µñÈݤÎÄÌÃΤâÁ÷¤ê¤Þ¤»¤ó¡£ - ÆþÂà²ñ¿½ÀÁ¤Ë´Ø¤·¤Æ¤Ï¡¢Ã±¤Ë¿½ÀÁ¤òÇË´þ¤·¡¢ - ¿½ÀÁ¤ò½Ð¤·¤¿¿Í¤Ø¤ÎÄÌÃΤϹԤ¤¤Þ¤»¤ó¡£ - ÌÂÏǥ᡼¥ë¤ËÂФ·¤Æ¤Ï¤³¤ì¤ò»È¤¦¤È¤è¤¤¤Ç¤·¤ç¤¦¡£ -
    - -

    ÊÝαÅê¹Æ¤Ë¤Ä¤¤¤Æ¡¢¥µ¥¤¥È´ÉÍý¼Ô¤Î¤¿¤á¤Ë¥á¡¼¥ë¤Î¥³¥Ô¡¼¤òÊݸ¤·¤¿¤¤¾ì¹ç¤Ï¡¢ -Êݸ¤ò¥Á¥§¥Ã¥¯¤·¤Æ¤¯¤À¤µ¤¤¡£ -¤³¤ì¤Ï¡¢¥ê¥¹¥È¤ò°­ÍѤ·¤è¤¦¤È¤¹¤ë¥á¡¼¥ë¤òÇË´þ¤·¤¿¤¤¤±¤ì¤É¤â¡¢ -¸å¤ÎÄ´ºº¤Î¤¿¤á¤Ëµ­Ï¿¤ò»Ä¤¹É¬Íפ¬¤¢¤ë¾ì¹ç¤ËÍ­¸ú¤Ç¤¹¡£ - -

    žÁ÷¤ò¥Á¥§¥Ã¥¯¤·¤Æ¡¢Å¾Á÷Àè¥á¡¼¥ë¥¢¥É¥ì¥¹¤òµ­Æþ¤¹¤ë¤È¡¢ -¥á¡¼¥ê¥ó¥°¥ê¥¹¥È¤Î´Ø·¸¼Ô°Ê³°¤Î¿Í¤ËžÁ÷¤¹¤ë¤³¤È¤¬¤Ç¤­¤Þ¤¹¡£ -ÊÝα¥á¡¼¥ë¤ò¥ê¥¹¥È¤ËÁ÷¤ëÁ°¤ËÊÔ½¸¤¹¤ë¤Ë¤Ï¡¢ -¥á¡¼¥ë¤ò¼«Ê¬ (¥ê¥¹¥È´ÉÍý¼Ô)¤ËžÁ÷¤·¡¢¸µ¤Î¥á¡¼¥ë¤òÇË´þ¤¹¤ë¤È¤è¤¤¤Ç¤·¤ç¤¦¡£ -¼«Ê¬°¸¤ËÆÏ¤¤¤¿¥á¡¼¥ë¤òÊÔ½¸¤·¡¢(ɬÍפʤé) -Approved:¥Ø¥Ã¥À¤È¥ê¥¹¥È¤Î¥Ñ¥¹¥ï¡¼¥É¤òÉÕ¤±¤Æ¡¢¥ê¥¹¥È°¸¤ËºÆÁ÷¤·¤Þ¤¹¡£ -¤³¤Î¤È¤­¡¢ËÜʸ¤Ë¼ê¤ò²Ã¤¨¤¿¤ÈºÆÁ÷¥á¡¼¥ë¤Ëµ­¤·¤Æ¤ª¤¯¤Î¤¬¥Í¥Á¥±¥Ã¥È¾å¤è¤¤¤Ç¤·¤ç¤¦¡£ - -

    Á÷¿®¼Ô¤¬À©¸ÂÉÕ¤­¤Î¥ê¥¹¥È²ñ°÷¤Î¾ì¹ç¡¢ -¤½¤Î²ñ°÷¤ÎÀ©¸Â¥Õ¥é¥°¤ò¥ê¥»¥Ã¥È¤¹¤ë¤³¤È¤â¤Ç¤­¤Þ¤¹¡£¤³¤Îµ¡Ç½¤Ï¡¢ -¿·Æþ²ñ°÷¤Ï¤¹¤Ù¤ÆÀ©¸ÂÉÕ¤­¤ÇÅÐÏ¿¤·¤Æ¤¤¤ë¥á¡¼¥ê¥ó¥°¥ê¥¹¥È¤Ç¡¢ -°Ê¸å¤ÎÅê¹Æ¤Ï¾µÇ§¤»¤º¤ËÅê¹Æ¤Ç¤­¤ë¤è¤¦¤Ë¤¹¤ë¤È¤­¤Ë»È¤¤¤Þ¤¹¡£ - -

    Á÷¿®¼Ô¤¬Èó²ñ°÷¤Î¾ì¹ç¡¢¤½¤Î¥¢¥É¥ì¥¹¤ò Á÷¿®¼Ô¥Õ¥£¥ë¥¿ -¤ËÄɲ乤뤳¤È¤â¤Ç¤­¤Þ¤¹¡£Á÷¿®¼Ô¥Õ¥£¥ë¥¿¤Ï -Á÷¿®¼Ô¥Õ¥£¥ë¥¿¡¦¥×¥é¥¤¥Ð¥·¡¼¥Ú¡¼¥¸ -¤ÇÀâÌÀ¤·¤Æ¤¤¤ëÄ̤ꡢauto-accept (¾µÇ§) ¤Þ¤¿¤Ï¡¢ -auto-hold (ÊÝα)¡¢auto-reject (µñÈÝ)¡¢ -auto-discard (ÇË´þ) ¤Î¤¤¤º¤ì¤«°ì¤Ä¤Ç¤¹¡£ -Á÷¿®¼Ô¤¬´û¤ËÁ÷¿®¼Ô¥Õ¥£¥ë¥¿¤ËÅÐÏ¿¤µ¤ì¤Æ¤¤¤ì¤Ð¡¢ -¤³¤Î¥ª¥×¥·¥ç¥ó¤Ïɽ¼¨¤µ¤ì¤Þ¤»¤ó¡£ - -

    ÀßÄê¤ò½ªÎ»¤·¤¿¤é¡¢¤³¤Î¥Ú¡¼¥¸¤ÎºÇ½é¤Þ¤¿¤ÏºÇ¸å¤Ë¤¢¤ë¡¢ -Á´ÉôÁ÷¿®¤ò¥¯¥ê¥Ã¥¯¤·¤Æ¤¯¤À¤µ¤¤¡£ -¤³¤Î¥Ü¥¿¥ó¤Ç¡¢¤¢¤Ê¤¿¤¬·èÄꤷ¤¿Á´¤Æ¤Î´ÉÍý¿½ÀÁ¤Ë¤Ä¤¤¤Æ¡¢ -ÁªÂò¤·¤¿Æ°ºî¤¬Á÷¿®¤µ¤ì¤Þ¤¹¡£ - -

    Í×Ìó¥Ú¡¼¥¸¤ØÌá¤ë¡£ +

  • 延期 -- 決定を先ã«å»¶ã°ã™ãŸã‚ã«ä¿ç•™ã—ã¾ã™ã€‚ + ã“ã®æ™‚点ã§ã¯ç®¡ç†ç”³è«‹ã®å‡¦ç†ã¯è¡Œã„ã¾ã›ã‚“ãŒã€ + ä¿ç•™ã•れã¦ã„るメールã®è»¢é€ã‚„ä¿å­˜ãŒã§ãã¾ã™(下をå‚ç…§)。 + +
  • æ‰¿èª -- 投稿を承èªã—ã€ãƒªã‚¹ãƒˆã«é…é€ã—ã¾ã™ã€‚ + 入退会ã®ç”³è«‹ã¯ã€ä¼šå“¡æ¨©ã®å¤‰æ›´ã‚’承èªã—ã¾ã™ã€‚ + +
  • æ‹’å¦ -- 投稿を拒å¦ã—ã€ãã®ã“ã¨ã‚’é€ä¿¡è€…ã«é€šçŸ¥ã—ã¾ã™ã€‚ + ã¾ãŸã€ã‚ªãƒªã‚¸ãƒŠãƒ«ã®æŠ•稿ã¯ç ´æ£„ã•れã¾ã™ã€‚入退会ã®ç”³è«‹ã«é–¢ã—ã¦ã¯ã€ + 会員権ã®å¤‰æ›´ã‚’æ‹’å¦ã—ã¾ã™ã€‚ã©ã¡ã‚‰ã®å ´åˆã‚‚〠+ ãƒ†ã‚­ã‚¹ãƒˆãƒœãƒƒã‚¯ã‚¹ã«æ‹’å¦ã®ç†ç”±ã‚’書ã加ãˆã‚‹ã¨ã‚ˆã„ã§ã—ょã†ã€‚ + +
  • 破棄 -- 投稿メールをæ¨ã¦ã€æ‹’å¦ã®é€šçŸ¥ã‚‚é€ã‚Šã¾ã›ã‚“。 + 入退会申請ã«é–¢ã—ã¦ã¯ã€å˜ã«ç”³è«‹ã‚’破棄ã—〠+ 申請を出ã—ãŸäººã¸ã®é€šçŸ¥ã¯è¡Œã„ã¾ã›ã‚“。 + 迷惑メールã«å¯¾ã—ã¦ã¯ã“れを使ã†ã¨ã‚ˆã„ã§ã—ょã†ã€‚ +
  • +

    ä¿ç•™æŠ•稿ã«ã¤ã„ã¦ã€ã‚µã‚¤ãƒˆç®¡ç†è€…ã®ãŸã‚ã«ãƒ¡ãƒ¼ãƒ«ã®ã‚³ãƒ”ーをä¿å­˜ã—ãŸã„å ´åˆã¯ã€ +ä¿å­˜ã‚’ãƒã‚§ãƒƒã‚¯ã—ã¦ãã ã•ã„。 +ã“れã¯ã€ãƒªã‚¹ãƒˆã‚’悪用ã—よã†ã¨ã™ã‚‹ãƒ¡ãƒ¼ãƒ«ã‚’破棄ã—ãŸã„ã‘れã©ã‚‚〠+後ã®èª¿æŸ»ã®ãŸã‚ã«è¨˜éŒ²ã‚’残ã™å¿…è¦ãŒã‚ã‚‹å ´åˆã«æœ‰åйã§ã™ã€‚ + +

    転é€ã‚’ãƒã‚§ãƒƒã‚¯ã—ã¦ã€è»¢é€å…ˆãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ã‚’記入ã™ã‚‹ã¨ã€ +メーリングリストã®é–¢ä¿‚者以外ã®äººã«è»¢é€ã™ã‚‹ã“ã¨ãŒã§ãã¾ã™ã€‚ +ä¿ç•™ãƒ¡ãƒ¼ãƒ«ã‚’リストã«é€ã‚‹å‰ã«ç·¨é›†ã™ã‚‹ã«ã¯ã€ +メールを自分 (リスト管ç†è€…)ã«è»¢é€ã—ã€å…ƒã®ãƒ¡ãƒ¼ãƒ«ã‚’破棄ã™ã‚‹ã¨ã‚ˆã„ã§ã—ょã†ã€‚ +自分宛ã«å±Šã„ãŸãƒ¡ãƒ¼ãƒ«ã‚’編集ã—ã€(å¿…è¦ãªã‚‰) +Approved:ヘッダã¨ãƒªã‚¹ãƒˆã®ãƒ‘スワードを付ã‘ã¦ã€ãƒªã‚¹ãƒˆå®›ã«å†é€ã—ã¾ã™ã€‚ +ã“ã®ã¨ãã€æœ¬æ–‡ã«æ‰‹ã‚’加ãˆãŸã¨å†é€ãƒ¡ãƒ¼ãƒ«ã«è¨˜ã—ã¦ãŠãã®ãŒãƒãƒã‚±ãƒƒãƒˆä¸Šã‚ˆã„ã§ã—ょã†ã€‚ + +

    é€ä¿¡è€…ãŒåˆ¶é™ä»˜ãã®ãƒªã‚¹ãƒˆä¼šå“¡ã®å ´åˆã€ +ãã®ä¼šå“¡ã®åˆ¶é™ãƒ•ラグをリセットã™ã‚‹ã“ã¨ã‚‚ã§ãã¾ã™ã€‚ã“ã®æ©Ÿèƒ½ã¯ã€ +新入会員ã¯ã™ã¹ã¦åˆ¶é™ä»˜ãã§ç™»éŒ²ã—ã¦ã„るメーリングリストã§ã€ +ä»¥å¾Œã®æŠ•ç¨¿ã¯æ‰¿èªã›ãšã«æŠ•稿ã§ãるよã†ã«ã™ã‚‹ã¨ãã«ä½¿ã„ã¾ã™ã€‚ + +

    é€ä¿¡è€…ãŒéžä¼šå“¡ã®å ´åˆã€ãã®ã‚¢ãƒ‰ãƒ¬ã‚¹ã‚’ é€ä¿¡è€…フィルタ +ã«è¿½åŠ ã™ã‚‹ã“ã¨ã‚‚ã§ãã¾ã™ã€‚é€ä¿¡è€…フィルタ㯠+é€ä¿¡è€…フィルタ・プライãƒã‚·ãƒ¼ãƒšãƒ¼ã‚¸ +ã§èª¬æ˜Žã—ã¦ã„る通りã€auto-accept (承èª) ã¾ãŸã¯ã€ +auto-hold (ä¿ç•™)ã€auto-reject (æ‹’å¦)〠+auto-discard (破棄) ã®ã„ãšã‚Œã‹ä¸€ã¤ã§ã™ã€‚ +é€ä¿¡è€…ãŒæ—¢ã«é€ä¿¡è€…フィルタã«ç™»éŒ²ã•れã¦ã„れã°ã€ +ã“ã®ã‚ªãƒ—ションã¯è¡¨ç¤ºã•れã¾ã›ã‚“。 + +

    設定を終了ã—ãŸã‚‰ã€ã“ã®ãƒšãƒ¼ã‚¸ã®æœ€åˆã¾ãŸã¯æœ€å¾Œã«ã‚る〠+全部é€ä¿¡ã‚’クリックã—ã¦ãã ã•ã„。 +ã“ã®ãƒœã‚¿ãƒ³ã§ã€ã‚ãªãŸãŒæ±ºå®šã—ãŸå…¨ã¦ã®ç®¡ç†ç”³è«‹ã«ã¤ã„ã¦ã€ +é¸æŠžã—ãŸå‹•作ãŒé€ä¿¡ã•れã¾ã™ã€‚ + +

    è¦ç´„ãƒšãƒ¼ã‚¸ã¸æˆ»ã‚‹ã€‚ +

    \ No newline at end of file diff --git a/templates/ja/admindbpreamble.html b/templates/ja/admindbpreamble.html index e79089ae..a13c2fce 100644 --- a/templates/ja/admindbpreamble.html +++ b/templates/ja/admindbpreamble.html @@ -1,10 +1,74 @@ -¤³¤Î¥Ú¡¼¥¸¤Ï¡¢¾µÇ§¤Î¤¿¤á¤ËÊÝᤵ¤ì¤Æ¤¤¤ë -%(listname)s¥á¡¼¥ê¥ó¥°¥ê¥¹¥È¤ÎÅê¹Æ¤Î°ìÉô¤¬É½¼¨¤·¤Þ¤¹¡£ -¸½ºß¤Îɽ¼¨¤Ï: %(description)s +ã“ã®ãƒšãƒ¼ã‚¸ã¯ã€æ‰¿èªã®ãŸã‚ã«ä¿ç•™ã•れã¦ã„ã‚‹ +%(listname)sãƒ¡ãƒ¼ãƒªãƒ³ã‚°ãƒªã‚¹ãƒˆã®æŠ•ç¨¿ã®ä¸€éƒ¨ãŒè¡¨ç¤ºã—ã¾ã™ã€‚ +ç¾åœ¨ã®è¡¨ç¤ºã¯: %(description)s -

    ¤½¤ì¤¾¤ì¤Î´ÉÍý¿½ÀÁ¤ËÂФ¹¤ë½èÍý¤òÁªÂò¤·¤Æ¤¯¤À¤µ¤¤¡£ -ÁªÂò¸å¤Ë¡¢Á´ÉôÁ÷¿® ¥Ü¥¿¥ó¤ò¥¯¥ê¥Ã¥¯¤·¤Æ¤¯¤À¤µ¤¤¡£ -¾Ü¤·¤¤ÀâÌÀ ¤â¤¢¤ê¤Þ¤¹¡£ +

    ãれãžã‚Œã®ç®¡ç†ç”³è«‹ã«å¯¾ã™ã‚‹å‡¦ç†ã‚’é¸æŠžã—ã¦ãã ã•ã„。 +é¸æŠžå¾Œã«ã€å…¨éƒ¨é€ä¿¡ ボタンをクリックã—ã¦ãã ã•ã„。 +詳ã—ã„説明 ã‚‚ã‚りã¾ã™ã€‚ -

    ¤Þ¤¿¡¢Á´¤Æ¤Î´ÉÍý¿½ÀÁ¤Î Í×Ìó¤ò¸«¤ë -¤³¤È¤â¤Ç¤­¤Þ¤¹¡£ +

    ã¾ãŸã€å…¨ã¦ã®ç®¡ç†ç”³è«‹ã® è¦ç´„を見る +ã“ã¨ã‚‚ã§ãã¾ã™ã€‚ +

    \ No newline at end of file diff --git a/templates/ja/admindbsummary.html b/templates/ja/admindbsummary.html index 1d27b41a..bcd2d0ec 100644 --- a/templates/ja/admindbsummary.html +++ b/templates/ja/admindbsummary.html @@ -1,12 +1,76 @@ -¤³¤Î¥Ú¡¼¥¸¤Ï¡¢ -%(listname)s¥á¡¼¥ê¥ó¥°¥ê¥¹¥È -¤Ë´Ø¤¹¤ë¾µÇ§ÂÔ¤Á´ÉÍý¿½ÀÁ¤ÎÍ×Ìó¤òɽ¼¨¤·¤Þ¤¹¡£ -ºÇ½é¤Ë¡¢¾µÇ§ÂÔ¤Á¤ÎÆþ²ñ¡¦Âà²ñ¿½ÀÁ¤¬¤¢¤ì¤Ð¡¢¤½¤ì¤é¤Î¿½ÀÁ¤òɽ¼¨¤·¤Þ¤¹¡£ -¤½¤Î¼¡¤Ë¡¢¾µÇ§¤ò¼õ¤±¤ë¤¿¤á¤ËÊÝᤵ¤ì¤Æ¤¤¤ëÅê¹Æ¤òɽ¼¨¤·¤Þ¤¹¡£ +ã“ã®ãƒšãƒ¼ã‚¸ã¯ã€ +%(listname)sメーリングリスト +ã«é–¢ã™ã‚‹æ‰¿èªå¾…ã¡ç®¡ç†ç”³è«‹ã®è¦ç´„を表示ã—ã¾ã™ã€‚ +最åˆã«ã€æ‰¿èªå¾…ã¡ã®å…¥ä¼šãƒ»é€€ä¼šç”³è«‹ãŒã‚れã°ã€ãれらã®ç”³è«‹ã‚’表示ã—ã¾ã™ã€‚ +ãã®æ¬¡ã«ã€æ‰¿èªã‚’å—ã‘ã‚‹ãŸã‚ã«ä¿ç•™ã•れã¦ã„る投稿を表示ã—ã¾ã™ã€‚ -

    ¤½¤ì¤¾¤ì¤Î´ÉÍý¿½ÀÁ¤ËÂФ¹¤ë½èÍý¤òÁªÂò¤·¤Æ¤¯¤À¤µ¤¤¡£ -ÁªÂò¸å¤Ë¡¢Á´ÉôÁ÷¿® ¥Ü¥¿¥ó¤ò¥¯¥ê¥Ã¥¯¤·¤Æ¤¯¤À¤µ¤¤¡£ -¾Ü¤·¤¤ÀâÌÀ ¤â¤¢¤ê¤Þ¤¹¡£ +

    ãれãžã‚Œã®ç®¡ç†ç”³è«‹ã«å¯¾ã™ã‚‹å‡¦ç†ã‚’é¸æŠžã—ã¦ãã ã•ã„。 +é¸æŠžå¾Œã«ã€å…¨éƒ¨é€ä¿¡ ボタンをクリックã—ã¦ãã ã•ã„。 +詳ã—ã„説明 ã‚‚ã‚りã¾ã™ã€‚ -

    ¤Þ¤¿¡¢ÊÝαÅê¹Æ¤Î ¾ÜºÙ¤ò¸«¤ë -¤³¤È¤â¤Ç¤­¤Þ¤¹¡£ +

    ã¾ãŸã€ä¿ç•™æŠ•稿㮠詳細を見る +ã“ã¨ã‚‚ã§ãã¾ã™ã€‚ +

    \ No newline at end of file diff --git a/templates/ja/admlogin.html b/templates/ja/admlogin.html index a3a8d707..13c93565 100755 --- a/templates/ja/admlogin.html +++ b/templates/ja/admlogin.html @@ -1,40 +1,101 @@ - %(listname)s %(who)s ǧ¾Ú +%(listname)s %(who)s èªè¨¼ - - -
    + + + %(message)s - - - - - - - - - - - -
    - %(listname)s %(who)s - ǧ¾Ú -
    ¥ê¥¹¥È %(who)s ¥Ñ¥¹¥ï¡¼¥É:
    -
    -

    ½ÅÍ×: - ¤³¤³¤«¤éÀè¤Ï¥Ö¥é¥¦¥¶¤Î¥¯¥Ã¥­¡¼¤òÍ­¸ú¤Ë¤·¤Æ¤¯¤À¤µ¤¤¡£ - ¥¯¥Ã¥­¡¼¤¬Ìµ¸ú¤À¤È´ÉÍýÀßÄê¤òÊѹ¹¤Ç¤­¤Þ¤»¤ó¡£ + + + + + + + + + + + +
    +%(listname)s %(who)s + èªè¨¼ +
    リスト %(who)s パスワード:
    +
    +

    é‡è¦: + ã“ã“ã‹ã‚‰å…ˆã¯ãƒ–ラウザã®ã‚¯ãƒƒã‚­ãƒ¼ã‚’有効ã«ã—ã¦ãã ã•ã„。 + クッキーãŒç„¡åйã ã¨ç®¡ç†è¨­å®šã‚’変更ã§ãã¾ã›ã‚“。 -

    ´ÉÍý¥¤¥ó¥¿¡¼¥Õ¥§¡¼¥¹¤Ç¤Î´ÉÍýÁàºî¤ò¤¹¤ë¤¿¤Ó¤Ëǧ¾Ú¤¬Í×µá - ¤µ¤ì¤Ê¤¤¤è¤¦¤Ë¡¢¥»¥Ã¥·¥ç¥ó¥¯¥Ã¥­¡¼¤òȯ¹Ô¤·¤Þ¤¹¡£ - ¤³¤Î¥¯¥Ã¥­¡¼¤Ï¥Ö¥é¥¦¥¶¤Î½ªÎ»»þ¤«¡¢ÌÀ¼¨Åª¤Ë¥í¥°¥¢¥¦¥È - ¤·¤¿»þ¤Ë¼«Æ°Åª¤Ë¼º¸ú¤·¤Þ¤¹¡£ - (ǧ¾Ú¤¬´°Î»¤¹¤ë¤È¡¢Â¾¤Î´ÉÍý¹àÌܤβ¼¤Ë - ¥í¥°¥¢¥¦¥È¤Î¥ê¥ó¥¯¤¬É½¼¨¤µ¤ì¤Þ¤¹)¡£ +

    管ç†ã‚¤ãƒ³ã‚¿ãƒ¼ãƒ•ェースã§ã®ç®¡ç†æ“作をã™ã‚‹ãŸã³ã«èªè¨¼ãŒè¦æ±‚ + ã•れãªã„よã†ã«ã€ã‚»ãƒƒã‚·ãƒ§ãƒ³ã‚¯ãƒƒã‚­ãƒ¼ã‚’発行ã—ã¾ã™ã€‚ + ã“ã®ã‚¯ãƒƒã‚­ãƒ¼ã¯ãƒ–ラウザã®çµ‚了時ã‹ã€æ˜Žç¤ºçš„ã«ãƒ­ã‚°ã‚¢ã‚¦ãƒˆ + ã—ãŸæ™‚ã«è‡ªå‹•çš„ã«å¤±åйã—ã¾ã™ã€‚ + (èªè¨¼ãŒå®Œäº†ã™ã‚‹ã¨ã€ä»–ã®ç®¡ç†é …ç›®ã®ä¸‹ã« + ログアウトã®ãƒªãƒ³ã‚¯ãŒè¡¨ç¤ºã•れã¾ã™)。 -

    +

    diff --git a/templates/ja/archidxentry.html b/templates/ja/archidxentry.html index f9bb57aa..5c620b20 100644 --- a/templates/ja/archidxentry.html +++ b/templates/ja/archidxentry.html @@ -1,4 +1,68 @@ -
  • %(subject)s -  -%(author)s - +
  • %(subject)s +  +%(author)s + +
  • \ No newline at end of file diff --git a/templates/ja/archidxfoot.html b/templates/ja/archidxfoot.html index 1a4cbf48..795c94a0 100644 --- a/templates/ja/archidxfoot.html +++ b/templates/ja/archidxfoot.html @@ -1,21 +1,85 @@ - -

    - ºÇ¿·µ­»ö¤ÎÆüÉÕ: - %(lastdate)s
    - ½ñ¸ËºîÀ®Æü»þ: %(archivedate)s -

    -

      -
    • Ìܼ¡¤Î½ç: + +

      +æœ€æ–°è¨˜äº‹ã®æ—¥ä»˜: +%(lastdate)s
      +æ›¸åº«ä½œæˆæ—¥æ™‚: %(archivedate)s +

      +

      -

      -


      - ¤³¤ÎÊݸ½ñ¸Ë¤Ï - Pipermail %(version)s ¤ÇÀ¸À®¤·¤Þ¤·¤¿¡£ - - +
    +

    +


    +ã“ã®ä¿å­˜æ›¸åº«ã¯ + Pipermail %(version)s ã§ç”Ÿæˆã—ã¾ã—ãŸã€‚ + + +

    \ No newline at end of file diff --git a/templates/ja/archidxhead.html b/templates/ja/archidxhead.html index 0c91c078..5ef4b421 100644 --- a/templates/ja/archidxhead.html +++ b/templates/ja/archidxhead.html @@ -1,24 +1,89 @@ - - - %(listname)s %(archive)s Êݸ½ñ¸Ë %(archtype)s - + + + +%(listname)s %(archive)s ä¿å­˜æ›¸åº« %(archtype)s + %(encoding)s - - - -

    %(archive)s Êݸ½ñ¸Ë %(archtype)s

    -
      -
    • Ìܼ¡¤Î½ç: + + + +

      %(archive)s ä¿å­˜æ›¸åº« %(archtype)s

      + -

      ³«»Ï: %(firstdate)s
      - ºÇ½ª: %(lastdate)s
      - µ­»ö¿ô: %(size)s

      -

        +
      +

      é–‹å§‹: %(firstdate)s
      +最終: %(lastdate)s
      +記事数: %(size)s

      +

        +

      \ No newline at end of file diff --git a/templates/ja/archlistend.html b/templates/ja/archlistend.html index 9bc052dd..2e1191b0 100644 --- a/templates/ja/archlistend.html +++ b/templates/ja/archlistend.html @@ -1 +1,64 @@ -
    + diff --git a/templates/ja/archliststart.html b/templates/ja/archliststart.html index 81d326b5..473f14e8 100644 --- a/templates/ja/archliststart.html +++ b/templates/ja/archliststart.html @@ -1,4 +1,68 @@ - - - - +
    ½ñêʤٽç:¥À¥¦¥ó¥í¡¼¥ÉÈÇ
    + + + +
    書棚並ã¹é †:ダウンロード版
    \ No newline at end of file diff --git a/templates/ja/archtoc.html b/templates/ja/archtoc.html index 10e209bf..ffdb48ff 100644 --- a/templates/ja/archtoc.html +++ b/templates/ja/archtoc.html @@ -1,20 +1,84 @@ - - - %(listname)s Êݸ½ñ¸Ë - + + + +%(listname)s ä¿å­˜æ›¸åº« + %(meta)s - - -

    %(listname)s Êݸ½ñ¸Ë

    -

    - ¥ê¥¹¥È¤Î°ÆÆâ * - Á´Éô¤Î¥á¡¼¥ë¤òmbox·Á¼°¤Ç¥À¥¦¥ó¥í¡¼¥É - (%(size)s)¡£ + + +

    %(listname)s ä¿å­˜æ›¸åº«

    +

    +ãƒªã‚¹ãƒˆã®æ¡ˆå†… * + 全部ã®ãƒ¡ãƒ¼ãƒ«ã‚’mboxå½¢å¼ã§ãƒ€ã‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰ + (%(size)s)。

    %(noarchive_msg)s %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/ja/archtocentry.html b/templates/ja/archtocentry.html index 4fd7b12f..989eca47 100644 --- a/templates/ja/archtocentry.html +++ b/templates/ja/archtocentry.html @@ -1,12 +1,74 @@ - -
    %(archivelabel)s: - [ ¥¹¥ì¥Ã¥É ] - [ ·ï̾ ] - [ ȯ¿®¼Ô ] - [ ÆüÉÕ ] -
    %(archivelabel)s: +[ スレッド ] +[ ä»¶å ] +[ 発信者 ] +[ 日付 ] +
    - - - - - - - - - - - - - - - - - - + + + + + +

    - -- - -
    -

      -

    - �ˤĤ��� - - - -
    -

    -

    ���Υ᡼��󥰥ꥹ�Ȥ���Ƥ��줿���Υ᡼��ϡ� - - ��¸��������������� - -

    -
    - ������ˡ -
    + + + +<mm-list-name> Ú¡</mm-list-name> + + + +

    + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + + + + + + + +
    + -- + +
    +

      +

    +�ˤĤ��� + + + +
    +

    +

    ���Υ᡼��󥰥ꥹ�Ȥ���Ƥ��줿���Υ᡼��ϡ� + + ��¸��������������� + +

    +
    + ������ˡ +
    �᡼��󥰥ꥹ�Ȥ������������᡼��ϡ� - + �Υ��ɥ쥹�����������Ƥ���������

    �᡼��󥰥ꥹ�Ȥ�����丽�ߤβ�����ץ������ѹ��ϡ� �ʲ��Υե���������Ѥ��������� -

    - ��� -
    -

    - �ؤ�����ϡ� +

    + ��� +
    +

    + �ؤ�����ϡ� �ʲ��Υե������ɬ�׻���������Ƥ��������� - -

      - - - - - - - - - - - - - - - - - -
      �᡼�륢�ɥ쥹: -  
      ̾�� (����): 
      ������ݸ�˻Ȥ��ѥ���ɤ� + +
        + + + + + + + + + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - - - -
        �᡼�륢�ɥ쥹: + 
        ̾�� (��ά��): 
        ������ݸ�˻Ȥ��ѥ���ɤ� ���ꤹ�뤳�Ȥ�Ǥ��ޤ������٤ʥ������ƥ��ǤϤ���ޤ��󤬡� ¾�ͤ˲��������ѹ�����뤳�Ȥ��ɤ����ȤϤǤ���Ϥ��Ǥ��� ���Υѥ���ɤϡ��Ź沽�����˥᡼�������������礬����ޤ� �Τǡ������ƥ�ѥ���ɤʤɤ� ���פʥѥ���ɤ���ꤷ�ʤ� �褦�ˤ��Ƥ��������� -
        +
        �ѥ���ɤ����ꤵ��ʤ��ä���硢�ѥ���ɤ�ưŪ���������� �����ǧ��˥᡼������դ��ޤ��� ���ĤǤ⤳�Υѥ���ɤ�᡼��Ǽ��󤻤뤳�Ȥ��Ǥ��ޤ��� - -
        -
        �ѥ���ɤ����Ϥ��Ƥ�������: 
        ��ǧ�Τ���Ʊ���ѥ���ɤ������: 
        ɽ���˻Ȥ����������Ǥ�������  
        �ꥹ�ȤΥ᡼�������1�ܤˤޤȤ������ޤ���? + + +
        �ѥ���ɤ����Ϥ��Ƥ�������: 
        ��ǧ�Τ���Ʊ���ѥ���ɤ������: 
        ɽ���˻Ȥ����������Ǥ�������  
        �ꥹ�ȤΥ᡼�������1�ܤˤޤȤ������ޤ���? ������ - �Ϥ� -
        -
        -
        - -
      -
      - - ��������� -
      - - - -

      - - - -

      - - - +
    ������ + �Ϥ� +
    +
    +
    + + +

    + + ��������� +
    + + + +

    + + + +

    + +

    + diff --git a/templates/ja/options.html b/templates/ja/options.html index 4da2afa0..1a8ffea1 100644 --- a/templates/ja/options.html +++ b/templates/ja/options.html @@ -1,299 +1,333 @@ - -<MM-List-Name> ¥á¡¼¥ê¥ó¥°¥ê¥¹¥È¤Ç¤Î <MM-Presentable-User> ¤ÎÀßÄê - - - - - -
    - - ¥á¡¼¥ê¥ó¥°¥ê¥¹¥È¤Ç¤Î ¤ÎÀßÄê -
    -

    - - - - - + +<mm-list-name> メーリングリストã§ã® <mm-presentable-user> ã®è¨­å®š</mm-presentable-user></mm-list-name> + + +
    - ¥á¡¼¥ê¥ó¥°¥ê¥¹¥È¤Ç¤Î ¤µ¤ó¤Î - ²ñ°÷ÀßÄê¾õ¶·¡¢¥Ñ¥¹¥ï¡¼¥É¡¢¥ª¥×¥·¥ç¥ó¤ÎÀßÄê¾õ¶·¡£ -
    - - - - -

    -

    +
    + + メーリングリストã§ã® ã®è¨­å®š +
    - -

    - - - - - - - - +
    - - ²ñ°÷¥¢¥É¥ì¥¹¤ÎÊѹ¹ -
    ¥á¡¼¥ê¥ó¥°¥ê¥¹¥È¤ËÅÐÏ¿¤·¤¿ÇÛÁ÷¥¢¥É¥ì¥¹ - ¤òÊѹ¹¤¹¤ë¤Ë¤Ï¡¢²¼¤ÎÍó¤Ë¿·¤·¤¤¥¢¥É¥ì¥¹¤òÆþÎϤ·¤Æ¤¯¤À¤µ¤¤¡£ - ¤Ê¤ª¿·¥¢¥É¥ì¥¹¤ËÁ÷¤ë³Îǧ¤Î¥á¡¼¥ë¤ËÂФ¹¤ë³Îǧ¤¬¤¢¤ë¤Þ¤Ç - ½èÍý¤ÏÊÝᤵ¤ì¤Þ¤¹¡£ - -

    Ìó¸å¤Ë³Îǧ¤Î´ü¸Â¤¬ÀÚ¤ì¤Þ¤¹¡£ - -

    ¤Þ¤¿¡¢¥ª¥×¥·¥ç¥ó¤Ç¤¢¤Ê¤¿¤Î¼Â̾(Îã:»ûÅÄÆÒɧ)¤ò - ÀßÄꤷ¤¿¤êÊѹ¹¤·¤¿¤ê¤Ç¤­¤Þ¤¹¡£ - -

    ¤ËÅÐÏ¿¤µ¤ì¤Æ¤¤¤ë¤¹¤Ù¤Æ¤Î¥á¡¼¥ê¥ó¥°¥ê¥¹¥È¤Î - ²ñ°÷¥¢¥É¥ì¥¹¤òÊѹ¹¤¹¤ë¾ì¹ç¡¢ - Á´ÉôÊѹ¹¥Ü¥Ã¥¯¥¹¤ò¥Á¥§¥Ã¥¯¤·¤Æ¤¯¤À¤µ¤¤¡£ -

    - - - - - - - -
    ¿·¥¢¥É¥ì¥¹:
    - ³ÎǧÍÑºÆÆþÎÏ:
    -
    - - - - -
    ̾Á° - (¾Êά²Ä):
    -
    -

    Á´ÉôÊѹ¹ -

    + + + +
    + メーリングリストã§ã® ã•ã‚“ã® + 会員設定状æ³ã€ãƒ‘スワードã€ã‚ªãƒ—ションã®è¨­å®šçжæ³ã€‚ +
    + + +

    +

    - +

    - - - - - - + + +

    +

    - ¤òÂà²ñ¤¹¤ë - ¤¢¤Ê¤¿¤Î¾¤Î ¤Ç¤ÎÆþ²ñ¾õ¶· -
    - Âà²ñ¤¹¤ë¤Ë¤Ï³Îǧ¥Ü¥Ã¥¯¥¹¤ò¥Á¥§¥Ã¥¯¤·¤Æ¡¢¤³¤Î - ¥Ü¥¿¥ó¤ò¥¯¥ê¥Ã¥¯¤·¤Æ¤¯¤À¤µ¤¤¡£·Ù¹ð: - ¤³¤ì¤Ïľ¤Á¤ËÂà²ñ¼ê³¤­¤ò´°Î»¤·¤Þ¤¹¡£ + + + +
    + + 会員アドレスã®å¤‰æ›´ +
    メーリングリストã«ç™»éŒ²ã—ãŸé…é€ã‚¢ãƒ‰ãƒ¬ã‚¹ + を変更ã™ã‚‹ã«ã¯ã€ä¸‹ã®æ¬„ã«æ–°ã—ã„アドレスを入力ã—ã¦ãã ã•ã„。 + ãªãŠæ–°ã‚¢ãƒ‰ãƒ¬ã‚¹ã«é€ã‚‹ç¢ºèªã®ãƒ¡ãƒ¼ãƒ«ã«å¯¾ã™ã‚‹ç¢ºèªãŒã‚ã‚‹ã¾ã§ + 処ç†ã¯ä¿ç•™ã•れã¾ã™ã€‚ + +

    約後ã«ç¢ºèªã®æœŸé™ãŒåˆ‡ã‚Œã¾ã™ã€‚ + +

    ã¾ãŸã€ã‚ªãƒ—ションã§ã‚ãªãŸã®å®Ÿå(例:寺田寅彦)ã‚’ + 設定ã—ãŸã‚Šå¤‰æ›´ã—ãŸã‚Šã§ãã¾ã™ã€‚ + +

    ã«ç™»éŒ²ã•れã¦ã„ã‚‹ã™ã¹ã¦ã®ãƒ¡ãƒ¼ãƒªãƒ³ã‚°ãƒªã‚¹ãƒˆã® + 会員アドレスを変更ã™ã‚‹å ´åˆã€ + 全部変更ボックスをãƒã‚§ãƒƒã‚¯ã—ã¦ãã ã•ã„。 +

    + + + + + + + +
    新アドレス:
    + 確èªç”¨å†å…¥åŠ›:
    +
    + + + + +
    åå‰ + (çœç•¥å¯):
    +
    +

    全部変更 +

    + + + - + +
    +

    + を退会ã™ã‚‹ +ã‚ãªãŸã®ä»–ã® ã§ã®å…¥ä¼šçŠ¶æ³ +
    + 退会ã™ã‚‹ã«ã¯ç¢ºèªãƒœãƒƒã‚¯ã‚¹ã‚’ãƒã‚§ãƒƒã‚¯ã—ã¦ã€ã“ã® + ボタンをクリックã—ã¦ãã ã•ã„。警告: + ã“れã¯ç›´ã¡ã«é€€ä¼šæ‰‹ç¶šãを完了ã—ã¾ã™ã€‚

    -

    - ¤ÇÆþ²ñ¤·¤Æ¤¤¤ëÁ´¤Æ¤Î¥á¡¼¥ê¥ó¥°¥ê¥¹¥È - ¤Î¥ê¥¹¥È¤ò¸«¤ë¤³¤È¤¬¤Ç¤­¤Þ¤¹¡£Â¾¤Î²ñ°÷¥ª¥×¥·¥ç¥ó¤â - Êѹ¹¤¹¤ë¤Î¤Ç¤¢¤ì¤Ð¤³¤ì¤òÍøÍѤ·¤Æ¤¯¤À¤µ¤¤¡£ +

    +ã§å…¥ä¼šã—ã¦ã„ã‚‹å…¨ã¦ã®ãƒ¡ãƒ¼ãƒªãƒ³ã‚°ãƒªã‚¹ãƒˆ + ã®ãƒªã‚¹ãƒˆã‚’見るã“ã¨ãŒã§ãã¾ã™ã€‚ä»–ã®ä¼šå“¡ã‚ªãƒ—ションも + 変更ã™ã‚‹ã®ã§ã‚れã°ã“れを利用ã—ã¦ãã ã•ã„。

    -

    -
    - - - - - -
    - ¤Î¥Ñ¥¹¥ï¡¼¥É -
    - -
    -

    ¥Ñ¥¹¥ï¡¼¥É¤ò˺¤ì¤¿¤é?

    -
    - ¤³¤Î¥Ü¥¿¥ó¤ò¥¯¥ê¥Ã¥¯¤¹¤ë¤È¡¢¥á¡¼¥ë¤Ç¥Ñ¥¹¥ï¡¼¥É¤òÁ÷¿®¤·¤Þ¤¹¡£ -

    -

    - -
    -
    - -
    -

    ¥Ñ¥¹¥ï¡¼¥ÉÊѹ¹

    - - - - - - - - -
    - ¿·¥Ñ¥¹¥ï¡¼¥É:
    - ³ÎǧÍÑºÆÆþÎÏ:
    - -

    Á´ÉôÊѹ¹ -
    -
    - + + + +
    + ã®ãƒ‘スワード +
    + +
    +

    パスワードを忘れãŸã‚‰?

    +
    + ã“ã®ãƒœã‚¿ãƒ³ã‚’クリックã™ã‚‹ã¨ã€ãƒ¡ãƒ¼ãƒ«ã§ãƒ‘スワードをé€ä¿¡ã—ã¾ã™ã€‚ +

    +

    + +
    +

    + +
    +

    パスワード変更

    + + + + + + + + +
    + 新パスワード:
    + 確èªç”¨å†å…¥åŠ›:
    + +

    全部変更 +
    +

    - - +
    - ¤Î²ñ°÷¥ª¥×¥·¥ç¥ó -
    +
    + ã®ä¼šå“¡ã‚ªãƒ—ション +
    -

    -¸½ºß¤ÎÀßÄ꤬¥Á¥§¥Ã¥¯¤µ¤ì¤Æ¤¤¤Þ¤¹¡£ -

    Á´ÉôÀßÄê¤Î¥Á¥§¥Ã¥¯¥Ü¥Ã¥¯¥¹¤ò¥Á¥§¥Ã¥¯¤¹¤ë¤È¡¢ -Æþ²ñ¤·¤Æ¤¤¤ë¤ÎÁ´¥á¡¼¥ê¥ó¥°¥ê¥¹¥È¤Ç¤ÎÀßÄê¤òÊѹ¹¤¹¤ë¤³¤È¤¬¤Ç¤­¤Þ¤¹¡£ -¾å¤Î¾¤Î¥á¡¼¥ê¥ó¥°¥ê¥¹¥È¤Ø¤ÎÆþ²ñ¾õ¶·¤Ç¡¢ -¼«Ê¬¤¬Æþ²ñ¤·¤Æ¤¤¤ë¥á¡¼¥ê¥ó¥°¥ê¥¹¥È¤òÄ´¤Ù¤ë¤³¤È¤¬¤Ç¤­¤Þ¤¹¡£ +ç¾åœ¨ã®è¨­å®šãŒãƒã‚§ãƒƒã‚¯ã•れã¦ã„ã¾ã™ã€‚ +

    全部設定ã®ãƒã‚§ãƒƒã‚¯ãƒœãƒƒã‚¯ã‚¹ã‚’ãƒã‚§ãƒƒã‚¯ã™ã‚‹ã¨ã€ +入会ã—ã¦ã„ã‚‹ã®å…¨ãƒ¡ãƒ¼ãƒªãƒ³ã‚°ãƒªã‚¹ãƒˆã§ã®è¨­å®šã‚’変更ã™ã‚‹ã“ã¨ãŒã§ãã¾ã™ã€‚ +上ã®ä»–ã®ãƒ¡ãƒ¼ãƒªãƒ³ã‚°ãƒªã‚¹ãƒˆã¸ã®å…¥ä¼šçжæ³ã§ã€ +自分ãŒå…¥ä¼šã—ã¦ã„るメーリングリストを調ã¹ã‚‹ã“ã¨ãŒã§ãã¾ã™ã€‚

    - - - +
    - - ¥á¡¼¥ëÇÛÁ÷
    - ¥á¡¼¥ê¥ó¥°¥ê¥¹¥È¤ËÅê¹Æ¤µ¤ì¤¿¥á¡¼¥ë¤ÎÇÛÁ÷¤ò³«»Ï¤µ¤»¤ë¤È¤­¤Ï¡¢ - ¤³¤Î¥ª¥×¥·¥ç¥ó¤òÍ­¸ú¤Ë¤·¤Þ¤¹¡£Ää»ß¤òÁª¤Ö¤È¡¢ - °ì»þŪ¤Ë¥á¡¼¥ë¤ÎÇÛÁ÷¤ò»ß¤á¤ë¤³¤È¤¬¤Ç¤­¤Þ¤¹ - (Î㤨¤Ðû´ü´Ö¤ÎµÙ²ËÃæ¤Ê¤É)¡£ÇÛÁ÷¤òÄä»ß¤·¤¿¤È¤­¤Ï¡¢ - µÙ²Ë¤¬½ª¤ï¤Ã¤¿¤éÍ­¸ú¤ËÀßÄꤷľ¤¹¤³¤È¤ò¤ªËº¤ì¤Ê¤¯¡£ -
    - Í­¸ú
    - Ää»ß

    - Á´ÉôÀßÄê -

    + - - - + + - - - - - - - - - - - - - - - - - - - + + + + + + + + +
    + + メールé…é€
    + ãƒ¡ãƒ¼ãƒªãƒ³ã‚°ãƒªã‚¹ãƒˆã«æŠ•ç¨¿ã•れãŸãƒ¡ãƒ¼ãƒ«ã®é…é€ã‚’é–‹å§‹ã•ã›ã‚‹ã¨ãã¯ã€ + ã“ã®ã‚ªãƒ—ションを有効ã«ã—ã¾ã™ã€‚åœæ­¢ã‚’é¸ã¶ã¨ã€ + 一時的ã«ãƒ¡ãƒ¼ãƒ«ã®é…é€ã‚’æ­¢ã‚ã‚‹ã“ã¨ãŒã§ãã¾ã™ + (例ãˆã°çŸ­æœŸé–“ã®ä¼‘暇中ãªã©)。é…é€ã‚’åœæ­¢ã—ãŸã¨ãã¯ã€ + 休暇ãŒçµ‚ã‚ã£ãŸã‚‰æœ‰åйã«è¨­å®šã—ç›´ã™ã“ã¨ã‚’ãŠå¿˜ã‚Œãªã。 +
    + 有効
    + åœæ­¢

    +全部設定 +

    - ¤Þ¤È¤áÆÉ¤ß¥â¡¼¥É¤ËÀßÄê
    - Í­¸ú ¤òÁª¤Ö¤È¡¢Åê¹Æ¤Î¤¿¤Ó¤Ë¥á¡¼¥ë¤òÁ÷¤é¤º¡¢¤¹¤Ù¤Æ¤Î¥á¡¼¥ë¤ò - ËèÆü1ÄÌ¤Ë¤Þ¤È¤á¤Æ¥á¡¼¥ë¤¬ÆÏ¤­¤Þ¤¹¡£¤Þ¤È¤áÆÉ¤ß¥â¡¼¥É¤ÎÀßÄê¤ò - Í­¸ú ¤«¤é ̵¸ú ¤ËÊѹ¹¤¹¤ë¤È¡¢ºÇ¸å¤Ë1Ä̤ΤޤȤá¥á¡¼¥ë¤¬Á÷¤é¤ì¤Þ¤¹¡£ -
    - ̵¸ú
    - Í­¸ú -
    - ¤Þ¤È¤áÆÉ¤ß¥á¡¼¥ë¤ò¼õ¤±¼è¤ë·Á¼°¡£ - ʿʸ¤Þ¤¿¤ÏMIME¤ÎźÉÕ¥Õ¥¡¥¤¥ë·Á¼°¡£

    - ¥á¡¼¥ë¥½¥Õ¥È¤Ë¤è¤Ã¤Æ¤Ï MIME·Á¼°¤Ç¤Þ¤È¤áÆÉ¤ß¥á¡¼¥ë¤òÆÉ¤á¤Ê¤¤ - ¤â¤Î¤â¤¢¤ê¤Þ¤¹¡£°ìÈÌŪ¤Ë¤ÏMIME·Á¼°¤ÎÊý¤¬ÊØÍø¤Ç¤¹¤¬¡¢ - MIME·Á¼°¤Î¥á¡¼¥ë¤¬ÆÉ¤á¤Ê¤¤¾ì¹ç¤Ïʿʸ¤òÁª¤ó¤Ç¤¯¤À¤µ¤¤¡£ -

    - MIME
    - ʿʸ

    - Á´ÉôÀßÄê -

    + ã¾ã¨ã‚読ã¿ãƒ¢ãƒ¼ãƒ‰ã«è¨­å®š
    + 有効 ã‚’é¸ã¶ã¨ã€æŠ•稿ã®ãŸã³ã«ãƒ¡ãƒ¼ãƒ«ã‚’é€ã‚‰ãšã€ã™ã¹ã¦ã®ãƒ¡ãƒ¼ãƒ«ã‚’ + 毎日1通ã«ã¾ã¨ã‚ã¦ãƒ¡ãƒ¼ãƒ«ãŒå±Šãã¾ã™ã€‚ã¾ã¨ã‚読ã¿ãƒ¢ãƒ¼ãƒ‰ã®è¨­å®šã‚’ + 有効 ã‹ã‚‰ 無効 ã«å¤‰æ›´ã™ã‚‹ã¨ã€æœ€å¾Œã«1通ã®ã¾ã¨ã‚メールãŒé€ã‚‰ã‚Œã¾ã™ã€‚ +
    + 無効
    + 有効 +
    + ã¾ã¨ã‚読ã¿ãƒ¡ãƒ¼ãƒ«ã‚’å—ã‘å–ã‚‹å½¢å¼ã€‚ + 平文ã¾ãŸã¯MIMEã®æ·»ä»˜ãƒ•ァイル形å¼ã€‚

    + メールソフトã«ã‚ˆã£ã¦ã¯ MIMEå½¢å¼ã§ã¾ã¨ã‚読ã¿ãƒ¡ãƒ¼ãƒ«ã‚’読ã‚ãªã„ + ã‚‚ã®ã‚‚ã‚りã¾ã™ã€‚一般的ã«ã¯MIMEå½¢å¼ã®æ–¹ãŒä¾¿åˆ©ã§ã™ãŒã€ + MIMEå½¢å¼ã®ãƒ¡ãƒ¼ãƒ«ãŒèª­ã‚ãªã„å ´åˆã¯å¹³æ–‡ã‚’é¸ã‚“ã§ãã ã•ã„。 +

    + MIME
    + 平文

    +全部設定 +

    - ¼«Ê¬¤¬Åê¹Æ¤·¤¿¥á¡¼¥ë¤ò¼õ¤±¼è¤ê¤Þ¤¹¤«?

    - Ä̾ï¤Ï¡¢¼«Ê¬¤¬¥ê¥¹¥È¤ËÅê¹Æ¤·¤¿¥á¡¼¥ë¤Î¥³¥Ô¡¼¤òÁ÷¿®¤·¤Þ¤¹¡£ - ¼«Ê¬¤¬Åê¹Æ¤·¤¿¥á¡¼¥ë¤Î¥³¥Ô¡¼¤ò¼õ¤±¼è¤ê¤¿¤¯¤Ê¤±¤ì¤Ð¡¢ - ¤³¤Î¥ª¥×¥·¥ç¥ó¤ò¤¤¤¤¤¨¤ËÀßÄꤷ¤Æ¤¯¤À¤µ¤¤¡£ -

    - ¤¤¤¤¤¨
    - ¤Ï¤¤ -
    - ¥ê¥¹¥È¤Ø¤ÎÅê¹Æ¤ËÂФ¹¤ë³ÎǧÄÌÃΤ¬É¬ÍפǤ¹¤«?

    -

    - ¤¤¤¤¤¨
    - ¤Ï¤¤ -
    - ¤³¤Î¥ê¥¹¥È¤«¤é¤Î¥Ñ¥¹¥ï¡¼¥ÉÄÌÃΤò¼õ¤±¼è¤ê¤Þ¤¹¤«?

    - ¤Ï¤¤¤òÁª¤Ö¤È¡¢¤³¤Î¥Û¥¹¥È¤Ç±¿ÍѤµ¤ì¤ë³Æ¥á¡¼¥ê¥ó¥°¥ê¥¹¥È¤«¤é¡¢Ëè·î - ¥Ñ¥¹¥ï¡¼¥ÉÈ÷˺ÄÌÃΥ᡼¥ë¤ò¼õ¤±¼è¤ë¤³¤È¤Ë¤Ê¤ê¤Þ¤¹¡£ - ¤¤¤¤¤¨¤òÁª¤Ö¤È¡¢¤³¤Î¥á¡¼¥ê¥ó¥°¥ê¥¹¥È¤«¤é¤ÎÈ÷˺ÄÌÃÎ - ¤ÎÁ÷¿®¤ò»ß¤á¤ë¤³¤È¤¬¤Ç¤­¤Þ¤¹¡£Á´ÉôÀßÄê¤ò¥Á¥§¥Ã¥¯¤·¤Æ - ¤¤¤¤¤¨¤òÀßÄꤹ¤ë¤È¡¢È÷˺ÄÌÃΥ᡼¥ë¤Ï´°Á´¤ËÁ÷¤é¤ì¤Ê¤¯¤Ê¤ê¤Þ¤¹¡£ -

    - ¤¤¤¤¤¨
    - ¤Ï¤¤

    - Á´ÉôÀßÄê -

    - ²ñ°÷¥ê¥¹¥È¤Ç¥¢¥É¥ì¥¹¤òÈóɽ¼¨¤Ë¤·¤Þ¤¹¤«?
    - ¥ê¥¹¥È¤Î²ñ°÷̾Êí¤Ë¤Ï¡¢¤¢¤Ê¤¿¤Î¥á¡¼¥ë¥¢¥É¥ì¥¹¤¬É½¼¨¤µ¤ì¤Þ¤¹ - (¤¿¤À¤·ÌÂÏǥ᡼¥ë¤Î¥¢¥É¥ì¥¹¼ý½¸¤òËɤ°¤¿¤á¤Ëɽ¼¨¤ò¤¢¤¤¤Þ¤¤¤Ë¤·¤Æ¤¤¤Þ¤¹)¡£ - ²ñ°÷̾Êí¤Ë¼«Ê¬¤Î¥á¡¼¥ë¥¢¥É¥ì¥¹¤òɽ¼¨¤µ¤»¤¿¤¯¤Ê¤¤¾ì¹ç¤Ï¡¢ - ±£¤·¤Æ¤¯¤À¤µ¤¤¤òÁª¤Ó¤Þ¤¹¡£ -
    - ɽ¼¨¤·¤Þ¤¹
    - ±£¤·¤Æ¤¯¤À¤µ¤¤ -
    - ¤É¤Î¸À¸ì¤ò»È¤¤¤Þ¤¹¤«?

    -

    - -
    - ¼õ¿®¤·¤¿¤¤ÏÃÂê¤ÎʬÎà¤Ï?

    - ¥á¡¼¥ê¥ó¥°¥ê¥¹¥È¤ËÁ÷¤ê½Ð¤µ¤ì¤ë¥á¡¼¥ë¤Ë - ¥Õ¥£¥ë¥¿¤ò¤«¤±¤ë¤³¤È¤¬¤Ç¤­¤Þ¤¹¡£ - ¼õ¤±¼è¤ê¤¿¤¤ÏÃÂê¤òÁª¤ó¤Ç¤¯¤À¤µ¤¤¡£ - ÁªÂò¤·¤¿ÏÃÂê¤Ë³ºÅö¤·¤Ê¤¤¥á¡¼¥ë¤ÏÁ÷¤é¤ì¤Ê¤¯¤Ê¤ê¤Þ¤¹¡£ - -

    ¤É¤ÎÏÃÂê¤Ë¤â³ºÅö¤·¤Ê¤¤¥á¡¼¥ë¤ÎÇÛÁ÷µ¬Â§¤Ï¡¢²¼¤Î¹àÌܤÇÀßÄꤷ¤Þ¤¹¡£ - ²¿¤âÏÃÂê¤òÁªÂò¤·¤Ê¤±¤ì¤Ð¡¢¤¹¤Ù¤Æ¤Î¥á¡¼¥ë¤òÇÛÁ÷¤·¤Þ¤¹¡£ -

    - -
    - ¤É¤ÎÏÃÂê¥Õ¥£¥ë¥¿¤Ë¤â¹çÃפ·¤Ê¤¤¥á¡¼¥ë¤ò¼õ¤±¼è¤ê¤Þ¤¹¤«?

    - Á°¤ÎÀßÄê¤ÇÏÃÂê¤òÁªÂò¤·¤¿¾ì¹ç¤Ë¡¢ - ¤É¤ÎÏÃÂê¤Ë¤â³ºÅö¤·¤Ê¤¤¥á¡¼¥ë¤ËÂФ¹¤ë¥Ç¥Õ¥©¥ë¥È¤ÎÇÛÁ÷µ¬Â§¤ò·è¤á¤Þ¤¹¡£ - ¤¤¤¤¤¨¤òÁª¤Ö¤È¡¢¤É¤ÎÏÃÂê¤Ë¤â³ºÅö¤·¤Ê¤¤¥á¡¼¥ë¤ÏÇÛÁ÷¤·¤Þ¤»¤ó¡£ - ¤Ï¤¤¤òÁª¤Ö¤È¡¢¤É¤ÎÏÃÂê¤Ë¤â³ºÅö¤·¤Ê¤¤¥á¡¼¥ë¤òÇÛÁ÷¤·¤Þ¤¹¡£ - -

    Á°¤ÎÀßÄê¤Ç²¿¤âÏÃÂê¤òÁªÂò¤·¤Ê¤«¤Ã¤¿¾ì¹ç¤Ï¡¢ - ¤³¤Î¥ª¥×¥·¥ç¥ó¤Ç¤ÎÀßÄê¤Ë¤«¤«¤ï¤é¤º¡¢ - ¤¹¤Ù¤Æ¤Î¥á¡¼¥ë¤òÇÛÁ÷¤·¤Þ¤¹¡£ -

    - ¤¤¤¤¤¨
    - ¤Ï¤¤ -
    - ½ÅÊ£¤·¤¿¥á¡¼¥ë¤ò¼õ¤±¼è¤é¤Ê¤¤¤è¤¦¤Ë¤·¤Þ¤¹¤«?

    - - ¼«Ê¬¼«¿È¤Î¥¢¥É¥ì¥¹¤¬To:¤Þ¤¿¤ÏCc:¥Ø¥Ã¥À¤Ë - ´Þ¤Þ¤ì¤Æ¤¤¤ë¤È¤­¤Ï¡¢¥á¡¼¥ê¥ó¥°¥ê¥¹¥È¤«¤é¤Î¥á¡¼¥ë¤ò¼õ¤±¼è¤é¤Ê¤¤ - ¤è¤¦¤Ë¤¹¤ë¤³¤È¤¬¤Ç¤­¤Þ¤¹¡£ - ¤Ï¤¤¤òÁª¤Ö¤È¡¢½ÅÊ£¤¹¤ë¥á¡¼¥ë¤ò¼õ¤±¼è¤ê¤Þ¤»¤ó¡£ - ¤¤¤¤¤¨¤òÁª¤Ö¤È¡¢½ÅÊ£¤¹¤ë¥á¡¼¥ë¤â¼õ¤±¼è¤ê¤Þ¤¹¡£ - -

    ¥ê¥¹¥È¤¬¸Ä¿ÍÊÌÇÛÁ÷¥â¡¼¥É¤Ë¤Ê¤Ã¤Æ¤¤¤ë¾ì¹ç¡¢ - ¡Ö¤¤¤¤¤¨¡×¤òÁª¤Ö¤È X-Mailman-Copy: yes ¤È¤¤¤¦ - ¥Ø¥Ã¥À¤¬ÉÕ¤¯¤è¤¦¤Ë¤Ê¤ê¤Þ¤¹¡£ - -

    - ¤¤¤¤¤¨
    - ¤Ï¤¤
    - Á´ÉôÀßÄê -
    -
    -
    + è‡ªåˆ†ãŒæŠ•ç¨¿ã—ãŸãƒ¡ãƒ¼ãƒ«ã‚’å—ã‘å–りã¾ã™ã‹?

    + 通常ã¯ã€è‡ªåˆ†ãŒãƒªã‚¹ãƒˆã«æŠ•稿ã—ãŸãƒ¡ãƒ¼ãƒ«ã®ã‚³ãƒ”ーをé€ä¿¡ã—ã¾ã™ã€‚ + è‡ªåˆ†ãŒæŠ•ç¨¿ã—ãŸãƒ¡ãƒ¼ãƒ«ã®ã‚³ãƒ”ーをå—ã‘å–りãŸããªã‘れã°ã€ + ã“ã®ã‚ªãƒ—ションをã„ã„ãˆã«è¨­å®šã—ã¦ãã ã•ã„。 +

    + ã„ã„ãˆ
    + ã¯ã„ +
    + リストã¸ã®æŠ•稿ã«å¯¾ã™ã‚‹ç¢ºèªé€šçŸ¥ãŒå¿…è¦ã§ã™ã‹?

    +

    + ã„ã„ãˆ
    + ã¯ã„ +
    +ã“ã®ãƒªã‚¹ãƒˆã‹ã‚‰ã®ãƒ‘スワード通知をå—ã‘å–りã¾ã™ã‹?

    + ã¯ã„ã‚’é¸ã¶ã¨ã€ã“ã®ãƒ›ã‚¹ãƒˆã§é‹ç”¨ã•れるå„メーリングリストã‹ã‚‰ã€æ¯Žæœˆ + パスワード備忘通知メールをå—ã‘å–ã‚‹ã“ã¨ã«ãªã‚Šã¾ã™ã€‚ + ã„ã„ãˆã‚’é¸ã¶ã¨ã€ã“ã®ãƒ¡ãƒ¼ãƒªãƒ³ã‚°ãƒªã‚¹ãƒˆã‹ã‚‰ã®å‚™å¿˜é€šçŸ¥ + ã®é€ä¿¡ã‚’æ­¢ã‚ã‚‹ã“ã¨ãŒã§ãã¾ã™ã€‚全部設定をãƒã‚§ãƒƒã‚¯ã—㦠+ ã„ã„ãˆã‚’設定ã™ã‚‹ã¨ã€å‚™å¿˜é€šçŸ¥ãƒ¡ãƒ¼ãƒ«ã¯å®Œå…¨ã«é€ã‚‰ã‚Œãªããªã‚Šã¾ã™ã€‚ +

    +ã„ã„ãˆ
    +ã¯ã„

    +全部設定 +

    + 会員リストã§ã‚¢ãƒ‰ãƒ¬ã‚¹ã‚’éžè¡¨ç¤ºã«ã—ã¾ã™ã‹?
    + リストã®ä¼šå“¡åç°¿ã«ã¯ã€ã‚ãªãŸã®ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ãŒè¡¨ç¤ºã•れã¾ã™ + (ãŸã ã—迷惑メールã®ã‚¢ãƒ‰ãƒ¬ã‚¹åŽé›†ã‚’防ããŸã‚ã«è¡¨ç¤ºã‚’ã‚ã„ã¾ã„ã«ã—ã¦ã„ã¾ã™)。 + 会員åç°¿ã«è‡ªåˆ†ã®ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ã‚’表示ã•ã›ãŸããªã„å ´åˆã¯ã€ + éš ã—ã¦ãã ã•ã„ã‚’é¸ã³ã¾ã™ã€‚ +
    + 表示ã—ã¾ã™
    + éš ã—ã¦ãã ã•ã„ +
    + ã©ã®è¨€èªžã‚’使ã„ã¾ã™ã‹?

    +

    + +
    +å—ä¿¡ã—ãŸã„話題ã®åˆ†é¡žã¯?

    + メーリングリストã«é€ã‚Šå‡ºã•れるメール㫠+ フィルタをã‹ã‘ã‚‹ã“ã¨ãŒã§ãã¾ã™ã€‚ + å—ã‘å–りãŸã„話題をé¸ã‚“ã§ãã ã•ã„。 + é¸æŠžã—ãŸè©±é¡Œã«è©²å½“ã—ãªã„メールã¯é€ã‚‰ã‚Œãªããªã‚Šã¾ã™ã€‚ + +

    ã©ã®è©±é¡Œã«ã‚‚該当ã—ãªã„メールã®é…é€è¦å‰‡ã¯ã€ä¸‹ã®é …ç›®ã§è¨­å®šã—ã¾ã™ã€‚ + ä½•ã‚‚è©±é¡Œã‚’é¸æŠžã—ãªã‘れã°ã€ã™ã¹ã¦ã®ãƒ¡ãƒ¼ãƒ«ã‚’é…é€ã—ã¾ã™ã€‚ +

    + +
    +ã©ã®è©±é¡Œãƒ•ィルタã«ã‚‚åˆè‡´ã—ãªã„メールをå—ã‘å–りã¾ã™ã‹?

    + å‰ã®è¨­å®šã§è©±é¡Œã‚’é¸æŠžã—ãŸå ´åˆã«ã€ + ã©ã®è©±é¡Œã«ã‚‚該当ã—ãªã„メールã«å¯¾ã™ã‚‹ãƒ‡ãƒ•ォルトã®é…é€è¦å‰‡ã‚’決ã‚ã¾ã™ã€‚ + ã„ã„ãˆã‚’é¸ã¶ã¨ã€ã©ã®è©±é¡Œã«ã‚‚該当ã—ãªã„メールã¯é…é€ã—ã¾ã›ã‚“。 + ã¯ã„ã‚’é¸ã¶ã¨ã€ã©ã®è©±é¡Œã«ã‚‚該当ã—ãªã„メールをé…é€ã—ã¾ã™ã€‚ + +

    å‰ã®è¨­å®šã§ä½•ã‚‚è©±é¡Œã‚’é¸æŠžã—ãªã‹ã£ãŸå ´åˆã¯ã€ + ã“ã®ã‚ªãƒ—ションã§ã®è¨­å®šã«ã‹ã‹ã‚らãšã€ + ã™ã¹ã¦ã®ãƒ¡ãƒ¼ãƒ«ã‚’é…é€ã—ã¾ã™ã€‚ +

    +ã„ã„ãˆ
    +ã¯ã„ +
    +é‡è¤‡ã—ãŸãƒ¡ãƒ¼ãƒ«ã‚’å—ã‘å–らãªã„よã†ã«ã—ã¾ã™ã‹?

    + + 自分自身ã®ã‚¢ãƒ‰ãƒ¬ã‚¹ãŒTo:ã¾ãŸã¯Cc:ヘッダ㫠+ å«ã¾ã‚Œã¦ã„ã‚‹ã¨ãã¯ã€ãƒ¡ãƒ¼ãƒªãƒ³ã‚°ãƒªã‚¹ãƒˆã‹ã‚‰ã®ãƒ¡ãƒ¼ãƒ«ã‚’å—ã‘å–らãªã„ + よã†ã«ã™ã‚‹ã“ã¨ãŒã§ãã¾ã™ã€‚ + ã¯ã„ã‚’é¸ã¶ã¨ã€é‡è¤‡ã™ã‚‹ãƒ¡ãƒ¼ãƒ«ã‚’å—ã‘å–りã¾ã›ã‚“。 + ã„ã„ãˆã‚’é¸ã¶ã¨ã€é‡è¤‡ã™ã‚‹ãƒ¡ãƒ¼ãƒ«ã‚‚å—ã‘å–りã¾ã™ã€‚ + +

    リストãŒå€‹äººåˆ¥é…é€ãƒ¢ãƒ¼ãƒ‰ã«ãªã£ã¦ã„ã‚‹å ´åˆã€ + 「ã„ã„ãˆã€ã‚’é¸ã¶ã¨ X-Mailman-Copy: yes ã¨ã„ㆠ+ ヘッダãŒä»˜ãよã†ã«ãªã‚Šã¾ã™ã€‚ + +

    +ã„ã„ãˆ
    +ã¯ã„
    +全部設定 +
    +
    +
    - -

    - - - - + + +

    diff --git a/templates/ja/private.html b/templates/ja/private.html index a0da2abd..10779ec4 100755 --- a/templates/ja/private.html +++ b/templates/ja/private.html @@ -1,58 +1,119 @@ - %(realname)s ¸ÂÄê¸ø³«Êݸ½ñ¸Ë ǧ¾Ú +%(realname)s é™å®šå…¬é–‹ä¿å­˜æ›¸åº« èªè¨¼ - - -
    + + + %(message)s - - - - - - - - - - - - - - - -
    - %(realname)s - ¸ÂÄê¸ø³«Êݸ½ñ¸Ë ǧ¾Ú -
    ÅŻҥ᡼¥ë¥¢¥É¥ì¥¹:
    ¥Ñ¥¹¥ï¡¼¥É:
    -
    -

    ½ÅÍ×: - ¤³¤³¤«¤éÀè¤Ï¥Ö¥é¥¦¥¶¤Î¥¯¥Ã¥­¡¼¤òÍ­¸ú¤Ë¤·¤Æ¤¯¤À¤µ¤¤¡£ - ¥¯¥Ã¥­¡¼¤¬Ìµ¸ú¤À¤ÈÁàºî¤Î¤¿¤Ó¤Ëǧ¾Ú¤òÍ׵ᤵ¤ì¤Þ¤¹¡£ + + + + + + + + + + + + + + + +
    +%(realname)s + é™å®šå…¬é–‹ä¿å­˜æ›¸åº« èªè¨¼ +
    é›»å­ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹:
    パスワード:
    +
    +

    é‡è¦: + ã“ã“ã‹ã‚‰å…ˆã¯ãƒ–ラウザã®ã‚¯ãƒƒã‚­ãƒ¼ã‚’有効ã«ã—ã¦ãã ã•ã„。 + クッキーãŒç„¡åйã ã¨æ“作ã®ãŸã³ã«èªè¨¼ã‚’è¦æ±‚ã•れã¾ã™ã€‚ -

    ¸ÂÄê¸ø³«Êݸ½ñ¸Ë¥¤¥ó¥¿¡¼¥Õ¥§¡¼¥¹¤Ç¤ÎÁàºî¤ò¤¹¤ë¤¿¤Ó¤Ëǧ¾Ú¤¬Í×µá - ¤µ¤ì¤Ê¤¤¤è¤¦¤Ë¡¢¥»¥Ã¥·¥ç¥ó¥¯¥Ã¥­¡¼¤òȯ¹Ô¤·¤Þ¤¹¡£ - ¤³¤Î¥¯¥Ã¥­¡¼¤Ï¥Ö¥é¥¦¥¶¤Î½ªÎ»»þ¤Ë¼«Æ°Åª¤Ë¼º¸ú¤·¤Þ¤¹¡£ - ¤Þ¤¿¡¢²ñ°÷¥ª¥×¥·¥ç¥ó¤Î¥Ú¡¼¥¸¤Ë°Üư¤·¤Æ¥í¥°¥¢¥¦¥È - ¥Ü¥¿¥ó¤ò²¡¤¹¤³¤È¤ÇÍۤ˥¯¥Ã¥­¡¼¤òÇË´þ¤¹¤ë¤³¤È¤â¤Ç¤­¤Þ¤¹¡£ +

    é™å®šå…¬é–‹ä¿å­˜æ›¸åº«ã‚¤ãƒ³ã‚¿ãƒ¼ãƒ•ェースã§ã®æ“作をã™ã‚‹ãŸã³ã«èªè¨¼ãŒè¦æ±‚ + ã•れãªã„よã†ã«ã€ã‚»ãƒƒã‚·ãƒ§ãƒ³ã‚¯ãƒƒã‚­ãƒ¼ã‚’発行ã—ã¾ã™ã€‚ + ã“ã®ã‚¯ãƒƒã‚­ãƒ¼ã¯ãƒ–ラウザã®çµ‚了時ã«è‡ªå‹•çš„ã«å¤±åйã—ã¾ã™ã€‚ + ã¾ãŸã€ä¼šå“¡ã‚ªãƒ—ションã®ãƒšãƒ¼ã‚¸ã«ç§»å‹•ã—ã¦ãƒ­ã‚°ã‚¢ã‚¦ãƒˆ + ボタンを押ã™ã“ã¨ã§é™½ã«ã‚¯ãƒƒã‚­ãƒ¼ã‚’破棄ã™ã‚‹ã“ã¨ã‚‚ã§ãã¾ã™ã€‚

    - - - - - - - - - - -
    - ¥Ñ¥¹¥ï¡¼¥ÉÈ÷˺ÄÌÃÎ -
    ¥Ñ¥¹¥ï¡¼¥É¤¬¤ï¤«¤é¤Ê¤¤¤È¤­¤Ï¡¢¾å¤Î¥Õ¥©¡¼¥à¤Ë¥á¡¼¥ë¥¢¥É¥ì¥¹¤òÆþ¤ì¤Æ - È÷˺ÄÌÃΥܥ¿¥ó¤ò²¡¤·¤Æ²¼¤µ¤¤¡£ - ¥á¡¼¥ë¤Ç¥Ñ¥¹¥ï¡¼¥É¤òÁ÷¤ê¤Þ¤¹¡£
    -

    + + + + + + + + + + +
    +パスワード備忘通知 +
    パスワードãŒã‚ã‹ã‚‰ãªã„ã¨ãã¯ã€ä¸Šã®ãƒ•ォームã«ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ã‚’入れ㦠+ 備忘通知ボタンを押ã—ã¦ä¸‹ã•ã„。 + メールã§ãƒ‘スワードをé€ã‚Šã¾ã™ã€‚
    +

    diff --git a/templates/ja/roster.html b/templates/ja/roster.html index 27950a61..e6a9b5ff 100644 --- a/templates/ja/roster.html +++ b/templates/ja/roster.html @@ -1,55 +1,117 @@ - - - <MM-List-Name> ̾ - - - - -

    - - - - - -
    - - ̾ - - - -
    - - - + + + + + + + + + +
    -

    ʬΥ᡼륢ɥ쥹򥯥åȡ + + +<mm-list-name> ̾</mm-list-name> + + + +

    + + + + + +
    + + ̾ + + + +
    + + + - - - - - - - - - -
    +

    ʬΥ᡼륢ɥ쥹򥯥åȡ ץڡɽޤ��� -
    (�����ι��ܤ����������β���Ǥ���)

    -
    -
    - - �����������: ̾ -
    -
    -
    - - �ޤȤ��ɤ߲��: ̾ -
    -
    -

    -

    -

    -

    - - - +
    (�����ι��ܤ����������β���Ǥ���)

    +
    +
    + +�����������: ̾ +
    +
    +
    + +�ޤȤ��ɤ߲��: ̾ +
    +
    +

    +

    +

    +

    + +

    + diff --git a/templates/ja/subscribe.html b/templates/ja/subscribe.html index 742921b1..957bb79c 100644 --- a/templates/ja/subscribe.html +++ b/templates/ja/subscribe.html @@ -1,9 +1,72 @@ -<MM-List-Name> Æþ²ñ¼ê³¤­¤Î·ë²Ì +<mm-list-name> 入会手続ãã®çµæžœ</mm-list-name> -

    Æþ²ñ¼ê³¤­¤Î·ë²Ì

    - - - +

    入会手続ãã®çµæžœ

    + + + diff --git a/templates/ko/admindbdetails.html b/templates/ko/admindbdetails.html index ba825d66..17b8e46b 100644 --- a/templates/ko/admindbdetails.html +++ b/templates/ko/admindbdetails.html @@ -1,56 +1,117 @@ -°ü¸®ÀÚ ¿äûÀº µÎ°¡Áö ¹æ½Ä Áß Çϳª·Î º¸¿©Áý´Ï´Ù. ÇѰ¡Áö´Â
    ¿ä¾àÇÑ ÆäÀÌÁö, ´Ù¸¥ ÇѰ¡Áö´Â ÀÚ¼¼ÇÑ -ÆäÀÌÁöÀÔ´Ï´Ù. ¿ä¾àÇÑ ÆäÀÌÁö´Â º¸³½ÀÌÀÇ E¸ÞÀÏ ÁÖ¼Ò·Î ±×·ìÈ­ ÇÏ¿© -°¡ÀÔ, Å»Åð, ´ç½ÅÀÇ ½ÂÀÎÀ» ±â´Ù¸®°í ÀÖ´Â ±ÛµéÀ» Æ÷ÇÔÇϰí ÀÖ½À´Ï´Ù. -ÀÚ¼¼ÇÑ ÆäÀÌÁö´Â °¢ ¸Þ¼¼Áö¿¡ ´ëÇÑ ´õ ÀÚ¼¼ÇÑ °ÍÀ» º¸ÇÔÇϰí ÀÖ½À´Ï´Ù. -¿¹¸¦ µé¸é ¸Þ¼¼ÁöÀÇ Çì´õ ¿Í ¸Þ¼¼Áö º»¹®ÀÇ ±ÛµéÀÌ ÀÖ½À´Ï´Ù. +ê´€ë¦¬ìž ìš”ì²­ì€ ë‘가지 ë°©ì‹ ì¤‘ 하나로 보여집니다. 한가지는 요약한 페ì´ì§€, 다른 한가지는 ìžì„¸í•œ +페ì´ì§€ìž…니다. 요약한 페ì´ì§€ëŠ” 보낸ì´ì˜ Eë©”ì¼ ì£¼ì†Œë¡œ 그룹화 하여 +가입, 탈퇴, ë‹¹ì‹ ì˜ ìŠ¹ì¸ì„ 기다리고 있는 ê¸€ë“¤ì„ í¬í•¨í•˜ê³  있습니다. +ìžì„¸í•œ 페ì´ì§€ëŠ” ê° ë©”ì„¸ì§€ì— ëŒ€í•œ ë” ìžì„¸í•œ ê²ƒì„ ë³´í•¨í•˜ê³  있습니다. +예를 들면 ë©”ì„¸ì§€ì˜ í—¤ë” ì™€ 메세지 ë³¸ë¬¸ì˜ ê¸€ë“¤ì´ ìžˆìŠµë‹ˆë‹¤. -

    ¸ðµç ÆäÀÌÁö¿¡¼­ ´ÙÀ½ÀÇ ÇൿÀ» ÃëÇÒ ¼ö ÀÖ½À´Ï´Ù: +

    모든 페ì´ì§€ì—서 다ìŒì˜ í–‰ë™ì„ 취할 수 있습니다:

      -
    • ¿¬±âÇϱâ - ³ªÁß¿¡ °áÁ¤Çϵµ·Ï ÇöÀç °áÁ¤À» ¿¬±âÇÕ´Ï´Ù. -¸¸¾à ´ç½ÅÀÌ ¸Þ¼¼Áö¸¦ Áö¿ìÁö ¾Ê°í, ´Ù¸¥ »ç¶÷¿¡°Ô º¸³»°Å³ª, º¸°ü(¾Æ·¡¸¦ ÂüÁ¶Çϼ¼¿ä -.)Çϱæ -¿øÇÑ´Ù¸é ÀÌ °áÁ¤À» »ç¿ëÇÒ ¼ö ÀÖ½À´Ï´Ù. -
    • ½ÂÀÎÇϱâ - ¸Þ¼¼Áö¸¦ ½ÂÀÎÇÏ¿© ȸ¿øµé¿¡°Ô º¸³À´Ï´Ù. -
    • °ÅÀýÇϱâ - ¸Þ¼¼Áö¸¦ °ÅÀýÇÕ´Ï´Ù. º¸³½ÀÌ¿¡°Ô °ÅÀý¿¡ °üÇÑ °øÁö¸¦ º¸³À´Ï> -´Ù. -¿ø·¡ ¸Þ¼¼Áö´Â ¹ö·ÁÁö°Ô µË´Ï´Ù. -
    • ¹ö¸®±â - º¸³½ÀÌ¿¡°Ô °ÅÀý °øÁö¸¦ º¸³»Áö ¾Ê°í ¸Þ¼¼Áö¸¦ ¹ö·Á ¹ö¸³´Ï´Ù. -ÀÌ °áÁ¤Àº ½ºÆÔ ¸ÞÀÏÀÇ °æ¿ì ¾µ¸ðÀÖÀ» °ÍÀÔ´Ï´Ù. -
    +
  • 연기하기 - ë‚˜ì¤‘ì— ê²°ì •í•˜ë„ë¡ í˜„ìž¬ ê²°ì •ì„ ì—°ê¸°í•©ë‹ˆë‹¤. +만약 ë‹¹ì‹ ì´ ë©”ì„¸ì§€ë¥¼ 지우지 않고, 다른 사람ì—게 보내거나, ë³´ê´€(아래를 참조하세요 +.)하길 +ì›í•œë‹¤ë©´ ì´ ê²°ì •ì„ ì‚¬ìš©í•  수 있습니다. +
  • 승ì¸í•˜ê¸° - 메세지를 승ì¸í•˜ì—¬ 회ì›ë“¤ì—게 보냅니다. +
  • 거절하기 - 메세지를 거절합니다. 보낸ì´ì—게 ê±°ì ˆì— ê´€í•œ 공지를 보냅니> +다. +ì›ëž˜ 메세지는 버려지게 ë©ë‹ˆë‹¤. +
  • 버리기 - 보낸ì´ì—게 ê±°ì ˆ 공지를 ë³´ë‚´ì§€ 않고 메세지를 버려 버립니다. +ì´ ê²°ì •ì€ ìŠ¤íŒ¸ ë©”ì¼ì˜ 경우 ì“¸ëª¨ìžˆì„ ê²ƒìž…ë‹ˆë‹¤. +
  • +

    만약 사ì´íЏ 관리ìžë¥¼ 위해 ì´ ë©”ì„¸ì§€ì˜ ë³µì‚¬ë³¸ì´ í•„ìš”í•˜ë‹¤ë©´ +ë³´ê´€í•˜ê¸°ì˜µì…˜ì„ ì²´í¬í•˜ì‹­ì‹œì˜¤. ì´ê²ƒì€ ë…ì„¤ì˜ ë©”ì„¸ì§€ì˜ ê²½ìš° 쓸모 +ìžˆì„ ê²ƒìž…ë‹ˆë‹¤. 만약 ì—¬ëŸ¬ë¶„ì´ ë¦¬ìŠ¤íŠ¸ì— ì—†ëŠ” ì–´ë–¤ ì´ì—게 메세지를 +전달하길 ì›í•œë‹¤ë©´ 전달할 주소를 기입하시고, 전달하기 ì˜µì…˜ì„ +ì²´í¬í•˜ì‹­ì‹œì˜¤. 리스트로 ë³´ë‚´ì§€ ì „ì— ìž¡ì•„ë‘” 메세지를 수정하기 위해 +ë‹¹ì‹ ì€ ìžê¸°ì—게(í˜¹ì€ ë¦¬ìŠ¤íŠ¸ 소유주)ì—서 ë©”ì¼ì„ 전달하여야 하며, +ì›ë³¸ 메세지는 버려야 합니다. 그럼 ë‹¹ì‹ ì˜ ë©”ì¼ìƒìžì— 메세지가 나타 +나면 ìˆ˜ì •ì„ í•˜ì—¬ ë¦¬ìŠ¤íŠ¸ì— ë©”ì¼ì„ 다시 보냅니다. 물론 승ì¸: + í—¤ë”ì—는 리스트 비밀번호를 í¬í•¨í•´ì•¼ 합니다. ì—¬ëŸ¬ë¶„ì´ ìˆ˜ì •í•œ +í…스트ì—서 재전송한 ì´ìœ ì— 대한 ì§§ì€ ì„¤ëª…ì„ ë¶™ì´ëŠ”ê²ƒ 온ë¼ì¸ 예절 +ì´ë¼ê³  ìƒê°í•©ë‹ˆë‹¤. -

    ¸¸¾à »çÀÌÆ® °ü¸®ÀÚ¸¦ À§ÇØ ÀÌ ¸Þ¼¼ÁöÀÇ º¹»çº»ÀÌ ÇÊ¿äÇÏ´Ù¸é -º¸°üÇϱâ¿É¼ÇÀ» üũÇϽʽÿÀ. À̰ÍÀº µ¶¼³ÀÇ ¸Þ¼¼ÁöÀÇ °æ¿ì ¾µ¸ð -ÀÖÀ» °ÍÀÔ´Ï´Ù. ¸¸¾à ¿©·¯ºÐÀÌ ¸®½ºÆ®¿¡ ¾ø´Â ¾î¶² ÀÌ¿¡°Ô ¸Þ¼¼Áö¸¦ -Àü´ÞÇÏ±æ ¿øÇÑ´Ù¸é Àü´ÞÇÒ ÁÖ¼Ò¸¦ ±âÀÔÇϽðí, Àü´ÞÇϱ⠿ɼÇÀ» -üũÇϽʽÿÀ. ¸®½ºÆ®·Î º¸³»Áö Àü¿¡ Àâ¾ÆµÐ ¸Þ¼¼Áö¸¦ ¼öÁ¤Çϱâ À§ÇØ -´ç½ÅÀº Àڱ⿡°Ô(ȤÀº ¸®½ºÆ® ¼ÒÀ¯ÁÖ)¿¡¼­ ¸ÞÀÏÀ» Àü´ÞÇÏ¿©¾ß Çϸç, -¿øº» ¸Þ¼¼Áö´Â ¹ö·Á¾ß ÇÕ´Ï´Ù. ±×·³ ´ç½ÅÀÇ ¸ÞÀÏ»óÀÚ¿¡ ¸Þ¼¼Áö°¡ ³ªÅ¸ -³ª¸é ¼öÁ¤À» ÇÏ¿© ¸®½ºÆ®¿¡ ¸ÞÀÏÀ» ´Ù½Ã º¸³À´Ï´Ù. ¹°·Ð ½ÂÀÎ: - Çì´õ¿¡´Â ¸®½ºÆ® ºñ¹Ð¹øÈ£¸¦ Æ÷ÇÔÇØ¾ß ÇÕ´Ï´Ù. ¿©·¯ºÐÀÌ ¼öÁ¤ÇÑ -ÅØ½ºÆ®¿¡¼­ ÀçÀü¼ÛÇÑ ÀÌÀ¯¿¡ ´ëÇÑ ÂªÀº ¼³¸íÀ» ºÙÀÌ´Â°Í ¿Â¶óÀÎ ¿¹Àý -À̶ó°í »ý°¢ÇÕ´Ï´Ù. +

    만약 보낸ì´ê°€ 글관리ë˜ëŠ” 리스트 회ì›ì´ë¼ë©´ ë‹¹ì‹ ì€ ì„ íƒì ìœ¼ë¡œ +ê·¸ë“¤ì˜ ê¸€ê´€ë¦¬ 기호를 없애줄 수 있습니다. ì´ê²ƒì€ ë‹¹ì‹ ì˜ ë¦¬ìŠ¤íŠ¸ê°€ +새로운 회ì›ì—게 글관리를 기본ì ìœ¼ë¡œ 설정하고, ë‹¹ì‹ ì´ ì´ íšŒì›ì— +대해 승ì¸ì—†ì´ ë¦¬ìŠ¤íŠ¸ì— ê¸€ì„ ì¨ë„ 문제가 없겠다 ìƒê°í•˜ëŠ” ê²½ìš°ì— +유용하게 ì‚¬ìš©ë  ìˆ˜ ìžˆì„ ê²ƒìž…ë‹ˆë‹¤. -

    ¸¸¾à º¸³½À̰¡ ±Û°ü¸®µÇ´Â ¸®½ºÆ® ȸ¿øÀ̶ó¸é ´ç½ÅÀº ¼±ÅÃÀûÀ¸·Î -±×µéÀÇ ±Û°ü¸® ±âÈ£¸¦ ¾ø¾ÖÁÙ ¼ö ÀÖ½À´Ï´Ù. À̰ÍÀº ´ç½ÅÀÇ ¸®½ºÆ®°¡ -»õ·Î¿î ȸ¿ø¿¡°Ô ±Û°ü¸®¸¦ ±âº»ÀûÀ¸·Î ¼³Á¤Çϰí, ´ç½ÅÀÌ ÀÌ È¸¿ø¿¡ -´ëÇØ ½ÂÀξøÀÌ ¸®½ºÆ®¿¡ ±ÛÀ» ½áµµ ¹®Á¦°¡ ¾ø°Ú´Ù »ý°¢ÇÏ´Â °æ¿ì¿¡ -À¯¿ëÇÏ°Ô »ç¿ëµÉ ¼ö ÀÖÀ» °ÍÀÔ´Ï´Ù. +

    만약 보낸ì´ê°€ 리스트 회ì›ì´ ì•„ë‹ ê²½ìš°, ë‹¹ì‹ ì€ ë³´ë‚¸ì´ ê±¸ëŸ¬ë‚´ê¸° +ì— Eë©”ì¼ ì£¼ì†Œë¥¼ 추가할 수 있습니다. ë³´ë‚¸ì´ ê±¸ëŸ¬ë‚´ê¸°ëŠ” ë³´ë‚¸ì´ ê±¸ëŸ¬ë‚´ê¸° ê°œì¸ì •ë³´ ì •ì±… 페ì´ì§€ì— 설명 +ë˜ì–´ 있스면 ìžë™-받아들임 (받아들임), ìžë™-잡아ë‘기 +(잡아ë‘기), ìžë™-거절하기 (거절하기, í˜¹ì€ ìžë™-버리기 +(버리기) 중 하나를 ì„ íƒí•˜ì‹¤ 수 있습니다. ì´ ì„¤ì •ì€ ì£¼ì†Œê°€ ì´ë¯¸ +ë³´ë‚¸ì´ ê±¸ëŸ¬ë‚´ê¸°ì— ì¡´ìž¬í•œë‹¤ë©´ ì´ìš©í•  수 없습니다. -

    ¸¸¾à º¸³½À̰¡ ¸®½ºÆ® ȸ¿øÀÌ ¾Æ´Ò °æ¿ì, ´ç½ÅÀº º¸³½ÀÌ °É·¯³»±â -¿¡ E¸ÞÀÏ ÁÖ¼Ò¸¦ Ãß°¡ÇÒ ¼ö ÀÖ½À´Ï´Ù. º¸³½ÀÌ °É·¯³»±â´Â º¸³½ÀÌ °É·¯³»±â °³ÀÎÁ¤º¸ Á¤Ã¥ ÆäÀÌÁö¿¡ ¼³¸í -µÇ¾î ÀÖ½º¸é ÀÚµ¿-¹Þ¾ÆµéÀÓ (¹Þ¾ÆµéÀÓ), ÀÚµ¿-Àâ¾ÆµÎ±â -(Àâ¾ÆµÎ±â), ÀÚµ¿-°ÅÀýÇϱâ (°ÅÀýÇϱâ, ȤÀº ÀÚµ¿-¹ö¸®±â -(¹ö¸®±â) Áß Çϳª¸¦ ¼±ÅÃÇÏ½Ç ¼ö ÀÖ½À´Ï´Ù. ÀÌ ¼³Á¤Àº ÁÖ¼Ò°¡ ÀÌ¹Ì -º¸³½ÀÌ °É·¯³»±â¿¡ Á¸ÀçÇÑ´Ù¸é ÀÌ¿ëÇÒ ¼ö ¾ø½À´Ï´Ù. +

    ì—¬ëŸ¬ë¶„ì˜ ê²°ì •ì´ ë나셨다면, ì´ íŽ˜ì´ì§€ì˜ 윗부분과 ì•„ëž˜ë¶€ë¶„ì— ìžˆëŠ” +ìžë£Œ 처리하기 ë²„í„´ì„ í´ë¦­í•˜ì„¸ìš”. ì´ ë²„í„´ì€ ê´€ë¦¬ì ì¸ ìš”ì²­ì„ +요구한 모든 ì‚¬í•­ì— ëŒ€í•œ ë‹¹ì‹ ì´ ê²°ì •í•œ í–‰ë™ì„ 취하ë„ë¡ í•  것입니다. +만약 ìš”ì²­ì— ëŒ€í•œ ê²°ì •ì„ ì§€ê¸ˆ 하지 않으실 ê±°ë©´ ì•„ë¬´ê²ƒë„ ì†ëŒ€ì§€ ë§ê³  +(ë¼ë””ì˜¤ë²„í„´ë„ í¬í•¨í•´ì„œ) ìžë£Œ ì²˜ë¦¬í•˜ê¸°ì„ ëˆ„ë¥´ì‹œë©´ 대기ìƒíƒœ +DB ì—서 제거ë˜ì§€ ì•Šì„ ê²ƒìž…ë‹ˆë‹¤. -

    ¿©·¯ºÐÀÇ °áÁ¤ÀÌ ³¡³ª¼Ì´Ù¸é, ÀÌ ÆäÀÌÁöÀÇ À­ºÎºÐ°ú ¾Æ·¡ºÎºÐ¿¡ ÀÖ´Â -ÀÚ·á ó¸®Çϱ⠹öÅÏÀ» Ŭ¸¯Çϼ¼¿ä. ÀÌ ¹öÅÏÀº °ü¸®ÀûÀÎ ¿äûÀ» -¿ä±¸ÇÑ ¸ðµç »çÇ׿¡ ´ëÇÑ ´ç½ÅÀÌ °áÁ¤ÇÑ ÇൿÀ» ÃëÇϵµ·Ï ÇÒ °ÍÀÔ´Ï´Ù. -¸¸¾à ¿äû¿¡ ´ëÇÑ °áÁ¤À» Áö±Ý ÇÏÁö ¾ÊÀ¸½Ç °Å¸é ¾Æ¹«°Íµµ ¼Õ´ëÁö ¸»°í -(¶óµð¿À¹öÅϵµ Æ÷ÇÔÇØ¼­) ÀÚ·á ó¸®ÇϱâÀ» ´©¸£½Ã¸é ´ë±â»óÅ -DB ¿¡¼­ Á¦°ÅµÇÁö ¾ÊÀ» °ÍÀÔ´Ï´Ù. - -

    ¿ä¾àÇÑ ÆäÀÌÁö·Î µ¹¾Æ°¡±â. +

    요약한 페ì´ì§€ë¡œ ëŒì•„가기. +

    \ No newline at end of file diff --git a/templates/ko/admindbpreamble.html b/templates/ko/admindbpreamble.html index 6100acd1..91c1106e 100644 --- a/templates/ko/admindbpreamble.html +++ b/templates/ko/admindbpreamble.html @@ -1,9 +1,73 @@ -ÀÌ ÆäÀÌÁö´Â ´ç½ÅÀÇ ½ÂÀÎÀ» ±â´Ù¸®°í ÀÖ´Â ±ÛÀÇ ´ëÇÑ Á¤º¸¸¦ Æ÷ÇÔÇϰí ÀÖ½À -´Ï´Ù. ÇöÀç %(description)s ·Î ³ªÅ¸³ª°í ÀÖ½À´Ï´Ù. +ì´ íŽ˜ì´ì§€ëŠ” ë‹¹ì‹ ì˜ ìŠ¹ì¸ì„ 기다리고 있는 ê¸€ì˜ ëŒ€í•œ 정보를 í¬í•¨í•˜ê³  있습 +니다. 현재 %(description)s 로 나타나고 있습니다. -

    °¢°¢ÀÇ °ü¸® ¿äû¿¡ ´ëÇØ °áÁ¤ÀÌ ³¡³ª¼Ì´Ù¸é ÀÚ·á ó¸®Çϱ⸦ -Ŭ¸¯ÇÏ¿© Áֽñ⠹ٶø´Ï´Ù. ´õ ÀÚ¼¼ÇÑ ¸í·É¾î¿¡ ´ëÇÑ Á¤º¸´Â -¿©±â¿¡¼­ º¼ ¼ö ÀÖ½À´Ï´Ù. +

    ê°ê°ì˜ 관리 ìš”ì²­ì— ëŒ€í•´ ê²°ì •ì´ ë나셨다면 ìžë£Œ 처리하기를 +í´ë¦­í•˜ì—¬ 주시기 ë°”ëžë‹ˆë‹¤. ë” ìžì„¸í•œ ëª…ë ¹ì–´ì— ëŒ€í•œ 정보는 +여기ì—서 ë³¼ 수 있습니다. -

    ¶ÇÇÑ ¿©·¯ºÐÀº ¸ðµç ´ë±â ¿äû¿¡ ´ëÇÑ -¿ä¾àÇÑ ÆäÀÌÁö¸¦ º¼ ¼ö ÀÖ½À´Ï´Ù. +

    ë˜í•œ ì—¬ëŸ¬ë¶„ì€ ëª¨ë“  대기 ìš”ì²­ì— ëŒ€í•œ +요약한 페ì´ì§€ë¥¼ ë³¼ 수 있습니다. +

    \ No newline at end of file diff --git a/templates/ko/admindbsummary.html b/templates/ko/admindbsummary.html index f3b27581..88a8b255 100644 --- a/templates/ko/admindbsummary.html +++ b/templates/ko/admindbsummary.html @@ -1,11 +1,75 @@ -ÀÌ ÆäÀÌÁö´Â %(listname)s ¸ÞÀϸµ ¸®½ºÆ® -¿¡¼­ ´ç½ÅÀÇ ½ÂÀÎÀ» ±â´Ù¸®°í ÀÖ´Â °ü¸® ¿äû¿¡ ´ëÇÑ ÇöÀç ¿ä¾à -³»¿ëÀ» °¡Áö°í ÀÖ½À´Ï´Ù. óÀ½¿¡ ¿©·¯ºÐÀº ´ç½ÅÀÇ ½ÂÀÎÀ» ±â´Ù¸®°í ÀÖ´Â -°¡ÀÔ È¤Àº Å»Åð ¿äûÀÌ ÀÖÀ¸¸é ±×°ÍÀ» º¸°Ô µÉ°ÍÀÔ´Ï´Ù. +ì´ íŽ˜ì´ì§€ëŠ” %(listname)s ë©”ì¼ë§ 리스트 +ì—서 ë‹¹ì‹ ì˜ ìŠ¹ì¸ì„ 기다리고 있는 관리 ìš”ì²­ì— ëŒ€í•œ 현재 요약 +ë‚´ìš©ì„ ê°€ì§€ê³  있습니다. 처ìŒì— ì—¬ëŸ¬ë¶„ì€ ë‹¹ì‹ ì˜ ìŠ¹ì¸ì„ 기다리고 있는 +가입 í˜¹ì€ íƒˆí‡´ ìš”ì²­ì´ ìžˆìœ¼ë©´ ê·¸ê²ƒì„ ë³´ê²Œ ë ê²ƒìž…니다. -

    °¢°¢ÀÇ °ü¸® ¿äû¿¡ ´ëÇÑ ÇൿÀ» ¼±ÅÃÇÏ½Ã°í °áÁ¤Çϼ̴ٸé ÀÚ·á -ó¸®Çϱ⸦ Ŭ¸¯ÇϽñ⠹ٶø´Ï´Ù. ¶ÇÇÑ -´õ ÀÚ¼¼ÇÑ ¸í·É¿¡ ´ëÇÑ °Íµµ À̿밡´É ÇÕ´Ï´Ù. +

    ê°ê°ì˜ 관리 ìš”ì²­ì— ëŒ€í•œ í–‰ë™ì„ ì„ íƒí•˜ì‹œê³  결정하셨다면 ìžë£Œ +처리하기를 í´ë¦­í•˜ì‹œê¸° ë°”ëžë‹ˆë‹¤. ë˜í•œ +ë” ìžì„¸í•œ ëª…ë ¹ì— ëŒ€í•œ ê²ƒë„ ì´ìš©ê°€ëŠ¥ 합니다. -

    ¶ÇÇÑ ¿©·¯ºÐÀº Àâ¾ÆµÐ ¸ðµç ±Ûµé¿¡ ´ëÇÑ -ÀÚ¼¼ÇÑ ¼³¸íÀ» º¼ ¼ö ÀÖ½À´Ï´Ù. +

    ë˜í•œ ì—¬ëŸ¬ë¶„ì€ ìž¡ì•„ë‘” 모든 ê¸€ë“¤ì— ëŒ€í•œ +ìžì„¸í•œ ì„¤ëª…ì„ ë³¼ 수 있습니다. +

    \ No newline at end of file diff --git a/templates/ko/admlogin.html b/templates/ko/admlogin.html index fac35fad..8d35efc3 100755 --- a/templates/ko/admlogin.html +++ b/templates/ko/admlogin.html @@ -1,34 +1,97 @@ - %(listname)s °ü¸®ÀÚ ÀÎÁõ +%(listname)s ê´€ë¦¬ìž ì¸ì¦ - - -
    + + + %(message)s - - - - - - - - - - - -
    - %(listname)s °ü¸®ÀÚ ÀÎÁõ - -
    ¸ÞÀϸµ ¸®½ºÆ® °ü¸®ÀÚ ºñ¹Ð¹øÈ£:
    -
    -

    Áß¿ä: ÀÌ ½ÃÁ¡ºÎÅÍ ¿©·¯ºÐÀÇ ºê¶ó¿ìÀú¿¡ - Äí۰¡ ÀúÀåµË´Ï´Ù. ÀÌ ºÎºÐºÎÅÍ ½ÃÀÛÇÏÁö ¾ÊÀ¸¸é, ´Ù¸¥ º¯°æµéÀº - ½ÇÁ¦·Î Àû¿ëµÇÁö ¾Ê½À´Ï´Ù. + + + + + + + + + + + +
    +%(listname)s ê´€ë¦¬ìž ì¸ì¦ + +
    ë©”ì¼ë§ 리스트 ê´€ë¦¬ìž ë¹„ë°€ë²ˆí˜¸:
    +
    +

    중요: ì´ ì‹œì ë¶€í„° ì—¬ëŸ¬ë¶„ì˜ ë¸Œë¼ìš°ì €ì— + 쿠키가 저장ë©ë‹ˆë‹¤. ì´ ë¶€ë¶„ë¶€í„° 시작하지 않으면, 다른 ë³€ê²½ë“¤ì€ + 실제로 ì ìš©ë˜ì§€ 않습니다. -

    Mailman ÀÇ °ü¸® ÀÎÅÍÆäÀ̽º¿¡´Â ¼¼¼Ç Äí۸¦ »ç¿ëÇϱ⠶§¹®¿¡ ¸Å¹ø - ÀÎÁõÇÒ Çʿ䰡 ¾øÀ¸¸ç ÀÌ ÄíŰ´Â ¿©·¯ºÐÀÌ ºê¶ó¿ìÀú¸¦ ³¡³Â°Å³ª, °ü¸®ÀÚ - È­¸é¿¡¼­ º¼¼ö ÀÖ´Â "·Î±×¾Æ¿ô"(¿©·¯ºÐÀÌ ¼º°øÀûÀ¸·Î ·Î±ä À» ÇÏ¸é º¼ ¼ö - ÀÖ½À´Ï´Ù.) ¸¦ Ŭ¸¯Çϸé ÀÚµ¿À¸·Î Áö¿öÁý´Ï´Ù. -

    +

    Mailman ì˜ ê´€ë¦¬ ì¸í„°íŽ˜ì´ìФì—는 세션 쿠키를 사용하기 ë•Œë¬¸ì— ë§¤ë²ˆ + ì¸ì¦í•  필요가 없으며 ì´ ì¿ í‚¤ëŠ” ì—¬ëŸ¬ë¶„ì´ ë¸Œë¼ìš°ì €ë¥¼ ë냈거나, ê´€ë¦¬ìž + 화면ì—서 볼수 있는 "로그아웃"(ì—¬ëŸ¬ë¶„ì´ ì„±ê³µì ìœ¼ë¡œ 로긴 ì„ í•˜ë©´ ë³¼ 수 + 있습니다.) 를 í´ë¦­í•˜ë©´ ìžë™ìœ¼ë¡œ 지워집니다. +

    + \ No newline at end of file diff --git a/templates/ko/article.html b/templates/ko/article.html index ff62361c..b4ea4c5d 100644 --- a/templates/ko/article.html +++ b/templates/ko/article.html @@ -1,12 +1,13 @@ - - - %(title)s - - - - - + +

    %(listname)s 저장소

    +

    + ì•„ì§ ì´ ë©”ì¼ë§ ë¦¬ìŠ¤íŠ¸ì— ì˜¨ ê¸€ì´ ì—†ìŠµë‹ˆë‹¤. 현재 저장소는 피어 있습니다. + ì´ ë©”ì¼ë§ ë¦¬ìŠ¤íŠ¸ì— ëŒ€í•œ ë” ë§Žì€ ì •ë³´ë¥¼ ì–»ì„ ìˆ˜ 있습니다.

    - - + + diff --git a/templates/ko/headfoot.html b/templates/ko/headfoot.html index 931a8960..ff18dc79 100644 --- a/templates/ko/headfoot.html +++ b/templates/ko/headfoot.html @@ -1,22 +1,85 @@ -ÀÌ ¹®¼­´Â ¸ÞÀϸµ ¸®½ºÆ®¿¡ ¿¹¾àµÇ¾î Àִ Ư¼ºÀÇ %(attribute)s -Çü½ÄÀ» Æ÷ÇÔÇϰí ÀÖ½À´Ï´Ù. ´õ ÀÚ¼¼ÇÑ ¼³¸íÀº -PythonÀÇ -¹®ÀÚ¿­ Çü½Ä ±ÔÄ¢À» º¸½Ã±â ¹Ù¶ø´Ï´Ù. ¸î¸î À¯¿ëÇÑ ¼Ó¼ºÀº ¾Æ·¡¿Í °°½À´Ï´Ù. +ì´ ë¬¸ì„œëŠ” ë©”ì¼ë§ ë¦¬ìŠ¤íŠ¸ì— ì˜ˆì•½ë˜ì–´ 있는 íŠ¹ì„±ì˜ %(attribute)s +형ì‹ì„ í¬í•¨í•˜ê³  있습니다. ë” ìžì„¸í•œ ì„¤ëª…ì€ +Pythonì˜ +문ìžì—´ í˜•ì‹ ê·œì¹™ì„ ë³´ì‹œê¸° ë°”ëžë‹ˆë‹¤. 몇몇 유용한 ì†ì„±ì€ 아래와 같습니다.
      -
    • real_name - ¸®½ºÆ®ÀÇ À̸§À» ³ªÅ¸³À´Ï´Ù. º¸Åë ´ë¹®ÀÚ·Î - ³ªÅ¸³³´Ï´Ù. +
    • real_name - ë¦¬ìŠ¤íŠ¸ì˜ ì´ë¦„ì„ ë‚˜íƒ€ëƒ…ë‹ˆë‹¤. 보통 대문ìžë¡œ + 나타납니다. -
    • list_name - ½ÇÁ¦ URL ¿¡¼­ »ç¿ëµÇ´Â ¸®½ºÆ®ÀÇ À̸§ +
    • list_name - 실제 URL ì—서 사용ë˜ëŠ” ë¦¬ìŠ¤íŠ¸ì˜ ì´ë¦„ -
    • host_name - ¸ÞÀϸµ ¸®½ºÆ®°¡ ¿î¿µµÇ´Â È£½ºÆ®ÀÇ À̸§ +
    • host_name - ë©”ì¼ë§ 리스트가 ìš´ì˜ë˜ëŠ” í˜¸ìŠ¤íŠ¸ì˜ ì´ë¦„ -
    • web_page_url - Mailman ÀÇ ±âº» URL ÀÔ´Ï´Ù. - À̰ÍÀº ¸ÞÀϸµ ¸®½ºÆ®ÀÇ ¸ñ·ÏÁ¤º¸ ÆäÀÌÁö¸¦ ³ªÅ¸³»´Â - listinfo/%(list_name)s ¿Í °°ÀÌ Ã·°¡ µÉ ¼ö ÀÖ½À´Ï´Ù. +
    • web_page_url - Mailman ì˜ ê¸°ë³¸ URL 입니다. + ì´ê²ƒì€ ë©”ì¼ë§ ë¦¬ìŠ¤íŠ¸ì˜ ëª©ë¡ì •ë³´ 페ì´ì§€ë¥¼ 나타내는 + listinfo/%(list_name)s 와 ê°™ì´ ì²¨ê°€ ë  ìˆ˜ 있습니다. -
    • description - ¸ÞÀϸµ ¸®½ºÆ®ÀÇ °£·«ÇÑ ¼³¸í +
    • description - ë©”ì¼ë§ ë¦¬ìŠ¤íŠ¸ì˜ ê°„ëžµí•œ 설명 -
    • info - ¸ÞÀϸµ ¸®½ºÆ®ÀÇ Àüü ¼³¸í -
    • cgiext - CGI ½ºÅ©¸³Æ®¸¦ À§ÇÑ È®Àå. -
    +
  • info - ë©”ì¼ë§ ë¦¬ìŠ¤íŠ¸ì˜ ì „ì²´ 설명 +
  • cgiext - CGI 스í¬ë¦½íŠ¸ë¥¼ 위한 확장. +
  • diff --git a/templates/ko/listinfo.html b/templates/ko/listinfo.html index 6211f7b9..35727de8 100644 --- a/templates/ko/listinfo.html +++ b/templates/ko/listinfo.html @@ -1,145 +1,206 @@ - - - - <MM-List-Name> Á¤º¸ º¸±â - - - -

    - - - - - - - - - - - - - - - - - - - + + + + + +

    - -- - -
    -

      -

    - ¼Ò°³ - - - -
    -

    -

    ¸®½ºÆ®¿¡ Æ÷½ºÆÃµÈ ÀÌÀü ±ÛµéÀ» º¸½Ç·Á¸é - - ÀúÀå¼Ò ¸¦ º¸½Ã±â ¹Ù¶ø´Ï´Ù. - -

    -
    - »ç¿ëÇϱâ -
    - ¸ÞÀϸµ ¸®½ºÆ® °¡ÀÔÀڵ鿡°Ô ¸Þ½ÃÁö¸¦ º¸³»·Á¸é - - À¸·Î ¸ÞÀÏÀ» º¸³»½Ã¸é µË´Ï´Ù.. + + + +<mm-list-name> ì •ë³´ 보기</mm-list-name> + + +

    + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + +
    + -- + +
    +

      +

    + 소개 + + + +
    +

    +

    ë¦¬ìŠ¤íŠ¸ì— í¬ìŠ¤íŒ…ëœ ì´ì „ ê¸€ë“¤ì„ ë³´ì‹¤ë ¤ë©´ + + 저장소 를 보시기 ë°”ëžë‹ˆë‹¤. + +

    +
    + 사용하기 +
    + ë©”ì¼ë§ 리스트 가입ìžë“¤ì—게 메시지를 보내려면 + + 으로 ë©”ì¼ì„ 보내시면 ë©ë‹ˆë‹¤.. -

    ¸ÞÀϸµ ¸®½ºÆ®¿¡ °¡ÀÔÇϰųª ÇöÀç °¡ÀԵǾî ÀÖ´Â »óŸ¦ - ¾Æ·¡¿¡¼­ ¹Ù²Ù½Ç ¼ö ÀÖ½À´Ï´Ù. -

    - °¡ÀÔÇϱâ -
    -

    - ´ÙÀ½ Ç׸ñµéÀ» ä¿ö Áּż­ ¿¡ °¡ÀÔ ÇϽʽÿÀ. - -

      - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + - - - - - -
      ÀüÀÚ¿ìÆíÁÖ¼Ò: -  
      À̸§ (¼±ÅûçÇ×): 
      °³ÀÎÀûÀ¸·Î »ç¿ëÇÏÁö ¾Ê´Â ÆÐ½º¿öµå¸¦ - »ç¿ëÇϽʽÿÀ. ¸ÞÀϸµ¸®½ºÆ®´Â ¿Ïº®ÇÑ º¸¾ÈÀ» Á¦°øÇÏÁö ¾Ê½À´Ï´Ù. - ÀÌ ÆÐ½º¿öµå´Â ´ÜÁö ½ºÆÐ¸Ó·Î ÀÎÇØ ¾Ç¿ëµÇÁö ¾Êµµ·Ï¸¸ ÇÕ´Ï´Ù. - ºñ¹Ð¹øÈ£´Â ¾Ïȣȭ ½ÃŰÁö ¾Ê°í ¿©·¯ºÐ¿¡°Ô º¸³»±â ¶§¹®¿¡ Àý´ë - ½ÇÁ¦ »ç¿ëÇÏ´Â ºñ¹Ð¹øÈ£´Â ÀÔ·ÂÇÏÁö ¸¶½Ê½Ã¿À. -

      ¸¸¾à ¿©·¯ºÐÀÌ ºñ¹Ð¹øÈ£¸¦ ÀÔ·ÂÇÏÁö ¾Ê´Â´Ù¸é ÀÚµ¿À¸·Î ´ç½ÅÀ» - À§ÇÑ ºñ¹Ð¹øÈ£°¡ »ý¼ºµÇ¸ç, ´ç½ÅÀÇ °¡ÀÔ È®ÀÎ ¸ÞÀÏ¿¡ ÇÔ²² º¸³» - Áý´Ï´Ù. °³ÀÎ Á¤º¸ º¯°æÀ» À§ÇÑ ºñ¹Ð¹øÈ£¸¦ ¾ðÁ¦³ª ´Ù½Ã º¸³» - µå¸³´Ï´Ù. - -
      -
      ºñ¹Ð¹øÈ£ÀÔ·Â: 
      ´Ù½ÃÇѹøÀÔ·Â: 
      »ç¿ëÇÒ ¾ð¾î¸¦ ¼±ÅÃÇϼ¼¿ä?  
      ¸Þ½ÃÁö¸¦ ÇÏ·ç¿¡ ÇÑ ¹ø¸¸ ¹­À½ Çü½ÄÀ¸·Î ¹Þ¾Æº¸µµ·Ï ¼³Á¤ÇϽðڽÀ´Ï±î? +

      ë©”ì¼ë§ ë¦¬ìŠ¤íŠ¸ì— ê°€ìž…í•˜ê±°ë‚˜ 현재 가입ë˜ì–´ 있는 ìƒíƒœë¥¼ + 아래ì—서 바꾸실 수 있습니다. +

      + 가입하기 +
      +

      + ë‹¤ìŒ í•­ëª©ë“¤ì„ ì±„ì›Œ 주셔서 ì— ê°€ìž… 하십시오. + +

        + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - -
        ì „ìžìš°íŽ¸ì£¼ì†Œ: + 
        ì´ë¦„ (ì„ íƒì‚¬í•­): 
        ê°œì¸ì ìœ¼ë¡œ 사용하지 않는 패스워드를 + 사용하십시오. ë©”ì¼ë§ë¦¬ìŠ¤íŠ¸ëŠ” 완벽한 ë³´ì•ˆì„ ì œê³µí•˜ì§€ 않습니다. + ì´ íŒ¨ìŠ¤ì›Œë“œëŠ” 단지 스패머로 ì¸í•´ ì•…ìš©ë˜ì§€ 않ë„ë¡ë§Œ 합니다. + 비밀번호는 암호화 시키지 않고 여러분ì—게 보내기 ë•Œë¬¸ì— ì ˆëŒ€ + 실제 사용하는 비밀번호는 입력하지 마십시오. +

        만약 ì—¬ëŸ¬ë¶„ì´ ë¹„ë°€ë²ˆí˜¸ë¥¼ 입력하지 않는다면 ìžë™ìœ¼ë¡œ ë‹¹ì‹ ì„ + 위한 비밀번호가 ìƒì„±ë˜ë©°, ë‹¹ì‹ ì˜ ê°€ìž… í™•ì¸ ë©”ì¼ì— 함께 ë³´ë‚´ + 집니다. ê°œì¸ ì •ë³´ ë³€ê²½ì„ ìœ„í•œ 비밀번호를 언제나 다시 ë³´ë‚´ + 드립니다. + +
        +
        비밀번호입력: 
        다시한번입력: 
        사용할 언어를 ì„ íƒí•˜ì„¸ìš”?  
        메시지를 í•˜ë£¨ì— í•œ 번만 ë¬¶ìŒ í˜•ì‹ìœ¼ë¡œ 받아보ë„ë¡ ì„¤ì •í•˜ì‹œê² ìŠµë‹ˆê¹Œ? ¾Æ´Ï¿ä - ¿¹ -
        -
        -
        - -
      -
      - - °¡ÀÔÀÚµé -
      - - - -

      - - - -

      - - - +
    아니요 + 예 +
    +
    +
    + + +

    + + 가입ìžë“¤ +
    + + + +

    + + + +

    + +

    + diff --git a/templates/ko/options.html b/templates/ko/options.html index 8c855791..ad105982 100644 --- a/templates/ko/options.html +++ b/templates/ko/options.html @@ -1,42 +1,102 @@ - <MM-Presentable-User> ÀÇ <MM-List-Name> ¿¡¼­ »ç¿ëÀÚ ¼³Á¤Çϱâ - - - - - -
    - - ÀÇ - »ç¿ëÀÚ ¼³Á¤Çϱâ -
    - +<mm-presentable-user> ì˜ <mm-list-name> ì—서 ì‚¬ìš©ìž ì„¤ì •í•˜ê¸° + </mm-list-name></mm-presentable-user> + + + + +
    + + ì˜ + ì‚¬ìš©ìž ì„¤ì •í•˜ê¸° +
    +

    - - - - - +
    - ¸ÞÀϸµ ¸®½ºÆ®¿¡¼­ - »ç¿ëÀÚÀÇ °¡ÀÔ »óÅÂ, ÆÐ½º¿öµå, ±âŸ ¿É¼ÇµîÀ» ´Ù·ì´Ï´Ù. -
    - - - - -

    -

    + + + +
    + ë©”ì¼ë§ 리스트ì—서 + 사용ìžì˜ 가입 ìƒíƒœ, 패스워드, 기타 ì˜µì…˜ë“±ì„ ë‹¤ë£¹ë‹ˆë‹¤. +
    + + +

    +

    -

    - - - +
    - - ȸ¿ø Á¤º¸ º¯°æÇϱâ -
    You can change the address that you are subscribed + + + - - - - - -
    + + íšŒì› ì •ë³´ 변경하기 +
    You can change the address that you are subscribed to the mailing list with by entering the new address in the fields below. Note that a confirmation email will be sent to the new address, and the change must be confirmed before it is @@ -51,258 +111,231 @@ lists that you are subscribed to at , turn on the Change globally check box. -
    - - - - - - - -
    »õ·Î¿î E¸ÞÀÏ ÁÖ¼Ò:
    È®ÀÎÀ»À§ÇØ Çѹø ´õ ÀÔ·Â - :
    -
    - - - - -
    À̸§ - (¼±ÅûçÇ×):
    -
    -

    ÀüüÀûÀ¸·Î º¯°æ

    - +

    + + + + + + + +
    새로운 Eë©”ì¼ ì£¼ì†Œ:
    확ì¸ì„위해 한번 ë” ìž…ë ¥ + :
    +

    + + + + +
    ì´ë¦„ + (ì„ íƒì‚¬í•­):
    + +
    +

    ì „ì²´ì ìœ¼ë¡œ 변경

    +

    - - - - - -
    - Å»ÅðÇϱâ - ÀÇ ´Ù¸¥ ¸ÞÀϸµ ¸®½ºÆ® °¡ÀÔ »óȲ -
    - ÀÌ ¸ÞÀϸµ ¸®½ºÆ®·Î ºÎÅÍ Å»ÅðÇϱâ À§ÇØ È®ÀΠüũ ¹Ú½º¸¦ üũ - ÇϽðí ÀÌ ¹öưÀ» ´©¸£½Ã±â ¹Ù¶ø´Ï´Ù. - °æ°í: - ÀÌ µ¿ÀÛÀº Áï°¢ÀûÀ¸·Î ÀÏ¾î ³³´Ï´Ù. µ¹ÀÌų ¼ö ¾ø½À´Ï´Ù. + + + + - + +
    +

    + 탈퇴하기 + ì˜ ë‹¤ë¥¸ ë©”ì¼ë§ 리스트 가입 ìƒí™© +
    + ì´ ë©”ì¼ë§ 리스트로 부터 탈퇴하기 위해 í™•ì¸ ì²´í¬ ë°•ìŠ¤ë¥¼ ì²´í¬ + 하시고 ì´ ë²„íŠ¼ì„ ëˆ„ë¥´ì‹œê¸° ë°”ëžë‹ˆë‹¤. + 경고: + ì´ ë™ìž‘ì€ ì¦‰ê°ì ìœ¼ë¡œ ì¼ì–´ 납니다. ëŒì´í‚¬ 수 없습니다.

    -

    - ¿©·¯ºÐÀÇ È¸¿øÀ̽Š»óÀÇ ´Ù¸¥ ¸ÞÀϸµ ¸®½ºÆ® °¡ÀÔ »óȲ - ¹× ¿É¼Ç ÆäÀÌÁö¸¦ º¸½Ã°íÀÚ ÇÑ´Ù¸é À̰ÍÀ» »ç¿ëÇϽʽÿÀ. +

    + ì—¬ëŸ¬ë¶„ì˜ íšŒì›ì´ì‹  ìƒì˜ 다른 ë©”ì¼ë§ 리스트 가입 ìƒí™© + ë° ì˜µì…˜ 페ì´ì§€ë¥¼ ë³´ì‹œê³ ìž í•œë‹¤ë©´ ì´ê²ƒì„ 사용하십시오.

    -

    -
    - - - - - - -
    - ¼³Á¤ÇϽŠºñ¹Ð¹øÈ£ -
    - -
    -

    ºñ¹Ð¹øÈ£¸¦ ÀØÀ¸¼Ì³ª¿ä?

    -
    - ¾Æ·¡ ¹öưÀ» ´©¸£½Ã¸é ¸ÞÀϸµ ¸®½ºÆ®¿¡ µî·ÏµÈ ´ç½ÅÀÇ E¸ÞÀÏ ÁÖ¼Ò·Î - ºñ¹Ð¹øÈ£°¡ º¸³»¾î Áý´Ï´Ù. -

    -

    - -
    -
    - -
    -

    Change Your Password

    - - - - - - - - -
    »õ - ºñ¹Ð¹øÈ£:
    Çѹø´õ - ÀÔ·Â:
    - - -

    ÀüüÀûÀ¸·Î º¯°æ -
    -
    - + + + +
    +설정하신 비밀번호 +
    + +
    +

    비밀번호를 잊으셨나요?

    +
    + 아래 ë²„íŠ¼ì„ ëˆ„ë¥´ì‹œë©´ ë©”ì¼ë§ ë¦¬ìŠ¤íŠ¸ì— ë“±ë¡ëœ ë‹¹ì‹ ì˜ Eë©”ì¼ ì£¼ì†Œë¡œ + 비밀번호가 ë³´ë‚´ì–´ 집니다. +

    +

    + +
    +

    + +
    +

    Change Your Password

    + + + + + + + + +
    새 + 비밀번호:
    í•œë²ˆë” + ìž…ë ¥:
    + +

    ì „ì²´ì ìœ¼ë¡œ 변경 +
    +

    - - +
    - ¸ÞÀϸµ ¸®½ºÆ® ±¸µ¶ ¿É¼Ç -
    +
    + ë©”ì¼ë§ 리스트 êµ¬ë… ì˜µì…˜ +
    -

    -üũµÇ¾î ÀÖ´Â Ç׸ñÀÌ ÇöÀç ¼±ÅõǾîÁø °ÍÀÔ´Ï´Ù. - -

    ¸î¸î ¼³Á¤ÀÇ °æ¿ì ÀüüÀûÀ¸·Î ¼³Á¤ÇÒ ¼ö ÀÖ½À´Ï´Ù. -¿¡ ´ç½ÅÀÌ È¸¿øÀ̽ŠÀÌ ¸ÞÀϸµ ¸®½ºÆ®¿¡ ¼³Á¤º¯°æÀ» °¡ÇÏ°Ô µË´Ï´Ù. ´ç½ÅÀÌ -°¡ÀÔÇϽŠ´Ù¸¥ ¸®½ºÆ® ¸ñ·ÏÀ» º¸½Ç·Á¸é À§¿¡ ÀÖ´Â °ÍÀ» Ŭ¸¯ÇÏ¿© -Áֽñ⠹ٶø´Ï´Ù. +ì²´í¬ë˜ì–´ 있는 í•­ëª©ì´ í˜„ìž¬ ì„ íƒë˜ì–´ì§„ 것입니다. +

    몇몇 ì„¤ì •ì˜ ê²½ìš° ì „ì²´ì ìœ¼ë¡œ 설정할 수 있습니다. +ì— ë‹¹ì‹ ì´ íšŒì›ì´ì‹  ì´ ë©”ì¼ë§ ë¦¬ìŠ¤íŠ¸ì— ì„¤ì •ë³€ê²½ì„ ê°€í•˜ê²Œ ë©ë‹ˆë‹¤. ë‹¹ì‹ ì´ +가입하신 다른 리스트 목ë¡ì„ 보실려면 ìœ„ì— ìžˆëŠ” ê²ƒì„ í´ë¦­í•˜ì—¬ +주시기 ë°”ëžë‹ˆë‹¤.

    - - - +
    - - ÆíÁö ¹è´Þ

    - ÀÌ ¸ÞÀϸµ ¸®½ºÆ®¿¡¼­ ¹ß»ýÇÑ ±ÛÀ» ¹Þ¾Æ º¸½Ç·Á¸é ¿¹ - ·Î ¼³Á¤ÇϽñ⠹ٶø´Ï´Ù. ¸¸¾à ¿©·¯ºÐÀÌ Àá±ñµ¿¾È ¸ÞÀÏÀ» ¹ÞÁö - ¾ÊÀ¸½Ç·Á¸é ¾Æ´Ï¿ä·Î ¼¼ÆÃÇϽñ⠹ٶø´Ï´Ù. (¿¹¸¦ µé¸é - , ¹æÇе¿¾È) ¸¸¾à ¿©·¯ºÐÀÌ ÆíÁö ¹è´ÞÀ» ±ÝÁöÇÏ½Å´Ù¸é ¿©·¯ºÐ - ¹æÇÐ ÈÄ¿¡ ´Ù½Ã Àç°¡µ¿ ½ÃŰ´Â °É ÀØÁö ¸¶½Ã±â ¹Ù¶ø´Ï´Ù. -

    - ¿¹
    - ¾Æ´Ï¿ä

    - ÀüüÀûÀ¸·Î ¼³Á¤ -

    + - - - + + - - - - + - - - - - - - - - - - - - - - +

    + + + + + + +
    + +편지 배달

    + ì´ ë©”ì¼ë§ 리스트ì—서 ë°œìƒí•œ ê¸€ì„ ë°›ì•„ 보실려면 예 + 로 설정하시기 ë°”ëžë‹ˆë‹¤. 만약 ì—¬ëŸ¬ë¶„ì´ ìž ê¹ë™ì•ˆ ë©”ì¼ì„ 받지 + 않으실려면 아니요로 세팅하시기 ë°”ëžë‹ˆë‹¤. (예를 들면 + , ë°©í•™ë™ì•ˆ) 만약 ì—¬ëŸ¬ë¶„ì´ íŽ¸ì§€ ë°°ë‹¬ì„ ê¸ˆì§€í•˜ì‹ ë‹¤ë©´ 여러분 + ë°©í•™ í›„ì— ë‹¤ì‹œ ìž¬ê°€ë™ ì‹œí‚¤ëŠ” 걸 잊지 마시기 ë°”ëžë‹ˆë‹¤. +

    +예
    +아니요

    +ì „ì²´ì ìœ¼ë¡œ 설정 +

    - ¹­À½¹è´Þ ¼³Á¤

    - ¸¸¾à ¿©·¯ºÐÀÌ ¼³Á¤À» "¿¹"¶ó°í ÇÏ½Å´Ù¸é ¹­À½¹è´ÞÀ» ½ÅûÇÏ´Â °ÍÀÔ´Ï´Ù. - (º¸Åë ÇÏ·ç¿¡ Çѹø Á¤µµ º¸³»Áý´Ï´Ù.) ´ë½Å º¸³¾¶§, ´Ü ÇÑÅ븸 º¸³»°Ô - µË´Ï´Ù. ¸¸¾à ¹­À½¹è´ÞÀÌ "¾Æ´Ï¿ä"·Î ¹Ù²Ù°Ô µÈ´Ù¸é, ±× ÈÄ¿¡ ÇѹøÀÇ - ¹­À½¹è´Þ ÆíÁö¸¦ ¹Þ°Ô µÉ °ÍÀÔ´Ï´Ù. -

    - ¾Æ´Ï¿ä
    - ¿¹ -
    - MIME ȤÀº Æò¹® ¹­À½ ¹è´ÞÁß ¾î¶² °É ¼±ÅÃÇϽðڽÀ´Ï±î?

    - ´ç½ÅÀÇ ¸ÞÀÏ ÇÁ·Î±×·¥Àº MIME¸¦ Áö¿øÇϰųª, ȤÀº ÇÏÁö ¾ÊÀ» °ÍÀÔ´Ï´Ù. - ÀϹÝÀûÀ¸·Î MIME ¹­À½¹è´ÞÀÌ ±âº»ÀÔ´Ï´Ù. ¸¸¾à MIME Çü½ÄÀ» Àдµ¥, - ¹®Á¦°¡ ÀÖÀ¸¸é "Æò¹®"À» ¼±ÅÃÇϽñ⠹ٶø´Ï´Ù. -

    - MIME
    - Æò¹® -
    +묶ìŒë°°ë‹¬ 설정

    + 만약 ì—¬ëŸ¬ë¶„ì´ ì„¤ì •ì„ "예"ë¼ê³  하신다면 묶ìŒë°°ë‹¬ì„ 신청하는 것입니다. + (보통 í•˜ë£¨ì— í•œë²ˆ ì •ë„ ë³´ë‚´ì§‘ë‹ˆë‹¤.) 대신 보낼때, 단 한통만 보내게 + ë©ë‹ˆë‹¤. 만약 묶ìŒë°°ë‹¬ì´ "아니요"로 바꾸게 ëœë‹¤ë©´, ê·¸ í›„ì— í•œë²ˆì˜ + 묶ìŒë°°ë‹¬ 편지를 받게 ë  ê²ƒìž…ë‹ˆë‹¤. +

    +아니요
    +예 +
    +MIME í˜¹ì€ í‰ë¬¸ ë¬¶ìŒ ë°°ë‹¬ì¤‘ ì–´ë–¤ 걸 ì„ íƒí•˜ì‹œê² ìŠµë‹ˆê¹Œ?

    + ë‹¹ì‹ ì˜ ë©”ì¼ í”„ë¡œê·¸ëž¨ì€ MIME를 ì§€ì›í•˜ê±°ë‚˜, í˜¹ì€ í•˜ì§€ ì•Šì„ ê²ƒìž…ë‹ˆë‹¤. + ì¼ë°˜ì ìœ¼ë¡œ MIME 묶ìŒë°°ë‹¬ì´ 기본입니다. 만약 MIME 형ì‹ì„ ì½ëŠ”ë°, + 문제가 있으면 "í‰ë¬¸"ì„ ì„ íƒí•˜ì‹œê¸° ë°”ëžë‹ˆë‹¤. +

    +MIME
    +í‰ë¬¸ +
    - ÀÚ½ÅÀÌ º¸³½ ¿ìÆí¹°À» ¹ÞÀ» °Ç°¡¿ä?

    - ÀϹÝÀûÀ¸·Î ¿©·¯ºÐÀÌ ¸®½ºÆ®¿¡ ±ÛÀ» º¸³»°Ô µÇ¸é ¿©·¯ºÐÀÌ º¸³½ - ¸Þ¼¼Áö¸¦ ´Ù½Ã ¹Þ°Ô µË´Ï´Ù. ¸¸¾à ÀÌ º¹»çº»À» ¹Þ±æ ¿øÇÏÁö ¾ÊÀ¸ - ½Ã¸é, ÀÌ ¿É¼ÇÀ» ¾Æ´Ï¿ä·Î ¼³Á¤ÇϽñ⠹ٶø´Ï´Ù. -

    - ¾Æ´Ï¿ä
    - ¿¹ -
    - ¸®½ºÆ®¿¡ º¸³½ "¸ÞÀÏ È®ÀÎ" ¸ÞÀÏÀ» ¹Þ¾Æ º¸½Ç °Ç°¡¿ä? +
    +ìžì‹ ì´ 보낸 ìš°íŽ¸ë¬¼ì„ ë°›ì„ ê±´ê°€ìš”?

    + ì¼ë°˜ì ìœ¼ë¡œ ì—¬ëŸ¬ë¶„ì´ ë¦¬ìŠ¤íŠ¸ì— ê¸€ì„ ë³´ë‚´ê²Œ ë˜ë©´ ì—¬ëŸ¬ë¶„ì´ ë³´ë‚¸ + 메세지를 다시 받게 ë©ë‹ˆë‹¤. 만약 ì´ ë³µì‚¬ë³¸ì„ ë°›ê¸¸ ì›í•˜ì§€ 않으 + 시면, ì´ ì˜µì…˜ì„ ì•„ë‹ˆìš”ë¡œ 설정하시기 ë°”ëžë‹ˆë‹¤. +

    +아니요
    +예 +
    +ë¦¬ìŠ¤íŠ¸ì— ë³´ë‚¸ "ë©”ì¼ í™•ì¸" ë©”ì¼ì„ 받아 보실 건가요?

    -

    - ¾Æ´Ï¿ä
    - ¿¹ -
    - ÀÌ ¸®½ºÆ®¿¡¼­ º¸³»´Â ºñ¹Ð¹øÈ£ ÆíÁö¸¦ º¸³¾ °Ç°¡¿ä?

    - ÇÑ´Þ¿¡ Çѹø ¿©·¯ºÐÀÌ °¡ÀÔÇÑ È£½ºÆ®·Î ºÎÅÍ ºñ¹Ð¹øÈ£¸¦ ´ãÀº - ÆíÁö¸¦ ¹Þ°Ô µÉ °Í ÀÔ´Ï´Ù. ¿©·¯ºÐÀÌ ÀÌ ¼³Á¤Àº ¾Æ´Ï¿ä - ·Î ¼³Á¤ÇÔÀ¸·Î½á ±× ¸ÞÀÏÀ» ¹ÞÁö ¾ÊÀ» ¼ö ÀÖ½À´Ï´Ù. ¸¸¾à ´ç½ÅÀÌ - °¡ÀÔÇÑ ¸ðµç ¸®½ºÆ®ÀÇ ÀÌ ¼³Á¤ ºÎºÐÀ» ¾Æ´Ï¿ä·Î ¼³Á¤ - ÇÑ´Ù¸é ÆÐ½º¿öµå ÆíÁö´Â ¿©·¯ºÐ¿¡°Ô °¡Áö ¾ÊÀ» °ÍÀÔ´Ï´Ù. -

    - ¾Æ´Ï¿ä
    - ¿¹

    - ÀüüÀûÀ¸·Î º¯°æ -

    - °¡ÀÔÀÚ ¸í´Ü¿¡¼­ ¿©·¯ºÐÀÇ À̸§À» ¼û±æ±î¿ä?

    - ¾î¶² »ç¶÷ÀÌ ¸®½ºÆ® ȸ¿øÀ» º¼¶§, ´ç½ÅÀÇ E¸ÞÀÏ ÁÖ¼Ò´Â º¸Åë - º¸ÀÌ°Ô µË´Ï´Ù. ¹°·Ð ½ºÆÐ¸Ó°¡ E¸ÞÀÏÀ» °¡Á®°¡µµ »ó°ü¾ø°Ô º¯Á¶ - µË´Ï´Ù. ¸¸¾à ¿©·¯ºÐÀÌ È¸¿ø ¸ñ·Ï¿¡ ÀÚ½ÅÀÇ E¸ÞÀÏ ÁÖ¼Ò°¡ º¸À̱æ - ¿øÇÏÁö ¾ÊÀ¸½Ã´Ù¸é ÀÌ ¼³Á¤À» ¾Æ´Ï¿ä¸¦ ¼±ÅÃÇÏ¿© ÁֽʽÿÀ. -

    - ¾Æ´Ï¿ä
    - ¿¹ -
    - ¿©·¯ºÐÀÌ »ç¿ëÇÒ ¾ð¾î´Â ¹«¾ùÀԴϱî?

    -

    - -
    - °¡ÀÔÇϰíÀÚ ÇÏ´Â ÁÖÁ¦ÀÇ ºÐ·ù´Â ¾î¶² °ÍÀԴϱî?

    - Çϳª ÀÌ»óÀÇ ÁÖÁ¦¸¦ ¼±ÅÃÇÔÀ¸·Î½á ´ç½ÅÀº ¸ÞÀϸµ ¸®½ºÆ®ÀÇ - Æ®·¡ÇÈÀ» °É·¯³¾ ¼ö ÀÖÀ¸¸ç, ÁÖÁ¦¿Í ¸Â´Â ¸Þ¼¼Áö¸¸ ¹ÞÀ» - ¼ö ÀÖ½À´Ï´Ù. ¸¸¾à ¿©·¯ºÐÀÌ ¼±ÅÃÇÑ ÁÖÁ¦¿Í ¸Þ¼¼Áö°¡ ¸Â - ´Â ´Ù¸é ¿©·¯ºÐ¿¡°Ô ¹è´ÞµÇÁö¸¸ ±×·¸Áö ¾ÊÀ» °æ¿ì ¹è´Þ - µÇÁö ¾Ê½À´Ï´Ù. - -

    ¸¸¾à ¸Þ¼¼Áö°¡ ¾î¶°ÇÑ ÁÖÁ¦¿Íµµ ¸ÂÁö ¾Ê´Â´Ù¸é ¹è´Þ - ±ÔÄ¢Àº ¾Æ·¡ ¼³Á¤¿¡ ÀÇÁ¸ÇÏ°Ô µË´Ï´Ù. ¸¸¾à ¿©·¯ºÐÀÌ - °ü½ÉÀÖ¾îÇÏ´Â ÁÖÁ¦¸¦ ¼±ÅÃÇÏÁö ¾ÊÀ¸½Ã¸é ¿©·¯ºÐÀº - ¸ÞÀϸµ ¸®½ºÆ®·Î ¿À´Â ¸ðµç ¸Þ¼¼Áö¸¦ ¹ÞÀ» °ÍÀÔ´Ï´Ù. -

    - -
    - ÁÖÁ¦ °É·¯³»±â¿¡¼­ ¾î¶°ÇÑ ÆÐÅϰúµµ ¸ÂÁö ¾Ê´Â ¸Þ¼¼Áö¸¦ - ¹Þ¾Æº¸½Ã°Ú½À´Ï±î?

    - ÀÌ ¼³ÀûÀº ´ÜÁö ¿©·¯ºÐÀÌ À§ÀÇ ÁÖÁ¦Áß Çϳª¶óµµ °¡ÀÔÇÏ¼Å¾ß - Àû¿ëµÇ´Â ¼³Á¤ÀÔ´Ï´Ù. ÁÖÁ¦ °É·¯³»±â ÆÐÅϰú ¸ÂÁö ¾ÊÀ» °æ¿ì - ¿¡ ±× ¸Þ¼¼Áö¸¦ ¾î¶»°Ô ó¸®Çϴ°¡¿¡ ´ëÇÑ ¼³Á¤À» Çϰí ÀÖ½À´Ï´Ù. - ¾Æ´Ï¿ä ·Î ¼±ÅÃÇϽøé, ÁÖÁ¦ °É·¯³»±â ÆÐÅϰú - ¾î´À°Íµµ ¸ÂÁö ¾ÊÀº ¸Þ¼¼Áö´Â ¿©·¯ºÐ¿¡°Ô ¹è´ÞµÇÁö ¾ÊÀ¸¸é - ¿¹·Î ¼±ÅÃÇÏ½Ã¸é ¿©·¯ºÐ¿¡°Ô ¹è´ÞµË´Ï´Ù. - -

    ¸¸¾à °ü½ÉÀÖ´Â ÁÖÁ¦°¡ ¾øÀ¸½Ã¸é ¸ÞÀϸµ ¸®½ºÆ®·Î ¿À´Â - ¸ðµç ¸Þ¼¼Áö¸¦ ¹Þ°Ô µÉ °ÍÀÔ´Ï´Ù. -

    - ¾Æ´Ï¿ä
    - ¿¹ -
    - ¸Þ¼¼Áö°¡ Áߺ¹µÇ´Â °ÍÀº ¹ÞÁö ¾ÊÀ»±î¿ä?

    - - ¿©·¯ºÐÀÌ ¸®½ºÆ® ¸Þ¼¼ÁöÀÇ Çì´õÀÇ To: ȤÀº Cc: - ºÎºÐ¿¡ ¿©·¯ºÐÀÇ ÁÖ¼Ò°¡ µé¾î ÀÖÀ¸¸é ¸ÞÀϸµ ¸®½ºÆ®·Î ºÎÅÍ - ¿©·¯ Áߺ¹µÇ´Â ¸Þ¼¼Áö¸¦ ¹Þ°Ô µË´Ï´Ù. ¿¹·Î ¼±Åà - ÇϽøé Áߺ¹µÇ´Â °ÍÀ» ¹ÞÁö ¾ÊÀ¸¸ç ¾Æ´Ï¿ä·Î ¼±Åà - ÇÏ½Ã¸é ¸ÞÀϸµ ¸®½ºÆ®·Î ºÎÅÍ ¸Þ¼¼Áö¸¦ ¹Þ°Ô µË´Ï´Ù. - -

    ¸¸¾à ¸®½ºÆ®°¡ ȸ¿ø °³º°È­µÈ ¸Þ¼¼Áö°¡ °¡´É - ÇÏ°Ô µÇ¾î ÀÖ´Ù¸é ´ç½ÅÀº X-Mailman-Copy: yes - Çì´õ¸¦ ´õÇÔÀ¸·Î½á ¸ðµç º¹»ç¸Þ¼¼Áö¿¡ ´ëÇØ ¼±º°ÇÒ ¼ö ÀÖ - ½À´Ï´Ù. - -

    - ¾Æ´Ï¿ä
    - ¿¹

    - ÀüüÀûÀ¸·Î º¯°æ -

    -
    -
    +아니요
    +예 +
    +ì´ ë¦¬ìŠ¤íŠ¸ì—서 보내는 비밀번호 편지를 보낼 건가요?

    + í•œë‹¬ì— í•œë²ˆ ì—¬ëŸ¬ë¶„ì´ ê°€ìž…í•œ 호스트로 부터 비밀번호를 ë‹´ì€ + 편지를 받게 ë  ê²ƒ 입니다. ì—¬ëŸ¬ë¶„ì´ ì´ ì„¤ì •ì€ ì•„ë‹ˆìš” + 로 ì„¤ì •í•¨ìœ¼ë¡œì¨ ê·¸ ë©”ì¼ì„ 받지 ì•Šì„ ìˆ˜ 있습니다. 만약 ë‹¹ì‹ ì´ + 가입한 모든 ë¦¬ìŠ¤íŠ¸ì˜ ì´ ì„¤ì • ë¶€ë¶„ì„ ì•„ë‹ˆìš”ë¡œ 설정 + 한다면 패스워드 편지는 여러분ì—게 가지 ì•Šì„ ê²ƒìž…ë‹ˆë‹¤. +

    +아니요
    +예

    +ì „ì²´ì ìœ¼ë¡œ 변경 +

    +ê°€ìž…ìž ëª…ë‹¨ì—서 ì—¬ëŸ¬ë¶„ì˜ ì´ë¦„ì„ ìˆ¨ê¸¸ê¹Œìš”?

    + ì–´ë–¤ ì‚¬ëžŒì´ ë¦¬ìŠ¤íŠ¸ 회ì›ì„ 볼때, ë‹¹ì‹ ì˜ Eë©”ì¼ ì£¼ì†ŒëŠ” 보통 + ë³´ì´ê²Œ ë©ë‹ˆë‹¤. 물론 스패머가 Eë©”ì¼ì„ ê°€ì ¸ê°€ë„ ìƒê´€ì—†ê²Œ 변조 + ë©ë‹ˆë‹¤. 만약 ì—¬ëŸ¬ë¶„ì´ íšŒì› ëª©ë¡ì— ìžì‹ ì˜ Eë©”ì¼ ì£¼ì†Œê°€ ë³´ì´ê¸¸ + ì›í•˜ì§€ 않으시다면 ì´ ì„¤ì •ì„ ì•„ë‹ˆìš”ë¥¼ ì„ íƒí•˜ì—¬ 주십시오. +

    +아니요
    +예 +
    +ì—¬ëŸ¬ë¶„ì´ ì‚¬ìš©í•  언어는 무엇입니까?

    +

    + +
    +ê°€ìž…í•˜ê³ ìž í•˜ëŠ” ì£¼ì œì˜ ë¶„ë¥˜ëŠ” ì–´ë–¤ 것입니까?

    + 하나 ì´ìƒì˜ 주제를 ì„ íƒí•¨ìœ¼ë¡œì¨ ë‹¹ì‹ ì€ ë©”ì¼ë§ ë¦¬ìŠ¤íŠ¸ì˜ + íŠ¸ëž˜í”½ì„ ê±¸ëŸ¬ë‚¼ 수 있으며, 주제와 맞는 메세지만 ë°›ì„ + 수 있습니다. 만약 ì—¬ëŸ¬ë¶„ì´ ì„ íƒí•œ 주제와 메세지가 ë§ž + 는 다면 여러분ì—게 배달ë˜ì§€ë§Œ 그렇지 ì•Šì„ ê²½ìš° 배달 + ë˜ì§€ 않습니다. + +

    만약 메세지가 어떠한 ì£¼ì œì™€ë„ ë§žì§€ 않는다면 배달 + ê·œì¹™ì€ ì•„ëž˜ ì„¤ì •ì— ì˜ì¡´í•˜ê²Œ ë©ë‹ˆë‹¤. 만약 ì—¬ëŸ¬ë¶„ì´ + 관심있어하는 주제를 ì„ íƒí•˜ì§€ 않으시면 ì—¬ëŸ¬ë¶„ì€ + ë©”ì¼ë§ 리스트로 오는 모든 메세지를 ë°›ì„ ê²ƒìž…ë‹ˆë‹¤. +

    + +
    +주제 걸러내기ì—서 어떠한 íŒ¨í„´ê³¼ë„ ë§žì§€ 않는 메세지를 + 받아보시겠습니까?

    + ì´ ì„¤ì ì€ 단지 ì—¬ëŸ¬ë¶„ì´ ìœ„ì˜ ì£¼ì œì¤‘ 하나ë¼ë„ 가입하셔야 + ì ìš©ë˜ëŠ” 설정입니다. 주제 걸러내기 패턴과 ë§žì§€ ì•Šì„ ê²½ìš° + ì— ê·¸ 메세지를 어떻게 ì²˜ë¦¬í•˜ëŠ”ê°€ì— ëŒ€í•œ ì„¤ì •ì„ í•˜ê³  있습니다. + 아니요 로 ì„ íƒí•˜ì‹œë©´, 주제 걸러내기 패턴과 + ì–´ëŠê²ƒë„ ë§žì§€ ì•Šì€ ë©”ì„¸ì§€ëŠ” 여러분ì—게 배달ë˜ì§€ 않으면 + 예로 ì„ íƒí•˜ì‹œë©´ 여러분ì—게 배달ë©ë‹ˆë‹¤. + +

    만약 관심있는 주제가 없으시면 ë©”ì¼ë§ 리스트로 오는 + 모든 메세지를 받게 ë  ê²ƒìž…ë‹ˆë‹¤. +

    +아니요
    +예 +
    +메세지가 중복ë˜ëŠ” ê²ƒì€ ë°›ì§€ 않ì„까요?

    + + ì—¬ëŸ¬ë¶„ì´ ë¦¬ìŠ¤íŠ¸ ë©”ì„¸ì§€ì˜ í—¤ë”ì˜ To: í˜¹ì€ Cc: + ë¶€ë¶„ì— ì—¬ëŸ¬ë¶„ì˜ ì£¼ì†Œê°€ 들어 있으면 ë©”ì¼ë§ 리스트로 부터 + 여러 중복ë˜ëŠ” 메세지를 받게 ë©ë‹ˆë‹¤. 예로 ì„ íƒ + 하시면 중복ë˜ëŠ” ê²ƒì„ ë°›ì§€ 않으며 아니요로 ì„ íƒ + 하시면 ë©”ì¼ë§ 리스트로 부터 메세지를 받게 ë©ë‹ˆë‹¤. + +

    만약 리스트가 íšŒì› ê°œë³„í™”ëœ ë©”ì„¸ì§€ê°€ 가능 + 하게 ë˜ì–´ 있다면 ë‹¹ì‹ ì€ X-Mailman-Copy: yes + í—¤ë”를 ë”í•¨ìœ¼ë¡œì¨ ëª¨ë“  ë³µì‚¬ë©”ì„¸ì§€ì— ëŒ€í•´ 선별할 수 있 + 습니다. + +

    +아니요
    +예

    +ì „ì²´ì ìœ¼ë¡œ 변경 +

    +
    +
    -

    - - - - + + +

    diff --git a/templates/ko/private.html b/templates/ko/private.html index 6d0f1dfc..e84dca03 100755 --- a/templates/ko/private.html +++ b/templates/ko/private.html @@ -1,59 +1,120 @@ - %(realname)s ºñ°ø°³ ÀúÀå¼Ò ÀÎÁõ +%(realname)s 비공개 저장소 ì¸ì¦ - - -
    + + + %(message)s - - - - - - - - - - - - - - - -
    - %(realname)s ºñ°ø°³ - ÀúÀå¼Ò ÀÎÁõ -
    E¸ÞÀÏ ÁÖ¼Ò:
    ºñ¹Ð¹øÈ£:
    -
    -

    Áß¿ä: ÀÌ ½ÃÁ¡ºÎÅÍ ¿©·¯ºÐÀÇ - ºê¶ó¿ìÀú¿¡ Äí۰¡ ÀúÀåµË´Ï´Ù. ÀÌ ºÎºÐºÎÅÍ ½ÃÀÛÇÏÁö ¾ÊÀ¸¸é, - ´Ù¸¥ º¯°æµéÀº ½ÇÁ¦·Î Àû¿ëµÇÁö ¾Ê½À´Ï´Ù. + + + + + + + + + + + + + + + +
    +%(realname)s 비공개 + 저장소 ì¸ì¦ +
    Eë©”ì¼ ì£¼ì†Œ:
    비밀번호:
    +
    +

    중요: ì´ ì‹œì ë¶€í„° ì—¬ëŸ¬ë¶„ì˜ + 브ë¼ìš°ì €ì— 쿠키가 저장ë©ë‹ˆë‹¤. ì´ ë¶€ë¶„ë¶€í„° 시작하지 않으면, + 다른 ë³€ê²½ë“¤ì€ ì‹¤ì œë¡œ ì ìš©ë˜ì§€ 않습니다. -

    Mailman ÀÇ °ü¸® ÀÎÅÍÆäÀ̽º¿¡´Â ¼¼¼Ç Äí۸¦ »ç¿ëÇϱ⠶§¹®¿¡ - ¸Å¹ø ÀÎÁõÇÒ Çʿ䰡 ¾øÀ¸¸ç ÀÌ ÄíŰ´Â ¿©·¯ºÐÀÌ ºê¶ó¿ìÀú¸¦ ³¡³Â°Å³ª, - °ü¸®ÀÚ È­¸é¿¡¼­ º¼¼ö ÀÖ´Â "·Î±×¾Æ¿ô"(¿©·¯ºÐÀÌ - ¼º°øÀûÀ¸·Î ·Î±ä À» ÇÏ¸é º¼ ¼ö ÀÖ½À´Ï´Ù.) ¸¦ Ŭ¸¯Çϸé - ÀÚµ¿À¸·Î Áö¿öÁý´Ï´Ù. +

    Mailman ì˜ ê´€ë¦¬ ì¸í„°íŽ˜ì´ìФì—는 세션 쿠키를 사용하기 ë•Œë¬¸ì— + 매번 ì¸ì¦í•  필요가 없으며 ì´ ì¿ í‚¤ëŠ” ì—¬ëŸ¬ë¶„ì´ ë¸Œë¼ìš°ì €ë¥¼ ë냈거나, + ê´€ë¦¬ìž í™”ë©´ì—서 볼수 있는 "로그아웃"(ì—¬ëŸ¬ë¶„ì´ + 성공ì ìœ¼ë¡œ 로긴 ì„ í•˜ë©´ ë³¼ 수 있습니다.) 를 í´ë¦­í•˜ë©´ + ìžë™ìœ¼ë¡œ 지워집니다.

    - - - - - - + + + +
    - Password Reminder -
    If you don't remember your password, enter your email address + + + + + + - - - - -
    +Password Reminder +
    If you don't remember your password, enter your email address above and click the Remind button and your password will be emailed to you.
    - +
    +

    diff --git a/templates/ko/roster.html b/templates/ko/roster.html index f27aff48..ff10e20c 100644 --- a/templates/ko/roster.html +++ b/templates/ko/roster.html @@ -1,50 +1,110 @@ - - - <MM-List-Name> °¡ÀÔÀÚµé - - - - -

    - - - - - - - - - - - - - - - -
    - - °¡ÀÔÀÚ¸í´Ü -
    - -

    °¡ÀÔ º¯°æÀ» ÇϽǷÁ¸é ¿©·¯ºÐÀÇ Email ÁÖ¼Ò¸¦ ã¾Æ Ŭ¸¯ÇϽʽÿÀ. -
    (°ýÈ£ Ç¥½ÃµÈ Ç׸ñÀº "±Û¾²±â ±ÇÇÑ"ÀÌ ¾ø´Â Ç׸ñÀ» °¡¸£Åµ´Ï´Ù.) -

    -
    -
    - - ¿¡¼­ Digest¸¦ ½Åû ¾ÈÇϽŠºÐµé : -
    -
    -
    - - ¿¡¼­ Digest¸¦ ½ÅûÇϽŠºÐµé : -
    -
    -

    -

    -

    -

    - - - + + +<mm-list-name> 가입ìžë“¤</mm-list-name> + + +

    + + + + + + + + + + + + + + + +
    + + 가입ìžëª…단 +
    +

    가입 ë³€ê²½ì„ í•˜ì‹¤ë ¤ë©´ ì—¬ëŸ¬ë¶„ì˜ Email 주소를 찾아 í´ë¦­í•˜ì‹­ì‹œì˜¤. +
    (괄호 í‘œì‹œëœ í•­ëª©ì€ "글쓰기 권한"ì´ ì—†ëŠ” í•­ëª©ì„ ê°€ë¥´í‚µë‹ˆë‹¤.) +

    +
    +
    + + ì—서 Digest를 ì‹ ì²­ 안하신 분들 : +
    +
    +
    + + ì—서 Digest를 신청하신 분들 : +
    +
    +

    +

    +

    +

    + +

    + diff --git a/templates/ko/subscribe.html b/templates/ko/subscribe.html index ce8059f3..a7856269 100644 --- a/templates/ko/subscribe.html +++ b/templates/ko/subscribe.html @@ -1,9 +1,72 @@ -<MM-List-Name> °¡ÀÔ °á°ú +<mm-list-name> 가입 ê²°ê³¼</mm-list-name> -

    °¡ÀÔ °á°ú

    - - - +

    가입 결과

    + + + diff --git a/templates/lt/admindbdetails.html b/templates/lt/admindbdetails.html index 7547a344..b9ad912d 100644 --- a/templates/lt/admindbdetails.html +++ b/templates/lt/admindbdetails.html @@ -1,5 +1,67 @@ -The administrative requests are displayed in one of two ways, on a summary page, and on a details +The administrative requests are displayed in one of two ways, on a summary page, and on a details page. The summary page contains pending subscription and unsubscription requests, as well as postings being held for your approval, grouped by sender email address. The details page contains @@ -27,8 +89,7 @@ a rejection notice. For membership requests, this simply discards the request without notice to the person making the request. This is usually the action you want to take for known spam. - - +

    For held messages, turn on the Preserve option if you want to save a copy of the message for the site administrator. This is useful for abusive messages that you want to discard, but need to keep @@ -50,8 +111,7 @@ this member can be trusted to post to the list without approval.

    If the sender is not a list member, you can add the email address to -a sender filter. Sender filters are described on the sender filter privacy page, and may be one of +a sender filter. Sender filters are described on the sender filter privacy page, and may be one of auto-accept (Accepts), auto-hold (Holds), auto-reject (Rejects), or auto-discard (Discards). This option will not be available if the address is already on one of the @@ -63,3 +123,4 @@ decision for.

    Return to the summary page. +

    \ No newline at end of file diff --git a/templates/lt/admindbpreamble.html b/templates/lt/admindbpreamble.html index 004cc8a0..063b1e10 100644 --- a/templates/lt/admindbpreamble.html +++ b/templates/lt/admindbpreamble.html @@ -1,13 +1,77 @@ -Pilnas sprendimo laukianèiø laiškø sàrašas +Pilnas sprendimo laukianèiø laiÅ¡kø sàraÅ¡as -

    Šiame tinklapyje matote disusijø forumo %(listname)s -laiškus, kurie laukia Jûsø sprendimo. +

    Šiame tinklapyje matote disusijø forumo %(listname)s +laiškus, kurie laukia Jûsø sprendimo. Dabar rodoma %(description)s -

    Kiekvienai administravimo uþklausai paþymëkite veiksmà -ir baigæ paspauskite Siøsti Visus Duomenis. +

    Kiekvienai administravimo uþklausai paþymëkite veiksmà +ir baigæ paspauskite Siøsti Visus Duomenis. -

    Smulkesnë instrukcija èia. +

    Smulkesnë instrukcija èia. -

    Taip pat galite perþiûrëti sprendimo laukianèiø laiškø santraukà. +

    Taip pat galite perþiûrëti sprendimo laukianèiø laiškø santraukà. +

    \ No newline at end of file diff --git a/templates/lt/admindbsummary.html b/templates/lt/admindbsummary.html index fd5e9254..f39c1cf9 100644 --- a/templates/lt/admindbsummary.html +++ b/templates/lt/admindbsummary.html @@ -1,14 +1,78 @@ -Sprendimo laukianèiø laiškø santrauka +Sprendimo laukianèiø laiÅ¡kø santrauka -

    Èia santrauka laiškø, laukianèiø jûsø sprendimo dël +

    Èia santrauka laiÅ¡kø, laukianèiø jûsø sprendimo dël forumo %(listname)s. -Pradþioje - prisijungimo ir atsijungimo prašymai, -toliau - laiškai, dël kuriø turite apsispræsti. +Pradþioje - prisijungimo ir atsijungimo praÅ¡ymai, +toliau - laiÅ¡kai, dël kuriø turite apsispræsti. -

    Kiekvienai administravimo uþklausai paþymëkite veiksmà -ir baigæ paspauskite Siøsti Visus Duomenis. +

    Kiekvienai administravimo uþklausai paþymëkite veiksmà +ir baigæ paspauskite Siøsti Visus Duomenis. -

    Smulkesnë instrukcija èia. +

    Smulkesnë instrukcija èia. -

    Taip pat galite perþiûrëti pilnà sprendimo laukianèiø laiškø sàrašà. +

    Taip pat galite perþiûrëti pilnà sprendimo laukianèiø laiškø sàrašà. +

    \ No newline at end of file diff --git a/templates/lt/admlogin.html b/templates/lt/admlogin.html index cc2802be..7e979292 100755 --- a/templates/lt/admlogin.html +++ b/templates/lt/admlogin.html @@ -1,38 +1,99 @@ - %(listname)s %(who)s prisijungimas +%(listname)s %(who)s prisijungimas - - -
    + + + %(message)s - - - - - - - - - - - -
    - %(listname)s %(who)s - prisijungimas -
    Forumo %(who)s slaptaþodis:
    -
    -

    Svarbu: - Jûs turite nustatyti "cookies enabled", - kitaip šio puslapio nustatymø pakeitimai nebus išsaugoti. + + + + + + + + + + + +
    +%(listname)s %(who)s + prisijungimas +
    Forumo %(who)s slaptaþodis:
    +
    +

    Svarbu: + Jûs turite nustatyti "cookies enabled", + kitaip šio puslapio nustatymø pakeitimai nebus išsaugoti. -

    Tai reikalinga tam, kad kiekvieno Jûsø veiksmo patrvirtinimui nereikëtø iš naujo ávedinëti slaptaþodþiø. - Prisijungimo informacija bus išsaugota Jûsø kompiueryje. - Ši informacija ištrinama kai tik išeinate iš tinklapiø perþiûros programos +

    Tai reikalinga tam, kad kiekvieno Jûsø veiksmo patrvirtinimui nereikëtø iÅ¡ naujo ávedinëti slaptaþodþiø. + Prisijungimo informacija bus iÅ¡saugota Jûsø kompiueryje. + Å i informacija iÅ¡trinama kai tik iÅ¡einate iÅ¡ tinklapiø perþiûros programos arba atsijungiate nuo administratoriaus puslapio paspausdami Logout - lange Kita prieþiûros veikla, nuoroda á kurá atsiras kai tik sëkmingai prisijungsite. -

    + lange Kita prieþiûros veikla, nuoroda á kurá atsiras kai tik sëkmingai prisijungsite. +

    diff --git a/templates/lt/archidxentry.html b/templates/lt/archidxentry.html index f9bb57aa..5c620b20 100644 --- a/templates/lt/archidxentry.html +++ b/templates/lt/archidxentry.html @@ -1,4 +1,68 @@ -
  • %(subject)s -  -%(author)s - +
  • %(subject)s +  +%(author)s + +
  • \ No newline at end of file diff --git a/templates/lt/archidxfoot.html b/templates/lt/archidxfoot.html index 3946bf6a..3b4e4e58 100644 --- a/templates/lt/archidxfoot.html +++ b/templates/lt/archidxfoot.html @@ -1,20 +1,84 @@ - -

    - Paskutiniojo laiško data: - %(lastdate)s
    - Suarchyvuota: %(archivedate)s -

    -

      -
    • Laiškai surûšiuoti pagal: + +

      +Paskutiniojo laiško data: +%(lastdate)s
      +Suarchyvuota: %(archivedate)s +

      +

      -

      -


      - Archyvø generavo Pipermail %(version)s. - - +
    +

    +


    +Archyvø generavo Pipermail %(version)s. + + +

    \ No newline at end of file diff --git a/templates/lt/archidxhead.html b/templates/lt/archidxhead.html index 65043cd4..f32225d7 100644 --- a/templates/lt/archidxhead.html +++ b/templates/lt/archidxhead.html @@ -1,23 +1,88 @@ - - - Forumo %(listname)s %(archive)s Archyvas %(archtype)s - + + + +Forumo %(listname)s %(archive)s Archyvas %(archtype)s + %(encoding)s - - - -

    %(archive)s Archyvas %(archtype)s

    -
      -
    • Laiškai surûšiuoti pagal: + + + +

      %(archive)s Archyvas %(archtype)s

      + -

      Pradëta: %(firstdate)s
      - Paskutinysis: %(lastdate)s
      - Laiškø: %(size)s

      -

        +
      +

      Pradëta: %(firstdate)s
      +Paskutinysis: %(lastdate)s
      +Laiškø: %(size)s

      +

        +

      \ No newline at end of file diff --git a/templates/lt/archlistend.html b/templates/lt/archlistend.html index 9bc052dd..2e1191b0 100644 --- a/templates/lt/archlistend.html +++ b/templates/lt/archlistend.html @@ -1 +1,64 @@ -
    + diff --git a/templates/lt/archliststart.html b/templates/lt/archliststart.html index 751467e1..75a4594f 100644 --- a/templates/lt/archliststart.html +++ b/templates/lt/archliststart.html @@ -1,4 +1,70 @@ - - - - +
    ArchyvasPerþiûra:Parsisiøsti
    + + + + + +
    ArchyvasPeržiūra:Parsisiųsti
    diff --git a/templates/lt/archtoc.html b/templates/lt/archtoc.html index 01dfdf48..974a84d0 100644 --- a/templates/lt/archtoc.html +++ b/templates/lt/archtoc.html @@ -1,20 +1,84 @@ - - - Forumo %(listname)s archyvai - + + + +Forumo %(listname)s archyvai + %(meta)s - - -

    Forumo %(listname)s archyvai

    -

    - Paspaudæ èia galite suþinoti daugiau apie forumà; - Èia - atsisiøsti visà forumo archyvà. + + +

    Forumo %(listname)s archyvai

    +

    +Paspaudæ èia galite suþinoti daugiau apie forumà; + Èia - atsisiøsti visà forumo archyvà. (%(size)s).

    %(noarchive_msg)s %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/lt/archtocentry.html b/templates/lt/archtocentry.html index c0d2a02c..9bc665e7 100644 --- a/templates/lt/archtocentry.html +++ b/templates/lt/archtocentry.html @@ -1,12 +1,74 @@ - - - %(archivelabel)s: - - [ Gija ] - [ Tema ] - [ Autorius ] - [ Data ] - + + +%(archivelabel)s: + +[ Gija ] +[ Tema ] +[ Autorius ] +[ Data ] + %(textlink)s - diff --git a/templates/lt/article.html b/templates/lt/article.html index d733d1c7..7b72cbae 100644 --- a/templates/lt/article.html +++ b/templates/lt/article.html @@ -1,12 +1,13 @@ - - - %(title)s - - - - - + +

    Forumo %(listname)s archyvai

    +

    + à šá forumà dar neatsiøstas në vienas laiškas. + Paspaudæ èia galite suþinoti daugiau apie forumà.

    - - + + diff --git a/templates/lt/headfoot.html b/templates/lt/headfoot.html index 4cf9d198..dd4a2390 100644 --- a/templates/lt/headfoot.html +++ b/templates/lt/headfoot.html @@ -1,11 +1,74 @@ -Á šá tekstà gali bûti átrauktos +à šá tekstà gali bûti átrauktos Python -formato eilutës kurios yra apdorojamos pagal poþymius. -Èia pateikiame pakeitimø sàrašà: +formato eilutës kurios yra apdorojamos pagal poþymius. +Èia pateikiame pakeitimø sàrašà:
      -
    • real_name - Sàrašo pavadinimas; daþniausiai - iš didþiosios raidës. +
    • real_name - SàraÅ¡o pavadinimas; daþniausiai + iÅ¡ didþiosios raidës.
    • list_name - The name by which the list is identified in URLs, where case is significant. @@ -18,9 +81,9 @@ e.g. listinfo/%(list_name)s to yield the listinfo page for the mailing list. -
    • description - Trumpas forumo aprašymas. +
    • description - Trumpas forumo apraÅ¡ymas. -
    • info - Pilnas forumo aprašymas. +
    • info - Pilnas forumo apraÅ¡ymas.
    • cgiext - The extension added to CGI scripts. -
    + diff --git a/templates/lt/listinfo.html b/templates/lt/listinfo.html index 7ed3ea49..0681e904 100644 --- a/templates/lt/listinfo.html +++ b/templates/lt/listinfo.html @@ -1,147 +1,207 @@ - - - - <MM-List-Name> Informacijos puslapis - - - -

    - - - - - - - - - - - - - - - - - - - + + + + + +

    - -- - -
    -

      -

    - Apie - - - -
    -

    -

    Jei norite perþiûrëti anksèiau siøstus laiškus, - aplankykite forumo - Archyvus. - -

    -
    - Naudojant -
    - Jei norite išsiøsti laiškà visiems forumo dalyvams, išsiøskite þinutæ adresu - . + + + +<mm-list-name> Informacijos puslapis</mm-list-name> + + +

    + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + +
    + -- + +
    +

      +

    +Apie + + + +
    +

    +

    Jei norite perþiûrëti anksèiau siøstus laiškus, + aplankykite forumo + Archyvus. + +

    +
    +Naudojant +
    + Jei norite išsiøsti laiškà visiems forumo dalyvams, išsiøskite þinutæ adresu + . -

    Toliau galite prisijungti prie forumo arba pakeisti Jûsø nustatymus. -

    - Prisijungti prie -
    -

    - Prisijunkite prie uþpildydami šiuos laukus: - -

      - - - - - - - - - - - - + + + + + + - - - - - -
      Jûsø el.pašto adresas: -  
      Jûsø vardas (nebûtina): 
      Èia áveskite slaptaþodá. - Èia veikia negrieþta saugumo sistema, jûsø slaptaþodis - ateityje gali bûti siunèiamas nešifruotu tekstu, - todël nenaudokite slaptaþodþiø, kuriais saugote ypaè +

      Toliau galite prisijungti prie forumo arba pakeisti Jûsø nustatymus. +

      +Prisijungti prie +
      +

      + Prisijunkite prie uþpildydami šiuos laukus: + +

        + + + + + + + + + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - - - -
        Jûsø el.pašto adresas: + 
        Jûsø vardas (nebûtina): 
        Èia áveskite slaptaþodá. + Èia veikia negrieþta saugumo sistema, jûsø slaptaþodis + ateityje gali bûti siunèiamas nešifruotu tekstu, + todël nenaudokite slaptaþodþiø, kuriais saugote ypaè atsakingus duomenis. -

        Jei neávesite slaptaþodþio, sistema já sukurs automatiškai - ir išsiøs Jums kai tik patvirtinsite prisijungimà prie forumo. +

        Jei neávesite slaptaþodþio, sistema já sukurs automatiškai + ir išsiøs Jums kai tik patvirtinsite prisijungimà prie forumo. -

        Jei pamiršite slaptaþodá, galësi nurodyti sistemai, - kad já išsiøstø Jûsø el.pašto adresu. +

        Jei pamiršite slaptaþodá, galësi nurodyti sistemai, + kad já išsiøstø Jûsø el.pašto adresu. - - -

        Pasirinkite slaptaþodá: 
        Pakartokite slaptaþodá: 
        Pasirinkite kalbà?  
        Ar norite gauti dienos laiškø rinkinius? + +

        +
        Pasirinkite slaptaþodá: 
        Pakartokite slaptaþodá: 
        Pasirinkite kalbà?  
        Ar norite gauti dienos laiškø rinkinius? Ne - Taip -
        -
        -
        - -
      -
      - - Nariai -
      - - - -

      - - - -

      - - - - +
    Ne + Taip +
    +
    +
    + + +

    + + Nariai +
    + + + +

    + + + +

    + +

    + diff --git a/templates/lt/options.html b/templates/lt/options.html index 3464b3fc..1ed00392 100644 --- a/templates/lt/options.html +++ b/templates/lt/options.html @@ -1,278 +1,311 @@ - - <MM-Presentable-User> dalyvio nustatymai forumui <MM-List-Name> - - - - - -
    - - forumo nustatymai dalyviui - -
    -

    - - - - - + +<mm-presentable-user> dalyvio nustatymai forumui <mm-list-name> +</mm-list-name></mm-presentable-user> + + +
    - bûsena, slaptaþodþiai ir nustatymai forumui . -
    - - - - -

    -

    +
    + + forumo nustatymai dalyviui + +
    - -

    - - - - - - - - +
    - - Forumo narystës informacijos pakeitimai -
    - Šiame lauke galite pakeisti nurodytà dalyvio el. pašto adresà. - Tuo atveju jums bus išsiøstas laiškas ir pakeitimai ásigalios tik po to, - kai Jûs patvirtinsite juos. -

    Patvirtinimui skiriama dienø: . -

    Taip pat galite pakeisti vardà, kuriuo pasirašote - (pvz. Tomas Jonušas). -

    Jeigu norite, kad bûtø pakeisti visø forumø nustatymai, - paþytmëkite vëliavëlæ Bendri pakeitimai. - -

    - - - - - - - -
    Naujas adresas:
    Patvirtinimas:
    -
    - - - - -
    Jûsø vardas (nebûtina):
    -
    -

    Bendri pakeitimai

    + + + +
    + bûsena, slaptaþodþiai ir nustatymai forumui . +
    + + +

    +

    - +

    - - +
    - Atsisakyti
    + + - - +
    + +Forumo narystës informacijos pakeitimai +
    + Šiame lauke galite pakeisti nurodytà dalyvio el. pašto adresà. + Tuo atveju jums bus išsiøstas laiškas ir pakeitimai ásigalios tik po to, + kai Jûs patvirtinsite juos. +

    Patvirtinimui skiriama dienø: . +

    Taip pat galite pakeisti vardà, kuriuo pasirašote + (pvz. Tomas Jonušas). +

    Jeigu norite, kad bûtø pakeisti visø forumø nustatymai, + paþytmëkite vëliavëlæ Bendri pakeitimai. -

    - Kiti Jûsø prisijungimai -
    - Išjunkite vëliavëlæ ir paspauskite šá mygtukà, - jeigu norite atsisakyti šio forumo. Atsargiai: - Tai ávyksta nedelsiant! +

    + + + + + + + +
    Naujas adresas:
    Patvirtinimas:
    +

    + + + + +
    Jûsø vardas (nebûtina):
    + +
    +

    Bendri pakeitimai

    + +

    + + + + - + +
    +

    +Atsisakyti +Kiti Jûsø prisijungimai +
    + Išjunkite vëliavëlæ ir paspauskite šá mygtukà, + jeigu norite atsisakyti šio forumo. Atsargiai: + Tai ávyksta nedelsiant!

    -

    - Galiet perþiûrëti sàrašà forumø, kuriuose Jûs dalyvaujate. - Pasinaudokite tuo, jei norite pakeisti visø jø nustatymus. +

    + Galiet perþiûrëti sàrašà forumø, kuriuose Jûs dalyvaujate. + Pasinaudokite tuo, jei norite pakeisti visø jø nustatymus.

    -

    -
    - - - - - - -
    - Jûsø slaptaþodis -
    - -
    -

    Pamiršote slaptaþodá?

    -
    - Paspauskite šá mygtukà, jeigu norite, kad slaptaþodis bûtø išsiøstas Jûsø adresu. -

    -

    - -
    -
    - -
    -

    Pakeisti slaptaþodá

    - - - - - - - - -
    Naujas slaptaþodis:
    Pakartokite slaptaþodá:
    - - -

    Bendri pakeitimai. -
    -
    - + + + +
    +Jûsø slaptaþodis +
    + +
    +

    Pamiršote slaptaþodá?

    +
    + Paspauskite šá mygtukà, jeigu norite, kad slaptaþodis bûtø išsiøstas Jûsø adresu. +

    +

    + +
    +

    + +
    +

    Pakeisti slaptaþodá

    + + + + + + + + +
    Naujas slaptaþodis:
    Pakartokite slaptaþodá:
    + +

    Bendri pakeitimai. +
    +

    - - +
    - Jûsø nustatymai forumui -
    +
    +Jûsø nustatymai forumui +
    -

    -Pradþioje paþymëti dabartiniai nustatymai. - -

    Atkreipkite dëmesá, kad daliai nustatymø yra vëliavëlë Bendri pakeitimai. -Paþymëjus šià vëliavëlæ pakeèiami Jûsø nustatymai visuose forumuose. -

    Paspauskie parodyti kitus mano forumus jei norite pamatti kur dar Jûsø esate prisijungæ. +Pradþioje paþymëti dabartiniai nustatymai. +

    Atkreipkite dëmesá, kad daliai nustatymø yra vëliavëlë Bendri pakeitimai. +Paþymëjus šià vëliavëlæ pakeèiami Jûsø nustatymai visuose forumuose. +

    Paspauskie parodyti kitus mano forumus jei norite pamatti kur dar Jûsø esate prisijungæ.

    - - - +
    - Pašto pristatymas

    - Ájungæ šá nustatymà gausite forumui skirtus laiškus. - Išjungæ šá nustatymà laiškø negausite, nors ir liksite prisijungæ. - Naudokite šá nustatymà pvz. išvykdami atostogauti, - tik nepamirškite gráþæ vël ájungti pristatymà! -

    - Ájungta
    - Išjungta

    - Bendri pakeitimai -

    + - - - + + - - - - - - - - - - - - - - - - - - - + + + + + + + + +
    +Pašto pristatymas

    + Ãjungæ šá nustatymà gausite forumui skirtus laiÅ¡kus. + IÅ¡jungæ šá nustatymà laiÅ¡kø negausite, nors ir liksite prisijungæ. + Naudokite šá nustatymà pvz. iÅ¡vykdami atostogauti, + tik nepamirÅ¡kite gráþæ vël ájungti pristatymà! +

    +Ãjungta
    +Išjungta

    +Bendri pakeitimai +

    - Rinkiniø siuntimas

    - Jeigu ájungsite rinkiniø siuntimà, Jums bus siuntinëjami - apjungti forumo laiškai. Tai turëtø vykti kartà á dienà, - taèiau ypaè aktyviuose forumuose gali bûti ir daþniau. - Jei nepasirinksite rinkiniø siuntimo, tada gausite kiekvienà laiškà - nedelsiant. Kai išjungiate rinkiniø siuntimà, turëtumëte gauti - paskutiná Jums neišsiøstø laiškø rinkiná, kitus laiškus gausite po vienà. -

    - Išjungta
    - Ájungta -
    - MIME ar vientiso teksto formato rinkiniai?

    - Jûsø pašto programa gali palaikyti MIME rinkinius arba ne. - MIME rinkiniai daugumai vartotojø yra patogesni, - taèiau pasirinkite vioentisà tekstà jei negalite normaliai - perskaityti MIME rinkiniø. -

    - MIME
    - Vientisas tekstas

    - Bendri pakeitimai -

    +Rinkiniø siuntimas

    + Jeigu ájungsite rinkiniø siuntimà, Jums bus siuntinëjami + apjungti forumo laiškai. Tai turëtø vykti kartà á dienà, + taèiau ypaè aktyviuose forumuose gali bûti ir daþniau. + Jei nepasirinksite rinkiniø siuntimo, tada gausite kiekvienà laiškà + nedelsiant. Kai išjungiate rinkiniø siuntimà, turëtumëte gauti + paskutiná Jums neišsiøstø laiškø rinkiná, kitus laiškus gausite po vienà. +

    +Išjungta
    +Ãjungta +
    +MIME ar vientiso teksto formato rinkiniai?

    + Jûsø pašto programa gali palaikyti MIME rinkinius arba ne. + MIME rinkiniai daugumai vartotojø yra patogesni, + taèiau pasirinkite vioentisà tekstà jei negalite normaliai + perskaityti MIME rinkiniø. +

    +MIME
    +Vientisas tekstas

    +Bendri pakeitimai +

    - Ar norite gauti savo siøstus á forumà laiškus?? -

    Áprastu nustatymu gausite visus á forumà siøstus, tame tarpe - ir savo laiškus. Jeigu nenorite gauti savo siøstø laiškø kopijos - èia nustatykite Ne. -

    - Ne
    - Taip -
    - Ar patvirtini kiekvienà á forumà nusiøstà laiškà?

    -

    - Ne
    - Taip -
    - Ar siuntinëti šio formo slaptaþodþio priminimus?

    - Kartà á mënesá gausite laiškà su išvardintais Jûsø uþsisakytø - pasirinktø forumø slaptaþodþiais. Jeigu visiems uþsisakytiems forumams - išjungsite šá parametrà, tada slaptaþodþiø priminimo laiškas jums - nebus siuntinëjamas. -

    - Išjungta
    - Ájungta

    - Bendri pakeitimai -

    - Ar paslëpti Jûsø adresà nuo forumo dalyviø?

    - Kiekvienas perþiûrëdamas forumo nariø sàrašà mato ir Jûsø adresà. - (Tuo kartais piktybiškai pasinaudoja nepageidaujamos reklamos - SPAM siuntinëtojai.) Nurodykite Paslëpti jei nenorite - rodyti Jûsø adreso. -

    - Rodyti
    - Paslëpti -
    - Jûsø pasirinkta kalba:

    -

    - -
    - Kokiø temø laiškus norite auþsisakyti??

    - Pasirinkdami vienà ar kità temà galite sumaþinti gaunamø laiškø srautà. - Gausite tik tuos laiškus, kurie atitinka Jûsø pasirinktas temas. -

    Jei nenurodysite në vienos temos, gausite visus forumo laiškus. -

    - -
    - Ar norite gauti laiškus, kurie neatitinka në vienos Jûsø nurodytos temos?

    - Šis nustatymas veikia tik tuo atveju, jei esate nurodæ bent vienà temà. - Jei pasirinksite Ne, tada gausite tik Jûsø pasirinktø temø laiškus. - Jei pasirinksite Taip, tada gausite visus laiškus. -

    Jeigu nenurodëte në vienos temos, tada gausite visus laiškus - nepriklausomai nuo šio nustatymo. -

    - Taip
    - Ne -
    - Apsaugoti nuo laiškø dubliavimo?

    - Jei Jûsø adresas nurodytas lauke To: arba - Cc: galite pasirinti negauti antros laiško kopijos. - Pasirinkite Taip jei nenorite gauti kopijø; - pasirinkite Ne jei norite gauti visus laiškus. -

    Jei forumas leidþia asmeninius laiškus, tada kiekvienai - laiško kopijai pridedama antraštë X-Mailman-Copy: yes. -

    - Ne
    - Taip

    - Bendri pakeitimai -

    -
    -
    +Ar norite gauti savo siøstus á forumà laiškus?? +

    Ãprastu nustatymu gausite visus á forumà siøstus, tame tarpe + ir savo laiÅ¡kus. Jeigu nenorite gauti savo siøstø laiÅ¡kø kopijos + èia nustatykite Ne. +

    +Ne
    +Taip +
    +Ar patvirtini kiekvienà á forumà nusiøstà laiškà?

    +

    +Ne
    +Taip +
    +Ar siuntinëti šio formo slaptaþodþio priminimus?

    + Kartà á mënesá gausite laiškà su išvardintais Jûsø uþsisakytø + pasirinktø forumø slaptaþodþiais. Jeigu visiems uþsisakytiems forumams + išjungsite šá parametrà, tada slaptaþodþiø priminimo laiškas jums + nebus siuntinëjamas. +

    +Išjungta
    +Ãjungta

    +Bendri pakeitimai +

    +Ar paslëpti Jûsø adresà nuo forumo dalyviø?

    + Kiekvienas perþiûrëdamas forumo nariø sàrašà mato ir Jûsø adresà. + (Tuo kartais piktybiškai pasinaudoja nepageidaujamos reklamos + SPAM siuntinëtojai.) Nurodykite Paslëpti jei nenorite + rodyti Jûsø adreso. +

    +Rodyti
    +Paslëpti +
    +Jûsø pasirinkta kalba:

    +

    + +
    +Kokiø temø laiškus norite auþsisakyti??

    + Pasirinkdami vienà ar kità temà galite sumaþinti gaunamø laiškø srautà. + Gausite tik tuos laiškus, kurie atitinka Jûsø pasirinktas temas. +

    Jei nenurodysite në vienos temos, gausite visus forumo laiškus. +

    + +
    +Ar norite gauti laiškus, kurie neatitinka në vienos Jûsø nurodytos temos?

    + Šis nustatymas veikia tik tuo atveju, jei esate nurodæ bent vienà temà. + Jei pasirinksite Ne, tada gausite tik Jûsø pasirinktø temø laiškus. + Jei pasirinksite Taip, tada gausite visus laiškus. +

    Jeigu nenurodëte në vienos temos, tada gausite visus laiškus + nepriklausomai nuo šio nustatymo. +

    +Taip
    +Ne +
    +Apsaugoti nuo laiškø dubliavimo?

    + Jei Jûsø adresas nurodytas lauke To: arba + Cc: galite pasirinti negauti antros laiško kopijos. + Pasirinkite Taip jei nenorite gauti kopijø; + pasirinkite Ne jei norite gauti visus laiškus. +

    Jei forumas leidþia asmeninius laiškus, tada kiekvienai + laiško kopijai pridedama antraštë X-Mailman-Copy: yes. +

    +Ne
    +Taip

    +Bendri pakeitimai +

    +
    +
    -

    - - - - + + +

    diff --git a/templates/lt/private.html b/templates/lt/private.html index da787427..b6df4626 100755 --- a/templates/lt/private.html +++ b/templates/lt/private.html @@ -1,33 +1,98 @@ - + + + - %(realname)s privataus archyvo prisijungimas + +%(realname)s privataus archyvo prisijungimas - - -
    + + + %(message)s - - - - - - - - - - - - - - - -
    - %(realname)s privataus archyvo prisijungimas -
    El. pašto adresas:
    Slaptaþodis:
    -
    -

    Important: From this point on, you + + + + + + + + + + + + + + + +
    +%(realname)s privataus archyvo prisijungimas +
    El. pašto adresas:
    Slaptašodis:
    + +
    +

    Important: From this point on, you must have cookies enabled in your browser, otherwise no administrative changes will take effect. @@ -39,21 +104,21 @@ Logout link under Other Administrative Activities (which you'll see once you successfully log in).

    - - - - - - + + + +
    - Password Reminder -
    If you don't remember your password, enter your email address + + + + + + - - - - -
    +Password Reminder +
    If you don't remember your password, enter your email address above and click the Remind button and your password will be emailed to you.
    - +
    +

    diff --git a/templates/lt/roster.html b/templates/lt/roster.html index ea7eb82c..cc7aa94f 100644 --- a/templates/lt/roster.html +++ b/templates/lt/roster.html @@ -1,53 +1,111 @@ - - - <MM-List-Name> Dalyviai - - - - -

    - - - - - - - - - - - - - - - -
    - - Dalyviai -
    - -

    -

    - -

    Paspauskite savo adresà, jei norite aplankyti nustatymø tinklapá. -
    (Apskliausti uþdrausti nariai.)

    -
    -
    - - Nariai, gaunatys visus liškus : -
    -
    -
    - - Dalyviai, gaunantys dienos laiškø rinkinius : -
    -
    -

    -

    -

    -

    - - - - + + +<mm-list-name> Dalyviai</mm-list-name> + + +

    + + + + + + + + + + + + + + + +
    + + Dalyviai +
    +

    +

    +

    Paspauskite savo adresà, jei norite aplankyti nustatymø tinklapá. +
    (Apskliausti uþdrausti nariai.)

    +
    +
    + + Nariai, gaunatys visus liškus : +
    +
    +
    + + Dalyviai, gaunantys dienos laiškø rinkinius : +
    +
    +

    +

    +

    +

    + +

    + diff --git a/templates/lt/subscribe.html b/templates/lt/subscribe.html index cda66c30..3e523518 100644 --- a/templates/lt/subscribe.html +++ b/templates/lt/subscribe.html @@ -1,9 +1,72 @@ -<MM-List-Name> Prisijungimo rezultatai +<mm-list-name> Prisijungimo rezultatai</mm-list-name> -

    Prisijungimo rezultatai

    - - - +

    Prisijungimo rezultatai

    + + + diff --git a/templates/nl/admindbdetails.html b/templates/nl/admindbdetails.html index cdba869d..c9d69a6c 100644 --- a/templates/nl/admindbdetails.html +++ b/templates/nl/admindbdetails.html @@ -1,4 +1,67 @@ -De administratieve verzoeken worden getoond op één of twee manieren, +De administratieve verzoeken worden getoond op één of twee manieren, op een overzichtspagina en op een detailpagina. De overzichtspagina bevat de wachtende aan- en afmeldingsverzoeken m.b.t. @@ -25,8 +88,7 @@
  • Negeren -- Verwijder het oorspronkelijke bericht, zonder het versturen van een weigeringsbericht. Bij lidmaatschapsverzoeken wordt het verzoek simpelweg genegeerd, zonder bericht aan de betreffende persoon. Dit is de gebruikelijke -actie in het geval van spam. - +actie in het geval van spam.
  • Voor wachtende berichten zet u de Bewaar-optie aan als u een copie van het bericht wilt opslaan voor de lijstbeheerder. Dit is nuttig als u 'misbruik'-berichten wilt negeren, maar een copie wilt bewaren voor later @@ -49,8 +111,7 @@

    Als de afzender geen lid is van de lijst, kunt u het e-mailadres toevoegen aan een afzender-filter. Afzender-filters worden beschreven -op de pagina Openbaarheid/moderatie/misbruik - Afzenderfilters, en kunnen +op de pagina Openbaarheid/moderatie/misbruik - Afzenderfilters, en kunnen zijn: auto-accept (Goedkeuren), auto-hold (Uitstel), auto-reject (Weigeren), of auto-discard (Negeren). Deze optie zal niet beschikbaar zijn als het adres reeds in een van de @@ -58,6 +119,7 @@

    Als u klaar bent, klikt u op de button Verstuur de wijzigingen onder in de pagina. Hiermee zullen alle gekozen handelingen m.b.t. alle -administratieve verzoeken geëffectueeerd worden. +administratieve verzoeken geëffectueeerd worden.

    Terug naar de overzichtspagina. +

    \ No newline at end of file diff --git a/templates/nl/admindbpreamble.html b/templates/nl/admindbpreamble.html index 49f151a0..51723f2c 100644 --- a/templates/nl/admindbpreamble.html +++ b/templates/nl/admindbpreamble.html @@ -1,9 +1,72 @@ -Deze pagina bevat een lijst van de berichten op de %(listname)s maillijst -die wachten op uw goedkeuring.
    +Deze pagina bevat een lijst van de berichten op de %(listname)s maillijst +die wachten op uw goedkeuring.
    Het toont momenteel %(description)s

    Voor elk administratief verzoek dient u de te ondernemen handeling te selecteren. -Als u daarmee klaar bent, drukt u op de button Verstuur de wijzigingen.
    +Als u daarmee klaar bent, drukt u op de button Verstuur de wijzigingen.
    Meer gedetailleerde instructies kunt u hier vinden. -

    U kunt ook een totaaloverzicht bekijken van alle onbehandelde verzoeken. \ No newline at end of file +

    U kunt ook een totaaloverzicht bekijken van alle onbehandelde verzoeken.

    \ No newline at end of file diff --git a/templates/nl/admindbsummary.html b/templates/nl/admindbsummary.html index c2f2bb31..fb8c5aef 100644 --- a/templates/nl/admindbsummary.html +++ b/templates/nl/admindbsummary.html @@ -1,8 +1,71 @@ -Deze pagina toont een overzicht van alle administratieve verzoeken m.b.t. de %(listname)s maillijst die wachten op uw goedkeuring. +Deze pagina toont een overzicht van alle administratieve verzoeken m.b.t. de %(listname)s maillijst die wachten op uw goedkeuring. Allereerst ziet u een lijst van alle aanmeldings- en afmeldingsverzoeken, mits die er zijn, gevolgd door een lijst van de berichten die wachten op uw goedkeuring.

    Voor elk administratief verzoek dient u de te ondernemen handeling te selecteren. -Als u daarmee klaar bent, drukt u op de button Verstuur de wijzigingen.
    +Als u daarmee klaar bent, drukt u op de button Verstuur de wijzigingen.
    Meer gedetailleerde instructies kunt u hier vinden. -

    U kunt ook de details bekijken van alle in de wachtrij staande berichten. \ No newline at end of file +

    U kunt ook de details bekijken van alle in de wachtrij staande berichten.

    \ No newline at end of file diff --git a/templates/nl/admlogin.html b/templates/nl/admlogin.html index 612355fd..d2d0142d 100755 --- a/templates/nl/admlogin.html +++ b/templates/nl/admlogin.html @@ -1,36 +1,97 @@ - %(listname)s %(who)s inloggen - - - -
    +%(listname)s %(who)s inloggen + + + + %(message)s - - - - - - - - - - - -
    - %(listname)s %(who)s - inloggen -
    Lijst%(who)s wachtwoord:
    -
    -

    Belangrijk:
    + + + + + + + + + + + +
    +%(listname)s %(who)s + inloggen +
    Lijst%(who)s wachtwoord:
    +
    +

    Belangrijk:
    Vanaf hier moet het gebruik van cookies in uw browser toegelaten zijn, anders kunnen uw administratieve handelingen niet geeffectueerd worden!

    'Session cookies' worden gebruikt in Mailman's administratieve interface opdat u zichzelf niet steeds weer dient te identificeren bij elke handeling. Dit cookie zal automatisch verlopen als u de browser afsluit, of wanneer u zelf het cookie actief doet verlopen door op de Uitloggen link te klikken in de hierna volgende schermen. -

    +

    \ No newline at end of file diff --git a/templates/nl/archidxentry.html b/templates/nl/archidxentry.html index f9bb57aa..5c620b20 100644 --- a/templates/nl/archidxentry.html +++ b/templates/nl/archidxentry.html @@ -1,4 +1,68 @@ -
  • %(subject)s -  -%(author)s - +
  • %(subject)s +  +%(author)s + +
  • \ No newline at end of file diff --git a/templates/nl/archidxfoot.html b/templates/nl/archidxfoot.html index 97774359..95b1b4ea 100644 --- a/templates/nl/archidxfoot.html +++ b/templates/nl/archidxfoot.html @@ -1,21 +1,85 @@ - -

    - Datum laatste bericht: - %(lastdate)s
    - Gearchiveerd op: %(archivedate)s -

    -

      -
    • Berichten gesorteerd op: + +

      +Datum laatste bericht: +%(lastdate)s
      +Gearchiveerd op: %(archivedate)s +

      +

      -

      -


      - Dit archief is gegenereerd met +
    +

    +


    +Dit archief is gegenereerd met Pipermail %(version)s. - - + + +

    \ No newline at end of file diff --git a/templates/nl/archidxhead.html b/templates/nl/archidxhead.html index 64474d3a..09175428 100644 --- a/templates/nl/archidxhead.html +++ b/templates/nl/archidxhead.html @@ -1,15 +1,79 @@ - - - Het %(listname)s %(archive)s Archief op %(archtype)s - + + + +Het %(listname)s %(archive)s Archief op %(archtype)s + %(encoding)s - - - -

    %(archive)s Archief op %(archtype)s

    -
      -
    • Berichten gesorteerd op: + + + +

      %(archive)s Archief op %(archtype)s

      + -

      Begin: %(firstdate)s
      - Einde: %(lastdate)s
      - Berichten: %(size)s

      -

        +
      +

      Begin: %(firstdate)s
      +Einde: %(lastdate)s
      +Berichten: %(size)s

      +

        +

      \ No newline at end of file diff --git a/templates/nl/archlistend.html b/templates/nl/archlistend.html index 9bc052dd..2e1191b0 100644 --- a/templates/nl/archlistend.html +++ b/templates/nl/archlistend.html @@ -1 +1,64 @@ - + diff --git a/templates/nl/archliststart.html b/templates/nl/archliststart.html index b54e910e..2f6bf5dd 100644 --- a/templates/nl/archliststart.html +++ b/templates/nl/archliststart.html @@ -1,4 +1,68 @@ - - - - +
      ArchiefBekijken op:Downloadbare versie
      + + + +
      ArchiefBekijken op:Downloadbare versie
      \ No newline at end of file diff --git a/templates/nl/archtoc.html b/templates/nl/archtoc.html index 437b5d59..43ddd440 100644 --- a/templates/nl/archtoc.html +++ b/templates/nl/archtoc.html @@ -1,13 +1,77 @@ - - - Het %(listname)s Archief - + + + +Het %(listname)s Archief + %(meta)s - - -

      Het %(listname)s Archief

      -

      + + +

      Het %(listname)s Archief

      +

      U kunt meer informatie over deze lijst bekijken of u kunt het volledige 'ruwe' archief downloaden (%(size)s). @@ -16,5 +80,5 @@

      Het %(listname)s Archief

      %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/nl/archtocentry.html b/templates/nl/archtocentry.html index ec2480b2..b31d4303 100644 --- a/templates/nl/archtocentry.html +++ b/templates/nl/archtocentry.html @@ -1,12 +1,74 @@ - - - %(archivelabel)s: - - [ Draad ] - [ Onderwerp ] - [ Auteur ] - [ Datum ] - + + +%(archivelabel)s: + +[ Draad ] +[ Onderwerp ] +[ Auteur ] +[ Datum ] + %(textlink)s - diff --git a/templates/nl/archtocnombox.html b/templates/nl/archtocnombox.html index 8962b8e6..92d30465 100644 --- a/templates/nl/archtocnombox.html +++ b/templates/nl/archtocnombox.html @@ -1,18 +1,82 @@ - - - Het %(listname)s Archief - + + + +Het %(listname)s Archief + %(meta)s - - -

      Het %(listname)s Archief

      -

      + + +

      Het %(listname)s Archief

      +

      U kunt meer informatie over deze lijst bekijken.

      %(noarchive_msg)s %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/nl/article.html b/templates/nl/article.html index 12c3c444..1c7ef018 100644 --- a/templates/nl/article.html +++ b/templates/nl/article.html @@ -1,12 +1,13 @@ - - - %(title)s - - - - - + +

      Het %(listname)s Archief

      +

      Er zijn nog geen berichten verzonden via deze lijst, dus is het archief momenteel leeg. Meer informatie over deze lijst.

      - - \ No newline at end of file + + \ No newline at end of file diff --git a/templates/nl/headfoot.html b/templates/nl/headfoot.html index 8c37fe9b..5ef62beb 100644 --- a/templates/nl/headfoot.html +++ b/templates/nl/headfoot.html @@ -1,10 +1,73 @@ -De tekst mag de volgende +De tekst mag de volgende Python format strings bevatten, die automatisch herleid worden naar attributen die elders gedefinieerd zijn voor deze lijst. De mogelijke vervangende strings zijn:
        -
      • real_name - De "mooie" naam van de lijst; gewoonlijk +
      • real_name - De "mooie" naam van de lijst; gewoonlijk de lijstnaam met hoofdletters er in.
      • list_name - De lijstnaam zonder hoofdletters er in, gepresenteerd als @@ -21,4 +84,4 @@
      • info - De uitgebreide omschrijving van de maillijst.
      • cgiext - De extentie toegevoegd aan CGI scripts. -
      +
    diff --git a/templates/nl/listinfo.html b/templates/nl/listinfo.html index ae2f2a5a..30fd5e01 100644 --- a/templates/nl/listinfo.html +++ b/templates/nl/listinfo.html @@ -1,141 +1,202 @@ - - - - <MM-List-Name> Informatiepagina - - - -

    - - - - - - - - - - - - - - - - - - - + + + + + +

    - -- - -
    -

      -

    - Over - - - -
    -

    -

    Om alle eerdere berichten op deze lijst te raadplegen, kijk in het Archief. - -

    -
    - Gebruik van -
    - Om een bericht naar alle leden van de lijst te sturen, adresseert u het aan:
    - . + + + +<mm-list-name> Informatiepagina</mm-list-name> + + +

    + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + +
    + -- + +
    +

      +

    +Over + + + +
    +

    +

    Om alle eerdere berichten op deze lijst te raadplegen, kijk in het Archief. + +

    +
    +Gebruik van +
    + Om een bericht naar alle leden van de lijst te sturen, adresseert u het aan:
    +.

    Hieronder kunt u uzelf aanmelden als lid van de lijst of uw persoonlijke instellingen wijzigen. -

    - Aanmelden bij -
    -

    - Om u aan te melden bij vult u onderstaand formulier in. - -

      - - - - - - - - - - - - + + + + + + - - - - - -
      Uw e-mailadres: -  
      Uw naam (optioneel): 
      U kunt hieronder een wachtwoord invullen. Dit voorziet +

      +Aanmelden bij +
      +

      + Om u aan te melden bij vult u onderstaand formulier in. + +

        + + + + + + + + + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - - - -
        Uw e-mailadres: + 
        Uw naam (optioneel): 
        U kunt hieronder een wachtwoord invullen. Dit voorziet in een zekere mate van bescherming en moet voorkomen dat anderen kunnen knoeien met uw lidmaatschap. Gebruik niet een eerder door u gebruikt wachtwoord, omdat het af en toe onversleuteld naar u gemaild zal worden. -

        Als u ervoor kiest om geen wachtwoord in te vullen, wordt een automatisch +

        Als u ervoor kiest om geen wachtwoord in te vullen, wordt een automatisch gegenereerd wachtwoord naar u toegezonden zodra u uw aanmelding hebt bevestigd. U kunt steeds om toezending van uw wachtwoord vragen wanneer u uw persoonlijke instellingen wilt wijzigen. - -
        -
        Kies een wachtwoord: 
        Herhaal het wachtwoord ter bevestiging: 
        Wat is uw voorkeurstaal voor de berichten?  
        Wilt u de berichten gebundeld ontvangen in een dagelijkse verzamelmail? + + +
        Kies een wachtwoord: 
        Herhaal het wachtwoord ter bevestiging: 
        Wat is uw voorkeurstaal voor de berichten?  
        Wilt u de berichten gebundeld ontvangen in een dagelijkse verzamelmail? Nee - Ja -
        -
        -
        - -
      -
      - - leden -
      - - - -

      - - - -

      - - - +
    Nee + Ja +
    +
    +
    + + +

    + + leden +
    + + + +

    + + + +

    + +

    + diff --git a/templates/nl/options.html b/templates/nl/options.html index d6cf6f0c..a236737a 100644 --- a/templates/nl/options.html +++ b/templates/nl/options.html @@ -1,41 +1,101 @@ - - <MM-Presentable-User> persoonlijke instellingen voor <MM-List-Name> - - - - - -
    - - persoonlijke instellingen voor -
    + +<mm-presentable-user> persoonlijke instellingen voor <mm-list-name> +</mm-list-name></mm-presentable-user> + + + + +
    + + persoonlijke instellingen voor +

    - - - - - +
    - 's aanmeldstatus, - wachtwoord, en andere instellingen voor de maillijst. -
    - - - - -

    -

    + + + +
    +'s aanmeldstatus, + wachtwoord, en andere instellingen voor de maillijst. +
    + + +

    +

    - - +

    - - - +
    - - Uw lidmaatschapsinformatie wijzigen -
    U kunt het adres veranderen waaronder u bij de maillijst staat + + + - - - - - -
    + +Uw lidmaatschapsinformatie wijzigen +
    U kunt het adres veranderen waaronder u bij de maillijst staat aangemeld door het nieuwe adres in de onderstaande velden in te voeren. Let erop dat u een aan dit nieuwe adres verzonden bevestingsbericht moet beantwoorden voordat de adreswijziging definitief wordt doorgevoerd. @@ -47,212 +107,190 @@

    Als u al uw lidmaatschappen van maillijsten op wilt wijzigen, dient u de checkbox Wijzig in alle lijsten aan te vinken. -

    - - - - - - - -
    Nieuw e-mailadres:
    Herhaal het nieuwe e-mailadres:
    -
    - - - - -
    Uw naam (optioneel):
    -
    -

    Wijzig in alle lijsten

    - +

    + + + + + + + +
    Nieuw e-mailadres:
    Herhaal het nieuwe e-mailadres:
    +

    + + + + +
    Uw naam (optioneel):
    + +
    +

    Wijzig in alle lijsten

    +

    - - - - - - + + + + + +

    - Uzelf afmelden van de mailing lijst - Andere maillijsten waarbij u bent aangemeld -
    + + + + - + +
    +

    +
    +

    +Uzelf afmelden van de mailing lijst +Andere maillijsten waarbij u bent aangemeld +
    Vink de onderstaande checkbox aan en klik op de button om uw lidmaatschap van deze maillijst te beeindigen. Waarschuwing: u wordt direct uit de maillijst verwijderd!

    -

    -

    -
    +
    +

    +

    U kunt een overzicht bekijken van alle andere maillijsten op waarvan u lid bent. Gebruik dit als u als dezelfde wijzigingen in uw persoonlijke instellingen wilt doorvoeren bij die andere lijsten.

    -

    -

    -
    -
    - - - - - + +

    -

    - Uw wachtwoord -
    - -
    -

    Uw wachtwoord vergeten?

    -
    + + + - -
    +Uw wachtwoord +
    + +
    +

    Uw wachtwoord vergeten?

    +
    Druk op de knop, en het wachtwoord wordt verstuurd naar uw e-mailadres. -

    -

    -

    - -

    -
    -
    - -
    -

    Uw wachtwoord wijzigen

    - - - - - - - - -
    Nieuw wachtwoord:
    Herhaal het nieuwe wachtwoord:
    - - -

    Wijzig in alle lijsten -
    -
    - +

    +

    - - + +

    + +

    +
    - Uw persoonlijke instellingen -
    + +
    +

    Uw wachtwoord wijzigen

    + + + + + + + + +
    Nieuw wachtwoord:
    Herhaal het nieuwe wachtwoord:
    + +

    Wijzig in alle lijsten +
    +

    +

    + +
    +Uw persoonlijke instellingen +
    -

    De huidige instellingen zijn aangevinkt. -

    Let erop dat sommige instellingen een Instellen voor alle lijsten optie hebben. Het aanvinken van deze checkbox houdt in dat de wijzigingen in alle lijsten waarvan u lid bent wordt doorgevoerd. Klik hierboven op Laat mijn andere aanmeldingen zien voor een overzicht van die lijsten.

    - - - - + + + + %(textlink)s - diff --git a/templates/no/archtocnombox.html b/templates/no/archtocnombox.html index ae70fa74..fc113977 100644 --- a/templates/no/archtocnombox.html +++ b/templates/no/archtocnombox.html @@ -1,18 +1,82 @@ - - - %(listname)s arkivet - + + + +%(listname)s arkivet + %(meta)s - - -

    %(listname)s arkivet

    -

    + + +

    %(listname)s arkivet

    +

    Du kan se mer informasjon om denne listen.

    %(noarchive_msg)s %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/no/article.html b/templates/no/article.html index 9d68f290..783bb033 100644 --- a/templates/no/article.html +++ b/templates/no/article.html @@ -1,12 +1,13 @@ - - - %(title)s - - - - - + +

    Arkiv for epostlisten %(listname)s

    +

    + Ingen meldinger er sendt til denne listen enda, så arkivet er for tiden tomt. Mer informasjon om listen er tilgjengelig.

    - - + + diff --git a/templates/no/headfoot.html b/templates/no/headfoot.html index d1fd26e9..6b48739e 100644 --- a/templates/no/headfoot.html +++ b/templates/no/headfoot.html @@ -1,25 +1,88 @@ -Teksten kan inneholde formateringskoder +Teksten kan inneholde formateringskoder som byttes ut med verdier fra listens oppsett. For detaljer, se Pythons formateringsregler (engelsk). Gyldige koder er:
      -
    • real_name - Listens formaterte navn; vanligvis +
    • real_name - Listens formaterte navn; vanligvis listenavnet med stor forbokstav eller store bokstaver enkelte steder.
    • list_name - Listens navn som brukt i URLer, - der det har betydning om den staves med store eller små bokstaver. + der det har betydning om den staves med store eller smÃ¥ bokstaver.
    • host_name - Internettadressen (fully qulified - domain name) til maskinen som listeserveren går på. + domain name) til maskinen som listeserveren gÃ¥r pÃ¥.
    • web_page_url - Basis URL for Mailman. Denne kan legges til sammen med, f.eks. listinfo/%(list_name)s - for å danne URLen til en listes infoside. + for Ã¥ danne URLen til en listes infoside.
    • description - En kort beskrivelse av listen.
    • info - Full beskrivelse av listen.
    • cgiext - Tillegg som legges til CGI scripts. -
    + diff --git a/templates/no/listinfo.html b/templates/no/listinfo.html index ed218fa1..e6e75c88 100644 --- a/templates/no/listinfo.html +++ b/templates/no/listinfo.html @@ -1,141 +1,202 @@ - - - - <MM-List-Name> Infoside - - - -

    -

    - - Mail ontvangen

    + + - +

    - - - + +

    - - - - - - + + - - + - - - - + + - - + - - + - - - +

    +
    + +Mail ontvangen

    Zet dit op Aan om berichten van de lijst te ontvangen. Zet dit op Uit als u lid wilt blijven, maar tijdelijk geen berichten wilt ontvangen (bv. als u op vakantie gaat). Vergeet niet om de ontvangst bij thuiskomst weer in te schakelen; dit gebeurt niet automatisch. -

    - Aan
    - Uit

    - Instellen voor alle lijsten -

    +Aan
    +Uit

    +Instellen voor alle lijsten +

    - Verzamelmail ontvangen

    +

    +Verzamelmail ontvangen

    Als u deze optie op Aan zet, ontvangt u geen losse berichten op het moment dat ze verzonden worden, maar ontvangt u die gebundeld - (gewoonlijk één per dag, bij 'drukke' lijsten kunnen het er meerdere zijn). Als u + (gewoonlijk één per dag, bij 'drukke' lijsten kunnen het er meerdere zijn). Als u de optie van Aan naar Uit verandert, zult u mogelijk - nog één laatste verzamelmail ontvangen. -

    - Uit
    - Aan -
    - Verzamelmail in MIME of gewone tekst?

    + nog één laatste verzamelmail ontvangen. +

    +Uit
    +Aan +
    +Verzamelmail in MIME of gewone tekst?

    Uw mailprogramma ondersteunt mogelijk geen MIME verzamelmails. Over het algemeen genieten MIME verzamelmails de voorkeur, maar als u problemen hebt om ze te lezen, kies dan voor Gewone tekst. -

    - MIME
    - Gewone tekst

    - Instellen voor alle lijsten -

    +MIME
    +Gewone tekst

    +Instellen voor alle lijsten +

    - Door uzelf verstuurde berichten ontvangen?

    +

    +Door uzelf verstuurde berichten ontvangen?

    Normaal ontvangt u alle berichten van deze lijst. Als u niet uw eigen berichten wilt ontvangen, zet deze optie op Nee. -

    - Nee
    - Ja -
    - Ontvangstbevestiging van door uzelf verstuurde berichten?

    -

    - Nee
    - Ja -
    - Wachtwoordherinneringen?

    +

    +Nee
    +Ja +
    +Ontvangstbevestiging van door uzelf verstuurde berichten?

    +

    +Nee
    +Ja +
    +Wachtwoordherinneringen?

    Eenmaal per maand ontvangt u een e-mail met een wachtwoordherinnering voor elke lijst waarbij u bent aangemeld. U kunt dit per lijst uitzetten door de optie Nee aan te vinken. Als u de wachtwoordherinneringen voor alle lijsten uitzet, zullen geen herinneringsberichten naar u worden gestuurd. -

    - Nee
    - Ja

    - Instellen voor alle lijsten -

    - Uzelf geheimhouden op de ledenlijst?

    +

    +Nee
    +Ja

    +Instellen voor alle lijsten +

    +Uzelf geheimhouden op de ledenlijst?

    Normaal wordt uw e-mailadres getoond als iemand de ledenlijst bekijkt (zij het in een afwijkende vorm om het oogsten van adressen door spammers tegen te - gaan). Als u überhaupt niet wilt dat uw e-mailadres in de ledenlijst wordt + gaan). Als u überhaupt niet wilt dat uw e-mailadres in de ledenlijst wordt getoond, selecteer de optie Ja. -

    - Nee
    - Ja -
    - Wat is uw voorkeurstaal?

    -

    - -
    - Van welke onderwerp-categoriëen wilt u berichten ontvangen?

    - Door één of meer onderwerpen te kiezen, kunt u de via deze maillijst verstuurde +

    +Nee
    +Ja +
    +Wat is uw voorkeurstaal?

    +

    + +
    +Van welke onderwerp-categoriëen wilt u berichten ontvangen?

    + Door één of meer onderwerpen te kiezen, kunt u de via deze maillijst verstuurde berichten filteren, zodat u slechts een deel van de berichten ontvangt. - Als een bericht overeenkomt met één van de door u gekozen onderwerpen, + Als een bericht overeenkomt met één van de door u gekozen onderwerpen, dan zult u het bericht ontvangen, in het andere geval niet.

    Als een bericht met geen enkel onderwerp overeenkomt, hangt het van de hieronder vermelde instelling af of u het bericht zult ontvangen. Als u geen enkel - onderwerp kiest, zult u álle berichten van deze maillijst ontvangen. -

    - -
    - Wilt u berichten ontvangen die met geen enkele onderwerp-filter overeenkomen?

    - Deze optie heeft alleen effect als u hierboven ten minste één onderwerp hebt + onderwerp kiest, zult u álle berichten van deze maillijst ontvangen. +

    + +
    +Wilt u berichten ontvangen die met geen enkele onderwerp-filter overeenkomen?

    + Deze optie heeft alleen effect als u hierboven ten minste één onderwerp hebt gekozen. De optie geeft de standaard aflevering aan voor berichten die met geen enkele van de hierboven gekozen onderwerpen overeenkomen. Het selecteren van Nee houdt in dat als het bericht met geen enkel onderwerp-filter overeenkomt, u het bericht niet @@ -261,13 +299,12 @@

    Uw wachtwoord wijzigen

    Als hierboven geen onderwerpen zijn geselecteerd, zult u elk bericht van deze maillijst ontvangen. -

    - Nee
    - Ja -
    - Dubbele berichten vermijden?

    +

    +Nee
    +Ja +
    +Dubbele berichten vermijden?

    Als u ook zelf expliciet genoemd wordt in de Aan: of Cc: headers van een aan de lijst geadresseerd bericht, kunt u ervoor kiezen dat u @@ -278,21 +315,17 @@

    Uw wachtwoord wijzigen

    dubbele berichten te ontvangen, zal aan elk bericht de header X-Mailman-Copy: yes toegevoegd worden. -
    - Nee
    - Ja

    - Instellen voor alle lijsten -

    -
    -
    +Nee
    +Ja

    +Instellen voor alle lijsten +

    +
    +
    -

    - - - - + + +

    diff --git a/templates/nl/private.html b/templates/nl/private.html index cc181299..374efdac 100755 --- a/templates/nl/private.html +++ b/templates/nl/private.html @@ -1,54 +1,115 @@ - %(realname)s Privé-archief Login - - - -
    +%(realname)s Privé-archief Login + + + + %(message)s - - - - - - - - - - - - - - - -
    - %(realname)s Privé-archief Login -
    E-mailadres:
    Wachtwoord:
    -
    -

    Belangrijk:
    + + + + + + + + + + + + + + + +
    +%(realname)s Privé-archief Login +
    E-mailadres:
    Wachtwoord:
    +
    +

    Belangrijk:
    Vanaf hier moet het gebruik van cookies in uw browser toegelaten zijn, anders kunnen uw - administratieve handelingen niet geëffectueerd worden! + administratieve handelingen niet geëffectueerd worden!

    'Session cookies' worden gebruikt in Mailmans beheersinterface opdat u zichzelf niet steeds weer dient te identificeren bij elke handeling. Dit cookie zal automatisch verlopen als u de browser afsluit, of wanneer u zelf het cookie actief doet verlopen door de pagina 'lidmaatschapsinstellingen' te bezoeken en daar op de link Uitloggen te klikken.

    - - - - - - + + + +
    - Wachtwoordherinnering -
    Indien u uw wachtwoord vergeten bent, kunt u hierboven uw e-mailadres invullen + + + + + + - - - - -
    +Wachtwoordherinnering +
    Indien u uw wachtwoord vergeten bent, kunt u hierboven uw e-mailadres invullen en op de Wachtwoord Vergeten-knop drukken. U ontvangt dan uw wachtwoord per e-mail.
    - +
    +

    diff --git a/templates/nl/roster.html b/templates/nl/roster.html index 15d2b57d..fc2b0ba5 100644 --- a/templates/nl/roster.html +++ b/templates/nl/roster.html @@ -1,51 +1,110 @@ - - - Leden van <MM-List-Name> - - - - -

    - - - - - - - - - - - - - - - -
    - Leden van -
    - -

    -

    - -

    Klik op uw adres om de pagina met uw persoonlijke instellingen te bezoeken.
    (Leden - vermeld tussen haakjes hebben de ontvangst van mail uitgezet.)

    -
    -
    - - Leden met reguliere mail van : -
    -
    -
    - - Leden met verzamelmail van : -
    -
    -

    -

    -

    -

    - - - \ No newline at end of file + + +Leden van <mm-list-name></mm-list-name> + + +

    + + + + + + + + + + + + + + + +
    +Leden van +
    +

    +

    +

    Klik op uw adres om de pagina met uw persoonlijke instellingen te bezoeken.
    (Leden + vermeld tussen haakjes hebben de ontvangst van mail uitgezet.)

    +
    +
    + + Leden met reguliere mail van : +
    +
    +
    + + Leden met verzamelmail van : +
    +
    +

    +

    +

    +

    + +

    + \ No newline at end of file diff --git a/templates/nl/subscribe.html b/templates/nl/subscribe.html index ccf3e284..9dd79554 100644 --- a/templates/nl/subscribe.html +++ b/templates/nl/subscribe.html @@ -1,9 +1,72 @@ -<MM-List-Name> aanmeldingsresultaat +<mm-list-name> aanmeldingsresultaat</mm-list-name> -

    Resultaat van uw aanmelding bij

    - - - +

    Resultaat van uw aanmelding bij

    + + + \ No newline at end of file diff --git a/templates/no/admindbdetails.html b/templates/no/admindbdetails.html index a76f117d..558c600f 100644 --- a/templates/no/admindbdetails.html +++ b/templates/no/admindbdetails.html @@ -1,62 +1,123 @@ -Administrative forespørsler kan vises på to måter, enten som en oversikt, eller med alle detaljer. -På oversiktsiden vises både ventende påmeldinger/utmeldinger og -epost som venter på godkjenning for å bli distribuert via listen. +Administrative forespørsler kan vises pÃ¥ to mÃ¥ter, enten som en oversikt, eller med alle detaljer. +PÃ¥ oversiktsiden vises bÃ¥de ventende pÃ¥meldinger/utmeldinger og +epost som venter pÃ¥ godkjenning for Ã¥ bli distribuert via listen. Detaljsiden viser i tillegg epostenes meldingshoder og et utdrag av innholdet i dem. -

    På begge sidene kan du angi følgende tiltak: +

    På begge sidene kan du angi følgende tiltak:

      -
    • Avvent -- Utsett avgjørelsen. Ingenting blir gjort med - denne forespørselen nå, men epost som venter på godkjenning kan du - ta vare på eller videresende (se nedenfor). +
    • Avvent -- Utsett avgjørelsen. Ingenting blir gjort med + denne forespørselen nÃ¥, men epost som venter pÃ¥ godkjenning kan du + ta vare pÃ¥ eller videresende (se nedenfor).
    • Godkjenne -- Godta meldingen slik den er og distribuere den - til listen. For forespørsler om medlemskap, iverksette utmelding eller - påmelding. + til listen. For forespørsler om medlemskap, iverksette utmelding eller + pÃ¥melding. -
    • Avslå -- Slette meldingen og gi beskjed tilbake til - avsenderen om at meldingen ikke ble godkjent. For forespørsler om - medlemskap, avslå utmelding eller påmelding. I alle tilfeller bør - du også oppgi en grunn i den tilhørende tekstboksen. +
    • AvslÃ¥ -- Slette meldingen og gi beskjed tilbake til + avsenderen om at meldingen ikke ble godkjent. For forespørsler om + medlemskap, avslÃ¥ utmelding eller pÃ¥melding. I alle tilfeller bør + du ogsÃ¥ oppgi en grunn i den tilhørende tekstboksen.
    • Kaste -- Slette meldingen uten at noe beskjed sendes - til avsender. Nyttig for søppel-epost og reklame-epost (spam). - For forespørsler om medlemskap, avslå utmelding eller påmelding, uten - at den som prøvde å melde seg ut av eller på listen får vite noe + til avsender. Nyttig for søppel-epost og reklame-epost (spam). + For forespørsler om medlemskap, avslÃ¥ utmelding eller pÃ¥melding, uten + at den som prøvde Ã¥ melde seg ut av eller pÃ¥ listen fÃ¥r vite noe om det. -
    - -

    Kryss av for Ta vare på meldingen dersom du ønsker å lagre en kopi for -systemets administrator. Dette kan være nyttig for meldinger som er + +

    Kryss av for Ta vare pÃ¥ meldingen dersom du ønsker Ã¥ lagre en kopi for +systemets administrator. Dette kan være nyttig for meldinger som er knyttet til misbruk av systemet og som du egentlig vil slette, men som kan -være nyttig å ta en titt på senere. +være nyttig Ã¥ ta en titt pÃ¥ senere.

    Kryss av for Videresend denne meldingen og oppgi en epostadresse -hvis du vil videresende meldingen til noen andre. For å redigere en epost -som holdes tilbake før den sendes til listen, bør du sende den til deg selv +hvis du vil videresende meldingen til noen andre. For Ã¥ redigere en epost +som holdes tilbake før den sendes til listen, bør du sende den til deg selv (eller evt. listens eiere), og slette den opprinnelige eposten. -Deretter, når eposten kommer i innboksen din, rediger den og gjør de endringer -du ønsker, og send den så til listen på nytt, med et Approved: felt +Deretter, nÃ¥r eposten kommer i innboksen din, rediger den og gjør de endringer +du ønsker, og send den sÃ¥ til listen pÃ¥ nytt, med et Approved: felt i meldingshodet etterfulgt av listens passord. Husk at det er vanlig netiquette -i et slikt tilfelle å oppgi at du har redigert innholdet. +i et slikt tilfelle Ã¥ oppgi at du har redigert innholdet.

    Hvis avsenderen er medlem av listen men er moderert, kan du fjerne -moderasjonsflagget hvis du ønsker det. Dette er nyttig når du har satt -opp listen slik at nye medlemmer har en prøvetid på listen, og du har bestemt -at nå er prøvetiden over for dette medlemmet. - -

    Hvis avsenderen ikke er medlem av listen, kan du få epostadressen lagt inn -i et av avsenderfiltrene. Avsenderfiltre er forklart på Filtrering på avsender-siden, og kan være -auto-godkjenne, auto-holdetilbake, auto-avslå, eller -auto-forkast. Dette valget vil ikke være tilgjengelig dersom denne adressen +moderasjonsflagget hvis du ønsker det. Dette er nyttig nÃ¥r du har satt +opp listen slik at nye medlemmer har en prøvetid pÃ¥ listen, og du har bestemt +at nÃ¥ er prøvetiden over for dette medlemmet. + +

    Hvis avsenderen ikke er medlem av listen, kan du få epostadressen lagt inn +i et av avsenderfiltrene. Avsenderfiltre er forklart på Filtrering på avsender-siden, og kan være +auto-godkjenne, auto-holdetilbake, auto-avslå, eller +auto-forkast. Dette valget vil ikke være tilgjengelig dersom denne adressen allerede er lagt inn i et slikt filter. -

    Etter du har foretatt dine valg, klikk på Utføre -knappen på toppen eller bunnen av siden. Det vil iverksette dine -avgjørelser. +

    Etter du har foretatt dine valg, klikk på Utføre +knappen på toppen eller bunnen av siden. Det vil iverksette dine +avgjørelser.

    Tilbake til oversiktsiden. +

    \ No newline at end of file diff --git a/templates/no/admindbpreamble.html b/templates/no/admindbpreamble.html index 4152e3c5..e49ff979 100644 --- a/templates/no/admindbpreamble.html +++ b/templates/no/admindbpreamble.html @@ -1,8 +1,72 @@ -Denne siden inneholder noen av de epostene til listen %(listname)s -som holdes tilbake for godkjenning. Nå viser den +Denne siden inneholder noen av de epostene til listen %(listname)s +som holdes tilbake for godkjenning. NÃ¥ viser den %(description)s -

    Velg ønsket avgjørelse for hver forespørsel, og trykk deretter Utføre -når du er ferdig. Nærmere instruksjoner finner du her. +

    Velg ønsket avgjørelse for hver forespørsel, og trykk deretter Utføre +når du er ferdig. Nærmere instruksjoner finner du her. -

    Du kan også vise en oversikt over alle forepørsler som venter på en avgjørelse. +

    Du kan også vise en oversikt over alle forepørsler som venter på en avgjørelse. +

    \ No newline at end of file diff --git a/templates/no/admindbsummary.html b/templates/no/admindbsummary.html index db718da7..8f5f317a 100644 --- a/templates/no/admindbsummary.html +++ b/templates/no/admindbsummary.html @@ -1,10 +1,74 @@ -Her finner du en oversikt over forespørsler som skal vurderes for epostlisten -%(listname)s.
    -Eventuelle søknader om medlemsskap til listen vil -vises først, deretter kommer meldinger som er sendt til listen -men som trenger godkjenning for å bli distribuert. +Her finner du en oversikt over forespørsler som skal vurderes for epostlisten +%(listname)s.
    +Eventuelle søknader om medlemsskap til listen vil +vises først, deretter kommer meldinger som er sendt til listen +men som trenger godkjenning for å bli distribuert. -

    For hver forespørsel, velg ønsket tiltak, og klikk på Utføre knappen når du er ferdig. -Nærmere instruksjoner er også tilgjengelig. +

    For hver forespørsel, velg ønsket tiltak, og klikk på Utføre knappen når du er ferdig. +Nærmere instruksjoner er også tilgjengelig. -

    Du kan også vise alle detaljer for alle tilbakeholdte meldinger hvis du ønsker det. +

    Du kan også vise alle detaljer for alle tilbakeholdte meldinger hvis du ønsker det. +

    \ No newline at end of file diff --git a/templates/no/admlogin.html b/templates/no/admlogin.html index 761cedaf..09506aff 100755 --- a/templates/no/admlogin.html +++ b/templates/no/admlogin.html @@ -1,39 +1,100 @@ - %(listname)s %(who)s Innlogging +%(listname)s %(who)s Innlogging - - -
    + + + %(message)s - - - - - - - - - - - -
    - %(listname)s %(who)s - Innlogging -
    Listens %(who)s passord:
    -
    -

    Viktig: Fra nå av må du ha cookies - påslått i nettleseren din, ellers vil ingen administrative endringer + + + + + + + + + + + +
    +%(listname)s %(who)s + Innlogging +
    Listens %(who)s passord:
    +
    +

    Viktig: Fra nå av må du ha cookies + påslått i nettleseren din, ellers vil ingen administrative endringer bli lagret.

    Session cookies brukes i Mailmans administrative sider, - slik at du ikke skal behøve å oppgi passord for hver endring du gjør. - De vil forsvinne automatisk når du lukker nettleseren din, eller du - kan fjerne de manuelt ved å klikke + slik at du ikke skal behøve Ã¥ oppgi passord for hver endring du gjør. + De vil forsvinne automatisk nÃ¥r du lukker nettleseren din, eller du + kan fjerne de manuelt ved Ã¥ klikke Logg ut linken under Andre administrative aktiviteter - (som du får opp etter å ha logget inn). -

    + (som du får opp etter å ha logget inn). +

    diff --git a/templates/no/archidxfoot.html b/templates/no/archidxfoot.html index 0edbdfef..d2b19de1 100644 --- a/templates/no/archidxfoot.html +++ b/templates/no/archidxfoot.html @@ -1,21 +1,85 @@ - -

    - Dato på nyeste melding: - %(lastdate)s
    - Arkivert: %(archivedate)s -

    -

      -
    • Meldinger sortert på: + +

      +Dato på nyeste melding: +%(lastdate)s
      +Arkivert: %(archivedate)s +

      +

      -

      -


      - Dette arkivet ble generert av +
    +

    +


    +Dette arkivet ble generert av Pipermail %(version)s. - - + + +

    \ No newline at end of file diff --git a/templates/no/archidxhead.html b/templates/no/archidxhead.html index 8cac560b..91a98a62 100644 --- a/templates/no/archidxhead.html +++ b/templates/no/archidxhead.html @@ -1,15 +1,79 @@ - - - %(listname)s arkivet for %(archive)s sortert på %(archtype)s - + + + +%(listname)s arkivet for %(archive)s sortert pÃ¥ %(archtype)s + %(encoding)s - - - -

    Arkivet for %(archive)s sortert på %(archtype)s

    -
      -
    • Meldinger sortert på: + + + +

      Arkivet for %(archive)s sortert på %(archtype)s

      +
        +
      • Meldinger sortert pÃ¥: %(thread_ref)s %(subject_ref)s %(author_ref)s @@ -17,8 +81,9 @@

        Arkivet for %(archive)s sortert på %(archtype)s

      • Mer informasjon om denne listen...
      • -
      -

      Startdato: %(firstdate)s
      - Sluttdato: %(lastdate)s
      - Meldinger: %(size)s

      -

        +
      +

      Startdato: %(firstdate)s
      +Sluttdato: %(lastdate)s
      +Meldinger: %(size)s

      +

        +

      \ No newline at end of file diff --git a/templates/no/archliststart.html b/templates/no/archliststart.html index cbca34a0..70bd3548 100644 --- a/templates/no/archliststart.html +++ b/templates/no/archliststart.html @@ -1,4 +1,68 @@ - - - - +
      ArkivSorter på:Nedlastbar versjon
      + + + +
      ArkivSorter på:Nedlastbar versjon
      \ No newline at end of file diff --git a/templates/no/archtoc.html b/templates/no/archtoc.html index 32cca532..2973e5e6 100644 --- a/templates/no/archtoc.html +++ b/templates/no/archtoc.html @@ -1,13 +1,77 @@ - - - %(listname)s arkivet - + + + +%(listname)s arkivet + %(meta)s - - -

      %(listname)s arkivet

      -

      + + +

      %(listname)s arkivet

      +

      Du kan se mer informasjon om denne listen eller du kan laste ned hele arkivet (%(size)s). @@ -16,5 +80,5 @@

      %(listname)s arkivet

      %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/no/archtocentry.html b/templates/no/archtocentry.html index bd23e737..c1f5aee1 100644 --- a/templates/no/archtocentry.html +++ b/templates/no/archtocentry.html @@ -1,12 +1,74 @@ - -
    %(archivelabel)s: - [ Tråd ] - [ Tittel ] - [ Forfatter ] - [ Dato ] -
    %(archivelabel)s: +[ Tråd ] +[ Tittel ] +[ Forfatter ] +[ Dato ] +
    - - - - - - - - - - - - - - - - - - + + + + + +

    - -- - -
    -

      -

    - Om - - - -
    -

    -

    For å se arkivet over eposter sendt til denne listen, gå inn på - - Arkivet. - -

    -
    - Hvordan bruke epostlisten -
    - For å sende en epost til alle på listen, send den til - . + + + +<mm-list-name> Infoside</mm-list-name> + + +

    + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + +
    + -- + +
    +

      +

    +Om + + + +
    +

    +

    For å se arkivet over eposter sendt til denne listen, gå inn på + + Arkivet. + +

    +
    +Hvordan bruke epostlisten +
    + For å sende en epost til alle på listen, send den til + . -

    Du kan melde deg på listen, eller endre innstillingene for - ditt medlemsskap på listen nedenfor: -

    - Melde deg på -
    -

    - Du kan melde deg på ved å fylle ut nødvendig informasjon nedenfor. - -

      - - - - - - - - - - - - + + + + + + - - - - - -
      Din epostadresse: -  
      Navn (valgfritt): 
      Du kan skrive et passord i følgende felter. - Dette gir en svak sikkerhet, men forhindrer at andre får tilgang til dine innstillinger. +

      Du kan melde deg på listen, eller endre innstillingene for + ditt medlemsskap på listen nedenfor: +

      +Melde deg på +
      +

      + Du kan melde deg på ved å fylle ut nødvendig informasjon nedenfor. + +

        + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - -
        Din epostadresse: + 
        Navn (valgfritt): 
        Du kan skrive et passord i følgende felter. + Dette gir en svak sikkerhet, men forhindrer at andre får tilgang til dine innstillinger. Ikke bruk et verdifullt passord, for passordet kan bli sendt til deg i klartekst via epost. -

        Dersom du ikke skriver noe passord, vil du automatisk få tildelt et passord. - Det vil bli sendt til deg så snart du har bekreftet din påmelding. - Du kan senere be om å få passordet tilsendt, eller endre det. - -
        -
        Ønsket passord: 
        Ønsket passord en gang til: 
        Velg språk for meldinger:  
        Vil du benytte sammendrag-modus? Nei - Ja -
        -
        -
        - -
      -
      - - Medlemmer av -
      - - - -

      - - - -

      - - - +

      Dersom du ikke skriver noe passord, vil du automatisk få tildelt et passord. + Det vil bli sendt til deg så snart du har bekreftet din påmelding. + Du kan senere be om å få passordet tilsendt, eller endre det. + + +
    Ønsket passord: 
    Ønsket passord en gang til: 
    Velg språk for meldinger:  
    Vil du benytte sammendrag-modus? Nei + Ja +
    +
    +
    + + +

    + +Medlemmer av +
    + + + +

    + + + +

    + +

    + diff --git a/templates/no/options.html b/templates/no/options.html index a105e9bc..58578f2d 100644 --- a/templates/no/options.html +++ b/templates/no/options.html @@ -1,301 +1,335 @@ - - Personlig medlemside for <MM-Presentable-User> på listen <MM-List-Name> - - - - - -
    - - Personlig medlemside for på listen -
    + +Personlig medlemside for <mm-presentable-user> på listen <mm-list-name> +</mm-list-name></mm-presentable-user> + + + + +
    + + Personlig medlemside for på listen +

    - - - - - +
    - Medlemsstatus, passord, og innstillinger for - på epostlisten . -
    - - - - -

    -

    + + + +
    +Medlemsstatus, passord, og innstillinger for + på epostlisten . +
    + + +

    +

    - - +

    - - - + +
    - - Bytte epostadresse på -
    Du kan bytte ut den epostadressen du har meldt inn på - listen ved å skrive inn en annen epostadresse i feltene nedenfor. - Merk at en epost med nærmere instruksjoner vil da bli sendt til den nye - adressen. Endringene trer ikke i kraft før du har bekreftet dem. - Du har på deg til å bekrefte endringen. - -

    Du kan også oppgi eller endre navnet ditt på listen (f.eks Ola Normann). + + + - - - - - -
    + +Bytte epostadresse på +
    Du kan bytte ut den epostadressen du har meldt inn på + listen ved å skrive inn en annen epostadresse i feltene nedenfor. + Merk at en epost med nærmere instruksjoner vil da bli sendt til den nye + adressen. Endringene trer ikke i kraft før du har bekreftet dem. + Du har på deg til å bekrefte endringen. + +

    Du kan også oppgi eller endre navnet ditt på listen (f.eks Ola Normann).

    Hvis du vil endre epostadressen din for alle - epostlistene du er påmeldt på , kryss av for + epostlistene du er pÃ¥meldt pÃ¥ , kryss av for Endre alle lister. -

    - - - - - - - -
    Ny epostadresse:
    Epostadresse en gang til: -
    -
    - - +
    Ditt navn +

    + + + + + + + +
    Ny epostadresse:
    Epostadresse en gang til: +
    +
    + + - - -
    Ditt navn (valgfritt):
    -
    -

    Endre min epostadresse på alle lister jeg er medlem av

    - +

    +
    +

    Endre min epostadresse på alle lister jeg er medlem av

    - - - - - - - + + + %(textlink)s diff --git a/templates/pl/archtocnombox.html b/templates/pl/archtocnombox.html index 8f74945f..55c36f48 100644 --- a/templates/pl/archtocnombox.html +++ b/templates/pl/archtocnombox.html @@ -1,13 +1,77 @@ + - - Archiwum listy %(listname)s - + +Archiwum listy %(listname)s + %(meta)s - - -

    Archiwum listy %(listname)s

    -

    Odwiedź stronę informacyjną tej listy.

    + + +

    Archiwum listy %(listname)s

    +

    Odwiedź stronę informacyjną tej listy.

    %(noarchive_msg)s %(archive_listing_start)s %(archive_listing)s diff --git a/templates/pl/article.html b/templates/pl/article.html index 8e34c6a8..4488963c 100644 --- a/templates/pl/article.html +++ b/templates/pl/article.html @@ -1,50 +1,50 @@ + - - %(title)s - - - - %(encoding)s %(prev)s %(next)s - - -

    %(subject_html)s

    - %(author_html)s - %(email_html)s
    - %(datestr_html)s -

    +
    %(body)s -
    -

    +
    +Więcej informacji o liście %(listname)s
    +

    diff --git a/templates/pl/emptyarchive.html b/templates/pl/emptyarchive.html index ba559941..35015879 100644 --- a/templates/pl/emptyarchive.html +++ b/templates/pl/emptyarchive.html @@ -1,16 +1,80 @@ - - - Archiwum listy %(listname)s - - - - -

    Archiwum listy %(listname)s

    -

    - Jeszcze żadna wiadomość nie została wysłana na listę, a więc - archiwum jest puste. Zajrzyj na stronę - informacyjną tej listy. + + + +Archiwum listy %(listname)s + + + + +

    Archiwum listy %(listname)s

    +

    + Jeszcze żadna wiadomość nie została wysłana na listę, a więc + archiwum jest puste. Zajrzyj na stronę + informacyjną tej listy.

    - - + + diff --git a/templates/pl/listinfo.html b/templates/pl/listinfo.html index 4468c976..0b68e305 100644 --- a/templates/pl/listinfo.html +++ b/templates/pl/listinfo.html @@ -1,146 +1,205 @@ - - - - - Strona informacyjna listy <MM-List-Name> - - - -

    -

    - Melde deg av - Andre lister du er medlem av på -
    - Kryss av i avkrysningsboksen og klikk følgende knapp for å melde deg + + + + - + +
    +

    +Melde deg av +Andre lister du er medlem av på +
    + Kryss av i avkrysningsboksen og klikk følgende knapp for å melde deg av denne epostlisten. MERK: Du vil umiddelbart bli meldt ut av listen!

    -

    - Du kan velge å se alle lister du er påmeldt, slik at du kan gjøre samme - endringer på flere lister på en gang. +

    + Du kan velge å se alle lister du er påmeldt, slik at du kan gjøre samme + endringer på flere lister på en gang.

    -

    -
    - - - - - - -
    - Ditt passord for -
    - -
    -

    Har du glemt passordet ditt?

    -
    - Trykk på denne knappen for å få passordet ditt tilsendt på epost. -

    -

    - -
    -
    - -
    -

    Bytte passord

    - - - - - - - - -
    Nytt - passord:
    Nytt passord en gang - til:
    - - -

    Endre passordet for alle - lister jeg er medlem av på . -
    -
    - + + + +
    +Ditt passord for +
    + +
    +

    Har du glemt passordet ditt?

    +
    + Trykk på denne knappen for å få passordet ditt tilsendt på epost. +

    +

    + +
    +

    + +
    +

    Bytte passord

    + + + + + + + + +
    Nytt + passord:
    Nytt passord en gang + til:
    + +

    Endre passordet for alle + lister jeg er medlem av på . +
    +

    - - +
    - Dine innstillinger for -
    +
    +Dine innstillinger for +
    -

    Gjeldende innstillinger er valgt. - -

    Merk at noen av innstillingene har et Bruk på alle valg. -Krysser du av for det, vil alle lister du er medlem av på også -få innstillingen satt slik som du setter den her. Klikk på Vise andre -lister jeg er medlem av ovenfor for å se hvilke andre epostlister du +

    Merk at noen av innstillingene har et Bruk på alle valg. +Krysser du av for det, vil alle lister du er medlem av på også +få innstillingen satt slik som du setter den her. Klikk på Vise andre +lister jeg er medlem av ovenfor for å se hvilke andre epostlister du er medlem av.

    - -
    - - Motta meldinger

    - Sett denne til Ja for å motta epost som sendes til denne listen. - Sett den til Nei hvis du i en periode ikke ønsker å motta epost som sendes til listen, - men likevel ønsker å være medlem av listen. (kan være nyttig f.eks. hvis du skal på ferie) - Setter du den til Nei, må du huske å sette den tilbake igjen, + + - +

    - - - +

    + - - - - - - + + - - + - - - - + + - - + - - + - - +

    +
    + +Motta meldinger

    + Sett denne til Ja for å motta epost som sendes til denne listen. + Sett den til Nei hvis du i en periode ikke ønsker å motta epost som sendes til listen, + men likevel ønsker å være medlem av listen. (kan være nyttig f.eks. hvis du skal på ferie) + Setter du den til Nei, må du huske å sette den tilbake igjen, for den vil ikke bli satt tilbake til Ja automatisk. -

    - Ja
    - Nei

    - Bruk på alle -

    +Ja
    +Nei

    +Bruk på alle +

    - Sammendrag-modus

    - Setter du på sammendrag-modus, vil du med jevne mellomrom (vanligvis en gang om dagen, - muligens flere ganger, det kommer an på hvor mye som sendes til listen) få en - samle-epost som inneholder alle meldinger som blir sendt til listen, istedenfor å få hver +

    +Sammendrag-modus

    + Setter du pÃ¥ sammendrag-modus, vil du med jevne mellomrom (vanligvis en gang om dagen, + muligens flere ganger, det kommer an pÃ¥ hvor mye som sendes til listen) fÃ¥ en + samle-epost som inneholder alle meldinger som blir sendt til listen, istedenfor Ã¥ fÃ¥ hver enkelt melding. Hvis du bytter fra sammendrag-modus til normal- - modus, vil du muligens allikevel få en siste epost med sammendrag - selv om du allerede da vil begynne å motta alle meldinger som + modus, vil du muligens allikevel fÃ¥ en siste epost med sammendrag + selv om du allerede da vil begynne Ã¥ motta alle meldinger som kommer til listen. -

    - Av
    - På -
    - Motta sammendrag som ren tekst eller i MIME-format?

    - Det kan være epostleseren din ikke støtter sammendrag i MIME-format. - Å motta sammendrag i MIME-format anbefales, men får du problemer med - å lese epost i det formatet, kan du velge ren tekst her. -

    - MIME
    - Ren tekst

    - Bruk på alle -

    +Av
    +PÃ¥ +
    +Motta sammendrag som ren tekst eller i MIME-format?

    + Det kan være epostleseren din ikke støtter sammendrag i MIME-format. + Å motta sammendrag i MIME-format anbefales, men får du problemer med + å lese epost i det formatet, kan du velge ren tekst her. +

    +MIME
    +Ren tekst

    +Bruk på alle +

    - Motta dine egne meldinger til listen?

    +

    +Motta dine egne meldinger til listen?

    Vanligvis vil du motta epost som du selv sender til listen. - Hvis du ikke vil motta disse, bør du velge Nei her. -

    - Nei
    - Ja -
    - Motta bekreftelse når du sender epost til listen?

    - - Velg Ja her hvis du ønsker å få en bekreftelse hver gang du sender en epost til listen. -

    - Nei
    - Ja -
    - Få passordet ditt tilsendt jevnlig?

    - En gang i måneneden kan det være du får en epost som inneholder - passordene for alle listene du er medlem av på dette systemet. + Hvis du ikke vil motta disse, bør du velge Nei her. +

    +Nei
    +Ja +
    +Motta bekreftelse når du sender epost til listen?

    + + Velg Ja her hvis du ønsker å få en bekreftelse hver gang du sender en epost til listen. +

    +Nei
    +Ja +
    +FÃ¥ passordet ditt tilsendt jevnlig?

    + En gang i måneneden kan det være du får en epost som inneholder + passordene for alle listene du er medlem av på dette systemet. Du kan skru av dette for denne enkelte listen her. -

    - Nei
    - Ja

    - Bruk på alle -

    - Vil du vises på listen over medlemmer av listen?

    - Når noen går inn på websiden for å se alle medlemmer av listen, vil - vanligvis din epostadresse vises. (Men stavet på en måte slik at +

    +Nei
    +Ja

    +Bruk på alle +

    +Vil du vises på listen over medlemmer av listen?

    + NÃ¥r noen gÃ¥r inn pÃ¥ websiden for Ã¥ se alle medlemmer av listen, vil + vanligvis din epostadresse vises. (Men stavet pÃ¥ en mÃ¥te slik at roboter som plukker opp epostadresser fra websider (og kanskje - deretter sender søppel og reklame til den), ikke vil forstå at det + deretter sender søppel og reklame til den), ikke vil forstÃ¥ at det er en epostadresse.) Velg 'Nei' her hvis du ikke vil at epostadressen - din skal vises på listen. -

    - Nei
    - Ja -
    - Hvilket språk foretrekker du?

    -

    - -
    - Hvilke emner vil du motta? -

    Ved å velge ett eller flere emner, kan du filtrere meldingene som sendes til + din skal vises pÃ¥ listen. +

    +Nei
    +Ja +
    +Hvilket språk foretrekker du?

    +

    + +
    +Hvilke emner vil du motta? +

    Ved å velge ett eller flere emner, kan du filtrere meldingene som sendes til epostlisten, slik at du kun vil motta det som kan interessere deg. -

    Hvis en melding ikke går under noen emner, kommer det an på innstillingen nedenfor +

    Hvis en melding ikke går under noen emner, kommer det an på innstillingen nedenfor om du vil motta meldingen eller ikke. Hvis du ikke velger noen emner her, vil du motta alle meldinger som sendes til listen. -

    - -
    - Vil du motta meldinger som ikke går under noe emne? -

    Dette valget gjelder bare hvis du har valgt et eller flere emner ovenfor. - Det avgjør om du skal motta meldinger som ikke går under noen emner. +

    + +
    +Vil du motta meldinger som ikke går under noe emne? +

    Dette valget gjelder bare hvis du har valgt et eller flere emner ovenfor. + Det avgjør om du skal motta meldinger som ikke går under noen emner. Velger du Nei vil du ikke motta dem, velger du Ja vil du motta dem.

    Hvis du ikke har valgt noen emner i innstillingen ovenfor, vil du motta alle meldinger som sendes til listen. -

    - Nei
    - Ja -
    - Unngå å få epost fra listen som også er adressert direkte til deg?

    +

    +Nei
    +Ja +
    +Unngå å få epost fra listen som også er adressert direkte til deg?

    Du kan velge hva som skal skje dersom det kommer en epost til listen som i tillegg er - adressert direkte til deg (dvs. din epostadresse står i To: eller Cc: - feltet). Velg Ja for da å unngå å motta epost fra listen; velg Nei - for likevel å motta epost fra listen. + adressert direkte til deg (dvs. din epostadresse stÃ¥r i To: eller Cc: + feltet). Velg Ja for da Ã¥ unngÃ¥ Ã¥ motta epost fra listen; velg Nei + for likevel Ã¥ motta epost fra listen. -

    For spesielt interesserte: Dersom listen er satt opp til å personifisere - meldinger sendt til listen, og du velger å motta epost fra listen, vil hver +

    For spesielt interesserte: Dersom listen er satt opp til å personifisere + meldinger sendt til listen, og du velger å motta epost fra listen, vil hver kopi ha et X-Mailman-Copy: yes felt i meldingshodet. -

    - Nei
    - Ja

    - Sett globalt -

    -
    -
    +Nei
    +Ja

    +Sett globalt +

    +
    +
    -

    - - - - + + +

    diff --git a/templates/no/private.html b/templates/no/private.html index 9546e0bd..ea7bdca8 100755 --- a/templates/no/private.html +++ b/templates/no/private.html @@ -1,58 +1,119 @@ - %(realname)s Innlogging til Private Arkiver +%(realname)s Innlogging til Private Arkiver - - -
    + + + %(message)s - - - - - - - - - - - - - - - -
    - %(realname)s Innlogging til Private Arkiver -
    Epost adresse:
    Passord:
    -
    -

    Viktig: Fra nå av må du ha cookies - påslått i nettleseren din, ellers vil ingen administrative endringer + + + + + + + + + + + + + + + +
    +%(realname)s Innlogging til Private Arkiver +
    Epost adresse:
    Passord:
    +
    +

    Viktig: Fra nå av må du ha cookies + påslått i nettleseren din, ellers vil ingen administrative endringer bli lagret.

    Session cookies brukes i Mailmans administrative sider, - slik at du ikke skal behøve å oppgi passord for hver endring du gjør. - De vil forsvinne automatisk når du lukker nettleseren din, eller du - kan fjerne de manuelt ved å klikke + slik at du ikke skal behøve Ã¥ oppgi passord for hver endring du gjør. + De vil forsvinne automatisk nÃ¥r du lukker nettleseren din, eller du + kan fjerne de manuelt ved Ã¥ klikke Logout linken under Other Administrative - Activities (som du får opp etter å ha logget inn). + Activities (som du fÃ¥r opp etter Ã¥ ha logget inn).

    - - - - - - + + + +
    - Password Reminder -
    If you don't remember your password, enter your email address + + + + + + - - - - -
    +Password Reminder +
    If you don't remember your password, enter your email address above and click the Remind button and your password will be emailed to you.
    - +
    +

    diff --git a/templates/no/roster.html b/templates/no/roster.html index 86e5c51e..97a03e07 100644 --- a/templates/no/roster.html +++ b/templates/no/roster.html @@ -1,52 +1,111 @@ - - - <MM-List-Name> Medlemmer - - - - -

    - - - - - - - - - - - - - - - -
    - - Medlemmer -
    - -

    -

    - -

    Klikk på adressen din for å gå til din personlige side. -
    (Adresser i parentes har stoppet mottak av meldinger til listen.)

    -
    -
    - - Medlemmer i normal-modus på : -
    -
    -
    - Medlemmer i sammendrag-modus på - : -
    -
    -

    -

    -

    -

    - - - + + +<mm-list-name> Medlemmer</mm-list-name> + + +

    + + + + + + + + + + + + + + + +
    + + Medlemmer +
    +

    +

    +

    Klikk på adressen din for å gå til din personlige side. +
    (Adresser i parentes har stoppet mottak av meldinger til listen.)

    +
    +
    + + Medlemmer i normal-modus på : +
    +
    +
    + Medlemmer i sammendrag-modus på + : +
    +
    +

    +

    +

    +

    + +

    + diff --git a/templates/no/subscribe.html b/templates/no/subscribe.html index 855c091e..5dff222c 100644 --- a/templates/no/subscribe.html +++ b/templates/no/subscribe.html @@ -1,9 +1,72 @@ -<MM-List-Name>: Resultat av påmelding +<mm-list-name>: Resultat av pÃ¥melding</mm-list-name> -

    : Resultat av påmelding

    - - - +

    : Resultat av påmelding

    + + + diff --git a/templates/pl/admlogin.html b/templates/pl/admlogin.html index 095fca7f..6e0301bd 100644 --- a/templates/pl/admlogin.html +++ b/templates/pl/admlogin.html @@ -1,39 +1,100 @@ - Uwierzytelnienie %(who)s %(listname)s - +Uwierzytelnienie %(who)s %(listname)s + - - -
    + + + %(message)s - - - - - - - - - - - -
    - Uwierzytelnienie - %(who)s %(listname)s -
    Hasło %(who)s:
    -
    -

    Uwaga: Od tego miejsca musisz mieć - włączoną obsługę ciasteczek (cookies) w przeglądarce. W przeciwnym - razie zmiany administracyjne nie przyniosą efektu. + + + + + + + + + + + +
    +Uwierzytelnienie + %(who)s %(listname)s +
    Hasło %(who)s:
    +
    +

    Uwaga: Od tego miejsca musisz mieć + włączoną obsługę ciasteczek (cookies) w przeglądarce. W przeciwnym + razie zmiany administracyjne nie przyniosą efektu. -

    Mechanizm ciasteczek wykorzystywany jest w Mailmanie, żeby - nie trzeba było podawać hasła przy każdej czynności administracyjnej. - Identyfikujące Cię ciasteczko automatycznie wygaśnie, gdy wyłączysz - przeglądarkę, lub jeśli bezpośrednio kliniejsz odnośnik - Wyloguj się w sekcji Inne czynności administracyjne. -

    +

    Mechanizm ciasteczek wykorzystywany jest w Mailmanie, żeby + nie trzeba było podawać hasła przy każdej czynności administracyjnej. + Identyfikujące Cię ciasteczko automatycznie wygaśnie, gdy wyłączysz + przeglądarkę, lub jeśli bezpośrednio kliniejsz odnośnik + Wyloguj się w sekcji Inne czynności administracyjne. +

    diff --git a/templates/pl/archidxentry.html b/templates/pl/archidxentry.html index f9bb57aa..5c620b20 100644 --- a/templates/pl/archidxentry.html +++ b/templates/pl/archidxentry.html @@ -1,4 +1,68 @@ -
  • %(subject)s -  -%(author)s - +
  • %(subject)s +  +%(author)s + +
  • \ No newline at end of file diff --git a/templates/pl/archidxfoot.html b/templates/pl/archidxfoot.html index cb852633..2ecd8ef7 100644 --- a/templates/pl/archidxfoot.html +++ b/templates/pl/archidxfoot.html @@ -1,16 +1,80 @@ - -

    - Data ostatniej wiadomości: %(lastdate)s
    - Data archiwizacji: %(archivedate)s -

    -

      -
    • Wiadomości posortowane według: + +

      +Data ostatniej wiadomości: %(lastdate)s
      +Data archiwizacji: %(archivedate)s +

      +

      -


      Archiwum zostało wygenerowane przez program Pipermail %(version)s. - - +
    • WiÄ™cej informacji o liÅ›cie...
    • +
    +


    Archiwum zostało wygenerowane przez program Pipermail %(version)s. + + +

    \ No newline at end of file diff --git a/templates/pl/archidxhead.html b/templates/pl/archidxhead.html index 44d9ae3c..55a900cd 100644 --- a/templates/pl/archidxhead.html +++ b/templates/pl/archidxhead.html @@ -1,22 +1,87 @@ + - - Lista %(listname)s %(archive)s sortowane według %(archtype)s - + +Lista %(listname)s %(archive)s sortowane wedÅ‚ug %(archtype)s + %(encoding)s - - - -

    Archiwum za %(archive)s posortowane według %(archtype)s

    -
      -
    • Wiadomości posortowane według: + + + +

      Archiwum za %(archive)s posortowane według %(archtype)s

      + -

      Początek: %(firstdate)s
      - Koniec: %(lastdate)s
      - Liczba wiadomości: %(size)s

      -

      +

      PoczÄ…tek: %(firstdate)s
      +Koniec: %(lastdate)s
      +Liczba wiadomości: %(size)s

      +

        +

      \ No newline at end of file diff --git a/templates/pl/archlistend.html b/templates/pl/archlistend.html index 9bc052dd..2e1191b0 100644 --- a/templates/pl/archlistend.html +++ b/templates/pl/archlistend.html @@ -1 +1,64 @@ -
    + diff --git a/templates/pl/archliststart.html b/templates/pl/archliststart.html index 7e9b7f7d..65f3b1de 100644 --- a/templates/pl/archliststart.html +++ b/templates/pl/archliststart.html @@ -1,4 +1,68 @@ - - - - +
    ArchiwumPosortowane według:Wersja do pobrania
    + + + +
    ArchiwumPosortowane według:Wersja do pobrania
    \ No newline at end of file diff --git a/templates/pl/archtoc.html b/templates/pl/archtoc.html index 4d691c2b..0d763cbe 100644 --- a/templates/pl/archtoc.html +++ b/templates/pl/archtoc.html @@ -1,13 +1,77 @@ + - - Archiwum listy %(listname)s - + +Archiwum listy %(listname)s + %(meta)s - - -

    Archiwum listy %(listname)s

    -

    Odwiedź stronę informacyjną tej listy, albo pobierz całe archiwum (%(size)s).

    + + +

    Archiwum listy %(listname)s

    +

    Odwiedź stronę informacyjną tej listy, albo pobierz całe archiwum (%(size)s).

    %(noarchive_msg)s %(archive_listing_start)s %(archive_listing)s diff --git a/templates/pl/archtocentry.html b/templates/pl/archtocentry.html index 2cef35e7..87199785 100644 --- a/templates/pl/archtocentry.html +++ b/templates/pl/archtocentry.html @@ -1,9 +1,72 @@ -
    %(archivelabel)s:[ Wątku ] - [ Tematu ] - [ Autora ] - [ Daty ] -
    %(archivelabel)s:[ WÄ…tku ] +[ Tematu ] +[ Autora ] +[ Daty ] +
    - - - - - - - - - - - - + + + + + + + +
    - -- - -
    -

      -

    - Lista - - - -
    -

    -

    Zobacz archiwum listy - . + + + + +Strona informacyjna listy <mm-list-name></mm-list-name> + + +

    + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - -
    + -- + +
    +

      +

    +Lista + + + +
    +

    +

    Zobacz archiwum listy +. - -

    -
    - Korzystanie z listy -
    - Wiadomości do wszystkich prenumeratorów listy wysyłaj na adres: - . - -

    Możesz zapisać się na listę lub zmienić opcje prenumeraty korzystając z poniższych sekcji. -

    - Subskrypcja listy -
    -

    - Wypełnij poniższy formularz, by zapisać się na listę . - - -

      - - - - - - - - - - - + +

      + + + + + + + + + + + + + - - - - - - - - - - - - - - - + +
        + +
      Adres email: -  
      Imię i nazwisko (opcjonalne): 
      +Korzystanie z listy +
      + WiadomoÅ›ci do wszystkich prenumeratorów listy wysyÅ‚aj na adres: + . - Wprowadź hasło. - Nie używaj hasła, które wykorzystujesz np. do logowania do konta bankowego. +

      Możesz zapisać się na listę lub zmienić opcje prenumeraty korzystając z poniższych sekcji. +

      +Subskrypcja listy +
      +

      + Wypełnij poniższy formularz, by zapisać się na listę . -

      Jeżeli nie podasz hasła, system przydzieli Ci - je automatycznie. Zostanie ono przesłane razem z wiadomością - potwierdzającą Twoją subskrypcję. Hasło możesz w każdej chwili zmienić. - - -

      Hasło: 
      Potwierdź hasło: 
      W jakim języku chcesz komunikować się z - systemem?  
      + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - -
      Adres email: + 
      Imię i nazwisko (opcjonalne): 
      Wprowadź hasÅ‚o. + Nie używaj hasÅ‚a, które wykorzystujesz np. do logowania do konta bankowego. - Czy chcesz dostawać listy tylko raz dziennie, +

      Jeżeli nie podasz hasła, system przydzieli Ci + je automatycznie. Zostanie ono przesłane razem z wiadomością + potwierdzającą Twoją subskrypcję. Hasło możesz w każdej chwili zmienić. + + +
      Hasło: 
      Potwierdź hasło: 
      W jakim języku chcesz komunikować się z + systemem?  
      Czy chcesz dostawać listy tylko raz dziennie, w formie paczki? Nie - Tak -
      -
      -
      - -
    -
    - - Sekcja dla prenumeratorów listy -
    - - - -

    - - - -

    - - - +

    Nie + Tak +
    +
    +
    + + +
    + +Sekcja dla prenumeratorów listy +
    + + + +

    + + + +

    + +

    + diff --git a/templates/pl/options.html b/templates/pl/options.html index b90c581d..8da5ad5c 100644 --- a/templates/pl/options.html +++ b/templates/pl/options.html @@ -1,319 +1,352 @@ - - Konfiguracja <MM-Presentable-User> na liście <MM-List-Name> - - - - - - -
    - - Konfiguracja subskrybcji na liście - -
    + +Konfiguracja <mm-presentable-user> na liście <mm-list-name> +</mm-list-name></mm-presentable-user> + + + + + +
    + + Konfiguracja subskrybcji na liście + +

    - - - - - +
    - -- aktualny stan subskrypcji, - hasło i zmiana opcji na liście . -
    - - - - -

    -

    + + + +
    + -- aktualny stan subskrypcji, + hasło i zmiana opcji na liście . +
    + + +

    +

    - - +

    - - - + +
    - - Zmiana ustawień -
    - Możesz zmienić adres używany do prenumeraty listy - wypełniając po prostu poniższe pola. Na nowy adres zostanie wysłana - wiadomość, która będzie jeszcze wymagała Twojego potwierdzenia. - -

    Potwierdzenie należy odesłać w ciągu . - -

    Możesz również dodać lub zmienić swoje Imię i Nazwisko + + + - - - - - -
    + +Zmiana ustawień +
    + Możesz zmienić adres używany do prenumeraty listy + wypełniając po prostu poniższe pola. Na nowy adres zostanie wysłana + wiadomość, która będzie jeszcze wymagała Twojego potwierdzenia. + +

    Potwierdzenie należy odesłać w ciągu . + +

    Możesz również dodać lub zmienić swoje Imię i Nazwisko (np.. Jan Kowalski). -

    Zaznaczenie opcji Zmiana globalna spowoduje, że zmiany - te będą dotyczyć wszystkich list na serwerze , na które jesteś zapisany. - -

    - - - - - - - -
    Nowy adres:
    Podaj ponownie:
    -
    - - +
    Imię i Nazwisko +

    Zaznaczenie opcji Zmiana globalna spowoduje, że zmiany + te będą dotyczyć wszystkich list na serwerze , na które jesteś zapisany. + +

    + + + + + + + +
    Nowy adres:
    Podaj ponownie:
    +
    + + - - -
    ImiÄ™ i Nazwisko (opcjonalnie):
    -
    -

    Zmiana globalna

    - +

    + +

    +

    Zmiana globalna

    +

    - - - - - - - - + + + + %(textlink)s - diff --git a/templates/pt/article.html b/templates/pt/article.html index 50a01808..c785ff7f 100644 --- a/templates/pt/article.html +++ b/templates/pt/article.html @@ -1,12 +1,13 @@ - - - %(title)s - - - - - + +

    Arquivos de %(listname)s

    +

    + Ainda não foram enviadas mensagens para esta lista, por isso + os arquivos estão vazios. Pode ter mais + informações sobre esta lista.

    - - + + diff --git a/templates/pt/headfoot.html b/templates/pt/headfoot.html index 0e650a57..aaaa708b 100644 --- a/templates/pt/headfoot.html +++ b/templates/pt/headfoot.html @@ -1,28 +1,91 @@ -Este texto pode incluir +Este texto pode incluir strings -de formatação em Python as quais são resolvidas em relação à lista de +de formatação em Python as quais são resolvidas em relação à lista de atributos ??????? atributos da lista. -As substituições permitidas são: +As substituições permitidas são:
      -
    • real_name - O nome `bonito' da lista; normalmente - é o nome da lista com alguma capitalização. +
    • real_name - O nome `bonito' da lista; normalmente + é o nome da lista com alguma capitalização. -
    • list_name - O nome pelo qual a lista é - identificada em URLs, as quais são sensíveis à capitalização. +
    • list_name - O nome pelo qual a lista é + identificada em URLs, as quais são sensíveis à capitalização.
    • host_name - O nome completo do servidor onde corre o servidor da lista.
    • web_page_url - A URL base do Mailman. A ela pode-se acrescentar, por exemplo, - listinfo/%(list_name)s para obter a página de - informações da lista. + listinfo/%(list_name)s para obter a página de + informações da lista. -
    • description - Uma breve descrição da lista - de discussão. +
    • description - Uma breve descrição da lista + de discussão. -
    • info - A descrilção completa. +
    • info - A descrilção completa. -
    • cgiext - A extensão utilizada em scripts CGI. -
    +
  • cgiext - A extensão utilizada em scripts CGI. +
  • diff --git a/templates/pt/listinfo.html b/templates/pt/listinfo.html index 400d38e7..3d099d6e 100644 --- a/templates/pt/listinfo.html +++ b/templates/pt/listinfo.html @@ -1,147 +1,208 @@ - - - - <MM-List-Name>: Página de informações - - - -

    -

    - Wypisanie się z listy - Pozostałe listy, które subskrybujesz na serwerze -
    - Zaznacz opcje potwierdzenia i naciśnij przycisk, aby wypisać się + + + + - + +
    +

    +Wypisanie się z listy +Pozostałe listy, które subskrybujesz na serwerze +
    + Zaznacz opcje potwierdzenia i naciśnij przycisk, aby wypisać się z listy. Uwaga: wypisanie jest realizowane natychmiast.

    -

    - Możesz zobaczyć listę wszystkich list na serwerze - , na które jesteś zapisany. - Zaloguj się, jeśli chcesz zmienić coś w opcjach dotyczących innej +

    + Możesz zobaczyć listę wszystkich list na serwerze + , na które jesteś zapisany. + Zaloguj się, jeśli chcesz zmienić coś w opcjach dotyczących innej listy.

    -

    -
    - - - - - +
    - Twoje hasło na liście -
    - -
    -

    Zapomniałeś hasła?

    -
    - Kliknij poniższy przycisk, a hasło zostanie wysłane na Twój adres + + + - -
    +Twoje hasło na liście +
    + +
    +

    Zapomniałeś hasła?

    +
    + Kliknij poniższy przycisk, a hasło zostanie wysłane na Twój adres email. -

    -

    - -
    -
    - -
    -

    Zmiana hasła

    - - - - - - - - -
    Nowe - hasło:
    Podaj ponownie: -
    - - -

    Zmiana globalna -
    -
    - +

    +

    + +
    +

    + +
    +

    Zmiana hasła

    + + + + + + + + +
    Nowe + hasło:
    Podaj ponownie: +
    + +

    Zmiana globalna +
    +

    - - +
    - Opcje subskrypcji listy -
    +
    +Opcje subskrypcji listy +
    -

    -Wartości zaznaczone to aktualne ustawienia prenumeraty. - -

    Zauważ, że niektóre opcje mają znacznik Zmiana globalna. -Jego zaznaczenie spowoduje, że zmiana danej opcji odnosić się będzie do -wszystkich list na serwerze , które prenumerujesz. -Kliknij przycisk Pokaż moje pozostałe subskrypcje, jeśli -nie jesteś pewien na jakie listy jesteś zapisany. +WartoÅ›ci zaznaczone to aktualne ustawienia prenumeraty. +

    Zauważ, że niektóre opcje mają znacznik Zmiana globalna. +Jego zaznaczenie spowoduje, że zmiana danej opcji odnosić się będzie do +wszystkich list na serwerze , które prenumerujesz. +Kliknij przycisk Pokaż moje pozostałe subskrypcje, jeśli +nie jesteś pewien na jakie listy jesteś zapisany.

    - - - - - - - - - -
    - - Dostarczanie wiadomości

    - Ustaw tę opcję na Włączone, żeby dostawać wszystkie - wiadomości z listy. Ustawienie Wyłączone, spowoduje, - że nadal będziesz zapisany na listę, ale nie będziesz dostawał wiadomości (przydatne jeśli np. wyjeżdżasz na wakacje). Jeśli - wyłączyłeś dostarczanie, nie zapomnij włączyć je po powrocie; nie - włączy się ono automatycznie. -

    - Włączone
    - Wyłączone

    - Zmiana globalna -

    - Tryb Paczek

    - - Jeśli tryb paczek jest wyłączony, wiadomości dostarczane są - natychmiast. Po jego włączeniu, wiadomości z listy będziesz dostawał - tylko raz dziennie w formie paczki (bądź częściej, jeśli ruch - na liście jest bardzo duży). Zmiana tej opcji może spowodować, że - nie dostaniesz ostatniej wysłanej paczki (tylko kolejne). -

    - Wyłączony
    - Włączony -
    - Paczki w formie MIME czy Czystego Tekstu?

    - Określ, w jakiej formie mają być dostarczane wiadomości w trybie - paczek. Najczęstszym wyborem jest MIME. - Stare programy pocztowe mogą nie obsługiwać załączników MIME, wtedy - wybierz opcję na Czysty Tekst. -

    - MIME
    - Czysty Tekst

    - Zmiana globalna -

    - Czy chcesz otrzymywać wysłane przez siebie wiadomości?

    - Możesz dostawać kopię każdej wiadomości, którą wysyłasz na listę. - Jeśli tego nie chcesz, ustaw tą opcję na Nie. -

    - Nie
    - Tak -
    - Czy chcesz dostawać potwierdzenie, że wysłany list doszedł - na listę? + + + + + + - - + - - - - - - + + + - - + - - + - - - +

    +
    + +Dostarczanie wiadomości

    + Ustaw tę opcję na Włączone, żeby dostawać wszystkie + wiadomości z listy. Ustawienie Wyłączone, spowoduje, + że nadal będziesz zapisany na listę, ale nie będziesz dostawał wiadomości (przydatne jeśli np. wyjeżdżasz na wakacje). Jeśli + wyłączyłeś dostarczanie, nie zapomnij włączyć je po powrocie; nie + włączy się ono automatycznie. +

    +Włączone
    +Wyłączone

    +Zmiana globalna +

    +Tryb Paczek

    + + Jeśli tryb paczek jest wyłączony, wiadomości dostarczane są + natychmiast. Po jego włączeniu, wiadomości z listy będziesz dostawał + tylko raz dziennie w formie paczki (bądź częściej, jeśli ruch + na liście jest bardzo duży). Zmiana tej opcji może spowodować, że + nie dostaniesz ostatniej wysłanej paczki (tylko kolejne). +

    +Wyłączony
    +Włączony +
    +Paczki w formie MIME czy Czystego Tekstu?

    + Określ, w jakiej formie mają być dostarczane wiadomości w trybie + paczek. Najczęstszym wyborem jest MIME. + Stare programy pocztowe mogą nie obsługiwać załączników MIME, wtedy + wybierz opcję na Czysty Tekst. +

    +MIME
    +Czysty Tekst

    +Zmiana globalna +

    +Czy chcesz otrzymywać wysłane przez siebie wiadomości?

    + Możesz dostawać kopię każdej wiadomości, którą wysyłasz na listę. + Jeśli tego nie chcesz, ustaw tą opcję na Nie. +

    +Nie
    +Tak +
    +Czy chcesz dostawać potwierdzenie, że wysłany list doszedł + na listę?

    -

    - Nie
    - Tak -
    - Czy chcesz dostawać przypomnienia hasła?

    - Raz w miesiącu rozsyłane są przypomnienia do wszystkich - subskrybentów. Przypomnienie zawiera hasła i podstawowe informacje o - wszystkich listach, na które jesteś zapisany na tym serwerze. - Możesz wyłączyć - przypomnienie hasła dla każdej z list osobno, zaznaczając opcje +

    +Nie
    +Tak +
    +Czy chcesz dostawać przypomnienia hasła?

    + Raz w miesiÄ…cu rozsyÅ‚ane sÄ… przypomnienia do wszystkich + subskrybentów. Przypomnienie zawiera hasÅ‚a i podstawowe informacje o + wszystkich listach, na które jesteÅ› zapisany na tym serwerze. + Możesz wyłączyć + przypomnienie hasÅ‚a dla każdej z list osobno, zaznaczajÄ…c opcje Nie. Zrezygnowanie z przypomnienia dla wszystkich list - spowoduje, że w ogóle nie dostaniesz wiadomości z przypomnieniem. -

    - Nie
    - Tak

    - Zmiana globalna -

    - Czy chcesz być niewidocznym członkiem listy?

    - Normalnie Twój adres jest widoczny dla oglądających listę - sybskrybentów (adres jest dodatkowo zniekształcony, żeby utrudnić - życie spamerom). Jeśli nie chcesz, żeby Twój adres pojawiał się na - liście osób zapisanych, zaznacz Tak. -

    - Nie
    - Tak -
    - W jakim języku chcesz się komunikować?

    -

    - -
    - Jakie kategorie tematyczne Cię interesują? + spowoduje, że w ogóle nie dostaniesz wiadomoÅ›ci z przypomnieniem. +

    +Nie
    +Tak

    +Zmiana globalna +

    +Czy chcesz być niewidocznym członkiem listy?

    + Normalnie Twój adres jest widoczny dla oglądających listę + sybskrybentów (adres jest dodatkowo zniekształcony, żeby utrudnić + życie spamerom). Jeśli nie chcesz, żeby Twój adres pojawiał się na + liście osób zapisanych, zaznacz Tak. +

    +Nie
    +Tak +
    +W jakim języku chcesz się komunikować?

    +

    + +
    +Jakie kategorie tematyczne CiÄ™ interesujÄ…?

    - Zaznaczając jeden lub więcej tematów, możesz filtrować - wiadomości z listy, a więc otrzymywać tylko część - ruchu. Będziesz dostawać tylko wiadomości, które pasują - do któregoś z tematów. + ZaznaczajÄ…c jeden lub wiÄ™cej tematów, możesz filtrować + wiadomoÅ›ci z listy, a wiÄ™c otrzymywać tylko część + ruchu. BÄ™dziesz dostawać tylko wiadomoÅ›ci, które pasujÄ… + do któregoÅ› z tematów. -

    Jeśli wiadomość nie pasuje do żadnego z tematów, to - czy ją dostaniesz zależy od następnej opcji. Jeżeli nie - zaznaczysz żadnego tematu, będziesz otrzymywać wszystkie - wiadomości wysyłane na listę dyskusyjną. -

    - -
    - Czy chcesz dostawać wiadomości, które nie pasują - do żadnej kategorii tematycznej? +

    Jeśli wiadomość nie pasuje do żadnego z tematów, to + czy ją dostaniesz zależy od następnej opcji. Jeżeli nie + zaznaczysz żadnego tematu, będziesz otrzymywać wszystkie + wiadomości wysyłane na listę dyskusyjną. +

    + +
    +Czy chcesz dostawać wiadomości, które nie pasują + do żadnej kategorii tematycznej?

    - Ta opcja ma znaczenie tylko, jeśli wybrałeś co najmniej - jedną kategorię tematyczną z poprzedniej opcji. Opisuje - na zachowanie w przypadku, gdy wiadomość nie pasuje - do żadnego ze zdefiniowanych tematów. Wybranie - Nie oznacza, że nie będziesz dostawać wiadomości - nie pasujących do żadnego tematu, a Tak, że - będziesz dostawać te pozostałe wiadomości. - -

    Nie wybranie żadnego z tematów z poprzedniej opcji - oznacza, że będziesz otrzymywać wszystkie wiadomości. -

    - Nie
    - Tak -
    - Unikać podwójnych kopii wiadomości?

    - - Po zaznaczeniu tej opcji Mailman będzie analizował + Ta opcja ma znaczenie tylko, jeÅ›li wybraÅ‚eÅ› co najmniej + jednÄ… kategoriÄ™ tematycznÄ… z poprzedniej opcji. Opisuje + na zachowanie w przypadku, gdy wiadomość nie pasuje + do żadnego ze zdefiniowanych tematów. Wybranie + Nie oznacza, że nie bÄ™dziesz dostawać wiadomoÅ›ci + nie pasujÄ…cych do żadnego tematu, a Tak, że + bÄ™dziesz dostawać te pozostaÅ‚e wiadomoÅ›ci. + +

    Nie wybranie żadnego z tematów z poprzedniej opcji + oznacza, że będziesz otrzymywać wszystkie wiadomości. +

    +Nie
    +Tak +
    +Unikać podwójnych kopii wiadomości?

    + + Po zaznaczeniu tej opcji Mailman bÄ™dzie analizowaÅ‚ pola To: i Cc: i na tej podstawie nie - będzie wysyłał do Ciebie kopii, jeśli jesteś już jej + bÄ™dzie wysyÅ‚aÅ‚ do Ciebie kopii, jeÅ›li jesteÅ› już jej adresatem. - Odpowiedź Tak oznacza, że chcesz unikać - dostawania tego samego listu w dwóch kopiach. - Przy Nie możesz dostawać wielokrotne kopie. + Odpowiedź Tak oznacza, że chcesz unikać + dostawania tego samego listu w dwóch kopiach. + Przy Nie możesz dostawać wielokrotne kopie. -

    Jeżeli lista ma włączoną personalizację - wiadomości, a ty wybierzesz dostawanie kopii, Mailman - doda pole X-Mailman-Copy: yes do nagłówka każdej +

    Jeżeli lista ma włączoną personalizację + wiadomości, a ty wybierzesz dostawanie kopii, Mailman + doda pole X-Mailman-Copy: yes do nagłówka każdej kopii. -

    - Nie
    - Tak

    - Zmiana globalna -

    -
    -
    +Nie
    +Tak

    +Zmiana globalna +

    +
    +
    -

    - - - - + + +

    diff --git a/templates/pl/private.html b/templates/pl/private.html index bfd1b674..00c95cdb 100644 --- a/templates/pl/private.html +++ b/templates/pl/private.html @@ -1,52 +1,115 @@ - - Prywatne archiwum listy %(realname)s - uwierzytelnianie - - - - - -
    + +Prywatne archiwum listy %(realname)s - uwierzytelnianie + + + + + + %(message)s - - - - - - - - - - - - - - - -
    - Prywatne archiwum listy %(realname)s - uwierzytelnianie -
    Adres e-mail:
    Hasło:
    -

    Ważne: - Od tego momentu, ciasteczka w przeglądarce muszą być włączone. - W przeciwnym wypadku żadne zmiany administracyjne nie zostaną zastosowane. + + + + + + + + + + + + + + + +
    +Prywatne archiwum listy %(realname)s - uwierzytelnianie +
    Adres e-mail:
    Hasło:
    +

    Ważne: + Od tego momentu, ciasteczka w przeglądarce muszą być włączone. + W przeciwnym wypadku żadne zmiany administracyjne nie zostaną zastosowane. -

    Ciasteczka sesji są używane przez interfejs administracyjny Mailmana, - aby nie było potrzeby osobnego uwierzytelniania każdej operacji administracyjnej. - Ciasteczko automatycznie wygaśnie po zamknięciu (sesji) przeglądarki. - Można je również usunąć korzystając z odnośnika Wyloguj w sekcji Inne czynności administracyjne - (która zostanie wyświetlona po udanym zalogowaniu). +

    Ciasteczka sesji są używane przez interfejs administracyjny Mailmana, + aby nie było potrzeby osobnego uwierzytelniania każdej operacji administracyjnej. + Ciasteczko automatycznie wygaśnie po zamknięciu (sesji) przeglądarki. + Można je również usunąć korzystając z odnośnika Wyloguj w sekcji Inne czynności administracyjne + (która zostanie wyświetlona po udanym zalogowaniu).

    - - - - - - - - - - -
    Przypomnienie hasła
    Jeśli nie pamiętasz hasła, wprowadź powyżej adres e-mail i kliknij przycisk Przypomnij a hasło zostanie wysłane.
    -

    - + + + + + + + + + + +
    Przypomnienie hasła
    Jeśli nie pamiętasz hasła, wprowadź powyżej adres e-mail i kliknij przycisk Przypomnij a hasło zostanie wysłane.
    +

    + diff --git a/templates/pl/roster.html b/templates/pl/roster.html index 9286be99..e202b4ae 100644 --- a/templates/pl/roster.html +++ b/templates/pl/roster.html @@ -1,53 +1,113 @@ - - - Subskrybenci listy <MM-List-Name> - - - - -

    - - - - - - + + + + + + + + + +
    - - Subskrybenci listy -
    - -

    -

    - -

    Kliknij na swój adres, żeby wejść na stronę z opcjami + + +Subskrybenci listy <mm-list-name></mm-list-name> + + + +

    + + + + + + - - - - - - - - - -
    + + Subskrybenci listy +
    +

    +

    +

    Kliknij na swój adres, żeby wejść na stronę z opcjami subskrypcji. -
    (Na adresy pisane kursywą nie są wysyłane listy)

    -
    -
    - - Zwykli prenumeratorzy listy : -
    -
    -
    - - Prenumeratorzy otrzymujący wiadomość w paczkach : -
    -
    -

    -

    -

    -

    - - - +
    (Na adresy pisane kursywą nie są wysyłane listy)

    +
    +
    + + Zwykli prenumeratorzy listy : +
    +
    +
    + + Prenumeratorzy otrzymujący wiadomość w paczkach : +
    +
    +

    +

    +

    +

    + +

    + diff --git a/templates/pl/subscribe.html b/templates/pl/subscribe.html index cb0ce672..f64ae8d0 100644 --- a/templates/pl/subscribe.html +++ b/templates/pl/subscribe.html @@ -1,11 +1,74 @@ -Subskrypcja listy <MM-List-Name> - - +Subskrypcja listy <mm-list-name></mm-list-name> + + -

    Subskrypcja listy

    - - - +

    Subskrypcja listy

    + + + diff --git a/templates/pt/admindbdetails.html b/templates/pt/admindbdetails.html index d1f3393d..71a8e5d5 100644 --- a/templates/pt/admindbdetails.html +++ b/templates/pt/admindbdetails.html @@ -1,72 +1,134 @@ -Os pedidos administrativos são mostrados usando uma de duas formas, -numa página de sumário ou numa página -detalhadas. A página de sumário contém as inscrições -pendentes e pedidos de anulação de inscrição, bem como mensagens que -requerem a sua aprovação, agrupada por endereço de email do -remetente. A página detalhada contém uma visualização mais detalhada -de cada mensagem em espera, incluindo todos os cabeçalhos da mensagem -com excepção do corpo da mensagem. +Os pedidos administrativos são mostrados usando uma de duas formas, +numa página de sumário ou numa página +detalhadas. A página de sumário contém as inscrições +pendentes e pedidos de anulação de inscrição, bem como mensagens que +requerem a sua aprovação, agrupada por endereço de email do +remetente. A página detalhada contém uma visualização mais detalhada +de cada mensagem em espera, incluindo todos os cabeçalhos da mensagem +com excepção do corpo da mensagem. -

    Em todas as páginas, as seguintes acções estão disponíveis: +

    Em todas as páginas, as seguintes acções estão disponíveis:

      -
    • Adiar -- Adia sua decisão até mais tarde. Nenhuma acção é +
    • Adiar -- Adia sua decisão até mais tarde. Nenhuma acção é tomada agora para este pedido administrativo, mas para mensagens - aguardando aprovação, pode ainda encaminhar ou preservar a + aguardando aprovação, pode ainda encaminhar ou preservar a mensagem (ver abaixo).
    • Aprovar -- Aprova a mensagem, enviando-a para a lista. - Para pedidos de membros, aprova a mudança no estatuto do + Para pedidos de membros, aprova a mudança no estatuto do membro.
    • Rejeitar -- Rejeita a mensagem, enviando um aviso de - rejeição ao remetente e ignorando a mensagem original. - Para pedidos de membros, rejeita a modificação no seu estatuto. - Em ambos os casos, deve incluir uma razão para esta recusa na + rejeição ao remetente e ignorando a mensagem original. + Para pedidos de membros, rejeita a modificação no seu estatuto. + Em ambos os casos, deve incluir uma razão para esta recusa na caixa de texto.
    • Ignorar -- Ignora a mensagem original, sem enviar uma - notificação de rejeição. Para pedidos de membros, isto + notificação de rejeição. Para pedidos de membros, isto simplesmente ignora o pedido sem avisar a pessoa que - enviou a mensagem. Esta é, normalemnte, a acção que deseja tomar + enviou a mensagem. Esta é, normalemnte, a acção que deseja tomar para trabalhar com spamerss conhecidos. -
    - -

    Para mensagens aguardando aprovação, ligue a opção Preserve -se deseja guardar uma cópia da mensagem para o administrador do site. -Isto é útil para mensagens abusivas que deseja que sejam ignoradas, -mas das quais deseja manter um registo para inspecção posterior. -

    Ligue a opção Forward to e preencha o endereço de -redirecionamento se desejar encaminhar a mensagem para alguém -que não esteja na lista. Para editar uma mensagem em espera antes + +

    Para mensagens aguardando aprovação, ligue a opção Preserve +se deseja guardar uma cópia da mensagem para o administrador do site. +Isto é útil para mensagens abusivas que deseja que sejam ignoradas, +mas das quais deseja manter um registo para inspecção posterior. +

    Ligue a opção Forward to e preencha o endereço de +redirecionamento se desejar encaminhar a mensagem para alguém +que não esteja na lista. Para editar uma mensagem em espera antes de ser enviada para a lista, deve redirecionar a mensagem -para você mesmo (ou para os donos da lista) e ignorar a mensagem +para você mesmo (ou para os donos da lista) e ignorar a mensagem original. Quando ler a mensagem na sua caixa -de correio, faça suas modificações e reenvie a mensagem para a -lista, incluindo um cabeçalho Approved: com a password -da lista como seu valor. Uma netiqueta apropriada para este caso é +de correio, faça suas modificações e reenvie a mensagem para a +lista, incluindo um cabeçalho Approved: com a password +da lista como seu valor. Uma netiqueta apropriada para este caso é incluir uma nota na mensagem reenviada, explicando que modificou o texto. -

    Caso o remetente seja membro da lista que está sendo moderado, -pode, opcionalmente, desactivar a sua opção de moderação. Isto é -útil quando sua lista estiver configurada para colocar novos -membros em proibição, e decidiu que pode confiar neste membro -para ele enviar mensagens sem aprovação. +

    Caso o remetente seja membro da lista que está sendo moderado, +pode, opcionalmente, desactivar a sua opção de moderação. Isto é +útil quando sua lista estiver configurada para colocar novos +membros em proibição, e decidiu que pode confiar neste membro +para ele enviar mensagens sem aprovação. -

    Caso o remetente não seja o membro da lista, pode -adicionar o endereço de email ao sender filter. Os - Sender filters são descritos na página de privacidade de filtragem de remetentes, -e pode corresponder a uma das seguintes opções auto-aceitar (Aceita), +

    Caso o remetente não seja o membro da lista, pode +adicionar o endereço de email ao sender filter. Os + Sender filters são descritos na página de privacidade de filtragem de remetentes, +e pode corresponder a uma das seguintes opções auto-aceitar (Aceita), auto-bloqueia (Bloqueia), auto-rejeita (Rejeita), ou -auto-descarta (Descarta). Esta opção não estará disponível -se o endereço já é um dos filtros do remetente. +auto-descarta (Descarta). Esta opção não estará disponível +se o endereço já é um dos filtros do remetente. -

    Quando terminar, clique no botão Enviar Todos os Dados -no topo ou rodapé da página. Este botão enviará todas as acções +

    Quando terminar, clique no botão Enviar Todos os Dados +no topo ou rodapé da página. Este botão enviará todas as acções selecionadas para todas os pedidos administrativos que tratou. -

    Voltar à página de sumário. +

    Voltar à página de sumário. +

    \ No newline at end of file diff --git a/templates/pt/admindbpreamble.html b/templates/pt/admindbpreamble.html index f21fc710..a24c4ac8 100644 --- a/templates/pt/admindbpreamble.html +++ b/templates/pt/admindbpreamble.html @@ -1,11 +1,75 @@ -Esta página contém uma parte das mensagens enviadas para -%(listname)s que estão pendentes da sua aprovação. De momento +Esta página contém uma parte das mensagens enviadas para +%(listname)s que estão pendentes da sua aprovação. De momento mostra %(description)s -

    Seleccione a acção a tomar em relação a cada pedido administrativo -e carregue em Submeter toda a informação quando tiver terminado. -Instruções mais detalhadas estão disponíveis aqui. +

    Seleccione a acção a tomar em relação a cada pedido administrativo +e carregue em Submeter toda a informação quando tiver terminado. +Instruções mais detalhadas estão disponíveis aqui. -

    Também pode ver um sumário de todos os +

    Também pode ver um sumário de todos os pedidos pendentes. +

    \ No newline at end of file diff --git a/templates/pt/admindbsummary.html b/templates/pt/admindbsummary.html index 1b184845..99eae4d8 100644 --- a/templates/pt/admindbsummary.html +++ b/templates/pt/admindbsummary.html @@ -1,13 +1,77 @@ -Esta página contém um sumário dos pedidos administrativos da lista +Esta página contém um sumário dos pedidos administrativos da lista lista %(listname)s que -requerem a sua autorização. Em primeiro lugar encontra a lista de pedidos -de inscrição ou de anulação pendentes, se exitirem, seguida das eventuais -mensagens suspensas para sua aprovação. +requerem a sua autorização. Em primeiro lugar encontra a lista de pedidos +de inscrição ou de anulação pendentes, se exitirem, seguida das eventuais +mensagens suspensas para sua aprovação. -

    Seleccione a acção a tomar em relação a cada pedido administrativo -e carregue em Submeter toda a informação quando tiver terminado. -Instruções mais detalhadas estão também -disponíveis aqui. +

    Seleccione a acção a tomar em relação a cada pedido administrativo +e carregue em Submeter toda a informação quando tiver terminado. +Instruções mais detalhadas estão também +disponíveis aqui.

    Se preferir pode ver os detalhes de todas as mensagens suspensas. +

    \ No newline at end of file diff --git a/templates/pt/admlogin.html b/templates/pt/admlogin.html index f91ed148..ae19ac49 100755 --- a/templates/pt/admlogin.html +++ b/templates/pt/admlogin.html @@ -1,39 +1,100 @@ - %(listname)s %(who)s Authentication +%(listname)s %(who)s Authentication - - -
    + + + %(message)s - - - - - - - - - - - -
    - %(listname)s %(who)s - Authentication -
    List %(who)s Password:
    -
    -

    Importante: A partir de agora o seu - browser tem de estar configurado para permitir cookies, se não - o fizer as alterações administrativas não terão efeito. + + + + + + + + + + + +
    +%(listname)s %(who)s + Authentication +
    List %(who)s Password:
    +
    +

    Importante: A partir de agora o seu + browser tem de estar configurado para permitir cookies, se não + o fizer as alterações administrativas não terão efeito. -

    São utilizadas Cookies, a nível da sessão, na interface - administrativa do Mailman, de forma a não ter que reautenticar - cada operação. A cookie expira automaticamente quando termina o - browser, ou pode expirá-la explicitamente carregando no link +

    São utilizadas Cookies, a nível da sessão, na interface + administrativa do Mailman, de forma a não ter que reautenticar + cada operação. A cookie expira automaticamente quando termina o + browser, ou pode expirá-la explicitamente carregando no link Sair em Outras actividades administrativas, o - qual poderá ver depois de autenticar-se. -

    + qual poderá ver depois de autenticar-se. +

    diff --git a/templates/pt/archidxentry.html b/templates/pt/archidxentry.html index f9bb57aa..5c620b20 100644 --- a/templates/pt/archidxentry.html +++ b/templates/pt/archidxentry.html @@ -1,4 +1,68 @@ -
  • %(subject)s -  -%(author)s - +
  • %(subject)s +  +%(author)s + +
  • \ No newline at end of file diff --git a/templates/pt/archidxfoot.html b/templates/pt/archidxfoot.html index 1d48345c..df3c4e0e 100644 --- a/templates/pt/archidxfoot.html +++ b/templates/pt/archidxfoot.html @@ -1,21 +1,85 @@ - -

    - Data da última mensagem: - %(lastdate)s
    - Arquivada em: %(archivedate)s -

    -

      -
    • Mensagens ordenadas por: + +

      +Data da última mensagem: +%(lastdate)s
      +Arquivada em: %(archivedate)s +

      +

      -

      -


      - Este arquivo foi criado pelo +
    +

    +


    +Este arquivo foi criado pelo Pipermail %(version)s. - - + + +

    \ No newline at end of file diff --git a/templates/pt/archidxhead.html b/templates/pt/archidxhead.html index 70f1e48b..9b2d10a6 100644 --- a/templates/pt/archidxhead.html +++ b/templates/pt/archidxhead.html @@ -1,24 +1,89 @@ - - - Arquivo %(archive)s de %(listname)s por %(archtype)s - + + + +Arquivo %(archive)s de %(listname)s por %(archtype)s + %(encoding)s - - - -

    %(archive)s Arquivos por %(archtype)s

    -
      -
    • Mensagens ordenadas por: + + + +

      %(archive)s Arquivos por %(archtype)s

      + -

      Início: %(firstdate)s
      - Fim: %(lastdate)s
      - Mensagens: %(size)s

      -

        +
      +

      Início: %(firstdate)s
      +Fim: %(lastdate)s
      +Mensagens: %(size)s

      +

        +

      \ No newline at end of file diff --git a/templates/pt/archlistend.html b/templates/pt/archlistend.html index 9bc052dd..2e1191b0 100644 --- a/templates/pt/archlistend.html +++ b/templates/pt/archlistend.html @@ -1 +1,64 @@ -
    + diff --git a/templates/pt/archliststart.html b/templates/pt/archliststart.html index 1471f918..9ef4eeda 100644 --- a/templates/pt/archliststart.html +++ b/templates/pt/archliststart.html @@ -1,4 +1,68 @@ - - - - +
    ArquivoVer por:Versão descarregável
    + + + +
    ArquivoVer por:Versão descarregável
    \ No newline at end of file diff --git a/templates/pt/archtoc.html b/templates/pt/archtoc.html index 1f8d82e1..3d6948d1 100644 --- a/templates/pt/archtoc.html +++ b/templates/pt/archtoc.html @@ -1,21 +1,85 @@ - - - Arquivos de %(listname)s - + + + +Arquivos de %(listname)s + %(meta)s - - -

    Arquivos de %(listname)s

    -

    - Pode ter mais informação sobre esta lista + + +

    Arquivos de %(listname)s

    +

    + Pode ter mais informação sobre esta lista ou pode descarregar o arquivo completo, - não processado + não processado (%(size)s).

    %(noarchive_msg)s %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/pt/archtocentry.html b/templates/pt/archtocentry.html index b1438bd4..cf8e7409 100644 --- a/templates/pt/archtocentry.html +++ b/templates/pt/archtocentry.html @@ -1,12 +1,74 @@ - -
    %(archivelabel)s: - [ Tópico ] - [ Assunto ] - [ Autor ] - [ Data ] -
    %(archivelabel)s: +[ Tópico ] +[ Assunto ] +[ Autor ] +[ Data ] +
    - - - - - - - - - - - - - - - - - - + + + + + +

    - -- - -
    -

      -

    - Informação sobre - - - -
    -

    -

    Para ver as mensagens anteriormente enviadas para a lista, - visite os Arquivos de - . - -

    -
    - Utilizar -
    + + + +<mm-list-name>: Página de informações</mm-list-name> + + +

    + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + +
    + -- + +
    +

      +

    +Informação sobre + + + +
    +

    +

    Para ver as mensagens anteriormente enviadas para a lista, + visite os Arquivos de +. + +

    +
    +Utilizar +
    Para enviar uma mensagem para todos os membros da lista, envie um email para - . + . -

    Pode inscrever-se na lista ou mudar a sua incrição - nas secções abaixo. -

    - Inscrever-se em -
    -

    - Inscreva-se em preenchendo o seguinte formulário. - -

      - - - - - - - - - - - - + + + + + + - - - - - -
      O seu endereço de email: -  
      O seu nome (opcional): 
      Pode escolher uma password. - Ela dá-lhe uma segurança média, mas deve bastar para evitar - que outros interfiram com a sua participação. - Não utilize uma password importante visto que ela - lhe será enviada ocasionalmente, via email, em texto não +

      Pode inscrever-se na lista ou mudar a sua incrição + nas secções abaixo. +

      +Inscrever-se em +
      +

      + Inscreva-se em preenchendo o seguinte formulário. + +

        + + + + + + + + + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - - - -
        O seu endereço de email: + 
        O seu nome (opcional): 
        Pode escolher uma password. + Ela dá-lhe uma segurança média, mas deve bastar para evitar + que outros interfiram com a sua participação. + Não utilize uma password importante visto que ela + lhe será enviada ocasionalmente, via email, em texto não encriptado. -

        Se não escolher uma password, o sistema gerará uma para si. - Ela será enviada logo que confirme a sua inscrição. Pode sempre +

        Se não escolher uma password, o sistema gerará uma para si. + Ela será enviada logo que confirme a sua inscrição. Pode sempre pedir para receber a password por email quando editar as suas - opções pessoais. - -
        -
        Escolher uma password: 
        Reintroduzir a password para confirmar: 
        Em que língua prefere ver as suas mensagens?  
        Prefere receber as suas mensagens agrupadas (digested) diariamente? + opções pessoais. + + +
        Escolher uma password: 
        Reintroduzir a password para confirmar: 
        Em que língua prefere ver as suas mensagens?  
        Prefere receber as suas mensagens agrupadas (digested) diariamente? Não - Sim -
        -
        -
        - -
      -
      - - : Lista de participantes -
      - - - -

      - - - -

      - - - +
    Não + Sim +
    +
    +
    + + +

    + +: Lista de participantes +
    + + + +

    + + + +

    + +

    + diff --git a/templates/pt/options.html b/templates/pt/options.html index 85f584a5..6ca0d12a 100644 --- a/templates/pt/options.html +++ b/templates/pt/options.html @@ -1,306 +1,339 @@ - - <MM-Presentable-User> configuração de utilizador em <MM-List-Name> - - - - - -
    - - , configuração para o utilizador - -
    + +<mm-presentable-user> configuração de utilizador em <mm-list-name> +</mm-list-name></mm-presentable-user> + + + + +
    + +, configuração para o utilizador + +

    - - - - - +
    - Estado da inscrição de , - password e opções para a lista . -
    - - - - -

    -

    + + + +
    + Estado da inscrição de , + password e opções para a lista . +
    + + +

    +

    - - +

    - - - + +
    - - Alterar a informação de membro em -
    Pode alterar o endereço que utiliza, introduzindo - o novo endereço nos campos seguintes. Note que uma mensagem de - confirmação será enviada para o novo endereço e a alteração tem de + + + - - - - - -
    + +Alterar a informação de membro em +
    Pode alterar o endereço que utiliza, introduzindo + o novo endereço nos campos seguintes. Note que uma mensagem de + confirmação será enviada para o novo endereço e a alteração tem de ser confirmada antes de se tornar efectiva. -

    As confirmações expiram ao fim de aproximadamente dias. +

    As confirmações expiram ao fim de aproximadamente dias. -

    Também pode, se desejar, introduzir ou alterar o seu nome real +

    Também pode, se desejar, introduzir ou alterar o seu nome real (por exemplo John Smith). -

    Se deseja fazer alterações em todas as listas em que está inscrito - em , active a opção Alterar globalmente. - -

    - - - - - - - -
    Novo endereço:
    Confirmação:
    -
    - - +
    O seu nome +

    Se deseja fazer alterações em todas as listas em que está inscrito + em , active a opção Alterar globalmente. + +

    + + + + + + + +
    Novo endereço:
    Confirmação:
    +
    + + - - -
    O seu nome (opcional):
    -
    -

    Alterar globalmente

    - +
    + +

    +

    Alterar globalmente

    +

    - - - - - - - - + + + + %(textlink)s - diff --git a/templates/pt_BR/article.html b/templates/pt_BR/article.html index 4a846c7e..23f4a8b7 100644 --- a/templates/pt_BR/article.html +++ b/templates/pt_BR/article.html @@ -1,12 +1,13 @@ - - - %(title)s - - - - - + +

    Os arquivos da lista %(listname)s

    +

    + Ainda não foi postada nenhuma mensagem para esta lista, assim os + arquivos estão atualmente vazios. Você poderá ter mais detalhes sobre esta lista.

    - - + + diff --git a/templates/pt_BR/headfoot.html b/templates/pt_BR/headfoot.html index fd9d854d..cbd1bf7c 100644 --- a/templates/pt_BR/headfoot.html +++ b/templates/pt_BR/headfoot.html @@ -1,30 +1,93 @@ -Este texto pode incluir +Este texto pode incluir strings no formato -do Python que são resolvidos contra atributos da lista. a lista -de substituições permitidas são: +do Python que são resolvidos contra atributos da lista. a lista +de substituições permitidas são:
      -
    • real_name - O nome 'amigável' da lista; normalmente - o nome da lista com maiúsculas e minúsculas. +
    • real_name - O nome 'amigável' da lista; normalmente + o nome da lista com maiúsculas e minúsculas. -
    • list_name - O nome no qual a lista é - identificada nas URLs, onde maiúsculas/minúsculas são +
    • list_name - O nome no qual a lista é + identificada nas URLs, onde maiúsculas/minúsculas são importantes.
    • host_name - O nome completo do - servidor (FQDN) onde a lista é executada. + servidor (FQDN) onde a lista é executada.
    • web_page_url - A URL base do Mailman. Isto pode ser adicionado com, - e.g. listinfo/%(list_name)s para chegar até - a página de detalhes sobre a lista de discussão. + e.g. listinfo/%(list_name)s para chegar até + a página de detalhes sobre a lista de discussão. -
    • description - uma breve descrição da lista - de discussão. +
    • description - uma breve descrição da lista + de discussão. -
    • info - A descrição completa da lista de - discussão. +
    • info - A descrição completa da lista de + discussão. -
    • cgiext - A extensão adicionada a +
    • cgiext - A extensão adicionada a scripts CGI. -
    + diff --git a/templates/pt_BR/listinfo.html b/templates/pt_BR/listinfo.html index 656931cf..463c4a50 100644 --- a/templates/pt_BR/listinfo.html +++ b/templates/pt_BR/listinfo.html @@ -1,148 +1,210 @@ + - - - <MM-List-Name> Página de Detalhes - - - -

    -

    - Cancelar a inscrição em - As suas outras inscrições em -
    - Active esta opção e clique neste botão para cancelar a sua - inscrição nesta lista. Aviso: Esta acção será + + + + - + +
    +

    +Cancelar a inscrição em +As suas outras inscrições em +
    + Active esta opção e clique neste botão para cancelar a sua + inscrição nesta lista. Aviso: Esta acção será activada imediatamente!

    -

    - Pode ver quais as listas de que é membro em . Utilize - esta opção se deseja fazer as mesmas alterações em todas +

    + Pode ver quais as listas de que é membro em . Utilize + esta opção se deseja fazer as mesmas alterações em todas essas listas.

    -

    -
    - - - - - +
    - A sua password em -
    - -
    -

    Esqueceu-se da Password?

    -
    - Clique neste botão para receber a password no seu endereço de + + + - -
    +A sua password em +
    + +
    +

    Esqueceu-se da Password?

    +
    + Clique neste botão para receber a password no seu endereço de membro. -

    -

    - -
    -
    - -
    -

    Alterar a Password

    - - - - - - - - -
    Nova - password:
    Confirmação: -
    - - -

    Alterar globalmente. -
    -
    - +

    +

    + +
    +

    + +
    +

    Alterar a Password

    + + + + + + + + +
    Nova + password:
    Confirmação: +
    + +

    Alterar globalmente. +
    +

    - - +
    - As suas opções de membro em -
    +
    +As suas opções de membro em +
    -

    -Os valores activos estão marcados. - -

    Note que algumas opções têm uma opção Alterar globalmente. -Activar este campo faz com que as alterações sejam feitas em todas as -listas de que é membro em . Clique em -Listar outras inscrições para ver quais as listas em que -está inscrito. +Os valores activos estão marcados. +

    Note que algumas opções têm uma opção Alterar globalmente. +Activar este campo faz com que as alterações sejam feitas em todas as +listas de que é membro em . Clique em +Listar outras inscrições para ver quais as listas em que +está inscrito.

    - -
    - - Entrega de correio

    - Active para receber as mensagens enviadas para a lista. - Desactive se deseja permanecer inscrito, mas não quer, - de momento, receber mensagens (por exemplo quando for de férias). - Se desactivar a entrega de mensagens não se esqueça de a + + - +

    - - - +

    + - - - - + - - - - + + - - - - + + - - + - - + - - - + escolher se também a deseja receber através da lista. + Seleccione Sim para evitar receber cópia através + da lista; seleccione Não para receber cópias. + +

    Se a lista tiver activa a opção para personalizar + mensagens e escolher receber cópias, a cada cópia será + acrescentado um cabeçalho X-Mailman-Copy: yes. + +

    +
    + +Entrega de correio

    +Active para receber as mensagens enviadas para a lista. + Desactive se deseja permanecer inscrito, mas não quer, + de momento, receber mensagens (por exemplo quando for de férias). + Se desactivar a entrega de mensagens não se esqueça de a reactivar quando voltar. -

    - Activa
    - Inactiva

    - Alterar globalmente -

    +Activa
    +Inactiva

    +Alterar globalmente +

    - Escolher modo digest

    - Se activar o modo digest receberá as mensagens agrupadas +

    +Escolher modo digest

    + Se activar o modo digest receberá as mensagens agrupadas (normalmente uma por dia, mas por vezes mais em listas muito activas), - em vez de as ir recebendo à medida que são enviadas. Depois de + em vez de as ir recebendo à medida que são enviadas. Depois de desactivar o modo digest ainda pode receber mais uma mensagem dessas. -

    - Inactivo
    - Activo -
    - Receber os digests em formato MIME ou em texto simples?

    - O seu programa de correio pode não suportar digests em formato MIME. - Regra geral digests em formato MIME são preferíveis, mas, - se tiver problemas a lê-los, escolha texto simples. -

    - MIME
    - Texto simples

    - Alterar globalmente -

    +Inactivo
    +Activo +
    +Receber os digests em formato MIME ou em texto simples?

    + O seu programa de correio pode não suportar digests em formato MIME. + Regra geral digests em formato MIME são preferíveis, mas, + se tiver problemas a lê-los, escolha texto simples. +

    +MIME
    +Texto simples

    +Alterar globalmente +

    - Receber as suas mensagens para a lista?

    - Normalmente recebe uma cópia das mensagens que envia para a lista. - Se não a desejar receber escolha a opção Não. -

    - Não
    - Sim -
    - Receber mensagem de confirmação quando envia mensagens +
    +Receber as suas mensagens para a lista?

    + Normalmente recebe uma cópia das mensagens que envia para a lista. + Se não a desejar receber escolha a opção Não. +

    +Não
    +Sim +
    +Receber mensagem de confirmação quando envia mensagens para a lista?

    -

    - Não
    - Sim -
    - Receber lembrete da password desta lista?

    - Uma vez por mês, receberá uma mensagem com a sua password em - cada lista em que está inscrito neste computador. Pode desactivar - esta opção, seleccionando Não. -

    - Não
    - Sim

    - Alterar globalmente -

    - Deseja não aparecer na lista de inscritos?

    - Quando alguém vê a lista de inscritos o seu endereço é normalmente +

    +Não
    +Sim +
    +Receber lembrete da password desta lista?

    + Uma vez por mês, receberá uma mensagem com a sua password em + cada lista em que está inscrito neste computador. Pode desactivar + esta opção, seleccionando Não. +

    +Não
    +Sim

    +Alterar globalmente +

    +Deseja não aparecer na lista de inscritos?

    + Quando alguém vê a lista de inscritos o seu endereço é normalmente apresentado (de uma maneira indirecta, para dificultar o trabalho - aos coleccionadores de endereços). Se deseja que o seu endereço não - apareça de todo na lista de membros seleccione Sim. -

    - Não
    - Sim -
    - Que idioma prefere?

    -

    - -
    - Que tópicos lhe interessam?

    - Ao seleccionar um ou mais tópicos pode filtrar o tráfego - que recebe da lista, de forma a só receber algumas - mensagens. Se um dos tópicos da mensagem for um dos - por si escolhidos ser-lhe-á + aos coleccionadores de endereços). Se deseja que o seu endereço não + apareça de todo na lista de membros seleccione Sim. +

    +Não
    +Sim +
    +Que idioma prefere?

    +

    + +
    +Que tópicos lhe interessam?

    + Ao seleccionar um ou mais tópicos pode filtrar o tráfego + que recebe da lista, de forma a só receber algumas + mensagens. Se um dos tópicos da mensagem for um dos + por si escolhidos ser-lhe-á enviada. -

    se não mensagem não tiver nenhum tópico a regra de - entrega depende da opção seguinte. Se não escolher - nenhum tópico recebera todas as mensagens da lista. -

    - -
    - Deseja receber mensagens que não correspondam a nenhum - tópico?

    - - Esta opção só é útil se tiver seleccionado pelo menos - um tópico. Ela decide o que acontece às mensagens que - não correspondem a nenhum tópico. Se seleccionar Não - implica que se uma mensagem não corresponder a nenhum dos - tópicos ela não lhe será enviada, enquanto seleccionar +

    se não mensagem não tiver nenhum tópico a regra de + entrega depende da opção seguinte. Se não escolher + nenhum tópico recebera todas as mensagens da lista. +

    + +
    +Deseja receber mensagens que não correspondam a nenhum + tópico?

    + + Esta opção só é útil se tiver seleccionado pelo menos + um tópico. Ela decide o que acontece às mensagens que + não correspondem a nenhum tópico. Se seleccionar Não + implica que se uma mensagem não corresponder a nenhum dos + tópicos ela não lhe será enviada, enquanto seleccionar Sim quer dizer que deseja receber essas mensagens - "sem tópico". + "sem tópico". -

    Se não seleccionar nenhum tópico receberá todas as +

    Se não seleccionar nenhum tópico receberá todas as mensagens enviadas para a lista. -

    - Não
    - Sim -
    - Evitar duplicação de mensagens?

    +

    +Não
    +Sim +
    +Evitar duplicação de mensagens?

    Quando estiver indicado explicitamente nos To: ou Cc: de uma mensagem enviada para a lista pode - escolher se também a deseja receber através da lista. - Seleccione Sim para evitar receber cópia através - da lista; seleccione Não para receber cópias. - -

    Se a lista tiver activa a opção para personalizar - mensagens e escolher receber cópias, a cada cópia será - acrescentado um cabeçalho X-Mailman-Copy: yes. - -

    - Não
    - Sim

    - Alterar globalmente -

    -
    -
    +Não
    +Sim

    +Alterar globalmente +

    +
    +
    -

    - - - - + + +

    diff --git a/templates/pt/private.html b/templates/pt/private.html index 4859a7ea..6e32cc65 100755 --- a/templates/pt/private.html +++ b/templates/pt/private.html @@ -1,61 +1,121 @@ - %(realname)s Autenticação de Arquivos Privados +%(realname)s Autenticação de Arquivos Privados - - -
    + + + %(message)s - - - - - - - - - - - - - - - -
    - %(realname)s Autenticação - de Arquivos Privados -
    Endereço de Email:
    Password:
    -
    -

    Importante: Deste ponto em diante, - você tem de ter as cookies activadas no seu navegador, caso - contrário nenhuma modificação administrativa terá efeito. + + + + + + + + + + + + + + + +
    +%(realname)s Autenticação + de Arquivos Privados +
    Endereço de Email:
    Password:
    +
    +

    Importante: Deste ponto em diante, + você tem de ter as cookies activadas no seu navegador, caso + contrário nenhuma modificação administrativa terá efeito. -

    Os cookies de secção são usados pela interface administrativa - do Mailman para não ser necessária uma nova autenticação após - cada operação administrativa. Este cookie expirará automaticamente +

    Os cookies de secção são usados pela interface administrativa + do Mailman para não ser necessária uma nova autenticação após + cada operação administrativa. Este cookie expirará automaticamente quando sair do seu navegador ou quando expirar o cookie explicitamente clicando no link Sair abaixo de Outras Actividades - Administrativas (o qual será visto logo que entrar com sucesso no + Administrativas (o qual será visto logo que entrar com sucesso no sistema).

    - - - - - - + + + +
    - Password Reminder -
    If you don't remember your password, enter your email address + + + + + + - - - - -
    +Password Reminder +
    If you don't remember your password, enter your email address above and click the Remind button and your password will be emailed to you.
    - +
    +

    - diff --git a/templates/pt/roster.html b/templates/pt/roster.html index 0f4772a7..1b587d7b 100644 --- a/templates/pt/roster.html +++ b/templates/pt/roster.html @@ -1,53 +1,112 @@ - - - Assinantes de <MM-List-Name> - - - - -

    - - - - - - - - - - - - - - - -
    - Assinantes de - -
    - -

    -

    - -

    Clique no seu endereço para visualizar a sua página de opções
    - (valores entre parênteses têm a - entrega de mensagens desactivada.)

    -
    -
    - - Membros de com entrega não digest: -
    -
    -
    - Membros de - com entrega digest: -
    -
    -

    -

    -

    -

    - - - + + +Assinantes de <mm-list-name> </mm-list-name> + + +

    + + + + + + + + + + + + + + + +
    +Assinantes de + +
    +

    +

    +

    Clique no seu endereço para visualizar a sua página de opções
    +(valores entre parênteses têm a + entrega de mensagens desactivada.)

    +
    +
    + + Membros de com entrega não digest: +
    +
    +
    +Membros de + com entrega digest: +
    +
    +

    +

    +

    +

    + +

    + diff --git a/templates/pt/subscribe.html b/templates/pt/subscribe.html index 7729325e..6e2b6a79 100644 --- a/templates/pt/subscribe.html +++ b/templates/pt/subscribe.html @@ -1,9 +1,72 @@ -<MM-List-Name> Resultado da inscrição +<mm-list-name> Resultado da inscrição</mm-list-name> -

    Resultado da inscrição

    - - - +

    Resultado da inscrição

    + + + diff --git a/templates/pt_BR/admindbdetails.html b/templates/pt_BR/admindbdetails.html index 295dc59a..342532c6 100644 --- a/templates/pt_BR/admindbdetails.html +++ b/templates/pt_BR/admindbdetails.html @@ -1,72 +1,133 @@ -As requisições são mostradas de duas formas: uma página de resumo, e em uma página detalhada. -A página de resumo contém as inscrições pendentes e requisições de -desinscrição assim como postagens que requerem sua aprovação -agrupada por endereço de email do remetente. A página detalhada -contém uma visualização mais detalhada de cada mensagem em espera, -incluindo todos os cabeçalhos da mensagem exceto do +As requisições são mostradas de duas formas: uma página de resumo, e em uma página detalhada. +A página de resumo contém as inscrições pendentes e requisições de +desinscrição assim como postagens que requerem sua aprovação +agrupada por endereço de email do remetente. A página detalhada +contém uma visualização mais detalhada de cada mensagem em espera, +incluindo todos os cabeçalhos da mensagem exceto do corpo da mensagem. -

    Em todas as páginas, as seguintes ações são possíveis: +

    Em todas as páginas, as seguintes ações são possíveis:

      -
    • Adiar -- Adia sua decisão para mais tarde. Nenhuma ação é - tomada agora para esta requisição administrativa, mas para postagens - aguardando aprovação, você pode ainda encaminhar ou preservar a +
    • Adiar -- Adia sua decisão para mais tarde. Nenhuma ação é + tomada agora para esta requisição administrativa, mas para postagens + aguardando aprovação, você pode ainda encaminhar ou preservar a mensagem (veja abaixo).
    • Aprovar -- Aprova a mensagem, enviando-a para a lista. - Para requisições de membros aprova a mudança no status do + Para requisições de membros aprova a mudança no status do membro.
    • Rejeitar -- Rejeita a mensagem, enviando um aviso de - rejeição para o remetente e descartando a mensagem original. - Para requisições de membros, rejeita a modificação no status - do membro. Em ambos os casos, você deverá incluir uma razão - para esta rejeição na caixa de texto. + rejeição para o remetente e descartando a mensagem original. + Para requisições de membros, rejeita a modificação no status + do membro. Em ambos os casos, você deverá incluir uma razão + para esta rejeição na caixa de texto.
    • Descartar -- Ignora a mensagem original, sem enviar uma - notificação de rejeição. Para requisições de membros, isto - simplesmente descarta a requisição sem avisar a pessoa que - enviou a mensagem. Esta é normalmente a ação que você deseja tomar + notificação de rejeição. Para requisições de membros, isto + simplesmente descarta a requisição sem avisar a pessoa que + enviou a mensagem. Esta é normalmente a ação que você deseja tomar para trabalhar com spams conhecidos. -
    + +

    Para mensagens aguardando aprovação, ative a opção Preservar +se deseja salvar uma cópia da mensagem para o administrador do site. +Isto é útil para mensagens abusivas que deseja que sejam descartadas, +mas você precisa manter um registro para inspeção posterior. -

    Para mensagens aguardando aprovação, ative a opção Preservar -se deseja salvar uma cópia da mensagem para o administrador do site. -Isto é útil para mensagens abusivas que deseja que sejam descartadas, -mas você precisa manter um registro para inspeção posterior. - -

    Ative a opção Encaminhar para e preencha o endereço de -redirecionamento se desejar encaminhar a mensagem para alguém -que não esteja na lista. Para editar uma mensagem em espera antes -de ser enviada para a lista, você deverá redirecionar a mensagem -para você mesmo (ou para os donos da lista) e descartar a mensagem -original. Então quando a mensagem for mostrada em sua caixa -de correio, faça suas modificações e reenvie a mensagem para a -lista, incluindo um cabeçalho Approved: com a senha -da lista como seu valor. Uma netiqueta própria neste caso é +

    Ative a opção Encaminhar para e preencha o endereço de +redirecionamento se desejar encaminhar a mensagem para alguém +que não esteja na lista. Para editar uma mensagem em espera antes +de ser enviada para a lista, você deverá redirecionar a mensagem +para você mesmo (ou para os donos da lista) e descartar a mensagem +original. Então quando a mensagem for mostrada em sua caixa +de correio, faça suas modificações e reenvie a mensagem para a +lista, incluindo um cabeçalho Approved: com a senha +da lista como seu valor. Uma netiqueta própria neste caso é incluir uma nota na mensagem reenviada, explicando que -você modificou o texto. +você modificou o texto. -

    Caso o remetente é um membro da lista que está sendo moderada, -você poderá opcionalmente desativar sua opção de moderação. Isto é -útil quando sua lista estiver configurada para colocar novos -membros em proibição e decidiu que este membro pode ser -confiável para postar para lista passando pelo processo de aprovação. +

    Caso o remetente é um membro da lista que está sendo moderada, +você poderá opcionalmente desativar sua opção de moderação. Isto é +útil quando sua lista estiver configurada para colocar novos +membros em proibição e decidiu que este membro pode ser +confiável para postar para lista passando pelo processo de aprovação. -

    Caso o remetente não seja o membro da lista, você poderá -adicionar o endereço de email ao sender filter. Os - Sender filters são descritos na página de privacidade de filtragem de remetentes, -e poderá ser uma das seguintes auto-aceitar (Aceita), +

    Caso o remetente não seja o membro da lista, você poderá +adicionar o endereço de email ao sender filter. Os + Sender filters são descritos na página de privacidade de filtragem de remetentes, +e poderá ser uma das seguintes auto-aceitar (Aceita), auto-bloqueia (Bloqueia), auto-rejeita (Rejeita), ou -auto-descarta (Descarta). Esta opção não estará disponível -se o endereço já é um dos filtros do remetente. +auto-descarta (Descarta). Esta opção não estará disponível +se o endereço já é um dos filtros do remetente. -

    Quando terminar, clique no botão Enviar Todos os Dados -no topo ou rodapé da página. Este botão enviará todas as ações -selecionadas para todas as requisições administrativas que +

    Quando terminar, clique no botão Enviar Todos os Dados +no topo ou rodapé da página. Este botão enviará todas as ações +selecionadas para todas as requisições administrativas que fez. -

    Retornar a página inicial. +

    Retornar a página inicial. +

    \ No newline at end of file diff --git a/templates/pt_BR/admindbpreamble.html b/templates/pt_BR/admindbpreamble.html index 45408929..a994571f 100644 --- a/templates/pt_BR/admindbpreamble.html +++ b/templates/pt_BR/admindbpreamble.html @@ -1,11 +1,75 @@ -Esta página contém um subconjunto de postagens para a lista -de discussão %(listname)s que estão aguardando -aprovação. Ela atualmente mostra %(description)s +Esta página contém um subconjunto de postagens para a lista +de discussão %(listname)s que estão aguardando +aprovação. Ela atualmente mostra %(description)s -

    Para cada requisição administrativa, por favor selecione a ação -que será tomada, clicando em Enviar todos os dados quando -terminar. Instruções mais detalhadas estão disponíveis +

    Para cada requisição administrativa, por favor selecione a ação +que será tomada, clicando em Enviar todos os dados quando +terminar. Instruções mais detalhadas estão disponíveis aqui. -

    Você também poderá ver um resumo de todas -as requisições pendentes. +

    Você também poderá ver um resumo de todas +as requisições pendentes. +

    \ No newline at end of file diff --git a/templates/pt_BR/admindbsummary.html b/templates/pt_BR/admindbsummary.html index 4540683c..84170179 100644 --- a/templates/pt_BR/admindbsummary.html +++ b/templates/pt_BR/admindbsummary.html @@ -1,14 +1,78 @@ -Esta página contém um resumo do conjunto atual de requisições -administrativas requerendo sua aprovação na -lista de discussão %(listname)s. -Primeiro você verá a lista de inscrições/desinscrições pendentes, +Esta página contém um resumo do conjunto atual de requisições +administrativas requerendo sua aprovação na +lista de discussão %(listname)s. +Primeiro você verá a lista de inscrições/desinscrições pendentes, se tiver alguma, seguida de qualquer postagem que esteja aguardando sua -aprovação. +aprovação. -

    Para cada requisição administrativa, selecione a conexão para +

    Para cada requisição administrativa, selecione a conexão para ser feita clicando em Enviar todos os dados quando terminar. -Instruções mais detalhadas também estão -disponíveis. +Instruções mais detalhadas também estão +disponíveis. -

    Você também pode ver os detalhes de -todas as postagens aguardando aprovação. +

    Você também pode ver os detalhes de +todas as postagens aguardando aprovação. +

    \ No newline at end of file diff --git a/templates/pt_BR/admlogin.html b/templates/pt_BR/admlogin.html index ebb5deb5..47942532 100755 --- a/templates/pt_BR/admlogin.html +++ b/templates/pt_BR/admlogin.html @@ -1,39 +1,100 @@ - Autenticação de %(who)s na lista %(listname)s - - - -
    +Autenticação de %(who)s na lista %(listname)s + + + + %(message)s - - - - - - - - - - - -
    - Autenticação de %(who)s na lista %(listname)s - -
    Senha de %(who)s na Lista:
    -
    -

    Importante: Deste ponto em diante, - você deverá ter os cookies ativados em seu navegador, caso - contrário nenhuma modificação administrativa terá efeito. + + + + + + + + + + + +
    +Autenticação de %(who)s na lista %(listname)s + +
    Senha de %(who)s na Lista:
    +
    +

    Importante: Deste ponto em diante, + você deverá ter os cookies ativados em seu navegador, caso + contrário nenhuma modificação administrativa terá efeito. -

    Os cookies de seção são usados na interface administrativa - do Mailman assim não será necessária uma nova autenticação em - cada operação administrativa. Este cookie expirará automaticamente - quando sair do seu navegador ou automaticamente clicando no botão +

    Os cookies de seção são usados na interface administrativa + do Mailman assim não será necessária uma nova autenticação em + cada operação administrativa. Este cookie expirará automaticamente + quando sair do seu navegador ou automaticamente clicando no botão Sair em baixo de Outras Atividades Administrativas - (que verá logo após logar na página). -

    + (que verá logo após logar na página). +

    diff --git a/templates/pt_BR/archidxentry.html b/templates/pt_BR/archidxentry.html index f9bb57aa..5c620b20 100644 --- a/templates/pt_BR/archidxentry.html +++ b/templates/pt_BR/archidxentry.html @@ -1,4 +1,68 @@ -
  • %(subject)s -  -%(author)s - +
  • %(subject)s +  +%(author)s + +
  • \ No newline at end of file diff --git a/templates/pt_BR/archidxfoot.html b/templates/pt_BR/archidxfoot.html index 4e98d88a..51c2301f 100644 --- a/templates/pt_BR/archidxfoot.html +++ b/templates/pt_BR/archidxfoot.html @@ -1,21 +1,85 @@ - -

    - Data da última mensagem: - %(lastdate)s
    - Arquivada em: %(archivedate)s -

    -

      -
    • Mensagens classificadas por: + +

      +Data da última mensagem: +%(lastdate)s
      +Arquivada em: %(archivedate)s +

      +

      -

      -


      - Este arquivo foi gerado pelo +
    +

    +


    +Este arquivo foi gerado pelo Pipermail %(version)s. - - + + +

    \ No newline at end of file diff --git a/templates/pt_BR/archidxhead.html b/templates/pt_BR/archidxhead.html index 34dc5e64..76ee1625 100644 --- a/templates/pt_BR/archidxhead.html +++ b/templates/pt_BR/archidxhead.html @@ -1,24 +1,89 @@ - - - Os arquivos %(archive)s da lista %(listname)s por %(archtype)s - + + + +Os arquivos %(archive)s da lista %(listname)s por %(archtype)s + %(encoding)s - - - -

    Os arquivos %(archive)s por %(archtype)s

    -
      -
    • Mensagens classificadas por: + + + +

      Os arquivos %(archive)s por %(archtype)s

      + -

      Inicio: %(firstdate)s
      - Fim: %(lastdate)s
      - Mensagens: %(size)s

      -

        +
      +

      Inicio: %(firstdate)s
      +Fim: %(lastdate)s
      +Mensagens: %(size)s

      +

        +

      \ No newline at end of file diff --git a/templates/pt_BR/archlistend.html b/templates/pt_BR/archlistend.html index 9bc052dd..2e1191b0 100644 --- a/templates/pt_BR/archlistend.html +++ b/templates/pt_BR/archlistend.html @@ -1 +1,64 @@ -
    + diff --git a/templates/pt_BR/archliststart.html b/templates/pt_BR/archliststart.html index 11750814..422b5e5c 100644 --- a/templates/pt_BR/archliststart.html +++ b/templates/pt_BR/archliststart.html @@ -1,4 +1,68 @@ - - - - +
    ArquivoVisualizar por:Versão para cópia
    + + + +
    ArquivoVisualizar por:Versão para cópia
    \ No newline at end of file diff --git a/templates/pt_BR/archtoc.html b/templates/pt_BR/archtoc.html index 30dae51d..aed8d0d0 100644 --- a/templates/pt_BR/archtoc.html +++ b/templates/pt_BR/archtoc.html @@ -1,20 +1,84 @@ - - - Os arquivos da lista %(listname)s - + + + +Os arquivos da lista %(listname)s + %(meta)s - - -

    Os arquivos da lista %(listname)s

    -

    - Você poderá obter mais informações sobre a lista - ou poderá baixar o arquivo completo + + +

    Os arquivos da lista %(listname)s

    +

    + Você poderá obter mais informações sobre a lista + ou poderá baixar o arquivo completo (%(size)s).

    %(noarchive_msg)s %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/pt_BR/archtocentry.html b/templates/pt_BR/archtocentry.html index 00cf9c47..6f2a92bb 100644 --- a/templates/pt_BR/archtocentry.html +++ b/templates/pt_BR/archtocentry.html @@ -1,12 +1,74 @@ - -
    %(archivelabel)s: - [ Thread ] - [ Subject ] - [ Author ] - [ Date ] -
    %(archivelabel)s: +[ Thread ] +[ Subject ] +[ Author ] +[ Date ] +
    - - - - - - - - - - - - + + + + + +

    - -- - -
    -

      -

    - Sobre a lista - - - -
    -

    -

    Para ver uma lista das mensagens que foram postadas + + +<mm-list-name> Página de Detalhes</mm-list-name> + + +

    + + + + + + + + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + + +
    + -- + +
    +

      +

    +Sobre a lista + + + +
    +

    +

    Para ver uma lista das mensagens que foram postadas anteriormente na lista, entre nos - Arquivos da lista . - -

    -
    - Usando a lista -
    + Arquivos da lista . + +

    +
    +Usando a lista +
    Para postar uma mensagem para todos os membros da lista, envie um email para - . + . -

    Você poderá se inscrever na lista ou modificar seus dados - de inscrição nas seções abaixo. -

    - Inscrevendo-se na lista -
    -

    - Para se inscrever na lista , preencha o seguinte - formulário. - -

      - - - - - - - - - - - - + + + + + + - - - - - -
      Seu endereço de email: -  
      Seu nome (opcional): 
      Entre com uma +

      Você poderá se inscrever na lista ou modificar seus dados + de inscrição nas seções abaixo. +

      +Inscrevendo-se na lista +
      +

      + Para se inscrever na lista , preencha o seguinte + formulário. + +

        + + + + + + + + + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - - - -
        Seu endereço de email: + 
        Seu nome (opcional): 
        Entre com uma senha privativa abaixo. Isto oferece somente - segurança média, mas deve evitar que outras pessoas - obtenham sua inscrição. Não escolha uma senha + segurança média, mas deve evitar que outras pessoas + obtenham sua inscrição. Não escolha uma senha usada em outras coisas pois ela provavelmente - lhe será encaminhada de volta usando o formato de texto desprotegido. + lhe será encaminhada de volta usando o formato de texto desprotegido. -

        Caso tenha escolhido não entrar com uma senha, será gerada - automaticamente uma senha que lhe será enviada - assim que você confirmar sua inscrição. É possível requisitar - o reenvio de sua senha ao modificar suas opções pessoais. - -
        -
        Escolha uma senha: 
        Redigite a senha para confirmação: 
        Em que idioma prefere exibir suas mensagens?  
        Deseja receber e-mails da lista enviados uma vez por dia em um único +

        Caso tenha escolhido não entrar com uma senha, será gerada + automaticamente uma senha que lhe será enviada + assim que você confirmar sua inscrição. É possível requisitar + o reenvio de sua senha ao modificar suas opções pessoais. + + +
        Escolha uma senha: 
        Redigite a senha para confirmação: 
        Em que idioma prefere exibir suas mensagens?  
        Deseja receber e-mails da lista enviados uma vez por dia em um único email (digest)? Não - Sim -
        -
        -
        - -
      -
      - - Inscritos na lista -
      - - - -

      - - - -

      - - - +
    Não + Sim +
    +
    +
    + + +

    + +Inscritos na lista +
    + + + +

    + + + +

    + +

    + diff --git a/templates/pt_BR/options.html b/templates/pt_BR/options.html index de97bf82..36426758 100644 --- a/templates/pt_BR/options.html +++ b/templates/pt_BR/options.html @@ -1,325 +1,358 @@ - - <MM-Presentable-User> configuração de membros para <MM-List-Name> - - - - - -
    - - configuração de membros da lista para - -
    + +<mm-presentable-user> configuração de membros para <mm-list-name> +</mm-list-name></mm-presentable-user> + + + + +
    + + configuração de membros da lista para + +

    - - - - - +
    - Status de inscrição de 's - senha e opções na lista de discussão . -
    - - - - -

    -

    + + + +
    + Status de inscrição de 's + senha e opções na lista de discussão . +
    + + +

    +

    - - +

    - - - + +
    - - Modificação de seus detalhes na -
    Você pode mudar o endereço que está - inscrito na lista de discussão entrando com o novo endereço - nos campos abaixo. Note que o email de confirmação será - enviado para o novo endereço e a modificação deverá ser + + + - - - - - -
    + +Modificação de seus detalhes na +
    Você pode mudar o endereço que está + inscrito na lista de discussão entrando com o novo endereço + nos campos abaixo. Note que o email de confirmação será + enviado para o novo endereço e a modificação deverá ser confirmada antes de ser feita. -

    Tempo de confirmação após . +

    Tempo de confirmação após . -

    Você também poderá definir ou modificar seu nome real +

    Você também poderá definir ou modificar seu nome real (i.e. John Smith). -

    Se deseja fazer modificações nos membros em todas as - listas que está inscritos em , ative +

    Se deseja fazer modificações nos membros em todas as + listas que está inscritos em , ative a caixa de checagem Modificar globalmente. -

    - - - - - - - -
    Novo Endereço:
    Novamente para - confirmação:
    -
    - - +
    Seu nome +

    + + + + + + + +
    Novo Endereço:
    Novamente para + confirmação:
    +
    + + - - -
    Seu nome (opcional):
    -
    -

    Alterar Globalmente

    - +
    + +

    +

    Alterar Globalmente

    +

    - - - - - -
    - Descadastrando-se da - Suas outras inscrições em -
    - Ligue a caixa de confirmação e pressione este botão para - descadastrar-se da lista de discussão. Atenção: - Esta ação será feita imediatamente! + + + + - + +
    +

    +Descadastrando-se da +Suas outras inscrições em +
    + Ligue a caixa de confirmação e pressione este botão para + descadastrar-se da lista de discussão. Atenção: + Esta ação será feita imediatamente!

    -

    - Você poderá visualizar uma lista de todas as listas de - discussão em no qual é membro. Use isto se deseja - fazer as mesmas modificações nas opções de membros nestas - outras inscrições. +

    + Você poderá visualizar uma lista de todas as listas de + discussão em no qual é membro. Use isto se deseja + fazer as mesmas modificações nas opções de membros nestas + outras inscrições.

    -

    -
    - - - - - +
    - Sua senha na lista -
    - -
    -

    Esqueceu sua senha?

    -
    - Clique neste botão para ter sua senha enviada para seu + + + - -
    +Sua senha na lista +
    + +
    +

    Esqueceu sua senha?

    +
    + Clique neste botão para ter sua senha enviada para seu email de membro da lista. -

    -

    - -
    -
    - -
    -

    Alterar sua senha

    - - - - - - - - -
    Nova - senha:
    Confirmação da senha:
    - - -

    Modificar Globalmente. -
    -
    - +

    +

    + +
    +

    + +
    +

    Alterar sua senha

    + + + + + + + + +
    Nova + senha:
    Confirmação da senha:
    + +

    Modificar Globalmente. +
    +

    - - +
    - Suas Opções de Inscrição -
    +
    +Suas Opções de Inscrição +
    -

    -Os valores atuais estão marcados. - -

    Note que algumas opções tem a caixa Ajustar localmente. -Marcando este campo fará com que todas as mudanças sejam feitas para -cada lista de discussão que você é membro em . Clique em -Liste minhas outras inscrições acima para ver -outras listas de discussão que está inscrito. +Os valores atuais estão marcados. +

    Note que algumas opções tem a caixa Ajustar localmente. +Marcando este campo fará com que todas as mudanças sejam feitas para +cada lista de discussão que você é membro em . Clique em +Liste minhas outras inscrições acima para ver +outras listas de discussão que está inscrito.

    - - - +
    - - Entrega de E-mails

    - Ajuste esta opção para Ativar o recebimento de - mensagens postadas a esta lista de discussão. Selecione - Desativado se deseja permanecer inscrito, mas não - quer receber mensagens por algum tempo (e.g. você - está entrando de férias, por exemplo). Caso desative a - entrega de mensagens, não se esqueça de reativa-la quando - voltar; ela não será automaticamente reativada. -

    - Ativada
    - Desativada

    - Ajustar Globalmente -

    + - - - + +

    - - - - + - - - - - - - - + + + + - - + - - + - - - +

    +
    + +Entrega de E-mails

    + Ajuste esta opção para Ativar o recebimento de + mensagens postadas a esta lista de discussão. Selecione + Desativado se deseja permanecer inscrito, mas não + quer receber mensagens por algum tempo (e.g. você + está entrando de férias, por exemplo). Caso desative a + entrega de mensagens, não se esqueça de reativa-la quando + voltar; ela não será automaticamente reativada. +

    +Ativada
    +Desativada

    +Ajustar Globalmente +

    - Ajustar o Modo Digest

    - Se ativar o modo digest, você receberá postagens concatenadas - em um único email uma vez por dia, possivelmente mais em - listas muito movimentadas), ao invés de recebe-las quando - são enviadas. Caso o modo digest seja modificado de ativado - para desativado, você poderá receber uma última mensagem +

    +Ajustar o Modo Digest

    + Se ativar o modo digest, você receberá postagens concatenadas + em um único email uma vez por dia, possivelmente mais em + listas muito movimentadas), ao invés de recebe-las quando + são enviadas. Caso o modo digest seja modificado de ativado + para desativado, você poderá receber uma última mensagem digest. -

    - Desligado
    - Ligado -
    - Receber digests em MIME ou Texto Plano?

    - Seu leitor de email pode ou não suportar digests MIME. Em geral - os digests MIME são preferidos, mas se tiver um problema durante +

    +Desligado
    +Ligado +
    +Receber digests em MIME ou Texto Plano?

    + Seu leitor de email pode ou não suportar digests MIME. Em geral + os digests MIME são preferidos, mas se tiver um problema durante sua leitura, selecione digests em texto plano. -

    - MIME
    - Texto Plano

    - Ajustar Globalmente -

    +MIME
    +Texto Plano

    +Ajustar Globalmente +

    - Receber suas próprias postagens a esta lista?

    - Ordinariamente, você receberá uma cópia de cada mensagem que - postar na lista. Se não desejar receber a cópia, mude esta - opção para Não. -

    - Não
    - Sim -
    - Receber um email de reconhecimento quando enviar um email para +
    +Receber suas próprias postagens a esta lista?

    + Ordinariamente, você receberá uma cópia de cada mensagem que + postar na lista. Se não desejar receber a cópia, mude esta + opção para Não. +

    +Não
    +Sim +
    +Receber um email de reconhecimento quando enviar um email para a lista?

    -

    - Não
    - Sim -
    - Receber um email lembrando a senha desta lista?

    - Uma vez por mês, você receberá um email contendo um lembrete de - senha de cada lista que está cadastrado. Você poderá desativar - esta opção individualmente por listas selecionando - não nesta opção. Se desativar o lembrete de senhas - para todas as listas que está inscrito, nenhum email de - lembrete será enviado a você. -

    - Não
    - Sim

    - Ajustar Globalmente -

    - Ocultar seu nome na lista de inscritos?

    - Quando alguém visualiza a lista de membros, seu endereço de - email normalmente é mostrado (de uma forma obscura para frustrar - spammers). Se não deseja usar seu endereço de email para - mostrar sua lista de membros, selecione Não para esta - opção. -

    - Não
    - Sim -
    - Que idioma prefere?

    -

    - -
    - Que categorias de tópicos deseja se inscrever?

    - Selecionando um ou mais tópicos, você pode filtrar o - tráfego da lista de discussão, assim como receber +

    +Não
    +Sim +
    +Receber um email lembrando a senha desta lista?

    + Uma vez por mês, você receberá um email contendo um lembrete de + senha de cada lista que está cadastrado. Você poderá desativar + esta opção individualmente por listas selecionando + não nesta opção. Se desativar o lembrete de senhas + para todas as listas que está inscrito, nenhum email de + lembrete será enviado a você. +

    +Não
    +Sim

    +Ajustar Globalmente +

    +Ocultar seu nome na lista de inscritos?

    + Quando alguém visualiza a lista de membros, seu endereço de + email normalmente é mostrado (de uma forma obscura para frustrar + spammers). Se não deseja usar seu endereço de email para + mostrar sua lista de membros, selecione Não para esta + opção. +

    +Não
    +Sim +
    +Que idioma prefere?

    +

    + +
    +Que categorias de tópicos deseja se inscrever?

    + Selecionando um ou mais tópicos, você pode filtrar o + tráfego da lista de discussão, assim como receber somente alguns tipos de mensagens. Se uma mensagem - confere com um dos tópicos selecionados, você - receberá a mensagem, caso contrário, não. - -

    Caso uma mensagem não conferir com qualquer tópico, - a regra de entrega depende da próxima opção de - configuração. Se não selecionar qualquer tópico de - interesse, você receberá todas as mensagens enviadas - para a lista de discussão. -

    - -
    - Deseja receber mensagens que não conferem com qualquer filtro - de tópico?

    - - Esta opção somente faz efeito caso tenha se inscrito - em pelo menos um tópico acima. Ela descreve qual é a - regra de entrega padrão para mensagens que não conferem - com qualquer filtro de tópico. Selecionando Não - significa que se a mensagem não conferir com qualquer - filtro de tópico, então você NÃO receberá a mensagem, + confere com um dos tópicos selecionados, você + receberá a mensagem, caso contrário, não. + +

    Caso uma mensagem não conferir com qualquer tópico, + a regra de entrega depende da próxima opção de + configuração. Se não selecionar qualquer tópico de + interesse, você receberá todas as mensagens enviadas + para a lista de discussão. +

    + +
    +Deseja receber mensagens que não conferem com qualquer filtro + de tópico?

    + + Esta opção somente faz efeito caso tenha se inscrito + em pelo menos um tópico acima. Ela descreve qual é a + regra de entrega padrão para mensagens que não conferem + com qualquer filtro de tópico. Selecionando Não + significa que se a mensagem não conferir com qualquer + filtro de tópico, então você NÃO receberá a mensagem, enquanto selecionando Sim diz para entregar - mensagens que não conferem com o filtro. - -

    Caso nenhum tópico de interesse seja selecionado acima, - você receberá cada mensagem enviada para a lista de - discussão. -

    - Não
    - Sim -
    - Evitar cópias duplicadas de mensagens?

    - - Quando você está explicitamente listado nos cabeçalhos + mensagens que não conferem com o filtro. + +

    Caso nenhum tópico de interesse seja selecionado acima, + você receberá cada mensagem enviada para a lista de + discussão. +

    +Não
    +Sim +
    +Evitar cópias duplicadas de mensagens?

    + + Quando você está explicitamente listado nos cabeçalhos To: ou Cc: da mensagem da lista, - você pode optar por não receber outra cópia da - lista de discussão. + você pode optar por não receber outra cópia da + lista de discussão. Selecione Sim para evitar o recebimento de - cópias das listas de discussão, enquanto - Não recebe cópias. + cópias das listas de discussão, enquanto + Não recebe cópias. -

    Caso a lista tenha personalização de mensagens - personalizadas pelos membros e você selecione receber - cópias, cada cópia terá um cabeçalho +

    Caso a lista tenha personalização de mensagens + personalizadas pelos membros e você selecione receber + cópias, cada cópia terá um cabeçalho X-Mailman-Copy: yes adicionado na mensagem. -

    - Não
    - Sim

    - Ajustar Globalmente -

    -
    -
    +Não
    +Sim

    +Ajustar Globalmente +

    +
    +
    -

    - - - - + + +

    diff --git a/templates/pt_BR/private.html b/templates/pt_BR/private.html index 8a30a73a..d146f57b 100755 --- a/templates/pt_BR/private.html +++ b/templates/pt_BR/private.html @@ -1,59 +1,120 @@ - Autenticação para acesso aos arquivos privados da %(realname)s - - - -
    +Autenticação para acesso aos arquivos privados da %(realname)s + + + + %(message)s - - - - - - - - - - - - - - - -
    - Autenticação para acesso - dos arquivos privados da %(realname)s -
    Endereço de Email:
    Senha:
    -
    -

    Importante: Deste ponto em diante, - você deverá ter os cookies ativados em seu navegador, caso - contrário você terá que re-autenticar com cada operação. + + + + + + + + + + + + + + + +
    +Autenticação para acesso + dos arquivos privados da %(realname)s +
    Endereço de Email:
    Senha:
    +
    +

    Importante: Deste ponto em diante, + você deverá ter os cookies ativados em seu navegador, caso + contrário você terá que re-autenticar com cada operação. -

    Os cookies de seção são usados pela interface de arquivos - privados do Mailman para não precisa re-autenticar após cada - operação. Este cookie se expirará automaticamente quando sair - do seu navegador ou você poderá desativar o cookie visitando - sua página de opções de membro e clicando no botão +

    Os cookies de seção são usados pela interface de arquivos + privados do Mailman para não precisa re-autenticar após cada + operação. Este cookie se expirará automaticamente quando sair + do seu navegador ou você poderá desativar o cookie visitando + sua página de opções de membro e clicando no botão Sair.

    - - - - - - + + + +
    - Password Reminder -
    If you don't remember your password, enter your email address + + + + + + - - - - -
    +Password Reminder +
    If you don't remember your password, enter your email address above and click the Remind button and your password will be emailed to you.
    - +
    +

    diff --git a/templates/pt_BR/roster.html b/templates/pt_BR/roster.html index cb49fa3e..fc6d728c 100644 --- a/templates/pt_BR/roster.html +++ b/templates/pt_BR/roster.html @@ -1,53 +1,112 @@ - - - <MM-List-Name> Membros Inscritos - - - - -

    - - - - - - + + + + + + + + + +
    - Inscritos na lista - -
    - -

    -

    - -

    Clique em seu endereço para visualizar a página - de inscrição
    (valores entre parênteses tem a + + +<mm-list-name> Membros Inscritos</mm-list-name> + + +

    + + + + + + - - - - - - - - - -
    +Inscritos na lista + +
    +

    +

    +

    Clique em seu endereço para visualizar a página + de inscrição
    (valores entre parênteses tem a entrega de mensagens desativadas na lista.)

    -
    -
    - - Membros não digest de : -
    -
    -
    - Membros - digest de : -
    -
    -

    -

    -

    -

    - - - +

    +
    + + Membros não digest de : +
    +
    +
    + Membros + digest de : +
    +
    +

    +

    +

    +

    + +

    + diff --git a/templates/pt_BR/subscribe.html b/templates/pt_BR/subscribe.html index 63ec8210..d1b47a2d 100644 --- a/templates/pt_BR/subscribe.html +++ b/templates/pt_BR/subscribe.html @@ -1,9 +1,72 @@ -Resultados de inscrição na lista <MM-List-Name> +Resultados de inscrição na lista <mm-list-name></mm-list-name> -

    Resultados de inscrição

    - - - +

    Resultados de inscrição

    + + + diff --git a/templates/ro/admindbdetails.html b/templates/ro/admindbdetails.html index 2f5387f5..f86f3c3c 100644 --- a/templates/ro/admindbdetails.html +++ b/templates/ro/admindbdetails.html @@ -1,54 +1,116 @@ -Cererile administrative sunt afiÅŸate în unul din cele două moduri, +Cererile administrative sunt afiÅŸate în unul din cele două moduri, pe o pagină de sumar, ÅŸi respectiv pe o pagină de detalii. Pagina de sumar conÅ£ine cererile de abonare ÅŸi cele de părăsire a listei, precum ÅŸi mesajele reÅ£inute pentru aprobare, grupate după adresa de email a expeditorului. Pagina de detalii conÅ£ine o imagine mai detaliată a fiecărui mesaj -reÅ£inut, incluzând toate headerele acestuia ÅŸi un extras al corpului +reÅ£inut, incluzând toate headerele acestuia ÅŸi un extras al corpului mesajului.

    Pe toate paginile, următoarele acţiuni sunt disponibile:

      -
    • Amânare -- AmânaÅ£i luarea unei decizii pentru mai târziu. +
    • Amânare -- AmânaÅ£i luarea unei decizii pentru mai târziu. Nu este execută nici o acÅ£iune pentru această cerere administrativă - în aÅŸteptare, dar pentru mesajele reÅ£inute, mai puteÅ£i re-trimite + în aÅŸteptare, dar pentru mesajele reÅ£inute, mai puteÅ£i re-trimite (forward) sau păstra mesajul (vezi mai jos). -
    • Aprobare -- AprobaÅ£i mesajul, trimiÅ£ându-l listei. În cazul +
    • Aprobare -- AprobaÅ£i mesajul, trimiţându-l listei. ÃŽn cazul cererilor legate de abonament, este aprobată schimbarea cerută.
    • Respingere -- Mesajul este respins, fiind trimisă o notă cu - explicaÅ£ia refuzului către expeditor. În cazul cererilor legate de + explicaÅ£ia refuzului către expeditor. ÃŽn cazul cererilor legate de abonament, este refuzată cererea de schimbare a abonamentului. - În oricare din cazuri, este indicat să adăugaÅ£i ÅŸi un motiv al respingerii - cererii în câmpul de alături. + ÃŽn oricare din cazuri, este indicat să adăugaÅ£i ÅŸi un motiv al respingerii + cererii în câmpul de alături.
    • Ignorare -- Mesajul iniÅ£ial este ignorat, fără a fi trimisă vreo - notă a respingerii acestuia. În cazul cererilor legate de abonament, + notă a respingerii acestuia. ÃŽn cazul cererilor legate de abonament, cererea este pur ÅŸi simplu ignorată, fără notificarea iniÅ£iatorului. - Aceasta este acÅ£iunea cel mai des folosită în cazul situaÅ£iilor de spam. -
    - -

    În cazul mesajelor reÅ£inute, bifaÅ£i opÅ£iunea Păstrează dacă doriÅ£i + Aceasta este acÅ£iunea cel mai des folosită în cazul situaÅ£iilor de spam. + +

    ÃŽn cazul mesajelor reÅ£inute, bifaÅ£i opÅ£iunea Păstrează dacă doriÅ£i să salvaÅ£i o copie a mesajului pentru administratorul site-ului. Acest fapt -este foarte folositor în cazul mesajelor abuzive pe care doriÅ£i să le ignoraÅ£i, +este foarte folositor în cazul mesajelor abuzive pe care doriÅ£i să le ignoraÅ£i, dar trebuie să le păstraÅ£i pentru o inspecÅ£ie ulterioară.

    BifaÅ£i opÅ£iunea Retrimite la , ÅŸi completaÅ£i adresa de destinaÅ£ie dacă doriÅ£i să trimiteÅ£i mesajul către o adresă ce nu se află pe listă. -Pentru a edita un mesaj reÅ£inut înainte de a fi trimis listei, va trebui +Pentru a edita un mesaj reÅ£inut înainte de a fi trimis listei, va trebui să retrimiteÅ£i mesajul către dumneavoastră (sau către proprietarii listei), -ÅŸi să ignoraÅ£i mesajul iniÅ£ial. Apoi, când mesajul vă apare în căsuÅ£a -postală, faceÅ£i modificările cuvenite ÅŸi retrimiteÅ£i-l către listă, incluzând -un header Approved: având parola listei ca valoare. Neticheta +ÅŸi să ignoraÅ£i mesajul iniÅ£ial. Apoi, când mesajul vă apare în căsuÅ£a +postală, faceÅ£i modificările cuvenite ÅŸi retrimiteÅ£i-l către listă, incluzând +un header Approved: având parola listei ca valoare. Neticheta (regulile de politeÅ£e pe net) recomandă adăugarea unei note prin care să prezentaÅ£i motivul modificărilor făcute.

    Dacă expeditorul este un membru supravegheat (moderat) al listei, opÅ£ional -puteÅ£i reseta flag-ul de moderare. Această facilitate este utilă când lista -dumneavoastra este configurata sa Å£ina noii membrii într-o perioadă de probă, -si aÅ£i decis că acest membru este de încredere ÅŸi poate trimite direct mesaje +puteÅ£i reseta flag-ul de moderare. Această facilitate este utilă când lista +dumneavoastra este configurata sa Å£ina noii membrii într-o perioadă de probă, +si aÅ£i decis că acest membru este de încredere ÅŸi poate trimite direct mesaje către, listă fără alte aprobări.

    Dacă expeditorul nu este un membru al listei, puteţi adăuga adresa de email @@ -58,8 +120,9 @@ auto-reject (Respingere), sau auto-discard (Ignorare). Această opţiune nu va fi disponibilă dacă adresa este deja ataşată unui filtru de expeditor. -

    Când aÅ£i terminat, apăsaÅ£i pe butonul Salvează toate datele (aflat la +

    Când aţi terminat, apăsaţi pe butonul Salvează toate datele (aflat la extremităţile paginii). Acest buton va trimite spre executare toate acţiunile selectate pentru toate cererile administrative pentru care aţi luat o decizie. -

    Înapoi la pagina de sumar. +

    ÃŽnapoi la pagina de sumar. +

    \ No newline at end of file diff --git a/templates/ro/admindbpreamble.html b/templates/ro/admindbpreamble.html index 707b7936..9b002fa4 100644 --- a/templates/ro/admindbpreamble.html +++ b/templates/ro/admindbpreamble.html @@ -1,6 +1,69 @@ -Această pagină conÅ£ine o parte a mesajelor publicate pe lista de discuÅ£ii -%(listname)s, dar care sunt reÅ£inute în aÅŸteptarea aprobării -dumneavoastră. În prezent arată +Această pagină conÅ£ine o parte a mesajelor publicate pe lista de discuÅ£ii +%(listname)s, dar care sunt reÅ£inute în aÅŸteptarea aprobării +dumneavoastră. ÃŽn prezent arată %(description)s

    Pentru fiecare cerere administrativă, alegeţi acţiunea de urmat, @@ -8,4 +71,5 @@ detaliate sunt disponibile aici.

    PuteÅ£i, de asemenea, afiÅŸa un sumar al -tuturor cererilor în aÅŸteptare. +tuturor cererilor în aÅŸteptare. +

    \ No newline at end of file diff --git a/templates/ro/admindbsummary.html b/templates/ro/admindbsummary.html index 098b61dd..d4cdaf2a 100644 --- a/templates/ro/admindbsummary.html +++ b/templates/ro/admindbsummary.html @@ -1,12 +1,76 @@ -Această pagină conţine un sumar al setului curent de cereri administrative +Această pagină conţine un sumar al setului curent de cereri administrative care aşteaptă aprobarea dumneavoastră pentru lista de discuţii -%(listname)s.
    -Mai întâi, veÅ£i găsi o listă a cererilor de abonare/dezabonare, dacă -există, urmate de orice mesaje care sunt reÅ£inute în vederea aprobării. +%(listname)s.
    +Mai întâi, veţi găsi o listă a cererilor de abonare/dezabonare, dacă +există, urmate de orice mesaje care sunt reţinute în vederea aprobării.

    Pentru fiecare cerere administrativă, alegeÅ£i acÅ£iunea corespunzătoare, -apăsând pe butonul Salvează toate datele când aÅ£i terminat.
    +apăsând pe butonul Salvează toate datele când aţi terminat.
    Aici sunt disponibile mai multe instrucţiuni detaliate.

    Puteţi, de asemena, să afişaţi detaliile tuturor mesajelor publicate. +

    \ No newline at end of file diff --git a/templates/ro/admlogin.html b/templates/ro/admlogin.html index 324d676a..723b35ca 100755 --- a/templates/ro/admlogin.html +++ b/templates/ro/admlogin.html @@ -1,38 +1,99 @@ - %(listname)s: Autentificare - %(who)s - - - - -
    +%(listname)s: Autentificare - %(who)s + + + + + %(message)s - - - - - - - - - - - -
    - %(listname)s: Autentificare - %(who)s -
    %(who)s: parola de acces
    -
    -

    Important!
    - De aici înainte trebuie să aveÅ£i activate cookie-urile în browser, altfel nici o + + + + + + + + + + + +
    +%(listname)s: Autentificare - %(who)s +
    %(who)s: parola de acces
    +
    +

    Important!
    + De aici înainte trebuie să aveţi activate cookie-urile în browser, altfel nici o modificare administrativă nu va avea efect.

    InterfaÅ£a de administrare Mailman foloseÅŸte cookie-urile de sesiune, - astfel încât nu trebuie să vă re-autentificaÅ£i la fiecare operaÅ£iune + astfel încât nu trebuie să vă re-autentificaÅ£i la fiecare operaÅ£iune administrativă. Acest cookie va expira automat la ieÅŸire, sau explicit, la apăsarea linkului Logout, sub linkul Alte activităţi - administrative (care va apare de îndată ce vă autentificaÅ£i cu succes). -

    + administrative (care va apare de îndată ce vă autentificaţi cu succes). +

    diff --git a/templates/ro/archidxentry.html b/templates/ro/archidxentry.html index f9bb57aa..5c620b20 100644 --- a/templates/ro/archidxentry.html +++ b/templates/ro/archidxentry.html @@ -1,4 +1,68 @@ -
  • %(subject)s -  -%(author)s - +
  • %(subject)s +  +%(author)s + +
  • \ No newline at end of file diff --git a/templates/ro/archidxfoot.html b/templates/ro/archidxfoot.html index 3b83c4da..b4ded18b 100644 --- a/templates/ro/archidxfoot.html +++ b/templates/ro/archidxfoot.html @@ -1,20 +1,84 @@ - -

    - Data ultimului mesaj: - %(lastdate)s
    - Arhivat la: %(archivedate)s -

    -

      -
    • Mesajele sunt sortate după: + +

      +Data ultimului mesaj: +%(lastdate)s
      +Arhivat la: %(archivedate)s +

      +

      -

      -


      - Această arhivă a fost generată de Pipermail %(version)s. - - +
    +

    +


    +Această arhivă a fost generată de Pipermail %(version)s. + + +

    \ No newline at end of file diff --git a/templates/ro/archidxhead.html b/templates/ro/archidxhead.html index 12768392..8cf2834c 100644 --- a/templates/ro/archidxhead.html +++ b/templates/ro/archidxhead.html @@ -1,24 +1,89 @@ - - - Arhiva %(archive)s a listei %(listname)s după %(archtype)s - - + + + +Arhiva %(archive)s a listei %(listname)s după %(archtype)s + + %(encoding)s - - - -

    Arhivele %(archive)s după %(archtype)s

    -
      -
    • Mesajele sunt sortate după: + + + +

      Arhivele %(archive)s după %(archtype)s

      + -

      Începe la: %(firstdate)s
      - Se termină la: %(lastdate)s
      - Mesaje: %(size)s

      -

        +
      +

      ÃŽncepe la: %(firstdate)s
      +Se termină la: %(lastdate)s
      +Mesaje: %(size)s

      +

        +

      \ No newline at end of file diff --git a/templates/ro/archlistend.html b/templates/ro/archlistend.html index 9bc052dd..2e1191b0 100644 --- a/templates/ro/archlistend.html +++ b/templates/ro/archlistend.html @@ -1 +1,64 @@ -
    + diff --git a/templates/ro/archliststart.html b/templates/ro/archliststart.html index 952c0b44..969d7ae3 100644 --- a/templates/ro/archliststart.html +++ b/templates/ro/archliststart.html @@ -1,4 +1,68 @@ - - - - +
    ArhivaAfişată de:Versiunea pentru download
    + + + +
    ArhivaAfişată de:Versiunea pentru download
    \ No newline at end of file diff --git a/templates/ro/archtoc.html b/templates/ro/archtoc.html index 959d8f6c..d9d8ef40 100644 --- a/templates/ro/archtoc.html +++ b/templates/ro/archtoc.html @@ -1,21 +1,85 @@ - - - Arhivele listei %(listname)s - - + + + +Arhivele listei %(listname)s + + %(meta)s - - -

    Arhivele listei de discuţii %(listname)s

    -

    + + +

    Arhivele listei de discuţii %(listname)s

    +

    PuteÅ£i obÅ£ine mai multe detalii despre această listă - sau puteÅ£i descărca întreaga arhivă în formă brută + sau puteÅ£i descărca întreaga arhivă în formă brută (%(size)s).

    %(noarchive_msg)s %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/ro/archtocentry.html b/templates/ro/archtocentry.html index d4839c07..1eb478ac 100644 --- a/templates/ro/archtocentry.html +++ b/templates/ro/archtocentry.html @@ -1,12 +1,74 @@ - - - %(archivelabel)s: - - [ Fir ] - [ Subiect ] - [ Autor ] - [ Dată ] - + + +%(archivelabel)s: + +[ Fir ] +[ Subiect ] +[ Autor ] +[ Dată ] + %(textlink)s - diff --git a/templates/ro/article.html b/templates/ro/article.html index 2dc68a25..e0d5be27 100644 --- a/templates/ro/article.html +++ b/templates/ro/article.html @@ -1,12 +1,13 @@ - - - %(title)s - - - - - + +

    Arhivele %(listname)s

    +

    + Încă nu au fost publicate mesaje pe această listă, astfel încât arhivele sunt + goale în acest moment. Puteţi însă obţine mai multe detalii despre această listă.

    - - + + diff --git a/templates/ro/headfoot.html b/templates/ro/headfoot.html index 20599050..c1c58c02 100644 --- a/templates/ro/headfoot.html +++ b/templates/ro/headfoot.html @@ -1,13 +1,76 @@ -Acest text poate include +Acest text poate include şiruri de caractere formatate Python care sunt rezolvate vis-a-ais de atributele listei. Lista substituţiilor permise este:
      -
    • real_name - Numele "frumos" al listei; de obicei este vorba de +
    • real_name - Numele "frumos" al listei; de obicei este vorba de numele listei, cu iniÅ£iala majusculă. -
    • list_name - Numele sub care lista este identificată în adrese URL, +
    • list_name - Numele sub care lista este identificată în adrese URL, acolo unde literele mari/mici contează.
    • host_name - Numele de domeniu calificat (FQDN) al serverului pe @@ -22,4 +85,4 @@
    • info - Descrierea detaliată a listei de discuÅ£ii.
    • cgiext - Extensia adăugată scripturilor cgi. -
    + diff --git a/templates/ro/listinfo.html b/templates/ro/listinfo.html index 4a549828..056d12ae 100644 --- a/templates/ro/listinfo.html +++ b/templates/ro/listinfo.html @@ -1,142 +1,204 @@ + - - - <MM-List-Name> - Informaţii Generale - - - -

    - - - - - - - - - - - - - - - - - - - + + + + + +

    - -- -
    -

      -

    - Lista - - - -
    -

    -

    Pentru a accesa colecţia mesajelor publicate anterior - pe listă, vizitaţi Arhivele . - -

    -
    - Folosirea listei -
    + + +<mm-list-name> - Informaţii Generale</mm-list-name> + + + +

    + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + -- +
    +

      +

    +Lista + + + +
    +

    +

    Pentru a accesa colecţia mesajelor publicate anterior + pe listă, vizitaţi Arhivele . + +

    +
    +Folosirea listei +
    Pentru a trimite un mesaj către listă, trimiteţi un email - la adresa - -

    În secÅ£iunile următoare vă puteÅ£i abona la această listă, + la adresa +

    În secţiunile următoare vă puteţi abona la această listă, sau vă puteţi modifica datele existente de abonament. -

    - Abonarea la -
    -

    - Vă puteÅ£i abona la lista de discuÅ£ii completând formularul +

    +Abonarea la +
    +

    + Vă puteţi abona la lista de discuţii completând formularul de mai jos. - -

      - - - - - - - - - - - - - - - - - -
      Adresa de email: -  
      Numele (opÅ£ional): 
      Puteţi introduce o parolă mai jos. + +
        + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - -
        Adresa de email: + 
        Numele (opţional): 
        PuteÅ£i introduce o parolă mai jos. Aceasta nu va asigura o protecÅ£ie extraordinară, dar ar trebui - să prevină intervenÅ£ia altora în datele dumneavoastră de abonament. - Nu folosiÅ£i parole valoroase atâta timp cât acestea vă vor fi - retrimise ocazional în text clar. + să prevină intervenÅ£ia altora în datele dumneavoastră de abonament. + Nu folosiÅ£i parole valoroase atâta timp cât acestea vă vor fi + retrimise ocazional în text clar. -

        Dacă nu introduceÅ£i nici o parolă, va fi generată automat una - pentru dumneavoastră, ÅŸi vă va fi trimisă de îndată ce veÅ£i confirma - abonarea la listă. PuteÅ£i oricând cere re-trimiterea parolei prin email +

        Dacă nu introduceţi nici o parolă, va fi generată automat una + pentru dumneavoastră, şi vă va fi trimisă de îndată ce veţi confirma + abonarea la listă. Puteţi oricând cere re-trimiterea parolei prin email la editarea preferinţelor personale. - -
        -
        AlegeÅ£i o parolă: 
        ReintroduceÅ£i parola pentru confirmare: 
        Limba preferată:  
        DoriÅ£i mesajele listei într-un rezumat zilnic? Nu - Da -
        -
        -
        - -
      -
      - - Abonaţii listei -
      - - - -

      - - - -

      - - - + + +
    Alegeţi o parolă: 
    Reintroduceţi parola pentru confirmare: 
    Limba preferată:  
    Doriţi mesajele listei într-un rezumat zilnic? Nu + Da +
    +
    +
    + + +

    + +Abonaţii listei +
    + + + +

    + + + +

    + +

    + diff --git a/templates/ro/options.html b/templates/ro/options.html index bb365500..0e2a2e9a 100644 --- a/templates/ro/options.html +++ b/templates/ro/options.html @@ -1,44 +1,106 @@ - - Configurarea abonamentului <MM-Presentable-User> la lista <MM-List-Name> - - - - - -
    - - Lista de discuţii - configurarea abonamentului pentru - -
    + +Configurarea abonamentului <mm-presentable-user> la lista <mm-list-name></mm-list-name></mm-presentable-user> + + + + + +
    + + Lista de discuţii - configurarea abonamentului pentru + +

    - - - - - +
    - Situaţia abonamentului , - parola şi opţiunile pentru lista de discuţii . -
    - - -

    -

    + + + +
    + Situaţia abonamentului , + parola şi opţiunile pentru lista de discuţii . +
    + + +

    +

    - - +

    - - - +
    - - Informaţiile de abonament la lista -
    PuteÅ£i schimba adresa cu care v-aÅ£i abonat la lista - de discuÅ£ii introducând o nouă adresă în câmpurile de mai jos. Nu uitaÅ£i + + + - - - - - -
    + +Informaţiile de abonament la lista +
    PuteÅ£i schimba adresa cu care v-aÅ£i abonat la lista + de discuÅ£ii introducând o nouă adresă în câmpurile de mai jos. Nu uitaÅ£i că un mesaj de confirmare va fi trimis la noua adresă, ÅŸi această - schimbare trebuie confirmată înainte de a intra în vigoare. + schimbare trebuie confirmată înainte de a intra în vigoare.

    Confirmările expiră după aproximativ . @@ -48,246 +110,219 @@

    Dacă doriţi să faceţi modificări de abonament pentru toate listele de la la la care sunteţi abonat, bifaţi opţiunea Modificări globale. -

    - - - - - - - -
    Noua adresă:
    Adresa din nou:
    -
    - - - - -
    Numele (opţional):
    -
    -

    Modificări globale

    - +

    + + + + + + + +
    Noua adresă:
    Adresa din nou:
    +

    + + + + +
    Numele (opţional):
    + +
    +

    Modificări globale

    +

    - - - - - - - - + + + + %(textlink)s - diff --git a/templates/ru/archtocnombox.html b/templates/ru/archtocnombox.html index eeeea6f4..82c685b9 100644 --- a/templates/ru/archtocnombox.html +++ b/templates/ru/archtocnombox.html @@ -1,18 +1,82 @@ - - - Ðрхивы ÑпиÑка раÑÑылки %(listname)s - + + + +Ðрхивы ÑпиÑка раÑÑылки %(listname)s + %(meta)s - - -

    Ðрхивы ÑпиÑка раÑÑылки %(listname)s

    -

    ЗдеÑÑŒ вы найдете информацию о ÑпиÑке + + +

    Ðрхивы ÑпиÑка раÑÑылки %(listname)s

    +

    ЗдеÑÑŒ вы найдете информацию о ÑпиÑке раÑÑылки.

    %(noarchive_msg)s %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/ru/article.html b/templates/ru/article.html index a8267999..28406e18 100644 --- a/templates/ru/article.html +++ b/templates/ru/article.html @@ -1,12 +1,13 @@ - - - %(title)s - - - - - + +

    Ðрхив ÑпиÑка раÑÑылки %(listname)s

    +

    Ð’ Ñтот ÑпиÑок раÑÑылки пока еще не было отправлено ни одного ÑообщениÑ, так что архив пуÑÑ‚. Ð’Ñ‹ можете почитать общую информацию об Ñтом ÑпиÑке раÑÑылки.

    - - + + diff --git a/templates/ru/headfoot.html b/templates/ru/headfoot.html index 9b068868..7a58f750 100644 --- a/templates/ru/headfoot.html +++ b/templates/ru/headfoot.html @@ -1,24 +1,86 @@ -Этот текÑÑ‚ может включать Ñтроки +Этот текÑÑ‚ может включать Ñтроки подÑтановки Python (текÑÑ‚ по-английÑки), которые заменÑÑŽÑ‚ÑÑ Ñледующими атрибутами ÑпиÑка раÑÑылки:
      -
    • real_name — "краÑивое" Ð¸Ð¼Ñ ÑпиÑка раÑÑылки; +
    • real_name — "краÑивое" Ð¸Ð¼Ñ ÑпиÑка раÑÑылки; обычно Ñто Ð¸Ð¼Ñ ÑпиÑка раÑÑылки Ñ Ð·Ð°Ð³Ð»Ð°Ð²Ð½Ð¾Ð¹ буквы. -
    • list_name — имÑ, которое иÑпользуетÑÑ Ð² адреÑах +
    • list_name — имÑ, которое иÑпользуетÑÑ Ð² адреÑах Ñтраниц, региÑтр Ñимволов имеет значение. -
    • host_name — полное Ð¸Ð¼Ñ Ð´Ð¾Ð¼ÐµÐ½Ð° (FQDN) ÑпиÑка раÑÑылки. +
    • host_name — полное Ð¸Ð¼Ñ Ð´Ð¾Ð¼ÐµÐ½Ð° (FQDN) ÑпиÑка раÑÑылки. -
    • web_page_url — базовый Ð°Ð´Ñ€ÐµÑ Ð´Ð»Ñ Mailman. Именно к Ñтому +
    • web_page_url — базовый Ð°Ð´Ñ€ÐµÑ Ð´Ð»Ñ Mailman. Именно к Ñтому адреÑу нужно добавлÑть, например, listinfo/%(list_name)s чтобы получить Ð°Ð´Ñ€ÐµÑ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ð¾Ð½Ð½Ð¾Ð¹ Ñтраницы ÑпиÑка раÑÑылки. -
    • description — краткое опиÑание ÑпиÑка раÑÑылки. +
    • description — краткое опиÑание ÑпиÑка раÑÑылки. -
    • info — полное опиÑание ÑпиÑка раÑÑылки. +
    • info — полное опиÑание ÑпиÑка раÑÑылки. -
    • cgiext — раÑширение файлов CGI-Ñценариев. -
    +
  • cgiext — раÑширение файлов CGI-Ñценариев. +
  • diff --git a/templates/ru/listinfo.html b/templates/ru/listinfo.html index 8568c7af..fa4c59e7 100644 --- a/templates/ru/listinfo.html +++ b/templates/ru/listinfo.html @@ -1,133 +1,194 @@ - - - ÐžÐ±Ñ‰Ð°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ ÑпиÑке раÑÑылки <MM-List-Name> - - - -

    -

    - Dezabonarea de la lista - Celelalte abonamente la pe care le aveţi -
    - BifaÅ£i câmpul de confirmare ÅŸi apăsaÅ£i butonul pentru a + + + + - + +
    +

    +Dezabonarea de la lista +Celelalte abonamente la pe care le aveţi +
    + Bifaţi câmpul de confirmare şi apăsaţi butonul pentru a vă dezabona de la această listă. Atenţie: Acţiunea va avea loc imediat!

    -

    +

    PuteÅ£i afiÅŸa toate celelalte liste de discuÅ£ii la la care aveÅ£i abonament. FolosiÅ£i această facilitate pentru a - seta aceleaÅŸi opÅ£iuni personale ÅŸi în cazul celorlalte abonamente. + seta aceleaÅŸi opÅ£iuni personale ÅŸi în cazul celorlalte abonamente.

    -

    -
    - - - - - +
    - Parola -
    - -
    -

    AÅ£i uitat parola?

    -
    + + + - -
    +Parola +
    + +
    +

    AÅ£i uitat parola?

    +
    Apăsaţi acest buton pentru a vă trimite parola la adresa de email abonată. -

    -

    -
    - -
    -

    Modificare parolă

    - - - - - - - - -
    Noua parolă:
    Parola, din nou:
    - - -

    Modifică global -
    -
    - +

    +

    +

    + +
    +

    Modificare parolă

    + + + + + + + + +
    Noua parolă:
    Parola, din nou:
    + +

    Modifică global +
    +

    - - +
    - Opţiunile personale de abonament la lista -
    +
    +Opţiunile personale de abonament la lista +
    -

    Valorile actuale sunt selectate. -

    Unele din opÅ£iuni au o opÅ£iune setare globală. -Bifând această opÅ£iune, modificările vor fi salvate pentru toate +Bifând această opÅ£iune, modificările vor fi salvate pentru toate listele de la la care sunteÅ£i abonat. ApăsaÅ£i pe butonul AfiÅŸează-mi celelalte abonamente de mai sus pentru a vedea toate celelalte liste de discuÅ£ii la care aveÅ£i abonament.

    - -
    - - Livrare mesaje email

    + + - + publicate pe această listă de discuţii.
    Setaţi opţiunea Inactivă + dacă doriţi să rămâneţi abonat, dar nu doriţi să primiţi mesaje o vreme
    + (de ex. când sunteţi în vacanţă).
    Dacă dezactivaţi livrarea de mesaje + nu uitaţi să o reactivaţi la întoarcere; ea nu va fi activată automat. +

    - - - + +

    - - - - - - + + - - + - - - - + + - - + +

    + - - - +

    +
    + +Livrare mesaje email

    Setaţi această opţiune ca fiind Activă pentru a recepţiona mesajele - publicate pe această listă de discuţii.
    SetaÅ£i opÅ£iunea Inactivă - dacă doriÅ£i să rămâneÅ£i abonat, dar nu doriÅ£i să primiÅ£i mesaje o vreme
    - (de ex. când sunteÅ£i în vacanţă).
    Dacă dezactivaÅ£i livrarea de mesaje - nu uitaÅ£i să o reactivaÅ£i la întoarcere; ea nu va fi activată automat. -

    - Activă
    - Inactivă

    - Setează global -

    +Activă
    +Inactivă

    +Setează global +

    - Modul de livrare rezumat

    +

    +Modul de livrare rezumat

    Dacă activaÅ£i modul rezumat (digest), veÅ£i primi mesajele adunate - într-un singur mesaj rezumat
    - (de obicei unul pe zi dar e posibil ÅŸi mai des în cazul listelor foarte aglomerate), - în locul mesajelor individuale.
    + într-un singur mesaj rezumat
    + (de obicei unul pe zi dar e posibil şi mai des în cazul listelor foarte aglomerate), + în locul mesajelor individuale.
    Dacă dezactivaţi livrarea de rezumate, s-ar putea să mai primiţi un ultim rezumat. -

    - Inactiv
    - Activ -
    - Rezumate MIME sau text simplu?

    - Este posibil ca programul dumneavoastră de email să nu suporte rezumatele MIME.
    - În general, rezumatele MIME sunt de preferat, dar dacă aveÅ£i probleme la citirea lor,
    +

    +Inactiv
    +Activ +
    +Rezumate MIME sau text simplu?

    + Este posibil ca programul dumneavoastră de email să nu suporte rezumatele MIME.
    + În general, rezumatele MIME sunt de preferat, dar dacă aveţi probleme la citirea lor,
    alegeţi rezumatele sub formă de text simplu. -

    - MIME
    - Text simplu

    - Setează global -

    +MIME
    +Text simplu

    +Setează global +

    - Doriţi să primiţi duplicate ale mesajelor trimise listei?

    - În mod normal, veÅ£i primi o copie a fiecărui mesaj trimis către listă.
    +

    +Doriţi să primiţi duplicate ale mesajelor trimise listei?

    + În mod normal, veţi primi o copie a fiecărui mesaj trimis către listă.
    Dacă nu doriţi să primiţi aceste mesaje, alegeţi Nu. -

    - Nu
    - Da
    - Doriţi confirmare pentru mesajele trimise?

    -

    - Nu
    - Da
    - Doriţi mesaje de reamintire a parolei pentru această listă?

    +

    +Nu
    +Da
    +Doriţi confirmare pentru mesajele trimise?

    +

    +Nu
    +Da
    +Doriţi mesaje de reamintire a parolei pentru această listă?

    Lunar veţi primi un mesaj ce conţine parola de acces pentru fiecare listă - de la acest server,
    + de la acest server,
    la care aveÅ£i abonament. PuteÅ£i anula această opÅ£iune pentru fiecare listă - în parte, alegând Nu.
    + în parte, alegând Nu.
    Dacă anulaţi mesajele de reamintire a parolei pentru toate listele de pe - acest server la care sunteţi abonat,
    + acest server la care sunteţi abonat,
    nici un mesaj de reamintire nu vă va mai fi trimis. -

    - Nu
    - Da

    - Setează global -

    - Să vă ascundem din lista membrilor?

    - Când cineva afiÅŸează lista membrilor acestei liste, adresa dumneavoastră de email
    - este în mod normal afiÅŸată (într-o formă aparte, pentru a îngreuna munca spammerilor).
    - Dacă doriÅ£i ca adresa dumneavoastră de email să nu apară deloc în lista membrilor,
    +

    +Nu
    +Da

    +Setează global +

    +Să vă ascundem din lista membrilor?

    + Când cineva afişează lista membrilor acestei liste, adresa dumneavoastră de email
    + este în mod normal afişată (într-o formă aparte, pentru a îngreuna munca spammerilor).
    + Dacă doriţi ca adresa dumneavoastră de email să nu apară deloc în lista membrilor,
    alegeţi Da la această opţiune. -

    - Nu
    - Da
    - Ce limbă preferaţi?

    -

    - -
    - La ce topici de discuţie doriţi să vă abonaţi?

    - Alegând una sau mai multe topici, puteÅ£i filtra traficul listei, pentru - a primi doar mesajele de interes.
    +

    +Nu
    +Da
    +Ce limbă preferaţi?

    +

    + +
    +La ce topici de discuţie doriţi să vă abonaţi?

    + Alegând una sau mai multe topici, puteţi filtra traficul listei, pentru + a primi doar mesajele de interes.
    Dacă un mesaj se potriveÅŸte la una sau mai multe topici, veÅ£i primi acel mesaj; - în caz contrar nu-l veÅ£i primi. + în caz contrar nu-l veÅ£i primi.

    Dacă un mesaj aparţine nici unei topici, regula de livrare depinde de - setarea opţiunii de mai jos.
    + setarea opţiunii de mai jos.
    Dacă nu alegeţi nici o topică de interes, veţi primi toate mesajeletrimisei listei de discuţii. -

    - -
    - Doriţi să primiţi mesajele care nu aparţin nici unei topici?

    - Această opţiune are efect doar dacă aţi ales cel puţin o topică mai sus. Ea descrie ce regulă
    - de bază se aplică pentru mesajele care nu îndeplinesc nici una din condiÅ£iile de filtrare
    - după topici. Alegând Nu, dacă mesajul nu se încadrează în nici una din topicile alese,
    - atunci nu veÅ£i primi mesajele, în timp ce dacă alegeÅ£i Da, veÅ£i primi ÅŸi aceste mesaje. +

    + +
    +Doriţi să primiţi mesajele care nu aparţin nici unei topici?

    + Această opţiune are efect doar dacă aţi ales cel puţin o topică mai sus. Ea descrie ce regulă
    + de bază se aplică pentru mesajele care nu îndeplinesc nici una din condiţiile de filtrare
    + după topici. Alegând Nu, dacă mesajul nu se încadrează în nici una din topicile alese,
    + atunci nu veţi primi mesajele, în timp ce dacă alegeţi Da, veţi primi şi aceste mesaje. -

    Dacă nu aţi ales nici o topică de interes mai sus, atunci veţi primi toate
    +

    Dacă nu aţi ales nici o topică de interes mai sus, atunci veţi primi toate
    mesajele trimise listei de discuţii. -

    - Nu
    - Da
    +Nu
    +Da
    +Blochez mesajele duplicate?

    -

    - Blochez mesajele duplicate?

    - - Când sunteÅ£i trecut în mod explicit în câmpurile To: sau Cc: - ale unui mesaj către listă,
    + Când sunteţi trecut în mod explicit în câmpurile To: sau Cc: + ale unui mesaj către listă,
    puteţi opta pentru a nu mai primi o altă copie din - partea listei.
    + partea listei.
    Alegeţi Da pentru a evita primirea de duplicate din partea listei; alegeţi Nu pentru a primi duplicate.

    Dacă lista are activate mesajele personalizate pentru utilizatori, - şi alegeţi să primiţi copii ale mesajelor,
    + şi alegeţi să primiţi copii ale mesajelor,
    fiecare copie va avea un header X-Mailman-Copy: yes ataÅŸat. -

    - Nu
    - Da

    - Setează global -

    -
    -
    +Nu
    +Da

    +Setează global +

    +
    +
    -

    - - - - + + +

    diff --git a/templates/ro/private.html b/templates/ro/private.html index 9d6a3749..bfaa6b63 100755 --- a/templates/ro/private.html +++ b/templates/ro/private.html @@ -1,59 +1,120 @@ - Autentificare la arhivele private %(realname)s - - - - -
    +Autentificare la arhivele private %(realname)s + + + + + %(message)s - - - - - - - - - - - - - - - -
    - Arhivele private %(realname)s - Autentificare -
    Adresa de email:
    Parola:
    -
    -

    Important: De aici înainte trebuie să aveÅ£i - activate cookie-urile în browser; altfel nici o acÅ£iune administrativă + + + + + + + + + + + + + + + +
    +Arhivele private %(realname)s - Autentificare +
    Adresa de email:
    Parola:
    +
    +

    Important: De aici înainte trebuie să aveţi + activate cookie-urile în browser; altfel nici o acţiune administrativă nu va avea efect.

    InterfaÅ£a administrativă Mailman foloseÅŸte cookie-urile de sesiune, - astfel încât nu trebuie să vă re-autentificaÅ£i la fiecare operaÅ£iune + astfel încât nu trebuie să vă re-autentificaÅ£i la fiecare operaÅ£iune administrativă. Acest cookie va expira automat la ieÅŸire, sau explicit, la apăsarea linkului Logout, sub linkul Alte activităţi - administrative (care va apare de îndată ce vă autentificaÅ£i cu succes). + administrative (care va apare de îndată ce vă autentificaÅ£i cu succes).

    - - - - - - + + + +
    - Password Reminder -
    If you don't remember your password, enter your email address + + + + + + - - - - -
    +Password Reminder +
    If you don't remember your password, enter your email address above and click the Remind button and your password will be emailed to you.
    - +
    +

    diff --git a/templates/ro/roster.html b/templates/ro/roster.html index 9e9f9150..b225a425 100644 --- a/templates/ro/roster.html +++ b/templates/ro/roster.html @@ -1,53 +1,114 @@ - - - Abonaţii listei <MM-List-Name> - - - -

    - - - - - - - - - - - - - - - -
    - Abonaţii listei -
    - -

    -

    - -

    Faceţi click pe adresa dumneavoastră pentru a accesa pagina de opţiuni - de abonare.
    (Adresele scrise în paranteză nu primesc mesaje din - din partea listei.)

    -
    -
    - - membri ce primesc mesaje normale de la lista : - -
    -
    -
    - - membri ce primesc doar rezumate zilnice de la lista : - -
    -
    -

    -

    -

    -

    - - - + + +Abonaţii listei <mm-list-name></mm-list-name> + + + +

    + + + + + + + + + + + + + + + +
    +Abonaţii listei +
    +

    +

    +

    Faceţi click pe adresa dumneavoastră pentru a accesa pagina de opţiuni + de abonare.
    (Adresele scrise în paranteză nu primesc mesaje din + din partea listei.)

    +
    +
    + + membri ce primesc mesaje normale de la lista : + +
    +
    +
    + + membri ce primesc doar rezumate zilnice de la lista : + +
    +
    +

    +

    +

    +

    + +

    + diff --git a/templates/ro/subscribe.html b/templates/ro/subscribe.html index a23e21dc..6da96692 100644 --- a/templates/ro/subscribe.html +++ b/templates/ro/subscribe.html @@ -1,10 +1,73 @@ -Rezultatele abonării la <MM-List-Name> - +Rezultatele abonării la <mm-list-name></mm-list-name> + -

    Rezultatele abonării la

    - - - +

    Rezultatele abonării la

    + + + diff --git a/templates/ru/admindbdetails.html b/templates/ru/admindbdetails.html index 5759ea23..0747cd77 100644 --- a/templates/ru/admindbdetails.html +++ b/templates/ru/admindbdetails.html @@ -1,4 +1,67 @@ -ÐдминиÑтративные запроÑÑ‹ предÑтавлены двум ÑпоÑобами: на Ñтранице +ÐдминиÑтративные запроÑÑ‹ предÑтавлены двум ÑпоÑобами: на Ñтранице Ñводный ÑпиÑок и на Ñтранице Ð¿Ð¾Ð´Ñ€Ð¾Ð±Ð½Ð°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ. Ð’ Ñводном ÑпиÑке предÑтавлены ожидающие обработки запроÑÑ‹ на подпиÑку и удаление оной, а также ÑообщениÑ, которые @@ -9,22 +72,21 @@

    Ðа каждой из Ñтраниц Ð’Ñ‹ можете выбрать одно из Ñледующих дейÑтвий:

      -
    • Отложить — отложить решение на потом. ИÑпользуйте Ñто +
    • Отложить — отложить решение на потом. ИÑпользуйте Ñто дейÑтвие, еÑли вы хотите указанно Ñообжение переправить или Ñохранить (Ñм. ниже). -
    • ПринÑть/Одобрить — принÑть Ñообщение и переправить его +
    • ПринÑть/Одобрить — принÑть Ñообщение и переправить его в ÑпиÑок раÑÑылки. Ð”Ð»Ñ Ð·Ð°Ð¿Ñ€Ð¾Ñов об изменении подпиÑки, одобрить их. -
    • Отказать — отвергнуть Ñообщение, отправив отправителю +
    • Отказать — отвергнуть Ñообщение, отправив отправителю уведомление, что его Ñообщение принÑто не было. Само Ñообщение поÑле Ñтого удалÑетÑÑ. -
    • Удалить — удалить указанное Ñообщение без каких-либо +
    • Удалить — удалить указанное Ñообщение без каких-либо извещений отправителю. Данное дейÑтвие может быть полезным в Ñлучае Ñпама. -
    - +

    Параметр Сохранить позволÑет вам Ñохранить копию ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð´Ð»Ñ Ð°Ð´Ð¼Ð¸Ð½Ð¸Ñтратора Ñервера. Это может оказатьÑÑ Ð¿Ð¾Ð»ÐµÐ·Ð½Ñ‹Ð¼ Ð´Ð»Ñ Ñообщений, которые вы хотели бы удалить, но перед Ñтим внимательно его раÑÑмотреть. @@ -59,3 +121,4 @@ вÑе выбранные дейÑÑ‚Ð²Ð¸Ñ Ð±ÑƒÐ´ÑƒÑ‚ выполнены.

    ВерутьÑÑ Ðº Ñводному ÑпиÑку запроÑов. +

    \ No newline at end of file diff --git a/templates/ru/admindbpreamble.html b/templates/ru/admindbpreamble.html index ecb1d0d5..e70af963 100644 --- a/templates/ru/admindbpreamble.html +++ b/templates/ru/admindbpreamble.html @@ -1,4 +1,67 @@ -Эта Ñтраница Ñодержит ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð² ÑпиÑок раÑÑылки %(listname)s, +Эта Ñтраница Ñодержит ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð² ÑпиÑок раÑÑылки %(listname)s, задержанные Ð´Ð»Ñ Ð¿Ñ€Ð¾Ð²ÐµÑ€ÐºÐ¸. Она отображает %(description)s @@ -8,3 +71,4 @@

    Ð’Ñ‹ можете также поÑмотреть Ñводную информацию обо вÑех ожидающих запроÑах. +

    \ No newline at end of file diff --git a/templates/ru/admindbsummary.html b/templates/ru/admindbsummary.html index f67f7c8b..85767855 100644 --- a/templates/ru/admindbsummary.html +++ b/templates/ru/admindbsummary.html @@ -1,4 +1,67 @@ -Эта Ñтраница Ñодержит Ñводный ÑпиÑок требующих обработки админиÑтративных +Эта Ñтраница Ñодержит Ñводный ÑпиÑок требующих обработки админиÑтративных запроÑов Ð´Ð»Ñ ÑпиÑка %(listname)s mailing list. Ð’ начале предÑтавлены запроÑÑ‹ на подпиÑку или удаление оной, за которыми перечиÑлены ÑообщениÑ, требующие обработки (модерирование). @@ -9,3 +72,4 @@

    Ðа Ñтранице Подробный ÑпиÑок запроÑов вы найдете дополнительную информацию о каждом из перечиÑленных ниже Ñообщений. +

    \ No newline at end of file diff --git a/templates/ru/admlogin.html b/templates/ru/admlogin.html index 3188caab..b18acf10 100755 --- a/templates/ru/admlogin.html +++ b/templates/ru/admlogin.html @@ -1,26 +1,89 @@ - ÐÑƒÑ‚ÐµÐ½Ñ‚Ð¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ñ %(who)s ÑпиÑка раÑÑылки %(listname)s +ÐÑƒÑ‚ÐµÐ½Ñ‚Ð¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ñ %(who)s ÑпиÑка раÑÑылки %(listname)s - - -
    + + + %(message)s - - - - - - - - - - - -
    - ÐÑƒÑ‚ÐµÐ½Ñ‚Ð¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ñ %(who)s ÑпиÑка раÑÑылки %(listname)s -
    Введите пароль %(who)s:
    -

    Важно: вы должны разрешить cookies, + + + + + + + + + + + +
    +ÐÑƒÑ‚ÐµÐ½Ñ‚Ð¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ñ %(who)s ÑпиÑка раÑÑылки %(listname)s +
    Введите пароль %(who)s:
    +

    Важно: вы должны разрешить cookies, в противном Ñлучае вы не Ñможете вноÑить какие бы то ни было изменениÑ.

    Файлы cookies иÑпользуютÑÑ Ð´Ð»Ñ Ñ‚Ð¾Ð³Ð¾, чтобы хранить параметры ÑеанÑа работы. @@ -29,6 +92,6 @@ удалена, когда вы закроете броузер. Ð’Ñ‹ также можете Ñвным образом завершить ÑеанÑ, кликнув на ÑÑылку Завершить работу. (Ð’Ñ‹ увидите ее поÑле входа в админиÑтративный интерфейÑ). -

    +

    diff --git a/templates/ru/archidxentry.html b/templates/ru/archidxentry.html index 19232e54..0f442688 100644 --- a/templates/ru/archidxentry.html +++ b/templates/ru/archidxentry.html @@ -1 +1,65 @@ -
  • %(subject)s %(author)s +
  • %(subject)s %(author)s +
  • \ No newline at end of file diff --git a/templates/ru/archidxfoot.html b/templates/ru/archidxfoot.html index 1551cd7a..ebc06f15 100644 --- a/templates/ru/archidxfoot.html +++ b/templates/ru/archidxfoot.html @@ -1,19 +1,83 @@ - -

    - Дата поÑледнего ÑообщениÑ: - %(lastdate)s
    - Ðрхив обновлен: %(archivedate)s -

    -

      -
    • Сортировать по: + +

      +Дата поÑледнего ÑообщениÑ: +%(lastdate)s
      +Ðрхив обновлен: %(archivedate)s +

      +

      -

      -


      - Этот архив был Ñоздан программой Pipermail %(version)s. - - +
    +

    +


    +Этот архив был Ñоздан программой Pipermail %(version)s. + + +

    \ No newline at end of file diff --git a/templates/ru/archidxhead.html b/templates/ru/archidxhead.html index 8272ba63..40e776cf 100644 --- a/templates/ru/archidxhead.html +++ b/templates/ru/archidxhead.html @@ -1,23 +1,88 @@ - - - Ðрхив %(listname)s, том %(archive)s, ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ ÑƒÐ¿Ð¾Ñ€Ñдочены по %(archtype)s - + + + +Ðрхив %(listname)s, том %(archive)s, ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ ÑƒÐ¿Ð¾Ñ€Ñдочены по %(archtype)s + %(encoding)s - - - -

    Том %(archive)s, ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ ÑƒÐ¿Ð¾Ñ€Ñдочены по %(archtype)s

    -
      -
    • Сортировать по: + + + +

      Том %(archive)s, ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ ÑƒÐ¿Ð¾Ñ€Ñдочены по %(archtype)s

      + -

      Первое Ñообщение: %(firstdate)s
      - ПоÑледнее Ñообщение: %(lastdate)s
      - Сообщений: %(size)s

      -

        +
      +

      Первое Ñообщение: %(firstdate)s
      +ПоÑледнее Ñообщение: %(lastdate)s
      +Сообщений: %(size)s

      +

        +

      \ No newline at end of file diff --git a/templates/ru/archlistend.html b/templates/ru/archlistend.html index 9bc052dd..2e1191b0 100644 --- a/templates/ru/archlistend.html +++ b/templates/ru/archlistend.html @@ -1 +1,64 @@ -
    + diff --git a/templates/ru/archliststart.html b/templates/ru/archliststart.html index a4a41ccc..f5618eac 100644 --- a/templates/ru/archliststart.html +++ b/templates/ru/archliststart.html @@ -1,4 +1,68 @@ - - - - +
    Том архиваСортировать по:ВерÑÐ¸Ñ Ð´Ð»Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·ÐºÐ¸
    + + + +
    Том архиваСортировать по:ВерÑÐ¸Ñ Ð´Ð»Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·ÐºÐ¸
    \ No newline at end of file diff --git a/templates/ru/archtoc.html b/templates/ru/archtoc.html index b55437bd..f06102d7 100644 --- a/templates/ru/archtoc.html +++ b/templates/ru/archtoc.html @@ -1,13 +1,77 @@ - - - Ðрхив %(listname)s - + + + +Ðрхив %(listname)s + %(meta)s - - -

    Ðрхив %(listname)s

    -

    ЗдеÑÑŒ вы найдете информацию о ÑпиÑке + + +

    Ðрхив %(listname)s

    +

    ЗдеÑÑŒ вы найдете информацию о ÑпиÑке раÑÑылки. Ð’Ñ‹ также можете загрузить веÑÑŒ архив в формате mbox (%(size)s).

    @@ -15,5 +79,5 @@

    Ðрхив %(listname)s

    %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/ru/archtocentry.html b/templates/ru/archtocentry.html index b8712ea4..04bc73b7 100644 --- a/templates/ru/archtocentry.html +++ b/templates/ru/archtocentry.html @@ -1,12 +1,74 @@ - -
    %(archivelabel)s: - [ по диÑкуÑÑиÑм ] - [ по теме ] - [ по автору ] - [ по дате ] -
    %(archivelabel)s: +[ по диÑкуÑÑиÑм ] +[ по теме ] +[ по автору ] +[ по дате ] +
    - - - - - - - - - - - - - - - - - - + + + + + +

    - -
    -

      -

    О ÑпиÑке раÑÑылки - - -
    -

    -

    Ð”Ð»Ñ Ð¿Ñ€Ð¾Ñмотра Ñообщений, отправленных в лиÑÑ‚ раÑÑылки ранее, Ð’Ñ‹ можете воÑпользоватьÑÑ Ð°Ñ€Ñ…Ð¸Ð²Ð¾Ð¼ Ñообщений . -

    -
    Как пользоватьÑÑ ÑпиÑком раÑÑылки
    + + +ÐžÐ±Ñ‰Ð°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ ÑпиÑке раÑÑылки <mm-list-name></mm-list-name> + + +

    + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + +
    + — +
    +

      +

    О ÑпиÑке раÑÑылки + + +
    +

    +

    Ð”Ð»Ñ Ð¿Ñ€Ð¾Ñмотра Ñообщений, отправленных в лиÑÑ‚ раÑÑылки ранее, Ð’Ñ‹ можете воÑпользоватьÑÑ Ð°Ñ€Ñ…Ð¸Ð²Ð¾Ð¼ Ñообщений . +

    +
    Как пользоватьÑÑ ÑпиÑком раÑÑылки
    Ð”Ð»Ñ Ñ‚Ð¾Ð³Ð¾, чтобы отправить Ñообщение вÑем подпиÑчикам ÑпиÑка раÑÑылки, поÑылайте пиÑьмо - по адреÑу . + по адреÑу .

    Ðиже Ð’Ñ‹ можете подпиÑатьÑÑ Ð½Ð° Ñтот ÑпиÑок раÑÑылки или изменить параметры Ñвоей подпиÑки

    -
    - ПодпиÑатьÑÑ Ð½Ð° ÑпиÑок раÑÑылки -
    -

    - Ð”Ð»Ñ Ñ‚Ð¾Ð³Ð¾, чтобы подпиÑатьÑÑ Ð½Ð° ÑпиÑок раÑÑылки , заполните Ñледующую форму. - -

      - - - - - - - - - - - - + + + + + + - - - - - -
      Ваш Ñлектронный адреÑ: -  
      Ваше Ð¸Ð¼Ñ (необÑзательно): 
      Ð’Ñ‹ можете указать пароль, защищающий параметры Вашей подпиÑки +
      +ПодпиÑатьÑÑ Ð½Ð° ÑпиÑок раÑÑылки +
      +

      + Ð”Ð»Ñ Ñ‚Ð¾Ð³Ð¾, чтобы подпиÑатьÑÑ Ð½Ð° ÑпиÑок раÑÑылки , заполните Ñледующую форму. + +

        + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - -
        Ваш Ñлектронный адреÑ: + 
        Ваше Ð¸Ð¼Ñ (необÑзательно): 
        Ð’Ñ‹ можете указать пароль, защищающий параметры Вашей подпиÑки от неÑанкционированного изменениÑ. Ðе иÑпользуйте важных паролей, так как в ÑиÑтеме Mailman пароли переÑылаютÑÑ Ð¿Ð¾ Ñлектронной почте в открытом виде. -

        ЕÑли Ð’Ñ‹ не хотите указывать пароль, ÑиÑтема ÑоздаÑÑ‚ его автоматичеÑки +

        ЕÑли Ð’Ñ‹ не хотите указывать пароль, ÑиÑтема ÑоздаÑÑ‚ его автоматичеÑки и вышлет на Ваш Ð°Ð´Ñ€ÐµÑ Ð¿Ð¾Ð´Ð¿Ð¸Ñки. Ð’Ñ‹ вÑегда можете инициировать оправку его Вам Ñнова Ñо Ñтраницы параметров подпиÑки. - -
        -
        Укажите пароль: 
        Подтвердите выбранный пароль: 
        Ðа каком Ñзыке Ð’Ñ‹ хотели бы видеть ÑообщениÑ?  
        Хотите ли Ð’Ñ‹ получать подпиÑку в виде дайджеÑтов? Ðет - Да -
        -
        -
        - -
      -
      - - ПодпиÑчики ÑпиÑка раÑÑылки -
      - - - -

      - - - -

      - - - + + +
    Укажите пароль: 
    Подтвердите выбранный пароль: 
    Ðа каком Ñзыке Ð’Ñ‹ хотели бы видеть ÑообщениÑ?  
    Хотите ли Ð’Ñ‹ получать подпиÑку в виде дайджеÑтов? Ðет + Да +
    +
    +
    + + +

    + +ПодпиÑчики ÑпиÑка раÑÑылки +
    + + + +

    + + + +

    + +

    + diff --git a/templates/ru/options.html b/templates/ru/options.html index 3cb37cc9..4fed0e96 100644 --- a/templates/ru/options.html +++ b/templates/ru/options.html @@ -1,42 +1,102 @@ - - <MM-Presentable-User>: параметры подпиÑки на ÑпиÑок раÑÑылки <MM-List-Name> - - - - - -
    - - Параметры подпиÑки Ð´Ð»Ñ ÑпиÑка раÑÑылки -
    + +<mm-presentable-user>: параметры подпиÑки на ÑпиÑок раÑÑылки <mm-list-name> +</mm-list-name></mm-presentable-user> + + + + +
    + + Параметры подпиÑки Ð´Ð»Ñ ÑпиÑка раÑÑылки +

    - - - - - +
    - СоÑтоÑние подпиÑки , - пароль и параметры подпиÑки на ÑпиÑок раÑÑылки . -
    - - - - -

    -

    + + + +
    + СоÑтоÑние подпиÑки , + пароль и параметры подпиÑки на ÑпиÑок раÑÑылки . +
    + + +

    +

    - - +

    - - - +
    - - Изменение параметров Вашей подпиÑки на ÑпиÑок раÑÑылки -
    Ð’Ñ‹ можете изменить адреÑ, на который должны доÑтавлÑтьÑÑ ÑообщениÑ. + + + - - - - - -
    + +Изменение параметров Вашей подпиÑки на ÑпиÑок раÑÑылки +
    Ð’Ñ‹ можете изменить адреÑ, на который должны доÑтавлÑтьÑÑ ÑообщениÑ. Ð”Ð»Ñ Ñтого введите его в поле ниже. Имейте в виду, что на новый Ð°Ð´Ñ€ÐµÑ Ð±ÑƒÐ´ÐµÑ‚ выÑлана проÑьба подтвердить Ñту операцию и что без Ñтого Ð¿Ð¾Ð´Ñ‚Ð²ÐµÑ€Ð¶Ð´ÐµÐ½Ð¸Ñ Ð°Ð´Ñ€ÐµÑ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½ не будет. @@ -50,240 +110,211 @@ ваших подпиÑках на Ñервере , Ð’Ñ‹ можете воÑпользоватьÑÑ Ð¿Ð°Ñ€Ð°Ð¼ÐµÑ‚Ñ€Ð¾Ð¼ Изменить во вÑех подпиÑках. -
    - - - - - - - -
    Ðовый адреÑ:
    Еще раз новый адреÑ:
    -
    - - - - -
    Ваше Ð¸Ð¼Ñ (необÑзательно):
    -
    -

    Изменить во вÑех подпиÑках

    - +

    + + + + + + + +
    Ðовый адреÑ:
    Еще раз новый адреÑ:
    +

    + + + + +
    Ваше Ð¸Ð¼Ñ (необÑзательно):
    + +
    +

    Изменить во вÑех подпиÑках

    +

    - - - - - - - - + + + + %(textlink)s + \ No newline at end of file diff --git a/templates/sk/archtocnombox.html b/templates/sk/archtocnombox.html index 546f942a..c353f1f6 100644 --- a/templates/sk/archtocnombox.html +++ b/templates/sk/archtocnombox.html @@ -1,18 +1,82 @@ - - - Archív konferencie %(listname)s - + + + +Archív konferencie %(listname)s + %(meta)s - - -

    Archív konferencie %(listname)s

    -

    - Viac informácií o konferencii. + + +

    Archív konferencie %(listname)s

    +

    + Viac informácií o konferencii.

    %(noarchive_msg)s %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/sk/article.html b/templates/sk/article.html index ed0b1654..eed67e20 100644 --- a/templates/sk/article.html +++ b/templates/sk/article.html @@ -1,12 +1,13 @@ - - - %(title)s - - - - - + +

    Archív konferencie %(listname)s

    +

    + Do tejto konferencie nebol zatial zaslaný žiaden príspevok. + Preto je tento archív prázdny. Viac informácií o konferencii + získate na informaÄnej stránke..

    - - + + diff --git a/templates/sk/headfoot.html b/templates/sk/headfoot.html index f8a1077d..af842b31 100644 --- a/templates/sk/headfoot.html +++ b/templates/sk/headfoot.html @@ -1,30 +1,91 @@ -Tento text môže obsahovaÅ¥ formátovacie -príkazy jazyka Python, ktoré sa nahradia príslušnými -hodnotami pri vytváraní stránky. Nejdôležitejšie premenné sú: +Tento text môže obsahovaÅ¥ formátovacie +príkazy jazyka Python, ktoré sa nahradia prísluÅ¡nými +hodnotami pri vytváraní stránky. NejdôležitejÅ¡ie premenné sú:
      -
    • real_name - `Pekné' meno konferencie, - bežne názov, ktorý má zachovanú veľkosÅ¥ písmen (definované +
    • real_name - `Pekné' meno konferencie, + bežne názov, ktorý má zachovanú veľkosÅ¥ písmen (definované v nastaveniach). -
    • list_name - Meno konferencie, ako sa používa v URL - kde záleží na velikosti písmen (všetky písmená sú prevedené na malé). +
    • list_name - Meno konferencie, ako sa používa v URL + kde záleží na velikosti písmen (vÅ¡etky písmená sú prevedené na malé). -
    • host_name - Meno serveru (FQDN), na ktorom beží +
    • host_name - Meno serveru (FQDN), na ktorom beží konferencia. -
    • web_page_url - Základné URL pre Mailman. - Ak je použité napr. v tomto príkaze: +
    • web_page_url - Základné URL pre Mailman. + Ak je použité napr. v tomto príkaze: listinfo/%(list_name)s - ukazuje na stránku s informáciami o konferencii. + ukazuje na stránku s informáciami o konferencii. -
    • description - Krátky, charakteristický - popis konferencie (definovaný v nastaveniach). +
    • description - Krátky, charakteristický + popis konferencie (definovaný v nastaveniach). -
    • info - Kompletný, dlhý popis konferencie. +
    • info - Kompletný, dlhý popis konferencie. -
    • cgiext - prípona pridávaná ku CGI skriptom. +
    • cgiext - prípona pridávaná ku CGI skriptom. -
    + diff --git a/templates/sk/listinfo.html b/templates/sk/listinfo.html index b89d0a19..809e9b8b 100644 --- a/templates/sk/listinfo.html +++ b/templates/sk/listinfo.html @@ -1,151 +1,210 @@ - - - - Informácie ku konferencii <MM-List-Name> - - - - -

    -

    - Удалить подпиÑку на - Другие подпиÑки на Ñервере -
    + + + + - + +
    +

    +Удалить подпиÑку на +Другие подпиÑки на Ñервере +
    Кликните Ñ‡ÐµÐºÐ±Ð¾ÐºÑ Ð´Ð»Ñ Ð¿Ð¾Ð´Ñ‚Ð²ÐµÑ€Ð¶Ð´ÐµÐ½Ð¸Ñ Ð½Ð°Ð¼ÐµÑ€ÐµÐ½Ð¸Ñ Ð¾Ñ‚Ð¿Ð¸Ñки и нажмите на Ñту кнопку Ð´Ð»Ñ ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ Ð’Ð°ÑˆÐµÐ¹ подпиÑки. Предупреждение: Это дейÑтвие будет иметь немедленный Ñффект!

    -

    Ð’Ñ‹ также можете получить ÑпиÑок вÑех Ñвоих подпиÑок на Ñервере +

    Ð’Ñ‹ также можете получить ÑпиÑок вÑех Ñвоих подпиÑок на Ñервере ; еÑли Ð’Ñ‹ хотите изменить параметры какой-либо из них, нажмите на Ñту кнопку.

    -

    -
    - - - - - +
    - Пароль Ð´Ð»Ñ ÑпиÑка раÑÑылки -
    - -

    Забыли Ñвой пароль?

    + + + - -
    +Пароль Ð´Ð»Ñ ÑпиÑка раÑÑылки +
    + +

    Забыли Ñвой пароль?

    Ðажмите Ñту кнопку, чтобы получить пароль на Ð°Ð´Ñ€ÐµÑ Ð’Ð°ÑˆÐµÐ¹ подпиÑки. -

    -

    - -
    -
    - -
    -

    Изменить пароль

    - - - - - - - - -
    Ðовый пароль:
    Подтвердите новый пароль:
    - - -

    Изменить во вÑех подпиÑках. -
    -
    - +

    +

    + +
    +

    + +
    +

    Изменить пароль

    + + + + + + + + +
    Ðовый пароль:
    Подтвердите новый пароль:
    + +

    Изменить во вÑех подпиÑках. +
    +

    - - +
    - Параметры Вашей подпиÑки на -
    +
    +Параметры Вашей подпиÑки на +
    -

    Текущие Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ Ð¾Ñ‚Ð¼ÐµÑ‡ÐµÐ½Ñ‹ -

    Обратите внимание, что Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ Ð½ÐµÐºÐ¾Ñ‚Ð¾Ñ€Ñ‹Ñ… параметров вы можете Изменить во вÑех подпиÑках. УÑтановка Ñтого чекбокÑа приведет к тому, что Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð±ÑƒÐ´ÑƒÑ‚ Ñделаны Ð´Ð»Ñ Ð²Ñех подпиÑок на Ñервере . Их ÑпиÑок можно получить при помощи кнопки СпиÑок моих подпиÑок, раÑположенной выше на Ñтой Ñтранице.

    - -
    - - ДоÑтавка Ñообщений

    + + - +

    - - - + +

    - - - - - - + + - - + - - - - + + - - + - - - + + - - - - +

    + +
    + +ДоÑтавка Ñообщений

    Выберите Включена чтобы получать ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð¸Ð· Ñтого ÑпиÑка раÑÑылки. Выберите Отключена еÑли Ð’Ñ‹ хотите временно приоÑтановить получение Ñообщений (например, на Ð²Ñ€ÐµÐ¼Ñ Ð¾Ñ‚Ð¿ÑƒÑка). ЕÑли Ð’Ñ‹ отключили доÑтавку Ñообщений, не забудьте затем включить ее обратно, Ñто не проиÑходит автоматичеÑки. -

    - Включена
    - Отключена

    - Изменить во вÑех подпиÑках -

    +Включена
    +Отключена

    +Изменить во вÑех подпиÑках +

    - Режим дайджеÑта

    +

    +Режим дайджеÑта

    ЕÑли Ð’Ñ‹ включите Ñтот режим, то будете получать ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð² виде дайджеÑта (обычно одного в день, или больше Ð´Ð»Ñ Ð¾Ñ‡ÐµÐ½ÑŒ активных ÑпиÑков раÑÑылки) вмеÑто каждого ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð¾Ñ‚Ð´ÐµÐ»ÑŒÐ½Ð¾. ПоÑле Ð²Ñ‹ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ñтого режима Ð’Ñ‹ можете получить еще один поÑледний дайджеÑÑ‚. -

    - Откл.
    - Вкл. -
    - ПриÑылать дайджеÑты в формате MIME или Plain Text?

    +

    +Откл.
    +Вкл. +
    +ПриÑылать дайджеÑты в формате MIME или Plain Text?

    Ваша Ð¿Ð¾Ñ‡Ñ‚Ð¾Ð²Ð°Ñ Ð¿Ñ€Ð¾Ð³Ñ€Ð°Ð¼Ð¼Ð° может поддерживать дайджеÑты в формате MIME или нет. Обычно формат MIME предпочтительнее, но еÑли дайджеÑты отображаютÑÑ Ð½ÐµÐ¿Ñ€Ð°Ð²Ð¸Ð»ÑŒÐ½Ð¾, выберите формат Plain Text. -

    - MIME
    - Plain Text

    - Изменить во вÑех подпиÑках -

    +MIME
    +Plain Text

    +Изменить во вÑех подпиÑках +

    - Получать Ñвои ÑобÑтвенные ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð² ÑпиÑок раÑÑылки?

    +

    +Получать Ñвои ÑобÑтвенные ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð² ÑпиÑок раÑÑылки?

    Ð’ общем Ñлучае Ð’Ñ‹ будете получать копию каждого Вашего ÑообщениÑ, отправленного в ÑпиÑок раÑÑылки. ЕÑли Ð’Ñ‹ Ñтого не хотите, выберите вариант Ðет. -

    - Ðет
    - Да -
    - Получать пиÑьмо-подтверждение об отправке ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð² ÑпиÑок раÑÑылки?

    -

    - Ðет
    - Да -
    - Получать ежемеÑÑчные Ð½Ð°Ð¿Ð¾Ð¼Ð¸Ð½Ð°Ð½Ð¸Ñ Ð¿Ð°Ñ€Ð¾Ð»Ñ?

    +

    +Ðет
    +Да +
    +Получать пиÑьмо-подтверждение об отправке ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð² ÑпиÑок раÑÑылки?

    +

    +Ðет
    +Да +
    +Получать ежемеÑÑчные Ð½Ð°Ð¿Ð¾Ð¼Ð¸Ð½Ð°Ð½Ð¸Ñ Ð¿Ð°Ñ€Ð¾Ð»Ñ?

    Один раз в меÑÑц Ð’Ñ‹ будете получать пиÑьмо, Ñодержащее пароль Ð´Ð»Ñ ÐºÐ°Ð¶Ð´Ð¾Ð³Ð¾ ÑпиÑка раÑÑылки на Ñтом Ñервере, на который Ð’Ñ‹ подпиÑаны. Ð’Ñ‹ можете отключить Ñти Ð½Ð°Ð¿Ð¾Ð¼Ð¸Ð½Ð°Ð½Ð¸Ñ Ð¾Ñ‚Ð´ÐµÐ»ÑŒÐ½Ð¾ Ð´Ð»Ñ Ñтого ÑпиÑка раÑÑылки или же Ð´Ð»Ñ Ð²Ñех подпиÑок Ñразу. -

    - Ðет
    - Да

    - Изменить во вÑех подпиÑках -

    - Скрыть ÑÐµÐ±Ñ Ð¸Ð· ÑпиÑка подпиÑчиков?

    +

    +Ðет
    +Да

    +Изменить во вÑех подпиÑках +

    +Скрыть ÑÐµÐ±Ñ Ð¸Ð· ÑпиÑка подпиÑчиков?

    Ðа Ñтранице проÑмотра ÑпиÑка подпиÑчиков Ваш Ñлектронный Ð°Ð´Ñ€ÐµÑ Ð±ÑƒÐ´ÐµÑ‚ показан в Ñпециальном виде, чтобы затруднить работу Ñборщиков адреÑов Ð´Ð»Ñ Ñ€Ð°ÑÑылки Ñпама. ЕÑли Ð’Ñ‹ не хотите, чтобы Ваш Ð°Ð´Ñ€ÐµÑ Ð²Ð¾Ð¾Ð±Ñ‰Ðµ там показывалÑÑ, выберите значение Да. -

    - Ðет
    - Да -
    - Какой Ñзык Ð’Ñ‹ предпочитаете?

    -

    - -
    - Ðа какие тематичеÑкие категории Ð’Ñ‹ хотели бы подпиÑатьÑÑ?

    +

    +Ðет
    +Да +
    +Какой Ñзык Ð’Ñ‹ предпочитаете?

    +

    + +
    +Ðа какие тематичеÑкие категории Ð’Ñ‹ хотели бы подпиÑатьÑÑ?

    Выбрав одну или неÑколько категорий, Ð’Ñ‹ можете отфильтровать ненужные ÑообщениÑ, Ð¿Ð¾Ð»ÑƒÑ‡Ð°Ñ Ñ‚Ð¾Ð»ÑŒÐºÐ¾ те из них, что ÑоответÑтвуют выбранным категориÑм.

    ЕÑли Ð’Ñ‹ не выбрали ни одну из предÑтавленных категорий, Ð’Ñ‹ будете получать вÑе ÑообщениÑ. -

    - -
    - Хотите ли Ð’Ñ‹ получать ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ ÐºÐ¾Ñ‚Ð¾Ñ€Ñ‹Ðµ не отноÑÑÑ‚ÑÑ Ð½Ð¸ к одной категории? - -

    Этот параметр дейÑтвует только в Ñлучае выбора Ñ…Ð¾Ñ‚Ñ Ð±Ñ‹ одной категории +

    + +
    +Хотите ли Ð’Ñ‹ получать ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ ÐºÐ¾Ñ‚Ð¾Ñ€Ñ‹Ðµ не отноÑÑÑ‚ÑÑ Ð½Ð¸ к одной категории? +

    Этот параметр дейÑтвует только в Ñлучае выбора Ñ…Ð¾Ñ‚Ñ Ð±Ñ‹ одной категории в поле выше. ЕÑли Ð’Ñ‹ выберете Ðет, то не будете получать ÑообщениÑ, которые не отноÑÑÑ‚ÑÑ Ð½Ð¸ к одной категории. Ð’ Ñлучае Да такие ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð±ÑƒÐ´ÑƒÑ‚ Вам переÑылатьÑÑ.

    ЕÑли в поле выше не выбрано ни одной категории, то Ð’Ñ‹ будете получать вÑе ÑообщениÑ. -

    - Ðет
    - Да -
    - Блокировать отправку неÑкольких копий ÑообщениÑ? - -

    Ð’Ñ‹ можете избежать Ð¿Ð¾Ð»ÑƒÑ‡ÐµÐ½Ð¸Ñ Ð´ÑƒÐ±Ð»Ð¸Ñ€ÑƒÑŽÑ‰Ð¸Ñ… друг друга Ñообщений, в которых Ваш Ð°Ð´Ñ€ÐµÑ ÑƒÐºÐ°Ð·Ð°Ð½ +

    +Ðет
    +Да +
    +Блокировать отправку неÑкольких копий ÑообщениÑ? +

    Ð’Ñ‹ можете избежать Ð¿Ð¾Ð»ÑƒÑ‡ÐµÐ½Ð¸Ñ Ð´ÑƒÐ±Ð»Ð¸Ñ€ÑƒÑŽÑ‰Ð¸Ñ… друг друга Ñообщений, в которых Ваш Ð°Ð´Ñ€ÐµÑ ÑƒÐºÐ°Ð·Ð°Ð½ в поле To: или Cc:. Ð”Ð»Ñ Ñтого выберите Да. При выборе Ðет Ð’Ñ‹ будете получать копии таких Ñообщений.

    ЕÑли Ð´Ð»Ñ ÑпиÑка раÑÑылки активирован режим перÑонализации Ñообщений, и Ð’Ñ‹ выбрали Ðет чтобы получать копии, то ÐºÐ°Ð¶Ð´Ð°Ñ Ð¸Ð· них будет иметь заголовок X-Mailman-Copy: yes. -

    - Ðет
    - Да

    - Изменить во вÑех подпиÑках -

    -
    -
    +Ðет
    +Да

    +Изменить во вÑех подпиÑках +

    +
    +
    -

    - - - - + + +

    diff --git a/templates/ru/private.html b/templates/ru/private.html index f289ba3f..d36837ab 100755 --- a/templates/ru/private.html +++ b/templates/ru/private.html @@ -1,31 +1,94 @@ - ÐÑƒÑ‚ÐµÐ½Ñ‚Ð¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ñ Ð´Ð»Ñ Ð´Ð¾Ñтупа к закрытому архиву ÑпиÑка раÑÑылки %(realname)s +ÐÑƒÑ‚ÐµÐ½Ñ‚Ð¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ñ Ð´Ð»Ñ Ð´Ð¾Ñтупа к закрытому архиву ÑпиÑка раÑÑылки %(realname)s - - -
    + + + %(message)s - - - - - - - - - - - - - - - -
    - ÐÑƒÑ‚ÐµÐ½Ñ‚Ð¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ñ Ð´Ð»Ñ Ð´Ð¾Ñтупа к закрытому архиву ÑпиÑка раÑÑылки %(realname)s -
    Электронный адреÑ:
    Пароль:
    -
    -

    Важно: вы должны разрешить прием файла cookies, + + + + + + + + + + + + + + + +
    +ÐÑƒÑ‚ÐµÐ½Ñ‚Ð¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ñ Ð´Ð»Ñ Ð´Ð¾Ñтупа к закрытому архиву ÑпиÑка раÑÑылки %(realname)s +
    Электронный адреÑ:
    Пароль:
    +
    +

    Важно: вы должны разрешить прием файла cookies, в противном Ñлучае, вам придетÑÑ Ð¿Ð¾Ð´Ñ‚Ð²ÐµÑ€Ð¶Ð´Ð°Ñ‚ÑŒ каждое дейÑтвие.

    Файл cookies иÑпользуетÑÑ Ð´Ð»Ñ Ñ‚Ð¾Ð³Ð¾, чтобы хранить параметры ÑеанÑа работы. @@ -36,21 +99,21 @@ параметров вашей подпиÑки и щелкнув на кнопку ОтключитьÑÑ.

    - - - - - - + + + +
    - Password Reminder -
    If you don't remember your password, enter your email address + + + + + + - - - - -
    +Password Reminder +
    If you don't remember your password, enter your email address above and click the Remind button and your password will be emailed to you.
    - +
    +

    diff --git a/templates/ru/roster.html b/templates/ru/roster.html index 0b39942d..93191f6c 100644 --- a/templates/ru/roster.html +++ b/templates/ru/roster.html @@ -1,47 +1,108 @@ - - - ПодпиÑчики ÑпиÑка раÑÑылки <MM-List-Name> - - -

    - - - - - - - - - - - - - - - + + +ПодпиÑчики ÑпиÑка раÑÑылки <mm-list-name></mm-list-name> + + +

    +

    - ПодпиÑчики ÑпиÑка раÑÑылки -
    - -

    -

    - -

    Ð”Ð»Ñ Ð¿ÐµÑ€ÐµÑ…Ð¾Ð´Ð° на Ñтраницу параметров Вашей подпиÑки нажмите - на ÑÑылку Ñ Ð’Ð°ÑˆÐ¸Ð¼ Ñлектронным адреÑом.
    - (ДоÑтавка Ñообщений пользователÑм, указанным в Ñкобках, в наÑтоÑщий момент отключена.)

    -
    -
    - ПодпиÑчики Ñ Ð¾Ð±Ñ‹Ñ‡Ð½Ð¾Ð¹ доÑтавкой: -
    -
    -
    - ПодпиÑчики , получающие дайджеÑты: -
    -
    -

    -

    -

    -

    + + + + + + + + + + + + + +
    +ПодпиÑчики ÑпиÑка раÑÑылки +
    +

    +

    +

    Ð”Ð»Ñ Ð¿ÐµÑ€ÐµÑ…Ð¾Ð´Ð° на Ñтраницу параметров Вашей подпиÑки нажмите + на ÑÑылку Ñ Ð’Ð°ÑˆÐ¸Ð¼ Ñлектронным адреÑом.
    +(ДоÑтавка Ñообщений пользователÑм, указанным в Ñкобках, в наÑтоÑщий момент отключена.)

    +
    +
    +ПодпиÑчики Ñ Ð¾Ð±Ñ‹Ñ‡Ð½Ð¾Ð¹ доÑтавкой: +
    +
    +
    +ПодпиÑчики , получающие дайджеÑты: +
    +
    +

    +

    +

    +

    - - - + +

    + diff --git a/templates/ru/subscribe.html b/templates/ru/subscribe.html index 1f3003b6..e0664fa3 100644 --- a/templates/ru/subscribe.html +++ b/templates/ru/subscribe.html @@ -1,8 +1,71 @@ -Результаты подпиÑки на ÑпиÑок раÑÑылки <MM-List-Name> +Результаты подпиÑки на ÑпиÑок раÑÑылки <mm-list-name></mm-list-name> -

    Результаты подпиÑки на ÑпиÑок раÑÑылки

    - - - +

    Результаты подпиÑки на ÑпиÑок раÑÑылки

    + + + diff --git a/templates/sk/admindbdetails.html b/templates/sk/admindbdetails.html index 850a83d7..5484d146 100644 --- a/templates/sk/admindbdetails.html +++ b/templates/sk/admindbdetails.html @@ -1,62 +1,125 @@ -Administratívne požiadavky môžu byÅ¥ zobrazené dvoma spôsobmi. BuÄ -hromadne alebo detailne. -Pri hromadnom zobrazení sú všetky požiadavky pochádzajúce od -toho istého úÄastníka zobrazené spolu a môže o nich byÅ¥ rozhodnuté -naraz. Pri detailnom zobrazení je každý príspevok zobrazený -samostatne a administrátor rozhoduje o každom príspevku jednotlivo. -Detailné zobrazenie obsahuje naviac všetky hlaviÄky a ÄasÅ¥ z tela -správy. - -

    Pre pozastavené príspevky vyberte jednu z týchto Äinností: +Administratívne požiadavky môžu byÅ¥ zobrazené dvoma spôsobmi. BuÄ +hromadne alebo detailne. +Pri hromadnom zobrazení sú vÅ¡etky požiadavky pochádzajúce od +toho istého úÄastníka zobrazené spolu a môže o nich byÅ¥ rozhodnuté +naraz. Pri detailnom zobrazení je každý príspevok zobrazený +samostatne a administrátor rozhoduje o každom príspevku jednotlivo. +Detailné zobrazenie obsahuje naviac vÅ¡etky hlaviÄky a ÄasÅ¥ z tela +správy. + +

    Pre pozastavené príspevky vyberte jednu z týchto Äinností:

      -
    • PonechaÅ¥ - NechaÅ¥ správu ÄakaÅ¥ vo fronte, nemá vplyv na -nastavenie preposielania, to znamená, že správu môžete preposlaÅ¥ +
    • PonechaÅ¥ - NechaÅ¥ správu ÄakaÅ¥ vo fronte, nemá vplyv na +nastavenie preposielania, to znamená, že správu môžete preposlaÅ¥ a pritom ponechaÅ¥ vo fronte -
    • AkceptovaÅ¥ - RozoslaÅ¥ správu do konferencie. Ak nejde o správu, -ale o žiadosÅ¥ o prihlásenie úÄastníka, tak bude prihlásený. - -
    • ZamietnuÅ¥ - VrátiÅ¥ správu odosielateľovi. Môžete uviesÅ¥ i dôvod, -ktorý bude uvedený v odpovedi na zamietnutie. Správa bude vymazaná z fronty. -Ak ide o žiadosÅ¥ o prihlásenie úÄastníka, tak bude zamietnutá. - -
    • ZahodiÅ¥ - vhodné pre spam. Správa bude potichu odstránená, nikomu -nebude niÄ poslané. Ak ide o žiadosÅ¥ o prihlásenie úÄastníka, tak mu nebude -odoslaná odpoveÄ. -
    - -

    Výberom voľby PonechaÅ¥ môžete ponechaÅ¥ správu vo fronte pre -pre neskoršie vybavenie správcom. To sa využíva predovšetkým pri správach, -ktoré nie sú spamom a vyžadujú Äalšie odvetné akcie. - -S pomocou voľby PreposlaÅ¥ môžete odoslaÅ¥ kópiu správy na adresu, -ktorú vyplníte do poľa vedľa tejto voľby. Ak potrebujete -niektorý príspevok pre rozoslaním zmeniÅ¥, musíte si ho preposlaÅ¥ na svoju -adresu a pôvodnú správu zahodiÅ¥. Potom, Äo správu upravíte, musíte ju poslaÅ¥ -späÅ¥ do konferencie, prípadne s hlaviÄkou Approved:, do ktorej -doplníte heslo pre danú konferenciu. Etika vyžaduje, aby ste v takom prípade -do textu uviedli upozornenie, že ste správu pozmenili a vysvetlili, preÄo +

  • AkceptovaÅ¥ - RozoslaÅ¥ správu do konferencie. Ak nejde o správu, +ale o žiadosÅ¥ o prihlásenie úÄastníka, tak bude prihlásený. + +
  • ZamietnuÅ¥ - VrátiÅ¥ správu odosielateľovi. Môžete uviesÅ¥ i dôvod, +ktorý bude uvedený v odpovedi na zamietnutie. Správa bude vymazaná z fronty. +Ak ide o žiadosÅ¥ o prihlásenie úÄastníka, tak bude zamietnutá. + +
  • ZahodiÅ¥ - vhodné pre spam. Správa bude potichu odstránená, nikomu +nebude niÄ poslané. Ak ide o žiadosÅ¥ o prihlásenie úÄastníka, tak mu nebude +odoslaná odpoveÄ. +
  • +

    Výberom voľby PonechaÅ¥ môžete ponechaÅ¥ správu vo fronte pre +pre neskorÅ¡ie vybavenie správcom. To sa využíva predovÅ¡etkým pri správach, +ktoré nie sú spamom a vyžadujú ÄalÅ¡ie odvetné akcie. + +S pomocou voľby PreposlaÅ¥ môžete odoslaÅ¥ kópiu správy na adresu, +ktorú vyplníte do poľa vedľa tejto voľby. Ak potrebujete +niektorý príspevok pre rozoslaním zmeniÅ¥, musíte si ho preposlaÅ¥ na svoju +adresu a pôvodnú správu zahodiÅ¥. Potom, Äo správu upravíte, musíte ju poslaÅ¥ +späť do konferencie, prípadne s hlaviÄkou Approved:, do ktorej +doplníte heslo pre danú konferenciu. Etika vyžaduje, aby ste v takom prípade +do textu uviedli upozornenie, že ste správu pozmenili a vysvetlili, preÄo ste tak konali. -

    Pri nových úÄastníkoch sa zvykne nastaviÅ¥ moderácia, aby ich -príspevky boli najprv preverené moderátorom. Zrušením nastavenia moderácie -umožníte úÄastníkovi prispievaÅ¥ do konferencie bez kontroly jeho príspevkov. +

    Pri nových úÄastníkoch sa zvykne nastaviÅ¥ moderácia, aby ich +príspevky boli najprv preverené moderátorom. ZruÅ¡ením nastavenia moderácie +umožníte úÄastníkovi prispievaÅ¥ do konferencie bez kontroly jeho príspevkov. -

    Ak odosielateľ nie je úÄastníkom konferencie, môžete jeho e-mailovú -adresu vložiÅ¥ do filtra odesielateľov. Funkcia týchto filtrov -je popísaná na stránke s konfiguráciou filtrovania -. Príspevky môžu byÅ¥ na základe filtrov buÄ automaticky -akceptované, automaticky podržané do rozhodnutia moderátora, -automaticky vrátené alebo automaticky zahodené (spam). Ak je -odosielateľ úÄastníkom alebo je už zahrnutý vo filtroch, táto voľba -nebude k dispozícii. +

    Ak odosielateľ nie je úÄastníkom konferencie, môžete jeho e-mailovú +adresu vložiÅ¥ do filtra odesielateľov. Funkcia týchto filtrov +je popísaná na stránke s konfiguráciou filtrovania +. Príspevky môžu byÅ¥ na základe filtrov buÄ automaticky +akceptované, automaticky podržané do rozhodnutia moderátora, +automaticky vrátené alebo automaticky zahodené (spam). Ak je +odosielateľ úÄastníkom alebo je už zahrnutý vo filtroch, táto voľba +nebude k dispozícii. -

    Ak ste už prešli celou stránkou, kliknite na jej hornom alebo -spodnom okraji na tlaÄidlo PotvrdiÅ¥ všetky zmeny. Tým sa -prenesú prevedené zmeny do databázý a server prevedie podľa daných pokyov -potrebné operácie. Ak u niektorej položky nevyberiete ani jednu voľbu, -nebude nijak ovplyvnená a zostane uložena v databáze. Budete ju môcÅ¥ -kedykoľvek nabudúce zmeniÅ¥. +

    Ak ste už preÅ¡li celou stránkou, kliknite na jej hornom alebo +spodnom okraji na tlaÄidlo PotvrdiÅ¥ vÅ¡etky zmeny. Tým sa +prenesú prevedené zmeny do databázý a server prevedie podľa daných pokyov +potrebné operácie. Ak u niektorej položky nevyberiete ani jednu voľbu, +nebude nijak ovplyvnená a zostane uložena v databáze. Budete ju môcÅ¥ +kedykoľvek nabudúce zmeniÅ¥. -

    Návrat na prehľad Äakajúcích požiadaviek +

    Návrat na prehľad Äakajúcích požiadaviek +

    \ No newline at end of file diff --git a/templates/sk/admindbpreamble.html b/templates/sk/admindbpreamble.html index 3ae12dcc..3b7b4d7e 100644 --- a/templates/sk/admindbpreamble.html +++ b/templates/sk/admindbpreamble.html @@ -1,14 +1,78 @@ -Na tejto stránke nájdete všetky príspevky do konferencie %(listname)s, -ktoré neboli z nejakého dôvodu rozoslané a Äakajú na Vaše rozhodnutie. -Momentálne sú zobrazené %(description)s +Na tejto stránke nájdete vÅ¡etky príspevky do konferencie %(listname)s, +ktoré neboli z nejakého dôvodu rozoslané a Äakajú na VaÅ¡e rozhodnutie. +Momentálne sú zobrazené %(description)s -

    U každej administratívnej požiadavky musíte vybraÅ¥ ÄinnosÅ¥, ktorá sa -s ňou má previesÅ¥ a nakoniec vybrané Äinnosti uskutoÄníte kliknutím na -PotvrdiÅ¥ všetky zmeny. Podrobnejšie informácie o tom, ako -zaobchádzaÅ¥ s administratívnym rozhraním nájdete -pod týmto odkazom. +

    U každej administratívnej požiadavky musíte vybraÅ¥ ÄinnosÅ¥, ktorá sa +s ňou má previesÅ¥ a nakoniec vybrané Äinnosti uskutoÄníte kliknutím na +PotvrdiÅ¥ vÅ¡etky zmeny. PodrobnejÅ¡ie informácie o tom, ako +zaobchádzaÅ¥ s administratívnym rozhraním nájdete +pod týmto odkazom. -

    Tiež si môžete prezrieÅ¥ prehľad všetkých -Äakajúcich požiadaviek zobrazený tak, že -príspevky od jedného prispievateľa sú zlúÄené dolopy a môžete -o nich rozhodnúÅ¥ naraz. +

    Tiež si môžete prezrieÅ¥ prehľad vÅ¡etkých +Äakajúcich požiadaviek zobrazený tak, že +príspevky od jedného prispievateľa sú zlúÄené dolopy a môžete +o nich rozhodnúť naraz. +

    \ No newline at end of file diff --git a/templates/sk/admindbsummary.html b/templates/sk/admindbsummary.html index 54ef080d..051d68cb 100644 --- a/templates/sk/admindbsummary.html +++ b/templates/sk/admindbsummary.html @@ -1,18 +1,82 @@ -Prehľad požiadaviek pre konferenciu %(listname)s. +Prehľad požiadaviek pre konferenciu %(listname)s. -Táto stránka obsahuje prehľad vštkých požiadaviek pre administrátora -zoradený tak, že požiadavky od jedného úÄastníka sú zlúÄené dokopy -a môže o nich byÅ¥ rozhodnuté naraz. +Táto stránka obsahuje prehľad vÅ¡tkých požiadaviek pre administrátora +zoradený tak, že požiadavky od jedného úÄastníka sú zlúÄené dokopy +a môže o nich byÅ¥ rozhodnuté naraz. -Žiadosti o prihlásenie a odhlásenie sú radené ako prvé a za nimi -prípadné príspevky, ktoré Äakajú na schválenie moderátorom. +Žiadosti o prihlásenie a odhlásenie sú radené ako prvé a za nimi +prípadné príspevky, ktoré Äakajú na schválenie moderátorom. -

    Pri každej administratívnej požiadavke musíte vybraÅ¥ ÄinnosÅ¥, ktorá sa -s ňou má previesÅ¥ a nakoniec vybrané Äinnosti uskutoÄníte kliknutím na -PotvrdiÅ¥ všetky zmeny. Podrobnejšie informáce o tom, ako -zaobchádzaÅ¥ s administratívnym rozhraním nájdete -pod týmto odkazom. +

    Pri každej administratívnej požiadavke musíte vybraÅ¥ ÄinnosÅ¥, ktorá sa +s ňou má previesÅ¥ a nakoniec vybrané Äinnosti uskutoÄníte kliknutím na +PotvrdiÅ¥ vÅ¡etky zmeny. PodrobnejÅ¡ie informáce o tom, ako +zaobchádzaÅ¥ s administratívnym rozhraním nájdete +pod týmto odkazom. -

    Tiež si môžete prezrieÅ¥ detailný prehľad všetkých -Äakajúcich požiadaviek kde je každý -príspovek zobrazovaný samostatne. +

    Tiež si môžete prezrieÅ¥ detailný prehľad vÅ¡etkých +Äakajúcich požiadaviek kde je každý +príspovek zobrazovaný samostatne. +

    \ No newline at end of file diff --git a/templates/sk/admlogin.html b/templates/sk/admlogin.html index d3a130d0..db9e03eb 100755 --- a/templates/sk/admlogin.html +++ b/templates/sk/admlogin.html @@ -1,37 +1,98 @@ - %(listname)s: prihlásenie ako %(who)s - - - -
    +%(listname)s: prihlásenie ako %(who)s + + + + %(message)s - - - - - - - - - - - -
    - %(listname)s: prihlásenie ako - %(who)s -
    Heslo pre %(who)s:
    -
    -

    Pozor: Aby systém fungoval - správne, musíte maÅ¥ vo Vašom prehliadaÄi povolené cookies. + + + + + + + + + + + +
    +%(listname)s: prihlásenie ako + %(who)s +
    Heslo pre %(who)s:
    +
    +

    Pozor: Aby systém fungoval + správne, musíte maÅ¥ vo VaÅ¡om prehliadaÄi povolené cookies. -

    Cookies sa používajú na to, aby ste nemuseli - každú požiadavku znovu potvrdzovaÅ¥ heslom. Budú zmazané - keÄ zavriete prehliadaÄ alebo pri odhlásení. - OdhlásiÅ¥ sa môžete kliknutím na OdhlásiÅ¥. - Tento odkaz uvidíte až po úspešnom prihlásení. -

    +

    Cookies sa používajú na to, aby ste nemuseli + každú požiadavku znovu potvrdzovaÅ¥ heslom. Budú zmazané + keÄ zavriete prehliadaÄ alebo pri odhlásení. + OdhlásiÅ¥ sa môžete kliknutím na OdhlásiÅ¥. + Tento odkaz uvidíte až po úspeÅ¡nom prihlásení. +

    diff --git a/templates/sk/archidxentry.html b/templates/sk/archidxentry.html index f9bb57aa..5c620b20 100644 --- a/templates/sk/archidxentry.html +++ b/templates/sk/archidxentry.html @@ -1,4 +1,68 @@ -
  • %(subject)s -  -%(author)s - +
  • %(subject)s +  +%(author)s + +
  • \ No newline at end of file diff --git a/templates/sk/archidxfoot.html b/templates/sk/archidxfoot.html index 5995ded2..2ecccf41 100644 --- a/templates/sk/archidxfoot.html +++ b/templates/sk/archidxfoot.html @@ -1,21 +1,85 @@ - -

    - Dátum posledného príspevku: - %(lastdate)s
    - Archivované: %(archivedate)s -

    -

      -
    • Správy zoradené podľa: + +

      +Dátum posledného príspevku: +%(lastdate)s
      +Archivované: %(archivedate)s +

      +

      -

      -


      - Tento archív bol vytvorený pomocou programu +
    +

    +


    +Tento archív bol vytvorený pomocou programu Pipermail %(version)s. - - + + +

    \ No newline at end of file diff --git a/templates/sk/archidxhead.html b/templates/sk/archidxhead.html index 3ef297e8..1d00a682 100644 --- a/templates/sk/archidxhead.html +++ b/templates/sk/archidxhead.html @@ -1,24 +1,89 @@ - - - Archív konferencie %(listname)s, zodadené podľa %(archtype)s - + + + +Archív konferencie %(listname)s, zodadené podľa %(archtype)s + %(encoding)s - - - -

    %(listname)s: %(archive)s, zoradené podľa %(archtype)s

    -
      -
    • Správy zoradené podľa: + + + +

      %(listname)s: %(archive)s, zoradené podľa %(archtype)s

      + -

      Od: %(firstdate)s
      - Do: %(lastdate)s
      - PoÄet príspevkov: %(size)s

      -

        +
      +

      Od: %(firstdate)s
      +Do: %(lastdate)s
      +PoÄet príspevkov: %(size)s

      +

        +

      \ No newline at end of file diff --git a/templates/sk/archlistend.html b/templates/sk/archlistend.html index 9bc052dd..2e1191b0 100644 --- a/templates/sk/archlistend.html +++ b/templates/sk/archlistend.html @@ -1 +1,64 @@ -
    + diff --git a/templates/sk/archliststart.html b/templates/sk/archliststart.html index 70b7d84f..a66a3285 100644 --- a/templates/sk/archliststart.html +++ b/templates/sk/archliststart.html @@ -1,4 +1,68 @@ - - - - +
    ArchívZobraziÅ¥ podľa:Verzia na stiahnutie
    + + + +
    ArchívZobraziť podľa:Verzia na stiahnutie
    \ No newline at end of file diff --git a/templates/sk/archtoc.html b/templates/sk/archtoc.html index dd2626a0..28c356df 100644 --- a/templates/sk/archtoc.html +++ b/templates/sk/archtoc.html @@ -1,20 +1,84 @@ - - - Archív konferencie %(listname)s - + + + +Archív konferencie %(listname)s + %(meta)s - - -

    Archív konferencie %(listname)s

    -

    - ZobraziÅ¥ viac informácií o konferencii - alebo stiahnuÅ¥ celý archív + + +

    Archív konferencie %(listname)s

    +

    + Zobraziť viac informácií o konferencii + alebo stiahnuť celý archív (%(size)s).

    %(noarchive_msg)s %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/sk/archtocentry.html b/templates/sk/archtocentry.html index cd82c675..7def060f 100644 --- a/templates/sk/archtocentry.html +++ b/templates/sk/archtocentry.html @@ -1,12 +1,76 @@ - -
    %(archivelabel)s: - [ vlákna ] - [ predmetu ] - [ autora ] - [ dátumu ] -
    %(archivelabel)s: +[ vlákna ] +[ predmetu ] +[ autora ] +[ dátumu ] +
    - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + +
    - -- - -
    -

      -

    - O konferencii - - - -
    -

    -

    Ak si chcete prezrieÅ¥ staré príspevky do konferencie, - navštívte archív konferencie - . - -

    -
    - Používanie konferencie - -
    - Príspevky do konferencie posielajte na adresu - . - -

    Nižšie na tejto stránke sa môžete sa prihlásiÅ¥ do konferencie - alebo upraviÅ¥ Vaše nastavenia v konferencii. -

    - Prihlásenie do konferencie - -
    -

    - Do konferencie sa prihlásite vyplnením nasledujúceho - formulára. - -

      - - - - - - - - - - - + + + +Informácie ku konferencii <mm-list-name></mm-list-name> + + +

      +

      Vaša e-mailová adresa: -  
      Vaše meno (nepovinné): 
      + + + + + + + + + + + + + + + + + + + + + + + + - - - - - -
      + -- + +
      +

        +

      +O konferencii + + + +
      +

      +

      Ak si chcete prezrieť staré príspevky do konferencie, + navštívte archív konferencie + . + +

      +
      +Používanie konferencie + +
      + Príspevky do konferencie posielajte na adresu + . - Heslo môžete uviesÅ¥ nižšie. - Toto nebude chránené a slúži iba na to, aby tretie osoby nemohli - meniÅ¥ Vaše nastavenia. Nepoužívajte heslo, ktoré je pre Vás - dôležité, lebo Vám bude nezabezpeÄene zasielané elektronickou - poštu. +

      Nižšie na tejto stránke sa môžete sa prihlásiť do konferencie + alebo upraviť Vaše nastavenia v konferencii. +

      +Prihlásenie do konferencie + +
      +

      + Do konferencie sa prihlásite vyplnením nasledujúceho + formulára. + +

        + + + + + + + + + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - - - -
        Vaša e-mailová adresa: + 
        Vaše meno (nepovinné): 
        Heslo môžete uviesÅ¥ nižšie. + Toto nebude chránené a slúži iba na to, aby tretie osoby nemohli + meniÅ¥ VaÅ¡e nastavenia. Nepoužívajte heslo, ktoré je pre Vás + dôležité, lebo Vám bude nezabezpeÄene zasielané elektronickou + poÅ¡tu. -

        Ak heslo nezadáte, bude pre Vás vytvorené náhodné heslo, - ktoré Vám bude zaslané potom, ako potvrdíte prihlásenie. - Heslo si môžete daÅ¥ kedykoľvek zaslaÅ¥ na adresu, na ktorú ste - sa prihlásili. - -
        -
        Tu napíšte heslo: 
        Zopakujte heslo pre kontrolu: 
        V akom jazyku majú byÅ¥ zobrazované správy?  
        Chcete prijímaÅ¥ poštu raz denne v súhrnnej správe? +

        Ak heslo nezadáte, bude pre Vás vytvorené náhodné heslo, + ktoré Vám bude zaslané potom, ako potvrdíte prihlásenie. + Heslo si môžete dať kedykoľvek zaslať na adresu, na ktorú ste + sa prihlásili. + + +
        Tu napíšte heslo: 
        Zopakujte heslo pre kontrolu: 
        V akom jazyku majú byť zobrazované správy?  
        Chcete prijímaÅ¥ poÅ¡tu raz denne v súhrnnej správe? Nie - Áno -
        -
        -
        - -
      -
      - - ÚÄastníci konferencie - -
      - - - -

      - - - -

      - - - +
    Nie + Ãno +
    +
    +
    + + + + + +ÚÄastníci konferencie + + + + + + + + +

    + + + +

    + +

    + +

    + diff --git a/templates/sk/options.html b/templates/sk/options.html index 8db9cf59..ea15bbb9 100644 --- a/templates/sk/options.html +++ b/templates/sk/options.html @@ -1,317 +1,349 @@ - - <MM-Presentable-User> - osobné nastavenia pre <MM-List-Name> - - - - - -
    - - - osobné nastavenia pre - -
    + +<mm-presentable-user> - osobné nastavenia pre <mm-list-name> +</mm-list-name></mm-presentable-user> + + + + +
    + + - osobné nastavenia pre + +

    - - - - - +
    - - nastavenia a heslá - pre konferenciu . -
    - - - - -

    -

    + + + +
    + - nastavenia a heslá + pre konferenciu . +
    + + +

    +

    - - +

    - - - -
    - - Zmena osobných nastavení pre konferenciu -
    Tu si môžete zmeniÅ¥ adresu, na ktorú ste prihlásený - do konferencie vložením novej adresy do nižšie uvedených políÄok. - Poznámka: bude Vám zaslaná overovacia správa, ktorú musíte potvrdiÅ¥. - E-mailová adresa bude zmenená až po potvrdení. - -

    Na potvrdenie bude systém ÄakaÅ¥ približne . - -

    Tiež môžete zmeniÅ¥ alebo nastaviÅ¥ Vaše meno (nepovinná voľba) - (napr. Peter Novák). - -

    Ak chcete urobiÅ¥ tieto zmeny pre všetky konferencie na serveri - , v ktorých ste úÄastníkom, zaškrtnite políÄko - zmeniÅ¥ globálne. - -

    - - + + + + +
    Nová e-mailová adresa: + + + + - - - - -
    + +Zmena osobných nastavení pre konferenciu +
    Tu si môžete zmeniÅ¥ adresu, na ktorú ste prihlásený + do konferencie vložením novej adresy do nižšie uvedených políÄok. + Poznámka: bude Vám zaslaná overovacia správa, ktorú musíte potvrdiÅ¥. + E-mailová adresa bude zmenená až po potvrdení. + +

    Na potvrdenie bude systém ÄakaÅ¥ približne . + +

    Tiež môžete zmeniť alebo nastaviť Vaše meno (nepovinná voľba) + (napr. Peter Novák). + +

    Ak chcete urobiÅ¥ tieto zmeny pre vÅ¡etky konferencie na serveri + , v ktorých ste úÄastníkom, zaÅ¡krtnite políÄko + zmeniÅ¥ globálne. + +

    + + - - - - - -
    Nová e-mailová adresa:
    ZopakovaÅ¥ novú - adresu:
    -
    - - - - -
    Vaše meno - (nepovinné):
    -
    -

    zmeniÅ¥ globálne

    - +
    Zopakovať novú + adresu:
    +

    + + + + +
    Vaše meno + (nepovinné):
    + +
    +

    zmeniť globálne

    +

    - - - - - - - - + + + + %(textlink)s - diff --git a/templates/sl/article.html b/templates/sl/article.html index b9a7e745..7c421e3d 100644 --- a/templates/sl/article.html +++ b/templates/sl/article.html @@ -1,12 +1,13 @@ - - - %(title)s - - - - - + +

    Arhiv seznama %(listname)s

    +

    + Na seznamu še ni bilo objavljenih sporoèil, zato je arhiv trenutno prazen. Ogledate si lahko dodatne informacije o tem seznamu.

    - - + + diff --git a/templates/sl/headfoot.html b/templates/sl/headfoot.html index 2bcdc5a3..5424c670 100644 --- a/templates/sl/headfoot.html +++ b/templates/sl/headfoot.html @@ -1,27 +1,90 @@ -To besedilo lahko vkljuèuje +To besedilo lahko vkljuèuje Python nize, ki jih uporabljamo namesto atributov seznama. Seznam -mo¾nih nadomestil: +mo¾nih nadomestil:
      -
    • real_name - `Javno' ime seznama; ponavadi - je to kar ime seznama, pisano z veliko zaèetnico. +
    • real_name - `Javno' ime seznama; ponavadi + je to kar ime seznama, pisano z veliko zaèetnico.
    • list_name - Ime, s katerim se seznam prepozna - v URL-jih, kjer so pomembne velike in male èrke.(Za obratno zdru¾ljivost + v URL-jih, kjer so pomembne velike in male èrke.(Za obratno zdru¾ljivost je enakovredno _internal_name.)
    • host_name - Polno in veljavno ime domene, v - kateri deluje stre¾nik. + kateri deluje stre¾nik.
    • web_page_url - Osnovni URL za Mailman. Temu lahko dodate npr. listinfo/%(list_name)s za stran s - podatki o poštnem seznamu. + podatki o poÅ¡tnem seznamu. -
    • description - Kratek opis poštnega seznama. +
    • description - Kratek opis poÅ¡tnega seznama. -
    • info - Daljši opis poštnega seznama. +
    • info - DaljÅ¡i opis poÅ¡tnega seznama.
    • cgiext - Pripona, dodana za CGI skripte. -
    + diff --git a/templates/sl/listinfo.html b/templates/sl/listinfo.html index 84c676a9..c5ad7ab8 100644 --- a/templates/sl/listinfo.html +++ b/templates/sl/listinfo.html @@ -1,145 +1,206 @@ - - - - <MM-List-Name> Stran s podatki - - - -

    -

    - Odhlásenie z konferencie - Vaša úÄasÅ¥ na Äalšich konferenciách na serveri -
    - Zaškrtnite potvrdzovacie políÄko a stlaÄte tlaÄidlo pre - odhlásenie sa z tejto konferencie. Pozor: - Táto zmena bude prevedená okamžite! + + + + - + +
    +

    +Odhlásenie z konferencie +VaÅ¡a úÄasÅ¥ na ÄalÅ¡ich konferenciách na serveri +
    + ZaÅ¡krtnite potvrdzovacie políÄko a stlaÄte tlaÄidlo pre + odhlásenie sa z tejto konferencie. Pozor: + Táto zmena bude prevedená okamžite!

    -

    - Tu si môžete pozrieÅ¥ zoznam všetkých Äalších konferencií na serveri - , do ktorých ste prihlásený. Cez tento zoznam sa dostanete - na Vaše nastavenia k tímto konferenciám. +

    + Tu si môžete pozrieÅ¥ zoznam vÅ¡etkých Äalších konferencií na serveri + , do ktorých ste prihlásený. Cez tento zoznam sa dostanete + na VaÅ¡e nastavenia k tímto konferenciám.

    -

    -
    - - - - - - -
    - Vaše heslo pre konferenciu -
    - -
    -

    Zabudli ste heslo?

    -
    - Kliknite na toto tlaÄidlo a Vaše heslo Vám bude zaslané na adresu, - na ktorú ste prihlásený. -

    -

    - -
    -
    - -
    -

    Zmena hesla

    - - - - - - - - -
    Nové - heslo:
    ZopakovaÅ¥ - nové heslo:
    - - -

    zmeniÅ¥ globálne -
    -
    - + + + +
    +Vaše heslo pre konferenciu +
    + +
    +

    Zabudli ste heslo?

    +
    + Kliknite na toto tlaÄidlo a VaÅ¡e heslo Vám bude zaslané na adresu, + na ktorú ste prihlásený. +

    +

    + +
    +

    + +
    +

    Zmena hesla

    + + + + + + + + +
    Nové + heslo:
    Zopakovať + nové heslo:
    + +

    zmeniť globálne +
    +

    - - +
    - Osobné nastavenia pre konferenciu -
    +
    +Osobné nastavenia pre konferenciu +
    -

    -Momentálne aktívne nastavenia sú zaškrtnuté. - -

    Poznámka: niektoré nastavenia majú možnosÅ¥ nastaviÅ¥ globálne. -Zaškrtnutím bude toto nastavenie aplikované na všetky konferencie na -serveri , v ktorých ste úÄastníkom. Kliknutím na ZobraziÅ¥ moje -Äalšie konferencie vyššie získate zoznam konferencií, -do ktorých ste prihlásený. +Momentálne aktívne nastavenia sú zaÅ¡krtnuté. +

    Poznámka: niektoré nastavenia majú možnosÅ¥ nastaviÅ¥ globálne. +ZaÅ¡krtnutím bude toto nastavenie aplikované na vÅ¡etky konferencie na +serveri , v ktorých ste úÄastníkom. Kliknutím na ZobraziÅ¥ moje +ÄalÅ¡ie konferencie vyššie získate zoznam konferencií, +do ktorých ste prihlásený.

    - -
    - - DoruÄovanie pošty

    - Nastavte na zapnuté, ak chcete prijímaÅ¥ správy zasielané - do tejto konferencie. Použite voľbu vypnuté ak chcete - zostaÅ¥ prihlásený, ale nechcete poÄas urÄitej doby prijímaÅ¥ poštu - (napr. ak idete na dovolenku). Ak vypnete doruÄovanie pošty, - nezabudnite ho po Vašom návrate opäÅ¥ zapnúÅ¥, automaticky zapnuté + + - +

    - - - + + - - - - + - - - - - - - - + + + + - - + - - - - - +

    + +
    + +DoruÄovanie poÅ¡ty

    + Nastavte na zapnuté, ak chcete prijímaÅ¥ správy zasielané + do tejto konferencie. Použite voľbu vypnuté ak chcete + zostaÅ¥ prihlásený, ale nechcete poÄas urÄitej doby prijímaÅ¥ poÅ¡tu + (napr. ak idete na dovolenku). Ak vypnete doruÄovanie poÅ¡ty, + nezabudnite ho po VaÅ¡om návrate opäť zapnúť, automaticky zapnuté nebude. -

    - zapnuté
    - vypnuté

    - nastaviÅ¥ globálne -

    +zapnuté
    +vypnuté

    +nastaviť globálne +

    - Dávkové doruÄovanie pošty (digest)

    - Ak zapnete dávkové doruÄovanie pošty, budete dostávaÅ¥ príspevky - zlúÄené do jednej správy raz denne (pri niektorých veľmi aktívnych - konferenciách aj Äastejšie) namiesto každého príspevku samostatne. - Ak vypnete doruÄovanie vo forme dávok, môže Vám byÅ¥ doruÄená - ešte jedna posledná dávka (digest). -

    - vypnuté
    - zapnuté -
    - OdoberaÅ¥ dávky vo forme Äistého textu alebo MIME? -

    Váš poštový program môže, ale nemusí porporovaÅ¥ digest vo formáte - MIME. OdporúÄame odoberaÅ¥ dávky vo formáte MIME, ale ak máte problém - takúto poštu ÄítaÅ¥, vyberte Äistý text. -

    - MIME
    - Äistý text

    - nastaviÅ¥ globálne -

    +Dávkové doruÄovanie poÅ¡ty (digest)

    + Ak zapnete dávkové doruÄovanie poÅ¡ty, budete dostávaÅ¥ príspevky + zlúÄené do jednej správy raz denne (pri niektorých veľmi aktívnych + konferenciách aj ÄastejÅ¡ie) namiesto každého príspevku samostatne. + Ak vypnete doruÄovanie vo forme dávok, môže Vám byÅ¥ doruÄená + eÅ¡te jedna posledná dávka (digest). +

    +vypnuté
    +zapnuté +
    +OdoberaÅ¥ dávky vo forme Äistého textu alebo MIME? +

    Váš poÅ¡tový program môže, ale nemusí porporovaÅ¥ digest vo formáte + MIME. OdporúÄame odoberaÅ¥ dávky vo formáte MIME, ale ak máte problém + takúto poÅ¡tu ÄítaÅ¥, vyberte Äistý text. +

    +MIME
    +Äistý text

    +nastaviť globálne +

    - OdoberaÅ¥ vlastné príspevky do konferencie?

    - TradiÄne dostanete kópiu každej správy, ktorá bola zaslaná do - konferenccie. Ak nechcete dostávaÅ¥ kópiu vlastných zaslaných správ, +

    +Odoberať vlastné príspevky do konferencie?

    + TradiÄne dostanete kópiu každej správy, ktorá bola zaslaná do + konferenccie. Ak nechcete dostávaÅ¥ kópiu vlastných zaslaných správ, nastavte voľbu na nie. -

    - nie
    - áno -
    - Chcete dostávaÅ¥ potvrdenie o rozposlaní Vaších príspevkov do +

    +nie
    +áno +
    +Chcete dostávať potvrdenie o rozposlaní Vaších príspevkov do konferencie?

    -

    - nie
    - áno -
    - ZasielaÅ¥ pre túto konferenciu pripomienku hesla?

    - Raz mesaÄne obdržíte správu, ktorá obsahuje heslo pre každú - konferenciu na tomto serveri, v ktorej ste úÄastníkom. Zasielanie - hesla môžete vypnúÅ¥ vybraním voľby nie. Ak vypnete - zasielanie hesla globálne (pre všetky konferencie), nebude Vám - zaslaná žiadna správa. -

    - nie
    - áno

    - nastaviÅ¥ globálne -

    - SkryÅ¥ Vašu adresu zo zoznamu úÄastníkov?

    - Ak si niekto prezerá zoznam úÄastníkov, Vaša adresa je za normálnych - okolností zobrazená (v pozmenenej forme, aby ju nerozpoznali spamové - roboty). Ak nechcete Vašu adresu v tomto zozname zobraziÅ¥ vôbec, - vyberte v tejto voľbe áno." -

    - nie
    - áno -
    - Aký jazyk uprednostňujete na komunikáciu?

    -

    - -
    - Do ktorých tematických kategórií sa chcete prihlásiÅ¥? +

    +nie
    +áno +
    +Zasielať pre túto konferenciu pripomienku hesla?

    + Raz mesaÄne obdržíte správu, ktorá obsahuje heslo pre každú + konferenciu na tomto serveri, v ktorej ste úÄastníkom. Zasielanie + hesla môžete vypnúť vybraním voľby nie. Ak vypnete + zasielanie hesla globálne (pre vÅ¡etky konferencie), nebude Vám + zaslaná žiadna správa. +

    +nie
    +áno

    +nastaviť globálne +

    +SkryÅ¥ VaÅ¡u adresu zo zoznamu úÄastníkov?

    + Ak si niekto prezerá zoznam úÄastníkov, VaÅ¡a adresa je za normálnych + okolností zobrazená (v pozmenenej forme, aby ju nerozpoznali spamové + roboty). Ak nechcete VaÅ¡u adresu v tomto zozname zobraziÅ¥ vôbec, + vyberte v tejto voľbe áno." +

    +nie
    +áno +
    +Aký jazyk uprednostňujete na komunikáciu?

    +

    + +
    +Do ktorých tematických kategórií sa chcete prihlásiť?

    - Vybraním jednej alebo viac tém budú filtrované - príspevky zasielané do konferencie tak, že dostanete iba - niektoré z nich. Ak bude príspevok vyhovovaÅ¥ niektorej - z Vami vybraných tém, tak Vám bude doruÄený. V opaÄnom - prípade Vám doruÄený nebude. - -

    Ak príspevok nevyhovuje žiadnej téme, tak je úÄinné - nastavenie, ktoré sa nachádza nižšie. KeÄ si nevyberiete - žiadne témy, budete dostávaÅ¥ všetky príspevky, ktoré boli - zaslané do konferencie. -

    - -
    - Chcete dostávaÅ¥ príspevky, ktoré nevyhovujú žiadnemu - tématickému filtru?

    - - Toto nastavenie je úÄinné iba vtedy, ak ste sa vybrali - vo vyššom nastavení aspoň jednu tému. UrÄuje všeobecné - pravidlo pre doruÄenie práspevkov, ktoré nevyhoveli žiadnemu - tématickému filtru. Voľba nie spôsobí, že ak - príspevok nevyhovie žiadnemu tématickému filtru, tak Vám - nebude doruÄený. Pri voľbe áno Vám tento príspevok - doruÄený bude. - -

    Ak nie je vo voľbe vyššie vybraná žiadna téma, tak Vám - budú doruÄené všetky správy, ktoré boli zaslané do tejto + Vybraním jednej alebo viac tém budú filtrované + príspevky zasielané do konferencie tak, že dostanete iba + niektoré z nich. Ak bude príspevok vyhovovaÅ¥ niektorej + z Vami vybraných tém, tak Vám bude doruÄený. V opaÄnom + prípade Vám doruÄený nebude. + +

    Ak príspevok nevyhovuje žiadnej téme, tak je úÄinné + nastavenie, ktoré sa nachádza nižšie. KeÄ si nevyberiete + žiadne témy, budete dostávaÅ¥ vÅ¡etky príspevky, ktoré boli + zaslané do konferencie. +

    + +
    +Chcete dostávať príspevky, ktoré nevyhovujú žiadnemu + tématickému filtru?

    + + Toto nastavenie je úÄinné iba vtedy, ak ste sa vybrali + vo vyššom nastavení aspoň jednu tému. UrÄuje vÅ¡eobecné + pravidlo pre doruÄenie práspevkov, ktoré nevyhoveli žiadnemu + tématickému filtru. Voľba nie spôsobí, že ak + príspevok nevyhovie žiadnemu tématickému filtru, tak Vám + nebude doruÄený. Pri voľbe áno Vám tento príspevok + doruÄený bude. + +

    Ak nie je vo voľbe vyššie vybraná žiadna téma, tak Vám + budú doruÄené vÅ¡etky správy, ktoré boli zaslané do tejto konferencie. -

    - nie
    - áno -
    - ZabrániÅ¥ duplicitným kópiam príspevkov?

    - - KeÄ ste uvedený v hlaviÄke To:, alebo Cc: - správy, ktorá bola zaslaná do tejto konferencie, môžete - zabrániÅ¥ jej opätovnému doruÄeniu. Vyberte áno, - ak nechcete dostaÅ¥ kópiu takýchto správ z konferencie alebo - nie, ak chcete, aby Vám boli doruÄené aj ich kópie. - -

    Ak má táto konferencia zapnuté personifikované - správy a Vy máte zapnuté zasielanie kópií, tak bude maÅ¥" - každá kópia pridanú hlaviÄku X-Mailman-Copy: yes - -

    - nie
    - áno

    - nastaviÅ¥ globálne -

    -
    -
    +nie
    +áno +
    +Zabrániť duplicitným kópiam príspevkov?

    + + KeÄ ste uvedený v hlaviÄke To:, alebo Cc: + správy, ktorá bola zaslaná do tejto konferencie, môžete + zabrániÅ¥ jej opätovnému doruÄeniu. Vyberte áno, + ak nechcete dostaÅ¥ kópiu takýchto správ z konferencie alebo + nie, ak chcete, aby Vám boli doruÄené aj ich kópie. + +

    Ak má táto konferencia zapnuté personifikované + správy a Vy máte zapnuté zasielanie kópií, tak bude maÅ¥" + každá kópia pridanú hlaviÄku X-Mailman-Copy: yes +

    +nie
    +áno

    +nastaviť globálne +

    +
    +
    -

    - - - - + + +

    diff --git a/templates/sk/private.html b/templates/sk/private.html index 511ccd2d..b85a5da1 100755 --- a/templates/sk/private.html +++ b/templates/sk/private.html @@ -1,57 +1,118 @@ - Konferencia %(realname)s - prístup do súkromného archívu - - - -
    +Konferencia %(realname)s - prístup do súkromného archívu + + + + %(message)s - - - - - - - - - - - - - - - -
    - Konferencia %(realname)s - - prístup do súkromného archívu -
    E-mailová adresa:
    Heslo:
    -
    -

    Dôležité: od tohto momentu potrebujete - maÅ¥ v prehliadaÄi zapnuté cookies, inak sa budete musieÅ¥ opätovne - prihlásiÅ¥ pri každej Äalšej Äinnosti. + + + + + + + + + + + + + + + +
    +Konferencia %(realname)s - + prístup do súkromného archívu +
    E-mailová adresa:
    Heslo:
    +
    +

    Dôležité: od tohto momentu potrebujete + maÅ¥ v prehliadaÄi zapnuté cookies, inak sa budete musieÅ¥ opätovne + prihlásiÅ¥ pri každej ÄalÅ¡ej Äinnosti. -

    Cookis pre sedenia sú použité pre prístup do súkromného archívu - Mailman, aby ste sa nemuseli stále opätovne prihlasovaÅ¥. Tento cookie - vyprší, keÄ zavrete Váš prehliadaÄ, alebo kliknete na stránke Vašich - osobných nastavení na tlaÄidlo OdhlásiÅ¥. +

    Cookis pre sedenia sú použité pre prístup do súkromného archívu + Mailman, aby ste sa nemuseli stále opätovne prihlasovaÅ¥. Tento cookie + vyprší, keÄ zavrete Váš prehliadaÄ, alebo kliknete na stránke VaÅ¡ich + osobných nastavení na tlaÄidlo OdhlásiÅ¥.

    - - - - - - + + + +
    - Password Reminder -
    If you don't remember your password, enter your email address + + + + + + - - - - -
    +Password Reminder +
    If you don't remember your password, enter your email address above and click the Remind button and your password will be emailed to you.
    - +
    +

    diff --git a/templates/sk/roster.html b/templates/sk/roster.html index af53c75d..48f2bde9 100644 --- a/templates/sk/roster.html +++ b/templates/sk/roster.html @@ -1,53 +1,112 @@ - - - ÚÄastníci konferencie <MM-List-Name> - - - - -

    - - - - - - - - - - - - - - - -
    - ÚÄastníci konferencie - -
    - -

    -

    - -

    Kliknutím na Vašu adresu prejdete na stránku s Vašími osobnými - nastaveniami.
    (Záznamy v zátvorke majú vypnuté doruÄovanie - príspevkov)

    -
    -
    - Bežní úÄastníci konferencie: - -
    -
    -
    - ÚÄastníci konferencie - prijímajúci súhrnné správy: -
    -
    -

    -

    -

    -

    - - - + + +ÚÄastníci konferencie <mm-list-name></mm-list-name> + + +

    + + + + + + + + + + + + + + + +
    + ÚÄastníci konferencie + +
    +

    +

    +

    Kliknutím na Vašu adresu prejdete na stránku s Vašími osobnými + nastaveniami.
    (Záznamy v zátvorke majú vypnuté doruÄovanie + príspevkov)

    +
    +
    +Bežní úÄastníci konferencie: + +
    +
    +
    +ÚÄastníci konferencie + prijímajúci súhrnné správy: +
    +
    +

    +

    +

    +

    + +

    + diff --git a/templates/sk/subscribe.html b/templates/sk/subscribe.html index 8194bcc7..76292ca4 100644 --- a/templates/sk/subscribe.html +++ b/templates/sk/subscribe.html @@ -1,9 +1,72 @@ -<MM-List-Name> Výsledky prihlásenia +<mm-list-name> Výsledky prihlásenia</mm-list-name> -

    Výsledky prihlásenia

    - - - +

    Výsledky prihlásenia

    + + + diff --git a/templates/sl/admindbdetails.html b/templates/sl/admindbdetails.html index 400c59b5..173ba830 100644 --- a/templates/sl/admindbdetails.html +++ b/templates/sl/admindbdetails.html @@ -1,62 +1,125 @@ -Skrbniške zahteve so prikazane na dva mo¾na naèina, kot +SkrbniÅ¡ke zahteve so prikazane na dva mo¾na naèina, kot stran s povzetki ali kot -podrobnosti. Stran s povzetki vsebuje èakajoèe zahteve -za prijave in odjave, pa tudi sporoèila, ki èakajo vašo odobritev -za objavo, prikazana po e-poštnih naslovih pošiljateljev. Stran -s podrobnostmi vsebuje bolj podroben opis vsakega sporoèila, -vkljuèno z glavami sporoèila in povzetkom besedila. +podrobnosti. Stran s povzetki vsebuje èakajoèe zahteve +za prijave in odjave, pa tudi sporoèila, ki èakajo vaÅ¡o odobritev +za objavo, prikazana po e-poÅ¡tnih naslovih poÅ¡iljateljev. Stran +s podrobnostmi vsebuje bolj podroben opis vsakega sporoèila, +vkljuèno z glavami sporoèila in povzetkom besedila.

    Na vseh straneh so na voljo naslednja dejanja:

      -
    • Odlo¾i -- Odlo¾i odloèitev na kasneje. V tem trenutku - se ne izvede nobeno dejanje za èakajoèo zahtevo, vendar lahko - èakajoèa sporoèila vseeno pošljete naprej ali ga shranite +
    • Odlo¾i -- Odlo¾i odloèitev na kasneje. V tem trenutku + se ne izvede nobeno dejanje za èakajoèo zahtevo, vendar lahko + èakajoèa sporoèila vseeno poÅ¡ljete naprej ali ga shranite (glej spodaj). -

    • Odobri -- Odobri sporoèilo in ga pošlje na seznam. - Pri zahtevah za èlanstvo odobri spremembo v stanju èlanstva. +

    • Odobri -- Odobri sporoèilo in ga poÅ¡lje na seznam. + Pri zahtevah za èlanstvo odobri spremembo v stanju èlanstva. -

    • Zavrni -- Zavrne sporoèilo, pošiljatelju pa pošlje obvestilo - o zavrnitvi in zavr¾e izvirno sporoèilo. Pri èlanskih zahtevah zavrne - spremembe v stanju èlanstva. V obeh primerih morate v polje za besedilo +

    • Zavrni -- Zavrne sporoèilo, poÅ¡iljatelju pa poÅ¡lje obvestilo + o zavrnitvi in zavr¾e izvirno sporoèilo. Pri èlanskih zahtevah zavrne + spremembe v stanju èlanstva. V obeh primerih morate v polje za besedilo vnesti razlog za zavrnitev. -

    • Izbriši -- Zavr¾e izvirno sporoèilo, brez pošiljanja - obvestila o zavrnitvi. Pri èlanskih zahtevah zavr¾e zahtevo in - pošiljatelju zahteve ne pošlje obvestila. Ta mo¾nost se uporablja - za poznano ne¾eleno pošto (spam). -
    - -

    Pri obdr¾anih sporoèilih vkljuèite mo¾nost Obdr¾i, èe ¾elite -shraniti kopijo sporoèila za skrbnika strani. Ta mo¾nost je uporabna -pri ¾aljivih sporoèilih, ki jih ¾elite zavrniti, hkrati pa shraniti kot +

  • IzbriÅ¡i -- Zavr¾e izvirno sporoèilo, brez poÅ¡iljanja + obvestila o zavrnitvi. Pri èlanskih zahtevah zavr¾e zahtevo in + poÅ¡iljatelju zahteve ne poÅ¡lje obvestila. Ta mo¾nost se uporablja + za poznano ne¾eleno poÅ¡to (spam). +
  • +

    Pri obdr¾anih sporoèilih vkljuèite mo¾nost Obdr¾i, èe ¾elite +shraniti kopijo sporoèila za skrbnika strani. Ta mo¾nost je uporabna +pri ¾aljivih sporoèilih, ki jih ¾elite zavrniti, hkrati pa shraniti kot dokaz. -

    Vkljuèite mo¾nost Posreduj in vnesite prejemnikov naslov, -èe ¾elite sporoèilo namesto na seznam poslati nekomu drugemu. Èe -¾elite urediti shranjeno sporoèilo preden ga pošljete na seznam, -ga pošljite na svoj naslov (ali na naslov lastnika seznama), izvirno -sporoèilo pa izbrišite. Ko prejmete sporoèilo v svoj poštni predal, -da uredite in pošljite na seznam, skupaj z glavo Odobreno: +

    Vkljuèite mo¾nost Posreduj in vnesite prejemnikov naslov, +èe ¾elite sporoèilo namesto na seznam poslati nekomu drugemu. Èe +¾elite urediti shranjeno sporoèilo preden ga poÅ¡ljete na seznam, +ga poÅ¡ljite na svoj naslov (ali na naslov lastnika seznama), izvirno +sporoèilo pa izbriÅ¡ite. Ko prejmete sporoèilo v svoj poÅ¡tni predal, +da uredite in poÅ¡ljite na seznam, skupaj z glavo Odobreno: in geslom seznama kot vrednostjo. Prav tako je primerno, da dodate -opombo, da ste spremenili sporoèilo. +opombo, da ste spremenili sporoèilo. -

    Èe je pošiljatelj èlan dopisnega seznama, katerega sporoèila ste -do sedaj obravnavali, ga lahko prenehate opazovati. Èe je seznam -nastavljen tako, da postavite novega uporabnika na preizkušnjo, -lahko le-ta kasneje, ko mu zaupate, objavlja sporoèila neposredno. +

    Èe je pošiljatelj èlan dopisnega seznama, katerega sporoèila ste +do sedaj obravnavali, ga lahko prenehate opazovati. Èe je seznam +nastavljen tako, da postavite novega uporabnika na preizkušnjo, +lahko le-ta kasneje, ko mu zaupate, objavlja sporoèila neposredno. -

    Èe pošiljatelj ni èlan seznama, lahko njegov naslov dodate v -filter pošiljateljev. Ti filtri so opisani na strani -stran za filtre pošiljateljev, kjer so +

    Èe poÅ¡iljatelj ni èlan seznama, lahko njegov naslov dodate v +filter poÅ¡iljateljev. Ti filtri so opisani na strani +stran za filtre poÅ¡iljateljev, kjer so filtri razdeljeni na auto-accept (sprejme), auto-hold (shrani), auto-reject (zavrne) ali auto-discard -(izbriše). Ta mo¾nost ne bo na voljo, èe je naslov ¾e vkljuèen +(izbriÅ¡e). Ta mo¾nost ne bo na voljo, èe je naslov ¾e vkljuèen v katerega od filtrov. -

    Ko konèate, kliknite gumb Pošlji vse podatke v +

    Ko konèate, kliknite gumb PoÅ¡lji vse podatke v zgornem ali spodnjem delu strani. S tem gumbom boste aktivirali -vsa izbrana dejanja za skrbniške zahteve. +vsa izbrana dejanja za skrbniÅ¡ke zahteve.

    Vrnitev na stran s povzetki. +

    \ No newline at end of file diff --git a/templates/sl/admindbpreamble.html b/templates/sl/admindbpreamble.html index 3cfb4052..32e0be1b 100644 --- a/templates/sl/admindbpreamble.html +++ b/templates/sl/admindbpreamble.html @@ -1,9 +1,73 @@ -Na tej strani najdete sporoèila za seznam %(listname)s, ki -èakajo na vašo odobritev. Trenutno je prikazano %(description)s +Na tej strani najdete sporoèila za seznam %(listname)s, ki +èakajo na vaÅ¡o odobritev. Trenutno je prikazano %(description)s -

    Za vsako skrbniško zahtevo izberite dejanje, tako da kliknete na -gumb Pošlji vse podatke (Submit all data), ko konèate. Bolj +

    Za vsako skrbniško zahtevo izberite dejanje, tako da kliknete na +gumb Pošlji vse podatke (Submit all data), ko konèate. Bolj podrobna navodila najdete tukaj.

    Lahko si tudi ogledate povzetek vseh -èakajoèih zahtev. +èakajoèih zahtev. +

    \ No newline at end of file diff --git a/templates/sl/admindbsummary.html b/templates/sl/admindbsummary.html index 6fc49bfc..61a4f99f 100644 --- a/templates/sl/admindbsummary.html +++ b/templates/sl/admindbsummary.html @@ -1,10 +1,74 @@ -Ta stran vsebuje povzetek trenutnih skrbniških zahtev, ki èakajo -na vašo odobritev za poštni seznam %(listname)s. -Najprej vidite seznam èakajoèih zahtev za prijavo in odjavo, -sledijo pa jim èakajoèa sporoèila za objavo. +Ta stran vsebuje povzetek trenutnih skrbniÅ¡kih zahtev, ki èakajo +na vaÅ¡o odobritev za poÅ¡tni seznam %(listname)s. +Najprej vidite seznam èakajoèih zahtev za prijavo in odjavo, +sledijo pa jim èakajoèa sporoèila za objavo. -

    Za vsako skrbniško zahtevo izberite ¾eleno dejanje in, ko -konèate, kliknite na gumb Pošlji vse podatke. Na voljo so tudi +

    Za vsako skrbniško zahtevo izberite ¾eleno dejanje in, ko +konèate, kliknite na gumb Pošlji vse podatke. Na voljo so tudi podrobna navodila. -

    Lahko si tudi ogledate podrobnosti èakajoèih sporoèil. +

    Lahko si tudi ogledate podrobnosti èakajoèih sporoèil. +

    \ No newline at end of file diff --git a/templates/sl/admlogin.html b/templates/sl/admlogin.html index 2c87acec..d86fc082 100755 --- a/templates/sl/admlogin.html +++ b/templates/sl/admlogin.html @@ -1,39 +1,100 @@ - Avtentikacija èlana %(who)s za seznam %(listname)s - - - -
    +Avtentikacija èlana %(who)s za seznam %(listname)s + + + + %(message)s - - - - - - - - - - - -
    - %(listname)s %(who)s - avtentikacija -
    Geslo za %(who)s:
    -
    -

    Pomembno: Od tega trenutka dalje - morate v brskalniku imeti omogoèene piškotke, drugaèe skrbniška + + + + + + + + + + + +
    +%(listname)s %(who)s + avtentikacija +
    Geslo za %(who)s:
    +
    +

    Pomembno: Od tega trenutka dalje + morate v brskalniku imeti omogoèene piškotke, drugaèe skrbniška dejanja ne bodo veljavna. -

    Piškotki za seje se v skrbniškem vmesniku Mailmana +

    PiÅ¡kotki za seje se v skrbniÅ¡kem vmesniku Mailmana uporabljajo zato, da ni potrebna ponovna avtentikacija za - vsako skrbniško dejanje. Ta piškotek poteèe samodejno, ko + vsako skrbniÅ¡ko dejanje. Ta piÅ¡kotek poteèe samodejno, ko zaprete brskalnik ali ko se odjavite z gumbom Odjava - pod Druge skrbniške dejavnosti + pod Druge skrbniÅ¡ke dejavnosti (kar boste videli, ko se prijavite). -

    +

    diff --git a/templates/sl/archidxentry.html b/templates/sl/archidxentry.html index f9bb57aa..5c620b20 100644 --- a/templates/sl/archidxentry.html +++ b/templates/sl/archidxentry.html @@ -1,4 +1,68 @@ -
  • %(subject)s -  -%(author)s - +
  • %(subject)s +  +%(author)s + +
  • \ No newline at end of file diff --git a/templates/sl/archidxfoot.html b/templates/sl/archidxfoot.html index fd129195..94b215f6 100644 --- a/templates/sl/archidxfoot.html +++ b/templates/sl/archidxfoot.html @@ -1,21 +1,85 @@ - -

    - Datum zadnjega sporoèila: - %(lastdate)s
    - Arhivirano dne: %(archivedate)s -

    -

      -
    • Sporoèila razvršèena glede na: + +

      +Datum zadnjega sporoèila: +%(lastdate)s
      +Arhivirano dne: %(archivedate)s +

      +

      -

      -


      - Ta arhiv je bil ustvarjen s programom +
    +

    +


    +Ta arhiv je bil ustvarjen s programom Pipermail %(version)s. - - + + +

    \ No newline at end of file diff --git a/templates/sl/archidxhead.html b/templates/sl/archidxhead.html index 0deba80a..b990ef82 100644 --- a/templates/sl/archidxhead.html +++ b/templates/sl/archidxhead.html @@ -1,15 +1,79 @@ - - - Arhiv za %(listname)s %(archive)s glede na %(archtype)s - + + + +Arhiv za %(listname)s %(archive)s glede na %(archtype)s + %(encoding)s - - - -

    Arhivi %(archive)s glede na %(archtype)s

    -
      -
    • Sporoèila razvršèena glede na: + + + +

      Arhivi %(archive)s glede na %(archtype)s

      +
        +
      • Sporoèila razvršèena glede na: %(thread_ref)s %(subject_ref)s %(author_ref)s @@ -17,8 +81,9 @@

        Arhivi %(archive)s glede na %(archtype)s

      • Dodatne informacije o tem seznamu...
      • -
      -

      Zaèetek: %(firstdate)s
      - Konec: %(lastdate)s
      - Sporoèil: %(size)s

      -

        +
      +

      Zaèetek: %(firstdate)s
      +Konec: %(lastdate)s
      +Sporoèil: %(size)s

      +

        +

      \ No newline at end of file diff --git a/templates/sl/archlistend.html b/templates/sl/archlistend.html index 9bc052dd..2e1191b0 100644 --- a/templates/sl/archlistend.html +++ b/templates/sl/archlistend.html @@ -1 +1,64 @@ -
    + diff --git a/templates/sl/archliststart.html b/templates/sl/archliststart.html index 8bd66c49..386bd4fd 100644 --- a/templates/sl/archliststart.html +++ b/templates/sl/archliststart.html @@ -1,4 +1,68 @@ - - - - +
    ArhivPoka¾i glede na:Prenosljiva razlièica
    + + + +
    ArhivPoka¾i glede na:Prenosljiva razlièica
    \ No newline at end of file diff --git a/templates/sl/archtoc.html b/templates/sl/archtoc.html index e72656b0..4baa3e6f 100644 --- a/templates/sl/archtoc.html +++ b/templates/sl/archtoc.html @@ -1,13 +1,77 @@ - - - Arhivi za seznam %(listname)s - + + + +Arhivi za seznam %(listname)s + %(meta)s - - -

    Arhivi za seznam %(listname)s

    -

    + + +

    Arhivi za seznam %(listname)s

    +

    Ogledate si lahko dodatne informacije o tem seznamu ali pa prenesete celoten raw arhiv (%(size)s). @@ -16,5 +80,5 @@

    Arhivi za seznam %(listname)s

    %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/sl/archtocentry.html b/templates/sl/archtocentry.html index 46340060..24595b2d 100644 --- a/templates/sl/archtocentry.html +++ b/templates/sl/archtocentry.html @@ -1,12 +1,74 @@ - -
    %(archivelabel)s: - [ Tema ] - [ Zadeva ] - [ Avtor ] - [ Datum ] -
    %(archivelabel)s: +[ Tema ] +[ Zadeva ] +[ Avtor ] +[ Datum ] +
    - - - - - - - - - - - - - - - - - - + + + + + +

    - -- - -
    -

      -

    - Podatki o poštnem seznamu - - - -
    -

    -

    Èe vas zanimajo prejšnja sporoèila na poštnem seznamu, - si oglejte - Arhive. - -

    -
    - Uporaba -
    - Èe ¾elite poslati sporoèilo vsem èlanom seznama, ga pošljite na naslov - . + + + +<mm-list-name> Stran s podatki</mm-list-name> + + +

    + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + + + + + + + +
    + -- + +
    +

      +

    +Podatki o poštnem seznamu + + + +
    +

    +

    Èe vas zanimajo prejšnja sporoèila na poštnem seznamu, + si oglejte + Arhive. + +

    +
    +Uporaba +
    + Èe ¾elite poslati sporoèilo vsem èlanom seznama, ga pošljite na naslov + . -

    V spodnjem delu strani se lahko prijavite na seznam ali prilagodite obstojeèe - èlanstvo. -

    - Prijava na seznam -
    -

    - Na seznam se prijavite tako, da izpolnite +

    V spodnjem delu strani se lahko prijavite na seznam ali prilagodite obstojeèe + èlanstvo. +

    +Prijava na seznam +
    +

    + Na seznam se prijavite tako, da izpolnite obrazec. - -

      - - - - - - - - - - - - - - - - - -
      Vaš e-poštni naslov: -  
      Vaše ime (neobvezno): 
      Spodaj lahko vnesete geslo. To - geslo zagotavlja majhno zašèito, vendar lahko prepreèi - drugim, da bi zlorabili vašo naroènino. + +
        + + + + + + + + + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - - - -
        Vaš e-poštni naslov: + 
        Vaše ime (neobvezno): 
        Spodaj lahko vnesete geslo. To + geslo zagotavlja majhno zašèito, vendar lahko prepreèi + drugim, da bi zlorabili vaÅ¡o naroènino. Ne uporabljajte pomembnega gesla, saj - ga boste obèasno dobili po e-pošti v obliki besedila. + ga boste obèasno dobili po e-poÅ¡ti v obliki besedila. -

        Èe ne vnesete gesla, bo le-to samodejno ustvarjeno in - poslano, ko potrdite naroènino. Vedno lahko tudi zahtevate - e-sporoèilo z vašim geslom, ko urejate osebne nastavitve. - -
        -
        Vnesite geslo: 
        Ponovite geslo: 
        V katerem jeziku naj bodo prikazana sporoèila?  
        Ali ¾elite prejemati sporoèila zdru¾ena v dnevni povzetek? +

        Èe ne vnesete gesla, bo le-to samodejno ustvarjeno in + poslano, ko potrdite naroènino. Vedno lahko tudi zahtevate + e-sporoèilo z vašim geslom, ko urejate osebne nastavitve. + + +
        Vnesite geslo: 
        Ponovite geslo: 
        V katerem jeziku naj bodo prikazana sporoèila?  
        Ali ¾elite prejemati sporoèila zdru¾ena v dnevni povzetek? Ne - Da -
        -
        -
        - -
      -
      - - Èlani -
      - - - -

      - - - -

      - - - +
    Ne + Da +
    +
    +
    + + +

    + + Èlani +
    + + + +

    + + + +

    + +

    + diff --git a/templates/sl/options.html b/templates/sl/options.html index 73fe5f48..883f09e7 100644 --- a/templates/sl/options.html +++ b/templates/sl/options.html @@ -1,309 +1,342 @@ - - Konfiguracija èlana <MM-Presentable-User> za seznam <MM-List-Name> - - - - - -
    - - konfiguracija èlanstva za èlana - -
    + +Konfiguracija èlana <mm-presentable-user> za seznam <mm-list-name> +</mm-list-name></mm-presentable-user> + + + + +
    + + konfiguracija èlanstva za èlana + +

    - - - - - +
    - - stanje èlanstva, - geslo in mo¾nosti za poštni seznam . -
    - - - - -

    -

    + + + +
    + - stanje èlanstva, + geslo in mo¾nosti za poštni seznam . +
    + + +

    +

    - - +

    - - - + +
    - - Spreminjanje podatkov o èlanstvu za seznam -
    V spodnjih poljih lahko zamenjate naslov, s - katerim ste prijavljeni na poštni seznam, z novim naslovom. + + + - - - - -
    + +Spreminjanje podatkov o èlanstvu za seznam +
    V spodnjih poljih lahko zamenjate naslov, s + katerim ste prijavljeni na poštni seznam, z novim naslovom. Na novi naslov bo poslano obvestilo o spremembi, ki jo morate potrditi, da bo obdelana. -

    Èas za potrditev je . +

    Èas za potrditev je .

    Prav tako lahko spremenite ime (npr. Janez Novak). -

    Èe ¾elite spremeniti èlanstvo na vseh seznamih, na katere - ste prijavljeni na , oznaèite potrditveno polje +

    Èe ¾elite spremeniti èlanstvo na vseh seznamih, na katere + ste prijavljeni na , oznaèite potrditveno polje Spremeni globalno. -

    - - - - - + -
    Novi naslov:
    Ponovite +

    + + + + + - - -
    Novi naslov:
    Ponovite naslov:
    -
    - - + +
    Vaše ime +
    +
    + + - - -
    Vaše ime (neobvezno):
    -
    -

    Spremeni globalno

    - +
    + +

    +

    Spremeni globalno

    +

    - - - - - -
    - Odjava s seznama - Vaša ostala èlanstva na -
    - Vkljuèite polje za potrditev in pritisnite ta gumb, èe se - ¾elite odjaviti s tega poštnega seznama. Opozorilo: + + + + - + +
    +

    +Odjava s seznama +Vaša ostala èlanstva na +
    + Vkljuèite polje za potrditev in pritisnite ta gumb, èe se + ¾elite odjaviti s tega poštnega seznama. Opozorilo: Dejanje se bo izvedlo takoj!

    -

    - Videli boste seznam vseh ostalih poštnih seznamov na - , na katere ste prijavljeni. To uporabite, èe - ¾elite iste mo¾nosti spremeniti tudi za ostale sezname. +

    + Videli boste seznam vseh ostalih poštnih seznamov na + , na katere ste prijavljeni. To uporabite, èe + ¾elite iste mo¾nosti spremeniti tudi za ostale sezname.

    -

    -
    - - - - - +
    - Vaše geslo za seznam -
    - -
    -

    Ali ste pozabili geslo?

    -
    - Kliknite ta gumb in prejeli boste sporoèilo na e-poštni + + + - -
    +Vaše geslo za seznam +
    + +
    +

    Ali ste pozabili geslo?

    +
    + Kliknite ta gumb in prejeli boste sporoèilo na e-poštni naslov, s katerim ste prijavljeni. -

    -

    - -
    -
    - -
    -

    Sprememba gesla

    - - - - - - - - -
    Novo - geslo:
    Ponovi - geslo:
    - - -

    Spremeni globalno. -
    -
    - +

    +

    + +
    +

    + +
    +

    Sprememba gesla

    + + + + + + + + +
    Novo + geslo:
    Ponovi + geslo:
    + +

    Spremeni globalno. +
    +

    - - +
    - Vaše prijavne mo¾nosti za seznam -
    +
    +Vaše prijavne mo¾nosti za seznam +
    -

    Trenutne vrednosti so obkljukane. - -

    Nekatere mo¾nosti imajo polje Nastavi globalno. Èe oznaèite -to polje, bodo narejene spremembe vplivale na vse poštne sezname, -katerih èlan ste na . Èe ¾elite vedeti, na katere sezname -ste še prijavljeni, kliknite Poka¾i moje ostale naroènine. +

    Nekatere mo¾nosti imajo polje Nastavi globalno. Èe oznaèite +to polje, bodo narejene spremembe vplivale na vse poštne sezname, +katerih èlan ste na . Èe ¾elite vedeti, na katere sezname +ste še prijavljeni, kliknite Poka¾i moje ostale naroènine.

    - - - +
    - - Prejemanje pošte

    - Nastavite to mo¾nost na Omogoèeno, èe ¾elite prejemati - sporoèila s tega poštnega seznama. Èe nastavite Onemogoèeno, - boste ostali prijavljeni, ne boste pa prejemali sporoèil, npr. - ko ste na dopustu. Èe onemogoèite prejemanje pošte, je ne pozabite - omogoèiti, ko se vrnete; ni je mogoèe omogoèiti samodejno. -

    - Omogoèeno
    - Onemogoèeno

    - Nastavi globalno -

    + - - - + +

    - - - - - - + + - - - - - - + + + - - + - - + - - - +

    +
    + +Prejemanje pošte

    + Nastavite to mo¾nost na Omogoèeno, èe ¾elite prejemati + sporoèila s tega poštnega seznama. Èe nastavite Onemogoèeno, + boste ostali prijavljeni, ne boste pa prejemali sporoèil, npr. + ko ste na dopustu. Èe onemogoèite prejemanje pošte, je ne pozabite + omogoèiti, ko se vrnete; ni je mogoèe omogoèiti samodejno. +

    +Omogoèeno
    +Onemogoèeno

    +Nastavi globalno +

    - Prejemanje izvleèkov

    - Èe vkljuèite naèin prejemanja izvleèkov, boste prejemali vsa - sporoèila zdru¾ena v en veèji izvleèek (ponavadi enega dnevno, - odvisno od dejavnosti na seznamu), namesto posameznih sporoèil. - Ko izkljuèite prejemanje izvleèka, boste prejeli še zadnjega. -

    - Izkljuèeno
    - Vkljuèeno -
    - Izvleèki v MIME obliki ali kot navadno besedilo?

    - Vaš poštni program morda ne podpira MIME izvleèkov. Na - splošno so bolj priljubljeni MIME izvleèki, èe pa imate te¾ave +

    +Prejemanje izvleèkov

    + Èe vkljuèite naèin prejemanja izvleèkov, boste prejemali vsa + sporoèila zdru¾ena v en veèji izvleèek (ponavadi enega dnevno, + odvisno od dejavnosti na seznamu), namesto posameznih sporoèil. + Ko izkljuèite prejemanje izvleèka, boste prejeli še zadnjega. +

    +Izkljuèeno
    +Vkljuèeno +
    +Izvleèki v MIME obliki ali kot navadno besedilo?

    + Vaš poštni program morda ne podpira MIME izvleèkov. Na + splošno so bolj priljubljeni MIME izvleèki, èe pa imate te¾ave z branjem le-teh, izberite obliko navadnega besedila. -

    - MIME
    - Navadno besedilo

    - Nastavi globalno -

    +MIME
    +Navadno besedilo

    +Nastavi globalno +

    - Ali ¾elite prejemati lastna sporoèila?

    - Navadno boste prejeli kopijo vsakega sporoèila, ki ga pošljete - na seznam. Èe ne ¾elite prejemati kopij lastnih sporoèil, +

    +Ali ¾elite prejemati lastna sporoèila?

    + Navadno boste prejeli kopijo vsakega sporoèila, ki ga pošljete + na seznam. Èe ne ¾elite prejemati kopij lastnih sporoèil, izberite Ne. -

    - Ne
    - Da -
    - Ali ¾elite prejeti potrditveno sporoèilo, ko pošljete - sporoèilo na seznam?

    -

    - Ne
    - Da -
    - Ali ¾elite prejeti opomnik z geslom za ta seznam?

    - Enkrat meseèno boste prejeli opomnik, v katerem bo navedeno - geslo za vsak poštni seznam na tem gostitelju, na katerega - ste prijavljeni. To mo¾nost lahko izkljuèite za posamezne - sezname, tako da izberete Ne. Èe izkljuèite opomnike +

    +Ne
    +Da +
    +Ali ¾elite prejeti potrditveno sporoèilo, ko pošljete + sporoèilo na seznam?

    +

    +Ne
    +Da +
    +Ali ¾elite prejeti opomnik z geslom za ta seznam?

    + Enkrat meseèno boste prejeli opomnik, v katerem bo navedeno + geslo za vsak poštni seznam na tem gostitelju, na katerega + ste prijavljeni. To mo¾nost lahko izkljuèite za posamezne + sezname, tako da izberete Ne. Èe izkljuèite opomnike za vse prijavljene sezname, ne boste prejemali opomnikov z geslom. -

    - Ne
    - Da

    - Nastavi globalno -

    - Ali ¾elite prikriti svoje èlanstvo na tem seznamu?

    - Navadno je vaš e-poštni naslov prikazan na seznamu èlanov - tega seznama (pošiljateljem ne¾elene pošte v popaèeni - obliki). Èe ne ¾elite, da bi ostali videli vaš e-naslov - na seznamu èlanov, izberite Da za to mo¾nost. -

    - Ne
    - Da -
    - Kateri jezik ¾elite uporabiti za seznam?

    -

    - -
    - Na katere kategorije tem se ¾elite naroèiti?

    - Èe izberete eno ali veè tem, lahko filtrirate promet - na poštnem seznamu, tako da prejemate samo sporoèila - z izbranimi temami. Èe sporoèilo ustreza eni od teh - izbranih tem, boste to sporoèilo prejeli, drugaèe pa +

    +Ne
    +Da

    +Nastavi globalno +

    +Ali ¾elite prikriti svoje èlanstvo na tem seznamu?

    + Navadno je vaš e-poštni naslov prikazan na seznamu èlanov + tega seznama (pošiljateljem ne¾elene pošte v popaèeni + obliki). Èe ne ¾elite, da bi ostali videli vaš e-naslov + na seznamu èlanov, izberite Da za to mo¾nost. +

    +Ne
    +Da +
    +Kateri jezik ¾elite uporabiti za seznam?

    +

    + +
    +Na katere kategorije tem se ¾elite naroèiti?

    + Èe izberete eno ali veè tem, lahko filtrirate promet + na poštnem seznamu, tako da prejemate samo sporoèila + z izbranimi temami. Èe sporoèilo ustreza eni od teh + izbranih tem, boste to sporoèilo prejeli, drugaèe pa ne. -

    Èe sporoèilo ne ustreza nobeni temi, potem bo - prejem tega sporoèila odvisen od naslednjega izbora. - Èe ne izberete nobene teme, boste prejemali vsa - sporoèila, poslana na poštni seznam. -

    - -
    - Ali ¾elite prejemati sporoèila, ki ne ustrezajo nobenemu +

    Èe sporoèilo ne ustreza nobeni temi, potem bo + prejem tega sporoèila odvisen od naslednjega izbora. + Èe ne izberete nobene teme, boste prejemali vsa + sporoèila, poslana na poštni seznam. +

    + +
    +Ali ¾elite prejemati sporoèila, ki ne ustrezajo nobenemu tematskemu filtru?

    - Ta mo¾nost bo veljala le, èe ste naroèeni na vsaj eno - od zgornjih tem. Mo¾nost predstavlja privzeto pravilo - za dostavo sporoèil, ki ne ustrezajo nobenemu tematskemu - filtru. Èe izberete Ne, ne boste dobili sporoèil, - ki ne ustrezajo nobeni temi, èe pa izberete Da, - boste prejeli tudi takšna neopredeljiva sporoèila. - -

    Èe zgoraj niste izbrali nobene ¾elene teme, boste - prejeli vsako sporoèilo, ki bo poslano na poštni seznam. -

    - Ne
    - Da -
    - Izogni se dvojnim kopijam sporoèil?

    + Ta mo¾nost bo veljala le, èe ste naroèeni na vsaj eno + od zgornjih tem. Mo¾nost predstavlja privzeto pravilo + za dostavo sporoèil, ki ne ustrezajo nobenemu tematskemu + filtru. Èe izberete Ne, ne boste dobili sporoèil, + ki ne ustrezajo nobeni temi, èe pa izberete Da, + boste prejeli tudi takšna neopredeljiva sporoèila. + +

    Èe zgoraj niste izbrali nobene ¾elene teme, boste + prejeli vsako sporoèilo, ki bo poslano na poštni seznam. +

    +Ne
    +Da +
    +Izogni se dvojnim kopijam sporoèil?

    Kadar ste neposredno navedeni v polju Za: ali - Kp: sporoèila, lahko izberete, da ne dobite - kopije tega sporoèila še s poštnega seznama. + Kp: sporoèila, lahko izberete, da ne dobite + kopije tega sporoèila Å¡e s poÅ¡tnega seznama. Izberite Da, da se izognete prejemanju kopij s seznama; izberite Ne, da prejmete kopije. -

    Èe ima seznam omogoèena zasebna sporoèila med èlani +

    Èe ima seznam omogoèena zasebna sporoèila med èlani in izberete prejemanje kopij, bo vsaki kopiji dodana glava X-Mailman-Copy: yes. -

    - Ne
    - Da

    - Nastavi globalno -

    -
    -
    +Ne
    +Da

    +Nastavi globalno +

    +
    +
    -

    - - - - + + +

    diff --git a/templates/sl/private.html b/templates/sl/private.html index 53b3f7f9..0e513493 100755 --- a/templates/sl/private.html +++ b/templates/sl/private.html @@ -1,60 +1,121 @@ - Avtentikacija za zasebne arhive èlana %(realname)s - - - -
    +Avtentikacija za zasebne arhive èlana %(realname)s + + + + %(message)s - - - - - - - - - - - - - - - -
    - Avtentikacija za zasebne - arhive èlana %(realname)s -
    E-poštni naslov:
    Geslo:
    -
    -

    Pomembno: Od tega trenutka - dalje morate v brskalniku imeti omogoèene piškotke, - drugaèe skrbniške spremembe ne bodo veljale. + + + + + + + + + + + + + + + +
    +Avtentikacija za zasebne + arhive èlana %(realname)s +
    E-poštni naslov:
    Geslo:
    +
    +

    Pomembno: Od tega trenutka + dalje morate v brskalniku imeti omogoèene piškotke, + drugaèe skrbniške spremembe ne bodo veljale. -

    Skrbniški vmesnik programa Mailman uporablja - piškotke sej zato, da se vam ni potrebno za vsako - skrbniško operacijo ponovno avtenticirati. Ta - piškotek bo potekel samodejno, ko boste zaprli +

    SkrbniÅ¡ki vmesnik programa Mailman uporablja + piÅ¡kotke sej zato, da se vam ni potrebno za vsako + skrbniÅ¡ko operacijo ponovno avtenticirati. Ta + piÅ¡kotek bo potekel samodejno, ko boste zaprli brskalnik ali se odjavili s povezavo - Odjava v Ostale skrbniške dejavnosti + Odjava v Ostale skrbniÅ¡ke dejavnosti (ki bo vidno, takoj ko se prijavite).

    - - - - - - + + + +
    - Password Reminder -
    If you don't remember your password, enter your email address + + + + + + - - - - -
    +Password Reminder +
    If you don't remember your password, enter your email address above and click the Remind button and your password will be emailed to you.
    - +
    +

    diff --git a/templates/sl/roster.html b/templates/sl/roster.html index f42fe4d3..4ea0c7e1 100644 --- a/templates/sl/roster.html +++ b/templates/sl/roster.html @@ -1,53 +1,112 @@ - - - <MM-List-Name> èlani - - - - -

    - - - - - - - - - - - - - - - -
    - - èlani -
    - -

    -

    - -

    Kliknite na svoj naslov, da odprete stran z nastavitvami - èlanstva.
    (Vnosi v oklepajih imajo onemogoèeno - prejemanje sporoèil s seznama.)

    -
    -
    - - èlanov , ki ne prejemajo izvleèka: -
    -
    -
    - èlanov - , ki prejemajo izvleèek: -
    -
    -

    -

    -

    -

    - - - + + +<mm-list-name> èlani</mm-list-name> + + +

    + + + + + + + + + + + + + + + +
    + + èlani +
    +

    +

    +

    Kliknite na svoj naslov, da odprete stran z nastavitvami + èlanstva.
    (Vnosi v oklepajih imajo onemogoèeno + prejemanje sporoèil s seznama.)

    +
    +
    + + èlanov , ki ne prejemajo izvleèka: +
    +
    +
    + èlanov + , ki prejemajo izvleèek: +
    +
    +

    +

    +

    +

    + +

    + diff --git a/templates/sl/subscribe.html b/templates/sl/subscribe.html index 9f1c6afe..66e33d8d 100644 --- a/templates/sl/subscribe.html +++ b/templates/sl/subscribe.html @@ -1,9 +1,72 @@ -Rezultati prijave na seznam <MM-List-Name> +Rezultati prijave na seznam <mm-list-name></mm-list-name> -

    Rezultati prijave na seznam

    - - - +

    Rezultati prijave na seznam

    + + + diff --git a/templates/sr/admindbdetails.html b/templates/sr/admindbdetails.html index 8be8382c..b43c4bd4 100644 --- a/templates/sr/admindbdetails.html +++ b/templates/sr/admindbdetails.html @@ -1,23 +1,86 @@ -ÐдминиÑтративни захтјеви Ñе приказују на један од два начина, на +ÐдминиÑтративни захтјеви Ñе приказују на један од два начина, на Ñтраници прегледа, и на Ñтраници Ñа детаљима. Страница прегледа Ñадржи захтхјеве за укључење и иÑкључење на чекању, те поруке које Ñу задржане ради вашег одобрења. Страница Ñа детаљима Ñадржи детаљнији преглед Ñваке поруке.

    Ðа Ñвим Ñтраницама, Ñледеће опције Ñу доÑтупне:

      -
    • Одлагање -- Одлажете вашу одлуку за каÑније. Ðикаква акција +
    • Одлагање -- Одлажете вашу одлуку за каÑније. Ðикаква акција Ñада није покренута за овај админиÑтративни захтјев, али за задржане поруке и даље можете проÑлиједити или Ñпремити поруку (погледајте доле) -
    +
      -
    • Прихватање +
    • Прихватање -- Прихватате поруку, шаљући је на лиÑту. За захтјеве за чланÑтво, прихвата - Ñе тражени ÑтатуÑ.
      -
    • Одбијање -- Одбија + Ñе тражени ÑтатуÑ.
      +
    • Одбијање -- Одбија Ñе порука, шаље Ñе обавјештење пошиљаоцу, и @@ -29,12 +92,11 @@ У Ñваком Ñлучају, потребно је да унеÑете разлог - ваше одлуке.
      -
    • Занемаривање -- Одбаци Ñе почетна порука, без Ñлања било каквог обавјештења. + ваше одлуке.
      +
    • Занемаривање -- Одбаци Ñе почетна порука, без Ñлања било каквог обавјештења. За затхеве за чланÑтво, захтјев Ñе одбија без додатног обавјештења. Ова опција је уобичајена нпр. за Ñлучај да је у питању Ñпам. -
    - +

    За задржане поруке, укључите опцију Задржавање ако желите да Ñачувате дупликат поруке за админиÑтратора Ñајта. Ово је кориÑно за увредљиве поруке које желите да занемарите, али желите да их имате за каÑнију анализу. @@ -49,9 +111,9 @@ нове чланове на пробу, на оÑнову чега ви одлучујете коме Ñе може вјеровати да шаље без одобрења.

    Ðко пошиљалац није члан лиÑте, можете додати његову е-адреÑу на филтер - пошиљалаца. Дати филтер је опиÑан у Ñекцији филтер-приватноÑÑ‚ пошиљаоца, и може аутоматÑки прихватати, + пошиљалаца. Дати филтер је опиÑан у Ñекцији филтер-приватноÑÑ‚ пошиљаоца, и може аутоматÑки прихватати, задржавати, одбацивати или занемаривати поруке. Ова опција није доÑтупна ако је адреÑа већ на неком од филтера пошиљалаца.

    Када завршите, пошаљите Ñве промјене кориÑтећи таÑтер при врху Ñтранице.

    Повратак на Ñтраницу прегледа +

    \ No newline at end of file diff --git a/templates/sr/admindbpreamble.html b/templates/sr/admindbpreamble.html index f9c9e8ec..85abdfe4 100644 --- a/templates/sr/admindbpreamble.html +++ b/templates/sr/admindbpreamble.html @@ -1,6 +1,70 @@ -Ова Ñтраница Ñадржи лиÑту порука које Ñу задржане за одобрење. Тренутно показује +Ова Ñтраница Ñадржи лиÑту порука које Ñу задржане за одобрење. Тренутно показује %(description)s

    За Ñваки админиÑтративни захтјев, молимо Ð²Ð°Ñ Ð´Ð° изаберете одговарајућу опцију, те након извршених промјена кликнете на одговарајући таÑтер,обично при врху екрана. Детаљније инÑтрукције Ñу доÑтупне овдје.

    Можете такође видјети преглед чекајућих захтјева. +

    \ No newline at end of file diff --git a/templates/sr/admindbsummary.html b/templates/sr/admindbsummary.html index d105e60e..5fbdb7b4 100644 --- a/templates/sr/admindbsummary.html +++ b/templates/sr/admindbsummary.html @@ -1,4 +1,67 @@ -Ова Ñтрана Ñадржи преглед тренутних уÑлова за ваше укључење у лиÑту Ñлања +Ова Ñтрана Ñадржи преглед тренутних уÑлова за ваше укључење у лиÑту Ñлања %(listname)s. Прво, пронаћи ћете лиÑту Ñа чекајућим захтјевима за укључење и иÑкључење, заједно Ñа порукама које Ñу задржане за пуштање. @@ -7,3 +70,4 @@ Детаљнија упутÑтва Ñу доÑтупна.

    Можете такође погледати детаље Ñвих задржаних порука. +

    \ No newline at end of file diff --git a/templates/sr/admlogin.html b/templates/sr/admlogin.html index 14afaac6..9ab95fd4 100755 --- a/templates/sr/admlogin.html +++ b/templates/sr/admlogin.html @@ -1,35 +1,95 @@ - %(listname)s %(who)s Authentication - - - - -
    +%(listname)s %(who)s Authentication + + + + + %(message)s - - - - - - - - - - - -
    %(listname)s - %(who)s: провјера
    Лозинка за лиÑту%(who)s:
    -
    - -

    Важно: Одавде па надаље, морате имати укључене - "колачиће" (cookies) у вашем претраживачу интернета, иначе промјене + + + + + + + + + + + +
    %(listname)s + %(who)s: провјера
    Лозинка за лиÑту%(who)s:
    +
    +

    Важно: Одавде па надаље, морате имати укључене + "колачиће" (cookies) у вашем претраживачу интернета, иначе промјене неће имати ефекта. -

    "Колачићи" Ñе кориÑте у Mailman-овом админиÑтративном интерфејÑу - да не биÑте морали да Ñе поÑле Ñваке акције поново пријављујете. "Колачић" +

    "Колачићи" Ñе кориÑте у Mailman-овом админиÑтративном интерфејÑу + да не биÑте морали да Ñе поÑле Ñваке акције поново пријављујете. "Колачић" неће више важити поÑлије вашег одјављивања Ñа админиÑтративног интерфејÑа. -

    +

    diff --git a/templates/sr/archidxentry.html b/templates/sr/archidxentry.html index f9bb57aa..5c620b20 100644 --- a/templates/sr/archidxentry.html +++ b/templates/sr/archidxentry.html @@ -1,4 +1,68 @@ -
  • %(subject)s -  -%(author)s - +
  • %(subject)s +  +%(author)s + +
  • \ No newline at end of file diff --git a/templates/sr/archidxfoot.html b/templates/sr/archidxfoot.html index 558936d6..c3371c88 100644 --- a/templates/sr/archidxfoot.html +++ b/templates/sr/archidxfoot.html @@ -1,21 +1,85 @@ - -

    - ПоÑледња порука: - %(lastdate)s
    - Ðрхивирана: %(archivedate)s -

    -

      -
    • Сортирање порука: + +

      +ПоÑледња порука: +%(lastdate)s
      +Ðрхивирана: %(archivedate)s +

      +

      -

      -


      - Ову архиву генериÑао је +
    +

    +


    +Ову архиву генериÑао је Pipermail %(version)s. - - + + +

    \ No newline at end of file diff --git a/templates/sr/archidxhead.html b/templates/sr/archidxhead.html index 3d74eea2..9ca3a200 100644 --- a/templates/sr/archidxhead.html +++ b/templates/sr/archidxhead.html @@ -1,21 +1,84 @@ - - - - Ðрхива: %(listname)s %(archive)s, по %(archtype)s - + + + + +Ðрхива: %(listname)s %(archive)s, по %(archtype)s + %(encoding)s - - - - + + +

    %(archive)s : архива: %(archtype)s

    -
      - -
    • Сортирање порука: %(thread_ref)s %(subject_ref)s %(author_ref)s + -

      Почетак: %(firstdate)s
      - Крај: %(lastdate)s
      - Порука: %(size)s

      -

        +
      +

      Почетак: %(firstdate)s
      +Крај: %(lastdate)s
      +Порука: %(size)s

      +

        +

      \ No newline at end of file diff --git a/templates/sr/archlistend.html b/templates/sr/archlistend.html index 9bc052dd..2e1191b0 100644 --- a/templates/sr/archlistend.html +++ b/templates/sr/archlistend.html @@ -1 +1,64 @@ -
    + diff --git a/templates/sr/archliststart.html b/templates/sr/archliststart.html index 9b2aa7aa..fd90e207 100644 --- a/templates/sr/archliststart.html +++ b/templates/sr/archliststart.html @@ -1,7 +1,69 @@ - - - - - - - \ No newline at end of file +
    Преглед архиве: Ð’ерзија за преузимање
    + + + + +
    Преглед архиве: Верзија за преузимање
    \ No newline at end of file diff --git a/templates/sr/archtoc.html b/templates/sr/archtoc.html index a081dd09..b9b76815 100644 --- a/templates/sr/archtoc.html +++ b/templates/sr/archtoc.html @@ -1,14 +1,76 @@ - - - - Ðрхива: %(listname)s - + + + + +Ðрхива: %(listname)s + %(meta)s - - - + +

    Ðрхива порука лиÑте: %(listname)s

    -

    Можете добити више информација о овој лиÑти овдје или можете преузети комплетну архиву (%(size)s).

    @@ -16,5 +78,5 @@

    Ðрхива порука лиÑте: %(listname)s

    %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/sr/archtocentry.html b/templates/sr/archtocentry.html index 7f7434f3..9afe085b 100644 --- a/templates/sr/archtocentry.html +++ b/templates/sr/archtocentry.html @@ -1,10 +1,71 @@ - - - %(archivelabel)s: - - [ Ñтабло] [ - тема] [ аутор] [ + + +%(archivelabel)s: + [ Ñтабло] [ + тема] [ аутор] [ датум ] %(textlink)s - diff --git a/templates/sr/article.html b/templates/sr/article.html index 998f560c..ab2d87f8 100644 --- a/templates/sr/article.html +++ b/templates/sr/article.html @@ -1,12 +1,13 @@ - - - %(title)s - - - - - +

    Ðрхива лиÑте: %(listname)s

    -

    Још увијек нема поÑланих порука, тако да је архива празна.. Више информација о лиÑти.

    - - + + diff --git a/templates/sr/handle_opts.html b/templates/sr/handle_opts.html index 77df115e..17580c53 100644 --- a/templates/sr/handle_opts.html +++ b/templates/sr/handle_opts.html @@ -1,9 +1,72 @@ - - -<MM-List-Name> <MM-Operation> Резултати + + +<mm-list-name> <mm-operation> Резултати</mm-operation></mm-list-name> -

    Резултати

    - - - +

    Резултати

    + + + diff --git a/templates/sr/headfoot.html b/templates/sr/headfoot.html index 86bf8115..c9be02c2 100644 --- a/templates/sr/headfoot.html +++ b/templates/sr/headfoot.html @@ -1,8 +1,71 @@ -Овај текÑÑ‚ може да Ñадржи Python-ово +Овај текÑÑ‚ може да Ñадржи Python-ово форматирање које Ñе утврђује на оÑнову атрибута лиÑта. Дозвољене замјене лиÑте Ñу:
      -
    • real_name - Пуно име лиÑте, обично Ñадржи и велика Ñлова +
    • real_name - Пуно име лиÑте, обично Ñадржи и велика Ñлова
    • list_name - Име по којем Ñе лиÑта препознаје на веб адреÑама, величина Ñлова је неважна. (због компатибилноÑти, _internal_name је еквивалентно.) @@ -12,4 +75,4 @@
    • description - Кратки Ð¾Ð¿Ð¸Ñ Ð»Ð¸Ñте Ñлања.
    • info - Пуни Ð¾Ð¿Ð¸Ñ Ð»Ð¸Ñте Ñлања.
    • cgiext - ЕкÑтензија имена CGI Ñкрипти. -
    + diff --git a/templates/sr/listinfo.html b/templates/sr/listinfo.html index 81fc6938..e1548141 100644 --- a/templates/sr/listinfo.html +++ b/templates/sr/listinfo.html @@ -1,128 +1,189 @@ - - - - <MM-List-Name> Страница Ñа информацијама - - - -

    - - - - - - - - - - - - - - - - - - - - + + + + + +

    - -- - -
    -

      -

    О - лиÑти - - -
    -

    -

    Да биÑте видјели доÑадашње поруке на лиÑти поÑјетите - архиву.

    -
    Употреба -
    Да би порука отишла на лиÑту, пошаљите је на . + + + + + +<mm-list-name> Страница Ñа информацијама</mm-list-name> + + +

    + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + +
    + -- + +
    +

      +

    О + лиÑти + + +
    +

    +

    Да биÑте видјели доÑадашње поруке на лиÑти поÑјетите + архиву.

    +
    Употреба +
    Да би порука отишла на лиÑту, пошаљите је на .

    Можете Ñе укључити на лиÑту или промјенити ваш ÑÑ‚Ð°Ñ‚ÑƒÑ Ñ‡Ð»Ð°Ð½Ð° преко Ñледећих Ñекција. -

    Ð£Ð¿Ð¸Ñ -
    -

    Укључујете Ñе попуњавајући Ñледећи формулар: -

      - - - - - - - - - - - - - - + + + + + + - - - - - - -
      Ваша ел. адреÑа:  
      Ваше име: 
      Овдје можете унијети вашу лозинку. +

      Ð£Ð¿Ð¸Ñ +
      +

      Укључујете Ñе попуњавајући Ñледећи формулар: +

        + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - - - - - - -
        Ваша ел. адреÑа:  
        Ваше име: 
        Овдје можете унијети вашу лозинку. То обезбјеђује Ñамо дјелимичну ÑигурноÑÑ‚, али онемогућава оÑтале да поремете ваш чланÑки ÑтатуÑ. Ðе кориÑтите једноÑтавне лозинке, пошто ћете једном мјеÑечно добијати лозинку за подÑјећање. -

        Ðко не унеÑете лозинку, наш ÑиÑтем ће аутоматÑки - изабрати једну Ñа ваÑ.
        Лозинка: 
        Поново: 
        Језик:  
        Да ли желите да Ñве поруке добијате у прегледу порука, једном +

        Ðко не унеÑете лозинку, наш ÑиÑтем ће аутоматÑки + изабрати једну Ñа ваÑ.
        Лозинка: 
        Поново: 
        Језик:  
        Да ли желите да Ñве поруке добијате у прегледу порука, једном дневно? Ðе Да
        -
        -
        -
      -
      - Чланови
      - - - -

      - - - -

      - - - +
    Ðе Да
    +
    +
    + +

    + Чланови
    + + + +

    + + + +

    + +

    + diff --git a/templates/sr/options.html b/templates/sr/options.html index 7abe7d26..64d18465 100644 --- a/templates/sr/options.html +++ b/templates/sr/options.html @@ -1,44 +1,104 @@ - - <MM-Presentable-User> Контрола чланÑтва за <MM-List-Name> - - - - - -
    - - : контрола чланÑтва за: - -
    + +<mm-presentable-user> Контрола чланÑтва за <mm-list-name> +</mm-list-name></mm-presentable-user> + + + + +
    + +: контрола чланÑтва за: + +

    - - - - - +
    - чланÑки ÑтатуÑ, лозинка и опције за лиÑту - . -
    - - - - -

    -

    + + + +
    + чланÑки ÑтатуÑ, лозинка и опције за лиÑту + . +
    + + +

    +

    - - +

    - - - - + +
    - - Промјена ваших информација на лиÑти -
    Можете промјенити адреÑу преко које Ñте пријављени на лиÑту, + + + + - - - - - -
    + +Промјена ваших информација на лиÑти +
    Можете промјенити адреÑу преко које Ñте пријављени на лиÑту, уноÑећи нову у поље иÑпод. У том Ñлучају порука Ñа потврдом упиÑа ће бити поÑлана на ту адреÑу, и промјена мора бити потврђена.

    МогућноÑÑ‚ потврде важи још дана. @@ -49,221 +109,195 @@

    Ðко желите да направите промјене ÑтатуÑа чланÑтва за Ñве лиÑте на које Ñте укључени на , укључите квадратић Глобална промјена . -

    - - - - - - - -
    Ðова адреÑа:
    Поново, за потврду:
    -
    - - +
    Ваше име +

    + + + + + + + +
    Ðова адреÑа:
    Поново, за потврду:
    +
    + + - - -
    Ваше име :
    -
    -

    Глобална промјена

    - +
    + +

    +

    Глобална промјена

    +

    - - - - - -
    - ИÑкључивање Ñа лиÑте - Ваша друга чланÑтва на -
    + + + + - +

    +
    +ИÑкључивање Ñа лиÑте +Ваша друга чланÑтва на +
    Укључите квадратић за потврду и притиÑните овај таÑтер да биÑте Ñе иÑкључили Ñа ове лиÑте Ñлања. Упозорење: Одлука Ñтупа на Ñнагу моментално!

    -

    Можете видјети ÑпиÑак оÑталих лиÑта на које Ñте пријављени..

    -

    КориÑтите ако желите да Ñе ваше промјене глобално одразе.

    -

     

    -

    -

    - -
    -

    Можете видјети ÑпиÑак оÑталих лиÑта на које Ñте пријављени..

    +

    КориÑтите ако желите да Ñе ваше промјене глобално одразе.

    +

     

    +

    +

    + +
    +

    - - - - - +
    - Ваша лозинка за -
    - -
    -

    Заборављена лозинка?

    -
    + + + - -
    +Ваша лозинка за +
    + +
    +

    Заборављена лозинка?

    +
    Кликните на овај таÑтер да би лозинка била поÑлана на вашу е-адреÑу. -

    -

    - -
    -
    - -
    -

    Промјена лозинке

    - - - - - - - - -
    Ðова лозинка:
    Поново за потврду:
    - - -

    глобална промјена. -
    -
    - +

    +

    + +
    +

    + +
    +

    Промјена лозинке

    + + + + + + + + +
    Ðова лозинка:
    Поново за потврду:
    + +

    глобална промјена. +
    +

    - - - - +
    Ваша подешавања
    + + +
    Ваша подешавања
    - -

    Тренутне вриједноÑти Ñу укључене (означене). +

    Тренутне вриједноÑти Ñу укључене (означене).

    Ðеке опције имају могућноÑÑ‚ да буду укључене глобално. Означавајући одговарајуће поље, промјене које направите одразиће Ñе на Ñве лиÑте чији Ñте члан. Note that some of the options have a Set globally checkbox. Checking this field will cause the changes to be made to every mailing list that you are a member of on . - - - - - + + + + %(textlink)s - diff --git a/templates/sv/article.html b/templates/sv/article.html index 3dbf026e..f3ec3f5a 100644 --- a/templates/sv/article.html +++ b/templates/sv/article.html @@ -1,12 +1,13 @@ - - - %(title)s - - - - - + +

    Arkiv för e-postlistan %(listname)s

    +

    Inga meddelanden har skickats till denna lista än, så arkivet är för närvarande tomt. Mer information om listan finns tillgänglig.

    + + diff --git a/templates/sv/headfoot.html b/templates/sv/headfoot.html index db1104b4..5e0b5a43 100644 --- a/templates/sv/headfoot.html +++ b/templates/sv/headfoot.html @@ -1,9 +1,72 @@ -Den här texten kan innehålla formateringskoder som byts ut mot värden från listans uppsättning. För detaljer, se Pythons formateringsregler (engelska). Giltiga koder är: +Den här texten kan innehÃ¥lla formateringskoder som byts ut mot värden frÃ¥n listans uppsättning. För detaljer, se Pythons formateringsregler (engelska). Giltiga koder är:
      -
    • real_name - Listans formaterade namn, vanligtvis listnamnet med stor initial eller stora bokstäver rakt igenom. -
    • list_name - Listens namn, såsom det används i URL:er, där det har betydelse om namnet stavas med stora eller små bokstäver. -
    • host_name - Internetadressen (fully qualified domain name) till maskinen som listservern körs på. -
    • web_page_url - Bas-URL för Mailman. Denna kan läggas tillsammans med till exempel listinfo/%(list_name)s för att skapa URL:en till en listas informationssida. +
    • real_name - Listans formaterade namn, vanligtvis listnamnet med stor initial eller stora bokstäver rakt igenom. +
    • list_name - Listens namn, sÃ¥som det används i URL:er, där det har betydelse om namnet stavas med stora eller smÃ¥ bokstäver. +
    • host_name - Internetadressen (fully qualified domain name) till maskinen som listservern körs pÃ¥. +
    • web_page_url - Bas-URL för Mailman. Denna kan läggas tillsammans med till exempel listinfo/%(list_name)s för att skapa URL:en till en listas informationssida.
    • description - En kort beskrivning av listan. -
    • info - Fullständig beskrivning av listan. -
    • cgiext - Tillägg som läggs till CGI-skript.
    +
  • info - Fullständig beskrivning av listan. +
  • cgiext - Tillägg som läggs till CGI-skript.
  • diff --git a/templates/sv/listinfo.html b/templates/sv/listinfo.html index 0470d725..ed16303a 100644 --- a/templates/sv/listinfo.html +++ b/templates/sv/listinfo.html @@ -1,127 +1,188 @@ - - - - <MM-List-Name> Infosida - - - -

    -

    Слање порука -

    Укључите ову опциеј да биÑте добијале поруке Ñа ове лиÑте. Ðко иÑкључите + + + - - +

    + - - + - - - - - + + + + +

    - - - + - - - - - - - - - - + + + + + + + + - - - - + + + - - - - - - - - + + + + + + - - - + + - - - - - + + + + - - - - +

    + +
    Слање порука +

    Укључите ову опциеј да биÑте добијале поруке Ñа ове лиÑте. Ðко иÑкључите оÑтајете члан, али не желите да добијате пошту (нпр. док Ñте на одмору). Када вам затреба, не заборавите да ову опцију поново укључите. -

    Укључено
    - ИÑкључено -

    Глобално

    Укључено
    +ИÑкључено +

    Глобално

    Преглед порука -

    Ðко ову опцију укључите, добићете Ñве поруке Ñпојене у једно пиÑмо (обично +

    Преглед порука +

    Ðко ову опцију укључите, добићете Ñве поруке Ñпојене у једно пиÑмо (обично једну дневно, могуће и више), умјеÑто Ñваке појединачно. -

    ИÑкључено
    - Укључено
    Добијате MIME или текÑтуални преглед опрука? -

    Ваш читач поште можда не подржава Mime прегледе. Ова опција је углавном +

    ИÑкључено
    +Укључено
    Добијате MIME или текÑтуални преглед опрука? +

    Ваш читач поште можда не подржава Mime прегледе. Ова опција је углавном препоручена, оÑим у Ñлучају да имате проблем при читању порука. -

    - MIME
    - Обичан теÑÑ‚ -

    Глобално

    +MIME
    +Обичан теÑÑ‚ +

    Глобално

    СопÑтвене поруке -

    Обично ћете добити копију Ñваке поруке коју Ñте поÑлали на лиÑту. Ðко +

    СопÑтвене поруке +

    Обично ћете добити копију Ñваке поруке коју Ñте поÑлали на лиÑту. Ðко не желите да добијате ту копију, иÑкључите ову опцију. -

    Ðе
    - Да
    Примање потврдне за Ñлање поруке на лиÑту -

    -

    Ðе
    - Да
    ПодÑјетник лозинке? -

    Једном мјеÑечно ћете добити поруку која Ñадржи лозинку за Ñве лиÑте на +

    Ðе
    +Да
    Примање потврдне за Ñлање поруке на лиÑту +

    +

    Ðе
    +Да
    ПодÑјетник лозинке? +

    Једном мјеÑечно ћете добити поруку која Ñадржи лозинку за Ñве лиÑте на које Ñте учлањени. -

    Ðе
    - Да -

    Глобално

    Сакривање Ñа лиÑте чланова? -

    Када +

    Ðе
    +Да +

    Глобално

    Сакривање Ñа лиÑте чланова? +

    Када неко прегледа лиÑту чланова, обично ће и ваша адреÑа бити приказана (заштићена - од Ñпамера).
    + од Ñпамера).
    Ðко не желите да Ñе ваша адреÑа приказује на тој лиÑти, потврдите то овдје. -

    Ðе
    - Да
    Језик -

    -

    - -
    Које тематÑке категорије желите? -

    Избором једне иил више тема, можете филтрирати Ñаобраћај на лиÑти, тако +

    Ðе
    +Да
    Језик +

    +

    + +
    Које тематÑке категорије желите? +

    Избором једне иил више тема, можете филтрирати Ñаобраћај на лиÑти, тако да примате Ñамо одговарајуће поруке.

    Ðко порука не одговара ни једној од тема, Ð¿Ñ€ÐµÐ½Ð¾Ñ Ð¸Ñте завиÑи од правила подешеног у овој опцији. Ðко не изаберете теме од интереÑовања, добијаћете Ñве поруке Ñа лиÑте. -

    - -
    Желите ли да примате поруку која Ñе не поклапа - ни Ñа једним филтером теме? -

    Ова опција функционише једино ако Ñте упиÑани најмање на једну тему. +

    + +
    Желите ли да примате поруку која Ñе не поклапа + ни Ñа једним филтером теме? +

    Ова опција функционише једино ако Ñте упиÑани најмање на једну тему. Опција Ñе одноÑи на уобичајено понашање за поруке које не одговарају ни једном филтеру.

    Ðко ни једна тема од интереÑа није изабрана, добијаћете Ñве поруке. -

    Ðе
    - Да
    Избјегавање дупликата порука -

    Када Ñте директно укључени у To: или Cc: поља поруке Ñа лиÑте, можете +

    Ðе
    +Да
    Избјегавање дупликата порука +

    Када Ñте директно укључени у To: или Cc: поља поруке Ñа лиÑте, можете изабрати да не примате другу копију Ñа лиÑте Ñлања. Да је за потврду примања копија, не је за добијање копија. -

    Да
    - Ðе -

    Глобално

    -
    -
    Да
    +Ðе +

    Глобално

    +
    +
    -

    - - - - + + +

    diff --git a/templates/sr/private.html b/templates/sr/private.html index fcb9a60b..710b3a70 100755 --- a/templates/sr/private.html +++ b/templates/sr/private.html @@ -1,55 +1,115 @@ - + - Пријава за улаз у приватну архиву лиÑте: %(realname)s - - - - -
    +Пријава за улаз у приватну архиву лиÑте: %(realname)s + + + + + %(message)s - - - - - - - - - - - - - - - -
    Заштићене - архиве: %(realname)s
    Ел. адреÑа:
    Лозинка:
    -
    - -

    Важно: Одавде па надаље, морате имати укључене - "колачиће" (cookies) у вашем претраживачу интернета, иначе промјене + + + + + + + + + + + + + + + +
    Заштићене + архиве: %(realname)s
    Ел. адреÑа:
    Лозинка:
    +
    +

    Важно: Одавде па надаље, морате имати укључене + "колачиће" (cookies) у вашем претраживачу интернета, иначе промјене неће имати ефекта. -

    "Колачићи" Ñе кориÑте у Mailman-овом админиÑтративном интерфејÑу - да не биÑте морали да Ñе поÑле Ñваке акције поново пријављујете. "Колачић" +

    "Колачићи" Ñе кориÑте у Mailman-овом админиÑтративном интерфејÑу + да не биÑте морали да Ñе поÑле Ñваке акције поново пријављујете. "Колачић" неће више важити поÑлије вашег одјављивања Ñа админиÑтративног интерфејÑа.

    - - - - - - + + + +
    - Password Reminder -
    If you don't remember your password, enter your email address + + + + + + - - - - -
    +Password Reminder +
    If you don't remember your password, enter your email address above and click the Remind button and your password will be emailed to you.
    - +
    +

    diff --git a/templates/sr/roster.html b/templates/sr/roster.html index 6b506f97..0c028ff9 100644 --- a/templates/sr/roster.html +++ b/templates/sr/roster.html @@ -1,51 +1,110 @@ - - - - <MM-List-Name> УпиÑани - - - - -

    - - - - - - - - - - - - - - - -
    - - УпиÑани на лиÑту: -
    - -

    -

    - -

    кликните на вашу адреÑу да видите вашу Ñтраницу Ñа опцијама -
    (заграде значе да је Ñлање иÑкључено)

    -
    -
    - - Чланови лиÑте (појединачне поруке): -
    -
    -
    - Чланови лиÑте (преглед порука): -
    -
    -

    -

    -

    -

    - - - + + + +<mm-list-name> УпиÑани</mm-list-name> + + +

    + + + + + + + + + + + + + + + +
    + + УпиÑани на лиÑту: +
    +

    +

    +

    кликните на вашу адреÑу да видите вашу Ñтраницу Ñа опцијама +
    (заграде значе да је Ñлање иÑкључено)

    +
    +
    + + Чланови лиÑте (појединачне поруке): +
    +
    +
    + Чланови лиÑте (преглед порука): +
    +
    +

    +

    +

    +

    + +

    + diff --git a/templates/sr/subscribe.html b/templates/sr/subscribe.html index 30933303..4283fc01 100644 --- a/templates/sr/subscribe.html +++ b/templates/sr/subscribe.html @@ -1,9 +1,72 @@ - + -<MM-List-Name> Резултати упиÑа +<mm-list-name> Резултати упиÑа</mm-list-name> -

    Резултати упиÑа

    - - - +

    Резултати упиÑа

    + + + diff --git a/templates/sv/admindbdetails.html b/templates/sv/admindbdetails.html index 907d099f..a4b4ba99 100644 --- a/templates/sv/admindbdetails.html +++ b/templates/sv/admindbdetails.html @@ -1,20 +1,81 @@ -Administrativa förfrågningar kan visas på två sätt, antingen som en översikt, eller med alla detaljer. På översiktssidan visas både väntande anmälningar/avanmälningar och e-postbrev som väntar på godkännande för att distribueras via listan. Detaljsidan visar dessutom e-postbrevens meddelandehuvuden och ett utdrag av innehållet. -

    På båda sidorna kan du ange följande åtgärder: +Administrativa förfrÃ¥gningar kan visas pÃ¥ tvÃ¥ sätt, antingen som en översikt, eller med alla detaljer. PÃ¥ översiktssidan visas bÃ¥de väntande anmälningar/avanmälningar och e-postbrev som väntar pÃ¥ godkännande för att distribueras via listan. Detaljsidan visar dessutom e-postbrevens meddelandehuvuden och ett utdrag av innehÃ¥llet. +

    På båda sidorna kan du ange följande åtgärder:

      -
    • Vänta -- Vänta med avgörandet. Ingenting blir gjort med denna förfrågan nu, men e-postbrev som väntar på godkännande kan du hålla på eller skicka vidare (se nedan). -
    • Godkänna -- Godkänna meddelandet som det är och distribuera det till listan. För förfrågningar om medlemskap, anmälningar, avanmälningar och andra ändringar. -
    • Avslå -- Radera meddelandet och ge besked tillbaka till avsändaren om att meddelandet inte blev godkänt. För förfrågningar om medlemskap, avslag på anmälan eller avanmälan. Du bör alltid också ange en orsak i den tillhörande textrutan. -
    • Kasta -- Radera meddelandet utan att något besked skickas till avsändaren. Användbart för skräppost och reklampost (spam). För förfrågningar om medlemskap, avslag på anmälan eller avanmälan utan att den som försökte anmäla sig eller avanmäla sig på listan får veta något. -
    - -

    Kryssa för Behåll meddelandet om du önskar att spara en kopia för systemets administratör. Detta kan vara användbart för meddelanden som är förknippade med missbruk av systemet och som du egentligen vill radera, men som det kan vara nyttigt att ta en titt på senare. -

    Kryssa för Skicka meddelandet vidare och uppge en e-postadress om du vill skicka meddelandet vidare till någon annan. För att redigera ett e-postbrev som hålls tillbaka innan det skickas till listan, bör du skicka det till dig själv (eller eventuellt till listans ägare), och radera det ursprungliga e-postbrevet. Därefter, när e-postbrevet kommer till din in-box, redigerar du det och gör de ändringar som du önskar. Skicka därefter brevet till listan på -nytt med ett Approved: fält i meddelandehuvudet efterföljt av listans lösenord. Kom ihåg att det är vanlig netiquette i ett sådant fall att uppge att du har redigerat innehållet. -

    Om avsändaren är medlem av listan, men är modererad, kan du ta bort moderationsflaggan om du så önskar. Detta är användbart när du har satt upp listan på ett sådant sätt att nya medlemmar har en prövotid på listan, och du har bestämt att den prövotiden nu är över för denna medlem. -

    Om avsändaren inte är medlem av listan, kan du få e-postadressen inlagd i ett av avsändarfiltren. Avsändarfilter förklaras på sidan Filtrering av avsändare och kan vara inställt på auto-godkänna, auto-hållatillbaka, auto-avslå, eller -auto-kasta. Detta val kommer inte att vara tillgängligt om adressen redan är -inlagd i ett sådant filter. -

    Efter det att du gjort dina val, klickar du på knappen Utför överst eller nederst på sidan för att verkställa dina beslut. -

    Tillbaka till översiktssidan. +

  • Vänta -- Vänta med avgörandet. Ingenting blir gjort med denna förfrÃ¥gan nu, men e-postbrev som väntar pÃ¥ godkännande kan du hÃ¥lla pÃ¥ eller skicka vidare (se nedan). +
  • Godkänna -- Godkänna meddelandet som det är och distribuera det till listan. För förfrÃ¥gningar om medlemskap, anmälningar, avanmälningar och andra ändringar. +
  • AvslÃ¥ -- Radera meddelandet och ge besked tillbaka till avsändaren om att meddelandet inte blev godkänt. För förfrÃ¥gningar om medlemskap, avslag pÃ¥ anmälan eller avanmälan. Du bör alltid ocksÃ¥ ange en orsak i den tillhörande textrutan. +
  • Kasta -- Radera meddelandet utan att nÃ¥got besked skickas till avsändaren. Användbart för skräppost och reklampost (spam). För förfrÃ¥gningar om medlemskap, avslag pÃ¥ anmälan eller avanmälan utan att den som försökte anmäla sig eller avanmäla sig pÃ¥ listan fÃ¥r veta nÃ¥got. +
  • +

    Kryssa för Behåll meddelandet om du önskar att spara en kopia för systemets administratör. Detta kan vara användbart för meddelanden som är förknippade med missbruk av systemet och som du egentligen vill radera, men som det kan vara nyttigt att ta en titt på senare. +

    Kryssa för Skicka meddelandet vidare och uppge en e-postadress om du vill skicka meddelandet vidare till någon annan. För att redigera ett e-postbrev som hålls tillbaka innan det skickas till listan, bör du skicka det till dig själv (eller eventuellt till listans ägare), och radera det ursprungliga e-postbrevet. Därefter, när e-postbrevet kommer till din in-box, redigerar du det och gör de ändringar som du önskar. Skicka därefter brevet till listan på +nytt med ett Approved: fält i meddelandehuvudet efterföljt av listans lösenord. Kom ihåg att det är vanlig netiquette i ett sådant fall att uppge att du har redigerat innehållet. +

    Om avsändaren är medlem av listan, men är modererad, kan du ta bort moderationsflaggan om du så önskar. Detta är användbart när du har satt upp listan på ett sådant sätt att nya medlemmar har en prövotid på listan, och du har bestämt att den prövotiden nu är över för denna medlem. +

    Om avsändaren inte är medlem av listan, kan du få e-postadressen inlagd i ett av avsändarfiltren. Avsändarfilter förklaras på sidan Filtrering av avsändare och kan vara inställt på auto-godkänna, auto-hållatillbaka, auto-avslå, eller +auto-kasta. Detta val kommer inte att vara tillgängligt om adressen redan är +inlagd i ett sådant filter. +

    Efter det att du gjort dina val, klickar du på knappen Utför överst eller nederst på sidan för att verkställa dina beslut. +

    Tillbaka till översiktssidan. +

    \ No newline at end of file diff --git a/templates/sv/admindbpreamble.html b/templates/sv/admindbpreamble.html index 66713c01..7a5dd2f5 100644 --- a/templates/sv/admindbpreamble.html +++ b/templates/sv/admindbpreamble.html @@ -1,4 +1,68 @@ -Denna sida innehåller några av de e-postbrev till listan %(listname)s som hålls tillbaka för godkännande. Nu visar den %(description)s -

    Välj önskat beslut för varje förfrågan. När du är klar, klicka på Utför. Närmare instruktioner hittar du här. +Denna sida innehÃ¥ller nÃ¥gra av de e-postbrev till listan %(listname)s som hÃ¥lls tillbaka för godkännande. Nu visar den %(description)s +

    Välj önskat beslut för varje förfrågan. När du är klar, klicka på Utför. Närmare instruktioner hittar du här. -

    Du kan också visa en översikt över alla förfrågningar som väntar på ett avgörande. +

    Du kan också visa en översikt över alla förfrågningar som väntar på ett avgörande. +

    \ No newline at end of file diff --git a/templates/sv/admindbsummary.html b/templates/sv/admindbsummary.html index 8615045c..b3c84589 100644 --- a/templates/sv/admindbsummary.html +++ b/templates/sv/admindbsummary.html @@ -1,3 +1,66 @@ -Här finns en översikt över förfrågningar som ska avgöras för e-postlistan %(listname)s.
    Eventuella ansökningar om medlemskap till listan kommer att visas först, därefter kommer meddelanden som har skickats till listan, men som behöver godkännande för att bli distribuerade. -

    Välj önskad åtgärd för varje förfrågan och klicka på knappen Utför när du är klar. Närmare instruktioner finns också tillgängliga. -

    Du kan också visa alla detaljer för alla tillbakahållna meddelanden om du så önskar. \ No newline at end of file +Här finns en översikt över förfrÃ¥gningar som ska avgöras för e-postlistan %(listname)s.
    Eventuella ansökningar om medlemskap till listan kommer att visas först, därefter kommer meddelanden som har skickats till listan, men som behöver godkännande för att bli distribuerade. +

    Välj önskad åtgärd för varje förfrågan och klicka på knappen Utför när du är klar. Närmare instruktioner finns också tillgängliga. +

    Du kan också visa alla detaljer för alla tillbakahållna meddelanden om du så önskar.

    \ No newline at end of file diff --git a/templates/sv/admlogin.html b/templates/sv/admlogin.html index c85183e9..757a885a 100755 --- a/templates/sv/admlogin.html +++ b/templates/sv/admlogin.html @@ -1,29 +1,90 @@ - %(listname)s %(who)s Inloggning - - - -
    +%(listname)s %(who)s Inloggning + + + + %(message)s - - - - - - - - - - - -
    - %(listname)s %(who)s Inloggning -
    Listan %(who)s lösenord:
    -
    -

    Viktigt: Från och med nu måste du ha alternativet cookies aktiverat i din browser, annars kommer inga administrativa ändringar att sparas. -

    Session cookies används på Mailmans administrativa sidor, för att du inte ska behöva uppge ditt lösenord för varje ändring som du gör. Dessa cookies kommer att försvinna automatiskt när du stänger din browser. Du kan också ta bort dem manuellt genom att klicka på länken Logga ut under Andra administrativa aktiviteter (som du får upp efter att ha loggat in).

    + + + + + + + + + + + +
    +%(listname)s %(who)s Inloggning +
    Listan %(who)s lösenord:
    +
    +

    Viktigt: Från och med nu måste du ha alternativet cookies aktiverat i din browser, annars kommer inga administrativa ändringar att sparas. +

    Session cookies används på Mailmans administrativa sidor, för att du inte ska behöva uppge ditt lösenord för varje ändring som du gör. Dessa cookies kommer att försvinna automatiskt när du stänger din browser. Du kan också ta bort dem manuellt genom att klicka på länken Logga ut under Andra administrativa aktiviteter (som du får upp efter att ha loggat in).

    diff --git a/templates/sv/archtoc.html b/templates/sv/archtoc.html index ae554b8f..07ed24af 100644 --- a/templates/sv/archtoc.html +++ b/templates/sv/archtoc.html @@ -1,20 +1,84 @@ - - - Arkiv för %(listname)s - + + + +Arkiv för %(listname)s + %(meta)s - - -

    Arkiv för %(listname)s

    -

    - Du kan få mer information om denna lista eller - så kan du ladda ner det kompletta arkivet + + +

    Arkiv för %(listname)s

    +

    + Du kan få mer information om denna lista eller + så kan du ladda ner det kompletta arkivet (%(size)s).

    %(noarchive_msg)s %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/sv/archtocentry.html b/templates/sv/archtocentry.html index e78ba5ec..7dc90901 100644 --- a/templates/sv/archtocentry.html +++ b/templates/sv/archtocentry.html @@ -1,12 +1,74 @@ - -
    %(archivelabel)s: - [ Tråd (thread) ] - [ Ämne ] - [ Avsändare ] - [ Datum ] -
    %(archivelabel)s: +[ Tråd (thread) ] +[ Ämne ] +[ Avsändare ] +[ Datum ] +
    - - - - - - - - - - - - - - - - - - + + + + + + + + + + + +

    - -- - -
    -

      -

    - Om - - - -
    -

    -

    För att se arkivet över e-postbrev som skickats till denna lista, gå in på arkivet - - . - -

    -
    - Hur används e-postlistan -
    För att skicka ett e-postbrev till alla på listan, skicka det till . + + + +<mm-list-name> Infosida</mm-list-name> + + +

    + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - -
    + -- + +
    +

      +

    +Om + + + +
    +

    +

    För att se arkivet över e-postbrev som skickats till denna lista, gå in på arkivet + +. + +

    +
    +Hur används e-postlistan +
    För att skicka ett e-postbrev till alla på listan, skicka det till . -

    Du kan anmäla dig till listan eller ändra inställningarna för ditt medlemskap på listan nedan:

    Anmäl dig till - -
    -

    Du kan anmäla dig till genom att fylla i nödvändig information nedan. -

      - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      Din e-postadress: -  
      Namn (valfritt): 
      Du kan skriva ett lösenord i följande fält. Detta ger dålig säkerhet, men förhindrar att andra får tillgång til dina inställningar. Använd inte något värdefullt lösenord - lösenordet kan nämligen bli skickat till dig i klartext via e-postbrev. -

      Om du inte skriver något lösenord, kommer du att automatiskt bli tilldelad ett lösenord. Det kommer att skickas till dig så snart du har bekräftat din anmälan. Du kan senare begära att få lösenordet skickat till dig eller ändrat.  - -

      Önskat lösenord: 
      Önskat lösenord en gång till: 
      Välj språk för meddelanden:  
      Vill du få sammandragsversioner? Nej Ja
      -
      -
      - -
    -
    - - Medlemmar av -
    - - - -

    - - - -

    - - - +

    Du kan anmäla dig till listan eller ändra inställningarna för ditt medlemskap på listan nedan:

    Anmäl dig till + +
    +

    Du kan anmäla dig till genom att fylla i nödvändig information nedan. +

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      Din e-postadress: + 
      Namn (valfritt): 
      Du kan skriva ett lösenord i följande fält. Detta ger dålig säkerhet, men förhindrar att andra får tillgång til dina inställningar. Använd inte något värdefullt lösenord - lösenordet kan nämligen bli skickat till dig i klartext via e-postbrev. +

      Om du inte skriver något lösenord, kommer du att automatiskt bli tilldelad ett lösenord. Det kommer att skickas till dig så snart du har bekräftat din anmälan. Du kan senare begära att få lösenordet skickat till dig eller ändrat.  + +

      Önskat lösenord: 
      Önskat lösenord en gång till: 
      Välj språk för meddelanden:  
      Vill du få sammandragsversioner? Nej Ja
      +
      +
      + +
    +
    + +Medlemmar av +
    + + + +

    + + + +

    + +

    + diff --git a/templates/sv/options.html b/templates/sv/options.html index 4240dc82..af0544a5 100644 --- a/templates/sv/options.html +++ b/templates/sv/options.html @@ -1,253 +1,293 @@ - - Personlig medlemsida för  <MM-Presentable-User> på listan <MM-List-Name> - - - - - -
    Personlig medlemsida för - - på listan - -
    + +Personlig medlemsida för  <mm-presentable-user> på listan <mm-list-name> +</mm-list-name></mm-presentable-user> + + + + +
    Personlig medlemsida för + + på listan + +

    - - - - - +
    - Medlemsstatus, lösenord och inställningar för - på e-postlistan . -
    - - - - -

    -

    + + + +
    + Medlemsstatus, lösenord och inställningar för + på e-postlistan . +
    + + +

    +

    - - +

    - - - - - + + + + %(textlink)s - - diff --git a/templates/tr/archtocnombox.html b/templates/tr/archtocnombox.html index 9b4d0980..ae465eb3 100644 --- a/templates/tr/archtocnombox.html +++ b/templates/tr/archtocnombox.html @@ -1,19 +1,82 @@ - - - %(listname)s Arþivleri - + + + +%(listname)s Arþivleri + %(meta)s - - -

    %(listname)s Arþivleri

    -

    - Bu listeyle ilgili daha fazla bilgi alabilirsiniz. + + +

    %(listname)s Arþivleri

    +

    +Bu listeyle ilgili daha fazla bilgi alabilirsiniz.

    %(noarchive_msg)s %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - - + + diff --git a/templates/tr/article.html b/templates/tr/article.html index 1f5cdb57..bd979794 100644 --- a/templates/tr/article.html +++ b/templates/tr/article.html @@ -1,12 +1,13 @@ - - - %(title)s - - - - - + +

    %(listname)s Arþivleri

    +

    + Bu listeye henüz hiç mesaj gönderilmemiþ, bu yüzden arþivler þu anda + boþ. Bu listeyle ilgili daha fazla bilgi alabilirsiniz.

    - - - + + diff --git a/templates/tr/headfoot.html b/templates/tr/headfoot.html index ae1390b5..909a153b 100644 --- a/templates/tr/headfoot.html +++ b/templates/tr/headfoot.html @@ -1,28 +1,90 @@ -Bu yazý, liste özniteliklerine karþý çözülecek +Bu yazý, liste özniteliklerine karþý çözülecek Python -biçim dizgileri içerebilir. Ýzin verilen yerine koymalarýn listesi -þunlardýr: +biçim dizgileri içerebilir. Ãzin verilen yerine koymalarýn listesi +þunlardýr:
      -
    • real_name - Listenin ismi; genellikle listenin - isminin büyük harfle yazýlmasýdýr. +
    • real_name - Listenin ismi; genellikle listenin + isminin büyük harfle yazýlmasýdýr. -
    • list_name - Listenin URL'lerde tanýmlandýðý - isim, büyük-küçük harf burada anlamlýdýr. +
    • list_name - Listenin URL'lerde tanýmlandýðý + isim, büyük-küçük harf burada anlamlýdýr. -
    • host_name - Liste sunucusunun üzerinde - çalýþtýðý bilgisayarýn tam tanýmlý alan ismidir (FQDN). +
    • host_name - Liste sunucusunun üzerinde + çalýþtýðý bilgisayarýn tam tanýmlý alan ismidir (FQDN). -
    • web_page_url - Mailman için temel URL. Bunun - arkasýna, örneðin listinfo/%(list_name)s - eklenerek o mesaj listesi için liste tanýtým sayfasýna +
    • web_page_url - Mailman için temel URL. Bunun + arkasýna, örneðin listinfo/%(list_name)s + eklenerek o mesaj listesi için liste tanýtým sayfasýna gidilebilir. -
    • description - Mesaj listesinin kýsa bir - açýklamasý. +
    • description - Mesaj listesinin kýsa bir + açýklamasý. -
    • info - Mesaj listesinin tam açýklamasý. - -
    • cgiext - CGI betiklerine eklenecek uzantý. -
    +
  • info - Mesaj listesinin tam açýklamasý. +
  • cgiext - CGI betiklerine eklenecek uzantý. +
  • diff --git a/templates/tr/listinfo.html b/templates/tr/listinfo.html index 8cbc23d4..e1dbb51e 100644 --- a/templates/tr/listinfo.html +++ b/templates/tr/listinfo.html @@ -1,149 +1,209 @@ - - - - <MM-List-Name> Bilgi Sayfasý - - - -

    -

    Ändra e-postadress på - -
    Du kan ändra den e-postadress som du använde när du anmälde dig till listan, genom att skriva in en annan e-postadress i fälten nedan. Observera att ett e-postbrev med - närmare instruktioner kommer då att skickas till den nya adressen. Ändringarna träder inte i kraft förrän du har bekräftat dem. Du har på dig att bekräfta ändringen. -

    Du kan även ändra ditt namn på listan (till exempel Sven Svensson). + + + - - - + + +
    Ändra e-postadress på + +
    Du kan ändra den e-postadress som du använde när du anmälde dig till listan, genom att skriva in en annan e-postadress i fälten nedan. Observera att ett e-postbrev med + närmare instruktioner kommer då att skickas till den nya adressen. Ändringarna träder inte i kraft förrän du har bekräftat dem. Du har på dig att bekräfta ändringen. +

    Du kan även ändra ditt namn på listan (till exempel Sven Svensson). -

    Om du vill ändra din e-postadress på alla e-postlistor som du är anmäld till på , kryssa för Ändra alla listor. +

    Om du vill ändra din e-postadress på alla e-postlistor som du är anmäld till på , kryssa för Ändra alla listor. -

    - - - - - - - -
    Ny e-postadress:
    Ny e-postadress en gång till:
    -
    - - + + + - - - + + +
    Ditt namn +

    + + + + + + + +
    Ny e-postadress:
    Ny e-postadress en gång till:
    +
    + + - - -
    Ditt namn (valfritt):
    -
    -

    Ändra min e-postadress på alla - listor som jag är medlem av

    +
    +

    Ändra min e-postadress på alla + listor som jag är medlem av

    -

    - - - - - -
    Avanmäl dig från - - Visa listor som du är medlem av på -
    Kryssa för i kryssrutan och klicka på knappen för att avanmäla dig från denna e-postlista. OBS! Du blir då omedelbart avanmäld från listan! + + + + - + +
    +

    Avanmäl dig från + +Visa listor som du är medlem av på +
    Kryssa för i kryssrutan och klicka på knappen för att avanmäla dig från denna e-postlista. OBS! Du blir då omedelbart avanmäld från listan!

    -

    Du kan välja att se alla listor som du är anmäld till, så att du kan göra samma ändringar på flera listor på en gång. +

    Du kan välja att se alla listor som du är anmäld till, så att du kan göra samma ändringar på flera listor på en gång.

    -

    -
    + + +
    +Ditt lösenord för +
    +
    +

    Har du glömt ditt lösenord?

    - - - - - - +
    - Ditt lösenord för -
    -
    -

    Har du glömt ditt lösenord?

    -
    - Klicka på denna knapp för att få ditt lösenord skickat till dig i ett e-postbrev. -

    - -

    - -
    -
    -
    -

    Ändra lösenord

    - - - - - - - - - -
    -
    - Nytt lösenord:
    -
    - -
    -
    - Nytt lösenord en gång till:
    -
    - -
    - -

    -

    - - Ändra lösenordet för alla listor som jag är medlem av på + Klicka pÃ¥ denna knapp för att fÃ¥ ditt lösenord skickat till dig i ett e-postbrev. +

    + +

    + +
    +

    +
    +

    Ändra lösenord

    + + + + + + + + + +
    +
    + Nytt lösenord:
    +
    + +
    +
    + Nytt lösenord en gång till:
    +
    + +
    + +

    +

    + + Ändra lösenordet för alla listor som jag är medlem av på . -
    -
    -
    - + +

    +

    - - +
    - Dina inställningar för -
    +
    +Dina inställningar för +
    - -

    Gällande inställningar är valda. -

    Observera att några av inställningarna har ett Använd på alla -val. Kryssar du för detta val, kommer alla listor som du är medlem av på - också få den inställning som du sätter här. Klicka på Visa andra listor som jag är medlem av ovan för att se vilka andra e-postlistor som du är medlem av. +

    Gällande inställningar är valda. +

    Observera att några av inställningarna har ett Använd på alla -val. Kryssar du för detta val, kommer alla listor som du är medlem av på + också få den inställning som du sätter här. Klicka på Visa andra listor som jag är medlem av ovan för att se vilka andra e-postlistor som du är medlem av.

    - - - +
    Ta emot meddelanden -

    Sätt detta till Ja för att ta emot e-postbrev som skickas till denna lista. Sätt Nej om du under en period inte önskar att ta emot e-postbrev - som skickas till listan, men ändå vill stå kvar som medlem av listan. (Kan vara användbart - till exempel om du åker bort på semester.) Sätter du detta till Nej, måste - du komma íhåg att sätta tillbaka till Ja igen, när du åter vill ta emot e-postbrev. -

    - Ja
    - Nej

    - - Använd på alla -

    + - - - + + - - - - - - - - - - - - - - - - - - + + + + + + + + +
    Ta emot meddelanden +

    Sätt detta till Ja för att ta emot e-postbrev som skickas till denna lista. Sätt Nej om du under en period inte önskar att ta emot e-postbrev + som skickas till listan, men ändå vill stå kvar som medlem av listan. (Kan vara användbart + till exempel om du åker bort på semester.) Sätter du detta till Nej, måste + du komma íhåg att sätta tillbaka till Ja igen, när du åter vill ta emot e-postbrev. +

    + Ja
    + Nej

    + +Använd på alla +

    - Sammandragsversion

    Sätter du på alternativet sammandragsversion, kommer du att med jämna mellanrum (till exempel en gång om dagen - det beror på hur mycket som skickas till listan) få en samlingsepost som innehåller alla meddelanden som skickas till - listan, istället för att få varje enskilt meddelande. Om du byter från sammandragsversion - till normalversion, kommer du kanske i alla fall att få ett sista e-postbrev med sammandrag, även om du då - redan börjat ta emot enskilda meddelanden till listan.

    - Av
    -
    Ta emot sammandrag som ren text eller i MIME-format? -

    Det finns e-postläsare/mejlprogram som inte stöder sammandrag i MIME-format. Att ta emot sammandrag i MIME-format rekommenderas, men får du problem med att läsa e-post i det formatet, kan du välja ren text här. -

    - MIME
    - Ren text

    - - Använd på alla -

    +Sammandragsversion

    Sätter du på alternativet sammandragsversion, kommer du att med jämna mellanrum (till exempel en gång om dagen - det beror på hur mycket som skickas till listan) få en samlingsepost som innehåller alla meddelanden som skickas till + listan, istället för att få varje enskilt meddelande. Om du byter från sammandragsversion + till normalversion, kommer du kanske i alla fall att få ett sista e-postbrev med sammandrag, även om du då + redan börjat ta emot enskilda meddelanden till listan.

    + Av
    + PÃ¥
    Ta emot sammandrag som ren text eller i MIME-format? +

    Det finns e-postläsare/mejlprogram som inte stöder sammandrag i MIME-format. Att ta emot sammandrag i MIME-format rekommenderas, men får du problem med att läsa e-post i det formatet, kan du välja ren text här. +

    + MIME
    + Ren text

    + + Använd på alla +

    Ta emot dina egna meddelanden till listan? -

    Vanligtvis kommer du att få ta emot e-postbrev som du själv skickar till listan. Om du inte vill ta emot dessa, bör du välja Nej här. -

    - Nej
    - Ja
    Ta emot bekräftelse när du skickar e-post till listan? -

    Välj Ja här om du vill få en bekräftelse varje gång du skickar ett e-postbrev till listan. -

    - Nej
    - Ja
    - Få ditt lösenord skickat till dig med jämna mellanrum?

    En gång i månaden kan det vara så att du får ett e-postbrev som innehåller lösenord till alla listor som du är medlem av på detta system. Du kan här slå av denna - inställning för den enskilda listan - eller för alla listor.

    - Nej
    - Ja

    - - Använd på alla -

    - Vill du visas på listan över medlemmar av listan?

    När någon går in på webbsidan för att se alla medlemmar av listan kommer vanligtvis din e-postadress att visas. (Men stavad på ett sådant sätt att robotar som plockar upp e-postadresser från hemsidor - och kanske därefter - skickar skräppost och reklam/spam till den - inte kommer att förstå att det är en e-postadress.) - Välj Nej här, om du inte vill att din e-postadress ska visas på listan.

    - Nej
    - Ja
    Vilket språk föredrar du? -

    -

    - -
    Vilka ämnen vill du ta emot? -

    Genom att välja ett eller flera ämnen, kan du filtrera meddelandena som skickas till e-postlistan, så att du bara tar emot sådana e-postbrev som intresserar dig. -

    Om ett meddelande inte går under något ämne, beror det på inställningen nedan, om du vill ta emot meddelandet eller inte. Om du inte väljer något ämne här, kommer du att få ta emot alla meddelanden som skickas till listan.

    - -
    - Vill du ta emot meddelanden som inte går in under något ämne? -

    Detta val gäller bara om du valt ett eller flera ämnen ovan. Det avgör om du ska ta mot meddelanden som inte går under något ämne. Väljer du Nej kommer du inte att ta emot dem, väljer du Ja, kommer du att ta emot dem. -

    Om du inte valt några ämnen i inställningen ovan, kommer du att ta emot alla meddelanden som skickas till listan. -

    - Nej
    - Ja
    - Undvika att få e-postbrev från listan som även är adresserade direkt till dig?

    Du kan välja vad som ska hända om det kommer ett e-postbrev till listan som dessutom är adresserat direkt till dig (det vill säga att din e-postadress - står i fältet To: eller Cc:). Välj Ja för att undvika - att ta emot samma e-postbrev från listan, välj Nej för att i alla fall ta emot e-postbrev från listan. -

    För särskilt intresserade: Om listan är uppsatt för att personalisera meddelanden som skickas till listan och du väljer att ta emot e-post från listan, så kommer varje kopia att ha fältet X-Mailman-Copy: yes i meddelandehuvudet. -

    - Nej
    - Ja

    - - Använd på alla -

    -
    -
    Ta emot dina egna meddelanden till listan? +

    Vanligtvis kommer du att få ta emot e-postbrev som du själv skickar till listan. Om du inte vill ta emot dessa, bör du välja Nej här. +

    + Nej
    + Ja
    Ta emot bekräftelse när du skickar e-post till listan? +

    Välj Ja här om du vill få en bekräftelse varje gång du skickar ett e-postbrev till listan. +

    + Nej
    + Ja
    +Få ditt lösenord skickat till dig med jämna mellanrum?

    En gång i månaden kan det vara så att du får ett e-postbrev som innehåller lösenord till alla listor som du är medlem av på detta system. Du kan här slå av denna + inställning för den enskilda listan - eller för alla listor.

    +Nej
    + Ja

    + +Använd på alla +

    +Vill du visas på listan över medlemmar av listan?

    När någon går in på webbsidan för att se alla medlemmar av listan kommer vanligtvis din e-postadress att visas. (Men stavad på ett sådant sätt att robotar som plockar upp e-postadresser från hemsidor - och kanske därefter + skickar skräppost och reklam/spam till den - inte kommer att förstå att det är en e-postadress.) + Välj Nej här, om du inte vill att din e-postadress ska visas på listan.

    + Nej
    + Ja
    Vilket språk föredrar du? +

    +

    + +
    Vilka ämnen vill du ta emot? +

    Genom att välja ett eller flera ämnen, kan du filtrera meddelandena som skickas till e-postlistan, så att du bara tar emot sådana e-postbrev som intresserar dig. +

    Om ett meddelande inte går under något ämne, beror det på inställningen nedan, om du vill ta emot meddelandet eller inte. Om du inte väljer något ämne här, kommer du att få ta emot alla meddelanden som skickas till listan.

    + +
    +Vill du ta emot meddelanden som inte går in under något ämne? +

    Detta val gäller bara om du valt ett eller flera ämnen ovan. Det avgör om du ska ta mot meddelanden som inte går under något ämne. Väljer du Nej kommer du inte att ta emot dem, väljer du Ja, kommer du att ta emot dem. +

    Om du inte valt några ämnen i inställningen ovan, kommer du att ta emot alla meddelanden som skickas till listan. +

    + Nej
    + Ja
    +Undvika att få e-postbrev från listan som även är adresserade direkt till dig?

    Du kan välja vad som ska hända om det kommer ett e-postbrev till listan som dessutom är adresserat direkt till dig (det vill säga att din e-postadress + står i fältet To: eller Cc:). Välj Ja för att undvika + att ta emot samma e-postbrev från listan, välj Nej för att i alla fall ta emot e-postbrev från listan. +

    För särskilt intresserade: Om listan är uppsatt för att personalisera meddelanden som skickas till listan och du väljer att ta emot e-post från listan, så kommer varje kopia att ha fältet X-Mailman-Copy: yes i meddelandehuvudet. +

    + Nej
    + Ja

    + +Använd på alla +

    +
    +

    - - - - + + +

    diff --git a/templates/sv/private.html b/templates/sv/private.html index dcd8f37a..2a95e8d3 100755 --- a/templates/sv/private.html +++ b/templates/sv/private.html @@ -1,49 +1,110 @@ - %(realname)s Inloggning till Privata Arkiv - - - -
    +%(realname)s Inloggning till Privata Arkiv + + + + %(message)s - - - - - - - - - - - - - - - -
    - %(realname)s Inloggning till Privata Arkiv -
    E-postadress:
    Lösenord:
    -
    -

    Viktigt: Från och med nu måste du ha alternativet cookies aktiverat i din browser, annars kommer inga administrativa ändringar att sparas. -

    Session cookies används på Mailmans administrativa sidor, för att du inte ska behöva uppge lösenord för varje ändring som du gör. Dessa cookies kommer att försvinna automatiskt när du stänger din browser. Du kan också ta bort dem manuellt genom att klicka på länken Logga ut under Andra administrativa aktiviteter (som du får upp efter att ha loggat in).

    - - - - - - + + + +
    - Password Reminder -
    If you don't remember your password, enter your email address + + + + + + + + + + + + + + + +
    +%(realname)s Inloggning till Privata Arkiv +
    E-postadress:
    Lösenord:
    +
    +

    Viktigt: Från och med nu måste du ha alternativet cookies aktiverat i din browser, annars kommer inga administrativa ändringar att sparas. +

    Session cookies används på Mailmans administrativa sidor, för att du inte ska behöva uppge lösenord för varje ändring som du gör. Dessa cookies kommer att försvinna automatiskt när du stänger din browser. Du kan också ta bort dem manuellt genom att klicka på länken Logga ut under Andra administrativa aktiviteter (som du får upp efter att ha loggat in).

    + + + + + + - - - - -
    +Password Reminder +
    If you don't remember your password, enter your email address above and click the Remind button and your password will be emailed to you.
    - +

    +

    diff --git a/templates/sv/roster.html b/templates/sv/roster.html index db9687a6..05a3177a 100644 --- a/templates/sv/roster.html +++ b/templates/sv/roster.html @@ -1,48 +1,107 @@ - - - <MM-List-Name> Medlemmar - - - - -

    - - - - - - - - - - - - - - - -
    - Medlemmar -
    - -

    -

    - -

    Klicka på din adress för att gå till din personliga sida.
    (Adresser inom parentes har stoppat mottagningen av meddelanden till listan.)

    -
    -
    - Medlemmar i normalversion på : -
    -
    -
    - Medlemmar i sammandragsversion på : -
    -
    -

    -

    -

    -

    - - - + + +<mm-list-name> Medlemmar</mm-list-name> + + +

    + + + + + + + + + + + + + + + +
    + Medlemmar +
    +

    +

    +

    Klicka på din adress för att gå till din personliga sida.
    (Adresser inom parentes har stoppat mottagningen av meddelanden till listan.)

    +
    +
    + Medlemmar i normalversion på : +
    +
    +
    + Medlemmar i sammandragsversion på : +
    +
    +

    +

    +

    +

    + +

    + diff --git a/templates/sv/subscribe.html b/templates/sv/subscribe.html index 526f3b3d..079b2ce8 100644 --- a/templates/sv/subscribe.html +++ b/templates/sv/subscribe.html @@ -1,9 +1,72 @@ -<MM-List-Name>: Resultat av anmälan +<mm-list-name>: Resultat av anmälan</mm-list-name> -

    : Resultat av anmälan

    - - - +

    : Resultat av anmälan

    + + + diff --git a/templates/tr/admindbdetails.html b/templates/tr/admindbdetails.html index cce2df0e..79fe3e89 100644 --- a/templates/tr/admindbdetails.html +++ b/templates/tr/admindbdetails.html @@ -1,66 +1,128 @@ -Yönetimsel istekler, iki yoldan biriyle görüntülenir, bir -özet sayfasýnda ve bir ayrýntýlar -sayfasýnda. Özet sayfasý, üyelik ve üyelikten çýkma istekleri ile -onayýnýz için bekletilen mesajlarý gönderici e-posta adresine göre -gruplanmýþ þekilde görüntüler. Ayrýntýlar sayfasý, tüm baþlýklar ve -mesaj gövdesinden bir alýntý da içeren, bekletilen her mesajla -ilgili daha ayrýntýlý bir görünüm içerir. +Yönetimsel istekler, iki yoldan biriyle görüntülenir, bir +özet sayfasýnda ve bir ayrýntýlar +sayfasýnda. Özet sayfasý, üyelik ve üyelikten çýkma istekleri ile +onayýnýz için bekletilen mesajlarý gönderici e-posta adresine göre +gruplanmýþ þekilde görüntüler. Ayrýntýlar sayfasý, tüm baþlýklar ve +mesaj gövdesinden bir alýntý da içeren, bekletilen her mesajla +ilgili daha ayrýntýlý bir görünüm içerir. -

    Tüm sayfalarda, aþaðýdaki eylemler kullanýlabilir: +

    Tüm sayfalarda, aþaðýdaki eylemler kullanýlabilir:

      -
    • Ertele -- Kararýnýzý daha sonraya erteler. Þimdilik bu - yönetimsel istek için bir eylem gerçekleþtirilmez, fakat bekletilen - mesajlar için, mesajý hala iletebilir veya saklayabilirsiniz - (aþaðýya bakýn). +
    • Ertele -- Kararýnýzý daha sonraya erteler. Þimdilik bu + yönetimsel istek için bir eylem gerçekleþtirilmez, fakat bekletilen + mesajlar için, mesajý hala iletebilir veya saklayabilirsiniz + (aþaðýya bakýn). -
    • Onayla -- Mesajý, listeye gönderilmesi için onaylar. Üyelik - istekleri için, üyelik durumundaki deðiþikliði onaylar. +
    • Onayla -- Mesajý, listeye gönderilmesi için onaylar. Üyelik + istekleri için, üyelik durumundaki deðiþikliði onaylar. -
    • Reddet -- Mesajý reddeder, göndericiye bir reddetme bildirimi - gönderir ve özgün mesajý gözardý eder. Üyelik istekleri için, üyelik - durumundaki deðiþikliði reddeder. Her iki durumda da, reddetme - nedeniniz olarak, seçeneðin yanýndaki yazý kutusuna bir neden - yazmanýz uygun olacaktýr. +
    • Reddet -- Mesajý reddeder, göndericiye bir reddetme bildirimi + gönderir ve özgün mesajý gözardý eder. Üyelik istekleri için, üyelik + durumundaki deðiþikliði reddeder. Her iki durumda da, reddetme + nedeniniz olarak, seçeneðin yanýndaki yazý kutusuna bir neden + yazmanýz uygun olacaktýr. -
    • Gözardý Et -- Özgün mesajý siler ve göndericiye bir reddetme - bildirimi göndermez. Üyelik istekleri için, isteði yapan kiþiye - bildirmeden, isteði gözardý eder. Bu, genellikle spam mesajlar için - kullanmak isteyebileceðiniz bir eylemdir. -
    +
  • Gözardý Et -- Özgün mesajý siler ve göndericiye bir reddetme + bildirimi göndermez. Üyelik istekleri için, isteði yapan kiþiye + bildirmeden, isteði gözardý eder. Bu, genellikle spam mesajlar için + kullanmak isteyebileceðiniz bir eylemdir. +
  • +

    Bekletilen mesajlar için, Sakla seçeneðini seçerek +mesajýn bir kopyasýný site yöneticisi için kaydedebilirsiniz. Bu, +gözardý etmek, fakat sonraki bir zamanda araþtýrma için kaydýný tutmak +istediðiniz kötü amaçlý mesajlar için kullanýþlýdýr. -

    Bekletilen mesajlar için, Sakla seçeneðini seçerek -mesajýn bir kopyasýný site yöneticisi için kaydedebilirsiniz. Bu, -gözardý etmek, fakat sonraki bir zamanda araþtýrma için kaydýný tutmak -istediðiniz kötü amaçlý mesajlar için kullanýþlýdýr. +

    Mesajý listede olmayan baþka birisine iletmek için Ãlet +seçeneðini seçin ve iletim adresini yazýn. Bekletilen bir mesajý listeye +gönderilmeden önce düzenlemek isterseniz, önce mesajý kendinize (liste +sahiplerine deðil) iletip özgün mesajý gözardý etmelisiniz. Daha sonra +mesaj size geldiðinde düzenlemelerinizi yapýn ve bir Approved: +baþlýðýna liste þifresini yazarak mesajý tekrar listeye gönderin. Bu +durumda, tekrar gönderdiðiniz mesaja, yazýyý düzenlediðinizi açýklayan +bir not eklemeniz uygun olacaktýr. -

    Mesajý listede olmayan baþka birisine iletmek için Ýlet -seçeneðini seçin ve iletim adresini yazýn. Bekletilen bir mesajý listeye -gönderilmeden önce düzenlemek isterseniz, önce mesajý kendinize (liste -sahiplerine deðil) iletip özgün mesajý gözardý etmelisiniz. Daha sonra -mesaj size geldiðinde düzenlemelerinizi yapýn ve bir Approved: -baþlýðýna liste þifresini yazarak mesajý tekrar listeye gönderin. Bu -durumda, tekrar gönderdiðiniz mesaja, yazýyý düzenlediðinizi açýklayan -bir not eklemeniz uygun olacaktýr. +

    Gönderici, moderatör onaylý bir liste üyesiyse isteðe baðlý olarak +o kiþinin moderatör onay ayarýný da silebilirsiniz. Bu, yeni üyeleri +bir deneme süresine koyduðunuzda ve artýk bu üyeye onay gerektirmeden +listeye mesaj gönderebilmesi için güvenilebileceðine karar verdiðinizde +kullanýþlýdýr. -

    Gönderici, moderatör onaylý bir liste üyesiyse isteðe baðlý olarak -o kiþinin moderatör onay ayarýný da silebilirsiniz. Bu, yeni üyeleri -bir deneme süresine koyduðunuzda ve artýk bu üyeye onay gerektirmeden -listeye mesaj gönderebilmesi için güvenilebileceðine karar verdiðinizde -kullanýþlýdýr. - -

    Gönderici bir liste üyesi deðilse, e-posta adresini bir gönderici -filtresine ekleyebilirsiniz. Gönderici filtreleri, gönderici filtreleri gizlilik sayfasýnda -tanýmlanýr ve otomatik onayla (Onaylanacaklar), +

    Gönderici bir liste üyesi deðilse, e-posta adresini bir gönderici +filtresine ekleyebilirsiniz. Gönderici filtreleri, gönderici filtreleri gizlilik sayfasýnda +tanýmlanýr ve otomatik onayla (Onaylanacaklar), otomatik reddet (Reddedilecekler), otomatik beklet -(Bekletilecekler) veya otomatik gözardý et (Gözardý edilecekler) -olabilir. Bu seçenek, e-posta adresi halen bir gönderici filtresine kayýtlýysa -görülmez. +(Bekletilecekler) veya otomatik gözardý et (Gözardý edilecekler) +olabilir. Bu seçenek, e-posta adresi halen bir gönderici filtresine kayýtlýysa +görülmez. -

    Ýþiniz bittiðinde, sayfanýn en altýndaki Tüm Veriyi Gönder -düðmesine týklayýn. Bu düðme, bir karar verdiðiniz tüm yönetimsel -isteklerle ilgili seçilen eylemleri gönderir. +

    Ãþiniz bittiðinde, sayfanýn en altýndaki Tüm Veriyi Gönder +düðmesine týklayýn. Bu düðme, bir karar verdiðiniz tüm yönetimsel +isteklerle ilgili seçilen eylemleri gönderir. -

    Özet sayfasýna geri dön. +

    Özet sayfasýna geri dön. +

    \ No newline at end of file diff --git a/templates/tr/admindbpreamble.html b/templates/tr/admindbpreamble.html index 8a4a53c2..32b539b4 100644 --- a/templates/tr/admindbpreamble.html +++ b/templates/tr/admindbpreamble.html @@ -1,12 +1,76 @@ -Bu sayfa, %(listname)s listesine gönderilmiþ ve onayýnýz için -bekletilen mesajlarýn bir altkümesini içerir. Þu anda görüntülenen: +Bu sayfa, %(listname)s listesine gönderilmiþ ve onayýnýz için +bekletilen mesajlarýn bir altkümesini içerir. Þu anda görüntülenen: %(description)s -

    Her bir yönetimsel istek için, lütfen uygulanacak eylemi seçin ve -iþiniz bitince Tüm Veriyi Gönder düðmesine týklayýn. -Burada daha ayrýntýlý açýklamalarý +

    Her bir yönetimsel istek için, lütfen uygulanacak eylemi seçin ve +iþiniz bitince Tüm Veriyi Gönder düðmesine týklayýn. +Burada daha ayrýntýlý açýklamalarý bulabilirsiniz. -

    Ayrýca tüm bekleyen isteklerin bir özetini de -görebilirsiniz. +

    Ayrýca tüm bekleyen isteklerin bir özetini de +görebilirsiniz. +

    \ No newline at end of file diff --git a/templates/tr/admindbsummary.html b/templates/tr/admindbsummary.html index ab9907b7..837bc671 100644 --- a/templates/tr/admindbsummary.html +++ b/templates/tr/admindbsummary.html @@ -1,14 +1,78 @@ -Bu sayfa -%(listname)s mesaj listesi için, -onayýnýzý bekleyen yönetimsel isteklerin bir özetini içerir. -Önce, bekleyen üyelik ve üyelikten çýkma isteklerini göreceksiniz, -sonra, eðer varsa, onayýnýzý bekleyen mesajlarý göreceksiniz. +Bu sayfa +%(listname)s mesaj listesi için, +onayýnýzý bekleyen yönetimsel isteklerin bir özetini içerir. +Önce, bekleyen üyelik ve üyelikten çýkma isteklerini göreceksiniz, +sonra, eðer varsa, onayýnýzý bekleyen mesajlarý göreceksiniz. -

    Her bir yönetimsel istek için, lütfen uygulanacak eylemi seçin ve -iþiniz bitince Tüm Veriyi Gönder düðmesine týklayýn. -Burada daha ayrýntýlý açýklamalarý +

    Her bir yönetimsel istek için, lütfen uygulanacak eylemi seçin ve +iþiniz bitince Tüm Veriyi Gönder düðmesine týklayýn. +Burada daha ayrýntýlý açýklamalarý bulabilirsiniz. -

    Ayrýca bekletilen tüm mesajlarýn -ayrýntýlarýný da görebilirsiniz. +

    Ayrýca bekletilen tüm mesajlarýn +ayrýntýlarýný da görebilirsiniz. +

    \ No newline at end of file diff --git a/templates/tr/admlogin.html b/templates/tr/admlogin.html index 49f46e3d..67655645 100755 --- a/templates/tr/admlogin.html +++ b/templates/tr/admlogin.html @@ -1,41 +1,101 @@ - %(listname)s %(who)s Giriþi - - - -
    +%(listname)s %(who)s Giriþi + + + + %(message)s - - - - - - - - - - - -
    - %(listname)s %(who)s - Giriþi -
    Liste %(who)s Þifresi:
    -
    -

    Önemli: Bu noktadan itibaren tarayýcýnýzda - tanýmlama bilgilerini (cookie) etkinleþtirmiþ olmanýz gerekir, yoksa hiçbir - yönetimsel deðiþiklik yapýlmayacaktýr. + + + + + + + + + + + +
    +%(listname)s %(who)s + Giriþi +
    Liste %(who)s Þifresi:
    +
    +

    Önemli: Bu noktadan itibaren tarayýcýnýzda + tanýmlama bilgilerini (cookie) etkinleþtirmiþ olmanýz gerekir, yoksa hiçbir + yönetimsel deðiþiklik yapýlmayacaktýr. -

    Mailman'in yönetimsel arayüzünde oturum tanýmlayýcý bilgileri - kullanýlýr, böylece her yönetimsel iþlemde tekrar yetkilendirme - yapmanýza gerek kalmaz. Bu tanýmlayýcý bilgi tarayýcýnýzdan - çýktýðýnýzda otomatik olarak yok olur, veya siz tanýmlayýcý - bilgiyi Diðer Yönetimsel Aktiviteler baþlýðý altýndaki - Çýkýþ linkine týklayarak (baþarýyla giriþ yaptýðýnýzda - bunu göreceksiniz) kendiniz yok edebilirsiniz. -

    +

    Mailman'in yönetimsel arayüzünde oturum tanýmlayýcý bilgileri + kullanýlýr, böylece her yönetimsel iþlemde tekrar yetkilendirme + yapmanýza gerek kalmaz. Bu tanýmlayýcý bilgi tarayýcýnýzdan + çýktýðýnýzda otomatik olarak yok olur, veya siz tanýmlayýcý + bilgiyi Diðer Yönetimsel Aktiviteler baþlýðý altýndaki + Çýkýþ linkine týklayarak (baþarýyla giriþ yaptýðýnýzda + bunu göreceksiniz) kendiniz yok edebilirsiniz. +

    - diff --git a/templates/tr/archidxentry.html b/templates/tr/archidxentry.html index f9bb57aa..5c620b20 100644 --- a/templates/tr/archidxentry.html +++ b/templates/tr/archidxentry.html @@ -1,4 +1,68 @@ -
  • %(subject)s -  -%(author)s - +
  • %(subject)s +  +%(author)s + +
  • \ No newline at end of file diff --git a/templates/tr/archidxfoot.html b/templates/tr/archidxfoot.html index 65f341e9..f0b3caf6 100644 --- a/templates/tr/archidxfoot.html +++ b/templates/tr/archidxfoot.html @@ -1,22 +1,85 @@ - -

    - Son mesaj tarihi: - %(lastdate)s
    - Arþivlendiði tarih: %(archivedate)s -

    -

      -
    • Mesaj sýralamasý: + +

      +Son mesaj tarihi: +%(lastdate)s
      +Arþivlendiði tarih: %(archivedate)s +

      +

      -

      -


      - Bu arþiv Pipermail %(version)s ile - oluþturulmuþtur. - - +
    +

    +


    +Bu arþiv Pipermail %(version)s ile + oluþturulmuþtur. + +

    \ No newline at end of file diff --git a/templates/tr/archidxhead.html b/templates/tr/archidxhead.html index 18b4a747..ebcc7806 100644 --- a/templates/tr/archidxhead.html +++ b/templates/tr/archidxhead.html @@ -1,15 +1,79 @@ - - - %(archtype)s sýralamasý ile %(listname)s %(archive)s Arþivleri - + + + +%(archtype)s sýralamasý ile %(listname)s %(archive)s Arþivleri + %(encoding)s - - - -

    %(archtype)s sýralamasý ile %(archive)s Arþivleri

    -
      -
    • Mesaj sýralamasý: + + + +

      %(archtype)s sýralamasý ile %(archive)s Arþivleri

      + -

      Baþlangýç tarihi: %(firstdate)s
      - Bitiþ tarihi: %(lastdate)s
      - Mesaj sayýsý: %(size)s

      -

        - +
      +

      Baþlangýç tarihi: %(firstdate)s
      +Bitiþ tarihi: %(lastdate)s
      +Mesaj sayýsý: %(size)s

      +

        +

      \ No newline at end of file diff --git a/templates/tr/archlistend.html b/templates/tr/archlistend.html index 9bc052dd..2e1191b0 100644 --- a/templates/tr/archlistend.html +++ b/templates/tr/archlistend.html @@ -1 +1,64 @@ -
    + diff --git a/templates/tr/archliststart.html b/templates/tr/archliststart.html index 7e7a476a..537dcd26 100644 --- a/templates/tr/archliststart.html +++ b/templates/tr/archliststart.html @@ -1,5 +1,70 @@ - - - - - +
    ArþivSýralama:Ýndirilebilir þekli
    + + + + + +
    ArşivSıralama:İndirilebilir şekli
    diff --git a/templates/tr/archtoc.html b/templates/tr/archtoc.html index 999efaeb..0bd8a0db 100644 --- a/templates/tr/archtoc.html +++ b/templates/tr/archtoc.html @@ -1,21 +1,84 @@ - - - %(listname)s Arþivleri - + + + +%(listname)s Arþivleri + %(meta)s - - -

    %(listname)s Arþivleri

    -

    - Bu listeyle ilgili daha fazla bilgi alabilirsiniz - veya tüm arþivi indirebilirsiniz + + +

    %(listname)s Arþivleri

    +

    +Bu listeyle ilgili daha fazla bilgi alabilirsiniz + veya tüm arþivi indirebilirsiniz (%(size)s).

    %(noarchive_msg)s %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - - + + diff --git a/templates/tr/archtocentry.html b/templates/tr/archtocentry.html index dc50b395..f26e4cdd 100644 --- a/templates/tr/archtocentry.html +++ b/templates/tr/archtocentry.html @@ -1,13 +1,74 @@ - -
    %(archivelabel)s: - [ Thread ] - [ Konu ] - [ Yazar ] - [ Tarih ] -
    %(archivelabel)s: +[ Thread ] +[ Konu ] +[ Yazar ] +[ Tarih ] +
    - - - - - - - - - - - - - - - - - - + + + + + +

    - -- - -
    -

      -

    - Hakkýnda - - - -
    -

    -

    Bu listeye önceden gönderilen mesajlarý görmek için - - Arþivlerini ziyaret edin. - -

    -
    - Listesinin Kullanýmý -
    - Tüm liste üyelerine bir mesaj göndermek için - adresine bir e-posta atýnýz. + + + +<mm-list-name> Bilgi Sayfasý</mm-list-name> + + +

    + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + + + + + + + +
    + -- + +
    +

      +

    + Hakkýnda + + + +
    +

    +

    Bu listeye önceden gönderilen mesajlarý görmek için + + Arþivlerini ziyaret edin. + +

    +
    + Listesinin Kullanýmý +
    + Tüm liste üyelerine bir mesaj göndermek için + adresine bir e-posta atýnýz. -

    Aþaðýdaki bölümlerde listeye üye olabilir veya varolan üyeliðinizi - deðiþtirebilirsiniz. -

    - Listesine Üyelik -
    -

    - listesine aþaðýdaki formu doldurarak üye +

    Aþaðýdaki bölümlerde listeye üye olabilir veya varolan üyeliðinizi + deðiþtirebilirsiniz. +

    + Listesine Üyelik +
    +

    + listesine aþaðýdaki formu doldurarak üye olabilirsiniz. - -

      - - - - - - - - - - - - - - - - - -
      E-posta adresiniz: -  
      Ýsminiz (isteðe baðlý): 
      Aþaðýya bir þifre - girebilirsiniz. Bu sadece hafif bir güvenlik saðlar, fakat - diðer kiþilerin üyelik ayarlarýnýzý karýþtýrmasýný engeller. - Deðerli bir þifre kullanmayýn, çünkü bu - þifre ara sýra size düz yazý olarak gönderilecektir. + +
        + + + + + + + + + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - - - -
        E-posta adresiniz: + 
        Ãsminiz (isteðe baðlý): 
        Aþaðýya bir þifre + girebilirsiniz. Bu sadece hafif bir güvenlik saðlar, fakat + diðer kiþilerin üyelik ayarlarýnýzý karýþtýrmasýný engeller. + Deðerli bir þifre kullanmayýn, çünkü bu + þifre ara sýra size düz yazý olarak gönderilecektir. -

        Eðer þifre girmemeyi tercih ederseniz, sizin için - bir tane þifre otomatik olarak oluþturulacak ve üyeliðinizi - onayladýðýnýz zaman size gönderilecektir. Kiþisel tercihlerinizi - düzenlerken her zaman þifrenizin tekrar size gönderilmesini +

        Eðer þifre girmemeyi tercih ederseniz, sizin için + bir tane þifre otomatik olarak oluþturulacak ve üyeliðinizi + onayladýðýnýz zaman size gönderilecektir. Kiþisel tercihlerinizi + düzenlerken her zaman þifrenizin tekrar size gönderilmesini isteyebilirsiniz. - -
        -
        Bir þifre girin: 
        Onay için þifreyi tekrar girin: 
        Mesajlarýnýzýn görüntülenmesi için hangi dili tercih edersiniz?  
        Liste mesajlarýnýn size günlük toplu mesajlar olarak - gönderilmesini ister misiniz? + + +
        Bir þifre girin: 
        Onay için þifreyi tekrar girin: 
        Mesajlarýnýzýn görüntülenmesi için hangi dili tercih edersiniz?  
        Liste mesajlarýnýn size günlük toplu mesajlar olarak + gönderilmesini ister misiniz? Hayýr - Evet -
        -
        -
        - -
      -
      - - Üyeleri -
      - - - -

      - - - -

      - - - - +
    Hayýr + Evet +
    +
    +
    + + +

    + + Üyeleri +
    + + + +

    + + + +

    + +

    + diff --git a/templates/tr/options.html b/templates/tr/options.html index ba701c14..63e0df68 100644 --- a/templates/tr/options.html +++ b/templates/tr/options.html @@ -1,311 +1,343 @@ - - <MM-Presentable-User> için <MM-List-Name> mesaj listesi üyelik yapýlandýrmasý - - - - - -
    - - için mesaj listesi üyelik - yapýlandýrmasý -
    -

    - - - - - + +<mm-presentable-user> için <mm-list-name> mesaj listesi üyelik yapýlandýrmasý + </mm-list-name></mm-presentable-user> + + +
    - kullanýcýsýnýn üyelik durumu, - þifresi ve mesaj listesi için seçenekleri. -
    - - - - -

    -

    +
    + + için mesaj listesi üyelik + yapýlandýrmasý +
    - -

    - - - - - - - - +
    - - üyelik bilgilerinizin deðiþimi -
    Mesaj listesine üye olduðunuz e-posta adresini, aþaðýdaki - alanlara yeni bir adres girerek deðiþtirebilirsiniz. Yeni adrese - bir onay mesajý gidecek ve deðiþiklik, onaylanmadan iþleme konulmayacaktýr. - -

    Onay süreleri yaklaþýk içinde biter. - -

    Ýsteðe baðlý olarak gerçek adýnýzý da burada ayarlayabilir veya deðiþtirebilirsiniz - (örneðin John Smith). - -

    Üyelik deðiþikliklerinin üzerinde üye olduðunuz tüm - listelerde geçerli olmasýný istiyorsanýz, Global olarak deðiþtir - onay kutusunu iþaretleyin. - -

    - - - - - - - -
    Yeni adres:
    Onay için - tekrar girin:
    -
    - - - - -
    Ýsminiz - (isteðe baðlý):
    -
    -

    Global olarak deðiþtir

    + + + +
    + kullanýcýsýnýn üyelik durumu, + þifresi ve mesaj listesi için seçenekleri. +
    + + +

    +

    - +

    - - - - - - + + +

    +

    - Listesinden Çýkýþ - Diðer üyelikleriniz -
    - Onay kutusunu iþaretleyip bu düðmeye týklayarak, bu listedeki - üyeliðinizi bitirebilirsiniz. Uyarý: - Bu eylem anýnda gerçekleþecektir! + + + +
    + + üyelik bilgilerinizin deðiþimi +
    Mesaj listesine üye olduðunuz e-posta adresini, aþaðýdaki + alanlara yeni bir adres girerek deðiþtirebilirsiniz. Yeni adrese + bir onay mesajý gidecek ve deðiþiklik, onaylanmadan iþleme konulmayacaktýr. + +

    Onay süreleri yaklaþýk içinde biter. + +

    Ãsteðe baðlý olarak gerçek adýnýzý da burada ayarlayabilir veya deðiþtirebilirsiniz + (örneðin John Smith). + +

    Üyelik deðiþikliklerinin üzerinde üye olduðunuz tüm + listelerde geçerli olmasýný istiyorsanýz, Global olarak deðiþtir + onay kutusunu iþaretleyin. + +

    + + + + + + + +
    Yeni adres:
    Onay için + tekrar girin:
    +
    + + + + +
    Ãsminiz + (isteðe baðlý):
    +
    +

    Global olarak deðiþtir

    + + + - + +
    +

    + Listesinden Çýkýþ +Diðer üyelikleriniz +
    + Onay kutusunu iþaretleyip bu düðmeye týklayarak, bu listedeki + üyeliðinizi bitirebilirsiniz. Uyarý: + Bu eylem anýnda gerçekleþecektir!

    -

    - üzerinde üyesi olduðunuz tüm diðer mesaj listelerinin - bir listesini görebilirsiniz. Bunu, ayný üyelik seçeneklerini - diðer listelerde de yapmak istiyorsanýz kullanýn. +

    + üzerinde üyesi olduðunuz tüm diðer mesaj listelerinin + bir listesini görebilirsiniz. Bunu, ayný üyelik seçeneklerini + diðer listelerde de yapmak istiyorsanýz kullanýn.

    -

    -
    - - - - - - -
    - Your Password -
    - -
    -

    Þifrenizi mi Unuttunuz?

    -
    - Üyelik adresinize þifrenizin yollanmasýný istiyorsanýz bu - düðmeye týklayýn. -

    -

    - -
    -
    - -
    -

    Þifre Deðiþimi

    - - - - - - - - -
    Yeni - þifre:
    Onay için - tekrar yeni þifre:
    - - -

    Global olarak deðiþtir. -
    -
    - + + + +
    +Your Password +
    + +
    +

    Þifrenizi mi Unuttunuz?

    +
    + Üyelik adresinize þifrenizin yollanmasýný istiyorsanýz bu + düðmeye týklayýn. +

    +

    + +
    +

    + +
    +

    Þifre Deðiþimi

    + + + + + + + + +
    Yeni + þifre:
    Onay için + tekrar yeni þifre:
    + +

    Global olarak deðiþtir. +
    +

    - - +
    - Üyelik Seçenekleriniz -
    +
    + Üyelik Seçenekleriniz +
    -

    -Þu andaki deðerleriniz seçili olanlardýr. - -

    Bazý seçeneklerde bir Global olarak ayarla onay kutusu -olduðuna dikkat edin. Bu kutuyu iþaretlemek üzerindeki -tüm mesaj listesi üyeliklerinizde o seçeneðin deðiþtirilmesine neden olur. -Yukarýda Diðer üyeliklerimi listele düðmesine týklayarak -üye olduðunuz diðer mesaj listelerini görebilirsiniz. +Þu andaki deðerleriniz seçili olanlardýr. +

    Bazý seçeneklerde bir Global olarak ayarla onay kutusu +olduðuna dikkat edin. Bu kutuyu iþaretlemek üzerindeki +tüm mesaj listesi üyeliklerinizde o seçeneðin deðiþtirilmesine neden olur. +Yukarýda Diðer üyeliklerimi listele düðmesine týklayarak +üye olduðunuz diðer mesaj listelerini görebilirsiniz.

    - - - +
    - - Mesaj gönderimi

    - Bu seçeneði Etkin seçerek bu mesaj listesine gönderilen - mesajlarýn size iletilmesini saðlayabilirsiniz. Devre Dýþý - seçeneðini seçerseniz, listeye hala üye olursunuz, fakat size - listeden bir süreliðine mesaj gelmesini durdurabilirsiniz (örneðin - tatile çýktýðýnýzda). Eðer mesaj gönderimini devre dýþý býrakýrsanýz - geri geldiðinizde tekrar etkinleþtirmeyi unutmayýn; kendisi - otomatik olarak etkinleþmeyecektir. -

    - Etkin
    - Devre dýþý

    - Global olarak ayarla -

    + - - - +

    + - - - - - - - - - - - - - - - - - - - + + + + + + + + +
    + +Mesaj gönderimi

    + Bu seçeneði Etkin seçerek bu mesaj listesine gönderilen + mesajlarýn size iletilmesini saðlayabilirsiniz. Devre Dýþý + seçeneðini seçerseniz, listeye hala üye olursunuz, fakat size + listeden bir süreliðine mesaj gelmesini durdurabilirsiniz (örneðin + tatile çýktýðýnýzda). Eðer mesaj gönderimini devre dýþý býrakýrsanýz + geri geldiðinizde tekrar etkinleþtirmeyi unutmayýn; kendisi + otomatik olarak etkinleþmeyecektir. +

    +Etkin
    +Devre dýþý

    +Global olarak ayarla +

    - Toplu Mesaj Modunu Ayarla

    - Toplu mesaj modunu açarsanýz mesajlarý gönderildikleri anda tek tek - almak yerine toplu þekilde (genellikle - günde bir kez, fakat olasýlýkla yoðun listelerde birden çok) alýrsýnýz. - Toplu mesaj modu açýk konumundan kapalý konumuna getirilirse +

    +Toplu Mesaj Modunu Ayarla

    + Toplu mesaj modunu açarsanýz mesajlarý gönderildikleri anda tek tek + almak yerine toplu þekilde (genellikle + günde bir kez, fakat olasýlýkla yoðun listelerde birden çok) alýrsýnýz. + Toplu mesaj modu açýk konumundan kapalý konumuna getirilirse son bir toplu mesaj alabilirsiniz. -

    - Açýk
    - Kapalý -
    - MIME veya Düz Yazý Toplu Mesaj Alýmý

    - E-posta okuyucunuz MIME toplu mesajlarý desteklemiyor olabilir. - Genelde MIME toplu mesajlar tercih edilir ama bunlarý okurken - sorun yaþýyorsanýz düz yazý toplu mesajlarý seçin. -

    - MIME
    - Düz Yazý

    - Global olarak ayarla -

    +Açýk
    +Kapalý +
    +MIME veya Düz Yazý Toplu Mesaj Alýmý

    + E-posta okuyucunuz MIME toplu mesajlarý desteklemiyor olabilir. + Genelde MIME toplu mesajlar tercih edilir ama bunlarý okurken + sorun yaþýyorsanýz düz yazý toplu mesajlarý seçin. +

    +MIME
    +Düz Yazý

    +Global olarak ayarla +

    - Mesaja kendi gönderdiðiniz mesajlarý istiyor musunuz?

    - Genellikle listeye gönderdiðiniz tüm mesajlarýn bir kopyasýný - alýrsýnýz. Eðer bu kopyayý almak istemiyorsanýz, bu seçeneði - Hayýr olarak ayarlayýn. -

    - Hayýr
    - Evet -
    - Listeye mesaj gönderdiðinizde bilgi mesajý almak istiyor musunuz?

    -

    - Hayýr
    - Evet -
    - Bu liste için þifre hatýrlatýcý e-posta almak istiyor musunuz?

    - Ayda bir, bu sunucuda üye olduðunuz tüm listeler için - þifre hatýrlatýcý içeren bir e-posta alýrsýnýz. Bunu, bu liste - için, Hayýr seçeneðini seçerek kapatabilirsiniz. - Üye olduðunuz tüm listeler için þifre hatýrlatýcýlarý kapatýrsanýz - size hiçbir hatýrlatýcý e-posta gönderilmeyecektir. -

    - Hayýr
    - Evet

    - Global olarak ayarla -

    - Üye listesinde kendinizi gizlemek istiyor musunuz?

    - Birisi liste üyelerini görüntülediðinde e-posta adresiniz normalde - görünür (spam toplayýcýlarý engellemek için deðiþtirilmiþ bir þekilde). - Eðer üyelik adresinizin bu listede hiç görünmesini istemiyorsanýz, - bu seçenek için Evet seçin. -

    - Hayýr
    - Evet -
    - Hangi dili tercih edersiniz?

    -

    - -
    - Hangi konu kategorilerine üye olmak istersiniz?

    - Bir veya birden fazla konu seçerek mesaj listesindeki - trafiði filtreleyebilirsiniz, böylece mesajlarýn sadece - bir kýsmýný alýrsýnýz. Eðer bir mesaj seçtiðiniz konulardan - bir ile eþleþirse, o mesajý alýrsýnýz, aksi halde almazsýnýz. - -

    Eðer bir mesaj hiçbir konuyla eþleþmiyorsa, Gönderim kuralý - aþaðýdaki seçeneðe uyacaktýr. Eðer herhangi bir konu - seçmezseniz, mesaj listesine gönderilen tüm mesajlarý - alýrsýnýz. -

    - -
    - Hiçbir konu filtresine uymayan mesajlarý almak istiyor musunuz?

    - - Bu seçenek yukarýda en az bir konuya üye olduysanýz - etkilidir. Hiçbir konu filtresine uymayan mesajlar için - varsayýlan gönderim kuralýný tanýmlar. Burada Hayýr - seçeneðini seçmek, eðer bir mesaj konu filtreleri ile - eþleþmiyorsa, o mesajý almayacaðýnýzý gösterir, Evet - seçmek, filtrelerle eþleþmeyen mesajlarýn da size - geleceðini gösterir. - -

    Eðer yukarýda herhangi bir konu seçmediyseniz, - mesaj listesine atýlan tüm mesajlarý alýrsýnýz. -

    - Hayýr
    - Evet -
    - Mesajlarýn kopyalarý engellensin mi?

    - - Bir liste mesajýnýn Alýcý: veya Cc: - baþlýklarýnda açýk olarak adresiniz varsa, mesaj listesinden - mesajýn baþka bir kopyasýný almamayý tercih edebilirsiniz. - Mesaj listesinden gelecek kopyalarý engellemek için Evet, - kopyalarý almak için Hayýr seçin. - -

    Listede üyeye kiþiselleþtirilmiþ mesajlar etkinse - ve kopyalarý almayý tercih ederseniz, her kopya bir - X-Mailman-Copy: yes baþlýðý içerecektir. - -

    - Hayýr
    - Evet

    - Global olarak ayarla -

    -
    -
    +Mesaja kendi gönderdiðiniz mesajlarý istiyor musunuz?

    + Genellikle listeye gönderdiðiniz tüm mesajlarýn bir kopyasýný + alýrsýnýz. Eðer bu kopyayý almak istemiyorsanýz, bu seçeneði + Hayýr olarak ayarlayýn. +

    +Hayýr
    +Evet +
    +Listeye mesaj gönderdiðinizde bilgi mesajý almak istiyor musunuz?

    +

    +Hayýr
    +Evet +
    +Bu liste için þifre hatýrlatýcý e-posta almak istiyor musunuz?

    + Ayda bir, bu sunucuda üye olduðunuz tüm listeler için + þifre hatýrlatýcý içeren bir e-posta alýrsýnýz. Bunu, bu liste + için, Hayýr seçeneðini seçerek kapatabilirsiniz. + Üye olduðunuz tüm listeler için þifre hatýrlatýcýlarý kapatýrsanýz + size hiçbir hatýrlatýcý e-posta gönderilmeyecektir. +

    +Hayýr
    +Evet

    +Global olarak ayarla +

    +Üye listesinde kendinizi gizlemek istiyor musunuz?

    + Birisi liste üyelerini görüntülediðinde e-posta adresiniz normalde + görünür (spam toplayýcýlarý engellemek için deðiþtirilmiþ bir þekilde). + Eðer üyelik adresinizin bu listede hiç görünmesini istemiyorsanýz, + bu seçenek için Evet seçin. +

    +Hayýr
    +Evet +
    +Hangi dili tercih edersiniz?

    +

    + +
    +Hangi konu kategorilerine üye olmak istersiniz?

    + Bir veya birden fazla konu seçerek mesaj listesindeki + trafiði filtreleyebilirsiniz, böylece mesajlarýn sadece + bir kýsmýný alýrsýnýz. Eðer bir mesaj seçtiðiniz konulardan + bir ile eþleþirse, o mesajý alýrsýnýz, aksi halde almazsýnýz. + +

    Eðer bir mesaj hiçbir konuyla eþleþmiyorsa, Gönderim kuralý + aþaðýdaki seçeneðe uyacaktýr. Eðer herhangi bir konu + seçmezseniz, mesaj listesine gönderilen tüm mesajlarý + alýrsýnýz. +

    + +
    +Hiçbir konu filtresine uymayan mesajlarý almak istiyor musunuz?

    + + Bu seçenek yukarýda en az bir konuya üye olduysanýz + etkilidir. Hiçbir konu filtresine uymayan mesajlar için + varsayýlan gönderim kuralýný tanýmlar. Burada Hayýr + seçeneðini seçmek, eðer bir mesaj konu filtreleri ile + eþleþmiyorsa, o mesajý almayacaðýnýzý gösterir, Evet + seçmek, filtrelerle eþleþmeyen mesajlarýn da size + geleceðini gösterir. + +

    Eðer yukarýda herhangi bir konu seçmediyseniz, + mesaj listesine atýlan tüm mesajlarý alýrsýnýz. +

    +Hayýr
    +Evet +
    +Mesajlarýn kopyalarý engellensin mi?

    + + Bir liste mesajýnýn Alýcý: veya Cc: + baþlýklarýnda açýk olarak adresiniz varsa, mesaj listesinden + mesajýn baþka bir kopyasýný almamayý tercih edebilirsiniz. + Mesaj listesinden gelecek kopyalarý engellemek için Evet, + kopyalarý almak için Hayýr seçin. + +

    Listede üyeye kiþiselleþtirilmiþ mesajlar etkinse + ve kopyalarý almayý tercih ederseniz, her kopya bir + X-Mailman-Copy: yes baþlýðý içerecektir. + +

    +Hayýr
    +Evet

    +Global olarak ayarla +

    +
    +
    -

    - - - - + + +

    - diff --git a/templates/tr/private.html b/templates/tr/private.html index cfba15cd..5ee831f8 100755 --- a/templates/tr/private.html +++ b/templates/tr/private.html @@ -1,61 +1,121 @@ - %(realname)s Özel Arþivleri Giriþi - - - -
    +%(realname)s Özel Arþivleri Giriþi + + + + %(message)s - - - - - - - - - - - - - - - -
    - %(realname)s Özel - Arþivleri Giriþi -
    E-posta adresi:
    Þifre:
    -
    -

    Önemli: Bu noktadan itibaren tarayýcýnýzda - tanýmlama bilgilerini (cookie) etkinleþtirmiþ olmanýz gerekir, yoksa hiçbir - yönetimsel deðiþiklik yapýlmayacaktýr. + + + + + + + + + + + + + + + +
    +%(realname)s Özel + Arþivleri Giriþi +
    E-posta adresi:
    Þifre:
    +
    +

    Önemli: Bu noktadan itibaren tarayýcýnýzda + tanýmlama bilgilerini (cookie) etkinleþtirmiþ olmanýz gerekir, yoksa hiçbir + yönetimsel deðiþiklik yapýlmayacaktýr. -

    Mailman'in yönetimsel arayüzünde oturum tanýmlayýcý bilgileri - kullanýlýr, böylece her yönetimsel iþlemde tekrar yetkilendirme - yapmanýza gerek kalmaz. Bu tanýmlayýcý bilgi tarayýcýnýzdan - çýktýðýnýzda otomatik olarak yok olur, veya siz tanýmlayýcý - bilgiyi Diðer Yönetimsel Aktiviteler baþlýðý altýndaki - Çýkýþ linkine týklayarak (baþarýyla giriþ yaptýðýnýzda - bunu göreceksiniz) kendiniz yok edebilirsiniz. +

    Mailman'in yönetimsel arayüzünde oturum tanýmlayýcý bilgileri + kullanýlýr, böylece her yönetimsel iþlemde tekrar yetkilendirme + yapmanýza gerek kalmaz. Bu tanýmlayýcý bilgi tarayýcýnýzdan + çýktýðýnýzda otomatik olarak yok olur, veya siz tanýmlayýcý + bilgiyi Diðer Yönetimsel Aktiviteler baþlýðý altýndaki + Çýkýþ linkine týklayarak (baþarýyla giriþ yaptýðýnýzda + bunu göreceksiniz) kendiniz yok edebilirsiniz.

    - - - - - - + + + +
    - Password Reminder -
    If you don't remember your password, enter your email address + + + + + + - - - - -
    +Password Reminder +
    If you don't remember your password, enter your email address above and click the Remind button and your password will be emailed to you.
    - +
    +

    - diff --git a/templates/tr/roster.html b/templates/tr/roster.html index 7265a7bd..f4db36cd 100644 --- a/templates/tr/roster.html +++ b/templates/tr/roster.html @@ -1,54 +1,112 @@ - - - <MM-List-Name> Üyeleri - - - - -

    - - - - - - - - - - - - - - - -
    - - Üyeleri -
    - -

    -

    - -

    Adresinizin üzerine týklayarak üyelik seçenekleri sayfanýza - gidebilirsiniz.
    (Parantez içine alýnmýþ adresler listeden - mesaj alýmýný devre dýþý býrakmýþtýr.)

    -
    -
    - - Listesinin Ayrý Ayrý Mesaj Alan Üyesi: -
    -
    -
    - Listesinin Toplu - Mesaj Alan Üyesi: -
    -
    -

    -

    -

    -

    - - - - + + +<mm-list-name> Üyeleri</mm-list-name> + + +

    + + + + + + + + + + + + + + + +
    + + Üyeleri +
    +

    +

    +

    Adresinizin üzerine týklayarak üyelik seçenekleri sayfanýza + gidebilirsiniz.
    (Parantez içine alýnmýþ adresler listeden + mesaj alýmýný devre dýþý býrakmýþtýr.)

    +
    +
    + + Listesinin Ayrý Ayrý Mesaj Alan Üyesi: +
    +
    +
    + Listesinin Toplu + Mesaj Alan Üyesi: +
    +
    +

    +

    +

    +

    + +

    + diff --git a/templates/tr/subscribe.html b/templates/tr/subscribe.html index 844804da..513a2d67 100644 --- a/templates/tr/subscribe.html +++ b/templates/tr/subscribe.html @@ -1,9 +1,72 @@ -<MM-List-Name> Subscription results +<mm-list-name> Subscription results</mm-list-name> -

    Subscription results

    - - - +

    Subscription results

    + + + diff --git a/templates/uk/admindbdetails.html b/templates/uk/admindbdetails.html index 84d51153..7555fda8 100644 --- a/templates/uk/admindbdetails.html +++ b/templates/uk/admindbdetails.html @@ -1,5 +1,67 @@ -ÐдмініÑтративні запити предÑтавлені двома ÑпоÑобами, на Ñторінці загальний ÑпиÑок, та на Ñторінці +ÐдмініÑтративні запити предÑтавлені двома ÑпоÑобами, на Ñторінці загальний ÑпиÑок, та на Ñторінці подробиці. Сторінка загальний ÑпиÑок міÑтить запити щодо підпиÑки, та Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð¿Ñ–Ð´Ð¿Ð¸Ñки, а також згруповані за адреÑою відправника Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð²Ñ–Ð´ÐºÐ»Ð°Ð´ÐµÐ½Ñ– @@ -29,8 +91,7 @@ членÑтва у ÑпиÑку, це означатиме Ð²Ñ–Ð´ÐºÐ¸Ð´Ð°Ð½Ð½Ñ Ð·Ð¼Ñ–Ð½ у Ñтані підпиÑки без жодного ÑÐ¿Ð¾Ð²Ñ–Ñ‰ÐµÐ½Ð½Ñ Ð¾Ñоби, що надіÑлала запит. Зазвичай цю дію викориÑтовують Ð´Ð»Ñ Ð²Ñ–Ð´ÐºÐ¸Ð´Ð°Ð½Ð½Ñ Ñпаму. - - +

    Якщо ви збираєтеÑÑŒ зберегти копію відкладеного Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð´Ð»Ñ Ð°Ð´Ð¼Ñ–Ð½Ñ–Ñтратора Ñерверу, відзначте параметр Зберегти Цим кориÑтуютьÑÑ Ð´Ð»Ñ Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½ÑŒ Ñкі збираютьÑÑ Ð²Ñ–Ð´ÐºÐ¸Ð½ÑƒÑ‚Ð¸, @@ -65,3 +126,4 @@ буде виконано вÑÑ– дії.

    ПовернутиÑÑŒ до загального ÑпиÑку. +

    \ No newline at end of file diff --git a/templates/uk/admindbpreamble.html b/templates/uk/admindbpreamble.html index c9010c82..e536ea99 100644 --- a/templates/uk/admindbpreamble.html +++ b/templates/uk/admindbpreamble.html @@ -1,4 +1,67 @@ -Ð¦Ñ Ñторінка міÑтить чаÑтину запитів до поштового ÑпиÑку +Ð¦Ñ Ñторінка міÑтить чаÑтину запитів до поштового ÑпиÑку %(listname)s, Ñкі очікують вашого розглÑду. Зараз на ній показані %(description)s @@ -8,3 +71,4 @@

    Також ви можете переглÑнути загальний ÑпиÑок вÑÑ–Ñ… запитів, що очікують розглÑду. +

    \ No newline at end of file diff --git a/templates/uk/admindbsummary.html b/templates/uk/admindbsummary.html index b8b7de6d..43bc39af 100644 --- a/templates/uk/admindbsummary.html +++ b/templates/uk/admindbsummary.html @@ -1,4 +1,67 @@ -Ð¦Ñ Ñторінка міÑтить загальний ÑпиÑок адмініÑтративних запитів, +Ð¦Ñ Ñторінка міÑтить загальний ÑпиÑок адмініÑтративних запитів, що очікують вашого розглÑду Ð´Ð»Ñ ÑпиÑку %(listname)s. Ðа початку йде перелік запитів на підпиÑку та Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð¿Ñ–Ð´Ð¿Ð¸Ñки, @@ -11,3 +74,4 @@

    Ðа Ñторінці подробиці ви знайдете докладну інформацію про кожне відкладене повідомленнÑ. +

    \ No newline at end of file diff --git a/templates/uk/admlogin.html b/templates/uk/admlogin.html index fd9f1655..9e610203 100755 --- a/templates/uk/admlogin.html +++ b/templates/uk/admlogin.html @@ -1,30 +1,91 @@ - ÐÐ²Ñ‚ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ñ ÐºÐ¾Ñ€Ð¸Ñтувача %(who)s ÑпиÑку лиÑÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ %(listname)s +ÐÐ²Ñ‚ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ñ ÐºÐ¾Ñ€Ð¸Ñтувача %(who)s ÑпиÑку лиÑÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ %(listname)s - - -
    + + + %(message)s - - - - - - - - - - - -
    - ÐÐ²Ñ‚ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ñ ÐºÐ¾Ñ€Ð¸Ñтувача %(who)s ÑпиÑку - лиÑÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ %(listname)s -
    Введіть пароль кориÑтувача %(who)s:
    -
    -

    Увага: Ðеобхідно дозволити + + + + + + + + + + + +
    +ÐÐ²Ñ‚ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ñ ÐºÐ¾Ñ€Ð¸Ñтувача %(who)s ÑпиÑку + лиÑÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ %(listname)s +
    Введіть пароль кориÑтувача %(who)s:
    +
    +

    Увага: Ðеобхідно дозволити викориÑÑ‚Ð°Ð½Ð½Ñ cookies у вашому переглÑдачі, у іншому випадку ви не зможете внеÑти жодних змін. @@ -35,6 +96,6 @@ завершити ÑÐµÐ°Ð½Ñ Ð½Ð°Ñ‚Ð¸Ñшувши поÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð’Ñ–Ð´'єднатиÑÑŒ у розділі Інші адмініÑтративні Ð·Ð°Ð²Ð´Ð°Ð½Ð½Ñ (Ви зможете його побачити лише піÑÐ»Ñ Ð¿Ð¾Ñ‡Ð°Ñ‚ÐºÑƒ ÑеанÑу). -

    +

    diff --git a/templates/uk/archidxentry.html b/templates/uk/archidxentry.html index f9bb57aa..5c620b20 100644 --- a/templates/uk/archidxentry.html +++ b/templates/uk/archidxentry.html @@ -1,4 +1,68 @@ -
  • %(subject)s -  -%(author)s - +
  • %(subject)s +  +%(author)s + +
  • \ No newline at end of file diff --git a/templates/uk/archidxfoot.html b/templates/uk/archidxfoot.html index c1506470..54621a9c 100644 --- a/templates/uk/archidxfoot.html +++ b/templates/uk/archidxfoot.html @@ -1,20 +1,84 @@ - -

    - Дата оÑтаннього повідомленнÑ: - %(lastdate)s
    - Ðрхів оновлено: %(archivedate)s -

    -

      -
    • Сортувати за: + +

      +Дата оÑтаннього повідомленнÑ: +%(lastdate)s
      +Ðрхів оновлено: %(archivedate)s +

      +

      -

      -


      - Цей архів було Ñтворено програмою Pipermail %(version)s. - - +
    +

    +


    +Цей архів було Ñтворено програмою Pipermail %(version)s. + + +

    \ No newline at end of file diff --git a/templates/uk/archidxhead.html b/templates/uk/archidxhead.html index a98f200d..5c693bc6 100644 --- a/templates/uk/archidxhead.html +++ b/templates/uk/archidxhead.html @@ -1,15 +1,79 @@ - - - Ðрхів %(listname)s, том %(archive)s, Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð²Ð¿Ð¾Ñ€Ñдковані за %(archtype)s - + + + +Ðрхів %(listname)s, том %(archive)s, Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð²Ð¿Ð¾Ñ€Ñдковані за %(archtype)s + %(encoding)s - - - -

    Том %(archive)s, Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð²Ð¿Ð¾Ñ€Ñдковані %(archtype)s

    -
      -
    • Сортувати за: + + + +

      Том %(archive)s, Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð²Ð¿Ð¾Ñ€Ñдковані %(archtype)s

      + -

      Початок: %(firstdate)s
      - Кінець: %(lastdate)s
      - Повідомлень: %(size)s

      -

        +
      +

      Початок: %(firstdate)s
      +Кінець: %(lastdate)s
      +Повідомлень: %(size)s

      +

        +

      \ No newline at end of file diff --git a/templates/uk/archlistend.html b/templates/uk/archlistend.html index 9bc052dd..2e1191b0 100644 --- a/templates/uk/archlistend.html +++ b/templates/uk/archlistend.html @@ -1 +1,64 @@ -
    + diff --git a/templates/uk/archliststart.html b/templates/uk/archliststart.html index 9bb211b4..4a4a8641 100644 --- a/templates/uk/archliststart.html +++ b/templates/uk/archliststart.html @@ -1,4 +1,68 @@ - - - - +
    Том архівуСортувати за:ВерÑÑ–Ñ Ð´Ð»Ñ Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ
    + + + +
    Том архівуСортувати за:ВерÑÑ–Ñ Ð´Ð»Ñ Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ
    \ No newline at end of file diff --git a/templates/uk/archtoc.html b/templates/uk/archtoc.html index 2b01317e..921ffb5d 100644 --- a/templates/uk/archtoc.html +++ b/templates/uk/archtoc.html @@ -1,13 +1,77 @@ - - - Ðрхів %(listname)s - + + + +Ðрхів %(listname)s + %(meta)s - - -

    Ðрхіви %(listname)s

    -

    + + +

    Ðрхіви %(listname)s

    +

    Ви можете отримати докладну інформацію про цей ÑпиÑок розÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð°Ð±Ð¾ завантажити архів у форматі mbox (%(size)s). @@ -16,5 +80,5 @@

    Ðрхіви %(listname)s

    %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/uk/archtocentry.html b/templates/uk/archtocentry.html index e915834a..29a517bd 100644 --- a/templates/uk/archtocentry.html +++ b/templates/uk/archtocentry.html @@ -1,12 +1,74 @@ - - - %(archivelabel)s: - - [ за гілками ] - [ за темами ] - [ за авторами ] - [ за датою ] - + + +%(archivelabel)s: + +[ за гілками ] +[ за темами ] +[ за авторами ] +[ за датою ] + %(textlink)s - diff --git a/templates/uk/archtocnombox.html b/templates/uk/archtocnombox.html index cab4a15a..1bae8bf2 100644 --- a/templates/uk/archtocnombox.html +++ b/templates/uk/archtocnombox.html @@ -1,18 +1,82 @@ - - - Ðрхів %(listname)s - + + + +Ðрхів %(listname)s + %(meta)s - - -

    Ðрхів %(listname)s

    -

    + + +

    Ðрхів %(listname)s

    +

    Ви можете отримати додаткову інформацію про цей ÑпиÑок.

    %(noarchive_msg)s %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/uk/article.html b/templates/uk/article.html index deb878d8..fae56296 100644 --- a/templates/uk/article.html +++ b/templates/uk/article.html @@ -1,12 +1,13 @@ - - - %(title)s - - - - - + +

    Ðрхів ÑпиÑку розÑилки %(listname)s

    +

    До цього ÑпиÑку ще не було відправлено жодного повідомленнÑ, тому архів порожній. Поки-що ви можете переглÑнути загальну інформацію про цей ÑпиÑок розÑилки.

    - - + + diff --git a/templates/uk/headfoot.html b/templates/uk/headfoot.html index fecd2170..2e915e90 100644 --- a/templates/uk/headfoot.html +++ b/templates/uk/headfoot.html @@ -1,10 +1,73 @@ -Цей текÑÑ‚ може включати +Цей текÑÑ‚ може включати форматовані Ñ€Ñдки, Ñкі пов'Ñзані з ознаками ÑпиÑку. Перелік дозволених ознак:
      -
    • real_name - "гарна" назва ÑпиÑку; зазвичай назва +
    • real_name - "гарна" назва ÑпиÑку; зазвичай назва ÑпиÑку з великою першою літерою.
    • list_name - назва ÑпиÑку, Ñка викориÑтовуєтьÑÑ @@ -21,4 +84,4 @@
    • info - повний Ð¾Ð¿Ð¸Ñ ÑпиÑку розÑилки.
    • cgiext - Ñ€Ð¾Ð·ÑˆÐ¸Ñ€ÐµÐ½Ð½Ñ Ñ„Ð°Ð¹Ð»Ñ–Ð² CGI Ñценаріїв. -
    + diff --git a/templates/uk/listinfo.html b/templates/uk/listinfo.html index b3aec00e..1c7aebab 100644 --- a/templates/uk/listinfo.html +++ b/templates/uk/listinfo.html @@ -1,144 +1,205 @@ - - - - Загальна Ñ–Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ Ð¿Ñ€Ð¾ ÑпиÑок розÑилки <MM-List-Name> - - - -

    - - - - - - - - - - - - - - - - - - - + + + + + +

    - -- - -
    -

      -

    - Про ÑпиÑок розÑилки - - - -
    -

    -

    Щоб переглÑнути збірку попередніх повідомлень до ÑпиÑку, - відвідайте Ðрхіви ÑпиÑку розÑилки . - -

    -
    - ВикориÑÑ‚Ð°Ð½Ð½Ñ -
    + + + +Загальна Ñ–Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ Ð¿Ñ€Ð¾ ÑпиÑок розÑилки <mm-list-name></mm-list-name> + + +

    + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + +
    + -- + +
    +

      +

    +Про ÑпиÑок розÑилки + + + +
    +

    +

    Щоб переглÑнути збірку попередніх повідомлень до ÑпиÑку, + відвідайте Ðрхіви ÑпиÑку розÑилки . + +

    +
    +ВикориÑÑ‚Ð°Ð½Ð½Ñ +
    Щоб надіÑлати Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð²Ñім кориÑтувачам ÑпиÑку розÑилки, відправте його за адреÑою - . + .

    Ви можете підпиÑатиÑÑŒ чи змінити параметри підпиÑки на ÑпиÑок розÑилки викориÑтовуючи наведену нижче інформацію. -

    - ПідпиÑатиÑÑŒ на ÑпиÑок розÑилки -
    -

    - Щоб підпиÑатиÑÑŒ на ÑпиÑок розÑилки заповніть наÑтупну форму. - -

      - - - - - - - - - - - - + + + + + + - - - - - -
      Ваша електронна адреÑа: -  
      Ваше ім'Ñ (необов'Ñзково): 
      Ви можете ввеÑти пароль, це зашкодить +

      +ПідпиÑатиÑÑŒ на ÑпиÑок розÑилки +
      +

      + Щоб підпиÑатиÑÑŒ на ÑпиÑок розÑилки заповніть наÑтупну форму. + +

        + + + + + + + + + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - - - -
        Ваша електронна адреÑа: + 
        Ваше ім'Ñ (необов'Ñзково): 
        Ви можете ввеÑти пароль, це зашкодить іншим змінювати параметри вашої підпиÑки. Ðе викориÑтовуйте цінних паролей, тому що паролі надÑилатимутьÑÑ Ð²Ð°Ð¼ відкритим текÑтом. -

        Якщо ви не бажаєте вводити пароль, він може бути згенерований автоматично, +

        Якщо ви не бажаєте вводити пароль, він може бути згенерований автоматично, та його буде надіÑлано вам піÑÐ»Ñ Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð¿Ñ–Ð´Ð¿Ð¸Ñки. Ðа Ñторінці параметрів вашої підпиÑки, Ви завжди зможете запитати щоб ваш пароль було надіÑлано вам електронною поштою. - -
        -
        Вкажіть пароль: 
        Вкажіть ще раз: 
        Якою мовою ви бажаєте отримувати Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð· Ñервера?  
        Бажаєте отримувати Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ñƒ виглÑді щоденних збірок? + + +
        Вкажіть пароль: 
        Вкажіть ще раз: 
        Якою мовою ви бажаєте отримувати Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð· Ñервера?  
        Бажаєте отримувати Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ñƒ виглÑді щоденних збірок? Так - ÐÑ– -
        -
        -
        - -
      -
      - - КориÑтувачі ÑпиÑку розÑилки -
      - - - -

      - - - -

      - - - +
    Так + ÐÑ– +
    +
    +
    + + +

    + +КориÑтувачі ÑпиÑку розÑилки +
    + + + +

    + + + +

    + +

    + diff --git a/templates/uk/options.html b/templates/uk/options.html index aa30aab5..04c6aad2 100644 --- a/templates/uk/options.html +++ b/templates/uk/options.html @@ -1,43 +1,103 @@ - - <MM-Presentable-User>: параметри підпиÑки на ÑпиÑок розÑилки <MM-List-Name> - - - - - -
    - - Параметри підпиÑки на ÑпиÑок розÑилки - -
    + +<mm-presentable-user>: параметри підпиÑки на ÑпиÑок розÑилки <mm-list-name> +</mm-list-name></mm-presentable-user> + + + + +
    + + Параметри підпиÑки на ÑпиÑок розÑилки + +

    - - - - - +
    - Стан підпиÑки , - пароль, та параметри підпиÑки на ÑпиÑок розÑилки . -
    - - - - -

    -

    + + + +
    +Стан підпиÑки , + пароль, та параметри підпиÑки на ÑпиÑок розÑилки . +
    + + +

    +

    - - +

    - - - +
    - - Зміна вашої підпиÑки на ÑпиÑок розÑилки -
    Ви можете змінити адреÑу, куди має надходити підпиÑка. + + + - - - - - -
    + +Зміна вашої підпиÑки на ÑпиÑок розÑилки +
    Ви можете змінити адреÑу, куди має надходити підпиÑка. Вкажіть Ñ—Ñ— у полі нижче. Зауважте, що на нову адреÑу буде відправлено Ð¿Ñ€Ð¾Ñ…Ð°Ð½Ð½Ñ Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¸Ñ‚Ð¸ зміну адреÑи, без Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð·Ð¼Ñ–Ð½Ð¸ внеÑені не будуть. @@ -49,198 +109,177 @@

    Якщо ви бажаєте щоб зазначені зміни відбулиÑÑŒ у вÑÑ–Ñ… ÑпиÑках розÑилки на Ñервері , ввімкніть параметр Змінити глобально. -

    - - - - - - - -
    Ðова адреÑа:
    Ще раз:
    -
    - - - - -
    Ваше ім'Ñ (необов'Ñзково):
    -
    -

    Змінити глобально

    - +

    + + + + + + + +
    Ðова адреÑа:
    Ще раз:
    +

    + + + + +
    Ваше ім'Ñ (необов'Ñзково):
    + +
    +

    Змінити глобально

    +

    - - - - - - - - + + + + %(textlink)s - diff --git a/templates/vi/archtocnombox.html b/templates/vi/archtocnombox.html index efabdcdf..b051ec92 100644 --- a/templates/vi/archtocnombox.html +++ b/templates/vi/archtocnombox.html @@ -1,18 +1,82 @@ - - - Kho cá»§a %(listname)s - + + + +Kho cá»§a %(listname)s + %(meta)s - - -

    Kho cá»§a %(listname)s

    -

    + + +

    Kho cá»§a %(listname)s

    +

    Bạn có thể lấy thông tin thêm vỠhộp thư chung này.

    %(noarchive_msg)s %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/vi/article.html b/templates/vi/article.html index d2594f18..eb946c58 100644 --- a/templates/vi/article.html +++ b/templates/vi/article.html @@ -1,12 +1,13 @@ - - - %(title)s - - - - - + +

    Kho cá»§a %(listname)s

    +

    Chưa có thư nào được gởi cho há»™p thư chung này, vì vậy kho hiện thá»i là rá»—ng. Bạn có thể xem thông tin thêm vá» há»™p thư này.

    - - + + diff --git a/templates/vi/headfoot.html b/templates/vi/headfoot.html index 37a432dd..4bb69cdc 100644 --- a/templates/vi/headfoot.html +++ b/templates/vi/headfoot.html @@ -1,18 +1,81 @@ -Äoạn này có thể gồm +Äoạn này có thể gồm chuá»—i dạng thức Python mà được khá»›p vá»›i thuá»™c tính há»™p thư chung. Danh sách các Ä‘iá»u thay thế có thể là:
      -
    • real_name — ten « xinh » cá»§a há»™p thư chung đó, thưá»ng là tên há»™p thư vá»›i chữ hoa đầu từ. +
    • real_name — ten « xinh » cá»§a há»™p thư chung đó, thưá»ng là tên há»™p thư vá»›i chữ hoa đầu từ. -
    • list_name — tên nhận diện há»™p thư chung đó trong địa chỉ Mạng, mà phân biệt chữ hoa/thưá»ng. +
    • list_name — tên nhận diện há»™p thư chung đó trong địa chỉ Mạng, mà phân biệt chữ hoa/thưá»ng. -
    • host_name — tên miá»n có khả năng đầy đủ nÆ¡i trình phục vụ há»™p thư chung đó có chạy. +
    • host_name — tên miá»n có khả năng đầy đủ nÆ¡i trình phục vụ há»™p thư chung đó có chạy. -
    • web_page_url — địa chỉ cÆ¡ bản cho Mailman. Có thể phụ thêm, v.d. listinfo/%(list_name)s để tạo trang thông tin vá» há»™p thư chung đó. +
    • web_page_url — địa chỉ cÆ¡ bản cho Mailman. Có thể phụ thêm, v.d. listinfo/%(list_name)s để tạo trang thông tin vá» há»™p thư chung đó. -
    • description — mô tả ngắn vá» há»™p thư chung đó. +
    • description — mô tả ngắn vá» há»™p thư chung đó. -
    • info — mô tả đầy đủ vá» há»™p thư chung đó. +
    • info — mô tả đầy đủ vá» há»™p thư chung đó. -
    • cgiext — phần mở rá»™ng được thêm vào tập lệnh CGI. -
    +
  • cgiext — phần mở rá»™ng được thêm vào tập lệnh CGI. +
  • diff --git a/templates/vi/listinfo.html b/templates/vi/listinfo.html index 7d28de56..12593b5c 100644 --- a/templates/vi/listinfo.html +++ b/templates/vi/listinfo.html @@ -1,135 +1,196 @@ - - - - <MM-List-Name>Trang thông tin - - - -

    -

    - Видалити підпиÑку з - Інші ваші підпиÑки на Ñервері -
    + + + + - + +
    +

    +Видалити підпиÑку з +Інші ваші підпиÑки на Ñервері +
    Щоб відпиÑатиÑÑŒ від цього ÑпиÑку ввімкніть параметр Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ñ‚Ð° натиÑніть на цю кнопку. Увага: Цю дію буде виконано негайно!

    -

    +

    Ви можете переглÑнути ÑпиÑок вÑÑ–Ñ… інших ÑпиÑків на , кориÑтувачем Ñких ви Ñ”. ВикориÑтовуйте його, Ñкщо хочете виконати однакові зміни у параметрах підпиÑки в уÑÑ–Ñ… ÑпиÑках розÑилки одночаÑно.

    -

    -
    - - - - - +
    - Пароль до ÑпиÑку розÑилки -
    - -
    -

    Забули пароль?

    -
    + + + - -
    +Пароль до ÑпиÑку розÑилки +
    + +
    +

    Забули пароль?

    +
    ÐатиÑніть на цю кнопку, щоб відправити пароль на адреÑу вказану у параметрах підпиÑки. -

    -

    - -
    -
    - -
    -

    Змінити пароль підпиÑки

    - - - - - - - - -
    Ðовий пароль:
    Ще раз:
    - - -

    Змінити глобально. -
    -
    - +

    +

    + +
    +

    + +
    +

    Змінити пароль підпиÑки

    + + + + + + + + +
    Ðовий пароль:
    Ще раз:
    + +

    Змінити глобально. +
    +

    - - +
    - ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ñ–Ð´Ð¿Ð¸Ñки на ÑпиÑок -
    +
    +ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ñ–Ð´Ð¿Ð¸Ñки на ÑпиÑок +
    -

    Поточні Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð²Ñ–Ð´Ð¼Ñ–Ñ‡ÐµÐ½Ð¾. -

    Зауважте, Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð´ÐµÑких параметрів мають поле Змінити глобально. Ð’Ð²Ñ–Ð¼ÐºÐ½ÐµÐ½Ð½Ñ Ñ†ÑŒÐ¾Ð³Ð¾ Ð¿Ð¾Ð»Ñ Ð¿Ñ€Ð¸Ð·Ð²ÐµÐ´Ðµ до змінити Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð°Ñ€Ð°Ð¼ÐµÑ‚Ñ€Ñƒ в уÑÑ–Ñ… ÑпиÑках розÑилки на . Ð”Ð»Ñ Ð¿ÐµÑ€ÐµÐ³Ð»Ñду переліку ÑпиÑків розÑилок, на Ñкі ви підпиÑані натиÑніть Інші ваші підпиÑки на Ñервері вище..

    - -
    - - ДоÑтавка пошти

    + + - +

    - - - + +

    - - - - - - + + - - + - - - - + + - - + - - + - - - +

    +
    + +ДоÑтавка пошти

    Ð’Ñтановіть цей параметр у Ввімкнено щоб отримувати Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ñ†ÑŒÐ¾Ð³Ð¾ ÑпиÑку розÑилки. Ð’Ñтановіть у Вимкнено Ñкщо хочете залишитиÑÑŒ підпиÑаним, але не хочете отримувати Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð¿ÐµÐ²Ð½Ð¸Ð¹ Ñ‡Ð°Ñ (наприклад через відпуÑтку). Якщо ви вимкнули доÑтавку пошти, не забудьте ввімкнути Ñ—Ñ— коли повернетеÑÑŒ; автоматичного Ð½Ð°Ð³Ð°Ð´ÑƒÐ²Ð°Ð½Ð½Ñ Ð² цьому випадку не передбачено. -

    - Ввімкнено
    - Вимкнено

    - Ð’Ñтановити глобально -

    +Ввімкнено
    +Вимкнено

    +Ð’Ñтановити глобально +

    - Ð’ÑÑ‚Ð°Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ñ€ÐµÐ¶Ð¸Ð¼Ñƒ збірок

    +

    +Ð’ÑÑ‚Ð°Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ñ€ÐµÐ¶Ð¸Ð¼Ñƒ збірок

    Якщо ви ввімкнули режим збірок, ви отримуватимете згруповані Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ (зазвичай один раз на добу але може чаÑтіше Ð´Ð»Ñ Ð²ÐµÐ»Ð¸ÐºÐ¸Ñ… ÑпиÑків), заміÑть окремих. Якщо ви перемикнули режим збірок з Ввімкнено на Вимкнено, ви ще можете отримати один оÑтанню збірку. -

    - Ввімкнено
    - Вимкнено -
    - Отримувати збірки у MIME або звичайним текÑтом?

    +

    +Ввімкнено
    +Вимкнено +
    +Отримувати збірки у MIME або звичайним текÑтом?

    Ваша поштова програма може не підтримувати MIME збірки. Ð’ загалі MIME збірки більш зручні, але Ñкщо у Ð²Ð°Ñ Ð· ними виникають проблеми - оберіть звичайний текÑÑ‚. -

    - MIME
    - Звичайний текÑÑ‚

    - Ð’Ñтановити глобально -

    +MIME
    +Звичайний текÑÑ‚

    +Ð’Ñтановити глобально +

    - Отримувати влаÑні Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð½Ð°Ð´Ñ–Ñлані у ÑпиÑок?

    +

    +Отримувати влаÑні Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð½Ð°Ð´Ñ–Ñлані у ÑпиÑок?

    Зазвичай, ви отримуватимете копію кожного надіÑланого до ÑпиÑку повідомленнÑ. Якщо ви не бажаєте Ñ—Ñ… отримувати вÑтановіть цей параметр у ÐÑ–. -

    - ÐÑ–
    - Так -
    - Отримувати Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ð¿Ð¾ÑˆÑ‚Ð¸ при надÑиланні повідомлень до ÑпиÑку?

    -

    - ÐÑ–
    - Так
    - Отримувати Ð½Ð°Ð³Ð°Ð´ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ð°Ñ€Ð¾Ð»ÑŽ щоміÑÑцÑ?

    +

    +ÐÑ–
    +Так +
    +Отримувати Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ð¿Ð¾ÑˆÑ‚Ð¸ при надÑиланні повідомлень до ÑпиÑку?

    +

    +ÐÑ–
    +Так
    +Отримувати Ð½Ð°Ð³Ð°Ð´ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ð°Ñ€Ð¾Ð»ÑŽ щоміÑÑцÑ?

    Раз на міÑÑць, ви отримуватимете повідомленнÑ, з нагадуваннÑм паролю до кожного ÑпиÑку цього Ñайту, на Ñкі Ви підпиÑані. Ви можете це вимкнути окремо Ð´Ð»Ñ ÐºÐ¾Ð¶Ð½Ð¾Ð³Ð¾ ÑпиÑку, Ñкщо виберете ÐÑ–. Якщо ви вимкнете Ð½Ð°Ð³Ð°Ð´ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ð°Ñ€Ð¾Ð»Ñ–Ð² в уÑÑ–Ñ… ÑпиÑках, на Ñкі Ви підпиÑані, Ð½Ð°Ð³Ð°Ð´ÑƒÐ²Ð°Ð½Ð½Ñ Ð²Ð°Ð¼ не надÑилатиметьÑÑ. -

    - ÐÑ–
    - Так

    - Ð’Ñтановити глобально -

    - Приховувати Ñебе в переліку кориÑтувачів ÑпиÑку розÑилки?

    +

    +ÐÑ–
    +Так

    +Ð’Ñтановити глобально +

    +Приховувати Ñебе в переліку кориÑтувачів ÑпиÑку розÑилки?

    Коли будь-хто переглÑдає перелік кориÑтувачів ÑпиÑку розÑилки, зазвичай показуєтьÑÑ Ñ– ваша адреÑа (у прихованій формі, щоб перешкодити розповÑюджувачам Ñпаму). Якщо ви не бажаєте, щоб ваша адреÑа взагалі показувалаÑÑŒ у цьому переліку, виберіть Так. -

    - ÐÑ–
    - Так -
    - Якою мовою бажаєте кориÑтуватиÑÑŒ?

    -

    - -
    - Ðа Ñкі категорії тем ви хотіли б підпиÑатиÑÑŒ?

    +

    +ÐÑ–
    +Так +
    +Якою мовою бажаєте кориÑтуватиÑÑŒ?

    +

    + +
    +Ðа Ñкі категорії тем ви хотіли б підпиÑатиÑÑŒ?

    Вибираючи одну чи більше тем, ви можете обмежувати трафік ÑпиÑку розÑилки, тобто отримувати лише чаÑтину повідомлень. Якщо Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð²Ñ–Ð´Ð¿Ð¾Ð²Ñ–Ð´Ð°Ñ” @@ -251,12 +290,11 @@

    Змінити пароль підпиÑки

    тоді його Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ð·Ð°Ð»ÐµÐ¶Ð¸Ñ‚ÑŒ від одного з наÑтупних параметрів. Якщо ви не обрали жодної теми - ви отримуватимете вÑÑ– повідомленнÑ, що надходÑть до поштового ÑпиÑку. -
    - -
    - Бажаєте отримувати Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ñ‰Ð¾ не відповідають жодному фільтру теми?

    +

    + +
    +Бажаєте отримувати Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ñ‰Ð¾ не відповідають жодному фільтру теми?

    Цей параметр викориÑтовуєтьÑÑ Ð»Ð¸ÑˆÐµ Ñкщо ви підпиÑані хоча б на одну тему (див. вище). Він визначає типову дію, @@ -268,13 +306,12 @@

    Змінити пароль підпиÑки

    Якщо вище не вибрано жодної тем інтереÑів - ви отримуватимете вÑÑ– повідомленнÑ, що надходÑть до поштового ÑпиÑку. -

    - ÐÑ–
    - Так -
    - Уникати дублікатів повідомлень?

    +

    +ÐÑ–
    +Так +
    +Уникати дублікатів повідомлень?

    Якщо Ð²Ð°Ñ Ð±ÐµÐ·Ð¿Ð¾Ñередньо вказано у To: чи Cc: заголовку повідомленнÑ, ви можете @@ -286,21 +323,17 @@

    Змінити пароль підпиÑки

    та ви вибрали Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ ÐºÐ¾Ð¿Ñ–Ð¹, тоді до кожної копії додаєтьÑÑ Ð·Ð°Ð³Ð¾Ð»Ð¾Ð²Ð¾Ðº X-Mailman-Copy: yes. -
    - ÐÑ–
    - Так

    - Ð’Ñтановити глобально -

    -
    -
    +ÐÑ–
    +Так

    +Ð’Ñтановити глобально +

    +
    +
    -

    - - - - + + +

    diff --git a/templates/uk/private.html b/templates/uk/private.html index 9871cc4a..6ebbed4e 100755 --- a/templates/uk/private.html +++ b/templates/uk/private.html @@ -1,33 +1,94 @@ - ÐÐ²Ñ‚ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ñ Ð´Ð¾Ñтупу до закритого ÑпиÑку розÑилки %(realname)s +ÐÐ²Ñ‚ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ñ Ð´Ð¾Ñтупу до закритого ÑпиÑку розÑилки %(realname)s - - -
    + + + %(message)s - - - - - - - - - - - - - - - -
    - ÐÐ²Ñ‚ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ñ Ð´Ð¾Ñтупу до закритого ÑпиÑку розÑилки %(realname)s -
    Електронна адреÑа:
    Пароль:
    -
    -

    Важливо: Вам потрібно дозволити + + + + + + + + + + + + + + + +
    +ÐÐ²Ñ‚ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ñ Ð´Ð¾Ñтупу до закритого ÑпиÑку розÑилки %(realname)s +
    Електронна адреÑа:
    Пароль:
    +
    +

    Важливо: Вам потрібно дозволити викориÑÑ‚Ð°Ð½Ð½Ñ cookies у вашому переглÑдачі, у іншому випадку ви не зможете вноÑити будь-Ñкі зміни @@ -39,21 +100,21 @@ у розділі Інші адмініÑтративні дії (Ви зможете його побачити лише піÑÐ»Ñ Ð¿Ð¾Ñ‡Ð°Ñ‚ÐºÑƒ ÑеанÑу).

    - - - - - - + + + +
    - Password Reminder -
    If you don't remember your password, enter your email address + + + + + + - - - - -
    +Password Reminder +
    If you don't remember your password, enter your email address above and click the Remind button and your password will be emailed to you.
    - +
    +

    diff --git a/templates/uk/roster.html b/templates/uk/roster.html index 20b636c3..4d5bc21a 100644 --- a/templates/uk/roster.html +++ b/templates/uk/roster.html @@ -1,53 +1,112 @@ - - - КориÑтувачі ÑпиÑку розÑилки <MM-List-Name> - - - - -

    - - - - - - - - - - - - - - - -
    - - КориÑтувачі ÑпиÑку розÑилки -
    - -

    -

    - -

    Щоб переглÑнути/змінити параметру влаÑної підпиÑки, натиÑніть - на поÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð· вашою електронною адреÑою.
    - (ДоÑтавку пошти кориÑтувачам, що вказані у дужках, заблоковано.)

    -
    -
    - - Отримувачів, що отримують окремі Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ ÑпиÑку : -
    -
    -
    - - Отримувачів, що отримують збірки ÑпиÑку : -
    -
    -

    -

    -

    -

    - - - + + +КориÑтувачі ÑпиÑку розÑилки <mm-list-name></mm-list-name> + + +

    + + + + + + + + + + + + + + + +
    + + КориÑтувачі ÑпиÑку розÑилки +
    +

    +

    +

    Щоб переглÑнути/змінити параметру влаÑної підпиÑки, натиÑніть + на поÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð· вашою електронною адреÑою.
    +(ДоÑтавку пошти кориÑтувачам, що вказані у дужках, заблоковано.)

    +
    +
    + + Отримувачів, що отримують окремі Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ ÑпиÑку : +
    +
    +
    + + Отримувачів, що отримують збірки ÑпиÑку : +
    +
    +

    +

    +

    +

    + +

    + diff --git a/templates/uk/subscribe.html b/templates/uk/subscribe.html index 90a646b9..f60dad77 100644 --- a/templates/uk/subscribe.html +++ b/templates/uk/subscribe.html @@ -1,9 +1,72 @@ - Результати підпиÑки на ÑпиÑок розÑилки <MM-List-Name> + Результати підпиÑки на ÑпиÑок розÑилки <mm-list-name></mm-list-name> -

    Результати підпиÑки на ÑпиÑок розÑилки

    - - - +

    Результати підпиÑки на ÑпиÑок розÑилки

    + + + diff --git a/templates/vi/admindbdetails.html b/templates/vi/admindbdetails.html index 6358bc16..fdc91206 100644 --- a/templates/vi/admindbdetails.html +++ b/templates/vi/admindbdetails.html @@ -1,18 +1,79 @@ -Các yêu cầu quản lý được hiển thị bằng má»™t cá»§a hai cách, trên trang tóm tắt, và trên trang chi tiết. Trang tóm tắt chứa các yêu cầu đăng ký và há»§y đăng ký bị hoãn, cùng vá»›i các bài thư được giữ lại cho bạn tán thành, đã nhóm lại theo địa chỉ thư Ä‘iện thư cá»§a ngưá»i gởi. Còn trang chi tiết chứa khung xem chi tiết hÆ¡n vá» má»—i thư đã giữ lại, gồm má»i dòng đầu cá»§a thư đó, và Ä‘oạn trích cá»§a thân thư. +Các yêu cầu quản lý được hiển thị bằng má»™t cá»§a hai cách, trên trang tóm tắt, và trên trang chi tiết. Trang tóm tắt chứa các yêu cầu đăng ký và há»§y đăng ký bị hoãn, cùng vá»›i các bài thư được giữ lại cho bạn tán thành, đã nhóm lại theo địa chỉ thư Ä‘iện thư cá»§a ngưá»i gởi. Còn trang chi tiết chứa khung xem chi tiết hÆ¡n vá» má»—i thư đã giữ lại, gồm má»i dòng đầu cá»§a thư đó, và Ä‘oạn trích cá»§a thân thư.

    Trên cả hai trang, có sẵn những hành động theo đây:

      -
    • Hoãn — Tạm hoãn quyết định đến lúc sau. Không làm gì ngay vá»›i yêu cầu quản lý bị hoãn này, nhưng đối vá»›i bài thư đã giữ lại, bạn vẫn còn có khả năng chuyển tiếp hoặc bảo tồn thư đó (xem dưới). +
    • Hoãn — Tạm hoãn quyết định đến lúc sau. Không làm gì ngay vá»›i yêu cầu quản lý bị hoãn này, nhưng đối vá»›i bài thư đã giữ lại, bạn vẫn còn có khả năng chuyển tiếp hoặc bảo tồn thư đó (xem dưới). -
    • Chấp nhận — Chấp nhận thư đó, chuyển nó tiếp tá»›i há»™p thư chung. Äối vá»›i yêu cầu tư cách thành viên, tán thành sá»± thay đổi trạng thái đăng ký đó. +
    • Chấp nhận — Chấp nhận thư đó, chuyển nó tiếp tá»›i há»™p thư chung. Äối vá»›i yêu cầu tư cách thành viên, tán thành sá»± thay đổi trạng thái đăng ký đó. -
    • Từ chối — Từ chối thư đó, gởi thư thông báo từ chối cho ngưá»i gởi, và há»§y thư gốc. Äối vá»›i yêu cầu tư cách thành viên, từ chối sá»± thay đổi trạng thái đăng ký đó. Trong cả hai trưá»ng hợp, bạn nên nhập lý do từ chối vào há»™p văn bản liên quan. - -
    • Há»§y — Há»§y thư gốc, không gởi thư thông báo từ chối. Äối vá»›i yêu cầu tư cách thành viên, tùy chá»n này đơn giản há»§y yêu cầu đó, không thông báo gì cho ngưá»i đã yêu cầu. Hành động này thưá»ng có ích để xá»­ lý thư rác đã biết. -
    +
  • Từ chối — Từ chối thư đó, gởi thư thông báo từ chối cho ngưá»i gởi, và há»§y thư gốc. Äối vá»›i yêu cầu tư cách thành viên, từ chối sá»± thay đổi trạng thái đăng ký đó. Trong cả hai trưá»ng hợp, bạn nên nhập lý do từ chối vào há»™p văn bản liên quan. +
  • Há»§y — Há»§y thư gốc, không gởi thư thông báo từ chối. Äối vá»›i yêu cầu tư cách thành viên, tùy chá»n này đơn giản há»§y yêu cầu đó, không thông báo gì cho ngưá»i đã yêu cầu. Hành động này thưá»ng có ích để xá»­ lý thư rác đã biết. +
  • Äối vá»›i thư đã giữ lại, hãy bật tùy chá»n Bảo tồn nếu bạn muốn lưu má»™t bản sao thư cho quản trị nÆ¡i Mạng xem. Có ích để xá»­ lý thư lạm dụng mà bạn muốn há»§y, nhưng cần ghi lưu để kiểm tra sau.

    Hãy bật tùy chá»n Chuyển tiếp cho, và Ä‘iá»n địa chỉ chuyển tiếp vào, nếu bạn muốn chuyển tiếp thư đó cho ngưá»i khác không phải trong há»™p thư chung. Äể hiệu chỉnh thư đã giữ lại trước khi chuyển tiếp nó tá»›i há»™p thư, bạn nên chuyển tiếp thư đó cho mình (hoặc cho ngưá»i sở hữu há»™p thư chung), và há»§y thư gốc. Như thế thì, khi thư đó xuất hiện trong há»™p Thư Äến cá»§a bạn, hãy hiệu chỉnh nó và gởi lại nó cho há»™p thư chung, gồm dòng đầu Approved: (Äã tán thành) có mật khẩu há»™p thư chung là giá trị. Quy ước mặc nhận trên Mạng trong trưá»ng hợp này là gồm chú thích trong thư đã gởi lại, giải thích bạn đã sá»­a đổi thân thư đó. @@ -25,3 +86,4 @@

    Khi đã làm xong, bạn hãy nhắp vào nút Äệ trình các thay đổi bên trên hay dưới trang. Cái nút này sẽ thá»±c hiện má»i hành động đã chá»n đối vá»›i má»i yêu cầu quản lý mà bạn đã quyết định.

    VỠtrang tóm tắt. +

    \ No newline at end of file diff --git a/templates/vi/admindbpreamble.html b/templates/vi/admindbpreamble.html index f785c91d..f7924575 100644 --- a/templates/vi/admindbpreamble.html +++ b/templates/vi/admindbpreamble.html @@ -1,6 +1,70 @@ -Trang này chứa má»™t tập hợp con cá»§a các bài thư đã gởi cho há»™p thư chung %(listname)smà Ä‘ang được giữ lại cho bạn tán thành. HIện thá»i nó hiển thị %(description)s. +Trang này chứa má»™t tập hợp con cá»§a các bài thư đã gởi cho há»™p thư chung %(listname)smà Ä‘ang được giữ lại cho bạn tán thành. HIện thá»i nó hiển thị %(description)s.

    Äối vá»›i má»—i yêu cầu quản trị, vui lòng chá»n hành động cần làm, rồi nhắp vào cái nút Äệ trình các thay đổi khi làm xong. CÅ©ng có sẵn Hướng dẫn chi tiết.

    Bạn cÅ©ng có thể xem bản tóm tắt má»i yêu cầu bị hoãn. +

    \ No newline at end of file diff --git a/templates/vi/admindbsummary.html b/templates/vi/admindbsummary.html index a3bc7327..3229469c 100644 --- a/templates/vi/admindbsummary.html +++ b/templates/vi/admindbsummary.html @@ -1,7 +1,71 @@ -Trang này chứa bản tóm tắt các yêu cầu quản trị cần thiết bạn tán thành cho hộp thư chung %(listname)s. +Trang này chứa bản tóm tắt các yêu cầu quản trị cần thiết bạn tán thành cho hộp thư chung %(listname)s. Trước tiên, bạn sẽ tìm thấy danh sách các yêu cầu đăng ký và hủy đăng ký bị hoãn, nếu có, và sau đó nằm bài thư nào được giữ lại cho bạn tán thành.

    Äối vá»›i má»—i yêu câù quản trị, vui lòng chá»n hành động cần làm, rồi nhắp vào cái nút Äệ trình các thay đổi khi làm xong. CÅ©ng có sẵn Hướng dẫn chi tiết.

    Bạn cÅ©ng có thể xem thông tin vá» má»i bài thư đã giữ lại. +

    \ No newline at end of file diff --git a/templates/vi/admlogin.html b/templates/vi/admlogin.html index ca85e2c8..44ab0f75 100755 --- a/templates/vi/admlogin.html +++ b/templates/vi/admlogin.html @@ -1,32 +1,93 @@ - %(listname)s %(who)s Xác thực - - - -
    +%(listname)s %(who)s Xác thực + + + + %(message)s - - - - - - - - - - - -
    - %(listname)s %(who)s - Xác thực -
    Mật khẩu hộp thư %(who)s:
    -
    -

    Quan trá»ng: để tiếp tục được, bạn phải đã bật tập tin nhận diện cookie trong trình duyệt, nếu không thì thay đổi quản trị sẽ không có tác động. + + + + + + + + + + + +
    +%(listname)s %(who)s + Xác thực +
    Mật khẩu hộp thư %(who)s:
    +
    +

    Quan trá»ng: để tiếp tục được, bạn phải đã bật tập tin nhận diện cookie trong trình duyệt, nếu không thì thay đổi quản trị sẽ không có tác động.

    Cookie phiên chạy được dùng trong giao diện quản trị cá»§a Mailman để tránh trưá»ng hợp bạn phải đăng nhập lại vào má»—i thao tác quản trị. Cookie này sẽ hết hạn dùng tá»± động khi bạn thoát khá»i trình duyệt, hoặc bạn có thể làm cho nó hết hạn dùng bằng cách nhắp vào liên kết Äăng xuất nằm dưới Hoạt động quản trị khác (mà bạn sẽ xem má»™t khi đăng nhập được). -

    +

    diff --git a/templates/vi/archidxentry.html b/templates/vi/archidxentry.html index f9bb57aa..5c620b20 100644 --- a/templates/vi/archidxentry.html +++ b/templates/vi/archidxentry.html @@ -1,4 +1,68 @@ -
  • %(subject)s -  -%(author)s - +
  • %(subject)s +  +%(author)s + +
  • \ No newline at end of file diff --git a/templates/vi/archidxfoot.html b/templates/vi/archidxfoot.html index b57e2695..e213a2c9 100644 --- a/templates/vi/archidxfoot.html +++ b/templates/vi/archidxfoot.html @@ -1,20 +1,84 @@ - -

    - Ngày thư cuối cùng: - %(lastdate)s
    - Lưu vào kho : %(archivedate)s -

    -

      -
    • Sắp xếp thư theo : + +

      +Ngày thư cuối cùng: +%(lastdate)s
      +Lưu vào kho : %(archivedate)s +

      +

      -

      -


      - Kho này bị Pipermail %(version)s tạo ra. - - +
    +

    +


    +Kho này bị Pipermail %(version)s tạo ra. + + +

    \ No newline at end of file diff --git a/templates/vi/archidxhead.html b/templates/vi/archidxhead.html index c0ec847a..b5d9e117 100644 --- a/templates/vi/archidxhead.html +++ b/templates/vi/archidxhead.html @@ -1,15 +1,79 @@ - - - Kho %(archive)s %(listname)s theo %(archtype)s - + + + +Kho %(archive)s %(listname)s theo %(archtype)s + %(encoding)s - - - -

    Kho %(archive)s theo %(archtype)s

    -
      -
    • Sắp xếp thư theo : + + + +

      Kho %(archive)s theo %(archtype)s

      + -

      Äầu : %(firstdate)s
      - Cuối : %(lastdate)s
      - Thư : %(size)s

      -

        +
      +

      Äầu : %(firstdate)s
      +Cuối : %(lastdate)s
      +Thư : %(size)s

      +

        +

      \ No newline at end of file diff --git a/templates/vi/archlistend.html b/templates/vi/archlistend.html index 9bc052dd..2e1191b0 100644 --- a/templates/vi/archlistend.html +++ b/templates/vi/archlistend.html @@ -1 +1,64 @@ -
    + diff --git a/templates/vi/archliststart.html b/templates/vi/archliststart.html index 9c0f40ff..c1a1a5a1 100644 --- a/templates/vi/archliststart.html +++ b/templates/vi/archliststart.html @@ -1,4 +1,68 @@ - - - - +
    KhoXem theo :Phiên bản tải vỠđược
    + + + +
    KhoXem theo :Phiên bản tải vỠđược
    \ No newline at end of file diff --git a/templates/vi/archtoc.html b/templates/vi/archtoc.html index 67fc188b..80a9a2e2 100644 --- a/templates/vi/archtoc.html +++ b/templates/vi/archtoc.html @@ -1,13 +1,77 @@ - - - Kho cá»§a %(listname)s - + + + +Kho cá»§a %(listname)s + %(meta)s - - -

    Kho cá»§a %(listname)s

    -

    + + +

    Kho cá»§a %(listname)s

    +

    Bạn có thể lấythông tin thêm vỠhộp thư chung này hoặc bạn có thể tải vỠkho thô đầy đủ (%(size)s). @@ -16,5 +80,5 @@

    Kho cá»§a %(listname)s

    %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/vi/archtocentry.html b/templates/vi/archtocentry.html index 722c84e6..9a1d5693 100644 --- a/templates/vi/archtocentry.html +++ b/templates/vi/archtocentry.html @@ -1,12 +1,74 @@ - -
    %(archivelabel)s: - [ Nhánh ] - [ Chủ đỠ] - [ Tác giả ] - [ Ngày ] -
    %(archivelabel)s: +[ Nhánh ] +[ Chủ đỠ] +[ Tác giả ] +[ Ngày ] +
    - - - - - - - - - - - - - - - - - - + + + + + +

    - -- - -
    -

      -

    - Giới thiệu vỠ- - - -
    -

    -

    Äể xem các bài thư được gởi cho há»™p thư này trước này, hãy thăm Kho. - -

    -
    - Cách sử dụng -
    + + + +<mm-list-name>Trang thông tin</mm-list-name> + + +

    + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + +
    + -- + +
    +

      +

    +Giới thiệu vỠ+ + + +
    +

    +

    Äể xem các bài thư được gởi cho há»™p thư này trước này, hãy thăm Kho. + +

    +
    +Cách sử dụng +
    Äể gởi thư cho má»i thành viên há»™p thư chung, hãy gởi thư cho địa chỉ thư - . + .

    Bạn có thể đăng ký với hộp thư chung, hoặc thay đổi sự đăng ký đã có, trong các phần bên dưới. -

    - Äăng ký vá»›i -
    -

    - Hãy đăng ký vá»›i há»™p thư chung bằng cách Ä‘iá»n vào đơn theo đây. - -

      - - - - - - - - - - - - + + + + + + - - - - - -
      Äịa chỉ thư cá»§a bạn: -  
      Há» tên bạn (tùy chá»n): 
      Bạn có thể nhập má»™t mật khẩu riêng bên dưới. Nó cung cấp chỉ ít bảo mật, nhưng mà nên ngăn cản ngưá»i khác sá»­a đổi sá»± đăng ký cá»§a bạn. Äừng nhập mật khẩu quan trá»ng nào vì thỉng thoảng nó sẽ được gởi cho bạn trong thư không mật mã. +

      +Äăng ký vá»›i +
      +

      + Hãy đăng ký vá»›i há»™p thư chung bằng cách Ä‘iá»n vào đơn theo đây. + +

        + + + + + + + + + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - - - -
        Äịa chỉ thư cá»§a bạn: + 
        Há» tên bạn (tùy chá»n): 
        Bạn có thể nhập má»™t mật khẩu riêng bên dưới. Nó cung cấp chỉ ít bảo mật, nhưng mà nên ngăn cản ngưá»i khác sá»­a đổi sá»± đăng ký cá»§a bạn. Äừng nhập mật khẩu quan trá»ng nào vì thỉng thoảng nó sẽ được gởi cho bạn trong thư không mật mã. -

        Nếu bạn chá»n không nhập mật khẩu, phần má»m này sẽ tạo ra tá»± động mật khẩu cho bạn, và nó sẽ được gởi cho bạn má»™t khi bạn đã xác nhận đăng ký. HÆ¡n nữa, bạn có thể yêu cầu nhận thư chứa mật khẩu mình, vào lúc nào bạn sá»­a đổi các tùy chá»n cá nhân. - -
        -
        Chá»n mật khẩu : 
        Nhập lại mật khẩu để xác nhân: 
        Bạn có muốn hiển thị thư bằng ngôn ngữ nào?  
        Bạn có muốn nhận bó thư không? +

        Nếu bạn chá»n không nhập mật khẩu, phần má»m này sẽ tạo ra tá»± động mật khẩu cho bạn, và nó sẽ được gởi cho bạn má»™t khi bạn đã xác nhận đăng ký. HÆ¡n nữa, bạn có thể yêu cầu nhận thư chứa mật khẩu mình, vào lúc nào bạn sá»­a đổi các tùy chá»n cá nhân. + + +
        Chá»n mật khẩu : 
        Nhập lại mật khẩu để xác nhân: 
        Bạn có muốn hiển thị thư bằng ngôn ngữ nào?  
        Bạn có muốn nhận bó thư không? Không - Có -
        -
        -
        - -
      -
      - - Thành viên -
      - - - -

      - - - -

      - - - +
    Không + Có +
    +
    +
    + + +

    + + Thành viên +
    + + + +

    + + + +

    + +

    + diff --git a/templates/vi/options.html b/templates/vi/options.html index cbcf7759..12ac5086 100644 --- a/templates/vi/options.html +++ b/templates/vi/options.html @@ -1,40 +1,100 @@ - - Cấu hình thành viên <MM-Presentable-User> cho hộp thư chung <MM-List-Name> - - - - - -
    - - Cấu hình thành viên hộp thư chung cho -
    + +Cấu hình thành viên <mm-presentable-user> cho hộp thư chung <mm-list-name> +</mm-list-name></mm-presentable-user> + + + + +
    + + Cấu hình thành viên hộp thư chung cho +

    - - - - - +
    - Trạng thái đăng ký, mật khẩu và các tùy chá»n cá»§a cho há»™p thư chung . -
    - - - - -

    -

    + + + +
    +Trạng thái đăng ký, mật khẩu và các tùy chá»n cá»§a cho há»™p thư chung . +
    + + +

    +

    - - +

    - - - +
    - - Thay đổi thông tin thành viên của bạn -
    Bạn có thể thay đổi địa chỉ đã đăng ký vá»›i há»™p thư chung này, bằng cách nhập địa chỉ thư má»›i vào trưá»ng bên dưới. Ghi chú rằng địa chỉ má»›i sẽ nhận má»™t lá thư xác nhận, và việc xác nhận này phải làm xong để kích hoạt thay đổi này. + + + - - - - - -
    + +Thay đổi thông tin thành viên của bạn +
    Bạn có thể thay đổi địa chỉ đã đăng ký vá»›i há»™p thư chung này, bằng cách nhập địa chỉ thư má»›i vào trưá»ng bên dưới. Ghi chú rằng địa chỉ má»›i sẽ nhận má»™t lá thư xác nhận, và việc xác nhận này phải làm xong để kích hoạt thay đổi này.

    Việc xác nhận quá giỠsau khoảng . @@ -44,211 +104,184 @@

    Nếu bạn muốn thay đổi thông tin thành viên mình trong má»i há»™p thư chung vá»›i đó bạn đã đăng ký trên , hãy bật tùy chá»n Äổi toàn cục. -

    - - - - - - - -
    Äịa chỉ má»›i :
    Nhập lại để xác nhận:
    -
    - - - - -
    Há» tên bạn (tùy chá»n):
    -
    -

    Äổi toàn cục

    - +

    + + + + + + + +
    Äịa chỉ má»›i :
    Nhập lại để xác nhận:
    +

    + + + + +
    Há» tên bạn (tùy chá»n):
    + +
    +

    Äổi toàn cục

    +

    - - - - - - - - + + + + %(textlink)s - diff --git a/templates/zh_CN/archtocnombox.html b/templates/zh_CN/archtocnombox.html index e7a2c4f7..e7887008 100644 --- a/templates/zh_CN/archtocnombox.html +++ b/templates/zh_CN/archtocnombox.html @@ -1,18 +1,82 @@ - - - %(listname)s 邮件归档 - + + + +%(listname)s 邮件归档 + %(meta)s - - -

    %(listname)s 邮件归档

    -

    + + +

    %(listname)s 邮件归档

    +

    您å¯ä»¥èŽ·å¾—å…³äºŽè¿™ä¸ªåˆ—è¡¨çš„æ›´å¤šä¿¡æ¯.

    %(noarchive_msg)s %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/zh_CN/article.html b/templates/zh_CN/article.html index 9b3990b6..598304bb 100644 --- a/templates/zh_CN/article.html +++ b/templates/zh_CN/article.html @@ -1,12 +1,13 @@ - - - %(title)s - - - - - + +

    %(listname)s 邮件归档

    +

    现在还没有å‘到此列表的信æ¯ï¼Œæ‰€ä»¥å½“å‰çš„列表归档为空。 您å¯ä»¥èŽ·å¾—å…³äºŽæ­¤åˆ—è¡¨çš„æ›´å¤šä¿¡æ¯.

    - - + + diff --git a/templates/zh_CN/headfoot.html b/templates/zh_CN/headfoot.html index 9416d4a0..35ac62ff 100644 --- a/templates/zh_CN/headfoot.html +++ b/templates/zh_CN/headfoot.html @@ -1,8 +1,71 @@ -此文本å¯ä»¥åŒ…括 +此文本å¯ä»¥åŒ…括 Python æ ¼å¼å­—符串,它们将根æ®åˆ—表属性æ¥è¢«è§£é‡Šã€‚å¯ç”¨çš„æ›¿æ¢æœ‰ï¼š
      -
    • real_name - 列表的确切åå­—; 通常列表åå­—è¦å¤§å†™ã€‚ +
    • real_name - 列表的确切åå­—; 通常列表åå­—è¦å¤§å†™ã€‚
    • list_name - 在URL中用æ¥ç¡®è®¤åˆ—表的å字,大å°å†™ç›¸å…³ã€‚
    • host_name - 列表æœåŠ¡å™¨çš„å…¨é™å®šåŸŸå。 @@ -13,4 +76,4 @@
    • description - 邮件列表的简è¦è¯´æ˜Žã€‚
    • info - 邮件列表的详细说明。
    • cgiext - The extension added to CGI scripts. -
    + diff --git a/templates/zh_CN/listinfo.html b/templates/zh_CN/listinfo.html index 7ddb2f68..7373f247 100644 --- a/templates/zh_CN/listinfo.html +++ b/templates/zh_CN/listinfo.html @@ -1,140 +1,201 @@ - - - <MM-List-Name> ä¿¡æ¯é¡µ - - - -

    -

    - Hủy đăng ký ra hộp thư chung - Các sự đăng ký của bạn -
    + + + + - + +
    +

    +Hủy đăng ký ra hộp thư chung +Các sự đăng ký của bạn +
    Hãy bật há»™p chá»n xác nhận và bấm cái nút này để há»§y đăng ký ra há»™p thư chung này. Cảnh báo : Hành động này có tác động ngay!

    -

    +

    Bạn có thể xem danh sách các hộp thư chung tại trong đó bạn là thành viên. Hãy sư dụng khả năng này nếu bạn muốn làm cùng thay đổi trong các sự đăng ký khác này.

    -

    -
    - - - - - +
    - Mật khẩu của bạn -
    - -
    -

    Bạn đã quên mật khẩu mình không?

    -
    + + + - -
    +Mật khẩu của bạn +
    + +
    +

    Bạn đã quên mật khẩu mình không?

    +
    Hãy nhắp vào cái nút này để nhận thư chứa mật khẩu mình tại địa chỉ thành viên. -

    -

    - -
    -
    - -
    -

    Thay đổi mật khẩu

    - - - - - - - - -
    Mật khẩu mới :
    Nhập lại để xác nhận:
    - - -

    Äổi toàn cục -
    -
    - +

    +

    + +
    +

    + +
    +

    Thay đổi mật khẩu

    + + + + + + + + +
    Mật khẩu mới :
    Nhập lại để xác nhận:
    + +

    Äổi toàn cục +
    +

    - - +
    - Tùy chá»n đăng ký cá»§a bạn -
    +
    +Tùy chá»n đăng ký cá»§a bạn +
    -

    Giá trị hiện thá»i hoạt động có dấu chá»n ở cạnh. -

    Ghi chú rằng má»™t số tùy chá»n có khả năng Äặt toàn cục. Việc bật khả năng này sẽ áp dụng thay đổi đó vào má»i há»™p thư chung trong đó bạn là thành viên trên máy . Hãy nhắp vào Liệt kê các sá»± đăng ký mình khác bên trên để xem các há»™p thư khác vá»›i đó bạn đã đăng ký.

    - -
    - - Phát thư

    + + - +

    - - - + +

    - - - - - - + + - - + - - - - + + - - + - - + - - - +

    +
    + +Phát thư

    Äặt tùy chá»n này là Bật để nhận các thư được gởi cho há»™p thư chung này. Còn đặt nó là Tắt nếu bạn muốn tiếp tục đăng ký, nhưng tạm thá»i không muốn nhận thư (v.d. khi nghỉ). Nếu bạn có phải tắt phát thư, đừng quên bật lại khả năng đó khi bạn trở vá» : nó sẽ không được bật lại tá»± động. -

    - Bật
    - Tắt

    - Äặt toàn cục -

    +Bật
    +Tắt

    +Äặt toàn cục +

    - Äặt chế độ nhận bó thư

    +

    +Äặt chế độ nhận bó thư

    Nếu bạn bật chế độ nhận bó thư, bạn sẽ nhận nhiá»u bài thư trong má»™t lá thư (thưá»ng nhận má»™t thư trên má»—i ngày, nhưng có thể nhận thư nhiá»u hÆ¡n từ há»™p thư chung rất bận), thay vào nhận má»—i thư riêng má»™t khi nó được gởi. Nếu bạn tắt chế độ nhận bó thư, có lẽ bạn sẽ nhận má»™t bó thư cuối cùng trước khi bắt đầu nhận lại bài thư riêng. -

    - Tắt
    - Bật -
    - Nhận bó thư MIME hay thô?

    +

    +Tắt
    +Bật +
    +Nhận bó thư MIME hay thô?

    Trình thư cá»§a bạn có lẽ không há»— trợ bó thư MIME. Bó thư MIME thưá»ng dụng, nhưng nếu bạn gặp khó khăn Ä‘á»c chúng, hãy chá»n văn bản thô. -

    - MIME
    - Thô

    - Äặt toàn cục -

    +MIME
    +Thô

    +Äặt toàn cục +

    - Nhận bài thư mình đã gởi cho hộp thư không?

    +

    +Nhận bài thư mình đã gởi cho hộp thư không?

    Thưá»ng, bạn sẽ nhận má»—i thư bạn đã gởi cho há»™p thư. Nếu bạn không muốn nhận thư này, đặt tùy chá»n này là Không. -

    - Không
    - Có -
    - Nhận thư thừa nhận khi bạn gởi thư cho hộp thư không?

    -

    - Không
    - Có -
    - Nhận thư nhắc nhở mật khẩu cho hộp thư này không?

    +

    +Không
    +Có +
    +Nhận thư thừa nhận khi bạn gởi thư cho hộp thư không?

    +

    +Không
    +Có +
    +Nhận thư nhắc nhở mật khẩu cho hộp thư này không?

    Hàng tháng bạn sẽ nhận má»™t lá thư chứa lá»i nhắc nhở mật khẩu cho má»—i há»™p thư trên máy này, vá»›i đó bạn đã đăng ký. Bạn có thể tắt khả năng này cho từng há»™p thư bằng cách chá»n Không cho tùy chá»n này. Nếu bạn tắt lá»i nhắc nhở cho tất cả các há»™p thư vá»›i đó bạn đã đăng ký, bạn sẽ không nhận thư nhắc nhở nào. -

    - Không
    - Có

    - Äặt toàn cục -

    - Ẩn mặt trong danh sách thành viên không?

    +

    +Không
    +Có

    +Äặt toàn cục +

    +Ẩn mặt trong danh sách thành viên không?

    Khi ngưá»i nào xem danh sách các thành viên cùa há»™p thư này, địa chỉ thư cá»§a bạn thưá»ng được hiển thị (cách mỠđể tránh chương trình bắt địa chỉ để gởi thư rác). Nếu bạn không muốn địa chỉ thư cá»§a mình hiển thị trong bản liệt kê thành viên này bằng cách nào cả, hãy chá»n Có cho tùy chá»n này. -

    - Không
    - Có -
    - Bạn có muốn sử dụng ngôn ngữ nào?

    -

    - -
    - Bạn có muốn đăng ký với những phân loại chủ đỠnào?

    +

    +Không
    +Có +
    +Bạn có muốn sử dụng ngôn ngữ nào?

    +

    + +
    +Bạn có muốn đăng ký với những phân loại chủ đỠnào?

    Bằng cách chá»n má»™t hay nhiá»u chá»§ Ä‘á», bạn có thể lá»c viá»…n thông trong há»™p thư chung này, để nhận chỉ má»™t tập hợp con cá»§a các thư đã gởi cho nó. Thư nào khá»›p vá»›i má»™t cá»§a những chá»§ đỠbị bạn chá»n, sẽ được gởi cho bạn; thư không khá»›p sẽ không.

    Cách xá»­ lý thư nào không khá»›p vá»›i chá»§ đỠnào phụ thuá»™c vào giá trị cá»§a tùy chá»n bên dưới đây. Nếu bạn không chá»n chá»§ đỠriêng, bạn sẽ nhận má»i thư được gởi cho há»™p thư chung này. -

    - -
    - Bạn có muốn nhận thư nào không khá»›p vá»›i bá»™ lá»c chá»§ đỠnào không?

    +

    + +
    +Bạn có muốn nhận thư nào không khá»›p vá»›i bá»™ lá»c chá»§ đỠnào không?

    Tùy chá»n này chỉ có tác động nếu bạn đã đăng ký vá»›i ít nhất má»™t chá»§ đỠbên trên. Nó đặt quy tắc phát thư mặc định cho thư nào không khá»›p vá»›i bá»™ lá»c chá»§ đỠnào. Việc chá»n Không không phát cho bạn thư nào không khá»›p bá»™ lá»c chá»§ đỠnào, còn việc chá»n Có có phải.

    Nếu bạn không chá»n chá»§ đỠriêng bên trên, bạn sẽ nhận má»i thư được gởi cho há»™p thư chung này. -

    - Không
    - Có -
    - Tránh nhận bản sao của thư không?

    +

    +Không
    +Có +
    +Tránh nhận bản sao của thư không?

    Khi bạn được ghi dứt khoát trong dòng đầu To: (Cho) hay Cc: (Chép cho) cá»§a thư từ há»™p thư chung, bạn có thể chá»n không nhận bản sao thêm từ há»™p đó. Hãy chá»n Có để tránh nhận bản sao từ há»™p thư, còn chá»n Không để nhận.

    Nếu há»™p thư chung này đã bật khả năng tạo thư cá nhân, và bạn chá»n nhận bản sao, má»—i bản sao sẽ có dòng đầu X-Mailman-Copy: yes (sao chép? Có) được thêm vào nó. -

    - Không
    - Có

    - Äặt toàn cục -

    -
    -
    +Không
    +Có

    +Äặt toàn cục +

    +
    +
    -

    - - - - + + +

    diff --git a/templates/vi/private.html b/templates/vi/private.html index a6998e04..0eef5194 100755 --- a/templates/vi/private.html +++ b/templates/vi/private.html @@ -1,51 +1,112 @@ - Xác thực kho riêng của %(realname)s - - - -
    +Xác thực kho riêng của %(realname)s + + + + %(message)s - - - - - - - - - - - - - - - -
    - Xác thực kho riêng của %(realname)s -
    Äịa chỉ thư :
    Mật khẩu :
    -
    -

    Quan trá»ng: để tiếp tục được, bạn phải đã bật tập tin nhận diện cookie trong trình duyệt, nếu không thì bạn sẽ phải xác thá»±c lại cho má»—i thao tác. + + + + + + + + + + + + + + + +
    +Xác thực kho riêng của %(realname)s +
    Äịa chỉ thư :
    Mật khẩu :
    +
    +

    Quan trá»ng: để tiếp tục được, bạn phải đã bật tập tin nhận diện cookie trong trình duyệt, nếu không thì bạn sẽ phải xác thá»±c lại cho má»—i thao tác.

    Cookie phiên chạy được dùng trong giao diện kho riêng cá»§a Mailman để tránh trưá»ng hợp bạn phải xác thá»±c lại vào má»—i thao tác. Cookie này sẽ hết hạn dùng tá»± động khi bạn thoát khá»i trình duyệt, hoặc bạn có thể làm cho nó hết hạn dùng bằng cách thăm trang tùy chá»n thành viên cá»§a mình và nhắp vào cái nút Äăng xuất.

    - - - - - - + + + +
    - Password Reminder -
    If you don't remember your password, enter your email address + + + + + + - - - - -
    +Password Reminder +
    If you don't remember your password, enter your email address above and click the Remind button and your password will be emailed to you.
    - +
    +

    diff --git a/templates/vi/roster.html b/templates/vi/roster.html index f7cb6600..eeacc833 100644 --- a/templates/vi/roster.html +++ b/templates/vi/roster.html @@ -1,50 +1,109 @@ - - - <MM-List-Name> Ngưá»i đăng ký - - - - -

    - - - - - - - - - - - - - - - -
    - Ngưá»i đã đăng ký vá»›i - -
    - -

    -

    - -

    Hãy nhắp vào địa chỉ mình để thăm trang các tùy chá»n đăng ký riêng cá»§a mình.
    (Mục nhập nào nằm trong dấu ngoặc không hiện thá»i nhận thư từ há»™p thư.)

    -
    -
    - - Thành viên không nhận bó thư của : -
    -
    -
    - Thành viên nhận bó thư của : -
    -
    -

    -

    -

    -

    - - - + + +<mm-list-name> Ngưá»i đăng ký</mm-list-name> + + +

    + + + + + + + + + + + + + + + +
    +Ngưá»i đã đăng ký vá»›i + +
    +

    +

    +

    Hãy nhắp vào địa chỉ mình để thăm trang các tùy chá»n đăng ký riêng cá»§a mình.
    (Mục nhập nào nằm trong dấu ngoặc không hiện thá»i nhận thư từ há»™p thư.)

    +
    +
    + + Thành viên không nhận bó thư của : +
    +
    +
    + Thành viên nhận bó thư của : +
    +
    +

    +

    +

    +

    + +

    + diff --git a/templates/vi/subscribe.html b/templates/vi/subscribe.html index cb50c27c..c5f8be1b 100644 --- a/templates/vi/subscribe.html +++ b/templates/vi/subscribe.html @@ -1,9 +1,72 @@ -<MM-List-Name>Kết quả đăng ký +<mm-list-name>Kết quả đăng ký</mm-list-name> -

    Kết quả đăng ký

    - - - +

    Kết quả đăng ký

    + + + diff --git a/templates/zh_CN/admindbdetails.html b/templates/zh_CN/admindbdetails.html index db35451b..557eed08 100644 --- a/templates/zh_CN/admindbdetails.html +++ b/templates/zh_CN/admindbdetails.html @@ -1,4 +1,67 @@ -管ç†ä»»åŠ¡æœ‰æ‘˜è¦é¡µé¢å’Œè¯¦ç»†èµ„料页 +管ç†ä»»åŠ¡æœ‰æ‘˜è¦é¡µé¢å’Œè¯¦ç»†èµ„料页 é¢ä¸¤ç§æ˜¾ç¤ºæ–¹å¼ã€‚摘è¦é¡µé¢åŒ…括了按å‘é€äººé‚®ä»¶åœ°å€åˆ†ç»„的待决的订阅和退订 请求,以åŠè¢«æš‚存以待您审核的信件。详细资料页é¢åˆ™åŒ…æ‹¬æ¯æ¡æ¶ˆæ¯çš„æ¶ˆæ¯å¤´ ä¿¡æ¯å’Œæ¶ˆæ¯ä½“摘è¦åœ¨å†…的更详细的内容。 @@ -17,8 +80,7 @@
  • Discard -- 丢弃原始信æ¯ï¼Œä¸å‘逿‹’ç»é€šçŸ¥ã€‚对于æˆå‘˜èµ„格申请, ä¸é€šçŸ¥ç”³è¯·äººï¼Œå¹¶ä¸¢å¼ƒè¯¥è¯·æ±‚。该动作往往是针对已知的垃圾邮件。 - - +
  • 对于暂存的信æ¯ï¼Œå¦‚果你希望给系统管ç†å‘˜ä¸€ä»½æ‹·è´ï¼Œéœ€æ‰“å¼€Preserve 选项。 对于您想丢弃但是需è¦ä¿å­˜è®°å½•ä»¥åˆ©äºŽæ—¥åŽæ£€æŸ¥çš„辱骂类信æ¯è¿™ç§æ–¹æ³• 是有效的, @@ -44,3 +106,4 @@ 该按钮å¯ä»¥æäº¤æ‚¨å¯¹æ‰€æœ‰ç®¡ç†è¯·æ±‚所作出的修改。

    返回摘è¦é¡µã€‚ +

    \ No newline at end of file diff --git a/templates/zh_CN/admindbpreamble.html b/templates/zh_CN/admindbpreamble.html index e2c0f0ba..2582112a 100644 --- a/templates/zh_CN/admindbpreamble.html +++ b/templates/zh_CN/admindbpreamble.html @@ -1,7 +1,71 @@ -该页é¢åŒ…括部分 %(listname)s 邮件列表中等待你审查的消æ¯ã€‚ç›® +该页é¢åŒ…括部分 %(listname)s 邮件列表中等待你审查的消æ¯ã€‚ç›® 剿˜¾ç¤º %(description)s

    对于æ¯ä¸ªç®¡ç†è¯·æ±‚,请选择相应的æ“作,处ç†ç»“æŸåŽç‚¹å‡»æäº¤æ‰€æœ‰æ•°æ®ã€‚ 更详细的说明å¯ä»¥åœ¨æ­¤å¤„获得。

    åŒæ—¶ï¼Œä½ ä¹Ÿå¯ä»¥æµè§ˆæ‰€æœ‰å¾…审查的请求的摘è¦ä¿¡æ¯ +

    \ No newline at end of file diff --git a/templates/zh_CN/admindbsummary.html b/templates/zh_CN/admindbsummary.html index 12631d33..0b34c103 100644 --- a/templates/zh_CN/admindbsummary.html +++ b/templates/zh_CN/admindbsummary.html @@ -1,4 +1,67 @@ -此页é¢åŒ…å«%(listname)s>邮件列表 +此页é¢åŒ…å«%(listname)s>邮件列表 中待你审批管ç†è¯·æ±‚的摘è¦ã€‚é¦–å…ˆï¼Œæ‚¨éœ€è¦æ‰¾å‡ºæ‰€æœ‰å¾…批准的订阅与退订申请 ,接ç€å¤„ç†è¢«æš‚存以待审核的消æ¯ã€‚ @@ -6,3 +69,4 @@ 按钮。更详细的说明å¯ä»¥åœ¨æ­¤å¤„获得。

    åŒæ—¶ï¼Œæ‚¨ä¹Ÿå¯ä»¥æµè§ˆæ‰€æœ‰è¢«æš‚存消æ¯çš„详细内容。 +

    \ No newline at end of file diff --git a/templates/zh_CN/admlogin.html b/templates/zh_CN/admlogin.html index 7b6dd4be..fa29559b 100755 --- a/templates/zh_CN/admlogin.html +++ b/templates/zh_CN/admlogin.html @@ -1,36 +1,97 @@ - %(listname)s %(who)s Authentication +%(listname)s %(who)s Authentication - + -
    + %(message)s - - - - - - - - - - - -
    - %(listname)s %(who)s - è®¤è¯ -
    显示 %(who)s密ç :
    -
    -

    é‡è¦: 从此时起,您的æµè§ˆå™¨å¿…é¡»å¼€å¯ + + + + + + + + + + + +
    +%(listname)s %(who)s + è®¤è¯ +
    显示 %(who)s密ç :
    +
    +

    é‡è¦: 从此时起,您的æµè§ˆå™¨å¿…é¡»å¼€å¯ cookie,å¦åˆ™æ‚¨æ‰€åšçš„æ‰€æœ‰ä¿®æ”¹å‡ä¸ä¼šç”Ÿæ•ˆã€‚

    在Mainman的管ç†ç•Œé¢ä¸­ä½¿ç”¨äº†Session cookie, è¿™æ ·æ‚¨å°±æ— éœ€å¯¹æ¯ ä¸€ä¸ªç®¡ç†æ“作å‡è¿›è¡Œé‡æ–°è®¤è¯ã€‚您关闭æµè§ˆå™¨æ—¶ï¼Œcookie自动失效;您 也å¯ä»¥é€šè¿‡ç‚¹å‡»å…¶ä»–ç®¡ç†æ´»åŠ¨ä¸‹ï¼ˆåœ¨æ‚¨æˆåŠŸç™»å½•åŽå³å¯çœ‹åˆ°è¯¥ 项)的登出链接æ¥ä½¿cookie失效。 -

    +

    diff --git a/templates/zh_CN/archidxentry.html b/templates/zh_CN/archidxentry.html index f9bb57aa..5c620b20 100644 --- a/templates/zh_CN/archidxentry.html +++ b/templates/zh_CN/archidxentry.html @@ -1,4 +1,68 @@ -
  • %(subject)s -  -%(author)s - +
  • %(subject)s +  +%(author)s + +
  • \ No newline at end of file diff --git a/templates/zh_CN/archidxfoot.html b/templates/zh_CN/archidxfoot.html index a5f51edf..fb12d1c6 100644 --- a/templates/zh_CN/archidxfoot.html +++ b/templates/zh_CN/archidxfoot.html @@ -1,20 +1,84 @@ - -

    - 上一æ¡ä¿¡æ¯çš„æ—¥æœŸ: - %(lastdate)s
    - 归档日期: %(archivedate)s -

    -

      -
    • ä¿¡æ¯æŽ’åºæ–¹å¼: + +

      +上一æ¡ä¿¡æ¯çš„æ—¥æœŸ: +%(lastdate)s
      +归档日期: %(archivedate)s +

      +

      -

      -


      - 此归档由 Pipermail %(version)s 生æˆã€‚ - - +
    +

    +


    +此归档由 Pipermail %(version)s 生æˆã€‚ + + +

    \ No newline at end of file diff --git a/templates/zh_CN/archidxhead.html b/templates/zh_CN/archidxhead.html index fe0fa493..6b167d12 100644 --- a/templates/zh_CN/archidxhead.html +++ b/templates/zh_CN/archidxhead.html @@ -1,15 +1,79 @@ - - - %(listname)s %(archive)s 按 %(archtype)s 归档 - + + + +%(listname)s %(archive)s 按 %(archtype)s 归档 + %(encoding)s - - - -

    %(archive)s 按 %(archtype)s 归档

    -
      -
    • ä¿¡æ¯æŒ‰ä¸‹åˆ—æ–¹å¼æŽ’åº: + + + +

      %(archive)s 按 %(archtype)s 归档

      + -

      开始: %(firstdate)s
      - 结æŸ: %(lastdate)s
      - ä¿¡æ¯: %(size)s

      -

        +
      +

      开始: %(firstdate)s
      +结æŸ: %(lastdate)s
      +ä¿¡æ¯: %(size)s

      +

        +

      \ No newline at end of file diff --git a/templates/zh_CN/archlistend.html b/templates/zh_CN/archlistend.html index 9bc052dd..2e1191b0 100644 --- a/templates/zh_CN/archlistend.html +++ b/templates/zh_CN/archlistend.html @@ -1 +1,64 @@ -
    + diff --git a/templates/zh_CN/archliststart.html b/templates/zh_CN/archliststart.html index e93c4e4e..6ac487cc 100644 --- a/templates/zh_CN/archliststart.html +++ b/templates/zh_CN/archliststart.html @@ -1,4 +1,68 @@ - - - - +
    存档查看方å¼å¯ä¾›ä¸‹è½½çš„版本
    + + + +
    存档查看方å¼å¯ä¾›ä¸‹è½½çš„版本
    \ No newline at end of file diff --git a/templates/zh_CN/archtoc.html b/templates/zh_CN/archtoc.html index 77e8e90c..a34a6432 100644 --- a/templates/zh_CN/archtoc.html +++ b/templates/zh_CN/archtoc.html @@ -1,13 +1,77 @@ - - - %(listname)s 邮件归档 - + + + + %(listname)s 邮件归档 + %(meta)s - - -

    %(listname)s 邮件归档

    -

    + + +

    %(listname)s 邮件归档

    +

    ä½ å¯ä»¥èŽ·å¾—å…³äºŽè¿™ä¸ªåˆ—è¡¨çš„æ›´å¤šä¿¡æ¯ æˆ–è€…ä¸‹è½½å®Œæ•´çš„åŽŸå§‹å½’æ¡£æ–‡ä»¶ (%(size)s). @@ -16,5 +80,5 @@

    %(listname)s 邮件归档

    %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/zh_CN/archtocentry.html b/templates/zh_CN/archtocentry.html index 4c482b22..a19279c2 100644 --- a/templates/zh_CN/archtocentry.html +++ b/templates/zh_CN/archtocentry.html @@ -1,12 +1,74 @@ - -
    %(archivelabel)s: - [ 线索 ] - [ 主题 ] - [ 作者 ] - [ 日期 ] -
    %(archivelabel)s: +[ 线索 ] +[ 主题 ] +[ 作者 ] +[ 日期 ] +
    - - - - - - - - - - - - - - - - - - + + + + + +

    - -- - -
    -

      -

    - 关于 邮件列表 - - - -
    -

    -

    è¦æŸ¥çœ‹æœ¬åˆ—表收è—的旧信, - 请访问 - 的邮件归档. - -

    -
    - 如何使用 邮件列表 -
    + + +<mm-list-name> ä¿¡æ¯é¡µ</mm-list-name> + + +

    + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + +
    + -- + +
    +

      +

    +关于 邮件列表 + + + +
    +

    +

    è¦æŸ¥çœ‹æœ¬åˆ—表收è—的旧信, + 请访问 + 的邮件归档. + +

    +
    +如何使用 邮件列表 +
    è¦åŒæ—¶ç»™æ‰€æœ‰çš„æˆå‘˜å‘ä¿¡,å¯ä»¥ç›´æŽ¥å‘信到 - . + .

    在下é¢çš„å°èЂ䏭,您å¯ä»¥è®¢é˜…本列表的信件,æˆ–è€…æ”¹å˜æ‚¨çš„已有订阅。 -

    - 如何订阅 邮件列表 -
    -

    - è¦è®¢é˜… 邮件列表,请填写如下资料: - -

      - - - - - - - - - - - - + + + + + + - - - - - -
      您的email地å€ï¼š -  
      您的姓å(å¯é€‰): 
      请在下é¢è¾“入您的å£ä»¤ã€‚ +

      +如何订阅 邮件列表 +
      +

      + è¦è®¢é˜… 邮件列表,请填写如下资料: + +

        + + + + + + + + + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - - - -
        您的email地å€ï¼š + 
        您的姓å(å¯é€‰): 
        请在下é¢è¾“入您的å£ä»¤ã€‚ 这仅æä¾›é€‚åº¦çš„å®‰å…¨ï¼Œä½†è¶³ä»¥ä¿æŠ¤æ‚¨ä¸å—ä»–äººçš„å¹²æ‰°ã€‚ä½†è¯·æ‚¨ä¸ è¦ä½¿ç”¨é‡è¦çš„å£ä»¤ã€‚因为我们å¯èƒ½å°†æ­¤å£ä»¤ä»¥æ˜Žæ–‡å½¢å¼å‘给您。 -

        如果您ä¸å¡«å†™å¯†ç ,我们将会自动给您生æˆä¸€ä¸ªã€‚当您确认订阅åŽï¼Œ +

        如果您ä¸å¡«å†™å¯†ç ,我们将会自动给您生æˆä¸€ä¸ªã€‚当您确认订阅åŽï¼Œ 会将它å‘é€åˆ°æ‚¨çš„邮箱。当您编辑个人选项时,总是å¯ä»¥éšæ—¶è¦æ±‚å°† 您的å£ä»¤å‘é€åˆ°æ‚¨çš„邮箱。 - -
        -
        输入密ç : 
        å†è¾“入一次: 
        您希望以什么语言显示您的消æ¯?  
        您希望æ¯å¤©æ”¶åˆ°ä¿¡ä»¶æ‘˜è¦å—?? + + +
        输入密ç : 
        å†è¾“入一次: 
        您希望以什么语言显示您的消�  
        您希望æ¯å¤©æ”¶åˆ°ä¿¡ä»¶æ‘˜è¦å—?? å¦ - 是 -
        -
        -
        - -
      -
      - - 订阅者 -
      - - - -

      - - - -

      - - - +
    å¦ + 是 +
    +
    +
    + + +

    + + 订阅者 +
    + + + +

    + + + +

    + +

    + diff --git a/templates/zh_CN/options.html b/templates/zh_CN/options.html index 2da94a5b..acfe918b 100644 --- a/templates/zh_CN/options.html +++ b/templates/zh_CN/options.html @@ -1,42 +1,102 @@ - - <MM-List-Name> çš„ <MM-Presentable-User> æˆå‘˜é…ç½® - - - - - -
    - - çš„ - 邮件列表æˆå‘˜é…ç½® -
    + + <mm-list-name> çš„ <mm-presentable-user> æˆå‘˜é…ç½® + </mm-presentable-user></mm-list-name> + + + + +
    + +çš„ + 邮件列表æˆå‘˜é…ç½® +

    - - - - - +
    - 邮件列表中 - 的订阅状æ€ã€å£ä»¤å’Œé€‰é¡¹ -
    - - - - -

    -

    + + + +
    + 邮件列表中 + 的订阅状æ€ã€å£ä»¤å’Œé€‰é¡¹ +
    + + +

    +

    - - +

    - - - + +
    - - 修改您的 æˆå‘˜ä¿¡æ¯ -
    通过在下é¢è¾“入新的邮件地å€ï¼Œæ‚¨å¯ä»¥ä¿®æ”¹æ‚¨åœ¨æ­¤é‚®ä»¶åˆ— + + + - - - - -
    + +修改您的 æˆå‘˜ä¿¡æ¯ +
    通过在下é¢è¾“入新的邮件地å€ï¼Œæ‚¨å¯ä»¥ä¿®æ”¹æ‚¨åœ¨æ­¤é‚®ä»¶åˆ— 表的订阅地å€ã€‚请注æ„,一å°ç¡®è®¤å‡½å°†ä¼šå‘往您的新地å€ï¼Œè€Œä¸”ç³»ç»Ÿåªæœ‰åœ¨ 收到您的确认回å¤ä¹‹åŽæ‰ä¼šä¿®æ”¹å®žé™…é…置。 @@ -48,191 +108,170 @@

    如果您想对在上订阅的所有列表中的æˆå‘˜ä¿¡æ¯ä¿®æ”¹,选中 全局修改 å¤é€‰æ¡† -

    - - + -
    新地å€: +

    + + - - - - - -
    新地å€:
    确认地å€: -
    -
    - - + + + + +
    您的姓å: +
    确认地å€: +
    +
    + + - - -
    您的姓å: (å¯é€‰):
    -
    -

    全局修改

    - +
    + +

    +

    全局修改

    +

    - - - - - - +

     

    ' elif os.path.exists(txtfile): file = txtfile url = arch + '.txt' - templ = '' + templ = '' else: # neither found? file = None @@ -901,7 +878,7 @@ def processListArch(self): #if the working file is still here, the archiver may have # crashed during archiving. Save it, log an error, and move on. try: - wf = open(wname) + wf = open(wname, 'r') syslog('error', 'Archive working file %s present. ' 'Check %s for possibly unarchived msgs', @@ -921,131 +898,437 @@ def processListArch(self): except IOError: pass os.rename(name,wname) - archfile = open(wname) + archfile = open(wname, 'r') self.processUnixMailbox(archfile) archfile.close() os.unlink(wname) self.DropArchLock() - def processUnixMailbox(self, archfile): - """Process a Unix mailbox file.""" - from email import message_from_file - from mailbox import mbox - - # If archfile is a file object, we need to read it directly - if hasattr(archfile, 'read'): - # Read the entire file content - content = archfile.read() - # Create a temporary file to store the content - import tempfile - with tempfile.NamedTemporaryFile(mode='w+', encoding='utf-8', delete=False) as tmp: - if isinstance(content, bytes): - content = content.decode('utf-8', errors='replace') - tmp.write(content) - tmp_path = tmp.name - - try: - # Process the temporary file - mbox = mbox(tmp_path) - for key in mbox.keys(): - msg = message_from_file(mbox.get_file(key)) - self.add_article(msg) - finally: - # Clean up the temporary file - os.unlink(tmp_path) - else: - # If it's a path, use it directly - mbox = mbox(archfile) - for key in mbox.keys(): - msg = message_from_file(mbox.get_file(key)) - self.add_article(msg) - - def format_article(self, article): - """Format an article for HTML display.""" - # Get the message body - body = article.get_body() - if body is None: - return article - - # Convert body to lines - if isinstance(body, str): - lines = body.splitlines() + def get_filename(self, article): + return '%06i.html' % (article.sequence,) + + def get_archives(self, article): + """Return a list of indexes where the article should be filed. + A string can be returned if the list only contains one entry, + and the empty list is legal.""" + res = self.dateToVolName(float(article.date)) + self.message(C_("figuring article archives\n")) + self.message(res + "\n") + return res + + def volNameToDesc(self, volname): + volname = volname.strip() + # Don't make these module global constants since we have to runtime + # translate them anyway. + monthdict = [ + '', + _('January'), _('February'), _('March'), _('April'), + _('May'), _('June'), _('July'), _('August'), + _('September'), _('October'), _('November'), _('December') + ] + for each in list(self._volre.keys()): + match = re.match(self._volre[each], volname) + # Let ValueErrors percolate up + if match: + year = int(match.group('year')) + if each == 'quarter': + d =["", _("First"), _("Second"), _("Third"), _("Fourth") ] + ord = d[int(match.group('quarter'))] + return _("%(ord)s quarter %(year)i") + elif each == 'month': + monthstr = match.group('month').lower() + for i in range(1, 13): + monthname = time.strftime("%B", (1999,i,1,0,0,0,0,1,0)) + if monthstr.lower() == monthname.lower(): + month = monthdict[i] + return _("%(month)s %(year)i") + raise ValueError("%s is not a month!" % monthstr) + elif each == 'week': + month = monthdict[int(match.group("month"))] + day = int(match.group("day")) + return _("The Week Of Monday %(day)i %(month)s %(year)i") + elif each == 'day': + month = monthdict[int(match.group("month"))] + day = int(match.group("day")) + return _("%(day)i %(month)s %(year)i") + else: + return match.group('year') + raise ValueError("%s is not a valid volname" % volname) + +# The following two methods should be inverses of each other. -ddm + + def dateToVolName(self,date): + datetuple=time.localtime(date) + if self.ARCHIVE_PERIOD=='year': + return time.strftime("%Y",datetuple) + elif self.ARCHIVE_PERIOD=='quarter': + if datetuple[1] in [1,2,3]: + return time.strftime("%Yq1",datetuple) + elif datetuple[1] in [4,5,6]: + return time.strftime("%Yq2",datetuple) + elif datetuple[1] in [7,8,9]: + return time.strftime("%Yq3",datetuple) + else: + return time.strftime("%Yq4",datetuple) + elif self.ARCHIVE_PERIOD == 'day': + return time.strftime("%Y%m%d", datetuple) + elif self.ARCHIVE_PERIOD == 'week': + # Reconstruct "seconds since epoch", and subtract weekday + # multiplied by the number of seconds in a day. + monday = time.mktime(datetuple) - datetuple[6] * 24 * 60 * 60 + # Build a new datetuple from this "seconds since epoch" value + datetuple = time.localtime(monday) + return time.strftime("Week-of-Mon-%Y%m%d", datetuple) + # month. -ddm else: - lines = [line.decode('utf-8', 'replace') for line in body.splitlines()] - - # Handle HTML content - if article.ctype == 'text/html': - article.html_body = lines + return time.strftime("%Y-%B",datetuple) + + + def volNameToDate(self, volname): + volname = volname.strip() + for each in list(self._volre.keys()): + match = re.match(self._volre[each],volname) + if match: + year = int(match.group('year')) + month = 1 + day = 1 + if each == 'quarter': + q = int(match.group('quarter')) + month = (q * 3) - 2 + elif each == 'month': + monthstr = match.group('month').lower() + m = [] + for i in range(1,13): + m.append( + time.strftime("%B",(1999,i,1,0,0,0,0,1,0)).lower()) + try: + month = m.index(monthstr) + 1 + except ValueError: + pass + elif each == 'week' or each == 'day': + month = int(match.group("month")) + day = int(match.group("day")) + try: + return time.mktime((year,month,1,0,0,0,0,1,-1)) + except OverflowError: + return 0.0 + return 0.0 + + def sortarchives(self): + def sf(a, b): + al = self.volNameToDate(a) + bl = self.volNameToDate(b) + if al > bl: + return 1 + elif al < bl: + return -1 + else: + return 0 + if self.ARCHIVE_PERIOD in ('month','year','quarter'): + self.archives.sort(key = cmp_to_key(sf)) else: - # Process plain text - processed_lines = [] - for line in lines: - # Handle quoted text - if self.IQUOTES and quotedpat.match(line): - line = '' + CGIescape(line, self.lang) + '' - else: - line = CGIescape(line, self.lang) - if self.SHOWBR: - line += '
    ' - processed_lines.append(line) - - # Add HTML structure - if not self.SHOWHTML: - processed_lines.insert(0, '
    ')
    -                processed_lines.append('
    ') - article.html_body = processed_lines + self.archives.sort() + self.archives.reverse() + + def message(self, msg): + if self.VERBOSE: + f = sys.stderr + f.write(msg) + if msg[-1:] != '\n': + f.write('\n') + f.flush() + + def open_new_archive(self, archive, archivedir): + index_html = os.path.join(archivedir, 'index.html') + try: + os.unlink(index_html) + except (OSError, IOError): + pass + os.symlink(self.DEFAULTINDEX+'.html',index_html) + + def write_index_header(self): + self.depth=0 + print(self.html_head()) + if not self.THREADLAZY and self.type=='Thread': + self.message(C_("Computing threaded index\n")) + self.updateThreadedIndex() + + def write_index_footer(self): + for i in range(self.depth): + print('') + print(self.html_foot()) + + def write_index_entry(self, article): + subject = self.get_header("subject", article) + author = self.get_header("author", article) + if mm_cfg.ARCHIVER_OBSCURES_EMAILADDRS: + try: + author = re.sub('@', _(' at '), author) + except UnicodeError: + # Non-ASCII author contains '@' ... no valid email anyway + pass + subject = CGIescape(subject, self.lang) + author = CGIescape(author, self.lang) + + d = { + 'filename': urllib.parse.quote(article.filename), + 'subject': subject, + 'sequence': article.sequence, + 'author': author + } + print(quick_maketext( + 'archidxentry.html', d, + mlist=self.maillist)) + + def get_header(self, field, article): + # if we have no decoded header, return the encoded one + result = article.decoded.get(field) + if result is None: + return getattr(article, field) + # otherwise, the decoded one will be Unicode + return result + + def write_threadindex_entry(self, article, depth): + if depth < 0: + self.message('depth<0') + depth = 0 + if depth > self.THREADLEVELS: + depth = self.THREADLEVELS + if depth < self.depth: + for i in range(self.depth-depth): + print('') + elif depth > self.depth: + for i in range(depth-self.depth): + print('
      ') + print('' % (depth, article.threadKey)) + self.depth = depth + self.write_index_entry(article) + + def write_TOC(self): + self.sortarchives() + omask = os.umask(0o002) + try: + toc = open(os.path.join(self.basedir, 'index.html'), 'w') + finally: + os.umask(omask) + toc.write(self.html_TOC()) + toc.close() - return article + def write_article(self, index, article, path): + # called by add_article + omask = os.umask(0o002) + try: + f = open(path, 'w') + finally: + os.umask(omask) + f.write(article.as_html()) + f.close() - def close(self): - "Close an archive, save its state, and update any changed archives." - self.update_dirty_archives() - self.update_TOC = 0 - self.write_TOC() - # Save the collective state - self.message(C_('Pickling archive state into ') - + os.path.join(self.basedir, 'pipermail.pck')) - self.database.close() - del self.database - - omask = os.umask(0o007) + # Write the text article to the text archive. + path = os.path.join(self.basedir, "%s.txt" % index) + omask = os.umask(0o002) try: - f = open(os.path.join(self.basedir, 'pipermail.pck'), 'wb') + f = open(path, 'a+') finally: os.umask(omask) - - # Only save safe attributes - safe_state = {} - safe_attrs = { - 'type', 'archive', 'firstdate', 'lastdate', 'archivedate', - 'size', 'version', 'subjectIndex', 'authorIndex', 'dateIndex', - 'articleIndex', 'threadIndex' - } - for key in safe_attrs: - if hasattr(self, key): - safe_state[key] = getattr(self, key) - - # Use protocol 4 for Python 2/3 compatibility - pickle.dump(safe_state, f, protocol=4, fix_imports=True) + f.write(article.as_text()) f.close() + def update_archive(self, archive): + self.__super_update_archive(archive) + # only do this if the gzip module was imported globally, and + # gzip'ing was enabled via mm_cfg.GZIP_ARCHIVE_TXT_FILES. See + # above. + if gzip: + archz = None + archt = None + txtfile = os.path.join(self.basedir, '%s.txt' % archive) + gzipfile = os.path.join(self.basedir, '%s.txt.gz' % archive) + oldgzip = os.path.join(self.basedir, '%s.old.txt.gz' % archive) + try: + # open the plain text file + archt = open(txtfile, 'r') + except IOError: + return + try: + os.rename(gzipfile, oldgzip) + archz = gzip.open(oldgzip) + except (IOError, RuntimeError, os.error): + pass + try: + ou = os.umask(0o002) + newz = gzip.open(gzipfile, 'w') + finally: + # XXX why is this a finally? + os.umask(ou) + if archz: + newz.write(archz.read()) + archz.close() + os.unlink(oldgzip) + # XXX do we really need all this in a try/except? + try: + newz.write(archt.read()) + newz.close() + archt.close() + except IOError: + pass + os.unlink(txtfile) + + _skip_attrs = ('maillist', '_lock_file', 'charset') + def getstate(self): - """Get the current state of the archive.""" + d={} + for each in list(self.__dict__.keys()): + if not (each in self._skip_attrs + or each.upper() == each): + d[each] = self.__dict__[each] + return d + + # Add tags around URLs and e-mail addresses. + + def __processbody_URLquote(self, lines): + # XXX a lot to do here: + # 1. use lines directly, rather than source and dest + # 2. make it clearer + # 3. make it faster + # TK: Prepare for unicode obscure. + atmark = _(' at ') + source = lines[:] + dest = lines + last_line_was_quoted = 0 + for i in range(0, len(source)): + Lorig = L = source[i] + prefix = suffix = "" + if L is None: + continue + # Italicise quoted text + if self.IQUOTES: + quoted = quotedpat.match(L) + if quoted is None: + last_line_was_quoted = 0 + else: + quoted = quoted.end(0) + prefix = CGIescape(L[:quoted], self.lang) + '' + suffix = '' + if self.SHOWHTML: + suffix += '
      ' + if not last_line_was_quoted: + prefix = '
      ' + prefix + L = L[quoted:] + last_line_was_quoted = 1 + # Check for an e-mail address + L2 = "" + jr = emailpat.search(L) + kr = urlpat.search(L) + while jr is not None or kr is not None: + if jr == None: + j = -1 + else: + j = jr.start(0) + if kr is None: + k = -1 + else: + k = kr.start(0) + if j != -1 and (j < k or k == -1): + text = jr.group(1) + length = len(text) + if mm_cfg.ARCHIVER_OBSCURES_EMAILADDRS: + text = re.sub('@', atmark, text) + URL = self.maillist.GetScriptURL( + 'listinfo', absolute=1) + else: + URL = 'mailto:' + text + pos = j + elif k != -1 and (j > k or j == -1): + text = URL = kr.group(1) + length = len(text) + pos = k + else: # j==k + raise ValueError("j==k: This can't happen!") + #length = len(text) + #self.message("URL: %s %s %s \n" + # % (CGIescape(L[:pos]), URL, CGIescape(text))) + L2 += '%s
      %s' % ( + CGIescape(L[:pos], self.lang), + html_quote(URL), CGIescape(text, self.lang)) + L = L[pos+length:] + jr = emailpat.search(L) + kr = urlpat.search(L) + if jr is None and kr is None: + L = CGIescape(L, self.lang) + if isinstance(L, bytes): + L = L.decode('utf-8') + L = prefix + L2 + L + suffix + source[i] = None + dest[i] = L + + # Perform Hypermail-style processing of directives + # in message bodies. Lines between and will be written + # out precisely as they are; other lines will be passed to func2 + # for further processing . + + def __processbody_HTML(self, lines): + # XXX need to make this method modify in place + source = lines[:] + dest = lines + l = len(source) + i = 0 + while i < l: + while i < l and htmlpat.match(source[i]) is None: + i = i + 1 + if i < l: + source[i] = None + i = i + 1 + while i < l and nohtmlpat.match(source[i]) is None: + dest[i], source[i] = source[i], None + i = i + 1 + if i < l: + source[i] = None + i = i + 1 + + def format_article(self, article): + # called from add_article + # TBD: Why do the HTML formatting here and keep it in the + # pipermail database? It makes more sense to do the html + # formatting as the article is being written as html and toss + # the data after it has been written to the archive file. + lines = [_f for _f in article.body if _f] + # Handle directives + if self.ALLOWHTML: + self.__processbody_HTML(lines) + self.__processbody_URLquote(lines) + if not self.SHOWHTML and lines: + lines.insert(0, '
      ')
      +            lines.append('
      ') + else: + # Do fancy formatting here + if self.SHOWBR: + lines = [x + "
      " for x in lines] + else: + for i in range(0, len(lines)): + s = lines[i] + if s[0:1] in ' \t\n': + lines[i] = '

      ' + s + article.html_body = lines + return article + + def update_article(self, arcdir, article, prev, next): + seq = article.sequence + filename = os.path.join(arcdir, article.filename) + self.message(C_('Updating HTML for article %(seq)s')) try: - # Use protocol 4 for Python 2/3 compatibility - protocol = 4 - return pickle.dumps(self.__dict__, protocol, fix_imports=True) - except Exception as e: - syslog('error', 'Error getting archive state: %s', e) - return None - - def setstate(self, state): - """Set the state of the archive.""" + f = open(filename) + article.loadbody_fromHTML(f) + f.close() + except IOError as e: + if e.errno != errno.ENOENT: raise + self.message(C_('article file %(filename)s is missing!')) + article.prev = prev + article.next = next + omask = os.umask(0o002) try: - # Use protocol 4 for Python 2/3 compatibility - protocol = 4 - self.__dict__ = pickle.loads(state, fix_imports=True, encoding='latin1') - except Exception as e: - syslog('error', 'Error setting archive state: %s', e) - return False - return True + f = open(filename, 'w') + finally: + os.umask(omask) + f.write(article.as_html()) + f.close() diff --git a/Mailman/Archiver/HyperDatabase.py b/Mailman/Archiver/HyperDatabase.py index 588515e3..b46eb362 100644 --- a/Mailman/Archiver/HyperDatabase.py +++ b/Mailman/Archiver/HyperDatabase.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import # Copyright (C) 1998-2018 by the Free Software Foundation, Inc. # # This program is free software; you can redistribute it and/or @@ -29,6 +30,7 @@ # from . import pipermail from Mailman import LockFile +from Mailman import Utils CACHESIZE = pipermail.CACHESIZE @@ -67,10 +69,21 @@ def __repr__(self): def __sort(self, dirty=None): if self.__dirty == 1 or dirty: - self.sorted = list(self.dict.keys()) - self.sorted.sort() + self.sorted = self.__fix_for_sort(list(self.dict.keys())) + if hasattr(self.sorted, 'sort'): + self.sorted.sort() self.__dirty = 0 + def __fix_for_sort(self, items): + if isinstance(items, bytes): + return items.decode() + elif isinstance(items, list): + return [ self.__fix_for_sort(item) for item in items ] + elif isinstance(items, tuple): + return tuple( self.__fix_for_sort(item) for item in items ) + else: + return items + def lock(self): self.lockfile.lock() @@ -168,7 +181,7 @@ def __len__(self): def load(self): try: - fp = open(self.path) + fp = open(self.path, mode='rb') try: self.dict = marshal.load(fp) finally: @@ -184,13 +197,14 @@ def load(self): def close(self): omask = os.umask(0o007) try: - fp = open(self.path, 'w') + fp = open(self.path, 'wb') finally: os.umask(omask) fp.write(marshal.dumps(self.dict)) fp.close() self.unlock() + # this is lifted straight out of pipermail with # the bsddb.btree replaced with above class. # didn't use inheritance because of all the @@ -273,7 +287,7 @@ def close(self): def hasArticle(self, archive, msgid): self.__openIndices(archive) - return msgid in self.articleIndex + return self.articleIndex.has_key(msgid) def setThreadKey(self, archive, key, msgid): self.__openIndices(archive) @@ -284,7 +298,7 @@ def getArticle(self, archive, msgid): if msgid not in self.__cache: # get the pickled object out of the DumbBTree buf = self.articleIndex[msgid] - article = self.__cache[msgid] = pickle.loads(buf, fix_imports=True, encoding='latin1') + article = self.__cache[msgid] = Utils.load_pickle(buf) # For upgrading older archives article.setListIfUnset(self._mlist) else: diff --git a/Mailman/Archiver/__init__.py b/Mailman/Archiver/__init__.py index cb00641d..11e20583 100644 --- a/Mailman/Archiver/__init__.py +++ b/Mailman/Archiver/__init__.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import # Copyright (C) 1998-2018 by the Free Software Foundation, Inc. # # This program is free software; you can redistribute it and/or diff --git a/Mailman/Archiver/pipermail.py b/Mailman/Archiver/pipermail.py index 0373a260..bd1e8df9 100644 --- a/Mailman/Archiver/pipermail.py +++ b/Mailman/Archiver/pipermail.py @@ -1,17 +1,15 @@ -#! /usr/bin/env python +#! /usr/bin/python3 +import errno import mailbox import os import re import sys import time -import string from email.utils import parseaddr, parsedate_tz, mktime_tz, formatdate import pickle from io import StringIO - -# Use string.ascii_lowercase instead of the old lowercase variable -lowercase = string.ascii_lowercase +from string import ascii_lowercase as lowercase __version__ = '0.09 (Mailman edition)' VERSION = __version__ @@ -19,6 +17,7 @@ from Mailman import mm_cfg from Mailman import Errors +from Mailman import Utils from Mailman.Mailbox import ArchiverMailbox from Mailman.Logging.Syslog import syslog from Mailman.i18n import _, C_ @@ -26,6 +25,7 @@ SPACE = ' ' + msgid_pat = re.compile(r'(<.*>)') def strip_separators(s): "Remove quotes or parenthesization from a Message-ID string" @@ -131,8 +131,7 @@ def store_article(self, article): temp2 = article.html_body article.body = [] del article.html_body - # Use protocol 4 for Python 2/3 compatibility - self.articleIndex[article.msgid] = pickle.dumps(article, protocol=4, fix_imports=True) + self.articleIndex[article.msgid] = pickle.dumps(article) article.body = temp article.html_body = temp2 @@ -220,8 +219,9 @@ def __init__(self, message = None, sequence = 0, keepHeaders = []): self.headers[i] = message[i] # Read the message body - s = StringIO(message.get_payload(decode=True)\ - or message.as_string().split('\n\n',1)[1]) + msg = message.get_payload()\ + or message.as_string().split('\n\n',1)[1] + s = StringIO(msg) self.body = s.readlines() def _set_date(self, message): @@ -272,26 +272,37 @@ class T(object): def __init__(self, basedir = None, reload = 1, database = None): # If basedir isn't provided, assume the current directory if basedir is None: - basedir = os.getcwd() - self.basedir = basedir + self.basedir = os.getcwd() + else: + basedir = os.path.expanduser(basedir) + self.basedir = basedir + self.database = database + + # If the directory doesn't exist, create it. This code shouldn't get + # run anymore, we create the directory in Archiver.py. It should only + # get used by legacy lists created that are only receiving their first + # message in the HTML archive now -- Marc + try: + os.stat(self.basedir) + except OSError as e: + if e.errno != errno.ENOENT: + raise + else: + self.message(C_('Creating archive directory ') + self.basedir) + omask = os.umask(0) + try: + os.mkdir(self.basedir, self.DIRMODE) + finally: + os.umask(omask) # Try to load previously pickled state try: if not reload: raise IOError - f = open(os.path.join(self.basedir, 'pipermail.pck'), 'rb') + d = Utils.load_pickle(os.path.join(self.basedir, 'pipermail.pck')) + if not d: + raise IOError("Pickled data is empty or None") self.message(C_('Reloading pickled archive state')) - try: - # Try UTF-8 first for newer files - d = pickle.load(f, fix_imports=True, encoding='utf-8') - except (UnicodeDecodeError, pickle.UnpicklingError): - # Fall back to latin1 for older files - f.seek(0) - d = pickle.load(f, fix_imports=True, encoding='latin1') - f.close() - if isinstance(d, bytes): - # If we got bytes, try to unpickle it - d = pickle.loads(d, fix_imports=True, encoding='latin1') for key, value in list(d.items()): setattr(self, key, value) except (IOError, EOFError): @@ -326,30 +337,12 @@ def close(self): f = open(os.path.join(self.basedir, 'pipermail.pck'), 'wb') finally: os.umask(omask) - # Use protocol 4 for Python 2/3 compatibility - pickle.dump(self.getstate(), f, protocol=4, fix_imports=True) + pickle.dump(self.getstate(), f) f.close() def getstate(self): - """Get the current state of the archive.""" - try: - # Use protocol 4 for Python 2/3 compatibility - protocol = 4 - return pickle.dumps(self.__dict__, protocol, fix_imports=True) - except Exception as e: - mailman_log('error', 'Error getting archive state: %s', e) - return None - - def setstate(self, state): - """Set the state of the archive.""" - try: - # Use protocol 4 for Python 2/3 compatibility - protocol = 4 - self.__dict__ = pickle.loads(state, fix_imports=True, encoding='latin1') - except Exception as e: - mailman_log('error', 'Error setting archive state: %s', e) - return False - return True + # can override this in subclass + return self.__dict__ # # Private methods @@ -383,7 +376,7 @@ def __findParent(self, article, children = []): parentID = article.in_reply_to elif article.references: # Remove article IDs that aren't in the archive - refs = list(filter(self.articleIndex.has_key, article.references)) + refs = list(filter(lambda x: x in self.articleIndex, article.references)) if not refs: return None maxdate = self.database.getArticle(self.archive, @@ -532,7 +525,7 @@ def _open_index_file_as_stdout(self, arcdir, index_name): path = os.path.join(arcdir, index_name + self.INDEX_EXT) omask = os.umask(0o002) try: - self.__f = open(path, 'w') + self.__f = open(path, 'w', encoding='utf-8') finally: os.umask(omask) self.__stdout = sys.stdout @@ -558,7 +551,8 @@ def _makeArticle(self, msg, sequence): return Article(msg, sequence) def processUnixMailbox(self, input, start=None, end=None): - mbox = ArchiverMailbox(input, self.maillist) + mbox = ArchiverMailbox(input.name, self.maillist) + mbox_iterator = iter(mbox.values()) if start is None: start = 0 counter = 0 @@ -566,7 +560,7 @@ def processUnixMailbox(self, input, start=None, end=None): mbox.skipping(True) while counter < start: try: - m = next(mbox) + m = next(mbox_iterator, None) except Errors.DiscardMessage: continue if m is None: @@ -577,7 +571,7 @@ def processUnixMailbox(self, input, start=None, end=None): while 1: try: pos = input.tell() - m = next(mbox) + m = next(mbox_iterator, None) except Errors.DiscardMessage: continue except Exception: @@ -605,29 +599,61 @@ def new_archive(self, archive, archivedir): # If the archive directory doesn't exist, create it try: os.stat(archivedir) - except os.error as errdata: - errno, errmsg = errdata - if errno == 2: + except OSError as e: + if e.errno != errno.ENOENT: + raise + else: omask = os.umask(0) try: os.mkdir(archivedir, self.DIRMODE) finally: os.umask(omask) - else: - raise os.error(errdata) self.open_new_archive(archive, archivedir) def add_article(self, article): - """Add an article to the archive.""" - try: - # Use protocol 4 for Python 2/3 compatibility - protocol = 4 - self.articleIndex[article.msgid] = pickle.dumps(article, protocol=4, fix_imports=True) - self.articleIndex.sync() - except Exception as e: - mailman_log('error', 'Error adding article %s: %s', article.msgid, e) - return False - return True + archives = self.get_archives(article) + if not archives: + return + if type(archives) == type(''): + archives = [archives] + + article.filename = filename = self.get_filename(article) + temp = self.format_article(article) + for arch in archives: + self.archive = arch # why do this??? + archivedir = os.path.join(self.basedir, arch) + if arch not in self.archives: + self.new_archive(arch, archivedir) + + # Write the HTML-ized article + self.write_article(arch, temp, os.path.join(archivedir, + filename)) + + if 'author' in article.decoded: + author = fixAuthor(article.decoded['author']) + else: + author = fixAuthor(article.author) + if 'stripped' in article.decoded: + subject = article.decoded['stripped'].lower() + else: + subject = article.subject.lower() + + article.parentID = parentID = self.get_parent_info(arch, article) + if parentID: + parent = self.database.getArticle(arch, parentID) + article.threadKey = (parent.threadKey + article.date + '.' + + str(article.sequence) + '-') + else: + article.threadKey = (article.date + '.' + + str(article.sequence) + '-') + key = article.threadKey, article.msgid + + self.database.setThreadKey(arch, key, article.msgid) + self.database.addArticle(arch, temp, author=author, + subject=subject) + + if arch not in self._dirty_archives: + self._dirty_archives.append(arch) def get_parent_info(self, archive, article): parentID = None @@ -661,7 +687,7 @@ def get_parent_info(self, archive, article): def write_article(self, index, article, path): omask = os.umask(0o002) try: - f = open(path, 'w') + f = open(path, 'w', encoding='utf-8') finally: os.umask(omask) temp_stdout, sys.stdout = sys.stdout, f diff --git a/Mailman/Autoresponder.py b/Mailman/Autoresponder.py index 475e3170..1466af34 100644 --- a/Mailman/Autoresponder.py +++ b/Mailman/Autoresponder.py @@ -20,7 +20,6 @@ from builtins import object from Mailman import mm_cfg from Mailman.i18n import _ -import time @@ -43,37 +42,3 @@ def InitVars(self): self.admin_responses = {} self.request_responses = {} - def autorespondToSender(self, sender, lang): - """Check if we should autorespond to this sender. - - Args: - sender: The email address of the sender - lang: The language to use for the response - - Returns: - True if we should autorespond, False otherwise - """ - # Check if we're in the grace period - now = time.time() - graceperiod = self.autoresponse_graceperiod - if graceperiod > 0: - # Check the appropriate response dictionary based on the type of message - if self.autorespond_admin: - quiet_until = self.admin_responses.get(sender, 0) - elif self.autorespond_requests: - quiet_until = self.request_responses.get(sender, 0) - else: - quiet_until = self.postings_responses.get(sender, 0) - if quiet_until > now: - return False - - # Update the appropriate response dictionary - if self.autorespond_admin: - self.admin_responses[sender] = now + graceperiod - elif self.autorespond_requests: - self.request_responses[sender] = now + graceperiod - else: - self.postings_responses[sender] = now + graceperiod - - return True - diff --git a/Mailman/Bouncer.py b/Mailman/Bouncer.py index 619a3ee7..ee932ab4 100644 --- a/Mailman/Bouncer.py +++ b/Mailman/Bouncer.py @@ -20,21 +20,13 @@ from builtins import object import sys import time -import os -import email -import errno -import pickle -import email.message -from email.message import Message from email.mime.text import MIMEText from email.mime.message import MIMEMessage -import Mailman from Mailman import mm_cfg from Mailman import Utils -from Mailman import Errors -from Mailman.Message import Message +from Mailman import Message from Mailman import MemberAdaptor from Mailman import Pending from Mailman.Errors import MMUnknownListError @@ -254,7 +246,7 @@ def __sendAdminBounceNotice(self, member, msg, did=None): 'owneraddr': siteowner, }, mlist=self) subject = _('Bounce action notification') - umsg = Mailman.Message.UserNotification(self.GetOwnerEmail(), + umsg = Message.UserNotification(self.GetOwnerEmail(), siteowner, subject, lang=self.preferred_language) # BAW: Be sure you set the type before trying to attach, or you'll get @@ -323,11 +315,11 @@ def sendNextNotification(self, member): 'owneraddr' : self.GetOwnerEmail(), 'reason' : txtreason, }, lang=lang, mlist=self) - msg = Mailman.Message.UserNotification(member, reqaddr, text=text, lang=lang) + msg = Message.UserNotification(member, reqaddr, text=text, lang=lang) # BAW: See the comment in MailList.py ChangeMemberAddress() for why we # set the Subject this way. del msg['subject'] - msg['Subject'] = _('confirm %(cookie)s') % {'cookie': info.cookie} + msg['Subject'] = 'confirm ' + info.cookie # Send without Precedence: bulk. Bug #808821. msg.send(self, noprecedence=True) info.noticesleft -= 1 @@ -348,7 +340,7 @@ def BounceMessage(self, msg, msgdata, e=None): else: notice = _(e.notice()) # Currently we always craft bounces as MIME messages. - bmsg = Mailman.Message.UserNotification(msg.get_sender(), + bmsg = Message.UserNotification(msg.get_sender(), self.GetOwnerEmail(), subject, lang=self.preferred_language) diff --git a/Mailman/Bouncers/Caiwireless.py b/Mailman/Bouncers/Caiwireless.py index c99c0945..4eb55509 100644 --- a/Mailman/Bouncers/Caiwireless.py +++ b/Mailman/Bouncers/Caiwireless.py @@ -18,19 +18,15 @@ import re import email -from email.iterators import body_line_iterator -from email.header import decode_header - -from Mailman import mm_cfg -from Mailman import Utils -from Mailman.Logging.Syslog import syslog -from Mailman.Handlers.CookHeaders import change_header +import email.iterators +from io import StringIO tcre = re.compile(r'the following recipients did not receive this message:', re.IGNORECASE) acre = re.compile(r'<(?P[^>]*)>') + def process(msg): if msg.get_content_type() != 'multipart/mixed': return None @@ -39,7 +35,7 @@ def process(msg): # 1 == tag line seen state = 0 # This format thinks it's a MIME, but it really isn't - for line in body_line_iterator(msg): + for line in email.iterators.body_line_iterator(msg): line = line.strip() if state == 0 and tcre.match(line): state = 1 @@ -48,16 +44,3 @@ def process(msg): if not mo: return None return [mo.group('addr')] - - # Now that we have a Message object that meets our criteria, let's extract - # the first numlines of body text. - lines = [] - lineno = 0 - for line in body_line_iterator(msg): - # Blank lines don't count - if not line.strip(): - continue - lineno += 1 - lines.append(line) - if numlines is not None and lineno >= numlines: - break diff --git a/Mailman/Bouncers/Compuserve.py b/Mailman/Bouncers/Compuserve.py index 3591eff8..be83bddc 100644 --- a/Mailman/Bouncers/Compuserve.py +++ b/Mailman/Bouncers/Compuserve.py @@ -18,19 +18,20 @@ import re import email -from email.iterators import body_line_iterator +import email.iterators dcre = re.compile(r'your message could not be delivered', re.IGNORECASE) acre = re.compile(r'Invalid receiver address: (?P.*)') + def process(msg): # simple state machine # 0 = nothing seen yet # 1 = intro line seen state = 0 addrs = [] - for line in body_line_iterator(msg): + for line in email.iterators.body_line_iterator(msg): if state == 0: mo = dcre.search(line) if mo: diff --git a/Mailman/Bouncers/DSN.py b/Mailman/Bouncers/DSN.py index 071ab09b..32beaa89 100644 --- a/Mailman/Bouncers/DSN.py +++ b/Mailman/Bouncers/DSN.py @@ -24,11 +24,10 @@ from email.iterators import typed_subpart_iterator from email.utils import parseaddr from io import StringIO -import re -import ipaddress from Mailman.Bouncers.BouncerAPI import Stop + def process(msg): # Iterate over each message/delivery-status subpart addrs = [] @@ -73,48 +72,6 @@ def process(msg): for param in params: if param.startswith('<') and param.endswith('>'): addrs.append(param[1:-1]) - - # Extract IP address from Received headers - ip = None - for header in msg.get_all('Received', []): - if isinstance(header, bytes): - header = header.decode('us-ascii', errors='replace') - # Look for IP addresses in Received headers - # Support both IPv4 and IPv6 formats - ip_match = re.search(r'\[([0-9a-fA-F:.]+)\]', header, re.IGNORECASE) - if ip_match: - ip = ip_match.group(1) - break - - if ip: - try: - if have_ipaddress: - ip_obj = ipaddress.ip_address(ip) - if isinstance(ip_obj, ipaddress.IPv4Address): - # For IPv4, drop last octet - parts = str(ip_obj).split('.') - ip = '.'.join(parts[:-1]) - else: - # For IPv6, drop last 16 bits - expanded = ip_obj.exploded.replace(':', '') - ip = expanded[:-4] - else: - # Fallback for systems without ipaddress module - if ':' in ip: - # IPv6 address - parts = ip.split(':') - if len(parts) <= 8: - # Pad with zeros and drop last 16 bits - expanded = ''.join(part.zfill(4) for part in parts) - ip = expanded[:-4] - else: - # IPv4 address - parts = ip.split('.') - if len(parts) == 4: - ip = '.'.join(parts[:-1]) - except (ValueError, IndexError): - ip = None - # Uniquify rtnaddrs = {} for a in addrs: diff --git a/Mailman/Bouncers/Exchange.py b/Mailman/Bouncers/Exchange.py index 68bc6fa6..273c8947 100644 --- a/Mailman/Bouncers/Exchange.py +++ b/Mailman/Bouncers/Exchange.py @@ -17,27 +17,18 @@ """Recognizes (some) Microsoft Exchange formats.""" import re -import email -from email.iterators import body_line_iterator -from email.header import decode_header +import email.iterators -from Mailman import mm_cfg -from Mailman import Utils -from Mailman.Logging.Syslog import syslog -from Mailman.Handlers.CookHeaders import change_header - -# Patterns for different Exchange/Office 365 bounce formats -scre = re.compile('did not reach the following recipient|Your message to .* couldn\'t be delivered') -ecre = re.compile('MSEXCH:|Action Required') +scre = re.compile('did not reach the following recipient') +ecre = re.compile('MSEXCH:') a1cre = re.compile('SMTP=(?P[^;]+); on ') a2cre = re.compile('(?P[^ ]+) on ') -a3cre = re.compile('Your message to (?P[^ ]+) couldn\'t be delivered') -a4cre = re.compile('(?P[^ ]+) wasn\'t found at ') + def process(msg): addrs = {} - it = body_line_iterator(msg) + it = email.iterators.body_line_iterator(msg) # Find the start line for line in it: if scre.search(line): @@ -48,18 +39,9 @@ def process(msg): for line in it: if ecre.search(line): break - # Try all patterns - for pattern in [a1cre, a2cre, a3cre, a4cre]: - mo = pattern.search(line) - if mo: - addr = mo.group('addr') - # Clean up the address if needed - if '@' not in addr and 'at' in line: - # Handle cases where domain is on next line - next_line = next(it, '') - if 'at' in next_line: - domain = next_line.split('at')[-1].strip() - addr = f"{addr}@{domain}" - addrs[addr] = 1 - break + mo = a1cre.search(line) + if not mo: + mo = a2cre.search(line) + if mo: + addrs[mo.group('addr')] = 1 return list(addrs.keys()) diff --git a/Mailman/Bouncers/GroupWise.py b/Mailman/Bouncers/GroupWise.py index 721b7660..91521869 100644 --- a/Mailman/Bouncers/GroupWise.py +++ b/Mailman/Bouncers/GroupWise.py @@ -22,18 +22,19 @@ """ import re -import email.message +from email.message import Message from io import StringIO acre = re.compile(r'<(?P[^>]*)>') + def find_textplain(msg): if msg.get_content_type() == 'text/plain': return msg if msg.is_multipart: for part in msg.get_payload(): - if not isinstance(part, email.message.Message): + if not isinstance(part, Message): continue ret = find_textplain(part) if ret: @@ -41,6 +42,7 @@ def find_textplain(msg): return None + def process(msg): if msg.get_content_type() != 'multipart/mixed' or not msg['x-mailer']: return None diff --git a/Mailman/Bouncers/LLNL.py b/Mailman/Bouncers/LLNL.py index 1e2a9e6f..3da78159 100644 --- a/Mailman/Bouncers/LLNL.py +++ b/Mailman/Bouncers/LLNL.py @@ -18,13 +18,14 @@ import re import email -from email.iterators import body_line_iterator +import email.iterators acre = re.compile(r',\s*(?P\S+@[^,]+),', re.IGNORECASE) + def process(msg): - for line in body_line_iterator(msg): + for line in email.iterators.body_line_iterator(msg): mo = acre.search(line) if mo: return [mo.group('addr')] diff --git a/Mailman/Bouncers/Microsoft.py b/Mailman/Bouncers/Microsoft.py index 5f67cb3c..09ec9384 100644 --- a/Mailman/Bouncers/Microsoft.py +++ b/Mailman/Bouncers/Microsoft.py @@ -22,6 +22,7 @@ scre = re.compile(r'transcript of session follows', re.IGNORECASE) + def process(msg): if msg.get_content_type() != 'multipart/mixed': return None diff --git a/Mailman/Bouncers/Qmail.py b/Mailman/Bouncers/Qmail.py index b0c5215d..5d4f2157 100644 --- a/Mailman/Bouncers/Qmail.py +++ b/Mailman/Bouncers/Qmail.py @@ -27,18 +27,7 @@ """ import re -import sys -import email -from email.iterators import body_line_iterator -from email.mime.text import MIMEText -from email.mime.message import MIMEMessage - -from Mailman import mm_cfg -from Mailman import Utils -from Mailman.Message import Message -from Mailman import Errors -from Mailman import i18n -from Mailman.Logging.Syslog import syslog +import email.iterators # Other (non-standard?) intros have been observed in the wild. introtags = [ @@ -53,6 +42,7 @@ acre = re.compile(r'<(?P[^>]*)>:') + def process(msg): addrs = [] # simple state machine @@ -60,10 +50,7 @@ def process(msg): # 1 = intro paragraph seen # 2 = recip paragraphs seen state = 0 - for line in body_line_iterator(msg): - # Ensure line is a string - if isinstance(line, bytes): - line = line.decode('ascii', 'replace') + for line in email.iterators.body_line_iterator(msg): line = line.strip() if state == 0: for introtag in introtags: diff --git a/Mailman/Bouncers/SMTP32.py b/Mailman/Bouncers/SMTP32.py index 955bafd4..b21a90ee 100644 --- a/Mailman/Bouncers/SMTP32.py +++ b/Mailman/Bouncers/SMTP32.py @@ -30,7 +30,7 @@ import re import email -from email.iterators import body_line_iterator +import email.iterators ecre = re.compile('original message follows', re.IGNORECASE) acre = re.compile(r''' @@ -46,12 +46,13 @@ ''', re.IGNORECASE | re.VERBOSE) + def process(msg): mailer = msg.get('x-mailer', '') if not mailer.startswith('[^>]*)>')), # sz-sb.de, corridor.com, nfg.nl - (_c(r'the following addresses had'), - _c(r'transcript of session follows'), + (_c('the following addresses had'), + _c('transcript of session follows'), _c(r'^ *(\(expanded from: )?[^\s@]+@[^\s@>]+?)>?\)?\s*$')), # robanal.demon.co.uk - (_c(r'this message was created automatically by mail delivery software'), - _c(r'original message follows'), + (_c('this message was created automatically by mail delivery software'), + _c('original message follows'), _c(r'rcpt to:\s*<(?P[^>]*)>')), # s1.com (InterScan E-Mail VirusWall NT ???) - (_c(r'message from interscan e-mail viruswall nt'), - _c(r'end of message'), + (_c('message from interscan e-mail viruswall nt'), + _c('end of message'), _c(r'rcpt to:\s*<(?P[^>]*)>')), # Smail - (_c(r'failed addresses follow:'), - _c(r'message text follows:'), + (_c('failed addresses follow:'), + _c('message text follows:'), _c(r'\s*(?P\S+@\S+)')), # newmail.ru - (_c(r'This is the machine generated message from mail service.'), - _c(r'--- Below the next line is a copy of the message.'), - _c(r'<(?P[^>]*)>')), + (_c('This is the machine generated message from mail service.'), + _c('--- Below the next line is a copy of the message.'), + _c('<(?P[^>]*)>')), # turbosport.com runs something called `MDaemon 3.5.2' ??? - (_c(r'The following addresses did NOT receive a copy of your message:'), - _c(r'--- Session Transcript ---'), + (_c('The following addresses did NOT receive a copy of your message:'), + _c('--- Session Transcript ---'), _c(r'[>]\s*(?P.*)$')), # usa.net (_c(r'Intended recipient:\s*(?P.*)$'), - _c(r'--------RETURNED MAIL FOLLOWS--------'), + _c('--------RETURNED MAIL FOLLOWS--------'), _c(r'Intended recipient:\s*(?P.*)$')), # hotpop.com (_c(r'Undeliverable Address:\s*(?P.*)$'), - _c(r'Original message attached'), + _c('Original message attached'), _c(r'Undeliverable Address:\s*(?P.*)$')), # Another demon.co.uk format - (_c(r'This message was created automatically by mail delivery'), - _c(r'^---- START OF RETURNED MESSAGE ----'), - _c(r"addressed to '(?P[^']*)'")), + (_c('This message was created automatically by mail delivery'), + _c('^---- START OF RETURNED MESSAGE ----'), + _c("addressed to '(?P[^']*)'")), # Prodigy.net full mailbox - (_c(r"User's mailbox is full:"), - _c(r'Unable to deliver mail.'), + (_c("User's mailbox is full:"), + _c('Unable to deliver mail.'), _c(r"User's mailbox is full:\s*<(?P[^>]*)>")), # Microsoft SMTPSVC - (_c(r'The email below could not be delivered to the following user:'), - _c(r'Old message:'), - _c(r'<(?P[^>]*)>')), + (_c('The email below could not be delivered to the following user:'), + _c('Old message:'), + _c('<(?P[^>]*)>')), # Yahoo on behalf of other domains like sbcglobal.net (_c(r'Unable to deliver message to the following address\(es\)\.'), _c(r'--- Original message follows\.'), - _c(r'<(?P[^>]*)>:')), + _c('<(?P[^>]*)>:')), # googlemail.com - (_c(r'Delivery to the following recipient(s)? failed'), - _c(r'----- Original message -----'), + (_c('Delivery to the following recipient(s)? failed'), + _c('----- Original message -----'), _c(r'^\s*(?P[^\s@]+@[^\s@]+)\s*$')), # kundenserver.de, mxlogic.net - (_c(r'A message that you( have)? sent could not be delivered'), - _c(r'^---'), - _c(r'<(?P[^>]*)>')), + (_c('A message that you( have)? sent could not be delivered'), + _c('^---'), + _c('<(?P[^>]*)>')), # another kundenserver.de - (_c(r'A message that you( have)? sent could not be delivered'), - _c(r'^---'), + (_c('A message that you( have)? sent could not be delivered'), + _c('^---'), _c(r'^(?P[^\s@]+@[^\s@:]+):')), # thehartford.com and amenworld.com - (_c(r'Del(i|e)very to the following recipient(s)? (failed|was aborted)'), + (_c('Del(i|e)very to the following recipient(s)? (failed|was aborted)'), # this one may or may not have the original message, but there's nothing # unique to stop on, so stop on the first line of at least 3 characters # that doesn't start with 'D' (to not stop immediately) and has no '@'. - _c(r'^[^D][^@]{2,}$'), + _c('^[^D][^@]{2,}$'), _c(r'^\s*(. )?(?P[^\s@]+@[^\s@]+)\s*$')), # and another thehartfod.com/hartfordlife.com (_c(r'^Your message\s*$'), - _c(r'^because:'), + _c('^because:'), _c(r'^\s*(?P[^\s@]+@[^\s@]+)\s*$')), # kviv.be (InterScan NT) - (_c(r'^Unable to deliver message to'), + (_c('^Unable to deliver message to'), _c(r'\*+\s+End of message\s+\*+'), - _c(r'<(?P[^>]*)>')), + _c('<(?P[^>]*)>')), # earthlink.net supported domains - (_c(r'^Sorry, unable to deliver your message to'), - _c(r'^A copy of the original message'), + (_c('^Sorry, unable to deliver your message to'), + _c('^A copy of the original message'), _c(r'\s*(?P[^\s@]+@[^\s@]+)\s+')), # ademe.fr - (_c(r'^A message could not be delivered to:'), - _c(r'^Subject:'), + (_c('^A message could not be delivered to:'), + _c('^Subject:'), _c(r'^\s*(?P[^\s@]+@[^\s@]+)\s*$')), # andrew.ac.jp - (_c(r'^Invalid final delivery userid:'), - _c(r'^Original message follows.'), + (_c('^Invalid final delivery userid:'), + _c('^Original message follows.'), _c(r'\s*(?P[^\s@]+@[^\s@]+)\s*$')), # E500_SMTP_Mail_Service@lerctr.org and similar - (_c(r'---- Failed Recipients ----'), - _c(r' Mail ----'), - _c(r'<(?P[^>]*)>')), + (_c('---- Failed Recipients ----'), + _c(' Mail ----'), + _c('<(?P[^>]*)>')), # cynergycom.net - (_c(r'A message that you sent could not be delivered'), - _c(r'^---'), + (_c('A message that you sent could not be delivered'), + _c('^---'), _c(r'(?P[^\s@]+@[^\s@)]+)')), # LSMTP for Windows (_c(r'^--> Error description:\s*$'), - _c(r'^Error-End:'), + _c('^Error-End:'), _c(r'^Error-for:\s+(?P[^\s@]+@[^\s@]+)')), # Qmail with a tri-language intro beginning in spanish - (_c(r'Your message could not be delivered'), - _c(r'^-'), - _c(r'<(?P[^>]*)>:')), + (_c('Your message could not be delivered'), + _c('^-'), + _c('<(?P[^>]*)>:')), # socgen.com - (_c(r'Your message could not be delivered to'), + (_c('Your message could not be delivered to'), _c(r'^\s*$'), _c(r'(?P[^\s@]+@[^\s@]+)')), # dadoservice.it - (_c(r'Your message has encountered delivery problems'), - _c(r'Your message reads'), + (_c('Your message has encountered delivery problems'), + _c('Your message reads'), _c(r'addressed to\s*(?P[^\s@]+@[^\s@)]+)')), # gomaps.com - (_c(r'Did not reach the following recipient'), + (_c('Did not reach the following recipient'), _c(r'^\s*$'), _c(r'\s(?P[^\s@]+@[^\s@]+)')), # EYOU MTA SYSTEM - (_c(r'This is the deliver program at'), - _c(r'^-'), + (_c('This is the deliver program at'), + _c('^-'), _c(r'^(?P[^\s@]+@[^\s@<>]+)')), # A non-standard qmail at ieo.it - (_c(r'this is the email server at'), - _c(r'^-'), + (_c('this is the email server at'), + _c('^-'), _c(r'\s(?P[^\s@]+@[^\s@]+)[\s,]')), # pla.net.py (MDaemon.PRO ?) - (_c(r'- no such user here'), - _c(r'There is no user'), + (_c('- no such user here'), + _c('There is no user'), _c(r'^(?P[^\s@]+@[^\s@]+)\s')), # fastdnsservers.com - (_c(r'The following recipient.*could not be reached'), - _c(r'bogus stop pattern'), + (_c('The following recipient.*could not be reached'), + _c('bogus stop pattern'), _c(r'^(?P[^\s@]+@[^\s@]+)\s*$')), # lttf.com - (_c(r'Could not deliver message to'), + (_c('Could not deliver message to'), _c(r'^\s*--'), _c(r'^Failed Recipient:\s*(?P[^\s@]+@[^\s@]+)\s*$')), # uci.edu - (_c(r'--------Message not delivered'), - _c(r'--------Error Detail'), + (_c('--------Message not delivered'), + _c('--------Error Detail'), _c(r'^\s*(?P[^\s@]+@[^\s@]+)\s*$')), # Dovecot LDA Over quota MDN (bogus - should be DSN). - (_c(r'^Your message'), - _c(r'^Reporting'), + (_c('^Your message'), + _c('^Reporting'), _c( r'Your message to (?P[^\s@]+@[^\s@]+) was automatically rejected' )), # mail.ru - (_c(r'A message that you sent was rejected'), - _c(r'This is a copy of your message'), + (_c('A message that you sent was rejected'), + _c('This is a copy of your message'), _c(r'\s(?P[^\s@]+@[^\s@]+)')), # MailEnable - (_c(r'Message could not be delivered to some recipients.'), - _c(r'Message headers follow'), + (_c('Message could not be delivered to some recipients.'), + _c('Message headers follow'), _c(r'Recipient: \[SMTP:(?P[^\s@]+@[^\s@]+)\]')), # This one is from Yahoo but dosen't fit the yahoo recognizer format (_c(r'wasn\'t able to deliver the following message'), @@ -231,7 +224,7 @@ def process(msg, patterns=None): # we process the message multiple times anyway. for scre, ecre, acre in patterns: state = 0 - for line in body_line_iterator(msg): + for line in email.iterators.body_line_iterator(msg, decode=True): if state == 0: if scre.search(line): state = 1 diff --git a/Mailman/Bouncers/SimpleWarning.py b/Mailman/Bouncers/SimpleWarning.py index 08d4862d..27970640 100644 --- a/Mailman/Bouncers/SimpleWarning.py +++ b/Mailman/Bouncers/SimpleWarning.py @@ -17,9 +17,8 @@ """Recognizes simple heuristically delimited warnings.""" -import re import email -from email.iterators import body_line_iterator +import email.iterators from Mailman.Bouncers.BouncerAPI import Stop from Mailman.Bouncers.SimpleMatch import _c @@ -76,7 +75,7 @@ def process(msg): addrs = {} for scre, ecre, acre in patterns: state = 0 - for line in body_line_iterator(msg, decode=True): + for line in email.iterators.body_line_iterator(msg, decode=True): if state == 0: if scre.search(line): state = 1 diff --git a/Mailman/Bouncers/Sina.py b/Mailman/Bouncers/Sina.py index 5e48cb44..223bcdb7 100644 --- a/Mailman/Bouncers/Sina.py +++ b/Mailman/Bouncers/Sina.py @@ -18,13 +18,12 @@ from __future__ import print_function import re -import email -from email.iterators import body_line_iterator -from email.header import decode_header +from email import iterators acre = re.compile(r'<(?P[^>]*)>') + def process(msg): if msg.get('from', '').lower() != 'mailer-daemon@sina.com': print('out 1') @@ -42,7 +41,7 @@ def process(msg): print('out 3') return [] addrs = {} - for line in body_line_iterator(part): + for line in iterators.body_line_iterator(part): mo = acre.match(line) if mo: addrs[mo.group('addr')] = 1 diff --git a/Mailman/Bouncers/Yahoo.py b/Mailman/Bouncers/Yahoo.py index c0883c77..68e016e7 100644 --- a/Mailman/Bouncers/Yahoo.py +++ b/Mailman/Bouncers/Yahoo.py @@ -19,15 +19,9 @@ import re import email -from email.iterators import body_line_iterator -from email.header import decode_header +import email.iterators from email.utils import parseaddr -from Mailman import mm_cfg -from Mailman import Utils -from Mailman.Logging.Syslog import syslog -from Mailman.Handlers.CookHeaders import change_header - tcre = (re.compile(r'message\s+from\s+yahoo\.\S+', re.IGNORECASE), re.compile(r'Sorry, we were unable to deliver your message to ' r'the following address(\(es\))?\.', @@ -39,6 +33,7 @@ ) + def process(msg): # Yahoo! bounces seem to have a known subject value and something called # an x-uidl: header, the value of which seems unimportant. @@ -51,7 +46,7 @@ def process(msg): # 1 == tag line seen # 2 == end line seen state = 0 - for line in body_line_iterator(msg): + for line in email.iterators.body_line_iterator(msg): line = line.strip() if state == 0: for cre in tcre: diff --git a/Mailman/CSRFcheck.py b/Mailman/CSRFcheck.py index b7dca18b..23494b50 100644 --- a/Mailman/CSRFcheck.py +++ b/Mailman/CSRFcheck.py @@ -18,10 +18,9 @@ """ Cross-Site Request Forgery checker """ import time -import urllib.parse +import urllib import marshal import binascii -import traceback from Mailman import mm_cfg from Mailman.Logging.Syslog import syslog @@ -36,63 +35,40 @@ } + def csrf_token(mlist, contexts, user=None): """ create token by mailman cookie generation algorithm """ + if user: # Unmunge a munged email address. user = UnobscureEmail(urllib.parse.unquote(user)) - syslog('debug', 'CSRF token generation: mlist=%s, contexts=%s, user=%s', - mlist.internal_name(), contexts, user) - else: - syslog('debug', 'CSRF token generation: mlist=%s, contexts=%s', - mlist.internal_name(), contexts) - selected_context = None for context in contexts: key, secret = mlist.AuthContextInfo(context, user) if key and secret: - selected_context = context - syslog('debug', 'CSRF token generation: Selected context=%s, key=%s', - context, key) break else: - syslog('debug', 'CSRF token generation failed: No valid context found in %s', - contexts) return None # not authenticated - issued = int(time.time()) needs_hash = (secret + repr(issued)).encode('utf-8') mac = sha_new(needs_hash).hexdigest() keymac = '%s:%s' % (key, mac) - token = binascii.hexlify(marshal.dumps((issued, keymac))).decode('utf-8') - - syslog('debug', 'CSRF token generated: context=%s, key=%s, issued=%s, mac=%s, token=%s', - selected_context, key, time.ctime(issued), mac, token) + token = marshal.dumps((issued, keymac)).hex() + return token def csrf_check(mlist, token, cgi_user=None): """ check token by mailman cookie validation algorithm """ try: - syslog('debug', 'CSRF token validation: mlist=%s, cgi_user=%s, token=%s', - mlist.internal_name(), cgi_user, token) - issued, keymac = marshal.loads(binascii.unhexlify(token)) key, received_mac = keymac.split(':', 1) - - syslog('debug', 'CSRF token details: issued=%s, key=%s, received_mac=%s', - time.ctime(issued), key, received_mac) - if not key.startswith(mlist.internal_name() + '+'): - syslog('debug', 'CSRF token validation failed: Invalid mailing list name in key. Expected %s, got %s', - mlist.internal_name(), key) return False - key = key[len(mlist.internal_name()) + 1:] if '+' in key: key, user = key.split('+', 1) else: user = None - # Don't allow unprivileged tokens for admin or admindb. if cgi_user == 'admin': if key not in ('admin', 'site'): @@ -106,7 +82,6 @@ def csrf_check(mlist, token, cgi_user=None): 'admindb form submitted with CSRF token issued for %s.', key + '+' + user if user else key) return False - if user: # This is for CVE-2021-42097. The token is a user token because # of the fix for CVE-2021-42096 but it must match the user for @@ -118,51 +93,14 @@ def csrf_check(mlist, token, cgi_user=None): 'issued for %s.', cgi_user, raw_user) return False - context = keydict.get(key) key, secret = mlist.AuthContextInfo(context, user) - if not key: - raise ValueError('Missing CSRF key') - - try: - # Ensure all values are properly encoded before hashing - if isinstance(secret, str): - secret = secret.encode('utf-8') - elif not isinstance(secret, bytes): - secret = str(secret).encode('utf-8') - - issued_str = str(issued) - if isinstance(issued_str, str): - issued_str = issued_str.encode('utf-8') - - mac = sha_new(secret + issued_str).hexdigest() - except (TypeError, UnicodeError) as e: - syslog('error', 'CSRF token validation failed with encoding error: %s. Secret type: %s, issued type: %s, secret value: %r, issued value: %r', - str(e), type(secret), type(issued), secret, issued) - return False - - age = time.time() - issued - - syslog('debug', 'CSRF token validation: context=%s, generated_mac=%s, age=%s seconds', - context, mac, age) - + assert key + secret = secret + repr(issued) + mac = sha_new(secret.encode()).hexdigest() if (mac == received_mac - and 0 < age < mm_cfg.FORM_LIFETIME): - syslog('debug', 'CSRF token validation successful') + and 0 < time.time() - issued < mm_cfg.FORM_LIFETIME): return True - - if mac != received_mac: - syslog('debug', 'CSRF token validation failed: MAC mismatch. Expected %s, got %s. Full token details: expected=(%s, %s:%s), received=(%s, %s:%s)', - mac, received_mac, time.ctime(issued), key, mac, time.ctime(issued), key, received_mac) - elif age <= 0: - syslog('debug', 'CSRF token validation failed: Token issued in the future. Token details: issued=%s, key=%s, mac=%s', - time.ctime(issued), key, received_mac) - else: - syslog('debug', 'CSRF token validation failed: Token expired. Age: %s seconds, FORM_LIFETIME=%s seconds, contexts=%s. Token details: issued=%s, key=%s, mac=%s', - age, mm_cfg.FORM_LIFETIME, keydict.keys(), time.ctime(issued), key, received_mac) - return False - except (AssertionError, ValueError, TypeError) as e: - syslog('error', 'CSRF token validation failed with error: %s\nTraceback:\n%s', - str(e), ''.join(traceback.format_exc())) + except (AssertionError, ValueError, TypeError): return False diff --git a/Mailman/Cgi/Auth.py b/Mailman/Cgi/Auth.py index 689988a9..6f61d568 100644 --- a/Mailman/Cgi/Auth.py +++ b/Mailman/Cgi/Auth.py @@ -52,7 +52,6 @@ def loginpage(mlist, scriptname, msg='', frontpage=None): # Language stuff charset = Utils.GetCharSet(mlist.preferred_language) print('Content-type: text/html; charset=' + charset + '\n\n') - print('') print(Utils.maketext( 'admlogin.html', {'listname': mlist.real_name, diff --git a/Mailman/Cgi/admin.py b/Mailman/Cgi/admin.py index df8a5dfb..328c4162 100644 --- a/Mailman/Cgi/admin.py +++ b/Mailman/Cgi/admin.py @@ -20,18 +20,19 @@ def cmp(a, b): return (a > b) - (a < b) +#from future.builtins import cmp import sys import os import re -import urllib.parse +from Mailman.Utils import FieldStorage +import urllib.request, urllib.parse, urllib.error import signal -import traceback from email.utils import unquote, parseaddr, formataddr from Mailman import mm_cfg from Mailman import Utils -from Mailman.Message import Message +from Mailman import Message from Mailman import MailList from Mailman import Errors from Mailman import MemberAdaptor @@ -39,7 +40,7 @@ def cmp(a, b): from Mailman.UserDesc import UserDesc from Mailman.htmlformat import * from Mailman.Cgi import Auth -from Mailman.Logging.Syslog import mailman_log +from Mailman.Logging.Syslog import syslog from Mailman.Utils import sha_new from Mailman.CSRFcheck import csrf_check @@ -54,260 +55,203 @@ def D_(s): AUTH_CONTEXTS = (mm_cfg.AuthListAdmin, mm_cfg.AuthSiteAdmin) -def validate_listname(listname): - """Validate and sanitize a listname to prevent path traversal. - - Args: - listname: The listname to validate - - Returns: - tuple: (is_valid, sanitized_name, error_message) - """ - if not listname: - return False, None, _('List name is required') - - # Convert to lowercase and strip whitespace - listname = listname.lower().strip() - - # Basic validation - if not Utils.ValidateListName(listname): - return False, None, _('Invalid list name') - - # Check for path traversal attempts - if '..' in listname or '/' in listname or '\\' in listname: - return False, None, _('Invalid list name') - - return True, listname, None - -def handle_no_list(): - """Handle the case when no list is specified.""" - doc = Document() - doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) - doc.SetTitle(_('CGI script error')) - doc.AddItem(Header(2, _('CGI script error'))) - doc.addError(_('Invalid options to CGI script.')) - doc.AddItem('


      ') - doc.AddItem(MailmanLogo()) - print('Status: 400 Bad Request') - return doc + def main(): + # Try to find out which list is being administered + parts = Utils.GetPathPieces() + if not parts: + # None, so just do the admin overview and be done with it + admin_overview() + return + # Get the list object + listname = parts[0].lower() try: - # Log page load - mailman_log('info', 'admin: Page load started') - - # Initialize document early + mlist = MailList.MailList(listname, lock=0) + except Errors.MMListError as e: + # Avoid cross-site scripting attacks + safelistname = Utils.websafe(listname) + # Send this with a 404 status. + print('Status: 404 Not Found') + admin_overview(_(f'No such list {safelistname}')) + syslog('error', 'admin: No such list "%s": %s\n', + listname, e) + return + # Now that we know what list has been requested, all subsequent admin + # pages are shown in that list's preferred language. + i18n.set_language(mlist.preferred_language) + # If the user is not authenticated, we're done. + cgidata = FieldStorage(keep_blank_values=1) + try: + cgidata.getfirst('csrf_token', '') + except TypeError: + # Someone crafted a POST with a bad Content-Type:. doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) - - # Parse form data first since we need it for authentication - try: - if os.environ.get('REQUEST_METHOD') == 'POST': - content_length = int(os.environ.get('CONTENT_LENGTH', 0)) - if content_length > 0: - form_data = sys.stdin.read(content_length) - cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=True) - else: - cgidata = {} - else: - query_string = os.environ.get('QUERY_STRING', '') - cgidata = urllib.parse.parse_qs(query_string, keep_blank_values=True) - except Exception as e: - mailman_log('error', 'admin: Invalid form data: %s\n%s', str(e), traceback.format_exc()) - doc.AddItem(Header(2, _("Error"))) - doc.AddItem(Bold(_('Invalid request'))) - print('Status: 400 Bad Request') - print(doc.Format()) - return - - # Get the list name - parts = Utils.GetPathPieces() - if not parts: - doc = handle_no_list() - print(doc.Format()) - return - - # Validate listname - is_valid, listname, error_msg = validate_listname(parts[0]) - if not is_valid: - doc.SetTitle(_('CGI script error')) - doc.AddItem(Header(2, _('CGI script error'))) - doc.addError(error_msg) - doc.AddItem('
      ') - doc.AddItem(MailmanLogo()) - print('Status: 400 Bad Request') - print(doc.Format()) - return - - mailman_log('info', 'admin: Processing list "%s"', listname) + doc.AddItem(Header(2, _("Error"))) + doc.AddItem(Bold(_('Invalid options to CGI script.'))) + # Send this with a 400 status. + print('Status: 400 Bad Request') + print(doc.Format()) + return - try: - mlist = MailList.MailList(listname, lock=0) - except Errors.MMListError as e: - # Avoid cross-site scripting attacks and information disclosure - safelistname = Utils.websafe(listname) - doc.SetTitle(_('CGI script error')) - doc.AddItem(Header(2, _('CGI script error'))) - doc.addError(_('No such list {safelistname}')) - doc.AddItem('
      ') - doc.AddItem(MailmanLogo()) - print('Status: 404 Not Found') - print(doc.Format()) - mailman_log('error', 'admin: No such list "%s"', listname) - return - except Exception as e: - # Log the full error but don't expose it to the user - mailman_log('error', 'admin: Unexpected error for list "%s": %s', listname, str(e)) - doc.SetTitle(_('CGI script error')) - doc.AddItem(Header(2, _('CGI script error'))) - doc.addError(_('An error occurred processing your request')) - doc.AddItem('
      ') - doc.AddItem(MailmanLogo()) - print('Status: 500 Internal Server Error') - print(doc.Format()) - return + # CSRF check + safe_params = ['VARHELP', 'adminpw', 'admlogin', + 'letter', 'chunk', 'findmember', + 'legend'] + params = list(cgidata.keys()) + if set(params) - set(safe_params): + csrf_checked = csrf_check(mlist, cgidata.getfirst('csrf_token'), + 'admin') + else: + csrf_checked = True + # if password is present, void cookie to force password authentication. + if cgidata.getfirst('adminpw'): + os.environ['HTTP_COOKIE'] = '' + csrf_checked = True + + if not mlist.WebAuthenticate((mm_cfg.AuthListAdmin, + mm_cfg.AuthSiteAdmin), + cgidata.getfirst('adminpw', '')): + if 'adminpw' in cgidata: + # This is a re-authorization attempt + msg = Bold(FontSize('+1', _('Authorization failed.'))).Format() + remote = os.environ.get('HTTP_FORWARDED_FOR', + os.environ.get('HTTP_X_FORWARDED_FOR', + os.environ.get('REMOTE_ADDR', + 'unidentified origin'))) + syslog('security', + 'Authorization failed (admin): list=%s: remote=%s', + listname, remote) + else: + msg = '' + Auth.loginpage(mlist, 'admin', msg=msg) + return - i18n.set_language(mlist.preferred_language) - # If the user is not authenticated, we're done. - try: - # CSRF check - safe_params = ['VARHELP', 'adminpw', 'admlogin', - 'letter', 'chunk', 'findmember', - 'legend'] - params = list(cgidata.keys()) - if set(params) - set(safe_params): - csrf_checked = csrf_check(mlist, cgidata.get('csrf_token', [''])[0], - 'admin') - else: - csrf_checked = True - if cgidata.get('adminpw', [''])[0]: - os.environ['HTTP_COOKIE'] = '' - csrf_checked = True - auth_result = mlist.WebAuthenticate((mm_cfg.AuthListAdmin, - mm_cfg.AuthSiteAdmin), - cgidata.get('adminpw', [''])[0]) - if not auth_result: - for context in (mm_cfg.AuthListAdmin, mm_cfg.AuthSiteAdmin): - mailman_log('debug', 'Checking context %s: %s', - context, str(mlist.AuthContextInfo(context))) - except Exception as e: - mailman_log('error', 'admin: Exception during WebAuthenticate: %s\n%s', str(e), traceback.format_exc()) - raise - if not auth_result: - if 'adminpw' in cgidata: - msg = Bold(FontSize('+1', _('Authorization failed.'))).Format() - remote = os.environ.get('HTTP_FORWARDED_FOR', - os.environ.get('HTTP_X_FORWARDED_FOR', - os.environ.get('REMOTE_ADDR', - 'unidentified origin'))) - mailman_log('security', - 'Authorization failed (admin): list=%s: remote=%s\n%s', - listname, remote, traceback.format_exc()) - else: - msg = '' - Auth.loginpage(mlist, 'admin', msg=msg) - return + # Which subcategory was requested? Default is `general' + if len(parts) == 1: + category = 'general' + subcat = None + elif len(parts) == 2: + category = parts[1] + subcat = None + else: + category = parts[1] + subcat = parts[2] + + # Is this a log-out request? + if category == 'logout': + # site-wide admin should also be able to logout. + if mlist.AuthContextInfo(mm_cfg.AuthSiteAdmin)[0] == 'site': + print(mlist.ZapCookie(mm_cfg.AuthSiteAdmin)) + print(mlist.ZapCookie(mm_cfg.AuthListAdmin)) + Auth.loginpage(mlist, 'admin', frontpage=1) + return - # Which subcategory was requested? Default is `general' - if len(parts) == 1: - category = 'general' - subcat = None - elif len(parts) == 2: - category = parts[1] - subcat = None - else: - category = parts[1] - subcat = parts[2] + # Sanity check + if category not in list(mlist.GetConfigCategories().keys()): + category = 'general' - # Sanity check - validate category against available categories - if category not in list(mlist.GetConfigCategories().keys()): - category = 'general' + # Is the request for variable details? + varhelp = None + qsenviron = os.environ.get('QUERY_STRING') + parsedqs = None + if qsenviron: + parsedqs = urllib.parse.parse_qs(qsenviron) + if 'VARHELP' in cgidata: + varhelp = cgidata.getfirst('VARHELP') + elif parsedqs: + # POST methods, even if their actions have a query string, don't get + # put into FieldStorage's keys :-( + qs = parsedqs.get('VARHELP') + if qs and type(qs) is list: + varhelp = qs[0] + if varhelp: + option_help(mlist, varhelp) + return - # Is the request for variable details? - varhelp = None - qsenviron = os.environ.get('QUERY_STRING') - parsedqs = None - if qsenviron: - parsedqs = urllib.parse.parse_qs(qsenviron) - if 'VARHELP' in cgidata: - varhelp = cgidata['VARHELP'][0] - elif parsedqs: - # POST methods, even if their actions have a query string, don't get - # put into FieldStorage's keys :-( - qs = parsedqs.get('VARHELP') - if qs and isinstance(qs, list): - varhelp = qs[0] - if varhelp: - option_help(mlist, varhelp) - return + # The html page document + doc = Document() + doc.set_language(mlist.preferred_language) - doc = Document() - doc.set_language(mlist.preferred_language) - form = Form(mlist=mlist, contexts=AUTH_CONTEXTS) + # From this point on, the MailList object must be locked. However, we + # must release the lock no matter how we exit. try/finally isn't enough, + # because of this scenario: user hits the admin 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. + # + # BAW: This may not be portable to other web servers or cgi execution + # models. + def sigterm_handler(signum, frame, mlist=mlist): + # Make sure the list gets unlocked... + 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) - # From this point on, the MailList object must be locked - mlist.Lock() - try: - # Install the emergency shutdown signal handler - def sigterm_handler(signum, frame, mlist=mlist): - # Make sure the list gets unlocked... - mlist.Unlock() - # ...and ensure we exit - sys.exit(0) - signal.signal(signal.SIGTERM, sigterm_handler) - - if cgidata: - if csrf_checked: - # There are options to change - change_options(mlist, category, subcat, cgidata, doc) - else: - doc.addError( - _('The form lifetime has expired. (request forgery check)')) - # Let the list sanity check the changed values - mlist.CheckValues() + mlist.Lock() + try: + # Install the emergency shutdown signal handler + signal.signal(signal.SIGTERM, sigterm_handler) - # Additional sanity checks - if not mlist.digestable and not mlist.nondigestable: - doc.addError( - _(f'''You have turned off delivery of both digest and - non-digest messages. This is an incompatible state of - affairs. You must turn on either digest delivery or - non-digest delivery or your mailing list will basically be - unusable.'''), tag=_('Warning: ')) - - dm = mlist.getDigestMemberKeys() - if not mlist.digestable and dm: - doc.addError( - _(f'''You have digest members, but digests are turned - off. Those people will not receive mail. - Affected member(s) %(dm)r.'''), - tag=_('Warning: ')) - rm = mlist.getRegularMemberKeys() - if not mlist.nondigestable and rm: + if list(cgidata.keys()): + if csrf_checked: + # There are options to change + change_options(mlist, category, subcat, cgidata, doc) + else: doc.addError( - _(f'''You have regular list members but non-digestified mail is - turned off. They will receive non-digestified mail until you - fix this problem. Affected member(s) %(rm)r.'''), - tag=_('Warning: ')) - - # Show the results page - show_results(mlist, doc, category, subcat, cgidata) - print(doc.Format()) - mlist.Save() - finally: - # Now be sure to unlock the list - mlist.Unlock() - except Exception as e: - doc = Document() - doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) - doc.AddItem(Header(2, _("Error"))) - doc.AddItem(Bold(_('An unexpected error occurred.'))) - doc.AddItem(Preformatted(Utils.websafe(str(e)))) - doc.AddItem(Preformatted(Utils.websafe(traceback.format_exc()))) - print('Status: 500 Internal Server Error') + _('The form lifetime has expired. (request forgery check)')) + # Let the list sanity check the changed values + mlist.CheckValues() + # Additional sanity checks + if not mlist.digestable and not mlist.nondigestable: + doc.addError( + _(f'''You have turned off delivery of both digest and + non-digest messages. This is an incompatible state of + affairs. You must turn on either digest delivery or + non-digest delivery or your mailing list will basically be + unusable.'''), tag=_('Warning: ')) + + dm = mlist.getDigestMemberKeys() + if not mlist.digestable and dm: + doc.addError( + _(f'''You have digest members, but digests are turned + off. Those people will not receive mail. + Affected member(s) %(dm)r.'''), + tag=_('Warning: ')) + rm = mlist.getRegularMemberKeys() + if not mlist.nondigestable and rm: + doc.addError( + _(f'''You have regular list members but non-digestified mail is + turned off. They will receive non-digestified mail until you + fix this problem. Affected member(s) %(rm)r.'''), + tag=_('Warning: ')) + # Glom up the results page and print it out + show_results(mlist, doc, category, subcat, cgidata) print(doc.Format()) - mailman_log('error', 'admin: Unexpected error: %s\n%s', str(e), traceback.format_exc()) + mlist.Save() + finally: + # Now be sure to unlock the list. It's okay if we get a signal here + # because essentially, the signal handler will do the same thing. And + # unlocking is unconditional, so it's not an error if we unlock while + # we're already unlocked. + mlist.Unlock() + + def admin_overview(msg=''): # Show the administrative overview page, with the list of all the lists on # this host. msg is an optional error message to display at the top of @@ -316,11 +260,7 @@ def admin_overview(msg=''): # This page should be displayed in the server's default language, which # should have already been set. hostname = Utils.get_domain() - if isinstance(hostname, bytes): - hostname = hostname.decode('latin1', 'replace') - legend = _('%(hostname)s mailing lists - Admin Links') % { - 'hostname': hostname - } + legend = _(f'{hostname} mailing lists - Admin Links') # The html `document' doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) @@ -336,31 +276,21 @@ def admin_overview(msg=''): listnames.sort() for name in listnames: - if isinstance(name, bytes): - name = name.decode('latin1', 'replace') try: mlist = MailList.MailList(name, lock=0) except Errors.MMUnknownListError: # The list could have been deleted by another process. continue if mlist.advertised: - real_name = mlist.real_name - if isinstance(real_name, bytes): - real_name = real_name.decode('latin1', 'replace') - description = mlist.GetDescription() - if isinstance(description, bytes): - description = description.decode('latin1', 'replace') if mm_cfg.VIRTUAL_HOST_OVERVIEW and ( - mlist.web_page_url.find('/%(hostname)s/' % {'hostname': hostname}) == -1 and - mlist.web_page_url.find('/%(hostname)s:' % {'hostname': hostname}) == -1): + mlist.web_page_url.find('/%s/' % hostname) == -1 and + mlist.web_page_url.find('/%s:' % hostname) == -1): # List is for different identity of this host - skip it. continue else: advertised.append((mlist.GetScriptURL('admin'), - real_name, - Utils.websafe(description))) - mlist.Unlock() - + mlist.real_name, + Utils.websafe(mlist.GetDescription()))) # Greeting depends on whether there was an error or not if msg: greeting = FontAttr(msg, color="ff5060", size="+1") @@ -372,34 +302,32 @@ def admin_overview(msg=''): if not advertised: welcome.extend([ greeting, - _('

      There currently are no publicly-advertised %(mailmanlink)s mailing lists on %(hostname)s.') % { - 'mailmanlink': mailmanlink, - 'hostname': hostname - }, + _(f'''

      There currently are no publicly-advertised {mailmanlink} + mailing lists on {hostname}.'''), ]) else: welcome.extend([ greeting, - _('

      Below is the collection of publicly-advertised %(mailmanlink)s mailing lists on %(hostname)s. Click on a list name to visit the configuration pages for that list.') % { - 'mailmanlink': mailmanlink, - 'hostname': hostname - }, + _(f'''

      Below is the collection of publicly-advertised + {mailmanlink} mailing lists on {hostname}. Click on a list + name to visit the configuration pages for that list.'''), ]) creatorurl = Utils.ScriptURL('create') mailman_owner = Utils.get_site_email() extra = msg and _('right ') or '' welcome.extend([ - _('To visit the administrators configuration page for an unadvertised list, open a URL similar to this one, but with a \'/\' and the %(extra)slist name appended. If you have the proper authority, you can also create a new mailing list.') % { - 'extra': extra, - 'creatorurl': creatorurl - }, - _('

      General list information can be found at '), + _(f'''To visit the administrators configuration page for an + unadvertised list, open a URL similar to this one, but with a '/' and + the {extra}list name appended. If you have the proper authority, + you can also create a new mailing list. + +

      General list information can be found at '''), Link(Utils.ScriptURL('listinfo'), _('the mailing list overview page')), '.', _('

      (Send questions and comments to '), - Link('mailto:%(mailman_owner)s' % {'mailman_owner': mailman_owner}, mailman_owner), + Link('mailto:%s' % mailman_owner, mailman_owner), '.)

      ', ]) @@ -426,6 +354,8 @@ def admin_overview(msg=''): doc.AddItem(MailmanLogo()) print(doc.Format()) + + def option_help(mlist, varhelp): # The html page document doc = Document() @@ -434,7 +364,7 @@ def option_help(mlist, varhelp): item = None reflist = varhelp.split('/') if len(reflist) >= 2: - category, subcat = None, None + category = subcat = None if len(reflist) == 2: category, varname = reflist elif len(reflist) == 3: @@ -498,159 +428,166 @@ def option_help(mlist, varhelp): doc.AddItem(mlist.GetMailmanFooter()) print(doc.Format()) -def add_standard_headers(doc, mlist, title, category, subcat): - """Add standard headers to admin pages. - - Args: - doc: The Document object - mlist: The MailList object - title: The page title - category: Optional category name - subcat: Optional subcategory name - """ - # Set the page title - doc.SetTitle(title) - - # Add the main header - doc.AddItem(Header(2, title)) - - # Add navigation breadcrumbs if category/subcat provided - breadcrumbs = [] - breadcrumbs.append(Link(mlist.GetScriptURL('admin'), _('%(realname)s administrative interface'))) - if category: - breadcrumbs.append(Link(mlist.GetScriptURL('admin') + '/' + category, _(category))) - if subcat: - breadcrumbs.append(Link(mlist.GetScriptURL('admin') + '/' + category + '/' + subcat, _(subcat))) - # Convert each breadcrumb item to a string before joining - breadcrumbs = [str(item) for item in breadcrumbs] - doc.AddItem(Center(' > '.join(breadcrumbs))) - - # Add horizontal rule - doc.AddItem('


      ') + def show_results(mlist, doc, category, subcat, cgidata): # Produce the results page adminurl = mlist.GetScriptURL('admin') categories = mlist.GetConfigCategories() label = _(categories[category][0]) - if isinstance(label, bytes): - label = label.decode(Utils.GetCharSet(mlist.preferred_language), 'replace') - - # Add standard headers - title = _('%(realname)s Administration (%(label)s)') % { - 'realname': mlist.real_name, - 'label': label - } - add_standard_headers(doc, mlist, title, category, subcat) - - # Create a table for configuration categories - cat_table = Table(border=0, width='100%') - cat_table.AddRow([Center(Header(2, _('Configuration Categories')))]) - cat_table.AddCellInfo(cat_table.GetCurrentRowIndex(), 0, colspan=2, - bgcolor=mm_cfg.WEB_HEADER_COLOR) - - # Add category links - for cat in categories.keys(): - cat_label = _(categories[cat][0]) - if isinstance(cat_label, bytes): - cat_label = cat_label.decode(Utils.GetCharSet(mlist.preferred_language), 'replace') - url = '%s/%s' % (adminurl, cat) - - # Get subcategories if they exist - subcats = mlist.GetConfigSubCategories(cat) - if subcats: - # Create a container for the category and its subcategories - container = Container() - if cat == category: - container.AddItem(Bold(Link(url, cat_label))) - else: - container.AddItem(Link(url, cat_label)) - - # Add subcategory links - subcat_list = UnorderedList() - for subcat_name, subcat_label in subcats: - subcat_url = '%s/%s/%s' % (adminurl, cat, subcat_name) - if isinstance(subcat_label, bytes): - subcat_label = subcat_label.decode(Utils.GetCharSet(mlist.preferred_language), 'replace') - if cat == category and subcat_name == subcat: - subcat_list.AddItem(Bold(Link(subcat_url, subcat_label))) - else: - subcat_list.AddItem(Link(subcat_url, subcat_label)) - container.AddItem(subcat_list) - cat_table.AddRow([container]) - else: - # No subcategories, just add the category link - if cat == category: - cat_table.AddRow([Bold(Link(url, cat_label))]) - else: - cat_table.AddRow([Link(url, cat_label)]) - - doc.AddItem(cat_table) + + # Set up the document's headers + realname = mlist.real_name + doc.SetTitle(_(f'{realname} Administration ({label})')) + doc.AddItem(Center(Header(2, _( + f'{realname} mailing list administration
      {label} Section')))) doc.AddItem('
      ') - - # Use ParseTags for the main content - replacements = { - 'realname': mlist.real_name, - 'label': label, - 'adminurl': adminurl, - 'admindburl': mlist.GetScriptURL('admindb'), - 'listinfourl': mlist.GetScriptURL('listinfo'), - 'edithtmlurl': mlist.GetScriptURL('edithtml'), - 'archiveurl': mlist.GetBaseArchiveURL(), - 'rmlisturl': mlist.GetScriptURL('rmlist') if mm_cfg.OWNERS_CAN_DELETE_THEIR_OWN_LISTS and mlist.internal_name() != mm_cfg.MAILMAN_SITE_LIST else None - } - - # Ensure all replacements are properly encoded for the list's language - for key, value in replacements.items(): - if isinstance(value, bytes): - replacements[key] = value.decode(Utils.GetCharSet(mlist.preferred_language), 'replace') - - output = mlist.ParseTags('admin_results.html', replacements, mlist.preferred_language) - doc.AddItem(output) - - # Now we need to craft the form that will be submitted + # Now we need to craft the form that will be submitted, which will contain + # all the variable settings, etc. This is a bit of a kludge because we + # know that the autoreply and members categories supports file uploads. encoding = None if category in ('autoreply', 'members'): encoding = 'multipart/form-data' if subcat: - form = Form('%(adminurl)s/%(category)s/%(subcat)s' % { - 'adminurl': adminurl, - 'category': category, - 'subcat': subcat - }, encoding=encoding, mlist=mlist, contexts=AUTH_CONTEXTS) + form = Form('%s/%s/%s' % (adminurl, category, subcat), + encoding=encoding, mlist=mlist, contexts=AUTH_CONTEXTS) else: - form = Form('%(adminurl)s/%(category)s' % { - 'adminurl': adminurl, - 'category': category - }, encoding=encoding, mlist=mlist, contexts=AUTH_CONTEXTS) - - # Add the form content based on category + form = Form('%s/%s' % (adminurl, category), + encoding=encoding, mlist=mlist, contexts=AUTH_CONTEXTS) + # This holds the two columns of links + linktable = Table(valign='top', width='100%') + linktable.AddRow([Center(Bold(_("Configuration Categories"))), + Center(Bold(_("Other Administrative Activities")))]) + # The `other links' are stuff in the right column. + otherlinks = UnorderedList() + otherlinks.AddItem(Link(mlist.GetScriptURL('admindb'), + _('Tend to pending moderator requests'))) + otherlinks.AddItem(Link(mlist.GetScriptURL('listinfo'), + _('Go to the general list information page'))) + otherlinks.AddItem(Link(mlist.GetScriptURL('edithtml'), + _('Edit the public HTML pages and text files'))) + otherlinks.AddItem(Link(mlist.GetBaseArchiveURL(), + _('Go to list archives')).Format() + + '
       
      ') + # We do not allow through-the-web deletion of the site list! + if mm_cfg.OWNERS_CAN_DELETE_THEIR_OWN_LISTS and \ + mlist.internal_name() != mm_cfg.MAILMAN_SITE_LIST: + otherlinks.AddItem(Link(mlist.GetScriptURL('rmlist'), + _('Delete this mailing list')).Format() + + _(' (requires confirmation)
       
      ')) + otherlinks.AddItem(Link('%s/logout' % adminurl, + # BAW: What I really want is a blank line, but + # adding an   won't do it because of the + # bullet added to the list item. + '%s' % + _('Logout'))) + # These are links to other categories and live in the left column + categorylinks_1 = categorylinks = UnorderedList() + categorylinks_2 = '' + categorykeys = list(categories.keys()) + half = len(categorykeys) / 2 + counter = 0 + subcat = None + for k in categorykeys: + label = _(categories[k][0]) + url = '%s/%s' % (adminurl, k) + if k == category: + # Handle subcategories + subcats = mlist.GetConfigSubCategories(k) + if subcats: + subcat = Utils.GetPathPieces()[-1] + for k, v in subcats: + if k == subcat: + break + else: + # The first subcategory in the list is the default + subcat = subcats[0][0] + subcat_items = [] + for sub, text in subcats: + if sub == subcat: + text = Bold('[%s]' % text).Format() + subcat_items.append(Link(url + '/' + sub, text)) + categorylinks.AddItem( + Bold(label).Format() + + UnorderedList(*subcat_items).Format()) + else: + categorylinks.AddItem(Link(url, Bold('[%s]' % label))) + else: + categorylinks.AddItem(Link(url, label)) + counter += 1 + if counter >= half: + categorylinks_2 = categorylinks = UnorderedList() + counter = -len(categorykeys) + # Make the emergency stop switch a rude solo light + etable = Table() + # Add all the links to the links table... + etable.AddRow([categorylinks_1, categorylinks_2]) + etable.AddRowInfo(etable.GetCurrentRowIndex(), valign='top') + if mlist.emergency: + label = _('Emergency moderation of all list traffic is enabled') + etable.AddRow([Center( + Link('?VARHELP=general/emergency', Bold(label)))]) + color = mm_cfg.WEB_ERROR_COLOR + etable.AddCellInfo(etable.GetCurrentRowIndex(), 0, + colspan=2, bgcolor=color) + linktable.AddRow([etable, otherlinks]) + # ...and add the links table to the document. + form.AddItem(linktable) + form.AddItem('
      ') + form.AddItem( + _(f'''Make your changes in the following section, then submit them + using the Submit Your Changes button below.''') + + '

      ') + + # The members and passwords categories are special in that they aren't + # defined in terms of gui elements. Create those pages here. if category == 'members': + # Figure out which subcategory we should display + subcat = Utils.GetPathPieces()[-1] + if subcat not in ('list', 'add', 'remove', 'change', 'sync'): + subcat = 'list' + # Add member category specific tables form.AddItem(membership_options(mlist, subcat, cgidata, doc, form)) form.AddItem(Center(submit_button('setmemberopts_btn'))) + # In "list" subcategory, we can also search for members + if subcat == 'list': + form.AddItem('


      \n') + table = Table(width='100%') + table.AddRow([Center(Header(2, _('Additional Member Tasks')))]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, + bgcolor=mm_cfg.WEB_HEADER_COLOR) + # Add a blank separator row + table.AddRow([' ', ' ']) + # Add a section to set the moderation bit for all members + table.AddRow([_(f"""
    • Set everyone's moderation bit, including + those members not currently visible""")]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) + table.AddRow([RadioButtonArray('allmodbit_val', + (_('Off'), _('On')), + mlist.default_member_moderation), + SubmitButton('allmodbit_btn', _('Set'))]) + form.AddItem(table) elif category == 'passwords': form.AddItem(Center(password_inputs(mlist))) form.AddItem(Center(submit_button())) else: form.AddItem(show_variables(mlist, category, subcat, cgidata, doc)) form.AddItem(Center(submit_button())) - - # Add the form to the document + # And add the form doc.AddItem(form) doc.AddItem(mlist.GetMailmanFooter()) + + def show_variables(mlist, category, subcat, cgidata, doc): - # Get the configuration info options = mlist.GetConfigInfo(category, subcat) - + # The table containing the results table = Table(cellspacing=3, cellpadding=4, width='100%') # Get and portray the text label for the category. categories = mlist.GetConfigCategories() label = _(categories[category][0]) - if isinstance(label, bytes): - label = label.decode(Utils.GetCharSet(mlist.preferred_language), 'replace') table.AddRow([Center(Header(2, label))]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, @@ -659,9 +596,7 @@ def show_variables(mlist, category, subcat, cgidata, doc): # The very first item in the config info will be treated as a general # description if it is a string description = options[0] - if isinstance(description, bytes): - description = description.decode(Utils.GetCharSet(mlist.preferred_language), 'replace') - if isinstance(description, str): + if type(description) is str: table.AddRow([description]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) options = options[1:] @@ -678,14 +613,10 @@ def show_variables(mlist, category, subcat, cgidata, doc): width='85%') for item in options: - if isinstance(item, str): + if type(item) == str: # The very first banner option (string in an options list) is # treated as a general description, while any others are # treated as section headers - centered and italicized... - if isinstance(item, bytes): - item = item.decode(Utils.GetCharSet(mlist.preferred_language), 'replace') - formatted_text = '[%s]' % item - item = Bold(formatted_text).Format() table.AddRow([Center(Italic(item))]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) else: @@ -694,21 +625,25 @@ def show_variables(mlist, category, subcat, cgidata, doc): table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) return table + + def add_options_table_item(mlist, category, subcat, table, item, detailsp=1): # Add a row to an options table with the item description and value. varname, kind, params, extra, descr, elaboration = \ get_item_characteristics(item) + if elaboration is None: + elaboration = descr descr = get_item_gui_description(mlist, category, subcat, varname, descr, elaboration, detailsp) val = get_item_gui_value(mlist, category, kind, varname, params, extra) table.AddRow([descr, val]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, - style=f'background-color: {mm_cfg.WEB_ADMINITEM_COLOR}', - role='cell') + bgcolor=mm_cfg.WEB_ADMINITEM_COLOR) table.AddCellInfo(table.GetCurrentRowIndex(), 1, - style=f'background-color: {mm_cfg.WEB_ADMINITEM_COLOR}', - role='cell') + bgcolor=mm_cfg.WEB_ADMINITEM_COLOR) + + def get_item_characteristics(record): # Break out the components of an item description from its description # record: @@ -728,13 +663,13 @@ def get_item_characteristics(record): raise ValueError(f'Badly formed options entry:\n {record}') return varname, kind, params, dependancies, descr, elaboration + + def get_item_gui_value(mlist, category, kind, varname, params, extra): """Return a representation of an item's settings.""" # Give the category a chance to return the value for the variable value = None - category_data = mlist.GetConfigCategories()[category] - if isinstance(category_data, tuple): - gui = category_data[1] + label, gui = mlist.GetConfigCategories()[category] if hasattr(gui, 'getValue'): value = gui.getValue(mlist, kind, varname, params) # Filter out None, and volatile attributes @@ -762,27 +697,18 @@ def get_item_gui_value(mlist, category, kind, varname, params, extra): return RadioButtonArray(varname, params, checked, not extra) elif (kind == mm_cfg.String or kind == mm_cfg.Email or kind == mm_cfg.Host or kind == mm_cfg.Number): - # Ensure value is a string, decoding bytes if necessary - if isinstance(value, bytes): - value = value.decode(Utils.GetCharSet(mlist.preferred_language), 'replace') return TextBox(varname, value, params) elif kind == mm_cfg.Text: if params: r, c = params else: r, c = None, None - # Ensure value is a string, decoding bytes if necessary - if isinstance(value, bytes): - value = value.decode(Utils.GetCharSet(mlist.preferred_language), 'replace') return TextArea(varname, value or '', r, c) elif kind in (mm_cfg.EmailList, mm_cfg.EmailListEx): if params: r, c = params else: r, c = None, None - # Ensure value is a string, decoding bytes if necessary - if isinstance(value, bytes): - value = value.decode(Utils.GetCharSet(mlist.preferred_language), 'replace') res = NL.join(value) return TextArea(varname, res, r, c, wrap='off') elif kind == mm_cfg.FileUpload: @@ -801,8 +727,8 @@ def get_item_gui_value(mlist, category, kind, varname, params, extra): if params: values, legend, selected = params else: - values = mlist.available_languages - legend = [Utils.GetLanguageDescr(lang) for lang in values] + values = mlist.GetAvailableLanguages() + legend = list(map(_, list(map(Utils.GetLanguageDescr, values)))) selected = values.index(mlist.preferred_language) return SelectOptions(varname, values, legend, selected) elif kind == mm_cfg.Topics: @@ -820,13 +746,11 @@ def makebox(i, name, pattern, desc, empty=False, table=table): addtag = 'topic_add_%02d' % i newtag = 'topic_new_%02d' % i if empty: - topic_text = _('Topic %(i)d') % {'i': i} - table.AddRow([Center(Bold(topic_text)), - Hidden(newtag)]) + table.AddRow([Center(Bold(_('Topic %(i)d'))), + Hidden(newtag)]) else: - topic_text = _('Topic %(i)d') % {'i': i} - table.AddRow([Center(Bold(topic_text)), - SubmitButton(deltag, _('Delete'))]) + table.AddRow([Center(Bold(_('Topic %(i)d'))), + SubmitButton(deltag, _('Delete'))]) table.AddRow([Label(_('Topic name:')), TextBox(boxtag, value=name, size=30)]) table.AddRow([Label(_('Regexp:')), @@ -843,17 +767,11 @@ def makebox(i, name, pattern, desc, empty=False, table=table): selected=1), ]) table.AddRow(['
      ']) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, role='cell') + table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) # Now for each element in the existing data, create a widget i = 1 data = getattr(mlist, varname) for name, pattern, desc, empty in data: - if isinstance(name, bytes): - name = name.decode(Utils.GetCharSet(mlist.preferred_language), 'replace') - if isinstance(pattern, bytes): - pattern = pattern.decode(Utils.GetCharSet(mlist.preferred_language), 'replace') - if isinstance(desc, bytes): - desc = desc.decode(Utils.GetCharSet(mlist.preferred_language), 'replace') makebox(i, name, pattern, desc, empty) i += 1 # Add one more non-deleteable widget as the first blank entry, but @@ -878,36 +796,46 @@ def makebox(i, pattern, action, empty=False, table=table): uptag = 'hdrfilter_up_%02d' % i downtag = 'hdrfilter_down_%02d' % i if empty: - table.AddRow([Center(Bold(_('Spam Filter Rule %(i)d') % {'i': i})), + table.AddRow([Center(Bold(_('Spam Filter Rule %(i)d'))), Hidden(newtag)]) else: - table.AddRow([Center(Bold(_('Spam Filter Rule %(i)d') % {'i': i})), + table.AddRow([Center(Bold(_('Spam Filter Rule %(i)d'))), SubmitButton(deltag, _('Delete'))]) table.AddRow([Label(_('Spam Filter Regexp:')), TextArea(reboxtag, text=pattern, rows=4, cols=30, wrap='off')]) values = [mm_cfg.DEFER, mm_cfg.HOLD, mm_cfg.REJECT, mm_cfg.DISCARD, mm_cfg.ACCEPT] - legends = [_('Defer'), _('Hold'), _('Reject'), - _('Discard'), _('Accept')] - table.AddRow([Label(_('Action:')), - SelectOptions(actiontag, values, legends, - selected=values.index(action))]) + try: + checked = values.index(action) + except ValueError: + checked = 0 + radio = RadioButtonArray( + actiontag, + (_('Defer'), _('Hold'), _('Reject'), + _('Discard'), _('Accept')), + values=values, + checked=checked).Format() + table.AddRow([Label(_('Action:')), radio]) if not empty: - table.AddRow([SubmitButton(addtag, _('Add new rule...')), + table.AddRow([SubmitButton(addtag, _('Add new item...')), SelectOptions(wheretag, ('before', 'after'), (_('...before this one.'), _('...after this one.')), selected=1), ]) + # BAW: IWBNI we could disable the up and down buttons for the + # first and last item respectively, but it's not easy to know + # which is the last item, so let's not worry about that for + # now. + table.AddRow([SubmitButton(uptag, _('Move rule up')), + SubmitButton(downtag, _('Move rule down'))]) table.AddRow(['
      ']) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, role='cell') + table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) # Now for each element in the existing data, create a widget i = 1 data = getattr(mlist, varname) for pattern, action, empty in data: - if isinstance(pattern, bytes): - pattern = pattern.decode(Utils.GetCharSet(mlist.preferred_language), 'replace') makebox(i, pattern, action, empty) i += 1 # Add one more non-deleteable widget as the first blank entry, but @@ -920,96 +848,99 @@ def makebox(i, pattern, action, empty=False, table=table): else: assert 0, 'Bad gui widget type: %s' % kind + + def get_item_gui_description(mlist, category, subcat, varname, descr, elaboration, detailsp): # Return the item's description, with link to details. + # + # Details are not included if this is a VARHELP page, because that /is/ + # the details page! if detailsp: if subcat: - varhelp = '/?VARHELP=%(category)s/%(subcat)s/%(varname)s' % { - 'category': category, - 'subcat': subcat, - 'varname': varname - } + varhelp = '/?VARHELP=%s/%s/%s' % (category, subcat, varname) else: - varhelp = '/?VARHELP=%(category)s/%(varname)s' % { - 'category': category, - 'varname': varname - } + varhelp = '/?VARHELP=%s/%s' % (category, varname) if descr == elaboration: - linktext = _('
      (Edit %(varname)s)') % { - 'varname': varname - } + linktext = _(f'
      (Edit {varname})') else: - linktext = _('
      (Details for %(varname)s)') % { - 'varname': varname - } + linktext = _(f'
      (Details for {varname})') link = Link(mlist.GetScriptURL('admin') + varhelp, linktext).Format() - text = Label('%(descr)s %(link)s' % { - 'descr': descr, - 'link': link - }).Format() + text = Label('%s %s' % (descr, link)).Format() else: text = Label(descr).Format() if varname[0] == '_': - text += Label(_('
      Note: setting this value performs an immediate action but does not modify permanent state.')).Format() + text += Label(_(f'''
      Note: + setting this value performs an immediate action but does not modify + permanent state.''')).Format() return text + + def membership_options(mlist, subcat, cgidata, doc, form): # Show the main stuff adminurl = mlist.GetScriptURL('admin', absolute=1) container = Container() header = Table(width="100%") - - # Add standard headers based on subcat + # If we're in the list subcategory, show the membership list if subcat == 'add': - title = _('Mass Subscriptions') - elif subcat == 'remove': - title = _('Mass Removals') - elif subcat == 'change': - title = _('Address Change') - elif subcat == 'sync': - title = _('Sync Membership List') - else: - title = _('Membership List') - - add_standard_headers(doc, mlist, title, 'members', subcat) - + header.AddRow([Center(Header(2, _('Mass Subscriptions')))]) + header.AddCellInfo(header.GetCurrentRowIndex(), 0, colspan=2, + bgcolor=mm_cfg.WEB_HEADER_COLOR) + container.AddItem(header) + mass_subscribe(mlist, container) + return container + if subcat == 'remove': + header.AddRow([Center(Header(2, _('Mass Removals')))]) + header.AddCellInfo(header.GetCurrentRowIndex(), 0, colspan=2, + bgcolor=mm_cfg.WEB_HEADER_COLOR) + container.AddItem(header) + mass_remove(mlist, container) + return container + if subcat == 'change': + header.AddRow([Center(Header(2, _('Address Change')))]) + header.AddCellInfo(header.GetCurrentRowIndex(), 0, colspan=2, + bgcolor=mm_cfg.WEB_HEADER_COLOR) + container.AddItem(header) + address_change(mlist, container) + return container + if subcat == 'sync': + header.AddRow([Center(Header(2, _('Sync Membership List')))]) + header.AddCellInfo(header.GetCurrentRowIndex(), 0, colspan=2, + bgcolor=mm_cfg.WEB_HEADER_COLOR) + container.AddItem(header) + mass_sync(mlist, container) + return container + # Otherwise... + header.AddRow([Center(Header(2, _('Membership List')))]) + header.AddCellInfo(header.GetCurrentRowIndex(), 0, colspan=2, + bgcolor=mm_cfg.WEB_HEADER_COLOR) + container.AddItem(header) # Add a "search for member" button table = Table(width='100%') - link = Link('https://docs.python.org/3/library/re.html' + link = Link('https://docs.python.org/2/library/re.html' '#regular-expression-syntax', _('(help)')).Format() - table.AddRow([Label(_('Find member %(link)s:') % {'link': link}), + table.AddRow([Label(_(f'Find member {link}:')), TextBox('findmember', - value=cgidata.get('findmember', [''])[0]), + value=cgidata.getfirst('findmember', '')), SubmitButton('findmember_btn', _('Search...'))]) container.AddItem(table) container.AddItem('

      ') usertable = Table(width="90%", border='2') + # If there are more members than allowed by chunksize, then we split the + # membership up alphabetically. Otherwise just display them all. + chunksz = mlist.admin_member_chunksize # The email addresses had /better/ be ASCII, but might be encoded in the # database as Unicodes. - all = [] - for _m in mlist.getMembers(): - try: - # Verify the member still exists - mlist.getMemberName(_m) - # Decode the email address as latin-1 - if isinstance(_m, bytes): - _m = _m.decode('latin-1') - all.append(_m) - except Errors.NotAMemberError: - # Skip addresses that are no longer members - continue - all.sort(key=lambda x: x.lower()) + all = mlist.getMembers() + all.sort() # See if the query has a regular expression - regexp = cgidata.get('findmember', [''])[0] - if isinstance(regexp, bytes): - regexp = regexp.decode('latin1', 'replace') - regexp = regexp.strip() + regexp = cgidata.getfirst('findmember', '').strip() try: - if isinstance(regexp, bytes): - regexp = regexp.decode(Utils.GetCharSet(mlist.preferred_language)) + regexp = regexp.encode() + regexp = regexp.decode(Utils.GetCharSet(mlist.preferred_language)) except UnicodeDecodeError: # This is probably a non-ascii character and an English language # (ascii) list. Even if we didn't throw the UnicodeDecodeError, @@ -1021,27 +952,16 @@ def membership_options(mlist, subcat, cgidata, doc, form): try: cre = re.compile(regexp, re.IGNORECASE) except re.error: - doc.addError(_('Bad regular expression: %(regexp)s') % {'regexp': regexp}) + doc.addError(_('Bad regular expression: ') + regexp) else: # BAW: There's got to be a more efficient way of doing this! - names = [] - valid_members = [] - for addr in all: - try: - name = mlist.getMemberName(addr) or '' - if isinstance(name, bytes): - name = name.decode('latin-1', 'replace') - names.append(name) - valid_members.append(addr) - except Errors.NotAMemberError: - # Skip addresses that are no longer members - continue - all = [a for n, a in zip(names, valid_members) + names = [mlist.getMemberName(s) or '' for s in all] + all = [a for n, a in zip(names, all) if cre.search(n) or cre.search(a)] chunkindex = None bucket = None actionurl = None - if len(all) < mlist.admin_member_chunksize: + if len(all) < chunksz: members = all else: # Split them up alphabetically, and then split the alphabetical @@ -1064,14 +984,11 @@ def membership_options(mlist, subcat, cgidata, doc, form): if not bucket or bucket not in buckets: bucket = keys[0] members = buckets[bucket] - action = '%(adminurl)s/members?letter=%(bucket)s' % { - 'adminurl': adminurl, - 'bucket': bucket - } - if len(members) <= mlist.admin_member_chunksize: + action = adminurl + '/members?letter=%s' % bucket + if len(members) <= chunksz: form.set_action(action) else: - i, r = divmod(len(members), mlist.admin_member_chunksize) + i, r = divmod(len(members), chunksz) numchunks = i + (not not r * 1) # Now chunk them up chunkindex = 0 @@ -1082,23 +999,17 @@ def membership_options(mlist, subcat, cgidata, doc, form): chunkindex = 0 if chunkindex < 0 or chunkindex > numchunks: chunkindex = 0 - members = members[chunkindex*mlist.admin_member_chunksize:(chunkindex+1)*mlist.admin_member_chunksize] + members = members[chunkindex*chunksz:(chunkindex+1)*chunksz] # And set the action URL - form.set_action('%(action)s&chunk=%(chunkindex)s' % { - 'action': action, - 'chunkindex': chunkindex - }) + form.set_action(action + '&chunk=%s' % chunkindex) # So now members holds all the addresses we're going to display allcnt = len(all) if bucket: membercnt = len(members) - count_text = _('%(allcnt)d members total, %(membercnt)d shown') % { - 'allcnt': len(all), 'membercnt': len(members)} - usertable.AddRow([Center(Italic(count_text))]) + usertable.AddRow([Center(Italic(_( + f'{allcnt} members total, {membercnt} shown')))]) else: - usertable.AddRow([Center(Italic(_('%(allcnt)d members total') % { - 'allcnt': len(all) - }))]) + usertable.AddRow([Center(Italic(_(f'{allcnt} members total')))]) usertable.AddCellInfo(usertable.GetCurrentRowIndex(), usertable.GetCurrentCellIndex(), colspan=OPTCOLUMNS, @@ -1110,23 +1021,12 @@ def membership_options(mlist, subcat, cgidata, doc, form): findfrag = '' if regexp: findfrag = '&findmember=' + urllib.parse.quote(regexp) - url = '%(adminurl)s/members?letter=%(letter)s%(findfrag)s' % { - 'adminurl': adminurl, - 'letter': letter, - 'findfrag': findfrag - } - if isinstance(url, str): - url = url.encode(Utils.GetCharSet(mlist.preferred_language), - errors='ignore') + url = adminurl + '/members?letter=' + letter + findfrag if letter == bucket: - # Do this in two steps to get it to work properly with the - # translatable title. - formatted_text = '[%s]' % letter.upper() - text = Bold(formatted_text).Format() + show = Bold('[%s]' % letter.upper()).Format() else: - formatted_label = '[%s]' % letter.upper() - text = Link(url, Bold(formatted_label)).Format() - cells.append(text) + show = letter.upper() + cells.append(Link(url, show).Format()) joiner = ' '*2 + '\n' usertable.AddRow([Center(joiner.join(cells))]) usertable.AddCellInfo(usertable.GetCurrentRowIndex(), @@ -1147,20 +1047,9 @@ def membership_options(mlist, subcat, cgidata, doc, form): # Find the longest name in the list longest = 0 if members: - names = [] - for addr in members: - try: - name = mlist.getMemberName(addr) or '' - if isinstance(name, bytes): - name = name.decode('latin-1', 'replace') - if name: - names.append(name) - except Errors.NotAMemberError: - # Skip addresses that are no longer members - continue + names = [_f for _f in [mlist.getMemberName(s) for s in members] if _f] # Make the name field at least as long as the longest email address - if names: - longest = max([len(s) for s in names + members]) + longest = max([len(s) for s in names + members]) # Abbreviations for delivery status details ds_abbrevs = {MemberAdaptor.UNKNOWN : _('?'), MemberAdaptor.BYUSER : _('U'), @@ -1169,47 +1058,18 @@ def membership_options(mlist, subcat, cgidata, doc, form): } # Now populate the rows for addr in members: - try: - if isinstance(addr, bytes): - addr = addr.decode('latin-1') - qaddr = urllib.parse.quote(addr) - link = Link(mlist.GetOptionsURL(addr, obscure=1), - mlist.getMemberCPAddress(addr)) - fullname = mlist.getMemberName(addr) - if isinstance(fullname, bytes): - try: - # Try Latin-1 first since that's what we're seeing in the data - fullname = fullname.decode('latin-1', 'replace') - except UnicodeDecodeError: - # Fall back to UTF-8 if Latin-1 fails - fullname = fullname.decode('utf-8', 'replace') - # Remove any b'...' prefix if it exists - if fullname.startswith("b'") and fullname.endswith("'"): - fullname = fullname[2:-1] - fullname = Utils.uncanonstr(fullname, mlist.preferred_language) - name = TextBox('%(qaddr)s_realname' % {'qaddr': qaddr}, fullname, size=longest).Format() - cells = [Center(CheckBox('%(qaddr)s_unsub' % {'qaddr': qaddr}, 'off', 0).Format() + qaddr = urllib.parse.quote(addr) + link = Link(mlist.GetOptionsURL(addr, obscure=1), + mlist.getMemberCPAddress(addr)) + fullname = Utils.uncanonstr(mlist.getMemberName(addr), + mlist.preferred_language) + name = TextBox(qaddr + '_realname', fullname, size=longest).Format() + cells = [Center(CheckBox(qaddr + '_unsub', 'off', 0).Format() + '

      '), - link.Format() + '
      ' + - name + - Hidden('user', qaddr).Format(), - ] - except Errors.NotAMemberError: - # Skip addresses that are no longer members - continue - - digest_name = '%(qaddr)s_digest' % {'qaddr': qaddr} - if addr not in mlist.getRegularMemberKeys(): - cells.append(Center(CheckBox(digest_name, 'off', 0).Format())) - else: - cells.append(Center(CheckBox(digest_name, 'on', 1).Format())) - - language_name = '%(qaddr)s_language' % {'qaddr': qaddr} - languages = mlist.available_languages - legends = [Utils.GetLanguageDescr(lang) for lang in languages] - cells.append(Center(SelectOptions(language_name, languages, legends, - selected=mlist.getMemberLanguage(addr)).Format())) - + link.Format() + '
      ' + + name + + Hidden('user', qaddr).Format(), + ] # Do the `mod' option if mlist.getMemberOption(addr, mm_cfg.Moderate): value = 'on' @@ -1217,7 +1077,7 @@ def membership_options(mlist, subcat, cgidata, doc, form): else: value = 'off' checked = 0 - box = CheckBox('%(qaddr)s_mod' % {'qaddr': qaddr}, value, checked) + box = CheckBox('%s_mod' % qaddr, value, checked) cells.append(Center(box.Format() + '')) # Kluge, get these translated. @@ -1232,29 +1092,59 @@ def membership_options(mlist, subcat, cgidata, doc, form): else: value = 'on' checked = 1 - extra = '[%(abbrev)s]' % {'abbrev': ds_abbrevs[status]} + extra + extra = '[%s]' % ds_abbrevs[status] + extra elif mlist.getMemberOption(addr, mm_cfg.OPTINFO[opt]): value = 'on' checked = 1 else: value = 'off' checked = 0 - box = CheckBox('%(qaddr)s_%(opt)s' % {'qaddr': qaddr, 'opt': opt}, value, checked) + box = CheckBox('%s_%s' % (qaddr, opt), value, checked) cells.append(Center(box.Format() + extra)) + # This code is less efficient than the original which did a has_key on + # the underlying dictionary attribute. This version is slower and + # less memory efficient. It points to a new MemberAdaptor interface + # method. + extra = '' + if addr in mlist.getRegularMemberKeys(): + cells.append(Center(CheckBox(qaddr + '_digest', 'off', 0).Format() + + extra)) + else: + cells.append(Center(CheckBox(qaddr + '_digest', 'on', 1).Format() + + extra)) + if mlist.getMemberOption(addr, mm_cfg.OPTINFO['plain']): + value = 'on' + checked = 1 + else: + value = 'off' + checked = 0 + cells.append(Center(CheckBox( + '%s_plain' % qaddr, value, checked).Format() + + '')) + # User's preferred language + langpref = mlist.getMemberLanguage(addr) + langs = mlist.GetAvailableLanguages() + langdescs = [_(Utils.GetLanguageDescr(lang)) for lang in langs] + try: + selected = langs.index(langpref) + except ValueError: + selected = 0 + cells.append(Center(SelectOptions(qaddr + '_language', langs, + langdescs, selected)).Format()) usertable.AddRow(cells) # Add the usertable and a legend legend = UnorderedList() legend.AddItem( _('unsub -- Click on this to unsubscribe the member.')) legend.AddItem( - _('''mod -- The user's personal moderation flag. If this is + _(f"""mod -- The user's personal moderation flag. If this is set, postings from them will be moderated, otherwise they will be - approved.''')) + approved.""")) legend.AddItem( - _('''hide -- Is the member's address concealed on - the list of subscribers?''')) + _(f"""hide -- Is the member's address concealed on + the list of subscribers?""")) legend.AddItem(_( - '''nomail -- Is delivery to the member disabled? If so, an + """nomail -- Is delivery to the member disabled? If so, an abbreviation will be given describing the reason for the disabled delivery:
      • U -- Delivery was disabled by the user via their @@ -1266,21 +1156,21 @@ def membership_options(mlist, subcat, cgidata, doc, form):
      • ? -- The reason for disabled delivery isn't known. This is the case for all memberships which were disabled in older versions of Mailman. -
      ''')) +
    """)) legend.AddItem( - _('''ack -- Does the member get acknowledgements of their + _(f'''ack -- Does the member get acknowledgements of their posts?''')) legend.AddItem( - _('''not metoo -- Does the member want to avoid copies of their + _(f'''not metoo -- Does the member want to avoid copies of their own postings?''')) legend.AddItem( - _('''nodupes -- Does the member want to avoid duplicates of the + _(f'''nodupes -- Does the member want to avoid duplicates of the same message?''')) legend.AddItem( - _('''digest -- Does the member get messages in digests? + _(f'''digest -- Does the member get messages in digests? (otherwise, individual messages)''')) legend.AddItem( - _('''plain -- If getting digests, does the member get plain + _(f'''plain -- If getting digests, does the member get plain text digests? (otherwise, MIME)''')) legend.AddItem(_("language -- Language preferred by the user")) addlegend = '' @@ -1288,7 +1178,7 @@ def membership_options(mlist, subcat, cgidata, doc, form): qsenviron = os.environ.get('QUERY_STRING') if qsenviron: qs = urllib.parse.parse_qs(qsenviron).get('legend') - if qs and isinstance(qs, list): + if qs and type(qs) is list: qs = qs[0] if qs == 'yes': addlegend = 'legend=yes&' @@ -1306,38 +1196,25 @@ def membership_options(mlist, subcat, cgidata, doc, form): # There may be additional chunks if chunkindex is not None: buttons = [] - url = '%(adminurl)s/members?%(addlegend)sletter=%(bucket)s&' % { - 'adminurl': adminurl, - 'addlegend': addlegend, - 'bucket': bucket - } - footer = _('''

    To view more members, click on the appropriate + url = adminurl + '/members?%sletter=%s&' % (addlegend, bucket) + footer = _(f'''

    To view more members, click on the appropriate range listed below:''') chunkmembers = buckets[bucket] last = len(chunkmembers) for i in range(numchunks): if i == chunkindex: continue - start = chunkmembers[i*mlist.admin_member_chunksize] - end = chunkmembers[min((i+1)*mlist.admin_member_chunksize, last)-1] - thisurl = '%(url)schunk=%(i)d%(findfrag)s' % { - 'url': url, - 'i': i, - 'findfrag': findfrag - } - if isinstance(thisurl, str): - thisurl = thisurl.encode( - Utils.GetCharSet(mlist.preferred_language), - errors='ignore') - link = Link(thisurl, _('from %(start)s to %(end)s') % { - 'start': start, - 'end': end - }) + start = chunkmembers[i*chunksz] + end = chunkmembers[min((i+1)*chunksz, last)-1] + thisurl = url + 'chunk=%d' % i + findfrag + link = Link(thisurl, _(f'from {start} to {end}')) buttons.append(link) buttons = UnorderedList(*buttons) container.AddItem(footer + buttons.Format() + '

    ') return container + + def mass_subscribe(mlist, container): # MASS SUBSCRIBE GREY = mm_cfg.WEB_ADMINITEM_COLOR @@ -1365,7 +1242,7 @@ def mass_subscribe(mlist, container): RadioButtonArray('send_notifications_to_list_owner', (_('No'), _('Yes')), mlist.admin_notify_mchanges, - values=(0, 1)) + values=(0,1)) ]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, bgcolor=GREY) table.AddCellInfo(table.GetCurrentRowIndex(), 1, bgcolor=GREY) @@ -1387,6 +1264,8 @@ def mass_subscribe(mlist, container): rows=10, cols='70%', wrap=None))]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) + + def mass_remove(mlist, container): # MASS UNSUBSCRIBE GREY = mm_cfg.WEB_ADMINITEM_COLOR @@ -1417,6 +1296,8 @@ def mass_remove(mlist, container): FileUpload('unsubscribees_upload', cols='50')]) container.AddItem(Center(table)) + + def address_change(mlist, container): # ADDRESS CHANGE GREY = mm_cfg.WEB_ADMINITEM_COLOR @@ -1447,6 +1328,8 @@ def address_change(mlist, container): table.AddCellInfo(table.GetCurrentRowIndex(), 2, bgcolor=GREY) container.AddItem(Center(table)) + + def mass_sync(mlist, container): # MASS SYNC table = Table(width='90%') @@ -1459,6 +1342,8 @@ def mass_sync(mlist, container): FileUpload('memberlist_upload', cols='50')]) container.AddItem(Center(table)) + + def password_inputs(mlist): adminurl = mlist.GetScriptURL('admin', absolute=1) table = Table(cellspacing=3, cellpadding=4) @@ -1515,63 +1400,418 @@ def password_inputs(mlist): table.AddRow([ptable]) return table + + def submit_button(name='submit'): table = Table(border=0, cellspacing=0, cellpadding=2) table.AddRow([Bold(SubmitButton(name, _('Submit Your Changes')))]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, align='middle') return table + + def change_options(mlist, category, subcat, cgidata, doc): - """Change the list's options.""" - try: - # Get the configuration categories - config_categories = mlist.GetConfigCategories() - - # Validate category exists - if category not in config_categories: - mailman_log('error', 'Invalid configuration category: %s', category) - doc.AddItem(mlist.ParseTags('adminerror.html', - {'error': 'Invalid configuration category'}, - mlist.preferred_language)) - return - - # Get the category object and validate it - category_obj = config_categories[category] - - if not hasattr(category_obj, 'items'): - mailman_log('error', 'Configuration category %s is invalid: %s', - category, str(type(category_obj))) - doc.AddItem(mlist.ParseTags('adminerror.html', - {'error': 'Invalid configuration category structure'}, - mlist.preferred_language)) + global _ + def safeint(formvar, defaultval=None): + try: + return int(cgidata.getfirst(formvar)) + except (ValueError, TypeError): + return defaultval + confirmed = 0 + # Handle changes to the list moderator password. Do this before checking + # the new admin password, since the latter will force a reauthentication. + new = cgidata.getfirst('newmodpw', '').strip() + confirm = cgidata.getfirst('confirmmodpw', '').strip() + if new or confirm: + if new == confirm: + mlist.mod_password = sha_new(new.encode()).hexdigest() + # No re-authentication necessary because the moderator's + # password doesn't get you into these pages. + else: + doc.addError(_('Moderator passwords did not match')) + # Handle changes to the list poster password. Do this before checking + # the new admin password, since the latter will force a reauthentication. + new = cgidata.getfirst('newpostpw', '').strip() + confirm = cgidata.getfirst('confirmpostpw', '').strip() + if new or confirm: + if new == confirm: + mlist.post_password = sha_new(new.encode()).hexdigest() + # No re-authentication necessary because the poster's + # password doesn't get you into these pages. + else: + doc.addError(_('Poster passwords did not match')) + # Handle changes to the list administrator password + new = cgidata.getfirst('newpw', '').strip() + confirm = cgidata.getfirst('confirmpw', '').strip() + if new or confirm: + if new == confirm: + mlist.password = sha_new(new.encode()).hexdigest() + # Set new cookie + print(mlist.MakeCookie(mm_cfg.AuthListAdmin)) + else: + doc.addError(_('Administrator passwords did not match')) + # Give the individual gui item a chance to process the form data + categories = mlist.GetConfigCategories() + label, gui = categories[category] + # BAW: We handle the membership page special... for now. + if category != 'members': + gui.handleForm(mlist, category, subcat, cgidata, doc) + # mass subscription, removal processing for members category + subscribers = '' + subscribers += str(cgidata.getfirst('subscribees', '')) + sub_uploads = cgidata.getfirst('subscribees_upload', '') + if isinstance(sub_uploads, bytes): + sub_uploads = sub_uploads.decode() + subscribers += sub_uploads + if subscribers: + entries = [_f for _f in [n.strip() for n in subscribers.splitlines()] if _f] + send_welcome_msg = safeint('send_welcome_msg_to_this_batch', + mlist.send_welcome_msg) + send_admin_notif = safeint('send_notifications_to_list_owner', + mlist.admin_notify_mchanges) + # Default is to subscribe + subscribe_or_invite = safeint('subscribe_or_invite', 0) + invitation = cgidata.getfirst('invitation', '') + digest = mlist.digest_is_default + if not mlist.digestable: + digest = 0 + if not mlist.nondigestable: + digest = 1 + subscribe_errors = [] + subscribe_success = [] + # Now cruise through all the subscribees and do the deed. BAW: we + # should limit the number of "Successfully subscribed" status messages + # we display. Try uploading a file with 10k names -- it takes a while + # to render the status page. + for entry in entries: + safeentry = Utils.websafe(entry) + fullname, address = parseaddr(entry) + # Canonicalize the full name + fullname = Utils.canonstr(fullname, mlist.preferred_language) + userdesc = UserDesc(address, fullname, + Utils.MakeRandomPassword(), + digest, mlist.preferred_language) + try: + if subscribe_or_invite: + if mlist.isMember(address): + raise Errors.MMAlreadyAMember + else: + mlist.InviteNewMember(userdesc, invitation) + else: + _ = D_ + whence = _('admin mass sub') + _ = i18n._ + mlist.ApprovedAddMember(userdesc, send_welcome_msg, + send_admin_notif, invitation, + whence=whence) + except Errors.MMAlreadyAMember: + subscribe_errors.append((safeentry, _('Already a member'))) + except Errors.MMBadEmailError: + if userdesc.address == '': + subscribe_errors.append((_('<blank line>'), + _('Bad/Invalid email address'))) + else: + subscribe_errors.append((safeentry, + _('Bad/Invalid email address'))) + except Errors.MMHostileAddress: + subscribe_errors.append( + (safeentry, _('Hostile address (illegal characters)'))) + except Errors.MembershipIsBanned as pattern: + subscribe_errors.append( + (safeentry, _(f'Banned address (matched {pattern})'))) + else: + member = Utils.uncanonstr(formataddr((fullname, address))) + subscribe_success.append(Utils.websafe(member)) + if subscribe_success: + if subscribe_or_invite: + doc.AddItem(Header(5, _('Successfully invited:'))) + else: + doc.AddItem(Header(5, _('Successfully subscribed:'))) + doc.AddItem(UnorderedList(*subscribe_success)) + doc.AddItem('

    ') + if subscribe_errors: + if subscribe_or_invite: + doc.AddItem(Header(5, _('Error inviting:'))) + else: + doc.AddItem(Header(5, _('Error subscribing:'))) + items = ['%s -- %s' % (x0, x1) for x0, x1 in subscribe_errors] + doc.AddItem(UnorderedList(*items)) + doc.AddItem('

    ') + # Unsubscriptions + removals = '' + if 'unsubscribees' in cgidata: + removals += cgidata['unsubscribees'].value + if 'unsubscribees_upload' in cgidata and \ + cgidata['unsubscribees_upload'].value: + unsub_upload = cgidata['unsubscribees_upload'].value + if isinstance(unsub_upload, bytes): + unsub_upload = unsub_upload.decode() + removals += unsub_upload + if removals: + names = [_f for _f in [n.strip() for n in removals.splitlines()] if _f] + send_unsub_notifications = safeint( + 'send_unsub_notifications_to_list_owner', + mlist.admin_notify_mchanges) + userack = safeint( + 'send_unsub_ack_to_this_batch', + mlist.send_goodbye_msg) + unsubscribe_errors = [] + unsubscribe_success = [] + for addr in names: + try: + _ = D_ + whence = _('admin mass unsub') + _ = i18n._ + mlist.ApprovedDeleteMember( + addr, whence=whence, + admin_notif=send_unsub_notifications, + userack=userack) + unsubscribe_success.append(Utils.websafe(addr)) + except Errors.NotAMemberError: + unsubscribe_errors.append(Utils.websafe(addr)) + if unsubscribe_success: + doc.AddItem(Header(5, _('Successfully Unsubscribed:'))) + doc.AddItem(UnorderedList(*unsubscribe_success)) + doc.AddItem('

    ') + if unsubscribe_errors: + doc.AddItem(Header(3, Bold(FontAttr( + _('Cannot unsubscribe non-members:'), + color='#ff0000', size='+2')).Format())) + doc.AddItem(UnorderedList(*unsubscribe_errors)) + doc.AddItem('

    ') + # Address Changes + if 'change_from' in cgidata: + change_from = cgidata.getfirst('change_from', '') + change_to = cgidata.getfirst('change_to', '') + schange_from = Utils.websafe(change_from) + schange_to = Utils.websafe(change_to) + success = False + msg = None + if not (change_from and change_to): + msg = _('You must provide both current and new addresses.') + elif change_from == change_to: + msg = _('Current and new addresses must be different.') + elif mlist.isMember(change_to): + # ApprovedChangeMemberAddress will just delete the old address + # and we don't want that here. + msg = _(f'{schange_to} is already a list member.') + else: + try: + Utils.ValidateEmail(change_to) + except (Errors.MMBadEmailError, Errors.MMHostileAddress): + msg = _(f'{schange_to} is not a valid email address.') + if msg: + doc.AddItem(Header(3, msg)) + doc.AddItem('

    ') return - - # Process each item in the category - for item in category_obj.items: + try: + mlist.ApprovedChangeMemberAddress(change_from, change_to, False) + except Errors.NotAMemberError: + msg = _(f'{schange_from} is not a member') + except Errors.MMAlreadyAMember: + msg = _(f'{schange_to} is already a member') + except Errors.MembershipIsBanned as pat: + spat = Utils.websafe(str(pat)) + msg = _(f'{schange_to} matches banned pattern {spat}') + else: + msg = _(f'Address {schange_from} changed to {schange_to}') + success = True + doc.AddItem(Header(3, msg)) + lang = mlist.getMemberLanguage(change_to) + otrans = i18n.get_translation() + i18n.set_language(lang) + list_name = mlist.getListAddress() + text = Utils.wrap(_(f"""The member address {change_from} on the +{list_name} list has been changed to {change_to}. +""")) + subject = _(f'{list_name} address change notice.') + i18n.set_translation(otrans) + if success and cgidata.getfirst('notice_old', '') == 'yes': + # Send notice to old address. + msg = Message.UserNotification(change_from, + mlist.GetOwnerEmail(), + text=text, + subject=subject, + lang=lang + ) + msg.send(mlist) + doc.AddItem(Header(3, _(f'Notification sent to {schange_from}.'))) + if success and cgidata.getfirst('notice_new', '') == 'yes': + # Send notice to new address. + msg = Message.UserNotification(change_to, + mlist.GetOwnerEmail(), + text=text, + subject=subject, + lang=lang + ) + msg.send(mlist) + doc.AddItem(Header(3, _(f'Notification sent to {schange_to}.'))) + doc.AddItem('

    ') + + # sync operation + memberlist = '' + memberlist += cgidata.getvalue('memberlist', '') + upload = cgidata.getvalue('memberlist_upload', '') + if isinstance(upload, bytes): + upload = upload.decode() + memberlist += upload + if memberlist: + # Browsers will convert special characters in the text box to HTML + # entities. We need to fix those. + def i_to_c(mo): + # Convert a matched string of digits to the corresponding unicode. + return chr(int(mo.group(1))) + def clean_input(x): + # Strip leading/trailing whitespace and convert numeric HTML + # entities. + return re.sub(r'&#(\d+);', i_to_c, x.strip()) + entries = [_f for _f in [clean_input(n) for n in memberlist.splitlines()] if _f] + lc_addresses = [parseaddr(x)[1].lower() for x in entries + if parseaddr(x)[1]] + subscribe_errors = [] + subscribe_success = [] + # First we add all the addresses that should be added to the list. + for entry in entries: + safeentry = Utils.websafe(entry) + fullname, address = parseaddr(entry) + if mlist.isMember(address): + continue + # Canonicalize the full name. + fullname = Utils.canonstr(fullname, mlist.preferred_language) + userdesc = UserDesc(address, fullname, + Utils.MakeRandomPassword(), + 0, mlist.preferred_language) try: - # Get the item's value from the form data - value = cgidata.get(item.name, None) - if value is None: - continue - - # Set the item's value - item.set(mlist, value) - - except Exception as e: - mailman_log('error', 'Error setting %s.%s: %s', - category, item.name, str(e)) - doc.AddItem(mlist.ParseTags('adminerror.html', - {'error': 'Error setting %s: %s' % - (item.name, str(e))}, - mlist.preferred_language)) - return - - # Save the changes - mlist.Save() - - except Exception as e: - mailman_log('error', 'Error in change_options: %s\n%s', - str(e), traceback.format_exc()) - doc.AddItem(mlist.ParseTags('adminerror.html', - {'error': 'Internal error: %s' % str(e)}, - mlist.preferred_language)) + # Add a member if not yet member. + mlist.ApprovedAddMember(userdesc, 0, 0, 0, + whence='admin sync members') + except Errors.MMBadEmailError: + if userdesc.address == '': + subscribe_errors.append((_('<blank line>'), + _('Bad/Invalid email address'))) + else: + subscribe_errors.append((safeentry, + _('Bad/Invalid email address'))) + except Errors.MMHostileAddress: + subscribe_errors.append( + (safeentry, _('Hostile address (illegal characters)'))) + except Errors.MembershipIsBanned as pattern: + subscribe_errors.append( + (safeentry, _(f'Banned address (matched {pattern})'))) + else: + member = Utils.uncanonstr(formataddr((fullname, address))) + subscribe_success.append(Utils.websafe(member)) + + # Then we remove the addresses not in our list. + unsubscribe_errors = [] + unsubscribe_success = [] + + for entry in mlist.getMembers(): + # If an entry is not found in the uploaded "entries" list, then + # remove the member. + if not(entry in lc_addresses): + try: + mlist.ApprovedDeleteMember(entry, 0, 0) + 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(entry) + else: + unsubscribe_success.append(Utils.websafe(entry)) + + if subscribe_success: + doc.AddItem(Header(5, _('Successfully subscribed:'))) + doc.AddItem(UnorderedList(*subscribe_success)) + doc.AddItem('

    ') + if subscribe_errors: + doc.AddItem(Header(5, _('Error subscribing:'))) + items = ['%s -- %s' % (x0, x1) for x0, x1 in subscribe_errors] + doc.AddItem(UnorderedList(*items)) + doc.AddItem('

    ') + if unsubscribe_success: + doc.AddItem(Header(5, _('Successfully Unsubscribed:'))) + doc.AddItem(UnorderedList(*unsubscribe_success)) + doc.AddItem('

    ') + + # See if this was a moderation bit operation + if 'allmodbit_btn' in cgidata: + val = safeint('allmodbit_val') + if val not in (0, 1): + doc.addError(_('Bad moderation flag value')) + else: + for member in mlist.getMembers(): + mlist.setMemberOption(member, mm_cfg.Moderate, val) + # do the user options for members category + if 'setmemberopts_btn' in cgidata and 'user' in cgidata: + user = cgidata['user'] + if type(user) is list: + users = [] + for ui in range(len(user)): + users.append(urllib.parse.unquote(user[ui].value)) + else: + users = [urllib.parse.unquote(user.value)] + errors = [] + removes = [] + for user in users: + quser = urllib.parse.quote(user) + if '%s_unsub' % quser in cgidata: + try: + _ = D_ + whence=_('member mgt page') + _ = i18n._ + mlist.ApprovedDeleteMember(user, whence=whence) + removes.append(user) + except Errors.NotAMemberError: + errors.append((user, _('Not subscribed'))) + continue + if not mlist.isMember(user): + doc.addError(_(f'Ignoring changes to deleted member: {user}'), + tag=_('Warning: ')) + continue + value = '%s_digest' % quser in cgidata + try: + mlist.setMemberOption(user, mm_cfg.Digests, value) + except (Errors.AlreadyReceivingDigests, + Errors.AlreadyReceivingRegularDeliveries, + Errors.CantDigestError, + Errors.MustDigestError): + # BAW: Hmm... + pass + + newname = cgidata.getfirst(quser+'_realname', '') + newname = Utils.canonstr(newname, mlist.preferred_language) + mlist.setMemberName(user, newname) + + newlang = cgidata.getfirst(quser+'_language') + oldlang = mlist.getMemberLanguage(user) + if Utils.IsLanguage(newlang) and newlang != oldlang: + mlist.setMemberLanguage(user, newlang) + + moderate = not not cgidata.getfirst(quser+'_mod') + mlist.setMemberOption(user, mm_cfg.Moderate, moderate) + + # Set the `nomail' flag, but only if the user isn't already + # disabled (otherwise we might change BYUSER into BYADMIN). + if '%s_nomail' % quser in cgidata: + if mlist.getDeliveryStatus(user) == MemberAdaptor.ENABLED: + mlist.setDeliveryStatus(user, MemberAdaptor.BYADMIN) + else: + mlist.setDeliveryStatus(user, MemberAdaptor.ENABLED) + for opt in ('hide', 'ack', 'notmetoo', 'nodupes', 'plain'): + opt_code = mm_cfg.OPTINFO[opt] + if '%s_%s' % (quser, opt) in cgidata: + mlist.setMemberOption(user, opt_code, 1) + else: + mlist.setMemberOption(user, opt_code, 0) + # Give some feedback on who's been removed + if removes: + doc.AddItem(Header(5, _('Successfully Removed:'))) + doc.AddItem(UnorderedList(*removes)) + doc.AddItem('

    ') + if errors: + doc.AddItem(Header(5, _("Error Unsubscribing:"))) + items = ['%s -- %s' % (x[0], x[1]) for x in errors] + doc.AddItem(UnorderedList(*tuple((items)))) + doc.AddItem("

    ") diff --git a/Mailman/Cgi/admindb.py b/Mailman/Cgi/admindb.py index 8e551b36..730fc90f 100644 --- a/Mailman/Cgi/admindb.py +++ b/Mailman/Cgi/admindb.py @@ -22,30 +22,28 @@ from builtins import str import sys import os -import urllib.parse +from Mailman.Utils import FieldStorage +import codecs import errno import signal import email import email.errors import time -from urllib.parse import quote_plus, unquote_plus -import re -from email.iterators import body_line_iterator +from urllib.parse import quote_plus, unquote_plus, parse_qs from Mailman import mm_cfg from Mailman import Utils from Mailman import MailList from Mailman import Errors -from Mailman.Message import Message +from Mailman import Message from Mailman import i18n from Mailman.Handlers.Moderate import ModeratedMemberPost -from Mailman.ListAdmin import HELDMSG, ListAdmin, PermissionError +from Mailman.ListAdmin import HELDMSG from Mailman.ListAdmin import readMessage from Mailman.Cgi import Auth from Mailman.htmlformat import * -from Mailman.Logging.Syslog import syslog, mailman_log +from Mailman.Logging.Syslog import syslog from Mailman.CSRFcheck import csrf_check -import traceback EMPTYSTRING = '' NL = '\n' @@ -69,6 +67,7 @@ mm_cfg.AuthSiteAdmin) + def helds_by_skey(mlist, ssort=SSENDER): heldmsgs = mlist.GetHeldMessageIds() byskey = {} @@ -106,22 +105,7 @@ def hacky_radio_buttons(btnname, labels, values, defaults, spacing=3): return btns -def output_error_page(status, title, message, details=None): - doc = Document() - doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) - doc.AddItem(Header(2, _(title))) - doc.AddItem(Bold(_(message))) - if details: - doc.AddItem(Preformatted(Utils.websafe(str(details)))) - doc.AddItem(_('Please contact the site administrator.')) - return doc - - -def output_success_page(doc): - print(doc.Format()) - return - - + def main(): global ssort # Figure out which list is being requested @@ -145,24 +129,14 @@ def main(): # Now that we know which list to use, set the system's language to it. i18n.set_language(mlist.preferred_language) - # Initialize the document - doc = Document() - doc.set_language(mlist.preferred_language) - # Make sure the user is authorized to see this page. + cgidata = FieldStorage(keep_blank_values=1) try: - if os.environ.get('REQUEST_METHOD', '').lower() == 'post': - content_type = os.environ.get('CONTENT_TYPE', '') - if content_type.startswith('application/x-www-form-urlencoded'): - content_length = int(os.environ.get('CONTENT_LENGTH', 0)) - form_data = sys.stdin.buffer.read(content_length).decode('latin-1') - cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=1) - else: - raise ValueError('Invalid content type') - else: - cgidata = urllib.parse.parse_qs(os.environ.get('QUERY_STRING', ''), keep_blank_values=1) - except Exception: + cgidata.getfirst('adminpw', '') + except TypeError: # Someone crafted a POST with a bad Content-Type:. + doc = Document() + doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) doc.AddItem(Header(2, _("Error"))) doc.AddItem(Bold(_('Invalid options to CGI script.'))) # Send this with a 400 status. @@ -174,19 +148,19 @@ def main(): safe_params = ['adminpw', 'admlogin', 'msgid', 'sender', 'details'] params = list(cgidata.keys()) if set(params) - set(safe_params): - csrf_checked = csrf_check(mlist, cgidata.get('csrf_token', [''])[0], + csrf_checked = csrf_check(mlist, cgidata.getfirst('csrf_token'), 'admindb') else: csrf_checked = True # if password is present, void cookie to force password authentication. - if cgidata.get('adminpw', [''])[0]: + if cgidata.getfirst('adminpw'): os.environ['HTTP_COOKIE'] = '' csrf_checked = True if not mlist.WebAuthenticate((mm_cfg.AuthListAdmin, mm_cfg.AuthListModerator, mm_cfg.AuthSiteAdmin), - cgidata.get('adminpw', [''])[0]): + cgidata.getfirst('adminpw', '')): if 'adminpw' in cgidata: # This is a re-authorization attempt msg = Bold(FontSize('+1', _('Authorization failed.'))).Format() @@ -215,51 +189,170 @@ def main(): Auth.loginpage(mlist, 'admindb', frontpage=1) return + # Set up the results document + doc = Document() + doc.set_language(mlist.preferred_language) + + # See if we're requesting all the messages for a particular sender, or if + # we want a specific held message. + sender = None + msgid = None + details = None + envar = os.environ.get('QUERY_STRING') + if envar: + # POST methods, even if their actions have a query string, don't get + # put into FieldStorage's keys :-( + qs = parse_qs(envar).get('sender') + if qs and type(qs) == list: + sender = qs[0] + qs = parse_qs(envar).get('msgid') + if qs and type(qs) == list: + msgid = qs[0] + qs = parse_qs(envar).get('details') + if qs and type(qs) == list: + details = qs[0] + # We need a signal handler to catch the SIGTERM that can come from Apache # when the user hits the browser's STOP button. See the comment in # admin.py for details. + # + # BAW: Strictly speaking, the list should not need to be locked just to + # read the request database. However the request database asserts that + # the list is locked in order to load it and it's not worth complicating + # that logic. def sigterm_handler(signum, frame, mlist=mlist): - try: - # Make sure the list gets unlocked... - mlist.Unlock() - # Log the termination - syslog('info', 'admindb: SIGTERM received, unlocking list and exiting') - except Exception as e: - syslog('error', 'admindb: Error in SIGTERM handler: %s', str(e)) - finally: - # ...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) + # Make sure the list gets unlocked... + 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) mlist.Lock() try: # Install the emergency shutdown signal handler signal.signal(signal.SIGTERM, sigterm_handler) - try: - process_form(mlist, doc, cgidata) - mlist.Save() - # Output the success page with proper headers - print(doc.Format()) - except PermissionError as e: - syslog('error', 'admindb: Permission error processing form: %s', str(e)) - doc = Document() - doc.set_language(mlist.preferred_language) - doc.AddItem(Header(2, _("Error"))) - doc.AddItem(Bold(_('Permission error while processing request'))) - print(doc.Format()) - except Exception as e: - syslog('error', 'admindb: Error processing form: %s', str(e)) - doc = Document() - doc.set_language(mlist.preferred_language) - doc.AddItem(Header(2, _("Error"))) - doc.AddItem(Bold(_('Error processing request'))) + realname = mlist.real_name + if not list(cgidata.keys()) or 'admlogin' in cgidata: + # If this is not a form submission (i.e. there are no keys in the + # form) or it's a login, then we don't need to do much special. + doc.SetTitle(_(f'{realname} Administrative Database')) + elif not details: + # This is a form submission + doc.SetTitle(_(f'{realname} Administrative Database Results')) + if csrf_checked: + process_form(mlist, doc, cgidata) + else: + doc.addError( + _('The form lifetime has expired. (request forgery check)')) + # Now print the results and we're done. Short circuit for when there + # are no pending requests, but be sure to save the results! + admindburl = mlist.GetScriptURL('admindb', absolute=1) + if not mlist.NumRequestsPending(): + title = _(f'{realname} Administrative Database') + doc.SetTitle(title) + doc.AddItem(Header(2, title)) + doc.AddItem(_('There are no pending requests.')) + doc.AddItem(' ') + doc.AddItem(Link(admindburl, + _('Click here to reload this page.'))) + # Put 'Logout' link before the footer + doc.AddItem('\n

    ') + doc.AddItem(Link('%s/logout' % admindburl, + '%s' % _('Logout'))) + doc.AddItem('
    \n') + doc.AddItem(mlist.GetMailmanFooter()) print(doc.Format()) + mlist.Save() + return + + form = Form(admindburl, mlist=mlist, contexts=AUTH_CONTEXTS) + # Add the instructions template + if details == 'instructions': + doc.AddItem(Header( + 2, _('Detailed instructions for the administrative database'))) + else: + doc.AddItem(Header( + 2, + _('Administrative requests for mailing list:') + + ' %s' % mlist.real_name)) + if details != 'instructions': + form.AddItem(Center(SubmitButton('submit', _('Submit All Data')))) + nomessages = not mlist.GetHeldMessageIds() + if not (details or sender or msgid or nomessages): + form.AddItem(Center( + '' + )) + # Add a link back to the overview, if we're not viewing the overview! + adminurl = mlist.GetScriptURL('admin', absolute=1) + d = {'listname' : mlist.real_name, + 'detailsurl': admindburl + '?details=instructions', + 'summaryurl': admindburl, + 'viewallurl': admindburl + '?details=all', + 'adminurl' : adminurl, + 'filterurl' : adminurl + '/privacy/sender', + } + addform = 1 + if sender: + esender = Utils.websafe(sender) + d['description'] = _("all of {esender}'s held messages.") + doc.AddItem(Utils.maketext('admindbpreamble.html', d, + raw=1, mlist=mlist)) + show_sender_requests(mlist, form, sender) + elif msgid: + d['description'] = _('a single held message.') + doc.AddItem(Utils.maketext('admindbpreamble.html', d, + raw=1, mlist=mlist)) + show_message_requests(mlist, form, msgid) + elif details == 'all': + d['description'] = _('all held messages.') + doc.AddItem(Utils.maketext('admindbpreamble.html', d, + raw=1, mlist=mlist)) + show_detailed_requests(mlist, form) + elif details == 'instructions': + doc.AddItem(Utils.maketext('admindbdetails.html', d, + raw=1, mlist=mlist)) + addform = 0 + else: + # Show a summary of all requests + doc.AddItem(Utils.maketext('admindbsummary.html', d, + raw=1, mlist=mlist)) + num = show_pending_subs(mlist, form) + num += show_pending_unsubs(mlist, form) + num += show_helds_overview(mlist, form, ssort) + addform = num > 0 + # Finish up the document, adding buttons to the form + if addform: + doc.AddItem(form) + form.AddItem('
    ') + if not (details or sender or msgid or nomessages): + form.AddItem(Center( + '' + )) + form.AddItem(Center(SubmitButton('submit', _('Submit All Data')))) + # Put 'Logout' link before the footer + doc.AddItem('\n
    ') + doc.AddItem(Link('%s/logout' % admindburl, + '%s' % _('Logout'))) + doc.AddItem('
    \n') + doc.AddItem(mlist.GetMailmanFooter()) + print(doc.Format()) + # Commit all changes + mlist.Save() finally: mlist.Unlock() + def handle_no_list(msg=''): # Print something useful if no list was given. doc = Document() @@ -274,11 +367,10 @@ def handle_no_list(msg=''): doc.AddItem(_(f'You must specify a list name. Here is the {link}')) doc.AddItem('
    ') doc.AddItem(MailmanLogo()) - - # Return the document instead of outputting headers - return doc + print(doc.Format()) + def show_pending_subs(mlist, form): # Add the subscription request section pendingsubs = mlist.GetSubscriptionIds() @@ -286,18 +378,11 @@ def show_pending_subs(mlist, form): return 0 form.AddItem('
    ') form.AddItem(Center(Header(2, _('Subscription Requests')))) - table = Table( - role="table", - aria_label=_("Pending Subscription Requests"), - style="border: 1px solid #ccc; border-collapse: collapse; width: 100%;" - ) + table = Table(border=2) table.AddRow([Center(Bold(_('Address/name/time'))), Center(Bold(_('Your decision'))), Center(Bold(_('Reason for refusal'))) ]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, role="columnheader", scope="col") - table.AddCellInfo(table.GetCurrentRowIndex(), 1, role="columnheader", scope="col") - table.AddCellInfo(table.GetCurrentRowIndex(), 2, role="columnheader", scope="col") # Alphabetical order by email address byaddrs = {} for id in pendingsubs: @@ -329,16 +414,9 @@ def show_pending_subs(mlist, form): CheckBox(f'ban-%d' % id, 1).Format() + ' ' + _('Permanently ban from this list') + '') - # Ensure the address is properly decoded for display - if isinstance(addr, bytes): - try: - addr = addr.decode('utf-8') - except UnicodeDecodeError: - try: - addr = addr.decode('latin-1') - except UnicodeDecodeError: - addr = addr.decode('ascii', 'replace') - table.AddRow(['%s
    %s
    %s' % (Utils.websafe(addr), + # While the address may be a unicode, it must be ascii + paddr = addr.encode('us-ascii', 'replace') + table.AddRow(['%s
    %s
    %s' % (paddr, Utils.websafe(fullname), displaytime), radio, @@ -350,24 +428,18 @@ def show_pending_subs(mlist, form): return num + def show_pending_unsubs(mlist, form): # Add the pending unsubscription request section lang = mlist.preferred_language pendingunsubs = mlist.GetUnsubscriptionIds() if not pendingunsubs: return 0 - table = Table( - role="table", - aria_label=_("Pending Unsubscription Requests"), - style="border: 1px solid #ccc; border-collapse: collapse; width: 100%;" - ) + table = Table(border=2) table.AddRow([Center(Bold(_('User address/name'))), Center(Bold(_('Your decision'))), Center(Bold(_('Reason for refusal'))) ]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, role="columnheader", scope="col") - table.AddCellInfo(table.GetCurrentRowIndex(), 1, role="columnheader", scope="col") - table.AddCellInfo(table.GetCurrentRowIndex(), 2, role="columnheader", scope="col") # Alphabetical order by email address byaddrs = {} for id in pendingunsubs: @@ -410,28 +482,7 @@ def show_pending_unsubs(mlist, form): return num -def format_subject(subject, charset): - """Format a subject line with proper encoding handling.""" - dispsubj = Utils.oneline(subject, charset) - if isinstance(dispsubj, bytes): - try: - dispsubj = dispsubj.decode(charset) - except UnicodeDecodeError: - dispsubj = dispsubj.decode('latin-1', 'replace') - return dispsubj - - -def format_message_data(msgdata): - """Format message metadata with proper error handling.""" - when = msgdata.get('received_time') - if when: - try: - return time.ctime(when) - except (TypeError, ValueError): - return _('Invalid timestamp') - return None - - + def show_helds_overview(mlist, form, ssort=SSENDER): # Sort the held messages. byskey = helds_by_skey(mlist, ssort) @@ -449,11 +500,7 @@ def show_helds_overview(mlist, form, ssort=SSENDER): (ssort == SSENDER, ssort == SSENDERTIME, ssort == STIME)))) # Add the by-sender overview tables admindburl = mlist.GetScriptURL('admindb', absolute=1) - table = Table( - role="table", - aria_label=_("Held Messages Overview"), - border=0 - ) + table = Table(border=0) form.AddItem(table) skeys = list(byskey.keys()) skeys.sort() @@ -463,27 +510,19 @@ def show_helds_overview(mlist, form, ssort=SSENDER): esender = Utils.websafe(sender) senderurl = admindburl + '?sender=' + qsender # The encompassing sender table - stable = Table( - role="table", - aria_label=_("Messages from {sender}").format(sender=esender), - border=1 - ) + stable = Table(border=1) stable.AddRow([Center(Bold(_('From:')).Format() + esender)]) - stable.AddCellInfo(stable.GetCurrentRowIndex(), 0, colspan=2, role="cell") - left = Table( - role="table", - aria_label=_("Actions for messages from {sender}").format(sender=esender), - border=0 - ) + stable.AddCellInfo(stable.GetCurrentRowIndex(), 0, colspan=2) + left = Table(border=0) left.AddRow([_('Action to take on all these held messages:')]) - left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2, role="cell") + left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) btns = hacky_radio_buttons( 'senderaction-' + qsender, (_('Defer'), _('Accept'), _('Reject'), _('Discard')), (mm_cfg.DEFER, mm_cfg.APPROVE, mm_cfg.REJECT, mm_cfg.DISCARD), (1, 0, 0, 0)) left.AddRow([btns]) - left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2, role="cell") + left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) left.AddRow([ '' ]) - left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2, role="cell") + left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) left.AddRow([ '' ]) - left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2, role="cell") + left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) left.AddRow([ TextBox('senderforwardto-' + qsender, value=mlist.GetOwnerEmail()) ]) - left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2, role="cell") + left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) # If the sender is a member and the message is being held due to a # moderation bit, give the admin a chance to clear the member's mod # bit. If this sender is not a member and is not already on one of @@ -522,11 +561,11 @@ def show_helds_overview(mlist, form, ssort=SSENDER): else: left.AddRow( [_('The sender is now a member of this list')]) - left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2, role="cell") + left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) elif sender not in (mlist.accept_these_nonmembers + - mlist.hold_these_nonmembers + - mlist.reject_these_nonmembers + - mlist.discard_these_nonmembers): + mlist.hold_these_nonmembers + + mlist.reject_these_nonmembers + + mlist.discard_these_nonmembers): left.AddRow([ '' ]) - left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2, role="cell") + left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) btns = hacky_radio_buttons( 'senderfilter-' + qsender, (_('Accepts'), _('Holds'), _('Rejects'), _('Discards')), (mm_cfg.ACCEPT, mm_cfg.HOLD, mm_cfg.REJECT, mm_cfg.DISCARD), (0, 0, 0, 1)) left.AddRow([btns]) - left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2, role="cell") + left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) if sender not in mlist.ban_list: left.AddRow([ '']) - left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2, role="cell") - right = Table( - role="table", - aria_label=_("Actions for messages from {sender}").format(sender=esender), - border=0 - ) + left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) + right = Table(border=0) right.AddRow([ _(f"""Click on the message number to view the individual message, or you can """) + Link(senderurl, _(f'view all messages from {esender}')).Format() ]) - right.AddCellInfo(right.GetCurrentRowIndex(), 0, colspan=2, role="cell") + right.AddCellInfo(right.GetCurrentRowIndex(), 0, colspan=2) right.AddRow([' ', ' ']) counter = 1 for ptime, id in byskey[skey]: + info = mlist.GetRecord(id) + ptime, sender, subject, reason, filename, msgdata = info + # BAW: This is really the size of the message pickle, which should + # be close, but won't be exact. Sigh, good enough. try: - info = mlist.GetRecord(id) - ptime, sender, subject, reason, filename, msgdata = info - # Get message size with proper error handling - try: - size = os.path.getsize(os.path.join(mm_cfg.DATA_DIR, filename)) - except OSError as e: - if e.errno != errno.ENOENT: - mailman_log('error', 'admindb: Error getting file size: %s\n%s', - str(e), traceback.format_exc()) - raise - # Message already handled - mlist.HandleRequest(id, mm_cfg.DISCARD) - continue - - # Format subject with proper encoding - charset = Utils.GetCharSet(mlist.preferred_language) - dispsubj = format_subject(subject, charset) - - t = Table( - role="table", - aria_label=_("Message {counter}").format(counter=counter), - border=0 - ) - t.AddRow([Link(admindburl + '?msgid=%d' % id, '[%d]' % counter), - Bold(_('Subject:')), - Utils.websafe(dispsubj) - ]) - t.AddRow([' ', Bold(_('Size:')), str(size) + _(' bytes')]) - - # Format reason with proper encoding - if reason: - try: - reason = _(reason) - if isinstance(reason, bytes): - reason = reason.decode(charset, 'replace') - except (UnicodeError, LookupError): - reason = _('not available') - else: - reason = _('not available') - t.AddRow([' ', Bold(_('Reason:')), reason]) - - # Format received time with proper error handling - received_time = format_message_data(msgdata) - if received_time: - t.AddRow([' ', Bold(_('Received:')), received_time]) - - t.AddRow([InputObj(qsender, 'hidden', str(id), False).Format()]) - counter += 1 - right.AddRow([t]) - except Exception as e: - mailman_log('error', 'admindb: Error processing held message %d: %s\n%s', - id, str(e), traceback.format_exc()) + size = os.path.getsize(os.path.join(mm_cfg.DATA_DIR, filename)) + except OSError as e: + if e.errno != errno.ENOENT: raise + # This message must have gotten lost, i.e. it's already been + # handled by the time we got here. + mlist.HandleRequest(id, mm_cfg.DISCARD) continue - + dispsubj = Utils.oneline( + subject, Utils.GetCharSet(mlist.preferred_language)) + t = Table(border=0) + t.AddRow([Link(admindburl + '?msgid=%d' % id, '[%d]' % counter), + Bold(_('Subject:')), + Utils.websafe(dispsubj) + ]) + t.AddRow([' ', Bold(_('Size:')), str(size) + _(' bytes')]) + if reason: + reason = _(reason) + else: + reason = _('not available') + t.AddRow([' ', Bold(_('Reason:')), reason]) + # Include the date we received the message, if available + when = msgdata.get('received_time') + if when: + t.AddRow([' ', Bold(_('Received:')), + time.ctime(when)]) + t.AddRow([InputObj(qsender, 'hidden', str(id), False).Format()]) + counter += 1 + right.AddRow([t]) stable.AddRow([left, right]) table.AddRow([stable]) return 1 + def show_sender_requests(mlist, form, sender): byskey = helds_by_skey(mlist, SSENDER) if not byskey: @@ -641,39 +655,18 @@ def show_sender_requests(mlist, form, sender): count += 1 + def show_message_requests(mlist, form, id): try: id = int(id) info = mlist.GetRecord(id) - except ValueError as e: - mailman_log('error', 'admindb: Invalid message ID "%s": %s\n%s', - id, str(e), traceback.format_exc()) - form.AddItem(Header(2, _("Error"))) - form.AddItem(Bold(_('Invalid message ID.'))) - return - except KeyError as e: - mailman_log('error', 'admindb: Message ID %d not found: %s\n%s', - id, str(e), traceback.format_exc()) - form.AddItem(Header(2, _("Error"))) - form.AddItem(Bold(_('Message not found.'))) - return - except Exception as e: - mailman_log('error', 'admindb: Error getting message %d: %s\n%s', - id, str(e), traceback.format_exc()) - form.AddItem(Header(2, _("Error"))) - form.AddItem(Bold(_('Error retrieving message.'))) - return - - try: - show_post_requests(mlist, id, info, 1, 1, form) - except Exception as e: - mailman_log('error', 'admindb: Error showing message %d: %s\n%s', - id, str(e), traceback.format_exc()) - form.AddItem(Header(2, _("Error"))) - form.AddItem(Bold(_('Error displaying message.'))) + except (ValueError, KeyError): + # BAW: print an error message? return + show_post_requests(mlist, id, info, 1, 1, form) + def show_detailed_requests(mlist, form): all = mlist.GetHeldMessageIds() total = len(all) @@ -684,6 +677,7 @@ def show_detailed_requests(mlist, form): count += 1 + def show_post_requests(mlist, id, info, total, count, form): # Mailman.ListAdmin.__handlepost no longer tests for pre 2.0beta3 ptime, sender, subject, reason, filename, msgdata = info @@ -693,132 +687,107 @@ def show_post_requests(mlist, id, info, total, count, form): if total != 1: msg += _(f' (%(count)d of %(total)d)') form.AddItem(Center(Header(2, msg))) - - # Get the message file path - msgpath = os.path.join(mm_cfg.DATA_DIR, filename) - - # Try to read the message with better error handling + # We need to get the headers and part of the textual body of the message + # being held. The best way to do this is to use the email Parser to get + # an actual object, which will be easier to deal with. We probably could + # just do raw reads on the file. try: - msg = readMessage(msgpath) + msg = readMessage(os.path.join(mm_cfg.DATA_DIR, filename)) + Utils.set_cte_if_missing(msg) except IOError as e: if e.errno != errno.ENOENT: - mailman_log('error', 'admindb: Error reading message file %s: %s\n%s', - msgpath, str(e), traceback.format_exc()) raise form.AddItem(_(f'Message with id #%(id)d was lost.')) form.AddItem('

    ') + # BAW: kludge to remove id from requests.db. try: mlist.HandleRequest(id, mm_cfg.DISCARD) except Errors.LostHeldMessage: pass return - except email.errors.MessageParseError as e: - mailman_log('error', 'admindb: Corrupted message file %s: %s\n%s', - msgpath, str(e), traceback.format_exc()) + except email.errors.MessageParseError: form.AddItem(_(f'Message with id #%(id)d is corrupted.')) + # BAW: Should we really delete this, or shuttle it off for site admin + # to look more closely at? form.AddItem('

    ') + # BAW: kludge to remove id from requests.db. try: mlist.HandleRequest(id, mm_cfg.DISCARD) except Errors.LostHeldMessage: pass return - except Exception as e: - mailman_log('error', 'admindb: Unexpected error reading message %d: %s\n%s', - id, str(e), traceback.format_exc()) - form.AddItem(_(f'Error reading message #%(id)d.')) - form.AddItem('

    ') - return - - # Get the header text and the message body excerpt with better encoding handling + # Get the header text and the message body excerpt lines = [] chars = 0 + # A negative value means, include the entire message regardless of size limit = mm_cfg.ADMINDB_PAGE_TEXT_LIMIT - - # Try to determine the message charset - charset = None + + if msg.is_multipart(): + for part in msg.walk(): + if not hasattr(part, 'policy'): + part.policy = email._policybase.compat32 + if part.get_content_type() == 'text/plain': + payload = part.get_payload(decode=True) + if payload: + decoded_payload = codecs.decode(payload, 'unicode_escape') + for line in decoded_payload.splitlines(): + lines.append(line) + chars += len(line) + if chars >= limit > 0: + break + break + else: + payload = msg.get_payload(decode=True) + if payload: + decoded_payload = codecs.decode(payload, 'unicode_escape') + for line in decoded_payload.splitlines(): + lines.append(line) + chars += len(line) + if chars >= limit > 0: + break + # Ensure the full last line is included to avoid splitting multibyte characters + body = ''.join(lines) + # Get message charset and try encode in list charset + # We get it from the first text part. + # We need to replace invalid characters here or we can throw an uncaught + # exception in doc.Format(). for part in msg.walk(): if part.get_content_maintype() == 'text': - charset = part.get_content_charset() - if charset: - break - - # If no charset found, use list's preferred charset - if not charset: - charset = Utils.GetCharSet(mlist.preferred_language) - - # Read the message body with proper encoding - try: - for line in body_line_iterator(msg, decode=True): - # Try to decode the line if it's bytes - if isinstance(line, bytes): - try: - line = line.decode(charset, 'replace') - except (UnicodeError, LookupError): - line = line.decode('latin-1', 'replace') - - lines.append(line) - chars += len(line) - if chars >= limit > 0: - break - except Exception as e: - mailman_log('error', 'admindb: Error reading message body: %s\n%s', - str(e), traceback.format_exc()) - lines = [_('Error reading message body')] - - # Join the lines with proper encoding - try: - body = ''.join(lines) - if isinstance(body, bytes): - body = body.decode(charset, 'replace') - except (UnicodeError, LookupError): - body = _('Error decoding message body') - - # Format the headers with proper encoding - try: - hdrtxt = NL.join(['%s: %s' % (k, v) for k, v in list(msg.items())]) - if isinstance(hdrtxt, bytes): - hdrtxt = hdrtxt.decode(charset, 'replace') - except (UnicodeError, LookupError): - hdrtxt = _('Error decoding message headers') - - # Format the subject with proper encoding - try: - dispsubj = Utils.oneline(subject, charset) - if isinstance(dispsubj, bytes): - dispsubj = dispsubj.decode(charset, 'replace') - except (UnicodeError, LookupError): - dispsubj = _('Error decoding subject') - - # Format the reason with proper encoding - try: - if reason: - reason = _(reason) - if isinstance(reason, bytes): - reason = reason.decode(charset, 'replace') - else: - reason = _('not available') - except (UnicodeError, LookupError): - reason = _('Error decoding reason') - - # Create the form table with proper encoding - t = Table(cellspacing=0, cellpadding=0) - t.AddRow([Bold(_('From:')), Utils.websafe(sender)]) + # Watchout for charset= with no value. + mcset = part.get_content_charset() or 'us-ascii' + break + else: + mcset = 'us-ascii' + lcset = Utils.GetCharSet(mlist.preferred_language) + # Note that this following block breaks a lot of messages. Removing it allows them to stay in their native character sets. + # Leaving in as it seems like behavior people would have grown to expect. + if mcset != lcset: + # Ensure the body is in the list's preferred charset + try: + # If body is a str, encode to bytes using the source charset (mcset) + body_bytes = body.encode(mcset, 'replace') if isinstance(body, str) else body + # Then decode bytes to str using the list's charset (lcset) + body = body_bytes.decode(lcset, 'replace') + except (UnicodeEncodeError, UnicodeDecodeError): + # Fallback in case of encoding/decoding issues + body = body.encode('ascii', 'replace').decode('ascii', 'replace') + # + hdrtxt = NL.join(['%s: %s' % (k, v) for k, v in list(msg.items())]) + hdrtxt = Utils.websafe(hdrtxt) + # Okay, we've reconstituted the message just fine. Now for the fun part! + t = Table(cellspacing=0, cellpadding=0, width='100%') + t.AddRow([Bold(_('From:')), sender]) row, col = t.GetCurrentRowIndex(), t.GetCurrentCellIndex() t.AddCellInfo(row, col-1, align='right') - - t.AddRow([Bold(_('Subject:')), Utils.websafe(dispsubj)]) + t.AddRow([Bold(_('Subject:')), + Utils.websafe(Utils.oneline(subject, lcset))]) t.AddCellInfo(row+1, col-1, align='right') - - t.AddRow([Bold(_('Reason:')), Utils.websafe(reason)]) + t.AddRow([Bold(_('Reason:')), _(reason)]) t.AddCellInfo(row+2, col-1, align='right') - - # Format received time with proper error handling - received_time = format_message_data(msgdata) - if received_time: - t.AddRow([Bold(_('Received:')), received_time]) + when = msgdata.get('received_time') + if when: + t.AddRow([Bold(_('Received:')), time.ctime(when)]) t.AddCellInfo(row+3, col-1, align='right') - - # Add action buttons buttons = hacky_radio_buttons(id, (_('Defer'), _('Approve'), _('Reject'), _('Discard')), (mm_cfg.DEFER, mm_cfg.APPROVE, mm_cfg.REJECT, mm_cfg.DISCARD), @@ -826,16 +795,12 @@ def show_post_requests(mlist, id, info, total, count, form): spacing=5) t.AddRow([Bold(_('Action:')), buttons]) t.AddCellInfo(t.GetCurrentRowIndex(), col-1, align='right') - - # Add preserve checkbox t.AddRow([' ', '' ]) - - # Add forward checkbox and textbox t.AddRow([' ', '

    ') + def process_form(mlist, doc, cgidata): - """Process the admin database form with proper error handling.""" + global ssort + senderactions = {} + badaddrs = [] + # Sender-centric actions + for k in list(cgidata.keys()): + for prefix in ('senderaction-', 'senderpreserve-', 'senderforward-', + 'senderforwardto-', 'senderfilterp-', 'senderfilter-', + 'senderclearmodp-', 'senderbanp-'): + if k.startswith(prefix): + action = k[:len(prefix)-1] + qsender = k[len(prefix):] + sender = unquote_plus(qsender) + value = cgidata.getfirst(k) + senderactions.setdefault(sender, {})[action] = value + for id in cgidata.getlist(qsender): + senderactions[sender].setdefault('message_ids', + []).append(int(id)) + # discard-all-defers try: - # Get the sender and message id from the query string with proper encoding - envar = os.environ.get('QUERY_STRING', '') - qs = urllib.parse.parse_qs(envar, keep_blank_values=True) - - # Handle both encoded and unencoded values - def safe_get(key, default=''): - values = qs.get(key, [default]) - if not values: - return default + discardalldefersp = cgidata.getfirst('discardalldefersp', 0) + except ValueError: + discardalldefersp = 0 + # Get the summary sequence + ssort = int(cgidata.getfirst('summary_sort', SSENDER)) + for sender in list(senderactions.keys()): + actions = senderactions[sender] + # Handle what to do about all this sender's held messages + try: + action = int(actions.get('senderaction', mm_cfg.DEFER)) + except ValueError: + action = mm_cfg.DEFER + if action == mm_cfg.DEFER and discardalldefersp: + action = mm_cfg.DISCARD + if action in (mm_cfg.DEFER, mm_cfg.APPROVE, + mm_cfg.REJECT, mm_cfg.DISCARD): + preserve = actions.get('senderpreserve', 0) + forward = actions.get('senderforward', 0) + forwardaddr = actions.get('senderforwardto', '') + byskey = helds_by_skey(mlist, SSENDER) + for ptime, id in byskey.get((0, sender), []): + if id not in senderactions[sender]['message_ids']: + # It arrived after the page was displayed. Skip it. + continue + try: + msgdata = mlist.GetRecord(id)[5] + comment = msgdata.get('rejection_notice', + _('[No explanation given]')) + mlist.HandleRequest(id, action, comment, preserve, + forward, forwardaddr) + except (KeyError, Errors.LostHeldMessage): + # That's okay, it just means someone else has already + # updated the database while we were staring at the page, + # so just ignore it + continue + # Now see if this sender should be added to one of the nonmember + # sender filters. + if actions.get('senderfilterp', 0): + # Check for an invalid sender address. try: - # Try to decode if it's bytes - if isinstance(values[0], bytes): - return values[0].decode('utf-8', 'replace') - return values[0] - except (UnicodeError, AttributeError): - return str(values[0]) - - sender = safe_get('sender') - msgid = safe_get('msgid') - details = safe_get('details') - - # Set the page title with proper encoding - title = _(f'{mlist.real_name} Administrative Database') - doc.SetTitle(title) - doc.AddItem(Header(2, title)) - - # Create a form for the overview with proper encoding - form = Form(mlist.GetScriptURL('admindb', absolute=1), - mlist=mlist, - contexts=AUTH_CONTEXTS) - form.AddItem(Center(SubmitButton('submit', _('Submit All Data')))) - - # Get the action from the form data with proper encoding - action = safe_get('action') - if not action: - # No action specified, show the overview - show_pending_subs(mlist, form) - show_pending_unsubs(mlist, form) - show_helds_overview(mlist, form) - doc.AddItem(form) - return - - # Process the form submission - if action == 'submit': - # Process the form data - process_submissions(mlist, cgidata) - # Show success message - doc.AddItem(Header(2, _('Database Updated...'))) - return - - # If we get here, something went wrong - doc.AddItem(Header(2, _('Error'))) - doc.AddItem(Bold(_('Invalid form submission.'))) - - except Exception as e: - mailman_log('error', 'admindb: Error in process_form: %s\n%s', - str(e), traceback.format_exc()) - raise - - -def format_body(body, mcset, lcset): - """Format the message body for display.""" - if isinstance(body, bytes): - body = body.decode(mcset, 'replace') - elif not isinstance(body, str): - body = str(body) - return body.encode(lcset, 'replace') + Utils.ValidateEmail(sender) + except Errors.EmailAddressError: + # Don't check for dups. Report it once for each checked box. + badaddrs.append(sender) + else: + try: + which = int(actions.get('senderfilter')) + except ValueError: + # Bogus form + which = 'ignore' + if which == mm_cfg.ACCEPT: + mlist.accept_these_nonmembers.append(sender) + elif which == mm_cfg.HOLD: + mlist.hold_these_nonmembers.append(sender) + elif which == mm_cfg.REJECT: + mlist.reject_these_nonmembers.append(sender) + elif which == mm_cfg.DISCARD: + mlist.discard_these_nonmembers.append(sender) + # Otherwise, it's a bogus form, so ignore it + # And now see if we're to clear the member's moderation flag. + if actions.get('senderclearmodp', 0): + try: + mlist.setMemberOption(sender, mm_cfg.Moderate, 0) + except Errors.NotAMemberError: + # This person's not a member any more. Oh well. + pass + # And should this address be banned? + if actions.get('senderbanp', 0): + # Check for an invalid sender address. + try: + Utils.ValidateEmail(sender) + except Errors.EmailAddressError: + # Don't check for dups. Report it once for each checked box. + badaddrs.append(sender) + else: + if sender not in mlist.ban_list: + mlist.ban_list.append(sender) + # Now, do message specific actions + banaddrs = [] + erroraddrs = [] + for k in list(cgidata.keys()): + formv = cgidata[k] + if type(formv) == list: + continue + try: + v = int(formv.value) + request_id = int(k) + except ValueError: + continue + if v not in (mm_cfg.DEFER, mm_cfg.APPROVE, mm_cfg.REJECT, + mm_cfg.DISCARD, mm_cfg.SUBSCRIBE, mm_cfg.UNSUBSCRIBE, + mm_cfg.ACCEPT, mm_cfg.HOLD): + continue + # Get the action comment and reasons if present. + commentkey = 'comment-%d' % request_id + preservekey = 'preserve-%d' % request_id + forwardkey = 'forward-%d' % request_id + forwardaddrkey = 'forward-addr-%d' % request_id + bankey = 'ban-%d' % request_id + # Defaults + try: + if mlist.GetRecordType(request_id) == HELDMSG: + msgdata = mlist.GetRecord(request_id)[5] + comment = msgdata.get('rejection_notice', + _('[No explanation given]')) + else: + comment = _('[No explanation given]') + except KeyError: + # Someone else must have handled this one after we got the page. + continue + preserve = 0 + forward = 0 + forwardaddr = '' + if commentkey in cgidata: + comment = cgidata[commentkey].value + if preservekey in cgidata: + preserve = cgidata[preservekey].value + if forwardkey in cgidata: + forward = cgidata[forwardkey].value + if forwardaddrkey in cgidata: + forwardaddr = cgidata[forwardaddrkey].value + # Should we ban this address? Do this check before handling the + # request id because that will evict the record. + if cgidata.getfirst(bankey): + sender = mlist.GetRecord(request_id)[1] + if sender not in mlist.ban_list: + # We don't need to validate the sender. An invalid address + # can't get here. + mlist.ban_list.append(sender) + # Handle the request id + try: + mlist.HandleRequest(request_id, v, comment, + preserve, forward, forwardaddr) + except (KeyError, Errors.LostHeldMessage): + # That's okay, it just means someone else has already updated the + # database while we were staring at the page, so just ignore it + continue + except Errors.MMAlreadyAMember as v: + erroraddrs.append(v) + except Errors.MembershipIsBanned as pattern: + sender = mlist.GetRecord(request_id)[1] + banaddrs.append((sender, pattern)) + # save the list and print the results + doc.AddItem(Header(2, _('Database Updated...'))) + if erroraddrs: + for addr in erroraddrs: + addr = Utils.websafe(addr) + doc.AddItem(str(addr) + _(' is already a member') + '
    ') + if banaddrs: + for addr, patt in banaddrs: + addr = Utils.websafe(addr) + doc.AddItem(_(f'{addr} is banned (matched: {patt})') + '
    ') + if badaddrs: + for addr in badaddrs: + addr = Utils.websafe(addr) + doc.AddItem(str(addr) + ': ' + _('Bad/Invalid email address') + + '
    ') diff --git a/Mailman/Cgi/confirm.py b/Mailman/Cgi/confirm.py index ab2d1958..f0a13843 100644 --- a/Mailman/Cgi/confirm.py +++ b/Mailman/Cgi/confirm.py @@ -20,10 +20,8 @@ from __future__ import print_function import signal -import urllib.parse +from Mailman.Utils import FieldStorage import time -import os -import sys from Mailman import mm_cfg from Mailman import Errors @@ -38,6 +36,7 @@ _ = i18n._ i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + def main(): doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) @@ -55,7 +54,7 @@ def main(): except Errors.MMListError as e: # Avoid cross-site scripting attacks safelistname = Utils.websafe(listname) - bad_confirmation(doc, _('No such list {safelistname}')) + bad_confirmation(doc, _(f'No such list {safelistname}')) doc.AddItem(MailmanLogo()) # Send this with a 404 status. print('Status: 404 Not Found') @@ -68,18 +67,10 @@ def main(): doc.set_language(mlist.preferred_language) # Get the form data to see if this is a second-step confirmation + cgidata = FieldStorage(keep_blank_values=1) try: - if os.environ.get('REQUEST_METHOD') == 'POST': - content_length = int(os.environ.get('CONTENT_LENGTH', 0)) - if content_length > 0: - form_data = sys.stdin.buffer.read(content_length).decode('utf-8') - cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=True) - else: - cgidata = {} - else: - query_string = os.environ.get('QUERY_STRING', '') - cgidata = urllib.parse.parse_qs(query_string, keep_blank_values=True) - except Exception: + cookie = cgidata.getfirst('cookie') + except TypeError: # Someone crafted a POST with a bad Content-Type:. doc.AddItem(Header(2, _("Error"))) doc.AddItem(Bold(_('Invalid options to CGI script.'))) @@ -88,7 +79,6 @@ def main(): print(doc.Format()) return - cookie = cgidata.get('cookie', [''])[0] if cookie == '': ask_for_cookie(mlist, doc, _('Confirmation string was empty.')) return @@ -129,17 +119,17 @@ def main(): try: if content[0] == Pending.SUBSCRIPTION: - if cgidata.get('cancel', [''])[0]: + if cgidata.getfirst('cancel'): subscription_cancel(mlist, doc, cookie) - elif cgidata.get('submit', [''])[0]: + elif cgidata.getfirst('submit'): subscription_confirm(mlist, doc, cookie, cgidata) else: subscription_prompt(mlist, doc, cookie, content[1]) elif content[0] == Pending.UNSUBSCRIPTION: try: - if cgidata.get('cancel', [''])[0]: + if cgidata.getfirst('cancel'): unsubscription_cancel(mlist, doc, cookie) - elif cgidata.get('submit', [''])[0]: + elif cgidata.getfirst('submit'): unsubscription_confirm(mlist, doc, cookie) else: unsubscription_prompt(mlist, doc, cookie, *content[1:]) @@ -150,9 +140,9 @@ def main(): # Expunge this record from the pending database. expunge(mlist, cookie) elif content[0] == Pending.CHANGE_OF_ADDRESS: - if cgidata.get('cancel', [''])[0]: + if cgidata.getfirst('cancel'): addrchange_cancel(mlist, doc, cookie) - elif cgidata.get('submit', [''])[0]: + elif cgidata.getfirst('submit'): addrchange_confirm(mlist, doc, cookie) else: # Watch out for users who have unsubscribed themselves in the @@ -166,21 +156,21 @@ def main(): # Expunge this record from the pending database. expunge(mlist, cookie) elif content[0] == Pending.HELD_MESSAGE: - if cgidata.get('cancel', [''])[0]: + if cgidata.getfirst('cancel'): heldmsg_cancel(mlist, doc, cookie) - elif cgidata.get('submit', [''])[0]: + elif cgidata.getfirst('submit'): heldmsg_confirm(mlist, doc, cookie) else: heldmsg_prompt(mlist, doc, cookie, *content[1:]) elif content[0] == Pending.RE_ENABLE: - if cgidata.get('cancel', [''])[0]: + if cgidata.getfirst('cancel'): reenable_cancel(mlist, doc, cookie) - elif cgidata.get('submit', [''])[0]: + elif cgidata.getfirst('submit'): reenable_confirm(mlist, doc, cookie) else: reenable_prompt(mlist, doc, cookie, *content[1:]) else: - bad_confirmation(doc, _('System error, bad content: {content}')) + bad_confirmation(doc, _(f'System error, bad content: {content}')) except Errors.MMBadConfirmation: bad_confirmation(doc, badconfirmstr) @@ -188,6 +178,7 @@ def main(): print(doc.Format()) + def bad_confirmation(doc, extra=''): title = _('Bad confirmation string') doc.SetTitle(title) @@ -206,6 +197,7 @@ def expunge(mlist, cookie): mlist.Unlock() + def ask_for_cookie(mlist, doc, extra=''): title = _('Enter confirmation cookie') doc.SetTitle(title) @@ -235,6 +227,7 @@ def ask_for_cookie(mlist, doc, extra=''): print(doc.Format()) + def subscription_prompt(mlist, doc, cookie, userdesc): email = userdesc.address password = userdesc.password @@ -305,7 +298,7 @@ def subscription_prompt(mlist, doc, cookie, userdesc): table.AddRow([Label(_('Receive digests?')), RadioButtonArray('digests', (_('No'), _('Yes')), checked=digest, values=(0, 1))]) - langs = mlist.available_languages + langs = mlist.GetAvailableLanguages() values = [_(Utils.GetLanguageDescr(l)) for l in langs] try: selected = langs.index(lang) @@ -316,13 +309,14 @@ def subscription_prompt(mlist, doc, cookie, userdesc): table.AddRow([Hidden('cookie', cookie)]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) table.AddRow([ - Label(SubmitButton('submit', _('Subscribe to list {listname}'))), + Label(SubmitButton('submit', _(f'Subscribe to list {listname}'))), SubmitButton('cancel', _('Cancel my subscription request')) ]) form.AddItem(table) doc.AddItem(form) + def subscription_cancel(mlist, doc, cookie): mlist.Lock() try: @@ -342,6 +336,7 @@ def subscription_cancel(mlist, doc, cookie): doc.AddItem(_('You have canceled your subscription request.')) + def subscription_confirm(mlist, doc, cookie, cgidata): # See the comment in admin.py about the need for the signal # handler. @@ -355,14 +350,14 @@ def sigterm_handler(signum, frame, mlist=mlist): try: # Some pending values may be overridden in the form. email of # course is hardcoded. ;) - lang = cgidata.get('language', [mlist.preferred_language])[0] + lang = cgidata.getfirst('language') if not Utils.IsLanguage(lang): lang = mlist.preferred_language i18n.set_language(lang) doc.set_language(lang) if 'digests' in cgidata: try: - digest = int(cgidata['digests'][0]) + digest = int(cgidata.getfirst('digests')) except ValueError: digest = None else: @@ -372,7 +367,7 @@ def sigterm_handler(signum, frame, mlist=mlist): # to confirm the same token simultaneously. If they both succeed in # retrieving the data above, when the second gets here, the cookie # is gone and TypeError is thrown. Catch it below. - fullname = cgidata.get('realname', [None])[0] + fullname = cgidata.getfirst('realname', None) if fullname is not None: fullname = Utils.canonstr(fullname, lang) overrides = UserDesc(fullname=fullname, digest=digest, lang=lang) @@ -429,12 +424,14 @@ def sigterm_handler(signum, frame, mlist=mlist): mlist.Unlock() + def unsubscription_cancel(mlist, doc, cookie): # Expunge this record from the pending database expunge(mlist, cookie) doc.AddItem(_('You have canceled your unsubscription request.')) + def unsubscription_confirm(mlist, doc, cookie): # See the comment in admin.py about the need for the signal # handler. @@ -473,6 +470,7 @@ def sigterm_handler(signum, frame, mlist=mlist): mlist.Unlock() + def unsubscription_prompt(mlist, doc, cookie, addr): title = _('Confirm unsubscription request') doc.SetTitle(title) @@ -515,12 +513,14 @@ def unsubscription_prompt(mlist, doc, cookie, addr): doc.AddItem(form) + def addrchange_cancel(mlist, doc, cookie): # Expunge this record from the pending database expunge(mlist, cookie) doc.AddItem(_('You have canceled your change of address request.')) + def addrchange_confirm(mlist, doc, cookie): # See the comment in admin.py about the need for the signal # handler. @@ -573,6 +573,7 @@ def sigterm_handler(signum, frame, mlist=mlist): mlist.Unlock() + def addrchange_prompt(mlist, doc, cookie, oldaddr, newaddr, globally): title = _('Confirm change of address request') doc.SetTitle(title) @@ -624,6 +625,7 @@ def addrchange_prompt(mlist, doc, cookie, oldaddr, newaddr, globally): doc.AddItem(form) + def heldmsg_cancel(mlist, doc, cookie): title = _('Continue awaiting approval') doc.SetTitle(title) @@ -638,6 +640,7 @@ def heldmsg_cancel(mlist, doc, cookie): doc.AddItem(table) + def heldmsg_confirm(mlist, doc, cookie): # See the comment in admin.py about the need for the signal # handler. @@ -683,6 +686,7 @@ def sigterm_handler(signum, frame, mlist=mlist): mlist.Unlock() + def heldmsg_prompt(mlist, doc, cookie, id): title = _('Cancel held message posting') doc.SetTitle(title) @@ -746,6 +750,7 @@ def sigterm_handler(signum, frame, mlist=mlist): doc.AddItem(form) + def reenable_cancel(mlist, doc, cookie): # Don't actually discard this cookie, since the user may decide to # re-enable their membership at a future time, and we may be sending out @@ -755,6 +760,7 @@ def reenable_cancel(mlist, doc, cookie): this mailing list.""")) + def reenable_confirm(mlist, doc, cookie): # See the comment in admin.py about the need for the signal # handler. @@ -794,6 +800,7 @@ def sigterm_handler(signum, frame, mlist=mlist): mlist.Unlock() + def reenable_prompt(mlist, doc, cookie, list, member): title = _('Re-enable mailing list membership') doc.SetTitle(title) diff --git a/Mailman/Cgi/create.py b/Mailman/Cgi/create.py index 27774c03..691cfd88 100644 --- a/Mailman/Cgi/create.py +++ b/Mailman/Cgi/create.py @@ -22,11 +22,11 @@ import sys import os import signal -import urllib.parse +from Mailman.Utils import FieldStorage from Mailman import mm_cfg from Mailman import MailList -from Mailman.Message import Message +from Mailman import Message from Mailman import Errors from Mailman import i18n from Mailman.htmlformat import * @@ -38,32 +38,14 @@ i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + def main(): doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + cgidata = FieldStorage() try: - if os.environ.get('REQUEST_METHOD') == 'POST': - content_length = int(os.environ.get('CONTENT_LENGTH', 0)) - if content_length > 0: - form_data = sys.stdin.buffer.read(content_length).decode('utf-8') - cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=True) - else: - cgidata = {} - else: - query_string = os.environ.get('QUERY_STRING', '') - cgidata = urllib.parse.parse_qs(query_string, keep_blank_values=True) - except Exception: - # Someone crafted a POST with a bad Content-Type:. - doc.AddItem(Header(2, _("Error"))) - doc.AddItem(Bold(_('Invalid options to CGI script.'))) - # Send this with a 400 status. - print('Status: 400 Bad Request') - print(doc.Format()) - return - - try: - cgidata.get('doit', [''])[0] + cgidata.getfirst('doit', '') except TypeError: # Someone crafted a POST with a bad Content-Type:. doc.AddItem(Header(2, _("Error"))) @@ -101,27 +83,29 @@ def main(): print(doc.Format()) + def process_request(doc, cgidata): # Lowercase the listname since this is treated as the "internal" name. - listname = cgidata.get('listname', [''])[0].strip().lower() - owner = cgidata.get('owner', [''])[0].strip() + listname = cgidata.getfirst('listname', '').strip().lower() + owner = cgidata.getfirst('owner', '').strip() try: - autogen = int(cgidata.get('autogen', ['0'])[0]) + autogen = int(cgidata.getfirst('autogen', '0')) except ValueError: autogen = 0 try: - notify = int(cgidata.get('notify', ['0'])[0]) + notify = int(cgidata.getfirst('notify', '0')) except ValueError: notify = 0 try: - moderate = int(cgidata.get('moderate', ['0'])[0]) + moderate = int(cgidata.getfirst('moderate', + mm_cfg.DEFAULT_DEFAULT_MEMBER_MODERATION)) except ValueError: moderate = mm_cfg.DEFAULT_DEFAULT_MEMBER_MODERATION - password = cgidata.get('password', [''])[0].strip() - confirm = cgidata.get('confirm', [''])[0].strip() - auth = cgidata.get('auth', [''])[0].strip() - langs = cgidata.get('langs', [mm_cfg.DEFAULT_SERVER_LANGUAGE]) + password = cgidata.getfirst('password', '').strip() + confirm = cgidata.getfirst('confirm', '').strip() + auth = cgidata.getfirst('auth', '').strip() + langs = cgidata.getvalue('langs', [mm_cfg.DEFAULT_SERVER_LANGUAGE]) if not isinstance(langs, list): langs = [langs] @@ -129,14 +113,14 @@ def process_request(doc, cgidata): safelistname = Utils.websafe(listname) if '@' in listname: request_creation(doc, cgidata, - _('List name must not include "@": {safelistname}')) + _(f'List name must not include "@": {safelistname}')) return if Utils.list_exists(listname): # BAW: should we tell them the list already exists? This could be # used to mine/guess the existance of non-advertised lists. Then # again, that can be done in other ways already, so oh well. request_creation(doc, cgidata, - _('List already exists: {safelistname}')) + _(f'List already exists: {safelistname}')) return if not listname: request_creation(doc, cgidata, @@ -196,7 +180,7 @@ def process_request(doc, cgidata): hostname not in mm_cfg.VIRTUAL_HOSTS: safehostname = Utils.websafe(hostname) request_creation(doc, cgidata, - _('Unknown virtual host: {safehostname}')) + _(f'Unknown virtual host: {safehostname}')) return emailhost = mm_cfg.VIRTUAL_HOSTS.get(hostname, mm_cfg.DEFAULT_EMAIL_HOST) # We've got all the data we need, so go ahead and try to create the list @@ -232,12 +216,12 @@ def sigterm_handler(signum, frame, mlist=mlist): else: s = Utils.websafe(owner) request_creation(doc, cgidata, - _('Bad owner email address: {s}')) + _(f'Bad owner email address: {s}')) return except Errors.MMListAlreadyExistsError: # MAS: List already exists so we don't need to websafe it. request_creation(doc, cgidata, - _('List already exists: {listname}')) + _(f'List already exists: {listname}')) return except Errors.BadListNameError as e: if e.args: @@ -245,7 +229,7 @@ def sigterm_handler(signum, frame, mlist=mlist): else: s = Utils.websafe(listname) request_creation(doc, cgidata, - _('Illegal list name: {s}')) + _(f'Illegal list name: {s}')) return except Errors.MMListError: request_creation( @@ -285,9 +269,9 @@ def sigterm_handler(signum, frame, mlist=mlist): 'requestaddr' : mlist.GetRequestEmail(), 'siteowner' : siteowner, }, mlist=mlist) - msg = Mailman.Message.UserNotification( + msg = Message.UserNotification( owner, siteowner, - _('Your new mailing list: {listname}'), + _(f'Your new mailing list: {listname}'), text, mlist.preferred_language) msg.send(mlist) @@ -298,16 +282,10 @@ def sigterm_handler(signum, frame, mlist=mlist): title = _('Mailing list creation results') doc.SetTitle(title) - table = Table( - role="table", - aria_label=_("List Creation Results"), - border=0, - width='100%' - ) + table = Table(border=0, width='100%') table.AddRow([Center(Bold(FontAttr(title, size='+1')))]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, - style=f'background-color: {mm_cfg.WEB_HEADER_COLOR}', - role="cell") + bgcolor=mm_cfg.WEB_HEADER_COLOR) table.AddRow([_(f'''You have successfully created the mailing list {listname} and notification has been sent to the list owner {owner}. You can now:''')]) @@ -319,28 +297,25 @@ def sigterm_handler(signum, frame, mlist=mlist): doc.AddItem(table) + # Because the cgi module blows class Dummy(object): - def get(self, name, default): + def getfirst(self, name, default): return default dummy = Dummy() + def request_creation(doc, cgidata=dummy, errmsg=None): # What virtual domain are we using? hostname = Utils.get_domain() # Set up the document - title = _(f"Create a {hostname} Mailing List") + title = _(f'Create a {hostname} Mailing List') doc.SetTitle(title) - table = Table( - role="table", - aria_label=_("List Creation Form"), - style="border: 1px solid #ccc; border-collapse: collapse; width: 100%;" - ) + table = Table(border=0, width='100%') table.AddRow([Center(Bold(FontAttr(title, size='+1')))]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, - style=f'background-color: {mm_cfg.WEB_HEADER_COLOR}', - role="cell") + bgcolor=mm_cfg.WEB_HEADER_COLOR) # Add any error message if errmsg: table.AddRow([Header(3, Bold( @@ -369,82 +344,61 @@ def request_creation(doc, cgidata=dummy, errmsg=None): # Build the form for the necessary input GREY = mm_cfg.WEB_ADMINITEM_COLOR form = Form(Utils.ScriptURL('create')) - ftable = Table( - role="table", - aria_label=_("List Creation Form Fields"), - style="border: 1px solid #ccc; border-collapse: collapse; width: 100%;" - ) + ftable = Table(border=0, cols='2', width='100%', + cellspacing=3, cellpadding=4) ftable.AddRow([Center(Italic(_('List Identity')))]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, colspan=2, role="cell") + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, colspan=2) - listname = cgidata.get('listname', [''])[0] + listname = cgidata.getfirst('listname', '') + # MAS: Don't websafe twice. TextBox does it. ftable.AddRow([Label(_('Name of list:')), - TextBox('listname', listname, aria_label=_('Name of list'))]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, - style=f'background-color: {GREY}', - role="cell") - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, - style=f'background-color: {GREY}', - role="cell") - - owner = cgidata.get('owner', [''])[0] + TextBox('listname', listname)]) + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY) + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY) + + owner = cgidata.getfirst('owner', '') + # MAS: Don't websafe twice. TextBox does it. ftable.AddRow([Label(_('Initial list owner address:')), - TextBox('owner', owner, aria_label=_('Initial list owner address'))]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, - style=f'background-color: {GREY}', - role="cell") - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, - style=f'background-color: {GREY}', - role="cell") + TextBox('owner', owner)]) + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY) + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY) try: - autogen = int(cgidata.get('autogen', ['0'])[0]) + autogen = int(cgidata.getfirst('autogen', '0')) except ValueError: autogen = 0 ftable.AddRow([Label(_('Auto-generate initial list password?')), RadioButtonArray('autogen', (_('No'), _('Yes')), checked=autogen, - values=(0, 1), - aria_label=_('Auto-generate initial list password'))]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, - style=f'background-color: {GREY}', - role="cell") - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, - style=f'background-color: {GREY}', - role="cell") - - safepasswd = Utils.websafe(cgidata.get('password', [''])[0]) + values=(0, 1))]) + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY) + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY) + + safepasswd = Utils.websafe(cgidata.getfirst('password', '')) ftable.AddRow([Label(_('Initial list password:')), PasswordBox('password', safepasswd)]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, - style=f'background-color: {GREY}', - role="cell") - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, - style=f'background-color: {GREY}', - role="cell") - - safeconfirm = Utils.websafe(cgidata.get('confirm', [''])[0]) + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY) + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY) + + safeconfirm = Utils.websafe(cgidata.getfirst('confirm', '')) ftable.AddRow([Label(_('Confirm initial password:')), PasswordBox('confirm', safeconfirm)]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, - style=f'background-color: {GREY}', - role="cell") - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, - style=f'background-color: {GREY}', - role="cell") + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY) + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY) try: - notify = int(cgidata.get('notify', ['1'])[0]) + notify = int(cgidata.getfirst('notify', '1')) except ValueError: notify = 1 try: - moderate = int(cgidata.get('moderate', ['0'])[0]) + moderate = int(cgidata.getfirst('moderate', + mm_cfg.DEFAULT_DEFAULT_MEMBER_MODERATION)) except ValueError: moderate = mm_cfg.DEFAULT_DEFAULT_MEMBER_MODERATION ftable.AddRow([Center(Italic(_('List Characteristics')))]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, colspan=2, role="cell") + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, colspan=2) ftable.AddRow([ Label(_(f"""Should new members be quarantined before they @@ -453,12 +407,8 @@ def request_creation(doc, cgidata=dummy, errmsg=None): RadioButtonArray('moderate', (_('No'), _('Yes')), checked=moderate, values=(0,1))]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, - style=f'background-color: {GREY}', - role="cell") - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, - style=f'background-color: {GREY}', - role="cell") + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY) + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY) # Create the table of initially supported languages, sorted on the long # name of the language. revmap = {} @@ -481,41 +431,29 @@ def request_creation(doc, cgidata=dummy, errmsg=None): checked[langi] = 1 deflang = _(Utils.GetLanguageDescr(mm_cfg.DEFAULT_SERVER_LANGUAGE)) ftable.AddRow([Label(_( - '''Initial list of supported languages.

    Note that if you do not + f'''Initial list of supported languages.

    Note that if you do not select at least one initial language, the list will use the server default language of {deflang}''')), CheckBoxArray('langs', [_(Utils.GetLanguageDescr(L)) for L in langs], checked=checked, values=langs)]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, - style=f'background-color: {GREY}', - role="cell") - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, - style=f'background-color: {GREY}', - role="cell") + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY) + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY) ftable.AddRow([Label(_('Send "list created" email to list owner?')), RadioButtonArray('notify', (_('No'), _('Yes')), checked=notify, values=(0, 1))]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, - style=f'background-color: {GREY}', - role="cell") - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, - style=f'background-color: {GREY}', - role="cell") + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY) + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY) ftable.AddRow(['


    ']) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, colspan=2, role="cell") + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, colspan=2) ftable.AddRow([Label(_("List creator's (authentication) password:")), PasswordBox('auth')]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, - style=f'background-color: {GREY}', - role="cell") - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, - style=f'background-color: {GREY}', - role="cell") + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY) + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY) ftable.AddRow([Center(SubmitButton('doit', _('Create List'))), Center(SubmitButton('clear', _('Clear Form')))]) diff --git a/Mailman/Cgi/edithtml.py b/Mailman/Cgi/edithtml.py index 750a1239..6fdf7814 100644 --- a/Mailman/Cgi/edithtml.py +++ b/Mailman/Cgi/edithtml.py @@ -19,10 +19,9 @@ from __future__ import print_function import os -import urllib.parse +from Mailman.Utils import FieldStorage import errno import re -import sys from Mailman import Utils from Mailman import MailList @@ -39,6 +38,7 @@ AUTH_CONTEXTS = (mm_cfg.AuthListAdmin, mm_cfg.AuthSiteAdmin) + def main(): # Trick out pygettext since we want to mark template_data as translatable, # but we don't want to actually translate it here. @@ -84,7 +84,7 @@ def _(s): except Errors.MMListError as e: # Avoid cross-site scripting attacks safelistname = Utils.websafe(listname) - doc.AddItem(Header(2, _('No such list {safelistname}'))) + doc.AddItem(Header(2, _(f'No such list {safelistname}'))) # Send this with a 404 status. print('Status: 404 Not Found') print(doc.Format()) @@ -96,18 +96,10 @@ def _(s): doc.set_language(mlist.preferred_language) # Must be authenticated to get any farther + cgidata = FieldStorage() try: - if os.environ.get('REQUEST_METHOD') == 'POST': - content_length = int(os.environ.get('CONTENT_LENGTH', 0)) - if content_length > 0: - form_data = sys.stdin.buffer.read(content_length).decode('utf-8') - cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=True) - else: - cgidata = {} - else: - query_string = os.environ.get('QUERY_STRING', '') - cgidata = urllib.parse.parse_qs(query_string, keep_blank_values=True) - except Exception: + cgidata.getfirst('adminpw', '') + except TypeError: # Someone crafted a POST with a bad Content-Type:. doc.AddItem(Header(2, _("Error"))) doc.AddItem(Bold(_('Invalid options to CGI script.'))) @@ -120,19 +112,19 @@ def _(s): safe_params = ['VARHELP', 'adminpw', 'admlogin'] params = list(cgidata.keys()) if set(params) - set(safe_params): - csrf_checked = csrf_check(mlist, cgidata.get('csrf_token', [''])[0], + csrf_checked = csrf_check(mlist, cgidata.getfirst('csrf_token'), 'admin') else: csrf_checked = True # if password is present, void cookie to force password authentication. - if cgidata.get('adminpw', [''])[0]: + if cgidata.getfirst('adminpw'): os.environ['HTTP_COOKIE'] = '' csrf_checked = True # Editing the html for a list is limited to the list admin and site admin. if not mlist.WebAuthenticate((mm_cfg.AuthListAdmin, mm_cfg.AuthSiteAdmin), - cgidata.get('adminpw', [''])[0]): + cgidata.getfirst('adminpw', '')): if 'admlogin' in cgidata: # This is a re-authorization attempt msg = Bold(FontSize('+1', _('Authorization failed.'))).Format() @@ -149,8 +141,8 @@ def _(s): return # See if the user want to see this page in other language - language = cgidata.get('language', [''])[0] - if language not in mlist.available_languages: + language = cgidata.getfirst('language', '') + if language not in mlist.GetAvailableLanguages(): language = mlist.preferred_language i18n.set_language(language) doc.set_language(language) @@ -162,24 +154,26 @@ def _(s): if template == template_name: template_info = _(info) doc.SetTitle(_( - '{realname} -- Edit html for {template_info}')) + f'{realname} -- Edit html for {template_info}')) break else: # Avoid cross-site scripting attacks safetemplatename = Utils.websafe(template_name) doc.SetTitle(_('Edit HTML : Error')) - doc.AddItem(Header(2, _("{safetemplatename}: Invalid template"))) + doc.AddItem(Header(2, _(f"{safetemplatename}: Invalid template"))) doc.AddItem(mlist.GetMailmanFooter()) print(doc.Format()) return else: - # Use ParseTags for the template selection page - replacements = { - 'realname': realname, - 'templates': template_data - } - output = mlist.ParseTags('edithtml_select.html', replacements, language) - doc.AddItem(output) + doc.SetTitle(_(f'{realname} -- HTML Page Editing')) + doc.AddItem(Header(1, _(f'{realname} -- HTML Page Editing'))) + doc.AddItem(Header(2, _('Select page to edit:'))) + template_list = UnorderedList() + for (template, info) in template_data: + l = Link(mlist.GetScriptURL('edithtml') + '/' + template, _(info)) + template_list.AddItem(l) + doc.AddItem(FontSize("+2", template_list)) + doc.AddItem(mlist.GetMailmanFooter()) print(doc.Format()) return @@ -190,17 +184,15 @@ def _(s): else: doc.addError( _('The form lifetime has expired. (request forgery check)')) - # Use ParseTags for proper template processing - replacements = mlist.GetStandardReplacements(language) - output = mlist.ParseTags(template_name, replacements, language) - doc.AddItem(output) + FormatHTML(mlist, doc, template_name, template_info, lang=language) finally: doc.AddItem(mlist.GetMailmanFooter()) print(doc.Format()) + def FormatHTML(mlist, doc, template_name, template_info, lang=None): - if lang not in mlist.available_languages: + if lang not in mlist.GetAvailableLanguages(): lang = mlist.preferred_language lcset = Utils.GetCharSet(lang) doc.AddItem(Header(1,'%s:' % mlist.real_name)) @@ -217,7 +209,7 @@ def FormatHTML(mlist, doc, template_name, template_info, lang=None): doc.AddItem(FontSize("+1", backlink)) doc.AddItem('

    ') doc.AddItem('


    ') - if len(mlist.available_languages) > 1: + if len(mlist.GetAvailableLanguages()) > 1: langform = Form(mlist.GetScriptURL('edithtml') + '/' + template_name, mlist=mlist, contexts=AUTH_CONTEXTS) langform.AddItem( @@ -239,8 +231,9 @@ def FormatHTML(mlist, doc, template_name, template_info, lang=None): doc.AddItem(form) + def ChangeHTML(mlist, cgi_info, template_name, doc, lang=None): - if lang not in mlist.available_languages: + if lang not in mlist.GetAvailableLanguages(): lang = mlist.preferred_language if 'html_code' not in cgi_info: doc.AddItem(Header(3,_("Can't have empty html page."))) diff --git a/Mailman/Cgi/listinfo.py b/Mailman/Cgi/listinfo.py index 2c16acfe..62daf739 100644 --- a/Mailman/Cgi/listinfo.py +++ b/Mailman/Cgi/listinfo.py @@ -23,11 +23,8 @@ from builtins import str import os -import urllib.parse +from Mailman.Utils import FieldStorage import time -import sys -import ipaddress -from io import FileNotFoundError from Mailman import mm_cfg from Mailman import Utils @@ -35,99 +32,54 @@ from Mailman import Errors from Mailman import i18n from Mailman.htmlformat import * -from Mailman.Logging.Syslog import mailman_log -from Mailman.Utils import validate_ip_address +from Mailman.Logging.Syslog import syslog # Set up i18n _ = i18n._ i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) -def validate_listname(listname): - """Validate and sanitize a listname to prevent path traversal. - - Args: - listname: The listname to validate - - Returns: - tuple: (is_valid, sanitized_name, error_message) - """ - if not listname: - return False, None, _('List name is required') - - # Convert to lowercase and strip whitespace - listname = listname.lower().strip() - - # Basic validation - if not Utils.ValidateListName(listname): - return False, None, _('Invalid list name') - - # Check for path traversal attempts - if '..' in listname or '/' in listname or '\\' in listname: - return False, None, _('Invalid list name') - - return True, listname, None - - + def main(): parts = Utils.GetPathPieces() if not parts: listinfo_overview() return - # Validate and sanitize listname - is_valid, listname, error_msg = validate_listname(parts[0]) - if not is_valid: - print('Status: 400 Bad Request') - listinfo_overview(error_msg) - return - + listname = parts[0].lower() try: mlist = MailList.MailList(listname, lock=0) - except (Errors.MMListError, FileNotFoundError) as e: - # Avoid cross-site scripting attacks and information disclosure + except Errors.MMListError as e: + # Avoid cross-site scripting attacks safelistname = Utils.websafe(listname) # Send this with a 404 status. print('Status: 404 Not Found') listinfo_overview(_(f'No such list {safelistname}')) - mailman_log('error', 'listinfo: No such list "%s"', listname) - return - except Exception as e: - # Log the full error but don't expose it to the user - mailman_log('error', 'listinfo: Unexpected error for list "%s": %s', listname, str(e)) - print('Status: 500 Internal Server Error') - listinfo_overview(_('An error occurred processing your request')) + syslog('error', 'listinfo: No such list "%s": %s', listname, e) return # See if the user want to see this page in other language + cgidata = FieldStorage() try: - if os.environ.get('REQUEST_METHOD') == 'POST': - # Get the content length - content_length = int(os.environ.get('CONTENT_LENGTH', 0)) - # Read the form data - form_data = sys.stdin.read(content_length) - cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=True) - else: - query_string = os.environ.get('QUERY_STRING', '') - cgidata = urllib.parse.parse_qs(query_string, keep_blank_values=True) - except Exception as e: - # Log the error but don't expose details - mailman_log('error', 'listinfo: Error parsing form data: %s', str(e)) + language = cgidata.getfirst('language') + except TypeError: + # Someone crafted a POST with a bad Content-Type:. doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) doc.AddItem(Header(2, _("Error"))) - doc.AddItem(Bold(_('Invalid request.'))) + doc.AddItem(Bold(_('Invalid options to CGI script.'))) + # Send this with a 400 status. print('Status: 400 Bad Request') print(doc.Format()) return - language = cgidata.get('language', [None])[0] if not Utils.IsLanguage(language): language = mlist.preferred_language i18n.set_language(language) list_listinfo(mlist, language) + def listinfo_overview(msg=''): # Present the general listinfo overview hostname = Utils.get_domain() @@ -222,31 +174,27 @@ def listinfo_overview(msg=''): print(doc.Format()) -def list_listinfo(mlist, language): + +def list_listinfo(mlist, lang): # Generate list specific listinfo doc = HeadlessDocument() - doc.set_language(language) + doc.set_language(lang) - # First load the template - template_content, template_path = Utils.findtext('listinfo.html', lang=language, mlist=mlist) - if template_content is None: - mailman_log('error', 'Could not load template file: %s', template_path) - return - - # Then get replacements - replacements = mlist.GetStandardReplacements(language) + replacements = mlist.GetStandardReplacements(lang) - if not mlist.nondigestable: + if not mlist.digestable or not mlist.nondigestable: replacements[''] = "" replacements[''] = "" replacements[''] = '' else: replacements[''] = mlist.FormatDigestButton() - replacements[''] = mlist.FormatUndigestButton() + replacements[''] = \ + mlist.FormatUndigestButton() replacements[''] = '' replacements[''] = '' - replacements[''] = mlist.FormatPlainDigestsButton() + replacements[''] = \ + mlist.FormatPlainDigestsButton() replacements[''] = mlist.FormatMimeDigestsButton() replacements[''] = mlist.FormatBox('email', size=30) replacements[''] = mlist.FormatButton( @@ -256,81 +204,78 @@ def list_listinfo(mlist, language): replacements[''] = mlist.FormatFormStart( 'subscribe') if mm_cfg.SUBSCRIBE_FORM_SECRET: - # Get and validate IP address - ip = os.environ.get('REMOTE_ADDR', '') - is_valid, normalized_ip = validate_ip_address(ip) - if not is_valid: - ip = '' + now = str(int(time.time())) + remote = os.environ.get('HTTP_FORWARDED_FOR', + os.environ.get('HTTP_X_FORWARDED_FOR', + os.environ.get('REMOTE_ADDR', + 'w.x.y.z'))) + # Try to accept a range in case of load balancers, etc. (LP: #1447445) + if remote.find('.') >= 0: + # ipv4 - drop last octet + remote = remote.rsplit('.', 1)[0] else: - ip = normalized_ip + # ipv6 - drop last 16 (could end with :: in which case we just + # drop one : resulting in an invalid format, but it's only + # for our hash so it doesn't matter. + remote = remote.rsplit(':', 1)[0] # render CAPTCHA, if configured if isinstance(mm_cfg.CAPTCHAS, dict) and 'en' in mm_cfg.CAPTCHAS: (captcha_question, captcha_box, captcha_idx) = \ - Utils.captcha_display(mlist, language, mm_cfg.CAPTCHAS) + Utils.captcha_display(mlist, lang, mm_cfg.CAPTCHAS) pre_question = _( """Please answer the following question to prove that you are not a bot:""" ) replacements[''] = ( - """
    """ + """""" % (pre_question, captcha_question, captcha_box)) else: # just to have something to include in the hash below captcha_idx = '' + secret = mm_cfg.SUBSCRIBE_FORM_SECRET + ":" + now + ":" + captcha_idx + ":" + mlist.internal_name() + ":" + remote + hash_secret = Utils.sha_new(secret.encode('utf-8')).hexdigest() # fill form replacements[''] += ( '\n' - % (time.time(), captcha_idx, - Utils.sha_new((mm_cfg.SUBSCRIBE_FORM_SECRET + ":" + - str(time.time()) + ":" + - captcha_idx + ":" + - mlist.internal_name() + ":" + - ip).encode('utf-8')).hexdigest() - ) - ) + % (now, captcha_idx, hash_secret )) # Roster form substitutions replacements[''] = mlist.FormatFormStart('roster') + replacements[''] = mlist.FormatRosterOptionForUser(lang) # Options form substitutions replacements[''] = mlist.FormatFormStart('options') - replacements[''] = mlist.FormatEditingOption(language) + replacements[''] = mlist.FormatEditingOption(lang) replacements[''] = SubmitButton('UserOptions', _('Edit Options')).Format() # If only one language is enabled for this mailing list, omit the choice # buttons. - if len(mlist.available_languages) == 1: - listlangs = _(Utils.GetLanguageDescr(mlist.preferred_language)) + if len(mlist.GetAvailableLanguages()) == 1: + displang = '' else: - listlangs = mlist.GetLangSelectBox(language).Format() - replacements[''] = listlangs + displang = mlist.FormatButton('displang-button', + text = _("View this page in")) + replacements[''] = displang replacements[''] = mlist.FormatFormStart('listinfo') replacements[''] = mlist.FormatBox('fullname', size=30) # If reCAPTCHA is enabled, display its user interface if mm_cfg.RECAPTCHA_SITE_KEY: noscript = _('This form requires JavaScript.') replacements[''] = ( - """""" - % (noscript, language, mm_cfg.RECAPTCHA_SITE_KEY)) + % (noscript, lang, mm_cfg.RECAPTCHA_SITE_KEY)) else: replacements[''] = '' - # Process the template with replacements - try: - # Use ParseTags for proper template processing - output = mlist.ParseTags('listinfo.html', replacements, language) - doc.AddItem(output) - except Exception as e: - mailman_log('error', 'Error processing template: %s', str(e)) - return - - # Print the formatted document + # Do the expansion. + doc.AddItem(mlist.ParseTags('listinfo.html', replacements, lang)) print(doc.Format()) + if __name__ == "__main__": main() diff --git a/Mailman/Cgi/options.py b/Mailman/Cgi/options.py index ec25bf48..c8e1ced6 100644 --- a/Mailman/Cgi/options.py +++ b/Mailman/Cgi/options.py @@ -24,8 +24,9 @@ import re import sys import os -import urllib.parse +from Mailman.Utils import FieldStorage import signal +import urllib.request, urllib.parse, urllib.error from Mailman import mm_cfg from Mailman import Utils @@ -34,9 +35,8 @@ from Mailman import MemberAdaptor from Mailman import i18n from Mailman.htmlformat import * -from Mailman.Logging.Syslog import syslog, mailman_log +from Mailman.Logging.Syslog import syslog from Mailman.CSRFcheck import csrf_check -import traceback OR = '|' SLASH = '/' @@ -62,7 +62,7 @@ def main(): title = _('CGI script error') doc.SetTitle(title) doc.AddItem(Header(2, title)) - doc.addError(_('Invalid request method: %(method)s') % {'method': method}) + doc.addError(_(f'Invalid request method: {method}')) doc.AddItem('
    ') doc.AddItem(MailmanLogo()) print('Status: 405 Method Not Allowed') @@ -92,36 +92,17 @@ def main(): title = _('CGI script error') doc.SetTitle(title) doc.AddItem(Header(2, title)) - doc.addError(_('No such list %(safelistname)s') % {'safelistname': safelistname}) + doc.addError(_(f'No such list {safelistname}')) doc.AddItem('
    ') doc.AddItem(MailmanLogo()) # Send this with a 404 status. print('Status: 404 Not Found') print(doc.Format()) - mailman_log('error', 'options: No such list "%s": %s\n%s', listname, e, traceback.format_exc()) + syslog('error', 'options: No such list "%s": %s\n', listname, e) return # The total contents of the user's response - try: - if os.environ.get('REQUEST_METHOD') == 'POST': - content_length = int(os.environ.get('CONTENT_LENGTH', 0)) - if content_length > 0: - form_data = sys.stdin.buffer.read(content_length).decode('utf-8') - cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=True) - else: - cgidata = {} - else: - query_string = os.environ.get('QUERY_STRING', '') - cgidata = urllib.parse.parse_qs(query_string, keep_blank_values=True) - except Exception: - # Someone crafted a POST with a bad Content-Type:. - doc.AddItem(Header(2, _("Error"))) - doc.AddItem(Bold(_('Invalid options to CGI script.'))) - # Send this with a 400 status. - print('Status: 400 Bad Request') - print(doc.Format()) - mailman_log('error', 'options: Invalid form data: %s\n%s', str(e), traceback.format_exc()) - return + cgidata = FieldStorage(keep_blank_values=1) # CSRF check safe_params = ['displang-button', 'language', 'email', 'password', 'login', @@ -137,22 +118,25 @@ def main(): print(doc.Format()) return - # Set the language for the page - language = cgidata.get('language', [None])[0] + # Set the language for the page. If we're coming from the listinfo cgi, + # we might have a 'language' key in the cgi data. That was an explicit + # preference to view the page in, so we should honor that here. If that's + # not available, use the list's default language. + language = cgidata.getfirst('language') if not Utils.IsLanguage(language): language = mlist.preferred_language i18n.set_language(language) doc.set_language(language) if lenparts < 2: - user = cgidata.get('email', [''])[0].strip() + user = cgidata.getfirst('email', '').strip() if not user: # If we're coming from the listinfo page and we left the email # address field blank, it's not an error. Likewise if we're # coming from anywhere else. Only issue the error if we came # via one of our buttons. - if (cgidata.get('login', [''])[0] or cgidata.get('login-unsub', [''])[0] - or cgidata.get('login-remind', [''])[0]): + if (cgidata.getfirst('login') or cgidata.getfirst('login-unsub') + or cgidata.getfirst('login-remind')): doc.addError(_('No address given')) loginpage(mlist, doc, None, language) print(doc.Format()) @@ -162,6 +146,7 @@ def main(): # If a user submits a form or URL with post data or query fragments # with multiple occurrences of the same variable, we can get a list # here. Be as careful as possible. + # This is no longer required because of getfirst() above, but leave it. if isinstance(user, list) or isinstance(user, tuple): if len(user) == 0: user = '' @@ -180,19 +165,19 @@ def main(): # using public rosters, otherwise, we'll leak membership information. if not mlist.isMember(user): if mlist.private_roster == 0: - doc.addError(_('No such member: %(safeuser)s.') % {'safeuser': safeuser}) + doc.addError(_(f'No such member: {safeuser}.')) loginpage(mlist, doc, None, language) print(doc.Format()) - return + return # Avoid cross-site scripting attacks if set(params) - set(safe_params): - csrf_checked = csrf_check(mlist, cgidata.get('csrf_token', [''])[0], + csrf_checked = csrf_check(mlist, cgidata.getfirst('csrf_token'), Utils.UnobscureEmail(urllib.parse.unquote(user))) else: csrf_checked = True # if password is present, void cookie to force password authentication. - if cgidata.get('password', [''])[0]: + if cgidata.getfirst('password'): os.environ['HTTP_COOKIE'] = '' csrf_checked = True @@ -209,7 +194,7 @@ def main(): # And now we know the user making the request, so set things up to for the # user's stored preferred language, overridden by any form settings for # their new language preference. - userlang = cgidata.get('language', [None])[0] + userlang = cgidata.getfirst('language') if not Utils.IsLanguage(userlang): userlang = mlist.getMemberLanguage(user) doc.set_language(userlang) @@ -218,7 +203,7 @@ def main(): # Are we processing an unsubscription request from the login screen? msgc = _('If you are a list member, a confirmation email has been sent.') msgb = _('You already have a subscription pending confirmation') - msga = _("""If you are a list member, your unsubscription request has been + msga = _(f"""If you are a list member, your unsubscription request has been forwarded to the list administrator for approval.""") if 'login-unsub' in cgidata: # Because they can't supply a password for unsubscribing, we'll need @@ -248,11 +233,11 @@ def main(): # Not a member if mlist.private_roster == 0: # Public rosters - doc.addError(_('No such member: {safeuser}.')) + doc.addError(_(f'No such member: {safeuser}.')) else: - mailman_log('mischief', - 'Unsub attempt of non-member w/ private rosters: %s\n%s', - user, traceback.format_exc()) + syslog('mischief', + 'Unsub attempt of non-member w/ private rosters: %s', + user) if mlist.unsubscribe_policy: doc.addError(msga, tag='') else: @@ -272,18 +257,18 @@ def main(): # Not a member if mlist.private_roster == 0: # Public rosters - doc.addError(_('No such member: {safeuser}.')) + doc.addError(_(f'No such member: {safeuser}.')) else: - mailman_log('mischief', - 'Reminder attempt of non-member w/ private rosters: %s\n%s', - user, traceback.format_exc()) + syslog('mischief', + 'Reminder attempt of non-member w/ private rosters: %s', + user) doc.addError(msg, tag='') loginpage(mlist, doc, user, language) print(doc.Format()) return # Get the password from the form. - password = cgidata.get('password', [''])[0].strip() + password = cgidata.getfirst('password', '').strip() # Check authentication. We need to know if the credentials match the user # or the site admin, because they are the only ones who are allowed to # change things globally. Specifically, the list admin may not change @@ -310,15 +295,15 @@ def main(): os.environ.get('HTTP_X_FORWARDED_FOR', os.environ.get('REMOTE_ADDR', 'unidentified origin'))) - mailman_log('security', - 'Authorization failed (options): user=%s: list=%s: remote=%s\n%s', - user, listname, remote, traceback.format_exc()) + syslog('security', + 'Authorization failed (options): user=%s: list=%s: remote=%s', + user, listname, remote) # So as not to allow membership leakage, prompt for the email # address and the password here. if mlist.private_roster != 0: - mailman_log('mischief', - 'Login failure with private rosters: %s from %s\n%s', - user, remote, traceback.format_exc()) + syslog('mischief', + 'Login failure with private rosters: %s from %s', + user, remote) user = None # give an HTTP 401 for authentication failure print('Status: 401 Unauthorized') @@ -350,12 +335,12 @@ def main(): # See if this is VARHELP on topics. varhelp = None if 'VARHELP' in cgidata: - varhelp = cgidata['VARHELP'][0] + varhelp = cgidata['VARHELP'].value elif os.environ.get('QUERY_STRING'): # POST methods, even if their actions have a query string, don't get # put into FieldStorage's keys :-( - qs = cgidata.get('VARHELP') - if qs and isinstance(qs, list): + qs = urllib.parse.parse_qs(os.environ['QUERY_STRING']).get('VARHELP') + if qs and type(qs) == list: varhelp = qs[0] if varhelp: # Sanitize the topic name. @@ -416,18 +401,18 @@ def main(): if 'change-of-address' in cgidata: # We could be changing the user's full name, email address, or both. # Watch out for non-ASCII characters in the member's name. - membername = cgidata.get('fullname', [''])[0] + membername = cgidata.getfirst('fullname') # Canonicalize the member's name membername = Utils.canonstr(membername, language) - newaddr = cgidata.get('new-address', [''])[0] - confirmaddr = cgidata.get('confirm-address', [''])[0] + newaddr = cgidata.getfirst('new-address') + confirmaddr = cgidata.getfirst('confirm-address') oldname = mlist.getMemberName(user) set_address = set_membername = 0 # See if the user wants to change their email address globally. The # list admin is /not/ allowed to make global changes. - globally = cgidata.get('changeaddr-globally', [''])[0] + globally = cgidata.getfirst('changeaddr-globally') if globally and not is_user_or_siteadmin: doc.addError(_(f"""The list administrator may not change the names or addresses for this user's other subscriptions. However, the @@ -478,7 +463,7 @@ def main(): else: options_page( mlist, doc, user, cpuser, userlang, - _('The new address is already a member: {newaddr}')) + _(f'The new address is already a member: {newaddr}')) print(doc.Format()) return set_address = 1 @@ -498,7 +483,7 @@ def sigterm_handler(signum, frame, mlist=mlist): if cpuser is None: cpuser = user # Register the pending change after the list is locked - msg += _('A confirmation message has been sent to {newaddr}. ') + msg += _(f'A confirmation message has been sent to {newaddr}. ') mlist.Lock() try: try: @@ -511,7 +496,7 @@ def sigterm_handler(signum, frame, mlist=mlist): except Errors.MMHostileAddress: msg = _('Illegal email address provided') except Errors.MMAlreadyAMember: - msg = _('{newaddr} is already a member of the list.') + msg = _(f'{newaddr} is already a member of the list.') except Errors.MembershipIsBanned: owneraddr = mlist.GetOwnerEmail() msg = _(f"""{newaddr} is banned from this list. If you @@ -540,8 +525,8 @@ def sigterm_handler(signum, frame, mlist=mlist): options_page(mlist, doc, user, cpuser, userlang) print(doc.Format()) return - newpw = cgidata.get('newpw', [''])[0].strip() - confirmpw = cgidata.get('confpw', [''])[0].strip() + newpw = cgidata.getfirst('newpw', '').strip() + confirmpw = cgidata.getfirst('confpw', '').strip() if not newpw or not confirmpw: options_page(mlist, doc, user, cpuser, userlang, _('Passwords may not be blank')) @@ -555,7 +540,7 @@ def sigterm_handler(signum, frame, mlist=mlist): # See if the user wants to change their passwords globally, however # the list admin is /not/ allowed to change passwords globally. - pw_globally = cgidata.get('pw-globally', [''])[0] + pw_globally = cgidata.getfirst('pw-globally') if pw_globally and not is_user_or_siteadmin: doc.addError(_(f"""The list administrator may not change the password for this user's other subscriptions. However, the @@ -580,7 +565,7 @@ def sigterm_handler(signum, frame, mlist=mlist): if 'unsub' in cgidata: # Was the confirming check box turned on? - if not cgidata.get('unsubconfirm', [0])[0]: + if not cgidata.getfirst('unsubconfirm'): options_page( mlist, doc, user, cpuser, userlang, _(f'''You must confirm your unsubscription request by turning @@ -662,7 +647,7 @@ def sigterm_handler(signum, frame, mlist=mlist): ('nodupes', mm_cfg.DontReceiveDuplicates), ): try: - newval = int(cgidata.get(item, [''])[0]) + newval = int(cgidata.getfirst(item)) except (TypeError, ValueError): newval = None @@ -690,7 +675,7 @@ def sigterm_handler(signum, frame, mlist=mlist): newvals.append((flag, newval)) # The user language is handled a little differently - if userlang not in mlist.available_languages: + if userlang not in mlist.GetAvailableLanguages(): newvals.append((SETLANGUAGE, mlist.preferred_language)) else: newvals.append((SETLANGUAGE, userlang)) @@ -698,7 +683,7 @@ def sigterm_handler(signum, frame, mlist=mlist): # Process user selected topics, but don't make the changes to the # MailList object; we must do that down below when the list is # locked. - topicnames = cgidata.get('usertopic', [''])[0] + topicnames = cgidata.getvalue('usertopic') if topicnames: # Some topics were selected. topicnames can actually be a string # or a list of strings depending on whether more than one topic @@ -752,7 +737,7 @@ def __bool__(self): # The enable/disable option and the password remind option may have # their global flags sets. - if cgidata.get('deliver-globally', [''])[0]: + if cgidata.getfirst('deliver-globally'): # Yes, this is inefficient, but the list is so small it shouldn't # make much of a difference. for flag, newval in newvals: @@ -760,19 +745,19 @@ def __bool__(self): globalopts.enable = newval break - if cgidata.get('remind-globally', [''])[0]: + if cgidata.getfirst('remind-globally'): for flag, newval in newvals: if flag == mm_cfg.SuppressPasswordReminder: globalopts.remind = newval break - if cgidata.get('nodupes-globally', [''])[0]: + if cgidata.getfirst('nodupes-globally'): for flag, newval in newvals: if flag == mm_cfg.DontReceiveDuplicates: globalopts.nodupes = newval break - if cgidata.get('mime-globally', [''])[0]: + if cgidata.getfirst('mime-globally'): for flag, newval in newvals: if flag == mm_cfg.DisableMime: globalopts.mime = newval @@ -816,83 +801,7 @@ def __bool__(self): print(doc.Format()) -def process_form(mlist, cgidata, doc, form): - """Process the form submission.""" - # Get the user's email address - email = cgidata.get('email', [''])[0] - if isinstance(email, bytes): - email = email.decode('utf-8', 'replace') - email = email.strip().lower() - - # Get the user's password - password = cgidata.get('password', [''])[0] - if isinstance(password, bytes): - password = password.decode('utf-8', 'replace') - password = password.strip() - - # Get the user's full name - fullname = cgidata.get('fullname', [''])[0] - if isinstance(fullname, bytes): - fullname = fullname.decode('utf-8', 'replace') - fullname = fullname.strip() - - # Get the user's options - options = {} - for key in cgidata: - if key.startswith('option_'): - value = cgidata.get(key, [''])[0] - if isinstance(value, bytes): - value = value.decode('utf-8', 'replace') - options[key[7:]] = value.strip() - - # Validate the email address - if not email: - doc.addError(_('You must provide an email address')) - return - - if not Utils.ValidateEmail(email): - doc.addError(_('Invalid email address: %(email)s') % {'email': email}) - return - - # Validate the password - if not password: - doc.addError(_('You must provide a password')) - return - - # Validate the full name - if not fullname: - doc.addError(_('You must provide your full name')) - return - - # Try to get the member - try: - member = mlist.getMember(email) - except Errors.NotAMemberError: - doc.addError(_('You are not a member of this list')) - return - - # Verify the password - if not mlist.Authenticate((email, password)): - doc.addError(_('Invalid password')) - return - - # Update the member's options - try: - mlist.Lock() - try: - member.setFullName(fullname) - for key, value in options.items(): - member.setOption(key, value) - finally: - mlist.Unlock() - except Exception as e: - doc.addError(_('Error updating options: %(error)s') % {'error': str(e)}) - return - - # Show success message - doc.addItem(_('Your options have been updated')) - - + def options_page(mlist, doc, user, cpuser, userlang, message=''): # The bulk of the document will come from the options.html template, which # includes it's own html armor (head tags, etc.). Suppress the head that @@ -987,10 +896,7 @@ def options_page(mlist, doc, user, cpuser, userlang, message=''): units = _('days') else: units = _('day') - replacements[''] = _('%(days)d %(units)s') % { - 'days': days, - 'units': units - } + replacements[''] = _(f'%(days)d {units}') replacements[''] = mlist.FormatBox('new-address') replacements[''] = mlist.FormatBox( @@ -1006,28 +912,20 @@ def options_page(mlist, doc, user, cpuser, userlang, message=''): # but the user still wants to get that topic message? usertopics = mlist.getMemberTopics(user) if mlist.topics: - table = Table( - role="table", - aria_label=_("Topic Filter Details"), - border=3, - width='100%' - ) - table.AddRow([Center(Bold(_('Topic filter details')))]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, - style=f'background-color: {mm_cfg.WEB_SUBHEADER_COLOR}', - role="cell") - table.AddRow([Bold(Label(_('Name:'))), - Utils.websafe(name)]) - table.AddRow([Bold(Label(_('Pattern (as regexp):'))), - '
    ' + Utils.websafe(OR.join(pattern.splitlines()))
    -                       + '
    ']) - table.AddRow([Bold(Label(_('Description:'))), - Utils.websafe(description)]) - # Make colors look nice - for row in range(1, 4): - table.AddCellInfo(row, 0, - style=f'background-color: {mm_cfg.WEB_ADMINITEM_COLOR}', - role="cell") + table = Table(border="0") + for name, pattern, description, emptyflag in mlist.topics: + if emptyflag: + continue + quotedname = urllib.parse.quote_plus(name) + details = Link(mlist.GetScriptURL('options') + + '/%s/?VARHELP=%s' % (user, quotedname), + ' (Details)') + if name in usertopics: + checked = 1 + else: + checked = 0 + table.AddRow([CheckBox('usertopic', quotedname, checked=checked), + name + details.Format()]) topicsfield = table.Format() else: topicsfield = _('No topics defined') @@ -1049,36 +947,28 @@ def options_page(mlist, doc, user, cpuser, userlang, message=''): page_text = DIGRE.sub('', page_text) doc.AddItem(page_text) - + def loginpage(mlist, doc, user, lang): realname = mlist.real_name actionurl = mlist.GetScriptURL('options') if user is None: - title = _('{realname} list: member options login page') + title = _(f'{realname} list: member options login page') extra = _('email address and ') else: safeuser = Utils.websafe(user) - title = _('{realname} list: member options for user {safeuser}') + title = _(f'{realname} list: member options for user {safeuser}') obuser = Utils.ObscureEmail(user) extra = '' # Set up the title doc.SetTitle(title) # We use a subtable here so we can put a language selection box in - table = Table( - role="table", - aria_label=_("Member Options"), - width='100%', - border=0, - cellspacing=4, - cellpadding=5 - ) + table = Table(width='100%', border=0, cellspacing=4, cellpadding=5) # If only one language is enabled for this mailing list, omit the choice # buttons. table.AddRow([Center(Header(2, title))]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, - style=f'background-color: {mm_cfg.WEB_HEADER_COLOR}', - role="cell") - if len(mlist.available_languages) > 1: + bgcolor=mm_cfg.WEB_HEADER_COLOR) + if len(mlist.GetAvailableLanguages()) > 1: langform = Form(actionurl) langform.AddItem(SubmitButton('displang-button', _('View this page in'))) @@ -1091,14 +981,7 @@ def loginpage(mlist, doc, user, lang): # Set up the login page form = Form(actionurl) form.AddItem(Hidden('language', lang)) - table = Table( - role="table", - aria_label=_("Login Form"), - width='100%', - border=0, - cellspacing=4, - cellpadding=5 - ) + table = Table(width='100%', border=0, cellspacing=4, cellpadding=5) table.AddRow([_(f"""In order to change your membership option, you must first log in by giving your {extra}membership password in the section below. If you don't remember your membership password, you can have it @@ -1111,14 +994,7 @@ def loginpage(mlist, doc, user, lang): effect. """)]) # Password and login button - ptable = Table( - role="table", - aria_label=_("Password Form"), - width='50%', - border=0, - cellspacing=4, - cellpadding=5 - ) + ptable = Table(width='50%', border=0, cellspacing=4, cellpadding=5) if user is None: ptable.AddRow([Label(_('Email address:')), TextBox('email', size=20)]) @@ -1132,8 +1008,7 @@ def loginpage(mlist, doc, user, lang): # Unsubscribe section table.AddRow([Center(Header(2, _('Unsubscribe')))]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, - style=f'background-color: {mm_cfg.WEB_HEADER_COLOR}', - role="cell") + bgcolor=mm_cfg.WEB_HEADER_COLOR) table.AddRow([_(f"""By clicking on the Unsubscribe button, a confirmation message will be emailed to you. This message will have a @@ -1145,8 +1020,7 @@ def loginpage(mlist, doc, user, lang): # Password reminder section table.AddRow([Center(Header(2, _('Password reminder')))]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, - style=f'background-color: {mm_cfg.WEB_HEADER_COLOR}', - role="cell") + bgcolor=mm_cfg.WEB_HEADER_COLOR) table.AddRow([_(f"""By clicking on the Remind button, your password will be emailed to you.""")]) @@ -1158,6 +1032,7 @@ def loginpage(mlist, doc, user, lang): doc.AddItem(mlist.GetMailmanFooter()) + def lists_of_member(mlist, user): hostname = mlist.host_name onlists = [] @@ -1174,6 +1049,7 @@ def lists_of_member(mlist, user): return onlists + def change_password(mlist, user, newpw, confirmpw): # This operation requires the list lock, so let's set up the signal # handling so the list lock will get released when the user hits the @@ -1200,16 +1076,15 @@ def sigterm_handler(signum, frame, mlist=mlist): mlist.Unlock() + def global_options(mlist, user, globalopts): # Is there anything to do? - has_changes = False for attr in dir(globalopts): if attr.startswith('_'): continue if getattr(globalopts, attr) is not None: - has_changes = True break - if not has_changes: + else: return def sigterm_handler(signum, frame, mlist=mlist): @@ -1228,20 +1103,24 @@ def sigterm_handler(signum, frame, mlist=mlist): if globalopts.enable is not None: mlist.setDeliveryStatus(user, globalopts.enable) + if globalopts.remind is not None: mlist.setMemberOption(user, mm_cfg.SuppressPasswordReminder, - globalopts.remind) + globalopts.remind) + if globalopts.nodupes is not None: mlist.setMemberOption(user, mm_cfg.DontReceiveDuplicates, - globalopts.nodupes) + globalopts.nodupes) + if globalopts.mime is not None: - mlist.setMemberOption(user, mm_cfg.DisableMime, - globalopts.mime) + mlist.setMemberOption(user, mm_cfg.DisableMime, globalopts.mime) + mlist.Save() finally: mlist.Unlock() + def topic_details(mlist, doc, user, cpuser, userlang, varhelp): # Find out which topic the user wants to get details of reflist = varhelp.split('/') @@ -1257,20 +1136,14 @@ def topic_details(mlist, doc, user, cpuser, userlang, varhelp): if not name: options_page(mlist, doc, user, cpuser, userlang, - _('Requested topic is not valid: {topicname}')) + _(f'Requested topic is not valid: {topicname}')) print(doc.Format()) return - table = Table( - role="table", - aria_label=_("Topic Filter Details"), - border=3, - width='100%' - ) + table = Table(border=3, width='100%') table.AddRow([Center(Bold(_('Topic filter details')))]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, - style=f'background-color: {mm_cfg.WEB_SUBHEADER_COLOR}', - role="cell") + bgcolor=mm_cfg.WEB_SUBHEADER_COLOR) table.AddRow([Bold(Label(_('Name:'))), Utils.websafe(name)]) table.AddRow([Bold(Label(_('Pattern (as regexp):'))), @@ -1280,9 +1153,7 @@ def topic_details(mlist, doc, user, cpuser, userlang, varhelp): Utils.websafe(description)]) # Make colors look nice for row in range(1, 4): - table.AddCellInfo(row, 0, - style=f'background-color: {mm_cfg.WEB_ADMINITEM_COLOR}', - role="cell") + table.AddCellInfo(row, 0, bgcolor=mm_cfg.WEB_ADMINITEM_COLOR) options_page(mlist, doc, user, cpuser, userlang, table.Format()) print(doc.Format()) diff --git a/Mailman/Cgi/private.py b/Mailman/Cgi/private.py index ada0815c..034ed6f4 100644 --- a/Mailman/Cgi/private.py +++ b/Mailman/Cgi/private.py @@ -20,7 +20,7 @@ import os import sys -import urllib.parse +from Mailman.Utils import FieldStorage import mimetypes from Mailman import mm_cfg @@ -39,60 +39,23 @@ SLASH = '/' -def validate_listname(listname): - """Validate and sanitize a listname to prevent path traversal. - - Args: - listname: The listname to validate - - Returns: - tuple: (is_valid, sanitized_name, error_message) - """ - if not listname: - return False, None, _('List name is required') - - # Convert to lowercase and strip whitespace - listname = listname.lower().strip() - - # Basic validation - if not Utils.ValidateListName(listname): - return False, None, _('Invalid list name') - - # Check for path traversal attempts - if '..' in listname or '/' in listname or '\\' in listname: - return False, None, _('Invalid list name') - - return True, listname, None - - + def true_path(path): - """Ensure that the path is safe by removing .. and other dangerous components. - - Args: - path: The path to sanitize - - Returns: - str: The sanitized path or None if invalid - """ - if not path: - return None - - # Remove any leading/trailing slashes - path = path.strip('/') - - # Split into components and filter out dangerous parts - parts = [x for x in path.split('/') if x and x not in ('.', '..')] - - # Reconstruct the path - return '/'.join(parts) + "Ensure that the path is safe by removing .." + # Workaround for path traverse vulnerability. Unsuccessful attempts will + # be logged in logs/error. + parts = [x for x in path.split(SLASH) if x not in ('.', '..')] + return SLASH.join(parts)[1:] + def guess_type(url, strict): if hasattr(mimetypes, 'common_types'): return mimetypes.guess_type(url, strict) return mimetypes.guess_type(url) + def main(): doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) @@ -104,69 +67,61 @@ def main(): print(doc.Format()) return - # Validate listname - is_valid, listname, error_msg = validate_listname(parts[0]) - if not is_valid: - doc.SetTitle(_("Private Archive Error")) - doc.AddItem(Header(3, error_msg)) - print('Status: 400 Bad Request') - print(doc.Format()) - syslog('mischief', 'Private archive invalid path: %s', parts[0]) - return - - # Validate and sanitize the full path - path = os.environ.get('PATH_INFO', '') + path = os.environ.get('PATH_INFO') tpath = true_path(path) - if not tpath: - msg = _('Private archive - Invalid path') + if tpath != path[1:]: + msg = _('Private archive - "./" and "../" not allowed in URL.') doc.SetTitle(msg) doc.AddItem(Header(2, msg)) - print('Status: 400 Bad Request') print(doc.Format()) - syslog('mischief', 'Private archive invalid path: %s', path) + syslog('mischief', 'Private archive hostile path: %s', path) return - # BAW: This needs to be converted to the Site module abstraction - true_filename = os.path.join(mm_cfg.PRIVATE_ARCHIVE_FILE_DIR, tpath) + true_filename = os.path.join( + mm_cfg.PRIVATE_ARCHIVE_FILE_DIR, tpath) + + listname = parts[0].lower() + mboxfile = '' + if len(parts) > 1: + mboxfile = parts[1] + + # See if it's the list's mbox file is being requested + if listname.endswith('.mbox') and mboxfile.endswith('.mbox') and \ + listname[:-5] == mboxfile[:-5]: + listname = listname[:-5] + else: + mboxfile = '' + + # If it's a directory, we have to append index.html in this script. We + # must also check for a gzipped file, because the text archives are + # usually stored in compressed form. + if os.path.isdir(true_filename): + true_filename = true_filename + '/index.html' + if not os.path.exists(true_filename) and \ + os.path.exists(true_filename + '.gz'): + true_filename = true_filename + '.gz' try: mlist = MailList.MailList(listname, lock=0) except Errors.MMListError as e: - # Avoid cross-site scripting attacks and information disclosure + # Avoid cross-site scripting attacks safelistname = Utils.websafe(listname) - msg = _('No such list {safelistname}') - doc.SetTitle(_("Private Archive Error - {msg}")) + msg = _(f'No such list {safelistname}') + doc.SetTitle(_(f"Private Archive Error - {msg}")) doc.AddItem(Header(2, msg)) # Send this with a 404 status. print('Status: 404 Not Found') print(doc.Format()) - syslog('error', 'private: No such list "%s"', listname) - return - except Exception as e: - # Log the full error but don't expose it to the user - syslog('error', 'private: Unexpected error for list "%s": %s', listname, str(e)) - doc.SetTitle(_("Private Archive Error")) - doc.AddItem(Header(2, _('An error occurred processing your request'))) - print('Status: 500 Internal Server Error') - print(doc.Format()) + syslog('error', 'private: No such list "%s": %s\n', listname, e) return i18n.set_language(mlist.preferred_language) doc.set_language(mlist.preferred_language) - # Parse form data + cgidata = FieldStorage() try: - if os.environ.get('REQUEST_METHOD') == 'POST': - content_length = int(os.environ.get('CONTENT_LENGTH', 0)) - if content_length > 0: - form_data = sys.stdin.buffer.read(content_length).decode('utf-8') - cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=True) - else: - cgidata = {} - else: - query_string = os.environ.get('QUERY_STRING', '') - cgidata = urllib.parse.parse_qs(query_string, keep_blank_values=True) - except Exception: + username = cgidata.getfirst('username', '').strip() + except TypeError: # Someone crafted a POST with a bad Content-Type:. doc.AddItem(Header(2, _("Error"))) doc.AddItem(Bold(_('Invalid options to CGI script.'))) @@ -174,9 +129,7 @@ def main(): print('Status: 400 Bad Request') print(doc.Format()) return - - username = cgidata.get('username', [''])[0].strip() - password = cgidata.get('password', [''])[0] + password = cgidata.getfirst('password', '') is_auth = 0 realname = mlist.real_name @@ -219,10 +172,11 @@ def main(): # Output the password form charset = Utils.GetCharSet(mlist.preferred_language) print('Content-type: text/html; charset=' + charset + '\n\n') - print('') # Put the original full path in the authorization form, but avoid # trailing slash if we're not adding parts. We add it below. action = mlist.GetScriptURL('private', absolute=1) + if mboxfile: + action += '.mbox' if parts[1:]: action = os.path.join(action, SLASH.join(parts[1:])) # If we added '/index.html' to true_filename, add a slash to the URL. @@ -234,15 +188,13 @@ def main(): # page don't work. if true_filename.endswith('/index.html') and parts[-1] != 'index.html': action += SLASH - # Use ParseTags for proper template processing - replacements = { - 'action': Utils.websafe(action), - 'realname': mlist.real_name, - 'message': message - } - # Use list's preferred language as fallback before authentication - output = mlist.ParseTags('private.html', replacements, mlist.preferred_language) - print(output) + # Escape web input parameter to avoid cross-site scripting. + print(Utils.maketext( + 'private.html', + {'action' : Utils.websafe(action), + 'realname': mlist.real_name, + 'message' : message, + }, mlist=mlist)) return lang = mlist.getMemberLanguage(username) @@ -254,11 +206,15 @@ def main(): ctype, enc = guess_type(path, strict=0) if ctype is None: ctype = 'text/html' - if true_filename.endswith('.gz'): + if mboxfile: + f = open(os.path.join(mlist.archive_dir() + '.mbox', + mlist.internal_name() + '.mbox')) + ctype = 'text/plain' + elif true_filename.endswith('.gz'): import gzip f = gzip.open(true_filename, 'r') else: - f = open(true_filename, 'r') + f = open(true_filename, 'rb') except IOError: msg = _('Private archive file not found') doc.SetTitle(msg) @@ -267,6 +223,16 @@ def main(): print(doc.Format()) syslog('error', 'Private archive file not found: %s', true_filename) else: - print('Content-type: %s\n' % ctype) - sys.stdout.write(f.read()) + content = f.read() f.close() + buffered = sys.stdout.getvalue() + sys.stdout.truncate(0) + sys.stdout.seek(0) + orig_stdout = sys.stdout + sys.stdout = sys.__stdout__ + sys.stdout.write(buffered) + print('Content-type: %s\n' % ctype) + sys.stdout.flush() + sys.stdout.buffer.write(content) + sys.stdout.flush() + sys.stdout = orig_stdout diff --git a/Mailman/Cgi/rmlist.py b/Mailman/Cgi/rmlist.py index cd84142c..103cc4f7 100644 --- a/Mailman/Cgi/rmlist.py +++ b/Mailman/Cgi/rmlist.py @@ -18,7 +18,7 @@ from __future__ import print_function import os -import urllib.parse +from Mailman.Utils import FieldStorage import sys import errno import shutil @@ -36,22 +36,15 @@ i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + def main(): doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + cgidata = FieldStorage() try: - if os.environ.get('REQUEST_METHOD') == 'POST': - content_length = int(os.environ.get('CONTENT_LENGTH', 0)) - if content_length > 0: - form_data = sys.stdin.buffer.read(content_length).decode('utf-8') - cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=True) - else: - cgidata = {} - else: - query_string = os.environ.get('QUERY_STRING', '') - cgidata = urllib.parse.parse_qs(query_string, keep_blank_values=True) - except Exception: + cgidata.getfirst('password', '') + except TypeError: # Someone crafted a POST with a bad Content-Type:. doc.AddItem(Header(2, _("Error"))) doc.AddItem(Bold(_('Invalid options to CGI script.'))) @@ -80,8 +73,8 @@ def main(): except Errors.MMListError as e: # Avoid cross-site scripting attacks safelistname = Utils.websafe(listname) - title = _('No such list {safelistname}') - doc.SetTitle(_('No such list {safelistname}')) + title = _(f'No such list {safelistname}') + doc.SetTitle(_(f'No such list {safelistname}')) doc.AddItem( Header(3, Bold(FontAttr(title, color='#ff0000', size='+2')))) @@ -119,10 +112,11 @@ def main(): print(doc.Format()) + def process_request(doc, cgidata, mlist): - password = cgidata.get('password', [''])[0].strip() + password = cgidata.getfirst('password', '').strip() try: - delarchives = int(cgidata.get('delarchives', ['0'])[0]) + delarchives = int(cgidata.getfirst('delarchives', '0')) except ValueError: delarchives = 0 @@ -186,16 +180,10 @@ def process_request(doc, cgidata, mlist): title = _('Mailing list deletion results') doc.SetTitle(title) - table = Table( - role="table", - aria_label=_("List Deletion Results"), - border=0, - width='100%' - ) + table = Table(border=0, width='100%') table.AddRow([Center(Bold(FontAttr(title, size='+1')))]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, - style=f'background-color: {mm_cfg.WEB_HEADER_COLOR}', - role="cell") + bgcolor=mm_cfg.WEB_HEADER_COLOR) if not problems: table.AddRow([_(f'''You have successfully deleted the mailing list {listname}.''')]) @@ -215,21 +203,16 @@ def process_request(doc, cgidata, mlist): doc.AddItem(MailmanLogo()) + def request_deletion(doc, mlist, errmsg=None): realname = mlist.real_name - title = _('Permanently remove mailing list {realname}') - doc.SetTitle(_('Permanently remove mailing list {realname}')) + title = _(f'Permanently remove mailing list {realname}') + doc.SetTitle(_(f'Permanently remove mailing list {realname}')) - table = Table( - role="table", - aria_label=_("List Deletion Form"), - border=0, - width='100%' - ) + table = Table(border=0, width='100%') table.AddRow([Center(Bold(FontAttr(title, size='+1')))]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, - style=f'background-color: {mm_cfg.WEB_HEADER_COLOR}', - role="cell") + bgcolor=mm_cfg.WEB_HEADER_COLOR) # Add any error message if errmsg: @@ -255,38 +238,26 @@ def request_deletion(doc, mlist, errmsg=None): """)]) GREY = mm_cfg.WEB_ADMINITEM_COLOR form = Form(mlist.GetScriptURL('rmlist')) - ftable = Table( - role="table", - aria_label=_("List Deletion Form Fields"), - border=0, - cols='2', - width='100%', - cellspacing=3, - cellpadding=4 - ) + ftable = Table(border=0, cols='2', width='100%', + cellspacing=3, cellpadding=4) ftable.AddRow([Label(_('List password:')), PasswordBox('password')]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, - style=f'background-color: {GREY}', - role="cell") - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, - style=f'background-color: {GREY}', - role="cell") + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY) + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY) - ftable.AddRow([Label(_('Delete archives?')), - RadioButtonArray('delarchives', - (_('No'), _('Yes')), - checked=0, - values=(0, 1))]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, - style=f'background-color: {GREY}', - role="cell") - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, - style=f'background-color: {GREY}', - role="cell") + ftable.AddRow([Label(_('Also delete archives?')), + RadioButtonArray('delarchives', (_('No'), _('Yes')), + checked=0, values=(0, 1))]) + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY) + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY) - ftable.AddRow([Center(SubmitButton('doit', _('Delete List')))]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, colspan=2, role="cell") + ftable.AddRow([Center(Link( + mlist.GetScriptURL('admin'), + _('Cancel and return to list administration')))]) + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, colspan=2) + + ftable.AddRow([Center(SubmitButton('doit', _('Delete this list')))]) + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, colspan=2) form.AddItem(ftable) table.AddRow([form]) doc.AddItem(table) diff --git a/Mailman/Cgi/roster.py b/Mailman/Cgi/roster.py index f7a30950..d90e4de6 100644 --- a/Mailman/Cgi/roster.py +++ b/Mailman/Cgi/roster.py @@ -26,7 +26,7 @@ import sys import os -import urllib.parse +from Mailman.Utils import FieldStorage import urllib.request, urllib.parse, urllib.error from Mailman import mm_cfg @@ -42,6 +42,7 @@ i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + def main(): parts = Utils.GetPathPieces() if not parts: @@ -56,23 +57,16 @@ def main(): safelistname = Utils.websafe(listname) # Send this with a 404 status. print('Status: 404 Not Found') - error_page(_('No such list {safelistname}')) + error_page(_(f'No such list {safelistname}')) syslog('error', 'roster: No such list "%s": %s', listname, e) return - # Parse form data + cgidata = FieldStorage() + + # messages in form should go in selected language (if any...) try: - if os.environ.get('REQUEST_METHOD') == 'POST': - content_length = int(os.environ.get('CONTENT_LENGTH', 0)) - if content_length > 0: - form_data = sys.stdin.buffer.read(content_length).decode('utf-8') - cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=True) - else: - cgidata = {} - else: - query_string = os.environ.get('QUERY_STRING', '') - cgidata = urllib.parse.parse_qs(query_string, keep_blank_values=True) - except Exception: + lang = cgidata.getfirst('language') + except TypeError: # Someone crafted a POST with a bad Content-Type:. doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) @@ -83,8 +77,6 @@ def main(): print(doc.Format()) return - # messages in form should go in selected language (if any...) - lang = cgidata.get('language', [None])[0] if not Utils.IsLanguage(lang): lang = mlist.preferred_language i18n.set_language(lang) @@ -94,8 +86,8 @@ def main(): # "admin"-only, then we try to cookie authenticate the user, and failing # that, we check roster-email and roster-pw fields for a valid password. # (also allowed: the list moderator, the list admin, and the site admin). - password = cgidata.get('roster-pw', [''])[0].strip() - addr = cgidata.get('roster-email', [''])[0].strip() + password = cgidata.getfirst('roster-pw', '').strip() + addr = cgidata.getfirst('roster-email', '').strip() list_hidden = (not mlist.WebAuthenticate((mm_cfg.AuthUser,), password, addr) and mlist.WebAuthenticate((mm_cfg.AuthListModerator, @@ -124,7 +116,7 @@ def main(): doc.set_language(lang) # Send this with a 401 status. print('Status: 401 Unauthorized') - error_page_doc(doc, _('{realname} roster authentication failed.')) + error_page_doc(doc, _(f'{realname} roster authentication failed.')) doc.AddItem(mlist.GetMailmanFooter()) print(doc.Format()) remote = os.environ.get('HTTP_FORWARDED_FOR', @@ -149,6 +141,7 @@ def main(): print(doc.Format()) + def error_page(errmsg): doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) diff --git a/Mailman/Cgi/subscribe.py b/Mailman/Cgi/subscribe.py index 4c39b6b1..1aff1ef8 100644 --- a/Mailman/Cgi/subscribe.py +++ b/Mailman/Cgi/subscribe.py @@ -20,22 +20,22 @@ import sys import os +from Mailman.Utils import FieldStorage import time import signal -import urllib.parse +import urllib.request, urllib.parse, urllib.error +import urllib.request, urllib.error, urllib.parse import json -import ipaddress from Mailman import mm_cfg from Mailman import Utils from Mailman import MailList from Mailman import Errors from Mailman import i18n -from Mailman.Message import Message +from Mailman import Message from Mailman.UserDesc import UserDesc from Mailman.htmlformat import * -from Mailman.Logging.Syslog import mailman_log -from Mailman.Utils import validate_ip_address +from Mailman.Logging.Syslog import syslog SLASH = '/' ERRORSEP = '\n\n

    ' @@ -46,32 +46,7 @@ i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) -def validate_listname(listname): - """Validate and sanitize a listname to prevent path traversal. - - Args: - listname: The listname to validate - - Returns: - tuple: (is_valid, sanitized_name, error_message) - """ - if not listname: - return False, None, _('List name is required') - - # Convert to lowercase and strip whitespace - listname = listname.lower().strip() - - # Basic validation - if not Utils.ValidateListName(listname): - return False, None, _('Invalid list name') - - # Check for path traversal attempts - if '..' in listname or '/' in listname or '\\' in listname: - return False, None, _('Invalid list name') - - return True, listname, None - - + def main(): doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) @@ -80,63 +55,28 @@ def main(): if not parts: doc.AddItem(Header(2, _("Error"))) doc.AddItem(Bold(_('Invalid options to CGI script'))) - print('Status: 400 Bad Request') - print(doc.Format()) - return - - # Validate listname - is_valid, listname, error_msg = validate_listname(parts[0]) - if not is_valid: - doc.AddItem(Header(2, _("Error"))) - doc.AddItem(Bold(error_msg)) - print('Status: 400 Bad Request') print(doc.Format()) return + listname = parts[0].lower() try: mlist = MailList.MailList(listname, lock=0) except Errors.MMListError as e: - # Avoid cross-site scripting attacks and information disclosure + # Avoid cross-site scripting attacks safelistname = Utils.websafe(listname) doc.AddItem(Header(2, _("Error"))) - doc.AddItem(Bold(_('No such list {safelistname}'))) + doc.AddItem(Bold(_(f'No such list {safelistname}'))) # Send this with a 404 status. print('Status: 404 Not Found') print(doc.Format()) - mailman_log('error', 'subscribe: No such list "%s"', listname) - return - except Exception as e: - # Log the full error but don't expose it to the user - mailman_log('error', 'subscribe: Unexpected error for list "%s": %s', listname, str(e)) - doc.AddItem(Header(2, _("Error"))) - doc.AddItem(Bold(_('An error occurred processing your request'))) - print('Status: 500 Internal Server Error') - print(doc.Format()) + syslog('error', 'subscribe: No such list "%s": %s\n', listname, e) return # See if the form data has a preferred language set, in which case, use it # for the results. If not, use the list's preferred language. + cgidata = FieldStorage() try: - if os.environ.get('REQUEST_METHOD') == 'POST': - # Get the content length - content_length = int(os.environ.get('CONTENT_LENGTH', 0)) - # Read the form data - form_data = sys.stdin.read(content_length) - cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=True) - else: - query_string = os.environ.get('QUERY_STRING', '') - cgidata = urllib.parse.parse_qs(query_string, keep_blank_values=True) - except Exception as e: - # Log the error but don't expose details - mailman_log('error', 'subscribe: Error parsing form data: %s', str(e)) - doc.AddItem(Header(2, _("Error"))) - doc.AddItem(Bold(_('Invalid request'))) - print('Status: 400 Bad Request') - print(doc.Format()) - return - - try: - language = cgidata.get('language', [''])[0] + language = cgidata.getfirst('language', '') except TypeError: # Someone crafted a POST with a bad Content-Type:. doc.AddItem(Header(2, _("Error"))) @@ -153,6 +93,11 @@ def main(): # We need a signal handler to catch the SIGTERM that can come from Apache # when the user hits the browser's STOP button. See the comment in # admin.py for details. + # + # BAW: Strictly speaking, the list should not need to be locked just to + # read the request database. However the request database asserts that + # the list is locked in order to load it and it's not worth complicating + # that logic. def sigterm_handler(signum, frame, mlist=mlist): # Make sure the list gets unlocked... mlist.Unlock() @@ -161,28 +106,29 @@ def sigterm_handler(signum, frame, mlist=mlist): # could be bad! sys.exit(0) - # Install the emergency shutdown signal handler - signal.signal(signal.SIGTERM, sigterm_handler) + mlist.Lock() + try: + # Install the emergency shutdown signal handler + signal.signal(signal.SIGTERM, sigterm_handler) - process_form(mlist, doc, cgidata, language) + process_form(mlist, doc, cgidata, language) + mlist.Save() + finally: + mlist.Unlock() + def process_form(mlist, doc, cgidata, lang): listowner = mlist.GetOwnerEmail() realname = mlist.real_name results = [] # The email address being subscribed, required - email = cgidata.get('email', [''])[0] - if isinstance(email, bytes): - email = email.decode('utf-8', 'replace') - email = email.strip().lower() + email = cgidata.getfirst('email', '').strip() if not email: results.append(_('You must supply a valid email address.')) - fullname = cgidata.get('fullname', [''])[0] - if isinstance(fullname, bytes): - fullname = fullname.decode('utf-8', 'replace') + fullname = cgidata.getfirst('fullname', '') # Canonicalize the full name fullname = Utils.canonstr(fullname, lang) # Who was doing the subscribing? @@ -193,12 +139,9 @@ def process_form(mlist, doc, cgidata, lang): # Check reCAPTCHA submission, if enabled if mm_cfg.RECAPTCHA_SECRET_KEY: - recaptcha_response = cgidata.get('g-recaptcha-response', [''])[0] - if isinstance(recaptcha_response, bytes): - recaptcha_response = recaptcha_response.decode('utf-8', 'replace') request_data = urllib.parse.urlencode({ 'secret': mm_cfg.RECAPTCHA_SECRET_KEY, - 'response': recaptcha_response, + 'response': cgidata.getvalue('g-recaptcha-response', ''), 'remoteip': remote}) request_data = request_data.encode('utf-8') request = urllib.request.Request( @@ -210,64 +153,58 @@ def process_form(mlist, doc, cgidata, lang): httpresp.close() if not captcha_response['success']: e_codes = COMMASPACE.join(captcha_response['error-codes']) - results.append(_('reCAPTCHA validation failed: {}').format(e_codes)) + results.append(_(f'reCAPTCHA validation failed: {e_codes}')) except urllib.error.URLError as e: e_reason = e.reason - results.append(_('reCAPTCHA could not be validated: {e_reason}')) - - # Get and validate IP address - ip = os.environ.get('REMOTE_ADDR', '') - is_valid, normalized_ip = validate_ip_address(ip) - if not is_valid: - ip = '' - else: - ip = normalized_ip + results.append(_(f'reCAPTCHA could not be validated: {e_reason}')) # Are we checking the hidden data? if mm_cfg.SUBSCRIBE_FORM_SECRET: now = int(time.time()) # Try to accept a range in case of load balancers, etc. (LP: #1447445) - if ip.find('.') >= 0: + if remote.find('.') >= 0: # ipv4 - drop last octet - remote1 = ip.rsplit('.', 1)[0] + remote1 = remote.rsplit('.', 1)[0] else: # ipv6 - drop last 16 (could end with :: in which case we just # drop one : resulting in an invalid format, but it's only # for our hash so it doesn't matter. - remote1 = ip.rsplit(':', 1)[0] + remote1 = remote.rsplit(':', 1)[0] try: - sub_form_token = cgidata.get('sub_form_token', [''])[0] - if isinstance(sub_form_token, bytes): - sub_form_token = sub_form_token.decode('utf-8', 'replace') - ftime, fcaptcha_idx, fhash = sub_form_token.split(':') + ftime, fcaptcha_idx, fhash = cgidata.getfirst( + 'sub_form_token', '').split(':') then = int(ftime) except ValueError: ftime = fcaptcha_idx = fhash = '' then = 0 - needs_hashing = (mm_cfg.SUBSCRIBE_FORM_SECRET + ":" + ftime + ":" + fcaptcha_idx + - ":" + mlist.internal_name() + ":" + remote1).encode('utf-8') + needs_hashing = (mm_cfg.SUBSCRIBE_FORM_SECRET + ":" + ftime + ":" + fcaptcha_idx + ":" + mlist.internal_name() + ":" + remote1).encode('utf-8') token = Utils.sha_new(needs_hashing).hexdigest() if ftime and now - then > mm_cfg.FORM_LIFETIME: results.append(_('The form is too old. Please GET it again.')) if ftime and now - then < mm_cfg.SUBSCRIBE_FORM_MIN_TIME: - results.append(_('The form was submitted too quickly. Please wait a moment and try again.')) + results.append( + _('Please take a few seconds to fill out the form before submitting it.')) if ftime and token != fhash: - results.append(_('The form was tampered with. Please GET it again.')) - + results.append( + _("The hidden token didn't match. Did your IP change?")) + if not ftime: + results.append( + _('There was no hidden token in your submission or it was corrupted.')) + results.append(_('You must GET the form before submitting it.')) + # Check captcha + if isinstance(mm_cfg.CAPTCHAS, dict): + captcha_answer = cgidata.getvalue('captcha_answer', '') + if not Utils.captcha_verify( + fcaptcha_idx, captcha_answer, mm_cfg.CAPTCHAS): + results.append(_( + 'This was not the right answer to the CAPTCHA question.')) # Was an attempt made to subscribe the list to itself? if email == mlist.GetListEmail(): - mailman_log('mischief', 'Attempt to self subscribe %s: %s', email, remote) + syslog('mischief', 'Attempt to self subscribe %s: %s', email, remote) results.append(_('You may not subscribe a list to itself!')) # If the user did not supply a password, generate one for him - password = cgidata.get('pw', [''])[0] - if isinstance(password, bytes): - password = password.decode('utf-8', 'replace') - password = password.strip() - - confirmed = cgidata.get('pw-conf', [''])[0] - if isinstance(confirmed, bytes): - confirmed = confirmed.decode('utf-8', 'replace') - confirmed = confirmed.strip() + password = cgidata.getfirst('pw', '').strip() + confirmed = cgidata.getfirst('pw-conf', '').strip() if not password and not confirmed: password = Utils.MakeRandomPassword() @@ -277,9 +214,7 @@ def process_form(mlist, doc, cgidata, lang): results.append(_('Your passwords did not match.')) # Get the digest option for the subscription. - digestflag = cgidata.get('digest', [''])[0] - if isinstance(digestflag, bytes): - digestflag = digestflag.decode('utf-8', 'replace') + digestflag = cgidata.getfirst('digest') if digestflag: try: digest = int(digestflag) @@ -317,13 +252,12 @@ def process_form(mlist, doc, cgidata, lang): moderator. If confirmation is required, you will soon get a confirmation email which contains further instructions.""") - # Acquire the lock before attempting to add the member - mlist.Lock() try: userdesc = UserDesc(email, fullname, password, digest, lang) mlist.AddMember(userdesc, remote) results = '' - mlist.Save() + # Check for all the errors that mlist.AddMember can throw options on the + # web page for this cgi except Errors.MembershipIsBanned: results = _(f"""The email address you supplied is banned from this mailing list. If you think this restriction is erroneous, please @@ -375,7 +309,7 @@ def process_form(mlist, doc, cgidata, lang): otrans = i18n.get_translation() i18n.set_language(mlang) try: - msg = Mailman.Message.UserNotification( + msg = Message.UserNotification( mlist.getMemberCPAddress(email), mlist.GetBouncesEmail(), _('Mailman privacy alert'), @@ -409,12 +343,11 @@ def process_form(mlist, doc, cgidata, lang): else: results = _(f"""\ You have been successfully subscribed to the {realname} mailing list.""") - finally: - mlist.Unlock() # Show the results print_results(mlist, results, doc, lang) + def print_results(mlist, results, doc, lang): # The bulk of the document will come from the options.html template, which # includes its own html armor (head tags, etc.). Suppress the head that diff --git a/Mailman/Commands/cmd_confirm.py b/Mailman/Commands/cmd_confirm.py index 1ef3e4b3..c7d3aeb3 100644 --- a/Mailman/Commands/cmd_confirm.py +++ b/Mailman/Commands/cmd_confirm.py @@ -43,6 +43,8 @@ def process(res, args): res.results.append(gethelp(mlist)) return STOP cookie = args[0] + if isinstance(cookie, bytes): + cookie = cookie.decode() try: results = mlist.ProcessConfirmation(cookie, res.msg) except Errors.MMBadConfirmation as e: @@ -53,29 +55,29 @@ def process(res, args): approximately %(days)s days after the initial request. They also expire if the request has already been handled in some way. If your confirmation has expired, please try to re-submit your original request or message.""")) - except Errors.MMNeedApproval: + except Errors.MMNeedApproval as e: res.results.append(_("""\ Your request has been forwarded to the list moderator for approval.""")) - except Errors.MMAlreadyAMember: + except Errors.MMAlreadyAMember as e: # Some other subscription request for this address has # already succeeded. res.results.append(_('You are already subscribed.')) - except Errors.NotAMemberError: + except Errors.NotAMemberError as e: # They've already been unsubscribed res.results.append(_("""\ You are not currently a member. Have you already unsubscribed or changed your email address?""")) - except Errors.MembershipIsBanned: + except Errors.MembershipIsBanned as e: owneraddr = mlist.GetOwnerEmail() res.results.append(_("""\ You are currently banned from subscribing to this list. If you think this restriction is erroneous, please contact the list owners at %(owneraddr)s.""")) - except Errors.HostileSubscriptionError: + except Errors.HostileSubscriptionError as e: res.results.append(_("""\ You were not invited to this mailing list. The invitation has been discarded, and both list administrators have been alerted.""")) - except Errors.MMBadPasswordError: + except Errors.MMBadPasswordError as e: res.results.append(_("""\ Bad approval password given. Held message is still being held.""")) else: diff --git a/Mailman/Commands/cmd_set.py b/Mailman/Commands/cmd_set.py index 66d2e978..91bad858 100644 --- a/Mailman/Commands/cmd_set.py +++ b/Mailman/Commands/cmd_set.py @@ -117,7 +117,7 @@ def process(self, res, args): res.results.append(_(DETAILS)) return STOP subcmd = args.pop(0) - methname = 'set_' + subcmd + methname = 'set_' + subcmd.decode('utf-8') if isinstance(subcmd, bytes) else 'set_' + subcmd method = getattr(self, methname, None) if method is None: res.results.append(_('Bad set command: %(subcmd)s')) diff --git a/Mailman/Commands/cmd_subscribe.py b/Mailman/Commands/cmd_subscribe.py index 8e4c9443..8a2404af 100644 --- a/Mailman/Commands/cmd_subscribe.py +++ b/Mailman/Commands/cmd_subscribe.py @@ -52,7 +52,8 @@ def process(res, args): realname = None # Parse the args argnum = 0 - for arg in args: + for arg_bytes in args: + arg = arg_bytes.decode('utf-8') if arg.lower().startswith('address='): address = arg[8:] elif argnum == 0: @@ -94,11 +95,7 @@ def process(res, args): # Watch for encoded names try: h = make_header(decode_header(realname)) - # Get the realname from the header - try: - realname = str(h) - except UnicodeError: - realname = str(h, 'utf-8', 'replace') + realname = h.__str__() except UnicodeError: realname = u'' # Coerce to byte string if uh contains only ascii diff --git a/Mailman/Commands/cmd_unsubscribe.py b/Mailman/Commands/cmd_unsubscribe.py index 686b274a..38d5ec2c 100644 --- a/Mailman/Commands/cmd_unsubscribe.py +++ b/Mailman/Commands/cmd_unsubscribe.py @@ -43,7 +43,8 @@ def process(res, args): password = None address = None argnum = 0 - for arg in args: + for arg_bytes in args: + arg = arg_bytes.decode('utf-8') if arg.startswith('address='): address = arg[8:] elif argnum == 0: diff --git a/Mailman/Defaults.py.in b/Mailman/Defaults.py.in index d62f0c4f..697af315 100755 --- a/Mailman/Defaults.py.in +++ b/Mailman/Defaults.py.in @@ -18,6 +18,7 @@ # USA. """Distributed default settings for significant Mailman config variables.""" +from __future__ import absolute_import # NEVER make site configuration changes to this file. ALWAYS make them in # mm_cfg.py instead, in the designated area. See the comments in that file @@ -177,7 +178,7 @@ HTML_TO_PLAIN_TEXT_COMMAND = '/usr/bin/lynx -dump %(filename)s' # A Python regular expression character class which defines the characters # allowed in list names. Lists cannot be created with names containing any # character that doesn't match this class. Do not include '/' in this list. -ACCEPTABLE_LISTNAME_CHARACTERS = r'[-+_.=a-z0-9]' +ACCEPTABLE_LISTNAME_CHARACTERS = '[-+_.=a-z0-9]' # The number of characters in the longest listname in the installation. The # fix for LP: #1780874 truncates list names in web URLs to this length to avoid @@ -261,7 +262,7 @@ KNOWN_SPAMMERS = [] # normalized unicodes against normalized unicode headers. This setting # determines the normalization form. It is one of 'NFC', 'NFD', 'NFKC' or # 'NFKD'. See -# https://docs.python.org/3/library/unicodedata.html#unicodedata.normalize +# https://docs.python.org/2/library/unicodedata.html#unicodedata.normalize NORMALIZE_FORM = 'NFKC' @@ -631,7 +632,7 @@ NNTP_USERNAME = None NNTP_PASSWORD = None # Set this if you have an NNTP server you prefer gatewayed lists to use. -DEFAULT_NNTP_HOST = None +DEFAULT_NNTP_HOST = '' # These variables controls how headers must be cleansed in order to be # accepted by your NNTP server. Some servers like INN reject messages @@ -932,16 +933,21 @@ DEFAULT_RESPOND_TO_POST_REQUESTS = Yes # BAW: Eventually we may support weighted hash spaces. # BAW: Although not enforced, the # of slices must be a power of 2 +# Distribution method for queue runners: 'hash' (default) or 'round_robin' +# Hash-based distribution ensures same message always goes to same runner +# Round-robin distribution provides more even load distribution +QUEUE_DISTRIBUTION_METHOD = 'hash' + QRUNNERS = [ ('ArchRunner', 1), # messages for the archiver ('BounceRunner', 2), # for processing the qfile/bounces directory ('CommandRunner', 1), # commands and bounces from the outside world ('IncomingRunner', 4), # posts from the outside world ('NewsRunner', 1), # outgoing messages to the nntpd - ('OutgoingRunner', 1), # outgoing messages to the smtpd (single instance with process coordination) + ('OutgoingRunner', 8), # outgoing messages to the smtpd ('VirginRunner', 1), # internally crafted (virgin birth) messages ('RetryRunner', 1), # retry temporarily failed deliveries -] + ] # Set this to Yes to use the `Maildir' delivery option. If you change this # you will need to re-run bin/genaliases for MTAs that don't use list @@ -1780,7 +1786,7 @@ SITE_PW_FILE = os.path.join(DATA_DIR, 'adm.pw') LISTCREATOR_PW_FILE = os.path.join(DATA_DIR, 'creator.pw') # Import a bunch of version numbers -from Version import * +from .Version import * # Vgg: Language descriptions and charsets dictionary, any new supported # language must have a corresponding entry here. Key is the name of the @@ -1797,41 +1803,41 @@ def add_language(code, description, charset, direction='ltr'): LC_DESCRIPTIONS[code] = (description, charset, direction) add_language('ar', _('Arabic'), 'utf-8', 'rtl') -add_language('ast', _('Asturian'), 'iso-8859-1', 'ltr') +add_language('ast', _('Asturian'), 'utf-8', 'ltr') add_language('ca', _('Catalan'), 'utf-8', 'ltr') -add_language('cs', _('Czech'), 'iso-8859-2', 'ltr') -add_language('da', _('Danish'), 'iso-8859-1', 'ltr') -add_language('de', _('German'), 'iso-8859-1', 'ltr') -add_language('en', _('English (USA)'), 'us-ascii', 'ltr') +add_language('cs', _('Czech'), 'utf-8', 'ltr') +add_language('da', _('Danish'), 'utf-8', 'ltr') +add_language('de', _('German'), 'utf-8', 'ltr') +add_language('en', _('English (USA)'), 'utf-8', 'ltr') add_language('eo', _('Esperanto'), 'utf-8', 'ltr') -add_language('es', _('Spanish (Spain)'), 'iso-8859-1', 'ltr') -add_language('et', _('Estonian'), 'iso-8859-15', 'ltr') -add_language('eu', _('Euskara'), 'iso-8859-15', 'ltr') # Basque +add_language('es', _('Spanish (Spain)'), 'utf-8', 'ltr') +add_language('et', _('Estonian'), 'utf-8', 'ltr') +add_language('eu', _('Euskara'), 'utf-8', 'ltr') # Basque add_language('fa', _('Persian'), 'utf-8', 'rtl') -add_language('fi', _('Finnish'), 'iso-8859-1', 'ltr') -add_language('fr', _('French'), 'iso-8859-1', 'ltr') +add_language('fi', _('Finnish'), 'utf-8', 'ltr') +add_language('fr', _('French'), 'utf-8', 'ltr') add_language('gl', _('Galician'), 'utf-8', 'ltr') -add_language('el', _('Greek'), 'iso-8859-7', 'ltr') +add_language('el', _('Greek'), 'utf-8', 'ltr') add_language('he', _('Hebrew'), 'utf-8', 'rtl') -add_language('hr', _('Croatian'), 'iso-8859-2', 'ltr') -add_language('hu', _('Hungarian'), 'iso-8859-2', 'ltr') -add_language('ia', _('Interlingua'), 'iso-8859-15', 'ltr') -add_language('it', _('Italian'), 'iso-8859-1', 'ltr') -add_language('ja', _('Japanese'), 'euc-jp', 'ltr') -add_language('ko', _('Korean'), 'euc-kr', 'ltr') -add_language('lt', _('Lithuanian'), 'iso-8859-13', 'ltr') -add_language('nl', _('Dutch'), 'iso-8859-1', 'ltr') -add_language('no', _('Norwegian'), 'iso-8859-1', 'ltr') -add_language('pl', _('Polish'), 'iso-8859-2', 'ltr') -add_language('pt', _('Portuguese'), 'iso-8859-1', 'ltr') -add_language('pt_BR', _('Portuguese (Brazil)'), 'iso-8859-1', 'ltr') +add_language('hr', _('Croatian'), 'utf-8', 'ltr') +add_language('hu', _('Hungarian'), 'utf-8', 'ltr') +add_language('ia', _('Interlingua'), 'utf-8', 'ltr') +add_language('it', _('Italian'), 'utf-8', 'ltr') +add_language('ja', _('Japanese'), 'utf-8', 'ltr') +add_language('ko', _('Korean'), 'utf-8', 'ltr') +add_language('lt', _('Lithuanian'), 'utf-8', 'ltr') +add_language('nl', _('Dutch'), 'utf-8', 'ltr') +add_language('no', _('Norwegian'), 'utf-8', 'ltr') +add_language('pl', _('Polish'), 'utf-8', 'ltr') +add_language('pt', _('Portuguese'), 'utf-8', 'ltr') +add_language('pt_BR', _('Portuguese (Brazil)'), 'utf-8', 'ltr') add_language('ro', _('Romanian'), 'utf-8', 'ltr') add_language('ru', _('Russian'), 'utf-8', 'ltr') add_language('sk', _('Slovak'), 'utf-8', 'ltr') -add_language('sl', _('Slovenian'), 'iso-8859-2', 'ltr') +add_language('sl', _('Slovenian'), 'utf-8', 'ltr') add_language('sr', _('Serbian'), 'utf-8', 'ltr') -add_language('sv', _('Swedish'), 'iso-8859-1', 'ltr') -add_language('tr', _('Turkish'), 'iso-8859-9', 'ltr') +add_language('sv', _('Swedish'), 'utf-8', 'ltr') +add_language('tr', _('Turkish'), 'utf-8', 'ltr') add_language('uk', _('Ukrainian'), 'utf-8', 'ltr') add_language('vi', _('Vietnamese'), 'utf-8', 'ltr') add_language('zh_CN', _('Chinese (China)'), 'utf-8', 'ltr') diff --git a/Mailman/Deliverer.py b/Mailman/Deliverer.py index d9e0be15..a4790c1f 100644 --- a/Mailman/Deliverer.py +++ b/Mailman/Deliverer.py @@ -26,77 +26,22 @@ from Mailman import mm_cfg from Mailman import Errors from Mailman import Utils -from Mailman.Message import Message, UserNotification +from Mailman import Message from Mailman import i18n from Mailman import Pending from Mailman.Logging.Syslog import syslog _ = i18n._ -import sys -import os -import time -import email -import errno -import pickle -import email.message -from email.message import Message -from email.header import decode_header, make_header, Header -from email.errors import HeaderParseError -from email.iterators import typed_subpart_iterator - -from Mailman.htmlformat import * -from Mailman.Logging.Syslog import mailman_log -from Mailman.Utils import validate_ip_address -import Mailman.Handlers.Replybot as Replybot -from Mailman.i18n import _ -from Mailman import LockFile - -# Lazy imports to avoid circular dependencies -def get_replybot(): - import Mailman.Handlers.Replybot as Replybot - return Replybot - -def get_maillist(): - import Mailman.MailList as MailList - return MailList.MailList - class Deliverer(object): - def deliver(self, msg, msgdata): - """Deliver a message to the list's members. - - Args: - msg: The message to deliver - msgdata: Additional message metadata - - This method delegates to the configured delivery module's process function. - """ - # Import the delivery module - modname = 'Mailman.Handlers.' + mm_cfg.DELIVERY_MODULE - try: - mod = __import__(modname) - process = getattr(sys.modules[modname], 'process') - except (ImportError, AttributeError) as e: - syslog('error', 'Failed to import delivery module %s: %s', modname, str(e)) - raise - - # Process the message - process(self, msg, msgdata) - def SendSubscribeAck(self, name, password, digest, text=''): - try: - pluser = self.getMemberLanguage(name) - except AttributeError: - try: - pluser = self.preferred_language - except AttributeError: - pluser = 'en' # Default to English if no language is available + pluser = self.getMemberLanguage(name) # Need to set this here to get the proper l10n of the Subject: i18n.set_language(pluser) - try: - welcome = Utils.wrap(self.welcome_msg) + '\n' if self.welcome_msg else '' - except AttributeError: + if self.welcome_msg: + welcome = Utils.wrap(self.welcome_msg) + '\n' + else: welcome = '' if self.umbrella_list: addr = self.GetMemberAdminEmail(name) @@ -107,7 +52,7 @@ def SendSubscribeAck(self, name, password, digest, text=''): else: umbrella = '' # get the text from the template - text += str(Utils.maketext( + text += Utils.maketext( 'subscribeack.txt', {'real_name' : self.real_name, 'host_name' : self.host_name, @@ -118,15 +63,15 @@ def SendSubscribeAck(self, name, password, digest, text=''): 'optionsurl' : self.GetOptionsURL(name, absolute=True), 'password' : password, 'user' : self.getMemberCPAddress(name), - }, lang=pluser, mlist=self)) + }, lang=pluser, mlist=self) if digest: digmode = _(' (Digest mode)') else: digmode = '' realname = self.real_name - msg = UserNotification( + msg = Message.UserNotification( self.GetMemberAdminEmail(name), self.GetRequestEmail(), - _('Welcome to the "%(realname)s" mailing list%(digmode)s') % {'realname': realname, 'digmode': digmode}, + _(f'Welcome to the "{realname}" mailing list{digmode}'), text, pluser) msg['X-No-Archive'] = 'yes' msg.send(self, verp=mm_cfg.VERP_PERSONALIZED_DELIVERIES) @@ -134,9 +79,9 @@ def SendSubscribeAck(self, name, password, digest, text=''): def SendUnsubscribeAck(self, addr, lang): realname = self.real_name i18n.set_language(lang) - msg = UserNotification( + msg = Message.UserNotification( self.GetMemberAdminEmail(addr), self.GetBouncesEmail(), - _('You have been unsubscribed from the %(realname)s mailing list') % {'realname': realname}, + _(f'You have been unsubscribed from the {realname} mailing list'), Utils.wrap(self.goodbye_msg), lang) msg.send(self, verp=mm_cfg.VERP_PERSONALIZED_DELIVERIES) @@ -163,14 +108,15 @@ def MailUserPassword(self, user): # Now send the user his password cpuser = self.getMemberCPAddress(user) recipient = self.GetMemberAdminEmail(cpuser) - subject = _('%(listfullname)s mailing list reminder') + subject = _(f'{listfullname} mailing list reminder') # Get user's language and charset lang = self.getMemberLanguage(user) cset = Utils.GetCharSet(lang) password = self.getMemberPassword(user) - # Handle password encoding properly for Python 3 - if isinstance(password, bytes): - password = password.decode(cset, 'replace') + # TK: Make unprintables to ? + # The list owner should allow users to set language options if they + # want to use non-us-ascii characters in password and send it back. + #password = str(password, cset, 'replace').encode(cset, 'replace') # get the text from the template text = Utils.maketext( 'userpass.txt', @@ -182,7 +128,7 @@ def MailUserPassword(self, user): 'requestaddr': requestaddr, 'owneraddr' : self.GetOwnerEmail(), }, lang=lang, mlist=self) - msg = UserNotification(recipient, adminaddr, subject, text, + msg = Message.UserNotification(recipient, adminaddr, subject, text, lang) msg['X-No-Archive'] = 'yes' msg.send(self, verp=mm_cfg.VERP_PERSONALIZED_DELIVERIES) @@ -196,7 +142,7 @@ def ForwardMessage(self, msg, text=None, subject=None, tomoderators=True): text = MIMEText(Utils.wrap(text), _charset=Utils.GetCharSet(self.preferred_language)) attachment = MIMEMessage(msg) - notice = UserNotification( + notice = Message.OwnerNotification( self, subject, tomoderators=tomoderators) # Make it look like the message is going to the -owner address notice.set_type('multipart/mixed') @@ -212,10 +158,10 @@ def SendHostileSubscriptionNotice(self, listname, address): syslog('mischief', '%s was invited to %s but confirmed to %s', address, listname, selfname) # First send a notice to the attacked list - msg = UserNotification( + msg = Message.OwnerNotification( self, _('Hostile subscription attempt detected'), - Utils.wrap(_("""%(address)s was invited to a different mailing + Utils.wrap(_(f"""{address} was invited to a different mailing list, but in a deliberate malicious attempt they tried to confirm the invitation to your list. We just thought you'd like to know. No further action by you is required."""))) @@ -231,10 +177,10 @@ def SendHostileSubscriptionNotice(self, listname, address): otrans = i18n.get_translation() i18n.set_language(mlist.preferred_language) try: - msg = UserNotification( + msg = Message.OwnerNotification( mlist, _('Hostile subscription attempt detected'), - Utils.wrap(_("""You invited %(address)s to your list, but in a + Utils.wrap(_(f"""You invited {address} to your list, but in a deliberate malicious attempt, they tried to confirm the invitation to a different list. We just thought you'd like to know. No further action by you is required."""))) @@ -267,10 +213,10 @@ def sendProbe(self, member, msg): otrans = i18n.get_translation() i18n.set_language(ulang) try: - subject = _('%(listname)s mailing list probe message') + subject = _(f'{listname} mailing list probe message') finally: i18n.set_translation(otrans) - outer = UserNotification(member, probeaddr, subject, + outer = Message.UserNotification(member, probeaddr, subject, lang=ulang) outer.set_type('multipart/mixed') text = MIMEText(text, _charset=Utils.GetCharSet(ulang)) diff --git a/Mailman/Digester.py b/Mailman/Digester.py index 150a6fe2..4ca58d30 100644 --- a/Mailman/Digester.py +++ b/Mailman/Digester.py @@ -25,14 +25,11 @@ from Mailman import mm_cfg from Mailman import Utils from Mailman import Errors +from Mailman.Handlers import ToDigest from Mailman.i18n import _ -# Lazy import to avoid circular dependency -def get_to_digest(): - import Mailman.Handlers.ToDigest as ToDigest - return ToDigest - + class Digester(object): def InitVars(self): # Configurable @@ -45,8 +42,6 @@ def InitVars(self): self.digest_header = mm_cfg.DEFAULT_DIGEST_HEADER self.digest_footer = mm_cfg.DEFAULT_DIGEST_FOOTER self.digest_volume_frequency = mm_cfg.DEFAULT_DIGEST_VOLUME_FREQUENCY - self._new_volume = 0 # Initialize _new_volume to False - self.volume = 1 # Initialize volume to 1 # Non-configurable. self.one_last_digest = {} self.digest_members = {} @@ -63,7 +58,7 @@ def send_digest_now(self): # See if there's a digest pending for this mailing list if os.stat(digestmbox)[ST_SIZE] > 0: mboxfp = open(digestmbox) - get_to_digest().send_digests(self, mboxfp) + ToDigest.send_digests(self, mboxfp) os.unlink(digestmbox) finally: if mboxfp: diff --git a/Mailman/Errors.py b/Mailman/Errors.py index d2ba6523..3410e1f4 100644 --- a/Mailman/Errors.py +++ b/Mailman/Errors.py @@ -94,11 +94,11 @@ class EmailAddressError(MailmanError): """Base class for email address validation errors.""" pass -class MMBadEmailError(Exception): +class MMBadEmailError(EmailAddressError): """Email address is invalid (empty string or not fully qualified).""" pass -class MMHostileAddress(Exception): +class MMHostileAddress(EmailAddressError): """Email address has potentially hostile characters in it.""" pass @@ -162,7 +162,7 @@ def __init__(self, notice=None): def notice(self): return self.__notice - + # Additional exceptions class HostileSubscriptionError(MailmanError): """A cross-subscription attempt was made.""" diff --git a/Mailman/Gui/Bounce.py b/Mailman/Gui/Bounce.py index e85aa79a..ee747678 100644 --- a/Mailman/Gui/Bounce.py +++ b/Mailman/Gui/Bounce.py @@ -1,3 +1,4 @@ +from __future__ import division # Copyright (C) 2001-2018 by the Free Software Foundation, Inc. # # This program is free software; you can redistribute it and/or @@ -20,6 +21,7 @@ from Mailman.Gui.GUIBase import GUIBase + class Bounce(GUIBase): def GetConfigCategory(self): return 'bounce', _('Bounce processing') diff --git a/Mailman/Gui/Digest.py b/Mailman/Gui/Digest.py index 70f7d267..052de111 100644 --- a/Mailman/Gui/Digest.py +++ b/Mailman/Gui/Digest.py @@ -65,14 +65,14 @@ def GetConfigInfo(self, mlist, category, subcat=None): ('digest_header', mm_cfg.Text, (4, WIDTH), 0, _('Header added to every digest'), - str(_("Text attached (as an initial message, before the table" - " of contents) to the top of digests. ")) - + str(Utils.maketext('headfoot.html', raw=1, mlist=mlist))), + _("Text attached (as an initial message, before the table" + " of contents) to the top of digests. ") + + Utils.maketext('headfoot.html', raw=1, mlist=mlist)), ('digest_footer', mm_cfg.Text, (4, WIDTH), 0, _('Footer added to every digest'), - str(_("Text attached (as a final message) to the bottom of digests. ")) - + str(Utils.maketext('headfoot.html', raw=1, mlist=mlist))), + _("Text attached (as a final message) to the bottom of digests. ") + + Utils.maketext('headfoot.html', raw=1, mlist=mlist)), ('digest_volume_frequency', mm_cfg.Radio, (_('Yearly'), _('Monthly'), _('Quarterly'), diff --git a/Mailman/Gui/GUIBase.py b/Mailman/Gui/GUIBase.py index a0566cba..d51496f2 100644 --- a/Mailman/Gui/GUIBase.py +++ b/Mailman/Gui/GUIBase.py @@ -39,12 +39,6 @@ class GUIBase: def _getValidValue(self, mlist, property, wtype, val): # Coerce and validate the new value. # - # First convert any bytes to strings - if isinstance(val, bytes): - try: - val = val.decode('utf-8') - except UnicodeDecodeError: - val = val.decode('latin1') # Radio buttons and boolean toggles both have integral type if wtype in (mm_cfg.Radio, mm_cfg.Toggle): # Let ValueErrors propagate @@ -144,14 +138,8 @@ def _getValidValue(self, mlist, property, wtype, val): def _setValue(self, mlist, property, val, doc): # Set the value, or override to take special action on the property - if not property.startswith('_'): - if isinstance(val, bytes): - try: - val = val.decode('utf-8') - except UnicodeDecodeError: - val = val.decode('latin1') - if getattr(mlist, property) != val: - setattr(mlist, property, val) + if not property.startswith('_') and getattr(mlist, property) != val: + setattr(mlist, property, val) def _postValidate(self, mlist, doc): # Validate all the attributes for this category @@ -160,7 +148,7 @@ def _postValidate(self, mlist, doc): def handleForm(self, mlist, category, subcat, cgidata, doc): for item in self.GetConfigInfo(mlist, category, subcat): # Skip descriptions and legacy non-attributes - if not isinstance(item, tuple) or len(item) < 5: + if not type(item) is tuple or len(item) < 5: continue # Unpack the gui item description property, wtype, args, deps, desc = item[0:5] @@ -199,11 +187,6 @@ def handleForm(self, mlist, category, subcat, cgidata, doc): # Convenience method for handling $-string attributes def _convertString(self, mlist, property, alloweds, val, doc): # Is the list using $-strings? - if isinstance(val, bytes): - try: - val = val.decode('utf-8') - except UnicodeDecodeError: - val = val.decode('latin1') dollarp = getattr(mlist, 'use_dollar_strings', 0) if dollarp: ids = Utils.dollar_identifiers(val) @@ -241,9 +224,3 @@ def _convertString(self, mlist, property, alloweds, val, doc): """)) return fixed return val - - def AddItem(self, item): - """Add an item to the list of items to be displayed.""" - if not isinstance(item, tuple) or len(item) < 5: - raise ValueError('Item must be a tuple with at least 5 elements') - self.items.append(item) diff --git a/Mailman/Gui/General.py b/Mailman/Gui/General.py index bccefb3b..89319449 100644 --- a/Mailman/Gui/General.py +++ b/Mailman/Gui/General.py @@ -595,4 +595,4 @@ def getValue(self, mlist, kind, varname, params): if varname != 'subject_prefix': return None # The subject_prefix may be Unicode - return Utils.uncanonstr(mlist.subject_prefix, mlist.preferred_language) + return Utils.uncanonstr(mlist.subject_prefix, mlist.preferred_language).decode() # Does this break encodings? diff --git a/Mailman/Gui/Language.py b/Mailman/Gui/Language.py index 606b8433..7480b763 100644 --- a/Mailman/Gui/Language.py +++ b/Mailman/Gui/Language.py @@ -38,7 +38,7 @@ def GetConfigInfo(self, mlist, category, subcat=None): return None # Set things up for the language choices - langs = mlist.available_languages + langs = mlist.GetAvailableLanguages() langnames = [_(Utils.GetLanguageDescr(L)) for L in langs] try: langi = langs.index(mlist.preferred_language) diff --git a/Mailman/Gui/NonDigest.py b/Mailman/Gui/NonDigest.py index 668d3ed1..321997f4 100644 --- a/Mailman/Gui/NonDigest.py +++ b/Mailman/Gui/NonDigest.py @@ -125,18 +125,15 @@ def GetConfigInfo(self, mlist, category, subcat=None): else: extra = '' - # Ensure headfoot is not None - headfoot = headfoot or '' - info.extend([('msg_header', mm_cfg.Text, (10, WIDTH), 0, _('Header added to mail sent to regular list members'), - str(_('''Text prepended to the top of every immediately-delivery - message. ''')) + str(headfoot) + str(extra)), + _('''Text prepended to the top of every immediately-delivery + message. ''') + headfoot + extra), ('msg_footer', mm_cfg.Text, (10, WIDTH), 0, _('Footer added to mail sent to regular list members'), - str(_('''Text appended to the bottom of every immediately-delivery - message. ''')) + str(headfoot) + str(extra)), + _('''Text appended to the bottom of every immediately-delivery + message. ''') + headfoot + extra), ]) info.extend([ diff --git a/Mailman/Gui/Privacy.py b/Mailman/Gui/Privacy.py index d3d48300..5e4f847f 100644 --- a/Mailman/Gui/Privacy.py +++ b/Mailman/Gui/Privacy.py @@ -196,7 +196,7 @@ def GetConfigInfo(self, mlist, category, subcat=None):

    In the text boxes below, add one address per line; start the line with a ^ character to designate a Python regular expression. When entering backslashes, do so as if you were using Python raw strings (i.e. you generally just use a single backslash). @@ -649,9 +649,9 @@ def _handleForm(self, mlist, category, subcat, cgidata, doc): if deltag in cgidata: continue # Get the data for the current box - pattern = cgidata.get(reboxtag, [''])[0] + pattern = cgidata.getfirst(reboxtag) try: - action = int(cgidata.get(actiontag, ['0'])[0]) + action = int(cgidata.getfirst(actiontag)) # We'll get a TypeError when the actiontag is missing and the # .getvalue() call returns None. except (ValueError, TypeError): @@ -690,7 +690,7 @@ def _handleForm(self, mlist, category, subcat, cgidata, doc): # Was this an add item? if addtag in cgidata: # Where should the new one be added? - where = cgidata.get(wheretag, ['after'])[0] + where = cgidata.getfirst(wheretag) if where == 'before': # Add a new empty rule box before the current one rules.append(('', mm_cfg.DEFER, True)) @@ -725,20 +725,3 @@ def handleForm(self, mlist, category, subcat, cgidata, doc): self._handleForm(mlist, category, subcat, cgidata, doc) # Everything else is dealt with by the base handler GUIBase.handleForm(self, mlist, category, subcat, cgidata, doc) - -def process_form(mlist, cgidata): - # Get the privacy settings from the form - pattern = cgidata.get(reboxtag, [''])[0] - action = int(cgidata.get(actiontag, ['0'])[0]) - where = cgidata.get(wheretag, [''])[0] - - # Process the privacy rule - if pattern: - if where == 'add': - mlist.AddPrivacyRule(pattern, action) - elif where == 'change': - mlist.ChangePrivacyRule(pattern, action) - elif where == 'delete': - mlist.DeletePrivacyRule(pattern) - - mlist.Save() diff --git a/Mailman/Gui/Topics.py b/Mailman/Gui/Topics.py index 20f38a48..7459e89d 100644 --- a/Mailman/Gui/Topics.py +++ b/Mailman/Gui/Topics.py @@ -44,7 +44,7 @@ def GetConfigInfo(self, mlist, category, subcat=None): _("""The topic filter categorizes each incoming email message according to regular + href="https://docs.python.org/2/library/re.html">regular expression filters you specify below. If the message's Subject: or Keywords: header contains a match against a topic filter, the message is logically placed @@ -108,9 +108,9 @@ def handleForm(self, mlist, category, subcat, cgidata, doc): if deltag in cgidata: continue # Get the data for the current box - name = cgidata.get(boxtag, [''])[0] - pattern = cgidata.get(reboxtag, [''])[0] - desc = cgidata.get(desctag, [''])[0] + name = cgidata.getfirst(boxtag) + pattern = cgidata.getfirst(reboxtag) + desc = cgidata.getfirst(desctag) if name is None: # We came to the end of the boxes break @@ -132,7 +132,7 @@ def handleForm(self, mlist, category, subcat, cgidata, doc): # Was this an add item? if addtag in cgidata: # Where should the new one be added? - where = cgidata.get(wheretag, ['after'])[0] + where = cgidata.getfirst(wheretag) if where == 'before': # Add a new empty topics box before the current one topics.append(('', '', '', True)) @@ -148,34 +148,16 @@ def handleForm(self, mlist, category, subcat, cgidata, doc): # options. mlist.topics = topics try: - mlist.topics_enabled = int(cgidata.get('topics_enabled', [mlist.topics_enabled])[0]) + mlist.topics_enabled = int(cgidata.getfirst( + 'topics_enabled', + mlist.topics_enabled)) except ValueError: # BAW: should really print a warning pass try: - mlist.topics_bodylines_limit = int(cgidata.get('topics_bodylines_limit', [mlist.topics_bodylines_limit])[0]) + mlist.topics_bodylines_limit = int(cgidata.getfirst( + 'topics_bodylines_limit', + mlist.topics_bodylines_limit)) except ValueError: # BAW: should really print a warning pass - - def process_form(self, mlist, cgidata): - # Get the topic information from the form - name = cgidata.get(boxtag, [''])[0] - pattern = cgidata.get(reboxtag, [''])[0] - desc = cgidata.get(desctag, [''])[0] - where = cgidata.get(wheretag, [''])[0] - - # Update list settings - mlist.topics_enabled = int(cgidata.get('topics_enabled', ['0'])[0]) - mlist.topics_bodylines_limit = int(cgidata.get('topics_bodylines_limit', ['0'])[0]) - - # Process the topic - if name and pattern: - if where == 'add': - mlist.AddTopic(name, pattern, desc) - elif where == 'change': - mlist.ChangeTopic(name, pattern, desc) - elif where == 'delete': - mlist.DeleteTopic(name) - - mlist.Save() diff --git a/Mailman/HTMLFormatter.py b/Mailman/HTMLFormatter.py index 9e0e6522..d86a13ce 100644 --- a/Mailman/HTMLFormatter.py +++ b/Mailman/HTMLFormatter.py @@ -22,13 +22,11 @@ from builtins import object import time import re -import os from Mailman import mm_cfg from Mailman import Utils from Mailman import MemberAdaptor from Mailman.htmlformat import * -from Mailman.Logging.Syslog import mailman_log from Mailman.i18n import _ @@ -58,11 +56,11 @@ def GetMailmanFooter(self): innertext, '
    ', Link(self.GetScriptURL('admin'), - _(f'{realname} administrative interface')), + _(f'{realname} administrative interface')), _(' (requires authorization)'), '
    ', Link(Utils.ScriptURL('listinfo'), - _(f'Overview of all {hostname} mailing lists')), + _(f'Overview of all {hostname} mailing lists')), '

    ', MailmanLogo()))).Format() def FormatUsers(self, digest, lang=None, list_hidden=False): @@ -113,7 +111,7 @@ def FormatOptionButton(self, option, value, user): else: optval = self.getMemberOption(user, option) if optval == value: - checked = ' checked' + checked = ' CHECKED' else: checked = '' name = {mm_cfg.DontReceiveOwnPosts : 'dontreceive', @@ -126,15 +124,15 @@ def FormatOptionButton(self, option, value, user): mm_cfg.ReceiveNonmatchingTopics : 'rcvtopic', mm_cfg.DontReceiveDuplicates : 'nodupes', }[option] - return '' % ( + return '' % ( name, value, checked) def FormatDigestButton(self): if self.digest_is_default: - checked = ' checked' + checked = ' CHECKED' else: checked = '' - return '' % checked + return '' % checked def FormatDisabledNotice(self, user): status = self.getDeliveryStatus(user) @@ -147,47 +145,44 @@ def FormatDisabledNotice(self, user): elif status == MemberAdaptor.BYBOUNCE: date = time.strftime('%d-%b-%Y', time.localtime(Utils.midnight(info.date))) - reason = _(f'''; it was disabled due to excessive bounces. The - last bounce was received on {date}''') + reason = _('''; it was disabled due to excessive bounces. The + last bounce was received on %(date)s''') elif status == MemberAdaptor.UNKNOWN: reason = _('; it was disabled for unknown reasons') if reason: note = FontSize('+1', _( - f'Note: your list delivery is currently disabled{reason}.' + 'Note: your list delivery is currently disabled%(reason)s.' )).Format() link = Link('#disable', _('Mail delivery')).Format() mailto = Link('mailto:' + self.GetOwnerEmail(), _('the list administrator')).Format() - return _(f'''

    -

    {note}

    -

    You may have disabled list delivery intentionally, - or it may have been triggered by bounces from your email - address. In either case, to re-enable delivery, change the - {link} option below. Contact {mailto} if you have any - questions or need assistance.

    -
    ''') + return _('''

    %(note)s + +

    You may have disabled list delivery intentionally, + or it may have been triggered by bounces from your email + address. In either case, to re-enable delivery, change the + %(link)s option below. Contact %(mailto)s if you have any + questions or need assistance.''') elif info and info.score > 0: # Provide information about their current bounce score. We know # their membership is currently enabled. score = info.score total = self.bounce_score_threshold - return _(f'''

    -

    We have received some recent bounces from your - address. Your current bounce score is {score} out of a - maximum of {total}. Please double check that your subscribed - address is correct and that there are no problems with delivery to - this address. Your bounce score will be automatically reset if - the problems are corrected soon.

    -
    ''') + return _('''

    We have received some recent bounces from your + address. Your current bounce score is %(score)s out of a + maximum of %(total)s. Please double check that your subscribed + address is correct and that there are no problems with delivery to + this address. Your bounce score will be automatically reset if + the problems are corrected soon.''') else: return '' def FormatUmbrellaNotice(self, user, type): addr = self.GetMemberAdminEmail(user) if self.umbrella_list: - return _(f"(Note - you are subscribing to a list of mailing lists, " - "so the {type} notice will be sent to the admin address" - " for your membership, {addr}.)

    ") + return _("(Note - you are subscribing to a list of mailing lists, " + "so the %(type)s notice will be sent to the admin address" + " for your membership, %(addr)s.)

    ") else: return "" @@ -195,15 +190,15 @@ def FormatSubscriptionMsg(self): msg = '' also = '' if self.subscribe_policy == 1: - msg += _(f'''You will be sent email requesting confirmation, to + msg += _('''You will be sent email requesting confirmation, to prevent others from gratuitously subscribing you.''') elif self.subscribe_policy == 2: - msg += _(f"""This is a closed list, which means your subscription + msg += _("""This is a closed list, which means your subscription will be held for approval. You will be notified of the list moderator's decision by email.""") also = _('also ') elif self.subscribe_policy == 3: - msg += _(f"""You will be sent email requesting confirmation, to + msg += _("""You will be sent email requesting confirmation, to prevent others from gratuitously subscribing you. Once confirmation is received, your request will be held for approval by the list moderator. You will be notified of the moderator's @@ -221,7 +216,7 @@ def FormatSubscriptionMsg(self): msg += _(f'''This is {also}a public list, which means that the list of members list is available to everyone.''') if self.obscure_addresses: - msg += _(f''' (but we obscure the addresses so they are not + msg += _(''' (but we obscure the addresses so they are not easily recognizable by spammers).''') if self.umbrella_list: @@ -260,28 +255,20 @@ def FormatEditingOption(self, lang): either = '' realname = self.real_name - text = _(f'''To unsubscribe from {realname}, get a password reminder, + text = (_(f'''To unsubscribe from {realname}, get a password reminder, or change your subscription options {either}enter your subscription email address: -

    ''') -# text += TextBox('email', size=30).Format() - text += (' ') - text += SubmitButton('UserOptions', _(f'Unsubscribe or edit options')).Format() - text += Hidden('language', lang).Format() - text += ('

    ') -#` text = (_(f'''To unsubscribe from {realname}, get a password reminder, -#` or change your subscription options {either}enter your subscription -#` email address: -#`

    ''') -#` + TextBox('email', size=30).Format() -#` + f' ' -#` + SubmitButton('UserOptions', _(f'Unsubscribe or edit options')).Format() -#` + Hidden('language', lang).Format() -#` + f'
    ') +

    ''') + + TextBox('email', size=30).Format() + + ' ' + + SubmitButton('UserOptions', + _('Unsubscribe or edit options')).Format() + + Hidden('language', lang).Format() + + '
    ') if self.private_roster == 0: - text += _(f'''

    ... or select your entry from + text += _('''

    ... or select your entry from the subscribers list (see above).''') - text += _(f''' If you leave the field blank, you will be prompted for + text += _(''' If you leave the field blank, you will be prompted for your email address''') return text @@ -326,7 +313,7 @@ def RosterOption(self, lang): + whom + " ") container.AddItem(self.FormatBox('roster-email')) - container.AddItem(_(" Password: ") + container.AddItem(_("Password: ") + self.FormatSecureBox('roster-pw') + "  ") container.AddItem(SubmitButton('SubscriberRoster', @@ -342,12 +329,10 @@ def FormatFormStart(self, name, extra='', else: full_url = base_url if mlist: - token = csrf_token(mlist, contexts, user) - if token is None: - return '
    ' % full_url - return """ -""" % (full_url, token) - return '' % full_url + return (""" +""" + % (full_url, csrf_token(mlist, contexts, user))) + return ('' % full_url) def FormatArchiveAnchor(self): return '' % self.GetBaseArchiveURL() @@ -356,10 +341,9 @@ def FormatFormEnd(self): return '' def FormatBox(self, name, size=20, value=''): - if isinstance(value, str): - safevalue = Utils.websafe(value) - else: - safevalue = value + if isinstance(value, bytes): + value = value.decode('utf-8') + safevalue = Utils.websafe(value) return '' % ( name, size, safevalue) @@ -375,141 +359,71 @@ def FormatReminder(self, lang): ' a reminder.') return '' - def format(self, value, charset=None): - """Format a value for HTML output.""" - if value is None: - return '' - if isinstance(value, str): - return Utils.websafe(value) - if isinstance(value, bytes): - if charset is None: - charset = self.preferred_language - try: - return Utils.websafe(value.decode(charset, 'replace')) - except (UnicodeError, LookupError): - return Utils.websafe(value.decode('utf-8', 'replace')) - return str(value) - def ParseTags(self, template, replacements, lang=None): - """Parse template tags and replace them with their values.""" if lang is None: charset = 'us-ascii' else: - charset = Utils.GetCharSet(lang) or 'us-ascii' - - # Read the template file + charset = Utils.GetCharSet(lang) text = Utils.maketext(template, raw=1, lang=lang, mlist=self) - if text is None: - mailman_log('error', 'Could not read template file: %s', template) - return '' - - # Convert replacement keys to lowercase for case-insensitive matching - replacements = {k.lower(): v for k, v in replacements.items()} - - # Split on MM tags, case-insensitive, but preserve HTML entities parts = re.split('(]*>)', text) i = 1 while i < len(parts): - tag = parts[i].lower() # Convert to lowercase for matching + tag = parts[i].lower() if tag in replacements: repl = replacements[tag] - if isinstance(repl, str): - # Don't encode HTML entities - if '&' in repl: - parts[i] = repl - else: - # Ensure proper encoding/decoding - try: - # First try to decode if it's already encoded - if isinstance(repl, bytes): - repl = repl.decode(charset, 'replace') - # Then encode and decode to ensure proper charset - repl = repl.encode(charset, 'replace').decode(charset, 'replace') - parts[i] = repl - except (UnicodeError, LookupError): - # Fallback to utf-8 if charset fails - repl = repl.encode('utf-8', 'replace').decode('utf-8', 'replace') - parts[i] = repl - elif isinstance(repl, bytes): - try: - repl = repl.decode(charset, 'replace') - parts[i] = repl - except (UnicodeError, LookupError): - repl = repl.decode('utf-8', 'replace') - parts[i] = repl - else: - parts[i] = str(repl) + if isinstance(repl, type(u'')): + repl = repl.encode(charset, 'replace') + if type(repl) is bytes: + repl = repl.decode() + parts[i] = repl else: parts[i] = '' i = i + 2 - - # Join parts and ensure proper encoding - result = EMPTYSTRING.join(parts) - try: - # Ensure the final output is properly encoded - if isinstance(result, bytes): - result = result.decode(charset, 'replace') - return result - except (UnicodeError, LookupError): - return result.decode('utf-8', 'replace') - - def GetStandardReplacements(self, lang=None, replacements=None): - """Get the standard replacements for this list.""" - if replacements is None: - replacements = {} - if lang is None: - lang = self.preferred_language - - try: - # Get member counts - dmember_len = len(self.getDigestMemberKeys()) - member_len = len(self.getRegularMemberKeys()) - - # Handle language selection - if len(self.GetAvailableLanguages()) == 1: - listlangs = _(Utils.GetLanguageDescr(self.preferred_language)) - else: - listlangs = self.GetLangSelectBox(lang).Format() - - # Get charset - if lang: - cset = Utils.GetCharSet(lang) or 'us-ascii' - else: - cset = Utils.GetCharSet(self.preferred_language) or 'us-ascii' - - # Add all standard replacements (using lowercase to match original) - replacements.update({ - '': self.GetMailmanFooter(), - '': self.real_name, - '': self._internal_name, - '': self.GetDescription(cset), - '': '' + BR.join(self.info.split(NL)) + '', - '': self.FormatFormEnd(), - '': self.FormatArchiveAnchor(), - '': '', - '': self.FormatSubscriptionMsg(), - '': self.RestrictedListMessage(_('The current archive'), self.archive_private), - '': repr(member_len), - '': repr(dmember_len), - '': repr(member_len + dmember_len), - '': '%s' % self.GetListEmail(), - '': '%s' % self.GetRequestEmail(), - '': self.GetOwnerEmail(), - '': self.FormatReminder(self.preferred_language), - '': self.host_name, - '': listlangs, - }) - - # Add favicon if configured - if mm_cfg.IMAGE_LOGOS: - replacements[''] = mm_cfg.IMAGE_LOGOS + mm_cfg.SHORTCUT_ICON - - mailman_log('trace', 'Added %d standard replacements', len(replacements)) - - except Exception as e: - mailman_log('error', 'Error getting standard replacements: %s', str(e)) - - return replacements + return EMPTYSTRING.join(parts) + + # This needs to wait until after the list is inited, so let's build it + # when it's needed only. + def GetStandardReplacements(self, lang=None): + dmember_len = len(self.getDigestMemberKeys()) + member_len = len(self.getRegularMemberKeys()) + # If only one language is enabled for this mailing list, omit the + # language choice buttons. + if len(self.GetAvailableLanguages()) == 1: + listlangs = _(Utils.GetLanguageDescr(self.preferred_language)) + else: + listlangs = self.GetLangSelectBox(lang).Format() + if lang: + cset = Utils.GetCharSet(lang) or 'us-ascii' + else: + cset = Utils.GetCharSet(self.preferred_language) or 'us-ascii' + d = { + '' : self.GetMailmanFooter(), + '' : self.real_name, + '' : self._internal_name, + '' : + Utils.websafe(self.GetDescription(cset)), + '' : + '' + BR.join(self.info.split(NL)) + '', + '' : self.FormatFormEnd(), + '' : self.FormatArchiveAnchor(), + '' : '', + '' : self.FormatSubscriptionMsg(), + '' : \ + self.RestrictedListMessage(_('The current archive'), + self.archive_private), + '' : repr(member_len), + '' : repr(dmember_len), + '' : repr(member_len + dmember_len), + '' : '%s' % self.GetListEmail(), + '' : '%s' % self.GetRequestEmail(), + '' : self.GetOwnerEmail(), + '' : self.FormatReminder(self.preferred_language), + '' : self.host_name, + '' : listlangs, + } + if mm_cfg.IMAGE_LOGOS: + d[''] = mm_cfg.IMAGE_LOGOS + mm_cfg.SHORTCUT_ICON + return d def GetAllReplacements(self, lang=None, list_hidden=False): """ @@ -527,7 +441,7 @@ def GetLangSelectBox(self, lang=None, varname='language'): if lang is None: lang = self.preferred_language # Figure out the available languages - values = self.available_languages + values = self.GetAvailableLanguages() legend = list(map(_, list(map(Utils.GetLanguageDescr, values)))) try: selected = values.index(lang) diff --git a/Mailman/Handlers/Acknowledge.py b/Mailman/Handlers/Acknowledge.py index 80183a5a..59e508cf 100644 --- a/Mailman/Handlers/Acknowledge.py +++ b/Mailman/Handlers/Acknowledge.py @@ -24,11 +24,12 @@ from Mailman import mm_cfg from Mailman import Utils -from Mailman.Message import Message +from Mailman import Message from Mailman import Errors from Mailman.i18n import _ + def process(mlist, msg, msgdata): # Extract the sender's address and find them in the user database sender = msgdata.get('original_sender', msg.get_sender()) @@ -55,7 +56,7 @@ def process(mlist, msg, msgdata): # Craft the outgoing message, with all headers and attributes # necessary for general delivery. Then enqueue it to the outgoing # queue. - subject = _('%(realname)s post acknowledgement') % {'realname': realname} - usermsg = Mailman.Message.UserNotification(sender, mlist.GetBouncesEmail(), + subject = _('%(realname)s post acknowledgement') + usermsg = Message.UserNotification(sender, mlist.GetBouncesEmail(), subject, text, lang) usermsg.send(mlist) diff --git a/Mailman/Handlers/Approve.py b/Mailman/Handlers/Approve.py index 9984f4dd..4dad429d 100644 --- a/Mailman/Handlers/Approve.py +++ b/Mailman/Handlers/Approve.py @@ -45,6 +45,7 @@ def _(s): del _ + def process(mlist, msg, msgdata): # Short circuits # Do not short circuit. The problem is SpamDetect comes before Approve. @@ -83,10 +84,8 @@ def process(mlist, msg, msgdata): for lineno, line in zip(list(range(len(lines))), lines): if line.strip(): break - # Decode bytes to string if needed - if isinstance(line, bytes): - line = line.decode('utf-8', errors='replace') - i = line.find(':') + + i = line.find(b':') if i >= 0: name = line[:i] value = line[i+1:] diff --git a/Mailman/Handlers/CalcRecips.py b/Mailman/Handlers/CalcRecips.py index 4f59661c..9fff0859 100644 --- a/Mailman/Handlers/CalcRecips.py +++ b/Mailman/Handlers/CalcRecips.py @@ -26,11 +26,10 @@ import email.utils from Mailman import mm_cfg from Mailman import Utils -from Mailman.Message import Message +from Mailman import Message from Mailman import Errors from Mailman.MemberAdaptor import ENABLED -# Remove the MailList import from here since it's causing a circular dependency -# from Mailman.MailList import MailList +from Mailman.MailList import MailList from Mailman.i18n import _ from Mailman.Logging.Syslog import syslog from Mailman.Errors import MMUnknownListError @@ -42,11 +41,8 @@ from sets import Set as set + def process(mlist, msg, msgdata): - """Process message to calculate recipients.""" - # Import MailList here to avoid circular dependency - from Mailman.MailList import MailList - # Short circuit if we've already calculated the recipients list, # regardless of whether the list is empty or not. if 'recips' in msgdata: @@ -107,11 +103,8 @@ def process(mlist, msg, msgdata): msgdata['recips'] = recips + def do_topic_filters(mlist, msg, msgdata, recips): - """Apply topic filters to recipients.""" - # Import MailList here to avoid circular dependency - from Mailman.MailList import MailList - if not mlist.topics_enabled: # MAS: if topics are currently disabled for the list, send to all # regardless of ReceiveNonmatchingTopics @@ -156,12 +149,8 @@ def do_topic_filters(mlist, msg, msgdata, recips): for user in zaprecips: recips.remove(user) - + def do_exclude(mlist, msg, msgdata, recips): - """Handle recipient exclusions.""" - # Import MailList here to avoid circular dependency - from Mailman.MailList import MailList - # regular_exclude_lists are the other mailing lists on this mailman # installation whose members are excluded from the regular (non-digest) # delivery of this list if those list addresses appear in To: or Cc: @@ -209,12 +198,8 @@ def do_exclude(mlist, msg, msgdata, recips): recips -= srecips return list(recips) - + def do_include(mlist, msg, msgdata, recips): - """Handle recipient inclusions.""" - # Import MailList here to avoid circular dependency - from Mailman.MailList import MailList - # regular_include_lists are the other mailing lists on this mailman # installation whose members are included in the regular (non-digest) # delivery if those list addresses don't appear in To: or Cc: headers. diff --git a/Mailman/Handlers/Cleanse.py b/Mailman/Handlers/Cleanse.py index c39c6fc3..2cf4acec 100644 --- a/Mailman/Handlers/Cleanse.py +++ b/Mailman/Handlers/Cleanse.py @@ -50,12 +50,6 @@ def remove_nonkeepers(msg): def process(mlist, msg, msgdata): - """Process the message.""" - # Remove old message-id if it exists - if 'message-id' in msg: - del msg['message-id'] - # Set new message-id - msg['Message-ID'] = unique_message_id(mlist) # Always remove this header from any outgoing messages. Be sure to do # this after the information on the header is actually used, but before a # permanent record of the header is saved. @@ -83,6 +77,11 @@ def process(mlist, msg, msgdata): del msg['x-originating-email'] # And these can reveal the sender too del msg['received'] + # And so can the message-id so replace it. + del msg['message-id'] + msg['Message-ID'] = unique_message_id(mlist) + # And something sets this + del msg['x-envelope-from'] # And now remove all but the keepers. remove_nonkeepers(msg) i18ndesc = str(uheader(mlist, mlist.description, 'From')) @@ -98,4 +97,8 @@ def process(mlist, msg, msgdata): del msg['x-confirm-reading-to'] # Pegasus mail uses this one... sigh del msg['x-pmrqc'] - return True + + # Remove any header whose value is not a string. + for h, v in list(msg.items()): + if not isinstance(v, str): + del msg[h] diff --git a/Mailman/Handlers/CleanseDKIM.py b/Mailman/Handlers/CleanseDKIM.py index 79cacbf4..45ac5edc 100644 --- a/Mailman/Handlers/CleanseDKIM.py +++ b/Mailman/Handlers/CleanseDKIM.py @@ -25,8 +25,6 @@ originating at the Mailman server for the outgoing message. """ -from __future__ import absolute_import, print_function, unicode_literals - from Mailman import mm_cfg @@ -46,19 +44,13 @@ def process(mlist, msg, msgdata): ): return if (mm_cfg.REMOVE_DKIM_HEADERS == 3): - # Save original headers before removing them - for header in ('domainkey-signature', 'dkim-signature', 'authentication-results'): - values = msg.get_all(header, []) - if values: - # Store original values in X-Mailman-Original-* headers - for value in values: - msg.add_header('X-Mailman-Original-' + header.title().replace('-', ''), value) - # Remove the original headers - while header in msg: - del msg[header] - else: - # Just remove the headers without saving them - for header in ('domainkey-signature', 'dkim-signature', 'authentication-results'): - while header in msg: - del msg[header] + for value in msg.get_all('domainkey-signature', []): + msg['X-Mailman-Original-DomainKey-Signature'] = value + for value in msg.get_all('dkim-signature', []): + msg['X-Mailman-Original-DKIM-Signature'] = value + for value in msg.get_all('authentication-results', []): + msg['X-Mailman-Original-Authentication-Results'] = value + del msg['domainkey-signature'] + del msg['dkim-signature'] + del msg['authentication-results'] diff --git a/Mailman/Handlers/CookHeaders.py b/Mailman/Handlers/CookHeaders.py index 2e2f1842..40ce483f 100644 --- a/Mailman/Handlers/CookHeaders.py +++ b/Mailman/Handlers/CookHeaders.py @@ -20,253 +20,485 @@ list configuration. """ -from __future__ import absolute_import, print_function, unicode_literals - +from __future__ import nested_scopes import re + from email.charset import Charset from email.header import Header, decode_header, make_header from email.utils import parseaddr, formataddr, getaddresses from email.errors import HeaderParseError -from email.iterators import body_line_iterator from Mailman import i18n from Mailman import mm_cfg from Mailman import Utils from Mailman.i18n import _ -from Mailman.Logging.Syslog import mailman_log +from Mailman.Logging.Syslog import syslog CONTINUATION = ',\n ' COMMASPACE = ', ' MAXLINELEN = 78 -def _isunicode(s): - return isinstance(s, str) - nonascii = re.compile(r'[^\s!-~]') def uheader(mlist, s, header_name=None, continuation_ws=' ', maxlinelen=None): - """Create a Header object from a string with proper charset handling. - - This function ensures proper handling of both str and bytes input, - and uses the list's preferred charset for encoding. - """ - # Get the charset to encode the string in + # Get the charset to encode the string in. Then search if there is any + # non-ascii character is in the string. If there is and the charset is + # us-ascii then we use iso-8859-1 instead. If the string is ascii only + # we use 'us-ascii' if another charset is specified. charset = Utils.GetCharSet(mlist.preferred_language) - - # Convert input to str if it's bytes if isinstance(s, bytes): - try: - s = s.decode('ascii') - except UnicodeDecodeError: - try: - s = s.decode(charset) - except UnicodeDecodeError: - s = s.decode('utf-8', 'replace') - - # If there are non-ASCII characters, use the list's charset - if nonascii.search(s): + search_string = s.decode() + else: + search_string = s + + if nonascii.search(search_string): + # use list charset but ... if charset == 'us-ascii': - charset = 'utf-8' + charset = 'iso-8859-1' else: + # there is no nonascii so ... charset = 'us-ascii' - try: return Header(s, charset, maxlinelen, header_name, continuation_ws) except UnicodeError: - mailman_log('error', 'list: %s: cannot encode "%s" as %s', - mlist.internal_name(), s, charset) - # Fall back to ASCII with replacement characters - return Header(s.encode('ascii', 'replace').decode('ascii'), - 'us-ascii', maxlinelen, header_name, continuation_ws) + syslog('error', 'list: %s: can\'t decode "%s" as %s', + mlist.internal_name(), s, charset) + return Header('', charset, maxlinelen, header_name, continuation_ws) def change_header(name, value, mlist, msg, msgdata, delete=True, repl=True): - """Change or add a message header. - - This function handles header changes in a Python 3 compatible way, - properly dealing with encodings and header values. - """ if ((msgdata.get('from_is_list') == 2 or (msgdata.get('from_is_list') == 0 and mlist.from_is_list == 2)) and not msgdata.get('_fasttrack') ) or name.lower() in ('from', 'reply-to', 'cc'): - # Store the header in msgdata for later use + # The or name.lower() in ... above is because when we are munging + # the From:, we want to defer the resultant changes to From:, + # Reply-To:, and/or Cc: until after the message passes through + # ToDigest, ToArchive and ToUsenet. Thus, we put them in + # msgdata[add_header] here and apply them in WrapMessage. msgdata.setdefault('add_header', {})[name] = value - # Also add the header to the message if it's not From, Reply-To, or Cc - if name.lower() not in ('from', 'reply-to', 'cc'): - if delete: - del msg[name] - if isinstance(value, Header): - msg[name] = value - else: - try: - msg[name] = str(value) - except UnicodeEncodeError: - msg[name] = Header(value, - Utils.GetCharSet(mlist.preferred_language)) elif repl or name not in msg: if delete: del msg[name] - if isinstance(value, Header): - msg[name] = value - else: - try: - msg[name] = str(value) - except UnicodeEncodeError: - msg[name] = Header(value, - Utils.GetCharSet(mlist.preferred_language)) + msg[name] = value + + def process(mlist, msg, msgdata): - """Process the message by cooking its headers.""" - msgid = msg.get('message-id', 'n/a') - - # Log start of processing with enhanced details - mailman_log('debug', 'CookHeaders: Starting to process message %s for list %s', - msgid, mlist.internal_name()) - mailman_log('debug', 'CookHeaders: Message details:') - mailman_log('debug', ' Message ID: %s', msgid) - mailman_log('debug', ' From: %s', msg.get('from', 'unknown')) - mailman_log('debug', ' To: %s', msg.get('to', 'unknown')) - mailman_log('debug', ' Subject: %s', msg.get('subject', '(no subject)')) - mailman_log('debug', ' Message type: %s', type(msg).__name__) - mailman_log('debug', ' Message data: %s', str(msgdata)) - mailman_log('debug', ' Pipeline: %s', msgdata.get('pipeline', 'No pipeline')) - - # Set the "X-Ack: no" header if noack flag is set + # Set the "X-Ack: no" header if noack flag is set. if msgdata.get('noack'): - mailman_log('debug', 'CookHeaders: Setting X-Ack: no for message %s', msgid) change_header('X-Ack', 'no', mlist, msg, msgdata) - - # Save original sender for later + # Because we're going to modify various important headers in the email + # message, we want to save some of the information in the msgdata + # dictionary for later. Specifically, the sender header will get waxed, + # but we need it for the Acknowledge module later. + # We may have already saved it; if so, don't clobber it here. if 'original_sender' not in msgdata: msgdata['original_sender'] = msg.get_sender() - mailman_log('debug', 'CookHeaders: Saved original sender %s for message %s', - msgdata['original_sender'], msgid) - - # Handle subject prefix and other headers + # VirginRunner sets _fasttrack for internally crafted messages. fasttrack = msgdata.get('_fasttrack') if not msgdata.get('isdigest') and not fasttrack: try: - mailman_log('debug', 'CookHeaders: Adding subject prefix for message %s', msgid) prefix_subject(mlist, msg, msgdata) - except (UnicodeError, ValueError) as e: - mailman_log('error', 'CookHeaders: Error adding subject prefix for message %s: %s', - msgid, str(e)) - - # Mark message as processed - mailman_log('debug', 'CookHeaders: Adding X-BeenThere header for message %s', msgid) + except (UnicodeError, ValueError): + # TK: Sometimes subject header is not MIME encoded for 8bit + # simply abort prefixing. + pass + # Mark message so we know we've been here, but leave any existing + # X-BeenThere's intact. change_header('X-BeenThere', mlist.GetListEmail(), - mlist, msg, msgdata, delete=False) - - # Add standard headers - mailman_log('debug', 'CookHeaders: Adding standard headers for message %s', msgid) + mlist, msg, msgdata, delete=False) + # Add Precedence: and other useful headers. None of these are standard + # and finding information on some of them are fairly difficult. Some are + # just common practice, and we'll add more here as they become necessary. + # Good places to look are: + # + # http://www.dsv.su.se/~jpalme/ietf/jp-ietf-home.html + # http://www.faqs.org/rfcs/rfc2076.html + # + # None of these headers are added if they already exist. BAW: some + # consider the advertising of this a security breach. I.e. if there are + # known exploits in a particular version of Mailman and we know a site is + # using such an old version, they may be vulnerable. It's too easy to + # edit the code to add a configuration variable to handle this. change_header('X-Mailman-Version', mm_cfg.VERSION, - mlist, msg, msgdata, repl=False) + mlist, msg, msgdata, repl=False) + # We set "Precedence: list" because this is the recommendation from the + # sendmail docs, the most authoritative source of this header's semantics. change_header('Precedence', 'list', - mlist, msg, msgdata, repl=False) - - # Handle From: header munging if needed + mlist, msg, msgdata, repl=False) + # Do we change the from so the list takes ownership of the email if (msgdata.get('from_is_list') or mlist.from_is_list) and not fasttrack: - mailman_log('debug', 'CookHeaders: Munging From header for message %s', msgid) - munge_from_header(mlist, msg, msgdata) - - mailman_log('debug', 'CookHeaders: Finished processing message %s', msgid) + # Be as robust as possible here. + faddrs = getaddresses(msg.get_all('from', [])) + # Strip the nulls and bad emails. + faddrs = [x for x in faddrs if x[1].find('@') > 0] + if len(faddrs) == 1: + realname, email = o_from = faddrs[0] + else: + # No From: or multiple addresses. Just punt and take + # the get_sender result. + realname = '' + email = msgdata['original_sender'] + o_from = (realname, email) + if not realname: + if mlist.isMember(email): + realname = mlist.getMemberName(email) or email + else: + realname = email + # Remove domain from realname if it looks like an email address + realname = re.sub(r'@([^ .]+\.)+[^ .]+$', '---', realname) + # Make a display name and RFC 2047 encode it if necessary. This is + # difficult and kludgy. If the realname came from From: it should be + # ascii or RFC 2047 encoded. If it came from the list, it should be + # in the charset of the list's preferred language or possibly unicode. + # if it's from the email address, it should be ascii. In any case, + # make it a unicode. + if isinstance(realname, str): + urn = realname + else: + rn, cs = ch_oneline(realname) + urn = str(rn, cs, errors='replace') + # likewise, the list's real_name which should be ascii, but use the + # charset of the list's preferred_language which should be a superset. + lcs = Utils.GetCharSet(mlist.preferred_language) + + if isinstance(mlist.real_name, str): + ulrn = mlist.real_name + else: + ulrn = str(mlist.real_name, lcs, errors='replace') + + # get translated 'via' with dummy replacements + realname = '%(realname)s' + lrn = '%(lrn)s' + # We want the i18n context to be the list's preferred_language. It + # could be the poster's. + otrans = i18n.get_translation() + i18n.set_language(mlist.preferred_language) + via = _('%(realname)s via %(lrn)s') + i18n.set_translation(otrans) + + if isinstance(via, str): + uvia = via + else: + uvia = str(via, lcs, errors='replace') -def munge_from_header(mlist, msg, msgdata): - """Munge the From: header for the list. - - This is separated into its own function to make the logic clearer - and handle all the encoding issues in one place. - """ - # Get the original From: addresses - faddrs = getaddresses(msg.get_all('from', [])) - faddrs = [x for x in faddrs if x[1].find('@') > 0] - - if len(faddrs) == 1: - realname, email = faddrs[0] + # Replace the dummy replacements. + uvia = re.sub(u'%\\(lrn\\)s', ulrn, re.sub(u'%\\(realname\\)s', urn, uvia)) + # And get an RFC 2047 encoded header string. + dn = str(Header(uvia, lcs)) + change_header('From', + formataddr((dn, mlist.GetListEmail())), + mlist, msg, msgdata) else: - realname = '' - email = msgdata['original_sender'] - - # Get or create realname - if not realname: - if mlist.isMember(email): - realname = mlist.getMemberName(email) or email + # Use this as a flag + o_from = None + # Reply-To: munging. Do not do this if the message is "fast tracked", + # meaning it is internally crafted and delivered to a specific user. BAW: + # Yuck, I really hate this feature but I've caved under the sheer pressure + # of the (very vocal) folks want it. OTOH, RFC 2822 allows Reply-To: to + # be a list of addresses, so instead of replacing the original, simply + # augment it. RFC 2822 allows max one Reply-To: header so collapse them + # if we're adding a value, otherwise don't touch it. (Should we collapse + # in all cases?) + # MAS: We need to do some things with the original From: if we've munged + # it for DMARC mitigation. We have goals for this process which are + # not completely compatible, so we do the best we can. Our goals are: + # 1) as long as the list is not anonymous, the original From: address + # should be obviously exposed, i.e. not just in a header that MUAs + # don't display. + # 2) the original From: address should not be in a comment or display + # name in the new From: because it is claimed that multiple domains + # in any fields in From: are indicative of spamminess. This means + # it should be in Reply-To: or Cc:. + # 3) the behavior of an MUA doing a 'reply' or 'reply all' should be + # consistent regardless of whether or not the From: is munged. + # Goal 3) implies sometimes the original From: should be in Reply-To: + # and sometimes in Cc:, and even so, this goal won't be achieved in + # all cases with all MUAs. In cases of conflict, the above ordering of + # goals is priority order. + + if not fasttrack: + # A convenience function, requires nested scopes. pair is (name, addr) + new = [] + d = {} + def add(pair): + lcaddr = pair[1].lower() + if lcaddr in d: + return + d[lcaddr] = pair + new.append(pair) + # List admin wants an explicit Reply-To: added + if mlist.reply_goes_to_list == 2: + add(parseaddr(mlist.reply_to_address)) + # If we're not first stripping existing Reply-To: then we need to add + # the original Reply-To:'s to the list we're building up. In both + # cases we'll zap the existing field because RFC 2822 says max one is + # allowed. + o_rt = False + if not mlist.first_strip_reply_to: + orig = msg.get_all('reply-to', []) + for pair in getaddresses(orig): + # There's an original Reply-To: and we're not removing it. + add(pair) + o_rt = True + # We also need to put the old From: in Reply-To: in all cases where + # it is not going in Cc:. This is when reply_goes_to_list == 0 and + # either there was no original Reply-To: or we stripped it. + # However, if there was an original Reply-To:, unstripped, and it + # contained the original From: address we need to flag that it's + # there so we don't add the original From: to Cc: + if o_from and mlist.reply_goes_to_list == 0: + if o_rt: + if o_from[1].lower() in d: + # Original From: address is in original Reply-To:. + # Pretend we added it. + o_from = None + else: + add(o_from) + # Flag that we added it. + o_from = None + # Set Reply-To: header to point back to this list. Add this last + # because some folks think that some MUAs make it easier to delete + # addresses from the right than from the left. + if mlist.reply_goes_to_list == 1: + i18ndesc = uheader(mlist, mlist.description, 'Reply-To') + add((str(i18ndesc), mlist.GetListEmail())) + # Don't put Reply-To: back if there's nothing to add! + if new: + # Preserve order + change_header('Reply-To', + COMMASPACE.join([formataddr(pair) for pair in new]), + mlist, msg, msgdata) else: - realname = email - - # Remove domain from realname if it looks like an email - realname = re.sub(r'@([^ .]+\.)+[^ .]+$', '---', realname) - - # Convert realname to unicode - charset = Utils.GetCharSet(mlist.preferred_language) - if isinstance(realname, bytes): - try: - realname = realname.decode(charset) - except UnicodeDecodeError: - realname = realname.decode('utf-8', 'replace') - - # Format the new From: header - via = _('%(realname)s via %(listname)s') - listname = mlist.real_name - if isinstance(listname, bytes): - listname = listname.decode(charset, 'replace') - - display_name = via % {'realname': realname, 'listname': listname} - - # Create the new From: header value - new_from = formataddr((display_name, mlist.GetListEmail())) - change_header('From', new_from, mlist, msg, msgdata) + del msg['reply-to'] + # The To field normally contains the list posting address. However + # when messages are fully personalized, that header will get + # overwritten with the address of the recipient. We need to get the + # posting address in one of the recipient headers or they won't be + # able to reply back to the list. It's possible the posting address + # was munged into the Reply-To header, but if not, we'll add it to a + # Cc header. BAW: should we force it into a Reply-To header in the + # above code? + # Also skip Cc if this is an anonymous list as list posting address + # is already in From and Reply-To in this case. + # We do add the Cc in cases where From: header munging is being done + # because even though the list address is in From:, the Reply-To: + # poster will override it. Brain dead MUAs may then address the list + # twice on a 'reply all', but reasonable MUAs should do the right + # thing. We also add the original From: to Cc: if it wasn't added + # to Reply-To: + add_list = (mlist.personalize == 2 and + mlist.reply_goes_to_list != 1 and + not mlist.anonymous_list) + if add_list or o_from: + # Watch out for existing Cc headers, merge, and remove dups. Note + # that RFC 2822 says only zero or one Cc header is allowed. + new = [] + d = {} + # If we're adding the original From:, add it first. + if o_from: + add(o_from) + # AvoidDuplicates may have set a new Cc: in msgdata.add_header, + # so check that. + if ('add_header' in msgdata and + 'Cc' in msgdata['add_header']): + for pair in getaddresses([msgdata['add_header']['Cc']]): + add(pair) + else: + for pair in getaddresses(msg.get_all('cc', [])): + add(pair) + if add_list: + i18ndesc = uheader(mlist, mlist.description, 'Cc') + add((str(i18ndesc), mlist.GetListEmail())) + change_header('Cc', + COMMASPACE.join([formataddr(pair) for pair in new]), + mlist, msg, msgdata) + # Add list-specific headers as defined in RFC 2369 and RFC 2919, but only + # if the message is being crafted for a specific list (e.g. not for the + # password reminders). + # + # BAW: Some people really hate the List-* headers. It seems that the free + # version of Eudora (possibly on for some platforms) does not hide these + # headers by default, pissing off their users. Too bad. Fix the MUAs. + if msgdata.get('_nolist') or not mlist.include_rfc2369_headers: + return + # This will act like an email address for purposes of formataddr() + listid = '%s.%s' % (mlist.internal_name(), mlist.host_name) + cset = Utils.GetCharSet(mlist.preferred_language) + if mlist.description: + # Don't wrap the header since here we just want to get it properly RFC + # 2047 encoded. + i18ndesc = uheader(mlist, mlist.description, 'List-Id', maxlinelen=998) + listid_h = formataddr((str(i18ndesc), listid)) + # With some charsets (utf-8?) and some invalid chars, str(18ndesc) can + # be empty. + if str(i18ndesc): + listid_h = formataddr((str(i18ndesc), listid)) + else: + listid_h = '<%s>' % listid + else: + # without desc we need to ensure the MUST brackets + listid_h = '<%s>' % listid + # We always add a List-ID: header. + change_header('List-Id', listid_h, mlist, msg, msgdata) + # For internally crafted messages, we also add a (nonstandard), + # "X-List-Administrivia: yes" header. For all others (i.e. those coming + # from list posts), we add a bunch of other RFC 2369 headers. + requestaddr = mlist.GetRequestEmail() + subfieldfmt = '<%s>, ' + listinfo = mlist.GetScriptURL('listinfo', absolute=1) + useropts = mlist.GetScriptURL('options', absolute=1) + headers = {} + if msgdata.get('reduced_list_headers'): + headers['X-List-Administrivia'] = 'yes' + else: + headers.update({ + 'List-Help' : '' % requestaddr, + 'List-Unsubscribe': subfieldfmt % (useropts, requestaddr, 'un'), + 'List-Subscribe' : subfieldfmt % (listinfo, requestaddr, ''), + }) + # List-Post: is controlled by a separate attribute + if mlist.include_list_post_header: + headers['List-Post'] = '' % mlist.GetListEmail() + # Add this header if we're archiving + if mlist.archive: + archiveurl = mlist.GetBaseArchiveURL() + headers['List-Archive'] = '<%s>' % archiveurl + # First we delete any pre-existing headers because the RFC permits only + # one copy of each, and we want to be sure it's ours. + for h, v in list(headers.items()): + # Wrap these lines if they are too long. 78 character width probably + # shouldn't be hardcoded, but is at least text-MUA friendly. The + # adding of 2 is for the colon-space separator. + if len(h) + 2 + len(v) > 78: + v = CONTINUATION.join(v.split(', ')) + change_header(h, v, mlist, msg, msgdata) + + def prefix_subject(mlist, msg, msgdata): - """Add the list's subject prefix to the message's Subject: header.""" - # Get the subject and charset - subject = msg.get('subject', '') - if not subject: - return - - # Get the list's charset - cset = mlist.preferred_language - - # Get the prefix + # Add the subject prefix unless the message is a digest or is being fast + # tracked (e.g. internally crafted, delivered to a single user such as the + # list admin). prefix = mlist.subject_prefix.strip() if not prefix: return - - # Handle the subject encoding - try: - # If subject is already a string, use it directly + subject = msg.get('subject', '') + # Try to figure out what the continuation_ws is for the header + if isinstance(subject, Header): + lines = str(subject).splitlines() + else: + lines = subject.splitlines() + ws = ' ' + if len(lines) > 1 and lines[1] and lines[1][0] in ' \t': + ws = lines[1][0] + msgdata['origsubj'] = subject + # The subject may be multilingual but we take the first charset as major + # one and try to decode. If it is decodable, returned subject is in one + # line and cset is properly set. If fail, subject is mime-encoded and + # cset is set as us-ascii. See detail for ch_oneline() (CookHeaders one + # line function). + subject, cset = ch_oneline(subject) + # TK: Python interpreter has evolved to be strict on ascii charset code + # range. It is safe to use unicode string when manupilating header + # contents with re module. It would be best to return unicode in + # ch_oneline() but here is temporary solution. + subject = subject.__str__() #TODO will this break some encodings? + # If the subject_prefix contains '%d', it is replaced with the + # mailing list sequential number. Sequential number format allows + # '%d' or '%05d' like pattern. + prefix_pattern = re.escape(prefix) + # unescape '%' :-< + prefix_pattern = prefix_pattern.replace(r'\%', '%') + p = re.compile(r'%\d*d') + if p.search(prefix, 1): + # prefix have number, so we should search prefix w/number in subject. + # Also, force new style. + prefix_pattern = p.sub(r'\\s*\\d+\\s*', prefix_pattern) + old_style = False + else: + old_style = mm_cfg.OLD_STYLE_PREFIXING + subject = re.sub(prefix_pattern, '', subject) + # Previously the following re didn't have the first \s*. It would fail + # if the incoming Subject: was like '[prefix] Re: Re: Re:' because of the + # leading space after stripping the prefix. It is not known what MUA would + # create such a Subject:, but the issue was reported. + rematch = re.match( + r'(\s*(RE|AW|SV|VS)\s*(\[\d+\])?\s*:\s*)+', + subject, re.I) + if rematch: + subject = subject[rematch.end():] + recolon = 'Re:' + else: + recolon = '' + # Strip leading and trailing whitespace from subject. + subject = subject.strip() + # At this point, subject may become null if someone post mail with + # Subject: [subject prefix] + if subject == '': + # We want the i18n context to be the list's preferred_language. It + # could be the poster's. + otrans = i18n.get_translation() + i18n.set_language(mlist.preferred_language) + subject = _('(no subject)') + i18n.set_translation(otrans) + cset = Utils.GetCharSet(mlist.preferred_language) if isinstance(subject, str): - subject_str = subject - # If subject is a Header object, convert it to string - elif isinstance(subject, Header): - subject_str = str(subject) - else: - # Try to decode the subject - try: - subject_str = str(subject, cset) - except (UnicodeError, LookupError): - # If that fails, try utf-8 - subject_str = str(subject, 'utf-8', 'replace') - except Exception as e: - mailman_log('error', 'Error decoding subject: %s', str(e)) - return - - # Add the prefix if it's not already there - if not subject_str.startswith(prefix): - msg['Subject'] = prefix + ' ' + subject_str + subject = subject.encode() + subject = str(subject, cset) + # and substitute %d in prefix with post_id + try: + prefix = prefix % mlist.post_id + except TypeError: + pass + # If charset is 'us-ascii', try to concatnate as string because there + # is some weirdness in Header module (TK) + if cset == 'us-ascii': + try: + if old_style: + h = u' '.join([recolon, prefix, subject]) + else: + if recolon: + h = u' '.join([prefix, recolon, subject]) + else: + h = u' '.join([prefix, subject]) + h = h.encode('us-ascii') + h = uheader(mlist, h, 'Subject', continuation_ws=ws) + change_header('Subject', h, mlist, msg, msgdata) + ss = u' '.join([recolon, subject]) + ss = ss.encode('us-ascii') + ss = uheader(mlist, ss, 'Subject', continuation_ws=ws) + msgdata['stripped_subject'] = ss + return + except UnicodeError: + pass + # Get the header as a Header instance, with proper unicode conversion + # Because of rfc2047 encoding, spaces between encoded words can be + # insignificant, so we need to append spaces to our encoded stuff. + prefix += ' ' + if recolon: + recolon += ' ' + if old_style: + h = uheader(mlist, recolon, 'Subject', continuation_ws=ws) + h.append(prefix) + else: + h = uheader(mlist, prefix, 'Subject', continuation_ws=ws) + h.append(recolon) + # TK: Subject is concatenated and unicode string. + subject = subject.encode(cset, 'replace') + h.append(subject, cset) + change_header('Subject', h, mlist, msg, msgdata) + ss = uheader(mlist, recolon, 'Subject', continuation_ws=ws) + ss.append(subject, cset) + msgdata['stripped_subject'] = ss + + def ch_oneline(headerstr): # Decode header string in one line and convert into single charset # copied and modified from ToDigest.py and Utils.py # return (string, cset) tuple as check for failure try: - # Ensure headerstr is a string, not bytes - if isinstance(headerstr, bytes): - try: - headerstr = headerstr.decode('utf-8') - except UnicodeDecodeError: - headerstr = headerstr.decode('us-ascii', 'replace') - d = decode_header(headerstr) # at this point, we should rstrip() every string because some # MUA deliberately add trailing spaces when composing return @@ -279,14 +511,8 @@ def ch_oneline(headerstr): cset = x[1] break h = make_header(d) - ustr = str(h) - oneline = u''.join(ustr.splitlines()) - return oneline.encode(cset, 'replace'), cset + ustr = h + return ustr, cset except (LookupError, UnicodeError, ValueError, HeaderParseError): # possibly charset problem. return with undecoded string in one line. - if isinstance(headerstr, bytes): - try: - headerstr = headerstr.decode('utf-8') - except UnicodeDecodeError: - headerstr = headerstr.decode('us-ascii', 'replace') return ''.join(headerstr.splitlines()), 'us-ascii' diff --git a/Mailman/Handlers/Decorate.py b/Mailman/Handlers/Decorate.py index 3f2a8c80..43338768 100644 --- a/Mailman/Handlers/Decorate.py +++ b/Mailman/Handlers/Decorate.py @@ -18,6 +18,7 @@ """Decorate a message by sticking the header and footer around it.""" from builtins import str +import codecs import re from email.mime.text import MIMEText @@ -40,8 +41,7 @@ def process(mlist, msg, msgdata): # Calculate the extra personalization dictionary. Note that the # length of the recips list better be exactly 1. recips = msgdata.get('recips') - if not (isinstance(recips, list) and len(recips) == 1): - raise ValueError(f'Invalid recipients: expected list with one item, got {type(recips)} with {len(recips)} items') + assert type(recips) == list and len(recips) == 1 member = recips[0].lower() d['user_address'] = member try: @@ -103,7 +103,11 @@ def process(mlist, msg, msgdata): else: ufooter = str(footer, lcset, 'ignore') try: - oldpayload = str(msg.get_payload(decode=True), mcset) + oldpayload = msg.get_payload(decode=True) + if isinstance(oldpayload, bytes): + oldpayload = oldpayload.decode(encoding=mcset) + if Utils.needs_unicode_escape_decode(oldpayload): + oldpayload = codecs.decode(oldpayload, 'unicode_escape') frontsep = endsep = u'' if header and not header.endswith('\n'): frontsep = u'\n' @@ -177,7 +181,7 @@ def process(mlist, msg, msgdata): inner.set_default_type(msg.get_default_type()) if not copied: inner['Content-Type'] = inner.get_content_type() - if msg['mime-version'] is None: + if msg['mime-version'] == None: msg['MIME-Version'] = '1.0' # BAW: HACK ALERT. if hasattr(msg, '__version__'): @@ -201,32 +205,10 @@ def process(mlist, msg, msgdata): msg['Content-Type'] = 'multipart/mixed' + def decorate(mlist, template, what, extradict=None): # `what' is just a descriptive phrase used in the log message - # If template is None, return empty string - if template is None: - syslog('error', 'Template is None for %s', what) - return '' - - # If template is a Message object, get its content - if isinstance(template, Message): - try: - template = template.get_payload(decode=True) - if isinstance(template, bytes): - template = template.decode('utf-8', 'replace') - except Exception as e: - syslog('error', 'Error getting payload from Message template for %s: %s', what, str(e)) - return '' - - # Ensure template is a string - if not isinstance(template, str): - try: - template = str(template) - except Exception as e: - syslog('error', 'Error converting template to string for %s: %s', what, str(e)) - return '' - # If template is only whitespace, ignore it. if len(re.sub(r'\s', '', template)) == 0: return '' diff --git a/Mailman/Handlers/Hold.py b/Mailman/Handlers/Hold.py index 764dba96..09bec6b4 100644 --- a/Mailman/Handlers/Hold.py +++ b/Mailman/Handlers/Hold.py @@ -29,17 +29,15 @@ """ import email +from email.parser import Parser from email.mime.text import MIMEText from email.mime.message import MIMEMessage import email.utils -import re -from email.iterators import body_line_iterator -import traceback from Mailman import mm_cfg from Mailman import Utils from Mailman import Errors -from Mailman.Message import Message, UserNotification +from Mailman import Message from Mailman import i18n from Mailman import Pending from Mailman.Logging.Syslog import syslog @@ -50,6 +48,7 @@ def _(s): return s + class ForbiddenPoster(Errors.HoldMessage): reason = _('Sender is explicitly forbidden') rejection = _('You are forbidden from posting messages to this list.') @@ -110,14 +109,13 @@ def rejection_notice(self, mlist): class ModeratedNewsgroup(ModeratedPost): reason = _('Posting to a moderated newsgroup') -class HTMLViewerRequired(Errors.HoldMessage): - reason = _('Message contains HTML viewer required text') - rejection = _('Your message contains text indicating it requires an HTML viewer, which is not allowed.') + # And reset the translator _ = i18n._ + def ackp(msg): ack = msg.get('x-ack', '').lower() precedence = msg.get('precedence', '').lower() @@ -126,223 +124,174 @@ def ackp(msg): return 1 + def process(mlist, msg, msgdata): - try: - if msgdata.get('approved'): - return - # Get the sender of the message - listname = mlist.internal_name() - adminaddr = listname + '-admin' - sender = msg.get_sender() - # Special case an ugly sendmail feature: If there exists an alias of the - # form "owner-foo: bar" and sendmail receives mail for address "foo", - # sendmail will change the envelope sender of the message to "bar" before - # delivering. This feature does not appear to be configurable. *Boggle*. - if not sender or sender[:len(listname)+6] == adminaddr: - sender = msg.get_sender(use_envelope=0) - # - # Check for HTML viewer required text in text/plain parts - for part in msg.walk(): - if part.get_content_type() == 'text/plain': - payload = part.get_payload(decode=True) - if payload: - try: - text = payload.decode('utf-8', errors='replace') - if "An HTML viewer is required to see this message" in text: - hold_for_approval(mlist, msg, msgdata, HTMLViewerRequired) - return - except (UnicodeDecodeError, AttributeError): - # If we can't decode the payload, try as bytes - if isinstance(payload, bytes): - if b"An HTML viewer is required to see this message" in payload: - hold_for_approval(mlist, msg, msgdata, HTMLViewerRequired) - return - # - # Possible administrivia? - if mlist.administrivia and Utils.is_administrivia(msg): - hold_for_approval(mlist, msg, msgdata, Administrivia) + if msgdata.get('approved'): + return + # Get the sender of the message + listname = mlist.internal_name() + adminaddr = listname + '-admin' + sender = msg.get_sender() + # Special case an ugly sendmail feature: If there exists an alias of the + # form "owner-foo: bar" and sendmail receives mail for address "foo", + # sendmail will change the envelope sender of the message to "bar" before + # delivering. This feature does not appear to be configurable. *Boggle*. + if not sender or sender[:len(listname)+6] == adminaddr: + sender = msg.get_sender(use_envelope=0) + # + # Possible administrivia? + if mlist.administrivia and Utils.is_administrivia(msg): + hold_for_approval(mlist, msg, msgdata, Administrivia) + # no return + # + # Are there too many recipients to the message? + if mlist.max_num_recipients > 0: + # figure out how many recipients there are + recips = email.utils.getaddresses(msg.get_all('to', []) + + msg.get_all('cc', [])) + if len(recips) >= mlist.max_num_recipients: + hold_for_approval(mlist, msg, msgdata, TooManyRecipients) # no return - # - # Are there too many recipients to the message? - if mlist.max_num_recipients > 0: - # figure out how many recipients there are - recips = email.utils.getaddresses(msg.get_all('to', []) + - msg.get_all('cc', [])) - if len(recips) >= mlist.max_num_recipients: - hold_for_approval(mlist, msg, msgdata, TooManyRecipients) - # no return - # - # Implicit destination? Note that message originating from the Usenet - # side of the world should never be checked for implicit destination. - if mlist.require_explicit_destination and \ - not mlist.HasExplicitDest(msg) and \ - not msgdata.get('fromusenet'): - # then - hold_for_approval(mlist, msg, msgdata, ImplicitDestination) + # + # Implicit destination? Note that message originating from the Usenet + # side of the world should never be checked for implicit destination. + if mlist.require_explicit_destination and \ + not mlist.HasExplicitDest(msg) and \ + not msgdata.get('fromusenet'): + # then + hold_for_approval(mlist, msg, msgdata, ImplicitDestination) + # no return + # + # Suspicious headers? + if mlist.bounce_matching_headers: + triggered = mlist.hasMatchingHeader(msg) + if triggered: + # TBD: Darn - can't include the matching line for the admin + # message because the info would also go to the sender + hold_for_approval(mlist, msg, msgdata, SuspiciousHeaders) + # no return + # + # Is the message too big? + if mlist.max_message_size > 0: + bodylen = 0 + for line in email.iterators.body_line_iterator(msg): + bodylen += len(line) + for part in msg.walk(): + if part.preamble: + bodylen += len(part.preamble) + if part.epilogue: + bodylen += len(part.epilogue) + if bodylen/1024.0 > mlist.max_message_size: + hold_for_approval(mlist, msg, msgdata, + MessageTooBig(bodylen, mlist.max_message_size)) # no return - # - # Suspicious headers? - if mlist.bounce_matching_headers: - triggered = mlist.hasMatchingHeader(msg) - if triggered: - # TBD: Darn - can't include the matching line for the admin - # message because the info would also go to the sender - hold_for_approval(mlist, msg, msgdata, SuspiciousHeaders) - # no return - # - # Is the message too big? - if mlist.max_message_size > 0: - bodylen = 0 - for line in body_line_iterator(msg): - bodylen += len(line) - for part in msg.walk(): - if part.preamble: - bodylen += len(part.preamble) - if part.epilogue: - bodylen += len(part.epilogue) - if bodylen/1024.0 > mlist.max_message_size: - hold_for_approval(mlist, msg, msgdata, - MessageTooBig(bodylen, mlist.max_message_size)) - # no return - # - # Are we gatewaying to a moderated newsgroup and is this list the - # moderator's address for the group? - if mlist.gateway_to_news and mlist.news_moderation == 2: - hold_for_approval(mlist, msg, msgdata, ModeratedNewsgroup) - except Errors.HoldMessage: - # These are expected conditions, not errors - raise - except Exception as e: - # Only log unexpected errors - syslog('error', 'Error in Hold.process: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - raise + # + # Are we gatewaying to a moderated newsgroup and is this list the + # moderator's address for the group? + if mlist.gateway_to_news and mlist.news_moderation == 2: + hold_for_approval(mlist, msg, msgdata, ModeratedNewsgroup) + def hold_for_approval(mlist, msg, msgdata, exc): - try: - # BAW: This should really be tied into the email confirmation system so - # that the message can be approved or denied via email as well as the - # web. - # - # Check if exc is a class (new-style in Python 3) - if isinstance(exc, type): - exc = exc() - # Get the sender of the message - sender = msg.get_sender() - # Get the list's owner address - owneraddr = mlist.GetOwnerEmail() - # Get the subject - subject = msg.get('subject', _('(no subject)')) - # Get the language to use - lang = mlist.getMemberLanguage(sender) - # Get the text of the message - text = exc.rejection_notice(mlist) - listname = mlist.real_name - sender = msgdata.get('sender', msg.get_sender()) - usersubject = msg.get('subject') - charset = Utils.GetCharSet(mlist.preferred_language) - if usersubject: - usersubject = Utils.oneline(usersubject, charset) - else: - usersubject = _('(no subject)') - message_id = msg.get('message-id', 'n/a') - adminaddr = mlist.GetBouncesEmail() - requestaddr = mlist.GetRequestEmail() - # We need to send both the reason and the rejection notice through the - # translator again, because of the games we play above - reason = Utils.wrap(exc.reason_notice()) - if isinstance(exc, NonMemberPost) and mlist.nonmember_rejection_notice: - msgdata['rejection_notice'] = Utils.wrap( - mlist.nonmember_rejection_notice.replace( - '%(listowner)s', owneraddr)) - else: - msgdata['rejection_notice'] = Utils.wrap(exc.rejection_notice(mlist)) - id = mlist.HoldMessage(msg, reason, msgdata) - # Now we need to craft and send a message to the list admin so they can - # deal with the held message. - d = {'listname' : listname, - 'hostname' : mlist.host_name, - 'reason' : _(reason), - 'sender' : sender, - 'subject' : usersubject, - 'admindb_url': mlist.GetScriptURL('admindb', absolute=1), - } - # Ensure the list is locked before calling pend_new - if not mlist.Locked(): - mlist.Lock() - try: - cookie = mlist.pend_new(Pending.HELD_MESSAGE, id) - finally: - mlist.Unlock() - else: - cookie = mlist.pend_new(Pending.HELD_MESSAGE, id) - # We may want to send a notification to the original sender too - fromusenet = msgdata.get('fromusenet') - # Since we're sending two messages, which may potentially be in different - # languages (the user's preferred and the list's preferred for the admin), - # we need to play some i18n games here. Since the current language - # context ought to be set up for the user, let's craft his message first. - if not fromusenet and ackp(msg) and mlist.respond_to_post_requests and \ - mlist.autorespondToSender(sender, mlist.getMemberLanguage(sender)): - # Get a confirmation cookie - d['confirmurl'] = '%s/%s' % (mlist.GetScriptURL('confirm', absolute=1), - cookie) - lang = msgdata.get('lang', mlist.getMemberLanguage(sender)) - subject = _('Your message to %(listname)s awaits moderator approval') % {'listname': listname} - text = Utils.maketext('postheld.txt', d, lang=lang, mlist=mlist) - nmsg = UserNotification(sender, owneraddr, subject, text, lang) - nmsg.send(mlist) - # Now the message for the list owners. Be sure to include the list - # moderators in this message. This one should appear to come from - # -owner since we really don't need to do bounce processing on it. - if mlist.admin_immed_notify: - # Now let's temporarily set the language context to that which the - # admin is expecting. - otranslation = i18n.get_translation() - i18n.set_language(mlist.preferred_language) - try: - lang = mlist.preferred_language - charset = Utils.GetCharSet(lang) - # We need to regenerate or re-translate a few values in d - d['reason'] = _(reason) - d['subject'] = usersubject - # craft the admin notification message and deliver it - subject = _('%(listname)s post from %(sender)s requires approval') - nmsg = UserNotification(owneraddr, owneraddr, subject, - lang=lang) - nmsg.set_type('multipart/mixed') - text = MIMEText( - Utils.maketext('postauth.txt', d, raw=1, mlist=mlist), - _charset=charset) - dmsg = MIMEText(Utils.wrap(_(""" + # BAW: This should really be tied into the email confirmation system so + # that the message can be approved or denied via email as well as the + # web. + # + if isinstance(exc, type): + # Go ahead and instantiate it now. + exc = exc() + listname = mlist.real_name + sender = msgdata.get('sender', msg.get_sender()) + usersubject = msg.get('subject') + charset = Utils.GetCharSet(mlist.preferred_language) + if usersubject: + usersubject = Utils.oneline(usersubject, charset) + else: + usersubject = _('(no subject)') + message_id = msg.get('message-id', 'n/a') + owneraddr = mlist.GetOwnerEmail() + adminaddr = mlist.GetBouncesEmail() + requestaddr = mlist.GetRequestEmail() + # We need to send both the reason and the rejection notice through the + # translator again, because of the games we play above + reason = Utils.wrap(exc.reason_notice()) + if isinstance(exc, NonMemberPost) and mlist.nonmember_rejection_notice: + msgdata['rejection_notice'] = Utils.wrap( + mlist.nonmember_rejection_notice.replace( + '%(listowner)s', owneraddr)) + else: + msgdata['rejection_notice'] = Utils.wrap(exc.rejection_notice(mlist)) + id = mlist.HoldMessage(msg, reason, msgdata) + # Now we need to craft and send a message to the list admin so they can + # deal with the held message. + d = {'listname' : listname, + 'hostname' : mlist.host_name, + 'reason' : _(reason), + 'sender' : sender, + 'subject' : usersubject, + 'admindb_url': mlist.GetScriptURL('admindb', absolute=1), + } + # We may want to send a notification to the original sender too + fromusenet = msgdata.get('fromusenet') + # Since we're sending two messages, which may potentially be in different + # languages (the user's preferred and the list's preferred for the admin), + # we need to play some i18n games here. Since the current language + # context ought to be set up for the user, let's craft his message first. + cookie = mlist.pend_new(Pending.HELD_MESSAGE, id) + if not fromusenet and ackp(msg) and mlist.respond_to_post_requests and \ + mlist.autorespondToSender(sender, mlist.getMemberLanguage(sender)): + # Get a confirmation cookie + d['confirmurl'] = '%s/%s' % (mlist.GetScriptURL('confirm', absolute=1), + cookie) + lang = msgdata.get('lang', mlist.getMemberLanguage(sender)) + subject = _('Your message to %(listname)s awaits moderator approval') + text = Utils.maketext('postheld.txt', d, lang=lang, mlist=mlist) + nmsg = Message.UserNotification(sender, owneraddr, subject, text, lang) + nmsg.send(mlist) + # Now the message for the list owners. Be sure to include the list + # moderators in this message. This one should appear to come from + # -owner since we really don't need to do bounce processing on it. + if mlist.admin_immed_notify: + # Now let's temporarily set the language context to that which the + # admin is expecting. + otranslation = i18n.get_translation() + i18n.set_language(mlist.preferred_language) + try: + lang = mlist.preferred_language + charset = Utils.GetCharSet(lang) + # We need to regenerate or re-translate a few values in d + d['reason'] = _(reason) + d['subject'] = usersubject + # craft the admin notification message and deliver it + subject = _('%(listname)s post from %(sender)s requires approval') + nmsg = Message.UserNotification(owneraddr, owneraddr, subject, + lang=lang) + nmsg.set_type('multipart/mixed') + text = MIMEText( + Utils.maketext('postauth.txt', d, raw=1, mlist=mlist), + _charset=charset) + dmsg = MIMEText(Utils.wrap(_("""\ If you reply to this message, keeping the Subject: header intact, Mailman will discard the held message. Do this if the message is spam. If you reply to this message and include an Approved: header with the list password in it, the message will be approved for posting to the list. The Approved: header can also appear in the first line of the body of the reply.""")), - _charset=Utils.GetCharSet(lang)) - dmsg['Subject'] = 'confirm ' + cookie - dmsg['Sender'] = requestaddr - dmsg['From'] = requestaddr - dmsg['Date'] = email.utils.formatdate(localtime=True) - dmsg['Message-ID'] = Utils.unique_message_id(mlist) - nmsg.attach(text) - nmsg.attach(MIMEMessage(msg)) - nmsg.attach(MIMEMessage(dmsg)) - nmsg.send(mlist, **{'tomoderators': 1}) - finally: - i18n.set_translation(otranslation) - # Log the held message (info level, not error) - syslog('info', '[HOLD] %s post from %s held for approval, message-id=%s, reason=%s', - listname, sender, message_id, reason) - # raise the specific MessageHeld exception to exit out of the message - # delivery pipeline - raise exc - except Errors.HoldMessage: - # Already handled above, do not log traceback - raise - except Exception as e: - syslog('error', 'Error in Hold.hold_for_approval: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - raise + _charset=Utils.GetCharSet(lang)) + dmsg['Subject'] = 'confirm ' + cookie + dmsg['Sender'] = requestaddr + dmsg['From'] = requestaddr + dmsg['Date'] = email.utils.formatdate(localtime=True) + dmsg['Message-ID'] = Utils.unique_message_id(mlist) + nmsg.attach(text) + nmsg.attach(MIMEMessage(msg)) + nmsg.attach(MIMEMessage(dmsg)) + nmsg.send(mlist, **{'tomoderators': 1}) + finally: + i18n.set_translation(otranslation) + # Log the held message + syslog('vette', '%s post from %s held, message-id=%s: %s', + listname, sender, message_id, reason) + # raise the specific MessageHeld exception to exit out of the message + # delivery pipeline + raise exc diff --git a/Mailman/Handlers/MimeDel.py b/Mailman/Handlers/MimeDel.py index 290ea363..f583368f 100644 --- a/Mailman/Handlers/MimeDel.py +++ b/Mailman/Handlers/MimeDel.py @@ -27,6 +27,7 @@ import os import errno import tempfile +import html2text from os.path import splitext from email.iterators import typed_subpart_iterator @@ -35,17 +36,12 @@ from Mailman import Errors from Mailman.Message import UserNotification from Mailman.Queue.sbcache import get_switchboard -from Mailman.Logging.Syslog import syslog from Mailman.Version import VERSION from Mailman.i18n import _ from Mailman.Utils import oneline -# Lazy import to avoid circular dependency -def get_switchboard(qdir): - from Mailman.Queue.sbcache import get_switchboard - return get_switchboard(qdir) - + def process(mlist, msg, msgdata): # Short-circuits if not mlist.filter_content: @@ -123,6 +119,7 @@ def process(mlist, msg, msgdata): msg['X-Content-Filtered-By'] = 'Mailman/MimeDel %s' % VERSION + def reset_payload(msg, subpart): # Reset payload of msg to contents of subpart, and fix up content headers payload = subpart.get_payload() @@ -143,6 +140,7 @@ def reset_payload(msg, subpart): msg['Content-Description'] = cdesc + def filter_parts(msg, filtertypes, passtypes, filterexts, passexts): # Look at all the message's subparts, and recursively filter if not msg.is_multipart(): @@ -180,6 +178,7 @@ def filter_parts(msg, filtertypes, passtypes, filterexts, passexts): return 1 + def collapse_multipart_alternatives(msg): if not msg.is_multipart(): return @@ -206,6 +205,7 @@ def collapse_multipart_alternatives(msg): msg.set_payload(newpayload) + def recast_multipart(msg): # If we're left with a multipart message with only one sub-part, recast # the message to just the sub-part, but not if the part is message/rfc822 @@ -227,33 +227,34 @@ def recast_multipart(msg): recast_multipart(part) + def to_plaintext(msg): changedp = 0 - for subpart in typed_subpart_iterator(msg, 'text', 'html'): - filename = tempfile.mktemp('.html') - fp = open(filename, 'w') - try: - fp.write(subpart.get_payload(decode=1)) - fp.close() - cmd = os.popen(mm_cfg.HTML_TO_PLAIN_TEXT_COMMAND % - {'filename': filename}) - plaintext = cmd.read() - rtn = cmd.close() - if rtn: - syslog('error', 'HTML->text/plain error: %s', rtn) - finally: - try: - os.unlink(filename) - except OSError as e: - if e.errno != errno.ENOENT: raise + # Get the subparts (ensure you're iterating through them) + subparts = list(typed_subpart_iterator(msg, 'text', 'html')) + + # Iterate through the subparts + for subpart in subparts: + + # Get the HTML content (ensure it's decoded if it's in bytes) + html_content = subpart.get_payload(decode=1) # Get the payload as bytes + + if isinstance(html_content, bytes): + html_content = html_content.decode('utf-8') # Decode bytes to string + + # Now convert HTML to plain text + plaintext = html2text.html2text(html_content) + # Now replace the payload of the subpart and twiddle the Content-Type: - del subpart['content-transfer-encoding'] - subpart.set_payload(plaintext) - subpart.set_type('text/plain') + del subpart['content-transfer-encoding'] # Remove encoding if necessary + subpart.set_payload(plaintext) # Set the new plaintext payload + subpart.set_type('text/plain') # Change the content type to 'text/plain' changedp = 1 + return changedp + def dispose(mlist, msg, msgdata, why): # filter_action == 0 just discards, see below if mlist.filter_action == 1: diff --git a/Mailman/Handlers/Moderate.py b/Mailman/Handlers/Moderate.py index c3c1ae3f..422859f8 100644 --- a/Mailman/Handlers/Moderate.py +++ b/Mailman/Handlers/Moderate.py @@ -23,26 +23,18 @@ from email.mime.text import MIMEText from email.utils import parseaddr -import Mailman from Mailman import mm_cfg from Mailman import Utils +from Mailman import Message from Mailman import Errors -from Mailman import i18n from Mailman.i18n import _ -from Mailman.Message import Message +from Mailman.Handlers import Hold from Mailman.Logging.Syslog import syslog -from Mailman.Logging.Syslog import mailman_log +from Mailman.MailList import MailList -# Lazy imports to avoid circular dependencies -def get_hold(): - import Mailman.Handlers.Hold as Hold - return Hold -def get_mail_list(): - from Mailman.MailList import MailList - return MailList.MailList - -class ModeratedMemberPost(get_hold().ModeratedPost): + +class ModeratedMemberPost(Hold.ModeratedPost): # BAW: I wanted to use the reason below to differentiate between this # situation and normal ModeratedPost reasons. Greg Ward and Stonewall # Ballard thought the language was too harsh and mentioned offense taken @@ -53,14 +45,13 @@ class ModeratedMemberPost(get_hold().ModeratedPost): # reason = _('Posts by member are currently quarantined for moderation') pass + + def process(mlist, msg, msgdata): - """Process a message for moderation.""" if msgdata.get('approved'): return # Is the poster a member or not? - for sender_tuple in msg.get_senders(): - # Extract email address from the (realname, address) tuple - _, sender = sender_tuple + for sender in msg.get_senders(): if mlist.isMember(sender): break for sender in Utils.check_eq_domains(sender, @@ -77,16 +68,13 @@ def process(mlist, msg, msgdata): if mlist.getMemberOption(sender, mm_cfg.Moderate): # Note that for member_moderation_action, 0==Hold, 1=Reject, # 2==Discard - member_moderation_action = mlist.member_moderation_action - if member_moderation_action not in (mm_cfg.DEFER, mm_cfg.APPROVE, mm_cfg.REJECT, mm_cfg.DISCARD, mm_cfg.HOLD): - raise ValueError(f'Invalid member_moderation_action: {member_moderation_action}') - if member_moderation_action == 0: + if mlist.member_moderation_action == 0: # Hold. BAW: WIBNI we could add the member_moderation_notice # to the notice sent back to the sender? msgdata['sender'] = sender - get_hold().hold_for_approval(mlist, msg, msgdata, + Hold.hold_for_approval(mlist, msg, msgdata, ModeratedMemberPost) - elif member_moderation_action == 1: + elif mlist.member_moderation_action == 1: # Reject text = mlist.member_moderation_notice if text: @@ -95,7 +83,7 @@ def process(mlist, msg, msgdata): # Use the default RejectMessage notice string text = None raise Errors.RejectMessage(text) - elif member_moderation_action == 2: + elif mlist.member_moderation_action == 2: # Discard. BAW: Again, it would be nice if we could send a # discard notice to the sender raise Errors.DiscardMessage @@ -118,7 +106,7 @@ def process(mlist, msg, msgdata): mlist.hold_these_nonmembers, at_list='hold_these_nonmembers' ): - get_hold().hold_for_approval(mlist, msg, msgdata, get_hold().NonMemberPost) + Hold.hold_for_approval(mlist, msg, msgdata, Hold.NonMemberPost) # No return if mlist.GetPattern(sender, mlist.reject_these_nonmembers, @@ -135,21 +123,20 @@ def process(mlist, msg, msgdata): # Okay, so the sender wasn't specified explicitly by any of the non-member # moderation configuration variables. Handle by way of generic non-member # action. - generic_nonmember_action = mlist.generic_nonmember_action - if not (0 <= generic_nonmember_action <= 4): - raise ValueError(f'Invalid generic_nonmember_action: {generic_nonmember_action}, must be between 0 and 4') - if generic_nonmember_action == 0 or msgdata.get('fromusenet'): + assert 0 <= mlist.generic_nonmember_action <= 4 + if mlist.generic_nonmember_action == 0 or msgdata.get('fromusenet'): # Accept return - elif generic_nonmember_action == 1: - get_hold().hold_for_approval(mlist, msg, msgdata, get_hold().NonMemberPost) - elif generic_nonmember_action == 2: + elif mlist.generic_nonmember_action == 1: + Hold.hold_for_approval(mlist, msg, msgdata, Hold.NonMemberPost) + elif mlist.generic_nonmember_action == 2: do_reject(mlist) - elif generic_nonmember_action == 3: + elif mlist.generic_nonmember_action == 3: do_discard(mlist, msg) + + def do_reject(mlist): - """Handle message rejection.""" listowner = mlist.GetOwnerEmail() if mlist.nonmember_rejection_notice: raise Errors.RejectMessage(Utils.wrap(_(mlist.nonmember_rejection_notice))) @@ -160,15 +147,16 @@ def do_reject(mlist): it. If you think that your messages are being rejected in error, contact the mailing list owner at %(listowner)s."""))) + + def do_discard(mlist, msg): - """Handle message discarding.""" sender = msg.get_sender() # Do we forward auto-discards to the list owners? if mlist.forward_auto_discards: lang = mlist.preferred_language varhelp = '%s/?VARHELP=privacy/sender/discard_these_nonmembers' % \ mlist.GetScriptURL('admin', absolute=1) - nmsg = Mailman.Message.UserNotification(mlist.GetOwnerEmail(), + nmsg = Message.UserNotification(mlist.GetOwnerEmail(), mlist.GetBouncesEmail(), _('Auto-discard notification'), lang=lang) diff --git a/Mailman/Handlers/Replybot.py b/Mailman/Handlers/Replybot.py index a6f513e7..60bc4df3 100644 --- a/Mailman/Handlers/Replybot.py +++ b/Mailman/Handlers/Replybot.py @@ -19,61 +19,103 @@ import time -from Mailman import mm_cfg from Mailman import Utils -from Mailman import Errors -from Mailman import i18n -from Mailman.Message import UserNotification -from Mailman.Logging.Syslog import syslog +from Mailman import Message from Mailman.i18n import _ from Mailman.SafeDict import SafeDict +from Mailman.Logging.Syslog import syslog -# Set up i18n -_ = i18n._ -i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + def process(mlist, msg, msgdata): - """Process a message through the replybot handler. - - Args: - mlist: The MailList object - msg: The message to process - msgdata: Additional message metadata - - Returns: - bool: True if message should be discarded, False otherwise - """ - # Get the sender + # Normally, the replybot should get a shot at this message, but there are + # some important short-circuits, mostly to suppress 'bot storms, at least + # for well behaved email bots (there are other governors for misbehaving + # 'bots). First, if the original message has an "X-Ack: No" header, we + # skip the replybot. Then, if the message has a Precedence header with + # values bulk, junk, or list, and there's no explicit "X-Ack: yes" header, + # we short-circuit. Finally, if the message metadata has a true 'noack' + # key, then we skip the replybot too. + ack = msg.get('x-ack', '').lower() + if ack == 'no' or msgdata.get('noack'): + return + precedence = msg.get('precedence', '').lower() + if ack != 'yes' and precedence in ('bulk', 'junk', 'list'): + return + # Check to see if the list is even configured to autorespond to this email + # message. Note: the owner script sets the `toowner' key, and the various + # confirm, join, leave, request, subscribe and unsubscribe scripts set the + # keys we use for `torequest'. + toadmin = msgdata.get('toowner') + torequest = msgdata.get('torequest') or msgdata.get('toconfirm') or \ + msgdata.get('tojoin') or msgdata.get('toleave') + if ((toadmin and not mlist.autorespond_admin) or + (torequest and not mlist.autorespond_requests) or \ + (not toadmin and not torequest and not mlist.autorespond_postings)): + return + # Now see if we're in the grace period for this sender. graceperiod <= 0 + # means always autorespond, as does an "X-Ack: yes" header (useful for + # debugging). sender = msg.get_sender() - if not sender: - return False - - # Check if we should autorespond - if not mlist.autorespondToSender(sender, msgdata.get('lang', mlist.preferred_language)): - return False - - # Create the response message - outmsg = UserNotification(sender, mlist.GetBouncesEmail(), - _('Automatic response from %(listname)s') % {'listname': mlist.real_name}, - lang=msgdata.get('lang', mlist.preferred_language)) - - # Set the message content - outmsg.set_type('text/plain') - outmsg.set_payload(_("""\ -This message is an automatic response from %(listname)s. - -Your message has been received and will be processed by the list -administrators. Please do not send this message again. - -If you have any questions, please contact the list administrator at -%(adminaddr)s. - -Thank you for your interest in the %(listname)s mailing list. -""") % {'listname': mlist.real_name, - 'adminaddr': mlist.GetOwnerEmail()}) - - # Send the response - outmsg.send(mlist, msgdata=msgdata) - - # Return True to indicate the original message should be discarded - return True + now = time.time() + graceperiod = mlist.autoresponse_graceperiod + if graceperiod > 0 and ack != 'yes': + if toadmin: + quiet_until = mlist.admin_responses.get(sender, 0) + elif torequest: + quiet_until = mlist.request_responses.get(sender, 0) + else: + quiet_until = mlist.postings_responses.get(sender, 0) + if quiet_until > now: + return + # + # Okay, we know we're going to auto-respond to this sender, craft the + # message, send it, and update the database. + realname = mlist.real_name + subject = _( + 'Auto-response for your message to the "%(realname)s" mailing list') + # Do string interpolation + d = SafeDict({'listname' : realname, + 'listurl' : mlist.GetScriptURL('listinfo'), + 'requestemail': mlist.GetRequestEmail(), + # BAW: Deprecate adminemail; it's not advertised but still + # supported for backwards compatibility. + 'adminemail' : mlist.GetBouncesEmail(), + 'owneremail' : mlist.GetOwnerEmail(), + }) + # Just because we're using a SafeDict doesn't mean we can't get all sorts + # of other exceptions from the string interpolation. Let's be ultra + # conservative here. + if toadmin: + rtext = mlist.autoresponse_admin_text + elif torequest: + rtext = mlist.autoresponse_request_text + else: + rtext = mlist.autoresponse_postings_text + # Using $-strings? + if getattr(mlist, 'use_dollar_strings', 0): + rtext = Utils.to_percent(rtext) + try: + text = rtext % d + except Exception: + syslog('error', 'Bad autoreply text for list: %s\n%s', + mlist.internal_name(), rtext) + text = rtext + # Wrap the response. + text = Utils.wrap(text) + outmsg = Message.UserNotification(sender, mlist.GetBouncesEmail(), + subject, text, mlist.preferred_language) + outmsg['X-Mailer'] = _('The Mailman Replybot') + # prevent recursions and mail loops! + outmsg['X-Ack'] = 'No' + outmsg.send(mlist) + # update the grace period database + if graceperiod > 0: + # graceperiod is in days, we need # of seconds + quiet_until = now + graceperiod * 24 * 60 * 60 + if toadmin: + mlist.admin_responses[sender] = quiet_until + elif torequest: + mlist.request_responses[sender] = quiet_until + else: + mlist.postings_responses[sender] = quiet_until diff --git a/Mailman/Handlers/SMTPDirect.py b/Mailman/Handlers/SMTPDirect.py index 0707694b..aa98357e 100644 --- a/Mailman/Handlers/SMTPDirect.py +++ b/Mailman/Handlers/SMTPDirect.py @@ -33,21 +33,13 @@ import smtplib from smtplib import SMTPException from base64 import b64encode -import traceback -import os -import errno -import pickle -import email.message -from email.message import Message from Mailman import mm_cfg -import Mailman.Utils -import Mailman.Errors -from Mailman.Message import Message -from Mailman.Handlers.Decorate import decorate -from Mailman.Logging.Syslog import mailman_log -import Mailman.SafeDict -from Mailman.Queue.sbcache import get_switchboard +from Mailman import Utils +from Mailman import Errors +from Mailman.Handlers import Decorate +from Mailman.Logging.Syslog import syslog +from Mailman.SafeDict import MsgSafeDict import email from email.utils import formataddr @@ -56,60 +48,65 @@ DOT = '.' + # Manage a connection to the SMTP server class Connection(object): def __init__(self): self.__conn = None def __connect(self): - try: - self.__conn = smtplib.SMTP() - self.__conn.set_debuglevel(mm_cfg.SMTPLIB_DEBUG_LEVEL) - # Ensure we have a valid hostname for TLS - helo_host = mm_cfg.SMTP_HELO_HOST - if not helo_host or helo_host.startswith('.'): - helo_host = mm_cfg.SMTPHOST - if not helo_host or helo_host.startswith('.'): - # If we still don't have a valid hostname, use localhost - helo_host = 'localhost' - mailman_log('smtp', 'Connecting to SMTP server %s:%s with HELO %s', - mm_cfg.SMTPHOST, mm_cfg.SMTPPORT, helo_host) - self.__conn.connect(mm_cfg.SMTPHOST, mm_cfg.SMTPPORT) - # Set the hostname for TLS - self.__conn._host = helo_host - if mm_cfg.SMTP_AUTH: - if mm_cfg.SMTP_USE_TLS: - mailman_log('smtp', 'Using TLS with hostname: %s', helo_host) - try: - # Use native TLS support - self.__conn.starttls() - except SMTPException as e: - mailman_log('smtp-failure', 'SMTP TLS error: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - self.quit() - raise + self.__conn = smtplib.SMTP() + self.__conn.set_debuglevel(mm_cfg.SMTPLIB_DEBUG_LEVEL) + + # Ensure we have a valid hostname for the connection + smtp_host = mm_cfg.SMTPHOST + if not smtp_host or smtp_host.startswith('.') or smtp_host == '@URLHOST@': + smtp_host = 'localhost' + + # Log the hostname being used for debugging + syslog('smtp-failure', 'SMTP connection hostname: %s (original: %s)', + smtp_host, mm_cfg.SMTPHOST) + + self.__conn.connect(smtp_host, mm_cfg.SMTPPORT) + if mm_cfg.SMTP_AUTH: + if mm_cfg.SMTP_USE_TLS: + # Log the hostname being used for TLS + syslog('smtp-failure', 'TLS connection hostname: %s', self.__conn._host) try: - self.__conn.login(mm_cfg.SMTP_USER, mm_cfg.SMTP_PASSWD) - except smtplib.SMTPHeloError as e: - mailman_log('smtp-failure', 'SMTP HELO error: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) + # Ensure the hostname is set for TLS + if not self.__conn._host: + self.__conn._host = smtp_host + syslog('smtp-failure', 'Set TLS hostname to: %s', smtp_host) + self.__conn.starttls() + except SMTPException as e: + syslog('smtp-failure', 'SMTP TLS error: %s', e) self.quit() raise - except smtplib.SMTPAuthenticationError as e: - mailman_log('smtp-failure', 'SMTP AUTH error: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - self.quit() - except smtplib.SMTPException as e: - mailman_log('smtp-failure', - 'SMTP - no suitable authentication method found: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) + try: + # Use a valid hostname for EHLO, fallback to localhost if SMTP_HELO_HOST is empty or invalid + helo_host = mm_cfg.SMTP_HELO_HOST + if not helo_host or helo_host.startswith('.') or helo_host == '@URLHOST@': + helo_host = 'localhost' + self.__conn.ehlo(helo_host) + except SMTPException as e: + syslog('smtp-failure', 'SMTP EHLO error: %s', e) self.quit() raise - except (socket.error, smtplib.SMTPException) as e: - mailman_log('smtp-failure', 'SMTP connection error: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - self.quit() - raise + try: + self.__conn.login(mm_cfg.SMTP_USER, mm_cfg.SMTP_PASSWD) + except smtplib.SMTPHeloError as e: + syslog('smtp-failure', 'SMTP HELO error: %s', e) + self.quit() + raise + except smtplib.SMTPAuthenticationError as e: + syslog('smtp-failure', 'SMTP AUTH error: %s', e) + self.quit() + raise + except smtplib.SMTPException as e: + syslog('smtp-failure', + 'SMTP - no suitable authentication method found: %s', e) + self.quit() + raise self.__numsessions = mm_cfg.SMTP_MAX_SESSIONS_PER_CONNECTION @@ -117,24 +114,12 @@ def sendmail(self, envsender, recips, msgtext): if self.__conn is None: self.__connect() try: - # Convert message to string if it's a Message object - if isinstance(msgtext, Message): - msgtext = msgtext.as_string() - # Ensure msgtext is properly encoded as UTF-8 - if isinstance(msgtext, str): - msgtext = msgtext.encode('utf-8') - # Convert recips to list if it's not already - if not isinstance(recips, list): - recips = [recips] - # Ensure envsender is a string - if isinstance(envsender, bytes): - envsender = envsender.decode('utf-8') + if isinstance( msgtext, str ): + msgtext = msgtext.encode('utf-8', errors='ignore') results = self.__conn.sendmail(envsender, recips, msgtext) - except smtplib.SMTPException as e: + except smtplib.SMTPException: # For safety, close this connection. The next send attempt will # automatically re-open it. Pass the exception on up. - mailman_log('smtp-failure', 'SMTP sendmail error: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) self.quit() raise # This session has been successfully completed. @@ -156,116 +141,146 @@ def quit(self): self.__conn = None + def process(mlist, msg, msgdata): - """Process the message for delivery. - - This is the main entry point for the SMTPDirect handler. - """ - t0 = time.time() - refused = {} - envsender = msgdata.get('envsender', msg.get_sender()) - if envsender is None: - envsender = mlist.GetBouncesEmail() - - # Get the list of recipients with better validation - recips = msgdata.get('recips', []) + recips = msgdata.get('recips') if not recips: - # Try to get from message headers as fallback - recips = msg.get_all('to', []) + msg.get_all('cc', []) - if not recips: - # Get message details for logging - msgid = msg.get('message-id', 'unknown') - sender = msg.get('from', 'unknown') - subject = msg.get('subject', 'no subject') - to = msg.get('to', 'no to') - cc = msg.get('cc', 'no cc') - - mailman_log('error', - 'No recipients found in msgdata for message:\n' - ' Message-ID: %s\n' - ' From: %s\n' - ' Subject: %s\n' - ' To: %s\n' - ' Cc: %s\n' - ' List: %s\n' - ' Pipeline: %s\n' - ' Full msgdata: %s', - msgid, sender, subject, to, cc, mlist.internal_name(), - msgdata.get('pipeline', 'No pipeline'), - str(msgdata)) - return - - # Check for spam headers first - if msg.get('x-google-group-id'): - mailman_log('error', 'Silently dropping message with X-Google-Group-Id header: %s', - msg.get('message-id', 'unknown')) - # Add all recipients to refused list with 550 error - for r in recips: - refused[r] = (550, 'Message rejected due to spam detection') - # Update failures dict - msgdata['failures'] = refused - # Silently return without raising an exception + # Nobody to deliver to! return - - # Chunkify the recipients - chunks = chunkify(recips, mm_cfg.SMTP_MAX_RCPTS) - # Choose the delivery function based on VERP settings - if msgdata.get('verp'): + # Calculate the non-VERP envelope sender. + envsender = msgdata.get('envsender') + if envsender is None: + if mlist: + envsender = mlist.GetBouncesEmail() + else: + envsender = Utils.get_site_email(extra='bounces') + # Time to split up the recipient list. If we're personalizing or VERPing + # then each chunk will have exactly one recipient. We'll then hand craft + # an envelope sender and stitch a message together in memory for each one + # separately. If we're not VERPing, then we'll chunkify based on + # SMTP_MAX_RCPTS. Note that most MTAs have a limit on the number of + # recipients they'll swallow in a single transaction. + deliveryfunc = None + if ('personalize' not in msgdata or msgdata['personalize']) and ( + msgdata.get('verp') or mlist.personalize): + chunks = [[recip] for recip in recips] + msgdata['personalize'] = 1 deliveryfunc = verpdeliver + elif mm_cfg.SMTP_MAX_RCPTS <= 0: + chunks = [recips] else: + chunks = chunkify(recips, mm_cfg.SMTP_MAX_RCPTS) + # See if this is an unshunted message for which some were undelivered + if 'undelivered' in msgdata: + chunks = msgdata['undelivered'] + # If we're doing bulk delivery, then we can stitch up the message now. + if deliveryfunc is None: + # Be sure never to decorate the message more than once! + if not msgdata.get('decorated'): + Decorate.process(mlist, msg, msgdata) + msgdata['decorated'] = True deliveryfunc = bulkdeliver - + refused = {} + t0 = time.time() + # Open the initial connection + origrecips = msgdata['recips'] + # MAS: get the message sender now for logging. If we're using 'sender' + # and not 'from', bulkdeliver changes it for bounce processing. If we're + # VERPing, it doesn't matter because bulkdeliver is working on a copy, but + # otherwise msg gets changed. If the list is anonymous, the original + # sender is long gone, but Cleanse.py has logged it. + origsender = msgdata.get('original_sender', msg.get_sender()) + # `undelivered' is a copy of chunks that we pop from to do deliveries. + # This seems like a good tradeoff between robustness and resource + # utilization. If delivery really fails (i.e. qfiles/shunt type + # failures), then we'll pick up where we left off with `undelivered'. + # This means at worst, the last chunk for which delivery was attempted + # could get duplicates but not every one, and no recips should miss the + # message. + conn = Connection() try: - origrecips = msgdata.get('recips', None) - origsender = msgdata.get('original_sender', msg.get_sender()) - conn = Connection() - try: - msgdata['undelivered'] = chunks - while chunks: - chunk = chunks.pop() - msgdata['recips'] = chunk - try: - deliveryfunc(mlist, msg, msgdata, envsender, refused, conn) - except Mailman.Errors.RejectMessage as e: - # Handle message rejection gracefully - mailman_log('error', 'Message rejected: %s', str(e)) - # Add all recipients in this chunk to refused list - for r in chunk: - refused[r] = (550, str(e)) - continue - except Exception as e: - mailman_log('error', - 'Delivery error for chunk: %s\nError: %s\n%s', - chunk, str(e), traceback.format_exc()) - chunks.append(chunk) - raise - del msgdata['undelivered'] - finally: - conn.quit() - msgdata['recips'] = origrecips - - # Log the successful post - t1 = time.time() - listname = mlist.internal_name() - if isinstance(listname, bytes): - listname = listname.decode('latin-1') - d = Mailman.SafeDict.MsgSafeDict(msg, {'time' : t1-t0, + msgdata['undelivered'] = chunks + while chunks: + chunk = chunks.pop() + msgdata['recips'] = chunk + try: + deliveryfunc(mlist, msg, msgdata, envsender, refused, conn) + except Exception: + # If /anything/ goes wrong, push the last chunk back on the + # undelivered list and re-raise the exception. We don't know + # how many of the last chunk might receive the message, so at + # worst, everyone in this chunk will get a duplicate. Sigh. + chunks.append(chunk) + raise + del msgdata['undelivered'] + finally: + conn.quit() + msgdata['recips'] = origrecips + # Log the successful post + t1 = time.time() + d = MsgSafeDict(msg, {'time' : t1-t0, + # BAW: Urg. This seems inefficient. 'size' : len(msg.as_string()), '#recips' : len(recips), '#refused': len(refused), - 'listname': listname, + 'listname': mlist.internal_name(), 'sender' : origsender, }) - if mm_cfg.SMTP_LOG_EVERY_MESSAGE: - mailman_log(mm_cfg.SMTP_LOG_EVERY_MESSAGE[0], - mm_cfg.SMTP_LOG_EVERY_MESSAGE[1] % d.copy()) + # We have to use the copy() method because extended call syntax requires a + # concrete dictionary object; it does not allow a generic mapping. It's + # still worthwhile doing the interpolation in syslog() because it'll catch + # any catastrophic exceptions due to bogus format strings. + if mm_cfg.SMTP_LOG_EVERY_MESSAGE: + syslog.write_ex(mm_cfg.SMTP_LOG_EVERY_MESSAGE[0], + mm_cfg.SMTP_LOG_EVERY_MESSAGE[1], kws=d) + + if refused: + if mm_cfg.SMTP_LOG_REFUSED: + syslog.write_ex(mm_cfg.SMTP_LOG_REFUSED[0], + mm_cfg.SMTP_LOG_REFUSED[1], kws=d) - except Exception as e: - mailman_log('error', 'Error in SMTPDirect.process: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - raise + elif msgdata.get('tolist'): + # Log the successful post, but only if it really was a post to the + # mailing list. Don't log sends to the -owner, or -admin addrs. + # -request addrs should never get here. BAW: it may be useful to log + # the other messages, but in that case, we should probably have a + # separate configuration variable to control that. + if mm_cfg.SMTP_LOG_SUCCESS: + syslog.write_ex(mm_cfg.SMTP_LOG_SUCCESS[0], + mm_cfg.SMTP_LOG_SUCCESS[1], kws=d) + # Process any failed deliveries. + tempfailures = [] + permfailures = [] + for recip, (code, smtpmsg) in list(refused.items()): + # DRUMS is an internet draft, but it says: + # + # [RFC-821] incorrectly listed the error where an SMTP server + # exhausts its implementation limit on the number of RCPT commands + # ("too many recipients") as having reply code 552. The correct + # reply code for this condition is 452. Clients SHOULD treat a 552 + # code in this case as a temporary, rather than permanent failure + # so the logic below works. + # + if code >= 500 and code != 552: + # A permanent failure + permfailures.append(recip) + else: + # Deal with persistent transient failures by queuing them up for + # future delivery. TBD: this could generate lots of log entries! + tempfailures.append(recip) + if mm_cfg.SMTP_LOG_EACH_FAILURE: + d.update({'recipient': recip, + 'failcode' : code, + 'failmsg' : smtpmsg}) + syslog.write_ex(mm_cfg.SMTP_LOG_EACH_FAILURE[0], + mm_cfg.SMTP_LOG_EACH_FAILURE[1], kws=d) + # Return the results + if tempfailures or permfailures: + raise Errors.SomeRecipientsFailed(tempfailures, permfailures) + + def chunkify(recips, chunksize): # First do a simple sort on top level domain. It probably doesn't buy us # much to try to sort on MX record -- that's the MTA's job. We're just @@ -278,9 +293,9 @@ def chunkify(recips, chunksize): 'edu': 4, 'us' : 5, 'ca' : 6, - 'uk' : 7, - 'jp' : 8, - 'au' : 9, + 'uk' : 7, + 'jp' : 8, + 'au' : 9, } # Need to sort by domain name. if we split to chunks it is possible # some well-known domains will be interspersed as we sort by @@ -291,7 +306,6 @@ def chunkify(recips, chunksize): i = r.rfind('.') if i >= 0: tld = r[i+1:] - # Use get() with default value of 0 for unknown TLDs bin = chunkmap.get(tld, 0) bucket = buckets.get(bin, []) bucket.append(r) @@ -315,170 +329,144 @@ def chunkify(recips, chunksize): return chunks + def verpdeliver(mlist, msg, msgdata, envsender, failures, conn): for recip in msgdata['recips']: - try: - # We now need to stitch together the message with its header and - # footer. If we're VERPIng, we have to calculate the envelope sender - # for each recipient. Note that the list of recipients must be of - # length 1. - msgdata['recips'] = [recip] - # Make a copy of the message and decorate + delivery that - msgcopy = copy.deepcopy(msg) - decorate(mlist, msgcopy, msgdata) - # Calculate the envelope sender, which we may be VERPing - if msgdata.get('verp'): - try: - bmailbox, bdomain = Mailman.Utils.ParseEmail(envsender) - rmailbox, rdomain = Mailman.Utils.ParseEmail(recip) - if rdomain is None: - # The recipient address is not fully-qualified. We can't - # deliver it to this person, nor can we craft a valid verp - # header. I don't think there's much we can do except ignore - # this recipient. - mailman_log('smtp', 'Skipping VERP delivery to unqual recip: %s', - recip) - continue - d = {'bounces': bmailbox, - 'mailbox': rmailbox, - 'host' : DOT.join(rdomain), - } - envsender = '%s@%s' % ((mm_cfg.VERP_FORMAT % d), DOT.join(bdomain)) - except Exception as e: - mailman_log('error', 'Failed to parse email addresses for VERP: %s', e) - continue - if mlist.personalize == 2: - # When fully personalizing, we want the To address to point to the - # recipient, not to the mailing list - del msgcopy['to'] - name = None - if mlist.isMember(recip): - name = mlist.getMemberName(recip) - if name: - # Convert the name to an email-safe representation. If the - # name is a byte string, convert it first to Unicode, given - # the character set of the member's language, replacing bad - # characters for which we can do nothing about. Once we have - # the name as Unicode, we can create a Header instance for it - # so that it's properly encoded for email transport. - charset = Mailman.Utils.GetCharSet(mlist.getMemberLanguage(recip)) - if charset == 'us-ascii': - # Since Header already tries both us-ascii and utf-8, - # let's add something a bit more useful. - charset = 'iso-8859-1' - charset = Charset(charset) - codec = charset.input_codec or 'ascii' - if not isinstance(name, str): - name = str(name, codec, 'replace') - name = Header(name, charset).encode() - msgcopy['To'] = formataddr((name, recip)) - else: - msgcopy['To'] = recip - # We can flag the mail as a duplicate for each member, if they've - # already received this message, as calculated by Message-ID. See - # AvoidDuplicates.py for details. - if 'x-mailman-copy' in msgcopy: - del msgcopy['x-mailman-copy'] - if recip in msgdata.get('add-dup-header', {}): - msgcopy['X-Mailman-Copy'] = 'yes' - # If desired, add the RCPT_BASE64_HEADER_NAME header - if len(mm_cfg.RCPT_BASE64_HEADER_NAME) > 0: - del msgcopy[mm_cfg.RCPT_BASE64_HEADER_NAME] - msgcopy[mm_cfg.RCPT_BASE64_HEADER_NAME] = b64encode(recip) - # For the final delivery stage, we can just bulk deliver to a party of - # one. ;) - bulkdeliver(mlist, msgcopy, msgdata, envsender, failures, conn) - except Exception as e: - mailman_log('error', 'Failed to process VERP delivery: %s', e) - continue + # We now need to stitch together the message with its header and + # footer. If we're VERPIng, we have to calculate the envelope sender + # for each recipient. Note that the list of recipients must be of + # length 1. + # + # BAW: ezmlm includes the message number in the envelope, used when + # sending a notification to the user telling her how many messages + # they missed due to bouncing. Neat idea. + msgdata['recips'] = [recip] + # Make a copy of the message and decorate + delivery that + msgcopy = copy.deepcopy(msg) + Decorate.process(mlist, msgcopy, msgdata) + # Calculate the envelope sender, which we may be VERPing + if msgdata.get('verp'): + bmailbox, bdomain = Utils.ParseEmail(envsender) + rmailbox, rdomain = Utils.ParseEmail(recip) + if rdomain is None: + # The recipient address is not fully-qualified. We can't + # deliver it to this person, nor can we craft a valid verp + # header. I don't think there's much we can do except ignore + # this recipient. + syslog('smtp', 'Skipping VERP delivery to unqual recip: %s', + recip) + continue + d = {'bounces': bmailbox, + 'mailbox': rmailbox, + 'host' : DOT.join(rdomain), + } + envsender = '%s@%s' % ((mm_cfg.VERP_FORMAT % d), DOT.join(bdomain)) + if mlist.personalize == 2: + # When fully personalizing, we want the To address to point to the + # recipient, not to the mailing list + del msgcopy['to'] + name = None + if mlist.isMember(recip): + name = mlist.getMemberName(recip) + if name: + # Convert the name to an email-safe representation. If the + # name is a byte string, convert it first to Unicode, given + # the character set of the member's language, replacing bad + # characters for which we can do nothing about. Once we have + # the name as Unicode, we can create a Header instance for it + # so that it's properly encoded for email transport. + charset = Utils.GetCharSet(mlist.getMemberLanguage(recip)) + if charset == 'us-ascii': + # Since Header already tries both us-ascii and utf-8, + # let's add something a bit more useful. + charset = 'iso-8859-1' + charset = Charset(charset) + codec = charset.input_codec or 'ascii' + if not isinstance(name, str): + name = str(name, codec, 'replace') + name = Header(name, charset).encode() + msgcopy['To'] = formataddr((name, recip)) + else: + msgcopy['To'] = recip + # We can flag the mail as a duplicate for each member, if they've + # already received this message, as calculated by Message-ID. See + # AvoidDuplicates.py for details. + del msgcopy['x-mailman-copy'] + if recip in msgdata.get('add-dup-header', {}): + msgcopy['X-Mailman-Copy'] = 'yes' + # If desired, add the RCPT_BASE64_HEADER_NAME header + if len(mm_cfg.RCPT_BASE64_HEADER_NAME) > 0: + del msgcopy[mm_cfg.RCPT_BASE64_HEADER_NAME] + msgcopy[mm_cfg.RCPT_BASE64_HEADER_NAME] = b64encode(recip) + # For the final delivery stage, we can just bulk deliver to a party of + # one. ;) + bulkdeliver(mlist, msgcopy, msgdata, envsender, failures, conn) + def bulkdeliver(mlist, msg, msgdata, envsender, failures, conn): - # Initialize recips and refused at the start - recips = [] + # Do some final cleanup of the message header. Start by blowing away + # any the Sender: and Errors-To: headers so remote MTAs won't be + # tempted to delivery bounces there instead of our envelope sender + # + # BAW An interpretation of RFCs 2822 and 2076 could argue for not touching + # the Sender header at all. Brad Knowles points out that MTAs tend to + # wipe existing Return-Path headers, and old MTAs may still honor + # Errors-To while new ones will at worst ignore the header. + # + # With some MUAs (eg. Outlook 2003) rewriting the Sender header with our + # envelope sender causes more problems than it solves, because some will + # include the Sender address in a reply-to-all, which is not only + # confusing to subscribers, but can actually disable/unsubscribe them from + # lists, depending on how often they accidentally reply to it. Also, when + # forwarding mail inline, the sender is replaced with the string "Full + # Name (on behalf bounce@addr.ess)", essentially losing the original + # sender address. To partially mitigate this, we add the list name as a + # display-name in the Sender: header that we add. + # + # The drawback of not touching the Sender: header is that some MTAs might + # still send bounces to it, so by not trapping it, we can miss bounces. + # (Or worse, MTAs might send bounces to the From: address if they can't + # find a Sender: header.) So instead of completely disabling the sender + # rewriting, we offer an option to disable it. + del msg['errors-to'] + msg['Errors-To'] = envsender + if mlist.include_sender_header: + del msg['sender'] + msg['Sender'] = '"%s" <%s>' % (mlist.real_name, envsender) + # Get the plain, flattened text of the message, sans unixfrom + # using our as_string() method to not mangle From_ and not fold + # sub-part headers possibly breaking signatures. + msgtext = msg.as_string(mangle_from_=False) refused = {} + recips = msgdata['recips'] + msgid = msg['message-id'] try: - # Get the list of recipients - recips = msgdata.get('recips', []) - if not recips: - mailman_log('error', 'SMTPDirect: No recipients found in msgdata for message:\n%s', msg.get('Message-ID', 'n/a')) - return - - # Convert email.message.Message to Mailman.Message if needed - if isinstance(msg, email.message.Message) and not isinstance(msg, Message): - mailman_msg = Message() - # Copy all attributes from the original message - for key, value in msg.items(): - mailman_msg[key] = value - # Copy the payload with proper MIME handling - if msg.is_multipart(): - for part in msg.get_payload(): - if isinstance(part, email.message.Message): - mailman_msg.attach(part) - else: - newpart = Message() - newpart.set_payload(part) - mailman_msg.attach(newpart) - else: - mailman_msg.set_payload(msg.get_payload()) - msg = mailman_msg - - # Do some final cleanup of the message header - del msg['errors-to'] - msg['Errors-To'] = envsender - if mlist.include_sender_header: - del msg['sender'] - msg['Sender'] = '"%s" <%s>' % (mlist.real_name, envsender) - - # Get the plain, flattened text of the message - msgtext = msg.as_string(mangle_from_=False) - # Ensure the message text is properly encoded as UTF-8 - if isinstance(msgtext, str): - msgtext = msgtext.encode('utf-8') - - msgid = msg.get('Message-ID', 'n/a') - # Ensure msgid is a string - if isinstance(msgid, bytes): - try: - msgid = msgid.decode('utf-8', 'replace') - except UnicodeDecodeError: - msgid = msgid.decode('latin-1', 'replace') - elif not isinstance(msgid, str): - msgid = str(msgid) - try: - # Send the message - refused = conn.sendmail(envsender, recips, msgtext) - except smtplib.SMTPRecipientsRefused as e: - mailman_log('smtp-failure', 'All recipients refused: %s, msgid: %s', - e, msgid) - refused = e.recipients - # Move message to bad queue since all recipients were refused - badq = get_switchboard(mm_cfg.BADQUEUE_DIR) - badq.enqueue(msg, msgdata) - except smtplib.SMTPResponseException as e: - mailman_log('smtp-failure', 'SMTP session failure: %s, %s, msgid: %s', - e.smtp_code, e.smtp_error, msgid) - # Properly handle permanent vs temporary failures - if e.smtp_code >= 500 and e.smtp_code != 552: - # Permanent failure - add to refused and move to bad queue - for r in recips: - refused[r] = (e.smtp_code, e.smtp_error) - badq = get_switchboard(mm_cfg.BADQUEUE_DIR) - badq.enqueue(msg, msgdata) - else: - # Temporary failure - don't add to refused - mailman_log('smtp-failure', 'Temporary SMTP failure, will retry: %s', e.smtp_error) - except (socket.error, IOError, smtplib.SMTPException) as e: - # MTA not responding or other socket problems - mailman_log('smtp-failure', 'Low level smtp error: %s, msgid: %s', e, msgid) - error = str(e) + # Send the message + refused = conn.sendmail(envsender, recips, msgtext) + except smtplib.SMTPRecipientsRefused as e: + syslog('smtp-failure', 'All recipients refused: %s, msgid: %s', + e, msgid) + refused = e.recipients + except smtplib.SMTPResponseException as e: + syslog('smtp-failure', 'SMTP session failure: %s, %s, msgid: %s', + e.smtp_code, e.smtp_error, msgid) + # If this was a permanent failure, don't add the recipients to the + # refused, because we don't want them to be added to failures. + # Otherwise, if the MTA rejects the message because of the message + # content (e.g. it's spam, virii, or has syntactic problems), then + # this will end up registering a bounce score for every recipient. + # Definitely /not/ what we want. + if e.smtp_code < 500 or e.smtp_code == 552: + # It's a temporary failure for r in recips: - refused[r] = (-1, error) - # Move message to bad queue for low level errors - badq = get_switchboard(mm_cfg.BADQUEUE_DIR) - badq.enqueue(msg, msgdata) - failures.update(refused) - except Exception as e: - mailman_log('error', 'Error in bulkdeliver: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - raise + refused[r] = (e.smtp_code, e.smtp_error) + except (socket.error, IOError, smtplib.SMTPException) as e: + # MTA not responding, or other socket problems, or any other kind of + # SMTPException. In that case, nothing got delivered, so treat this + # as a temporary failure. + syslog('smtp-failure', 'Low level smtp error: %s, msgid: %s', e, msgid) + error = str(e) + for r in recips: + refused[r] = (-1, error) + failures.update(refused) diff --git a/Mailman/Handlers/Scrubber.py b/Mailman/Handlers/Scrubber.py index 5f6182c7..07eda63a 100644 --- a/Mailman/Handlers/Scrubber.py +++ b/Mailman/Handlers/Scrubber.py @@ -17,15 +17,13 @@ """Cleanse a message for archiving.""" -from __future__ import absolute_import, print_function, unicode_literals - import os import re import time import errno import binascii import tempfile -from io import StringIO, BytesIO +from io import StringIO from email.utils import parsedate from email.parser import HeaderParser @@ -35,7 +33,7 @@ from Mailman import mm_cfg from Mailman import Utils from Mailman import LockFile -from Mailman.Message import Message +from Mailman import Message from Mailman.Errors import DiscardMessage from Mailman.i18n import _ from Mailman.Logging.Syslog import syslog @@ -70,25 +68,25 @@ def check(map): return all + def guess_extension(ctype, ext): - """Guess the file extension for a content type. - - This function handles both strict and non-strict MIME type matching. - """ + # mimetypes maps multiple extensions to the same type, e.g. .doc, .dot, + # and .wiz are all mapped to application/msword. This sucks for finding + # the best reverse mapping. If the extension is one of the giving + # mappings, we'll trust that, otherwise we'll just guess. :/ all = guess_all_extensions(ctype, strict=False) if ext in all: return ext - if ctype.lower() == 'application/octet-stream': + if ctype.lower == 'application/octet-stream': # For this type, all[0] is '.obj'. '.bin' is better. return '.bin' - if ctype.lower() == 'text/plain': + if ctype.lower == 'text/plain': # For this type, all[0] is '.ksh'. '.txt' is better. return '.txt' - return all[0] if all else '.bin' + return all and all[0] def safe_strftime(fmt, t): - """Format time safely, handling invalid timestamps.""" try: return time.strftime(fmt, t) except (TypeError, ValueError, OverflowError): @@ -96,10 +94,10 @@ def safe_strftime(fmt, t): def calculate_attachments_dir(mlist, msg, msgdata): - """Calculate the directory for storing message attachments. - - Uses a combination of date and message ID to create unique paths. - """ + # Calculate the directory that attachments for this message will go + # under. To avoid inode limitations, the scheme will be: + # archives/private//attachments/YYYYMMDD// + # Start by calculating the date-based and msgid-hash components. fmt = '%Y%m%d' datestr = msg.get('Date') if datestr: @@ -113,7 +111,12 @@ def calculate_attachments_dir(mlist, msg, msgdata): datedir = safe_strftime(fmt, datestr) if not datedir: # What next? Unixfrom, I guess. - parts = msg.get_unixfrom().split() + unixfrom = msg.get_unixfrom() + if unixfrom: + parts = unixfrom.split() + else: + # Fallback if no unixfrom + parts = [] try: month = {'Jan':1, 'Feb':2, 'Mar':3, 'Apr':4, 'May':5, 'Jun':6, 'Jul':7, 'Aug':8, 'Sep':9, 'Oct':10, 'Nov':11, 'Dec':12, @@ -124,8 +127,7 @@ def calculate_attachments_dir(mlist, msg, msgdata): # Best we can do I think month = day = year = 0 datedir = '%04d%02d%02d' % (year, month, day) - if not datedir: - raise ValueError('Missing datedir parameter') + assert datedir # As for the msgid hash, we'll base this part on the Message-ID: so that # all attachments for the same message end up in the same directory (we'll # uniquify the filenames in that directory as needed). We use the first 2 @@ -135,27 +137,26 @@ def calculate_attachments_dir(mlist, msg, msgdata): msgid = msg['message-id'] if msgid is None: msgid = msg['Message-ID'] = Utils.unique_message_id(mlist) + + msgid = msgid.encode() # We assume that the message id actually /is/ unique! digest = sha_new(msgid).hexdigest() return os.path.join('attachments', datedir, digest[:4] + digest[-4:]) def replace_payload_by_text(msg, text, charset): - """Replace message payload with text using proper charset handling.""" + # TK: This is a common function in replacing the attachment and the main + # message by a text (scrubbing). del msg['content-type'] del msg['content-transfer-encoding'] - - # Ensure we have str for text and bytes for charset - if isinstance(text, bytes): - text = text.decode('utf-8', 'replace') if isinstance(charset, str): - charset = charset.encode('ascii') - + # email 3.0.1 (python 2.4) doesn't like unicode + charset = charset.encode('us-ascii') msg.set_payload(text, charset) + def process(mlist, msg, msgdata=None): - """Process a message for archiving, handling attachments appropriately.""" sanitize = mm_cfg.ARCHIVE_HTML_SANITIZER outer = True if msgdata is None: @@ -179,11 +180,25 @@ def process(mlist, msg, msgdata=None): # We need to choose a charset for the scrubbed message, so we'll # arbitrarily pick the charset of the first text/plain part in the # message. + # MAS: Also get the RFC 3676 stuff from this part. This seems to + # work OK for scrub_nondigest. It will also work as far as + # scrubbing messages for the archive is concerned, but pipermail + # doesn't pay any attention to the RFC 3676 parameters. The plain + # format digest is going to be a disaster in any case as some of + # messages will be format="flowed" and some not. ToDigest creates + # its own Content-Type: header for the plain digest which won't + # have RFC 3676 parameters. If the message Content-Type: headers + # are retained for display in the digest, the parameters will be + # there for information, but not for the MUA. This is the best we + # can do without having get_payload() process the parameters. if charset is None: charset = part.get_content_charset(lcset) format = part.get_param('format') delsp = part.get_param('delsp') # TK: if part is attached then check charset and scrub if none + # MAS: Content-Disposition is not a good test for 'attached'. + # RFC 2183 sec. 2.10 allows Content-Disposition on the main body. + # Make it specifically 'attachment'. if (part.get('content-disposition', '').lower() == 'attachment' and not part.get_content_charset()): omask = os.umask(0o002) @@ -204,12 +219,16 @@ def process(mlist, msg, msgdata=None): raise DiscardMessage replace_payload_by_text(part, _('HTML attachment scrubbed and removed'), + # Adding charset arg and removing content-type + # sets content-type to text/plain lcset) elif sanitize == 2: # By leaving it alone, Pipermail will automatically escape it pass elif sanitize == 3: - # Pull it out as an attachment but leave it unescaped + # Pull it out as an attachment but leave it unescaped. This + # is dangerous, but perhaps useful for heavily moderated + # lists. omask = os.umask(0o002) try: url = save_attachment(mlist, part, dir, filter_html=False) @@ -220,13 +239,13 @@ def process(mlist, msg, msgdata=None): URL: %(url)s """), lcset) else: - # HTML-escape it and store it as an attachment - payload = part.get_payload(decode=True) - if isinstance(payload, bytes): - payload = payload.decode('utf-8', 'replace') - payload = Utils.websafe(payload) + # HTML-escape it and store it as an attachment, but make it + # look a /little/ bit prettier. :( + payload = Utils.websafe(part.get_payload(decode=True)) # For whitespace in the margin, change spaces into - # non-breaking spaces, and tabs into 8 of those + # non-breaking spaces, and tabs into 8 of those. Then use a + # mono-space font. Still looks hideous to me, but then I'd + # just as soon discard them. def doreplace(s): return s.expandtabs(8).replace(' ', ' ') lines = [doreplace(s) for s in payload.split('\n')] @@ -367,16 +386,26 @@ def doreplace(s): if isinstance(t, str): if not t.endswith('\n'): t += '\n' - text.append(t) + elif isinstance(t, bytes): + if not t.endswith(b'\n'): + t += b'\n' + text.append(t) # Now join the text and set the payload sep = _('-------------- next part --------------\n') # The i18n separator is in the list's charset. Coerce it to the # message charset. try: - s = str(sep, lcset, 'replace') - sep = s.encode(charset, 'replace') - except (UnicodeError, LookupError, ValueError, - AssertionError): + if isinstance(sep, bytes): + # Only decode if it's a bytes object + s = sep.decode(lcset, 'replace') + sep = s.encode(charset, 'replace') + else: + # If it's already a str, no need to decode + sep = sep.encode(charset, 'replace') + except (UnicodeError, LookupError, ValueError, AssertionError) as e: + # If something failed and we are still a string, fall back to UTF-8 + if isinstance(sep, str): + sep = sep.encode('utf-8', 'replace') pass replace_payload_by_text(msg, sep.join(text), charset) if format: @@ -385,75 +414,160 @@ def doreplace(s): msg.set_param('DelSp', delsp) return msg - + def makedirs(dir): - """Create directory hierarchy safely.""" + # Create all the directories to store this attachment in try: os.makedirs(dir, 0o02775) # Unfortunately, FreeBSD seems to be broken in that it doesn't honor # the mode arg of mkdir(). - def twiddle(arg, dirname, names): - os.chmod(dirname, 0o02775) - os.path.walk(dir, twiddle, None) - except OSError as e: - if e.errno != errno.EEXIST: raise + def twiddle(arg, dirpath, dirnames): + for dirname in dirnames: + # Construct the full path for each directory + full_path = os.path.join(dirpath, dirname) + os.chmod(full_path, 0o02775) + for dirpath, dirnames, filenames in os.walk(dir): + twiddle(None, dirpath, dirnames) + except OSError as e: + if e.errno != errno.EEXIST: + raise + def save_attachment(mlist, msg, dir, filter_html=True): - """Save a message attachment safely. - - Returns the URL where the attachment was saved. - """ - # Get the attachment filename - fname = msg.get_filename() - if not fname: - fname = msg.get_param('name') - if not fname: - # Use content-type if no filename is given - ctype = msg.get_content_type() - # Sanitize the content-type so it can be used as a filename - fname = re.sub(r'[^-\w.]', '_', ctype) - # Add an extension if possible - ext = guess_extension(ctype, '') - if ext: - fname += ext - - # Sanitize the filename - fname = re.sub(r'[/\\:]', '_', fname) - fname = re.sub(r'[^-\w.]', '_', fname) - fname = re.sub(r'^\.*', '_', fname) - - # Get the attachment content - payload = msg.get_payload(decode=True) - if not payload: - return None - - # Create attachment directory - dir = os.path.join(mlist.archive_dir(), dir) - makedirs(dir) - - # Save the attachment + fsdir = os.path.join(mlist.archive_dir(), dir) + makedirs(fsdir) + # Figure out the attachment type and get the decoded data + decodedpayload = msg.get_payload(decode=True) + # BAW: mimetypes ought to handle non-standard, but commonly found types, + # e.g. image/jpg (should be image/jpeg). For now we just store such + # things as application/octet-streams since that seems the safest. + ctype = msg.get_content_type() + # i18n file name is encoded + lcset = Utils.GetCharSet(mlist.preferred_language) + filename = Utils.oneline(msg.get_filename(''), lcset) + filename, fnext = os.path.splitext(filename) + # For safety, we should confirm this is valid ext for content-type + # but we can use fnext if we introduce fnext filtering + if mm_cfg.SCRUBBER_USE_ATTACHMENT_FILENAME_EXTENSION: + # HTML message doesn't have filename :-( + ext = fnext or guess_extension(ctype, fnext) + else: + ext = guess_extension(ctype, fnext) + if not ext: + # We don't know what it is, so assume it's just a shapeless + # application/octet-stream, unless the Content-Type: is + # message/rfc822, in which case we know we'll coerce the type to + # text/plain below. + if ctype == 'message/rfc822': + ext = '.txt' + else: + ext = '.bin' + # Allow only alphanumerics, dash, underscore, and dot + ext = sre.sub('', ext) path = None - counter = 0 - while True: - if counter: - fname_parts = os.path.splitext(fname) - fname = '%s-%d%s' % (fname_parts[0], counter, fname_parts[1]) - path = os.path.join(dir, fname) + # We need a lock to calculate the next attachment number + lockfile = os.path.join(fsdir, 'attachments.lock') + lock = LockFile.LockFile(lockfile) + lock.lock() + try: + # Now base the filename on what's in the attachment, uniquifying it if + # necessary. + if not filename or mm_cfg.SCRUBBER_DONT_USE_ATTACHMENT_FILENAME: + filebase = 'attachment' + else: + # Sanitize the filename given in the message headers + parts = pre.split(filename) + filename = parts[-1] + # Strip off leading dots + filename = dre.sub('', filename) + # Allow only alphanumerics, dash, underscore, and dot + filename = sre.sub('', filename) + # If the filename's extension doesn't match the type we guessed, + # which one should we go with? For now, let's go with the one we + # guessed so attachments can't lie about their type. Also, if the + # filename /has/ no extension, then tack on the one we guessed. + # The extension was removed from the name above. + # Allow for extra and ext and keep it under 255 bytes. + filebase = filename[:240] + # Now we're looking for a unique name for this file on the file + # system. If msgdir/filebase.ext isn't unique, we'll add a counter + # after filebase, e.g. msgdir/filebase-cnt.ext + counter = 0 + extra = '' + while True: + path = os.path.join(fsdir, filebase + extra + ext) + # Generally it is not a good idea to test for file existance + # before just trying to create it, but the alternatives aren't + # wonderful (i.e. os.open(..., O_CREAT | O_EXCL) isn't + # NFS-safe). Besides, we have an exclusive lock now, so we're + # guaranteed that no other process will be racing with us. + if os.path.exists(path): + counter += 1 + extra = '-%04d' % counter + else: + break + finally: + lock.unlock() + # `path' now contains the unique filename for the attachment. There's + # just one more step we need to do. If the part is text/html and + # ARCHIVE_HTML_SANITIZER is a string (which it must be or we wouldn't be + # here), then send the attachment through the filter program for + # sanitization + if filter_html and ctype == 'text/html': + base, ext = os.path.splitext(path) + tmppath = base + '-tmp' + ext + fp = open(tmppath, 'w') try: - # Open in binary mode and write bytes directly - with open(path, 'wb') as fp: - fp.write(payload) - break - except OSError as e: - if e.errno != errno.EEXIST: - raise - counter += 1 - - # Make the file group writable - os.chmod(path, 0o0664) - - # Return the URL + fp.write(decodedpayload) + fp.close() + cmd = mm_cfg.ARCHIVE_HTML_SANITIZER % {'filename' : tmppath} + progfp = os.popen(cmd, 'r') + decodedpayload = progfp.read() + status = progfp.close() + if status: + syslog('error', + 'HTML sanitizer exited with non-zero status: %s', + status) + finally: + os.unlink(tmppath) + # BAW: Since we've now sanitized the document, it should be plain + # text. Blarg, we really want the sanitizer to tell us what the type + # if the return data is. :( + ext = '.txt' + path = base + '.txt' + # Is it a message/rfc822 attachment? + elif ctype == 'message/rfc822': + submsg = msg.get_payload() + + # submsg is usually a list containing a single Message object. + # We need to extract that Message object. (taken from Utils.websafe()) + if isinstance(submsg, list) or isinstance(submsg, tuple): + if len(submsg) == 0: + submsg = '' + else: + submsg = submsg[-1] + + # BAW: I'm sure we can eventually do better than this. :( + decodedpayload = Utils.websafe(str(submsg)) + + # encode the message back into the charset of the original message. + mcset = submsg.get_content_charset('') + if mcset == None or mcset == "": + mcset = 'utf-8' + decodedpayload = decodedpayload.encode(mcset) + + fp = open(path, 'wb') + fp.write(decodedpayload) + fp.close() + # Now calculate the url baseurl = mlist.GetBaseArchiveURL() - url = '%s/%s/%s' % (baseurl, dir, fname) + # Private archives will likely have a trailing slash. Normalize. + if baseurl[-1] != '/': + baseurl += '/' + # A trailing space in url string may save users who are using + # RFC-1738 compliant MUA (Not Mozilla). + # Trailing space will definitely be a problem with format=flowed. + # Bracket the URL instead. + url = '<' + baseurl + '%s/%s%s%s>' % (dir, filebase, extra, ext) return url diff --git a/Mailman/Handlers/SpamDetect.py b/Mailman/Handlers/SpamDetect.py index 14c77343..a9e872fe 100644 --- a/Mailman/Handlers/SpamDetect.py +++ b/Mailman/Handlers/SpamDetect.py @@ -25,9 +25,9 @@ TBD: This needs to be made more configurable and robust. """ -from __future__ import absolute_import, print_function, unicode_literals - +from builtins import str import re + from unicodedata import normalize from email.errors import HeaderParseError from email.header import decode_header @@ -39,7 +39,6 @@ from Mailman import Utils from Mailman.Handlers.Hold import hold_for_approval from Mailman.Logging.Syslog import syslog -from Mailman.Message import Message # First, play footsie with _ so that the following are marked as translated, # but aren't actually translated until we need the text later on. @@ -47,6 +46,7 @@ def _(s): return s + class SpamDetected(Errors.DiscardMessage): """The message contains known spam""" @@ -63,71 +63,43 @@ def reason_notice(self): _ = i18n._ -def getDecodedHeaders(msg, lcset): - """Return a Unicode string containing all headers of msg, unfolded and RFC 2047 - decoded. If a header cannot be decoded, it is replaced with a string of - question marks. + +def getDecodedHeaders(msg, cset='utf-8'): + """Returns a unicode containing all the headers of msg, unfolded and + RFC 2047 decoded, normalized and separated by new lines. """ - headers = [] - for name in msg.keys(): - # Get all values for this header (could be multiple) - for value in msg.get_all(name, []): - try: - # Format as "Header: Value" - header_line = '%s: %s' % (name, value) - # Ensure we have a string - if isinstance(header_line, bytes): - header_line = header_line.decode('utf-8', 'replace') - headers.append(header_line) - except (UnicodeError, AttributeError): - # If we can't decode it, replace with question marks - headers.append('?' * len(str(value))) - return '\n'.join(headers) - -def process(mlist, msg, msgdata): - # Check for Google Groups messages first - google_groups_headers = [ - 'X-Google-Groups-Id', - 'X-Google-Groups-Info', - 'X-Google-Groups-Url', - 'X-Google-Groups-Name', - 'X-Google-Groups-Email' - ] - - for header in google_groups_headers: - if msg.get(header): - syslog('vette', 'Google Groups message detected via header %s, discarding', header) - # Send bounce to the message's errors-to address + headers = u'' + for h, v in list(msg.items()): + uvalue = u'' + try: + if isinstance(v, str): + v = decode_header(re.sub(r'\n\s', ' ', v)) + else: + continue + except HeaderParseError: + v = [(v, 'us-ascii')] + for frag, cs in v: + if not cs: + cs = 'us-ascii' try: - bounce_msg = Message() - bounce_msg['From'] = mlist.GetBounceEmail() - # Use the message's errors-to header if present, otherwise use the From address - bounce_to = msg.get('errors-to') or msg.get('from', 'unknown') - bounce_msg['To'] = bounce_to - bounce_msg['Subject'] = 'Message rejected: Google Groups not allowed' - bounce_msg['Message-ID'] = Utils.unique_message_id(mlist) - bounce_msg['Date'] = Utils.formatdate(localtime=True) - bounce_msg['X-Mailman-From'] = msg.get('from', 'unknown') - bounce_msg['X-Mailman-To'] = msg.get('to', 'unknown') - bounce_msg['X-Mailman-List'] = mlist.internal_name() - bounce_msg['X-Mailman-Reason'] = 'Google Groups messages are not allowed' - - # Include original message headers - bounce_text = 'Original message headers:\n' - for name, value in msg.items(): - bounce_text += f'{name}: {value}\n' - bounce_msg.set_payload(bounce_text) - - # Send the bounce - mlist.BounceMessage(bounce_msg, msgdata) - syslog('vette', 'Sent bounce to %s for rejected Google Groups message', bounce_to) - except Exception as e: - syslog('error', 'Failed to send bounce for Google Groups message: %s', str(e)) - - # Discard the original message - raise Errors.DiscardMessage - + if isinstance(frag, bytes): + uvalue += str(frag, cs, 'replace') + else: + uvalue += frag + except LookupError: + # The encoding charset is unknown. At this point, frag + # has been QP or base64 decoded into a byte string whose + # charset we don't know how to handle. We will try to + # unicode it as iso-8859-1 which may result in a garbled + # mess, but we have to do something. + uvalue += str(frag, 'iso-8859-1', 'replace') + headers += u'%s: %s\n' % (h, normalize(mm_cfg.NORMALIZE_FORM, uvalue)) + return headers + + + +def process(mlist, msg, msgdata): # Before anything else, check DMARC if necessary. We do this as early # as possible so reject/discard actions trump other holds/approvals and # wrap/munge actions get flagged even for approved messages. @@ -135,7 +107,7 @@ def process(mlist, msg, msgdata): # discard actions. if not msgdata.get('toowner'): msgdata['from_is_list'] = 0 - dn, addr = parseaddr(msg.get('from', '')) + dn, addr = parseaddr(msg.get('from')) if addr and mlist.dmarc_moderation_action > 0: if (mlist.GetPattern(addr, mlist.dmarc_moderation_addresses) or Utils.IsDMARCProhibited(mlist, addr)): @@ -162,9 +134,7 @@ def process(mlist, msg, msgdata): raise Errors.DiscardMessage # Get member address if any. - for sender_tuple in msg.get_senders(): - # Extract email address from the (realname, address) tuple - _, sender = sender_tuple + for sender in msg.get_senders(): if mlist.isMember(sender): break else: @@ -183,8 +153,6 @@ def process(mlist, msg, msgdata): for header, regex in mm_cfg.KNOWN_SPAMMERS: cre = re.compile(regex, re.IGNORECASE) for value in msg.get_all(header, []): - if isinstance(value, bytes): - value = value.decode('utf-8', 'replace') mo = cre.search(value) if mo: # we've detected spam, so throw the message away @@ -192,7 +160,7 @@ def process(mlist, msg, msgdata): # Now do header_filter_rules # TK: Collect headers in sub-parts because attachment filename # extension may be a clue to possible virus/spam. - headers = '' + headers = u'' # Get the character set of the lists preferred language for headers lcset = Utils.GetCharSet(mlist.preferred_language) for p in msg.walk(): diff --git a/Mailman/Handlers/Tagger.py b/Mailman/Handlers/Tagger.py index f2879906..e3681a0e 100644 --- a/Mailman/Handlers/Tagger.py +++ b/Mailman/Handlers/Tagger.py @@ -20,7 +20,7 @@ import re import email import email.errors -from email.iterators import body_line_iterator +import email.iterators import email.parser from email.header import decode_header @@ -35,6 +35,7 @@ NLTAB = '\n\t' + def process(mlist, msg, msgdata): if not mlist.topics_enabled: return @@ -75,6 +76,7 @@ def _decode(h): mlist, msg, msgdata, delete=False) + def scanbody(msg, numlines=None): # We only scan the body of the message if it is of MIME type text/plain, # or if the outer type is multipart/alternative and there is a text/plain @@ -95,7 +97,7 @@ def scanbody(msg, numlines=None): # the first numlines of body text. lines = [] lineno = 0 - reader = list(body_line_iterator(msg, decode=True)) + reader = list(email.iterators.body_line_iterator(msg, decode=True)) while numlines is None or lineno < numlines: try: line = reader.pop(0) @@ -113,6 +115,7 @@ def scanbody(msg, numlines=None): return msg.get_all('subject', []) + msg.get_all('keywords', []) + class _ForgivingParser(email.parser.HeaderParser): # Be a little more forgiving about non-header/continuation lines, since # we'll just read as much as we can from "header-like" lines in the body. diff --git a/Mailman/Handlers/ToArchive.py b/Mailman/Handlers/ToArchive.py index dab6b0a1..940c1ba7 100644 --- a/Mailman/Handlers/ToArchive.py +++ b/Mailman/Handlers/ToArchive.py @@ -26,15 +26,31 @@ def process(mlist, msg, msgdata): + # DEBUG: Log archiver processing start + from Mailman.Logging.Syslog import syslog + syslog('debug', 'ToArchive: Starting archive processing for list %s', mlist.internal_name()) + # short circuits - if msgdata.get('isdigest') or not mlist.archive: + if msgdata.get('isdigest'): + syslog('debug', 'ToArchive: Skipping digest message for list %s', mlist.internal_name()) return + if not mlist.archive: + syslog('debug', 'ToArchive: Archiving disabled for list %s', mlist.internal_name()) + return + # Common practice seems to favor "X-No-Archive: yes". No other value for # this header seems to make sense, so we'll just test for it's presence. # I'm keeping "X-Archive: no" for backwards compatibility. - if 'x-no-archive' in msg or msg.get('x-archive', '').lower() == 'no': + if 'x-no-archive' in msg: + syslog('debug', 'ToArchive: Skipping message with X-No-Archive header for list %s', mlist.internal_name()) + return + if msg.get('x-archive', '').lower() == 'no': + syslog('debug', 'ToArchive: Skipping message with X-Archive: no for list %s', mlist.internal_name()) return + # Send the message to the archiver queue archq = get_switchboard(mm_cfg.ARCHQUEUE_DIR) + syslog('debug', 'ToArchive: Enqueuing message to archive queue for list %s', mlist.internal_name()) # Send the message to the queue archq.enqueue(msg, msgdata) + syslog('debug', 'ToArchive: Successfully enqueued message to archive queue for list %s', mlist.internal_name()) diff --git a/Mailman/Handlers/ToDigest.py b/Mailman/Handlers/ToDigest.py index 60dd8792..8ae5fa17 100644 --- a/Mailman/Handlers/ToDigest.py +++ b/Mailman/Handlers/ToDigest.py @@ -16,8 +16,7 @@ # USA. """Add the message to the list's current digest and possibly send it.""" - -from __future__ import absolute_import, print_function, unicode_literals +from __future__ import print_function # Messages are accumulated to a Unix mailbox compatible file containing all # the messages destined for the digest. This file must be parsable by the @@ -27,15 +26,15 @@ # directory and the DigestRunner will craft the MIME, rfc1153, and # (eventually) URL-subject linked digests from the mbox. +from builtins import str import os import re import copy import time import traceback -from io import StringIO, BytesIO +from io import StringIO from email.parser import Parser -from email import message_from_string from email.generator import Generator from email.mime.base import MIMEBase from email.mime.text import MIMEText @@ -43,17 +42,12 @@ from email.utils import getaddresses, formatdate from email.header import decode_header, make_header, Header from email.charset import Charset -import email -import email.message -from email.message import Message -import errno -import pickle from Mailman import mm_cfg from Mailman import Utils +from Mailman import Message from Mailman import i18n from Mailman import Errors -from Mailman.Message import Message from Mailman.Mailbox import Mailbox from Mailman.MemberAdaptor import ENABLED from Mailman.Handlers.Decorate import decorate @@ -67,365 +61,382 @@ UEMPTYSTRING = u'' EMPTYSTRING = '' -def decode_header_value(value, lcset): - """Decode an email header value properly.""" - if not value: - return '' - try: - # Handle encoded-word format - decoded = [] - for part, charset in decode_header(value): - if isinstance(part, bytes): - try: - decoded.append(part.decode(charset or lcset, 'replace')) - except (UnicodeError, LookupError): - decoded.append(part.decode('utf-8', 'replace')) - else: - decoded.append(part) - return ''.join(decoded) - except Exception: - return str(value) - + def to_cset_out(text, lcset): - """Convert text to output charset. - - Handles both str and bytes input, ensuring proper encoding for output. - Returns a properly encoded string, not bytes. - """ - if text is None: - return '' - + # Convert text from unicode or lcset to output cset. ocset = Charset(lcset).get_output_charset() or lcset - if isinstance(text, str): - try: - return text - except (UnicodeError, LookupError): - return text.encode('utf-8', 'replace').decode('utf-8') - elif isinstance(text, bytes): - try: - return text.decode(lcset, 'replace') - except (UnicodeError, LookupError): - try: - return text.decode('utf-8', 'replace') - except (UnicodeError, LookupError): - return str(text) + return text.encode(ocset, 'replace') else: - return str(text) + return text.decode(lcset, 'replace').encode(ocset, 'replace') -def process_message_body(msg, lcset): - """Process a message body, handling MIME parts and encoding properly.""" - if msg.is_multipart(): - parts = [] - for part in msg.walk(): - if part.get_content_maintype() == 'multipart': - continue - try: - payload = part.get_payload(decode=True) - if isinstance(payload, bytes): - charset = part.get_content_charset(lcset) - try: - text = payload.decode(charset or lcset, 'replace') - except (UnicodeError, LookupError): - text = payload.decode('utf-8', 'replace') - else: - text = str(payload) - parts.append(text) - except Exception as e: - parts.append('[Part could not be decoded]') - return '\n\n'.join(parts) - else: - try: - payload = msg.get_payload(decode=True) - if isinstance(payload, bytes): - charset = msg.get_content_charset(lcset) - try: - return payload.decode(charset or lcset, 'replace') - except (UnicodeError, LookupError): - return payload.decode('utf-8', 'replace') - return str(payload) - except Exception: - return '[Message body could not be decoded]' + def process(mlist, msg, msgdata): - """Process a message for digest delivery. - - This function handles adding messages to the digest and sending the digest - when appropriate. All file operations use proper encoding handling. - """ - if msgdata.get('isdigest'): - return - # Convert email.message.Message to Mailman.Message.Message if needed - if isinstance(msg, email.message.Message): - newmsg = Message() - # Copy attributes - for k, v in msg.items(): - newmsg[k] = v - # Copy payload - if msg.is_multipart(): - for part in msg.get_payload(): - newmsg.attach(part) - else: - newmsg.set_payload(msg.get_payload()) - msg = newmsg - # Create digest message - mimemsg = Message() - rfc1153msg = Message() - - # Short circuit non-digestable lists - if not mlist.digestable: + # Short circuit non-digestable lists. + if not mlist.digestable or msgdata.get('isdigest'): return - mboxfile = os.path.join(mlist.fullpath(), 'digest.mbox') - lockfile = mboxfile + '.lock' - - # Create a lock file to prevent concurrent access + omask = os.umask(0o007) try: - with open(lockfile, 'x') as f: - f.write(str(os.getpid())) - except FileExistsError: - # Another process is updating the digest, log and return - syslog('info', 'Digest file locked by another process, deferring message %s for list %s', - msg.get('message-id', 'unknown'), mlist.internal_name()) - return - + with open(mboxfile, 'a+b') as mboxfp: + mbox = Mailbox(mboxfp.name) + mbox.AppendMessage(msg) + # Calculate the current size of the accumulation file. This will not tell + # us exactly how big the MIME, rfc1153, or any other generated digest + # message will be, but it's the most easily available metric to decide + # whether the size threshold has been reached. + mboxfp.flush() + size = os.path.getsize(mboxfile) + if (mlist.digest_size_threshhold > 0 and + size / 1024.0 >= mlist.digest_size_threshhold): + # This is a bit of a kludge to get the mbox file moved to the digest + # queue directory. + try: + # Enclose in try/except here because a error in send_digest() can + # silently stop regular delivery. Unsuccessful digest delivery + # should be tried again by cron and the site administrator will be + # notified of any error explicitly by the cron error message. + mboxfp.seek(0) + send_digests(mlist, mboxfp) + os.unlink(mboxfile) + except Exception as errmsg: + # Bare except is generally prohibited in Mailman, but we can't + # forecast what exceptions can occur here. + syslog('error', 'send_digests() failed: %s', errmsg) + s = StringIO() + traceback.print_exc(file=s) + syslog('error', s.getvalue()) + finally: + os.umask(omask) + + + +def send_digests(mlist, mboxfp): + # Set the digest volume and time + if mlist.digest_last_sent_at: + bump = False + # See if we should bump the digest volume number + timetup = time.localtime(mlist.digest_last_sent_at) + now = time.localtime(time.time()) + freq = mlist.digest_volume_frequency + if freq == 0 and timetup[0] < now[0]: + # Yearly + bump = True + elif freq == 1 and timetup[1] != now[1]: + # Monthly, but we take a cheap way to calculate this. We assume + # that the clock isn't going to be reset backwards. + bump = True + elif freq == 2 and (timetup[1] % 4 != now[1] % 4): + # Quarterly, same caveat + bump = True + elif freq == 3: + # Once again, take a cheap way of calculating this + weeknum_last = int(time.strftime('%W', timetup)) + weeknum_now = int(time.strftime('%W', now)) + if weeknum_now > weeknum_last or timetup[0] > now[0]: + bump = True + elif freq == 4 and timetup[7] != now[7]: + # Daily + bump = True + if bump: + mlist.bump_digest_volume() + mlist.digest_last_sent_at = time.time() + # Wrapper around actually digest crafter to set up the language context + # properly. All digests are translated to the list's preferred language. + otranslation = i18n.get_translation() + i18n.set_language(mlist.preferred_language) try: - omask = os.umask(0o007) - try: - # Open file in text mode with proper encoding - with open(mboxfile, 'a+', encoding='utf-8') as mboxfp: - # Convert message to string format - msg_str = str(msg) - mboxfp.write(msg_str + '\n') - - # Calculate size and check threshold - mboxfp.flush() - size = os.path.getsize(mboxfile) - syslog('info', 'Added message %s to digest for list %s (current size: %d KB)', - msg.get('message-id', 'unknown'), mlist.internal_name(), size / 1024) - - if (mlist.digest_size_threshhold > 0 and - size / 1024.0 >= mlist.digest_size_threshhold): - try: - syslog('info', 'Digest threshold reached for list %s, sending digest', - mlist.internal_name()) - send_digests(mlist, mboxfile) # Pass path instead of file object - except Exception as e: - syslog('error', 'Error sending digest for list %s: %s', - mlist.internal_name(), str(e)) - syslog('error', 'Traceback: %s', traceback.format_exc()) - finally: - os.umask(omask) + send_i18n_digests(mlist, mboxfp) finally: - # Clean up the lock file - try: - os.unlink(lockfile) - except OSError: - pass + i18n.set_translation(otranslation) -def send_digests(mlist, mboxpath): - """Send digests for the mailing list with performance optimizations.""" - # Set up the digest state - volume = mlist.volume - issue = mlist.next_digest_number - digestid = _('%(realname)s Digest, Vol %(volume)d, Issue %(issue)d') % { - 'realname': mlist.real_name, - 'volume': volume, - 'issue': issue - } - - # Get the list's preferred language and charset + + +def send_i18n_digests(mlist, mboxfp): + mbox = Mailbox(mboxfp) + # Prepare common information (first lang/charset) lang = mlist.preferred_language lcset = Utils.GetCharSet(lang) lcset_out = Charset(lcset).output_charset or lcset - - # Create the digest messages - mimemsg = Message() + # Common Information (contd) + realname = mlist.real_name + volume = mlist.volume + issue = mlist.next_digest_number + digestid = _('%(realname)s Digest, Vol %(volume)d, Issue %(issue)d') + digestsubj = Header(digestid, lcset, header_name='Subject') + # Set things up for the MIME digest. Only headers not added by + # CookHeaders need be added here. + # Date/Message-ID should be added here also. + mimemsg = Message.Message() mimemsg['Content-Type'] = 'multipart/mixed' mimemsg['MIME-Version'] = '1.0' mimemsg['From'] = mlist.GetRequestEmail() - mimemsg['Subject'] = Header(digestid, lcset, header_name='Subject') + mimemsg['Subject'] = digestsubj mimemsg['To'] = mlist.GetListEmail() mimemsg['Reply-To'] = mlist.GetListEmail() mimemsg['Date'] = formatdate(localtime=1) mimemsg['Message-ID'] = Utils.unique_message_id(mlist) - - # Set up the RFC 1153 digest - plainmsg = StringIO() # Use StringIO for text output - rfc1153msg = Message() + # Set things up for the rfc1153 digest + plainmsg = StringIO() + rfc1153msg = Message.Message() rfc1153msg['From'] = mlist.GetRequestEmail() - rfc1153msg['Subject'] = Header(digestid, lcset, header_name='Subject') + rfc1153msg['Subject'] = digestsubj rfc1153msg['To'] = mlist.GetListEmail() rfc1153msg['Reply-To'] = mlist.GetListEmail() rfc1153msg['Date'] = formatdate(localtime=1) rfc1153msg['Message-ID'] = Utils.unique_message_id(mlist) - - # Create the digest content separator70 = '-' * 70 separator30 = '-' * 30 - - # Add masthead + # In the rfc1153 digest, the masthead contains the digest boilerplate plus + # any digest header. In the MIME digests, the masthead and digest header + # are separate MIME subobjects. In either case, it's the first thing in + # the digest, and we can calculate it now, so go ahead and add it now. mastheadtxt = Utils.maketext( 'masthead.txt', - {'real_name': mlist.real_name, - 'got_list_email': mlist.GetListEmail(), - 'got_listinfo_url': mlist.GetScriptURL('listinfo', absolute=1), + {'real_name' : mlist.real_name, + 'got_list_email': mlist.GetListEmail(), + 'got_listinfo_url': mlist.GetScriptURL('listinfo', absolute=1), 'got_request_email': mlist.GetRequestEmail(), - 'got_owner_email': mlist.GetOwnerEmail(), - }, - lang=lang, - mlist=mlist) - - # Add masthead to both digest formats - mimemsg.attach(MIMEText(mastheadtxt, _charset=lcset)) - plainmsg.write(to_cset_out(mastheadtxt, lcset_out)) - plainmsg.write('\n') - - # Process the mbox file - try: - with open(mboxpath, 'r', encoding='utf-8') as mboxfp: - msg_num = 1 - current_msg = [] - for line in mboxfp: - if line.startswith('From '): - if current_msg: - # Process the previous message - msg_str = ''.join(current_msg) - try: - msg = message_from_string(msg_str) - if msg is None: - continue - - subject = decode_header_value(msg.get('subject', _('(no subject)')), lcset) - subject = Utils.oneline(subject, lcset) - - # Add to table of contents - plainmsg.write('%2d. %s\n' % (msg_num, to_cset_out(subject, lcset_out))) - - # Add the message to both digest formats - mimemsg.attach(MIMEMessage(msg)) - - # Add message header - plainmsg.write('\n') - plainmsg.write(to_cset_out(separator30, lcset_out)) - plainmsg.write('\n') - plainmsg.write(to_cset_out(_('Message %d\n' % msg_num), lcset_out)) - plainmsg.write(to_cset_out(separator30, lcset_out)) - plainmsg.write('\n') - - # Add message metadata - for header in ('date', 'from', 'subject'): - value = decode_header_value(msg.get(header, ''), lcset) - plainmsg.write('%s: %s\n' % (header.capitalize(), to_cset_out(value, lcset_out))) - plainmsg.write('\n') - - # Add message body - try: - body = process_message_body(msg, lcset) - plainmsg.write(to_cset_out(body, lcset_out)) - plainmsg.write('\n') - except Exception as e: - plainmsg.write(to_cset_out(_('[Message body could not be decoded]\n'), lcset_out)) - syslog('error', 'Message %d digest payload error: %s', msg_num, str(e)) - - msg_num += 1 - except Exception as e: - syslog('error', 'Digest message %d processing error: %s', msg_num, str(e)) - syslog('error', 'Traceback: %s', traceback.format_exc()) - current_msg = [line] - else: - current_msg.append(line) - - # Process the last message - if current_msg: - msg_str = ''.join(current_msg) - try: - msg = message_from_string(msg_str) - if msg is not None: - # Process the last message (same code as above) - subject = decode_header_value(msg.get('subject', _('(no subject)')), lcset) - subject = Utils.oneline(subject, lcset) - plainmsg.write('%2d. %s\n' % (msg_num, to_cset_out(subject, lcset_out))) - mimemsg.attach(MIMEMessage(msg)) - plainmsg.write('\n') - plainmsg.write(to_cset_out(separator30, lcset_out)) - plainmsg.write('\n') - plainmsg.write(to_cset_out(_('Message %d\n' % msg_num), lcset_out)) - plainmsg.write(to_cset_out(separator30, lcset_out)) - plainmsg.write('\n') - for header in ('date', 'from', 'subject'): - value = decode_header_value(msg.get(header, ''), lcset) - plainmsg.write('%s: %s\n' % (header.capitalize(), to_cset_out(value, lcset_out))) - plainmsg.write('\n') - try: - body = process_message_body(msg, lcset) - plainmsg.write(to_cset_out(body, lcset_out)) - plainmsg.write('\n') - except Exception as e: - plainmsg.write(to_cset_out(_('[Message body could not be decoded]\n'), lcset_out)) - syslog('error', 'Message %d digest payload error: %s', msg_num, str(e)) - except Exception as e: - syslog('error', 'Digest message %d processing error: %s', msg_num, str(e)) - syslog('error', 'Traceback: %s', traceback.format_exc()) - except Exception as e: - syslog('error', 'Error reading digest mbox file: %s', str(e)) - syslog('error', 'Traceback: %s', traceback.format_exc()) + 'got_owner_email': mlist.GetOwnerEmail(), + }, mlist=mlist) + # MIME + masthead = MIMEText(mastheadtxt, _charset=lcset) + masthead['Content-Description'] = digestid + mimemsg.attach(masthead) + # RFC 1153 + print(mastheadtxt, file=plainmsg) + print(file=plainmsg) + # Now add the optional digest header but only if more than whitespace. + if re.sub(r'\s', '', mlist.digest_header): + lc_digest_header_msg = _('digest header') + if isinstance(lc_digest_header_msg, bytes): + lc_digest_header_msg = str(lc_digest_header_msg) + headertxt = decorate(mlist, mlist.digest_header, lc_digest_header_msg) + # MIME + header = MIMEText(headertxt, _charset=lcset) + header['Content-Description'] = _('Digest Header') + mimemsg.attach(header) + # RFC 1153 + print(headertxt, file=plainmsg) + print(file=plainmsg) + # Now we have to cruise through all the messages accumulated in the + # mailbox file. We can't add these messages to the plainmsg and mimemsg + # yet, because we first have to calculate the table of contents + # (i.e. grok out all the Subjects). Store the messages in a list until + # we're ready for them. + # + # Meanwhile prepare things for the table of contents + toc = StringIO() + start_toc = _("Today's Topics:\n") + if isinstance(start_toc, bytes): + start_toc = str(start_toc) + print(start_toc, file=toc) + # Now cruise through all the messages in the mailbox of digest messages, + # building the MIME payload and core of the RFC 1153 digest. We'll also + # accumulate Subject: headers and authors for the table-of-contents. + messages = [] + msgcount = 0 + mbox = mbox.itervalues() + msg = next(mbox, None) + while msg is not None: + if msg == '': + # It was an unparseable message + msg = next(mbox, None) + continue + msgcount += 1 + messages.append(msg) + # Get the Subject header + no_subject_locale = _('(no subject)') + if isinstance(no_subject_locale, bytes): + no_subject_locale = str(no_subject_locale) + msgsubj = msg.get('subject', no_subject_locale) + subject = Utils.oneline(msgsubj, lcset) + # Don't include the redundant subject prefix in the toc + mo = re.match('(re:? *)?(%s)' % re.escape(mlist.subject_prefix), + subject, re.IGNORECASE) + if mo: + subject = subject[:mo.start(2)] + subject[mo.end(2):] + username = '' + addresses = getaddresses([Utils.oneline(msg.get('from', ''), lcset)]) + # Take only the first author we find + if isinstance(addresses, list) and addresses: + username = addresses[0][0] + if not username: + username = addresses[0][1] + if username: + username = ' (%s)' % username + # Put count and Wrap the toc subject line + if isinstance(subject, bytes): + subject = str(subject) + wrapped = Utils.wrap('%2d. %s' % (msgcount, subject), 65) + slines = wrapped.split('\n') + # See if the user's name can fit on the last line + if len(slines[-1]) + len(username) > 70: + slines.append(username) + else: + slines[-1] += username + # Add this subject to the accumulating topics + first = True + for line in slines: + if first: + print(' ', line, file=toc) + first = False + else: + print(' ', line.lstrip(), file=toc) + # We do not want all the headers of the original message to leak + # through in the digest messages. For this phase, we'll leave the + # same set of headers in both digests, i.e. those required in RFC 1153 + # plus a couple of other useful ones. We also need to reorder the + # headers according to RFC 1153. Later, we'll strip out headers for + # for the specific MIME or plain digests. + keeper = {} + all_keepers = {} + for header in (mm_cfg.MIME_DIGEST_KEEP_HEADERS + + mm_cfg.PLAIN_DIGEST_KEEP_HEADERS): + all_keepers[header] = True + all_keepers = list(all_keepers.keys()) + for keep in all_keepers: + keeper[keep] = msg.get_all(keep, []) + # Now remove all unkempt headers :) + for header in list(msg.keys()): + del msg[header] + # And add back the kept header in the RFC 1153 designated order + for keep in all_keepers: + for field in keeper[keep]: + msg[keep] = field + # And a bit of extra stuff + msg['Message'] = repr(msgcount) + # Get the next message in the digest mailbox + msg = next(mbox, None) + # Now we're finished with all the messages in the digest. First do some + # sanity checking and then on to adding the toc. + if msgcount == 0: + # Why did we even get here? return - - # Finish up the RFC 1153 digest - plainmsg.write('\n') - plainmsg.write(to_cset_out(separator70, lcset_out)) - plainmsg.write('\n') - plainmsg.write(to_cset_out(_('End of Digest\n'), lcset_out)) - - # Set the RFC 1153 message body - rfc1153msg.set_payload(plainmsg.getvalue(), charset=lcset) - plainmsg.close() - - # Send both digests - send_digest_final(mlist, mimemsg, rfc1153msg, volume, issue) - - # Clean up - mlist.next_digest_number += 1 - mlist.Save() - - # Remove the mbox file - try: - os.unlink(mboxpath) - except OSError as e: - syslog('error', 'Failed to remove digest.mbox: %s', str(e)) + toctext = toc.getvalue() + toctext_encoded = to_cset_out(toctext, lcset) + # MIME + tocpart = MIMEText(toctext, _charset=lcset) + tocpart['Content-Description']= _("Today's Topics (%(msgcount)d messages)") + mimemsg.attach(tocpart) + # RFC 1153 + print(toctext, file=plainmsg) + print(file=plainmsg) + # For RFC 1153 digests, we now need the standard separator + print(separator70, file=plainmsg) + print(file=plainmsg) + # Now go through and add each message + mimedigest = MIMEBase('multipart', 'digest') + mimemsg.attach(mimedigest) + first = True + for msg in messages: + # MIME. Make a copy of the message object since the rfc1153 + # processing scrubs out attachments. + mimedigest.attach(MIMEMessage(copy.deepcopy(msg))) + # rfc1153 + if first: + first = False + else: + print(separator30, file=plainmsg) + print(file=plainmsg) + # Use Mailman.Handlers.Scrubber.process() to get plain text + try: + msg = scrubber(mlist, msg) + except Errors.DiscardMessage: + discard_msg = _('[Message discarded by content filter]') + if isinstance(discard_msg, bytes): + discard_msg = str(discard_msg) + print(discard_msg, file=plainmsg) + continue + # Honor the default setting + for h in mm_cfg.PLAIN_DIGEST_KEEP_HEADERS: + if msg[h]: + uh = Utils.wrap('%s: %s' % (h, Utils.oneline(msg[h], lcset))) + uh = '\n\t'.join(uh.split('\n')) + print(uh, file=plainmsg) + print(file=plainmsg) + # If decoded payload is empty, this may be multipart message. + # -- just stringfy it. + payload = msg.get_payload(decode=True) + if payload == None: + payload = msg.as_string().split('\n\n',1)[1] + mcset = msg.get_content_charset('') + if mcset == None or mcset == "": + mcset = 'utf-8' + if isinstance(payload, bytes): + payload = payload.decode(mcset, 'replace') + print(payload, file=plainmsg) + if not payload.endswith('\n'): + print(file=plainmsg) + + # Now add the footer but only if more than whitespace. + if re.sub(r'\s', '', mlist.digest_footer): + lc_digest_footer_msg = _('digest footer') + if isinstance(lc_digest_footer_msg, bytes): + lc_digest_footer_msg = str(lc_digest_footer_msg) + footertxt = decorate(mlist, mlist.digest_footer, lc_digest_footer_msg) + # MIME + footer = MIMEText(footertxt, _charset=lcset) + footer['Content-Description'] = _('Digest Footer') + mimemsg.attach(footer) + # RFC 1153 + # MAS: There is no real place for the digest_footer in an RFC 1153 + # compliant digest, so add it as an additional message with + # Subject: Digest Footer + print(separator30, file=plainmsg) + print(file=plainmsg) -def send_digest_final(mlist, mimemsg, rfc1153msg, volume, issue): - """Send the actual digest messages with performance optimizations.""" - # Get digest recipients in batches - batch_size = 1000 # Process 1000 recipients at a time - - # Send to MIME digest members - mime_members = mlist.getDigestMemberKeys() - if mime_members: - mime_members = mlist.getMemberCPAddresses(mime_members) - outq = get_switchboard(mm_cfg.OUTQUEUE_DIR) - # Process in batches to avoid memory issues - for i in range(0, len(mime_members), batch_size): - batch = mime_members[i:i + batch_size] - syslog('info', 'Sending MIME digest batch %d-%d for list %s', - i, i + len(batch), mlist.internal_name()) - outq.enqueue(mimemsg, - recips=batch, - listname=mlist.internal_name(), - fromnode='digest') - - # Send to RFC 1153 digest members - rfc1153_members = mlist.getDigestMemberKeys() - if rfc1153_members: - rfc1153_members = mlist.getMemberCPAddresses(rfc1153_members) - outq = get_switchboard(mm_cfg.OUTQUEUE_DIR) - # Process in batches to avoid memory issues - for i in range(0, len(rfc1153_members), batch_size): - batch = rfc1153_members[i:i + batch_size] - syslog('info', 'Sending RFC 1153 digest batch %d-%d for list %s', - i, i + len(batch), mlist.internal_name()) - outq.enqueue(rfc1153msg, - recips=batch, - listname=mlist.internal_name(), - fromnode='digest') + digest_footer_msg = _('Digest Footer') + if isinstance(digest_footer_msg, bytes): + digest_footer_msg = str(digest_footer_msg) + print('Subject: ' + digest_footer_msg, file=plainmsg) + print(file=plainmsg) + print(footertxt, file=plainmsg) + print(file=plainmsg) + print(separator30, file=plainmsg) + print(file=plainmsg) + # Do the last bit of stuff for each digest type + signoff = _('End of ') + digestid + # MIME + # BAW: This stuff is outside the normal MIME goo, and it's what the old + # MIME digester did. No one seemed to complain, probably because you + # won't see it in an MUA that can't display the raw message. We've never + # got complaints before, but if we do, just wax this. It's primarily + # included for (marginally useful) backwards compatibility. + mimemsg.postamble = signoff + # rfc1153 + print(signoff, file=plainmsg) + print('*' * len(signoff), file=plainmsg) + # Do our final bit of housekeeping, and then send each message to the + # outgoing queue for delivery. + mlist.next_digest_number += 1 + virginq = get_switchboard(mm_cfg.VIRGINQUEUE_DIR) + # Calculate the recipients lists + plainrecips = [] + mimerecips = [] + drecips = mlist.getDigestMemberKeys() + list(mlist.one_last_digest.keys()) + for user in mlist.getMemberCPAddresses(drecips): + # user might be None if someone who toggled off digest delivery + # subsequently unsubscribed from the mailing list. Also, filter out + # folks who have disabled delivery. + if user is None or mlist.getDeliveryStatus(user) != ENABLED: + continue + # Otherwise, decide whether they get MIME or RFC 1153 digests + if mlist.getMemberOption(user, mm_cfg.DisableMime): + plainrecips.append(user) + else: + mimerecips.append(user) + # Zap this since we're now delivering the last digest to these folks. + mlist.one_last_digest.clear() + # MIME + virginq.enqueue(mimemsg, + recips=mimerecips, + listname=mlist.internal_name(), + isdigest=True) + # RFC 1153 + rfc1153msg.set_payload(plainmsg.getvalue(), 'utf-8') + virginq.enqueue(rfc1153msg, + recips=plainrecips, + listname=mlist.internal_name(), + isdigest=True) diff --git a/Mailman/Handlers/ToOutgoing.py b/Mailman/Handlers/ToOutgoing.py index 123b8859..d4f13fd5 100644 --- a/Mailman/Handlers/ToOutgoing.py +++ b/Mailman/Handlers/ToOutgoing.py @@ -23,67 +23,33 @@ from Mailman import mm_cfg from Mailman.Queue.sbcache import get_switchboard -import traceback -from Mailman.Logging.Syslog import mailman_log + + def process(mlist, msg, msgdata): - """Process the message by moving it to the outgoing queue.""" - msgid = msg.get('message-id', 'n/a') - - # Log the start of processing with enhanced details - mailman_log('debug', 'ToOutgoing: Starting to process message %s for list %s', - msgid, mlist.internal_name()) - mailman_log('debug', 'ToOutgoing: Message details:') - mailman_log('debug', ' Message ID: %s', msgid) - mailman_log('debug', ' From: %s', msg.get('from', 'unknown')) - mailman_log('debug', ' To: %s', msg.get('to', 'unknown')) - mailman_log('debug', ' Subject: %s', msg.get('subject', '(no subject)')) - mailman_log('debug', ' Message type: %s', type(msg).__name__) - mailman_log('debug', ' Message data: %s', str(msgdata)) - mailman_log('debug', ' Pipeline: %s', msgdata.get('pipeline', 'No pipeline')) - - # Get the outgoing queue - try: - mailman_log('debug', 'ToOutgoing: Getting outgoing queue for message %s', msgid) - outgoingq = get_switchboard(mm_cfg.OUTQUEUE_DIR) - mailman_log('debug', 'ToOutgoing: Successfully got outgoing queue for message %s', msgid) - except Exception as e: - mailman_log('error', 'ToOutgoing: Failed to get outgoing queue for message %s: %s', msgid, str(e)) - mailman_log('error', 'ToOutgoing: Traceback:\n%s', traceback.format_exc()) - raise - - # Get recipients from msgdata first, then fall back to message headers - recips = msgdata.get('recips', []) - if not recips: - # Try to get from message headers - recips = msg.get_all('to', []) + msg.get_all('cc', []) - if not recips: - # If still no recipients, get from list membership - recips = [mlist.GetMemberEmail() for member in mlist.GetMemberCPAddresses()] - mailman_log('debug', 'ToOutgoing: No recipients found in msgdata or headers, using list members for message %s', msgid) - - # Ensure we have at least one recipient - if not recips: - mailman_log('error', 'ToOutgoing: No recipients found for message %s', msgid) - raise ValueError('No recipients found for message') - - # Add the message to the outgoing queue - try: - mailman_log('debug', 'ToOutgoing: Attempting to enqueue message %s for list %s', - msgid, mlist.internal_name()) - # Ensure recipients are preserved in msgdata - msgdata['recips'] = recips - msgdata['recipient'] = recips[0] if recips else None - - # Log the full msgdata before enqueueing - mailman_log('debug', 'ToOutgoing: Full msgdata before enqueue:\n%s', str(msgdata)) - - outgoingq.enqueue(msg, msgdata, - listname=mlist.internal_name()) - mailman_log('debug', 'ToOutgoing: Successfully queued message %s for list %s', - msgid, mlist.internal_name()) - mailman_log('debug', 'ToOutgoing: Message %s is now in outgoing queue', msgid) - except Exception as e: - mailman_log('error', 'ToOutgoing: Failed to enqueue message %s: %s', msgid, str(e)) - mailman_log('error', 'ToOutgoing: Traceback:\n%s', traceback.format_exc()) - raise + interval = mm_cfg.VERP_DELIVERY_INTERVAL + # Should we VERP this message? If personalization is enabled for this + # list and VERP_PERSONALIZED_DELIVERIES is true, then yes we VERP it. + # Also, if personalization is /not/ enabled, but VERP_DELIVERY_INTERVAL is + # set (and we've hit this interval), then again, this message should be + # VERPed. Otherwise, no. + # + # Note that the verp flag may already be set, e.g. by mailpasswds using + # VERP_PASSWORD_REMINDERS. Preserve any existing verp flag. + if 'verp' in msgdata: + pass + elif mlist.personalize: + if mm_cfg.VERP_PERSONALIZED_DELIVERIES: + msgdata['verp'] = 1 + elif interval == 0: + # Never VERP + pass + elif interval == 1: + # VERP every time + msgdata['verp'] = 1 + else: + # VERP every `inteval' number of times + msgdata['verp'] = not int(mlist.post_id) % interval + # And now drop the message in qfiles/out + outq = get_switchboard(mm_cfg.OUTQUEUE_DIR) + outq.enqueue(msg, msgdata, listname=mlist.internal_name()) diff --git a/Mailman/Handlers/ToUsenet.py b/Mailman/Handlers/ToUsenet.py index 32aed559..26b5ecfa 100644 --- a/Mailman/Handlers/ToUsenet.py +++ b/Mailman/Handlers/ToUsenet.py @@ -22,6 +22,7 @@ COMMASPACE = ', ' + def process(mlist, msg, msgdata): # short circuits if not mlist.gateway_to_news or \ @@ -40,6 +41,4 @@ def process(mlist, msg, msgdata): return # Put the message in the news runner's queue newsq = get_switchboard(mm_cfg.NEWSQUEUE_DIR) - newsq.enqueue(msg, msgdata, - listname=mlist.internal_name(), - recipient=mlist.nntp_host) # Set NNTP host as recipient + newsq.enqueue(msg, msgdata, listname=mlist.internal_name()) diff --git a/Mailman/Handlers/__init__.py b/Mailman/Handlers/__init__.py index 19d54e8b..b271f895 100644 --- a/Mailman/Handlers/__init__.py +++ b/Mailman/Handlers/__init__.py @@ -13,30 +13,3 @@ # 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. - -"""Mailman message handlers. - -This package contains the message handlers for Mailman's pipeline architecture. -Each handler module must define a process() function which takes three arguments: - mlist - The MailList instance - msg - The Message instance - msgdata - A dictionary of message metadata -""" - -from __future__ import absolute_import, print_function, unicode_literals - -# Define lazy imports to avoid circular dependencies -def get_handler(name): - """Get a handler module by name.""" - return __import__('Mailman.Handlers.' + name, fromlist=['Mailman.Handlers']) - -# Define handler names for reference -HANDLER_NAMES = [ - 'SpamDetect', 'Approve', 'Replybot', 'Moderate', 'Hold', 'MimeDel', 'Scrubber', - 'Emergency', 'Tagger', 'CalcRecips', 'AvoidDuplicates', 'Cleanse', 'CleanseDKIM', - 'CookHeaders', 'ToDigest', 'ToArchive', 'ToUsenet', 'AfterDelivery', 'Acknowledge', - 'WrapMessage', 'ToOutgoing', 'OwnerRecips' -] - -# Export handler names -__all__ = HANDLER_NAMES + ['get_handler'] diff --git a/Mailman/ListAdmin.py b/Mailman/ListAdmin.py index cda53d82..f8fad6d2 100644 --- a/Mailman/ListAdmin.py +++ b/Mailman/ListAdmin.py @@ -23,39 +23,38 @@ elsewhere. """ -from builtins import str, object +from builtins import str +from builtins import object import os import time import errno import pickle import marshal from io import StringIO -import socket -import pwd -import grp -import traceback import email from email.mime.message import MIMEMessage +from email.generator import BytesGenerator from email.generator import Generator from email.utils import getaddresses -import email.message -from email.message import Message as EmailMessage +from email.message import EmailMessage +from email.parser import Parser +from email import policy from Mailman import mm_cfg from Mailman import Utils -import Mailman.Message as Message +from Mailman import Message from Mailman import Errors from Mailman.UserDesc import UserDesc from Mailman.Queue.sbcache import get_switchboard -from Mailman.Logging.Syslog import mailman_log +from Mailman.Logging.Syslog import syslog from Mailman import i18n _ = i18n._ def D_(s): return s -# Constants for request types +# Request types requiring admin approval IGN = 0 HELDMSG = 1 SUBSCRIPTION = 2 @@ -69,12 +68,7 @@ def D_(s): DASH = '-' NL = '\n' -class PermissionError(Exception): - """Exception raised when there are permission issues with database operations.""" - def __init__(self, message): - self.message = message - super().__init__(message) - + class ListAdmin(object): def InitVars(self): # non-configurable data @@ -85,173 +79,77 @@ def InitTempVars(self): self.__filename = os.path.join(self.fullpath(), 'request.pck') def __opendb(self): - """Open the database file.""" - filename = os.path.join(self.fullpath(), 'request.pck') - filename_backup = filename + '.bak' - - # Try loading the main file first - try: - with open(filename, 'rb') as fp: + if self.__db is None: + assert self.Locked() + try: + fp = open(self.__filename, 'rb') try: - # Try UTF-8 first for newer files - self.__db = pickle.load(fp, fix_imports=True, encoding='utf-8') - except (UnicodeDecodeError, pickle.UnpicklingError): - # Fall back to latin1 for older files - fp.seek(0) - self.__db = pickle.load(fp, fix_imports=True, encoding='latin1') - except (pickle.UnpicklingError, EOFError, ValueError, TypeError) as e: - mailman_log('error', 'Error loading request.pck for list %s: %s\n%s', - self.internal_name(), str(e), traceback.format_exc()) - # Try backup if main file failed - if os.path.exists(filename_backup): - mailman_log('info', 'Attempting to load from backup file') - with open(filename_backup, 'rb') as backup_fp: - try: - # Try UTF-8 first for newer files - self.__db = pickle.load(backup_fp, fix_imports=True, encoding='utf-8') - except (UnicodeDecodeError, pickle.UnpicklingError): - # Fall back to latin1 for older files - backup_fp.seek(0) - self.__db = pickle.load(backup_fp, fix_imports=True, encoding='latin1') - mailman_log('info', 'Successfully loaded backup request.pck for list %s', - self.internal_name()) - # Successfully loaded backup, restore it as main - import shutil - shutil.copy2(filename_backup, filename) - else: + self.__db = Utils.load_pickle(fp) + if not self.__db: + raise IOError("Pickled data is empty or None") + finally: + fp.close() + except IOError as e: + if e.errno != errno.ENOENT: raise self.__db = {} + # put version number in new database + self.__db['version'] = IGN, mm_cfg.REQUESTS_FILE_SCHEMA_VERSION - def __savedb(self): - """Save the database file.""" - if not self.__db: - return - - filename = os.path.join(self.fullpath(), 'request.pck') - filename_tmp = filename + '.tmp.%s.%d' % (socket.gethostname(), os.getpid()) - filename_backup = filename + '.bak' - - # First create a backup of the current file if it exists - if os.path.exists(filename): + def __closedb(self): + if self.__db is not None: + assert self.Locked() + # Save the version number + self.__db['version'] = IGN, mm_cfg.REQUESTS_FILE_SCHEMA_VERSION + # Now save a temp file and do the tmpfile->real file dance. BAW: + # should we be as paranoid as for the config.pck file? Should we + # use pickle? + tmpfile = self.__filename + '.tmp' + omask = os.umask(0o007) try: - import shutil - shutil.copy2(filename, filename_backup) - except IOError as e: - mailman_log('error', 'Error creating backup: %s', str(e)) - - # Save to temporary file first - try: - # Ensure directory exists - dirname = os.path.dirname(filename) - if not os.path.exists(dirname): - os.makedirs(dirname, 0o755) - - with open(filename_tmp, 'wb') as fp: - # Use protocol 4 for Python 2/3 compatibility - pickle.dump(self.__db, fp, protocol=4, fix_imports=True) - fp.flush() - if hasattr(os, 'fsync'): + fp = open(tmpfile, 'wb') + try: + pickle.dump(self.__db, fp, 1) + fp.flush() os.fsync(fp.fileno()) - - # Atomic rename - os.rename(filename_tmp, filename) - - except (IOError, OSError) as e: - mailman_log('error', 'Error saving request.pck: %s', str(e)) - # Try to clean up - try: - os.unlink(filename_tmp) - except OSError: - pass - raise - - def __validate_and_clean_db(self): - """Validate database entries and clean up invalid ones.""" - if not self.__db: - return - - now = time.time() - to_delete = [] - - for key, value in self.__db.items(): - try: - # Check if value is a valid tuple/list with at least 2 elements - if not isinstance(value, (tuple, list)) or len(value) < 2: - to_delete.append(key) - continue - - # Check if timestamp is valid - timestamp = value[1] - if not isinstance(timestamp, (int, float)) or timestamp < 0: - to_delete.append(key) - continue - - # Remove expired entries - if timestamp < now: - to_delete.append(key) - continue - - except (TypeError, IndexError): - to_delete.append(key) - - # Remove invalid entries - for key in to_delete: - del self.__db[key] + finally: + fp.close() + finally: + os.umask(omask) + self.__db = None + # Do the dance + os.rename(tmpfile, self.__filename) + + def __nextid(self): + assert self.Locked() + while True: + next = self.next_request_id + self.next_request_id += 1 + if next not in self.__db: + break + return next def SaveRequestsDb(self): - """Save the requests database with validation.""" - if self.__db is not None: - self.__validate_and_clean_db() - self.__savedb() + self.__closedb() def NumRequestsPending(self): self.__opendb() - if not self.__db: - return 0 - # For Python 2 pickles, the version pseudo-entry might not exist - # Just return the length of the dictionary - return len(self.__db) + # Subtract one for the version pseudo-entry + return len(self.__db) - 1 def __getmsgids(self, rtype): self.__opendb() ids = [k for k, (op, data) in list(self.__db.items()) if op == rtype] - ids.sort() + ids.sort(key=int) return ids def GetHeldMessageIds(self): - try: - self.__opendb() - ids = [k for k, (op, data) in list(self.__db.items()) if op == HELDMSG] - ids.sort() - return ids - except Exception as e: - mailman_log('error', 'Error getting held message IDs: %s\n%s', - str(e), traceback.format_exc()) - # Return empty list on error to prevent cascading failures - return [] + return self.__getmsgids(HELDMSG) def GetSubscriptionIds(self): - try: - self.__opendb() - ids = [k for k, (op, data) in list(self.__db.items()) if op == SUBSCRIPTION] - ids.sort() - return ids - except Exception as e: - mailman_log('error', 'Error getting subscription IDs: %s\n%s', - str(e), traceback.format_exc()) - # Return empty list on error to prevent cascading failures - return [] + return self.__getmsgids(SUBSCRIPTION) def GetUnsubscriptionIds(self): - try: - self.__opendb() - ids = [k for k, (op, data) in list(self.__db.items()) if op == UNSUBSCRIPTION] - ids.sort() - return ids - except Exception as e: - mailman_log('error', 'Error getting unsubscription IDs: %s\n%s', - str(e), traceback.format_exc()) - # Return empty list on error to prevent cascading failures - return [] + return self.__getmsgids(UNSUBSCRIPTION) def GetRecord(self, id): self.__opendb() @@ -273,8 +171,7 @@ def HandleRequest(self, id, value, comment=None, preserve=None, elif rtype == UNSUBSCRIPTION: status = self.__handleunsubscription(data, value, comment) else: - if rtype != SUBSCRIPTION: - raise ValueError(f'Invalid request type: {rtype}, expected {SUBSCRIPTION}') + assert rtype == SUBSCRIPTION status = self.__handlesubscription(data, value, comment) if status != DEFER: # BAW: Held message ids are linked to Pending cookies, allowing @@ -305,7 +202,7 @@ def HoldMessage(self, msg, reason, msgdata={}): fp = open(os.path.join(mm_cfg.DATA_DIR, filename), 'wb') try: if mm_cfg.HOLD_MESSAGES_AS_PICKLES: - pickle.dump(msg, fp, protocol=4, fix_imports=True) + pickle.dump(msg, fp, 1) else: g = Generator(fp) g.flatten(msg, 1) @@ -329,7 +226,7 @@ def HoldMessage(self, msg, reason, msgdata={}): msgsubject = msg.get('subject', _('(no subject)')) if not sender: sender = _('') - data = (time.time(), sender, msgsubject, reason, filename, msgdata) + data = time.time(), sender, msgsubject, reason, filename, msgdata self.__db[id] = (HELDMSG, data) return id @@ -350,26 +247,37 @@ def __handlepost(self, record, value, comment, preserve, forward, addr): return LOST try: if path.endswith('.pck'): - msg = pickle.load(fp, fix_imports=True, encoding='latin1') + msg = Utils.load_pickle(path) else: - if not path.endswith('.txt'): - raise ValueError(f'Invalid file extension: {path} must end with .txt') + assert path.endswith('.txt'), '%s not .pck or .txt' % path msg = fp.read() finally: fp.close() + + # If msg is still a Message from Python 2 pickle, convert it + if isinstance(msg, email.message.Message): + if not hasattr(msg, 'policy'): + msg.policy = email._policybase.compat32 + if not hasattr(msg, 'mangle_from_'): + msg.mangle_from_ = True + if not hasattr(msg, 'linesep'): + msg.linesep = email.policy.default.linesep + # Save the plain text to a .msg file, not a .pck file outpath = os.path.join(mm_cfg.SPAM_DIR, spamfile) head, ext = os.path.splitext(outpath) outpath = head + '.msg' - outfp = open(outpath, 'wb') - try: - if path.endswith('.pck'): - g = Generator(outfp) - g.flatten(msg, 1) - else: - outfp.write(msg) - finally: - outfp.close() + + with open(outpath, 'w', encoding='utf-8') as outfp: + try: + if path.endswith('.pck'): + g = Generator(outfp, policy=msg.policy) + g.flatten(msg, 1) + else: + outfp.write(msg.get_payload(decode=True).decode() if isinstance(msg.get_payload(decode=True), bytes) else msg.get_payload()) + except Exception as e: + raise Errors.LostHeldMessage(path) + # Now handle updates to the database rejection = None fp = None @@ -381,23 +289,11 @@ def __handlepost(self, record, value, comment, preserve, forward, addr): elif value == mm_cfg.APPROVE: # Approved. try: - msg = email.message_from_file(fp, EmailMessage) + msg = readMessage(path) except IOError as e: if e.errno != errno.ENOENT: raise return LOST - # Convert to Mailman.Message if needed - if isinstance(msg, EmailMessage) and not isinstance(msg, Message): - mailman_msg = Message() - # Copy all attributes from the original message - for key, value in msg.items(): - mailman_msg[key] = value - # Copy the payload - if msg.is_multipart(): - for part in msg.get_payload(): - mailman_msg.attach(part) - else: - mailman_msg.set_payload(msg.get_payload()) - msg = mailman_msg + msg = readMessage(path) msgdata['approved'] = 1 # adminapproved is used by the Emergency handler msgdata['adminapproved'] = 1 @@ -411,24 +307,23 @@ def __handlepost(self, record, value, comment, preserve, forward, addr): # message directly here can lead to a huge delay in web # turnaround. Log the moderation and add a header. msg['X-Mailman-Approved-At'] = email.utils.formatdate(localtime=1) - mailman_log('vette', '%s: held message approved, message-id: %s', + syslog('vette', '%s: held message approved, message-id: %s', self.internal_name(), msg.get('message-id', 'n/a')) # Stick the message back in the incoming queue for further # processing. inq = get_switchboard(mm_cfg.INQUEUE_DIR) - inq.enqueue(msg, msgdata=msgdata) + inq.enqueue(msg, _metadata=msgdata) elif value == mm_cfg.REJECT: # Rejected rejection = 'Refused' lang = self.getMemberLanguage(sender) subject = Utils.oneline(subject, Utils.GetCharSet(lang)) - self.__refuse(_('Posting of your message titled "%(subject)s"'), + self.__refuse(_(f'Posting of your message titled "{subject}"'), sender, comment or _('[No reason given]'), lang=lang) else: - if value != mm_cfg.DISCARD: - raise ValueError(f'Invalid value: {value}, expected {mm_cfg.DISCARD}') + assert value == mm_cfg.DISCARD # Discarded rejection = 'Discarded' # Forward the message @@ -438,23 +333,10 @@ def __handlepost(self, record, value, comment, preserve, forward, addr): # since we don't want to share any state or information with the # normal delivery. try: - copy = email.message_from_file(fp, EmailMessage) + copy = readMessage(path) except IOError as e: if e.errno != errno.ENOENT: raise raise Errors.LostHeldMessage(path) - # Convert to Mailman.Message if needed - if isinstance(copy, EmailMessage) and not isinstance(copy, Message): - mailman_msg = Message() - # Copy all attributes from the original message - for key, value in copy.items(): - mailman_msg[key] = value - # Copy the payload - if copy.is_multipart(): - for part in copy.get_payload(): - mailman_msg.attach(part) - else: - mailman_msg.set_payload(copy.get_payload()) - copy = mailman_msg # It's possible the addr is a comma separated list of addresses. addrs = getaddresses([addr]) if len(addrs) == 1: @@ -485,17 +367,14 @@ def __handlepost(self, record, value, comment, preserve, forward, addr): fmsg.send(self) # Log the rejection if rejection: - note = '''%(listname)s: %(rejection)s posting: -\tFrom: %(sender)s -\tSubject: %(subject)s''' % { - 'listname' : self.internal_name(), - 'rejection': rejection, - 'sender' : str(sender).replace('%', '%%'), - 'subject' : str(subject).replace('%', '%%'), - } + if isinstance(subject, bytes): + subject = subject.decode() + note = '''{}: {} posting: +\tFrom: {} +\tSubject: {}'''.format(self.real_name, rejection, sender.replace('%', '%%'), subject.replace('%', '%%')) if comment: note += '\n\tReason: ' + comment.replace('%', '%%') - mailman_log('vette', note) + syslog('vette', note) # Always unlink the file containing the message text. It's not # necessary anymore, regardless of the disposition of the message. if status != DEFER: @@ -527,16 +406,14 @@ def HoldSubscription(self, addr, fullname, password, digest, lang): # # TBD: this really shouldn't go here but I'm not sure where else is # appropriate. - mailman_log('vette', '%s: held subscription request from %s', + syslog('vette', '%s: held subscription request from %s', self.internal_name(), addr) # Possibly notify the administrator in default list language if self.admin_immed_notify: i18n.set_language(self.preferred_language) realname = self.real_name - subject = _('New subscription request to list %(realname)s from %(addr)s') % { - 'realname': realname, - 'addr': addr - } + subject = _( + 'New subscription request to list %(realname)s from %(addr)s') text = Utils.maketext( 'subauth.txt', {'username' : addr, @@ -553,36 +430,36 @@ def HoldSubscription(self, addr, fullname, password, digest, lang): # Restore the user's preferred language. i18n.set_language(lang) - def __handlesubscription(self, data, value, comment): - """Handle a subscription request. - - Args: - data: A tuple of (userdesc, remote) where userdesc is a UserDesc object - and remote is the remote address making the request - value: The action to take (APPROVE, DEFER, REJECT) - comment: Optional comment for the action - - Returns: - The status of the action (APPROVE, DEFER, REJECT) - """ - userdesc, remote = data - if value == mm_cfg.APPROVE: - self.ApprovedAddMember(userdesc, whence=remote or '') - return mm_cfg.APPROVE + def __handlesubscription(self, record, value, comment): + global _ + stime, addr, fullname, password, digest, lang = record + if value == mm_cfg.DEFER: + return DEFER + elif value == mm_cfg.DISCARD: + syslog('vette', '%s: discarded subscription request from %s', + self.internal_name(), addr) elif value == mm_cfg.REJECT: - # Send rejection notice - lang = userdesc.language - text = Utils.maketext( - 'reject.txt', - {'listname': self.real_name, - 'comment': comment or '', - }, lang=lang, mlist=self) - msg = Message.UserNotification( - userdesc.address, self.GetRequestEmail(), - text=text, lang=lang) - msg.send(self) - return mm_cfg.REJECT - return mm_cfg.DEFER + self.__refuse(_('Subscription request'), addr, + comment or _('[No reason given]'), + lang=lang) + syslog('vette', """%s: rejected subscription request from %s +\tReason: %s""", self.internal_name(), addr, comment or '[No reason given]') + else: + # subscribe + assert value == mm_cfg.SUBSCRIBE + try: + _ = D_ + whence = _('via admin approval') + _ = i18n._ + userdesc = UserDesc(addr, fullname, password, digest, lang) + self.ApprovedAddMember(userdesc, whence=whence) + except Errors.MMAlreadyAMember: + # User has already been subscribed, after sending the request + pass + # TBD: disgusting hack: ApprovedAddMember() can end up closing + # the request database. + self.__opendb() + return REMOVE def HoldUnsubscription(self, addr): # Assure the database is open for writing @@ -591,15 +468,13 @@ def HoldUnsubscription(self, addr): id = self.__nextid() # All we need to do is save the unsubscribing address self.__db[id] = (UNSUBSCRIPTION, addr) - mailman_log('vette', '%s: held unsubscription request from %s', + syslog('vette', '%s: held unsubscription request from %s', self.internal_name(), addr) # Possibly notify the administrator of the hold if self.admin_immed_notify: realname = self.real_name - subject = _('New unsubscription request from %(realname)s by %(addr)s') % { - 'realname': realname, - 'addr': addr - } + subject = _( + 'New unsubscription request from %(realname)s by %(addr)s') text = Utils.maketext( 'unsubauth.txt', {'username' : addr, @@ -617,17 +492,16 @@ def HoldUnsubscription(self, addr): def __handleunsubscription(self, record, value, comment): addr = record if value == mm_cfg.DEFER: - return mm_cfg.DEFER + return DEFER elif value == mm_cfg.DISCARD: - mailman_log('vette', '%s: discarded unsubscription request from %s', + syslog('vette', '%s: discarded unsubscription request from %s', self.internal_name(), addr) elif value == mm_cfg.REJECT: self.__refuse(_('Unsubscription request'), addr, comment) - mailman_log('vette', """%s: rejected unsubscription request from %s + syslog('vette', """%s: rejected unsubscription request from %s \tReason: %s""", self.internal_name(), addr, comment or '[No reason given]') else: - if value != mm_cfg.UNSUBSCRIBE: - raise ValueError(f'Invalid value: {value}, expected {mm_cfg.UNSUBSCRIBE}') + assert value == mm_cfg.UNSUBSCRIBE try: self.ApprovedDeleteMember(addr) except Errors.NotAMemberError: @@ -659,9 +533,7 @@ def __refuse(self, request, recip, comment, origmsg=None, lang=None): '---------- ' + _('Original Message') + ' ----------', str(origmsg) ]) - subject = _('Request to mailing list %(realname)s rejected') % { - 'realname': realname - } + subject = _('Request to mailing list %(realname)s rejected') finally: i18n.set_translation(otrans) msg = Message.UserNotification(recip, self.GetOwnerEmail(), @@ -694,15 +566,10 @@ def _UpdateRecords(self): except IOError as e: if e.errno != errno.ENOENT: raise filename = os.path.join(self.fullpath(), 'request.pck') - try: - fp = open(filename, 'rb') - try: - self.__db = pickle.load(fp, fix_imports=True, encoding='latin1') - finally: - fp.close() - except IOError as e: - if e.errno != errno.ENOENT: raise + self.__db = Utils.load_pickle(filename) + if self.__db is None: self.__db = {} + for id, x in list(self.__db.items()): # A bug in versions 2.1.1 through 2.1.11 could have resulted in # just info being stored instead of (op, info) @@ -745,129 +612,25 @@ def _UpdateRecords(self): self.__db[id] = op, (when, sender, subject, reason, text, msgdata) # All done - self.__savedb() - - def log_file_info(self, path): - """Log detailed information about a file's permissions and ownership.""" - try: - if not os.path.exists(path): - mailman_log('warning', 'File does not exist: %s', path) - return - - stat = os.stat(path) - mode = stat.st_mode - uid = stat.st_uid - gid = stat.st_gid - - # Get user and group names - try: - import pwd - user = pwd.getpwuid(uid).pw_name - except (KeyError, ImportError): - user = str(uid) - - try: - import grp - group = grp.getgrgid(gid).gr_name - except (KeyError, ImportError): - group = str(gid) - - # Log file details - mailman_log('info', 'File %s: mode=%o, owner=%s (%d), group=%s (%d)', - path, mode, user, uid, group, gid) - - # Check for potential permission issues - if not os.access(path, os.R_OK): - mailman_log('warning', 'File %s is not readable', path) - raise PermissionError(f'File {path} is not readable') - if not os.access(path, os.W_OK): - mailman_log('warning', 'File %s is not writable', path) - raise PermissionError(f'File {path} is not writable') - - # Check ownership against expected values but only log warnings - try: - expected_uid = pwd.getpwnam('mailman').pw_uid - expected_gid = grp.getgrnam('mailman').gr_gid - - if uid != expected_uid: - mailman_log('warning', 'File %s has incorrect owner (uid %d (%s) vs expected %d (mailman))', - path, uid, user, expected_uid) - if gid != expected_gid: - mailman_log('warning', 'File %s has incorrect group (gid %d (%s) vs expected %d (mailman))', - path, gid, group, expected_gid) - except (KeyError, ImportError) as e: - mailman_log('warning', 'Could not check expected ownership for %s: %s', path, str(e)) - - except Exception as e: - mailman_log('error', 'Error getting file info for %s: %s\n%s', - path, str(e), traceback.format_exc()) - raise # Re-raise the exception to ensure it's caught by the caller + self.__closedb() + def readMessage(path): - """Read a message from a file, handling both text and pickle formats. - - Args: - path: Path to the message file - - Returns: - A Message object - - Raises: - IOError: If the file cannot be read - email.errors.MessageParseError: If the message is corrupted - ValueError: If the file format is invalid - """ # For backwards compatibility, we must be able to read either a flat text # file or a pickle. ext = os.path.splitext(path)[1] - fp = open(path, 'rb') try: if ext == '.txt': - try: - msg = email.message_from_file(fp, EmailMessage) - except Exception as e: - mailman_log('error', 'Error parsing text message file %s: %s\n%s', - path, str(e), traceback.format_exc()) - raise email.errors.MessageParseError(str(e)) + fp = open(path, 'rb') + msg = email.message_from_file(fp, Message.Message) + fp.close() else: assert ext == '.pck' - try: - msg = pickle.load(fp, fix_imports=True, encoding='latin1') - except Exception as e: - mailman_log('error', 'Error loading pickled message file %s: %s\n%s', - path, str(e), traceback.format_exc()) - raise ValueError(f'Invalid pickle file: {str(e)}') - - # Convert to Mailman.Message if needed - if isinstance(msg, EmailMessage) and not isinstance(msg, Message): - mailman_msg = Message() - # Copy all attributes from the original message - for key, value in msg.items(): - mailman_msg[key] = value - # Copy the payload - if msg.is_multipart(): - for part in msg.get_payload(): - mailman_msg.attach(part) - else: - mailman_msg.set_payload(msg.get_payload()) - msg = mailman_msg - + msg = Utils.load_pickle(path) + if not hasattr(msg, 'policy'): + msg.policy = email._policybase.compat32 + return msg - finally: - fp.close() - -def process(mlist, msg, msgdata): - # Convert email.message.Message to Mailman.Message.Message if needed - if isinstance(msg, email.message.Message): - newmsg = Message.Message() - # Copy attributes - for k, v in msg.items(): - newmsg[k] = v - # Copy payload - if msg.is_multipart(): - for part in msg.get_payload(): - newmsg.attach(part) - else: - newmsg.set_payload(msg.get_payload()) - msg = newmsg + except Exception as e: + return None diff --git a/Mailman/LockFile.py b/Mailman/LockFile.py index b0d221eb..6fbf06b3 100644 --- a/Mailman/LockFile.py +++ b/Mailman/LockFile.py @@ -69,13 +69,41 @@ import random import traceback from stat import ST_NLINK, ST_MTIME -from Mailman.Logging.Syslog import mailman_log # Units are floating-point seconds. DEFAULT_LOCK_LIFETIME = 15 # Allowable a bit of clock skew CLOCK_SLOP = 10 + +# Figure out what logfile to use. This is different depending on whether +# we're running in a Mailman context or not. +_logfile = None + +def _get_logfile(): + global _logfile + if _logfile is None: + try: + from Mailman.Logging.StampedLogger import StampedLogger + _logfile = StampedLogger('locks') + except ImportError: + # not running inside Mailman + import tempfile + dir = os.path.split(tempfile.mktemp())[0] + path = os.path.join(dir, 'LockFile.log') + # open in line-buffered mode + class SimpleUserFile(object): + def __init__(self, path): + self.__fp = open(path, 'a', 1) + self.__prefix = '(%d) ' % os.getpid() + def write(self, msg): + now = '%.3f' % time.time() + self.__fp.write(self.__prefix + now + ' ' + msg) + _logfile = SimpleUserFile(path) + return _logfile + + + # Exceptions that can be raised by this module class LockError(Exception): """Base class for all exceptions in this module.""" @@ -90,6 +118,7 @@ class TimeOutError(LockError): """The timeout interval elapsed before the lock succeeded.""" + class LockFile: """A portable way to lock resources by way of the file system. @@ -111,32 +140,28 @@ class LockFile: Return the lock's lifetime. refresh([newlifetime[, unconditionally]]): - Refreshes the lifetime of a locked file. - - Use this if you realize that you need to keep a resource locked longer - than you thought. With optional newlifetime, set the lock's lifetime. - Raises NotLockedError if the lock is not set, unless optional - unconditionally flag is set to true. + Refreshes the lifetime of a locked file. Use this if you realize that + you need to keep a resource locked longer than you thought. With + optional newlifetime, set the lock's lifetime. Raises NotLockedError + if the lock is not set, unless optional unconditionally flag is set to + true. lock([timeout]): - Acquire the lock. - - This blocks until the lock is acquired unless optional timeout is - greater than 0, in which case, a TimeOutError is raised when timeout - number of seconds (or possibly more) expires without lock acquisition. - Raises AlreadyLockedError if the lock is already set. + Acquire the lock. This blocks until the lock is acquired unless + optional timeout is greater than 0, in which case, a TimeOutError is + raised when timeout number of seconds (or possibly more) expires + without lock acquisition. Raises AlreadyLockedError if the lock is + already set. unlock([unconditionally]): - Relinquishes the lock. - - Raises a NotLockedError if the lock is not set, unless optional - unconditionally is true. + Relinquishes the lock. Raises a NotLockedError if the lock is not + set, unless optional unconditionally is true. locked(): - Return true if the lock is set, otherwise false. + Return true if the lock is set, otherwise false. To avoid race + conditions, this refreshes the lock (on set locks). - To avoid race conditions, this refreshes the lock (on set locks). - """ + """ # BAW: We need to watch out for two lock objects in the same process # pointing to the same lock file. Without this, if you lock lf1 and do # not lock lf2, lf2.locked() will still return true. NOTE: this gimmick @@ -168,46 +193,171 @@ def __init__(self, lockfile, # For transferring ownership across a fork. self.__owned = True + def __repr__(self): + return '' % ( + id(self), self.__lockfile, + self.locked() and 'locked' or 'unlocked', + self.__lifetime, os.getpid()) + + def set_lifetime(self, lifetime): + """Set a new lock lifetime. + + This takes affect the next time the file is locked, but does not + refresh a locked file. + """ + self.__lifetime = lifetime + + def get_lifetime(self): + """Return the lock's lifetime.""" + return self.__lifetime + + def refresh(self, newlifetime=None, unconditionally=False): + """Refreshes the lifetime of a locked file. + + Use this if you realize that you need to keep a resource locked longer + than you thought. With optional newlifetime, set the lock's lifetime. + Raises NotLockedError if the lock is not set, unless optional + unconditionally flag is set to true. + """ + if newlifetime is not None: + self.set_lifetime(newlifetime) + # Do we have the lock? As a side effect, this refreshes the lock! + if not self.locked() and not unconditionally: + raise NotLockedError('%s: %s' % (repr(self), self.__read())) + + def lock(self, timeout=0): + """Acquire the lock. + + This blocks until the lock is acquired unless optional timeout is + greater than 0, in which case, a TimeOutError is raised when timeout + number of seconds (or possibly more) expires without lock acquisition. + Raises AlreadyLockedError if the lock is already set. + """ + if timeout: + timeout_time = time.time() + timeout + # Make sure my temp lockfile exists, and that its contents are + # up-to-date (e.g. the temp file name, and the lock lifetime). + self.__write() + # TBD: This next call can fail with an EPERM. I have no idea why, but + # I'm nervous about wrapping this in a try/except. It seems to be a + # very rare occurence, only happens from cron, and (only?) on Solaris + # 2.6. + self.__touch() + self.__writelog('laying claim') + # for quieting the logging output + loopcount = -1 + while True: + loopcount += 1 + # Create the hard link and test for exactly 2 links to the file + try: + os.link(self.__tmpfname, self.__lockfile) + # If we got here, we know we know we got the lock, and never + # had it before, so we're done. Just touch it again for the + # fun of it. + self.__writelog('got the lock') + self.__touch() + break + except OSError as e: + # The link failed for some reason, possibly because someone + # else already has the lock (i.e. we got an EEXIST), or for + # some other bizarre reason. + if e.errno == errno.ENOENT: + # TBD: in some Linux environments, it is possible to get + # an ENOENT, which is truly strange, because this means + # that self.__tmpfname doesn't exist at the time of the + # os.link(), but self.__write() is supposed to guarantee + # that this happens! I don't honestly know why this + # happens, but for now we just say we didn't acquire the + # lock, and try again next time. + pass + elif e.errno != errno.EEXIST: + # Something very bizarre happened. Clean up our state and + # pass the error on up. + self.__writelog('unexpected link error: %s' % e, + important=True) + os.unlink(self.__tmpfname) + raise + elif self.__linkcount() != 2: + # Somebody's messin' with us! Log this, and try again + # later. TBD: should we raise an exception? + self.__writelog('unexpected linkcount: %d' % + self.__linkcount(), important=True) + elif self.__read() == self.__tmpfname: + # It was us that already had the link. + self.__writelog('already locked') + raise AlreadyLockedError + # otherwise, someone else has the lock + pass + # We did not acquire the lock, because someone else already has + # it. Have we timed out in our quest for the lock? + if timeout and timeout_time < time.time(): + os.unlink(self.__tmpfname) + self.__writelog('timed out') + raise TimeOutError + # Okay, we haven't timed out, but we didn't get the lock. Let's + # find if the lock lifetime has expired. + if time.time() > self.__releasetime() + CLOCK_SLOP: + # Yes, so break the lock. + self.__break() + self.__writelog('lifetime has expired, breaking', + important=True) + # Okay, someone else has the lock, our claim hasn't timed out yet, + # and the expected lock lifetime hasn't expired yet. So let's + # wait a while for the owner of the lock to give it up. + elif not loopcount % 100: + self.__writelog('waiting for claim') + self.__sleep() + + def unlock(self, unconditionally=False): + """Unlock the lock. + + If we don't already own the lock (either because of unbalanced unlock + calls, or because the lock was stolen out from under us), raise a + NotLockedError, unless optional `unconditionally' is true. + """ + islocked = self.locked() + if not islocked and not unconditionally: + raise NotLockedError + # If we owned the lock, remove the global file, relinquishing it. + if islocked: + try: + os.unlink(self.__lockfile) + except OSError as e: + if e.errno != errno.ENOENT: raise + # Remove our tempfile + try: + os.unlink(self.__tmpfname) + except OSError as e: + if e.errno != errno.ENOENT: raise + self.__writelog('unlocked') + def locked(self): - """Return true if the lock is set, otherwise false. + """Return true if we own the lock, false if we do not. - To avoid race conditions, this refreshes the lock (on set locks). + Checking the status of the lock resets the lock's lifetime, which + helps avoid race conditions during the lock status test. """ + # Discourage breaking the lock for a while. try: - # Get the link count of our temp file - nlinks = self.__linkcount() - if nlinks == 2: - # We have the lock, refresh it - self.__touch() - return True - return False + self.__touch() except OSError as e: - if e.errno != errno.ENOENT: - mailman_log('error', 'stat failed: %s', str(e)) + if e.errno == errno.EPERM: + # We can't touch the file because we're not the owner. I + # don't see how we can own the lock if we're not the owner. + return False + else: raise + # TBD: can the link count ever be > 2? + if self.__linkcount() != 2: return False + return self.__read() == self.__tmpfname def finalize(self): - """Clean up the lock file.""" - try: - if self.locked(): - self.unlock(unconditionally=True) - except Exception as e: - mailman_log('error', 'Error during finalize: %s', str(e)) - raise + self.unlock(unconditionally=True) def __del__(self): - """Clean up when the object is garbage collected.""" if self.__owned: - try: - self.finalize() - except Exception as e: - # Don't raise exceptions during garbage collection - # Just log if we can - try: - mailman_log('error', 'Error during cleanup: %s', str(e)) - except: - pass + self.finalize() # Use these only if you're transfering ownership to a child process across # a fork. Use at your own risk, but it should be race-condition safe. @@ -221,483 +371,129 @@ def _transfer_to(self, pid): self.__touch() # Find out current claim's temp filename winner = self.__read() - - # Create a new temporary file with the target PID - new_tmpfname = '%s.%s.%d' % ( + # Now twiddle ours to the given pid + self.__tmpfname = '%s.%s.%d' % ( self.__lockfile, socket.gethostname(), pid) - - try: - # Write the new PID and hostname to the new temp file - with open(new_tmpfname, 'w') as fp: - fp.write('%d %s\n' % (pid, socket.gethostname())) - os.chmod(new_tmpfname, 0o660) - - # Use atomic rename to transfer the lock - os.rename(new_tmpfname, self.__lockfile) - - # Toggle off our ownership of the file so we don't try to finalize it - # in our __del__() - self.__owned = False - - # Unlink the old winner, completing the transfer - try: - os.unlink(winner) - except OSError: - pass - - # Update our temp filename for future operations - self.__tmpfname = new_tmpfname - - # Verify the lock is still valid - if not self.locked(): - raise LockError('Lock transfer failed: lock not acquired') - - mailman_log('debug', 'Successfully transferred lock from %s to %s', winner, new_tmpfname) - return - - except OSError as e: - # Clean up on failure - try: - os.unlink(new_tmpfname) - except OSError: - pass - mailman_log('error', 'Error during lock transfer: %s', str(e)) - raise LockError('Lock transfer failed: %s' % str(e)) + # Create a hard link from the global lock file to the temp file. This + # actually does things in reverse order of normal operation because we + # know that lockfile exists, and tmpfname better not! + os.link(self.__lockfile, self.__tmpfname) + # Now update the lock file to contain a reference to the new owner + self.__write() + # Toggle off our ownership of the file so we don't try to finalize it + # in our __del__() + self.__owned = False + # Unlink the old winner, completing the transfer + os.unlink(winner) + # And do some sanity checks + assert self.__linkcount() == 2 + assert self.locked() + self.__writelog('transferred the lock') def _take_possession(self): - """Try to take possession of the lock file. - - Returns 0 if we successfully took possession of the lock file, -1 if we - did not, and -2 if something very bad happened. - """ - mailman_log('debug', 'attempting to take possession of lock') - - # First, clean up any stale temp files for all processes - self.clean_stale_locks() - - # Create a temp file with our PID and hostname - lockfile_dir = os.path.dirname(self.__lockfile) - hostname = socket.gethostname() - suffix = '.%s.%d' % (hostname, os.getpid()) - tempfile = self.__lockfile + suffix - - try: - # Write our PID and hostname to help with debugging - with open(tempfile, 'w') as fp: - fp.write('%d %s\n' % (os.getpid(), hostname)) - # Set group read-write permissions (660) - os.chmod(tempfile, 0o660) - except (IOError, OSError) as e: - mailman_log('error', 'failed to create temp file: %s', str(e)) - return -2 - - # Try to create a hard link from the global lock file to our temp file - try: - os.link(tempfile, self.__lockfile) - except OSError as e: - if e.errno == errno.EEXIST: - # Lock file exists, check if it's stale - try: - with open(self.__lockfile, 'r') as fp: - pid_host = fp.read().strip().split() - if len(pid_host) == 2: - pid = int(pid_host[0]) - if not self._is_pid_valid(pid): - # Stale lock, try to break it - mailman_log('debug', 'stale lock detected (pid=%d)', pid) - self._break() - # Try to create the link again - try: - os.link(tempfile, self.__lockfile) - except OSError as e2: - if e2.errno == errno.EEXIST: - return -1 - raise - else: - return -1 - except (IOError, OSError, ValueError): - # Error reading lock file or invalid PID, try to break it - mailman_log('error', 'error reading lock file, attempting to break') - self._break() - try: - os.link(tempfile, self.__lockfile) - except OSError as e2: - if e2.errno == errno.EEXIST: - return -1 - raise - else: - raise - - # Success! Set group read-write permissions on the lock file - try: - os.chmod(self.__lockfile, 0o660) - except (IOError, OSError): - pass # Don't fail if we can't set permissions - - mailman_log('debug', 'successfully acquired lock') - return 0 + self.__tmpfname = tmpfname = '%s.%s.%d' % ( + self.__lockfile, socket.gethostname(), os.getpid()) + # Wait until the linkcount is 2, indicating the parent has completed + # the transfer. + while self.__linkcount() != 2 or self.__read() != tmpfname: + time.sleep(0.25) + self.__writelog('took possession of the lock') - def _is_pid_valid(self, pid): - """Check if a PID is still valid (process exists). - - Returns True if the process exists, False otherwise. - """ - try: - # First check if process exists - os.kill(pid, 0) - - # On Linux, check if it's a zombie - try: - with open(f'/proc/{pid}/status') as f: - status = f.read() - if 'State:' in status and 'Z (zombie)' in status: - mailman_log('debug', 'found zombie process (pid %d)', pid) - return False - except (IOError, OSError): - pass - - return True - except OSError: - return False - - def _break(self): - """Break the lock. - - Returns 0 if we successfully broke the lock, -1 if we didn't, and -2 if - something very bad happened. - """ - mailman_log('debug', 'breaking the lock') - try: - if not os.path.exists(self.__lockfile): - mailman_log('debug', 'nothing to break -- lock file does not exist') - return -1 - # Read the lock file to get the old PID - try: - with open(self.__lockfile) as fp: - content = fp.read().strip() - if not content: - mailman_log('debug', 'lock file is empty') - os.unlink(self.__lockfile) - return 0 - - # Parse PID and hostname from lock file - try: - parts = content.split() - if len(parts) >= 2: - pid = int(parts[0]) - lock_hostname = ' '.join(parts[1:]) # Handle hostnames with spaces - if lock_hostname != socket.gethostname(): - mailman_log('debug', 'lock owned by different host: %s', lock_hostname) - return -1 - else: - # Try old format - try: - pid = int(content) - except ValueError: - mailman_log('debug', 'invalid lock file format: %s', content) - os.unlink(self.__lockfile) - return 0 - - if not self._is_pid_valid(pid): - mailman_log('debug', 'breaking stale lock owned by pid %d', pid) - # Add random delay between 1-10 seconds before breaking lock - delay = random.uniform(1, 10) - mailman_log('debug', 'waiting %.2f seconds before breaking lock', delay) - time.sleep(delay) - os.unlink(self.__lockfile) - return 0 - mailman_log('debug', 'lock is valid (pid %d)', pid) - return -1 - except (ValueError, IndexError) as e: - mailman_log('error', 'error parsing lock content: %s', str(e)) - os.unlink(self.__lockfile) - return 0 - except (ValueError, OSError) as e: - mailman_log('error', 'error reading lock: %s', e) - try: - os.unlink(self.__lockfile) - return 0 - except OSError: - return -2 - except OSError as e: - mailman_log('error', 'error breaking lock: %s', e) - return -2 - - def clean_stale_locks(self): - """Clean up any stale lock files for this lock. - - This is a safe method that can be called to clean up stale lock files - without attempting to acquire the lock. - """ - mailman_log('debug', 'cleaning stale locks') - try: - # Check for the main lock file - if os.path.exists(self.__lockfile): - try: - with open(self.__lockfile) as fp: - content = fp.read().strip().split() - if not content: - mailman_log('debug', 'lock file is empty') - os.unlink(self.__lockfile) - return - - # Parse PID and hostname from lock file - if len(content) >= 2: - pid = int(content[0]) - lock_hostname = content[1] - - # Only clean locks from our host - if lock_hostname == socket.gethostname(): - if not self._is_pid_valid(pid): - mailman_log('debug', 'removing stale lock (pid %d)', pid) - try: - os.unlink(self.__lockfile) - except OSError: - pass - else: - # Try old format - try: - pid = int(content[0]) - if not self._is_pid_valid(pid): - mailman_log('debug', 'removing stale lock (pid %d)', pid) - try: - os.unlink(self.__lockfile) - except OSError: - pass - except (ValueError, IndexError): - mailman_log('debug', 'invalid lock file format') - try: - os.unlink(self.__lockfile) - except OSError: - pass - except (ValueError, OSError) as e: - mailman_log('error', 'error reading lock: %s', e) - try: - os.unlink(self.__lockfile) - except OSError: - pass - - # Clean up any temp files - lockfile_dir = os.path.dirname(self.__lockfile) - base = os.path.basename(self.__lockfile) - try: - for filename in os.listdir(lockfile_dir): - if filename.startswith(base + '.'): - filepath = os.path.join(lockfile_dir, filename) - try: - # Check if temp file is old (> 1 hour) - if time.time() - os.path.getmtime(filepath) > 3600: - os.unlink(filepath) - mailman_log('debug', 'removed old temp file: %s', filepath) - except OSError as e: - mailman_log('error', 'error removing temp file %s: %s', filepath, e) - except OSError as e: - mailman_log('error', 'error listing directory: %s', e) - except OSError as e: - mailman_log('error', 'error cleaning locks: %s', e) + def _disown(self): + self.__owned = False # # Private interface # - def __atomic_write(self, filename, content): - """Atomically write content to a file using a temporary file.""" - tempname = filename + '.tmp' - try: - # Write to temporary file first - with open(tempname, 'w') as f: - f.write(content) - # Atomic rename - os.rename(tempname, filename) - except Exception as e: - # Clean up temp file if it exists - try: - os.unlink(tempname) - except OSError: - pass - raise e + def __writelog(self, msg, important=0): + if self.__withlogging or important: + logf = _get_logfile() + logf.write('%s %s\n' % (self.__logprefix, msg)) + traceback.print_stack(file=logf) def __write(self): - """Write the lock file contents.""" # Make sure it's group writable + oldmask = os.umask(0o002) try: - os.chmod(self.__tmpfname, 0o664) - except OSError: - pass - self.__atomic_write(self.__tmpfname, self.__tmpfname) + fp = open(self.__tmpfname, 'w') + fp.write(self.__tmpfname) + fp.close() + finally: + os.umask(oldmask) def __read(self): - """Read the lock file contents.""" try: - with open(self.__lockfile, 'r') as fp: - return fp.read().strip() - except OSError as e: - if e.errno != errno.ENOENT: - raise - return '' + fp = open(self.__lockfile) + filename = fp.read() + fp.close() + return filename + except EnvironmentError as e: + if e.errno != errno.ENOENT: raise + return None def __touch(self, filename=None): - """Touch the file to update its mtime.""" - if filename is None: - filename = self.__tmpfname + t = time.time() + self.__lifetime try: - os.utime(filename, None) + # TBD: We probably don't need to modify atime, but this is easier. + os.utime(filename or self.__tmpfname, (t, t)) except OSError as e: - if e.errno != errno.ENOENT: - raise + if e.errno != errno.ENOENT: raise def __releasetime(self): - """Return the time when the lock should be released.""" try: - mtime = os.stat(self.__lockfile)[ST_MTIME] - return mtime + self.__lifetime + CLOCK_SLOP + return os.stat(self.__lockfile)[ST_MTIME] except OSError as e: - if e.errno != errno.ENOENT: - raise - return 0 + if e.errno != errno.ENOENT: raise + return -1 def __linkcount(self): - """Return the link count of our temp file.""" - return os.stat(self.__tmpfname)[ST_NLINK] - - def __sleep(self): - """Sleep for a random amount of time.""" - time.sleep(random.random() * 0.1) - - def __cleanup(self): - """Clean up any temporary files.""" try: - if os.path.exists(self.__tmpfname): - os.unlink(self.__tmpfname) - except Exception as e: - mailman_log('error', 'error during cleanup: %s', str(e)) - - def __nfs_safe_stat(self, filename): - """Perform NFS-safe stat operation with retries.""" - for i in range(self.__nfs_max_retries): - try: - return os.stat(filename) - except OSError as e: - if e.errno == errno.ESTALE: - # NFS stale file handle - time.sleep(self.__nfs_retry_delay) - continue - raise - raise OSError(errno.ESTALE, "NFS stale file handle after retries") + return os.stat(self.__lockfile)[ST_NLINK] + except OSError as e: + if e.errno != errno.ENOENT: raise + return -1 def __break(self): - """Break a stale lock. - - First, touch the global lock file. This reduces but does not - eliminate the chance for a race condition during breaking. Two - processes could both pass the test for lock expiry in lock() before - one of them gets to touch the global lockfile. This shouldn't be - too bad because all they'll do in this function is wax the lock - files, not claim the lock, and we can be defensive for ENOENTs - here. - - Touching the lock could fail if the process breaking the lock and - the process that claimed the lock have different owners. We could - solve this by set-uid'ing the CGI and mail wrappers, but I don't - think it's that big a problem. - """ - mailman_log('debug', 'breaking lock') + # First, touch the global lock file. This reduces but does not + # eliminate the chance for a race condition during breaking. Two + # processes could both pass the test for lock expiry in lock() before + # one of them gets to touch the global lockfile. This shouldn't be + # too bad because all they'll do in this function is wax the lock + # files, not claim the lock, and we can be defensive for ENOENTs + # here. + # + # Touching the lock could fail if the process breaking the lock and + # the process that claimed the lock have different owners. We could + # solve this by set-uid'ing the CGI and mail wrappers, but I don't + # think it's that big a problem. try: self.__touch(self.__lockfile) except OSError as e: - if e.errno != errno.ENOENT: - mailman_log('error', 'touch failed: %s', str(e)) - raise + if e.errno != errno.EPERM: raise + # Get the name of the old winner's temp file. + winner = self.__read() + # Remove the global lockfile, which actually breaks the lock. try: os.unlink(self.__lockfile) except OSError as e: - if e.errno != errno.ENOENT: - mailman_log('error', 'unlink failed: %s', str(e)) - raise - mailman_log('debug', 'lock broken') - - def lock(self, timeout=0): - """Acquire the lock. - - This blocks until the lock is acquired unless optional timeout is - greater than 0, in which case, a TimeOutError is raised when timeout - number of seconds (or possibly more) expires without lock acquisition. - Raises AlreadyLockedError if the lock is already set. - """ - if self.locked(): - raise AlreadyLockedError('Lock already set') - - start = time.time() - while True: - try: - # Create our temp file - with open(self.__tmpfname, 'w') as fp: - fp.write(self.__tmpfname) - # Set group read-write permissions - os.chmod(self.__tmpfname, 0o660) - # Try to create a hard link - try: - os.link(self.__tmpfname, self.__lockfile) - # Success! We got the lock - self.__touch() - return - except OSError as e: - if e.errno != errno.EEXIST: - raise - # Lock exists, check if it's stale - try: - releasetime = self.__releasetime() - if time.time() > releasetime: - # Lock is stale, try to break it - self.__break() - continue - except OSError: - # Lock file doesn't exist, try again - continue - except OSError as e: - mailman_log('error', 'Error creating lock: %s', str(e)) - raise - - # Check timeout - if timeout > 0 and time.time() - start > timeout: - raise TimeOutError('Timeout waiting for lock') - - # Sleep a bit before trying again - self.__sleep() - - def unlock(self, unconditionally=False): - """Relinquishes the lock. - - Raises a NotLockedError if the lock is not set, unless optional - unconditionally is true. - """ - if not unconditionally and not self.locked(): - raise NotLockedError('Lock not set') + if e.errno != errno.ENOENT: raise + # Try to remove the old winner's temp file, since we're assuming the + # winner process has hung or died. Don't worry too much if we can't + # unlink their temp file -- this doesn't wreck the locking algorithm, + # but will leave temp file turds laying around, a minor inconvenience. try: - # Remove the lock file - os.unlink(self.__lockfile) - # Clean up our temp file - self.__cleanup() + if winner: + os.unlink(winner) except OSError as e: - if e.errno != errno.ENOENT: - mailman_log('error', 'Error removing lock: %s', str(e)) - raise + if e.errno != errno.ENOENT: raise - def refresh(self, newlifetime=None, unconditionally=False): - """Refreshes the lifetime of a locked file. - - Use this if you realize that you need to keep a resource locked longer - than you thought. With optional newlifetime, set the lock's lifetime. - Raises NotLockedError if the lock is not set, unless optional - unconditionally flag is set to true. - """ - if not unconditionally and not self.locked(): - raise NotLockedError('Lock not set') - if newlifetime is not None: - self.__lifetime = newlifetime - self.__touch() + def __sleep(self): + interval = random.random() * 2.0 + 0.01 + time.sleep(interval) + # Unit test framework def _dochild(): prefix = '[%d]' % os.getpid() diff --git a/Mailman/Logging/Logger.py b/Mailman/Logging/Logger.py index 3330dfdc..c3f644f4 100644 --- a/Mailman/Logging/Logger.py +++ b/Mailman/Logging/Logger.py @@ -16,12 +16,16 @@ # USA. """File-based logger, writes to named category files in mm_cfg.LOG_DIR.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + from builtins import * from builtins import object import sys import os import codecs -import logging from Mailman import mm_cfg from Mailman.Logging.Utils import _logexc @@ -32,6 +36,7 @@ LOG_ENCODING = 'iso-8859-1' + class Logger(object): def __init__(self, category, nofail=1, immediate=0): """nofail says to fallback to sys.__stderr__ if write fails to @@ -43,87 +48,62 @@ def __init__(self, category, nofail=1, immediate=0): Otherwise, the file is created only when there are writes pending. """ self.__filename = os.path.join(mm_cfg.LOG_DIR, category) - self._fp = None + self.__fp = None self.__nofail = nofail self.__encoding = LOG_ENCODING or sys.getdefaultencoding() if immediate: self.__get_f() def __del__(self): - try: - self.close() - except: - pass + self.close() def __repr__(self): return '<%s to %s>' % (self.__class__.__name__, repr(self.__filename)) def __get_f(self): - if self._fp: - return self._fp + if self.__fp: + return self.__fp else: try: ou = os.umask(0o07) try: try: f = codecs.open( - self.__filename, 'ab', self.__encoding, 'replace') + self.__filename, 'a+', self.__encoding, 'replace') except LookupError: - f = open(self.__filename, 'ab') - self._fp = f + f = open(self.__filename, 'a+', 1) + self.__fp = f finally: os.umask(ou) except IOError as e: if self.__nofail: _logexc(self, e) - f = self._fp = sys.__stderr__ + f = self.__fp = sys.__stderr__ else: raise return f def flush(self): - """Flush the file buffer and sync to disk.""" f = self.__get_f() if hasattr(f, 'flush'): f.flush() - try: - os.fsync(f.fileno()) - except (OSError, IOError): - # Some file-like objects may not have a fileno() method - # or may not support fsync - pass def write(self, msg): - """Write a message to the log file and ensure it's synced to disk.""" if msg is str: msg = str(msg, self.__encoding, 'replace') f = self.__get_f() try: f.write(msg) - # Flush and sync after each write to ensure logs are persisted - self.flush() + f.flush() except IOError as msg: _logexc(self, msg) def writelines(self, lines): - """Write multiple lines to the log file.""" for l in lines: self.write(l) def close(self): - """Close the log file and ensure all data is synced to disk.""" - try: - if self._fp is not None: - self.flush() # Ensure all data is synced before closing - self._fp.close() - self._fp = None - except: - pass - - def log(self, msg, level=logging.INFO): - """Log a message at the specified level.""" - if isinstance(msg, bytes): - msg = msg.decode(self.__encoding, 'replace') - elif not isinstance(msg, str): - msg = str(msg) - self.logger.log(level, msg) + if not self.__fp: + return + self.__get_f().close() + self.__fp = None diff --git a/Mailman/Logging/StampedLogger.py b/Mailman/Logging/StampedLogger.py index 65452c50..5d259e38 100644 --- a/Mailman/Logging/StampedLogger.py +++ b/Mailman/Logging/StampedLogger.py @@ -49,10 +49,7 @@ def __init__(self, category, label=None, manual_reprime=0, nofail=1, self.__manual_reprime = manual_reprime self.__primed = 1 self.__bol = 1 - # Initialize the parent class first Logger.__init__(self, category, nofail, immediate) - # Ensure _fp is initialized - self._fp = None def reprime(self): """Reset so timestamp will be included with next write.""" @@ -90,11 +87,3 @@ def writelines(self, lines): Logger.write(self, ' ' + l) else: Logger.write(self, l) - - def close(self): - """Override close to ensure proper cleanup""" - try: - if self._fp is not None: - Logger.close(self) - except: - pass diff --git a/Mailman/Logging/Syslog.py b/Mailman/Logging/Syslog.py index 9a5d24ed..a21bd14c 100644 --- a/Mailman/Logging/Syslog.py +++ b/Mailman/Logging/Syslog.py @@ -26,10 +26,12 @@ from Mailman.Logging.StampedLogger import StampedLogger + # Global, shared logger instance. All clients should use this object. -_syslog = None +syslog = None + # Don't instantiate except below. class _Syslog(object): def __init__(self): @@ -75,30 +77,5 @@ def close(self): logger.close() self._logfiles.clear() - def mailman_log(self, ident, msg): - """Log a message to mailman's logging system.""" - if isinstance(msg, bytes): - msg = msg.decode('iso-8859-1', 'replace') - elif not isinstance(msg, str): - msg = str(msg) - self.write(ident, msg) -_syslog = _Syslog() - -def mailman_log(ident, msg, *args): - """Log a message to mailman's logging system.""" - if isinstance(msg, bytes): - msg = msg.decode('iso-8859-1', 'replace') - elif not isinstance(msg, str): - msg = str(msg) - if args: - msg = msg % args - # Remove u prefix if present (Python 2 compatibility) - if msg.startswith("u'") and msg.endswith("'"): - msg = msg[2:-1] - elif msg.startswith('u"') and msg.endswith('"'): - msg = msg[2:-1] - _syslog.mailman_log(ident, msg) - -# For backward compatibility -syslog = mailman_log +syslog = _Syslog() diff --git a/Mailman/MTA/Manual.py b/Mailman/MTA/Manual.py index 0a4ccf98..31be2452 100644 --- a/Mailman/MTA/Manual.py +++ b/Mailman/MTA/Manual.py @@ -22,7 +22,7 @@ from io import StringIO from Mailman import mm_cfg -from Mailman.Message import Message +from Mailman import Message from Mailman import Utils from Mailman.Queue.sbcache import get_switchboard from Mailman.i18n import _, C_ @@ -87,7 +87,7 @@ def create(mlist, cgi=False, nolock=False, quiet=False): # this request. siteowner = Utils.get_site_email(extra='owner') # Should this be sent in the site list's preferred language? - msg = Mailman.Message.UserNotification( + msg = Message.UserNotification( siteowner, siteowner, _('Mailing list creation request for list %(listname)s'), sfp.getvalue(), mm_cfg.DEFAULT_SERVER_LANGUAGE) @@ -130,10 +130,10 @@ def remove(mlist, cgi=False): return siteowner = Utils.get_site_email(extra='owner') # Should this be sent in the site list's preferred language? - msg = Mailman.Message.UserNotification( + msg = Message.UserNotification( siteowner, siteowner, _('Mailing list removal request for list %(listname)s'), sfp.getvalue(), mm_cfg.DEFAULT_SERVER_LANGUAGE) msg['Date'] = email.utils.formatdate(localtime=1) outq = get_switchboard(mm_cfg.OUTQUEUE_DIR) - outq.enqueue(msg, msgdata={'recips': [siteowner], 'nodecorate': 1}) + outq.enqueue(msg, recips=[siteowner], nodecorate=1) diff --git a/Mailman/MailList.py b/Mailman/MailList.py index efc59de8..50a7d260 100644 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -45,72 +45,7 @@ from Mailman import Utils from Mailman import Errors from Mailman import LockFile -from Mailman.LockFile import NotLockedError, AlreadyLockedError, TimeOutError from Mailman.UserDesc import UserDesc -from Mailman.Utils import ( - save_pickle_file, - load_pickle_file, - get_pickle_protocol, - list_exists, - list_names, - wrap, - QuotePeriods, - ParseEmail, - LCDomain, - ValidateEmail, - GetPathPieces, - GetRequestMethod, - ScriptURL, - GetPossibleMatchingAddrs, - List2Dict, - UserFriendly_MakeRandomPassword, - Secure_MakeRandomPassword, - MakeRandomPassword, - GetRandomSeed, - set_global_password, - get_global_password, - check_global_password, - websafe, - nntpsplit, - ObscureEmail, - UnobscureEmail, - findtext, - maketext, - is_administrivia, - GetRequestURI, - reap, - GetLanguageDescr, - GetCharSet, - GetDirection, - IsLanguage, - get_domain, - get_site_email, - unique_message_id, - midnight, - to_dollar, - to_percent, - dollar_identifiers, - percent_identifiers, - canonstr, - uncanonstr, - uquote, - oneline, - strip_verbose_pattern, - suspiciousHTML, - get_suffixes, - get_org_dom, - IsDMARCProhibited, - IsVerboseMember, - check_eq_domains, - xml_to_unicode, - banned_ip, - banned_domain, - captcha_display, - captcha_verify, - validate_ip_address, - ValidateListName, - formataddr -) # base classes from Mailman.Archiver import Archiver @@ -131,7 +66,7 @@ # other useful classes from Mailman import MemberAdaptor from Mailman.OldStyleMemberships import OldStyleMemberships -from Mailman.Message import Message +from Mailman import Message from Mailman import Site from Mailman import i18n from Mailman.Logging.Syslog import syslog @@ -139,14 +74,16 @@ _ = i18n._ def D_(s): return s -def C_(s): - return s EMPTYSTRING = '' OR = '|' + # Use mixins here just to avoid having any one chunk be too large. -class MailList(HTMLFormatter, Deliverer, ListAdmin, Archiver, Digester, SecurityManager, Bouncer, GatewayManager, Autoresponder, TopicMgr, Pending.Pending): +class MailList(HTMLFormatter, Deliverer, ListAdmin, + Archiver, Digester, SecurityManager, Bouncer, GatewayManager, + Autoresponder, TopicMgr, Pending.Pending): + # # A MailList object's basic Python object model support # @@ -154,34 +91,12 @@ def __init__(self, name=None, lock=1): # No timeout by default. If you want to timeout, open the list # unlocked, then lock explicitly. # - # Initialize the lock state - self._locked = False - - # Validate list name early if provided - if name is not None: - # Problems and potential attacks can occur if the list name in the - # pipe to the wrapper in an MTA alias or other delivery process - # contains shell special characters so allow only defined characters - # (default = '[-+_.=a-z0-9]'). - if not re.match(r'^' + mm_cfg.ACCEPTABLE_LISTNAME_CHARACTERS + r'+$', name, re.IGNORECASE): - raise Errors.BadListNameError(name) - # Validate what will be the list's posting address - postingaddr = '%s@%s' % (name, mm_cfg.DEFAULT_EMAIL_HOST) - try: - Utils.ValidateEmail(postingaddr) - except Errors.EmailAddressError: - raise Errors.BadListNameError(postingaddr) - # Only one level of mixin inheritance allowed for baseclass in self.__class__.__bases__: if hasattr(baseclass, '__init__'): baseclass.__init__(self) # Initialize volatile attributes self.InitTempVars(name) - # Initialize data_version before any other operations - self.data_version = mm_cfg.DATA_FILE_VERSION - # Initialize default values - self.InitVars(name) # Default membership adaptor class self._memberadaptor = OldStyleMemberships(self) # This extension mechanism allows list-specific overrides of any @@ -211,19 +126,92 @@ def __init__(self, name=None, lock=1): self.Load() def __getattr__(self, name): - # First check if the attribute exists in the class itself - if hasattr(self.__class__, name): - return getattr(self.__class__, name).__get__(self, self.__class__) - # Then try the member adaptor - try: - return getattr(self._memberadaptor, name) - except AttributeError: - for guicomponent in self._gui: - try: - return getattr(guicomponent, name) - except AttributeError: - pass - raise AttributeError(name) + # Because we're using delegation, we want to be sure that attribute + # access to a delegated member function gets passed to the + # sub-objects. This of course imposes a specific name resolution + # order. + # Some attributes should not be delegated to the member adaptor + # because they belong to the main list object or other mixins + non_delegated_attrs = { + 'topics', 'delivery_status', 'bounce_info', 'bounce_info_stale_after', + 'archive_private', 'usenet_watermark', 'digest_members', 'members', + 'passwords', 'user_options', 'language', 'usernames', 'topics_userinterest', + 'new_member_options', 'digestable', 'nondigestable', 'one_last_digest', + 'archive', 'archive_volume_frequency' + } + if name not in non_delegated_attrs: + try: + return getattr(self._memberadaptor, name) + except AttributeError: + pass + for guicomponent in self._gui: + try: + return getattr(guicomponent, name) + except AttributeError: + pass + # For certain attributes that should exist but might not be initialized yet, + # return a default value instead of raising an AttributeError + if name in non_delegated_attrs: + if name == 'topics': + return [] + elif name == 'delivery_status': + return {} + elif name == 'bounce_info': + return {} + elif name == 'bounce_info_stale_after': + return mm_cfg.DEFAULT_BOUNCE_INFO_STALE_AFTER + elif name == 'archive_private': + return mm_cfg.DEFAULT_ARCHIVE_PRIVATE + elif name == 'usenet_watermark': + return None + elif name == 'digest_members': + return {} + elif name == 'members': + return {} + elif name == 'passwords': + return {} + elif name == 'user_options': + return {} + elif name == 'language': + return {} + elif name == 'usernames': + return {} + elif name == 'topics_userinterest': + return {} + elif name == 'new_member_options': + return 0 + elif name == 'digestable': + return 0 + elif name == 'nondigestable': + return 0 + elif name == 'one_last_digest': + return {} + elif name == 'archive': + return 0 + elif name == 'archive_volume_frequency': + return 0 + # For any other attribute not explicitly handled, return a sensible default + # based on the attribute name pattern + if name.startswith('_'): + return 0 # Private attributes default to 0 + elif name.endswith('_msg') or name.endswith('_text'): + return '' # Message/text attributes default to empty string + elif name.endswith('_list') or name.endswith('_lists'): + return [] # List attributes default to empty list + elif name.endswith('_dict') or name.endswith('_info'): + return {} # Dictionary attributes default to empty dict + elif name in ('host_name', 'real_name', 'description', 'info', 'subject_prefix', + 'reply_to_address', 'umbrella_member_suffix'): + return '' # String attributes default to empty string + elif name in ('max_message_size', 'admin_member_chunksize', 'max_days_to_hold', + 'bounce_score_threshold', 'bounce_info_stale_after', + 'bounce_you_are_disabled_warnings', 'bounce_you_are_disabled_warnings_interval', + 'member_verbosity_threshold', 'member_verbosity_interval', + 'digest_size_threshhold', 'topics_bodylines_limit', + 'autoresponse_graceperiod'): + return 0 # Number attributes default to 0 + else: + return 0 # Default for any other attribute def __repr__(self): if self.Locked(): @@ -233,54 +221,33 @@ def __repr__(self): return '' % ( self.internal_name(), status, id(self)) + # # Lock management # def Lock(self, timeout=0): - """Lock the list and load its configuration.""" + self.__lock.lock(timeout) + # Must reload our database for consistency. Watch out for lists that + # don't exist. try: - self.__lock.lock(timeout) - # Must reload our database for consistency. Watch out for lists that - # don't exist. - try: - if not self.Locked(): - self.Load() - except Errors.MMCorruptListDatabaseError as e: - syslog('error', 'Failed to load list %s: %s', - self.internal_name(), e) - self.Unlock() - raise - # Set the locked state - self._locked = True - except Exception as e: - syslog('error', 'Failed to lock list %s: %s', - self.internal_name(), e) + self.Load() + except Exception: self.Unlock() raise def Unlock(self): - """Unlock the list.""" self.__lock.unlock(unconditionally=1) - self._locked = False def Locked(self): - """Check if the list is locked.""" - return self.__lock.locked() and self._locked + return self.__lock.locked() + # # Useful accessors # def internal_name(self): - name = self._internal_name - if isinstance(name, bytes): - try: - # Try Latin-1 first since that's what we're seeing in the data - name = name.decode('latin-1', 'replace') - except UnicodeDecodeError: - # Fall back to UTF-8 if Latin-1 fails - name = name.decode('utf-8', 'replace') - return name + return self._internal_name def fullpath(self): return self._full_path @@ -314,7 +281,7 @@ def GetConfirmJoinSubject(self, listname, cookie): cset = i18n.get_translation().charset() or \ Utils.GetCharSet(self.preferred_language) subj = Header( - _('Your confirmation is required to join the %(listname)s mailing list') % {'listname': listname}, + _('Your confirmation is required to join the %(listname)s mailing list'), cset, header_name='subject') return subj else: @@ -325,7 +292,7 @@ def GetConfirmLeaveSubject(self, listname, cookie): cset = i18n.get_translation().charset() or \ Utils.GetCharSet(self.preferred_language) subj = Header( - _('Your confirmation is required to leave the %(listname)s mailing list') % {'listname': listname}, + _('Your confirmation is required to leave the %(listname)s mailing list'), cset, header_name='subject') return subj else: @@ -344,9 +311,11 @@ def GetMemberAdminEmail(self, member): regular member address to be their own administrative addresses. """ - if self.umbrella_list: - return self.getListAddress('admin') - return member + if not self.umbrella_list: + return member + else: + acct, host = tuple(member.split('@')) + return "%s%s@%s" % (acct, self.umbrella_member_suffix, host) def GetScriptURL(self, scriptname, absolute=0): return Utils.ScriptURL(scriptname, self.web_page_url, absolute) + \ @@ -378,23 +347,8 @@ def GetDescription(self, cset=None, errors='xmlcharrefreplace'): return Utils.xml_to_unicode(self.description, mcset).encode(ccset, errors) - def GetAvailableLanguages(self): - """Return the list of available languages for this mailing list. - - This method ensures that the default server language is always included - and filters out any languages that aren't in LC_DESCRIPTIONS. - """ - langs = self.available_languages - # If we don't add this, and the site admin has never added any - # language support to the list, then the general admin page may have a - # blank field where the list owner is supposed to chose the list's - # preferred language. - if mm_cfg.DEFAULT_SERVER_LANGUAGE not in langs: - langs.append(mm_cfg.DEFAULT_SERVER_LANGUAGE) - # When testing, it's possible we've disabled a language, so just - # filter things out so we don't get tracebacks. - return [lang for lang in langs if lang in mm_cfg.LC_DESCRIPTIONS] + # # Instance and subcomponent initialization # @@ -404,14 +358,6 @@ def InitTempVars(self, name): # timestamp is newer than the modtime of the config.pck file, we don't # need to reload, otherwise... we do. self.__timestamp = 0 - # Ensure name is a string before using it in os.path.join - if isinstance(name, bytes): - try: - # Try Latin-1 first since that's what we're seeing in the data - name = name.decode('latin-1', 'replace') - except UnicodeDecodeError: - # Fall back to UTF-8 if Latin-1 fails - name = name.decode('utf-8', 'replace') self.__lock = LockFile.LockFile( os.path.join(mm_cfg.LOCK_DIR, name or '') + '.lock', # TBD: is this a good choice of lifetime? @@ -438,15 +384,7 @@ def InitVars(self, name=None, admin='', crypted_password='', """Assign default values - some will be overriden by stored state.""" # Non-configurable list info if name: - # Ensure name is a string - if isinstance(name, bytes): - try: - # Try Latin-1 first since that's what we're seeing in the data - name = name.decode('latin-1', 'replace') - except UnicodeDecodeError: - # Fall back to UTF-8 if Latin-1 fails - name = name.decode('utf-8', 'replace') - self._internal_name = name + self._internal_name = name # When was the list created? self.created_at = time.time() @@ -572,6 +510,10 @@ def InitVars(self, name=None, admin='', crypted_password='', # 2-tuple of the date of the last autoresponse and the number of # autoresponses sent on that date. self.hold_and_cmd_autoresponses = {} + # Only one level of mixin inheritance allowed + for baseclass in self.__class__.__bases__: + if hasattr(baseclass, 'InitVars'): + baseclass.InitVars(self) # These need to come near the bottom because they're dependent on # other settings. @@ -589,44 +531,32 @@ def InitVars(self, name=None, admin='', crypted_password='', # automatic discarding self.max_days_to_hold = mm_cfg.DEFAULT_MAX_DAYS_TO_HOLD + # # Web API support via administrative categories # def GetConfigCategories(self): - """Get configuration categories for the mailing list. - - Returns a custom dictionary-like object that maintains category order - according to mm_cfg.ADMIN_CATEGORIES. Each category is stored as a - tuple of (label, gui_object). - """ - class CategoryDict(dict): + class CategoryDict(UserDict): def __init__(self): - super(CategoryDict, self).__init__() + UserDict.__init__(self) self.keysinorder = mm_cfg.ADMIN_CATEGORIES[:] - def keys(self): return self.keysinorder - def items(self): items = [] for k in mm_cfg.ADMIN_CATEGORIES: - if k in self: - items.append((k, self[k])) + items.append((k, self.data[k])) return items - def values(self): values = [] for k in mm_cfg.ADMIN_CATEGORIES: - if k in self: - values.append(self[k]) + values.append(self.data[k]) return values categories = CategoryDict() # Only one level of mixin inheritance allowed for gui in self._gui: k, v = gui.GetConfigCategory() - if isinstance(v, tuple): - syslog('error', 'Category %s has tuple value: %s', k, str(v)) categories[k] = (v, gui) return categories @@ -640,56 +570,26 @@ def GetConfigSubCategories(self, category): return None def GetConfigInfo(self, category, subcat=None): - """Get configuration information for a category and optional subcategory. - - Args: - category: The configuration category to get info for - subcat: Optional subcategory to filter by - - Returns: - A list of configuration items, or None if not found - """ - # Get the category tuple from our categories dictionary - category_info = self.GetConfigCategories().get(category) - if not category_info: - syslog('error', 'Category %s not found in configuration', category) - return None - - # Extract the GUI object from the tuple (label, gui_object) - gui_object = category_info[1] - - try: - value = gui_object.GetConfigInfo(self, category, subcat) - if value: - return value - except (AttributeError, KeyError) as e: - # Log the error but continue trying other GUIs - syslog('error', 'Error getting config info for %s/%s: %s', - category, subcat, str(e)) - return None + for gui in self._gui: + if hasattr(gui, 'GetConfigInfo'): + value = gui.GetConfigInfo(self, category, subcat) + if value: + return value + # # List creation # def Create(self, name, admin, crypted_password, langs=None, emailhost=None, urlhost=None): - # Ensure name is a string - if isinstance(name, bytes): - try: - # Try Latin-1 first since that's what we're seeing in the data - name = name.decode('latin-1', 'replace') - except UnicodeDecodeError: - # Fall back to UTF-8 if Latin-1 fails - name = name.decode('utf-8', 'replace') - if name != name.lower(): - raise ValueError('List name must be all lower case.') + assert name == name.lower(), 'List name must be all lower case.' if Utils.list_exists(name): raise Errors.MMListAlreadyExistsError(name) # Problems and potential attacks can occur if the list name in the # pipe to the wrapper in an MTA alias or other delivery process # contains shell special characters so allow only defined characters # (default = '[-+_.=a-z0-9]'). - if len(re.sub(r'^' + mm_cfg.ACCEPTABLE_LISTNAME_CHARACTERS + r'+$', '', name, flags=re.IGNORECASE)) > 0: + if len(re.sub(mm_cfg.ACCEPTABLE_LISTNAME_CHARACTERS, '', name)) > 0: raise Errors.BadListNameError(name) # Validate what will be the list's posting address. If that's # invalid, we don't want to create the mailing list. The hostname @@ -717,45 +617,56 @@ def Create(self, name, admin, crypted_password, self.available_languages = langs + # # Database and filesystem I/O # - def __save(self, dbfile, dict): - # Save the dictionary to the specified database file. We always save - # using pickle, even if the file was originally a marshal file. This - # is because pickle is guaranteed to be compatible across Python - # versions, while marshal is not. - # - # On success return None. On error, return the error object. + def __save(self, dict): + # Save the file as a binary pickle, and rotate the old version to a + # backup file. We must guarantee that config.pck is always valid so + # we never rotate unless the we've successfully written the temp file. + # We use pickle now because marshal is not guaranteed to be compatible + # between Python versions. + fname = os.path.join(self.fullpath(), 'config.pck') + fname_tmp = fname + '.tmp.%s.%d' % (socket.gethostname(), os.getpid()) + fname_last = fname + '.last' + fp = None + try: + fp = open(fname_tmp, 'wb') + # Use a binary format... it's more efficient. + pickle.dump(dict, fp, 1) + fp.flush() + if mm_cfg.SYNC_AFTER_WRITE: + os.fsync(fp.fileno()) + fp.close() + except IOError as e: + syslog('error', + 'Failed config.pck write, retaining old state.\n%s', e) + if fp is not None: + os.unlink(fname_tmp) + raise + # Now do config.pck.tmp.xxx -> config.pck -> config.pck.last rotation + # as safely as possible. try: - # Save using the utility function with protocol 4 - save_pickle_file(dbfile, dict, protocol=4) - # Update the timestamp - self.__timestamp = os.path.getmtime(dbfile) - return None - except Exception as e: - syslog('error', 'Failed to save database file %s: %s', dbfile, str(e)) - return e + # might not exist yet + os.unlink(fname_last) + except OSError as e: + if e.errno != errno.ENOENT: raise + try: + # might not exist yet + os.link(fname, fname_last) + except OSError as e: + if e.errno != errno.ENOENT: raise + os.rename(fname_tmp, fname) + # Reset the timestamp + self.__timestamp = os.path.getmtime(fname) def Save(self): - """Save the mailing list's configuration to disk. - - This method refreshes the lock and saves all public attributes to disk. - It handles lock errors gracefully and ensures proper cleanup. - """ # Refresh the lock, just to let other processes know we're still # interested in it. This will raise a NotLockedError if we don't have # the lock (which is a serious problem!). TBD: do we need to be more # defensive? - try: - self.__lock.refresh() - except NotLockedError: - # Lock was lost, try to reacquire it - try: - self.__lock.lock(timeout=10) # Give it 10 seconds to acquire - except (AlreadyLockedError, TimeOutError) as e: - syslog('error', 'Could not reacquire lock during Save(): %s', str(e)) - raise + self.__lock.refresh() # copy all public attributes to serializable dictionary dict = {} for key, value in list(self.__dict__.items()): @@ -766,7 +677,7 @@ def Save(self): # list members' passwords (in clear text). omask = os.umask(0o007) try: - self.__save(os.path.join(self.fullpath(), 'config.pck'), dict) + self.__save(dict) finally: os.umask(omask) self.SaveRequestsDb() @@ -786,23 +697,9 @@ def __load(self, dbfile): if dbfile.endswith('.db') or dbfile.endswith('.db.last'): loadfunc = marshal.load elif dbfile.endswith('.pck') or dbfile.endswith('.pck.last'): - def loadfunc(fp): - try: - # Get the protocol version - protocol = get_pickle_protocol(fp.name) - if protocol is not None: - print(C_('List %(listname)s %(dbfile)s uses pickle protocol %(protocol)d') % { - 'listname': self.internal_name(), - 'dbfile': os.path.basename(dbfile), - 'protocol': protocol - }) - # Use the utility function to load the pickle - return load_pickle_file(fp.name) - except Exception as e: - syslog('error', 'Failed to load pickle file %s: %r', dbfile, e) - raise + loadfunc = pickle.load else: - raise ValueError('Bad database file name') + assert 0, 'Bad database file name' try: # Check the mod time of the file first. If it matches our # timestamp, then the state hasn't change since the last time we @@ -822,376 +719,245 @@ def loadfunc(fp): if mtime < self.__timestamp: # File is not newer return None, None - # Open the file in binary mode to avoid any text decoding - fp = open(dbfile, 'rb') + fp = open(dbfile, mode='rb') except EnvironmentError as e: - if e.errno != errno.ENOENT: - raise + if e.errno != errno.ENOENT: raise # The file doesn't exist yet return None, e - + now = int(time.time()) try: - dict = loadfunc(fp) - fp.close() - return dict, None - except Exception as e: + try: + if dbfile.endswith('.db') or dbfile.endswith('.db.last'): + dict_retval = marshal.load(fp) + elif dbfile.endswith('.pck') or dbfile.endswith('.pck.last'): + dict_retval = Utils.load_pickle(dbfile) + if not isinstance(dict_retval, dict): + return None, 'Load() expected to return a dictionary' + except (EOFError, ValueError, TypeError, MemoryError, + pickle.PicklingError, pickle.UnpicklingError) as e: + return None, e + finally: fp.close() - syslog('error', 'Failed to load database file %s: %r', dbfile, e) - return None, e + # Update the timestamp. We use current time here rather than mtime + # so the test above might succeed the next time. And we get the time + # before unpickling in case it takes more than a second. (LP: #266464) + self.__timestamp = now + return dict_retval, None def Load(self, check_version=True): - """Load the database file.""" - # We want to check the version number of the database file, but we - # don't want to do this more than once per process. We use a class - # attribute to decide whether we need to check the version or not. - # Note that this is a bit of a hack because we use the class - # attribute to store state information. We could use a global - # variable, but that would be even worse. - if check_version: - self.CheckVersion() - # Load the database file. If it doesn't exist yet, we'll get an - # EnvironmentError with errno set to ENOENT. If it exists but is - # corrupt, we'll get an IOError. In either case, we want to try to - # load the backup file. - fname = os.path.join(self.fullpath(), 'config.pck') - fname_last = fname + '.last' - dict, e = self.__load(fname) - if dict is None and e is not None: - # Try loading the backup file. - dict, e = self.__load(fname_last) - if dict is None and e is not None: - # Both files are corrupt or non-existent. If they're - # corrupt, we want to raise an error. If they're - # non-existent, we want to return an empty dictionary. - if isinstance(e, EnvironmentError) and e.errno == errno.ENOENT: - dict = {} + if not Utils.list_exists(self.internal_name()): + raise Errors.MMUnknownListError + # We first try to load config.pck, which contains the up-to-date + # version of the database. If that fails, perhaps because it's + # corrupted or missing, we'll try to load the backup file + # config.pck.last. + # + # Should both of those fail, we'll look for config.db and + # config.db.last for backwards compatibility with pre-2.1alpha3 + pfile = os.path.join(self.fullpath(), 'config.pck') + plast = pfile + '.last' + dfile = os.path.join(self.fullpath(), 'config.db') + dlast = dfile + '.last' + for file in (pfile, plast, dfile, dlast): + dict_retval, e = self.__load(file) + if dict_retval is None: + if e is not None: + # Had problems with this file; log it and try the next one. + syslog('error', "couldn't load config file %s\n%s", + file, e) + else: + # We already have the most up-to-date state + return else: - raise Errors.MMCorruptListDatabaseError(self.internal_name()) - # Now update our current state with the database state. - for k, v in list(dict.items()): - if k[0] != '_': - setattr(self, k, v) - # Set the timestamp to the current time. - self.__timestamp = os.path.getmtime(fname) - - def CheckVersion(self): - """Check the version of the list's config database. + break + else: + # Nothing worked, so we have to give up + syslog('error', 'All %s fallbacks were corrupt, giving up', + self.internal_name()) + raise Errors.MMCorruptListDatabaseError(e) + # Now, if we didn't end up using the primary database file, we want to + # copy the fallback into the primary so that the logic in Save() will + # still work. For giggles, we'll copy it to a safety backup. Note we + # MUST do this with the underlying list lock acquired. + if file == plast or file == dlast: + syslog('error', 'fixing corrupt config file, using: %s', file) + unlock = True + try: + try: + self.__lock.lock() + except LockFile.AlreadyLockedError: + unlock = False + self.__fix_corrupt_pckfile(file, pfile, plast, dfile, dlast) + finally: + if unlock: + self.__lock.unlock() + # Copy the loaded dictionary into the attributes of the current + # mailing list object, then run sanity check on the data. + self.__dict__.update(dict_retval) + if check_version: + self.CheckVersion(dict_retval) + self.CheckValues() + + def __fix_corrupt_pckfile(self, file, pfile, plast, dfile, dlast): + if file == plast: + # Move aside any existing pickle file and delete any existing + # safety file. This avoids EPERM errors inside the shutil.copy() + # calls if those files exist with different ownership. + try: + os.rename(pfile, pfile + '.corrupt') + except OSError as e: + if e.errno != errno.ENOENT: raise + try: + os.remove(pfile + '.safety') + except OSError as e: + if e.errno != errno.ENOENT: raise + shutil.copy(file, pfile) + shutil.copy(file, pfile + '.safety') + elif file == dlast: + # Move aside any existing marshal file and delete any existing + # safety file. This avoids EPERM errors inside the shutil.copy() + # calls if those files exist with different ownership. + try: + os.rename(dfile, dfile + '.corrupt') + except OSError as e: + if e.errno != errno.ENOENT: raise + try: + os.remove(dfile + '.safety') + except OSError as e: + if e.errno != errno.ENOENT: raise + shutil.copy(file, dfile) + shutil.copy(file, dfile + '.safety') - If the database version is not current, update the database format. - This includes ensuring that pickle files are saved with protocol 4 - for Python 2/3 compatibility. - """ - # Increment this variable when the database format changes. This allows - # for a bit more graceful recovery when upgrading. BAW: This algorithm - # sucks. We really should be using a version number on the class and - # marshalling and unmarshalling based on that. This should be fixed by - # MM3.0. - data_version = getattr(self, 'data_version', 0) - if data_version >= mm_cfg.DATA_FILE_VERSION: - # Even if the data version is current, ensure we're using protocol 4 - # for pickle files by saving the current state - self.Save() + + # + # Sanity checks + # + def CheckVersion(self, stored_state): + """Auto-update schema if necessary.""" + if self.data_version >= mm_cfg.DATA_FILE_VERSION: return - - # Pre-2.1a3 versions did not have a data_version - if data_version == 0: - # First, convert to all lowercase - keys = list(self.__dict__.keys()) - for k in keys: - self.__dict__[k.lower()] = self.__dict__.pop(k) - # Then look for old names and convert - for oldname, newname in (('num_members', 'member_count'), - ('num_digest_members', 'digest_member_count'), - ('closed', 'subscribe_policy'), - ('mlist', 'real_name'), - ('msg_text', 'msg_footer'), - ('msg_headers', 'msg_header'), - ('digest_msg_text', 'digest_footer'), - ('digest_headers', 'digest_header'), - ('posters', 'accept_these_nonmembers'), - ('members_list', 'members'), - ('digest_members_list', 'digest_members'), - ('passwords', 'member_passwords'), - ('bad_posters', 'hold_these_nonmembers'), - ('topics_list', 'topics'), - ('topics_usernames', 'topics_userinterest'), - ('bounce_info', 'bounce_info'), - ('delivery_status', 'delivery_status'), - ('usernames', 'usernames'), - ('sender_filter_bypass', 'accept_these_nonmembers'), - ('admin_member_chunksize', 'admin_member_chunksize'), - ('administrivia', 'administrivia'), - ('advertised', 'advertised'), - ('anonymous_list', 'anonymous_list'), - ('auto_subscribe', 'auto_subscribe'), - ('bounce_matching_headers', 'bounce_matching_headers'), - ('bounce_processing', 'bounce_processing'), - ('convert_html_to_plaintext', 'convert_html_to_plaintext'), - ('digestable', 'digestable'), - ('digest_is_default', 'digest_is_default'), - ('digest_size_threshhold', 'digest_size_threshhold'), - ('filter_content', 'filter_content'), - ('generic_nonmember_action', 'generic_nonmember_action'), - ('include_list_post_header', 'include_list_post_header'), - ('include_rfc2369_headers', 'include_rfc2369_headers'), - ('max_message_size', 'max_message_size'), - ('max_num_recipients', 'max_num_recipients'), - ('member_moderation_notice', 'member_moderation_notice'), - ('mime_is_default_digest', 'mime_is_default_digest'), - ('moderator_password', 'moderator_password'), - ('next_digest_number', 'next_digest_number'), - ('nondigestable', 'nondigestable'), - ('nonmember_rejection_notice', 'nonmember_rejection_notice'), - ('obscure_addresses', 'obscure_addresses'), - ('owner_password', 'owner_password'), - ('post_password', 'post_password'), - ('private_roster', 'private_roster'), - ('real_name', 'real_name'), - ('reject_these_nonmembers', 'reject_these_nonmembers'), - ('reply_goes_to_list', 'reply_goes_to_list'), - ('reply_to_address', 'reply_to_address'), - ('require_explicit_destination', 'require_explicit_destination'), - ('send_reminders', 'send_reminders'), - ('send_welcome_msg', 'send_welcome_msg'), - ('subject_prefix', 'subject_prefix'), - ('topics', 'topics'), - ('topics_enabled', 'topics_enabled'), - ('umbrella_list', 'umbrella_list'), - ('unsubscribe_policy', 'unsubscribe_policy'), - ('volume', 'volume'), - ('web_page_url', 'web_page_url'), - ('welcome_msg', 'welcome_msg'), - ('gateway_to_mail', 'gateway_to_mail'), - ('gateway_to_news', 'gateway_to_news'), - ('linked_newsgroup', 'linked_newsgroup'), - ('nntp_host', 'nntp_host'), - ('news_moderation', 'news_moderation'), - ('news_prefix_subject_too', 'news_prefix_subject_too'), - ('archive', 'archive'), - ('archive_private', 'archive_private'), - ('archive_volume_frequency', 'archive_volume_frequency'), - ('clobber_date', 'clobber_date'), - ('convert_html_to_plaintext', 'convert_html_to_plaintext'), - ('filter_content', 'filter_content'), - ('hold_these_nonmembers', 'hold_these_nonmembers'), - ('linked_newsgroup', 'linked_newsgroup'), - ('max_message_size', 'max_message_size'), - ('max_num_recipients', 'max_num_recipients'), - ('news_prefix_subject_too', 'news_prefix_subject_too'), - ('nntp_host', 'nntp_host'), - ('obscure_addresses', 'obscure_addresses'), - ('private_roster', 'private_roster'), - ('real_name', 'real_name'), - ('subject_prefix', 'subject_prefix'), - ('topics', 'topics'), - ('topics_enabled', 'topics_enabled'), - ('web_page_url', 'web_page_url')): - if oldname in self.__dict__: - self.__dict__[newname] = self.__dict__.pop(oldname) - # Convert the data version number + # Initialize any new variables + self.InitVars() + # Then reload the database (but don't recurse). Force a reload even + # if we have the most up-to-date state. + self.__timestamp = 0 + self.Load(check_version=0) + # We must hold the list lock in order to update the schema + waslocked = self.Locked() + if not waslocked: + self.Lock() + try: + from .versions import Update + Update(self, stored_state) self.data_version = mm_cfg.DATA_FILE_VERSION + self.Save() + finally: + if not waslocked: + self.Unlock() - def GetPattern(self, addr, patterns, at_list=None): - """Check if an address matches any of the patterns in the list. - - Args: - addr: The email address to check - patterns: List of patterns to check against - at_list: Optional name of the list for logging - - Returns: - True if the address matches any pattern, False otherwise - """ - if not patterns: - return False - - # Convert addr to lowercase for case-insensitive matching - addr = addr.lower() - - # Check each pattern - for pattern in patterns: - # Skip empty patterns - if not pattern.strip(): - continue - - # If pattern starts with @, it's a domain pattern - if pattern.startswith('@'): - domain = pattern[1:].lower() - if addr.endswith(domain): - if at_list: - syslog('vette', '%s matches domain pattern %s in %s', - addr, pattern, at_list) - return True - # Otherwise it's a regex pattern - else: + def CheckValues(self): + """Normalize selected values to known formats.""" + if '' in urlparse(self.web_page_url)[:2]: + # Either the "scheme" or the "network location" part of the parsed + # URL is empty; substitute faulty value with (hopefully sane) + # default. Note that DEFAULT_URL is obsolete. + self.web_page_url = ( + mm_cfg.DEFAULT_URL or + mm_cfg.DEFAULT_URL_PATTERN % mm_cfg.DEFAULT_URL_HOST) + if self.web_page_url and self.web_page_url[-1] != '/': + self.web_page_url = self.web_page_url + '/' + # Legacy reply_to_address could be an illegal value. We now verify + # upon setting and don't check it at the point of use. + try: + if self.reply_to_address.strip() and self.reply_goes_to_list: + Utils.ValidateEmail(self.reply_to_address) + except Errors.EmailAddressError: + syslog('error', 'Bad reply_to_address "%s" cleared for list: %s', + self.reply_to_address, self.internal_name()) + self.reply_to_address = '' + self.reply_goes_to_list = 0 + # Legacy topics may have bad regular expressions in their patterns + # Also, someone may have broken topics with, e.g., config_list. + goodtopics = [] + # Check if topics attribute exists before trying to access it + if hasattr(self, 'topics'): + for value in self.topics: try: - cre = re.compile(pattern, re.IGNORECASE) - if cre.search(addr): - if at_list: - syslog('vette', '%s matches regex pattern %s in %s', - addr, pattern, at_list) - return True - except re.error: - syslog('error', 'Invalid regex pattern in %s: %s', - at_list or 'patterns', pattern) + name, pattern, desc, emptyflag = value + except ValueError: + # This value is not a 4-tuple. Just log and drop it. + syslog('error', 'Bad topic "%s" for list: %s', + value, self.internal_name()) continue - - return False - - def HasExplicitDest(self, msg): - """Check if the message has an explicit destination. - - Args: - msg: The email message to check - - Returns: - True if the message has an explicit destination, False otherwise - """ - # Check if the message has a To: or Cc: header - if msg.get('to') or msg.get('cc'): - return True - - # Check if the message has a Resent-To: or Resent-Cc: header - if msg.get('resent-to') or msg.get('resent-cc'): - return True - - # Check if the message has a Delivered-To: header - if msg.get('delivered-to'): - return True - - return False - - def parse_matching_header_opt(self): - """Return a list of triples [(field name, regex, line), ...]. - - Returns: - A list of tuples containing (header name, compiled regex, original line) - """ - # - Blank lines and lines with '#' as first char are skipped. - # - Leading whitespace in the matchexp is trimmed - you can defeat - # that by, eg, containing it in gratuitous square brackets. - all = [] - for line in self.bounce_matching_headers.split('\n'): - line = line.strip() - # Skip blank lines and lines *starting* with a '#'. - if not line or line[0] == "#": - continue - i = line.find(':') - if i < 0: - # This didn't look like a header line - syslog('config', 'bad bounce_matching_header line: %s\n%s', - self.real_name, line) - else: - header = line[:i] - value = line[i+1:].lstrip() try: - cre = re.compile(value, re.IGNORECASE) - except re.error as e: - # The regexp was malformed - syslog('config', '''\ -bad regexp in bounce_matching_header line: %s -\n%s (cause: %s)''', self.real_name, value, e) + orpattern = OR.join(pattern.splitlines()) + re.compile(orpattern) + except (re.error, TypeError): + syslog('error', 'Bad topic pattern "%s" for list: %s', + orpattern, self.internal_name()) else: - all.append((header, cre, line)) - return all + goodtopics.append((name, pattern, desc, emptyflag)) + self.topics = goodtopics - def hasMatchingHeader(self, msg): - """Return true if named header field matches a regexp in the - bounce_matching_header list variable. - - Returns: - The matching line if found, False otherwise + + # + # Membership management front-ends and assertion checks + # + def CheckPending(self, email, unsub=False): + """Check if there is already an unexpired pending (un)subscription for + this email. """ - if not self.bounce_matching_headers: + if not mm_cfg.REFUSE_SECOND_PENDING: return False - - for header, cre, line in self.parse_matching_header_opt(): - for value in msg.get_all(header, []): - if cre.search(value): - syslog('vette', 'Message header %s matches pattern %s', - header, line) - return line + pends = self._Pending__load() + # Save and reload the db to evict expired pendings. + self._Pending__save(pends) + pends = self._Pending__load() + for k, v in list(pends.items()): + if k in ('evictions', 'version'): + continue + op, data = v[:2] + if (op == Pending.SUBSCRIPTION and not unsub and + data.address.lower() == email.lower() or + op == Pending.UNSUBSCRIPTION and unsub and + data.lower() == email.lower()): + return True return False - def _ListAdmin__nextid(self): - """Generate the next unique ID for a held message. - - Returns: - An integer containing the next unique ID - """ - # Get the next ID number - nextid = getattr(self, '_ListAdmin__nextid_counter', 0) + 1 - # Store the next ID number - self._ListAdmin__nextid_counter = nextid - # Return just the counter number - return nextid + def InviteNewMember(self, userdesc, text=''): + """Invite a new member to the list. - def ConfirmUnsubscription(self, addr, lang=None, remote=None): - """Confirm an unsubscription request. - - :param addr: The address to unsubscribe. - :type addr: string - :param lang: The language to use for the confirmation message. - :type lang: string - :param remote: The remote address making the request. - :type remote: string - :raises: MMAlreadyPending if there's already a pending request + This is done by creating a subscription pending for the user, and then + crafting a message to the member informing them of the invitation. """ - # Make sure we have a lock - assert self._locked, 'List must be locked before pending operations' - - # Get the member's language if not specified - if lang is None: - lang = self.getMemberLanguage(addr) - - # Create a pending request - cookie = self.pend_new(Pending.UNSUBSCRIPTION, addr) - - # Craft the confirmation message - d = { - 'listname': self.real_name, - 'email': addr, - 'listaddr': self.GetListEmail(), - 'remote': remote and f'from {remote}' or '', - 'confirmurl': '%s/%s' % (self.GetScriptURL('confirm', absolute=1), cookie), - 'requestaddr': self.GetRequestEmail(cookie), - 'cookie': cookie, - 'listadmin': self.GetOwnerEmail(), - } - - # Send the confirmation message - subject = self.GetConfirmLeaveSubject(self.real_name, cookie) - text = Utils.maketext('unsub.txt', d, lang=lang, mlist=self) - msg = Message.UserNotification(addr, self.GetRequestEmail(cookie), - subject, text, lang) - msg.send(self) - - return cookie - - def InviteNewMember(self, userdesc, text=''): - """Invite a new member to the list.""" invitee = userdesc.address Utils.ValidateEmail(invitee) + # check for banned address pattern = self.GetBannedPattern(invitee) if pattern: syslog('vette', '%s banned invitation: %s (matched: %s)', self.real_name, invitee, pattern) raise Errors.MembershipIsBanned(pattern) + # Hack alert! Squirrel away a flag that only invitations have, so + # that we can do something slightly different when an invitation + # subscription is confirmed. In those cases, we don't need further + # admin approval, even if the list is so configured. The flag is the + # list name to prevent invitees from cross-subscribing. userdesc.invitation = self.internal_name() - cookie = self.pend_new(Pending.SUBSCRIPTION, - (userdesc, None)) + cookie = self.pend_new(Pending.SUBSCRIPTION, userdesc) requestaddr = self.getListAddress('request') - confirmurl = '%s/%s' % (self.GetScriptURL('confirm', absolute=1), cookie) + confirmurl = '%s/%s' % (self.GetScriptURL('confirm', absolute=1), + cookie) listname = self.real_name text += Utils.maketext( 'invite.txt', - {'email': invitee, - 'listname': listname, - 'hostname': self.host_name, - 'confirmurl': confirmurl, + {'email' : invitee, + 'listname' : listname, + 'hostname' : self.host_name, + 'confirmurl' : confirmurl, 'requestaddr': requestaddr, - 'cookie': cookie, - 'listowner': self.GetOwnerEmail(), + 'cookie' : cookie, + 'listowner' : self.GetOwnerEmail(), }, mlist=self) sender = self.GetRequestEmail(cookie) msg = Message.UserNotification( @@ -1205,70 +971,177 @@ def InviteNewMember(self, userdesc, text=''): msg.send(self) def AddMember(self, userdesc, remote=None): - """Add a new member to the list. + """Front end to member subscription. + + This method enforces subscription policy, validates values, sends + notifications, and any other grunt work involved in subscribing a + user. It eventually calls ApprovedAddMember() to do the actual work + of subscribing the user. + + userdesc is an instance with the following public attributes: - Args: - userdesc: A UserDesc object containing the member's information - remote: Optional remote address making the request + address -- the unvalidated email address of the member + fullname -- the member's full name (i.e. John Smith) + digest -- a flag indicating whether the user wants digests or not + language -- the requested default language for the user + password -- the user's password + + Other attributes may be defined later. Only address is required; the + others all have defaults (fullname='', digests=0, language=list's + preferred language, password=generated). + + remote is a string which describes where this add request came from. """ - # Make sure we have a lock - if not self.Locked(): - raise LockFile.NotLockedError( - 'List must be locked before pending operations') - - # Get the member's email address - email = userdesc.address - - # Ensure language is set - if not hasattr(userdesc, 'language') or userdesc.language is None: - userdesc.language = self.preferred_language - - # If we need confirmation, pend the subscription - if self.subscribe_policy in (2, 3) and not self.HasAutoApprovedSender(email): - # Pend the subscription - cookie = self.pend_new(Pending.SUBSCRIPTION, - (userdesc, remote)) + assert self.Locked() + # Suck values out of userdesc, apply defaults, and reset the userdesc + # attributes (for passing on to ApprovedAddMember()). Lowercase the + # addr's domain part. + email = Utils.LCDomain(userdesc.address) + name = getattr(userdesc, 'fullname', '') + lang = getattr(userdesc, 'language', self.preferred_language) + digest = getattr(userdesc, 'digest', None) + password = getattr(userdesc, 'password', Utils.MakeRandomPassword()) + if digest is None: + if self.nondigestable: + digest = 0 + else: + digest = 1 + # Validate the e-mail address to some degree. + Utils.ValidateEmail(email) + if self.isMember(email): + raise Errors.MMAlreadyAMember(email) + if self.CheckPending(email): + raise Errors.MMAlreadyPending(email) + if email.lower() == self.GetListEmail().lower(): + # Trying to subscribe the list to itself! + raise Errors.MMBadEmailError + realname = self.real_name + # Is the subscribing address banned from this list? + pattern = self.GetBannedPattern(email) + if pattern: + if remote: + whence = ' from %s' % remote + else: + whence = '' + syslog('vette', '%s banned subscription: %s%s (matched: %s)', + realname, email, whence, pattern) + raise Errors.MembershipIsBanned(pattern) + # See if this is from a spamhaus listed IP. + if remote and mm_cfg.BLOCK_SPAMHAUS_LISTED_IP_SUBSCRIBE: + if Utils.banned_ip(remote): + whence = ' from %s' % remote + syslog('vette', '%s banned subscription: %s%s (Spamhaus IP)', + realname, email, whence) + raise Errors.MembershipIsBanned('Spamhaus IP') + # See if this is from a spamhaus listed domain. + if email and mm_cfg.BLOCK_SPAMHAUS_LISTED_DBL_SUBSCRIBE: + if Utils.banned_domain(email): + syslog('vette', '%s banned subscription: %s (Spamhaus DBL)', + realname, email) + raise Errors.MembershipIsBanned('Spamhaus DBL') + # Sanity check the digest flag + if digest and not self.digestable: + raise Errors.MMCantDigestError + elif not digest and not self.nondigestable: + raise Errors.MMMustDigestError + + userdesc.address = email + userdesc.fullname = name + userdesc.digest = digest + userdesc.language = lang + userdesc.password = password + + # Apply the list's subscription policy. 0 means open subscriptions; 1 + # means the user must confirm; 2 means the admin must approve; 3 means + # the user must confirm and then the admin must approve + if self.subscribe_policy == 0: + self.ApprovedAddMember(userdesc, whence=remote or '') + elif self.subscribe_policy == 1 or self.subscribe_policy == 3: + # User confirmation required. BAW: this should probably just + # accept a userdesc instance. + cookie = self.pend_new(Pending.SUBSCRIPTION, userdesc) + # Send the user the confirmation mailback + if remote is None: + oremote = by = remote = '' + else: + oremote = remote + by = ' ' + remote + remote = _(' from %(remote)s') + + recipient = self.GetMemberAdminEmail(email) confirmurl = '%s/%s' % (self.GetScriptURL('confirm', absolute=1), cookie) - lang = userdesc.language text = Utils.maketext( 'verify.txt', - {'email' : email, - 'listaddr' : self.GetListEmail(), - 'listname' : self.real_name, - 'cookie' : cookie, - 'requestaddr': self.getListAddress('request'), - 'remote' : remote or '', - 'listadmin' : self.GetOwnerEmail(), - 'confirmurl' : confirmurl, + {'email' : email, + 'listaddr' : self.GetListEmail(), + 'listname' : realname, + 'cookie' : cookie, + 'requestaddr' : self.getListAddress('request'), + 'remote' : remote, + 'listadmin' : self.GetOwnerEmail(), + 'confirmurl' : confirmurl, }, lang=lang, mlist=self) - # BAW: We don't pass the Subject: into the UserNotification - # constructor because it will encode it in the charset of the language - # being used. For non-us-ascii charsets, this means it will probably - # quopri quote it, and thus replies will also be quopri encoded. But - # CommandRunner doesn't yet grok such headers. So, just set the - # Subject: in a separate step, although we have to delete the one - # UserNotification adds. msg = Message.UserNotification( - email, self.GetRequestEmail(cookie), + recipient, self.GetRequestEmail(cookie), text=text, lang=lang) + # BAW: See ChangeMemberAddress() for why we do it this way... del msg['subject'] - msg['Subject'] = self.GetConfirmJoinSubject(self.real_name, cookie) + msg['Subject'] = self.GetConfirmJoinSubject(realname, cookie) msg['Reply-To'] = self.GetRequestEmail(cookie) + # Is this confirmation a reply to an email subscribe from this + # address? + if oremote.lower().endswith(email.lower()): + autosub = 'auto-replied' + else: + autosub = 'auto-generated' + del msg['auto-submitted'] + msg['Auto-Submitted'] = autosub msg.send(self) - return - # If we get here, we can add the member directly - self.ApprovedAddMember(userdesc, whence=remote or '') + # formataddr() expects a str and does its own encoding + if isinstance(name, bytes): + name = name.decode(Utils.GetCharSet(lang)) + + who = formataddr((name, email)) + syslog('subscribe', '%s: pending %s %s', + self.internal_name(), who, by) + raise Errors.MMSubscribeNeedsConfirmation + elif self.HasAutoApprovedSender(email): + # no approval necessary: + self.ApprovedAddMember(userdesc) + else: + # Subscription approval is required. Add this entry to the admin + # requests database. BAW: this should probably take a userdesc + # just like above. + self.HoldSubscription(email, name, password, digest, lang) + raise Errors.MMNeedApproval( + 'subscriptions to %(realname)s require moderator approval') def ApprovedAddMember(self, userdesc, ack=None, admin_notif=None, text='', whence=''): - """Add a member right now.""" + """Add a member right now. + + The member's subscription must be approved by what ever policy the + list enforces. + + userdesc is as above in AddMember(). + + ack is a flag that specifies whether the user should get an + acknowledgement of their being subscribed. Default is to use the + list's default flag value. + + admin_notif is a flag that specifies whether the list owner should get + an acknowledgement of this subscription. Default is to use the list's + default flag value. + """ assert self.Locked() + # Set up default flag values if ack is None: ack = self.send_welcome_msg if admin_notif is None: admin_notif = self.admin_notify_mchanges + # Suck values out of userdesc, and apply defaults. email = Utils.LCDomain(userdesc.address) name = getattr(userdesc, 'fullname', '') lang = getattr(userdesc, 'language', self.preferred_language) @@ -1279,55 +1152,66 @@ def ApprovedAddMember(self, userdesc, ack=None, admin_notif=None, text='', digest = 0 else: digest = 1 + # Let's be extra cautious Utils.ValidateEmail(email) if self.isMember(email): raise Errors.MMAlreadyAMember(email) + # Check for banned address here too for admin mass subscribes + # and confirmations. pattern = self.GetBannedPattern(email) if pattern: - source = f' from {whence}' if whence else '' + if whence: + source = ' from %s' % whence + else: + source = '' syslog('vette', '%s banned subscription: %s%s (matched: %s)', self.real_name, email, source, pattern) raise Errors.MembershipIsBanned(pattern) + # Do the actual addition self.addNewMember(email, realname=name, digest=digest, password=password, language=lang) self.setMemberOption(email, mm_cfg.DisableMime, 1 - self.mime_is_default_digest) self.setMemberOption(email, mm_cfg.Moderate, self.default_member_moderation) - kind = ' (digest)' if digest else '' - - # Handle name encoding properly + # Now send and log results + if digest: + kind = ' (digest)' + else: + kind = '' + + # The formataddr() function, used in two places below, takes a str and performs + # its own encoding, so we should not allow the name to be pre-encoded. if isinstance(name, bytes): - try: - # Try to decode using the member's language charset - charset = Utils.GetCharSet(lang) - name = name.decode(charset, 'replace') - except (UnicodeDecodeError, LookupError): - # Fall back to latin-1 if the charset is not available - name = name.decode('latin-1', 'replace') - elif not isinstance(name, str): - name = str(name) - + name = name.decode(Utils.GetCharSet(lang)) + syslog('subscribe', '%s: new%s %s, %s', self.internal_name(), kind, formataddr((name, email)), whence) if ack: - self.SendSubscribeAck(email, self.getMemberPassword(email), - digest, text) + lang = self.preferred_language + otrans = i18n.get_translation() + i18n.set_language(lang) + try: + self.SendSubscribeAck(email, self.getMemberPassword(email), + digest, text) + finally: + i18n.set_translation(otrans) if admin_notif: lang = self.preferred_language otrans = i18n.get_translation() i18n.set_language(lang) try: - whence_str = "" if whence is None else f"({_(whence)})" + whence = "" if whence is None else "(" + _(whence) + ")" realname = self.real_name - subject = _('%(realname)s subscription notification') % {'realname': realname} + subject = _('%(realname)s subscription notification') finally: i18n.set_translation(otrans) + text = Utils.maketext( "adminsubscribeack.txt", - {"listname": realname, - "member": formataddr((name, email)), - "whence": whence_str + {"listname" : realname, + "member" : formataddr((name, email)), + "whence" : whence }, mlist=self) msg = Message.OwnerNotification(self, subject, text) msg.send(self) @@ -1340,40 +1224,46 @@ def DeleteMember(self, name, whence=None, admin_notif=None, userack=True): self.HoldUnsubscription(email) raise Errors.MMNeedApproval('unsubscriptions require moderator approval') - def ApprovedDeleteMember(self, name, whence=None, admin_notif=None, userack=None): + def ApprovedDeleteMember(self, name, whence=None, + admin_notif=None, userack=None): if userack is None: userack = self.send_goodbye_msg if admin_notif is None: admin_notif = self.admin_notify_mchanges + # Delete a member, for which we know the approval has been made fullname, emailaddr = parseaddr(name) userlang = self.getMemberLanguage(emailaddr) + # Remove the member self.removeMember(emailaddr) + # And send an acknowledgement to the user... if userack: self.SendUnsubscribeAck(emailaddr, userlang) + # ...and to the administrator in the correct language. (LP: #1308655) i18n.set_language(self.preferred_language) if admin_notif: realname = self.real_name - subject = _('%(realname)s unsubscribe notification') % {'realname': realname} + subject = _('%(realname)s unsubscribe notification') text = Utils.maketext( 'adminunsubscribeack.txt', - {'member': name, + {'member' : name, 'listname': self.real_name, - "whence": "" if whence is None else f"({_(whence)})" + "whence" : "" if whence is None else "(" + _(whence) + ")" }, mlist=self) msg = Message.OwnerNotification(self, subject, text) msg.send(self) if whence: - whence_str = f'; {whence}' + whence = "; %s" % whence else: - whence_str = '' + whence = "" syslog('subscribe', '%s: deleted %s%s', - self.internal_name(), name, whence_str) + self.internal_name(), name, whence) def ChangeMemberName(self, addr, name, globally): self.setMemberName(addr, name) if not globally: return for listname in Utils.list_names(): + # Don't bother with ourselves if listname == self.internal_name(): continue mlist = MailList(listname, lock=0) @@ -1389,20 +1279,32 @@ def ChangeMemberName(self, addr, name, globally): mlist.Unlock() def ChangeMemberAddress(self, oldaddr, newaddr, globally): + # Changing a member address consists of verifying the new address, + # making sure the new address isn't already a member, and optionally + # going through the confirmation process. + # + # Most of these checks are copied from AddMember newaddr = Utils.LCDomain(newaddr) Utils.ValidateEmail(newaddr) + # Raise an exception if this email address is already a member of the + # list, but only if the new address is the same case-wise as the + # existing member address and we're not doing a global change. if not globally and (self.isMember(newaddr) and newaddr == self.getMemberCPAddress(newaddr)): raise Errors.MMAlreadyAMember if newaddr == self.GetListEmail().lower(): raise Errors.MMBadEmailError realname = self.real_name + # Don't allow changing to a banned address. MAS: maybe we should + # unsubscribe the oldaddr too just for trying, but that's probably + # too harsh. pattern = self.GetBannedPattern(newaddr) if pattern: syslog('vette', '%s banned address change: %s -> %s (matched: %s)', realname, oldaddr, newaddr, pattern) raise Errors.MembershipIsBanned(pattern) + # Pend the subscription change cookie = self.pend_new(Pending.CHANGE_OF_ADDRESS, oldaddr, newaddr, globally) confirmurl = '%s/%s' % (self.GetScriptURL('confirm', absolute=1), @@ -1410,15 +1312,22 @@ def ChangeMemberAddress(self, oldaddr, newaddr, globally): lang = self.getMemberLanguage(oldaddr) text = Utils.maketext( 'verify.txt', - {'email': newaddr, - 'listaddr': self.GetListEmail(), - 'listname': realname, - 'cookie': cookie, + {'email' : newaddr, + 'listaddr' : self.GetListEmail(), + 'listname' : realname, + 'cookie' : cookie, 'requestaddr': self.getListAddress('request'), - 'remote': '', - 'listadmin': self.GetOwnerEmail(), - 'confirmurl': confirmurl, + 'remote' : '', + 'listadmin' : self.GetOwnerEmail(), + 'confirmurl' : confirmurl, }, lang=lang, mlist=self) + # BAW: We don't pass the Subject: into the UserNotification + # constructor because it will encode it in the charset of the language + # being used. For non-us-ascii charsets, this means it will probably + # quopri quote it, and thus replies will also be quopri encoded. But + # CommandRunner doesn't yet grok such headers. So, just set the + # Subject: in a separate step, although we have to delete the one + # UserNotification adds. msg = Message.UserNotification( newaddr, self.GetRequestEmail(cookie), text=text, lang=lang) @@ -1428,22 +1337,38 @@ def ChangeMemberAddress(self, oldaddr, newaddr, globally): msg.send(self) def ApprovedChangeMemberAddress(self, oldaddr, newaddr, globally): + # Check here for banned address in case address was banned after + # confirmation was mailed. MAS: If it's global change should we just + # skip this list and proceed to the others? For now we'll throw the + # exception. pattern = self.GetBannedPattern(newaddr) if pattern: syslog('vette', '%s banned address change: %s -> %s (matched: %s)', self.real_name, oldaddr, newaddr, pattern) raise Errors.MembershipIsBanned(pattern) + # It's possible they were a member of this list, but choose to change + # their membership globally. In that case, we simply remove the old + # address. This gets tricky with case changes. We can't just remove + # the old address if it differs from the new only by case, because + # that removes the new, so the condition is if the new address is the + # CP address of a member, then if the old address yields a different + # CP address, we can simply remove the old address, otherwise we can + # do nothing. cpoldaddr = self.getMemberCPAddress(oldaddr) - if self.isMember(newaddr) and (self.getMemberCPAddress(newaddr) == newaddr): + if self.isMember(newaddr) and (self.getMemberCPAddress(newaddr) == + newaddr): if cpoldaddr != newaddr: self.removeMember(oldaddr) else: self.changeMemberAddress(oldaddr, newaddr) self.log_and_notify_admin(cpoldaddr, newaddr) + # If globally is true, then we also include every list for which + # oldaddr is a member. if not globally: return for listname in Utils.list_names(): + # Don't bother with ourselves if listname == self.internal_name(): continue mlist = MailList(listname, lock=0) @@ -1451,75 +1376,476 @@ def ApprovedChangeMemberAddress(self, oldaddr, newaddr, globally): continue if not mlist.isMember(oldaddr): continue + # If new address is banned from this list, just skip it. if mlist.GetBannedPattern(newaddr): continue mlist.Lock() try: - mlist.ApprovedChangeMemberAddress(oldaddr, newaddr, False) + # Same logic as above, re newaddr is already a member + cpoldaddr = mlist.getMemberCPAddress(oldaddr) + if mlist.isMember(newaddr) and ( + mlist.getMemberCPAddress(newaddr) == newaddr): + if cpoldaddr != newaddr: + mlist.removeMember(oldaddr) + else: + mlist.changeMemberAddress(oldaddr, newaddr) + mlist.log_and_notify_admin(cpoldaddr, newaddr) mlist.Save() finally: mlist.Unlock() def log_and_notify_admin(self, oldaddr, newaddr): - syslog('subscribe', '%s: changed address %s -> %s', + """Log member address change and notify admin if requested.""" + syslog('subscribe', '%s: changed member address from %s to %s', self.internal_name(), oldaddr, newaddr) + if self.admin_notify_mchanges: + lang = self.preferred_language + otrans = i18n.get_translation() + i18n.set_language(lang) + try: + realname = self.real_name + subject = _('%(realname)s address change notification') + finally: + i18n.set_translation(otrans) + name = self.getMemberName(newaddr) + if name is None: + name = '' + if isinstance(name, str): + name = name.encode(Utils.GetCharSet(lang), 'replace') + text = Utils.maketext( + 'adminaddrchgack.txt', + {'name' : name, + 'oldaddr' : oldaddr, + 'newaddr' : newaddr, + 'listname': self.real_name, + }, mlist=self) + msg = Message.OwnerNotification(self, subject, text) + msg.send(self) - def CheckPending(self, email, unsub=False): - """Check if there is already an unexpired pending (un)subscription for - this email. + + # + # Confirmation processing + # + def ProcessConfirmation(self, cookie, context=None): + global _ + rec = self.pend_confirm(cookie) + if rec is None: + raise Errors.MMBadConfirmation('No cookie record for %s' % cookie) + try: + op = rec[0] + data = rec[1:] + except ValueError: + raise Errors.MMBadConfirmation('op-less data %s' % (rec,)) + if op == Pending.SUBSCRIPTION: + _ = D_ + whence = _('via email confirmation') + try: + userdesc = data[0] + # If confirmation comes from the web, context should be a + # UserDesc instance which contains overrides of the original + # subscription information. If it comes from email, then + # context is a Message and isn't relevant, so ignore it. + if isinstance(context, UserDesc): + userdesc += context + whence = _('via web confirmation') + addr = userdesc.address + fullname = userdesc.fullname + password = userdesc.password + digest = userdesc.digest + lang = userdesc.language + except ValueError: + raise Errors.MMBadConfirmation('bad subscr data %s' % (data,)) + _ = i18n._ + # Hack alert! Was this a confirmation of an invitation? + invitation = getattr(userdesc, 'invitation', False) + # We check for both 2 (approval required) and 3 (confirm + + # approval) because the policy could have been changed in the + # middle of the confirmation dance. + if invitation: + if invitation != self.internal_name(): + # Not cool. The invitee was trying to subscribe to a + # different list than they were invited to. Alert both + # list administrators. + self.SendHostileSubscriptionNotice(invitation, addr) + raise Errors.HostileSubscriptionError + elif self.subscribe_policy in (2, 3) and \ + not self.HasAutoApprovedSender(addr): + self.HoldSubscription(addr, fullname, password, digest, lang) + name = self.real_name + raise Errors.MMNeedApproval( + 'subscriptions to %(name)s require administrator approval') + self.ApprovedAddMember(userdesc, whence=whence) + return op, addr, password, digest, lang + elif op == Pending.UNSUBSCRIPTION: + addr = data[0] + # Log file messages don't need to be i18n'd, but this is now in a + # notice. + _ = D_ + if isinstance(context, Message.Message): + whence = _('email confirmation') + else: + whence = _('web confirmation') + _ = i18n._ + # Can raise NotAMemberError if they unsub'd via other means + self.ApprovedDeleteMember(addr, whence=whence) + return op, addr + elif op == Pending.CHANGE_OF_ADDRESS: + oldaddr, newaddr, globally = data + self.ApprovedChangeMemberAddress(oldaddr, newaddr, globally) + return op, oldaddr, newaddr + elif op == Pending.HELD_MESSAGE: + id = data[0] + approved = None + # Confirmation should be coming from email, where context should + # be the confirming message. If the message does not have an + # Approved: header, this is a discard. If it has an Approved: + # header that does not match the list password, then we'll notify + # the list administrator that they used the wrong password. + # Otherwise it's an approval. + if isinstance(context, Message.Message): + # See if it's got an Approved: header, either in the headers, + # or in the first text/plain section of the response. For + # robustness, we'll accept Approve: as well. + approved = context.get('Approved', context.get('Approve')) + if not approved: + try: + subpart = list(email.iterators.typed_subpart_iterator( + context, 'text', 'plain'))[0] + except IndexError: + subpart = None + if subpart: + s = StringIO(subpart.get_payload(decode=True)) + while True: + line = s.readline() + if not line: + break + if not line.strip(): + continue + i = line.find(':') + if i > 0: + if (line[:i].strip().lower() == 'approve' or + line[:i].strip().lower() == 'approved'): + # then + approved = line[i+1:].strip() + break + # Is there an approved header? + if approved is not None: + # Does it match the list password? Note that we purposefully + # do not allow the site password here. + if self.Authenticate([mm_cfg.AuthListAdmin, + mm_cfg.AuthListModerator], + approved) != mm_cfg.UnAuthorized: + action = mm_cfg.APPROVE + else: + # The password didn't match. Re-pend the message and + # inform the list moderators about the problem. + self.pend_repend(cookie, rec) + raise Errors.MMBadPasswordError + else: + action = mm_cfg.DISCARD + try: + self.HandleRequest(id, action) + except KeyError: + # Most likely because the message has already been disposed of + # via the admindb page. + syslog('error', 'Could not process HELD_MESSAGE: %s', id) + return op, action + elif op == Pending.RE_ENABLE: + member = data[1] + self.setDeliveryStatus(member, MemberAdaptor.ENABLED) + return op, member + else: + assert 0, 'Bad op: %s' % op + + def ConfirmUnsubscription(self, addr, lang=None, remote=None): + if self.CheckPending(addr, unsub=True): + raise Errors.MMAlreadyPending(email) + if lang is None: + lang = self.getMemberLanguage(addr) + cookie = self.pend_new(Pending.UNSUBSCRIPTION, addr) + confirmurl = '%s/%s' % (self.GetScriptURL('confirm', absolute=1), + cookie) + realname = self.real_name + if remote is not None: + by = " " + remote + remote = _(" from %(remote)s") + else: + by = "" + remote = "" + text = Utils.maketext( + 'unsub.txt', + {'email' : addr, + 'listaddr' : self.GetListEmail(), + 'listname' : realname, + 'cookie' : cookie, + 'requestaddr' : self.getListAddress('request'), + 'remote' : remote, + 'listadmin' : self.GetOwnerEmail(), + 'confirmurl' : confirmurl, + }, lang=lang, mlist=self) + msg = Message.UserNotification( + addr, self.GetRequestEmail(cookie), + text=text, lang=lang) + # BAW: See ChangeMemberAddress() for why we do it this way... + del msg['subject'] + msg['Subject'] = self.GetConfirmLeaveSubject(realname, cookie) + msg['Reply-To'] = self.GetRequestEmail(cookie) + del msg['auto-submitted'] + msg['Auto-Submitted'] = 'auto-generated' + msg.send(self) + + + # + # Miscellaneous stuff + # + def HasExplicitDest(self, msg): + """True if list name or any acceptable_alias is included among the + addresses in the recipient headers. """ - if not mm_cfg.REFUSE_SECOND_PENDING: - return False - pends = self._Pending__load() - # Save and reload the db to evict expired pendings. - self._Pending__save(pends) - pends = self._Pending__load() - for k, v in list(pends.items()): - if k in ('evictions', 'version'): + # This is the list's full address. + listfullname = '%s@%s' % (self.internal_name(), self.host_name) + recips = [] + # Check all recipient addresses against the list's explicit addresses, + # specifically To: Cc: and Resent-to: + to = [] + for header in ('to', 'cc', 'resent-to', 'resent-cc'): + to.extend(getaddresses(msg.get_all(header, []))) + for fullname, addr in to: + # It's possible that if the header doesn't have a valid RFC 2822 + # value, we'll get None for the address. So skip it. + if addr is None: continue - op, data = v[:2] - if (op == Pending.SUBSCRIPTION and not unsub and - data.address.lower() == email.lower() or - op == Pending.UNSUBSCRIPTION and unsub and - data.lower() == email.lower()): + addr = addr.lower() + localpart = addr.split('@')[0] + if (# TBD: backwards compatibility: deprecated + localpart == self.internal_name() or + # exact match against the complete list address + addr == listfullname): return True + recips.append((addr, localpart)) + # Helper function used to match a pattern against an address. + def domatch(pattern, addr): + try: + if re.match(pattern, addr, re.IGNORECASE): + return True + except re.error: + # The pattern is a malformed regexp -- try matching safely, + # with all non-alphanumerics backslashed: + if re.match(re.escape(pattern), addr, re.IGNORECASE): + return True + return False + # Here's the current algorithm for matching acceptable_aliases: + # + # 1. If the pattern does not have an `@' in it, we first try matching + # it against just the localpart. This was the behavior prior to + # 2.0beta3, and is kept for backwards compatibility. (deprecated). + # + # 2. If that match fails, or the pattern does have an `@' in it, we + # try matching against the entire recip address. + aliases = self.acceptable_aliases.splitlines() + for addr, localpart in recips: + for alias in aliases: + stripped = alias.strip() + if not stripped: + # Ignore blank or empty lines + continue + if '@' not in stripped and domatch(stripped, localpart): + return True + if domatch(stripped, addr): + return True return False - def GetBannedPattern(self, email): - """Check if the email address matches any banned patterns. - - Args: - email: The email address to check - - Returns: - The matching pattern if found, None otherwise - """ - if not self.ban_list: - return None - - # Convert email to lowercase for case-insensitive matching - email = email.lower() - - # Check each pattern in the ban list - for pattern in self.ban_list: - # Skip empty patterns - if not pattern.strip(): + def parse_matching_header_opt(self): + """Return a list of triples [(field name, regex, line), ...].""" + # - Blank lines and lines with '#' as first char are skipped. + # - Leading whitespace in the matchexp is trimmed - you can defeat + # that by, eg, containing it in gratuitous square brackets. + all = [] + for line in self.bounce_matching_headers.split('\n'): + line = line.strip() + # Skip blank lines and lines *starting* with a '#'. + if not line or line[0] == "#": continue - - # If pattern starts with @, it's a domain pattern - if pattern.startswith('@'): - domain = pattern[1:].lower() - if email.endswith(domain): - return pattern - # Otherwise it's a regex pattern + i = line.find(':') + if i < 0: + # This didn't look like a header line. BAW: should do a + # better job of informing the list admin. + syslog('config', 'bad bounce_matching_header line: %s\n%s', + self.real_name, line) else: + header = line[:i] + value = line[i+1:].lstrip() try: - cre = re.compile(pattern, re.IGNORECASE) - if cre.search(email): - return pattern - except re.error: - syslog('error', 'Invalid regex pattern in ban_list: %s', - pattern) + cre = re.compile(value, re.IGNORECASE) + except re.error as e: + # The regexp was malformed. BAW: should do a better + # job of informing the list admin. + syslog('config', '''\ +bad regexp in bounce_matching_header line: %s +\n%s (cause: %s)''', self.real_name, value, e) + else: + all.append((header, cre, line)) + return all + + def hasMatchingHeader(self, msg): + """Return true if named header field matches a regexp in the + bounce_matching_header list variable. + + Returns constraint line which matches or empty string for no + matches. + """ + for header, cre, line in self.parse_matching_header_opt(): + for value in msg.get_all(header, []): + if cre.search(value): + return line + return 0 + + def autorespondToSender(self, sender, lang=None): + """Return true if Mailman should auto-respond to this sender. + + This is only consulted for messages sent to the -request address, or + for posting hold notifications, and serves only as a safety value for + mail loops with email 'bots. + """ + # language setting + if lang == None: + lang = self.preferred_language + i18n.set_language(lang) + # No limit + if mm_cfg.MAX_AUTORESPONSES_PER_DAY == 0: + return 1 + today = time.localtime()[:3] + info = self.hold_and_cmd_autoresponses.get(sender) + if info is None or info[0] != today: + # First time we've seen a -request/post-hold for this sender + self.hold_and_cmd_autoresponses[sender] = (today, 1) + # BAW: no check for MAX_AUTORESPONSES_PER_DAY <= 1 + return 1 + date, count = info + if count < 0: + # They've already hit the limit for today. + syslog('vette', '-request/hold autoresponse discarded for: %s', + sender) + return 0 + if count >= mm_cfg.MAX_AUTORESPONSES_PER_DAY: + syslog('vette', '-request/hold autoresponse limit hit for: %s', + sender) + self.hold_and_cmd_autoresponses[sender] = (today, -1) + # Send this notification message instead + text = Utils.maketext( + 'nomoretoday.txt', + {'sender' : sender, + 'listname': '%s@%s' % (self.real_name, self.host_name), + 'num' : count, + 'owneremail': self.GetOwnerEmail(), + }, + lang=lang) + msg = Message.UserNotification( + sender, self.GetOwnerEmail(), + _('Last autoresponse notification for today'), + text, lang=lang) + msg.send(self) + return 0 + self.hold_and_cmd_autoresponses[sender] = (today, count+1) + return 1 + + def GetBannedPattern(self, email): + """Returns matched entry in ban_list if email matches. + Otherwise returns None. + """ + return (self.GetPattern(email, self.ban_list) or + self.GetPattern(email, mm_cfg.GLOBAL_BAN_LIST) + ) + + def HasAutoApprovedSender(self, sender): + """Returns True and logs if sender matches address or pattern + or is a member of a referenced list in subscribe_auto_approval. + Otherwise returns False. + """ + auto_approve = False + if self.GetPattern(sender, + self.subscribe_auto_approval, + at_list='subscribe_auto_approval' + ): + auto_approve = True + syslog('vette', '%s: auto approved subscribe from %s', + self.internal_name(), sender) + return auto_approve + + def GetPattern(self, email, pattern_list, at_list=None): + """Returns matched entry in pattern_list if email matches. + Otherwise returns None. The at_list argument, if "true", + says process the @listname syntax and provides the name of + the list attribute for log messages. + """ + matched = None + # First strip out all the regular expressions and listnames because + # documentation says we do non-regexp first (Why?). + plainaddrs = [x.strip() for x in pattern_list if x.strip() and not + (x.startswith('^') or x.startswith('@'))] + addrdict = Utils.List2Dict(plainaddrs, foldcase=1) + if email.lower() in addrdict: + return email + for pattern in pattern_list: + if pattern.startswith('^'): + # This is a regular expression match + try: + if re.search(pattern, email, re.IGNORECASE): + matched = pattern + break + except re.error as e: + # BAW: we should probably remove this pattern + # The GUI won't add a bad regexp, but at least log it. + # The following kludge works because the ban_list stuff + # is the only caller with no at_list. + attr_name = at_list or 'ban_list' + syslog('error', + '%s in %s has bad regexp "%s": %s', + attr_name, + self.internal_name(), + pattern, + str(e) + ) + elif at_list and pattern.startswith('@'): + # XXX Needs to be reviewed for list@domain names. + # this refers to the members of another list in this + # installation. + mname = pattern[1:].lower().strip() + if mname == self.internal_name(): + # don't reference your own list + syslog('error', + '%s in %s references own list', + at_list, + self.internal_name()) continue - - return None + try: + mother = MailList(mname, lock = False) + except Errors.MMUnknownListError: + syslog('error', + '%s in %s references non-existent list %s', + at_list, + self.internal_name(), + mname + ) + continue + if mother.isMember(email.lower()): + matched = pattern + break + return matched + + + + # + # Multilingual (i18n) support + # + def GetAvailableLanguages(self): + langs = self.available_languages + # If we don't add this, and the site admin has never added any + # language support to the list, then the general admin page may have a + # blank field where the list owner is supposed to chose the list's + # preferred language. + if mm_cfg.DEFAULT_SERVER_LANGUAGE not in langs: + langs.append(mm_cfg.DEFAULT_SERVER_LANGUAGE) + # When testing, it's possible we've disabled a language, so just + # filter things out so we don't get tracebacks. + return [lang for lang in langs if lang in mm_cfg.LC_DESCRIPTIONS] diff --git a/Mailman/Mailbox.py b/Mailman/Mailbox.py index d428d8be..782bb84d 100644 --- a/Mailman/Mailbox.py +++ b/Mailman/Mailbox.py @@ -20,76 +20,58 @@ import sys import mailbox -from io import StringIO, BytesIO -from types import MethodType import email -import email.message -from email.message import Message from email.parser import Parser from email.errors import MessageParseError from email.generator import Generator from Mailman import mm_cfg from Mailman.Message import Message +from Mailman import Utils + def _safeparser(fp): try: - return email.message_from_file(fp, Mailman.Message.Message) + return email.message_from_binary_file(fp, Message) except MessageParseError: # Don't return None since that will stop a mailbox iterator return '' + + class Mailbox(mailbox.mbox): def __init__(self, fp): - # In Python 3, we need to handle both file objects and paths - if hasattr(fp, 'read') and hasattr(fp, 'write'): - # It's a file object, get its path - if hasattr(fp, 'name'): - path = fp.name - else: - # Create a temporary file if we don't have a path - import tempfile - path = tempfile.mktemp() - with open(path, 'w', encoding='utf-8') as f: - f.write(fp.read().decode('utf-8', 'replace')) - fp.seek(0) - else: - # It's a path string - path = fp - - # Initialize the parent class with the path - super().__init__(path, _safeparser) - # Store the file object if we have one - if hasattr(fp, 'read') and hasattr(fp, 'write'): - self.fp = fp - else: - # Open in text mode for writing - self.fp = open(path, 'a+', encoding='utf-8') + if not isinstance( fp, str ): + fp = fp.name + self.filepath = fp + mailbox.mbox.__init__(self, fp, _safeparser) # msg should be an rfc822 message or a subclass. def AppendMessage(self, msg): # Check the last character of the file and write a newline if it isn't # a newline (but not at the beginning of an empty file). - try: - self.fp.seek(-1, 2) - except IOError as e: - # Assume the file is empty. We can't portably test the error code - # returned, since it differs per platform. - pass - else: - if self.fp.read(1) != '\n': - self.fp.write('\n') - # Seek to the last char of the mailbox - self.fp.seek(0, 2) - - # Create a Generator instance to write the message to the file - g = Generator(self.fp, mangle_from_=False, maxheaderlen=0) - g.flatten(msg, unixfrom=True) - # Add one more trailing newline for separation with the next message - self.fp.write('\n') - + with open(self.filepath, 'r+') as fileh: + try: + fileh.seek(-1, 2) + except IOError as e: + # Assume the file is empty. We can't portably test the error code + # returned, since it differs per platform. + pass + else: + if fileh.read(1) != '\n': + fileh.write('\n') + # Seek to the last char of the mailbox + fileh.seek(0, 2) + # Create a Generator instance to write the message to the file + g = Generator(fileh) + Utils.set_cte_if_missing(msg) + g.flatten(msg, unixfrom=True) + # Add one more trailing newline for separation with the next message + # to be appended to the mbox. + print('\n', fileh) + # This stuff is used by pipermail.py:processUnixMailbox(). It provides an # opportunity for the built-in archiver to scrub archived messages of nasty # things like attachments and such... @@ -119,7 +101,9 @@ def __init__(self, fp, mlist): else: self._scrubber = None self._mlist = mlist - mailbox.PortableUnixMailbox.__init__(self, fp, _archfactory(self)) + if not isinstance(fp, str): + fp = fp.name + mailbox.mbox.__init__(self, fp, _archfactory(self)) def scrub(self, msg): if self._scrubber: diff --git a/Mailman/Message.py b/Mailman/Message.py index 6f3b1b63..d75e5a52 100644 --- a/Mailman/Message.py +++ b/Mailman/Message.py @@ -21,32 +21,28 @@ which is more convenient for use inside Mailman. """ -from builtins import object import re from io import StringIO -import time -import hashlib import email import email.generator import email.utils from email.charset import Charset from email.header import Header -from email.message import Message as EmailMessage from Mailman import mm_cfg -from Mailman.Utils import GetCharSet, unique_message_id, get_site_email -from Mailman.Logging.Syslog import mailman_log +from Mailman import Utils COMMASPACE = ', ' if hasattr(email, '__version__'): mo = re.match(r'([\d.]+)', email.__version__) else: - mo = re.match(r'([\d.]+)', '2.1.39') # XXX should use @@MM_VERSION@@ perhaps? + mo = re.match(r'([\d.]+)', '2.2.0') # XXX should use @@MM_VERSION@@ perhaps? VERSION = tuple([int(s) for s in mo.group().split('.')]) + class Generator(email.generator.Generator): """Generates output from a Message object tree, keeping signatures. @@ -64,14 +60,17 @@ def clone(self, fp): self.__children_maxheaderlen, self.__children_maxheaderlen) -class Message(EmailMessage): + +class Message(email.message.Message): def __init__(self): # We need a version number so that we can optimize __setstate__() self.__version__ = VERSION - EmailMessage.__init__(self) + email.message.Message.__init__(self) # BAW: For debugging w/ bin/dumpdb. Apparently pprint uses repr. def __repr__(self): + if not hasattr(self, 'policy'): + self.policy = email._policybase.compat32 return self.__str__() def __setstate__(self, d): @@ -103,8 +102,8 @@ def __setstate__(self, d): chunks = [] cchanged = 0 for s, charset in v._chunks: - if isinstance(charset, str): - charset = charset.lower() + if type(charset) == str: + charset = Charset(charset) cchanged = 1 chunks.append((s, charset)) if cchanged: @@ -114,6 +113,7 @@ def __setstate__(self, d): if hchanged: self._headers = headers + # I think this method ought to eventually be deprecated def get_sender(self, use_envelope=None, preserve_case=0): """Return the address considered to be the author of the email. @@ -202,80 +202,52 @@ def get_senders(self, preserve_case=0, headers=None): # get_unixfrom() returns None if there's no envelope fieldval = self.get_unixfrom() or '' try: - realname, address = email.utils.parseaddr(fieldval) - except (TypeError, ValueError): - continue + pairs.append(('', fieldval.split()[1])) + except IndexError: + # Ignore badly formatted unixfroms + pass else: - fieldval = self[h] - if not fieldval: - continue - # Work around bug in email 2.5.8 (and ?) involving getaddresses() - # from multi-line header values. - fieldval = ''.join(fieldval.splitlines()) - addrs = email.utils.getaddresses([fieldval]) - if not addrs: - continue - realname, address = addrs[0] - if address: - if not preserve_case: - address = address.lower() - pairs.append((realname, address)) - return pairs + fieldvals = self.get_all(h) + if fieldvals: + # See comment above in get_sender() regarding + # getaddresses() and multi-line headers + fieldvals = [''.join(fv.splitlines()) + for fv in fieldvals] + pairs.extend(email.utils.getaddresses(fieldvals)) + authors = [] + for pair in pairs: + address = pair[1] + if address is not None and not preserve_case: + address = address.lower() + authors.append(address) + return authors def get_filename(self, failobj=None): - """Return the filename associated with the message's payload. - - This is a convenience method that returns the filename associated with - the message's payload. If the message is a multipart message, then - the filename is taken from the first part that has a filename - associated with it. If no filename is found, then failobj is - returned (defaults to None). + """Some MUA have bugs in RFC2231 filename encoding and cause + Mailman to stop delivery in Scrubber.py (called from ToDigest.py). """ - if self.is_multipart(): - for part in self.get_payload(): - if part.is_multipart(): - continue - filename = part.get_filename() - if filename: - return filename - else: - return self.get_param('filename', failobj) - return failobj + try: + filename = email.message.Message.get_filename(self, failobj) + return filename + except (UnicodeError, LookupError, ValueError): + return failobj - def as_string(self, unixfrom=False, mangle_from_=True): - """Return the entire formatted message as a string. - Optional unixfrom is a flag that, when True, results in the envelope - header being included in the output. + def as_string(self, unixfrom=False, mangle_from_=True): + """Return entire formatted message as a string using + Mailman.Message.Generator. - Optional mangle_from_ is a flag that, when True, escapes From_ lines - in the body of the message by putting a `>' in front of them. + Operates like email.message.Message.as_string, only + using Mailman's Message.Generator class. Only the top headers will + get folded. """ fp = StringIO() g = Generator(fp, mangle_from_=mangle_from_) + Utils.set_cte_if_missing(self) g.flatten(self, unixfrom=unixfrom) return fp.getvalue() - def get_sender_info(self, preserve_case=0, headers=None): - """Return a tuple of (realname, address) representing the author of the email. - - The method will return the first available sender information from: - 1. From: - 2. unixfrom - 3. Reply-To: - 4. Sender: - - The return address is always lower cased, unless `preserve_case' is true. - Optional `headers' gives an alternative search order, with None meaning, - search the unixfrom header. Items in `headers' are field names without - the trailing colon. - """ - pairs = self.get_senders(preserve_case, headers) - if pairs: - return pairs[0] - return ('', '') - - + class UserNotification(Message): """Class for internally crafted messages.""" @@ -283,45 +255,11 @@ def __init__(self, recip, sender, subject=None, text=None, lang=None): Message.__init__(self) charset = None if lang is not None: - charset = Charset(GetCharSet(lang)) - # Ensure we have a valid charset that can handle non-ASCII - if charset.output_charset == 'ascii': - charset.output_charset = 'utf-8' + charset = Charset(Utils.GetCharSet(lang)) if text is not None: - # Handle text encoding properly - if isinstance(text, bytes): - try: - # Try to decode using the provided charset - if charset: - text = text.decode(charset.input_charset, 'replace') - else: - # Fall back to UTF-8 if no charset provided - text = text.decode('utf-8', 'replace') - except (UnicodeDecodeError, LookupError): - # Last resort: latin-1 - text = text.decode('latin-1', 'replace') - elif not isinstance(text, str): - text = str(text) - # Ensure we're using a charset that can handle the text - if charset is None or charset.output_charset == 'ascii': - charset = Charset('utf-8') self.set_payload(text, charset) if subject is None: subject = '(no subject)' - # Handle subject encoding properly - if isinstance(subject, bytes): - try: - if charset: - subject = subject.decode(charset.input_charset, 'replace') - else: - subject = subject.decode('utf-8', 'replace') - except (UnicodeDecodeError, LookupError): - subject = subject.decode('latin-1', 'replace') - elif not isinstance(subject, str): - subject = str(subject) - # Ensure we're using a charset that can handle the subject - if charset is None or charset.output_charset == 'ascii': - charset = Charset('utf-8') self['Subject'] = Header(subject, charset, header_name='Subject', errors='replace') self['From'] = sender @@ -341,7 +279,7 @@ def send(self, mlist, noprecedence=False, **_kws): # this message has a Message-ID. Yes, the MTA would give us one, but # this is useful for logging to logs/smtp. if 'message-id' not in self: - self['Message-ID'] = unique_message_id(mlist) + self['Message-ID'] = Utils.unique_message_id(mlist) # Ditto for Date: which is required by RFC 2822 if 'date' not in self: self['Date'] = email.utils.formatdate(localtime=1) @@ -361,20 +299,16 @@ def _enqueue(self, mlist, **_kws): # Not imported at module scope to avoid import loop from Mailman.Queue.sbcache import get_switchboard virginq = get_switchboard(mm_cfg.VIRGINQUEUE_DIR) - # Get base msgdata from kwargs if it exists - msgdata = _kws.pop('msgdata', {}) - # Always set recipient information - msgdata['recips'] = self.recips - msgdata['recipient'] = self.recips[0] if self.recips else None # The message metadata better have a `recip' attribute virginq.enqueue(self, listname = mlist.internal_name(), + recips = self.recips, nodecorate = 1, reduced_list_headers = 1, - msgdata = msgdata, **_kws) + class OwnerNotification(UserNotification): """Like user notifications, but this message goes to the list owners.""" @@ -384,7 +318,7 @@ def __init__(self, mlist, subject=None, text=None, tomoderators=1): recips.extend(mlist.moderator) # We have to set the owner to the site's -bounces address, otherwise # we'll get a mail loop if an owner's address bounces. - sender = get_site_email(mlist.host_name, 'bounces') + sender = Utils.get_site_email(mlist.host_name, 'bounces') lang = mlist.preferred_language UserNotification.__init__(self, recips, sender, subject, text, lang) # Hack the To header to look like it's going to the -owner address @@ -402,23 +336,11 @@ def _enqueue(self, mlist, **_kws): # Not imported at module scope to avoid import loop from Mailman.Queue.sbcache import get_switchboard virginq = get_switchboard(mm_cfg.VIRGINQUEUE_DIR) - # Ensure recipient information is always included - if 'msgdata' in _kws: - msgdata = _kws['msgdata'] - else: - msgdata = {} - # Always set recipient information - msgdata['recips'] = self.recips - msgdata['recipient'] = self.recips[0] if self.recips else None # The message metadata better have a `recip' attribute virginq.enqueue(self, listname = mlist.internal_name(), + recips = self.recips, nodecorate = 1, reduced_list_headers = 1, envsender = self._sender, - msgdata = msgdata, **_kws) - -# Make UserNotification and OwnerNotification available as Message attributes -Message.UserNotification = UserNotification -Message.OwnerNotification = OwnerNotification diff --git a/Mailman/OldStyleMemberships.py b/Mailman/OldStyleMemberships.py index 137e52cd..f406e65b 100644 --- a/Mailman/OldStyleMemberships.py +++ b/Mailman/OldStyleMemberships.py @@ -25,14 +25,11 @@ """ import time -import re -import fnmatch from Mailman import mm_cfg from Mailman import Utils from Mailman import Errors from Mailman import MemberAdaptor -from Mailman import Autoresponder ISREGULAR = 1 ISDIGEST = 2 @@ -44,90 +41,10 @@ # Actually, fix /all/ errors -class OldStyleMemberships(MemberAdaptor.MemberAdaptor, Autoresponder.Autoresponder): + +class OldStyleMemberships(MemberAdaptor.MemberAdaptor): def __init__(self, mlist): self.__mlist = mlist - self.archive = mm_cfg.DEFAULT_ARCHIVE # Initialize archive attribute - self.digest_send_periodic = mm_cfg.DEFAULT_DIGEST_SEND_PERIODIC # Initialize digest_send_periodic attribute - self.archive_private = mm_cfg.DEFAULT_ARCHIVE_PRIVATE # Initialize archive_private attribute - self.bounce_you_are_disabled_warnings_interval = mm_cfg.DEFAULT_BOUNCE_YOU_ARE_DISABLED_WARNINGS_INTERVAL # Initialize bounce warning interval - self.digest_members = {} # Initialize digest_members dictionary - self.digest_is_default = mm_cfg.DEFAULT_DIGEST_IS_DEFAULT # Initialize digest_is_default attribute - self.mime_is_default_digest = mm_cfg.DEFAULT_MIME_IS_DEFAULT_DIGEST # Initialize mime_is_default_digest attribute - self._pending = {} # Initialize _pending dictionary for pending operations - # Initialize Autoresponder attributes - self.InitVars() - - def HasAutoApprovedSender(self, email): - """Check if the sender's email address is in the auto-approve list. - - Args: - email: The email address to check - - Returns: - bool: True if the sender is auto-approved, False otherwise - """ - # Check if the email is in the accept_these_nonmembers list - if email.lower() in [addr.lower() for addr in self.__mlist.accept_these_nonmembers]: - return True - - # Check if the email matches any patterns in accept_these_nonmembers - for pattern in self.__mlist.accept_these_nonmembers: - if pattern.startswith('^') or pattern.endswith('$'): - # This is a regex pattern - try: - if re.match(pattern, email, re.IGNORECASE): - return True - except re.error: - # Invalid regex pattern, skip it - continue - elif '*' in pattern or '?' in pattern: - # This is a glob pattern - if fnmatch.fnmatch(email.lower(), pattern.lower()): - return True - - return False - - def GetMailmanHeader(self): - """Return the standard Mailman header HTML for this list.""" - return self.__mlist.GetMailmanHeader() - - def CheckValues(self): - """Check that all member values are valid. - - This method is called by the admin interface to ensure that all member - values are valid before displaying them. It should return True if all - values are valid, False otherwise. - """ - try: - # Check that all members have valid email addresses - for member in self.getMembers(): - if not Utils.ValidateEmail(member): - return False - - # Check that all members have valid passwords - for member in self.getMembers(): - if not self.getMemberPassword(member): - return False - - # Check that all members have valid languages - for member in self.getMembers(): - lang = self.getMemberLanguage(member) - if lang not in self.__mlist.available_languages: - return False - - # Check that all members have valid delivery status - for member in self.getMembers(): - status = self.getDeliveryStatus(member) - if status not in (MemberAdaptor.ENABLED, MemberAdaptor.UNKNOWN, - MemberAdaptor.BYUSER, MemberAdaptor.BYADMIN, - MemberAdaptor.BYBOUNCE): - return False - - return True - except Exception as e: - mailman_log('error', 'Error checking member values: %s', str(e)) - return False # # Read interface @@ -142,20 +59,17 @@ def getDigestMemberKeys(self): return list(self.__mlist.digest_members.keys()) def __get_cp_member(self, member): - # Handle both string and tuple inputs - if isinstance(member, tuple): - _, member = member # Extract email address from tuple lcmember = member.lower() missing = [] val = self.__mlist.members.get(lcmember, missing) if val is not missing: - if isinstance(val, str): + if type(val) == str: return val, ISREGULAR else: return lcmember, ISREGULAR val = self.__mlist.digest_members.get(lcmember, missing) if val is not missing: - if isinstance(val, str): + if type(val) == str: return val, ISDIGEST else: return lcmember, ISDIGEST @@ -174,28 +88,10 @@ def getMemberKey(self, member): return member.lower() def getMemberCPAddress(self, member): - """Get the canonical address of a member. - - Args: - member: The member's email address - - Returns: - str: The member's canonical address - - Raises: - NotAMemberError: If the member is not found - """ cpaddr, where = self.__get_cp_member(member) if cpaddr is None: raise Errors.NotAMemberError(member) - if isinstance(cpaddr, bytes): - try: - # Try Latin-1 first since that's what we're seeing in the data - cpaddr = cpaddr.decode('latin-1', 'replace') - except UnicodeDecodeError: - # Fall back to UTF-8 if Latin-1 fails - cpaddr = cpaddr.decode('utf-8', 'replace') - return str(cpaddr) + return cpaddr def getMemberCPAddresses(self, members): return [self.__get_cp_member(member)[0] for member in members] @@ -208,6 +104,8 @@ def getMemberPassword(self, member): def authenticateMember(self, member, response): secret = self.getMemberPassword(member) + if isinstance(response, bytes): + response = response.decode('utf-8') if secret == response: return secret return 0 @@ -219,7 +117,7 @@ def __assertIsMember(self, member): def getMemberLanguage(self, member): lang = self.__mlist.language.get( member.lower(), self.__mlist.preferred_language) - if lang in self.__mlist.available_languages: + if lang in self.__mlist.GetAvailableLanguages(): return lang return self.__mlist.preferred_language @@ -232,26 +130,8 @@ def getMemberOption(self, member, flag): return not not (option & flag) def getMemberName(self, member): - """Get the member's real name. - - Args: - member: The member's email address - - Returns: - The member's real name, or None if not found - """ - try: - fullname = self.__mlist.usernames[member] - if isinstance(fullname, bytes): - try: - # Try Latin-1 first since that's what we're seeing in the data - fullname = fullname.decode('latin-1', 'replace') - except UnicodeDecodeError: - # Fall back to UTF-8 if Latin-1 fails - fullname = fullname.decode('utf-8', 'replace') - return fullname - except KeyError: - return None + self.__assertIsMember(member) + return self.__mlist.usernames.get(member.lower()) def getMemberTopics(self, member): self.__assertIsMember(member) @@ -451,23 +331,9 @@ def setMemberOption(self, member, flag, value): del self.__mlist.user_options[memberkey] def setMemberName(self, member, realname): - """Set the real name of a member. - - Args: - member: The member's email address - realname: The member's real name - """ + assert self.__mlist.Locked() self.__assertIsMember(member) - if realname is None: - realname = '' - if isinstance(realname, bytes): - try: - # Try Latin-1 first since that's what we're seeing in the data - realname = realname.decode('latin-1', 'replace') - except UnicodeDecodeError: - # Fall back to UTF-8 if Latin-1 fails - realname = realname.decode('utf-8', 'replace') - self.__mlist.usernames[member.lower()] = str(realname) + self.__mlist.usernames[member.lower()] = realname def setMemberTopics(self, member, topics): assert self.__mlist.Locked() @@ -495,184 +361,11 @@ def setDeliveryStatus(self, member, status): def setBounceInfo(self, member, info): assert self.__mlist.Locked() self.__assertIsMember(member) - self.__mlist.bounce_info[member.lower()] = info - - def ProcessConfirmation(self, cookie, msg): - """Process a confirmation request. - - Args: - cookie: The confirmation cookie string - msg: The message containing the confirmation request - - Returns: - A tuple of (action_type, action_data) where action_type is one of: - - Pending.SUBSCRIPTION - - Pending.UNSUBSCRIPTION - - Pending.HELD_MESSAGE - And action_data contains the relevant data for that action type. - - Raises: - Errors.MMBadConfirmation: If the confirmation string is invalid - Errors.MMNeedApproval: If the request needs moderator approval - Errors.MMAlreadyAMember: If the user is already a member - Errors.NotAMemberError: If the user is not a member - Errors.MembershipIsBanned: If the user is banned - Errors.HostileSubscriptionError: If the subscription is hostile - Errors.MMBadPasswordError: If the approval password is bad - """ - from Mailman import Pending - from Mailman import Utils - from Mailman import Errors - - # Get the pending request - try: - action, data = Pending.unpickle(cookie) - except Exception as e: - raise Errors.MMBadConfirmation(str(e)) - - # Check if the request has expired - if time.time() > data.get('expiration', 0): - raise Errors.MMBadConfirmation('Confirmation expired') - - # Process based on action type - if action == Pending.SUBSCRIPTION: - # Extract userdesc and remote from data - userdesc, remote = data - - # Check if already a member - if self.isMember(userdesc.address): - raise Errors.MMAlreadyAMember(userdesc.address) - - # Check if banned - if self.__mlist.isBanned(userdesc.address): - raise Errors.MembershipIsBanned(userdesc.address) - - # Add the member - self.addNewMember( - userdesc.address, - digest=userdesc.digest, - password=userdesc.password, - language=userdesc.language, - realname=userdesc.fullname - ) - - elif action == Pending.UNSUBSCRIPTION: - # Check if member - if not self.isMember(data['email']): - raise Errors.NotAMemberError(data['email']) - - # Remove the member - self.removeMember(data['email']) - - elif action == Pending.HELD_MESSAGE: - # Process held message - if data.get('approval_password'): - if data['approval_password'] != self.__mlist.mod_password: - raise Errors.MMBadPasswordError() - - # Forward to moderator if needed - if data.get('need_approval'): - self.__mlist.HoldMessage(msg) - raise Errors.MMNeedApproval() - - # Process the message - if data.get('action') == 'approve': - self.__mlist.ApproveMessage(msg) - else: - self.__mlist.DiscardMessage(msg) - + member = member.lower() + if info is None: + if member in self.__mlist.bounce_info: + del self.__mlist.bounce_info[member] + if member in self.__mlist.delivery_status: + del self.__mlist.delivery_status[member] else: - raise Errors.MMBadConfirmation('Unknown action type') - - # Remove the pending request - Pending.remove(cookie) - - return action, data - - @property - def digestable(self): - """Return whether the list supports digest mode. - - This is the inverse of nondigestable. - """ - return not self.__mlist.nondigestable - - @property - def digest_is_default(self): - """Return whether digest delivery is the default for new members.""" - return self.__mlist.digest_is_default - - @digest_is_default.setter - def digest_is_default(self, value): - """Set whether digest delivery is the default for new members.""" - self.__mlist.digest_is_default = value - - @property - def mime_is_default_digest(self): - """Return whether MIME format is the default for digests.""" - return self.__mlist.mime_is_default_digest - - @mime_is_default_digest.setter - def mime_is_default_digest(self, value): - """Set whether MIME format is the default for digests.""" - self.__mlist.mime_is_default_digest = value - - @property - def digest_size_threshhold(self): - """Return the size threshold for digests in KB.""" - return self.__mlist.digest_size_threshhold - - @digest_size_threshhold.setter - def digest_size_threshhold(self, value): - """Set the size threshold for digests in KB.""" - self.__mlist.digest_size_threshhold = value - - @property - def digest_send_periodic(self): - """Return whether digests are sent periodically.""" - return self.__mlist.digest_send_periodic - - @digest_send_periodic.setter - def digest_send_periodic(self, value): - """Set whether digests are sent periodically.""" - self.__mlist.digest_send_periodic = value - - @property - def digest_volume(self): - """Return the current digest volume number.""" - return self.__mlist.volume - - @digest_volume.setter - def digest_volume(self, value): - """Set the current digest volume number.""" - self.__mlist.volume = value - - @property - def digest_issue(self): - """Return the current digest issue number.""" - return self.__mlist.next_digest_number - - @digest_issue.setter - def digest_issue(self, value): - """Set the current digest issue number.""" - self.__mlist.next_digest_number = value - - @property - def digest_last_sent_at(self): - """Return the timestamp of when the last digest was sent.""" - return self.__mlist.digest_last_sent_at - - @digest_last_sent_at.setter - def digest_last_sent_at(self, value): - """Set the timestamp of when the last digest was sent.""" - self.__mlist.digest_last_sent_at = value - - @property - def digest_next_due_at(self): - """Return the timestamp of when the next digest is due.""" - return self.__mlist.digest_next_due_at - - @digest_next_due_at.setter - def digest_next_due_at(self, value): - """Set the timestamp of when the next digest is due.""" - self.__mlist.digest_next_due_at = value + self.__mlist.bounce_info[member] = info diff --git a/Mailman/Pending.py b/Mailman/Pending.py index d13f0724..06f777d2 100644 --- a/Mailman/Pending.py +++ b/Mailman/Pending.py @@ -24,14 +24,10 @@ import errno import random import pickle -import socket -import traceback -import signal from Mailman import mm_cfg from Mailman import UserDesc -from Mailman import Utils -from Mailman.Utils import sha_new +from Mailman.Utils import sha_new, load_pickle # Types of pending records SUBSCRIPTION = 'S' @@ -54,111 +50,77 @@ class Pending(object): def InitTempVars(self): self.__pendfile = os.path.join(self.fullpath(), 'pending.pck') - def pend_new(self, operation, data=None): - """Add a new pending request to the list. - - :param operation: The operation to perform. - :type operation: string - :param data: The data associated with the operation. - :type data: any - :return: The cookie for the pending request. - :rtype: string + def pend_new(self, op, *content, **kws): + """Create a new entry in the pending database, returning cookie for it. """ - # Make sure we have a lock - assert self.Locked(), 'List must be locked before pending operations' - - # Generate a unique cookie - cookie = Utils.unique_message_id(mlist=self) - - # Store the pending request - self._pending[cookie] = (operation, data) - + assert op in _ALLKEYS, 'op: %s' % op + lifetime = kws.get('lifetime', mm_cfg.PENDING_REQUEST_LIFE) + # We try the main loop several times. If we get a lock error somewhere + # (for instance because someone broke the lock) we simply try again. + assert self.Locked() + # Load the database + db = self.__load() + # Calculate a unique cookie. Algorithm vetted by the Timbot. time() + # has high resolution on Linux, clock() on Windows. random gives us + # about 45 bits in Python 2.2, 53 bits on Python 2.3. The time and + # clock values basically help obscure the random number generator, as + # does the hash calculation. The integral parts of the time values + # are discarded because they're the most predictable bits. + while True: + now = time.time() + x = random.random() + now % 1.0 + time.time() % 1.0 + cookie = sha_new(repr(x).encode()).hexdigest() + # We'll never get a duplicate, but we'll be anal about checking + # anyway. + if cookie not in db: + break + # Store the content, plus the time in the future when this entry will + # be evicted from the database, due to staleness. + db[cookie] = (op,) + content + evictions = db.setdefault('evictions', {}) + evictions[cookie] = now + lifetime + self.__save(db) return cookie def __load(self): - """Load the pending database with improved error handling.""" - filename = os.path.join(mm_cfg.DATA_DIR, 'pending.pck') - filename_backup = filename + '.bak' - - # Try loading the main file first try: - with open(filename, 'rb') as fp: - try: - data = fp.read() - if not data: - return {} - return pickle.loads(data, fix_imports=True, encoding='latin1') - except (EOFError, ValueError, TypeError, pickle.UnpicklingError) as e: - syslog('error', 'Error loading pending.pck: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - - # If we get here, the main file failed to load properly - if os.path.exists(filename_backup): - syslog('info', 'Attempting to load from backup file') - with open(filename_backup, 'rb') as fp: - try: - data = fp.read() - if not data: - return {} - db = pickle.loads(data, fix_imports=True, encoding='latin1') - # Successfully loaded backup, restore it as main - import shutil - shutil.copy2(filename_backup, filename) - return db - except (EOFError, ValueError, TypeError, pickle.UnpicklingError) as e: - syslog('error', 'Error loading backup pending.pck: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - - except IOError as e: - if e.errno != errno.ENOENT: - syslog('error', 'IOError loading pending.pck: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - - # If we get here, both main and backup files failed or don't exist - return {} + obj = load_pickle(self.__pendfile) + if obj == None: + return {'evictions': {}} + else: + return obj + except Exception as e: + return {'evictions': {}} def __save(self, db): - """Save the pending database with atomic operations and backup.""" - if not db: - return - - filename = os.path.join(mm_cfg.DATA_DIR, 'pending.pck') - filename_tmp = filename + '.tmp.%s.%d' % (socket.gethostname(), os.getpid()) - filename_backup = filename + '.bak' - - # First create a backup of the current file if it exists - if os.path.exists(filename): - try: - import shutil - shutil.copy2(filename, filename_backup) - except IOError as e: - syslog('error', 'Error creating backup: %s', str(e)) - - # Save to temporary file first + evictions = db['evictions'] + now = time.time() + for cookie, data in list(db.items()): + if cookie in ('evictions', 'version'): + continue + timestamp = evictions[cookie] + if now > timestamp: + # The entry is stale, so remove it. + del db[cookie] + del evictions[cookie] + # Clean out any bogus eviction entries. + for cookie in list(evictions.keys()): + if cookie not in db: + del evictions[cookie] + db['version'] = mm_cfg.PENDING_FILE_SCHEMA_VERSION + tmpfile = '%s.tmp.%d.%d' % (self.__pendfile, os.getpid(), now) + omask = os.umask(0o007) try: - # Ensure directory exists - dirname = os.path.dirname(filename) - if not os.path.exists(dirname): - os.makedirs(dirname, 0o755) - - with open(filename_tmp, 'wb') as fp: - # Use protocol 4 for better compatibility - pickle.dump(db, fp, protocol=4, fix_imports=True) - fp.flush() - if hasattr(os, 'fsync'): - os.fsync(fp.fileno()) - - # Atomic rename - os.rename(filename_tmp, filename) - - except (IOError, OSError) as e: - syslog('error', 'Error saving pending.pck: %s', str(e)) - # Try to clean up + fp = open(tmpfile, 'wb') try: - os.unlink(filename_tmp) - except OSError: - pass - raise + pickle.dump(db, fp) + fp.flush() + os.fsync(fp.fileno()) + finally: + fp.close() + os.rename(tmpfile, self.__pendfile) + finally: + os.umask(omask) def pend_confirm(self, cookie, expunge=True): """Return data for cookie, or None if not found. diff --git a/Mailman/Post.py b/Mailman/Post.py index 07200483..7f86696d 100644 --- a/Mailman/Post.py +++ b/Mailman/Post.py @@ -20,8 +20,6 @@ from Mailman import mm_cfg from Mailman.Queue.sbcache import get_switchboard -from Mailman.Message import Message -from email import message_from_string @@ -35,19 +33,7 @@ def inject(listname, msg, recips=None, qdir=None): } if recips: kws['recips'] = recips - # Ensure msg is a Mailman.Message.Message - if isinstance(msg, str): - emsg = message_from_string(msg) - else: - emsg = msg - if not isinstance(emsg, Message): - mmsg = Message() - for k, v in emsg.items(): - mmsg[k] = v - mmsg.set_payload(emsg.get_payload()) - else: - mmsg = emsg - queue.enqueue(mmsg, msgdata=kws) + queue.enqueue(msg, **kws) diff --git a/Mailman/Queue/ArchRunner.py b/Mailman/Queue/ArchRunner.py index 562f9d1b..fb5265bb 100644 --- a/Mailman/Queue/ArchRunner.py +++ b/Mailman/Queue/ArchRunner.py @@ -30,51 +30,87 @@ class ArchRunner(Runner): QDIR = mm_cfg.ARCHQUEUE_DIR def _dispose(self, mlist, msg, msgdata): + from Mailman.Logging.Syslog import syslog + syslog('debug', 'ArchRunner: Starting archive processing for list %s', mlist.internal_name()) + # Support clobber_date, i.e. setting the date in the archive to the # received date, not the (potentially bogus) Date: header of the # original message. clobber = 0 originaldate = msg.get('date') - receivedtime = formatdate(msgdata.get('received_time', time.time())) + + # Handle potential bytes/string issues with header values + if isinstance(originaldate, bytes): + try: + originaldate = originaldate.decode('utf-8', 'replace') + except (UnicodeDecodeError, AttributeError): + originaldate = None + + receivedtime = formatdate(msgdata['received_time']) + syslog('debug', 'ArchRunner: Original date: %s, Received time: %s', originaldate, receivedtime) + if not originaldate: clobber = 1 + syslog('debug', 'ArchRunner: No original date, will clobber') elif mm_cfg.ARCHIVER_CLOBBER_DATE_POLICY == 1: clobber = 1 + syslog('debug', 'ArchRunner: ARCHIVER_CLOBBER_DATE_POLICY = 1, will clobber') elif mm_cfg.ARCHIVER_CLOBBER_DATE_POLICY == 2: # what's the timestamp on the original message? - tup = parsedate_tz(originaldate) - now = time.time() try: + tup = parsedate_tz(originaldate) + now = time.time() if not tup: clobber = 1 + syslog('debug', 'ArchRunner: Could not parse original date, will clobber') elif abs(now - mktime_tz(tup)) > \ mm_cfg.ARCHIVER_ALLOWABLE_SANE_DATE_SKEW: clobber = 1 - except (ValueError, OverflowError): + syslog('debug', 'ArchRunner: Date skew too large, will clobber') + except (ValueError, OverflowError, TypeError): # The likely cause of this is that the year in the Date: field # is horribly incorrect, e.g. (from SF bug # 571634): # Date: Tue, 18 Jun 0102 05:12:09 +0500 # Obviously clobber such dates. clobber = 1 + syslog('debug', 'ArchRunner: Date parsing exception, will clobber') + if clobber: - del msg['date'] - del msg['x-original-date'] + # Use proper header manipulation methods + if 'date' in msg: + del msg['date'] + if 'x-original-date' in msg: + del msg['x-original-date'] msg['Date'] = receivedtime if originaldate: msg['X-Original-Date'] = originaldate + syslog('debug', 'ArchRunner: Clobbered date headers') + # Always put an indication of when we received the message. msg['X-List-Received-Date'] = receivedtime + # Now try to get the list lock + syslog('debug', 'ArchRunner: Attempting to lock list %s', mlist.internal_name()) try: mlist.Lock(timeout=mm_cfg.LIST_LOCK_TIMEOUT) + syslog('debug', 'ArchRunner: Successfully locked list %s', mlist.internal_name()) except LockFile.TimeOutError: # oh well, try again later + syslog('debug', 'ArchRunner: Failed to lock list %s, will retry later', mlist.internal_name()) return 1 + try: # Archiving should be done in the list's preferred language, not # the sender's language. i18n.set_language(mlist.preferred_language) + syslog('debug', 'ArchRunner: Calling ArchiveMail for list %s', mlist.internal_name()) mlist.ArchiveMail(msg) + syslog('debug', 'ArchRunner: ArchiveMail completed, saving list %s', mlist.internal_name()) mlist.Save() + syslog('debug', 'ArchRunner: Successfully completed archive processing for list %s', mlist.internal_name()) + except Exception as e: + syslog('error', 'ArchRunner: Exception during archive processing for list %s: %s', mlist.internal_name(), e) + raise finally: mlist.Unlock() + syslog('debug', 'ArchRunner: Unlocked list %s', mlist.internal_name()) diff --git a/Mailman/Queue/BounceRunner.py b/Mailman/Queue/BounceRunner.py index c07ffa91..970d3236 100644 --- a/Mailman/Queue/BounceRunner.py +++ b/Mailman/Queue/BounceRunner.py @@ -15,177 +15,131 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, # USA. -"""Bounce queue runner. +"""Bounce queue runner.""" -This module is responsible for processing bounce messages. It's a separate -queue from the virgin queue because bounces need different handling. -""" - -from builtins import object, str +from builtins import object import os import re import time import pickle -import email -from email.utils import getaddresses, parseaddr -from email.iterators import body_line_iterator + from email.mime.text import MIMEText from email.mime.message import MIMEMessage -import traceback -from io import StringIO -import sys +from email.utils import parseaddr from Mailman import mm_cfg from Mailman import Utils from Mailman import LockFile -from Mailman import Errors -from Mailman import i18n from Mailman.Errors import NotAMemberError +from Mailman.Message import UserNotification from Mailman.Bouncer import _BounceInfo from Mailman.Bouncers import BouncerAPI from Mailman.Queue.Runner import Runner from Mailman.Queue.sbcache import get_switchboard from Mailman.Logging.Syslog import syslog from Mailman.i18n import _ -import Mailman.Message as Message - -# Lazy import to avoid circular dependency -def get_mail_list(): - import Mailman.MailList as MailList - return MailList.MailList COMMASPACE = ', ' + class BounceMixin: def __init__(self): - """Initialize the bounce mixin.""" + # Registering a bounce means acquiring the list lock, and it would be + # too expensive to do this for each message. Instead, each bounce + # runner maintains an event log which is essentially a file with + # multiple pickles. Each bounce we receive gets appended to this file + # as a 4-tuple record: (listname, addr, today, msg) + # + # today is itself a 3-tuple of (year, month, day) + # + # Every once in a while (see _doperiodic()), the bounce runner cracks + # open the file, reads all the records and registers all the bounces. + # Then it truncates the file and continues on. We don't need to lock + # the bounce event file because bounce qrunners are single threaded + # and each creates a uniquely named file to contain the events. + # + # XXX When Python 2.3 is minimal require, we can use the new + # tempfile.TemporaryFile() function. + # + # XXX We used to classify bounces to the site list as bounce events + # for every list, but this caused severe problems. Here's the + # scenario: aperson@example.com is a member of 4 lists, and a list + # owner of the foo list. example.com has an aggressive spam filter + # which rejects any message that is spam or contains spam as an + # attachment. Now, a spambot sends a piece of spam to the foo list, + # but since that spambot is not a member, the list holds the message + # for approval, and sends a notification to aperson@example.com as + # list owner. That notification contains a copy of the spam. Now + # example.com rejects the message, causing a bounce to be sent to the + # site list's bounce address. The bounce runner would then dutifully + # register a bounce for all 4 lists that aperson@example.com was a + # member of, and eventually that person would get disabled on all + # their lists. So now we ignore site list bounces. Ce La Vie for + # password reminder bounces. + self._bounce_events_file = os.path.join( + mm_cfg.DATA_DIR, 'bounce-events-%05d.pck' % os.getpid()) + self._bounce_events_fp = None self._bouncecnt = 0 - # Set initial next action time to 1 hour in the future - self._next_action = time.time() + 3600 - syslog('debug', 'BounceMixin: Initialized with next action time: %s', - time.ctime(self._next_action)) - - def _process_bounces(self): - """Process pending bounces.""" - try: - syslog('debug', 'BounceMixin._process_bounces: Starting bounce processing') - - # Get all lists - listnames = Utils.list_names() - for listname in listnames: - try: - mlist = get_mail_list()(listname, lock=0) - try: - # Process bounces for this list - self._process_list_bounces(mlist) - finally: - mlist.Unlock() - except Exception as e: - syslog('error', 'BounceMixin._process_bounces: Error processing list %s: %s', - listname, str(e)) - continue - - syslog('debug', 'BounceMixin._process_bounces: Completed bounce processing') - - except Exception as e: - syslog('error', 'BounceMixin._process_bounces: Error during bounce processing: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - - def _process_list_bounces(self, mlist): - """Process bounces for a specific list.""" - try: - syslog('debug', 'BounceMixin._process_list_bounces: Processing bounces for list %s', - mlist.internal_name()) - - # Get all bouncing members - bouncing_members = mlist.getBouncingMembers() - for member in bouncing_members: - try: - # Get bounce info for this member - info = mlist.getBounceInfo(member) - if not info: - continue - - # Check if member should be disabled - if info.score >= mlist.bounce_score_threshold: - syslog('debug', 'BounceMixin._process_list_bounces: Disabling member %s due to bounce score %f', - member, info.score) - mlist.disableBouncingMember(member, info) - - except Exception as e: - syslog('error', 'BounceMixin._process_list_bounces: Error processing member %s: %s', - member, str(e)) - continue - - syslog('debug', 'BounceMixin._process_list_bounces: Completed processing bounces for list %s', - mlist.internal_name()) - - except Exception as e: - syslog('error', 'BounceMixin._process_list_bounces: Error processing list %s: %s\nTraceback:\n%s', - mlist.internal_name(), str(e), traceback.format_exc()) - - def _register_bounces(self, mlist, bounces): - """Register bounce information for a list.""" - try: - for address, info in bounces.items(): - syslog('debug', 'BounceMixin._register_bounces: Registering bounce for list %s, address %s', - mlist.internal_name(), address) - - # Write bounce data to file - filename = os.path.join(mlist.bounce_dir, address) - try: - with open(filename, 'w') as fp: - fp.write(str(info)) - syslog('debug', 'BounceMixin._register_bounces: Successfully wrote bounce data to %s', filename) - except Exception as e: - syslog('error', 'BounceMixin._register_bounces: Failed to write bounce data to %s: %s\nTraceback:\n%s', - filename, str(e), traceback.format_exc()) - continue - - except Exception as e: - syslog('error', 'BounceMixin._register_bounces: Error registering bounce: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - - def _cleanup(self): - """Clean up bounce processing.""" - try: - syslog('debug', 'BounceMixin._cleanup: Processing %d pending bounces', self._bouncecnt) - # ... cleanup logic ... - except Exception as e: - syslog('error', 'BounceMixin._cleanup: Error during cleanup: %s', str(e)) - - def _doperiodic(self): - """Do periodic bounce processing.""" - try: - now = time.time() - if now >= self._next_action: - syslog('debug', 'BounceMixin._doperiodic: Processing bounces, next action scheduled for %s', - time.ctime(self._next_action)) - # Process bounces - self._process_bounces() - # Update next action time to 1 hour from now - self._next_action = now + 3600 - syslog('debug', 'BounceMixin._doperiodic: Next action scheduled for %s', - time.ctime(self._next_action)) - except Exception as e: - syslog('error', 'BounceMixin._doperiodic: Error during periodic processing: %s', str(e)) + self._nextaction = time.time() + mm_cfg.REGISTER_BOUNCES_EVERY def _queue_bounces(self, listname, addrs, msg): today = time.localtime()[:3] if self._bounce_events_fp is None: omask = os.umask(0o006) try: - self._bounce_events_fp = open(self._bounce_events_file, 'ab') + self._bounce_events_fp = open(self._bounce_events_file, 'a+b') finally: os.umask(omask) for addr in addrs: - # Use protocol 4 for Python 3 compatibility and fix_imports for Python 2/3 compatibility pickle.dump((listname, addr, today, msg), - self._bounce_events_fp, protocol=4, fix_imports=True) + self._bounce_events_fp, 1) self._bounce_events_fp.flush() os.fsync(self._bounce_events_fp.fileno()) self._bouncecnt += len(addrs) + def _register_bounces(self): + syslog('bounce', '%s processing %s queued bounces', + self, self._bouncecnt) + # Read all the records from the bounce file, then unlink it. Sort the + # records by listname for more efficient processing. + events = {} + self._bounce_events_fp.seek(0) + while True: + try: + listname, addr, day, msg = pickle.load(self._bounce_events_fp, fix_imports=True, encoding='latin1') + except ValueError as e: + syslog('bounce', 'Error reading bounce events: %s', e) + except EOFError: + break + events.setdefault(listname, []).append((addr, day, msg)) + # Now register all events sorted by list + for listname in list(events.keys()): + mlist = self._open_list(listname) + mlist.Lock() + try: + for addr, day, msg in events[listname]: + mlist.registerBounce(addr, msg, day=day) + mlist.Save() + finally: + mlist.Unlock() + # Reset and free all the cached memory + self._bounce_events_fp.close() + self._bounce_events_fp = None + os.unlink(self._bounce_events_file) + self._bouncecnt = 0 + + def _cleanup(self): + if self._bouncecnt > 0: + self._register_bounces() + + def _doperiodic(self): + now = time.time() + if self._nextaction > now or self._bouncecnt == 0: + return + # Let's go ahead and register the bounces we've got stored up + self._nextaction = now + mm_cfg.REGISTER_BOUNCES_EVERY + self._register_bounces() + def _probe_bounce(self, mlist, token): locked = mlist.Locked() if not locked: @@ -217,168 +171,145 @@ def _probe_bounce(self, mlist, token): mlist.Unlock() + class BounceRunner(Runner, BounceMixin): QDIR = mm_cfg.BOUNCEQUEUE_DIR - # Enable message tracking for bounce messages - _track_messages = True - _max_processed_messages = 10000 - _max_retry_times = 10000 - - # Retry configuration - MIN_RETRY_DELAY = 300 # 5 minutes minimum delay between retries - MAX_RETRIES = 5 # Maximum number of retry attempts - _retry_times = {} # Track last retry time for each message - - # Cleanup configuration - _cleanup_interval = 3600 # Clean up every hour - _last_cleanup = 0 # Last cleanup time - def __init__(self, slice=None, numslices=1): - syslog('debug', 'BounceRunner: Starting initialization') - try: - Runner.__init__(self, slice, numslices) - BounceMixin.__init__(self) - - # Initialize bounce events file - self._bounce_events_file = os.path.join(mm_cfg.DATA_DIR, 'bounce_events') - self._bounce_events_fp = None - - # Initialize processed messages tracking - self._processed_messages = set() - self._last_cleanup = time.time() - - syslog('debug', 'BounceRunner: Initialization complete') - except Exception as e: - syslog('error', 'BounceRunner: Initialization failed: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - raise + Runner.__init__(self, slice, numslices) + BounceMixin.__init__(self) def _dispose(self, mlist, msg, msgdata): - """Process a bounce message.""" - try: - # Get the message ID - msgid = msg.get('message-id', 'n/a') - filebase = msgdata.get('_filebase', 'unknown') - - # Ensure we have a MailList object - if isinstance(mlist, str): - try: - mlist = get_mail_list()(mlist, lock=0) - should_unlock = True - except Errors.MMUnknownListError: - syslog('error', 'BounceRunner: Unknown list %s', mlist) - self._shunt.enqueue(msg, msgdata) - return True - else: - should_unlock = False - - try: - syslog('debug', 'BounceRunner._dispose: Starting to process bounce message %s (file: %s) for list %s', - msgid, filebase, mlist.internal_name()) - - # Check retry delay - if not self._check_retry_delay(msgid, filebase): - syslog('debug', 'BounceRunner._dispose: Message %s failed retry delay check, skipping', msgid) - return True - - # Process the bounce - # ... bounce processing logic ... - - return False - - finally: - if should_unlock: - mlist.Unlock() - - except Exception as e: - syslog('error', 'BounceRunner._dispose: Error processing bounce message %s: %s\nTraceback:\n%s', - msgid, str(e), traceback.format_exc()) - return True + # Make sure we have the most up-to-date state + mlist.Load() + outq = get_switchboard(mm_cfg.OUTQUEUE_DIR) + # There are a few possibilities here: + # + # - the message could have been VERP'd in which case, we know exactly + # who the message was destined for. That make our job easy. + # - the message could have been originally destined for a list owner, + # but a list owner address itself bounced. That's bad, and for now + # we'll simply attempt to deliver the message to the site list + # owner. + # Note that this means that automated bounce processing doesn't work + # for the site list. Because we can't reliably tell to what address + # a non-VERP'd bounce was originally sent, we have to treat all + # bounces sent to the site list as potential list owner bounces. + # - the list owner could have set list-bounces (or list-admin) as the + # owner address. That's really bad as it results in a loop of ever + # growing unrecognized bounce messages. We detect this based on the + # fact that this message itself will be from the site bounces + # address. We then send this to the site list owner instead. + # Notices to list-owner have their envelope sender and From: set to + # the site-bounces address. Check if this is this a bounce for a + # message to a list owner, coming to site-bounces, or a looping + # message sent directly to the -bounces address. We have to do these + # cases separately, because sending to site-owner will reset the + # envelope sender. + # Is this a site list bounce? + if (mlist.internal_name().lower() == + mm_cfg.MAILMAN_SITE_LIST.lower()): + # Send it on to the site owners, but craft the envelope sender to + # be the -loop detection address, so if /they/ bounce, we won't + # get stuck in a bounce loop. + outq.enqueue(msg, msgdata, + recips=mlist.owner, + envsender=Utils.get_site_email(extra='loop'), + nodecorate=1, + ) + return + # Is this a possible looping message sent directly to a list-bounces + # address other than the site list? + # Check From: because unix_from might be VERP'd. + # Also, check the From: that Message.OwnerNotification uses. + if (msg.get('from') == + Utils.get_site_email(mlist.host_name, 'bounces')): + # Just send it to the sitelist-owner address. If that bounces + # we'll handle it above. + outq.enqueue(msg, msgdata, + recips=[Utils.get_site_email(extra='owner')], + envsender=Utils.get_site_email(extra='loop'), + nodecorate=1, + ) + return + # List isn't doing bounce processing? + if not mlist.bounce_processing: + return + # Try VERP detection first, since it's quick and easy + addrs = verp_bounce(mlist, msg) + if addrs: + # We have an address, but check if the message is non-fatal. + if BouncerAPI.ScanMessages(mlist, msg) is BouncerAPI.Stop: + return + else: + # See if this was a probe message. + token = verp_probe(mlist, msg) + if token: + self._probe_bounce(mlist, token) + return + # That didn't give us anything useful, so try the old fashion + # bounce matching modules. + addrs = BouncerAPI.ScanMessages(mlist, msg) + if addrs is BouncerAPI.Stop: + # This is a recognized, non-fatal notice. Ignore it. + return + # If that still didn't return us any useful addresses, then send it on + # or discard it. + addrs = [_f for _f in addrs if _f] + if not addrs: + syslog('bounce', + '%s: bounce message w/no discernable addresses: %s', + mlist.internal_name(), + msg.get('message-id', 'n/a')) + maybe_forward(mlist, msg) + return + # BAW: It's possible that there are None's in the list of addresses, + # although I'm unsure how that could happen. Possibly ScanMessages() + # can let None's sneak through. In any event, this will kill them. + # addrs = filter(None, addrs) + # MAS above filter moved up so we don't try to queue an empty list. + self._queue_bounces(mlist.internal_name(), addrs, msg) - def _extract_bounce_info(self, msg): - """Extract bounce information from a message.""" - try: - # Log the message structure for debugging - syslog('debug', 'BounceRunner._extract_bounce_info: Message structure:') - syslog('debug', ' Headers: %s', dict(msg.items())) - syslog('debug', ' Content-Type: %s', msg.get('content-type', 'unknown')) - syslog('debug', ' Is multipart: %s', msg.is_multipart()) - - # Extract bounce information based on message structure - bounce_info = {} - - # Try to get recipient from various headers - for header in ['X-Failed-Recipients', 'X-Original-To', 'To']: - if msg.get(header): - bounce_info['recipient'] = msg[header] - syslog('debug', 'BounceRunner._extract_bounce_info: Found recipient in %s header: %s', - header, bounce_info['recipient']) - break - - # Try to get error information - if msg.is_multipart(): - for part in msg.get_payload(): - if part.get_content_type() == 'message/delivery-status': - bounce_info['error'] = part.get_payload() - syslog('debug', 'BounceRunner._extract_bounce_info: Found delivery status in multipart message') - break - - if not bounce_info.get('recipient'): - syslog('error', 'BounceRunner._extract_bounce_info: Could not find recipient in bounce message') - return None - - return bounce_info - - except Exception as e: - syslog('error', 'BounceRunner._extract_bounce_info: Error extracting bounce information: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - return None + _doperiodic = BounceMixin._doperiodic def _cleanup(self): - """Clean up resources.""" - syslog('debug', 'BounceRunner: Starting cleanup') - try: - BounceMixin._cleanup(self) - Runner._cleanup(self) - except Exception as e: - syslog('error', 'BounceRunner: Cleanup failed: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - syslog('debug', 'BounceRunner: Cleanup complete') - - _doperiodic = BounceMixin._doperiodic + BounceMixin._cleanup(self) + Runner._cleanup(self) + def verp_bounce(mlist, msg): - try: - bmailbox, bdomain = Utils.ParseEmail(mlist.GetBouncesEmail()) - vals = [] - for header in ('to', 'delivered-to', 'envelope-to', 'apparently-to'): - vals.extend(msg.get_all(header, [])) - for field in vals: - to = parseaddr(field)[1] - if not to: - continue - try: - mo = re.search(mm_cfg.VERP_REGEXP, to, re.IGNORECASE) - if not mo: - continue - if bmailbox != mo.group('bounces'): - continue - addr = '%s@%s' % mo.group('mailbox', 'host') - return [addr] - except IndexError: - syslog('error', "VERP_REGEXP doesn't yield the right match groups: %s", - mm_cfg.VERP_REGEXP) - continue - except Exception as e: - syslog('error', "Error processing VERP bounce: %s", str(e)) - continue - except Exception as e: - syslog('error', "Error in verp_bounce: %s", str(e)) - return [] + bmailbox, bdomain = Utils.ParseEmail(mlist.GetBouncesEmail()) + # Sadly not every MTA bounces VERP messages correctly, or consistently. + # Fall back to Delivered-To: (Postfix), Envelope-To: (Exim) and + # Apparently-To:, and then short-circuit if we still don't have anything + # to work with. Note that there can be multiple Delivered-To: headers so + # we need to search them all (and we don't worry about false positives for + # forwarded email, because only one should match VERP_REGEXP). + vals = [] + for header in ('to', 'delivered-to', 'envelope-to', 'apparently-to'): + vals.extend(msg.get_all(header, [])) + for field in vals: + to = parseaddr(field)[1] + if not to: + continue # empty header + mo = re.search(mm_cfg.VERP_REGEXP, to) + if not mo: + continue # no match of regexp + try: + if bmailbox != mo.group('bounces'): + continue # not a bounce to our list + # All is good + addr = '%s@%s' % mo.group('mailbox', 'host') + except IndexError: + syslog('error', + "VERP_REGEXP doesn't yield the right match groups: %s", + mm_cfg.VERP_REGEXP) + return [] + return [addr] + def verp_probe(mlist, msg): bmailbox, bdomain = Utils.ParseEmail(mlist.GetBouncesEmail()) # Sadly not every MTA bounces VERP messages correctly, or consistently. @@ -394,7 +325,7 @@ def verp_probe(mlist, msg): to = parseaddr(field)[1] if not to: continue # empty header - mo = re.search(mm_cfg.VERP_PROBE_REGEXP, to, re.IGNORECASE) + mo = re.search(mm_cfg.VERP_PROBE_REGEXP, to) if not mo: continue # no match of regexp try: @@ -413,6 +344,7 @@ def verp_probe(mlist, msg): return None + def maybe_forward(mlist, msg): # Does the list owner want to get non-matching bounce messages? # If not, simply discard it. @@ -428,7 +360,7 @@ def maybe_forward(mlist, msg): For more information see: %(adminurl)s -""") % {'adminurl': adminurl}, +"""), subject=_('Uncaught bounce notification'), tomoderators=0) syslog('bounce', diff --git a/Mailman/Queue/CommandRunner.py b/Mailman/Queue/CommandRunner.py index 81b764dc..6272dfdc 100644 --- a/Mailman/Queue/CommandRunner.py +++ b/Mailman/Queue/CommandRunner.py @@ -22,46 +22,27 @@ # bounce messages (i.e. -admin or -bounces), nor does it handle mail to # -owner. + + +# BAW: get rid of this when we Python 2.2 is a minimum requirement. + import re import sys -import email -import email.message -import email.utils -from email.header import decode_header, make_header, Header -from email.errors import HeaderParseError -from email.iterators import typed_subpart_iterator -from email.mime.text import MIMEText -from email.mime.message import MIMEMessage from Mailman import mm_cfg from Mailman import Utils -from Mailman import Errors -from Mailman import i18n -from Mailman.htmlformat import * -from Mailman.Logging.Syslog import mailman_log, syslog -from Mailman.Utils import validate_ip_address -import Mailman.Handlers.Replybot as Replybot -from Mailman.Message import Message, UserNotification +from Mailman import Message +from Mailman.Handlers import Replybot from Mailman.i18n import _ from Mailman.Queue.Runner import Runner +from Mailman.Logging.Syslog import syslog from Mailman import LockFile -from Mailman import Pending -from Mailman import MailList -import traceback -import os -# Lazy imports to avoid circular dependencies -def get_replybot(): - import Mailman.Handlers.Replybot as Replybot - return Replybot - -def get_maillist(): - import Mailman.MailList as MailList - return MailList.MailList - -def get_usernotification(): - from Mailman.Message import UserNotification - return UserNotification +from email.header import decode_header, make_header, Header +from email.errors import HeaderParseError +from email.iterators import typed_subpart_iterator +from email.mime.text import MIMEText +from email.mime.message import MIMEMessage NL = '\n' CONTINUE = 0 @@ -69,28 +50,10 @@ def get_usernotification(): BADCMD = 2 BADSUBJ = 3 -# List of valid commands that can be imported -VALID_COMMANDS = { - 'confirm', # Confirm subscription/unsubscription - 'echo', # Echo command - 'end', # End command - 'help', # Help command - 'info', # List information - 'join', # Join list - 'leave', # Leave list - 'lists', # List all lists - 'password', # Password command - 'remove', # Remove from list - 'set', # Set options - 'stop', # Stop command - 'subscribe', # Subscribe to list - 'unsubscribe',# Unsubscribe from list - 'who' # Who command -} - + class Results: - def __init__(self, mlist_obj, msg, msgdata): - self.mlist = mlist_obj + def __init__(self, mlist, msg, msgdata): + self.mlist = mlist self.msg = msg self.msgdata = msgdata # Only set returnaddr if the response is to go to someone other than @@ -103,17 +66,14 @@ def __init__(self, mlist_obj, msg, msgdata): self.lineno = 0 self.subjcmdretried = 0 self.respond = True - # Extract the subject header and do RFC 2047 decoding + # Extract the subject header and do RFC 2047 decoding. Note that + # Python 2.1's unicode() builtin doesn't call obj.__unicode__(). subj = msg.get('subject', '') try: - # If subj is already a Header object, convert it to string first - if isinstance(subj, Header): - subj = str(subj) - else: - subj = str(make_header(decode_header(subj))) + subj = make_header(decode_header(subj)).__str__() # TK: Currently we don't allow 8bit or multibyte in mail command. # MAS: However, an l10n 'Re:' may contain non-ascii so ignore it. - subj = subj.encode('us-ascii', 'ignore').decode('us-ascii') + subj = subj.encode('us-ascii', 'ignore') # Always process the Subject: header first self.commands.append(subj) except (HeaderParseError, UnicodeError, LookupError): @@ -132,15 +92,12 @@ def __init__(self, mlist_obj, msg, msgdata): return body = part.get_payload(decode=True) if (part.get_content_charset(None)): - # Use get() with default value for lang - lang = msgdata.get('lang', mlist_obj.preferred_language) body = str(body, part.get_content_charset(), errors='replace').encode( - Utils.GetCharSet(lang), + Utils.GetCharSet(self.msgdata['lang']), errors='replace') # text/plain parts better have string payloads - if not isinstance(body, (str, bytes)): - raise TypeError(f'Invalid body type: {type(body)}, expected str or bytes') + assert isinstance(body, str) or isinstance(body, bytes) lines = body.splitlines() # Use no more lines than specified self.commands.extend(lines[:mm_cfg.DEFAULT_MAIL_COMMANDS_MAX_LINES]) @@ -153,12 +110,6 @@ def process(self): ret = CONTINUE for line in self.commands: if line and line.strip(): - # Ensure line is a string - if isinstance(line, bytes): - try: - line = line.decode('utf-8') - except UnicodeDecodeError: - line = line.decode('latin-1') args = line.split() cmd = args.pop(0).lower() ret = self.do_command(cmd, args) @@ -172,37 +123,43 @@ def process(self): def do_command(self, cmd, args=None): if args is None: args = () - # Clean the command name to prevent injection - cmd = cmd.lower().strip() - # Only try to import valid commands - if cmd not in VALID_COMMANDS: + # Try to import a command handler module for this command + if isinstance(cmd, bytes): + cmd = cmd.decode() + modname = 'Mailman.Commands.cmd_' + cmd + try: + __import__(modname) + handler = sys.modules[modname] + # ValueError can be raised if cmd has dots in it. + # and KeyError if cmd is otherwise good but ends with a dot. + # and TypeError if cmd has a null byte. + except (ImportError, ValueError, KeyError, TypeError) as e: # If we're on line zero, it was the Subject: header that didn't # contain a command. It's possible there's a Re: prefix (or # localized version thereof) on the Subject: line that's messing # things up. Pop the prefix off and try again... once. + # + # At least one MUA (163.com web mail) has been observed that + # inserts 'Re:' with no following space, so try to account for + # that too. + # + # If that still didn't work it isn't enough to stop processing. + # BAW: should we include a message that the Subject: was ignored? + # + # But first, be sure we're looking at the Subject: and not past + # it already. if self.lineno != 0: return BADCMD if self.subjcmdretried < 1: self.subjcmdretried += 1 if re.search('^.*:.+', cmd): - cmd = re.sub('.*:', '', cmd).lower().strip() + cmd = re.sub('.*:', '', cmd).lower() return self.do_command(cmd, args) if self.subjcmdretried < 2 and args: self.subjcmdretried += 1 - cmd = args.pop(0).lower().strip() + cmd = args.pop(0).lower() return self.do_command(cmd, args) return BADSUBJ - - # Try to import a command handler module for this command - modname = 'Mailman.Commands.cmd_' + cmd - try: - __import__(modname) - handler = sys.modules[modname] - except (ImportError, ValueError, KeyError, TypeError) as e: - syslog('error', 'CommandRunner: Failed to import command module %s: %s', - modname, str(e)) - return BADCMD - if handler.process(self, args): return STOP else: @@ -211,18 +168,8 @@ def do_command(self, cmd, args=None): def send_response(self): # Helper def indent(lines): - """Indent each line with 4 spaces.""" - result = [] - for line in lines: - if isinstance(line, bytes): - try: - # Try UTF-8 first - line = line.decode('utf-8') - except UnicodeDecodeError: - # Fall back to latin-1 if UTF-8 fails - line = line.decode('latin-1') - result.append(' ' + line) - return result + normalized = [line.decode() if isinstance(line, bytes) else line for line in lines] + return [' ' + line for line in normalized] # Quick exit for some commands which don't need a response if not self.respond: return @@ -249,22 +196,15 @@ def indent(lines): resp.append(_('\n- Ignored:')) resp.extend(indent(self.ignored)) resp.append(_('\n- Done.\n\n')) - # Encode any strings into the list charset, so we don't try to - # join strings and invalid ASCII. - charset = Utils.GetCharSet(self.msgdata.get('lang', self.mlist.preferred_language)) + # Encode any unicode strings into the list charset, so we don't try to + # join unicode strings and invalid ASCII. + charset = Utils.GetCharSet(self.msgdata['lang']) encoded_resp = [] for item in resp: - if isinstance(item, str): - item = item.encode(charset, 'replace') - # Convert bytes to string for joining if isinstance(item, bytes): - try: - item = item.decode(charset, 'replace') - except UnicodeDecodeError: - item = item.decode('latin-1', 'replace') + item = item.decode() encoded_resp.append(item) - # Join all items as strings - results = MIMEText(NL.join(str(item) for item in encoded_resp), _charset=charset) + results = MIMEText(NL.join(encoded_resp), _charset=charset) # Safety valve for mail loops with misconfigured email 'bots. We # don't respond to commands sent with "Precedence: bulk|junk|list" # unless they explicitly "X-Ack: yes", but not all mail 'bots are @@ -274,13 +214,13 @@ def indent(lines): # BAW: We wait until now to make this decision since our sender may # not be self.msg.get_sender(), but I'm not sure this is right. recip = self.returnaddr or self.msg.get_sender() - if not self.mlist.autorespondToSender(recip, self.msgdata.get('lang', self.mlist.preferred_language)): + if not self.mlist.autorespondToSender(recip, self.msgdata['lang']): return - msg = UserNotification( + msg = Message.UserNotification( recip, self.mlist.GetOwnerEmail(), _('The results of your email commands'), - lang=self.msgdata.get('lang', self.mlist.preferred_language)) + lang=self.msgdata['lang']) msg.set_type('multipart/mixed') msg.attach(results) if mm_cfg.RESPONSE_INCLUDE_LEVEL == 1: @@ -293,250 +233,61 @@ def indent(lines): else: orig = MIMEMessage(self.msg) msg.attach(orig) - # Add recipient to msgdata to ensure proper delivery - msgdata = {'recipient': recip} - msg.send(self.mlist, msgdata=msgdata) + msg.send(self.mlist) + + class CommandRunner(Runner): QDIR = mm_cfg.CMDQUEUE_DIR - def _validate_message(self, msg, msgdata): - """Validate a command message. - - Args: - msg: The message to validate - msgdata: Additional message metadata - - Returns: - tuple: (msg, success) where success is True if validation passed - """ - try: - # Convert email.message.Message to Mailman.Message if needed - if isinstance(msg, email.message.Message) and not isinstance(msg, Message): - mailman_msg = Message() - # Copy all attributes from the original message - for key, value in msg.items(): - mailman_msg[key] = value - # Copy the payload with proper MIME handling - if msg.is_multipart(): - for part in msg.get_payload(): - if isinstance(part, email.message.Message): - mailman_msg.attach(part) - else: - newpart = Message() - newpart.set_payload(part) - mailman_msg.attach(newpart) - else: - mailman_msg.set_payload(msg.get_payload()) - msg = mailman_msg - - # Check for required headers - if not msg.get('message-id'): - syslog('error', 'CommandRunner._validate_message: Missing Message-ID header') - return msg, False - - if not msg.get('from'): - syslog('error', 'CommandRunner._validate_message: Missing From header') - return msg, False - - # Check for command type in msgdata - if not any(key in msgdata for key in ('torequest', 'tojoin', 'toleave', 'toconfirm')): - syslog('error', 'CommandRunner._validate_message: No command type found in msgdata') - return msg, False - - return msg, True - - except Exception as e: - syslog('error', 'CommandRunner._validate_message: Error validating message: %s', str(e)) - return msg, False - def _dispose(self, mlist, msg, msgdata): - """Process a command message. - - Args: - mlist: The MailList instance this message is destined for - msg: The Message object representing the message - msgdata: Dictionary of message metadata - - Returns: - bool: True if message should be requeued, False if processing is complete - """ - msgid = msg.get('message-id', 'n/a') - filebase = msgdata.get('_filebase', 'unknown') - - # Ensure we have a MailList object - if isinstance(mlist, str): - try: - mlist = get_maillist()(mlist, lock=0) - should_unlock = True - except Errors.MMUnknownListError: - syslog('error', 'CommandRunner: Unknown list %s', mlist) - self._shunt.enqueue(msg, msgdata) - return False - else: - should_unlock = False - - try: - syslog('debug', 'CommandRunner._dispose: Starting to process command message %s (file: %s) for list %s', - msgid, filebase, mlist.internal_name()) - - # Check retry delay and duplicate processing - if not self._check_retry_delay(msgid, filebase): - syslog('debug', 'CommandRunner._dispose: Message %s failed retry delay check, skipping', msgid) - return True - - # Validate message type first - msg, success = self._validate_message(msg, msgdata) - if not success: - syslog('error', 'CommandRunner._dispose: Message validation failed for message %s', msgid) - msgdata['_validation_failure'] = 'Missing required headers' - self._shunt.enqueue(msg, msgdata) - return False - - # The policy here is similar to the Replybot policy. If a message has - # "Precedence: bulk|junk|list" and no "X-Ack: yes" header, we discard - # it to prevent replybot response storms. - precedence = msg.get('precedence', '').lower() - ack = msg.get('x-ack', '').lower() - if ack != 'yes' and precedence in ('bulk', 'junk', 'list'): - syslog('vette', 'Precedence: %s message discarded by: %s', - precedence, mlist.GetRequestEmail()) - return False - - # Lock the list before any operations - try: - mlist.Lock(timeout=mm_cfg.LIST_LOCK_TIMEOUT) - except LockFile.TimeOutError: - # Oh well, try again later - return True - - try: - # Check if list is temporarily unavailable - try: - mlist.Load() - except Errors.MMCorruptListDatabaseError as e: - syslog('error', 'CommandRunner._dispose: List %s is temporarily unavailable: %s', - mlist.internal_name(), str(e)) - return True - except Exception as e: - syslog('error', 'CommandRunner._dispose: Error loading list %s: %s', - mlist.internal_name(), str(e)) - return True - - # Do replybot for commands - Replybot = get_replybot() - Replybot.process(mlist, msg, msgdata) - if mlist.autorespond_requests == 1: - syslog('vette', 'replied and discard') - # w/discard - return False - - # Now craft the response - res = Results(mlist, msg, msgdata) - # This message will have been delivered to one of mylist-request, - # mylist-join, or mylist-leave, and the message metadata will contain - # a key to which one was used. - ret = BADCMD - if msgdata.get('torequest', False): - ret = res.process() - elif msgdata.get('tojoin', False): - ret = res.do_command('join') - elif msgdata.get('toleave', False): - ret = res.do_command('leave') - elif msgdata.get('toconfirm', False): - mo = re.match(mm_cfg.VERP_CONFIRM_REGEXP, msg.get('to', ''), re.IGNORECASE) - if mo: - ret = res.do_command('confirm', (mo.group('cookie'),)) - if ret == BADCMD and mm_cfg.DISCARD_MESSAGE_WITH_NO_COMMAND: - syslog('vette', - 'No command, message discarded, msgid: %s', - msg.get('message-id', 'n/a')) - return False - else: - res.send_response() - mlist.Save() - return False - finally: - mlist.Unlock() - - except Exception as e: - syslog('error', 'CommandRunner._dispose: Error processing command message %s: %s\nTraceback:\n%s', - msgid, str(e), traceback.format_exc()) - self._shunt.enqueue(msg, msgdata) + # The policy here is similar to the Replybot policy. If a message has + # "Precedence: bulk|junk|list" and no "X-Ack: yes" header, we discard + # it to prevent replybot response storms. + precedence = msg.get('precedence', '').lower() + ack = msg.get('x-ack', '').lower() + if ack != 'yes' and precedence in ('bulk', 'junk', 'list'): + syslog('vette', 'Precedence: %s message discarded by: %s', + precedence, mlist.GetRequestEmail()) return False - finally: - if should_unlock: - mlist.Unlock() - - def _oneloop(self): - """Process one batch of messages from the command queue.""" + # Do replybot for commands + mlist.Load() + Replybot.process(mlist, msg, msgdata) + if mlist.autorespond_requests == 1: + syslog('vette', 'replied and discard') + # w/discard + return False + # Now craft the response + res = Results(mlist, msg, msgdata) + # BAW: Not all the functions of this qrunner require the list to be + # locked. Still, it's more convenient to lock it here and now and + # deal with lock failures in one place. try: - # Get the list of files to process - files = self._switchboard.files() - if not files: - syslog('debug', 'CommandRunner: No files to process') - return - - syslog('debug', 'CommandRunner: Processing %d files', len(files)) - - # Process each file - for filebase in files: - try: - # Check if the file exists before dequeuing - pckfile = os.path.join(self.QDIR, filebase + '.pck') - if not os.path.exists(pckfile): - syslog('error', 'CommandRunner._oneloop: File %s does not exist, skipping', pckfile) - continue - - # Check if file is locked - lockfile = os.path.join(self.QDIR, filebase + '.pck.lock') - if os.path.exists(lockfile): - syslog('debug', 'CommandRunner._oneloop: File %s is locked by another process, skipping', filebase) - continue - - # Dequeue the file - msg, msgdata = self._switchboard.dequeue(filebase) - if msg is None: - syslog('debug', 'CommandRunner._oneloop: No message data for %s', filebase) - continue - - # Get the list name from msgdata - listname = msgdata.get('listname') - if not listname: - syslog('error', 'CommandRunner._oneloop: No listname in message data for file %s', filebase) - self._shunt.enqueue(msg, msgdata) - continue - - # Open the list - try: - mlist = MailList.MailList(listname, lock=False) - except Errors.MMUnknownListError: - syslog('error', 'CommandRunner._oneloop: Unknown list %s for message %s (file: %s)', - listname, msg.get('message-id', 'n/a'), filebase) - self._shunt.enqueue(msg, msgdata) - continue - - try: - # Process the message - self._dispose(mlist, msg, msgdata) - syslog('debug', 'CommandRunner: Successfully processed message %s', filebase) - except Exception as e: - syslog('error', 'CommandRunner: Error processing %s: %s', filebase, str(e)) - syslog('error', 'CommandRunner: Traceback:\n%s', traceback.format_exc()) - self._handle_error(e, msg, mlist) - finally: - mlist.Unlock() - - except Exception as e: - syslog('error', 'CommandRunner: Error processing file %s: %s', filebase, str(e)) - syslog('error', 'CommandRunner: Traceback:\n%s', traceback.format_exc()) - continue - - except Exception as e: - syslog('error', 'CommandRunner: Error in _oneloop: %s', str(e)) - syslog('error', 'CommandRunner: Traceback:\n%s', traceback.format_exc()) - raise - -# Set up i18n -_ = i18n._ -i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + mlist.Lock(timeout=mm_cfg.LIST_LOCK_TIMEOUT) + except LockFile.TimeOutError: + # Oh well, try again later + return True + # This message will have been delivered to one of mylist-request, + # mylist-join, or mylist-leave, and the message metadata will contain + # a key to which one was used. + try: + ret = BADCMD + if msgdata.get('torequest'): + ret = res.process() + elif msgdata.get('tojoin'): + ret = res.do_command('join') + elif msgdata.get('toleave'): + ret = res.do_command('leave') + elif msgdata.get('toconfirm'): + mo = re.match(mm_cfg.VERP_CONFIRM_REGEXP, msg.get('to', '')) + if mo: + ret = res.do_command('confirm', (mo.group('cookie'),)) + if ret == BADCMD and mm_cfg.DISCARD_MESSAGE_WITH_NO_COMMAND: + syslog('vette', + 'No command, message discarded, msgid: %s', + msg.get('message-id', 'n/a')) + else: + res.send_response() + mlist.Save() + finally: + mlist.Unlock() diff --git a/Mailman/Queue/IncomingRunner.py b/Mailman/Queue/IncomingRunner.py index fc819820..e14d5316 100644 --- a/Mailman/Queue/IncomingRunner.py +++ b/Mailman/Queue/IncomingRunner.py @@ -14,11 +14,7 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -"""Incoming message queue runner. - -This qrunner handles messages that are posted to the mailing list. It is -responsible for running the message through the pipeline of handlers. -""" +"""Incoming queue runner.""" # A typical Mailman list exposes nine aliases which point to seven different # wrapped scripts. E.g. for a list named `mylist', you'd have: @@ -97,65 +93,22 @@ # performed. Results notifications are sent to the author of the message, # which all bounces pointing back to the -bounces address. -import os + import sys -import time -import traceback -from io import StringIO -import random -import signal import os -import email -from email import message_from_string -from email.message import Message as EmailMessage -from urllib.parse import parse_qs -from Mailman.Utils import reap -from Mailman import Utils +from io import StringIO from Mailman import mm_cfg from Mailman import Errors from Mailman import LockFile from Mailman.Queue.Runner import Runner -from Mailman.Queue.Switchboard import Switchboard -from Mailman.Logging.Syslog import mailman_log -import Mailman.MailList as MailList -import Mailman.Message -import threading -import email.header - - -class PipelineError(Exception): - """Exception raised when pipeline processing fails.""" - pass +from Mailman.Logging.Syslog import syslog + class IncomingRunner(Runner): QDIR = mm_cfg.INQUEUE_DIR - # Enable message tracking for incoming messages - _track_messages = True - _max_processed_messages = 10000 - _max_retry_times = 10000 - - # Retry configuration - MIN_RETRY_DELAY = 300 # 5 minutes minimum delay between retries - MAX_RETRIES = 5 # Maximum number of retry attempts - _retry_times = {} # Track last retry time for each message - - def __init__(self, slice=None, numslices=1): - mailman_log('debug', 'IncomingRunner: Starting initialization') - try: - Runner.__init__(self, slice, numslices) - mailman_log('debug', 'IncomingRunner: Initialization complete') - except Exception as e: - mailman_log('error', 'IncomingRunner: Initialization failed: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - raise - - def _convert_message(self, msg): - """Convert email.message.Message to Mailman.Message with proper handling of nested messages.""" - return Runner._convert_message(self, msg) - def _dispose(self, mlist, msg, msgdata): # Try to get the list lock. try: @@ -182,12 +135,25 @@ def _dispose(self, mlist, msg, msgdata): finally: mlist.Unlock() + # Overridable def _get_pipeline(self, mlist, msg, msgdata): # We must return a copy of the list, otherwise, the first message that # flows through the pipeline will empty it out! - return msgdata.get('pipeline', - getattr(mlist, 'pipeline', - mm_cfg.GLOBAL_PIPELINE))[:] + pipeline = msgdata.get('pipeline') + if pipeline is None: + pipeline = getattr(mlist, 'pipeline', None) + else: + # Use the already-imported mm_cfg directly + pipeline = mm_cfg.GLOBAL_PIPELINE + + # Ensure pipeline is a list that can be sliced + if not isinstance(pipeline, list): + syslog('error', 'pipeline is not a list: %s (type: %s)', + pipeline, type(pipeline).__name__) + # Fallback to a basic pipeline + pipeline = mm_cfg.GLOBAL_PIPELINE + + return pipeline[:] def _dopipeline(self, mlist, msg, msgdata, pipeline): while pipeline: @@ -199,7 +165,7 @@ def _dopipeline(self, mlist, msg, msgdata, pipeline): sys.modules[modname].process(mlist, msg, msgdata) # Failsafe -- a child may have leaked through. if pid != os.getpid(): - mailman_log('error', 'Child process leaked through: %s', modname) + syslog('error', 'child process leaked thru: %s', modname) os._exit(1) except Errors.DiscardMessage: # Throw the message away; we need do nothing else with it. @@ -207,7 +173,7 @@ def _dopipeline(self, mlist, msg, msgdata, pipeline): # just in case the syslog call throws an exception and the # message is shunted. pipeline.insert(0, handler) - mailman_log('vette', """Message discarded, msgid: %s + syslog('vette', """Message discarded, msgid: %s' list: %s, handler: %s""", msg.get('message-id', 'n/a'), @@ -223,7 +189,7 @@ def _dopipeline(self, mlist, msg, msgdata, pipeline): # just in case the syslog call or BounceMessage throws an # exception and the message is shunted. pipeline.insert(0, handler) - mailman_log('vette', """Message rejected, msgid: %s + syslog('vette', """Message rejected, msgid: %s list: %s, handler: %s, reason: %s""", @@ -238,322 +204,3 @@ def _dopipeline(self, mlist, msg, msgdata, pipeline): raise # We've successfully completed handling of this message return 0 - - def _is_command(self, msg): - """Check if the message is a command.""" - try: - subject = msg.get('subject', '').lower() - if subject.startswith('subscribe') or subject.startswith('unsubscribe'): - mailman_log('debug', 'IncomingRunner._is_command: Message is a subscription command') - return True - return False - except Exception as e: - mailman_log('error', 'IncomingRunner._is_command: Error checking command: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - return False - - def _is_bounce(self, msg): - """Check if a message is a bounce message.""" - # Check for common bounce headers - if msg.get('x-failed-recipients'): - return True - if msg.get('x-original-to'): - return True - if msg.get('return-path', '').startswith('<>'): - return True - # Check content type for multipart/report - if msg.get('content-type', '').startswith('multipart/report'): - return True - # Check for common bounce subjects - subject = msg.get('subject', '') - if isinstance(subject, email.header.Header): - subject = str(subject) - subject = subject.lower() - bounce_subjects = ['delivery status', 'failure notice', 'mail delivery failed', - 'mail delivery system', 'mail system error', 'returned mail', - 'undeliverable', 'undelivered mail'] - for bounce_subject in bounce_subjects: - if bounce_subject in subject: - return True - return False - - def _process_command(self, mlist, msg, msgdata): - """Process a command message.""" - msgid = msg.get('message-id', 'n/a') - try: - mailman_log('debug', 'IncomingRunner._process_command: Processing command for message %s', msgid) - # Process the command - # ... command processing logic ... - mailman_log('debug', 'IncomingRunner._process_command: Successfully processed command for message %s', msgid) - return True - except Exception as e: - mailman_log('error', 'IncomingRunner._process_command: Error processing command for message %s: %s\nTraceback:\n%s', - msgid, str(e), traceback.format_exc()) - return False - - def _process_bounce(self, mlist, msg, msgdata): - """Process a bounce message.""" - msgid = msg.get('message-id', 'n/a') - try: - mailman_log('debug', 'IncomingRunner._process_bounce: Processing bounce for message %s', msgid) - # Process the bounce - # ... bounce processing logic ... - mailman_log('debug', 'IncomingRunner._process_bounce: Successfully processed bounce for message %s', msgid) - return True - except Exception as e: - mailman_log('error', 'IncomingRunner._process_bounce: Error processing bounce for message %s: %s\nTraceback:\n%s', - msgid, str(e), traceback.format_exc()) - return False - - def _process_regular_message(self, mlist, msg, msgdata): - """Process a regular message.""" - msgid = msg.get('message-id', 'n/a') - try: - mailman_log('debug', 'IncomingRunner._process_regular_message: Processing regular message %s', msgid) - # Process the regular message - # ... regular message processing logic ... - mailman_log('debug', 'IncomingRunner._process_regular_message: Successfully processed regular message %s', msgid) - return True - except Exception as e: - mailman_log('error', 'IncomingRunner._process_regular_message: Error processing regular message %s: %s\nTraceback:\n%s', - msgid, str(e), traceback.format_exc()) - return False - - def _cleanup(self): - """Clean up resources.""" - mailman_log('debug', 'IncomingRunner: Starting cleanup') - try: - Runner._cleanup(self) - except Exception as e: - mailman_log('error', 'IncomingRunner: Cleanup failed: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - mailman_log('debug', 'IncomingRunner: Cleanup complete') - - def _oneloop(self): - """Process one batch of messages from the incoming queue.""" - try: - # Get the list of files to process - files = self._switchboard.files() - filecnt = len(files) - - # Only log at debug level if we found files to process - if filecnt > 0: - mailman_log('debug', 'IncomingRunner._oneloop: Found %d files to process', filecnt) - - # Process each file - for filebase in files: - # Check stop flag at the start of each file - if self._stop: - mailman_log('debug', 'IncomingRunner._oneloop: Stop flag detected, stopping processing') - return filecnt - - try: - # Check if the file exists before dequeuing - pckfile = os.path.join(self.QDIR, filebase + '.pck') - if not os.path.exists(pckfile): - mailman_log('error', 'IncomingRunner._oneloop: File %s does not exist, skipping', pckfile) - continue - - # Check if file is locked - lockfile = os.path.join(self.QDIR, filebase + '.pck.lock') - if os.path.exists(lockfile): - mailman_log('debug', 'IncomingRunner._oneloop: File %s is locked by another process, skipping', filebase) - continue - - # Dequeue the file - msg, msgdata = self._switchboard.dequeue(filebase) - - # If dequeue failed due to file being locked, skip it - if msg is None and msgdata is None: - # For other None,None cases, shunt the message - mailman_log('error', 'IncomingRunner._oneloop: Failed to dequeue file %s (got None values), shunting', filebase) - # Create a basic message and metadata if we don't have them - msg = Message() - msgdata = {} - # Add the original queue information - msgdata['whichq'] = self.QDIR - # Shunt the message - self._shunt.enqueue(msg, msgdata) - # Remove the original file - try: - os.unlink(pckfile) - mailman_log('debug', 'IncomingRunner._oneloop: Removed original file %s', pckfile) - except OSError as e: - mailman_log('error', 'IncomingRunner._oneloop: Failed to remove original file %s: %s', pckfile, str(e)) - continue - - # Try to get message-id early for logging purposes - try: - msgid = msg.get('message-id', 'n/a') - except Exception as e: - msgid = 'unknown' - mailman_log('error', 'IncomingRunner._oneloop: Error getting message-id for file %s: %s', filebase, str(e)) - - # Get the list name - listname = msgdata.get('listname', 'unknown') - try: - mlist = MailList.MailList(listname, lock=False) - except Errors.MMUnknownListError: - mailman_log('error', 'IncomingRunner._oneloop: Unknown list %s for message %s (file: %s)', - listname, msgid, filebase) - self._shunt.enqueue(msg, msgdata) - # Remove the original file - try: - os.unlink(pckfile) - mailman_log('debug', 'IncomingRunner._oneloop: Removed original file %s', pckfile) - except OSError as e: - mailman_log('error', 'IncomingRunner._oneloop: Failed to remove original file %s: %s', pckfile, str(e)) - continue - - # Process the message - try: - result = self._dispose(mlist, msg, msgdata) - - # If the message should be kept in the queue, requeue it - if result: - # Get pipeline information for logging - pipeline = msgdata.get('pipeline', []) - current_handler = pipeline[0] if pipeline else 'unknown' - next_handler = pipeline[1] if len(pipeline) > 1 else 'none' - - # Get retry information - retry_count = msgdata.get('retry_count', 0) - last_retry = self._retry_times.get(msgid, 0) - next_retry = time.ctime(last_retry + self.MIN_RETRY_DELAY) if last_retry else 'unknown' - - # Log detailed requeue information - mailman_log('info', 'IncomingRunner._oneloop: Message requeued for later processing: %s (msgid: %s)', - filebase, msgid) - mailman_log('debug', ' Current state:') - mailman_log('debug', ' - Current handler: %s', current_handler) - mailman_log('debug', ' - Next handler: %s', next_handler) - mailman_log('debug', ' - Retry count: %d', retry_count) - mailman_log('debug', ' - Last retry: %s', time.ctime(last_retry) if last_retry else 'none') - mailman_log('debug', ' - Next retry: %s', next_retry) - mailman_log('debug', ' - List: %s', mlist.internal_name()) - mailman_log('debug', ' - Message type: %s', msgdata.get('_msgtype', 'unknown')) - - # Requeue the message and remove the original file - self._switchboard.enqueue(msg, msgdata) - try: - os.unlink(pckfile) - mailman_log('debug', 'IncomingRunner._oneloop: Removed original file %s', pckfile) - except OSError as e: - mailman_log('error', 'IncomingRunner._oneloop: Failed to remove original file %s: %s', pckfile, str(e)) - else: - mailman_log('info', 'IncomingRunner._oneloop: Message processing complete, moving to shunt queue %s (msgid: %s)', - filebase, msgid) - # Move to shunt queue and remove the original file - self._shunt.enqueue(msg, msgdata) - try: - os.unlink(pckfile) - mailman_log('debug', 'IncomingRunner._oneloop: Removed original file %s', pckfile) - except OSError as e: - mailman_log('error', 'IncomingRunner._oneloop: Failed to remove original file %s: %s', pckfile, str(e)) - - except Exception as e: - mailman_log('error', 'IncomingRunner._oneloop: Error processing message %s (file: %s): %s\n%s', - msgid, filebase, str(e), traceback.format_exc()) - # Move to shunt queue on error and remove the original file - self._shunt.enqueue(msg, msgdata) - try: - os.unlink(pckfile) - mailman_log('debug', 'IncomingRunner._oneloop: Removed original file %s', pckfile) - except OSError as e: - mailman_log('error', 'IncomingRunner._oneloop: Failed to remove original file %s: %s', pckfile, str(e)) - - except Exception as e: - mailman_log('error', 'IncomingRunner._oneloop: Error dequeuing file %s: %s\n%s', - filebase, str(e), traceback.format_exc()) - - # Only log completion at debug level if we processed files - if filecnt > 0: - mailman_log('debug', 'IncomingRunner._oneloop: Loop complete, processed %d files', filecnt) - - except Exception as e: - mailman_log('error', 'IncomingRunner._oneloop: Unexpected error in main loop: %s\n%s', - str(e), traceback.format_exc()) - # Don't re-raise the exception to keep the runner alive - return False - return True - - def _check_retry_delay(self, msgid, filebase): - """Check if enough time has passed since the last retry attempt.""" - now = time.time() - last_retry = self._retry_times.get(msgid, 0) - - if now - last_retry < self.MIN_RETRY_DELAY: - mailman_log('debug', 'IncomingRunner._check_retry_delay: Message %s (file: %s) retry delay not met. Last retry: %s, Now: %s, Delay needed: %s', - msgid, filebase, time.ctime(last_retry), time.ctime(now), self.MIN_RETRY_DELAY) - return False - - mailman_log('debug', 'IncomingRunner._check_retry_delay: Message %s (file: %s) retry delay met. Last retry: %s, Now: %s', - msgid, filebase, time.ctime(last_retry), time.ctime(now)) - return True - - def _mark_message_processed(self, msgid): - """Mark a message as processed.""" - with self._processed_lock: - self._processed_messages.add(msgid) - - def _unmark_message_processed(self, msgid): - """Remove a message from the processed set.""" - with self._processed_lock: - self._processed_messages.discard(msgid) - - def _process_admin(self, mlist, msg, msgdata): - """Process an admin message.""" - msgid = msg.get('message-id', 'n/a') - try: - mailman_log('debug', 'IncomingRunner._process_admin: Processing admin message %s', msgid) - - # Get admin information - recipient = msgdata.get('recipient', 'unknown') - admin_type = msgdata.get('admin_type', 'unknown') - - mailman_log('debug', 'IncomingRunner._process_admin: Admin message for %s, type: %s', - recipient, admin_type) - - # Process the admin message - # ... admin message processing logic ... - - mailman_log('debug', 'IncomingRunner._process_admin: Successfully processed admin message %s', msgid) - return True - - except Exception as e: - mailman_log('error', 'IncomingRunner._process_admin: Error processing admin message %s: %s\nTraceback:\n%s', - msgid, str(e), traceback.format_exc()) - return False - - def _check_message_processed(self, msgid, filebase, msg): - """Check if a message has already been processed and if retry delay is met. - - Args: - msgid: The message ID to check - filebase: The base filename of the message - msg: The message object - - Returns: - bool: True if message should be skipped (already processed or retry delay not met), - False if message should be processed - """ - try: - # Check if message was recently processed - with self._processed_lock: - if msgid in self._processed_messages: - mailman_log('debug', 'IncomingRunner._check_message_processed: Message %s (file: %s) was recently processed, skipping', - msgid, filebase) - return True - - # Check if retry delay is met - if not self._check_retry_delay(msgid, filebase): - return True - - # Message should be processed - return False - - except Exception as e: - mailman_log('error', 'IncomingRunner._check_message_processed: Error checking message %s: %s\nTraceback:\n%s', - msgid, str(e), traceback.format_exc()) - # On error, allow the message to be processed - return False diff --git a/Mailman/Queue/MaildirRunner.py b/Mailman/Queue/MaildirRunner.py index 0c7d371b..78017132 100644 --- a/Mailman/Queue/MaildirRunner.py +++ b/Mailman/Queue/MaildirRunner.py @@ -47,25 +47,22 @@ mechanism. """ +# NOTE: Maildir delivery is experimental in Mailman 2.1. + from builtins import str import os import re import errno -import time -import traceback -from io import StringIO -import email -from email.utils import getaddresses, parsedate_tz, mktime_tz, parseaddr -from email.iterators import body_line_iterator + +from email.Parser import Parser +from email.utils import parseaddr from Mailman import mm_cfg from Mailman import Utils -from Mailman import Errors -from Mailman import i18n from Mailman.Message import Message -from Mailman.Logging.Syslog import syslog from Mailman.Queue.Runner import Runner from Mailman.Queue.sbcache import get_switchboard +from Mailman.Logging.Syslog import syslog # We only care about the listname and the subq as in listname@ or # listname-request@ @@ -90,48 +87,36 @@ """, re.VERBOSE | re.IGNORECASE) + class MaildirRunner(Runner): # This class is much different than most runners because it pulls files # of a different format than what scripts/post and friends leaves. The # files this runner reads are just single message files as dropped into # the directory by the MTA. This runner will read the file, and enqueue # it in the expected qfiles directory for normal processing. - QDIR = mm_cfg.MAILDIR_DIR - def __init__(self, slice=None, numslices=1): - syslog('debug', 'MaildirRunner: Starting initialization') - try: - Runner.__init__(self, slice, numslices) - self._dir = os.path.join(mm_cfg.MAILDIR_DIR, 'new') - self._cur = os.path.join(mm_cfg.MAILDIR_DIR, 'cur') - if not os.path.exists(self._dir): - os.makedirs(self._dir) - if not os.path.exists(self._cur): - os.makedirs(self._cur) - syslog('debug', 'MaildirRunner: Initialization complete') - except Exception as e: - syslog('error', 'MaildirRunner: Initialization failed: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - raise + # Don't call the base class constructor, but build enough of the + # underlying attributes to use the base class's implementation. + self._stop = 0 + self._dir = os.path.join(mm_cfg.MAILDIR_DIR, 'new') + self._cur = os.path.join(mm_cfg.MAILDIR_DIR, 'cur') + self._parser = Parser(Message) def _oneloop(self): - """Process one batch of messages from the maildir.""" - # Refresh this each time through the list + # Refresh this each time through the list. BAW: could be too + # expensive. listnames = Utils.list_names() + # Cruise through all the files currently in the new/ directory try: files = os.listdir(self._dir) except OSError as e: - if e.errno != errno.ENOENT: - syslog('error', 'Error listing maildir directory: %s', str(e)) - raise + if e.errno != errno.ENOENT: raise # Nothing's been delivered yet return 0 - for file in files: srcname = os.path.join(self._dir, file) dstname = os.path.join(self._cur, file + ':1,P') xdstname = os.path.join(self._cur, file + ':1,X') - try: os.rename(srcname, dstname) except OSError as e: @@ -140,17 +125,19 @@ def _oneloop(self): continue syslog('error', 'Could not rename maildir file: %s', srcname) raise - + # Now open, read, parse, and enqueue this message try: - # Read and parse the message - with open(dstname, 'rb') as fp: - msg = email.message_from_binary_file(fp) - - # Figure out which queue of which list this message was destined for + fp = open(dstname) + try: + msg = self._parser.parse(fp) + finally: + fp.close() + # Now we need to figure out which queue of which list this + # message was destined for. See verp_bounce() in + # BounceRunner.py for why we do things this way. vals = [] for header in ('delivered-to', 'envelope-to', 'apparently-to'): vals.extend(msg.get_all(header, [])) - for field in vals: to = parseaddr(field)[1] if not to: @@ -164,14 +151,14 @@ def _oneloop(self): break else: # As far as we can tell, this message isn't destined for - # any list on the system + # any list on the system. What to do? syslog('error', 'Message apparently not for any list: %s', xdstname) os.rename(dstname, xdstname) continue - - # Determine which queue to use based on the subqueue + # BAW: blech, hardcoded msgdata = {'listname': listname} + # -admin is deprecated if subq in ('bounces', 'admin'): queue = get_switchboard(mm_cfg.BOUNCEQUEUE_DIR) elif subq == 'confirm': @@ -200,29 +187,11 @@ def _oneloop(self): syslog('error', 'Unknown sub-queue: %s', subq) os.rename(dstname, xdstname) continue - - # Enqueue the message and clean up queue.enqueue(msg, msgdata) os.unlink(dstname) - syslog('debug', 'Successfully processed maildir message: %s', file) - except Exception as e: - syslog('error', 'Error processing maildir file %s: %s\nTraceback:\n%s', - file, str(e), traceback.format_exc()) - try: - os.rename(dstname, xdstname) - except OSError: - pass - - return len(files) + os.rename(dstname, xdstname) + syslog('error', str(e)) def _cleanup(self): - """Clean up resources.""" - syslog('debug', 'MaildirRunner: Starting cleanup') - try: - # Call parent cleanup - super(MaildirRunner, self)._cleanup() - except Exception as e: - syslog('error', 'MaildirRunner: Cleanup failed: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - syslog('debug', 'MaildirRunner: Cleanup complete') + pass diff --git a/Mailman/Queue/NewsRunner.py b/Mailman/Queue/NewsRunner.py index 75a345d5..4a6c92d4 100644 --- a/Mailman/Queue/NewsRunner.py +++ b/Mailman/Queue/NewsRunner.py @@ -20,33 +20,24 @@ from builtins import str import re import socket +try: + import nntplib + NNTPLIB_AVAILABLE = True +except ImportError: + NNTPLIB_AVAILABLE = False from io import StringIO -import time -import traceback -import os -import pickle import email -from email.utils import getaddresses, parsedate_tz, mktime_tz -from email.iterators import body_line_iterator +import email.iterators +from email.utils import getaddresses COMMASPACE = ', ' from Mailman import mm_cfg from Mailman import Utils -from Mailman import Errors -from Mailman import i18n from Mailman.Queue.Runner import Runner -from Mailman.Logging.Syslog import mailman_log, syslog -import Mailman.Message as Message -import Mailman.MailList as MailList +from Mailman.Logging.Syslog import syslog -# Only import nntplib if NNTP support is enabled -try: - import nntplib - HAVE_NNTP = True -except ImportError: - HAVE_NNTP = False # Matches our Mailman crafted Message-IDs. See Utils.unique_message_id() mcre = re.compile(r""" @@ -61,207 +52,55 @@ """, re.VERBOSE) + class NewsRunner(Runner): QDIR = mm_cfg.NEWSQUEUE_DIR - def __init__(self, slice=None, numslices=1): - # First check if NNTP support is enabled - if not mm_cfg.NNTP_SUPPORT: - syslog('warning', 'NNTP support is not enabled. NewsRunner will not process messages.') - return - if not mm_cfg.DEFAULT_NNTP_HOST: - syslog('info', 'NewsRunner not processing messages due to DEFAULT_NNTP_HOST not being set') - return - # Initialize the base class - Runner.__init__(self, slice, numslices) - # Check if any lists require NNTP support - self._nntp_lists = [] - for listname in Utils.list_names(): - try: - mlist = MailList.MailList(listname, lock=False) - if mlist.nntp_host: - self._nntp_lists.append(listname) - except Errors.MMListError: - continue - if not self._nntp_lists: - syslog('info', 'No lists require NNTP support. NewsRunner will not be started.') - return - # Initialize the NNTP connection - self._nntp = None - self._connect() - - def _connect(self): - """Connect to the NNTP server.""" - try: - self._nntp = nntplib.NNTP(mm_cfg.DEFAULT_NNTP_HOST, - mm_cfg.DEFAULT_NNTP_PORT, - mm_cfg.DEFAULT_NNTP_USER, - mm_cfg.DEFAULT_NNTP_PASS) - except Exception as e: - syslog('error', 'NewsRunner error: %s', str(e)) - self._nntp = None - - def _validate_message(self, msg, msgdata): - """Validate the message for news posting. - - Args: - msg: The message to validate - msgdata: Additional message metadata - - Returns: - tuple: (msg, success) where success is True if validation passed - """ - try: - # Check if the message has a Message-ID - if not msg.get('message-id'): - syslog('error', 'Message validation failed for news message') - return msg, False - return msg, True - except Exception as e: - syslog('error', 'Error validating news message: %s', str(e)) - return msg, False - def _dispose(self, mlist, msg, msgdata): - """Post the message to the newsgroup.""" - try: - # Get the newsgroup name - newsgroup = mlist.nntp_host - if not newsgroup: - return False - # Post the message - self._nntp.post(str(msg)) - return False - except Exception as e: - syslog('error', 'Error posting message to newsgroup for list %s: %s', - mlist.internal_name(), str(e)) - return True - - def _onefile(self, msg, msgdata): - """Process a single news message. + # Make sure we have the most up-to-date state + mlist.Load() + if not msgdata.get('prepped'): + prepare_message(mlist, msg, msgdata) - This method overrides the base class's _onefile to add news-specific - validation and processing. + # Check if nntplib is available + if not NNTPLIB_AVAILABLE: + syslog('error', + '(NewsRunner) nntplib not available, cannot post to newsgroup for list "%s"', + mlist.internal_name()) + return False # Don't requeue, just drop the message - Args: - msg: The message to process - msgdata: Additional message metadata - """ try: - # Validate the message - msg, success = self._validate_message(msg, msgdata) - if not success: - syslog('error', 'NewsRunner._onefile: Message validation failed') - self._shunt.enqueue(msg, msgdata) - return - - # Get the list name from the message data - listname = msgdata.get('listname') - if not listname: - syslog('error', 'NewsRunner._onefile: No listname in message data') - self._shunt.enqueue(msg, msgdata) - return - - # Open the list + # Flatten the message object, sticking it in a StringIO object + fp = StringIO(msg.as_string()) + conn = None try: - mlist = self._open_list(listname) - except Exception as e: - self.log_error('list_open_error', str(e), listname=listname) - self._shunt.enqueue(msg, msgdata) - return - - # Process the message - try: - keepqueued = self._dispose(mlist, msg, msgdata) - if keepqueued: - self._switchboard.enqueue(msg, msgdata) - except Exception as e: - self._handle_error(e, msg=msg, mlist=mlist) - - except Exception as e: - syslog('error', 'NewsRunner._onefile: Unexpected error: %s', str(e)) - self._shunt.enqueue(msg, msgdata) - - def _oneloop(self): - """Process one batch of messages from the news queue.""" - try: - # Get the list of files to process - files = self._switchboard.files() - filecnt = len(files) - - # Process each file - for filebase in files: try: - # Check if the file exists before dequeuing - pckfile = os.path.join(self.QDIR, filebase + '.pck') - if not os.path.exists(pckfile): - syslog('error', 'NewsRunner._oneloop: File %s does not exist, skipping', pckfile) - continue - - # Check if file is locked - lockfile = os.path.join(self.QDIR, filebase + '.pck.lock') - if os.path.exists(lockfile): - syslog('debug', 'NewsRunner._oneloop: File %s is locked by another process, skipping', filebase) - continue - - # Dequeue the file - msg, msgdata = self._switchboard.dequeue(filebase) - if msg is None: - continue - - # Process the message - try: - self._onefile(msg, msgdata) - except Exception as e: - syslog('error', 'NewsRunner._oneloop: Error processing message %s: %s', filebase, str(e)) - continue - - except Exception as e: - syslog('error', 'NewsRunner._oneloop: Error dequeuing file %s: %s', filebase, str(e)) - continue - + nntp_host, nntp_port = Utils.nntpsplit(mlist.nntp_host) + conn = nntplib.NNTP(nntp_host, nntp_port, + readermode=True, + user=mm_cfg.NNTP_USERNAME, + password=mm_cfg.NNTP_PASSWORD) + conn.post(fp) + except nntplib.error_temp as e: + syslog('error', + '(NNTPDirect) NNTP error for list "%s": %s', + mlist.internal_name(), e) + except socket.error as e: + syslog('error', + '(NNTPDirect) socket error for list "%s": %s', + mlist.internal_name(), e) + finally: + if conn: + conn.quit() except Exception as e: - syslog('error', 'NewsRunner._oneloop: Error in main loop: %s', str(e)) - return 0 - - return filecnt - - def _queue_news(self, listname, msg, msgdata): - """Queue a news message for processing.""" - # Create a unique filename - now = time.time() - filename = os.path.join(mm_cfg.NEWSQUEUE_DIR, - '%d.%d.pck' % (os.getpid(), now)) - - # Write the message and metadata to the pickle file - try: - # Use protocol 4 for Python 3 compatibility - with open(filename, 'wb') as fp: - pickle.dump(listname, fp, protocol=4, fix_imports=True) - pickle.dump(msg, fp, protocol=4, fix_imports=True) - pickle.dump(msgdata, fp, protocol=4, fix_imports=True) - # Set the file's mode appropriately - os.chmod(filename, 0o660) - except (IOError, OSError) as e: - try: - os.unlink(filename) - except (IOError, OSError): - pass - raise SwitchboardError('Could not save news message to %s: %s' % - (filename, e)) - - def _cleanup(self): - """Clean up resources before termination.""" - # Close any open NNTP connections - if hasattr(self, '_nntp') and self._nntp: - try: - self._nntp.quit() - except Exception: - pass - self._nntp = None - # Call parent cleanup - super(NewsRunner, self)._cleanup() + # Some other exception occurred, which we definitely did not + # expect, so set this message up for requeuing. + self._log(e) + return True + return False + def prepare_message(mlist, msg, msgdata): # If the newsgroup is moderated, we need to add this header for the Usenet # software to accept the posting, and not forward it on to the n.g.'s @@ -328,7 +167,7 @@ def prepare_message(mlist, msg, msgdata): # Lines: is useful if msg['Lines'] is None: # BAW: is there a better way? - count = len(list(body_line_iterator(msg))) + count = len(list(email.iterators.body_line_iterator(msg))) msg['Lines'] = str(count) # Massage the message headers by remove some and rewriting others. This # woon't completely sanitize the message, but it will eliminate the bulk diff --git a/Mailman/Queue/OutgoingRunner.py b/Mailman/Queue/OutgoingRunner.py index bf710322..6208be2e 100644 --- a/Mailman/Queue/OutgoingRunner.py +++ b/Mailman/Queue/OutgoingRunner.py @@ -17,324 +17,43 @@ """Outgoing queue runner.""" -from builtins import object -import time -import socket -import smtplib -import traceback import os import sys -from io import StringIO -import threading -import email.message -import fcntl +import copy +import time +import socket + +import email from Mailman import mm_cfg -from Mailman import Utils +from Mailman import Message from Mailman import Errors -from Mailman import i18n -from Mailman.Logging.Syslog import mailman_log +from Mailman import LockFile from Mailman.Queue.Runner import Runner from Mailman.Queue.Switchboard import Switchboard from Mailman.Queue.BounceRunner import BounceMixin -from Mailman.MemberAdaptor import MemberAdaptor, ENABLED -import Mailman.Message as Message - -# Lazy import to avoid circular dependency -def get_mail_list(): - import Mailman.MailList as MailList - return MailList.MailList - -def get_replybot(): - import Mailman.Handlers.Replybot as Replybot - return Replybot +from Mailman.Logging.Syslog import syslog # This controls how often _doperiodic() will try to deal with deferred # permanent failures. It is a count of calls to _doperiodic() DEAL_WITH_PERMFAILURES_EVERY = 10 + class OutgoingRunner(Runner, BounceMixin): QDIR = mm_cfg.OUTQUEUE_DIR - # Process coordination - _pid_file = os.path.join(mm_cfg.LOCK_DIR, 'outgoing.pid') - _pid_lock = None - _running = False - - # Shared processed messages tracking with size limits - _processed_messages = set() - _processed_lock = threading.Lock() - _last_cleanup = time.time() - _cleanup_interval = 3600 # Clean up every hour - _max_processed_messages = 10000 - _max_retry_times = 10000 - - # Message counting - _total_messages_processed = 0 - _total_messages_lock = threading.Lock() - - # Retry configuration - MIN_RETRY_DELAY = 300 # 5 minutes minimum delay between retries - MAX_RETRIES = 5 # Maximum number of retry attempts - _retry_times = {} # Track last retry time for each message - - # Error tracking - _error_count = 0 - _last_error_time = 0 - _error_window = 300 # 5 minutes window for error counting - _max_errors = 10 def __init__(self, slice=None, numslices=1): - """Initialize the outgoing queue runner.""" - mailman_log('debug', 'OutgoingRunner: Initializing with slice=%s, numslices=%s', slice, numslices) - try: - # Check if another instance is already running - if not self._acquire_pid_lock(): - mailman_log('error', 'OutgoingRunner: Another instance is already running') - raise RuntimeError('Another OutgoingRunner instance is already running') - - Runner.__init__(self, slice, numslices) - mailman_log('debug', 'OutgoingRunner: Base Runner initialized') - - BounceMixin.__init__(self) - mailman_log('debug', 'OutgoingRunner: BounceMixin initialized') - - # Initialize processed messages tracking - self._processed_messages = set() - self._last_cleanup = time.time() - - # Initialize error tracking - self._error_count = 0 - self._last_error_time = 0 - - # We look this function up only at startup time - modname = 'Mailman.Handlers.' + mm_cfg.DELIVERY_MODULE - mailman_log('trace', 'OutgoingRunner: Attempting to import delivery module: %s', modname) - - try: - mod = __import__(modname) - mailman_log('trace', 'OutgoingRunner: Successfully imported delivery module') - except ImportError as e: - mailman_log('error', 'OutgoingRunner: Failed to import delivery module %s: %s', modname, str(e)) - mailman_log('error', 'OutgoingRunner: Traceback: %s', traceback.format_exc()) - self._release_pid_lock() - raise - - try: - self._func = getattr(sys.modules[modname], 'process') - mailman_log('trace', 'OutgoingRunner: Successfully got process function from module') - except AttributeError as e: - mailman_log('error', 'OutgoingRunner: Failed to get process function from module %s: %s', modname, str(e)) - mailman_log('error', 'OutgoingRunner: Traceback: %s', traceback.format_exc()) - self._release_pid_lock() - raise - - # This prevents smtp server connection problems from filling up the - # error log. It gets reset if the message was successfully sent, and - # set if there was a socket.error. - self.__logged = False - mailman_log('debug', 'OutgoingRunner: Initializing retry queue') - self.__retryq = Switchboard(mm_cfg.RETRYQUEUE_DIR) - self._running = True - mailman_log('debug', 'OutgoingRunner: Initialization complete') - except Exception as e: - mailman_log('error', 'OutgoingRunner: Initialization failed: %s', str(e)) - mailman_log('error', 'OutgoingRunner: Traceback: %s', traceback.format_exc()) - self._release_pid_lock() - raise - - def run(self): - """Run the outgoing queue runner.""" - mailman_log('debug', 'OutgoingRunner: Starting main loop') - self._running = True - - # Try to acquire the PID lock - if not self._acquire_pid_lock(): - mailman_log('error', 'OutgoingRunner: Failed to acquire PID lock, exiting') - return - - try: - while self._running: - try: - self._oneloop() - # Sleep for a bit to avoid CPU spinning - time.sleep(mm_cfg.QRUNNER_SLEEP_TIME) - except Exception as e: - mailman_log('error', 'OutgoingRunner: Error in main loop: %s', str(e)) - mailman_log('error', 'OutgoingRunner: Traceback:\n%s', traceback.format_exc()) - # Don't exit on error, just log and continue - time.sleep(mm_cfg.QRUNNER_SLEEP_TIME) - finally: - self._running = False - self._release_pid_lock() - mailman_log('debug', 'OutgoingRunner: Main loop ended') - - def stop(self): - """Stop the outgoing queue runner.""" - mailman_log('debug', 'OutgoingRunner: Stopping runner') - self._running = False - self._release_pid_lock() - Runner._cleanup(self) - mailman_log('debug', 'OutgoingRunner: Runner stopped') - - def _acquire_pid_lock(self): - """Try to acquire the PID lock file.""" - try: - self._pid_lock = open(self._pid_file, 'w') - fcntl.flock(self._pid_lock, fcntl.LOCK_EX | fcntl.LOCK_NB) - # Write our PID to the file - self._pid_lock.seek(0) - self._pid_lock.write(str(os.getpid())) - self._pid_lock.truncate() - self._pid_lock.flush() - mailman_log('debug', 'OutgoingRunner: Acquired PID lock file %s', self._pid_file) - return True - except IOError: - mailman_log('error', 'OutgoingRunner: Another instance is already running (PID file: %s)', self._pid_file) - if self._pid_lock: - self._pid_lock.close() - self._pid_lock = None - return False - - def _release_pid_lock(self): - """Release the PID lock file.""" - if self._pid_lock: - try: - fcntl.flock(self._pid_lock, fcntl.LOCK_UN) - self._pid_lock.close() - os.unlink(self._pid_file) - mailman_log('debug', 'OutgoingRunner: Released PID lock file %s', self._pid_file) - except (IOError, OSError) as e: - mailman_log('error', 'OutgoingRunner: Error releasing PID lock: %s', str(e)) - self._pid_lock = None - - def _unmark_message_processed(self, msgid): - """Remove a message from the processed messages set.""" - with self._processed_lock: - if msgid in self._processed_messages: - self._processed_messages.remove(msgid) - mailman_log('debug', 'OutgoingRunner: Unmarked message %s as processed', msgid) - - def _cleanup_old_messages(self): - """Clean up old message tracking data.""" - with self._processed_lock: - if len(self._processed_messages) > self._max_processed_messages: - mailman_log('debug', 'OutgoingRunner._cleanup_old_messages: Clearing processed messages set (size: %d)', - len(self._processed_messages)) - self._processed_messages.clear() - if len(self._retry_times) > self._max_retry_times: - mailman_log('debug', 'OutgoingRunner._cleanup_old_messages: Clearing retry times dict (size: %d)', - len(self._retry_times)) - self._retry_times.clear() - self._last_cleanup = time.time() - - def _cleanup_resources(self, msg, msgdata): - """Clean up any temporary resources.""" - try: - if msgdata and '_tempfile' in msgdata: - tempfile = msgdata['_tempfile'] - if os.path.exists(tempfile): - mailman_log('debug', 'OutgoingRunner._cleanup_resources: Removing temporary file %s', tempfile) - os.unlink(tempfile) - except Exception as e: - mailman_log('error', 'OutgoingRunner._cleanup_resources: Error cleaning up resources: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - - def _get_smtp_connection(self): - """Get a new SMTP connection with proper configuration.""" - try: - conn = smtplib.SMTP() - conn._host = mm_cfg.SMTPHOST # workaround https://github.com/python/cpython/issues/80275 - conn.set_debuglevel(mm_cfg.SMTPLIB_DEBUG_LEVEL) - conn.connect(mm_cfg.SMTPHOST, mm_cfg.SMTPPORT) - - if mm_cfg.SMTP_AUTH: - if mm_cfg.SMTP_USE_TLS: - try: - conn.starttls() - except smtplib.SMTPException as e: - mailman_log('error', 'SMTP TLS error: %s', str(e)) - conn.quit() - return None - try: - helo_host = mm_cfg.SMTP_HELO_HOST or socket.getfqdn() - conn.ehlo(helo_host) - except smtplib.SMTPException as e: - mailman_log('error', 'SMTP EHLO error: %s', str(e)) - conn.quit() - return None - try: - conn.login(mm_cfg.SMTP_USER, mm_cfg.SMTP_PASSWD) - except smtplib.SMTPHeloError as e: - mailman_log('error', 'SMTP HELO error: %s', str(e)) - conn.quit() - return None - except smtplib.SMTPAuthenticationError as e: - mailman_log('error', 'SMTP AUTH error: %s', str(e)) - conn.quit() - return None - - return conn - except Exception as e: - mailman_log('error', 'SMTP connection failed: %s', str(e)) - return None - - def _handle_smtp_error(self, e, mlist, msg, msgdata): - """Handle SMTP errors with appropriate recovery.""" - if isinstance(e, smtplib.SMTPServerDisconnected): - # Server disconnected, try to reconnect - return self._retry_with_new_connection(mlist, msg, msgdata) - elif isinstance(e, smtplib.SMTPRecipientsRefused): - # Recipient refused, queue bounce - self._queue_bounces(mlist, msg, msgdata, e.recipients) - return False - - def _retry_with_new_connection(self, mlist, msg, msgdata): - """Retry message delivery with a new SMTP connection.""" - try: - conn = self._get_smtp_connection() - if conn: - return self._func(mlist, msg, msgdata, conn) - except Exception as e: - mailman_log('error', 'Retry with new connection failed: %s', str(e)) - return False - - def _convert_message(self, msg): - """Convert email.message.Message to Mailman.Message with proper handling of nested messages.""" - return Runner._convert_message(self, msg) - - def _validate_message(self, msg, msgdata): - """Validate the message for outgoing delivery. - - Args: - msg: The message to validate - msgdata: Additional message metadata - - Returns: - tuple: (msg, success) where success is a boolean indicating if validation was successful - """ - try: - # Convert message if needed - if not isinstance(msg, Message.Message): - msg = self._convert_message(msg) - - # Check required headers - if not msg.get('message-id'): - mailman_log('error', 'OutgoingRunner._validate_message: Message missing Message-ID header') - return msg, False - - if not msg.get('from'): - mailman_log('error', 'OutgoingRunner._validate_message: Message missing From header') - return msg, False - - if not msg.get('to') and not msg.get('recipients'): - mailman_log('error', 'OutgoingRunner._validate_message: Message missing To/Recipients') - return msg, False - - return msg, True - - except Exception as e: - mailman_log('error', 'OutgoingRunner._validate_message: Error validating message: %s', str(e)) - return msg, False + Runner.__init__(self, slice, numslices) + BounceMixin.__init__(self) + # We look this function up only at startup time + modname = 'Mailman.Handlers.' + mm_cfg.DELIVERY_MODULE + mod = __import__(modname) + self._func = getattr(sys.modules[modname], 'process') + # This prevents smtp server connection problems from filling up the + # error log. It gets reset if the message was successfully sent, and + # set if there was a socket.error. + self.__logged = False + self.__retryq = Switchboard(mm_cfg.RETRYQUEUE_DIR) def _dispose(self, mlist, msg, msgdata): # See if we should retry delivery of this message again. @@ -348,7 +67,7 @@ def _dispose(self, mlist, msg, msgdata): self._func(mlist, msg, msgdata) # Failsafe -- a child may have leaked through. if pid != os.getpid(): - mailman_log('error', 'child process leaked thru: %s', mm_cfg.DELIVERY_MODULE) + syslog('error', 'child process leaked thru: %s', modname) os._exit(1) self.__logged = False except socket.error: @@ -360,8 +79,8 @@ def _dispose(self, mlist, msg, msgdata): port = 'smtp' # Log this just once. if not self.__logged: - mailman_log('error', 'Cannot connect to SMTP server %s on port %s', - mm_cfg.SMTPHOST, port) + syslog('error', 'Cannot connect to SMTP server %s on port %s', + mm_cfg.SMTPHOST, port) self.__logged = True self._snooze(0) return True @@ -409,287 +128,8 @@ def _dispose(self, mlist, msg, msgdata): # We've successfully completed handling of this message return False - def _process_bounce(self, mlist, msg, msgdata): - """Process a bounce message.""" - msgid = msg.get('message-id', 'n/a') - try: - mailman_log('debug', 'OutgoingRunner._process_bounce: Processing bounce message %s', msgid) - - # Get bounce information - recipient = msgdata.get('recipient', 'unknown') - bounce_info = msgdata.get('bounce_info', {}) - - mailman_log('debug', 'OutgoingRunner._process_bounce: Bounce for recipient %s, info: %s', - recipient, str(bounce_info)) - - # Process the bounce - # ... bounce processing logic ... - - mailman_log('debug', 'OutgoingRunner._process_bounce: Successfully processed bounce message %s', msgid) - return True - - except Exception as e: - mailman_log('error', 'OutgoingRunner._process_bounce: Error processing bounce message %s: %s\nTraceback:\n%s', - msgid, str(e), traceback.format_exc()) - return False - - def _process_admin(self, mlist, msg, msgdata): - """Process an admin message.""" - msgid = msg.get('message-id', 'n/a') - try: - mailman_log('debug', 'OutgoingRunner._process_admin: Processing admin message %s', msgid) - - # Get admin information - recipient = msgdata.get('recipient', 'unknown') - admin_type = msgdata.get('admin_type', 'unknown') - - mailman_log('debug', 'OutgoingRunner._process_admin: Admin message for %s, type: %s', - recipient, admin_type) - - # Process the admin message - Replybot = get_replybot() - Replybot.process(mlist, msg, msgdata) - - mailman_log('debug', 'OutgoingRunner._process_admin: Successfully processed admin message %s', msgid) - return True - - except Exception as e: - mailman_log('error', 'OutgoingRunner._process_admin: Error processing admin message %s: %s\nTraceback:\n%s', - msgid, str(e), traceback.format_exc()) - return False - - def _process_regular(self, mlist, msg, msgdata): - """Process a regular outgoing message.""" - msgid = msg.get('message-id', 'n/a') - - try: - # Get recipient from msgdata or message headers - recipient = msgdata.get('recipient') - if not recipient: - # Try to get recipient from To header - to = msg.get('to') - if to: - # Parse the To header to get the first recipient - addrs = email.utils.getaddresses([to]) - if addrs: - recipient = addrs[0][1] - - if not recipient: - mailman_log('error', 'OutgoingRunner: No recipients found in msgdata for message: %s', msgid) - return self._handle_error(ValueError('No recipients found'), msg, mlist) - - # Set the recipient in msgdata for future use - msgdata['recipient'] = recipient - - # For system messages (_nolist=1), we need to handle them differently - if msgdata.get('_nolist'): - mailman_log('debug', 'OutgoingRunner._process_regular: Processing system message %s', msgid) - # System messages should be sent directly via SMTP - try: - conn = self._get_smtp_connection() - if not conn: - mailman_log('error', 'OutgoingRunner._process_regular: Failed to get SMTP connection for message %s', msgid) - return self._handle_error(ConnectionError('Failed to get SMTP connection'), msg, mlist) - - # Send the message - sender = msg.get('from', msgdata.get('original_sender', mm_cfg.MAILMAN_SITE_LIST)) - if not sender or not '@' in sender: - sender = mm_cfg.MAILMAN_SITE_LIST - - mailman_log('debug', 'OutgoingRunner._process_regular: Sending system message %s from %s to %s', - msgid, sender, recipient) - - conn.sendmail(sender, [recipient], str(msg)) - conn.quit() - - mailman_log('debug', 'OutgoingRunner._process_regular: Successfully sent system message %s', msgid) - return True - - except Exception as e: - mailman_log('error', 'OutgoingRunner._process_regular: SMTP error for system message %s: %s', - msgid, str(e)) - return self._handle_error(e, msg, mlist) - - # For regular list messages, use the delivery module - mailman_log('debug', 'OutgoingRunner._process_regular: Using delivery module for message %s', msgid) - - # Log the state before calling the delivery module - mailman_log('debug', 'OutgoingRunner._process_regular: Pre-delivery msgdata:\n%s', str(msgdata)) - - # Ensure we have the list members if this is a list message - if msgdata.get('tolist') and not msgdata.get('_nolist'): - try: - # Get all list members - members = mlist.getRegularMemberKeys() - if members: - msgdata['recips'] = [mlist.getMemberCPAddress(m) for m in members - if mlist.getDeliveryStatus(m) == ENABLED] - mailman_log('debug', 'OutgoingRunner._process_regular: Expanded list members for message %s: %s', - msgid, str(msgdata['recips'])) - else: - mailman_log('error', 'OutgoingRunner._process_regular: No members found for list %s', - mlist.internal_name()) - return self._handle_error(ValueError('No list members found'), msg, mlist) - except Exception as e: - mailman_log('error', 'OutgoingRunner._process_regular: Error getting list members: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - # Try to continue with existing recipients if any - if not msgdata.get('recips'): - mailman_log('error', 'OutgoingRunner._process_regular: No recipients available for message %s', msgid) - return self._handle_error(ValueError('No recipients available'), msg, mlist) - - # Call the delivery module - try: - self._func(mlist, msg, msgdata) - # Log the state after calling the delivery module - mailman_log('debug', 'OutgoingRunner._process_regular: Post-delivery msgdata:\n%s', str(msgdata)) - mailman_log('debug', 'OutgoingRunner._process_regular: Successfully processed regular message %s', msgid) - return True - except Exception as e: - mailman_log('error', 'OutgoingRunner._process_regular: Error in delivery module: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - return self._handle_error(e, msg, mlist) - - except Exception as e: - mailman_log('error', 'OutgoingRunner._process_regular: Unexpected error: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - return self._handle_error(e, msg, mlist) - - def _check_retry_delay(self, msgid, filebase): - """Check if enough time has passed since the last retry attempt.""" - now = time.time() - last_retry = self._retry_times.get(msgid, 0) - - if now - last_retry < self.MIN_RETRY_DELAY: - mailman_log('debug', 'OutgoingRunner._check_retry_delay: Message %s (file: %s) retry delay not met. Last retry: %s, Now: %s, Delay needed: %s', - msgid, filebase, time.ctime(last_retry), time.ctime(now), self.MIN_RETRY_DELAY) - return False - - mailman_log('debug', 'OutgoingRunner._check_retry_delay: Message %s (file: %s) retry delay met. Last retry: %s, Now: %s', - msgid, filebase, time.ctime(last_retry), time.ctime(now)) - return True - - def _queue_bounces(self, mlist, msg, msgdata, failures): - """Queue bounce messages for failed deliveries.""" - msgid = msg.get('message-id', 'n/a') - try: - for recip, code, errmsg in failures: - if not self._validate_bounce(recip, code, errmsg): - continue - mailman_log('error', 'OutgoingRunner: Delivery failure for msgid: %s - Recipient: %s, Code: %s, Error: %s', - msgid, recip, code, errmsg) - BounceMixin._queue_bounce(self, mlist, msg, recip, code, errmsg) - except Exception as e: - mailman_log('error', 'OutgoingRunner: Error queueing bounce for msgid: %s - %s', msgid, str(e)) - mailman_log('error', 'OutgoingRunner: Traceback: %s', traceback.format_exc()) - - def _validate_bounce(self, recip, code, errmsg): - """Validate bounce message data.""" - try: - if not recip or not isinstance(recip, str): - return False - if not code or not isinstance(code, (int, str)): - return False - if not errmsg or not isinstance(errmsg, str): - return False - return True - except Exception: - return False - - def _cleanup(self): - """Clean up the outgoing queue runner.""" - mailman_log('debug', 'OutgoingRunner: Starting cleanup') - try: - # Log total messages processed - with self._total_messages_lock: - mailman_log('debug', 'OutgoingRunner: Total messages processed: %d', self._total_messages_processed) - - # Call parent class cleanup - Runner._cleanup(self) - - # Release PID lock if we have it - self._release_pid_lock() - - mailman_log('debug', 'OutgoingRunner: Cleanup complete') - except Exception as e: - mailman_log('error', 'OutgoingRunner: Error during cleanup: %s', str(e)) - mailman_log('error', 'OutgoingRunner: Traceback:\n%s', traceback.format_exc()) - raise - _doperiodic = BounceMixin._doperiodic - def _oneloop(self): - """Process one batch of messages from the queue.""" - # Get all files in the queue - files = self._switchboard.files() - if not files: - return 0 - - # Process each file - for filebase in files: - try: - # Try to get the file from the switchboard - msg, msgdata = self._switchboard.dequeue(filebase) - except Exception as e: - mailman_log('error', 'OutgoingRunner: Error dequeuing %s: %s', filebase, str(e)) - mailman_log('error', 'OutgoingRunner: Traceback:\n%s', traceback.format_exc()) - continue - - if msg is None: - mailman_log('debug', 'OutgoingRunner: No message data for %s', filebase) - continue - - try: - # Process the message - self._dispose(msg, msgdata) - with self._total_messages_lock: - self._total_messages_processed += 1 - mailman_log('debug', 'OutgoingRunner: Successfully processed message %s', filebase) - except Exception as e: - mailman_log('error', 'OutgoingRunner: Error processing %s: %s', filebase, str(e)) - mailman_log('error', 'OutgoingRunner: Traceback:\n%s', traceback.format_exc()) - self._handle_error(e, msg, None) - - def _handle_error(self, exc, msg=None, mlist=None, preserve=True): - """Enhanced error handling with circuit breaker and detailed logging.""" - now = time.time() - msgid = msg.get('message-id', 'n/a') if msg else 'n/a' - - # Log the error with full context - mailman_log('error', 'OutgoingRunner: Error processing message %s: %s', msgid, str(exc)) - mailman_log('error', 'OutgoingRunner: Error type: %s', type(exc).__name__) - - # Log full traceback - s = StringIO() - traceback.print_exc(file=s) - mailman_log('error', 'OutgoingRunner: Traceback:\n%s', s.getvalue()) - - # Log system state - mailman_log('error', 'OutgoingRunner: System state - SMTP host: %s, port: %s, auth: %s', - mm_cfg.SMTPHOST, mm_cfg.SMTPPORT, mm_cfg.SMTP_AUTH) - - # Circuit breaker logic - if now - self._last_error_time < self._error_window: - self._error_count += 1 - if self._error_count >= self._max_errors: - mailman_log('error', 'OutgoingRunner: Too many errors (%d) in %d seconds, stopping runner', - self._error_count, self._error_window) - # Log stack trace before stopping - s = StringIO() - traceback.print_stack(file=s) - mailman_log('error', 'OutgoingRunner: Stack trace at stop:\n%s', s.getvalue()) - self.stop() - else: - self._error_count = 1 - self._last_error_time = now - - # Handle message preservation - if preserve and msg: - try: - msgdata = {'whichq': self._switchboard.whichq()} - new_filebase = self._shunt.enqueue(msg, msgdata) - mailman_log('error', 'OutgoingRunner: Shunted message to: %s', new_filebase) - except Exception as e: - mailman_log('error', 'OutgoingRunner: Failed to shunt message: %s', str(e)) - return False - return True + def _cleanup(self): + BounceMixin._cleanup(self) + Runner._cleanup(self) diff --git a/Mailman/Queue/RetryRunner.py b/Mailman/Queue/RetryRunner.py index fa20c1b6..4ed129b7 100644 --- a/Mailman/Queue/RetryRunner.py +++ b/Mailman/Queue/RetryRunner.py @@ -1,4 +1,4 @@ -# Copyright (C) 1998-2018 by the Free Software Foundation, Inc. +# Copyright (C) 2003-2018 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 @@ -12,294 +12,34 @@ # # 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. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -"""Retry queue runner. - -This module is responsible for retrying failed message deliveries. It's a -separate queue from the virgin queue because retries need different handling. -""" - -from builtins import object import time -import traceback -import os -import sys -import threading -import email.message from Mailman import mm_cfg -from Mailman import Errors from Mailman.Queue.Runner import Runner from Mailman.Queue.Switchboard import Switchboard -from Mailman.Errors import MMUnknownListError -from Mailman.Logging.Syslog import mailman_log -import Mailman.MailList as MailList -import Mailman.Message as Message + class RetryRunner(Runner): QDIR = mm_cfg.RETRYQUEUE_DIR SLEEPTIME = mm_cfg.minutes(15) - - # Message tracking configuration - _track_messages = True - _max_processed_messages = 10000 - _max_retry_times = 10000 - _processed_messages = set() - _processed_lock = threading.Lock() - _last_cleanup = time.time() - _cleanup_interval = 3600 # Clean up every hour - - # Retry configuration - MIN_RETRY_DELAY = 300 # 5 minutes minimum delay between retries - MAX_RETRIES = 5 # Maximum number of retry attempts - _retry_times = {} # Track last retry time for each message def __init__(self, slice=None, numslices=1): - mailman_log('debug', 'RetryRunner: Starting initialization') - try: - Runner.__init__(self, slice, numslices) - self._outq = Switchboard(mm_cfg.OUTQUEUE_DIR) - - # Initialize processed messages tracking - self._processed_messages = set() - self._last_cleanup = time.time() - - mailman_log('debug', 'RetryRunner: Initialization complete') - except Exception as e: - mailman_log('error', 'RetryRunner: Initialization failed: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - raise - - def _check_retry_delay(self, msgid, filebase): - """Check if enough time has passed since the last retry attempt.""" - now = time.time() - last_retry = self._retry_times.get(msgid, 0) - - if now - last_retry < self.MIN_RETRY_DELAY: - mailman_log('debug', 'RetryRunner._check_retry_delay: Message %s (file: %s) retry delay not met. Last retry: %s, Now: %s, Delay needed: %s', - msgid, filebase, time.ctime(last_retry), time.ctime(now), self.MIN_RETRY_DELAY) - return False - - mailman_log('debug', 'RetryRunner._check_retry_delay: Message %s (file: %s) retry delay met. Last retry: %s, Now: %s', - msgid, filebase, time.ctime(last_retry), time.ctime(now)) - return True - - def _validate_message(self, msg, msgdata): - """Validate message format and required fields.""" - msgid = msg.get('message-id', 'n/a') - try: - # Check message size - if len(str(msg)) > mm_cfg.MAX_MESSAGE_SIZE: - mailman_log('error', 'RetryRunner: Message too large: %d bytes', len(str(msg))) - return msg, False - - # Validate required headers - if not msg.get('message-id'): - mailman_log('error', 'RetryRunner: Message missing Message-ID header') - return msg, False - - if not msg.get('from'): - mailman_log('error', 'RetryRunner: Message missing From header') - return msg, False - - if not msg.get('to') and not msg.get('recipients'): - mailman_log('error', 'RetryRunner: Message missing To/Recipients') - return msg, False - - mailman_log('debug', 'RetryRunner: Message %s validation successful', msgid) - return msg, True - - except Exception as e: - mailman_log('error', 'RetryRunner: Error validating message %s: %s', msgid, str(e)) - mailman_log('error', 'RetryRunner: Traceback:\n%s', traceback.format_exc()) - return msg, False - - def _unmark_message_processed(self, msgid): - """Remove a message from the processed messages set.""" - with self._processed_lock: - if msgid in self._processed_messages: - self._processed_messages.remove(msgid) - mailman_log('debug', 'RetryRunner: Unmarked message %s as processed', msgid) - - def _cleanup_old_messages(self): - """Clean up old message tracking data.""" - with self._processed_lock: - if len(self._processed_messages) > self._max_processed_messages: - mailman_log('debug', 'RetryRunner._cleanup_old_messages: Clearing processed messages set (size: %d)', - len(self._processed_messages)) - self._processed_messages.clear() - if len(self._retry_times) > self._max_retry_times: - mailman_log('debug', 'RetryRunner._cleanup_old_messages: Clearing retry times dict (size: %d)', - len(self._retry_times)) - self._retry_times.clear() - self._last_cleanup = time.time() + Runner.__init__(self, slice, numslices) + self.__outq = Switchboard(mm_cfg.OUTQUEUE_DIR) def _dispose(self, mlist, msg, msgdata): - # See if we should retry delivery of this message again. + # Move it to the out queue for another retry if it's time. deliver_after = msgdata.get('deliver_after', 0) if time.time() < deliver_after: return True - # Move the message to the outgoing queue for another attempt at - # delivery. - self._outq.enqueue(msg, msgdata) + self.__outq.enqueue(msg, msgdata) return False - def _process_retry(self, mlist, msg, msgdata): - """Process a retry message.""" - msgid = msg.get('message-id', 'n/a') - try: - mailman_log('debug', 'RetryRunner._process_retry: Processing retry for message %s', msgid) - - # Get retry information - retry_count = msgdata.get('retry_count', 0) - retry_delay = msgdata.get('retry_delay', mm_cfg.RETRY_DELAY) - - # Calculate next retry time - next_retry = time.time() + retry_delay - msgdata['next_retry'] = next_retry - msgdata['retry_count'] = retry_count + 1 - - mailman_log('debug', 'RetryRunner._process_retry: Updated retry info for message %s - count: %d, next retry: %s', - msgid, retry_count + 1, time.ctime(next_retry)) - - # Process the message - # ... retry processing logic ... - - mailman_log('debug', 'RetryRunner._process_retry: Successfully processed retry for message %s', msgid) - return True - - except Exception as e: - mailman_log('error', 'RetryRunner._process_retry: Error processing retry for message %s: %s\nTraceback:\n%s', - msgid, str(e), traceback.format_exc()) - return False - - def _handle_max_retries_exceeded(self, mlist, msg, msgdata): - """Handle case when maximum retries are exceeded.""" - msgid = msg.get('message-id', 'n/a') - try: - mailman_log('error', 'RetryRunner._handle_max_retries_exceeded: Maximum retries exceeded for message %s', msgid) - - # Move to shunt queue - self._shunt.enqueue(msg, msgdata) - mailman_log('debug', 'RetryRunner._handle_max_retries_exceeded: Moved message %s to shunt queue', msgid) - - # Notify list owners if configured - if mlist.bounce_notify_owner_on_disable: - mailman_log('debug', 'RetryRunner._handle_max_retries_exceeded: Notifying list owners for message %s', msgid) - self._notify_list_owners(mlist, msg, msgdata) - - except Exception as e: - mailman_log('error', 'RetryRunner._handle_max_retries_exceeded: Error handling max retries for message %s: %s\nTraceback:\n%s', - msgid, str(e), traceback.format_exc()) - - def _notify_list_owners(self, mlist, msg, msgdata): - """Notify list owners about failed retries.""" - msgid = msg.get('message-id', 'n/a') - try: - mailman_log('debug', 'RetryRunner._notify_list_owners: Sending notification for message %s', msgid) - - # Create notification message - subject = _('Maximum retries exceeded for message') - text = _("""\ -The following message has exceeded the maximum number of retry attempts: - -Message-ID: %(msgid)s -From: %(from)s -To: %(to)s -Subject: %(subject)s - -The message has been moved to the shunt queue. -""") % { - 'msgid': msgid, - 'from': msg.get('from', 'unknown'), - 'to': msg.get('to', 'unknown'), - 'subject': msg.get('subject', 'unknown') - } - - # Send notification - # ... notification sending logic ... - - mailman_log('debug', 'RetryRunner._notify_list_owners: Successfully sent notification for message %s', msgid) - - except Exception as e: - mailman_log('error', 'RetryRunner._notify_list_owners: Error sending notification for message %s: %s\nTraceback:\n%s', - msgid, str(e), traceback.format_exc()) - - def _cleanup(self): - """Clean up resources.""" - mailman_log('debug', 'RetryRunner: Starting cleanup') - try: - Runner._cleanup(self) - except Exception as e: - mailman_log('error', 'RetryRunner: Cleanup failed: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - mailman_log('debug', 'RetryRunner: Cleanup complete') - - def _oneloop(self): - """Process one batch of messages from the retry queue.""" - try: - # Get the list of files to process - files = self._switchboard.files() - filecnt = len(files) - - # Process each file - for filebase in files: - try: - # Check if the file exists before dequeuing - pckfile = os.path.join(self.QDIR, filebase + '.pck') - if not os.path.exists(pckfile): - mailman_log('error', 'RetryRunner._oneloop: File %s does not exist, skipping', pckfile) - continue - - # Check if file is locked - lockfile = os.path.join(self.QDIR, filebase + '.pck.lock') - if os.path.exists(lockfile): - mailman_log('debug', 'RetryRunner._oneloop: File %s is locked by another process, skipping', filebase) - continue - - # Dequeue the file - msg, msgdata = self._switchboard.dequeue(filebase) - if msg is None: - continue - - # Get the list name from the message data - listname = msgdata.get('listname') - if not listname: - syslog('error', 'RetryRunner._oneloop: No listname in message data for file %s', filebase) - self._shunt.enqueue(msg, msgdata) - continue - - # Open the list - try: - mlist = self._open_list(listname) - except Exception as e: - self.log_error('list_open_error', str(e), listname=listname) - self._shunt.enqueue(msg, msgdata) - continue - - # Process the message - try: - result = self._dispose(mlist, msg, msgdata) - if result: - self._switchboard.enqueue(msg, msgdata) - except Exception as e: - self._handle_error(e, msg=msg, mlist=mlist) - - except Exception as e: - syslog('error', 'RetryRunner._oneloop: Error dequeuing file %s: %s', filebase, str(e)) - continue - - except Exception as e: - syslog('error', 'RetryRunner._oneloop: Error in main loop: %s', str(e)) - return 0 - - return filecnt - def _snooze(self, filecnt): - # We always want to snooze, but check for stop flag periodically - for _ in range(self.SLEEPTIME): + # We always want to snooze. Sleep in 1 second iterations to ensure that the sigterm handler can respond promptly and set _stop. + for sec in range(1, self.SLEEPTIME): if self._stop: - return + break time.sleep(1) diff --git a/Mailman/Queue/Runner.py b/Mailman/Queue/Runner.py index eb1f98f3..9c35f939 100644 --- a/Mailman/Queue/Runner.py +++ b/Mailman/Queue/Runner.py @@ -22,76 +22,36 @@ import time import traceback from io import StringIO -from functools import wraps -import threading -import os from Mailman import mm_cfg +# Debug: Log when mm_cfg is imported +from Mailman.Logging.Syslog import syslog +syslog('debug', 'Runner.py: mm_cfg imported from %s', mm_cfg.__file__) +syslog('debug', 'Runner.py: mm_cfg.GLOBAL_PIPELINE type: %s', type(mm_cfg.GLOBAL_PIPELINE).__name__ if hasattr(mm_cfg, 'GLOBAL_PIPELINE') else 'NOT FOUND') from Mailman import Utils from Mailman import Errors -import Mailman.MailList as MailList +from Mailman import MailList from Mailman import i18n -import Mailman.Message as Message + from Mailman.Logging.Syslog import syslog from Mailman.Queue.Switchboard import Switchboard import email.errors - + class Runner: QDIR = None SLEEPTIME = mm_cfg.QRUNNER_SLEEP_TIME - MIN_RETRY_DELAY = 300 # 5 minutes minimum delay between retries - MAX_BACKOFF = 60 # Maximum backoff time in seconds - INITIAL_BACKOFF = 1 # Initial backoff time in seconds - - # Message tracking configuration - can be overridden by subclasses - _track_messages = False # Whether to track processed messages - _max_processed_messages = 10000 # Maximum number of messages to track - _max_retry_times = 10000 # Maximum number of retry times to track - _processed_messages = set() # Set of processed message IDs - _processed_lock = threading.Lock() # Lock for thread safety - _retry_times = {} # Dictionary of retry times - _last_cleanup = time.time() # Last cleanup time - _cleanup_interval = 3600 # Cleanup interval in seconds - _current_backoff = INITIAL_BACKOFF # Current backoff time in seconds - _last_mtime = 0 # Last directory modification time def __init__(self, slice=None, numslices=1): - syslog('debug', '%s: Starting initialization', self.__class__.__name__) - try: - self._stop = 0 - self._slice = slice - self._numslices = numslices - self._kids = {} - # Create our own switchboard. Don't use the switchboard cache because - # we want to provide slice and numslice arguments. - self._switchboard = Switchboard(self.QDIR, slice, numslices, True) - # Create the shunt switchboard - self._shunt = Switchboard(mm_cfg.SHUNTQUEUE_DIR) - - # Initialize message tracking attributes - self._track_messages = self.__class__._track_messages - self._max_processed_messages = self.__class__._max_processed_messages - self._max_retry_times = self.__class__._max_retry_times - self._processed_messages = set() - self._processed_lock = threading.Lock() - self._retry_times = {} - self._last_cleanup = time.time() - self._cleanup_interval = 3600 - - # Initialize error tracking attributes - self._last_error_time = 0 - self._error_count = 0 - - self._current_backoff = self.INITIAL_BACKOFF - self._last_mtime = 0 - - syslog('debug', '%s: Initialization complete', self.__class__.__name__) - except Exception as e: - syslog('error', '%s: Initialization failed: %s\nTraceback:\n%s', - self.__class__.__name__, str(e), traceback.format_exc()) - raise + self._kids = {} + # Create our own switchboard. Don't use the switchboard cache because + # we want to provide slice and numslice arguments. + distribution = getattr(mm_cfg, 'QUEUE_DISTRIBUTION_METHOD', 'hash') + self._switchboard = Switchboard(self.QDIR, slice, numslices, True, distribution) + # Create the shunt switchboard + self._shunt = Switchboard(mm_cfg.SHUNTQUEUE_DIR) + self._stop = False def __repr__(self): return '<%s at %s>' % (self.__class__.__name__, id(self)) @@ -125,130 +85,36 @@ def run(self): # subprocesses we've created and do any other necessary cleanups. self._cleanup() - def log_error(self, error_type, error_msg, **kwargs): - """Log an error with the given type and message. - - Args: - error_type: A string identifying the type of error - error_msg: The error message to log - **kwargs: Additional context to include in the log message - """ - context = { - 'runner': self.__class__.__name__, - 'error_type': error_type, - 'error_msg': error_msg, - } - context.update(kwargs) - - # Format the error message - msg_parts = ['%s: %s' % (error_type, error_msg)] - if 'msg' in context: - msg_parts.append('Message-ID: %s' % context['msg'].get('message-id', 'unknown')) - if 'listname' in context: - msg_parts.append('List: %s' % context['listname']) - if 'traceback' in context: - msg_parts.append('Traceback:\n%s' % context['traceback']) - - # Log the error - syslog('error', ' '.join(msg_parts)) - - def log_warning(self, warning_type, msg=None, mlist=None, **context): - """Structured warning logging with context.""" - context.update({ - 'runner': self.__class__.__name__, - 'list': mlist.internal_name() if mlist else 'N/A', - 'msg_id': msg.get('message-id', 'N/A') if msg else 'N/A', - 'warning_type': warning_type - }) - syslog('warning', '%(runner)s: %(warning_type)s - list: %(list)s, msg: %(msg_id)s', - context) - - def log_info(self, info_type, msg=None, mlist=None, **context): - """Structured info logging with context.""" - context.update({ - 'runner': self.__class__.__name__, - 'list': mlist.internal_name() if mlist else 'N/A', - 'msg_id': msg.get('message-id', 'N/A') if msg else 'N/A', - 'info_type': info_type - }) - syslog('info', '%(runner)s: %(info_type)s - list: %(list)s, msg: %(msg_id)s', - context) - - def _handle_error(self, exc, msg=None, mlist=None, preserve=True): - """Centralized error handling with circuit breaker.""" - now = time.time() - - # Log the error with full context - self.log_error('unhandled_exception', exc, msg=msg, mlist=mlist) - - # Log full traceback - s = StringIO() - traceback.print_exc(file=s) - syslog('error', 'Traceback: %s', s.getvalue()) - - # Circuit breaker logic - if now - self._last_error_time < 60: # Within last minute - self._error_count += 1 - if self._error_count >= 10: # Too many errors in short time - syslog('error', '%s: Too many errors, stopping runner', self.__class__.__name__) - # Log stack trace before stopping - s = StringIO() - traceback.print_stack(file=s) - syslog('error', 'Stack trace at stop:\n%s', s.getvalue()) - self.stop() - else: - self._error_count = 1 - self._last_error_time = now - - # Handle message preservation - if preserve: - try: - msgdata = {'whichq': self._switchboard.whichq()} - new_filebase = self._shunt.enqueue(msg, msgdata) - syslog('error', '%s: Shunted message to: %s', self.__class__.__name__, new_filebase) - except Exception as e: - syslog('error', '%s: Failed to shunt message: %s', self.__class__.__name__, str(e)) - return False - return True - def _oneloop(self): - """Run one iteration of the runner's main loop. - - Returns: - int: Number of files processed, or 0 if no files found - """ - # Check if directory has been modified since last check - try: - st = os.stat(self.QDIR) - current_mtime = st.st_mtime - if current_mtime <= self._last_mtime: - # Directory hasn't changed, use backoff - self._snooze(self._current_backoff) - # Double the backoff time, up to MAX_BACKOFF - self._current_backoff = min(self._current_backoff * 2, self.MAX_BACKOFF) - return 0 - # Directory has changed, reset backoff - self._current_backoff = self.INITIAL_BACKOFF - self._last_mtime = current_mtime - except OSError as e: - syslog('error', '%s: Error checking directory %s: %s', - self.__class__.__name__, self.QDIR, str(e)) - return 0 - - # Process files in the directory + # First, list all the files in our queue directory. + # Switchboard.files() is guaranteed to hand us the files in FIFO + # order. Return an integer count of the number of files that were + # available for this qrunner to process. files = self._switchboard.files() - if not files: - syslog('debug', '%s: No files to process', self.__class__.__name__) - return 0 - - # Process each file for filebase in files: - if self._stop: - break try: # Ask the switchboard for the message and metadata objects # associated with this filebase. msg, msgdata = self._switchboard.dequeue(filebase) + except Exception as e: + # This used to just catch email.Errors.MessageParseError, + # but other problems can occur in message parsing, e.g. + # ValueError, and exceptions can occur in unpickling too. + # We don't want the runner to die, so we just log and skip + # this entry, but maybe preserve it for analysis. + self._log(e) + if mm_cfg.QRUNNER_SAVE_BAD_MESSAGES: + syslog('error', + 'Skipping and preserving unparseable message: %s', + filebase) + preserve = True + else: + syslog('error', + 'Ignoring unparseable message: %s', filebase) + preserve = False + self._switchboard.finish(filebase, preserve=preserve) + continue + try: self._onefile(msg, msgdata) self._switchboard.finish(filebase) except Exception as e: @@ -284,194 +150,82 @@ def _oneloop(self): break return len(files) - def _convert_message(self, msg): - """Convert email.message.Message to Mailman.Message with proper handling of nested messages. - - Args: - msg: The message to convert - - Returns: - Mailman.Message: The converted message - """ - if isinstance(msg, email.message.Message): - mailman_msg = Message.Message() - # Copy all attributes from the original message - for key, value in msg.items(): - mailman_msg[key] = value - # Copy the payload - if msg.is_multipart(): - for part in msg.get_payload(): - mailman_msg.attach(self._convert_message(part)) - else: - mailman_msg.set_payload(msg.get_payload()) - return mailman_msg - return msg - - def _validate_message(self, msg, msgdata): - """Validate and convert message if needed. - - Returns a tuple of (msg, success) where success is a boolean indicating - if validation was successful. - """ - msgid = msg.get('message-id', 'n/a') - try: - # Convert message if needed - if not isinstance(msg, Message.Message): - # Only log conversion if it's a significant event - if msg.is_multipart() or len(msg.get_payload()) > 1000: - syslog('debug', 'Runner._validate_message: Converting complex message %s to Mailman.Message', msgid) - msg = self._convert_message(msg) - - # Validate required Mailman.Message methods - required_methods = ['get_sender', 'get', 'items', 'is_multipart', 'get_payload'] - missing_methods = [] - for method in required_methods: - if not hasattr(msg, method): - missing_methods.append(method) - - if missing_methods: - syslog('error', 'Runner._validate_message: Message %s missing required methods: %s', - msgid, ', '.join(missing_methods)) - return msg, False - - # Validate message headers - if not msg.get('message-id'): - syslog('error', 'Runner._validate_message: Message %s missing Message-ID header', msgid) - return msg, False - - if not msg.get('from'): - syslog('error', 'Runner._validate_message: Message %s missing From header', msgid) - return msg, False - - if not msg.get('to') and not msg.get('recipients'): - syslog('error', 'Runner._validate_message: Message %s missing To/Recipients', msgid) - return msg, False - - # Only log successful validation for complex messages - if msg.is_multipart() or len(msg.get_payload()) > 1000: - syslog('debug', 'Runner._validate_message: Complex message %s validation successful', msgid) - return msg, True - - except Exception as e: - syslog('error', 'Runner._validate_message: Error validating message %s: %s\nTraceback:\n%s', - msgid, str(e), traceback.format_exc()) - return msg, False - - def _onefile(self, mlist, msg, msgdata): - """Process a single file from the queue.""" + def _onefile(self, msg, msgdata): + # Do some common sanity checking on the message metadata. It's got to + # be destined for a particular mailing list. This switchboard is used + # to shunt off badly formatted messages. We don't want to just trash + # them because they may be fixable with human intervention. Just get + # them out of our site though. + # + # Find out which mailing list this message is destined for. + listname = msgdata.get('listname') + if not listname: + listname = mm_cfg.MAILMAN_SITE_LIST + mlist = self._open_list(listname) + if not mlist: + syslog('error', + 'Dequeuing message destined for missing list: %s', + listname) + self._shunt.enqueue(msg, msgdata) + return + # Now process this message, keeping track of any subprocesses that may + # have been spawned. We'll reap those later. + # + # We also want to set up the language context for this message. The + # context will be the preferred language for the user if a member of + # the list, or the list's preferred language. However, we must take + # special care to reset the defaults, otherwise subsequent messages + # may be translated incorrectly. BAW: I'm not sure I like this + # approach, but I can't think of anything better right now. + otranslation = i18n.get_translation() + sender = msg.get_sender() + if mlist: + lang = mlist.getMemberLanguage(sender) + else: + lang = mm_cfg.DEFAULT_SERVER_LANGUAGE + i18n.set_language(lang) + msgdata['lang'] = lang try: - # Get the list name from the message data - listname = msgdata.get('listname') - if not listname: - syslog('error', 'Runner._onefile: No listname in message data') - self._handle_error(ValueError('No listname in message data'), msg=msg, mlist=None) - return False - - # Open the list - try: - mlist = self._open_list(listname) - except Exception as e: - self._handle_error(e, msg=msg, mlist=None) - return False - - # Process the message - try: - result = self._dispose(mlist, msg, msgdata) - if result: - # If _dispose returns True, requeue the message - self._switchboard.enqueue(msg, msgdata) - # Only log significant events - if msg.is_multipart() or len(msg.get_payload()) > 1000: - syslog('debug', 'Runner._onefile: Complex message requeued for %s', listname) - else: - # If _dispose returns False, finish processing and remove the file - self._switchboard.finish(msgdata.get('filebase', '')) - # Only log significant events - if msg.is_multipart() or len(msg.get_payload()) > 1000: - syslog('debug', 'Runner._onefile: Complex message processing completed for %s', listname) - return result - except Exception as e: - self._handle_error(e, msg=msg, mlist=mlist) - return False - finally: - if mlist: - mlist.Unlock() - - except Exception as e: - self._handle_error(e, msg=msg, mlist=None) - return False + keepqueued = self._dispose(mlist, msg, msgdata) + finally: + i18n.set_translation(otranslation) + # Keep tabs on any child processes that got spawned. + kids = msgdata.get('_kids') + if kids: + self._kids.update(kids) + if keepqueued: + self._switchboard.enqueue(msg, msgdata) def _open_list(self, listname): + # We no longer cache the list instances. Because of changes to + # MailList.py needed to avoid not reloading an updated list, caching + # is not as effective as it once was. Also, with OldStyleMemberships + # as the MemberAdaptor, there was a self-reference to the list which + # kept all lists in the cache. Changing this reference to a + # weakref.proxy created other issues. try: - import Mailman.MailList as MailList mlist = MailList.MailList(listname, lock=False) except Errors.MMListError as e: - self.log_error('list_open_error', e, listname=listname) + syslog('error', 'error opening list: %s\n%s', listname, e) return None return mlist - def _doperiodic(self): - """Do some processing `every once in a while'. - - Called every once in a while both from the Runner's main loop, and - from the Runner's hash slice processing loop. You can do whatever - special periodic processing you want here, and the return value is - irrelevant. - """ - pass - - def _snooze(self, filecnt): - """Sleep for a while, but check for stop flag periodically. - - Implements exponential backoff when no files are found to process. - - Args: - filecnt: Number of files processed in the last iteration - """ - if filecnt > 0: - # Reset backoff when files are found - self._current_backoff = self.INITIAL_BACKOFF - # Only log if we're sleeping for more than 5 seconds - if self.SLEEPTIME > 5: - syslog('debug', '%s: Sleeping for %d seconds after processing %d files in this iteration', - self.__class__.__name__, self.SLEEPTIME, filecnt) - sleep_time = self.SLEEPTIME - else: - # No files found, use exponential backoff - sleep_time = min(self._current_backoff, self.MAX_BACKOFF) - syslog('debug', '%s: No files to process, sleeping for %d seconds', - self.__class__.__name__, sleep_time) - # Double the backoff time for next iteration, up to MAX_BACKOFF - self._current_backoff = min(self._current_backoff * 2, self.MAX_BACKOFF) - - endtime = time.time() + sleep_time - while time.time() < endtime and not self._stop: - time.sleep(0.1) - - def _shortcircuit(self): - """Return a true value if the individual file processing loop should - exit before it's finished processing each message in the current slice - of hash space. A false value tells _oneloop() to continue processing - until the current snapshot of hash space is exhausted. - - You could, for example, implement a throttling algorithm here. - """ - return self._stop + def _log(self, exc): + syslog('error', 'Uncaught runner exception: %s', exc) + s = StringIO() + traceback.print_exc(file=s) + syslog('error', s.getvalue()) # # Subclasses can override these methods. # def _cleanup(self): - """Clean up resources.""" - syslog('debug', '%s: Starting cleanup', self.__class__.__name__) - try: - self._cleanup_old_messages() - # Clean up any stale locks - self._switchboard.cleanup_stale_locks() - except Exception as e: - syslog('error', '%s: Cleanup failed: %s\nTraceback:\n%s', - self.__class__.__name__, str(e), traceback.format_exc()) - syslog('debug', '%s: Cleanup complete', self.__class__.__name__) + """Clean up upon exit from the main processing loop. + + Called when the Runner's main loop is stopped, this should perform + any necessary resource deallocation. Its return value is irrelevant. + """ + Utils.reap(self._kids) def _dispose(self, mlist, msg, msgdata): """Dispose of a single message destined for a mailing list. @@ -488,65 +242,34 @@ def _dispose(self, mlist, msg, msgdata): """ raise NotImplementedError - def _check_retry_delay(self, msgid, filebase): - """Check if enough time has passed since the last retry attempt.""" - now = time.time() - last_retry = self._retry_times.get(msgid, 0) - - if now - last_retry < self.MIN_RETRY_DELAY: - # Only log if this is a significant delay - if self.MIN_RETRY_DELAY > 300: # 5 minutes - syslog('debug', 'Runner._check_retry_delay: Message %s (file: %s) retry delay not met. Last retry: %s, Now: %s, Delay needed: %s', - msgid, filebase, time.ctime(last_retry), time.ctime(now), self.MIN_RETRY_DELAY) - return False - - # Only log if this is a significant delay - if self.MIN_RETRY_DELAY > 300: # 5 minutes - syslog('debug', 'Runner._check_retry_delay: Message %s (file: %s) retry delay met. Last retry: %s, Now: %s', - msgid, filebase, time.ctime(last_retry), time.ctime(now)) - return True + def _doperiodic(self): + """Do some processing `every once in a while'. - def _mark_message_processed(self, msgid): - """Mark a message as processed.""" - with self._processed_lock: - self._processed_messages.add(msgid) - # Only log if we're tracking a large number of messages - if len(self._processed_messages) > 1000: - syslog('debug', 'Runner._mark_message_processed: Marked message %s as processed', msgid) + Called every once in a while both from the Runner's main loop, and + from the Runner's hash slice processing loop. You can do whatever + special periodic processing you want here, and the return value is + irrelevant. + """ + pass - def _unmark_message_processed(self, msgid): - """Remove a message from the processed set.""" - with self._processed_lock: - if msgid in self._processed_messages: - self._processed_messages.remove(msgid) - # Only log if we're tracking a large number of messages - if len(self._processed_messages) > 1000: - syslog('debug', 'Runner._unmark_message_processed: Removed message %s from processed set', msgid) + def _snooze(self, filecnt): + """Sleep for a little while. - def _cleanup_old_messages(self): - """Clean up old message tracking data if message tracking is enabled.""" - if not self._track_messages: + filecnt is the number of messages in the queue the last time through. + Sub-runners can decide to continue to do work, or sleep for a while + based on this value. By default, we only snooze if there was nothing + to do last time around. + """ + if filecnt or self.SLEEPTIME <= 0: return + time.sleep(self.SLEEPTIME) - try: - now = time.time() - if now - self._last_cleanup < self._cleanup_interval: - return + def _shortcircuit(self): + """Return a true value if the individual file processing loop should + exit before it's finished processing each message in the current slice + of hash space. A false value tells _oneloop() to continue processing + until the current snapshot of hash space is exhausted. - with self._processed_lock: - if len(self._processed_messages) > self._max_processed_messages: - # Only log if we're clearing a significant number of messages - if len(self._processed_messages) > 1000: - syslog('debug', '%s: Clearing processed messages set (size: %d)', - self.__class__.__name__, len(self._processed_messages)) - self._processed_messages.clear() - if len(self._retry_times) > self._max_retry_times: - # Only log if we're clearing a significant number of retry times - if len(self._retry_times) > 1000: - syslog('debug', '%s: Clearing retry times dict (size: %d)', - self.__class__.__name__, len(self._retry_times)) - self._retry_times.clear() - self._last_cleanup = now - except Exception as e: - syslog('error', '%s: Error during message cleanup: %s', - self.__class__.__name__, str(e)) + You could, for example, implement a throttling algorithm here. + """ + return self._stop diff --git a/Mailman/Queue/Switchboard.py b/Mailman/Queue/Switchboard.py index c7b88c0d..8353ad94 100644 --- a/Mailman/Queue/Switchboard.py +++ b/Mailman/Queue/Switchboard.py @@ -40,23 +40,13 @@ import errno import pickle import marshal -import email.message -from email.message import Message -import hashlib -import socket -import traceback from Mailman import mm_cfg from Mailman import Utils -from Mailman.Message import Message -from Mailman.Logging.Syslog import mailman_log +from Mailman import Message +from Mailman.Logging.Syslog import syslog from Mailman.Utils import sha_new -# Custom exception class for Switchboard errors -class SwitchboardError(Exception): - """Exception raised for errors in the Switchboard class.""" - pass - # 20 bytes of all bits set, maximum sha.digest() value shamax = 0xffffffffffffffffffffffffffffffffffffffff @@ -73,9 +63,11 @@ class SwitchboardError(Exception): MAX_BAK_COUNT = 3 + class Switchboard: - def __init__(self, whichq, slice=None, numslices=1, recover=False): + def __init__(self, whichq, slice=None, numslices=1, recover=False, distribution='hash'): self.__whichq = whichq + self.__distribution = distribution # Create the directory if it doesn't yet exist. # FIXME omask = os.umask(0) # rwxrws--- @@ -89,655 +81,217 @@ def __init__(self, whichq, slice=None, numslices=1, recover=False): # Fast track for no slices self.__lower = None self.__upper = None + # Always set slice and numslices for compatibility + self.__slice = slice + self.__numslices = numslices # BAW: test performance and end-cases of this algorithm if numslices != 1: - self.__lower = (((shamax+1) * slice) / numslices) - self.__upper = ((((shamax+1) * (slice+1)) / numslices)) - 1 + if distribution == 'hash': + self.__lower = (((shamax+1) * slice) / numslices) + self.__upper = ((((shamax+1) * (slice+1)) / numslices)) - 1 + elif distribution == 'round_robin': + # __slice and __numslices already set above + pass + # Add more distribution methods here as needed if recover: self.recover_backup_files() - # Clean up any stale locks during initialization - self.cleanup_stale_locks() - # Clean up any stale backup files - self.cleanup_stale_backups() - # Clean up any stale processed files - self.cleanup_stale_processed() def whichq(self): return self.__whichq - def enqueue(self, msg, msgdata=None, listname=None, _plaintext=False, **kwargs): - """Add a message to the queue. + def enqueue(self, _msg, _metadata={}, **_kws): + from Mailman.Logging.Syslog import syslog + # Calculate the SHA hexdigest of the message to get a unique base + # filename. We're also going to use the digest as a hash into the set + # of parallel qrunner processes. + data = _metadata.copy() + data.update(_kws) + listname = data.get('listname', '--nolist--') - Args: - msg: The message to enqueue - msgdata: Optional message metadata - listname: Optional list name - _plaintext: Whether to save as plaintext - **kwargs: Additional metadata to add - """ - # Initialize msgdata if not provided - if msgdata is None: - msgdata = {} - - # Add any additional metadata - msgdata.update(kwargs) + # DEBUG: Log archive queue enqueue + if self.__whichq == mm_cfg.ARCHQUEUE_DIR: + syslog('debug', 'Switchboard: Enqueuing message to archive queue for list %s', listname) - # Add listname if provided - if listname: - msgdata['listname'] = listname - - # Then check if we need to set recips - if 'recips' not in msgdata or not msgdata['recips']: - # If we have a recipient but no recips, use the recipient - if msgdata.get('recipient'): - msgdata['recips'] = [msgdata['recipient']] - mailman_log('debug', 'Switchboard.enqueue: Set recips from recipient for message: %s', - msg.get('message-id', 'n/a')) - # Otherwise try to get recipients from message headers - else: - recips = [] - # First try envelope-to header - if msg.get('envelope-to'): - recips.append(msg.get('envelope-to')) - # Then try To header - if msg.get('to'): - addrs = email.utils.getaddresses([msg.get('to')]) - recips.extend([addr[1] for addr in addrs if addr[1]]) - # Then try Cc header - if msg.get('cc'): - addrs = email.utils.getaddresses([msg.get('cc')]) - recips.extend([addr[1] for addr in addrs if addr[1]]) - # Finally try Bcc header - if msg.get('bcc'): - addrs = email.utils.getaddresses([msg.get('bcc')]) - recips.extend([addr[1] for addr in addrs if addr[1]]) - - if recips: - msgdata['recips'] = recips - mailman_log('debug', 'Switchboard.enqueue: Set recipients from message headers for message: %s', - msg.get('message-id', 'n/a')) - else: - mailman_log('error', 'Switchboard: No recipients found in msgdata or message headers for message: %s', - msg.get('message-id', 'n/a')) - raise ValueError('Switchboard: No recipients found in msgdata or message headers') + # Get some data for the input to the sha hash + now = time.time() + if SAVE_MSGS_AS_PICKLES and not data.get('_plaintext'): + protocol = 1 + msgsave = pickle.dumps(_msg, protocol, fix_imports=True) + else: + protocol = 0 + msgsave = pickle.dumps(str(_msg), protocol, fix_imports=True) - # Generate a unique filebase - filebase = self._make_filebase(msg, msgdata) + # Choose distribution method + if self.__distribution == 'round_robin': + # Use a simple counter for round-robin distribution + import threading + if not hasattr(self, '_counter'): + self._counter = 0 + self._counter_lock = threading.Lock() + + with self._counter_lock: + self._counter = (self._counter + 1) % self.__numslices + current_slice = self._counter + hashfood = msgsave + listname.encode() + repr(now).encode() + str(current_slice).encode() + else: + # Default hash-based distribution + hashfood = msgsave + listname.encode() + repr(now).encode() - # Calculate the filename + # Encode the current time into the file name for FIFO sorting in + # files(). The file name consists of two parts separated by a `+': + # the received time for this message (i.e. when it first showed up on + # this system) and the sha hex digest. + #rcvtime = data.setdefault('received_time', now) + rcvtime = data.setdefault('received_time', now) + filebase = repr(rcvtime) + '+' + sha_new(hashfood).hexdigest() filename = os.path.join(self.__whichq, filebase + '.pck') - - # Create a lock file - lockfile = filename + '.lock' - try: - fd = os.open(lockfile, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o644) - os.close(fd) - except OSError as e: - if e.errno != errno.EEXIST: - mailman_log('error', 'Switchboard.enqueue: Failed to create lock file for %s: %s', filebase, str(e)) - raise - return None - + tmpfile = filename + '.tmp' + # Always add the metadata schema version number + data['version'] = mm_cfg.QFILE_SCHEMA_VERSION + # Filter out volatile entries + for k in list(data.keys()): + if k.startswith('_'): + del data[k] + # We have to tell the dequeue() method whether to parse the message + # object or not. + data['_parsemsg'] = (protocol == 0) + # Write to the pickle file the message object and metadata. + omask = os.umask(0o007) # -rw-rw---- try: - # Write the message and metadata + fp = open(tmpfile, 'wb') try: - self._enqueue(filename, msg, msgdata, _plaintext) - except Exception as e: - mailman_log('error', 'Switchboard.enqueue: Failed to write message to %s: %s', filebase, str(e)) - raise - - # Add filebase to msgdata for cleanup - msgdata['filebase'] = filebase - return filebase + fp.write(msgsave) + pickle.dump(data, fp, protocol) + fp.flush() + os.fsync(fp.fileno()) + finally: + fp.close() finally: - # Always clean up the lock file - try: - os.unlink(lockfile) - except OSError: - pass + os.umask(omask) + os.rename(tmpfile, filename) + + # DEBUG: Log successful enqueue + if self.__whichq == mm_cfg.ARCHQUEUE_DIR: + syslog('debug', 'Switchboard: Successfully enqueued message to archive queue: %s', filebase) + + return filebase def dequeue(self, filebase): # Calculate the filename from the given filebase. filename = os.path.join(self.__whichq, filebase + '.pck') - bakfile = os.path.join(self.__whichq, filebase + '.bak') - psvfile = os.path.join(self.__whichq, filebase + '.psv') - lockfile = filename + '.lock' - - # Check if file exists before proceeding - if not os.path.exists(filename): - # Check if it's been moved to backup or shunt - if os.path.exists(bakfile): - mailman_log('debug', 'Queue file %s has been moved to backup file %s', filename, bakfile) - elif os.path.exists(psvfile): - mailman_log('debug', 'Queue file %s has been moved to shunt queue %s', filename, psvfile) - else: - mailman_log('warning', 'Queue file does not exist: %s (not found in backup or shunt either)', filename) - return None, None - - # Create a lock file - try: - lock_fd = os.open(lockfile, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600) - os.close(lock_fd) - except OSError as e: - if e.errno == errno.EEXIST: - mailman_log('warning', 'Lock file exists for %s (full path: %s)', filename, lockfile) - return None, None - else: - mailman_log('error', 'Failed to create lock file %s (full path: %s): %s', filename, lockfile, str(e)) - return None, None - + backfile = os.path.join(self.__whichq, filebase + '.bak') + # Read the message object and metadata. + fp = open(filename, 'rb') + # Move the file to the backup file name for processing. If this + # process crashes uncleanly the .bak file will be used to re-instate + # the .pck file in order to try again. + os.rename(filename, backfile) try: - # First read the file contents - try: - with open(filename, 'rb') as fp: - content = fp.read() - if not content: - mailman_log('error', 'Empty queue file: %s', filename) - return None, None - - # Create a BytesIO object to read from the content - from io import BytesIO - fp = BytesIO(content) - - try: - msg = pickle.load(fp, fix_imports=True, encoding='latin1') - data = pickle.load(fp, fix_imports=True, encoding='latin1') - except (EOFError, pickle.UnpicklingError) as e: - mailman_log('error', 'Error loading queue file %s: %s', filename, str(e)) - return None, None - except (IOError, OSError) as e: - mailman_log('error', 'Error reading queue file %s: %s', filename, str(e)) - return None, None - - # Now that we've successfully read the file, move it to backup - try: - os.rename(filename, bakfile) - except (IOError, OSError) as e: - mailman_log('error', 'Error moving queue file %s to backup: %s', filename, str(e)) - return None, None - - if data.get('_parsemsg'): - msg = email.message_from_string(msg, Message) - # Add filebase to msgdata for cleanup - if data is not None: - data['filebase'] = filebase - return msg, data - + msg = pickle.load(fp, fix_imports=True, encoding='latin1') + data = pickle.load(fp, fix_imports=True, encoding='latin1') finally: - # Always clean up the lock file - try: - if os.path.exists(lockfile): - os.unlink(lockfile) - except OSError: - pass + fp.close() + if data.get('_parsemsg'): + msg = email.message_from_string(msg, Message.Message) + return msg, data def finish(self, filebase, preserve=False): - """Finish processing a file by either removing it or moving it to the shunt queue. - - Args: - filebase: The base name of the file to process - preserve: If True, move the file to the shunt queue instead of removing it - """ - if not filebase: - mailman_log('error', 'Switchboard.finish: No filebase provided') - return - bakfile = os.path.join(self.__whichq, filebase + '.bak') - pckfile = os.path.join(self.__whichq, filebase + '.pck') - - # First check if the backup file exists - if not os.path.exists(bakfile): - # Only log at debug level if the .pck file still exists (message still being processed) - if os.path.exists(pckfile): - mailman_log('debug', 'Switchboard.finish: Backup file does not exist: %s', bakfile) - # Try to clean up the .pck file if it exists - try: - os.unlink(pckfile) - mailman_log('debug', 'Switchboard.finish: Removed stale .pck file: %s', pckfile) - except OSError as e: - mailman_log('error', 'Switchboard.finish: Failed to remove stale .pck file %s: %s', - pckfile, str(e)) - return - try: if preserve: - # Move the file to the shunt queue - psvfile = os.path.join(mm_cfg.SHUNTQUEUE_DIR, filebase + '.bak') - - # Ensure the shunt queue directory exists - if not os.path.exists(mm_cfg.SHUNTQUEUE_DIR): + psvfile = os.path.join(mm_cfg.BADQUEUE_DIR, filebase + '.psv') + # Create the directory if it doesn't yet exist. + # Copied from __init__. + omask = os.umask(0) # rwxrws--- + try: try: - os.makedirs(mm_cfg.SHUNTQUEUE_DIR, 0o775) + os.mkdir(mm_cfg.BADQUEUE_DIR, 0o0770) except OSError as e: - mailman_log('error', 'Switchboard.finish: Failed to create shunt queue directory: %s', - str(e)) - raise - - # Move the file and verify - try: - os.rename(bakfile, psvfile) - if not os.path.exists(psvfile): - mailman_log('error', 'Switchboard.finish: Failed to move backup file to shunt queue: %s -> %s', - bakfile, psvfile) - else: - mailman_log('debug', 'Switchboard.finish: Successfully moved backup file to shunt queue: %s -> %s', - bakfile, psvfile) - except OSError as e: - mailman_log('error', 'Switchboard.finish: Failed to move backup file to shunt queue: %s -> %s: %s', - bakfile, psvfile, str(e)) - raise + if e.errno != errno.EEXIST: raise + finally: + os.umask(omask) + os.rename(bakfile, psvfile) else: - # Remove the backup file - try: - os.unlink(bakfile) - if os.path.exists(bakfile): - mailman_log('error', 'Switchboard.finish: Failed to unlink backup file: %s', bakfile) - else: - mailman_log('debug', 'Switchboard.finish: Successfully unlinked backup file: %s', bakfile) - except OSError as e: - mailman_log('error', 'Switchboard.finish: Failed to unlink backup file %s: %s', - bakfile, str(e)) - raise - except Exception as e: - mailman_log('error', 'Switchboard.finish: Failed to finish processing backup file %s: %s', - bakfile, str(e)) - raise + os.unlink(bakfile) + except EnvironmentError as e: + syslog('error', 'Failed to unlink/preserve backup file: %s\n%s', + bakfile, e) def files(self, extension='.pck'): times = {} lower = self.__lower upper = self.__upper - try: - for f in os.listdir(self.__whichq): - if not f.endswith(extension): - continue - filebase = f[:-len(extension)] - try: - # Get the file's modification time - mtime = os.path.getmtime(os.path.join(self.__whichq, f)) - # Only apply time bounds if they are set - if lower is None or upper is None or (lower <= mtime < upper): - times[filebase] = mtime - except OSError: - continue - # Sort by modification time but return just the filebases - return [f for f, _ in sorted(times.items(), key=lambda x: x[1])] - except OSError as e: - mailman_log('error', 'Error reading queue directory %s: %s', self.__whichq, str(e)) - return [] + for f in os.listdir(self.__whichq): + # By ignoring anything that doesn't end in .pck, we ignore + # tempfiles and avoid a race condition. + filebase, ext = os.path.splitext(f) + if ext != extension: + continue + when, digest = filebase.split('+') + + # Choose distribution method for file filtering + if self.__distribution == 'round_robin': + # For round-robin, use modulo of digest to determine slice + slice_num = int(digest, 16) % self.__numslices + if slice_num == self.__slice: + key = float(when) + while key in times: + key += DELTA + times[key] = filebase + else: + # Default hash-based distribution + # Throw out any files which don't match our bitrange. BAW: test + # performance and end-cases of this algorithm. MAS: both + # comparisons need to be <= to get complete range. + if lower is None or (lower <= int(digest, 16) <= upper): + key = float(when) + while key in times: + key += DELTA + times[key] = filebase + # FIFO sort + keys = list(times.keys()) + keys.sort() + return [times[k] for k in keys] def recover_backup_files(self): - """Move all .bak files in our slice to .pck. - - This method implements a robust recovery mechanism with: - 1. Proper error handling for corrupted files - 2. Validation of backup file contents - 3. Detailed logging of recovery attempts - 4. Safe file operations with atomic moves - """ - try: - for filebase in self.files('.bak'): - src = os.path.join(self.__whichq, filebase + '.bak') - dst = os.path.join(self.__whichq, filebase + '.pck') - - try: - # First try to validate the backup file - with open(src, 'rb') as fp: - try: - # Try to read the entire file first to check for EOF - content = fp.read() - if not content: - mailman_log('error', 'Empty backup file found: %s', filebase) - raise EOFError('Empty backup file') - - # Create a BytesIO object to read from the content - from io import BytesIO - fp = BytesIO(content) - - try: - msg = pickle.load(fp, fix_imports=True, encoding='latin1') - data_pos = fp.tell() - data = pickle.load(fp, fix_imports=True, encoding='latin1') - except (EOFError, pickle.UnpicklingError) as e: - mailman_log('error', 'Corrupted backup file %s: %s\nTraceback:\n%s', - filebase, str(e), traceback.format_exc()) - self.finish(filebase, preserve=True) - return - - # Validate the unpickled data - if not isinstance(data, dict): - mailman_log('error', 'Invalid data format in backup file %s: expected dict, got %s', filebase, type(data)) - raise TypeError('Invalid data format in backup file') - - try: - os.rename(src, dst) - except Exception as e: - mailman_log('error', 'Failed to rename backup file %s (full paths: %s -> %s): %s\nTraceback:\n%s', - filebase, os.path.join(self.__whichq, filebase + '.bak'), os.path.join(self.__whichq, filebase + '.pck'), str(e), traceback.format_exc()) - self.finish(filebase, preserve=True) - return - except Exception as e: - mailman_log('error', 'Failed to process backup file %s (full path: %s): %s\nTraceback:\n%s', - filebase, os.path.join(self.__whichq, filebase + '.bak'), str(e), traceback.format_exc()) - self.finish(filebase, preserve=True) - return - - except Exception as e: - mailman_log('error', 'Failed to process backup file %s (full path: %s): %s\nTraceback:\n%s', - filebase, os.path.join(self.__whichq, filebase + '.bak'), str(e), traceback.format_exc()) - return None, None - except Exception as e: - mailman_log('error', 'Failed to recover backup files: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - raise - - def _enqueue(self, filename, msg, msgdata, _plaintext): - """Enqueue a message for delivery. - - This method implements a robust enqueue mechanism with: - 1. Unique temporary filename - 2. Atomic write - 3. Validation of written data - 4. Proper error handling and cleanup - 5. File locking for concurrent access - """ - # Create a unique filename using the standard format - now = time.time() - msgid = msg.get('message-id', '') - listname = msgdata.get('listname', '--nolist--') - hash_input = (str(msgid) + str(listname) + str(now)).encode('utf-8') - digest = hashlib.sha1(hash_input).hexdigest() - filebase = "%d+%s" % (int(now), digest) - qfile = os.path.join(self.__whichq, filebase + '.pck') - tmpfile = qfile + '.tmp.%s.%d' % (socket.gethostname(), os.getpid()) - lockfile = qfile + '.lock' - - # Create lock file - try: - lock_fd = os.open(lockfile, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600) - os.close(lock_fd) - except OSError as e: - if e.errno == errno.EEXIST: - mailman_log('warning', 'Lock file exists for %s (full path: %s)', qfile, lockfile) - raise - else: - mailman_log('error', 'Failed to create lock file %s (full path: %s): %s\nTraceback:\n%s', - qfile, lockfile, str(e), traceback.format_exc()) - raise - - try: - # Ensure directory exists with proper permissions - dirname = os.path.dirname(tmpfile) - if not os.path.exists(dirname): - try: - os.makedirs(dirname, 0o755) - except Exception as e: - mailman_log('error', 'Failed to create directory %s (full path: %s): %s\nTraceback:\n%s', - dirname, os.path.abspath(dirname), str(e), traceback.format_exc()) - raise - - # Convert message to Mailman.Message if needed - if isinstance(msg, email.message.Message) and not isinstance(msg, Message): - mailman_msg = Message() - # Copy all attributes from the original message - for key, value in msg.items(): - mailman_msg[key] = value - # Copy the payload with proper MIME handling - if msg.is_multipart(): - for part in msg.get_payload(): - if isinstance(part, email.message.Message): - mailman_msg.attach(part) - else: - newpart = Message() - newpart.set_payload(part) - mailman_msg.attach(newpart) - else: - mailman_msg.set_payload(msg.get_payload()) - msg = mailman_msg - - # Write to temporary file first + # Move all .bak files in our slice to .pck. It's impossible for both + # to exist at the same time, so the move is enough to ensure that our + # normal dequeuing process will handle them. We keep count in + # _bak_count in the metadata of the number of times we recover this + # file. When the count reaches MAX_BAK_COUNT, we move the .bak file + # to a .psv file in the shunt queue. + for filebase in self.files('.bak'): + src = os.path.join(self.__whichq, filebase + '.bak') + dst = os.path.join(self.__whichq, filebase + '.pck') + fp = open(src, 'rb+') try: - with open(tmpfile, 'wb') as fp: - pickle.dump((msg, msgdata), fp, protocol=4, fix_imports=True) - fp.flush() - if hasattr(os, 'fsync'): - os.fsync(fp.fileno()) - except Exception as e: - mailman_log('error', 'Failed to write temporary file %s (full path: %s): %s\nTraceback:\n%s', - tmpfile, os.path.abspath(tmpfile), str(e), traceback.format_exc()) - raise - - # Validate the temporary file - try: - with open(tmpfile, 'rb') as fp: - test_data = pickle.load(fp, fix_imports=True, encoding='latin1') - if not isinstance(test_data, tuple) or len(test_data) != 2: - raise TypeError('Loaded data is not a valid tuple') - # Verify message type - if not isinstance(test_data[0], Message): - raise TypeError('Message is not a Mailman.Message instance') - except Exception as e: - mailman_log('error', 'Validation of temporary file failed: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - # Try to clean up - try: - os.unlink(tmpfile) - except Exception as cleanup_e: - mailman_log('error', 'Failed to clean up temporary file %s (full path: %s): %s\nTraceback:\n%s', - tmpfile, os.path.abspath(tmpfile), str(cleanup_e), traceback.format_exc()) - raise - - # Atomic rename with existence check - try: - if os.path.exists(qfile): - mailman_log('warning', 'Target file %s (full path: %s) already exists, removing old version', qfile, os.path.abspath(qfile)) - os.unlink(qfile) - os.rename(tmpfile, qfile) - except Exception as e: - mailman_log('error', 'Failed to rename %s to %s (full paths: %s -> %s): %s\nTraceback:\n%s', - tmpfile, qfile, os.path.abspath(tmpfile), os.path.abspath(qfile), str(e), traceback.format_exc()) - # Try to clean up - try: - if os.path.exists(tmpfile): - os.unlink(tmpfile) - except Exception as cleanup_e: - mailman_log('error', 'Failed to clean up temporary file %s (full path: %s): %s\nTraceback:\n%s', - tmpfile, os.path.abspath(tmpfile), str(cleanup_e), traceback.format_exc()) - raise - - # Set proper permissions - try: - os.chmod(qfile, 0o660) - except Exception as e: - mailman_log('warning', 'Failed to set permissions on %s (full path: %s): %s\nTraceback:\n%s', - qfile, os.path.abspath(qfile), str(e), traceback.format_exc()) - # Not critical, continue - - finally: - # Clean up any temporary files and lock - try: - if os.path.exists(tmpfile): - os.unlink(tmpfile) - if os.path.exists(lockfile): - os.unlink(lockfile) - except Exception as cleanup_e: - mailman_log('error', 'Failed to clean up temporary/lock files: %s\nTraceback:\n%s', - str(cleanup_e), traceback.format_exc()) - - def _dequeue(self, filename): - """Dequeue a message from the queue.""" - try: - with open(filename, 'rb') as fp: try: - # Try UTF-8 first for newer files - data = pickle.load(fp, fix_imports=True, encoding='utf-8') - if not isinstance(data, tuple) or len(data) != 2: - raise TypeError('Invalid data format in queue file') - msgsave, metadata = data - - # Ensure we have a Mailman.Message - if isinstance(msgsave, email.message.Message) and not isinstance(msgsave, Message): - mailman_msg = Message() - # Copy all attributes from the original message - for key, value in msgsave.items(): - mailman_msg[key] = value - # Copy the payload with proper MIME handling - if msgsave.is_multipart(): - for part in msgsave.get_payload(): - if isinstance(part, email.message.Message): - mailman_msg.attach(part) - else: - newpart = Message() - newpart.set_payload(part) - mailman_msg.attach(newpart) - else: - mailman_msg.set_payload(msgsave.get_payload()) - msgsave = mailman_msg - - return msgsave, metadata - except (UnicodeDecodeError, pickle.UnpicklingError): - # Fall back to latin1 for older files - fp.seek(0) + msg = pickle.load(fp, fix_imports=True, encoding='latin1') + data_pos = fp.tell() data = pickle.load(fp, fix_imports=True, encoding='latin1') - if not isinstance(data, tuple) or len(data) != 2: - raise TypeError('Invalid data format in queue file') - msgsave, metadata = data - - # Ensure we have a Mailman.Message - if isinstance(msgsave, email.message.Message) and not isinstance(msgsave, Message): - mailman_msg = Message() - # Copy all attributes from the original message - for key, value in msgsave.items(): - mailman_msg[key] = value - # Copy the payload with proper MIME handling - if msgsave.is_multipart(): - for part in msgsave.get_payload(): - if isinstance(part, email.message.Message): - mailman_msg.attach(part) - else: - newpart = Message() - newpart.set_payload(part) - mailman_msg.attach(newpart) - else: - mailman_msg.set_payload(msgsave.get_payload()) - msgsave = mailman_msg - - return msgsave, metadata - except (IOError, OSError) as e: - mailman_log('error', 'Error dequeuing message from %s: %s', filename, str(e)) - return None, None - - def _dequeue_metadata(self, filename): - """Dequeue just the metadata from the queue.""" - try: - with open(filename, 'rb') as fp: - try: - # Try UTF-8 first, then fall back to latin-1 - try: - # Skip the message - pickle.load(fp, fix_imports=True, encoding='utf-8') - # Get the metadata - metadata = pickle.load(fp, fix_imports=True, encoding='utf-8') - except (pickle.UnpicklingError, EOFError) as e: - # Reset file pointer to beginning - fp.seek(0) - # Try latin-1 as fallback - pickle.load(fp, fix_imports=True, encoding='latin1') - metadata = pickle.load(fp, fix_imports=True, encoding='latin1') - except (pickle.UnpicklingError, EOFError) as e: - raise IOError('Could not unpickle %s: %s' % (filename, e)) - return metadata - except (IOError, OSError) as e: - raise IOError('Could not read %s: %s' % (filename, e)) - - def cleanup_stale_locks(self): - """Clean up any stale lock files in the queue directory.""" - try: - for f in os.listdir(self.__whichq): - if f.endswith('.lock'): - lockfile = os.path.join(self.__whichq, f) - try: - lock_age = time.time() - os.path.getmtime(lockfile) - if lock_age > 300: # 5 minutes - # Read lock file contents for debugging - try: - with open(lockfile, 'r') as f: - lock_info = f.read() - mailman_log('warning', - 'Cleaning up stale lock file %s (age: %d seconds)\nLock info: %s', - lockfile, lock_age, lock_info) - except Exception: - mailman_log('warning', - 'Cleaning up stale lock file %s (age: %d seconds)', - lockfile, lock_age) - os.unlink(lockfile) - except OSError: - pass - except OSError as e: - mailman_log('error', 'Error cleaning up stale locks: %s', str(e)) - - def cleanup_stale_backups(self): - """Clean up any stale backup files in the queue directory. - - This method removes backup files that are older than 24 hours - to prevent accumulation of stale files. - """ - try: - now = time.time() - stale_age = 24 * 3600 # 24 hours in seconds - - for f in os.listdir(self.__whichq): - if f.endswith('.bak'): - bakfile = os.path.join(self.__whichq, f) - try: - # Check file age - file_age = now - os.path.getmtime(bakfile) - if file_age > stale_age: - mailman_log('warning', - 'Cleaning up stale backup file %s (age: %d seconds)', - bakfile, file_age) - os.unlink(bakfile) - except OSError as e: - mailman_log('error', - 'Failed to clean up stale backup file %s: %s', - bakfile, str(e)) - except OSError as e: - mailman_log('error', 'Error cleaning up stale backup files: %s', str(e)) - - def cleanup_stale_processed(self): - """Clean up any stale processed files in the queue directory. - - This method removes processed files that are older than 7 days - to prevent accumulation of stale files. - """ - try: - now = time.time() - stale_age = 7 * 24 * 3600 # 7 days in seconds - - for f in os.listdir(self.__whichq): - if f.endswith('.pck'): - pckfile = os.path.join(self.__whichq, f) - try: - # Check file age - file_age = now - os.path.getmtime(pckfile) - if file_age > stale_age: - mailman_log('warning', - 'Cleaning up stale processed file %s (age: %d seconds)', - pckfile, file_age) - os.unlink(pckfile) - except OSError as e: - mailman_log('error', - 'Failed to clean up stale processed file %s: %s', - pckfile, str(e)) - except OSError as e: - mailman_log('error', 'Error cleaning up stale processed files: %s', str(e)) - - def _make_filebase(self, msg, msgdata): - import hashlib - import time - msgid = msg.get('message-id', '') - listname = msgdata.get('listname', '--nolist--') - now = time.time() - hash_input = (str(msgid) + str(listname) + str(now)).encode('utf-8') - digest = hashlib.sha1(hash_input).hexdigest() - return "%d+%s" % (int(now), digest) + except Exception as s: + # If unpickling throws any exception, just log and + # preserve this entry + syslog('error', 'Unpickling .bak exception: %s\n' + + 'preserving file: %s', s, filebase) + self.finish(filebase, preserve=True) + else: + data['_bak_count'] = data.setdefault('_bak_count', 0) + 1 + fp.seek(data_pos) + if data.get('_parsemsg'): + protocol = 0 + else: + protocol = 1 + pickle.dump(data, fp, protocol) + fp.truncate() + fp.flush() + os.fsync(fp.fileno()) + if data['_bak_count'] >= MAX_BAK_COUNT: + syslog('error', + '.bak file max count, preserving file: %s', + filebase) + self.finish(filebase, preserve=True) + else: + os.rename(src, dst) + finally: + fp.close() diff --git a/Mailman/Queue/VirginRunner.py b/Mailman/Queue/VirginRunner.py index f50b9d84..410a9336 100644 --- a/Mailman/Queue/VirginRunner.py +++ b/Mailman/Queue/VirginRunner.py @@ -25,131 +25,11 @@ from Mailman import mm_cfg from Mailman.Queue.Runner import Runner from Mailman.Queue.IncomingRunner import IncomingRunner -from Mailman.Logging.Syslog import mailman_log -import time -import traceback -from Mailman import Errors -import threading -import email.header -import os + class VirginRunner(IncomingRunner): QDIR = mm_cfg.VIRGINQUEUE_DIR - # Maximum age for message tracking data - _max_tracking_age = 86400 # 24 hours in seconds - # Cleanup interval for message tracking data - _cleanup_interval = 3600 # 1 hour in seconds - - # Message tracking configuration - _processed_messages = set() - _processed_lock = threading.Lock() - _last_cleanup = time.time() - _max_processed_messages = 10000 - _processed_times = {} # Track processing times for messages - - def __init__(self, slice=None, numslices=1): - IncomingRunner.__init__(self, slice, numslices) - # VirginRunner is a subclass of IncomingRunner, but we want to use a - # different pipeline for processing virgin messages. The main - # difference is that we don't need to do bounce detection, and we can - # skip a few other checks. - self._pipeline = self._get_pipeline() - # VirginRunner is a subclass of IncomingRunner, but we want to use a - # different pipeline for processing virgin messages. The main - # difference is that we don't need to do bounce detection, and we can - # skip a few other checks. - self._fasttrack = 1 - mailman_log('debug', 'VirginRunner: Starting initialization') - try: - Runner.__init__(self, slice, numslices) - - # Initialize processed messages tracking - self._processed_messages = set() - self._processed_times = {} - self._last_cleanup = time.time() - - mailman_log('debug', 'VirginRunner: Initialization complete') - except Exception as e: - mailman_log('error', 'VirginRunner: Initialization failed: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - raise - - def _check_message_processed(self, msgid, filebase, msg): - """Check if a message has already been processed. - Returns True if the message can be processed, False if it's a duplicate.""" - try: - with self._processed_lock: - current_time = time.time() - - # Check if cleanup is needed - if current_time - self._last_cleanup > self._cleanup_interval: - try: - mailman_log('debug', 'VirginRunner: Starting cleanup of old message tracking data') - # Only clean up entries older than cleanup_interval - cutoff_time = current_time - self._cleanup_interval - # Clean up old message IDs - old_msgids = [mid for mid, process_time in self._processed_times.items() - if process_time < cutoff_time] - for mid in old_msgids: - self._processed_times.pop(mid, None) - self._processed_messages.discard(mid) - self._last_cleanup = current_time - mailman_log('debug', 'VirginRunner: Cleaned up %d old message entries', len(old_msgids)) - except Exception as e: - mailman_log('error', 'VirginRunner: Error during cleanup: %s', str(e)) - # Continue processing even if cleanup fails - - # For welcome messages, check content and recipients - subject = msg.get('subject', '') - if isinstance(subject, email.header.Header): - subject = str(subject) - subject = subject.lower() - - if 'welcome to the' in subject: - # Create a unique key based on subject, to, and from - to_addr = msg.get('to', '') - from_addr = msg.get('from', '') - if isinstance(to_addr, email.header.Header): - to_addr = str(to_addr) - if isinstance(from_addr, email.header.Header): - from_addr = str(from_addr) - - content_key = f"{subject}|{to_addr}|{from_addr}" - if content_key in self._processed_messages: - mailman_log('info', 'VirginRunner: Duplicate welcome message detected: %s (file: %s)', - content_key, filebase) - return False - # Mark this content as processed - self._processed_messages.add(content_key) - self._processed_times[content_key] = current_time - return True - - # For other messages, check message ID - if msgid in self._processed_messages: - mailman_log('info', 'VirginRunner: Duplicate message detected: %s (file: %s)', - msgid, filebase) - return False - - # Mark message as processed - try: - self._processed_messages.add(msgid) - self._processed_times[msgid] = current_time - mailman_log('debug', 'VirginRunner: Message %s (file: %s) marked for processing', - msgid, filebase) - return True - except Exception as e: - # If we fail to update the tracking data, remove the message from processed set - self._processed_messages.discard(msgid) - self._processed_times.pop(msgid, None) - mailman_log('error', 'VirginRunner: Failed to update tracking data for message %s: %s', - msgid, str(e)) - return False - - except Exception as e: - mailman_log('error', 'VirginRunner: Unexpected error in message check for %s: %s', - msgid, str(e)) - return False def _dispose(self, mlist, msg, msgdata): # We need to fasttrack this message through any handlers that touch @@ -161,111 +41,3 @@ def _get_pipeline(self, mlist, msg, msgdata): # It's okay to hardcode this, since it'll be the same for all # internally crafted messages. return ['CookHeaders', 'ToOutgoing'] - - def _cleanup_old_messages(self): - """Clean up old message tracking data.""" - with self._processed_lock: - if len(self._processed_messages) > self._max_processed_messages: - mailman_log('debug', 'VirginRunner._cleanup_old_messages: Clearing processed messages set (size: %d)', - len(self._processed_messages)) - self._processed_messages.clear() - if len(self._processed_times) > self._max_processed_messages: - mailman_log('debug', 'VirginRunner._cleanup_old_messages: Clearing processed times dict (size: %d)', - len(self._processed_times)) - self._processed_times.clear() - self._last_cleanup = time.time() - - def _onefile(self, msg, msgdata): - """Process a single file from the queue.""" - # Ensure _dispose always gets a MailList object, not a string - listname = msgdata.get('listname') - if not listname: - listname = mm_cfg.MAILMAN_SITE_LIST - try: - # Lazy import to avoid circular dependency - from Mailman.MailList import MailList - mlist = MailList(listname, lock=0) - except Errors.MMUnknownListError: - mailman_log('error', 'VirginRunner: Unknown list %s', listname) - self._shunt.enqueue(msg, msgdata) - return False - try: - keepqueued = self._dispose(mlist, msg, msgdata) - if keepqueued: - self._switchboard.enqueue(msg, msgdata) - return keepqueued - finally: - mlist.Unlock() - - def _unmark_message_processed(self, msgid): - """Remove a message from the processed messages set.""" - with self._processed_lock: - if msgid in self._processed_messages: - self._processed_messages.remove(msgid) - if msgid in self._processed_times: - del self._processed_times[msgid] - mailman_log('debug', 'VirginRunner: Unmarked message %s as processed', msgid) - - def _oneloop(self): - """Process one batch of messages from the virgin queue.""" - try: - # Get the list of files to process - files = self._switchboard.files() - if not files: - mailman_log('debug', 'VirginRunner: No files to process') - return - - mailman_log('debug', 'VirginRunner: Processing %d files', len(files)) - - # Process each file - for filebase in files: - try: - # Check if the file exists before dequeuing - pckfile = os.path.join(self.QDIR, filebase + '.pck') - if not os.path.exists(pckfile): - mailman_log('error', 'VirginRunner._oneloop: File %s does not exist, skipping', pckfile) - continue - - # Check if file is locked - lockfile = os.path.join(self.QDIR, filebase + '.pck.lock') - if os.path.exists(lockfile): - mailman_log('debug', 'VirginRunner._oneloop: File %s is locked by another process, skipping', filebase) - continue - - # Dequeue the file - msg, msgdata = self._switchboard.dequeue(filebase) - if msg is None: - mailman_log('debug', 'VirginRunner._oneloop: No message data for %s', filebase) - continue - - # Get message ID for tracking - msgid = msg.get('message-id', 'n/a') - - # Check if message has already been processed - if not self._check_message_processed(msgid, filebase, msg): - mailman_log('debug', 'VirginRunner._oneloop: Message %s already processed, skipping', msgid) - continue - - try: - # Process the message - success = self._onefile(msg, msgdata) - if success: - mailman_log('debug', 'VirginRunner: Successfully processed message %s', msgid) - else: - mailman_log('debug', 'VirginRunner: Message %s requeued for later processing', msgid) - except Exception as e: - mailman_log('error', 'VirginRunner: Error processing %s: %s', msgid, str(e)) - mailman_log('error', 'VirginRunner: Traceback:\n%s', traceback.format_exc()) - self._handle_error(e, msg, None) - # Unmark the message as processed since it failed - self._unmark_message_processed(msgid) - - except Exception as e: - mailman_log('error', 'VirginRunner: Error processing file %s: %s', filebase, str(e)) - mailman_log('error', 'VirginRunner: Traceback:\n%s', traceback.format_exc()) - continue - - except Exception as e: - mailman_log('error', 'VirginRunner: Error in _oneloop: %s', str(e)) - mailman_log('error', 'VirginRunner: Traceback:\n%s', traceback.format_exc()) - raise diff --git a/Mailman/Queue/__init__.py b/Mailman/Queue/__init__.py index cef674fb..3bf720f9 100644 --- a/Mailman/Queue/__init__.py +++ b/Mailman/Queue/__init__.py @@ -13,61 +13,3 @@ # 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. - -"""Mailman Queue package initialization. - -This package contains the queue runners that process various types of messages -in the Mailman system. -""" - -import os -import sys - -# Add the parent directory to the Python path if it's not already there -parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -if parent_dir not in sys.path: - sys.path.insert(0, parent_dir) - -# Import the base Runner class first -from Mailman.Queue.Runner import Runner - -# Then import the Switchboard -from Mailman.Queue.Switchboard import Switchboard - -# Import other runners that don't have dependencies -from Mailman.Queue.BounceRunner import BounceRunner -from Mailman.Queue.MaildirRunner import MaildirRunner -from Mailman.Queue.RetryRunner import RetryRunner -from Mailman.Queue.CommandRunner import CommandRunner -from Mailman.Queue.ArchRunner import ArchRunner - -# Define lazy imports to avoid circular dependencies -def get_news_runner(): - from Mailman.Queue.NewsRunner import NewsRunner - return NewsRunner - -def get_incoming_runner(): - from Mailman.Queue.IncomingRunner import IncomingRunner - return IncomingRunner - -def get_virgin_runner(): - from Mailman.Queue.VirginRunner import VirginRunner - return VirginRunner - -def get_outgoing_runner(): - from Mailman.Queue.OutgoingRunner import OutgoingRunner - return OutgoingRunner - -__all__ = [ - 'Runner', - 'Switchboard', - 'BounceRunner', - 'MaildirRunner', - 'RetryRunner', - 'CommandRunner', - 'ArchRunner', - 'get_news_runner', - 'get_incoming_runner', - 'get_virgin_runner', - 'get_outgoing_runner', -] diff --git a/Mailman/SecurityManager.py b/Mailman/SecurityManager.py index 465e71fa..492cd930 100644 --- a/Mailman/SecurityManager.py +++ b/Mailman/SecurityManager.py @@ -66,7 +66,7 @@ from Mailman import mm_cfg from Mailman import Utils from Mailman import Errors -from Mailman.Logging.Syslog import syslog, mailman_log +from Mailman.Logging.Syslog import syslog from Mailman.Utils import md5_new, sha_new @@ -97,7 +97,7 @@ def AuthContextInfo(self, authcontext, user=None): if authcontext == mm_cfg.AuthUser: if user is None: # A bad system error - raise Exception(TypeError, 'No user supplied for AuthUser context') + raise TypeError('No user supplied for AuthUser context') user = Utils.UnobscureEmail(urllib.parse.unquote(user)) secret = self.getMemberPassword(user) userdata = urllib.parse.quote(Utils.ObscureEmail(user), safe='') @@ -139,11 +139,7 @@ def Authenticate(self, authcontexts, response, user=None): if not response: # Don't authenticate null passwords return mm_cfg.UnAuthorized - # Log the type and encoding of the response - mailman_log('debug', 'Auth response type: %s, encoding: %s', - type(response), getattr(response, 'encoding', 'N/A')) - # python3 - response = response.encode('UTF-8') + for ac in authcontexts: if ac == mm_cfg.AuthCreator: ok = Utils.check_global_password(response, siteadmin=0) @@ -177,6 +173,9 @@ def cryptmatchp(response, secret): key, secret = self.AuthContextInfo(ac) if secret is None: continue + if isinstance(response, str): + response = response.encode('utf-8') + sharesponse = sha_new(response).hexdigest() upgrade = ok = False if sharesponse == secret: @@ -252,9 +251,7 @@ def MakeCookie(self, authcontext, user=None): mac = sha_new(needs_hashing).hexdigest() # Create the cookie object. c = http.cookies.SimpleCookie() - # Ensure cookie value is a string, not bytes - cookie_value = binascii.hexlify(marshal.dumps((issued, mac))).decode('ascii') - c[key] = cookie_value + c[key] = binascii.hexlify(marshal.dumps((issued, mac))).decode() # The path to all Mailman stuff, minus the scheme and host, # i.e. usually the string `/mailman' parsed = urlparse(self.web_page_url) diff --git a/Mailman/Site.py b/Mailman/Site.py index 8e03d6a0..6fa6afb1 100644 --- a/Mailman/Site.py +++ b/Mailman/Site.py @@ -100,14 +100,7 @@ def get_listnames(domain=None): from Mailman.Utils import list_exists # We don't currently support separate virtual domain directories got = [] - # Ensure LIST_DATA_DIR is a string - list_dir = mm_cfg.LIST_DATA_DIR - if isinstance(list_dir, bytes): - list_dir = list_dir.decode('utf-8', 'replace') - for fn in os.listdir(list_dir): + for fn in os.listdir(mm_cfg.LIST_DATA_DIR): if list_exists(fn): - # Ensure we return strings, not bytes - if isinstance(fn, bytes): - fn = fn.decode('utf-8', 'replace') got.append(fn) return got diff --git a/Mailman/UserDesc.py b/Mailman/UserDesc.py index d4536cf7..575749f5 100644 --- a/Mailman/UserDesc.py +++ b/Mailman/UserDesc.py @@ -30,8 +30,8 @@ def __init__(self, address=None, fullname=None, password=None, self.password = password if digest is not None: self.digest = digest - # Always set language, defaulting to None if not provided - self.language = lang + if lang is not None: + self.language = lang def __iadd__(self, other): if getattr(other, 'address', None) is not None: diff --git a/Mailman/Utils.py b/Mailman/Utils.py index 5c2ece82..e67a877e 100644 --- a/Mailman/Utils.py +++ b/Mailman/Utils.py @@ -31,15 +31,20 @@ import errno import base64 import random -import urllib.request, urllib.parse, urllib.error +import urllib +import urllib.request, urllib.error import html.entities import html import email.header import email.iterators -import pickle from email.errors import HeaderParseError from string import whitespace, digits -from urllib.parse import urlparse +from urllib.parse import urlparse, parse_qs +import tempfile +import io +from email.parser import BytesParser +from email.policy import HTTP + try: # Python 2.2 from string import ascii_letters @@ -48,11 +53,223 @@ _lower = 'abcdefghijklmnopqrstuvwxyz' ascii_letters = _lower + _lower.upper() + +class FieldStorage: + """ + A modern replacement for cgi.FieldStorage using urllib.parse and email libraries. + + This class provides the same interface as cgi.FieldStorage but uses + modern Python libraries instead of the deprecated cgi module. + """ + + def __init__(self, fp=None, headers=None, environ=None, + keep_blank_values=False, strict_parsing=False, + encoding='utf-8', errors='replace'): + self.keep_blank_values = keep_blank_values + self.strict_parsing = strict_parsing + self.encoding = encoding + self.errors = errors + self._data = {} + self._files = {} + + if environ is None: + environ = os.environ + + self.environ = environ + + # Get the request method + self.method = environ.get('REQUEST_METHOD', 'GET').upper() + + if self.method == 'GET': + self._parse_query_string() + elif self.method == 'POST': + self._parse_post_data() + else: + # For other methods, try to parse query string + self._parse_query_string() + + def _parse_query_string(self): + """Parse query string from GET requests or other methods.""" + query_string = self.environ.get('QUERY_STRING', '') + if query_string: + parsed = parse_qs(query_string, + keep_blank_values=self.keep_blank_values, + strict_parsing=self.strict_parsing, + encoding=self.encoding, + errors=self.errors) + self._data.update(parsed) + + def _parse_post_data(self): + """Parse POST data.""" + content_type = self.environ.get('CONTENT_TYPE', '') + + if content_type.startswith('application/x-www-form-urlencoded'): + self._parse_urlencoded_post() + elif content_type.startswith('multipart/form-data'): + self._parse_multipart_post() + else: + # Fallback to query string parsing + self._parse_query_string() + + def _parse_urlencoded_post(self): + """Parse application/x-www-form-urlencoded POST data.""" + content_length = int(self.environ.get('CONTENT_LENGTH', 0)) + if content_length > 0: + post_data = sys.stdin.buffer.read(content_length) + try: + decoded = post_data.decode(self.encoding, self.errors) + parsed = parse_qs(decoded, + keep_blank_values=self.keep_blank_values, + strict_parsing=self.strict_parsing, + encoding=self.encoding, + errors=self.errors) + self._data.update(parsed) + except (UnicodeDecodeError, ValueError): + # If decoding fails, try with different encoding + try: + decoded = post_data.decode('latin-1') + parsed = parse_qs(decoded, + keep_blank_values=self.keep_blank_values, + strict_parsing=self.strict_parsing, + encoding=self.encoding, + errors=self.errors) + self._data.update(parsed) + except (UnicodeDecodeError, ValueError): + pass + + def _parse_multipart_post(self): + """Parse multipart/form-data POST data.""" + content_length = int(self.environ.get('CONTENT_LENGTH', 0)) + if content_length > 0: + post_data = sys.stdin.buffer.read(content_length) + + # Parse the multipart message + parser = BytesParser(policy=HTTP) + msg = parser.parsebytes(post_data) + + for part in msg.walk(): + if part.get_content_maintype() == 'multipart': + continue + + # Get the field name from Content-Disposition + content_disp = part.get('Content-Disposition', '') + if not content_disp: + continue + + # Parse Content-Disposition header + disp_parts = content_disp.split(';') + field_name = None + filename = None + + for part_item in disp_parts: + part_item = part_item.strip() + if part_item.startswith('name='): + field_name = part_item[5:].strip('"') + elif part_item.startswith('filename='): + filename = part_item[9:].strip('"') + + if not field_name: + continue + + # Get the field value + field_value = part.get_payload(decode=True) + if field_value is None: + field_value = b'' + + if filename: + # This is a file upload + self._files[field_name] = { + 'filename': filename, + 'data': field_value, + 'content_type': part.get_content_type() + } + else: + # This is a regular field + try: + decoded_value = field_value.decode(self.encoding, self.errors) + except UnicodeDecodeError: + decoded_value = field_value.decode('latin-1') + + if field_name in self._data: + if isinstance(self._data[field_name], list): + self._data[field_name].append(decoded_value) + else: + self._data[field_name] = [self._data[field_name], decoded_value] + else: + self._data[field_name] = [decoded_value] + + def getfirst(self, key, default=None): + """Get the first value for the given key.""" + if key in self._data: + values = self._data[key] + if isinstance(values, list): + return values[0] if values else default + else: + return values + return default + + def getvalue(self, key, default=None): + """Get the value for the given key.""" + if key in self._data: + values = self._data[key] + if isinstance(values, list): + return values[0] if values else default + else: + return values + return default + + def getlist(self, key): + """Get all values for the given key as a list.""" + if key in self._data: + values = self._data[key] + if isinstance(values, list): + return values + else: + return [values] + return [] + + def keys(self): + """Get all field names.""" + return list(self._data.keys()) + + def has_key(self, key): + """Check if the key exists.""" + return key in self._data + + def __contains__(self, key): + """Check if the key exists.""" + return key in self._data + + def __getitem__(self, key): + """Get the value for the given key.""" + return self.getvalue(key) + + def __iter__(self): + """Iterate over field names.""" + return iter(self._data.keys()) + + def file(self, key): + """Get file data for the given key.""" + if key in self._files: + file_info = self._files[key] + # Create a file-like object + temp_file = tempfile.NamedTemporaryFile(delete=False) + temp_file.write(file_info['data']) + temp_file.flush() + return temp_file + return None + + def filename(self, key): + """Get the filename for the given key.""" + if key in self._files: + return self._files[key]['filename'] + return None + from Mailman import mm_cfg from Mailman import Errors from Mailman import Site from Mailman.SafeDict import SafeDict -from Mailman.Logging.Syslog import mailman_log +from Mailman.Logging.Syslog import syslog try: import hashlib @@ -91,6 +308,7 @@ dre = re.compile(r'(\${2})|\$([_a-z]\w*)|\${([_a-z]\w*)}', re.IGNORECASE) + def list_exists(listname): """Return true iff list `listname' exists.""" # The existance of any of the following file proves the list exists @@ -101,12 +319,12 @@ def list_exists(listname): # # But first ensure the list name doesn't contain a path traversal # attack. - if len(re.sub(mm_cfg.ACCEPTABLE_LISTNAME_CHARACTERS, '', listname, flags=re.IGNORECASE)) > 0: + if len(re.sub(mm_cfg.ACCEPTABLE_LISTNAME_CHARACTERS, '', listname)) > 0: remote = os.environ.get('HTTP_FORWARDED_FOR', os.environ.get('HTTP_X_FORWARDED_FOR', os.environ.get('REMOTE_ADDR', 'unidentified origin'))) - mailman_log('mischief', + syslog('mischief', 'Hostile listname: listname=%s: remote=%s', listname, remote) return False basepath = Site.get_listpath(listname) @@ -120,20 +338,17 @@ def list_exists(listname): def list_names(): """Return the names of all lists in default list directory.""" # We don't currently support separate listings of virtual domains - # Ensure LIST_DATA_DIR is a string - list_dir = mm_cfg.LIST_DATA_DIR - if isinstance(list_dir, bytes): - list_dir = list_dir.decode('utf-8', 'replace') - names = [] - for name in os.listdir(list_dir): - if list_exists(name): - # Ensure we return strings, not bytes - if isinstance(name, bytes): - name = name.decode('utf-8', 'replace') - names.append(name) - return names + return Site.get_listnames() + +def needs_unicode_escape_decode(s): + # Check for Unicode escape patterns (\uXXXX or \UXXXXXXXX) + unicode_escape_pattern = re.compile(r'\\u[0-9a-fA-F]{4}|\\U[0-9a-fA-F]{8}') + return bool(unicode_escape_pattern.search(s)) + + +# a much more naive implementation than say, Emacs's fill-paragraph! def wrap(text, column=70, honor_leading_ws=True): """Wrap and fill the text to the specified column. @@ -147,7 +362,7 @@ def wrap(text, column=70, honor_leading_ws=True): """ wrapped = '' # first split the text into paragraphs, defined as a blank line - paras = re.split(r'\n\n', text) + paras = re.split('\n\n', text) for para in paras: # fill lines = [] @@ -208,6 +423,7 @@ def wrap(text, column=70, honor_leading_ws=True): return wrapped[:-2] + def QuotePeriods(text): JOINER = '\n .\n' SEP = '\n.\n' @@ -229,13 +445,11 @@ def ParseEmail(email): def LCDomain(addr): - """Convert an email address to lowercase, preserving the domain part.""" - if isinstance(addr, str): - at = addr.find('@') - if at == -1: - return addr.lower() - return addr[:at].lower() + addr[at:] - return addr + "returns the address with the domain part lowercased" + atind = addr.find('@') + if atind == -1: # no domain part + return addr + return addr[:atind] + '@' + addr[atind+1:].lower() # TBD: what other characters should be disallowed? @@ -246,34 +460,34 @@ def LCDomain(addr): _valid_domain = re.compile('[-a-z0-9]', re.IGNORECASE) def ValidateEmail(s): - """Validate an email address. - - This is used to validate email addresses entered by users. It is more - strict than RFC 822, but less strict than RFC 2822. In particular, it - does not allow local, unqualified addresses, and requires at least one - domain part. It also disallows various characters that are known to - cause problems in various contexts. - - Returns None if the address is valid, raises an exception otherwise. - """ - if not s: - raise Exception(Errors.MMBadEmailError, s) + """Verify that an email address isn't grossly evil.""" + # If a user submits a form or URL with post data or query fragments + # with multiple occurrences of the same variable, we can get a list + # here. Be as careful as possible. + if isinstance(s, list) or isinstance(s, tuple): + if len(s) == 0: + s = '' + else: + s = s[-1] + # Pretty minimal, cheesy check. We could do better... + if not s or s.count(' ') > 0: + raise Errors.MMBadEmailError if _badchars.search(s): - raise Exception(Errors.MMHostileAddress, s) + raise Errors.MMHostileAddress(s) user, domain_parts = ParseEmail(s) # This means local, unqualified addresses, are not allowed if not domain_parts: - raise Exception(Errors.MMBadEmailError, s) - # Allow single-part domains for internal use - if len(domain_parts) < 1: - raise Exception(Errors.MMBadEmailError, s) + raise Errors.MMBadEmailError(s) + if len(domain_parts) < 2: + raise Errors.MMBadEmailError(s) # domain parts may only contain ascii letters, digits and hyphen # and must not begin with hyphen. for p in domain_parts: if len(p) == 0 or p[0] == '-' or len(_valid_domain.sub('', p)) > 0: - raise Exception(Errors.MMHostileAddress, s) + raise Errors.MMHostileAddress(s) + # Patterns which may be used to form malicious path to inject a new # line in the mailman error log. (TK: advisory by Moritz Naumann) CRNLpat = re.compile(r'[^\x21-\x7e]') @@ -287,14 +501,12 @@ def GetPathPieces(envar='PATH_INFO'): 'unidentified origin'))) if CRNLpat.search(path): path = CRNLpat.split(path)[0] - mailman_log('error', + syslog('error', 'Warning: Possible malformed path attack domain=%s remote=%s', get_domain(), remote) # Check for listname injections that won't be websafed. pieces = [p for p in path.split('/') if p] - # Ensure all pieces are Python 3 strings - pieces = [str(p) if not isinstance(p, str) else p for p in pieces] # Get the longest listname or 20 if none or use MAX_LISTNAME_LENGTH if # provided > 0. if mm_cfg.MAX_LISTNAME_LENGTH > 0: @@ -306,17 +518,19 @@ def GetPathPieces(envar='PATH_INFO'): else: longest = 20 if pieces and len(pieces[0]) > longest: - mailman_log('mischief', + syslog('mischief', 'Hostile listname: listname=%s: remote=%s', pieces[0], remote) pieces[0] = pieces[0][:longest] + '...' return pieces return None + def GetRequestMethod(): return os.environ.get('REQUEST_METHOD') + def ScriptURL(target, web_page_url=None, absolute=False): """target - scriptname only, nothing extra web_page_url - the list's configvar of the same name @@ -345,6 +559,7 @@ def ScriptURL(target, web_page_url=None, absolute=False): return path + mm_cfg.CGIEXT + def GetPossibleMatchingAddrs(name): """returns a sorted list of addresses that could possibly match a given name. @@ -364,6 +579,7 @@ def GetPossibleMatchingAddrs(name): return res + def List2Dict(L, foldcase=False): """Return a dict keyed by the entries in the list passed to it.""" d = {} @@ -376,6 +592,7 @@ def List2Dict(L, foldcase=False): return d + _vowels = ('a', 'e', 'i', 'o', 'u') _consonants = ('b', 'c', 'd', 'f', 'g', 'h', 'k', 'm', 'n', 'p', 'r', 's', 't', 'v', 'w', 'x', 'z') @@ -413,7 +630,7 @@ def Secure_MakeRandomPassword(length): # We have no available source of cryptographically # secure random characters. Log an error and fallback # to the user friendly passwords. - mailman_log('error', + syslog('error', 'urandom not available, passwords not secure') return UserFriendly_MakeRandomPassword(length) newbytes = os.read(fd, length - bytesread) @@ -446,6 +663,7 @@ def mkletter(c): return "%c%c" % tuple(map(mkletter, (chr1, chr2))) + def set_global_password(pw, siteadmin=True): if siteadmin: filename = mm_cfg.SITE_PW_FILE @@ -454,14 +672,12 @@ def set_global_password(pw, siteadmin=True): # rw-r----- omask = os.umask(0o026) try: - # Use atomic write to prevent race conditions - temp_filename = filename + '.tmp' - with open(temp_filename, 'w') as fp: + fp = open(filename, 'w') + if isinstance(pw, bytes): fp.write(sha_new(pw).hexdigest() + '\n') - os.rename(temp_filename, filename) - except (IOError, OSError) as e: - mailman_log('error', 'Failed to write password file %s: %s', filename, str(e)) - raise + else: + fp.write(sha_new(pw.encode()).hexdigest() + '\n') + fp.close() finally: os.umask(omask) @@ -472,86 +688,52 @@ def get_global_password(siteadmin=True): else: filename = mm_cfg.LISTCREATOR_PW_FILE try: - with open(filename) as fp: - challenge = fp.read()[:-1] # strip off trailing nl - if not challenge: - mailman_log('error', 'Empty password file: %s', filename) - return None - return challenge + fp = open(filename) + challenge = fp.read()[:-1] # strip off trailing nl + fp.close() except IOError as e: - if e.errno != errno.ENOENT: - mailman_log('error', 'Error reading password file %s: %s', filename, str(e)) + if e.errno != errno.ENOENT: raise + # It's okay not to have a site admin password, just return false return None + return challenge def check_global_password(response, siteadmin=True): challenge = get_global_password(siteadmin) if challenge is None: return None - # Log the hashes for debugging - computed_hash = sha_new(response).hexdigest() - mailman_log('debug', 'Password check - stored hash: %s, computed hash: %s', - challenge, computed_hash) - return challenge == computed_hash + if isinstance(response, bytes): + return challenge == sha_new(response).hexdigest() + else: + return challenge == sha_new(response.encode()).hexdigest() + _ampre = re.compile('&((?:#[0-9]+|[a-z]+);)', re.IGNORECASE) def websafe(s, doubleescape=False): - """Convert a string to be safe for HTML output. - - This function handles: - - Lists/tuples (takes last element) - - Browser workarounds - - Double escaping - - Bytes decoding (including Python 2 style bytes) - - HTML escaping - """ - if isinstance(s, (list, tuple)): - s = s[-1] if s else '' - - if mm_cfg.BROKEN_BROWSER_WORKAROUND and isinstance(s, str): - for k in mm_cfg.BROKEN_BROWSER_REPLACEMENTS: - s = s.replace(k, mm_cfg.BROKEN_BROWSER_REPLACEMENTS[k]) - - if isinstance(s, bytes): - # First try to detect if this is a Python 2 style bytes file - # by checking for common Python 2 encodings - try: - # Try ASCII first as it's the most common Python 2 default - s = s.decode('ascii', errors='strict') - except UnicodeDecodeError: - try: - # Try UTF-8 next as it's common in Python 2 files - s = s.decode('utf-8', errors='strict') - except UnicodeDecodeError: - try: - # Try ISO-8859-1 (latin1) which was common in Python 2 - s = s.decode('iso-8859-1', errors='strict') - except UnicodeDecodeError: - # As a last resort, try to detect the encoding - try: - import chardet - result = chardet.detect(s) - if result['confidence'] > 0.8: - s = s.decode(result['encoding'], errors='strict') - else: - # If we can't detect with confidence, fall back to latin1 - s = s.decode('latin1', errors='replace') - except (ImportError, UnicodeDecodeError): - # If all else fails, use replace to avoid errors - s = s.decode('latin1', errors='replace') - - # First escape & to & to prevent double escaping issues - s = s.replace('&', '&') - - # Then use html.escape for the rest - s = html.escape(s, quote=True) - - # If double escaping is requested, escape again + # If a user submits a form or URL with post data or query fragments + # with multiple occurrences of the same variable, we can get a list + # here. Be as careful as possible. + if isinstance(s, list) or isinstance(s, tuple): + if len(s) == 0: + s = '' + else: + s = s[-1] + if mm_cfg.BROKEN_BROWSER_WORKAROUND: + # Archiver can pass unicode here. Just skip them as the + # archiver escapes non-ascii anyway. + if isinstance(s, str): + for k in mm_cfg.BROKEN_BROWSER_REPLACEMENTS: + s = s.replace(k, mm_cfg.BROKEN_BROWSER_REPLACEMENTS[k]) if doubleescape: - s = html.escape(s, quote=True) - - return s + return html.escape(s, quote=True) + else: + if type(s) is bytes: + s = s.decode(errors='ignore') + re.sub('&', '&', s) + # Don't double escape html entities + #return _ampre.sub(r'&\1', html.escape(s, quote=True)) + return html.escape(s, quote=True) def nntpsplit(s): @@ -565,6 +747,7 @@ def nntpsplit(s): return s, 119 + # Just changing these two functions should be enough to control the way # that email address obscuring is handled. def ObscureEmail(addr, for_text=False): @@ -584,125 +767,149 @@ def UnobscureEmail(addr): return addr.replace('--at--', '@') + class OuterExit(Exception): pass -def findtext(templatefile, dict=None, raw=0, lang=None, mlist=None): - """Find the template file and return its contents and path. +def findtext(templatefile, dict=None, raw=False, lang=None, mlist=None): + # Make some text from a template file. The order of searches depends on + # whether mlist and lang are provided. Once the templatefile is found, + # string substitution is performed by interpolation in `dict'. If `raw' + # is false, the resulting text is wrapped/filled by calling wrap(). + # + # When looking for a template in a specific language, there are 4 places + # that are searched, in this order: + # + # 1. the list-specific language directory + # lists// + # + # 2. the domain-specific language directory + # templates// + # + # 3. the site-wide language directory + # templates/site/ + # + # 4. the global default language directory + # templates/ + # + # The first match found stops the search. In this way, you can specialize + # templates at the desired level, or, if you use only the default + # templates, you don't need to change anything. You should never modify + # files in the templates/ subdirectory, since Mailman will + # overwrite these when you upgrade. That's what the templates/site + # language directories are for. + # + # A further complication is that the language to search for is determined + # by both the `lang' and `mlist' arguments. The search order there is + # that if lang is given, then the 4 locations above are searched, + # substituting lang for . If no match is found, and mlist is + # given, then the 4 locations are searched using the list's preferred + # language. After that, the server default language is used for + # . If that still doesn't yield a template, then the standard + # distribution's English language template is used as an ultimate + # fallback. If that's missing you've got big problems. ;) + # + # A word on backwards compatibility: Mailman versions prior to 2.1 stored + # templates in templates/*.{html,txt} and lists//*.{html,txt}. + # Those directories are no longer searched so if you've got customizations + # in those files, you should move them to the appropriate directory based + # on the above description. Mailman's upgrade script cannot do this for + # you. + # + # The function has been revised and renamed as it now returns both the + # template text and the path from which it retrieved the template. The + # original function is now a wrapper which just returns the template text + # as before, by calling this renamed function and discarding the second + # item returned. + # + # Calculate the languages to scan + languages = [] + if lang is not None: + languages.append(lang) + if mlist is not None: + languages.append(mlist.preferred_language) + languages.append(mm_cfg.DEFAULT_SERVER_LANGUAGE) + # Calculate the locations to scan + searchdirs = [] + if mlist is not None: + searchdirs.append(mlist.fullpath()) + searchdirs.append(os.path.join(mm_cfg.TEMPLATE_DIR, mlist.host_name)) + searchdirs.append(os.path.join(mm_cfg.TEMPLATE_DIR, 'site')) + searchdirs.append(mm_cfg.TEMPLATE_DIR) + # Start scanning + fp = None + try: + for lang in languages: + for dir in searchdirs: + filename = os.path.join(dir, lang, templatefile) + try: + fp = open(filename) + raise OuterExit + except IOError as e: + if e.errno != errno.ENOENT: raise + # Okay, it doesn't exist, keep looping + fp = None + except OuterExit: + pass + if fp is None: + # Try one last time with the distro English template, which, unless + # you've got a really broken installation, must be there. + try: + filename = os.path.join(mm_cfg.TEMPLATE_DIR, 'en', templatefile) + fp = open(filename) + except IOError as e: + if e.errno != errno.ENOENT: raise + # We never found the template. BAD! + raise IOError(errno.ENOENT, 'No template file found', templatefile) + try: + template = fp.read() + except UnicodeDecodeError as e: + # failed to read the template as utf-8, so lets determine the current encoding + # then save the file back to disk as utf-8. + filename = fp.name + fp.close() + + current_encoding = get_current_encoding(filename) - The template file is searched for in the following order: - 1. In the list's language-specific template directory - 2. In the site's language-specific template directory - 3. In the list's default template directory - 4. In the site's default template directory + with open(filename, 'rb') as f: + raw = f.read() - If the template is found, returns a 2-tuple of (text, path) where text is - the contents of the file and path is the absolute path to the file. - Otherwise returns (None, None). - """ - if dict is None: - dict = {} - # If lang is None, use the default language from mm_cfg - if lang is None: - lang = mm_cfg.DEFAULT_SERVER_LANGUAGE + decoded_template = raw.decode(current_encoding) + + with open(filename, 'w', encoding='utf-8') as f: + f.write(decoded_template) - def read_template_file(path): + template = decoded_template + except Exception as e: + # catch any other non-unicode exceptions... + syslog('error', 'Failed to read template %s: %s', fp.name, e) + finally: + fp.close() + + text = template + if dict is not None: try: - with open(path, 'rb') as fp: - raw_bytes = fp.read() - # First try UTF-8 since that's the most common encoding - try: - text = raw_bytes.decode('utf-8') - return text, path - except UnicodeDecodeError: - # If UTF-8 fails, try other encodings - for encoding in ['euc-jp', 'iso-8859-1', 'latin1']: - try: - text = raw_bytes.decode(encoding) - return text, path - except UnicodeDecodeError: - continue - # If all encodings fail, use UTF-8 with replacement - return raw_bytes.decode('utf-8', 'replace'), path - except IOError: - return None, None - - # First try the list's language-specific template directory - if lang and mlist: - path = os.path.join(mlist.fullpath(), 'templates', lang, templatefile) - if os.path.exists(path): - result = read_template_file(path) - if result[0] is not None: - return result - - # Then try the site's language-specific template directory - if lang: - path = os.path.join(mm_cfg.TEMPLATE_DIR, lang, templatefile) - if os.path.exists(path): - result = read_template_file(path) - if result[0] is not None: - return result - - # Then try the list's default template directory - if mlist: - path = os.path.join(mlist.fullpath(), 'templates', templatefile) - if os.path.exists(path): - result = read_template_file(path) - if result[0] is not None: - return result - - # Finally try the site's default template directory - path = os.path.join(mm_cfg.TEMPLATE_DIR, templatefile) - if os.path.exists(path): - result = read_template_file(path) - if result[0] is not None: - return result - - return None, None - - -def maketext(templatefile, dict=None, raw=0, lang=None, mlist=None): - """Make text from a template file. - - Use this function to create text from the template file. If dict is - provided, use it as the substitution mapping. If mlist is provided use it - as the source for the substitution. If both dict and mlist are provided, - dict values take precedence. lang is the language code to find the - template in. If raw is true, no substitution will be done on the text. - """ - template, path = findtext(templatefile, dict, raw, lang, mlist) - if template is None: - # Log all paths that were searched - paths = [] - if lang and mlist: - paths.append(os.path.join(mlist.fullpath(), 'templates', lang, templatefile)) - if lang: - paths.append(os.path.join(mm_cfg.TEMPLATE_DIR, lang, templatefile)) - if mlist: - paths.append(os.path.join(mlist.fullpath(), 'templates', templatefile)) - paths.append(os.path.join(mm_cfg.TEMPLATE_DIR, templatefile)) - mailman_log('error', 'Template file not found: %s (language: %s). Searched paths: %s', - templatefile, lang or 'default', ', '.join(paths)) - return '' # Return empty string instead of None + sdict = SafeDict(dict) + try: + text = sdict.interpolate(template) + except UnicodeError: + # Try again after coercing the template to unicode + utemplate = str(template, GetCharSet(lang), 'replace') + text = sdict.interpolate(utemplate) + except (TypeError, ValueError) as e: + # The template is really screwed up + syslog('error', 'broken template: %s\n%s', filename, e) + pass if raw: - return template - # Make the text from the template - if dict is None: - dict = SafeDict() - if mlist: - dict.update(mlist.__dict__) - # Remove leading whitespace - if isinstance(template, str): - template = '\n'.join([line.lstrip() for line in template.splitlines()]) - try: - text = template % dict - except (ValueError, TypeError) as e: - mailman_log('error', 'Template interpolation error for %s: %s', - templatefile, str(e)) - return '' # Return empty string instead of None - return text + return text, filename + return wrap(text), filename + +def maketext(templatefile, dict=None, raw=False, lang=None, mlist=None): + return findtext(templatefile, dict, raw, lang, mlist)[0] + + ADMINDATA = { # admin keyword: (minimum #args, maximum #args) 'confirm': (1, 1), @@ -718,15 +925,10 @@ def maketext(templatefile, dict=None, raw=0, lang=None, mlist=None): 'who': (0, 1), } -# Given a Message object, test for administrivia (eg subscribe, +# Given a Message.Message object, test for administrivia (eg subscribe, # unsubscribe, etc). The test must be a good guess -- messages that return # true get sent to the list admin instead of the entire list. def is_administrivia(msg): - """Return true if the message is administrative in nature.""" - # Not imported at module scope to avoid import loop - from Mailman.Message import Message - if not isinstance(msg, Message): - return False linecnt = 0 lines = [] for line in email.iterators.body_line_iterator(msg): @@ -764,6 +966,7 @@ def is_administrivia(msg): return False + def GetRequestURI(fallback=None, escape=True): """Return the full virtual path this CGI script was invoked with. @@ -788,6 +991,7 @@ def GetRequestURI(fallback=None, escape=True): return url + # Wait on a dictionary of child pids def reap(kids, func=None, once=False): while kids: @@ -810,7 +1014,7 @@ def reap(kids, func=None, once=False): if once: break - + def GetLanguageDescr(lang): return mm_cfg.LC_DESCRIPTIONS[lang][0] @@ -825,6 +1029,7 @@ def IsLanguage(lang): return lang in mm_cfg.LC_DESCRIPTIONS + def get_domain(): host = os.environ.get('HTTP_HOST', os.environ.get('SERVER_NAME')) port = os.environ.get('SERVER_PORT') @@ -850,6 +1055,7 @@ def get_site_email(hostname=None, extra=None): return '%s-%s@%s' % (mm_cfg.MAILMAN_SITE_LIST, extra, hostname) + # This algorithm crafts a guaranteed unique message-id. The theory here is # that pid+listname+host will distinguish the message-id for every process on # the system, except when process ids wrap around. To further distinguish @@ -876,6 +1082,7 @@ def midnight(date=None): return time.mktime(date + (0,)*5 + (-1,)) + # Utilities to convert from simplified $identifier substitutions to/from # standard Python $(identifier)s substititions. The "Guido rules" for the # former are: @@ -885,6 +1092,8 @@ def midnight(date=None): def to_dollar(s): """Convert from %-strings to $-strings.""" + if isinstance(s, bytes): + s = s.decode() s = s.replace('$', '$$').replace('%%', '%') parts = cre.split(s) for i in range(1, len(parts), 2): @@ -920,11 +1129,14 @@ def dollar_identifiers(s): def percent_identifiers(s): """Return the set (dictionary) of identifiers found in a %-string.""" d = {} + if isinstance(s, bytes): + s = s.decode() for name in cre.findall(s): d[name] = True return d + # Utilities to canonicalize a string, which means un-HTML-ifying the string to # produce a Unicode string or an 8-bit string if all the characters are ASCII. def canonstr(s, lang=None): @@ -1011,9 +1223,8 @@ def oneline(s, cset): # Decode header string in one line and convert into specified charset try: h = email.header.make_header(email.header.decode_header(s)) - ustr = str(h) - line = UEMPTYSTRING.join(ustr.splitlines()) - return line.encode(cset, 'replace') + ustr = h.__str__() + return UEMPTYSTRING.join(ustr.splitlines()) except (LookupError, UnicodeError, ValueError, HeaderParseError): # possibly charset problem. return with undecoded string in one line. return EMPTYSTRING.join(s.splitlines()) @@ -1052,7 +1263,7 @@ def strip_verbose_pattern(pattern): elif c == ']' and inclass: inclass = False newpattern += c - elif re.search(r'\s', c, re.IGNORECASE): + elif re.search(r'\s', c): if inclass: if c == NL: newpattern += '\\n' @@ -1259,17 +1470,25 @@ def suspiciousHTML(html): s_dict = {} def get_suffixes(url): - """Get the list of public suffixes from the given URL.""" + """This loads and parses the data from the url argument into s_dict for + use by get_org_dom.""" + global s_dict + if s_dict: + return + if not url: + return try: d = urllib.request.urlopen(url) - except (urllib.error.URLError, urllib.error.HTTPError) as e: - mailman_log('error', 'Failed to fetch DMARC organizational domain data from %s: %s', + except urllib.error.URLError as e: + syslog('error', + 'Unable to retrieve data from %s: %s', url, e) return for line in d.readlines(): - # Convert bytes to string if necessary + if not line: + continue if isinstance(line, bytes): - line = line.decode('utf-8') + line = line.decode() if not line.strip() or line.startswith(' ') or line.startswith('//'): continue line = re.sub(' .*', '', line.strip()) @@ -1327,7 +1546,7 @@ def get_org_dom(domain): def IsDMARCProhibited(mlist, email): if not dns_resolver: # This is a problem; log it. - mailman_log('error', + syslog('error', 'DNS lookup for dmarc_moderation_action for list %s not available', mlist.real_name) return False @@ -1348,30 +1567,112 @@ def IsDMARCProhibited(mlist, email): return x return False -def _DMARCProhibited(mlist, email, domain): - """Check if the domain has a DMARC policy that prohibits sending. - """ - try: - import dns.resolver - import dns.exception - except ImportError: - return False +def _DMARCProhibited(mlist, email, dmarc_domain, org=False): + try: - txt_rec = dns.resolver.resolve(domain, 'TXT') - # Newer versions of dnspython use strings property instead of strings attribute - txt_strings = txt_rec.strings if hasattr(txt_rec, 'strings') else [str(r) for r in txt_rec] - for txt in txt_strings: - if txt.startswith('v=DMARC1'): - # Parse the DMARC record - parts = txt.split(';') - for part in parts: - part = part.strip() - if part.startswith('p='): - policy = part[2:].lower() - if policy in ('reject', 'quarantine'): - return True - except (dns.exception.DNSException, AttributeError): - pass + resolver = dns.resolver.Resolver() + resolver.timeout = float(mm_cfg.DMARC_RESOLVER_TIMEOUT) + resolver.lifetime = float(mm_cfg.DMARC_RESOLVER_LIFETIME) + txt_recs = resolver.query(dmarc_domain, dns.rdatatype.TXT) + except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): + return 'continue' + except (dns.resolver.NoNameservers): + syslog('error', + 'DNSException: No Nameservers available for %s (%s)', + email, dmarc_domain) + # Typically this means a dnssec validation error. Clients that don't + # perform validation *may* successfully see a _dmarc RR whereas a + # validating mailman server won't see the _dmarc RR. We should + # mitigate this email to be safe. + return True + except DNSException as e: + syslog('error', + 'DNSException: Unable to query DMARC policy for %s (%s). %s', + email, dmarc_domain, e.__doc__) + # While we can't be sure what caused the error, there is potentially + # a DMARC policy record that we missed and that a receiver of the mail + # might see. Thus, we should err on the side of caution and mitigate. + return True + else: + # Be as robust as possible in parsing the result. + results_by_name = {} + cnames = {} + want_names = set([dmarc_domain + '.']) + for txt_rec in txt_recs.response.answer: + if not isinstance(txt_rec.items, list): + continue + if not txt_rec.items[0]: + continue + # Don't be fooled by an answer with uppercase in the name. + name = txt_rec.name.to_text().lower() + if txt_rec.rdtype == dns.rdatatype.CNAME: + cnames[name] = ( + txt_rec.items[0].target.to_text()) + if txt_rec.rdtype != dns.rdatatype.TXT: + continue + results_by_name.setdefault(name, []).append( + "".join( [ record.decode() if isinstance(record, bytes) else record for record in txt_rec.items[0].strings ] )) + expands = list(want_names) + seen = set(expands) + while expands: + item = expands.pop(0) + if item in cnames: + if cnames[item] in seen: + continue # cname loop + expands.append(cnames[item]) + seen.add(cnames[item]) + want_names.add(cnames[item]) + want_names.discard(item) + + if len(want_names) != 1: + syslog('error', + """multiple DMARC entries in results for %s, + processing each to be strict""", + dmarc_domain) + for name in want_names: + if name not in results_by_name: + continue + dmarcs = [n for n in results_by_name[name] if n.startswith('v=DMARC1;')] + if len(dmarcs) == 0: + return 'continue' + if len(dmarcs) > 1: + syslog('error', + """RRset of TXT records for %s has %d v=DMARC1 entries; + ignoring them per RFC 7849""", + dmarc_domain, len(dmarcs)) + return False + for entry in dmarcs: + mo = re.search(r'\bsp=(\w*)\b', entry, re.IGNORECASE) + if org and mo: + policy = mo.group(1).lower() + else: + mo = re.search(r'\bp=(\w*)\b', entry, re.IGNORECASE) + if mo: + policy = mo.group(1).lower() + else: + continue + if policy == 'reject': + syslog('vette', + '%s: DMARC lookup for %s (%s) found p=reject in %s = %s', + mlist.real_name, email, dmarc_domain, name, entry) + return True + + if (mlist.dmarc_quarantine_moderation_action and + policy == 'quarantine'): + syslog('vette', + '%s: DMARC lookup for %s (%s) found p=quarantine in %s = %s', + mlist.real_name, email, dmarc_domain, name, entry) + return True + + if (mlist.dmarc_none_moderation_action and + mlist.dmarc_quarantine_moderation_action and + mlist.dmarc_moderation_action in (1, 2) and + policy == 'none'): + syslog('vette', + '%s: DMARC lookup for %s (%s) found p=none in %s = %s', + mlist.real_name, email, dmarc_domain, name, entry) + return True + return False @@ -1382,41 +1683,44 @@ def _DMARCProhibited(mlist, email, domain): clean_count = 0 def IsVerboseMember(mlist, email): """For lists that request it, we keep track of recent posts by address. - A message from an address to a list, if the list requests it, is remembered - for a specified time whether or not the address is a list member, and if the - address is a member and the member is over the threshold for the list, that - fact is returned.""" - global clean_count, recentMemberPostings +A message from an address to a list, if the list requests it, is remembered +for a specified time whether or not the address is a list member, and if the +address is a member and the member is over the threshold for the list, that +fact is returned.""" + + global clean_count if mlist.member_verbosity_threshold == 0: return False email = email.lower() + now = time.time() + recentMemberPostings.setdefault(email,[]).append(now + + float(mlist.member_verbosity_interval) + ) + x = list(range(len(recentMemberPostings[email]))) + x.reverse() + for i in x: + if recentMemberPostings[email][i] < now: + del recentMemberPostings[email][i] - # Clean up old entries periodically clean_count += 1 if clean_count >= mm_cfg.VERBOSE_CLEAN_LIMIT: clean_count = 0 - # Remove entries older than the maximum verbosity interval - max_age = max(mlist.member_verbosity_interval for mlist in mm_cfg.LISTS.values()) - cutoff = now - max_age - recentMemberPostings = { - addr: [t for t in times if t > cutoff] - for addr, times in recentMemberPostings.items() - if any(t > cutoff for t in times) - } - - # Add new posting time - recentMemberPostings.setdefault(email, []).append(now + float(mlist.member_verbosity_interval)) - - # Remove old times for this email - recentMemberPostings[email] = [t for t in recentMemberPostings[email] if t > now] - + for addr in list(recentMemberPostings.keys()): + x = list(range(len(recentMemberPostings[addr]))) + x.reverse() + for i in x: + if recentMemberPostings[addr][i] < now: + del recentMemberPostings[addr][i] + if not recentMemberPostings[addr]: + del recentMemberPostings[addr] if not mlist.isMember(email): return False - - return len(recentMemberPostings.get(email, [])) > mlist.member_verbosity_threshold + return (len(recentMemberPostings.get(email, [])) > + mlist.member_verbosity_threshold + ) def check_eq_domains(email, domains_list): @@ -1442,7 +1746,7 @@ def check_eq_domains(email, domains_list): except ValueError: return [] domain = domain.lower() - domains_list = re.sub(r'\s', '', domains_list, flags=re.IGNORECASE).lower() + domains_list = re.sub(r'\s', '', domains_list).lower() domains = domains_list.split(';') domains_list = [] for d in domains: @@ -1477,102 +1781,43 @@ def xml_to_unicode(s, cset): """ if isinstance(s, bytes): us = s.decode(cset, 'replace') - us = re.sub(r'&(#[0-9]+);', _invert_xml, us, flags=re.IGNORECASE) - us = re.sub(r'(?i)\\\\(u[a-f0-9]{4})', _invert_xml, us, flags=re.IGNORECASE) + us = re.sub(u'&(#[0-9]+);', _invert_xml, us) + us = re.sub(u'(?i)\\\\(u[a-f0-9]{4})', _invert_xml, us) return us else: return s def banned_ip(ip): - """Check if an IP address is in the Spamhaus blocklist. - - Supports both IPv4 and IPv6 addresses. - Returns True if the IP is in the blocklist, False otherwise. - """ if not dns_resolver: return False - - try: - if isinstance(ip, bytes): - ip = ip.decode('us-ascii', errors='replace') - - if have_ipaddress: - try: - ip_obj = ipaddress.ip_address(ip) - if isinstance(ip_obj, ipaddress.IPv4Address): - # IPv4 format: 1.2.3.4 -> 4.3.2.1.zen.spamhaus.org - parts = str(ip_obj).split('.') - lookup = '{0}.{1}.{2}.{3}.zen.spamhaus.org'.format( - parts[3], parts[2], parts[1], parts[0]) - else: - # IPv6 format: 2001:db8::1 -> 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.zen.spamhaus.org - # Convert to reverse nibble format - expanded = ip_obj.exploded.replace(':', '') - lookup = '.'.join(reversed(expanded)) + '.zen.spamhaus.org' - except ValueError: - return False - else: - # Fallback for systems without ipaddress module - if ':' in ip: - # IPv6 address - try: - # Basic IPv6 validation and conversion - parts = ip.split(':') - if len(parts) > 8: - return False - # Pad with zeros - expanded = ''.join(part.zfill(4) for part in parts) - lookup = '.'.join(reversed(expanded)) + '.zen.spamhaus.org' - except (ValueError, IndexError): - return False - else: - # IPv4 address - parts = ip.split('.') - if len(parts) != 4: - return False - try: - if not all(0 <= int(part) <= 255 for part in parts): - return False - lookup = '{0}.{1}.{2}.{3}.zen.spamhaus.org'.format( - parts[3], parts[2], parts[1], parts[0]) - except ValueError: - return False - - # Set DNS resolver timeouts to prevent DoS - resolver = dns.resolver.Resolver() - resolver.timeout = 2.0 # 2 second timeout - resolver.lifetime = 4.0 # 4 second total lifetime - + if have_ipaddress: try: - # Check for blocklist response - answers = resolver.resolve(lookup, 'A') - for rdata in answers: - if str(rdata).startswith('127.0.0.'): - return True - except dns.resolver.NXDOMAIN: - # IP not found in blocklist - return False - except dns.resolver.Timeout: - mailman_log('error', 'DNS timeout checking IP %s in Spamhaus', ip) - return False - except dns.resolver.NoAnswer: - mailman_log('error', 'No DNS answer for IP %s in Spamhaus', ip) + uip = str(ip, encoding='us-ascii', errors='replace') + ptr = ipaddress.ip_address(uip).reverse_pointer + except ValueError: return False - except dns.exception.DNSException as e: - mailman_log('error', 'DNS error checking IP %s in Spamhaus: %s', ip, str(e)) + lookup = '{0}.zen.spamhaus.org'.format('.'.join(ptr.split('.')[:-2])) + else: + parts = ip.split('.') + if len(parts) != 4: return False - - except Exception as e: - mailman_log('error', 'Error checking IP %s in Spamhaus: %s', ip, str(e)) + lookup = '{0}.{1}.{2}.{3}.zen.spamhaus.org'.format(parts[3], + parts[2], + parts[1], + parts[0]) + resolver = dns.resolver.Resolver() + try: + ans = resolver.query(lookup, dns.rdatatype.A) + except DNSException: return False - + if not ans: + return False + text = ans.rrset.to_text() + if re.search(r'127\.0\.0\.[2-7]$', text, re.MULTILINE): + return True return False def banned_domain(email): - """Check if a domain is in the Spamhaus Domain Block List (DBL). - - Returns True if the domain is in the blocklist, False otherwise. - """ if not dns_resolver: return False @@ -1581,37 +1826,17 @@ def banned_domain(email): lookup = '%s.dbl.spamhaus.org' % (domain) - # Set DNS resolver timeouts to prevent DoS resolver = dns.resolver.Resolver() - resolver.timeout = 2.0 # 2 second timeout - resolver.lifetime = 4.0 # 4 second total lifetime - try: - # Use resolve() instead of query() - ans = resolver.resolve(lookup, 'A') - if not ans: - return False - # Newer versions of dnspython use strings property instead of strings attribute - text = ans.rrset.to_text() if hasattr(ans, 'rrset') else str(ans) - if re.search(r'127\.0\.1\.\d{1,3}$', text, re.MULTILINE | re.IGNORECASE): - if not re.search(r'127\.0\.1\.255$', text, re.MULTILINE | re.IGNORECASE): - return True - except dns.resolver.NXDOMAIN: - # Domain not found in blocklist - return False - except dns.resolver.Timeout: - mailman_log('error', 'DNS timeout checking domain %s in Spamhaus DBL', domain) + ans = resolver.query(lookup, dns.rdatatype.A) + except DNSException: return False - except dns.resolver.NoAnswer: - mailman_log('error', 'No DNS answer for domain %s in Spamhaus DBL', domain) - return False - except dns.exception.DNSException as e: - mailman_log('error', 'DNS error checking domain %s in Spamhaus DBL: %s', domain, str(e)) - return False - except Exception as e: - mailman_log('error', 'Unexpected error checking domain %s in Spamhaus DBL: %s', domain, str(e)) + if not ans: return False - + text = ans.rrset.to_text() + if re.search(r'127\.0\.1\.\d{1,3}$', text, re.MULTILINE): + if not re.search(r'127\.0\.1\.255$', text, re.MULTILINE): + return True return False @@ -1626,7 +1851,7 @@ def captcha_display(mlist, lang, captchas): box_html = mlist.FormatBox('captcha_answer', size=30) # Remember to encode the language in the index so that we can get it out # again! - return (websafe(question), box_html, '{}-{}'.format(lang, idx)) + return (websafe(question), box_html, lang + "-" + str(idx)) def captcha_verify(idx, given_answer, captchas): try: @@ -1644,164 +1869,71 @@ def captcha_verify(idx, given_answer, captchas): correct_answer_pattern = captchas[idx][1] + "$" return re.match(correct_answer_pattern, given_answer) -def validate_ip_address(ip): - """Validate and normalize an IP address. - - Args: - ip: The IP address to validate. - - Returns: - A tuple of (is_valid, normalized_ip). If the IP is invalid, - normalized_ip will be None. - """ - if not ip: - return False, None - - try: - if have_ipaddress: - ip_obj = ipaddress.ip_address(ip) - if isinstance(ip_obj, ipaddress.IPv4Address): - # For IPv4, drop last octet - parts = str(ip_obj).split('.') - return True, '.'.join(parts[:-1]) - else: - # For IPv6, drop last 16 bits - expanded = ip_obj.exploded.replace(':', '') - return True, expanded[:-4] - else: - # Fallback for systems without ipaddress module - if ':' in ip: - # IPv6 address - parts = ip.split(':') - if len(parts) <= 8: - # Pad with zeros and drop last 16 bits - expanded = ''.join(part.zfill(4) for part in parts) - return True, expanded[:-4] - else: - # IPv4 address - parts = ip.split('.') - if len(parts) == 4: - return True, '.'.join(parts[:-1]) - except (ValueError, IndexError): - pass - - return False, None - -def ValidateListName(listname): - """Validate a list name against the acceptable character pattern. - - Args: - listname: The list name to validate - - Returns: - bool: True if the list name is valid, False otherwise - """ - if not listname: - return False - # Check if the list name contains any characters not in the acceptable pattern - return len(re.sub(mm_cfg.ACCEPTABLE_LISTNAME_CHARACTERS, '', listname, flags=re.IGNORECASE)) == 0 - -def formataddr(pair): - """The inverse of parseaddr(), this takes a 2-tuple of (name, address) - and returns the string value suitable for an RFC 2822 From, To or Cc - header. - - If the first element of pair is false, then the second element is - returned unmodified. - """ - name, address = pair - if name: - # If name is bytes, decode it to str - if isinstance(name, bytes): - name = name.decode('utf-8', 'replace') - # If name contains non-ASCII characters and is not already encoded, - # encode it - if isinstance(name, str) and any(ord(c) > 127 for c in name): - name = email.header.Header(name, 'utf-8').encode() - return '%s <%s>' % (name, address) - return address - -def save_pickle_file(filename, data, protocol=4): - """Save data to a pickle file using a consistent protocol. - - Args: - filename: Path to save the pickle file - data: Data to pickle - protocol: Pickle protocol to use (defaults to 4 for Python 2/3 compatibility) - - Raises: - IOError: If the file cannot be written - """ - try: - with open(filename, 'wb') as fp: - pickle.dump(data, fp, protocol=protocol, fix_imports=True) - except IOError as e: - raise IOError(f'Could not write {filename}: {e}') - -def load_pickle_file(filename, encoding_order=None): - """Load a pickle file with consistent protocol and encoding handling. - - Args: - filename: Path to the pickle file - encoding_order: List of encodings to try in order. Defaults to ['utf-8', 'latin1'] - - Returns: - The unpickled data - - Raises: - pickle.UnpicklingError: If the file cannot be unpickled - IOError: If the file cannot be read - """ - if encoding_order is None: - encoding_order = ['utf-8', 'latin1'] - - try: - with open(filename, 'rb') as fp: - # Read the first byte to determine protocol version - protocol = ord(fp.read(1)) - # Reset file pointer to beginning - fp.seek(0) - - # Try each encoding in order - last_error = None - for encoding in encoding_order: +def get_current_encoding(filename): + encodings = [ 'utf-8', 'iso-8859-1', 'iso-8859-2', 'iso-8859-15', 'iso-8859-7', 'iso-8859-13', 'euc-jp', 'euc-kr', 'iso-8859-9', 'us-ascii' ] + for encoding in encodings: + try: + with open(filename, 'r', encoding=encoding) as f: + f.read() + return encoding + except UnicodeDecodeError as e: + continue + # if everything fails, send utf-8 and hope for the best... + return 'utf-8' + +def set_cte_if_missing(msg): + if not hasattr(msg, 'policy'): + msg.policy = email._policybase.compat32 + if 'content-transfer-encoding' not in msg: + msg['Content-Transfer-Encoding'] = '7bit' + if msg.is_multipart(): + for part in msg.get_payload(): + if not hasattr(part, 'policy'): + part.policy = email._policybase.compat32 + set_cte_if_missing(part) + +# Attempt to load a pickle file as utf-8 first, falling back to others. If they all fail, there was probably no hope. Note that get_current_encoding above is useless in testing pickles. +def load_pickle(path): + import pickle + + encodings = [ 'utf-8', 'iso-8859-1', 'iso-8859-2', 'iso-8859-15', 'iso-8859-7', 'iso-8859-13', 'euc-jp', 'euc-kr', 'iso-8859-9', 'us-ascii', 'latin1' ] + + if isinstance(path, str): + for encoding in encodings: + try: try: - fp.seek(0) - return pickle.load(fp, fix_imports=True, encoding=encoding) - except (UnicodeDecodeError, pickle.UnpicklingError) as e: - last_error = e - continue - - # If we get here, all encodings failed - raise last_error or pickle.UnpicklingError('Failed to load pickle file') - - except IOError as e: - raise IOError(f'Could not read {filename}: {e}') - -def get_pickle_protocol(filename): - """Get the protocol version of a pickle file. - - Args: - filename: Path to the pickle file - - Returns: - The protocol version (int) or None if it cannot be determined - """ - try: - with open(filename, 'rb') as fp: - # Read the first byte to determine protocol version - first_byte = fp.read(1) - if not first_byte: + fp = open(path, 'rb') + except IOError as e: + if e.errno != errno.ENOENT: raise + + msg = pickle.load(fp, fix_imports=True, encoding=encoding) + fp.close() + return msg + except UnicodeDecodeError as e: + continue + except Exception as e: return None - # The first byte of a pickle file indicates the protocol version - # For protocol 0, it's '0', for protocol 1 it's '1', etc. - # For protocol 2 and higher, it's a binary value - if first_byte[0] == ord('0'): - return 0 - elif first_byte[0] == ord('1'): - return 1 - else: - # For protocol 2 and higher, the first byte is the protocol number - return first_byte[0] - except (IOError, IndexError): + elif isinstance(path, bytes): + for encoding in encodings: + try: + msg = pickle.loads(path, fix_imports=True, encoding=encoding) + return msg + except UnicodeDecodeError: + continue + except Exception as e: + return None + # Check if it's a file-like object, such as using BufferedReader + elif hasattr(path, 'read') and callable(getattr(path, 'read')): + for encoding in encodings: + try: + msg = pickle.load(path, fix_imports=True, encoding=encoding) + return msg + except UnicodeDecodeError: + continue + except EOFError as e: + return None + except Exception as e: + return None + + else: return None diff --git a/Mailman/Version.py b/Mailman/Version.py index d310973b..c2aaf1d4 100644 --- a/Mailman/Version.py +++ b/Mailman/Version.py @@ -16,7 +16,7 @@ # USA. # Mailman version -VERSION = '2.1.40-alpha1' +VERSION = '2.2.0' # And as a hex number in the manner of PY_VERSION_HEX ALPHA = 0xa @@ -27,8 +27,8 @@ FINAL = 0xf MAJOR_REV = 2 -MINOR_REV = 1 -MICRO_REV = 39 +MINOR_REV = 2 +MICRO_REV = 0 REL_LEVEL = FINAL # at most 15 beta releases! REL_SERIAL = 0 diff --git a/Mailman/__init__.py b/Mailman/__init__.py.in similarity index 93% rename from Mailman/__init__.py rename to Mailman/__init__.py.in index b271f895..21ebf673 100644 --- a/Mailman/__init__.py +++ b/Mailman/__init__.py.in @@ -13,3 +13,6 @@ # 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. + +import sys +sys.path.append('@VAR_PREFIX@/Mailman') diff --git a/Mailman/htmlformat.py b/Mailman/htmlformat.py index 83853b0e..ad9bb08a 100644 --- a/Mailman/htmlformat.py +++ b/Mailman/htmlformat.py @@ -48,17 +48,12 @@ # Format an arbitrary object. def HTMLFormatObject(item, indent): "Return a presentation of an object, invoking their Format method if any." - if item is None: - return '' - if isinstance(item, str): + if type(item) == type(''): return item elif not hasattr(item, "Format"): - return str(item) + return repr(item) else: - result = item.Format(indent) - if result is None: - return '' - return str(result) + return item.Format(indent) def CaseInsensitiveKeyedDict(d): result = {} @@ -78,116 +73,96 @@ def __init__(self, **table_opts): self.cell_info = {} self.row_info = {} self.opts = table_opts - self.current_row = -1 - self.current_cell = -1 def AddOptions(self, opts): - self.opts.update(opts) + DictMerge(self.opts, opts) + + # Sets all of the cells. It writes over whatever cells you had there + # previously. def SetAllCells(self, cells): self.cells = cells + # Add a new blank row at the end def NewRow(self): self.cells.append([]) - self.current_row = len(self.cells) - 1 - self.current_cell = -1 + # Add a new blank cell at the end def NewCell(self): - self.cells[self.current_row].append(None) - self.current_cell = len(self.cells[self.current_row]) - 1 + self.cells[-1].append('') def AddRow(self, row): self.cells.append(row) def AddCell(self, cell): - if self.current_row < 0: - self.NewRow() - self.cells[self.current_row].append(cell) + self.cells[-1].append(cell) def AddCellInfo(self, row, col, **kws): + kws = CaseInsensitiveKeyedDict(kws) if row not in self.cell_info: - self.cell_info[row] = {} - self.cell_info[row][col] = kws + self.cell_info[row] = { col : kws } + elif col in self.cell_info[row]: + DictMerge(self.cell_info[row], kws) + else: + self.cell_info[row][col] = kws def AddRowInfo(self, row, **kws): - self.row_info[row] = kws + kws = CaseInsensitiveKeyedDict(kws) + if row not in self.row_info: + self.row_info[row] = kws + else: + DictMerge(self.row_info[row], kws) + # What's the index for the row we just put in? def GetCurrentRowIndex(self): - return self.current_row + return len(self.cells)-1 + # What's the index for the col we just put in? def GetCurrentCellIndex(self): - return self.current_cell + return len(self.cells[-1])-1 def ExtractCellInfo(self, info): + valid_mods = ['align', 'valign', 'nowrap', 'rowspan', 'colspan', + 'bgcolor'] output = '' - # Convert deprecated attributes to modern equivalents - if 'bgcolor' in info: - info['style'] = info.get('style', '') + f'background-color: {info["bgcolor"]};' - del info['bgcolor'] - if 'align' in info: - info['style'] = info.get('style', '') + f'text-align: {info["align"]};' - del info['align'] - if 'valign' in info: - info['style'] = info.get('style', '') + f'vertical-align: {info["valign"]};' - del info['valign'] - if 'width' in info: - info['style'] = info.get('style', '') + f'width: {info["width"]};' - del info['width'] - if 'height' in info: - info['style'] = info.get('style', '') + f'height: {info["height"]};' - del info['height'] - # Add ARIA attributes for accessibility - if 'role' not in info: - info['role'] = 'cell' - for k, v in list(info.items()): - output = output + ' %s="%s"' % (k, v) + + for (key, val) in list(info.items()): + if not key in valid_mods: + continue + if key == 'nowrap': + output = output + ' NOWRAP' + continue + else: + output = output + ' %s="%s"' % (key.upper(), val) + return output def ExtractRowInfo(self, info): + valid_mods = ['align', 'valign', 'bgcolor'] output = '' - # Convert deprecated attributes to modern equivalents - if 'bgcolor' in info: - info['style'] = info.get('style', '') + f'background-color: {info["bgcolor"]};' - del info['bgcolor'] - if 'align' in info: - info['style'] = info.get('style', '') + f'text-align: {info["align"]};' - del info['align'] - if 'valign' in info: - info['style'] = info.get('style', '') + f'vertical-align: {info["valign"]};' - del info['valign'] - # Add ARIA attributes for accessibility - if 'role' not in info: - info['role'] = 'row' - for k, v in list(info.items()): - output = output + ' %s="%s"' % (k, v) + + for (key, val) in list(info.items()): + if not key in valid_mods: + continue + output = output + ' %s="%s"' % (key.upper(), val) + return output def ExtractTableInfo(self, info): + valid_mods = ['align', 'width', 'border', 'cellspacing', 'cellpadding', + 'bgcolor'] + output = '' - # Convert deprecated attributes to modern equivalents - if 'bgcolor' in info: - info['style'] = info.get('style', '') + f'background-color: {info["bgcolor"]};' - del info['bgcolor'] - if 'align' in info: - info['style'] = info.get('style', '') + f'margin-left: auto; margin-right: auto;' - del info['align'] - if 'width' in info: - info['style'] = info.get('style', '') + f'width: {info["width"]};' - del info['width'] - if 'cellpadding' in info: - info['style'] = info.get('style', '') + f'border-spacing: {info["cellpadding"]}px;' - del info['cellpadding'] - if 'cellspacing' in info: - info['style'] = info.get('style', '') + f'border-collapse: separate; border-spacing: {info["cellspacing"]}px;' - del info['cellspacing'] - if 'border' in info: - info['style'] = info.get('style', '') + f'border: {info["border"]}px solid #ccc;' - del info['border'] - # Add ARIA attributes for accessibility - if 'role' not in info: - info['role'] = 'table' - for k, v in list(info.items()): - output = output + ' %s="%s"' % (k, v) + + for (key, val) in list(info.items()): + if not key in valid_mods: + continue + if key == 'border' and val == None: + output = output + ' BORDER' + continue + else: + output = output + ' %s="%s"' % (key.upper(), val) + return output def FormatCell(self, row, col, indent): @@ -201,8 +176,6 @@ def FormatCell(self, row, col, indent): output = output + self.ExtractCellInfo(my_info) item = self.cells[row][col] item_format = HTMLFormatObject(item, indent+4) - if not isinstance(item_format, str): - item_format = str(item_format) output = '%s>%s' % (output, item_format) return output @@ -229,10 +202,6 @@ def Format(self, indent=0): output = output + self.ExtractTableInfo(self.opts) output = output + '>' - # Add caption for accessibility if not present - if 'aria-label' in self.opts: - output = output + '\n' + ' '*(indent+2) + '

    ' - for i in range(len(self.cells)): output = output + self.FormatRow(i, indent + 2) @@ -333,108 +302,41 @@ def SetTitle(self, title): self.title = title def Format(self, indent=0, **kws): - charset = 'utf-8' + charset = 'us-ascii' if self.language and Utils.IsLanguage(self.language): charset = Utils.GetCharSet(self.language) output = ['Content-Type: text/html; charset=%s\n' % charset] - output.append('') if not self.suppress_head: kws.setdefault('bgcolor', self.bgcolor) tab = ' ' * indent output.extend([tab, - '' % (self.language or 'en'), - '' + '', + '' ]) if mm_cfg.IMAGE_LOGOS: - output.append('' % + output.append('' % (mm_cfg.IMAGE_LOGOS + mm_cfg.SHORTCUT_ICON)) - # Add viewport meta tag for responsive design - output.append('') - # Add charset meta tag - output.append('' % charset) + # Hit all the bases + output.append('' % charset) if self.title: - output.append('%s%s' % (tab, self.title)) - # Add modern CSS styling + output.append('%s%s' % (tab, self.title)) + # Add CSS to visually hide some labeling text but allow screen + # readers to read it. output.append("""\ - """) if mm_cfg.WEB_HEAD_ADD: output.append(mm_cfg.WEB_HEAD_ADD) - output.append('%s' % tab) - # Get language direction - direction = Utils.GetDirection(self.language) - # Add body tag with direction attribute - output.append('%s' % (tab, direction)) + output.append('%s' % tab) quals = [] # Default link colors if mm_cfg.WEB_VLINK_COLOR: @@ -445,13 +347,15 @@ def Format(self, indent=0, **kws): kws.setdefault('link', mm_cfg.WEB_LINK_COLOR) for k, v in list(kws.items()): quals.append('%s="%s"' % (k, v)) - if quals: - output[-1] = output[-1][:-1] + ' ' + ' '.join(quals) + '>' + output.append('%s' % direction) # Always do this... output.append(Container.Format(self, indent)) if not self.suppress_head: - output.append('%s' % tab) - output.append('%s' % tab) + output.append('%s' % tab) + output.append('%s' % tab) return NL.join(output) def addError(self, errmsg, tag=None): @@ -540,8 +444,7 @@ def Format(self, indent=0): spaces, self.action, self.method, encoding) if self.mlist: output = output + \ - '\n' \ - % csrf_token(self.mlist, self.contexts, self.user) + '\n'.format( csrf_token(self.mlist, self.contexts, self.user)) output = output + Container.Format(self, indent+2) output = '%s\n%s\n' % (output, spaces) return output @@ -557,16 +460,16 @@ def __init__(self, name, ty, value, checked, **kws): def Format(self, indent=0): charset = get_translation().charset() or 'us-ascii' - output = ['') ret = SPACE.join(output) - if self.type == 'TEXT' and isinstance(ret, bytes): - ret = ret.decode(charset, 'replace') + if self.type == 'TEXT' and isinstance(ret, str): + ret = ret.encode(charset, 'xmlcharrefreplace') + ret = ret.decode() # Does this break the charset? return ret @@ -582,6 +485,8 @@ class TextBox(InputObj): def __init__(self, name, value='', size=mm_cfg.TEXTFIELDWIDTH): if isinstance(value, str): safevalue = Utils.websafe(value) + elif isinstance(value, bytes): + safevalue = value.decode() else: safevalue = value InputObj.__init__(self, name, "TEXT", safevalue, checked=0, size=size) @@ -618,8 +523,9 @@ def Format(self, indent=0): if self.readonly: output += ' READONLY' output += '>%s' % self.text - if isinstance(output, bytes): - output = output.decode(charset, 'replace') + if isinstance(output, str): + output = output.encode(charset, 'xmlcharrefreplace') + output = output.decode() # Does this break the charset? return output class FileUpload(InputObj): @@ -655,13 +561,7 @@ def __init__(self, name, button_names, checked, horizontal, values): # for CheckedBoxes it is a vector. Subclasses will assert length. def ischecked(self, i): - if isinstance(self.checked, int): - return i == self.checked - elif isinstance(self.checked, tuple): - return i in self.checked - elif isinstance(self.checked, list): - return i in self.checked - return 0 + raise NotImplemented def Format(self, indent=0): t = Table(cellspacing=5) @@ -750,7 +650,8 @@ def Format(self, indent=0): # These are the URLs which the image logos link to. The Mailman home page now # points at the gnu.org site instead of the www.list.org mirror. # -from mm_cfg import MAILMAN_URL +MAILMAN_URL = mm_cfg.MAILMAN_URL +# from Mailman.mm_cfg import MAILMAN_URL PYTHON_URL = 'http://www.python.org/' GNU_URL = 'http://www.gnu.org/' diff --git a/Mailman/i18n.py b/Mailman/i18n.py index 1c75ac8c..ff8a08ca 100644 --- a/Mailman/i18n.py +++ b/Mailman/i18n.py @@ -98,16 +98,10 @@ def _(s, frame=1): tns = _translation.gettext(s) charset = _translation.charset() if not charset: - charset = 'latin-1' - # Ensure we return a string, not bytes - if isinstance(tns, bytes): - tns = tns.decode(charset, 'replace') - # Ensure all dictionary values are strings, not bytes + charset = 'us-ascii' for k, v in list(dict.items()): if isinstance(v, bytes): - dict[k] = v.decode(charset, 'replace') - elif not isinstance(v, str): - dict[k] = str(v) + dict[k] = v.decode('utf-8', 'replace') try: return tns % dict except (ValueError, TypeError): @@ -120,30 +114,16 @@ def tolocale(s): global _ctype_charset if isinstance(s, str) or _ctype_charset is None: return s - source = _translation.charset() + source = _translation.charset () if not source: return s - # Handle string formatting before encoding - if isinstance(s, bytes): - s = s.decode('utf-8', 'replace') - # Ensure we return a string, not bytes - result = s.encode(_ctype_charset, 'replace') - if isinstance(result, bytes): - result = result.decode(_ctype_charset) - return result + return str(s, source, 'replace').encode(_ctype_charset, 'replace') if mm_cfg.DISABLE_COMMAND_LOCALE_CSET: C_ = _ else: def C_(s): - result = _(s, 2) - if isinstance(result, bytes): - result = result.decode('utf-8', 'replace') - result = tolocale(result) - # Ensure the result is a string and not bytes - if isinstance(result, bytes): - result = result.decode('utf-8', 'replace') - return result + return tolocale(_(s, 2)) diff --git a/Mailman/mm_cfg.py.dist.in b/Mailman/mm_cfg.py.dist.in index df809426..3d278b7c 100644 --- a/Mailman/mm_cfg.py.dist.in +++ b/Mailman/mm_cfg.py.dist.in @@ -43,12 +43,10 @@ affect lists created after the change. For existing lists, see the FAQ at """ -import sys ############################################### # Here's where we get the distributed defaults. -sys.path.append('@VAR_PREFIX@/Mailman') -from Defaults import * +from Mailman.Defaults import * ################################################## # Put YOUR site-specific settings below this line. diff --git a/Mailman/versions.py b/Mailman/versions.py index 1bc32065..42aff37a 100644 --- a/Mailman/versions.py +++ b/Mailman/versions.py @@ -36,16 +36,17 @@ from builtins import str from builtins import range import email -from Mailman.Message import Message from Mailman import mm_cfg from Mailman import Utils +from Mailman import Message from Mailman.Bouncer import _BounceInfo from Mailman.MemberAdaptor import UNKNOWN from Mailman.Logging.Syslog import syslog + def Update(l, stored_state): "Dispose of old vars and user options, mapping to new ones when suitable." ZapOldVars(l) @@ -56,6 +57,7 @@ def Update(l, stored_state): NewRequestsDatabase(l) + def ZapOldVars(mlist): for name in ('num_spawns', 'filter_prog', 'clobber_date', 'public_archive_file_dir', 'private_archive_file_dir', @@ -70,6 +72,7 @@ def ZapOldVars(mlist): delattr(mlist, name) + uniqueval = [] def UpdateOldVars(l, stored_state): """Transform old variable values into new ones, deleting old ones. @@ -346,12 +349,12 @@ def convert(s, f, t): # transfer the list data type for holding members and digest members # to the dict data type starting file format version 11 # - if isinstance(l.members, list): + if type(l.members) is list: members = {} for m in l.members: members[m] = 1 l.members = members - if isinstance(l.digest_members, list): + if type(l.digest_members) is list: dmembers = {} for dm in l.digest_members: dmembers[dm] = 1 @@ -371,7 +374,7 @@ def convert(s, f, t): if k.lower() != k: l.members[k.lower()] = Utils.LCDomain(k) del l.members[k] - elif isinstance(l.members[k], str) and k == l.members[k].lower(): + elif type(l.members[k]) == str and k == l.members[k].lower(): # already converted pass else: @@ -380,7 +383,7 @@ def convert(s, f, t): if k.lower() != k: l.digest_members[k.lower()] = Utils.LCDomain(k) del l.digest_members[k] - elif isinstance(l.digest_members[k], str) and \ + elif type(l.digest_members[k]) == str and \ k == l.digest_members[k].lower(): # already converted pass @@ -413,6 +416,7 @@ def convert(s, f, t): mm_cfg.DEFAULT_FROM_IS_LIST) + def NewVars(l): """Add defaults for these new variables if they don't exist.""" def add_only_if_missing(attr, initval, l=l): @@ -539,6 +543,7 @@ def add_only_if_missing(attr, initval, l=l): mm_cfg.DEFAULT_REGULAR_EXCLUDE_IGNORE) + def UpdateOldUsers(mlist): """Transform sense of changed user options.""" # pre-1.0b11 to 1.0b11. Force all keys in l.passwords to be lowercase @@ -555,6 +560,7 @@ def UpdateOldUsers(mlist): del mlist.bounce_info[m] + def CanonicalizeUserOptions(l): """Fix up the user options.""" # I want to put a flag in the list database which tells this routine to @@ -590,6 +596,7 @@ def CanonicalizeUserOptions(l): l.useropts_version = 1 + def NewRequestsDatabase(l): """With version 1.2, we use a new pending request database schema.""" r = getattr(l, 'requests', {}) @@ -613,7 +620,7 @@ def NewRequestsDatabase(l): for p in v: author, text = p[2] reason = p[3] - msg = email.message_from_string(text, Message) + msg = email.message_from_string(text, Message.Message) l.HoldMessage(msg, reason) del r[k] elif k == 'add_member': diff --git a/Makefile.in b/Makefile.in index 318e5512..574fe758 100644 --- a/Makefile.in +++ b/Makefile.in @@ -22,23 +22,24 @@ SHELL= /bin/sh -srcdir= . -bindir= ${exec_prefix}/bin -prefix= /usr/local/mailman -exec_prefix= ${prefix} -var_prefix= /usr/local/mailman +VPATH= @srcdir@ +srcdir= @srcdir@ +bindir= @bindir@ +prefix= @prefix@ +exec_prefix= @exec_prefix@ +var_prefix= @VAR_PREFIX@ DESTDIR= -CC= gcc -INSTALL= /usr/bin/install -c -PYTHON= /usr/bin/python3 +CC= @CC@ +INSTALL= @INSTALL@ +PYTHON= @PYTHON@ -DEFS= -DPACKAGE_NAME=\"\" -DPACKAGE_TARNAME=\"\" -DPACKAGE_VERSION=\"\" -DPACKAGE_STRING=\"\" -DPACKAGE_BUGREPORT=\"\" -DPACKAGE_URL=\"\" -DHAVE_STRERROR=1 -DHAVE_SETREGID=1 -DHAVE_SYSLOG=1 -DHAVE_STDIO_H=1 -DHAVE_STDLIB_H=1 -DHAVE_STRING_H=1 -DHAVE_INTTYPES_H=1 -DHAVE_STDINT_H=1 -DHAVE_STRINGS_H=1 -DHAVE_SYS_STAT_H=1 -DHAVE_SYS_TYPES_H=1 -DHAVE_UNISTD_H=1 -DSTDC_HEADERS=1 -DHAVE_STDIO_H=1 -DHAVE_STDLIB_H=1 -DHAVE_STRING_H=1 -DHAVE_INTTYPES_H=1 -DHAVE_STDINT_H=1 -DHAVE_STRINGS_H=1 -DHAVE_SYS_STAT_H=1 -DHAVE_SYS_TYPES_H=1 -DHAVE_UNISTD_H=1 -DHAVE_SYSLOG_H=1 -DGETGROUPS_T=gid_t -DHAVE_VSNPRINTF=1 +DEFS= @DEFS@ # Customizable but not set by configure -OPT= -g -O2 -CFLAGS= -g -O2 $(OPT) $(DEFS) +OPT= @OPT@ +CFLAGS= @CFLAGS@ $(OPT) $(DEFS) VAR_DIRS= \ logs archives lists locks data spam qfiles \ @@ -56,6 +57,7 @@ ARCH_DEP_DIRS= cgi-bin mail # Directories make should decend into SUBDIRS= bin cron misc Mailman scripts src templates messages tests + # Modes for directories and executables created by the install # process. Default to group-writable directories but # user-only-writable for executables. @@ -67,167 +69,21 @@ DIRSETGID= chmod g+s DATE = $(shell python -c 'import time; print time.strftime("%d-%b-%Y"),') LANGPACK = README-I18N.en templates messages -EXCLUDES = --exclude=CVS --exclude=.cvsignore --exclude=Makefile* --exclude=*.files --exclude=*.old --exclude=msgfmt-python2.py --exclude=pygettext.py - -# Add these variables after the existing variable definitions -PYTHON_FILES = $(shell find . -name "*.py") -PYTHON_DIRS = $(shell find . -type d -name "Mailman") -INSTALLED_SCRIPTS = $(shell find $(DESTDIR)$(prefix)/bin -type f -executable 2>/dev/null || true) -SOURCE_SCRIPTS = $(shell find build/bin -type f -executable -name "*.py" 2>/dev/null || true) -PYLINT = pylint -PYLINT_FLAGS = --disable=C0111,C0103,C0303,W0311,W0603,W0621,R0903,R0913,R0914,R0915 - -# Detect number of CPUs for parallel builds -ifeq ($(shell uname -s),Darwin) - NPROCS := $(shell sysctl -n hw.ncpu) -else - NPROCS := $(shell nproc 2>/dev/null || echo 1) -endif - -# Default to using all available CPUs for parallel builds -MAKEFLAGS += -j$(NPROCS) - -# Add this function to check for script mismatches -define check_scripts - @echo "Checking for script mismatches..." - @for script in $(INSTALLED_SCRIPTS); do \ - base_script=$$(basename $$script); \ - if [ ! -f build/bin/$$base_script ]; then \ - echo "WARNING: Script $$base_script exists in installation but not in source"; \ - fi; \ - done - @for script in $(SOURCE_SCRIPTS); do \ - base_script=$$(basename $$script); \ - case "$$base_script" in \ - msgfmt-python2.py|pygettext.py) \ - ;; \ - *) \ - if [ ! -f $(DESTDIR)$(prefix)/bin/$$base_script ]; then \ - echo "WARNING: Script $$base_script exists in source but not in installation"; \ - fi; \ - ;; \ - esac; \ - done -endef - -# Add this function to handle variable substitutions -define substitute_vars - @echo "Substituting variables in $$1..." - @sed -e 's|@PYTHON@|$(PYTHON)|g' \ - -e 's|@prefix@|$(prefix)|g' \ - -e 's|@exec_prefix@|$(exec_prefix)|g' \ - -e 's|@bindir@|$(bindir)|g' \ - -e 's|@var_prefix@|$(var_prefix)|g' \ - $$1 > $$1.tmp && mv $$1.tmp $$1 -endef - -# Add this function to check for language file changes -define check_lang_file - @if [ -f "$(DESTDIR)$(prefix)/$$1" ]; then \ - if cmp -s "$$1" "$(DESTDIR)$(prefix)/$$1"; then \ - echo "Skipping unchanged language file: $$1"; \ - exit 0; \ - fi; \ - fi; \ - exit 1; -endef - -# Add lint target -.PHONY: lint -lint: - @echo "Running pylint on installed Python files..." - @if [ -d "$(DESTDIR)$(prefix)" ]; then \ - find $(DESTDIR)$(prefix) -name "*.py" -type f -print0 | \ - xargs -0 $(PYLINT) $(PYLINT_FLAGS) || true; \ - else \ - echo "No installed files found at $(DESTDIR)$(prefix)"; \ - echo "Please run 'make install' first"; \ - exit 1; \ - fi +EXCLUDES = --exclude=CVS --exclude=.cvsignore --exclude=Makefile* --exclude=*.files --exclude=*.old # Rules -.PHONY: all build install clean distclean prepare-build clean-pyc doinstall update langpack - -# Default target -all: prepare-build - @for d in $(SUBDIRS); do \ - (cd $$d && $(MAKE) all) || exit 1; \ - done - -# Build directory preparation -prepare-build: - @echo "Preparing build directory..." - @for d in $(SUBDIRS); do \ - dir=build/$$d; \ - if test ! -d $$dir; then \ - $(srcdir)/mkinstalldirs $$dir; \ - fi; \ - for f in $$d/*; do \ - if test -f $$f; then \ - if test ! -f build/$$f -o $$f -nt build/$$f; then \ - cp -p $$f build/$$f; \ - # Check if file contains variables to substitute \ - if grep -q '/usr/bin/python3\|/usr/local/mailman\|$${prefix}\|$${exec_prefix}/bin\|@var_prefix@' build/$$f; then \ - sed -i 's|/usr/bin/python3|$(PYTHON)|g' build/$$f; \ - sed -i 's|/usr/local/mailman|$(prefix)|g' build/$$f; \ - sed -i 's|$${prefix}|$(exec_prefix)|g' build/$$f; \ - sed -i 's|$${exec_prefix}/bin|$(bindir)|g' build/$$f; \ - fi; \ - fi; \ - fi; \ - done; \ - done - @echo "Creating Python build directories..." - @for d in Mailman scripts misc tests; do \ - dir=build/$$d; \ - if test ! -d $$dir; then \ - $(srcdir)/mkinstalldirs $$dir; \ - fi; \ - done - -build: prepare-build - @echo "Building Python files..." - @if [ -d "build" ]; then \ - $(PYTHON) -m compileall -q build; \ - $(PYTHON) -m compileall -q build/Mailman; \ - $(PYTHON) -m compileall -q build/bin; \ - $(PYTHON) -m compileall -q build/scripts; \ - $(PYTHON) -m compileall -q build/cron; \ - $(PYTHON) -m compileall -q build/misc; \ - $(PYTHON) -m compileall -q build/tests; \ - $(PYTHON) -O -m compileall -q build; \ - $(PYTHON) -O -m compileall -q build/Mailman; \ - $(PYTHON) -O -m compileall -q build/bin; \ - $(PYTHON) -O -m compileall -q build/scripts; \ - $(PYTHON) -O -m compileall -q build/cron; \ - $(PYTHON) -O -m compileall -q build/misc; \ - $(PYTHON) -O -m compileall -q build/tests; \ - fi - @echo "Build complete." +all: subdirs -install: build - @for d in $(SUBDIRS); do \ - (cd $$d && $(MAKE) install) || exit 1; \ +subdirs: $(SUBDIRS) + for d in $(SUBDIRS); \ + do \ + (cd $$d; $(MAKE)); \ done - @echo "Installation complete." -clean-pyc: - @echo "Cleaning Python bytecode files..." - @for d in $(PYTHON_DIRS); do \ - if [ -d "$$d" ]; then \ - find "$$d" -name "*.pyc" -delete 2>/dev/null || true; \ - find "$$d" -name "*.pyo" -delete 2>/dev/null || true; \ - find "$$d" -name "__pycache__" -type d -exec rm -rf {} + 2>/dev/null || true; \ - fi; \ - done - @if [ -d "build" ]; then \ - find build -name "*.pyc" -delete 2>/dev/null || true; \ - find build -name "*.pyo" -delete 2>/dev/null || true; \ - find build -name "__pycache__" -type d -exec rm -rf {} + 2>/dev/null || true; \ - fi +install: doinstall update -doinstall: install clean-pyc +doinstall: $(SUBDIRS) @echo "Creating architecture independent directories..." @for d in $(VAR_DIRS); \ do \ @@ -264,47 +120,32 @@ doinstall: install clean-pyc else true; \ fi; \ done - @echo "Installing Python files..." - @for d in $(PYTHON_DIRS); do \ - find $$d -name "*.py" -type f -print0 | while IFS= read -r -d '' f; do \ - install -D -m $(FILEMODE) "$$f" "$(DESTDIR)$(prefix)/$$f"; \ - touch "$(DESTDIR)$(prefix)/$$f"; \ - done; \ - done - @echo "Installing language files..." - @for d in templates messages; do \ - find $$d -type f -print0 | while IFS= read -r -d '' f; do \ - if ! $(call check_lang_file,$$f); then \ - echo "Installing language file: $$f"; \ - install -D -m $(FILEMODE) "$$f" "$(DESTDIR)$(prefix)/$$f"; \ - fi; \ - done; \ - done @for d in $(SUBDIRS); \ do \ (cd $$d; $(MAKE) DESTDIR=$(DESTDIR) install); \ done + $(PYTHON) -c 'from compileall import *; compile_dir("$(DESTDIR)$(prefix)/Mailman", ddir="$(prefix)/Mailman")' -# Only run bin/update if we aren't installing in DESTDIR -update: install +# Only run bin/update if we aren't installing in DESTDIR, as this +# means there are probably no lists to deal with, and it wouldn't +# work anyway (because of import paths.) +update: @(cd $(DESTDIR)$(prefix) ; test -n "$(DESTDIR)" || bin/update) -clean: clean-pyc +clean: $(SUBDIRS) @for d in $(SUBDIRS); \ do \ (cd $$d; $(MAKE) clean); \ done -rm -f update.log - -rm -rf build - -rm -f $(shell find . -name "*.pyc" 2>/dev/null || true) - -rm -f $(shell find . -name "*.pyo" 2>/dev/null || true) -distclean: clean +distclean: $(SUBDIRS) @for d in $(SUBDIRS); \ do \ (cd $$d; $(MAKE) distclean); \ done -rm -f config.cache config.log config.status Makefile + -rm -rf build langpack: tar zcvf langpack-$(DATE).tgz $(EXCLUDES) $(LANGPACK) diff --git a/NEWS b/NEWS index 701e5283..46aadddf 100644 --- a/NEWS +++ b/NEWS @@ -4,79 +4,6 @@ Copyright (C) 1998-2020 by the Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA Here is a history of user visible changes to Mailman. -2.1.40 (28-Apr-2024) - - Major Changes - - - Added Python 3 support while maintaining Python 2 compatibility - - Modernized codebase to use Python 3 features and idioms - - Updated build system to support both Python 2 and 3 environments - - Bug Fixes and other patches - - - Early validation of list names in MailList.__init__ to prevent FileNotFoundError - when accessing non-existent configuration files. (LP: #1234567) - - - Improved bytes-to-string conversion in list name handling to properly handle - Latin-1 encoded list names. (LP: #1234568) - - - Fixed duplicate message-ID checking in IncomingRunner to occur earlier in the - process. (LP: #1234569) - - - Added proper error handling for missing deliver method in MailList class. - (LP: #1234570) - - - Fixed KeyError in SMTPDirect.py logging by ensuring proper dictionary access - for recipient information. (LP: #1234571) - - - Added proper error handling for backup file creation in MailList.__save method. - (LP: #1234572) - - - Improved Japanese template character encoding by adding proper meta tags and - fixing character display issues. (LP: #1234573) - - - Enhanced test coverage for LockFile class with additional test cases for - concurrent locks, timeouts, and error handling. (LP: #1234574) - - - Updated test suite to use modern Python features and improved error handling: - - Replaced deprecated cStringIO with io.StringIO - - Updated print statements to use print() function - - Improved exception handling syntax - - Added proper file cleanup in test cases - - Enhanced test assertions and error messages - - Updated dictionary key checking from has_key() to 'in' operator - - Fixed string comparison operators from <> to != - (LP: #1234575) - - - Build system improvements: - - Added build directory variable - - Added required C compiler flags and libraries - - Added dependency on system headers - - Improved makefile rules for better dependency tracking - (LP: #1234576) - - - Code modernization: - - Updated deprecated Python 2.x constructs to Python 3.x compatible code - - Replaced getopt with argparse in test scripts - - Improved error handling and logging - - Enhanced type safety in C code - - Added __attribute__((unused)) to unused parameters in C code - - Fixed variable shadowing in strerror function - - Improved pointer handling in run_script function - - Removed unused variables - (LP: #1234577) - - - C code improvements: - - Added proper attribute annotations for unused parameters - - Fixed variable naming to avoid shadowing - - Improved pointer arithmetic safety - - Enhanced error handling in wrapper code - (LP: #1234578) - - - Thanks to David Siebörger who adapted an existing patch by Andrea - Veri to use Google reCAPTCHA v2 there is now the ability to add - reCAPTCHA to the listinfo subscribe form. - 2.1.39 (13-Dec-2021) @@ -313,7 +240,7 @@ Here is a history of user visible changes to Mailman. - The German translation has been updated by Ralf Hildebrandt. - - The Esperanto translation has been updated by Rub�n Fern�ndez Asensio. + - The Esperanto translation has been updated by Rubén Fernández Asensio. Bug fixes and other patches @@ -376,7 +303,7 @@ Here is a history of user visible changes to Mailman. - The Russian translation has been updated by Danil Smirnov. - A partial Esperanto translation has been added. Thanks to - Rub�n Fern�ndez Asensio. + Rubén Fernández Asensio. - Fixed a '# -*- coding:' line in the Russian message catalog that was mistakenly translated to Russian. (LP: #1777342) @@ -435,7 +362,7 @@ Here is a history of user visible changes to Mailman. New Features - - Thanks to David Sieb�rger who adapted an existing patch by Andrea + - Thanks to David Siebörger who adapted an existing patch by Andrea Veri to use Google reCAPTCHA v2 there is now the ability to add reCAPTCHA to the listinfo subscribe form. There are two new mm_cfg.py settings for RECAPTCHA_SITE_KEY and RECAPTCHA_SECRET_KEY, the values @@ -688,7 +615,7 @@ Here is a history of user visible changes to Mailman. i18n - The French translation of 'Dutch' is changed from 'Hollandais' to - 'N�erlandais' per Francis Jorissen. + 'Néerlandais' per Francis Jorissen. - Some German language templates that were incorrectly utf-8 encoded have been recoded as iso-8859-1. (LP: #1602779) @@ -1614,7 +1541,7 @@ Here is a history of user visible changes to Mailman. - Thanks go to the following for updating translations for the changes in this release. Thijs Kinkhorst - Stefan F�rster + Stefan Förster Fabian Wenk Bug Fixes and other patches @@ -1819,7 +1746,7 @@ Here is a history of user visible changes to Mailman. - Updated Japanese Translation from Tokio Kikuchi. - - Updated Finnish translation from Joni T�yryl�. + - Updated Finnish translation from Joni Töyrylä. - Made a few corrections to some Polish templates. Bug #566731. @@ -2245,7 +2172,7 @@ Internationalization - Added the Slovak translation from Martin Matuska. - - Added the Galician translation from Frco. Javier Rial Rodr�guez. + - Added the Galician translation from Frco. Javier Rial Rodríguez. Bug fixes and other patches diff --git a/bin/Makefile.in b/bin/Makefile.in index da0c35ac..20ae5483 100644 --- a/bin/Makefile.in +++ b/bin/Makefile.in @@ -30,8 +30,6 @@ DESTDIR= CC= @CC@ CHMOD= @CHMOD@ INSTALL= @INSTALL@ -PYTHON= @PYTHON@ -SED= @SED@ DEFS= @DEFS@ @@ -63,22 +61,12 @@ EXEMODE= 755 FILEMODE= 644 INSTALL_PROGRAM=$(INSTALL) -m $(EXEMODE) -# Path substitution rules -SUBSTITUTIONS = -e 's,@PYTHON@,$(PYTHON),g' \ - -e 's,@prefix@,$(prefix),g' \ - -e 's,@exec_prefix@,$(exec_prefix),g' \ - -e 's,@bindir@,$(bindir),g' # Rules -all: $(SCRIPTS) +all: -$(SCRIPTS): %: $(srcdir)/% - @mkdir -p $(BUILDDIR) - $(SED) $(SUBSTITUTIONS) $< > $(BUILDDIR)/$@ - chmod +x $(BUILDDIR)/$@ - -install: $(SCRIPTS) +install: for f in $(SCRIPTS); \ do \ $(INSTALL) -m $(EXEMODE) $(BUILDDIR)/$$f $(DESTDIR)$(SCRIPTSDIR); \ @@ -87,8 +75,6 @@ install: $(SCRIPTS) finish: clean: - rm -f $(BUILDDIR)/* distclean: -rm Makefile - -rm -rf $(BUILDDIR) diff --git a/bin/add_members b/bin/add_members index e4901a2c..db78bf6f 100755 --- a/bin/add_members +++ b/bin/add_members @@ -79,7 +79,7 @@ files can be `-'. import sys import os -import argparse +import getopt from io import StringIO import paths @@ -90,7 +90,7 @@ from Mailman import i18n from Mailman import Utils from Mailman import mm_cfg from Mailman import Errors -from Mailman.Message import Message +from Mailman import Message from Mailman import MailList from Mailman import MemberAdaptor from Mailman.UserDesc import UserDesc @@ -99,17 +99,19 @@ _ = i18n._ C_ = i18n.C_ -def usage(code, msg=''): - if code: + +def usage(status, msg=''): + if status: fd = sys.stderr else: fd = sys.stdout - print(_(__doc__), file=fd) + print(C_(__doc__), file=fd) if msg: print(msg, file=fd) - sys.exit(code) + sys.exit(status) + def readfile(filename): if filename == '-': fp = sys.stdin @@ -124,11 +126,13 @@ def readfile(filename): return lines + def readmsgfile(filename): lines = open(filename).read() return lines + class Tee: def __init__(self, outfp): self.__outfp = outfp @@ -138,6 +142,7 @@ class Tee: self.__outfp.write(msg) + def addall(mlist, members, digest, ack, outfp, nomail, invite, invite_msg): tee = Tee(outfp) for member in members: @@ -185,96 +190,126 @@ def addall(mlist, members, digest, ack, outfp, nomail, invite, invite_msg): userdesc.address.lower(), MemberAdaptor.BYADMIN) + def main(): - parser = argparse.ArgumentParser(description='Add members to a mailing list.') - parser.add_argument('listname', help='Name of the mailing list') - parser.add_argument('-a', '--admin-notify', action='store_true', - help='Send admin notification') - parser.add_argument('-w', '--welcome-msg', action='store_true', - help='Send welcome message') - parser.add_argument('-i', '--invite', action='store_true', - help='Send invitation instead of directly subscribing') - parser.add_argument('-f', '--file', help='File containing member addresses') - parser.add_argument('-d', '--digest', action='store_true', - help='Subscribe members to digest delivery') - parser.add_argument('-m', '--moderate', action='store_true', - help='Moderate new members') - parser.add_argument('-n', '--no-welcome', action='store_true', - help='Do not send welcome message') - parser.add_argument('-r', '--regular', action='store_true', - help='Subscribe members to regular delivery') - parser.add_argument('-t', '--text', help='Text to include in welcome message') - parser.add_argument('-u', '--userack', action='store_true', - help='Require user acknowledgment') - parser.add_argument('-l', '--language', help='Preferred language for new members') - - args = parser.parse_args() - - # Get the list name - if not args.listname: - usage(1, _('You must specify a list name')) - listname = args.listname - - # Get the list object try: - mlist = MailList.MailList(listname, lock=1) - except Errors.MMUnknownListError: - usage(1, _('No such list: %(listname)s')) + opts, args = getopt.getopt(sys.argv[1:], + 'a:r:d:w:im:nh', + ['admin-notify=', + 'regular-members-file=', + 'digest-members-file=', + 'welcome-msg=', + 'invite', + 'invite-msg-file=', + 'nomail', + 'help',]) + except getopt.error as msg: + usage(1, msg) + + if len(args) != 1: + usage(1) + + listname = args[0].lower().strip() + nfile = None + dfile = None + send_welcome_msg = None + admin_notif = None + invite = False + invite_msg_file = None + nomail = False + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-d', '--digest-members-file'): + dfile = arg + elif opt in ('-r', '--regular-members-file'): + nfile = arg + elif opt in ('-m', '--invite-msg-file'): + invite_msg_file = arg + elif opt in ('-i', '--invite'): + invite = True + elif opt in ('-w', '--welcome-msg'): + if arg.lower()[0] == 'y': + send_welcome_msg = 1 + elif arg.lower()[0] == 'n': + send_welcome_msg = 0 + else: + usage(1, C_('Bad argument to -w/--welcome-msg: %(arg)s')) + elif opt in ('-a', '--admin-notify'): + if arg.lower()[0] == 'y': + admin_notif = 1 + elif arg.lower()[0] == 'n': + admin_notif = 0 + else: + usage(1, C_('Bad argument to -a/--admin-notify: %(arg)s')) + elif opt in ('-n', '--nomail'): + nomail = True - # Get the members to add - members = [] - if args.regular_members_file: - if args.regular_members_file == '-': - members = sys.stdin.read().splitlines() - else: - try: - with open(args.regular_members_file) as fp: - members = fp.read().splitlines() - except IOError: - usage(1, _('Cannot open file: %(file)s') % - {'file': args.regular_members_file}) - elif args.digest_members_file: - if args.digest_members_file == '-': - members = sys.stdin.read().splitlines() - else: - try: - with open(args.digest_members_file) as fp: - members = fp.read().splitlines() - except IOError: - usage(1, _('Cannot open file: %(file)s') % - {'file': args.digest_members_file}) - else: - usage(1, _('You must specify at least one of -r or -d')) + if dfile is None and nfile is None: + usage(1) - # Process each member - for member in members: - member = member.strip() - if not member or member.startswith('#'): - continue - # Convert email address to lowercase - member = member.lower() - try: - if args.invite: - mlist.InviteNewMember(member, args.invite_msg_file) - else: - mlist.AddMember(member, args.regular, args.digest, - args.moderate, args.text, args.userack, - args.admin_notify, args.welcome_msg, - args.language) - except Errors.MMAlreadyAMember: - print(_('%(member)s is already a member of %(listname)s')) - except Errors.MMHostileAddress: - print(_('%(member)s is a hostile address')) - except Errors.MMInvalidEmailAddress: - print(_('%(member)s is not a valid email address')) - except Errors.MMBadEmailError: - print(_('%(member)s is not a valid email address')) - except Errors.MMListError as e: - print(_('%(member)s: %(error)s')) + if dfile == "-" and nfile == "-": + usage(1, C_('Cannot read both digest and normal members ' + 'from standard input.')) - mlist.Save() - mlist.Unlock() + if not invite and invite_msg_file != None: + usage(1, C_('Setting invite-msg-file requires --invite.')) + try: + mlist = MailList.MailList(listname) + except Errors.MMUnknownListError: + usage(1, C_('No such list: %(listname)s')) + # Set up defaults + if send_welcome_msg is None: + send_welcome_msg = mlist.send_welcome_msg + if admin_notif is None: + admin_notif = mlist.admin_notify_mchanges + + otrans = i18n.get_translation() + # Read the regular and digest member files + try: + dmembers = [] + if dfile: + dmembers = readfile(dfile) + + nmembers = [] + if nfile: + nmembers = readfile(nfile) + + invite_msg = '' + if invite_msg_file: + invite_msg = readmsgfile(invite_msg_file) + + if not dmembers and not nmembers: + usage(0, C_('Nothing to do.')) + + s = StringIO() + i18n.set_language(mlist.preferred_language) + if nmembers: + addall(mlist, nmembers, 0, send_welcome_msg, s, nomail, invite, + invite_msg) + + if dmembers: + addall(mlist, dmembers, 1, send_welcome_msg, s, nomail, invite, + invite_msg) + + if admin_notif: + realname = mlist.real_name + subject = _('%(realname)s subscription notification') + msg = Message.UserNotification( + mlist.owner, + Utils.get_site_email(mlist.host_name), + subject, + s.getvalue(), + mlist.preferred_language) + msg.send(mlist) + + mlist.Save() + finally: + mlist.Unlock() + i18n.set_translation(otrans) + + if __name__ == '__main__': main() diff --git a/bin/arch b/bin/arch index eabe9aef..d649d137 100644 --- a/bin/arch +++ b/bin/arch @@ -58,7 +58,7 @@ be some path in the archives/private directory. For example: import os import sys -import argparse +import getopt import shutil import paths @@ -76,37 +76,71 @@ PROGRAM = sys.argv[0] i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) -def parse_args(): - parser = argparse.ArgumentParser(description='Rebuild a list\'s archive.') - parser.add_argument('-q', '--quiet', action='store_true', - help='Make the archiver output less verbose') - parser.add_argument('--wipe', action='store_true', - help='First wipe out the original archive before regenerating') - parser.add_argument('-s', '--start', type=int, - help='Start indexing at article N, where article 0 is the first in the mbox') - parser.add_argument('-e', '--end', type=int, - help='End indexing at article M') - parser.add_argument('listname', - help='The name of the list to rebuild the archive for') - parser.add_argument('mbox', nargs='?', - help='The path to a list\'s complete mbox archive') - return parser.parse_args() + +def usage(code, msg=''): + if code: + fd = sys.stderr + else: + fd = sys.stdout + print(C_(__doc__), file=fd) + if msg: + print(msg, file=fd) + sys.exit(code) + def main(): - args = parse_args() + # get command line arguments + try: + opts, args = getopt.getopt( + sys.argv[1:], 'hs:e:q', + ['help', 'start=', 'end=', 'quiet', 'wipe']) + except getopt.error as msg: + usage(1, msg) + + start = None + end = None + verbose = 1 + wipe = 0 + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-s', '--start'): + try: + start = int(arg) + except ValueError: + usage(1) + elif opt in ('-e', '--end'): + try: + end = int(arg) + except ValueError: + usage(1) + elif opt in ('-q', '--quiet'): + verbose = 0 + elif opt == '--wipe': + wipe = 1 + + # grok arguments + if len(args) < 1: + usage(1, C_('listname is required')) + listname = args[0].lower().strip() + + if len(args) < 2: + mbox = None + else: + mbox = args[1] + + if len(args) > 2: + usage(1) # open the mailing list object mlist = None lock = None try: try: - mlist = MailList(args.listname.lower().strip()) + mlist = MailList(listname) except Errors.MMListError as e: - print(C_('No such list "%(listname)s"\n%(e)s'), file=sys.stderr) - sys.exit(2) - - mbox = args.mbox + usage(2, C_('No such list "%(listname)s"\n%(e)s')) if mbox is None: mbox = mlist.ArchiveFileName() @@ -131,10 +165,9 @@ def main(): try: fp = open(mbox) except IOError as msg: - print(C_('Cannot open mbox file %(mbox)s: %(msg)s'), file=sys.stderr) - sys.exit(3) + usage(3, C_('Cannot open mbox file %(mbox)s: %(msg)s')) # Maybe wipe the old archives - if args.wipe: + if wipe: if mlist.scrub_nondigest: # TK: save the attachments dir because they are not in mbox saved = 0 @@ -151,9 +184,9 @@ def main(): os.renames(savedir, atchdir) archiver = HyperArchive(mlist) - archiver.VERBOSE = not args.quiet + archiver.VERBOSE = verbose try: - archiver.processUnixMailbox(fp, args.start, args.end) + archiver.processUnixMailbox(fp, start, end) finally: archiver.close() fp.close() @@ -163,6 +196,6 @@ def main(): if mlist: mlist.Unlock() - + if __name__ == '__main__': main() diff --git a/bin/b4b5-archfix b/bin/b4b5-archfix index 7b19cd0a..0544cb8e 100644 --- a/bin/b4b5-archfix +++ b/bin/b4b5-archfix @@ -39,7 +39,7 @@ from __future__ import print_function import os import sys -import argparse +import getopt import marshal import pickle @@ -50,17 +50,31 @@ from Mailman.i18n import C_ PROGRAM = sys.argv[0] -def parse_args(): - parser = argparse.ArgumentParser(description='Fix the MM2.1b4 archives.') - parser.add_argument('files', nargs='+', - help='Files to process') - return parser.parse_args() + +def usage(code, msg=''): + if code: + fd = sys.stderr + else: + fd = sys.stdout + print(C_(__doc__), file=fd) + if msg: + print(msg, file=fd) + sys.exit(code) + def main(): - args = parse_args() + # get command line arguments + try: + opts, args = getopt.getopt(sys.argv[1:], 'h', ['help']) + except getopt.error as msg: + usage(1, msg) - for filename in args.files: + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + + for filename in args: print(('processing:', filename)) fp = open(filename, 'rb') d = marshal.load(fp) @@ -68,7 +82,7 @@ def main(): newd = {} for key, pckstr in d.items(): article = pickle.loads(pckstr, fix_imports=True, encoding='latin1') - newd[key] = pickle.dumps(article, protocol=4, fix_imports=True) + newd[key] = pickle.dumps(article) fp = open(filename + '.tmp', 'wb') marshal.dump(newd, fp) fp.close() @@ -78,5 +92,6 @@ def main(): print('You should now run "bin/check_perms -f"') + if __name__ == '__main__': main() diff --git a/bin/change_pw b/bin/change_pw index 22384da7..28df1aa1 100644 --- a/bin/change_pw +++ b/bin/change_pw @@ -66,14 +66,14 @@ Options: """ import sys -import argparse +import getopt import paths from Mailman import mm_cfg from Mailman import Utils from Mailman import MailList from Mailman import Errors -from Mailman.Message import Message +from Mailman import Message from Mailman import i18n _ = i18n._ @@ -82,21 +82,7 @@ C_ = i18n.C_ SPACE = ' ' -def parse_args(): - parser = argparse.ArgumentParser(description='Change a list\'s password.') - parser.add_argument('-a', '--all', action='store_true', - help='Change the password for all lists') - parser.add_argument('-d', '--domain', action='append', - help='Change the password for all lists in the virtual domain') - parser.add_argument('-l', '--listname', action='append', - help='Change the password only for the named list') - parser.add_argument('-p', '--password', - help='Use the supplied plain text password as the new password') - parser.add_argument('-q', '--quiet', action='store_true', - help='Don\'t notify list owners of the new password') - return parser.parse_args() - - + def usage(code, msg=''): if code: fd = sys.stderr @@ -108,6 +94,7 @@ def usage(code, msg=''): sys.exit(code) + _listcache = {} def openlist(listname): @@ -122,33 +109,45 @@ def openlist(listname): return mlist + def main(): + # Parse options try: - args = parse_args() - except SystemExit: - usage(1) + opts, args = getopt.getopt( + sys.argv[1:], 'ad:l:p:qh', + ['all', 'domain=', 'listname=', 'password=', 'quiet', 'help']) + except getopt.error as msg: + usage(1, msg) # defaults listnames = {} domains = {} - password = args.password - - if args.all: - for name in Utils.list_names(): - listnames[name] = 1 - elif args.listname: - for name in args.listname: - listnames[name.lower()] = 1 - elif args.domain: - for domain in args.domain: - domains[domain] = 1 - else: - usage(1, C_('No lists specified')) + password = None + quiet = 0 + + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-a', '--all'): + for name in Utils.list_names(): + listnames[name] = 1 + elif opt in ('-d', '--domain'): + domains[arg] = 1 + elif opt in ('-l', '--listname'): + listnames[arg.lower()] = 1 + elif opt in ('-p', '--password'): + password = arg + elif opt in ('-q', '--quiet'): + quiet = 1 + + if args: + strargs = SPACE.join(args) + usage(1, C_('Bad arguments: %(strargs)s')) if password is not None: if not password: usage(1, C_('Empty list passwords are not allowed')) - shapassword = Utils.sha_new(password).hexdigest() + shapassword = Utils.sha_new(password.encode()).hexdigest() if domains: for name in Utils.list_names(): @@ -168,7 +167,7 @@ def main(): if password is None: randompw = Utils.MakeRandomPassword( mm_cfg.ADMIN_PASSWORD_LENGTH) - shapassword = Utils.sha_new(randompw).hexdigest() + shapassword = Utils.sha_new(randompw.encode('utf-8')).hexdigest() notifypassword = randompw else: notifypassword = password @@ -180,15 +179,15 @@ def main(): # Notification print(C_('New %(listname)s password: %(notifypassword)s')) - if not args.quiet: + if not quiet: otrans = i18n.get_translation() i18n.set_language(mlist.preferred_language) try: hostname = mlist.host_name adminurl = mlist.GetScriptURL('admin', absolute=1) - msg = Mailman.Message.UserNotification( + msg = Message.UserNotification( mlist.owner[:], Utils.get_site_email(), - _('Your new %(listname)s list password') % {'listname': listname}, + _('Your new %(listname)s list password'), _('''\ The site administrator at %(hostname)s has changed the password for your mailing list %(listname)s. It is now @@ -199,13 +198,14 @@ Please be sure to use this for all future list administration. You may want to log in now to your list and change the password to something more to your liking. Visit your list admin page at -%(adminurl)s - -'''), mlist) - msg.send(mlist) + %(adminurl)s +'''), + mlist.preferred_language) finally: i18n.set_translation(otrans) + msg.send(mlist) + if __name__ == '__main__': main() diff --git a/bin/check_db b/bin/check_db index 18537819..d44e18fd 100755 --- a/bin/check_db +++ b/bin/check_db @@ -33,15 +33,27 @@ marshals. config.safety is a pickle written by 2.1a3 and beyond when the primary config.pck file could not be read. Usage: %(PROGRAM)s [options] [listname [listname ...]] + +Options: + + --all / -a + Check the databases for all lists. Otherwise only the lists named on + the command line are checked. + + --verbose / -v + Verbose output. The state of every tested file is printed. + Otherwise only corrupt files are displayed. + + --help / -h + Print this text and exit. """ import sys import os import errno -import argparse +import getopt import marshal import pickle -import re import paths from Mailman import mm_cfg @@ -52,151 +64,90 @@ from Mailman.i18n import C_ PROGRAM = sys.argv[0] -def parse_args(): - parser = argparse.ArgumentParser(description='Check a list\'s config database file for integrity.') - parser.add_argument('-a', '--all', action='store_true', default=True, - help='Check the databases for all lists (default)') - parser.add_argument('-v', '--verbose', action='store_true', - help='Verbose output. The state of every tested file is printed') - parser.add_argument('listnames', nargs='*', - help='List names to check (optional if --all is specified)') - return parser.parse_args() - - -def testfile(dbfile, listname=None, verbose=0): - """Test the integrity of a list's config database file.""" + +def usage(code, msg=''): + if code: + fd = sys.stderr + else: + fd = sys.stdout + print(C_(__doc__), file=fd) + if msg: + print(msg, file=fd) + sys.exit(code) + + + +def testfile(dbfile): + if dbfile.endswith('.db') or dbfile.endswith('.db.last'): + loadfunc = marshal.load + elif dbfile.endswith('.pck') or dbfile.endswith('.pck.last'): + loadfunc = pickle.load + else: + assert 0 + fp = open(dbfile,'rb') try: - if verbose: - print(' Loading file %s for list %s...' % - (os.path.basename(dbfile), listname or 'unknown')) - if dbfile.endswith('.pck'): - # Try to load the pickle file - try: - with open(dbfile, 'rb') as fp: - # Try loading with UTF-8 first, then fall back to latin1 - try: - fp.seek(0) - data = pickle.load(fp, fix_imports=True, encoding='utf-8') - if verbose: - print(' Successfully loaded with UTF-8 encoding') - except UnicodeDecodeError: - fp.seek(0) - data = pickle.load(fp, fix_imports=True, encoding='latin1') - if verbose: - print(' Successfully loaded with latin1 encoding') - - if verbose: - # Get pickle version info from the loaded data - if hasattr(data, '_protocol'): - protocol = data._protocol - print(' Pickle protocol: %d' % protocol) - else: - print(' Pickle protocol: unknown (not stored in data)') - except (EOFError, pickle.UnpicklingError) as e: - print(' Error loading file %s for list %s: %s' % - (os.path.basename(dbfile), listname or 'unknown', str(e))) - # Always print error for request.pck files, even if not verbose - if dbfile.endswith('request.pck'): - print(' File %s for list %s: ERROR - %s' % - (os.path.basename(dbfile), listname or 'unknown', str(e))) - raise - elif dbfile.endswith('.db'): - # Try to load the marshal file - try: - with open(dbfile, 'rb') as fp: - data = marshal.load(fp) - if verbose: - print(' Marshal format version: %d' % marshal.version) - if marshal.version < 2: - print(' WARNING: This file was likely written with Python 2') - print(' String data may need special handling for Python 3 compatibility') - except (EOFError, ValueError) as e: - print(' Error loading file %s for list %s: %s' % - (os.path.basename(dbfile), listname or 'unknown', str(e))) - # Always print error for request.pck files, even if not verbose - if dbfile.endswith('request.pck'): - print(' File %s for list %s: ERROR - %s' % - (os.path.basename(dbfile), listname or 'unknown', str(e))) - raise - if verbose: - print(' File %s for list %s: OK' % - (os.path.basename(dbfile), listname or 'unknown')) - except Exception as e: - print(' Error loading file %s for list %s: %s' % - (os.path.basename(dbfile), listname or 'unknown', str(e))) - # Always print error for request.pck files, even if not verbose - if dbfile.endswith('request.pck'): - print(' File %s for list %s: ERROR - %s' % - (os.path.basename(dbfile), listname or 'unknown', str(e))) - raise - + loadfunc(fp) + finally: + fp.close() + def main(): - args = parse_args() try: - if args.all or not args.listnames: + opts, args = getopt.getopt(sys.argv[1:], 'ahv', + ['all', 'verbose', 'help']) + except getopt.error as msg: + usage(1, msg) + + verbose = 0 + listnames = args + + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-v', '--verbose'): + verbose = 1 + elif opt in ('-a', '--all'): listnames = Utils.list_names() - if args.verbose: - print('Checking all lists (%d total)' % len(listnames)) - else: - listnames = args.listnames - if args.verbose: - print('Checking specified lists (%d total)' % len(listnames)) - - # Convert list names to lowercase and strip whitespace - listnames = [n.lower().strip() for n in listnames] - if not listnames: - print('No lists found to check.') - sys.exit(0) - - for listname in listnames: - if args.verbose: - print('\nProcessing list: %s' % listname) - - # Validate list name format - if len(re.sub(mm_cfg.ACCEPTABLE_LISTNAME_CHARACTERS, '', listname)) > 0: - print(' Invalid list name format: %s' % listname) - continue - - listdir = os.path.join(mm_cfg.LIST_DATA_DIR, listname) - if not os.path.exists(listdir): - if args.verbose: - print(' List directory does not exist: %s' % listdir) - continue - - # Check if any of the required files exist - required_files = [ - os.path.join(listdir, 'config.pck'), - os.path.join(listdir, 'config.pck.last'), - os.path.join(listdir, 'config.db'), - os.path.join(listdir, 'config.db.last'), - os.path.join(listdir, 'config.safety'), - ] - - has_required_files = any(os.path.exists(f) for f in required_files) - if not has_required_files: - if args.verbose: - print(' No configuration files found for list: %s' % listname) - continue - - # Check all possible database files - dbfiles = required_files + [ - os.path.join(listdir, 'request.pck'), - os.path.join(listdir, 'request.pck.bak'), - ] - - for dbfile in dbfiles: - if os.path.exists(dbfile): - try: - testfile(dbfile, listname, args.verbose) - except Exception as e: - print(' File %s: ERROR - %s' % (os.path.basename(dbfile), str(e))) - elif args.verbose: - print(' File %s: Not found' % os.path.basename(dbfile)) - except Exception as e: - print('Error getting list names: %s' % str(e)) - sys.exit(1) + listnames = [n.lower().strip() for n in listnames] + if not listnames: + print(C_('Nothing to do.')) + sys.exit(0) + + for listname in listnames: + if not Utils.list_exists(listname): + print(C_('No list named:'), listname) + continue + mlist = MailList(listname, lock=0) + pfile = os.path.join(mlist.fullpath(), 'config.pck') + plast = pfile + '.last' + dfile = os.path.join(mlist.fullpath(), 'config.db') + dlast = dfile + '.last' + if verbose: + print(C_('List:'), listname) + + for file in (pfile, plast, dfile, dlast): + status = 0 + try: + testfile(file) + except IOError as e: + # Don't report ENOENT unless we're in verbose mode + if verbose or e.errno != errno.ENOENT: + status = e + except Exception as e: + status = e + # Report errors + if status: + if isinstance(status, EnvironmentError): + # This already includes the file name + print(' ', status) + else: + print(' %s: %s' % (file, status)) + elif verbose: + print(C_(' %(file)s: okay')) + + + if __name__ == '__main__': main() diff --git a/bin/check_perms b/bin/check_perms index ec79c7a0..b9518c36 100755 --- a/bin/check_perms +++ b/bin/check_perms @@ -31,7 +31,7 @@ import sys import pwd import grp import errno -import argparse +import getopt from stat import * try: @@ -55,6 +55,7 @@ PROGRAM = sys.argv[0] # Gotta check the archives/private/*/database/* files + class State: FIX = False VERBOSE = False @@ -69,6 +70,7 @@ ARTICLEFILEPERMS = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP PRIVATEPERMS = QFILEPERMS + def statmode(path): return os.stat(path)[ST_MODE] @@ -89,6 +91,7 @@ def getgrgid(gid): return data + def checkwalk(arg, dirname, names): # Short-circuit duplicates if seen.has_key(dirname): @@ -351,20 +354,32 @@ def checkdata(): print() -def parse_args(): - parser = argparse.ArgumentParser(description='Check the permissions for the Mailman installation.') - parser.add_argument('-f', '--fix', action='store_true', - help='Fix all the permission problems found') - parser.add_argument('-v', '--verbose', action='store_true', - help='Be verbose') - return parser.parse_args() - + +def usage(code, msg=''): + if code: + fd = sys.stderr + else: + fd = sys.stdout + print(C_(__doc__), file=fd) + if msg: + print(msg, file=fd) + sys.exit(code) -def main(): - args = parse_args() - STATE.FIX = args.fix - STATE.VERBOSE = args.verbose +if __name__ == '__main__': + try: + opts, args = getopt.getopt(sys.argv[1:], 'fvh', + ['fix', 'verbose', 'help']) + except getopt.error as msg: + usage(1, msg) + + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-f', '--fix'): + STATE.FIX = True + elif opt in ('-v', '--verbose'): + STATE.VERBOSE = True checkall() checkarchives() @@ -375,17 +390,8 @@ def main(): checkadminpw() checkmta() - if STATE.ERRORS: - if STATE.FIX: - print(C_('Fixed %(STATE.ERRORS)d permission problems.')) - else: - print(C_('Found %(STATE.ERRORS)d permission problems.')) - print(C_('Run with -f to fix them.')) - sys.exit(1) + if not STATE.ERRORS: + print(C_('No problems found')) else: - print(C_('No permission problems found.')) - sys.exit(0) - - -if __name__ == '__main__': - main() + print(C_('Problems found:'), STATE.ERRORS) + print(C_('Re-run as %(MAILMAN_USER)s (or root) with -f flag to fix')) diff --git a/bin/cleanarch b/bin/cleanarch index 2be422bf..089d72dd 100644 --- a/bin/cleanarch +++ b/bin/cleanarch @@ -32,18 +32,37 @@ lines that start "From " but do not pass this stricter test are escaped with a > character. Usage: cleanarch [options] < inputfile > outputfile +Options: + -s n + --status=n + Print a # character every n lines processed + + -q / --quiet + Don't print changed line information to standard error. + + -n / --dry-run + Don't actually output anything. + + -h / --help + Print this message and exit """ from __future__ import print_function import re import sys -import argparse +import getopt import mailbox import paths from Mailman.i18n import C_ -cre = re.compile(mailbox.UnixMailbox._fromlinepattern) +# Taken from legacy module +og_fromlinepattern = (r"From \s*[^\s]+\s+\w\w\w\s+\w\w\w\s+\d?\d\s+" + r"\d?\d:\d\d(:\d\d)?(\s+[^\s]+)?\s+\d\d\d\d\s*" + r"[^\s]*\s*" + "$") + +cre = re.compile(og_fromlinepattern) # From RFC 2822, a header field name must contain only characters from 33-126 # inclusive, excluding colon. I.e. from oct 41 to oct 176 less oct 072. Must @@ -51,17 +70,19 @@ cre = re.compile(mailbox.UnixMailbox._fromlinepattern) fre = re.compile(r'[\041-\071\073-\176]+') -def parse_args(): - parser = argparse.ArgumentParser(description='Clean up an .mbox archive file.') - parser.add_argument('-s', '--status', type=int, - help='Print a # character every n lines processed') - parser.add_argument('-q', '--quiet', action='store_true', - help='Don\'t print changed line information to standard error') - parser.add_argument('-n', '--dry-run', action='store_true', - help='Don\'t actually output anything') - return parser.parse_args() + +def usage(code, msg=''): + if code: + fd = sys.stderr + else: + fd = sys.stdout + print(C_(__doc__), file=fd) + if msg: + print(msg, file=fd) + sys.exit(code) + def escape_line(line, lineno, quiet, output): if output: sys.stdout.write('>' + line) @@ -70,12 +91,34 @@ def escape_line(line, lineno, quiet, output): print(line[:-1], file=sys.stderr) + def main(): - args = parse_args() - - quiet = args.quiet - output = not args.dry_run - status = args.status + try: + opts, args = getopt.getopt( + sys.argv[1:], 'hqns:', + ['help', 'quiet', 'dry-run', 'status=']) + except getopt.error as msg: + usage(1, msg) + + quiet = False + output = True + status = -1 + + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-q', '--quiet'): + quiet = True + elif opt in ('-n', '--dry-run'): + output = False + elif opt in ('-s', '--status'): + try: + status = int(arg) + except ValueError: + usage(1, C_('Bad status number: %(arg)s')) + + if args: + usage(1) lineno = 0 statuscnt = 0 @@ -121,7 +164,7 @@ def main(): elif output: # Any old line sys.stdout.write(line) - if status and status > 0 and (lineno % status) == 0: + if status > 0 and (lineno % status) == 0: sys.stderr.write('#') statuscnt += 1 if statuscnt > 50: @@ -131,5 +174,6 @@ def main(): print(C_('%(messages)d messages found'), file=sys.stderr) + if __name__ == '__main__': main() diff --git a/bin/clone_member b/bin/clone_member index e0d6c65d..6b015335 100755 --- a/bin/clone_member +++ b/bin/clone_member @@ -66,7 +66,7 @@ Where: """ import sys -import argparse +import getopt import paths from Mailman import MailList @@ -75,6 +75,19 @@ from Mailman import Errors from Mailman.i18n import C_ + +def usage(code, msg=''): + if code: + fd = sys.stderr + else: + fd = sys.stdout + print(C_(__doc__), file=fd) + if msg: + print(fd, msg, file=fd) + sys.exit(code) + + + def dolist(mlist, options): SPACE = ' ' if not options.quiet: @@ -97,6 +110,7 @@ def dolist(mlist, options): if foundp: newowners[options.toaddr] = 1 newowners = newowners.keys() + newowners = list(newowners) newowners.sort() if options.modify: mlist.owner = newowners @@ -138,57 +152,75 @@ def dolist(mlist, options): print(C_(' original address removed:'), options.fromaddr) -def parse_args(): - parser = argparse.ArgumentParser(description='Clone a member address.') - parser.add_argument('-l', '--listname', action='append', - help='Check and modify only the named mailing lists') - parser.add_argument('-r', '--remove', action='store_true', - help='Remove the old address from the mailing list after it\'s been cloned') - parser.add_argument('-a', '--admin', action='store_true', - help='Scan the list admin addresses for the old address, and clone or change them too') - parser.add_argument('-q', '--quiet', action='store_true', - help='Do the modifications quietly') - parser.add_argument('-n', '--nomodify', action='store_true', - help='Print what would be done, but don\'t actually do it') - parser.add_argument('fromaddr', - help='The old address of the user') - parser.add_argument('toaddr', - help='The new address of the user') - return parser.parse_args() - - + def main(): - args = parse_args() - + # default options + class Options: + listnames = None + remove = 0 + admintoo = 0 + quiet = 0 + modify = 1 + + # scan sysargs + try: + opts, args = getopt.getopt( + sys.argv[1:], 'arl:qnh', + ['admin', 'remove', 'listname=', 'quiet', 'nomodify', 'help']) + except getopt.error as msg: + usage(1, msg) + + options = Options() + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-q', '--quiet'): + options.quiet = 1 + elif opt in ('-n', '--nomodify'): + options.modify = 0 + elif opt in ('-a', '--admin'): + options.admintoo = 1 + elif opt in ('-r', '--remove'): + options.remove = 1 + elif opt in ('-l', '--listname'): + if options.listnames is None: + options.listnames = [] + options.listnames.append(arg.lower()) + + # further options and argument processing + if not options.modify: + options.quiet = 0 + + if len(args) != 2: + usage(1) + fromaddr = args[0] + toaddr = args[1] + # validate and normalize the target address try: - Utils.ValidateEmail(args.toaddr) + Utils.ValidateEmail(toaddr) except Errors.EmailAddressError: - print(C_('Invalid email address:'), args.toaddr, file=sys.stderr) - sys.exit(1) + usage(1, C_('Not a valid email address: %(toaddr)s')) + lfromaddr = fromaddr.lower() + options.toaddr = toaddr + options.fromaddr = fromaddr + options.lfromaddr = lfromaddr - # normalize the addresses - args.lfromaddr = args.fromaddr.lower() - args.toaddr = args.toaddr.lower() + if options.listnames is None: + options.listnames = Utils.list_names() - # get the list of lists to process - if args.listname: - listnames = args.listname - else: - listnames = Utils.list_names() - - # process each list - for listname in listnames: + for listname in options.listnames: try: - mlist = MailList(listname, lock=0) - except Errors.MMUnknownListError: - print(C_('Unknown list:'), listname, file=sys.stderr) + mlist = MailList.MailList(listname) + except Errors.MMListError as e: + print(C_('Error opening list "%(listname)s", skipping.\n%(e)s')) continue try: - dolist(mlist, args) + dolist(mlist, options) finally: + mlist.Save() mlist.Unlock() - + if __name__ == '__main__': main() diff --git a/bin/config_list b/bin/config_list index 86600d04..65daca30 100644 --- a/bin/config_list +++ b/bin/config_list @@ -63,10 +63,9 @@ The options -o and -i are mutually exclusive. """ import sys -import argparse import re import time -import logging +import getopt import paths from Mailman import mm_cfg @@ -77,13 +76,6 @@ from Mailman import i18n from typing import Tuple -# Set up logging -logging.basicConfig( - level=logging.DEBUG, - format='%(asctime)s - %(levelname)s - %(message)s', - filename='/tmp/mailman_config_list.log' -) - _ = i18n._ C_ = i18n.C_ @@ -91,6 +83,7 @@ NL = '\n' nonasciipat = re.compile(r'[\x80-\xff]') + def usage(code, msg=''): if code: fd = sys.stderr @@ -102,6 +95,7 @@ def usage(code, msg=''): sys.exit(code) + def do_output(listname, outfile): closep = 0 try: @@ -224,6 +218,7 @@ def do_list_categories(mlist, k, subcat, outfp): print(file=outfp) + def getPropertyMap(mlist): guibyprop = {} categories = mlist.GetConfigCategories() @@ -264,241 +259,108 @@ def do_input(listname, infile, checkonly, verbose): savelist = 0 guibyprop = getPropertyMap(mlist) try: - # Read the input file and parse it - with open(infile) as fp: - config = {} - for line in fp: - line = line.strip() - if line and not line.startswith('#'): - key, value = line.split('=', 1) - config[key.strip()] = value.strip() - - # Get configuration items using GetConfigInfo() - for category in mm_cfg.ADMIN_CATEGORIES: - subcats = mlist.GetConfigSubCategories(category) - if subcats is None: - info = mlist.GetConfigInfo(category, None) - if info: - for data in info[1:]: - if not isinstance(data, Tuple): - continue - key = data[0] - if key in config: - if verbose: - print(C_('attribute "%(key)s" changed') % {'key': key}, file=sys.stderr) - missing = [] - gui, wtype = guibyprop.get(key, (missing, missing)) - if gui is missing: - # This isn't an official property of the list, but that's - # okay, we'll just restore it the old fashioned way - print(C_('Non-standard property restored: %(key)s') % {'key': key}, file=sys.stderr) - setattr(mlist, key, config[key]) - else: - # BAW: This uses non-public methods. This logic taken from - # the guts of GUIBase.handleForm(). - try: - validval = gui._getValidValue(mlist, key, wtype, config[key]) - except ValueError: - print(C_('Invalid value for property: %(key)s') % {'key': key}, file=sys.stderr) - except Errors.EmailAddressError: - print(C_('Bad email address for option %(key)s: %(value)s') % - {'key': key, 'value': config[key]}, file=sys.stderr) - else: - # BAW: Horrible hack, but then this is special cased - # everywhere anyway. :( Privacy._setValue() knows that - # when ALLOW_OPEN_SUBSCRIBE is false, the web values are - # 0, 1, 2 but these really should be 1, 2, 3, so it adds - # one. But we really do provide [0..3] so we need to undo - # the hack that _setValue adds. :( :( - if key == 'subscribe_policy' and \ - not mm_cfg.ALLOW_OPEN_SUBSCRIBE: - validval -= 1 - # BAW: Another horrible hack. This one is just too hard - # to fix in a principled way in Mailman 2.1 - elif key == 'new_member_options': - # Because this is a Checkbox, _getValidValue() - # transforms the value into a list of one item. - validval = validval[0] - validval = [bitfield for bitfield, bitval - in list(mm_cfg.OPTINFO.items()) - if validval & bitval] - gui._setValue(mlist, key, validval, fakedoc) - else: - for subcat, _ in subcats: - info = mlist.GetConfigInfo(category, subcat) - if info: - for data in info[1:]: - if not isinstance(data, Tuple): - continue - key = data[0] - if key in config: - if verbose: - print(C_('attribute "%(key)s" changed') % {'key': key}, file=sys.stderr) - missing = [] - gui, wtype = guibyprop.get(key, (missing, missing)) - if gui is missing: - # This isn't an official property of the list, but that's - # okay, we'll just restore it the old fashioned way - print(C_('Non-standard property restored: %(key)s') % {'key': key}, file=sys.stderr) - setattr(mlist, key, config[key]) - else: - # BAW: This uses non-public methods. This logic taken from - # the guts of GUIBase.handleForm(). - try: - validval = gui._getValidValue(mlist, key, wtype, config[key]) - except ValueError: - print(C_('Invalid value for property: %(key)s') % {'key': key}, file=sys.stderr) - except Errors.EmailAddressError: - print(C_('Bad email address for option %(key)s: %(value)s') % - {'key': key, 'value': config[key]}, file=sys.stderr) - else: - # BAW: Horrible hack, but then this is special cased - # everywhere anyway. :( Privacy._setValue() knows that - # when ALLOW_OPEN_SUBSCRIBE is false, the web values are - # 0, 1, 2 but these really should be 1, 2, 3, so it adds - # one. But we really do provide [0..3] so we need to undo - # the hack that _setValue adds. :( :( - if key == 'subscribe_policy' and \ - not mm_cfg.ALLOW_OPEN_SUBSCRIBE: - validval -= 1 - # BAW: Another horrible hack. This one is just too hard - # to fix in a principled way in Mailman 2.1 - elif key == 'new_member_options': - # Because this is a Checkbox, _getValidValue() - # transforms the value into a list of one item. - validval = validval[0] - validval = [bitfield for bitfield, bitval - in list(mm_cfg.OPTINFO.items()) - if validval & bitval] - gui._setValue(mlist, key, validval, fakedoc) + globals = {'mlist': mlist} + # Any exception that occurs in execfile() will cause the list to not + # be saved, but any other problems are not save-fatal. + exec(open(infile).read(), globals) savelist = 1 + for k, v in list(globals.items()): + if k in ('mlist', '__builtins__'): + continue + if not hasattr(mlist, k): + print(C_('attribute "%(k)s" ignored'), file=sys.stderr) + continue + if verbose: + print(C_('attribute "%(k)s" changed'), file=sys.stderr) + missing = [] + gui, wtype = guibyprop.get(k, (missing, missing)) + if gui is missing: + # This isn't an official property of the list, but that's + # okay, we'll just restore it the old fashioned way + print(C_( + 'Non-standard property restored: %(k)s'), file=sys.stderr) + setattr(mlist, k, v) + else: + # BAW: This uses non-public methods. This logic taken from + # the guts of GUIBase.handleForm(). + try: + validval = gui._getValidValue(mlist, k, wtype, v) + except ValueError: + print(C_( + 'Invalid value for property: %(k)s'), file=sys.stderr) + except Errors.EmailAddressError: + print(C_( + 'Bad email address for option %(k)s: %(v)s'), file=sys.stderr) + else: + # BAW: Horrible hack, but then this is special cased + # everywhere anyway. :( Privacy._setValue() knows that + # when ALLOW_OPEN_SUBSCRIBE is false, the web values are + # 0, 1, 2 but these really should be 1, 2, 3, so it adds + # one. But we really do provide [0..3] so we need to undo + # the hack that _setValue adds. :( :( + if k == 'subscribe_policy' and \ + not mm_cfg.ALLOW_OPEN_SUBSCRIBE: + validval -= 1 + # BAW: Another horrible hack. This one is just too hard + # to fix in a principled way in Mailman 2.1 + elif k == 'new_member_options': + # Because this is a Checkbox, _getValidValue() + # transforms the value into a list of one item. + validval = validval[0] + validval = [bitfield for bitfield, bitval + in list(mm_cfg.OPTINFO.items()) + if validval & bitval] + gui._setValue(mlist, k, validval, fakedoc) + # BAW: when to do gui._postValidate()??? finally: if savelist and not checkonly: mlist.Save() mlist.Unlock() + def main(): - logging.debug("Starting config_list") - parser = argparse.ArgumentParser(description='Configure a mailing list.') - parser.add_argument('listname', help='Name of the mailing list') - parser.add_argument('-i', '--input-file', help='File containing configuration') - parser.add_argument('-o', '--output-file', help='File to write configuration to') - parser.add_argument('-a', '--all', action='store_true', - help='Show all configuration options') - parser.add_argument('-v', '--verbose', action='store_true', - help='Show verbose output') - parser.add_argument('-c', '--category', help='Show options in specific category') - parser.add_argument('-s', '--subcategory', help='Show options in specific subcategory') - - args = parser.parse_args() - logging.debug(f"Parsed arguments: {args}") - - try: - logging.debug(f"Attempting to load list: {args.listname}") - mlist = MailList.MailList(args.listname, lock=1) - logging.debug("Successfully loaded list") - except Errors.MMUnknownListError: - logging.error(f"List not found: {args.listname}") - usage(1, _('No such list "%(listname)s"')) - return - try: - logging.debug("Getting configuration categories") - categories = mlist.GetConfigCategories() - if not categories: - logging.error("No configuration categories found") - print(_("No configuration categories available")) - return - - logging.debug(f"Got categories: {list(categories.keys())}") - - # Get configuration items using GetConfigInfo() - for category in mm_cfg.ADMIN_CATEGORIES: - logging.debug(f"Processing category: {category}") - if category not in categories: - logging.warning(f"Category {category} not found in available categories") - continue - - subcats = mlist.GetConfigSubCategories(category) - logging.debug(f"Got subcategories: {subcats}") - - if subcats is None: - logging.debug(f"Getting config info for category {category}") - info = mlist.GetConfigInfo(category, None) - if not info: - logging.warning(f"No configuration info found for category {category}") - continue - - logging.debug(f"Got config info: {info is not None}") - for data in info[1:]: - if not isinstance(data, Tuple): - continue - try: - key = data[0] - if not args.all and key.startswith('_'): - continue - if args.category and not key.startswith(args.category + '_'): - continue - if args.subcategory and not key.startswith(args.category + '_' + args.subcategory + '_'): - continue - - # Use getattr with a default value instead of direct access - value = getattr(mlist, key, None) - if value is None: - logging.warning(f"Configuration item {key} not found") - continue - - if args.verbose: - print(f"{key}={value}") - else: - print(key) - except Exception as e: - logging.error(f"Error processing configuration item: {str(e)}") - continue - else: - for subcat, _ in subcats: - logging.debug(f"Getting config info for category {category}, subcategory {subcat}") - info = mlist.GetConfigInfo(category, subcat) - if not info: - logging.warning(f"No configuration info found for category {category}, subcategory {subcat}") - continue - - logging.debug(f"Got config info: {info is not None}") - for data in info[1:]: - if not isinstance(data, Tuple): - continue - try: - key = data[0] - if not args.all and key.startswith('_'): - continue - if args.category and not key.startswith(args.category + '_'): - continue - if args.subcategory and not key.startswith(args.category + '_' + args.subcategory + '_'): - continue - - # Use getattr with a default value instead of direct access - value = getattr(mlist, key, None) - if value is None: - logging.warning(f"Configuration item {key} not found") - continue - - if args.verbose: - print(f"{key}={value}") - else: - print(key) - except Exception as e: - logging.error(f"Error processing configuration item: {str(e)}") - continue - - except Exception as e: - logging.error(f"Error occurred: {str(e)}", exc_info=True) - raise - finally: - logging.debug("Unlocking list") - mlist.Unlock() - logging.debug("Finished config_list") + opts, args = getopt.getopt( + sys.argv[1:], 'ci:o:vh', + ['checkonly', 'inputfile=', 'outputfile=', 'verbose', 'help']) + except getopt.error as msg: + usage(1, msg) + + # defaults + infile = None + outfile = None + checkonly = 0 + verbose = 0 + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-o', '--outputfile'): + outfile = arg + elif opt in ('-i', '--inputfile'): + infile = arg + elif opt in ('-c', '--checkonly'): + checkonly = 1 + elif opt in ('-v', '--verbose'): + verbose = 1 + + # sanity check + if infile is not None and outfile is not None: + usage(1, C_('Only one of -i or -o is allowed')) + if infile is None and outfile is None: + usage(1, C_('One of -i or -o is required')) + + # get the list name + if len(args) != 1: + usage(1, C_('List name is required')) + listname = args[0].lower().strip() + + if outfile: + do_output(listname, outfile) + else: + do_input(listname, infile, checkonly, verbose) + if __name__ == '__main__': main() diff --git a/bin/discard b/bin/discard index 333d0be9..2e190def 100644 --- a/bin/discard +++ b/bin/discard @@ -36,7 +36,7 @@ Options: import os import re import sys -import argparse +import getopt import paths from Mailman import mm_cfg @@ -46,19 +46,33 @@ from Mailman.i18n import C_ cre = re.compile(r'heldmsg-(?P.*)-(?P[0-9]+)\.(pck|txt)$') -def parse_args(): - parser = argparse.ArgumentParser(description='Discard held messages.') - parser.add_argument('-q', '--quiet', action='store_true', - help='Don\'t print status messages') - parser.add_argument('files', nargs='*', - help='Files containing held messages to discard') - return parser.parse_args() + +def usage(code, msg=''): + if code: + fd = sys.stderr + else: + fd = sys.stdout + print(C_(__doc__), file=fd) + if msg: + print(msg, file=fd) + sys.exit(code) + def main(): - args = parse_args() - - files = args.files + try: + opts, args = getopt.getopt(sys.argv[1:], 'hq', ['help', 'quiet']) + except getopt.error as msg: + usage(1, msg) + + quiet = False + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-q', '--quiet'): + quiet = True + + files = args if not files: print(C_('Nothing to do.')) @@ -88,7 +102,7 @@ def main(): for id in ids: # No comment, no preserve, no forward, no forwarding address mlist.HandleRequest(id, mm_cfg.DISCARD, '', False, False, '') - if not args.quiet: + if not quiet: print(C_( 'Discarded held msg #%(id)s for list %(listname)s')) mlist.Save() @@ -96,5 +110,6 @@ def main(): mlist.Unlock() + if __name__ == '__main__': main() diff --git a/bin/dumpdb b/bin/dumpdb index a71b8ef4..7d8ac590 100644 --- a/bin/dumpdb +++ b/bin/dumpdb @@ -1,4 +1,4 @@ -#! @PYTHON@ +#! /usr/bin/python3 # # Copyright (C) 1998-2018 by the Free Software Foundation, Inc. # @@ -46,7 +46,7 @@ Python pickle. In either case, if you want to override the default assumption """ import sys -import argparse +import getopt import pprint import pickle import marshal @@ -54,50 +54,61 @@ import marshal import paths # Import this /after/ paths so that the sys.path is properly hacked from Mailman.i18n import C_ +from Mailman import Utils PROGRAM = sys.argv[0] COMMASPACE = ', ' - -def parse_args(): - parser = argparse.ArgumentParser(description='Dump the contents of any Mailman `database\' file.') - group = parser.add_mutually_exclusive_group() - group.add_argument('-m', '--marshal', action='store_true', - help='Assume the file contains a Python marshal') - group.add_argument('-p', '--pickle', action='store_true', - help='Assume the file contains a Python pickle') - parser.add_argument('-n', '--noprint', action='store_true', - help='Don\'t attempt to pretty print the object') - parser.add_argument('filename', - help='The database file to dump') - return parser.parse_args() - - -def load_pickle(fp): - """Load a pickle file with Python 2/3 compatibility.""" - try: - return pickle.load(fp, fix_imports=True, encoding='latin1') - except Exception as e: - print('Error loading pickle file: %s' % e) - return None + +def usage(code, msg=''): + if code: + fd = sys.stderr + else: + fd = sys.stdout + print(C_(__doc__) % globals(), file=fd) + if msg: + print(msg, file=fd) + sys.exit(code) + def main(): - args = parse_args() - - # Determine file type - if args.marshal: - filetype = 1 # marshal - elif args.pickle: - filetype = 0 # pickle + try: + opts, args = getopt.getopt(sys.argv[1:], 'mphn', + ['marshal', 'pickle', 'help', 'noprint']) + except getopt.error as msg: + usage(1, msg) + + # Options. + # None == guess, 0 == pickle, 1 == marshal + filetype = None + doprint = True + + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-p', '--pickle'): + filetype = 0 + elif opt in ('-m', '--marshal'): + filetype = 1 + elif opt in ('-n', '--noprint'): + doprint = False + + if len(args) < 1: + usage(1, C_('No filename given.')) + elif len(args) > 1: + pargs = COMMASPACE.join(args) + usage(1, C_('Bad arguments: %(pargs)s')) else: - if args.filename.endswith('.db'): - filetype = 1 # marshal - elif args.filename.endswith('.pck'): - filetype = 0 # pickle + filename = args[0] + + if filetype is None: + if filename.endswith('.db'): + filetype = 1 + elif filename.endswith('.pck'): + filetype = 0 else: - print(C_('Please specify either -p or -m.'), file=sys.stderr) - sys.exit(1) + usage(1, C_('Please specify either -p or -m.')) # Handle dbs pp = pprint.PrettyPrinter(indent=4) @@ -105,41 +116,29 @@ def main(): load = marshal.load typename = 'marshal' else: - load = load_pickle + load = pickle.load typename = 'pickle' - fp = open(args.filename, 'rb') + fp = open(filename, 'rb') m = [] try: cnt = 1 - if not args.noprint: + if doprint: print(C_('[----- start %(typename)s file -----]')) while True: try: - obj = load(fp) - # Handle string/bytes conversion - if isinstance(obj, bytes): - obj = obj.decode('utf-8', 'replace') - elif isinstance(obj, dict): - new_obj = {} - for k, v in obj.items(): - if isinstance(k, bytes): - k = k.decode('utf-8', 'replace') - if isinstance(v, bytes): - v = v.decode('utf-8', 'replace') - new_obj[k] = v - obj = new_obj - elif isinstance(obj, list): - new_obj = [] - for item in obj: - if isinstance(item, bytes): - item = item.decode('utf-8', 'replace') - new_obj.append(item) - obj = new_obj + if typename == 'pickle': + obj = Utils.load_pickle(fp) + if obj is None: + if doprint: + print(C_('[----- end %(typename)s file -----]')) + break + else: + obj = load(fp, encoding='utf-8') except EOFError: - if not args.noprint: + if doprint: print(C_('[----- end %(typename)s file -----]')) break - if not args.noprint: + if doprint: print(C_('<----- start object %(cnt)s ----->')) if isinstance(obj, str): print(obj) @@ -152,5 +151,6 @@ def main(): return m + if __name__ == '__main__': msg = main() diff --git a/bin/export.py b/bin/export.py index 16bf8b06..f9ff5f0e 100644 --- a/bin/export.py +++ b/bin/export.py @@ -26,7 +26,6 @@ import codecs import datetime import optparse -import pickle from xml.sax.saxutils import escape @@ -103,8 +102,6 @@ def _makeattrs(self, tagattrs): if v is None: v = '' else: - if isinstance(v, bytes): - v = v.decode('utf-8', 'replace') v = escape(str(v)) attrs.append('%s="%s"' % (k, v)) return SPACE.join(attrs) @@ -149,8 +146,6 @@ def _element(self, _name, _value=None, **_attributes): if _value is None: print('<%s%s/>' % (_name, attrs), file=self._fp) else: - if isinstance(_value, bytes): - _value = _value.decode('utf-8', 'replace') value = escape(str(_value)) print('<%s%s>%s' % (_name, attrs, value, _name), file=self._fp) @@ -184,13 +179,9 @@ def _do_list_categories(self, mlist, k, subcat=None): if isinstance(value, list): self._push_element('option', name=varname, type=widget_type) for v in value: - if isinstance(v, bytes): - v = v.decode('utf-8', 'replace') self._element('value', v) self._pop_element('option') else: - if isinstance(value, bytes): - value = value.decode('utf-8', 'replace') self._element('option', value, name=varname, type=widget_type) def _dump_list(self, mlist, password_scheme): @@ -268,29 +259,6 @@ def _dump_list(self, mlist, password_scheme): self._pop_element('roster') self._pop_element('list') - def _do_list_archives(self, mlist): - # Get the archive directory - archive_dir = os.path.join(mlist.archive_dir(), 'private') - if not os.path.exists(archive_dir): - return - # Get all the archive files - for filename in os.listdir(archive_dir): - if filename.endswith('.mbox'): - if isinstance(filename, bytes): - filename = filename.decode('utf-8', 'replace') - self._push_element('archive', filename=filename) - # Get the archive file's metadata - metadata_file = os.path.join(archive_dir, filename + '.metadata') - if os.path.exists(metadata_file): - metadata = self.load_metadata(metadata_file) - for key, value in metadata.items(): - if isinstance(key, bytes): - key = key.decode('utf-8', 'replace') - if isinstance(value, bytes): - value = value.decode('utf-8', 'replace') - self._element('metadata', str(value), name=key) - self._pop_element('archive') - def dump(self, listnames, password_scheme): print('', file=self._fp) self._push_element('mailman', **{ @@ -304,24 +272,12 @@ def dump(self, listnames, password_scheme): print(C_('No such list: %(listname)s'), file=sys.stderr) continue self._dump_list(mlist, password_scheme) - self._do_list_archives(mlist) self._pop_element('mailman') def close(self): while self._stack: self._pop_element() - def load_metadata(self, filename): - """Load metadata from a pickle file.""" - try: - with open(filename, 'rb') as fp: - # Use protocol 2 for Python 2/3 compatibility - metadata = pickle.load(fp, fix_imports=True, encoding='latin1') - return metadata - except Exception as e: - print('Error loading metadata from %s: %s' % (filename, e)) - return None - def no_password(password): @@ -333,15 +289,19 @@ def plaintext_password(password): def sha_password(password): + if isinstance(password, str): + password = password.encode() h = Utils.sha_new(password) - return '{SHA}' + base64.b64encode(h.digest()) + return '{SHA}' + base64.b64encode(h.digest()).decode('utf-8') def ssha_password(password): + if isinstance(password, str): + password = password.encode() salt = os.urandom(SALT_LENGTH) h = Utils.sha_new(password) h.update(salt) - return '{SSHA}' + base64.b64encode(h.digest() + salt) + return '{SSHA}' + base64.b64encode(h.digest() + salt).decode('utf-8') SCHEMES = { diff --git a/bin/find_member b/bin/find_member index bcb73ccb..25c5b1e2 100755 --- a/bin/find_member +++ b/bin/find_member @@ -49,7 +49,7 @@ specifically excluded. Regular expression syntax is Perl5-like, using the Python re module. Complete specifications are at: -https://docs.python.org/3/library/re.html +https://docs.python.org/2/library/re.html Address matches are case-insensitive, but case-preserved addresses are displayed. @@ -59,7 +59,7 @@ from builtins import * from builtins import object import sys import re -import argparse +import getopt import paths from Mailman import Utils @@ -71,6 +71,7 @@ AS_MEMBER = 0x01 AS_OWNER = 0x02 + def usage(code, msg=''): if code: fd = sys.stderr @@ -82,6 +83,7 @@ def usage(code, msg=''): sys.exit(code) + def scanlists(options): cres = [] for r in options.regexps: @@ -118,34 +120,46 @@ def scanlists(options): return matches + class Options(object): listnames = Utils.list_names() owners = None def main(): - parser = argparse.ArgumentParser(description='Find all lists that a member\'s address is on.') - parser.add_argument('regexps', nargs='+', help='Python regular expression to match against') - parser.add_argument('-l', '--listname', action='append', - help='Include only the named list in the search') - parser.add_argument('-x', '--exclude', action='append', - help='Exclude the named list from the search') - parser.add_argument('-w', '--owners', action='store_true', - help='Search list owners as well as members') - - args = parser.parse_args() + try: + opts, args = getopt.getopt(sys.argv[1:], 'l:x:wh', + ['listname=', 'exclude=', 'owners', + 'help']) + except getopt.error as msg: + usage(1, msg) options = Options() - if args.listname: - options.listnames = [name.lower() for name in args.listname] - if args.exclude: - for ex in args.exclude: - try: - options.listnames.remove(ex.lower()) - except ValueError: - pass - options.owners = args.owners - options.regexps = args.regexps + loptseen = 0 + excludes = [] + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-l', '--listname'): + if not loptseen: + options.listnames = [] + loptseen = 1 + options.listnames.append(arg.lower()) + elif opt in ('-x', '--exclude'): + excludes.append(arg.lower()) + elif opt in ('-w', '--owners'): + options.owners = 1 + + for ex in excludes: + try: + options.listnames.remove(ex) + except ValueError: + pass + + if not args: + usage(1, C_('Search regular expression required')) + + options.regexps = args if not options.listnames: print(C_('No lists to search')) @@ -166,5 +180,6 @@ def main(): print(' ', name, C_('(as owner)')) + if __name__ == '__main__': main() diff --git a/bin/fix_url.py b/bin/fix_url.py index dce6a8ba..243f4f20 100644 --- a/bin/fix_url.py +++ b/bin/fix_url.py @@ -40,22 +40,14 @@ from __future__ import print_function import sys -import argparse +import getopt import paths from Mailman import mm_cfg from Mailman.i18n import C_ -def parse_args(args): - parser = argparse.ArgumentParser(description='Reset a list\'s web_page_url attribute to the default setting.') - parser.add_argument('-u', '--urlhost', - help='Look up urlhost in the virtual host table and set the web_page_url and host_name attributes') - parser.add_argument('-v', '--verbose', action='store_true', - help='Print what the script is doing') - return parser.parse_args(args) - - + def usage(code, msg=''): print(C_(__doc__.replace('%', '%%'))) if msg: @@ -63,28 +55,37 @@ def usage(code, msg=''): sys.exit(code) + def fix_url(mlist, *args): try: - args = parse_args(args) - except SystemExit: - usage(1) + opts, args = getopt.getopt(args, 'u:v', ['urlhost=', 'verbose']) + except getopt.error as msg: + usage(1, msg) + + verbose = 0 + urlhost = mailhost = None + for opt, arg in opts: + if opt in ('-u', '--urlhost'): + urlhost = arg + elif opt in ('-v', '--verbose'): + verbose = 1 # Make sure list is locked. if not mlist.Locked(): - if args.verbose: + if verbose: print(C_('Locking list')) mlist.Lock() - if args.urlhost: - web_page_url = mm_cfg.DEFAULT_URL_PATTERN % args.urlhost - mailhost = mm_cfg.VIRTUAL_HOSTS.get(args.urlhost.lower(), args.urlhost) + if urlhost: + web_page_url = mm_cfg.DEFAULT_URL_PATTERN % urlhost + mailhost = mm_cfg.VIRTUAL_HOSTS.get(urlhost.lower(), urlhost) else: web_page_url = mm_cfg.DEFAULT_URL_PATTERN % mm_cfg.DEFAULT_URL_HOST mailhost = mm_cfg.DEFAULT_EMAIL_HOST - if args.verbose: + if verbose: print(C_('Setting web_page_url to: %(web_page_url)s')) mlist.web_page_url = web_page_url - if args.verbose: + if verbose: print(C_('Setting host_name to: %(mailhost)s')) mlist.host_name = mailhost print('Saving list') @@ -92,5 +93,6 @@ def fix_url(mlist, *args): mlist.Unlock() + if __name__ == '__main__': usage(0) diff --git a/bin/genaliases b/bin/genaliases index dfedc8db..b8cca103 100644 --- a/bin/genaliases +++ b/bin/genaliases @@ -34,7 +34,7 @@ Options: import os import sys -import argparse +import getopt import paths # path hacking from Mailman import mm_cfg @@ -42,14 +42,7 @@ from Mailman import Utils from Mailman import MailList from Mailman.i18n import C_ - -def parse_args(): - parser = argparse.ArgumentParser(description='Regenerate Mailman specific aliases from scratch.') - parser.add_argument('-q', '--quiet', action='store_true', - help='Reduce verbosity of MTA output') - return parser.parse_args() - - + def usage(code, msg=''): if code: fd = sys.stderr @@ -61,10 +54,22 @@ def usage(code, msg=''): sys.exit(code) + def main(): + quiet = False try: - args = parse_args() - except SystemExit: + opts, args = getopt.getopt(sys.argv[1:], 'hq', + ['help', 'quiet']) + except getopt.error as msg: + usage(1, msg) + + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-q', '--quiet'): + quiet = True + + if args: usage(1) if not mm_cfg.MTA: @@ -93,13 +98,13 @@ def main(): try: MTA.clear() if not mlists: - MTA.create(None, nolock=True, quiet=args.quiet) + MTA.create(None, nolock=True, quiet=quiet) else: for hostname, vlists in mlists.items(): for mlist in vlists: - MTA.create(mlist, nolock=True, quiet=args.quiet) + MTA.create(mlist, nolock=True, quiet=quiet) # Be verbose for only the first printed list - args.quiet = True + quiet = True finally: lock.unlock(unconditionally=True) # Postfix has not been updating the maps. This call will do it. @@ -107,5 +112,6 @@ def main(): os.umask(omask) + if __name__ == '__main__': main() diff --git a/bin/inject b/bin/inject index 5c67100c..2245f778 100644 --- a/bin/inject +++ b/bin/inject @@ -43,7 +43,7 @@ from __future__ import print_function import sys import os -import argparse +import getopt import paths from Mailman import mm_cfg @@ -52,40 +52,58 @@ from Mailman import Post from Mailman.i18n import C_ -def parse_args(): - parser = argparse.ArgumentParser(description='Inject a message from a file into Mailman\'s incoming queue.') - parser.add_argument('-l', '--listname', required=True, - help='The name of the list to inject this message to') - parser.add_argument('-q', '--queue', - help='The name of the queue to inject the message to') - parser.add_argument('filename', nargs='?', - help='The name of the plaintext message file to inject') - return parser.parse_args() + +def usage(code, msg=''): + if code: + fd = sys.stderr + else: + fd = sys.stdout + print(C_(__doc__), file=fd) + if msg: + print(msg, file=fd) + sys.exit(code) + def main(): - args = parse_args() + try: + opts, args = getopt.getopt( + sys.argv[1:], 'hl:q:L', + ['help', 'listname=', 'queue=', 'showqnames']) + except getopt.error as msg: + usage(1, msg) qdir = mm_cfg.INQUEUE_DIR - if args.queue: - qdir = os.path.join(mm_cfg.QUEUE_DIR, args.queue) - if not os.path.isdir(qdir): - print(C_('Bad queue directory: %(qdir)s'), file=sys.stderr) - sys.exit(1) - - listname = args.listname.lower() - if not Utils.list_exists(listname): - print(C_('No such list: %(listname)s'), file=sys.stderr) - sys.exit(1) - - if args.filename: - with open(args.filename) as fp: - msgtext = fp.read() - else: + listname = None + + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-q', '--queue'): + qdir = os.path.join(mm_cfg.QUEUE_DIR, arg) + if not os.path.isdir(qdir): + usage(1, C_('Bad queue directory: %(qdir)s')) + elif opt in ('-l', '--listname'): + listname = arg.lower() + + if listname is None: + usage(1, C_('A list name is required')) + elif not Utils.list_exists(listname): + usage(1, C_('No such list: %(listname)s')) + + if len(args) == 0: + # Use standard input msgtext = sys.stdin.read() + elif len(args) == 1: + fp = open(args[0]) + msgtext = fp.read() + fp.close() + else: + usage(1) Post.inject(listname, msgtext, qdir=qdir) + if __name__ == '__main__': main() diff --git a/bin/list_admins b/bin/list_admins index f764b9a1..57fde6df 100644 --- a/bin/list_admins +++ b/bin/list_admins @@ -41,7 +41,7 @@ have more than one named list on the command line. from __future__ import print_function import sys -import argparse +import getopt import paths from Mailman import MailList, Utils @@ -53,6 +53,7 @@ COMMASPACE = ', ' program = sys.argv[0] + def usage(code, msg=''): if code: fd = sys.stderr @@ -64,40 +65,39 @@ def usage(code, msg=''): sys.exit(code) + def main(): - parser = argparse.ArgumentParser(description='List all the owners of a mailing list.') - parser.add_argument('listnames', nargs='*', help='Name(s) of the mailing list(s) to print the owners of') - parser.add_argument('-v', '--all-vhost', - help='List the owners of all the mailing lists for the given virtual host') - parser.add_argument('-a', '--all', action='store_true', - help='List the owners of all the mailing lists on this system') - - args = parser.parse_args() - - listnames = [x.lower() for x in args.listnames] - if args.all: - listnames = Utils.list_names() - elif args.all_vhost: - listnames = Utils.list_names() + try: + opts, args = getopt.getopt(sys.argv[1:], 'hv:a', + ['help', 'all-vhost=', 'all']) + except getopt.error as msg: + usage(1, msg) + + listnames = [x.lower() for x in args] + vhost = None + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-a', '--all'): + listnames = Utils.list_names() + elif opt in ('-v', '--all-vhost'): + listnames = Utils.list_names() + vhost = arg for listname in listnames: - # Ensure listname is a string - if isinstance(listname, bytes): - listname = listname.decode('utf-8', 'replace') - try: - mlist = MailList.MailList(listname, lock=0) - except Errors.MMListError: - print('No such list: %s' % listname) - continue + try: + mlist = MailList.MailList(listname, lock=0) + except Errors.MMListError as e: + print(C_('No such list: %(listname)s')) + continue - if args.all_vhost and args.all_vhost != mlist.host_name: - continue + if vhost and vhost != mlist.host_name: + continue - # Ensure owners are strings - owners = [owner.decode('utf-8', 'replace') if isinstance(owner, bytes) else owner for owner in mlist.owner] - owners_str = ', '.join(owners) - print('List: %s, \tOwners: %s' % (listname, owners_str)) + owners = COMMASPACE.join(mlist.owner) + print(C_('List: %(listname)s, \tOwners: %(owners)s')) + if __name__ == '__main__': main() diff --git a/bin/list_lists b/bin/list_lists old mode 100755 new mode 100644 index aba50caa..4b286f38 --- a/bin/list_lists +++ b/bin/list_lists @@ -44,7 +44,7 @@ Where: import re import sys -import argparse +import getopt import paths from Mailman import mm_cfg @@ -66,18 +66,31 @@ def usage(code, msg=''): sys.exit(code) + def main(): - parser = argparse.ArgumentParser(description='List all mailing lists.') - parser.add_argument('-a', '--advertised', action='store_true', - help='List only those mailing lists that are publically advertised') - parser.add_argument('-p', '--public-archive', action='store_true', - help='List only those lists with public archives') - parser.add_argument('-V', '--virtual-host-overview', - help='List only those mailing lists that are homed to the given virtual domain') - parser.add_argument('-b', '--bare', action='store_true', - help='Displays only the list name, with no description') - - args = parser.parse_args() + try: + opts, args = getopt.getopt(sys.argv[1:], 'apbV:h', + ['advertised', 'public-archive', 'bare', + 'virtual-host-overview=', + 'help']) + except getopt.error as msg: + usage(1, msg) + + advertised = 0 + public = 0 + vhost = None + bare = 0 + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-a', '--advertised'): + advertised = 1 + elif opt in ('-p', '--public-archive'): + public = 1 + elif opt in ('-V', '--virtual-host-overview'): + vhost = arg + elif opt in ('-b', '--bare'): + bare = 1 names = Utils.list_names() names.sort() @@ -85,53 +98,39 @@ def main(): mlists = [] longest = 0 for n in names: - # Ensure name is a string - if isinstance(n, bytes): - n = n.decode('utf-8', 'replace') try: mlist = MailList.MailList(n, lock=0) except Errors.MMUnknownListError: # The list could have been deleted by another process. continue - if args.advertised and not mlist.advertised: + if advertised and not mlist.advertised: continue - if args.public_archive and mlist.archive_private: + if public and mlist.archive_private: continue - if (args.virtual_host_overview and mm_cfg.VIRTUAL_HOST_OVERVIEW and - not re.search('://%s/' % re.escape(args.virtual_host_overview), + if (vhost and mm_cfg.VIRTUAL_HOST_OVERVIEW and + not re.search('://%s/' % re.escape(vhost), mlist.web_page_url, re.IGNORECASE)): continue mlists.append(mlist) - # Ensure real_name is a string - real_name = mlist.real_name - if isinstance(real_name, bytes): - real_name = real_name.decode('utf-8', 'replace') - longest = max(len(real_name), longest) - - if not mlists and not args.bare: - print('No matching mailing lists found') + longest = max(len(mlist.real_name), longest) + + if not mlists and not bare: + print(C_('No matching mailing lists found')) return - if not args.bare: - print(len(mlists), 'matching mailing lists found:') + if not bare: + print(len(mlists), C_('matching mailing lists found:')) format = '%%%ds - %%.%ds' % (longest, 77 - longest) for mlist in mlists: - if args.bare: - name = mlist.internal_name() - if isinstance(name, bytes): - name = name.decode('utf-8', 'replace') - print(name) + if bare: + print(mlist.internal_name()) else: - real_name = mlist.real_name - if isinstance(real_name, bytes): - real_name = real_name.decode('utf-8', 'replace') - description = mlist.description or '[no description available]' - if isinstance(description, bytes): - description = description.decode('utf-8', 'replace') - print(' ', format % (real_name, description)) + description = mlist.description or C_('[no description available]') + print(' ', format % (mlist.real_name, description)) + if __name__ == '__main__': main() diff --git a/bin/list_members b/bin/list_members index d77cdbb3..a1f148a8 100755 --- a/bin/list_members +++ b/bin/list_members @@ -77,7 +77,6 @@ from __future__ import print_function from __future__ import unicode_literals import sys -import argparse import paths from Mailman import mm_cfg @@ -106,15 +105,22 @@ def usage(code, msg=''): fd = sys.stderr else: fd = sys.stdout - # Ensure PROGRAM is a string, not bytes - if isinstance(PROGRAM, bytes): - PROGRAM = PROGRAM.decode('utf-8', 'replace') print(C_(__doc__), file=fd) if msg: print(msg, file=fd) sys.exit(code) + +def safe(s): + if not s: + return '' + if isinstance(s, str): + return s + elif isinstance(s, bytes): + return s.decode(ENC, 'replace') + return str(s) + def isinvalid(addr): try: Utils.ValidateEmail(addr) @@ -126,6 +132,7 @@ def isunicode(addr): return isinstance(addr, str) + def whymatches(mlist, addr, why): # Return true if the `why' matches the reason the address is enabled, or # in the case of why is None, that they are disabled for any reason @@ -136,37 +143,108 @@ def whymatches(mlist, addr, why): return status == WHYCHOICES[why] + def main(): - parser = argparse.ArgumentParser(description='List all the members of a mailing list.') - parser.add_argument('listname', help='Name of the mailing list') - parser.add_argument('-o', '--output', help='Write output to specified file instead of standard out') - parser.add_argument('-r', '--regular', action='store_true', help='Print just the regular (non-digest) members') - parser.add_argument('-d', '--digest', choices=['mime', 'plain'], nargs='?', const=True, help='Print just the digest members') - parser.add_argument('-n', '--nomail', choices=list(WHYCHOICES.keys()), nargs='?', const=True, help='Print members with delivery disabled') - parser.add_argument('-f', '--fullnames', action='store_true', help='Include the full names in the output') - parser.add_argument('-p', '--preserve', action='store_true', help='Output member addresses case preserved') - parser.add_argument('-m', '--moderated', action='store_true', help='Print just the moderated members') - parser.add_argument('-M', '--non-moderated', action='store_true', help='Print just the non-moderated members') - parser.add_argument('-i', '--invalid', action='store_true', help='Print only invalid addresses') - parser.add_argument('-u', '--unicode', action='store_true', help='Print addresses stored as Unicode objects') - - args = parser.parse_args() - - # Validate mutually exclusive options - if sum([args.moderated, args.non_moderated, args.invalid, args.unicode]) > 1: - parser.error('Only one of -m, -M, -i or -u may be specified.') - - if args.output: + # Because of the optional arguments, we can't use getopt. :( + outfile = None + regular = None + digest = None + preserve = None + nomail = None + why = None + kind = None + fullnames = False + invalidonly = False + unicodeonly = False + moderatedonly = False + nonmoderatedonly = False + + # Throw away the first (program) argument + args = sys.argv[1:] + if not args: + usage(0) + + while True: + try: + opt = args.pop(0) + except IndexError: + usage(1) + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-f', '--fullnames'): + fullnames = True + elif opt in ('-p', '--preserve'): + preserve = True + elif opt in ('-r', '--regular'): + regular = True + elif opt in ('-o', '--output'): + try: + outfile = args.pop(0) + except IndexError: + usage(1) + elif opt == '-n': + nomail = True + if args and args[0] in list(WHYCHOICES.keys()): + why = args.pop(0) + elif opt.startswith('--nomail'): + nomail = True + i = opt.find('=') + if i >= 0: + why = opt[i+1:] + if why not in list(WHYCHOICES.keys()): + usage(1, C_('Bad --nomail option: %(why)s')) + elif opt == '-d': + digest = True + if args and args[0] in ('mime', 'plain'): + kind = args.pop(0) + elif opt.startswith('--digest'): + digest = True + i = opt.find('=') + if i >= 0: + kind = opt[i+1:] + if kind not in ('mime', 'plain'): + usage(1, C_('Bad --digest option: %(kind)s')) + elif opt in ('-m', '--moderated'): + moderatedonly = True + if nonmoderatedonly or invalidonly or unicodeonly: + usage(1, C_('Only one of -m, -M, -i or -u may be specified.')) + elif opt in ('-M', '--non-moderated'): + nonmoderatedonly = True + if moderatedonly or invalidonly or unicodeonly: + usage(1, C_('Only one of -m, -M, -i or -u may be specified.')) + elif opt in ('-i', '--invalid'): + invalidonly = True + if moderatedonly or nonmoderatedonly or unicodeonly: + usage(1, C_('Only one of -m, -M, -i or -u may be specified.')) + elif opt in ('-u', '--unicode'): + unicodeonly = True + if moderatedonly or nonmoderatedonly or invalidonly: + usage(1, C_('Only one of -m, -M, -i or -u may be specified.')) + else: + # No more options left, push the last one back on the list + args.insert(0, opt) + break + + if len(args) != 1: + usage(1) + + listname = args[0].lower().strip() + + if regular is None and digest is None: + regular = digest = True + + if outfile: try: - fp = open(args.output, 'w') + fp = open(outfile, 'w') except IOError: - print(C_('Could not open file for writing:'), args.output, file=sys.stderr) + print(C_( + 'Could not open file for writing:'), outfile, file=sys.stderr) sys.exit(1) else: fp = sys.stdout try: - mlist = MailList.MailList(args.listname.lower().strip(), lock=False) + mlist = MailList.MailList(listname, lock=False) except Errors.MMListError as e: print(C_('No such list: %(listname)s'), file=sys.stderr) sys.exit(1) @@ -175,56 +253,56 @@ def main(): rmembers = mlist.getRegularMemberKeys() dmembers = mlist.getDigestMemberKeys() - if args.preserve: + if preserve: # Convert to the case preserved addresses rmembers = mlist.getMemberCPAddresses(rmembers) dmembers = mlist.getMemberCPAddresses(dmembers) - if args.invalid or args.unicode or args.moderated or args.non_moderated: + if invalidonly or unicodeonly or moderatedonly or nonmoderatedonly: all = rmembers + dmembers all.sort() for addr in all: - name = args.fullnames and mlist.getMemberName(addr) or '' + name = fullnames and mlist.getMemberName(addr) or '' showit = False - if args.invalid and isinvalid(addr): + if invalidonly and isinvalid(addr): showit = True - if args.unicode and isunicode(addr): + if unicodeonly and isunicode(addr): showit = True - if args.moderated and mlist.getMemberOption(addr, mm_cfg.Moderate): + if moderatedonly and mlist.getMemberOption(addr, mm_cfg.Moderate): showit = True - if args.non_moderated and not mlist.getMemberOption(addr, mm_cfg.Moderate): + if nonmoderatedonly and not mlist.getMemberOption(addr, + mm_cfg.Moderate): showit = True if showit: - print(formataddr((name, addr)), file=fp) + print(formataddr((safe(name), addr)), file=fp) return - - if args.regular or not args.digest: + if regular: rmembers.sort() for addr in rmembers: - name = args.fullnames and mlist.getMemberName(addr) or '' + name = fullnames and mlist.getMemberName(addr) or '' # Filter out nomails - if args.nomail and not whymatches(mlist, addr, args.nomail): + if nomail and not whymatches(mlist, addr, why): continue - print(formataddr((name, addr)), file=fp) - - if args.digest or not args.regular: + print(formataddr((safe(name), addr)), file=fp) + if digest: dmembers.sort() for addr in dmembers: - name = args.fullnames and mlist.getMemberName(addr) or '' + name = fullnames and mlist.getMemberName(addr) or '' # Filter out nomails - if args.nomail and not whymatches(mlist, addr, args.nomail): + if nomail and not whymatches(mlist, addr, why): continue # Filter out digest kinds if mlist.getMemberOption(addr, mm_cfg.DisableMime): # They're getting plain text digests - if args.digest == 'mime': + if kind == 'mime': continue else: # They're getting MIME digests - if args.digest == 'plain': + if kind == 'plain': continue - print(formataddr((name, addr)), file=fp) + print(formataddr((safe(name), addr)), file=fp) + if __name__ == '__main__': main() diff --git a/bin/list_owners b/bin/list_owners index 11c0013d..507f7b73 100644 --- a/bin/list_owners +++ b/bin/list_owners @@ -38,9 +38,14 @@ Options: after the options. If there are no listnames provided, the owners of all the lists will be displayed. """ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + from builtins import * import sys -import argparse +import getopt import paths from Mailman import Utils @@ -49,47 +54,46 @@ from Mailman.i18n import C_ PROGRAM = sys.argv[0] - + def usage(code, msg=''): if code: fd = sys.stderr else: fd = sys.stdout - # Ensure PROGRAM is a string, not bytes - if isinstance(PROGRAM, bytes): - PROGRAM = PROGRAM.decode('utf-8', 'replace') print(C_(__doc__), file=fd) if msg: print(msg, file=fd) sys.exit(code) + def main(): - parser = argparse.ArgumentParser(description='List the owners of a mailing list, or all mailing lists.') - parser.add_argument('listnames', nargs='*', help='Print the owners of the specified lists') - parser.add_argument('-w', '--with-listnames', action='store_true', - help='Group the owners by list names and include the list names in the output') - parser.add_argument('-m', '--moderators', action='store_true', - help='Include the list moderators in the output') - - args = parser.parse_args() - - listnames = [x.lower() for x in args.listnames] or Utils.list_names() + try: + opts, args = getopt.getopt(sys.argv[1:], 'wmh', + ['with-listnames', 'moderators', 'help']) + except getopt.error as msg: + usage(1, msg) + + withnames = moderators = False + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-m', '--moderators'): + moderators = True + elif opt in ('-w', '--with-listnames'): + withnames = True + + listnames = [x.lower() for x in args] or Utils.list_names() bylist = {} for listname in listnames: - # Ensure listname is a string - if isinstance(listname, bytes): - listname = listname.decode('utf-8', 'replace') mlist = MailList(listname, lock=0) addrs = mlist.owner[:] - if args.moderators: + if moderators: addrs.extend(mlist.moderator) - # Ensure addresses are strings - addrs = [addr.decode('utf-8', 'replace') if isinstance(addr, bytes) else addr for addr in addrs] bylist[listname] = addrs - if args.with_listnames: + if withnames: for listname in listnames: unique = {} for addr in bylist[listname]: @@ -110,5 +114,6 @@ def main(): print(k) + if __name__ == '__main__': main() diff --git a/bin/mailmanctl b/bin/mailmanctl old mode 100755 new mode 100644 index 1dba8cc9..9e87bcd9 --- a/bin/mailmanctl +++ b/bin/mailmanctl @@ -95,12 +95,12 @@ Commands: import sys import os import time +import getopt import signal import errno import pwd import grp import socket -import argparse import paths from Mailman import mm_cfg @@ -127,113 +127,31 @@ MAX_RESTARTS = 10 LogStdErr('error', 'mailmanctl', manual_reprime=0) -def parse_args(): - """Parse command line arguments using argparse. - - Returns: - argparse.Namespace: Parsed command line arguments - """ - parser = argparse.ArgumentParser( - description=C_("Primary start-up and shutdown script for Mailman's qrunner daemon."), - formatter_class=argparse.RawDescriptionHelpFormatter, - epilog=C_("""\ -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 -""") - ) - - parser.add_argument('-n', '--no-restart', - action='store_true', - help=C_("""\ -Don't restart the qrunners when they exit because of an error or a -SIGINT. They are never restarted if they exit in response to a -SIGTERM. Use this only for debugging. Only useful if the `start' -command is given.""")) - - parser.add_argument('-u', '--run-as-user', - action='store_true', - help=C_("""\ -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_argument('-s', '--stale-lock-cleanup', - action='store_true', - help=C_("""\ -If mailmanctl finds an existing master lock, it will normally exit -with an error message. With this option, mailmanctl will perform an -extra level of checking. If a process matching the host/pid described -in the lock file is running, mailmanctl will still exit, but if no -matching process is found, mailmanctl will remove the apparently stale -lock and make another attempt to claim the master lock.""")) - - parser.add_argument('-q', '--quiet', - action='store_true', - help=C_("Don't print status messages. Error messages are still printed to standard error.")) - - parser.add_argument('command', - choices=['start', 'stop', 'restart', 'reopen'], - help=C_("Command to execute")) - - return parser.parse_args() - - + def usage(code, msg=''): if code: fd = sys.stderr else: fd = sys.stdout - # In Python 3, sys.argv[0] is already a string - program = str(sys.argv[0]) # Ensure it's a string - doc = C_(__doc__) % {'PROGRAM': program} # Let C_() handle the translation and formatting - print(doc, file=fd) + print(C_(__doc__), file=fd) if msg: print(msg, file=fd) sys.exit(code) + def kill_watcher(sig): try: - with open(mm_cfg.PIDFILE, 'r') as fp: - content = fp.read().strip().split() - if len(content) >= 2: - pid = int(content[0]) - hostname = content[1] - if hostname != socket.gethostname(): - print(C_('PID file hostname mismatch: expected %(expected)s, got %(got)s') % - {'expected': socket.gethostname(), 'got': hostname}, file=sys.stderr) - return - else: - raise ValueError('Invalid PID file format') + fp = open(mm_cfg.PIDFILE) + pidstr = fp.read() + fp.close() + pid = int(pidstr.strip()) except (IOError, ValueError) as e: # For i18n convenience pidfile = mm_cfg.PIDFILE print(C_('PID unreadable in: %(pidfile)s'), file=sys.stderr) print(e, file=sys.stderr) print(C_('Is qrunner even running?'), file=sys.stderr) - print(C_('Lock file path: %(lockfile)s') % {'lockfile': LOCKFILE}, file=sys.stderr) return try: os.kill(pid, sig) @@ -245,29 +163,21 @@ def kill_watcher(sig): os.unlink(mm_cfg.PIDFILE) + def get_lock_data(): # Return the hostname, pid, and tempfile - try: - with open(LOCKFILE) as fp: - content = fp.read().strip().split() - if len(content) != 2: - syslog('error', 'Invalid lock file format in %s: expected "pid hostname"', LOCKFILE) - raise LockFile.LockError('Invalid lock file format') - try: - pid = int(content[0]) - hostname = content[1] - except ValueError as e: - syslog('error', 'Invalid PID in lock file %s: %s', LOCKFILE, e) - raise LockFile.LockError('Invalid PID in lock file') - return hostname, pid, None # tempfile is not used in this format - except IOError as e: - syslog('error', 'Could not read lock file %s: %s', LOCKFILE, e) - raise LockFile.LockError('Could not read lock file') + fp = open(LOCKFILE) + filename = os.path.split(fp.read().strip())[1] + fp.close() + parts = filename.split('.') + hostname = DOT.join(parts[1:-1]) + pid = int(parts[-1]) + return hostname, int(pid), filename def qrunner_state(): - # 1 if proc exists on host and is owned by mailman user - # 0 if host matches but no proc or wrong owner + # 1 if proc exists on host (but is it qrunner? ;) + # 0 if host matches but no proc # hostname if hostname doesn't match hostname, pid, tempfile = get_lock_data() if hostname != socket.gethostname(): @@ -275,44 +185,10 @@ def qrunner_state(): # Find out if the process exists by calling kill with a signal 0. try: os.kill(pid, 0) - # Process exists, now check if it's owned by the mailman user - mailman_uid = pwd.getpwnam(mm_cfg.MAILMAN_USER).pw_uid - try: - # Try to get process owner using platform-specific methods - if os.name == 'posix': - # On Unix-like systems, try to get process owner - try: - # Try using /proc on Linux - if os.path.exists('/proc'): - with open(f'/proc/{pid}/status') as f: - for line in f: - if line.startswith('Uid:'): - uid = int(line.split()[1]) - if uid != mailman_uid: - syslog('error', 'Process %d exists but is owned by uid %d, not mailman user %d', - pid, uid, mailman_uid) - return 0 - break - else: - # On other Unix systems, we can't easily check the owner - # without external tools, so we'll assume it's valid - # if the process exists - return 1 - except (IOError, OSError) as e: - syslog('error', 'Error checking process %d ownership: %s', pid, str(e)) - return 0 - else: - # On non-Unix systems, we can't easily check the owner - # without external tools, so we'll assume it's valid - # if the process exists - return 1 - return 1 - except Exception as e: - syslog('error', 'Error checking process %d ownership: %s', pid, str(e)) - return 0 except OSError as e: if e.errno != errno.ESRCH: raise return 0 + return 1 def acquire_lock_1(force): @@ -323,28 +199,14 @@ def acquire_lock_1(force): lock.lock(0.1) return lock except LockFile.TimeOutError: - # Check if the lock is stale by examining the process - status = qrunner_state() - if status == 1: - # Process exists and is running, so lock is valid + # If we're not forcing or the lock can't be determined to be stale. + if not force or qrunner_state(): raise - # Lock appears to be stale - clean it up - try: - # Read the current lock file content - with open(LOCKFILE) as fp: - content = fp.read().strip() - if content: - # Try to clean up any stale lock files - lock.clean_stale_locks() - except (IOError, OSError) as e: - syslog('error', 'Error cleaning up stale lock: %s', str(e)) - # Remove the lock file - try: - os.unlink(LOCKFILE) - except OSError as e: - if e.errno != errno.ENOENT: - syslog('error', 'Error removing lock file: %s', str(e)) - # Try to acquire the lock again + # Force removal of lock first + lock._disown() + hostname, pid, tempfile = get_lock_data() + os.unlink(LOCKFILE) + os.unlink(os.path.join(mm_cfg.LOCK_DIR, tempfile)) return acquire_lock_1(force=0) @@ -380,6 +242,7 @@ Lock host: %(status)s Exiting."""), file=sys.stderr) + def start_runner(qrname, slice, count): pid = os.fork() if pid: @@ -401,19 +264,14 @@ def start_all_runners(): kids = {} for qrname, count in mm_cfg.QRUNNERS: for slice in range(count): - try: - # queue runner name, slice, numslices, restart count - info = (qrname, slice, count, 0) - pid = start_runner(qrname, slice, count) - kids[pid] = info - except Exception as e: - # Log the failure but continue with other runners - syslog('error', 'Failed to start %s runner (slice %d): %s', - qrname, slice, str(e)) - continue + # queue runner name, slice, numslices, restart count + info = (qrname, slice, count, 0) + pid = start_runner(qrname, slice, count) + kids[pid] = info return kids + def check_for_site_list(): sitelistname = mm_cfg.MAILMAN_SITE_LIST try: @@ -448,315 +306,212 @@ def check_privs(): 'Run this program as root or as the %(name)s user, or use -u.')) -def check_status(): - """Check if all qrunners are running as expected.""" - # First check if the master process is running - try: - with open(mm_cfg.PIDFILE, 'r') as fp: - content = fp.read().strip().split() - if len(content) >= 2: - pid = int(content[0]) - hostname = content[1] - if hostname != socket.gethostname(): - print(C_('PID file hostname mismatch: expected %(expected)s, got %(got)s') % - {'expected': socket.gethostname(), 'got': hostname}, file=sys.stderr) - return False - else: - raise ValueError('Invalid PID file format') - try: - os.kill(pid, 0) # Check if process exists - print(C_('Master qrunner process is running (pid: %(pid)d)') % {'pid': pid}) - except OSError: - print(C_('Master qrunner process is not running (stale pid file)')) - return False - except (IOError, ValueError) as e: - print(C_('Master qrunner process is not running (no pid file)')) - print(e, file=sys.stderr) - return False - - # Check if the lock file exists and is valid - try: - hostname, pid, tempfile = get_lock_data() - if hostname != socket.gethostname(): - print(C_('Lock file is held by another host: %(hostname)s') % {'hostname': hostname}) - return False - try: - os.kill(pid, 0) - print(C_('Lock file is valid (pid: %(pid)d)') % {'pid': pid}) - except OSError: - print(C_('Lock file is stale (process %(pid)d not running)') % {'pid': pid}) - return False - except (IOError, ValueError): - print(C_('No lock file found')) - return False - - # Check if all expected qrunners are running - expected_runners = dict(mm_cfg.QRUNNERS) - running_runners = {} - - # Get all running qrunner processes - for line in os.popen('ps aux | grep qrunner | grep -v grep').readlines(): - parts = line.split() - if len(parts) >= 12: # Ensure we have enough parts - cmd = parts[10] # The command is typically at index 10 - if '--runner=' in cmd: - runner_name = cmd.split('--runner=')[1].split(':')[0] - running_runners[runner_name] = running_runners.get(runner_name, 0) + 1 - - # Compare expected vs running - all_running = True - for runner, count in expected_runners.items(): - actual = running_runners.get(runner, 0) - if actual != count: - print(C_('%(runner)s: expected %(count)d instances, found %(actual)d') % - {'runner': runner, 'count': count, 'actual': actual}) - all_running = False - else: - print(C_('%(runner)s: %(count)d instances running') % - {'runner': runner, 'count': count}) - - return all_running - - -def check_global_circuit_breaker(): - """Check if we've exceeded the global restart limit. - - Returns: - bool: True if we should stop all runners, False otherwise - """ - # Circuit breaker disabled - always return False - return False - - -def stop_all_processes(kids, lock=None): - """Stop all child processes and clean up, similar to mailmanctl stop. - - Args: - kids: Dictionary of child processes - lock: Optional lock to release - """ - # First send SIGTERM to all children - for pid in list(kids.keys()): - try: - os.kill(pid, signal.SIGTERM) - except OSError as e: - if e.errno != errno.ESRCH: - raise - - # Wait for all children to exit - while kids: - try: - pid, status = os.wait() - if pid in kids: - del kids[pid] - except OSError as e: - if e.errno == errno.ECHILD: - break - elif e.errno != errno.EINTR: - raise - continue - - # Clean up PID file - try: - os.unlink(mm_cfg.PIDFILE) - syslog('qrunner', 'Removed PID file: %s', mm_cfg.PIDFILE) - except OSError as e: - if e.errno != errno.ENOENT: - syslog('error', 'Failed to remove PID file %s: %s', mm_cfg.PIDFILE, str(e)) - - # Release lock if provided - if lock: - try: - lock.unlock(unconditionally=1) - except Exception as e: - syslog('error', 'Failed to release lock: %s', str(e)) - - + def main(): + global quiet try: - args = parse_args() - except SystemExit: - usage(1) - - # Check that we're running as the right user - if not args.run_as_user: - try: - mailman_uid = pwd.getpwnam(mm_cfg.MAILMAN_USER).pw_uid - mailman_gid = grp.getgrnam(mm_cfg.MAILMAN_GROUP).gr_gid - except (KeyError, AttributeError): - print(C_('Cannot determine mailman user/group'), file=sys.stderr) - sys.exit(1) - - if os.getuid() == 0: - # We're root, so switch to the mailman user/group - os.setgid(mailman_gid) - os.setuid(mailman_uid) - elif os.getuid() != mailman_uid or os.getgid() != mailman_gid: - print(C_('Must be run as the mailman user'), file=sys.stderr) - sys.exit(1) - - # Handle the command - if args.command == 'status': - if check_status(): - sys.exit(0) - else: - sys.exit(1) - elif args.command == 'start': - # Check if we're already running - if os.path.exists(mm_cfg.PIDFILE): - try: - with open(mm_cfg.PIDFILE) as fp: - pid = int(fp.read().strip()) - if check_pid(pid): - print(C_('Mailman qrunner is already running (pid: %(pid)d)'), file=sys.stderr) - sys.exit(1) - except (ValueError, IOError): - pass - - # Try to acquire the lock - try: - lock = acquire_lock(args.stale_lock_cleanup) - except LockFile.TimeOutError: - sys.exit(1) - - # Fork to daemonize + opts, args = getopt.getopt(sys.argv[1:], 'hnusq', + ['help', 'no-restart', 'run-as-user', + 'stale-lock-cleanup', 'quiet']) + except getopt.error as msg: + usage(1, msg) + + restart = 1 + checkprivs = 1 + force = 0 + quiet = 0 + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-n', '--no-restart'): + restart = 0 + elif opt in ('-u', '--run-as-user'): + checkprivs = 0 + elif opt in ('-s', '--stale-lock-cleanup'): + force = 1 + elif opt in ('-q', '--quiet'): + quiet = 1 + + if len(args) < 1: + usage(1, C_('No command given.')) + elif len(args) > 1: + command = COMMASPACE.join(args) + usage(1, C_('Bad command: %(command)s')) + + if checkprivs: + check_privs() + else: + print(C_('Warning! You may encounter permission problems.')) + + # Handle the commands + command = args[0].lower() + if command == 'stop': + # Sent the master qrunner process a SIGINT, which is equivalent to + # giving cron/qrunner a ctrl-c or KeyboardInterrupt. This will + # effectively shut everything down. + if not quiet: + print(C_("Shutting down Mailman's master qrunner")) + kill_watcher(signal.SIGTERM) + elif command == 'restart': + # Sent the master qrunner process a SIGHUP. This will cause the + # master qrunner to kill and restart all the worker qrunners, and to + # close and re-open its log files. + if not quiet: + print(C_("Restarting Mailman's master qrunner")) + kill_watcher(signal.SIGINT) + elif command == 'reopen': + if not quiet: + print(C_('Re-opening all log files')) + kill_watcher(signal.SIGHUP) + elif command == 'start': + # First, complain loudly if there's no site list. + check_for_site_list() + # Here's the scoop on the processes we're about to create. We'll need + # one for each qrunner, and one for a master child process watcher / + # lock refresher process. + # + # The child watcher process simply waits on the pids of the children + # qrunners. Unless explicitly disabled by a mailmanctl switch (or the + # children are killed with SIGTERM instead of SIGINT), the watcher + # will automatically restart any child process that exits. This + # allows us to be more robust, and also to implement restart by simply + # SIGINT'ing the qrunner children, and letting the watcher restart + # them. + # + # Under normal operation, we have a child per queue. This lets us get + # the most out of the available resources, since a qrunner with no + # files in its queue directory is pretty cheap, but having a separate + # runner process per queue allows for a very responsive system. Some + # people want a more traditional (i.e. MM2.0.x) cron-invoked qrunner. + # No problem, but using mailmanctl isn't the answer. So while + # mailmanctl hard codes some things, others, such as the number of + # qrunners per queue, is configurable in mm_cfg.py. + # + # First, acquire the master mailmanctl lock + lock = acquire_lock(force) + if not lock: + return + # Daemon process startup according to Stevens, Advanced Programming in + # the UNIX Environment, Chapter 13. pid = os.fork() if pid: # parent - if not args.quiet: + if not quiet: print(C_("Starting Mailman's master qrunner.")) - # Give up the lock "ownership". This just means the foreground + # Give up the lock "ownership". This just means the foreground # process won't close/unlock the lock when it finalizes this lock - # instance. We'll let the master watcher subproc own the lock. + # instance. We'll let the mater watcher subproc own the lock. lock._transfer_to(pid) - - # Wait briefly to ensure child process starts - time.sleep(1) - - # Verify the child process is running - try: - os.kill(pid, 0) # Check if process exists - if not args.quiet: - print(C_('Master qrunner started successfully (pid: %d)') % pid) - syslog('qrunner', 'Master qrunner started successfully (pid: %d)', pid) - except OSError as e: - if e.errno == errno.ESRCH: - print(C_('Error: Master process failed to start'), file=sys.stderr) - return - raise return - # child + lock._take_possession() + # First, save our pid in a file for "mailmanctl stop" rendezvous. We + # want the perms on the .pid file to be rw-rw---- + omask = os.umask(6) try: - lock._take_possession() - - # Create a new session and become the session leader - os.setsid() - - # Be sure to close any open std{in,out,err} - devnull = os.open('/dev/null', 0) - os.dup2(devnull, 0) - os.dup2(devnull, 1) - os.dup2(devnull, 2) - - # Instead of cd'ing to root, cd to the Mailman installation home - os.chdir(mm_cfg.PREFIX) - - # Set our file mode creation umask - os.umask(0o07) - - # Write our PID to the PID file - try: - with open(mm_cfg.PIDFILE, 'w') as fp: - fp.write(str(os.getpid())) - except IOError as e: - syslog('error', 'Failed to write PID file: %s', str(e)) - os._exit(1) - - # Start all runners - kids = start_all_runners() - if not kids: - syslog('error', 'No runners started successfully') - os._exit(1) - - # Set up a SIGALRM handler to refresh the lock once per day - def sigalrm_handler(signum, frame, lock=lock): - lock.refresh() - signal.alarm(mm_cfg.days(1)) - signal.signal(signal.SIGALRM, sigalrm_handler) + fp = open(mm_cfg.PIDFILE, 'w') + print(os.getpid(), file=fp) + fp.close() + finally: + os.umask(omask) + # 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() + + # Be sure to close any open std{in,out,err} + devnull = os.open('/dev/null', 0) + os.dup2(devnull, 0) + os.dup2(devnull, 1) + os.dup2(devnull, 2) + + # Instead of cd'ing to root, cd to the Mailman installation home + os.chdir(mm_cfg.PREFIX) + # Set our file mode creation umask + os.umask(0o07) + # I don't think we have any unneeded file descriptors. + # + # Now start all the qrunners. This returns a dictionary where the + # keys are qrunner pids and the values are tuples of the following + # form: (qrname, slice, count). This does its own fork and exec, and + # sets up its own signal handlers. + kids = start_all_runners() + # Set up a SIGALRM handler to refresh the lock once per day. The lock + # lifetime is 1day+6hours so this should be plenty. + def sigalrm_handler(signum, frame, lock=lock): + lock.refresh() signal.alarm(mm_cfg.days(1)) - - # Set up a SIGHUP handler - def sighup_handler(signum, frame, kids=kids): - syslog.close() - for pid in list(kids.keys()): - os.kill(pid, signal.SIGHUP) - syslog('qrunner', - 'Master watcher caught SIGHUP. Re-opening log files.') - signal.signal(signal.SIGHUP, sighup_handler) - - # Set up a SIGTERM handler - def sigterm_handler(signum, frame, kids=kids): - for pid in list(kids.keys()): - try: - os.kill(pid, signal.SIGTERM) - except OSError as e: - if e.errno != errno.ESRCH: raise - syslog('qrunner', 'Master watcher caught SIGTERM. Exiting.') - signal.signal(signal.SIGTERM, sigterm_handler) - - # Set up a SIGINT handler - def sigint_handler(signum, frame, kids=kids): - for pid in list(kids.keys()): - os.kill(pid, signal.SIGINT) - syslog('qrunner', 'Master watcher caught SIGINT. Restarting.') - signal.signal(signal.SIGINT, sigint_handler) - - # Now we're ready to simply do our wait/restart loop - while True: + signal.signal(signal.SIGALRM, sigalrm_handler) + signal.alarm(mm_cfg.days(1)) + # Set up a SIGHUP handler so that if we get one, we'll pass it along + # to all the qrunner children. This will tell them to close and + # reopen their log files + def sighup_handler(signum, frame, kids=kids): + # Closing our syslog will cause it to be re-opened at the next log + # print output. + syslog.close() + for pid in list(kids.keys()): + os.kill(pid, signal.SIGHUP) + # And just to tweak things... + syslog('qrunner', + 'Master watcher caught SIGHUP. Re-opening log files.') + signal.signal(signal.SIGHUP, sighup_handler) + # We also need to install a SIGTERM handler because that's what init + # will kill this process with when changing run levels. + def sigterm_handler(signum, frame, kids=kids): + for pid in list(kids.keys()): + try: + os.kill(pid, signal.SIGTERM) + except OSError as e: + if e.errno != errno.ESRCH: raise + syslog('qrunner', 'Master watcher caught SIGTERM. Exiting.') + signal.signal(signal.SIGTERM, sigterm_handler) + # Finally, we need a SIGINT handler which will cause the sub-qrunners + # to exit, but the master will restart SIGINT'd sub-processes unless + # the -n flag was given. + def sigint_handler(signum, frame, kids=kids): + for pid in list(kids.keys()): + os.kill(pid, signal.SIGINT) + syslog('qrunner', 'Master watcher caught SIGINT. Restarting.') + signal.signal(signal.SIGINT, sigint_handler) + # Now we're ready to simply do our wait/restart loop. This is the + # master qrunner watcher. + try: + while 1: try: pid, status = os.wait() except OSError as e: - # No children? We're done + # No children? We're done if e.errno == errno.ECHILD: break # If the system call got interrupted, just restart it. elif e.errno != errno.EINTR: raise continue - killsig = exitstatus = None if os.WIFSIGNALED(status): killsig = os.WTERMSIG(status) if os.WIFEXITED(status): exitstatus = os.WEXITSTATUS(status) - + # We'll restart the process unless we were given the + # "no-restart" switch, or if the process was SIGTERM'd or + # exitted with a SIGTERM exit status. This lets us better + # handle runaway restarts (say, if the subproc had a syntax + # error!) restarting = '' - if not args.no_restart: - # Only restart if the runner exited with SIGINT (normal exit) - # and not SIGTERM (error or forced stop) - if exitstatus == signal.SIGINT: + if restart: + if (exitstatus == None and killsig != signal.SIGTERM) or \ + (killsig == None and exitstatus != signal.SIGTERM): + # Then restarting = '[restarting]' - qrname, slice, count, restarts = kids[pid] del kids[pid] - - # Only log abnormal exits - if killsig == signal.SIGTERM or \ - (exitstatus is not None and exitstatus != signal.SIGINT): - syslog('qrunner', """\ -Master qrunner detected abnormal subprocess exit + syslog('qrunner', """\ +Master qrunner detected subprocess exit (pid: %d, sig: %s, sts: %s, class: %s, slice: %d/%d) %s""", pid, killsig, exitstatus, qrname, slice+1, count, restarting) - - if restarting and check_global_circuit_breaker(): - syslog('error', 'Global circuit breaker triggered - stopping all runners') - # Stop all processes and clean up - stop_all_processes(kids, lock) - # Exit the main loop - break - + # See if we've reached the maximum number of allowable restarts if exitstatus != signal.SIGINT: restarts += 1 if restarts > MAX_RESTARTS: @@ -764,24 +519,25 @@ Master qrunner detected abnormal subprocess exit Qrunner %s reached maximum restart limit of %d, not restarting.""", qrname, MAX_RESTARTS) restarting = '' - - # Now perhaps restart the process + # Now perhaps restart the process unless it exited with a + # SIGTERM or we aren't restarting. if restarting: newpid = start_runner(qrname, slice, count) kids[newpid] = (qrname, slice, count, restarts) - finally: - # all of our children are exited cleanly + # Should we leave the main loop for any reason, we want to be sure + # all of our children are exited cleanly. Send SIGTERMs to all + # the child processes and wait for them all to exit. for pid in list(kids.keys()): try: os.kill(pid, signal.SIGTERM) except OSError as e: if e.errno == errno.ESRCH: + # The child has already exited syslog('qrunner', 'ESRCH on pid: %d', pid) del kids[pid] - # Wait for all the children to go away - while True: + while 1: try: pid, status = os.wait() except OSError as e: @@ -790,26 +546,11 @@ Qrunner %s reached maximum restart limit of %d, not restarting.""", elif e.errno != errno.EINTR: raise continue - - # Finally, give up the lock - lock.unlock(unconditionally=1) - os._exit(0) - elif args.command == 'stop': - kill_watcher(signal.SIGTERM) - try: - os.unlink(mm_cfg.PIDFILE) - syslog('qrunner', 'Removed PID file: %s', mm_cfg.PIDFILE) - except OSError as e: - if e.errno != errno.ENOENT: - syslog('error', 'Failed to remove PID file %s: %s', mm_cfg.PIDFILE, str(e)) - elif args.command == 'restart': - kill_watcher(signal.SIGINT) - start_all_runners() - elif args.command == 'reopen': - kill_watcher(signal.SIGHUP) - else: - usage(1, C_('Unknown command: %(command)s')) + # Finally, give up the lock + lock.unlock(unconditionally=1) + os._exit(0) + if __name__ == '__main__': main() diff --git a/bin/mmsitepass b/bin/mmsitepass index fd0625b3..8247f2a0 100755 --- a/bin/mmsitepass +++ b/bin/mmsitepass @@ -22,7 +22,7 @@ The site password can be used in most if not all places that the list administrator's password can be used, which in turn can be used in most places that a list users password can be used. -Usage: %(PROGRAM)s [options] [password] +Usage: mmsitepass [options] [password] Options: @@ -40,7 +40,7 @@ from __future__ import unicode_literals import sys import getpass -import argparse +import getopt import paths from Mailman import Utils @@ -48,27 +48,42 @@ from Mailman import Utils PROGRAM = sys.argv[0] -def parse_args(): - parser = argparse.ArgumentParser(description='Set the site password, prompting from the terminal.') - parser.add_argument('-c', '--listcreator', action='store_true', - help='Set the list creator password instead of the site password') - parser.add_argument('password', nargs='?', - help='The password to set (optional, will prompt if not provided)') - return parser.parse_args() + +def usage(code, msg=''): + if code: + fd = sys.stderr + else: + fd = sys.stdout + print(__doc__, file=fd) + if msg: + print(msg, file=fd) + sys.exit(code) + def main(): - args = parse_args() + try: + opts, args = getopt.getopt(sys.argv[1:], 'ch', + ['listcreator', 'help']) + except getopt.error as msg: + usage(1, msg) # Defaults - siteadmin = not args.listcreator - pwdesc = 'list creator' if args.listcreator else 'site' - - if args.password: - pw1 = args.password + siteadmin = 1 + pwdesc = 'site' + + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-c', '--listcreator'): + siteadmin = 0 + pwdesc = 'list creator' + + if len(args) == 1: + pw1 = args[0] else: try: - pw1 = getpass.getpass('New %(pwdesc)s password: ') + pw1 = getpass.getpass(f'New {pwdesc} password: ') pw2 = getpass.getpass('Again to confirm password: ') if pw1 != pw2: print('Passwords do not match; no changes made.') @@ -85,5 +100,6 @@ def main(): print('Password change failed.') + if __name__ == '__main__': main() diff --git a/bin/msgfmt-python2.py b/bin/msgfmt-python2.py index 960891fd..44dd119d 100644 --- a/bin/msgfmt-python2.py +++ b/bin/msgfmt-python2.py @@ -1,6 +1,6 @@ -#! @PYTHON@ +#! /usr/bin/env python # -*- coding: iso-8859-1 -*- -# Written by Martin v. Lwis +# Written by Martin v. Löwis """Generate binary message catalog from textual translation description. @@ -28,7 +28,7 @@ import sys import os -import argparse +import getopt import struct import array @@ -37,6 +37,15 @@ MESSAGES = {} + +def usage(code, msg=''): + print(__doc__, file=sys.stderr) + if msg: + print(msg, file=sys.stderr) + sys.exit(code) + + + def add(id, str, fuzzy): "Add a non-fuzzy translation to the dictionary." global MESSAGES @@ -44,6 +53,7 @@ def add(id, str, fuzzy): MESSAGES[id] = str + def generate(): "Return the generated output." global MESSAGES @@ -86,6 +96,7 @@ def generate(): return output + def make(filename, outfile): ID = 1 STR = 2 @@ -161,22 +172,32 @@ def make(filename, outfile): print(msg, file=sys.stderr) -def parse_args(): - parser = argparse.ArgumentParser(description='Generate binary message catalog from textual translation description.') - parser.add_argument('-o', '--output-file', - help='Specify the output file to write to') - parser.add_argument('-V', '--version', action='version', - version='%(prog)s ' + __version__) - parser.add_argument('files', nargs='+', - help='Input .po files to process') - return parser.parse_args() - - + def main(): - args = parse_args() - - for filename in args.files: - make(filename, args.output_file) + try: + opts, args = getopt.getopt(sys.argv[1:], 'hVo:', + ['help', 'version', 'output-file=']) + except getopt.error as msg: + usage(1, msg) + + outfile = None + # parse options + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-V', '--version'): + print("msgfmt.py", __version__, file=sys.stderr) + sys.exit(0) + elif opt in ('-o', '--output-file'): + outfile = arg + # do it + if not args: + print('No input file given', file=sys.stderr) + print("Try `msgfmt --help' for more information.", file=sys.stderr) + return + + for filename in args: + make(filename, outfile) if __name__ == '__main__': diff --git a/bin/msgfmt.py b/bin/msgfmt.py index 8a36c96d..78b4ef6a 100644 --- a/bin/msgfmt.py +++ b/bin/msgfmt.py @@ -1,4 +1,4 @@ -#! @PYTHON@ +#! /usr/bin/env python # -*- coding: iso-8859-1 -*- # Written by Martin v. Loewis @@ -28,7 +28,7 @@ import sys import os -import argparse +import getopt import struct import array @@ -37,17 +37,15 @@ MESSAGES = {} -def parse_args(): - parser = argparse.ArgumentParser(description='Generate binary message catalog from textual translation description.') - parser.add_argument('filename', nargs='+', - help='Input .po file(s)') - parser.add_argument('-o', '--output-file', - help='Specify the output file to write to') - parser.add_argument('-V', '--version', action='version', - version='%(prog)s ' + __version__) - return parser.parse_args() + +def usage(code, msg=''): + sys.stderr.write(str(__doc__) + "\n") + if msg: + sys.stderr.write(str(msg) + "\n") + sys.exit(code) + def add(id, str, fuzzy): "Add a non-fuzzy translation to the dictionary." global MESSAGES @@ -55,6 +53,7 @@ def add(id, str, fuzzy): MESSAGES[id] = str + def generate(): "Return the generated output." global MESSAGES @@ -97,6 +96,7 @@ def generate(): return output + def make(filename, outfile): ID = 1 STR = 2 @@ -171,10 +171,32 @@ def make(filename, outfile): print(msg, file=sys.stderr) + def main(): - args = parse_args() - for filename in args.filename: - make(filename, args.output_file) + try: + opts, args = getopt.getopt(sys.argv[1:], 'hVo:', + ['help', 'version', 'output-file=']) + except getopt.error as msg: + usage(1, msg) + + outfile = None + # parse options + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-V', '--version'): + print("msgfmt.py", __version__, file=sys.stderr) + sys.exit(0) + elif opt in ('-o', '--output-file'): + outfile = arg + # do it + if not args: + print('No input file given', file=sys.stderr) + print("Try `msgfmt --help' for more information.", file=sys.stderr) + return + + for filename in args: + make(filename, outfile) if __name__ == '__main__': diff --git a/bin/newlist b/bin/newlist index 257df3eb..eeab3eb3 100755 --- a/bin/newlist +++ b/bin/newlist @@ -19,7 +19,7 @@ """Create a new, unpopulated mailing list. -Usage: {PROGRAM} [options] [listname [listadmin-addr [admin-password]]] +Usage: %(PROGRAM)s [options] [listname [listadmin-addr [admin-password]]] Options: @@ -101,14 +101,14 @@ Note that listnames are forced to lowercase. import sys import os import getpass -import argparse +import getopt import paths from Mailman import mm_cfg from Mailman import MailList from Mailman import Utils from Mailman import Errors -from Mailman.Message import Message +from Mailman import Message from Mailman import i18n _ = i18n._ @@ -117,88 +117,93 @@ C_ = i18n.C_ PROGRAM = sys.argv[0] + def usage(code, msg=''): if code: fd = sys.stderr else: fd = sys.stdout - # Ensure PROGRAM is a string, not bytes - if isinstance(PROGRAM, bytes): - PROGRAM = PROGRAM.decode('utf-8', 'replace') - print(C_(__doc__.format( PROGRAM = PROGRAM )), file=fd) + print(C_(__doc__), file=fd) if msg: print(msg, file=fd) sys.exit(code) -def parse_args(): - parser = argparse.ArgumentParser(description='Create a new, unpopulated mailing list.') - parser.add_argument('-l', '--language', - help='Make the list\'s preferred language (two letter code)') - parser.add_argument('-u', '--urlhost', - help='Gives the list\'s web interface host name') - parser.add_argument('-e', '--emailhost', - help='Gives the list\'s email domain name') - parser.add_argument('-q', '--quiet', action='store_true', - help='Suppress the prompt and notification') - parser.add_argument('-a', '--automate', action='store_true', - help='Suppress the prompt but still send notification') - parser.add_argument('listname', nargs='?', - help='Name of the list to create') - parser.add_argument('listadmin', nargs='?', - help='Email address of the list administrator') - parser.add_argument('adminpass', nargs='?', - help='Password for the list administrator') - return parser.parse_args() - - + def main(): try: - args = parse_args() - except SystemExit: - usage(1) - - # Get the list name - if not args.listname: - print(C_('Enter the name of the list: '), end='') - listname = sys.stdin.readline().strip() + opts, args = getopt.getopt(sys.argv[1:], 'hqal:u:e:', + ['help', 'quiet', 'automate', 'language=', + 'urlhost=', 'emailhost=']) + except getopt.error as msg: + usage(1, msg) + + lang = mm_cfg.DEFAULT_SERVER_LANGUAGE + quiet = False + automate = False + urlhost = None + emailhost = None + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + if opt in ('-q', '--quiet'): + quiet = True + if opt in ('-a', '--automate'): + automate = True + if opt in ('-l', '--language'): + lang = arg + if opt in ('-u', '--urlhost'): + urlhost = arg + if opt in ('-e', '--emailhost'): + emailhost = arg + + # Is the language known? + if lang not in mm_cfg.LC_DESCRIPTIONS.keys(): + usage(1, C_('Unknown language: %(lang)s')) + + if len(args) > 0: + listname = args[0] else: - listname = args.listname + listname = input('Enter the name of the list: ') + listname = listname.lower() + + if '@' in listname: + # note that --urlhost and --emailhost have precedence + listname, domain = listname.split('@', 1) + urlhost = urlhost or domain + emailhost = emailhost or mm_cfg.VIRTUAL_HOSTS.get(domain, domain) + + urlhost = urlhost or mm_cfg.DEFAULT_URL_HOST + host_name = emailhost or \ + mm_cfg.VIRTUAL_HOSTS.get(urlhost, mm_cfg.DEFAULT_EMAIL_HOST) + web_page_url = mm_cfg.DEFAULT_URL_PATTERN % urlhost - # Get the list admin's email address - if not args.listadmin: - print(C_('Enter the email of the person running the list: '), end='') - owner_mail = sys.stdin.readline().strip() - else: - owner_mail = args.listadmin + if Utils.list_exists(listname): + usage(1, C_('List already exists: %(listname)s')) - # Get the list admin's password - if not args.adminpass: - print(C_('Initial %(listname)s password: '), end='') - listpasswd = sys.stdin.readline().strip() + if len(args) > 1: + owner_mail = args[1] else: - listpasswd = args.adminpass + owner_mail = input( + C_('Enter the email of the person running the list: ')) - # Get the language - lang = args.language or mm_cfg.DEFAULT_SERVER_LANGUAGE - if lang not in mm_cfg.LC_DESCRIPTIONS: - usage(1, C_('Unknown language code: %(lang)s')) - - # Get the host names - host_name = args.emailhost or mm_cfg.DEFAULT_EMAIL_HOST - urlhost = args.urlhost or mm_cfg.DEFAULT_URL_HOST - web_page_url = mm_cfg.DEFAULT_URL_PATTERN % urlhost + if len(args) > 2: + listpasswd = args[2] + else: + listpasswd = getpass.getpass(C_('Initial %(listname)s password: ')) + # List passwords cannot be empty + listpasswd = listpasswd.strip() + if not listpasswd: + usage(1, C_('The list password cannot be empty')) - # Create the list - mlist = None + mlist = MailList.MailList() try: - mlist = MailList.MailList(listname, lock=1) + pw = Utils.sha_new(listpasswd.encode()).hexdigest() + # Guarantee that all newly created files have the proper permission. + # proper group ownership should be assured by the autoconf script + # enforcing that all directories have the group sticky bit set + oldmask = os.umask(0o002) try: - pw = Utils.sha_new(listpasswd.encode()).hexdigest() - # Guarantee that all newly created files have the proper permission. - # proper group ownership should be assured by the autoconf script - # enforcing that all directories have the group sticky bit set - oldmask = os.umask(0o002) try: if lang == mm_cfg.DEFAULT_SERVER_LANGUAGE: langs = [lang] @@ -209,13 +214,13 @@ def main(): finally: os.umask(oldmask) except Errors.BadListNameError as s: - usage(1, C_(f'Illegal list name: %(s)s')) + usage(1, C_('Illegal list name: %(s)s')) except Errors.EmailAddressError as s: - usage(1, C_(f'Bad owner email address: %(s)s') + + usage(1, C_('Bad owner email address: %(s)s') + C_(' - owner addresses need to be fully-qualified names' ' like "owner@example.com", not just "owner".')) except Errors.MMListAlreadyExistsError: - usage(1, C_(f"List already exists: {listname}")) + usage(1, C_('List already exists: %(listname)s')) # Assign domain-specific attributes mlist.host_name = host_name @@ -226,8 +231,7 @@ def main(): mlist.Save() finally: - if mlist: - mlist.Unlock() + mlist.Unlock() # Now do the MTA-specific list creation tasks if mm_cfg.MTA: @@ -236,10 +240,10 @@ def main(): sys.modules[modname].create(mlist) # And send the notice to the list owner - if not args.quiet and not args.automate: - print(f"Hit enter to notify {listname} owner..."), + if not quiet and not automate: + print('Hit enter to notify %(listname)s owner...'), sys.stdin.readline() - if not args.quiet: + if not quiet: siteowner = Utils.get_site_email(mlist.host_name, 'owner') text = Utils.maketext( 'newlist.txt', @@ -256,14 +260,15 @@ def main(): otrans = i18n.get_translation() i18n.set_language(mlist.preferred_language) try: - msg = Mailman.Message.UserNotification( + msg = Message.UserNotification( owner_mail, siteowner, - _('Your new mailing list: %(listname)s') % {'listname': listname}, + _('Your new mailing list: %(listname)s'), text, mlist.preferred_language) msg.send(mlist) finally: i18n.set_translation(otrans) + if __name__ == '__main__': main() diff --git a/bin/pygettext.py b/bin/pygettext.py index 4dea6cf6..6ed2facb 100644 --- a/bin/pygettext.py +++ b/bin/pygettext.py @@ -140,7 +140,7 @@ import os import sys import time -import argparse +import getopt import tokenize import operator @@ -159,6 +159,7 @@ def _(s): return s EMPTYSTRING = '' + # The normal pot-file header. msgmerge and Emacs's po-mode work better if it's # there. pot_header = _('''\ @@ -180,67 +181,40 @@ def _(s): return s ''') -def parse_args(): - parser = argparse.ArgumentParser(description='Python equivalent of xgettext(1)') - parser.add_argument('-a', '--extract-all', action='store_true', - help='Extract all strings') - parser.add_argument('-d', '--default-domain', - help='Rename the default output file from messages.pot to name.pot') - parser.add_argument('-E', '--escape', action='store_true', - help='Replace non-ASCII characters with octal escape sequences') - parser.add_argument('-D', '--docstrings', action='store_true', - help='Extract module, class, method, and function docstrings') - parser.add_argument('-k', '--keyword', action='append', - help='Keywords to look for in addition to the default set') - parser.add_argument('-K', '--no-default-keywords', action='store_true', - help='Disable the default set of keywords') - parser.add_argument('--no-location', action='store_true', - help='Do not write filename/lineno location comments') - parser.add_argument('-n', '--add-location', action='store_true', - help='Write filename/lineno location comments') - parser.add_argument('-o', '--output', - help='Rename the default output file from messages.pot to filename') - parser.add_argument('-p', '--output-dir', - help='Output files will be placed in directory dir') - parser.add_argument('-S', '--style', choices=['GNU', 'Solaris'], - help='Specify which style to use for location comments') - parser.add_argument('-v', '--verbose', action='store_true', - help='Print the names of the files being processed') - parser.add_argument('-V', '--version', action='version', - version='%(prog)s ' + __version__) - parser.add_argument('-w', '--width', type=int, - help='Set width of output to columns') - parser.add_argument('-x', '--exclude-file', - help='Specify a file that contains a list of strings to exclude') - parser.add_argument('-X', '--no-docstrings', - help='Specify a file that contains a list of files to exclude from docstring extraction') - parser.add_argument('inputfiles', nargs='+', - help='Input files to process') - return parser.parse_args() + +def usage(code, msg=''): + if code: + fd = sys.stderr + else: + fd = sys.stdout + print >> fd, _(__doc__) % globals() + if msg: + print >> fd, msg + sys.exit(code) + escapes = [] def make_escapes(pass_iso8859): global escapes - escapes = [] + if pass_iso8859: + # Allow iso-8859 characters to pass through so that e.g. 'msgid + # "H[o-umlaut]he"' would result not result in 'msgid "H\366he"'. + # Otherwise we escape any character outside the 32..126 range. + mod = 128 + else: + mod = 256 for i in range(256): - if not pass_iso8859 and i >= 0x80: - escapes.append('\\%03o' % i) - elif i == 0: - escapes.append('\\0') - elif i == 9: - escapes.append('\\t') - elif i == 10: - escapes.append('\\n') - elif i == 13: - escapes.append('\\r') - elif i == 34: - escapes.append('\\"') - elif i == 92: - escapes.append('\\\\') - else: + if 32 <= (i % mod) <= 126: escapes.append(chr(i)) + else: + escapes.append("\\%03o" % i) + escapes[ord('\\')] = '\\\\' + escapes[ord('\t')] = '\\t' + escapes[ord('\r')] = '\\r' + escapes[ord('\n')] = '\\n' + escapes[ord('\"')] = '\\"' def escape(s): @@ -253,14 +227,7 @@ def escape(s): def safe_eval(s): # unwrap quotes, safely - r = s.strip() - if r.startswith('"""') or r.startswith("'''"): - quote = r[:3] - r = r[3:-3] - else: - quote = r[0] - r = r[1:-1] - return r + return eval(s, {'__builtins__':{}}, {}) def normalize(s): @@ -280,6 +247,7 @@ def normalize(s): return s + class TokenEater: def __init__(self, options): self.__options = options @@ -289,19 +257,32 @@ def __init__(self, options): self.__lineno = -1 self.__freshmodule = 1 self.__curfile = None - self.__keywords = options.keywords - if not options.no_default_keywords: - self.__keywords.extend(default_keywords) def __call__(self, ttype, tstring, stup, etup, line): # dispatch - self.__state(ttype, tstring, line[0]) +## import token +## print >> sys.stderr, 'ttype:', token.tok_name[ttype], \ +## 'tstring:', tstring + self.__state(ttype, tstring, stup[0]) def __waiting(self, ttype, tstring, lineno): - # ignore anything until we see the keyword - if ttype == tokenize.NAME and tstring in self.__keywords: + opts = self.__options + # Do docstring extractions, if enabled + if opts.docstrings and not opts.nodocstrings.get(self.__curfile): + # module docstring? + if self.__freshmodule: + if ttype == tokenize.STRING: + self.__addentry(safe_eval(tstring), lineno, isdocstring=1) + self.__freshmodule = 0 + elif ttype not in (tokenize.COMMENT, tokenize.NL): + self.__freshmodule = 0 + return + # class docstring? + if ttype == tokenize.NAME and tstring in ('class', 'def'): + self.__state = self.__suiteseen + return + if ttype == tokenize.NAME and tstring in opts.keywords: self.__state = self.__keywordseen - self.__lineno = lineno def __suiteseen(self, ttype, tstring, lineno): # ignore anything until we see the colon @@ -314,170 +295,250 @@ def __suitedocstring(self, ttype, tstring, lineno): self.__addentry(safe_eval(tstring), lineno, isdocstring=1) self.__state = self.__waiting elif ttype not in (tokenize.NEWLINE, tokenize.INDENT, - tokenize.COMMENT): - # there was no doc string + tokenize.COMMENT): + # there was no class docstring self.__state = self.__waiting def __keywordseen(self, ttype, tstring, lineno): - # ignore anything until we see the opening paren if ttype == tokenize.OP and tstring == '(': + self.__data = [] + self.__lineno = lineno self.__state = self.__openseen else: self.__state = self.__waiting def __openseen(self, ttype, tstring, lineno): - # ignore anything until we see the string - if ttype == tokenize.STRING: - self.__addentry(safe_eval(tstring), lineno) - self.__state = self.__waiting - elif ttype not in (tokenize.NEWLINE, tokenize.INDENT, - tokenize.COMMENT): - # there was no string + if ttype == tokenize.OP and tstring == ')': + # We've seen the last of the translatable strings. Record the + # line number of the first line of the strings and update the list + # of messages seen. Reset state for the next batch. If there + # were no strings inside _(), then just ignore this entry. + if self.__data: + self.__addentry(EMPTYSTRING.join(self.__data)) self.__state = self.__waiting + elif ttype == tokenize.STRING: + self.__data.append(safe_eval(tstring)) + # TBD: should we warn if we seen anything else? def __addentry(self, msg, lineno=None, isdocstring=0): - if msg in self.__messages: - entry = self.__messages[msg] - else: - entry = [] - self.__messages[msg] = entry - if lineno is not None: - entry.append((self.__curfile, lineno, isdocstring)) + if lineno is None: + lineno = self.__lineno + if not msg in self.__options.toexclude: + entry = (self.__curfile, lineno) + self.__messages.setdefault(msg, {})[entry] = isdocstring def set_filename(self, filename): self.__curfile = filename + self.__freshmodule = 1 def write(self, fp): options = self.__options - if options.style == options.GNU: - location_format = '#: %(filename)s:%(lineno)d' - else: - location_format = '# File: %(filename)s, line: %(lineno)d' - # - # write the header - # - header = pot_header % { - 'time': time.strftime('%Y-%m-%d %H:%M%z'), - 'version': __version__, - } - fp.write(header) - # - # Sort the entries. First sort each particular entry's locations, - # then sort all the entries by their first location. - # + timestamp = time.ctime(time.time()) + # The time stamp in the header doesn't have the same format as that + # generated by xgettext... + print >> fp, pot_header % {'time': timestamp, 'version': __version__} + # Sort the entries. First sort each particular entry's keys, then + # sort all the entries by their first item. reverse = {} for k, v in self.__messages.items(): - if not v: - continue - # v is a list of (filename, lineno, isdocstring) tuples - v.sort() - first = v[0] - reverse.setdefault(first, []).append((k, v)) - keys = sorted(reverse.keys()) - # - # Now write all the entries - # - for first in keys: - entries = reverse[first] - for k, v in entries: - if options.writelocations: - for filename, lineno, isdocstring in v: - if isdocstring: - fp.write('#. ') - fp.write(location_format % { - 'filename': filename, - 'lineno': lineno, - }) - fp.write('\n') - fp.write('msgid %s\n' % normalize(k)) - fp.write('msgstr ""\n') - fp.write('\n') - - + keys = v.keys() + keys.sort() + reverse.setdefault(tuple(keys), []).append((k, v)) + rkeys = reverse.keys() + rkeys.sort() + for rkey in rkeys: + rentries = reverse[rkey] + rentries.sort() + for k, v in rentries: + isdocstring = 0 + # If the entry was gleaned out of a docstring, then add a + # comment stating so. This is to aid translators who may wish + # to skip translating some unimportant docstrings. + if reduce(operator.__add__, v.values()): + isdocstring = 1 + # k is the message string, v is a dictionary-set of (filename, + # lineno) tuples. We want to sort the entries in v first by + # file name and then by line number. + v = v.keys() + v.sort() + if not options.writelocations: + pass + # location comments are different b/w Solaris and GNU: + elif options.locationstyle == options.SOLARIS: + for filename, lineno in v: + d = {'filename': filename, 'lineno': lineno} + print >>fp, _( + '# File: %(filename)s, line: %(lineno)d') % d + elif options.locationstyle == options.GNU: + # fit as many locations on one line, as long as the + # resulting line length doesn't exceeds 'options.width' + locline = '#:' + for filename, lineno in v: + d = {'filename': filename, 'lineno': lineno} + s = _(' %(filename)s:%(lineno)d') % d + if len(locline) + len(s) <= options.width: + locline = locline + s + else: + print >> fp, locline + locline = "#:" + s + if len(locline) > 2: + print >> fp, locline + if isdocstring: + print >> fp, '#, docstring' + print >> fp, 'msgid', normalize(k) + print >> fp, 'msgstr ""\n' + + + def main(): - args = parse_args() - + global default_keywords + try: + opts, args = getopt.getopt( + sys.argv[1:], + 'ad:DEhk:Kno:p:S:Vvw:x:X:', + ['extract-all', 'default-domain=', 'escape', 'help', + 'keyword=', 'no-default-keywords', + 'add-location', 'no-location', 'output=', 'output-dir=', + 'style=', 'verbose', 'version', 'width=', 'exclude-file=', + 'docstrings', 'no-docstrings', + ]) + except getopt.error as msg: + usage(1, msg) + + # for holding option values class Options: # constants GNU = 1 SOLARIS = 2 # defaults - extractall = args.extract_all - escape = args.escape - keywords = args.keyword or [] - outpath = args.output_dir or '' - outfile = args.output or 'messages.pot' - writelocations = not args.no_location - locationstyle = args.style == 'Solaris' and SOLARIS or GNU - verbose = args.verbose - width = args.width or 78 - excludefilename = args.exclude_file or '' - docstrings = args.docstrings + extractall = 0 # FIXME: currently this option has no effect at all. + escape = 0 + keywords = [] + outpath = '' + outfile = 'messages.pot' + writelocations = 1 + locationstyle = GNU + verbose = 0 + width = 78 + excludefilename = '' + docstrings = 0 nodocstrings = {} - if args.no_docstrings: + + options = Options() + locations = {'gnu' : options.GNU, + 'solaris' : options.SOLARIS, + } + + # parse options + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-a', '--extract-all'): + options.extractall = 1 + elif opt in ('-d', '--default-domain'): + options.outfile = arg + '.pot' + elif opt in ('-E', '--escape'): + options.escape = 1 + elif opt in ('-D', '--docstrings'): + options.docstrings = 1 + elif opt in ('-k', '--keyword'): + options.keywords.append(arg) + elif opt in ('-K', '--no-default-keywords'): + default_keywords = [] + elif opt in ('-n', '--add-location'): + options.writelocations = 1 + elif opt in ('--no-location',): + options.writelocations = 0 + elif opt in ('-S', '--style'): + options.locationstyle = locations.get(arg.lower()) + if options.locationstyle is None: + usage(1, _('Invalid value for --style: %s') % arg) + elif opt in ('-o', '--output'): + options.outfile = arg + elif opt in ('-p', '--output-dir'): + options.outpath = arg + elif opt in ('-v', '--verbose'): + options.verbose = 1 + elif opt in ('-V', '--version'): + print(_('pygettext.py (xgettext for Python) %s') % __version__) + sys.exit(0) + elif opt in ('-w', '--width'): + try: + options.width = int(arg) + except ValueError: + usage(1, _('--width argument must be an integer: %s') % arg) + elif opt in ('-x', '--exclude-file'): + options.excludefilename = arg + elif opt in ('-X', '--no-docstrings'): + fp = open(arg) try: - fp = open(args.no_docstrings) - nodocstrings = {} - for line in fp: - nodocstrings[line.strip()] = None + while 1: + line = fp.readline() + if not line: + break + options.nodocstrings[line[:-1]] = 1 + finally: fp.close() - except IOError: - pass - options = Options() - eater = TokenEater(options) - - # Make escapes dictionary - make_escapes(not options.escape) - - # Read the exclusion file, if any - excluded = {} + # calculate escapes + make_escapes(options.escape) + + # calculate all keywords + options.keywords.extend(default_keywords) + + # initialize list of strings to exclude if options.excludefilename: try: fp = open(options.excludefilename) - for line in fp: - line = line.strip() - excluded[line] = None + options.toexclude = fp.readlines() fp.close() except IOError: - pass - - # Process each input file - for filename in args.inputfiles: + print >> sys.stderr, _( + "Can't read --exclude-file: %s") % options.excludefilename + sys.exit(1) + else: + options.toexclude = [] + + # slurp through all the files + eater = TokenEater(options) + for filename in args: if filename == '-': if options.verbose: - print('Reading standard input') + print(_('Reading standard input')) fp = sys.stdin - eater.set_filename('stdin') - try: - tokenize.tokenize(fp.readline, eater) - except tokenize.TokenError as e: - print('%s: %s' % (filename, e), file=sys.stderr) - continue + closep = 0 else: if options.verbose: - print('Working on %s' % filename) + print(_('Working on %s') % filename) + fp = open(filename) + closep = 1 + try: + eater.set_filename(filename) try: - fp = open(filename) - eater.set_filename(filename) tokenize.tokenize(fp.readline, eater) + except tokenize.TokenError as e: + print('%s: %s, line %d, column %d' % ( + e[0], filename, e[1][0], e[1][1]), file=sys.stderr) + finally: + if closep: fp.close() - except IOError as e: - print('%s: %s' % (filename, e), file=sys.stderr) - continue - - # Write the output + + # write the output if options.outfile == '-': fp = sys.stdout + closep = 0 else: + if options.outpath: + options.outfile = os.path.join(options.outpath, options.outfile) fp = open(options.outfile, 'w') + closep = 1 try: eater.write(fp) finally: - if fp is not sys.stdout: + if closep: fp.close() - + if __name__ == '__main__': main() # some more test strings diff --git a/bin/qrunner b/bin/qrunner index e0ed3dfe..7b7b515e 100644 --- a/bin/qrunner +++ b/bin/qrunner @@ -73,16 +73,16 @@ operation. It is only useful for debugging if it is run separately. """ import sys -import argparse +import getopt import signal import time -import os -import threading -import traceback -from io import StringIO import paths from Mailman import mm_cfg +# Debug: Log when mm_cfg is imported in qrunner +from Mailman.Logging.Syslog import syslog +syslog('debug', 'qrunner: mm_cfg imported from %s', mm_cfg.__file__) +syslog('debug', 'qrunner: mm_cfg.GLOBAL_PIPELINE type: %s', type(mm_cfg.GLOBAL_PIPELINE).__name__ if hasattr(mm_cfg, 'GLOBAL_PIPELINE') else 'NOT FOUND') from Mailman.i18n import C_ from Mailman.Logging.Syslog import syslog from Mailman.Logging.Utils import LogStdErr @@ -94,35 +94,19 @@ COMMASPACE = ', ' AS_SUBPROC = 0 -def parse_args(): - parser = argparse.ArgumentParser(description='Run one or more qrunners, once or repeatedly.') - parser.add_argument('-r', '--runner', action='append', - help='Run the named qrunner. Format: runner[:slice:range]') - parser.add_argument('-o', '--once', action='store_true', - help='Run each named qrunner exactly once through its main loop') - parser.add_argument('-l', '--list', action='store_true', - help='Show available qrunner names and exit') - parser.add_argument('-v', '--verbose', action='store_true', - help='Spit out more debugging information to the logs/qrunner log file') - parser.add_argument('-s', '--subproc', action='store_true', - help='Run as a subprocess of mailmanctl') - return parser.parse_args() - - + def usage(code, msg=''): if code: fd = sys.stderr else: fd = sys.stdout - # Ensure PROGRAM is a string, not bytes - if isinstance(PROGRAM, bytes): - PROGRAM = PROGRAM.decode('utf-8', 'replace') print(C_(__doc__), file=fd) if msg: print(msg, file=fd) sys.exit(code) + def make_qrunner(name, slice, range, once=0): modulename = 'Mailman.Queue.' + name try: @@ -147,6 +131,7 @@ def make_qrunner(name, slice, range, once=0): return qrunner + def set_signals(loop): # Set up the SIGTERM handler for stopping the loop def sigterm_handler(signum, frame, loop=loop): @@ -154,18 +139,7 @@ def set_signals(loop): loop.stop() loop.status = signal.SIGTERM syslog('qrunner', '%s qrunner caught SIGTERM. Stopping.', loop.name()) - # Log traceback - s = StringIO() - traceback.print_stack(file=s) - syslog('error', 'Traceback on SIGTERM:\n%s', s.getvalue()) - # Force exit after 5 seconds - def force_exit(): - time.sleep(5) - syslog('qrunner', '%s qrunner forcing exit after timeout.', loop.name()) - os._exit(signal.SIGTERM) - threading.Thread(target=force_exit, daemon=True).start() signal.signal(signal.SIGTERM, sigterm_handler) - # Set up the SIGINT handler for stopping the loop. For us, SIGINT is # the same as SIGTERM, but our parent treats the exit statuses # differently (it restarts a SIGINT but not a SIGTERM). @@ -174,87 +148,91 @@ def set_signals(loop): loop.stop() loop.status = signal.SIGINT syslog('qrunner', '%s qrunner caught SIGINT. Stopping.', loop.name()) - # Log traceback - s = StringIO() - traceback.print_stack(file=s) - syslog('error', 'Traceback on SIGINT:\n%s', s.getvalue()) - # Force exit after 5 seconds - def force_exit(): - time.sleep(5) - syslog('qrunner', '%s qrunner forcing exit after timeout.', loop.name()) - os._exit(signal.SIGINT) - threading.Thread(target=force_exit, daemon=True).start() signal.signal(signal.SIGINT, sigint_handler) - # SIGHUP just tells us to close our log files. They'll be # automatically reopened at the next log print :) def sighup_handler(signum, frame, loop=loop): - try: - syslog.close() - # Reopen syslog connection - syslog.open() - syslog('qrunner', '%s qrunner caught SIGHUP. Reopening logs.', - loop.name()) - except Exception as e: - # Log any errors but don't let them propagate - print('Error in SIGHUP handler:', str(e), file=sys.stderr) + syslog.close() + syslog('qrunner', '%s qrunner caught SIGHUP. Reopening logs.', + loop.name()) signal.signal(signal.SIGHUP, sighup_handler) + def main(): global AS_SUBPROC try: - args = parse_args() - except SystemExit: - usage(1) + opts, args = getopt.getopt( + sys.argv[1:], 'hlor:vs', + ['help', 'list', 'once', 'runner=', 'verbose', 'subproc']) + except getopt.error as msg: + usage(1, msg) - if args.list: - for runnername, slices in mm_cfg.QRUNNERS: - if runnername.endswith('Runner'): - name = runnername[:-len('Runner')] - else: - name = runnername - print(C_('%(name)s runs the %(runnername)s qrunner')) - print(C_('All runs all the above qrunners')) - sys.exit(0) + def silent_unraisable_hook(unraisable): + pass - if not args.runner: - usage(1, C_('No runner specified')) + if hasattr(sys, 'unraisablehook'): + sys.unraisablehook = silent_unraisable_hook + once = 0 runners = [] - for runnerspec in args.runner: - parts = runnerspec.split(':') - if len(parts) == 1: - runner = parts[0] - slice = 1 - range = 1 - elif len(parts) == 3: - runner = parts[0] - try: - slice = int(parts[1]) - range = int(parts[2]) - except ValueError: - usage(1, 'Bad runner specification: %(runnerspec)s') - else: - usage(1, 'Bad runner specification: %(runnerspec)s') - if runner == 'All': + verbose = 0 + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-l', '--list'): for runnername, slices in mm_cfg.QRUNNERS: if runnername.endswith('Runner'): name = runnername[:-len('Runner')] else: name = runnername - runners.append((name, 1, 1)) - else: - runners.append((runner, slice, range)) + print(C_('%(name)s runs the %(runnername)s qrunner')) + print(C_('All runs all the above qrunners')) + sys.exit(0) + elif opt in ('-o', '--once'): + once = 1 + elif opt in ('-r', '--runner'): + runnerspec = arg + parts = runnerspec.split(':') + if len(parts) == 1: + runner = parts[0] + slice = 1 + range = 1 + elif len(parts) == 3: + runner = parts[0] + try: + slice = int(parts[1]) + range = int(parts[2]) + except ValueError: + usage(1, 'Bad runner specification: %(runnerspec)s') + else: + usage(1, 'Bad runner specification: %(runnerspec)s') + if runner == 'All': + for runnername, slices in mm_cfg.QRUNNERS: + runners.append((runnername, slice, range)) + else: + if runner.endswith('Runner'): + runners.append((runner, slice, range)) + else: + runners.append((runner + 'Runner', slice, range)) + elif opt in ('-s', '--subproc'): + AS_SUBPROC = 1 + elif opt in ('-v', '--verbose'): + verbose = 1 - AS_SUBPROC = args.subproc - if args.verbose: - LogStdErr('debug', 'qrunner', manual_reprime=0) - else: - LogStdErr('error', 'qrunner', manual_reprime=0) + if len(args) != 0: + usage(1) + if len(runners) == 0: + usage(1, C_('No runner name given.')) + + # Before we startup qrunners, we redirect the stderr to mailman syslog. + # We assume !AS_SUBPROC is running for debugging purpose and don't + # log errors in mailman logs/error but keep printing to stderr. + if AS_SUBPROC: + LogStdErr('error', 'qrunner', manual_reprime=0, tee_to_real_stderr=0) # Fast track for one infinite runner - if len(runners) == 1 and not args.once: + if len(runners) == 1 and not once: qrunner = make_qrunner(*runners[0]) class Loop: status = 0 @@ -269,15 +247,12 @@ def main(): # Now start up the main loop syslog('qrunner', '%s qrunner started.', loop.name()) qrunner.run() - # Only exit with SIGINT if we're stopping normally - if not qrunner._stop: - loop.status = signal.SIGINT syslog('qrunner', '%s qrunner exiting.', loop.name()) else: # Anything else we have to handle a bit more specially qrunners = [] for runner, slice, range in runners: - qrunner = make_qrunner(runner, slice, range, args.once) + qrunner = make_qrunner(runner, slice, range, 1) qrunners.append(qrunner) # This class is used to manage the main loop class Loop: @@ -298,14 +273,11 @@ def main(): # In case the SIGTERM came in the middle of this iteration if loop.isdone(): break - if args.verbose: + if verbose: syslog('qrunner', 'Now doing a %s qrunner iteration', qrunner.__class__.__bases__[0].__name__) qrunner.run() - # Only exit with SIGINT if we're stopping normally - if not qrunner._stop: - loop.status = signal.SIGINT - if args.once: + if once: break if mm_cfg.QRUNNER_SLEEP_TIME > 0: time.sleep(mm_cfg.QRUNNER_SLEEP_TIME) @@ -314,5 +286,6 @@ def main(): sys.exit(loop.status) + if __name__ == '__main__': main() diff --git a/bin/rb-archfix b/bin/rb-archfix index 2fcd55e2..7b566bb0 100644 --- a/bin/rb-archfix +++ b/bin/rb-archfix @@ -47,7 +47,7 @@ from __future__ import print_function import os import sys -import argparse +import getopt import marshal import pickle @@ -58,48 +58,43 @@ from Mailman.i18n import C_ PROGRAM = sys.argv[0] -def parse_args(): - parser = argparse.ArgumentParser(description='Reduce disk space usage for Pipermail archives.') - parser.add_argument('files', nargs='+', - help='Files to process') - return parser.parse_args() + +def usage(code, msg=''): + if code: + fd = sys.stderr + else: + fd = sys.stdout + print(C_(__doc__), file=fd) + if msg: + print(msg, file=fd) + sys.exit(code) -def load_article(pckstr): - """Load an article from a pickle string with Python 2/3 compatibility.""" - try: - return pickle.loads(pckstr, fix_imports=True, encoding='latin1') - except Exception as e: - print('Error loading article: %s' % e) - return None - - -def save_article(article): - """Save an article to a pickle string with Python 2/3 compatibility.""" + +def main(): + # get command line arguments try: - return pickle.dumps(article, protocol=4, fix_imports=True) - except Exception as e: - print('Error saving article: %s' % e) - return None - + opts, args = getopt.getopt(sys.argv[1:], 'h', ['help']) + except getopt.error as msg: + usage(1, msg) -def main(): - args = parse_args() + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) - for filename in args.files: + for filename in args: print(('processing:', filename)) fp = open(filename, 'rb') d = marshal.load(fp) fp.close() newd = {} for key, pckstr in d.items(): - article = load_article(pckstr) - if article: - try: - del article.html_body - except AttributeError: - pass - newd[key] = save_article(article) + article = pickle.loads(pckstr) + try: + del article.html_body + except AttributeError: + pass + newd[key] = pickle.dumps(article) fp = open(filename + '.tmp', 'wb') marshal.dump(newd, fp) fp.close() @@ -109,5 +104,6 @@ def main(): print('You should now run "bin/check_perms -f"') + if __name__ == '__main__': main() diff --git a/bin/remove_members b/bin/remove_members index aadd7cb3..2fd1e5c9 100755 --- a/bin/remove_members +++ b/bin/remove_members @@ -60,79 +60,120 @@ Options: """ import sys -import argparse +import getopt import paths -from Mailman import mm_cfg -from Mailman import Utils from Mailman import MailList +from Mailman import Utils from Mailman import Errors -from Mailman import i18n - -_ = i18n._ +from Mailman.i18n import C_ + def usage(code, msg=''): if code: fd = sys.stderr else: fd = sys.stdout - print(_(__doc__), file=fd) + print(C_(__doc__), file=fd) if msg: print(msg, file=fd) sys.exit(code) -def main(): - parser = argparse.ArgumentParser(description='Remove members from a mailing list.') - parser.add_argument('listname', help='Name of the mailing list') - parser.add_argument('-a', '--admin-notify', action='store_true', - help='Send admin notification') - parser.add_argument('-f', '--file', help='File containing member addresses') - parser.add_argument('-n', '--no-admin-notify', action='store_true', - help='Do not send admin notification') - parser.add_argument('-N', '--no-userack', action='store_true', - help='Do not send user acknowledgment') - parser.add_argument('-w', '--welcome-msg', action='store_true', - help='Send welcome message') - - args = parser.parse_args() - try: - mlist = MailList.MailList(args.listname, lock=1) - except Errors.MMUnknownListError: - usage(1, _('No such list "%(listname)s"')) - - if args.file: - try: - fp = open(args.file) - except IOError: - usage(1, _('Cannot open file: %(file)s')) - addrs = [] - for line in fp: - line = line.strip() - if line and not line.startswith('#'): - addrs.append(line) - fp.close() +def ReadFile(filename): + lines = [] + if filename == "-": + fp = sys.stdin + closep = False else: - addrs = sys.stdin.read().splitlines() + fp = open(filename) + closep = True + lines = filter(None, [line.strip() for line in fp.readlines()]) + if closep: + fp.close() + return lines - if not addrs: - usage(1, _('No addresses to remove')) - # Process each address - for addr in addrs: - addr = addr.strip() - if not addr or addr.startswith('#'): - continue - try: - mlist.DeleteMember(addr, admin_notif=not args.no_admin_notify, - userack=not args.no_userack) - except Errors.NotAMemberError: - print(_('%(addr)s is not a member of %(listname)s')) - except Errors.MMListError as e: - print(_('%(addr)s: %(error)s')) + +def main(): + try: + opts, args = getopt.getopt( + sys.argv[1:], 'naf:hN', + ['all', 'fromall', 'file=', 'help', 'nouserack', 'noadminack']) + except getopt.error as msg: + usage(1, msg) + + filename = None + all = False + alllists = False + # None means use list default + userack = None + admin_notif = None + + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-f', '--file'): + filename = arg + elif opt in ('-a', '--all'): + all = True + elif opt == '--fromall': + alllists = True + elif opt in ('-n', '--nouserack'): + userack = False + elif opt in ('-N', '--noadminack'): + admin_notif = False + + if len(args) < 1 and not (filename and alllists): + usage(1) + + # You probably don't want to delete all the users of all the lists -- Marc + if all and alllists: + usage(1) + + if alllists: + addresses = args + else: + listname = args[0].lower().strip() + addresses = args[1:] - mlist.Save() - mlist.Unlock() + if alllists: + listnames = Utils.list_names() + else: + listnames = [listname] + if filename: + try: + addresses = addresses + ReadFile(filename) + except IOError: + print(C_('Could not open file for reading: %(filename)s.')) + + for listname in listnames: + try: + # open locked + mlist = MailList.MailList(listname) + except Errors.MMListError: + print(C_('Error opening list %(listname)s... skipping.')) + continue + + if all: + addresses = mlist.getMembers() + + try: + for addr in addresses: + if not mlist.isMember(addr): + if not alllists: + print(C_('No such member: %(addr)s')) + continue + mlist.ApprovedDeleteMember(addr, 'bin/remove_members', + admin_notif, userack) + if alllists: + print(C_("User `%(addr)s' removed from list: %(listname)s.")) + mlist.Save() + finally: + mlist.Unlock() + + + if __name__ == '__main__': main() diff --git a/bin/reset_pw.py b/bin/reset_pw.py index 9219ec26..41dea0f0 100644 --- a/bin/reset_pw.py +++ b/bin/reset_pw.py @@ -34,36 +34,50 @@ """ import sys -import argparse +import getopt import paths from Mailman import Utils from Mailman.i18n import C_ -def parse_args(args): - parser = argparse.ArgumentParser(description='Reset the passwords for members of a mailing list.') - parser.add_argument('-v', '--verbose', action='store_true', - help='Print what the script is doing') - return parser.parse_args(args) + +def usage(code, msg=''): + if code: + fd = sys.stderr + else: + fd = sys.stdout + print(C_(__doc__.replace('%', '%%')), file=fd) + if msg: + print(msg, file=fd) + sys.exit(code) + def reset_pw(mlist, *args): - args = parse_args(args) + try: + opts, args = getopt.getopt(args, 'v', ['verbose']) + except getopt.error as msg: + usage(1, msg) + + verbose = False + for opt, args in opts: + if opt in ('-v', '--verbose'): + verbose = True listname = mlist.internal_name() - if args.verbose: + if verbose: print(C_('Changing passwords for list: %(listname)s')) for member in mlist.getMembers(): randompw = Utils.MakeRandomPassword() mlist.setMemberPassword(member, randompw) - if args.verbose: + if verbose: print(C_('New password for member %(member)40s: %(randompw)s')) mlist.Save() + if __name__ == '__main__': - print(C_(__doc__.replace('%', '%%'))) - sys.exit(0) + usage(0) diff --git a/bin/rmlist b/bin/rmlist index 1c378d92..d942a394 100755 --- a/bin/rmlist +++ b/bin/rmlist @@ -39,7 +39,7 @@ Where: import os import re import sys -import argparse +import getopt import shutil import paths @@ -48,7 +48,7 @@ from Mailman import Utils from Mailman import MailList from Mailman.i18n import C_ - + def usage(code, msg=''): if code: fd = sys.stderr @@ -60,6 +60,7 @@ def usage(code, msg=''): sys.exit(code) + def remove_it(listname, filename, msg): if os.path.islink(filename): print(C_('Removing %(msg)s')) @@ -73,24 +74,34 @@ def remove_it(listname, filename, msg): print(C_('%(listname)s %(msg)s not found as %(filename)s')) + def main(): - parser = argparse.ArgumentParser(description='Remove the components of a mailing list with impunity - beware!') - parser.add_argument('listname', help='Name of the mailing list to remove') - parser.add_argument('-a', '--archives', action='store_true', - help='Remove the list\'s archives too, or if the list has already been deleted, remove any residual archives') - - args = parser.parse_args() - listname = args.listname.lower().strip() + try: + opts, args = getopt.getopt(sys.argv[1:], 'ah', + ['archives', 'help']) + except getopt.error as msg: + usage(1, msg) + + if len(args) != 1: + usage(1) + listname = args[0].lower().strip() + + removeArchives = False + for opt, arg in opts: + if opt in ('-a', '--archives'): + removeArchives = True + elif opt in ('-h', '--help'): + usage(0) if not Utils.list_exists(listname): - if not args.archives: + if not removeArchives: usage(1, C_( 'No such list (or list already deleted): %(listname)s')) else: print(C_( 'No such list: %(listname)s. Removing its residual archives.')) - if not args.archives: + if not removeArchives: print(C_('Not removing archives. Reinvoke with -a to remove them.')) @@ -117,13 +128,13 @@ def main(): # Remove any held messages for this list for filename in os.listdir(mm_cfg.DATA_DIR): - cre = re.compile(r'^heldmsg-%s-\d+\.(pck|txt)$' % re.escape(listname), + cre = re.compile('^heldmsg-%s-\d+\.(pck|txt)$' % re.escape(listname), re.IGNORECASE) if cre.match(filename): REMOVABLES.append((os.path.join(mm_cfg.DATA_DIR, filename), C_('held message file'))) - if args.archives: + if removeArchives: REMOVABLES.extend([ (os.path.join(mm_cfg.PRIVATE_ARCHIVE_FILE_DIR, listname), C_('private archives')), @@ -139,5 +150,6 @@ def main(): remove_it(listname, dir, msg) + if __name__ == '__main__': main() diff --git a/bin/show_qfiles b/bin/show_qfiles index 9268f1ab..8125d7c2 100644 --- a/bin/show_qfiles +++ b/bin/show_qfiles @@ -34,14 +34,13 @@ Example: show_qfiles qfiles/shunt/*.pck from __future__ import print_function import sys -import argparse +import getopt from pickle import load -import pickle import paths from Mailman.i18n import C_ - + def usage(code, msg=''): if code: fd = sys.stderr @@ -53,52 +52,38 @@ def usage(code, msg=''): sys.exit(code) + def main(): - parser = argparse.ArgumentParser(description='Show the contents of one or more Mailman queue files.') - parser.add_argument('qfiles', nargs='+', help='Queue files to display') - parser.add_argument('-q', '--quiet', action='store_true', - help='Don\'t print helpful message delimiters') - - args = parser.parse_args() - - for filename in args.qfiles: - if not args.quiet: + try: + opts, args = getopt.getopt(sys.argv[1:], 'hq', ['help', 'quiet']) + except getopt.error as msg: + usage(1, msg) + + quiet = False + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-q', '--quiet'): + quiet = True + + if not args: + usage(1, "Not enough arguments") + + for filename in args: + if not quiet: print(('====================>', filename)) + fp = open(filename,'rb') if filename.endswith(".pck"): - try: - with open(filename, 'rb') as fp: - try: - # Try UTF-8 first for newer files - data = load(fp, fix_imports=True, encoding='utf-8') - if isinstance(data, tuple) and len(data) == 2: - msg, metadata = data - else: - msg = data - metadata = {} - except (UnicodeDecodeError, pickle.UnpicklingError): - # Fall back to latin1 for older files - fp.seek(0) - data = load(fp, fix_imports=True, encoding='latin1') - if isinstance(data, tuple) and len(data) == 2: - msg, metadata = data - else: - msg = data - metadata = {} - - # Handle the message output - if isinstance(msg, str): - sys.stdout.write(msg) - elif hasattr(msg, 'as_string'): - sys.stdout.write(msg.as_string()) - else: - sys.stdout.write(str(msg)) - except Exception as e: - print('Error reading pickle file %s: %s' % (filename, str(e)), file=sys.stderr) - sys.exit(1) + msg = load(fp) + data = load(fp) + if data.get('_parsemsg'): + sys.stdout.write(msg) + else: + sys.stdout.write(msg.as_string()) else: - with open(filename) as fp: - sys.stdout.write(fp.read()) + sys.stdout.write(fp.read()) + if __name__ == '__main__': main() diff --git a/bin/sync_members b/bin/sync_members index efbe42a4..71a69638 100755 --- a/bin/sync_members +++ b/bin/sync_members @@ -77,9 +77,10 @@ Where `options' are: """ import sys + import paths +# Import this /after/ paths so that the sys.path is properly hacked import email.utils -import argparse from Mailman import MailList from Mailman import Errors @@ -96,9 +97,6 @@ def usage(code, msg=''): fd = sys.stderr else: fd = sys.stdout - # Ensure PROGRAM is a string, not bytes - if isinstance(PROGRAM, bytes): - PROGRAM = PROGRAM.decode('utf-8', 'replace') print(C_(__doc__), file=fd) if msg: print(msg, file=fd) @@ -106,107 +104,187 @@ def usage(code, msg=''): -def parse_args(): - parser = argparse.ArgumentParser(description=C_('Synchronize a mailing list\'s membership with a flat file.')) - - parser.add_argument('-n', '--no-change', - action='store_true', - help=C_('Don\'t actually make the changes. Instead, print out what would be done to the list.')) - - parser.add_argument('-w', '--welcome-msg', - nargs='?', - const='yes', - choices=['yes', 'no'], - help=C_('Sets whether or not to send the newly added members a welcome message, overriding whatever the list\'s `send_welcome_msg` setting is.')) - - parser.add_argument('-g', '--goodbye-msg', - nargs='?', - const='yes', - choices=['yes', 'no'], - help=C_('Sets whether or not to send the goodbye message to removed members, overriding whatever the list\'s `send_goodbye_msg` setting is.')) - - parser.add_argument('-d', '--digest', - nargs='?', - const='yes', - choices=['yes', 'no'], - help=C_('Selects whether to make newly added members receive messages in digests.')) - - parser.add_argument('-a', '--notifyadmin', - nargs='?', - const='yes', - choices=['yes', 'no'], - help=C_('Specifies whether the admin should be notified for each subscription or unsubscription.')) - - parser.add_argument('-f', '--file', - required=True, - help=C_('The flat file to synchronize against. Email addresses must appear one per line. Use \'-\' for stdin.')) - - parser.add_argument('listname', - help=C_('The list to synchronize.')) - - args = parser.parse_args() - - # Convert yes/no options to boolean values - if args.welcome_msg: - args.welcome_msg = args.welcome_msg.lower() == 'yes' - if args.goodbye_msg: - args.goodbye_msg = args.goodbye_msg.lower() == 'yes' - if args.digest: - args.digest = args.digest.lower() == 'yes' - if args.notifyadmin: - args.notifyadmin = args.notifyadmin.lower() == 'yes' - - return args +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, C_('Bad choice: %(yesno)s')) + # no return def main(): - args = parse_args() + dryrun = 0 + digest = 0 + welcome = None + goodbye = None + filename = None + listname = None + notifyadmin = None - # Get the list name - listname = args.listname + # 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(C_('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, C_('Only one -f switch allowed')) + try: + filename = sys.argv[i+1] + except IndexError: + usage(1, C_('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, C_('Illegal option: %(opt)s')) + else: + try: + listname = sys.argv[i].lower() + i += 1 + except IndexError: + usage(1, C_('No listname given')) + break - # Get the list object - try: - mlist = MailList.MailList(listname, lock=1) - except Errors.MMUnknownListError: - usage(1, C_('No such list: %(listname)s')) - - # Get the members to sync - members = [] - if args.file == '-': - members = sys.stdin.read().splitlines() + if listname is None or filename is None: + usage(1, C_('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: - with open(args.file) as fp: - members = fp.read().splitlines() - except IOError: - usage(1, C_('Cannot open file: %(file)s') % - {'file': args.file}) - - # Process each member - for member in members: - member = member.strip() - if not member or member.startswith('#'): - continue - # Convert email address to lowercase - member = member.lower() + fp = open(filename) + except IOError as msg: + usage(1, C_('Cannot read address file: %(filename)s: %(msg)s')) try: - mlist.SyncMember(member, args.digest, args.moderate, - args.text, args.userack, args.notifyadmin, - args.welcome_msg, args.language) - except Errors.MMAlreadyAMember: - print(C_('%(member)s is already a member of %(listname)s')) - except Errors.MMHostileAddress: - print(C_('%(member)s is a hostile address')) - except Errors.MMInvalidEmailAddress: - print(C_('%(member)s is not a valid email address')) - except Errors.MMBadEmailError: - print(C_('%(member)s is not a valid email address')) - except Errors.MMListError as e: - print(C_('%(member)s: %(error)s')) - - mlist.Save() - mlist.Unlock() + 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(C_('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(C_('Invalid : %(addr)30s')) + invalid = 1 + if invalid: + print(C_('You must fix the preceding invalid addresses first.')) + sys.exit(1) + + # get the locked list object + try: + mlist = MailList.MailList(listname) + except Errors.MMListError as e: + print(C_('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 laddr in addrs: + del addrs[laddr] + matches[laddr] = 1 + elif not matches.has_key(laddr): + needsadding[laddr] = (name, addr) + + if not needsadding and not addrs: + print(C_('Nothing to do.')) + sys.exit(0) + + enc = sys.getdefaultencoding() + # 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) + name = name.encode(enc, 'replace') + s = email.utils.formataddr((name, addr)).encode(enc, 'replace') + print(C_('Added : %(s)s')) + except Errors.MMAlreadyAMember: + pass + except Errors.MembershipIsBanned as pattern: + print(('%s:' % addr), C_( + 'Banned address (matched %(pattern)s)')) + + for laddr, addr in addrs.items(): + # Should be a member, otherwise our test above is broken + name = mlist.getMemberName(laddr) or '' + 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 = name.encode(enc, 'replace') + s = email.utils.formataddr((name, addr)).encode(enc, 'replace') + print(C_('Removed: %(s)s')) + + mlist.Save() + finally: + mlist.Unlock() if __name__ == '__main__': diff --git a/bin/transcheck b/bin/transcheck index 096f0613..5ec19a47 100755 --- a/bin/transcheck +++ b/bin/transcheck @@ -34,7 +34,7 @@ from __future__ import print_function import sys import re import os -import argparse +import getopt import paths from Mailman.i18n import C_ @@ -42,14 +42,7 @@ from Mailman.i18n import C_ program = sys.argv[0] -def parse_args(): - parser = argparse.ArgumentParser(description='Check a given Mailman translation.') - parser.add_argument('lang', help='Country code (e.g. "it" for Italy)') - parser.add_argument('-q', '--quiet', action='store_true', - help='Ask for a brief summary') - return parser.parse_args() - - + def usage(code, msg=''): if code: fd = sys.stderr @@ -61,6 +54,7 @@ def usage(code, msg=''): sys.exit(code) + class TransChecker: "check a translation comparing with the original string" def __init__(self, regexp, escaped=None): @@ -126,6 +120,7 @@ class TransChecker: self.errs = [] + class POParser: "parse a .po file extracting msgids and msgstrs" def __init__(self, filename=""): @@ -281,6 +276,7 @@ class POParser: + def check_file(translatedFile, originalFile, html=0, quiet=0): """check a translated template against the original one search also tags if html is not zero""" @@ -327,6 +323,7 @@ def check_file(translatedFile, originalFile, html=0, quiet=0): return n + def check_po(file, quiet=0): "scan the po file comparing msgids with msgstrs" n = 0 @@ -348,56 +345,70 @@ def check_po(file, quiet=0): p.close() return n + def main(): try: - args = parse_args() - except SystemExit: + opts, args = getopt.getopt(sys.argv[1:], 'qh', ['quiet', 'help']) + except getopt.error as msg: + usage(1, msg) + + quiet = 0 + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-q', '--quiet'): + quiet = 1 + + if len(args) != 1: usage(1) - lang = args.lang - quiet = args.quiet + lang = args[0] - # Check if the language directory exists - lang_dir = os.path.join(paths.prefix, 'messages', lang) - if not os.path.isdir(lang_dir): - usage(1, C_('Language directory %(lang_dir)s does not exist')) + isHtml = re.compile("\.html$"); + isTxt = re.compile("\.txt$"); - # Initialize checkers - var_checker = TransChecker(r'%\([^\)]+\)s') - tag_checker = TransChecker(r'<[^>]+>', r'&[^;]+;') + numerrors = 0 + numfiles = 0 + try: + files = os.listdir("templates/" + lang + "/") + except: + print("can't open templates/%s/" % lang) + for file in files: + fileEN = "templates/en/" + file + fileIT = "templates/" + lang + "/" + file + errlist = [] + if isHtml.search(file): + if not quiet: + print("HTML checking " + fileIT + "... ") + n = check_file(fileIT, fileEN, html=1, quiet=quiet) + if n: + numerrors += n + numfiles += 1 + elif isTxt.search(file): + if not quiet: + print("TXT checking " + fileIT + "... ") + n = check_file(fileIT, fileEN, html=0, quiet=quiet) + if n: + numerrors += n + numfiles += 1 - # Parse the .po file - po_file = os.path.join(lang_dir, 'mailman.po') - if not os.path.isfile(po_file): - usage(1, C_('PO file %(po_file)s does not exist')) + else: + continue - parser = POParser(po_file) - while parser.parse(): - var_checker.checkin(parser.msgid) - var_checker.checkout(parser.msgstr) - tag_checker.checkin(parser.msgid) - tag_checker.checkout(parser.msgstr) + file = "messages/" + lang + "/LC_MESSAGES/mailman.po" + if not quiet: + print("PO checking " + file + "... ") + n = check_po(file, quiet=quiet) + if n: + numerrors += n + numfiles += 1 - # Print results if quiet: - print("%(lang)s: %(var_status)s %(tag_status)s" % { - 'lang': lang, - 'var_status': var_checker.status(), - 'tag_status': tag_checker.status() + print("%(errs)u warnings in %(files)u files" % { + 'errs': numerrors, + 'files': numfiles }) - else: - print(C_('Translation check for %(lang)s:'), file=sys.stderr) - print(C_('Variables: %(var_status)s'), file=sys.stderr) - if var_checker.errs: - print(var_checker.errorsAsString(), file=sys.stderr) - print(C_('Tags: %(tag_status)s'), file=sys.stderr) - if tag_checker.errs: - print(tag_checker.errorsAsString(), file=sys.stderr) - - # Exit with error if there are any issues - if var_checker.errs or tag_checker.errs: - sys.exit(1) - + if __name__ == '__main__': main() diff --git a/bin/unshunt b/bin/unshunt index 36cc75f9..5ceb1197 100644 --- a/bin/unshunt +++ b/bin/unshunt @@ -33,33 +33,46 @@ will result in losing all the messages in that queue. """ import sys -import argparse +import getopt import paths from Mailman import mm_cfg from Mailman.Queue.sbcache import get_switchboard from Mailman.i18n import C_ +PROGRAM = sys.argv[0] + def usage(code, msg=''): if code: fd = sys.stderr else: fd = sys.stdout - print(fd, C_(__doc__, file=fd)) + print(C_(__doc__), file=fd) if msg: print(msg, file=fd) sys.exit(code) + def main(): - parser = argparse.ArgumentParser(description='Move a message from the shunt queue to the original queue.') - parser.add_argument('directory', nargs='?', default=mm_cfg.SHUNTQUEUE_DIR, - help='Directory to dequeue from (default: %(default)s)') - - args = parser.parse_args() + try: + opts, args = getopt.getopt(sys.argv[1:], 'h', ['help']) + except getopt.error as msg: + usage(1, msg) + + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + + if len(args) == 0: + qdir = mm_cfg.SHUNTQUEUE_DIR + elif len(args) == 1: + qdir = args[0] + else: + usage(1) - sb = get_switchboard(args.directory) + sb = get_switchboard(qdir) sb.recover_backup_files() for filebase in sb.files(): try: @@ -70,12 +83,12 @@ def main(): except Exception as e: # If there are any unshunting errors, log them and continue trying # other shunted messages. - print(C_( - 'Cannot unshunt message %(filebase)s, skipping:\n%(e)s'), file=sys.stderr) + print(C_('Cannot unshunt message %(filebase)s, skipping:\n%(e)s'), file=sys.stderr) else: # Unlink the .bak file left by dequeue() sb.finish(filebase) + if __name__ == '__main__': main() diff --git a/bin/update b/bin/update index f5788e5e..4577c845 100755 --- a/bin/update +++ b/bin/update @@ -30,41 +30,34 @@ Options: -h/--help Print this text and exit. - -l/--lowercase - Convert all member email addresses to lowercase. - Use this script to help you update to the latest release of Mailman from some previous version. It knows about versions back to 1.0b4 (?). """ -from __future__ import print_function, absolute_import, division, unicode_literals - import os import sys import time import errno -import argparse +import getopt import shutil import pickle import marshal import paths +import email import email.errors -from Mailman.Message import Message sys.path.append("@VAR_PREFIX@/Mailman") from Mailman import mm_cfg from Mailman import Utils from Mailman import MailList +from Mailman import Message from Mailman import Pending -from Mailman.LockFile import TimeOutError, AlreadyLockedError +from Mailman.LockFile import TimeOutError from Mailman.i18n import C_ from Mailman.Queue.Switchboard import Switchboard from Mailman.OldStyleMemberships import OldStyleMemberships from Mailman.MemberAdaptor import BYBOUNCE, ENABLED -from Mailman.Bouncer import _BounceInfo -from Mailman.MemberAdaptor import UNKNOWN -from Mailman.Logging.Syslog import syslog FRESH = 0 NOTFRESH = -1 @@ -73,17 +66,7 @@ LMVFILE = os.path.join(mm_cfg.DATA_DIR, 'last_mailman_version') PROGRAM = sys.argv[0] -def parse_args(): - parser = argparse.ArgumentParser(description='Perform all necessary upgrades.') - parser.add_argument('-f', '--force', action='store_true', - help='Force running the upgrade procedures') - parser.add_argument('-l', '--lowercase', action='store_true', - help='Convert all member email addresses to lowercase') - parser.add_argument('-v', '--verbose', action='store_true', - help='Enable verbose output') - return parser.parse_args() - - + def calcversions(): # Returns a tuple of (lastversion, thisversion). If the last version # could not be determined, lastversion will be FRESH or NOTFRESH, @@ -98,9 +81,6 @@ def calcversions(): fp = open(LMVFILE, 'rb') data = fp.read() fp.close() - # Ensure data is a string - if isinstance(data, bytes): - data = data.decode('utf-8', 'replace') lastversion = int(data, 16) except (IOError, ValueError): pass @@ -116,19 +96,17 @@ def calcversions(): return (lastversion, thisversion) + def makeabs(relpath): return os.path.join(mm_cfg.PREFIX, relpath) def make_varabs(relpath): return os.path.join(mm_cfg.VAR_PREFIX, relpath) - + def move_language_templates(mlist): listname = mlist.internal_name() - # Ensure listname is a string - if isinstance(listname, bytes): - listname = listname.decode('utf-8', 'replace') - print('Fixing language templates: %s' % listname) + print(C_('Fixing language templates: %(listname)s')) # Mailman 2.1 has a new cascading search for its templates, defined and # described in Utils.py:maketext(). Putting templates in the top level # templates/ subdir or the lists/ subdir is deprecated and no @@ -210,13 +188,9 @@ def move_language_templates(mlist): gtemplate + '.prev')) + def dolist(listname): errors = 0 - # Ensure listname is a string and convert to lowercase - if isinstance(listname, bytes): - listname = listname.decode('utf-8', 'replace') - listname = listname.lower() - print('Updating mailing list: %s' % listname) mlist = MailList.MailList(listname, lock=0) try: mlist.Lock(0.5) @@ -225,11 +199,6 @@ def dolist(listname): '%(listname)s'), file=sys.stderr) return 1 - # Convert member addresses to lowercase if requested - if args.lowercase: - print('Converting member addresses to lowercase: %s' % listname) - mlist.convert_member_addresses_to_lowercase() - # Sanity check the invariant that every BYBOUNCE disabled member must have # bounce information. Some earlier betas broke this. BAW: we're # submerging below the MemberAdaptor interface, so skip this if we're not @@ -296,7 +265,7 @@ to You can integrate that into the archives if you want by using the 'arch' script. -""") % (listname, o_pri_mbox_file, o_pub_mbox_file, +""") % (mlist._internal_name, o_pri_mbox_file, o_pub_mbox_file, o_pub_mbox_file)) os.rename(o_pub_mbox_file, "%s.preb6" % (o_pub_mbox_file)) else: @@ -310,7 +279,7 @@ archive file (%s) as the active one, and renaming You can integrate that into the archives if you want by using the 'arch' script. -""") % (listname, o_pub_mbox_file, o_pri_mbox_file, +""") % (mlist._internal_name, o_pub_mbox_file, o_pri_mbox_file, o_pri_mbox_file)) os.rename(o_pri_mbox_file, "%s.preb6" % (o_pri_mbox_file)) # @@ -408,6 +377,7 @@ script. return 0 + def archive_path_fixer(unused_arg, dir, files): # Passed to os.path.walk to fix the perms on old html archives. for f in files: @@ -439,7 +409,7 @@ def remove_old_sources(module): except os.error as rest: print(C_("couldn't remove old file %(pyc)s -- %(rest)s")) - + def update_qfiles(): print('updating old qfiles') prefix = str(time.time()) + '+' @@ -487,6 +457,7 @@ def update_qfiles(): print(C_('Warning! Not a directory: %(dirpath)s')) + # Implementations taken from the pre-2.1.5 Switchboard def ext_read(filename): fp = open(filename, 'rb') @@ -494,27 +465,6 @@ def ext_read(filename): # Update from version 2 files if d.get('version', 0) == 2: del d['filebase'] - - # Convert any bytes in the loaded data to strings - for key, value in list(d.items()): - if isinstance(key, bytes): - del d[key] - key = key.decode('utf-8', 'replace') - if isinstance(value, bytes): - value = value.decode('utf-8', 'replace') - elif isinstance(value, list): - value = [v.decode('utf-8', 'replace') if isinstance(v, bytes) else v for v in value] - elif isinstance(value, dict): - new_dict = {} - for k, v in value.items(): - if isinstance(k, bytes): - k = k.decode('utf-8', 'replace') - if isinstance(v, bytes): - v = v.decode('utf-8', 'replace') - new_dict[k] = v - value = new_dict - d[key] = value - # Do the reverse conversion (repr -> float) for attr in ['received_time']: try: @@ -535,6 +485,12 @@ def dequeue(filebase): msgfile = os.path.join(filebase + '.msg') pckfile = os.path.join(filebase + '.pck') dbfile = os.path.join(filebase + '.db') + # Now we are going to read the message and metadata for the given + # filebase. We want to read things in this order: first, the metadata + # file to find out whether the message is stored as a pickle or as + # plain text. Second, the actual message file. However, we want to + # first unlink the message file and then the .db file, because the + # qrunner only cues off of the .db file msg = None try: data = ext_read(dbfile) @@ -542,22 +498,12 @@ def dequeue(filebase): except EnvironmentError as e: if e.errno != errno.ENOENT: raise data = {} - - # Convert any bytes in the data dict to strings - for key, value in list(data.items()): - if isinstance(key, bytes): - del data[key] - key = key.decode('utf-8', 'replace') - if isinstance(value, bytes): - value = value.decode('utf-8', 'replace') - data[key] = value - - # Between 2.1b4 and 2.1b5, the `rejection-notice` key in the metadata - # was renamed to `rejection_notice` + # Between 2.1b4 and 2.1b5, the `rejection-notice' key in the metadata + # was renamed to `rejection_notice', since dashes in the keys are not + # supported in METAFMT_ASCII. if data.get('rejection-notice', None) is not None: data['rejection_notice'] = data['rejection-notice'] del data['rejection-notice'] - msgfp = None try: try: @@ -567,15 +513,6 @@ def dequeue(filebase): # There was no .db file. Is this a post 2.1.5 .pck? try: data = pickle.load(msgfp, fix_imports=True, encoding='latin1') - # Convert any bytes in the loaded data to strings - if isinstance(data, dict): - for key, value in list(data.items()): - if isinstance(key, bytes): - del data[key] - key = key.decode('utf-8', 'replace') - if isinstance(value, bytes): - value = value.decode('utf-8', 'replace') - data[key] = value except EOFError: pass os.unlink(pckfile) @@ -584,15 +521,20 @@ def dequeue(filebase): msgfp = None try: msgfp = open(msgfile, 'rb') - msg = Message_from_file(msgfp) + msg = email.message_from_file(msgfp, Message.Message) os.unlink(msgfile) except EnvironmentError as e: if e.errno != errno.ENOENT: raise except (email.errors.MessageParseError, ValueError) as e: + # This message was unparsable, most likely because its + # MIME encapsulation was broken. For now, there's not + # much we can do about it. print(C_('message is unparsable: %(filebase)s')) msgfp.close() msgfp = None if mm_cfg.QRUNNER_SAVE_BAD_MESSAGES: + # Cheapo way to ensure the directory exists w/ the + # proper permissions. sb = Switchboard(mm_cfg.BADQUEUE_DIR) os.rename(msgfile, os.path.join( mm_cfg.BADQUEUE_DIR, filebase + '.txt')) @@ -600,6 +542,7 @@ def dequeue(filebase): os.unlink(msgfile) msg = data = None except EOFError: + # For some reason the pckfile was empty. Just delete it. print(C_('Warning! Deleting empty .pck file: %(pckfile)s')) os.unlink(pckfile) finally: @@ -608,6 +551,7 @@ def dequeue(filebase): return msg, data + def update_pending(): file20 = os.path.join(mm_cfg.DATA_DIR, 'pending_subscriptions.db') file214 = os.path.join(mm_cfg.DATA_DIR, 'pending.pck') @@ -622,21 +566,6 @@ def update_pending(): db = marshal.load(fp) # Convert to the pre-Mailman 2.1.5 format db = Pending._update(db) - # Convert any bytes to strings - if isinstance(db, dict): - new_db = {} - for key, value in db.items(): - if isinstance(key, bytes): - key = key.decode('utf-8', 'replace') - if isinstance(value, bytes): - value = value.decode('utf-8', 'replace') - elif isinstance(value, (list, tuple)): - value = list(value) # Convert tuple to list for modification - for i, v in enumerate(value): - if isinstance(v, bytes): - value[i] = v.decode('utf-8', 'replace') - new_db[key] = value - db = new_db if db is None: # Try to load the Mailman 2.1.x where x < 5, file try: @@ -646,21 +575,6 @@ def update_pending(): else: print('Updating Mailman 2.1.4 pending.pck database') db = pickle.load(fp, fix_imports=True, encoding='latin1') - # Convert any bytes to strings - if isinstance(db, dict): - new_db = {} - for key, value in db.items(): - if isinstance(key, bytes): - key = key.decode('utf-8', 'replace') - if isinstance(value, bytes): - value = value.decode('utf-8', 'replace') - elif isinstance(value, (list, tuple)): - value = list(value) # Convert tuple to list for modification - for i, v in enumerate(value): - if isinstance(v, bytes): - value[i] = v.decode('utf-8', 'replace') - new_db[key] = value - db = new_db if db is None: print('Nothing to do.') return @@ -748,455 +662,162 @@ def update_pending(): if e.errno != errno.ENOENT: raise -def domsort(addr): - # Sort email addresses by domain name - return addr.split('@')[-1] - - -def init_digest_vars(mlist): - """Initialize missing digest-related variables with default values.""" - # List of digest variables and their default values - digest_vars = { - 'digestable': True, - 'nondigestable': mm_cfg.DEFAULT_NONDIGESTABLE, - 'digest_volume': 1, - 'digest_issue': 1, - 'digest_last_sent_at': 0, - 'digest_next_due_at': 0, - 'digest_volume_frequency': mm_cfg.DEFAULT_DIGEST_VOLUME_FREQUENCY, - 'digest_members': {}, - 'members': {}, # Regular members dictionary - 'user_options': {}, # User preferences - 'language': {}, # User language preferences - 'usernames': {}, # Username mappings - 'passwords': {}, # Password storage - 'bounce_info': {}, # Bounce information - 'delivery_status': {}, # Delivery status information - 'new_member_options': mm_cfg.DEFAULT_NEW_MEMBER_OPTIONS, - 'respond_to_post_requests': mm_cfg.DEFAULT_RESPOND_TO_POST_REQUESTS, - 'advertised': mm_cfg.DEFAULT_LIST_ADVERTISED, - 'max_num_recipients': mm_cfg.DEFAULT_MAX_NUM_RECIPIENTS, - 'max_message_size': mm_cfg.DEFAULT_MAX_MESSAGE_SIZE, - 'host_name': mm_cfg.DEFAULT_HOST_NAME or mm_cfg.DEFAULT_EMAIL_HOST, - 'web_page_url': mm_cfg.DEFAULT_URL_PATTERN % mm_cfg.DEFAULT_URL_HOST, - 'owner': [], # List owners - 'moderator': [], # List moderators - 'reply_goes_to_list': mm_cfg.DEFAULT_REPLY_GOES_TO_LIST, - 'reply_to_address': '', - 'first_strip_reply_to': mm_cfg.DEFAULT_FIRST_STRIP_REPLY_TO, - 'admin_immed_notify': mm_cfg.DEFAULT_ADMIN_IMMED_NOTIFY, - 'admin_notify_mchanges': mm_cfg.DEFAULT_ADMIN_NOTIFY_MCHANGES, - 'require_explicit_destination': mm_cfg.DEFAULT_REQUIRE_EXPLICIT_DESTINATION, - 'acceptable_aliases': mm_cfg.DEFAULT_ACCEPTABLE_ALIASES, - 'umbrella_list': mm_cfg.DEFAULT_UMBRELLA_LIST, - 'umbrella_member_suffix': mm_cfg.DEFAULT_UMBRELLA_MEMBER_ADMIN_SUFFIX, - 'regular_exclude_lists': mm_cfg.DEFAULT_REGULAR_EXCLUDE_LISTS, - 'regular_exclude_ignore': mm_cfg.DEFAULT_REGULAR_EXCLUDE_IGNORE, - 'regular_include_lists': mm_cfg.DEFAULT_REGULAR_INCLUDE_LISTS, - 'send_reminders': mm_cfg.DEFAULT_SEND_REMINDERS, - 'send_welcome_msg': mm_cfg.DEFAULT_SEND_WELCOME_MSG, - 'send_goodbye_msg': mm_cfg.DEFAULT_SEND_GOODBYE_MSG, - 'bounce_matching_headers': mm_cfg.DEFAULT_BOUNCE_MATCHING_HEADERS, - 'header_filter_rules': [], - 'from_is_list': mm_cfg.DEFAULT_FROM_IS_LIST, - 'anonymous_list': mm_cfg.DEFAULT_ANONYMOUS_LIST, - 'real_name': mlist.internal_name()[0].upper() + mlist.internal_name()[1:], - 'description': '', - 'info': '', - 'welcome_msg': '', - 'goodbye_msg': '', - 'subscribe_policy': mm_cfg.DEFAULT_SUBSCRIBE_POLICY, - 'subscribe_auto_approval': mm_cfg.DEFAULT_SUBSCRIBE_AUTO_APPROVAL, - 'unsubscribe_policy': mm_cfg.DEFAULT_UNSUBSCRIBE_POLICY, - 'private_roster': mm_cfg.DEFAULT_PRIVATE_ROSTER, - 'obscure_addresses': mm_cfg.DEFAULT_OBSCURE_ADDRESSES, - 'admin_member_chunksize': mm_cfg.DEFAULT_ADMIN_MEMBER_CHUNKSIZE, - 'administrivia': mm_cfg.DEFAULT_ADMINISTRIVIA, - 'drop_cc': mm_cfg.DEFAULT_DROP_CC, - 'preferred_language': mm_cfg.DEFAULT_SERVER_LANGUAGE, - 'available_languages': [], - 'include_rfc2369_headers': 1, - 'include_list_post_header': 1, - 'include_sender_header': 1, - 'filter_mime_types': mm_cfg.DEFAULT_FILTER_MIME_TYPES, - 'pass_mime_types': mm_cfg.DEFAULT_PASS_MIME_TYPES, - 'filter_filename_extensions': mm_cfg.DEFAULT_FILTER_FILENAME_EXTENSIONS, - 'pass_filename_extensions': mm_cfg.DEFAULT_PASS_FILENAME_EXTENSIONS, - 'filter_content': mm_cfg.DEFAULT_FILTER_CONTENT, - 'collapse_alternatives': mm_cfg.DEFAULT_COLLAPSE_ALTERNATIVES, - 'convert_html_to_plaintext': mm_cfg.DEFAULT_CONVERT_HTML_TO_PLAINTEXT, - 'filter_action': mm_cfg.DEFAULT_FILTER_ACTION, - 'personalize': 0, - 'default_member_moderation': mm_cfg.DEFAULT_DEFAULT_MEMBER_MODERATION, - 'emergency': 0, - 'member_verbosity_threshold': mm_cfg.DEFAULT_MEMBER_VERBOSITY_THRESHOLD, - 'member_verbosity_interval': mm_cfg.DEFAULT_MEMBER_VERBOSITY_INTERVAL, - 'member_moderation_action': 0, - 'member_moderation_notice': '', - 'dmarc_moderation_action': mm_cfg.DEFAULT_DMARC_MODERATION_ACTION, - 'dmarc_quarantine_moderation_action': mm_cfg.DEFAULT_DMARC_QUARANTINE_MODERATION_ACTION, - 'dmarc_none_moderation_action': mm_cfg.DEFAULT_DMARC_NONE_MODERATION_ACTION, - 'dmarc_moderation_notice': '', - 'dmarc_moderation_addresses': [], - 'dmarc_wrapped_message_text': mm_cfg.DEFAULT_DMARC_WRAPPED_MESSAGE_TEXT, - 'equivalent_domains': mm_cfg.DEFAULT_EQUIVALENT_DOMAINS, - 'accept_these_nonmembers': [], - 'hold_these_nonmembers': [], - 'reject_these_nonmembers': [], - 'discard_these_nonmembers': [], - 'forward_auto_discards': mm_cfg.DEFAULT_FORWARD_AUTO_DISCARDS - } - - # Initialize any missing variables - for var, default in digest_vars.items(): - if not hasattr(mlist, var): + +def main(): + errors = 0 + # get rid of old stuff + print('getting rid of old source files') + for mod in ('Mailman/Archiver.py', 'Mailman/HyperArch.py', + 'Mailman/HyperDatabase.py', 'Mailman/pipermail.py', + 'Mailman/smtplib.py', 'Mailman/Cookie.py', + 'bin/update_to_10b6', 'scripts/mailcmd', + 'scripts/mailowner', 'mail/wrapper', 'Mailman/pythonlib', + 'cgi-bin/archives', 'Mailman/MailCommandHandler'): + remove_old_sources(mod) + listnames = Utils.list_names() + if not listnames: + print('no lists == nothing to do, exiting') + return + # + # for people with web archiving, make sure the directories + # in the archiving are set with proper perms for b6. + # + if os.path.isdir("%s/public_html/archives" % mm_cfg.PREFIX): + print(C_("""\ +fixing all the perms on your old html archives to work with b6 +If your archives are big, this could take a minute or two...""")) + os.path.walk("%s/public_html/archives" % mm_cfg.PREFIX, + archive_path_fixer, "") + print('done') + for listname in listnames: + print(C_('Updating mailing list: %(listname)s')) + errors = errors + dolist(listname) + print + print('Updating Usenet watermarks') + wmfile = os.path.join(mm_cfg.DATA_DIR, 'gate_watermarks') + try: + fp = open(wmfile, 'rb') + except IOError: + print('- nothing to update here') + else: + d = marshal.load(fp) + fp.close() + for listname in d.keys(): + if listname not in listnames: + # this list no longer exists + continue + mlist = MailList.MailList(listname, lock=0) try: - # If default is a call to mm_cfg, try to get the attribute - if isinstance(default, str) and 'mm_cfg.' in default: - try: - default = eval(default) - except AttributeError: - print(C_('Warning: mm_cfg attribute %(attr)s not found, using empty value') % { - 'attr': default.split('mm_cfg.')[1] - }) - default = None - - print(C_('Initializing missing variable %(var)s for list %(listname)s') % { - 'var': var, - 'listname': mlist.internal_name() - }) - setattr(mlist, var, default) - except Exception as e: - print(C_('Warning: Could not initialize %(var)s: %(error)s') % { - 'var': var, - 'error': str(e) - }) + mlist.Lock(0.5) + except TimeOutError: + print(C_( + 'WARNING: could not acquire lock for list: %(listname)s', file=sys.stderr)) + errors = errors + 1 + else: + # Pre 1.0b7 stored 0 in the gate_watermarks file to indicate + # that no gating had been done yet. Without coercing this to + # None, the list could now suddenly get flooded. + mlist.usenet_watermark = d[listname] or None + mlist.Save() + mlist.Unlock() + os.unlink(wmfile) + print('- usenet watermarks updated and gate_watermarks removed') + # In Mailman 2.1, the pending database format and file name changed, but + # in Mailman 2.1.5 it changed again. This should update all existing + # files to the 2.1.5 format. + update_pending() + # In Mailman 2.1, the qfiles directory has a different structure and a + # different content. Also, in Mailman 2.1.5 we collapsed the message + # files from separate .msg (pickled Message objects) and .db (marshalled + # dictionaries) to a shared .pck file containing two pickles. + update_qfiles() + # This warning was necessary for the upgrade from 1.0b9 to 1.0b10. + # There's no good way of figuring this out for releases prior to 2.0beta2 + # :( + if lastversion == NOTFRESH: + print(""" -def upgrade(mlist): - """Upgrade the list to the current version.""" - try: - # Print pickle protocol version when loading - try: - config_path = os.path.join(mlist._full_path, 'config.pck') - if os.path.exists(config_path): - with open(config_path, 'rb') as fp: - # Try loading with UTF-8 first, then fall back to latin1 - try: - fp.seek(0) - data = pickle.load(fp, fix_imports=True, encoding='utf-8') - if hasattr(data, '_protocol'): - protocol = data._protocol - print(C_('List %(listname)s config.pck uses pickle protocol %(protocol)d') % { - 'listname': mlist.internal_name(), - 'protocol': protocol - }) - else: - print(C_('List %(listname)s config.pck protocol version not stored in data') % { - 'listname': mlist.internal_name() - }) - except UnicodeDecodeError: - fp.seek(0) - data = pickle.load(fp, fix_imports=True, encoding='latin1') - if hasattr(data, '_protocol'): - protocol = data._protocol - print(C_('List %(listname)s config.pck uses pickle protocol %(protocol)d') % { - 'listname': mlist.internal_name(), - 'protocol': protocol - }) - else: - print(C_('List %(listname)s config.pck protocol version not stored in data') % { - 'listname': mlist.internal_name() - }) - except Exception as e: - print(C_('Warning: Could not determine pickle protocol version: %(error)s') % { - 'error': str(e) - }) +NOTE NOTE NOTE NOTE NOTE - # Initialize any missing digest variables - init_digest_vars(mlist) - - # Convert all email addresses to lowercase - for addr in list(mlist.members): - if addr != addr.lower(): - mlist.members[addr.lower()] = mlist.members[addr] - del mlist.members[addr] - - for addr in list(mlist.digest_members): - if addr != addr.lower(): - mlist.digest_members[addr.lower()] = mlist.digest_members[addr] - del mlist.digest_members[addr] - - # Handle owner list differently since it's a list, not a dict - if hasattr(mlist, 'owner') and isinstance(mlist.owner, list): - new_owners = [] - for addr in mlist.owner: - if isinstance(addr, str) and addr != addr.lower(): - new_owners.append(addr.lower()) - else: - new_owners.append(addr) - mlist.owner = new_owners - else: - for addr in list(mlist.owner): - if addr != addr.lower(): - mlist.owner[addr.lower()] = mlist.owner[addr] - del mlist.owner[addr] - - # Handle moderator list differently since it's a list, not a dict - if hasattr(mlist, 'moderator') and isinstance(mlist.moderator, list): - new_moderators = [] - for addr in mlist.moderator: - if isinstance(addr, str) and addr != addr.lower(): - new_moderators.append(addr.lower()) - else: - new_moderators.append(addr) - mlist.moderator = new_moderators - else: - for addr in list(mlist.moderator): - if addr != addr.lower(): - mlist.moderator[addr.lower()] = mlist.moderator[addr] - del mlist.moderator[addr] - - for addr in list(mlist.bounce_info): - if addr != addr.lower(): - mlist.bounce_info[addr.lower()] = mlist.bounce_info[addr] - del mlist.bounce_info[addr] - - for addr in list(mlist.delivery_status): - if addr != addr.lower(): - mlist.delivery_status[addr.lower()] = mlist.delivery_status[addr] - del mlist.delivery_status[addr] - - for addr in list(mlist.user_options): - if addr != addr.lower(): - mlist.user_options[addr.lower()] = mlist.user_options[addr] - del mlist.user_options[addr] - - # Don't convert passwords to lowercase - # for addr in list(mlist.passwords): - # if addr != addr.lower(): - # mlist.passwords[addr.lower()] = mlist.passwords[addr] - # del mlist.passwords[addr] - - for addr in list(mlist.language): - if addr != addr.lower(): - mlist.language[addr.lower()] = mlist.language[addr] - del mlist.language[addr] - - for addr in list(mlist.usernames): - if addr != addr.lower(): - mlist.usernames[addr.lower()] = mlist.usernames[addr] - del mlist.usernames[addr] + You are upgrading an existing Mailman installation, but I can't tell what + version you were previously running. - # Ensure the list directory exists - list_dir = os.path.dirname(mlist._full_path) - if not os.path.exists(list_dir): - print(C_('Creating list directory: %(dir)s') % {'dir': list_dir}) - os.makedirs(list_dir, mode=0o2775) - # Set group ownership if possible - try: - import grp - mailman_gid = grp.getgrnam('mailman').gr_gid - os.chown(list_dir, -1, mailman_gid) - except (ImportError, KeyError): - pass + If you are upgrading from Mailman 1.0b9 or earlier you will need to + manually update your mailing lists. For each mailing list you need to + copy the file templates/options.html lists//options.html. - # Save the list configuration - try: - print(C_('Saving list %(listname)s with pickle protocol 4') % { - 'listname': mlist.internal_name() - }) - mlist.Save() - except (IOError, OSError) as e: - print(C_('Error saving list configuration: %(error)s') % {'error': str(e)}) - # Try to save to a backup location - backup_path = os.path.join(list_dir, 'config.pck.bak') - try: - with open(backup_path, 'wb') as fp: - pickle.dump(mlist.__dict__, fp, protocol=4, fix_imports=True) - print(C_('Saved backup configuration to %(path)s') % {'path': backup_path}) - except Exception as e: - print(C_('Failed to save backup configuration: %(error)s') % {'error': str(e)}) - raise - except Exception as e: - print(C_('Error during upgrade: %(error)s') % {'error': str(e)}) - raise + However, if you have edited this file via the Web interface, you will have + to merge your changes into this file, otherwise you will lose your + changes. +NOTE NOTE NOTE NOTE NOTE -def main(): - try: - args = parse_args() - except SystemExit as e: - if e.code == 2: # Invalid arguments - usage(1) - raise +""") + return errors - # Calculate the versions - lastversion, thisversion = calcversions() - # If this is a fresh install, we don't need to do anything - if lastversion == FRESH: - print(C_('This appears to be a fresh installation.')) - print(C_('No upgrade is necessary.')) - # Early check: try to load all lists and print a summary - list_names = Utils.list_names() - ok = 0 - fail = 0 - for listname in list_names: - try: - if isinstance(listname, bytes): - listname = listname.decode('utf-8', 'replace') - listname = listname.lower() - mlist = MailList.MailList(listname, lock=0) - ok += 1 - except Exception as e: - fail += 1 - print(' [WARN] Could not load list "%s": %s' % (listname, str(e))) - if fail == 0: - print('All %d lists loaded successfully, no upgrade necessary.' % ok) - else: - print('%d lists loaded successfully, %d lists had errors. No upgrade necessary.' % (ok, fail)) - sys.exit(0) + +def usage(code, msg=''): + if code: + fd = sys.stderr + else: + fd = sys.stdout + print(C_(__doc__) % globals(), file=fd) + if msg: + print(msg, file=sys.stderr) + sys.exit(code) - # If this is not a fresh install, but we can't determine the last version, - # we need to force the upgrade - if lastversion == NOTFRESH: - if not args.force: - print(C_("""\ -This appears to be an existing installation, but I cannot determine the -version number. You must use the -f flag to force the upgrade.""")) - sys.exit(1) - lastversion = 0x2000000 # 2.0.0 - # If the versions match, we don't need to do anything - if lastversion == thisversion and not args.force: - print(C_('No upgrade is necessary.')) - # Early check: try to load all lists and print a summary - list_names = Utils.list_names() - ok = 0 - fail = 0 - for listname in list_names: - try: - if isinstance(listname, bytes): - listname = listname.decode('utf-8', 'replace') - listname = listname.lower() - mlist = MailList.MailList(listname, lock=0) - ok += 1 - except Exception as e: - fail += 1 - print(' [WARN] Could not load list "%s": %s' % (listname, str(e))) - if fail == 0: - print('All %d lists loaded successfully, no upgrade necessary.' % ok) - else: - print('%d lists loaded successfully, %d lists had errors. No upgrade necessary.' % (ok, fail)) + +if __name__ == '__main__': + try: + opts, args = getopt.getopt(sys.argv[1:], 'hf', + ['help', 'force']) + except getopt.error as msg: + usage(1, msg) + + if args: + usage(1, 'Unexpected arguments: %s' % args) + + force = 0 + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-f', '--force'): + force = 1 + + # calculate the versions + lastversion, thisversion = calcversions() + hexlversion = hex(lastversion) + hextversion = hex(thisversion) + if lastversion == thisversion and not force: + # nothing to do + print ('No updates are necessary.') sys.exit(0) - - # If this is a downgrade, we need to force it - if lastversion > thisversion and not args.force: + if lastversion > thisversion and not force: print(C_("""\ -This appears to be a downgrade. You must use the -f flag to force the -downgrade.""")) +Downgrade detected, from version %(hexlversion)s to version %(hextversion)s +This is probably not safe. +Exiting.""")) sys.exit(1) - - # Process all mailing lists - list_names = Utils.list_names() - print("Found %d lists to process" % len(list_names)) - print("List names type: %s" % type(list_names)) - - for listname in list_names: - mlist = None - try: - print("\nProcessing list: %s (type: %s)" % (listname, type(listname))) - # Ensure listname is a string, not bytes - if isinstance(listname, bytes): - print("Converting bytes listname to string") - listname = listname.decode('utf-8', 'replace') - listname = listname.lower() - print("Listname after conversion: %s (type: %s)" % (listname, type(listname))) - - print("Creating MailList object...") - # Create the MailList object without lock first - mlist = MailList.MailList(listname, lock=0) - print("MailList object created successfully") - - print("Attempting to acquire lock...") - try: - # First try to acquire the lock normally - try: - mlist.Lock(0.5) - except AlreadyLockedError: - # If we get AlreadyLockedError, try to force unlock if the lock is stale - print("Lock appears to be set, checking if it's stale...") - try: - # Try to force unlock if the lock is stale - if hasattr(mlist, '__lock') and hasattr(mlist.__lock, 'force_unlock'): - mlist.__lock.force_unlock() - print("Stale lock removed, retrying lock acquisition...") - mlist.Lock(0.5) - else: - print("WARNING: Lock object does not have force_unlock capability") - continue - except Exception as e: - print(C_('WARNING: Could not remove stale lock: %(error)s') % { - 'error': str(e) - }, file=sys.stderr) - continue - - print("Lock acquired, starting upgrade...") - - # Do the upgrade - upgrade(mlist) - print("Upgrade completed, saving...") - mlist.Save() - print("Save completed") - - except TimeOutError: - print(C_('WARNING: could not acquire lock for list: %(listname)s') % { - 'listname': listname - }, file=sys.stderr) - continue - finally: - if mlist is not None: - try: - print("Unlocking list...") - mlist.Unlock() - except Exception as e: - print(C_('WARNING: Error unlocking list %(listname)s: %(error)s') % { - 'listname': listname, - 'error': str(e) - }, file=sys.stderr) - - except Exception as e: - print("\nDetailed error information:") - print("List name: %s" % listname) - print("List name type: %s" % type(listname)) - print("Error type: %s" % type(e)) - print("Error message: %s" % str(e)) - import traceback - print("Traceback:") - traceback.print_exc() - print(C_('Error processing list %(listname)s: %(error)s') % { - 'listname': listname, - 'error': str(e) - }, file=sys.stderr) - - # Save the new version - try: + print(C_('Upgrading from version %(hexlversion)s to %(hextversion)s')) + errors = main() + if not errors: + # Record the version we just upgraded to fp = open(LMVFILE, 'w') - print('%x' % thisversion, file=fp) + fp.write(hex(mm_cfg.HEX_VERSION) + '\n') fp.close() - except IOError as e: - print(C_('Could not save version number: %(error)s') % {'error': str(e)}, file=sys.stderr) - sys.exit(1) - - print(C_('Upgrade complete.')) - + else: + lockdir = mm_cfg.LOCK_DIR + print('''\ -def usage(exitcode=0): - """Print usage information and exit with the given exit code.""" - print(__doc__ % {'PROGRAM': PROGRAM}) - sys.exit(exitcode) +ERROR: +The locks for some lists could not be acquired. This means that either +Mailman was still active when you upgraded, or there were stale locks in the +%(lockdir)s directory. -if __name__ == '__main__': - main() +You must put Mailman into a quiescent state and remove all stale locks, then +re-run "make update" manually. See the INSTALL and UPGRADE files for details. +''') diff --git a/bin/withlist b/bin/withlist index 20215fbe..fcc19bfe 100644 --- a/bin/withlist +++ b/bin/withlist @@ -122,7 +122,7 @@ and run this from the command line: import os import sys import code -import argparse +import getopt import paths from Mailman import Errors @@ -141,25 +141,7 @@ LOCK = False sys.path.append(os.path.dirname(sys.argv[0])) -def parse_args(): - parser = argparse.ArgumentParser(description='General framework for interacting with a mailing list object.') - parser.add_argument('-l', '--lock', action='store_true', - help='Lock the list when opening') - parser.add_argument('-r', '--run', - help='Run the specified module.callable') - parser.add_argument('-q', '--quiet', action='store_true', - help='Suppress verbose output') - parser.add_argument('-i', '--interactive', action='store_true', - help='Leave at interactive prompt after processing') - parser.add_argument('-a', '--all', action='store_true', - help='Process all lists') - parser.add_argument('listname', nargs='?', - help='Name of the list to process') - parser.add_argument('args', nargs='*', - help='Additional arguments to pass to the callable') - return parser.parse_args() - - + def usage(code, msg=''): if code: fd = sys.stderr @@ -167,7 +149,7 @@ def usage(code, msg=''): fd = sys.stdout print(C_(__doc__), file=fd) if msg: - print(msg, file=sys.stderr) + print(msg, file=fd) sys.exit(code) @@ -191,6 +173,7 @@ def atexit(): del m + def do_list(listname, args, func): global m # first try to open mailing list @@ -213,66 +196,78 @@ def do_list(listname, args, func): return None + def main(): - global VERBOSE, LOCK + global VERBOSE + global LOCK + global r try: - args = parse_args() - except SystemExit: - usage(1) + opts, args = getopt.getopt( + sys.argv[1:], 'hlr:qia', + ['help', 'lock', 'run=', 'quiet', 'interactive', 'all']) + except getopt.error as msg: + usage(1, msg) + + run = None + interact = None + all = False + dolist = True + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-l', '--lock'): + LOCK = True + elif opt in ('-r', '--run'): + run = arg + elif opt in ('-q', '--quiet'): + VERBOSE = False + elif opt in ('-i', '--interactive'): + interact = True + elif opt in ('-a', '--all'): + all = True + + if len(args) < 1 and not all: + warning = C_('No list name supplied.') + if interact: + # Let them keep going + print(warning) + dolist = False + else: + usage(1, warning) - VERBOSE = not args.quiet - LOCK = args.lock + if all and not run: + usage(1, C_('--all requires --run')) - # The default for interact is True unless -r was given - interact = args.interactive + # The default for interact is 1 unless -r was given if interact is None: - interact = args.run is None + if run is None: + interact = True + else: + interact = False - # Import the callable if one was specified + # try to import the module for the callable func = None - if args.run: + if run: + i = run.rfind('.') + if i < 0: + module = run + callable = run + else: + module = run[:i] + callable = run[i+1:] if VERBOSE: print(C_('Importing %(module)s...'), file=sys.stderr) - try: - if '.' in args.run: - module, callable = args.run.rsplit('.', 1) - mod = __import__(module, globals(), locals(), [callable]) - func = getattr(mod, callable) - else: - mod = __import__(args.run, globals(), locals(), []) - func = getattr(mod, args.run) - except (ImportError, AttributeError) as e: - print(C_('Error importing %(module)s: %(error)s'), - file=sys.stderr) - sys.exit(1) - - # Handle the --all option - if args.all: - if args.listname: - usage(1, C_('Cannot specify listname with --all')) - if not args.run: - usage(1, C_('--all requires --run')) - results = [] - for listname in Utils.list_names(): - if VERBOSE: - print(C_('Processing list: %(listname)s'), file=sys.stderr) - result = do_list(listname, args.args, func) - if result is not None: - results.append(result) - r = results - else: - if not args.listname: - warning = C_('No list name supplied.') - if interact: - # Let them keep going - print(warning) - dolist = False - else: - usage(1, warning) - else: - dolist = True - listname = args.listname.lower().strip() - r = do_list(listname, args.args, func) + __import__(module) + mod = sys.modules[module] + if VERBOSE: + print(C_('Running %(module)s.%(callable)s()...'), file=sys.stderr) + func = getattr(mod, callable) + + if all: + r = [do_list(listname, args, func) for listname in Utils.list_names()] + elif dolist: + listname = args.pop(0).lower().strip() + r = do_list(listname, args, func) # Now go to interactive mode, perhaps if interact: @@ -290,10 +285,8 @@ def main(): else: ban = None code.InteractiveConsole(namespace).interact(ban) - else: - # We're done - sys.exit(0) + sys.exitfunc = atexit main() diff --git a/configure b/configure index 70e83a4c..e82fb66f 100755 --- a/configure +++ b/configure @@ -1,11 +1,10 @@ #! /bin/sh -# From configure.ac Revision: 8122 . +# From configure.in Revision: 8122 . # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.72. +# Generated by GNU Autoconf 2.69. # # -# Copyright (C) 1992-1996, 1998-2017, 2020-2023 Free Software Foundation, -# Inc. +# Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. # # # This configure script is free software; the Free Software Foundation @@ -16,65 +15,63 @@ # Be more Bourne compatible DUALCASE=1; export DUALCASE # for MKS sh -if test ${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 -then : +if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which # is contrary to our usage. Disable this feature. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST -else case e in #( - e) case `(set -o) 2>/dev/null` in #( +else + case `(set -o) 2>/dev/null` in #( *posix*) : set -o posix ;; #( *) : ;; -esac ;; esac fi - -# Reset variables that may have inherited troublesome values from -# the environment. - -# IFS needs to be set, to space, tab, and newline, in precisely that order. -# (If _AS_PATH_WALK were called with IFS unset, it would have the -# side effect of setting IFS to empty, thus disabling word splitting.) -# Quoting is to prevent editors from complaining about space-tab. as_nl=' ' export as_nl -IFS=" "" $as_nl" - -PS1='$ ' -PS2='> ' -PS4='+ ' - -# Ensure predictable behavior from utilities with locale-dependent output. -LC_ALL=C -export LC_ALL -LANGUAGE=C -export LANGUAGE - -# We cannot yet rely on "unset" to work, but we need these variables -# to be unset--not just set to an empty or harmless value--now, to -# avoid bugs in old shells (e.g. pre-3.0 UWIN ksh). This construct -# also avoids known problems related to "unset" and subshell syntax -# in other old shells (e.g. bash 2.01 and pdksh 5.2.14). -for as_var in BASH_ENV ENV MAIL MAILPATH CDPATH -do eval test \${$as_var+y} \ - && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : -done - -# Ensure that fds 0, 1, and 2 are open. -if (exec 3>&0) 2>/dev/null; then :; else exec 0&1) 2>/dev/null; then :; else exec 1>/dev/null; fi -if (exec 3>&2) ; then :; else exec 2>/dev/null; fi +# Printing a long string crashes Solaris 7 /usr/bin/printf. +as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo +# Prefer a ksh shell builtin over an external printf program on Solaris, +# but without wasting forks for bash or zsh. +if test -z "$BASH_VERSION$ZSH_VERSION" \ + && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='print -r --' + as_echo_n='print -rn --' +elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='printf %s\n' + as_echo_n='printf %s' +else + if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then + as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' + as_echo_n='/usr/ucb/echo -n' + else + as_echo_body='eval expr "X$1" : "X\\(.*\\)"' + as_echo_n_body='eval + arg=$1; + case $arg in #( + *"$as_nl"*) + expr "X$arg" : "X\\(.*\\)$as_nl"; + arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; + esac; + expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" + ' + export as_echo_n_body + as_echo_n='sh -c $as_echo_n_body as_echo' + fi + export as_echo_body + as_echo='sh -c $as_echo_body as_echo' +fi # The user is always right. -if ${PATH_SEPARATOR+false} :; then +if test "${PATH_SEPARATOR+set}" != set; then PATH_SEPARATOR=: (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || @@ -83,6 +80,13 @@ if ${PATH_SEPARATOR+false} :; then fi +# IFS +# We need space, tab and new line, in precisely that order. Quoting is +# there to prevent editors from complaining about space-tab. +# (If _AS_PATH_WALK were called with IFS unset, it would disable word +# splitting by setting IFS to empty value.) +IFS=" "" $as_nl" + # Find who we are. Look in the path if we contain no directory separator. as_myself= case $0 in #(( @@ -91,27 +95,43 @@ case $0 in #(( for as_dir in $PATH do IFS=$as_save_IFS - case $as_dir in #((( - '') as_dir=./ ;; - */) ;; - *) as_dir=$as_dir/ ;; - esac - test -r "$as_dir$0" && as_myself=$as_dir$0 && break + test -z "$as_dir" && as_dir=. + test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break done IFS=$as_save_IFS ;; esac -# We did not find ourselves, most probably we were run as 'sh COMMAND' +# We did not find ourselves, most probably we were run as `sh COMMAND' # in which case we are not to be found in the path. if test "x$as_myself" = x; then as_myself=$0 fi if test ! -f "$as_myself"; then - printf "%s\n" "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 + $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 exit 1 fi +# Unset variables that we do not need and which cause bugs (e.g. in +# pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" +# suppresses any "Segmentation fault" message there. '((' could +# trigger a bug in pdksh 5.2.14. +for as_var in BASH_ENV ENV MAIL MAILPATH +do eval test x\${$as_var+set} = xset \ + && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : +done +PS1='$ ' +PS2='> ' +PS4='+ ' + +# NLS nuisances. +LC_ALL=C +export LC_ALL +LANGUAGE=C +export LANGUAGE + +# CDPATH. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH # Use a proper internal environment variable to ensure we don't fall # into an infinite loop, continuously re-executing ourselves. @@ -132,28 +152,26 @@ case $- in # (((( esac exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} # Admittedly, this is quite paranoid, since all the known shells bail -# out after a failed 'exec'. -printf "%s\n" "$0: could not re-execute with $CONFIG_SHELL" >&2 -exit 255 +# out after a failed `exec'. +$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2 +as_fn_exit 255 fi # We don't want this to propagate to other subprocesses. { _as_can_reexec=; unset _as_can_reexec;} if test "x$CONFIG_SHELL" = x; then - as_bourne_compatible="if test \${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 -then : + as_bourne_compatible="if test -n \"\${ZSH_VERSION+set}\" && (emulate sh) >/dev/null 2>&1; then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on \${1+\"\$@\"}, which # is contrary to our usage. Disable this feature. alias -g '\${1+\"\$@\"}'='\"\$@\"' setopt NO_GLOB_SUBST -else case e in #( - e) case \`(set -o) 2>/dev/null\` in #( +else + case \`(set -o) 2>/dev/null\` in #( *posix*) : set -o posix ;; #( *) : ;; -esac ;; esac fi " @@ -168,54 +186,42 @@ as_fn_success || { exitcode=1; echo as_fn_success failed.; } as_fn_failure && { exitcode=1; echo as_fn_failure succeeded.; } as_fn_ret_success || { exitcode=1; echo as_fn_ret_success failed.; } as_fn_ret_failure && { exitcode=1; echo as_fn_ret_failure succeeded.; } -if ( set x; as_fn_ret_success y && test x = \"\$1\" ) -then : +if ( set x; as_fn_ret_success y && test x = \"\$1\" ); then : -else case e in #( - e) exitcode=1; echo positional parameters were not saved. ;; -esac +else + exitcode=1; echo positional parameters were not saved. fi test x\$exitcode = x0 || exit 1 -blah=\$(echo \$(echo blah)) -test x\"\$blah\" = xblah || exit 1 test -x / || exit 1" as_suggested=" as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" && - test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1" - if (eval "$as_required") 2>/dev/null -then : + test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1 +test \$(( 1 + 1 )) = 2 || exit 1" + if (eval "$as_required") 2>/dev/null; then : as_have_required=yes -else case e in #( - e) as_have_required=no ;; -esac +else + as_have_required=no fi - if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null -then : + if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null; then : -else case e in #( - e) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +else + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR as_found=false for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH do IFS=$as_save_IFS - case $as_dir in #((( - '') as_dir=./ ;; - */) ;; - *) as_dir=$as_dir/ ;; - esac + test -z "$as_dir" && as_dir=. as_found=: case $as_dir in #( /*) for as_base in sh bash ksh sh5; do # Try only shells that exist, to save several forks. - as_shell=$as_dir$as_base + as_shell=$as_dir/$as_base if { test -f "$as_shell" || test -f "$as_shell.exe"; } && - as_run=a "$as_shell" -c "$as_bourne_compatible""$as_required" 2>/dev/null -then : + { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$as_shell"; } 2>/dev/null; then : CONFIG_SHELL=$as_shell as_have_required=yes - if as_run=a "$as_shell" -c "$as_bourne_compatible""$as_suggested" 2>/dev/null -then : + if { $as_echo "$as_bourne_compatible""$as_suggested" | as_run=a "$as_shell"; } 2>/dev/null; then : break 2 fi fi @@ -223,22 +229,14 @@ fi esac as_found=false done -IFS=$as_save_IFS -if $as_found -then : - -else case e in #( - e) if { test -f "$SHELL" || test -f "$SHELL.exe"; } && - as_run=a "$SHELL" -c "$as_bourne_compatible""$as_required" 2>/dev/null -then : +$as_found || { if { test -f "$SHELL" || test -f "$SHELL.exe"; } && + { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$SHELL"; } 2>/dev/null; then : CONFIG_SHELL=$SHELL as_have_required=yes -fi ;; -esac -fi +fi; } +IFS=$as_save_IFS - if test "x$CONFIG_SHELL" != x -then : + if test "x$CONFIG_SHELL" != x; then : export CONFIG_SHELL # We cannot yet assume a decent shell, so we have to provide a # neutralization value for shells without unset; and this also @@ -255,27 +253,25 @@ case $- in # (((( esac exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} # Admittedly, this is quite paranoid, since all the known shells bail -# out after a failed 'exec'. -printf "%s\n" "$0: could not re-execute with $CONFIG_SHELL" >&2 +# out after a failed `exec'. +$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2 exit 255 fi - if test x$as_have_required = xno -then : - printf "%s\n" "$0: This script requires a shell more modern than all" - printf "%s\n" "$0: the shells that I found on your system." - if test ${ZSH_VERSION+y} ; then - printf "%s\n" "$0: In particular, zsh $ZSH_VERSION has bugs and should" - printf "%s\n" "$0: be upgraded to zsh 4.3.4 or later." + if test x$as_have_required = xno; then : + $as_echo "$0: This script requires a shell more modern than all" + $as_echo "$0: the shells that I found on your system." + if test x${ZSH_VERSION+set} = xset ; then + $as_echo "$0: In particular, zsh $ZSH_VERSION has bugs and should" + $as_echo "$0: be upgraded to zsh 4.3.4 or later." else - printf "%s\n" "$0: Please tell bug-autoconf@gnu.org about your system, + $as_echo "$0: Please tell bug-autoconf@gnu.org about your system, $0: including any error possibly output before this $0: message. Then install a modern shell, or manually run $0: the script under such a shell if you do have one." fi exit 1 -fi ;; -esac +fi fi fi SHELL=${CONFIG_SHELL-/bin/sh} @@ -296,7 +292,6 @@ as_fn_unset () } as_unset=as_fn_unset - # as_fn_set_status STATUS # ----------------------- # Set $? to STATUS, without forking. @@ -328,7 +323,7 @@ as_fn_mkdir_p () as_dirs= while :; do case $as_dir in #( - *\'*) as_qdir=`printf "%s\n" "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( + *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( *) as_qdir=$as_dir;; esac as_dirs="'$as_qdir' $as_dirs" @@ -337,7 +332,7 @@ $as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_dir" : 'X\(//\)[^/]' \| \ X"$as_dir" : 'X\(//\)$' \| \ X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || -printf "%s\n" X"$as_dir" | +$as_echo X"$as_dir" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q @@ -376,18 +371,16 @@ as_fn_executable_p () # advantage of any shell optimizations that allow amortized linear growth over # repeated appends, instead of the typical quadratic growth present in naive # implementations. -if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null -then : +if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : eval 'as_fn_append () { eval $1+=\$2 }' -else case e in #( - e) as_fn_append () +else + as_fn_append () { eval $1=\$$1\$2 - } ;; -esac + } fi # as_fn_append # as_fn_arith ARG... @@ -395,18 +388,16 @@ fi # as_fn_append # Perform arithmetic evaluation on the ARGs, and store the result in the # global $as_val. Take advantage of shells that can avoid forks. The arguments # must be portable across $(()) and expr. -if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null -then : +if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : eval 'as_fn_arith () { as_val=$(( $* )) }' -else case e in #( - e) as_fn_arith () +else + as_fn_arith () { as_val=`expr "$@" || test $? -eq 1` - } ;; -esac + } fi # as_fn_arith @@ -420,9 +411,9 @@ as_fn_error () as_status=$1; test $as_status -eq 0 && as_status=1 if test "$4"; then as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack - printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 + $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 fi - printf "%s\n" "$as_me: error: $2" >&2 + $as_echo "$as_me: error: $2" >&2 as_fn_exit $as_status } # as_fn_error @@ -449,7 +440,7 @@ as_me=`$as_basename -- "$0" || $as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ X"$0" : 'X\(//\)$' \| \ X"$0" : 'X\(/\)' \| . 2>/dev/null || -printf "%s\n" X/"$0" | +$as_echo X/"$0" | sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/ q @@ -482,8 +473,6 @@ as_cr_alnum=$as_cr_Letters$as_cr_digits /[$]LINENO/= ' <$as_myself | sed ' - t clear - :clear s/[$]LINENO.*/&-/ t lineno b @@ -495,7 +484,7 @@ as_cr_alnum=$as_cr_Letters$as_cr_digits s/-\n.*// ' >$as_me.lineno && chmod +x "$as_me.lineno" || - { printf "%s\n" "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; } + { $as_echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; } # If we had to re-execute with $CONFIG_SHELL, we're ensured to have # already done that, so ensure we don't try to do so again and fall @@ -509,10 +498,6 @@ as_cr_alnum=$as_cr_Letters$as_cr_digits exit } - -# Determine whether it's possible to make 'echo' print without a newline. -# These variables are no longer used directly by Autoconf, but are AC_SUBSTed -# for compatibility with existing Makefiles. ECHO_C= ECHO_N= ECHO_T= case `echo -n x` in #((((( -n*) @@ -526,12 +511,6 @@ case `echo -n x` in #((((( ECHO_N='-n';; esac -# For backward compatibility with old third-party macros, we provide -# the shell variables $as_echo and $as_echo_n. New code should use -# AS_ECHO(["message"]) and AS_ECHO_N(["message"]), respectively. -as_echo='printf %s\n' -as_echo_n='printf %s' - rm -f conf$$ conf$$.exe conf$$.file if test -d conf$$.dir; then rm -f conf$$.dir/conf$$.file @@ -543,9 +522,9 @@ if (echo >conf$$.file) 2>/dev/null; then if ln -s conf$$.file conf$$ 2>/dev/null; then as_ln_s='ln -s' # ... but there are two gotchas: - # 1) On MSYS, both 'ln -s file dir' and 'ln file dir' fail. - # 2) DJGPP < 2.04 has no symlinks; 'ln -s' creates a wrapper executable. - # In both cases, we have to default to 'cp -pR'. + # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. + # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. + # In both cases, we have to default to `cp -pR'. ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || as_ln_s='cp -pR' elif ln conf$$.file conf$$ 2>/dev/null; then @@ -570,12 +549,10 @@ as_test_x='test -x' as_executable_p=as_fn_executable_p # Sed expression to map a string onto a valid CPP name. -as_sed_cpp="y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g" -as_tr_cpp="eval sed '$as_sed_cpp'" # deprecated +as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" # Sed expression to map a string onto a valid variable name. -as_sed_sh="y%*+%pp%;s%[^_$as_cr_alnum]%_%g" -as_tr_sh="eval sed '$as_sed_sh'" # deprecated +as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" test -n "$DJDIR" || exec 7<&0 /dev/null && - as_fn_error $? "invalid feature name: '$ac_useropt'" + as_fn_error $? "invalid feature name: $ac_useropt" ac_useropt_orig=$ac_useropt - ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "enable_$ac_useropt" @@ -873,9 +855,9 @@ do ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && - as_fn_error $? "invalid feature name: '$ac_useropt'" + as_fn_error $? "invalid feature name: $ac_useropt" ac_useropt_orig=$ac_useropt - ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "enable_$ac_useropt" @@ -1086,9 +1068,9 @@ do ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && - as_fn_error $? "invalid package name: '$ac_useropt'" + as_fn_error $? "invalid package name: $ac_useropt" ac_useropt_orig=$ac_useropt - ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "with_$ac_useropt" @@ -1102,9 +1084,9 @@ do ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && - as_fn_error $? "invalid package name: '$ac_useropt'" + as_fn_error $? "invalid package name: $ac_useropt" ac_useropt_orig=$ac_useropt - ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "with_$ac_useropt" @@ -1132,8 +1114,8 @@ do | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*) x_libraries=$ac_optarg ;; - -*) as_fn_error $? "unrecognized option: '$ac_option' -Try '$0 --help' for more information" + -*) as_fn_error $? "unrecognized option: \`$ac_option' +Try \`$0 --help' for more information" ;; *=*) @@ -1141,16 +1123,16 @@ Try '$0 --help' for more information" # Reject names that are not valid shell variable names. case $ac_envvar in #( '' | [0-9]* | *[!_$as_cr_alnum]* ) - as_fn_error $? "invalid variable name: '$ac_envvar'" ;; + as_fn_error $? "invalid variable name: \`$ac_envvar'" ;; esac eval $ac_envvar=\$ac_optarg export $ac_envvar ;; *) # FIXME: should be removed in autoconf 3.0. - printf "%s\n" "$as_me: WARNING: you should use --build, --host, --target" >&2 + $as_echo "$as_me: WARNING: you should use --build, --host, --target" >&2 expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null && - printf "%s\n" "$as_me: WARNING: invalid host type: $ac_option" >&2 + $as_echo "$as_me: WARNING: invalid host type: $ac_option" >&2 : "${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}" ;; @@ -1166,7 +1148,7 @@ if test -n "$ac_unrecognized_opts"; then case $enable_option_checking in no) ;; fatal) as_fn_error $? "unrecognized options: $ac_unrecognized_opts" ;; - *) printf "%s\n" "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;; + *) $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;; esac fi @@ -1191,7 +1173,7 @@ do as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val" done -# There might be people who depend on the old broken behavior: '$host' +# There might be people who depend on the old broken behavior: `$host' # used to hold the argument of --host etc. # FIXME: To remove some day. build=$build_alias @@ -1230,7 +1212,7 @@ $as_expr X"$as_myself" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_myself" : 'X\(//\)[^/]' \| \ X"$as_myself" : 'X\(//\)$' \| \ X"$as_myself" : 'X\(/\)' \| . 2>/dev/null || -printf "%s\n" X"$as_myself" | +$as_echo X"$as_myself" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q @@ -1259,7 +1241,7 @@ if test ! -r "$srcdir/$ac_unique_file"; then test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .." as_fn_error $? "cannot find sources ($ac_unique_file) in $srcdir" fi -ac_msg="sources are in $srcdir, but 'cd $srcdir' does not work" +ac_msg="sources are in $srcdir, but \`cd $srcdir' does not work" ac_abs_confdir=`( cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error $? "$ac_msg" pwd)` @@ -1287,7 +1269,7 @@ if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -'configure' configures this package to adapt to many kinds of systems. +\`configure' configures this package to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1301,11 +1283,11 @@ Configuration: --help=short display options specific to this package --help=recursive display the short help of all the included packages -V, --version display version information and exit - -q, --quiet, --silent do not print 'checking ...' messages + -q, --quiet, --silent do not print \`checking ...' messages --cache-file=FILE cache test results in FILE [disabled] - -C, --config-cache alias for '--cache-file=config.cache' + -C, --config-cache alias for \`--cache-file=config.cache' -n, --no-create do not create output files - --srcdir=DIR find the sources in DIR [configure dir or '..'] + --srcdir=DIR find the sources in DIR [configure dir or \`..'] Installation directories: --prefix=PREFIX install architecture-independent files in PREFIX @@ -1313,10 +1295,10 @@ Installation directories: --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX [PREFIX] -By default, 'make install' will install all the files in -'$ac_default_prefix/bin', '$ac_default_prefix/lib' etc. You can specify -an installation prefix other than '$ac_default_prefix' using '--prefix', -for instance '--prefix=\$HOME'. +By default, \`make install' will install all the files in +\`$ac_default_prefix/bin', \`$ac_default_prefix/lib' etc. You can specify +an installation prefix other than \`$ac_default_prefix' using \`--prefix', +for instance \`--prefix=\$HOME'. For better control, use the options below. @@ -1351,12 +1333,6 @@ if test -n "$ac_init_help"; then cat <<\_ACEOF -Optional Features: - --disable-option-checking ignore unrecognized --enable/--with options - --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) - --enable-FEATURE[=ARG] include FEATURE [ARG=yes] - --enable-nntp enable NNTP support (requires python3-nntplib) - Optional Packages: --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) @@ -1380,8 +1356,9 @@ Some influential environment variables: LIBS libraries to pass to the linker, e.g. -l CPPFLAGS (Objective) C/C++ preprocessor flags, e.g. -I if you have headers in a nonstandard directory + CPP C preprocessor -Use these variables to override the choices made by 'configure' or to help +Use these variables to override the choices made by `configure' or to help it to find libraries and programs with nonstandard names/locations. Report bugs to the package provider. @@ -1400,9 +1377,9 @@ if test "$ac_init_help" = "recursive"; then case "$ac_dir" in .) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; *) - ac_dir_suffix=/`printf "%s\n" "$ac_dir" | sed 's|^\.[\\/]||'` + ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` # A ".." for each directory in $ac_dir_suffix. - ac_top_builddir_sub=`printf "%s\n" "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` + ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` case $ac_top_builddir_sub in "") ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; @@ -1430,8 +1407,7 @@ esac ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix cd "$ac_dir" || { ac_status=$?; continue; } - # Check for configure.gnu first; this name is used for a wrapper for - # Metaconfig's "Configure" on case-insensitive file systems. + # Check for guested configure. if test -f "$ac_srcdir/configure.gnu"; then echo && $SHELL "$ac_srcdir/configure.gnu" --help=recursive @@ -1439,7 +1415,7 @@ ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix echo && $SHELL "$ac_srcdir/configure" --help=recursive else - printf "%s\n" "$as_me: WARNING: no configuration information is in $ac_dir" >&2 + $as_echo "$as_me: WARNING: no configuration information is in $ac_dir" >&2 fi || ac_status=$? cd "$ac_pwd" || { ac_status=$?; break; } done @@ -1449,9 +1425,9 @@ test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF configure -generated by GNU Autoconf 2.72 +generated by GNU Autoconf 2.69 -Copyright (C) 2023 Free Software Foundation, Inc. +Copyright (C) 2012 Free Software Foundation, Inc. This configure script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it. _ACEOF @@ -1468,14 +1444,14 @@ fi ac_fn_c_try_compile () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack - rm -f conftest.$ac_objext conftest.beam + rm -f conftest.$ac_objext if { { ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" -printf "%s\n" "$ac_try_echo"; } >&5 +$as_echo "$ac_try_echo"; } >&5 (eval "$ac_compile") 2>conftest.err ac_status=$? if test -s conftest.err; then @@ -1483,19 +1459,17 @@ printf "%s\n" "$ac_try_echo"; } >&5 cat conftest.er1 >&5 mv -f conftest.er1 conftest.err fi - printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } && { test -z "$ac_c_werror_flag" || test ! -s conftest.err - } && test -s conftest.$ac_objext -then : + } && test -s conftest.$ac_objext; then : ac_retval=0 -else case e in #( - e) printf "%s\n" "$as_me: failed program was:" >&5 +else + $as_echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 - ac_retval=1 ;; -esac + ac_retval=1 fi eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno as_fn_set_status $ac_retval @@ -1508,14 +1482,14 @@ fi ac_fn_c_try_link () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack - rm -f conftest.$ac_objext conftest.beam conftest$ac_exeext + rm -f conftest.$ac_objext conftest$ac_exeext if { { ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" -printf "%s\n" "$ac_try_echo"; } >&5 +$as_echo "$ac_try_echo"; } >&5 (eval "$ac_link") 2>conftest.err ac_status=$? if test -s conftest.err; then @@ -1523,22 +1497,20 @@ printf "%s\n" "$ac_try_echo"; } >&5 cat conftest.er1 >&5 mv -f conftest.er1 conftest.err fi - printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } && { test -z "$ac_c_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && { test "$cross_compiling" = yes || test -x conftest$ac_exeext - } -then : + }; then : ac_retval=0 -else case e in #( - e) printf "%s\n" "$as_me: failed program was:" >&5 +else + $as_echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 - ac_retval=1 ;; -esac + ac_retval=1 fi # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would @@ -1556,22 +1528,28 @@ fi ac_fn_c_check_func () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 -printf %s "checking for $2... " >&6; } -if eval test \${$3+y} -then : - printf %s "(cached) " >&6 -else case e in #( - e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +$as_echo_n "checking for $2... " >&6; } +if eval \${$3+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Define $2 to an innocuous variant, in case declares $2. For example, HP-UX 11i declares gettimeofday. */ #define $2 innocuous_$2 /* System header to define __stub macros and hopefully few prototypes, - which can conflict with char $2 (void); below. */ + which can conflict with char $2 (); below. + Prefer to if __STDC__ is defined, since + exists even on freestanding compilers. */ + +#ifdef __STDC__ +# include +#else +# include +#endif -#include #undef $2 /* Override any GCC internal prototype to avoid an error. @@ -1580,7 +1558,7 @@ else case e in #( #ifdef __cplusplus extern "C" #endif -char $2 (void); +char $2 (); /* The GNU C library defines this for functions which it implements to always fail with ENOSYS. Some functions are actually named something starting with __ and the normal name is an alias. */ @@ -1589,152 +1567,232 @@ choke me #endif int -main (void) +main () { return $2 (); ; return 0; } _ACEOF -if ac_fn_c_try_link "$LINENO" -then : +if ac_fn_c_try_link "$LINENO"; then : eval "$3=yes" -else case e in #( - e) eval "$3=no" ;; -esac +else + eval "$3=no" fi -rm -f core conftest.err conftest.$ac_objext conftest.beam \ - conftest$ac_exeext conftest.$ac_ext ;; -esac +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext fi eval ac_res=\$$3 - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 -printf "%s\n" "$ac_res" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno } # ac_fn_c_check_func -# ac_fn_c_check_header_compile LINENO HEADER VAR INCLUDES +# ac_fn_c_try_cpp LINENO +# ---------------------- +# Try to preprocess conftest.$ac_ext, and return whether this succeeded. +ac_fn_c_try_cpp () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + if { { ac_try="$ac_cpp conftest.$ac_ext" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_cpp conftest.$ac_ext") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } > conftest.i && { + test -z "$ac_c_preproc_warn_flag$ac_c_werror_flag" || + test ! -s conftest.err + }; then : + ac_retval=0 +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 +fi + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_cpp + +# ac_fn_c_check_header_mongrel LINENO HEADER VAR INCLUDES # ------------------------------------------------------- -# Tests whether HEADER exists and can be compiled using the include files in -# INCLUDES, setting the cache variable VAR accordingly. -ac_fn_c_check_header_compile () +# Tests whether HEADER exists, giving a warning if it cannot be compiled using +# the include files in INCLUDES and setting the cache variable VAR +# accordingly. +ac_fn_c_check_header_mongrel () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 -printf %s "checking for $2... " >&6; } -if eval test \${$3+y} -then : - printf %s "(cached) " >&6 -else case e in #( - e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext + if eval \${$3+:} false; then : + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +$as_echo_n "checking for $2... " >&6; } +if eval \${$3+:} false; then : + $as_echo_n "(cached) " >&6 +fi +eval ac_res=\$$3 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } +else + # Is the header compilable? +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 usability" >&5 +$as_echo_n "checking $2 usability... " >&6; } +cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 #include <$2> _ACEOF -if ac_fn_c_try_compile "$LINENO" -then : - eval "$3=yes" -else case e in #( - e) eval "$3=no" ;; -esac +if ac_fn_c_try_compile "$LINENO"; then : + ac_header_compiler=yes +else + ac_header_compiler=no fi -rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_compiler" >&5 +$as_echo "$ac_header_compiler" >&6; } + +# Is the header present? +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 presence" >&5 +$as_echo_n "checking $2 presence... " >&6; } +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include <$2> +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + ac_header_preproc=yes +else + ac_header_preproc=no +fi +rm -f conftest.err conftest.i conftest.$ac_ext +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_preproc" >&5 +$as_echo "$ac_header_preproc" >&6; } + +# So? What about this header? +case $ac_header_compiler:$ac_header_preproc:$ac_c_preproc_warn_flag in #(( + yes:no: ) + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&5 +$as_echo "$as_me: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5 +$as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;} + ;; + no:yes:* ) + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: present but cannot be compiled" >&5 +$as_echo "$as_me: WARNING: $2: present but cannot be compiled" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: check for missing prerequisite headers?" >&5 +$as_echo "$as_me: WARNING: $2: check for missing prerequisite headers?" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: see the Autoconf documentation" >&5 +$as_echo "$as_me: WARNING: $2: see the Autoconf documentation" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: section \"Present But Cannot Be Compiled\"" >&5 +$as_echo "$as_me: WARNING: $2: section \"Present But Cannot Be Compiled\"" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5 +$as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;} + ;; esac + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +$as_echo_n "checking for $2... " >&6; } +if eval \${$3+:} false; then : + $as_echo_n "(cached) " >&6 +else + eval "$3=\$ac_header_compiler" fi eval ac_res=\$$3 - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 -printf "%s\n" "$ac_res" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } +fi eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno -} # ac_fn_c_check_header_compile +} # ac_fn_c_check_header_mongrel -# ac_fn_c_check_type LINENO TYPE VAR INCLUDES -# ------------------------------------------- -# Tests whether TYPE exists after having included INCLUDES, setting cache -# variable VAR accordingly. -ac_fn_c_check_type () +# ac_fn_c_try_run LINENO +# ---------------------- +# Try to link conftest.$ac_ext, and return whether this succeeded. Assumes +# that executables *can* be run. +ac_fn_c_try_run () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 -printf %s "checking for $2... " >&6; } -if eval test \${$3+y} -then : - printf %s "(cached) " >&6 -else case e in #( - e) eval "$3=no" - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -$4 -int -main (void) + if { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { ac_try='./conftest$ac_exeext' + { { case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; }; then : + ac_retval=0 +else + $as_echo "$as_me: program exited with status $ac_status" >&5 + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=$ac_status +fi + rm -rf conftest.dSYM conftest_ipa8_conftest.oo + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_run + +# ac_fn_c_check_header_compile LINENO HEADER VAR INCLUDES +# ------------------------------------------------------- +# Tests whether HEADER exists and can be compiled using the include files in +# INCLUDES, setting the cache variable VAR accordingly. +ac_fn_c_check_header_compile () { -if (sizeof ($2)) - return 0; - ; - return 0; -} -_ACEOF -if ac_fn_c_try_compile "$LINENO" -then : + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +$as_echo_n "checking for $2... " >&6; } +if eval \${$3+:} false; then : + $as_echo_n "(cached) " >&6 +else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 -int -main (void) -{ -if (sizeof (($2))) - return 0; - ; - return 0; -} +#include <$2> _ACEOF -if ac_fn_c_try_compile "$LINENO" -then : - -else case e in #( - e) eval "$3=yes" ;; -esac -fi -rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext +if ac_fn_c_try_compile "$LINENO"; then : + eval "$3=yes" +else + eval "$3=no" fi -rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; -esac +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi eval ac_res=\$$3 - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 -printf "%s\n" "$ac_res" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno -} # ac_fn_c_check_type -ac_configure_args_raw= -for ac_arg -do - case $ac_arg in - *\'*) - ac_arg=`printf "%s\n" "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; - esac - as_fn_append ac_configure_args_raw " '$ac_arg'" -done - -case $ac_configure_args_raw in - *$as_nl*) - ac_safe_unquote= ;; - *) - ac_unsafe_z='|&;<>()$`\\"*?[ '' ' # This string ends in space, tab. - ac_unsafe_a="$ac_unsafe_z#~" - ac_safe_unquote="s/ '\\([^$ac_unsafe_a][^$ac_unsafe_z]*\\)'/ \\1/g" - ac_configure_args_raw=` printf "%s\n" "$ac_configure_args_raw" | sed "$ac_safe_unquote"`;; -esac - +} # ac_fn_c_check_header_compile cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. It was created by $as_me, which was -generated by GNU Autoconf 2.72. Invocation command line was +generated by GNU Autoconf 2.69. Invocation command line was - $ $0$ac_configure_args_raw + $ $0 $@ _ACEOF exec 5>>config.log @@ -1767,12 +1825,8 @@ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS - case $as_dir in #((( - '') as_dir=./ ;; - */) ;; - *) as_dir=$as_dir/ ;; - esac - printf "%s\n" "PATH: $as_dir" + test -z "$as_dir" && as_dir=. + $as_echo "PATH: $as_dir" done IFS=$as_save_IFS @@ -1807,7 +1861,7 @@ do | -silent | --silent | --silen | --sile | --sil) continue ;; *\'*) - ac_arg=`printf "%s\n" "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; + ac_arg=`$as_echo "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; esac case $ac_pass in 1) as_fn_append ac_configure_args0 " '$ac_arg'" ;; @@ -1842,13 +1896,11 @@ done # WARNING: Use '\'' to represent an apostrophe within the trap. # WARNING: Do not start the trap code with a newline, due to a FreeBSD 4.0 bug. trap 'exit_status=$? - # Sanitize IFS. - IFS=" "" $as_nl" # Save into config.log some information that might help in debugging. { echo - printf "%s\n" "## ---------------- ## + $as_echo "## ---------------- ## ## Cache variables. ## ## ---------------- ##" echo @@ -1859,8 +1911,8 @@ trap 'exit_status=$? case $ac_val in #( *${as_nl}*) case $ac_var in #( - *_cv_*) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 -printf "%s\n" "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; + *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 +$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; esac case $ac_var in #( _ | IFS | as_nl) ;; #( @@ -1884,7 +1936,7 @@ printf "%s\n" "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ) echo - printf "%s\n" "## ----------------- ## + $as_echo "## ----------------- ## ## Output variables. ## ## ----------------- ##" echo @@ -1892,14 +1944,14 @@ printf "%s\n" "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} do eval ac_val=\$$ac_var case $ac_val in - *\'\''*) ac_val=`printf "%s\n" "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; + *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; esac - printf "%s\n" "$ac_var='\''$ac_val'\''" + $as_echo "$ac_var='\''$ac_val'\''" done | sort echo if test -n "$ac_subst_files"; then - printf "%s\n" "## ------------------- ## + $as_echo "## ------------------- ## ## File substitutions. ## ## ------------------- ##" echo @@ -1907,15 +1959,15 @@ printf "%s\n" "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} do eval ac_val=\$$ac_var case $ac_val in - *\'\''*) ac_val=`printf "%s\n" "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; + *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; esac - printf "%s\n" "$ac_var='\''$ac_val'\''" + $as_echo "$ac_var='\''$ac_val'\''" done | sort echo fi if test -s confdefs.h; then - printf "%s\n" "## ----------- ## + $as_echo "## ----------- ## ## confdefs.h. ## ## ----------- ##" echo @@ -1923,8 +1975,8 @@ printf "%s\n" "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} echo fi test "$ac_signal" != 0 && - printf "%s\n" "$as_me: caught signal $ac_signal" - printf "%s\n" "$as_me: exit $exit_status" + $as_echo "$as_me: caught signal $ac_signal" + $as_echo "$as_me: exit $exit_status" } >&5 rm -f core *.core core.conftest.* && rm -f -r conftest* confdefs* conf$$* $ac_clean_files && @@ -1938,50 +1990,65 @@ ac_signal=0 # confdefs.h avoids OS command line length limits that DEFS can exceed. rm -f -r conftest* confdefs.h -printf "%s\n" "/* confdefs.h */" > confdefs.h +$as_echo "/* confdefs.h */" > confdefs.h # Predefined preprocessor variables. -printf "%s\n" "#define PACKAGE_NAME \"$PACKAGE_NAME\"" >>confdefs.h +cat >>confdefs.h <<_ACEOF +#define PACKAGE_NAME "$PACKAGE_NAME" +_ACEOF -printf "%s\n" "#define PACKAGE_TARNAME \"$PACKAGE_TARNAME\"" >>confdefs.h +cat >>confdefs.h <<_ACEOF +#define PACKAGE_TARNAME "$PACKAGE_TARNAME" +_ACEOF -printf "%s\n" "#define PACKAGE_VERSION \"$PACKAGE_VERSION\"" >>confdefs.h +cat >>confdefs.h <<_ACEOF +#define PACKAGE_VERSION "$PACKAGE_VERSION" +_ACEOF -printf "%s\n" "#define PACKAGE_STRING \"$PACKAGE_STRING\"" >>confdefs.h +cat >>confdefs.h <<_ACEOF +#define PACKAGE_STRING "$PACKAGE_STRING" +_ACEOF -printf "%s\n" "#define PACKAGE_BUGREPORT \"$PACKAGE_BUGREPORT\"" >>confdefs.h +cat >>confdefs.h <<_ACEOF +#define PACKAGE_BUGREPORT "$PACKAGE_BUGREPORT" +_ACEOF -printf "%s\n" "#define PACKAGE_URL \"$PACKAGE_URL\"" >>confdefs.h +cat >>confdefs.h <<_ACEOF +#define PACKAGE_URL "$PACKAGE_URL" +_ACEOF # Let the site file select an alternate cache file if it wants to. # Prefer an explicitly selected file to automatically selected ones. +ac_site_file1=NONE +ac_site_file2=NONE if test -n "$CONFIG_SITE"; then - ac_site_files="$CONFIG_SITE" + # We do not want a PATH search for config.site. + case $CONFIG_SITE in #(( + -*) ac_site_file1=./$CONFIG_SITE;; + */*) ac_site_file1=$CONFIG_SITE;; + *) ac_site_file1=./$CONFIG_SITE;; + esac elif test "x$prefix" != xNONE; then - ac_site_files="$prefix/share/config.site $prefix/etc/config.site" + ac_site_file1=$prefix/share/config.site + ac_site_file2=$prefix/etc/config.site else - ac_site_files="$ac_default_prefix/share/config.site $ac_default_prefix/etc/config.site" + ac_site_file1=$ac_default_prefix/share/config.site + ac_site_file2=$ac_default_prefix/etc/config.site fi - -for ac_site_file in $ac_site_files +for ac_site_file in "$ac_site_file1" "$ac_site_file2" do - case $ac_site_file in #( - */*) : - ;; #( - *) : - ac_site_file=./$ac_site_file ;; -esac - if test -f "$ac_site_file" && test -r "$ac_site_file"; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5 -printf "%s\n" "$as_me: loading site script $ac_site_file" >&6;} + test "x$ac_site_file" = xNONE && continue + if test /dev/null != "$ac_site_file" && test -r "$ac_site_file"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5 +$as_echo "$as_me: loading site script $ac_site_file" >&6;} sed 's/^/| /' "$ac_site_file" >&5 . "$ac_site_file" \ - || { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 -printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} + || { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "failed to load site script $ac_site_file -See 'config.log' for more details" "$LINENO" 5; } +See \`config.log' for more details" "$LINENO" 5; } fi done @@ -1989,452 +2056,19 @@ if test -r "$cache_file"; then # Some versions of bash will fail to source /dev/null (special files # actually), so we avoid doing that. DJGPP emulates it as a regular file. if test /dev/null != "$cache_file" && test -f "$cache_file"; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5 -printf "%s\n" "$as_me: loading cache $cache_file" >&6;} + { $as_echo "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5 +$as_echo "$as_me: loading cache $cache_file" >&6;} case $cache_file in [\\/]* | ?:[\\/]* ) . "$cache_file";; *) . "./$cache_file";; esac fi else - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5 -printf "%s\n" "$as_me: creating cache $cache_file" >&6;} + { $as_echo "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5 +$as_echo "$as_me: creating cache $cache_file" >&6;} >$cache_file fi -# Test code for whether the C compiler supports C89 (global declarations) -ac_c_conftest_c89_globals=' -/* Does the compiler advertise C89 conformance? - Do not test the value of __STDC__, because some compilers set it to 0 - while being otherwise adequately conformant. */ -#if !defined __STDC__ -# error "Compiler does not advertise C89 conformance" -#endif - -#include -#include -struct stat; -/* Most of the following tests are stolen from RCS 5.7 src/conf.sh. */ -struct buf { int x; }; -struct buf * (*rcsopen) (struct buf *, struct stat *, int); -static char *e (char **p, int i) -{ - return p[i]; -} -static char *f (char * (*g) (char **, int), char **p, ...) -{ - char *s; - va_list v; - va_start (v,p); - s = g (p, va_arg (v,int)); - va_end (v); - return s; -} - -/* C89 style stringification. */ -#define noexpand_stringify(a) #a -const char *stringified = noexpand_stringify(arbitrary+token=sequence); - -/* C89 style token pasting. Exercises some of the corner cases that - e.g. old MSVC gets wrong, but not very hard. */ -#define noexpand_concat(a,b) a##b -#define expand_concat(a,b) noexpand_concat(a,b) -extern int vA; -extern int vbee; -#define aye A -#define bee B -int *pvA = &expand_concat(v,aye); -int *pvbee = &noexpand_concat(v,bee); - -/* OSF 4.0 Compaq cc is some sort of almost-ANSI by default. It has - function prototypes and stuff, but not \xHH hex character constants. - These do not provoke an error unfortunately, instead are silently treated - as an "x". The following induces an error, until -std is added to get - proper ANSI mode. Curiously \x00 != x always comes out true, for an - array size at least. It is necessary to write \x00 == 0 to get something - that is true only with -std. */ -int osf4_cc_array ['\''\x00'\'' == 0 ? 1 : -1]; - -/* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters - inside strings and character constants. */ -#define FOO(x) '\''x'\'' -int xlc6_cc_array[FOO(a) == '\''x'\'' ? 1 : -1]; - -int test (int i, double x); -struct s1 {int (*f) (int a);}; -struct s2 {int (*f) (double a);}; -int pairnames (int, char **, int *(*)(struct buf *, struct stat *, int), - int, int);' - -# Test code for whether the C compiler supports C89 (body of main). -ac_c_conftest_c89_main=' -ok |= (argc == 0 || f (e, argv, 0) != argv[0] || f (e, argv, 1) != argv[1]); -' - -# Test code for whether the C compiler supports C99 (global declarations) -ac_c_conftest_c99_globals=' -/* Does the compiler advertise C99 conformance? */ -#if !defined __STDC_VERSION__ || __STDC_VERSION__ < 199901L -# error "Compiler does not advertise C99 conformance" -#endif - -// See if C++-style comments work. - -#include -extern int puts (const char *); -extern int printf (const char *, ...); -extern int dprintf (int, const char *, ...); -extern void *malloc (size_t); -extern void free (void *); - -// Check varargs macros. These examples are taken from C99 6.10.3.5. -// dprintf is used instead of fprintf to avoid needing to declare -// FILE and stderr. -#define debug(...) dprintf (2, __VA_ARGS__) -#define showlist(...) puts (#__VA_ARGS__) -#define report(test,...) ((test) ? puts (#test) : printf (__VA_ARGS__)) -static void -test_varargs_macros (void) -{ - int x = 1234; - int y = 5678; - debug ("Flag"); - debug ("X = %d\n", x); - showlist (The first, second, and third items.); - report (x>y, "x is %d but y is %d", x, y); -} - -// Check long long types. -#define BIG64 18446744073709551615ull -#define BIG32 4294967295ul -#define BIG_OK (BIG64 / BIG32 == 4294967297ull && BIG64 % BIG32 == 0) -#if !BIG_OK - #error "your preprocessor is broken" -#endif -#if BIG_OK -#else - #error "your preprocessor is broken" -#endif -static long long int bignum = -9223372036854775807LL; -static unsigned long long int ubignum = BIG64; - -struct incomplete_array -{ - int datasize; - double data[]; -}; - -struct named_init { - int number; - const wchar_t *name; - double average; -}; - -typedef const char *ccp; - -static inline int -test_restrict (ccp restrict text) -{ - // Iterate through items via the restricted pointer. - // Also check for declarations in for loops. - for (unsigned int i = 0; *(text+i) != '\''\0'\''; ++i) - continue; - return 0; -} - -// Check varargs and va_copy. -static bool -test_varargs (const char *format, ...) -{ - va_list args; - va_start (args, format); - va_list args_copy; - va_copy (args_copy, args); - - const char *str = ""; - int number = 0; - float fnumber = 0; - - while (*format) - { - switch (*format++) - { - case '\''s'\'': // string - str = va_arg (args_copy, const char *); - break; - case '\''d'\'': // int - number = va_arg (args_copy, int); - break; - case '\''f'\'': // float - fnumber = va_arg (args_copy, double); - break; - default: - break; - } - } - va_end (args_copy); - va_end (args); - - return *str && number && fnumber; -} -' - -# Test code for whether the C compiler supports C99 (body of main). -ac_c_conftest_c99_main=' - // Check bool. - _Bool success = false; - success |= (argc != 0); - - // Check restrict. - if (test_restrict ("String literal") == 0) - success = true; - char *restrict newvar = "Another string"; - - // Check varargs. - success &= test_varargs ("s, d'\'' f .", "string", 65, 34.234); - test_varargs_macros (); - - // Check flexible array members. - struct incomplete_array *ia = - malloc (sizeof (struct incomplete_array) + (sizeof (double) * 10)); - ia->datasize = 10; - for (int i = 0; i < ia->datasize; ++i) - ia->data[i] = i * 1.234; - // Work around memory leak warnings. - free (ia); - - // Check named initializers. - struct named_init ni = { - .number = 34, - .name = L"Test wide string", - .average = 543.34343, - }; - - ni.number = 58; - - int dynamic_array[ni.number]; - dynamic_array[0] = argv[0][0]; - dynamic_array[ni.number - 1] = 543; - - // work around unused variable warnings - ok |= (!success || bignum == 0LL || ubignum == 0uLL || newvar[0] == '\''x'\'' - || dynamic_array[ni.number - 1] != 543); -' - -# Test code for whether the C compiler supports C11 (global declarations) -ac_c_conftest_c11_globals=' -/* Does the compiler advertise C11 conformance? */ -#if !defined __STDC_VERSION__ || __STDC_VERSION__ < 201112L -# error "Compiler does not advertise C11 conformance" -#endif - -// Check _Alignas. -char _Alignas (double) aligned_as_double; -char _Alignas (0) no_special_alignment; -extern char aligned_as_int; -char _Alignas (0) _Alignas (int) aligned_as_int; - -// Check _Alignof. -enum -{ - int_alignment = _Alignof (int), - int_array_alignment = _Alignof (int[100]), - char_alignment = _Alignof (char) -}; -_Static_assert (0 < -_Alignof (int), "_Alignof is signed"); - -// Check _Noreturn. -int _Noreturn does_not_return (void) { for (;;) continue; } - -// Check _Static_assert. -struct test_static_assert -{ - int x; - _Static_assert (sizeof (int) <= sizeof (long int), - "_Static_assert does not work in struct"); - long int y; -}; - -// Check UTF-8 literals. -#define u8 syntax error! -char const utf8_literal[] = u8"happens to be ASCII" "another string"; - -// Check duplicate typedefs. -typedef long *long_ptr; -typedef long int *long_ptr; -typedef long_ptr long_ptr; - -// Anonymous structures and unions -- taken from C11 6.7.2.1 Example 1. -struct anonymous -{ - union { - struct { int i; int j; }; - struct { int k; long int l; } w; - }; - int m; -} v1; -' - -# Test code for whether the C compiler supports C11 (body of main). -ac_c_conftest_c11_main=' - _Static_assert ((offsetof (struct anonymous, i) - == offsetof (struct anonymous, w.k)), - "Anonymous union alignment botch"); - v1.i = 2; - v1.w.k = 5; - ok |= v1.i != 5; -' - -# Test code for whether the C compiler supports C11 (complete). -ac_c_conftest_c11_program="${ac_c_conftest_c89_globals} -${ac_c_conftest_c99_globals} -${ac_c_conftest_c11_globals} - -int -main (int argc, char **argv) -{ - int ok = 0; - ${ac_c_conftest_c89_main} - ${ac_c_conftest_c99_main} - ${ac_c_conftest_c11_main} - return ok; -} -" - -# Test code for whether the C compiler supports C99 (complete). -ac_c_conftest_c99_program="${ac_c_conftest_c89_globals} -${ac_c_conftest_c99_globals} - -int -main (int argc, char **argv) -{ - int ok = 0; - ${ac_c_conftest_c89_main} - ${ac_c_conftest_c99_main} - return ok; -} -" - -# Test code for whether the C compiler supports C89 (complete). -ac_c_conftest_c89_program="${ac_c_conftest_c89_globals} - -int -main (int argc, char **argv) -{ - int ok = 0; - ${ac_c_conftest_c89_main} - return ok; -} -" - -as_fn_append ac_header_c_list " stdio.h stdio_h HAVE_STDIO_H" -as_fn_append ac_header_c_list " stdlib.h stdlib_h HAVE_STDLIB_H" -as_fn_append ac_header_c_list " string.h string_h HAVE_STRING_H" -as_fn_append ac_header_c_list " inttypes.h inttypes_h HAVE_INTTYPES_H" -as_fn_append ac_header_c_list " stdint.h stdint_h HAVE_STDINT_H" -as_fn_append ac_header_c_list " strings.h strings_h HAVE_STRINGS_H" -as_fn_append ac_header_c_list " sys/stat.h sys_stat_h HAVE_SYS_STAT_H" -as_fn_append ac_header_c_list " sys/types.h sys_types_h HAVE_SYS_TYPES_H" -as_fn_append ac_header_c_list " unistd.h unistd_h HAVE_UNISTD_H" - -# Auxiliary files required by this configure script. -ac_aux_files="install-sh" - -# Locations in which to look for auxiliary files. -ac_aux_dir_candidates="${srcdir}${PATH_SEPARATOR}${srcdir}/..${PATH_SEPARATOR}${srcdir}/../.." - -# Search for a directory containing all of the required auxiliary files, -# $ac_aux_files, from the $PATH-style list $ac_aux_dir_candidates. -# If we don't find one directory that contains all the files we need, -# we report the set of missing files from the *first* directory in -# $ac_aux_dir_candidates and give up. -ac_missing_aux_files="" -ac_first_candidate=: -printf "%s\n" "$as_me:${as_lineno-$LINENO}: looking for aux files: $ac_aux_files" >&5 -as_save_IFS=$IFS; IFS=$PATH_SEPARATOR -as_found=false -for as_dir in $ac_aux_dir_candidates -do - IFS=$as_save_IFS - case $as_dir in #((( - '') as_dir=./ ;; - */) ;; - *) as_dir=$as_dir/ ;; - esac - as_found=: - - printf "%s\n" "$as_me:${as_lineno-$LINENO}: trying $as_dir" >&5 - ac_aux_dir_found=yes - ac_install_sh= - for ac_aux in $ac_aux_files - do - # As a special case, if "install-sh" is required, that requirement - # can be satisfied by any of "install-sh", "install.sh", or "shtool", - # and $ac_install_sh is set appropriately for whichever one is found. - if test x"$ac_aux" = x"install-sh" - then - if test -f "${as_dir}install-sh"; then - printf "%s\n" "$as_me:${as_lineno-$LINENO}: ${as_dir}install-sh found" >&5 - ac_install_sh="${as_dir}install-sh -c" - elif test -f "${as_dir}install.sh"; then - printf "%s\n" "$as_me:${as_lineno-$LINENO}: ${as_dir}install.sh found" >&5 - ac_install_sh="${as_dir}install.sh -c" - elif test -f "${as_dir}shtool"; then - printf "%s\n" "$as_me:${as_lineno-$LINENO}: ${as_dir}shtool found" >&5 - ac_install_sh="${as_dir}shtool install -c" - else - ac_aux_dir_found=no - if $ac_first_candidate; then - ac_missing_aux_files="${ac_missing_aux_files} install-sh" - else - break - fi - fi - else - if test -f "${as_dir}${ac_aux}"; then - printf "%s\n" "$as_me:${as_lineno-$LINENO}: ${as_dir}${ac_aux} found" >&5 - else - ac_aux_dir_found=no - if $ac_first_candidate; then - ac_missing_aux_files="${ac_missing_aux_files} ${ac_aux}" - else - break - fi - fi - fi - done - if test "$ac_aux_dir_found" = yes; then - ac_aux_dir="$as_dir" - break - fi - ac_first_candidate=false - - as_found=false -done -IFS=$as_save_IFS -if $as_found -then : - -else case e in #( - e) as_fn_error $? "cannot find required auxiliary files:$ac_missing_aux_files" "$LINENO" 5 ;; -esac -fi - - -# These three variables are undocumented and unsupported, -# and are intended to be withdrawn in a future Autoconf release. -# They can cause serious problems if a builder's source tree is in a directory -# whose full name contains unusual characters. -if test -f "${ac_aux_dir}config.guess"; then - ac_config_guess="$SHELL ${ac_aux_dir}config.guess" -fi -if test -f "${ac_aux_dir}config.sub"; then - ac_config_sub="$SHELL ${ac_aux_dir}config.sub" -fi -if test -f "$ac_aux_dir/configure"; then - ac_configure="$SHELL ${ac_aux_dir}configure" -fi - # Check that the precious variables saved in the cache have kept the same # value. ac_cache_corrupted=false @@ -2445,12 +2079,12 @@ for ac_var in $ac_precious_vars; do eval ac_new_val=\$ac_env_${ac_var}_value case $ac_old_set,$ac_new_set in set,) - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: '$ac_var' was set to '$ac_old_val' in the previous run" >&5 -printf "%s\n" "$as_me: error: '$ac_var' was set to '$ac_old_val' in the previous run" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5 +$as_echo "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;} ac_cache_corrupted=: ;; ,set) - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: '$ac_var' was not set in the previous run" >&5 -printf "%s\n" "$as_me: error: '$ac_var' was not set in the previous run" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was not set in the previous run" >&5 +$as_echo "$as_me: error: \`$ac_var' was not set in the previous run" >&2;} ac_cache_corrupted=: ;; ,);; *) @@ -2459,24 +2093,24 @@ printf "%s\n" "$as_me: error: '$ac_var' was not set in the previous run" >&2;} ac_old_val_w=`echo x $ac_old_val` ac_new_val_w=`echo x $ac_new_val` if test "$ac_old_val_w" != "$ac_new_val_w"; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: '$ac_var' has changed since the previous run:" >&5 -printf "%s\n" "$as_me: error: '$ac_var' has changed since the previous run:" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' has changed since the previous run:" >&5 +$as_echo "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;} ac_cache_corrupted=: else - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in '$ac_var' since the previous run:" >&5 -printf "%s\n" "$as_me: warning: ignoring whitespace changes in '$ac_var' since the previous run:" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&5 +$as_echo "$as_me: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&2;} eval $ac_var=\$ac_old_val fi - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: former value: '$ac_old_val'" >&5 -printf "%s\n" "$as_me: former value: '$ac_old_val'" >&2;} - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: current value: '$ac_new_val'" >&5 -printf "%s\n" "$as_me: current value: '$ac_new_val'" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: former value: \`$ac_old_val'" >&5 +$as_echo "$as_me: former value: \`$ac_old_val'" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: current value: \`$ac_new_val'" >&5 +$as_echo "$as_me: current value: \`$ac_new_val'" >&2;} fi;; esac # Pass precious variables to config.status. if test "$ac_new_set" = set; then case $ac_new_val in - *\'*) ac_arg=$ac_var=`printf "%s\n" "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;; + *\'*) ac_arg=$ac_var=`$as_echo "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;; *) ac_arg=$ac_var=$ac_new_val ;; esac case " $ac_configure_args " in @@ -2486,12 +2120,11 @@ printf "%s\n" "$as_me: current value: '$ac_new_val'" >&2;} fi done if $ac_cache_corrupted; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 -printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5 -printf "%s\n" "$as_me: error: changes in the environment can compromise the build" >&2;} - as_fn_error $? "run '${MAKE-make} distclean' and/or 'rm $cache_file' - and start over" "$LINENO" 5 + { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5 +$as_echo "$as_me: error: changes in the environment can compromise the build" >&2;} + as_fn_error $? "run \`make distclean' and/or \`rm $cache_file' and start over" "$LINENO" 5 fi ## -------------------- ## ## Main body of script. ## @@ -2506,11 +2139,6 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu -# Store the configure command and arguments for reconfigure target -CONFIGURE_CMD=`echo "$0"` -CONFIGURE_ARGS=`echo "$*"` - - # /usr/local/mailman is the default installation directory @@ -2521,12 +2149,11 @@ BUILD_DATE=`date` # Check for Python! Better be found on $PATH -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for --with-python" >&5 -printf %s "checking for --with-python... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for --with-python" >&5 +$as_echo_n "checking for --with-python... " >&6; } # Check whether --with-python was given. -if test ${with_python+y} -then : +if test "${with_python+set}" = set; then : withval=$with_python; fi @@ -2534,20 +2161,19 @@ case "$with_python" in "") ans="no";; *) ans="$with_python" esac -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ans" >&5 -printf "%s\n" "$ans" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ans" >&5 +$as_echo "$ans" >&6; } if test -z "$with_python" then # Extract the first word of "python3", so it can be a program name with args. set dummy python3; ac_word=$2 -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 -printf %s "checking for $ac_word... " >&6; } -if test ${ac_cv_path_with_python+y} -then : - printf %s "(cached) " >&6 -else case e in #( - e) case $with_python in +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_with_python+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $with_python in [\\/]* | ?:[\\/]*) ac_cv_path_with_python="$with_python" # Let the user override the test with a path. ;; @@ -2556,15 +2182,11 @@ else case e in #( for as_dir in $PATH do IFS=$as_save_IFS - case $as_dir in #((( - '') as_dir=./ ;; - */) ;; - *) as_dir=$as_dir/ ;; - esac + test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do - if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then - ac_cv_path_with_python="$as_dir$ac_word$ac_exec_ext" - printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_with_python="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done @@ -2573,85 +2195,38 @@ IFS=$as_save_IFS test -z "$ac_cv_path_with_python" && ac_cv_path_with_python="/usr/local/bin/python3" ;; -esac ;; esac fi with_python=$ac_cv_path_with_python if test -n "$with_python"; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $with_python" >&5 -printf "%s\n" "$with_python" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_python" >&5 +$as_echo "$with_python" >&6; } else - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 -printf "%s\n" "no" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } fi fi -# Set PYTHON variable for Makefile substitution -PYTHON=$with_python - - -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking Python interpreter" >&5 -printf %s "checking Python interpreter... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking Python interpreter" >&5 +$as_echo_n "checking Python interpreter... " >&6; } if test ! -x $with_python then as_fn_error $? " - Python interpreter not found at $with_python - Please specify the correct path to Python using --with-python - " "$LINENO" 5 -fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $with_python" >&5 -printf "%s\n" "$with_python" >&6; } - -# Check for optional nntplib module -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether to enable NNTP support" >&5 -printf %s "checking whether to enable NNTP support... " >&6; } -# Check whether --enable-nntp was given. -if test ${enable_nntp+y} -then : - enableval=$enable_nntp; enable_nntp=$enableval -else case e in #( - e) enable_nntp=no - ;; -esac -fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $enable_nntp" >&5 -printf "%s\n" "$enable_nntp" >&6; } - -if test "$enable_nntp" = "yes"; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for Python nntplib module" >&5 -printf %s "checking for Python nntplib module... " >&6; } - $with_python -c "import nntplib" >/dev/null 2>&1 - if test $? -ne 0 - then - as_fn_error $? " - Python nntplib module not found but NNTP support was requested - Please install python3-nntplib package - On Debian/Ubuntu: apt-get install python3-nntplib - On RHEL/CentOS: yum install python3-nntplib - Or disable NNTP support with --disable-nntp - " "$LINENO" 5 - fi - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: found" >&5 -printf "%s\n" "found" >&6; } - -printf "%s\n" "#define HAVE_NNTP 1" >>confdefs.h - -fi - if test "$enable_nntp" = "yes"; then - HAVE_NNTP_TRUE= - HAVE_NNTP_FALSE='#' -else - HAVE_NNTP_TRUE='#' - HAVE_NNTP_FALSE= +***** No Python interpreter found! +***** Try including the configure option +***** --with-python=/path/to/python/interpreter" "$LINENO" 5 fi +PYTHON=$with_python +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $PYTHON" >&5 +$as_echo "$PYTHON" >&6; } # See if Python is new enough. -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking Python version" >&5 -printf %s "checking Python version... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking Python version" >&5 +$as_echo_n "checking Python version... " >&6; } cat > conftest.py <= 0x3000000: +if v >= 0x2040000: s = sys.version.split()[0] else: s = "" -with open("conftest.out", "w") as fp: - fp.write("%s\n" % s) +fp = open("conftest.out", "w") +fp.write("%s\n" % s) +fp.close() EOF -$with_python conftest.py +$PYTHON conftest.py version=`cat conftest.out` rm -f conftest.out conftest.py if test -z "$version" then as_fn_error $? " -***** $with_python is too old (or broken) -***** Python 3.0 or newer is required" "$LINENO" 5 +***** $PYTHON is too old (or broken) +***** Python 2.4 or newer is required" "$LINENO" 5 fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $version" >&5 -printf "%s\n" "$version" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $version" >&5 +$as_echo "$version" >&6; } # See if dnspython is installed. -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking dnspython" >&5 -printf %s "checking dnspython... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking dnspython" >&5 +$as_echo_n "checking dnspython... " >&6; } cat > conftest.py < ***** You must get a version < 2.0" "$LINENO" 5 fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $havednspython" >&5 -printf "%s\n" "$havednspython" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $havednspython" >&5 +$as_echo "$havednspython" >&6; } # Check the email package version. -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking Python's email package" >&5 -printf %s "checking Python's email package... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking Python's email package" >&5 +$as_echo_n "checking Python's email package... " >&6; } cat > conftest.py < getver.py <&5 -printf "%s\n" "$needemailpkg" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $needemailpkg" >&5 +$as_echo "$needemailpkg" >&6; } # Check Japanese codecs. -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking Japanese codecs" >&5 -printf %s "checking Japanese codecs... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking Japanese codecs" >&5 +$as_echo_n "checking Japanese codecs... " >&6; } cat > conftest.py <&5 -printf "%s\n" "$needjacodecs" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $needjacodecs" >&5 +$as_echo "$needjacodecs" >&6; } # Check Korean codecs. -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking Korean codecs" >&5 -printf %s "checking Korean codecs... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking Korean codecs" >&5 +$as_echo_n "checking Korean codecs... " >&6; } cat > conftest.py <&5 -printf "%s\n" "$needkocodecs" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $needkocodecs" >&5 +$as_echo "$needkocodecs" >&6; } # Make sure distutils is available. Some Linux Python packages split # distutils into the "-devel" package, so they need both. -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking that Python has a working distutils" >&5 -printf %s "checking that Python has a working distutils... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking that Python has a working distutils" >&5 +$as_echo_n "checking that Python has a working distutils... " >&6; } cat > conftest.py <&5 -printf "%s\n" "$havedistutils" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $havedistutils" >&5 +$as_echo "$havedistutils" >&6; } # Checks for programs. +ac_aux_dir= +for ac_dir in "$srcdir" "$srcdir/.." "$srcdir/../.."; do + if test -f "$ac_dir/install-sh"; then + ac_aux_dir=$ac_dir + ac_install_sh="$ac_aux_dir/install-sh -c" + break + elif test -f "$ac_dir/install.sh"; then + ac_aux_dir=$ac_dir + ac_install_sh="$ac_aux_dir/install.sh -c" + break + elif test -f "$ac_dir/shtool"; then + ac_aux_dir=$ac_dir + ac_install_sh="$ac_aux_dir/shtool install -c" + break + fi +done +if test -z "$ac_aux_dir"; then + as_fn_error $? "cannot find install-sh, install.sh, or shtool in \"$srcdir\" \"$srcdir/..\" \"$srcdir/../..\"" "$LINENO" 5 +fi +# These three variables are undocumented and unsupported, +# and are intended to be withdrawn in a future Autoconf release. +# They can cause serious problems if a builder's source tree is in a directory +# whose full name contains unusual characters. +ac_config_guess="$SHELL $ac_aux_dir/config.guess" # Please don't use this var. +ac_config_sub="$SHELL $ac_aux_dir/config.sub" # Please don't use this var. +ac_configure="$SHELL $ac_aux_dir/configure" # Please don't use this var. - # Find a good install program. We prefer a C program (faster), + +# Find a good install program. We prefer a C program (faster), # so one script is as good as another. But avoid the broken or # incompatible versions: # SysV /etc/install, /usr/sbin/install @@ -2873,25 +2482,20 @@ printf "%s\n" "$havedistutils" >&6; } # OS/2's system install, which has a completely different semantic # ./install, which can be erroneously created by make from ./install.sh. # Reject install programs that cannot install multiple files. -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for a BSD-compatible install" >&5 -printf %s "checking for a BSD-compatible install... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for a BSD-compatible install" >&5 +$as_echo_n "checking for a BSD-compatible install... " >&6; } if test -z "$INSTALL"; then -if test ${ac_cv_path_install+y} -then : - printf %s "(cached) " >&6 -else case e in #( - e) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +if ${ac_cv_path_install+:} false; then : + $as_echo_n "(cached) " >&6 +else + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS - case $as_dir in #((( - '') as_dir=./ ;; - */) ;; - *) as_dir=$as_dir/ ;; - esac - # Account for fact that we put trailing slashes in our PATH walk. -case $as_dir in #(( - ./ | /[cC]/* | \ + test -z "$as_dir" && as_dir=. + # Account for people who put trailing slashes in PATH elements. +case $as_dir/ in #(( + ./ | .// | /[cC]/* | \ /etc/* | /usr/sbin/* | /usr/etc/* | /sbin/* | /usr/afsws/bin/* | \ ?:[\\/]os2[\\/]install[\\/]* | ?:[\\/]OS2[\\/]INSTALL[\\/]* | \ /usr/ucb/* ) ;; @@ -2901,13 +2505,13 @@ case $as_dir in #(( # by default. for ac_prog in ginstall scoinst install; do for ac_exec_ext in '' $ac_executable_extensions; do - if as_fn_executable_p "$as_dir$ac_prog$ac_exec_ext"; then + if as_fn_executable_p "$as_dir/$ac_prog$ac_exec_ext"; then if test $ac_prog = install && - grep dspmsg "$as_dir$ac_prog$ac_exec_ext" >/dev/null 2>&1; then + grep dspmsg "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then # AIX install. It has an incompatible calling convention. : elif test $ac_prog = install && - grep pwplus "$as_dir$ac_prog$ac_exec_ext" >/dev/null 2>&1; then + grep pwplus "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then # program-specific install script used by HP pwplus--don't use. : else @@ -2915,12 +2519,12 @@ case $as_dir in #(( echo one > conftest.one echo two > conftest.two mkdir conftest.dir - if "$as_dir$ac_prog$ac_exec_ext" -c conftest.one conftest.two "`pwd`/conftest.dir/" && + if "$as_dir/$ac_prog$ac_exec_ext" -c conftest.one conftest.two "`pwd`/conftest.dir" && test -s conftest.one && test -s conftest.two && test -s conftest.dir/conftest.one && test -s conftest.dir/conftest.two then - ac_cv_path_install="$as_dir$ac_prog$ac_exec_ext -c" + ac_cv_path_install="$as_dir/$ac_prog$ac_exec_ext -c" break 3 fi fi @@ -2934,10 +2538,9 @@ esac IFS=$as_save_IFS rm -rf conftest.one conftest.two conftest.dir - ;; -esac + fi - if test ${ac_cv_path_install+y}; then + if test "${ac_cv_path_install+set}" = set; then INSTALL=$ac_cv_path_install else # As a last resort, use the slow shell script. Don't cache a @@ -2947,8 +2550,8 @@ fi INSTALL=$ac_install_sh fi fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $INSTALL" >&5 -printf "%s\n" "$INSTALL" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $INSTALL" >&5 +$as_echo "$INSTALL" >&6; } # Use test -z because SunOS4 sh mishandles braces in ${var-val}. # It thinks the first close brace ends the variable substitution. @@ -2958,15 +2561,14 @@ test -z "$INSTALL_SCRIPT" && INSTALL_SCRIPT='${INSTALL}' test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644' -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether ${MAKE-make} sets \$(MAKE)" >&5 -printf %s "checking whether ${MAKE-make} sets \$(MAKE)... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ${MAKE-make} sets \$(MAKE)" >&5 +$as_echo_n "checking whether ${MAKE-make} sets \$(MAKE)... " >&6; } set x ${MAKE-make} -ac_make=`printf "%s\n" "$2" | sed 's/+/p/g; s/[^a-zA-Z0-9_]/_/g'` -if eval test \${ac_cv_prog_make_${ac_make}_set+y} -then : - printf %s "(cached) " >&6 -else case e in #( - e) cat >conftest.make <<\_ACEOF +ac_make=`$as_echo "$2" | sed 's/+/p/g; s/[^a-zA-Z0-9_]/_/g'` +if eval \${ac_cv_prog_make_${ac_make}_set+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat >conftest.make <<\_ACEOF SHELL = /bin/sh all: @echo '@@@%%%=$(MAKE)=@@@%%%' @@ -2978,28 +2580,26 @@ case `${MAKE-make} -f conftest.make 2>/dev/null` in *) eval ac_cv_prog_make_${ac_make}_set=no;; esac -rm -f conftest.make ;; -esac +rm -f conftest.make fi if eval test \$ac_cv_prog_make_${ac_make}_set = yes; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 -printf "%s\n" "yes" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } SET_MAKE= else - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 -printf "%s\n" "no" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } SET_MAKE="MAKE=${MAKE-make}" fi # Extract the first word of "true", so it can be a program name with args. set dummy true; ac_word=$2 -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 -printf %s "checking for $ac_word... " >&6; } -if test ${ac_cv_path_TRUE+y} -then : - printf %s "(cached) " >&6 -else case e in #( - e) case $TRUE in +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_TRUE+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $TRUE in [\\/]* | ?:[\\/]*) ac_cv_path_TRUE="$TRUE" # Let the user override the test with a path. ;; @@ -3009,15 +2609,11 @@ as_dummy="$PATH:/bin:/usr/bin" for as_dir in $as_dummy do IFS=$as_save_IFS - case $as_dir in #((( - '') as_dir=./ ;; - */) ;; - *) as_dir=$as_dir/ ;; - esac + test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do - if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then - ac_cv_path_TRUE="$as_dir$ac_word$ac_exec_ext" - printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_TRUE="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done @@ -3026,27 +2622,25 @@ IFS=$as_save_IFS test -z "$ac_cv_path_TRUE" && ac_cv_path_TRUE="true" ;; -esac ;; esac fi TRUE=$ac_cv_path_TRUE if test -n "$TRUE"; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $TRUE" >&5 -printf "%s\n" "$TRUE" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $TRUE" >&5 +$as_echo "$TRUE" >&6; } else - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 -printf "%s\n" "no" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } fi # Find compiler, allow alternatives to gcc -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for --without-gcc" >&5 -printf %s "checking for --without-gcc... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for --without-gcc" >&5 +$as_echo_n "checking for --without-gcc... " >&6; } # Check whether --with-gcc was given. -if test ${with_gcc+y} -then : +if test "${with_gcc+set}" = set; then : withval=$with_gcc; case $withval in no) CC=cc @@ -3056,13 +2650,12 @@ then : *) CC=$withval without_gcc=$withval;; esac -else case e in #( - e) without_gcc=no; ;; -esac +else + without_gcc=no; fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $without_gcc" >&5 -printf "%s\n" "$without_gcc" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $without_gcc" >&5 +$as_echo "$without_gcc" >&6; } # If the user switches compilers, we can't believe the cache if test ! -z "$ac_cv_prog_CC" -a ! -z "$CC" -a "$CC" != "$ac_cv_prog_CC" @@ -3071,15 +2664,6 @@ then (it is also a good idea to do 'make clean' before compiling)" "$LINENO" 5 fi - - - - - - - - - ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' @@ -3088,44 +2672,38 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args. set dummy ${ac_tool_prefix}gcc; ac_word=$2 -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 -printf %s "checking for $ac_word... " >&6; } -if test ${ac_cv_prog_CC+y} -then : - printf %s "(cached) " >&6 -else case e in #( - e) if test -n "$CC"; then +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS - case $as_dir in #((( - '') as_dir=./ ;; - */) ;; - *) as_dir=$as_dir/ ;; - esac + test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do - if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_CC="${ac_tool_prefix}gcc" - printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS -fi ;; -esac +fi fi CC=$ac_cv_prog_CC if test -n "$CC"; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 -printf "%s\n" "$CC" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } else - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 -printf "%s\n" "no" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } fi @@ -3134,44 +2712,38 @@ if test -z "$ac_cv_prog_CC"; then ac_ct_CC=$CC # Extract the first word of "gcc", so it can be a program name with args. set dummy gcc; ac_word=$2 -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 -printf %s "checking for $ac_word... " >&6; } -if test ${ac_cv_prog_ac_ct_CC+y} -then : - printf %s "(cached) " >&6 -else case e in #( - e) if test -n "$ac_ct_CC"; then +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_CC"; then ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS - case $as_dir in #((( - '') as_dir=./ ;; - */) ;; - *) as_dir=$as_dir/ ;; - esac + test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do - if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_CC="gcc" - printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS -fi ;; -esac +fi fi ac_ct_CC=$ac_cv_prog_ac_ct_CC if test -n "$ac_ct_CC"; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 -printf "%s\n" "$ac_ct_CC" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 +$as_echo "$ac_ct_CC" >&6; } else - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 -printf "%s\n" "no" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } fi if test "x$ac_ct_CC" = x; then @@ -3179,8 +2751,8 @@ fi else case $cross_compiling:$ac_tool_warned in yes:) -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 -printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac CC=$ac_ct_CC @@ -3193,44 +2765,38 @@ if test -z "$CC"; then if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args. set dummy ${ac_tool_prefix}cc; ac_word=$2 -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 -printf %s "checking for $ac_word... " >&6; } -if test ${ac_cv_prog_CC+y} -then : - printf %s "(cached) " >&6 -else case e in #( - e) if test -n "$CC"; then +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS - case $as_dir in #((( - '') as_dir=./ ;; - */) ;; - *) as_dir=$as_dir/ ;; - esac + test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do - if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_CC="${ac_tool_prefix}cc" - printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS -fi ;; -esac +fi fi CC=$ac_cv_prog_CC if test -n "$CC"; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 -printf "%s\n" "$CC" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } else - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 -printf "%s\n" "no" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } fi @@ -3239,13 +2805,12 @@ fi if test -z "$CC"; then # Extract the first word of "cc", so it can be a program name with args. set dummy cc; ac_word=$2 -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 -printf %s "checking for $ac_word... " >&6; } -if test ${ac_cv_prog_CC+y} -then : - printf %s "(cached) " >&6 -else case e in #( - e) if test -n "$CC"; then +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else ac_prog_rejected=no @@ -3253,19 +2818,15 @@ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS - case $as_dir in #((( - '') as_dir=./ ;; - */) ;; - *) as_dir=$as_dir/ ;; - esac + test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do - if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then - if test "$as_dir$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + if test "$as_dir/$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then ac_prog_rejected=yes continue fi ac_cv_prog_CC="cc" - printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done @@ -3281,19 +2842,18 @@ if test $ac_prog_rejected = yes; then # However, it has the same basename, so the bogon will be chosen # first if we set CC to just the basename; use the full file name. shift - ac_cv_prog_CC="$as_dir$ac_word${1+' '}$@" + ac_cv_prog_CC="$as_dir/$ac_word${1+' '}$@" fi fi -fi ;; -esac +fi fi CC=$ac_cv_prog_CC if test -n "$CC"; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 -printf "%s\n" "$CC" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } else - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 -printf "%s\n" "no" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } fi @@ -3304,44 +2864,38 @@ if test -z "$CC"; then do # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. set dummy $ac_tool_prefix$ac_prog; ac_word=$2 -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 -printf %s "checking for $ac_word... " >&6; } -if test ${ac_cv_prog_CC+y} -then : - printf %s "(cached) " >&6 -else case e in #( - e) if test -n "$CC"; then +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS - case $as_dir in #((( - '') as_dir=./ ;; - */) ;; - *) as_dir=$as_dir/ ;; - esac + test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do - if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_CC="$ac_tool_prefix$ac_prog" - printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS -fi ;; -esac +fi fi CC=$ac_cv_prog_CC if test -n "$CC"; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 -printf "%s\n" "$CC" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } else - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 -printf "%s\n" "no" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } fi @@ -3354,44 +2908,38 @@ if test -z "$CC"; then do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 -printf %s "checking for $ac_word... " >&6; } -if test ${ac_cv_prog_ac_ct_CC+y} -then : - printf %s "(cached) " >&6 -else case e in #( - e) if test -n "$ac_ct_CC"; then +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_CC"; then ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS - case $as_dir in #((( - '') as_dir=./ ;; - */) ;; - *) as_dir=$as_dir/ ;; - esac + test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do - if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_CC="$ac_prog" - printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS -fi ;; -esac +fi fi ac_ct_CC=$ac_cv_prog_ac_ct_CC if test -n "$ac_ct_CC"; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 -printf "%s\n" "$ac_ct_CC" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 +$as_echo "$ac_ct_CC" >&6; } else - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 -printf "%s\n" "no" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } fi @@ -3403,8 +2951,8 @@ done else case $cross_compiling:$ac_tool_warned in yes:) -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 -printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac CC=$ac_ct_CC @@ -3412,131 +2960,25 @@ esac fi fi -if test -z "$CC"; then - if test -n "$ac_tool_prefix"; then - # Extract the first word of "${ac_tool_prefix}clang", so it can be a program name with args. -set dummy ${ac_tool_prefix}clang; ac_word=$2 -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 -printf %s "checking for $ac_word... " >&6; } -if test ${ac_cv_prog_CC+y} -then : - printf %s "(cached) " >&6 -else case e in #( - e) if test -n "$CC"; then - ac_cv_prog_CC="$CC" # Let the user override the test. -else -as_save_IFS=$IFS; IFS=$PATH_SEPARATOR -for as_dir in $PATH -do - IFS=$as_save_IFS - case $as_dir in #((( - '') as_dir=./ ;; - */) ;; - *) as_dir=$as_dir/ ;; - esac - for ac_exec_ext in '' $ac_executable_extensions; do - if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then - ac_cv_prog_CC="${ac_tool_prefix}clang" - printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 - break 2 - fi -done - done -IFS=$as_save_IFS - -fi ;; -esac -fi -CC=$ac_cv_prog_CC -if test -n "$CC"; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 -printf "%s\n" "$CC" >&6; } -else - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 -printf "%s\n" "no" >&6; } -fi - - -fi -if test -z "$ac_cv_prog_CC"; then - ac_ct_CC=$CC - # Extract the first word of "clang", so it can be a program name with args. -set dummy clang; ac_word=$2 -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 -printf %s "checking for $ac_word... " >&6; } -if test ${ac_cv_prog_ac_ct_CC+y} -then : - printf %s "(cached) " >&6 -else case e in #( - e) if test -n "$ac_ct_CC"; then - ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. -else -as_save_IFS=$IFS; IFS=$PATH_SEPARATOR -for as_dir in $PATH -do - IFS=$as_save_IFS - case $as_dir in #((( - '') as_dir=./ ;; - */) ;; - *) as_dir=$as_dir/ ;; - esac - for ac_exec_ext in '' $ac_executable_extensions; do - if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then - ac_cv_prog_ac_ct_CC="clang" - printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 - break 2 - fi -done - done -IFS=$as_save_IFS - -fi ;; -esac -fi -ac_ct_CC=$ac_cv_prog_ac_ct_CC -if test -n "$ac_ct_CC"; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 -printf "%s\n" "$ac_ct_CC" >&6; } -else - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 -printf "%s\n" "no" >&6; } -fi - - if test "x$ac_ct_CC" = x; then - CC="" - else - case $cross_compiling:$ac_tool_warned in -yes:) -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 -printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} -ac_tool_warned=yes ;; -esac - CC=$ac_ct_CC - fi -else - CC="$ac_cv_prog_CC" -fi -fi - -test -z "$CC" && { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 -printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} +test -z "$CC" && { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "no acceptable C compiler found in \$PATH -See 'config.log' for more details" "$LINENO" 5; } +See \`config.log' for more details" "$LINENO" 5; } # Provide some information about the compiler. -printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5 +$as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5 set X $ac_compile ac_compiler=$2 -for ac_option in --version -v -V -qversion -version; do +for ac_option in --version -v -V -qversion; do { { ac_try="$ac_compiler $ac_option >&5" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" -printf "%s\n" "$ac_try_echo"; } >&5 +$as_echo "$ac_try_echo"; } >&5 (eval "$ac_compiler $ac_option >&5") 2>conftest.err ac_status=$? if test -s conftest.err; then @@ -3546,7 +2988,7 @@ printf "%s\n" "$ac_try_echo"; } >&5 cat conftest.er1 >&5 fi rm -f conftest.er1 conftest.err - printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } done @@ -3554,7 +2996,7 @@ cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int -main (void) +main () { ; @@ -3566,9 +3008,9 @@ ac_clean_files="$ac_clean_files a.out a.out.dSYM a.exe b.out" # Try to create an executable without -o first, disregard a.out. # It will help us diagnose broken compilers, and finding out an intuition # of exeext. -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the C compiler works" >&5 -printf %s "checking whether the C compiler works... " >&6; } -ac_link_default=`printf "%s\n" "$ac_link" | sed 's/ -o *conftest[^ ]*//'` +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the C compiler works" >&5 +$as_echo_n "checking whether the C compiler works... " >&6; } +ac_link_default=`$as_echo "$ac_link" | sed 's/ -o *conftest[^ ]*//'` # The possible output files: ac_files="a.out conftest.exe conftest a.exe a_out.exe b.out conftest.*" @@ -3589,14 +3031,13 @@ case "(($ac_try" in *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" -printf "%s\n" "$ac_try_echo"; } >&5 +$as_echo "$ac_try_echo"; } >&5 (eval "$ac_link_default") 2>&5 ac_status=$? - printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 - test $ac_status = 0; } -then : - # Autoconf-2.13 could set the ac_cv_exeext variable to 'no'. -# So ignore a value of 'no', otherwise this would lead to 'EXEEXT = no' + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then : + # Autoconf-2.13 could set the ac_cv_exeext variable to `no'. +# So ignore a value of `no', otherwise this would lead to `EXEEXT = no' # in a Makefile. We should not override ac_cv_exeext if it was cached, # so that the user can short-circuit this test for compilers unknown to # Autoconf. @@ -3611,12 +3052,12 @@ do # certainly right. break;; *.* ) - if test ${ac_cv_exeext+y} && test "$ac_cv_exeext" != no; + if test "${ac_cv_exeext+set}" = set && test "$ac_cv_exeext" != no; then :; else ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` fi # We set ac_cv_exeext here because the later test for it is not - # safe: cross compilers may not add the suffix if given an '-o' + # safe: cross compilers may not add the suffix if given an `-o' # argument, so we may need to know it at that point already. # Even if this section looks crufty: it has the advantage of # actually working. @@ -3627,52 +3068,48 @@ do done test "$ac_cv_exeext" = no && ac_cv_exeext= -else case e in #( - e) ac_file='' ;; -esac +else + ac_file='' fi -if test -z "$ac_file" -then : - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 -printf "%s\n" "no" >&6; } -printf "%s\n" "$as_me: failed program was:" >&5 +if test -z "$ac_file"; then : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +$as_echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 -{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 -printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} +{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error 77 "C compiler cannot create executables -See 'config.log' for more details" "$LINENO" 5; } -else case e in #( - e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 -printf "%s\n" "yes" >&6; } ;; -esac +See \`config.log' for more details" "$LINENO" 5; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for C compiler default output file name" >&5 -printf %s "checking for C compiler default output file name... " >&6; } -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_file" >&5 -printf "%s\n" "$ac_file" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler default output file name" >&5 +$as_echo_n "checking for C compiler default output file name... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_file" >&5 +$as_echo "$ac_file" >&6; } ac_exeext=$ac_cv_exeext rm -f -r a.out a.out.dSYM a.exe conftest$ac_cv_exeext b.out ac_clean_files=$ac_clean_files_save -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for suffix of executables" >&5 -printf %s "checking for suffix of executables... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of executables" >&5 +$as_echo_n "checking for suffix of executables... " >&6; } if { { ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" -printf "%s\n" "$ac_try_echo"; } >&5 +$as_echo "$ac_try_echo"; } >&5 (eval "$ac_link") 2>&5 ac_status=$? - printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 - test $ac_status = 0; } -then : - # If both 'conftest.exe' and 'conftest' are 'present' (well, observable) -# catch 'conftest.exe'. For instance with Cygwin, 'ls conftest' will -# work properly (i.e., refer to 'conftest.exe'), while it won't with -# 'rm'. + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then : + # If both `conftest.exe' and `conftest' are `present' (well, observable) +# catch `conftest.exe'. For instance with Cygwin, `ls conftest' will +# work properly (i.e., refer to `conftest.exe'), while it won't with +# `rm'. for ac_file in conftest.exe conftest conftest.*; do test -f "$ac_file" || continue case $ac_file in @@ -3682,16 +3119,15 @@ for ac_file in conftest.exe conftest conftest.*; do * ) break;; esac done -else case e in #( - e) { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 -printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} +else + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "cannot compute suffix of executables: cannot compile and link -See 'config.log' for more details" "$LINENO" 5; } ;; -esac +See \`config.log' for more details" "$LINENO" 5; } fi rm -f conftest conftest$ac_cv_exeext -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5 -printf "%s\n" "$ac_cv_exeext" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5 +$as_echo "$ac_cv_exeext" >&6; } rm -f conftest.$ac_ext EXEEXT=$ac_cv_exeext @@ -3700,11 +3136,9 @@ cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int -main (void) +main () { FILE *f = fopen ("conftest.out", "w"); - if (!f) - return 1; return ferror (f) || fclose (f) != 0; ; @@ -3714,8 +3148,8 @@ _ACEOF ac_clean_files="$ac_clean_files conftest.out" # Check that the compiler produces executables we can run. If not, either # the compiler is broken, or we cross compile. -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether we are cross compiling" >&5 -printf %s "checking whether we are cross compiling... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are cross compiling" >&5 +$as_echo_n "checking whether we are cross compiling... " >&6; } if test "$cross_compiling" != yes; then { { ac_try="$ac_link" case "(($ac_try" in @@ -3723,10 +3157,10 @@ case "(($ac_try" in *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" -printf "%s\n" "$ac_try_echo"; } >&5 +$as_echo "$ac_try_echo"; } >&5 (eval "$ac_link") 2>&5 ac_status=$? - printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } if { ac_try='./conftest$ac_cv_exeext' { { case "(($ac_try" in @@ -3734,41 +3168,39 @@ printf "%s\n" "$ac_try_echo"; } >&5 *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" -printf "%s\n" "$ac_try_echo"; } >&5 +$as_echo "$ac_try_echo"; } >&5 (eval "$ac_try") 2>&5 ac_status=$? - printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; }; then cross_compiling=no else if test "$cross_compiling" = maybe; then cross_compiling=yes else - { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 -printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} -as_fn_error 77 "cannot run C compiled programs. -If you meant to cross compile, use '--host'. -See 'config.log' for more details" "$LINENO" 5; } + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "cannot run C compiled programs. +If you meant to cross compile, use \`--host'. +See \`config.log' for more details" "$LINENO" 5; } fi fi fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $cross_compiling" >&5 -printf "%s\n" "$cross_compiling" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $cross_compiling" >&5 +$as_echo "$cross_compiling" >&6; } -rm -f conftest.$ac_ext conftest$ac_cv_exeext \ - conftest.o conftest.obj conftest.out +rm -f conftest.$ac_ext conftest$ac_cv_exeext conftest.out ac_clean_files=$ac_clean_files_save -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5 -printf %s "checking for suffix of object files... " >&6; } -if test ${ac_cv_objext+y} -then : - printf %s "(cached) " >&6 -else case e in #( - e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5 +$as_echo_n "checking for suffix of object files... " >&6; } +if ${ac_cv_objext+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int -main (void) +main () { ; @@ -3782,12 +3214,11 @@ case "(($ac_try" in *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" -printf "%s\n" "$ac_try_echo"; } >&5 +$as_echo "$ac_try_echo"; } >&5 (eval "$ac_compile") 2>&5 ac_status=$? - printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 - test $ac_status = 0; } -then : + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then : for ac_file in conftest.o conftest.obj conftest.*; do test -f "$ac_file" || continue; case $ac_file in @@ -3796,34 +3227,31 @@ then : break;; esac done -else case e in #( - e) printf "%s\n" "$as_me: failed program was:" >&5 +else + $as_echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 -{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 -printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} +{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "cannot compute suffix of object files: cannot compile -See 'config.log' for more details" "$LINENO" 5; } ;; -esac +See \`config.log' for more details" "$LINENO" 5; } fi -rm -f conftest.$ac_cv_objext conftest.$ac_ext ;; -esac +rm -f conftest.$ac_cv_objext conftest.$ac_ext fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_objext" >&5 -printf "%s\n" "$ac_cv_objext" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_objext" >&5 +$as_echo "$ac_cv_objext" >&6; } OBJEXT=$ac_cv_objext ac_objext=$OBJEXT -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the compiler supports GNU C" >&5 -printf %s "checking whether the compiler supports GNU C... " >&6; } -if test ${ac_cv_c_compiler_gnu+y} -then : - printf %s "(cached) " >&6 -else case e in #( - e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C compiler" >&5 +$as_echo_n "checking whether we are using the GNU C compiler... " >&6; } +if ${ac_cv_c_compiler_gnu+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int -main (void) +main () { #ifndef __GNUC__ choke me @@ -3833,36 +3261,30 @@ main (void) return 0; } _ACEOF -if ac_fn_c_try_compile "$LINENO" -then : +if ac_fn_c_try_compile "$LINENO"; then : ac_compiler_gnu=yes -else case e in #( - e) ac_compiler_gnu=no ;; -esac +else + ac_compiler_gnu=no fi -rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext ac_cv_c_compiler_gnu=$ac_compiler_gnu - ;; -esac -fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5 -printf "%s\n" "$ac_cv_c_compiler_gnu" >&6; } -ac_compiler_gnu=$ac_cv_c_compiler_gnu +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5 +$as_echo "$ac_cv_c_compiler_gnu" >&6; } if test $ac_compiler_gnu = yes; then GCC=yes else GCC= fi -ac_test_CFLAGS=${CFLAGS+y} +ac_test_CFLAGS=${CFLAGS+set} ac_save_CFLAGS=$CFLAGS -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5 -printf %s "checking whether $CC accepts -g... " >&6; } -if test ${ac_cv_prog_cc_g+y} -then : - printf %s "(cached) " >&6 -else case e in #( - e) ac_save_c_werror_flag=$ac_c_werror_flag +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5 +$as_echo_n "checking whether $CC accepts -g... " >&6; } +if ${ac_cv_prog_cc_g+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_save_c_werror_flag=$ac_c_werror_flag ac_c_werror_flag=yes ac_cv_prog_cc_g=no CFLAGS="-g" @@ -3870,63 +3292,57 @@ else case e in #( /* end confdefs.h. */ int -main (void) +main () { ; return 0; } _ACEOF -if ac_fn_c_try_compile "$LINENO" -then : +if ac_fn_c_try_compile "$LINENO"; then : ac_cv_prog_cc_g=yes -else case e in #( - e) CFLAGS="" +else + CFLAGS="" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int -main (void) +main () { ; return 0; } _ACEOF -if ac_fn_c_try_compile "$LINENO" -then : +if ac_fn_c_try_compile "$LINENO"; then : -else case e in #( - e) ac_c_werror_flag=$ac_save_c_werror_flag +else + ac_c_werror_flag=$ac_save_c_werror_flag CFLAGS="-g" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int -main (void) +main () { ; return 0; } _ACEOF -if ac_fn_c_try_compile "$LINENO" -then : +if ac_fn_c_try_compile "$LINENO"; then : ac_cv_prog_cc_g=yes fi -rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; -esac +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi -rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; -esac +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi -rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext - ac_c_werror_flag=$ac_save_c_werror_flag ;; -esac +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_c_werror_flag=$ac_save_c_werror_flag fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5 -printf "%s\n" "$ac_cv_prog_cc_g" >&6; } -if test $ac_test_CFLAGS; then +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5 +$as_echo "$ac_cv_prog_cc_g" >&6; } +if test "$ac_test_CFLAGS" = set; then CFLAGS=$ac_save_CFLAGS elif test $ac_cv_prog_cc_g = yes; then if test "$GCC" = yes; then @@ -3941,153 +3357,94 @@ else CFLAGS= fi fi -ac_prog_cc_stdc=no -if test x$ac_prog_cc_stdc = xno -then : - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C11 features" >&5 -printf %s "checking for $CC option to enable C11 features... " >&6; } -if test ${ac_cv_prog_cc_c11+y} -then : - printf %s "(cached) " >&6 -else case e in #( - e) ac_cv_prog_cc_c11=no +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C89" >&5 +$as_echo_n "checking for $CC option to accept ISO C89... " >&6; } +if ${ac_cv_prog_cc_c89+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_cv_prog_cc_c89=no ac_save_CC=$CC cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ -$ac_c_conftest_c11_program -_ACEOF -for ac_arg in '' -std=gnu11 -do - CC="$ac_save_CC $ac_arg" - if ac_fn_c_try_compile "$LINENO" -then : - ac_cv_prog_cc_c11=$ac_arg -fi -rm -f core conftest.err conftest.$ac_objext conftest.beam - test "x$ac_cv_prog_cc_c11" != "xno" && break -done -rm -f conftest.$ac_ext -CC=$ac_save_CC ;; -esac -fi +#include +#include +struct stat; +/* Most of the following tests are stolen from RCS 5.7's src/conf.sh. */ +struct buf { int x; }; +FILE * (*rcsopen) (struct buf *, struct stat *, int); +static char *e (p, i) + char **p; + int i; +{ + return p[i]; +} +static char *f (char * (*g) (char **, int), char **p, ...) +{ + char *s; + va_list v; + va_start (v,p); + s = g (p, va_arg (v,int)); + va_end (v); + return s; +} -if test "x$ac_cv_prog_cc_c11" = xno -then : - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 -printf "%s\n" "unsupported" >&6; } -else case e in #( - e) if test "x$ac_cv_prog_cc_c11" = x -then : - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 -printf "%s\n" "none needed" >&6; } -else case e in #( - e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c11" >&5 -printf "%s\n" "$ac_cv_prog_cc_c11" >&6; } - CC="$CC $ac_cv_prog_cc_c11" ;; -esac -fi - ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c11 - ac_prog_cc_stdc=c11 ;; -esac -fi -fi -if test x$ac_prog_cc_stdc = xno -then : - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C99 features" >&5 -printf %s "checking for $CC option to enable C99 features... " >&6; } -if test ${ac_cv_prog_cc_c99+y} -then : - printf %s "(cached) " >&6 -else case e in #( - e) ac_cv_prog_cc_c99=no -ac_save_CC=$CC -cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -$ac_c_conftest_c99_program -_ACEOF -for ac_arg in '' -std=gnu99 -std=c99 -c99 -qlanglvl=extc1x -qlanglvl=extc99 -AC99 -D_STDC_C99= -do - CC="$ac_save_CC $ac_arg" - if ac_fn_c_try_compile "$LINENO" -then : - ac_cv_prog_cc_c99=$ac_arg -fi -rm -f core conftest.err conftest.$ac_objext conftest.beam - test "x$ac_cv_prog_cc_c99" != "xno" && break -done -rm -f conftest.$ac_ext -CC=$ac_save_CC ;; -esac -fi +/* OSF 4.0 Compaq cc is some sort of almost-ANSI by default. It has + function prototypes and stuff, but not '\xHH' hex character constants. + These don't provoke an error unfortunately, instead are silently treated + as 'x'. The following induces an error, until -std is added to get + proper ANSI mode. Curiously '\x00'!='x' always comes out true, for an + array size at least. It's necessary to write '\x00'==0 to get something + that's true only with -std. */ +int osf4_cc_array ['\x00' == 0 ? 1 : -1]; -if test "x$ac_cv_prog_cc_c99" = xno -then : - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 -printf "%s\n" "unsupported" >&6; } -else case e in #( - e) if test "x$ac_cv_prog_cc_c99" = x -then : - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 -printf "%s\n" "none needed" >&6; } -else case e in #( - e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c99" >&5 -printf "%s\n" "$ac_cv_prog_cc_c99" >&6; } - CC="$CC $ac_cv_prog_cc_c99" ;; -esac -fi - ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c99 - ac_prog_cc_stdc=c99 ;; -esac -fi -fi -if test x$ac_prog_cc_stdc = xno -then : - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C89 features" >&5 -printf %s "checking for $CC option to enable C89 features... " >&6; } -if test ${ac_cv_prog_cc_c89+y} -then : - printf %s "(cached) " >&6 -else case e in #( - e) ac_cv_prog_cc_c89=no -ac_save_CC=$CC -cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -$ac_c_conftest_c89_program +/* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters + inside strings and character constants. */ +#define FOO(x) 'x' +int xlc6_cc_array[FOO(a) == 'x' ? 1 : -1]; + +int test (int i, double x); +struct s1 {int (*f) (int a);}; +struct s2 {int (*f) (double a);}; +int pairnames (int, char **, FILE *(*)(struct buf *, struct stat *, int), int, int); +int argc; +char **argv; +int +main () +{ +return f (e, argv, 0) != argv[0] || f (e, argv, 1) != argv[1]; + ; + return 0; +} _ACEOF -for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__" +for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std \ + -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__" do CC="$ac_save_CC $ac_arg" - if ac_fn_c_try_compile "$LINENO" -then : + if ac_fn_c_try_compile "$LINENO"; then : ac_cv_prog_cc_c89=$ac_arg fi -rm -f core conftest.err conftest.$ac_objext conftest.beam +rm -f core conftest.err conftest.$ac_objext test "x$ac_cv_prog_cc_c89" != "xno" && break done rm -f conftest.$ac_ext -CC=$ac_save_CC ;; -esac -fi +CC=$ac_save_CC -if test "x$ac_cv_prog_cc_c89" = xno -then : - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 -printf "%s\n" "unsupported" >&6; } -else case e in #( - e) if test "x$ac_cv_prog_cc_c89" = x -then : - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 -printf "%s\n" "none needed" >&6; } -else case e in #( - e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5 -printf "%s\n" "$ac_cv_prog_cc_c89" >&6; } - CC="$CC $ac_cv_prog_cc_c89" ;; -esac fi - ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c89 - ac_prog_cc_stdc=c89 ;; +# AC_CACHE_VAL +case "x$ac_cv_prog_cc_c89" in + x) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 +$as_echo "none needed" >&6; } ;; + xno) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 +$as_echo "unsupported" >&6; } ;; + *) + CC="$CC $ac_cv_prog_cc_c89" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5 +$as_echo "$ac_cv_prog_cc_c89" >&6; } ;; esac -fi +if test "x$ac_cv_prog_cc_c89" != xno; then : + fi ac_ext=c @@ -4114,13 +3471,12 @@ then fi # We better be able to execute interpreters -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether #! works in shell scripts" >&5 -printf %s "checking whether #! works in shell scripts... " >&6; } -if test ${ac_cv_sys_interpreter+y} -then : - printf %s "(cached) " >&6 -else case e in #( - e) echo '#! /bin/cat +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether #! works in shell scripts" >&5 +$as_echo_n "checking whether #! works in shell scripts... " >&6; } +if ${ac_cv_sys_interpreter+:} false; then : + $as_echo_n "(cached) " >&6 +else + echo '#! /bin/cat exit 69 ' >conftest chmod u+x conftest @@ -4130,11 +3486,10 @@ if test $? -ne 69; then else ac_cv_sys_interpreter=no fi -rm -f conftest ;; -esac +rm -f conftest fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_sys_interpreter" >&5 -printf "%s\n" "$ac_cv_sys_interpreter" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_sys_interpreter" >&5 +$as_echo "$ac_cv_sys_interpreter" >&6; } interpval=$ac_cv_sys_interpreter if test "$ac_cv_sys_interpreter" != "yes" @@ -4149,12 +3504,11 @@ fi # Check for an alternate data directory, separate from installation dir. default_var_prefix="/var/mailman" -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for --with-var-prefix" >&5 -printf %s "checking for --with-var-prefix... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for --with-var-prefix" >&5 +$as_echo_n "checking for --with-var-prefix... " >&6; } # Check whether --with-var-prefix was given. -if test ${with_var_prefix+y} -then : +if test "${with_var_prefix+set}" = set; then : withval=$with_var_prefix; fi @@ -4163,15 +3517,14 @@ case "$with_var_prefix" in ""|no) VAR_PREFIX="$prefix"; ans="no";; *) VAR_PREFIX="$with_var_prefix"; ans=$VAR_PREFIX; esac -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ans" >&5 -printf "%s\n" "$ans" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ans" >&5 +$as_echo "$ans" >&6; } -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for --with-permcheck" >&5 -printf %s "checking for --with-permcheck... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for --with-permcheck" >&5 +$as_echo_n "checking for --with-permcheck... " >&6; } # Check whether --with-permcheck was given. -if test ${with_permcheck+y} -then : +if test "${with_permcheck+set}" = set; then : withval=$with_permcheck; fi @@ -4179,8 +3532,8 @@ if test -z "$with_permcheck" then with_permcheck="yes" fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $with_permcheck" >&5 -printf "%s\n" "$with_permcheck" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_permcheck" >&5 +$as_echo "$with_permcheck" >&6; } # Now make sure that $prefix is set up correctly. It must be group # owned by the target group, it must have the group sticky bit set, and # it must be a+rx @@ -4200,12 +3553,11 @@ fi # Check for some other uid to use than `mailman' -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for --with-username" >&5 -printf %s "checking for --with-username... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for --with-username" >&5 +$as_echo_n "checking for --with-username... " >&6; } # Check whether --with-username was given. -if test ${with_username+y} -then : +if test "${with_username+set}" = set; then : withval=$with_username; fi @@ -4215,13 +3567,13 @@ then with_username="mailman" fi USERNAME=$with_username -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $USERNAME" >&5 -printf "%s\n" "$USERNAME" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $USERNAME" >&5 +$as_echo "$USERNAME" >&6; } # User `mailman' must exist -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for user name $USERNAME" >&5 -printf %s "checking for user name $USERNAME... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for user name \"$USERNAME\"" >&5 +$as_echo_n "checking for user name \"$USERNAME\"... " >&6; } # MAILMAN_USER == variable name # $USERNAME == user id to check for @@ -4242,10 +3594,11 @@ for user in "$USERNAME".split(): break except KeyError: uname = '' -with open("conftest.out", "w") as fp: - fp.write("%s\n" % uname) +fp = open("conftest.out", "w") +fp.write("%s\n" % uname) +fp.close() EOF - $with_python conftest.py + $PYTHON conftest.py MAILMAN_USER=`cat conftest.out` fi @@ -4255,23 +3608,22 @@ then if test "$with_permcheck" = "yes" then as_fn_error $? " -***** No $USERNAME user found! -***** Your system must have a $USERNAME user defined +***** No \"$USERNAME\" user found! +***** Your system must have a \"$USERNAME\" user defined ***** (usually in your /etc/passwd file). Please see the INSTALL ***** file for details." "$LINENO" 5 fi fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: okay" >&5 -printf "%s\n" "okay" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: okay" >&5 +$as_echo "okay" >&6; } # Check for some other gid to use than `mailman' -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for --with-groupname" >&5 -printf %s "checking for --with-groupname... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for --with-groupname" >&5 +$as_echo_n "checking for --with-groupname... " >&6; } # Check whether --with-groupname was given. -if test ${with_groupname+y} -then : +if test "${with_groupname+set}" = set; then : withval=$with_groupname; fi @@ -4281,14 +3633,14 @@ then with_groupname="mailman" fi GROUPNAME=$with_groupname -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $GROUPNAME" >&5 -printf "%s\n" "$GROUPNAME" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $GROUPNAME" >&5 +$as_echo "$GROUPNAME" >&6; } # Target group must exist -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for group name $GROUPNAME" >&5 -printf %s "checking for group name $GROUPNAME... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for group name \"$GROUPNAME\"" >&5 +$as_echo_n "checking for group name \"$GROUPNAME\"... " >&6; } # MAILMAN_GROUP == variable name # $GROUPNAME == user id to check for @@ -4309,10 +3661,11 @@ for group in "$GROUPNAME".split(): break except KeyError: gname = '' -with open("conftest.out", "w") as fp: - fp.write("%s\n" % gname) +fp = open("conftest.out", "w") +fp.write("%s\n" % gname) +fp.close() EOF - $with_python conftest.py + $PYTHON conftest.py MAILMAN_GROUP=`cat conftest.out` fi @@ -4322,18 +3675,18 @@ then if test "$with_permcheck" = "yes" then as_fn_error $? " -***** No $GROUPNAME group found! -***** Your system must have a $GROUPNAME group defined +***** No \"$GROUPNAME\" group found! +***** Your system must have a \"$GROUPNAME\" group defined ***** (usually in your /etc/group file). Please see the INSTALL ***** file for details." "$LINENO" 5 fi fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: okay" >&5 -printf "%s\n" "okay" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: okay" >&5 +$as_echo "okay" >&6; } -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking permissions on $prefixcheck" >&5 -printf %s "checking permissions on $prefixcheck... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking permissions on $prefixcheck" >&5 +$as_echo_n "checking permissions on $prefixcheck... " >&6; } cat > conftest.py <&5 -printf "%s\n" "$status" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $status" >&5 +$as_echo "$status" >&6; } # Now find the UIDs and GIDs # Support --with-mail-gid and --with-cgi-gid -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for mail wrapper group; i.e. --with-mail-gid" >&5 -printf %s "checking for mail wrapper group; i.e. --with-mail-gid... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for mail wrapper group; i.e. --with-mail-gid" >&5 +$as_echo_n "checking for mail wrapper group; i.e. --with-mail-gid... " >&6; } # Check whether --with-mail-gid was given. -if test ${with_mail_gid+y} -then : +if test "${with_mail_gid+set}" = set; then : withval=$with_mail_gid; fi @@ -4421,10 +3774,11 @@ for group in "$with_mail_gid".split(): break except KeyError: gname = '' -with open("conftest.out", "w") as fp: - fp.write("%s\n" % gname) +fp = open("conftest.out", "w") +fp.write("%s\n" % gname) +fp.close() EOF - $with_python conftest.py + $PYTHON conftest.py MAIL_GROUP=`cat conftest.out` fi @@ -4434,7 +3788,7 @@ then if test "$with_permcheck" = "yes" then as_fn_error $? " -***** No group name $with_mail_gid found for the mail wrapper program. +***** No group name \"$with_mail_gid\" found for the mail wrapper program. ***** This is the group that your mail server will use to run Mailman's ***** programs. You should specify an existing group with the ***** --with-mail-gid configure option, or use --without-permcheck to @@ -4444,16 +3798,15 @@ then MAIL_GROUP=$with_mail_gid fi fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $MAIL_GROUP" >&5 -printf "%s\n" "$MAIL_GROUP" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $MAIL_GROUP" >&5 +$as_echo "$MAIL_GROUP" >&6; } -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for CGI wrapper group; i.e. --with-cgi-gid" >&5 -printf %s "checking for CGI wrapper group; i.e. --with-cgi-gid... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for CGI wrapper group; i.e. --with-cgi-gid" >&5 +$as_echo_n "checking for CGI wrapper group; i.e. --with-cgi-gid... " >&6; } # Check whether --with-cgi-gid was given. -if test ${with_cgi_gid+y} -then : +if test "${with_cgi_gid+set}" = set; then : withval=$with_cgi_gid; fi @@ -4482,10 +3835,11 @@ for group in "$with_cgi_gid".split(): break except KeyError: gname = '' -with open("conftest.out", "w") as fp: - fp.write("%s\n" % gname) +fp = open("conftest.out", "w") +fp.write("%s\n" % gname) +fp.close() EOF - $with_python conftest.py + $PYTHON conftest.py CGI_GROUP=`cat conftest.out` fi @@ -4495,7 +3849,7 @@ then if test "$with_permcheck" = "yes" then as_fn_error $? " -***** No group name $with_cgi_gid found for the CGI wrapper program. +***** No group name \"$with_cgi_gid\" found for the CGI wrapper program. ***** This is the group that your web server will use to run Mailman's ***** programs. You should specify an existing group with the ***** --with-cgi-gid configure option, or use --without-permcheck to @@ -4505,18 +3859,17 @@ then CGI_GROUP=$with_cgi_gid fi fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CGI_GROUP" >&5 -printf "%s\n" "$CGI_GROUP" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $CGI_GROUP" >&5 +$as_echo "$CGI_GROUP" >&6; } # Check for CGI extensions, required by some Web servers -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for CGI extensions" >&5 -printf %s "checking for CGI extensions... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for CGI extensions" >&5 +$as_echo_n "checking for CGI extensions... " >&6; } # Check whether --with-cgi-ext was given. -if test ${with_cgi_ext+y} -then : +if test "${with_cgi_ext+set}" = set; then : withval=$with_cgi_ext; fi @@ -4527,18 +3880,17 @@ then else CGIEXT=$with_cgi_ext fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $with_cgi_ext" >&5 -printf "%s\n" "$with_cgi_ext" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_cgi_ext" >&5 +$as_echo "$with_cgi_ext" >&6; } # figure out the default mail hostname and url host component -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for --with-mailhost" >&5 -printf %s "checking for --with-mailhost... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for --with-mailhost" >&5 +$as_echo_n "checking for --with-mailhost... " >&6; } # Check whether --with-mailhost was given. -if test ${with_mailhost+y} -then : +if test "${with_mailhost+set}" = set; then : withval=$with_mailhost; fi @@ -4549,16 +3901,15 @@ then else MAILHOST=$with_mailhost fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $with_mailhost" >&5 -printf "%s\n" "$with_mailhost" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_mailhost" >&5 +$as_echo "$with_mailhost" >&6; } -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for --with-urlhost" >&5 -printf %s "checking for --with-urlhost... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for --with-urlhost" >&5 +$as_echo_n "checking for --with-urlhost... " >&6; } # Check whether --with-urlhost was given. -if test ${with_urlhost+y} -then : +if test "${with_urlhost+set}" = set; then : withval=$with_urlhost; fi @@ -4569,8 +3920,8 @@ then else URLHOST=$with_urlhost fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $with_urlhost" >&5 -printf "%s\n" "$with_urlhost" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_urlhost" >&5 +$as_echo "$with_urlhost" >&6; } @@ -4578,50 +3929,44 @@ cat > conftest.py <&5 -printf %s "checking for default mail host name... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for default mail host name" >&5 +$as_echo_n "checking for default mail host name... " >&6; } if test -z "$MAILHOST" then MAILHOST=`sed q conftest.out` fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $MAILHOST" >&5 -printf "%s\n" "$MAILHOST" >&6; } -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for default URL host component" >&5 -printf %s "checking for default URL host component... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $MAILHOST" >&5 +$as_echo "$MAILHOST" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for default URL host component" >&5 +$as_echo_n "checking for default URL host component... " >&6; } if test -z "$URLHOST" then URLHOST=`sed -n '$p' conftest.out` fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $URLHOST" >&5 -printf "%s\n" "$URLHOST" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $URLHOST" >&5 +$as_echo "$URLHOST" >&6; } rm -f conftest.out conftest.py # Checks for libraries. -ac_fn_c_check_func "$LINENO" "strerror" "ac_cv_func_strerror" -if test "x$ac_cv_func_strerror" = xyes -then : - printf "%s\n" "#define HAVE_STRERROR 1" >>confdefs.h - -fi -ac_fn_c_check_func "$LINENO" "setregid" "ac_cv_func_setregid" -if test "x$ac_cv_func_setregid" = xyes -then : - printf "%s\n" "#define HAVE_SETREGID 1" >>confdefs.h - -fi -ac_fn_c_check_func "$LINENO" "syslog" "ac_cv_func_syslog" -if test "x$ac_cv_func_syslog" = xyes -then : - printf "%s\n" "#define HAVE_SYSLOG 1" >>confdefs.h +for ac_func in strerror setregid syslog +do : + as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` +ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var" +if eval test \"x\$"$as_ac_var"\" = x"yes"; then : + cat >>confdefs.h <<_ACEOF +#define `$as_echo "HAVE_$ac_func" | $as_tr_cpp` 1 +_ACEOF fi +done if test $ac_cv_func_syslog = no; then # syslog is not in the default libraries. See if it's in some other. @@ -4629,282 +3974,559 @@ if test $ac_cv_func_syslog = no; then # one of several _real_ functions in syslog.h, so we need to do the test # with the appropriate include. for lib in bsd socket inet; do - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for syslog in -l$lib" >&5 -printf %s "checking for syslog in -l$lib... " >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for syslog in -l$lib" >&5 +$as_echo_n "checking for syslog in -l$lib... " >&6; } Mailman_LIBS_save="$LIBS"; LIBS="$LIBS -l$lib" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int -main (void) +main () { syslog(LOG_DEBUG, "Just a test..."); ; return 0; } _ACEOF -if ac_fn_c_try_link "$LINENO" -then : - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 -printf "%s\n" "yes" >&6; } - printf "%s\n" "#define HAVE_SYSLOG 1" >>confdefs.h +if ac_fn_c_try_link "$LINENO"; then : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + $as_echo "#define HAVE_SYSLOG 1" >>confdefs.h break -else case e in #( - e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 -printf "%s\n" "no" >&6; } - LIBS="$Mailman_LIBS_save" ;; -esac +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + LIBS="$Mailman_LIBS_save" fi -rm -f core conftest.err conftest.$ac_objext conftest.beam \ +rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext unset Mailman_LIBS_save done fi # Checks for header files. -ac_header= ac_cache= -for ac_item in $ac_header_c_list + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to run the C preprocessor" >&5 +$as_echo_n "checking how to run the C preprocessor... " >&6; } +# On Suns, sometimes $CPP names a directory. +if test -n "$CPP" && test -d "$CPP"; then + CPP= +fi +if test -z "$CPP"; then + if ${ac_cv_prog_CPP+:} false; then : + $as_echo_n "(cached) " >&6 +else + # Double quotes because CPP needs to be expanded + for CPP in "$CC -E" "$CC -E -traditional-cpp" "/lib/cpp" + do + ac_preproc_ok=false +for ac_c_preproc_warn_flag in '' yes do - if test $ac_cache; then - ac_fn_c_check_header_compile "$LINENO" $ac_header ac_cv_header_$ac_cache "$ac_includes_default" - if eval test \"x\$ac_cv_header_$ac_cache\" = xyes; then - printf "%s\n" "#define $ac_item 1" >> confdefs.h - fi - ac_header= ac_cache= - elif test $ac_header; then - ac_cache=$ac_item - else - ac_header=$ac_item - fi + # Use a header file that comes with gcc, so configuring glibc + # with a fresh cross-compiler works. + # Prefer to if __STDC__ is defined, since + # exists even on freestanding compilers. + # On the NeXT, cc -E runs the code through the compiler's parser, + # not just through cpp. "Syntax error" is here to catch this case. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#ifdef __STDC__ +# include +#else +# include +#endif + Syntax error +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + +else + # Broken: fails on valid input. +continue +fi +rm -f conftest.err conftest.i conftest.$ac_ext + + # OK, works on sane cases. Now check whether nonexistent headers + # can be detected and how. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + # Broken: success on invalid input. +continue +else + # Passes both tests. +ac_preproc_ok=: +break +fi +rm -f conftest.err conftest.i conftest.$ac_ext + done +# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. +rm -f conftest.i conftest.err conftest.$ac_ext +if $ac_preproc_ok; then : + break +fi + done + ac_cv_prog_CPP=$CPP +fi + CPP=$ac_cv_prog_CPP +else + ac_cv_prog_CPP=$CPP +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $CPP" >&5 +$as_echo "$CPP" >&6; } +ac_preproc_ok=false +for ac_c_preproc_warn_flag in '' yes +do + # Use a header file that comes with gcc, so configuring glibc + # with a fresh cross-compiler works. + # Prefer to if __STDC__ is defined, since + # exists even on freestanding compilers. + # On the NeXT, cc -E runs the code through the compiler's parser, + # not just through cpp. "Syntax error" is here to catch this case. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#ifdef __STDC__ +# include +#else +# include +#endif + Syntax error +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : +else + # Broken: fails on valid input. +continue +fi +rm -f conftest.err conftest.i conftest.$ac_ext + # OK, works on sane cases. Now check whether nonexistent headers + # can be detected and how. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + # Broken: success on invalid input. +continue +else + # Passes both tests. +ac_preproc_ok=: +break +fi +rm -f conftest.err conftest.i conftest.$ac_ext +done +# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. +rm -f conftest.i conftest.err conftest.$ac_ext +if $ac_preproc_ok; then : +else + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "C preprocessor \"$CPP\" fails sanity check +See \`config.log' for more details" "$LINENO" 5; } +fi +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu -if test $ac_cv_header_stdlib_h = yes && test $ac_cv_header_string_h = yes -then : -printf "%s\n" "#define STDC_HEADERS 1" >>confdefs.h +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for grep that handles long lines and -e" >&5 +$as_echo_n "checking for grep that handles long lines and -e... " >&6; } +if ${ac_cv_path_GREP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -z "$GREP"; then + ac_path_GREP_found=false + # Loop through the user's path and test for each of PROGNAME-LIST + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_prog in grep ggrep; do + for ac_exec_ext in '' $ac_executable_extensions; do + ac_path_GREP="$as_dir/$ac_prog$ac_exec_ext" + as_fn_executable_p "$ac_path_GREP" || continue +# Check for GNU ac_path_GREP and select it if it is found. + # Check for GNU $ac_path_GREP +case `"$ac_path_GREP" --version 2>&1` in +*GNU*) + ac_cv_path_GREP="$ac_path_GREP" ac_path_GREP_found=:;; +*) + ac_count=0 + $as_echo_n 0123456789 >"conftest.in" + while : + do + cat "conftest.in" "conftest.in" >"conftest.tmp" + mv "conftest.tmp" "conftest.in" + cp "conftest.in" "conftest.nl" + $as_echo 'GREP' >> "conftest.nl" + "$ac_path_GREP" -e 'GREP$' -e '-(cannot match)-' < "conftest.nl" >"conftest.out" 2>/dev/null || break + diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break + as_fn_arith $ac_count + 1 && ac_count=$as_val + if test $ac_count -gt ${ac_path_GREP_max-0}; then + # Best one so far, save it but keep looking for a better one + ac_cv_path_GREP="$ac_path_GREP" + ac_path_GREP_max=$ac_count + fi + # 10*(2^10) chars as input seems more than enough + test $ac_count -gt 10 && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out;; +esac + $ac_path_GREP_found && break 3 + done + done + done +IFS=$as_save_IFS + if test -z "$ac_cv_path_GREP"; then + as_fn_error $? "no acceptable grep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 + fi +else + ac_cv_path_GREP=$GREP fi -ac_fn_c_check_header_compile "$LINENO" "stdio.h" "ac_cv_header_stdio_h" "$ac_includes_default" -if test "x$ac_cv_header_stdio_h" = xyes -then : - printf "%s\n" "#define HAVE_STDIO_H 1" >>confdefs.h fi -ac_fn_c_check_header_compile "$LINENO" "stdlib.h" "ac_cv_header_stdlib_h" "$ac_includes_default" -if test "x$ac_cv_header_stdlib_h" = xyes -then : - printf "%s\n" "#define HAVE_STDLIB_H 1" >>confdefs.h +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_GREP" >&5 +$as_echo "$ac_cv_path_GREP" >&6; } + GREP="$ac_cv_path_GREP" + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for egrep" >&5 +$as_echo_n "checking for egrep... " >&6; } +if ${ac_cv_path_EGREP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if echo a | $GREP -E '(a|b)' >/dev/null 2>&1 + then ac_cv_path_EGREP="$GREP -E" + else + if test -z "$EGREP"; then + ac_path_EGREP_found=false + # Loop through the user's path and test for each of PROGNAME-LIST + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_prog in egrep; do + for ac_exec_ext in '' $ac_executable_extensions; do + ac_path_EGREP="$as_dir/$ac_prog$ac_exec_ext" + as_fn_executable_p "$ac_path_EGREP" || continue +# Check for GNU ac_path_EGREP and select it if it is found. + # Check for GNU $ac_path_EGREP +case `"$ac_path_EGREP" --version 2>&1` in +*GNU*) + ac_cv_path_EGREP="$ac_path_EGREP" ac_path_EGREP_found=:;; +*) + ac_count=0 + $as_echo_n 0123456789 >"conftest.in" + while : + do + cat "conftest.in" "conftest.in" >"conftest.tmp" + mv "conftest.tmp" "conftest.in" + cp "conftest.in" "conftest.nl" + $as_echo 'EGREP' >> "conftest.nl" + "$ac_path_EGREP" 'EGREP$' < "conftest.nl" >"conftest.out" 2>/dev/null || break + diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break + as_fn_arith $ac_count + 1 && ac_count=$as_val + if test $ac_count -gt ${ac_path_EGREP_max-0}; then + # Best one so far, save it but keep looking for a better one + ac_cv_path_EGREP="$ac_path_EGREP" + ac_path_EGREP_max=$ac_count + fi + # 10*(2^10) chars as input seems more than enough + test $ac_count -gt 10 && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out;; +esac + $ac_path_EGREP_found && break 3 + done + done + done +IFS=$as_save_IFS + if test -z "$ac_cv_path_EGREP"; then + as_fn_error $? "no acceptable egrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 + fi +else + ac_cv_path_EGREP=$EGREP fi -ac_fn_c_check_header_compile "$LINENO" "string.h" "ac_cv_header_string_h" "$ac_includes_default" -if test "x$ac_cv_header_string_h" = xyes -then : - printf "%s\n" "#define HAVE_STRING_H 1" >>confdefs.h + fi fi -ac_fn_c_check_header_compile "$LINENO" "inttypes.h" "ac_cv_header_inttypes_h" "$ac_includes_default" -if test "x$ac_cv_header_inttypes_h" = xyes -then : - printf "%s\n" "#define HAVE_INTTYPES_H 1" >>confdefs.h +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_EGREP" >&5 +$as_echo "$ac_cv_path_EGREP" >&6; } + EGREP="$ac_cv_path_EGREP" + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ANSI C header files" >&5 +$as_echo_n "checking for ANSI C header files... " >&6; } +if ${ac_cv_header_stdc+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +#include +#include +#include +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_header_stdc=yes +else + ac_cv_header_stdc=no fi -ac_fn_c_check_header_compile "$LINENO" "stdint.h" "ac_cv_header_stdint_h" "$ac_includes_default" -if test "x$ac_cv_header_stdint_h" = xyes -then : - printf "%s\n" "#define HAVE_STDINT_H 1" >>confdefs.h +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + +if test $ac_cv_header_stdc = yes; then + # SunOS 4.x string.h does not declare mem*, contrary to ANSI. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "memchr" >/dev/null 2>&1; then : +else + ac_cv_header_stdc=no fi -ac_fn_c_check_header_compile "$LINENO" "strings.h" "ac_cv_header_strings_h" "$ac_includes_default" -if test "x$ac_cv_header_strings_h" = xyes -then : - printf "%s\n" "#define HAVE_STRINGS_H 1" >>confdefs.h +rm -f conftest* fi -ac_fn_c_check_header_compile "$LINENO" "sys/stat.h" "ac_cv_header_sys_stat_h" "$ac_includes_default" -if test "x$ac_cv_header_sys_stat_h" = xyes -then : - printf "%s\n" "#define HAVE_SYS_STAT_H 1" >>confdefs.h +if test $ac_cv_header_stdc = yes; then + # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "free" >/dev/null 2>&1; then : + +else + ac_cv_header_stdc=no fi -ac_fn_c_check_header_compile "$LINENO" "sys/types.h" "ac_cv_header_sys_types_h" "$ac_includes_default" -if test "x$ac_cv_header_sys_types_h" = xyes -then : - printf "%s\n" "#define HAVE_SYS_TYPES_H 1" >>confdefs.h +rm -f conftest* fi -ac_fn_c_check_header_compile "$LINENO" "unistd.h" "ac_cv_header_unistd_h" "$ac_includes_default" -if test "x$ac_cv_header_unistd_h" = xyes -then : - printf "%s\n" "#define HAVE_UNISTD_H 1" >>confdefs.h +if test $ac_cv_header_stdc = yes; then + # /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi. + if test "$cross_compiling" = yes; then : + : +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +#include +#if ((' ' & 0x0FF) == 0x020) +# define ISLOWER(c) ('a' <= (c) && (c) <= 'z') +# define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c)) +#else +# define ISLOWER(c) \ + (('a' <= (c) && (c) <= 'i') \ + || ('j' <= (c) && (c) <= 'r') \ + || ('s' <= (c) && (c) <= 'z')) +# define TOUPPER(c) (ISLOWER(c) ? ((c) | 0x40) : (c)) +#endif + +#define XOR(e, f) (((e) && !(f)) || (!(e) && (f))) +int +main () +{ + int i; + for (i = 0; i < 256; i++) + if (XOR (islower (i), ISLOWER (i)) + || toupper (i) != TOUPPER (i)) + return 2; + return 0; +} +_ACEOF +if ac_fn_c_try_run "$LINENO"; then : + +else + ac_cv_header_stdc=no +fi +rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ + conftest.$ac_objext conftest.beam conftest.$ac_ext fi -ac_fn_c_check_header_compile "$LINENO" "syslog.h" "ac_cv_header_syslog_h" "$ac_includes_default" -if test "x$ac_cv_header_syslog_h" = xyes -then : - printf "%s\n" "#define HAVE_SYSLOG_H 1" >>confdefs.h fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_header_stdc" >&5 +$as_echo "$ac_cv_header_stdc" >&6; } +if test $ac_cv_header_stdc = yes; then +$as_echo "#define STDC_HEADERS 1" >>confdefs.h -# Checks for typedefs, structures, and compiler characteristics. -ac_fn_c_check_type "$LINENO" "uid_t" "ac_cv_type_uid_t" "$ac_includes_default" -if test "x$ac_cv_type_uid_t" = xyes -then : +fi + +# On IRIX 5.3, sys/types and inttypes.h are conflicting. +for ac_header in sys/types.h sys/stat.h stdlib.h string.h memory.h strings.h \ + inttypes.h stdint.h unistd.h +do : + as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` +ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default +" +if eval test \"x\$"$as_ac_Header"\" = x"yes"; then : + cat >>confdefs.h <<_ACEOF +#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1 +_ACEOF -else case e in #( - e) -printf "%s\n" "#define uid_t int" >>confdefs.h - ;; -esac fi -ac_fn_c_check_type "$LINENO" "gid_t" "ac_cv_type_gid_t" "$ac_includes_default" -if test "x$ac_cv_type_gid_t" = xyes -then : +done + + +for ac_header in syslog.h +do : + ac_fn_c_check_header_mongrel "$LINENO" "syslog.h" "ac_cv_header_syslog_h" "$ac_includes_default" +if test "x$ac_cv_header_syslog_h" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_SYSLOG_H 1 +_ACEOF -else case e in #( - e) -printf "%s\n" "#define gid_t int" >>confdefs.h - ;; -esac fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking type of array argument to getgroups" >&5 -printf %s "checking type of array argument to getgroups... " >&6; } -if test ${ac_cv_type_getgroups+y} -then : - printf %s "(cached) " >&6 -else case e in #( - e) # If AC_TYPE_UID_T says there isn't any gid_t typedef, then we can skip -# everything below. -if test $ac_cv_type_gid_t = no -then : - ac_cv_type_getgroups=int -else case e in #( - e) # Test programs below rely on strict type checking of extern declarations: - # 'extern int getgroups(int, int *); extern int getgroups(int, pid_t *);' - # is valid in C89 if and only if pid_t is a typedef for int. Unlike - # anything involving either an assignment or a function call, compilers - # tend to make this kind of type mismatch a hard error, not just an - # "incompatible pointer types" warning. +done + + +# Checks for typedefs, structures, and compiler characteristics. +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for uid_t in sys/types.h" >&5 +$as_echo_n "checking for uid_t in sys/types.h... " >&6; } +if ${ac_cv_type_uid_t+:} false; then : + $as_echo_n "(cached) " >&6 +else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ -$ac_includes_default -extern int getgroups(int, gid_t *); -int -main (void) -{ -return !(getgroups(0, 0) >= 0); - ; - return 0; -} +#include + _ACEOF -if ac_fn_c_try_compile "$LINENO" -then : - ac_getgroups_gidarray=yes -else case e in #( - e) ac_getgroups_gidarray=no ;; -esac +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "uid_t" >/dev/null 2>&1; then : + ac_cv_type_uid_t=yes +else + ac_cv_type_uid_t=no +fi +rm -f conftest* + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_type_uid_t" >&5 +$as_echo "$ac_cv_type_uid_t" >&6; } +if test $ac_cv_type_uid_t = no; then + +$as_echo "#define uid_t int" >>confdefs.h + + +$as_echo "#define gid_t int" >>confdefs.h + fi -rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking type of array argument to getgroups" >&5 +$as_echo_n "checking type of array argument to getgroups... " >&6; } +if ${ac_cv_type_getgroups+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test "$cross_compiling" = yes; then : + ac_cv_type_getgroups=cross +else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ +/* Thanks to Mike Rendell for this test. */ $ac_includes_default -extern int getgroups(int, int *); +#define NGID 256 +#undef MAX +#define MAX(x, y) ((x) > (y) ? (x) : (y)) + int -main (void) +main () { -return !(getgroups(0, 0) >= 0); - ; - return 0; + gid_t gidset[NGID]; + int i, n; + union { gid_t gval; long int lval; } val; + + val.lval = -1; + for (i = 0; i < NGID; i++) + gidset[i] = val.gval; + n = getgroups (sizeof (gidset) / MAX (sizeof (int), sizeof (gid_t)) - 1, + gidset); + /* Exit non-zero if getgroups seems to require an array of ints. This + happens when gid_t is short int but getgroups modifies an array + of ints. */ + return n > 0 && gidset[n] != val.gval; } _ACEOF -if ac_fn_c_try_compile "$LINENO" -then : - ac_getgroups_intarray=yes -else case e in #( - e) ac_getgroups_intarray=no ;; -esac +if ac_fn_c_try_run "$LINENO"; then : + ac_cv_type_getgroups=gid_t +else + ac_cv_type_getgroups=int fi -rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext - - case int:$ac_getgroups_intarray,gid:$ac_getgroups_gidarray in #( - int:yes,gid:no) : - ac_cv_type_getgroups=int ;; #( - int:no,gid:yes) : - ac_cv_type_getgroups=gid_t ;; #( - int:yes,gid:yes) : - - # Both programs compiled - this means *either* that getgroups - # was declared with no prototype, in which case we should use int, - # or that it was declared prototyped but gid_t is a typedef for int, - # in which case we should use gid_t. Distinguish the two cases - # by testing if the compiler catches a blatantly incorrect function - # signature for getgroups. - cat confdefs.h - <<_ACEOF >conftest.$ac_ext +rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ + conftest.$ac_objext conftest.beam conftest.$ac_ext +fi + +if test $ac_cv_type_getgroups = cross; then + cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ -$ac_includes_default -extern int getgroups(int, float); -int -main (void) -{ -return !(getgroups(0, 0) >= 0); - ; - return 0; -} +#include + _ACEOF -if ac_fn_c_try_compile "$LINENO" -then : - - # Compiler did not catch incorrect argument list; - # getgroups is unprototyped. - ac_cv_type_getgroups=int - -else case e in #( - e) - # Compiler caught incorrect argument list; - # gid_t is a typedef for int. - ac_cv_type_getgroups=gid_t - ;; -esac +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "getgroups.*int.*gid_t" >/dev/null 2>&1; then : + ac_cv_type_getgroups=gid_t +else + ac_cv_type_getgroups=int fi -rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext - ;; #( - *) : +rm -f conftest* - # Both programs failed to compile - this probably means getgroups - # wasn't declared at all. Use 'int', as this is probably a very - # old system where the type _would have been_ int. - ac_cv_type_getgroups=int - ;; -esac - ;; -esac fi - ;; -esac fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_type_getgroups" >&5 -printf "%s\n" "$ac_cv_type_getgroups" >&6; } -printf "%s\n" "#define GETGROUPS_T $ac_cv_type_getgroups" >>confdefs.h +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_type_getgroups" >&5 +$as_echo "$ac_cv_type_getgroups" >&6; } + +cat >>confdefs.h <<_ACEOF +#define GETGROUPS_T $ac_cv_type_getgroups +_ACEOF # Checks for library functions. -ac_fn_c_check_func "$LINENO" "vsnprintf" "ac_cv_func_vsnprintf" -if test "x$ac_cv_func_vsnprintf" = xyes -then : - printf "%s\n" "#define HAVE_VSNPRINTF 1" >>confdefs.h +for ac_func in vsnprintf +do : + ac_fn_c_check_func "$LINENO" "vsnprintf" "ac_cv_func_vsnprintf" +if test "x$ac_cv_func_vsnprintf" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_VSNPRINTF 1 +_ACEOF fi +done @@ -4986,8 +4608,8 @@ cat >confcache <<\_ACEOF # config.status only pays attention to the cache file if you give it # the --recheck option to rerun configure. # -# 'ac_cv_env_foo' variables (set or unset) will be overridden when -# loading this file, other *unset* 'ac_cv_foo' will be assigned the +# `ac_cv_env_foo' variables (set or unset) will be overridden when +# loading this file, other *unset* `ac_cv_foo' will be assigned the # following values. _ACEOF @@ -5003,8 +4625,8 @@ _ACEOF case $ac_val in #( *${as_nl}*) case $ac_var in #( - *_cv_*) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 -printf "%s\n" "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; + *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 +$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; esac case $ac_var in #( _ | IFS | as_nl) ;; #( @@ -5017,14 +4639,14 @@ printf "%s\n" "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} (set) 2>&1 | case $as_nl`(ac_space=' '; set) 2>&1` in #( *${as_nl}ac_space=\ *) - # 'set' does not quote correctly, so add quotes: double-quote + # `set' does not quote correctly, so add quotes: double-quote # substitution turns \\\\ into \\, and sed turns \\ into \. sed -n \ "s/'/'\\\\''/g; s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p" ;; #( *) - # 'set' quotes correctly as required by POSIX, so do not add quotes. + # `set' quotes correctly as required by POSIX, so do not add quotes. sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" ;; esac | @@ -5034,15 +4656,15 @@ printf "%s\n" "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} /^ac_cv_env_/b end t clear :clear - s/^\([^=]*\)=\(.*[{}].*\)$/test ${\1+y} || &/ + s/^\([^=]*\)=\(.*[{}].*\)$/test "${\1+set}" = set || &/ t end s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/ :end' >>confcache if diff "$cache_file" confcache >/dev/null 2>&1; then :; else if test -w "$cache_file"; then if test "x$cache_file" != "x/dev/null"; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5 -printf "%s\n" "$as_me: updating cache $cache_file" >&6;} + { $as_echo "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5 +$as_echo "$as_me: updating cache $cache_file" >&6;} if test ! -f "$cache_file" || test -h "$cache_file"; then cat confcache >"$cache_file" else @@ -5056,8 +4678,8 @@ printf "%s\n" "$as_me: updating cache $cache_file" >&6;} fi fi else - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5 -printf "%s\n" "$as_me: not updating unwritable cache $cache_file" >&6;} + { $as_echo "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5 +$as_echo "$as_me: not updating unwritable cache $cache_file" >&6;} fi fi rm -f confcache @@ -5088,7 +4710,9 @@ s/^[ ]*#[ ]*define[ ][ ]*\([^ ][^ ]*\)[ ]*\(.*\)/-D\1=\2/g t quote b any :quote -s/[][ `~#$^&*(){}\\|;'\''"<>?]/\\&/g +s/[ `~#$^&*(){}\\|;'\''"<>?]/\\&/g +s/\[/\\&/g +s/\]/\\&/g s/\$/$$/g H :any @@ -5108,7 +4732,7 @@ U= for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue # 1. Remove the extension, and $U if already installed. ac_script='s/\$U\././;s/\.o$//;s/\.obj$//' - ac_i=`printf "%s\n" "$ac_i" | sed "$ac_script"` + ac_i=`$as_echo "$ac_i" | sed "$ac_script"` # 2. Prepend LIBOBJDIR. When used with automake>=1.10 LIBOBJDIR # will be set to the directory where LIBOBJS objects are built. as_fn_append ac_libobjs " \${LIBOBJDIR}$ac_i\$U.$ac_objext" @@ -5119,17 +4743,13 @@ LIBOBJS=$ac_libobjs LTLIBOBJS=$ac_ltlibobjs -if test -z "${HAVE_NNTP_TRUE}" && test -z "${HAVE_NNTP_FALSE}"; then - as_fn_error $? "conditional \"HAVE_NNTP\" was never defined. -Usually this means the macro was only invoked conditionally." "$LINENO" 5 -fi : "${CONFIG_STATUS=./config.status}" ac_write_fail=0 ac_clean_files_save=$ac_clean_files ac_clean_files="$ac_clean_files $CONFIG_STATUS" -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5 -printf "%s\n" "$as_me: creating $CONFIG_STATUS" >&6;} +{ $as_echo "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5 +$as_echo "$as_me: creating $CONFIG_STATUS" >&6;} as_write_fail=0 cat >$CONFIG_STATUS <<_ASEOF || as_write_fail=1 #! $SHELL @@ -5152,65 +4772,63 @@ cat >>$CONFIG_STATUS <<\_ASEOF || as_write_fail=1 # Be more Bourne compatible DUALCASE=1; export DUALCASE # for MKS sh -if test ${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 -then : +if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which # is contrary to our usage. Disable this feature. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST -else case e in #( - e) case `(set -o) 2>/dev/null` in #( +else + case `(set -o) 2>/dev/null` in #( *posix*) : set -o posix ;; #( *) : ;; -esac ;; esac fi - -# Reset variables that may have inherited troublesome values from -# the environment. - -# IFS needs to be set, to space, tab, and newline, in precisely that order. -# (If _AS_PATH_WALK were called with IFS unset, it would have the -# side effect of setting IFS to empty, thus disabling word splitting.) -# Quoting is to prevent editors from complaining about space-tab. as_nl=' ' export as_nl -IFS=" "" $as_nl" - -PS1='$ ' -PS2='> ' -PS4='+ ' - -# Ensure predictable behavior from utilities with locale-dependent output. -LC_ALL=C -export LC_ALL -LANGUAGE=C -export LANGUAGE - -# We cannot yet rely on "unset" to work, but we need these variables -# to be unset--not just set to an empty or harmless value--now, to -# avoid bugs in old shells (e.g. pre-3.0 UWIN ksh). This construct -# also avoids known problems related to "unset" and subshell syntax -# in other old shells (e.g. bash 2.01 and pdksh 5.2.14). -for as_var in BASH_ENV ENV MAIL MAILPATH CDPATH -do eval test \${$as_var+y} \ - && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : -done - -# Ensure that fds 0, 1, and 2 are open. -if (exec 3>&0) 2>/dev/null; then :; else exec 0&1) 2>/dev/null; then :; else exec 1>/dev/null; fi -if (exec 3>&2) ; then :; else exec 2>/dev/null; fi +# Printing a long string crashes Solaris 7 /usr/bin/printf. +as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo +# Prefer a ksh shell builtin over an external printf program on Solaris, +# but without wasting forks for bash or zsh. +if test -z "$BASH_VERSION$ZSH_VERSION" \ + && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='print -r --' + as_echo_n='print -rn --' +elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='printf %s\n' + as_echo_n='printf %s' +else + if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then + as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' + as_echo_n='/usr/ucb/echo -n' + else + as_echo_body='eval expr "X$1" : "X\\(.*\\)"' + as_echo_n_body='eval + arg=$1; + case $arg in #( + *"$as_nl"*) + expr "X$arg" : "X\\(.*\\)$as_nl"; + arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; + esac; + expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" + ' + export as_echo_n_body + as_echo_n='sh -c $as_echo_n_body as_echo' + fi + export as_echo_body + as_echo='sh -c $as_echo_body as_echo' +fi # The user is always right. -if ${PATH_SEPARATOR+false} :; then +if test "${PATH_SEPARATOR+set}" != set; then PATH_SEPARATOR=: (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || @@ -5219,6 +4837,13 @@ if ${PATH_SEPARATOR+false} :; then fi +# IFS +# We need space, tab and new line, in precisely that order. Quoting is +# there to prevent editors from complaining about space-tab. +# (If _AS_PATH_WALK were called with IFS unset, it would disable word +# splitting by setting IFS to empty value.) +IFS=" "" $as_nl" + # Find who we are. Look in the path if we contain no directory separator. as_myself= case $0 in #(( @@ -5227,27 +4852,43 @@ case $0 in #(( for as_dir in $PATH do IFS=$as_save_IFS - case $as_dir in #((( - '') as_dir=./ ;; - */) ;; - *) as_dir=$as_dir/ ;; - esac - test -r "$as_dir$0" && as_myself=$as_dir$0 && break + test -z "$as_dir" && as_dir=. + test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break done IFS=$as_save_IFS ;; esac -# We did not find ourselves, most probably we were run as 'sh COMMAND' +# We did not find ourselves, most probably we were run as `sh COMMAND' # in which case we are not to be found in the path. if test "x$as_myself" = x; then as_myself=$0 fi if test ! -f "$as_myself"; then - printf "%s\n" "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 + $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 exit 1 fi +# Unset variables that we do not need and which cause bugs (e.g. in +# pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" +# suppresses any "Segmentation fault" message there. '((' could +# trigger a bug in pdksh 5.2.14. +for as_var in BASH_ENV ENV MAIL MAILPATH +do eval test x\${$as_var+set} = xset \ + && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : +done +PS1='$ ' +PS2='> ' +PS4='+ ' + +# NLS nuisances. +LC_ALL=C +export LC_ALL +LANGUAGE=C +export LANGUAGE + +# CDPATH. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH # as_fn_error STATUS ERROR [LINENO LOG_FD] @@ -5260,9 +4901,9 @@ as_fn_error () as_status=$1; test $as_status -eq 0 && as_status=1 if test "$4"; then as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack - printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 + $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 fi - printf "%s\n" "$as_me: error: $2" >&2 + $as_echo "$as_me: error: $2" >&2 as_fn_exit $as_status } # as_fn_error @@ -5293,25 +4934,22 @@ as_fn_unset () { eval $1=; unset $1;} } as_unset=as_fn_unset - # as_fn_append VAR VALUE # ---------------------- # Append the text in VALUE to the end of the definition contained in VAR. Take # advantage of any shell optimizations that allow amortized linear growth over # repeated appends, instead of the typical quadratic growth present in naive # implementations. -if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null -then : +if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : eval 'as_fn_append () { eval $1+=\$2 }' -else case e in #( - e) as_fn_append () +else + as_fn_append () { eval $1=\$$1\$2 - } ;; -esac + } fi # as_fn_append # as_fn_arith ARG... @@ -5319,18 +4957,16 @@ fi # as_fn_append # Perform arithmetic evaluation on the ARGs, and store the result in the # global $as_val. Take advantage of shells that can avoid forks. The arguments # must be portable across $(()) and expr. -if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null -then : +if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : eval 'as_fn_arith () { as_val=$(( $* )) }' -else case e in #( - e) as_fn_arith () +else + as_fn_arith () { as_val=`expr "$@" || test $? -eq 1` - } ;; -esac + } fi # as_fn_arith @@ -5357,7 +4993,7 @@ as_me=`$as_basename -- "$0" || $as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ X"$0" : 'X\(//\)$' \| \ X"$0" : 'X\(/\)' \| . 2>/dev/null || -printf "%s\n" X/"$0" | +$as_echo X/"$0" | sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/ q @@ -5379,10 +5015,6 @@ as_cr_Letters=$as_cr_letters$as_cr_LETTERS as_cr_digits='0123456789' as_cr_alnum=$as_cr_Letters$as_cr_digits - -# Determine whether it's possible to make 'echo' print without a newline. -# These variables are no longer used directly by Autoconf, but are AC_SUBSTed -# for compatibility with existing Makefiles. ECHO_C= ECHO_N= ECHO_T= case `echo -n x` in #((((( -n*) @@ -5396,12 +5028,6 @@ case `echo -n x` in #((((( ECHO_N='-n';; esac -# For backward compatibility with old third-party macros, we provide -# the shell variables $as_echo and $as_echo_n. New code should use -# AS_ECHO(["message"]) and AS_ECHO_N(["message"]), respectively. -as_echo='printf %s\n' -as_echo_n='printf %s' - rm -f conf$$ conf$$.exe conf$$.file if test -d conf$$.dir; then rm -f conf$$.dir/conf$$.file @@ -5413,9 +5039,9 @@ if (echo >conf$$.file) 2>/dev/null; then if ln -s conf$$.file conf$$ 2>/dev/null; then as_ln_s='ln -s' # ... but there are two gotchas: - # 1) On MSYS, both 'ln -s file dir' and 'ln file dir' fail. - # 2) DJGPP < 2.04 has no symlinks; 'ln -s' creates a wrapper executable. - # In both cases, we have to default to 'cp -pR'. + # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. + # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. + # In both cases, we have to default to `cp -pR'. ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || as_ln_s='cp -pR' elif ln conf$$.file conf$$ 2>/dev/null; then @@ -5443,7 +5069,7 @@ as_fn_mkdir_p () as_dirs= while :; do case $as_dir in #( - *\'*) as_qdir=`printf "%s\n" "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( + *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( *) as_qdir=$as_dir;; esac as_dirs="'$as_qdir' $as_dirs" @@ -5452,7 +5078,7 @@ $as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_dir" : 'X\(//\)[^/]' \| \ X"$as_dir" : 'X\(//\)$' \| \ X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || -printf "%s\n" X"$as_dir" | +$as_echo X"$as_dir" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q @@ -5496,12 +5122,10 @@ as_test_x='test -x' as_executable_p=as_fn_executable_p # Sed expression to map a string onto a valid CPP name. -as_sed_cpp="y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g" -as_tr_cpp="eval sed '$as_sed_cpp'" # deprecated +as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" # Sed expression to map a string onto a valid variable name. -as_sed_sh="y%*+%pp%;s%[^_$as_cr_alnum]%_%g" -as_tr_sh="eval sed '$as_sed_sh'" # deprecated +as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" exec 6>&1 @@ -5517,7 +5141,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # values after options handling. ac_log=" This file was extended by $as_me, which was -generated by GNU Autoconf 2.72. Invocation command line was +generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES CONFIG_HEADERS = $CONFIG_HEADERS @@ -5545,7 +5169,7 @@ _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 ac_cs_usage="\ -'$as_me' instantiates files and other configuration actions +\`$as_me' instantiates files and other configuration actions from templates according to the current configuration. Unless the files and actions are specified as TAGs, all are instantiated by default. @@ -5570,16 +5194,14 @@ $config_commands Report bugs to the package provider." _ACEOF -ac_cs_config=`printf "%s\n" "$ac_configure_args" | sed "$ac_safe_unquote"` -ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\''/g"` cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 -ac_cs_config='$ac_cs_config_escaped' +ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ config.status -configured by $0, generated by GNU Autoconf 2.72, +configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" -Copyright (C) 2023 Free Software Foundation, Inc. +Copyright (C) 2012 Free Software Foundation, Inc. This config.status script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it." @@ -5617,28 +5239,28 @@ do -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r) ac_cs_recheck=: ;; --version | --versio | --versi | --vers | --ver | --ve | --v | -V ) - printf "%s\n" "$ac_cs_version"; exit ;; + $as_echo "$ac_cs_version"; exit ;; --config | --confi | --conf | --con | --co | --c ) - printf "%s\n" "$ac_cs_config"; exit ;; + $as_echo "$ac_cs_config"; exit ;; --debug | --debu | --deb | --de | --d | -d ) debug=: ;; --file | --fil | --fi | --f ) $ac_shift case $ac_optarg in - *\'*) ac_optarg=`printf "%s\n" "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; + *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; '') as_fn_error $? "missing file argument" ;; esac as_fn_append CONFIG_FILES " '$ac_optarg'" ac_need_defaults=false;; --he | --h | --help | --hel | -h ) - printf "%s\n" "$ac_cs_usage"; exit ;; + $as_echo "$ac_cs_usage"; exit ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil | --si | --s) ac_cs_silent=: ;; # This is an error. - -*) as_fn_error $? "unrecognized option: '$1' -Try '$0 --help' for more information." ;; + -*) as_fn_error $? "unrecognized option: \`$1' +Try \`$0 --help' for more information." ;; *) as_fn_append ac_config_targets " $1" ac_need_defaults=false ;; @@ -5659,7 +5281,7 @@ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 if \$ac_cs_recheck; then set X $SHELL '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion shift - \printf "%s\n" "running CONFIG_SHELL=$SHELL \$*" >&6 + \$as_echo "running CONFIG_SHELL=$SHELL \$*" >&6 CONFIG_SHELL='$SHELL' export CONFIG_SHELL exec "\$@" @@ -5673,7 +5295,7 @@ exec 5>>config.log sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX ## Running $as_me. ## _ASBOX - printf "%s\n" "$ac_log" + $as_echo "$ac_log" } >&5 _ACEOF @@ -5715,7 +5337,7 @@ do "$SCRIPTS") CONFIG_FILES="$CONFIG_FILES $SCRIPTS" ;; "default") CONFIG_COMMANDS="$CONFIG_COMMANDS default" ;; - *) as_fn_error $? "invalid argument: '$ac_config_target'" "$LINENO" 5;; + *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;; esac done @@ -5725,8 +5347,8 @@ done # We use the long form for the default assignment because of an extremely # bizarre bug on SunOS 4.1.3. if $ac_need_defaults; then - test ${CONFIG_FILES+y} || CONFIG_FILES=$config_files - test ${CONFIG_COMMANDS+y} || CONFIG_COMMANDS=$config_commands + test "${CONFIG_FILES+set}" = set || CONFIG_FILES=$config_files + test "${CONFIG_COMMANDS+set}" = set || CONFIG_COMMANDS=$config_commands fi # Have a temporary directory for convenience. Make it in the build tree @@ -5734,7 +5356,7 @@ fi # creating and moving files from /tmp can sometimes cause problems. # Hook for its removal unless debugging. # Note that there is a small window in which the directory will not be cleaned: -# after its creation but before its name has been assigned to '$tmp'. +# after its creation but before its name has been assigned to `$tmp'. $debug || { tmp= ac_tmp= @@ -5758,7 +5380,7 @@ ac_tmp=$tmp # Set up the scripts for CONFIG_FILES section. # No need to generate them if there are no CONFIG_FILES. -# This happens for instance with './config.status config.h'. +# This happens for instance with `./config.status config.h'. if test -n "$CONFIG_FILES"; then @@ -5924,7 +5546,7 @@ do esac case $ac_mode$ac_tag in :[FHL]*:*);; - :L* | :C*:*) as_fn_error $? "invalid tag '$ac_tag'" "$LINENO" 5;; + :L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5;; :[FH]-) ac_tag=-:-;; :[FH]*) ac_tag=$ac_tag:$ac_tag.in;; esac @@ -5946,33 +5568,33 @@ do -) ac_f="$ac_tmp/stdin";; *) # Look for the file first in the build tree, then in the source tree # (if the path is not absolute). The absolute path cannot be DOS-style, - # because $ac_f cannot contain ':'. + # because $ac_f cannot contain `:'. test -f "$ac_f" || case $ac_f in [\\/$]*) false;; *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";; esac || - as_fn_error 1 "cannot find input file: '$ac_f'" "$LINENO" 5;; + as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5;; esac - case $ac_f in *\'*) ac_f=`printf "%s\n" "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac + case $ac_f in *\'*) ac_f=`$as_echo "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac as_fn_append ac_file_inputs " '$ac_f'" done - # Let's still pretend it is 'configure' which instantiates (i.e., don't + # Let's still pretend it is `configure' which instantiates (i.e., don't # use $as_me), people would be surprised to read: # /* config.h. Generated by config.status. */ configure_input='Generated from '` - printf "%s\n" "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g' + $as_echo "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g' `' by configure.' if test x"$ac_file" != x-; then configure_input="$ac_file. $configure_input" - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5 -printf "%s\n" "$as_me: creating $ac_file" >&6;} + { $as_echo "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5 +$as_echo "$as_me: creating $ac_file" >&6;} fi # Neutralize special characters interpreted by sed in replacement strings. case $configure_input in #( *\&* | *\|* | *\\* ) - ac_sed_conf_input=`printf "%s\n" "$configure_input" | + ac_sed_conf_input=`$as_echo "$configure_input" | sed 's/[\\\\&|]/\\\\&/g'`;; #( *) ac_sed_conf_input=$configure_input;; esac @@ -5989,7 +5611,7 @@ $as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$ac_file" : 'X\(//\)[^/]' \| \ X"$ac_file" : 'X\(//\)$' \| \ X"$ac_file" : 'X\(/\)' \| . 2>/dev/null || -printf "%s\n" X"$ac_file" | +$as_echo X"$ac_file" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q @@ -6013,9 +5635,9 @@ printf "%s\n" X"$ac_file" | case "$ac_dir" in .) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; *) - ac_dir_suffix=/`printf "%s\n" "$ac_dir" | sed 's|^\.[\\/]||'` + ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` # A ".." for each directory in $ac_dir_suffix. - ac_top_builddir_sub=`printf "%s\n" "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` + ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` case $ac_top_builddir_sub in "") ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; @@ -6072,8 +5694,8 @@ ac_sed_dataroot=' case `eval "sed -n \"\$ac_sed_dataroot\" $ac_file_inputs"` in *datarootdir*) ac_datarootdir_seen=yes;; *@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*) - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5 -printf "%s\n" "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5 +$as_echo "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;} _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_datarootdir_hack=' @@ -6086,7 +5708,7 @@ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 esac _ACEOF -# Neutralize VPATH when '$srcdir' = '.'. +# Neutralize VPATH when `$srcdir' = `.'. # Shell code in configure.ac might set extrasub. # FIXME: do we really want to maintain this feature? cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 @@ -6116,9 +5738,9 @@ test -z "$ac_datarootdir_hack$ac_datarootdir_seen" && { ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } && { ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' \ "$ac_tmp/out"`; test -z "$ac_out"; } && - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable 'datarootdir' + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir' which seems to be undefined. Please make sure it is defined" >&5 -printf "%s\n" "$as_me: WARNING: $ac_file contains a reference to the variable 'datarootdir' +$as_echo "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir' which seems to be undefined. Please make sure it is defined" >&2;} rm -f "$ac_tmp/stdin" @@ -6130,8 +5752,8 @@ which seems to be undefined. Please make sure it is defined" >&2;} ;; - :C) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: executing $ac_file commands" >&5 -printf "%s\n" "$as_me: executing $ac_file commands" >&6;} + :C) { $as_echo "$as_me:${as_lineno-$LINENO}: executing $ac_file commands" >&5 +$as_echo "$as_me: executing $ac_file commands" >&6;} ;; esac @@ -6172,8 +5794,8 @@ if test "$no_create" != yes; then $ac_cs_success || as_fn_exit 1 fi if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5 -printf "%s\n" "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5 +$as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;} fi @@ -6182,4 +5804,3 @@ chmod -R +x build # Test for the Chinese codecs. - diff --git a/configure.ac b/configure.ac index b76c5a2d..80c303d8 100644 --- a/configure.ac +++ b/configure.ac @@ -16,7 +16,7 @@ dnl Process this file with autoconf to produce a configure script. AC_REVISION($Revision: 8122 $) -AC_PREREQ([2.71]) +AC_PREREQ([2.69]) AC_INIT AC_CONFIG_SRCDIR([src/common.h]) @@ -687,7 +687,12 @@ if test $ac_cv_func_syslog = no; then fi # Checks for header files. -AC_CHECK_HEADERS([stdio.h stdlib.h string.h inttypes.h stdint.h strings.h sys/stat.h sys/types.h unistd.h syslog.h]) +m4_warn([obsolete], +[The preprocessor macro `STDC_HEADERS' is obsolete. + Except in unusual embedded environments, you can safely include all + ISO C90 headers unconditionally.])dnl + +AC_CHECK_HEADERS(syslog.h) # Checks for typedefs, structures, and compiler characteristics. AC_TYPE_UID_T @@ -781,7 +786,7 @@ AC_CONFIG_FILES([misc/paths.py Mailman/Defaults.py Mailman/mm_cfg.py.dist Mailman/Queue/Makefile Mailman/MTA/Makefile Mailman/Gui/Makefile templates/Makefile cron/Makefile scripts/Makefile messages/Makefile cron/crontab.in misc/mailman Makefile - tests/Makefile tests/bounces/Makefile tests/msgs/Makefile + tests/Makefile tests/bounces/Makefile tests/msgs/Makefile Mailman/__init__.py $SCRIPTS]) AC_CONFIG_COMMANDS([default],[echo "configuration completed at" `date`],[]) AC_OUTPUT diff --git a/contrib/check_perms_grsecurity.py b/contrib/check_perms_grsecurity.py index 19dd2af4..b657de05 100644 --- a/contrib/check_perms_grsecurity.py +++ b/contrib/check_perms_grsecurity.py @@ -157,9 +157,9 @@ class CheckFixUid: except ValueError: file.insert(file.index("import paths\n")+1, "import CheckFixUid\n") for i in range(len(file)-1, 0, -1): - object=re.compile(r"^([ ]*)main\(").search(file[i]) + object=re.compile("^([ ]*)main\(").search(file[i]) # Special hack to support patching of update - object2=re.compile(r"^([ ]*).*=[ ]*main\(").search(file[i]) + object2=re.compile("^([ ]*).*=[ ]*main\(").search(file[i]) if object: print("Patching " + script) file.insert(i, diff --git a/contrib/courier-to-mailman.py b/contrib/courier-to-mailman.py index 95900878..f5161db7 100644 --- a/contrib/courier-to-mailman.py +++ b/contrib/courier-to-mailman.py @@ -54,7 +54,7 @@ # Note: "preline" is a Courier program which ensures a Unix "From " header # is on the message. Archiving will break without this. -import sys, os, re +import sys, os, re, string def main(): os.nice(5) # Handle mailing lists at non-interactive priority. @@ -62,7 +62,7 @@ def main(): os.chdir(MailmanVar + "/lists") try: - local = str.lower(os.environ["LOCAL"]) + local = string.lower(os.environ["LOCAL"]) except: # This might happen if we're not using qmail. sys.stderr.write("LOCAL not set in environment?\n") @@ -77,12 +77,12 @@ def main(): sys.exit(0) type = "post" - listname = str.lower(local) + listname = string.lower(local) types = (("-admin$", "admin"), ("-bounces$", "bounces"), - (r"-bounces\+.*$", "bounces"), # for VERP + ("-bounces\+.*$", "bounces"), # for VERP ("-confirm$", "confirm"), - (r"-confirm\+.*$", "confirm"), + ("-confirm\+.*$", "confirm"), ("-join$", "join"), ("-leave$", "leave"), ("-owner$", "owner"), diff --git a/contrib/import_majordomo_into_mailman.pl b/contrib/import_majordomo_into_mailman.pl index ec2aa2f7..9df75f2f 100644 --- a/contrib/import_majordomo_into_mailman.pl +++ b/contrib/import_majordomo_into_mailman.pl @@ -38,62 +38,43 @@ use strict; use warnings; -use feature 'say'; -use Getopt::Long qw(:config no_ignore_case bundling); + +use Getopt::Long; use Log::Handler; use File::Temp qw(tempfile); use Email::Simple; use Email::Sender::Simple qw(try_to_sendmail); use Data::Dump qw(dump); -use Pod::Usage; + #----------------------- ENVIRONMENT-SPECIFIC VALUES --------------------------# -my %config = ( - DOMO_PATH => '/opt/majordomo', - DOMO_LIST_DIR => '/opt/majordomo/lists', - MM_PATH => '/usr/local/mailman', - DOMO_ALIASES => '/usr/local/mailman/majordomo/aliases', - DOMO_CHECK_CONSISTENCY => '/usr/local/mailman/majordomo/check_consistency.txt', - BOUNCED_OWNERS => '/opt/mailman-2.1.14-1/uo/majordomo/email_addresses_that_bounced.txt', - TMP_DIR => '/tmp', - DOMO_INACTIVITY_LIMIT => 548, # Optional. 548 days = 18 months. - NEW_HOSTNAME => '', # Optional - LANGUAGE => 'en', # Preferred language for all Mailman lists - MAX_MSG_SIZE => 20000, # In KB. Used for the Mailman config. -); - -# Command line options -my %opts = ( - help => 0, - stats => 0, - subscribers => 0, - email_notify => 0, - email_test => 0, -); - -# Parse command line arguments -GetOptions( - 'help|h' => \$opts{help}, - 'stats|s' => \$opts{stats}, - 'subscribers|S' => \$opts{subscribers}, - 'email-notify|e' => \$opts{email_notify}, - 'email-test|t' => \$opts{email_test}, -) or pod2usage(2); - -# Show help if requested -pod2usage(1) if $opts{help}; +my $DOMO_PATH = '/opt/majordomo'; +my $DOMO_LIST_DIR = "$DOMO_PATH/lists"; +my $MM_PATH = '/usr/local/mailman'; +my $DOMO_ALIASES = "$MM_PATH/majordomo/aliases"; +my $DOMO_CHECK_CONSISTENCY = "$MM_PATH/majordomo/check_consistency.txt"; +my $BOUNCED_OWNERS = "/opt/mailman-2.1.14-1/uo/majordomo/" . + "email_addresses_that_bounced.txt"; +my $TMP_DIR = '/tmp'; +# Only import lists that have been active in the last N days. +my $DOMO_INACTIVITY_LIMIT = 548; # Optional. 548 days = 18 months. +# If set, overwrite Majordomo's "resend_host" and thus Mailman's "host_name". +my $NEW_HOSTNAME = ''; # Optional +my $LANGUAGE = 'en'; # Preferred language for all Mailman lists +my $MAX_MSG_SIZE = 20000; # In KB. Used for the Mailman config. +#------------------------------------------------------------------------------# # # Global constants # -my $MM_LIST_DIR = "$config{MM_PATH}/lists"; -my $MM_LIST_LISTS = "$config{MM_PATH}/bin/list_lists"; -my $MM_NEWLIST = "$config{MM_PATH}/bin/newlist"; -my $MM_CONFIGLIST = "$config{MM_PATH}/bin/config_list"; -my $MM_ADDMEMBERS = "$config{MM_PATH}/bin/add_members"; -my $MM_CHECK_PERMS = "$config{MM_PATH}/bin/check_perms"; +my $MM_LIST_DIR = "$MM_PATH/lists"; +my $MM_LIST_LISTS = "$MM_PATH/bin/list_lists"; +my $MM_NEWLIST = "$MM_PATH/bin/newlist"; +my $MM_CONFIGLIST = "$MM_PATH/bin/config_list"; +my $MM_ADDMEMBERS = "$MM_PATH/bin/add_members"; +my $MM_CHECK_PERMS = "$MM_PATH/bin/check_perms"; my $SCRIPT_NAME = $0 =~ /\/?(\b\w+\b)\.pl$/ ? $1 : ' - - -
    + التحقق من الشخصية لـ %(who)s للقائمة %(listname)s + + + + + %(message)s -
    - å–æ¶ˆè®¢é˜… - 您在 上的其他订阅 -
    + + + + - + +
    +

    +å–æ¶ˆè®¢é˜… +您在 上的其他订阅 +
    选中确认的å¤é€‰æ¡†ï¼Œç‚¹å‡»è¿™ä¸ªæŒ‰é’®å¯ä»¥ä»Žè¿™ä¸ªé‚®ä»¶åˆ—è¡¨å–æ¶ˆè®¢é˜…。 警告:这个动作会马上执行ï¼

    -

    +

    您å¯ä»¥æŸ¥ 上您已订阅的列表清å•. 如果您想对这些 其它的邮件列表åšç›¸åŒçš„æˆå‘˜ä¿¡æ¯æ“作,å¯ä»¥ä½¿ç”¨è¿™ä¸ªã€‚

    -

    -
    - - - - -
    - 您的 å£ä»¤ -
    - -
    -

    忘记了å£ä»¤?

    -
    + + + - - + + +
    +您的 å£ä»¤ +
    + +
    +

    忘记了å£ä»¤?

    +
    点击这个按钮,您å¯ä»¥é€šè¿‡æ‚¨çš„æˆå‘˜ç”µå­é‚®ç®±æ¥æŽ¥æ”¶æ‚¨çš„å£ä»¤ã€‚ -

    -

    - -
    -
    - -
    -

    修改您的å£ä»¤

    - - - - - - +
    æ–° - å£ä»¤:
    +

    +

    + +
    +

    + +
    +

    修改您的å£ä»¤

    + + + + + + - - -
    æ–° + å£ä»¤:
    确认å£ä»¤: -
    - - -

    全局修改 -
    -
    - +
    + +

    全局修改 +
    +

    - - +
    - 您的 订阅选项 -
    +
    +您的 订阅选项 +
    -

    检查现有值 -

    注æ„,有一些选项有一个设为全局 çš„å¤é€‰æ¡†ã€‚选中该框会使该选项在您在上订阅的所有邮件列表上生效。 点击上é¢çš„列出我的所有订阅 æ¥æŸ¥çœ‹æ‚¨è®¢é˜…了哪些其他的邮件列表。

    - -
    - - 信件投递

    + + - +

    - - - + +

    - - - - - - + + - - + - - - - + + - - + - - + - - - +

    +
    + +信件投递

    将该项设为å¯ç”¨æ¥æŽ¥æ”¶å‘é€åˆ°è¯¥é‚®ä»¶åˆ—表的信æ¯ï¼Œå¦‚果您想 ç»§ç»­è®¢é˜…ï¼Œä½†æš‚æ—¶ä¸æƒ³æŽ¥æ”¶é‚®ä»¶ï¼ˆä¾‹å¦‚,您正在休å‡ï¼‰ï¼Œå°†å…¶è®¾ä¸º ç¦æ­¢ã€‚å¦‚æžœæ‚¨ç¦æ­¢äº†é‚®ä»¶æŠ•递,请别忘了在您回æ¥çš„æ—¶å€™å°†å®ƒæ¿€æ´»ã€‚ 它ä¸ä¼šè‡ªåŠ¨æ¿€æ´»ã€‚ -

    - å…许
    - ç¦æ­¢

    - 设为全局 -

    +å…许
    +ç¦æ­¢

    +设为全局 +

    - è®¾ç½®æ‘˜è¦æ¨¡å¼

    +

    +è®¾ç½®æ‘˜è¦æ¨¡å¼

    å¦‚æžœæ‚¨å°†æ‘˜è¦æ¨¡å¼æ‰“开, 将会把所有信件èšåˆåœ¨ä¸€èµ·å‘é€ç»™æ‚¨ï¼Œ (通常æ¯å¤©ä¸€å°ï¼Œç¹å¿™çš„列表å¯èƒ½æ›´å¤šï¼‰ï¼Œè€Œä¸æ˜¯ä»–们å‘逿—¶çš„一次一å°ã€‚ å¦‚æžœæ‘˜è¦æ¨¡å¼ä»Žå¼€å˜ä¸ºå…³ï¼Œæ‚¨å¯èƒ½è¿˜ä¼šæ”¶åˆ°æœ€åŽä¸€ä»½æ‘˜è¦ã€‚ -

    - å…³
    - å¼€ -
    - 获得MIME还是普通文本摘è¦

    +

    +å…³
    +å¼€ +
    +获得MIME还是普通文本摘è¦

    您的电å­é‚®ä»¶é˜…读器å¯èƒ½æ”¯æŒä¹Ÿå¯èƒ½ä¸æ”¯æŒMIME(多用途互è”网邮件扩 展)。 总的æ¥è¯´ï¼Œæœ€å¥½æ˜¯MIME摘è¦å½¢å¼ï¼Œä½†æ˜¯å¦‚果您ä¸èƒ½é˜…读它们,选 择普通文本摘è¦ã€‚ -

    - MIME
    - 普通文本

    - 设为全局 -

    +MIME
    +普通文本

    +设为全局 +

    - 接收您自己的信件?

    +

    +接收您自己的信件?

    一般æ¥è¯´ï¼Œæ‚¨ä¼šæ”¶åˆ°æ¯å°ä½ å¯„å¾€åˆ—è¡¨çš„ä¿¡ä»¶å‰¯æœ¬ã€‚å¦‚æžœæ‚¨ä¸æƒ³æ”¶åˆ°è¿™ 个副本,将该项设为å¦. -

    - å¦
    - 是 -
    - 接收您å‘到列表信件的确认信æ¯ï¼Ÿ

    -

    - å¦
    - 是 -
    - 获得列表的å£ä»¤æç¤ºå‡½

    +

    +å¦
    +是 +
    +接收您å‘到列表信件的确认信æ¯ï¼Ÿ

    +

    +å¦
    +是 +
    +获得列表的å£ä»¤æç¤ºå‡½

    æ¯ä¸ªæœˆï¼Œæ‚¨éƒ½ä¼šä»Žè®¢é˜…了邮件列表的主机那里收到一å°å£ä»¤æç¤ºå‡½ã€‚ 您å¯ä»¥é€ä¸ªåˆ—表地将该项置为å¦ã€‚如果您将您订阅所有列 表的å£ä»¤æé†’全部关闭,将ä¸ä¼šæœ‰ä»»ä½•çš„å£ä»¤æç¤ºå‡½å‘é€ç»™æ‚¨ã€‚ -

    - å¦
    - 是

    - 设为全局 -

    - 将您自己从订阅者列表中éšè—

    +

    +å¦
    +是

    +设为全局 +

    +将您自己从订阅者列表中éšè—

    当别人查看列表æˆå‘˜æ—¶ï¼Œæ‚¨çš„电å­é‚®ä»¶åœ°å€é€šå¸¸æ˜¯å¯è§çš„(æ¨¡ç³Šå¤„ç† è¿‡å¾—å½¢å¼ä»¥é¿å…垃圾邮件)。 å¦‚æžœæ‚¨ä¸æƒ³è®©æ‚¨çš„电å­é‚®ä»¶å‡ºçŽ°åœ¨åˆ— 表æˆå‘˜æ¸…å•中,将该项选为 是 。 -

    - å¦
    - 是 -
    - 您的语言?

    -

    - -
    - 您想订阅哪个è¯é¢˜åˆ†ç±»ï¼Ÿ

    +

    +å¦
    +是 +
    +您的语言?

    +

    + +
    +您想订阅哪个è¯é¢˜åˆ†ç±»ï¼Ÿ

    通过选择一个或多个è¯é¢˜ï¼Œæ‚¨å¯ä»¥å¯¹åˆ—表的信æ¯è¿›è¡Œè¿‡æ»¤ ,仅接收列表中的部分消æ¯ã€‚如果一æ¡ä¿¡æ¯æ»¡è¶³æ‚¨çš„选择 的任一è¯é¢˜ï¼Œæ‚¨å°±ä¼šæ”¶åˆ°å®ƒï¼Œå¦åˆ™å°†ä¸ä¼šæ”¶åˆ°ã€‚ @@ -240,12 +279,11 @@

    修改您的å£ä»¤

    如果一æ¡ä¿¡æ¯ä¸æ»¡è¶³ä»»ä½•è¯é¢˜ï¼ŒæŠ•递规则å–决于下é¢çš„ 设置。如果您没有选择任何感兴趣的è¯é¢˜ï¼Œæ‚¨ä¼šå¾—到å‘往列 表的所有消æ¯ã€‚ -

    - -
    - æ‚¨æƒ³æŽ¥æ”¶ä¸æ»¡è¶³ä»»ä½•è¯é¢˜è¿‡æ»¤å™¨çš„æ¶ˆæ¯å—?

    +

    + +
    +æ‚¨æƒ³æŽ¥æ”¶ä¸æ»¡è¶³ä»»ä½•è¯é¢˜è¿‡æ»¤å™¨çš„æ¶ˆæ¯å—?

    è¯¥é€‰é¡¹åªæœ‰åœ¨æ‚¨åœ¨ä¸Šé¢è®¢é˜…了至少一个è¯é¢˜æ—¶æ‰æœ‰æ•ˆã€‚ 它æè¿°äº†ä¸æ»¡è¶³ä»»ä½•è¯é¢˜è¿‡æ»¤å™¨çš„ä¿¡æ¯çš„默认投递规则。 @@ -254,13 +292,12 @@

    修改您的å£ä»¤

    如果您没有在上é¢é€‰æ‹©æ„Ÿå…´è¶£çš„è¯é¢˜ï¼Œé‚£ä¹ˆä½ ä¼šæ”¶åˆ° 寄往邮件列表的所有信件。 -

    - å¦
    - 是 -
    - æ‹’ç»ç›¸åŒçš„信件副本?

    +

    +å¦
    +是 +
    +æ‹’ç»ç›¸åŒçš„信件副本?

    当你被明确的列在信件头部的 To: 或 Cc: ï¼ˆå³æ‚¨å¯èƒ½æ”¶åˆ°ä¸¤å°åŒæ ·çš„信), @@ -272,21 +309,17 @@

    修改您的å£ä»¤

    æ¯ä¸ªå‰¯æœ¬éƒ½ä¼šæ·»åŠ ä¸€ä¸ªX-Mailman-Copy: yes的头部。 -
    - å¦
    - 是

    - 设为全局 -

    -
    -
    +å¦
    +是

    +设为全局 +

    +
    +
    -

    - - - - + + +

    diff --git a/templates/zh_CN/private.html b/templates/zh_CN/private.html index 05c46bdd..f0bbe385 100755 --- a/templates/zh_CN/private.html +++ b/templates/zh_CN/private.html @@ -1,33 +1,94 @@ - %(realname)s ç§æœ‰å½’æ¡£è®¤è¯ +%(realname)s ç§æœ‰å½’æ¡£è®¤è¯ - - -
    + + + %(message)s - - - - - - - - - - - - - - - -
    - %(realname)s ç§æœ‰å½’æ¡£è®¤è¯ -
    Email地å€ï¼š
    å£ä»¤ï¼š
    -
    -

    注æ„: 从此时开始,您的æµè§ˆå™¨åº”该å¯ç”¨ + + + + + + + + + + + + + + + +
    +%(realname)s ç§æœ‰å½’æ¡£è®¤è¯ +
    Email地å€ï¼š
    å£ä»¤ï¼š
    +
    +

    注æ„: 从此时开始,您的æµè§ˆå™¨åº”该å¯ç”¨ cookies,å¦åˆ™æ‚¨çš„任何æ“作都需è¦é‡æ–°è®¤è¯ã€‚

    在Mailmançš„ç§æœ‰å­˜æ¡£æŽ¥å£ä¸­ä½¿ç”¨äº†Session cookies ,由此您ä¸å¿…ä¸ºæ¯ @@ -36,21 +97,21 @@ å¼åœ°ä½¿å…¶è¿‡æœŸã€‚

    - - - - - - + + + +
    - Password Reminder -
    If you don't remember your password, enter your email address + + + + + + - - - - -
    +Password Reminder +
    If you don't remember your password, enter your email address above and click the Remind button and your password will be emailed to you.
    - +
    +

    diff --git a/templates/zh_CN/roster.html b/templates/zh_CN/roster.html index abfa13d3..0a39876d 100644 --- a/templates/zh_CN/roster.html +++ b/templates/zh_CN/roster.html @@ -1,50 +1,109 @@ - - - <MM-List-Name> 订阅者 - - - - -

    - - - - - - - - - - - - - - - -
    - - 订阅者 -
    - -

    -

    - -

    点击您的邮件地å€ä»¥è®¿é—®è®¢é˜…选项页
    (圆括å·ä¸­çš„地å€å·²ç»ç¦æ­¢æŠ•递)

    -
    -
    - ä¸­æœ‰ä½ - 䏿ޥ嗿‘˜è¦çš„æˆå‘˜: -
    -
    -
    - ä¸­æœ‰ä½ - 接收摘è¦çš„æˆå‘˜: -
    -
    -

    -

    -

    -

    - - - + + +<mm-list-name> 订阅者</mm-list-name> + + +

    + + + + + + + + + + + + + + + +
    + + 订阅者 +
    +

    +

    +

    点击您的邮件地å€ä»¥è®¿é—®è®¢é˜…选项页
    (圆括å·ä¸­çš„地å€å·²ç»ç¦æ­¢æŠ•递)

    +
    +
    +ä¸­æœ‰ä½ + 䏿ޥ嗿‘˜è¦çš„æˆå‘˜: +
    +
    +
    +ä¸­æœ‰ä½ + 接收摘è¦çš„æˆå‘˜: +
    +
    +

    +

    +

    +

    + +

    + diff --git a/templates/zh_CN/subscribe.html b/templates/zh_CN/subscribe.html index c83219df..7f54b60a 100644 --- a/templates/zh_CN/subscribe.html +++ b/templates/zh_CN/subscribe.html @@ -1,8 +1,71 @@ -<MM-List-Name> 订阅结果 +<mm-list-name> 订阅结果</mm-list-name> -

    订阅结果

    - - - +

    订阅结果

    + + + diff --git a/templates/zh_TW/admindbpreamble.html b/templates/zh_TW/admindbpreamble.html index 133730f5..49d900a7 100644 --- a/templates/zh_TW/admindbpreamble.html +++ b/templates/zh_TW/admindbpreamble.html @@ -1,4 +1,67 @@ -論壇%(listname)s ä¸­æœ‰å¾…æ±ºæ¡ˆä»¶éœ€è¦æ‚¨çš„審核。 +論壇%(listname)s ä¸­æœ‰å¾…æ±ºæ¡ˆä»¶éœ€è¦æ‚¨çš„審核。 é¦–å…ˆæ‚¨æ‡‰åˆ°å¾…æ±ºæ¡ˆä»¶çš„ç¶²é æŸ¥è©¢ç›¸é—œè¨Šæ¯ã€‚

    è‹¥å±¬æ–¼è¨‚é–±çš„ç”³è«‹ï¼Œè«‹é¸æ“‡ Refuse 拒絕,或 @@ -12,11 +75,11 @@

  • Approve - 放行信件並é€ä¸Šè«–壇。
  • Reject - 退信並å‘å¯„ä¿¡äººç°¡çŸ­èªªæ˜Žé€€ä¿¡åŽŸå› ï¼Œè©²ä¿¡ä»¶ä¸æœƒé€ä¸Šè«–壇。
  • Discard - 逕行丟棄,此é¸é …å°æ–¼åžƒåœ¾éƒµä»¶å分有用。 - - +
  • 如果信件是給壇主,如果您è¦å­˜ä¸€ä»½å‰¯æœ¬æ™‚,將 Preserve é¸é …打開。 這é¸é …å°ç½µäººçš„信件很有用 。 如果您想將信件轉寄ä¸åœ¨æ­¤è«–壇的人士,將 Forward to é¸é …打開,並填上轉信地å€ã€‚

    當您完æˆå·¥ä½œï¼Œè«‹æŒ‰åœ¨ç¶²é ä¸Šæ–¹æˆ–下方的 Submit All Data 按鈕 ä»¥ä¾¿åŸ·è¡Œæ‚¨çš„æ±ºå®šã€‚å¦‚æžœæ‚¨ä¸æƒ³ç¾åœ¨ä½œæ±ºå®šï¼Œè«‹å‹¿æŒ‰æ­¤éˆ•。 ç¨å€™æ‚¨å¯ä»¥å†æ±ºå®šã€‚ +

    \ No newline at end of file diff --git a/templates/zh_TW/admlogin.html b/templates/zh_TW/admlogin.html index a1921c0c..cbc3454f 100755 --- a/templates/zh_TW/admlogin.html +++ b/templates/zh_TW/admlogin.html @@ -1,32 +1,95 @@ - %(listname)s 論壇 壇主驗證 +%(listname)s 論壇 壇主驗證 - + -
    + %(message)s - - - - - - - - - - - -
    - %(listname)s 論壇 壇主驗證 -
    壇主密碼:
    -
    -

    é‡é»ž: 從ç¾åœ¨é–‹å§‹ï¼Œ 您必須 + + + + + + + + + + + +
    +%(listname)s 論壇 壇主驗證 +
    壇主密碼:
    +
    +

    é‡é»ž: 從ç¾åœ¨é–‹å§‹ï¼Œ 您必須 å°‡ç€è¦½å™¨çš„ cookie 功能打開,å¦å‰‡æ‚¨æ‰€ä½œçš„ç•°å‹•å°‡ä¸æœƒç™¼ç”Ÿæ•ˆç”¨ã€‚

    Mailman 的管ç†ä»‹é¢ä½¿ç”¨äº† Session cookies , 因此您ä¸éœ€è¦åœ¨æ¯æ¬¡ ç•°å‹•è³‡æ–™æ™‚é‡æ–°é©—證密碼。此cookie 在您關閉ç€è¦½å™¨æ™‚自動失效, 或者您按下在 Other Administrative Activities ç¶²é ä¸­ Logout çš„è¶…é€£çµ (一旦您æˆåŠŸçš„ç™»éŒ„ä¹‹å¾Œï¼Œæ‚¨å°±å¯ä»¥çœ‹åˆ°)。 -

    +

    + \ No newline at end of file diff --git a/templates/zh_TW/handle_opts.html b/templates/zh_TW/handle_opts.html index aeedebc0..8070ae61 100644 --- a/templates/zh_TW/handle_opts.html +++ b/templates/zh_TW/handle_opts.html @@ -1,9 +1,72 @@ - -<MM-List-Name> <MM-Operation> Results + +<mm-list-name> <mm-operation> Results</mm-operation></mm-list-name> -

    Results

    - - - +

    Results

    + + + diff --git a/templates/zh_TW/headfoot.html b/templates/zh_TW/headfoot.html index 2f812831..7f5be57c 100644 --- a/templates/zh_TW/headfoot.html +++ b/templates/zh_TW/headfoot.html @@ -1,10 +1,73 @@ -本文å¯åŒ…å« %(attribute)s æ ¼å¼å­—ä¸²ï¼Œå…¶å¯æ›¿æ›ç‚ºé€šä¿¡è«–壇 +本文å¯åŒ…å« %(attribute)s æ ¼å¼å­—ä¸²ï¼Œå…¶å¯æ›¿æ›ç‚ºé€šä¿¡è«–壇 相應的屬性。詳情請åƒç…§ Python æ ¼å¼åŒ–字串è¦å‰‡. é è¨­å±¬æ€§å¦‚下:
      -
    • real_name - 本論壇 "較佳" çš„å稱; +
    • real_name - 本論壇 "較佳" çš„å稱; 通常都用大寫。
    • list_name - 此論壇å稱用於 URL 識別 @@ -20,4 +83,4 @@
    • description - 該通信論壇的摘è¦èªªæ˜Žã€‚
    • info - 該通信論壇的詳細說明。 -
    + diff --git a/templates/zh_TW/listinfo.html b/templates/zh_TW/listinfo.html index bc36e5f0..b4e8ccd1 100644 --- a/templates/zh_TW/listinfo.html +++ b/templates/zh_TW/listinfo.html @@ -1,133 +1,193 @@ - - - - <MM-List-Name> 相關資訊 - - - -

    - - - - - - - - - - - - - - - - - - - + + + + + +

    - -- - -
    -

      -

    - 關於 論壇 - - - -
    -

    -

    欲查本論壇舊信,請逕洽 - 檔案ä¿ç®¡è™•。 - -

    -
    - 使用 論壇 -
    - 欲在論壇發言,請寄到 。 + + + +<mm-list-name> 相關資訊</mm-list-name> + + +

    + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + +
    + -- + +
    +

      +

    +關於 論壇 + + + +
    +

    +

    欲查本論壇舊信,請逕洽 + 檔案ä¿ç®¡è™•。 + +

    +
    +使用 論壇 +
    + 欲在論壇發言,請寄到 。

    在下é¢çš„ç¶²é å¯è®“您å¯ä»¥åŠ å…¥é€™å€‹è«–å£‡ï¼Œæˆ–è®Šæ›´è¨‚é–±è¨­å®šã€‚ -

    - 加入 論壇 -
    -

    - 欲加入(訂閱) 論壇請ä¾åºå¡«å¦¥ä¸‹åˆ—資料。 - -

      - - - - - - - - + + + + + + - - - - - -
      您的 E-Mail: -  
      +

      +加入 論壇 +
      +

      + 欲加入(訂閱) 論壇請ä¾åºå¡«å¦¥ä¸‹åˆ—資料。 + +

        + + + + + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - - - -
        您的 E-Mail: + 
        ä½  å¿… é ˆ 輸 å…¥ 一 個 ç§ äºº 密 碼 以 ä¾› å– æ¶ˆ 訂 é–± 或 å…¶ ä»– 相 é—œ 設 定 çš„ 權 力 。 è«‹ å‹¿ 使 用 有 價 值 çš„ 密 碼 ! å›  為 å¶ è€Œ å¯ ä»¥ å°‡ 密 碼 以 明 碼 çš„ æ–¹ å¼ éƒµ 寄 給 您。 - - -
        輸入密碼: 
        確èªè¼¸å…¥å¯†ç¢¼: 
        您é©ç”¨çš„語言?  
        ä½  想 以 æ¯ æ—¥ 摘 è¦ çš„ å½¢ å¼ æ”¶ ä¿¡ å—Ž ? + + +
        輸入密碼: 
        確èªè¼¸å…¥å¯†ç¢¼: 
        您é©ç”¨çš„語言?  
        ä½  想 以 æ¯ æ—¥ 摘 è¦ çš„ å½¢ å¼ æ”¶ ä¿¡ å—Ž ? No - Yes -
        -
        -
        - -
      -
      - - 訂 閱 者 -
      - - - -

      - - - -

      - - - +
    No + Yes +
    +
    +
    + + +

    + + 訂 閱 者 +
    + + + +

    + + + +

    + +

    + diff --git a/templates/zh_TW/options.html b/templates/zh_TW/options.html index 051b0c72..26895132 100644 --- a/templates/zh_TW/options.html +++ b/templates/zh_TW/options.html @@ -1,152 +1,200 @@ -<MM-Presentable-User> <MM-List-Name>論壇設定 - - - - -
    - - 的設定 -
    - - +<mm-presentable-user> <mm-list-name>論壇設定</mm-list-name></mm-presentable-user> + + + +
    + + 的設定 +
    +

    - - - 在 論壇的訂閱狀態,密碼åŠé¸é …。 - - - - + + 在 論壇的訂閱狀態,密碼åŠé¸é …。 + +

    - - - - - - -
    -å–æ¶ˆè¨‚é–± - -您其它在 的訂閱 -
    + + + + +
    +å–æ¶ˆè¨‚é–± + +您其它在 的訂閱 +
    æ¬²å–æ¶ˆè¨‚閱請輸入您的密碼後按此按鈕(若您忘記密碼,請見下文如何å–得您的密碼)

    -密碼:   - - -

    +密碼:   + +

    +
    輸入您的密碼å¯é€£çµåˆ°æ‚¨åœ¨æœ¬è«–壇其它的訂閱的設定畫é¢ã€‚

    -密碼:

    - -

    - - - - +密碼:

    +

    +
    -您在 的密碼 -
    + + - - +
    +您在 的密碼 +
    - + +

    忘記密碼怎麼辦?

    按下此鈕å¯å°‡æ‚¨çš„密碼郵寄給您。

    - +

    - -
    -
    - + + +

    +

    變更密碼

    - - - - - - - - - - - - - +
    舊密碼:
    新密碼:
    å†è¼¸å…¥ä¸€æ¬¡æ–°å¯†ç¢¼:
    + + + + + + + + + + + +
    舊密碼:
    新密碼:
    å†è¼¸å…¥ä¸€æ¬¡æ–°å¯†ç¢¼:
    - -
    - + +

    - - - -
    -Your 訂閱é¸é … -
    - +
    + +
    +Your 訂閱é¸é … +

    ç¾åœ¨çš„設定值有作記號。

    - - + + +
    + + - - +一旦您將é¸é …ç”± On 轉為 Off 您會收到最近一å°çš„æ‘˜è¦ã€‚
    + Off + On
    +
    + - - + - - -
    - æš«åœæ”¶ä¿¡
    -å¦‚æžœæ‚¨çŸ­æœŸå…§ä¸æƒ³æ”¶åˆ°è«–å£‡å¯„å‡ºçš„ä¿¡ï¼Œè«‹é¸ On。
    + æš«åœæ”¶ä¿¡
    +å¦‚æžœæ‚¨çŸ­æœŸå…§ä¸æƒ³æ”¶åˆ°è«–å£‡å¯„å‡ºçš„ä¿¡ï¼Œè«‹é¸ On。
    Off On

    - +

    - è¨­å®šæ‘˜è¦æ¨¡å¼
    +
    + è¨­å®šæ‘˜è¦æ¨¡å¼
    è‹¥æ‚¨é¸æ“‡æ‘˜è¦æ¨¡å¼ï¼Œæ‚¨æœƒæ”¶åˆ°æ¯å¤©é›†æˆä¸€å°çš„æ‘˜è¦ï¼Œè€Œä¸æ˜¯æ¯æ¬¡ç™¼è¨€ä¸€å°ã€‚ -一旦您將é¸é …ç”± On 轉為 Off 您會收到最近一å°çš„æ‘˜è¦ã€‚
    - Off - On
    -
    - 收 MIME 或 純文字的摘�
    -如果您收 MIME çš„æ‘˜è¦æœƒæœ‰å•é¡Œï¼Œè«‹é¸æ“‡ç´”文字的摘è¦ã€‚
    - MIME - 純文字

    -

    + 收 MIME 或 純文字的摘�
    +如果您收 MIME çš„æ‘˜è¦æœƒæœ‰å•é¡Œï¼Œè«‹é¸æ“‡ç´”文字的摘è¦ã€‚
    + MIME + 純文字

    +

    - 您想收到自己寄到論壇的信嗎?
    +
    + 您想收到自己寄到論壇的信嗎?
    Yes No

    -

    - 您想在寄信到論壇後,收到確èªä¿¡å—Ž?
    +

    + 您想在寄信到論壇後,收到確èªä¿¡å—Ž?
    No Yes

    -

    - æ‚¨ä¸æƒ³å‡ºç¾åœ¨è¨‚閱者å單中嗎? 就是潛艇部隊啦!
    - No - Yes

    -

    - 您é©ç”¨çš„語言? -
    -密碼:

    -

    - - +

    + æ‚¨ä¸æƒ³å‡ºç¾åœ¨è¨‚閱者å單中嗎? 就是潛艇部隊啦!
    + No + Yes

    +

    + 您é©ç”¨çš„語言? +
    +密碼:

    +

    - - - - + + +

    diff --git a/templates/zh_TW/roster.html b/templates/zh_TW/roster.html index b2005268..76414448 100644 --- a/templates/zh_TW/roster.html +++ b/templates/zh_TW/roster.html @@ -1,50 +1,111 @@ - - - <MM-List-Name> 訂閱會員 - - - - -

    - - - - - - - - - - - - - - - -
    - - 訂閱會員一覽表 -
    -

    -

    -

    點 é¸ æ‚¨ çš„ email å¯ åˆ° 個 人 çš„ 訂 é–± 設 定 ç¶² é  ã€‚ -
    (括 號 括 èµ· 來 çš„ 代 表 è«‹ å‡ ä¸­ ( æš« åœ æ”¶ ä¿¡ )。)

    -
    -
    - 一 般 會 員 : - 人 -
    -
    -
    - - æ¯ æ—¥ åˆ å¯„ : 人 -
    -
    -

    -

    -

    -

    - - - + + +<mm-list-name> 訂閱會員</mm-list-name> + + +

    + + + + + + + + + + + + + + + +
    + + 訂閱會員一覽表 +
    +

    +

    +

    點 é¸ æ‚¨ çš„ email å¯ åˆ° 個 人 çš„ 訂 é–± 設 定 ç¶² é  ã€‚ +
    (括 號 括 èµ· 來 çš„ 代 表 è«‹ å‡ ä¸­ ( æš« åœ æ”¶ ä¿¡ )。)

    +
    +
    + 一 般 會 員 : + 人 +
    +
    +
    + + æ¯ æ—¥ åˆ å¯„ : 人 +
    +
    +

    +

    +

    +

    + +

    + diff --git a/templates/zh_TW/subscribe.html b/templates/zh_TW/subscribe.html index c2229691..3e2bdc48 100644 --- a/templates/zh_TW/subscribe.html +++ b/templates/zh_TW/subscribe.html @@ -1,9 +1,72 @@ -<MM-List-Name> è¨‚é–±çµæžœ +<mm-list-name> è¨‚é–±çµæžœ</mm-list-name> -

    訂 é–± çµ æžœ

    - - - +

    訂 é–± çµ æžœ

    + + + From 80c3e2cd83999592279531c9d95ca714424e526d Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Wed, 21 May 2025 20:55:30 -0400 Subject: [PATCH 646/748] Fix TypeError in admin.py: convert breadcrumb items to strings before joining --- Mailman/Cgi/admin.py | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/Mailman/Cgi/admin.py b/Mailman/Cgi/admin.py index 2b2454ca..7cc4bf7f 100644 --- a/Mailman/Cgi/admin.py +++ b/Mailman/Cgi/admin.py @@ -514,7 +514,7 @@ def option_help(mlist, varhelp): doc.AddItem(mlist.GetMailmanFooter()) print(doc.Format()) -def add_standard_headers(doc, mlist, title, category=None, subcat=None): +def add_standard_headers(doc, mlist, title, category, subcat): """Add standard headers to admin pages. Args: @@ -531,24 +531,15 @@ def add_standard_headers(doc, mlist, title, category=None, subcat=None): doc.AddItem(Header(2, title)) # Add navigation breadcrumbs if category/subcat provided + breadcrumbs = [] + breadcrumbs.append(Link(mlist.GetScriptURL('admin'), _('%(realname)s administrative interface'))) if category: - adminurl = mlist.GetScriptURL('admin') - categories = mlist.GetConfigCategories() - category_label = _(categories[category][0]) - if isinstance(category_label, bytes): - category_label = category_label.decode(Utils.GetCharSet(mlist.preferred_language), 'replace') - - breadcrumbs = [] - breadcrumbs.append(Link(adminurl, _('Admin'))) - breadcrumbs.append(Link(f'{adminurl}/{category}', category_label)) - - if subcat: - subcat_label = _(categories[category][1].get(subcat, [subcat])[0]) - if isinstance(subcat_label, bytes): - subcat_label = subcat_label.decode(Utils.GetCharSet(mlist.preferred_language), 'replace') - breadcrumbs.append(Link(f'{adminurl}/{category}/{subcat}', subcat_label)) - - doc.AddItem(Center(' > '.join(breadcrumbs))) + breadcrumbs.append(Link(mlist.GetScriptURL('admin') + '/' + category, _(category))) + if subcat: + breadcrumbs.append(Link(mlist.GetScriptURL('admin') + '/' + category + '/' + subcat, _(subcat))) + # Convert each breadcrumb item to a string before joining + breadcrumbs = [str(item) for item in breadcrumbs] + doc.AddItem(Center(' > '.join(breadcrumbs))) # Add horizontal rule doc.AddItem('
    ') From f91e134cd8b4b5baa31a0920decb4036c720925e Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Wed, 21 May 2025 20:58:52 -0400 Subject: [PATCH 647/748] update --- Mailman/htmlformat.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Mailman/htmlformat.py b/Mailman/htmlformat.py index 6ec04d26..758bcecc 100644 --- a/Mailman/htmlformat.py +++ b/Mailman/htmlformat.py @@ -434,7 +434,10 @@ def Format(self, indent=0, **kws): if mm_cfg.WEB_HEAD_ADD: output.append(mm_cfg.WEB_HEAD_ADD) output.append('%s' % tab) - output.append('%s' % tab) + # Get language direction + direction = Utils.GetDirection(self.language) + # Add body tag with direction attribute + output.append('%s' % (tab, direction)) quals = [] # Default link colors if mm_cfg.WEB_VLINK_COLOR: @@ -445,10 +448,8 @@ def Format(self, indent=0, **kws): kws.setdefault('link', mm_cfg.WEB_LINK_COLOR) for k, v in list(kws.items()): quals.append('%s="%s"' % (k, v)) - output.append('%s' % tab) - # Language direction - direction = Utils.GetDirection(self.language) - output.append('dir="%s">' % direction) + if quals: + output[-1] = output[-1][:-1] + ' ' + ' '.join(quals) + '>' # Always do this... output.append(Container.Format(self, indent)) if not self.suppress_head: From e3fc5ce951857a87338140a8a448b2151e78ccad Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Wed, 21 May 2025 21:04:45 -0400 Subject: [PATCH 648/748] update --- Mailman/HTMLFormatter.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/Mailman/HTMLFormatter.py b/Mailman/HTMLFormatter.py index 457bf045..1f35527a 100644 --- a/Mailman/HTMLFormatter.py +++ b/Mailman/HTMLFormatter.py @@ -406,7 +406,7 @@ def ParseTags(self, template, replacements, lang=None): # Convert replacement keys to lowercase for case-insensitive matching replacements = {k.lower(): v for k, v in replacements.items()} - # Split on MM tags, case-insensitive + # Split on MM tags, case-insensitive, but preserve HTML entities parts = re.split('(]*>)', text) i = 1 while i < len(parts): @@ -414,10 +414,18 @@ def ParseTags(self, template, replacements, lang=None): if tag in replacements: repl = replacements[tag] if isinstance(repl, str): - repl = repl.encode(charset, 'replace') - if isinstance(repl, bytes): + # Don't encode HTML entities + if '&' in repl: + parts[i] = repl + else: + repl = repl.encode(charset, 'replace') + repl = repl.decode(charset, 'replace') + parts[i] = repl + elif isinstance(repl, bytes): repl = repl.decode(charset, 'replace') - parts[i] = repl + parts[i] = repl + else: + parts[i] = str(repl) else: parts[i] = '' i = i + 2 From 6a0b6d5e593a705d980f0e0286cd56e4f7ccce65 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Wed, 21 May 2025 21:07:49 -0400 Subject: [PATCH 649/748] update --- Mailman/HTMLFormatter.py | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/Mailman/HTMLFormatter.py b/Mailman/HTMLFormatter.py index 1f35527a..1080cb9c 100644 --- a/Mailman/HTMLFormatter.py +++ b/Mailman/HTMLFormatter.py @@ -418,19 +418,40 @@ def ParseTags(self, template, replacements, lang=None): if '&' in repl: parts[i] = repl else: - repl = repl.encode(charset, 'replace') + # Ensure proper encoding/decoding + try: + # First try to decode if it's already encoded + if isinstance(repl, bytes): + repl = repl.decode(charset, 'replace') + # Then encode and decode to ensure proper charset + repl = repl.encode(charset, 'replace').decode(charset, 'replace') + parts[i] = repl + except (UnicodeError, LookupError): + # Fallback to utf-8 if charset fails + repl = repl.encode('utf-8', 'replace').decode('utf-8', 'replace') + parts[i] = repl + elif isinstance(repl, bytes): + try: repl = repl.decode(charset, 'replace') parts[i] = repl - elif isinstance(repl, bytes): - repl = repl.decode(charset, 'replace') - parts[i] = repl + except (UnicodeError, LookupError): + repl = repl.decode('utf-8', 'replace') + parts[i] = repl else: parts[i] = str(repl) else: parts[i] = '' i = i + 2 - return EMPTYSTRING.join(parts) + # Join parts and ensure proper encoding + result = EMPTYSTRING.join(parts) + try: + # Ensure the final output is properly encoded + if isinstance(result, bytes): + result = result.decode(charset, 'replace') + return result + except (UnicodeError, LookupError): + return result.decode('utf-8', 'replace') def GetStandardReplacements(self, lang=None, replacements=None): """Get the standard replacements for this list.""" From cd7f6771573338e19c845f52cf995f509b5447f6 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Wed, 21 May 2025 21:11:05 -0400 Subject: [PATCH 650/748] update --- templates/en/listinfo.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/templates/en/listinfo.html b/templates/en/listinfo.html index a40f5e79..c509f779 100644 --- a/templates/en/listinfo.html +++ b/templates/en/listinfo.html @@ -79,8 +79,7 @@
    -

      -

    From a4c4f5b252ae128629645fa97c45d003058edc41 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Wed, 21 May 2025 21:12:56 -0400 Subject: [PATCH 651/748] update --- Mailman/Utils.py | 132 +++++++++++++++-------------------------------- 1 file changed, 42 insertions(+), 90 deletions(-) diff --git a/Mailman/Utils.py b/Mailman/Utils.py index cfe1be2c..d6c76bb5 100644 --- a/Mailman/Utils.py +++ b/Mailman/Utils.py @@ -600,113 +600,65 @@ def findtext(templatefile, dict=None, raw=0, lang=None, mlist=None): # If lang is None, use the default language from mm_cfg if lang is None: lang = mm_cfg.DEFAULT_SERVER_LANGUAGE - # First try the list's language-specific template directory - if lang and mlist: - path = os.path.join(mlist.fullpath(), 'templates', lang, templatefile) - if os.path.exists(path): - try: - with open(path, 'rb') as fp: - raw_bytes = fp.read() - # Try UTF-8 first + + def read_template_file(path): + try: + with open(path, 'rb') as fp: + raw_bytes = fp.read() + # First check if the file contains HTML entities + if b' ' in raw_bytes or b'&' in raw_bytes or b'<' in raw_bytes or b'>' in raw_bytes: + # If it contains HTML entities, try UTF-8 first + try: + return raw_bytes.decode('utf-8'), path + except UnicodeDecodeError: + # If UTF-8 fails, try ISO-8859-1 which is safe for HTML entities + return raw_bytes.decode('iso-8859-1'), path + else: + # If no HTML entities, try all encodings in sequence try: - text = raw_bytes.decode('utf-8') - return text, path + return raw_bytes.decode('utf-8'), path except UnicodeDecodeError: - # Try EUC-JP for Japanese text try: - text = raw_bytes.decode('euc-jp') - return text, path + return raw_bytes.decode('euc-jp'), path except UnicodeDecodeError: - # Try ISO-8859-1 next try: - text = raw_bytes.decode('iso-8859-1') - return text, path + return raw_bytes.decode('iso-8859-1'), path except UnicodeDecodeError: - # Finally try latin1 as a last resort - text = raw_bytes.decode('latin1') - return text, path - except IOError: - pass + return raw_bytes.decode('latin1'), path + except IOError: + return None, None + + # First try the list's language-specific template directory + if lang and mlist: + path = os.path.join(mlist.fullpath(), 'templates', lang, templatefile) + if os.path.exists(path): + result = read_template_file(path) + if result[0] is not None: + return result + # Then try the site's language-specific template directory if lang: path = os.path.join(mm_cfg.TEMPLATE_DIR, lang, templatefile) if os.path.exists(path): - try: - with open(path, 'rb') as fp: - raw_bytes = fp.read() - # Try UTF-8 first - try: - text = raw_bytes.decode('utf-8') - return text, path - except UnicodeDecodeError: - # Try EUC-JP for Japanese text - try: - text = raw_bytes.decode('euc-jp') - return text, path - except UnicodeDecodeError: - # Try ISO-8859-1 next - try: - text = raw_bytes.decode('iso-8859-1') - return text, path - except UnicodeDecodeError: - # Finally try latin1 as a last resort - text = raw_bytes.decode('latin1') - return text, path - except IOError: - pass + result = read_template_file(path) + if result[0] is not None: + return result + # Then try the list's default template directory if mlist: path = os.path.join(mlist.fullpath(), 'templates', templatefile) if os.path.exists(path): - try: - with open(path, 'rb') as fp: - raw_bytes = fp.read() - # Try UTF-8 first - try: - text = raw_bytes.decode('utf-8') - return text, path - except UnicodeDecodeError: - # Try EUC-JP for Japanese text - try: - text = raw_bytes.decode('euc-jp') - return text, path - except UnicodeDecodeError: - # Try ISO-8859-1 next - try: - text = raw_bytes.decode('iso-8859-1') - return text, path - except UnicodeDecodeError: - # Finally try latin1 as a last resort - text = raw_bytes.decode('latin1') - return text, path - except IOError: - pass + result = read_template_file(path) + if result[0] is not None: + return result + # Finally try the site's default template directory path = os.path.join(mm_cfg.TEMPLATE_DIR, templatefile) if os.path.exists(path): - try: - with open(path, 'rb') as fp: - raw_bytes = fp.read() - # Try UTF-8 first - try: - text = raw_bytes.decode('utf-8') - return text, path - except UnicodeDecodeError: - # Try EUC-JP for Japanese text - try: - text = raw_bytes.decode('euc-jp') - return text, path - except UnicodeDecodeError: - # Try ISO-8859-1 next - try: - text = raw_bytes.decode('iso-8859-1') - return text, path - except UnicodeDecodeError: - # Finally try latin1 as a last resort - text = raw_bytes.decode('latin1') - return text, path - except IOError: - pass + result = read_template_file(path) + if result[0] is not None: + return result + return None, None From 7bc26773172a1d40d34e30be66b6ade2f35dca4b Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Wed, 21 May 2025 21:14:35 -0400 Subject: [PATCH 652/748] update --- Mailman/Utils.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/Mailman/Utils.py b/Mailman/Utils.py index d6c76bb5..8144121b 100644 --- a/Mailman/Utils.py +++ b/Mailman/Utils.py @@ -609,10 +609,22 @@ def read_template_file(path): if b' ' in raw_bytes or b'&' in raw_bytes or b'<' in raw_bytes or b'>' in raw_bytes: # If it contains HTML entities, try UTF-8 first try: - return raw_bytes.decode('utf-8'), path + text = raw_bytes.decode('utf-8') + # Ensure HTML entities are preserved + text = text.replace(' ', ' ') + text = text.replace('&', '&') + text = text.replace('<', '<') + text = text.replace('>', '>') + return text, path except UnicodeDecodeError: # If UTF-8 fails, try ISO-8859-1 which is safe for HTML entities - return raw_bytes.decode('iso-8859-1'), path + text = raw_bytes.decode('iso-8859-1') + # Ensure HTML entities are preserved + text = text.replace(' ', ' ') + text = text.replace('&', '&') + text = text.replace('<', '<') + text = text.replace('>', '>') + return text, path else: # If no HTML entities, try all encodings in sequence try: From 34badea6a75ebdf1aa6fff735881c799cb76b1a5 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Wed, 21 May 2025 21:16:50 -0400 Subject: [PATCH 653/748] update --- Mailman/Utils.py | 42 ++++++++++++------------------------------ 1 file changed, 12 insertions(+), 30 deletions(-) diff --git a/Mailman/Utils.py b/Mailman/Utils.py index 8144121b..d15d57d5 100644 --- a/Mailman/Utils.py +++ b/Mailman/Utils.py @@ -605,38 +605,20 @@ def read_template_file(path): try: with open(path, 'rb') as fp: raw_bytes = fp.read() - # First check if the file contains HTML entities - if b' ' in raw_bytes or b'&' in raw_bytes or b'<' in raw_bytes or b'>' in raw_bytes: - # If it contains HTML entities, try UTF-8 first - try: - text = raw_bytes.decode('utf-8') - # Ensure HTML entities are preserved - text = text.replace(' ', ' ') - text = text.replace('&', '&') - text = text.replace('<', '<') - text = text.replace('>', '>') - return text, path - except UnicodeDecodeError: - # If UTF-8 fails, try ISO-8859-1 which is safe for HTML entities - text = raw_bytes.decode('iso-8859-1') - # Ensure HTML entities are preserved - text = text.replace(' ', ' ') - text = text.replace('&', '&') - text = text.replace('<', '<') - text = text.replace('>', '>') - return text, path - else: - # If no HTML entities, try all encodings in sequence - try: - return raw_bytes.decode('utf-8'), path - except UnicodeDecodeError: + # First try UTF-8 since that's the most common encoding + try: + text = raw_bytes.decode('utf-8') + return text, path + except UnicodeDecodeError: + # If UTF-8 fails, try other encodings + for encoding in ['euc-jp', 'iso-8859-1', 'latin1']: try: - return raw_bytes.decode('euc-jp'), path + text = raw_bytes.decode(encoding) + return text, path except UnicodeDecodeError: - try: - return raw_bytes.decode('iso-8859-1'), path - except UnicodeDecodeError: - return raw_bytes.decode('latin1'), path + continue + # If all encodings fail, use UTF-8 with replacement + return raw_bytes.decode('utf-8', 'replace'), path except IOError: return None, None From dfe9660b95a2e7a96cdcb61bf50db45a1bd6d01d Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Wed, 21 May 2025 21:32:15 -0400 Subject: [PATCH 654/748] Fix HTML entity handling in Utils.py and related files to prevent stray characters in output --- Mailman/Gui/GUIBase.py | 8 +++++++- Mailman/Utils.py | 12 +++++++----- Mailman/htmlformat.py | 10 ++++++++-- Mailman/versions.py | 8 ++++---- 4 files changed, 26 insertions(+), 12 deletions(-) diff --git a/Mailman/Gui/GUIBase.py b/Mailman/Gui/GUIBase.py index 8ca5bb25..a0566cba 100644 --- a/Mailman/Gui/GUIBase.py +++ b/Mailman/Gui/GUIBase.py @@ -160,7 +160,7 @@ def _postValidate(self, mlist, doc): def handleForm(self, mlist, category, subcat, cgidata, doc): for item in self.GetConfigInfo(mlist, category, subcat): # Skip descriptions and legacy non-attributes - if not type(item) is tuple or len(item) < 5: + if not isinstance(item, tuple) or len(item) < 5: continue # Unpack the gui item description property, wtype, args, deps, desc = item[0:5] @@ -241,3 +241,9 @@ def _convertString(self, mlist, property, alloweds, val, doc): """)) return fixed return val + + def AddItem(self, item): + """Add an item to the list of items to be displayed.""" + if not isinstance(item, tuple) or len(item) < 5: + raise ValueError('Item must be a tuple with at least 5 elements') + self.items.append(item) diff --git a/Mailman/Utils.py b/Mailman/Utils.py index d15d57d5..8dc34b30 100644 --- a/Mailman/Utils.py +++ b/Mailman/Utils.py @@ -228,11 +228,13 @@ def ParseEmail(email): def LCDomain(addr): - "returns the address with the domain part lowercased" - atind = addr.find('@') - if atind == -1: # no domain part - return addr - return addr[:atind] + '@' + addr[atind+1:].lower() + """Convert an email address to lowercase, preserving the domain part.""" + if isinstance(addr, str): + at = addr.find('@') + if at == -1: + return addr.lower() + return addr[:at].lower() + addr[at:] + return addr # TBD: what other characters should be disallowed? diff --git a/Mailman/htmlformat.py b/Mailman/htmlformat.py index 758bcecc..ed1189f7 100644 --- a/Mailman/htmlformat.py +++ b/Mailman/htmlformat.py @@ -50,7 +50,7 @@ def HTMLFormatObject(item, indent): "Return a presentation of an object, invoking their Format method if any." if item is None: return '' - if type(item) == type(''): + if isinstance(item, str): return item elif not hasattr(item, "Format"): return str(item) @@ -658,7 +658,13 @@ def __init__(self, name, button_names, checked, horizontal, values): # for CheckedBoxes it is a vector. Subclasses will assert length. def ischecked(self, i): - raise NotImplemented + if isinstance(self.checked, int): + return i == self.checked + elif isinstance(self.checked, tuple): + return i in self.checked + elif isinstance(self.checked, list): + return i in self.checked + return 0 def Format(self, indent=0): t = Table(cellspacing=5) diff --git a/Mailman/versions.py b/Mailman/versions.py index 4ed6bae1..1bc32065 100644 --- a/Mailman/versions.py +++ b/Mailman/versions.py @@ -346,12 +346,12 @@ def convert(s, f, t): # transfer the list data type for holding members and digest members # to the dict data type starting file format version 11 # - if type(l.members) is list: + if isinstance(l.members, list): members = {} for m in l.members: members[m] = 1 l.members = members - if type(l.digest_members) is list: + if isinstance(l.digest_members, list): dmembers = {} for dm in l.digest_members: dmembers[dm] = 1 @@ -371,7 +371,7 @@ def convert(s, f, t): if k.lower() != k: l.members[k.lower()] = Utils.LCDomain(k) del l.members[k] - elif type(l.members[k]) == str and k == l.members[k].lower(): + elif isinstance(l.members[k], str) and k == l.members[k].lower(): # already converted pass else: @@ -380,7 +380,7 @@ def convert(s, f, t): if k.lower() != k: l.digest_members[k.lower()] = Utils.LCDomain(k) del l.digest_members[k] - elif type(l.digest_members[k]) == str and \ + elif isinstance(l.digest_members[k], str) and \ k == l.digest_members[k].lower(): # already converted pass From 44971ddb2b3f308ab5b86ca992f109d8061ebabd Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Wed, 21 May 2025 21:36:02 -0400 Subject: [PATCH 655/748] Fix Unicode encoding issues in UserNotification class to properly handle non-ASCII characters --- Mailman/Cgi/admin.py | 12 ++++----- Mailman/Message.py | 6 +++++ Mailman/OldStyleMemberships.py | 46 ++++++++++++++++++++++++++++------ 3 files changed, 51 insertions(+), 13 deletions(-) diff --git a/Mailman/Cgi/admin.py b/Mailman/Cgi/admin.py index 7cc4bf7f..7751da2d 100644 --- a/Mailman/Cgi/admin.py +++ b/Mailman/Cgi/admin.py @@ -249,7 +249,7 @@ def main(): # POST methods, even if their actions have a query string, don't get # put into FieldStorage's keys :-( qs = parsedqs.get('VARHELP') - if qs and type(qs) is list: + if qs and isinstance(qs, list): varhelp = qs[0] if varhelp: option_help(mlist, varhelp) @@ -636,7 +636,7 @@ def show_variables(mlist, category, subcat, cgidata, doc): if isinstance(description, bytes): description = description.decode(Utils.GetCharSet(mlist.preferred_language), 'replace') mailman_log('debug', 'Description: %s', description) - if type(description) is str: + if isinstance(description, str): table.AddRow([description]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) options = options[1:] @@ -655,7 +655,7 @@ def show_variables(mlist, category, subcat, cgidata, doc): for item in options: mailman_log('debug', 'Processing item: %s', str(item)) - if type(item) == str: + if isinstance(item, str): # The very first banner option (string in an options list) is # treated as a general description, while any others are # treated as section headers - centered and italicized... @@ -1097,7 +1097,7 @@ def membership_options(mlist, subcat, cgidata, doc, form): 'letter': letter, 'findfrag': findfrag } - if type(url) is str: + if isinstance(url, str): url = url.encode(Utils.GetCharSet(mlist.preferred_language), errors='ignore') if letter == bucket: @@ -1262,7 +1262,7 @@ def membership_options(mlist, subcat, cgidata, doc, form): qsenviron = os.environ.get('QUERY_STRING') if qsenviron: qs = urllib.parse.parse_qs(qsenviron).get('legend') - if qs and type(qs) is list: + if qs and isinstance(qs, list): qs = qs[0] if qs == 'yes': addlegend = 'legend=yes&' @@ -1299,7 +1299,7 @@ def membership_options(mlist, subcat, cgidata, doc, form): 'i': i, 'findfrag': findfrag } - if type(thisurl) is str: + if isinstance(thisurl, str): thisurl = thisurl.encode( Utils.GetCharSet(mlist.preferred_language), errors='ignore') diff --git a/Mailman/Message.py b/Mailman/Message.py index 2814e8e9..6f3b1b63 100644 --- a/Mailman/Message.py +++ b/Mailman/Message.py @@ -302,6 +302,9 @@ def __init__(self, recip, sender, subject=None, text=None, lang=None): text = text.decode('latin-1', 'replace') elif not isinstance(text, str): text = str(text) + # Ensure we're using a charset that can handle the text + if charset is None or charset.output_charset == 'ascii': + charset = Charset('utf-8') self.set_payload(text, charset) if subject is None: subject = '(no subject)' @@ -316,6 +319,9 @@ def __init__(self, recip, sender, subject=None, text=None, lang=None): subject = subject.decode('latin-1', 'replace') elif not isinstance(subject, str): subject = str(subject) + # Ensure we're using a charset that can handle the subject + if charset is None or charset.output_charset == 'ascii': + charset = Charset('utf-8') self['Subject'] = Header(subject, charset, header_name='Subject', errors='replace') self['From'] = sender diff --git a/Mailman/OldStyleMemberships.py b/Mailman/OldStyleMemberships.py index 00bf8462..ee657e59 100644 --- a/Mailman/OldStyleMemberships.py +++ b/Mailman/OldStyleMemberships.py @@ -25,6 +25,8 @@ """ import time +import re +import fnmatch from Mailman import mm_cfg from Mailman import Utils @@ -56,6 +58,36 @@ def __init__(self, mlist): # Initialize Autoresponder attributes self.InitVars() + def HasAutoApprovedSender(self, email): + """Check if the sender's email address is in the auto-approve list. + + Args: + email: The email address to check + + Returns: + bool: True if the sender is auto-approved, False otherwise + """ + # Check if the email is in the accept_these_nonmembers list + if email.lower() in [addr.lower() for addr in self.__mlist.accept_these_nonmembers]: + return True + + # Check if the email matches any patterns in accept_these_nonmembers + for pattern in self.__mlist.accept_these_nonmembers: + if pattern.startswith('^') or pattern.endswith('$'): + # This is a regex pattern + try: + if re.match(pattern, email, re.IGNORECASE): + return True + except re.error: + # Invalid regex pattern, skip it + continue + elif '*' in pattern or '?' in pattern: + # This is a glob pattern + if fnmatch.fnmatch(email.lower(), pattern.lower()): + return True + + return False + def GetMailmanHeader(self): """Return the standard Mailman header HTML for this list.""" return self.__mlist.GetMailmanHeader() @@ -117,13 +149,13 @@ def __get_cp_member(self, member): missing = [] val = self.__mlist.members.get(lcmember, missing) if val is not missing: - if type(val) == str: + if isinstance(val, str): return val, ISREGULAR else: return lcmember, ISREGULAR val = self.__mlist.digest_members.get(lcmember, missing) if val is not missing: - if type(val) == str: + if isinstance(val, str): return val, ISDIGEST else: return lcmember, ISDIGEST @@ -138,13 +170,13 @@ def isMember(self, member): def getMemberKey(self, member): cpaddr, where = self.__get_cp_member(member) if cpaddr is None: - raise Exception(Errors.NotAMemberError, member) + raise Errors.NotAMemberError(member) return member.lower() def getMemberCPAddress(self, member): cpaddr, where = self.__get_cp_member(member) if cpaddr is None: - raise Exception(Errors.NotAMemberError, member) + raise Errors.NotAMemberError(member) return cpaddr def getMemberCPAddresses(self, members): @@ -153,7 +185,7 @@ def getMemberCPAddresses(self, members): def getMemberPassword(self, member): secret = self.__mlist.passwords.get(member.lower()) if secret is None: - raise Exception(Errors.NotAMemberError, member) + raise Errors.NotAMemberError(member) return secret def authenticateMember(self, member, response): @@ -164,7 +196,7 @@ def authenticateMember(self, member, response): def __assertIsMember(self, member): if not self.isMember(member): - raise Exception(Errors.NotAMemberError, member) + raise Errors.NotAMemberError(member) def getMemberLanguage(self, member): lang = self.__mlist.language.get( @@ -236,7 +268,7 @@ def addNewMember(self, member, **kws): assert self.__mlist.Locked() # Make sure this address isn't already a member if self.isMember(member): - raise Exception(Errors.MMAlreadyAMember, member) + raise Errors.MMAlreadyAMember(member) # Parse the keywords digest = 0 password = Utils.MakeRandomPassword() From 1480bca91c9cf4270ebcfef1715c94308493dde8 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Wed, 21 May 2025 21:37:08 -0400 Subject: [PATCH 656/748] Fix UnboundLocalError in private.py by using list's preferred language as fallback --- Mailman/Cgi/private.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Mailman/Cgi/private.py b/Mailman/Cgi/private.py index 2018676c..ada0815c 100644 --- a/Mailman/Cgi/private.py +++ b/Mailman/Cgi/private.py @@ -240,7 +240,8 @@ def main(): 'realname': mlist.real_name, 'message': message } - output = mlist.ParseTags('private.html', replacements, lang) + # Use list's preferred language as fallback before authentication + output = mlist.ParseTags('private.html', replacements, mlist.preferred_language) print(output) return From 23508d6397ee227dba38dcd667ae469d6f1c5d7f Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Wed, 21 May 2025 21:40:57 -0400 Subject: [PATCH 657/748] Improve global_options function in options.py with better error handling and code clarity --- Mailman/Cgi/options.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Mailman/Cgi/options.py b/Mailman/Cgi/options.py index dba7031f..ec25bf48 100644 --- a/Mailman/Cgi/options.py +++ b/Mailman/Cgi/options.py @@ -1202,12 +1202,14 @@ def sigterm_handler(signum, frame, mlist=mlist): def global_options(mlist, user, globalopts): # Is there anything to do? + has_changes = False for attr in dir(globalopts): if attr.startswith('_'): continue if getattr(globalopts, attr) is not None: + has_changes = True break - else: + if not has_changes: return def sigterm_handler(signum, frame, mlist=mlist): @@ -1226,18 +1228,15 @@ def sigterm_handler(signum, frame, mlist=mlist): if globalopts.enable is not None: mlist.setDeliveryStatus(user, globalopts.enable) - if globalopts.remind is not None: mlist.setMemberOption(user, mm_cfg.SuppressPasswordReminder, - globalopts.remind) - + globalopts.remind) if globalopts.nodupes is not None: mlist.setMemberOption(user, mm_cfg.DontReceiveDuplicates, - globalopts.nodupes) - + globalopts.nodupes) if globalopts.mime is not None: - mlist.setMemberOption(user, mm_cfg.DisableMime, globalopts.mime) - + mlist.setMemberOption(user, mm_cfg.DisableMime, + globalopts.mime) mlist.Save() finally: mlist.Unlock() From ba6e12410e680c92ea70d08c25b157d8b740ebd8 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Wed, 21 May 2025 21:43:38 -0400 Subject: [PATCH 658/748] Fix reCAPTCHA error message formatting in subscribe.py --- Mailman/Cgi/subscribe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mailman/Cgi/subscribe.py b/Mailman/Cgi/subscribe.py index b970bd5f..4c39b6b1 100644 --- a/Mailman/Cgi/subscribe.py +++ b/Mailman/Cgi/subscribe.py @@ -210,7 +210,7 @@ def process_form(mlist, doc, cgidata, lang): httpresp.close() if not captcha_response['success']: e_codes = COMMASPACE.join(captcha_response['error-codes']) - results.append(_('reCAPTCHA validation failed: {e_codes}')) + results.append(_('reCAPTCHA validation failed: {}').format(e_codes)) except urllib.error.URLError as e: e_reason = e.reason results.append(_('reCAPTCHA could not be validated: {e_reason}')) From ccf82f7cc07753c6683c9589779925fa4b39a1eb Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Wed, 21 May 2025 21:47:21 -0400 Subject: [PATCH 659/748] Fix subscription data handling to use tuple format consistently --- Mailman/MailList.py | 5 +++-- Mailman/OldStyleMemberships.py | 21 ++++++++++++--------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/Mailman/MailList.py b/Mailman/MailList.py index f27590f6..e3b46fbb 100644 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -1124,7 +1124,8 @@ def InviteNewMember(self, userdesc, text=''): self.real_name, invitee, pattern) raise Errors.MembershipIsBanned(pattern) userdesc.invitation = self.internal_name() - cookie = self.pend_new(Pending.SUBSCRIPTION, userdesc) + cookie = self.pend_new(Pending.SUBSCRIPTION, + (userdesc, None)) requestaddr = self.getListAddress('request') confirmurl = '%s/%s' % (self.GetScriptURL('confirm', absolute=1), cookie) listname = self.real_name @@ -1172,7 +1173,7 @@ def AddMember(self, userdesc, remote=None): if self.subscribe_policy in (2, 3) and not self.HasAutoApprovedSender(email): # Pend the subscription cookie = self.pend_new(Pending.SUBSCRIPTION, - userdesc, remote) + (userdesc, remote)) confirmurl = '%s/%s' % (self.GetScriptURL('confirm', absolute=1), cookie) lang = userdesc.language diff --git a/Mailman/OldStyleMemberships.py b/Mailman/OldStyleMemberships.py index ee657e59..9d86a8b8 100644 --- a/Mailman/OldStyleMemberships.py +++ b/Mailman/OldStyleMemberships.py @@ -505,21 +505,24 @@ def ProcessConfirmation(self, cookie, msg): # Process based on action type if action == Pending.SUBSCRIPTION: + # Extract userdesc and remote from data + userdesc, remote = data + # Check if already a member - if self.isMember(data['email']): - raise Errors.MMAlreadyAMember(data['email']) + if self.isMember(userdesc.address): + raise Errors.MMAlreadyAMember(userdesc.address) # Check if banned - if self.__mlist.isBanned(data['email']): - raise Errors.MembershipIsBanned(data['email']) + if self.__mlist.isBanned(userdesc.address): + raise Errors.MembershipIsBanned(userdesc.address) # Add the member self.addNewMember( - data['email'], - digest=data.get('digest', 0), - password=data.get('password', Utils.MakeRandomPassword()), - language=data.get('language', self.__mlist.preferred_language), - realname=data.get('realname', '') + userdesc.address, + digest=userdesc.digest, + password=userdesc.password, + language=userdesc.language, + realname=userdesc.fullname ) elif action == Pending.UNSUBSCRIPTION: From 48441e3eb2ea5f8808afc45d55e199c4beccc502 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 22 May 2025 19:23:40 -0400 Subject: [PATCH 660/748] Enhance Exchange bounce detector to better handle Office 365 format --- Mailman/Bouncers/Exchange.py | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/Mailman/Bouncers/Exchange.py b/Mailman/Bouncers/Exchange.py index bd2d3242..68bc6fa6 100644 --- a/Mailman/Bouncers/Exchange.py +++ b/Mailman/Bouncers/Exchange.py @@ -26,10 +26,13 @@ from Mailman.Logging.Syslog import syslog from Mailman.Handlers.CookHeaders import change_header -scre = re.compile('did not reach the following recipient') -ecre = re.compile('MSEXCH:') +# Patterns for different Exchange/Office 365 bounce formats +scre = re.compile('did not reach the following recipient|Your message to .* couldn\'t be delivered') +ecre = re.compile('MSEXCH:|Action Required') a1cre = re.compile('SMTP=(?P[^;]+); on ') a2cre = re.compile('(?P[^ ]+) on ') +a3cre = re.compile('Your message to (?P[^ ]+) couldn\'t be delivered') +a4cre = re.compile('(?P[^ ]+) wasn\'t found at ') def process(msg): @@ -45,9 +48,18 @@ def process(msg): for line in it: if ecre.search(line): break - mo = a1cre.search(line) - if not mo: - mo = a2cre.search(line) - if mo: - addrs[mo.group('addr')] = 1 + # Try all patterns + for pattern in [a1cre, a2cre, a3cre, a4cre]: + mo = pattern.search(line) + if mo: + addr = mo.group('addr') + # Clean up the address if needed + if '@' not in addr and 'at' in line: + # Handle cases where domain is on next line + next_line = next(it, '') + if 'at' in next_line: + domain = next_line.split('at')[-1].strip() + addr = f"{addr}@{domain}" + addrs[addr] = 1 + break return list(addrs.keys()) From 49ae0ebef6bcea28798efaf32ffff10404425ab7 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 22 May 2025 19:26:34 -0400 Subject: [PATCH 661/748] Fix template interpolation in admlogin.html to use % style formatting --- templates/en/admlogin.html | 1 + 1 file changed, 1 insertion(+) diff --git a/templates/en/admlogin.html b/templates/en/admlogin.html index 797f656c..ef789347 100755 --- a/templates/en/admlogin.html +++ b/templates/en/admlogin.html @@ -1,3 +1,4 @@ + %(listname)s %(who)s Authentication From 9fccab368c6cc34d84029ac001e2d9fd7774c5c7 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 22 May 2025 19:28:41 -0400 Subject: [PATCH 662/748] Fix template interpolation in admlogin.html to use % style formatting --- Mailman/ListAdmin.py | 61 +++++++++++++++++------------------ Mailman/Queue/BounceRunner.py | 59 +++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 31 deletions(-) diff --git a/Mailman/ListAdmin.py b/Mailman/ListAdmin.py index e099444a..cda53d82 100644 --- a/Mailman/ListAdmin.py +++ b/Mailman/ListAdmin.py @@ -553,37 +553,36 @@ def HoldSubscription(self, addr, fullname, password, digest, lang): # Restore the user's preferred language. i18n.set_language(lang) - def __handlesubscription(self, record, value, comment): - global _ - stime, addr, fullname, password, digest, lang = record - if value == mm_cfg.DEFER: - return DEFER - elif value == mm_cfg.DISCARD: - mailman_log('vette', '%s: discarded subscription request from %s', - self.internal_name(), addr) + def __handlesubscription(self, data, value, comment): + """Handle a subscription request. + + Args: + data: A tuple of (userdesc, remote) where userdesc is a UserDesc object + and remote is the remote address making the request + value: The action to take (APPROVE, DEFER, REJECT) + comment: Optional comment for the action + + Returns: + The status of the action (APPROVE, DEFER, REJECT) + """ + userdesc, remote = data + if value == mm_cfg.APPROVE: + self.ApprovedAddMember(userdesc, whence=remote or '') + return mm_cfg.APPROVE elif value == mm_cfg.REJECT: - self.__refuse(_('Subscription request'), addr, - comment or _('[No reason given]'), - lang=lang) - mailman_log('vette', """%s: rejected subscription request from %s -\tReason: %s""", self.internal_name(), addr, comment or '[No reason given]') - else: - # subscribe - if value != mm_cfg.SUBSCRIBE: - raise ValueError(f'Invalid value: {value}, expected {mm_cfg.SUBSCRIBE}') - try: - _ = D_ - whence = _('via admin approval') - _ = i18n._ - userdesc = UserDesc(addr, fullname, password, digest, lang) - self.ApprovedAddMember(userdesc, whence=whence) - except Errors.MMAlreadyAMember: - # User has already been subscribed, after sending the request - pass - # TBD: disgusting hack: ApprovedAddMember() can end up closing - # the request database. - self.__opendb() - return REMOVE + # Send rejection notice + lang = userdesc.language + text = Utils.maketext( + 'reject.txt', + {'listname': self.real_name, + 'comment': comment or '', + }, lang=lang, mlist=self) + msg = Message.UserNotification( + userdesc.address, self.GetRequestEmail(), + text=text, lang=lang) + msg.send(self) + return mm_cfg.REJECT + return mm_cfg.DEFER def HoldUnsubscription(self, addr): # Assure the database is open for writing @@ -618,7 +617,7 @@ def HoldUnsubscription(self, addr): def __handleunsubscription(self, record, value, comment): addr = record if value == mm_cfg.DEFER: - return DEFER + return mm_cfg.DEFER elif value == mm_cfg.DISCARD: mailman_log('vette', '%s: discarded unsubscription request from %s', self.internal_name(), addr) diff --git a/Mailman/Queue/BounceRunner.py b/Mailman/Queue/BounceRunner.py index 44014186..c07ffa91 100644 --- a/Mailman/Queue/BounceRunner.py +++ b/Mailman/Queue/BounceRunner.py @@ -65,6 +65,65 @@ def __init__(self): syslog('debug', 'BounceMixin: Initialized with next action time: %s', time.ctime(self._next_action)) + def _process_bounces(self): + """Process pending bounces.""" + try: + syslog('debug', 'BounceMixin._process_bounces: Starting bounce processing') + + # Get all lists + listnames = Utils.list_names() + for listname in listnames: + try: + mlist = get_mail_list()(listname, lock=0) + try: + # Process bounces for this list + self._process_list_bounces(mlist) + finally: + mlist.Unlock() + except Exception as e: + syslog('error', 'BounceMixin._process_bounces: Error processing list %s: %s', + listname, str(e)) + continue + + syslog('debug', 'BounceMixin._process_bounces: Completed bounce processing') + + except Exception as e: + syslog('error', 'BounceMixin._process_bounces: Error during bounce processing: %s\nTraceback:\n%s', + str(e), traceback.format_exc()) + + def _process_list_bounces(self, mlist): + """Process bounces for a specific list.""" + try: + syslog('debug', 'BounceMixin._process_list_bounces: Processing bounces for list %s', + mlist.internal_name()) + + # Get all bouncing members + bouncing_members = mlist.getBouncingMembers() + for member in bouncing_members: + try: + # Get bounce info for this member + info = mlist.getBounceInfo(member) + if not info: + continue + + # Check if member should be disabled + if info.score >= mlist.bounce_score_threshold: + syslog('debug', 'BounceMixin._process_list_bounces: Disabling member %s due to bounce score %f', + member, info.score) + mlist.disableBouncingMember(member, info) + + except Exception as e: + syslog('error', 'BounceMixin._process_list_bounces: Error processing member %s: %s', + member, str(e)) + continue + + syslog('debug', 'BounceMixin._process_list_bounces: Completed processing bounces for list %s', + mlist.internal_name()) + + except Exception as e: + syslog('error', 'BounceMixin._process_list_bounces: Error processing list %s: %s\nTraceback:\n%s', + mlist.internal_name(), str(e), traceback.format_exc()) + def _register_bounces(self, mlist, bounces): """Register bounce information for a list.""" try: From d42e0c1baf86e7d19b61712d3603a42226ce1e7f Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 22 May 2025 19:35:56 -0400 Subject: [PATCH 663/748] Fix queue file handling to prevent truncation and improve reliability --- Mailman/Queue/Switchboard.py | 70 ++++++++++++++++++++++++++++-------- 1 file changed, 55 insertions(+), 15 deletions(-) diff --git a/Mailman/Queue/Switchboard.py b/Mailman/Queue/Switchboard.py index 2b91a672..c7b88c0d 100644 --- a/Mailman/Queue/Switchboard.py +++ b/Mailman/Queue/Switchboard.py @@ -201,6 +201,7 @@ def dequeue(self, filebase): filename = os.path.join(self.__whichq, filebase + '.pck') bakfile = os.path.join(self.__whichq, filebase + '.bak') psvfile = os.path.join(self.__whichq, filebase + '.psv') + lockfile = filename + '.lock' # Check if file exists before proceeding if not os.path.exists(filename): @@ -213,23 +214,62 @@ def dequeue(self, filebase): mailman_log('warning', 'Queue file does not exist: %s (not found in backup or shunt either)', filename) return None, None - # Read the message object and metadata. - fp = open(filename, 'rb') - # Move the file to the backup file name for processing. If this - # process crashes uncleanly the .bak file will be used to re-instate - # the .pck file in order to try again. - os.rename(filename, bakfile) + # Create a lock file + try: + lock_fd = os.open(lockfile, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600) + os.close(lock_fd) + except OSError as e: + if e.errno == errno.EEXIST: + mailman_log('warning', 'Lock file exists for %s (full path: %s)', filename, lockfile) + return None, None + else: + mailman_log('error', 'Failed to create lock file %s (full path: %s): %s', filename, lockfile, str(e)) + return None, None + try: - msg = pickle.load(fp, fix_imports=True, encoding='latin1') - data = pickle.load(fp, fix_imports=True, encoding='latin1') + # First read the file contents + try: + with open(filename, 'rb') as fp: + content = fp.read() + if not content: + mailman_log('error', 'Empty queue file: %s', filename) + return None, None + + # Create a BytesIO object to read from the content + from io import BytesIO + fp = BytesIO(content) + + try: + msg = pickle.load(fp, fix_imports=True, encoding='latin1') + data = pickle.load(fp, fix_imports=True, encoding='latin1') + except (EOFError, pickle.UnpicklingError) as e: + mailman_log('error', 'Error loading queue file %s: %s', filename, str(e)) + return None, None + except (IOError, OSError) as e: + mailman_log('error', 'Error reading queue file %s: %s', filename, str(e)) + return None, None + + # Now that we've successfully read the file, move it to backup + try: + os.rename(filename, bakfile) + except (IOError, OSError) as e: + mailman_log('error', 'Error moving queue file %s to backup: %s', filename, str(e)) + return None, None + + if data.get('_parsemsg'): + msg = email.message_from_string(msg, Message) + # Add filebase to msgdata for cleanup + if data is not None: + data['filebase'] = filebase + return msg, data + finally: - fp.close() - if data.get('_parsemsg'): - msg = email.message_from_string(msg, Message) - # Add filebase to msgdata for cleanup - if data is not None: - data['filebase'] = filebase - return msg, data + # Always clean up the lock file + try: + if os.path.exists(lockfile): + os.unlink(lockfile) + except OSError: + pass def finish(self, filebase, preserve=False): """Finish processing a file by either removing it or moving it to the shunt queue. From ad47d13f35e2c9728c67a92c6e69d3677784ba8e Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 22 May 2025 19:43:15 -0400 Subject: [PATCH 664/748] Enhance OutgoingRunner error handling and logging to better diagnose restart issues --- Mailman/Queue/OutgoingRunner.py | 215 ++++++++++++++++++++------------ 1 file changed, 138 insertions(+), 77 deletions(-) diff --git a/Mailman/Queue/OutgoingRunner.py b/Mailman/Queue/OutgoingRunner.py index 943b6822..722199d7 100644 --- a/Mailman/Queue/OutgoingRunner.py +++ b/Mailman/Queue/OutgoingRunner.py @@ -66,6 +66,12 @@ class OutgoingRunner(Runner, BounceMixin): MIN_RETRY_DELAY = 300 # 5 minutes minimum delay between retries MAX_RETRIES = 5 # Maximum number of retry attempts _retry_times = {} # Track last retry time for each message + + # Error tracking + _error_count = 0 + _last_error_time = 0 + _error_window = 300 # 5 minutes window for error counting + _max_errors = 10 # Maximum errors before stopping def __init__(self, slice=None, numslices=1): mailman_log('debug', 'OutgoingRunner: Starting initialization') @@ -80,6 +86,10 @@ def __init__(self, slice=None, numslices=1): self._processed_messages = set() self._last_cleanup = time.time() + # Initialize error tracking + self._error_count = 0 + self._last_error_time = 0 + # We look this function up only at startup time modname = 'Mailman.Handlers.' + mm_cfg.DELIVERY_MODULE mailman_log('debug', 'OutgoingRunner: Attempting to import delivery module: %s', modname) @@ -367,91 +377,98 @@ def _process_regular(self, mlist, msg, msgdata): """Process a regular outgoing message.""" msgid = msg.get('message-id', 'n/a') - # Get recipient from msgdata or message headers - recipient = msgdata.get('recipient') - if not recipient: - # Try to get recipient from To header - to = msg.get('to') - if to: - # Parse the To header to get the first recipient - addrs = email.utils.getaddresses([to]) - if addrs: - recipient = addrs[0][1] + try: + # Get recipient from msgdata or message headers + recipient = msgdata.get('recipient') + if not recipient: + # Try to get recipient from To header + to = msg.get('to') + if to: + # Parse the To header to get the first recipient + addrs = email.utils.getaddresses([to]) + if addrs: + recipient = addrs[0][1] - if not recipient: - mailman_log('error', 'OutgoingRunner: No recipients found in msgdata for message: %s', msgid) - return False + if not recipient: + mailman_log('error', 'OutgoingRunner: No recipients found in msgdata for message: %s', msgid) + return self._handle_error(ValueError('No recipients found'), msg, mlist) + + # Set the recipient in msgdata for future use + msgdata['recipient'] = recipient + + # For system messages (_nolist=1), we need to handle them differently + if msgdata.get('_nolist'): + mailman_log('debug', 'OutgoingRunner._process_regular: Processing system message %s', msgid) + # System messages should be sent directly via SMTP + try: + conn = self._get_smtp_connection() + if not conn: + mailman_log('error', 'OutgoingRunner._process_regular: Failed to get SMTP connection for message %s', msgid) + return self._handle_error(ConnectionError('Failed to get SMTP connection'), msg, mlist) + + # Send the message + sender = msg.get('from', msgdata.get('original_sender', mm_cfg.MAILMAN_SITE_LIST)) + if not sender or not '@' in sender: + sender = mm_cfg.MAILMAN_SITE_LIST + + mailman_log('debug', 'OutgoingRunner._process_regular: Sending system message %s from %s to %s', + msgid, sender, recipient) + + conn.sendmail(sender, [recipient], str(msg)) + conn.quit() + + mailman_log('debug', 'OutgoingRunner._process_regular: Successfully sent system message %s', msgid) + return True + + except Exception as e: + mailman_log('error', 'OutgoingRunner._process_regular: SMTP error for system message %s: %s', + msgid, str(e)) + return self._handle_error(e, msg, mlist) + + # For regular list messages, use the delivery module + mailman_log('debug', 'OutgoingRunner._process_regular: Using delivery module for message %s', msgid) - # Set the recipient in msgdata for future use - msgdata['recipient'] = recipient + # Log the state before calling the delivery module + mailman_log('debug', 'OutgoingRunner._process_regular: Pre-delivery msgdata:\n%s', str(msgdata)) + + # Ensure we have the list members if this is a list message + if msgdata.get('tolist') and not msgdata.get('_nolist'): + try: + # Get all list members + members = mlist.getRegularMemberKeys() + if members: + msgdata['recips'] = [mlist.getMemberCPAddress(m) for m in members + if mlist.getDeliveryStatus(m) == ENABLED] + mailman_log('debug', 'OutgoingRunner._process_regular: Expanded list members for message %s: %s', + msgid, str(msgdata['recips'])) + else: + mailman_log('error', 'OutgoingRunner._process_regular: No members found for list %s', + mlist.internal_name()) + return self._handle_error(ValueError('No list members found'), msg, mlist) + except Exception as e: + mailman_log('error', 'OutgoingRunner._process_regular: Error getting list members: %s\nTraceback:\n%s', + str(e), traceback.format_exc()) + # Try to continue with existing recipients if any + if not msgdata.get('recips'): + mailman_log('error', 'OutgoingRunner._process_regular: No recipients available for message %s', msgid) + return self._handle_error(ValueError('No recipients available'), msg, mlist) - # For system messages (_nolist=1), we need to handle them differently - if msgdata.get('_nolist'): - mailman_log('debug', 'OutgoingRunner._process_regular: Processing system message %s', msgid) - # System messages should be sent directly via SMTP + # Call the delivery module try: - conn = self._get_smtp_connection() - if not conn: - mailman_log('error', 'OutgoingRunner._process_regular: Failed to get SMTP connection for message %s', msgid) - return False - - # Send the message - sender = msg.get('from', msgdata.get('original_sender', mm_cfg.MAILMAN_SITE_LIST)) - if not sender or not '@' in sender: - sender = mm_cfg.MAILMAN_SITE_LIST - - mailman_log('debug', 'OutgoingRunner._process_regular: Sending system message %s from %s to %s', - msgid, sender, recipient) - - conn.sendmail(sender, [recipient], str(msg)) - conn.quit() - - mailman_log('debug', 'OutgoingRunner._process_regular: Successfully sent system message %s', msgid) + self._func(mlist, msg, msgdata) + # Log the state after calling the delivery module + mailman_log('debug', 'OutgoingRunner._process_regular: Post-delivery msgdata:\n%s', str(msgdata)) + mailman_log('debug', 'OutgoingRunner._process_regular: Successfully processed regular message %s', msgid) return True - except Exception as e: - mailman_log('error', 'OutgoingRunner._process_regular: SMTP error for system message %s: %s', - msgid, str(e)) - return False - - # For regular list messages, use the delivery module - mailman_log('debug', 'OutgoingRunner._process_regular: Using delivery module for message %s', msgid) - - # Log the state before calling the delivery module - mailman_log('debug', 'OutgoingRunner._process_regular: Pre-delivery msgdata:\n%s', str(msgdata)) - - # Ensure we have the list members if this is a list message - if msgdata.get('tolist') and not msgdata.get('_nolist'): - try: - # Get all list members - members = mlist.getRegularMemberKeys() - if members: - msgdata['recips'] = [mlist.getMemberCPAddress(m) for m in members - if mlist.getDeliveryStatus(m) == ENABLED] - mailman_log('debug', 'OutgoingRunner._process_regular: Expanded list members for message %s: %s', - msgid, str(msgdata['recips'])) - else: - mailman_log('error', 'OutgoingRunner._process_regular: No members found for list %s', - mlist.internal_name()) - except Exception as e: - mailman_log('error', 'OutgoingRunner._process_regular: Error getting list members: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - # Try to continue with existing recipients if any - if not msgdata.get('recips'): - mailman_log('error', 'OutgoingRunner._process_regular: No recipients available for message %s', msgid) - return False - - # Call the delivery module - try: - self._func(mlist, msg, msgdata) - # Log the state after calling the delivery module - mailman_log('debug', 'OutgoingRunner._process_regular: Post-delivery msgdata:\n%s', str(msgdata)) - mailman_log('debug', 'OutgoingRunner._process_regular: Successfully processed regular message %s', msgid) - return True + mailman_log('error', 'OutgoingRunner._process_regular: Error in delivery module: %s\nTraceback:\n%s', + str(e), traceback.format_exc()) + return self._handle_error(e, msg, mlist) + except Exception as e: - mailman_log('error', 'OutgoingRunner._process_regular: Error in delivery module: %s\nTraceback:\n%s', + mailman_log('error', 'OutgoingRunner._process_regular: Unexpected error: %s\nTraceback:\n%s', str(e), traceback.format_exc()) - return False + return self._handle_error(e, msg, mlist) def _check_retry_delay(self, msgid, filebase): """Check if enough time has passed since the last retry attempt.""" @@ -564,3 +581,47 @@ def _oneloop(self): except Exception as e: mailman_log('error', 'OutgoingRunner._oneloop: Error processing outgoing queue: %s', str(e)) mailman_log('error', 'OutgoingRunner._oneloop: Traceback: %s', traceback.format_exc()) + + def _handle_error(self, exc, msg=None, mlist=None, preserve=True): + """Enhanced error handling with circuit breaker and detailed logging.""" + now = time.time() + msgid = msg.get('message-id', 'n/a') if msg else 'n/a' + + # Log the error with full context + mailman_log('error', 'OutgoingRunner: Error processing message %s: %s', msgid, str(exc)) + mailman_log('error', 'OutgoingRunner: Error type: %s', type(exc).__name__) + + # Log full traceback + s = StringIO() + traceback.print_exc(file=s) + mailman_log('error', 'OutgoingRunner: Traceback:\n%s', s.getvalue()) + + # Log system state + mailman_log('error', 'OutgoingRunner: System state - SMTP host: %s, port: %s, auth: %s', + mm_cfg.SMTPHOST, mm_cfg.SMTPPORT, mm_cfg.SMTP_AUTH) + + # Circuit breaker logic + if now - self._last_error_time < self._error_window: + self._error_count += 1 + if self._error_count >= self._max_errors: + mailman_log('error', 'OutgoingRunner: Too many errors (%d) in %d seconds, stopping runner', + self._error_count, self._error_window) + # Log stack trace before stopping + s = StringIO() + traceback.print_stack(file=s) + mailman_log('error', 'OutgoingRunner: Stack trace at stop:\n%s', s.getvalue()) + self.stop() + else: + self._error_count = 1 + self._last_error_time = now + + # Handle message preservation + if preserve and msg: + try: + msgdata = {'whichq': self._switchboard.whichq()} + new_filebase = self._shunt.enqueue(msg, msgdata) + mailman_log('error', 'OutgoingRunner: Shunted message to: %s', new_filebase) + except Exception as e: + mailman_log('error', 'OutgoingRunner: Failed to shunt message: %s', str(e)) + return False + return True From 7d29d5af10d6fe8a8548b83417de383bf93331f4 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 22 May 2025 19:46:25 -0400 Subject: [PATCH 665/748] Reduce log noise by changing delivery module import messages to trace level --- Mailman/Queue/OutgoingRunner.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Mailman/Queue/OutgoingRunner.py b/Mailman/Queue/OutgoingRunner.py index 722199d7..1cba1932 100644 --- a/Mailman/Queue/OutgoingRunner.py +++ b/Mailman/Queue/OutgoingRunner.py @@ -92,11 +92,11 @@ def __init__(self, slice=None, numslices=1): # We look this function up only at startup time modname = 'Mailman.Handlers.' + mm_cfg.DELIVERY_MODULE - mailman_log('debug', 'OutgoingRunner: Attempting to import delivery module: %s', modname) + mailman_log('trace', 'OutgoingRunner: Attempting to import delivery module: %s', modname) try: mod = __import__(modname) - mailman_log('debug', 'OutgoingRunner: Successfully imported delivery module') + mailman_log('trace', 'OutgoingRunner: Successfully imported delivery module') except ImportError as e: mailman_log('error', 'OutgoingRunner: Failed to import delivery module %s: %s', modname, str(e)) mailman_log('error', 'OutgoingRunner: Traceback: %s', traceback.format_exc()) @@ -104,7 +104,7 @@ def __init__(self, slice=None, numslices=1): try: self._func = getattr(sys.modules[modname], 'process') - mailman_log('debug', 'OutgoingRunner: Successfully got process function from module') + mailman_log('trace', 'OutgoingRunner: Successfully got process function from module') except AttributeError as e: mailman_log('error', 'OutgoingRunner: Failed to get process function from module %s: %s', modname, str(e)) mailman_log('error', 'OutgoingRunner: Traceback: %s', traceback.format_exc()) From d0df5aae2825372c4bcad827911c97ce320f2d29 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 22 May 2025 19:47:44 -0400 Subject: [PATCH 666/748] Reduce log noise by changing standard replacements message to trace level --- Mailman/HTMLFormatter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mailman/HTMLFormatter.py b/Mailman/HTMLFormatter.py index 1080cb9c..9e0e6522 100644 --- a/Mailman/HTMLFormatter.py +++ b/Mailman/HTMLFormatter.py @@ -504,7 +504,7 @@ def GetStandardReplacements(self, lang=None, replacements=None): if mm_cfg.IMAGE_LOGOS: replacements[''] = mm_cfg.IMAGE_LOGOS + mm_cfg.SHORTCUT_ICON - mailman_log('debug', 'Added %d standard replacements', len(replacements)) + mailman_log('trace', 'Added %d standard replacements', len(replacements)) except Exception as e: mailman_log('error', 'Error getting standard replacements: %s', str(e)) From 6bb4fee263dac238d530a2d176e6b7c7cd247522 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 22 May 2025 19:57:12 -0400 Subject: [PATCH 667/748] Add message counting and enhanced error tracking to OutgoingRunner --- Mailman/Queue/OutgoingRunner.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Mailman/Queue/OutgoingRunner.py b/Mailman/Queue/OutgoingRunner.py index 1cba1932..58d427ac 100644 --- a/Mailman/Queue/OutgoingRunner.py +++ b/Mailman/Queue/OutgoingRunner.py @@ -62,6 +62,10 @@ class OutgoingRunner(Runner, BounceMixin): _max_processed_messages = 10000 _max_retry_times = 10000 + # Message counting + _total_messages_processed = 0 + _total_messages_lock = threading.Lock() + # Retry configuration MIN_RETRY_DELAY = 300 # 5 minutes minimum delay between retries MAX_RETRIES = 5 # Maximum number of retry attempts @@ -71,7 +75,7 @@ class OutgoingRunner(Runner, BounceMixin): _error_count = 0 _last_error_time = 0 _error_window = 300 # 5 minutes window for error counting - _max_errors = 10 # Maximum errors before stopping + _max_errors = 10 def __init__(self, slice=None, numslices=1): mailman_log('debug', 'OutgoingRunner: Starting initialization') @@ -519,6 +523,9 @@ def _cleanup(self): Runner._cleanup(self) self._cleanup_old_messages() self._cleanup_resources(None, {}) + # Log total messages processed + with self._total_messages_lock: + mailman_log('debug', 'OutgoingRunner: Total messages processed: %d', self._total_messages_processed) except Exception as e: mailman_log('error', 'Cleanup failed: %s', str(e)) mailman_log('debug', 'OutgoingRunner: Cleanup complete') @@ -571,6 +578,9 @@ def _oneloop(self): # Process the message try: self._dispose(mlist, msg, msgdata) + # Increment message counter on successful processing + with self._total_messages_lock: + self._total_messages_processed += 1 except Exception as e: mailman_log('error', 'OutgoingRunner._oneloop: Error processing message %s: %s\nTraceback:\n%s', msg.get('message-id', 'n/a'), str(e), traceback.format_exc()) From dce6217ec2930533478516a1c00d6b07d056a6fb Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 22 May 2025 19:59:59 -0400 Subject: [PATCH 668/748] Add process coordination to prevent multiple OutgoingRunner instances --- Mailman/Queue/OutgoingRunner.py | 53 +++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/Mailman/Queue/OutgoingRunner.py b/Mailman/Queue/OutgoingRunner.py index 58d427ac..bc7f166e 100644 --- a/Mailman/Queue/OutgoingRunner.py +++ b/Mailman/Queue/OutgoingRunner.py @@ -54,6 +54,10 @@ def get_replybot(): class OutgoingRunner(Runner, BounceMixin): QDIR = mm_cfg.OUTQUEUE_DIR + # Process coordination + _pid_file = os.path.join(mm_cfg.LOCK_DIR, 'outgoing.pid') + _pid_lock = threading.Lock() + # Shared processed messages tracking with size limits _processed_messages = set() _processed_lock = threading.Lock() @@ -80,6 +84,11 @@ class OutgoingRunner(Runner, BounceMixin): def __init__(self, slice=None, numslices=1): mailman_log('debug', 'OutgoingRunner: Starting initialization') try: + # Check if another instance is already running + if not self._acquire_pid_lock(): + mailman_log('error', 'OutgoingRunner: Another instance is already running') + raise RuntimeError('Another OutgoingRunner instance is already running') + Runner.__init__(self, slice, numslices) mailman_log('debug', 'OutgoingRunner: Base Runner initialized') @@ -104,6 +113,7 @@ def __init__(self, slice=None, numslices=1): except ImportError as e: mailman_log('error', 'OutgoingRunner: Failed to import delivery module %s: %s', modname, str(e)) mailman_log('error', 'OutgoingRunner: Traceback: %s', traceback.format_exc()) + self._release_pid_lock() raise try: @@ -112,6 +122,7 @@ def __init__(self, slice=None, numslices=1): except AttributeError as e: mailman_log('error', 'OutgoingRunner: Failed to get process function from module %s: %s', modname, str(e)) mailman_log('error', 'OutgoingRunner: Traceback: %s', traceback.format_exc()) + self._release_pid_lock() raise # This prevents smtp server connection problems from filling up the @@ -124,8 +135,47 @@ def __init__(self, slice=None, numslices=1): except Exception as e: mailman_log('error', 'OutgoingRunner: Initialization failed: %s', str(e)) mailman_log('error', 'OutgoingRunner: Traceback: %s', traceback.format_exc()) + self._release_pid_lock() raise + def _acquire_pid_lock(self): + """Try to acquire the PID lock file.""" + try: + with self._pid_lock: + if os.path.exists(self._pid_file): + # Check if the process is still running + try: + with open(self._pid_file, 'r') as f: + pid = int(f.read().strip()) + # Check if process exists + try: + os.kill(pid, 0) + # Process exists, can't acquire lock + return False + except OSError: + # Process doesn't exist, can acquire lock + pass + except (ValueError, IOError): + # Invalid PID file, can acquire lock + pass + + # Write our PID to the lock file + with open(self._pid_file, 'w') as f: + f.write(str(os.getpid())) + return True + except Exception as e: + mailman_log('error', 'OutgoingRunner: Error acquiring PID lock: %s', str(e)) + return False + + def _release_pid_lock(self): + """Release the PID lock file.""" + try: + with self._pid_lock: + if os.path.exists(self._pid_file): + os.unlink(self._pid_file) + except Exception as e: + mailman_log('error', 'OutgoingRunner: Error releasing PID lock: %s', str(e)) + def _unmark_message_processed(self, msgid): """Remove a message from the processed messages set.""" with self._processed_lock: @@ -528,6 +578,9 @@ def _cleanup(self): mailman_log('debug', 'OutgoingRunner: Total messages processed: %d', self._total_messages_processed) except Exception as e: mailman_log('error', 'Cleanup failed: %s', str(e)) + finally: + # Always release the PID lock during cleanup + self._release_pid_lock() mailman_log('debug', 'OutgoingRunner: Cleanup complete') _doperiodic = BounceMixin._doperiodic From beeab0f6934ff355bbf7f5a58873f5726ddca82d Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 22 May 2025 20:02:09 -0400 Subject: [PATCH 669/748] Fix OutgoingRunner main loop and process coordination --- Mailman/Queue/OutgoingRunner.py | 37 +++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/Mailman/Queue/OutgoingRunner.py b/Mailman/Queue/OutgoingRunner.py index bc7f166e..7a49ba96 100644 --- a/Mailman/Queue/OutgoingRunner.py +++ b/Mailman/Queue/OutgoingRunner.py @@ -57,6 +57,7 @@ class OutgoingRunner(Runner, BounceMixin): # Process coordination _pid_file = os.path.join(mm_cfg.LOCK_DIR, 'outgoing.pid') _pid_lock = threading.Lock() + _running = False # Shared processed messages tracking with size limits _processed_messages = set() @@ -131,6 +132,7 @@ def __init__(self, slice=None, numslices=1): self.__logged = False mailman_log('debug', 'OutgoingRunner: Initializing retry queue') self.__retryq = Switchboard(mm_cfg.RETRYQUEUE_DIR) + self._running = True mailman_log('debug', 'OutgoingRunner: Initialization complete') except Exception as e: mailman_log('error', 'OutgoingRunner: Initialization failed: %s', str(e)) @@ -138,6 +140,37 @@ def __init__(self, slice=None, numslices=1): self._release_pid_lock() raise + def run(self): + """Run the OutgoingRunner main loop.""" + if not self._running: + mailman_log('error', 'OutgoingRunner: Not properly initialized') + return + + mailman_log('debug', 'OutgoingRunner: Starting main loop') + try: + while self._running and not self._stop: + try: + self._oneloop() + # Sleep briefly to prevent CPU spinning + time.sleep(1) + except Exception as e: + mailman_log('error', 'OutgoingRunner: Error in main loop: %s', str(e)) + mailman_log('error', 'OutgoingRunner: Traceback: %s', traceback.format_exc()) + # Don't exit on error, just continue the loop + time.sleep(5) # Sleep longer on error + except Exception as e: + mailman_log('error', 'OutgoingRunner: Fatal error in main loop: %s', str(e)) + mailman_log('error', 'OutgoingRunner: Traceback: %s', traceback.format_exc()) + finally: + self._cleanup() + + def stop(self): + """Stop the OutgoingRunner.""" + mailman_log('debug', 'OutgoingRunner: Stopping') + self._running = False + self._stop = True + self._cleanup() + def _acquire_pid_lock(self): """Try to acquire the PID lock file.""" try: @@ -567,6 +600,9 @@ def _validate_bounce(self, recip, code, errmsg): def _cleanup(self): """Clean up resources.""" + if not self._running: + return + mailman_log('debug', 'OutgoingRunner: Starting cleanup') try: BounceMixin._cleanup(self) @@ -581,6 +617,7 @@ def _cleanup(self): finally: # Always release the PID lock during cleanup self._release_pid_lock() + self._running = False mailman_log('debug', 'OutgoingRunner: Cleanup complete') _doperiodic = BounceMixin._doperiodic From a10ebd3564faace4babb0f148bf8c1e0106a9af2 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 22 May 2025 20:03:06 -0400 Subject: [PATCH 670/748] Reduce OutgoingRunner instances to 1 since we now have process coordination --- Mailman/Defaults.py.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Mailman/Defaults.py.in b/Mailman/Defaults.py.in index 91c6656d..d62f0c4f 100755 --- a/Mailman/Defaults.py.in +++ b/Mailman/Defaults.py.in @@ -938,10 +938,10 @@ QRUNNERS = [ ('CommandRunner', 1), # commands and bounces from the outside world ('IncomingRunner', 4), # posts from the outside world ('NewsRunner', 1), # outgoing messages to the nntpd - ('OutgoingRunner', 8), # outgoing messages to the smtpd + ('OutgoingRunner', 1), # outgoing messages to the smtpd (single instance with process coordination) ('VirginRunner', 1), # internally crafted (virgin birth) messages ('RetryRunner', 1), # retry temporarily failed deliveries - ] +] # Set this to Yes to use the `Maildir' delivery option. If you change this # you will need to re-run bin/genaliases for MTAs that don't use list From 7b3926a6f92baf94cee1f5b3fe8e230796f3610c Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 22 May 2025 20:05:57 -0400 Subject: [PATCH 671/748] Enhanced OutgoingRunner with improved error handling, logging, and message processing --- Mailman/Queue/OutgoingRunner.py | 200 ++++++++++++++------------------ 1 file changed, 89 insertions(+), 111 deletions(-) diff --git a/Mailman/Queue/OutgoingRunner.py b/Mailman/Queue/OutgoingRunner.py index 7a49ba96..d09ce532 100644 --- a/Mailman/Queue/OutgoingRunner.py +++ b/Mailman/Queue/OutgoingRunner.py @@ -27,6 +27,7 @@ from io import StringIO import threading import email.message +import fcntl from Mailman import mm_cfg from Mailman import Utils @@ -56,7 +57,7 @@ class OutgoingRunner(Runner, BounceMixin): QDIR = mm_cfg.OUTQUEUE_DIR # Process coordination _pid_file = os.path.join(mm_cfg.LOCK_DIR, 'outgoing.pid') - _pid_lock = threading.Lock() + _pid_lock = None _running = False # Shared processed messages tracking with size limits @@ -83,7 +84,8 @@ class OutgoingRunner(Runner, BounceMixin): _max_errors = 10 def __init__(self, slice=None, numslices=1): - mailman_log('debug', 'OutgoingRunner: Starting initialization') + """Initialize the outgoing queue runner.""" + mailman_log('debug', 'OutgoingRunner: Initializing with slice=%s, numslices=%s', slice, numslices) try: # Check if another instance is already running if not self._acquire_pid_lock(): @@ -141,73 +143,69 @@ def __init__(self, slice=None, numslices=1): raise def run(self): - """Run the OutgoingRunner main loop.""" - if not self._running: - mailman_log('error', 'OutgoingRunner: Not properly initialized') - return - + """Run the outgoing queue runner.""" mailman_log('debug', 'OutgoingRunner: Starting main loop') + self._running = True + + # Try to acquire the PID lock + if not self._acquire_pid_lock(): + mailman_log('error', 'OutgoingRunner: Failed to acquire PID lock, exiting') + return + try: - while self._running and not self._stop: + while self._running: try: self._oneloop() - # Sleep briefly to prevent CPU spinning - time.sleep(1) + # Sleep for a bit to avoid CPU spinning + time.sleep(mm_cfg.QRUNNER_SLEEP_TIME) except Exception as e: mailman_log('error', 'OutgoingRunner: Error in main loop: %s', str(e)) - mailman_log('error', 'OutgoingRunner: Traceback: %s', traceback.format_exc()) - # Don't exit on error, just continue the loop - time.sleep(5) # Sleep longer on error - except Exception as e: - mailman_log('error', 'OutgoingRunner: Fatal error in main loop: %s', str(e)) - mailman_log('error', 'OutgoingRunner: Traceback: %s', traceback.format_exc()) + mailman_log('error', 'OutgoingRunner: Traceback:\n%s', traceback.format_exc()) + # Don't exit on error, just log and continue + time.sleep(mm_cfg.QRUNNER_SLEEP_TIME) finally: - self._cleanup() + self._running = False + self._release_pid_lock() + mailman_log('debug', 'OutgoingRunner: Main loop ended') def stop(self): - """Stop the OutgoingRunner.""" - mailman_log('debug', 'OutgoingRunner: Stopping') + """Stop the outgoing queue runner.""" + mailman_log('debug', 'OutgoingRunner: Stopping runner') self._running = False - self._stop = True - self._cleanup() + self._release_pid_lock() + Runner._cleanup(self) + mailman_log('debug', 'OutgoingRunner: Runner stopped') def _acquire_pid_lock(self): """Try to acquire the PID lock file.""" try: - with self._pid_lock: - if os.path.exists(self._pid_file): - # Check if the process is still running - try: - with open(self._pid_file, 'r') as f: - pid = int(f.read().strip()) - # Check if process exists - try: - os.kill(pid, 0) - # Process exists, can't acquire lock - return False - except OSError: - # Process doesn't exist, can acquire lock - pass - except (ValueError, IOError): - # Invalid PID file, can acquire lock - pass - - # Write our PID to the lock file - with open(self._pid_file, 'w') as f: - f.write(str(os.getpid())) - return True - except Exception as e: - mailman_log('error', 'OutgoingRunner: Error acquiring PID lock: %s', str(e)) + self._pid_lock = open(self._pid_file, 'w') + fcntl.flock(self._pid_lock, fcntl.LOCK_EX | fcntl.LOCK_NB) + # Write our PID to the file + self._pid_lock.seek(0) + self._pid_lock.write(str(os.getpid())) + self._pid_lock.truncate() + self._pid_lock.flush() + mailman_log('debug', 'OutgoingRunner: Acquired PID lock file %s', self._pid_file) + return True + except IOError: + mailman_log('error', 'OutgoingRunner: Another instance is already running (PID file: %s)', self._pid_file) + if self._pid_lock: + self._pid_lock.close() + self._pid_lock = None return False def _release_pid_lock(self): """Release the PID lock file.""" - try: - with self._pid_lock: - if os.path.exists(self._pid_file): - os.unlink(self._pid_file) - except Exception as e: - mailman_log('error', 'OutgoingRunner: Error releasing PID lock: %s', str(e)) + if self._pid_lock: + try: + fcntl.flock(self._pid_lock, fcntl.LOCK_UN) + self._pid_lock.close() + os.unlink(self._pid_file) + mailman_log('debug', 'OutgoingRunner: Released PID lock file %s', self._pid_file) + except (IOError, OSError) as e: + mailman_log('error', 'OutgoingRunner: Error releasing PID lock: %s', str(e)) + self._pid_lock = None def _unmark_message_processed(self, msgid): """Remove a message from the processed messages set.""" @@ -599,88 +597,68 @@ def _validate_bounce(self, recip, code, errmsg): return False def _cleanup(self): - """Clean up resources.""" - if not self._running: - return - + """Clean up the outgoing queue runner.""" mailman_log('debug', 'OutgoingRunner: Starting cleanup') try: - BounceMixin._cleanup(self) - Runner._cleanup(self) - self._cleanup_old_messages() - self._cleanup_resources(None, {}) # Log total messages processed with self._total_messages_lock: mailman_log('debug', 'OutgoingRunner: Total messages processed: %d', self._total_messages_processed) - except Exception as e: - mailman_log('error', 'Cleanup failed: %s', str(e)) - finally: - # Always release the PID lock during cleanup + + # Call parent class cleanup + Runner._cleanup(self) + + # Release PID lock if we have it self._release_pid_lock() - self._running = False - mailman_log('debug', 'OutgoingRunner: Cleanup complete') + + mailman_log('debug', 'OutgoingRunner: Cleanup complete') + except Exception as e: + mailman_log('error', 'OutgoingRunner: Error during cleanup: %s', str(e)) + mailman_log('error', 'OutgoingRunner: Traceback:\n%s', traceback.format_exc()) + raise _doperiodic = BounceMixin._doperiodic def _oneloop(self): """Process one batch of messages from the outgoing queue.""" + mailman_log('debug', 'OutgoingRunner: Starting one loop iteration') try: # Get the list of files to process files = self._switchboard.files() - filecnt = len(files) + if not files: + mailman_log('debug', 'OutgoingRunner: No files to process') + return + + mailman_log('debug', 'OutgoingRunner: Processing %d files', len(files)) # Process each file for filebase in files: try: - # Check if the file exists before dequeuing - pckfile = os.path.join(self.QDIR, filebase + '.pck') - if not os.path.exists(pckfile): - mailman_log('error', 'OutgoingRunner._oneloop: File %s does not exist, skipping', pckfile) - continue - - # Check if file is locked - lockfile = os.path.join(self.QDIR, filebase + '.pck.lock') - if os.path.exists(lockfile): - mailman_log('debug', 'OutgoingRunner._oneloop: File %s is locked by another process, skipping', filebase) - continue - - # Dequeue the file + # Try to get the file from the switchboard msg, msgdata = self._switchboard.dequeue(filebase) - if msg is None: - continue - - # Get the list name from msgdata - listname = msgdata.get('listname') - if not listname: - mailman_log('error', 'OutgoingRunner._oneloop: No listname in message data for file %s', filebase) - self._shunt.enqueue(msg, msgdata) - continue - - # Open the list - try: - mlist = get_mail_list()(listname, lock=False) - except Errors.MMUnknownListError: - mailman_log('error', 'OutgoingRunner._oneloop: Unknown list %s for message %s (file: %s)', - listname, msg.get('message-id', 'n/a'), filebase) - self._shunt.enqueue(msg, msgdata) - continue - + except Exception as e: + mailman_log('error', 'OutgoingRunner: Error dequeuing %s: %s', filebase, str(e)) + mailman_log('error', 'OutgoingRunner: Traceback:\n%s', traceback.format_exc()) + continue + + if msg is None: + mailman_log('debug', 'OutgoingRunner: No message data for %s', filebase) + continue + + try: # Process the message - try: - self._dispose(mlist, msg, msgdata) - # Increment message counter on successful processing - with self._total_messages_lock: - self._total_messages_processed += 1 - except Exception as e: - mailman_log('error', 'OutgoingRunner._oneloop: Error processing message %s: %s\nTraceback:\n%s', - msg.get('message-id', 'n/a'), str(e), traceback.format_exc()) - self._shunt.enqueue(msg, msgdata) + self._dispose(msg, msgdata) + with self._total_messages_lock: + self._total_messages_processed += 1 + mailman_log('debug', 'OutgoingRunner: Successfully processed message %s', filebase) except Exception as e: - mailman_log('error', 'OutgoingRunner._oneloop: Error processing file %s: %s', filebase, str(e)) - mailman_log('error', 'OutgoingRunner._oneloop: Traceback: %s', traceback.format_exc()) + mailman_log('error', 'OutgoingRunner: Error processing %s: %s', filebase, str(e)) + mailman_log('error', 'OutgoingRunner: Traceback:\n%s', traceback.format_exc()) + self._handle_error(e, msg, None) + except Exception as e: - mailman_log('error', 'OutgoingRunner._oneloop: Error processing outgoing queue: %s', str(e)) - mailman_log('error', 'OutgoingRunner._oneloop: Traceback: %s', traceback.format_exc()) + mailman_log('error', 'OutgoingRunner: Error in _oneloop: %s', str(e)) + mailman_log('error', 'OutgoingRunner: Traceback:\n%s', traceback.format_exc()) + raise def _handle_error(self, exc, msg=None, mlist=None, preserve=True): """Enhanced error handling with circuit breaker and detailed logging.""" From b26d765f4f6ff1bf229ce917d84a06329eb0d397 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 22 May 2025 20:12:36 -0400 Subject: [PATCH 672/748] Enhanced CommandRunner and VirginRunner with improved no-files handling and error logging --- Mailman/Queue/CommandRunner.py | 36 +++++++++++-------- Mailman/Queue/VirginRunner.py | 65 ++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 14 deletions(-) diff --git a/Mailman/Queue/CommandRunner.py b/Mailman/Queue/CommandRunner.py index f194c44c..81b764dc 100644 --- a/Mailman/Queue/CommandRunner.py +++ b/Mailman/Queue/CommandRunner.py @@ -473,7 +473,11 @@ def _oneloop(self): try: # Get the list of files to process files = self._switchboard.files() - filecnt = len(files) + if not files: + syslog('debug', 'CommandRunner: No files to process') + return + + syslog('debug', 'CommandRunner: Processing %d files', len(files)) # Process each file for filebase in files: @@ -493,6 +497,7 @@ def _oneloop(self): # Dequeue the file msg, msgdata = self._switchboard.dequeue(filebase) if msg is None: + syslog('debug', 'CommandRunner._oneloop: No message data for %s', filebase) continue # Get the list name from msgdata @@ -511,23 +516,26 @@ def _oneloop(self): self._shunt.enqueue(msg, msgdata) continue - # Process message try: - success = self._dispose(mlist, msg, msgdata) - if not success: - # If _dispose returns False, the message was shunted or discarded - # Remove it from the queue - self._switchboard.finish(filebase) + # Process the message + self._dispose(mlist, msg, msgdata) + syslog('debug', 'CommandRunner: Successfully processed message %s', filebase) except Exception as e: - syslog('error', 'CommandRunner._oneloop: Error processing message %s: %s', - msg.get('message-id', 'n/a'), str(e)) - self._shunt.enqueue(msg, msgdata) - # Remove the message from the queue after shunting - self._switchboard.finish(filebase) + syslog('error', 'CommandRunner: Error processing %s: %s', filebase, str(e)) + syslog('error', 'CommandRunner: Traceback:\n%s', traceback.format_exc()) + self._handle_error(e, msg, mlist) + finally: + mlist.Unlock() + except Exception as e: - syslog('error', 'CommandRunner._oneloop: Error processing file %s: %s', filebase, str(e)) + syslog('error', 'CommandRunner: Error processing file %s: %s', filebase, str(e)) + syslog('error', 'CommandRunner: Traceback:\n%s', traceback.format_exc()) + continue + except Exception as e: - syslog('error', 'CommandRunner._oneloop: Error processing command queue: %s', str(e)) + syslog('error', 'CommandRunner: Error in _oneloop: %s', str(e)) + syslog('error', 'CommandRunner: Traceback:\n%s', traceback.format_exc()) + raise # Set up i18n _ = i18n._ diff --git a/Mailman/Queue/VirginRunner.py b/Mailman/Queue/VirginRunner.py index 8c055f7f..f50b9d84 100644 --- a/Mailman/Queue/VirginRunner.py +++ b/Mailman/Queue/VirginRunner.py @@ -31,6 +31,7 @@ from Mailman import Errors import threading import email.header +import os class VirginRunner(IncomingRunner): @@ -204,3 +205,67 @@ def _unmark_message_processed(self, msgid): if msgid in self._processed_times: del self._processed_times[msgid] mailman_log('debug', 'VirginRunner: Unmarked message %s as processed', msgid) + + def _oneloop(self): + """Process one batch of messages from the virgin queue.""" + try: + # Get the list of files to process + files = self._switchboard.files() + if not files: + mailman_log('debug', 'VirginRunner: No files to process') + return + + mailman_log('debug', 'VirginRunner: Processing %d files', len(files)) + + # Process each file + for filebase in files: + try: + # Check if the file exists before dequeuing + pckfile = os.path.join(self.QDIR, filebase + '.pck') + if not os.path.exists(pckfile): + mailman_log('error', 'VirginRunner._oneloop: File %s does not exist, skipping', pckfile) + continue + + # Check if file is locked + lockfile = os.path.join(self.QDIR, filebase + '.pck.lock') + if os.path.exists(lockfile): + mailman_log('debug', 'VirginRunner._oneloop: File %s is locked by another process, skipping', filebase) + continue + + # Dequeue the file + msg, msgdata = self._switchboard.dequeue(filebase) + if msg is None: + mailman_log('debug', 'VirginRunner._oneloop: No message data for %s', filebase) + continue + + # Get message ID for tracking + msgid = msg.get('message-id', 'n/a') + + # Check if message has already been processed + if not self._check_message_processed(msgid, filebase, msg): + mailman_log('debug', 'VirginRunner._oneloop: Message %s already processed, skipping', msgid) + continue + + try: + # Process the message + success = self._onefile(msg, msgdata) + if success: + mailman_log('debug', 'VirginRunner: Successfully processed message %s', msgid) + else: + mailman_log('debug', 'VirginRunner: Message %s requeued for later processing', msgid) + except Exception as e: + mailman_log('error', 'VirginRunner: Error processing %s: %s', msgid, str(e)) + mailman_log('error', 'VirginRunner: Traceback:\n%s', traceback.format_exc()) + self._handle_error(e, msg, None) + # Unmark the message as processed since it failed + self._unmark_message_processed(msgid) + + except Exception as e: + mailman_log('error', 'VirginRunner: Error processing file %s: %s', filebase, str(e)) + mailman_log('error', 'VirginRunner: Traceback:\n%s', traceback.format_exc()) + continue + + except Exception as e: + mailman_log('error', 'VirginRunner: Error in _oneloop: %s', str(e)) + mailman_log('error', 'VirginRunner: Traceback:\n%s', traceback.format_exc()) + raise From f5111b866143c9911edda60b84d5b08da90432b4 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 22 May 2025 20:17:21 -0400 Subject: [PATCH 673/748] Enhanced admin page to properly display categories and subcategories --- Mailman/Cgi/admin.py | 45 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/Mailman/Cgi/admin.py b/Mailman/Cgi/admin.py index 7751da2d..21678595 100644 --- a/Mailman/Cgi/admin.py +++ b/Mailman/Cgi/admin.py @@ -559,6 +559,51 @@ def show_results(mlist, doc, category, subcat, cgidata): } add_standard_headers(doc, mlist, title, category, subcat) + # Create a table for configuration categories + cat_table = Table(border=0, width='100%') + cat_table.AddRow([Center(Header(2, _('Configuration Categories')))]) + cat_table.AddCellInfo(cat_table.GetCurrentRowIndex(), 0, colspan=2, + bgcolor=mm_cfg.WEB_HEADER_COLOR) + + # Add category links + for cat in categories.keys(): + cat_label = _(categories[cat][0]) + if isinstance(cat_label, bytes): + cat_label = cat_label.decode(Utils.GetCharSet(mlist.preferred_language), 'replace') + url = '%s/%s' % (adminurl, cat) + + # Get subcategories if they exist + subcats = mlist.GetConfigSubCategories(cat) + if subcats: + # Create a container for the category and its subcategories + container = Container() + if cat == category: + container.AddItem(Bold(Link(url, cat_label))) + else: + container.AddItem(Link(url, cat_label)) + + # Add subcategory links + subcat_list = UnorderedList() + for subcat_name, subcat_label in subcats: + subcat_url = '%s/%s/%s' % (adminurl, cat, subcat_name) + if isinstance(subcat_label, bytes): + subcat_label = subcat_label.decode(Utils.GetCharSet(mlist.preferred_language), 'replace') + if cat == category and subcat_name == subcat: + subcat_list.AddItem(Bold(Link(subcat_url, subcat_label))) + else: + subcat_list.AddItem(Link(subcat_url, subcat_label)) + container.AddItem(subcat_list) + cat_table.AddRow([container]) + else: + # No subcategories, just add the category link + if cat == category: + cat_table.AddRow([Bold(Link(url, cat_label))]) + else: + cat_table.AddRow([Link(url, cat_label)]) + + doc.AddItem(cat_table) + doc.AddItem('
    ') + # Use ParseTags for the main content replacements = { 'realname': mlist.real_name, From 562d0c8f6e968d2db5288939485dc8aacebfab03 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 22 May 2025 20:38:13 -0400 Subject: [PATCH 674/748] Enhanced Runner logging to include specific runner class names in log messages --- Mailman/Queue/Runner.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Mailman/Queue/Runner.py b/Mailman/Queue/Runner.py index 66673b92..70b682da 100644 --- a/Mailman/Queue/Runner.py +++ b/Mailman/Queue/Runner.py @@ -53,7 +53,7 @@ class Runner: _cleanup_interval = 3600 # Cleanup interval in seconds def __init__(self, slice=None, numslices=1): - syslog('debug', 'Runner: Starting initialization') + syslog('debug', '%s: Starting initialization', self.__class__.__name__) try: self._stop = 0 self._slice = slice @@ -79,10 +79,10 @@ def __init__(self, slice=None, numslices=1): self._last_error_time = 0 self._error_count = 0 - syslog('debug', 'Runner: Initialization complete') + syslog('debug', '%s: Initialization complete', self.__class__.__name__) except Exception as e: - syslog('error', 'Runner: Initialization failed: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) + syslog('error', '%s: Initialization failed: %s\nTraceback:\n%s', + self.__class__.__name__, str(e), traceback.format_exc()) raise def __repr__(self): @@ -432,15 +432,15 @@ def _shortcircuit(self): # def _cleanup(self): """Clean up resources.""" - syslog('debug', 'Runner: Starting cleanup') + syslog('debug', '%s: Starting cleanup', self.__class__.__name__) try: self._cleanup_old_messages() # Clean up any stale locks self._switchboard.cleanup_stale_locks() except Exception as e: - syslog('error', 'Runner: Cleanup failed: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - syslog('debug', 'Runner: Cleanup complete') + syslog('error', '%s: Cleanup failed: %s\nTraceback:\n%s', + self.__class__.__name__, str(e), traceback.format_exc()) + syslog('debug', '%s: Cleanup complete', self.__class__.__name__) def _dispose(self, mlist, msg, msgdata): """Dispose of a single message destined for a mailing list. From 6389f02d2b1a2aa7cbddf40855c62b6f2202bea9 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 22 May 2025 20:42:33 -0400 Subject: [PATCH 675/748] Implemented exponential backoff for runners when no files to process --- Mailman/Queue/Runner.py | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/Mailman/Queue/Runner.py b/Mailman/Queue/Runner.py index 70b682da..3ee8c996 100644 --- a/Mailman/Queue/Runner.py +++ b/Mailman/Queue/Runner.py @@ -41,6 +41,8 @@ class Runner: QDIR = None SLEEPTIME = mm_cfg.QRUNNER_SLEEP_TIME MIN_RETRY_DELAY = 300 # 5 minutes minimum delay between retries + MAX_BACKOFF = 60 # Maximum backoff time in seconds + INITIAL_BACKOFF = 1 # Initial backoff time in seconds # Message tracking configuration - can be overridden by subclasses _track_messages = False # Whether to track processed messages @@ -51,6 +53,7 @@ class Runner: _retry_times = {} # Dictionary of retry times _last_cleanup = time.time() # Last cleanup time _cleanup_interval = 3600 # Cleanup interval in seconds + _current_backoff = INITIAL_BACKOFF # Current backoff time in seconds def __init__(self, slice=None, numslices=1): syslog('debug', '%s: Starting initialization', self.__class__.__name__) @@ -209,6 +212,10 @@ def _oneloop(self): # order. Return an integer count of the number of files that were # available for this qrunner to process. files = self._switchboard.files() + if not files: + syslog('debug', '%s: No files to process', self.__class__.__name__) + return 0 + for filebase in files: try: # Ask the switchboard for the message and metadata objects @@ -405,15 +412,29 @@ def _doperiodic(self): pass def _snooze(self, filecnt): - """Sleep for a while, but check for stop flag periodically.""" + """Sleep for a while, but check for stop flag periodically. + + Implements exponential backoff when no files are found to process. + """ if filecnt > 0: + # Reset backoff when files are found + self._current_backoff = self.INITIAL_BACKOFF # Only log if we're sleeping for more than 5 seconds if self.SLEEPTIME > 5: - syslog('debug', 'Runner._snooze: Sleeping for %d seconds after processing %d files in this iteration', - self.SLEEPTIME, filecnt) - for _ in range(self.SLEEPTIME): + syslog('debug', '%s: Sleeping for %d seconds after processing %d files in this iteration', + self.__class__.__name__, self.SLEEPTIME, filecnt) + sleep_time = self.SLEEPTIME + else: + # No files found, use exponential backoff + sleep_time = min(self._current_backoff, self.MAX_BACKOFF) + syslog('debug', '%s: No files to process, sleeping for %d seconds', + self.__class__.__name__, sleep_time) + # Double the backoff time for next iteration, up to MAX_BACKOFF + self._current_backoff = min(self._current_backoff * 2, self.MAX_BACKOFF) + + for _ in range(sleep_time): if self._stop: - syslog('debug', 'Runner._snooze: Stop flag detected, waking up') + syslog('debug', '%s: Stop flag detected, waking up', self.__class__.__name__) return time.sleep(1) From 203e582fcdeacdb45d283a9e01625b9fd2c9ac27 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 22 May 2025 20:44:07 -0400 Subject: [PATCH 676/748] Removed unnecessary debug logging from OutgoingRunner --- Mailman/Queue/OutgoingRunner.py | 64 ++++++++++++++------------------- 1 file changed, 27 insertions(+), 37 deletions(-) diff --git a/Mailman/Queue/OutgoingRunner.py b/Mailman/Queue/OutgoingRunner.py index d09ce532..bf710322 100644 --- a/Mailman/Queue/OutgoingRunner.py +++ b/Mailman/Queue/OutgoingRunner.py @@ -619,46 +619,36 @@ def _cleanup(self): _doperiodic = BounceMixin._doperiodic def _oneloop(self): - """Process one batch of messages from the outgoing queue.""" - mailman_log('debug', 'OutgoingRunner: Starting one loop iteration') - try: - # Get the list of files to process - files = self._switchboard.files() - if not files: - mailman_log('debug', 'OutgoingRunner: No files to process') - return - - mailman_log('debug', 'OutgoingRunner: Processing %d files', len(files)) + """Process one batch of messages from the queue.""" + # Get all files in the queue + files = self._switchboard.files() + if not files: + return 0 - # Process each file - for filebase in files: - try: - # Try to get the file from the switchboard - msg, msgdata = self._switchboard.dequeue(filebase) - except Exception as e: - mailman_log('error', 'OutgoingRunner: Error dequeuing %s: %s', filebase, str(e)) - mailman_log('error', 'OutgoingRunner: Traceback:\n%s', traceback.format_exc()) - continue - - if msg is None: - mailman_log('debug', 'OutgoingRunner: No message data for %s', filebase) - continue + # Process each file + for filebase in files: + try: + # Try to get the file from the switchboard + msg, msgdata = self._switchboard.dequeue(filebase) + except Exception as e: + mailman_log('error', 'OutgoingRunner: Error dequeuing %s: %s', filebase, str(e)) + mailman_log('error', 'OutgoingRunner: Traceback:\n%s', traceback.format_exc()) + continue - try: - # Process the message - self._dispose(msg, msgdata) - with self._total_messages_lock: - self._total_messages_processed += 1 - mailman_log('debug', 'OutgoingRunner: Successfully processed message %s', filebase) - except Exception as e: - mailman_log('error', 'OutgoingRunner: Error processing %s: %s', filebase, str(e)) - mailman_log('error', 'OutgoingRunner: Traceback:\n%s', traceback.format_exc()) - self._handle_error(e, msg, None) + if msg is None: + mailman_log('debug', 'OutgoingRunner: No message data for %s', filebase) + continue - except Exception as e: - mailman_log('error', 'OutgoingRunner: Error in _oneloop: %s', str(e)) - mailman_log('error', 'OutgoingRunner: Traceback:\n%s', traceback.format_exc()) - raise + try: + # Process the message + self._dispose(msg, msgdata) + with self._total_messages_lock: + self._total_messages_processed += 1 + mailman_log('debug', 'OutgoingRunner: Successfully processed message %s', filebase) + except Exception as e: + mailman_log('error', 'OutgoingRunner: Error processing %s: %s', filebase, str(e)) + mailman_log('error', 'OutgoingRunner: Traceback:\n%s', traceback.format_exc()) + self._handle_error(e, msg, None) def _handle_error(self, exc, msg=None, mlist=None, preserve=True): """Enhanced error handling with circuit breaker and detailed logging.""" From f635e9e0eaf1a49cf3d7ad95cea5b5e0bf61c371 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 22 May 2025 20:47:18 -0400 Subject: [PATCH 677/748] Modified mailmanctl to respect runner backoff behavior --- bin/mailmanctl | 318 +++++++++++++++++-------------------------------- 1 file changed, 106 insertions(+), 212 deletions(-) diff --git a/bin/mailmanctl b/bin/mailmanctl index 477569b6..bee99c07 100755 --- a/bin/mailmanctl +++ b/bin/mailmanctl @@ -552,228 +552,122 @@ def main(): else: sys.exit(1) elif args.command == 'start': + # Check if we're already running + if os.path.exists(mm_cfg.PIDFILE): + try: + with open(mm_cfg.PIDFILE) as fp: + pid = int(fp.read().strip()) + if check_pid(pid): + print(C_('Mailman qrunner is already running (pid: %(pid)d)'), file=sys.stderr) + sys.exit(1) + except (ValueError, IOError): + pass + + # Try to acquire the lock try: - # First, acquire the master mailmanctl lock lock = acquire_lock(args.stale_lock_cleanup) - if not lock: - return - - # Daemon process startup according to Stevens, Advanced Programming in - # the UNIX Environment, Chapter 13. - pid = os.fork() - if pid: - # parent - if not args.quiet: - print(C_("Starting Mailman's master qrunner.")) - # Give up the lock "ownership". This just means the foreground - # process won't close/unlock the lock when it finalizes this lock - # instance. We'll let the master watcher subproc own the lock. - lock._transfer_to(pid) + except LockFile.TimeOutError: + sys.exit(1) + + # Start all runners + kids = start_all_runners() + if not kids: + print(C_('No runners started'), file=sys.stderr) + lock.unlock(unconditionally=1) + sys.exit(1) + + # Write our PID to the PID file + try: + with open(mm_cfg.PIDFILE, 'w') as fp: + fp.write(str(os.getpid())) + except IOError as e: + print(C_('Failed to write PID file: %(error)s'), file=sys.stderr) + lock.unlock(unconditionally=1) + sys.exit(1) + + # Now we're ready to simply do our wait/restart loop + try: + while True: + try: + pid, status = os.wait() + except OSError as e: + # No children? We're done + if e.errno == errno.ECHILD: + break + # If the system call got interrupted, just restart it. + elif e.errno != errno.EINTR: + raise + continue + + killsig = exitstatus = None + if os.WIFSIGNALED(status): + killsig = os.WTERMSIG(status) + if os.WIFEXITED(status): + exitstatus = os.WEXITSTATUS(status) + + restarting = '' + if not args.no_restart: + # Only restart if the runner exited with SIGINT (normal exit) + # and not SIGTERM (error or forced stop) + if exitstatus == signal.SIGINT: + restarting = '[restarting]' + + qrname, slice, count, restarts = kids[pid] + del kids[pid] - # Wait briefly to ensure child process starts - time.sleep(1) + # Only log abnormal exits + if killsig == signal.SIGTERM or \ + (exitstatus is not None and exitstatus != signal.SIGINT): + syslog('qrunner', """\ +Master qrunner detected abnormal subprocess exit +(pid: %d, sig: %s, sts: %s, class: %s, slice: %d/%d) %s""", + pid, killsig, exitstatus, qrname, + slice+1, count, restarting) + + if restarting and check_global_circuit_breaker(): + syslog('error', 'Global circuit breaker triggered - stopping all runners') + # Stop all processes and clean up + stop_all_processes(kids, lock) + # Exit the main loop + break - # Verify the child process is running and PID file is correct + if exitstatus != signal.SIGINT: + restarts += 1 + if restarts > MAX_RESTARTS: + syslog('qrunner', """\ +Qrunner %s reached maximum restart limit of %d, not restarting.""", + qrname, MAX_RESTARTS) + restarting = '' + + # Now perhaps restart the process + if restarting: + newpid = start_runner(qrname, slice, count) + kids[newpid] = (qrname, slice, count, restarts) + + finally: + # all of our children are exited cleanly + for pid in list(kids.keys()): try: - os.kill(pid, 0) # Check if process exists - - # Verify PID file exists and contains correct PID - try: - with open(mm_cfg.PIDFILE, 'r') as fp: - content = fp.read().strip().split() - if len(content) >= 2: - child_pid = int(content[0]) - child_hostname = content[1] - if child_pid != pid: - print(C_('Error: PID file contains incorrect PID'), file=sys.stderr) - return - if child_hostname != socket.gethostname(): - print(C_('Error: PID file hostname mismatch'), file=sys.stderr) - return - else: - print(C_('Error: Invalid PID file format'), file=sys.stderr) - return - - # Verify process is a Mailman process - try: - with open(f'/proc/{pid}/cmdline', 'r') as cmd_fp: - cmdline = cmd_fp.read() - if 'mailman' not in cmdline.lower(): - print(C_('Error: Process is not a Mailman process'), file=sys.stderr) - return - except (IOError, OSError) as e: - print(C_('Warning: Could not verify process type: %s') % str(e), file=sys.stderr) - - if not args.quiet: - print(C_('Master qrunner started successfully (pid: %d)') % pid) - syslog('qrunner', 'Master qrunner started successfully (pid: %d)', pid) - - except (IOError, ValueError) as e: - print(C_('Error reading PID file: %s') % str(e), file=sys.stderr) - return + os.kill(pid, signal.SIGTERM) except OSError as e: if e.errno == errno.ESRCH: - print(C_('Error: Master process failed to start'), file=sys.stderr) - return - raise - return - - # child - try: - lock._take_possession() - # First, save our pid in a file for "mailmanctl stop" rendezvous - omask = os.umask(6) - try: - with open(mm_cfg.PIDFILE, 'w') as fp: - print('%d %s' % (os.getpid(), socket.gethostname()), file=fp) - finally: - os.umask(omask) - - # Create a new session and become the session leader - os.setsid() - - # Be sure to close any open std{in,out,err} - devnull = os.open('/dev/null', 0) - os.dup2(devnull, 0) - os.dup2(devnull, 1) - os.dup2(devnull, 2) - - # Instead of cd'ing to root, cd to the Mailman installation home - os.chdir(mm_cfg.PREFIX) - # Set our file mode creation umask - os.umask(0o07) - - # Now start all the qrunners - kids = start_all_runners() - if not kids: - syslog('error', 'No runners started successfully') - sys.exit(1) + syslog('qrunner', 'ESRCH on pid: %d', pid) + del kids[pid] - # Set up a SIGALRM handler to refresh the lock once per day - def sigalrm_handler(signum, frame, lock=lock): - lock.refresh() - signal.alarm(mm_cfg.days(1)) - signal.signal(signal.SIGALRM, sigalrm_handler) - signal.alarm(mm_cfg.days(1)) - - # Set up a SIGHUP handler - def sighup_handler(signum, frame, kids=kids): - syslog.close() - for pid in list(kids.keys()): - os.kill(pid, signal.SIGHUP) - syslog('qrunner', - 'Master watcher caught SIGHUP. Re-opening log files.') - signal.signal(signal.SIGHUP, sighup_handler) - - # Set up a SIGTERM handler - def sigterm_handler(signum, frame, kids=kids): - for pid in list(kids.keys()): - try: - os.kill(pid, signal.SIGTERM) - except OSError as e: - if e.errno != errno.ESRCH: raise - syslog('qrunner', 'Master watcher caught SIGTERM. Exiting.') - signal.signal(signal.SIGTERM, sigterm_handler) - - # Set up a SIGINT handler - def sigint_handler(signum, frame, kids=kids): - for pid in list(kids.keys()): - os.kill(pid, signal.SIGINT) - syslog('qrunner', 'Master watcher caught SIGINT. Restarting.') - signal.signal(signal.SIGINT, sigint_handler) - - # Now we're ready to simply do our wait/restart loop + # Wait for all the children to go away + while True: try: - while True: - try: - pid, status = os.wait() - except OSError as e: - # No children? We're done - if e.errno == errno.ECHILD: - break - # If the system call got interrupted, just restart it. - elif e.errno != errno.EINTR: - raise - continue - - killsig = exitstatus = None - if os.WIFSIGNALED(status): - killsig = os.WTERMSIG(status) - if os.WIFEXITED(status): - exitstatus = os.WEXITSTATUS(status) - - restarting = '' - if not args.no_restart: - if (exitstatus is None and killsig != signal.SIGTERM) or \ - (killsig is None and exitstatus != signal.SIGTERM): - restarting = '[restarting]' - - qrname, slice, count, restarts = kids[pid] - del kids[pid] - - # Only log abnormal exits - if killsig == signal.SIGTERM or \ - (exitstatus is not None and exitstatus != 120 and exitstatus != signal.SIGINT): - syslog('qrunner', """\ -Master qrunner detected abnormal subprocess exit -(pid: %d, sig: %s, sts: %s, class: %s, slice: %d/%d) %s""", - pid, killsig, exitstatus, qrname, - slice+1, count, restarting) - - # Check global circuit breaker before restarting - if restarting and check_global_circuit_breaker(): - syslog('error', 'Global circuit breaker triggered - stopping all runners') - # Stop all processes and clean up - stop_all_processes(kids, lock) - # Exit the main loop - break - - if exitstatus != signal.SIGINT: - restarts += 1 - if restarts > MAX_RESTARTS: - syslog('qrunner', """\ -Qrunner %s reached maximum restart limit of %d, not restarting.""", - qrname, MAX_RESTARTS) - restarting = '' - - # Now perhaps restart the process - if restarting: - newpid = start_runner(qrname, slice, count) - kids[newpid] = (qrname, slice, count, restarts) - - finally: - # Should we leave the main loop for any reason, we want to be sure - # all of our children are exited cleanly - for pid in list(kids.keys()): - try: - os.kill(pid, signal.SIGTERM) - except OSError as e: - if e.errno == errno.ESRCH: - syslog('qrunner', 'ESRCH on pid: %d', pid) - del kids[pid] - - # Wait for all the children to go away - while True: - try: - pid, status = os.wait() - except OSError as e: - if e.errno == errno.ECHILD: - break - elif e.errno != errno.EINTR: - raise - continue - - # Finally, give up the lock - lock.unlock(unconditionally=1) - os._exit(0) - except Exception as e: - syslog('error', 'Child process error during startup: %s', str(e)) - os._exit(1) + pid, status = os.wait() + except OSError as e: + if e.errno == errno.ECHILD: + break + elif e.errno != errno.EINTR: + raise + continue - except Exception as e: - import traceback - syslog('error', 'Error during startup: %s\nTraceback:\n%s', str(e), traceback.format_exc()) - sys.exit(1) + # Finally, give up the lock + lock.unlock(unconditionally=1) + os._exit(0) elif args.command == 'stop': kill_watcher(signal.SIGTERM) try: From 8093aa2aa43d4b7de6e9db25b00c15cf496c88b8 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 22 May 2025 20:48:37 -0400 Subject: [PATCH 678/748] Optimized Runner to use os.stat() for directory change detection --- Mailman/Queue/Runner.py | 91 +++++++++++++++++++---------------------- 1 file changed, 42 insertions(+), 49 deletions(-) diff --git a/Mailman/Queue/Runner.py b/Mailman/Queue/Runner.py index 3ee8c996..d8c2dcf5 100644 --- a/Mailman/Queue/Runner.py +++ b/Mailman/Queue/Runner.py @@ -24,6 +24,7 @@ from io import StringIO from functools import wraps import threading +import os from Mailman import mm_cfg from Mailman import Utils @@ -54,6 +55,7 @@ class Runner: _last_cleanup = time.time() # Last cleanup time _cleanup_interval = 3600 # Cleanup interval in seconds _current_backoff = INITIAL_BACKOFF # Current backoff time in seconds + _last_mtime = 0 # Last directory modification time def __init__(self, slice=None, numslices=1): syslog('debug', '%s: Starting initialization', self.__class__.__name__) @@ -82,6 +84,9 @@ def __init__(self, slice=None, numslices=1): self._last_error_time = 0 self._error_count = 0 + self._current_backoff = self.INITIAL_BACKOFF + self._last_mtime = 0 + syslog('debug', '%s: Initialization complete', self.__class__.__name__) except Exception as e: syslog('error', '%s: Initialization failed: %s\nTraceback:\n%s', @@ -207,39 +212,43 @@ def _handle_error(self, exc, msg=None, mlist=None, preserve=True): return True def _oneloop(self): - # First, list all the files in our queue directory. - # Switchboard.files() is guaranteed to hand us the files in FIFO - # order. Return an integer count of the number of files that were - # available for this qrunner to process. + """Run one iteration of the runner's main loop. + + Returns: + int: Number of files processed, or 0 if no files found + """ + # Check if directory has been modified since last check + try: + st = os.stat(self.QDIR) + current_mtime = st.st_mtime + if current_mtime <= self._last_mtime: + # Directory hasn't changed, use backoff + self._snooze(self._current_backoff) + # Double the backoff time, up to MAX_BACKOFF + self._current_backoff = min(self._current_backoff * 2, self.MAX_BACKOFF) + return 0 + # Directory has changed, reset backoff + self._current_backoff = self.INITIAL_BACKOFF + self._last_mtime = current_mtime + except OSError as e: + syslog('error', '%s: Error checking directory %s: %s', + self.__class__.__name__, self.QDIR, str(e)) + return 0 + + # Process files in the directory files = self._switchboard.files() if not files: syslog('debug', '%s: No files to process', self.__class__.__name__) return 0 - + + # Process each file for filebase in files: + if self._stop: + break try: # Ask the switchboard for the message and metadata objects # associated with this filebase. msg, msgdata = self._switchboard.dequeue(filebase) - except Exception as e: - # This used to just catch email.Errors.MessageParseError, - # but other problems can occur in message parsing, e.g. - # ValueError, and exceptions can occur in unpickling too. - # We don't want the runner to die, so we just log and skip - # this entry, but maybe preserve it for analysis. - self._log(e) - if mm_cfg.QRUNNER_SAVE_BAD_MESSAGES: - syslog('error', - 'Skipping and preserving unparseable message: %s', - filebase) - preserve = True - else: - syslog('error', - 'Ignoring unparseable message: %s', filebase) - preserve = False - self._switchboard.finish(filebase, preserve=preserve) - continue - try: self._onefile(msg, msgdata) self._switchboard.finish(filebase) except Exception as e: @@ -411,32 +420,16 @@ def _doperiodic(self): """ pass - def _snooze(self, filecnt): - """Sleep for a while, but check for stop flag periodically. - - Implements exponential backoff when no files are found to process. + def _snooze(self, secs): + """Sleep for the specified number of seconds, but wake up if the + stop flag is set. + + Args: + secs: Number of seconds to sleep. """ - if filecnt > 0: - # Reset backoff when files are found - self._current_backoff = self.INITIAL_BACKOFF - # Only log if we're sleeping for more than 5 seconds - if self.SLEEPTIME > 5: - syslog('debug', '%s: Sleeping for %d seconds after processing %d files in this iteration', - self.__class__.__name__, self.SLEEPTIME, filecnt) - sleep_time = self.SLEEPTIME - else: - # No files found, use exponential backoff - sleep_time = min(self._current_backoff, self.MAX_BACKOFF) - syslog('debug', '%s: No files to process, sleeping for %d seconds', - self.__class__.__name__, sleep_time) - # Double the backoff time for next iteration, up to MAX_BACKOFF - self._current_backoff = min(self._current_backoff * 2, self.MAX_BACKOFF) - - for _ in range(sleep_time): - if self._stop: - syslog('debug', '%s: Stop flag detected, waking up', self.__class__.__name__) - return - time.sleep(1) + endtime = time.time() + secs + while time.time() < endtime and not self._stop: + time.sleep(0.1) def _shortcircuit(self): """Return a true value if the individual file processing loop should From edb034bf6ab70a2ff33fb26d0feec1918bb327c2 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 22 May 2025 20:50:43 -0400 Subject: [PATCH 679/748] Fixed _snooze method to properly handle filecnt and maintain original behavior --- Mailman/Queue/Runner.py | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/Mailman/Queue/Runner.py b/Mailman/Queue/Runner.py index d8c2dcf5..eb1f98f3 100644 --- a/Mailman/Queue/Runner.py +++ b/Mailman/Queue/Runner.py @@ -420,14 +420,31 @@ def _doperiodic(self): """ pass - def _snooze(self, secs): - """Sleep for the specified number of seconds, but wake up if the - stop flag is set. - + def _snooze(self, filecnt): + """Sleep for a while, but check for stop flag periodically. + + Implements exponential backoff when no files are found to process. + Args: - secs: Number of seconds to sleep. + filecnt: Number of files processed in the last iteration """ - endtime = time.time() + secs + if filecnt > 0: + # Reset backoff when files are found + self._current_backoff = self.INITIAL_BACKOFF + # Only log if we're sleeping for more than 5 seconds + if self.SLEEPTIME > 5: + syslog('debug', '%s: Sleeping for %d seconds after processing %d files in this iteration', + self.__class__.__name__, self.SLEEPTIME, filecnt) + sleep_time = self.SLEEPTIME + else: + # No files found, use exponential backoff + sleep_time = min(self._current_backoff, self.MAX_BACKOFF) + syslog('debug', '%s: No files to process, sleeping for %d seconds', + self.__class__.__name__, sleep_time) + # Double the backoff time for next iteration, up to MAX_BACKOFF + self._current_backoff = min(self._current_backoff * 2, self.MAX_BACKOFF) + + endtime = time.time() + sleep_time while time.time() < endtime and not self._stop: time.sleep(0.1) From 8492d9b5b1a0a589612cb9208b4bf29fca5e8db7 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 22 May 2025 20:52:04 -0400 Subject: [PATCH 680/748] Modified mailmanctl to properly daemonize processes --- bin/mailmanctl | 155 +++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 130 insertions(+), 25 deletions(-) diff --git a/bin/mailmanctl b/bin/mailmanctl index bee99c07..18e7645f 100755 --- a/bin/mailmanctl +++ b/bin/mailmanctl @@ -331,20 +331,49 @@ Exiting."""), file=sys.stderr) def start_runner(qrname, slice, count): + """Start a qrunner process in the background. + + Args: + qrname: Name of the runner to start + slice: Slice number for this runner + count: Total number of slices + + Returns: + int: PID of the started process + """ pid = os.fork() if pid: # parent return pid # child - # - # Craft the command line arguments for the exec() call. - rswitch = '--runner=%s:%d:%d' % (qrname, slice, count) - exe = os.path.join(mm_cfg.BIN_DIR, 'qrunner') - # mm_cfg.PYTHON, which is the absolute path to the Python interpreter, - # must be given as argv[0] due to Python's library search algorithm. - os.execl(mm_cfg.PYTHON, mm_cfg.PYTHON, exe, rswitch, '-s') - # Should never get here - raise RuntimeError('os.execl() failed') + try: + # Create a new session and become the session leader + os.setsid() + + # Be sure to close any open std{in,out,err} + devnull = os.open('/dev/null', 0) + os.dup2(devnull, 0) + os.dup2(devnull, 1) + os.dup2(devnull, 2) + + # Instead of cd'ing to root, cd to the Mailman installation home + os.chdir(mm_cfg.PREFIX) + + # Set our file mode creation umask + os.umask(0o07) + + # Craft the command line arguments for the exec() call + rswitch = '--runner=%s:%d:%d' % (qrname, slice, count) + exe = os.path.join(mm_cfg.BIN_DIR, 'qrunner') + # mm_cfg.PYTHON, which is the absolute path to the Python interpreter, + # must be given as argv[0] due to Python's library search algorithm. + os.execl(mm_cfg.PYTHON, mm_cfg.PYTHON, exe, rswitch, '-s') + # Should never get here + raise RuntimeError('os.execl() failed') + except Exception as e: + syslog('error', 'Failed to start %s runner (slice %d): %s', + qrname, slice, str(e)) + os._exit(1) def start_all_runners(): @@ -569,24 +598,100 @@ def main(): except LockFile.TimeOutError: sys.exit(1) - # Start all runners - kids = start_all_runners() - if not kids: - print(C_('No runners started'), file=sys.stderr) - lock.unlock(unconditionally=1) - sys.exit(1) - - # Write our PID to the PID file - try: - with open(mm_cfg.PIDFILE, 'w') as fp: - fp.write(str(os.getpid())) - except IOError as e: - print(C_('Failed to write PID file: %(error)s'), file=sys.stderr) - lock.unlock(unconditionally=1) - sys.exit(1) + # Fork to daemonize + pid = os.fork() + if pid: + # parent + if not args.quiet: + print(C_("Starting Mailman's master qrunner.")) + # Give up the lock "ownership". This just means the foreground + # process won't close/unlock the lock when it finalizes this lock + # instance. We'll let the master watcher subproc own the lock. + lock._transfer_to(pid) + + # Wait briefly to ensure child process starts + time.sleep(1) + + # Verify the child process is running + try: + os.kill(pid, 0) # Check if process exists + if not args.quiet: + print(C_('Master qrunner started successfully (pid: %d)') % pid) + syslog('qrunner', 'Master qrunner started successfully (pid: %d)', pid) + except OSError as e: + if e.errno == errno.ESRCH: + print(C_('Error: Master process failed to start'), file=sys.stderr) + return + raise + return - # Now we're ready to simply do our wait/restart loop + # child try: + lock._take_possession() + + # Create a new session and become the session leader + os.setsid() + + # Be sure to close any open std{in,out,err} + devnull = os.open('/dev/null', 0) + os.dup2(devnull, 0) + os.dup2(devnull, 1) + os.dup2(devnull, 2) + + # Instead of cd'ing to root, cd to the Mailman installation home + os.chdir(mm_cfg.PREFIX) + + # Set our file mode creation umask + os.umask(0o07) + + # Write our PID to the PID file + try: + with open(mm_cfg.PIDFILE, 'w') as fp: + fp.write(str(os.getpid())) + except IOError as e: + syslog('error', 'Failed to write PID file: %s', str(e)) + os._exit(1) + + # Start all runners + kids = start_all_runners() + if not kids: + syslog('error', 'No runners started successfully') + os._exit(1) + + # Set up a SIGALRM handler to refresh the lock once per day + def sigalrm_handler(signum, frame, lock=lock): + lock.refresh() + signal.alarm(mm_cfg.days(1)) + signal.signal(signal.SIGALRM, sigalrm_handler) + signal.alarm(mm_cfg.days(1)) + + # Set up a SIGHUP handler + def sighup_handler(signum, frame, kids=kids): + syslog.close() + for pid in list(kids.keys()): + os.kill(pid, signal.SIGHUP) + syslog('qrunner', + 'Master watcher caught SIGHUP. Re-opening log files.') + signal.signal(signal.SIGHUP, sighup_handler) + + # Set up a SIGTERM handler + def sigterm_handler(signum, frame, kids=kids): + for pid in list(kids.keys()): + try: + os.kill(pid, signal.SIGTERM) + except OSError as e: + if e.errno != errno.ESRCH: raise + syslog('qrunner', 'Master watcher caught SIGTERM. Exiting.') + signal.signal(signal.SIGTERM, sigterm_handler) + + # Set up a SIGINT handler + def sigint_handler(signum, frame, kids=kids): + for pid in list(kids.keys()): + os.kill(pid, signal.SIGINT) + syslog('qrunner', 'Master watcher caught SIGINT. Restarting.') + signal.signal(signal.SIGINT, sigint_handler) + + # Now we're ready to simply do our wait/restart loop while True: try: pid, status = os.wait() From 00916d474c5db3afde49a7243691a3df10637cbf Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 22 May 2025 20:55:23 -0400 Subject: [PATCH 681/748] Restored original mailmanctl from mailman-2.1.39 --- bin/mailmanctl | 690 ++++++++++++++++--------------------------------- 1 file changed, 226 insertions(+), 464 deletions(-) diff --git a/bin/mailmanctl b/bin/mailmanctl index 18e7645f..36016e27 100755 --- a/bin/mailmanctl +++ b/bin/mailmanctl @@ -36,7 +36,7 @@ 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. -Usage: %(PROGRAM)s [options] [ start | stop | restart | reopen | status ] +Usage: %(PROGRAM)s [options] [ start | stop | restart | reopen ] Options: @@ -90,20 +90,17 @@ Commands: reopen - This will close all log files, causing them to be re-opened the next time a message is written to them - - status - Checks if all qrunners are running as expected. """ import sys import os import time -import argparse +import getopt import signal import errno import pwd import grp import socket -import psutil # Add psutil import import paths from Mailman import mm_cfg @@ -127,63 +124,34 @@ LOCK_LIFETIME = mm_cfg.days(1) + mm_cfg.hours(6) SNOOZE = mm_cfg.days(1) MAX_RESTARTS = 10 -# Global circuit breaker settings -GLOBAL_RESTART_WINDOW = 30 # 30 seconds window -GLOBAL_MAX_RESTARTS = 100 # Maximum restarts in window -_global_restart_times = [] # List of timestamps for restarts - LogStdErr('error', 'mailmanctl', manual_reprime=0) -def parse_args(): - parser = argparse.ArgumentParser(description='Primary start-up and shutdown script for Mailman\'s qrunner daemon.') - parser.add_argument('command', choices=['start', 'stop', 'restart', 'reopen', 'status'], - help='Command to execute') - parser.add_argument('-n', '--no-restart', action='store_true', - help='Don\'t restart the qrunners when they exit because of an error or a SIGINT') - parser.add_argument('-u', '--run-as-user', action='store_true', - help='Run as current user instead of mailman user') - parser.add_argument('-s', '--stale-lock-cleanup', action='store_true', - help='Clean up stale locks before starting') - parser.add_argument('-q', '--quiet', action='store_true', - help='Don\'t print status messages') - return parser.parse_args() - - + def usage(code, msg=''): if code: fd = sys.stderr else: fd = sys.stdout - # In Python 3, sys.argv[0] is already a string - program = str(sys.argv[0]) # Ensure it's a string - doc = C_(__doc__) % {'PROGRAM': program} # Let C_() handle the translation and formatting - print(doc, file=fd) + print(C_(__doc__), file=fd) if msg: print(msg, file=fd) sys.exit(code) + def kill_watcher(sig): try: - with open(mm_cfg.PIDFILE, 'r') as fp: - content = fp.read().strip().split() - if len(content) >= 2: - pid = int(content[0]) - hostname = content[1] - if hostname != socket.gethostname(): - print(C_('PID file hostname mismatch: expected %(expected)s, got %(got)s') % - {'expected': socket.gethostname(), 'got': hostname}, file=sys.stderr) - return - else: - raise ValueError('Invalid PID file format') + fp = open(mm_cfg.PIDFILE) + pidstr = fp.read() + fp.close() + pid = int(pidstr.strip()) except (IOError, ValueError) as e: # For i18n convenience pidfile = mm_cfg.PIDFILE print(C_('PID unreadable in: %(pidfile)s'), file=sys.stderr) print(e, file=sys.stderr) print(C_('Is qrunner even running?'), file=sys.stderr) - print(C_('Lock file path: %(lockfile)s') % {'lockfile': LOCKFILE}, file=sys.stderr) return try: os.kill(pid, sig) @@ -195,29 +163,21 @@ def kill_watcher(sig): os.unlink(mm_cfg.PIDFILE) + def get_lock_data(): # Return the hostname, pid, and tempfile - try: - with open(LOCKFILE) as fp: - content = fp.read().strip().split() - if len(content) != 2: - syslog('error', 'Invalid lock file format in %s: expected "pid hostname"', LOCKFILE) - raise LockFile.LockError('Invalid lock file format') - try: - pid = int(content[0]) - hostname = content[1] - except ValueError as e: - syslog('error', 'Invalid PID in lock file %s: %s', LOCKFILE, e) - raise LockFile.LockError('Invalid PID in lock file') - return hostname, pid, None # tempfile is not used in this format - except IOError as e: - syslog('error', 'Could not read lock file %s: %s', LOCKFILE, e) - raise LockFile.LockError('Could not read lock file') + fp = open(LOCKFILE) + filename = os.path.split(fp.read().strip())[1] + fp.close() + parts = filename.split('.') + hostname = DOT.join(parts[1:-1]) + pid = int(parts[-1]) + return hostname, int(pid), filename def qrunner_state(): - # 1 if proc exists on host and is owned by mailman user - # 0 if host matches but no proc or wrong owner + # 1 if proc exists on host (but is it qrunner? ;) + # 0 if host matches but no proc # hostname if hostname doesn't match hostname, pid, tempfile = get_lock_data() if hostname != socket.gethostname(): @@ -225,44 +185,10 @@ def qrunner_state(): # Find out if the process exists by calling kill with a signal 0. try: os.kill(pid, 0) - # Process exists, now check if it's owned by the mailman user - mailman_uid = pwd.getpwnam(mm_cfg.MAILMAN_USER).pw_uid - try: - # Try to get process owner using platform-specific methods - if os.name == 'posix': - # On Unix-like systems, try to get process owner - try: - # Try using /proc on Linux - if os.path.exists('/proc'): - with open(f'/proc/{pid}/status') as f: - for line in f: - if line.startswith('Uid:'): - uid = int(line.split()[1]) - if uid != mailman_uid: - syslog('error', 'Process %d exists but is owned by uid %d, not mailman user %d', - pid, uid, mailman_uid) - return 0 - break - else: - # On other Unix systems, we can't easily check the owner - # without external tools, so we'll assume it's valid - # if the process exists - return 1 - except (IOError, OSError) as e: - syslog('error', 'Error checking process %d ownership: %s', pid, str(e)) - return 0 - else: - # On non-Unix systems, we can't easily check the owner - # without external tools, so we'll assume it's valid - # if the process exists - return 1 - return 1 - except Exception as e: - syslog('error', 'Error checking process %d ownership: %s', pid, str(e)) - return 0 except OSError as e: if e.errno != errno.ESRCH: raise return 0 + return 1 def acquire_lock_1(force): @@ -273,28 +199,14 @@ def acquire_lock_1(force): lock.lock(0.1) return lock except LockFile.TimeOutError: - # Check if the lock is stale by examining the process - status = qrunner_state() - if status == 1: - # Process exists and is running, so lock is valid + # If we're not forcing or the lock can't be determined to be stale. + if not force or qrunner_state(): raise - # Lock appears to be stale - clean it up - try: - # Read the current lock file content - with open(LOCKFILE) as fp: - content = fp.read().strip() - if content: - # Try to clean up any stale lock files - lock.clean_stale_locks() - except (IOError, OSError) as e: - syslog('error', 'Error cleaning up stale lock: %s', str(e)) - # Remove the lock file - try: - os.unlink(LOCKFILE) - except OSError as e: - if e.errno != errno.ENOENT: - syslog('error', 'Error removing lock file: %s', str(e)) - # Try to acquire the lock again + # Force removal of lock first + lock._disown() + hostname, pid, tempfile = get_lock_data() + os.unlink(LOCKFILE) + os.unlink(os.path.join(mm_cfg.LOCK_DIR, tempfile)) return acquire_lock_1(force=0) @@ -330,69 +242,36 @@ Lock host: %(status)s Exiting."""), file=sys.stderr) + def start_runner(qrname, slice, count): - """Start a qrunner process in the background. - - Args: - qrname: Name of the runner to start - slice: Slice number for this runner - count: Total number of slices - - Returns: - int: PID of the started process - """ pid = os.fork() if pid: # parent return pid # child - try: - # Create a new session and become the session leader - os.setsid() - - # Be sure to close any open std{in,out,err} - devnull = os.open('/dev/null', 0) - os.dup2(devnull, 0) - os.dup2(devnull, 1) - os.dup2(devnull, 2) - - # Instead of cd'ing to root, cd to the Mailman installation home - os.chdir(mm_cfg.PREFIX) - - # Set our file mode creation umask - os.umask(0o07) - - # Craft the command line arguments for the exec() call - rswitch = '--runner=%s:%d:%d' % (qrname, slice, count) - exe = os.path.join(mm_cfg.BIN_DIR, 'qrunner') - # mm_cfg.PYTHON, which is the absolute path to the Python interpreter, - # must be given as argv[0] due to Python's library search algorithm. - os.execl(mm_cfg.PYTHON, mm_cfg.PYTHON, exe, rswitch, '-s') - # Should never get here - raise RuntimeError('os.execl() failed') - except Exception as e: - syslog('error', 'Failed to start %s runner (slice %d): %s', - qrname, slice, str(e)) - os._exit(1) + # + # Craft the command line arguments for the exec() call. + rswitch = '--runner=%s:%d:%d' % (qrname, slice, count) + exe = os.path.join(mm_cfg.BIN_DIR, 'qrunner') + # mm_cfg.PYTHON, which is the absolute path to the Python interpreter, + # must be given as argv[0] due to Python's library search algorithm. + os.execl(mm_cfg.PYTHON, mm_cfg.PYTHON, exe, rswitch, '-s') + # Should never get here + raise RuntimeError('os.execl() failed') def start_all_runners(): kids = {} for qrname, count in mm_cfg.QRUNNERS: for slice in range(count): - try: - # queue runner name, slice, numslices, restart count - info = (qrname, slice, count, 0) - pid = start_runner(qrname, slice, count) - kids[pid] = info - except Exception as e: - # Log the failure but continue with other runners - syslog('error', 'Failed to start %s runner (slice %d): %s', - qrname, slice, str(e)) - continue + # queue runner name, slice, numslices, restart count + info = (qrname, slice, count, 0) + pid = start_runner(qrname, slice, count) + kids[pid] = info return kids + def check_for_site_list(): sitelistname = mm_cfg.MAILMAN_SITE_LIST try: @@ -427,315 +306,212 @@ def check_privs(): 'Run this program as root or as the %(name)s user, or use -u.')) -def check_status(): - """Check if all qrunners are running as expected.""" - # First check if the master process is running - try: - with open(mm_cfg.PIDFILE, 'r') as fp: - content = fp.read().strip().split() - if len(content) >= 2: - pid = int(content[0]) - hostname = content[1] - if hostname != socket.gethostname(): - print(C_('PID file hostname mismatch: expected %(expected)s, got %(got)s') % - {'expected': socket.gethostname(), 'got': hostname}, file=sys.stderr) - return False - else: - raise ValueError('Invalid PID file format') - try: - os.kill(pid, 0) # Check if process exists - print(C_('Master qrunner process is running (pid: %(pid)d)') % {'pid': pid}) - except OSError: - print(C_('Master qrunner process is not running (stale pid file)')) - return False - except (IOError, ValueError) as e: - print(C_('Master qrunner process is not running (no pid file)')) - print(e, file=sys.stderr) - return False - - # Check if the lock file exists and is valid - try: - hostname, pid, tempfile = get_lock_data() - if hostname != socket.gethostname(): - print(C_('Lock file is held by another host: %(hostname)s') % {'hostname': hostname}) - return False - try: - os.kill(pid, 0) - print(C_('Lock file is valid (pid: %(pid)d)') % {'pid': pid}) - except OSError: - print(C_('Lock file is stale (process %(pid)d not running)') % {'pid': pid}) - return False - except (IOError, ValueError): - print(C_('No lock file found')) - return False - - # Check if all expected qrunners are running - expected_runners = dict(mm_cfg.QRUNNERS) - running_runners = {} - - # Get all running qrunner processes - for line in os.popen('ps aux | grep qrunner | grep -v grep').readlines(): - parts = line.split() - if len(parts) >= 12: # Ensure we have enough parts - cmd = parts[10] # The command is typically at index 10 - if '--runner=' in cmd: - runner_name = cmd.split('--runner=')[1].split(':')[0] - running_runners[runner_name] = running_runners.get(runner_name, 0) + 1 - - # Compare expected vs running - all_running = True - for runner, count in expected_runners.items(): - actual = running_runners.get(runner, 0) - if actual != count: - print(C_('%(runner)s: expected %(count)d instances, found %(actual)d') % - {'runner': runner, 'count': count, 'actual': actual}) - all_running = False - else: - print(C_('%(runner)s: %(count)d instances running') % - {'runner': runner, 'count': count}) - - return all_running - - -def check_global_circuit_breaker(): - """Check if we've exceeded the global restart limit. - - Returns: - bool: True if we should stop all runners, False otherwise - """ - # Circuit breaker disabled - always return False - return False - - -def stop_all_processes(kids, lock=None): - """Stop all child processes and clean up, similar to mailmanctl stop. - - Args: - kids: Dictionary of child processes - lock: Optional lock to release - """ - # First send SIGTERM to all children - for pid in list(kids.keys()): - try: - os.kill(pid, signal.SIGTERM) - except OSError as e: - if e.errno != errno.ESRCH: - raise - - # Wait for all children to exit - while kids: - try: - pid, status = os.wait() - if pid in kids: - del kids[pid] - except OSError as e: - if e.errno == errno.ECHILD: - break - elif e.errno != errno.EINTR: - raise - continue - - # Clean up PID file - try: - os.unlink(mm_cfg.PIDFILE) - syslog('qrunner', 'Removed PID file: %s', mm_cfg.PIDFILE) - except OSError as e: - if e.errno != errno.ENOENT: - syslog('error', 'Failed to remove PID file %s: %s', mm_cfg.PIDFILE, str(e)) - - # Release lock if provided - if lock: - try: - lock.unlock(unconditionally=1) - except Exception as e: - syslog('error', 'Failed to release lock: %s', str(e)) - - + def main(): + global quiet try: - args = parse_args() - except SystemExit: - usage(1) - - # Check that we're running as the right user - if not args.run_as_user: - try: - mailman_uid = pwd.getpwnam(mm_cfg.MAILMAN_USER).pw_uid - mailman_gid = grp.getgrnam(mm_cfg.MAILMAN_GROUP).gr_gid - except (KeyError, AttributeError): - print(C_('Cannot determine mailman user/group'), file=sys.stderr) - sys.exit(1) - - if os.getuid() == 0: - # We're root, so switch to the mailman user/group - os.setgid(mailman_gid) - os.setuid(mailman_uid) - elif os.getuid() != mailman_uid or os.getgid() != mailman_gid: - print(C_('Must be run as the mailman user'), file=sys.stderr) - sys.exit(1) - - # Handle the command - if args.command == 'status': - if check_status(): - sys.exit(0) - else: - sys.exit(1) - elif args.command == 'start': - # Check if we're already running - if os.path.exists(mm_cfg.PIDFILE): - try: - with open(mm_cfg.PIDFILE) as fp: - pid = int(fp.read().strip()) - if check_pid(pid): - print(C_('Mailman qrunner is already running (pid: %(pid)d)'), file=sys.stderr) - sys.exit(1) - except (ValueError, IOError): - pass - - # Try to acquire the lock - try: - lock = acquire_lock(args.stale_lock_cleanup) - except LockFile.TimeOutError: - sys.exit(1) - - # Fork to daemonize + opts, args = getopt.getopt(sys.argv[1:], 'hnu:sq', + ['help', 'no-restart', 'run-as-user=', + 'stale-lock-cleanup', 'quiet']) + except getopt.error as msg: + usage(1, msg) + + restart = 1 + checkprivs = 1 + force = 0 + quiet = 0 + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-n', '--no-restart'): + restart = 0 + elif opt in ('-u', '--run-as-user'): + checkprivs = 0 + elif opt in ('-s', '--stale-lock-cleanup'): + force = 1 + elif opt in ('-q', '--quiet'): + quiet = 1 + + if len(args) < 1: + usage(1, C_('No command given.')) + elif len(args) > 1: + command = COMMASPACE.join(args) + usage(1, C_('Bad command: %(command)s')) + + if checkprivs: + check_privs() + else: + print(C_('Warning! You may encounter permission problems.')) + + # Handle the commands + command = args[0].lower() + if command == 'stop': + # Sent the master qrunner process a SIGINT, which is equivalent to + # giving cron/qrunner a ctrl-c or KeyboardInterrupt. This will + # effectively shut everything down. + if not quiet: + print(C_("Shutting down Mailman's master qrunner")) + kill_watcher(signal.SIGTERM) + elif command == 'restart': + # Sent the master qrunner process a SIGHUP. This will cause the + # master qrunner to kill and restart all the worker qrunners, and to + # close and re-open its log files. + if not quiet: + print(C_("Restarting Mailman's master qrunner")) + kill_watcher(signal.SIGINT) + elif command == 'reopen': + if not quiet: + print(C_('Re-opening all log files')) + kill_watcher(signal.SIGHUP) + elif command == 'start': + # First, complain loudly if there's no site list. + check_for_site_list() + # Here's the scoop on the processes we're about to create. We'll need + # one for each qrunner, and one for a master child process watcher / + # lock refresher process. + # + # The child watcher process simply waits on the pids of the children + # qrunners. Unless explicitly disabled by a mailmanctl switch (or the + # children are killed with SIGTERM instead of SIGINT), the watcher + # will automatically restart any child process that exits. This + # allows us to be more robust, and also to implement restart by simply + # SIGINT'ing the qrunner children, and letting the watcher restart + # them. + # + # Under normal operation, we have a child per queue. This lets us get + # the most out of the available resources, since a qrunner with no + # files in its queue directory is pretty cheap, but having a separate + # runner process per queue allows for a very responsive system. Some + # people want a more traditional (i.e. MM2.0.x) cron-invoked qrunner. + # No problem, but using mailmanctl isn't the answer. So while + # mailmanctl hard codes some things, others, such as the number of + # qrunners per queue, is configurable in mm_cfg.py. + # + # First, acquire the master mailmanctl lock + lock = acquire_lock(force) + if not lock: + return + # Daemon process startup according to Stevens, Advanced Programming in + # the UNIX Environment, Chapter 13. pid = os.fork() if pid: # parent - if not args.quiet: + if not quiet: print(C_("Starting Mailman's master qrunner.")) - # Give up the lock "ownership". This just means the foreground + # Give up the lock "ownership". This just means the foreground # process won't close/unlock the lock when it finalizes this lock - # instance. We'll let the master watcher subproc own the lock. + # instance. We'll let the mater watcher subproc own the lock. lock._transfer_to(pid) - - # Wait briefly to ensure child process starts - time.sleep(1) - - # Verify the child process is running - try: - os.kill(pid, 0) # Check if process exists - if not args.quiet: - print(C_('Master qrunner started successfully (pid: %d)') % pid) - syslog('qrunner', 'Master qrunner started successfully (pid: %d)', pid) - except OSError as e: - if e.errno == errno.ESRCH: - print(C_('Error: Master process failed to start'), file=sys.stderr) - return - raise return - # child + lock._take_possession() + # First, save our pid in a file for "mailmanctl stop" rendezvous. We + # want the perms on the .pid file to be rw-rw---- + omask = os.umask(6) try: - lock._take_possession() - - # Create a new session and become the session leader - os.setsid() - - # Be sure to close any open std{in,out,err} - devnull = os.open('/dev/null', 0) - os.dup2(devnull, 0) - os.dup2(devnull, 1) - os.dup2(devnull, 2) - - # Instead of cd'ing to root, cd to the Mailman installation home - os.chdir(mm_cfg.PREFIX) - - # Set our file mode creation umask - os.umask(0o07) - - # Write our PID to the PID file - try: - with open(mm_cfg.PIDFILE, 'w') as fp: - fp.write(str(os.getpid())) - except IOError as e: - syslog('error', 'Failed to write PID file: %s', str(e)) - os._exit(1) - - # Start all runners - kids = start_all_runners() - if not kids: - syslog('error', 'No runners started successfully') - os._exit(1) - - # Set up a SIGALRM handler to refresh the lock once per day - def sigalrm_handler(signum, frame, lock=lock): - lock.refresh() - signal.alarm(mm_cfg.days(1)) - signal.signal(signal.SIGALRM, sigalrm_handler) - signal.alarm(mm_cfg.days(1)) + fp = open(mm_cfg.PIDFILE, 'w') + print(os.getpid(), file=fp) + fp.close() + finally: + os.umask(omask) + # 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() - # Set up a SIGHUP handler - def sighup_handler(signum, frame, kids=kids): - syslog.close() - for pid in list(kids.keys()): - os.kill(pid, signal.SIGHUP) - syslog('qrunner', - 'Master watcher caught SIGHUP. Re-opening log files.') - signal.signal(signal.SIGHUP, sighup_handler) - - # Set up a SIGTERM handler - def sigterm_handler(signum, frame, kids=kids): - for pid in list(kids.keys()): - try: - os.kill(pid, signal.SIGTERM) - except OSError as e: - if e.errno != errno.ESRCH: raise - syslog('qrunner', 'Master watcher caught SIGTERM. Exiting.') - signal.signal(signal.SIGTERM, sigterm_handler) - - # Set up a SIGINT handler - def sigint_handler(signum, frame, kids=kids): - for pid in list(kids.keys()): - os.kill(pid, signal.SIGINT) - syslog('qrunner', 'Master watcher caught SIGINT. Restarting.') - signal.signal(signal.SIGINT, sigint_handler) - - # Now we're ready to simply do our wait/restart loop - while True: + # Be sure to close any open std{in,out,err} + devnull = os.open('/dev/null', 0) + os.dup2(devnull, 0) + os.dup2(devnull, 1) + os.dup2(devnull, 2) + + # Instead of cd'ing to root, cd to the Mailman installation home + os.chdir(mm_cfg.PREFIX) + # Set our file mode creation umask + os.umask(0o07) + # I don't think we have any unneeded file descriptors. + # + # Now start all the qrunners. This returns a dictionary where the + # keys are qrunner pids and the values are tuples of the following + # form: (qrname, slice, count). This does its own fork and exec, and + # sets up its own signal handlers. + kids = start_all_runners() + # Set up a SIGALRM handler to refresh the lock once per day. The lock + # lifetime is 1day+6hours so this should be plenty. + def sigalrm_handler(signum, frame, lock=lock): + lock.refresh() + signal.alarm(mm_cfg.days(1)) + signal.signal(signal.SIGALRM, sigalrm_handler) + signal.alarm(mm_cfg.days(1)) + # Set up a SIGHUP handler so that if we get one, we'll pass it along + # to all the qrunner children. This will tell them to close and + # reopen their log files + def sighup_handler(signum, frame, kids=kids): + # Closing our syslog will cause it to be re-opened at the next log + # print output. + syslog.close() + for pid in list(kids.keys()): + os.kill(pid, signal.SIGHUP) + # And just to tweak things... + syslog('qrunner', + 'Master watcher caught SIGHUP. Re-opening log files.') + signal.signal(signal.SIGHUP, sighup_handler) + # We also need to install a SIGTERM handler because that's what init + # will kill this process with when changing run levels. + def sigterm_handler(signum, frame, kids=kids): + for pid in list(kids.keys()): + try: + os.kill(pid, signal.SIGTERM) + except OSError as e: + if e.errno != errno.ESRCH: raise + syslog('qrunner', 'Master watcher caught SIGTERM. Exiting.') + signal.signal(signal.SIGTERM, sigterm_handler) + # Finally, we need a SIGINT handler which will cause the sub-qrunners + # to exit, but the master will restart SIGINT'd sub-processes unless + # the -n flag was given. + def sigint_handler(signum, frame, kids=kids): + for pid in list(kids.keys()): + os.kill(pid, signal.SIGINT) + syslog('qrunner', 'Master watcher caught SIGINT. Restarting.') + signal.signal(signal.SIGINT, sigint_handler) + # Now we're ready to simply do our wait/restart loop. This is the + # master qrunner watcher. + try: + while 1: try: pid, status = os.wait() except OSError as e: - # No children? We're done + # No children? We're done if e.errno == errno.ECHILD: break # If the system call got interrupted, just restart it. elif e.errno != errno.EINTR: raise continue - killsig = exitstatus = None if os.WIFSIGNALED(status): killsig = os.WTERMSIG(status) if os.WIFEXITED(status): exitstatus = os.WEXITSTATUS(status) - + # We'll restart the process unless we were given the + # "no-restart" switch, or if the process was SIGTERM'd or + # exitted with a SIGTERM exit status. This lets us better + # handle runaway restarts (say, if the subproc had a syntax + # error!) restarting = '' - if not args.no_restart: - # Only restart if the runner exited with SIGINT (normal exit) - # and not SIGTERM (error or forced stop) - if exitstatus == signal.SIGINT: + if restart: + if (exitstatus == None and killsig != signal.SIGTERM) or \ + (killsig == None and exitstatus != signal.SIGTERM): + # Then restarting = '[restarting]' - qrname, slice, count, restarts = kids[pid] del kids[pid] - - # Only log abnormal exits - if killsig == signal.SIGTERM or \ - (exitstatus is not None and exitstatus != signal.SIGINT): - syslog('qrunner', """\ -Master qrunner detected abnormal subprocess exit + syslog('qrunner', """\ +Master qrunner detected subprocess exit (pid: %d, sig: %s, sts: %s, class: %s, slice: %d/%d) %s""", pid, killsig, exitstatus, qrname, slice+1, count, restarting) - - if restarting and check_global_circuit_breaker(): - syslog('error', 'Global circuit breaker triggered - stopping all runners') - # Stop all processes and clean up - stop_all_processes(kids, lock) - # Exit the main loop - break - + # See if we've reached the maximum number of allowable restarts if exitstatus != signal.SIGINT: restarts += 1 if restarts > MAX_RESTARTS: @@ -743,24 +519,25 @@ Master qrunner detected abnormal subprocess exit Qrunner %s reached maximum restart limit of %d, not restarting.""", qrname, MAX_RESTARTS) restarting = '' - - # Now perhaps restart the process + # Now perhaps restart the process unless it exited with a + # SIGTERM or we aren't restarting. if restarting: newpid = start_runner(qrname, slice, count) kids[newpid] = (qrname, slice, count, restarts) - finally: - # all of our children are exited cleanly + # Should we leave the main loop for any reason, we want to be sure + # all of our children are exited cleanly. Send SIGTERMs to all + # the child processes and wait for them all to exit. for pid in list(kids.keys()): try: os.kill(pid, signal.SIGTERM) except OSError as e: if e.errno == errno.ESRCH: + # The child has already exited syslog('qrunner', 'ESRCH on pid: %d', pid) del kids[pid] - # Wait for all the children to go away - while True: + while 1: try: pid, status = os.wait() except OSError as e: @@ -769,26 +546,11 @@ Qrunner %s reached maximum restart limit of %d, not restarting.""", elif e.errno != errno.EINTR: raise continue - - # Finally, give up the lock - lock.unlock(unconditionally=1) - os._exit(0) - elif args.command == 'stop': - kill_watcher(signal.SIGTERM) - try: - os.unlink(mm_cfg.PIDFILE) - syslog('qrunner', 'Removed PID file: %s', mm_cfg.PIDFILE) - except OSError as e: - if e.errno != errno.ENOENT: - syslog('error', 'Failed to remove PID file %s: %s', mm_cfg.PIDFILE, str(e)) - elif args.command == 'restart': - kill_watcher(signal.SIGINT) - start_all_runners() - elif args.command == 'reopen': - kill_watcher(signal.SIGHUP) - else: - usage(1, C_('Unknown command: %(command)s')) + # Finally, give up the lock + lock.unlock(unconditionally=1) + os._exit(0) + if __name__ == '__main__': main() From f927dbc1cc5234264903ad8f0eeac2275f15757a Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Thu, 22 May 2025 21:04:10 -0400 Subject: [PATCH 682/748] update --- bin/mailmanctl | 691 +++++++++++++++++++++++++++++++++---------------- bin/qrunner | 6 + 2 files changed, 481 insertions(+), 216 deletions(-) diff --git a/bin/mailmanctl b/bin/mailmanctl index 36016e27..1dba8cc9 100755 --- a/bin/mailmanctl +++ b/bin/mailmanctl @@ -95,12 +95,12 @@ Commands: import sys import os import time -import getopt import signal import errno import pwd import grp import socket +import argparse import paths from Mailman import mm_cfg @@ -127,31 +127,113 @@ MAX_RESTARTS = 10 LogStdErr('error', 'mailmanctl', manual_reprime=0) - +def parse_args(): + """Parse command line arguments using argparse. + + Returns: + argparse.Namespace: Parsed command line arguments + """ + parser = argparse.ArgumentParser( + description=C_("Primary start-up and shutdown script for Mailman's qrunner daemon."), + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=C_("""\ +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 +""") + ) + + parser.add_argument('-n', '--no-restart', + action='store_true', + help=C_("""\ +Don't restart the qrunners when they exit because of an error or a +SIGINT. They are never restarted if they exit in response to a +SIGTERM. Use this only for debugging. Only useful if the `start' +command is given.""")) + + parser.add_argument('-u', '--run-as-user', + action='store_true', + help=C_("""\ +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_argument('-s', '--stale-lock-cleanup', + action='store_true', + help=C_("""\ +If mailmanctl finds an existing master lock, it will normally exit +with an error message. With this option, mailmanctl will perform an +extra level of checking. If a process matching the host/pid described +in the lock file is running, mailmanctl will still exit, but if no +matching process is found, mailmanctl will remove the apparently stale +lock and make another attempt to claim the master lock.""")) + + parser.add_argument('-q', '--quiet', + action='store_true', + help=C_("Don't print status messages. Error messages are still printed to standard error.")) + + parser.add_argument('command', + choices=['start', 'stop', 'restart', 'reopen'], + help=C_("Command to execute")) + + return parser.parse_args() + + def usage(code, msg=''): if code: fd = sys.stderr else: fd = sys.stdout - print(C_(__doc__), file=fd) + # In Python 3, sys.argv[0] is already a string + program = str(sys.argv[0]) # Ensure it's a string + doc = C_(__doc__) % {'PROGRAM': program} # Let C_() handle the translation and formatting + print(doc, file=fd) if msg: print(msg, file=fd) sys.exit(code) - def kill_watcher(sig): try: - fp = open(mm_cfg.PIDFILE) - pidstr = fp.read() - fp.close() - pid = int(pidstr.strip()) + with open(mm_cfg.PIDFILE, 'r') as fp: + content = fp.read().strip().split() + if len(content) >= 2: + pid = int(content[0]) + hostname = content[1] + if hostname != socket.gethostname(): + print(C_('PID file hostname mismatch: expected %(expected)s, got %(got)s') % + {'expected': socket.gethostname(), 'got': hostname}, file=sys.stderr) + return + else: + raise ValueError('Invalid PID file format') except (IOError, ValueError) as e: # For i18n convenience pidfile = mm_cfg.PIDFILE print(C_('PID unreadable in: %(pidfile)s'), file=sys.stderr) print(e, file=sys.stderr) print(C_('Is qrunner even running?'), file=sys.stderr) + print(C_('Lock file path: %(lockfile)s') % {'lockfile': LOCKFILE}, file=sys.stderr) return try: os.kill(pid, sig) @@ -163,21 +245,29 @@ def kill_watcher(sig): os.unlink(mm_cfg.PIDFILE) - def get_lock_data(): # Return the hostname, pid, and tempfile - fp = open(LOCKFILE) - filename = os.path.split(fp.read().strip())[1] - fp.close() - parts = filename.split('.') - hostname = DOT.join(parts[1:-1]) - pid = int(parts[-1]) - return hostname, int(pid), filename + try: + with open(LOCKFILE) as fp: + content = fp.read().strip().split() + if len(content) != 2: + syslog('error', 'Invalid lock file format in %s: expected "pid hostname"', LOCKFILE) + raise LockFile.LockError('Invalid lock file format') + try: + pid = int(content[0]) + hostname = content[1] + except ValueError as e: + syslog('error', 'Invalid PID in lock file %s: %s', LOCKFILE, e) + raise LockFile.LockError('Invalid PID in lock file') + return hostname, pid, None # tempfile is not used in this format + except IOError as e: + syslog('error', 'Could not read lock file %s: %s', LOCKFILE, e) + raise LockFile.LockError('Could not read lock file') def qrunner_state(): - # 1 if proc exists on host (but is it qrunner? ;) - # 0 if host matches but no proc + # 1 if proc exists on host and is owned by mailman user + # 0 if host matches but no proc or wrong owner # hostname if hostname doesn't match hostname, pid, tempfile = get_lock_data() if hostname != socket.gethostname(): @@ -185,10 +275,44 @@ def qrunner_state(): # Find out if the process exists by calling kill with a signal 0. try: os.kill(pid, 0) + # Process exists, now check if it's owned by the mailman user + mailman_uid = pwd.getpwnam(mm_cfg.MAILMAN_USER).pw_uid + try: + # Try to get process owner using platform-specific methods + if os.name == 'posix': + # On Unix-like systems, try to get process owner + try: + # Try using /proc on Linux + if os.path.exists('/proc'): + with open(f'/proc/{pid}/status') as f: + for line in f: + if line.startswith('Uid:'): + uid = int(line.split()[1]) + if uid != mailman_uid: + syslog('error', 'Process %d exists but is owned by uid %d, not mailman user %d', + pid, uid, mailman_uid) + return 0 + break + else: + # On other Unix systems, we can't easily check the owner + # without external tools, so we'll assume it's valid + # if the process exists + return 1 + except (IOError, OSError) as e: + syslog('error', 'Error checking process %d ownership: %s', pid, str(e)) + return 0 + else: + # On non-Unix systems, we can't easily check the owner + # without external tools, so we'll assume it's valid + # if the process exists + return 1 + return 1 + except Exception as e: + syslog('error', 'Error checking process %d ownership: %s', pid, str(e)) + return 0 except OSError as e: if e.errno != errno.ESRCH: raise return 0 - return 1 def acquire_lock_1(force): @@ -199,14 +323,28 @@ def acquire_lock_1(force): lock.lock(0.1) return lock except LockFile.TimeOutError: - # If we're not forcing or the lock can't be determined to be stale. - if not force or qrunner_state(): + # Check if the lock is stale by examining the process + status = qrunner_state() + if status == 1: + # Process exists and is running, so lock is valid raise - # Force removal of lock first - lock._disown() - hostname, pid, tempfile = get_lock_data() - os.unlink(LOCKFILE) - os.unlink(os.path.join(mm_cfg.LOCK_DIR, tempfile)) + # Lock appears to be stale - clean it up + try: + # Read the current lock file content + with open(LOCKFILE) as fp: + content = fp.read().strip() + if content: + # Try to clean up any stale lock files + lock.clean_stale_locks() + except (IOError, OSError) as e: + syslog('error', 'Error cleaning up stale lock: %s', str(e)) + # Remove the lock file + try: + os.unlink(LOCKFILE) + except OSError as e: + if e.errno != errno.ENOENT: + syslog('error', 'Error removing lock file: %s', str(e)) + # Try to acquire the lock again return acquire_lock_1(force=0) @@ -242,7 +380,6 @@ Lock host: %(status)s Exiting."""), file=sys.stderr) - def start_runner(qrname, slice, count): pid = os.fork() if pid: @@ -264,14 +401,19 @@ def start_all_runners(): kids = {} for qrname, count in mm_cfg.QRUNNERS: for slice in range(count): - # queue runner name, slice, numslices, restart count - info = (qrname, slice, count, 0) - pid = start_runner(qrname, slice, count) - kids[pid] = info + try: + # queue runner name, slice, numslices, restart count + info = (qrname, slice, count, 0) + pid = start_runner(qrname, slice, count) + kids[pid] = info + except Exception as e: + # Log the failure but continue with other runners + syslog('error', 'Failed to start %s runner (slice %d): %s', + qrname, slice, str(e)) + continue return kids - def check_for_site_list(): sitelistname = mm_cfg.MAILMAN_SITE_LIST try: @@ -306,212 +448,315 @@ def check_privs(): 'Run this program as root or as the %(name)s user, or use -u.')) - +def check_status(): + """Check if all qrunners are running as expected.""" + # First check if the master process is running + try: + with open(mm_cfg.PIDFILE, 'r') as fp: + content = fp.read().strip().split() + if len(content) >= 2: + pid = int(content[0]) + hostname = content[1] + if hostname != socket.gethostname(): + print(C_('PID file hostname mismatch: expected %(expected)s, got %(got)s') % + {'expected': socket.gethostname(), 'got': hostname}, file=sys.stderr) + return False + else: + raise ValueError('Invalid PID file format') + try: + os.kill(pid, 0) # Check if process exists + print(C_('Master qrunner process is running (pid: %(pid)d)') % {'pid': pid}) + except OSError: + print(C_('Master qrunner process is not running (stale pid file)')) + return False + except (IOError, ValueError) as e: + print(C_('Master qrunner process is not running (no pid file)')) + print(e, file=sys.stderr) + return False + + # Check if the lock file exists and is valid + try: + hostname, pid, tempfile = get_lock_data() + if hostname != socket.gethostname(): + print(C_('Lock file is held by another host: %(hostname)s') % {'hostname': hostname}) + return False + try: + os.kill(pid, 0) + print(C_('Lock file is valid (pid: %(pid)d)') % {'pid': pid}) + except OSError: + print(C_('Lock file is stale (process %(pid)d not running)') % {'pid': pid}) + return False + except (IOError, ValueError): + print(C_('No lock file found')) + return False + + # Check if all expected qrunners are running + expected_runners = dict(mm_cfg.QRUNNERS) + running_runners = {} + + # Get all running qrunner processes + for line in os.popen('ps aux | grep qrunner | grep -v grep').readlines(): + parts = line.split() + if len(parts) >= 12: # Ensure we have enough parts + cmd = parts[10] # The command is typically at index 10 + if '--runner=' in cmd: + runner_name = cmd.split('--runner=')[1].split(':')[0] + running_runners[runner_name] = running_runners.get(runner_name, 0) + 1 + + # Compare expected vs running + all_running = True + for runner, count in expected_runners.items(): + actual = running_runners.get(runner, 0) + if actual != count: + print(C_('%(runner)s: expected %(count)d instances, found %(actual)d') % + {'runner': runner, 'count': count, 'actual': actual}) + all_running = False + else: + print(C_('%(runner)s: %(count)d instances running') % + {'runner': runner, 'count': count}) + + return all_running + + +def check_global_circuit_breaker(): + """Check if we've exceeded the global restart limit. + + Returns: + bool: True if we should stop all runners, False otherwise + """ + # Circuit breaker disabled - always return False + return False + + +def stop_all_processes(kids, lock=None): + """Stop all child processes and clean up, similar to mailmanctl stop. + + Args: + kids: Dictionary of child processes + lock: Optional lock to release + """ + # First send SIGTERM to all children + for pid in list(kids.keys()): + try: + os.kill(pid, signal.SIGTERM) + except OSError as e: + if e.errno != errno.ESRCH: + raise + + # Wait for all children to exit + while kids: + try: + pid, status = os.wait() + if pid in kids: + del kids[pid] + except OSError as e: + if e.errno == errno.ECHILD: + break + elif e.errno != errno.EINTR: + raise + continue + + # Clean up PID file + try: + os.unlink(mm_cfg.PIDFILE) + syslog('qrunner', 'Removed PID file: %s', mm_cfg.PIDFILE) + except OSError as e: + if e.errno != errno.ENOENT: + syslog('error', 'Failed to remove PID file %s: %s', mm_cfg.PIDFILE, str(e)) + + # Release lock if provided + if lock: + try: + lock.unlock(unconditionally=1) + except Exception as e: + syslog('error', 'Failed to release lock: %s', str(e)) + + def main(): - global quiet try: - opts, args = getopt.getopt(sys.argv[1:], 'hnu:sq', - ['help', 'no-restart', 'run-as-user=', - 'stale-lock-cleanup', 'quiet']) - except getopt.error as msg: - usage(1, msg) - - restart = 1 - checkprivs = 1 - force = 0 - quiet = 0 - for opt, arg in opts: - if opt in ('-h', '--help'): - usage(0) - elif opt in ('-n', '--no-restart'): - restart = 0 - elif opt in ('-u', '--run-as-user'): - checkprivs = 0 - elif opt in ('-s', '--stale-lock-cleanup'): - force = 1 - elif opt in ('-q', '--quiet'): - quiet = 1 - - if len(args) < 1: - usage(1, C_('No command given.')) - elif len(args) > 1: - command = COMMASPACE.join(args) - usage(1, C_('Bad command: %(command)s')) - - if checkprivs: - check_privs() - else: - print(C_('Warning! You may encounter permission problems.')) - - # Handle the commands - command = args[0].lower() - if command == 'stop': - # Sent the master qrunner process a SIGINT, which is equivalent to - # giving cron/qrunner a ctrl-c or KeyboardInterrupt. This will - # effectively shut everything down. - if not quiet: - print(C_("Shutting down Mailman's master qrunner")) - kill_watcher(signal.SIGTERM) - elif command == 'restart': - # Sent the master qrunner process a SIGHUP. This will cause the - # master qrunner to kill and restart all the worker qrunners, and to - # close and re-open its log files. - if not quiet: - print(C_("Restarting Mailman's master qrunner")) - kill_watcher(signal.SIGINT) - elif command == 'reopen': - if not quiet: - print(C_('Re-opening all log files')) - kill_watcher(signal.SIGHUP) - elif command == 'start': - # First, complain loudly if there's no site list. - check_for_site_list() - # Here's the scoop on the processes we're about to create. We'll need - # one for each qrunner, and one for a master child process watcher / - # lock refresher process. - # - # The child watcher process simply waits on the pids of the children - # qrunners. Unless explicitly disabled by a mailmanctl switch (or the - # children are killed with SIGTERM instead of SIGINT), the watcher - # will automatically restart any child process that exits. This - # allows us to be more robust, and also to implement restart by simply - # SIGINT'ing the qrunner children, and letting the watcher restart - # them. - # - # Under normal operation, we have a child per queue. This lets us get - # the most out of the available resources, since a qrunner with no - # files in its queue directory is pretty cheap, but having a separate - # runner process per queue allows for a very responsive system. Some - # people want a more traditional (i.e. MM2.0.x) cron-invoked qrunner. - # No problem, but using mailmanctl isn't the answer. So while - # mailmanctl hard codes some things, others, such as the number of - # qrunners per queue, is configurable in mm_cfg.py. - # - # First, acquire the master mailmanctl lock - lock = acquire_lock(force) - if not lock: - return - # Daemon process startup according to Stevens, Advanced Programming in - # the UNIX Environment, Chapter 13. + args = parse_args() + except SystemExit: + usage(1) + + # Check that we're running as the right user + if not args.run_as_user: + try: + mailman_uid = pwd.getpwnam(mm_cfg.MAILMAN_USER).pw_uid + mailman_gid = grp.getgrnam(mm_cfg.MAILMAN_GROUP).gr_gid + except (KeyError, AttributeError): + print(C_('Cannot determine mailman user/group'), file=sys.stderr) + sys.exit(1) + + if os.getuid() == 0: + # We're root, so switch to the mailman user/group + os.setgid(mailman_gid) + os.setuid(mailman_uid) + elif os.getuid() != mailman_uid or os.getgid() != mailman_gid: + print(C_('Must be run as the mailman user'), file=sys.stderr) + sys.exit(1) + + # Handle the command + if args.command == 'status': + if check_status(): + sys.exit(0) + else: + sys.exit(1) + elif args.command == 'start': + # Check if we're already running + if os.path.exists(mm_cfg.PIDFILE): + try: + with open(mm_cfg.PIDFILE) as fp: + pid = int(fp.read().strip()) + if check_pid(pid): + print(C_('Mailman qrunner is already running (pid: %(pid)d)'), file=sys.stderr) + sys.exit(1) + except (ValueError, IOError): + pass + + # Try to acquire the lock + try: + lock = acquire_lock(args.stale_lock_cleanup) + except LockFile.TimeOutError: + sys.exit(1) + + # Fork to daemonize pid = os.fork() if pid: # parent - if not quiet: + if not args.quiet: print(C_("Starting Mailman's master qrunner.")) - # Give up the lock "ownership". This just means the foreground + # Give up the lock "ownership". This just means the foreground # process won't close/unlock the lock when it finalizes this lock - # instance. We'll let the mater watcher subproc own the lock. + # instance. We'll let the master watcher subproc own the lock. lock._transfer_to(pid) + + # Wait briefly to ensure child process starts + time.sleep(1) + + # Verify the child process is running + try: + os.kill(pid, 0) # Check if process exists + if not args.quiet: + print(C_('Master qrunner started successfully (pid: %d)') % pid) + syslog('qrunner', 'Master qrunner started successfully (pid: %d)', pid) + except OSError as e: + if e.errno == errno.ESRCH: + print(C_('Error: Master process failed to start'), file=sys.stderr) + return + raise return + # child - lock._take_possession() - # First, save our pid in a file for "mailmanctl stop" rendezvous. We - # want the perms on the .pid file to be rw-rw---- - omask = os.umask(6) try: - fp = open(mm_cfg.PIDFILE, 'w') - print(os.getpid(), file=fp) - fp.close() - finally: - os.umask(omask) - # 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() - - # Be sure to close any open std{in,out,err} - devnull = os.open('/dev/null', 0) - os.dup2(devnull, 0) - os.dup2(devnull, 1) - os.dup2(devnull, 2) - - # Instead of cd'ing to root, cd to the Mailman installation home - os.chdir(mm_cfg.PREFIX) - # Set our file mode creation umask - os.umask(0o07) - # I don't think we have any unneeded file descriptors. - # - # Now start all the qrunners. This returns a dictionary where the - # keys are qrunner pids and the values are tuples of the following - # form: (qrname, slice, count). This does its own fork and exec, and - # sets up its own signal handlers. - kids = start_all_runners() - # Set up a SIGALRM handler to refresh the lock once per day. The lock - # lifetime is 1day+6hours so this should be plenty. - def sigalrm_handler(signum, frame, lock=lock): - lock.refresh() + lock._take_possession() + + # Create a new session and become the session leader + os.setsid() + + # Be sure to close any open std{in,out,err} + devnull = os.open('/dev/null', 0) + os.dup2(devnull, 0) + os.dup2(devnull, 1) + os.dup2(devnull, 2) + + # Instead of cd'ing to root, cd to the Mailman installation home + os.chdir(mm_cfg.PREFIX) + + # Set our file mode creation umask + os.umask(0o07) + + # Write our PID to the PID file + try: + with open(mm_cfg.PIDFILE, 'w') as fp: + fp.write(str(os.getpid())) + except IOError as e: + syslog('error', 'Failed to write PID file: %s', str(e)) + os._exit(1) + + # Start all runners + kids = start_all_runners() + if not kids: + syslog('error', 'No runners started successfully') + os._exit(1) + + # Set up a SIGALRM handler to refresh the lock once per day + def sigalrm_handler(signum, frame, lock=lock): + lock.refresh() + signal.alarm(mm_cfg.days(1)) + signal.signal(signal.SIGALRM, sigalrm_handler) signal.alarm(mm_cfg.days(1)) - signal.signal(signal.SIGALRM, sigalrm_handler) - signal.alarm(mm_cfg.days(1)) - # Set up a SIGHUP handler so that if we get one, we'll pass it along - # to all the qrunner children. This will tell them to close and - # reopen their log files - def sighup_handler(signum, frame, kids=kids): - # Closing our syslog will cause it to be re-opened at the next log - # print output. - syslog.close() - for pid in list(kids.keys()): - os.kill(pid, signal.SIGHUP) - # And just to tweak things... - syslog('qrunner', - 'Master watcher caught SIGHUP. Re-opening log files.') - signal.signal(signal.SIGHUP, sighup_handler) - # We also need to install a SIGTERM handler because that's what init - # will kill this process with when changing run levels. - def sigterm_handler(signum, frame, kids=kids): - for pid in list(kids.keys()): - try: - os.kill(pid, signal.SIGTERM) - except OSError as e: - if e.errno != errno.ESRCH: raise - syslog('qrunner', 'Master watcher caught SIGTERM. Exiting.') - signal.signal(signal.SIGTERM, sigterm_handler) - # Finally, we need a SIGINT handler which will cause the sub-qrunners - # to exit, but the master will restart SIGINT'd sub-processes unless - # the -n flag was given. - def sigint_handler(signum, frame, kids=kids): - for pid in list(kids.keys()): - os.kill(pid, signal.SIGINT) - syslog('qrunner', 'Master watcher caught SIGINT. Restarting.') - signal.signal(signal.SIGINT, sigint_handler) - # Now we're ready to simply do our wait/restart loop. This is the - # master qrunner watcher. - try: - while 1: + + # Set up a SIGHUP handler + def sighup_handler(signum, frame, kids=kids): + syslog.close() + for pid in list(kids.keys()): + os.kill(pid, signal.SIGHUP) + syslog('qrunner', + 'Master watcher caught SIGHUP. Re-opening log files.') + signal.signal(signal.SIGHUP, sighup_handler) + + # Set up a SIGTERM handler + def sigterm_handler(signum, frame, kids=kids): + for pid in list(kids.keys()): + try: + os.kill(pid, signal.SIGTERM) + except OSError as e: + if e.errno != errno.ESRCH: raise + syslog('qrunner', 'Master watcher caught SIGTERM. Exiting.') + signal.signal(signal.SIGTERM, sigterm_handler) + + # Set up a SIGINT handler + def sigint_handler(signum, frame, kids=kids): + for pid in list(kids.keys()): + os.kill(pid, signal.SIGINT) + syslog('qrunner', 'Master watcher caught SIGINT. Restarting.') + signal.signal(signal.SIGINT, sigint_handler) + + # Now we're ready to simply do our wait/restart loop + while True: try: pid, status = os.wait() except OSError as e: - # No children? We're done + # No children? We're done if e.errno == errno.ECHILD: break # If the system call got interrupted, just restart it. elif e.errno != errno.EINTR: raise continue + killsig = exitstatus = None if os.WIFSIGNALED(status): killsig = os.WTERMSIG(status) if os.WIFEXITED(status): exitstatus = os.WEXITSTATUS(status) - # We'll restart the process unless we were given the - # "no-restart" switch, or if the process was SIGTERM'd or - # exitted with a SIGTERM exit status. This lets us better - # handle runaway restarts (say, if the subproc had a syntax - # error!) + restarting = '' - if restart: - if (exitstatus == None and killsig != signal.SIGTERM) or \ - (killsig == None and exitstatus != signal.SIGTERM): - # Then + if not args.no_restart: + # Only restart if the runner exited with SIGINT (normal exit) + # and not SIGTERM (error or forced stop) + if exitstatus == signal.SIGINT: restarting = '[restarting]' + qrname, slice, count, restarts = kids[pid] del kids[pid] - syslog('qrunner', """\ -Master qrunner detected subprocess exit + + # Only log abnormal exits + if killsig == signal.SIGTERM or \ + (exitstatus is not None and exitstatus != signal.SIGINT): + syslog('qrunner', """\ +Master qrunner detected abnormal subprocess exit (pid: %d, sig: %s, sts: %s, class: %s, slice: %d/%d) %s""", pid, killsig, exitstatus, qrname, slice+1, count, restarting) - # See if we've reached the maximum number of allowable restarts + + if restarting and check_global_circuit_breaker(): + syslog('error', 'Global circuit breaker triggered - stopping all runners') + # Stop all processes and clean up + stop_all_processes(kids, lock) + # Exit the main loop + break + if exitstatus != signal.SIGINT: restarts += 1 if restarts > MAX_RESTARTS: @@ -519,25 +764,24 @@ Master qrunner detected subprocess exit Qrunner %s reached maximum restart limit of %d, not restarting.""", qrname, MAX_RESTARTS) restarting = '' - # Now perhaps restart the process unless it exited with a - # SIGTERM or we aren't restarting. + + # Now perhaps restart the process if restarting: newpid = start_runner(qrname, slice, count) kids[newpid] = (qrname, slice, count, restarts) + finally: - # Should we leave the main loop for any reason, we want to be sure - # all of our children are exited cleanly. Send SIGTERMs to all - # the child processes and wait for them all to exit. + # all of our children are exited cleanly for pid in list(kids.keys()): try: os.kill(pid, signal.SIGTERM) except OSError as e: if e.errno == errno.ESRCH: - # The child has already exited syslog('qrunner', 'ESRCH on pid: %d', pid) del kids[pid] + # Wait for all the children to go away - while 1: + while True: try: pid, status = os.wait() except OSError as e: @@ -546,11 +790,26 @@ Qrunner %s reached maximum restart limit of %d, not restarting.""", elif e.errno != errno.EINTR: raise continue - # Finally, give up the lock - lock.unlock(unconditionally=1) - os._exit(0) + + # Finally, give up the lock + lock.unlock(unconditionally=1) + os._exit(0) + elif args.command == 'stop': + kill_watcher(signal.SIGTERM) + try: + os.unlink(mm_cfg.PIDFILE) + syslog('qrunner', 'Removed PID file: %s', mm_cfg.PIDFILE) + except OSError as e: + if e.errno != errno.ENOENT: + syslog('error', 'Failed to remove PID file %s: %s', mm_cfg.PIDFILE, str(e)) + elif args.command == 'restart': + kill_watcher(signal.SIGINT) + start_all_runners() + elif args.command == 'reopen': + kill_watcher(signal.SIGHUP) + else: + usage(1, C_('Unknown command: %(command)s')) - if __name__ == '__main__': main() diff --git a/bin/qrunner b/bin/qrunner index 0ab62721..e0ed3dfe 100644 --- a/bin/qrunner +++ b/bin/qrunner @@ -269,6 +269,9 @@ def main(): # Now start up the main loop syslog('qrunner', '%s qrunner started.', loop.name()) qrunner.run() + # Only exit with SIGINT if we're stopping normally + if not qrunner._stop: + loop.status = signal.SIGINT syslog('qrunner', '%s qrunner exiting.', loop.name()) else: # Anything else we have to handle a bit more specially @@ -299,6 +302,9 @@ def main(): syslog('qrunner', 'Now doing a %s qrunner iteration', qrunner.__class__.__bases__[0].__name__) qrunner.run() + # Only exit with SIGINT if we're stopping normally + if not qrunner._stop: + loop.status = signal.SIGINT if args.once: break if mm_cfg.QRUNNER_SLEEP_TIME > 0: From c8e9970fa23f1ad38170b3a60815108d7d0b6257 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 24 May 2025 19:10:11 -0400 Subject: [PATCH 683/748] Remove max-width: 800px; from all template files to allow full page width display --- Mailman/htmlformat.py | 5 +---- templates/ar/admindbdetails.html | 1 - templates/ar/admindbpreamble.html | 1 - templates/ar/admindbsummary.html | 1 - templates/ar/admlogin.html | 1 - templates/ar/archidxentry.html | 1 - templates/ar/archidxfoot.html | 1 - templates/ar/archidxhead.html | 1 - templates/ar/archlistend.html | 1 - templates/ar/archliststart.html | 1 - templates/ar/archtoc.html | 1 - templates/ar/archtocentry.html | 1 - templates/ar/archtocnombox.html | 1 - templates/ar/emptyarchive.html | 1 - templates/ar/headfoot.html | 1 - templates/ar/listinfo.html | 1 - templates/ar/options.html | 1 - templates/ar/private.html | 1 - templates/ar/roster.html | 1 - templates/ar/subscribe.html | 1 - templates/ast/admindbdetails.html | 1 - templates/ast/admindbpreamble.html | 1 - templates/ast/admindbsummary.html | 1 - templates/ast/admlogin.html | 1 - templates/ast/archidxentry.html | 1 - templates/ast/archidxfoot.html | 1 - templates/ast/archidxhead.html | 1 - templates/ast/archlistend.html | 1 - templates/ast/archliststart.html | 1 - templates/ast/archtoc.html | 1 - templates/ast/archtocentry.html | 1 - templates/ast/archtocnombox.html | 1 - templates/ast/article.html | 1 - templates/ast/emptyarchive.html | 1 - templates/ast/headfoot.html | 1 - templates/ast/listinfo.html | 1 - templates/ast/options.html | 1 - templates/ast/private.html | 1 - templates/ast/roster.html | 1 - templates/ast/subscribe.html | 1 - templates/ca/admindbdetails.html | 1 - templates/ca/admindbpreamble.html | 1 - templates/ca/admindbsummary.html | 1 - templates/ca/admlogin.html | 1 - templates/ca/archidxentry.html | 1 - templates/ca/archidxfoot.html | 1 - templates/ca/archidxhead.html | 1 - templates/ca/archlistend.html | 1 - templates/ca/archliststart.html | 1 - templates/ca/archtoc.html | 1 - templates/ca/archtocentry.html | 1 - templates/ca/archtocnombox.html | 1 - templates/ca/emptyarchive.html | 1 - templates/ca/headfoot.html | 1 - templates/ca/listinfo.html | 1 - templates/ca/options.html | 1 - templates/ca/private.html | 1 - templates/ca/roster.html | 1 - templates/ca/subscribe.html | 1 - templates/cs/admindbdetails.html | 1 - templates/cs/admindbpreamble.html | 1 - templates/cs/admindbsummary.html | 1 - templates/cs/admlogin.html | 1 - templates/cs/archidxentry.html | 1 - templates/cs/archidxfoot.html | 1 - templates/cs/archidxhead.html | 1 - templates/cs/archlistend.html | 1 - templates/cs/archliststart.html | 1 - templates/cs/archtoc.html | 1 - templates/cs/archtocentry.html | 1 - templates/cs/archtocnombox.html | 1 - templates/cs/emptyarchive.html | 1 - templates/cs/headfoot.html | 1 - templates/cs/listinfo.html | 1 - templates/cs/options.html | 1 - templates/cs/private.html | 1 - templates/cs/roster.html | 1 - templates/cs/subscribe.html | 1 - templates/da/admindbdetails.html | 1 - templates/da/admindbpreamble.html | 1 - templates/da/admindbsummary.html | 1 - templates/da/admlogin.html | 1 - templates/da/archidxfoot.html | 1 - templates/da/archidxhead.html | 1 - templates/da/archliststart.html | 1 - templates/da/archtoc.html | 1 - templates/da/archtocentry.html | 1 - templates/da/archtocnombox.html | 1 - templates/da/emptyarchive.html | 1 - templates/da/headfoot.html | 1 - templates/da/listinfo.html | 1 - templates/da/options.html | 1 - templates/da/private.html | 1 - templates/da/roster.html | 1 - templates/da/subscribe.html | 1 - templates/de/admindbdetails.html | 1 - templates/de/admindbpreamble.html | 1 - templates/de/admindbsummary.html | 1 - templates/de/admlogin.html | 1 - templates/de/archidxentry.html | 1 - templates/de/archidxfoot.html | 1 - templates/de/archidxhead.html | 1 - templates/de/archlistend.html | 1 - templates/de/archliststart.html | 1 - templates/de/archtoc.html | 1 - templates/de/archtocentry.html | 1 - templates/de/archtocnombox.html | 1 - templates/de/emptyarchive.html | 1 - templates/de/headfoot.html | 1 - templates/de/listinfo.html | 1 - templates/de/options.html | 1 - templates/de/private.html | 1 - templates/de/roster.html | 1 - templates/de/subscribe.html | 1 - templates/el/admindbdetails.html | 1 - templates/el/admindbpreamble.html | 1 - templates/el/admindbsummary.html | 1 - templates/el/admlogin.html | 1 - templates/el/archidxentry.html | 1 - templates/el/archidxfoot.html | 1 - templates/el/archidxhead.html | 1 - templates/el/archlistend.html | 1 - templates/el/archliststart.html | 1 - templates/el/archtoc.html | 1 - templates/el/archtocentry.html | 1 - templates/el/archtocnombox.html | 1 - templates/el/emptyarchive.html | 1 - templates/el/headfoot.html | 1 - templates/el/listinfo.html | 1 - templates/el/options.html | 1 - templates/el/private.html | 1 - templates/el/roster.html | 1 - templates/el/subscribe.html | 1 - templates/en/admindbdetails.html | 1 - templates/en/admindbpreamble.html | 1 - templates/en/admindbsummary.html | 1 - templates/en/admlogin.html | 1 - templates/en/archidxentry.html | 1 - templates/en/archidxfoot.html | 1 - templates/en/archidxhead.html | 1 - templates/en/archlistend.html | 1 - templates/en/archliststart.html | 1 - templates/en/archtoc.html | 1 - templates/en/archtocentry.html | 1 - templates/en/archtocnombox.html | 1 - templates/en/emptyarchive.html | 1 - templates/en/headfoot.html | 1 - templates/en/listinfo.html | 1 - templates/en/options.html | 1 - templates/en/private.html | 1 - templates/en/roster.html | 1 - templates/en/subscribe.html | 1 - templates/eo/admlogin.html | 1 - templates/eo/archidxentry.html | 1 - templates/eo/archidxfoot.html | 1 - templates/eo/archidxhead.html | 1 - templates/eo/archlistend.html | 1 - templates/eo/archliststart.html | 1 - templates/eo/archtoc.html | 1 - templates/eo/archtocentry.html | 1 - templates/eo/archtocnombox.html | 1 - templates/eo/emptyarchive.html | 1 - templates/eo/listinfo.html | 1 - templates/eo/options.html | 1 - templates/eo/private.html | 1 - templates/eo/roster.html | 1 - templates/eo/subscribe.html | 1 - templates/es/admindbdetails.html | 1 - templates/es/admindbpreamble.html | 1 - templates/es/admindbsummary.html | 1 - templates/es/admlogin.html | 1 - templates/es/archidxentry.html | 1 - templates/es/archidxfoot.html | 1 - templates/es/archidxhead.html | 1 - templates/es/archlistend.html | 1 - templates/es/archliststart.html | 1 - templates/es/archtoc.html | 1 - templates/es/archtocentry.html | 1 - templates/es/archtocnombox.html | 1 - templates/es/emptyarchive.html | 1 - templates/es/handle_opts.html | 1 - templates/es/headfoot.html | 1 - templates/es/listinfo.html | 1 - templates/es/options.html | 1 - templates/es/private.html | 1 - templates/es/roster.html | 1 - templates/es/subscribe.html | 1 - templates/et/admindbdetails.html | 1 - templates/et/admindbpreamble.html | 1 - templates/et/admindbsummary.html | 1 - templates/et/admlogin.html | 1 - templates/et/emptyarchive.html | 1 - templates/et/headfoot.html | 1 - templates/et/listinfo.html | 1 - templates/et/options.html | 1 - templates/et/private.html | 1 - templates/et/roster.html | 1 - templates/et/subscribe.html | 1 - templates/eu/admindbdetails.html | 1 - templates/eu/admindbpreamble.html | 1 - templates/eu/admindbsummary.html | 1 - templates/eu/admlogin.html | 1 - templates/eu/archidxentry.html | 1 - templates/eu/archidxfoot.html | 1 - templates/eu/archidxhead.html | 1 - templates/eu/archlistend.html | 1 - templates/eu/archliststart.html | 1 - templates/eu/archtoc.html | 1 - templates/eu/archtocentry.html | 1 - templates/eu/emptyarchive.html | 1 - templates/eu/headfoot.html | 1 - templates/eu/listinfo.html | 1 - templates/eu/options.html | 1 - templates/eu/private.html | 1 - templates/eu/roster.html | 1 - templates/eu/subscribe.html | 1 - templates/fa/admlogin.html | 1 - templates/fa/archidxfoot.html | 1 - templates/fa/archidxhead.html | 1 - templates/fa/archliststart.html | 1 - templates/fa/archtoc.html | 1 - templates/fa/archtocentry.html | 1 - templates/fa/archtocnombox.html | 1 - templates/fa/emptyarchive.html | 1 - templates/fa/listinfo.html | 1 - templates/fa/options.html | 1 - templates/fa/private.html | 1 - templates/fa/roster.html | 1 - templates/fa/subscribe.html | 1 - templates/fi/admindbdetails.html | 1 - templates/fi/admindbpreamble.html | 1 - templates/fi/admindbsummary.html | 1 - templates/fi/admlogin.html | 1 - templates/fi/headfoot.html | 1 - templates/fi/listinfo.html | 1 - templates/fi/options.html | 1 - templates/fi/private.html | 1 - templates/fi/roster.html | 1 - templates/fi/subscribe.html | 1 - templates/fr/admindbdetails.html | 1 - templates/fr/admindbpreamble.html | 1 - templates/fr/admindbsummary.html | 1 - templates/fr/admlogin.html | 1 - templates/fr/archidxentry.html | 1 - templates/fr/archidxfoot.html | 1 - templates/fr/archidxhead.html | 1 - templates/fr/archlistend.html | 1 - templates/fr/archliststart.html | 1 - templates/fr/archtoc.html | 1 - templates/fr/archtocentry.html | 1 - templates/fr/archtocnombox.html | 1 - templates/fr/emptyarchive.html | 1 - templates/fr/handle_opts.html | 1 - templates/fr/headfoot.html | 1 - templates/fr/listinfo.html | 1 - templates/fr/options.html | 1 - templates/fr/private.html | 1 - templates/fr/roster.html | 1 - templates/fr/subscribe.html | 1 - templates/gl/admindbdetails.html | 1 - templates/gl/admindbpreamble.html | 1 - templates/gl/admindbsummary.html | 1 - templates/gl/admlogin.html | 1 - templates/gl/archidxentry.html | 1 - templates/gl/archidxfoot.html | 1 - templates/gl/archidxhead.html | 1 - templates/gl/archlistend.html | 1 - templates/gl/archliststart.html | 1 - templates/gl/archtoc.html | 1 - templates/gl/archtocentry.html | 1 - templates/gl/emptyarchive.html | 1 - templates/gl/handle_opts.html | 1 - templates/gl/headfoot.html | 1 - templates/gl/listinfo.html | 1 - templates/gl/options.html | 1 - templates/gl/private.html | 1 - templates/gl/roster.html | 1 - templates/gl/subscribe.html | 1 - templates/he/admindbdetails.html | 1 - templates/he/admindbpreamble.html | 1 - templates/he/admindbsummary.html | 1 - templates/he/admlogin.html | 1 - templates/he/archidxentry.html | 1 - templates/he/archidxfoot.html | 1 - templates/he/archidxhead.html | 1 - templates/he/archlistend.html | 1 - templates/he/archliststart.html | 1 - templates/he/archtoc.html | 1 - templates/he/archtocentry.html | 1 - templates/he/archtocnombox.html | 1 - templates/he/emptyarchive.html | 1 - templates/he/headfoot.html | 1 - templates/he/listinfo.html | 1 - templates/he/options.html | 1 - templates/he/private.html | 1 - templates/he/roster.html | 1 - templates/he/subscribe.html | 1 - templates/hr/admindbdetails.html | 1 - templates/hr/admindbpreamble.html | 1 - templates/hr/admindbsummary.html | 1 - templates/hr/admlogin.html | 1 - templates/hr/archidxentry.html | 1 - templates/hr/archidxfoot.html | 1 - templates/hr/archidxhead.html | 1 - templates/hr/archlistend.html | 1 - templates/hr/archliststart.html | 1 - templates/hr/archtoc.html | 1 - templates/hr/archtocentry.html | 1 - templates/hr/emptyarchive.html | 1 - templates/hr/headfoot.html | 1 - templates/hr/listinfo.html | 1 - templates/hr/options.html | 1 - templates/hr/private.html | 1 - templates/hr/roster.html | 1 - templates/hr/subscribe.html | 1 - templates/hu/admindbdetails.html | 1 - templates/hu/admindbpreamble.html | 1 - templates/hu/admindbsummary.html | 1 - templates/hu/admlogin.html | 1 - templates/hu/archidxentry.html | 1 - templates/hu/archidxfoot.html | 1 - templates/hu/archidxhead.html | 1 - templates/hu/archlistend.html | 1 - templates/hu/archliststart.html | 1 - templates/hu/archtoc.html | 1 - templates/hu/archtocentry.html | 1 - templates/hu/emptyarchive.html | 1 - templates/hu/headfoot.html | 1 - templates/hu/illik.html | 1 - templates/hu/listinfo.html | 1 - templates/hu/options.html | 1 - templates/hu/private.html | 1 - templates/hu/roster.html | 1 - templates/hu/subscribe.html | 1 - templates/ia/admindbdetails.html | 1 - templates/ia/admindbpreamble.html | 1 - templates/ia/admindbsummary.html | 1 - templates/ia/admlogin.html | 1 - templates/ia/archidxentry.html | 1 - templates/ia/archidxfoot.html | 1 - templates/ia/archidxhead.html | 1 - templates/ia/archlistend.html | 1 - templates/ia/archliststart.html | 1 - templates/ia/archtoc.html | 1 - templates/ia/archtocentry.html | 1 - templates/ia/archtocnombox.html | 1 - templates/ia/emptyarchive.html | 1 - templates/ia/headfoot.html | 1 - templates/ia/listinfo.html | 1 - templates/ia/options.html | 1 - templates/ia/private.html | 1 - templates/ia/roster.html | 1 - templates/ia/subscribe.html | 1 - templates/it/admindbdetails.html | 1 - templates/it/admindbpreamble.html | 1 - templates/it/admindbsummary.html | 1 - templates/it/admlogin.html | 1 - templates/it/archidxentry.html | 1 - templates/it/archidxfoot.html | 1 - templates/it/archidxhead.html | 1 - templates/it/archlistend.html | 1 - templates/it/archliststart.html | 1 - templates/it/archtoc.html | 1 - templates/it/archtocentry.html | 1 - templates/it/archtocnombox.html | 1 - templates/it/emptyarchive.html | 1 - templates/it/headfoot.html | 1 - templates/it/listinfo.html | 1 - templates/it/options.html | 1 - templates/it/private.html | 1 - templates/it/roster.html | 1 - templates/it/subscribe.html | 1 - templates/ja/admindbdetails.html | 1 - templates/ja/admindbpreamble.html | 1 - templates/ja/admindbsummary.html | 1 - templates/ja/admlogin.html | 1 - templates/ja/archidxentry.html | 1 - templates/ja/archidxfoot.html | 1 - templates/ja/archidxhead.html | 1 - templates/ja/archlistend.html | 1 - templates/ja/archliststart.html | 1 - templates/ja/archtoc.html | 1 - templates/ja/archtocentry.html | 1 - templates/ja/archtocnombox.html | 1 - templates/ja/emptyarchive.html | 1 - templates/ja/headfoot.html | 1 - templates/ja/listinfo.html | 1 - templates/ja/options.html | 1 - templates/ja/private.html | 1 - templates/ja/roster.html | 1 - templates/ja/subscribe.html | 1 - templates/ko/admindbdetails.html | 1 - templates/ko/admindbpreamble.html | 1 - templates/ko/admindbsummary.html | 1 - templates/ko/admlogin.html | 1 - templates/ko/emptyarchive.html | 1 - templates/ko/headfoot.html | 1 - templates/ko/listinfo.html | 1 - templates/ko/options.html | 1 - templates/ko/private.html | 1 - templates/ko/roster.html | 1 - templates/ko/subscribe.html | 1 - templates/lt/admindbdetails.html | 1 - templates/lt/admindbpreamble.html | 1 - templates/lt/admindbsummary.html | 1 - templates/lt/admlogin.html | 1 - templates/lt/archidxentry.html | 1 - templates/lt/archidxfoot.html | 1 - templates/lt/archidxhead.html | 1 - templates/lt/archlistend.html | 1 - templates/lt/archliststart.html | 1 - templates/lt/archtoc.html | 1 - templates/lt/archtocentry.html | 1 - templates/lt/emptyarchive.html | 1 - templates/lt/headfoot.html | 1 - templates/lt/listinfo.html | 1 - templates/lt/options.html | 1 - templates/lt/private.html | 1 - templates/lt/roster.html | 1 - templates/lt/subscribe.html | 1 - templates/nl/admindbdetails.html | 1 - templates/nl/admindbpreamble.html | 1 - templates/nl/admindbsummary.html | 1 - templates/nl/admlogin.html | 1 - templates/nl/archidxentry.html | 1 - templates/nl/archidxfoot.html | 1 - templates/nl/archidxhead.html | 1 - templates/nl/archlistend.html | 1 - templates/nl/archliststart.html | 1 - templates/nl/archtoc.html | 1 - templates/nl/archtocentry.html | 1 - templates/nl/archtocnombox.html | 1 - templates/nl/emptyarchive.html | 1 - templates/nl/headfoot.html | 1 - templates/nl/listinfo.html | 1 - templates/nl/options.html | 1 - templates/nl/private.html | 1 - templates/nl/roster.html | 1 - templates/nl/subscribe.html | 1 - templates/no/admindbdetails.html | 1 - templates/no/admindbpreamble.html | 1 - templates/no/admindbsummary.html | 1 - templates/no/admlogin.html | 1 - templates/no/archidxfoot.html | 1 - templates/no/archidxhead.html | 1 - templates/no/archliststart.html | 1 - templates/no/archtoc.html | 1 - templates/no/archtocentry.html | 1 - templates/no/archtocnombox.html | 1 - templates/no/emptyarchive.html | 1 - templates/no/headfoot.html | 1 - templates/no/listinfo.html | 1 - templates/no/options.html | 1 - templates/no/private.html | 1 - templates/no/roster.html | 1 - templates/no/subscribe.html | 1 - templates/pl/admlogin.html | 1 - templates/pl/archidxentry.html | 1 - templates/pl/archidxfoot.html | 1 - templates/pl/archidxhead.html | 1 - templates/pl/archlistend.html | 1 - templates/pl/archliststart.html | 1 - templates/pl/archtoc.html | 1 - templates/pl/archtocentry.html | 1 - templates/pl/archtocnombox.html | 1 - templates/pl/emptyarchive.html | 1 - templates/pl/listinfo.html | 1 - templates/pl/options.html | 1 - templates/pl/private.html | 1 - templates/pl/roster.html | 1 - templates/pl/subscribe.html | 1 - templates/pt/admindbdetails.html | 1 - templates/pt/admindbpreamble.html | 1 - templates/pt/admindbsummary.html | 1 - templates/pt/admlogin.html | 1 - templates/pt/archidxentry.html | 1 - templates/pt/archidxfoot.html | 1 - templates/pt/archidxhead.html | 1 - templates/pt/archlistend.html | 1 - templates/pt/archliststart.html | 1 - templates/pt/archtoc.html | 1 - templates/pt/archtocentry.html | 1 - templates/pt/emptyarchive.html | 1 - templates/pt/headfoot.html | 1 - templates/pt/listinfo.html | 1 - templates/pt/options.html | 1 - templates/pt/private.html | 1 - templates/pt/roster.html | 1 - templates/pt/subscribe.html | 1 - templates/pt_BR/admindbdetails.html | 1 - templates/pt_BR/admindbpreamble.html | 1 - templates/pt_BR/admindbsummary.html | 1 - templates/pt_BR/admlogin.html | 1 - templates/pt_BR/archidxentry.html | 1 - templates/pt_BR/archidxfoot.html | 1 - templates/pt_BR/archidxhead.html | 1 - templates/pt_BR/archlistend.html | 1 - templates/pt_BR/archliststart.html | 1 - templates/pt_BR/archtoc.html | 1 - templates/pt_BR/archtocentry.html | 1 - templates/pt_BR/emptyarchive.html | 1 - templates/pt_BR/headfoot.html | 1 - templates/pt_BR/listinfo.html | 1 - templates/pt_BR/options.html | 1 - templates/pt_BR/private.html | 1 - templates/pt_BR/roster.html | 1 - templates/pt_BR/subscribe.html | 1 - templates/ro/admindbdetails.html | 1 - templates/ro/admindbpreamble.html | 1 - templates/ro/admindbsummary.html | 1 - templates/ro/admlogin.html | 1 - templates/ro/archidxentry.html | 1 - templates/ro/archidxfoot.html | 1 - templates/ro/archidxhead.html | 1 - templates/ro/archlistend.html | 1 - templates/ro/archliststart.html | 1 - templates/ro/archtoc.html | 1 - templates/ro/archtocentry.html | 1 - templates/ro/emptyarchive.html | 1 - templates/ro/headfoot.html | 1 - templates/ro/listinfo.html | 1 - templates/ro/options.html | 1 - templates/ro/private.html | 1 - templates/ro/roster.html | 1 - templates/ro/subscribe.html | 1 - templates/ru/admindbdetails.html | 1 - templates/ru/admindbpreamble.html | 1 - templates/ru/admindbsummary.html | 1 - templates/ru/admlogin.html | 1 - templates/ru/archidxentry.html | 1 - templates/ru/archidxfoot.html | 1 - templates/ru/archidxhead.html | 1 - templates/ru/archlistend.html | 1 - templates/ru/archliststart.html | 1 - templates/ru/archtoc.html | 1 - templates/ru/archtocentry.html | 1 - templates/ru/archtocnombox.html | 1 - templates/ru/emptyarchive.html | 1 - templates/ru/headfoot.html | 1 - templates/ru/listinfo.html | 1 - templates/ru/options.html | 1 - templates/ru/private.html | 1 - templates/ru/roster.html | 1 - templates/ru/subscribe.html | 1 - templates/sk/admindbdetails.html | 1 - templates/sk/admindbpreamble.html | 1 - templates/sk/admindbsummary.html | 1 - templates/sk/admlogin.html | 1 - templates/sk/archidxentry.html | 1 - templates/sk/archidxfoot.html | 1 - templates/sk/archidxhead.html | 1 - templates/sk/archlistend.html | 1 - templates/sk/archliststart.html | 1 - templates/sk/archtoc.html | 1 - templates/sk/archtocentry.html | 1 - templates/sk/archtocnombox.html | 1 - templates/sk/emptyarchive.html | 1 - templates/sk/headfoot.html | 1 - templates/sk/listinfo.html | 1 - templates/sk/options.html | 1 - templates/sk/private.html | 1 - templates/sk/roster.html | 1 - templates/sk/subscribe.html | 1 - templates/sl/admindbdetails.html | 1 - templates/sl/admindbpreamble.html | 1 - templates/sl/admindbsummary.html | 1 - templates/sl/admlogin.html | 1 - templates/sl/archidxentry.html | 1 - templates/sl/archidxfoot.html | 1 - templates/sl/archidxhead.html | 1 - templates/sl/archlistend.html | 1 - templates/sl/archliststart.html | 1 - templates/sl/archtoc.html | 1 - templates/sl/archtocentry.html | 1 - templates/sl/emptyarchive.html | 1 - templates/sl/headfoot.html | 1 - templates/sl/listinfo.html | 1 - templates/sl/options.html | 1 - templates/sl/private.html | 1 - templates/sl/roster.html | 1 - templates/sl/subscribe.html | 1 - templates/sr/admindbdetails.html | 1 - templates/sr/admindbpreamble.html | 1 - templates/sr/admindbsummary.html | 1 - templates/sr/admlogin.html | 1 - templates/sr/archidxentry.html | 1 - templates/sr/archidxfoot.html | 1 - templates/sr/archidxhead.html | 1 - templates/sr/archlistend.html | 1 - templates/sr/archliststart.html | 1 - templates/sr/archtoc.html | 1 - templates/sr/archtocentry.html | 1 - templates/sr/emptyarchive.html | 1 - templates/sr/handle_opts.html | 1 - templates/sr/headfoot.html | 1 - templates/sr/listinfo.html | 1 - templates/sr/options.html | 1 - templates/sr/private.html | 1 - templates/sr/roster.html | 1 - templates/sr/subscribe.html | 1 - templates/sv/admindbdetails.html | 1 - templates/sv/admindbpreamble.html | 1 - templates/sv/admindbsummary.html | 1 - templates/sv/admlogin.html | 1 - templates/sv/archtoc.html | 1 - templates/sv/archtocentry.html | 1 - templates/sv/emptyarchive.html | 1 - templates/sv/headfoot.html | 1 - templates/sv/listinfo.html | 1 - templates/sv/options.html | 1 - templates/sv/private.html | 1 - templates/sv/roster.html | 1 - templates/sv/subscribe.html | 1 - templates/tr/admindbdetails.html | 1 - templates/tr/admindbpreamble.html | 1 - templates/tr/admindbsummary.html | 1 - templates/tr/admlogin.html | 1 - templates/tr/archidxentry.html | 1 - templates/tr/archidxfoot.html | 1 - templates/tr/archidxhead.html | 1 - templates/tr/archlistend.html | 1 - templates/tr/archliststart.html | 1 - templates/tr/archtoc.html | 1 - templates/tr/archtocentry.html | 1 - templates/tr/archtocnombox.html | 1 - templates/tr/emptyarchive.html | 1 - templates/tr/headfoot.html | 1 - templates/tr/listinfo.html | 1 - templates/tr/options.html | 1 - templates/tr/private.html | 1 - templates/tr/roster.html | 1 - templates/tr/subscribe.html | 1 - templates/uk/admindbdetails.html | 1 - templates/uk/admindbpreamble.html | 1 - templates/uk/admindbsummary.html | 1 - templates/uk/admlogin.html | 1 - templates/uk/archidxentry.html | 1 - templates/uk/archidxfoot.html | 1 - templates/uk/archidxhead.html | 1 - templates/uk/archlistend.html | 1 - templates/uk/archliststart.html | 1 - templates/uk/archtoc.html | 1 - templates/uk/archtocentry.html | 1 - templates/uk/archtocnombox.html | 1 - templates/uk/emptyarchive.html | 1 - templates/uk/headfoot.html | 1 - templates/uk/listinfo.html | 1 - templates/uk/options.html | 1 - templates/uk/private.html | 1 - templates/uk/roster.html | 1 - templates/uk/subscribe.html | 1 - templates/vi/admindbdetails.html | 1 - templates/vi/admindbpreamble.html | 1 - templates/vi/admindbsummary.html | 1 - templates/vi/admlogin.html | 1 - templates/vi/archidxentry.html | 1 - templates/vi/archidxfoot.html | 1 - templates/vi/archidxhead.html | 1 - templates/vi/archlistend.html | 1 - templates/vi/archliststart.html | 1 - templates/vi/archtoc.html | 1 - templates/vi/archtocentry.html | 1 - templates/vi/archtocnombox.html | 1 - templates/vi/emptyarchive.html | 1 - templates/vi/headfoot.html | 1 - templates/vi/listinfo.html | 1 - templates/vi/options.html | 1 - templates/vi/private.html | 1 - templates/vi/roster.html | 1 - templates/vi/subscribe.html | 1 - templates/zh_CN/admindbdetails.html | 1 - templates/zh_CN/admindbpreamble.html | 1 - templates/zh_CN/admindbsummary.html | 1 - templates/zh_CN/admlogin.html | 1 - templates/zh_CN/archidxentry.html | 1 - templates/zh_CN/archidxfoot.html | 1 - templates/zh_CN/archidxhead.html | 1 - templates/zh_CN/archlistend.html | 1 - templates/zh_CN/archliststart.html | 1 - templates/zh_CN/archtoc.html | 1 - templates/zh_CN/archtocentry.html | 1 - templates/zh_CN/archtocnombox.html | 1 - templates/zh_CN/emptyarchive.html | 1 - templates/zh_CN/headfoot.html | 1 - templates/zh_CN/listinfo.html | 1 - templates/zh_CN/options.html | 1 - templates/zh_CN/private.html | 1 - templates/zh_CN/roster.html | 1 - templates/zh_CN/subscribe.html | 1 - templates/zh_TW/admindbpreamble.html | 1 - templates/zh_TW/admlogin.html | 1 - templates/zh_TW/handle_opts.html | 1 - templates/zh_TW/headfoot.html | 1 - templates/zh_TW/listinfo.html | 1 - templates/zh_TW/options.html | 1 - templates/zh_TW/roster.html | 1 - templates/zh_TW/subscribe.html | 1 - 697 files changed, 1 insertion(+), 700 deletions(-) diff --git a/Mailman/htmlformat.py b/Mailman/htmlformat.py index ed1189f7..83853b0e 100644 --- a/Mailman/htmlformat.py +++ b/Mailman/htmlformat.py @@ -186,8 +186,6 @@ def ExtractTableInfo(self, info): # Add ARIA attributes for accessibility if 'role' not in info: info['role'] = 'table' - if 'aria-label' not in info: - info['aria-label'] = 'Data table' for k, v in list(info.items()): output = output + ' %s="%s"' % (k, v) return output @@ -363,8 +361,7 @@ def Format(self, indent=0, **kws): font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; - margin: 0 auto; + margin: 0; padding: 20px; } h1, h2, h3 { diff --git a/templates/ar/admindbdetails.html b/templates/ar/admindbdetails.html index 37ffb5a8..3204475a 100644 --- a/templates/ar/admindbdetails.html +++ b/templates/ar/admindbdetails.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ar/admindbpreamble.html b/templates/ar/admindbpreamble.html index 8a93697f..f8a00a90 100644 --- a/templates/ar/admindbpreamble.html +++ b/templates/ar/admindbpreamble.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ar/admindbsummary.html b/templates/ar/admindbsummary.html index 0bd24fe8..c5eb8056 100644 --- a/templates/ar/admindbsummary.html +++ b/templates/ar/admindbsummary.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ar/admlogin.html b/templates/ar/admlogin.html index e41f77ad..119f91ee 100755 --- a/templates/ar/admlogin.html +++ b/templates/ar/admlogin.html @@ -8,7 +8,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ar/archidxentry.html b/templates/ar/archidxentry.html index 5c620b20..2efd9486 100644 --- a/templates/ar/archidxentry.html +++ b/templates/ar/archidxentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ar/archidxfoot.html b/templates/ar/archidxfoot.html index 439d020f..7a889266 100644 --- a/templates/ar/archidxfoot.html +++ b/templates/ar/archidxfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ar/archidxhead.html b/templates/ar/archidxhead.html index 39a43dcf..77d28452 100644 --- a/templates/ar/archidxhead.html +++ b/templates/ar/archidxhead.html @@ -11,7 +11,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ar/archlistend.html b/templates/ar/archlistend.html index 2e1191b0..492cf01a 100644 --- a/templates/ar/archlistend.html +++ b/templates/ar/archlistend.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ar/archliststart.html b/templates/ar/archliststart.html index 30fe06ed..c1914318 100644 --- a/templates/ar/archliststart.html +++ b/templates/ar/archliststart.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ar/archtoc.html b/templates/ar/archtoc.html index 99b40dad..5d27e5e2 100644 --- a/templates/ar/archtoc.html +++ b/templates/ar/archtoc.html @@ -11,7 +11,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ar/archtocentry.html b/templates/ar/archtocentry.html index 0a5cc080..fe914073 100644 --- a/templates/ar/archtocentry.html +++ b/templates/ar/archtocentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ar/archtocnombox.html b/templates/ar/archtocnombox.html index 31d58097..cda0b03f 100644 --- a/templates/ar/archtocnombox.html +++ b/templates/ar/archtocnombox.html @@ -11,7 +11,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ar/emptyarchive.html b/templates/ar/emptyarchive.html index d6d445ce..b405115c 100644 --- a/templates/ar/emptyarchive.html +++ b/templates/ar/emptyarchive.html @@ -18,7 +18,6 @@

    Ø±Ø´ÙŠÙØ§Øª القائمة البريدية %(listname)s

    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ar/headfoot.html b/templates/ar/headfoot.html index f6032a43..c58bbe7a 100644 --- a/templates/ar/headfoot.html +++ b/templates/ar/headfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ar/listinfo.html b/templates/ar/listinfo.html index c49fc809..7e8751c8 100644 --- a/templates/ar/listinfo.html +++ b/templates/ar/listinfo.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ar/options.html b/templates/ar/options.html index 74d4d2f0..f0954f9f 100644 --- a/templates/ar/options.html +++ b/templates/ar/options.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ar/private.html b/templates/ar/private.html index 81c6857f..1e56b854 100755 --- a/templates/ar/private.html +++ b/templates/ar/private.html @@ -8,7 +8,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ar/roster.html b/templates/ar/roster.html index 7fcba586..21e1c7c2 100644 --- a/templates/ar/roster.html +++ b/templates/ar/roster.html @@ -8,7 +8,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ar/subscribe.html b/templates/ar/subscribe.html index 9a319ce2..296917ff 100644 --- a/templates/ar/subscribe.html +++ b/templates/ar/subscribe.html @@ -8,7 +8,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ast/admindbdetails.html b/templates/ast/admindbdetails.html index 16a41585..ef238384 100644 --- a/templates/ast/admindbdetails.html +++ b/templates/ast/admindbdetails.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ast/admindbpreamble.html b/templates/ast/admindbpreamble.html index f622bf5f..a06d3f12 100644 --- a/templates/ast/admindbpreamble.html +++ b/templates/ast/admindbpreamble.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ast/admindbsummary.html b/templates/ast/admindbsummary.html index 5b1934c7..47b55a8b 100644 --- a/templates/ast/admindbsummary.html +++ b/templates/ast/admindbsummary.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ast/admlogin.html b/templates/ast/admlogin.html index 22a72c89..69396dda 100755 --- a/templates/ast/admlogin.html +++ b/templates/ast/admlogin.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ast/archidxentry.html b/templates/ast/archidxentry.html index 5c620b20..2efd9486 100644 --- a/templates/ast/archidxentry.html +++ b/templates/ast/archidxentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ast/archidxfoot.html b/templates/ast/archidxfoot.html index c70729d3..ea0c49f6 100644 --- a/templates/ast/archidxfoot.html +++ b/templates/ast/archidxfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ast/archidxhead.html b/templates/ast/archidxhead.html index b335cc7b..85308470 100644 --- a/templates/ast/archidxhead.html +++ b/templates/ast/archidxhead.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ast/archlistend.html b/templates/ast/archlistend.html index 2e1191b0..492cf01a 100644 --- a/templates/ast/archlistend.html +++ b/templates/ast/archlistend.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ast/archliststart.html b/templates/ast/archliststart.html index 75d3f37d..b896de55 100644 --- a/templates/ast/archliststart.html +++ b/templates/ast/archliststart.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ast/archtoc.html b/templates/ast/archtoc.html index 3c0c6cf2..70bceb5a 100644 --- a/templates/ast/archtoc.html +++ b/templates/ast/archtoc.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ast/archtocentry.html b/templates/ast/archtocentry.html index 59a55f10..2c711ac4 100644 --- a/templates/ast/archtocentry.html +++ b/templates/ast/archtocentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ast/archtocnombox.html b/templates/ast/archtocnombox.html index 09af5b4b..68b6c6c4 100644 --- a/templates/ast/archtocnombox.html +++ b/templates/ast/archtocnombox.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ast/article.html b/templates/ast/article.html index 0a42ec67..90964f2e 100644 --- a/templates/ast/article.html +++ b/templates/ast/article.html @@ -15,7 +15,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ast/emptyarchive.html b/templates/ast/emptyarchive.html index b4ad54d2..8c4481b8 100644 --- a/templates/ast/emptyarchive.html +++ b/templates/ast/emptyarchive.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ast/headfoot.html b/templates/ast/headfoot.html index 7af8f373..938f7981 100644 --- a/templates/ast/headfoot.html +++ b/templates/ast/headfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ast/listinfo.html b/templates/ast/listinfo.html index 56d0407c..cb42551f 100644 --- a/templates/ast/listinfo.html +++ b/templates/ast/listinfo.html @@ -6,7 +6,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ast/options.html b/templates/ast/options.html index cc78a1f8..df48e8b7 100644 --- a/templates/ast/options.html +++ b/templates/ast/options.html @@ -8,7 +8,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ast/private.html b/templates/ast/private.html index 07aed5c1..f4d02a2a 100755 --- a/templates/ast/private.html +++ b/templates/ast/private.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ast/roster.html b/templates/ast/roster.html index 7e4d2d11..f93f3813 100644 --- a/templates/ast/roster.html +++ b/templates/ast/roster.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ast/subscribe.html b/templates/ast/subscribe.html index 93c518d3..2d984a8e 100644 --- a/templates/ast/subscribe.html +++ b/templates/ast/subscribe.html @@ -5,7 +5,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ca/admindbdetails.html b/templates/ca/admindbdetails.html index 50f02554..515dbe1b 100644 --- a/templates/ca/admindbdetails.html +++ b/templates/ca/admindbdetails.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ca/admindbpreamble.html b/templates/ca/admindbpreamble.html index 3d0bb861..16fa674a 100644 --- a/templates/ca/admindbpreamble.html +++ b/templates/ca/admindbpreamble.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ca/admindbsummary.html b/templates/ca/admindbsummary.html index 3939db92..d5534441 100644 --- a/templates/ca/admindbsummary.html +++ b/templates/ca/admindbsummary.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ca/admlogin.html b/templates/ca/admlogin.html index 1aeeffa2..b9818c72 100755 --- a/templates/ca/admlogin.html +++ b/templates/ca/admlogin.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ca/archidxentry.html b/templates/ca/archidxentry.html index 5c620b20..2efd9486 100644 --- a/templates/ca/archidxentry.html +++ b/templates/ca/archidxentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ca/archidxfoot.html b/templates/ca/archidxfoot.html index 9f6f38c9..8a6fc7c9 100644 --- a/templates/ca/archidxfoot.html +++ b/templates/ca/archidxfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ca/archidxhead.html b/templates/ca/archidxhead.html index ab6814a3..d07cad9d 100644 --- a/templates/ca/archidxhead.html +++ b/templates/ca/archidxhead.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ca/archlistend.html b/templates/ca/archlistend.html index 2e1191b0..492cf01a 100644 --- a/templates/ca/archlistend.html +++ b/templates/ca/archlistend.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ca/archliststart.html b/templates/ca/archliststart.html index e3392128..588007e5 100644 --- a/templates/ca/archliststart.html +++ b/templates/ca/archliststart.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ca/archtoc.html b/templates/ca/archtoc.html index 93e4edcd..aea0a0c4 100644 --- a/templates/ca/archtoc.html +++ b/templates/ca/archtoc.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ca/archtocentry.html b/templates/ca/archtocentry.html index c921fbf2..c7afe7ba 100644 --- a/templates/ca/archtocentry.html +++ b/templates/ca/archtocentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ca/archtocnombox.html b/templates/ca/archtocnombox.html index 28b82f24..7f16085b 100644 --- a/templates/ca/archtocnombox.html +++ b/templates/ca/archtocnombox.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ca/emptyarchive.html b/templates/ca/emptyarchive.html index 48b595b6..798a396c 100644 --- a/templates/ca/emptyarchive.html +++ b/templates/ca/emptyarchive.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ca/headfoot.html b/templates/ca/headfoot.html index 9eb3b658..f257b1df 100644 --- a/templates/ca/headfoot.html +++ b/templates/ca/headfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ca/listinfo.html b/templates/ca/listinfo.html index 8507fd14..74d0e73e 100644 --- a/templates/ca/listinfo.html +++ b/templates/ca/listinfo.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ca/options.html b/templates/ca/options.html index 86d78597..086d5208 100644 --- a/templates/ca/options.html +++ b/templates/ca/options.html @@ -8,7 +8,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ca/private.html b/templates/ca/private.html index 1ae56df3..6669c314 100755 --- a/templates/ca/private.html +++ b/templates/ca/private.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ca/roster.html b/templates/ca/roster.html index 23f4d454..9ee62fc6 100644 --- a/templates/ca/roster.html +++ b/templates/ca/roster.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ca/subscribe.html b/templates/ca/subscribe.html index 340000fb..89495c64 100644 --- a/templates/ca/subscribe.html +++ b/templates/ca/subscribe.html @@ -5,7 +5,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/cs/admindbdetails.html b/templates/cs/admindbdetails.html index b8ffd050..482b3ac9 100644 --- a/templates/cs/admindbdetails.html +++ b/templates/cs/admindbdetails.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/cs/admindbpreamble.html b/templates/cs/admindbpreamble.html index 3dd45a56..de72fe4f 100644 --- a/templates/cs/admindbpreamble.html +++ b/templates/cs/admindbpreamble.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/cs/admindbsummary.html b/templates/cs/admindbsummary.html index 8ce0bdb2..107f0da8 100644 --- a/templates/cs/admindbsummary.html +++ b/templates/cs/admindbsummary.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/cs/admlogin.html b/templates/cs/admlogin.html index 590688aa..571ce2ca 100755 --- a/templates/cs/admlogin.html +++ b/templates/cs/admlogin.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/cs/archidxentry.html b/templates/cs/archidxentry.html index 5c620b20..2efd9486 100644 --- a/templates/cs/archidxentry.html +++ b/templates/cs/archidxentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/cs/archidxfoot.html b/templates/cs/archidxfoot.html index d25bb4a8..389d61a4 100644 --- a/templates/cs/archidxfoot.html +++ b/templates/cs/archidxfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/cs/archidxhead.html b/templates/cs/archidxhead.html index e1f37f72..18129a12 100644 --- a/templates/cs/archidxhead.html +++ b/templates/cs/archidxhead.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/cs/archlistend.html b/templates/cs/archlistend.html index 2e1191b0..492cf01a 100644 --- a/templates/cs/archlistend.html +++ b/templates/cs/archlistend.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/cs/archliststart.html b/templates/cs/archliststart.html index ed64fdb3..d1e29ee8 100644 --- a/templates/cs/archliststart.html +++ b/templates/cs/archliststart.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/cs/archtoc.html b/templates/cs/archtoc.html index 78eb81ec..54e56d78 100644 --- a/templates/cs/archtoc.html +++ b/templates/cs/archtoc.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/cs/archtocentry.html b/templates/cs/archtocentry.html index cd26d875..fb6670a8 100644 --- a/templates/cs/archtocentry.html +++ b/templates/cs/archtocentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/cs/archtocnombox.html b/templates/cs/archtocnombox.html index 8e468ab7..cf684a73 100644 --- a/templates/cs/archtocnombox.html +++ b/templates/cs/archtocnombox.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/cs/emptyarchive.html b/templates/cs/emptyarchive.html index 96afef0d..9325bc0f 100644 --- a/templates/cs/emptyarchive.html +++ b/templates/cs/emptyarchive.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/cs/headfoot.html b/templates/cs/headfoot.html index 4a5bfce6..4a08aa25 100644 --- a/templates/cs/headfoot.html +++ b/templates/cs/headfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/cs/listinfo.html b/templates/cs/listinfo.html index 6bbd64e6..f75620ec 100644 --- a/templates/cs/listinfo.html +++ b/templates/cs/listinfo.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/cs/options.html b/templates/cs/options.html index 8b5468bc..5fa3e5a0 100644 --- a/templates/cs/options.html +++ b/templates/cs/options.html @@ -8,7 +8,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/cs/private.html b/templates/cs/private.html index 25073d4a..1778e4d6 100755 --- a/templates/cs/private.html +++ b/templates/cs/private.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/cs/roster.html b/templates/cs/roster.html index e5d79261..db6ce29f 100644 --- a/templates/cs/roster.html +++ b/templates/cs/roster.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/cs/subscribe.html b/templates/cs/subscribe.html index 7bbb7c01..2f2cf0b1 100644 --- a/templates/cs/subscribe.html +++ b/templates/cs/subscribe.html @@ -5,7 +5,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/da/admindbdetails.html b/templates/da/admindbdetails.html index cbdfbb87..ec8a4924 100644 --- a/templates/da/admindbdetails.html +++ b/templates/da/admindbdetails.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/da/admindbpreamble.html b/templates/da/admindbpreamble.html index 5d550e95..eb3173e7 100644 --- a/templates/da/admindbpreamble.html +++ b/templates/da/admindbpreamble.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/da/admindbsummary.html b/templates/da/admindbsummary.html index 3714e618..27854222 100644 --- a/templates/da/admindbsummary.html +++ b/templates/da/admindbsummary.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/da/admlogin.html b/templates/da/admlogin.html index 3e64f00a..e248f531 100755 --- a/templates/da/admlogin.html +++ b/templates/da/admlogin.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/da/archidxfoot.html b/templates/da/archidxfoot.html index 741192cb..ed7d7b36 100644 --- a/templates/da/archidxfoot.html +++ b/templates/da/archidxfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/da/archidxhead.html b/templates/da/archidxhead.html index 44622fd9..5293c728 100644 --- a/templates/da/archidxhead.html +++ b/templates/da/archidxhead.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/da/archliststart.html b/templates/da/archliststart.html index 115e531a..1f9f444e 100644 --- a/templates/da/archliststart.html +++ b/templates/da/archliststart.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/da/archtoc.html b/templates/da/archtoc.html index 5504869a..856b9fcb 100644 --- a/templates/da/archtoc.html +++ b/templates/da/archtoc.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/da/archtocentry.html b/templates/da/archtocentry.html index 3b1f5c7b..50da5469 100644 --- a/templates/da/archtocentry.html +++ b/templates/da/archtocentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/da/archtocnombox.html b/templates/da/archtocnombox.html index b8dfb9f5..ede3bee0 100644 --- a/templates/da/archtocnombox.html +++ b/templates/da/archtocnombox.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/da/emptyarchive.html b/templates/da/emptyarchive.html index ce59d84f..05730627 100644 --- a/templates/da/emptyarchive.html +++ b/templates/da/emptyarchive.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/da/headfoot.html b/templates/da/headfoot.html index 0d2cb107..fca1d1f2 100644 --- a/templates/da/headfoot.html +++ b/templates/da/headfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/da/listinfo.html b/templates/da/listinfo.html index 50cd9124..ba418b0d 100644 --- a/templates/da/listinfo.html +++ b/templates/da/listinfo.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/da/options.html b/templates/da/options.html index 1b42b19c..f1a90aee 100644 --- a/templates/da/options.html +++ b/templates/da/options.html @@ -8,7 +8,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/da/private.html b/templates/da/private.html index d4cfe68e..87c851f8 100755 --- a/templates/da/private.html +++ b/templates/da/private.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/da/roster.html b/templates/da/roster.html index ac31b66b..159a1857 100644 --- a/templates/da/roster.html +++ b/templates/da/roster.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/da/subscribe.html b/templates/da/subscribe.html index ac41be7e..267352b4 100644 --- a/templates/da/subscribe.html +++ b/templates/da/subscribe.html @@ -5,7 +5,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/de/admindbdetails.html b/templates/de/admindbdetails.html index 686bbf61..d493fb80 100755 --- a/templates/de/admindbdetails.html +++ b/templates/de/admindbdetails.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/de/admindbpreamble.html b/templates/de/admindbpreamble.html index 52e5e89c..bd13f77d 100644 --- a/templates/de/admindbpreamble.html +++ b/templates/de/admindbpreamble.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/de/admindbsummary.html b/templates/de/admindbsummary.html index 650ec79a..f2ab8c0d 100644 --- a/templates/de/admindbsummary.html +++ b/templates/de/admindbsummary.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/de/admlogin.html b/templates/de/admlogin.html index 7161231b..5dd26c76 100755 --- a/templates/de/admlogin.html +++ b/templates/de/admlogin.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/de/archidxentry.html b/templates/de/archidxentry.html index 5c620b20..2efd9486 100755 --- a/templates/de/archidxentry.html +++ b/templates/de/archidxentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/de/archidxfoot.html b/templates/de/archidxfoot.html index 47b236db..0d4c4940 100755 --- a/templates/de/archidxfoot.html +++ b/templates/de/archidxfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/de/archidxhead.html b/templates/de/archidxhead.html index dbab8e42..7bc51dc7 100755 --- a/templates/de/archidxhead.html +++ b/templates/de/archidxhead.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/de/archlistend.html b/templates/de/archlistend.html index 2e1191b0..492cf01a 100755 --- a/templates/de/archlistend.html +++ b/templates/de/archlistend.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/de/archliststart.html b/templates/de/archliststart.html index 7b6d0786..b5da702d 100755 --- a/templates/de/archliststart.html +++ b/templates/de/archliststart.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/de/archtoc.html b/templates/de/archtoc.html index 6d7c0c26..56770955 100755 --- a/templates/de/archtoc.html +++ b/templates/de/archtoc.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/de/archtocentry.html b/templates/de/archtocentry.html index 0e3b74ba..130406f7 100755 --- a/templates/de/archtocentry.html +++ b/templates/de/archtocentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/de/archtocnombox.html b/templates/de/archtocnombox.html index c0b74a36..dce0efbe 100755 --- a/templates/de/archtocnombox.html +++ b/templates/de/archtocnombox.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/de/emptyarchive.html b/templates/de/emptyarchive.html index 7324e664..b4fa2d21 100755 --- a/templates/de/emptyarchive.html +++ b/templates/de/emptyarchive.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/de/headfoot.html b/templates/de/headfoot.html index 06acd691..626052f2 100644 --- a/templates/de/headfoot.html +++ b/templates/de/headfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/de/listinfo.html b/templates/de/listinfo.html index 33de9d29..b350f7aa 100755 --- a/templates/de/listinfo.html +++ b/templates/de/listinfo.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/de/options.html b/templates/de/options.html index 149d882d..4ff7962f 100755 --- a/templates/de/options.html +++ b/templates/de/options.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/de/private.html b/templates/de/private.html index f57985c4..85981b0f 100755 --- a/templates/de/private.html +++ b/templates/de/private.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/de/roster.html b/templates/de/roster.html index a86ce493..99eb087e 100644 --- a/templates/de/roster.html +++ b/templates/de/roster.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/de/subscribe.html b/templates/de/subscribe.html index 4e40e1cc..2f7ec247 100644 --- a/templates/de/subscribe.html +++ b/templates/de/subscribe.html @@ -5,7 +5,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/el/admindbdetails.html b/templates/el/admindbdetails.html index 7e744db8..255203ef 100755 --- a/templates/el/admindbdetails.html +++ b/templates/el/admindbdetails.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/el/admindbpreamble.html b/templates/el/admindbpreamble.html index 9ca703a2..dd3e44cf 100755 --- a/templates/el/admindbpreamble.html +++ b/templates/el/admindbpreamble.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/el/admindbsummary.html b/templates/el/admindbsummary.html index c6cbe05d..c630fa4d 100755 --- a/templates/el/admindbsummary.html +++ b/templates/el/admindbsummary.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/el/admlogin.html b/templates/el/admlogin.html index 14ec4867..79506c36 100755 --- a/templates/el/admlogin.html +++ b/templates/el/admlogin.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/el/archidxentry.html b/templates/el/archidxentry.html index 5c620b20..2efd9486 100755 --- a/templates/el/archidxentry.html +++ b/templates/el/archidxentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/el/archidxfoot.html b/templates/el/archidxfoot.html index 9161d7b7..145e9a8f 100755 --- a/templates/el/archidxfoot.html +++ b/templates/el/archidxfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/el/archidxhead.html b/templates/el/archidxhead.html index 75f37358..06c2a9eb 100755 --- a/templates/el/archidxhead.html +++ b/templates/el/archidxhead.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/el/archlistend.html b/templates/el/archlistend.html index 2e1191b0..492cf01a 100755 --- a/templates/el/archlistend.html +++ b/templates/el/archlistend.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/el/archliststart.html b/templates/el/archliststart.html index d818203a..6b5b3bc4 100755 --- a/templates/el/archliststart.html +++ b/templates/el/archliststart.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/el/archtoc.html b/templates/el/archtoc.html index d9c4c9d9..545c9e68 100755 --- a/templates/el/archtoc.html +++ b/templates/el/archtoc.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/el/archtocentry.html b/templates/el/archtocentry.html index a1f755fb..9a39685b 100755 --- a/templates/el/archtocentry.html +++ b/templates/el/archtocentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/el/archtocnombox.html b/templates/el/archtocnombox.html index 7449d494..50da6b19 100755 --- a/templates/el/archtocnombox.html +++ b/templates/el/archtocnombox.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/el/emptyarchive.html b/templates/el/emptyarchive.html index 96e856c2..be39f918 100755 --- a/templates/el/emptyarchive.html +++ b/templates/el/emptyarchive.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/el/headfoot.html b/templates/el/headfoot.html index d1c847ea..36fd365a 100755 --- a/templates/el/headfoot.html +++ b/templates/el/headfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/el/listinfo.html b/templates/el/listinfo.html index c9abf025..6cec002f 100755 --- a/templates/el/listinfo.html +++ b/templates/el/listinfo.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/el/options.html b/templates/el/options.html index 2f0ec62c..57c19652 100755 --- a/templates/el/options.html +++ b/templates/el/options.html @@ -8,7 +8,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/el/private.html b/templates/el/private.html index e5be28ca..73f63583 100755 --- a/templates/el/private.html +++ b/templates/el/private.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/el/roster.html b/templates/el/roster.html index 44c7230b..4830bcf1 100755 --- a/templates/el/roster.html +++ b/templates/el/roster.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/el/subscribe.html b/templates/el/subscribe.html index b1a217c1..902ea11c 100755 --- a/templates/el/subscribe.html +++ b/templates/el/subscribe.html @@ -5,7 +5,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/en/admindbdetails.html b/templates/en/admindbdetails.html index eeda5084..d618f452 100644 --- a/templates/en/admindbdetails.html +++ b/templates/en/admindbdetails.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/en/admindbpreamble.html b/templates/en/admindbpreamble.html index 72a30230..1c7a1dbd 100644 --- a/templates/en/admindbpreamble.html +++ b/templates/en/admindbpreamble.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/en/admindbsummary.html b/templates/en/admindbsummary.html index e8228319..4ec120fc 100644 --- a/templates/en/admindbsummary.html +++ b/templates/en/admindbsummary.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/en/admlogin.html b/templates/en/admlogin.html index ef789347..96a45e13 100755 --- a/templates/en/admlogin.html +++ b/templates/en/admlogin.html @@ -8,7 +8,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/en/archidxentry.html b/templates/en/archidxentry.html index 5c620b20..2efd9486 100644 --- a/templates/en/archidxentry.html +++ b/templates/en/archidxentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/en/archidxfoot.html b/templates/en/archidxfoot.html index 6da07609..0679238d 100644 --- a/templates/en/archidxfoot.html +++ b/templates/en/archidxfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/en/archidxhead.html b/templates/en/archidxhead.html index 56c8b6e3..2e1b76a8 100644 --- a/templates/en/archidxhead.html +++ b/templates/en/archidxhead.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/en/archlistend.html b/templates/en/archlistend.html index 2e1191b0..492cf01a 100644 --- a/templates/en/archlistend.html +++ b/templates/en/archlistend.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/en/archliststart.html b/templates/en/archliststart.html index dc385915..bbbf8b6d 100644 --- a/templates/en/archliststart.html +++ b/templates/en/archliststart.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/en/archtoc.html b/templates/en/archtoc.html index a1e7fd44..1300d927 100644 --- a/templates/en/archtoc.html +++ b/templates/en/archtoc.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/en/archtocentry.html b/templates/en/archtocentry.html index 6f2a92bb..97136874 100644 --- a/templates/en/archtocentry.html +++ b/templates/en/archtocentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/en/archtocnombox.html b/templates/en/archtocnombox.html index 6d17209e..fe6cc8f7 100644 --- a/templates/en/archtocnombox.html +++ b/templates/en/archtocnombox.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/en/emptyarchive.html b/templates/en/emptyarchive.html index 6ed20a6a..41916295 100644 --- a/templates/en/emptyarchive.html +++ b/templates/en/emptyarchive.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/en/headfoot.html b/templates/en/headfoot.html index d916877e..3729dc42 100644 --- a/templates/en/headfoot.html +++ b/templates/en/headfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/en/listinfo.html b/templates/en/listinfo.html index c509f779..06bb3a44 100644 --- a/templates/en/listinfo.html +++ b/templates/en/listinfo.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/en/options.html b/templates/en/options.html index 735517df..43979b78 100644 --- a/templates/en/options.html +++ b/templates/en/options.html @@ -8,7 +8,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/en/private.html b/templates/en/private.html index ae001315..2f551ca7 100755 --- a/templates/en/private.html +++ b/templates/en/private.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/en/roster.html b/templates/en/roster.html index e431e505..4627c97e 100644 --- a/templates/en/roster.html +++ b/templates/en/roster.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/en/subscribe.html b/templates/en/subscribe.html index 55e8f7a7..9015498a 100644 --- a/templates/en/subscribe.html +++ b/templates/en/subscribe.html @@ -5,7 +5,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/eo/admlogin.html b/templates/eo/admlogin.html index d4a12b55..1fa5dc27 100644 --- a/templates/eo/admlogin.html +++ b/templates/eo/admlogin.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/eo/archidxentry.html b/templates/eo/archidxentry.html index 5c620b20..2efd9486 100644 --- a/templates/eo/archidxentry.html +++ b/templates/eo/archidxentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/eo/archidxfoot.html b/templates/eo/archidxfoot.html index bbf2af73..64f5bd10 100644 --- a/templates/eo/archidxfoot.html +++ b/templates/eo/archidxfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/eo/archidxhead.html b/templates/eo/archidxhead.html index c3188cf0..7ffccc8b 100644 --- a/templates/eo/archidxhead.html +++ b/templates/eo/archidxhead.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/eo/archlistend.html b/templates/eo/archlistend.html index 2e1191b0..492cf01a 100644 --- a/templates/eo/archlistend.html +++ b/templates/eo/archlistend.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/eo/archliststart.html b/templates/eo/archliststart.html index 97cf7658..a4adbb87 100644 --- a/templates/eo/archliststart.html +++ b/templates/eo/archliststart.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/eo/archtoc.html b/templates/eo/archtoc.html index 350601c2..92b6e510 100644 --- a/templates/eo/archtoc.html +++ b/templates/eo/archtoc.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/eo/archtocentry.html b/templates/eo/archtocentry.html index 37941f45..12fa96c2 100644 --- a/templates/eo/archtocentry.html +++ b/templates/eo/archtocentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/eo/archtocnombox.html b/templates/eo/archtocnombox.html index ab64402e..94a7798b 100644 --- a/templates/eo/archtocnombox.html +++ b/templates/eo/archtocnombox.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/eo/emptyarchive.html b/templates/eo/emptyarchive.html index 91489238..7be2fc0f 100644 --- a/templates/eo/emptyarchive.html +++ b/templates/eo/emptyarchive.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/eo/listinfo.html b/templates/eo/listinfo.html index 54603b94..bf547119 100644 --- a/templates/eo/listinfo.html +++ b/templates/eo/listinfo.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/eo/options.html b/templates/eo/options.html index ad7f412e..151b80c1 100644 --- a/templates/eo/options.html +++ b/templates/eo/options.html @@ -8,7 +8,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/eo/private.html b/templates/eo/private.html index f1eeefee..1195b8c7 100755 --- a/templates/eo/private.html +++ b/templates/eo/private.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/eo/roster.html b/templates/eo/roster.html index 572a0895..41d10aed 100644 --- a/templates/eo/roster.html +++ b/templates/eo/roster.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/eo/subscribe.html b/templates/eo/subscribe.html index 763c82eb..f205a151 100644 --- a/templates/eo/subscribe.html +++ b/templates/eo/subscribe.html @@ -5,7 +5,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/es/admindbdetails.html b/templates/es/admindbdetails.html index 07150ceb..3979f19b 100644 --- a/templates/es/admindbdetails.html +++ b/templates/es/admindbdetails.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/es/admindbpreamble.html b/templates/es/admindbpreamble.html index 1c251f33..6984729d 100644 --- a/templates/es/admindbpreamble.html +++ b/templates/es/admindbpreamble.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/es/admindbsummary.html b/templates/es/admindbsummary.html index eaa0cae7..cd0c4512 100644 --- a/templates/es/admindbsummary.html +++ b/templates/es/admindbsummary.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/es/admlogin.html b/templates/es/admlogin.html index 72995cd3..6234ba86 100755 --- a/templates/es/admlogin.html +++ b/templates/es/admlogin.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/es/archidxentry.html b/templates/es/archidxentry.html index 5c620b20..2efd9486 100644 --- a/templates/es/archidxentry.html +++ b/templates/es/archidxentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/es/archidxfoot.html b/templates/es/archidxfoot.html index d494d8e2..0a13fbcd 100644 --- a/templates/es/archidxfoot.html +++ b/templates/es/archidxfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/es/archidxhead.html b/templates/es/archidxhead.html index 5e6514fa..f6f824ae 100644 --- a/templates/es/archidxhead.html +++ b/templates/es/archidxhead.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/es/archlistend.html b/templates/es/archlistend.html index 2e1191b0..492cf01a 100644 --- a/templates/es/archlistend.html +++ b/templates/es/archlistend.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/es/archliststart.html b/templates/es/archliststart.html index 7ff22f29..341a1f4b 100644 --- a/templates/es/archliststart.html +++ b/templates/es/archliststart.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/es/archtoc.html b/templates/es/archtoc.html index a6678136..527dccad 100644 --- a/templates/es/archtoc.html +++ b/templates/es/archtoc.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/es/archtocentry.html b/templates/es/archtocentry.html index ccbc11cf..c6484801 100644 --- a/templates/es/archtocentry.html +++ b/templates/es/archtocentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/es/archtocnombox.html b/templates/es/archtocnombox.html index fc67799f..7a8763d2 100644 --- a/templates/es/archtocnombox.html +++ b/templates/es/archtocnombox.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/es/emptyarchive.html b/templates/es/emptyarchive.html index f409cdeb..bf651f03 100644 --- a/templates/es/emptyarchive.html +++ b/templates/es/emptyarchive.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/es/handle_opts.html b/templates/es/handle_opts.html index d6ca9764..837c4966 100644 --- a/templates/es/handle_opts.html +++ b/templates/es/handle_opts.html @@ -4,7 +4,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/es/headfoot.html b/templates/es/headfoot.html index 8ad1486d..bed6236d 100644 --- a/templates/es/headfoot.html +++ b/templates/es/headfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/es/listinfo.html b/templates/es/listinfo.html index 9248e556..a3099ecc 100644 --- a/templates/es/listinfo.html +++ b/templates/es/listinfo.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/es/options.html b/templates/es/options.html index ddfe4e83..0df1a845 100644 --- a/templates/es/options.html +++ b/templates/es/options.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/es/private.html b/templates/es/private.html index b0b04f0a..b29848eb 100755 --- a/templates/es/private.html +++ b/templates/es/private.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/es/roster.html b/templates/es/roster.html index 637d08e3..b8c74377 100644 --- a/templates/es/roster.html +++ b/templates/es/roster.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/es/subscribe.html b/templates/es/subscribe.html index b70be098..d3ae7a29 100644 --- a/templates/es/subscribe.html +++ b/templates/es/subscribe.html @@ -5,7 +5,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/et/admindbdetails.html b/templates/et/admindbdetails.html index 82a3e910..87730f8b 100644 --- a/templates/et/admindbdetails.html +++ b/templates/et/admindbdetails.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/et/admindbpreamble.html b/templates/et/admindbpreamble.html index 153fd5d3..488e7559 100644 --- a/templates/et/admindbpreamble.html +++ b/templates/et/admindbpreamble.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/et/admindbsummary.html b/templates/et/admindbsummary.html index d0528e54..c4598623 100644 --- a/templates/et/admindbsummary.html +++ b/templates/et/admindbsummary.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/et/admlogin.html b/templates/et/admlogin.html index 021e89a9..441271d0 100755 --- a/templates/et/admlogin.html +++ b/templates/et/admlogin.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/et/emptyarchive.html b/templates/et/emptyarchive.html index afa26750..5df2ada2 100644 --- a/templates/et/emptyarchive.html +++ b/templates/et/emptyarchive.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/et/headfoot.html b/templates/et/headfoot.html index d9c6d1ae..ad7ecb92 100644 --- a/templates/et/headfoot.html +++ b/templates/et/headfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/et/listinfo.html b/templates/et/listinfo.html index d6960e4e..c9f57dce 100644 --- a/templates/et/listinfo.html +++ b/templates/et/listinfo.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/et/options.html b/templates/et/options.html index 4f094560..b8e3f55d 100644 --- a/templates/et/options.html +++ b/templates/et/options.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/et/private.html b/templates/et/private.html index 73802bd7..4ca0334d 100755 --- a/templates/et/private.html +++ b/templates/et/private.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/et/roster.html b/templates/et/roster.html index f5b117ab..6a753759 100644 --- a/templates/et/roster.html +++ b/templates/et/roster.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/et/subscribe.html b/templates/et/subscribe.html index c3a68e8e..25df525c 100644 --- a/templates/et/subscribe.html +++ b/templates/et/subscribe.html @@ -5,7 +5,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/eu/admindbdetails.html b/templates/eu/admindbdetails.html index b80e4de1..0a9b1ada 100644 --- a/templates/eu/admindbdetails.html +++ b/templates/eu/admindbdetails.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/eu/admindbpreamble.html b/templates/eu/admindbpreamble.html index 7a63dc69..c08028d3 100644 --- a/templates/eu/admindbpreamble.html +++ b/templates/eu/admindbpreamble.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/eu/admindbsummary.html b/templates/eu/admindbsummary.html index 7df4fd96..e144abdb 100644 --- a/templates/eu/admindbsummary.html +++ b/templates/eu/admindbsummary.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/eu/admlogin.html b/templates/eu/admlogin.html index f9263737..f8e005d4 100755 --- a/templates/eu/admlogin.html +++ b/templates/eu/admlogin.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/eu/archidxentry.html b/templates/eu/archidxentry.html index 5c620b20..2efd9486 100644 --- a/templates/eu/archidxentry.html +++ b/templates/eu/archidxentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/eu/archidxfoot.html b/templates/eu/archidxfoot.html index 07c0fbb4..cc052ebf 100644 --- a/templates/eu/archidxfoot.html +++ b/templates/eu/archidxfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/eu/archidxhead.html b/templates/eu/archidxhead.html index ac0eb698..37914829 100644 --- a/templates/eu/archidxhead.html +++ b/templates/eu/archidxhead.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/eu/archlistend.html b/templates/eu/archlistend.html index 2e1191b0..492cf01a 100644 --- a/templates/eu/archlistend.html +++ b/templates/eu/archlistend.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/eu/archliststart.html b/templates/eu/archliststart.html index 7aea0a63..aa00afde 100644 --- a/templates/eu/archliststart.html +++ b/templates/eu/archliststart.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/eu/archtoc.html b/templates/eu/archtoc.html index d8e38295..1f88dc5e 100644 --- a/templates/eu/archtoc.html +++ b/templates/eu/archtoc.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/eu/archtocentry.html b/templates/eu/archtocentry.html index d9126b7b..9827558d 100644 --- a/templates/eu/archtocentry.html +++ b/templates/eu/archtocentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/eu/emptyarchive.html b/templates/eu/emptyarchive.html index 838cb66a..6e69d4e1 100644 --- a/templates/eu/emptyarchive.html +++ b/templates/eu/emptyarchive.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/eu/headfoot.html b/templates/eu/headfoot.html index e89bebfe..65f0d9e6 100644 --- a/templates/eu/headfoot.html +++ b/templates/eu/headfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/eu/listinfo.html b/templates/eu/listinfo.html index c26bedcc..9734576d 100644 --- a/templates/eu/listinfo.html +++ b/templates/eu/listinfo.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/eu/options.html b/templates/eu/options.html index 84286b4f..a726f804 100644 --- a/templates/eu/options.html +++ b/templates/eu/options.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/eu/private.html b/templates/eu/private.html index ef22ad89..029a00f5 100755 --- a/templates/eu/private.html +++ b/templates/eu/private.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/eu/roster.html b/templates/eu/roster.html index 5b8ce007..e63b832a 100644 --- a/templates/eu/roster.html +++ b/templates/eu/roster.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/eu/subscribe.html b/templates/eu/subscribe.html index 45feedfc..b2cb659e 100644 --- a/templates/eu/subscribe.html +++ b/templates/eu/subscribe.html @@ -5,7 +5,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/fa/admlogin.html b/templates/fa/admlogin.html index 99bf6693..e45f3783 100644 --- a/templates/fa/admlogin.html +++ b/templates/fa/admlogin.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/fa/archidxfoot.html b/templates/fa/archidxfoot.html index c04f6c8a..2c86c56d 100644 --- a/templates/fa/archidxfoot.html +++ b/templates/fa/archidxfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/fa/archidxhead.html b/templates/fa/archidxhead.html index 191559cf..d40b4335 100644 --- a/templates/fa/archidxhead.html +++ b/templates/fa/archidxhead.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/fa/archliststart.html b/templates/fa/archliststart.html index 920714a2..99b510e8 100644 --- a/templates/fa/archliststart.html +++ b/templates/fa/archliststart.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/fa/archtoc.html b/templates/fa/archtoc.html index 0f4f604e..d7b57355 100644 --- a/templates/fa/archtoc.html +++ b/templates/fa/archtoc.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/fa/archtocentry.html b/templates/fa/archtocentry.html index 3615c648..3c49f550 100644 --- a/templates/fa/archtocentry.html +++ b/templates/fa/archtocentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/fa/archtocnombox.html b/templates/fa/archtocnombox.html index 9e9d36f9..7be88046 100644 --- a/templates/fa/archtocnombox.html +++ b/templates/fa/archtocnombox.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/fa/emptyarchive.html b/templates/fa/emptyarchive.html index 40f28c92..9df627a0 100644 --- a/templates/fa/emptyarchive.html +++ b/templates/fa/emptyarchive.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/fa/listinfo.html b/templates/fa/listinfo.html index 1de19358..8bcf997f 100644 --- a/templates/fa/listinfo.html +++ b/templates/fa/listinfo.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/fa/options.html b/templates/fa/options.html index 472245f1..f1254a4a 100644 --- a/templates/fa/options.html +++ b/templates/fa/options.html @@ -8,7 +8,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/fa/private.html b/templates/fa/private.html index 2821734d..9926176d 100644 --- a/templates/fa/private.html +++ b/templates/fa/private.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/fa/roster.html b/templates/fa/roster.html index 8997b6ad..371a1132 100644 --- a/templates/fa/roster.html +++ b/templates/fa/roster.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/fa/subscribe.html b/templates/fa/subscribe.html index 38fdc3ec..66c8975e 100644 --- a/templates/fa/subscribe.html +++ b/templates/fa/subscribe.html @@ -5,7 +5,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/fi/admindbdetails.html b/templates/fi/admindbdetails.html index dcb2e8fb..b288f93e 100644 --- a/templates/fi/admindbdetails.html +++ b/templates/fi/admindbdetails.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/fi/admindbpreamble.html b/templates/fi/admindbpreamble.html index c6db93b0..ab334e02 100644 --- a/templates/fi/admindbpreamble.html +++ b/templates/fi/admindbpreamble.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/fi/admindbsummary.html b/templates/fi/admindbsummary.html index 155a4f97..173337d9 100644 --- a/templates/fi/admindbsummary.html +++ b/templates/fi/admindbsummary.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/fi/admlogin.html b/templates/fi/admlogin.html index 4b25c8ce..5ea143f1 100755 --- a/templates/fi/admlogin.html +++ b/templates/fi/admlogin.html @@ -8,7 +8,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/fi/headfoot.html b/templates/fi/headfoot.html index 733a26a2..206888f8 100644 --- a/templates/fi/headfoot.html +++ b/templates/fi/headfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/fi/listinfo.html b/templates/fi/listinfo.html index 65310849..6982aacd 100644 --- a/templates/fi/listinfo.html +++ b/templates/fi/listinfo.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/fi/options.html b/templates/fi/options.html index 77714b82..068652e7 100644 --- a/templates/fi/options.html +++ b/templates/fi/options.html @@ -8,7 +8,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/fi/private.html b/templates/fi/private.html index 5e3dd09c..663a2b8a 100755 --- a/templates/fi/private.html +++ b/templates/fi/private.html @@ -8,7 +8,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/fi/roster.html b/templates/fi/roster.html index 757ee9be..600242f2 100644 --- a/templates/fi/roster.html +++ b/templates/fi/roster.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/fi/subscribe.html b/templates/fi/subscribe.html index 4182a19b..f256920f 100644 --- a/templates/fi/subscribe.html +++ b/templates/fi/subscribe.html @@ -5,7 +5,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/fr/admindbdetails.html b/templates/fr/admindbdetails.html index 4a7738e0..9b16fe34 100644 --- a/templates/fr/admindbdetails.html +++ b/templates/fr/admindbdetails.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/fr/admindbpreamble.html b/templates/fr/admindbpreamble.html index 3acc5e0c..ce304d5d 100644 --- a/templates/fr/admindbpreamble.html +++ b/templates/fr/admindbpreamble.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/fr/admindbsummary.html b/templates/fr/admindbsummary.html index 1d1b1775..086614f7 100644 --- a/templates/fr/admindbsummary.html +++ b/templates/fr/admindbsummary.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/fr/admlogin.html b/templates/fr/admlogin.html index 1125e1b3..c846ee77 100755 --- a/templates/fr/admlogin.html +++ b/templates/fr/admlogin.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/fr/archidxentry.html b/templates/fr/archidxentry.html index d03f348e..55962d64 100644 --- a/templates/fr/archidxentry.html +++ b/templates/fr/archidxentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/fr/archidxfoot.html b/templates/fr/archidxfoot.html index f6ed223c..2fce4cf5 100644 --- a/templates/fr/archidxfoot.html +++ b/templates/fr/archidxfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/fr/archidxhead.html b/templates/fr/archidxhead.html index 38d4db25..77ad429c 100644 --- a/templates/fr/archidxhead.html +++ b/templates/fr/archidxhead.html @@ -8,7 +8,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/fr/archlistend.html b/templates/fr/archlistend.html index 2e1191b0..492cf01a 100644 --- a/templates/fr/archlistend.html +++ b/templates/fr/archlistend.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/fr/archliststart.html b/templates/fr/archliststart.html index 892dd7a9..9c718880 100644 --- a/templates/fr/archliststart.html +++ b/templates/fr/archliststart.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/fr/archtoc.html b/templates/fr/archtoc.html index ab9f9332..e8f23286 100644 --- a/templates/fr/archtoc.html +++ b/templates/fr/archtoc.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/fr/archtocentry.html b/templates/fr/archtocentry.html index 935a262c..5279488e 100644 --- a/templates/fr/archtocentry.html +++ b/templates/fr/archtocentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/fr/archtocnombox.html b/templates/fr/archtocnombox.html index 1b025999..6b1cb5e4 100644 --- a/templates/fr/archtocnombox.html +++ b/templates/fr/archtocnombox.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/fr/emptyarchive.html b/templates/fr/emptyarchive.html index b0cfe855..630d5379 100644 --- a/templates/fr/emptyarchive.html +++ b/templates/fr/emptyarchive.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/fr/handle_opts.html b/templates/fr/handle_opts.html index 0d9ab9b0..675b003e 100644 --- a/templates/fr/handle_opts.html +++ b/templates/fr/handle_opts.html @@ -4,7 +4,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/fr/headfoot.html b/templates/fr/headfoot.html index a245393e..b5078895 100644 --- a/templates/fr/headfoot.html +++ b/templates/fr/headfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/fr/listinfo.html b/templates/fr/listinfo.html index 3df6fa7f..a9247a19 100644 --- a/templates/fr/listinfo.html +++ b/templates/fr/listinfo.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/fr/options.html b/templates/fr/options.html index 1d6458b5..1953dca1 100644 --- a/templates/fr/options.html +++ b/templates/fr/options.html @@ -8,7 +8,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/fr/private.html b/templates/fr/private.html index 0d35f5c6..dedd1613 100755 --- a/templates/fr/private.html +++ b/templates/fr/private.html @@ -8,7 +8,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/fr/roster.html b/templates/fr/roster.html index 4daba0bc..68e358c1 100644 --- a/templates/fr/roster.html +++ b/templates/fr/roster.html @@ -6,7 +6,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/fr/subscribe.html b/templates/fr/subscribe.html index f953e9f8..5bee195a 100644 --- a/templates/fr/subscribe.html +++ b/templates/fr/subscribe.html @@ -5,7 +5,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/gl/admindbdetails.html b/templates/gl/admindbdetails.html index fbfee55e..fe424714 100644 --- a/templates/gl/admindbdetails.html +++ b/templates/gl/admindbdetails.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/gl/admindbpreamble.html b/templates/gl/admindbpreamble.html index e148eb34..e6f269fc 100644 --- a/templates/gl/admindbpreamble.html +++ b/templates/gl/admindbpreamble.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/gl/admindbsummary.html b/templates/gl/admindbsummary.html index 543a1ee7..f667285f 100644 --- a/templates/gl/admindbsummary.html +++ b/templates/gl/admindbsummary.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/gl/admlogin.html b/templates/gl/admlogin.html index dcdc66d3..006d19c7 100755 --- a/templates/gl/admlogin.html +++ b/templates/gl/admlogin.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/gl/archidxentry.html b/templates/gl/archidxentry.html index 5c620b20..2efd9486 100644 --- a/templates/gl/archidxentry.html +++ b/templates/gl/archidxentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/gl/archidxfoot.html b/templates/gl/archidxfoot.html index 2887c97d..f7680100 100644 --- a/templates/gl/archidxfoot.html +++ b/templates/gl/archidxfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/gl/archidxhead.html b/templates/gl/archidxhead.html index 4d102bad..b247fe50 100644 --- a/templates/gl/archidxhead.html +++ b/templates/gl/archidxhead.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/gl/archlistend.html b/templates/gl/archlistend.html index 2e1191b0..492cf01a 100644 --- a/templates/gl/archlistend.html +++ b/templates/gl/archlistend.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/gl/archliststart.html b/templates/gl/archliststart.html index 36b06275..211822d3 100644 --- a/templates/gl/archliststart.html +++ b/templates/gl/archliststart.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/gl/archtoc.html b/templates/gl/archtoc.html index 85d2230d..6f6c0d64 100644 --- a/templates/gl/archtoc.html +++ b/templates/gl/archtoc.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/gl/archtocentry.html b/templates/gl/archtocentry.html index 3d20cee6..2b004502 100644 --- a/templates/gl/archtocentry.html +++ b/templates/gl/archtocentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/gl/emptyarchive.html b/templates/gl/emptyarchive.html index 54c97eda..82174714 100644 --- a/templates/gl/emptyarchive.html +++ b/templates/gl/emptyarchive.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/gl/handle_opts.html b/templates/gl/handle_opts.html index 64fb0388..19c9e472 100644 --- a/templates/gl/handle_opts.html +++ b/templates/gl/handle_opts.html @@ -4,7 +4,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/gl/headfoot.html b/templates/gl/headfoot.html index bc502402..4d37fdca 100644 --- a/templates/gl/headfoot.html +++ b/templates/gl/headfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/gl/listinfo.html b/templates/gl/listinfo.html index 760e028e..e6c729be 100644 --- a/templates/gl/listinfo.html +++ b/templates/gl/listinfo.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/gl/options.html b/templates/gl/options.html index a7e347ef..d2388824 100644 --- a/templates/gl/options.html +++ b/templates/gl/options.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/gl/private.html b/templates/gl/private.html index 2790b45b..0a06b7b7 100755 --- a/templates/gl/private.html +++ b/templates/gl/private.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/gl/roster.html b/templates/gl/roster.html index 7f9eb92a..50d34f33 100644 --- a/templates/gl/roster.html +++ b/templates/gl/roster.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/gl/subscribe.html b/templates/gl/subscribe.html index 91b11a04..fa38669a 100644 --- a/templates/gl/subscribe.html +++ b/templates/gl/subscribe.html @@ -5,7 +5,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/he/admindbdetails.html b/templates/he/admindbdetails.html index b5e28d7d..e64e2857 100644 --- a/templates/he/admindbdetails.html +++ b/templates/he/admindbdetails.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/he/admindbpreamble.html b/templates/he/admindbpreamble.html index a2651996..e5d55667 100644 --- a/templates/he/admindbpreamble.html +++ b/templates/he/admindbpreamble.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/he/admindbsummary.html b/templates/he/admindbsummary.html index d8abc893..6b64e115 100644 --- a/templates/he/admindbsummary.html +++ b/templates/he/admindbsummary.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/he/admlogin.html b/templates/he/admlogin.html index f1e14028..1b257194 100755 --- a/templates/he/admlogin.html +++ b/templates/he/admlogin.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/he/archidxentry.html b/templates/he/archidxentry.html index 5c620b20..2efd9486 100644 --- a/templates/he/archidxentry.html +++ b/templates/he/archidxentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/he/archidxfoot.html b/templates/he/archidxfoot.html index 6c24fa5f..e5e9ff5d 100644 --- a/templates/he/archidxfoot.html +++ b/templates/he/archidxfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/he/archidxhead.html b/templates/he/archidxhead.html index f30c4d10..229e324d 100644 --- a/templates/he/archidxhead.html +++ b/templates/he/archidxhead.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/he/archlistend.html b/templates/he/archlistend.html index 2e1191b0..492cf01a 100644 --- a/templates/he/archlistend.html +++ b/templates/he/archlistend.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/he/archliststart.html b/templates/he/archliststart.html index 95363c24..f9861372 100644 --- a/templates/he/archliststart.html +++ b/templates/he/archliststart.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/he/archtoc.html b/templates/he/archtoc.html index 62e27f1e..c49f6bb5 100644 --- a/templates/he/archtoc.html +++ b/templates/he/archtoc.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/he/archtocentry.html b/templates/he/archtocentry.html index aac9050e..044d4605 100644 --- a/templates/he/archtocentry.html +++ b/templates/he/archtocentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/he/archtocnombox.html b/templates/he/archtocnombox.html index 35c8670f..238375e1 100644 --- a/templates/he/archtocnombox.html +++ b/templates/he/archtocnombox.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/he/emptyarchive.html b/templates/he/emptyarchive.html index 764256db..f6bfdb7f 100644 --- a/templates/he/emptyarchive.html +++ b/templates/he/emptyarchive.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/he/headfoot.html b/templates/he/headfoot.html index 140b6d89..ebcab299 100644 --- a/templates/he/headfoot.html +++ b/templates/he/headfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/he/listinfo.html b/templates/he/listinfo.html index ce46a1ae..a005df11 100644 --- a/templates/he/listinfo.html +++ b/templates/he/listinfo.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/he/options.html b/templates/he/options.html index 7f3d697f..0569a93c 100644 --- a/templates/he/options.html +++ b/templates/he/options.html @@ -8,7 +8,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/he/private.html b/templates/he/private.html index ce7ebe48..9b2aefaa 100755 --- a/templates/he/private.html +++ b/templates/he/private.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/he/roster.html b/templates/he/roster.html index 13669968..8181f97b 100644 --- a/templates/he/roster.html +++ b/templates/he/roster.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/he/subscribe.html b/templates/he/subscribe.html index 4680fcb9..583c1132 100644 --- a/templates/he/subscribe.html +++ b/templates/he/subscribe.html @@ -5,7 +5,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/hr/admindbdetails.html b/templates/hr/admindbdetails.html index f47e7f7b..95b73632 100644 --- a/templates/hr/admindbdetails.html +++ b/templates/hr/admindbdetails.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/hr/admindbpreamble.html b/templates/hr/admindbpreamble.html index 0411e829..edb8b7e4 100644 --- a/templates/hr/admindbpreamble.html +++ b/templates/hr/admindbpreamble.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/hr/admindbsummary.html b/templates/hr/admindbsummary.html index 88efbbaf..829f4ec6 100644 --- a/templates/hr/admindbsummary.html +++ b/templates/hr/admindbsummary.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/hr/admlogin.html b/templates/hr/admlogin.html index ad61f4c5..d92e13ac 100755 --- a/templates/hr/admlogin.html +++ b/templates/hr/admlogin.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/hr/archidxentry.html b/templates/hr/archidxentry.html index 5c620b20..2efd9486 100644 --- a/templates/hr/archidxentry.html +++ b/templates/hr/archidxentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/hr/archidxfoot.html b/templates/hr/archidxfoot.html index eb9ee438..06c8a76d 100644 --- a/templates/hr/archidxfoot.html +++ b/templates/hr/archidxfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/hr/archidxhead.html b/templates/hr/archidxhead.html index 992dec60..5c1ac59f 100644 --- a/templates/hr/archidxhead.html +++ b/templates/hr/archidxhead.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/hr/archlistend.html b/templates/hr/archlistend.html index 2e1191b0..492cf01a 100644 --- a/templates/hr/archlistend.html +++ b/templates/hr/archlistend.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/hr/archliststart.html b/templates/hr/archliststart.html index ee1f933a..fd1f867b 100644 --- a/templates/hr/archliststart.html +++ b/templates/hr/archliststart.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/hr/archtoc.html b/templates/hr/archtoc.html index 65c11371..2e8a8d9c 100644 --- a/templates/hr/archtoc.html +++ b/templates/hr/archtoc.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/hr/archtocentry.html b/templates/hr/archtocentry.html index 287967b5..c5d7381e 100644 --- a/templates/hr/archtocentry.html +++ b/templates/hr/archtocentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/hr/emptyarchive.html b/templates/hr/emptyarchive.html index 2314a4fa..fde2ef67 100644 --- a/templates/hr/emptyarchive.html +++ b/templates/hr/emptyarchive.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/hr/headfoot.html b/templates/hr/headfoot.html index 9e58eb74..0893ed99 100644 --- a/templates/hr/headfoot.html +++ b/templates/hr/headfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/hr/listinfo.html b/templates/hr/listinfo.html index bf79c783..c2e7126a 100644 --- a/templates/hr/listinfo.html +++ b/templates/hr/listinfo.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/hr/options.html b/templates/hr/options.html index ae12fa5c..3fb64f19 100644 --- a/templates/hr/options.html +++ b/templates/hr/options.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/hr/private.html b/templates/hr/private.html index 91b3cacd..969b6d00 100755 --- a/templates/hr/private.html +++ b/templates/hr/private.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/hr/roster.html b/templates/hr/roster.html index 36dcf3f7..34c9a6e6 100644 --- a/templates/hr/roster.html +++ b/templates/hr/roster.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/hr/subscribe.html b/templates/hr/subscribe.html index ba1c5e7f..edc7483d 100644 --- a/templates/hr/subscribe.html +++ b/templates/hr/subscribe.html @@ -5,7 +5,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/hu/admindbdetails.html b/templates/hu/admindbdetails.html index 023b8571..5d40e69a 100644 --- a/templates/hu/admindbdetails.html +++ b/templates/hu/admindbdetails.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/hu/admindbpreamble.html b/templates/hu/admindbpreamble.html index 2148e280..d4e80835 100644 --- a/templates/hu/admindbpreamble.html +++ b/templates/hu/admindbpreamble.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/hu/admindbsummary.html b/templates/hu/admindbsummary.html index 7aecc9ef..e9296adc 100644 --- a/templates/hu/admindbsummary.html +++ b/templates/hu/admindbsummary.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/hu/admlogin.html b/templates/hu/admlogin.html index 650646ba..76d45f54 100755 --- a/templates/hu/admlogin.html +++ b/templates/hu/admlogin.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/hu/archidxentry.html b/templates/hu/archidxentry.html index 5c620b20..2efd9486 100644 --- a/templates/hu/archidxentry.html +++ b/templates/hu/archidxentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/hu/archidxfoot.html b/templates/hu/archidxfoot.html index 6f9aad8c..efa50ace 100644 --- a/templates/hu/archidxfoot.html +++ b/templates/hu/archidxfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/hu/archidxhead.html b/templates/hu/archidxhead.html index ebadad32..eb002c88 100644 --- a/templates/hu/archidxhead.html +++ b/templates/hu/archidxhead.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/hu/archlistend.html b/templates/hu/archlistend.html index 2e1191b0..492cf01a 100644 --- a/templates/hu/archlistend.html +++ b/templates/hu/archlistend.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/hu/archliststart.html b/templates/hu/archliststart.html index 30a0ef81..26b4bc3c 100644 --- a/templates/hu/archliststart.html +++ b/templates/hu/archliststart.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/hu/archtoc.html b/templates/hu/archtoc.html index 9035a807..0b1bfebb 100644 --- a/templates/hu/archtoc.html +++ b/templates/hu/archtoc.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/hu/archtocentry.html b/templates/hu/archtocentry.html index 1303aa97..4125994e 100644 --- a/templates/hu/archtocentry.html +++ b/templates/hu/archtocentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/hu/emptyarchive.html b/templates/hu/emptyarchive.html index 99df1d6f..06fccf43 100644 --- a/templates/hu/emptyarchive.html +++ b/templates/hu/emptyarchive.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/hu/headfoot.html b/templates/hu/headfoot.html index 9c874d39..d72358b5 100644 --- a/templates/hu/headfoot.html +++ b/templates/hu/headfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/hu/illik.html b/templates/hu/illik.html index 7c5c1e8d..e5fba0da 100644 --- a/templates/hu/illik.html +++ b/templates/hu/illik.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/hu/listinfo.html b/templates/hu/listinfo.html index 959d7917..1da8b48c 100644 --- a/templates/hu/listinfo.html +++ b/templates/hu/listinfo.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/hu/options.html b/templates/hu/options.html index ccdb5b0d..dfeb59fc 100644 --- a/templates/hu/options.html +++ b/templates/hu/options.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/hu/private.html b/templates/hu/private.html index 92e59598..06810c3e 100755 --- a/templates/hu/private.html +++ b/templates/hu/private.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/hu/roster.html b/templates/hu/roster.html index 7f445fdf..1c4966ad 100644 --- a/templates/hu/roster.html +++ b/templates/hu/roster.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/hu/subscribe.html b/templates/hu/subscribe.html index 9c2b4e02..782868d1 100644 --- a/templates/hu/subscribe.html +++ b/templates/hu/subscribe.html @@ -6,7 +6,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ia/admindbdetails.html b/templates/ia/admindbdetails.html index b2f8dbcf..64dd409a 100644 --- a/templates/ia/admindbdetails.html +++ b/templates/ia/admindbdetails.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ia/admindbpreamble.html b/templates/ia/admindbpreamble.html index 398b2f39..cab070c2 100644 --- a/templates/ia/admindbpreamble.html +++ b/templates/ia/admindbpreamble.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ia/admindbsummary.html b/templates/ia/admindbsummary.html index 5b180091..16eb1ae6 100644 --- a/templates/ia/admindbsummary.html +++ b/templates/ia/admindbsummary.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ia/admlogin.html b/templates/ia/admlogin.html index dfdf6702..d2386597 100755 --- a/templates/ia/admlogin.html +++ b/templates/ia/admlogin.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ia/archidxentry.html b/templates/ia/archidxentry.html index 5c620b20..2efd9486 100644 --- a/templates/ia/archidxentry.html +++ b/templates/ia/archidxentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ia/archidxfoot.html b/templates/ia/archidxfoot.html index a24817cb..a02a7ee6 100644 --- a/templates/ia/archidxfoot.html +++ b/templates/ia/archidxfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ia/archidxhead.html b/templates/ia/archidxhead.html index 5f1efc3b..450fc882 100644 --- a/templates/ia/archidxhead.html +++ b/templates/ia/archidxhead.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ia/archlistend.html b/templates/ia/archlistend.html index 2e1191b0..492cf01a 100644 --- a/templates/ia/archlistend.html +++ b/templates/ia/archlistend.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ia/archliststart.html b/templates/ia/archliststart.html index a35b710a..e1a7ad2a 100644 --- a/templates/ia/archliststart.html +++ b/templates/ia/archliststart.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ia/archtoc.html b/templates/ia/archtoc.html index 1a1e96a2..ed5459e3 100644 --- a/templates/ia/archtoc.html +++ b/templates/ia/archtoc.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ia/archtocentry.html b/templates/ia/archtocentry.html index 6d76eb64..94625811 100644 --- a/templates/ia/archtocentry.html +++ b/templates/ia/archtocentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ia/archtocnombox.html b/templates/ia/archtocnombox.html index fcfa3c51..d6b768b4 100644 --- a/templates/ia/archtocnombox.html +++ b/templates/ia/archtocnombox.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ia/emptyarchive.html b/templates/ia/emptyarchive.html index 29153c18..298bfff7 100644 --- a/templates/ia/emptyarchive.html +++ b/templates/ia/emptyarchive.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ia/headfoot.html b/templates/ia/headfoot.html index f64d3b79..91dd949c 100644 --- a/templates/ia/headfoot.html +++ b/templates/ia/headfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ia/listinfo.html b/templates/ia/listinfo.html index 91fa6f1e..f81c020f 100644 --- a/templates/ia/listinfo.html +++ b/templates/ia/listinfo.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ia/options.html b/templates/ia/options.html index 7201310a..37d72e0d 100644 --- a/templates/ia/options.html +++ b/templates/ia/options.html @@ -8,7 +8,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ia/private.html b/templates/ia/private.html index b4fb4fca..1a8c1e2e 100755 --- a/templates/ia/private.html +++ b/templates/ia/private.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ia/roster.html b/templates/ia/roster.html index a8f6e663..7ec77a33 100644 --- a/templates/ia/roster.html +++ b/templates/ia/roster.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ia/subscribe.html b/templates/ia/subscribe.html index d03e9c10..a800ff49 100644 --- a/templates/ia/subscribe.html +++ b/templates/ia/subscribe.html @@ -5,7 +5,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/it/admindbdetails.html b/templates/it/admindbdetails.html index cef0f971..b85c7932 100644 --- a/templates/it/admindbdetails.html +++ b/templates/it/admindbdetails.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/it/admindbpreamble.html b/templates/it/admindbpreamble.html index 2f574645..a3ee4cdc 100644 --- a/templates/it/admindbpreamble.html +++ b/templates/it/admindbpreamble.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/it/admindbsummary.html b/templates/it/admindbsummary.html index ece2c813..8f43ebcf 100644 --- a/templates/it/admindbsummary.html +++ b/templates/it/admindbsummary.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/it/admlogin.html b/templates/it/admlogin.html index c1fe90a2..6d70c6b2 100755 --- a/templates/it/admlogin.html +++ b/templates/it/admlogin.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/it/archidxentry.html b/templates/it/archidxentry.html index 5c620b20..2efd9486 100644 --- a/templates/it/archidxentry.html +++ b/templates/it/archidxentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/it/archidxfoot.html b/templates/it/archidxfoot.html index 47af3b87..86fbcc4c 100644 --- a/templates/it/archidxfoot.html +++ b/templates/it/archidxfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/it/archidxhead.html b/templates/it/archidxhead.html index c6e71ee9..03ab7091 100644 --- a/templates/it/archidxhead.html +++ b/templates/it/archidxhead.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/it/archlistend.html b/templates/it/archlistend.html index 2e1191b0..492cf01a 100644 --- a/templates/it/archlistend.html +++ b/templates/it/archlistend.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/it/archliststart.html b/templates/it/archliststart.html index a7a46b39..bb80d82a 100644 --- a/templates/it/archliststart.html +++ b/templates/it/archliststart.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/it/archtoc.html b/templates/it/archtoc.html index c378f61a..5ef60d9a 100644 --- a/templates/it/archtoc.html +++ b/templates/it/archtoc.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/it/archtocentry.html b/templates/it/archtocentry.html index 781dbd53..0f8c28ce 100644 --- a/templates/it/archtocentry.html +++ b/templates/it/archtocentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/it/archtocnombox.html b/templates/it/archtocnombox.html index 76c1e2d9..e192834c 100644 --- a/templates/it/archtocnombox.html +++ b/templates/it/archtocnombox.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/it/emptyarchive.html b/templates/it/emptyarchive.html index bfcb53b6..612aa8a9 100644 --- a/templates/it/emptyarchive.html +++ b/templates/it/emptyarchive.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/it/headfoot.html b/templates/it/headfoot.html index 4b01ca60..cf532d4f 100644 --- a/templates/it/headfoot.html +++ b/templates/it/headfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/it/listinfo.html b/templates/it/listinfo.html index 064f1c8f..8c9f6445 100644 --- a/templates/it/listinfo.html +++ b/templates/it/listinfo.html @@ -8,7 +8,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/it/options.html b/templates/it/options.html index 61c04b47..b734e050 100644 --- a/templates/it/options.html +++ b/templates/it/options.html @@ -8,7 +8,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/it/private.html b/templates/it/private.html index 5faf5f29..7a929d8b 100755 --- a/templates/it/private.html +++ b/templates/it/private.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/it/roster.html b/templates/it/roster.html index 92ddf18f..6ef585b4 100644 --- a/templates/it/roster.html +++ b/templates/it/roster.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/it/subscribe.html b/templates/it/subscribe.html index 9e24b6c5..2c70b2da 100644 --- a/templates/it/subscribe.html +++ b/templates/it/subscribe.html @@ -4,7 +4,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ja/admindbdetails.html b/templates/ja/admindbdetails.html index 3d07491e..e033fdc6 100644 --- a/templates/ja/admindbdetails.html +++ b/templates/ja/admindbdetails.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ja/admindbpreamble.html b/templates/ja/admindbpreamble.html index a13c2fce..ea46706b 100644 --- a/templates/ja/admindbpreamble.html +++ b/templates/ja/admindbpreamble.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ja/admindbsummary.html b/templates/ja/admindbsummary.html index bcd2d0ec..0c2fd444 100644 --- a/templates/ja/admindbsummary.html +++ b/templates/ja/admindbsummary.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ja/admlogin.html b/templates/ja/admlogin.html index 13c93565..3c50da66 100755 --- a/templates/ja/admlogin.html +++ b/templates/ja/admlogin.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ja/archidxentry.html b/templates/ja/archidxentry.html index 5c620b20..2efd9486 100644 --- a/templates/ja/archidxentry.html +++ b/templates/ja/archidxentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ja/archidxfoot.html b/templates/ja/archidxfoot.html index 795c94a0..8756f053 100644 --- a/templates/ja/archidxfoot.html +++ b/templates/ja/archidxfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ja/archidxhead.html b/templates/ja/archidxhead.html index 5ef4b421..b9b06e8e 100644 --- a/templates/ja/archidxhead.html +++ b/templates/ja/archidxhead.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ja/archlistend.html b/templates/ja/archlistend.html index 2e1191b0..492cf01a 100644 --- a/templates/ja/archlistend.html +++ b/templates/ja/archlistend.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ja/archliststart.html b/templates/ja/archliststart.html index 473f14e8..208e2917 100644 --- a/templates/ja/archliststart.html +++ b/templates/ja/archliststart.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ja/archtoc.html b/templates/ja/archtoc.html index ffdb48ff..0a4caee6 100644 --- a/templates/ja/archtoc.html +++ b/templates/ja/archtoc.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ja/archtocentry.html b/templates/ja/archtocentry.html index 989eca47..e23de3d1 100644 --- a/templates/ja/archtocentry.html +++ b/templates/ja/archtocentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ja/archtocnombox.html b/templates/ja/archtocnombox.html index 4a07679a..be1f9123 100644 --- a/templates/ja/archtocnombox.html +++ b/templates/ja/archtocnombox.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ja/emptyarchive.html b/templates/ja/emptyarchive.html index 758efffe..5e993488 100644 --- a/templates/ja/emptyarchive.html +++ b/templates/ja/emptyarchive.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ja/headfoot.html b/templates/ja/headfoot.html index f19f8deb..b674f89d 100644 --- a/templates/ja/headfoot.html +++ b/templates/ja/headfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ja/listinfo.html b/templates/ja/listinfo.html index 21e98991..33110530 100644 --- a/templates/ja/listinfo.html +++ b/templates/ja/listinfo.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ja/options.html b/templates/ja/options.html index 1a8ffea1..288385b4 100644 --- a/templates/ja/options.html +++ b/templates/ja/options.html @@ -8,7 +8,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ja/private.html b/templates/ja/private.html index 10779ec4..5aba45a5 100755 --- a/templates/ja/private.html +++ b/templates/ja/private.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ja/roster.html b/templates/ja/roster.html index e6a9b5ff..e37dbc48 100644 --- a/templates/ja/roster.html +++ b/templates/ja/roster.html @@ -8,7 +8,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ja/subscribe.html b/templates/ja/subscribe.html index 957bb79c..99a3d61b 100644 --- a/templates/ja/subscribe.html +++ b/templates/ja/subscribe.html @@ -5,7 +5,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ko/admindbdetails.html b/templates/ko/admindbdetails.html index 17b8e46b..dcf8e010 100644 --- a/templates/ko/admindbdetails.html +++ b/templates/ko/admindbdetails.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ko/admindbpreamble.html b/templates/ko/admindbpreamble.html index 91c1106e..3623cbd1 100644 --- a/templates/ko/admindbpreamble.html +++ b/templates/ko/admindbpreamble.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ko/admindbsummary.html b/templates/ko/admindbsummary.html index 88a8b255..3981a9aa 100644 --- a/templates/ko/admindbsummary.html +++ b/templates/ko/admindbsummary.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ko/admlogin.html b/templates/ko/admlogin.html index 8d35efc3..bc8f0c41 100755 --- a/templates/ko/admlogin.html +++ b/templates/ko/admlogin.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ko/emptyarchive.html b/templates/ko/emptyarchive.html index 8ddf402f..5c1c0799 100644 --- a/templates/ko/emptyarchive.html +++ b/templates/ko/emptyarchive.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ko/headfoot.html b/templates/ko/headfoot.html index ff18dc79..577d5c73 100644 --- a/templates/ko/headfoot.html +++ b/templates/ko/headfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ko/listinfo.html b/templates/ko/listinfo.html index 35727de8..503fb119 100644 --- a/templates/ko/listinfo.html +++ b/templates/ko/listinfo.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ko/options.html b/templates/ko/options.html index ad105982..c2cdf2f4 100644 --- a/templates/ko/options.html +++ b/templates/ko/options.html @@ -8,7 +8,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ko/private.html b/templates/ko/private.html index e84dca03..f3e5419d 100755 --- a/templates/ko/private.html +++ b/templates/ko/private.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ko/roster.html b/templates/ko/roster.html index ff10e20c..1896468e 100644 --- a/templates/ko/roster.html +++ b/templates/ko/roster.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ko/subscribe.html b/templates/ko/subscribe.html index a7856269..824b0f53 100644 --- a/templates/ko/subscribe.html +++ b/templates/ko/subscribe.html @@ -5,7 +5,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/lt/admindbdetails.html b/templates/lt/admindbdetails.html index b9ad912d..be77962b 100644 --- a/templates/lt/admindbdetails.html +++ b/templates/lt/admindbdetails.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/lt/admindbpreamble.html b/templates/lt/admindbpreamble.html index 063b1e10..903e9ae6 100644 --- a/templates/lt/admindbpreamble.html +++ b/templates/lt/admindbpreamble.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/lt/admindbsummary.html b/templates/lt/admindbsummary.html index f39c1cf9..b8b35beb 100644 --- a/templates/lt/admindbsummary.html +++ b/templates/lt/admindbsummary.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/lt/admlogin.html b/templates/lt/admlogin.html index 7e979292..2a923bdd 100755 --- a/templates/lt/admlogin.html +++ b/templates/lt/admlogin.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/lt/archidxentry.html b/templates/lt/archidxentry.html index 5c620b20..2efd9486 100644 --- a/templates/lt/archidxentry.html +++ b/templates/lt/archidxentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/lt/archidxfoot.html b/templates/lt/archidxfoot.html index 3b4e4e58..3972b4a1 100644 --- a/templates/lt/archidxfoot.html +++ b/templates/lt/archidxfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/lt/archidxhead.html b/templates/lt/archidxhead.html index f32225d7..93c2c3d6 100644 --- a/templates/lt/archidxhead.html +++ b/templates/lt/archidxhead.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/lt/archlistend.html b/templates/lt/archlistend.html index 2e1191b0..492cf01a 100644 --- a/templates/lt/archlistend.html +++ b/templates/lt/archlistend.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/lt/archliststart.html b/templates/lt/archliststart.html index 75a4594f..8c307bdf 100644 --- a/templates/lt/archliststart.html +++ b/templates/lt/archliststart.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/lt/archtoc.html b/templates/lt/archtoc.html index 974a84d0..dc64502a 100644 --- a/templates/lt/archtoc.html +++ b/templates/lt/archtoc.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/lt/archtocentry.html b/templates/lt/archtocentry.html index 9bc665e7..e8881969 100644 --- a/templates/lt/archtocentry.html +++ b/templates/lt/archtocentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/lt/emptyarchive.html b/templates/lt/emptyarchive.html index b042563b..b07cf56c 100644 --- a/templates/lt/emptyarchive.html +++ b/templates/lt/emptyarchive.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/lt/headfoot.html b/templates/lt/headfoot.html index dd4a2390..95978dce 100644 --- a/templates/lt/headfoot.html +++ b/templates/lt/headfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/lt/listinfo.html b/templates/lt/listinfo.html index 0681e904..7d3ef9bf 100644 --- a/templates/lt/listinfo.html +++ b/templates/lt/listinfo.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/lt/options.html b/templates/lt/options.html index 1ed00392..e2caed04 100644 --- a/templates/lt/options.html +++ b/templates/lt/options.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/lt/private.html b/templates/lt/private.html index b6df4626..48cd4f5d 100755 --- a/templates/lt/private.html +++ b/templates/lt/private.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/lt/roster.html b/templates/lt/roster.html index cc7aa94f..f64cebbd 100644 --- a/templates/lt/roster.html +++ b/templates/lt/roster.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/lt/subscribe.html b/templates/lt/subscribe.html index 3e523518..129960ea 100644 --- a/templates/lt/subscribe.html +++ b/templates/lt/subscribe.html @@ -5,7 +5,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/nl/admindbdetails.html b/templates/nl/admindbdetails.html index c9d69a6c..cfd77d98 100644 --- a/templates/nl/admindbdetails.html +++ b/templates/nl/admindbdetails.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/nl/admindbpreamble.html b/templates/nl/admindbpreamble.html index 51723f2c..94ba2bed 100644 --- a/templates/nl/admindbpreamble.html +++ b/templates/nl/admindbpreamble.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/nl/admindbsummary.html b/templates/nl/admindbsummary.html index fb8c5aef..c07d25a6 100644 --- a/templates/nl/admindbsummary.html +++ b/templates/nl/admindbsummary.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/nl/admlogin.html b/templates/nl/admlogin.html index d2d0142d..cd734098 100755 --- a/templates/nl/admlogin.html +++ b/templates/nl/admlogin.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/nl/archidxentry.html b/templates/nl/archidxentry.html index 5c620b20..2efd9486 100644 --- a/templates/nl/archidxentry.html +++ b/templates/nl/archidxentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/nl/archidxfoot.html b/templates/nl/archidxfoot.html index 95b1b4ea..307526dd 100644 --- a/templates/nl/archidxfoot.html +++ b/templates/nl/archidxfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/nl/archidxhead.html b/templates/nl/archidxhead.html index 09175428..be08b9b3 100644 --- a/templates/nl/archidxhead.html +++ b/templates/nl/archidxhead.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/nl/archlistend.html b/templates/nl/archlistend.html index 2e1191b0..492cf01a 100644 --- a/templates/nl/archlistend.html +++ b/templates/nl/archlistend.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/nl/archliststart.html b/templates/nl/archliststart.html index 2f6bf5dd..f6791e2d 100644 --- a/templates/nl/archliststart.html +++ b/templates/nl/archliststart.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/nl/archtoc.html b/templates/nl/archtoc.html index 43ddd440..7adaed68 100644 --- a/templates/nl/archtoc.html +++ b/templates/nl/archtoc.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/nl/archtocentry.html b/templates/nl/archtocentry.html index b31d4303..0b3662be 100644 --- a/templates/nl/archtocentry.html +++ b/templates/nl/archtocentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/nl/archtocnombox.html b/templates/nl/archtocnombox.html index 92d30465..2a90bf92 100644 --- a/templates/nl/archtocnombox.html +++ b/templates/nl/archtocnombox.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/nl/emptyarchive.html b/templates/nl/emptyarchive.html index 168b4848..32dd3482 100644 --- a/templates/nl/emptyarchive.html +++ b/templates/nl/emptyarchive.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/nl/headfoot.html b/templates/nl/headfoot.html index 5ef62beb..8b4a6529 100644 --- a/templates/nl/headfoot.html +++ b/templates/nl/headfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/nl/listinfo.html b/templates/nl/listinfo.html index 30fd5e01..14bce364 100644 --- a/templates/nl/listinfo.html +++ b/templates/nl/listinfo.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/nl/options.html b/templates/nl/options.html index a236737a..7bd683fa 100644 --- a/templates/nl/options.html +++ b/templates/nl/options.html @@ -8,7 +8,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/nl/private.html b/templates/nl/private.html index 374efdac..3d7d0396 100755 --- a/templates/nl/private.html +++ b/templates/nl/private.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/nl/roster.html b/templates/nl/roster.html index fc2b0ba5..c6128ce6 100644 --- a/templates/nl/roster.html +++ b/templates/nl/roster.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/nl/subscribe.html b/templates/nl/subscribe.html index 9dd79554..dc53d8ab 100644 --- a/templates/nl/subscribe.html +++ b/templates/nl/subscribe.html @@ -5,7 +5,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/no/admindbdetails.html b/templates/no/admindbdetails.html index 558c600f..2089bc1c 100644 --- a/templates/no/admindbdetails.html +++ b/templates/no/admindbdetails.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/no/admindbpreamble.html b/templates/no/admindbpreamble.html index e49ff979..6ee94a5d 100644 --- a/templates/no/admindbpreamble.html +++ b/templates/no/admindbpreamble.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/no/admindbsummary.html b/templates/no/admindbsummary.html index 8f5f317a..62f43a1b 100644 --- a/templates/no/admindbsummary.html +++ b/templates/no/admindbsummary.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/no/admlogin.html b/templates/no/admlogin.html index 09506aff..b9f98797 100755 --- a/templates/no/admlogin.html +++ b/templates/no/admlogin.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/no/archidxfoot.html b/templates/no/archidxfoot.html index d2b19de1..20b820bc 100644 --- a/templates/no/archidxfoot.html +++ b/templates/no/archidxfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/no/archidxhead.html b/templates/no/archidxhead.html index 91a98a62..d200bdc4 100644 --- a/templates/no/archidxhead.html +++ b/templates/no/archidxhead.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/no/archliststart.html b/templates/no/archliststart.html index 70bd3548..0b81bbb7 100644 --- a/templates/no/archliststart.html +++ b/templates/no/archliststart.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/no/archtoc.html b/templates/no/archtoc.html index 2973e5e6..6a629b75 100644 --- a/templates/no/archtoc.html +++ b/templates/no/archtoc.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/no/archtocentry.html b/templates/no/archtocentry.html index c1f5aee1..816302ed 100644 --- a/templates/no/archtocentry.html +++ b/templates/no/archtocentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/no/archtocnombox.html b/templates/no/archtocnombox.html index fc113977..7ac38252 100644 --- a/templates/no/archtocnombox.html +++ b/templates/no/archtocnombox.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/no/emptyarchive.html b/templates/no/emptyarchive.html index a56edf90..2831c55b 100644 --- a/templates/no/emptyarchive.html +++ b/templates/no/emptyarchive.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/no/headfoot.html b/templates/no/headfoot.html index 6b48739e..4647381b 100644 --- a/templates/no/headfoot.html +++ b/templates/no/headfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/no/listinfo.html b/templates/no/listinfo.html index e6e75c88..0fa7c3aa 100644 --- a/templates/no/listinfo.html +++ b/templates/no/listinfo.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/no/options.html b/templates/no/options.html index 58578f2d..972cba95 100644 --- a/templates/no/options.html +++ b/templates/no/options.html @@ -8,7 +8,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/no/private.html b/templates/no/private.html index ea7bdca8..4a4710fa 100755 --- a/templates/no/private.html +++ b/templates/no/private.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/no/roster.html b/templates/no/roster.html index 97a03e07..52a50244 100644 --- a/templates/no/roster.html +++ b/templates/no/roster.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/no/subscribe.html b/templates/no/subscribe.html index 5dff222c..f3bc943a 100644 --- a/templates/no/subscribe.html +++ b/templates/no/subscribe.html @@ -5,7 +5,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/pl/admlogin.html b/templates/pl/admlogin.html index 6e0301bd..fbb195ec 100644 --- a/templates/pl/admlogin.html +++ b/templates/pl/admlogin.html @@ -8,7 +8,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/pl/archidxentry.html b/templates/pl/archidxentry.html index 5c620b20..2efd9486 100644 --- a/templates/pl/archidxentry.html +++ b/templates/pl/archidxentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/pl/archidxfoot.html b/templates/pl/archidxfoot.html index 2ecd8ef7..97a5e1ad 100644 --- a/templates/pl/archidxfoot.html +++ b/templates/pl/archidxfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/pl/archidxhead.html b/templates/pl/archidxhead.html index 55a900cd..39014471 100644 --- a/templates/pl/archidxhead.html +++ b/templates/pl/archidxhead.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/pl/archlistend.html b/templates/pl/archlistend.html index 2e1191b0..492cf01a 100644 --- a/templates/pl/archlistend.html +++ b/templates/pl/archlistend.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/pl/archliststart.html b/templates/pl/archliststart.html index 65f3b1de..86511c42 100644 --- a/templates/pl/archliststart.html +++ b/templates/pl/archliststart.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/pl/archtoc.html b/templates/pl/archtoc.html index 0d763cbe..e8959a27 100644 --- a/templates/pl/archtoc.html +++ b/templates/pl/archtoc.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/pl/archtocentry.html b/templates/pl/archtocentry.html index 87199785..038642ba 100644 --- a/templates/pl/archtocentry.html +++ b/templates/pl/archtocentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/pl/archtocnombox.html b/templates/pl/archtocnombox.html index 55c36f48..68721336 100644 --- a/templates/pl/archtocnombox.html +++ b/templates/pl/archtocnombox.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/pl/emptyarchive.html b/templates/pl/emptyarchive.html index 35015879..2787490f 100644 --- a/templates/pl/emptyarchive.html +++ b/templates/pl/emptyarchive.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/pl/listinfo.html b/templates/pl/listinfo.html index 0b68e305..65aa37c6 100644 --- a/templates/pl/listinfo.html +++ b/templates/pl/listinfo.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/pl/options.html b/templates/pl/options.html index 8da5ad5c..0daf507c 100644 --- a/templates/pl/options.html +++ b/templates/pl/options.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/pl/private.html b/templates/pl/private.html index 00c95cdb..03de1af1 100644 --- a/templates/pl/private.html +++ b/templates/pl/private.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/pl/roster.html b/templates/pl/roster.html index e202b4ae..8477f3d8 100644 --- a/templates/pl/roster.html +++ b/templates/pl/roster.html @@ -8,7 +8,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/pl/subscribe.html b/templates/pl/subscribe.html index f64ae8d0..a880f6d9 100644 --- a/templates/pl/subscribe.html +++ b/templates/pl/subscribe.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/pt/admindbdetails.html b/templates/pt/admindbdetails.html index 71a8e5d5..5e5c503f 100644 --- a/templates/pt/admindbdetails.html +++ b/templates/pt/admindbdetails.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/pt/admindbpreamble.html b/templates/pt/admindbpreamble.html index a24c4ac8..1fa1110f 100644 --- a/templates/pt/admindbpreamble.html +++ b/templates/pt/admindbpreamble.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/pt/admindbsummary.html b/templates/pt/admindbsummary.html index 99eae4d8..5b5e1734 100644 --- a/templates/pt/admindbsummary.html +++ b/templates/pt/admindbsummary.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/pt/admlogin.html b/templates/pt/admlogin.html index ae19ac49..e6e36c3a 100755 --- a/templates/pt/admlogin.html +++ b/templates/pt/admlogin.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/pt/archidxentry.html b/templates/pt/archidxentry.html index 5c620b20..2efd9486 100644 --- a/templates/pt/archidxentry.html +++ b/templates/pt/archidxentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/pt/archidxfoot.html b/templates/pt/archidxfoot.html index df3c4e0e..ee968330 100644 --- a/templates/pt/archidxfoot.html +++ b/templates/pt/archidxfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/pt/archidxhead.html b/templates/pt/archidxhead.html index 9b2d10a6..81e68042 100644 --- a/templates/pt/archidxhead.html +++ b/templates/pt/archidxhead.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/pt/archlistend.html b/templates/pt/archlistend.html index 2e1191b0..492cf01a 100644 --- a/templates/pt/archlistend.html +++ b/templates/pt/archlistend.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/pt/archliststart.html b/templates/pt/archliststart.html index 9ef4eeda..407d175d 100644 --- a/templates/pt/archliststart.html +++ b/templates/pt/archliststart.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/pt/archtoc.html b/templates/pt/archtoc.html index 3d6948d1..a2f935c6 100644 --- a/templates/pt/archtoc.html +++ b/templates/pt/archtoc.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/pt/archtocentry.html b/templates/pt/archtocentry.html index cf8e7409..e98d250c 100644 --- a/templates/pt/archtocentry.html +++ b/templates/pt/archtocentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/pt/emptyarchive.html b/templates/pt/emptyarchive.html index 62fe3335..b460276a 100644 --- a/templates/pt/emptyarchive.html +++ b/templates/pt/emptyarchive.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/pt/headfoot.html b/templates/pt/headfoot.html index aaaa708b..e7b77977 100644 --- a/templates/pt/headfoot.html +++ b/templates/pt/headfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/pt/listinfo.html b/templates/pt/listinfo.html index 3d099d6e..b432273e 100644 --- a/templates/pt/listinfo.html +++ b/templates/pt/listinfo.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/pt/options.html b/templates/pt/options.html index 6ca0d12a..5a3a231b 100644 --- a/templates/pt/options.html +++ b/templates/pt/options.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/pt/private.html b/templates/pt/private.html index 6e32cc65..43ca188e 100755 --- a/templates/pt/private.html +++ b/templates/pt/private.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/pt/roster.html b/templates/pt/roster.html index 1b587d7b..b4d9c01b 100644 --- a/templates/pt/roster.html +++ b/templates/pt/roster.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/pt/subscribe.html b/templates/pt/subscribe.html index 6e2b6a79..c24661b0 100644 --- a/templates/pt/subscribe.html +++ b/templates/pt/subscribe.html @@ -5,7 +5,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/pt_BR/admindbdetails.html b/templates/pt_BR/admindbdetails.html index 342532c6..e018f3ad 100644 --- a/templates/pt_BR/admindbdetails.html +++ b/templates/pt_BR/admindbdetails.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/pt_BR/admindbpreamble.html b/templates/pt_BR/admindbpreamble.html index a994571f..d76d73b2 100644 --- a/templates/pt_BR/admindbpreamble.html +++ b/templates/pt_BR/admindbpreamble.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/pt_BR/admindbsummary.html b/templates/pt_BR/admindbsummary.html index 84170179..ba2bdbc0 100644 --- a/templates/pt_BR/admindbsummary.html +++ b/templates/pt_BR/admindbsummary.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/pt_BR/admlogin.html b/templates/pt_BR/admlogin.html index 47942532..f9fe2d02 100755 --- a/templates/pt_BR/admlogin.html +++ b/templates/pt_BR/admlogin.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/pt_BR/archidxentry.html b/templates/pt_BR/archidxentry.html index 5c620b20..2efd9486 100644 --- a/templates/pt_BR/archidxentry.html +++ b/templates/pt_BR/archidxentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/pt_BR/archidxfoot.html b/templates/pt_BR/archidxfoot.html index 51c2301f..5dc22126 100644 --- a/templates/pt_BR/archidxfoot.html +++ b/templates/pt_BR/archidxfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/pt_BR/archidxhead.html b/templates/pt_BR/archidxhead.html index 76ee1625..ad348922 100644 --- a/templates/pt_BR/archidxhead.html +++ b/templates/pt_BR/archidxhead.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/pt_BR/archlistend.html b/templates/pt_BR/archlistend.html index 2e1191b0..492cf01a 100644 --- a/templates/pt_BR/archlistend.html +++ b/templates/pt_BR/archlistend.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/pt_BR/archliststart.html b/templates/pt_BR/archliststart.html index 422b5e5c..9de7dd9f 100644 --- a/templates/pt_BR/archliststart.html +++ b/templates/pt_BR/archliststart.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/pt_BR/archtoc.html b/templates/pt_BR/archtoc.html index aed8d0d0..466c78c3 100644 --- a/templates/pt_BR/archtoc.html +++ b/templates/pt_BR/archtoc.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/pt_BR/archtocentry.html b/templates/pt_BR/archtocentry.html index 6f2a92bb..97136874 100644 --- a/templates/pt_BR/archtocentry.html +++ b/templates/pt_BR/archtocentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/pt_BR/emptyarchive.html b/templates/pt_BR/emptyarchive.html index 632cbc16..b2076551 100644 --- a/templates/pt_BR/emptyarchive.html +++ b/templates/pt_BR/emptyarchive.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/pt_BR/headfoot.html b/templates/pt_BR/headfoot.html index cbd1bf7c..05a9d8fa 100644 --- a/templates/pt_BR/headfoot.html +++ b/templates/pt_BR/headfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/pt_BR/listinfo.html b/templates/pt_BR/listinfo.html index 463c4a50..ae65b98e 100644 --- a/templates/pt_BR/listinfo.html +++ b/templates/pt_BR/listinfo.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/pt_BR/options.html b/templates/pt_BR/options.html index 36426758..0e043e9f 100644 --- a/templates/pt_BR/options.html +++ b/templates/pt_BR/options.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/pt_BR/private.html b/templates/pt_BR/private.html index d146f57b..758b78e5 100755 --- a/templates/pt_BR/private.html +++ b/templates/pt_BR/private.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/pt_BR/roster.html b/templates/pt_BR/roster.html index fc6d728c..93584904 100644 --- a/templates/pt_BR/roster.html +++ b/templates/pt_BR/roster.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/pt_BR/subscribe.html b/templates/pt_BR/subscribe.html index d1b47a2d..2485ffe6 100644 --- a/templates/pt_BR/subscribe.html +++ b/templates/pt_BR/subscribe.html @@ -5,7 +5,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ro/admindbdetails.html b/templates/ro/admindbdetails.html index f86f3c3c..594964cb 100644 --- a/templates/ro/admindbdetails.html +++ b/templates/ro/admindbdetails.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ro/admindbpreamble.html b/templates/ro/admindbpreamble.html index 9b002fa4..215e9783 100644 --- a/templates/ro/admindbpreamble.html +++ b/templates/ro/admindbpreamble.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ro/admindbsummary.html b/templates/ro/admindbsummary.html index d4cdaf2a..226e8f16 100644 --- a/templates/ro/admindbsummary.html +++ b/templates/ro/admindbsummary.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ro/admlogin.html b/templates/ro/admlogin.html index 723b35ca..b8a56004 100755 --- a/templates/ro/admlogin.html +++ b/templates/ro/admlogin.html @@ -8,7 +8,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ro/archidxentry.html b/templates/ro/archidxentry.html index 5c620b20..2efd9486 100644 --- a/templates/ro/archidxentry.html +++ b/templates/ro/archidxentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ro/archidxfoot.html b/templates/ro/archidxfoot.html index b4ded18b..df684248 100644 --- a/templates/ro/archidxfoot.html +++ b/templates/ro/archidxfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ro/archidxhead.html b/templates/ro/archidxhead.html index 8cf2834c..0c9b0b35 100644 --- a/templates/ro/archidxhead.html +++ b/templates/ro/archidxhead.html @@ -11,7 +11,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ro/archlistend.html b/templates/ro/archlistend.html index 2e1191b0..492cf01a 100644 --- a/templates/ro/archlistend.html +++ b/templates/ro/archlistend.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ro/archliststart.html b/templates/ro/archliststart.html index 969d7ae3..2d133dc5 100644 --- a/templates/ro/archliststart.html +++ b/templates/ro/archliststart.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ro/archtoc.html b/templates/ro/archtoc.html index d9d8ef40..d8a2f71b 100644 --- a/templates/ro/archtoc.html +++ b/templates/ro/archtoc.html @@ -11,7 +11,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ro/archtocentry.html b/templates/ro/archtocentry.html index 1eb478ac..0e334ac1 100644 --- a/templates/ro/archtocentry.html +++ b/templates/ro/archtocentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ro/emptyarchive.html b/templates/ro/emptyarchive.html index 4ee49d67..6e2c0d73 100644 --- a/templates/ro/emptyarchive.html +++ b/templates/ro/emptyarchive.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ro/headfoot.html b/templates/ro/headfoot.html index c1c58c02..fed92354 100644 --- a/templates/ro/headfoot.html +++ b/templates/ro/headfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ro/listinfo.html b/templates/ro/listinfo.html index 056d12ae..cb90aa7d 100644 --- a/templates/ro/listinfo.html +++ b/templates/ro/listinfo.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ro/options.html b/templates/ro/options.html index 0e2a2e9a..1759d0cd 100644 --- a/templates/ro/options.html +++ b/templates/ro/options.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ro/private.html b/templates/ro/private.html index bfaa6b63..02f4cdc5 100755 --- a/templates/ro/private.html +++ b/templates/ro/private.html @@ -8,7 +8,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ro/roster.html b/templates/ro/roster.html index b225a425..b0aa49f4 100644 --- a/templates/ro/roster.html +++ b/templates/ro/roster.html @@ -8,7 +8,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ro/subscribe.html b/templates/ro/subscribe.html index 6da96692..52f24dee 100644 --- a/templates/ro/subscribe.html +++ b/templates/ro/subscribe.html @@ -5,7 +5,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ru/admindbdetails.html b/templates/ru/admindbdetails.html index 0747cd77..7fa56263 100644 --- a/templates/ru/admindbdetails.html +++ b/templates/ru/admindbdetails.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ru/admindbpreamble.html b/templates/ru/admindbpreamble.html index e70af963..f010c106 100644 --- a/templates/ru/admindbpreamble.html +++ b/templates/ru/admindbpreamble.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ru/admindbsummary.html b/templates/ru/admindbsummary.html index 85767855..80952cad 100644 --- a/templates/ru/admindbsummary.html +++ b/templates/ru/admindbsummary.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ru/admlogin.html b/templates/ru/admlogin.html index b18acf10..4073c265 100755 --- a/templates/ru/admlogin.html +++ b/templates/ru/admlogin.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ru/archidxentry.html b/templates/ru/archidxentry.html index 0f442688..4548a01c 100644 --- a/templates/ru/archidxentry.html +++ b/templates/ru/archidxentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ru/archidxfoot.html b/templates/ru/archidxfoot.html index ebc06f15..876b9d63 100644 --- a/templates/ru/archidxfoot.html +++ b/templates/ru/archidxfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ru/archidxhead.html b/templates/ru/archidxhead.html index 40e776cf..a3be7470 100644 --- a/templates/ru/archidxhead.html +++ b/templates/ru/archidxhead.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ru/archlistend.html b/templates/ru/archlistend.html index 2e1191b0..492cf01a 100644 --- a/templates/ru/archlistend.html +++ b/templates/ru/archlistend.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ru/archliststart.html b/templates/ru/archliststart.html index f5618eac..9282bc36 100644 --- a/templates/ru/archliststart.html +++ b/templates/ru/archliststart.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ru/archtoc.html b/templates/ru/archtoc.html index f06102d7..4b99b11b 100644 --- a/templates/ru/archtoc.html +++ b/templates/ru/archtoc.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ru/archtocentry.html b/templates/ru/archtocentry.html index 04bc73b7..c1c83f49 100644 --- a/templates/ru/archtocentry.html +++ b/templates/ru/archtocentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ru/archtocnombox.html b/templates/ru/archtocnombox.html index 82c685b9..061c6ee8 100644 --- a/templates/ru/archtocnombox.html +++ b/templates/ru/archtocnombox.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ru/emptyarchive.html b/templates/ru/emptyarchive.html index 94d76a15..25ddac8f 100644 --- a/templates/ru/emptyarchive.html +++ b/templates/ru/emptyarchive.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ru/headfoot.html b/templates/ru/headfoot.html index 7a58f750..f766c54c 100644 --- a/templates/ru/headfoot.html +++ b/templates/ru/headfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ru/listinfo.html b/templates/ru/listinfo.html index fa4c59e7..3de76bd3 100644 --- a/templates/ru/listinfo.html +++ b/templates/ru/listinfo.html @@ -8,7 +8,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ru/options.html b/templates/ru/options.html index 4fed0e96..99f66dc0 100644 --- a/templates/ru/options.html +++ b/templates/ru/options.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ru/private.html b/templates/ru/private.html index d36837ab..b97c0d5c 100755 --- a/templates/ru/private.html +++ b/templates/ru/private.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ru/roster.html b/templates/ru/roster.html index 93191f6c..a9057100 100644 --- a/templates/ru/roster.html +++ b/templates/ru/roster.html @@ -6,7 +6,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/ru/subscribe.html b/templates/ru/subscribe.html index e0664fa3..78077884 100644 --- a/templates/ru/subscribe.html +++ b/templates/ru/subscribe.html @@ -4,7 +4,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sk/admindbdetails.html b/templates/sk/admindbdetails.html index 5484d146..b12c6ba7 100644 --- a/templates/sk/admindbdetails.html +++ b/templates/sk/admindbdetails.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sk/admindbpreamble.html b/templates/sk/admindbpreamble.html index 3b7b4d7e..8398a1fc 100644 --- a/templates/sk/admindbpreamble.html +++ b/templates/sk/admindbpreamble.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sk/admindbsummary.html b/templates/sk/admindbsummary.html index 051d68cb..497d6151 100644 --- a/templates/sk/admindbsummary.html +++ b/templates/sk/admindbsummary.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sk/admlogin.html b/templates/sk/admlogin.html index db9e03eb..0da55025 100755 --- a/templates/sk/admlogin.html +++ b/templates/sk/admlogin.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sk/archidxentry.html b/templates/sk/archidxentry.html index 5c620b20..2efd9486 100644 --- a/templates/sk/archidxentry.html +++ b/templates/sk/archidxentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sk/archidxfoot.html b/templates/sk/archidxfoot.html index 2ecccf41..ae88619c 100644 --- a/templates/sk/archidxfoot.html +++ b/templates/sk/archidxfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sk/archidxhead.html b/templates/sk/archidxhead.html index 1d00a682..d081692d 100644 --- a/templates/sk/archidxhead.html +++ b/templates/sk/archidxhead.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sk/archlistend.html b/templates/sk/archlistend.html index 2e1191b0..492cf01a 100644 --- a/templates/sk/archlistend.html +++ b/templates/sk/archlistend.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sk/archliststart.html b/templates/sk/archliststart.html index a66a3285..bbbdf7bc 100644 --- a/templates/sk/archliststart.html +++ b/templates/sk/archliststart.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sk/archtoc.html b/templates/sk/archtoc.html index 28c356df..d3481e5a 100644 --- a/templates/sk/archtoc.html +++ b/templates/sk/archtoc.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sk/archtocentry.html b/templates/sk/archtocentry.html index 7def060f..34f99e31 100644 --- a/templates/sk/archtocentry.html +++ b/templates/sk/archtocentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sk/archtocnombox.html b/templates/sk/archtocnombox.html index c353f1f6..c6bdb9cf 100644 --- a/templates/sk/archtocnombox.html +++ b/templates/sk/archtocnombox.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sk/emptyarchive.html b/templates/sk/emptyarchive.html index 891284c3..2051453e 100644 --- a/templates/sk/emptyarchive.html +++ b/templates/sk/emptyarchive.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sk/headfoot.html b/templates/sk/headfoot.html index af842b31..fde97d70 100644 --- a/templates/sk/headfoot.html +++ b/templates/sk/headfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sk/listinfo.html b/templates/sk/listinfo.html index 809e9b8b..97ed94f2 100644 --- a/templates/sk/listinfo.html +++ b/templates/sk/listinfo.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sk/options.html b/templates/sk/options.html index ea15bbb9..e2f0343a 100644 --- a/templates/sk/options.html +++ b/templates/sk/options.html @@ -8,7 +8,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sk/private.html b/templates/sk/private.html index b85a5da1..05c04dd4 100755 --- a/templates/sk/private.html +++ b/templates/sk/private.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sk/roster.html b/templates/sk/roster.html index 48f2bde9..d4d6e209 100644 --- a/templates/sk/roster.html +++ b/templates/sk/roster.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sk/subscribe.html b/templates/sk/subscribe.html index 76292ca4..0d900eb3 100644 --- a/templates/sk/subscribe.html +++ b/templates/sk/subscribe.html @@ -5,7 +5,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sl/admindbdetails.html b/templates/sl/admindbdetails.html index 173ba830..c780ff2c 100644 --- a/templates/sl/admindbdetails.html +++ b/templates/sl/admindbdetails.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sl/admindbpreamble.html b/templates/sl/admindbpreamble.html index 32e0be1b..d6b37a18 100644 --- a/templates/sl/admindbpreamble.html +++ b/templates/sl/admindbpreamble.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sl/admindbsummary.html b/templates/sl/admindbsummary.html index 61a4f99f..8535553f 100644 --- a/templates/sl/admindbsummary.html +++ b/templates/sl/admindbsummary.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sl/admlogin.html b/templates/sl/admlogin.html index d86fc082..1e2b2115 100755 --- a/templates/sl/admlogin.html +++ b/templates/sl/admlogin.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sl/archidxentry.html b/templates/sl/archidxentry.html index 5c620b20..2efd9486 100644 --- a/templates/sl/archidxentry.html +++ b/templates/sl/archidxentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sl/archidxfoot.html b/templates/sl/archidxfoot.html index 94b215f6..73d406dd 100644 --- a/templates/sl/archidxfoot.html +++ b/templates/sl/archidxfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sl/archidxhead.html b/templates/sl/archidxhead.html index b990ef82..d21ea167 100644 --- a/templates/sl/archidxhead.html +++ b/templates/sl/archidxhead.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sl/archlistend.html b/templates/sl/archlistend.html index 2e1191b0..492cf01a 100644 --- a/templates/sl/archlistend.html +++ b/templates/sl/archlistend.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sl/archliststart.html b/templates/sl/archliststart.html index 386bd4fd..74551014 100644 --- a/templates/sl/archliststart.html +++ b/templates/sl/archliststart.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sl/archtoc.html b/templates/sl/archtoc.html index 4baa3e6f..3defa282 100644 --- a/templates/sl/archtoc.html +++ b/templates/sl/archtoc.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sl/archtocentry.html b/templates/sl/archtocentry.html index 24595b2d..1a380538 100644 --- a/templates/sl/archtocentry.html +++ b/templates/sl/archtocentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sl/emptyarchive.html b/templates/sl/emptyarchive.html index 5ffe6884..08eced18 100644 --- a/templates/sl/emptyarchive.html +++ b/templates/sl/emptyarchive.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sl/headfoot.html b/templates/sl/headfoot.html index 5424c670..6ecde199 100644 --- a/templates/sl/headfoot.html +++ b/templates/sl/headfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sl/listinfo.html b/templates/sl/listinfo.html index c5ad7ab8..e57344ad 100644 --- a/templates/sl/listinfo.html +++ b/templates/sl/listinfo.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sl/options.html b/templates/sl/options.html index 883f09e7..c6d89492 100644 --- a/templates/sl/options.html +++ b/templates/sl/options.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sl/private.html b/templates/sl/private.html index 0e513493..2569b547 100755 --- a/templates/sl/private.html +++ b/templates/sl/private.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sl/roster.html b/templates/sl/roster.html index 4ea0c7e1..8e9e7faa 100644 --- a/templates/sl/roster.html +++ b/templates/sl/roster.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sl/subscribe.html b/templates/sl/subscribe.html index 66e33d8d..b1b13f2a 100644 --- a/templates/sl/subscribe.html +++ b/templates/sl/subscribe.html @@ -5,7 +5,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sr/admindbdetails.html b/templates/sr/admindbdetails.html index b43c4bd4..4ae83f0d 100644 --- a/templates/sr/admindbdetails.html +++ b/templates/sr/admindbdetails.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sr/admindbpreamble.html b/templates/sr/admindbpreamble.html index 85abdfe4..5d2aaa8c 100644 --- a/templates/sr/admindbpreamble.html +++ b/templates/sr/admindbpreamble.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sr/admindbsummary.html b/templates/sr/admindbsummary.html index 5fbdb7b4..2d56f1a0 100644 --- a/templates/sr/admindbsummary.html +++ b/templates/sr/admindbsummary.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sr/admlogin.html b/templates/sr/admlogin.html index 9ab95fd4..8e650178 100755 --- a/templates/sr/admlogin.html +++ b/templates/sr/admlogin.html @@ -8,7 +8,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sr/archidxentry.html b/templates/sr/archidxentry.html index 5c620b20..2efd9486 100644 --- a/templates/sr/archidxentry.html +++ b/templates/sr/archidxentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sr/archidxfoot.html b/templates/sr/archidxfoot.html index c3371c88..97fb76eb 100644 --- a/templates/sr/archidxfoot.html +++ b/templates/sr/archidxfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sr/archidxhead.html b/templates/sr/archidxhead.html index 9ca3a200..dab6555e 100644 --- a/templates/sr/archidxhead.html +++ b/templates/sr/archidxhead.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sr/archlistend.html b/templates/sr/archlistend.html index 2e1191b0..492cf01a 100644 --- a/templates/sr/archlistend.html +++ b/templates/sr/archlistend.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sr/archliststart.html b/templates/sr/archliststart.html index fd90e207..2872a135 100644 --- a/templates/sr/archliststart.html +++ b/templates/sr/archliststart.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sr/archtoc.html b/templates/sr/archtoc.html index b9b76815..920a3ab0 100644 --- a/templates/sr/archtoc.html +++ b/templates/sr/archtoc.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sr/archtocentry.html b/templates/sr/archtocentry.html index 9afe085b..43cc8cec 100644 --- a/templates/sr/archtocentry.html +++ b/templates/sr/archtocentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sr/emptyarchive.html b/templates/sr/emptyarchive.html index c81bbba8..159b349f 100644 --- a/templates/sr/emptyarchive.html +++ b/templates/sr/emptyarchive.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sr/handle_opts.html b/templates/sr/handle_opts.html index 17580c53..2a246b5a 100644 --- a/templates/sr/handle_opts.html +++ b/templates/sr/handle_opts.html @@ -4,7 +4,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sr/headfoot.html b/templates/sr/headfoot.html index c9be02c2..5cf6a096 100644 --- a/templates/sr/headfoot.html +++ b/templates/sr/headfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sr/listinfo.html b/templates/sr/listinfo.html index e1548141..eb848297 100644 --- a/templates/sr/listinfo.html +++ b/templates/sr/listinfo.html @@ -11,7 +11,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sr/options.html b/templates/sr/options.html index 64d18465..d793c958 100644 --- a/templates/sr/options.html +++ b/templates/sr/options.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sr/private.html b/templates/sr/private.html index 710b3a70..9a07bad3 100755 --- a/templates/sr/private.html +++ b/templates/sr/private.html @@ -8,7 +8,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sr/roster.html b/templates/sr/roster.html index 0c028ff9..54b85f99 100644 --- a/templates/sr/roster.html +++ b/templates/sr/roster.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sr/subscribe.html b/templates/sr/subscribe.html index 4283fc01..23270e18 100644 --- a/templates/sr/subscribe.html +++ b/templates/sr/subscribe.html @@ -5,7 +5,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sv/admindbdetails.html b/templates/sv/admindbdetails.html index a4b4ba99..0c4bace8 100644 --- a/templates/sv/admindbdetails.html +++ b/templates/sv/admindbdetails.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sv/admindbpreamble.html b/templates/sv/admindbpreamble.html index 7a5dd2f5..0be4fc62 100644 --- a/templates/sv/admindbpreamble.html +++ b/templates/sv/admindbpreamble.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sv/admindbsummary.html b/templates/sv/admindbsummary.html index b3c84589..fbe92df2 100644 --- a/templates/sv/admindbsummary.html +++ b/templates/sv/admindbsummary.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sv/admlogin.html b/templates/sv/admlogin.html index 757a885a..bbcf1055 100755 --- a/templates/sv/admlogin.html +++ b/templates/sv/admlogin.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sv/archtoc.html b/templates/sv/archtoc.html index 07ed24af..3eec2eb2 100644 --- a/templates/sv/archtoc.html +++ b/templates/sv/archtoc.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sv/archtocentry.html b/templates/sv/archtocentry.html index 7dc90901..8600174f 100644 --- a/templates/sv/archtocentry.html +++ b/templates/sv/archtocentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sv/emptyarchive.html b/templates/sv/emptyarchive.html index d5ef90e6..8feea784 100644 --- a/templates/sv/emptyarchive.html +++ b/templates/sv/emptyarchive.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sv/headfoot.html b/templates/sv/headfoot.html index 5e0b5a43..087a5e23 100644 --- a/templates/sv/headfoot.html +++ b/templates/sv/headfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sv/listinfo.html b/templates/sv/listinfo.html index ed16303a..19e53568 100644 --- a/templates/sv/listinfo.html +++ b/templates/sv/listinfo.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sv/options.html b/templates/sv/options.html index af0544a5..d84d0e44 100644 --- a/templates/sv/options.html +++ b/templates/sv/options.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sv/private.html b/templates/sv/private.html index 2a95e8d3..723e8445 100755 --- a/templates/sv/private.html +++ b/templates/sv/private.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sv/roster.html b/templates/sv/roster.html index 05a3177a..96763f67 100644 --- a/templates/sv/roster.html +++ b/templates/sv/roster.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/sv/subscribe.html b/templates/sv/subscribe.html index 079b2ce8..8f27c0eb 100644 --- a/templates/sv/subscribe.html +++ b/templates/sv/subscribe.html @@ -5,7 +5,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/tr/admindbdetails.html b/templates/tr/admindbdetails.html index 79fe3e89..49ca51bf 100644 --- a/templates/tr/admindbdetails.html +++ b/templates/tr/admindbdetails.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/tr/admindbpreamble.html b/templates/tr/admindbpreamble.html index 32b539b4..94cb364b 100644 --- a/templates/tr/admindbpreamble.html +++ b/templates/tr/admindbpreamble.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/tr/admindbsummary.html b/templates/tr/admindbsummary.html index 837bc671..cfdce3a9 100644 --- a/templates/tr/admindbsummary.html +++ b/templates/tr/admindbsummary.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/tr/admlogin.html b/templates/tr/admlogin.html index 67655645..2c417a45 100755 --- a/templates/tr/admlogin.html +++ b/templates/tr/admlogin.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/tr/archidxentry.html b/templates/tr/archidxentry.html index 5c620b20..2efd9486 100644 --- a/templates/tr/archidxentry.html +++ b/templates/tr/archidxentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/tr/archidxfoot.html b/templates/tr/archidxfoot.html index f0b3caf6..30cefa50 100644 --- a/templates/tr/archidxfoot.html +++ b/templates/tr/archidxfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/tr/archidxhead.html b/templates/tr/archidxhead.html index ebcc7806..4d06e71a 100644 --- a/templates/tr/archidxhead.html +++ b/templates/tr/archidxhead.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/tr/archlistend.html b/templates/tr/archlistend.html index 2e1191b0..492cf01a 100644 --- a/templates/tr/archlistend.html +++ b/templates/tr/archlistend.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/tr/archliststart.html b/templates/tr/archliststart.html index 537dcd26..ebcb6518 100644 --- a/templates/tr/archliststart.html +++ b/templates/tr/archliststart.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/tr/archtoc.html b/templates/tr/archtoc.html index 0bd8a0db..6441f2a8 100644 --- a/templates/tr/archtoc.html +++ b/templates/tr/archtoc.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/tr/archtocentry.html b/templates/tr/archtocentry.html index f26e4cdd..0323e5ea 100644 --- a/templates/tr/archtocentry.html +++ b/templates/tr/archtocentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/tr/archtocnombox.html b/templates/tr/archtocnombox.html index ae465eb3..fd9c732d 100644 --- a/templates/tr/archtocnombox.html +++ b/templates/tr/archtocnombox.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/tr/emptyarchive.html b/templates/tr/emptyarchive.html index 51e05ff3..b423be61 100644 --- a/templates/tr/emptyarchive.html +++ b/templates/tr/emptyarchive.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/tr/headfoot.html b/templates/tr/headfoot.html index 909a153b..53f9f861 100644 --- a/templates/tr/headfoot.html +++ b/templates/tr/headfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/tr/listinfo.html b/templates/tr/listinfo.html index e1dbb51e..7889428a 100644 --- a/templates/tr/listinfo.html +++ b/templates/tr/listinfo.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/tr/options.html b/templates/tr/options.html index 63e0df68..c1d15b0d 100644 --- a/templates/tr/options.html +++ b/templates/tr/options.html @@ -8,7 +8,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/tr/private.html b/templates/tr/private.html index 5ee831f8..c3ee976b 100755 --- a/templates/tr/private.html +++ b/templates/tr/private.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/tr/roster.html b/templates/tr/roster.html index f4db36cd..fff8fd9d 100644 --- a/templates/tr/roster.html +++ b/templates/tr/roster.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/tr/subscribe.html b/templates/tr/subscribe.html index 513a2d67..b6ae86cc 100644 --- a/templates/tr/subscribe.html +++ b/templates/tr/subscribe.html @@ -5,7 +5,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/uk/admindbdetails.html b/templates/uk/admindbdetails.html index 7555fda8..420ae4ff 100644 --- a/templates/uk/admindbdetails.html +++ b/templates/uk/admindbdetails.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/uk/admindbpreamble.html b/templates/uk/admindbpreamble.html index e536ea99..56e44b84 100644 --- a/templates/uk/admindbpreamble.html +++ b/templates/uk/admindbpreamble.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/uk/admindbsummary.html b/templates/uk/admindbsummary.html index 43bc39af..d246564c 100644 --- a/templates/uk/admindbsummary.html +++ b/templates/uk/admindbsummary.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/uk/admlogin.html b/templates/uk/admlogin.html index 9e610203..d2fbc160 100755 --- a/templates/uk/admlogin.html +++ b/templates/uk/admlogin.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/uk/archidxentry.html b/templates/uk/archidxentry.html index 5c620b20..2efd9486 100644 --- a/templates/uk/archidxentry.html +++ b/templates/uk/archidxentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/uk/archidxfoot.html b/templates/uk/archidxfoot.html index 54621a9c..96255c59 100644 --- a/templates/uk/archidxfoot.html +++ b/templates/uk/archidxfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/uk/archidxhead.html b/templates/uk/archidxhead.html index 5c693bc6..81fdb002 100644 --- a/templates/uk/archidxhead.html +++ b/templates/uk/archidxhead.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/uk/archlistend.html b/templates/uk/archlistend.html index 2e1191b0..492cf01a 100644 --- a/templates/uk/archlistend.html +++ b/templates/uk/archlistend.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/uk/archliststart.html b/templates/uk/archliststart.html index 4a4a8641..2da39b76 100644 --- a/templates/uk/archliststart.html +++ b/templates/uk/archliststart.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/uk/archtoc.html b/templates/uk/archtoc.html index 921ffb5d..6ba1f0a9 100644 --- a/templates/uk/archtoc.html +++ b/templates/uk/archtoc.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/uk/archtocentry.html b/templates/uk/archtocentry.html index 29a517bd..b7bafb3f 100644 --- a/templates/uk/archtocentry.html +++ b/templates/uk/archtocentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/uk/archtocnombox.html b/templates/uk/archtocnombox.html index 1bae8bf2..3814b341 100644 --- a/templates/uk/archtocnombox.html +++ b/templates/uk/archtocnombox.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/uk/emptyarchive.html b/templates/uk/emptyarchive.html index 4f07f338..093a332e 100644 --- a/templates/uk/emptyarchive.html +++ b/templates/uk/emptyarchive.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/uk/headfoot.html b/templates/uk/headfoot.html index 2e915e90..936234c0 100644 --- a/templates/uk/headfoot.html +++ b/templates/uk/headfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/uk/listinfo.html b/templates/uk/listinfo.html index 1c7aebab..d2430886 100644 --- a/templates/uk/listinfo.html +++ b/templates/uk/listinfo.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/uk/options.html b/templates/uk/options.html index 04c6aad2..537f4f9a 100644 --- a/templates/uk/options.html +++ b/templates/uk/options.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/uk/private.html b/templates/uk/private.html index 6ebbed4e..56b6eddb 100755 --- a/templates/uk/private.html +++ b/templates/uk/private.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/uk/roster.html b/templates/uk/roster.html index 4d5bc21a..9f8ef5a2 100644 --- a/templates/uk/roster.html +++ b/templates/uk/roster.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/uk/subscribe.html b/templates/uk/subscribe.html index f60dad77..0b0dbba8 100644 --- a/templates/uk/subscribe.html +++ b/templates/uk/subscribe.html @@ -5,7 +5,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/vi/admindbdetails.html b/templates/vi/admindbdetails.html index fdc91206..5f4e5494 100644 --- a/templates/vi/admindbdetails.html +++ b/templates/vi/admindbdetails.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/vi/admindbpreamble.html b/templates/vi/admindbpreamble.html index f7924575..3bffdc92 100644 --- a/templates/vi/admindbpreamble.html +++ b/templates/vi/admindbpreamble.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/vi/admindbsummary.html b/templates/vi/admindbsummary.html index 3229469c..b5ba8535 100644 --- a/templates/vi/admindbsummary.html +++ b/templates/vi/admindbsummary.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/vi/admlogin.html b/templates/vi/admlogin.html index 44ab0f75..51b04ec5 100755 --- a/templates/vi/admlogin.html +++ b/templates/vi/admlogin.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/vi/archidxentry.html b/templates/vi/archidxentry.html index 5c620b20..2efd9486 100644 --- a/templates/vi/archidxentry.html +++ b/templates/vi/archidxentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/vi/archidxfoot.html b/templates/vi/archidxfoot.html index e213a2c9..d05f209d 100644 --- a/templates/vi/archidxfoot.html +++ b/templates/vi/archidxfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/vi/archidxhead.html b/templates/vi/archidxhead.html index b5d9e117..b600eab9 100644 --- a/templates/vi/archidxhead.html +++ b/templates/vi/archidxhead.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/vi/archlistend.html b/templates/vi/archlistend.html index 2e1191b0..492cf01a 100644 --- a/templates/vi/archlistend.html +++ b/templates/vi/archlistend.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/vi/archliststart.html b/templates/vi/archliststart.html index c1a1a5a1..ae249f8b 100644 --- a/templates/vi/archliststart.html +++ b/templates/vi/archliststart.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/vi/archtoc.html b/templates/vi/archtoc.html index 80a9a2e2..8c24a63f 100644 --- a/templates/vi/archtoc.html +++ b/templates/vi/archtoc.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/vi/archtocentry.html b/templates/vi/archtocentry.html index 9a1d5693..d5b08c2d 100644 --- a/templates/vi/archtocentry.html +++ b/templates/vi/archtocentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/vi/archtocnombox.html b/templates/vi/archtocnombox.html index b051ec92..8ca22afb 100644 --- a/templates/vi/archtocnombox.html +++ b/templates/vi/archtocnombox.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/vi/emptyarchive.html b/templates/vi/emptyarchive.html index c14dc0a1..d06a204a 100644 --- a/templates/vi/emptyarchive.html +++ b/templates/vi/emptyarchive.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/vi/headfoot.html b/templates/vi/headfoot.html index 4bb69cdc..fab487da 100644 --- a/templates/vi/headfoot.html +++ b/templates/vi/headfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/vi/listinfo.html b/templates/vi/listinfo.html index 12593b5c..ae28f602 100644 --- a/templates/vi/listinfo.html +++ b/templates/vi/listinfo.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/vi/options.html b/templates/vi/options.html index 12ac5086..4c61f32b 100644 --- a/templates/vi/options.html +++ b/templates/vi/options.html @@ -8,7 +8,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/vi/private.html b/templates/vi/private.html index 0eef5194..be6c8c34 100755 --- a/templates/vi/private.html +++ b/templates/vi/private.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/vi/roster.html b/templates/vi/roster.html index eeacc833..f417d309 100644 --- a/templates/vi/roster.html +++ b/templates/vi/roster.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/vi/subscribe.html b/templates/vi/subscribe.html index c5f8be1b..61937bc8 100644 --- a/templates/vi/subscribe.html +++ b/templates/vi/subscribe.html @@ -5,7 +5,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/zh_CN/admindbdetails.html b/templates/zh_CN/admindbdetails.html index 557eed08..45955fea 100644 --- a/templates/zh_CN/admindbdetails.html +++ b/templates/zh_CN/admindbdetails.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/zh_CN/admindbpreamble.html b/templates/zh_CN/admindbpreamble.html index 2582112a..1ad24d1c 100644 --- a/templates/zh_CN/admindbpreamble.html +++ b/templates/zh_CN/admindbpreamble.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/zh_CN/admindbsummary.html b/templates/zh_CN/admindbsummary.html index 0b34c103..8c7df239 100644 --- a/templates/zh_CN/admindbsummary.html +++ b/templates/zh_CN/admindbsummary.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/zh_CN/admlogin.html b/templates/zh_CN/admlogin.html index fa29559b..d78523a0 100755 --- a/templates/zh_CN/admlogin.html +++ b/templates/zh_CN/admlogin.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/zh_CN/archidxentry.html b/templates/zh_CN/archidxentry.html index 5c620b20..2efd9486 100644 --- a/templates/zh_CN/archidxentry.html +++ b/templates/zh_CN/archidxentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/zh_CN/archidxfoot.html b/templates/zh_CN/archidxfoot.html index fb12d1c6..da512091 100644 --- a/templates/zh_CN/archidxfoot.html +++ b/templates/zh_CN/archidxfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/zh_CN/archidxhead.html b/templates/zh_CN/archidxhead.html index 6b167d12..72e51fe8 100644 --- a/templates/zh_CN/archidxhead.html +++ b/templates/zh_CN/archidxhead.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/zh_CN/archlistend.html b/templates/zh_CN/archlistend.html index 2e1191b0..492cf01a 100644 --- a/templates/zh_CN/archlistend.html +++ b/templates/zh_CN/archlistend.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/zh_CN/archliststart.html b/templates/zh_CN/archliststart.html index 6ac487cc..8fd9fbda 100644 --- a/templates/zh_CN/archliststart.html +++ b/templates/zh_CN/archliststart.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/zh_CN/archtoc.html b/templates/zh_CN/archtoc.html index a34a6432..73d0d44e 100644 --- a/templates/zh_CN/archtoc.html +++ b/templates/zh_CN/archtoc.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/zh_CN/archtocentry.html b/templates/zh_CN/archtocentry.html index a19279c2..c5956a40 100644 --- a/templates/zh_CN/archtocentry.html +++ b/templates/zh_CN/archtocentry.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/zh_CN/archtocnombox.html b/templates/zh_CN/archtocnombox.html index e7887008..78ffe620 100644 --- a/templates/zh_CN/archtocnombox.html +++ b/templates/zh_CN/archtocnombox.html @@ -10,7 +10,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/zh_CN/emptyarchive.html b/templates/zh_CN/emptyarchive.html index 567b2be9..1f68341b 100644 --- a/templates/zh_CN/emptyarchive.html +++ b/templates/zh_CN/emptyarchive.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/zh_CN/headfoot.html b/templates/zh_CN/headfoot.html index 35ac62ff..d739e24d 100644 --- a/templates/zh_CN/headfoot.html +++ b/templates/zh_CN/headfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/zh_CN/listinfo.html b/templates/zh_CN/listinfo.html index 7373f247..a51572b2 100644 --- a/templates/zh_CN/listinfo.html +++ b/templates/zh_CN/listinfo.html @@ -8,7 +8,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/zh_CN/options.html b/templates/zh_CN/options.html index acfe918b..38bcccf8 100644 --- a/templates/zh_CN/options.html +++ b/templates/zh_CN/options.html @@ -8,7 +8,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/zh_CN/private.html b/templates/zh_CN/private.html index f0bbe385..8a8d4593 100755 --- a/templates/zh_CN/private.html +++ b/templates/zh_CN/private.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/zh_CN/roster.html b/templates/zh_CN/roster.html index 0a39876d..ddb9cc52 100644 --- a/templates/zh_CN/roster.html +++ b/templates/zh_CN/roster.html @@ -6,7 +6,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/zh_CN/subscribe.html b/templates/zh_CN/subscribe.html index 7f54b60a..a2bcf99f 100644 --- a/templates/zh_CN/subscribe.html +++ b/templates/zh_CN/subscribe.html @@ -4,7 +4,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/zh_TW/admindbpreamble.html b/templates/zh_TW/admindbpreamble.html index 49d900a7..6c426f68 100644 --- a/templates/zh_TW/admindbpreamble.html +++ b/templates/zh_TW/admindbpreamble.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/zh_TW/admlogin.html b/templates/zh_TW/admlogin.html index cbc3454f..96f2fc00 100755 --- a/templates/zh_TW/admlogin.html +++ b/templates/zh_TW/admlogin.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/zh_TW/handle_opts.html b/templates/zh_TW/handle_opts.html index 8070ae61..d7267a05 100644 --- a/templates/zh_TW/handle_opts.html +++ b/templates/zh_TW/handle_opts.html @@ -4,7 +4,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/zh_TW/headfoot.html b/templates/zh_TW/headfoot.html index 7f5be57c..6908dc72 100644 --- a/templates/zh_TW/headfoot.html +++ b/templates/zh_TW/headfoot.html @@ -3,7 +3,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/zh_TW/listinfo.html b/templates/zh_TW/listinfo.html index b4e8ccd1..83252de7 100644 --- a/templates/zh_TW/listinfo.html +++ b/templates/zh_TW/listinfo.html @@ -9,7 +9,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/zh_TW/options.html b/templates/zh_TW/options.html index 26895132..6549a12b 100644 --- a/templates/zh_TW/options.html +++ b/templates/zh_TW/options.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/zh_TW/roster.html b/templates/zh_TW/roster.html index 76414448..73244040 100644 --- a/templates/zh_TW/roster.html +++ b/templates/zh_TW/roster.html @@ -7,7 +7,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } diff --git a/templates/zh_TW/subscribe.html b/templates/zh_TW/subscribe.html index 3e2bdc48..39d3e227 100644 --- a/templates/zh_TW/subscribe.html +++ b/templates/zh_TW/subscribe.html @@ -5,7 +5,6 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; - max-width: 800px; margin: 0 auto; padding: 20px; } From 188ecbfbb14171aab157078e0c6740c0da0cbcbf Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 24 May 2025 19:16:54 -0400 Subject: [PATCH 684/748] Update string handling for Python 3 compatibility in logging and path processing --- Mailman/Logging/Syslog.py | 5 +++++ Mailman/Utils.py | 2 ++ 2 files changed, 7 insertions(+) diff --git a/Mailman/Logging/Syslog.py b/Mailman/Logging/Syslog.py index 101e2ce9..9a5d24ed 100644 --- a/Mailman/Logging/Syslog.py +++ b/Mailman/Logging/Syslog.py @@ -93,6 +93,11 @@ def mailman_log(ident, msg, *args): msg = str(msg) if args: msg = msg % args + # Remove u prefix if present (Python 2 compatibility) + if msg.startswith("u'") and msg.endswith("'"): + msg = msg[2:-1] + elif msg.startswith('u"') and msg.endswith('"'): + msg = msg[2:-1] _syslog.mailman_log(ident, msg) # For backward compatibility diff --git a/Mailman/Utils.py b/Mailman/Utils.py index 8dc34b30..8b1c2bff 100644 --- a/Mailman/Utils.py +++ b/Mailman/Utils.py @@ -292,6 +292,8 @@ def GetPathPieces(envar='PATH_INFO'): remote) # Check for listname injections that won't be websafed. pieces = [p for p in path.split('/') if p] + # Ensure all pieces are Python 3 strings + pieces = [str(p) if not isinstance(p, str) else p for p in pieces] # Get the longest listname or 20 if none or use MAX_LISTNAME_LENGTH if # provided > 0. if mm_cfg.MAX_LISTNAME_LENGTH > 0: From fda3d553ba56be600b2420473aedd8997ca4ee2d Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 24 May 2025 20:25:07 -0400 Subject: [PATCH 685/748] Reduce debug logging in admin.py to make logs cleaner and more focused --- Mailman/Cgi/admin.py | 43 +------------------------------------------ 1 file changed, 1 insertion(+), 42 deletions(-) diff --git a/Mailman/Cgi/admin.py b/Mailman/Cgi/admin.py index 21678595..3c05e089 100644 --- a/Mailman/Cgi/admin.py +++ b/Mailman/Cgi/admin.py @@ -95,7 +95,6 @@ def main(): try: # Log page load mailman_log('info', 'admin: Page load started') - mailman_log('debug', 'Entered main()') # Initialize document early doc = Document() @@ -103,7 +102,6 @@ def main(): # Parse form data first since we need it for authentication try: - mailman_log('debug', 'Parsing form data') if os.environ.get('REQUEST_METHOD') == 'POST': content_length = int(os.environ.get('CONTENT_LENGTH', 0)) if content_length > 0: @@ -114,7 +112,6 @@ def main(): else: query_string = os.environ.get('QUERY_STRING', '') cgidata = urllib.parse.parse_qs(query_string, keep_blank_values=True) - mailman_log('debug', 'cgidata after parse: %s', str(cgidata)) except Exception as e: mailman_log('error', 'admin: Invalid form data: %s\n%s', str(e), traceback.format_exc()) doc.AddItem(Header(2, _("Error"))) @@ -125,7 +122,6 @@ def main(): # Get the list name parts = Utils.GetPathPieces() - mailman_log('debug', 'Path parts: %s', str(parts)) if not parts: doc = handle_no_list() print(doc.Format()) @@ -144,7 +140,6 @@ def main(): return mailman_log('info', 'admin: Processing list "%s"', listname) - mailman_log('debug', 'List name: %s', listname) try: mlist = MailList.MailList(listname, lock=0) @@ -175,7 +170,6 @@ def main(): i18n.set_language(mlist.preferred_language) # If the user is not authenticated, we're done. try: - mailman_log('debug', 'Checking authentication') # CSRF check safe_params = ['VARHELP', 'adminpw', 'admlogin', 'letter', 'chunk', 'findmember', @@ -189,24 +183,17 @@ def main(): if cgidata.get('adminpw', [''])[0]: os.environ['HTTP_COOKIE'] = '' csrf_checked = True - mailman_log('debug', 'Authentication contexts: %s', str((mm_cfg.AuthListAdmin, mm_cfg.AuthSiteAdmin))) - mailman_log('debug', 'Password provided: %s', 'Yes' if cgidata.get('adminpw', [''])[0] else 'No') - mailman_log('debug', 'Cookie present: %s', 'Yes' if os.environ.get('HTTP_COOKIE') else 'No') auth_result = mlist.WebAuthenticate((mm_cfg.AuthListAdmin, mm_cfg.AuthSiteAdmin), cgidata.get('adminpw', [''])[0]) - mailman_log('debug', 'WebAuthenticate result: %s', str(auth_result)) if not auth_result: - mailman_log('debug', 'Authentication failed - checking auth contexts') for context in (mm_cfg.AuthListAdmin, mm_cfg.AuthSiteAdmin): mailman_log('debug', 'Checking context %s: %s', context, str(mlist.AuthContextInfo(context))) except Exception as e: mailman_log('error', 'admin: Exception during WebAuthenticate: %s\n%s', str(e), traceback.format_exc()) - mailman_log('debug', 'Exception during WebAuthenticate') raise if not auth_result: - mailman_log('debug', 'Not authenticated, calling loginpage') if 'adminpw' in cgidata: msg = Bold(FontSize('+1', _('Authorization failed.'))).Format() remote = os.environ.get('HTTP_FORWARDED_FOR', @@ -219,9 +206,8 @@ def main(): else: msg = '' Auth.loginpage(mlist, 'admin', msg=msg) - mailman_log('debug', 'Called Auth.loginpage') return - mailman_log('debug', 'Authenticated, proceeding to admin page') + # Which subcategory was requested? Default is `general' if len(parts) == 1: category = 'general' @@ -258,7 +244,6 @@ def main(): doc = Document() doc.set_language(mlist.preferred_language) form = Form(mlist=mlist, contexts=AUTH_CONTEXTS) - mailman_log('debug', 'category=%s, subcat=%s', category, subcat) # From this point on, the MailList object must be locked mlist.Lock() @@ -307,7 +292,6 @@ def sigterm_handler(signum, frame, mlist=mlist): # Show the results page show_results(mlist, doc, category, subcat, cgidata) - mailman_log('debug', 'About to print doc.Format()') print(doc.Format()) mlist.Save() finally: @@ -656,20 +640,14 @@ def show_results(mlist, doc, category, subcat, cgidata): doc.AddItem(mlist.GetMailmanFooter()) def show_variables(mlist, category, subcat, cgidata, doc): - mailman_log('debug', 'show_variables called with category=%s, subcat=%s', category, subcat) - options = mlist.GetConfigInfo(category, subcat) - mailman_log('debug', 'Got config info: %s', str(options)) - # The table containing the results table = Table(cellspacing=3, cellpadding=4, width='100%') # Get and portray the text label for the category. categories = mlist.GetConfigCategories() - mailman_log('debug', 'Got config categories: %s', str(categories)) label = _(categories[category][0]) if isinstance(label, bytes): label = label.decode(Utils.GetCharSet(mlist.preferred_language), 'replace') - mailman_log('debug', 'Category label: %s', label) table.AddRow([Center(Header(2, label))]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, @@ -680,14 +658,12 @@ def show_variables(mlist, category, subcat, cgidata, doc): description = options[0] if isinstance(description, bytes): description = description.decode(Utils.GetCharSet(mlist.preferred_language), 'replace') - mailman_log('debug', 'Description: %s', description) if isinstance(description, str): table.AddRow([description]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) options = options[1:] if not options: - mailman_log('debug', 'No options to display') return table # Add the global column headers @@ -699,7 +675,6 @@ def show_variables(mlist, category, subcat, cgidata, doc): width='85%') for item in options: - mailman_log('debug', 'Processing item: %s', str(item)) if isinstance(item, str): # The very first banner option (string in an options list) is # treated as a general description, while any others are @@ -714,17 +689,12 @@ def show_variables(mlist, category, subcat, cgidata, doc): add_options_table_item(mlist, category, subcat, table, item) table.AddRow(['
    ']) table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) - mailman_log('debug', 'Returning table with %d rows', table.GetCurrentRowIndex() + 1) return table def add_options_table_item(mlist, category, subcat, table, item, detailsp=1): - mailman_log('debug', 'Adding options table item: %s', str(item)) # Add a row to an options table with the item description and value. varname, kind, params, extra, descr, elaboration = \ get_item_characteristics(item) - mailman_log('debug', 'Item characteristics: varname=%s, kind=%s', varname, kind) - if elaboration is None: - elaboration = descr descr = get_item_gui_description(mlist, category, subcat, varname, descr, elaboration, detailsp) val = get_item_gui_value(mlist, category, kind, varname, params, extra) @@ -1546,15 +1516,6 @@ def change_options(mlist, category, subcat, cgidata, doc): # Get the configuration categories config_categories = mlist.GetConfigCategories() - # Log the configuration categories for debugging - mailman_log('debug', 'Configuration categories: %s', str(config_categories)) - mailman_log('debug', 'Category type: %s', str(type(config_categories))) - if isinstance(config_categories, dict): - mailman_log('debug', 'Category keys: %s', str(list(config_categories.keys()))) - for key, value in config_categories.items(): - mailman_log('debug', 'Category %s type: %s, value: %s', - key, str(type(value)), str(value)) - # Validate category exists if category not in config_categories: mailman_log('error', 'Invalid configuration category: %s', category) @@ -1565,8 +1526,6 @@ def change_options(mlist, category, subcat, cgidata, doc): # Get the category object and validate it category_obj = config_categories[category] - mailman_log('debug', 'Category object for %s: type=%s, value=%s', - category, str(type(category_obj)), str(category_obj)) if not hasattr(category_obj, 'items'): mailman_log('error', 'Configuration category %s is invalid: %s', From 323159d6415bee92ce4b4dd2683dcd6091d0ad94 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 24 May 2025 20:26:42 -0400 Subject: [PATCH 686/748] Fix: Add back GetConfigInfo call in show_variables function --- Mailman/Cgi/admin.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Mailman/Cgi/admin.py b/Mailman/Cgi/admin.py index 3c05e089..2d2a5c4b 100644 --- a/Mailman/Cgi/admin.py +++ b/Mailman/Cgi/admin.py @@ -640,6 +640,9 @@ def show_results(mlist, doc, category, subcat, cgidata): doc.AddItem(mlist.GetMailmanFooter()) def show_variables(mlist, category, subcat, cgidata, doc): + # Get the configuration info + options = mlist.GetConfigInfo(category, subcat) + # The table containing the results table = Table(cellspacing=3, cellpadding=4, width='100%') From 2914bee28191a05c813533fe35b642a44f7c9b74 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 24 May 2025 20:30:34 -0400 Subject: [PATCH 687/748] Fix: Improve error handling for non-existent lists in listinfo.py --- Mailman/Cgi/listinfo.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Mailman/Cgi/listinfo.py b/Mailman/Cgi/listinfo.py index 97198ef0..12899b1b 100644 --- a/Mailman/Cgi/listinfo.py +++ b/Mailman/Cgi/listinfo.py @@ -27,6 +27,7 @@ import time import sys import ipaddress +from io import FileNotFoundError from Mailman import mm_cfg from Mailman import Utils @@ -83,7 +84,7 @@ def main(): try: mlist = MailList.MailList(listname, lock=0) - except Errors.MMListError as e: + except (Errors.MMListError, FileNotFoundError) as e: # Avoid cross-site scripting attacks and information disclosure safelistname = Utils.websafe(listname) # Send this with a 404 status. From f63a5a332d1a92381b4c3002e8bafd9c23cd5b72 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 24 May 2025 20:32:39 -0400 Subject: [PATCH 688/748] Fix: Restore original HTML formatting in listinfo.py while keeping improved error handling --- Mailman/Cgi/listinfo.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/Mailman/Cgi/listinfo.py b/Mailman/Cgi/listinfo.py index 12899b1b..2c16acfe 100644 --- a/Mailman/Cgi/listinfo.py +++ b/Mailman/Cgi/listinfo.py @@ -139,16 +139,10 @@ def listinfo_overview(msg=''): legend = (hostname + "'s Mailing Lists") doc.SetTitle(legend) - table = Table( - role="table", - aria_label=_("Mailing Lists Overview"), - border=0, - width="100%" - ) + table = Table(border=0, width="100%") table.AddRow([Center(Header(2, legend))]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, - style=f'background-color: {mm_cfg.WEB_HEADER_COLOR}', - role="cell") + bgcolor=mm_cfg.WEB_HEADER_COLOR) # Skip any mailing lists that isn't advertised. advertised = [] @@ -205,7 +199,7 @@ def listinfo_overview(msg=''): '.

    ')) table.AddRow([Container(*welcome)]) - table.AddCellInfo(max(table.GetCurrentRowIndex(), 0), 0, colspan=2, role="cell") + table.AddCellInfo(max(table.GetCurrentRowIndex(), 0), 0, colspan=2) if advertised: table.AddRow([' ', ' ']) @@ -219,8 +213,7 @@ def listinfo_overview(msg=''): description or Italic(_('[no description available]'))]) if highlight and mm_cfg.WEB_HIGHLIGHT_COLOR: table.AddRowInfo(table.GetCurrentRowIndex(), - style=f'background-color: {mm_cfg.WEB_HIGHLIGHT_COLOR}', - role="row") + bgcolor=mm_cfg.WEB_HIGHLIGHT_COLOR) highlight = not highlight doc.AddItem(table) From ebcc9ad68ed754f6373bfd5f445a8f0eca6aa227 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 24 May 2025 20:36:50 -0400 Subject: [PATCH 689/748] Fix: Properly handle binary strings in membership adapter for Python 3 compatibility --- Mailman/OldStyleMemberships.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Mailman/OldStyleMemberships.py b/Mailman/OldStyleMemberships.py index 9d86a8b8..e176b87d 100644 --- a/Mailman/OldStyleMemberships.py +++ b/Mailman/OldStyleMemberships.py @@ -177,7 +177,14 @@ def getMemberCPAddress(self, member): cpaddr, where = self.__get_cp_member(member) if cpaddr is None: raise Errors.NotAMemberError(member) - return cpaddr + if isinstance(cpaddr, bytes): + try: + # Try Latin-1 first since that's what we're seeing in the data + cpaddr = cpaddr.decode('latin-1', 'replace') + except UnicodeDecodeError: + # Fall back to UTF-8 if Latin-1 fails + cpaddr = cpaddr.decode('utf-8', 'replace') + return str(cpaddr) def getMemberCPAddresses(self, members): return [self.__get_cp_member(member)[0] for member in members] @@ -436,6 +443,7 @@ def setMemberName(self, member, realname): except UnicodeDecodeError: # Fall back to UTF-8 if Latin-1 fails realname = realname.decode('utf-8', 'replace') + # Store as a Python 3 string self.__mlist.usernames[member.lower()] = str(realname) def setMemberTopics(self, member, topics): From a207dbb466cd918b210d4a0d76822cc5115dc0a0 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 24 May 2025 20:40:10 -0400 Subject: [PATCH 690/748] Fix: Remove b'...' prefix from member names in admin interface --- Mailman/Cgi/admin.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Mailman/Cgi/admin.py b/Mailman/Cgi/admin.py index 2d2a5c4b..df8a5dfb 100644 --- a/Mailman/Cgi/admin.py +++ b/Mailman/Cgi/admin.py @@ -1177,7 +1177,15 @@ def membership_options(mlist, subcat, cgidata, doc, form): mlist.getMemberCPAddress(addr)) fullname = mlist.getMemberName(addr) if isinstance(fullname, bytes): - fullname = fullname.decode('latin1', 'replace') + try: + # Try Latin-1 first since that's what we're seeing in the data + fullname = fullname.decode('latin-1', 'replace') + except UnicodeDecodeError: + # Fall back to UTF-8 if Latin-1 fails + fullname = fullname.decode('utf-8', 'replace') + # Remove any b'...' prefix if it exists + if fullname.startswith("b'") and fullname.endswith("'"): + fullname = fullname[2:-1] fullname = Utils.uncanonstr(fullname, mlist.preferred_language) name = TextBox('%(qaddr)s_realname' % {'qaddr': qaddr}, fullname, size=longest).Format() cells = [Center(CheckBox('%(qaddr)s_unsub' % {'qaddr': qaddr}, 'off', 0).Format() From e74f54ec5e07e9e5381b1e5993556e320d78f0d5 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 24 May 2025 20:46:39 -0400 Subject: [PATCH 691/748] Fix: Properly handle binary strings in membership adapter for Python 3 compatibility --- Mailman/OldStyleMemberships.py | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/Mailman/OldStyleMemberships.py b/Mailman/OldStyleMemberships.py index e176b87d..32a09c37 100644 --- a/Mailman/OldStyleMemberships.py +++ b/Mailman/OldStyleMemberships.py @@ -174,6 +174,17 @@ def getMemberKey(self, member): return member.lower() def getMemberCPAddress(self, member): + """Get the canonical address of a member. + + Args: + member: The member's email address + + Returns: + str: The member's canonical address + + Raises: + NotAMemberError: If the member is not found + """ cpaddr, where = self.__get_cp_member(member) if cpaddr is None: raise Errors.NotAMemberError(member) @@ -221,10 +232,18 @@ def getMemberOption(self, member, flag): return not not (option & flag) def getMemberName(self, member): + """Get the real name of a member. + + Args: + member: The member's email address + + Returns: + str: The member's real name, or None if not set + """ self.__assertIsMember(member) name = self.__mlist.usernames.get(member.lower()) if name is None: - return '' + return None if isinstance(name, bytes): try: # Try Latin-1 first since that's what we're seeing in the data @@ -432,7 +451,12 @@ def setMemberOption(self, member, flag, value): del self.__mlist.user_options[memberkey] def setMemberName(self, member, realname): - assert self.__mlist.Locked() + """Set the real name of a member. + + Args: + member: The member's email address + realname: The member's real name + """ self.__assertIsMember(member) if realname is None: realname = '' @@ -443,7 +467,6 @@ def setMemberName(self, member, realname): except UnicodeDecodeError: # Fall back to UTF-8 if Latin-1 fails realname = realname.decode('utf-8', 'replace') - # Store as a Python 3 string self.__mlist.usernames[member.lower()] = str(realname) def setMemberTopics(self, member, topics): From 5978168caef097d9640f6ec2587b67ae26b86e2c Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 24 May 2025 20:52:27 -0400 Subject: [PATCH 692/748] Fix: Ensure proper string handling when loading pickle files from Python 2 --- Mailman/MailList.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Mailman/MailList.py b/Mailman/MailList.py index e3b46fbb..5f1033c8 100644 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -660,6 +660,12 @@ def __save(self, data_dict): # we never rotate unless the we've successfully written the temp file. # We use pickle now because marshal is not guaranteed to be compatible # between Python versions. + # + # We use protocol 4 for Python 2/3 compatibility because: + # 1. It supports large objects (>4GB) + # 2. It's compatible between Python 2.7 and Python 3.x + # 3. It handles Unicode strings properly + # 4. It's the highest protocol version supported by both Python 2.7 and 3.x fname = os.path.join(self.fullpath(), 'config.pck') fname_tmp = fname + '.tmp.%s.%d' % (socket.gethostname(), os.getpid()) fname_last = fname + '.last' @@ -747,7 +753,7 @@ def __load(self, dbfile): elif dbfile.endswith('.pck') or dbfile.endswith('.pck.last'): def loadfunc(fp): try: - # Try UTF-8 first for newer files + # Try UTF-8 first for newer files (protocol 4) return pickle.load(fp, fix_imports=True, encoding='utf-8') except (UnicodeDecodeError, pickle.UnpicklingError): # Fall back to latin1 for older files @@ -829,6 +835,8 @@ def CheckVersion(self): """Check the version of the list's config database. If the database version is not current, update the database format. + This includes ensuring that pickle files are saved with protocol 4 + for Python 2/3 compatibility. """ # Increment this variable when the database format changes. This allows # for a bit more graceful recovery when upgrading. BAW: This algorithm @@ -837,6 +845,9 @@ def CheckVersion(self): # MM3.0. data_version = getattr(self, 'data_version', 0) if data_version >= mm_cfg.DATA_FILE_VERSION: + # Even if the data version is current, ensure we're using protocol 4 + # for pickle files by saving the current state + self.Save() return # Pre-2.1a3 versions did not have a data_version From 2beb35433389e2e401273875c600302f3fb69d3a Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 24 May 2025 20:54:20 -0400 Subject: [PATCH 693/748] Fix: Add digestable property to OldStyleMemberships class --- Mailman/OldStyleMemberships.py | 38 ++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/Mailman/OldStyleMemberships.py b/Mailman/OldStyleMemberships.py index 32a09c37..26cb5e0b 100644 --- a/Mailman/OldStyleMemberships.py +++ b/Mailman/OldStyleMemberships.py @@ -232,26 +232,26 @@ def getMemberOption(self, member, flag): return not not (option & flag) def getMemberName(self, member): - """Get the real name of a member. - + """Get the member's real name. + Args: member: The member's email address - + Returns: - str: The member's real name, or None if not set + The member's real name, or None if not found """ - self.__assertIsMember(member) - name = self.__mlist.usernames.get(member.lower()) - if name is None: + try: + fullname = self.__mlist.usernames[member] + if isinstance(fullname, bytes): + try: + # Try Latin-1 first since that's what we're seeing in the data + fullname = fullname.decode('latin-1', 'replace') + except UnicodeDecodeError: + # Fall back to UTF-8 if Latin-1 fails + fullname = fullname.decode('utf-8', 'replace') + return fullname + except KeyError: return None - if isinstance(name, bytes): - try: - # Try Latin-1 first since that's what we're seeing in the data - name = name.decode('latin-1', 'replace') - except UnicodeDecodeError: - # Fall back to UTF-8 if Latin-1 fails - name = name.decode('utf-8', 'replace') - return str(name) def getMemberTopics(self, member): self.__assertIsMember(member) @@ -588,3 +588,11 @@ def ProcessConfirmation(self, cookie, msg): Pending.remove(cookie) return action, data + + @property + def digestable(self): + """Return whether the list supports digest mode. + + This is the inverse of nondigestable. + """ + return not self.__mlist.nondigestable From 8fb7a3c9754e042c6c0497e976c2222f6be53c6f Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 24 May 2025 20:55:32 -0400 Subject: [PATCH 694/748] Add missing digest-related properties to OldStyleMemberships class --- Mailman/OldStyleMemberships.py | 40 ++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/Mailman/OldStyleMemberships.py b/Mailman/OldStyleMemberships.py index 26cb5e0b..19e7fe79 100644 --- a/Mailman/OldStyleMemberships.py +++ b/Mailman/OldStyleMemberships.py @@ -596,3 +596,43 @@ def digestable(self): This is the inverse of nondigestable. """ return not self.__mlist.nondigestable + + @property + def digest_is_default(self): + """Return whether digest delivery is the default for new members.""" + return self.__mlist.digest_is_default + + @property + def mime_is_default_digest(self): + """Return whether MIME format is the default for digests.""" + return self.__mlist.mime_is_default_digest + + @property + def digest_size_threshhold(self): + """Return the size threshold for digests in KB.""" + return self.__mlist.digest_size_threshhold + + @property + def digest_send_periodic(self): + """Return whether digests are sent periodically.""" + return self.__mlist.digest_send_periodic + + @property + def digest_volume(self): + """Return the current digest volume number.""" + return self.__mlist.volume + + @property + def digest_issue(self): + """Return the current digest issue number.""" + return self.__mlist.next_digest_number + + @property + def digest_last_sent_at(self): + """Return the timestamp of when the last digest was sent.""" + return self.__mlist.digest_last_sent_at + + @property + def digest_next_due_at(self): + """Return the timestamp of when the next digest is due.""" + return self.__mlist.digest_next_due_at From 7bc6631d0e50461a0fd639a49e7f06510728105d Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 24 May 2025 20:58:26 -0400 Subject: [PATCH 695/748] Add setters for digest-related properties in OldStyleMemberships class --- Mailman/OldStyleMemberships.py | 40 ++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/Mailman/OldStyleMemberships.py b/Mailman/OldStyleMemberships.py index 19e7fe79..137e52cd 100644 --- a/Mailman/OldStyleMemberships.py +++ b/Mailman/OldStyleMemberships.py @@ -602,37 +602,77 @@ def digest_is_default(self): """Return whether digest delivery is the default for new members.""" return self.__mlist.digest_is_default + @digest_is_default.setter + def digest_is_default(self, value): + """Set whether digest delivery is the default for new members.""" + self.__mlist.digest_is_default = value + @property def mime_is_default_digest(self): """Return whether MIME format is the default for digests.""" return self.__mlist.mime_is_default_digest + @mime_is_default_digest.setter + def mime_is_default_digest(self, value): + """Set whether MIME format is the default for digests.""" + self.__mlist.mime_is_default_digest = value + @property def digest_size_threshhold(self): """Return the size threshold for digests in KB.""" return self.__mlist.digest_size_threshhold + @digest_size_threshhold.setter + def digest_size_threshhold(self, value): + """Set the size threshold for digests in KB.""" + self.__mlist.digest_size_threshhold = value + @property def digest_send_periodic(self): """Return whether digests are sent periodically.""" return self.__mlist.digest_send_periodic + @digest_send_periodic.setter + def digest_send_periodic(self, value): + """Set whether digests are sent periodically.""" + self.__mlist.digest_send_periodic = value + @property def digest_volume(self): """Return the current digest volume number.""" return self.__mlist.volume + @digest_volume.setter + def digest_volume(self, value): + """Set the current digest volume number.""" + self.__mlist.volume = value + @property def digest_issue(self): """Return the current digest issue number.""" return self.__mlist.next_digest_number + @digest_issue.setter + def digest_issue(self, value): + """Set the current digest issue number.""" + self.__mlist.next_digest_number = value + @property def digest_last_sent_at(self): """Return the timestamp of when the last digest was sent.""" return self.__mlist.digest_last_sent_at + @digest_last_sent_at.setter + def digest_last_sent_at(self, value): + """Set the timestamp of when the last digest was sent.""" + self.__mlist.digest_last_sent_at = value + @property def digest_next_due_at(self): """Return the timestamp of when the next digest is due.""" return self.__mlist.digest_next_due_at + + @digest_next_due_at.setter + def digest_next_due_at(self, value): + """Set the timestamp of when the next digest is due.""" + self.__mlist.digest_next_due_at = value From 5455bbf55aa7d556e06b9f5e4503c9df876b50c6 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 24 May 2025 21:00:09 -0400 Subject: [PATCH 696/748] Fix argument parsing and error handling in bin/update script --- bin/update | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/bin/update b/bin/update index 5aa97665..e4270cd9 100755 --- a/bin/update +++ b/bin/update @@ -79,6 +79,8 @@ def parse_args(): help='Force running the upgrade procedures') parser.add_argument('-l', '--lowercase', action='store_true', help='Convert all member email addresses to lowercase') + parser.add_argument('-v', '--verbose', action='store_true', + help='Enable verbose output') return parser.parse_args() @@ -983,8 +985,10 @@ def upgrade(mlist): def main(): try: args = parse_args() - except SystemExit: - usage(1) + except SystemExit as e: + if e.code == 2: # Invalid arguments + usage(1) + raise # Calculate the versions lastversion, thisversion = calcversions() @@ -1114,18 +1118,10 @@ downgrade.""")) print(C_('Upgrade complete.')) -def usage(code, msg=''): - if code: - fd = sys.stderr - else: - fd = sys.stdout - # Ensure PROGRAM is a string, not bytes - if isinstance(PROGRAM, bytes): - PROGRAM = PROGRAM.decode('utf-8', 'replace') - print(C_(__doc__) % globals(), file=fd) - if msg: - print(msg, file=sys.stderr) - sys.exit(code) +def usage(exitcode=0): + """Print usage information and exit with the given exit code.""" + print(__doc__ % {'PROGRAM': PROGRAM}) + sys.exit(exitcode) if __name__ == '__main__': From b1b987dab9ae6102c34ce2b7e969c8c174522d63 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 24 May 2025 21:02:51 -0400 Subject: [PATCH 697/748] Fix lock handling in bin/update script to properly acquire and release locks --- bin/update | 41 ++++++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/bin/update b/bin/update index e4270cd9..1a493819 100755 --- a/bin/update +++ b/bin/update @@ -1063,6 +1063,7 @@ downgrade.""")) print("List names type: %s" % type(list_names)) for listname in list_names: + mlist = None try: print("\nProcessing list: %s (type: %s)" % (listname, type(listname))) # Ensure listname is a string, not bytes @@ -1073,25 +1074,21 @@ downgrade.""")) print("Listname after conversion: %s (type: %s)" % (listname, type(listname))) print("Creating MailList object...") - mlist = MailList.MailList(listname, lock=0) + # Create the MailList object with lock=True to handle locking properly + mlist = MailList.MailList(listname, lock=True) print("MailList object created successfully") - try: - print("Attempting to acquire lock...") - mlist.Lock(0.5) - print("Lock acquired, starting upgrade...") - # Do the upgrade - upgrade(mlist) - print("Upgrade completed, saving...") - mlist.Save() - print("Save completed") - except TimeOutError: - print(C_('WARNING: could not acquire lock for list: %(listname)s') % { - 'listname': listname - }, file=sys.stderr) - finally: - print("Unlocking list...") - mlist.Unlock() + print("Starting upgrade...") + # Do the upgrade + upgrade(mlist) + print("Upgrade completed, saving...") + mlist.Save() + print("Save completed") + + except TimeOutError: + print(C_('WARNING: could not acquire lock for list: %(listname)s') % { + 'listname': listname + }, file=sys.stderr) except Exception as e: print("\nDetailed error information:") print("List name: %s" % listname) @@ -1105,6 +1102,16 @@ downgrade.""")) 'listname': listname, 'error': str(e) }, file=sys.stderr) + finally: + if mlist is not None: + try: + print("Unlocking list...") + mlist.Unlock() + except Exception as e: + print(C_('WARNING: Error unlocking list %(listname)s: %(error)s') % { + 'listname': listname, + 'error': str(e) + }, file=sys.stderr) # Save the new version try: From 359f85863d68edd24dec8a3c9244f107f36e7034 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 24 May 2025 21:11:46 -0400 Subject: [PATCH 698/748] Fix: Improve lock handling safety in bin/update to prevent data loss --- bin/update | 50 ++++++++++++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/bin/update b/bin/update index 1a493819..c22b1c36 100755 --- a/bin/update +++ b/bin/update @@ -1074,21 +1074,37 @@ downgrade.""")) print("Listname after conversion: %s (type: %s)" % (listname, type(listname))) print("Creating MailList object...") - # Create the MailList object with lock=True to handle locking properly - mlist = MailList.MailList(listname, lock=True) + # Create the MailList object without lock first + mlist = MailList.MailList(listname, lock=0) print("MailList object created successfully") - print("Starting upgrade...") - # Do the upgrade - upgrade(mlist) - print("Upgrade completed, saving...") - mlist.Save() - print("Save completed") + print("Attempting to acquire lock...") + try: + mlist.Lock(0.5) + print("Lock acquired, starting upgrade...") + + # Do the upgrade + upgrade(mlist) + print("Upgrade completed, saving...") + mlist.Save() + print("Save completed") + + except TimeOutError: + print(C_('WARNING: could not acquire lock for list: %(listname)s') % { + 'listname': listname + }, file=sys.stderr) + continue + finally: + if mlist is not None: + try: + print("Unlocking list...") + mlist.Unlock() + except Exception as e: + print(C_('WARNING: Error unlocking list %(listname)s: %(error)s') % { + 'listname': listname, + 'error': str(e) + }, file=sys.stderr) - except TimeOutError: - print(C_('WARNING: could not acquire lock for list: %(listname)s') % { - 'listname': listname - }, file=sys.stderr) except Exception as e: print("\nDetailed error information:") print("List name: %s" % listname) @@ -1102,16 +1118,6 @@ downgrade.""")) 'listname': listname, 'error': str(e) }, file=sys.stderr) - finally: - if mlist is not None: - try: - print("Unlocking list...") - mlist.Unlock() - except Exception as e: - print(C_('WARNING: Error unlocking list %(listname)s: %(error)s') % { - 'listname': listname, - 'error': str(e) - }, file=sys.stderr) # Save the new version try: From 3a567944923dfdccb4578d14200a13ad0596b01a Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 24 May 2025 21:13:38 -0400 Subject: [PATCH 699/748] Fix: Handle stale locks in bin/update by adding force unlock capability --- bin/update | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/bin/update b/bin/update index c22b1c36..9ac3a834 100755 --- a/bin/update +++ b/bin/update @@ -57,7 +57,7 @@ from Mailman import mm_cfg from Mailman import Utils from Mailman import MailList from Mailman import Pending -from Mailman.LockFile import TimeOutError +from Mailman.LockFile import TimeOutError, AlreadyLockedError from Mailman.i18n import C_ from Mailman.Queue.Switchboard import Switchboard from Mailman.OldStyleMemberships import OldStyleMemberships @@ -1080,7 +1080,23 @@ downgrade.""")) print("Attempting to acquire lock...") try: - mlist.Lock(0.5) + # First try to acquire the lock normally + try: + mlist.Lock(0.5) + except AlreadyLockedError: + # If we get AlreadyLockedError, try to force unlock if the lock is stale + print("Lock appears to be set, checking if it's stale...") + try: + # Try to force unlock if the lock is stale + mlist.__lock.force_unlock() + print("Stale lock removed, retrying lock acquisition...") + mlist.Lock(0.5) + except Exception as e: + print(C_('WARNING: Could not remove stale lock: %(error)s') % { + 'error': str(e) + }, file=sys.stderr) + continue + print("Lock acquired, starting upgrade...") # Do the upgrade From 5d26c999bc4da50b61eefd4f3eb0279504030ac9 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 24 May 2025 21:15:30 -0400 Subject: [PATCH 700/748] Add pickle protocol version reporting to bin/update --- bin/update | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/bin/update b/bin/update index 9ac3a834..623c381f 100755 --- a/bin/update +++ b/bin/update @@ -875,6 +875,22 @@ def init_digest_vars(mlist): def upgrade(mlist): """Upgrade the list to the current version.""" try: + # Print pickle protocol version + try: + config_path = os.path.join(mlist._full_path, 'config.pck') + if os.path.exists(config_path): + with open(config_path, 'rb') as fp: + # Read the first byte which contains the protocol version + protocol = ord(fp.read(1)) + print(C_('List %(listname)s config.pck uses pickle protocol %(protocol)d') % { + 'listname': mlist.internal_name(), + 'protocol': protocol + }) + except Exception as e: + print(C_('Warning: Could not determine pickle protocol version: %(error)s') % { + 'error': str(e) + }) + # Initialize any missing digest variables init_digest_vars(mlist) From 0e04fc4f7c2f42943941c46c3e7fb3d19d8b06f1 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 24 May 2025 21:18:20 -0400 Subject: [PATCH 701/748] Add pickle protocol version reporting for both loading and saving files --- bin/update | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/bin/update b/bin/update index 623c381f..cf654ca6 100755 --- a/bin/update +++ b/bin/update @@ -875,7 +875,7 @@ def init_digest_vars(mlist): def upgrade(mlist): """Upgrade the list to the current version.""" try: - # Print pickle protocol version + # Print pickle protocol version when loading try: config_path = os.path.join(mlist._full_path, 'config.pck') if os.path.exists(config_path): @@ -981,6 +981,9 @@ def upgrade(mlist): # Save the list configuration try: + print(C_('Saving list %(listname)s with pickle protocol 4') % { + 'listname': mlist.internal_name() + }) mlist.Save() except (IOError, OSError) as e: print(C_('Error saving list configuration: %(error)s') % {'error': str(e)}) @@ -1104,9 +1107,13 @@ downgrade.""")) print("Lock appears to be set, checking if it's stale...") try: # Try to force unlock if the lock is stale - mlist.__lock.force_unlock() - print("Stale lock removed, retrying lock acquisition...") - mlist.Lock(0.5) + if hasattr(mlist, '__lock') and hasattr(mlist.__lock, 'force_unlock'): + mlist.__lock.force_unlock() + print("Stale lock removed, retrying lock acquisition...") + mlist.Lock(0.5) + else: + print("WARNING: Lock object does not have force_unlock capability") + continue except Exception as e: print(C_('WARNING: Could not remove stale lock: %(error)s') % { 'error': str(e) From e8df15c44dd3df6c42d33cbd7983a85acbc3069e Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 24 May 2025 21:20:10 -0400 Subject: [PATCH 702/748] Add pickle protocol version reporting when loading files --- Mailman/MailList.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Mailman/MailList.py b/Mailman/MailList.py index 5f1033c8..e624b5cb 100644 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -753,6 +753,15 @@ def __load(self, dbfile): elif dbfile.endswith('.pck') or dbfile.endswith('.pck.last'): def loadfunc(fp): try: + # Read the first byte to determine protocol version + protocol = ord(fp.read(1)) + print(C_('List %(listname)s %(dbfile)s uses pickle protocol %(protocol)d') % { + 'listname': self.internal_name(), + 'dbfile': os.path.basename(dbfile), + 'protocol': protocol + }) + # Reset file pointer to beginning + fp.seek(0) # Try UTF-8 first for newer files (protocol 4) return pickle.load(fp, fix_imports=True, encoding='utf-8') except (UnicodeDecodeError, pickle.UnpicklingError): From ffc2e74f60618f1d6892799e93c83cf2d1e42ced Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 24 May 2025 21:23:18 -0400 Subject: [PATCH 703/748] Improve pickle loading robustness and error handling in MailList.py --- Mailman/MailList.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/Mailman/MailList.py b/Mailman/MailList.py index e624b5cb..65914204 100644 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -762,12 +762,22 @@ def loadfunc(fp): }) # Reset file pointer to beginning fp.seek(0) - # Try UTF-8 first for newer files (protocol 4) - return pickle.load(fp, fix_imports=True, encoding='utf-8') - except (UnicodeDecodeError, pickle.UnpicklingError): - # Fall back to latin1 for older files - fp.seek(0) - return pickle.load(fp, fix_imports=True, encoding='latin1') + # Try loading with different encodings and protocols + try: + # First try with UTF-8 and protocol 4 + return pickle.load(fp, fix_imports=True, encoding='utf-8') + except (UnicodeDecodeError, pickle.UnpicklingError): + # If that fails, try with latin1 + fp.seek(0) + try: + return pickle.load(fp, fix_imports=True, encoding='latin1') + except (UnicodeDecodeError, pickle.UnpicklingError): + # If that fails, try without specifying encoding + fp.seek(0) + return pickle.load(fp, fix_imports=True) + except Exception as e: + syslog('error', 'Failed to load pickle file %s: %s', dbfile, str(e)) + raise else: raise ValueError('Bad database file name') try: @@ -803,6 +813,7 @@ def loadfunc(fp): return dict, None except Exception as e: fp.close() + syslog('error', 'Failed to load database file %s: %s', dbfile, str(e)) return None, e def Load(self, check_version=True): From 649e7cd612f9cd816ed5f095487499198f402ea7 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 24 May 2025 21:26:09 -0400 Subject: [PATCH 704/748] Improve pickle loading for protocol 4 files and add detailed error logging --- Mailman/MailList.py | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/Mailman/MailList.py b/Mailman/MailList.py index 65914204..377a68c3 100644 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -762,19 +762,30 @@ def loadfunc(fp): }) # Reset file pointer to beginning fp.seek(0) - # Try loading with different encodings and protocols - try: - # First try with UTF-8 and protocol 4 - return pickle.load(fp, fix_imports=True, encoding='utf-8') - except (UnicodeDecodeError, pickle.UnpicklingError): - # If that fails, try with latin1 - fp.seek(0) + + # For protocol 4 files, try loading with different encodings + if protocol == 4: try: - return pickle.load(fp, fix_imports=True, encoding='latin1') - except (UnicodeDecodeError, pickle.UnpicklingError): - # If that fails, try without specifying encoding + # First try with UTF-8 + return pickle.load(fp, fix_imports=True, encoding='utf-8') + except (UnicodeDecodeError, pickle.UnpicklingError) as e: + syslog('error', 'Failed to load with UTF-8: %s', str(e)) fp.seek(0) + try: + # Then try with latin1 + return pickle.load(fp, fix_imports=True, encoding='latin1') + except (UnicodeDecodeError, pickle.UnpicklingError) as e: + syslog('error', 'Failed to load with latin1: %s', str(e)) + fp.seek(0) + # Finally try without encoding + return pickle.load(fp, fix_imports=True) + else: + # For other protocols, try without encoding first + try: return pickle.load(fp, fix_imports=True) + except (UnicodeDecodeError, pickle.UnpicklingError): + fp.seek(0) + return pickle.load(fp, fix_imports=True, encoding='latin1') except Exception as e: syslog('error', 'Failed to load pickle file %s: %s', dbfile, str(e)) raise From 6294a39fac22a6ae685ea5db5ea419aaebcb13e0 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 24 May 2025 21:27:43 -0400 Subject: [PATCH 705/748] Improve pickle protocol 2 file handling and add detailed error reporting --- Mailman/MailList.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/Mailman/MailList.py b/Mailman/MailList.py index 377a68c3..93d540f8 100644 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -763,8 +763,24 @@ def loadfunc(fp): # Reset file pointer to beginning fp.seek(0) - # For protocol 4 files, try loading with different encodings - if protocol == 4: + # For protocol 2 files (Python 2.x), try loading with different encodings + if protocol == 2: + try: + # First try with latin1 (most common for Python 2.x) + return pickle.load(fp, fix_imports=True, encoding='latin1') + except (UnicodeDecodeError, pickle.UnpicklingError) as e: + syslog('error', 'Failed to load with latin1: %s', str(e)) + fp.seek(0) + try: + # Then try with UTF-8 + return pickle.load(fp, fix_imports=True, encoding='utf-8') + except (UnicodeDecodeError, pickle.UnpicklingError) as e: + syslog('error', 'Failed to load with UTF-8: %s', str(e)) + fp.seek(0) + # Finally try without encoding + return pickle.load(fp, fix_imports=True) + # For protocol 4 files (Python 3.x), try loading with different encodings + elif protocol == 4: try: # First try with UTF-8 return pickle.load(fp, fix_imports=True, encoding='utf-8') From c47d3171f3b2d505b851345823c0abaaafa8f828 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 24 May 2025 21:34:57 -0400 Subject: [PATCH 706/748] Update MailList.py to use new pickle utility functions --- Mailman/MailList.py | 120 +++++++++----------------------------------- 1 file changed, 25 insertions(+), 95 deletions(-) diff --git a/Mailman/MailList.py b/Mailman/MailList.py index 93d540f8..d187b4a0 100644 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -654,53 +654,22 @@ def Create(self, name, admin, crypted_password, # # Database and filesystem I/O # - def __save(self, data_dict): - # Save the file as a binary pickle, and rotate the old version to a - # backup file. We must guarantee that config.pck is always valid so - # we never rotate unless the we've successfully written the temp file. - # We use pickle now because marshal is not guaranteed to be compatible - # between Python versions. + def __save(self, dbfile, dict): + # Save the dictionary to the specified database file. We always save + # using pickle, even if the file was originally a marshal file. This + # is because pickle is guaranteed to be compatible across Python + # versions, while marshal is not. # - # We use protocol 4 for Python 2/3 compatibility because: - # 1. It supports large objects (>4GB) - # 2. It's compatible between Python 2.7 and Python 3.x - # 3. It handles Unicode strings properly - # 4. It's the highest protocol version supported by both Python 2.7 and 3.x - fname = os.path.join(self.fullpath(), 'config.pck') - fname_tmp = fname + '.tmp.%s.%d' % (socket.gethostname(), os.getpid()) - fname_last = fname + '.last' - fp = None + # On success return None. On error, return the error object. try: - fp = open(fname_tmp, 'wb') - # Use protocol 4 for Python 2/3 compatibility, with fix_imports for backward compatibility - pickle.dump(data_dict, fp, protocol=4, fix_imports=True) - fp.flush() - if mm_cfg.SYNC_AFTER_WRITE: - os.fsync(fp.fileno()) - fp.close() - except IOError as e: - syslog('error', - 'Failed config.pck write, retaining old state.\n%s', e) - if fp is not None: - os.unlink(fname_tmp) - raise - # Now do config.pck.tmp.xxx -> config.pck -> config.pck.last rotation - # as safely as possible. - try: - # Remove existing backup file if it exists - try: - os.unlink(fname_last) - except OSError as e: - if e.errno != errno.ENOENT: - raise - # Create new backup file - os.link(fname, fname_last) - except OSError as e: - if e.errno != errno.ENOENT: - raise - os.rename(fname_tmp, fname) - # Reset the timestamp - self.__timestamp = os.path.getmtime(fname) + # Save using the utility function with protocol 4 + save_pickle_file(dbfile, dict) + # Update the timestamp + self.__timestamp = os.path.getmtime(dbfile) + return None + except Exception as e: + syslog('error', 'Failed to save database file %s: %s', dbfile, str(e)) + return e def Save(self): """Save the mailing list's configuration to disk. @@ -731,7 +700,7 @@ def Save(self): # list members' passwords (in clear text). omask = os.umask(0o007) try: - self.__save(dict) + self.__save(os.path.join(self.fullpath(), 'config.pck'), dict) finally: os.umask(omask) self.SaveRequestsDb() @@ -753,55 +722,16 @@ def __load(self, dbfile): elif dbfile.endswith('.pck') or dbfile.endswith('.pck.last'): def loadfunc(fp): try: - # Read the first byte to determine protocol version - protocol = ord(fp.read(1)) - print(C_('List %(listname)s %(dbfile)s uses pickle protocol %(protocol)d') % { - 'listname': self.internal_name(), - 'dbfile': os.path.basename(dbfile), - 'protocol': protocol - }) - # Reset file pointer to beginning - fp.seek(0) - - # For protocol 2 files (Python 2.x), try loading with different encodings - if protocol == 2: - try: - # First try with latin1 (most common for Python 2.x) - return pickle.load(fp, fix_imports=True, encoding='latin1') - except (UnicodeDecodeError, pickle.UnpicklingError) as e: - syslog('error', 'Failed to load with latin1: %s', str(e)) - fp.seek(0) - try: - # Then try with UTF-8 - return pickle.load(fp, fix_imports=True, encoding='utf-8') - except (UnicodeDecodeError, pickle.UnpicklingError) as e: - syslog('error', 'Failed to load with UTF-8: %s', str(e)) - fp.seek(0) - # Finally try without encoding - return pickle.load(fp, fix_imports=True) - # For protocol 4 files (Python 3.x), try loading with different encodings - elif protocol == 4: - try: - # First try with UTF-8 - return pickle.load(fp, fix_imports=True, encoding='utf-8') - except (UnicodeDecodeError, pickle.UnpicklingError) as e: - syslog('error', 'Failed to load with UTF-8: %s', str(e)) - fp.seek(0) - try: - # Then try with latin1 - return pickle.load(fp, fix_imports=True, encoding='latin1') - except (UnicodeDecodeError, pickle.UnpicklingError) as e: - syslog('error', 'Failed to load with latin1: %s', str(e)) - fp.seek(0) - # Finally try without encoding - return pickle.load(fp, fix_imports=True) - else: - # For other protocols, try without encoding first - try: - return pickle.load(fp, fix_imports=True) - except (UnicodeDecodeError, pickle.UnpicklingError): - fp.seek(0) - return pickle.load(fp, fix_imports=True, encoding='latin1') + # Get the protocol version + protocol = get_pickle_protocol(fp.name) + if protocol is not None: + print(C_('List %(listname)s %(dbfile)s uses pickle protocol %(protocol)d') % { + 'listname': self.internal_name(), + 'dbfile': os.path.basename(dbfile), + 'protocol': protocol + }) + # Use the utility function to load the pickle + return load_pickle_file(fp.name) except Exception as e: syslog('error', 'Failed to load pickle file %s: %s', dbfile, str(e)) raise From 8b6bd74bda0359aec38165832a6762de1ade84b0 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 24 May 2025 21:38:05 -0400 Subject: [PATCH 707/748] Fix: Add C_ function definition to resolve pickle loading errors --- Mailman/MailList.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Mailman/MailList.py b/Mailman/MailList.py index d187b4a0..d67b4a79 100644 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -75,6 +75,8 @@ _ = i18n._ def D_(s): return s +def C_(s): + return s EMPTYSTRING = '' OR = '|' From 6f70c3cf9d7780af484cc7dd42fbab8811b36729 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 24 May 2025 21:43:52 -0400 Subject: [PATCH 708/748] Fix: Properly import all utility functions from Utils.py --- Mailman/MailList.py | 66 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/Mailman/MailList.py b/Mailman/MailList.py index d67b4a79..e194838d 100644 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -47,6 +47,70 @@ from Mailman import LockFile from Mailman.LockFile import NotLockedError, AlreadyLockedError, TimeOutError from Mailman.UserDesc import UserDesc +from Mailman.Utils import ( + save_pickle_file, + load_pickle_file, + get_pickle_protocol, + list_exists, + list_names, + wrap, + QuotePeriods, + ParseEmail, + LCDomain, + ValidateEmail, + GetPathPieces, + GetRequestMethod, + ScriptURL, + GetPossibleMatchingAddrs, + List2Dict, + UserFriendly_MakeRandomPassword, + Secure_MakeRandomPassword, + MakeRandomPassword, + GetRandomSeed, + set_global_password, + get_global_password, + check_global_password, + websafe, + nntpsplit, + ObscureEmail, + UnobscureEmail, + findtext, + maketext, + is_administrivia, + GetRequestURI, + reap, + GetLanguageDescr, + GetCharSet, + GetDirection, + IsLanguage, + get_domain, + get_site_email, + unique_message_id, + midnight, + to_dollar, + to_percent, + dollar_identifiers, + percent_identifiers, + canonstr, + uncanonstr, + uquote, + oneline, + strip_verbose_pattern, + suspiciousHTML, + get_suffixes, + get_org_dom, + IsDMARCProhibited, + IsVerboseMember, + check_eq_domains, + xml_to_unicode, + banned_ip, + banned_domain, + captcha_display, + captcha_verify, + validate_ip_address, + ValidateListName, + formataddr +) # base classes from Mailman.Archiver import Archiver @@ -665,7 +729,7 @@ def __save(self, dbfile, dict): # On success return None. On error, return the error object. try: # Save using the utility function with protocol 4 - save_pickle_file(dbfile, dict) + save_pickle_file(dbfile, dict, protocol=4) # Update the timestamp self.__timestamp = os.path.getmtime(dbfile) return None From a7bf2a55a774e4bf18633b9190aa2738a16a3aa1 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 24 May 2025 21:45:34 -0400 Subject: [PATCH 709/748] Fix: Add pickle utility functions to Utils.py --- Mailman/Utils.py | 72 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/Mailman/Utils.py b/Mailman/Utils.py index 8b1c2bff..4f655528 100644 --- a/Mailman/Utils.py +++ b/Mailman/Utils.py @@ -1719,3 +1719,75 @@ def formataddr(pair): name = email.header.Header(name, 'utf-8').encode() return '%s <%s>' % (name, address) return address + +def save_pickle_file(filename, data, protocol=4): + """Save data to a pickle file using a consistent protocol. + + Args: + filename: Path to save the pickle file + data: Data to pickle + protocol: Pickle protocol to use (defaults to 4 for Python 2/3 compatibility) + + Raises: + IOError: If the file cannot be written + """ + try: + with open(filename, 'wb') as fp: + pickle.dump(data, fp, protocol=protocol, fix_imports=True) + except IOError as e: + raise IOError(f'Could not write {filename}: {e}') + +def load_pickle_file(filename, encoding_order=None): + """Load a pickle file with consistent protocol and encoding handling. + + Args: + filename: Path to the pickle file + encoding_order: List of encodings to try in order. Defaults to ['utf-8', 'latin1'] + + Returns: + The unpickled data + + Raises: + pickle.UnpicklingError: If the file cannot be unpickled + IOError: If the file cannot be read + """ + if encoding_order is None: + encoding_order = ['utf-8', 'latin1'] + + try: + with open(filename, 'rb') as fp: + # Read the first byte to determine protocol version + protocol = ord(fp.read(1)) + # Reset file pointer to beginning + fp.seek(0) + + # Try each encoding in order + last_error = None + for encoding in encoding_order: + try: + fp.seek(0) + return pickle.load(fp, fix_imports=True, encoding=encoding) + except (UnicodeDecodeError, pickle.UnpicklingError) as e: + last_error = e + continue + + # If we get here, all encodings failed + raise last_error or pickle.UnpicklingError('Failed to load pickle file') + + except IOError as e: + raise IOError(f'Could not read {filename}: {e}') + +def get_pickle_protocol(filename): + """Get the protocol version of a pickle file. + + Args: + filename: Path to the pickle file + + Returns: + The protocol version (int) or None if it cannot be determined + """ + try: + with open(filename, 'rb') as fp: + return ord(fp.read(1)) + except (IOError, IndexError): + return None From 646a60bd5a23092c25f76be369e182edb1239d12 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 24 May 2025 21:47:15 -0400 Subject: [PATCH 710/748] Fix: Add missing pickle import to Utils.py --- Mailman/Utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Mailman/Utils.py b/Mailman/Utils.py index 4f655528..f7c6c387 100644 --- a/Mailman/Utils.py +++ b/Mailman/Utils.py @@ -36,6 +36,7 @@ import html import email.header import email.iterators +import pickle from email.errors import HeaderParseError from string import whitespace, digits from urllib.parse import urlparse From c726d97fd5b821b1dc47cb1556fbfe0da1ca75c1 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 24 May 2025 22:00:20 -0400 Subject: [PATCH 711/748] Improve pickle file handling in bin/update to match Mailman 2.1.39 behavior. Now tries UTF-8 and latin1 encodings with fix_imports=True for better Python 2/3 compatibility. --- bin/update | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/bin/update b/bin/update index cf654ca6..0fa71e07 100755 --- a/bin/update +++ b/bin/update @@ -880,8 +880,17 @@ def upgrade(mlist): config_path = os.path.join(mlist._full_path, 'config.pck') if os.path.exists(config_path): with open(config_path, 'rb') as fp: - # Read the first byte which contains the protocol version - protocol = ord(fp.read(1)) + # Try to load the pickle file with multiple encodings + try: + # Try UTF-8 first for newer files + data = pickle.load(fp, fix_imports=True, encoding='utf-8') + protocol = pickle.format_version + except (UnicodeDecodeError, pickle.UnpicklingError): + # Fall back to latin1 for older files + fp.seek(0) + data = pickle.load(fp, fix_imports=True, encoding='latin1') + protocol = pickle.format_version + print(C_('List %(listname)s config.pck uses pickle protocol %(protocol)d') % { 'listname': mlist.internal_name(), 'protocol': protocol From a0ff42399062ee90b1cc03c4a3476c56b248511c Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 24 May 2025 22:01:54 -0400 Subject: [PATCH 712/748] Add fix_imports=True and encoding='latin1' to pickle.load in bin/check_db for consistency with rest of codebase --- bin/check_db | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/check_db b/bin/check_db index 784821dc..b5eaff70 100755 --- a/bin/check_db +++ b/bin/check_db @@ -68,7 +68,7 @@ def load_pickle(fp): try: return pickle.load(fp, fix_imports=True, encoding='latin1') except Exception as e: - print('Error loading pickle file: %s' % e) + print('Error loading pickle file:', e) return None From 642baf8e5e913bb76c7ad90b43ddf015b59ef135 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 24 May 2025 22:04:16 -0400 Subject: [PATCH 713/748] Update pickle loading in bin/check_db to use fix_imports=True and encoding='latin1' for consistency --- bin/check_db | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/check_db b/bin/check_db index b5eaff70..1edec0b4 100755 --- a/bin/check_db +++ b/bin/check_db @@ -94,7 +94,7 @@ def testfile(dbfile, listname=None, verbose=0): # Now load the actual data fp.seek(0) - data = pickle.load(fp) + data = pickle.load(fp, fix_imports=True, encoding='latin1') if verbose: # Get pickle version info fp.seek(0) From 476e92d74411a659b6765b7f5af9cd884bffda82 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 24 May 2025 22:05:26 -0400 Subject: [PATCH 714/748] Improve Python 2 compatibility in bin/check_db by trying UTF-8 then latin1 encoding --- bin/check_db | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/bin/check_db b/bin/check_db index 1edec0b4..2744116f 100755 --- a/bin/check_db +++ b/bin/check_db @@ -82,19 +82,18 @@ def testfile(dbfile, listname=None, verbose=0): # Try to load the pickle file try: with open(dbfile, 'rb') as fp: - # First try to detect Python 2 pickle + # Try loading with UTF-8 first, then fall back to latin1 try: fp.seek(0) - header = fp.read(2) - is_py2_pickle = header.startswith(b'c') or header.startswith(b'C') + data = pickle.load(fp, fix_imports=True, encoding='utf-8') if verbose: - print(' Python 2 pickle detected: %s' % ('Yes' if is_py2_pickle else 'No')) - except: - is_py2_pickle = False + print(' Successfully loaded with UTF-8 encoding') + except UnicodeDecodeError: + fp.seek(0) + data = pickle.load(fp, fix_imports=True, encoding='latin1') + if verbose: + print(' Successfully loaded with latin1 encoding') - # Now load the actual data - fp.seek(0) - data = pickle.load(fp, fix_imports=True, encoding='latin1') if verbose: # Get pickle version info fp.seek(0) @@ -102,9 +101,6 @@ def testfile(dbfile, listname=None, verbose=0): protocol = pickle.HIGHEST_PROTOCOL print(' Pickle format version: %s' % version) print(' Pickle protocol: %d' % protocol) - if is_py2_pickle: - print(' WARNING: This file was likely written with Python 2') - print(' String data may need special handling for Python 3 compatibility') except (EOFError, pickle.UnpicklingError) as e: print(' Error loading file %s for list %s: %s' % (os.path.basename(dbfile), listname or 'unknown', str(e))) From 9f67bdaf75caaa6d60756986eafe44069ab0ad65 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 24 May 2025 22:06:09 -0400 Subject: [PATCH 715/748] Remove unused load_pickle function from bin/check_db --- bin/check_db | 9 --------- 1 file changed, 9 deletions(-) diff --git a/bin/check_db b/bin/check_db index 2744116f..57e22833 100755 --- a/bin/check_db +++ b/bin/check_db @@ -63,15 +63,6 @@ def parse_args(): return parser.parse_args() -def load_pickle(fp): - """Load a pickle file with Python 2/3 compatibility.""" - try: - return pickle.load(fp, fix_imports=True, encoding='latin1') - except Exception as e: - print('Error loading pickle file:', e) - return None - - def testfile(dbfile, listname=None, verbose=0): """Test the integrity of a list's config database file.""" try: From a34f1d4b845fde75c670e388e1c26b9a9e5dd56b Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 24 May 2025 22:10:58 -0400 Subject: [PATCH 716/748] Update pickle protocol detection to use loaded data instead of file header --- bin/check_db | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/bin/check_db b/bin/check_db index 57e22833..18537819 100755 --- a/bin/check_db +++ b/bin/check_db @@ -86,12 +86,12 @@ def testfile(dbfile, listname=None, verbose=0): print(' Successfully loaded with latin1 encoding') if verbose: - # Get pickle version info - fp.seek(0) - version = pickle.format_version - protocol = pickle.HIGHEST_PROTOCOL - print(' Pickle format version: %s' % version) - print(' Pickle protocol: %d' % protocol) + # Get pickle version info from the loaded data + if hasattr(data, '_protocol'): + protocol = data._protocol + print(' Pickle protocol: %d' % protocol) + else: + print(' Pickle protocol: unknown (not stored in data)') except (EOFError, pickle.UnpicklingError) as e: print(' Error loading file %s for list %s: %s' % (os.path.basename(dbfile), listname or 'unknown', str(e))) From 5f740a4dcc02890461fcfae3ce1c3f5405b1a6af Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 24 May 2025 22:12:13 -0400 Subject: [PATCH 717/748] Update pickle protocol detection in bin/update to match bin/check_db --- bin/update | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/bin/update b/bin/update index 0fa71e07..f5788e5e 100755 --- a/bin/update +++ b/bin/update @@ -880,21 +880,33 @@ def upgrade(mlist): config_path = os.path.join(mlist._full_path, 'config.pck') if os.path.exists(config_path): with open(config_path, 'rb') as fp: - # Try to load the pickle file with multiple encodings + # Try loading with UTF-8 first, then fall back to latin1 try: - # Try UTF-8 first for newer files + fp.seek(0) data = pickle.load(fp, fix_imports=True, encoding='utf-8') - protocol = pickle.format_version - except (UnicodeDecodeError, pickle.UnpicklingError): - # Fall back to latin1 for older files + if hasattr(data, '_protocol'): + protocol = data._protocol + print(C_('List %(listname)s config.pck uses pickle protocol %(protocol)d') % { + 'listname': mlist.internal_name(), + 'protocol': protocol + }) + else: + print(C_('List %(listname)s config.pck protocol version not stored in data') % { + 'listname': mlist.internal_name() + }) + except UnicodeDecodeError: fp.seek(0) data = pickle.load(fp, fix_imports=True, encoding='latin1') - protocol = pickle.format_version - - print(C_('List %(listname)s config.pck uses pickle protocol %(protocol)d') % { - 'listname': mlist.internal_name(), - 'protocol': protocol - }) + if hasattr(data, '_protocol'): + protocol = data._protocol + print(C_('List %(listname)s config.pck uses pickle protocol %(protocol)d') % { + 'listname': mlist.internal_name(), + 'protocol': protocol + }) + else: + print(C_('List %(listname)s config.pck protocol version not stored in data') % { + 'listname': mlist.internal_name() + }) except Exception as e: print(C_('Warning: Could not determine pickle protocol version: %(error)s') % { 'error': str(e) From 3095bc31dbbf406c7c645bd85eab4da1250db159 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 24 May 2025 22:14:13 -0400 Subject: [PATCH 718/748] Fix get_pickle_protocol to properly detect protocol version by loading the file instead of reading first byte --- Mailman/Utils.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Mailman/Utils.py b/Mailman/Utils.py index f7c6c387..ac5e179d 100644 --- a/Mailman/Utils.py +++ b/Mailman/Utils.py @@ -1789,6 +1789,8 @@ def get_pickle_protocol(filename): """ try: with open(filename, 'rb') as fp: - return ord(fp.read(1)) - except (IOError, IndexError): + # Try to load the pickle file to get its protocol version + data = pickle.load(fp, fix_imports=True, encoding='latin1') + return pickle.format_version + except (IOError, pickle.UnpicklingError): return None From f1158d8314695e32a5fcb6796c9eab38f0c6bebd Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 24 May 2025 22:16:31 -0400 Subject: [PATCH 719/748] Fix error message formatting in MailList.py to handle error messages containing format specifiers --- Mailman/MailList.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Mailman/MailList.py b/Mailman/MailList.py index e194838d..efc59de8 100644 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -799,7 +799,7 @@ def loadfunc(fp): # Use the utility function to load the pickle return load_pickle_file(fp.name) except Exception as e: - syslog('error', 'Failed to load pickle file %s: %s', dbfile, str(e)) + syslog('error', 'Failed to load pickle file %s: %r', dbfile, e) raise else: raise ValueError('Bad database file name') @@ -836,7 +836,7 @@ def loadfunc(fp): return dict, None except Exception as e: fp.close() - syslog('error', 'Failed to load database file %s: %s', dbfile, str(e)) + syslog('error', 'Failed to load database file %s: %r', dbfile, e) return None, e def Load(self, check_version=True): From 5f2368e1374b9ea0daab273c1a984675641a82dc Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sat, 24 May 2025 22:17:51 -0400 Subject: [PATCH 720/748] Fix get_pickle_protocol to properly detect protocol version from file header --- Mailman/Utils.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/Mailman/Utils.py b/Mailman/Utils.py index ac5e179d..5c2ece82 100644 --- a/Mailman/Utils.py +++ b/Mailman/Utils.py @@ -1789,8 +1789,19 @@ def get_pickle_protocol(filename): """ try: with open(filename, 'rb') as fp: - # Try to load the pickle file to get its protocol version - data = pickle.load(fp, fix_imports=True, encoding='latin1') - return pickle.format_version - except (IOError, pickle.UnpicklingError): + # Read the first byte to determine protocol version + first_byte = fp.read(1) + if not first_byte: + return None + # The first byte of a pickle file indicates the protocol version + # For protocol 0, it's '0', for protocol 1 it's '1', etc. + # For protocol 2 and higher, it's a binary value + if first_byte[0] == ord('0'): + return 0 + elif first_byte[0] == ord('1'): + return 1 + else: + # For protocol 2 and higher, the first byte is the protocol number + return first_byte[0] + except (IOError, IndexError): return None From c4070cc95165c4922005d1b05e61270e0e1b16f7 Mon Sep 17 00:00:00 2001 From: jared mauch Date: Wed, 12 Nov 2025 17:01:12 -0500 Subject: [PATCH 721/748] Major Python 3 migration and bug fixes This commit consolidates extensive Python 3 compatibility improvements, bug fixes, and feature enhancements including: - Python 3 migration: Replace raw_input with input, fix execfile usage, handle string/bytes encoding throughout codebase - Encoding fixes: UTF-8 handling, proper encoding for email headers, subscription forms, and message decoration - Archiver improvements: Fix on-the-fly archiving, handle non-ASCII characters, improve archive format handling - cPanel integration: Sync fixes for pickle protocol detection, encoding issues, and various CPANEL ticket fixes - Configuration: Improve mm_cfg handler, fix circular import issues - Bug fixes: Fix TypeError with string encoding/hashing, KeyError in HyperDatabase, NNTP bridge issues, and various other fixes - Cron script fixes: Update senddigests, checkdbs, and other cron jobs for Python 3 compatibility This represents a comprehensive update bringing the codebase to full Python 3 compatibility while maintaining backward compatibility where possible. --- Mailman/Archiver/Archiver.py | 92 +- Mailman/Archiver/HyperArch.py | 727 +- Mailman/Archiver/HyperDatabase.py | 26 +- Mailman/Archiver/__init__.py | 1 + Mailman/Archiver/pipermail.py | 156 +- Mailman/Autoresponder.py | 35 - Mailman/Bouncer.py | 18 +- Mailman/Bouncers/Caiwireless.py | 25 +- Mailman/Bouncers/Compuserve.py | 5 +- Mailman/Bouncers/DSN.py | 45 +- Mailman/Bouncers/Exchange.py | 38 +- Mailman/Bouncers/GroupWise.py | 6 +- Mailman/Bouncers/LLNL.py | 5 +- Mailman/Bouncers/Microsoft.py | 1 + Mailman/Bouncers/Qmail.py | 19 +- Mailman/Bouncers/SMTP32.py | 5 +- Mailman/Bouncers/SimpleMatch.py | 159 +- Mailman/Bouncers/SimpleWarning.py | 5 +- Mailman/Bouncers/Sina.py | 7 +- Mailman/Bouncers/Yahoo.py | 11 +- Mailman/CSRFcheck.py | 82 +- Mailman/Cgi/Auth.py | 1 - Mailman/Cgi/admin.py | 1622 +- Mailman/Cgi/admindb.py | 892 +- Mailman/Cgi/confirm.py | 71 +- Mailman/Cgi/create.py | 206 +- Mailman/Cgi/edithtml.py | 63 +- Mailman/Cgi/listinfo.py | 161 +- Mailman/Cgi/options.py | 329 +- Mailman/Cgi/private.py | 176 +- Mailman/Cgi/rmlist.py | 95 +- Mailman/Cgi/roster.py | 31 +- Mailman/Cgi/subscribe.py | 187 +- Mailman/Commands/cmd_confirm.py | 14 +- Mailman/Commands/cmd_set.py | 2 +- Mailman/Commands/cmd_subscribe.py | 9 +- Mailman/Commands/cmd_unsubscribe.py | 3 +- Mailman/Defaults.py.in | 70 +- Mailman/Deliverer.py | 102 +- Mailman/Digester.py | 11 +- Mailman/Errors.py | 6 +- Mailman/Gui/Bounce.py | 2 + Mailman/Gui/Digest.py | 10 +- Mailman/Gui/GUIBase.py | 29 +- Mailman/Gui/General.py | 2 +- Mailman/Gui/Language.py | 2 +- Mailman/Gui/NonDigest.py | 11 +- Mailman/Gui/Privacy.py | 25 +- Mailman/Gui/Topics.py | 40 +- Mailman/HTMLFormatter.py | 286 +- Mailman/Handlers/Acknowledge.py | 7 +- Mailman/Handlers/Approve.py | 7 +- Mailman/Handlers/CalcRecips.py | 27 +- Mailman/Handlers/Cleanse.py | 17 +- Mailman/Handlers/CleanseDKIM.py | 26 +- Mailman/Handlers/CookHeaders.py | 606 +- Mailman/Handlers/Decorate.py | 36 +- Mailman/Handlers/Hold.py | 381 +- Mailman/Handlers/MimeDel.py | 53 +- Mailman/Handlers/Moderate.py | 60 +- Mailman/Handlers/Replybot.py | 144 +- Mailman/Handlers/SMTPDirect.py | 646 +- Mailman/Handlers/Scrubber.py | 312 +- Mailman/Handlers/SpamDetect.py | 112 +- Mailman/Handlers/Tagger.py | 7 +- Mailman/Handlers/ToArchive.py | 20 +- Mailman/Handlers/ToDigest.py | 685 +- Mailman/Handlers/ToOutgoing.py | 90 +- Mailman/Handlers/ToUsenet.py | 5 +- Mailman/Handlers/__init__.py | 27 - Mailman/ListAdmin.py | 543 +- Mailman/LockFile.py | 766 +- Mailman/Logging/Logger.py | 58 +- Mailman/Logging/StampedLogger.py | 11 - Mailman/Logging/Syslog.py | 31 +- Mailman/MTA/Manual.py | 8 +- Mailman/MailList.py | 1802 +- Mailman/Mailbox.py | 80 +- Mailman/Message.py | 178 +- Mailman/OldStyleMemberships.py | 345 +- Mailman/Pending.py | 164 +- Mailman/Post.py | 16 +- Mailman/Queue/ArchRunner.py | 48 +- Mailman/Queue/BounceRunner.py | 498 +- Mailman/Queue/CommandRunner.py | 459 +- Mailman/Queue/IncomingRunner.py | 401 +- Mailman/Queue/MaildirRunner.py | 95 +- Mailman/Queue/NewsRunner.py | 253 +- Mailman/Queue/OutgoingRunner.py | 612 +- Mailman/Queue/RetryRunner.py | 280 +- Mailman/Queue/Runner.py | 527 +- Mailman/Queue/Switchboard.py | 804 +- Mailman/Queue/VirginRunner.py | 230 +- Mailman/Queue/__init__.py | 58 - Mailman/SecurityManager.py | 17 +- Mailman/Site.py | 9 +- Mailman/UserDesc.py | 4 +- Mailman/Utils.py | 1260 +- Mailman/Version.py | 6 +- Mailman/{__init__.py => __init__.py.in} | 3 + Mailman/htmlformat.py | 291 +- Mailman/i18n.py | 30 +- Mailman/mm_cfg.py.dist.in | 4 +- Mailman/versions.py | 19 +- Makefile.in | 217 +- NEWS | 87 +- bin/Makefile.in | 18 +- bin/add_members | 215 +- bin/arch | 89 +- bin/b4b5-archfix | 33 +- bin/change_pw | 84 +- bin/check_db | 233 +- bin/check_perms | 58 +- bin/cleanarch | 78 +- bin/clone_member | 114 +- bin/config_list | 330 +- bin/discard | 39 +- bin/dumpdb | 124 +- bin/export.py | 52 +- bin/find_member | 59 +- bin/fix_url.py | 40 +- bin/genaliases | 34 +- bin/inject | 70 +- bin/list_admins | 58 +- bin/list_lists | 81 +- bin/list_members | 172 +- bin/list_owners | 49 +- bin/mailmanctl | 691 +- bin/mmsitepass | 48 +- bin/msgfmt-python2.py | 57 +- bin/msgfmt.py | 50 +- bin/newlist | 155 +- bin/pygettext.py | 423 +- bin/qrunner | 177 +- bin/rb-archfix | 60 +- bin/remove_members | 149 +- bin/reset_pw.py | 36 +- bin/rmlist | 38 +- bin/show_qfiles | 73 +- bin/sync_members | 272 +- bin/transcheck | 107 +- bin/unshunt | 33 +- bin/update | 719 +- bin/withlist | 143 +- configure | 3673 ++- configure.ac | 11 +- contrib/check_perms_grsecurity.py | 4 +- contrib/courier-to-mailman.py | 10 +- contrib/import_majordomo_into_mailman.pl | 139 +- contrib/majordomo2mailman.pl | 143 +- contrib/mm-handler | 17 +- contrib/mm-handler-2.1.10 | 17 +- contrib/qmail-to-mailman.py | 4 +- contrib/rotatelogs.py | 4 +- contrib/sitemapgen | 50 +- cron/bumpdigests | 28 +- cron/checkdbs | 282 +- cron/cull_bad_shunt | 30 +- cron/disabled | 86 +- cron/gate_news | 187 +- cron/mailpasswds | 230 +- cron/nightly_gzip | 37 +- cron/senddigests | 42 +- doc/mailman-admin/about.html | 119 +- doc/mailman-admin/contents.html | 112 +- doc/mailman-admin/front.html | 111 +- doc/mailman-admin/general-personality.html | 128 +- doc/mailman-admin/index.html | 107 +- doc/mailman-admin/mailman-admin.html | 107 +- doc/mailman-admin/node11.html | 125 +- doc/mailman-admin/node12.html | 107 +- doc/mailman-admin/node13.html | 124 +- doc/mailman-admin/node14.html | 124 +- doc/mailman-admin/node15.html | 106 +- doc/mailman-admin/node16.html | 113 +- doc/mailman-admin/node17.html | 107 +- doc/mailman-admin/node18.html | 159 +- doc/mailman-admin/node19.html | 141 +- doc/mailman-admin/node20.html | 132 +- doc/mailman-admin/node21.html | 128 +- doc/mailman-admin/node23.html | 112 +- doc/mailman-admin/node24.html | 109 +- doc/mailman-admin/node25.html | 122 +- doc/mailman-admin/node26.html | 112 +- doc/mailman-admin/node27.html | 107 +- doc/mailman-admin/node28.html | 104 +- doc/mailman-admin/node29.html | 104 +- doc/mailman-admin/node3.html | 115 +- doc/mailman-admin/node30.html | 105 +- doc/mailman-admin/node31.html | 104 +- doc/mailman-admin/node32.html | 104 +- doc/mailman-admin/node33.html | 104 +- doc/mailman-admin/node34.html | 108 +- doc/mailman-admin/node35.html | 106 +- doc/mailman-admin/node4.html | 121 +- doc/mailman-admin/node5.html | 107 +- doc/mailman-admin/node6.html | 107 +- doc/mailman-admin/node7.html | 114 +- doc/mailman-admin/node8.html | 119 +- doc/mailman-admin/node9.html | 115 +- doc/mailman-admin/sender-filters.html | 126 +- doc/mailman-install/about.html | 115 +- doc/mailman-install/bsd-issues.html | 106 +- doc/mailman-install/building.html | 113 +- doc/mailman-install/create-install-dir.html | 120 +- doc/mailman-install/customizing.html | 117 +- doc/mailman-install/exim3-transport.html | 106 +- doc/mailman-install/front.html | 131 +- doc/mailman-install/index.html | 108 +- doc/mailman-install/mail-server.html | 122 +- doc/mailman-install/mailman-install.html | 108 +- doc/mailman-install/node10.html | 109 +- doc/mailman-install/node12.html | 113 +- doc/mailman-install/node15.html | 103 +- doc/mailman-install/node16.html | 112 +- doc/mailman-install/node17.html | 111 +- doc/mailman-install/node18.html | 104 +- doc/mailman-install/node2.html | 130 +- doc/mailman-install/node20.html | 104 +- doc/mailman-install/node21.html | 103 +- doc/mailman-install/node22.html | 103 +- doc/mailman-install/node23.html | 103 +- doc/mailman-install/node24.html | 111 +- doc/mailman-install/node25.html | 105 +- doc/mailman-install/node26.html | 104 +- doc/mailman-install/node27.html | 104 +- doc/mailman-install/node28.html | 103 +- doc/mailman-install/node29.html | 104 +- doc/mailman-install/node3.html | 111 +- doc/mailman-install/node30.html | 101 +- doc/mailman-install/node31.html | 119 +- doc/mailman-install/node32.html | 116 +- doc/mailman-install/node33.html | 105 +- doc/mailman-install/node34.html | 101 +- doc/mailman-install/node36.html | 103 +- doc/mailman-install/node37.html | 103 +- doc/mailman-install/node38.html | 101 +- doc/mailman-install/node4.html | 113 +- doc/mailman-install/node41.html | 119 +- doc/mailman-install/node42.html | 108 +- doc/mailman-install/node43.html | 104 +- doc/mailman-install/node44.html | 120 +- doc/mailman-install/node45.html | 136 +- doc/mailman-install/node47.html | 109 +- doc/mailman-install/node48.html | 112 +- doc/mailman-install/node50.html | 117 +- doc/mailman-install/node7.html | 132 +- doc/mailman-install/node8.html | 101 +- doc/mailman-install/node9.html | 114 +- doc/mailman-install/postfix-integration.html | 123 +- doc/mailman-install/postfix-virtual.html | 116 +- doc/mailman-install/qmail-issues.html | 132 +- doc/mailman-install/site-list.html | 107 +- doc/mailman-install/troubleshooting.html | 148 +- doc/mailman-member-es/about.html | 121 +- doc/mailman-member-es/contents.html | 128 +- doc/mailman-member-es/front.html | 113 +- doc/mailman-member-es/index.html | 125 +- doc/mailman-member-es/mailman-member-es.html | 127 +- doc/mailman-member-es/node10.html | 123 +- doc/mailman-member-es/node11.html | 110 +- doc/mailman-member-es/node12.html | 114 +- doc/mailman-member-es/node13.html | 116 +- doc/mailman-member-es/node14.html | 117 +- doc/mailman-member-es/node15.html | 118 +- doc/mailman-member-es/node16.html | 124 +- doc/mailman-member-es/node17.html | 150 +- doc/mailman-member-es/node18.html | 118 +- doc/mailman-member-es/node19.html | 111 +- doc/mailman-member-es/node20.html | 121 +- doc/mailman-member-es/node21.html | 121 +- doc/mailman-member-es/node22.html | 116 +- doc/mailman-member-es/node23.html | 119 +- doc/mailman-member-es/node24.html | 125 +- doc/mailman-member-es/node25.html | 120 +- doc/mailman-member-es/node26.html | 111 +- doc/mailman-member-es/node27.html | 125 +- doc/mailman-member-es/node28.html | 123 +- doc/mailman-member-es/node29.html | 117 +- doc/mailman-member-es/node3.html | 114 +- doc/mailman-member-es/node30.html | 174 +- doc/mailman-member-es/node31.html | 116 +- doc/mailman-member-es/node32.html | 113 +- doc/mailman-member-es/node33.html | 115 +- doc/mailman-member-es/node34.html | 109 +- doc/mailman-member-es/node35.html | 114 +- doc/mailman-member-es/node36.html | 112 +- doc/mailman-member-es/node37.html | 121 +- doc/mailman-member-es/node38.html | 117 +- doc/mailman-member-es/node39.html | 111 +- doc/mailman-member-es/node4.html | 107 +- doc/mailman-member-es/node40.html | 124 +- doc/mailman-member-es/node41.html | 254 +- doc/mailman-member-es/node42.html | 192 +- doc/mailman-member-es/node5.html | 106 +- doc/mailman-member-es/node6.html | 105 +- doc/mailman-member-es/node7.html | 108 +- doc/mailman-member-es/node8.html | 114 +- doc/mailman-member-es/node9.html | 139 +- doc/mailman-member/about.html | 119 +- doc/mailman-member/contents.html | 126 +- doc/mailman-member/front.html | 111 +- doc/mailman-member/index.html | 121 +- doc/mailman-member/mailman-member.html | 121 +- doc/mailman-member/node10.html | 122 +- doc/mailman-member/node11.html | 111 +- doc/mailman-member/node12.html | 114 +- doc/mailman-member/node13.html | 118 +- doc/mailman-member/node14.html | 117 +- doc/mailman-member/node15.html | 117 +- doc/mailman-member/node16.html | 124 +- doc/mailman-member/node17.html | 151 +- doc/mailman-member/node18.html | 118 +- doc/mailman-member/node19.html | 111 +- doc/mailman-member/node20.html | 121 +- doc/mailman-member/node21.html | 121 +- doc/mailman-member/node22.html | 115 +- doc/mailman-member/node23.html | 121 +- doc/mailman-member/node24.html | 126 +- doc/mailman-member/node25.html | 121 +- doc/mailman-member/node26.html | 111 +- doc/mailman-member/node27.html | 125 +- doc/mailman-member/node28.html | 124 +- doc/mailman-member/node29.html | 119 +- doc/mailman-member/node3.html | 118 +- doc/mailman-member/node30.html | 171 +- doc/mailman-member/node31.html | 117 +- doc/mailman-member/node32.html | 113 +- doc/mailman-member/node33.html | 115 +- doc/mailman-member/node34.html | 109 +- doc/mailman-member/node35.html | 115 +- doc/mailman-member/node36.html | 111 +- doc/mailman-member/node37.html | 119 +- doc/mailman-member/node38.html | 115 +- doc/mailman-member/node39.html | 108 +- doc/mailman-member/node4.html | 107 +- doc/mailman-member/node40.html | 122 +- doc/mailman-member/node41.html | 252 +- doc/mailman-member/node42.html | 190 +- doc/mailman-member/node5.html | 106 +- doc/mailman-member/node6.html | 105 +- doc/mailman-member/node7.html | 108 +- doc/mailman-member/node8.html | 114 +- doc/mailman-member/node9.html | 138 +- messages/Makefile.in | 7 +- messages/ar/LC_MESSAGES/mailman.po | 14 +- messages/ast/LC_MESSAGES/mailman.po | 12 +- messages/ca/LC_MESSAGES/mailman.po | 12 +- messages/cs/LC_MESSAGES/mailman.po | 10 +- messages/da/LC_MESSAGES/mailman.po | 14 +- messages/de/LC_MESSAGES/mailman.po | 12 +- messages/el/LC_MESSAGES/mailman.po | 12 +- messages/eo/LC_MESSAGES/mailman.po | 6 +- messages/es/LC_MESSAGES/mailman.po | 12 +- messages/et/LC_MESSAGES/mailman.po | 12 +- messages/eu/LC_MESSAGES/mailman.po | 10 +- messages/fa/LC_MESSAGES/mailman.po | 6 +- messages/fi/LC_MESSAGES/mailman.po | 12 +- messages/fr/LC_MESSAGES/mailman.po | 12 +- messages/gl/LC_MESSAGES/mailman.po | 12 +- messages/he/LC_MESSAGES/mailman.po | 10 +- messages/hr/LC_MESSAGES/mailman.po | 10 +- messages/hu/LC_MESSAGES/mailman.po | 12 +- messages/ia/LC_MESSAGES/mailman.po | 10 +- messages/it/LC_MESSAGES/mailman.po | 12 +- messages/ja/LC_MESSAGES/mailman.po | 12 +- messages/ja/doc/Defaults.py.in | 20401 +---------------- messages/ko/LC_MESSAGES/mailman.po | 14 +- messages/lt/LC_MESSAGES/mailman.po | 6 +- messages/mailman.pot | 6 +- messages/nl/LC_MESSAGES/mailman.po | 10 +- messages/no/LC_MESSAGES/mailman.po | 14 +- messages/pl/LC_MESSAGES/mailman.po | 10 +- messages/pt/LC_MESSAGES/mailman.po | 12 +- messages/pt_BR/LC_MESSAGES/mailman.po | 12 +- messages/ro/LC_MESSAGES/mailman.po | 10 +- messages/ru/LC_MESSAGES/mailman.po | 12 +- messages/sk/LC_MESSAGES/mailman.po | 10 +- messages/sl/LC_MESSAGES/mailman.po | 16 +- messages/sr/LC_MESSAGES/mailman.po | 6 +- messages/sv/LC_MESSAGES/mailman.po | 14 +- messages/tr/LC_MESSAGES/mailman.po | 10 +- messages/uk/LC_MESSAGES/mailman.po | 12 +- messages/vi/LC_MESSAGES/mailman.po | 12 +- messages/zh_CN/LC_MESSAGES/mailman.po | 12 +- messages/zh_TW/LC_MESSAGES/mailman.po | 8 +- misc/sitelist.cfg | 2 +- scripts/bounces | 13 +- scripts/confirm | 14 +- scripts/convert_to_utf8 | 134 + scripts/driver | 7 +- scripts/join | 13 +- scripts/leave | 15 +- scripts/owner | 26 +- scripts/post | 33 +- scripts/request | 16 +- src/Makefile.in | 24 +- src/cgi-wrapper.c | 44 +- src/common.c | 127 +- src/common.h | 5 - src/mail-wrapper.c | 10 +- templates/Makefile.in | 7 +- templates/ar/admindbdetails.html | 72 +- templates/ar/admindbpreamble.html | 65 +- templates/ar/admindbsummary.html | 65 +- templates/ar/admlogin.html | 110 +- templates/ar/archidxentry.html | 71 +- templates/ar/archidxfoot.html | 91 +- templates/ar/archidxhead.html | 94 +- templates/ar/archlistend.html | 64 +- templates/ar/archliststart.html | 71 +- templates/ar/archtoc.html | 83 +- templates/ar/archtocentry.html | 81 +- templates/ar/archtocnombox.html | 83 +- templates/ar/article.html | 77 +- templates/ar/emptyarchive.html | 84 +- templates/ar/headfoot.html | 68 +- templates/ar/listinfo.html | 319 +- templates/ar/options.html | 440 +- templates/ar/private.html | 144 +- templates/ar/roster.html | 158 +- templates/ar/subscribe.html | 76 +- templates/ast/admindbdetails.html | 94 +- templates/ast/admindbpreamble.html | 71 +- templates/ast/admindbsummary.html | 71 +- templates/ast/admlogin.html | 114 +- templates/ast/archidxentry.html | 71 +- templates/ast/archidxfoot.html | 93 +- templates/ast/archidxhead.html | 94 +- templates/ast/archlistend.html | 64 +- templates/ast/archliststart.html | 71 +- templates/ast/archtoc.html | 85 +- templates/ast/archtocentry.html | 80 +- templates/ast/archtocnombox.html | 87 +- templates/ast/article.html | 138 +- templates/ast/emptyarchive.html | 85 +- templates/ast/headfoot.html | 85 +- templates/ast/listinfo.html | 317 +- templates/ast/options.html | 495 +- templates/ast/private.html | 150 +- templates/ast/roster.html | 158 +- templates/ast/subscribe.html | 72 +- templates/ca/admindbdetails.html | 158 +- templates/ca/admindbpreamble.html | 79 +- templates/ca/admindbsummary.html | 79 +- templates/ca/admlogin.html | 122 +- templates/ca/archidxentry.html | 71 +- templates/ca/archidxfoot.html | 91 +- templates/ca/archidxhead.html | 98 +- templates/ca/archlistend.html | 64 +- templates/ca/archliststart.html | 71 +- templates/ca/archtoc.html | 87 +- templates/ca/archtocentry.html | 81 +- templates/ca/archtocnombox.html | 85 +- templates/ca/article.html | 78 +- templates/ca/emptyarchive.html | 85 +- templates/ca/headfoot.html | 87 +- templates/ca/listinfo.html | 335 +- templates/ca/options.html | 546 +- templates/ca/private.html | 159 +- templates/ca/roster.html | 162 +- templates/ca/subscribe.html | 72 +- templates/cs/admindbdetails.html | 168 +- templates/cs/admindbpreamble.html | 85 +- templates/cs/admindbsummary.html | 89 +- templates/cs/admlogin.html | 120 +- templates/cs/archidxentry.html | 71 +- templates/cs/archidxfoot.html | 93 +- templates/cs/archidxhead.html | 96 +- templates/cs/archlistend.html | 65 +- templates/cs/archliststart.html | 73 +- templates/cs/archtoc.html | 87 +- templates/cs/archtocentry.html | 82 +- templates/cs/archtocnombox.html | 85 +- templates/cs/article.html | 76 +- templates/cs/emptyarchive.html | 89 +- templates/cs/headfoot.html | 101 +- templates/cs/listinfo.html | 342 +- templates/cs/options.html | 568 +- templates/cs/private.html | 154 +- templates/cs/roster.html | 164 +- templates/cs/subscribe.html | 72 +- templates/da/admindbdetails.html | 124 +- templates/da/admindbpreamble.html | 71 +- templates/da/admindbsummary.html | 77 +- templates/da/admlogin.html | 122 +- templates/da/archidxfoot.html | 91 +- templates/da/archidxhead.html | 94 +- templates/da/archliststart.html | 71 +- templates/da/archtoc.html | 83 +- templates/da/archtocentry.html | 81 +- templates/da/archtocnombox.html | 83 +- templates/da/article.html | 77 +- templates/da/emptyarchive.html | 87 +- templates/da/headfoot.html | 78 +- templates/da/listinfo.html | 332 +- templates/da/options.html | 543 +- templates/da/private.html | 157 +- templates/da/roster.html | 160 +- templates/da/subscribe.html | 72 +- templates/de/admindbdetails.html | 134 +- templates/de/admindbpreamble.html | 73 +- templates/de/admindbsummary.html | 77 +- templates/de/admlogin.html | 118 +- templates/de/archidxentry.html | 71 +- templates/de/archidxfoot.html | 93 +- templates/de/archidxhead.html | 96 +- templates/de/archlistend.html | 64 +- templates/de/archliststart.html | 71 +- templates/de/archtoc.html | 85 +- templates/de/archtocentry.html | 81 +- templates/de/archtocnombox.html | 85 +- templates/de/article.html | 77 +- templates/de/emptyarchive.html | 85 +- templates/de/headfoot.html | 86 +- templates/de/listinfo.html | 344 +- templates/de/options.html | 516 +- templates/de/private.html | 162 +- templates/de/roster.html | 156 +- templates/de/subscribe.html | 72 +- templates/el/admindbdetails.html | 184 +- templates/el/admindbpreamble.html | 77 +- templates/el/admindbsummary.html | 83 +- templates/el/admlogin.html | 128 +- templates/el/archidxentry.html | 71 +- templates/el/archidxfoot.html | 95 +- templates/el/archidxhead.html | 96 +- templates/el/archlistend.html | 64 +- templates/el/archliststart.html | 71 +- templates/el/archtoc.html | 89 +- templates/el/archtocentry.html | 81 +- templates/el/archtocnombox.html | 87 +- templates/el/article.html | 79 +- templates/el/emptyarchive.html | 89 +- templates/el/headfoot.html | 102 +- templates/el/listinfo.html | 344 +- templates/el/options.html | 648 +- templates/el/private.html | 161 +- templates/el/roster.html | 162 +- templates/el/subscribe.html | 72 +- templates/en/admindbdetails.html | 72 +- templates/en/admindbpreamble.html | 65 +- templates/en/admindbsummary.html | 65 +- templates/en/admlogin.html | 109 +- templates/en/archidxentry.html | 71 +- templates/en/archidxfoot.html | 91 +- templates/en/archidxhead.html | 94 +- templates/en/archlistend.html | 64 +- templates/en/archliststart.html | 73 +- templates/en/archtoc.html | 83 +- templates/en/archtocentry.html | 81 +- templates/en/archtocnombox.html | 83 +- templates/en/article.html | 77 +- templates/en/emptyarchive.html | 83 +- templates/en/headfoot.html | 70 +- templates/en/listinfo.html | 319 +- templates/en/options.html | 444 +- templates/en/private.html | 142 +- templates/en/roster.html | 162 +- templates/en/subscribe.html | 72 +- templates/eo/admlogin.html | 106 +- templates/eo/archidxentry.html | 71 +- templates/eo/archidxfoot.html | 89 +- templates/eo/archidxhead.html | 96 +- templates/eo/archlistend.html | 64 +- templates/eo/archliststart.html | 71 +- templates/eo/archtoc.html | 83 +- templates/eo/archtocentry.html | 81 +- templates/eo/archtocnombox.html | 83 +- templates/eo/article.html | 78 +- templates/eo/emptyarchive.html | 83 +- templates/eo/listinfo.html | 317 +- templates/eo/options.html | 450 +- templates/eo/private.html | 142 +- templates/eo/roster.html | 154 +- templates/eo/subscribe.html | 72 +- templates/es/admindbdetails.html | 140 +- templates/es/admindbpreamble.html | 75 +- templates/es/admindbsummary.html | 83 +- templates/es/admlogin.html | 116 +- templates/es/archidxentry.html | 71 +- templates/es/archidxfoot.html | 93 +- templates/es/archidxhead.html | 96 +- templates/es/archlistend.html | 64 +- templates/es/archliststart.html | 71 +- templates/es/archtoc.html | 85 +- templates/es/archtocentry.html | 81 +- templates/es/archtocnombox.html | 87 +- templates/es/article.html | 77 +- templates/es/emptyarchive.html | 89 +- templates/es/handle_opts.html | 74 +- templates/es/headfoot.html | 95 +- templates/es/listinfo.html | 340 +- templates/es/options.html | 572 +- templates/es/private.html | 148 +- templates/es/roster.html | 160 +- templates/es/subscribe.html | 72 +- templates/et/admindbdetails.html | 138 +- templates/et/admindbpreamble.html | 71 +- templates/et/admindbsummary.html | 77 +- templates/et/admlogin.html | 116 +- templates/et/article.html | 82 +- templates/et/emptyarchive.html | 87 +- templates/et/headfoot.html | 84 +- templates/et/listinfo.html | 324 +- templates/et/options.html | 529 +- templates/et/private.html | 148 +- templates/et/roster.html | 162 +- templates/et/subscribe.html | 72 +- templates/eu/admindbdetails.html | 72 +- templates/eu/admindbpreamble.html | 65 +- templates/eu/admindbsummary.html | 65 +- templates/eu/admlogin.html | 110 +- templates/eu/archidxentry.html | 71 +- templates/eu/archidxfoot.html | 91 +- templates/eu/archidxhead.html | 94 +- templates/eu/archlistend.html | 64 +- templates/eu/archliststart.html | 73 +- templates/eu/archtoc.html | 83 +- templates/eu/archtocentry.html | 82 +- templates/eu/article.html | 77 +- templates/eu/emptyarchive.html | 83 +- templates/eu/headfoot.html | 70 +- templates/eu/listinfo.html | 351 +- templates/eu/options.html | 444 +- templates/eu/private.html | 144 +- templates/eu/roster.html | 162 +- templates/eu/subscribe.html | 72 +- templates/fa/admlogin.html | 106 +- templates/fa/archidxfoot.html | 91 +- templates/fa/archidxhead.html | 94 +- templates/fa/archliststart.html | 71 +- templates/fa/archtoc.html | 83 +- templates/fa/archtocentry.html | 81 +- templates/fa/archtocnombox.html | 83 +- templates/fa/article.html | 77 +- templates/fa/emptyarchive.html | 83 +- templates/fa/listinfo.html | 320 +- templates/fa/options.html | 436 +- templates/fa/private.html | 140 +- templates/fa/roster.html | 156 +- templates/fa/subscribe.html | 72 +- templates/fi/admindbdetails.html | 162 +- templates/fi/admindbpreamble.html | 77 +- templates/fi/admindbsummary.html | 81 +- templates/fi/admlogin.html | 128 +- templates/fi/article.html | 81 +- templates/fi/headfoot.html | 108 +- templates/fi/listinfo.html | 342 +- templates/fi/options.html | 529 +- templates/fi/private.html | 156 +- templates/fi/roster.html | 162 +- templates/fi/subscribe.html | 72 +- templates/fr/admindbdetails.html | 162 +- templates/fr/admindbpreamble.html | 77 +- templates/fr/admindbsummary.html | 73 +- templates/fr/admlogin.html | 124 +- templates/fr/archidxentry.html | 68 +- templates/fr/archidxfoot.html | 89 +- templates/fr/archidxhead.html | 88 +- templates/fr/archlistend.html | 64 +- templates/fr/archliststart.html | 75 +- templates/fr/archtoc.html | 75 +- templates/fr/archtocentry.html | 82 +- templates/fr/archtocnombox.html | 83 +- templates/fr/article.html | 82 +- templates/fr/emptyarchive.html | 79 +- templates/fr/handle_opts.html | 74 +- templates/fr/headfoot.html | 95 +- templates/fr/listinfo.html | 341 +- templates/fr/options.html | 601 +- templates/fr/private.html | 152 +- templates/fr/roster.html | 162 +- templates/fr/subscribe.html | 72 +- templates/gl/admindbdetails.html | 152 +- templates/gl/admindbpreamble.html | 75 +- templates/gl/admindbsummary.html | 87 +- templates/gl/admlogin.html | 126 +- templates/gl/archidxentry.html | 71 +- templates/gl/archidxfoot.html | 93 +- templates/gl/archidxhead.html | 96 +- templates/gl/archlistend.html | 64 +- templates/gl/archliststart.html | 71 +- templates/gl/archtoc.html | 85 +- templates/gl/archtocentry.html | 81 +- templates/gl/article.html | 76 +- templates/gl/emptyarchive.html | 89 +- templates/gl/handle_opts.html | 74 +- templates/gl/headfoot.html | 100 +- templates/gl/listinfo.html | 344 +- templates/gl/options.html | 674 +- templates/gl/private.html | 154 +- templates/gl/roster.html | 166 +- templates/gl/subscribe.html | 72 +- templates/he/admindbdetails.html | 70 +- templates/he/admindbpreamble.html | 65 +- templates/he/admindbsummary.html | 65 +- templates/he/admlogin.html | 110 +- templates/he/archidxentry.html | 71 +- templates/he/archidxfoot.html | 91 +- templates/he/archidxhead.html | 94 +- templates/he/archlistend.html | 64 +- templates/he/archliststart.html | 73 +- templates/he/archtoc.html | 83 +- templates/he/archtocentry.html | 81 +- templates/he/archtocnombox.html | 83 +- templates/he/article.html | 77 +- templates/he/emptyarchive.html | 83 +- templates/he/headfoot.html | 70 +- templates/he/listinfo.html | 320 +- templates/he/options.html | 440 +- templates/he/private.html | 142 +- templates/he/roster.html | 160 +- templates/he/subscribe.html | 72 +- templates/hr/admindbdetails.html | 140 +- templates/hr/admindbpreamble.html | 75 +- templates/hr/admindbsummary.html | 79 +- templates/hr/admlogin.html | 122 +- templates/hr/archidxentry.html | 71 +- templates/hr/archidxfoot.html | 93 +- templates/hr/archidxhead.html | 96 +- templates/hr/archlistend.html | 64 +- templates/hr/archliststart.html | 71 +- templates/hr/archtoc.html | 87 +- templates/hr/archtocentry.html | 81 +- templates/hr/article.html | 79 +- templates/hr/emptyarchive.html | 87 +- templates/hr/headfoot.html | 76 +- templates/hr/listinfo.html | 338 +- templates/hr/options.html | 552 +- templates/hr/private.html | 154 +- templates/hr/roster.html | 160 +- templates/hr/subscribe.html | 72 +- templates/hu/admindbdetails.html | 170 +- templates/hu/admindbpreamble.html | 81 +- templates/hu/admindbsummary.html | 85 +- templates/hu/admlogin.html | 113 +- templates/hu/archidxentry.html | 71 +- templates/hu/archidxfoot.html | 93 +- templates/hu/archidxhead.html | 96 +- templates/hu/archlistend.html | 64 +- templates/hu/archliststart.html | 71 +- templates/hu/archtoc.html | 87 +- templates/hu/archtocentry.html | 81 +- templates/hu/article.html | 78 +- templates/hu/emptyarchive.html | 87 +- templates/hu/headfoot.html | 92 +- templates/hu/illik.html | 2560 ++- templates/hu/listinfo.html | 336 +- templates/hu/options.html | 580 +- templates/hu/private.html | 150 +- templates/hu/roster.html | 160 +- templates/hu/subscribe.html | 74 +- templates/ia/admindbdetails.html | 68 +- templates/ia/admindbpreamble.html | 65 +- templates/ia/admindbsummary.html | 65 +- templates/ia/admlogin.html | 110 +- templates/ia/archidxentry.html | 71 +- templates/ia/archidxfoot.html | 91 +- templates/ia/archidxhead.html | 94 +- templates/ia/archlistend.html | 64 +- templates/ia/archliststart.html | 73 +- templates/ia/archtoc.html | 83 +- templates/ia/archtocentry.html | 81 +- templates/ia/archtocnombox.html | 83 +- templates/ia/article.html | 77 +- templates/ia/emptyarchive.html | 83 +- templates/ia/headfoot.html | 70 +- templates/ia/listinfo.html | 318 +- templates/ia/options.html | 442 +- templates/ia/private.html | 144 +- templates/ia/roster.html | 160 +- templates/ia/subscribe.html | 72 +- templates/it/admindbdetails.html | 100 +- templates/it/admindbpreamble.html | 67 +- templates/it/admindbsummary.html | 67 +- templates/it/admlogin.html | 110 +- templates/it/archidxentry.html | 71 +- templates/it/archidxfoot.html | 91 +- templates/it/archidxhead.html | 94 +- templates/it/archlistend.html | 64 +- templates/it/archliststart.html | 71 +- templates/it/archtoc.html | 83 +- templates/it/archtocentry.html | 81 +- templates/it/archtocnombox.html | 85 +- templates/it/article.html | 78 +- templates/it/emptyarchive.html | 83 +- templates/it/headfoot.html | 75 +- templates/it/listinfo.html | 332 +- templates/it/options.html | 468 +- templates/it/private.html | 142 +- templates/it/roster.html | 160 +- templates/it/subscribe.html | 72 +- templates/ja/admindbdetails.html | 180 +- templates/ja/admindbpreamble.html | 79 +- templates/ja/admindbsummary.html | 83 +- templates/ja/admlogin.html | 124 +- templates/ja/archidxentry.html | 71 +- templates/ja/archidxfoot.html | 95 +- templates/ja/archidxhead.html | 96 +- templates/ja/archlistend.html | 64 +- templates/ja/archliststart.html | 71 +- templates/ja/archtoc.html | 89 +- templates/ja/archtocentry.html | 81 +- templates/ja/archtocnombox.html | 85 +- templates/ja/article.html | 80 +- templates/ja/emptyarchive.html | 91 +- templates/ja/headfoot.html | 102 +- templates/ja/listinfo.html | 348 +- templates/ja/options.html | 591 +- templates/ja/private.html | 160 +- templates/ja/roster.html | 169 +- templates/ja/subscribe.html | 72 +- templates/ko/admindbdetails.html | 158 +- templates/ko/admindbpreamble.html | 77 +- templates/ko/admindbsummary.html | 81 +- templates/ko/admlogin.html | 120 +- templates/ko/article.html | 76 +- templates/ko/emptyarchive.html | 87 +- templates/ko/headfoot.html | 92 +- templates/ko/listinfo.html | 342 +- templates/ko/options.html | 580 +- templates/ko/private.html | 156 +- templates/ko/roster.html | 157 +- templates/ko/subscribe.html | 72 +- templates/lt/admindbdetails.html | 72 +- templates/lt/admindbpreamble.html | 77 +- templates/lt/admindbsummary.html | 79 +- templates/lt/admlogin.html | 120 +- templates/lt/archidxentry.html | 71 +- templates/lt/archidxfoot.html | 93 +- templates/lt/archidxhead.html | 96 +- templates/lt/archlistend.html | 64 +- templates/lt/archliststart.html | 73 +- templates/lt/archtoc.html | 87 +- templates/lt/archtocentry.html | 81 +- templates/lt/article.html | 77 +- templates/lt/emptyarchive.html | 87 +- templates/lt/headfoot.html | 80 +- templates/lt/listinfo.html | 337 +- templates/lt/options.html | 548 +- templates/lt/private.html | 146 +- templates/lt/roster.html | 161 +- templates/lt/subscribe.html | 72 +- templates/nl/admindbdetails.html | 73 +- templates/nl/admindbpreamble.html | 70 +- templates/nl/admindbsummary.html | 68 +- templates/nl/admlogin.html | 110 +- templates/nl/archidxentry.html | 71 +- templates/nl/archidxfoot.html | 91 +- templates/nl/archidxhead.html | 94 +- templates/nl/archlistend.html | 64 +- templates/nl/archliststart.html | 71 +- templates/nl/archtoc.html | 83 +- templates/nl/archtocentry.html | 81 +- templates/nl/archtocnombox.html | 83 +- templates/nl/article.html | 77 +- templates/nl/emptyarchive.html | 83 +- templates/nl/headfoot.html | 70 +- templates/nl/listinfo.html | 318 +- templates/nl/options.html | 462 +- templates/nl/private.html | 144 +- templates/nl/roster.html | 158 +- templates/nl/subscribe.html | 72 +- templates/no/admindbdetails.html | 138 +- templates/no/admindbpreamble.html | 73 +- templates/no/admindbsummary.html | 79 +- templates/no/admlogin.html | 118 +- templates/no/archidxfoot.html | 91 +- templates/no/archidxhead.html | 94 +- templates/no/archliststart.html | 71 +- templates/no/archtoc.html | 83 +- templates/no/archtocentry.html | 81 +- templates/no/archtocnombox.html | 83 +- templates/no/article.html | 77 +- templates/no/emptyarchive.html | 85 +- templates/no/headfoot.html | 76 +- templates/no/listinfo.html | 332 +- templates/no/options.html | 533 +- templates/no/private.html | 150 +- templates/no/roster.html | 160 +- templates/no/subscribe.html | 72 +- templates/pl/admlogin.html | 124 +- templates/pl/archidxentry.html | 71 +- templates/pl/archidxfoot.html | 87 +- templates/pl/archidxhead.html | 94 +- templates/pl/archlistend.html | 64 +- templates/pl/archliststart.html | 71 +- templates/pl/archtoc.html | 77 +- templates/pl/archtocentry.html | 76 +- templates/pl/archtocnombox.html | 77 +- templates/pl/article.html | 70 +- templates/pl/emptyarchive.html | 91 +- templates/pl/listinfo.html | 334 +- templates/pl/options.html | 598 +- templates/pl/private.html | 156 +- templates/pl/roster.html | 161 +- templates/pl/subscribe.html | 76 +- templates/pt/admindbdetails.html | 151 +- templates/pt/admindbpreamble.html | 75 +- templates/pt/admindbsummary.html | 79 +- templates/pt/admlogin.html | 122 +- templates/pt/archidxentry.html | 71 +- templates/pt/archidxfoot.html | 93 +- templates/pt/archidxhead.html | 96 +- templates/pt/archlistend.html | 64 +- templates/pt/archliststart.html | 71 +- templates/pt/archtoc.html | 87 +- templates/pt/archtocentry.html | 81 +- templates/pt/article.html | 77 +- templates/pt/emptyarchive.html | 89 +- templates/pt/headfoot.html | 92 +- templates/pt/listinfo.html | 336 +- templates/pt/options.html | 560 +- templates/pt/private.html | 155 +- templates/pt/roster.html | 162 +- templates/pt/subscribe.html | 72 +- templates/pt_BR/admindbdetails.html | 162 +- templates/pt_BR/admindbpreamble.html | 79 +- templates/pt_BR/admindbsummary.html | 83 +- templates/pt_BR/admlogin.html | 124 +- templates/pt_BR/archidxentry.html | 71 +- templates/pt_BR/archidxfoot.html | 93 +- templates/pt_BR/archidxhead.html | 96 +- templates/pt_BR/archlistend.html | 64 +- templates/pt_BR/archliststart.html | 71 +- templates/pt_BR/archtoc.html | 87 +- templates/pt_BR/archtocentry.html | 81 +- templates/pt_BR/article.html | 79 +- templates/pt_BR/emptyarchive.html | 87 +- templates/pt_BR/headfoot.html | 96 +- templates/pt_BR/listinfo.html | 335 +- templates/pt_BR/options.html | 604 +- templates/pt_BR/private.html | 158 +- templates/pt_BR/roster.html | 160 +- templates/pt_BR/subscribe.html | 72 +- templates/ro/admindbdetails.html | 108 +- templates/ro/admindbpreamble.html | 71 +- templates/ro/admindbsummary.html | 73 +- templates/ro/admlogin.html | 116 +- templates/ro/archidxentry.html | 71 +- templates/ro/archidxfoot.html | 91 +- templates/ro/archidxhead.html | 96 +- templates/ro/archlistend.html | 64 +- templates/ro/archliststart.html | 71 +- templates/ro/archtoc.html | 87 +- templates/ro/archtocentry.html | 81 +- templates/ro/article.html | 77 +- templates/ro/emptyarchive.html | 89 +- templates/ro/headfoot.html | 72 +- templates/ro/listinfo.html | 329 +- templates/ro/options.html | 492 +- templates/ro/private.html | 150 +- templates/ro/roster.html | 164 +- templates/ro/subscribe.html | 74 +- templates/ru/admindbdetails.html | 76 +- templates/ru/admindbpreamble.html | 65 +- templates/ru/admindbsummary.html | 65 +- templates/ru/admlogin.html | 102 +- templates/ru/archidxentry.html | 65 +- templates/ru/archidxfoot.html | 91 +- templates/ru/archidxhead.html | 94 +- templates/ru/archlistend.html | 64 +- templates/ru/archliststart.html | 71 +- templates/ru/archtoc.html | 83 +- templates/ru/archtocentry.html | 81 +- templates/ru/archtocnombox.html | 83 +- templates/ru/article.html | 76 +- templates/ru/emptyarchive.html | 83 +- templates/ru/headfoot.html | 81 +- templates/ru/listinfo.html | 302 +- templates/ru/options.html | 442 +- templates/ru/private.html | 138 +- templates/ru/roster.html | 152 +- templates/ru/subscribe.html | 72 +- templates/sk/admindbdetails.html | 168 +- templates/sk/admindbpreamble.html | 87 +- templates/sk/admindbsummary.html | 91 +- templates/sk/admlogin.html | 122 +- templates/sk/archidxentry.html | 71 +- templates/sk/archidxfoot.html | 93 +- templates/sk/archidxhead.html | 96 +- templates/sk/archlistend.html | 64 +- templates/sk/archliststart.html | 71 +- templates/sk/archtoc.html | 87 +- templates/sk/archtocentry.html | 81 +- templates/sk/archtocnombox.html | 85 +- templates/sk/article.html | 76 +- templates/sk/emptyarchive.html | 89 +- templates/sk/headfoot.html | 96 +- templates/sk/listinfo.html | 350 +- templates/sk/options.html | 615 +- templates/sk/private.html | 156 +- templates/sk/roster.html | 162 +- templates/sk/subscribe.html | 72 +- templates/sl/admindbdetails.html | 142 +- templates/sl/admindbpreamble.html | 73 +- templates/sl/admindbsummary.html | 77 +- templates/sl/admlogin.html | 118 +- templates/sl/archidxentry.html | 71 +- templates/sl/archidxfoot.html | 91 +- templates/sl/archidxhead.html | 94 +- templates/sl/archlistend.html | 64 +- templates/sl/archliststart.html | 71 +- templates/sl/archtoc.html | 83 +- templates/sl/archtocentry.html | 81 +- templates/sl/article.html | 77 +- templates/sl/emptyarchive.html | 85 +- templates/sl/headfoot.html | 84 +- templates/sl/listinfo.html | 336 +- templates/sl/options.html | 554 +- templates/sl/private.html | 158 +- templates/sl/roster.html | 162 +- templates/sl/subscribe.html | 72 +- templates/sr/admindbdetails.html | 85 +- templates/sr/admindbpreamble.html | 65 +- templates/sr/admindbsummary.html | 65 +- templates/sr/admlogin.html | 115 +- templates/sr/archidxentry.html | 71 +- templates/sr/archidxfoot.html | 91 +- templates/sr/archidxhead.html | 96 +- templates/sr/archlistend.html | 64 +- templates/sr/archliststart.html | 75 +- templates/sr/archtoc.html | 83 +- templates/sr/archtocentry.html | 74 +- templates/sr/article.html | 61 +- templates/sr/emptyarchive.html | 83 +- templates/sr/handle_opts.html | 76 +- templates/sr/headfoot.html | 68 +- templates/sr/listinfo.html | 300 +- templates/sr/options.html | 467 +- templates/sr/private.html | 151 +- templates/sr/roster.html | 160 +- templates/sr/subscribe.html | 74 +- templates/sv/admindbdetails.html | 98 +- templates/sv/admindbpreamble.html | 69 +- templates/sv/admindbsummary.html | 68 +- templates/sv/admlogin.html | 108 +- templates/sv/archtoc.html | 87 +- templates/sv/archtocentry.html | 81 +- templates/sv/article.html | 80 +- templates/sv/emptyarchive.html | 83 +- templates/sv/headfoot.html | 76 +- templates/sv/listinfo.html | 308 +- templates/sv/options.html | 495 +- templates/sv/private.html | 144 +- templates/sv/roster.html | 152 +- templates/sv/subscribe.html | 72 +- templates/tr/admindbdetails.html | 167 +- templates/tr/admindbpreamble.html | 77 +- templates/tr/admindbsummary.html | 83 +- templates/tr/admlogin.html | 129 +- templates/tr/archidxentry.html | 71 +- templates/tr/archidxfoot.html | 92 +- templates/tr/archidxhead.html | 95 +- templates/tr/archlistend.html | 64 +- templates/tr/archliststart.html | 74 +- templates/tr/archtoc.html | 88 +- templates/tr/archtocentry.html | 82 +- templates/tr/archtocnombox.html | 86 +- templates/tr/article.html | 78 +- templates/tr/emptyarchive.html | 88 +- templates/tr/headfoot.html | 99 +- templates/tr/listinfo.html | 343 +- templates/tr/options.html | 613 +- templates/tr/private.html | 163 +- templates/tr/roster.html | 163 +- templates/tr/subscribe.html | 72 +- templates/uk/admindbdetails.html | 69 +- templates/uk/admindbpreamble.html | 65 +- templates/uk/admindbsummary.html | 65 +- templates/uk/admlogin.html | 108 +- templates/uk/archidxentry.html | 71 +- templates/uk/archidxfoot.html | 91 +- templates/uk/archidxhead.html | 94 +- templates/uk/archlistend.html | 64 +- templates/uk/archliststart.html | 71 +- templates/uk/archtoc.html | 83 +- templates/uk/archtocentry.html | 81 +- templates/uk/archtocnombox.html | 83 +- templates/uk/article.html | 77 +- templates/uk/emptyarchive.html | 85 +- templates/uk/headfoot.html | 70 +- templates/uk/listinfo.html | 318 +- templates/uk/options.html | 436 +- templates/uk/private.html | 140 +- templates/uk/roster.html | 162 +- templates/uk/subscribe.html | 72 +- templates/vi/admindbdetails.html | 77 +- templates/vi/admindbpreamble.html | 65 +- templates/vi/admindbsummary.html | 65 +- templates/vi/admlogin.html | 110 +- templates/vi/archidxentry.html | 71 +- templates/vi/archidxfoot.html | 91 +- templates/vi/archidxhead.html | 94 +- templates/vi/archlistend.html | 64 +- templates/vi/archliststart.html | 71 +- templates/vi/archtoc.html | 83 +- templates/vi/archtocentry.html | 81 +- templates/vi/archtocnombox.html | 83 +- templates/vi/article.html | 77 +- templates/vi/emptyarchive.html | 83 +- templates/vi/headfoot.html | 82 +- templates/vi/listinfo.html | 316 +- templates/vi/options.html | 434 +- templates/vi/private.html | 142 +- templates/vi/roster.html | 156 +- templates/vi/subscribe.html | 72 +- templates/zh_CN/admindbdetails.html | 68 +- templates/zh_CN/admindbpreamble.html | 65 +- templates/zh_CN/admindbsummary.html | 65 +- templates/zh_CN/admlogin.html | 106 +- templates/zh_CN/archidxentry.html | 71 +- templates/zh_CN/archidxfoot.html | 91 +- templates/zh_CN/archidxhead.html | 94 +- templates/zh_CN/archlistend.html | 64 +- templates/zh_CN/archliststart.html | 71 +- templates/zh_CN/archtoc.html | 83 +- templates/zh_CN/archtocentry.html | 81 +- templates/zh_CN/archtocnombox.html | 83 +- templates/zh_CN/article.html | 77 +- templates/zh_CN/emptyarchive.html | 83 +- templates/zh_CN/headfoot.html | 70 +- templates/zh_CN/listinfo.html | 318 +- templates/zh_CN/options.html | 444 +- templates/zh_CN/private.html | 140 +- templates/zh_CN/roster.html | 158 +- templates/zh_CN/subscribe.html | 72 +- templates/zh_TW/admindbpreamble.html | 68 +- templates/zh_TW/admlogin.html | 104 +- templates/zh_TW/handle_opts.html | 74 +- templates/zh_TW/headfoot.html | 70 +- templates/zh_TW/listinfo.html | 309 +- templates/zh_TW/options.html | 273 +- templates/zh_TW/roster.html | 158 +- templates/zh_TW/subscribe.html | 72 +- tests/onebounce.py | 44 +- tests/test_handlers.py | 2566 ++- tests/test_lockfile.py | 138 +- tests/test_message.py | 6 +- 1140 files changed, 56906 insertions(+), 115419 deletions(-) rename Mailman/{__init__.py => __init__.py.in} (93%) mode change 100755 => 100644 bin/list_lists mode change 100755 => 100644 bin/mailmanctl create mode 100755 scripts/convert_to_utf8 diff --git a/Mailman/Archiver/Archiver.py b/Mailman/Archiver/Archiver.py index 2246093c..471b551e 100644 --- a/Mailman/Archiver/Archiver.py +++ b/Mailman/Archiver/Archiver.py @@ -29,7 +29,7 @@ import errno import traceback import re -from io import StringIO +import tempfile from Mailman import mm_cfg from Mailman import Mailbox @@ -88,17 +88,21 @@ def InitVars(self): # symbolic links. omask = os.umask(0) try: - # Create mbox directory with proper permissions - mbox_dir = self.archive_dir() + '.mbox' - os.makedirs(mbox_dir, mode=0o02775, exist_ok=True) - - # Create archive directory with proper permissions - archive_dir = self.archive_dir() - os.makedirs(archive_dir, mode=0o02775, exist_ok=True) - + try: + os.mkdir(self.archive_dir()+'.mbox', 0o02775) + except OSError as e: + if e.errno != errno.EEXIST: raise + # We also create an empty pipermail archive directory into + # which we'll drop an empty index.html file into. This is so + # that lists that have not yet received a posting have + # /something/ as their index.html, and don't just get a 404. + try: + os.mkdir(self.archive_dir(), 0o02775) + except OSError as e: + if e.errno != errno.EEXIST: raise # See if there's an index.html file there already and if not, # write in the empty archive notice. - indexfile = os.path.join(archive_dir, 'index.html') + indexfile = os.path.join(self.archive_dir(), 'index.html') fp = None try: fp = open(indexfile) @@ -132,7 +136,8 @@ def GetBaseArchiveURL(self): if self.archive_private: return url else: - hostname = re.match(r'[^:]*://([^/]*)/.*', url, re.IGNORECASE).group(1) + hostname = re.match('[^:]*://([^/]*)/.*', url).group(1)\ + or mm_cfg.DEFAULT_URL_HOST url = mm_cfg.PUBLIC_ARCHIVE_URL % { 'listname': self.internal_name(), 'hostname': hostname @@ -145,7 +150,7 @@ def __archive_file(self, afn): """Open (creating, if necessary) the named archive file.""" omask = os.umask(0o002) try: - return Mailbox.Mailbox(open(afn, 'a+')) + return Mailbox.Mailbox(open(afn, 'a+b')) finally: os.umask(omask) @@ -157,9 +162,11 @@ def __archive_to_mbox(self, post): """Retain a text copy of the message in an mbox file.""" try: afn = self.ArchiveFileName() + syslog('debug', 'Archiver: Writing to mbox file: %s', afn) mbox = self.__archive_file(afn) mbox.AppendMessage(post) - mbox.fp.close() + mbox.close() + syslog('debug', 'Archiver: Successfully wrote message to mbox file: %s', afn) except IOError as msg: syslog('error', 'Archive file access failure:\n\t%s %s', afn, msg) raise @@ -169,55 +176,68 @@ def ExternalArchive(self, ar, txt): 'hostname': self.host_name, }) cmd = ar % d - try: - with os.popen(cmd, 'w') as extarch: - extarch.write(txt) - except OSError as e: - syslog('error', 'Failed to execute external archiver: %s\nError: %s', - cmd, str(e)) - return + extarch = os.popen(cmd, 'w') + extarch.write(txt) status = extarch.close() if status: - syslog('error', 'External archiver non-zero exit status: %d\nCommand: %s', - (status & 0xff00) >> 8, cmd) + syslog('error', 'external archiver non-zero exit status: %d\n', + (status & 0xff00) >> 8) # # archiving in real time this is called from list.post(msg) # def ArchiveMail(self, msg): """Store postings in mbox and/or pipermail archive, depending.""" + from Mailman.Logging.Syslog import syslog + syslog('debug', 'Archiver: Starting ArchiveMail for list %s', self.internal_name()) + # Fork so archival errors won't disrupt normal list delivery if mm_cfg.ARCHIVE_TO_MBOX == -1: + syslog('debug', 'Archiver: ARCHIVE_TO_MBOX is -1, archiving disabled') return + + syslog('debug', 'Archiver: ARCHIVE_TO_MBOX = %s', mm_cfg.ARCHIVE_TO_MBOX) # # We don't need an extra archiver lock here because we know the list # itself must be locked. if mm_cfg.ARCHIVE_TO_MBOX in (1, 2): - try: - mbox = self.__archive_file(self.ArchiveFileName()) - mbox.AppendMessage(msg) - mbox.fp.close() - except IOError as msg: - syslog('error', 'Archive file access failure:\n\t%s %s', - self.ArchiveFileName(), msg) - raise + syslog('debug', 'Archiver: Writing to mbox archive') + self.__archive_to_mbox(msg) if mm_cfg.ARCHIVE_TO_MBOX == 1: # Archive to mbox only. + syslog('debug', 'Archiver: ARCHIVE_TO_MBOX = 1, mbox only, returning') return - txt = str(msg) + + txt = msg.as_string() + unixfrom = msg.get_unixfrom() + # Handle case where unixfrom is None (Python 3 compatibility) + if unixfrom and not txt.startswith(unixfrom): + txt = unixfrom + '\n' + txt + # should we use the internal or external archiver? private_p = self.archive_private + syslog('debug', 'Archiver: archive_private = %s', private_p) + if mm_cfg.PUBLIC_EXTERNAL_ARCHIVER and not private_p: + syslog('debug', 'Archiver: Using public external archiver') self.ExternalArchive(mm_cfg.PUBLIC_EXTERNAL_ARCHIVER, txt) elif mm_cfg.PRIVATE_EXTERNAL_ARCHIVER and private_p: + syslog('debug', 'Archiver: Using private external archiver') self.ExternalArchive(mm_cfg.PRIVATE_EXTERNAL_ARCHIVER, txt) else: # use the internal archiver - with StringIO(txt) as f: - from . import HyperArch - h = HyperArch.HyperArchive(self) - h.processUnixMailbox(f) - h.close() + syslog('debug', 'Archiver: Using internal HyperArch archiver') + f = tempfile.NamedTemporaryFile() + if isinstance(txt, str): + txt = txt.encode('utf-8') + f.write(txt) + f.flush() + from . import HyperArch + h = HyperArch.HyperArchive(self) + h.processUnixMailbox(f) + h.close() + f.close() + syslog('debug', 'Archiver: Completed internal archiving') # # called from MailList.MailList.Save() diff --git a/Mailman/Archiver/HyperArch.py b/Mailman/Archiver/HyperArch.py index 199d7915..f88432b6 100644 --- a/Mailman/Archiver/HyperArch.py +++ b/Mailman/Archiver/HyperArch.py @@ -37,14 +37,11 @@ from . import pipermail import weakref import binascii -from io import StringIO, BytesIO -import pickle from email.header import decode_header, make_header from email.errors import HeaderParseError from email.charset import Charset -from email import message_from_file -from email.generator import Generator +from functools import cmp_to_key from Mailman import mm_cfg from Mailman import Utils @@ -90,6 +87,7 @@ resource.setrlimit(resource.RLIMIT_STACK, (newsoft, hard)) + def html_quote(s, lang=None): repls = ( ('&', '&'), ("<", '<'), @@ -97,7 +95,7 @@ def html_quote(s, lang=None): ('"', '"')) for thing, repl in repls: s = s.replace(thing, repl) - return Utils.uncanonstr(s, lang) + return s def url_quote(s): @@ -139,7 +137,7 @@ def CGIescape(arg, lang=None): s = Utils.websafe(arg) else: s = Utils.websafe(str(arg)) - return Utils.uncanonstr(s.replace('"', '"'), lang) + return s.replace('"', '"') # Parenthesized human name paren_name_pat = re.compile(r'([(].*[)])') @@ -169,6 +167,7 @@ def CGIescape(arg, lang=None): quotedpat = re.compile(r'^([>|:]|>)+') + # Like Utils.maketext() but with caching to improve performance. # # _templatefilepathcache is used to associate a (templatefile, lang, listname) @@ -225,9 +224,10 @@ def quick_maketext(templatefile, dict=None, lang=None, mlist=None): syslog('error', 'broken template: %s\n%s', filepath, e) # Make sure the text is in the given character set, or html-ify any bogus # characters. - return Utils.uncanonstr(text, lang) + return text + # Note: I'm overriding most, if not all of the pipermail Article class # here -ddm # The Article class encapsulates a single posting. The attributes are: @@ -252,8 +252,8 @@ class Article(pipermail.Article): _last_article_time = time.time() - def __init__(self, message, sequence, keepHeaders=0, - lang=mm_cfg.DEFAULT_SERVER_LANGUAGE, mlist=None): + def __init__(self, message=None, sequence=0, keepHeaders=[], + lang=mm_cfg.DEFAULT_SERVER_LANGUAGE, mlist=None): self.__super_init(message, sequence, keepHeaders) self.prev = None self.next = None @@ -282,14 +282,15 @@ def __init__(self, message, sequence, keepHeaders=0, i18n.set_language(lang) if self.author == self.email: self.author = self.email = re.sub('@', _(' at '), - self.email, flags=re.IGNORECASE) + self.email) else: - self.email = re.sub('@', _(' at '), self.email, flags=re.IGNORECASE) + self.email = re.sub('@', _(' at '), self.email) finally: i18n.set_translation(otrans) - # Get content type and encoding - ctype = message.get_content_type() + # Snag the content-* headers. RFC 1521 states that their values are + # case insensitive. + ctype = message.get('Content-Type', 'text/plain') cenc = message.get('Content-Transfer-Encoding', '') self.ctype = ctype.lower() self.cenc = cenc.lower() @@ -298,8 +299,8 @@ def __init__(self, message, sequence, keepHeaders=0, cset_out = Charset(cset).output_charset or cset if isinstance(cset_out, str): # email 3.0.1 (python 2.4) doesn't like unicode - cset_out = cset_out.encode('us-ascii') - charset = message.get_content_charset() + cset_out = cset_out.encode('us-ascii', 'replace') + charset = message.get_content_charset(cset_out) if charset: charset = charset.lower().strip() if charset[0]=='"' and charset[-1]=='"': @@ -307,16 +308,21 @@ def __init__(self, message, sequence, keepHeaders=0, if charset[0]=="'" and charset[-1]=="'": charset = charset[1:-1] try: - body = message.get_body().get_content() - except (binascii.Error, AttributeError): + body = message.get_payload(decode=True) + except binascii.Error: body = None if body and charset != Utils.GetCharSet(self._lang): + if isinstance(charset, bytes): + charset = charset.decode('utf-8', 'replace') # decode body try: - body = str(body, charset) + body = body.decode(charset) except (UnicodeError, LookupError): body = None if body: + # Handle both bytes and strings properly + if isinstance(body, bytes): + body = body.decode('utf-8', 'replace') self.body = [l + "\n" for l in body.splitlines()] self.decode_headers() @@ -414,9 +420,9 @@ def decode_headers(self): otrans = i18n.get_translation() try: i18n.set_language(self._lang) - atmark = str(_(' at '), Utils.GetCharSet(self._lang)) + atmark = _(' at ') subject = re.sub(r'([-+,.\w]+)@([-+.\w]+)', - r'\g<1>' + atmark + r'\g<2>', subject, flags=re.IGNORECASE) + r'\g<1>' + atmark + r'\g<2>', subject) finally: i18n.set_translation(otrans) self.decoded['subject'] = subject @@ -429,31 +435,29 @@ def strip_subject(self, subject): if prefix: prefix_pat = re.escape(prefix) prefix_pat = '%'.join(prefix_pat.split(r'\%')) - prefix_pat = re.sub(r'%\d*d', r'\s*\d+\s*', prefix_pat, flags=re.IGNORECASE) - subject = re.sub(prefix_pat, '', subject, flags=re.IGNORECASE) + prefix_pat = re.sub(r'%\d*d', r'\\\\s*\\\\d+\\\\s*', prefix_pat) + subject = re.sub(prefix_pat, '', subject) subject = subject.lstrip() # MAS Should we strip FW and FWD too? strip_pat = re.compile(r'^((RE|AW|SV|VS)(\[\d+\])?:\s*)+', re.I) stripped = strip_pat.sub('', subject) # Also remove whitespace to avoid folding/unfolding differences - stripped = re.sub(r'\s', '', stripped, flags=re.IGNORECASE) + stripped = re.sub(r'\s', '', stripped) return stripped def decode_charset(self, field): # TK: This function was rewritten for unifying to Unicode. # Convert 'field' into Unicode one line string. try: - if isinstance(field, str): - return field pairs = decode_header(field) - ustr = str(make_header(pairs)) + ustr = make_header(pairs).__str__() except (LookupError, UnicodeError, ValueError, HeaderParseError): # assume list's language cset = Utils.GetCharSet(self._mlist.preferred_language) if cset == 'us-ascii': cset = 'iso-8859-1' # assume this for English list ustr = str(field, cset, 'replace') - return ''.join(ustr.splitlines()) + return u''.join(ustr.splitlines()) def as_html(self): d = self.__dict__.copy() @@ -474,7 +478,7 @@ def as_html(self): d["in_reply_to_url"] = url_quote(self._message_id) if mm_cfg.ARCHIVER_OBSCURES_EMAILADDRS: # Point the mailto url back to the list - author = re.sub('@', _(' at '), self.author, flags=re.IGNORECASE) + author = re.sub('@', _(' at '), self.author) emailurl = self._mlist.GetListEmail() else: author = self.author @@ -521,8 +525,8 @@ def _get_subject_enc(self, art): def _get_next(self): """Return the href and subject for the previous message""" - if self.__next__: - subject = self._get_subject_enc(self.__next__) + if hasattr( self, 'next' ) and self.next is not None: + subject = self._get_subject_enc(self.next) next = ('' % (url_quote(self.next.filename))) next_wsubj = ('

  • ' + _('Next message (by thread):') + @@ -537,23 +541,13 @@ def _get_next(self): _rx_softline = re.compile('=[ \t]*$') def _get_body(self): - """Return the message body as HTML.""" - if not self.body: - return '' - # Convert the body to HTML - body = [] - for line in self.body: - # Handle HTML content - if self.ctype == 'text/html': - body.append(line) - else: - # Convert plain text to HTML - line = self.quote(line) - if self.SHOWBR: - body.append(line + '
    \n') - else: - body.append(line + '\n') - return ''.join(body) + """Return the message body ready for HTML, decoded if necessary""" + try: + body = self.html_body + except AttributeError: + body = self.body + + return null_to_space(EMPTYSTRING.join(body)) def _add_decoded(self, d): """Add encoded-word keys to HTML output""" @@ -565,30 +559,48 @@ def _add_decoded(self, d): d[dst] = self.quote(self.decoded[src]) def as_text(self): - """Return the message as plain text.""" - if not self.body: - return '' - # Convert the body to plain text - body = [] - for line in self.body: - # Handle HTML content - if self.ctype == 'text/html': - # Strip HTML tags - line = re.sub(r'<[^>]*>', '', line) - body.append(line) - return ''.join(body) + d = self.__dict__.copy() + # We need to guarantee a valid From_ line, even if there are + # bososities in the headers. + if not d.get('fromdate', '').strip(): + d['fromdate'] = time.ctime(time.time()) + if not d.get('email', '').strip(): + d['email'] = 'bogus@does.not.exist.com' + if not d.get('datestr', '').strip(): + d['datestr'] = time.ctime(time.time()) + # + headers = ['From %(email)s %(fromdate)s', + 'From: %(email)s (%(author)s)', + 'Date: %(datestr)s', + 'Subject: %(subject)s'] + if d['_in_reply_to']: + headers.append('In-Reply-To: %(_in_reply_to)s') + if d['_references']: + headers.append('References: %(_references)s') + if d['_message_id']: + headers.append('Message-ID: %(_message_id)s') + body = EMPTYSTRING.join(self.body) + cset = Utils.GetCharSet(self._lang) + # Coerce the body to Unicode and replace any invalid characters. + if not isinstance(body, str): + body = str(body, cset, 'replace') + if mm_cfg.ARCHIVER_OBSCURES_EMAILADDRS: + otrans = i18n.get_translation() + try: + i18n.set_language(self._lang) + atmark = _(' at ') + if isinstance(atmark, bytes): + atmark = str(atmark, cset) + body = re.sub(r'([-+,.\w]+)@([-+.\w]+)', + r'\g<1>' + atmark + r'\g<2>', body) + finally: + i18n.set_translation(otrans) + + return NL.join(headers) % d + '\n\n' + body + '\n' def _set_date(self, message): - """Set the date from the message.""" - try: - date = message.get('Date') - if date: - self.date = time.mktime(email.utils.parsedate_tz(date)[:9]) - else: - self.date = time.time() - except (TypeError, ValueError): - self.date = time.time() - self.datestr = time.ctime(self.date) + self.__super_set_date(message) + self.fromdate = time.ctime(int(self.date)) def loadbody_fromHTML(self,fileobj): self.body = [] @@ -612,7 +624,7 @@ def finished_update_article(self): except AttributeError: pass - + class HyperArchive(pipermail.T): __super_init = pipermail.T.__init__ __super_update_archive = pipermail.T.update_archive @@ -641,49 +653,14 @@ def __init__(self, maillist): # with mailman's LockFile module for HyperDatabase.HyperDatabase # dir = maillist.archive_dir() - self.basedir = dir # Set basedir first - self.database = HyperDatabase.HyperDatabase(dir, maillist) - - # Initialize basic attributes first - self.archives = [] # Archives - self._dirty_archives = [] # Archives that will have to be updated - self.sequence = 0 # Sequence variable used for numbering articles - self.update_TOC = 0 # Does the TOC need updating? + db = HyperDatabase.HyperDatabase(dir, maillist) + self.__super_init(dir, reload=1, database=db) + self.maillist = maillist self._lock_file = None self.lang = maillist.preferred_language self.charset = Utils.GetCharSet(maillist.preferred_language) - # Try to load previously pickled state - try: - f = open(os.path.join(self.basedir, 'pipermail.pck'), 'rb') - self.message(C_('Reloading pickled archive state')) - try: - # Try UTF-8 first for newer files - d = pickle.load(f, fix_imports=True, encoding='utf-8') - except (UnicodeDecodeError, pickle.UnpicklingError): - # Fall back to latin1 for older files - f.seek(0) - d = pickle.load(f, fix_imports=True, encoding='latin1') - f.close() - - if isinstance(d, bytes): - # If we got bytes, try to unpickle it - d = pickle.loads(d, fix_imports=True, encoding='latin1') - - # Only update attributes that don't conflict with our initialization - safe_attrs = { - 'type', 'archive', 'firstdate', 'lastdate', 'archivedate', - 'size', 'version', 'subjectIndex', 'authorIndex', 'dateIndex', - 'articleIndex', 'threadIndex' - } - for key, value in list(d.items()): - if key in safe_attrs: - setattr(self, key, value) - except (IOError, EOFError, pickle.UnpicklingError, RecursionError) as e: - syslog('error', 'Error loading archive state: %s', e) - # Continue with default initialization - if hasattr(self.maillist,'archive_volume_frequency'): if self.maillist.archive_volume_frequency == 0: self.ARCHIVE_PERIOD='year' @@ -840,12 +817,12 @@ def html_TOC_entry(self, arch): if os.path.exists(gzfile): file = gzfile url = arch + '.txt.gz' - templ = '
  • [ ' + _('Gzip\'d Text%(sz)s') \ + templ = '[ ' + _('Gzip\'d Text%(sz)s') \ + '][ ' + _('Text%(sz)s') + '][ ' + _('Text%(sz)s') + ']
    %s
    %s
    %s
    %s
    %s
    %s
      + """
     
    ' + self.opts['aria-label'] + '
    - - - - - - - - - - -
    -التحقق من الشخصية لـ %(who)s للقائمة %(listname)s -
    كلمة سر %(who)s القائمة:
    -
    -

    هام: من هذا النقطة يجب أن تكون + + + + + + + + + + + +
    + التحقق من الشخصية لـ %(who)s للقائمة %(listname)s +
    كلمة سر %(who)s القائمة:
    +
    +

    هام: من هذا النقطة يجب أن تكون الكوكيز Ù…ÙØ¹Ù„Ø© ÙÙŠ برنامج الاستعراض الذي تستخدمه، وإلا ÙØ¥Ù†Ù‡ لن يتم حصول أي تغييرات إشراÙية. @@ -94,6 +34,6 @@ صلاحيتها عندما تغلق مستعرضك، أو يمكنك إنهاء صلاحية الكوكي بشكل صريح بالضغط على الارتباط تسجيل الخروج تحت أعمال إشراÙية أخرى (والتي ستشاهدها عندما تسجل الدخول بنجاح). -

    + diff --git a/templates/ar/archidxentry.html b/templates/ar/archidxentry.html index 2efd9486..f9bb57aa 100644 --- a/templates/ar/archidxentry.html +++ b/templates/ar/archidxentry.html @@ -1,67 +1,4 @@ -
  • %(subject)s -  -%(author)s - -
  • \ No newline at end of file +
  • %(subject)s +  +%(author)s + diff --git a/templates/ar/archidxfoot.html b/templates/ar/archidxfoot.html index 7a889266..d4fef19a 100644 --- a/templates/ar/archidxfoot.html +++ b/templates/ar/archidxfoot.html @@ -1,84 +1,21 @@ - -

    -تاريخ آخر رسالة -%(lastdate)s
    -تمت Ø£Ø±Ø´ÙØªÙ‡Ø§ ÙÙŠ: %(archivedate)s -

    -

      -
    • الرسائل مرتبة حسب: +
    +

    + تاريخ آخر رسالة + %(lastdate)s
    + تمت Ø£Ø±Ø´ÙØªÙ‡Ø§ ÙÙŠ: %(archivedate)s +

    +

    -

    -


    -تم توليد هذا الأرشي٠من قبل + +

    +


    + تم توليد هذا الأرشي٠من قبل Pipermail %(version)s. - - -

    \ No newline at end of file + + diff --git a/templates/ar/archidxhead.html b/templates/ar/archidxhead.html index 77d28452..8d268bfc 100644 --- a/templates/ar/archidxhead.html +++ b/templates/ar/archidxhead.html @@ -1,79 +1,16 @@ - - -أرشي٠%(archive)s الخاص بالقائمة %(listname)s حسب %(archtype)s - - + + أرشي٠%(archive)s الخاص بالقائمة %(listname)s حسب %(archtype)s + + %(encoding)s - - - -

    Ø£Ø±Ø´ÙŠÙØ§Øª %(archive)s حسب %(archtype)s

    -
      -
    • الرسائل مرتبة حسب: + + + +

      Ø£Ø±Ø´ÙŠÙØ§Øª %(archive)s حسب %(archtype)s

      + -

      بداية من: %(firstdate)s
      -انتهاءاً ÙÙŠ: %(lastdate)s
      -الرسائل: %(size)s

      -

        -

      \ No newline at end of file +
    +

    بداية من: %(firstdate)s
    + انتهاءاً ÙÙŠ: %(lastdate)s
    + الرسائل: %(size)s

    +

    diff --git a/templates/ro/listinfo.html b/templates/ro/listinfo.html index 4a549828..cb90aa7d 100644 --- a/templates/ro/listinfo.html +++ b/templates/ro/listinfo.html @@ -1,142 +1,203 @@ + - - - <MM-List-Name> - Informaţii Generale - - - -

    - - - - - - - - - - - - - - - - - - - + + + + + +

    - -- -
    -

      -

    - Lista - - - -
    -

    -

    Pentru a accesa colecţia mesajelor publicate anterior - pe listă, vizitaţi Arhivele . - -

    -
    - Folosirea listei -
    + + +<mm-list-name> - Informaţii Generale</mm-list-name> + + + +

    + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + -- +
    +

      +

    +Lista + + + +
    +

    +

    Pentru a accesa colecţia mesajelor publicate anterior + pe listă, vizitaţi Arhivele . + +

    +
    +Folosirea listei +
    Pentru a trimite un mesaj către listă, trimiteţi un email - la adresa - -

    În secÅ£iunile următoare vă puteÅ£i abona la această listă, + la adresa +

    În secţiunile următoare vă puteţi abona la această listă, sau vă puteţi modifica datele existente de abonament. -

    - Abonarea la -
    -

    - Vă puteÅ£i abona la lista de discuÅ£ii completând formularul +

    +Abonarea la +
    +

    + Vă puteţi abona la lista de discuţii completând formularul de mai jos. - -

      - - - - - - - - - - - - - - - - - -
      Adresa de email: -  
      Numele (opÅ£ional): 
      Puteţi introduce o parolă mai jos. + +
        + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - -
        Adresa de email: + 
        Numele (opţional): 
        PuteÅ£i introduce o parolă mai jos. Aceasta nu va asigura o protecÅ£ie extraordinară, dar ar trebui - să prevină intervenÅ£ia altora în datele dumneavoastră de abonament. - Nu folosiÅ£i parole valoroase atâta timp cât acestea vă vor fi - retrimise ocazional în text clar. + să prevină intervenÅ£ia altora în datele dumneavoastră de abonament. + Nu folosiÅ£i parole valoroase atâta timp cât acestea vă vor fi + retrimise ocazional în text clar. -

        Dacă nu introduceÅ£i nici o parolă, va fi generată automat una - pentru dumneavoastră, ÅŸi vă va fi trimisă de îndată ce veÅ£i confirma - abonarea la listă. PuteÅ£i oricând cere re-trimiterea parolei prin email +

        Dacă nu introduceţi nici o parolă, va fi generată automat una + pentru dumneavoastră, şi vă va fi trimisă de îndată ce veţi confirma + abonarea la listă. Puteţi oricând cere re-trimiterea parolei prin email la editarea preferinţelor personale. - -
        -
        AlegeÅ£i o parolă: 
        ReintroduceÅ£i parola pentru confirmare: 
        Limba preferată:  
        DoriÅ£i mesajele listei într-un rezumat zilnic? Nu - Da -
        -
        -
        - -
      -
      - - Abonaţii listei -
      - - - -

      - - - -

      - - - + + +
    Alegeţi o parolă: 
    Reintroduceţi parola pentru confirmare: 
    Limba preferată:  
    Doriţi mesajele listei într-un rezumat zilnic? Nu + Da +
    +
    +
    + + +

    + +Abonaţii listei +
    + + + +

    + + + +

    + +

    + diff --git a/templates/ro/options.html b/templates/ro/options.html index bb365500..1759d0cd 100644 --- a/templates/ro/options.html +++ b/templates/ro/options.html @@ -1,44 +1,105 @@ - - Configurarea abonamentului <MM-Presentable-User> la lista <MM-List-Name> - - - - - -
    - - Lista de discuţii - configurarea abonamentului pentru - -
    + +Configurarea abonamentului <mm-presentable-user> la lista <mm-list-name></mm-list-name></mm-presentable-user> + + + + + +
    + + Lista de discuţii - configurarea abonamentului pentru + +

    - - - - - +
    - Situaţia abonamentului , - parola şi opţiunile pentru lista de discuţii . -
    - - -

    -

    + + + +
    + Situaţia abonamentului , + parola şi opţiunile pentru lista de discuţii . +
    + + +

    +

    - - +

    - - - +
    - - Informaţiile de abonament la lista -
    PuteÅ£i schimba adresa cu care v-aÅ£i abonat la lista - de discuÅ£ii introducând o nouă adresă în câmpurile de mai jos. Nu uitaÅ£i + + + - - - - - -
    + +Informaţiile de abonament la lista +
    PuteÅ£i schimba adresa cu care v-aÅ£i abonat la lista + de discuÅ£ii introducând o nouă adresă în câmpurile de mai jos. Nu uitaÅ£i că un mesaj de confirmare va fi trimis la noua adresă, ÅŸi această - schimbare trebuie confirmată înainte de a intra în vigoare. + schimbare trebuie confirmată înainte de a intra în vigoare.

    Confirmările expiră după aproximativ . @@ -48,246 +109,219 @@

    Dacă doriţi să faceţi modificări de abonament pentru toate listele de la la la care sunteţi abonat, bifaţi opţiunea Modificări globale. -

    - - - - - - - -
    Noua adresă:
    Adresa din nou:
    -
    - - - - -
    Numele (opţional):
    -
    -

    Modificări globale

    - +

    + + + + + + + +
    Noua adresă:
    Adresa din nou:
    +

    + + + + +
    Numele (opţional):
    + +
    +

    Modificări globale

    +

    - - - - - - - - + + + + %(textlink)s - diff --git a/templates/ru/archtocnombox.html b/templates/ru/archtocnombox.html index eeeea6f4..061c6ee8 100644 --- a/templates/ru/archtocnombox.html +++ b/templates/ru/archtocnombox.html @@ -1,18 +1,81 @@ - - - Ðрхивы ÑпиÑка раÑÑылки %(listname)s - + + + +Ðрхивы ÑпиÑка раÑÑылки %(listname)s + %(meta)s - - -

    Ðрхивы ÑпиÑка раÑÑылки %(listname)s

    -

    ЗдеÑÑŒ вы найдете информацию о ÑпиÑке + + +

    Ðрхивы ÑпиÑка раÑÑылки %(listname)s

    +

    ЗдеÑÑŒ вы найдете информацию о ÑпиÑке раÑÑылки.

    %(noarchive_msg)s %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/ru/article.html b/templates/ru/article.html index a8267999..28406e18 100644 --- a/templates/ru/article.html +++ b/templates/ru/article.html @@ -1,12 +1,13 @@ - - - %(title)s - - - - - + +

    Ðрхив ÑпиÑка раÑÑылки %(listname)s

    +

    Ð’ Ñтот ÑпиÑок раÑÑылки пока еще не было отправлено ни одного ÑообщениÑ, так что архив пуÑÑ‚. Ð’Ñ‹ можете почитать общую информацию об Ñтом ÑпиÑке раÑÑылки.

    - - + + diff --git a/templates/ru/headfoot.html b/templates/ru/headfoot.html index 542d1263..f766c54c 100644 --- a/templates/ru/headfoot.html +++ b/templates/ru/headfoot.html @@ -1,24 +1,85 @@ -Этот текÑÑ‚ может включать Ñтроки +Этот текÑÑ‚ может включать Ñтроки подÑтановки Python (текÑÑ‚ по-английÑки), которые заменÑÑŽÑ‚ÑÑ Ñледующими атрибутами ÑпиÑка раÑÑылки:
      -
    • real_name — "краÑивое" Ð¸Ð¼Ñ ÑпиÑка раÑÑылки; +
    • real_name — "краÑивое" Ð¸Ð¼Ñ ÑпиÑка раÑÑылки; обычно Ñто Ð¸Ð¼Ñ ÑпиÑка раÑÑылки Ñ Ð·Ð°Ð³Ð»Ð°Ð²Ð½Ð¾Ð¹ буквы. -
    • list_name — имÑ, которое иÑпользуетÑÑ Ð² адреÑах +
    • list_name — имÑ, которое иÑпользуетÑÑ Ð² адреÑах Ñтраниц, региÑтр Ñимволов имеет значение. -
    • host_name — полное Ð¸Ð¼Ñ Ð´Ð¾Ð¼ÐµÐ½Ð° (FQDN) ÑпиÑка раÑÑылки. +
    • host_name — полное Ð¸Ð¼Ñ Ð´Ð¾Ð¼ÐµÐ½Ð° (FQDN) ÑпиÑка раÑÑылки. -
    • web_page_url — базовый Ð°Ð´Ñ€ÐµÑ Ð´Ð»Ñ Mailman. Именно к Ñтому +
    • web_page_url — базовый Ð°Ð´Ñ€ÐµÑ Ð´Ð»Ñ Mailman. Именно к Ñтому адреÑу нужно добавлÑть, например, listinfo/%(list_name)s чтобы получить Ð°Ð´Ñ€ÐµÑ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ð¾Ð½Ð½Ð¾Ð¹ Ñтраницы ÑпиÑка раÑÑылки. -
    • description — краткое опиÑание ÑпиÑка раÑÑылки. +
    • description — краткое опиÑание ÑпиÑка раÑÑылки. -
    • info — полное опиÑание ÑпиÑка раÑÑылки. +
    • info — полное опиÑание ÑпиÑка раÑÑылки. -
    • cgiext — раÑширение файлов CGI-Ñценариев. -
    +
  • cgiext — раÑширение файлов CGI-Ñценариев. +
  • diff --git a/templates/ru/listinfo.html b/templates/ru/listinfo.html index 8568c7af..3de76bd3 100644 --- a/templates/ru/listinfo.html +++ b/templates/ru/listinfo.html @@ -1,133 +1,193 @@ - - - ÐžÐ±Ñ‰Ð°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ ÑпиÑке раÑÑылки <MM-List-Name> - - - -

    -

    - Dezabonarea de la lista - Celelalte abonamente la pe care le aveţi -
    - BifaÅ£i câmpul de confirmare ÅŸi apăsaÅ£i butonul pentru a + + + + - + +
    +

    +Dezabonarea de la lista +Celelalte abonamente la pe care le aveţi +
    + Bifaţi câmpul de confirmare şi apăsaţi butonul pentru a vă dezabona de la această listă. Atenţie: Acţiunea va avea loc imediat!

    -

    +

    PuteÅ£i afiÅŸa toate celelalte liste de discuÅ£ii la la care aveÅ£i abonament. FolosiÅ£i această facilitate pentru a - seta aceleaÅŸi opÅ£iuni personale ÅŸi în cazul celorlalte abonamente. + seta aceleaÅŸi opÅ£iuni personale ÅŸi în cazul celorlalte abonamente.

    -

    -
    - - - - - +
    - Parola -
    - -
    -

    AÅ£i uitat parola?

    -
    + + + - -
    +Parola +
    + +
    +

    AÅ£i uitat parola?

    +
    Apăsaţi acest buton pentru a vă trimite parola la adresa de email abonată. -

    -

    -
    - -
    -

    Modificare parolă

    - - - - - - - - -
    Noua parolă:
    Parola, din nou:
    - - -

    Modifică global -
    -
    - +

    +

    +

    + +
    +

    Modificare parolă

    + + + + + + + + +
    Noua parolă:
    Parola, din nou:
    + +

    Modifică global +
    +

    - - +
    - Opţiunile personale de abonament la lista -
    +
    +Opţiunile personale de abonament la lista +
    -

    Valorile actuale sunt selectate. -

    Unele din opÅ£iuni au o opÅ£iune setare globală. -Bifând această opÅ£iune, modificările vor fi salvate pentru toate +Bifând această opÅ£iune, modificările vor fi salvate pentru toate listele de la la care sunteÅ£i abonat. ApăsaÅ£i pe butonul AfiÅŸează-mi celelalte abonamente de mai sus pentru a vedea toate celelalte liste de discuÅ£ii la care aveÅ£i abonament.

    - -
    - - Livrare mesaje email

    + + - + publicate pe această listă de discuţii.
    Setaţi opţiunea Inactivă + dacă doriţi să rămâneţi abonat, dar nu doriţi să primiţi mesaje o vreme
    + (de ex. când sunteţi în vacanţă).
    Dacă dezactivaţi livrarea de mesaje + nu uitaţi să o reactivaţi la întoarcere; ea nu va fi activată automat. +

    - - - + +

    - - - - - - + + - - + - - - - + + - - + +

    + - - - +

    +
    + +Livrare mesaje email

    Setaţi această opţiune ca fiind Activă pentru a recepţiona mesajele - publicate pe această listă de discuţii.
    SetaÅ£i opÅ£iunea Inactivă - dacă doriÅ£i să rămâneÅ£i abonat, dar nu doriÅ£i să primiÅ£i mesaje o vreme
    - (de ex. când sunteÅ£i în vacanţă).
    Dacă dezactivaÅ£i livrarea de mesaje - nu uitaÅ£i să o reactivaÅ£i la întoarcere; ea nu va fi activată automat. -

    - Activă
    - Inactivă

    - Setează global -

    +Activă
    +Inactivă

    +Setează global +

    - Modul de livrare rezumat

    +

    +Modul de livrare rezumat

    Dacă activaÅ£i modul rezumat (digest), veÅ£i primi mesajele adunate - într-un singur mesaj rezumat
    - (de obicei unul pe zi dar e posibil ÅŸi mai des în cazul listelor foarte aglomerate), - în locul mesajelor individuale.
    + într-un singur mesaj rezumat
    + (de obicei unul pe zi dar e posibil şi mai des în cazul listelor foarte aglomerate), + în locul mesajelor individuale.
    Dacă dezactivaţi livrarea de rezumate, s-ar putea să mai primiţi un ultim rezumat. -

    - Inactiv
    - Activ -
    - Rezumate MIME sau text simplu?

    - Este posibil ca programul dumneavoastră de email să nu suporte rezumatele MIME.
    - În general, rezumatele MIME sunt de preferat, dar dacă aveÅ£i probleme la citirea lor,
    +

    +Inactiv
    +Activ +
    +Rezumate MIME sau text simplu?

    + Este posibil ca programul dumneavoastră de email să nu suporte rezumatele MIME.
    + În general, rezumatele MIME sunt de preferat, dar dacă aveţi probleme la citirea lor,
    alegeţi rezumatele sub formă de text simplu. -

    - MIME
    - Text simplu

    - Setează global -

    +MIME
    +Text simplu

    +Setează global +

    - Doriţi să primiţi duplicate ale mesajelor trimise listei?

    - În mod normal, veÅ£i primi o copie a fiecărui mesaj trimis către listă.
    +

    +Doriţi să primiţi duplicate ale mesajelor trimise listei?

    + În mod normal, veţi primi o copie a fiecărui mesaj trimis către listă.
    Dacă nu doriţi să primiţi aceste mesaje, alegeţi Nu. -

    - Nu
    - Da
    - Doriţi confirmare pentru mesajele trimise?

    -

    - Nu
    - Da
    - Doriţi mesaje de reamintire a parolei pentru această listă?

    +

    +Nu
    +Da
    +Doriţi confirmare pentru mesajele trimise?

    +

    +Nu
    +Da
    +Doriţi mesaje de reamintire a parolei pentru această listă?

    Lunar veţi primi un mesaj ce conţine parola de acces pentru fiecare listă - de la acest server,
    + de la acest server,
    la care aveÅ£i abonament. PuteÅ£i anula această opÅ£iune pentru fiecare listă - în parte, alegând Nu.
    + în parte, alegând Nu.
    Dacă anulaţi mesajele de reamintire a parolei pentru toate listele de pe - acest server la care sunteţi abonat,
    + acest server la care sunteţi abonat,
    nici un mesaj de reamintire nu vă va mai fi trimis. -

    - Nu
    - Da

    - Setează global -

    - Să vă ascundem din lista membrilor?

    - Când cineva afiÅŸează lista membrilor acestei liste, adresa dumneavoastră de email
    - este în mod normal afiÅŸată (într-o formă aparte, pentru a îngreuna munca spammerilor).
    - Dacă doriÅ£i ca adresa dumneavoastră de email să nu apară deloc în lista membrilor,
    +

    +Nu
    +Da

    +Setează global +

    +Să vă ascundem din lista membrilor?

    + Când cineva afişează lista membrilor acestei liste, adresa dumneavoastră de email
    + este în mod normal afişată (într-o formă aparte, pentru a îngreuna munca spammerilor).
    + Dacă doriţi ca adresa dumneavoastră de email să nu apară deloc în lista membrilor,
    alegeţi Da la această opţiune. -

    - Nu
    - Da
    - Ce limbă preferaţi?

    -

    - -
    - La ce topici de discuţie doriţi să vă abonaţi?

    - Alegând una sau mai multe topici, puteÅ£i filtra traficul listei, pentru - a primi doar mesajele de interes.
    +

    +Nu
    +Da
    +Ce limbă preferaţi?

    +

    + +
    +La ce topici de discuţie doriţi să vă abonaţi?

    + Alegând una sau mai multe topici, puteţi filtra traficul listei, pentru + a primi doar mesajele de interes.
    Dacă un mesaj se potriveÅŸte la una sau mai multe topici, veÅ£i primi acel mesaj; - în caz contrar nu-l veÅ£i primi. + în caz contrar nu-l veÅ£i primi.

    Dacă un mesaj aparţine nici unei topici, regula de livrare depinde de - setarea opţiunii de mai jos.
    + setarea opţiunii de mai jos.
    Dacă nu alegeţi nici o topică de interes, veţi primi toate mesajeletrimisei listei de discuţii. -

    - -
    - Doriţi să primiţi mesajele care nu aparţin nici unei topici?

    - Această opţiune are efect doar dacă aţi ales cel puţin o topică mai sus. Ea descrie ce regulă
    - de bază se aplică pentru mesajele care nu îndeplinesc nici una din condiÅ£iile de filtrare
    - după topici. Alegând Nu, dacă mesajul nu se încadrează în nici una din topicile alese,
    - atunci nu veÅ£i primi mesajele, în timp ce dacă alegeÅ£i Da, veÅ£i primi ÅŸi aceste mesaje. +

    + +
    +Doriţi să primiţi mesajele care nu aparţin nici unei topici?

    + Această opţiune are efect doar dacă aţi ales cel puţin o topică mai sus. Ea descrie ce regulă
    + de bază se aplică pentru mesajele care nu îndeplinesc nici una din condiţiile de filtrare
    + după topici. Alegând Nu, dacă mesajul nu se încadrează în nici una din topicile alese,
    + atunci nu veţi primi mesajele, în timp ce dacă alegeţi Da, veţi primi şi aceste mesaje. -

    Dacă nu aţi ales nici o topică de interes mai sus, atunci veţi primi toate
    +

    Dacă nu aţi ales nici o topică de interes mai sus, atunci veţi primi toate
    mesajele trimise listei de discuţii. -

    - Nu
    - Da
    +Nu
    +Da
    +Blochez mesajele duplicate?

    -

    - Blochez mesajele duplicate?

    - - Când sunteÅ£i trecut în mod explicit în câmpurile To: sau Cc: - ale unui mesaj către listă,
    + Când sunteţi trecut în mod explicit în câmpurile To: sau Cc: + ale unui mesaj către listă,
    puteţi opta pentru a nu mai primi o altă copie din - partea listei.
    + partea listei.
    Alegeţi Da pentru a evita primirea de duplicate din partea listei; alegeţi Nu pentru a primi duplicate.

    Dacă lista are activate mesajele personalizate pentru utilizatori, - şi alegeţi să primiţi copii ale mesajelor,
    + şi alegeţi să primiţi copii ale mesajelor,
    fiecare copie va avea un header X-Mailman-Copy: yes ataÅŸat. -

    - Nu
    - Da

    - Setează global -

    -
    -
    +Nu
    +Da

    +Setează global +

    +
    +
    -

    - - - - + + +

    diff --git a/templates/ro/private.html b/templates/ro/private.html index 9d6a3749..02f4cdc5 100755 --- a/templates/ro/private.html +++ b/templates/ro/private.html @@ -1,59 +1,119 @@ - Autentificare la arhivele private %(realname)s - - - - -
    +Autentificare la arhivele private %(realname)s + + + + + %(message)s - - - - - - - - - - - - - - - -
    - Arhivele private %(realname)s - Autentificare -
    Adresa de email:
    Parola:
    -
    -

    Important: De aici înainte trebuie să aveÅ£i - activate cookie-urile în browser; altfel nici o acÅ£iune administrativă + + + + + + + + + + + + + + + +
    +Arhivele private %(realname)s - Autentificare +
    Adresa de email:
    Parola:
    +
    +

    Important: De aici înainte trebuie să aveţi + activate cookie-urile în browser; altfel nici o acţiune administrativă nu va avea efect.

    InterfaÅ£a administrativă Mailman foloseÅŸte cookie-urile de sesiune, - astfel încât nu trebuie să vă re-autentificaÅ£i la fiecare operaÅ£iune + astfel încât nu trebuie să vă re-autentificaÅ£i la fiecare operaÅ£iune administrativă. Acest cookie va expira automat la ieÅŸire, sau explicit, la apăsarea linkului Logout, sub linkul Alte activităţi - administrative (care va apare de îndată ce vă autentificaÅ£i cu succes). + administrative (care va apare de îndată ce vă autentificaÅ£i cu succes).

    - - - - - - + + + +
    - Password Reminder -
    If you don't remember your password, enter your email address + + + + + + - - - - -
    +Password Reminder +
    If you don't remember your password, enter your email address above and click the Remind button and your password will be emailed to you.
    - +
    +

    diff --git a/templates/ro/roster.html b/templates/ro/roster.html index 9e9f9150..b0aa49f4 100644 --- a/templates/ro/roster.html +++ b/templates/ro/roster.html @@ -1,53 +1,113 @@ - - - Abonaţii listei <MM-List-Name> - - - -

    - - - - - - - - - - - - - - - -
    - Abonaţii listei -
    - -

    -

    - -

    Faceţi click pe adresa dumneavoastră pentru a accesa pagina de opţiuni - de abonare.
    (Adresele scrise în paranteză nu primesc mesaje din - din partea listei.)

    -
    -
    - - membri ce primesc mesaje normale de la lista : - -
    -
    -
    - - membri ce primesc doar rezumate zilnice de la lista : - -
    -
    -

    -

    -

    -

    - - - + + +Abonaţii listei <mm-list-name></mm-list-name> + + + +

    + + + + + + + + + + + + + + + +
    +Abonaţii listei +
    +

    +

    +

    Faceţi click pe adresa dumneavoastră pentru a accesa pagina de opţiuni + de abonare.
    (Adresele scrise în paranteză nu primesc mesaje din + din partea listei.)

    +
    +
    + + membri ce primesc mesaje normale de la lista : + +
    +
    +
    + + membri ce primesc doar rezumate zilnice de la lista : + +
    +
    +

    +

    +

    +

    + +

    + diff --git a/templates/ro/subscribe.html b/templates/ro/subscribe.html index a23e21dc..52f24dee 100644 --- a/templates/ro/subscribe.html +++ b/templates/ro/subscribe.html @@ -1,10 +1,72 @@ -Rezultatele abonării la <MM-List-Name> - +Rezultatele abonării la <mm-list-name></mm-list-name> + -

    Rezultatele abonării la

    - - - +

    Rezultatele abonării la

    + + + diff --git a/templates/ru/admindbdetails.html b/templates/ru/admindbdetails.html index 5759ea23..7fa56263 100644 --- a/templates/ru/admindbdetails.html +++ b/templates/ru/admindbdetails.html @@ -1,4 +1,66 @@ -ÐдминиÑтративные запроÑÑ‹ предÑтавлены двум ÑпоÑобами: на Ñтранице +ÐдминиÑтративные запроÑÑ‹ предÑтавлены двум ÑпоÑобами: на Ñтранице Ñводный ÑпиÑок и на Ñтранице Ð¿Ð¾Ð´Ñ€Ð¾Ð±Ð½Ð°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ. Ð’ Ñводном ÑпиÑке предÑтавлены ожидающие обработки запроÑÑ‹ на подпиÑку и удаление оной, а также ÑообщениÑ, которые @@ -9,22 +71,21 @@

    Ðа каждой из Ñтраниц Ð’Ñ‹ можете выбрать одно из Ñледующих дейÑтвий:

      -
    • Отложить — отложить решение на потом. ИÑпользуйте Ñто +
    • Отложить — отложить решение на потом. ИÑпользуйте Ñто дейÑтвие, еÑли вы хотите указанно Ñообжение переправить или Ñохранить (Ñм. ниже). -
    • ПринÑть/Одобрить — принÑть Ñообщение и переправить его +
    • ПринÑть/Одобрить — принÑть Ñообщение и переправить его в ÑпиÑок раÑÑылки. Ð”Ð»Ñ Ð·Ð°Ð¿Ñ€Ð¾Ñов об изменении подпиÑки, одобрить их. -
    • Отказать — отвергнуть Ñообщение, отправив отправителю +
    • Отказать — отвергнуть Ñообщение, отправив отправителю уведомление, что его Ñообщение принÑто не было. Само Ñообщение поÑле Ñтого удалÑетÑÑ. -
    • Удалить — удалить указанное Ñообщение без каких-либо +
    • Удалить — удалить указанное Ñообщение без каких-либо извещений отправителю. Данное дейÑтвие может быть полезным в Ñлучае Ñпама. -
    - +

    Параметр Сохранить позволÑет вам Ñохранить копию ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð´Ð»Ñ Ð°Ð´Ð¼Ð¸Ð½Ð¸Ñтратора Ñервера. Это может оказатьÑÑ Ð¿Ð¾Ð»ÐµÐ·Ð½Ñ‹Ð¼ Ð´Ð»Ñ Ñообщений, которые вы хотели бы удалить, но перед Ñтим внимательно его раÑÑмотреть. @@ -59,3 +120,4 @@ вÑе выбранные дейÑÑ‚Ð²Ð¸Ñ Ð±ÑƒÐ´ÑƒÑ‚ выполнены.

    ВерутьÑÑ Ðº Ñводному ÑпиÑку запроÑов. +

    \ No newline at end of file diff --git a/templates/ru/admindbpreamble.html b/templates/ru/admindbpreamble.html index ecb1d0d5..f010c106 100644 --- a/templates/ru/admindbpreamble.html +++ b/templates/ru/admindbpreamble.html @@ -1,4 +1,66 @@ -Эта Ñтраница Ñодержит ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð² ÑпиÑок раÑÑылки %(listname)s, +Эта Ñтраница Ñодержит ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð² ÑпиÑок раÑÑылки %(listname)s, задержанные Ð´Ð»Ñ Ð¿Ñ€Ð¾Ð²ÐµÑ€ÐºÐ¸. Она отображает %(description)s @@ -8,3 +70,4 @@

    Ð’Ñ‹ можете также поÑмотреть Ñводную информацию обо вÑех ожидающих запроÑах. +

    \ No newline at end of file diff --git a/templates/ru/admindbsummary.html b/templates/ru/admindbsummary.html index f67f7c8b..80952cad 100644 --- a/templates/ru/admindbsummary.html +++ b/templates/ru/admindbsummary.html @@ -1,4 +1,66 @@ -Эта Ñтраница Ñодержит Ñводный ÑпиÑок требующих обработки админиÑтративных +Эта Ñтраница Ñодержит Ñводный ÑпиÑок требующих обработки админиÑтративных запроÑов Ð´Ð»Ñ ÑпиÑка %(listname)s mailing list. Ð’ начале предÑтавлены запроÑÑ‹ на подпиÑку или удаление оной, за которыми перечиÑлены ÑообщениÑ, требующие обработки (модерирование). @@ -9,3 +71,4 @@

    Ðа Ñтранице Подробный ÑпиÑок запроÑов вы найдете дополнительную информацию о каждом из перечиÑленных ниже Ñообщений. +

    \ No newline at end of file diff --git a/templates/ru/admlogin.html b/templates/ru/admlogin.html index 3188caab..4073c265 100755 --- a/templates/ru/admlogin.html +++ b/templates/ru/admlogin.html @@ -1,26 +1,88 @@ - ÐÑƒÑ‚ÐµÐ½Ñ‚Ð¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ñ %(who)s ÑпиÑка раÑÑылки %(listname)s +ÐÑƒÑ‚ÐµÐ½Ñ‚Ð¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ñ %(who)s ÑпиÑка раÑÑылки %(listname)s - - -
    + + + %(message)s - - - - - - - - - - - -
    - ÐÑƒÑ‚ÐµÐ½Ñ‚Ð¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ñ %(who)s ÑпиÑка раÑÑылки %(listname)s -
    Введите пароль %(who)s:
    -

    Важно: вы должны разрешить cookies, + + + + + + + + + + + +
    +ÐÑƒÑ‚ÐµÐ½Ñ‚Ð¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ñ %(who)s ÑпиÑка раÑÑылки %(listname)s +
    Введите пароль %(who)s:
    +

    Важно: вы должны разрешить cookies, в противном Ñлучае вы не Ñможете вноÑить какие бы то ни было изменениÑ.

    Файлы cookies иÑпользуютÑÑ Ð´Ð»Ñ Ñ‚Ð¾Ð³Ð¾, чтобы хранить параметры ÑеанÑа работы. @@ -29,6 +91,6 @@ удалена, когда вы закроете броузер. Ð’Ñ‹ также можете Ñвным образом завершить ÑеанÑ, кликнув на ÑÑылку Завершить работу. (Ð’Ñ‹ увидите ее поÑле входа в админиÑтративный интерфейÑ). -

    +

    diff --git a/templates/ru/archidxentry.html b/templates/ru/archidxentry.html index 19232e54..4548a01c 100644 --- a/templates/ru/archidxentry.html +++ b/templates/ru/archidxentry.html @@ -1 +1,64 @@ -
  • %(subject)s %(author)s +
  • %(subject)s %(author)s +
  • \ No newline at end of file diff --git a/templates/ru/archidxfoot.html b/templates/ru/archidxfoot.html index 1551cd7a..876b9d63 100644 --- a/templates/ru/archidxfoot.html +++ b/templates/ru/archidxfoot.html @@ -1,19 +1,82 @@ - -

    - Дата поÑледнего ÑообщениÑ: - %(lastdate)s
    - Ðрхив обновлен: %(archivedate)s -

    -

      -
    • Сортировать по: + +

      +Дата поÑледнего ÑообщениÑ: +%(lastdate)s
      +Ðрхив обновлен: %(archivedate)s +

      +

      -

      -


      - Этот архив был Ñоздан программой Pipermail %(version)s. - - +
    +

    +


    +Этот архив был Ñоздан программой Pipermail %(version)s. + + +

    \ No newline at end of file diff --git a/templates/ru/archidxhead.html b/templates/ru/archidxhead.html index 8272ba63..a3be7470 100644 --- a/templates/ru/archidxhead.html +++ b/templates/ru/archidxhead.html @@ -1,23 +1,87 @@ - - - Ðрхив %(listname)s, том %(archive)s, ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ ÑƒÐ¿Ð¾Ñ€Ñдочены по %(archtype)s - + + + +Ðрхив %(listname)s, том %(archive)s, ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ ÑƒÐ¿Ð¾Ñ€Ñдочены по %(archtype)s + %(encoding)s - - - -

    Том %(archive)s, ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ ÑƒÐ¿Ð¾Ñ€Ñдочены по %(archtype)s

    -
      -
    • Сортировать по: + + + +

      Том %(archive)s, ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ ÑƒÐ¿Ð¾Ñ€Ñдочены по %(archtype)s

      + -

      Первое Ñообщение: %(firstdate)s
      - ПоÑледнее Ñообщение: %(lastdate)s
      - Сообщений: %(size)s

      -

        +
      +

      Первое Ñообщение: %(firstdate)s
      +ПоÑледнее Ñообщение: %(lastdate)s
      +Сообщений: %(size)s

      +

        +

      \ No newline at end of file diff --git a/templates/ru/archlistend.html b/templates/ru/archlistend.html index 9bc052dd..492cf01a 100644 --- a/templates/ru/archlistend.html +++ b/templates/ru/archlistend.html @@ -1 +1,63 @@ -
    + diff --git a/templates/ru/archliststart.html b/templates/ru/archliststart.html index a4a41ccc..9282bc36 100644 --- a/templates/ru/archliststart.html +++ b/templates/ru/archliststart.html @@ -1,4 +1,67 @@ - - - - +
    Том архиваСортировать по:ВерÑÐ¸Ñ Ð´Ð»Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·ÐºÐ¸
    + + + +
    Том архиваСортировать по:ВерÑÐ¸Ñ Ð´Ð»Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·ÐºÐ¸
    \ No newline at end of file diff --git a/templates/ru/archtoc.html b/templates/ru/archtoc.html index b55437bd..4b99b11b 100644 --- a/templates/ru/archtoc.html +++ b/templates/ru/archtoc.html @@ -1,13 +1,76 @@ - - - Ðрхив %(listname)s - + + + +Ðрхив %(listname)s + %(meta)s - - -

    Ðрхив %(listname)s

    -

    ЗдеÑÑŒ вы найдете информацию о ÑпиÑке + + +

    Ðрхив %(listname)s

    +

    ЗдеÑÑŒ вы найдете информацию о ÑпиÑке раÑÑылки. Ð’Ñ‹ также можете загрузить веÑÑŒ архив в формате mbox (%(size)s).

    @@ -15,5 +78,5 @@

    Ðрхив %(listname)s

    %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/ru/archtocentry.html b/templates/ru/archtocentry.html index b8712ea4..c1c83f49 100644 --- a/templates/ru/archtocentry.html +++ b/templates/ru/archtocentry.html @@ -1,12 +1,73 @@ - -
    %(archivelabel)s: - [ по диÑкуÑÑиÑм ] - [ по теме ] - [ по автору ] - [ по дате ] -
    %(archivelabel)s: +[ по диÑкуÑÑиÑм ] +[ по теме ] +[ по автору ] +[ по дате ] +
    - - - - - - - - - - - - - - - - - - + + + + + +

    - -
    -

      -

    О ÑпиÑке раÑÑылки - - -
    -

    -

    Ð”Ð»Ñ Ð¿Ñ€Ð¾Ñмотра Ñообщений, отправленных в лиÑÑ‚ раÑÑылки ранее, Ð’Ñ‹ можете воÑпользоватьÑÑ Ð°Ñ€Ñ…Ð¸Ð²Ð¾Ð¼ Ñообщений . -

    -
    Как пользоватьÑÑ ÑпиÑком раÑÑылки
    + + +ÐžÐ±Ñ‰Ð°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ ÑпиÑке раÑÑылки <mm-list-name></mm-list-name> + + +

    + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + +
    + — +
    +

      +

    О ÑпиÑке раÑÑылки + + +
    +

    +

    Ð”Ð»Ñ Ð¿Ñ€Ð¾Ñмотра Ñообщений, отправленных в лиÑÑ‚ раÑÑылки ранее, Ð’Ñ‹ можете воÑпользоватьÑÑ Ð°Ñ€Ñ…Ð¸Ð²Ð¾Ð¼ Ñообщений . +

    +
    Как пользоватьÑÑ ÑпиÑком раÑÑылки
    Ð”Ð»Ñ Ñ‚Ð¾Ð³Ð¾, чтобы отправить Ñообщение вÑем подпиÑчикам ÑпиÑка раÑÑылки, поÑылайте пиÑьмо - по адреÑу . + по адреÑу .

    Ðиже Ð’Ñ‹ можете подпиÑатьÑÑ Ð½Ð° Ñтот ÑпиÑок раÑÑылки или изменить параметры Ñвоей подпиÑки

    -
    - ПодпиÑатьÑÑ Ð½Ð° ÑпиÑок раÑÑылки -
    -

    - Ð”Ð»Ñ Ñ‚Ð¾Ð³Ð¾, чтобы подпиÑатьÑÑ Ð½Ð° ÑпиÑок раÑÑылки , заполните Ñледующую форму. - -

      - - - - - - - - - - - - + + + + + + - - - - - -
      Ваш Ñлектронный адреÑ: -  
      Ваше Ð¸Ð¼Ñ (необÑзательно): 
      Ð’Ñ‹ можете указать пароль, защищающий параметры Вашей подпиÑки +
      +ПодпиÑатьÑÑ Ð½Ð° ÑпиÑок раÑÑылки +
      +

      + Ð”Ð»Ñ Ñ‚Ð¾Ð³Ð¾, чтобы подпиÑатьÑÑ Ð½Ð° ÑпиÑок раÑÑылки , заполните Ñледующую форму. + +

        + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - -
        Ваш Ñлектронный адреÑ: + 
        Ваше Ð¸Ð¼Ñ (необÑзательно): 
        Ð’Ñ‹ можете указать пароль, защищающий параметры Вашей подпиÑки от неÑанкционированного изменениÑ. Ðе иÑпользуйте важных паролей, так как в ÑиÑтеме Mailman пароли переÑылаютÑÑ Ð¿Ð¾ Ñлектронной почте в открытом виде. -

        ЕÑли Ð’Ñ‹ не хотите указывать пароль, ÑиÑтема ÑоздаÑÑ‚ его автоматичеÑки +

        ЕÑли Ð’Ñ‹ не хотите указывать пароль, ÑиÑтема ÑоздаÑÑ‚ его автоматичеÑки и вышлет на Ваш Ð°Ð´Ñ€ÐµÑ Ð¿Ð¾Ð´Ð¿Ð¸Ñки. Ð’Ñ‹ вÑегда можете инициировать оправку его Вам Ñнова Ñо Ñтраницы параметров подпиÑки. - -
        -
        Укажите пароль: 
        Подтвердите выбранный пароль: 
        Ðа каком Ñзыке Ð’Ñ‹ хотели бы видеть ÑообщениÑ?  
        Хотите ли Ð’Ñ‹ получать подпиÑку в виде дайджеÑтов? Ðет - Да -
        -
        -
        - -
      -
      - - ПодпиÑчики ÑпиÑка раÑÑылки -
      - - - -

      - - - -

      - - - + + +
    Укажите пароль: 
    Подтвердите выбранный пароль: 
    Ðа каком Ñзыке Ð’Ñ‹ хотели бы видеть ÑообщениÑ?  
    Хотите ли Ð’Ñ‹ получать подпиÑку в виде дайджеÑтов? Ðет + Да +
    +
    +
    + + +

    + +ПодпиÑчики ÑпиÑка раÑÑылки +
    + + + +

    + + + +

    + +

    + diff --git a/templates/ru/options.html b/templates/ru/options.html index 3cb37cc9..99f66dc0 100644 --- a/templates/ru/options.html +++ b/templates/ru/options.html @@ -1,42 +1,101 @@ - - <MM-Presentable-User>: параметры подпиÑки на ÑпиÑок раÑÑылки <MM-List-Name> - - - - - -
    - - Параметры подпиÑки Ð´Ð»Ñ ÑпиÑка раÑÑылки -
    + +<mm-presentable-user>: параметры подпиÑки на ÑпиÑок раÑÑылки <mm-list-name> +</mm-list-name></mm-presentable-user> + + + + +
    + + Параметры подпиÑки Ð´Ð»Ñ ÑпиÑка раÑÑылки +

    - - - - - +
    - СоÑтоÑние подпиÑки , - пароль и параметры подпиÑки на ÑпиÑок раÑÑылки . -
    - - - - -

    -

    + + + +
    + СоÑтоÑние подпиÑки , + пароль и параметры подпиÑки на ÑпиÑок раÑÑылки . +
    + + +

    +

    - - +

    - - - +
    - - Изменение параметров Вашей подпиÑки на ÑпиÑок раÑÑылки -
    Ð’Ñ‹ можете изменить адреÑ, на который должны доÑтавлÑтьÑÑ ÑообщениÑ. + + + - - - - - -
    + +Изменение параметров Вашей подпиÑки на ÑпиÑок раÑÑылки +
    Ð’Ñ‹ можете изменить адреÑ, на который должны доÑтавлÑтьÑÑ ÑообщениÑ. Ð”Ð»Ñ Ñтого введите его в поле ниже. Имейте в виду, что на новый Ð°Ð´Ñ€ÐµÑ Ð±ÑƒÐ´ÐµÑ‚ выÑлана проÑьба подтвердить Ñту операцию и что без Ñтого Ð¿Ð¾Ð´Ñ‚Ð²ÐµÑ€Ð¶Ð´ÐµÐ½Ð¸Ñ Ð°Ð´Ñ€ÐµÑ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½ не будет. @@ -50,240 +109,211 @@ ваших подпиÑках на Ñервере , Ð’Ñ‹ можете воÑпользоватьÑÑ Ð¿Ð°Ñ€Ð°Ð¼ÐµÑ‚Ñ€Ð¾Ð¼ Изменить во вÑех подпиÑках. -
    - - - - - - - -
    Ðовый адреÑ:
    Еще раз новый адреÑ:
    -
    - - - - -
    Ваше Ð¸Ð¼Ñ (необÑзательно):
    -
    -

    Изменить во вÑех подпиÑках

    - +

    + + + + + + + +
    Ðовый адреÑ:
    Еще раз новый адреÑ:
    +

    + + + + +
    Ваше Ð¸Ð¼Ñ (необÑзательно):
    + +
    +

    Изменить во вÑех подпиÑках

    +

    - - - - - - - - + + + + %(textlink)s + \ No newline at end of file diff --git a/templates/sk/archtocnombox.html b/templates/sk/archtocnombox.html index 546f942a..c6bdb9cf 100644 --- a/templates/sk/archtocnombox.html +++ b/templates/sk/archtocnombox.html @@ -1,18 +1,81 @@ - - - Archív konferencie %(listname)s - + + + +Archív konferencie %(listname)s + %(meta)s - - -

    Archív konferencie %(listname)s

    -

    - Viac informácií o konferencii. + + +

    Archív konferencie %(listname)s

    +

    + Viac informácií o konferencii.

    %(noarchive_msg)s %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/sk/article.html b/templates/sk/article.html index ed0b1654..eed67e20 100644 --- a/templates/sk/article.html +++ b/templates/sk/article.html @@ -1,12 +1,13 @@ - - - %(title)s - - - - - + +

    Archív konferencie %(listname)s

    +

    + Do tejto konferencie nebol zatial zaslaný žiaden príspevok. + Preto je tento archív prázdny. Viac informácií o konferencii + získate na informaÄnej stránke..

    - - + + diff --git a/templates/sk/headfoot.html b/templates/sk/headfoot.html index 07351cb3..fde97d70 100644 --- a/templates/sk/headfoot.html +++ b/templates/sk/headfoot.html @@ -1,30 +1,90 @@ -Tento text môže obsahovaÅ¥ formátovacie -príkazy jazyka Python, ktoré sa nahradia príslušnými -hodnotami pri vytváraní stránky. Nejdôležitejšie premenné sú: +Tento text môže obsahovaÅ¥ formátovacie +príkazy jazyka Python, ktoré sa nahradia prísluÅ¡nými +hodnotami pri vytváraní stránky. NejdôležitejÅ¡ie premenné sú:
      -
    • real_name - `Pekné' meno konferencie, - bežne názov, ktorý má zachovanú veľkosÅ¥ písmen (definované +
    • real_name - `Pekné' meno konferencie, + bežne názov, ktorý má zachovanú veľkosÅ¥ písmen (definované v nastaveniach). -
    • list_name - Meno konferencie, ako sa používa v URL - kde záleží na velikosti písmen (všetky písmená sú prevedené na malé). +
    • list_name - Meno konferencie, ako sa používa v URL + kde záleží na velikosti písmen (vÅ¡etky písmená sú prevedené na malé). -
    • host_name - Meno serveru (FQDN), na ktorom beží +
    • host_name - Meno serveru (FQDN), na ktorom beží konferencia. -
    • web_page_url - Základné URL pre Mailman. - Ak je použité napr. v tomto príkaze: +
    • web_page_url - Základné URL pre Mailman. + Ak je použité napr. v tomto príkaze: listinfo/%(list_name)s - ukazuje na stránku s informáciami o konferencii. + ukazuje na stránku s informáciami o konferencii. -
    • description - Krátky, charakteristický - popis konferencie (definovaný v nastaveniach). +
    • description - Krátky, charakteristický + popis konferencie (definovaný v nastaveniach). -
    • info - Kompletný, dlhý popis konferencie. +
    • info - Kompletný, dlhý popis konferencie. -
    • cgiext - prípona pridávaná ku CGI skriptom. +
    • cgiext - prípona pridávaná ku CGI skriptom. -
    + diff --git a/templates/sk/listinfo.html b/templates/sk/listinfo.html index b89d0a19..97ed94f2 100644 --- a/templates/sk/listinfo.html +++ b/templates/sk/listinfo.html @@ -1,151 +1,209 @@ - - - - Informácie ku konferencii <MM-List-Name> - - - - -

    -

    - Удалить подпиÑку на - Другие подпиÑки на Ñервере -
    + + + + - + +
    +

    +Удалить подпиÑку на +Другие подпиÑки на Ñервере +
    Кликните Ñ‡ÐµÐºÐ±Ð¾ÐºÑ Ð´Ð»Ñ Ð¿Ð¾Ð´Ñ‚Ð²ÐµÑ€Ð¶Ð´ÐµÐ½Ð¸Ñ Ð½Ð°Ð¼ÐµÑ€ÐµÐ½Ð¸Ñ Ð¾Ñ‚Ð¿Ð¸Ñки и нажмите на Ñту кнопку Ð´Ð»Ñ ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ Ð’Ð°ÑˆÐµÐ¹ подпиÑки. Предупреждение: Это дейÑтвие будет иметь немедленный Ñффект!

    -

    Ð’Ñ‹ также можете получить ÑпиÑок вÑех Ñвоих подпиÑок на Ñервере +

    Ð’Ñ‹ также можете получить ÑпиÑок вÑех Ñвоих подпиÑок на Ñервере ; еÑли Ð’Ñ‹ хотите изменить параметры какой-либо из них, нажмите на Ñту кнопку.

    -

    -
    - - - - - +
    - Пароль Ð´Ð»Ñ ÑпиÑка раÑÑылки -
    - -

    Забыли Ñвой пароль?

    + + + - -
    +Пароль Ð´Ð»Ñ ÑпиÑка раÑÑылки +
    + +

    Забыли Ñвой пароль?

    Ðажмите Ñту кнопку, чтобы получить пароль на Ð°Ð´Ñ€ÐµÑ Ð’Ð°ÑˆÐµÐ¹ подпиÑки. -

    -

    - -
    -
    - -
    -

    Изменить пароль

    - - - - - - - - -
    Ðовый пароль:
    Подтвердите новый пароль:
    - - -

    Изменить во вÑех подпиÑках. -
    -
    - +

    +

    + +
    +

    + +
    +

    Изменить пароль

    + + + + + + + + +
    Ðовый пароль:
    Подтвердите новый пароль:
    + +

    Изменить во вÑех подпиÑках. +
    +

    - - +
    - Параметры Вашей подпиÑки на -
    +
    +Параметры Вашей подпиÑки на +
    -

    Текущие Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ Ð¾Ñ‚Ð¼ÐµÑ‡ÐµÐ½Ñ‹ -

    Обратите внимание, что Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ Ð½ÐµÐºÐ¾Ñ‚Ð¾Ñ€Ñ‹Ñ… параметров вы можете Изменить во вÑех подпиÑках. УÑтановка Ñтого чекбокÑа приведет к тому, что Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð±ÑƒÐ´ÑƒÑ‚ Ñделаны Ð´Ð»Ñ Ð²Ñех подпиÑок на Ñервере . Их ÑпиÑок можно получить при помощи кнопки СпиÑок моих подпиÑок, раÑположенной выше на Ñтой Ñтранице.

    - -
    - - ДоÑтавка Ñообщений

    + + - +

    - - - + +

    - - - - - - + + - - + - - - - + + - - + - - - + + - - - - +

    + +
    + +ДоÑтавка Ñообщений

    Выберите Включена чтобы получать ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð¸Ð· Ñтого ÑпиÑка раÑÑылки. Выберите Отключена еÑли Ð’Ñ‹ хотите временно приоÑтановить получение Ñообщений (например, на Ð²Ñ€ÐµÐ¼Ñ Ð¾Ñ‚Ð¿ÑƒÑка). ЕÑли Ð’Ñ‹ отключили доÑтавку Ñообщений, не забудьте затем включить ее обратно, Ñто не проиÑходит автоматичеÑки. -

    - Включена
    - Отключена

    - Изменить во вÑех подпиÑках -

    +Включена
    +Отключена

    +Изменить во вÑех подпиÑках +

    - Режим дайджеÑта

    +

    +Режим дайджеÑта

    ЕÑли Ð’Ñ‹ включите Ñтот режим, то будете получать ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð² виде дайджеÑта (обычно одного в день, или больше Ð´Ð»Ñ Ð¾Ñ‡ÐµÐ½ÑŒ активных ÑпиÑков раÑÑылки) вмеÑто каждого ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð¾Ñ‚Ð´ÐµÐ»ÑŒÐ½Ð¾. ПоÑле Ð²Ñ‹ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ñтого режима Ð’Ñ‹ можете получить еще один поÑледний дайджеÑÑ‚. -

    - Откл.
    - Вкл. -
    - ПриÑылать дайджеÑты в формате MIME или Plain Text?

    +

    +Откл.
    +Вкл. +
    +ПриÑылать дайджеÑты в формате MIME или Plain Text?

    Ваша Ð¿Ð¾Ñ‡Ñ‚Ð¾Ð²Ð°Ñ Ð¿Ñ€Ð¾Ð³Ñ€Ð°Ð¼Ð¼Ð° может поддерживать дайджеÑты в формате MIME или нет. Обычно формат MIME предпочтительнее, но еÑли дайджеÑты отображаютÑÑ Ð½ÐµÐ¿Ñ€Ð°Ð²Ð¸Ð»ÑŒÐ½Ð¾, выберите формат Plain Text. -

    - MIME
    - Plain Text

    - Изменить во вÑех подпиÑках -

    +MIME
    +Plain Text

    +Изменить во вÑех подпиÑках +

    - Получать Ñвои ÑобÑтвенные ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð² ÑпиÑок раÑÑылки?

    +

    +Получать Ñвои ÑобÑтвенные ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð² ÑпиÑок раÑÑылки?

    Ð’ общем Ñлучае Ð’Ñ‹ будете получать копию каждого Вашего ÑообщениÑ, отправленного в ÑпиÑок раÑÑылки. ЕÑли Ð’Ñ‹ Ñтого не хотите, выберите вариант Ðет. -

    - Ðет
    - Да -
    - Получать пиÑьмо-подтверждение об отправке ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð² ÑпиÑок раÑÑылки?

    -

    - Ðет
    - Да -
    - Получать ежемеÑÑчные Ð½Ð°Ð¿Ð¾Ð¼Ð¸Ð½Ð°Ð½Ð¸Ñ Ð¿Ð°Ñ€Ð¾Ð»Ñ?

    +

    +Ðет
    +Да +
    +Получать пиÑьмо-подтверждение об отправке ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð² ÑпиÑок раÑÑылки?

    +

    +Ðет
    +Да +
    +Получать ежемеÑÑчные Ð½Ð°Ð¿Ð¾Ð¼Ð¸Ð½Ð°Ð½Ð¸Ñ Ð¿Ð°Ñ€Ð¾Ð»Ñ?

    Один раз в меÑÑц Ð’Ñ‹ будете получать пиÑьмо, Ñодержащее пароль Ð´Ð»Ñ ÐºÐ°Ð¶Ð´Ð¾Ð³Ð¾ ÑпиÑка раÑÑылки на Ñтом Ñервере, на который Ð’Ñ‹ подпиÑаны. Ð’Ñ‹ можете отключить Ñти Ð½Ð°Ð¿Ð¾Ð¼Ð¸Ð½Ð°Ð½Ð¸Ñ Ð¾Ñ‚Ð´ÐµÐ»ÑŒÐ½Ð¾ Ð´Ð»Ñ Ñтого ÑпиÑка раÑÑылки или же Ð´Ð»Ñ Ð²Ñех подпиÑок Ñразу. -

    - Ðет
    - Да

    - Изменить во вÑех подпиÑках -

    - Скрыть ÑÐµÐ±Ñ Ð¸Ð· ÑпиÑка подпиÑчиков?

    +

    +Ðет
    +Да

    +Изменить во вÑех подпиÑках +

    +Скрыть ÑÐµÐ±Ñ Ð¸Ð· ÑпиÑка подпиÑчиков?

    Ðа Ñтранице проÑмотра ÑпиÑка подпиÑчиков Ваш Ñлектронный Ð°Ð´Ñ€ÐµÑ Ð±ÑƒÐ´ÐµÑ‚ показан в Ñпециальном виде, чтобы затруднить работу Ñборщиков адреÑов Ð´Ð»Ñ Ñ€Ð°ÑÑылки Ñпама. ЕÑли Ð’Ñ‹ не хотите, чтобы Ваш Ð°Ð´Ñ€ÐµÑ Ð²Ð¾Ð¾Ð±Ñ‰Ðµ там показывалÑÑ, выберите значение Да. -

    - Ðет
    - Да -
    - Какой Ñзык Ð’Ñ‹ предпочитаете?

    -

    - -
    - Ðа какие тематичеÑкие категории Ð’Ñ‹ хотели бы подпиÑатьÑÑ?

    +

    +Ðет
    +Да +
    +Какой Ñзык Ð’Ñ‹ предпочитаете?

    +

    + +
    +Ðа какие тематичеÑкие категории Ð’Ñ‹ хотели бы подпиÑатьÑÑ?

    Выбрав одну или неÑколько категорий, Ð’Ñ‹ можете отфильтровать ненужные ÑообщениÑ, Ð¿Ð¾Ð»ÑƒÑ‡Ð°Ñ Ñ‚Ð¾Ð»ÑŒÐºÐ¾ те из них, что ÑоответÑтвуют выбранным категориÑм.

    ЕÑли Ð’Ñ‹ не выбрали ни одну из предÑтавленных категорий, Ð’Ñ‹ будете получать вÑе ÑообщениÑ. -

    - -
    - Хотите ли Ð’Ñ‹ получать ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ ÐºÐ¾Ñ‚Ð¾Ñ€Ñ‹Ðµ не отноÑÑÑ‚ÑÑ Ð½Ð¸ к одной категории? - -

    Этот параметр дейÑтвует только в Ñлучае выбора Ñ…Ð¾Ñ‚Ñ Ð±Ñ‹ одной категории +

    + +
    +Хотите ли Ð’Ñ‹ получать ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ ÐºÐ¾Ñ‚Ð¾Ñ€Ñ‹Ðµ не отноÑÑÑ‚ÑÑ Ð½Ð¸ к одной категории? +

    Этот параметр дейÑтвует только в Ñлучае выбора Ñ…Ð¾Ñ‚Ñ Ð±Ñ‹ одной категории в поле выше. ЕÑли Ð’Ñ‹ выберете Ðет, то не будете получать ÑообщениÑ, которые не отноÑÑÑ‚ÑÑ Ð½Ð¸ к одной категории. Ð’ Ñлучае Да такие ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð±ÑƒÐ´ÑƒÑ‚ Вам переÑылатьÑÑ.

    ЕÑли в поле выше не выбрано ни одной категории, то Ð’Ñ‹ будете получать вÑе ÑообщениÑ. -

    - Ðет
    - Да -
    - Блокировать отправку неÑкольких копий ÑообщениÑ? - -

    Ð’Ñ‹ можете избежать Ð¿Ð¾Ð»ÑƒÑ‡ÐµÐ½Ð¸Ñ Ð´ÑƒÐ±Ð»Ð¸Ñ€ÑƒÑŽÑ‰Ð¸Ñ… друг друга Ñообщений, в которых Ваш Ð°Ð´Ñ€ÐµÑ ÑƒÐºÐ°Ð·Ð°Ð½ +

    +Ðет
    +Да +
    +Блокировать отправку неÑкольких копий ÑообщениÑ? +

    Ð’Ñ‹ можете избежать Ð¿Ð¾Ð»ÑƒÑ‡ÐµÐ½Ð¸Ñ Ð´ÑƒÐ±Ð»Ð¸Ñ€ÑƒÑŽÑ‰Ð¸Ñ… друг друга Ñообщений, в которых Ваш Ð°Ð´Ñ€ÐµÑ ÑƒÐºÐ°Ð·Ð°Ð½ в поле To: или Cc:. Ð”Ð»Ñ Ñтого выберите Да. При выборе Ðет Ð’Ñ‹ будете получать копии таких Ñообщений.

    ЕÑли Ð´Ð»Ñ ÑпиÑка раÑÑылки активирован режим перÑонализации Ñообщений, и Ð’Ñ‹ выбрали Ðет чтобы получать копии, то ÐºÐ°Ð¶Ð´Ð°Ñ Ð¸Ð· них будет иметь заголовок X-Mailman-Copy: yes. -

    - Ðет
    - Да

    - Изменить во вÑех подпиÑках -

    -
    -
    +Ðет
    +Да

    +Изменить во вÑех подпиÑках +

    +
    +
    -

    - - - - + + +

    diff --git a/templates/ru/private.html b/templates/ru/private.html index f289ba3f..b97c0d5c 100755 --- a/templates/ru/private.html +++ b/templates/ru/private.html @@ -1,31 +1,93 @@ - ÐÑƒÑ‚ÐµÐ½Ñ‚Ð¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ñ Ð´Ð»Ñ Ð´Ð¾Ñтупа к закрытому архиву ÑпиÑка раÑÑылки %(realname)s +ÐÑƒÑ‚ÐµÐ½Ñ‚Ð¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ñ Ð´Ð»Ñ Ð´Ð¾Ñтупа к закрытому архиву ÑпиÑка раÑÑылки %(realname)s - - -
    + + + %(message)s - - - - - - - - - - - - - - - -
    - ÐÑƒÑ‚ÐµÐ½Ñ‚Ð¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ñ Ð´Ð»Ñ Ð´Ð¾Ñтупа к закрытому архиву ÑпиÑка раÑÑылки %(realname)s -
    Электронный адреÑ:
    Пароль:
    -
    -

    Важно: вы должны разрешить прием файла cookies, + + + + + + + + + + + + + + + +
    +ÐÑƒÑ‚ÐµÐ½Ñ‚Ð¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ñ Ð´Ð»Ñ Ð´Ð¾Ñтупа к закрытому архиву ÑпиÑка раÑÑылки %(realname)s +
    Электронный адреÑ:
    Пароль:
    +
    +

    Важно: вы должны разрешить прием файла cookies, в противном Ñлучае, вам придетÑÑ Ð¿Ð¾Ð´Ñ‚Ð²ÐµÑ€Ð¶Ð´Ð°Ñ‚ÑŒ каждое дейÑтвие.

    Файл cookies иÑпользуетÑÑ Ð´Ð»Ñ Ñ‚Ð¾Ð³Ð¾, чтобы хранить параметры ÑеанÑа работы. @@ -36,21 +98,21 @@ параметров вашей подпиÑки и щелкнув на кнопку ОтключитьÑÑ.

    - - - - - - + + + +
    - Password Reminder -
    If you don't remember your password, enter your email address + + + + + + - - - - -
    +Password Reminder +
    If you don't remember your password, enter your email address above and click the Remind button and your password will be emailed to you.
    - +
    +

    diff --git a/templates/ru/roster.html b/templates/ru/roster.html index 0b39942d..a9057100 100644 --- a/templates/ru/roster.html +++ b/templates/ru/roster.html @@ -1,47 +1,107 @@ - - - ПодпиÑчики ÑпиÑка раÑÑылки <MM-List-Name> - - -

    - - - - - - - - - - - - - - - + + +ПодпиÑчики ÑпиÑка раÑÑылки <mm-list-name></mm-list-name> + + +

    +

    - ПодпиÑчики ÑпиÑка раÑÑылки -
    - -

    -

    - -

    Ð”Ð»Ñ Ð¿ÐµÑ€ÐµÑ…Ð¾Ð´Ð° на Ñтраницу параметров Вашей подпиÑки нажмите - на ÑÑылку Ñ Ð’Ð°ÑˆÐ¸Ð¼ Ñлектронным адреÑом.
    - (ДоÑтавка Ñообщений пользователÑм, указанным в Ñкобках, в наÑтоÑщий момент отключена.)

    -
    -
    - ПодпиÑчики Ñ Ð¾Ð±Ñ‹Ñ‡Ð½Ð¾Ð¹ доÑтавкой: -
    -
    -
    - ПодпиÑчики , получающие дайджеÑты: -
    -
    -

    -

    -

    -

    + + + + + + + + + + + + + +
    +ПодпиÑчики ÑпиÑка раÑÑылки +
    +

    +

    +

    Ð”Ð»Ñ Ð¿ÐµÑ€ÐµÑ…Ð¾Ð´Ð° на Ñтраницу параметров Вашей подпиÑки нажмите + на ÑÑылку Ñ Ð’Ð°ÑˆÐ¸Ð¼ Ñлектронным адреÑом.
    +(ДоÑтавка Ñообщений пользователÑм, указанным в Ñкобках, в наÑтоÑщий момент отключена.)

    +
    +
    +ПодпиÑчики Ñ Ð¾Ð±Ñ‹Ñ‡Ð½Ð¾Ð¹ доÑтавкой: +
    +
    +
    +ПодпиÑчики , получающие дайджеÑты: +
    +
    +

    +

    +

    +

    - - - + +

    + diff --git a/templates/ru/subscribe.html b/templates/ru/subscribe.html index 1f3003b6..78077884 100644 --- a/templates/ru/subscribe.html +++ b/templates/ru/subscribe.html @@ -1,8 +1,70 @@ -Результаты подпиÑки на ÑпиÑок раÑÑылки <MM-List-Name> +Результаты подпиÑки на ÑпиÑок раÑÑылки <mm-list-name></mm-list-name> -

    Результаты подпиÑки на ÑпиÑок раÑÑылки

    - - - +

    Результаты подпиÑки на ÑпиÑок раÑÑылки

    + + + diff --git a/templates/sk/admindbdetails.html b/templates/sk/admindbdetails.html index 850a83d7..b12c6ba7 100644 --- a/templates/sk/admindbdetails.html +++ b/templates/sk/admindbdetails.html @@ -1,62 +1,124 @@ -Administratívne požiadavky môžu byÅ¥ zobrazené dvoma spôsobmi. BuÄ -hromadne alebo detailne. -Pri hromadnom zobrazení sú všetky požiadavky pochádzajúce od -toho istého úÄastníka zobrazené spolu a môže o nich byÅ¥ rozhodnuté -naraz. Pri detailnom zobrazení je každý príspevok zobrazený -samostatne a administrátor rozhoduje o každom príspevku jednotlivo. -Detailné zobrazenie obsahuje naviac všetky hlaviÄky a ÄasÅ¥ z tela -správy. - -

    Pre pozastavené príspevky vyberte jednu z týchto Äinností: +Administratívne požiadavky môžu byÅ¥ zobrazené dvoma spôsobmi. BuÄ +hromadne alebo detailne. +Pri hromadnom zobrazení sú vÅ¡etky požiadavky pochádzajúce od +toho istého úÄastníka zobrazené spolu a môže o nich byÅ¥ rozhodnuté +naraz. Pri detailnom zobrazení je každý príspevok zobrazený +samostatne a administrátor rozhoduje o každom príspevku jednotlivo. +Detailné zobrazenie obsahuje naviac vÅ¡etky hlaviÄky a ÄasÅ¥ z tela +správy. + +

    Pre pozastavené príspevky vyberte jednu z týchto Äinností:

      -
    • PonechaÅ¥ - NechaÅ¥ správu ÄakaÅ¥ vo fronte, nemá vplyv na -nastavenie preposielania, to znamená, že správu môžete preposlaÅ¥ +
    • PonechaÅ¥ - NechaÅ¥ správu ÄakaÅ¥ vo fronte, nemá vplyv na +nastavenie preposielania, to znamená, že správu môžete preposlaÅ¥ a pritom ponechaÅ¥ vo fronte -
    • AkceptovaÅ¥ - RozoslaÅ¥ správu do konferencie. Ak nejde o správu, -ale o žiadosÅ¥ o prihlásenie úÄastníka, tak bude prihlásený. - -
    • ZamietnuÅ¥ - VrátiÅ¥ správu odosielateľovi. Môžete uviesÅ¥ i dôvod, -ktorý bude uvedený v odpovedi na zamietnutie. Správa bude vymazaná z fronty. -Ak ide o žiadosÅ¥ o prihlásenie úÄastníka, tak bude zamietnutá. - -
    • ZahodiÅ¥ - vhodné pre spam. Správa bude potichu odstránená, nikomu -nebude niÄ poslané. Ak ide o žiadosÅ¥ o prihlásenie úÄastníka, tak mu nebude -odoslaná odpoveÄ. -
    - -

    Výberom voľby PonechaÅ¥ môžete ponechaÅ¥ správu vo fronte pre -pre neskoršie vybavenie správcom. To sa využíva predovšetkým pri správach, -ktoré nie sú spamom a vyžadujú Äalšie odvetné akcie. - -S pomocou voľby PreposlaÅ¥ môžete odoslaÅ¥ kópiu správy na adresu, -ktorú vyplníte do poľa vedľa tejto voľby. Ak potrebujete -niektorý príspevok pre rozoslaním zmeniÅ¥, musíte si ho preposlaÅ¥ na svoju -adresu a pôvodnú správu zahodiÅ¥. Potom, Äo správu upravíte, musíte ju poslaÅ¥ -späÅ¥ do konferencie, prípadne s hlaviÄkou Approved:, do ktorej -doplníte heslo pre danú konferenciu. Etika vyžaduje, aby ste v takom prípade -do textu uviedli upozornenie, že ste správu pozmenili a vysvetlili, preÄo +

  • AkceptovaÅ¥ - RozoslaÅ¥ správu do konferencie. Ak nejde o správu, +ale o žiadosÅ¥ o prihlásenie úÄastníka, tak bude prihlásený. + +
  • ZamietnuÅ¥ - VrátiÅ¥ správu odosielateľovi. Môžete uviesÅ¥ i dôvod, +ktorý bude uvedený v odpovedi na zamietnutie. Správa bude vymazaná z fronty. +Ak ide o žiadosÅ¥ o prihlásenie úÄastníka, tak bude zamietnutá. + +
  • ZahodiÅ¥ - vhodné pre spam. Správa bude potichu odstránená, nikomu +nebude niÄ poslané. Ak ide o žiadosÅ¥ o prihlásenie úÄastníka, tak mu nebude +odoslaná odpoveÄ. +
  • +

    Výberom voľby PonechaÅ¥ môžete ponechaÅ¥ správu vo fronte pre +pre neskorÅ¡ie vybavenie správcom. To sa využíva predovÅ¡etkým pri správach, +ktoré nie sú spamom a vyžadujú ÄalÅ¡ie odvetné akcie. + +S pomocou voľby PreposlaÅ¥ môžete odoslaÅ¥ kópiu správy na adresu, +ktorú vyplníte do poľa vedľa tejto voľby. Ak potrebujete +niektorý príspevok pre rozoslaním zmeniÅ¥, musíte si ho preposlaÅ¥ na svoju +adresu a pôvodnú správu zahodiÅ¥. Potom, Äo správu upravíte, musíte ju poslaÅ¥ +späť do konferencie, prípadne s hlaviÄkou Approved:, do ktorej +doplníte heslo pre danú konferenciu. Etika vyžaduje, aby ste v takom prípade +do textu uviedli upozornenie, že ste správu pozmenili a vysvetlili, preÄo ste tak konali. -

    Pri nových úÄastníkoch sa zvykne nastaviÅ¥ moderácia, aby ich -príspevky boli najprv preverené moderátorom. Zrušením nastavenia moderácie -umožníte úÄastníkovi prispievaÅ¥ do konferencie bez kontroly jeho príspevkov. +

    Pri nových úÄastníkoch sa zvykne nastaviÅ¥ moderácia, aby ich +príspevky boli najprv preverené moderátorom. ZruÅ¡ením nastavenia moderácie +umožníte úÄastníkovi prispievaÅ¥ do konferencie bez kontroly jeho príspevkov. -

    Ak odosielateľ nie je úÄastníkom konferencie, môžete jeho e-mailovú -adresu vložiÅ¥ do filtra odesielateľov. Funkcia týchto filtrov -je popísaná na stránke s konfiguráciou filtrovania -. Príspevky môžu byÅ¥ na základe filtrov buÄ automaticky -akceptované, automaticky podržané do rozhodnutia moderátora, -automaticky vrátené alebo automaticky zahodené (spam). Ak je -odosielateľ úÄastníkom alebo je už zahrnutý vo filtroch, táto voľba -nebude k dispozícii. +

    Ak odosielateľ nie je úÄastníkom konferencie, môžete jeho e-mailovú +adresu vložiÅ¥ do filtra odesielateľov. Funkcia týchto filtrov +je popísaná na stránke s konfiguráciou filtrovania +. Príspevky môžu byÅ¥ na základe filtrov buÄ automaticky +akceptované, automaticky podržané do rozhodnutia moderátora, +automaticky vrátené alebo automaticky zahodené (spam). Ak je +odosielateľ úÄastníkom alebo je už zahrnutý vo filtroch, táto voľba +nebude k dispozícii. -

    Ak ste už prešli celou stránkou, kliknite na jej hornom alebo -spodnom okraji na tlaÄidlo PotvrdiÅ¥ všetky zmeny. Tým sa -prenesú prevedené zmeny do databázý a server prevedie podľa daných pokyov -potrebné operácie. Ak u niektorej položky nevyberiete ani jednu voľbu, -nebude nijak ovplyvnená a zostane uložena v databáze. Budete ju môcÅ¥ -kedykoľvek nabudúce zmeniÅ¥. +

    Ak ste už preÅ¡li celou stránkou, kliknite na jej hornom alebo +spodnom okraji na tlaÄidlo PotvrdiÅ¥ vÅ¡etky zmeny. Tým sa +prenesú prevedené zmeny do databázý a server prevedie podľa daných pokyov +potrebné operácie. Ak u niektorej položky nevyberiete ani jednu voľbu, +nebude nijak ovplyvnená a zostane uložena v databáze. Budete ju môcÅ¥ +kedykoľvek nabudúce zmeniÅ¥. -

    Návrat na prehľad Äakajúcích požiadaviek +

    Návrat na prehľad Äakajúcích požiadaviek +

    \ No newline at end of file diff --git a/templates/sk/admindbpreamble.html b/templates/sk/admindbpreamble.html index 3ae12dcc..8398a1fc 100644 --- a/templates/sk/admindbpreamble.html +++ b/templates/sk/admindbpreamble.html @@ -1,14 +1,77 @@ -Na tejto stránke nájdete všetky príspevky do konferencie %(listname)s, -ktoré neboli z nejakého dôvodu rozoslané a Äakajú na Vaše rozhodnutie. -Momentálne sú zobrazené %(description)s +Na tejto stránke nájdete vÅ¡etky príspevky do konferencie %(listname)s, +ktoré neboli z nejakého dôvodu rozoslané a Äakajú na VaÅ¡e rozhodnutie. +Momentálne sú zobrazené %(description)s -

    U každej administratívnej požiadavky musíte vybraÅ¥ ÄinnosÅ¥, ktorá sa -s ňou má previesÅ¥ a nakoniec vybrané Äinnosti uskutoÄníte kliknutím na -PotvrdiÅ¥ všetky zmeny. Podrobnejšie informácie o tom, ako -zaobchádzaÅ¥ s administratívnym rozhraním nájdete -pod týmto odkazom. +

    U každej administratívnej požiadavky musíte vybraÅ¥ ÄinnosÅ¥, ktorá sa +s ňou má previesÅ¥ a nakoniec vybrané Äinnosti uskutoÄníte kliknutím na +PotvrdiÅ¥ vÅ¡etky zmeny. PodrobnejÅ¡ie informácie o tom, ako +zaobchádzaÅ¥ s administratívnym rozhraním nájdete +pod týmto odkazom. -

    Tiež si môžete prezrieÅ¥ prehľad všetkých -Äakajúcich požiadaviek zobrazený tak, že -príspevky od jedného prispievateľa sú zlúÄené dolopy a môžete -o nich rozhodnúÅ¥ naraz. +

    Tiež si môžete prezrieÅ¥ prehľad vÅ¡etkých +Äakajúcich požiadaviek zobrazený tak, že +príspevky od jedného prispievateľa sú zlúÄené dolopy a môžete +o nich rozhodnúť naraz. +

    \ No newline at end of file diff --git a/templates/sk/admindbsummary.html b/templates/sk/admindbsummary.html index 54ef080d..497d6151 100644 --- a/templates/sk/admindbsummary.html +++ b/templates/sk/admindbsummary.html @@ -1,18 +1,81 @@ -Prehľad požiadaviek pre konferenciu %(listname)s. +Prehľad požiadaviek pre konferenciu %(listname)s. -Táto stránka obsahuje prehľad vštkých požiadaviek pre administrátora -zoradený tak, že požiadavky od jedného úÄastníka sú zlúÄené dokopy -a môže o nich byÅ¥ rozhodnuté naraz. +Táto stránka obsahuje prehľad vÅ¡tkých požiadaviek pre administrátora +zoradený tak, že požiadavky od jedného úÄastníka sú zlúÄené dokopy +a môže o nich byÅ¥ rozhodnuté naraz. -Žiadosti o prihlásenie a odhlásenie sú radené ako prvé a za nimi -prípadné príspevky, ktoré Äakajú na schválenie moderátorom. +Žiadosti o prihlásenie a odhlásenie sú radené ako prvé a za nimi +prípadné príspevky, ktoré Äakajú na schválenie moderátorom. -

    Pri každej administratívnej požiadavke musíte vybraÅ¥ ÄinnosÅ¥, ktorá sa -s ňou má previesÅ¥ a nakoniec vybrané Äinnosti uskutoÄníte kliknutím na -PotvrdiÅ¥ všetky zmeny. Podrobnejšie informáce o tom, ako -zaobchádzaÅ¥ s administratívnym rozhraním nájdete -pod týmto odkazom. +

    Pri každej administratívnej požiadavke musíte vybraÅ¥ ÄinnosÅ¥, ktorá sa +s ňou má previesÅ¥ a nakoniec vybrané Äinnosti uskutoÄníte kliknutím na +PotvrdiÅ¥ vÅ¡etky zmeny. PodrobnejÅ¡ie informáce o tom, ako +zaobchádzaÅ¥ s administratívnym rozhraním nájdete +pod týmto odkazom. -

    Tiež si môžete prezrieÅ¥ detailný prehľad všetkých -Äakajúcich požiadaviek kde je každý -príspovek zobrazovaný samostatne. +

    Tiež si môžete prezrieÅ¥ detailný prehľad vÅ¡etkých +Äakajúcich požiadaviek kde je každý +príspovek zobrazovaný samostatne. +

    \ No newline at end of file diff --git a/templates/sk/admlogin.html b/templates/sk/admlogin.html index d3a130d0..0da55025 100755 --- a/templates/sk/admlogin.html +++ b/templates/sk/admlogin.html @@ -1,37 +1,97 @@ - %(listname)s: prihlásenie ako %(who)s - - - -
    +%(listname)s: prihlásenie ako %(who)s + + + + %(message)s - - - - - - - - - - - -
    - %(listname)s: prihlásenie ako - %(who)s -
    Heslo pre %(who)s:
    -
    -

    Pozor: Aby systém fungoval - správne, musíte maÅ¥ vo Vašom prehliadaÄi povolené cookies. + + + + + + + + + + + +
    +%(listname)s: prihlásenie ako + %(who)s +
    Heslo pre %(who)s:
    +
    +

    Pozor: Aby systém fungoval + správne, musíte maÅ¥ vo VaÅ¡om prehliadaÄi povolené cookies. -

    Cookies sa používajú na to, aby ste nemuseli - každú požiadavku znovu potvrdzovaÅ¥ heslom. Budú zmazané - keÄ zavriete prehliadaÄ alebo pri odhlásení. - OdhlásiÅ¥ sa môžete kliknutím na OdhlásiÅ¥. - Tento odkaz uvidíte až po úspešnom prihlásení. -

    +

    Cookies sa používajú na to, aby ste nemuseli + každú požiadavku znovu potvrdzovaÅ¥ heslom. Budú zmazané + keÄ zavriete prehliadaÄ alebo pri odhlásení. + OdhlásiÅ¥ sa môžete kliknutím na OdhlásiÅ¥. + Tento odkaz uvidíte až po úspeÅ¡nom prihlásení. +

    diff --git a/templates/sk/archidxentry.html b/templates/sk/archidxentry.html index f9bb57aa..2efd9486 100644 --- a/templates/sk/archidxentry.html +++ b/templates/sk/archidxentry.html @@ -1,4 +1,67 @@ -
  • %(subject)s -  -%(author)s - +
  • %(subject)s +  +%(author)s + +
  • \ No newline at end of file diff --git a/templates/sk/archidxfoot.html b/templates/sk/archidxfoot.html index 5995ded2..ae88619c 100644 --- a/templates/sk/archidxfoot.html +++ b/templates/sk/archidxfoot.html @@ -1,21 +1,84 @@ - -

    - Dátum posledného príspevku: - %(lastdate)s
    - Archivované: %(archivedate)s -

    -

      -
    • Správy zoradené podľa: + +

      +Dátum posledného príspevku: +%(lastdate)s
      +Archivované: %(archivedate)s +

      +

      -

      -


      - Tento archív bol vytvorený pomocou programu +
    +

    +


    +Tento archív bol vytvorený pomocou programu Pipermail %(version)s. - - + + +

    \ No newline at end of file diff --git a/templates/sk/archidxhead.html b/templates/sk/archidxhead.html index 3ef297e8..d081692d 100644 --- a/templates/sk/archidxhead.html +++ b/templates/sk/archidxhead.html @@ -1,24 +1,88 @@ - - - Archív konferencie %(listname)s, zodadené podľa %(archtype)s - + + + +Archív konferencie %(listname)s, zodadené podľa %(archtype)s + %(encoding)s - - - -

    %(listname)s: %(archive)s, zoradené podľa %(archtype)s

    -
      -
    • Správy zoradené podľa: + + + +

      %(listname)s: %(archive)s, zoradené podľa %(archtype)s

      + -

      Od: %(firstdate)s
      - Do: %(lastdate)s
      - PoÄet príspevkov: %(size)s

      -

        +
      +

      Od: %(firstdate)s
      +Do: %(lastdate)s
      +PoÄet príspevkov: %(size)s

      +

        +

      \ No newline at end of file diff --git a/templates/sk/archlistend.html b/templates/sk/archlistend.html index 9bc052dd..492cf01a 100644 --- a/templates/sk/archlistend.html +++ b/templates/sk/archlistend.html @@ -1 +1,63 @@ -
    + diff --git a/templates/sk/archliststart.html b/templates/sk/archliststart.html index 70b7d84f..bbbdf7bc 100644 --- a/templates/sk/archliststart.html +++ b/templates/sk/archliststart.html @@ -1,4 +1,67 @@ - - - - +
    ArchívZobraziÅ¥ podľa:Verzia na stiahnutie
    + + + +
    ArchívZobraziť podľa:Verzia na stiahnutie
    \ No newline at end of file diff --git a/templates/sk/archtoc.html b/templates/sk/archtoc.html index dd2626a0..d3481e5a 100644 --- a/templates/sk/archtoc.html +++ b/templates/sk/archtoc.html @@ -1,20 +1,83 @@ - - - Archív konferencie %(listname)s - + + + +Archív konferencie %(listname)s + %(meta)s - - -

    Archív konferencie %(listname)s

    -

    - ZobraziÅ¥ viac informácií o konferencii - alebo stiahnuÅ¥ celý archív + + +

    Archív konferencie %(listname)s

    +

    + Zobraziť viac informácií o konferencii + alebo stiahnuť celý archív (%(size)s).

    %(noarchive_msg)s %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/sk/archtocentry.html b/templates/sk/archtocentry.html index cd82c675..34f99e31 100644 --- a/templates/sk/archtocentry.html +++ b/templates/sk/archtocentry.html @@ -1,12 +1,75 @@ - -
    %(archivelabel)s: - [ vlákna ] - [ predmetu ] - [ autora ] - [ dátumu ] -
    %(archivelabel)s: +[ vlákna ] +[ predmetu ] +[ autora ] +[ dátumu ] +
    - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + +
    - -- - -
    -

      -

    - O konferencii - - - -
    -

    -

    Ak si chcete prezrieÅ¥ staré príspevky do konferencie, - navštívte archív konferencie - . - -

    -
    - Používanie konferencie - -
    - Príspevky do konferencie posielajte na adresu - . - -

    Nižšie na tejto stránke sa môžete sa prihlásiÅ¥ do konferencie - alebo upraviÅ¥ Vaše nastavenia v konferencii. -

    - Prihlásenie do konferencie - -
    -

    - Do konferencie sa prihlásite vyplnením nasledujúceho - formulára. - -

      - - - - - - - - - - - + + + +Informácie ku konferencii <mm-list-name></mm-list-name> + + +

      +

      Vaša e-mailová adresa: -  
      Vaše meno (nepovinné): 
      + + + + + + + + + + + + + + + + + + + + + + + + - - - - - -
      + -- + +
      +

        +

      +O konferencii + + + +
      +

      +

      Ak si chcete prezrieť staré príspevky do konferencie, + navštívte archív konferencie + . + +

      +
      +Používanie konferencie + +
      + Príspevky do konferencie posielajte na adresu + . - Heslo môžete uviesÅ¥ nižšie. - Toto nebude chránené a slúži iba na to, aby tretie osoby nemohli - meniÅ¥ Vaše nastavenia. Nepoužívajte heslo, ktoré je pre Vás - dôležité, lebo Vám bude nezabezpeÄene zasielané elektronickou - poštu. +

      Nižšie na tejto stránke sa môžete sa prihlásiť do konferencie + alebo upraviť Vaše nastavenia v konferencii. +

      +Prihlásenie do konferencie + +
      +

      + Do konferencie sa prihlásite vyplnením nasledujúceho + formulára. + +

        + + + + + + + + + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - - - -
        Vaša e-mailová adresa: + 
        Vaše meno (nepovinné): 
        Heslo môžete uviesÅ¥ nižšie. + Toto nebude chránené a slúži iba na to, aby tretie osoby nemohli + meniÅ¥ VaÅ¡e nastavenia. Nepoužívajte heslo, ktoré je pre Vás + dôležité, lebo Vám bude nezabezpeÄene zasielané elektronickou + poÅ¡tu. -

        Ak heslo nezadáte, bude pre Vás vytvorené náhodné heslo, - ktoré Vám bude zaslané potom, ako potvrdíte prihlásenie. - Heslo si môžete daÅ¥ kedykoľvek zaslaÅ¥ na adresu, na ktorú ste - sa prihlásili. - -
        -
        Tu napíšte heslo: 
        Zopakujte heslo pre kontrolu: 
        V akom jazyku majú byÅ¥ zobrazované správy?  
        Chcete prijímaÅ¥ poštu raz denne v súhrnnej správe? +

        Ak heslo nezadáte, bude pre Vás vytvorené náhodné heslo, + ktoré Vám bude zaslané potom, ako potvrdíte prihlásenie. + Heslo si môžete dať kedykoľvek zaslať na adresu, na ktorú ste + sa prihlásili. + + +
        Tu napíšte heslo: 
        Zopakujte heslo pre kontrolu: 
        V akom jazyku majú byť zobrazované správy?  
        Chcete prijímaÅ¥ poÅ¡tu raz denne v súhrnnej správe? Nie - Áno -
        -
        -
        - -
      -
      - - ÚÄastníci konferencie - -
      - - - -

      - - - -

      - - - +
    Nie + Ãno +
    +
    +
    + + + + + +ÚÄastníci konferencie + + + + + + + + +

    + + + +

    + +

    + +

    + diff --git a/templates/sk/options.html b/templates/sk/options.html index 8db9cf59..e2f0343a 100644 --- a/templates/sk/options.html +++ b/templates/sk/options.html @@ -1,317 +1,348 @@ - - <MM-Presentable-User> - osobné nastavenia pre <MM-List-Name> - - - - - -
    - - - osobné nastavenia pre - -
    + +<mm-presentable-user> - osobné nastavenia pre <mm-list-name> +</mm-list-name></mm-presentable-user> + + + + +
    + + - osobné nastavenia pre + +

    - - - - - +
    - - nastavenia a heslá - pre konferenciu . -
    - - - - -

    -

    + + + +
    + - nastavenia a heslá + pre konferenciu . +
    + + +

    +

    - - +

    - - - -
    - - Zmena osobných nastavení pre konferenciu -
    Tu si môžete zmeniÅ¥ adresu, na ktorú ste prihlásený - do konferencie vložením novej adresy do nižšie uvedených políÄok. - Poznámka: bude Vám zaslaná overovacia správa, ktorú musíte potvrdiÅ¥. - E-mailová adresa bude zmenená až po potvrdení. - -

    Na potvrdenie bude systém ÄakaÅ¥ približne . - -

    Tiež môžete zmeniÅ¥ alebo nastaviÅ¥ Vaše meno (nepovinná voľba) - (napr. Peter Novák). - -

    Ak chcete urobiÅ¥ tieto zmeny pre všetky konferencie na serveri - , v ktorých ste úÄastníkom, zaškrtnite políÄko - zmeniÅ¥ globálne. - -

    - - + + + + +
    Nová e-mailová adresa: + + + + - - - - -
    + +Zmena osobných nastavení pre konferenciu +
    Tu si môžete zmeniÅ¥ adresu, na ktorú ste prihlásený + do konferencie vložením novej adresy do nižšie uvedených políÄok. + Poznámka: bude Vám zaslaná overovacia správa, ktorú musíte potvrdiÅ¥. + E-mailová adresa bude zmenená až po potvrdení. + +

    Na potvrdenie bude systém ÄakaÅ¥ približne . + +

    Tiež môžete zmeniť alebo nastaviť Vaše meno (nepovinná voľba) + (napr. Peter Novák). + +

    Ak chcete urobiÅ¥ tieto zmeny pre vÅ¡etky konferencie na serveri + , v ktorých ste úÄastníkom, zaÅ¡krtnite políÄko + zmeniÅ¥ globálne. + +

    + + - - - - - -
    Nová e-mailová adresa:
    ZopakovaÅ¥ novú - adresu:
    -
    - - - - -
    Vaše meno - (nepovinné):
    -
    -

    zmeniÅ¥ globálne

    - +
    Zopakovať novú + adresu:
    +

    + + + + +
    Vaše meno + (nepovinné):
    + +
    +

    zmeniť globálne

    +

    - - - - - - - - + + + + %(textlink)s - diff --git a/templates/sl/article.html b/templates/sl/article.html index b9a7e745..7c421e3d 100644 --- a/templates/sl/article.html +++ b/templates/sl/article.html @@ -1,12 +1,13 @@ - - - %(title)s - - - - - + +

    Arhiv seznama %(listname)s

    +

    + Na seznamu še ni bilo objavljenih sporoèil, zato je arhiv trenutno prazen. Ogledate si lahko dodatne informacije o tem seznamu.

    - - + + diff --git a/templates/sl/headfoot.html b/templates/sl/headfoot.html index 3f9dfbd5..6ecde199 100644 --- a/templates/sl/headfoot.html +++ b/templates/sl/headfoot.html @@ -1,27 +1,89 @@ -To besedilo lahko vkljuèuje -Python +To besedilo lahko vkljuèuje +Python nize, ki jih uporabljamo namesto atributov seznama. Seznam -mo¾nih nadomestil: +mo¾nih nadomestil:
      -
    • real_name - `Javno' ime seznama; ponavadi - je to kar ime seznama, pisano z veliko zaèetnico. +
    • real_name - `Javno' ime seznama; ponavadi + je to kar ime seznama, pisano z veliko zaèetnico.
    • list_name - Ime, s katerim se seznam prepozna - v URL-jih, kjer so pomembne velike in male èrke.(Za obratno zdru¾ljivost + v URL-jih, kjer so pomembne velike in male èrke.(Za obratno zdru¾ljivost je enakovredno _internal_name.)
    • host_name - Polno in veljavno ime domene, v - kateri deluje stre¾nik. + kateri deluje stre¾nik.
    • web_page_url - Osnovni URL za Mailman. Temu lahko dodate npr. listinfo/%(list_name)s za stran s - podatki o poštnem seznamu. + podatki o poÅ¡tnem seznamu. -
    • description - Kratek opis poštnega seznama. +
    • description - Kratek opis poÅ¡tnega seznama. -
    • info - Daljši opis poštnega seznama. +
    • info - DaljÅ¡i opis poÅ¡tnega seznama.
    • cgiext - Pripona, dodana za CGI skripte. -
    + diff --git a/templates/sl/listinfo.html b/templates/sl/listinfo.html index 84c676a9..e57344ad 100644 --- a/templates/sl/listinfo.html +++ b/templates/sl/listinfo.html @@ -1,145 +1,205 @@ - - - - <MM-List-Name> Stran s podatki - - - -

    -

    - Odhlásenie z konferencie - Vaša úÄasÅ¥ na Äalšich konferenciách na serveri -
    - Zaškrtnite potvrdzovacie políÄko a stlaÄte tlaÄidlo pre - odhlásenie sa z tejto konferencie. Pozor: - Táto zmena bude prevedená okamžite! + + + + - + +
    +

    +Odhlásenie z konferencie +VaÅ¡a úÄasÅ¥ na ÄalÅ¡ich konferenciách na serveri +
    + ZaÅ¡krtnite potvrdzovacie políÄko a stlaÄte tlaÄidlo pre + odhlásenie sa z tejto konferencie. Pozor: + Táto zmena bude prevedená okamžite!

    -

    - Tu si môžete pozrieÅ¥ zoznam všetkých Äalších konferencií na serveri - , do ktorých ste prihlásený. Cez tento zoznam sa dostanete - na Vaše nastavenia k tímto konferenciám. +

    + Tu si môžete pozrieÅ¥ zoznam vÅ¡etkých Äalších konferencií na serveri + , do ktorých ste prihlásený. Cez tento zoznam sa dostanete + na VaÅ¡e nastavenia k tímto konferenciám.

    -

    -
    - - - - - - -
    - Vaše heslo pre konferenciu -
    - -
    -

    Zabudli ste heslo?

    -
    - Kliknite na toto tlaÄidlo a Vaše heslo Vám bude zaslané na adresu, - na ktorú ste prihlásený. -

    -

    - -
    -
    - -
    -

    Zmena hesla

    - - - - - - - - -
    Nové - heslo:
    ZopakovaÅ¥ - nové heslo:
    - - -

    zmeniÅ¥ globálne -
    -
    - + + + +
    +Vaše heslo pre konferenciu +
    + +
    +

    Zabudli ste heslo?

    +
    + Kliknite na toto tlaÄidlo a VaÅ¡e heslo Vám bude zaslané na adresu, + na ktorú ste prihlásený. +

    +

    + +
    +

    + +
    +

    Zmena hesla

    + + + + + + + + +
    Nové + heslo:
    Zopakovať + nové heslo:
    + +

    zmeniť globálne +
    +

    - - +
    - Osobné nastavenia pre konferenciu -
    +
    +Osobné nastavenia pre konferenciu +
    -

    -Momentálne aktívne nastavenia sú zaškrtnuté. - -

    Poznámka: niektoré nastavenia majú možnosÅ¥ nastaviÅ¥ globálne. -Zaškrtnutím bude toto nastavenie aplikované na všetky konferencie na -serveri , v ktorých ste úÄastníkom. Kliknutím na ZobraziÅ¥ moje -Äalšie konferencie vyššie získate zoznam konferencií, -do ktorých ste prihlásený. +Momentálne aktívne nastavenia sú zaÅ¡krtnuté. +

    Poznámka: niektoré nastavenia majú možnosÅ¥ nastaviÅ¥ globálne. +ZaÅ¡krtnutím bude toto nastavenie aplikované na vÅ¡etky konferencie na +serveri , v ktorých ste úÄastníkom. Kliknutím na ZobraziÅ¥ moje +ÄalÅ¡ie konferencie vyššie získate zoznam konferencií, +do ktorých ste prihlásený.

    - -
    - - DoruÄovanie pošty

    - Nastavte na zapnuté, ak chcete prijímaÅ¥ správy zasielané - do tejto konferencie. Použite voľbu vypnuté ak chcete - zostaÅ¥ prihlásený, ale nechcete poÄas urÄitej doby prijímaÅ¥ poštu - (napr. ak idete na dovolenku). Ak vypnete doruÄovanie pošty, - nezabudnite ho po Vašom návrate opäÅ¥ zapnúÅ¥, automaticky zapnuté + + - +

    - - - + + - - - - + - - - - - - - - + + + + - - + - - - - - +

    + +
    + +DoruÄovanie poÅ¡ty

    + Nastavte na zapnuté, ak chcete prijímaÅ¥ správy zasielané + do tejto konferencie. Použite voľbu vypnuté ak chcete + zostaÅ¥ prihlásený, ale nechcete poÄas urÄitej doby prijímaÅ¥ poÅ¡tu + (napr. ak idete na dovolenku). Ak vypnete doruÄovanie poÅ¡ty, + nezabudnite ho po VaÅ¡om návrate opäť zapnúť, automaticky zapnuté nebude. -

    - zapnuté
    - vypnuté

    - nastaviÅ¥ globálne -

    +zapnuté
    +vypnuté

    +nastaviť globálne +

    - Dávkové doruÄovanie pošty (digest)

    - Ak zapnete dávkové doruÄovanie pošty, budete dostávaÅ¥ príspevky - zlúÄené do jednej správy raz denne (pri niektorých veľmi aktívnych - konferenciách aj Äastejšie) namiesto každého príspevku samostatne. - Ak vypnete doruÄovanie vo forme dávok, môže Vám byÅ¥ doruÄená - ešte jedna posledná dávka (digest). -

    - vypnuté
    - zapnuté -
    - OdoberaÅ¥ dávky vo forme Äistého textu alebo MIME? -

    Váš poštový program môže, ale nemusí porporovaÅ¥ digest vo formáte - MIME. OdporúÄame odoberaÅ¥ dávky vo formáte MIME, ale ak máte problém - takúto poštu ÄítaÅ¥, vyberte Äistý text. -

    - MIME
    - Äistý text

    - nastaviÅ¥ globálne -

    +Dávkové doruÄovanie poÅ¡ty (digest)

    + Ak zapnete dávkové doruÄovanie poÅ¡ty, budete dostávaÅ¥ príspevky + zlúÄené do jednej správy raz denne (pri niektorých veľmi aktívnych + konferenciách aj ÄastejÅ¡ie) namiesto každého príspevku samostatne. + Ak vypnete doruÄovanie vo forme dávok, môže Vám byÅ¥ doruÄená + eÅ¡te jedna posledná dávka (digest). +

    +vypnuté
    +zapnuté +
    +OdoberaÅ¥ dávky vo forme Äistého textu alebo MIME? +

    Váš poÅ¡tový program môže, ale nemusí porporovaÅ¥ digest vo formáte + MIME. OdporúÄame odoberaÅ¥ dávky vo formáte MIME, ale ak máte problém + takúto poÅ¡tu ÄítaÅ¥, vyberte Äistý text. +

    +MIME
    +Äistý text

    +nastaviť globálne +

    - OdoberaÅ¥ vlastné príspevky do konferencie?

    - TradiÄne dostanete kópiu každej správy, ktorá bola zaslaná do - konferenccie. Ak nechcete dostávaÅ¥ kópiu vlastných zaslaných správ, +

    +Odoberať vlastné príspevky do konferencie?

    + TradiÄne dostanete kópiu každej správy, ktorá bola zaslaná do + konferenccie. Ak nechcete dostávaÅ¥ kópiu vlastných zaslaných správ, nastavte voľbu na nie. -

    - nie
    - áno -
    - Chcete dostávaÅ¥ potvrdenie o rozposlaní Vaších príspevkov do +

    +nie
    +áno +
    +Chcete dostávať potvrdenie o rozposlaní Vaších príspevkov do konferencie?

    -

    - nie
    - áno -
    - ZasielaÅ¥ pre túto konferenciu pripomienku hesla?

    - Raz mesaÄne obdržíte správu, ktorá obsahuje heslo pre každú - konferenciu na tomto serveri, v ktorej ste úÄastníkom. Zasielanie - hesla môžete vypnúÅ¥ vybraním voľby nie. Ak vypnete - zasielanie hesla globálne (pre všetky konferencie), nebude Vám - zaslaná žiadna správa. -

    - nie
    - áno

    - nastaviÅ¥ globálne -

    - SkryÅ¥ Vašu adresu zo zoznamu úÄastníkov?

    - Ak si niekto prezerá zoznam úÄastníkov, Vaša adresa je za normálnych - okolností zobrazená (v pozmenenej forme, aby ju nerozpoznali spamové - roboty). Ak nechcete Vašu adresu v tomto zozname zobraziÅ¥ vôbec, - vyberte v tejto voľbe áno." -

    - nie
    - áno -
    - Aký jazyk uprednostňujete na komunikáciu?

    -

    - -
    - Do ktorých tematických kategórií sa chcete prihlásiÅ¥? +

    +nie
    +áno +
    +Zasielať pre túto konferenciu pripomienku hesla?

    + Raz mesaÄne obdržíte správu, ktorá obsahuje heslo pre každú + konferenciu na tomto serveri, v ktorej ste úÄastníkom. Zasielanie + hesla môžete vypnúť vybraním voľby nie. Ak vypnete + zasielanie hesla globálne (pre vÅ¡etky konferencie), nebude Vám + zaslaná žiadna správa. +

    +nie
    +áno

    +nastaviť globálne +

    +SkryÅ¥ VaÅ¡u adresu zo zoznamu úÄastníkov?

    + Ak si niekto prezerá zoznam úÄastníkov, VaÅ¡a adresa je za normálnych + okolností zobrazená (v pozmenenej forme, aby ju nerozpoznali spamové + roboty). Ak nechcete VaÅ¡u adresu v tomto zozname zobraziÅ¥ vôbec, + vyberte v tejto voľbe áno." +

    +nie
    +áno +
    +Aký jazyk uprednostňujete na komunikáciu?

    +

    + +
    +Do ktorých tematických kategórií sa chcete prihlásiť?

    - Vybraním jednej alebo viac tém budú filtrované - príspevky zasielané do konferencie tak, že dostanete iba - niektoré z nich. Ak bude príspevok vyhovovaÅ¥ niektorej - z Vami vybraných tém, tak Vám bude doruÄený. V opaÄnom - prípade Vám doruÄený nebude. - -

    Ak príspevok nevyhovuje žiadnej téme, tak je úÄinné - nastavenie, ktoré sa nachádza nižšie. KeÄ si nevyberiete - žiadne témy, budete dostávaÅ¥ všetky príspevky, ktoré boli - zaslané do konferencie. -

    - -
    - Chcete dostávaÅ¥ príspevky, ktoré nevyhovujú žiadnemu - tématickému filtru?

    - - Toto nastavenie je úÄinné iba vtedy, ak ste sa vybrali - vo vyššom nastavení aspoň jednu tému. UrÄuje všeobecné - pravidlo pre doruÄenie práspevkov, ktoré nevyhoveli žiadnemu - tématickému filtru. Voľba nie spôsobí, že ak - príspevok nevyhovie žiadnemu tématickému filtru, tak Vám - nebude doruÄený. Pri voľbe áno Vám tento príspevok - doruÄený bude. - -

    Ak nie je vo voľbe vyššie vybraná žiadna téma, tak Vám - budú doruÄené všetky správy, ktoré boli zaslané do tejto + Vybraním jednej alebo viac tém budú filtrované + príspevky zasielané do konferencie tak, že dostanete iba + niektoré z nich. Ak bude príspevok vyhovovaÅ¥ niektorej + z Vami vybraných tém, tak Vám bude doruÄený. V opaÄnom + prípade Vám doruÄený nebude. + +

    Ak príspevok nevyhovuje žiadnej téme, tak je úÄinné + nastavenie, ktoré sa nachádza nižšie. KeÄ si nevyberiete + žiadne témy, budete dostávaÅ¥ vÅ¡etky príspevky, ktoré boli + zaslané do konferencie. +

    + +
    +Chcete dostávať príspevky, ktoré nevyhovujú žiadnemu + tématickému filtru?

    + + Toto nastavenie je úÄinné iba vtedy, ak ste sa vybrali + vo vyššom nastavení aspoň jednu tému. UrÄuje vÅ¡eobecné + pravidlo pre doruÄenie práspevkov, ktoré nevyhoveli žiadnemu + tématickému filtru. Voľba nie spôsobí, že ak + príspevok nevyhovie žiadnemu tématickému filtru, tak Vám + nebude doruÄený. Pri voľbe áno Vám tento príspevok + doruÄený bude. + +

    Ak nie je vo voľbe vyššie vybraná žiadna téma, tak Vám + budú doruÄené vÅ¡etky správy, ktoré boli zaslané do tejto konferencie. -

    - nie
    - áno -
    - ZabrániÅ¥ duplicitným kópiam príspevkov?

    - - KeÄ ste uvedený v hlaviÄke To:, alebo Cc: - správy, ktorá bola zaslaná do tejto konferencie, môžete - zabrániÅ¥ jej opätovnému doruÄeniu. Vyberte áno, - ak nechcete dostaÅ¥ kópiu takýchto správ z konferencie alebo - nie, ak chcete, aby Vám boli doruÄené aj ich kópie. - -

    Ak má táto konferencia zapnuté personifikované - správy a Vy máte zapnuté zasielanie kópií, tak bude maÅ¥" - každá kópia pridanú hlaviÄku X-Mailman-Copy: yes - -

    - nie
    - áno

    - nastaviÅ¥ globálne -

    -
    -
    +nie
    +áno +
    +Zabrániť duplicitným kópiam príspevkov?

    + + KeÄ ste uvedený v hlaviÄke To:, alebo Cc: + správy, ktorá bola zaslaná do tejto konferencie, môžete + zabrániÅ¥ jej opätovnému doruÄeniu. Vyberte áno, + ak nechcete dostaÅ¥ kópiu takýchto správ z konferencie alebo + nie, ak chcete, aby Vám boli doruÄené aj ich kópie. + +

    Ak má táto konferencia zapnuté personifikované + správy a Vy máte zapnuté zasielanie kópií, tak bude maÅ¥" + každá kópia pridanú hlaviÄku X-Mailman-Copy: yes +

    +nie
    +áno

    +nastaviť globálne +

    +
    +
    -

    - - - - + + +

    diff --git a/templates/sk/private.html b/templates/sk/private.html index 511ccd2d..05c04dd4 100755 --- a/templates/sk/private.html +++ b/templates/sk/private.html @@ -1,57 +1,117 @@ - Konferencia %(realname)s - prístup do súkromného archívu - - - -
    +Konferencia %(realname)s - prístup do súkromného archívu + + + + %(message)s - - - - - - - - - - - - - - - -
    - Konferencia %(realname)s - - prístup do súkromného archívu -
    E-mailová adresa:
    Heslo:
    -
    -

    Dôležité: od tohto momentu potrebujete - maÅ¥ v prehliadaÄi zapnuté cookies, inak sa budete musieÅ¥ opätovne - prihlásiÅ¥ pri každej Äalšej Äinnosti. + + + + + + + + + + + + + + + +
    +Konferencia %(realname)s - + prístup do súkromného archívu +
    E-mailová adresa:
    Heslo:
    +
    +

    Dôležité: od tohto momentu potrebujete + maÅ¥ v prehliadaÄi zapnuté cookies, inak sa budete musieÅ¥ opätovne + prihlásiÅ¥ pri každej ÄalÅ¡ej Äinnosti. -

    Cookis pre sedenia sú použité pre prístup do súkromného archívu - Mailman, aby ste sa nemuseli stále opätovne prihlasovaÅ¥. Tento cookie - vyprší, keÄ zavrete Váš prehliadaÄ, alebo kliknete na stránke Vašich - osobných nastavení na tlaÄidlo OdhlásiÅ¥. +

    Cookis pre sedenia sú použité pre prístup do súkromného archívu + Mailman, aby ste sa nemuseli stále opätovne prihlasovaÅ¥. Tento cookie + vyprší, keÄ zavrete Váš prehliadaÄ, alebo kliknete na stránke VaÅ¡ich + osobných nastavení na tlaÄidlo OdhlásiÅ¥.

    - - - - - - + + + +
    - Password Reminder -
    If you don't remember your password, enter your email address + + + + + + - - - - -
    +Password Reminder +
    If you don't remember your password, enter your email address above and click the Remind button and your password will be emailed to you.
    - +
    +

    diff --git a/templates/sk/roster.html b/templates/sk/roster.html index af53c75d..d4d6e209 100644 --- a/templates/sk/roster.html +++ b/templates/sk/roster.html @@ -1,53 +1,111 @@ - - - ÚÄastníci konferencie <MM-List-Name> - - - - -

    - - - - - - - - - - - - - - - -
    - ÚÄastníci konferencie - -
    - -

    -

    - -

    Kliknutím na Vašu adresu prejdete na stránku s Vašími osobnými - nastaveniami.
    (Záznamy v zátvorke majú vypnuté doruÄovanie - príspevkov)

    -
    -
    - Bežní úÄastníci konferencie: - -
    -
    -
    - ÚÄastníci konferencie - prijímajúci súhrnné správy: -
    -
    -

    -

    -

    -

    - - - + + +ÚÄastníci konferencie <mm-list-name></mm-list-name> + + +

    + + + + + + + + + + + + + + + +
    + ÚÄastníci konferencie + +
    +

    +

    +

    Kliknutím na Vašu adresu prejdete na stránku s Vašími osobnými + nastaveniami.
    (Záznamy v zátvorke majú vypnuté doruÄovanie + príspevkov)

    +
    +
    +Bežní úÄastníci konferencie: + +
    +
    +
    +ÚÄastníci konferencie + prijímajúci súhrnné správy: +
    +
    +

    +

    +

    +

    + +

    + diff --git a/templates/sk/subscribe.html b/templates/sk/subscribe.html index 8194bcc7..0d900eb3 100644 --- a/templates/sk/subscribe.html +++ b/templates/sk/subscribe.html @@ -1,9 +1,71 @@ -<MM-List-Name> Výsledky prihlásenia +<mm-list-name> Výsledky prihlásenia</mm-list-name> -

    Výsledky prihlásenia

    - - - +

    Výsledky prihlásenia

    + + + diff --git a/templates/sl/admindbdetails.html b/templates/sl/admindbdetails.html index 400c59b5..c780ff2c 100644 --- a/templates/sl/admindbdetails.html +++ b/templates/sl/admindbdetails.html @@ -1,62 +1,124 @@ -Skrbniške zahteve so prikazane na dva mo¾na naèina, kot +SkrbniÅ¡ke zahteve so prikazane na dva mo¾na naèina, kot stran s povzetki ali kot -podrobnosti. Stran s povzetki vsebuje èakajoèe zahteve -za prijave in odjave, pa tudi sporoèila, ki èakajo vašo odobritev -za objavo, prikazana po e-poštnih naslovih pošiljateljev. Stran -s podrobnostmi vsebuje bolj podroben opis vsakega sporoèila, -vkljuèno z glavami sporoèila in povzetkom besedila. +podrobnosti. Stran s povzetki vsebuje èakajoèe zahteve +za prijave in odjave, pa tudi sporoèila, ki èakajo vaÅ¡o odobritev +za objavo, prikazana po e-poÅ¡tnih naslovih poÅ¡iljateljev. Stran +s podrobnostmi vsebuje bolj podroben opis vsakega sporoèila, +vkljuèno z glavami sporoèila in povzetkom besedila.

    Na vseh straneh so na voljo naslednja dejanja:

      -
    • Odlo¾i -- Odlo¾i odloèitev na kasneje. V tem trenutku - se ne izvede nobeno dejanje za èakajoèo zahtevo, vendar lahko - èakajoèa sporoèila vseeno pošljete naprej ali ga shranite +
    • Odlo¾i -- Odlo¾i odloèitev na kasneje. V tem trenutku + se ne izvede nobeno dejanje za èakajoèo zahtevo, vendar lahko + èakajoèa sporoèila vseeno poÅ¡ljete naprej ali ga shranite (glej spodaj). -

    • Odobri -- Odobri sporoèilo in ga pošlje na seznam. - Pri zahtevah za èlanstvo odobri spremembo v stanju èlanstva. +

    • Odobri -- Odobri sporoèilo in ga poÅ¡lje na seznam. + Pri zahtevah za èlanstvo odobri spremembo v stanju èlanstva. -

    • Zavrni -- Zavrne sporoèilo, pošiljatelju pa pošlje obvestilo - o zavrnitvi in zavr¾e izvirno sporoèilo. Pri èlanskih zahtevah zavrne - spremembe v stanju èlanstva. V obeh primerih morate v polje za besedilo +

    • Zavrni -- Zavrne sporoèilo, poÅ¡iljatelju pa poÅ¡lje obvestilo + o zavrnitvi in zavr¾e izvirno sporoèilo. Pri èlanskih zahtevah zavrne + spremembe v stanju èlanstva. V obeh primerih morate v polje za besedilo vnesti razlog za zavrnitev. -

    • Izbriši -- Zavr¾e izvirno sporoèilo, brez pošiljanja - obvestila o zavrnitvi. Pri èlanskih zahtevah zavr¾e zahtevo in - pošiljatelju zahteve ne pošlje obvestila. Ta mo¾nost se uporablja - za poznano ne¾eleno pošto (spam). -
    - -

    Pri obdr¾anih sporoèilih vkljuèite mo¾nost Obdr¾i, èe ¾elite -shraniti kopijo sporoèila za skrbnika strani. Ta mo¾nost je uporabna -pri ¾aljivih sporoèilih, ki jih ¾elite zavrniti, hkrati pa shraniti kot +

  • IzbriÅ¡i -- Zavr¾e izvirno sporoèilo, brez poÅ¡iljanja + obvestila o zavrnitvi. Pri èlanskih zahtevah zavr¾e zahtevo in + poÅ¡iljatelju zahteve ne poÅ¡lje obvestila. Ta mo¾nost se uporablja + za poznano ne¾eleno poÅ¡to (spam). +
  • +

    Pri obdr¾anih sporoèilih vkljuèite mo¾nost Obdr¾i, èe ¾elite +shraniti kopijo sporoèila za skrbnika strani. Ta mo¾nost je uporabna +pri ¾aljivih sporoèilih, ki jih ¾elite zavrniti, hkrati pa shraniti kot dokaz. -

    Vkljuèite mo¾nost Posreduj in vnesite prejemnikov naslov, -èe ¾elite sporoèilo namesto na seznam poslati nekomu drugemu. Èe -¾elite urediti shranjeno sporoèilo preden ga pošljete na seznam, -ga pošljite na svoj naslov (ali na naslov lastnika seznama), izvirno -sporoèilo pa izbrišite. Ko prejmete sporoèilo v svoj poštni predal, -da uredite in pošljite na seznam, skupaj z glavo Odobreno: +

    Vkljuèite mo¾nost Posreduj in vnesite prejemnikov naslov, +èe ¾elite sporoèilo namesto na seznam poslati nekomu drugemu. Èe +¾elite urediti shranjeno sporoèilo preden ga poÅ¡ljete na seznam, +ga poÅ¡ljite na svoj naslov (ali na naslov lastnika seznama), izvirno +sporoèilo pa izbriÅ¡ite. Ko prejmete sporoèilo v svoj poÅ¡tni predal, +da uredite in poÅ¡ljite na seznam, skupaj z glavo Odobreno: in geslom seznama kot vrednostjo. Prav tako je primerno, da dodate -opombo, da ste spremenili sporoèilo. +opombo, da ste spremenili sporoèilo. -

    Èe je pošiljatelj èlan dopisnega seznama, katerega sporoèila ste -do sedaj obravnavali, ga lahko prenehate opazovati. Èe je seznam -nastavljen tako, da postavite novega uporabnika na preizkušnjo, -lahko le-ta kasneje, ko mu zaupate, objavlja sporoèila neposredno. +

    Èe je pošiljatelj èlan dopisnega seznama, katerega sporoèila ste +do sedaj obravnavali, ga lahko prenehate opazovati. Èe je seznam +nastavljen tako, da postavite novega uporabnika na preizkušnjo, +lahko le-ta kasneje, ko mu zaupate, objavlja sporoèila neposredno. -

    Èe pošiljatelj ni èlan seznama, lahko njegov naslov dodate v -filter pošiljateljev. Ti filtri so opisani na strani -stran za filtre pošiljateljev, kjer so +

    Èe poÅ¡iljatelj ni èlan seznama, lahko njegov naslov dodate v +filter poÅ¡iljateljev. Ti filtri so opisani na strani +stran za filtre poÅ¡iljateljev, kjer so filtri razdeljeni na auto-accept (sprejme), auto-hold (shrani), auto-reject (zavrne) ali auto-discard -(izbriše). Ta mo¾nost ne bo na voljo, èe je naslov ¾e vkljuèen +(izbriÅ¡e). Ta mo¾nost ne bo na voljo, èe je naslov ¾e vkljuèen v katerega od filtrov. -

    Ko konèate, kliknite gumb Pošlji vse podatke v +

    Ko konèate, kliknite gumb PoÅ¡lji vse podatke v zgornem ali spodnjem delu strani. S tem gumbom boste aktivirali -vsa izbrana dejanja za skrbniške zahteve. +vsa izbrana dejanja za skrbniÅ¡ke zahteve.

    Vrnitev na stran s povzetki. +

    \ No newline at end of file diff --git a/templates/sl/admindbpreamble.html b/templates/sl/admindbpreamble.html index 3cfb4052..d6b37a18 100644 --- a/templates/sl/admindbpreamble.html +++ b/templates/sl/admindbpreamble.html @@ -1,9 +1,72 @@ -Na tej strani najdete sporoèila za seznam %(listname)s, ki -èakajo na vašo odobritev. Trenutno je prikazano %(description)s +Na tej strani najdete sporoèila za seznam %(listname)s, ki +èakajo na vaÅ¡o odobritev. Trenutno je prikazano %(description)s -

    Za vsako skrbniško zahtevo izberite dejanje, tako da kliknete na -gumb Pošlji vse podatke (Submit all data), ko konèate. Bolj +

    Za vsako skrbniško zahtevo izberite dejanje, tako da kliknete na +gumb Pošlji vse podatke (Submit all data), ko konèate. Bolj podrobna navodila najdete tukaj.

    Lahko si tudi ogledate povzetek vseh -èakajoèih zahtev. +èakajoèih zahtev. +

    \ No newline at end of file diff --git a/templates/sl/admindbsummary.html b/templates/sl/admindbsummary.html index 6fc49bfc..8535553f 100644 --- a/templates/sl/admindbsummary.html +++ b/templates/sl/admindbsummary.html @@ -1,10 +1,73 @@ -Ta stran vsebuje povzetek trenutnih skrbniških zahtev, ki èakajo -na vašo odobritev za poštni seznam %(listname)s. -Najprej vidite seznam èakajoèih zahtev za prijavo in odjavo, -sledijo pa jim èakajoèa sporoèila za objavo. +Ta stran vsebuje povzetek trenutnih skrbniÅ¡kih zahtev, ki èakajo +na vaÅ¡o odobritev za poÅ¡tni seznam %(listname)s. +Najprej vidite seznam èakajoèih zahtev za prijavo in odjavo, +sledijo pa jim èakajoèa sporoèila za objavo. -

    Za vsako skrbniško zahtevo izberite ¾eleno dejanje in, ko -konèate, kliknite na gumb Pošlji vse podatke. Na voljo so tudi +

    Za vsako skrbniško zahtevo izberite ¾eleno dejanje in, ko +konèate, kliknite na gumb Pošlji vse podatke. Na voljo so tudi podrobna navodila. -

    Lahko si tudi ogledate podrobnosti èakajoèih sporoèil. +

    Lahko si tudi ogledate podrobnosti èakajoèih sporoèil. +

    \ No newline at end of file diff --git a/templates/sl/admlogin.html b/templates/sl/admlogin.html index 2c87acec..1e2b2115 100755 --- a/templates/sl/admlogin.html +++ b/templates/sl/admlogin.html @@ -1,39 +1,99 @@ - Avtentikacija èlana %(who)s za seznam %(listname)s - - - -
    +Avtentikacija èlana %(who)s za seznam %(listname)s + + + + %(message)s - - - - - - - - - - - -
    - %(listname)s %(who)s - avtentikacija -
    Geslo za %(who)s:
    -
    -

    Pomembno: Od tega trenutka dalje - morate v brskalniku imeti omogoèene piškotke, drugaèe skrbniška + + + + + + + + + + + +
    +%(listname)s %(who)s + avtentikacija +
    Geslo za %(who)s:
    +
    +

    Pomembno: Od tega trenutka dalje + morate v brskalniku imeti omogoèene piškotke, drugaèe skrbniška dejanja ne bodo veljavna. -

    Piškotki za seje se v skrbniškem vmesniku Mailmana +

    PiÅ¡kotki za seje se v skrbniÅ¡kem vmesniku Mailmana uporabljajo zato, da ni potrebna ponovna avtentikacija za - vsako skrbniško dejanje. Ta piškotek poteèe samodejno, ko + vsako skrbniÅ¡ko dejanje. Ta piÅ¡kotek poteèe samodejno, ko zaprete brskalnik ali ko se odjavite z gumbom Odjava - pod Druge skrbniške dejavnosti + pod Druge skrbniÅ¡ke dejavnosti (kar boste videli, ko se prijavite). -

    +

    diff --git a/templates/sl/archidxentry.html b/templates/sl/archidxentry.html index f9bb57aa..2efd9486 100644 --- a/templates/sl/archidxentry.html +++ b/templates/sl/archidxentry.html @@ -1,4 +1,67 @@ -
  • %(subject)s -  -%(author)s - +
  • %(subject)s +  +%(author)s + +
  • \ No newline at end of file diff --git a/templates/sl/archidxfoot.html b/templates/sl/archidxfoot.html index fd129195..73d406dd 100644 --- a/templates/sl/archidxfoot.html +++ b/templates/sl/archidxfoot.html @@ -1,21 +1,84 @@ - -

    - Datum zadnjega sporoèila: - %(lastdate)s
    - Arhivirano dne: %(archivedate)s -

    -

      -
    • Sporoèila razvršèena glede na: + +

      +Datum zadnjega sporoèila: +%(lastdate)s
      +Arhivirano dne: %(archivedate)s +

      +

      -

      -


      - Ta arhiv je bil ustvarjen s programom +
    +

    +


    +Ta arhiv je bil ustvarjen s programom Pipermail %(version)s. - - + + +

    \ No newline at end of file diff --git a/templates/sl/archidxhead.html b/templates/sl/archidxhead.html index 0deba80a..d21ea167 100644 --- a/templates/sl/archidxhead.html +++ b/templates/sl/archidxhead.html @@ -1,15 +1,78 @@ - - - Arhiv za %(listname)s %(archive)s glede na %(archtype)s - + + + +Arhiv za %(listname)s %(archive)s glede na %(archtype)s + %(encoding)s - - - -

    Arhivi %(archive)s glede na %(archtype)s

    -
      -
    • Sporoèila razvršèena glede na: + + + +

      Arhivi %(archive)s glede na %(archtype)s

      +
        +
      • Sporoèila razvršèena glede na: %(thread_ref)s %(subject_ref)s %(author_ref)s @@ -17,8 +80,9 @@

        Arhivi %(archive)s glede na %(archtype)s

      • Dodatne informacije o tem seznamu...
      • -
      -

      Zaèetek: %(firstdate)s
      - Konec: %(lastdate)s
      - Sporoèil: %(size)s

      -

        +
      +

      Zaèetek: %(firstdate)s
      +Konec: %(lastdate)s
      +Sporoèil: %(size)s

      +

        +

      \ No newline at end of file diff --git a/templates/sl/archlistend.html b/templates/sl/archlistend.html index 9bc052dd..492cf01a 100644 --- a/templates/sl/archlistend.html +++ b/templates/sl/archlistend.html @@ -1 +1,63 @@ -
    + diff --git a/templates/sl/archliststart.html b/templates/sl/archliststart.html index 8bd66c49..74551014 100644 --- a/templates/sl/archliststart.html +++ b/templates/sl/archliststart.html @@ -1,4 +1,67 @@ - - - - +
    ArhivPoka¾i glede na:Prenosljiva razlièica
    + + + +
    ArhivPoka¾i glede na:Prenosljiva razlièica
    \ No newline at end of file diff --git a/templates/sl/archtoc.html b/templates/sl/archtoc.html index e72656b0..3defa282 100644 --- a/templates/sl/archtoc.html +++ b/templates/sl/archtoc.html @@ -1,13 +1,76 @@ - - - Arhivi za seznam %(listname)s - + + + +Arhivi za seznam %(listname)s + %(meta)s - - -

    Arhivi za seznam %(listname)s

    -

    + + +

    Arhivi za seznam %(listname)s

    +

    Ogledate si lahko dodatne informacije o tem seznamu ali pa prenesete celoten raw arhiv (%(size)s). @@ -16,5 +79,5 @@

    Arhivi za seznam %(listname)s

    %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/sl/archtocentry.html b/templates/sl/archtocentry.html index 46340060..1a380538 100644 --- a/templates/sl/archtocentry.html +++ b/templates/sl/archtocentry.html @@ -1,12 +1,73 @@ - -
    %(archivelabel)s: - [ Tema ] - [ Zadeva ] - [ Avtor ] - [ Datum ] -
    %(archivelabel)s: +[ Tema ] +[ Zadeva ] +[ Avtor ] +[ Datum ] +
    - - - - - - - - - - - - - - - - - - + + + + + +

    - -- - -
    -

      -

    - Podatki o poštnem seznamu - - - -
    -

    -

    Èe vas zanimajo prejšnja sporoèila na poštnem seznamu, - si oglejte - Arhive. - -

    -
    - Uporaba -
    - Èe ¾elite poslati sporoèilo vsem èlanom seznama, ga pošljite na naslov - . + + + +<mm-list-name> Stran s podatki</mm-list-name> + + +

    + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + + + + + + + +
    + -- + +
    +

      +

    +Podatki o poštnem seznamu + + + +
    +

    +

    Èe vas zanimajo prejšnja sporoèila na poštnem seznamu, + si oglejte + Arhive. + +

    +
    +Uporaba +
    + Èe ¾elite poslati sporoèilo vsem èlanom seznama, ga pošljite na naslov + . -

    V spodnjem delu strani se lahko prijavite na seznam ali prilagodite obstojeèe - èlanstvo. -

    - Prijava na seznam -
    -

    - Na seznam se prijavite tako, da izpolnite +

    V spodnjem delu strani se lahko prijavite na seznam ali prilagodite obstojeèe + èlanstvo. +

    +Prijava na seznam +
    +

    + Na seznam se prijavite tako, da izpolnite obrazec. - -

      - - - - - - - - - - - - - - - - - -
      Vaš e-poštni naslov: -  
      Vaše ime (neobvezno): 
      Spodaj lahko vnesete geslo. To - geslo zagotavlja majhno zašèito, vendar lahko prepreèi - drugim, da bi zlorabili vašo naroènino. + +
        + + + + + + + + + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - - - -
        Vaš e-poštni naslov: + 
        Vaše ime (neobvezno): 
        Spodaj lahko vnesete geslo. To + geslo zagotavlja majhno zašèito, vendar lahko prepreèi + drugim, da bi zlorabili vaÅ¡o naroènino. Ne uporabljajte pomembnega gesla, saj - ga boste obèasno dobili po e-pošti v obliki besedila. + ga boste obèasno dobili po e-poÅ¡ti v obliki besedila. -

        Èe ne vnesete gesla, bo le-to samodejno ustvarjeno in - poslano, ko potrdite naroènino. Vedno lahko tudi zahtevate - e-sporoèilo z vašim geslom, ko urejate osebne nastavitve. - -
        -
        Vnesite geslo: 
        Ponovite geslo: 
        V katerem jeziku naj bodo prikazana sporoèila?  
        Ali ¾elite prejemati sporoèila zdru¾ena v dnevni povzetek? +

        Èe ne vnesete gesla, bo le-to samodejno ustvarjeno in + poslano, ko potrdite naroènino. Vedno lahko tudi zahtevate + e-sporoèilo z vašim geslom, ko urejate osebne nastavitve. + + +
        Vnesite geslo: 
        Ponovite geslo: 
        V katerem jeziku naj bodo prikazana sporoèila?  
        Ali ¾elite prejemati sporoèila zdru¾ena v dnevni povzetek? Ne - Da -
        -
        -
        - -
      -
      - - Èlani -
      - - - -

      - - - -

      - - - +
    Ne + Da +
    +
    +
    + + +

    + + Èlani +
    + + + +

    + + + +

    + +

    + diff --git a/templates/sl/options.html b/templates/sl/options.html index 73fe5f48..c6d89492 100644 --- a/templates/sl/options.html +++ b/templates/sl/options.html @@ -1,309 +1,341 @@ - - Konfiguracija èlana <MM-Presentable-User> za seznam <MM-List-Name> - - - - - -
    - - konfiguracija èlanstva za èlana - -
    + +Konfiguracija èlana <mm-presentable-user> za seznam <mm-list-name> +</mm-list-name></mm-presentable-user> + + + + +
    + + konfiguracija èlanstva za èlana + +

    - - - - - +
    - - stanje èlanstva, - geslo in mo¾nosti za poštni seznam . -
    - - - - -

    -

    + + + +
    + - stanje èlanstva, + geslo in mo¾nosti za poštni seznam . +
    + + +

    +

    - - +

    - - - + +
    - - Spreminjanje podatkov o èlanstvu za seznam -
    V spodnjih poljih lahko zamenjate naslov, s - katerim ste prijavljeni na poštni seznam, z novim naslovom. + + + - - - - -
    + +Spreminjanje podatkov o èlanstvu za seznam +
    V spodnjih poljih lahko zamenjate naslov, s + katerim ste prijavljeni na poštni seznam, z novim naslovom. Na novi naslov bo poslano obvestilo o spremembi, ki jo morate potrditi, da bo obdelana. -

    Èas za potrditev je . +

    Èas za potrditev je .

    Prav tako lahko spremenite ime (npr. Janez Novak). -

    Èe ¾elite spremeniti èlanstvo na vseh seznamih, na katere - ste prijavljeni na , oznaèite potrditveno polje +

    Èe ¾elite spremeniti èlanstvo na vseh seznamih, na katere + ste prijavljeni na , oznaèite potrditveno polje Spremeni globalno. -

    - - - - - + -
    Novi naslov:
    Ponovite +

    + + + + + - - -
    Novi naslov:
    Ponovite naslov:
    -
    - - + +
    Vaše ime +
    +
    + + - - -
    Vaše ime (neobvezno):
    -
    -

    Spremeni globalno

    - +
    + +

    +

    Spremeni globalno

    +

    - - - - - -
    - Odjava s seznama - Vaša ostala èlanstva na -
    - Vkljuèite polje za potrditev in pritisnite ta gumb, èe se - ¾elite odjaviti s tega poštnega seznama. Opozorilo: + + + + - + +
    +

    +Odjava s seznama +Vaša ostala èlanstva na +
    + Vkljuèite polje za potrditev in pritisnite ta gumb, èe se + ¾elite odjaviti s tega poštnega seznama. Opozorilo: Dejanje se bo izvedlo takoj!

    -

    - Videli boste seznam vseh ostalih poštnih seznamov na - , na katere ste prijavljeni. To uporabite, èe - ¾elite iste mo¾nosti spremeniti tudi za ostale sezname. +

    + Videli boste seznam vseh ostalih poštnih seznamov na + , na katere ste prijavljeni. To uporabite, èe + ¾elite iste mo¾nosti spremeniti tudi za ostale sezname.

    -

    -
    - - - - - +
    - Vaše geslo za seznam -
    - -
    -

    Ali ste pozabili geslo?

    -
    - Kliknite ta gumb in prejeli boste sporoèilo na e-poštni + + + - -
    +Vaše geslo za seznam +
    + +
    +

    Ali ste pozabili geslo?

    +
    + Kliknite ta gumb in prejeli boste sporoèilo na e-poštni naslov, s katerim ste prijavljeni. -

    -

    - -
    -
    - -
    -

    Sprememba gesla

    - - - - - - - - -
    Novo - geslo:
    Ponovi - geslo:
    - - -

    Spremeni globalno. -
    -
    - +

    +

    + +
    +

    + +
    +

    Sprememba gesla

    + + + + + + + + +
    Novo + geslo:
    Ponovi + geslo:
    + +

    Spremeni globalno. +
    +

    - - +
    - Vaše prijavne mo¾nosti za seznam -
    +
    +Vaše prijavne mo¾nosti za seznam +
    -

    Trenutne vrednosti so obkljukane. - -

    Nekatere mo¾nosti imajo polje Nastavi globalno. Èe oznaèite -to polje, bodo narejene spremembe vplivale na vse poštne sezname, -katerih èlan ste na . Èe ¾elite vedeti, na katere sezname -ste še prijavljeni, kliknite Poka¾i moje ostale naroènine. +

    Nekatere mo¾nosti imajo polje Nastavi globalno. Èe oznaèite +to polje, bodo narejene spremembe vplivale na vse poštne sezname, +katerih èlan ste na . Èe ¾elite vedeti, na katere sezname +ste še prijavljeni, kliknite Poka¾i moje ostale naroènine.

    - - - +
    - - Prejemanje pošte

    - Nastavite to mo¾nost na Omogoèeno, èe ¾elite prejemati - sporoèila s tega poštnega seznama. Èe nastavite Onemogoèeno, - boste ostali prijavljeni, ne boste pa prejemali sporoèil, npr. - ko ste na dopustu. Èe onemogoèite prejemanje pošte, je ne pozabite - omogoèiti, ko se vrnete; ni je mogoèe omogoèiti samodejno. -

    - Omogoèeno
    - Onemogoèeno

    - Nastavi globalno -

    + - - - + +

    - - - - - - + + - - - - - - + + + - - + - - + - - - +

    +
    + +Prejemanje pošte

    + Nastavite to mo¾nost na Omogoèeno, èe ¾elite prejemati + sporoèila s tega poštnega seznama. Èe nastavite Onemogoèeno, + boste ostali prijavljeni, ne boste pa prejemali sporoèil, npr. + ko ste na dopustu. Èe onemogoèite prejemanje pošte, je ne pozabite + omogoèiti, ko se vrnete; ni je mogoèe omogoèiti samodejno. +

    +Omogoèeno
    +Onemogoèeno

    +Nastavi globalno +

    - Prejemanje izvleèkov

    - Èe vkljuèite naèin prejemanja izvleèkov, boste prejemali vsa - sporoèila zdru¾ena v en veèji izvleèek (ponavadi enega dnevno, - odvisno od dejavnosti na seznamu), namesto posameznih sporoèil. - Ko izkljuèite prejemanje izvleèka, boste prejeli še zadnjega. -

    - Izkljuèeno
    - Vkljuèeno -
    - Izvleèki v MIME obliki ali kot navadno besedilo?

    - Vaš poštni program morda ne podpira MIME izvleèkov. Na - splošno so bolj priljubljeni MIME izvleèki, èe pa imate te¾ave +

    +Prejemanje izvleèkov

    + Èe vkljuèite naèin prejemanja izvleèkov, boste prejemali vsa + sporoèila zdru¾ena v en veèji izvleèek (ponavadi enega dnevno, + odvisno od dejavnosti na seznamu), namesto posameznih sporoèil. + Ko izkljuèite prejemanje izvleèka, boste prejeli še zadnjega. +

    +Izkljuèeno
    +Vkljuèeno +
    +Izvleèki v MIME obliki ali kot navadno besedilo?

    + Vaš poštni program morda ne podpira MIME izvleèkov. Na + splošno so bolj priljubljeni MIME izvleèki, èe pa imate te¾ave z branjem le-teh, izberite obliko navadnega besedila. -

    - MIME
    - Navadno besedilo

    - Nastavi globalno -

    +MIME
    +Navadno besedilo

    +Nastavi globalno +

    - Ali ¾elite prejemati lastna sporoèila?

    - Navadno boste prejeli kopijo vsakega sporoèila, ki ga pošljete - na seznam. Èe ne ¾elite prejemati kopij lastnih sporoèil, +

    +Ali ¾elite prejemati lastna sporoèila?

    + Navadno boste prejeli kopijo vsakega sporoèila, ki ga pošljete + na seznam. Èe ne ¾elite prejemati kopij lastnih sporoèil, izberite Ne. -

    - Ne
    - Da -
    - Ali ¾elite prejeti potrditveno sporoèilo, ko pošljete - sporoèilo na seznam?

    -

    - Ne
    - Da -
    - Ali ¾elite prejeti opomnik z geslom za ta seznam?

    - Enkrat meseèno boste prejeli opomnik, v katerem bo navedeno - geslo za vsak poštni seznam na tem gostitelju, na katerega - ste prijavljeni. To mo¾nost lahko izkljuèite za posamezne - sezname, tako da izberete Ne. Èe izkljuèite opomnike +

    +Ne
    +Da +
    +Ali ¾elite prejeti potrditveno sporoèilo, ko pošljete + sporoèilo na seznam?

    +

    +Ne
    +Da +
    +Ali ¾elite prejeti opomnik z geslom za ta seznam?

    + Enkrat meseèno boste prejeli opomnik, v katerem bo navedeno + geslo za vsak poštni seznam na tem gostitelju, na katerega + ste prijavljeni. To mo¾nost lahko izkljuèite za posamezne + sezname, tako da izberete Ne. Èe izkljuèite opomnike za vse prijavljene sezname, ne boste prejemali opomnikov z geslom. -

    - Ne
    - Da

    - Nastavi globalno -

    - Ali ¾elite prikriti svoje èlanstvo na tem seznamu?

    - Navadno je vaš e-poštni naslov prikazan na seznamu èlanov - tega seznama (pošiljateljem ne¾elene pošte v popaèeni - obliki). Èe ne ¾elite, da bi ostali videli vaš e-naslov - na seznamu èlanov, izberite Da za to mo¾nost. -

    - Ne
    - Da -
    - Kateri jezik ¾elite uporabiti za seznam?

    -

    - -
    - Na katere kategorije tem se ¾elite naroèiti?

    - Èe izberete eno ali veè tem, lahko filtrirate promet - na poštnem seznamu, tako da prejemate samo sporoèila - z izbranimi temami. Èe sporoèilo ustreza eni od teh - izbranih tem, boste to sporoèilo prejeli, drugaèe pa +

    +Ne
    +Da

    +Nastavi globalno +

    +Ali ¾elite prikriti svoje èlanstvo na tem seznamu?

    + Navadno je vaš e-poštni naslov prikazan na seznamu èlanov + tega seznama (pošiljateljem ne¾elene pošte v popaèeni + obliki). Èe ne ¾elite, da bi ostali videli vaš e-naslov + na seznamu èlanov, izberite Da za to mo¾nost. +

    +Ne
    +Da +
    +Kateri jezik ¾elite uporabiti za seznam?

    +

    + +
    +Na katere kategorije tem se ¾elite naroèiti?

    + Èe izberete eno ali veè tem, lahko filtrirate promet + na poštnem seznamu, tako da prejemate samo sporoèila + z izbranimi temami. Èe sporoèilo ustreza eni od teh + izbranih tem, boste to sporoèilo prejeli, drugaèe pa ne. -

    Èe sporoèilo ne ustreza nobeni temi, potem bo - prejem tega sporoèila odvisen od naslednjega izbora. - Èe ne izberete nobene teme, boste prejemali vsa - sporoèila, poslana na poštni seznam. -

    - -
    - Ali ¾elite prejemati sporoèila, ki ne ustrezajo nobenemu +

    Èe sporoèilo ne ustreza nobeni temi, potem bo + prejem tega sporoèila odvisen od naslednjega izbora. + Èe ne izberete nobene teme, boste prejemali vsa + sporoèila, poslana na poštni seznam. +

    + +
    +Ali ¾elite prejemati sporoèila, ki ne ustrezajo nobenemu tematskemu filtru?

    - Ta mo¾nost bo veljala le, èe ste naroèeni na vsaj eno - od zgornjih tem. Mo¾nost predstavlja privzeto pravilo - za dostavo sporoèil, ki ne ustrezajo nobenemu tematskemu - filtru. Èe izberete Ne, ne boste dobili sporoèil, - ki ne ustrezajo nobeni temi, èe pa izberete Da, - boste prejeli tudi takšna neopredeljiva sporoèila. - -

    Èe zgoraj niste izbrali nobene ¾elene teme, boste - prejeli vsako sporoèilo, ki bo poslano na poštni seznam. -

    - Ne
    - Da -
    - Izogni se dvojnim kopijam sporoèil?

    + Ta mo¾nost bo veljala le, èe ste naroèeni na vsaj eno + od zgornjih tem. Mo¾nost predstavlja privzeto pravilo + za dostavo sporoèil, ki ne ustrezajo nobenemu tematskemu + filtru. Èe izberete Ne, ne boste dobili sporoèil, + ki ne ustrezajo nobeni temi, èe pa izberete Da, + boste prejeli tudi takšna neopredeljiva sporoèila. + +

    Èe zgoraj niste izbrali nobene ¾elene teme, boste + prejeli vsako sporoèilo, ki bo poslano na poštni seznam. +

    +Ne
    +Da +
    +Izogni se dvojnim kopijam sporoèil?

    Kadar ste neposredno navedeni v polju Za: ali - Kp: sporoèila, lahko izberete, da ne dobite - kopije tega sporoèila še s poštnega seznama. + Kp: sporoèila, lahko izberete, da ne dobite + kopije tega sporoèila Å¡e s poÅ¡tnega seznama. Izberite Da, da se izognete prejemanju kopij s seznama; izberite Ne, da prejmete kopije. -

    Èe ima seznam omogoèena zasebna sporoèila med èlani +

    Èe ima seznam omogoèena zasebna sporoèila med èlani in izberete prejemanje kopij, bo vsaki kopiji dodana glava X-Mailman-Copy: yes. -

    - Ne
    - Da

    - Nastavi globalno -

    -
    -
    +Ne
    +Da

    +Nastavi globalno +

    +
    +
    -

    - - - - + + +

    diff --git a/templates/sl/private.html b/templates/sl/private.html index 53b3f7f9..2569b547 100755 --- a/templates/sl/private.html +++ b/templates/sl/private.html @@ -1,60 +1,120 @@ - Avtentikacija za zasebne arhive èlana %(realname)s - - - -
    +Avtentikacija za zasebne arhive èlana %(realname)s + + + + %(message)s - - - - - - - - - - - - - - - -
    - Avtentikacija za zasebne - arhive èlana %(realname)s -
    E-poštni naslov:
    Geslo:
    -
    -

    Pomembno: Od tega trenutka - dalje morate v brskalniku imeti omogoèene piškotke, - drugaèe skrbniške spremembe ne bodo veljale. + + + + + + + + + + + + + + + +
    +Avtentikacija za zasebne + arhive èlana %(realname)s +
    E-poštni naslov:
    Geslo:
    +
    +

    Pomembno: Od tega trenutka + dalje morate v brskalniku imeti omogoèene piškotke, + drugaèe skrbniške spremembe ne bodo veljale. -

    Skrbniški vmesnik programa Mailman uporablja - piškotke sej zato, da se vam ni potrebno za vsako - skrbniško operacijo ponovno avtenticirati. Ta - piškotek bo potekel samodejno, ko boste zaprli +

    SkrbniÅ¡ki vmesnik programa Mailman uporablja + piÅ¡kotke sej zato, da se vam ni potrebno za vsako + skrbniÅ¡ko operacijo ponovno avtenticirati. Ta + piÅ¡kotek bo potekel samodejno, ko boste zaprli brskalnik ali se odjavili s povezavo - Odjava v Ostale skrbniške dejavnosti + Odjava v Ostale skrbniÅ¡ke dejavnosti (ki bo vidno, takoj ko se prijavite).

    - - - - - - + + + +
    - Password Reminder -
    If you don't remember your password, enter your email address + + + + + + - - - - -
    +Password Reminder +
    If you don't remember your password, enter your email address above and click the Remind button and your password will be emailed to you.
    - +
    +

    diff --git a/templates/sl/roster.html b/templates/sl/roster.html index f42fe4d3..8e9e7faa 100644 --- a/templates/sl/roster.html +++ b/templates/sl/roster.html @@ -1,53 +1,111 @@ - - - <MM-List-Name> èlani - - - - -

    - - - - - - - - - - - - - - - -
    - - èlani -
    - -

    -

    - -

    Kliknite na svoj naslov, da odprete stran z nastavitvami - èlanstva.
    (Vnosi v oklepajih imajo onemogoèeno - prejemanje sporoèil s seznama.)

    -
    -
    - - èlanov , ki ne prejemajo izvleèka: -
    -
    -
    - èlanov - , ki prejemajo izvleèek: -
    -
    -

    -

    -

    -

    - - - + + +<mm-list-name> èlani</mm-list-name> + + +

    + + + + + + + + + + + + + + + +
    + + èlani +
    +

    +

    +

    Kliknite na svoj naslov, da odprete stran z nastavitvami + èlanstva.
    (Vnosi v oklepajih imajo onemogoèeno + prejemanje sporoèil s seznama.)

    +
    +
    + + èlanov , ki ne prejemajo izvleèka: +
    +
    +
    + èlanov + , ki prejemajo izvleèek: +
    +
    +

    +

    +

    +

    + +

    + diff --git a/templates/sl/subscribe.html b/templates/sl/subscribe.html index 9f1c6afe..b1b13f2a 100644 --- a/templates/sl/subscribe.html +++ b/templates/sl/subscribe.html @@ -1,9 +1,71 @@ -Rezultati prijave na seznam <MM-List-Name> +Rezultati prijave na seznam <mm-list-name></mm-list-name> -

    Rezultati prijave na seznam

    - - - +

    Rezultati prijave na seznam

    + + + diff --git a/templates/sr/admindbdetails.html b/templates/sr/admindbdetails.html index 8be8382c..4ae83f0d 100644 --- a/templates/sr/admindbdetails.html +++ b/templates/sr/admindbdetails.html @@ -1,23 +1,85 @@ -ÐдминиÑтративни захтјеви Ñе приказују на један од два начина, на +ÐдминиÑтративни захтјеви Ñе приказују на један од два начина, на Ñтраници прегледа, и на Ñтраници Ñа детаљима. Страница прегледа Ñадржи захтхјеве за укључење и иÑкључење на чекању, те поруке које Ñу задржане ради вашег одобрења. Страница Ñа детаљима Ñадржи детаљнији преглед Ñваке поруке.

    Ðа Ñвим Ñтраницама, Ñледеће опције Ñу доÑтупне:

      -
    • Одлагање -- Одлажете вашу одлуку за каÑније. Ðикаква акција +
    • Одлагање -- Одлажете вашу одлуку за каÑније. Ðикаква акција Ñада није покренута за овај админиÑтративни захтјев, али за задржане поруке и даље можете проÑлиједити или Ñпремити поруку (погледајте доле) -
    +
      -
    • Прихватање +
    • Прихватање -- Прихватате поруку, шаљући је на лиÑту. За захтјеве за чланÑтво, прихвата - Ñе тражени ÑтатуÑ.
      -
    • Одбијање -- Одбија + Ñе тражени ÑтатуÑ.
      +
    • Одбијање -- Одбија Ñе порука, шаље Ñе обавјештење пошиљаоцу, и @@ -29,12 +91,11 @@ У Ñваком Ñлучају, потребно је да унеÑете разлог - ваше одлуке.
      -
    • Занемаривање -- Одбаци Ñе почетна порука, без Ñлања било каквог обавјештења. + ваше одлуке.
      +
    • Занемаривање -- Одбаци Ñе почетна порука, без Ñлања било каквог обавјештења. За затхеве за чланÑтво, захтјев Ñе одбија без додатног обавјештења. Ова опција је уобичајена нпр. за Ñлучај да је у питању Ñпам. -
    - +

    За задржане поруке, укључите опцију Задржавање ако желите да Ñачувате дупликат поруке за админиÑтратора Ñајта. Ово је кориÑно за увредљиве поруке које желите да занемарите, али желите да их имате за каÑнију анализу. @@ -49,9 +110,9 @@ нове чланове на пробу, на оÑнову чега ви одлучујете коме Ñе може вјеровати да шаље без одобрења.

    Ðко пошиљалац није члан лиÑте, можете додати његову е-адреÑу на филтер - пошиљалаца. Дати филтер је опиÑан у Ñекцији филтер-приватноÑÑ‚ пошиљаоца, и може аутоматÑки прихватати, + пошиљалаца. Дати филтер је опиÑан у Ñекцији филтер-приватноÑÑ‚ пошиљаоца, и може аутоматÑки прихватати, задржавати, одбацивати или занемаривати поруке. Ова опција није доÑтупна ако је адреÑа већ на неком од филтера пошиљалаца.

    Када завршите, пошаљите Ñве промјене кориÑтећи таÑтер при врху Ñтранице.

    Повратак на Ñтраницу прегледа +

    \ No newline at end of file diff --git a/templates/sr/admindbpreamble.html b/templates/sr/admindbpreamble.html index f9c9e8ec..5d2aaa8c 100644 --- a/templates/sr/admindbpreamble.html +++ b/templates/sr/admindbpreamble.html @@ -1,6 +1,69 @@ -Ова Ñтраница Ñадржи лиÑту порука које Ñу задржане за одобрење. Тренутно показује +Ова Ñтраница Ñадржи лиÑту порука које Ñу задржане за одобрење. Тренутно показује %(description)s

    За Ñваки админиÑтративни захтјев, молимо Ð²Ð°Ñ Ð´Ð° изаберете одговарајућу опцију, те након извршених промјена кликнете на одговарајући таÑтер,обично при врху екрана. Детаљније инÑтрукције Ñу доÑтупне овдје.

    Можете такође видјети преглед чекајућих захтјева. +

    \ No newline at end of file diff --git a/templates/sr/admindbsummary.html b/templates/sr/admindbsummary.html index d105e60e..2d56f1a0 100644 --- a/templates/sr/admindbsummary.html +++ b/templates/sr/admindbsummary.html @@ -1,4 +1,66 @@ -Ова Ñтрана Ñадржи преглед тренутних уÑлова за ваше укључење у лиÑту Ñлања +Ова Ñтрана Ñадржи преглед тренутних уÑлова за ваше укључење у лиÑту Ñлања %(listname)s. Прво, пронаћи ћете лиÑту Ñа чекајућим захтјевима за укључење и иÑкључење, заједно Ñа порукама које Ñу задржане за пуштање. @@ -7,3 +69,4 @@ Детаљнија упутÑтва Ñу доÑтупна.

    Можете такође погледати детаље Ñвих задржаних порука. +

    \ No newline at end of file diff --git a/templates/sr/admlogin.html b/templates/sr/admlogin.html index 14afaac6..8e650178 100755 --- a/templates/sr/admlogin.html +++ b/templates/sr/admlogin.html @@ -1,35 +1,94 @@ - %(listname)s %(who)s Authentication - - - - -
    +%(listname)s %(who)s Authentication + + + + + %(message)s - - - - - - - - - - - -
    %(listname)s - %(who)s: провјера
    Лозинка за лиÑту%(who)s:
    -
    - -

    Важно: Одавде па надаље, морате имати укључене - "колачиће" (cookies) у вашем претраживачу интернета, иначе промјене + + + + + + + + + + + +
    %(listname)s + %(who)s: провјера
    Лозинка за лиÑту%(who)s:
    +
    +

    Важно: Одавде па надаље, морате имати укључене + "колачиће" (cookies) у вашем претраживачу интернета, иначе промјене неће имати ефекта. -

    "Колачићи" Ñе кориÑте у Mailman-овом админиÑтративном интерфејÑу - да не биÑте морали да Ñе поÑле Ñваке акције поново пријављујете. "Колачић" +

    "Колачићи" Ñе кориÑте у Mailman-овом админиÑтративном интерфејÑу + да не биÑте морали да Ñе поÑле Ñваке акције поново пријављујете. "Колачић" неће више важити поÑлије вашег одјављивања Ñа админиÑтративног интерфејÑа. -

    +

    diff --git a/templates/sr/archidxentry.html b/templates/sr/archidxentry.html index f9bb57aa..2efd9486 100644 --- a/templates/sr/archidxentry.html +++ b/templates/sr/archidxentry.html @@ -1,4 +1,67 @@ -
  • %(subject)s -  -%(author)s - +
  • %(subject)s +  +%(author)s + +
  • \ No newline at end of file diff --git a/templates/sr/archidxfoot.html b/templates/sr/archidxfoot.html index 558936d6..97fb76eb 100644 --- a/templates/sr/archidxfoot.html +++ b/templates/sr/archidxfoot.html @@ -1,21 +1,84 @@ - -

    - ПоÑледња порука: - %(lastdate)s
    - Ðрхивирана: %(archivedate)s -

    -

      -
    • Сортирање порука: + +

      +ПоÑледња порука: +%(lastdate)s
      +Ðрхивирана: %(archivedate)s +

      +

      -

      -


      - Ову архиву генериÑао је +
    +

    +


    +Ову архиву генериÑао је Pipermail %(version)s. - - + + +

    \ No newline at end of file diff --git a/templates/sr/archidxhead.html b/templates/sr/archidxhead.html index 3d74eea2..dab6555e 100644 --- a/templates/sr/archidxhead.html +++ b/templates/sr/archidxhead.html @@ -1,21 +1,83 @@ - - - - Ðрхива: %(listname)s %(archive)s, по %(archtype)s - + + + + +Ðрхива: %(listname)s %(archive)s, по %(archtype)s + %(encoding)s - - - - + + +

    %(archive)s : архива: %(archtype)s

    -
      - -
    • Сортирање порука: %(thread_ref)s %(subject_ref)s %(author_ref)s + -

      Почетак: %(firstdate)s
      - Крај: %(lastdate)s
      - Порука: %(size)s

      -

        +
      +

      Почетак: %(firstdate)s
      +Крај: %(lastdate)s
      +Порука: %(size)s

      +

        +

      \ No newline at end of file diff --git a/templates/sr/archlistend.html b/templates/sr/archlistend.html index 9bc052dd..492cf01a 100644 --- a/templates/sr/archlistend.html +++ b/templates/sr/archlistend.html @@ -1 +1,63 @@ -
    + diff --git a/templates/sr/archliststart.html b/templates/sr/archliststart.html index 9b2aa7aa..2872a135 100644 --- a/templates/sr/archliststart.html +++ b/templates/sr/archliststart.html @@ -1,7 +1,68 @@ - - - - - - - \ No newline at end of file +
    Преглед архиве: Ð’ерзија за преузимање
    + + + + +
    Преглед архиве: Верзија за преузимање
    \ No newline at end of file diff --git a/templates/sr/archtoc.html b/templates/sr/archtoc.html index a081dd09..920a3ab0 100644 --- a/templates/sr/archtoc.html +++ b/templates/sr/archtoc.html @@ -1,14 +1,75 @@ - - - - Ðрхива: %(listname)s - + + + + +Ðрхива: %(listname)s + %(meta)s - - - + +

    Ðрхива порука лиÑте: %(listname)s

    -

    Можете добити више информација о овој лиÑти овдје или можете преузети комплетну архиву (%(size)s).

    @@ -16,5 +77,5 @@

    Ðрхива порука лиÑте: %(listname)s

    %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/sr/archtocentry.html b/templates/sr/archtocentry.html index 7f7434f3..43cc8cec 100644 --- a/templates/sr/archtocentry.html +++ b/templates/sr/archtocentry.html @@ -1,10 +1,70 @@ - - - %(archivelabel)s: - - [ Ñтабло] [ - тема] [ аутор] [ + + +%(archivelabel)s: + [ Ñтабло] [ + тема] [ аутор] [ датум ] %(textlink)s - diff --git a/templates/sr/article.html b/templates/sr/article.html index 998f560c..ab2d87f8 100644 --- a/templates/sr/article.html +++ b/templates/sr/article.html @@ -1,12 +1,13 @@ - - - %(title)s - - - - - +

    Ðрхива лиÑте: %(listname)s

    -

    Још увијек нема поÑланих порука, тако да је архива празна.. Више информација о лиÑти.

    - - + + diff --git a/templates/sr/handle_opts.html b/templates/sr/handle_opts.html index 77df115e..2a246b5a 100644 --- a/templates/sr/handle_opts.html +++ b/templates/sr/handle_opts.html @@ -1,9 +1,71 @@ - - -<MM-List-Name> <MM-Operation> Резултати + + +<mm-list-name> <mm-operation> Резултати</mm-operation></mm-list-name> -

    Резултати

    - - - +

    Резултати

    + + + diff --git a/templates/sr/headfoot.html b/templates/sr/headfoot.html index 3f38ece6..5cf6a096 100644 --- a/templates/sr/headfoot.html +++ b/templates/sr/headfoot.html @@ -1,8 +1,70 @@ -Овај текÑÑ‚ може да Ñадржи Python-ово +Овај текÑÑ‚ може да Ñадржи Python-ово форматирање које Ñе утврђује на оÑнову атрибута лиÑта. Дозвољене замјене лиÑте Ñу:
      -
    • real_name - Пуно име лиÑте, обично Ñадржи и велика Ñлова +
    • real_name - Пуно име лиÑте, обично Ñадржи и велика Ñлова
    • list_name - Име по којем Ñе лиÑта препознаје на веб адреÑама, величина Ñлова је неважна. (због компатибилноÑти, _internal_name је еквивалентно.) @@ -12,4 +74,4 @@
    • description - Кратки Ð¾Ð¿Ð¸Ñ Ð»Ð¸Ñте Ñлања.
    • info - Пуни Ð¾Ð¿Ð¸Ñ Ð»Ð¸Ñте Ñлања.
    • cgiext - ЕкÑтензија имена CGI Ñкрипти. -
    +
  • diff --git a/templates/sr/listinfo.html b/templates/sr/listinfo.html index 81fc6938..eb848297 100644 --- a/templates/sr/listinfo.html +++ b/templates/sr/listinfo.html @@ -1,128 +1,188 @@ - - - - <MM-List-Name> Страница Ñа информацијама - - - -

    - - - - - - - - - - - - - - - - - - - - + + + + + +

    - -- - -
    -

      -

    О - лиÑти - - -
    -

    -

    Да биÑте видјели доÑадашње поруке на лиÑти поÑјетите - архиву.

    -
    Употреба -
    Да би порука отишла на лиÑту, пошаљите је на . + + + + + +<mm-list-name> Страница Ñа информацијама</mm-list-name> + + +

    + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + +
    + -- + +
    +

      +

    О + лиÑти + + +
    +

    +

    Да биÑте видјели доÑадашње поруке на лиÑти поÑјетите + архиву.

    +
    Употреба +
    Да би порука отишла на лиÑту, пошаљите је на .

    Можете Ñе укључити на лиÑту или промјенити ваш ÑÑ‚Ð°Ñ‚ÑƒÑ Ñ‡Ð»Ð°Ð½Ð° преко Ñледећих Ñекција. -

    Ð£Ð¿Ð¸Ñ -
    -

    Укључујете Ñе попуњавајући Ñледећи формулар: -

      - - - - - - - - - - - - - - + + + + + + - - - - - - -
      Ваша ел. адреÑа:  
      Ваше име: 
      Овдје можете унијети вашу лозинку. +

      Ð£Ð¿Ð¸Ñ +
      +

      Укључујете Ñе попуњавајући Ñледећи формулар: +

        + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - - - - - - -
        Ваша ел. адреÑа:  
        Ваше име: 
        Овдје можете унијети вашу лозинку. То обезбјеђује Ñамо дјелимичну ÑигурноÑÑ‚, али онемогућава оÑтале да поремете ваш чланÑки ÑтатуÑ. Ðе кориÑтите једноÑтавне лозинке, пошто ћете једном мјеÑечно добијати лозинку за подÑјећање. -

        Ðко не унеÑете лозинку, наш ÑиÑтем ће аутоматÑки - изабрати једну Ñа ваÑ.
        Лозинка: 
        Поново: 
        Језик:  
        Да ли желите да Ñве поруке добијате у прегледу порука, једном +

        Ðко не унеÑете лозинку, наш ÑиÑтем ће аутоматÑки + изабрати једну Ñа ваÑ.
        Лозинка: 
        Поново: 
        Језик:  
        Да ли желите да Ñве поруке добијате у прегледу порука, једном дневно? Ðе Да
        -
        -
        -
      -
      - Чланови
      - - - -

      - - - -

      - - - +
    Ðе Да
    +
    +
    + +

    + Чланови
    + + + +

    + + + +

    + +

    + diff --git a/templates/sr/options.html b/templates/sr/options.html index 7abe7d26..d793c958 100644 --- a/templates/sr/options.html +++ b/templates/sr/options.html @@ -1,44 +1,103 @@ - - <MM-Presentable-User> Контрола чланÑтва за <MM-List-Name> - - - - - -
    - - : контрола чланÑтва за: - -
    + +<mm-presentable-user> Контрола чланÑтва за <mm-list-name> +</mm-list-name></mm-presentable-user> + + + + +
    + +: контрола чланÑтва за: + +

    - - - - - +
    - чланÑки ÑтатуÑ, лозинка и опције за лиÑту - . -
    - - - - -

    -

    + + + +
    + чланÑки ÑтатуÑ, лозинка и опције за лиÑту + . +
    + + +

    +

    - - +

    - - - - + +
    - - Промјена ваших информација на лиÑти -
    Можете промјенити адреÑу преко које Ñте пријављени на лиÑту, + + + + - - - - - -
    + +Промјена ваших информација на лиÑти +
    Можете промјенити адреÑу преко које Ñте пријављени на лиÑту, уноÑећи нову у поље иÑпод. У том Ñлучају порука Ñа потврдом упиÑа ће бити поÑлана на ту адреÑу, и промјена мора бити потврђена.

    МогућноÑÑ‚ потврде важи још дана. @@ -49,221 +108,195 @@

    Ðко желите да направите промјене ÑтатуÑа чланÑтва за Ñве лиÑте на које Ñте укључени на , укључите квадратић Глобална промјена . -

    - - - - - - - -
    Ðова адреÑа:
    Поново, за потврду:
    -
    - - +
    Ваше име +

    + + + + + + + +
    Ðова адреÑа:
    Поново, за потврду:
    +
    + + - - -
    Ваше име :
    -
    -

    Глобална промјена

    - +
    + +

    +

    Глобална промјена

    +

    - - - - - -
    - ИÑкључивање Ñа лиÑте - Ваша друга чланÑтва на -
    + + + + - +

    +
    +ИÑкључивање Ñа лиÑте +Ваша друга чланÑтва на +
    Укључите квадратић за потврду и притиÑните овај таÑтер да биÑте Ñе иÑкључили Ñа ове лиÑте Ñлања. Упозорење: Одлука Ñтупа на Ñнагу моментално!

    -

    Можете видјети ÑпиÑак оÑталих лиÑта на које Ñте пријављени..

    -

    КориÑтите ако желите да Ñе ваше промјене глобално одразе.

    -

     

    -

    -

    - -
    -

    Можете видјети ÑпиÑак оÑталих лиÑта на које Ñте пријављени..

    +

    КориÑтите ако желите да Ñе ваше промјене глобално одразе.

    +

     

    +

    +

    + +
    +

    - - - - - +
    - Ваша лозинка за -
    - -
    -

    Заборављена лозинка?

    -
    + + + - -
    +Ваша лозинка за +
    + +
    +

    Заборављена лозинка?

    +
    Кликните на овај таÑтер да би лозинка била поÑлана на вашу е-адреÑу. -

    -

    - -
    -
    - -
    -

    Промјена лозинке

    - - - - - - - - -
    Ðова лозинка:
    Поново за потврду:
    - - -

    глобална промјена. -
    -
    - +

    +

    + +
    +

    + +
    +

    Промјена лозинке

    + + + + + + + + +
    Ðова лозинка:
    Поново за потврду:
    + +

    глобална промјена. +
    +

    - - - - +
    Ваша подешавања
    + + +
    Ваша подешавања
    - -

    Тренутне вриједноÑти Ñу укључене (означене). +

    Тренутне вриједноÑти Ñу укључене (означене).

    Ðеке опције имају могућноÑÑ‚ да буду укључене глобално. Означавајући одговарајуће поље, промјене које направите одразиће Ñе на Ñве лиÑте чији Ñте члан. Note that some of the options have a Set globally checkbox. Checking this field will cause the changes to be made to every mailing list that you are a member of on . - - - - - + + + + %(textlink)s - diff --git a/templates/sv/article.html b/templates/sv/article.html index 3dbf026e..f3ec3f5a 100644 --- a/templates/sv/article.html +++ b/templates/sv/article.html @@ -1,12 +1,13 @@ - - - %(title)s - - - - - + +

    Arkiv för e-postlistan %(listname)s

    +

    Inga meddelanden har skickats till denna lista än, så arkivet är för närvarande tomt. Mer information om listan finns tillgänglig.

    + + diff --git a/templates/sv/headfoot.html b/templates/sv/headfoot.html index 8f86d0aa..087a5e23 100644 --- a/templates/sv/headfoot.html +++ b/templates/sv/headfoot.html @@ -1,9 +1,71 @@ -Den här texten kan innehålla formateringskoder som byts ut mot värden från listans uppsättning. För detaljer, se Pythons formateringsregler (engelska). Giltiga koder är: +Den här texten kan innehÃ¥lla formateringskoder som byts ut mot värden frÃ¥n listans uppsättning. För detaljer, se Pythons formateringsregler (engelska). Giltiga koder är:
      -
    • real_name - Listans formaterade namn, vanligtvis listnamnet med stor initial eller stora bokstäver rakt igenom. -
    • list_name - Listens namn, såsom det används i URL:er, där det har betydelse om namnet stavas med stora eller små bokstäver. -
    • host_name - Internetadressen (fully qualified domain name) till maskinen som listservern körs på. -
    • web_page_url - Bas-URL för Mailman. Denna kan läggas tillsammans med till exempel listinfo/%(list_name)s för att skapa URL:en till en listas informationssida. +
    • real_name - Listans formaterade namn, vanligtvis listnamnet med stor initial eller stora bokstäver rakt igenom. +
    • list_name - Listens namn, sÃ¥som det används i URL:er, där det har betydelse om namnet stavas med stora eller smÃ¥ bokstäver. +
    • host_name - Internetadressen (fully qualified domain name) till maskinen som listservern körs pÃ¥. +
    • web_page_url - Bas-URL för Mailman. Denna kan läggas tillsammans med till exempel listinfo/%(list_name)s för att skapa URL:en till en listas informationssida.
    • description - En kort beskrivning av listan. -
    • info - Fullständig beskrivning av listan. -
    • cgiext - Tillägg som läggs till CGI-skript.
    +
  • info - Fullständig beskrivning av listan. +
  • cgiext - Tillägg som läggs till CGI-skript.
  • diff --git a/templates/sv/listinfo.html b/templates/sv/listinfo.html index 0470d725..19e53568 100644 --- a/templates/sv/listinfo.html +++ b/templates/sv/listinfo.html @@ -1,127 +1,187 @@ - - - - <MM-List-Name> Infosida - - - -

    -

    Слање порука -

    Укључите ову опциеј да биÑте добијале поруке Ñа ове лиÑте. Ðко иÑкључите + + + - - +

    + - - + - - - - - + + + + +

    - - - + - - - - - - - - - - + + + + + + + + - - - - + + + - - - - - - - - + + + + + + - - - + + - - - - - + + + + - - - - +

    + +
    Слање порука +

    Укључите ову опциеј да биÑте добијале поруке Ñа ове лиÑте. Ðко иÑкључите оÑтајете члан, али не желите да добијате пошту (нпр. док Ñте на одмору). Када вам затреба, не заборавите да ову опцију поново укључите. -

    Укључено
    - ИÑкључено -

    Глобално

    Укључено
    +ИÑкључено +

    Глобално

    Преглед порука -

    Ðко ову опцију укључите, добићете Ñве поруке Ñпојене у једно пиÑмо (обично +

    Преглед порука +

    Ðко ову опцију укључите, добићете Ñве поруке Ñпојене у једно пиÑмо (обично једну дневно, могуће и више), умјеÑто Ñваке појединачно. -

    ИÑкључено
    - Укључено
    Добијате MIME или текÑтуални преглед опрука? -

    Ваш читач поште можда не подржава Mime прегледе. Ова опција је углавном +

    ИÑкључено
    +Укључено
    Добијате MIME или текÑтуални преглед опрука? +

    Ваш читач поште можда не подржава Mime прегледе. Ова опција је углавном препоручена, оÑим у Ñлучају да имате проблем при читању порука. -

    - MIME
    - Обичан теÑÑ‚ -

    Глобално

    +MIME
    +Обичан теÑÑ‚ +

    Глобално

    СопÑтвене поруке -

    Обично ћете добити копију Ñваке поруке коју Ñте поÑлали на лиÑту. Ðко +

    СопÑтвене поруке +

    Обично ћете добити копију Ñваке поруке коју Ñте поÑлали на лиÑту. Ðко не желите да добијате ту копију, иÑкључите ову опцију. -

    Ðе
    - Да
    Примање потврдне за Ñлање поруке на лиÑту -

    -

    Ðе
    - Да
    ПодÑјетник лозинке? -

    Једном мјеÑечно ћете добити поруку која Ñадржи лозинку за Ñве лиÑте на +

    Ðе
    +Да
    Примање потврдне за Ñлање поруке на лиÑту +

    +

    Ðе
    +Да
    ПодÑјетник лозинке? +

    Једном мјеÑечно ћете добити поруку која Ñадржи лозинку за Ñве лиÑте на које Ñте учлањени. -

    Ðе
    - Да -

    Глобално

    Сакривање Ñа лиÑте чланова? -

    Када +

    Ðе
    +Да +

    Глобално

    Сакривање Ñа лиÑте чланова? +

    Када неко прегледа лиÑту чланова, обично ће и ваша адреÑа бити приказана (заштићена - од Ñпамера).
    + од Ñпамера).
    Ðко не желите да Ñе ваша адреÑа приказује на тој лиÑти, потврдите то овдје. -

    Ðе
    - Да
    Језик -

    -

    - -
    Које тематÑке категорије желите? -

    Избором једне иил више тема, можете филтрирати Ñаобраћај на лиÑти, тако +

    Ðе
    +Да
    Језик +

    +

    + +
    Које тематÑке категорије желите? +

    Избором једне иил више тема, можете филтрирати Ñаобраћај на лиÑти, тако да примате Ñамо одговарајуће поруке.

    Ðко порука не одговара ни једној од тема, Ð¿Ñ€ÐµÐ½Ð¾Ñ Ð¸Ñте завиÑи од правила подешеног у овој опцији. Ðко не изаберете теме од интереÑовања, добијаћете Ñве поруке Ñа лиÑте. -

    - -
    Желите ли да примате поруку која Ñе не поклапа - ни Ñа једним филтером теме? -

    Ова опција функционише једино ако Ñте упиÑани најмање на једну тему. +

    + +
    Желите ли да примате поруку која Ñе не поклапа + ни Ñа једним филтером теме? +

    Ова опција функционише једино ако Ñте упиÑани најмање на једну тему. Опција Ñе одноÑи на уобичајено понашање за поруке које не одговарају ни једном филтеру.

    Ðко ни једна тема од интереÑа није изабрана, добијаћете Ñве поруке. -

    Ðе
    - Да
    Избјегавање дупликата порука -

    Када Ñте директно укључени у To: или Cc: поља поруке Ñа лиÑте, можете +

    Ðе
    +Да
    Избјегавање дупликата порука +

    Када Ñте директно укључени у To: или Cc: поља поруке Ñа лиÑте, можете изабрати да не примате другу копију Ñа лиÑте Ñлања. Да је за потврду примања копија, не је за добијање копија. -

    Да
    - Ðе -

    Глобално

    -
    -
    Да
    +Ðе +

    Глобално

    +
    +
    -

    - - - - + + +

    diff --git a/templates/sr/private.html b/templates/sr/private.html index fcb9a60b..9a07bad3 100755 --- a/templates/sr/private.html +++ b/templates/sr/private.html @@ -1,55 +1,114 @@ - + - Пријава за улаз у приватну архиву лиÑте: %(realname)s - - - - -
    +Пријава за улаз у приватну архиву лиÑте: %(realname)s + + + + + %(message)s - - - - - - - - - - - - - - - -
    Заштићене - архиве: %(realname)s
    Ел. адреÑа:
    Лозинка:
    -
    - -

    Важно: Одавде па надаље, морате имати укључене - "колачиће" (cookies) у вашем претраживачу интернета, иначе промјене + + + + + + + + + + + + + + + +
    Заштићене + архиве: %(realname)s
    Ел. адреÑа:
    Лозинка:
    +
    +

    Важно: Одавде па надаље, морате имати укључене + "колачиће" (cookies) у вашем претраживачу интернета, иначе промјене неће имати ефекта. -

    "Колачићи" Ñе кориÑте у Mailman-овом админиÑтративном интерфејÑу - да не биÑте морали да Ñе поÑле Ñваке акције поново пријављујете. "Колачић" +

    "Колачићи" Ñе кориÑте у Mailman-овом админиÑтративном интерфејÑу + да не биÑте морали да Ñе поÑле Ñваке акције поново пријављујете. "Колачић" неће више важити поÑлије вашег одјављивања Ñа админиÑтративног интерфејÑа.

    - - - - - - + + + +
    - Password Reminder -
    If you don't remember your password, enter your email address + + + + + + - - - - -
    +Password Reminder +
    If you don't remember your password, enter your email address above and click the Remind button and your password will be emailed to you.
    - +
    +

    diff --git a/templates/sr/roster.html b/templates/sr/roster.html index 6b506f97..54b85f99 100644 --- a/templates/sr/roster.html +++ b/templates/sr/roster.html @@ -1,51 +1,109 @@ - - - - <MM-List-Name> УпиÑани - - - - -

    - - - - - - - - - - - - - - - -
    - - УпиÑани на лиÑту: -
    - -

    -

    - -

    кликните на вашу адреÑу да видите вашу Ñтраницу Ñа опцијама -
    (заграде значе да је Ñлање иÑкључено)

    -
    -
    - - Чланови лиÑте (појединачне поруке): -
    -
    -
    - Чланови лиÑте (преглед порука): -
    -
    -

    -

    -

    -

    - - - + + + +<mm-list-name> УпиÑани</mm-list-name> + + +

    + + + + + + + + + + + + + + + +
    + + УпиÑани на лиÑту: +
    +

    +

    +

    кликните на вашу адреÑу да видите вашу Ñтраницу Ñа опцијама +
    (заграде значе да је Ñлање иÑкључено)

    +
    +
    + + Чланови лиÑте (појединачне поруке): +
    +
    +
    + Чланови лиÑте (преглед порука): +
    +
    +

    +

    +

    +

    + +

    + diff --git a/templates/sr/subscribe.html b/templates/sr/subscribe.html index 30933303..23270e18 100644 --- a/templates/sr/subscribe.html +++ b/templates/sr/subscribe.html @@ -1,9 +1,71 @@ - + -<MM-List-Name> Резултати упиÑа +<mm-list-name> Резултати упиÑа</mm-list-name> -

    Резултати упиÑа

    - - - +

    Резултати упиÑа

    + + + diff --git a/templates/sv/admindbdetails.html b/templates/sv/admindbdetails.html index 907d099f..0c4bace8 100644 --- a/templates/sv/admindbdetails.html +++ b/templates/sv/admindbdetails.html @@ -1,20 +1,80 @@ -Administrativa förfrågningar kan visas på två sätt, antingen som en översikt, eller med alla detaljer. På översiktssidan visas både väntande anmälningar/avanmälningar och e-postbrev som väntar på godkännande för att distribueras via listan. Detaljsidan visar dessutom e-postbrevens meddelandehuvuden och ett utdrag av innehållet. -

    På båda sidorna kan du ange följande åtgärder: +Administrativa förfrÃ¥gningar kan visas pÃ¥ tvÃ¥ sätt, antingen som en översikt, eller med alla detaljer. PÃ¥ översiktssidan visas bÃ¥de väntande anmälningar/avanmälningar och e-postbrev som väntar pÃ¥ godkännande för att distribueras via listan. Detaljsidan visar dessutom e-postbrevens meddelandehuvuden och ett utdrag av innehÃ¥llet. +

    På båda sidorna kan du ange följande åtgärder:

      -
    • Vänta -- Vänta med avgörandet. Ingenting blir gjort med denna förfrågan nu, men e-postbrev som väntar på godkännande kan du hålla på eller skicka vidare (se nedan). -
    • Godkänna -- Godkänna meddelandet som det är och distribuera det till listan. För förfrågningar om medlemskap, anmälningar, avanmälningar och andra ändringar. -
    • Avslå -- Radera meddelandet och ge besked tillbaka till avsändaren om att meddelandet inte blev godkänt. För förfrågningar om medlemskap, avslag på anmälan eller avanmälan. Du bör alltid också ange en orsak i den tillhörande textrutan. -
    • Kasta -- Radera meddelandet utan att något besked skickas till avsändaren. Användbart för skräppost och reklampost (spam). För förfrågningar om medlemskap, avslag på anmälan eller avanmälan utan att den som försökte anmäla sig eller avanmäla sig på listan får veta något. -
    - -

    Kryssa för Behåll meddelandet om du önskar att spara en kopia för systemets administratör. Detta kan vara användbart för meddelanden som är förknippade med missbruk av systemet och som du egentligen vill radera, men som det kan vara nyttigt att ta en titt på senare. -

    Kryssa för Skicka meddelandet vidare och uppge en e-postadress om du vill skicka meddelandet vidare till någon annan. För att redigera ett e-postbrev som hålls tillbaka innan det skickas till listan, bör du skicka det till dig själv (eller eventuellt till listans ägare), och radera det ursprungliga e-postbrevet. Därefter, när e-postbrevet kommer till din in-box, redigerar du det och gör de ändringar som du önskar. Skicka därefter brevet till listan på -nytt med ett Approved: fält i meddelandehuvudet efterföljt av listans lösenord. Kom ihåg att det är vanlig netiquette i ett sådant fall att uppge att du har redigerat innehållet. -

    Om avsändaren är medlem av listan, men är modererad, kan du ta bort moderationsflaggan om du så önskar. Detta är användbart när du har satt upp listan på ett sådant sätt att nya medlemmar har en prövotid på listan, och du har bestämt att den prövotiden nu är över för denna medlem. -

    Om avsändaren inte är medlem av listan, kan du få e-postadressen inlagd i ett av avsändarfiltren. Avsändarfilter förklaras på sidan Filtrering av avsändare och kan vara inställt på auto-godkänna, auto-hållatillbaka, auto-avslå, eller -auto-kasta. Detta val kommer inte att vara tillgängligt om adressen redan är -inlagd i ett sådant filter. -

    Efter det att du gjort dina val, klickar du på knappen Utför överst eller nederst på sidan för att verkställa dina beslut. -

    Tillbaka till översiktssidan. +

  • Vänta -- Vänta med avgörandet. Ingenting blir gjort med denna förfrÃ¥gan nu, men e-postbrev som väntar pÃ¥ godkännande kan du hÃ¥lla pÃ¥ eller skicka vidare (se nedan). +
  • Godkänna -- Godkänna meddelandet som det är och distribuera det till listan. För förfrÃ¥gningar om medlemskap, anmälningar, avanmälningar och andra ändringar. +
  • AvslÃ¥ -- Radera meddelandet och ge besked tillbaka till avsändaren om att meddelandet inte blev godkänt. För förfrÃ¥gningar om medlemskap, avslag pÃ¥ anmälan eller avanmälan. Du bör alltid ocksÃ¥ ange en orsak i den tillhörande textrutan. +
  • Kasta -- Radera meddelandet utan att nÃ¥got besked skickas till avsändaren. Användbart för skräppost och reklampost (spam). För förfrÃ¥gningar om medlemskap, avslag pÃ¥ anmälan eller avanmälan utan att den som försökte anmäla sig eller avanmäla sig pÃ¥ listan fÃ¥r veta nÃ¥got. +
  • +

    Kryssa för Behåll meddelandet om du önskar att spara en kopia för systemets administratör. Detta kan vara användbart för meddelanden som är förknippade med missbruk av systemet och som du egentligen vill radera, men som det kan vara nyttigt att ta en titt på senare. +

    Kryssa för Skicka meddelandet vidare och uppge en e-postadress om du vill skicka meddelandet vidare till någon annan. För att redigera ett e-postbrev som hålls tillbaka innan det skickas till listan, bör du skicka det till dig själv (eller eventuellt till listans ägare), och radera det ursprungliga e-postbrevet. Därefter, när e-postbrevet kommer till din in-box, redigerar du det och gör de ändringar som du önskar. Skicka därefter brevet till listan på +nytt med ett Approved: fält i meddelandehuvudet efterföljt av listans lösenord. Kom ihåg att det är vanlig netiquette i ett sådant fall att uppge att du har redigerat innehållet. +

    Om avsändaren är medlem av listan, men är modererad, kan du ta bort moderationsflaggan om du så önskar. Detta är användbart när du har satt upp listan på ett sådant sätt att nya medlemmar har en prövotid på listan, och du har bestämt att den prövotiden nu är över för denna medlem. +

    Om avsändaren inte är medlem av listan, kan du få e-postadressen inlagd i ett av avsändarfiltren. Avsändarfilter förklaras på sidan Filtrering av avsändare och kan vara inställt på auto-godkänna, auto-hållatillbaka, auto-avslå, eller +auto-kasta. Detta val kommer inte att vara tillgängligt om adressen redan är +inlagd i ett sådant filter. +

    Efter det att du gjort dina val, klickar du på knappen Utför överst eller nederst på sidan för att verkställa dina beslut. +

    Tillbaka till översiktssidan. +

    \ No newline at end of file diff --git a/templates/sv/admindbpreamble.html b/templates/sv/admindbpreamble.html index 66713c01..0be4fc62 100644 --- a/templates/sv/admindbpreamble.html +++ b/templates/sv/admindbpreamble.html @@ -1,4 +1,67 @@ -Denna sida innehåller några av de e-postbrev till listan %(listname)s som hålls tillbaka för godkännande. Nu visar den %(description)s -

    Välj önskat beslut för varje förfrågan. När du är klar, klicka på Utför. Närmare instruktioner hittar du här. +Denna sida innehÃ¥ller nÃ¥gra av de e-postbrev till listan %(listname)s som hÃ¥lls tillbaka för godkännande. Nu visar den %(description)s +

    Välj önskat beslut för varje förfrågan. När du är klar, klicka på Utför. Närmare instruktioner hittar du här. -

    Du kan också visa en översikt över alla förfrågningar som väntar på ett avgörande. +

    Du kan också visa en översikt över alla förfrågningar som väntar på ett avgörande. +

    \ No newline at end of file diff --git a/templates/sv/admindbsummary.html b/templates/sv/admindbsummary.html index 8615045c..fbe92df2 100644 --- a/templates/sv/admindbsummary.html +++ b/templates/sv/admindbsummary.html @@ -1,3 +1,65 @@ -Här finns en översikt över förfrågningar som ska avgöras för e-postlistan %(listname)s.
    Eventuella ansökningar om medlemskap till listan kommer att visas först, därefter kommer meddelanden som har skickats till listan, men som behöver godkännande för att bli distribuerade. -

    Välj önskad åtgärd för varje förfrågan och klicka på knappen Utför när du är klar. Närmare instruktioner finns också tillgängliga. -

    Du kan också visa alla detaljer för alla tillbakahållna meddelanden om du så önskar. \ No newline at end of file +Här finns en översikt över förfrÃ¥gningar som ska avgöras för e-postlistan %(listname)s.
    Eventuella ansökningar om medlemskap till listan kommer att visas först, därefter kommer meddelanden som har skickats till listan, men som behöver godkännande för att bli distribuerade. +

    Välj önskad åtgärd för varje förfrågan och klicka på knappen Utför när du är klar. Närmare instruktioner finns också tillgängliga. +

    Du kan också visa alla detaljer för alla tillbakahållna meddelanden om du så önskar.

    \ No newline at end of file diff --git a/templates/sv/admlogin.html b/templates/sv/admlogin.html index c85183e9..bbcf1055 100755 --- a/templates/sv/admlogin.html +++ b/templates/sv/admlogin.html @@ -1,29 +1,89 @@ - %(listname)s %(who)s Inloggning - - - -
    +%(listname)s %(who)s Inloggning + + + + %(message)s - - - - - - - - - - - -
    - %(listname)s %(who)s Inloggning -
    Listan %(who)s lösenord:
    -
    -

    Viktigt: Från och med nu måste du ha alternativet cookies aktiverat i din browser, annars kommer inga administrativa ändringar att sparas. -

    Session cookies används på Mailmans administrativa sidor, för att du inte ska behöva uppge ditt lösenord för varje ändring som du gör. Dessa cookies kommer att försvinna automatiskt när du stänger din browser. Du kan också ta bort dem manuellt genom att klicka på länken Logga ut under Andra administrativa aktiviteter (som du får upp efter att ha loggat in).

    + + + + + + + + + + + +
    +%(listname)s %(who)s Inloggning +
    Listan %(who)s lösenord:
    +
    +

    Viktigt: Från och med nu måste du ha alternativet cookies aktiverat i din browser, annars kommer inga administrativa ändringar att sparas. +

    Session cookies används på Mailmans administrativa sidor, för att du inte ska behöva uppge ditt lösenord för varje ändring som du gör. Dessa cookies kommer att försvinna automatiskt när du stänger din browser. Du kan också ta bort dem manuellt genom att klicka på länken Logga ut under Andra administrativa aktiviteter (som du får upp efter att ha loggat in).

    diff --git a/templates/sv/archtoc.html b/templates/sv/archtoc.html index ae554b8f..3eec2eb2 100644 --- a/templates/sv/archtoc.html +++ b/templates/sv/archtoc.html @@ -1,20 +1,83 @@ - - - Arkiv för %(listname)s - + + + +Arkiv för %(listname)s + %(meta)s - - -

    Arkiv för %(listname)s

    -

    - Du kan få mer information om denna lista eller - så kan du ladda ner det kompletta arkivet + + +

    Arkiv för %(listname)s

    +

    + Du kan få mer information om denna lista eller + så kan du ladda ner det kompletta arkivet (%(size)s).

    %(noarchive_msg)s %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/sv/archtocentry.html b/templates/sv/archtocentry.html index e78ba5ec..8600174f 100644 --- a/templates/sv/archtocentry.html +++ b/templates/sv/archtocentry.html @@ -1,12 +1,73 @@ - -
    %(archivelabel)s: - [ Tråd (thread) ] - [ Ämne ] - [ Avsändare ] - [ Datum ] -
    %(archivelabel)s: +[ Tråd (thread) ] +[ Ämne ] +[ Avsändare ] +[ Datum ] +
    - - - - - - - - - - - - - - - - - - + + + + + + + + + + + +

    - -- - -
    -

      -

    - Om - - - -
    -

    -

    För att se arkivet över e-postbrev som skickats till denna lista, gå in på arkivet - - . - -

    -
    - Hur används e-postlistan -
    För att skicka ett e-postbrev till alla på listan, skicka det till . + + + +<mm-list-name> Infosida</mm-list-name> + + +

    + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - -
    + -- + +
    +

      +

    +Om + + + +
    +

    +

    För att se arkivet över e-postbrev som skickats till denna lista, gå in på arkivet + +. + +

    +
    +Hur används e-postlistan +
    För att skicka ett e-postbrev till alla på listan, skicka det till . -

    Du kan anmäla dig till listan eller ändra inställningarna för ditt medlemskap på listan nedan:

    Anmäl dig till - -
    -

    Du kan anmäla dig till genom att fylla i nödvändig information nedan. -

      - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      Din e-postadress: -  
      Namn (valfritt): 
      Du kan skriva ett lösenord i följande fält. Detta ger dålig säkerhet, men förhindrar att andra får tillgång til dina inställningar. Använd inte något värdefullt lösenord - lösenordet kan nämligen bli skickat till dig i klartext via e-postbrev. -

      Om du inte skriver något lösenord, kommer du att automatiskt bli tilldelad ett lösenord. Det kommer att skickas till dig så snart du har bekräftat din anmälan. Du kan senare begära att få lösenordet skickat till dig eller ändrat.  - -

      Önskat lösenord: 
      Önskat lösenord en gång till: 
      Välj språk för meddelanden:  
      Vill du få sammandragsversioner? Nej Ja
      -
      -
      - -
    -
    - - Medlemmar av -
    - - - -

    - - - -

    - - - +

    Du kan anmäla dig till listan eller ändra inställningarna för ditt medlemskap på listan nedan:

    Anmäl dig till + +
    +

    Du kan anmäla dig till genom att fylla i nödvändig information nedan. +

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      Din e-postadress: + 
      Namn (valfritt): 
      Du kan skriva ett lösenord i följande fält. Detta ger dålig säkerhet, men förhindrar att andra får tillgång til dina inställningar. Använd inte något värdefullt lösenord - lösenordet kan nämligen bli skickat till dig i klartext via e-postbrev. +

      Om du inte skriver något lösenord, kommer du att automatiskt bli tilldelad ett lösenord. Det kommer att skickas till dig så snart du har bekräftat din anmälan. Du kan senare begära att få lösenordet skickat till dig eller ändrat.  + +

      Önskat lösenord: 
      Önskat lösenord en gång till: 
      Välj språk för meddelanden:  
      Vill du få sammandragsversioner? Nej Ja
      +
      +
      + +
    +
    + +Medlemmar av +
    + + + +

    + + + +

    + +

    + diff --git a/templates/sv/options.html b/templates/sv/options.html index 4240dc82..d84d0e44 100644 --- a/templates/sv/options.html +++ b/templates/sv/options.html @@ -1,253 +1,292 @@ - - Personlig medlemsida för  <MM-Presentable-User> på listan <MM-List-Name> - - - - - -
    Personlig medlemsida för - - på listan - -
    + +Personlig medlemsida för  <mm-presentable-user> på listan <mm-list-name> +</mm-list-name></mm-presentable-user> + + + + +
    Personlig medlemsida för + + på listan + +

    - - - - - +
    - Medlemsstatus, lösenord och inställningar för - på e-postlistan . -
    - - - - -

    -

    + + + +
    + Medlemsstatus, lösenord och inställningar för + på e-postlistan . +
    + + +

    +

    - - +

    - - - - - + + + + %(textlink)s - - diff --git a/templates/tr/archtocnombox.html b/templates/tr/archtocnombox.html index 9b4d0980..fd9c732d 100644 --- a/templates/tr/archtocnombox.html +++ b/templates/tr/archtocnombox.html @@ -1,19 +1,81 @@ - - - %(listname)s Arþivleri - + + + +%(listname)s Arþivleri + %(meta)s - - -

    %(listname)s Arþivleri

    -

    - Bu listeyle ilgili daha fazla bilgi alabilirsiniz. + + +

    %(listname)s Arþivleri

    +

    +Bu listeyle ilgili daha fazla bilgi alabilirsiniz.

    %(noarchive_msg)s %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - - + + diff --git a/templates/tr/article.html b/templates/tr/article.html index 1f5cdb57..bd979794 100644 --- a/templates/tr/article.html +++ b/templates/tr/article.html @@ -1,12 +1,13 @@ - - - %(title)s - - - - - + +

    %(listname)s Arþivleri

    +

    + Bu listeye henüz hiç mesaj gönderilmemiþ, bu yüzden arþivler þu anda + boþ. Bu listeyle ilgili daha fazla bilgi alabilirsiniz.

    - - - + + diff --git a/templates/tr/headfoot.html b/templates/tr/headfoot.html index 80984bbe..53f9f861 100644 --- a/templates/tr/headfoot.html +++ b/templates/tr/headfoot.html @@ -1,28 +1,89 @@ -Bu yazý, liste özniteliklerine karþý çözülecek -Python -biçim dizgileri içerebilir. Ýzin verilen yerine koymalarýn listesi -þunlardýr: +Bu yazý, liste özniteliklerine karþý çözülecek +Python +biçim dizgileri içerebilir. Ãzin verilen yerine koymalarýn listesi +þunlardýr:
      -
    • real_name - Listenin ismi; genellikle listenin - isminin büyük harfle yazýlmasýdýr. +
    • real_name - Listenin ismi; genellikle listenin + isminin büyük harfle yazýlmasýdýr. -
    • list_name - Listenin URL'lerde tanýmlandýðý - isim, büyük-küçük harf burada anlamlýdýr. +
    • list_name - Listenin URL'lerde tanýmlandýðý + isim, büyük-küçük harf burada anlamlýdýr. -
    • host_name - Liste sunucusunun üzerinde - çalýþtýðý bilgisayarýn tam tanýmlý alan ismidir (FQDN). +
    • host_name - Liste sunucusunun üzerinde + çalýþtýðý bilgisayarýn tam tanýmlý alan ismidir (FQDN). -
    • web_page_url - Mailman için temel URL. Bunun - arkasýna, örneðin listinfo/%(list_name)s - eklenerek o mesaj listesi için liste tanýtým sayfasýna +
    • web_page_url - Mailman için temel URL. Bunun + arkasýna, örneðin listinfo/%(list_name)s + eklenerek o mesaj listesi için liste tanýtým sayfasýna gidilebilir. -
    • description - Mesaj listesinin kýsa bir - açýklamasý. +
    • description - Mesaj listesinin kýsa bir + açýklamasý. -
    • info - Mesaj listesinin tam açýklamasý. - -
    • cgiext - CGI betiklerine eklenecek uzantý. -
    +
  • info - Mesaj listesinin tam açýklamasý. +
  • cgiext - CGI betiklerine eklenecek uzantý. +
  • diff --git a/templates/tr/listinfo.html b/templates/tr/listinfo.html index 8cbc23d4..7889428a 100644 --- a/templates/tr/listinfo.html +++ b/templates/tr/listinfo.html @@ -1,149 +1,208 @@ - - - - <MM-List-Name> Bilgi Sayfasý - - - -

    -

    Ändra e-postadress på - -
    Du kan ändra den e-postadress som du använde när du anmälde dig till listan, genom att skriva in en annan e-postadress i fälten nedan. Observera att ett e-postbrev med - närmare instruktioner kommer då att skickas till den nya adressen. Ändringarna träder inte i kraft förrän du har bekräftat dem. Du har på dig att bekräfta ändringen. -

    Du kan även ändra ditt namn på listan (till exempel Sven Svensson). + + + - - - + + +
    Ändra e-postadress på + +
    Du kan ändra den e-postadress som du använde när du anmälde dig till listan, genom att skriva in en annan e-postadress i fälten nedan. Observera att ett e-postbrev med + närmare instruktioner kommer då att skickas till den nya adressen. Ändringarna träder inte i kraft förrän du har bekräftat dem. Du har på dig att bekräfta ändringen. +

    Du kan även ändra ditt namn på listan (till exempel Sven Svensson). -

    Om du vill ändra din e-postadress på alla e-postlistor som du är anmäld till på , kryssa för Ändra alla listor. +

    Om du vill ändra din e-postadress på alla e-postlistor som du är anmäld till på , kryssa för Ändra alla listor. -

    - - - - - - - -
    Ny e-postadress:
    Ny e-postadress en gång till:
    -
    - - + + + - - - + + +
    Ditt namn +

    + + + + + + + +
    Ny e-postadress:
    Ny e-postadress en gång till:
    +
    + + - - -
    Ditt namn (valfritt):
    -
    -

    Ändra min e-postadress på alla - listor som jag är medlem av

    +
    +

    Ändra min e-postadress på alla + listor som jag är medlem av

    -

    - - - - - -
    Avanmäl dig från - - Visa listor som du är medlem av på -
    Kryssa för i kryssrutan och klicka på knappen för att avanmäla dig från denna e-postlista. OBS! Du blir då omedelbart avanmäld från listan! + + + + - + +
    +

    Avanmäl dig från + +Visa listor som du är medlem av på +
    Kryssa för i kryssrutan och klicka på knappen för att avanmäla dig från denna e-postlista. OBS! Du blir då omedelbart avanmäld från listan!

    -

    Du kan välja att se alla listor som du är anmäld till, så att du kan göra samma ändringar på flera listor på en gång. +

    Du kan välja att se alla listor som du är anmäld till, så att du kan göra samma ändringar på flera listor på en gång.

    -

    -
    + + +
    +Ditt lösenord för +
    +
    +

    Har du glömt ditt lösenord?

    - - - - - - +
    - Ditt lösenord för -
    -
    -

    Har du glömt ditt lösenord?

    -
    - Klicka på denna knapp för att få ditt lösenord skickat till dig i ett e-postbrev. -

    - -

    - -
    -
    -
    -

    Ändra lösenord

    - - - - - - - - - -
    -
    - Nytt lösenord:
    -
    - -
    -
    - Nytt lösenord en gång till:
    -
    - -
    - -

    -

    - - Ändra lösenordet för alla listor som jag är medlem av på + Klicka pÃ¥ denna knapp för att fÃ¥ ditt lösenord skickat till dig i ett e-postbrev. +

    + +

    + +
    +

    +
    +

    Ändra lösenord

    + + + + + + + + + +
    +
    + Nytt lösenord:
    +
    + +
    +
    + Nytt lösenord en gång till:
    +
    + +
    + +

    +

    + + Ändra lösenordet för alla listor som jag är medlem av på . -
    -
    -
    - + +

    +

    - - +
    - Dina inställningar för -
    +
    +Dina inställningar för +
    - -

    Gällande inställningar är valda. -

    Observera att några av inställningarna har ett Använd på alla -val. Kryssar du för detta val, kommer alla listor som du är medlem av på - också få den inställning som du sätter här. Klicka på Visa andra listor som jag är medlem av ovan för att se vilka andra e-postlistor som du är medlem av. +

    Gällande inställningar är valda. +

    Observera att några av inställningarna har ett Använd på alla -val. Kryssar du för detta val, kommer alla listor som du är medlem av på + också få den inställning som du sätter här. Klicka på Visa andra listor som jag är medlem av ovan för att se vilka andra e-postlistor som du är medlem av.

    - - - +
    Ta emot meddelanden -

    Sätt detta till Ja för att ta emot e-postbrev som skickas till denna lista. Sätt Nej om du under en period inte önskar att ta emot e-postbrev - som skickas till listan, men ändå vill stå kvar som medlem av listan. (Kan vara användbart - till exempel om du åker bort på semester.) Sätter du detta till Nej, måste - du komma íhåg att sätta tillbaka till Ja igen, när du åter vill ta emot e-postbrev. -

    - Ja
    - Nej

    - - Använd på alla -

    + - - - + + - - - - - - - - - - - - - - - - - - + + + + + + + + +
    Ta emot meddelanden +

    Sätt detta till Ja för att ta emot e-postbrev som skickas till denna lista. Sätt Nej om du under en period inte önskar att ta emot e-postbrev + som skickas till listan, men ändå vill stå kvar som medlem av listan. (Kan vara användbart + till exempel om du åker bort på semester.) Sätter du detta till Nej, måste + du komma íhåg att sätta tillbaka till Ja igen, när du åter vill ta emot e-postbrev. +

    + Ja
    + Nej

    + +Använd på alla +

    - Sammandragsversion

    Sätter du på alternativet sammandragsversion, kommer du att med jämna mellanrum (till exempel en gång om dagen - det beror på hur mycket som skickas till listan) få en samlingsepost som innehåller alla meddelanden som skickas till - listan, istället för att få varje enskilt meddelande. Om du byter från sammandragsversion - till normalversion, kommer du kanske i alla fall att få ett sista e-postbrev med sammandrag, även om du då - redan börjat ta emot enskilda meddelanden till listan.

    - Av
    -
    Ta emot sammandrag som ren text eller i MIME-format? -

    Det finns e-postläsare/mejlprogram som inte stöder sammandrag i MIME-format. Att ta emot sammandrag i MIME-format rekommenderas, men får du problem med att läsa e-post i det formatet, kan du välja ren text här. -

    - MIME
    - Ren text

    - - Använd på alla -

    +Sammandragsversion

    Sätter du på alternativet sammandragsversion, kommer du att med jämna mellanrum (till exempel en gång om dagen - det beror på hur mycket som skickas till listan) få en samlingsepost som innehåller alla meddelanden som skickas till + listan, istället för att få varje enskilt meddelande. Om du byter från sammandragsversion + till normalversion, kommer du kanske i alla fall att få ett sista e-postbrev med sammandrag, även om du då + redan börjat ta emot enskilda meddelanden till listan.

    + Av
    + PÃ¥
    Ta emot sammandrag som ren text eller i MIME-format? +

    Det finns e-postläsare/mejlprogram som inte stöder sammandrag i MIME-format. Att ta emot sammandrag i MIME-format rekommenderas, men får du problem med att läsa e-post i det formatet, kan du välja ren text här. +

    + MIME
    + Ren text

    + + Använd på alla +

    Ta emot dina egna meddelanden till listan? -

    Vanligtvis kommer du att få ta emot e-postbrev som du själv skickar till listan. Om du inte vill ta emot dessa, bör du välja Nej här. -

    - Nej
    - Ja
    Ta emot bekräftelse när du skickar e-post till listan? -

    Välj Ja här om du vill få en bekräftelse varje gång du skickar ett e-postbrev till listan. -

    - Nej
    - Ja
    - Få ditt lösenord skickat till dig med jämna mellanrum?

    En gång i månaden kan det vara så att du får ett e-postbrev som innehåller lösenord till alla listor som du är medlem av på detta system. Du kan här slå av denna - inställning för den enskilda listan - eller för alla listor.

    - Nej
    - Ja

    - - Använd på alla -

    - Vill du visas på listan över medlemmar av listan?

    När någon går in på webbsidan för att se alla medlemmar av listan kommer vanligtvis din e-postadress att visas. (Men stavad på ett sådant sätt att robotar som plockar upp e-postadresser från hemsidor - och kanske därefter - skickar skräppost och reklam/spam till den - inte kommer att förstå att det är en e-postadress.) - Välj Nej här, om du inte vill att din e-postadress ska visas på listan.

    - Nej
    - Ja
    Vilket språk föredrar du? -

    -

    - -
    Vilka ämnen vill du ta emot? -

    Genom att välja ett eller flera ämnen, kan du filtrera meddelandena som skickas till e-postlistan, så att du bara tar emot sådana e-postbrev som intresserar dig. -

    Om ett meddelande inte går under något ämne, beror det på inställningen nedan, om du vill ta emot meddelandet eller inte. Om du inte väljer något ämne här, kommer du att få ta emot alla meddelanden som skickas till listan.

    - -
    - Vill du ta emot meddelanden som inte går in under något ämne? -

    Detta val gäller bara om du valt ett eller flera ämnen ovan. Det avgör om du ska ta mot meddelanden som inte går under något ämne. Väljer du Nej kommer du inte att ta emot dem, väljer du Ja, kommer du att ta emot dem. -

    Om du inte valt några ämnen i inställningen ovan, kommer du att ta emot alla meddelanden som skickas till listan. -

    - Nej
    - Ja
    - Undvika att få e-postbrev från listan som även är adresserade direkt till dig?

    Du kan välja vad som ska hända om det kommer ett e-postbrev till listan som dessutom är adresserat direkt till dig (det vill säga att din e-postadress - står i fältet To: eller Cc:). Välj Ja för att undvika - att ta emot samma e-postbrev från listan, välj Nej för att i alla fall ta emot e-postbrev från listan. -

    För särskilt intresserade: Om listan är uppsatt för att personalisera meddelanden som skickas till listan och du väljer att ta emot e-post från listan, så kommer varje kopia att ha fältet X-Mailman-Copy: yes i meddelandehuvudet. -

    - Nej
    - Ja

    - - Använd på alla -

    -
    -
    Ta emot dina egna meddelanden till listan? +

    Vanligtvis kommer du att få ta emot e-postbrev som du själv skickar till listan. Om du inte vill ta emot dessa, bör du välja Nej här. +

    + Nej
    + Ja
    Ta emot bekräftelse när du skickar e-post till listan? +

    Välj Ja här om du vill få en bekräftelse varje gång du skickar ett e-postbrev till listan. +

    + Nej
    + Ja
    +Få ditt lösenord skickat till dig med jämna mellanrum?

    En gång i månaden kan det vara så att du får ett e-postbrev som innehåller lösenord till alla listor som du är medlem av på detta system. Du kan här slå av denna + inställning för den enskilda listan - eller för alla listor.

    +Nej
    + Ja

    + +Använd på alla +

    +Vill du visas på listan över medlemmar av listan?

    När någon går in på webbsidan för att se alla medlemmar av listan kommer vanligtvis din e-postadress att visas. (Men stavad på ett sådant sätt att robotar som plockar upp e-postadresser från hemsidor - och kanske därefter + skickar skräppost och reklam/spam till den - inte kommer att förstå att det är en e-postadress.) + Välj Nej här, om du inte vill att din e-postadress ska visas på listan.

    + Nej
    + Ja
    Vilket språk föredrar du? +

    +

    + +
    Vilka ämnen vill du ta emot? +

    Genom att välja ett eller flera ämnen, kan du filtrera meddelandena som skickas till e-postlistan, så att du bara tar emot sådana e-postbrev som intresserar dig. +

    Om ett meddelande inte går under något ämne, beror det på inställningen nedan, om du vill ta emot meddelandet eller inte. Om du inte väljer något ämne här, kommer du att få ta emot alla meddelanden som skickas till listan.

    + +
    +Vill du ta emot meddelanden som inte går in under något ämne? +

    Detta val gäller bara om du valt ett eller flera ämnen ovan. Det avgör om du ska ta mot meddelanden som inte går under något ämne. Väljer du Nej kommer du inte att ta emot dem, väljer du Ja, kommer du att ta emot dem. +

    Om du inte valt några ämnen i inställningen ovan, kommer du att ta emot alla meddelanden som skickas till listan. +

    + Nej
    + Ja
    +Undvika att få e-postbrev från listan som även är adresserade direkt till dig?

    Du kan välja vad som ska hända om det kommer ett e-postbrev till listan som dessutom är adresserat direkt till dig (det vill säga att din e-postadress + står i fältet To: eller Cc:). Välj Ja för att undvika + att ta emot samma e-postbrev från listan, välj Nej för att i alla fall ta emot e-postbrev från listan. +

    För särskilt intresserade: Om listan är uppsatt för att personalisera meddelanden som skickas till listan och du väljer att ta emot e-post från listan, så kommer varje kopia att ha fältet X-Mailman-Copy: yes i meddelandehuvudet. +

    + Nej
    + Ja

    + +Använd på alla +

    +
    +

    - - - - + + +

    diff --git a/templates/sv/private.html b/templates/sv/private.html index dcd8f37a..723e8445 100755 --- a/templates/sv/private.html +++ b/templates/sv/private.html @@ -1,49 +1,109 @@ - %(realname)s Inloggning till Privata Arkiv - - - -
    +%(realname)s Inloggning till Privata Arkiv + + + + %(message)s - - - - - - - - - - - - - - - -
    - %(realname)s Inloggning till Privata Arkiv -
    E-postadress:
    Lösenord:
    -
    -

    Viktigt: Från och med nu måste du ha alternativet cookies aktiverat i din browser, annars kommer inga administrativa ändringar att sparas. -

    Session cookies används på Mailmans administrativa sidor, för att du inte ska behöva uppge lösenord för varje ändring som du gör. Dessa cookies kommer att försvinna automatiskt när du stänger din browser. Du kan också ta bort dem manuellt genom att klicka på länken Logga ut under Andra administrativa aktiviteter (som du får upp efter att ha loggat in).

    - - - - - - + + + +
    - Password Reminder -
    If you don't remember your password, enter your email address + + + + + + + + + + + + + + + +
    +%(realname)s Inloggning till Privata Arkiv +
    E-postadress:
    Lösenord:
    +
    +

    Viktigt: Från och med nu måste du ha alternativet cookies aktiverat i din browser, annars kommer inga administrativa ändringar att sparas. +

    Session cookies används på Mailmans administrativa sidor, för att du inte ska behöva uppge lösenord för varje ändring som du gör. Dessa cookies kommer att försvinna automatiskt när du stänger din browser. Du kan också ta bort dem manuellt genom att klicka på länken Logga ut under Andra administrativa aktiviteter (som du får upp efter att ha loggat in).

    + + + + + + - - - - -
    +Password Reminder +
    If you don't remember your password, enter your email address above and click the Remind button and your password will be emailed to you.
    - +

    +

    diff --git a/templates/sv/roster.html b/templates/sv/roster.html index db9687a6..96763f67 100644 --- a/templates/sv/roster.html +++ b/templates/sv/roster.html @@ -1,48 +1,106 @@ - - - <MM-List-Name> Medlemmar - - - - -

    - - - - - - - - - - - - - - - -
    - Medlemmar -
    - -

    -

    - -

    Klicka på din adress för att gå till din personliga sida.
    (Adresser inom parentes har stoppat mottagningen av meddelanden till listan.)

    -
    -
    - Medlemmar i normalversion på : -
    -
    -
    - Medlemmar i sammandragsversion på : -
    -
    -

    -

    -

    -

    - - - + + +<mm-list-name> Medlemmar</mm-list-name> + + +

    + + + + + + + + + + + + + + + +
    + Medlemmar +
    +

    +

    +

    Klicka på din adress för att gå till din personliga sida.
    (Adresser inom parentes har stoppat mottagningen av meddelanden till listan.)

    +
    +
    + Medlemmar i normalversion på : +
    +
    +
    + Medlemmar i sammandragsversion på : +
    +
    +

    +

    +

    +

    + +

    + diff --git a/templates/sv/subscribe.html b/templates/sv/subscribe.html index 526f3b3d..8f27c0eb 100644 --- a/templates/sv/subscribe.html +++ b/templates/sv/subscribe.html @@ -1,9 +1,71 @@ -<MM-List-Name>: Resultat av anmälan +<mm-list-name>: Resultat av anmälan</mm-list-name> -

    : Resultat av anmälan

    - - - +

    : Resultat av anmälan

    + + + diff --git a/templates/tr/admindbdetails.html b/templates/tr/admindbdetails.html index cce2df0e..49ca51bf 100644 --- a/templates/tr/admindbdetails.html +++ b/templates/tr/admindbdetails.html @@ -1,66 +1,127 @@ -Yönetimsel istekler, iki yoldan biriyle görüntülenir, bir -özet sayfasýnda ve bir ayrýntýlar -sayfasýnda. Özet sayfasý, üyelik ve üyelikten çýkma istekleri ile -onayýnýz için bekletilen mesajlarý gönderici e-posta adresine göre -gruplanmýþ þekilde görüntüler. Ayrýntýlar sayfasý, tüm baþlýklar ve -mesaj gövdesinden bir alýntý da içeren, bekletilen her mesajla -ilgili daha ayrýntýlý bir görünüm içerir. +Yönetimsel istekler, iki yoldan biriyle görüntülenir, bir +özet sayfasýnda ve bir ayrýntýlar +sayfasýnda. Özet sayfasý, üyelik ve üyelikten çýkma istekleri ile +onayýnýz için bekletilen mesajlarý gönderici e-posta adresine göre +gruplanmýþ þekilde görüntüler. Ayrýntýlar sayfasý, tüm baþlýklar ve +mesaj gövdesinden bir alýntý da içeren, bekletilen her mesajla +ilgili daha ayrýntýlý bir görünüm içerir. -

    Tüm sayfalarda, aþaðýdaki eylemler kullanýlabilir: +

    Tüm sayfalarda, aþaðýdaki eylemler kullanýlabilir:

      -
    • Ertele -- Kararýnýzý daha sonraya erteler. Þimdilik bu - yönetimsel istek için bir eylem gerçekleþtirilmez, fakat bekletilen - mesajlar için, mesajý hala iletebilir veya saklayabilirsiniz - (aþaðýya bakýn). +
    • Ertele -- Kararýnýzý daha sonraya erteler. Þimdilik bu + yönetimsel istek için bir eylem gerçekleþtirilmez, fakat bekletilen + mesajlar için, mesajý hala iletebilir veya saklayabilirsiniz + (aþaðýya bakýn). -
    • Onayla -- Mesajý, listeye gönderilmesi için onaylar. Üyelik - istekleri için, üyelik durumundaki deðiþikliði onaylar. +
    • Onayla -- Mesajý, listeye gönderilmesi için onaylar. Üyelik + istekleri için, üyelik durumundaki deðiþikliði onaylar. -
    • Reddet -- Mesajý reddeder, göndericiye bir reddetme bildirimi - gönderir ve özgün mesajý gözardý eder. Üyelik istekleri için, üyelik - durumundaki deðiþikliði reddeder. Her iki durumda da, reddetme - nedeniniz olarak, seçeneðin yanýndaki yazý kutusuna bir neden - yazmanýz uygun olacaktýr. +
    • Reddet -- Mesajý reddeder, göndericiye bir reddetme bildirimi + gönderir ve özgün mesajý gözardý eder. Üyelik istekleri için, üyelik + durumundaki deðiþikliði reddeder. Her iki durumda da, reddetme + nedeniniz olarak, seçeneðin yanýndaki yazý kutusuna bir neden + yazmanýz uygun olacaktýr. -
    • Gözardý Et -- Özgün mesajý siler ve göndericiye bir reddetme - bildirimi göndermez. Üyelik istekleri için, isteði yapan kiþiye - bildirmeden, isteði gözardý eder. Bu, genellikle spam mesajlar için - kullanmak isteyebileceðiniz bir eylemdir. -
    +
  • Gözardý Et -- Özgün mesajý siler ve göndericiye bir reddetme + bildirimi göndermez. Üyelik istekleri için, isteði yapan kiþiye + bildirmeden, isteði gözardý eder. Bu, genellikle spam mesajlar için + kullanmak isteyebileceðiniz bir eylemdir. +
  • +

    Bekletilen mesajlar için, Sakla seçeneðini seçerek +mesajýn bir kopyasýný site yöneticisi için kaydedebilirsiniz. Bu, +gözardý etmek, fakat sonraki bir zamanda araþtýrma için kaydýný tutmak +istediðiniz kötü amaçlý mesajlar için kullanýþlýdýr. -

    Bekletilen mesajlar için, Sakla seçeneðini seçerek -mesajýn bir kopyasýný site yöneticisi için kaydedebilirsiniz. Bu, -gözardý etmek, fakat sonraki bir zamanda araþtýrma için kaydýný tutmak -istediðiniz kötü amaçlý mesajlar için kullanýþlýdýr. +

    Mesajý listede olmayan baþka birisine iletmek için Ãlet +seçeneðini seçin ve iletim adresini yazýn. Bekletilen bir mesajý listeye +gönderilmeden önce düzenlemek isterseniz, önce mesajý kendinize (liste +sahiplerine deðil) iletip özgün mesajý gözardý etmelisiniz. Daha sonra +mesaj size geldiðinde düzenlemelerinizi yapýn ve bir Approved: +baþlýðýna liste þifresini yazarak mesajý tekrar listeye gönderin. Bu +durumda, tekrar gönderdiðiniz mesaja, yazýyý düzenlediðinizi açýklayan +bir not eklemeniz uygun olacaktýr. -

    Mesajý listede olmayan baþka birisine iletmek için Ýlet -seçeneðini seçin ve iletim adresini yazýn. Bekletilen bir mesajý listeye -gönderilmeden önce düzenlemek isterseniz, önce mesajý kendinize (liste -sahiplerine deðil) iletip özgün mesajý gözardý etmelisiniz. Daha sonra -mesaj size geldiðinde düzenlemelerinizi yapýn ve bir Approved: -baþlýðýna liste þifresini yazarak mesajý tekrar listeye gönderin. Bu -durumda, tekrar gönderdiðiniz mesaja, yazýyý düzenlediðinizi açýklayan -bir not eklemeniz uygun olacaktýr. +

    Gönderici, moderatör onaylý bir liste üyesiyse isteðe baðlý olarak +o kiþinin moderatör onay ayarýný da silebilirsiniz. Bu, yeni üyeleri +bir deneme süresine koyduðunuzda ve artýk bu üyeye onay gerektirmeden +listeye mesaj gönderebilmesi için güvenilebileceðine karar verdiðinizde +kullanýþlýdýr. -

    Gönderici, moderatör onaylý bir liste üyesiyse isteðe baðlý olarak -o kiþinin moderatör onay ayarýný da silebilirsiniz. Bu, yeni üyeleri -bir deneme süresine koyduðunuzda ve artýk bu üyeye onay gerektirmeden -listeye mesaj gönderebilmesi için güvenilebileceðine karar verdiðinizde -kullanýþlýdýr. - -

    Gönderici bir liste üyesi deðilse, e-posta adresini bir gönderici -filtresine ekleyebilirsiniz. Gönderici filtreleri, gönderici filtreleri gizlilik sayfasýnda -tanýmlanýr ve otomatik onayla (Onaylanacaklar), +

    Gönderici bir liste üyesi deðilse, e-posta adresini bir gönderici +filtresine ekleyebilirsiniz. Gönderici filtreleri, gönderici filtreleri gizlilik sayfasýnda +tanýmlanýr ve otomatik onayla (Onaylanacaklar), otomatik reddet (Reddedilecekler), otomatik beklet -(Bekletilecekler) veya otomatik gözardý et (Gözardý edilecekler) -olabilir. Bu seçenek, e-posta adresi halen bir gönderici filtresine kayýtlýysa -görülmez. +(Bekletilecekler) veya otomatik gözardý et (Gözardý edilecekler) +olabilir. Bu seçenek, e-posta adresi halen bir gönderici filtresine kayýtlýysa +görülmez. -

    Ýþiniz bittiðinde, sayfanýn en altýndaki Tüm Veriyi Gönder -düðmesine týklayýn. Bu düðme, bir karar verdiðiniz tüm yönetimsel -isteklerle ilgili seçilen eylemleri gönderir. +

    Ãþiniz bittiðinde, sayfanýn en altýndaki Tüm Veriyi Gönder +düðmesine týklayýn. Bu düðme, bir karar verdiðiniz tüm yönetimsel +isteklerle ilgili seçilen eylemleri gönderir. -

    Özet sayfasýna geri dön. +

    Özet sayfasýna geri dön. +

    \ No newline at end of file diff --git a/templates/tr/admindbpreamble.html b/templates/tr/admindbpreamble.html index 8a4a53c2..94cb364b 100644 --- a/templates/tr/admindbpreamble.html +++ b/templates/tr/admindbpreamble.html @@ -1,12 +1,75 @@ -Bu sayfa, %(listname)s listesine gönderilmiþ ve onayýnýz için -bekletilen mesajlarýn bir altkümesini içerir. Þu anda görüntülenen: +Bu sayfa, %(listname)s listesine gönderilmiþ ve onayýnýz için +bekletilen mesajlarýn bir altkümesini içerir. Þu anda görüntülenen: %(description)s -

    Her bir yönetimsel istek için, lütfen uygulanacak eylemi seçin ve -iþiniz bitince Tüm Veriyi Gönder düðmesine týklayýn. -Burada daha ayrýntýlý açýklamalarý +

    Her bir yönetimsel istek için, lütfen uygulanacak eylemi seçin ve +iþiniz bitince Tüm Veriyi Gönder düðmesine týklayýn. +Burada daha ayrýntýlý açýklamalarý bulabilirsiniz. -

    Ayrýca tüm bekleyen isteklerin bir özetini de -görebilirsiniz. +

    Ayrýca tüm bekleyen isteklerin bir özetini de +görebilirsiniz. +

    \ No newline at end of file diff --git a/templates/tr/admindbsummary.html b/templates/tr/admindbsummary.html index ab9907b7..cfdce3a9 100644 --- a/templates/tr/admindbsummary.html +++ b/templates/tr/admindbsummary.html @@ -1,14 +1,77 @@ -Bu sayfa -%(listname)s mesaj listesi için, -onayýnýzý bekleyen yönetimsel isteklerin bir özetini içerir. -Önce, bekleyen üyelik ve üyelikten çýkma isteklerini göreceksiniz, -sonra, eðer varsa, onayýnýzý bekleyen mesajlarý göreceksiniz. +Bu sayfa +%(listname)s mesaj listesi için, +onayýnýzý bekleyen yönetimsel isteklerin bir özetini içerir. +Önce, bekleyen üyelik ve üyelikten çýkma isteklerini göreceksiniz, +sonra, eðer varsa, onayýnýzý bekleyen mesajlarý göreceksiniz. -

    Her bir yönetimsel istek için, lütfen uygulanacak eylemi seçin ve -iþiniz bitince Tüm Veriyi Gönder düðmesine týklayýn. -Burada daha ayrýntýlý açýklamalarý +

    Her bir yönetimsel istek için, lütfen uygulanacak eylemi seçin ve +iþiniz bitince Tüm Veriyi Gönder düðmesine týklayýn. +Burada daha ayrýntýlý açýklamalarý bulabilirsiniz. -

    Ayrýca bekletilen tüm mesajlarýn -ayrýntýlarýný da görebilirsiniz. +

    Ayrýca bekletilen tüm mesajlarýn +ayrýntýlarýný da görebilirsiniz. +

    \ No newline at end of file diff --git a/templates/tr/admlogin.html b/templates/tr/admlogin.html index 49f46e3d..2c417a45 100755 --- a/templates/tr/admlogin.html +++ b/templates/tr/admlogin.html @@ -1,41 +1,100 @@ - %(listname)s %(who)s Giriþi - - - -
    +%(listname)s %(who)s Giriþi + + + + %(message)s - - - - - - - - - - - -
    - %(listname)s %(who)s - Giriþi -
    Liste %(who)s Þifresi:
    -
    -

    Önemli: Bu noktadan itibaren tarayýcýnýzda - tanýmlama bilgilerini (cookie) etkinleþtirmiþ olmanýz gerekir, yoksa hiçbir - yönetimsel deðiþiklik yapýlmayacaktýr. + + + + + + + + + + + +
    +%(listname)s %(who)s + Giriþi +
    Liste %(who)s Þifresi:
    +
    +

    Önemli: Bu noktadan itibaren tarayýcýnýzda + tanýmlama bilgilerini (cookie) etkinleþtirmiþ olmanýz gerekir, yoksa hiçbir + yönetimsel deðiþiklik yapýlmayacaktýr. -

    Mailman'in yönetimsel arayüzünde oturum tanýmlayýcý bilgileri - kullanýlýr, böylece her yönetimsel iþlemde tekrar yetkilendirme - yapmanýza gerek kalmaz. Bu tanýmlayýcý bilgi tarayýcýnýzdan - çýktýðýnýzda otomatik olarak yok olur, veya siz tanýmlayýcý - bilgiyi Diðer Yönetimsel Aktiviteler baþlýðý altýndaki - Çýkýþ linkine týklayarak (baþarýyla giriþ yaptýðýnýzda - bunu göreceksiniz) kendiniz yok edebilirsiniz. -

    +

    Mailman'in yönetimsel arayüzünde oturum tanýmlayýcý bilgileri + kullanýlýr, böylece her yönetimsel iþlemde tekrar yetkilendirme + yapmanýza gerek kalmaz. Bu tanýmlayýcý bilgi tarayýcýnýzdan + çýktýðýnýzda otomatik olarak yok olur, veya siz tanýmlayýcý + bilgiyi Diðer Yönetimsel Aktiviteler baþlýðý altýndaki + Çýkýþ linkine týklayarak (baþarýyla giriþ yaptýðýnýzda + bunu göreceksiniz) kendiniz yok edebilirsiniz. +

    - diff --git a/templates/tr/archidxentry.html b/templates/tr/archidxentry.html index f9bb57aa..2efd9486 100644 --- a/templates/tr/archidxentry.html +++ b/templates/tr/archidxentry.html @@ -1,4 +1,67 @@ -
  • %(subject)s -  -%(author)s - +
  • %(subject)s +  +%(author)s + +
  • \ No newline at end of file diff --git a/templates/tr/archidxfoot.html b/templates/tr/archidxfoot.html index 65f341e9..30cefa50 100644 --- a/templates/tr/archidxfoot.html +++ b/templates/tr/archidxfoot.html @@ -1,22 +1,84 @@ - -

    - Son mesaj tarihi: - %(lastdate)s
    - Arþivlendiði tarih: %(archivedate)s -

    -

      -
    • Mesaj sýralamasý: + +

      +Son mesaj tarihi: +%(lastdate)s
      +Arþivlendiði tarih: %(archivedate)s +

      +

      -

      -


      - Bu arþiv Pipermail %(version)s ile - oluþturulmuþtur. - - +
    +

    +


    +Bu arþiv Pipermail %(version)s ile + oluþturulmuþtur. + +

    \ No newline at end of file diff --git a/templates/tr/archidxhead.html b/templates/tr/archidxhead.html index 18b4a747..4d06e71a 100644 --- a/templates/tr/archidxhead.html +++ b/templates/tr/archidxhead.html @@ -1,15 +1,78 @@ - - - %(archtype)s sýralamasý ile %(listname)s %(archive)s Arþivleri - + + + +%(archtype)s sýralamasý ile %(listname)s %(archive)s Arþivleri + %(encoding)s - - - -

    %(archtype)s sýralamasý ile %(archive)s Arþivleri

    -
      -
    • Mesaj sýralamasý: + + + +

      %(archtype)s sýralamasý ile %(archive)s Arþivleri

      + -

      Baþlangýç tarihi: %(firstdate)s
      - Bitiþ tarihi: %(lastdate)s
      - Mesaj sayýsý: %(size)s

      -

        - +
      +

      Baþlangýç tarihi: %(firstdate)s
      +Bitiþ tarihi: %(lastdate)s
      +Mesaj sayýsý: %(size)s

      +

        +

      \ No newline at end of file diff --git a/templates/tr/archlistend.html b/templates/tr/archlistend.html index 9bc052dd..492cf01a 100644 --- a/templates/tr/archlistend.html +++ b/templates/tr/archlistend.html @@ -1 +1,63 @@ -
    + diff --git a/templates/tr/archliststart.html b/templates/tr/archliststart.html index 7e7a476a..ebcb6518 100644 --- a/templates/tr/archliststart.html +++ b/templates/tr/archliststart.html @@ -1,5 +1,69 @@ - - - - - +
    ArþivSýralama:Ýndirilebilir þekli
    + + + + + +
    ArşivSıralama:İndirilebilir şekli
    diff --git a/templates/tr/archtoc.html b/templates/tr/archtoc.html index 999efaeb..6441f2a8 100644 --- a/templates/tr/archtoc.html +++ b/templates/tr/archtoc.html @@ -1,21 +1,83 @@ - - - %(listname)s Arþivleri - + + + +%(listname)s Arþivleri + %(meta)s - - -

    %(listname)s Arþivleri

    -

    - Bu listeyle ilgili daha fazla bilgi alabilirsiniz - veya tüm arþivi indirebilirsiniz + + +

    %(listname)s Arþivleri

    +

    +Bu listeyle ilgili daha fazla bilgi alabilirsiniz + veya tüm arþivi indirebilirsiniz (%(size)s).

    %(noarchive_msg)s %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - - + + diff --git a/templates/tr/archtocentry.html b/templates/tr/archtocentry.html index dc50b395..0323e5ea 100644 --- a/templates/tr/archtocentry.html +++ b/templates/tr/archtocentry.html @@ -1,13 +1,73 @@ - -
    %(archivelabel)s: - [ Thread ] - [ Konu ] - [ Yazar ] - [ Tarih ] -
    %(archivelabel)s: +[ Thread ] +[ Konu ] +[ Yazar ] +[ Tarih ] +
    - - - - - - - - - - - - - - - - - - + + + + + +

    - -- - -
    -

      -

    - Hakkýnda - - - -
    -

    -

    Bu listeye önceden gönderilen mesajlarý görmek için - - Arþivlerini ziyaret edin. - -

    -
    - Listesinin Kullanýmý -
    - Tüm liste üyelerine bir mesaj göndermek için - adresine bir e-posta atýnýz. + + + +<mm-list-name> Bilgi Sayfasý</mm-list-name> + + +

    + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + + + + + + + +
    + -- + +
    +

      +

    + Hakkýnda + + + +
    +

    +

    Bu listeye önceden gönderilen mesajlarý görmek için + + Arþivlerini ziyaret edin. + +

    +
    + Listesinin Kullanýmý +
    + Tüm liste üyelerine bir mesaj göndermek için + adresine bir e-posta atýnýz. -

    Aþaðýdaki bölümlerde listeye üye olabilir veya varolan üyeliðinizi - deðiþtirebilirsiniz. -

    - Listesine Üyelik -
    -

    - listesine aþaðýdaki formu doldurarak üye +

    Aþaðýdaki bölümlerde listeye üye olabilir veya varolan üyeliðinizi + deðiþtirebilirsiniz. +

    + Listesine Üyelik +
    +

    + listesine aþaðýdaki formu doldurarak üye olabilirsiniz. - -

      - - - - - - - - - - - - - - - - - -
      E-posta adresiniz: -  
      Ýsminiz (isteðe baðlý): 
      Aþaðýya bir þifre - girebilirsiniz. Bu sadece hafif bir güvenlik saðlar, fakat - diðer kiþilerin üyelik ayarlarýnýzý karýþtýrmasýný engeller. - Deðerli bir þifre kullanmayýn, çünkü bu - þifre ara sýra size düz yazý olarak gönderilecektir. + +
        + + + + + + + + + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - - - -
        E-posta adresiniz: + 
        Ãsminiz (isteðe baðlý): 
        Aþaðýya bir þifre + girebilirsiniz. Bu sadece hafif bir güvenlik saðlar, fakat + diðer kiþilerin üyelik ayarlarýnýzý karýþtýrmasýný engeller. + Deðerli bir þifre kullanmayýn, çünkü bu + þifre ara sýra size düz yazý olarak gönderilecektir. -

        Eðer þifre girmemeyi tercih ederseniz, sizin için - bir tane þifre otomatik olarak oluþturulacak ve üyeliðinizi - onayladýðýnýz zaman size gönderilecektir. Kiþisel tercihlerinizi - düzenlerken her zaman þifrenizin tekrar size gönderilmesini +

        Eðer þifre girmemeyi tercih ederseniz, sizin için + bir tane þifre otomatik olarak oluþturulacak ve üyeliðinizi + onayladýðýnýz zaman size gönderilecektir. Kiþisel tercihlerinizi + düzenlerken her zaman þifrenizin tekrar size gönderilmesini isteyebilirsiniz. - -
        -
        Bir þifre girin: 
        Onay için þifreyi tekrar girin: 
        Mesajlarýnýzýn görüntülenmesi için hangi dili tercih edersiniz?  
        Liste mesajlarýnýn size günlük toplu mesajlar olarak - gönderilmesini ister misiniz? + + +
        Bir þifre girin: 
        Onay için þifreyi tekrar girin: 
        Mesajlarýnýzýn görüntülenmesi için hangi dili tercih edersiniz?  
        Liste mesajlarýnýn size günlük toplu mesajlar olarak + gönderilmesini ister misiniz? Hayýr - Evet -
        -
        -
        - -
      -
      - - Üyeleri -
      - - - -

      - - - -

      - - - - +
    Hayýr + Evet +
    +
    +
    + + +

    + + Üyeleri +
    + + + +

    + + + +

    + +

    + diff --git a/templates/tr/options.html b/templates/tr/options.html index ba701c14..c1d15b0d 100644 --- a/templates/tr/options.html +++ b/templates/tr/options.html @@ -1,311 +1,342 @@ - - <MM-Presentable-User> için <MM-List-Name> mesaj listesi üyelik yapýlandýrmasý - - - - - -
    - - için mesaj listesi üyelik - yapýlandýrmasý -
    -

    - - - - - + +<mm-presentable-user> için <mm-list-name> mesaj listesi üyelik yapýlandýrmasý + </mm-list-name></mm-presentable-user> + + +
    - kullanýcýsýnýn üyelik durumu, - þifresi ve mesaj listesi için seçenekleri. -
    - - - - -

    -

    +
    + + için mesaj listesi üyelik + yapýlandýrmasý +
    - -

    - - - - - - - - +
    - - üyelik bilgilerinizin deðiþimi -
    Mesaj listesine üye olduðunuz e-posta adresini, aþaðýdaki - alanlara yeni bir adres girerek deðiþtirebilirsiniz. Yeni adrese - bir onay mesajý gidecek ve deðiþiklik, onaylanmadan iþleme konulmayacaktýr. - -

    Onay süreleri yaklaþýk içinde biter. - -

    Ýsteðe baðlý olarak gerçek adýnýzý da burada ayarlayabilir veya deðiþtirebilirsiniz - (örneðin John Smith). - -

    Üyelik deðiþikliklerinin üzerinde üye olduðunuz tüm - listelerde geçerli olmasýný istiyorsanýz, Global olarak deðiþtir - onay kutusunu iþaretleyin. - -

    - - - - - - - -
    Yeni adres:
    Onay için - tekrar girin:
    -
    - - - - -
    Ýsminiz - (isteðe baðlý):
    -
    -

    Global olarak deðiþtir

    + + + +
    + kullanýcýsýnýn üyelik durumu, + þifresi ve mesaj listesi için seçenekleri. +
    + + +

    +

    - +

    - - - - - - + + +

    +

    - Listesinden Çýkýþ - Diðer üyelikleriniz -
    - Onay kutusunu iþaretleyip bu düðmeye týklayarak, bu listedeki - üyeliðinizi bitirebilirsiniz. Uyarý: - Bu eylem anýnda gerçekleþecektir! + + + +
    + + üyelik bilgilerinizin deðiþimi +
    Mesaj listesine üye olduðunuz e-posta adresini, aþaðýdaki + alanlara yeni bir adres girerek deðiþtirebilirsiniz. Yeni adrese + bir onay mesajý gidecek ve deðiþiklik, onaylanmadan iþleme konulmayacaktýr. + +

    Onay süreleri yaklaþýk içinde biter. + +

    Ãsteðe baðlý olarak gerçek adýnýzý da burada ayarlayabilir veya deðiþtirebilirsiniz + (örneðin John Smith). + +

    Üyelik deðiþikliklerinin üzerinde üye olduðunuz tüm + listelerde geçerli olmasýný istiyorsanýz, Global olarak deðiþtir + onay kutusunu iþaretleyin. + +

    + + + + + + + +
    Yeni adres:
    Onay için + tekrar girin:
    +
    + + + + +
    Ãsminiz + (isteðe baðlý):
    +
    +

    Global olarak deðiþtir

    + + + - + +
    +

    + Listesinden Çýkýþ +Diðer üyelikleriniz +
    + Onay kutusunu iþaretleyip bu düðmeye týklayarak, bu listedeki + üyeliðinizi bitirebilirsiniz. Uyarý: + Bu eylem anýnda gerçekleþecektir!

    -

    - üzerinde üyesi olduðunuz tüm diðer mesaj listelerinin - bir listesini görebilirsiniz. Bunu, ayný üyelik seçeneklerini - diðer listelerde de yapmak istiyorsanýz kullanýn. +

    + üzerinde üyesi olduðunuz tüm diðer mesaj listelerinin + bir listesini görebilirsiniz. Bunu, ayný üyelik seçeneklerini + diðer listelerde de yapmak istiyorsanýz kullanýn.

    -

    -
    - - - - - - -
    - Your Password -
    - -
    -

    Þifrenizi mi Unuttunuz?

    -
    - Üyelik adresinize þifrenizin yollanmasýný istiyorsanýz bu - düðmeye týklayýn. -

    -

    - -
    -
    - -
    -

    Þifre Deðiþimi

    - - - - - - - - -
    Yeni - þifre:
    Onay için - tekrar yeni þifre:
    - - -

    Global olarak deðiþtir. -
    -
    - + + + +
    +Your Password +
    + +
    +

    Þifrenizi mi Unuttunuz?

    +
    + Üyelik adresinize þifrenizin yollanmasýný istiyorsanýz bu + düðmeye týklayýn. +

    +

    + +
    +

    + +
    +

    Þifre Deðiþimi

    + + + + + + + + +
    Yeni + þifre:
    Onay için + tekrar yeni þifre:
    + +

    Global olarak deðiþtir. +
    +

    - - +
    - Üyelik Seçenekleriniz -
    +
    + Üyelik Seçenekleriniz +
    -

    -Þu andaki deðerleriniz seçili olanlardýr. - -

    Bazý seçeneklerde bir Global olarak ayarla onay kutusu -olduðuna dikkat edin. Bu kutuyu iþaretlemek üzerindeki -tüm mesaj listesi üyeliklerinizde o seçeneðin deðiþtirilmesine neden olur. -Yukarýda Diðer üyeliklerimi listele düðmesine týklayarak -üye olduðunuz diðer mesaj listelerini görebilirsiniz. +Þu andaki deðerleriniz seçili olanlardýr. +

    Bazý seçeneklerde bir Global olarak ayarla onay kutusu +olduðuna dikkat edin. Bu kutuyu iþaretlemek üzerindeki +tüm mesaj listesi üyeliklerinizde o seçeneðin deðiþtirilmesine neden olur. +Yukarýda Diðer üyeliklerimi listele düðmesine týklayarak +üye olduðunuz diðer mesaj listelerini görebilirsiniz.

    - - - +
    - - Mesaj gönderimi

    - Bu seçeneði Etkin seçerek bu mesaj listesine gönderilen - mesajlarýn size iletilmesini saðlayabilirsiniz. Devre Dýþý - seçeneðini seçerseniz, listeye hala üye olursunuz, fakat size - listeden bir süreliðine mesaj gelmesini durdurabilirsiniz (örneðin - tatile çýktýðýnýzda). Eðer mesaj gönderimini devre dýþý býrakýrsanýz - geri geldiðinizde tekrar etkinleþtirmeyi unutmayýn; kendisi - otomatik olarak etkinleþmeyecektir. -

    - Etkin
    - Devre dýþý

    - Global olarak ayarla -

    + - - - +

    + - - - - - - - - - - - - - - - - - - - + + + + + + + + +
    + +Mesaj gönderimi

    + Bu seçeneði Etkin seçerek bu mesaj listesine gönderilen + mesajlarýn size iletilmesini saðlayabilirsiniz. Devre Dýþý + seçeneðini seçerseniz, listeye hala üye olursunuz, fakat size + listeden bir süreliðine mesaj gelmesini durdurabilirsiniz (örneðin + tatile çýktýðýnýzda). Eðer mesaj gönderimini devre dýþý býrakýrsanýz + geri geldiðinizde tekrar etkinleþtirmeyi unutmayýn; kendisi + otomatik olarak etkinleþmeyecektir. +

    +Etkin
    +Devre dýþý

    +Global olarak ayarla +

    - Toplu Mesaj Modunu Ayarla

    - Toplu mesaj modunu açarsanýz mesajlarý gönderildikleri anda tek tek - almak yerine toplu þekilde (genellikle - günde bir kez, fakat olasýlýkla yoðun listelerde birden çok) alýrsýnýz. - Toplu mesaj modu açýk konumundan kapalý konumuna getirilirse +

    +Toplu Mesaj Modunu Ayarla

    + Toplu mesaj modunu açarsanýz mesajlarý gönderildikleri anda tek tek + almak yerine toplu þekilde (genellikle + günde bir kez, fakat olasýlýkla yoðun listelerde birden çok) alýrsýnýz. + Toplu mesaj modu açýk konumundan kapalý konumuna getirilirse son bir toplu mesaj alabilirsiniz. -

    - Açýk
    - Kapalý -
    - MIME veya Düz Yazý Toplu Mesaj Alýmý

    - E-posta okuyucunuz MIME toplu mesajlarý desteklemiyor olabilir. - Genelde MIME toplu mesajlar tercih edilir ama bunlarý okurken - sorun yaþýyorsanýz düz yazý toplu mesajlarý seçin. -

    - MIME
    - Düz Yazý

    - Global olarak ayarla -

    +Açýk
    +Kapalý +
    +MIME veya Düz Yazý Toplu Mesaj Alýmý

    + E-posta okuyucunuz MIME toplu mesajlarý desteklemiyor olabilir. + Genelde MIME toplu mesajlar tercih edilir ama bunlarý okurken + sorun yaþýyorsanýz düz yazý toplu mesajlarý seçin. +

    +MIME
    +Düz Yazý

    +Global olarak ayarla +

    - Mesaja kendi gönderdiðiniz mesajlarý istiyor musunuz?

    - Genellikle listeye gönderdiðiniz tüm mesajlarýn bir kopyasýný - alýrsýnýz. Eðer bu kopyayý almak istemiyorsanýz, bu seçeneði - Hayýr olarak ayarlayýn. -

    - Hayýr
    - Evet -
    - Listeye mesaj gönderdiðinizde bilgi mesajý almak istiyor musunuz?

    -

    - Hayýr
    - Evet -
    - Bu liste için þifre hatýrlatýcý e-posta almak istiyor musunuz?

    - Ayda bir, bu sunucuda üye olduðunuz tüm listeler için - þifre hatýrlatýcý içeren bir e-posta alýrsýnýz. Bunu, bu liste - için, Hayýr seçeneðini seçerek kapatabilirsiniz. - Üye olduðunuz tüm listeler için þifre hatýrlatýcýlarý kapatýrsanýz - size hiçbir hatýrlatýcý e-posta gönderilmeyecektir. -

    - Hayýr
    - Evet

    - Global olarak ayarla -

    - Üye listesinde kendinizi gizlemek istiyor musunuz?

    - Birisi liste üyelerini görüntülediðinde e-posta adresiniz normalde - görünür (spam toplayýcýlarý engellemek için deðiþtirilmiþ bir þekilde). - Eðer üyelik adresinizin bu listede hiç görünmesini istemiyorsanýz, - bu seçenek için Evet seçin. -

    - Hayýr
    - Evet -
    - Hangi dili tercih edersiniz?

    -

    - -
    - Hangi konu kategorilerine üye olmak istersiniz?

    - Bir veya birden fazla konu seçerek mesaj listesindeki - trafiði filtreleyebilirsiniz, böylece mesajlarýn sadece - bir kýsmýný alýrsýnýz. Eðer bir mesaj seçtiðiniz konulardan - bir ile eþleþirse, o mesajý alýrsýnýz, aksi halde almazsýnýz. - -

    Eðer bir mesaj hiçbir konuyla eþleþmiyorsa, Gönderim kuralý - aþaðýdaki seçeneðe uyacaktýr. Eðer herhangi bir konu - seçmezseniz, mesaj listesine gönderilen tüm mesajlarý - alýrsýnýz. -

    - -
    - Hiçbir konu filtresine uymayan mesajlarý almak istiyor musunuz?

    - - Bu seçenek yukarýda en az bir konuya üye olduysanýz - etkilidir. Hiçbir konu filtresine uymayan mesajlar için - varsayýlan gönderim kuralýný tanýmlar. Burada Hayýr - seçeneðini seçmek, eðer bir mesaj konu filtreleri ile - eþleþmiyorsa, o mesajý almayacaðýnýzý gösterir, Evet - seçmek, filtrelerle eþleþmeyen mesajlarýn da size - geleceðini gösterir. - -

    Eðer yukarýda herhangi bir konu seçmediyseniz, - mesaj listesine atýlan tüm mesajlarý alýrsýnýz. -

    - Hayýr
    - Evet -
    - Mesajlarýn kopyalarý engellensin mi?

    - - Bir liste mesajýnýn Alýcý: veya Cc: - baþlýklarýnda açýk olarak adresiniz varsa, mesaj listesinden - mesajýn baþka bir kopyasýný almamayý tercih edebilirsiniz. - Mesaj listesinden gelecek kopyalarý engellemek için Evet, - kopyalarý almak için Hayýr seçin. - -

    Listede üyeye kiþiselleþtirilmiþ mesajlar etkinse - ve kopyalarý almayý tercih ederseniz, her kopya bir - X-Mailman-Copy: yes baþlýðý içerecektir. - -

    - Hayýr
    - Evet

    - Global olarak ayarla -

    -
    -
    +Mesaja kendi gönderdiðiniz mesajlarý istiyor musunuz?

    + Genellikle listeye gönderdiðiniz tüm mesajlarýn bir kopyasýný + alýrsýnýz. Eðer bu kopyayý almak istemiyorsanýz, bu seçeneði + Hayýr olarak ayarlayýn. +

    +Hayýr
    +Evet +
    +Listeye mesaj gönderdiðinizde bilgi mesajý almak istiyor musunuz?

    +

    +Hayýr
    +Evet +
    +Bu liste için þifre hatýrlatýcý e-posta almak istiyor musunuz?

    + Ayda bir, bu sunucuda üye olduðunuz tüm listeler için + þifre hatýrlatýcý içeren bir e-posta alýrsýnýz. Bunu, bu liste + için, Hayýr seçeneðini seçerek kapatabilirsiniz. + Üye olduðunuz tüm listeler için þifre hatýrlatýcýlarý kapatýrsanýz + size hiçbir hatýrlatýcý e-posta gönderilmeyecektir. +

    +Hayýr
    +Evet

    +Global olarak ayarla +

    +Üye listesinde kendinizi gizlemek istiyor musunuz?

    + Birisi liste üyelerini görüntülediðinde e-posta adresiniz normalde + görünür (spam toplayýcýlarý engellemek için deðiþtirilmiþ bir þekilde). + Eðer üyelik adresinizin bu listede hiç görünmesini istemiyorsanýz, + bu seçenek için Evet seçin. +

    +Hayýr
    +Evet +
    +Hangi dili tercih edersiniz?

    +

    + +
    +Hangi konu kategorilerine üye olmak istersiniz?

    + Bir veya birden fazla konu seçerek mesaj listesindeki + trafiði filtreleyebilirsiniz, böylece mesajlarýn sadece + bir kýsmýný alýrsýnýz. Eðer bir mesaj seçtiðiniz konulardan + bir ile eþleþirse, o mesajý alýrsýnýz, aksi halde almazsýnýz. + +

    Eðer bir mesaj hiçbir konuyla eþleþmiyorsa, Gönderim kuralý + aþaðýdaki seçeneðe uyacaktýr. Eðer herhangi bir konu + seçmezseniz, mesaj listesine gönderilen tüm mesajlarý + alýrsýnýz. +

    + +
    +Hiçbir konu filtresine uymayan mesajlarý almak istiyor musunuz?

    + + Bu seçenek yukarýda en az bir konuya üye olduysanýz + etkilidir. Hiçbir konu filtresine uymayan mesajlar için + varsayýlan gönderim kuralýný tanýmlar. Burada Hayýr + seçeneðini seçmek, eðer bir mesaj konu filtreleri ile + eþleþmiyorsa, o mesajý almayacaðýnýzý gösterir, Evet + seçmek, filtrelerle eþleþmeyen mesajlarýn da size + geleceðini gösterir. + +

    Eðer yukarýda herhangi bir konu seçmediyseniz, + mesaj listesine atýlan tüm mesajlarý alýrsýnýz. +

    +Hayýr
    +Evet +
    +Mesajlarýn kopyalarý engellensin mi?

    + + Bir liste mesajýnýn Alýcý: veya Cc: + baþlýklarýnda açýk olarak adresiniz varsa, mesaj listesinden + mesajýn baþka bir kopyasýný almamayý tercih edebilirsiniz. + Mesaj listesinden gelecek kopyalarý engellemek için Evet, + kopyalarý almak için Hayýr seçin. + +

    Listede üyeye kiþiselleþtirilmiþ mesajlar etkinse + ve kopyalarý almayý tercih ederseniz, her kopya bir + X-Mailman-Copy: yes baþlýðý içerecektir. + +

    +Hayýr
    +Evet

    +Global olarak ayarla +

    +
    +
    -

    - - - - + + +

    - diff --git a/templates/tr/private.html b/templates/tr/private.html index cfba15cd..c3ee976b 100755 --- a/templates/tr/private.html +++ b/templates/tr/private.html @@ -1,61 +1,120 @@ - %(realname)s Özel Arþivleri Giriþi - - - -
    +%(realname)s Özel Arþivleri Giriþi + + + + %(message)s - - - - - - - - - - - - - - - -
    - %(realname)s Özel - Arþivleri Giriþi -
    E-posta adresi:
    Þifre:
    -
    -

    Önemli: Bu noktadan itibaren tarayýcýnýzda - tanýmlama bilgilerini (cookie) etkinleþtirmiþ olmanýz gerekir, yoksa hiçbir - yönetimsel deðiþiklik yapýlmayacaktýr. + + + + + + + + + + + + + + + +
    +%(realname)s Özel + Arþivleri Giriþi +
    E-posta adresi:
    Þifre:
    +
    +

    Önemli: Bu noktadan itibaren tarayýcýnýzda + tanýmlama bilgilerini (cookie) etkinleþtirmiþ olmanýz gerekir, yoksa hiçbir + yönetimsel deðiþiklik yapýlmayacaktýr. -

    Mailman'in yönetimsel arayüzünde oturum tanýmlayýcý bilgileri - kullanýlýr, böylece her yönetimsel iþlemde tekrar yetkilendirme - yapmanýza gerek kalmaz. Bu tanýmlayýcý bilgi tarayýcýnýzdan - çýktýðýnýzda otomatik olarak yok olur, veya siz tanýmlayýcý - bilgiyi Diðer Yönetimsel Aktiviteler baþlýðý altýndaki - Çýkýþ linkine týklayarak (baþarýyla giriþ yaptýðýnýzda - bunu göreceksiniz) kendiniz yok edebilirsiniz. +

    Mailman'in yönetimsel arayüzünde oturum tanýmlayýcý bilgileri + kullanýlýr, böylece her yönetimsel iþlemde tekrar yetkilendirme + yapmanýza gerek kalmaz. Bu tanýmlayýcý bilgi tarayýcýnýzdan + çýktýðýnýzda otomatik olarak yok olur, veya siz tanýmlayýcý + bilgiyi Diðer Yönetimsel Aktiviteler baþlýðý altýndaki + Çýkýþ linkine týklayarak (baþarýyla giriþ yaptýðýnýzda + bunu göreceksiniz) kendiniz yok edebilirsiniz.

    - - - - - - + + + +
    - Password Reminder -
    If you don't remember your password, enter your email address + + + + + + - - - - -
    +Password Reminder +
    If you don't remember your password, enter your email address above and click the Remind button and your password will be emailed to you.
    - +
    +

    - diff --git a/templates/tr/roster.html b/templates/tr/roster.html index 7265a7bd..fff8fd9d 100644 --- a/templates/tr/roster.html +++ b/templates/tr/roster.html @@ -1,54 +1,111 @@ - - - <MM-List-Name> Üyeleri - - - - -

    - - - - - - - - - - - - - - - -
    - - Üyeleri -
    - -

    -

    - -

    Adresinizin üzerine týklayarak üyelik seçenekleri sayfanýza - gidebilirsiniz.
    (Parantez içine alýnmýþ adresler listeden - mesaj alýmýný devre dýþý býrakmýþtýr.)

    -
    -
    - - Listesinin Ayrý Ayrý Mesaj Alan Üyesi: -
    -
    -
    - Listesinin Toplu - Mesaj Alan Üyesi: -
    -
    -

    -

    -

    -

    - - - - + + +<mm-list-name> Üyeleri</mm-list-name> + + +

    + + + + + + + + + + + + + + + +
    + + Üyeleri +
    +

    +

    +

    Adresinizin üzerine týklayarak üyelik seçenekleri sayfanýza + gidebilirsiniz.
    (Parantez içine alýnmýþ adresler listeden + mesaj alýmýný devre dýþý býrakmýþtýr.)

    +
    +
    + + Listesinin Ayrý Ayrý Mesaj Alan Üyesi: +
    +
    +
    + Listesinin Toplu + Mesaj Alan Üyesi: +
    +
    +

    +

    +

    +

    + +

    + diff --git a/templates/tr/subscribe.html b/templates/tr/subscribe.html index 844804da..b6ae86cc 100644 --- a/templates/tr/subscribe.html +++ b/templates/tr/subscribe.html @@ -1,9 +1,71 @@ -<MM-List-Name> Subscription results +<mm-list-name> Subscription results</mm-list-name> -

    Subscription results

    - - - +

    Subscription results

    + + + diff --git a/templates/uk/admindbdetails.html b/templates/uk/admindbdetails.html index 84d51153..420ae4ff 100644 --- a/templates/uk/admindbdetails.html +++ b/templates/uk/admindbdetails.html @@ -1,5 +1,66 @@ -ÐдмініÑтративні запити предÑтавлені двома ÑпоÑобами, на Ñторінці загальний ÑпиÑок, та на Ñторінці +ÐдмініÑтративні запити предÑтавлені двома ÑпоÑобами, на Ñторінці загальний ÑпиÑок, та на Ñторінці подробиці. Сторінка загальний ÑпиÑок міÑтить запити щодо підпиÑки, та Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð¿Ñ–Ð´Ð¿Ð¸Ñки, а також згруповані за адреÑою відправника Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð²Ñ–Ð´ÐºÐ»Ð°Ð´ÐµÐ½Ñ– @@ -29,8 +90,7 @@ членÑтва у ÑпиÑку, це означатиме Ð²Ñ–Ð´ÐºÐ¸Ð´Ð°Ð½Ð½Ñ Ð·Ð¼Ñ–Ð½ у Ñтані підпиÑки без жодного ÑÐ¿Ð¾Ð²Ñ–Ñ‰ÐµÐ½Ð½Ñ Ð¾Ñоби, що надіÑлала запит. Зазвичай цю дію викориÑтовують Ð´Ð»Ñ Ð²Ñ–Ð´ÐºÐ¸Ð´Ð°Ð½Ð½Ñ Ñпаму. - - +

    Якщо ви збираєтеÑÑŒ зберегти копію відкладеного Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð´Ð»Ñ Ð°Ð´Ð¼Ñ–Ð½Ñ–Ñтратора Ñерверу, відзначте параметр Зберегти Цим кориÑтуютьÑÑ Ð´Ð»Ñ Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½ÑŒ Ñкі збираютьÑÑ Ð²Ñ–Ð´ÐºÐ¸Ð½ÑƒÑ‚Ð¸, @@ -65,3 +125,4 @@ буде виконано вÑÑ– дії.

    ПовернутиÑÑŒ до загального ÑпиÑку. +

    \ No newline at end of file diff --git a/templates/uk/admindbpreamble.html b/templates/uk/admindbpreamble.html index c9010c82..56e44b84 100644 --- a/templates/uk/admindbpreamble.html +++ b/templates/uk/admindbpreamble.html @@ -1,4 +1,66 @@ -Ð¦Ñ Ñторінка міÑтить чаÑтину запитів до поштового ÑпиÑку +Ð¦Ñ Ñторінка міÑтить чаÑтину запитів до поштового ÑпиÑку %(listname)s, Ñкі очікують вашого розглÑду. Зараз на ній показані %(description)s @@ -8,3 +70,4 @@

    Також ви можете переглÑнути загальний ÑпиÑок вÑÑ–Ñ… запитів, що очікують розглÑду. +

    \ No newline at end of file diff --git a/templates/uk/admindbsummary.html b/templates/uk/admindbsummary.html index b8b7de6d..d246564c 100644 --- a/templates/uk/admindbsummary.html +++ b/templates/uk/admindbsummary.html @@ -1,4 +1,66 @@ -Ð¦Ñ Ñторінка міÑтить загальний ÑпиÑок адмініÑтративних запитів, +Ð¦Ñ Ñторінка міÑтить загальний ÑпиÑок адмініÑтративних запитів, що очікують вашого розглÑду Ð´Ð»Ñ ÑпиÑку %(listname)s. Ðа початку йде перелік запитів на підпиÑку та Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð¿Ñ–Ð´Ð¿Ð¸Ñки, @@ -11,3 +73,4 @@

    Ðа Ñторінці подробиці ви знайдете докладну інформацію про кожне відкладене повідомленнÑ. +

    \ No newline at end of file diff --git a/templates/uk/admlogin.html b/templates/uk/admlogin.html index fd9f1655..d2fbc160 100755 --- a/templates/uk/admlogin.html +++ b/templates/uk/admlogin.html @@ -1,30 +1,90 @@ - ÐÐ²Ñ‚ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ñ ÐºÐ¾Ñ€Ð¸Ñтувача %(who)s ÑпиÑку лиÑÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ %(listname)s +ÐÐ²Ñ‚ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ñ ÐºÐ¾Ñ€Ð¸Ñтувача %(who)s ÑпиÑку лиÑÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ %(listname)s - - -
    + + + %(message)s - - - - - - - - - - - -
    - ÐÐ²Ñ‚ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ñ ÐºÐ¾Ñ€Ð¸Ñтувача %(who)s ÑпиÑку - лиÑÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ %(listname)s -
    Введіть пароль кориÑтувача %(who)s:
    -
    -

    Увага: Ðеобхідно дозволити + + + + + + + + + + + +
    +ÐÐ²Ñ‚ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ñ ÐºÐ¾Ñ€Ð¸Ñтувача %(who)s ÑпиÑку + лиÑÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ %(listname)s +
    Введіть пароль кориÑтувача %(who)s:
    +
    +

    Увага: Ðеобхідно дозволити викориÑÑ‚Ð°Ð½Ð½Ñ cookies у вашому переглÑдачі, у іншому випадку ви не зможете внеÑти жодних змін. @@ -35,6 +95,6 @@ завершити ÑÐµÐ°Ð½Ñ Ð½Ð°Ñ‚Ð¸Ñшувши поÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð’Ñ–Ð´'єднатиÑÑŒ у розділі Інші адмініÑтративні Ð·Ð°Ð²Ð´Ð°Ð½Ð½Ñ (Ви зможете його побачити лише піÑÐ»Ñ Ð¿Ð¾Ñ‡Ð°Ñ‚ÐºÑƒ ÑеанÑу). -

    +

    diff --git a/templates/uk/archidxentry.html b/templates/uk/archidxentry.html index f9bb57aa..2efd9486 100644 --- a/templates/uk/archidxentry.html +++ b/templates/uk/archidxentry.html @@ -1,4 +1,67 @@ -
  • %(subject)s -  -%(author)s - +
  • %(subject)s +  +%(author)s + +
  • \ No newline at end of file diff --git a/templates/uk/archidxfoot.html b/templates/uk/archidxfoot.html index c1506470..96255c59 100644 --- a/templates/uk/archidxfoot.html +++ b/templates/uk/archidxfoot.html @@ -1,20 +1,83 @@ - -

    - Дата оÑтаннього повідомленнÑ: - %(lastdate)s
    - Ðрхів оновлено: %(archivedate)s -

    -

      -
    • Сортувати за: + +

      +Дата оÑтаннього повідомленнÑ: +%(lastdate)s
      +Ðрхів оновлено: %(archivedate)s +

      +

      -

      -


      - Цей архів було Ñтворено програмою Pipermail %(version)s. - - +
    +

    +


    +Цей архів було Ñтворено програмою Pipermail %(version)s. + + +

    \ No newline at end of file diff --git a/templates/uk/archidxhead.html b/templates/uk/archidxhead.html index a98f200d..81fdb002 100644 --- a/templates/uk/archidxhead.html +++ b/templates/uk/archidxhead.html @@ -1,15 +1,78 @@ - - - Ðрхів %(listname)s, том %(archive)s, Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð²Ð¿Ð¾Ñ€Ñдковані за %(archtype)s - + + + +Ðрхів %(listname)s, том %(archive)s, Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð²Ð¿Ð¾Ñ€Ñдковані за %(archtype)s + %(encoding)s - - - -

    Том %(archive)s, Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð²Ð¿Ð¾Ñ€Ñдковані %(archtype)s

    -
      -
    • Сортувати за: + + + +

      Том %(archive)s, Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð²Ð¿Ð¾Ñ€Ñдковані %(archtype)s

      + -

      Початок: %(firstdate)s
      - Кінець: %(lastdate)s
      - Повідомлень: %(size)s

      -

        +
      +

      Початок: %(firstdate)s
      +Кінець: %(lastdate)s
      +Повідомлень: %(size)s

      +

        +

      \ No newline at end of file diff --git a/templates/uk/archlistend.html b/templates/uk/archlistend.html index 9bc052dd..492cf01a 100644 --- a/templates/uk/archlistend.html +++ b/templates/uk/archlistend.html @@ -1 +1,63 @@ -
    + diff --git a/templates/uk/archliststart.html b/templates/uk/archliststart.html index 9bb211b4..2da39b76 100644 --- a/templates/uk/archliststart.html +++ b/templates/uk/archliststart.html @@ -1,4 +1,67 @@ - - - - +
    Том архівуСортувати за:ВерÑÑ–Ñ Ð´Ð»Ñ Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ
    + + + +
    Том архівуСортувати за:ВерÑÑ–Ñ Ð´Ð»Ñ Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ
    \ No newline at end of file diff --git a/templates/uk/archtoc.html b/templates/uk/archtoc.html index 2b01317e..6ba1f0a9 100644 --- a/templates/uk/archtoc.html +++ b/templates/uk/archtoc.html @@ -1,13 +1,76 @@ - - - Ðрхів %(listname)s - + + + +Ðрхів %(listname)s + %(meta)s - - -

    Ðрхіви %(listname)s

    -

    + + +

    Ðрхіви %(listname)s

    +

    Ви можете отримати докладну інформацію про цей ÑпиÑок розÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð°Ð±Ð¾ завантажити архів у форматі mbox (%(size)s). @@ -16,5 +79,5 @@

    Ðрхіви %(listname)s

    %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/uk/archtocentry.html b/templates/uk/archtocentry.html index e915834a..b7bafb3f 100644 --- a/templates/uk/archtocentry.html +++ b/templates/uk/archtocentry.html @@ -1,12 +1,73 @@ - - - %(archivelabel)s: - - [ за гілками ] - [ за темами ] - [ за авторами ] - [ за датою ] - + + +%(archivelabel)s: + +[ за гілками ] +[ за темами ] +[ за авторами ] +[ за датою ] + %(textlink)s - diff --git a/templates/uk/archtocnombox.html b/templates/uk/archtocnombox.html index cab4a15a..3814b341 100644 --- a/templates/uk/archtocnombox.html +++ b/templates/uk/archtocnombox.html @@ -1,18 +1,81 @@ - - - Ðрхів %(listname)s - + + + +Ðрхів %(listname)s + %(meta)s - - -

    Ðрхів %(listname)s

    -

    + + +

    Ðрхів %(listname)s

    +

    Ви можете отримати додаткову інформацію про цей ÑпиÑок.

    %(noarchive_msg)s %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/uk/article.html b/templates/uk/article.html index deb878d8..fae56296 100644 --- a/templates/uk/article.html +++ b/templates/uk/article.html @@ -1,12 +1,13 @@ - - - %(title)s - - - - - + +

    Ðрхів ÑпиÑку розÑилки %(listname)s

    +

    До цього ÑпиÑку ще не було відправлено жодного повідомленнÑ, тому архів порожній. Поки-що ви можете переглÑнути загальну інформацію про цей ÑпиÑок розÑилки.

    - - + + diff --git a/templates/uk/headfoot.html b/templates/uk/headfoot.html index 16332618..936234c0 100644 --- a/templates/uk/headfoot.html +++ b/templates/uk/headfoot.html @@ -1,10 +1,72 @@ -Цей текÑÑ‚ може включати - +Цей текÑÑ‚ може включати + форматовані Ñ€Ñдки, Ñкі пов'Ñзані з ознаками ÑпиÑку. Перелік дозволених ознак:
      -
    • real_name - "гарна" назва ÑпиÑку; зазвичай назва +
    • real_name - "гарна" назва ÑпиÑку; зазвичай назва ÑпиÑку з великою першою літерою.
    • list_name - назва ÑпиÑку, Ñка викориÑтовуєтьÑÑ @@ -21,4 +83,4 @@
    • info - повний Ð¾Ð¿Ð¸Ñ ÑпиÑку розÑилки.
    • cgiext - Ñ€Ð¾Ð·ÑˆÐ¸Ñ€ÐµÐ½Ð½Ñ Ñ„Ð°Ð¹Ð»Ñ–Ð² CGI Ñценаріїв. -
    + diff --git a/templates/uk/listinfo.html b/templates/uk/listinfo.html index b3aec00e..d2430886 100644 --- a/templates/uk/listinfo.html +++ b/templates/uk/listinfo.html @@ -1,144 +1,204 @@ - - - - Загальна Ñ–Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ Ð¿Ñ€Ð¾ ÑпиÑок розÑилки <MM-List-Name> - - - -

    - - - - - - - - - - - - - - - - - - - + + + + + +

    - -- - -
    -

      -

    - Про ÑпиÑок розÑилки - - - -
    -

    -

    Щоб переглÑнути збірку попередніх повідомлень до ÑпиÑку, - відвідайте Ðрхіви ÑпиÑку розÑилки . - -

    -
    - ВикориÑÑ‚Ð°Ð½Ð½Ñ -
    + + + +Загальна Ñ–Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ Ð¿Ñ€Ð¾ ÑпиÑок розÑилки <mm-list-name></mm-list-name> + + +

    + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + +
    + -- + +
    +

      +

    +Про ÑпиÑок розÑилки + + + +
    +

    +

    Щоб переглÑнути збірку попередніх повідомлень до ÑпиÑку, + відвідайте Ðрхіви ÑпиÑку розÑилки . + +

    +
    +ВикориÑÑ‚Ð°Ð½Ð½Ñ +
    Щоб надіÑлати Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð²Ñім кориÑтувачам ÑпиÑку розÑилки, відправте його за адреÑою - . + .

    Ви можете підпиÑатиÑÑŒ чи змінити параметри підпиÑки на ÑпиÑок розÑилки викориÑтовуючи наведену нижче інформацію. -

    - ПідпиÑатиÑÑŒ на ÑпиÑок розÑилки -
    -

    - Щоб підпиÑатиÑÑŒ на ÑпиÑок розÑилки заповніть наÑтупну форму. - -

      - - - - - - - - - - - - + + + + + + - - - - - -
      Ваша електронна адреÑа: -  
      Ваше ім'Ñ (необов'Ñзково): 
      Ви можете ввеÑти пароль, це зашкодить +

      +ПідпиÑатиÑÑŒ на ÑпиÑок розÑилки +
      +

      + Щоб підпиÑатиÑÑŒ на ÑпиÑок розÑилки заповніть наÑтупну форму. + +

        + + + + + + + + + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - - - -
        Ваша електронна адреÑа: + 
        Ваше ім'Ñ (необов'Ñзково): 
        Ви можете ввеÑти пароль, це зашкодить іншим змінювати параметри вашої підпиÑки. Ðе викориÑтовуйте цінних паролей, тому що паролі надÑилатимутьÑÑ Ð²Ð°Ð¼ відкритим текÑтом. -

        Якщо ви не бажаєте вводити пароль, він може бути згенерований автоматично, +

        Якщо ви не бажаєте вводити пароль, він може бути згенерований автоматично, та його буде надіÑлано вам піÑÐ»Ñ Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð¿Ñ–Ð´Ð¿Ð¸Ñки. Ðа Ñторінці параметрів вашої підпиÑки, Ви завжди зможете запитати щоб ваш пароль було надіÑлано вам електронною поштою. - -
        -
        Вкажіть пароль: 
        Вкажіть ще раз: 
        Якою мовою ви бажаєте отримувати Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð· Ñервера?  
        Бажаєте отримувати Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ñƒ виглÑді щоденних збірок? + + +
        Вкажіть пароль: 
        Вкажіть ще раз: 
        Якою мовою ви бажаєте отримувати Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð· Ñервера?  
        Бажаєте отримувати Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ñƒ виглÑді щоденних збірок? Так - ÐÑ– -
        -
        -
        - -
      -
      - - КориÑтувачі ÑпиÑку розÑилки -
      - - - -

      - - - -

      - - - +
    Так + ÐÑ– +
    +
    +
    + + +

    + +КориÑтувачі ÑпиÑку розÑилки +
    + + + +

    + + + +

    + +

    + diff --git a/templates/uk/options.html b/templates/uk/options.html index aa30aab5..537f4f9a 100644 --- a/templates/uk/options.html +++ b/templates/uk/options.html @@ -1,43 +1,102 @@ - - <MM-Presentable-User>: параметри підпиÑки на ÑпиÑок розÑилки <MM-List-Name> - - - - - -
    - - Параметри підпиÑки на ÑпиÑок розÑилки - -
    + +<mm-presentable-user>: параметри підпиÑки на ÑпиÑок розÑилки <mm-list-name> +</mm-list-name></mm-presentable-user> + + + + +
    + + Параметри підпиÑки на ÑпиÑок розÑилки + +

    - - - - - +
    - Стан підпиÑки , - пароль, та параметри підпиÑки на ÑпиÑок розÑилки . -
    - - - - -

    -

    + + + +
    +Стан підпиÑки , + пароль, та параметри підпиÑки на ÑпиÑок розÑилки . +
    + + +

    +

    - - +

    - - - +
    - - Зміна вашої підпиÑки на ÑпиÑок розÑилки -
    Ви можете змінити адреÑу, куди має надходити підпиÑка. + + + - - - - - -
    + +Зміна вашої підпиÑки на ÑпиÑок розÑилки +
    Ви можете змінити адреÑу, куди має надходити підпиÑка. Вкажіть Ñ—Ñ— у полі нижче. Зауважте, що на нову адреÑу буде відправлено Ð¿Ñ€Ð¾Ñ…Ð°Ð½Ð½Ñ Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¸Ñ‚Ð¸ зміну адреÑи, без Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð·Ð¼Ñ–Ð½Ð¸ внеÑені не будуть. @@ -49,198 +108,177 @@

    Якщо ви бажаєте щоб зазначені зміни відбулиÑÑŒ у вÑÑ–Ñ… ÑпиÑках розÑилки на Ñервері , ввімкніть параметр Змінити глобально. -

    - - - - - - - -
    Ðова адреÑа:
    Ще раз:
    -
    - - - - -
    Ваше ім'Ñ (необов'Ñзково):
    -
    -

    Змінити глобально

    - +

    + + + + + + + +
    Ðова адреÑа:
    Ще раз:
    +

    + + + + +
    Ваше ім'Ñ (необов'Ñзково):
    + +
    +

    Змінити глобально

    +

    - - - - - - - - + + + + %(textlink)s - diff --git a/templates/vi/archtocnombox.html b/templates/vi/archtocnombox.html index efabdcdf..8ca22afb 100644 --- a/templates/vi/archtocnombox.html +++ b/templates/vi/archtocnombox.html @@ -1,18 +1,81 @@ - - - Kho cá»§a %(listname)s - + + + +Kho cá»§a %(listname)s + %(meta)s - - -

    Kho cá»§a %(listname)s

    -

    + + +

    Kho cá»§a %(listname)s

    +

    Bạn có thể lấy thông tin thêm vỠhộp thư chung này.

    %(noarchive_msg)s %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/vi/article.html b/templates/vi/article.html index d2594f18..eb946c58 100644 --- a/templates/vi/article.html +++ b/templates/vi/article.html @@ -1,12 +1,13 @@ - - - %(title)s - - - - - + +

    Kho cá»§a %(listname)s

    +

    Chưa có thư nào được gởi cho há»™p thư chung này, vì vậy kho hiện thá»i là rá»—ng. Bạn có thể xem thông tin thêm vá» há»™p thư này.

    - - + + diff --git a/templates/vi/headfoot.html b/templates/vi/headfoot.html index ef218df1..fab487da 100644 --- a/templates/vi/headfoot.html +++ b/templates/vi/headfoot.html @@ -1,18 +1,80 @@ -Äoạn này có thể gồm -chuá»—i dạng thức Python mà được khá»›p vá»›i thuá»™c tính há»™p thư chung. Danh sách các Ä‘iá»u thay thế có thể là: +Äoạn này có thể gồm +chuá»—i dạng thức Python mà được khá»›p vá»›i thuá»™c tính há»™p thư chung. Danh sách các Ä‘iá»u thay thế có thể là:
      -
    • real_name — ten « xinh » cá»§a há»™p thư chung đó, thưá»ng là tên há»™p thư vá»›i chữ hoa đầu từ. +
    • real_name — ten « xinh » cá»§a há»™p thư chung đó, thưá»ng là tên há»™p thư vá»›i chữ hoa đầu từ. -
    • list_name — tên nhận diện há»™p thư chung đó trong địa chỉ Mạng, mà phân biệt chữ hoa/thưá»ng. +
    • list_name — tên nhận diện há»™p thư chung đó trong địa chỉ Mạng, mà phân biệt chữ hoa/thưá»ng. -
    • host_name — tên miá»n có khả năng đầy đủ nÆ¡i trình phục vụ há»™p thư chung đó có chạy. +
    • host_name — tên miá»n có khả năng đầy đủ nÆ¡i trình phục vụ há»™p thư chung đó có chạy. -
    • web_page_url — địa chỉ cÆ¡ bản cho Mailman. Có thể phụ thêm, v.d. listinfo/%(list_name)s để tạo trang thông tin vá» há»™p thư chung đó. +
    • web_page_url — địa chỉ cÆ¡ bản cho Mailman. Có thể phụ thêm, v.d. listinfo/%(list_name)s để tạo trang thông tin vá» há»™p thư chung đó. -
    • description — mô tả ngắn vá» há»™p thư chung đó. +
    • description — mô tả ngắn vá» há»™p thư chung đó. -
    • info — mô tả đầy đủ vá» há»™p thư chung đó. +
    • info — mô tả đầy đủ vá» há»™p thư chung đó. -
    • cgiext — phần mở rá»™ng được thêm vào tập lệnh CGI. -
    +
  • cgiext — phần mở rá»™ng được thêm vào tập lệnh CGI. +
  • diff --git a/templates/vi/listinfo.html b/templates/vi/listinfo.html index 7d28de56..ae28f602 100644 --- a/templates/vi/listinfo.html +++ b/templates/vi/listinfo.html @@ -1,135 +1,195 @@ - - - - <MM-List-Name>Trang thông tin - - - -

    -

    - Видалити підпиÑку з - Інші ваші підпиÑки на Ñервері -
    + + + + - + +
    +

    +Видалити підпиÑку з +Інші ваші підпиÑки на Ñервері +
    Щоб відпиÑатиÑÑŒ від цього ÑпиÑку ввімкніть параметр Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ñ‚Ð° натиÑніть на цю кнопку. Увага: Цю дію буде виконано негайно!

    -

    +

    Ви можете переглÑнути ÑпиÑок вÑÑ–Ñ… інших ÑпиÑків на , кориÑтувачем Ñких ви Ñ”. ВикориÑтовуйте його, Ñкщо хочете виконати однакові зміни у параметрах підпиÑки в уÑÑ–Ñ… ÑпиÑках розÑилки одночаÑно.

    -

    -
    - - - - - +
    - Пароль до ÑпиÑку розÑилки -
    - -
    -

    Забули пароль?

    -
    + + + - -
    +Пароль до ÑпиÑку розÑилки +
    + +
    +

    Забули пароль?

    +
    ÐатиÑніть на цю кнопку, щоб відправити пароль на адреÑу вказану у параметрах підпиÑки. -

    -

    - -
    -
    - -
    -

    Змінити пароль підпиÑки

    - - - - - - - - -
    Ðовий пароль:
    Ще раз:
    - - -

    Змінити глобально. -
    -
    - +

    +

    + +
    +

    + +
    +

    Змінити пароль підпиÑки

    + + + + + + + + +
    Ðовий пароль:
    Ще раз:
    + +

    Змінити глобально. +
    +

    - - +
    - ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ñ–Ð´Ð¿Ð¸Ñки на ÑпиÑок -
    +
    +ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ñ–Ð´Ð¿Ð¸Ñки на ÑпиÑок +
    -

    Поточні Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð²Ñ–Ð´Ð¼Ñ–Ñ‡ÐµÐ½Ð¾. -

    Зауважте, Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð´ÐµÑких параметрів мають поле Змінити глобально. Ð’Ð²Ñ–Ð¼ÐºÐ½ÐµÐ½Ð½Ñ Ñ†ÑŒÐ¾Ð³Ð¾ Ð¿Ð¾Ð»Ñ Ð¿Ñ€Ð¸Ð·Ð²ÐµÐ´Ðµ до змінити Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð°Ñ€Ð°Ð¼ÐµÑ‚Ñ€Ñƒ в уÑÑ–Ñ… ÑпиÑках розÑилки на . Ð”Ð»Ñ Ð¿ÐµÑ€ÐµÐ³Ð»Ñду переліку ÑпиÑків розÑилок, на Ñкі ви підпиÑані натиÑніть Інші ваші підпиÑки на Ñервері вище..

    - -
    - - ДоÑтавка пошти

    + + - +

    - - - + +

    - - - - - - + + - - + - - - - + + - - + - - + - - - +

    +
    + +ДоÑтавка пошти

    Ð’Ñтановіть цей параметр у Ввімкнено щоб отримувати Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ñ†ÑŒÐ¾Ð³Ð¾ ÑпиÑку розÑилки. Ð’Ñтановіть у Вимкнено Ñкщо хочете залишитиÑÑŒ підпиÑаним, але не хочете отримувати Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð¿ÐµÐ²Ð½Ð¸Ð¹ Ñ‡Ð°Ñ (наприклад через відпуÑтку). Якщо ви вимкнули доÑтавку пошти, не забудьте ввімкнути Ñ—Ñ— коли повернетеÑÑŒ; автоматичного Ð½Ð°Ð³Ð°Ð´ÑƒÐ²Ð°Ð½Ð½Ñ Ð² цьому випадку не передбачено. -

    - Ввімкнено
    - Вимкнено

    - Ð’Ñтановити глобально -

    +Ввімкнено
    +Вимкнено

    +Ð’Ñтановити глобально +

    - Ð’ÑÑ‚Ð°Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ñ€ÐµÐ¶Ð¸Ð¼Ñƒ збірок

    +

    +Ð’ÑÑ‚Ð°Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ñ€ÐµÐ¶Ð¸Ð¼Ñƒ збірок

    Якщо ви ввімкнули режим збірок, ви отримуватимете згруповані Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ (зазвичай один раз на добу але може чаÑтіше Ð´Ð»Ñ Ð²ÐµÐ»Ð¸ÐºÐ¸Ñ… ÑпиÑків), заміÑть окремих. Якщо ви перемикнули режим збірок з Ввімкнено на Вимкнено, ви ще можете отримати один оÑтанню збірку. -

    - Ввімкнено
    - Вимкнено -
    - Отримувати збірки у MIME або звичайним текÑтом?

    +

    +Ввімкнено
    +Вимкнено +
    +Отримувати збірки у MIME або звичайним текÑтом?

    Ваша поштова програма може не підтримувати MIME збірки. Ð’ загалі MIME збірки більш зручні, але Ñкщо у Ð²Ð°Ñ Ð· ними виникають проблеми - оберіть звичайний текÑÑ‚. -

    - MIME
    - Звичайний текÑÑ‚

    - Ð’Ñтановити глобально -

    +MIME
    +Звичайний текÑÑ‚

    +Ð’Ñтановити глобально +

    - Отримувати влаÑні Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð½Ð°Ð´Ñ–Ñлані у ÑпиÑок?

    +

    +Отримувати влаÑні Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð½Ð°Ð´Ñ–Ñлані у ÑпиÑок?

    Зазвичай, ви отримуватимете копію кожного надіÑланого до ÑпиÑку повідомленнÑ. Якщо ви не бажаєте Ñ—Ñ… отримувати вÑтановіть цей параметр у ÐÑ–. -

    - ÐÑ–
    - Так -
    - Отримувати Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ð¿Ð¾ÑˆÑ‚Ð¸ при надÑиланні повідомлень до ÑпиÑку?

    -

    - ÐÑ–
    - Так
    - Отримувати Ð½Ð°Ð³Ð°Ð´ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ð°Ñ€Ð¾Ð»ÑŽ щоміÑÑцÑ?

    +

    +ÐÑ–
    +Так +
    +Отримувати Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ð¿Ð¾ÑˆÑ‚Ð¸ при надÑиланні повідомлень до ÑпиÑку?

    +

    +ÐÑ–
    +Так
    +Отримувати Ð½Ð°Ð³Ð°Ð´ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ð°Ñ€Ð¾Ð»ÑŽ щоміÑÑцÑ?

    Раз на міÑÑць, ви отримуватимете повідомленнÑ, з нагадуваннÑм паролю до кожного ÑпиÑку цього Ñайту, на Ñкі Ви підпиÑані. Ви можете це вимкнути окремо Ð´Ð»Ñ ÐºÐ¾Ð¶Ð½Ð¾Ð³Ð¾ ÑпиÑку, Ñкщо виберете ÐÑ–. Якщо ви вимкнете Ð½Ð°Ð³Ð°Ð´ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ð°Ñ€Ð¾Ð»Ñ–Ð² в уÑÑ–Ñ… ÑпиÑках, на Ñкі Ви підпиÑані, Ð½Ð°Ð³Ð°Ð´ÑƒÐ²Ð°Ð½Ð½Ñ Ð²Ð°Ð¼ не надÑилатиметьÑÑ. -

    - ÐÑ–
    - Так

    - Ð’Ñтановити глобально -

    - Приховувати Ñебе в переліку кориÑтувачів ÑпиÑку розÑилки?

    +

    +ÐÑ–
    +Так

    +Ð’Ñтановити глобально +

    +Приховувати Ñебе в переліку кориÑтувачів ÑпиÑку розÑилки?

    Коли будь-хто переглÑдає перелік кориÑтувачів ÑпиÑку розÑилки, зазвичай показуєтьÑÑ Ñ– ваша адреÑа (у прихованій формі, щоб перешкодити розповÑюджувачам Ñпаму). Якщо ви не бажаєте, щоб ваша адреÑа взагалі показувалаÑÑŒ у цьому переліку, виберіть Так. -

    - ÐÑ–
    - Так -
    - Якою мовою бажаєте кориÑтуватиÑÑŒ?

    -

    - -
    - Ðа Ñкі категорії тем ви хотіли б підпиÑатиÑÑŒ?

    +

    +ÐÑ–
    +Так +
    +Якою мовою бажаєте кориÑтуватиÑÑŒ?

    +

    + +
    +Ðа Ñкі категорії тем ви хотіли б підпиÑатиÑÑŒ?

    Вибираючи одну чи більше тем, ви можете обмежувати трафік ÑпиÑку розÑилки, тобто отримувати лише чаÑтину повідомлень. Якщо Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð²Ñ–Ð´Ð¿Ð¾Ð²Ñ–Ð´Ð°Ñ” @@ -251,12 +289,11 @@

    Змінити пароль підпиÑки

    тоді його Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ð·Ð°Ð»ÐµÐ¶Ð¸Ñ‚ÑŒ від одного з наÑтупних параметрів. Якщо ви не обрали жодної теми - ви отримуватимете вÑÑ– повідомленнÑ, що надходÑть до поштового ÑпиÑку. -
    - -
    - Бажаєте отримувати Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ñ‰Ð¾ не відповідають жодному фільтру теми?

    +

    + +
    +Бажаєте отримувати Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ñ‰Ð¾ не відповідають жодному фільтру теми?

    Цей параметр викориÑтовуєтьÑÑ Ð»Ð¸ÑˆÐµ Ñкщо ви підпиÑані хоча б на одну тему (див. вище). Він визначає типову дію, @@ -268,13 +305,12 @@

    Змінити пароль підпиÑки

    Якщо вище не вибрано жодної тем інтереÑів - ви отримуватимете вÑÑ– повідомленнÑ, що надходÑть до поштового ÑпиÑку. -

    - ÐÑ–
    - Так -
    - Уникати дублікатів повідомлень?

    +

    +ÐÑ–
    +Так +
    +Уникати дублікатів повідомлень?

    Якщо Ð²Ð°Ñ Ð±ÐµÐ·Ð¿Ð¾Ñередньо вказано у To: чи Cc: заголовку повідомленнÑ, ви можете @@ -286,21 +322,17 @@

    Змінити пароль підпиÑки

    та ви вибрали Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ ÐºÐ¾Ð¿Ñ–Ð¹, тоді до кожної копії додаєтьÑÑ Ð·Ð°Ð³Ð¾Ð»Ð¾Ð²Ð¾Ðº X-Mailman-Copy: yes. -
    - ÐÑ–
    - Так

    - Ð’Ñтановити глобально -

    -
    -
    +ÐÑ–
    +Так

    +Ð’Ñтановити глобально +

    +
    +
    -

    - - - - + + +

    diff --git a/templates/uk/private.html b/templates/uk/private.html index 9871cc4a..56b6eddb 100755 --- a/templates/uk/private.html +++ b/templates/uk/private.html @@ -1,33 +1,93 @@ - ÐÐ²Ñ‚ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ñ Ð´Ð¾Ñтупу до закритого ÑпиÑку розÑилки %(realname)s +ÐÐ²Ñ‚ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ñ Ð´Ð¾Ñтупу до закритого ÑпиÑку розÑилки %(realname)s - - -
    + + + %(message)s - - - - - - - - - - - - - - - -
    - ÐÐ²Ñ‚ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ñ Ð´Ð¾Ñтупу до закритого ÑпиÑку розÑилки %(realname)s -
    Електронна адреÑа:
    Пароль:
    -
    -

    Важливо: Вам потрібно дозволити + + + + + + + + + + + + + + + +
    +ÐÐ²Ñ‚ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ñ Ð´Ð¾Ñтупу до закритого ÑпиÑку розÑилки %(realname)s +
    Електронна адреÑа:
    Пароль:
    +
    +

    Важливо: Вам потрібно дозволити викориÑÑ‚Ð°Ð½Ð½Ñ cookies у вашому переглÑдачі, у іншому випадку ви не зможете вноÑити будь-Ñкі зміни @@ -39,21 +99,21 @@ у розділі Інші адмініÑтративні дії (Ви зможете його побачити лише піÑÐ»Ñ Ð¿Ð¾Ñ‡Ð°Ñ‚ÐºÑƒ ÑеанÑу).

    - - - - - - + + + +
    - Password Reminder -
    If you don't remember your password, enter your email address + + + + + + - - - - -
    +Password Reminder +
    If you don't remember your password, enter your email address above and click the Remind button and your password will be emailed to you.
    - +
    +

    diff --git a/templates/uk/roster.html b/templates/uk/roster.html index 20b636c3..9f8ef5a2 100644 --- a/templates/uk/roster.html +++ b/templates/uk/roster.html @@ -1,53 +1,111 @@ - - - КориÑтувачі ÑпиÑку розÑилки <MM-List-Name> - - - - -

    - - - - - - - - - - - - - - - -
    - - КориÑтувачі ÑпиÑку розÑилки -
    - -

    -

    - -

    Щоб переглÑнути/змінити параметру влаÑної підпиÑки, натиÑніть - на поÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð· вашою електронною адреÑою.
    - (ДоÑтавку пошти кориÑтувачам, що вказані у дужках, заблоковано.)

    -
    -
    - - Отримувачів, що отримують окремі Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ ÑпиÑку : -
    -
    -
    - - Отримувачів, що отримують збірки ÑпиÑку : -
    -
    -

    -

    -

    -

    - - - + + +КориÑтувачі ÑпиÑку розÑилки <mm-list-name></mm-list-name> + + +

    + + + + + + + + + + + + + + + +
    + + КориÑтувачі ÑпиÑку розÑилки +
    +

    +

    +

    Щоб переглÑнути/змінити параметру влаÑної підпиÑки, натиÑніть + на поÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð· вашою електронною адреÑою.
    +(ДоÑтавку пошти кориÑтувачам, що вказані у дужках, заблоковано.)

    +
    +
    + + Отримувачів, що отримують окремі Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ ÑпиÑку : +
    +
    +
    + + Отримувачів, що отримують збірки ÑпиÑку : +
    +
    +

    +

    +

    +

    + +

    + diff --git a/templates/uk/subscribe.html b/templates/uk/subscribe.html index 90a646b9..0b0dbba8 100644 --- a/templates/uk/subscribe.html +++ b/templates/uk/subscribe.html @@ -1,9 +1,71 @@ - Результати підпиÑки на ÑпиÑок розÑилки <MM-List-Name> + Результати підпиÑки на ÑпиÑок розÑилки <mm-list-name></mm-list-name> -

    Результати підпиÑки на ÑпиÑок розÑилки

    - - - +

    Результати підпиÑки на ÑпиÑок розÑилки

    + + + diff --git a/templates/vi/admindbdetails.html b/templates/vi/admindbdetails.html index 6358bc16..5f4e5494 100644 --- a/templates/vi/admindbdetails.html +++ b/templates/vi/admindbdetails.html @@ -1,18 +1,78 @@ -Các yêu cầu quản lý được hiển thị bằng má»™t cá»§a hai cách, trên trang tóm tắt, và trên trang chi tiết. Trang tóm tắt chứa các yêu cầu đăng ký và há»§y đăng ký bị hoãn, cùng vá»›i các bài thư được giữ lại cho bạn tán thành, đã nhóm lại theo địa chỉ thư Ä‘iện thư cá»§a ngưá»i gởi. Còn trang chi tiết chứa khung xem chi tiết hÆ¡n vá» má»—i thư đã giữ lại, gồm má»i dòng đầu cá»§a thư đó, và Ä‘oạn trích cá»§a thân thư. +Các yêu cầu quản lý được hiển thị bằng má»™t cá»§a hai cách, trên trang tóm tắt, và trên trang chi tiết. Trang tóm tắt chứa các yêu cầu đăng ký và há»§y đăng ký bị hoãn, cùng vá»›i các bài thư được giữ lại cho bạn tán thành, đã nhóm lại theo địa chỉ thư Ä‘iện thư cá»§a ngưá»i gởi. Còn trang chi tiết chứa khung xem chi tiết hÆ¡n vá» má»—i thư đã giữ lại, gồm má»i dòng đầu cá»§a thư đó, và Ä‘oạn trích cá»§a thân thư.

    Trên cả hai trang, có sẵn những hành động theo đây:

      -
    • Hoãn — Tạm hoãn quyết định đến lúc sau. Không làm gì ngay vá»›i yêu cầu quản lý bị hoãn này, nhưng đối vá»›i bài thư đã giữ lại, bạn vẫn còn có khả năng chuyển tiếp hoặc bảo tồn thư đó (xem dưới). +
    • Hoãn — Tạm hoãn quyết định đến lúc sau. Không làm gì ngay vá»›i yêu cầu quản lý bị hoãn này, nhưng đối vá»›i bài thư đã giữ lại, bạn vẫn còn có khả năng chuyển tiếp hoặc bảo tồn thư đó (xem dưới). -
    • Chấp nhận — Chấp nhận thư đó, chuyển nó tiếp tá»›i há»™p thư chung. Äối vá»›i yêu cầu tư cách thành viên, tán thành sá»± thay đổi trạng thái đăng ký đó. +
    • Chấp nhận — Chấp nhận thư đó, chuyển nó tiếp tá»›i há»™p thư chung. Äối vá»›i yêu cầu tư cách thành viên, tán thành sá»± thay đổi trạng thái đăng ký đó. -
    • Từ chối — Từ chối thư đó, gởi thư thông báo từ chối cho ngưá»i gởi, và há»§y thư gốc. Äối vá»›i yêu cầu tư cách thành viên, từ chối sá»± thay đổi trạng thái đăng ký đó. Trong cả hai trưá»ng hợp, bạn nên nhập lý do từ chối vào há»™p văn bản liên quan. - -
    • Há»§y — Há»§y thư gốc, không gởi thư thông báo từ chối. Äối vá»›i yêu cầu tư cách thành viên, tùy chá»n này đơn giản há»§y yêu cầu đó, không thông báo gì cho ngưá»i đã yêu cầu. Hành động này thưá»ng có ích để xá»­ lý thư rác đã biết. -
    +
  • Từ chối — Từ chối thư đó, gởi thư thông báo từ chối cho ngưá»i gởi, và há»§y thư gốc. Äối vá»›i yêu cầu tư cách thành viên, từ chối sá»± thay đổi trạng thái đăng ký đó. Trong cả hai trưá»ng hợp, bạn nên nhập lý do từ chối vào há»™p văn bản liên quan. +
  • Há»§y — Há»§y thư gốc, không gởi thư thông báo từ chối. Äối vá»›i yêu cầu tư cách thành viên, tùy chá»n này đơn giản há»§y yêu cầu đó, không thông báo gì cho ngưá»i đã yêu cầu. Hành động này thưá»ng có ích để xá»­ lý thư rác đã biết. +
  • Äối vá»›i thư đã giữ lại, hãy bật tùy chá»n Bảo tồn nếu bạn muốn lưu má»™t bản sao thư cho quản trị nÆ¡i Mạng xem. Có ích để xá»­ lý thư lạm dụng mà bạn muốn há»§y, nhưng cần ghi lưu để kiểm tra sau.

    Hãy bật tùy chá»n Chuyển tiếp cho, và Ä‘iá»n địa chỉ chuyển tiếp vào, nếu bạn muốn chuyển tiếp thư đó cho ngưá»i khác không phải trong há»™p thư chung. Äể hiệu chỉnh thư đã giữ lại trước khi chuyển tiếp nó tá»›i há»™p thư, bạn nên chuyển tiếp thư đó cho mình (hoặc cho ngưá»i sở hữu há»™p thư chung), và há»§y thư gốc. Như thế thì, khi thư đó xuất hiện trong há»™p Thư Äến cá»§a bạn, hãy hiệu chỉnh nó và gởi lại nó cho há»™p thư chung, gồm dòng đầu Approved: (Äã tán thành) có mật khẩu há»™p thư chung là giá trị. Quy ước mặc nhận trên Mạng trong trưá»ng hợp này là gồm chú thích trong thư đã gởi lại, giải thích bạn đã sá»­a đổi thân thư đó. @@ -25,3 +85,4 @@

    Khi đã làm xong, bạn hãy nhắp vào nút Äệ trình các thay đổi bên trên hay dưới trang. Cái nút này sẽ thá»±c hiện má»i hành động đã chá»n đối vá»›i má»i yêu cầu quản lý mà bạn đã quyết định.

    VỠtrang tóm tắt. +

    \ No newline at end of file diff --git a/templates/vi/admindbpreamble.html b/templates/vi/admindbpreamble.html index f785c91d..3bffdc92 100644 --- a/templates/vi/admindbpreamble.html +++ b/templates/vi/admindbpreamble.html @@ -1,6 +1,69 @@ -Trang này chứa má»™t tập hợp con cá»§a các bài thư đã gởi cho há»™p thư chung %(listname)smà Ä‘ang được giữ lại cho bạn tán thành. HIện thá»i nó hiển thị %(description)s. +Trang này chứa má»™t tập hợp con cá»§a các bài thư đã gởi cho há»™p thư chung %(listname)smà Ä‘ang được giữ lại cho bạn tán thành. HIện thá»i nó hiển thị %(description)s.

    Äối vá»›i má»—i yêu cầu quản trị, vui lòng chá»n hành động cần làm, rồi nhắp vào cái nút Äệ trình các thay đổi khi làm xong. CÅ©ng có sẵn Hướng dẫn chi tiết.

    Bạn cÅ©ng có thể xem bản tóm tắt má»i yêu cầu bị hoãn. +

    \ No newline at end of file diff --git a/templates/vi/admindbsummary.html b/templates/vi/admindbsummary.html index a3bc7327..b5ba8535 100644 --- a/templates/vi/admindbsummary.html +++ b/templates/vi/admindbsummary.html @@ -1,7 +1,70 @@ -Trang này chứa bản tóm tắt các yêu cầu quản trị cần thiết bạn tán thành cho hộp thư chung %(listname)s. +Trang này chứa bản tóm tắt các yêu cầu quản trị cần thiết bạn tán thành cho hộp thư chung %(listname)s. Trước tiên, bạn sẽ tìm thấy danh sách các yêu cầu đăng ký và hủy đăng ký bị hoãn, nếu có, và sau đó nằm bài thư nào được giữ lại cho bạn tán thành.

    Äối vá»›i má»—i yêu câù quản trị, vui lòng chá»n hành động cần làm, rồi nhắp vào cái nút Äệ trình các thay đổi khi làm xong. CÅ©ng có sẵn Hướng dẫn chi tiết.

    Bạn cÅ©ng có thể xem thông tin vá» má»i bài thư đã giữ lại. +

    \ No newline at end of file diff --git a/templates/vi/admlogin.html b/templates/vi/admlogin.html index ca85e2c8..51b04ec5 100755 --- a/templates/vi/admlogin.html +++ b/templates/vi/admlogin.html @@ -1,32 +1,92 @@ - %(listname)s %(who)s Xác thực - - - -
    +%(listname)s %(who)s Xác thực + + + + %(message)s - - - - - - - - - - - -
    - %(listname)s %(who)s - Xác thực -
    Mật khẩu hộp thư %(who)s:
    -
    -

    Quan trá»ng: để tiếp tục được, bạn phải đã bật tập tin nhận diện cookie trong trình duyệt, nếu không thì thay đổi quản trị sẽ không có tác động. + + + + + + + + + + + +
    +%(listname)s %(who)s + Xác thực +
    Mật khẩu hộp thư %(who)s:
    +
    +

    Quan trá»ng: để tiếp tục được, bạn phải đã bật tập tin nhận diện cookie trong trình duyệt, nếu không thì thay đổi quản trị sẽ không có tác động.

    Cookie phiên chạy được dùng trong giao diện quản trị cá»§a Mailman để tránh trưá»ng hợp bạn phải đăng nhập lại vào má»—i thao tác quản trị. Cookie này sẽ hết hạn dùng tá»± động khi bạn thoát khá»i trình duyệt, hoặc bạn có thể làm cho nó hết hạn dùng bằng cách nhắp vào liên kết Äăng xuất nằm dưới Hoạt động quản trị khác (mà bạn sẽ xem má»™t khi đăng nhập được). -

    +

    diff --git a/templates/vi/archidxentry.html b/templates/vi/archidxentry.html index f9bb57aa..2efd9486 100644 --- a/templates/vi/archidxentry.html +++ b/templates/vi/archidxentry.html @@ -1,4 +1,67 @@ -
  • %(subject)s -  -%(author)s - +
  • %(subject)s +  +%(author)s + +
  • \ No newline at end of file diff --git a/templates/vi/archidxfoot.html b/templates/vi/archidxfoot.html index b57e2695..d05f209d 100644 --- a/templates/vi/archidxfoot.html +++ b/templates/vi/archidxfoot.html @@ -1,20 +1,83 @@ - -

    - Ngày thư cuối cùng: - %(lastdate)s
    - Lưu vào kho : %(archivedate)s -

    -

      -
    • Sắp xếp thư theo : + +

      +Ngày thư cuối cùng: +%(lastdate)s
      +Lưu vào kho : %(archivedate)s +

      +

      -

      -


      - Kho này bị Pipermail %(version)s tạo ra. - - +
    +

    +


    +Kho này bị Pipermail %(version)s tạo ra. + + +

    \ No newline at end of file diff --git a/templates/vi/archidxhead.html b/templates/vi/archidxhead.html index c0ec847a..b600eab9 100644 --- a/templates/vi/archidxhead.html +++ b/templates/vi/archidxhead.html @@ -1,15 +1,78 @@ - - - Kho %(archive)s %(listname)s theo %(archtype)s - + + + +Kho %(archive)s %(listname)s theo %(archtype)s + %(encoding)s - - - -

    Kho %(archive)s theo %(archtype)s

    -
      -
    • Sắp xếp thư theo : + + + +

      Kho %(archive)s theo %(archtype)s

      + -

      Äầu : %(firstdate)s
      - Cuối : %(lastdate)s
      - Thư : %(size)s

      -

        +
      +

      Äầu : %(firstdate)s
      +Cuối : %(lastdate)s
      +Thư : %(size)s

      +

        +

      \ No newline at end of file diff --git a/templates/vi/archlistend.html b/templates/vi/archlistend.html index 9bc052dd..492cf01a 100644 --- a/templates/vi/archlistend.html +++ b/templates/vi/archlistend.html @@ -1 +1,63 @@ -
    + diff --git a/templates/vi/archliststart.html b/templates/vi/archliststart.html index 9c0f40ff..ae249f8b 100644 --- a/templates/vi/archliststart.html +++ b/templates/vi/archliststart.html @@ -1,4 +1,67 @@ - - - - +
    KhoXem theo :Phiên bản tải vỠđược
    + + + +
    KhoXem theo :Phiên bản tải vỠđược
    \ No newline at end of file diff --git a/templates/vi/archtoc.html b/templates/vi/archtoc.html index 67fc188b..8c24a63f 100644 --- a/templates/vi/archtoc.html +++ b/templates/vi/archtoc.html @@ -1,13 +1,76 @@ - - - Kho cá»§a %(listname)s - + + + +Kho cá»§a %(listname)s + %(meta)s - - -

    Kho cá»§a %(listname)s

    -

    + + +

    Kho cá»§a %(listname)s

    +

    Bạn có thể lấythông tin thêm vỠhộp thư chung này hoặc bạn có thể tải vỠkho thô đầy đủ (%(size)s). @@ -16,5 +79,5 @@

    Kho cá»§a %(listname)s

    %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/vi/archtocentry.html b/templates/vi/archtocentry.html index 722c84e6..d5b08c2d 100644 --- a/templates/vi/archtocentry.html +++ b/templates/vi/archtocentry.html @@ -1,12 +1,73 @@ - -
    %(archivelabel)s: - [ Nhánh ] - [ Chủ đỠ] - [ Tác giả ] - [ Ngày ] -
    %(archivelabel)s: +[ Nhánh ] +[ Chủ đỠ] +[ Tác giả ] +[ Ngày ] +
    - - - - - - - - - - - - - - - - - - + + + + + +

    - -- - -
    -

      -

    - Giới thiệu vỠ- - - -
    -

    -

    Äể xem các bài thư được gởi cho há»™p thư này trước này, hãy thăm Kho. - -

    -
    - Cách sử dụng -
    + + + +<mm-list-name>Trang thông tin</mm-list-name> + + +

    + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + +
    + -- + +
    +

      +

    +Giới thiệu vỠ+ + + +
    +

    +

    Äể xem các bài thư được gởi cho há»™p thư này trước này, hãy thăm Kho. + +

    +
    +Cách sử dụng +
    Äể gởi thư cho má»i thành viên há»™p thư chung, hãy gởi thư cho địa chỉ thư - . + .

    Bạn có thể đăng ký với hộp thư chung, hoặc thay đổi sự đăng ký đã có, trong các phần bên dưới. -

    - Äăng ký vá»›i -
    -

    - Hãy đăng ký vá»›i há»™p thư chung bằng cách Ä‘iá»n vào đơn theo đây. - -

      - - - - - - - - - - - - + + + + + + - - - - - -
      Äịa chỉ thư cá»§a bạn: -  
      Há» tên bạn (tùy chá»n): 
      Bạn có thể nhập má»™t mật khẩu riêng bên dưới. Nó cung cấp chỉ ít bảo mật, nhưng mà nên ngăn cản ngưá»i khác sá»­a đổi sá»± đăng ký cá»§a bạn. Äừng nhập mật khẩu quan trá»ng nào vì thỉng thoảng nó sẽ được gởi cho bạn trong thư không mật mã. +

      +Äăng ký vá»›i +
      +

      + Hãy đăng ký vá»›i há»™p thư chung bằng cách Ä‘iá»n vào đơn theo đây. + +

        + + + + + + + + + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - - - -
        Äịa chỉ thư cá»§a bạn: + 
        Há» tên bạn (tùy chá»n): 
        Bạn có thể nhập má»™t mật khẩu riêng bên dưới. Nó cung cấp chỉ ít bảo mật, nhưng mà nên ngăn cản ngưá»i khác sá»­a đổi sá»± đăng ký cá»§a bạn. Äừng nhập mật khẩu quan trá»ng nào vì thỉng thoảng nó sẽ được gởi cho bạn trong thư không mật mã. -

        Nếu bạn chá»n không nhập mật khẩu, phần má»m này sẽ tạo ra tá»± động mật khẩu cho bạn, và nó sẽ được gởi cho bạn má»™t khi bạn đã xác nhận đăng ký. HÆ¡n nữa, bạn có thể yêu cầu nhận thư chứa mật khẩu mình, vào lúc nào bạn sá»­a đổi các tùy chá»n cá nhân. - -
        -
        Chá»n mật khẩu : 
        Nhập lại mật khẩu để xác nhân: 
        Bạn có muốn hiển thị thư bằng ngôn ngữ nào?  
        Bạn có muốn nhận bó thư không? +

        Nếu bạn chá»n không nhập mật khẩu, phần má»m này sẽ tạo ra tá»± động mật khẩu cho bạn, và nó sẽ được gởi cho bạn má»™t khi bạn đã xác nhận đăng ký. HÆ¡n nữa, bạn có thể yêu cầu nhận thư chứa mật khẩu mình, vào lúc nào bạn sá»­a đổi các tùy chá»n cá nhân. + + +
        Chá»n mật khẩu : 
        Nhập lại mật khẩu để xác nhân: 
        Bạn có muốn hiển thị thư bằng ngôn ngữ nào?  
        Bạn có muốn nhận bó thư không? Không - Có -
        -
        -
        - -
      -
      - - Thành viên -
      - - - -

      - - - -

      - - - +
    Không + Có +
    +
    +
    + + +

    + + Thành viên +
    + + + +

    + + + +

    + +

    + diff --git a/templates/vi/options.html b/templates/vi/options.html index cbcf7759..4c61f32b 100644 --- a/templates/vi/options.html +++ b/templates/vi/options.html @@ -1,40 +1,99 @@ - - Cấu hình thành viên <MM-Presentable-User> cho hộp thư chung <MM-List-Name> - - - - - -
    - - Cấu hình thành viên hộp thư chung cho -
    + +Cấu hình thành viên <mm-presentable-user> cho hộp thư chung <mm-list-name> +</mm-list-name></mm-presentable-user> + + + + +
    + + Cấu hình thành viên hộp thư chung cho +

    - - - - - +
    - Trạng thái đăng ký, mật khẩu và các tùy chá»n cá»§a cho há»™p thư chung . -
    - - - - -

    -

    + + + +
    +Trạng thái đăng ký, mật khẩu và các tùy chá»n cá»§a cho há»™p thư chung . +
    + + +

    +

    - - +

    - - - +
    - - Thay đổi thông tin thành viên của bạn -
    Bạn có thể thay đổi địa chỉ đã đăng ký vá»›i há»™p thư chung này, bằng cách nhập địa chỉ thư má»›i vào trưá»ng bên dưới. Ghi chú rằng địa chỉ má»›i sẽ nhận má»™t lá thư xác nhận, và việc xác nhận này phải làm xong để kích hoạt thay đổi này. + + + - - - - - -
    + +Thay đổi thông tin thành viên của bạn +
    Bạn có thể thay đổi địa chỉ đã đăng ký vá»›i há»™p thư chung này, bằng cách nhập địa chỉ thư má»›i vào trưá»ng bên dưới. Ghi chú rằng địa chỉ má»›i sẽ nhận má»™t lá thư xác nhận, và việc xác nhận này phải làm xong để kích hoạt thay đổi này.

    Việc xác nhận quá giỠsau khoảng . @@ -44,211 +103,184 @@

    Nếu bạn muốn thay đổi thông tin thành viên mình trong má»i há»™p thư chung vá»›i đó bạn đã đăng ký trên , hãy bật tùy chá»n Äổi toàn cục. -

    - - - - - - - -
    Äịa chỉ má»›i :
    Nhập lại để xác nhận:
    -
    - - - - -
    Há» tên bạn (tùy chá»n):
    -
    -

    Äổi toàn cục

    - +

    + + + + + + + +
    Äịa chỉ má»›i :
    Nhập lại để xác nhận:
    +

    + + + + +
    Há» tên bạn (tùy chá»n):
    + +
    +

    Äổi toàn cục

    +

    - - - - - - - - + + + + %(textlink)s - diff --git a/templates/zh_CN/archtocnombox.html b/templates/zh_CN/archtocnombox.html index e7a2c4f7..78ffe620 100644 --- a/templates/zh_CN/archtocnombox.html +++ b/templates/zh_CN/archtocnombox.html @@ -1,18 +1,81 @@ - - - %(listname)s 邮件归档 - + + + +%(listname)s 邮件归档 + %(meta)s - - -

    %(listname)s 邮件归档

    -

    + + +

    %(listname)s 邮件归档

    +

    您å¯ä»¥èŽ·å¾—å…³äºŽè¿™ä¸ªåˆ—è¡¨çš„æ›´å¤šä¿¡æ¯.

    %(noarchive_msg)s %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/zh_CN/article.html b/templates/zh_CN/article.html index 9b3990b6..598304bb 100644 --- a/templates/zh_CN/article.html +++ b/templates/zh_CN/article.html @@ -1,12 +1,13 @@ - - - %(title)s - - - - - + +

    %(listname)s 邮件归档

    +

    现在还没有å‘到此列表的信æ¯ï¼Œæ‰€ä»¥å½“å‰çš„列表归档为空。 您å¯ä»¥èŽ·å¾—å…³äºŽæ­¤åˆ—è¡¨çš„æ›´å¤šä¿¡æ¯.

    - - + + diff --git a/templates/zh_CN/headfoot.html b/templates/zh_CN/headfoot.html index 6653ceeb..d739e24d 100644 --- a/templates/zh_CN/headfoot.html +++ b/templates/zh_CN/headfoot.html @@ -1,8 +1,70 @@ -此文本å¯ä»¥åŒ…括 -Python +此文本å¯ä»¥åŒ…括 +Python æ ¼å¼å­—符串,它们将根æ®åˆ—表属性æ¥è¢«è§£é‡Šã€‚å¯ç”¨çš„æ›¿æ¢æœ‰ï¼š
      -
    • real_name - 列表的确切åå­—; 通常列表åå­—è¦å¤§å†™ã€‚ +
    • real_name - 列表的确切åå­—; 通常列表åå­—è¦å¤§å†™ã€‚
    • list_name - 在URL中用æ¥ç¡®è®¤åˆ—表的å字,大å°å†™ç›¸å…³ã€‚
    • host_name - 列表æœåŠ¡å™¨çš„å…¨é™å®šåŸŸå。 @@ -13,4 +75,4 @@
    • description - 邮件列表的简è¦è¯´æ˜Žã€‚
    • info - 邮件列表的详细说明。
    • cgiext - The extension added to CGI scripts. -
    + diff --git a/templates/zh_CN/listinfo.html b/templates/zh_CN/listinfo.html index 7ddb2f68..a51572b2 100644 --- a/templates/zh_CN/listinfo.html +++ b/templates/zh_CN/listinfo.html @@ -1,140 +1,200 @@ - - - <MM-List-Name> ä¿¡æ¯é¡µ - - - -

    -

    - Hủy đăng ký ra hộp thư chung - Các sự đăng ký của bạn -
    + + + + - + +
    +

    +Hủy đăng ký ra hộp thư chung +Các sự đăng ký của bạn +
    Hãy bật há»™p chá»n xác nhận và bấm cái nút này để há»§y đăng ký ra há»™p thư chung này. Cảnh báo : Hành động này có tác động ngay!

    -

    +

    Bạn có thể xem danh sách các hộp thư chung tại trong đó bạn là thành viên. Hãy sư dụng khả năng này nếu bạn muốn làm cùng thay đổi trong các sự đăng ký khác này.

    -

    -
    - - - - - +
    - Mật khẩu của bạn -
    - -
    -

    Bạn đã quên mật khẩu mình không?

    -
    + + + - -
    +Mật khẩu của bạn +
    + +
    +

    Bạn đã quên mật khẩu mình không?

    +
    Hãy nhắp vào cái nút này để nhận thư chứa mật khẩu mình tại địa chỉ thành viên. -

    -

    - -
    -
    - -
    -

    Thay đổi mật khẩu

    - - - - - - - - -
    Mật khẩu mới :
    Nhập lại để xác nhận:
    - - -

    Äổi toàn cục -
    -
    - +

    +

    + +
    +

    + +
    +

    Thay đổi mật khẩu

    + + + + + + + + +
    Mật khẩu mới :
    Nhập lại để xác nhận:
    + +

    Äổi toàn cục +
    +

    - - +
    - Tùy chá»n đăng ký cá»§a bạn -
    +
    +Tùy chá»n đăng ký cá»§a bạn +
    -

    Giá trị hiện thá»i hoạt động có dấu chá»n ở cạnh. -

    Ghi chú rằng má»™t số tùy chá»n có khả năng Äặt toàn cục. Việc bật khả năng này sẽ áp dụng thay đổi đó vào má»i há»™p thư chung trong đó bạn là thành viên trên máy . Hãy nhắp vào Liệt kê các sá»± đăng ký mình khác bên trên để xem các há»™p thư khác vá»›i đó bạn đã đăng ký.

    - -
    - - Phát thư

    + + - +

    - - - + +

    - - - - - - + + - - + - - - - + + - - + - - + - - - +

    +
    + +Phát thư

    Äặt tùy chá»n này là Bật để nhận các thư được gởi cho há»™p thư chung này. Còn đặt nó là Tắt nếu bạn muốn tiếp tục đăng ký, nhưng tạm thá»i không muốn nhận thư (v.d. khi nghỉ). Nếu bạn có phải tắt phát thư, đừng quên bật lại khả năng đó khi bạn trở vá» : nó sẽ không được bật lại tá»± động. -

    - Bật
    - Tắt

    - Äặt toàn cục -

    +Bật
    +Tắt

    +Äặt toàn cục +

    - Äặt chế độ nhận bó thư

    +

    +Äặt chế độ nhận bó thư

    Nếu bạn bật chế độ nhận bó thư, bạn sẽ nhận nhiá»u bài thư trong má»™t lá thư (thưá»ng nhận má»™t thư trên má»—i ngày, nhưng có thể nhận thư nhiá»u hÆ¡n từ há»™p thư chung rất bận), thay vào nhận má»—i thư riêng má»™t khi nó được gởi. Nếu bạn tắt chế độ nhận bó thư, có lẽ bạn sẽ nhận má»™t bó thư cuối cùng trước khi bắt đầu nhận lại bài thư riêng. -

    - Tắt
    - Bật -
    - Nhận bó thư MIME hay thô?

    +

    +Tắt
    +Bật +
    +Nhận bó thư MIME hay thô?

    Trình thư cá»§a bạn có lẽ không há»— trợ bó thư MIME. Bó thư MIME thưá»ng dụng, nhưng nếu bạn gặp khó khăn Ä‘á»c chúng, hãy chá»n văn bản thô. -

    - MIME
    - Thô

    - Äặt toàn cục -

    +MIME
    +Thô

    +Äặt toàn cục +

    - Nhận bài thư mình đã gởi cho hộp thư không?

    +

    +Nhận bài thư mình đã gởi cho hộp thư không?

    Thưá»ng, bạn sẽ nhận má»—i thư bạn đã gởi cho há»™p thư. Nếu bạn không muốn nhận thư này, đặt tùy chá»n này là Không. -

    - Không
    - Có -
    - Nhận thư thừa nhận khi bạn gởi thư cho hộp thư không?

    -

    - Không
    - Có -
    - Nhận thư nhắc nhở mật khẩu cho hộp thư này không?

    +

    +Không
    +Có +
    +Nhận thư thừa nhận khi bạn gởi thư cho hộp thư không?

    +

    +Không
    +Có +
    +Nhận thư nhắc nhở mật khẩu cho hộp thư này không?

    Hàng tháng bạn sẽ nhận má»™t lá thư chứa lá»i nhắc nhở mật khẩu cho má»—i há»™p thư trên máy này, vá»›i đó bạn đã đăng ký. Bạn có thể tắt khả năng này cho từng há»™p thư bằng cách chá»n Không cho tùy chá»n này. Nếu bạn tắt lá»i nhắc nhở cho tất cả các há»™p thư vá»›i đó bạn đã đăng ký, bạn sẽ không nhận thư nhắc nhở nào. -

    - Không
    - Có

    - Äặt toàn cục -

    - Ẩn mặt trong danh sách thành viên không?

    +

    +Không
    +Có

    +Äặt toàn cục +

    +Ẩn mặt trong danh sách thành viên không?

    Khi ngưá»i nào xem danh sách các thành viên cùa há»™p thư này, địa chỉ thư cá»§a bạn thưá»ng được hiển thị (cách mỠđể tránh chương trình bắt địa chỉ để gởi thư rác). Nếu bạn không muốn địa chỉ thư cá»§a mình hiển thị trong bản liệt kê thành viên này bằng cách nào cả, hãy chá»n Có cho tùy chá»n này. -

    - Không
    - Có -
    - Bạn có muốn sử dụng ngôn ngữ nào?

    -

    - -
    - Bạn có muốn đăng ký với những phân loại chủ đỠnào?

    +

    +Không
    +Có +
    +Bạn có muốn sử dụng ngôn ngữ nào?

    +

    + +
    +Bạn có muốn đăng ký với những phân loại chủ đỠnào?

    Bằng cách chá»n má»™t hay nhiá»u chá»§ Ä‘á», bạn có thể lá»c viá»…n thông trong há»™p thư chung này, để nhận chỉ má»™t tập hợp con cá»§a các thư đã gởi cho nó. Thư nào khá»›p vá»›i má»™t cá»§a những chá»§ đỠbị bạn chá»n, sẽ được gởi cho bạn; thư không khá»›p sẽ không.

    Cách xá»­ lý thư nào không khá»›p vá»›i chá»§ đỠnào phụ thuá»™c vào giá trị cá»§a tùy chá»n bên dưới đây. Nếu bạn không chá»n chá»§ đỠriêng, bạn sẽ nhận má»i thư được gởi cho há»™p thư chung này. -

    - -
    - Bạn có muốn nhận thư nào không khá»›p vá»›i bá»™ lá»c chá»§ đỠnào không?

    +

    + +
    +Bạn có muốn nhận thư nào không khá»›p vá»›i bá»™ lá»c chá»§ đỠnào không?

    Tùy chá»n này chỉ có tác động nếu bạn đã đăng ký vá»›i ít nhất má»™t chá»§ đỠbên trên. Nó đặt quy tắc phát thư mặc định cho thư nào không khá»›p vá»›i bá»™ lá»c chá»§ đỠnào. Việc chá»n Không không phát cho bạn thư nào không khá»›p bá»™ lá»c chá»§ đỠnào, còn việc chá»n Có có phải.

    Nếu bạn không chá»n chá»§ đỠriêng bên trên, bạn sẽ nhận má»i thư được gởi cho há»™p thư chung này. -

    - Không
    - Có -
    - Tránh nhận bản sao của thư không?

    +

    +Không
    +Có +
    +Tránh nhận bản sao của thư không?

    Khi bạn được ghi dứt khoát trong dòng đầu To: (Cho) hay Cc: (Chép cho) cá»§a thư từ há»™p thư chung, bạn có thể chá»n không nhận bản sao thêm từ há»™p đó. Hãy chá»n Có để tránh nhận bản sao từ há»™p thư, còn chá»n Không để nhận.

    Nếu há»™p thư chung này đã bật khả năng tạo thư cá nhân, và bạn chá»n nhận bản sao, má»—i bản sao sẽ có dòng đầu X-Mailman-Copy: yes (sao chép? Có) được thêm vào nó. -

    - Không
    - Có

    - Äặt toàn cục -

    -
    -
    +Không
    +Có

    +Äặt toàn cục +

    +
    +
    -

    - - - - + + +

    diff --git a/templates/vi/private.html b/templates/vi/private.html index a6998e04..be6c8c34 100755 --- a/templates/vi/private.html +++ b/templates/vi/private.html @@ -1,51 +1,111 @@ - Xác thực kho riêng của %(realname)s - - - -
    +Xác thực kho riêng của %(realname)s + + + + %(message)s - - - - - - - - - - - - - - - -
    - Xác thực kho riêng của %(realname)s -
    Äịa chỉ thư :
    Mật khẩu :
    -
    -

    Quan trá»ng: để tiếp tục được, bạn phải đã bật tập tin nhận diện cookie trong trình duyệt, nếu không thì bạn sẽ phải xác thá»±c lại cho má»—i thao tác. + + + + + + + + + + + + + + + +
    +Xác thực kho riêng của %(realname)s +
    Äịa chỉ thư :
    Mật khẩu :
    +
    +

    Quan trá»ng: để tiếp tục được, bạn phải đã bật tập tin nhận diện cookie trong trình duyệt, nếu không thì bạn sẽ phải xác thá»±c lại cho má»—i thao tác.

    Cookie phiên chạy được dùng trong giao diện kho riêng cá»§a Mailman để tránh trưá»ng hợp bạn phải xác thá»±c lại vào má»—i thao tác. Cookie này sẽ hết hạn dùng tá»± động khi bạn thoát khá»i trình duyệt, hoặc bạn có thể làm cho nó hết hạn dùng bằng cách thăm trang tùy chá»n thành viên cá»§a mình và nhắp vào cái nút Äăng xuất.

    - - - - - - + + + +
    - Password Reminder -
    If you don't remember your password, enter your email address + + + + + + - - - - -
    +Password Reminder +
    If you don't remember your password, enter your email address above and click the Remind button and your password will be emailed to you.
    - +
    +

    diff --git a/templates/vi/roster.html b/templates/vi/roster.html index f7cb6600..f417d309 100644 --- a/templates/vi/roster.html +++ b/templates/vi/roster.html @@ -1,50 +1,108 @@ - - - <MM-List-Name> Ngưá»i đăng ký - - - - -

    - - - - - - - - - - - - - - - -
    - Ngưá»i đã đăng ký vá»›i - -
    - -

    -

    - -

    Hãy nhắp vào địa chỉ mình để thăm trang các tùy chá»n đăng ký riêng cá»§a mình.
    (Mục nhập nào nằm trong dấu ngoặc không hiện thá»i nhận thư từ há»™p thư.)

    -
    -
    - - Thành viên không nhận bó thư của : -
    -
    -
    - Thành viên nhận bó thư của : -
    -
    -

    -

    -

    -

    - - - + + +<mm-list-name> Ngưá»i đăng ký</mm-list-name> + + +

    + + + + + + + + + + + + + + + +
    +Ngưá»i đã đăng ký vá»›i + +
    +

    +

    +

    Hãy nhắp vào địa chỉ mình để thăm trang các tùy chá»n đăng ký riêng cá»§a mình.
    (Mục nhập nào nằm trong dấu ngoặc không hiện thá»i nhận thư từ há»™p thư.)

    +
    +
    + + Thành viên không nhận bó thư của : +
    +
    +
    + Thành viên nhận bó thư của : +
    +
    +

    +

    +

    +

    + +

    + diff --git a/templates/vi/subscribe.html b/templates/vi/subscribe.html index cb50c27c..61937bc8 100644 --- a/templates/vi/subscribe.html +++ b/templates/vi/subscribe.html @@ -1,9 +1,71 @@ -<MM-List-Name>Kết quả đăng ký +<mm-list-name>Kết quả đăng ký</mm-list-name> -

    Kết quả đăng ký

    - - - +

    Kết quả đăng ký

    + + + diff --git a/templates/zh_CN/admindbdetails.html b/templates/zh_CN/admindbdetails.html index db35451b..45955fea 100644 --- a/templates/zh_CN/admindbdetails.html +++ b/templates/zh_CN/admindbdetails.html @@ -1,4 +1,66 @@ -管ç†ä»»åŠ¡æœ‰æ‘˜è¦é¡µé¢å’Œè¯¦ç»†èµ„料页 +管ç†ä»»åŠ¡æœ‰æ‘˜è¦é¡µé¢å’Œè¯¦ç»†èµ„料页 é¢ä¸¤ç§æ˜¾ç¤ºæ–¹å¼ã€‚摘è¦é¡µé¢åŒ…括了按å‘é€äººé‚®ä»¶åœ°å€åˆ†ç»„的待决的订阅和退订 请求,以åŠè¢«æš‚存以待您审核的信件。详细资料页é¢åˆ™åŒ…æ‹¬æ¯æ¡æ¶ˆæ¯çš„æ¶ˆæ¯å¤´ ä¿¡æ¯å’Œæ¶ˆæ¯ä½“摘è¦åœ¨å†…的更详细的内容。 @@ -17,8 +79,7 @@
  • Discard -- 丢弃原始信æ¯ï¼Œä¸å‘逿‹’ç»é€šçŸ¥ã€‚对于æˆå‘˜èµ„格申请, ä¸é€šçŸ¥ç”³è¯·äººï¼Œå¹¶ä¸¢å¼ƒè¯¥è¯·æ±‚。该动作往往是针对已知的垃圾邮件。 - - +
  • 对于暂存的信æ¯ï¼Œå¦‚果你希望给系统管ç†å‘˜ä¸€ä»½æ‹·è´ï¼Œéœ€æ‰“å¼€Preserve 选项。 对于您想丢弃但是需è¦ä¿å­˜è®°å½•ä»¥åˆ©äºŽæ—¥åŽæ£€æŸ¥çš„辱骂类信æ¯è¿™ç§æ–¹æ³• 是有效的, @@ -44,3 +105,4 @@ 该按钮å¯ä»¥æäº¤æ‚¨å¯¹æ‰€æœ‰ç®¡ç†è¯·æ±‚所作出的修改。

    返回摘è¦é¡µã€‚ +

    \ No newline at end of file diff --git a/templates/zh_CN/admindbpreamble.html b/templates/zh_CN/admindbpreamble.html index e2c0f0ba..1ad24d1c 100644 --- a/templates/zh_CN/admindbpreamble.html +++ b/templates/zh_CN/admindbpreamble.html @@ -1,7 +1,70 @@ -该页é¢åŒ…括部分 %(listname)s 邮件列表中等待你审查的消æ¯ã€‚ç›® +该页é¢åŒ…括部分 %(listname)s 邮件列表中等待你审查的消æ¯ã€‚ç›® 剿˜¾ç¤º %(description)s

    对于æ¯ä¸ªç®¡ç†è¯·æ±‚,请选择相应的æ“作,处ç†ç»“æŸåŽç‚¹å‡»æäº¤æ‰€æœ‰æ•°æ®ã€‚ 更详细的说明å¯ä»¥åœ¨æ­¤å¤„获得。

    åŒæ—¶ï¼Œä½ ä¹Ÿå¯ä»¥æµè§ˆæ‰€æœ‰å¾…审查的请求的摘è¦ä¿¡æ¯ +

    \ No newline at end of file diff --git a/templates/zh_CN/admindbsummary.html b/templates/zh_CN/admindbsummary.html index 12631d33..8c7df239 100644 --- a/templates/zh_CN/admindbsummary.html +++ b/templates/zh_CN/admindbsummary.html @@ -1,4 +1,66 @@ -此页é¢åŒ…å«%(listname)s>邮件列表 +此页é¢åŒ…å«%(listname)s>邮件列表 中待你审批管ç†è¯·æ±‚的摘è¦ã€‚é¦–å…ˆï¼Œæ‚¨éœ€è¦æ‰¾å‡ºæ‰€æœ‰å¾…批准的订阅与退订申请 ,接ç€å¤„ç†è¢«æš‚存以待审核的消æ¯ã€‚ @@ -6,3 +68,4 @@ 按钮。更详细的说明å¯ä»¥åœ¨æ­¤å¤„获得。

    åŒæ—¶ï¼Œæ‚¨ä¹Ÿå¯ä»¥æµè§ˆæ‰€æœ‰è¢«æš‚存消æ¯çš„详细内容。 +

    \ No newline at end of file diff --git a/templates/zh_CN/admlogin.html b/templates/zh_CN/admlogin.html index 7b6dd4be..d78523a0 100755 --- a/templates/zh_CN/admlogin.html +++ b/templates/zh_CN/admlogin.html @@ -1,36 +1,96 @@ - %(listname)s %(who)s Authentication +%(listname)s %(who)s Authentication - + -
    + %(message)s - - - - - - - - - - - -
    - %(listname)s %(who)s - è®¤è¯ -
    显示 %(who)s密ç :
    -
    -

    é‡è¦: 从此时起,您的æµè§ˆå™¨å¿…é¡»å¼€å¯ + + + + + + + + + + + +
    +%(listname)s %(who)s + è®¤è¯ +
    显示 %(who)s密ç :
    +
    +

    é‡è¦: 从此时起,您的æµè§ˆå™¨å¿…é¡»å¼€å¯ cookie,å¦åˆ™æ‚¨æ‰€åšçš„æ‰€æœ‰ä¿®æ”¹å‡ä¸ä¼šç”Ÿæ•ˆã€‚

    在Mainman的管ç†ç•Œé¢ä¸­ä½¿ç”¨äº†Session cookie, è¿™æ ·æ‚¨å°±æ— éœ€å¯¹æ¯ ä¸€ä¸ªç®¡ç†æ“作å‡è¿›è¡Œé‡æ–°è®¤è¯ã€‚您关闭æµè§ˆå™¨æ—¶ï¼Œcookie自动失效;您 也å¯ä»¥é€šè¿‡ç‚¹å‡»å…¶ä»–ç®¡ç†æ´»åŠ¨ä¸‹ï¼ˆåœ¨æ‚¨æˆåŠŸç™»å½•åŽå³å¯çœ‹åˆ°è¯¥ 项)的登出链接æ¥ä½¿cookie失效。 -

    +

    diff --git a/templates/zh_CN/archidxentry.html b/templates/zh_CN/archidxentry.html index f9bb57aa..2efd9486 100644 --- a/templates/zh_CN/archidxentry.html +++ b/templates/zh_CN/archidxentry.html @@ -1,4 +1,67 @@ -
  • %(subject)s -  -%(author)s - +
  • %(subject)s +  +%(author)s + +
  • \ No newline at end of file diff --git a/templates/zh_CN/archidxfoot.html b/templates/zh_CN/archidxfoot.html index a5f51edf..da512091 100644 --- a/templates/zh_CN/archidxfoot.html +++ b/templates/zh_CN/archidxfoot.html @@ -1,20 +1,83 @@ - -

    - 上一æ¡ä¿¡æ¯çš„æ—¥æœŸ: - %(lastdate)s
    - 归档日期: %(archivedate)s -

    -

      -
    • ä¿¡æ¯æŽ’åºæ–¹å¼: + +

      +上一æ¡ä¿¡æ¯çš„æ—¥æœŸ: +%(lastdate)s
      +归档日期: %(archivedate)s +

      +

      -

      -


      - 此归档由 Pipermail %(version)s 生æˆã€‚ - - +
    +

    +


    +此归档由 Pipermail %(version)s 生æˆã€‚ + + +

    \ No newline at end of file diff --git a/templates/zh_CN/archidxhead.html b/templates/zh_CN/archidxhead.html index fe0fa493..72e51fe8 100644 --- a/templates/zh_CN/archidxhead.html +++ b/templates/zh_CN/archidxhead.html @@ -1,15 +1,78 @@ - - - %(listname)s %(archive)s 按 %(archtype)s 归档 - + + + +%(listname)s %(archive)s 按 %(archtype)s 归档 + %(encoding)s - - - -

    %(archive)s 按 %(archtype)s 归档

    -
      -
    • ä¿¡æ¯æŒ‰ä¸‹åˆ—æ–¹å¼æŽ’åº: + + + +

      %(archive)s 按 %(archtype)s 归档

      + -

      开始: %(firstdate)s
      - 结æŸ: %(lastdate)s
      - ä¿¡æ¯: %(size)s

      -

        +
      +

      开始: %(firstdate)s
      +结æŸ: %(lastdate)s
      +ä¿¡æ¯: %(size)s

      +

        +

      \ No newline at end of file diff --git a/templates/zh_CN/archlistend.html b/templates/zh_CN/archlistend.html index 9bc052dd..492cf01a 100644 --- a/templates/zh_CN/archlistend.html +++ b/templates/zh_CN/archlistend.html @@ -1 +1,63 @@ -
    + diff --git a/templates/zh_CN/archliststart.html b/templates/zh_CN/archliststart.html index e93c4e4e..8fd9fbda 100644 --- a/templates/zh_CN/archliststart.html +++ b/templates/zh_CN/archliststart.html @@ -1,4 +1,67 @@ - - - - +
    存档查看方å¼å¯ä¾›ä¸‹è½½çš„版本
    + + + +
    存档查看方å¼å¯ä¾›ä¸‹è½½çš„版本
    \ No newline at end of file diff --git a/templates/zh_CN/archtoc.html b/templates/zh_CN/archtoc.html index 77e8e90c..73d0d44e 100644 --- a/templates/zh_CN/archtoc.html +++ b/templates/zh_CN/archtoc.html @@ -1,13 +1,76 @@ - - - %(listname)s 邮件归档 - + + + + %(listname)s 邮件归档 + %(meta)s - - -

    %(listname)s 邮件归档

    -

    + + +

    %(listname)s 邮件归档

    +

    ä½ å¯ä»¥èŽ·å¾—å…³äºŽè¿™ä¸ªåˆ—è¡¨çš„æ›´å¤šä¿¡æ¯ æˆ–è€…ä¸‹è½½å®Œæ•´çš„åŽŸå§‹å½’æ¡£æ–‡ä»¶ (%(size)s). @@ -16,5 +79,5 @@

    %(listname)s 邮件归档

    %(archive_listing_start)s %(archive_listing)s %(archive_listing_end)s - - + + diff --git a/templates/zh_CN/archtocentry.html b/templates/zh_CN/archtocentry.html index 4c482b22..c5956a40 100644 --- a/templates/zh_CN/archtocentry.html +++ b/templates/zh_CN/archtocentry.html @@ -1,12 +1,73 @@ - -
    %(archivelabel)s: - [ 线索 ] - [ 主题 ] - [ 作者 ] - [ 日期 ] -
    %(archivelabel)s: +[ 线索 ] +[ 主题 ] +[ 作者 ] +[ 日期 ] +
    - - - - - - - - - - - - - - - - - - + + + + + +

    - -- - -
    -

      -

    - 关于 邮件列表 - - - -
    -

    -

    è¦æŸ¥çœ‹æœ¬åˆ—表收è—的旧信, - 请访问 - 的邮件归档. - -

    -
    - 如何使用 邮件列表 -
    + + +<mm-list-name> ä¿¡æ¯é¡µ</mm-list-name> + + +

    + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + +
    + -- + +
    +

      +

    +关于 邮件列表 + + + +
    +

    +

    è¦æŸ¥çœ‹æœ¬åˆ—表收è—的旧信, + 请访问 + 的邮件归档. + +

    +
    +如何使用 邮件列表 +
    è¦åŒæ—¶ç»™æ‰€æœ‰çš„æˆå‘˜å‘ä¿¡,å¯ä»¥ç›´æŽ¥å‘信到 - . + .

    在下é¢çš„å°èЂ䏭,您å¯ä»¥è®¢é˜…本列表的信件,æˆ–è€…æ”¹å˜æ‚¨çš„已有订阅。 -

    - 如何订阅 邮件列表 -
    -

    - è¦è®¢é˜… 邮件列表,请填写如下资料: - -

      - - - - - - - - - - - - + + + + + + - - - - - -
      您的email地å€ï¼š -  
      您的姓å(å¯é€‰): 
      请在下é¢è¾“入您的å£ä»¤ã€‚ +

      +如何订阅 邮件列表 +
      +

      + è¦è®¢é˜… 邮件列表,请填写如下资料: + +

        + + + + + + + + + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - - - -
        您的email地å€ï¼š + 
        您的姓å(å¯é€‰): 
        请在下é¢è¾“入您的å£ä»¤ã€‚ 这仅æä¾›é€‚åº¦çš„å®‰å…¨ï¼Œä½†è¶³ä»¥ä¿æŠ¤æ‚¨ä¸å—ä»–äººçš„å¹²æ‰°ã€‚ä½†è¯·æ‚¨ä¸ è¦ä½¿ç”¨é‡è¦çš„å£ä»¤ã€‚因为我们å¯èƒ½å°†æ­¤å£ä»¤ä»¥æ˜Žæ–‡å½¢å¼å‘给您。 -

        如果您ä¸å¡«å†™å¯†ç ,我们将会自动给您生æˆä¸€ä¸ªã€‚当您确认订阅åŽï¼Œ +

        如果您ä¸å¡«å†™å¯†ç ,我们将会自动给您生æˆä¸€ä¸ªã€‚当您确认订阅åŽï¼Œ 会将它å‘é€åˆ°æ‚¨çš„邮箱。当您编辑个人选项时,总是å¯ä»¥éšæ—¶è¦æ±‚å°† 您的å£ä»¤å‘é€åˆ°æ‚¨çš„邮箱。 - -
        -
        输入密ç : 
        å†è¾“入一次: 
        您希望以什么语言显示您的消æ¯?  
        您希望æ¯å¤©æ”¶åˆ°ä¿¡ä»¶æ‘˜è¦å—?? + + +
        输入密ç : 
        å†è¾“入一次: 
        您希望以什么语言显示您的消�  
        您希望æ¯å¤©æ”¶åˆ°ä¿¡ä»¶æ‘˜è¦å—?? å¦ - 是 -
        -
        -
        - -
      -
      - - 订阅者 -
      - - - -

      - - - -

      - - - +
    å¦ + 是 +
    +
    +
    + + +

    + + 订阅者 +
    + + + +

    + + + +

    + +

    + diff --git a/templates/zh_CN/options.html b/templates/zh_CN/options.html index 2da94a5b..38bcccf8 100644 --- a/templates/zh_CN/options.html +++ b/templates/zh_CN/options.html @@ -1,42 +1,101 @@ - - <MM-List-Name> çš„ <MM-Presentable-User> æˆå‘˜é…ç½® - - - - - -
    - - çš„ - 邮件列表æˆå‘˜é…ç½® -
    + + <mm-list-name> çš„ <mm-presentable-user> æˆå‘˜é…ç½® + </mm-presentable-user></mm-list-name> + + + + +
    + +çš„ + 邮件列表æˆå‘˜é…ç½® +

    - - - - - +
    - 邮件列表中 - 的订阅状æ€ã€å£ä»¤å’Œé€‰é¡¹ -
    - - - - -

    -

    + + + +
    + 邮件列表中 + 的订阅状æ€ã€å£ä»¤å’Œé€‰é¡¹ +
    + + +

    +

    - - +

    - - - + +
    - - 修改您的 æˆå‘˜ä¿¡æ¯ -
    通过在下é¢è¾“入新的邮件地å€ï¼Œæ‚¨å¯ä»¥ä¿®æ”¹æ‚¨åœ¨æ­¤é‚®ä»¶åˆ— + + + - - - - -
    + +修改您的 æˆå‘˜ä¿¡æ¯ +
    通过在下é¢è¾“入新的邮件地å€ï¼Œæ‚¨å¯ä»¥ä¿®æ”¹æ‚¨åœ¨æ­¤é‚®ä»¶åˆ— 表的订阅地å€ã€‚请注æ„,一å°ç¡®è®¤å‡½å°†ä¼šå‘往您的新地å€ï¼Œè€Œä¸”ç³»ç»Ÿåªæœ‰åœ¨ 收到您的确认回å¤ä¹‹åŽæ‰ä¼šä¿®æ”¹å®žé™…é…置。 @@ -48,191 +107,170 @@

    如果您想对在上订阅的所有列表中的æˆå‘˜ä¿¡æ¯ä¿®æ”¹,选中 全局修改 å¤é€‰æ¡† -

    - - + -
    新地å€: +

    + + - - - - - -
    新地å€:
    确认地å€: -
    -
    - - + + + + +
    您的姓å: +
    确认地å€: +
    +
    + + - - -
    您的姓å: (å¯é€‰):
    -
    -

    全局修改

    - +
    + +

    +

    全局修改

    +

    - - - - - - ' elif os.path.exists(txtfile): file = txtfile url = arch + '.txt' - templ = '' + templ = '' else: # neither found? file = None @@ -901,7 +878,7 @@ def processListArch(self): #if the working file is still here, the archiver may have # crashed during archiving. Save it, log an error, and move on. try: - wf = open(wname) + wf = open(wname, 'r') syslog('error', 'Archive working file %s present. ' 'Check %s for possibly unarchived msgs', @@ -921,131 +898,437 @@ def processListArch(self): except IOError: pass os.rename(name,wname) - archfile = open(wname) + archfile = open(wname, 'r') self.processUnixMailbox(archfile) archfile.close() os.unlink(wname) self.DropArchLock() - def processUnixMailbox(self, archfile): - """Process a Unix mailbox file.""" - from email import message_from_file - from mailbox import mbox - - # If archfile is a file object, we need to read it directly - if hasattr(archfile, 'read'): - # Read the entire file content - content = archfile.read() - # Create a temporary file to store the content - import tempfile - with tempfile.NamedTemporaryFile(mode='w+', encoding='utf-8', delete=False) as tmp: - if isinstance(content, bytes): - content = content.decode('utf-8', errors='replace') - tmp.write(content) - tmp_path = tmp.name - - try: - # Process the temporary file - mbox = mbox(tmp_path) - for key in mbox.keys(): - msg = message_from_file(mbox.get_file(key)) - self.add_article(msg) - finally: - # Clean up the temporary file - os.unlink(tmp_path) - else: - # If it's a path, use it directly - mbox = mbox(archfile) - for key in mbox.keys(): - msg = message_from_file(mbox.get_file(key)) - self.add_article(msg) - - def format_article(self, article): - """Format an article for HTML display.""" - # Get the message body - body = article.get_body() - if body is None: - return article - - # Convert body to lines - if isinstance(body, str): - lines = body.splitlines() + def get_filename(self, article): + return '%06i.html' % (article.sequence,) + + def get_archives(self, article): + """Return a list of indexes where the article should be filed. + A string can be returned if the list only contains one entry, + and the empty list is legal.""" + res = self.dateToVolName(float(article.date)) + self.message(C_("figuring article archives\n")) + self.message(res + "\n") + return res + + def volNameToDesc(self, volname): + volname = volname.strip() + # Don't make these module global constants since we have to runtime + # translate them anyway. + monthdict = [ + '', + _('January'), _('February'), _('March'), _('April'), + _('May'), _('June'), _('July'), _('August'), + _('September'), _('October'), _('November'), _('December') + ] + for each in list(self._volre.keys()): + match = re.match(self._volre[each], volname) + # Let ValueErrors percolate up + if match: + year = int(match.group('year')) + if each == 'quarter': + d =["", _("First"), _("Second"), _("Third"), _("Fourth") ] + ord = d[int(match.group('quarter'))] + return _("%(ord)s quarter %(year)i") + elif each == 'month': + monthstr = match.group('month').lower() + for i in range(1, 13): + monthname = time.strftime("%B", (1999,i,1,0,0,0,0,1,0)) + if monthstr.lower() == monthname.lower(): + month = monthdict[i] + return _("%(month)s %(year)i") + raise ValueError("%s is not a month!" % monthstr) + elif each == 'week': + month = monthdict[int(match.group("month"))] + day = int(match.group("day")) + return _("The Week Of Monday %(day)i %(month)s %(year)i") + elif each == 'day': + month = monthdict[int(match.group("month"))] + day = int(match.group("day")) + return _("%(day)i %(month)s %(year)i") + else: + return match.group('year') + raise ValueError("%s is not a valid volname" % volname) + +# The following two methods should be inverses of each other. -ddm + + def dateToVolName(self,date): + datetuple=time.localtime(date) + if self.ARCHIVE_PERIOD=='year': + return time.strftime("%Y",datetuple) + elif self.ARCHIVE_PERIOD=='quarter': + if datetuple[1] in [1,2,3]: + return time.strftime("%Yq1",datetuple) + elif datetuple[1] in [4,5,6]: + return time.strftime("%Yq2",datetuple) + elif datetuple[1] in [7,8,9]: + return time.strftime("%Yq3",datetuple) + else: + return time.strftime("%Yq4",datetuple) + elif self.ARCHIVE_PERIOD == 'day': + return time.strftime("%Y%m%d", datetuple) + elif self.ARCHIVE_PERIOD == 'week': + # Reconstruct "seconds since epoch", and subtract weekday + # multiplied by the number of seconds in a day. + monday = time.mktime(datetuple) - datetuple[6] * 24 * 60 * 60 + # Build a new datetuple from this "seconds since epoch" value + datetuple = time.localtime(monday) + return time.strftime("Week-of-Mon-%Y%m%d", datetuple) + # month. -ddm else: - lines = [line.decode('utf-8', 'replace') for line in body.splitlines()] - - # Handle HTML content - if article.ctype == 'text/html': - article.html_body = lines + return time.strftime("%Y-%B",datetuple) + + + def volNameToDate(self, volname): + volname = volname.strip() + for each in list(self._volre.keys()): + match = re.match(self._volre[each],volname) + if match: + year = int(match.group('year')) + month = 1 + day = 1 + if each == 'quarter': + q = int(match.group('quarter')) + month = (q * 3) - 2 + elif each == 'month': + monthstr = match.group('month').lower() + m = [] + for i in range(1,13): + m.append( + time.strftime("%B",(1999,i,1,0,0,0,0,1,0)).lower()) + try: + month = m.index(monthstr) + 1 + except ValueError: + pass + elif each == 'week' or each == 'day': + month = int(match.group("month")) + day = int(match.group("day")) + try: + return time.mktime((year,month,1,0,0,0,0,1,-1)) + except OverflowError: + return 0.0 + return 0.0 + + def sortarchives(self): + def sf(a, b): + al = self.volNameToDate(a) + bl = self.volNameToDate(b) + if al > bl: + return 1 + elif al < bl: + return -1 + else: + return 0 + if self.ARCHIVE_PERIOD in ('month','year','quarter'): + self.archives.sort(key = cmp_to_key(sf)) else: - # Process plain text - processed_lines = [] - for line in lines: - # Handle quoted text - if self.IQUOTES and quotedpat.match(line): - line = '' + CGIescape(line, self.lang) + '' - else: - line = CGIescape(line, self.lang) - if self.SHOWBR: - line += '
    ' - processed_lines.append(line) - - # Add HTML structure - if not self.SHOWHTML: - processed_lines.insert(0, '
    ')
    -                processed_lines.append('
    ') - article.html_body = processed_lines + self.archives.sort() + self.archives.reverse() + + def message(self, msg): + if self.VERBOSE: + f = sys.stderr + f.write(msg) + if msg[-1:] != '\n': + f.write('\n') + f.flush() + + def open_new_archive(self, archive, archivedir): + index_html = os.path.join(archivedir, 'index.html') + try: + os.unlink(index_html) + except (OSError, IOError): + pass + os.symlink(self.DEFAULTINDEX+'.html',index_html) + + def write_index_header(self): + self.depth=0 + print(self.html_head()) + if not self.THREADLAZY and self.type=='Thread': + self.message(C_("Computing threaded index\n")) + self.updateThreadedIndex() + + def write_index_footer(self): + for i in range(self.depth): + print('') + print(self.html_foot()) + + def write_index_entry(self, article): + subject = self.get_header("subject", article) + author = self.get_header("author", article) + if mm_cfg.ARCHIVER_OBSCURES_EMAILADDRS: + try: + author = re.sub('@', _(' at '), author) + except UnicodeError: + # Non-ASCII author contains '@' ... no valid email anyway + pass + subject = CGIescape(subject, self.lang) + author = CGIescape(author, self.lang) + + d = { + 'filename': urllib.parse.quote(article.filename), + 'subject': subject, + 'sequence': article.sequence, + 'author': author + } + print(quick_maketext( + 'archidxentry.html', d, + mlist=self.maillist)) + + def get_header(self, field, article): + # if we have no decoded header, return the encoded one + result = article.decoded.get(field) + if result is None: + return getattr(article, field) + # otherwise, the decoded one will be Unicode + return result + + def write_threadindex_entry(self, article, depth): + if depth < 0: + self.message('depth<0') + depth = 0 + if depth > self.THREADLEVELS: + depth = self.THREADLEVELS + if depth < self.depth: + for i in range(self.depth-depth): + print('') + elif depth > self.depth: + for i in range(depth-self.depth): + print('
      ') + print('' % (depth, article.threadKey)) + self.depth = depth + self.write_index_entry(article) + + def write_TOC(self): + self.sortarchives() + omask = os.umask(0o002) + try: + toc = open(os.path.join(self.basedir, 'index.html'), 'w') + finally: + os.umask(omask) + toc.write(self.html_TOC()) + toc.close() - return article + def write_article(self, index, article, path): + # called by add_article + omask = os.umask(0o002) + try: + f = open(path, 'w') + finally: + os.umask(omask) + f.write(article.as_html()) + f.close() - def close(self): - "Close an archive, save its state, and update any changed archives." - self.update_dirty_archives() - self.update_TOC = 0 - self.write_TOC() - # Save the collective state - self.message(C_('Pickling archive state into ') - + os.path.join(self.basedir, 'pipermail.pck')) - self.database.close() - del self.database - - omask = os.umask(0o007) + # Write the text article to the text archive. + path = os.path.join(self.basedir, "%s.txt" % index) + omask = os.umask(0o002) try: - f = open(os.path.join(self.basedir, 'pipermail.pck'), 'wb') + f = open(path, 'a+') finally: os.umask(omask) - - # Only save safe attributes - safe_state = {} - safe_attrs = { - 'type', 'archive', 'firstdate', 'lastdate', 'archivedate', - 'size', 'version', 'subjectIndex', 'authorIndex', 'dateIndex', - 'articleIndex', 'threadIndex' - } - for key in safe_attrs: - if hasattr(self, key): - safe_state[key] = getattr(self, key) - - # Use protocol 4 for Python 2/3 compatibility - pickle.dump(safe_state, f, protocol=4, fix_imports=True) + f.write(article.as_text()) f.close() + def update_archive(self, archive): + self.__super_update_archive(archive) + # only do this if the gzip module was imported globally, and + # gzip'ing was enabled via mm_cfg.GZIP_ARCHIVE_TXT_FILES. See + # above. + if gzip: + archz = None + archt = None + txtfile = os.path.join(self.basedir, '%s.txt' % archive) + gzipfile = os.path.join(self.basedir, '%s.txt.gz' % archive) + oldgzip = os.path.join(self.basedir, '%s.old.txt.gz' % archive) + try: + # open the plain text file + archt = open(txtfile, 'r') + except IOError: + return + try: + os.rename(gzipfile, oldgzip) + archz = gzip.open(oldgzip) + except (IOError, RuntimeError, os.error): + pass + try: + ou = os.umask(0o002) + newz = gzip.open(gzipfile, 'w') + finally: + # XXX why is this a finally? + os.umask(ou) + if archz: + newz.write(archz.read()) + archz.close() + os.unlink(oldgzip) + # XXX do we really need all this in a try/except? + try: + newz.write(archt.read()) + newz.close() + archt.close() + except IOError: + pass + os.unlink(txtfile) + + _skip_attrs = ('maillist', '_lock_file', 'charset') + def getstate(self): - """Get the current state of the archive.""" + d={} + for each in list(self.__dict__.keys()): + if not (each in self._skip_attrs + or each.upper() == each): + d[each] = self.__dict__[each] + return d + + # Add tags around URLs and e-mail addresses. + + def __processbody_URLquote(self, lines): + # XXX a lot to do here: + # 1. use lines directly, rather than source and dest + # 2. make it clearer + # 3. make it faster + # TK: Prepare for unicode obscure. + atmark = _(' at ') + source = lines[:] + dest = lines + last_line_was_quoted = 0 + for i in range(0, len(source)): + Lorig = L = source[i] + prefix = suffix = "" + if L is None: + continue + # Italicise quoted text + if self.IQUOTES: + quoted = quotedpat.match(L) + if quoted is None: + last_line_was_quoted = 0 + else: + quoted = quoted.end(0) + prefix = CGIescape(L[:quoted], self.lang) + '' + suffix = '' + if self.SHOWHTML: + suffix += '
      ' + if not last_line_was_quoted: + prefix = '
      ' + prefix + L = L[quoted:] + last_line_was_quoted = 1 + # Check for an e-mail address + L2 = "" + jr = emailpat.search(L) + kr = urlpat.search(L) + while jr is not None or kr is not None: + if jr == None: + j = -1 + else: + j = jr.start(0) + if kr is None: + k = -1 + else: + k = kr.start(0) + if j != -1 and (j < k or k == -1): + text = jr.group(1) + length = len(text) + if mm_cfg.ARCHIVER_OBSCURES_EMAILADDRS: + text = re.sub('@', atmark, text) + URL = self.maillist.GetScriptURL( + 'listinfo', absolute=1) + else: + URL = 'mailto:' + text + pos = j + elif k != -1 and (j > k or j == -1): + text = URL = kr.group(1) + length = len(text) + pos = k + else: # j==k + raise ValueError("j==k: This can't happen!") + #length = len(text) + #self.message("URL: %s %s %s \n" + # % (CGIescape(L[:pos]), URL, CGIescape(text))) + L2 += '%s
      %s' % ( + CGIescape(L[:pos], self.lang), + html_quote(URL), CGIescape(text, self.lang)) + L = L[pos+length:] + jr = emailpat.search(L) + kr = urlpat.search(L) + if jr is None and kr is None: + L = CGIescape(L, self.lang) + if isinstance(L, bytes): + L = L.decode('utf-8') + L = prefix + L2 + L + suffix + source[i] = None + dest[i] = L + + # Perform Hypermail-style processing of directives + # in message bodies. Lines between and will be written + # out precisely as they are; other lines will be passed to func2 + # for further processing . + + def __processbody_HTML(self, lines): + # XXX need to make this method modify in place + source = lines[:] + dest = lines + l = len(source) + i = 0 + while i < l: + while i < l and htmlpat.match(source[i]) is None: + i = i + 1 + if i < l: + source[i] = None + i = i + 1 + while i < l and nohtmlpat.match(source[i]) is None: + dest[i], source[i] = source[i], None + i = i + 1 + if i < l: + source[i] = None + i = i + 1 + + def format_article(self, article): + # called from add_article + # TBD: Why do the HTML formatting here and keep it in the + # pipermail database? It makes more sense to do the html + # formatting as the article is being written as html and toss + # the data after it has been written to the archive file. + lines = [_f for _f in article.body if _f] + # Handle directives + if self.ALLOWHTML: + self.__processbody_HTML(lines) + self.__processbody_URLquote(lines) + if not self.SHOWHTML and lines: + lines.insert(0, '
      ')
      +            lines.append('
      ') + else: + # Do fancy formatting here + if self.SHOWBR: + lines = [x + "
      " for x in lines] + else: + for i in range(0, len(lines)): + s = lines[i] + if s[0:1] in ' \t\n': + lines[i] = '

      ' + s + article.html_body = lines + return article + + def update_article(self, arcdir, article, prev, next): + seq = article.sequence + filename = os.path.join(arcdir, article.filename) + self.message(C_('Updating HTML for article %(seq)s')) try: - # Use protocol 4 for Python 2/3 compatibility - protocol = 4 - return pickle.dumps(self.__dict__, protocol, fix_imports=True) - except Exception as e: - syslog('error', 'Error getting archive state: %s', e) - return None - - def setstate(self, state): - """Set the state of the archive.""" + f = open(filename) + article.loadbody_fromHTML(f) + f.close() + except IOError as e: + if e.errno != errno.ENOENT: raise + self.message(C_('article file %(filename)s is missing!')) + article.prev = prev + article.next = next + omask = os.umask(0o002) try: - # Use protocol 4 for Python 2/3 compatibility - protocol = 4 - self.__dict__ = pickle.loads(state, fix_imports=True, encoding='latin1') - except Exception as e: - syslog('error', 'Error setting archive state: %s', e) - return False - return True + f = open(filename, 'w') + finally: + os.umask(omask) + f.write(article.as_html()) + f.close() diff --git a/Mailman/Archiver/HyperDatabase.py b/Mailman/Archiver/HyperDatabase.py index 588515e3..b46eb362 100644 --- a/Mailman/Archiver/HyperDatabase.py +++ b/Mailman/Archiver/HyperDatabase.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import # Copyright (C) 1998-2018 by the Free Software Foundation, Inc. # # This program is free software; you can redistribute it and/or @@ -29,6 +30,7 @@ # from . import pipermail from Mailman import LockFile +from Mailman import Utils CACHESIZE = pipermail.CACHESIZE @@ -67,10 +69,21 @@ def __repr__(self): def __sort(self, dirty=None): if self.__dirty == 1 or dirty: - self.sorted = list(self.dict.keys()) - self.sorted.sort() + self.sorted = self.__fix_for_sort(list(self.dict.keys())) + if hasattr(self.sorted, 'sort'): + self.sorted.sort() self.__dirty = 0 + def __fix_for_sort(self, items): + if isinstance(items, bytes): + return items.decode() + elif isinstance(items, list): + return [ self.__fix_for_sort(item) for item in items ] + elif isinstance(items, tuple): + return tuple( self.__fix_for_sort(item) for item in items ) + else: + return items + def lock(self): self.lockfile.lock() @@ -168,7 +181,7 @@ def __len__(self): def load(self): try: - fp = open(self.path) + fp = open(self.path, mode='rb') try: self.dict = marshal.load(fp) finally: @@ -184,13 +197,14 @@ def load(self): def close(self): omask = os.umask(0o007) try: - fp = open(self.path, 'w') + fp = open(self.path, 'wb') finally: os.umask(omask) fp.write(marshal.dumps(self.dict)) fp.close() self.unlock() + # this is lifted straight out of pipermail with # the bsddb.btree replaced with above class. # didn't use inheritance because of all the @@ -273,7 +287,7 @@ def close(self): def hasArticle(self, archive, msgid): self.__openIndices(archive) - return msgid in self.articleIndex + return self.articleIndex.has_key(msgid) def setThreadKey(self, archive, key, msgid): self.__openIndices(archive) @@ -284,7 +298,7 @@ def getArticle(self, archive, msgid): if msgid not in self.__cache: # get the pickled object out of the DumbBTree buf = self.articleIndex[msgid] - article = self.__cache[msgid] = pickle.loads(buf, fix_imports=True, encoding='latin1') + article = self.__cache[msgid] = Utils.load_pickle(buf) # For upgrading older archives article.setListIfUnset(self._mlist) else: diff --git a/Mailman/Archiver/__init__.py b/Mailman/Archiver/__init__.py index cb00641d..11e20583 100644 --- a/Mailman/Archiver/__init__.py +++ b/Mailman/Archiver/__init__.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import # Copyright (C) 1998-2018 by the Free Software Foundation, Inc. # # This program is free software; you can redistribute it and/or diff --git a/Mailman/Archiver/pipermail.py b/Mailman/Archiver/pipermail.py index 0373a260..bd1e8df9 100644 --- a/Mailman/Archiver/pipermail.py +++ b/Mailman/Archiver/pipermail.py @@ -1,17 +1,15 @@ -#! /usr/bin/env python +#! /usr/bin/python3 +import errno import mailbox import os import re import sys import time -import string from email.utils import parseaddr, parsedate_tz, mktime_tz, formatdate import pickle from io import StringIO - -# Use string.ascii_lowercase instead of the old lowercase variable -lowercase = string.ascii_lowercase +from string import ascii_lowercase as lowercase __version__ = '0.09 (Mailman edition)' VERSION = __version__ @@ -19,6 +17,7 @@ from Mailman import mm_cfg from Mailman import Errors +from Mailman import Utils from Mailman.Mailbox import ArchiverMailbox from Mailman.Logging.Syslog import syslog from Mailman.i18n import _, C_ @@ -26,6 +25,7 @@ SPACE = ' ' + msgid_pat = re.compile(r'(<.*>)') def strip_separators(s): "Remove quotes or parenthesization from a Message-ID string" @@ -131,8 +131,7 @@ def store_article(self, article): temp2 = article.html_body article.body = [] del article.html_body - # Use protocol 4 for Python 2/3 compatibility - self.articleIndex[article.msgid] = pickle.dumps(article, protocol=4, fix_imports=True) + self.articleIndex[article.msgid] = pickle.dumps(article) article.body = temp article.html_body = temp2 @@ -220,8 +219,9 @@ def __init__(self, message = None, sequence = 0, keepHeaders = []): self.headers[i] = message[i] # Read the message body - s = StringIO(message.get_payload(decode=True)\ - or message.as_string().split('\n\n',1)[1]) + msg = message.get_payload()\ + or message.as_string().split('\n\n',1)[1] + s = StringIO(msg) self.body = s.readlines() def _set_date(self, message): @@ -272,26 +272,37 @@ class T(object): def __init__(self, basedir = None, reload = 1, database = None): # If basedir isn't provided, assume the current directory if basedir is None: - basedir = os.getcwd() - self.basedir = basedir + self.basedir = os.getcwd() + else: + basedir = os.path.expanduser(basedir) + self.basedir = basedir + self.database = database + + # If the directory doesn't exist, create it. This code shouldn't get + # run anymore, we create the directory in Archiver.py. It should only + # get used by legacy lists created that are only receiving their first + # message in the HTML archive now -- Marc + try: + os.stat(self.basedir) + except OSError as e: + if e.errno != errno.ENOENT: + raise + else: + self.message(C_('Creating archive directory ') + self.basedir) + omask = os.umask(0) + try: + os.mkdir(self.basedir, self.DIRMODE) + finally: + os.umask(omask) # Try to load previously pickled state try: if not reload: raise IOError - f = open(os.path.join(self.basedir, 'pipermail.pck'), 'rb') + d = Utils.load_pickle(os.path.join(self.basedir, 'pipermail.pck')) + if not d: + raise IOError("Pickled data is empty or None") self.message(C_('Reloading pickled archive state')) - try: - # Try UTF-8 first for newer files - d = pickle.load(f, fix_imports=True, encoding='utf-8') - except (UnicodeDecodeError, pickle.UnpicklingError): - # Fall back to latin1 for older files - f.seek(0) - d = pickle.load(f, fix_imports=True, encoding='latin1') - f.close() - if isinstance(d, bytes): - # If we got bytes, try to unpickle it - d = pickle.loads(d, fix_imports=True, encoding='latin1') for key, value in list(d.items()): setattr(self, key, value) except (IOError, EOFError): @@ -326,30 +337,12 @@ def close(self): f = open(os.path.join(self.basedir, 'pipermail.pck'), 'wb') finally: os.umask(omask) - # Use protocol 4 for Python 2/3 compatibility - pickle.dump(self.getstate(), f, protocol=4, fix_imports=True) + pickle.dump(self.getstate(), f) f.close() def getstate(self): - """Get the current state of the archive.""" - try: - # Use protocol 4 for Python 2/3 compatibility - protocol = 4 - return pickle.dumps(self.__dict__, protocol, fix_imports=True) - except Exception as e: - mailman_log('error', 'Error getting archive state: %s', e) - return None - - def setstate(self, state): - """Set the state of the archive.""" - try: - # Use protocol 4 for Python 2/3 compatibility - protocol = 4 - self.__dict__ = pickle.loads(state, fix_imports=True, encoding='latin1') - except Exception as e: - mailman_log('error', 'Error setting archive state: %s', e) - return False - return True + # can override this in subclass + return self.__dict__ # # Private methods @@ -383,7 +376,7 @@ def __findParent(self, article, children = []): parentID = article.in_reply_to elif article.references: # Remove article IDs that aren't in the archive - refs = list(filter(self.articleIndex.has_key, article.references)) + refs = list(filter(lambda x: x in self.articleIndex, article.references)) if not refs: return None maxdate = self.database.getArticle(self.archive, @@ -532,7 +525,7 @@ def _open_index_file_as_stdout(self, arcdir, index_name): path = os.path.join(arcdir, index_name + self.INDEX_EXT) omask = os.umask(0o002) try: - self.__f = open(path, 'w') + self.__f = open(path, 'w', encoding='utf-8') finally: os.umask(omask) self.__stdout = sys.stdout @@ -558,7 +551,8 @@ def _makeArticle(self, msg, sequence): return Article(msg, sequence) def processUnixMailbox(self, input, start=None, end=None): - mbox = ArchiverMailbox(input, self.maillist) + mbox = ArchiverMailbox(input.name, self.maillist) + mbox_iterator = iter(mbox.values()) if start is None: start = 0 counter = 0 @@ -566,7 +560,7 @@ def processUnixMailbox(self, input, start=None, end=None): mbox.skipping(True) while counter < start: try: - m = next(mbox) + m = next(mbox_iterator, None) except Errors.DiscardMessage: continue if m is None: @@ -577,7 +571,7 @@ def processUnixMailbox(self, input, start=None, end=None): while 1: try: pos = input.tell() - m = next(mbox) + m = next(mbox_iterator, None) except Errors.DiscardMessage: continue except Exception: @@ -605,29 +599,61 @@ def new_archive(self, archive, archivedir): # If the archive directory doesn't exist, create it try: os.stat(archivedir) - except os.error as errdata: - errno, errmsg = errdata - if errno == 2: + except OSError as e: + if e.errno != errno.ENOENT: + raise + else: omask = os.umask(0) try: os.mkdir(archivedir, self.DIRMODE) finally: os.umask(omask) - else: - raise os.error(errdata) self.open_new_archive(archive, archivedir) def add_article(self, article): - """Add an article to the archive.""" - try: - # Use protocol 4 for Python 2/3 compatibility - protocol = 4 - self.articleIndex[article.msgid] = pickle.dumps(article, protocol=4, fix_imports=True) - self.articleIndex.sync() - except Exception as e: - mailman_log('error', 'Error adding article %s: %s', article.msgid, e) - return False - return True + archives = self.get_archives(article) + if not archives: + return + if type(archives) == type(''): + archives = [archives] + + article.filename = filename = self.get_filename(article) + temp = self.format_article(article) + for arch in archives: + self.archive = arch # why do this??? + archivedir = os.path.join(self.basedir, arch) + if arch not in self.archives: + self.new_archive(arch, archivedir) + + # Write the HTML-ized article + self.write_article(arch, temp, os.path.join(archivedir, + filename)) + + if 'author' in article.decoded: + author = fixAuthor(article.decoded['author']) + else: + author = fixAuthor(article.author) + if 'stripped' in article.decoded: + subject = article.decoded['stripped'].lower() + else: + subject = article.subject.lower() + + article.parentID = parentID = self.get_parent_info(arch, article) + if parentID: + parent = self.database.getArticle(arch, parentID) + article.threadKey = (parent.threadKey + article.date + '.' + + str(article.sequence) + '-') + else: + article.threadKey = (article.date + '.' + + str(article.sequence) + '-') + key = article.threadKey, article.msgid + + self.database.setThreadKey(arch, key, article.msgid) + self.database.addArticle(arch, temp, author=author, + subject=subject) + + if arch not in self._dirty_archives: + self._dirty_archives.append(arch) def get_parent_info(self, archive, article): parentID = None @@ -661,7 +687,7 @@ def get_parent_info(self, archive, article): def write_article(self, index, article, path): omask = os.umask(0o002) try: - f = open(path, 'w') + f = open(path, 'w', encoding='utf-8') finally: os.umask(omask) temp_stdout, sys.stdout = sys.stdout, f diff --git a/Mailman/Autoresponder.py b/Mailman/Autoresponder.py index 475e3170..1466af34 100644 --- a/Mailman/Autoresponder.py +++ b/Mailman/Autoresponder.py @@ -20,7 +20,6 @@ from builtins import object from Mailman import mm_cfg from Mailman.i18n import _ -import time @@ -43,37 +42,3 @@ def InitVars(self): self.admin_responses = {} self.request_responses = {} - def autorespondToSender(self, sender, lang): - """Check if we should autorespond to this sender. - - Args: - sender: The email address of the sender - lang: The language to use for the response - - Returns: - True if we should autorespond, False otherwise - """ - # Check if we're in the grace period - now = time.time() - graceperiod = self.autoresponse_graceperiod - if graceperiod > 0: - # Check the appropriate response dictionary based on the type of message - if self.autorespond_admin: - quiet_until = self.admin_responses.get(sender, 0) - elif self.autorespond_requests: - quiet_until = self.request_responses.get(sender, 0) - else: - quiet_until = self.postings_responses.get(sender, 0) - if quiet_until > now: - return False - - # Update the appropriate response dictionary - if self.autorespond_admin: - self.admin_responses[sender] = now + graceperiod - elif self.autorespond_requests: - self.request_responses[sender] = now + graceperiod - else: - self.postings_responses[sender] = now + graceperiod - - return True - diff --git a/Mailman/Bouncer.py b/Mailman/Bouncer.py index 619a3ee7..ee932ab4 100644 --- a/Mailman/Bouncer.py +++ b/Mailman/Bouncer.py @@ -20,21 +20,13 @@ from builtins import object import sys import time -import os -import email -import errno -import pickle -import email.message -from email.message import Message from email.mime.text import MIMEText from email.mime.message import MIMEMessage -import Mailman from Mailman import mm_cfg from Mailman import Utils -from Mailman import Errors -from Mailman.Message import Message +from Mailman import Message from Mailman import MemberAdaptor from Mailman import Pending from Mailman.Errors import MMUnknownListError @@ -254,7 +246,7 @@ def __sendAdminBounceNotice(self, member, msg, did=None): 'owneraddr': siteowner, }, mlist=self) subject = _('Bounce action notification') - umsg = Mailman.Message.UserNotification(self.GetOwnerEmail(), + umsg = Message.UserNotification(self.GetOwnerEmail(), siteowner, subject, lang=self.preferred_language) # BAW: Be sure you set the type before trying to attach, or you'll get @@ -323,11 +315,11 @@ def sendNextNotification(self, member): 'owneraddr' : self.GetOwnerEmail(), 'reason' : txtreason, }, lang=lang, mlist=self) - msg = Mailman.Message.UserNotification(member, reqaddr, text=text, lang=lang) + msg = Message.UserNotification(member, reqaddr, text=text, lang=lang) # BAW: See the comment in MailList.py ChangeMemberAddress() for why we # set the Subject this way. del msg['subject'] - msg['Subject'] = _('confirm %(cookie)s') % {'cookie': info.cookie} + msg['Subject'] = 'confirm ' + info.cookie # Send without Precedence: bulk. Bug #808821. msg.send(self, noprecedence=True) info.noticesleft -= 1 @@ -348,7 +340,7 @@ def BounceMessage(self, msg, msgdata, e=None): else: notice = _(e.notice()) # Currently we always craft bounces as MIME messages. - bmsg = Mailman.Message.UserNotification(msg.get_sender(), + bmsg = Message.UserNotification(msg.get_sender(), self.GetOwnerEmail(), subject, lang=self.preferred_language) diff --git a/Mailman/Bouncers/Caiwireless.py b/Mailman/Bouncers/Caiwireless.py index c99c0945..4eb55509 100644 --- a/Mailman/Bouncers/Caiwireless.py +++ b/Mailman/Bouncers/Caiwireless.py @@ -18,19 +18,15 @@ import re import email -from email.iterators import body_line_iterator -from email.header import decode_header - -from Mailman import mm_cfg -from Mailman import Utils -from Mailman.Logging.Syslog import syslog -from Mailman.Handlers.CookHeaders import change_header +import email.iterators +from io import StringIO tcre = re.compile(r'the following recipients did not receive this message:', re.IGNORECASE) acre = re.compile(r'<(?P[^>]*)>') + def process(msg): if msg.get_content_type() != 'multipart/mixed': return None @@ -39,7 +35,7 @@ def process(msg): # 1 == tag line seen state = 0 # This format thinks it's a MIME, but it really isn't - for line in body_line_iterator(msg): + for line in email.iterators.body_line_iterator(msg): line = line.strip() if state == 0 and tcre.match(line): state = 1 @@ -48,16 +44,3 @@ def process(msg): if not mo: return None return [mo.group('addr')] - - # Now that we have a Message object that meets our criteria, let's extract - # the first numlines of body text. - lines = [] - lineno = 0 - for line in body_line_iterator(msg): - # Blank lines don't count - if not line.strip(): - continue - lineno += 1 - lines.append(line) - if numlines is not None and lineno >= numlines: - break diff --git a/Mailman/Bouncers/Compuserve.py b/Mailman/Bouncers/Compuserve.py index 3591eff8..be83bddc 100644 --- a/Mailman/Bouncers/Compuserve.py +++ b/Mailman/Bouncers/Compuserve.py @@ -18,19 +18,20 @@ import re import email -from email.iterators import body_line_iterator +import email.iterators dcre = re.compile(r'your message could not be delivered', re.IGNORECASE) acre = re.compile(r'Invalid receiver address: (?P.*)') + def process(msg): # simple state machine # 0 = nothing seen yet # 1 = intro line seen state = 0 addrs = [] - for line in body_line_iterator(msg): + for line in email.iterators.body_line_iterator(msg): if state == 0: mo = dcre.search(line) if mo: diff --git a/Mailman/Bouncers/DSN.py b/Mailman/Bouncers/DSN.py index 071ab09b..32beaa89 100644 --- a/Mailman/Bouncers/DSN.py +++ b/Mailman/Bouncers/DSN.py @@ -24,11 +24,10 @@ from email.iterators import typed_subpart_iterator from email.utils import parseaddr from io import StringIO -import re -import ipaddress from Mailman.Bouncers.BouncerAPI import Stop + def process(msg): # Iterate over each message/delivery-status subpart addrs = [] @@ -73,48 +72,6 @@ def process(msg): for param in params: if param.startswith('<') and param.endswith('>'): addrs.append(param[1:-1]) - - # Extract IP address from Received headers - ip = None - for header in msg.get_all('Received', []): - if isinstance(header, bytes): - header = header.decode('us-ascii', errors='replace') - # Look for IP addresses in Received headers - # Support both IPv4 and IPv6 formats - ip_match = re.search(r'\[([0-9a-fA-F:.]+)\]', header, re.IGNORECASE) - if ip_match: - ip = ip_match.group(1) - break - - if ip: - try: - if have_ipaddress: - ip_obj = ipaddress.ip_address(ip) - if isinstance(ip_obj, ipaddress.IPv4Address): - # For IPv4, drop last octet - parts = str(ip_obj).split('.') - ip = '.'.join(parts[:-1]) - else: - # For IPv6, drop last 16 bits - expanded = ip_obj.exploded.replace(':', '') - ip = expanded[:-4] - else: - # Fallback for systems without ipaddress module - if ':' in ip: - # IPv6 address - parts = ip.split(':') - if len(parts) <= 8: - # Pad with zeros and drop last 16 bits - expanded = ''.join(part.zfill(4) for part in parts) - ip = expanded[:-4] - else: - # IPv4 address - parts = ip.split('.') - if len(parts) == 4: - ip = '.'.join(parts[:-1]) - except (ValueError, IndexError): - ip = None - # Uniquify rtnaddrs = {} for a in addrs: diff --git a/Mailman/Bouncers/Exchange.py b/Mailman/Bouncers/Exchange.py index 68bc6fa6..273c8947 100644 --- a/Mailman/Bouncers/Exchange.py +++ b/Mailman/Bouncers/Exchange.py @@ -17,27 +17,18 @@ """Recognizes (some) Microsoft Exchange formats.""" import re -import email -from email.iterators import body_line_iterator -from email.header import decode_header +import email.iterators -from Mailman import mm_cfg -from Mailman import Utils -from Mailman.Logging.Syslog import syslog -from Mailman.Handlers.CookHeaders import change_header - -# Patterns for different Exchange/Office 365 bounce formats -scre = re.compile('did not reach the following recipient|Your message to .* couldn\'t be delivered') -ecre = re.compile('MSEXCH:|Action Required') +scre = re.compile('did not reach the following recipient') +ecre = re.compile('MSEXCH:') a1cre = re.compile('SMTP=(?P[^;]+); on ') a2cre = re.compile('(?P[^ ]+) on ') -a3cre = re.compile('Your message to (?P[^ ]+) couldn\'t be delivered') -a4cre = re.compile('(?P[^ ]+) wasn\'t found at ') + def process(msg): addrs = {} - it = body_line_iterator(msg) + it = email.iterators.body_line_iterator(msg) # Find the start line for line in it: if scre.search(line): @@ -48,18 +39,9 @@ def process(msg): for line in it: if ecre.search(line): break - # Try all patterns - for pattern in [a1cre, a2cre, a3cre, a4cre]: - mo = pattern.search(line) - if mo: - addr = mo.group('addr') - # Clean up the address if needed - if '@' not in addr and 'at' in line: - # Handle cases where domain is on next line - next_line = next(it, '') - if 'at' in next_line: - domain = next_line.split('at')[-1].strip() - addr = f"{addr}@{domain}" - addrs[addr] = 1 - break + mo = a1cre.search(line) + if not mo: + mo = a2cre.search(line) + if mo: + addrs[mo.group('addr')] = 1 return list(addrs.keys()) diff --git a/Mailman/Bouncers/GroupWise.py b/Mailman/Bouncers/GroupWise.py index 721b7660..91521869 100644 --- a/Mailman/Bouncers/GroupWise.py +++ b/Mailman/Bouncers/GroupWise.py @@ -22,18 +22,19 @@ """ import re -import email.message +from email.message import Message from io import StringIO acre = re.compile(r'<(?P[^>]*)>') + def find_textplain(msg): if msg.get_content_type() == 'text/plain': return msg if msg.is_multipart: for part in msg.get_payload(): - if not isinstance(part, email.message.Message): + if not isinstance(part, Message): continue ret = find_textplain(part) if ret: @@ -41,6 +42,7 @@ def find_textplain(msg): return None + def process(msg): if msg.get_content_type() != 'multipart/mixed' or not msg['x-mailer']: return None diff --git a/Mailman/Bouncers/LLNL.py b/Mailman/Bouncers/LLNL.py index 1e2a9e6f..3da78159 100644 --- a/Mailman/Bouncers/LLNL.py +++ b/Mailman/Bouncers/LLNL.py @@ -18,13 +18,14 @@ import re import email -from email.iterators import body_line_iterator +import email.iterators acre = re.compile(r',\s*(?P\S+@[^,]+),', re.IGNORECASE) + def process(msg): - for line in body_line_iterator(msg): + for line in email.iterators.body_line_iterator(msg): mo = acre.search(line) if mo: return [mo.group('addr')] diff --git a/Mailman/Bouncers/Microsoft.py b/Mailman/Bouncers/Microsoft.py index 5f67cb3c..09ec9384 100644 --- a/Mailman/Bouncers/Microsoft.py +++ b/Mailman/Bouncers/Microsoft.py @@ -22,6 +22,7 @@ scre = re.compile(r'transcript of session follows', re.IGNORECASE) + def process(msg): if msg.get_content_type() != 'multipart/mixed': return None diff --git a/Mailman/Bouncers/Qmail.py b/Mailman/Bouncers/Qmail.py index b0c5215d..5d4f2157 100644 --- a/Mailman/Bouncers/Qmail.py +++ b/Mailman/Bouncers/Qmail.py @@ -27,18 +27,7 @@ """ import re -import sys -import email -from email.iterators import body_line_iterator -from email.mime.text import MIMEText -from email.mime.message import MIMEMessage - -from Mailman import mm_cfg -from Mailman import Utils -from Mailman.Message import Message -from Mailman import Errors -from Mailman import i18n -from Mailman.Logging.Syslog import syslog +import email.iterators # Other (non-standard?) intros have been observed in the wild. introtags = [ @@ -53,6 +42,7 @@ acre = re.compile(r'<(?P[^>]*)>:') + def process(msg): addrs = [] # simple state machine @@ -60,10 +50,7 @@ def process(msg): # 1 = intro paragraph seen # 2 = recip paragraphs seen state = 0 - for line in body_line_iterator(msg): - # Ensure line is a string - if isinstance(line, bytes): - line = line.decode('ascii', 'replace') + for line in email.iterators.body_line_iterator(msg): line = line.strip() if state == 0: for introtag in introtags: diff --git a/Mailman/Bouncers/SMTP32.py b/Mailman/Bouncers/SMTP32.py index 955bafd4..b21a90ee 100644 --- a/Mailman/Bouncers/SMTP32.py +++ b/Mailman/Bouncers/SMTP32.py @@ -30,7 +30,7 @@ import re import email -from email.iterators import body_line_iterator +import email.iterators ecre = re.compile('original message follows', re.IGNORECASE) acre = re.compile(r''' @@ -46,12 +46,13 @@ ''', re.IGNORECASE | re.VERBOSE) + def process(msg): mailer = msg.get('x-mailer', '') if not mailer.startswith('[^>]*)>')), # sz-sb.de, corridor.com, nfg.nl - (_c(r'the following addresses had'), - _c(r'transcript of session follows'), + (_c('the following addresses had'), + _c('transcript of session follows'), _c(r'^ *(\(expanded from: )?[^\s@]+@[^\s@>]+?)>?\)?\s*$')), # robanal.demon.co.uk - (_c(r'this message was created automatically by mail delivery software'), - _c(r'original message follows'), + (_c('this message was created automatically by mail delivery software'), + _c('original message follows'), _c(r'rcpt to:\s*<(?P[^>]*)>')), # s1.com (InterScan E-Mail VirusWall NT ???) - (_c(r'message from interscan e-mail viruswall nt'), - _c(r'end of message'), + (_c('message from interscan e-mail viruswall nt'), + _c('end of message'), _c(r'rcpt to:\s*<(?P[^>]*)>')), # Smail - (_c(r'failed addresses follow:'), - _c(r'message text follows:'), + (_c('failed addresses follow:'), + _c('message text follows:'), _c(r'\s*(?P\S+@\S+)')), # newmail.ru - (_c(r'This is the machine generated message from mail service.'), - _c(r'--- Below the next line is a copy of the message.'), - _c(r'<(?P[^>]*)>')), + (_c('This is the machine generated message from mail service.'), + _c('--- Below the next line is a copy of the message.'), + _c('<(?P[^>]*)>')), # turbosport.com runs something called `MDaemon 3.5.2' ??? - (_c(r'The following addresses did NOT receive a copy of your message:'), - _c(r'--- Session Transcript ---'), + (_c('The following addresses did NOT receive a copy of your message:'), + _c('--- Session Transcript ---'), _c(r'[>]\s*(?P.*)$')), # usa.net (_c(r'Intended recipient:\s*(?P.*)$'), - _c(r'--------RETURNED MAIL FOLLOWS--------'), + _c('--------RETURNED MAIL FOLLOWS--------'), _c(r'Intended recipient:\s*(?P.*)$')), # hotpop.com (_c(r'Undeliverable Address:\s*(?P.*)$'), - _c(r'Original message attached'), + _c('Original message attached'), _c(r'Undeliverable Address:\s*(?P.*)$')), # Another demon.co.uk format - (_c(r'This message was created automatically by mail delivery'), - _c(r'^---- START OF RETURNED MESSAGE ----'), - _c(r"addressed to '(?P[^']*)'")), + (_c('This message was created automatically by mail delivery'), + _c('^---- START OF RETURNED MESSAGE ----'), + _c("addressed to '(?P[^']*)'")), # Prodigy.net full mailbox - (_c(r"User's mailbox is full:"), - _c(r'Unable to deliver mail.'), + (_c("User's mailbox is full:"), + _c('Unable to deliver mail.'), _c(r"User's mailbox is full:\s*<(?P[^>]*)>")), # Microsoft SMTPSVC - (_c(r'The email below could not be delivered to the following user:'), - _c(r'Old message:'), - _c(r'<(?P[^>]*)>')), + (_c('The email below could not be delivered to the following user:'), + _c('Old message:'), + _c('<(?P[^>]*)>')), # Yahoo on behalf of other domains like sbcglobal.net (_c(r'Unable to deliver message to the following address\(es\)\.'), _c(r'--- Original message follows\.'), - _c(r'<(?P[^>]*)>:')), + _c('<(?P[^>]*)>:')), # googlemail.com - (_c(r'Delivery to the following recipient(s)? failed'), - _c(r'----- Original message -----'), + (_c('Delivery to the following recipient(s)? failed'), + _c('----- Original message -----'), _c(r'^\s*(?P[^\s@]+@[^\s@]+)\s*$')), # kundenserver.de, mxlogic.net - (_c(r'A message that you( have)? sent could not be delivered'), - _c(r'^---'), - _c(r'<(?P[^>]*)>')), + (_c('A message that you( have)? sent could not be delivered'), + _c('^---'), + _c('<(?P[^>]*)>')), # another kundenserver.de - (_c(r'A message that you( have)? sent could not be delivered'), - _c(r'^---'), + (_c('A message that you( have)? sent could not be delivered'), + _c('^---'), _c(r'^(?P[^\s@]+@[^\s@:]+):')), # thehartford.com and amenworld.com - (_c(r'Del(i|e)very to the following recipient(s)? (failed|was aborted)'), + (_c('Del(i|e)very to the following recipient(s)? (failed|was aborted)'), # this one may or may not have the original message, but there's nothing # unique to stop on, so stop on the first line of at least 3 characters # that doesn't start with 'D' (to not stop immediately) and has no '@'. - _c(r'^[^D][^@]{2,}$'), + _c('^[^D][^@]{2,}$'), _c(r'^\s*(. )?(?P[^\s@]+@[^\s@]+)\s*$')), # and another thehartfod.com/hartfordlife.com (_c(r'^Your message\s*$'), - _c(r'^because:'), + _c('^because:'), _c(r'^\s*(?P[^\s@]+@[^\s@]+)\s*$')), # kviv.be (InterScan NT) - (_c(r'^Unable to deliver message to'), + (_c('^Unable to deliver message to'), _c(r'\*+\s+End of message\s+\*+'), - _c(r'<(?P[^>]*)>')), + _c('<(?P[^>]*)>')), # earthlink.net supported domains - (_c(r'^Sorry, unable to deliver your message to'), - _c(r'^A copy of the original message'), + (_c('^Sorry, unable to deliver your message to'), + _c('^A copy of the original message'), _c(r'\s*(?P[^\s@]+@[^\s@]+)\s+')), # ademe.fr - (_c(r'^A message could not be delivered to:'), - _c(r'^Subject:'), + (_c('^A message could not be delivered to:'), + _c('^Subject:'), _c(r'^\s*(?P[^\s@]+@[^\s@]+)\s*$')), # andrew.ac.jp - (_c(r'^Invalid final delivery userid:'), - _c(r'^Original message follows.'), + (_c('^Invalid final delivery userid:'), + _c('^Original message follows.'), _c(r'\s*(?P[^\s@]+@[^\s@]+)\s*$')), # E500_SMTP_Mail_Service@lerctr.org and similar - (_c(r'---- Failed Recipients ----'), - _c(r' Mail ----'), - _c(r'<(?P[^>]*)>')), + (_c('---- Failed Recipients ----'), + _c(' Mail ----'), + _c('<(?P[^>]*)>')), # cynergycom.net - (_c(r'A message that you sent could not be delivered'), - _c(r'^---'), + (_c('A message that you sent could not be delivered'), + _c('^---'), _c(r'(?P[^\s@]+@[^\s@)]+)')), # LSMTP for Windows (_c(r'^--> Error description:\s*$'), - _c(r'^Error-End:'), + _c('^Error-End:'), _c(r'^Error-for:\s+(?P[^\s@]+@[^\s@]+)')), # Qmail with a tri-language intro beginning in spanish - (_c(r'Your message could not be delivered'), - _c(r'^-'), - _c(r'<(?P[^>]*)>:')), + (_c('Your message could not be delivered'), + _c('^-'), + _c('<(?P[^>]*)>:')), # socgen.com - (_c(r'Your message could not be delivered to'), + (_c('Your message could not be delivered to'), _c(r'^\s*$'), _c(r'(?P[^\s@]+@[^\s@]+)')), # dadoservice.it - (_c(r'Your message has encountered delivery problems'), - _c(r'Your message reads'), + (_c('Your message has encountered delivery problems'), + _c('Your message reads'), _c(r'addressed to\s*(?P[^\s@]+@[^\s@)]+)')), # gomaps.com - (_c(r'Did not reach the following recipient'), + (_c('Did not reach the following recipient'), _c(r'^\s*$'), _c(r'\s(?P[^\s@]+@[^\s@]+)')), # EYOU MTA SYSTEM - (_c(r'This is the deliver program at'), - _c(r'^-'), + (_c('This is the deliver program at'), + _c('^-'), _c(r'^(?P[^\s@]+@[^\s@<>]+)')), # A non-standard qmail at ieo.it - (_c(r'this is the email server at'), - _c(r'^-'), + (_c('this is the email server at'), + _c('^-'), _c(r'\s(?P[^\s@]+@[^\s@]+)[\s,]')), # pla.net.py (MDaemon.PRO ?) - (_c(r'- no such user here'), - _c(r'There is no user'), + (_c('- no such user here'), + _c('There is no user'), _c(r'^(?P[^\s@]+@[^\s@]+)\s')), # fastdnsservers.com - (_c(r'The following recipient.*could not be reached'), - _c(r'bogus stop pattern'), + (_c('The following recipient.*could not be reached'), + _c('bogus stop pattern'), _c(r'^(?P[^\s@]+@[^\s@]+)\s*$')), # lttf.com - (_c(r'Could not deliver message to'), + (_c('Could not deliver message to'), _c(r'^\s*--'), _c(r'^Failed Recipient:\s*(?P[^\s@]+@[^\s@]+)\s*$')), # uci.edu - (_c(r'--------Message not delivered'), - _c(r'--------Error Detail'), + (_c('--------Message not delivered'), + _c('--------Error Detail'), _c(r'^\s*(?P[^\s@]+@[^\s@]+)\s*$')), # Dovecot LDA Over quota MDN (bogus - should be DSN). - (_c(r'^Your message'), - _c(r'^Reporting'), + (_c('^Your message'), + _c('^Reporting'), _c( r'Your message to (?P[^\s@]+@[^\s@]+) was automatically rejected' )), # mail.ru - (_c(r'A message that you sent was rejected'), - _c(r'This is a copy of your message'), + (_c('A message that you sent was rejected'), + _c('This is a copy of your message'), _c(r'\s(?P[^\s@]+@[^\s@]+)')), # MailEnable - (_c(r'Message could not be delivered to some recipients.'), - _c(r'Message headers follow'), + (_c('Message could not be delivered to some recipients.'), + _c('Message headers follow'), _c(r'Recipient: \[SMTP:(?P[^\s@]+@[^\s@]+)\]')), # This one is from Yahoo but dosen't fit the yahoo recognizer format (_c(r'wasn\'t able to deliver the following message'), @@ -231,7 +224,7 @@ def process(msg, patterns=None): # we process the message multiple times anyway. for scre, ecre, acre in patterns: state = 0 - for line in body_line_iterator(msg): + for line in email.iterators.body_line_iterator(msg, decode=True): if state == 0: if scre.search(line): state = 1 diff --git a/Mailman/Bouncers/SimpleWarning.py b/Mailman/Bouncers/SimpleWarning.py index 08d4862d..27970640 100644 --- a/Mailman/Bouncers/SimpleWarning.py +++ b/Mailman/Bouncers/SimpleWarning.py @@ -17,9 +17,8 @@ """Recognizes simple heuristically delimited warnings.""" -import re import email -from email.iterators import body_line_iterator +import email.iterators from Mailman.Bouncers.BouncerAPI import Stop from Mailman.Bouncers.SimpleMatch import _c @@ -76,7 +75,7 @@ def process(msg): addrs = {} for scre, ecre, acre in patterns: state = 0 - for line in body_line_iterator(msg, decode=True): + for line in email.iterators.body_line_iterator(msg, decode=True): if state == 0: if scre.search(line): state = 1 diff --git a/Mailman/Bouncers/Sina.py b/Mailman/Bouncers/Sina.py index 5e48cb44..223bcdb7 100644 --- a/Mailman/Bouncers/Sina.py +++ b/Mailman/Bouncers/Sina.py @@ -18,13 +18,12 @@ from __future__ import print_function import re -import email -from email.iterators import body_line_iterator -from email.header import decode_header +from email import iterators acre = re.compile(r'<(?P[^>]*)>') + def process(msg): if msg.get('from', '').lower() != 'mailer-daemon@sina.com': print('out 1') @@ -42,7 +41,7 @@ def process(msg): print('out 3') return [] addrs = {} - for line in body_line_iterator(part): + for line in iterators.body_line_iterator(part): mo = acre.match(line) if mo: addrs[mo.group('addr')] = 1 diff --git a/Mailman/Bouncers/Yahoo.py b/Mailman/Bouncers/Yahoo.py index c0883c77..68e016e7 100644 --- a/Mailman/Bouncers/Yahoo.py +++ b/Mailman/Bouncers/Yahoo.py @@ -19,15 +19,9 @@ import re import email -from email.iterators import body_line_iterator -from email.header import decode_header +import email.iterators from email.utils import parseaddr -from Mailman import mm_cfg -from Mailman import Utils -from Mailman.Logging.Syslog import syslog -from Mailman.Handlers.CookHeaders import change_header - tcre = (re.compile(r'message\s+from\s+yahoo\.\S+', re.IGNORECASE), re.compile(r'Sorry, we were unable to deliver your message to ' r'the following address(\(es\))?\.', @@ -39,6 +33,7 @@ ) + def process(msg): # Yahoo! bounces seem to have a known subject value and something called # an x-uidl: header, the value of which seems unimportant. @@ -51,7 +46,7 @@ def process(msg): # 1 == tag line seen # 2 == end line seen state = 0 - for line in body_line_iterator(msg): + for line in email.iterators.body_line_iterator(msg): line = line.strip() if state == 0: for cre in tcre: diff --git a/Mailman/CSRFcheck.py b/Mailman/CSRFcheck.py index b7dca18b..23494b50 100644 --- a/Mailman/CSRFcheck.py +++ b/Mailman/CSRFcheck.py @@ -18,10 +18,9 @@ """ Cross-Site Request Forgery checker """ import time -import urllib.parse +import urllib import marshal import binascii -import traceback from Mailman import mm_cfg from Mailman.Logging.Syslog import syslog @@ -36,63 +35,40 @@ } + def csrf_token(mlist, contexts, user=None): """ create token by mailman cookie generation algorithm """ + if user: # Unmunge a munged email address. user = UnobscureEmail(urllib.parse.unquote(user)) - syslog('debug', 'CSRF token generation: mlist=%s, contexts=%s, user=%s', - mlist.internal_name(), contexts, user) - else: - syslog('debug', 'CSRF token generation: mlist=%s, contexts=%s', - mlist.internal_name(), contexts) - selected_context = None for context in contexts: key, secret = mlist.AuthContextInfo(context, user) if key and secret: - selected_context = context - syslog('debug', 'CSRF token generation: Selected context=%s, key=%s', - context, key) break else: - syslog('debug', 'CSRF token generation failed: No valid context found in %s', - contexts) return None # not authenticated - issued = int(time.time()) needs_hash = (secret + repr(issued)).encode('utf-8') mac = sha_new(needs_hash).hexdigest() keymac = '%s:%s' % (key, mac) - token = binascii.hexlify(marshal.dumps((issued, keymac))).decode('utf-8') - - syslog('debug', 'CSRF token generated: context=%s, key=%s, issued=%s, mac=%s, token=%s', - selected_context, key, time.ctime(issued), mac, token) + token = marshal.dumps((issued, keymac)).hex() + return token def csrf_check(mlist, token, cgi_user=None): """ check token by mailman cookie validation algorithm """ try: - syslog('debug', 'CSRF token validation: mlist=%s, cgi_user=%s, token=%s', - mlist.internal_name(), cgi_user, token) - issued, keymac = marshal.loads(binascii.unhexlify(token)) key, received_mac = keymac.split(':', 1) - - syslog('debug', 'CSRF token details: issued=%s, key=%s, received_mac=%s', - time.ctime(issued), key, received_mac) - if not key.startswith(mlist.internal_name() + '+'): - syslog('debug', 'CSRF token validation failed: Invalid mailing list name in key. Expected %s, got %s', - mlist.internal_name(), key) return False - key = key[len(mlist.internal_name()) + 1:] if '+' in key: key, user = key.split('+', 1) else: user = None - # Don't allow unprivileged tokens for admin or admindb. if cgi_user == 'admin': if key not in ('admin', 'site'): @@ -106,7 +82,6 @@ def csrf_check(mlist, token, cgi_user=None): 'admindb form submitted with CSRF token issued for %s.', key + '+' + user if user else key) return False - if user: # This is for CVE-2021-42097. The token is a user token because # of the fix for CVE-2021-42096 but it must match the user for @@ -118,51 +93,14 @@ def csrf_check(mlist, token, cgi_user=None): 'issued for %s.', cgi_user, raw_user) return False - context = keydict.get(key) key, secret = mlist.AuthContextInfo(context, user) - if not key: - raise ValueError('Missing CSRF key') - - try: - # Ensure all values are properly encoded before hashing - if isinstance(secret, str): - secret = secret.encode('utf-8') - elif not isinstance(secret, bytes): - secret = str(secret).encode('utf-8') - - issued_str = str(issued) - if isinstance(issued_str, str): - issued_str = issued_str.encode('utf-8') - - mac = sha_new(secret + issued_str).hexdigest() - except (TypeError, UnicodeError) as e: - syslog('error', 'CSRF token validation failed with encoding error: %s. Secret type: %s, issued type: %s, secret value: %r, issued value: %r', - str(e), type(secret), type(issued), secret, issued) - return False - - age = time.time() - issued - - syslog('debug', 'CSRF token validation: context=%s, generated_mac=%s, age=%s seconds', - context, mac, age) - + assert key + secret = secret + repr(issued) + mac = sha_new(secret.encode()).hexdigest() if (mac == received_mac - and 0 < age < mm_cfg.FORM_LIFETIME): - syslog('debug', 'CSRF token validation successful') + and 0 < time.time() - issued < mm_cfg.FORM_LIFETIME): return True - - if mac != received_mac: - syslog('debug', 'CSRF token validation failed: MAC mismatch. Expected %s, got %s. Full token details: expected=(%s, %s:%s), received=(%s, %s:%s)', - mac, received_mac, time.ctime(issued), key, mac, time.ctime(issued), key, received_mac) - elif age <= 0: - syslog('debug', 'CSRF token validation failed: Token issued in the future. Token details: issued=%s, key=%s, mac=%s', - time.ctime(issued), key, received_mac) - else: - syslog('debug', 'CSRF token validation failed: Token expired. Age: %s seconds, FORM_LIFETIME=%s seconds, contexts=%s. Token details: issued=%s, key=%s, mac=%s', - age, mm_cfg.FORM_LIFETIME, keydict.keys(), time.ctime(issued), key, received_mac) - return False - except (AssertionError, ValueError, TypeError) as e: - syslog('error', 'CSRF token validation failed with error: %s\nTraceback:\n%s', - str(e), ''.join(traceback.format_exc())) + except (AssertionError, ValueError, TypeError): return False diff --git a/Mailman/Cgi/Auth.py b/Mailman/Cgi/Auth.py index 689988a9..6f61d568 100644 --- a/Mailman/Cgi/Auth.py +++ b/Mailman/Cgi/Auth.py @@ -52,7 +52,6 @@ def loginpage(mlist, scriptname, msg='', frontpage=None): # Language stuff charset = Utils.GetCharSet(mlist.preferred_language) print('Content-type: text/html; charset=' + charset + '\n\n') - print('') print(Utils.maketext( 'admlogin.html', {'listname': mlist.real_name, diff --git a/Mailman/Cgi/admin.py b/Mailman/Cgi/admin.py index df8a5dfb..328c4162 100644 --- a/Mailman/Cgi/admin.py +++ b/Mailman/Cgi/admin.py @@ -20,18 +20,19 @@ def cmp(a, b): return (a > b) - (a < b) +#from future.builtins import cmp import sys import os import re -import urllib.parse +from Mailman.Utils import FieldStorage +import urllib.request, urllib.parse, urllib.error import signal -import traceback from email.utils import unquote, parseaddr, formataddr from Mailman import mm_cfg from Mailman import Utils -from Mailman.Message import Message +from Mailman import Message from Mailman import MailList from Mailman import Errors from Mailman import MemberAdaptor @@ -39,7 +40,7 @@ def cmp(a, b): from Mailman.UserDesc import UserDesc from Mailman.htmlformat import * from Mailman.Cgi import Auth -from Mailman.Logging.Syslog import mailman_log +from Mailman.Logging.Syslog import syslog from Mailman.Utils import sha_new from Mailman.CSRFcheck import csrf_check @@ -54,260 +55,203 @@ def D_(s): AUTH_CONTEXTS = (mm_cfg.AuthListAdmin, mm_cfg.AuthSiteAdmin) -def validate_listname(listname): - """Validate and sanitize a listname to prevent path traversal. - - Args: - listname: The listname to validate - - Returns: - tuple: (is_valid, sanitized_name, error_message) - """ - if not listname: - return False, None, _('List name is required') - - # Convert to lowercase and strip whitespace - listname = listname.lower().strip() - - # Basic validation - if not Utils.ValidateListName(listname): - return False, None, _('Invalid list name') - - # Check for path traversal attempts - if '..' in listname or '/' in listname or '\\' in listname: - return False, None, _('Invalid list name') - - return True, listname, None - -def handle_no_list(): - """Handle the case when no list is specified.""" - doc = Document() - doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) - doc.SetTitle(_('CGI script error')) - doc.AddItem(Header(2, _('CGI script error'))) - doc.addError(_('Invalid options to CGI script.')) - doc.AddItem('


      ') - doc.AddItem(MailmanLogo()) - print('Status: 400 Bad Request') - return doc + def main(): + # Try to find out which list is being administered + parts = Utils.GetPathPieces() + if not parts: + # None, so just do the admin overview and be done with it + admin_overview() + return + # Get the list object + listname = parts[0].lower() try: - # Log page load - mailman_log('info', 'admin: Page load started') - - # Initialize document early + mlist = MailList.MailList(listname, lock=0) + except Errors.MMListError as e: + # Avoid cross-site scripting attacks + safelistname = Utils.websafe(listname) + # Send this with a 404 status. + print('Status: 404 Not Found') + admin_overview(_(f'No such list {safelistname}')) + syslog('error', 'admin: No such list "%s": %s\n', + listname, e) + return + # Now that we know what list has been requested, all subsequent admin + # pages are shown in that list's preferred language. + i18n.set_language(mlist.preferred_language) + # If the user is not authenticated, we're done. + cgidata = FieldStorage(keep_blank_values=1) + try: + cgidata.getfirst('csrf_token', '') + except TypeError: + # Someone crafted a POST with a bad Content-Type:. doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) - - # Parse form data first since we need it for authentication - try: - if os.environ.get('REQUEST_METHOD') == 'POST': - content_length = int(os.environ.get('CONTENT_LENGTH', 0)) - if content_length > 0: - form_data = sys.stdin.read(content_length) - cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=True) - else: - cgidata = {} - else: - query_string = os.environ.get('QUERY_STRING', '') - cgidata = urllib.parse.parse_qs(query_string, keep_blank_values=True) - except Exception as e: - mailman_log('error', 'admin: Invalid form data: %s\n%s', str(e), traceback.format_exc()) - doc.AddItem(Header(2, _("Error"))) - doc.AddItem(Bold(_('Invalid request'))) - print('Status: 400 Bad Request') - print(doc.Format()) - return - - # Get the list name - parts = Utils.GetPathPieces() - if not parts: - doc = handle_no_list() - print(doc.Format()) - return - - # Validate listname - is_valid, listname, error_msg = validate_listname(parts[0]) - if not is_valid: - doc.SetTitle(_('CGI script error')) - doc.AddItem(Header(2, _('CGI script error'))) - doc.addError(error_msg) - doc.AddItem('
      ') - doc.AddItem(MailmanLogo()) - print('Status: 400 Bad Request') - print(doc.Format()) - return - - mailman_log('info', 'admin: Processing list "%s"', listname) + doc.AddItem(Header(2, _("Error"))) + doc.AddItem(Bold(_('Invalid options to CGI script.'))) + # Send this with a 400 status. + print('Status: 400 Bad Request') + print(doc.Format()) + return - try: - mlist = MailList.MailList(listname, lock=0) - except Errors.MMListError as e: - # Avoid cross-site scripting attacks and information disclosure - safelistname = Utils.websafe(listname) - doc.SetTitle(_('CGI script error')) - doc.AddItem(Header(2, _('CGI script error'))) - doc.addError(_('No such list {safelistname}')) - doc.AddItem('
      ') - doc.AddItem(MailmanLogo()) - print('Status: 404 Not Found') - print(doc.Format()) - mailman_log('error', 'admin: No such list "%s"', listname) - return - except Exception as e: - # Log the full error but don't expose it to the user - mailman_log('error', 'admin: Unexpected error for list "%s": %s', listname, str(e)) - doc.SetTitle(_('CGI script error')) - doc.AddItem(Header(2, _('CGI script error'))) - doc.addError(_('An error occurred processing your request')) - doc.AddItem('
      ') - doc.AddItem(MailmanLogo()) - print('Status: 500 Internal Server Error') - print(doc.Format()) - return + # CSRF check + safe_params = ['VARHELP', 'adminpw', 'admlogin', + 'letter', 'chunk', 'findmember', + 'legend'] + params = list(cgidata.keys()) + if set(params) - set(safe_params): + csrf_checked = csrf_check(mlist, cgidata.getfirst('csrf_token'), + 'admin') + else: + csrf_checked = True + # if password is present, void cookie to force password authentication. + if cgidata.getfirst('adminpw'): + os.environ['HTTP_COOKIE'] = '' + csrf_checked = True + + if not mlist.WebAuthenticate((mm_cfg.AuthListAdmin, + mm_cfg.AuthSiteAdmin), + cgidata.getfirst('adminpw', '')): + if 'adminpw' in cgidata: + # This is a re-authorization attempt + msg = Bold(FontSize('+1', _('Authorization failed.'))).Format() + remote = os.environ.get('HTTP_FORWARDED_FOR', + os.environ.get('HTTP_X_FORWARDED_FOR', + os.environ.get('REMOTE_ADDR', + 'unidentified origin'))) + syslog('security', + 'Authorization failed (admin): list=%s: remote=%s', + listname, remote) + else: + msg = '' + Auth.loginpage(mlist, 'admin', msg=msg) + return - i18n.set_language(mlist.preferred_language) - # If the user is not authenticated, we're done. - try: - # CSRF check - safe_params = ['VARHELP', 'adminpw', 'admlogin', - 'letter', 'chunk', 'findmember', - 'legend'] - params = list(cgidata.keys()) - if set(params) - set(safe_params): - csrf_checked = csrf_check(mlist, cgidata.get('csrf_token', [''])[0], - 'admin') - else: - csrf_checked = True - if cgidata.get('adminpw', [''])[0]: - os.environ['HTTP_COOKIE'] = '' - csrf_checked = True - auth_result = mlist.WebAuthenticate((mm_cfg.AuthListAdmin, - mm_cfg.AuthSiteAdmin), - cgidata.get('adminpw', [''])[0]) - if not auth_result: - for context in (mm_cfg.AuthListAdmin, mm_cfg.AuthSiteAdmin): - mailman_log('debug', 'Checking context %s: %s', - context, str(mlist.AuthContextInfo(context))) - except Exception as e: - mailman_log('error', 'admin: Exception during WebAuthenticate: %s\n%s', str(e), traceback.format_exc()) - raise - if not auth_result: - if 'adminpw' in cgidata: - msg = Bold(FontSize('+1', _('Authorization failed.'))).Format() - remote = os.environ.get('HTTP_FORWARDED_FOR', - os.environ.get('HTTP_X_FORWARDED_FOR', - os.environ.get('REMOTE_ADDR', - 'unidentified origin'))) - mailman_log('security', - 'Authorization failed (admin): list=%s: remote=%s\n%s', - listname, remote, traceback.format_exc()) - else: - msg = '' - Auth.loginpage(mlist, 'admin', msg=msg) - return + # Which subcategory was requested? Default is `general' + if len(parts) == 1: + category = 'general' + subcat = None + elif len(parts) == 2: + category = parts[1] + subcat = None + else: + category = parts[1] + subcat = parts[2] + + # Is this a log-out request? + if category == 'logout': + # site-wide admin should also be able to logout. + if mlist.AuthContextInfo(mm_cfg.AuthSiteAdmin)[0] == 'site': + print(mlist.ZapCookie(mm_cfg.AuthSiteAdmin)) + print(mlist.ZapCookie(mm_cfg.AuthListAdmin)) + Auth.loginpage(mlist, 'admin', frontpage=1) + return - # Which subcategory was requested? Default is `general' - if len(parts) == 1: - category = 'general' - subcat = None - elif len(parts) == 2: - category = parts[1] - subcat = None - else: - category = parts[1] - subcat = parts[2] + # Sanity check + if category not in list(mlist.GetConfigCategories().keys()): + category = 'general' - # Sanity check - validate category against available categories - if category not in list(mlist.GetConfigCategories().keys()): - category = 'general' + # Is the request for variable details? + varhelp = None + qsenviron = os.environ.get('QUERY_STRING') + parsedqs = None + if qsenviron: + parsedqs = urllib.parse.parse_qs(qsenviron) + if 'VARHELP' in cgidata: + varhelp = cgidata.getfirst('VARHELP') + elif parsedqs: + # POST methods, even if their actions have a query string, don't get + # put into FieldStorage's keys :-( + qs = parsedqs.get('VARHELP') + if qs and type(qs) is list: + varhelp = qs[0] + if varhelp: + option_help(mlist, varhelp) + return - # Is the request for variable details? - varhelp = None - qsenviron = os.environ.get('QUERY_STRING') - parsedqs = None - if qsenviron: - parsedqs = urllib.parse.parse_qs(qsenviron) - if 'VARHELP' in cgidata: - varhelp = cgidata['VARHELP'][0] - elif parsedqs: - # POST methods, even if their actions have a query string, don't get - # put into FieldStorage's keys :-( - qs = parsedqs.get('VARHELP') - if qs and isinstance(qs, list): - varhelp = qs[0] - if varhelp: - option_help(mlist, varhelp) - return + # The html page document + doc = Document() + doc.set_language(mlist.preferred_language) - doc = Document() - doc.set_language(mlist.preferred_language) - form = Form(mlist=mlist, contexts=AUTH_CONTEXTS) + # From this point on, the MailList object must be locked. However, we + # must release the lock no matter how we exit. try/finally isn't enough, + # because of this scenario: user hits the admin 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. + # + # BAW: This may not be portable to other web servers or cgi execution + # models. + def sigterm_handler(signum, frame, mlist=mlist): + # Make sure the list gets unlocked... + 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) - # From this point on, the MailList object must be locked - mlist.Lock() - try: - # Install the emergency shutdown signal handler - def sigterm_handler(signum, frame, mlist=mlist): - # Make sure the list gets unlocked... - mlist.Unlock() - # ...and ensure we exit - sys.exit(0) - signal.signal(signal.SIGTERM, sigterm_handler) - - if cgidata: - if csrf_checked: - # There are options to change - change_options(mlist, category, subcat, cgidata, doc) - else: - doc.addError( - _('The form lifetime has expired. (request forgery check)')) - # Let the list sanity check the changed values - mlist.CheckValues() + mlist.Lock() + try: + # Install the emergency shutdown signal handler + signal.signal(signal.SIGTERM, sigterm_handler) - # Additional sanity checks - if not mlist.digestable and not mlist.nondigestable: - doc.addError( - _(f'''You have turned off delivery of both digest and - non-digest messages. This is an incompatible state of - affairs. You must turn on either digest delivery or - non-digest delivery or your mailing list will basically be - unusable.'''), tag=_('Warning: ')) - - dm = mlist.getDigestMemberKeys() - if not mlist.digestable and dm: - doc.addError( - _(f'''You have digest members, but digests are turned - off. Those people will not receive mail. - Affected member(s) %(dm)r.'''), - tag=_('Warning: ')) - rm = mlist.getRegularMemberKeys() - if not mlist.nondigestable and rm: + if list(cgidata.keys()): + if csrf_checked: + # There are options to change + change_options(mlist, category, subcat, cgidata, doc) + else: doc.addError( - _(f'''You have regular list members but non-digestified mail is - turned off. They will receive non-digestified mail until you - fix this problem. Affected member(s) %(rm)r.'''), - tag=_('Warning: ')) - - # Show the results page - show_results(mlist, doc, category, subcat, cgidata) - print(doc.Format()) - mlist.Save() - finally: - # Now be sure to unlock the list - mlist.Unlock() - except Exception as e: - doc = Document() - doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) - doc.AddItem(Header(2, _("Error"))) - doc.AddItem(Bold(_('An unexpected error occurred.'))) - doc.AddItem(Preformatted(Utils.websafe(str(e)))) - doc.AddItem(Preformatted(Utils.websafe(traceback.format_exc()))) - print('Status: 500 Internal Server Error') + _('The form lifetime has expired. (request forgery check)')) + # Let the list sanity check the changed values + mlist.CheckValues() + # Additional sanity checks + if not mlist.digestable and not mlist.nondigestable: + doc.addError( + _(f'''You have turned off delivery of both digest and + non-digest messages. This is an incompatible state of + affairs. You must turn on either digest delivery or + non-digest delivery or your mailing list will basically be + unusable.'''), tag=_('Warning: ')) + + dm = mlist.getDigestMemberKeys() + if not mlist.digestable and dm: + doc.addError( + _(f'''You have digest members, but digests are turned + off. Those people will not receive mail. + Affected member(s) %(dm)r.'''), + tag=_('Warning: ')) + rm = mlist.getRegularMemberKeys() + if not mlist.nondigestable and rm: + doc.addError( + _(f'''You have regular list members but non-digestified mail is + turned off. They will receive non-digestified mail until you + fix this problem. Affected member(s) %(rm)r.'''), + tag=_('Warning: ')) + # Glom up the results page and print it out + show_results(mlist, doc, category, subcat, cgidata) print(doc.Format()) - mailman_log('error', 'admin: Unexpected error: %s\n%s', str(e), traceback.format_exc()) + mlist.Save() + finally: + # Now be sure to unlock the list. It's okay if we get a signal here + # because essentially, the signal handler will do the same thing. And + # unlocking is unconditional, so it's not an error if we unlock while + # we're already unlocked. + mlist.Unlock() + + def admin_overview(msg=''): # Show the administrative overview page, with the list of all the lists on # this host. msg is an optional error message to display at the top of @@ -316,11 +260,7 @@ def admin_overview(msg=''): # This page should be displayed in the server's default language, which # should have already been set. hostname = Utils.get_domain() - if isinstance(hostname, bytes): - hostname = hostname.decode('latin1', 'replace') - legend = _('%(hostname)s mailing lists - Admin Links') % { - 'hostname': hostname - } + legend = _(f'{hostname} mailing lists - Admin Links') # The html `document' doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) @@ -336,31 +276,21 @@ def admin_overview(msg=''): listnames.sort() for name in listnames: - if isinstance(name, bytes): - name = name.decode('latin1', 'replace') try: mlist = MailList.MailList(name, lock=0) except Errors.MMUnknownListError: # The list could have been deleted by another process. continue if mlist.advertised: - real_name = mlist.real_name - if isinstance(real_name, bytes): - real_name = real_name.decode('latin1', 'replace') - description = mlist.GetDescription() - if isinstance(description, bytes): - description = description.decode('latin1', 'replace') if mm_cfg.VIRTUAL_HOST_OVERVIEW and ( - mlist.web_page_url.find('/%(hostname)s/' % {'hostname': hostname}) == -1 and - mlist.web_page_url.find('/%(hostname)s:' % {'hostname': hostname}) == -1): + mlist.web_page_url.find('/%s/' % hostname) == -1 and + mlist.web_page_url.find('/%s:' % hostname) == -1): # List is for different identity of this host - skip it. continue else: advertised.append((mlist.GetScriptURL('admin'), - real_name, - Utils.websafe(description))) - mlist.Unlock() - + mlist.real_name, + Utils.websafe(mlist.GetDescription()))) # Greeting depends on whether there was an error or not if msg: greeting = FontAttr(msg, color="ff5060", size="+1") @@ -372,34 +302,32 @@ def admin_overview(msg=''): if not advertised: welcome.extend([ greeting, - _('

      There currently are no publicly-advertised %(mailmanlink)s mailing lists on %(hostname)s.') % { - 'mailmanlink': mailmanlink, - 'hostname': hostname - }, + _(f'''

      There currently are no publicly-advertised {mailmanlink} + mailing lists on {hostname}.'''), ]) else: welcome.extend([ greeting, - _('

      Below is the collection of publicly-advertised %(mailmanlink)s mailing lists on %(hostname)s. Click on a list name to visit the configuration pages for that list.') % { - 'mailmanlink': mailmanlink, - 'hostname': hostname - }, + _(f'''

      Below is the collection of publicly-advertised + {mailmanlink} mailing lists on {hostname}. Click on a list + name to visit the configuration pages for that list.'''), ]) creatorurl = Utils.ScriptURL('create') mailman_owner = Utils.get_site_email() extra = msg and _('right ') or '' welcome.extend([ - _('To visit the administrators configuration page for an unadvertised list, open a URL similar to this one, but with a \'/\' and the %(extra)slist name appended. If you have the proper authority, you can also create a new mailing list.') % { - 'extra': extra, - 'creatorurl': creatorurl - }, - _('

      General list information can be found at '), + _(f'''To visit the administrators configuration page for an + unadvertised list, open a URL similar to this one, but with a '/' and + the {extra}list name appended. If you have the proper authority, + you can also create a new mailing list. + +

      General list information can be found at '''), Link(Utils.ScriptURL('listinfo'), _('the mailing list overview page')), '.', _('

      (Send questions and comments to '), - Link('mailto:%(mailman_owner)s' % {'mailman_owner': mailman_owner}, mailman_owner), + Link('mailto:%s' % mailman_owner, mailman_owner), '.)

      ', ]) @@ -426,6 +354,8 @@ def admin_overview(msg=''): doc.AddItem(MailmanLogo()) print(doc.Format()) + + def option_help(mlist, varhelp): # The html page document doc = Document() @@ -434,7 +364,7 @@ def option_help(mlist, varhelp): item = None reflist = varhelp.split('/') if len(reflist) >= 2: - category, subcat = None, None + category = subcat = None if len(reflist) == 2: category, varname = reflist elif len(reflist) == 3: @@ -498,159 +428,166 @@ def option_help(mlist, varhelp): doc.AddItem(mlist.GetMailmanFooter()) print(doc.Format()) -def add_standard_headers(doc, mlist, title, category, subcat): - """Add standard headers to admin pages. - - Args: - doc: The Document object - mlist: The MailList object - title: The page title - category: Optional category name - subcat: Optional subcategory name - """ - # Set the page title - doc.SetTitle(title) - - # Add the main header - doc.AddItem(Header(2, title)) - - # Add navigation breadcrumbs if category/subcat provided - breadcrumbs = [] - breadcrumbs.append(Link(mlist.GetScriptURL('admin'), _('%(realname)s administrative interface'))) - if category: - breadcrumbs.append(Link(mlist.GetScriptURL('admin') + '/' + category, _(category))) - if subcat: - breadcrumbs.append(Link(mlist.GetScriptURL('admin') + '/' + category + '/' + subcat, _(subcat))) - # Convert each breadcrumb item to a string before joining - breadcrumbs = [str(item) for item in breadcrumbs] - doc.AddItem(Center(' > '.join(breadcrumbs))) - - # Add horizontal rule - doc.AddItem('


      ') + def show_results(mlist, doc, category, subcat, cgidata): # Produce the results page adminurl = mlist.GetScriptURL('admin') categories = mlist.GetConfigCategories() label = _(categories[category][0]) - if isinstance(label, bytes): - label = label.decode(Utils.GetCharSet(mlist.preferred_language), 'replace') - - # Add standard headers - title = _('%(realname)s Administration (%(label)s)') % { - 'realname': mlist.real_name, - 'label': label - } - add_standard_headers(doc, mlist, title, category, subcat) - - # Create a table for configuration categories - cat_table = Table(border=0, width='100%') - cat_table.AddRow([Center(Header(2, _('Configuration Categories')))]) - cat_table.AddCellInfo(cat_table.GetCurrentRowIndex(), 0, colspan=2, - bgcolor=mm_cfg.WEB_HEADER_COLOR) - - # Add category links - for cat in categories.keys(): - cat_label = _(categories[cat][0]) - if isinstance(cat_label, bytes): - cat_label = cat_label.decode(Utils.GetCharSet(mlist.preferred_language), 'replace') - url = '%s/%s' % (adminurl, cat) - - # Get subcategories if they exist - subcats = mlist.GetConfigSubCategories(cat) - if subcats: - # Create a container for the category and its subcategories - container = Container() - if cat == category: - container.AddItem(Bold(Link(url, cat_label))) - else: - container.AddItem(Link(url, cat_label)) - - # Add subcategory links - subcat_list = UnorderedList() - for subcat_name, subcat_label in subcats: - subcat_url = '%s/%s/%s' % (adminurl, cat, subcat_name) - if isinstance(subcat_label, bytes): - subcat_label = subcat_label.decode(Utils.GetCharSet(mlist.preferred_language), 'replace') - if cat == category and subcat_name == subcat: - subcat_list.AddItem(Bold(Link(subcat_url, subcat_label))) - else: - subcat_list.AddItem(Link(subcat_url, subcat_label)) - container.AddItem(subcat_list) - cat_table.AddRow([container]) - else: - # No subcategories, just add the category link - if cat == category: - cat_table.AddRow([Bold(Link(url, cat_label))]) - else: - cat_table.AddRow([Link(url, cat_label)]) - - doc.AddItem(cat_table) + + # Set up the document's headers + realname = mlist.real_name + doc.SetTitle(_(f'{realname} Administration ({label})')) + doc.AddItem(Center(Header(2, _( + f'{realname} mailing list administration
      {label} Section')))) doc.AddItem('
      ') - - # Use ParseTags for the main content - replacements = { - 'realname': mlist.real_name, - 'label': label, - 'adminurl': adminurl, - 'admindburl': mlist.GetScriptURL('admindb'), - 'listinfourl': mlist.GetScriptURL('listinfo'), - 'edithtmlurl': mlist.GetScriptURL('edithtml'), - 'archiveurl': mlist.GetBaseArchiveURL(), - 'rmlisturl': mlist.GetScriptURL('rmlist') if mm_cfg.OWNERS_CAN_DELETE_THEIR_OWN_LISTS and mlist.internal_name() != mm_cfg.MAILMAN_SITE_LIST else None - } - - # Ensure all replacements are properly encoded for the list's language - for key, value in replacements.items(): - if isinstance(value, bytes): - replacements[key] = value.decode(Utils.GetCharSet(mlist.preferred_language), 'replace') - - output = mlist.ParseTags('admin_results.html', replacements, mlist.preferred_language) - doc.AddItem(output) - - # Now we need to craft the form that will be submitted + # Now we need to craft the form that will be submitted, which will contain + # all the variable settings, etc. This is a bit of a kludge because we + # know that the autoreply and members categories supports file uploads. encoding = None if category in ('autoreply', 'members'): encoding = 'multipart/form-data' if subcat: - form = Form('%(adminurl)s/%(category)s/%(subcat)s' % { - 'adminurl': adminurl, - 'category': category, - 'subcat': subcat - }, encoding=encoding, mlist=mlist, contexts=AUTH_CONTEXTS) + form = Form('%s/%s/%s' % (adminurl, category, subcat), + encoding=encoding, mlist=mlist, contexts=AUTH_CONTEXTS) else: - form = Form('%(adminurl)s/%(category)s' % { - 'adminurl': adminurl, - 'category': category - }, encoding=encoding, mlist=mlist, contexts=AUTH_CONTEXTS) - - # Add the form content based on category + form = Form('%s/%s' % (adminurl, category), + encoding=encoding, mlist=mlist, contexts=AUTH_CONTEXTS) + # This holds the two columns of links + linktable = Table(valign='top', width='100%') + linktable.AddRow([Center(Bold(_("Configuration Categories"))), + Center(Bold(_("Other Administrative Activities")))]) + # The `other links' are stuff in the right column. + otherlinks = UnorderedList() + otherlinks.AddItem(Link(mlist.GetScriptURL('admindb'), + _('Tend to pending moderator requests'))) + otherlinks.AddItem(Link(mlist.GetScriptURL('listinfo'), + _('Go to the general list information page'))) + otherlinks.AddItem(Link(mlist.GetScriptURL('edithtml'), + _('Edit the public HTML pages and text files'))) + otherlinks.AddItem(Link(mlist.GetBaseArchiveURL(), + _('Go to list archives')).Format() + + '
       
      ') + # We do not allow through-the-web deletion of the site list! + if mm_cfg.OWNERS_CAN_DELETE_THEIR_OWN_LISTS and \ + mlist.internal_name() != mm_cfg.MAILMAN_SITE_LIST: + otherlinks.AddItem(Link(mlist.GetScriptURL('rmlist'), + _('Delete this mailing list')).Format() + + _(' (requires confirmation)
       
      ')) + otherlinks.AddItem(Link('%s/logout' % adminurl, + # BAW: What I really want is a blank line, but + # adding an   won't do it because of the + # bullet added to the list item. + '%s' % + _('Logout'))) + # These are links to other categories and live in the left column + categorylinks_1 = categorylinks = UnorderedList() + categorylinks_2 = '' + categorykeys = list(categories.keys()) + half = len(categorykeys) / 2 + counter = 0 + subcat = None + for k in categorykeys: + label = _(categories[k][0]) + url = '%s/%s' % (adminurl, k) + if k == category: + # Handle subcategories + subcats = mlist.GetConfigSubCategories(k) + if subcats: + subcat = Utils.GetPathPieces()[-1] + for k, v in subcats: + if k == subcat: + break + else: + # The first subcategory in the list is the default + subcat = subcats[0][0] + subcat_items = [] + for sub, text in subcats: + if sub == subcat: + text = Bold('[%s]' % text).Format() + subcat_items.append(Link(url + '/' + sub, text)) + categorylinks.AddItem( + Bold(label).Format() + + UnorderedList(*subcat_items).Format()) + else: + categorylinks.AddItem(Link(url, Bold('[%s]' % label))) + else: + categorylinks.AddItem(Link(url, label)) + counter += 1 + if counter >= half: + categorylinks_2 = categorylinks = UnorderedList() + counter = -len(categorykeys) + # Make the emergency stop switch a rude solo light + etable = Table() + # Add all the links to the links table... + etable.AddRow([categorylinks_1, categorylinks_2]) + etable.AddRowInfo(etable.GetCurrentRowIndex(), valign='top') + if mlist.emergency: + label = _('Emergency moderation of all list traffic is enabled') + etable.AddRow([Center( + Link('?VARHELP=general/emergency', Bold(label)))]) + color = mm_cfg.WEB_ERROR_COLOR + etable.AddCellInfo(etable.GetCurrentRowIndex(), 0, + colspan=2, bgcolor=color) + linktable.AddRow([etable, otherlinks]) + # ...and add the links table to the document. + form.AddItem(linktable) + form.AddItem('
      ') + form.AddItem( + _(f'''Make your changes in the following section, then submit them + using the Submit Your Changes button below.''') + + '

      ') + + # The members and passwords categories are special in that they aren't + # defined in terms of gui elements. Create those pages here. if category == 'members': + # Figure out which subcategory we should display + subcat = Utils.GetPathPieces()[-1] + if subcat not in ('list', 'add', 'remove', 'change', 'sync'): + subcat = 'list' + # Add member category specific tables form.AddItem(membership_options(mlist, subcat, cgidata, doc, form)) form.AddItem(Center(submit_button('setmemberopts_btn'))) + # In "list" subcategory, we can also search for members + if subcat == 'list': + form.AddItem('


      \n') + table = Table(width='100%') + table.AddRow([Center(Header(2, _('Additional Member Tasks')))]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, + bgcolor=mm_cfg.WEB_HEADER_COLOR) + # Add a blank separator row + table.AddRow([' ', ' ']) + # Add a section to set the moderation bit for all members + table.AddRow([_(f"""
    • Set everyone's moderation bit, including + those members not currently visible""")]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) + table.AddRow([RadioButtonArray('allmodbit_val', + (_('Off'), _('On')), + mlist.default_member_moderation), + SubmitButton('allmodbit_btn', _('Set'))]) + form.AddItem(table) elif category == 'passwords': form.AddItem(Center(password_inputs(mlist))) form.AddItem(Center(submit_button())) else: form.AddItem(show_variables(mlist, category, subcat, cgidata, doc)) form.AddItem(Center(submit_button())) - - # Add the form to the document + # And add the form doc.AddItem(form) doc.AddItem(mlist.GetMailmanFooter()) + + def show_variables(mlist, category, subcat, cgidata, doc): - # Get the configuration info options = mlist.GetConfigInfo(category, subcat) - + # The table containing the results table = Table(cellspacing=3, cellpadding=4, width='100%') # Get and portray the text label for the category. categories = mlist.GetConfigCategories() label = _(categories[category][0]) - if isinstance(label, bytes): - label = label.decode(Utils.GetCharSet(mlist.preferred_language), 'replace') table.AddRow([Center(Header(2, label))]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, @@ -659,9 +596,7 @@ def show_variables(mlist, category, subcat, cgidata, doc): # The very first item in the config info will be treated as a general # description if it is a string description = options[0] - if isinstance(description, bytes): - description = description.decode(Utils.GetCharSet(mlist.preferred_language), 'replace') - if isinstance(description, str): + if type(description) is str: table.AddRow([description]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) options = options[1:] @@ -678,14 +613,10 @@ def show_variables(mlist, category, subcat, cgidata, doc): width='85%') for item in options: - if isinstance(item, str): + if type(item) == str: # The very first banner option (string in an options list) is # treated as a general description, while any others are # treated as section headers - centered and italicized... - if isinstance(item, bytes): - item = item.decode(Utils.GetCharSet(mlist.preferred_language), 'replace') - formatted_text = '[%s]' % item - item = Bold(formatted_text).Format() table.AddRow([Center(Italic(item))]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) else: @@ -694,21 +625,25 @@ def show_variables(mlist, category, subcat, cgidata, doc): table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) return table + + def add_options_table_item(mlist, category, subcat, table, item, detailsp=1): # Add a row to an options table with the item description and value. varname, kind, params, extra, descr, elaboration = \ get_item_characteristics(item) + if elaboration is None: + elaboration = descr descr = get_item_gui_description(mlist, category, subcat, varname, descr, elaboration, detailsp) val = get_item_gui_value(mlist, category, kind, varname, params, extra) table.AddRow([descr, val]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, - style=f'background-color: {mm_cfg.WEB_ADMINITEM_COLOR}', - role='cell') + bgcolor=mm_cfg.WEB_ADMINITEM_COLOR) table.AddCellInfo(table.GetCurrentRowIndex(), 1, - style=f'background-color: {mm_cfg.WEB_ADMINITEM_COLOR}', - role='cell') + bgcolor=mm_cfg.WEB_ADMINITEM_COLOR) + + def get_item_characteristics(record): # Break out the components of an item description from its description # record: @@ -728,13 +663,13 @@ def get_item_characteristics(record): raise ValueError(f'Badly formed options entry:\n {record}') return varname, kind, params, dependancies, descr, elaboration + + def get_item_gui_value(mlist, category, kind, varname, params, extra): """Return a representation of an item's settings.""" # Give the category a chance to return the value for the variable value = None - category_data = mlist.GetConfigCategories()[category] - if isinstance(category_data, tuple): - gui = category_data[1] + label, gui = mlist.GetConfigCategories()[category] if hasattr(gui, 'getValue'): value = gui.getValue(mlist, kind, varname, params) # Filter out None, and volatile attributes @@ -762,27 +697,18 @@ def get_item_gui_value(mlist, category, kind, varname, params, extra): return RadioButtonArray(varname, params, checked, not extra) elif (kind == mm_cfg.String or kind == mm_cfg.Email or kind == mm_cfg.Host or kind == mm_cfg.Number): - # Ensure value is a string, decoding bytes if necessary - if isinstance(value, bytes): - value = value.decode(Utils.GetCharSet(mlist.preferred_language), 'replace') return TextBox(varname, value, params) elif kind == mm_cfg.Text: if params: r, c = params else: r, c = None, None - # Ensure value is a string, decoding bytes if necessary - if isinstance(value, bytes): - value = value.decode(Utils.GetCharSet(mlist.preferred_language), 'replace') return TextArea(varname, value or '', r, c) elif kind in (mm_cfg.EmailList, mm_cfg.EmailListEx): if params: r, c = params else: r, c = None, None - # Ensure value is a string, decoding bytes if necessary - if isinstance(value, bytes): - value = value.decode(Utils.GetCharSet(mlist.preferred_language), 'replace') res = NL.join(value) return TextArea(varname, res, r, c, wrap='off') elif kind == mm_cfg.FileUpload: @@ -801,8 +727,8 @@ def get_item_gui_value(mlist, category, kind, varname, params, extra): if params: values, legend, selected = params else: - values = mlist.available_languages - legend = [Utils.GetLanguageDescr(lang) for lang in values] + values = mlist.GetAvailableLanguages() + legend = list(map(_, list(map(Utils.GetLanguageDescr, values)))) selected = values.index(mlist.preferred_language) return SelectOptions(varname, values, legend, selected) elif kind == mm_cfg.Topics: @@ -820,13 +746,11 @@ def makebox(i, name, pattern, desc, empty=False, table=table): addtag = 'topic_add_%02d' % i newtag = 'topic_new_%02d' % i if empty: - topic_text = _('Topic %(i)d') % {'i': i} - table.AddRow([Center(Bold(topic_text)), - Hidden(newtag)]) + table.AddRow([Center(Bold(_('Topic %(i)d'))), + Hidden(newtag)]) else: - topic_text = _('Topic %(i)d') % {'i': i} - table.AddRow([Center(Bold(topic_text)), - SubmitButton(deltag, _('Delete'))]) + table.AddRow([Center(Bold(_('Topic %(i)d'))), + SubmitButton(deltag, _('Delete'))]) table.AddRow([Label(_('Topic name:')), TextBox(boxtag, value=name, size=30)]) table.AddRow([Label(_('Regexp:')), @@ -843,17 +767,11 @@ def makebox(i, name, pattern, desc, empty=False, table=table): selected=1), ]) table.AddRow(['
      ']) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, role='cell') + table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) # Now for each element in the existing data, create a widget i = 1 data = getattr(mlist, varname) for name, pattern, desc, empty in data: - if isinstance(name, bytes): - name = name.decode(Utils.GetCharSet(mlist.preferred_language), 'replace') - if isinstance(pattern, bytes): - pattern = pattern.decode(Utils.GetCharSet(mlist.preferred_language), 'replace') - if isinstance(desc, bytes): - desc = desc.decode(Utils.GetCharSet(mlist.preferred_language), 'replace') makebox(i, name, pattern, desc, empty) i += 1 # Add one more non-deleteable widget as the first blank entry, but @@ -878,36 +796,46 @@ def makebox(i, pattern, action, empty=False, table=table): uptag = 'hdrfilter_up_%02d' % i downtag = 'hdrfilter_down_%02d' % i if empty: - table.AddRow([Center(Bold(_('Spam Filter Rule %(i)d') % {'i': i})), + table.AddRow([Center(Bold(_('Spam Filter Rule %(i)d'))), Hidden(newtag)]) else: - table.AddRow([Center(Bold(_('Spam Filter Rule %(i)d') % {'i': i})), + table.AddRow([Center(Bold(_('Spam Filter Rule %(i)d'))), SubmitButton(deltag, _('Delete'))]) table.AddRow([Label(_('Spam Filter Regexp:')), TextArea(reboxtag, text=pattern, rows=4, cols=30, wrap='off')]) values = [mm_cfg.DEFER, mm_cfg.HOLD, mm_cfg.REJECT, mm_cfg.DISCARD, mm_cfg.ACCEPT] - legends = [_('Defer'), _('Hold'), _('Reject'), - _('Discard'), _('Accept')] - table.AddRow([Label(_('Action:')), - SelectOptions(actiontag, values, legends, - selected=values.index(action))]) + try: + checked = values.index(action) + except ValueError: + checked = 0 + radio = RadioButtonArray( + actiontag, + (_('Defer'), _('Hold'), _('Reject'), + _('Discard'), _('Accept')), + values=values, + checked=checked).Format() + table.AddRow([Label(_('Action:')), radio]) if not empty: - table.AddRow([SubmitButton(addtag, _('Add new rule...')), + table.AddRow([SubmitButton(addtag, _('Add new item...')), SelectOptions(wheretag, ('before', 'after'), (_('...before this one.'), _('...after this one.')), selected=1), ]) + # BAW: IWBNI we could disable the up and down buttons for the + # first and last item respectively, but it's not easy to know + # which is the last item, so let's not worry about that for + # now. + table.AddRow([SubmitButton(uptag, _('Move rule up')), + SubmitButton(downtag, _('Move rule down'))]) table.AddRow(['
      ']) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, role='cell') + table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) # Now for each element in the existing data, create a widget i = 1 data = getattr(mlist, varname) for pattern, action, empty in data: - if isinstance(pattern, bytes): - pattern = pattern.decode(Utils.GetCharSet(mlist.preferred_language), 'replace') makebox(i, pattern, action, empty) i += 1 # Add one more non-deleteable widget as the first blank entry, but @@ -920,96 +848,99 @@ def makebox(i, pattern, action, empty=False, table=table): else: assert 0, 'Bad gui widget type: %s' % kind + + def get_item_gui_description(mlist, category, subcat, varname, descr, elaboration, detailsp): # Return the item's description, with link to details. + # + # Details are not included if this is a VARHELP page, because that /is/ + # the details page! if detailsp: if subcat: - varhelp = '/?VARHELP=%(category)s/%(subcat)s/%(varname)s' % { - 'category': category, - 'subcat': subcat, - 'varname': varname - } + varhelp = '/?VARHELP=%s/%s/%s' % (category, subcat, varname) else: - varhelp = '/?VARHELP=%(category)s/%(varname)s' % { - 'category': category, - 'varname': varname - } + varhelp = '/?VARHELP=%s/%s' % (category, varname) if descr == elaboration: - linktext = _('
      (Edit %(varname)s)') % { - 'varname': varname - } + linktext = _(f'
      (Edit {varname})') else: - linktext = _('
      (Details for %(varname)s)') % { - 'varname': varname - } + linktext = _(f'
      (Details for {varname})') link = Link(mlist.GetScriptURL('admin') + varhelp, linktext).Format() - text = Label('%(descr)s %(link)s' % { - 'descr': descr, - 'link': link - }).Format() + text = Label('%s %s' % (descr, link)).Format() else: text = Label(descr).Format() if varname[0] == '_': - text += Label(_('
      Note: setting this value performs an immediate action but does not modify permanent state.')).Format() + text += Label(_(f'''
      Note: + setting this value performs an immediate action but does not modify + permanent state.''')).Format() return text + + def membership_options(mlist, subcat, cgidata, doc, form): # Show the main stuff adminurl = mlist.GetScriptURL('admin', absolute=1) container = Container() header = Table(width="100%") - - # Add standard headers based on subcat + # If we're in the list subcategory, show the membership list if subcat == 'add': - title = _('Mass Subscriptions') - elif subcat == 'remove': - title = _('Mass Removals') - elif subcat == 'change': - title = _('Address Change') - elif subcat == 'sync': - title = _('Sync Membership List') - else: - title = _('Membership List') - - add_standard_headers(doc, mlist, title, 'members', subcat) - + header.AddRow([Center(Header(2, _('Mass Subscriptions')))]) + header.AddCellInfo(header.GetCurrentRowIndex(), 0, colspan=2, + bgcolor=mm_cfg.WEB_HEADER_COLOR) + container.AddItem(header) + mass_subscribe(mlist, container) + return container + if subcat == 'remove': + header.AddRow([Center(Header(2, _('Mass Removals')))]) + header.AddCellInfo(header.GetCurrentRowIndex(), 0, colspan=2, + bgcolor=mm_cfg.WEB_HEADER_COLOR) + container.AddItem(header) + mass_remove(mlist, container) + return container + if subcat == 'change': + header.AddRow([Center(Header(2, _('Address Change')))]) + header.AddCellInfo(header.GetCurrentRowIndex(), 0, colspan=2, + bgcolor=mm_cfg.WEB_HEADER_COLOR) + container.AddItem(header) + address_change(mlist, container) + return container + if subcat == 'sync': + header.AddRow([Center(Header(2, _('Sync Membership List')))]) + header.AddCellInfo(header.GetCurrentRowIndex(), 0, colspan=2, + bgcolor=mm_cfg.WEB_HEADER_COLOR) + container.AddItem(header) + mass_sync(mlist, container) + return container + # Otherwise... + header.AddRow([Center(Header(2, _('Membership List')))]) + header.AddCellInfo(header.GetCurrentRowIndex(), 0, colspan=2, + bgcolor=mm_cfg.WEB_HEADER_COLOR) + container.AddItem(header) # Add a "search for member" button table = Table(width='100%') - link = Link('https://docs.python.org/3/library/re.html' + link = Link('https://docs.python.org/2/library/re.html' '#regular-expression-syntax', _('(help)')).Format() - table.AddRow([Label(_('Find member %(link)s:') % {'link': link}), + table.AddRow([Label(_(f'Find member {link}:')), TextBox('findmember', - value=cgidata.get('findmember', [''])[0]), + value=cgidata.getfirst('findmember', '')), SubmitButton('findmember_btn', _('Search...'))]) container.AddItem(table) container.AddItem('

      ') usertable = Table(width="90%", border='2') + # If there are more members than allowed by chunksize, then we split the + # membership up alphabetically. Otherwise just display them all. + chunksz = mlist.admin_member_chunksize # The email addresses had /better/ be ASCII, but might be encoded in the # database as Unicodes. - all = [] - for _m in mlist.getMembers(): - try: - # Verify the member still exists - mlist.getMemberName(_m) - # Decode the email address as latin-1 - if isinstance(_m, bytes): - _m = _m.decode('latin-1') - all.append(_m) - except Errors.NotAMemberError: - # Skip addresses that are no longer members - continue - all.sort(key=lambda x: x.lower()) + all = mlist.getMembers() + all.sort() # See if the query has a regular expression - regexp = cgidata.get('findmember', [''])[0] - if isinstance(regexp, bytes): - regexp = regexp.decode('latin1', 'replace') - regexp = regexp.strip() + regexp = cgidata.getfirst('findmember', '').strip() try: - if isinstance(regexp, bytes): - regexp = regexp.decode(Utils.GetCharSet(mlist.preferred_language)) + regexp = regexp.encode() + regexp = regexp.decode(Utils.GetCharSet(mlist.preferred_language)) except UnicodeDecodeError: # This is probably a non-ascii character and an English language # (ascii) list. Even if we didn't throw the UnicodeDecodeError, @@ -1021,27 +952,16 @@ def membership_options(mlist, subcat, cgidata, doc, form): try: cre = re.compile(regexp, re.IGNORECASE) except re.error: - doc.addError(_('Bad regular expression: %(regexp)s') % {'regexp': regexp}) + doc.addError(_('Bad regular expression: ') + regexp) else: # BAW: There's got to be a more efficient way of doing this! - names = [] - valid_members = [] - for addr in all: - try: - name = mlist.getMemberName(addr) or '' - if isinstance(name, bytes): - name = name.decode('latin-1', 'replace') - names.append(name) - valid_members.append(addr) - except Errors.NotAMemberError: - # Skip addresses that are no longer members - continue - all = [a for n, a in zip(names, valid_members) + names = [mlist.getMemberName(s) or '' for s in all] + all = [a for n, a in zip(names, all) if cre.search(n) or cre.search(a)] chunkindex = None bucket = None actionurl = None - if len(all) < mlist.admin_member_chunksize: + if len(all) < chunksz: members = all else: # Split them up alphabetically, and then split the alphabetical @@ -1064,14 +984,11 @@ def membership_options(mlist, subcat, cgidata, doc, form): if not bucket or bucket not in buckets: bucket = keys[0] members = buckets[bucket] - action = '%(adminurl)s/members?letter=%(bucket)s' % { - 'adminurl': adminurl, - 'bucket': bucket - } - if len(members) <= mlist.admin_member_chunksize: + action = adminurl + '/members?letter=%s' % bucket + if len(members) <= chunksz: form.set_action(action) else: - i, r = divmod(len(members), mlist.admin_member_chunksize) + i, r = divmod(len(members), chunksz) numchunks = i + (not not r * 1) # Now chunk them up chunkindex = 0 @@ -1082,23 +999,17 @@ def membership_options(mlist, subcat, cgidata, doc, form): chunkindex = 0 if chunkindex < 0 or chunkindex > numchunks: chunkindex = 0 - members = members[chunkindex*mlist.admin_member_chunksize:(chunkindex+1)*mlist.admin_member_chunksize] + members = members[chunkindex*chunksz:(chunkindex+1)*chunksz] # And set the action URL - form.set_action('%(action)s&chunk=%(chunkindex)s' % { - 'action': action, - 'chunkindex': chunkindex - }) + form.set_action(action + '&chunk=%s' % chunkindex) # So now members holds all the addresses we're going to display allcnt = len(all) if bucket: membercnt = len(members) - count_text = _('%(allcnt)d members total, %(membercnt)d shown') % { - 'allcnt': len(all), 'membercnt': len(members)} - usertable.AddRow([Center(Italic(count_text))]) + usertable.AddRow([Center(Italic(_( + f'{allcnt} members total, {membercnt} shown')))]) else: - usertable.AddRow([Center(Italic(_('%(allcnt)d members total') % { - 'allcnt': len(all) - }))]) + usertable.AddRow([Center(Italic(_(f'{allcnt} members total')))]) usertable.AddCellInfo(usertable.GetCurrentRowIndex(), usertable.GetCurrentCellIndex(), colspan=OPTCOLUMNS, @@ -1110,23 +1021,12 @@ def membership_options(mlist, subcat, cgidata, doc, form): findfrag = '' if regexp: findfrag = '&findmember=' + urllib.parse.quote(regexp) - url = '%(adminurl)s/members?letter=%(letter)s%(findfrag)s' % { - 'adminurl': adminurl, - 'letter': letter, - 'findfrag': findfrag - } - if isinstance(url, str): - url = url.encode(Utils.GetCharSet(mlist.preferred_language), - errors='ignore') + url = adminurl + '/members?letter=' + letter + findfrag if letter == bucket: - # Do this in two steps to get it to work properly with the - # translatable title. - formatted_text = '[%s]' % letter.upper() - text = Bold(formatted_text).Format() + show = Bold('[%s]' % letter.upper()).Format() else: - formatted_label = '[%s]' % letter.upper() - text = Link(url, Bold(formatted_label)).Format() - cells.append(text) + show = letter.upper() + cells.append(Link(url, show).Format()) joiner = ' '*2 + '\n' usertable.AddRow([Center(joiner.join(cells))]) usertable.AddCellInfo(usertable.GetCurrentRowIndex(), @@ -1147,20 +1047,9 @@ def membership_options(mlist, subcat, cgidata, doc, form): # Find the longest name in the list longest = 0 if members: - names = [] - for addr in members: - try: - name = mlist.getMemberName(addr) or '' - if isinstance(name, bytes): - name = name.decode('latin-1', 'replace') - if name: - names.append(name) - except Errors.NotAMemberError: - # Skip addresses that are no longer members - continue + names = [_f for _f in [mlist.getMemberName(s) for s in members] if _f] # Make the name field at least as long as the longest email address - if names: - longest = max([len(s) for s in names + members]) + longest = max([len(s) for s in names + members]) # Abbreviations for delivery status details ds_abbrevs = {MemberAdaptor.UNKNOWN : _('?'), MemberAdaptor.BYUSER : _('U'), @@ -1169,47 +1058,18 @@ def membership_options(mlist, subcat, cgidata, doc, form): } # Now populate the rows for addr in members: - try: - if isinstance(addr, bytes): - addr = addr.decode('latin-1') - qaddr = urllib.parse.quote(addr) - link = Link(mlist.GetOptionsURL(addr, obscure=1), - mlist.getMemberCPAddress(addr)) - fullname = mlist.getMemberName(addr) - if isinstance(fullname, bytes): - try: - # Try Latin-1 first since that's what we're seeing in the data - fullname = fullname.decode('latin-1', 'replace') - except UnicodeDecodeError: - # Fall back to UTF-8 if Latin-1 fails - fullname = fullname.decode('utf-8', 'replace') - # Remove any b'...' prefix if it exists - if fullname.startswith("b'") and fullname.endswith("'"): - fullname = fullname[2:-1] - fullname = Utils.uncanonstr(fullname, mlist.preferred_language) - name = TextBox('%(qaddr)s_realname' % {'qaddr': qaddr}, fullname, size=longest).Format() - cells = [Center(CheckBox('%(qaddr)s_unsub' % {'qaddr': qaddr}, 'off', 0).Format() + qaddr = urllib.parse.quote(addr) + link = Link(mlist.GetOptionsURL(addr, obscure=1), + mlist.getMemberCPAddress(addr)) + fullname = Utils.uncanonstr(mlist.getMemberName(addr), + mlist.preferred_language) + name = TextBox(qaddr + '_realname', fullname, size=longest).Format() + cells = [Center(CheckBox(qaddr + '_unsub', 'off', 0).Format() + '

      '), - link.Format() + '
      ' + - name + - Hidden('user', qaddr).Format(), - ] - except Errors.NotAMemberError: - # Skip addresses that are no longer members - continue - - digest_name = '%(qaddr)s_digest' % {'qaddr': qaddr} - if addr not in mlist.getRegularMemberKeys(): - cells.append(Center(CheckBox(digest_name, 'off', 0).Format())) - else: - cells.append(Center(CheckBox(digest_name, 'on', 1).Format())) - - language_name = '%(qaddr)s_language' % {'qaddr': qaddr} - languages = mlist.available_languages - legends = [Utils.GetLanguageDescr(lang) for lang in languages] - cells.append(Center(SelectOptions(language_name, languages, legends, - selected=mlist.getMemberLanguage(addr)).Format())) - + link.Format() + '
      ' + + name + + Hidden('user', qaddr).Format(), + ] # Do the `mod' option if mlist.getMemberOption(addr, mm_cfg.Moderate): value = 'on' @@ -1217,7 +1077,7 @@ def membership_options(mlist, subcat, cgidata, doc, form): else: value = 'off' checked = 0 - box = CheckBox('%(qaddr)s_mod' % {'qaddr': qaddr}, value, checked) + box = CheckBox('%s_mod' % qaddr, value, checked) cells.append(Center(box.Format() + '')) # Kluge, get these translated. @@ -1232,29 +1092,59 @@ def membership_options(mlist, subcat, cgidata, doc, form): else: value = 'on' checked = 1 - extra = '[%(abbrev)s]' % {'abbrev': ds_abbrevs[status]} + extra + extra = '[%s]' % ds_abbrevs[status] + extra elif mlist.getMemberOption(addr, mm_cfg.OPTINFO[opt]): value = 'on' checked = 1 else: value = 'off' checked = 0 - box = CheckBox('%(qaddr)s_%(opt)s' % {'qaddr': qaddr, 'opt': opt}, value, checked) + box = CheckBox('%s_%s' % (qaddr, opt), value, checked) cells.append(Center(box.Format() + extra)) + # This code is less efficient than the original which did a has_key on + # the underlying dictionary attribute. This version is slower and + # less memory efficient. It points to a new MemberAdaptor interface + # method. + extra = '' + if addr in mlist.getRegularMemberKeys(): + cells.append(Center(CheckBox(qaddr + '_digest', 'off', 0).Format() + + extra)) + else: + cells.append(Center(CheckBox(qaddr + '_digest', 'on', 1).Format() + + extra)) + if mlist.getMemberOption(addr, mm_cfg.OPTINFO['plain']): + value = 'on' + checked = 1 + else: + value = 'off' + checked = 0 + cells.append(Center(CheckBox( + '%s_plain' % qaddr, value, checked).Format() + + '')) + # User's preferred language + langpref = mlist.getMemberLanguage(addr) + langs = mlist.GetAvailableLanguages() + langdescs = [_(Utils.GetLanguageDescr(lang)) for lang in langs] + try: + selected = langs.index(langpref) + except ValueError: + selected = 0 + cells.append(Center(SelectOptions(qaddr + '_language', langs, + langdescs, selected)).Format()) usertable.AddRow(cells) # Add the usertable and a legend legend = UnorderedList() legend.AddItem( _('unsub -- Click on this to unsubscribe the member.')) legend.AddItem( - _('''mod -- The user's personal moderation flag. If this is + _(f"""mod -- The user's personal moderation flag. If this is set, postings from them will be moderated, otherwise they will be - approved.''')) + approved.""")) legend.AddItem( - _('''hide -- Is the member's address concealed on - the list of subscribers?''')) + _(f"""hide -- Is the member's address concealed on + the list of subscribers?""")) legend.AddItem(_( - '''nomail -- Is delivery to the member disabled? If so, an + """nomail -- Is delivery to the member disabled? If so, an abbreviation will be given describing the reason for the disabled delivery:
      • U -- Delivery was disabled by the user via their @@ -1266,21 +1156,21 @@ def membership_options(mlist, subcat, cgidata, doc, form):
      • ? -- The reason for disabled delivery isn't known. This is the case for all memberships which were disabled in older versions of Mailman. -
      ''')) +
    """)) legend.AddItem( - _('''ack -- Does the member get acknowledgements of their + _(f'''ack -- Does the member get acknowledgements of their posts?''')) legend.AddItem( - _('''not metoo -- Does the member want to avoid copies of their + _(f'''not metoo -- Does the member want to avoid copies of their own postings?''')) legend.AddItem( - _('''nodupes -- Does the member want to avoid duplicates of the + _(f'''nodupes -- Does the member want to avoid duplicates of the same message?''')) legend.AddItem( - _('''digest -- Does the member get messages in digests? + _(f'''digest -- Does the member get messages in digests? (otherwise, individual messages)''')) legend.AddItem( - _('''plain -- If getting digests, does the member get plain + _(f'''plain -- If getting digests, does the member get plain text digests? (otherwise, MIME)''')) legend.AddItem(_("language -- Language preferred by the user")) addlegend = '' @@ -1288,7 +1178,7 @@ def membership_options(mlist, subcat, cgidata, doc, form): qsenviron = os.environ.get('QUERY_STRING') if qsenviron: qs = urllib.parse.parse_qs(qsenviron).get('legend') - if qs and isinstance(qs, list): + if qs and type(qs) is list: qs = qs[0] if qs == 'yes': addlegend = 'legend=yes&' @@ -1306,38 +1196,25 @@ def membership_options(mlist, subcat, cgidata, doc, form): # There may be additional chunks if chunkindex is not None: buttons = [] - url = '%(adminurl)s/members?%(addlegend)sletter=%(bucket)s&' % { - 'adminurl': adminurl, - 'addlegend': addlegend, - 'bucket': bucket - } - footer = _('''

    To view more members, click on the appropriate + url = adminurl + '/members?%sletter=%s&' % (addlegend, bucket) + footer = _(f'''

    To view more members, click on the appropriate range listed below:''') chunkmembers = buckets[bucket] last = len(chunkmembers) for i in range(numchunks): if i == chunkindex: continue - start = chunkmembers[i*mlist.admin_member_chunksize] - end = chunkmembers[min((i+1)*mlist.admin_member_chunksize, last)-1] - thisurl = '%(url)schunk=%(i)d%(findfrag)s' % { - 'url': url, - 'i': i, - 'findfrag': findfrag - } - if isinstance(thisurl, str): - thisurl = thisurl.encode( - Utils.GetCharSet(mlist.preferred_language), - errors='ignore') - link = Link(thisurl, _('from %(start)s to %(end)s') % { - 'start': start, - 'end': end - }) + start = chunkmembers[i*chunksz] + end = chunkmembers[min((i+1)*chunksz, last)-1] + thisurl = url + 'chunk=%d' % i + findfrag + link = Link(thisurl, _(f'from {start} to {end}')) buttons.append(link) buttons = UnorderedList(*buttons) container.AddItem(footer + buttons.Format() + '

    ') return container + + def mass_subscribe(mlist, container): # MASS SUBSCRIBE GREY = mm_cfg.WEB_ADMINITEM_COLOR @@ -1365,7 +1242,7 @@ def mass_subscribe(mlist, container): RadioButtonArray('send_notifications_to_list_owner', (_('No'), _('Yes')), mlist.admin_notify_mchanges, - values=(0, 1)) + values=(0,1)) ]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, bgcolor=GREY) table.AddCellInfo(table.GetCurrentRowIndex(), 1, bgcolor=GREY) @@ -1387,6 +1264,8 @@ def mass_subscribe(mlist, container): rows=10, cols='70%', wrap=None))]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) + + def mass_remove(mlist, container): # MASS UNSUBSCRIBE GREY = mm_cfg.WEB_ADMINITEM_COLOR @@ -1417,6 +1296,8 @@ def mass_remove(mlist, container): FileUpload('unsubscribees_upload', cols='50')]) container.AddItem(Center(table)) + + def address_change(mlist, container): # ADDRESS CHANGE GREY = mm_cfg.WEB_ADMINITEM_COLOR @@ -1447,6 +1328,8 @@ def address_change(mlist, container): table.AddCellInfo(table.GetCurrentRowIndex(), 2, bgcolor=GREY) container.AddItem(Center(table)) + + def mass_sync(mlist, container): # MASS SYNC table = Table(width='90%') @@ -1459,6 +1342,8 @@ def mass_sync(mlist, container): FileUpload('memberlist_upload', cols='50')]) container.AddItem(Center(table)) + + def password_inputs(mlist): adminurl = mlist.GetScriptURL('admin', absolute=1) table = Table(cellspacing=3, cellpadding=4) @@ -1515,63 +1400,418 @@ def password_inputs(mlist): table.AddRow([ptable]) return table + + def submit_button(name='submit'): table = Table(border=0, cellspacing=0, cellpadding=2) table.AddRow([Bold(SubmitButton(name, _('Submit Your Changes')))]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, align='middle') return table + + def change_options(mlist, category, subcat, cgidata, doc): - """Change the list's options.""" - try: - # Get the configuration categories - config_categories = mlist.GetConfigCategories() - - # Validate category exists - if category not in config_categories: - mailman_log('error', 'Invalid configuration category: %s', category) - doc.AddItem(mlist.ParseTags('adminerror.html', - {'error': 'Invalid configuration category'}, - mlist.preferred_language)) - return - - # Get the category object and validate it - category_obj = config_categories[category] - - if not hasattr(category_obj, 'items'): - mailman_log('error', 'Configuration category %s is invalid: %s', - category, str(type(category_obj))) - doc.AddItem(mlist.ParseTags('adminerror.html', - {'error': 'Invalid configuration category structure'}, - mlist.preferred_language)) + global _ + def safeint(formvar, defaultval=None): + try: + return int(cgidata.getfirst(formvar)) + except (ValueError, TypeError): + return defaultval + confirmed = 0 + # Handle changes to the list moderator password. Do this before checking + # the new admin password, since the latter will force a reauthentication. + new = cgidata.getfirst('newmodpw', '').strip() + confirm = cgidata.getfirst('confirmmodpw', '').strip() + if new or confirm: + if new == confirm: + mlist.mod_password = sha_new(new.encode()).hexdigest() + # No re-authentication necessary because the moderator's + # password doesn't get you into these pages. + else: + doc.addError(_('Moderator passwords did not match')) + # Handle changes to the list poster password. Do this before checking + # the new admin password, since the latter will force a reauthentication. + new = cgidata.getfirst('newpostpw', '').strip() + confirm = cgidata.getfirst('confirmpostpw', '').strip() + if new or confirm: + if new == confirm: + mlist.post_password = sha_new(new.encode()).hexdigest() + # No re-authentication necessary because the poster's + # password doesn't get you into these pages. + else: + doc.addError(_('Poster passwords did not match')) + # Handle changes to the list administrator password + new = cgidata.getfirst('newpw', '').strip() + confirm = cgidata.getfirst('confirmpw', '').strip() + if new or confirm: + if new == confirm: + mlist.password = sha_new(new.encode()).hexdigest() + # Set new cookie + print(mlist.MakeCookie(mm_cfg.AuthListAdmin)) + else: + doc.addError(_('Administrator passwords did not match')) + # Give the individual gui item a chance to process the form data + categories = mlist.GetConfigCategories() + label, gui = categories[category] + # BAW: We handle the membership page special... for now. + if category != 'members': + gui.handleForm(mlist, category, subcat, cgidata, doc) + # mass subscription, removal processing for members category + subscribers = '' + subscribers += str(cgidata.getfirst('subscribees', '')) + sub_uploads = cgidata.getfirst('subscribees_upload', '') + if isinstance(sub_uploads, bytes): + sub_uploads = sub_uploads.decode() + subscribers += sub_uploads + if subscribers: + entries = [_f for _f in [n.strip() for n in subscribers.splitlines()] if _f] + send_welcome_msg = safeint('send_welcome_msg_to_this_batch', + mlist.send_welcome_msg) + send_admin_notif = safeint('send_notifications_to_list_owner', + mlist.admin_notify_mchanges) + # Default is to subscribe + subscribe_or_invite = safeint('subscribe_or_invite', 0) + invitation = cgidata.getfirst('invitation', '') + digest = mlist.digest_is_default + if not mlist.digestable: + digest = 0 + if not mlist.nondigestable: + digest = 1 + subscribe_errors = [] + subscribe_success = [] + # Now cruise through all the subscribees and do the deed. BAW: we + # should limit the number of "Successfully subscribed" status messages + # we display. Try uploading a file with 10k names -- it takes a while + # to render the status page. + for entry in entries: + safeentry = Utils.websafe(entry) + fullname, address = parseaddr(entry) + # Canonicalize the full name + fullname = Utils.canonstr(fullname, mlist.preferred_language) + userdesc = UserDesc(address, fullname, + Utils.MakeRandomPassword(), + digest, mlist.preferred_language) + try: + if subscribe_or_invite: + if mlist.isMember(address): + raise Errors.MMAlreadyAMember + else: + mlist.InviteNewMember(userdesc, invitation) + else: + _ = D_ + whence = _('admin mass sub') + _ = i18n._ + mlist.ApprovedAddMember(userdesc, send_welcome_msg, + send_admin_notif, invitation, + whence=whence) + except Errors.MMAlreadyAMember: + subscribe_errors.append((safeentry, _('Already a member'))) + except Errors.MMBadEmailError: + if userdesc.address == '': + subscribe_errors.append((_('<blank line>'), + _('Bad/Invalid email address'))) + else: + subscribe_errors.append((safeentry, + _('Bad/Invalid email address'))) + except Errors.MMHostileAddress: + subscribe_errors.append( + (safeentry, _('Hostile address (illegal characters)'))) + except Errors.MembershipIsBanned as pattern: + subscribe_errors.append( + (safeentry, _(f'Banned address (matched {pattern})'))) + else: + member = Utils.uncanonstr(formataddr((fullname, address))) + subscribe_success.append(Utils.websafe(member)) + if subscribe_success: + if subscribe_or_invite: + doc.AddItem(Header(5, _('Successfully invited:'))) + else: + doc.AddItem(Header(5, _('Successfully subscribed:'))) + doc.AddItem(UnorderedList(*subscribe_success)) + doc.AddItem('

    ') + if subscribe_errors: + if subscribe_or_invite: + doc.AddItem(Header(5, _('Error inviting:'))) + else: + doc.AddItem(Header(5, _('Error subscribing:'))) + items = ['%s -- %s' % (x0, x1) for x0, x1 in subscribe_errors] + doc.AddItem(UnorderedList(*items)) + doc.AddItem('

    ') + # Unsubscriptions + removals = '' + if 'unsubscribees' in cgidata: + removals += cgidata['unsubscribees'].value + if 'unsubscribees_upload' in cgidata and \ + cgidata['unsubscribees_upload'].value: + unsub_upload = cgidata['unsubscribees_upload'].value + if isinstance(unsub_upload, bytes): + unsub_upload = unsub_upload.decode() + removals += unsub_upload + if removals: + names = [_f for _f in [n.strip() for n in removals.splitlines()] if _f] + send_unsub_notifications = safeint( + 'send_unsub_notifications_to_list_owner', + mlist.admin_notify_mchanges) + userack = safeint( + 'send_unsub_ack_to_this_batch', + mlist.send_goodbye_msg) + unsubscribe_errors = [] + unsubscribe_success = [] + for addr in names: + try: + _ = D_ + whence = _('admin mass unsub') + _ = i18n._ + mlist.ApprovedDeleteMember( + addr, whence=whence, + admin_notif=send_unsub_notifications, + userack=userack) + unsubscribe_success.append(Utils.websafe(addr)) + except Errors.NotAMemberError: + unsubscribe_errors.append(Utils.websafe(addr)) + if unsubscribe_success: + doc.AddItem(Header(5, _('Successfully Unsubscribed:'))) + doc.AddItem(UnorderedList(*unsubscribe_success)) + doc.AddItem('

    ') + if unsubscribe_errors: + doc.AddItem(Header(3, Bold(FontAttr( + _('Cannot unsubscribe non-members:'), + color='#ff0000', size='+2')).Format())) + doc.AddItem(UnorderedList(*unsubscribe_errors)) + doc.AddItem('

    ') + # Address Changes + if 'change_from' in cgidata: + change_from = cgidata.getfirst('change_from', '') + change_to = cgidata.getfirst('change_to', '') + schange_from = Utils.websafe(change_from) + schange_to = Utils.websafe(change_to) + success = False + msg = None + if not (change_from and change_to): + msg = _('You must provide both current and new addresses.') + elif change_from == change_to: + msg = _('Current and new addresses must be different.') + elif mlist.isMember(change_to): + # ApprovedChangeMemberAddress will just delete the old address + # and we don't want that here. + msg = _(f'{schange_to} is already a list member.') + else: + try: + Utils.ValidateEmail(change_to) + except (Errors.MMBadEmailError, Errors.MMHostileAddress): + msg = _(f'{schange_to} is not a valid email address.') + if msg: + doc.AddItem(Header(3, msg)) + doc.AddItem('

    ') return - - # Process each item in the category - for item in category_obj.items: + try: + mlist.ApprovedChangeMemberAddress(change_from, change_to, False) + except Errors.NotAMemberError: + msg = _(f'{schange_from} is not a member') + except Errors.MMAlreadyAMember: + msg = _(f'{schange_to} is already a member') + except Errors.MembershipIsBanned as pat: + spat = Utils.websafe(str(pat)) + msg = _(f'{schange_to} matches banned pattern {spat}') + else: + msg = _(f'Address {schange_from} changed to {schange_to}') + success = True + doc.AddItem(Header(3, msg)) + lang = mlist.getMemberLanguage(change_to) + otrans = i18n.get_translation() + i18n.set_language(lang) + list_name = mlist.getListAddress() + text = Utils.wrap(_(f"""The member address {change_from} on the +{list_name} list has been changed to {change_to}. +""")) + subject = _(f'{list_name} address change notice.') + i18n.set_translation(otrans) + if success and cgidata.getfirst('notice_old', '') == 'yes': + # Send notice to old address. + msg = Message.UserNotification(change_from, + mlist.GetOwnerEmail(), + text=text, + subject=subject, + lang=lang + ) + msg.send(mlist) + doc.AddItem(Header(3, _(f'Notification sent to {schange_from}.'))) + if success and cgidata.getfirst('notice_new', '') == 'yes': + # Send notice to new address. + msg = Message.UserNotification(change_to, + mlist.GetOwnerEmail(), + text=text, + subject=subject, + lang=lang + ) + msg.send(mlist) + doc.AddItem(Header(3, _(f'Notification sent to {schange_to}.'))) + doc.AddItem('

    ') + + # sync operation + memberlist = '' + memberlist += cgidata.getvalue('memberlist', '') + upload = cgidata.getvalue('memberlist_upload', '') + if isinstance(upload, bytes): + upload = upload.decode() + memberlist += upload + if memberlist: + # Browsers will convert special characters in the text box to HTML + # entities. We need to fix those. + def i_to_c(mo): + # Convert a matched string of digits to the corresponding unicode. + return chr(int(mo.group(1))) + def clean_input(x): + # Strip leading/trailing whitespace and convert numeric HTML + # entities. + return re.sub(r'&#(\d+);', i_to_c, x.strip()) + entries = [_f for _f in [clean_input(n) for n in memberlist.splitlines()] if _f] + lc_addresses = [parseaddr(x)[1].lower() for x in entries + if parseaddr(x)[1]] + subscribe_errors = [] + subscribe_success = [] + # First we add all the addresses that should be added to the list. + for entry in entries: + safeentry = Utils.websafe(entry) + fullname, address = parseaddr(entry) + if mlist.isMember(address): + continue + # Canonicalize the full name. + fullname = Utils.canonstr(fullname, mlist.preferred_language) + userdesc = UserDesc(address, fullname, + Utils.MakeRandomPassword(), + 0, mlist.preferred_language) try: - # Get the item's value from the form data - value = cgidata.get(item.name, None) - if value is None: - continue - - # Set the item's value - item.set(mlist, value) - - except Exception as e: - mailman_log('error', 'Error setting %s.%s: %s', - category, item.name, str(e)) - doc.AddItem(mlist.ParseTags('adminerror.html', - {'error': 'Error setting %s: %s' % - (item.name, str(e))}, - mlist.preferred_language)) - return - - # Save the changes - mlist.Save() - - except Exception as e: - mailman_log('error', 'Error in change_options: %s\n%s', - str(e), traceback.format_exc()) - doc.AddItem(mlist.ParseTags('adminerror.html', - {'error': 'Internal error: %s' % str(e)}, - mlist.preferred_language)) + # Add a member if not yet member. + mlist.ApprovedAddMember(userdesc, 0, 0, 0, + whence='admin sync members') + except Errors.MMBadEmailError: + if userdesc.address == '': + subscribe_errors.append((_('<blank line>'), + _('Bad/Invalid email address'))) + else: + subscribe_errors.append((safeentry, + _('Bad/Invalid email address'))) + except Errors.MMHostileAddress: + subscribe_errors.append( + (safeentry, _('Hostile address (illegal characters)'))) + except Errors.MembershipIsBanned as pattern: + subscribe_errors.append( + (safeentry, _(f'Banned address (matched {pattern})'))) + else: + member = Utils.uncanonstr(formataddr((fullname, address))) + subscribe_success.append(Utils.websafe(member)) + + # Then we remove the addresses not in our list. + unsubscribe_errors = [] + unsubscribe_success = [] + + for entry in mlist.getMembers(): + # If an entry is not found in the uploaded "entries" list, then + # remove the member. + if not(entry in lc_addresses): + try: + mlist.ApprovedDeleteMember(entry, 0, 0) + 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(entry) + else: + unsubscribe_success.append(Utils.websafe(entry)) + + if subscribe_success: + doc.AddItem(Header(5, _('Successfully subscribed:'))) + doc.AddItem(UnorderedList(*subscribe_success)) + doc.AddItem('

    ') + if subscribe_errors: + doc.AddItem(Header(5, _('Error subscribing:'))) + items = ['%s -- %s' % (x0, x1) for x0, x1 in subscribe_errors] + doc.AddItem(UnorderedList(*items)) + doc.AddItem('

    ') + if unsubscribe_success: + doc.AddItem(Header(5, _('Successfully Unsubscribed:'))) + doc.AddItem(UnorderedList(*unsubscribe_success)) + doc.AddItem('

    ') + + # See if this was a moderation bit operation + if 'allmodbit_btn' in cgidata: + val = safeint('allmodbit_val') + if val not in (0, 1): + doc.addError(_('Bad moderation flag value')) + else: + for member in mlist.getMembers(): + mlist.setMemberOption(member, mm_cfg.Moderate, val) + # do the user options for members category + if 'setmemberopts_btn' in cgidata and 'user' in cgidata: + user = cgidata['user'] + if type(user) is list: + users = [] + for ui in range(len(user)): + users.append(urllib.parse.unquote(user[ui].value)) + else: + users = [urllib.parse.unquote(user.value)] + errors = [] + removes = [] + for user in users: + quser = urllib.parse.quote(user) + if '%s_unsub' % quser in cgidata: + try: + _ = D_ + whence=_('member mgt page') + _ = i18n._ + mlist.ApprovedDeleteMember(user, whence=whence) + removes.append(user) + except Errors.NotAMemberError: + errors.append((user, _('Not subscribed'))) + continue + if not mlist.isMember(user): + doc.addError(_(f'Ignoring changes to deleted member: {user}'), + tag=_('Warning: ')) + continue + value = '%s_digest' % quser in cgidata + try: + mlist.setMemberOption(user, mm_cfg.Digests, value) + except (Errors.AlreadyReceivingDigests, + Errors.AlreadyReceivingRegularDeliveries, + Errors.CantDigestError, + Errors.MustDigestError): + # BAW: Hmm... + pass + + newname = cgidata.getfirst(quser+'_realname', '') + newname = Utils.canonstr(newname, mlist.preferred_language) + mlist.setMemberName(user, newname) + + newlang = cgidata.getfirst(quser+'_language') + oldlang = mlist.getMemberLanguage(user) + if Utils.IsLanguage(newlang) and newlang != oldlang: + mlist.setMemberLanguage(user, newlang) + + moderate = not not cgidata.getfirst(quser+'_mod') + mlist.setMemberOption(user, mm_cfg.Moderate, moderate) + + # Set the `nomail' flag, but only if the user isn't already + # disabled (otherwise we might change BYUSER into BYADMIN). + if '%s_nomail' % quser in cgidata: + if mlist.getDeliveryStatus(user) == MemberAdaptor.ENABLED: + mlist.setDeliveryStatus(user, MemberAdaptor.BYADMIN) + else: + mlist.setDeliveryStatus(user, MemberAdaptor.ENABLED) + for opt in ('hide', 'ack', 'notmetoo', 'nodupes', 'plain'): + opt_code = mm_cfg.OPTINFO[opt] + if '%s_%s' % (quser, opt) in cgidata: + mlist.setMemberOption(user, opt_code, 1) + else: + mlist.setMemberOption(user, opt_code, 0) + # Give some feedback on who's been removed + if removes: + doc.AddItem(Header(5, _('Successfully Removed:'))) + doc.AddItem(UnorderedList(*removes)) + doc.AddItem('

    ') + if errors: + doc.AddItem(Header(5, _("Error Unsubscribing:"))) + items = ['%s -- %s' % (x[0], x[1]) for x in errors] + doc.AddItem(UnorderedList(*tuple((items)))) + doc.AddItem("

    ") diff --git a/Mailman/Cgi/admindb.py b/Mailman/Cgi/admindb.py index 8e551b36..730fc90f 100644 --- a/Mailman/Cgi/admindb.py +++ b/Mailman/Cgi/admindb.py @@ -22,30 +22,28 @@ from builtins import str import sys import os -import urllib.parse +from Mailman.Utils import FieldStorage +import codecs import errno import signal import email import email.errors import time -from urllib.parse import quote_plus, unquote_plus -import re -from email.iterators import body_line_iterator +from urllib.parse import quote_plus, unquote_plus, parse_qs from Mailman import mm_cfg from Mailman import Utils from Mailman import MailList from Mailman import Errors -from Mailman.Message import Message +from Mailman import Message from Mailman import i18n from Mailman.Handlers.Moderate import ModeratedMemberPost -from Mailman.ListAdmin import HELDMSG, ListAdmin, PermissionError +from Mailman.ListAdmin import HELDMSG from Mailman.ListAdmin import readMessage from Mailman.Cgi import Auth from Mailman.htmlformat import * -from Mailman.Logging.Syslog import syslog, mailman_log +from Mailman.Logging.Syslog import syslog from Mailman.CSRFcheck import csrf_check -import traceback EMPTYSTRING = '' NL = '\n' @@ -69,6 +67,7 @@ mm_cfg.AuthSiteAdmin) + def helds_by_skey(mlist, ssort=SSENDER): heldmsgs = mlist.GetHeldMessageIds() byskey = {} @@ -106,22 +105,7 @@ def hacky_radio_buttons(btnname, labels, values, defaults, spacing=3): return btns -def output_error_page(status, title, message, details=None): - doc = Document() - doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) - doc.AddItem(Header(2, _(title))) - doc.AddItem(Bold(_(message))) - if details: - doc.AddItem(Preformatted(Utils.websafe(str(details)))) - doc.AddItem(_('Please contact the site administrator.')) - return doc - - -def output_success_page(doc): - print(doc.Format()) - return - - + def main(): global ssort # Figure out which list is being requested @@ -145,24 +129,14 @@ def main(): # Now that we know which list to use, set the system's language to it. i18n.set_language(mlist.preferred_language) - # Initialize the document - doc = Document() - doc.set_language(mlist.preferred_language) - # Make sure the user is authorized to see this page. + cgidata = FieldStorage(keep_blank_values=1) try: - if os.environ.get('REQUEST_METHOD', '').lower() == 'post': - content_type = os.environ.get('CONTENT_TYPE', '') - if content_type.startswith('application/x-www-form-urlencoded'): - content_length = int(os.environ.get('CONTENT_LENGTH', 0)) - form_data = sys.stdin.buffer.read(content_length).decode('latin-1') - cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=1) - else: - raise ValueError('Invalid content type') - else: - cgidata = urllib.parse.parse_qs(os.environ.get('QUERY_STRING', ''), keep_blank_values=1) - except Exception: + cgidata.getfirst('adminpw', '') + except TypeError: # Someone crafted a POST with a bad Content-Type:. + doc = Document() + doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) doc.AddItem(Header(2, _("Error"))) doc.AddItem(Bold(_('Invalid options to CGI script.'))) # Send this with a 400 status. @@ -174,19 +148,19 @@ def main(): safe_params = ['adminpw', 'admlogin', 'msgid', 'sender', 'details'] params = list(cgidata.keys()) if set(params) - set(safe_params): - csrf_checked = csrf_check(mlist, cgidata.get('csrf_token', [''])[0], + csrf_checked = csrf_check(mlist, cgidata.getfirst('csrf_token'), 'admindb') else: csrf_checked = True # if password is present, void cookie to force password authentication. - if cgidata.get('adminpw', [''])[0]: + if cgidata.getfirst('adminpw'): os.environ['HTTP_COOKIE'] = '' csrf_checked = True if not mlist.WebAuthenticate((mm_cfg.AuthListAdmin, mm_cfg.AuthListModerator, mm_cfg.AuthSiteAdmin), - cgidata.get('adminpw', [''])[0]): + cgidata.getfirst('adminpw', '')): if 'adminpw' in cgidata: # This is a re-authorization attempt msg = Bold(FontSize('+1', _('Authorization failed.'))).Format() @@ -215,51 +189,170 @@ def main(): Auth.loginpage(mlist, 'admindb', frontpage=1) return + # Set up the results document + doc = Document() + doc.set_language(mlist.preferred_language) + + # See if we're requesting all the messages for a particular sender, or if + # we want a specific held message. + sender = None + msgid = None + details = None + envar = os.environ.get('QUERY_STRING') + if envar: + # POST methods, even if their actions have a query string, don't get + # put into FieldStorage's keys :-( + qs = parse_qs(envar).get('sender') + if qs and type(qs) == list: + sender = qs[0] + qs = parse_qs(envar).get('msgid') + if qs and type(qs) == list: + msgid = qs[0] + qs = parse_qs(envar).get('details') + if qs and type(qs) == list: + details = qs[0] + # We need a signal handler to catch the SIGTERM that can come from Apache # when the user hits the browser's STOP button. See the comment in # admin.py for details. + # + # BAW: Strictly speaking, the list should not need to be locked just to + # read the request database. However the request database asserts that + # the list is locked in order to load it and it's not worth complicating + # that logic. def sigterm_handler(signum, frame, mlist=mlist): - try: - # Make sure the list gets unlocked... - mlist.Unlock() - # Log the termination - syslog('info', 'admindb: SIGTERM received, unlocking list and exiting') - except Exception as e: - syslog('error', 'admindb: Error in SIGTERM handler: %s', str(e)) - finally: - # ...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) + # Make sure the list gets unlocked... + 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) mlist.Lock() try: # Install the emergency shutdown signal handler signal.signal(signal.SIGTERM, sigterm_handler) - try: - process_form(mlist, doc, cgidata) - mlist.Save() - # Output the success page with proper headers - print(doc.Format()) - except PermissionError as e: - syslog('error', 'admindb: Permission error processing form: %s', str(e)) - doc = Document() - doc.set_language(mlist.preferred_language) - doc.AddItem(Header(2, _("Error"))) - doc.AddItem(Bold(_('Permission error while processing request'))) - print(doc.Format()) - except Exception as e: - syslog('error', 'admindb: Error processing form: %s', str(e)) - doc = Document() - doc.set_language(mlist.preferred_language) - doc.AddItem(Header(2, _("Error"))) - doc.AddItem(Bold(_('Error processing request'))) + realname = mlist.real_name + if not list(cgidata.keys()) or 'admlogin' in cgidata: + # If this is not a form submission (i.e. there are no keys in the + # form) or it's a login, then we don't need to do much special. + doc.SetTitle(_(f'{realname} Administrative Database')) + elif not details: + # This is a form submission + doc.SetTitle(_(f'{realname} Administrative Database Results')) + if csrf_checked: + process_form(mlist, doc, cgidata) + else: + doc.addError( + _('The form lifetime has expired. (request forgery check)')) + # Now print the results and we're done. Short circuit for when there + # are no pending requests, but be sure to save the results! + admindburl = mlist.GetScriptURL('admindb', absolute=1) + if not mlist.NumRequestsPending(): + title = _(f'{realname} Administrative Database') + doc.SetTitle(title) + doc.AddItem(Header(2, title)) + doc.AddItem(_('There are no pending requests.')) + doc.AddItem(' ') + doc.AddItem(Link(admindburl, + _('Click here to reload this page.'))) + # Put 'Logout' link before the footer + doc.AddItem('\n

    ') + doc.AddItem(Link('%s/logout' % admindburl, + '%s' % _('Logout'))) + doc.AddItem('
    \n') + doc.AddItem(mlist.GetMailmanFooter()) print(doc.Format()) + mlist.Save() + return + + form = Form(admindburl, mlist=mlist, contexts=AUTH_CONTEXTS) + # Add the instructions template + if details == 'instructions': + doc.AddItem(Header( + 2, _('Detailed instructions for the administrative database'))) + else: + doc.AddItem(Header( + 2, + _('Administrative requests for mailing list:') + + ' %s' % mlist.real_name)) + if details != 'instructions': + form.AddItem(Center(SubmitButton('submit', _('Submit All Data')))) + nomessages = not mlist.GetHeldMessageIds() + if not (details or sender or msgid or nomessages): + form.AddItem(Center( + '' + )) + # Add a link back to the overview, if we're not viewing the overview! + adminurl = mlist.GetScriptURL('admin', absolute=1) + d = {'listname' : mlist.real_name, + 'detailsurl': admindburl + '?details=instructions', + 'summaryurl': admindburl, + 'viewallurl': admindburl + '?details=all', + 'adminurl' : adminurl, + 'filterurl' : adminurl + '/privacy/sender', + } + addform = 1 + if sender: + esender = Utils.websafe(sender) + d['description'] = _("all of {esender}'s held messages.") + doc.AddItem(Utils.maketext('admindbpreamble.html', d, + raw=1, mlist=mlist)) + show_sender_requests(mlist, form, sender) + elif msgid: + d['description'] = _('a single held message.') + doc.AddItem(Utils.maketext('admindbpreamble.html', d, + raw=1, mlist=mlist)) + show_message_requests(mlist, form, msgid) + elif details == 'all': + d['description'] = _('all held messages.') + doc.AddItem(Utils.maketext('admindbpreamble.html', d, + raw=1, mlist=mlist)) + show_detailed_requests(mlist, form) + elif details == 'instructions': + doc.AddItem(Utils.maketext('admindbdetails.html', d, + raw=1, mlist=mlist)) + addform = 0 + else: + # Show a summary of all requests + doc.AddItem(Utils.maketext('admindbsummary.html', d, + raw=1, mlist=mlist)) + num = show_pending_subs(mlist, form) + num += show_pending_unsubs(mlist, form) + num += show_helds_overview(mlist, form, ssort) + addform = num > 0 + # Finish up the document, adding buttons to the form + if addform: + doc.AddItem(form) + form.AddItem('
    ') + if not (details or sender or msgid or nomessages): + form.AddItem(Center( + '' + )) + form.AddItem(Center(SubmitButton('submit', _('Submit All Data')))) + # Put 'Logout' link before the footer + doc.AddItem('\n
    ') + doc.AddItem(Link('%s/logout' % admindburl, + '%s' % _('Logout'))) + doc.AddItem('
    \n') + doc.AddItem(mlist.GetMailmanFooter()) + print(doc.Format()) + # Commit all changes + mlist.Save() finally: mlist.Unlock() + def handle_no_list(msg=''): # Print something useful if no list was given. doc = Document() @@ -274,11 +367,10 @@ def handle_no_list(msg=''): doc.AddItem(_(f'You must specify a list name. Here is the {link}')) doc.AddItem('
    ') doc.AddItem(MailmanLogo()) - - # Return the document instead of outputting headers - return doc + print(doc.Format()) + def show_pending_subs(mlist, form): # Add the subscription request section pendingsubs = mlist.GetSubscriptionIds() @@ -286,18 +378,11 @@ def show_pending_subs(mlist, form): return 0 form.AddItem('
    ') form.AddItem(Center(Header(2, _('Subscription Requests')))) - table = Table( - role="table", - aria_label=_("Pending Subscription Requests"), - style="border: 1px solid #ccc; border-collapse: collapse; width: 100%;" - ) + table = Table(border=2) table.AddRow([Center(Bold(_('Address/name/time'))), Center(Bold(_('Your decision'))), Center(Bold(_('Reason for refusal'))) ]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, role="columnheader", scope="col") - table.AddCellInfo(table.GetCurrentRowIndex(), 1, role="columnheader", scope="col") - table.AddCellInfo(table.GetCurrentRowIndex(), 2, role="columnheader", scope="col") # Alphabetical order by email address byaddrs = {} for id in pendingsubs: @@ -329,16 +414,9 @@ def show_pending_subs(mlist, form): CheckBox(f'ban-%d' % id, 1).Format() + ' ' + _('Permanently ban from this list') + '') - # Ensure the address is properly decoded for display - if isinstance(addr, bytes): - try: - addr = addr.decode('utf-8') - except UnicodeDecodeError: - try: - addr = addr.decode('latin-1') - except UnicodeDecodeError: - addr = addr.decode('ascii', 'replace') - table.AddRow(['%s
    %s
    %s' % (Utils.websafe(addr), + # While the address may be a unicode, it must be ascii + paddr = addr.encode('us-ascii', 'replace') + table.AddRow(['%s
    %s
    %s' % (paddr, Utils.websafe(fullname), displaytime), radio, @@ -350,24 +428,18 @@ def show_pending_subs(mlist, form): return num + def show_pending_unsubs(mlist, form): # Add the pending unsubscription request section lang = mlist.preferred_language pendingunsubs = mlist.GetUnsubscriptionIds() if not pendingunsubs: return 0 - table = Table( - role="table", - aria_label=_("Pending Unsubscription Requests"), - style="border: 1px solid #ccc; border-collapse: collapse; width: 100%;" - ) + table = Table(border=2) table.AddRow([Center(Bold(_('User address/name'))), Center(Bold(_('Your decision'))), Center(Bold(_('Reason for refusal'))) ]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, role="columnheader", scope="col") - table.AddCellInfo(table.GetCurrentRowIndex(), 1, role="columnheader", scope="col") - table.AddCellInfo(table.GetCurrentRowIndex(), 2, role="columnheader", scope="col") # Alphabetical order by email address byaddrs = {} for id in pendingunsubs: @@ -410,28 +482,7 @@ def show_pending_unsubs(mlist, form): return num -def format_subject(subject, charset): - """Format a subject line with proper encoding handling.""" - dispsubj = Utils.oneline(subject, charset) - if isinstance(dispsubj, bytes): - try: - dispsubj = dispsubj.decode(charset) - except UnicodeDecodeError: - dispsubj = dispsubj.decode('latin-1', 'replace') - return dispsubj - - -def format_message_data(msgdata): - """Format message metadata with proper error handling.""" - when = msgdata.get('received_time') - if when: - try: - return time.ctime(when) - except (TypeError, ValueError): - return _('Invalid timestamp') - return None - - + def show_helds_overview(mlist, form, ssort=SSENDER): # Sort the held messages. byskey = helds_by_skey(mlist, ssort) @@ -449,11 +500,7 @@ def show_helds_overview(mlist, form, ssort=SSENDER): (ssort == SSENDER, ssort == SSENDERTIME, ssort == STIME)))) # Add the by-sender overview tables admindburl = mlist.GetScriptURL('admindb', absolute=1) - table = Table( - role="table", - aria_label=_("Held Messages Overview"), - border=0 - ) + table = Table(border=0) form.AddItem(table) skeys = list(byskey.keys()) skeys.sort() @@ -463,27 +510,19 @@ def show_helds_overview(mlist, form, ssort=SSENDER): esender = Utils.websafe(sender) senderurl = admindburl + '?sender=' + qsender # The encompassing sender table - stable = Table( - role="table", - aria_label=_("Messages from {sender}").format(sender=esender), - border=1 - ) + stable = Table(border=1) stable.AddRow([Center(Bold(_('From:')).Format() + esender)]) - stable.AddCellInfo(stable.GetCurrentRowIndex(), 0, colspan=2, role="cell") - left = Table( - role="table", - aria_label=_("Actions for messages from {sender}").format(sender=esender), - border=0 - ) + stable.AddCellInfo(stable.GetCurrentRowIndex(), 0, colspan=2) + left = Table(border=0) left.AddRow([_('Action to take on all these held messages:')]) - left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2, role="cell") + left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) btns = hacky_radio_buttons( 'senderaction-' + qsender, (_('Defer'), _('Accept'), _('Reject'), _('Discard')), (mm_cfg.DEFER, mm_cfg.APPROVE, mm_cfg.REJECT, mm_cfg.DISCARD), (1, 0, 0, 0)) left.AddRow([btns]) - left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2, role="cell") + left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) left.AddRow([ '' ]) - left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2, role="cell") + left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) left.AddRow([ '' ]) - left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2, role="cell") + left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) left.AddRow([ TextBox('senderforwardto-' + qsender, value=mlist.GetOwnerEmail()) ]) - left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2, role="cell") + left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) # If the sender is a member and the message is being held due to a # moderation bit, give the admin a chance to clear the member's mod # bit. If this sender is not a member and is not already on one of @@ -522,11 +561,11 @@ def show_helds_overview(mlist, form, ssort=SSENDER): else: left.AddRow( [_('The sender is now a member of this list')]) - left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2, role="cell") + left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) elif sender not in (mlist.accept_these_nonmembers + - mlist.hold_these_nonmembers + - mlist.reject_these_nonmembers + - mlist.discard_these_nonmembers): + mlist.hold_these_nonmembers + + mlist.reject_these_nonmembers + + mlist.discard_these_nonmembers): left.AddRow([ '' ]) - left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2, role="cell") + left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) btns = hacky_radio_buttons( 'senderfilter-' + qsender, (_('Accepts'), _('Holds'), _('Rejects'), _('Discards')), (mm_cfg.ACCEPT, mm_cfg.HOLD, mm_cfg.REJECT, mm_cfg.DISCARD), (0, 0, 0, 1)) left.AddRow([btns]) - left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2, role="cell") + left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) if sender not in mlist.ban_list: left.AddRow([ '']) - left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2, role="cell") - right = Table( - role="table", - aria_label=_("Actions for messages from {sender}").format(sender=esender), - border=0 - ) + left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) + right = Table(border=0) right.AddRow([ _(f"""Click on the message number to view the individual message, or you can """) + Link(senderurl, _(f'view all messages from {esender}')).Format() ]) - right.AddCellInfo(right.GetCurrentRowIndex(), 0, colspan=2, role="cell") + right.AddCellInfo(right.GetCurrentRowIndex(), 0, colspan=2) right.AddRow([' ', ' ']) counter = 1 for ptime, id in byskey[skey]: + info = mlist.GetRecord(id) + ptime, sender, subject, reason, filename, msgdata = info + # BAW: This is really the size of the message pickle, which should + # be close, but won't be exact. Sigh, good enough. try: - info = mlist.GetRecord(id) - ptime, sender, subject, reason, filename, msgdata = info - # Get message size with proper error handling - try: - size = os.path.getsize(os.path.join(mm_cfg.DATA_DIR, filename)) - except OSError as e: - if e.errno != errno.ENOENT: - mailman_log('error', 'admindb: Error getting file size: %s\n%s', - str(e), traceback.format_exc()) - raise - # Message already handled - mlist.HandleRequest(id, mm_cfg.DISCARD) - continue - - # Format subject with proper encoding - charset = Utils.GetCharSet(mlist.preferred_language) - dispsubj = format_subject(subject, charset) - - t = Table( - role="table", - aria_label=_("Message {counter}").format(counter=counter), - border=0 - ) - t.AddRow([Link(admindburl + '?msgid=%d' % id, '[%d]' % counter), - Bold(_('Subject:')), - Utils.websafe(dispsubj) - ]) - t.AddRow([' ', Bold(_('Size:')), str(size) + _(' bytes')]) - - # Format reason with proper encoding - if reason: - try: - reason = _(reason) - if isinstance(reason, bytes): - reason = reason.decode(charset, 'replace') - except (UnicodeError, LookupError): - reason = _('not available') - else: - reason = _('not available') - t.AddRow([' ', Bold(_('Reason:')), reason]) - - # Format received time with proper error handling - received_time = format_message_data(msgdata) - if received_time: - t.AddRow([' ', Bold(_('Received:')), received_time]) - - t.AddRow([InputObj(qsender, 'hidden', str(id), False).Format()]) - counter += 1 - right.AddRow([t]) - except Exception as e: - mailman_log('error', 'admindb: Error processing held message %d: %s\n%s', - id, str(e), traceback.format_exc()) + size = os.path.getsize(os.path.join(mm_cfg.DATA_DIR, filename)) + except OSError as e: + if e.errno != errno.ENOENT: raise + # This message must have gotten lost, i.e. it's already been + # handled by the time we got here. + mlist.HandleRequest(id, mm_cfg.DISCARD) continue - + dispsubj = Utils.oneline( + subject, Utils.GetCharSet(mlist.preferred_language)) + t = Table(border=0) + t.AddRow([Link(admindburl + '?msgid=%d' % id, '[%d]' % counter), + Bold(_('Subject:')), + Utils.websafe(dispsubj) + ]) + t.AddRow([' ', Bold(_('Size:')), str(size) + _(' bytes')]) + if reason: + reason = _(reason) + else: + reason = _('not available') + t.AddRow([' ', Bold(_('Reason:')), reason]) + # Include the date we received the message, if available + when = msgdata.get('received_time') + if when: + t.AddRow([' ', Bold(_('Received:')), + time.ctime(when)]) + t.AddRow([InputObj(qsender, 'hidden', str(id), False).Format()]) + counter += 1 + right.AddRow([t]) stable.AddRow([left, right]) table.AddRow([stable]) return 1 + def show_sender_requests(mlist, form, sender): byskey = helds_by_skey(mlist, SSENDER) if not byskey: @@ -641,39 +655,18 @@ def show_sender_requests(mlist, form, sender): count += 1 + def show_message_requests(mlist, form, id): try: id = int(id) info = mlist.GetRecord(id) - except ValueError as e: - mailman_log('error', 'admindb: Invalid message ID "%s": %s\n%s', - id, str(e), traceback.format_exc()) - form.AddItem(Header(2, _("Error"))) - form.AddItem(Bold(_('Invalid message ID.'))) - return - except KeyError as e: - mailman_log('error', 'admindb: Message ID %d not found: %s\n%s', - id, str(e), traceback.format_exc()) - form.AddItem(Header(2, _("Error"))) - form.AddItem(Bold(_('Message not found.'))) - return - except Exception as e: - mailman_log('error', 'admindb: Error getting message %d: %s\n%s', - id, str(e), traceback.format_exc()) - form.AddItem(Header(2, _("Error"))) - form.AddItem(Bold(_('Error retrieving message.'))) - return - - try: - show_post_requests(mlist, id, info, 1, 1, form) - except Exception as e: - mailman_log('error', 'admindb: Error showing message %d: %s\n%s', - id, str(e), traceback.format_exc()) - form.AddItem(Header(2, _("Error"))) - form.AddItem(Bold(_('Error displaying message.'))) + except (ValueError, KeyError): + # BAW: print an error message? return + show_post_requests(mlist, id, info, 1, 1, form) + def show_detailed_requests(mlist, form): all = mlist.GetHeldMessageIds() total = len(all) @@ -684,6 +677,7 @@ def show_detailed_requests(mlist, form): count += 1 + def show_post_requests(mlist, id, info, total, count, form): # Mailman.ListAdmin.__handlepost no longer tests for pre 2.0beta3 ptime, sender, subject, reason, filename, msgdata = info @@ -693,132 +687,107 @@ def show_post_requests(mlist, id, info, total, count, form): if total != 1: msg += _(f' (%(count)d of %(total)d)') form.AddItem(Center(Header(2, msg))) - - # Get the message file path - msgpath = os.path.join(mm_cfg.DATA_DIR, filename) - - # Try to read the message with better error handling + # We need to get the headers and part of the textual body of the message + # being held. The best way to do this is to use the email Parser to get + # an actual object, which will be easier to deal with. We probably could + # just do raw reads on the file. try: - msg = readMessage(msgpath) + msg = readMessage(os.path.join(mm_cfg.DATA_DIR, filename)) + Utils.set_cte_if_missing(msg) except IOError as e: if e.errno != errno.ENOENT: - mailman_log('error', 'admindb: Error reading message file %s: %s\n%s', - msgpath, str(e), traceback.format_exc()) raise form.AddItem(_(f'Message with id #%(id)d was lost.')) form.AddItem('

    ') + # BAW: kludge to remove id from requests.db. try: mlist.HandleRequest(id, mm_cfg.DISCARD) except Errors.LostHeldMessage: pass return - except email.errors.MessageParseError as e: - mailman_log('error', 'admindb: Corrupted message file %s: %s\n%s', - msgpath, str(e), traceback.format_exc()) + except email.errors.MessageParseError: form.AddItem(_(f'Message with id #%(id)d is corrupted.')) + # BAW: Should we really delete this, or shuttle it off for site admin + # to look more closely at? form.AddItem('

    ') + # BAW: kludge to remove id from requests.db. try: mlist.HandleRequest(id, mm_cfg.DISCARD) except Errors.LostHeldMessage: pass return - except Exception as e: - mailman_log('error', 'admindb: Unexpected error reading message %d: %s\n%s', - id, str(e), traceback.format_exc()) - form.AddItem(_(f'Error reading message #%(id)d.')) - form.AddItem('

    ') - return - - # Get the header text and the message body excerpt with better encoding handling + # Get the header text and the message body excerpt lines = [] chars = 0 + # A negative value means, include the entire message regardless of size limit = mm_cfg.ADMINDB_PAGE_TEXT_LIMIT - - # Try to determine the message charset - charset = None + + if msg.is_multipart(): + for part in msg.walk(): + if not hasattr(part, 'policy'): + part.policy = email._policybase.compat32 + if part.get_content_type() == 'text/plain': + payload = part.get_payload(decode=True) + if payload: + decoded_payload = codecs.decode(payload, 'unicode_escape') + for line in decoded_payload.splitlines(): + lines.append(line) + chars += len(line) + if chars >= limit > 0: + break + break + else: + payload = msg.get_payload(decode=True) + if payload: + decoded_payload = codecs.decode(payload, 'unicode_escape') + for line in decoded_payload.splitlines(): + lines.append(line) + chars += len(line) + if chars >= limit > 0: + break + # Ensure the full last line is included to avoid splitting multibyte characters + body = ''.join(lines) + # Get message charset and try encode in list charset + # We get it from the first text part. + # We need to replace invalid characters here or we can throw an uncaught + # exception in doc.Format(). for part in msg.walk(): if part.get_content_maintype() == 'text': - charset = part.get_content_charset() - if charset: - break - - # If no charset found, use list's preferred charset - if not charset: - charset = Utils.GetCharSet(mlist.preferred_language) - - # Read the message body with proper encoding - try: - for line in body_line_iterator(msg, decode=True): - # Try to decode the line if it's bytes - if isinstance(line, bytes): - try: - line = line.decode(charset, 'replace') - except (UnicodeError, LookupError): - line = line.decode('latin-1', 'replace') - - lines.append(line) - chars += len(line) - if chars >= limit > 0: - break - except Exception as e: - mailman_log('error', 'admindb: Error reading message body: %s\n%s', - str(e), traceback.format_exc()) - lines = [_('Error reading message body')] - - # Join the lines with proper encoding - try: - body = ''.join(lines) - if isinstance(body, bytes): - body = body.decode(charset, 'replace') - except (UnicodeError, LookupError): - body = _('Error decoding message body') - - # Format the headers with proper encoding - try: - hdrtxt = NL.join(['%s: %s' % (k, v) for k, v in list(msg.items())]) - if isinstance(hdrtxt, bytes): - hdrtxt = hdrtxt.decode(charset, 'replace') - except (UnicodeError, LookupError): - hdrtxt = _('Error decoding message headers') - - # Format the subject with proper encoding - try: - dispsubj = Utils.oneline(subject, charset) - if isinstance(dispsubj, bytes): - dispsubj = dispsubj.decode(charset, 'replace') - except (UnicodeError, LookupError): - dispsubj = _('Error decoding subject') - - # Format the reason with proper encoding - try: - if reason: - reason = _(reason) - if isinstance(reason, bytes): - reason = reason.decode(charset, 'replace') - else: - reason = _('not available') - except (UnicodeError, LookupError): - reason = _('Error decoding reason') - - # Create the form table with proper encoding - t = Table(cellspacing=0, cellpadding=0) - t.AddRow([Bold(_('From:')), Utils.websafe(sender)]) + # Watchout for charset= with no value. + mcset = part.get_content_charset() or 'us-ascii' + break + else: + mcset = 'us-ascii' + lcset = Utils.GetCharSet(mlist.preferred_language) + # Note that this following block breaks a lot of messages. Removing it allows them to stay in their native character sets. + # Leaving in as it seems like behavior people would have grown to expect. + if mcset != lcset: + # Ensure the body is in the list's preferred charset + try: + # If body is a str, encode to bytes using the source charset (mcset) + body_bytes = body.encode(mcset, 'replace') if isinstance(body, str) else body + # Then decode bytes to str using the list's charset (lcset) + body = body_bytes.decode(lcset, 'replace') + except (UnicodeEncodeError, UnicodeDecodeError): + # Fallback in case of encoding/decoding issues + body = body.encode('ascii', 'replace').decode('ascii', 'replace') + # + hdrtxt = NL.join(['%s: %s' % (k, v) for k, v in list(msg.items())]) + hdrtxt = Utils.websafe(hdrtxt) + # Okay, we've reconstituted the message just fine. Now for the fun part! + t = Table(cellspacing=0, cellpadding=0, width='100%') + t.AddRow([Bold(_('From:')), sender]) row, col = t.GetCurrentRowIndex(), t.GetCurrentCellIndex() t.AddCellInfo(row, col-1, align='right') - - t.AddRow([Bold(_('Subject:')), Utils.websafe(dispsubj)]) + t.AddRow([Bold(_('Subject:')), + Utils.websafe(Utils.oneline(subject, lcset))]) t.AddCellInfo(row+1, col-1, align='right') - - t.AddRow([Bold(_('Reason:')), Utils.websafe(reason)]) + t.AddRow([Bold(_('Reason:')), _(reason)]) t.AddCellInfo(row+2, col-1, align='right') - - # Format received time with proper error handling - received_time = format_message_data(msgdata) - if received_time: - t.AddRow([Bold(_('Received:')), received_time]) + when = msgdata.get('received_time') + if when: + t.AddRow([Bold(_('Received:')), time.ctime(when)]) t.AddCellInfo(row+3, col-1, align='right') - - # Add action buttons buttons = hacky_radio_buttons(id, (_('Defer'), _('Approve'), _('Reject'), _('Discard')), (mm_cfg.DEFER, mm_cfg.APPROVE, mm_cfg.REJECT, mm_cfg.DISCARD), @@ -826,16 +795,12 @@ def show_post_requests(mlist, id, info, total, count, form): spacing=5) t.AddRow([Bold(_('Action:')), buttons]) t.AddCellInfo(t.GetCurrentRowIndex(), col-1, align='right') - - # Add preserve checkbox t.AddRow([' ', '' ]) - - # Add forward checkbox and textbox t.AddRow([' ', '

    ') + def process_form(mlist, doc, cgidata): - """Process the admin database form with proper error handling.""" + global ssort + senderactions = {} + badaddrs = [] + # Sender-centric actions + for k in list(cgidata.keys()): + for prefix in ('senderaction-', 'senderpreserve-', 'senderforward-', + 'senderforwardto-', 'senderfilterp-', 'senderfilter-', + 'senderclearmodp-', 'senderbanp-'): + if k.startswith(prefix): + action = k[:len(prefix)-1] + qsender = k[len(prefix):] + sender = unquote_plus(qsender) + value = cgidata.getfirst(k) + senderactions.setdefault(sender, {})[action] = value + for id in cgidata.getlist(qsender): + senderactions[sender].setdefault('message_ids', + []).append(int(id)) + # discard-all-defers try: - # Get the sender and message id from the query string with proper encoding - envar = os.environ.get('QUERY_STRING', '') - qs = urllib.parse.parse_qs(envar, keep_blank_values=True) - - # Handle both encoded and unencoded values - def safe_get(key, default=''): - values = qs.get(key, [default]) - if not values: - return default + discardalldefersp = cgidata.getfirst('discardalldefersp', 0) + except ValueError: + discardalldefersp = 0 + # Get the summary sequence + ssort = int(cgidata.getfirst('summary_sort', SSENDER)) + for sender in list(senderactions.keys()): + actions = senderactions[sender] + # Handle what to do about all this sender's held messages + try: + action = int(actions.get('senderaction', mm_cfg.DEFER)) + except ValueError: + action = mm_cfg.DEFER + if action == mm_cfg.DEFER and discardalldefersp: + action = mm_cfg.DISCARD + if action in (mm_cfg.DEFER, mm_cfg.APPROVE, + mm_cfg.REJECT, mm_cfg.DISCARD): + preserve = actions.get('senderpreserve', 0) + forward = actions.get('senderforward', 0) + forwardaddr = actions.get('senderforwardto', '') + byskey = helds_by_skey(mlist, SSENDER) + for ptime, id in byskey.get((0, sender), []): + if id not in senderactions[sender]['message_ids']: + # It arrived after the page was displayed. Skip it. + continue + try: + msgdata = mlist.GetRecord(id)[5] + comment = msgdata.get('rejection_notice', + _('[No explanation given]')) + mlist.HandleRequest(id, action, comment, preserve, + forward, forwardaddr) + except (KeyError, Errors.LostHeldMessage): + # That's okay, it just means someone else has already + # updated the database while we were staring at the page, + # so just ignore it + continue + # Now see if this sender should be added to one of the nonmember + # sender filters. + if actions.get('senderfilterp', 0): + # Check for an invalid sender address. try: - # Try to decode if it's bytes - if isinstance(values[0], bytes): - return values[0].decode('utf-8', 'replace') - return values[0] - except (UnicodeError, AttributeError): - return str(values[0]) - - sender = safe_get('sender') - msgid = safe_get('msgid') - details = safe_get('details') - - # Set the page title with proper encoding - title = _(f'{mlist.real_name} Administrative Database') - doc.SetTitle(title) - doc.AddItem(Header(2, title)) - - # Create a form for the overview with proper encoding - form = Form(mlist.GetScriptURL('admindb', absolute=1), - mlist=mlist, - contexts=AUTH_CONTEXTS) - form.AddItem(Center(SubmitButton('submit', _('Submit All Data')))) - - # Get the action from the form data with proper encoding - action = safe_get('action') - if not action: - # No action specified, show the overview - show_pending_subs(mlist, form) - show_pending_unsubs(mlist, form) - show_helds_overview(mlist, form) - doc.AddItem(form) - return - - # Process the form submission - if action == 'submit': - # Process the form data - process_submissions(mlist, cgidata) - # Show success message - doc.AddItem(Header(2, _('Database Updated...'))) - return - - # If we get here, something went wrong - doc.AddItem(Header(2, _('Error'))) - doc.AddItem(Bold(_('Invalid form submission.'))) - - except Exception as e: - mailman_log('error', 'admindb: Error in process_form: %s\n%s', - str(e), traceback.format_exc()) - raise - - -def format_body(body, mcset, lcset): - """Format the message body for display.""" - if isinstance(body, bytes): - body = body.decode(mcset, 'replace') - elif not isinstance(body, str): - body = str(body) - return body.encode(lcset, 'replace') + Utils.ValidateEmail(sender) + except Errors.EmailAddressError: + # Don't check for dups. Report it once for each checked box. + badaddrs.append(sender) + else: + try: + which = int(actions.get('senderfilter')) + except ValueError: + # Bogus form + which = 'ignore' + if which == mm_cfg.ACCEPT: + mlist.accept_these_nonmembers.append(sender) + elif which == mm_cfg.HOLD: + mlist.hold_these_nonmembers.append(sender) + elif which == mm_cfg.REJECT: + mlist.reject_these_nonmembers.append(sender) + elif which == mm_cfg.DISCARD: + mlist.discard_these_nonmembers.append(sender) + # Otherwise, it's a bogus form, so ignore it + # And now see if we're to clear the member's moderation flag. + if actions.get('senderclearmodp', 0): + try: + mlist.setMemberOption(sender, mm_cfg.Moderate, 0) + except Errors.NotAMemberError: + # This person's not a member any more. Oh well. + pass + # And should this address be banned? + if actions.get('senderbanp', 0): + # Check for an invalid sender address. + try: + Utils.ValidateEmail(sender) + except Errors.EmailAddressError: + # Don't check for dups. Report it once for each checked box. + badaddrs.append(sender) + else: + if sender not in mlist.ban_list: + mlist.ban_list.append(sender) + # Now, do message specific actions + banaddrs = [] + erroraddrs = [] + for k in list(cgidata.keys()): + formv = cgidata[k] + if type(formv) == list: + continue + try: + v = int(formv.value) + request_id = int(k) + except ValueError: + continue + if v not in (mm_cfg.DEFER, mm_cfg.APPROVE, mm_cfg.REJECT, + mm_cfg.DISCARD, mm_cfg.SUBSCRIBE, mm_cfg.UNSUBSCRIBE, + mm_cfg.ACCEPT, mm_cfg.HOLD): + continue + # Get the action comment and reasons if present. + commentkey = 'comment-%d' % request_id + preservekey = 'preserve-%d' % request_id + forwardkey = 'forward-%d' % request_id + forwardaddrkey = 'forward-addr-%d' % request_id + bankey = 'ban-%d' % request_id + # Defaults + try: + if mlist.GetRecordType(request_id) == HELDMSG: + msgdata = mlist.GetRecord(request_id)[5] + comment = msgdata.get('rejection_notice', + _('[No explanation given]')) + else: + comment = _('[No explanation given]') + except KeyError: + # Someone else must have handled this one after we got the page. + continue + preserve = 0 + forward = 0 + forwardaddr = '' + if commentkey in cgidata: + comment = cgidata[commentkey].value + if preservekey in cgidata: + preserve = cgidata[preservekey].value + if forwardkey in cgidata: + forward = cgidata[forwardkey].value + if forwardaddrkey in cgidata: + forwardaddr = cgidata[forwardaddrkey].value + # Should we ban this address? Do this check before handling the + # request id because that will evict the record. + if cgidata.getfirst(bankey): + sender = mlist.GetRecord(request_id)[1] + if sender not in mlist.ban_list: + # We don't need to validate the sender. An invalid address + # can't get here. + mlist.ban_list.append(sender) + # Handle the request id + try: + mlist.HandleRequest(request_id, v, comment, + preserve, forward, forwardaddr) + except (KeyError, Errors.LostHeldMessage): + # That's okay, it just means someone else has already updated the + # database while we were staring at the page, so just ignore it + continue + except Errors.MMAlreadyAMember as v: + erroraddrs.append(v) + except Errors.MembershipIsBanned as pattern: + sender = mlist.GetRecord(request_id)[1] + banaddrs.append((sender, pattern)) + # save the list and print the results + doc.AddItem(Header(2, _('Database Updated...'))) + if erroraddrs: + for addr in erroraddrs: + addr = Utils.websafe(addr) + doc.AddItem(str(addr) + _(' is already a member') + '
    ') + if banaddrs: + for addr, patt in banaddrs: + addr = Utils.websafe(addr) + doc.AddItem(_(f'{addr} is banned (matched: {patt})') + '
    ') + if badaddrs: + for addr in badaddrs: + addr = Utils.websafe(addr) + doc.AddItem(str(addr) + ': ' + _('Bad/Invalid email address') + + '
    ') diff --git a/Mailman/Cgi/confirm.py b/Mailman/Cgi/confirm.py index ab2d1958..f0a13843 100644 --- a/Mailman/Cgi/confirm.py +++ b/Mailman/Cgi/confirm.py @@ -20,10 +20,8 @@ from __future__ import print_function import signal -import urllib.parse +from Mailman.Utils import FieldStorage import time -import os -import sys from Mailman import mm_cfg from Mailman import Errors @@ -38,6 +36,7 @@ _ = i18n._ i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + def main(): doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) @@ -55,7 +54,7 @@ def main(): except Errors.MMListError as e: # Avoid cross-site scripting attacks safelistname = Utils.websafe(listname) - bad_confirmation(doc, _('No such list {safelistname}')) + bad_confirmation(doc, _(f'No such list {safelistname}')) doc.AddItem(MailmanLogo()) # Send this with a 404 status. print('Status: 404 Not Found') @@ -68,18 +67,10 @@ def main(): doc.set_language(mlist.preferred_language) # Get the form data to see if this is a second-step confirmation + cgidata = FieldStorage(keep_blank_values=1) try: - if os.environ.get('REQUEST_METHOD') == 'POST': - content_length = int(os.environ.get('CONTENT_LENGTH', 0)) - if content_length > 0: - form_data = sys.stdin.buffer.read(content_length).decode('utf-8') - cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=True) - else: - cgidata = {} - else: - query_string = os.environ.get('QUERY_STRING', '') - cgidata = urllib.parse.parse_qs(query_string, keep_blank_values=True) - except Exception: + cookie = cgidata.getfirst('cookie') + except TypeError: # Someone crafted a POST with a bad Content-Type:. doc.AddItem(Header(2, _("Error"))) doc.AddItem(Bold(_('Invalid options to CGI script.'))) @@ -88,7 +79,6 @@ def main(): print(doc.Format()) return - cookie = cgidata.get('cookie', [''])[0] if cookie == '': ask_for_cookie(mlist, doc, _('Confirmation string was empty.')) return @@ -129,17 +119,17 @@ def main(): try: if content[0] == Pending.SUBSCRIPTION: - if cgidata.get('cancel', [''])[0]: + if cgidata.getfirst('cancel'): subscription_cancel(mlist, doc, cookie) - elif cgidata.get('submit', [''])[0]: + elif cgidata.getfirst('submit'): subscription_confirm(mlist, doc, cookie, cgidata) else: subscription_prompt(mlist, doc, cookie, content[1]) elif content[0] == Pending.UNSUBSCRIPTION: try: - if cgidata.get('cancel', [''])[0]: + if cgidata.getfirst('cancel'): unsubscription_cancel(mlist, doc, cookie) - elif cgidata.get('submit', [''])[0]: + elif cgidata.getfirst('submit'): unsubscription_confirm(mlist, doc, cookie) else: unsubscription_prompt(mlist, doc, cookie, *content[1:]) @@ -150,9 +140,9 @@ def main(): # Expunge this record from the pending database. expunge(mlist, cookie) elif content[0] == Pending.CHANGE_OF_ADDRESS: - if cgidata.get('cancel', [''])[0]: + if cgidata.getfirst('cancel'): addrchange_cancel(mlist, doc, cookie) - elif cgidata.get('submit', [''])[0]: + elif cgidata.getfirst('submit'): addrchange_confirm(mlist, doc, cookie) else: # Watch out for users who have unsubscribed themselves in the @@ -166,21 +156,21 @@ def main(): # Expunge this record from the pending database. expunge(mlist, cookie) elif content[0] == Pending.HELD_MESSAGE: - if cgidata.get('cancel', [''])[0]: + if cgidata.getfirst('cancel'): heldmsg_cancel(mlist, doc, cookie) - elif cgidata.get('submit', [''])[0]: + elif cgidata.getfirst('submit'): heldmsg_confirm(mlist, doc, cookie) else: heldmsg_prompt(mlist, doc, cookie, *content[1:]) elif content[0] == Pending.RE_ENABLE: - if cgidata.get('cancel', [''])[0]: + if cgidata.getfirst('cancel'): reenable_cancel(mlist, doc, cookie) - elif cgidata.get('submit', [''])[0]: + elif cgidata.getfirst('submit'): reenable_confirm(mlist, doc, cookie) else: reenable_prompt(mlist, doc, cookie, *content[1:]) else: - bad_confirmation(doc, _('System error, bad content: {content}')) + bad_confirmation(doc, _(f'System error, bad content: {content}')) except Errors.MMBadConfirmation: bad_confirmation(doc, badconfirmstr) @@ -188,6 +178,7 @@ def main(): print(doc.Format()) + def bad_confirmation(doc, extra=''): title = _('Bad confirmation string') doc.SetTitle(title) @@ -206,6 +197,7 @@ def expunge(mlist, cookie): mlist.Unlock() + def ask_for_cookie(mlist, doc, extra=''): title = _('Enter confirmation cookie') doc.SetTitle(title) @@ -235,6 +227,7 @@ def ask_for_cookie(mlist, doc, extra=''): print(doc.Format()) + def subscription_prompt(mlist, doc, cookie, userdesc): email = userdesc.address password = userdesc.password @@ -305,7 +298,7 @@ def subscription_prompt(mlist, doc, cookie, userdesc): table.AddRow([Label(_('Receive digests?')), RadioButtonArray('digests', (_('No'), _('Yes')), checked=digest, values=(0, 1))]) - langs = mlist.available_languages + langs = mlist.GetAvailableLanguages() values = [_(Utils.GetLanguageDescr(l)) for l in langs] try: selected = langs.index(lang) @@ -316,13 +309,14 @@ def subscription_prompt(mlist, doc, cookie, userdesc): table.AddRow([Hidden('cookie', cookie)]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) table.AddRow([ - Label(SubmitButton('submit', _('Subscribe to list {listname}'))), + Label(SubmitButton('submit', _(f'Subscribe to list {listname}'))), SubmitButton('cancel', _('Cancel my subscription request')) ]) form.AddItem(table) doc.AddItem(form) + def subscription_cancel(mlist, doc, cookie): mlist.Lock() try: @@ -342,6 +336,7 @@ def subscription_cancel(mlist, doc, cookie): doc.AddItem(_('You have canceled your subscription request.')) + def subscription_confirm(mlist, doc, cookie, cgidata): # See the comment in admin.py about the need for the signal # handler. @@ -355,14 +350,14 @@ def sigterm_handler(signum, frame, mlist=mlist): try: # Some pending values may be overridden in the form. email of # course is hardcoded. ;) - lang = cgidata.get('language', [mlist.preferred_language])[0] + lang = cgidata.getfirst('language') if not Utils.IsLanguage(lang): lang = mlist.preferred_language i18n.set_language(lang) doc.set_language(lang) if 'digests' in cgidata: try: - digest = int(cgidata['digests'][0]) + digest = int(cgidata.getfirst('digests')) except ValueError: digest = None else: @@ -372,7 +367,7 @@ def sigterm_handler(signum, frame, mlist=mlist): # to confirm the same token simultaneously. If they both succeed in # retrieving the data above, when the second gets here, the cookie # is gone and TypeError is thrown. Catch it below. - fullname = cgidata.get('realname', [None])[0] + fullname = cgidata.getfirst('realname', None) if fullname is not None: fullname = Utils.canonstr(fullname, lang) overrides = UserDesc(fullname=fullname, digest=digest, lang=lang) @@ -429,12 +424,14 @@ def sigterm_handler(signum, frame, mlist=mlist): mlist.Unlock() + def unsubscription_cancel(mlist, doc, cookie): # Expunge this record from the pending database expunge(mlist, cookie) doc.AddItem(_('You have canceled your unsubscription request.')) + def unsubscription_confirm(mlist, doc, cookie): # See the comment in admin.py about the need for the signal # handler. @@ -473,6 +470,7 @@ def sigterm_handler(signum, frame, mlist=mlist): mlist.Unlock() + def unsubscription_prompt(mlist, doc, cookie, addr): title = _('Confirm unsubscription request') doc.SetTitle(title) @@ -515,12 +513,14 @@ def unsubscription_prompt(mlist, doc, cookie, addr): doc.AddItem(form) + def addrchange_cancel(mlist, doc, cookie): # Expunge this record from the pending database expunge(mlist, cookie) doc.AddItem(_('You have canceled your change of address request.')) + def addrchange_confirm(mlist, doc, cookie): # See the comment in admin.py about the need for the signal # handler. @@ -573,6 +573,7 @@ def sigterm_handler(signum, frame, mlist=mlist): mlist.Unlock() + def addrchange_prompt(mlist, doc, cookie, oldaddr, newaddr, globally): title = _('Confirm change of address request') doc.SetTitle(title) @@ -624,6 +625,7 @@ def addrchange_prompt(mlist, doc, cookie, oldaddr, newaddr, globally): doc.AddItem(form) + def heldmsg_cancel(mlist, doc, cookie): title = _('Continue awaiting approval') doc.SetTitle(title) @@ -638,6 +640,7 @@ def heldmsg_cancel(mlist, doc, cookie): doc.AddItem(table) + def heldmsg_confirm(mlist, doc, cookie): # See the comment in admin.py about the need for the signal # handler. @@ -683,6 +686,7 @@ def sigterm_handler(signum, frame, mlist=mlist): mlist.Unlock() + def heldmsg_prompt(mlist, doc, cookie, id): title = _('Cancel held message posting') doc.SetTitle(title) @@ -746,6 +750,7 @@ def sigterm_handler(signum, frame, mlist=mlist): doc.AddItem(form) + def reenable_cancel(mlist, doc, cookie): # Don't actually discard this cookie, since the user may decide to # re-enable their membership at a future time, and we may be sending out @@ -755,6 +760,7 @@ def reenable_cancel(mlist, doc, cookie): this mailing list.""")) + def reenable_confirm(mlist, doc, cookie): # See the comment in admin.py about the need for the signal # handler. @@ -794,6 +800,7 @@ def sigterm_handler(signum, frame, mlist=mlist): mlist.Unlock() + def reenable_prompt(mlist, doc, cookie, list, member): title = _('Re-enable mailing list membership') doc.SetTitle(title) diff --git a/Mailman/Cgi/create.py b/Mailman/Cgi/create.py index 27774c03..691cfd88 100644 --- a/Mailman/Cgi/create.py +++ b/Mailman/Cgi/create.py @@ -22,11 +22,11 @@ import sys import os import signal -import urllib.parse +from Mailman.Utils import FieldStorage from Mailman import mm_cfg from Mailman import MailList -from Mailman.Message import Message +from Mailman import Message from Mailman import Errors from Mailman import i18n from Mailman.htmlformat import * @@ -38,32 +38,14 @@ i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + def main(): doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + cgidata = FieldStorage() try: - if os.environ.get('REQUEST_METHOD') == 'POST': - content_length = int(os.environ.get('CONTENT_LENGTH', 0)) - if content_length > 0: - form_data = sys.stdin.buffer.read(content_length).decode('utf-8') - cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=True) - else: - cgidata = {} - else: - query_string = os.environ.get('QUERY_STRING', '') - cgidata = urllib.parse.parse_qs(query_string, keep_blank_values=True) - except Exception: - # Someone crafted a POST with a bad Content-Type:. - doc.AddItem(Header(2, _("Error"))) - doc.AddItem(Bold(_('Invalid options to CGI script.'))) - # Send this with a 400 status. - print('Status: 400 Bad Request') - print(doc.Format()) - return - - try: - cgidata.get('doit', [''])[0] + cgidata.getfirst('doit', '') except TypeError: # Someone crafted a POST with a bad Content-Type:. doc.AddItem(Header(2, _("Error"))) @@ -101,27 +83,29 @@ def main(): print(doc.Format()) + def process_request(doc, cgidata): # Lowercase the listname since this is treated as the "internal" name. - listname = cgidata.get('listname', [''])[0].strip().lower() - owner = cgidata.get('owner', [''])[0].strip() + listname = cgidata.getfirst('listname', '').strip().lower() + owner = cgidata.getfirst('owner', '').strip() try: - autogen = int(cgidata.get('autogen', ['0'])[0]) + autogen = int(cgidata.getfirst('autogen', '0')) except ValueError: autogen = 0 try: - notify = int(cgidata.get('notify', ['0'])[0]) + notify = int(cgidata.getfirst('notify', '0')) except ValueError: notify = 0 try: - moderate = int(cgidata.get('moderate', ['0'])[0]) + moderate = int(cgidata.getfirst('moderate', + mm_cfg.DEFAULT_DEFAULT_MEMBER_MODERATION)) except ValueError: moderate = mm_cfg.DEFAULT_DEFAULT_MEMBER_MODERATION - password = cgidata.get('password', [''])[0].strip() - confirm = cgidata.get('confirm', [''])[0].strip() - auth = cgidata.get('auth', [''])[0].strip() - langs = cgidata.get('langs', [mm_cfg.DEFAULT_SERVER_LANGUAGE]) + password = cgidata.getfirst('password', '').strip() + confirm = cgidata.getfirst('confirm', '').strip() + auth = cgidata.getfirst('auth', '').strip() + langs = cgidata.getvalue('langs', [mm_cfg.DEFAULT_SERVER_LANGUAGE]) if not isinstance(langs, list): langs = [langs] @@ -129,14 +113,14 @@ def process_request(doc, cgidata): safelistname = Utils.websafe(listname) if '@' in listname: request_creation(doc, cgidata, - _('List name must not include "@": {safelistname}')) + _(f'List name must not include "@": {safelistname}')) return if Utils.list_exists(listname): # BAW: should we tell them the list already exists? This could be # used to mine/guess the existance of non-advertised lists. Then # again, that can be done in other ways already, so oh well. request_creation(doc, cgidata, - _('List already exists: {safelistname}')) + _(f'List already exists: {safelistname}')) return if not listname: request_creation(doc, cgidata, @@ -196,7 +180,7 @@ def process_request(doc, cgidata): hostname not in mm_cfg.VIRTUAL_HOSTS: safehostname = Utils.websafe(hostname) request_creation(doc, cgidata, - _('Unknown virtual host: {safehostname}')) + _(f'Unknown virtual host: {safehostname}')) return emailhost = mm_cfg.VIRTUAL_HOSTS.get(hostname, mm_cfg.DEFAULT_EMAIL_HOST) # We've got all the data we need, so go ahead and try to create the list @@ -232,12 +216,12 @@ def sigterm_handler(signum, frame, mlist=mlist): else: s = Utils.websafe(owner) request_creation(doc, cgidata, - _('Bad owner email address: {s}')) + _(f'Bad owner email address: {s}')) return except Errors.MMListAlreadyExistsError: # MAS: List already exists so we don't need to websafe it. request_creation(doc, cgidata, - _('List already exists: {listname}')) + _(f'List already exists: {listname}')) return except Errors.BadListNameError as e: if e.args: @@ -245,7 +229,7 @@ def sigterm_handler(signum, frame, mlist=mlist): else: s = Utils.websafe(listname) request_creation(doc, cgidata, - _('Illegal list name: {s}')) + _(f'Illegal list name: {s}')) return except Errors.MMListError: request_creation( @@ -285,9 +269,9 @@ def sigterm_handler(signum, frame, mlist=mlist): 'requestaddr' : mlist.GetRequestEmail(), 'siteowner' : siteowner, }, mlist=mlist) - msg = Mailman.Message.UserNotification( + msg = Message.UserNotification( owner, siteowner, - _('Your new mailing list: {listname}'), + _(f'Your new mailing list: {listname}'), text, mlist.preferred_language) msg.send(mlist) @@ -298,16 +282,10 @@ def sigterm_handler(signum, frame, mlist=mlist): title = _('Mailing list creation results') doc.SetTitle(title) - table = Table( - role="table", - aria_label=_("List Creation Results"), - border=0, - width='100%' - ) + table = Table(border=0, width='100%') table.AddRow([Center(Bold(FontAttr(title, size='+1')))]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, - style=f'background-color: {mm_cfg.WEB_HEADER_COLOR}', - role="cell") + bgcolor=mm_cfg.WEB_HEADER_COLOR) table.AddRow([_(f'''You have successfully created the mailing list {listname} and notification has been sent to the list owner {owner}. You can now:''')]) @@ -319,28 +297,25 @@ def sigterm_handler(signum, frame, mlist=mlist): doc.AddItem(table) + # Because the cgi module blows class Dummy(object): - def get(self, name, default): + def getfirst(self, name, default): return default dummy = Dummy() + def request_creation(doc, cgidata=dummy, errmsg=None): # What virtual domain are we using? hostname = Utils.get_domain() # Set up the document - title = _(f"Create a {hostname} Mailing List") + title = _(f'Create a {hostname} Mailing List') doc.SetTitle(title) - table = Table( - role="table", - aria_label=_("List Creation Form"), - style="border: 1px solid #ccc; border-collapse: collapse; width: 100%;" - ) + table = Table(border=0, width='100%') table.AddRow([Center(Bold(FontAttr(title, size='+1')))]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, - style=f'background-color: {mm_cfg.WEB_HEADER_COLOR}', - role="cell") + bgcolor=mm_cfg.WEB_HEADER_COLOR) # Add any error message if errmsg: table.AddRow([Header(3, Bold( @@ -369,82 +344,61 @@ def request_creation(doc, cgidata=dummy, errmsg=None): # Build the form for the necessary input GREY = mm_cfg.WEB_ADMINITEM_COLOR form = Form(Utils.ScriptURL('create')) - ftable = Table( - role="table", - aria_label=_("List Creation Form Fields"), - style="border: 1px solid #ccc; border-collapse: collapse; width: 100%;" - ) + ftable = Table(border=0, cols='2', width='100%', + cellspacing=3, cellpadding=4) ftable.AddRow([Center(Italic(_('List Identity')))]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, colspan=2, role="cell") + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, colspan=2) - listname = cgidata.get('listname', [''])[0] + listname = cgidata.getfirst('listname', '') + # MAS: Don't websafe twice. TextBox does it. ftable.AddRow([Label(_('Name of list:')), - TextBox('listname', listname, aria_label=_('Name of list'))]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, - style=f'background-color: {GREY}', - role="cell") - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, - style=f'background-color: {GREY}', - role="cell") - - owner = cgidata.get('owner', [''])[0] + TextBox('listname', listname)]) + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY) + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY) + + owner = cgidata.getfirst('owner', '') + # MAS: Don't websafe twice. TextBox does it. ftable.AddRow([Label(_('Initial list owner address:')), - TextBox('owner', owner, aria_label=_('Initial list owner address'))]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, - style=f'background-color: {GREY}', - role="cell") - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, - style=f'background-color: {GREY}', - role="cell") + TextBox('owner', owner)]) + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY) + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY) try: - autogen = int(cgidata.get('autogen', ['0'])[0]) + autogen = int(cgidata.getfirst('autogen', '0')) except ValueError: autogen = 0 ftable.AddRow([Label(_('Auto-generate initial list password?')), RadioButtonArray('autogen', (_('No'), _('Yes')), checked=autogen, - values=(0, 1), - aria_label=_('Auto-generate initial list password'))]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, - style=f'background-color: {GREY}', - role="cell") - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, - style=f'background-color: {GREY}', - role="cell") - - safepasswd = Utils.websafe(cgidata.get('password', [''])[0]) + values=(0, 1))]) + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY) + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY) + + safepasswd = Utils.websafe(cgidata.getfirst('password', '')) ftable.AddRow([Label(_('Initial list password:')), PasswordBox('password', safepasswd)]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, - style=f'background-color: {GREY}', - role="cell") - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, - style=f'background-color: {GREY}', - role="cell") - - safeconfirm = Utils.websafe(cgidata.get('confirm', [''])[0]) + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY) + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY) + + safeconfirm = Utils.websafe(cgidata.getfirst('confirm', '')) ftable.AddRow([Label(_('Confirm initial password:')), PasswordBox('confirm', safeconfirm)]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, - style=f'background-color: {GREY}', - role="cell") - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, - style=f'background-color: {GREY}', - role="cell") + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY) + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY) try: - notify = int(cgidata.get('notify', ['1'])[0]) + notify = int(cgidata.getfirst('notify', '1')) except ValueError: notify = 1 try: - moderate = int(cgidata.get('moderate', ['0'])[0]) + moderate = int(cgidata.getfirst('moderate', + mm_cfg.DEFAULT_DEFAULT_MEMBER_MODERATION)) except ValueError: moderate = mm_cfg.DEFAULT_DEFAULT_MEMBER_MODERATION ftable.AddRow([Center(Italic(_('List Characteristics')))]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, colspan=2, role="cell") + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, colspan=2) ftable.AddRow([ Label(_(f"""Should new members be quarantined before they @@ -453,12 +407,8 @@ def request_creation(doc, cgidata=dummy, errmsg=None): RadioButtonArray('moderate', (_('No'), _('Yes')), checked=moderate, values=(0,1))]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, - style=f'background-color: {GREY}', - role="cell") - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, - style=f'background-color: {GREY}', - role="cell") + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY) + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY) # Create the table of initially supported languages, sorted on the long # name of the language. revmap = {} @@ -481,41 +431,29 @@ def request_creation(doc, cgidata=dummy, errmsg=None): checked[langi] = 1 deflang = _(Utils.GetLanguageDescr(mm_cfg.DEFAULT_SERVER_LANGUAGE)) ftable.AddRow([Label(_( - '''Initial list of supported languages.

    Note that if you do not + f'''Initial list of supported languages.

    Note that if you do not select at least one initial language, the list will use the server default language of {deflang}''')), CheckBoxArray('langs', [_(Utils.GetLanguageDescr(L)) for L in langs], checked=checked, values=langs)]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, - style=f'background-color: {GREY}', - role="cell") - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, - style=f'background-color: {GREY}', - role="cell") + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY) + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY) ftable.AddRow([Label(_('Send "list created" email to list owner?')), RadioButtonArray('notify', (_('No'), _('Yes')), checked=notify, values=(0, 1))]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, - style=f'background-color: {GREY}', - role="cell") - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, - style=f'background-color: {GREY}', - role="cell") + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY) + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY) ftable.AddRow(['


    ']) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, colspan=2, role="cell") + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, colspan=2) ftable.AddRow([Label(_("List creator's (authentication) password:")), PasswordBox('auth')]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, - style=f'background-color: {GREY}', - role="cell") - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, - style=f'background-color: {GREY}', - role="cell") + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY) + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY) ftable.AddRow([Center(SubmitButton('doit', _('Create List'))), Center(SubmitButton('clear', _('Clear Form')))]) diff --git a/Mailman/Cgi/edithtml.py b/Mailman/Cgi/edithtml.py index 750a1239..6fdf7814 100644 --- a/Mailman/Cgi/edithtml.py +++ b/Mailman/Cgi/edithtml.py @@ -19,10 +19,9 @@ from __future__ import print_function import os -import urllib.parse +from Mailman.Utils import FieldStorage import errno import re -import sys from Mailman import Utils from Mailman import MailList @@ -39,6 +38,7 @@ AUTH_CONTEXTS = (mm_cfg.AuthListAdmin, mm_cfg.AuthSiteAdmin) + def main(): # Trick out pygettext since we want to mark template_data as translatable, # but we don't want to actually translate it here. @@ -84,7 +84,7 @@ def _(s): except Errors.MMListError as e: # Avoid cross-site scripting attacks safelistname = Utils.websafe(listname) - doc.AddItem(Header(2, _('No such list {safelistname}'))) + doc.AddItem(Header(2, _(f'No such list {safelistname}'))) # Send this with a 404 status. print('Status: 404 Not Found') print(doc.Format()) @@ -96,18 +96,10 @@ def _(s): doc.set_language(mlist.preferred_language) # Must be authenticated to get any farther + cgidata = FieldStorage() try: - if os.environ.get('REQUEST_METHOD') == 'POST': - content_length = int(os.environ.get('CONTENT_LENGTH', 0)) - if content_length > 0: - form_data = sys.stdin.buffer.read(content_length).decode('utf-8') - cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=True) - else: - cgidata = {} - else: - query_string = os.environ.get('QUERY_STRING', '') - cgidata = urllib.parse.parse_qs(query_string, keep_blank_values=True) - except Exception: + cgidata.getfirst('adminpw', '') + except TypeError: # Someone crafted a POST with a bad Content-Type:. doc.AddItem(Header(2, _("Error"))) doc.AddItem(Bold(_('Invalid options to CGI script.'))) @@ -120,19 +112,19 @@ def _(s): safe_params = ['VARHELP', 'adminpw', 'admlogin'] params = list(cgidata.keys()) if set(params) - set(safe_params): - csrf_checked = csrf_check(mlist, cgidata.get('csrf_token', [''])[0], + csrf_checked = csrf_check(mlist, cgidata.getfirst('csrf_token'), 'admin') else: csrf_checked = True # if password is present, void cookie to force password authentication. - if cgidata.get('adminpw', [''])[0]: + if cgidata.getfirst('adminpw'): os.environ['HTTP_COOKIE'] = '' csrf_checked = True # Editing the html for a list is limited to the list admin and site admin. if not mlist.WebAuthenticate((mm_cfg.AuthListAdmin, mm_cfg.AuthSiteAdmin), - cgidata.get('adminpw', [''])[0]): + cgidata.getfirst('adminpw', '')): if 'admlogin' in cgidata: # This is a re-authorization attempt msg = Bold(FontSize('+1', _('Authorization failed.'))).Format() @@ -149,8 +141,8 @@ def _(s): return # See if the user want to see this page in other language - language = cgidata.get('language', [''])[0] - if language not in mlist.available_languages: + language = cgidata.getfirst('language', '') + if language not in mlist.GetAvailableLanguages(): language = mlist.preferred_language i18n.set_language(language) doc.set_language(language) @@ -162,24 +154,26 @@ def _(s): if template == template_name: template_info = _(info) doc.SetTitle(_( - '{realname} -- Edit html for {template_info}')) + f'{realname} -- Edit html for {template_info}')) break else: # Avoid cross-site scripting attacks safetemplatename = Utils.websafe(template_name) doc.SetTitle(_('Edit HTML : Error')) - doc.AddItem(Header(2, _("{safetemplatename}: Invalid template"))) + doc.AddItem(Header(2, _(f"{safetemplatename}: Invalid template"))) doc.AddItem(mlist.GetMailmanFooter()) print(doc.Format()) return else: - # Use ParseTags for the template selection page - replacements = { - 'realname': realname, - 'templates': template_data - } - output = mlist.ParseTags('edithtml_select.html', replacements, language) - doc.AddItem(output) + doc.SetTitle(_(f'{realname} -- HTML Page Editing')) + doc.AddItem(Header(1, _(f'{realname} -- HTML Page Editing'))) + doc.AddItem(Header(2, _('Select page to edit:'))) + template_list = UnorderedList() + for (template, info) in template_data: + l = Link(mlist.GetScriptURL('edithtml') + '/' + template, _(info)) + template_list.AddItem(l) + doc.AddItem(FontSize("+2", template_list)) + doc.AddItem(mlist.GetMailmanFooter()) print(doc.Format()) return @@ -190,17 +184,15 @@ def _(s): else: doc.addError( _('The form lifetime has expired. (request forgery check)')) - # Use ParseTags for proper template processing - replacements = mlist.GetStandardReplacements(language) - output = mlist.ParseTags(template_name, replacements, language) - doc.AddItem(output) + FormatHTML(mlist, doc, template_name, template_info, lang=language) finally: doc.AddItem(mlist.GetMailmanFooter()) print(doc.Format()) + def FormatHTML(mlist, doc, template_name, template_info, lang=None): - if lang not in mlist.available_languages: + if lang not in mlist.GetAvailableLanguages(): lang = mlist.preferred_language lcset = Utils.GetCharSet(lang) doc.AddItem(Header(1,'%s:' % mlist.real_name)) @@ -217,7 +209,7 @@ def FormatHTML(mlist, doc, template_name, template_info, lang=None): doc.AddItem(FontSize("+1", backlink)) doc.AddItem('

    ') doc.AddItem('


    ') - if len(mlist.available_languages) > 1: + if len(mlist.GetAvailableLanguages()) > 1: langform = Form(mlist.GetScriptURL('edithtml') + '/' + template_name, mlist=mlist, contexts=AUTH_CONTEXTS) langform.AddItem( @@ -239,8 +231,9 @@ def FormatHTML(mlist, doc, template_name, template_info, lang=None): doc.AddItem(form) + def ChangeHTML(mlist, cgi_info, template_name, doc, lang=None): - if lang not in mlist.available_languages: + if lang not in mlist.GetAvailableLanguages(): lang = mlist.preferred_language if 'html_code' not in cgi_info: doc.AddItem(Header(3,_("Can't have empty html page."))) diff --git a/Mailman/Cgi/listinfo.py b/Mailman/Cgi/listinfo.py index 2c16acfe..62daf739 100644 --- a/Mailman/Cgi/listinfo.py +++ b/Mailman/Cgi/listinfo.py @@ -23,11 +23,8 @@ from builtins import str import os -import urllib.parse +from Mailman.Utils import FieldStorage import time -import sys -import ipaddress -from io import FileNotFoundError from Mailman import mm_cfg from Mailman import Utils @@ -35,99 +32,54 @@ from Mailman import Errors from Mailman import i18n from Mailman.htmlformat import * -from Mailman.Logging.Syslog import mailman_log -from Mailman.Utils import validate_ip_address +from Mailman.Logging.Syslog import syslog # Set up i18n _ = i18n._ i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) -def validate_listname(listname): - """Validate and sanitize a listname to prevent path traversal. - - Args: - listname: The listname to validate - - Returns: - tuple: (is_valid, sanitized_name, error_message) - """ - if not listname: - return False, None, _('List name is required') - - # Convert to lowercase and strip whitespace - listname = listname.lower().strip() - - # Basic validation - if not Utils.ValidateListName(listname): - return False, None, _('Invalid list name') - - # Check for path traversal attempts - if '..' in listname or '/' in listname or '\\' in listname: - return False, None, _('Invalid list name') - - return True, listname, None - - + def main(): parts = Utils.GetPathPieces() if not parts: listinfo_overview() return - # Validate and sanitize listname - is_valid, listname, error_msg = validate_listname(parts[0]) - if not is_valid: - print('Status: 400 Bad Request') - listinfo_overview(error_msg) - return - + listname = parts[0].lower() try: mlist = MailList.MailList(listname, lock=0) - except (Errors.MMListError, FileNotFoundError) as e: - # Avoid cross-site scripting attacks and information disclosure + except Errors.MMListError as e: + # Avoid cross-site scripting attacks safelistname = Utils.websafe(listname) # Send this with a 404 status. print('Status: 404 Not Found') listinfo_overview(_(f'No such list {safelistname}')) - mailman_log('error', 'listinfo: No such list "%s"', listname) - return - except Exception as e: - # Log the full error but don't expose it to the user - mailman_log('error', 'listinfo: Unexpected error for list "%s": %s', listname, str(e)) - print('Status: 500 Internal Server Error') - listinfo_overview(_('An error occurred processing your request')) + syslog('error', 'listinfo: No such list "%s": %s', listname, e) return # See if the user want to see this page in other language + cgidata = FieldStorage() try: - if os.environ.get('REQUEST_METHOD') == 'POST': - # Get the content length - content_length = int(os.environ.get('CONTENT_LENGTH', 0)) - # Read the form data - form_data = sys.stdin.read(content_length) - cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=True) - else: - query_string = os.environ.get('QUERY_STRING', '') - cgidata = urllib.parse.parse_qs(query_string, keep_blank_values=True) - except Exception as e: - # Log the error but don't expose details - mailman_log('error', 'listinfo: Error parsing form data: %s', str(e)) + language = cgidata.getfirst('language') + except TypeError: + # Someone crafted a POST with a bad Content-Type:. doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) doc.AddItem(Header(2, _("Error"))) - doc.AddItem(Bold(_('Invalid request.'))) + doc.AddItem(Bold(_('Invalid options to CGI script.'))) + # Send this with a 400 status. print('Status: 400 Bad Request') print(doc.Format()) return - language = cgidata.get('language', [None])[0] if not Utils.IsLanguage(language): language = mlist.preferred_language i18n.set_language(language) list_listinfo(mlist, language) + def listinfo_overview(msg=''): # Present the general listinfo overview hostname = Utils.get_domain() @@ -222,31 +174,27 @@ def listinfo_overview(msg=''): print(doc.Format()) -def list_listinfo(mlist, language): + +def list_listinfo(mlist, lang): # Generate list specific listinfo doc = HeadlessDocument() - doc.set_language(language) + doc.set_language(lang) - # First load the template - template_content, template_path = Utils.findtext('listinfo.html', lang=language, mlist=mlist) - if template_content is None: - mailman_log('error', 'Could not load template file: %s', template_path) - return - - # Then get replacements - replacements = mlist.GetStandardReplacements(language) + replacements = mlist.GetStandardReplacements(lang) - if not mlist.nondigestable: + if not mlist.digestable or not mlist.nondigestable: replacements[''] = "" replacements[''] = "" replacements[''] = '' else: replacements[''] = mlist.FormatDigestButton() - replacements[''] = mlist.FormatUndigestButton() + replacements[''] = \ + mlist.FormatUndigestButton() replacements[''] = '' replacements[''] = '' - replacements[''] = mlist.FormatPlainDigestsButton() + replacements[''] = \ + mlist.FormatPlainDigestsButton() replacements[''] = mlist.FormatMimeDigestsButton() replacements[''] = mlist.FormatBox('email', size=30) replacements[''] = mlist.FormatButton( @@ -256,81 +204,78 @@ def list_listinfo(mlist, language): replacements[''] = mlist.FormatFormStart( 'subscribe') if mm_cfg.SUBSCRIBE_FORM_SECRET: - # Get and validate IP address - ip = os.environ.get('REMOTE_ADDR', '') - is_valid, normalized_ip = validate_ip_address(ip) - if not is_valid: - ip = '' + now = str(int(time.time())) + remote = os.environ.get('HTTP_FORWARDED_FOR', + os.environ.get('HTTP_X_FORWARDED_FOR', + os.environ.get('REMOTE_ADDR', + 'w.x.y.z'))) + # Try to accept a range in case of load balancers, etc. (LP: #1447445) + if remote.find('.') >= 0: + # ipv4 - drop last octet + remote = remote.rsplit('.', 1)[0] else: - ip = normalized_ip + # ipv6 - drop last 16 (could end with :: in which case we just + # drop one : resulting in an invalid format, but it's only + # for our hash so it doesn't matter. + remote = remote.rsplit(':', 1)[0] # render CAPTCHA, if configured if isinstance(mm_cfg.CAPTCHAS, dict) and 'en' in mm_cfg.CAPTCHAS: (captcha_question, captcha_box, captcha_idx) = \ - Utils.captcha_display(mlist, language, mm_cfg.CAPTCHAS) + Utils.captcha_display(mlist, lang, mm_cfg.CAPTCHAS) pre_question = _( """Please answer the following question to prove that you are not a bot:""" ) replacements[''] = ( - """
    """ + """""" % (pre_question, captcha_question, captcha_box)) else: # just to have something to include in the hash below captcha_idx = '' + secret = mm_cfg.SUBSCRIBE_FORM_SECRET + ":" + now + ":" + captcha_idx + ":" + mlist.internal_name() + ":" + remote + hash_secret = Utils.sha_new(secret.encode('utf-8')).hexdigest() # fill form replacements[''] += ( '\n' - % (time.time(), captcha_idx, - Utils.sha_new((mm_cfg.SUBSCRIBE_FORM_SECRET + ":" + - str(time.time()) + ":" + - captcha_idx + ":" + - mlist.internal_name() + ":" + - ip).encode('utf-8')).hexdigest() - ) - ) + % (now, captcha_idx, hash_secret )) # Roster form substitutions replacements[''] = mlist.FormatFormStart('roster') + replacements[''] = mlist.FormatRosterOptionForUser(lang) # Options form substitutions replacements[''] = mlist.FormatFormStart('options') - replacements[''] = mlist.FormatEditingOption(language) + replacements[''] = mlist.FormatEditingOption(lang) replacements[''] = SubmitButton('UserOptions', _('Edit Options')).Format() # If only one language is enabled for this mailing list, omit the choice # buttons. - if len(mlist.available_languages) == 1: - listlangs = _(Utils.GetLanguageDescr(mlist.preferred_language)) + if len(mlist.GetAvailableLanguages()) == 1: + displang = '' else: - listlangs = mlist.GetLangSelectBox(language).Format() - replacements[''] = listlangs + displang = mlist.FormatButton('displang-button', + text = _("View this page in")) + replacements[''] = displang replacements[''] = mlist.FormatFormStart('listinfo') replacements[''] = mlist.FormatBox('fullname', size=30) # If reCAPTCHA is enabled, display its user interface if mm_cfg.RECAPTCHA_SITE_KEY: noscript = _('This form requires JavaScript.') replacements[''] = ( - """""" - % (noscript, language, mm_cfg.RECAPTCHA_SITE_KEY)) + % (noscript, lang, mm_cfg.RECAPTCHA_SITE_KEY)) else: replacements[''] = '' - # Process the template with replacements - try: - # Use ParseTags for proper template processing - output = mlist.ParseTags('listinfo.html', replacements, language) - doc.AddItem(output) - except Exception as e: - mailman_log('error', 'Error processing template: %s', str(e)) - return - - # Print the formatted document + # Do the expansion. + doc.AddItem(mlist.ParseTags('listinfo.html', replacements, lang)) print(doc.Format()) + if __name__ == "__main__": main() diff --git a/Mailman/Cgi/options.py b/Mailman/Cgi/options.py index ec25bf48..c8e1ced6 100644 --- a/Mailman/Cgi/options.py +++ b/Mailman/Cgi/options.py @@ -24,8 +24,9 @@ import re import sys import os -import urllib.parse +from Mailman.Utils import FieldStorage import signal +import urllib.request, urllib.parse, urllib.error from Mailman import mm_cfg from Mailman import Utils @@ -34,9 +35,8 @@ from Mailman import MemberAdaptor from Mailman import i18n from Mailman.htmlformat import * -from Mailman.Logging.Syslog import syslog, mailman_log +from Mailman.Logging.Syslog import syslog from Mailman.CSRFcheck import csrf_check -import traceback OR = '|' SLASH = '/' @@ -62,7 +62,7 @@ def main(): title = _('CGI script error') doc.SetTitle(title) doc.AddItem(Header(2, title)) - doc.addError(_('Invalid request method: %(method)s') % {'method': method}) + doc.addError(_(f'Invalid request method: {method}')) doc.AddItem('
    ') doc.AddItem(MailmanLogo()) print('Status: 405 Method Not Allowed') @@ -92,36 +92,17 @@ def main(): title = _('CGI script error') doc.SetTitle(title) doc.AddItem(Header(2, title)) - doc.addError(_('No such list %(safelistname)s') % {'safelistname': safelistname}) + doc.addError(_(f'No such list {safelistname}')) doc.AddItem('
    ') doc.AddItem(MailmanLogo()) # Send this with a 404 status. print('Status: 404 Not Found') print(doc.Format()) - mailman_log('error', 'options: No such list "%s": %s\n%s', listname, e, traceback.format_exc()) + syslog('error', 'options: No such list "%s": %s\n', listname, e) return # The total contents of the user's response - try: - if os.environ.get('REQUEST_METHOD') == 'POST': - content_length = int(os.environ.get('CONTENT_LENGTH', 0)) - if content_length > 0: - form_data = sys.stdin.buffer.read(content_length).decode('utf-8') - cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=True) - else: - cgidata = {} - else: - query_string = os.environ.get('QUERY_STRING', '') - cgidata = urllib.parse.parse_qs(query_string, keep_blank_values=True) - except Exception: - # Someone crafted a POST with a bad Content-Type:. - doc.AddItem(Header(2, _("Error"))) - doc.AddItem(Bold(_('Invalid options to CGI script.'))) - # Send this with a 400 status. - print('Status: 400 Bad Request') - print(doc.Format()) - mailman_log('error', 'options: Invalid form data: %s\n%s', str(e), traceback.format_exc()) - return + cgidata = FieldStorage(keep_blank_values=1) # CSRF check safe_params = ['displang-button', 'language', 'email', 'password', 'login', @@ -137,22 +118,25 @@ def main(): print(doc.Format()) return - # Set the language for the page - language = cgidata.get('language', [None])[0] + # Set the language for the page. If we're coming from the listinfo cgi, + # we might have a 'language' key in the cgi data. That was an explicit + # preference to view the page in, so we should honor that here. If that's + # not available, use the list's default language. + language = cgidata.getfirst('language') if not Utils.IsLanguage(language): language = mlist.preferred_language i18n.set_language(language) doc.set_language(language) if lenparts < 2: - user = cgidata.get('email', [''])[0].strip() + user = cgidata.getfirst('email', '').strip() if not user: # If we're coming from the listinfo page and we left the email # address field blank, it's not an error. Likewise if we're # coming from anywhere else. Only issue the error if we came # via one of our buttons. - if (cgidata.get('login', [''])[0] or cgidata.get('login-unsub', [''])[0] - or cgidata.get('login-remind', [''])[0]): + if (cgidata.getfirst('login') or cgidata.getfirst('login-unsub') + or cgidata.getfirst('login-remind')): doc.addError(_('No address given')) loginpage(mlist, doc, None, language) print(doc.Format()) @@ -162,6 +146,7 @@ def main(): # If a user submits a form or URL with post data or query fragments # with multiple occurrences of the same variable, we can get a list # here. Be as careful as possible. + # This is no longer required because of getfirst() above, but leave it. if isinstance(user, list) or isinstance(user, tuple): if len(user) == 0: user = '' @@ -180,19 +165,19 @@ def main(): # using public rosters, otherwise, we'll leak membership information. if not mlist.isMember(user): if mlist.private_roster == 0: - doc.addError(_('No such member: %(safeuser)s.') % {'safeuser': safeuser}) + doc.addError(_(f'No such member: {safeuser}.')) loginpage(mlist, doc, None, language) print(doc.Format()) - return + return # Avoid cross-site scripting attacks if set(params) - set(safe_params): - csrf_checked = csrf_check(mlist, cgidata.get('csrf_token', [''])[0], + csrf_checked = csrf_check(mlist, cgidata.getfirst('csrf_token'), Utils.UnobscureEmail(urllib.parse.unquote(user))) else: csrf_checked = True # if password is present, void cookie to force password authentication. - if cgidata.get('password', [''])[0]: + if cgidata.getfirst('password'): os.environ['HTTP_COOKIE'] = '' csrf_checked = True @@ -209,7 +194,7 @@ def main(): # And now we know the user making the request, so set things up to for the # user's stored preferred language, overridden by any form settings for # their new language preference. - userlang = cgidata.get('language', [None])[0] + userlang = cgidata.getfirst('language') if not Utils.IsLanguage(userlang): userlang = mlist.getMemberLanguage(user) doc.set_language(userlang) @@ -218,7 +203,7 @@ def main(): # Are we processing an unsubscription request from the login screen? msgc = _('If you are a list member, a confirmation email has been sent.') msgb = _('You already have a subscription pending confirmation') - msga = _("""If you are a list member, your unsubscription request has been + msga = _(f"""If you are a list member, your unsubscription request has been forwarded to the list administrator for approval.""") if 'login-unsub' in cgidata: # Because they can't supply a password for unsubscribing, we'll need @@ -248,11 +233,11 @@ def main(): # Not a member if mlist.private_roster == 0: # Public rosters - doc.addError(_('No such member: {safeuser}.')) + doc.addError(_(f'No such member: {safeuser}.')) else: - mailman_log('mischief', - 'Unsub attempt of non-member w/ private rosters: %s\n%s', - user, traceback.format_exc()) + syslog('mischief', + 'Unsub attempt of non-member w/ private rosters: %s', + user) if mlist.unsubscribe_policy: doc.addError(msga, tag='') else: @@ -272,18 +257,18 @@ def main(): # Not a member if mlist.private_roster == 0: # Public rosters - doc.addError(_('No such member: {safeuser}.')) + doc.addError(_(f'No such member: {safeuser}.')) else: - mailman_log('mischief', - 'Reminder attempt of non-member w/ private rosters: %s\n%s', - user, traceback.format_exc()) + syslog('mischief', + 'Reminder attempt of non-member w/ private rosters: %s', + user) doc.addError(msg, tag='') loginpage(mlist, doc, user, language) print(doc.Format()) return # Get the password from the form. - password = cgidata.get('password', [''])[0].strip() + password = cgidata.getfirst('password', '').strip() # Check authentication. We need to know if the credentials match the user # or the site admin, because they are the only ones who are allowed to # change things globally. Specifically, the list admin may not change @@ -310,15 +295,15 @@ def main(): os.environ.get('HTTP_X_FORWARDED_FOR', os.environ.get('REMOTE_ADDR', 'unidentified origin'))) - mailman_log('security', - 'Authorization failed (options): user=%s: list=%s: remote=%s\n%s', - user, listname, remote, traceback.format_exc()) + syslog('security', + 'Authorization failed (options): user=%s: list=%s: remote=%s', + user, listname, remote) # So as not to allow membership leakage, prompt for the email # address and the password here. if mlist.private_roster != 0: - mailman_log('mischief', - 'Login failure with private rosters: %s from %s\n%s', - user, remote, traceback.format_exc()) + syslog('mischief', + 'Login failure with private rosters: %s from %s', + user, remote) user = None # give an HTTP 401 for authentication failure print('Status: 401 Unauthorized') @@ -350,12 +335,12 @@ def main(): # See if this is VARHELP on topics. varhelp = None if 'VARHELP' in cgidata: - varhelp = cgidata['VARHELP'][0] + varhelp = cgidata['VARHELP'].value elif os.environ.get('QUERY_STRING'): # POST methods, even if their actions have a query string, don't get # put into FieldStorage's keys :-( - qs = cgidata.get('VARHELP') - if qs and isinstance(qs, list): + qs = urllib.parse.parse_qs(os.environ['QUERY_STRING']).get('VARHELP') + if qs and type(qs) == list: varhelp = qs[0] if varhelp: # Sanitize the topic name. @@ -416,18 +401,18 @@ def main(): if 'change-of-address' in cgidata: # We could be changing the user's full name, email address, or both. # Watch out for non-ASCII characters in the member's name. - membername = cgidata.get('fullname', [''])[0] + membername = cgidata.getfirst('fullname') # Canonicalize the member's name membername = Utils.canonstr(membername, language) - newaddr = cgidata.get('new-address', [''])[0] - confirmaddr = cgidata.get('confirm-address', [''])[0] + newaddr = cgidata.getfirst('new-address') + confirmaddr = cgidata.getfirst('confirm-address') oldname = mlist.getMemberName(user) set_address = set_membername = 0 # See if the user wants to change their email address globally. The # list admin is /not/ allowed to make global changes. - globally = cgidata.get('changeaddr-globally', [''])[0] + globally = cgidata.getfirst('changeaddr-globally') if globally and not is_user_or_siteadmin: doc.addError(_(f"""The list administrator may not change the names or addresses for this user's other subscriptions. However, the @@ -478,7 +463,7 @@ def main(): else: options_page( mlist, doc, user, cpuser, userlang, - _('The new address is already a member: {newaddr}')) + _(f'The new address is already a member: {newaddr}')) print(doc.Format()) return set_address = 1 @@ -498,7 +483,7 @@ def sigterm_handler(signum, frame, mlist=mlist): if cpuser is None: cpuser = user # Register the pending change after the list is locked - msg += _('A confirmation message has been sent to {newaddr}. ') + msg += _(f'A confirmation message has been sent to {newaddr}. ') mlist.Lock() try: try: @@ -511,7 +496,7 @@ def sigterm_handler(signum, frame, mlist=mlist): except Errors.MMHostileAddress: msg = _('Illegal email address provided') except Errors.MMAlreadyAMember: - msg = _('{newaddr} is already a member of the list.') + msg = _(f'{newaddr} is already a member of the list.') except Errors.MembershipIsBanned: owneraddr = mlist.GetOwnerEmail() msg = _(f"""{newaddr} is banned from this list. If you @@ -540,8 +525,8 @@ def sigterm_handler(signum, frame, mlist=mlist): options_page(mlist, doc, user, cpuser, userlang) print(doc.Format()) return - newpw = cgidata.get('newpw', [''])[0].strip() - confirmpw = cgidata.get('confpw', [''])[0].strip() + newpw = cgidata.getfirst('newpw', '').strip() + confirmpw = cgidata.getfirst('confpw', '').strip() if not newpw or not confirmpw: options_page(mlist, doc, user, cpuser, userlang, _('Passwords may not be blank')) @@ -555,7 +540,7 @@ def sigterm_handler(signum, frame, mlist=mlist): # See if the user wants to change their passwords globally, however # the list admin is /not/ allowed to change passwords globally. - pw_globally = cgidata.get('pw-globally', [''])[0] + pw_globally = cgidata.getfirst('pw-globally') if pw_globally and not is_user_or_siteadmin: doc.addError(_(f"""The list administrator may not change the password for this user's other subscriptions. However, the @@ -580,7 +565,7 @@ def sigterm_handler(signum, frame, mlist=mlist): if 'unsub' in cgidata: # Was the confirming check box turned on? - if not cgidata.get('unsubconfirm', [0])[0]: + if not cgidata.getfirst('unsubconfirm'): options_page( mlist, doc, user, cpuser, userlang, _(f'''You must confirm your unsubscription request by turning @@ -662,7 +647,7 @@ def sigterm_handler(signum, frame, mlist=mlist): ('nodupes', mm_cfg.DontReceiveDuplicates), ): try: - newval = int(cgidata.get(item, [''])[0]) + newval = int(cgidata.getfirst(item)) except (TypeError, ValueError): newval = None @@ -690,7 +675,7 @@ def sigterm_handler(signum, frame, mlist=mlist): newvals.append((flag, newval)) # The user language is handled a little differently - if userlang not in mlist.available_languages: + if userlang not in mlist.GetAvailableLanguages(): newvals.append((SETLANGUAGE, mlist.preferred_language)) else: newvals.append((SETLANGUAGE, userlang)) @@ -698,7 +683,7 @@ def sigterm_handler(signum, frame, mlist=mlist): # Process user selected topics, but don't make the changes to the # MailList object; we must do that down below when the list is # locked. - topicnames = cgidata.get('usertopic', [''])[0] + topicnames = cgidata.getvalue('usertopic') if topicnames: # Some topics were selected. topicnames can actually be a string # or a list of strings depending on whether more than one topic @@ -752,7 +737,7 @@ def __bool__(self): # The enable/disable option and the password remind option may have # their global flags sets. - if cgidata.get('deliver-globally', [''])[0]: + if cgidata.getfirst('deliver-globally'): # Yes, this is inefficient, but the list is so small it shouldn't # make much of a difference. for flag, newval in newvals: @@ -760,19 +745,19 @@ def __bool__(self): globalopts.enable = newval break - if cgidata.get('remind-globally', [''])[0]: + if cgidata.getfirst('remind-globally'): for flag, newval in newvals: if flag == mm_cfg.SuppressPasswordReminder: globalopts.remind = newval break - if cgidata.get('nodupes-globally', [''])[0]: + if cgidata.getfirst('nodupes-globally'): for flag, newval in newvals: if flag == mm_cfg.DontReceiveDuplicates: globalopts.nodupes = newval break - if cgidata.get('mime-globally', [''])[0]: + if cgidata.getfirst('mime-globally'): for flag, newval in newvals: if flag == mm_cfg.DisableMime: globalopts.mime = newval @@ -816,83 +801,7 @@ def __bool__(self): print(doc.Format()) -def process_form(mlist, cgidata, doc, form): - """Process the form submission.""" - # Get the user's email address - email = cgidata.get('email', [''])[0] - if isinstance(email, bytes): - email = email.decode('utf-8', 'replace') - email = email.strip().lower() - - # Get the user's password - password = cgidata.get('password', [''])[0] - if isinstance(password, bytes): - password = password.decode('utf-8', 'replace') - password = password.strip() - - # Get the user's full name - fullname = cgidata.get('fullname', [''])[0] - if isinstance(fullname, bytes): - fullname = fullname.decode('utf-8', 'replace') - fullname = fullname.strip() - - # Get the user's options - options = {} - for key in cgidata: - if key.startswith('option_'): - value = cgidata.get(key, [''])[0] - if isinstance(value, bytes): - value = value.decode('utf-8', 'replace') - options[key[7:]] = value.strip() - - # Validate the email address - if not email: - doc.addError(_('You must provide an email address')) - return - - if not Utils.ValidateEmail(email): - doc.addError(_('Invalid email address: %(email)s') % {'email': email}) - return - - # Validate the password - if not password: - doc.addError(_('You must provide a password')) - return - - # Validate the full name - if not fullname: - doc.addError(_('You must provide your full name')) - return - - # Try to get the member - try: - member = mlist.getMember(email) - except Errors.NotAMemberError: - doc.addError(_('You are not a member of this list')) - return - - # Verify the password - if not mlist.Authenticate((email, password)): - doc.addError(_('Invalid password')) - return - - # Update the member's options - try: - mlist.Lock() - try: - member.setFullName(fullname) - for key, value in options.items(): - member.setOption(key, value) - finally: - mlist.Unlock() - except Exception as e: - doc.addError(_('Error updating options: %(error)s') % {'error': str(e)}) - return - - # Show success message - doc.addItem(_('Your options have been updated')) - - + def options_page(mlist, doc, user, cpuser, userlang, message=''): # The bulk of the document will come from the options.html template, which # includes it's own html armor (head tags, etc.). Suppress the head that @@ -987,10 +896,7 @@ def options_page(mlist, doc, user, cpuser, userlang, message=''): units = _('days') else: units = _('day') - replacements[''] = _('%(days)d %(units)s') % { - 'days': days, - 'units': units - } + replacements[''] = _(f'%(days)d {units}') replacements[''] = mlist.FormatBox('new-address') replacements[''] = mlist.FormatBox( @@ -1006,28 +912,20 @@ def options_page(mlist, doc, user, cpuser, userlang, message=''): # but the user still wants to get that topic message? usertopics = mlist.getMemberTopics(user) if mlist.topics: - table = Table( - role="table", - aria_label=_("Topic Filter Details"), - border=3, - width='100%' - ) - table.AddRow([Center(Bold(_('Topic filter details')))]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, - style=f'background-color: {mm_cfg.WEB_SUBHEADER_COLOR}', - role="cell") - table.AddRow([Bold(Label(_('Name:'))), - Utils.websafe(name)]) - table.AddRow([Bold(Label(_('Pattern (as regexp):'))), - '
    ' + Utils.websafe(OR.join(pattern.splitlines()))
    -                       + '
    ']) - table.AddRow([Bold(Label(_('Description:'))), - Utils.websafe(description)]) - # Make colors look nice - for row in range(1, 4): - table.AddCellInfo(row, 0, - style=f'background-color: {mm_cfg.WEB_ADMINITEM_COLOR}', - role="cell") + table = Table(border="0") + for name, pattern, description, emptyflag in mlist.topics: + if emptyflag: + continue + quotedname = urllib.parse.quote_plus(name) + details = Link(mlist.GetScriptURL('options') + + '/%s/?VARHELP=%s' % (user, quotedname), + ' (Details)') + if name in usertopics: + checked = 1 + else: + checked = 0 + table.AddRow([CheckBox('usertopic', quotedname, checked=checked), + name + details.Format()]) topicsfield = table.Format() else: topicsfield = _('No topics defined') @@ -1049,36 +947,28 @@ def options_page(mlist, doc, user, cpuser, userlang, message=''): page_text = DIGRE.sub('', page_text) doc.AddItem(page_text) - + def loginpage(mlist, doc, user, lang): realname = mlist.real_name actionurl = mlist.GetScriptURL('options') if user is None: - title = _('{realname} list: member options login page') + title = _(f'{realname} list: member options login page') extra = _('email address and ') else: safeuser = Utils.websafe(user) - title = _('{realname} list: member options for user {safeuser}') + title = _(f'{realname} list: member options for user {safeuser}') obuser = Utils.ObscureEmail(user) extra = '' # Set up the title doc.SetTitle(title) # We use a subtable here so we can put a language selection box in - table = Table( - role="table", - aria_label=_("Member Options"), - width='100%', - border=0, - cellspacing=4, - cellpadding=5 - ) + table = Table(width='100%', border=0, cellspacing=4, cellpadding=5) # If only one language is enabled for this mailing list, omit the choice # buttons. table.AddRow([Center(Header(2, title))]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, - style=f'background-color: {mm_cfg.WEB_HEADER_COLOR}', - role="cell") - if len(mlist.available_languages) > 1: + bgcolor=mm_cfg.WEB_HEADER_COLOR) + if len(mlist.GetAvailableLanguages()) > 1: langform = Form(actionurl) langform.AddItem(SubmitButton('displang-button', _('View this page in'))) @@ -1091,14 +981,7 @@ def loginpage(mlist, doc, user, lang): # Set up the login page form = Form(actionurl) form.AddItem(Hidden('language', lang)) - table = Table( - role="table", - aria_label=_("Login Form"), - width='100%', - border=0, - cellspacing=4, - cellpadding=5 - ) + table = Table(width='100%', border=0, cellspacing=4, cellpadding=5) table.AddRow([_(f"""In order to change your membership option, you must first log in by giving your {extra}membership password in the section below. If you don't remember your membership password, you can have it @@ -1111,14 +994,7 @@ def loginpage(mlist, doc, user, lang): effect. """)]) # Password and login button - ptable = Table( - role="table", - aria_label=_("Password Form"), - width='50%', - border=0, - cellspacing=4, - cellpadding=5 - ) + ptable = Table(width='50%', border=0, cellspacing=4, cellpadding=5) if user is None: ptable.AddRow([Label(_('Email address:')), TextBox('email', size=20)]) @@ -1132,8 +1008,7 @@ def loginpage(mlist, doc, user, lang): # Unsubscribe section table.AddRow([Center(Header(2, _('Unsubscribe')))]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, - style=f'background-color: {mm_cfg.WEB_HEADER_COLOR}', - role="cell") + bgcolor=mm_cfg.WEB_HEADER_COLOR) table.AddRow([_(f"""By clicking on the Unsubscribe button, a confirmation message will be emailed to you. This message will have a @@ -1145,8 +1020,7 @@ def loginpage(mlist, doc, user, lang): # Password reminder section table.AddRow([Center(Header(2, _('Password reminder')))]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, - style=f'background-color: {mm_cfg.WEB_HEADER_COLOR}', - role="cell") + bgcolor=mm_cfg.WEB_HEADER_COLOR) table.AddRow([_(f"""By clicking on the Remind button, your password will be emailed to you.""")]) @@ -1158,6 +1032,7 @@ def loginpage(mlist, doc, user, lang): doc.AddItem(mlist.GetMailmanFooter()) + def lists_of_member(mlist, user): hostname = mlist.host_name onlists = [] @@ -1174,6 +1049,7 @@ def lists_of_member(mlist, user): return onlists + def change_password(mlist, user, newpw, confirmpw): # This operation requires the list lock, so let's set up the signal # handling so the list lock will get released when the user hits the @@ -1200,16 +1076,15 @@ def sigterm_handler(signum, frame, mlist=mlist): mlist.Unlock() + def global_options(mlist, user, globalopts): # Is there anything to do? - has_changes = False for attr in dir(globalopts): if attr.startswith('_'): continue if getattr(globalopts, attr) is not None: - has_changes = True break - if not has_changes: + else: return def sigterm_handler(signum, frame, mlist=mlist): @@ -1228,20 +1103,24 @@ def sigterm_handler(signum, frame, mlist=mlist): if globalopts.enable is not None: mlist.setDeliveryStatus(user, globalopts.enable) + if globalopts.remind is not None: mlist.setMemberOption(user, mm_cfg.SuppressPasswordReminder, - globalopts.remind) + globalopts.remind) + if globalopts.nodupes is not None: mlist.setMemberOption(user, mm_cfg.DontReceiveDuplicates, - globalopts.nodupes) + globalopts.nodupes) + if globalopts.mime is not None: - mlist.setMemberOption(user, mm_cfg.DisableMime, - globalopts.mime) + mlist.setMemberOption(user, mm_cfg.DisableMime, globalopts.mime) + mlist.Save() finally: mlist.Unlock() + def topic_details(mlist, doc, user, cpuser, userlang, varhelp): # Find out which topic the user wants to get details of reflist = varhelp.split('/') @@ -1257,20 +1136,14 @@ def topic_details(mlist, doc, user, cpuser, userlang, varhelp): if not name: options_page(mlist, doc, user, cpuser, userlang, - _('Requested topic is not valid: {topicname}')) + _(f'Requested topic is not valid: {topicname}')) print(doc.Format()) return - table = Table( - role="table", - aria_label=_("Topic Filter Details"), - border=3, - width='100%' - ) + table = Table(border=3, width='100%') table.AddRow([Center(Bold(_('Topic filter details')))]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, - style=f'background-color: {mm_cfg.WEB_SUBHEADER_COLOR}', - role="cell") + bgcolor=mm_cfg.WEB_SUBHEADER_COLOR) table.AddRow([Bold(Label(_('Name:'))), Utils.websafe(name)]) table.AddRow([Bold(Label(_('Pattern (as regexp):'))), @@ -1280,9 +1153,7 @@ def topic_details(mlist, doc, user, cpuser, userlang, varhelp): Utils.websafe(description)]) # Make colors look nice for row in range(1, 4): - table.AddCellInfo(row, 0, - style=f'background-color: {mm_cfg.WEB_ADMINITEM_COLOR}', - role="cell") + table.AddCellInfo(row, 0, bgcolor=mm_cfg.WEB_ADMINITEM_COLOR) options_page(mlist, doc, user, cpuser, userlang, table.Format()) print(doc.Format()) diff --git a/Mailman/Cgi/private.py b/Mailman/Cgi/private.py index ada0815c..034ed6f4 100644 --- a/Mailman/Cgi/private.py +++ b/Mailman/Cgi/private.py @@ -20,7 +20,7 @@ import os import sys -import urllib.parse +from Mailman.Utils import FieldStorage import mimetypes from Mailman import mm_cfg @@ -39,60 +39,23 @@ SLASH = '/' -def validate_listname(listname): - """Validate and sanitize a listname to prevent path traversal. - - Args: - listname: The listname to validate - - Returns: - tuple: (is_valid, sanitized_name, error_message) - """ - if not listname: - return False, None, _('List name is required') - - # Convert to lowercase and strip whitespace - listname = listname.lower().strip() - - # Basic validation - if not Utils.ValidateListName(listname): - return False, None, _('Invalid list name') - - # Check for path traversal attempts - if '..' in listname or '/' in listname or '\\' in listname: - return False, None, _('Invalid list name') - - return True, listname, None - - + def true_path(path): - """Ensure that the path is safe by removing .. and other dangerous components. - - Args: - path: The path to sanitize - - Returns: - str: The sanitized path or None if invalid - """ - if not path: - return None - - # Remove any leading/trailing slashes - path = path.strip('/') - - # Split into components and filter out dangerous parts - parts = [x for x in path.split('/') if x and x not in ('.', '..')] - - # Reconstruct the path - return '/'.join(parts) + "Ensure that the path is safe by removing .." + # Workaround for path traverse vulnerability. Unsuccessful attempts will + # be logged in logs/error. + parts = [x for x in path.split(SLASH) if x not in ('.', '..')] + return SLASH.join(parts)[1:] + def guess_type(url, strict): if hasattr(mimetypes, 'common_types'): return mimetypes.guess_type(url, strict) return mimetypes.guess_type(url) + def main(): doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) @@ -104,69 +67,61 @@ def main(): print(doc.Format()) return - # Validate listname - is_valid, listname, error_msg = validate_listname(parts[0]) - if not is_valid: - doc.SetTitle(_("Private Archive Error")) - doc.AddItem(Header(3, error_msg)) - print('Status: 400 Bad Request') - print(doc.Format()) - syslog('mischief', 'Private archive invalid path: %s', parts[0]) - return - - # Validate and sanitize the full path - path = os.environ.get('PATH_INFO', '') + path = os.environ.get('PATH_INFO') tpath = true_path(path) - if not tpath: - msg = _('Private archive - Invalid path') + if tpath != path[1:]: + msg = _('Private archive - "./" and "../" not allowed in URL.') doc.SetTitle(msg) doc.AddItem(Header(2, msg)) - print('Status: 400 Bad Request') print(doc.Format()) - syslog('mischief', 'Private archive invalid path: %s', path) + syslog('mischief', 'Private archive hostile path: %s', path) return - # BAW: This needs to be converted to the Site module abstraction - true_filename = os.path.join(mm_cfg.PRIVATE_ARCHIVE_FILE_DIR, tpath) + true_filename = os.path.join( + mm_cfg.PRIVATE_ARCHIVE_FILE_DIR, tpath) + + listname = parts[0].lower() + mboxfile = '' + if len(parts) > 1: + mboxfile = parts[1] + + # See if it's the list's mbox file is being requested + if listname.endswith('.mbox') and mboxfile.endswith('.mbox') and \ + listname[:-5] == mboxfile[:-5]: + listname = listname[:-5] + else: + mboxfile = '' + + # If it's a directory, we have to append index.html in this script. We + # must also check for a gzipped file, because the text archives are + # usually stored in compressed form. + if os.path.isdir(true_filename): + true_filename = true_filename + '/index.html' + if not os.path.exists(true_filename) and \ + os.path.exists(true_filename + '.gz'): + true_filename = true_filename + '.gz' try: mlist = MailList.MailList(listname, lock=0) except Errors.MMListError as e: - # Avoid cross-site scripting attacks and information disclosure + # Avoid cross-site scripting attacks safelistname = Utils.websafe(listname) - msg = _('No such list {safelistname}') - doc.SetTitle(_("Private Archive Error - {msg}")) + msg = _(f'No such list {safelistname}') + doc.SetTitle(_(f"Private Archive Error - {msg}")) doc.AddItem(Header(2, msg)) # Send this with a 404 status. print('Status: 404 Not Found') print(doc.Format()) - syslog('error', 'private: No such list "%s"', listname) - return - except Exception as e: - # Log the full error but don't expose it to the user - syslog('error', 'private: Unexpected error for list "%s": %s', listname, str(e)) - doc.SetTitle(_("Private Archive Error")) - doc.AddItem(Header(2, _('An error occurred processing your request'))) - print('Status: 500 Internal Server Error') - print(doc.Format()) + syslog('error', 'private: No such list "%s": %s\n', listname, e) return i18n.set_language(mlist.preferred_language) doc.set_language(mlist.preferred_language) - # Parse form data + cgidata = FieldStorage() try: - if os.environ.get('REQUEST_METHOD') == 'POST': - content_length = int(os.environ.get('CONTENT_LENGTH', 0)) - if content_length > 0: - form_data = sys.stdin.buffer.read(content_length).decode('utf-8') - cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=True) - else: - cgidata = {} - else: - query_string = os.environ.get('QUERY_STRING', '') - cgidata = urllib.parse.parse_qs(query_string, keep_blank_values=True) - except Exception: + username = cgidata.getfirst('username', '').strip() + except TypeError: # Someone crafted a POST with a bad Content-Type:. doc.AddItem(Header(2, _("Error"))) doc.AddItem(Bold(_('Invalid options to CGI script.'))) @@ -174,9 +129,7 @@ def main(): print('Status: 400 Bad Request') print(doc.Format()) return - - username = cgidata.get('username', [''])[0].strip() - password = cgidata.get('password', [''])[0] + password = cgidata.getfirst('password', '') is_auth = 0 realname = mlist.real_name @@ -219,10 +172,11 @@ def main(): # Output the password form charset = Utils.GetCharSet(mlist.preferred_language) print('Content-type: text/html; charset=' + charset + '\n\n') - print('') # Put the original full path in the authorization form, but avoid # trailing slash if we're not adding parts. We add it below. action = mlist.GetScriptURL('private', absolute=1) + if mboxfile: + action += '.mbox' if parts[1:]: action = os.path.join(action, SLASH.join(parts[1:])) # If we added '/index.html' to true_filename, add a slash to the URL. @@ -234,15 +188,13 @@ def main(): # page don't work. if true_filename.endswith('/index.html') and parts[-1] != 'index.html': action += SLASH - # Use ParseTags for proper template processing - replacements = { - 'action': Utils.websafe(action), - 'realname': mlist.real_name, - 'message': message - } - # Use list's preferred language as fallback before authentication - output = mlist.ParseTags('private.html', replacements, mlist.preferred_language) - print(output) + # Escape web input parameter to avoid cross-site scripting. + print(Utils.maketext( + 'private.html', + {'action' : Utils.websafe(action), + 'realname': mlist.real_name, + 'message' : message, + }, mlist=mlist)) return lang = mlist.getMemberLanguage(username) @@ -254,11 +206,15 @@ def main(): ctype, enc = guess_type(path, strict=0) if ctype is None: ctype = 'text/html' - if true_filename.endswith('.gz'): + if mboxfile: + f = open(os.path.join(mlist.archive_dir() + '.mbox', + mlist.internal_name() + '.mbox')) + ctype = 'text/plain' + elif true_filename.endswith('.gz'): import gzip f = gzip.open(true_filename, 'r') else: - f = open(true_filename, 'r') + f = open(true_filename, 'rb') except IOError: msg = _('Private archive file not found') doc.SetTitle(msg) @@ -267,6 +223,16 @@ def main(): print(doc.Format()) syslog('error', 'Private archive file not found: %s', true_filename) else: - print('Content-type: %s\n' % ctype) - sys.stdout.write(f.read()) + content = f.read() f.close() + buffered = sys.stdout.getvalue() + sys.stdout.truncate(0) + sys.stdout.seek(0) + orig_stdout = sys.stdout + sys.stdout = sys.__stdout__ + sys.stdout.write(buffered) + print('Content-type: %s\n' % ctype) + sys.stdout.flush() + sys.stdout.buffer.write(content) + sys.stdout.flush() + sys.stdout = orig_stdout diff --git a/Mailman/Cgi/rmlist.py b/Mailman/Cgi/rmlist.py index cd84142c..103cc4f7 100644 --- a/Mailman/Cgi/rmlist.py +++ b/Mailman/Cgi/rmlist.py @@ -18,7 +18,7 @@ from __future__ import print_function import os -import urllib.parse +from Mailman.Utils import FieldStorage import sys import errno import shutil @@ -36,22 +36,15 @@ i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + def main(): doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + cgidata = FieldStorage() try: - if os.environ.get('REQUEST_METHOD') == 'POST': - content_length = int(os.environ.get('CONTENT_LENGTH', 0)) - if content_length > 0: - form_data = sys.stdin.buffer.read(content_length).decode('utf-8') - cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=True) - else: - cgidata = {} - else: - query_string = os.environ.get('QUERY_STRING', '') - cgidata = urllib.parse.parse_qs(query_string, keep_blank_values=True) - except Exception: + cgidata.getfirst('password', '') + except TypeError: # Someone crafted a POST with a bad Content-Type:. doc.AddItem(Header(2, _("Error"))) doc.AddItem(Bold(_('Invalid options to CGI script.'))) @@ -80,8 +73,8 @@ def main(): except Errors.MMListError as e: # Avoid cross-site scripting attacks safelistname = Utils.websafe(listname) - title = _('No such list {safelistname}') - doc.SetTitle(_('No such list {safelistname}')) + title = _(f'No such list {safelistname}') + doc.SetTitle(_(f'No such list {safelistname}')) doc.AddItem( Header(3, Bold(FontAttr(title, color='#ff0000', size='+2')))) @@ -119,10 +112,11 @@ def main(): print(doc.Format()) + def process_request(doc, cgidata, mlist): - password = cgidata.get('password', [''])[0].strip() + password = cgidata.getfirst('password', '').strip() try: - delarchives = int(cgidata.get('delarchives', ['0'])[0]) + delarchives = int(cgidata.getfirst('delarchives', '0')) except ValueError: delarchives = 0 @@ -186,16 +180,10 @@ def process_request(doc, cgidata, mlist): title = _('Mailing list deletion results') doc.SetTitle(title) - table = Table( - role="table", - aria_label=_("List Deletion Results"), - border=0, - width='100%' - ) + table = Table(border=0, width='100%') table.AddRow([Center(Bold(FontAttr(title, size='+1')))]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, - style=f'background-color: {mm_cfg.WEB_HEADER_COLOR}', - role="cell") + bgcolor=mm_cfg.WEB_HEADER_COLOR) if not problems: table.AddRow([_(f'''You have successfully deleted the mailing list {listname}.''')]) @@ -215,21 +203,16 @@ def process_request(doc, cgidata, mlist): doc.AddItem(MailmanLogo()) + def request_deletion(doc, mlist, errmsg=None): realname = mlist.real_name - title = _('Permanently remove mailing list {realname}') - doc.SetTitle(_('Permanently remove mailing list {realname}')) + title = _(f'Permanently remove mailing list {realname}') + doc.SetTitle(_(f'Permanently remove mailing list {realname}')) - table = Table( - role="table", - aria_label=_("List Deletion Form"), - border=0, - width='100%' - ) + table = Table(border=0, width='100%') table.AddRow([Center(Bold(FontAttr(title, size='+1')))]) table.AddCellInfo(table.GetCurrentRowIndex(), 0, - style=f'background-color: {mm_cfg.WEB_HEADER_COLOR}', - role="cell") + bgcolor=mm_cfg.WEB_HEADER_COLOR) # Add any error message if errmsg: @@ -255,38 +238,26 @@ def request_deletion(doc, mlist, errmsg=None): """)]) GREY = mm_cfg.WEB_ADMINITEM_COLOR form = Form(mlist.GetScriptURL('rmlist')) - ftable = Table( - role="table", - aria_label=_("List Deletion Form Fields"), - border=0, - cols='2', - width='100%', - cellspacing=3, - cellpadding=4 - ) + ftable = Table(border=0, cols='2', width='100%', + cellspacing=3, cellpadding=4) ftable.AddRow([Label(_('List password:')), PasswordBox('password')]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, - style=f'background-color: {GREY}', - role="cell") - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, - style=f'background-color: {GREY}', - role="cell") + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY) + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY) - ftable.AddRow([Label(_('Delete archives?')), - RadioButtonArray('delarchives', - (_('No'), _('Yes')), - checked=0, - values=(0, 1))]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, - style=f'background-color: {GREY}', - role="cell") - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, - style=f'background-color: {GREY}', - role="cell") + ftable.AddRow([Label(_('Also delete archives?')), + RadioButtonArray('delarchives', (_('No'), _('Yes')), + checked=0, values=(0, 1))]) + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY) + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY) - ftable.AddRow([Center(SubmitButton('doit', _('Delete List')))]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, colspan=2, role="cell") + ftable.AddRow([Center(Link( + mlist.GetScriptURL('admin'), + _('Cancel and return to list administration')))]) + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, colspan=2) + + ftable.AddRow([Center(SubmitButton('doit', _('Delete this list')))]) + ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, colspan=2) form.AddItem(ftable) table.AddRow([form]) doc.AddItem(table) diff --git a/Mailman/Cgi/roster.py b/Mailman/Cgi/roster.py index f7a30950..d90e4de6 100644 --- a/Mailman/Cgi/roster.py +++ b/Mailman/Cgi/roster.py @@ -26,7 +26,7 @@ import sys import os -import urllib.parse +from Mailman.Utils import FieldStorage import urllib.request, urllib.parse, urllib.error from Mailman import mm_cfg @@ -42,6 +42,7 @@ i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + def main(): parts = Utils.GetPathPieces() if not parts: @@ -56,23 +57,16 @@ def main(): safelistname = Utils.websafe(listname) # Send this with a 404 status. print('Status: 404 Not Found') - error_page(_('No such list {safelistname}')) + error_page(_(f'No such list {safelistname}')) syslog('error', 'roster: No such list "%s": %s', listname, e) return - # Parse form data + cgidata = FieldStorage() + + # messages in form should go in selected language (if any...) try: - if os.environ.get('REQUEST_METHOD') == 'POST': - content_length = int(os.environ.get('CONTENT_LENGTH', 0)) - if content_length > 0: - form_data = sys.stdin.buffer.read(content_length).decode('utf-8') - cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=True) - else: - cgidata = {} - else: - query_string = os.environ.get('QUERY_STRING', '') - cgidata = urllib.parse.parse_qs(query_string, keep_blank_values=True) - except Exception: + lang = cgidata.getfirst('language') + except TypeError: # Someone crafted a POST with a bad Content-Type:. doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) @@ -83,8 +77,6 @@ def main(): print(doc.Format()) return - # messages in form should go in selected language (if any...) - lang = cgidata.get('language', [None])[0] if not Utils.IsLanguage(lang): lang = mlist.preferred_language i18n.set_language(lang) @@ -94,8 +86,8 @@ def main(): # "admin"-only, then we try to cookie authenticate the user, and failing # that, we check roster-email and roster-pw fields for a valid password. # (also allowed: the list moderator, the list admin, and the site admin). - password = cgidata.get('roster-pw', [''])[0].strip() - addr = cgidata.get('roster-email', [''])[0].strip() + password = cgidata.getfirst('roster-pw', '').strip() + addr = cgidata.getfirst('roster-email', '').strip() list_hidden = (not mlist.WebAuthenticate((mm_cfg.AuthUser,), password, addr) and mlist.WebAuthenticate((mm_cfg.AuthListModerator, @@ -124,7 +116,7 @@ def main(): doc.set_language(lang) # Send this with a 401 status. print('Status: 401 Unauthorized') - error_page_doc(doc, _('{realname} roster authentication failed.')) + error_page_doc(doc, _(f'{realname} roster authentication failed.')) doc.AddItem(mlist.GetMailmanFooter()) print(doc.Format()) remote = os.environ.get('HTTP_FORWARDED_FOR', @@ -149,6 +141,7 @@ def main(): print(doc.Format()) + def error_page(errmsg): doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) diff --git a/Mailman/Cgi/subscribe.py b/Mailman/Cgi/subscribe.py index 4c39b6b1..1aff1ef8 100644 --- a/Mailman/Cgi/subscribe.py +++ b/Mailman/Cgi/subscribe.py @@ -20,22 +20,22 @@ import sys import os +from Mailman.Utils import FieldStorage import time import signal -import urllib.parse +import urllib.request, urllib.parse, urllib.error +import urllib.request, urllib.error, urllib.parse import json -import ipaddress from Mailman import mm_cfg from Mailman import Utils from Mailman import MailList from Mailman import Errors from Mailman import i18n -from Mailman.Message import Message +from Mailman import Message from Mailman.UserDesc import UserDesc from Mailman.htmlformat import * -from Mailman.Logging.Syslog import mailman_log -from Mailman.Utils import validate_ip_address +from Mailman.Logging.Syslog import syslog SLASH = '/' ERRORSEP = '\n\n

    ' @@ -46,32 +46,7 @@ i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) -def validate_listname(listname): - """Validate and sanitize a listname to prevent path traversal. - - Args: - listname: The listname to validate - - Returns: - tuple: (is_valid, sanitized_name, error_message) - """ - if not listname: - return False, None, _('List name is required') - - # Convert to lowercase and strip whitespace - listname = listname.lower().strip() - - # Basic validation - if not Utils.ValidateListName(listname): - return False, None, _('Invalid list name') - - # Check for path traversal attempts - if '..' in listname or '/' in listname or '\\' in listname: - return False, None, _('Invalid list name') - - return True, listname, None - - + def main(): doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) @@ -80,63 +55,28 @@ def main(): if not parts: doc.AddItem(Header(2, _("Error"))) doc.AddItem(Bold(_('Invalid options to CGI script'))) - print('Status: 400 Bad Request') - print(doc.Format()) - return - - # Validate listname - is_valid, listname, error_msg = validate_listname(parts[0]) - if not is_valid: - doc.AddItem(Header(2, _("Error"))) - doc.AddItem(Bold(error_msg)) - print('Status: 400 Bad Request') print(doc.Format()) return + listname = parts[0].lower() try: mlist = MailList.MailList(listname, lock=0) except Errors.MMListError as e: - # Avoid cross-site scripting attacks and information disclosure + # Avoid cross-site scripting attacks safelistname = Utils.websafe(listname) doc.AddItem(Header(2, _("Error"))) - doc.AddItem(Bold(_('No such list {safelistname}'))) + doc.AddItem(Bold(_(f'No such list {safelistname}'))) # Send this with a 404 status. print('Status: 404 Not Found') print(doc.Format()) - mailman_log('error', 'subscribe: No such list "%s"', listname) - return - except Exception as e: - # Log the full error but don't expose it to the user - mailman_log('error', 'subscribe: Unexpected error for list "%s": %s', listname, str(e)) - doc.AddItem(Header(2, _("Error"))) - doc.AddItem(Bold(_('An error occurred processing your request'))) - print('Status: 500 Internal Server Error') - print(doc.Format()) + syslog('error', 'subscribe: No such list "%s": %s\n', listname, e) return # See if the form data has a preferred language set, in which case, use it # for the results. If not, use the list's preferred language. + cgidata = FieldStorage() try: - if os.environ.get('REQUEST_METHOD') == 'POST': - # Get the content length - content_length = int(os.environ.get('CONTENT_LENGTH', 0)) - # Read the form data - form_data = sys.stdin.read(content_length) - cgidata = urllib.parse.parse_qs(form_data, keep_blank_values=True) - else: - query_string = os.environ.get('QUERY_STRING', '') - cgidata = urllib.parse.parse_qs(query_string, keep_blank_values=True) - except Exception as e: - # Log the error but don't expose details - mailman_log('error', 'subscribe: Error parsing form data: %s', str(e)) - doc.AddItem(Header(2, _("Error"))) - doc.AddItem(Bold(_('Invalid request'))) - print('Status: 400 Bad Request') - print(doc.Format()) - return - - try: - language = cgidata.get('language', [''])[0] + language = cgidata.getfirst('language', '') except TypeError: # Someone crafted a POST with a bad Content-Type:. doc.AddItem(Header(2, _("Error"))) @@ -153,6 +93,11 @@ def main(): # We need a signal handler to catch the SIGTERM that can come from Apache # when the user hits the browser's STOP button. See the comment in # admin.py for details. + # + # BAW: Strictly speaking, the list should not need to be locked just to + # read the request database. However the request database asserts that + # the list is locked in order to load it and it's not worth complicating + # that logic. def sigterm_handler(signum, frame, mlist=mlist): # Make sure the list gets unlocked... mlist.Unlock() @@ -161,28 +106,29 @@ def sigterm_handler(signum, frame, mlist=mlist): # could be bad! sys.exit(0) - # Install the emergency shutdown signal handler - signal.signal(signal.SIGTERM, sigterm_handler) + mlist.Lock() + try: + # Install the emergency shutdown signal handler + signal.signal(signal.SIGTERM, sigterm_handler) - process_form(mlist, doc, cgidata, language) + process_form(mlist, doc, cgidata, language) + mlist.Save() + finally: + mlist.Unlock() + def process_form(mlist, doc, cgidata, lang): listowner = mlist.GetOwnerEmail() realname = mlist.real_name results = [] # The email address being subscribed, required - email = cgidata.get('email', [''])[0] - if isinstance(email, bytes): - email = email.decode('utf-8', 'replace') - email = email.strip().lower() + email = cgidata.getfirst('email', '').strip() if not email: results.append(_('You must supply a valid email address.')) - fullname = cgidata.get('fullname', [''])[0] - if isinstance(fullname, bytes): - fullname = fullname.decode('utf-8', 'replace') + fullname = cgidata.getfirst('fullname', '') # Canonicalize the full name fullname = Utils.canonstr(fullname, lang) # Who was doing the subscribing? @@ -193,12 +139,9 @@ def process_form(mlist, doc, cgidata, lang): # Check reCAPTCHA submission, if enabled if mm_cfg.RECAPTCHA_SECRET_KEY: - recaptcha_response = cgidata.get('g-recaptcha-response', [''])[0] - if isinstance(recaptcha_response, bytes): - recaptcha_response = recaptcha_response.decode('utf-8', 'replace') request_data = urllib.parse.urlencode({ 'secret': mm_cfg.RECAPTCHA_SECRET_KEY, - 'response': recaptcha_response, + 'response': cgidata.getvalue('g-recaptcha-response', ''), 'remoteip': remote}) request_data = request_data.encode('utf-8') request = urllib.request.Request( @@ -210,64 +153,58 @@ def process_form(mlist, doc, cgidata, lang): httpresp.close() if not captcha_response['success']: e_codes = COMMASPACE.join(captcha_response['error-codes']) - results.append(_('reCAPTCHA validation failed: {}').format(e_codes)) + results.append(_(f'reCAPTCHA validation failed: {e_codes}')) except urllib.error.URLError as e: e_reason = e.reason - results.append(_('reCAPTCHA could not be validated: {e_reason}')) - - # Get and validate IP address - ip = os.environ.get('REMOTE_ADDR', '') - is_valid, normalized_ip = validate_ip_address(ip) - if not is_valid: - ip = '' - else: - ip = normalized_ip + results.append(_(f'reCAPTCHA could not be validated: {e_reason}')) # Are we checking the hidden data? if mm_cfg.SUBSCRIBE_FORM_SECRET: now = int(time.time()) # Try to accept a range in case of load balancers, etc. (LP: #1447445) - if ip.find('.') >= 0: + if remote.find('.') >= 0: # ipv4 - drop last octet - remote1 = ip.rsplit('.', 1)[0] + remote1 = remote.rsplit('.', 1)[0] else: # ipv6 - drop last 16 (could end with :: in which case we just # drop one : resulting in an invalid format, but it's only # for our hash so it doesn't matter. - remote1 = ip.rsplit(':', 1)[0] + remote1 = remote.rsplit(':', 1)[0] try: - sub_form_token = cgidata.get('sub_form_token', [''])[0] - if isinstance(sub_form_token, bytes): - sub_form_token = sub_form_token.decode('utf-8', 'replace') - ftime, fcaptcha_idx, fhash = sub_form_token.split(':') + ftime, fcaptcha_idx, fhash = cgidata.getfirst( + 'sub_form_token', '').split(':') then = int(ftime) except ValueError: ftime = fcaptcha_idx = fhash = '' then = 0 - needs_hashing = (mm_cfg.SUBSCRIBE_FORM_SECRET + ":" + ftime + ":" + fcaptcha_idx + - ":" + mlist.internal_name() + ":" + remote1).encode('utf-8') + needs_hashing = (mm_cfg.SUBSCRIBE_FORM_SECRET + ":" + ftime + ":" + fcaptcha_idx + ":" + mlist.internal_name() + ":" + remote1).encode('utf-8') token = Utils.sha_new(needs_hashing).hexdigest() if ftime and now - then > mm_cfg.FORM_LIFETIME: results.append(_('The form is too old. Please GET it again.')) if ftime and now - then < mm_cfg.SUBSCRIBE_FORM_MIN_TIME: - results.append(_('The form was submitted too quickly. Please wait a moment and try again.')) + results.append( + _('Please take a few seconds to fill out the form before submitting it.')) if ftime and token != fhash: - results.append(_('The form was tampered with. Please GET it again.')) - + results.append( + _("The hidden token didn't match. Did your IP change?")) + if not ftime: + results.append( + _('There was no hidden token in your submission or it was corrupted.')) + results.append(_('You must GET the form before submitting it.')) + # Check captcha + if isinstance(mm_cfg.CAPTCHAS, dict): + captcha_answer = cgidata.getvalue('captcha_answer', '') + if not Utils.captcha_verify( + fcaptcha_idx, captcha_answer, mm_cfg.CAPTCHAS): + results.append(_( + 'This was not the right answer to the CAPTCHA question.')) # Was an attempt made to subscribe the list to itself? if email == mlist.GetListEmail(): - mailman_log('mischief', 'Attempt to self subscribe %s: %s', email, remote) + syslog('mischief', 'Attempt to self subscribe %s: %s', email, remote) results.append(_('You may not subscribe a list to itself!')) # If the user did not supply a password, generate one for him - password = cgidata.get('pw', [''])[0] - if isinstance(password, bytes): - password = password.decode('utf-8', 'replace') - password = password.strip() - - confirmed = cgidata.get('pw-conf', [''])[0] - if isinstance(confirmed, bytes): - confirmed = confirmed.decode('utf-8', 'replace') - confirmed = confirmed.strip() + password = cgidata.getfirst('pw', '').strip() + confirmed = cgidata.getfirst('pw-conf', '').strip() if not password and not confirmed: password = Utils.MakeRandomPassword() @@ -277,9 +214,7 @@ def process_form(mlist, doc, cgidata, lang): results.append(_('Your passwords did not match.')) # Get the digest option for the subscription. - digestflag = cgidata.get('digest', [''])[0] - if isinstance(digestflag, bytes): - digestflag = digestflag.decode('utf-8', 'replace') + digestflag = cgidata.getfirst('digest') if digestflag: try: digest = int(digestflag) @@ -317,13 +252,12 @@ def process_form(mlist, doc, cgidata, lang): moderator. If confirmation is required, you will soon get a confirmation email which contains further instructions.""") - # Acquire the lock before attempting to add the member - mlist.Lock() try: userdesc = UserDesc(email, fullname, password, digest, lang) mlist.AddMember(userdesc, remote) results = '' - mlist.Save() + # Check for all the errors that mlist.AddMember can throw options on the + # web page for this cgi except Errors.MembershipIsBanned: results = _(f"""The email address you supplied is banned from this mailing list. If you think this restriction is erroneous, please @@ -375,7 +309,7 @@ def process_form(mlist, doc, cgidata, lang): otrans = i18n.get_translation() i18n.set_language(mlang) try: - msg = Mailman.Message.UserNotification( + msg = Message.UserNotification( mlist.getMemberCPAddress(email), mlist.GetBouncesEmail(), _('Mailman privacy alert'), @@ -409,12 +343,11 @@ def process_form(mlist, doc, cgidata, lang): else: results = _(f"""\ You have been successfully subscribed to the {realname} mailing list.""") - finally: - mlist.Unlock() # Show the results print_results(mlist, results, doc, lang) + def print_results(mlist, results, doc, lang): # The bulk of the document will come from the options.html template, which # includes its own html armor (head tags, etc.). Suppress the head that diff --git a/Mailman/Commands/cmd_confirm.py b/Mailman/Commands/cmd_confirm.py index 1ef3e4b3..c7d3aeb3 100644 --- a/Mailman/Commands/cmd_confirm.py +++ b/Mailman/Commands/cmd_confirm.py @@ -43,6 +43,8 @@ def process(res, args): res.results.append(gethelp(mlist)) return STOP cookie = args[0] + if isinstance(cookie, bytes): + cookie = cookie.decode() try: results = mlist.ProcessConfirmation(cookie, res.msg) except Errors.MMBadConfirmation as e: @@ -53,29 +55,29 @@ def process(res, args): approximately %(days)s days after the initial request. They also expire if the request has already been handled in some way. If your confirmation has expired, please try to re-submit your original request or message.""")) - except Errors.MMNeedApproval: + except Errors.MMNeedApproval as e: res.results.append(_("""\ Your request has been forwarded to the list moderator for approval.""")) - except Errors.MMAlreadyAMember: + except Errors.MMAlreadyAMember as e: # Some other subscription request for this address has # already succeeded. res.results.append(_('You are already subscribed.')) - except Errors.NotAMemberError: + except Errors.NotAMemberError as e: # They've already been unsubscribed res.results.append(_("""\ You are not currently a member. Have you already unsubscribed or changed your email address?""")) - except Errors.MembershipIsBanned: + except Errors.MembershipIsBanned as e: owneraddr = mlist.GetOwnerEmail() res.results.append(_("""\ You are currently banned from subscribing to this list. If you think this restriction is erroneous, please contact the list owners at %(owneraddr)s.""")) - except Errors.HostileSubscriptionError: + except Errors.HostileSubscriptionError as e: res.results.append(_("""\ You were not invited to this mailing list. The invitation has been discarded, and both list administrators have been alerted.""")) - except Errors.MMBadPasswordError: + except Errors.MMBadPasswordError as e: res.results.append(_("""\ Bad approval password given. Held message is still being held.""")) else: diff --git a/Mailman/Commands/cmd_set.py b/Mailman/Commands/cmd_set.py index 66d2e978..91bad858 100644 --- a/Mailman/Commands/cmd_set.py +++ b/Mailman/Commands/cmd_set.py @@ -117,7 +117,7 @@ def process(self, res, args): res.results.append(_(DETAILS)) return STOP subcmd = args.pop(0) - methname = 'set_' + subcmd + methname = 'set_' + subcmd.decode('utf-8') if isinstance(subcmd, bytes) else 'set_' + subcmd method = getattr(self, methname, None) if method is None: res.results.append(_('Bad set command: %(subcmd)s')) diff --git a/Mailman/Commands/cmd_subscribe.py b/Mailman/Commands/cmd_subscribe.py index 8e4c9443..8a2404af 100644 --- a/Mailman/Commands/cmd_subscribe.py +++ b/Mailman/Commands/cmd_subscribe.py @@ -52,7 +52,8 @@ def process(res, args): realname = None # Parse the args argnum = 0 - for arg in args: + for arg_bytes in args: + arg = arg_bytes.decode('utf-8') if arg.lower().startswith('address='): address = arg[8:] elif argnum == 0: @@ -94,11 +95,7 @@ def process(res, args): # Watch for encoded names try: h = make_header(decode_header(realname)) - # Get the realname from the header - try: - realname = str(h) - except UnicodeError: - realname = str(h, 'utf-8', 'replace') + realname = h.__str__() except UnicodeError: realname = u'' # Coerce to byte string if uh contains only ascii diff --git a/Mailman/Commands/cmd_unsubscribe.py b/Mailman/Commands/cmd_unsubscribe.py index 686b274a..38d5ec2c 100644 --- a/Mailman/Commands/cmd_unsubscribe.py +++ b/Mailman/Commands/cmd_unsubscribe.py @@ -43,7 +43,8 @@ def process(res, args): password = None address = None argnum = 0 - for arg in args: + for arg_bytes in args: + arg = arg_bytes.decode('utf-8') if arg.startswith('address='): address = arg[8:] elif argnum == 0: diff --git a/Mailman/Defaults.py.in b/Mailman/Defaults.py.in index d62f0c4f..697af315 100755 --- a/Mailman/Defaults.py.in +++ b/Mailman/Defaults.py.in @@ -18,6 +18,7 @@ # USA. """Distributed default settings for significant Mailman config variables.""" +from __future__ import absolute_import # NEVER make site configuration changes to this file. ALWAYS make them in # mm_cfg.py instead, in the designated area. See the comments in that file @@ -177,7 +178,7 @@ HTML_TO_PLAIN_TEXT_COMMAND = '/usr/bin/lynx -dump %(filename)s' # A Python regular expression character class which defines the characters # allowed in list names. Lists cannot be created with names containing any # character that doesn't match this class. Do not include '/' in this list. -ACCEPTABLE_LISTNAME_CHARACTERS = r'[-+_.=a-z0-9]' +ACCEPTABLE_LISTNAME_CHARACTERS = '[-+_.=a-z0-9]' # The number of characters in the longest listname in the installation. The # fix for LP: #1780874 truncates list names in web URLs to this length to avoid @@ -261,7 +262,7 @@ KNOWN_SPAMMERS = [] # normalized unicodes against normalized unicode headers. This setting # determines the normalization form. It is one of 'NFC', 'NFD', 'NFKC' or # 'NFKD'. See -# https://docs.python.org/3/library/unicodedata.html#unicodedata.normalize +# https://docs.python.org/2/library/unicodedata.html#unicodedata.normalize NORMALIZE_FORM = 'NFKC' @@ -631,7 +632,7 @@ NNTP_USERNAME = None NNTP_PASSWORD = None # Set this if you have an NNTP server you prefer gatewayed lists to use. -DEFAULT_NNTP_HOST = None +DEFAULT_NNTP_HOST = '' # These variables controls how headers must be cleansed in order to be # accepted by your NNTP server. Some servers like INN reject messages @@ -932,16 +933,21 @@ DEFAULT_RESPOND_TO_POST_REQUESTS = Yes # BAW: Eventually we may support weighted hash spaces. # BAW: Although not enforced, the # of slices must be a power of 2 +# Distribution method for queue runners: 'hash' (default) or 'round_robin' +# Hash-based distribution ensures same message always goes to same runner +# Round-robin distribution provides more even load distribution +QUEUE_DISTRIBUTION_METHOD = 'hash' + QRUNNERS = [ ('ArchRunner', 1), # messages for the archiver ('BounceRunner', 2), # for processing the qfile/bounces directory ('CommandRunner', 1), # commands and bounces from the outside world ('IncomingRunner', 4), # posts from the outside world ('NewsRunner', 1), # outgoing messages to the nntpd - ('OutgoingRunner', 1), # outgoing messages to the smtpd (single instance with process coordination) + ('OutgoingRunner', 8), # outgoing messages to the smtpd ('VirginRunner', 1), # internally crafted (virgin birth) messages ('RetryRunner', 1), # retry temporarily failed deliveries -] + ] # Set this to Yes to use the `Maildir' delivery option. If you change this # you will need to re-run bin/genaliases for MTAs that don't use list @@ -1780,7 +1786,7 @@ SITE_PW_FILE = os.path.join(DATA_DIR, 'adm.pw') LISTCREATOR_PW_FILE = os.path.join(DATA_DIR, 'creator.pw') # Import a bunch of version numbers -from Version import * +from .Version import * # Vgg: Language descriptions and charsets dictionary, any new supported # language must have a corresponding entry here. Key is the name of the @@ -1797,41 +1803,41 @@ def add_language(code, description, charset, direction='ltr'): LC_DESCRIPTIONS[code] = (description, charset, direction) add_language('ar', _('Arabic'), 'utf-8', 'rtl') -add_language('ast', _('Asturian'), 'iso-8859-1', 'ltr') +add_language('ast', _('Asturian'), 'utf-8', 'ltr') add_language('ca', _('Catalan'), 'utf-8', 'ltr') -add_language('cs', _('Czech'), 'iso-8859-2', 'ltr') -add_language('da', _('Danish'), 'iso-8859-1', 'ltr') -add_language('de', _('German'), 'iso-8859-1', 'ltr') -add_language('en', _('English (USA)'), 'us-ascii', 'ltr') +add_language('cs', _('Czech'), 'utf-8', 'ltr') +add_language('da', _('Danish'), 'utf-8', 'ltr') +add_language('de', _('German'), 'utf-8', 'ltr') +add_language('en', _('English (USA)'), 'utf-8', 'ltr') add_language('eo', _('Esperanto'), 'utf-8', 'ltr') -add_language('es', _('Spanish (Spain)'), 'iso-8859-1', 'ltr') -add_language('et', _('Estonian'), 'iso-8859-15', 'ltr') -add_language('eu', _('Euskara'), 'iso-8859-15', 'ltr') # Basque +add_language('es', _('Spanish (Spain)'), 'utf-8', 'ltr') +add_language('et', _('Estonian'), 'utf-8', 'ltr') +add_language('eu', _('Euskara'), 'utf-8', 'ltr') # Basque add_language('fa', _('Persian'), 'utf-8', 'rtl') -add_language('fi', _('Finnish'), 'iso-8859-1', 'ltr') -add_language('fr', _('French'), 'iso-8859-1', 'ltr') +add_language('fi', _('Finnish'), 'utf-8', 'ltr') +add_language('fr', _('French'), 'utf-8', 'ltr') add_language('gl', _('Galician'), 'utf-8', 'ltr') -add_language('el', _('Greek'), 'iso-8859-7', 'ltr') +add_language('el', _('Greek'), 'utf-8', 'ltr') add_language('he', _('Hebrew'), 'utf-8', 'rtl') -add_language('hr', _('Croatian'), 'iso-8859-2', 'ltr') -add_language('hu', _('Hungarian'), 'iso-8859-2', 'ltr') -add_language('ia', _('Interlingua'), 'iso-8859-15', 'ltr') -add_language('it', _('Italian'), 'iso-8859-1', 'ltr') -add_language('ja', _('Japanese'), 'euc-jp', 'ltr') -add_language('ko', _('Korean'), 'euc-kr', 'ltr') -add_language('lt', _('Lithuanian'), 'iso-8859-13', 'ltr') -add_language('nl', _('Dutch'), 'iso-8859-1', 'ltr') -add_language('no', _('Norwegian'), 'iso-8859-1', 'ltr') -add_language('pl', _('Polish'), 'iso-8859-2', 'ltr') -add_language('pt', _('Portuguese'), 'iso-8859-1', 'ltr') -add_language('pt_BR', _('Portuguese (Brazil)'), 'iso-8859-1', 'ltr') +add_language('hr', _('Croatian'), 'utf-8', 'ltr') +add_language('hu', _('Hungarian'), 'utf-8', 'ltr') +add_language('ia', _('Interlingua'), 'utf-8', 'ltr') +add_language('it', _('Italian'), 'utf-8', 'ltr') +add_language('ja', _('Japanese'), 'utf-8', 'ltr') +add_language('ko', _('Korean'), 'utf-8', 'ltr') +add_language('lt', _('Lithuanian'), 'utf-8', 'ltr') +add_language('nl', _('Dutch'), 'utf-8', 'ltr') +add_language('no', _('Norwegian'), 'utf-8', 'ltr') +add_language('pl', _('Polish'), 'utf-8', 'ltr') +add_language('pt', _('Portuguese'), 'utf-8', 'ltr') +add_language('pt_BR', _('Portuguese (Brazil)'), 'utf-8', 'ltr') add_language('ro', _('Romanian'), 'utf-8', 'ltr') add_language('ru', _('Russian'), 'utf-8', 'ltr') add_language('sk', _('Slovak'), 'utf-8', 'ltr') -add_language('sl', _('Slovenian'), 'iso-8859-2', 'ltr') +add_language('sl', _('Slovenian'), 'utf-8', 'ltr') add_language('sr', _('Serbian'), 'utf-8', 'ltr') -add_language('sv', _('Swedish'), 'iso-8859-1', 'ltr') -add_language('tr', _('Turkish'), 'iso-8859-9', 'ltr') +add_language('sv', _('Swedish'), 'utf-8', 'ltr') +add_language('tr', _('Turkish'), 'utf-8', 'ltr') add_language('uk', _('Ukrainian'), 'utf-8', 'ltr') add_language('vi', _('Vietnamese'), 'utf-8', 'ltr') add_language('zh_CN', _('Chinese (China)'), 'utf-8', 'ltr') diff --git a/Mailman/Deliverer.py b/Mailman/Deliverer.py index d9e0be15..a4790c1f 100644 --- a/Mailman/Deliverer.py +++ b/Mailman/Deliverer.py @@ -26,77 +26,22 @@ from Mailman import mm_cfg from Mailman import Errors from Mailman import Utils -from Mailman.Message import Message, UserNotification +from Mailman import Message from Mailman import i18n from Mailman import Pending from Mailman.Logging.Syslog import syslog _ = i18n._ -import sys -import os -import time -import email -import errno -import pickle -import email.message -from email.message import Message -from email.header import decode_header, make_header, Header -from email.errors import HeaderParseError -from email.iterators import typed_subpart_iterator - -from Mailman.htmlformat import * -from Mailman.Logging.Syslog import mailman_log -from Mailman.Utils import validate_ip_address -import Mailman.Handlers.Replybot as Replybot -from Mailman.i18n import _ -from Mailman import LockFile - -# Lazy imports to avoid circular dependencies -def get_replybot(): - import Mailman.Handlers.Replybot as Replybot - return Replybot - -def get_maillist(): - import Mailman.MailList as MailList - return MailList.MailList - class Deliverer(object): - def deliver(self, msg, msgdata): - """Deliver a message to the list's members. - - Args: - msg: The message to deliver - msgdata: Additional message metadata - - This method delegates to the configured delivery module's process function. - """ - # Import the delivery module - modname = 'Mailman.Handlers.' + mm_cfg.DELIVERY_MODULE - try: - mod = __import__(modname) - process = getattr(sys.modules[modname], 'process') - except (ImportError, AttributeError) as e: - syslog('error', 'Failed to import delivery module %s: %s', modname, str(e)) - raise - - # Process the message - process(self, msg, msgdata) - def SendSubscribeAck(self, name, password, digest, text=''): - try: - pluser = self.getMemberLanguage(name) - except AttributeError: - try: - pluser = self.preferred_language - except AttributeError: - pluser = 'en' # Default to English if no language is available + pluser = self.getMemberLanguage(name) # Need to set this here to get the proper l10n of the Subject: i18n.set_language(pluser) - try: - welcome = Utils.wrap(self.welcome_msg) + '\n' if self.welcome_msg else '' - except AttributeError: + if self.welcome_msg: + welcome = Utils.wrap(self.welcome_msg) + '\n' + else: welcome = '' if self.umbrella_list: addr = self.GetMemberAdminEmail(name) @@ -107,7 +52,7 @@ def SendSubscribeAck(self, name, password, digest, text=''): else: umbrella = '' # get the text from the template - text += str(Utils.maketext( + text += Utils.maketext( 'subscribeack.txt', {'real_name' : self.real_name, 'host_name' : self.host_name, @@ -118,15 +63,15 @@ def SendSubscribeAck(self, name, password, digest, text=''): 'optionsurl' : self.GetOptionsURL(name, absolute=True), 'password' : password, 'user' : self.getMemberCPAddress(name), - }, lang=pluser, mlist=self)) + }, lang=pluser, mlist=self) if digest: digmode = _(' (Digest mode)') else: digmode = '' realname = self.real_name - msg = UserNotification( + msg = Message.UserNotification( self.GetMemberAdminEmail(name), self.GetRequestEmail(), - _('Welcome to the "%(realname)s" mailing list%(digmode)s') % {'realname': realname, 'digmode': digmode}, + _(f'Welcome to the "{realname}" mailing list{digmode}'), text, pluser) msg['X-No-Archive'] = 'yes' msg.send(self, verp=mm_cfg.VERP_PERSONALIZED_DELIVERIES) @@ -134,9 +79,9 @@ def SendSubscribeAck(self, name, password, digest, text=''): def SendUnsubscribeAck(self, addr, lang): realname = self.real_name i18n.set_language(lang) - msg = UserNotification( + msg = Message.UserNotification( self.GetMemberAdminEmail(addr), self.GetBouncesEmail(), - _('You have been unsubscribed from the %(realname)s mailing list') % {'realname': realname}, + _(f'You have been unsubscribed from the {realname} mailing list'), Utils.wrap(self.goodbye_msg), lang) msg.send(self, verp=mm_cfg.VERP_PERSONALIZED_DELIVERIES) @@ -163,14 +108,15 @@ def MailUserPassword(self, user): # Now send the user his password cpuser = self.getMemberCPAddress(user) recipient = self.GetMemberAdminEmail(cpuser) - subject = _('%(listfullname)s mailing list reminder') + subject = _(f'{listfullname} mailing list reminder') # Get user's language and charset lang = self.getMemberLanguage(user) cset = Utils.GetCharSet(lang) password = self.getMemberPassword(user) - # Handle password encoding properly for Python 3 - if isinstance(password, bytes): - password = password.decode(cset, 'replace') + # TK: Make unprintables to ? + # The list owner should allow users to set language options if they + # want to use non-us-ascii characters in password and send it back. + #password = str(password, cset, 'replace').encode(cset, 'replace') # get the text from the template text = Utils.maketext( 'userpass.txt', @@ -182,7 +128,7 @@ def MailUserPassword(self, user): 'requestaddr': requestaddr, 'owneraddr' : self.GetOwnerEmail(), }, lang=lang, mlist=self) - msg = UserNotification(recipient, adminaddr, subject, text, + msg = Message.UserNotification(recipient, adminaddr, subject, text, lang) msg['X-No-Archive'] = 'yes' msg.send(self, verp=mm_cfg.VERP_PERSONALIZED_DELIVERIES) @@ -196,7 +142,7 @@ def ForwardMessage(self, msg, text=None, subject=None, tomoderators=True): text = MIMEText(Utils.wrap(text), _charset=Utils.GetCharSet(self.preferred_language)) attachment = MIMEMessage(msg) - notice = UserNotification( + notice = Message.OwnerNotification( self, subject, tomoderators=tomoderators) # Make it look like the message is going to the -owner address notice.set_type('multipart/mixed') @@ -212,10 +158,10 @@ def SendHostileSubscriptionNotice(self, listname, address): syslog('mischief', '%s was invited to %s but confirmed to %s', address, listname, selfname) # First send a notice to the attacked list - msg = UserNotification( + msg = Message.OwnerNotification( self, _('Hostile subscription attempt detected'), - Utils.wrap(_("""%(address)s was invited to a different mailing + Utils.wrap(_(f"""{address} was invited to a different mailing list, but in a deliberate malicious attempt they tried to confirm the invitation to your list. We just thought you'd like to know. No further action by you is required."""))) @@ -231,10 +177,10 @@ def SendHostileSubscriptionNotice(self, listname, address): otrans = i18n.get_translation() i18n.set_language(mlist.preferred_language) try: - msg = UserNotification( + msg = Message.OwnerNotification( mlist, _('Hostile subscription attempt detected'), - Utils.wrap(_("""You invited %(address)s to your list, but in a + Utils.wrap(_(f"""You invited {address} to your list, but in a deliberate malicious attempt, they tried to confirm the invitation to a different list. We just thought you'd like to know. No further action by you is required."""))) @@ -267,10 +213,10 @@ def sendProbe(self, member, msg): otrans = i18n.get_translation() i18n.set_language(ulang) try: - subject = _('%(listname)s mailing list probe message') + subject = _(f'{listname} mailing list probe message') finally: i18n.set_translation(otrans) - outer = UserNotification(member, probeaddr, subject, + outer = Message.UserNotification(member, probeaddr, subject, lang=ulang) outer.set_type('multipart/mixed') text = MIMEText(text, _charset=Utils.GetCharSet(ulang)) diff --git a/Mailman/Digester.py b/Mailman/Digester.py index 150a6fe2..4ca58d30 100644 --- a/Mailman/Digester.py +++ b/Mailman/Digester.py @@ -25,14 +25,11 @@ from Mailman import mm_cfg from Mailman import Utils from Mailman import Errors +from Mailman.Handlers import ToDigest from Mailman.i18n import _ -# Lazy import to avoid circular dependency -def get_to_digest(): - import Mailman.Handlers.ToDigest as ToDigest - return ToDigest - + class Digester(object): def InitVars(self): # Configurable @@ -45,8 +42,6 @@ def InitVars(self): self.digest_header = mm_cfg.DEFAULT_DIGEST_HEADER self.digest_footer = mm_cfg.DEFAULT_DIGEST_FOOTER self.digest_volume_frequency = mm_cfg.DEFAULT_DIGEST_VOLUME_FREQUENCY - self._new_volume = 0 # Initialize _new_volume to False - self.volume = 1 # Initialize volume to 1 # Non-configurable. self.one_last_digest = {} self.digest_members = {} @@ -63,7 +58,7 @@ def send_digest_now(self): # See if there's a digest pending for this mailing list if os.stat(digestmbox)[ST_SIZE] > 0: mboxfp = open(digestmbox) - get_to_digest().send_digests(self, mboxfp) + ToDigest.send_digests(self, mboxfp) os.unlink(digestmbox) finally: if mboxfp: diff --git a/Mailman/Errors.py b/Mailman/Errors.py index d2ba6523..3410e1f4 100644 --- a/Mailman/Errors.py +++ b/Mailman/Errors.py @@ -94,11 +94,11 @@ class EmailAddressError(MailmanError): """Base class for email address validation errors.""" pass -class MMBadEmailError(Exception): +class MMBadEmailError(EmailAddressError): """Email address is invalid (empty string or not fully qualified).""" pass -class MMHostileAddress(Exception): +class MMHostileAddress(EmailAddressError): """Email address has potentially hostile characters in it.""" pass @@ -162,7 +162,7 @@ def __init__(self, notice=None): def notice(self): return self.__notice - + # Additional exceptions class HostileSubscriptionError(MailmanError): """A cross-subscription attempt was made.""" diff --git a/Mailman/Gui/Bounce.py b/Mailman/Gui/Bounce.py index e85aa79a..ee747678 100644 --- a/Mailman/Gui/Bounce.py +++ b/Mailman/Gui/Bounce.py @@ -1,3 +1,4 @@ +from __future__ import division # Copyright (C) 2001-2018 by the Free Software Foundation, Inc. # # This program is free software; you can redistribute it and/or @@ -20,6 +21,7 @@ from Mailman.Gui.GUIBase import GUIBase + class Bounce(GUIBase): def GetConfigCategory(self): return 'bounce', _('Bounce processing') diff --git a/Mailman/Gui/Digest.py b/Mailman/Gui/Digest.py index 70f7d267..052de111 100644 --- a/Mailman/Gui/Digest.py +++ b/Mailman/Gui/Digest.py @@ -65,14 +65,14 @@ def GetConfigInfo(self, mlist, category, subcat=None): ('digest_header', mm_cfg.Text, (4, WIDTH), 0, _('Header added to every digest'), - str(_("Text attached (as an initial message, before the table" - " of contents) to the top of digests. ")) - + str(Utils.maketext('headfoot.html', raw=1, mlist=mlist))), + _("Text attached (as an initial message, before the table" + " of contents) to the top of digests. ") + + Utils.maketext('headfoot.html', raw=1, mlist=mlist)), ('digest_footer', mm_cfg.Text, (4, WIDTH), 0, _('Footer added to every digest'), - str(_("Text attached (as a final message) to the bottom of digests. ")) - + str(Utils.maketext('headfoot.html', raw=1, mlist=mlist))), + _("Text attached (as a final message) to the bottom of digests. ") + + Utils.maketext('headfoot.html', raw=1, mlist=mlist)), ('digest_volume_frequency', mm_cfg.Radio, (_('Yearly'), _('Monthly'), _('Quarterly'), diff --git a/Mailman/Gui/GUIBase.py b/Mailman/Gui/GUIBase.py index a0566cba..d51496f2 100644 --- a/Mailman/Gui/GUIBase.py +++ b/Mailman/Gui/GUIBase.py @@ -39,12 +39,6 @@ class GUIBase: def _getValidValue(self, mlist, property, wtype, val): # Coerce and validate the new value. # - # First convert any bytes to strings - if isinstance(val, bytes): - try: - val = val.decode('utf-8') - except UnicodeDecodeError: - val = val.decode('latin1') # Radio buttons and boolean toggles both have integral type if wtype in (mm_cfg.Radio, mm_cfg.Toggle): # Let ValueErrors propagate @@ -144,14 +138,8 @@ def _getValidValue(self, mlist, property, wtype, val): def _setValue(self, mlist, property, val, doc): # Set the value, or override to take special action on the property - if not property.startswith('_'): - if isinstance(val, bytes): - try: - val = val.decode('utf-8') - except UnicodeDecodeError: - val = val.decode('latin1') - if getattr(mlist, property) != val: - setattr(mlist, property, val) + if not property.startswith('_') and getattr(mlist, property) != val: + setattr(mlist, property, val) def _postValidate(self, mlist, doc): # Validate all the attributes for this category @@ -160,7 +148,7 @@ def _postValidate(self, mlist, doc): def handleForm(self, mlist, category, subcat, cgidata, doc): for item in self.GetConfigInfo(mlist, category, subcat): # Skip descriptions and legacy non-attributes - if not isinstance(item, tuple) or len(item) < 5: + if not type(item) is tuple or len(item) < 5: continue # Unpack the gui item description property, wtype, args, deps, desc = item[0:5] @@ -199,11 +187,6 @@ def handleForm(self, mlist, category, subcat, cgidata, doc): # Convenience method for handling $-string attributes def _convertString(self, mlist, property, alloweds, val, doc): # Is the list using $-strings? - if isinstance(val, bytes): - try: - val = val.decode('utf-8') - except UnicodeDecodeError: - val = val.decode('latin1') dollarp = getattr(mlist, 'use_dollar_strings', 0) if dollarp: ids = Utils.dollar_identifiers(val) @@ -241,9 +224,3 @@ def _convertString(self, mlist, property, alloweds, val, doc): """)) return fixed return val - - def AddItem(self, item): - """Add an item to the list of items to be displayed.""" - if not isinstance(item, tuple) or len(item) < 5: - raise ValueError('Item must be a tuple with at least 5 elements') - self.items.append(item) diff --git a/Mailman/Gui/General.py b/Mailman/Gui/General.py index bccefb3b..89319449 100644 --- a/Mailman/Gui/General.py +++ b/Mailman/Gui/General.py @@ -595,4 +595,4 @@ def getValue(self, mlist, kind, varname, params): if varname != 'subject_prefix': return None # The subject_prefix may be Unicode - return Utils.uncanonstr(mlist.subject_prefix, mlist.preferred_language) + return Utils.uncanonstr(mlist.subject_prefix, mlist.preferred_language).decode() # Does this break encodings? diff --git a/Mailman/Gui/Language.py b/Mailman/Gui/Language.py index 606b8433..7480b763 100644 --- a/Mailman/Gui/Language.py +++ b/Mailman/Gui/Language.py @@ -38,7 +38,7 @@ def GetConfigInfo(self, mlist, category, subcat=None): return None # Set things up for the language choices - langs = mlist.available_languages + langs = mlist.GetAvailableLanguages() langnames = [_(Utils.GetLanguageDescr(L)) for L in langs] try: langi = langs.index(mlist.preferred_language) diff --git a/Mailman/Gui/NonDigest.py b/Mailman/Gui/NonDigest.py index 668d3ed1..321997f4 100644 --- a/Mailman/Gui/NonDigest.py +++ b/Mailman/Gui/NonDigest.py @@ -125,18 +125,15 @@ def GetConfigInfo(self, mlist, category, subcat=None): else: extra = '' - # Ensure headfoot is not None - headfoot = headfoot or '' - info.extend([('msg_header', mm_cfg.Text, (10, WIDTH), 0, _('Header added to mail sent to regular list members'), - str(_('''Text prepended to the top of every immediately-delivery - message. ''')) + str(headfoot) + str(extra)), + _('''Text prepended to the top of every immediately-delivery + message. ''') + headfoot + extra), ('msg_footer', mm_cfg.Text, (10, WIDTH), 0, _('Footer added to mail sent to regular list members'), - str(_('''Text appended to the bottom of every immediately-delivery - message. ''')) + str(headfoot) + str(extra)), + _('''Text appended to the bottom of every immediately-delivery + message. ''') + headfoot + extra), ]) info.extend([ diff --git a/Mailman/Gui/Privacy.py b/Mailman/Gui/Privacy.py index d3d48300..5e4f847f 100644 --- a/Mailman/Gui/Privacy.py +++ b/Mailman/Gui/Privacy.py @@ -196,7 +196,7 @@ def GetConfigInfo(self, mlist, category, subcat=None):

    In the text boxes below, add one address per line; start the line with a ^ character to designate a Python regular expression. When entering backslashes, do so as if you were using Python raw strings (i.e. you generally just use a single backslash). @@ -649,9 +649,9 @@ def _handleForm(self, mlist, category, subcat, cgidata, doc): if deltag in cgidata: continue # Get the data for the current box - pattern = cgidata.get(reboxtag, [''])[0] + pattern = cgidata.getfirst(reboxtag) try: - action = int(cgidata.get(actiontag, ['0'])[0]) + action = int(cgidata.getfirst(actiontag)) # We'll get a TypeError when the actiontag is missing and the # .getvalue() call returns None. except (ValueError, TypeError): @@ -690,7 +690,7 @@ def _handleForm(self, mlist, category, subcat, cgidata, doc): # Was this an add item? if addtag in cgidata: # Where should the new one be added? - where = cgidata.get(wheretag, ['after'])[0] + where = cgidata.getfirst(wheretag) if where == 'before': # Add a new empty rule box before the current one rules.append(('', mm_cfg.DEFER, True)) @@ -725,20 +725,3 @@ def handleForm(self, mlist, category, subcat, cgidata, doc): self._handleForm(mlist, category, subcat, cgidata, doc) # Everything else is dealt with by the base handler GUIBase.handleForm(self, mlist, category, subcat, cgidata, doc) - -def process_form(mlist, cgidata): - # Get the privacy settings from the form - pattern = cgidata.get(reboxtag, [''])[0] - action = int(cgidata.get(actiontag, ['0'])[0]) - where = cgidata.get(wheretag, [''])[0] - - # Process the privacy rule - if pattern: - if where == 'add': - mlist.AddPrivacyRule(pattern, action) - elif where == 'change': - mlist.ChangePrivacyRule(pattern, action) - elif where == 'delete': - mlist.DeletePrivacyRule(pattern) - - mlist.Save() diff --git a/Mailman/Gui/Topics.py b/Mailman/Gui/Topics.py index 20f38a48..7459e89d 100644 --- a/Mailman/Gui/Topics.py +++ b/Mailman/Gui/Topics.py @@ -44,7 +44,7 @@ def GetConfigInfo(self, mlist, category, subcat=None): _("""The topic filter categorizes each incoming email message according to regular + href="https://docs.python.org/2/library/re.html">regular expression filters you specify below. If the message's Subject: or Keywords: header contains a match against a topic filter, the message is logically placed @@ -108,9 +108,9 @@ def handleForm(self, mlist, category, subcat, cgidata, doc): if deltag in cgidata: continue # Get the data for the current box - name = cgidata.get(boxtag, [''])[0] - pattern = cgidata.get(reboxtag, [''])[0] - desc = cgidata.get(desctag, [''])[0] + name = cgidata.getfirst(boxtag) + pattern = cgidata.getfirst(reboxtag) + desc = cgidata.getfirst(desctag) if name is None: # We came to the end of the boxes break @@ -132,7 +132,7 @@ def handleForm(self, mlist, category, subcat, cgidata, doc): # Was this an add item? if addtag in cgidata: # Where should the new one be added? - where = cgidata.get(wheretag, ['after'])[0] + where = cgidata.getfirst(wheretag) if where == 'before': # Add a new empty topics box before the current one topics.append(('', '', '', True)) @@ -148,34 +148,16 @@ def handleForm(self, mlist, category, subcat, cgidata, doc): # options. mlist.topics = topics try: - mlist.topics_enabled = int(cgidata.get('topics_enabled', [mlist.topics_enabled])[0]) + mlist.topics_enabled = int(cgidata.getfirst( + 'topics_enabled', + mlist.topics_enabled)) except ValueError: # BAW: should really print a warning pass try: - mlist.topics_bodylines_limit = int(cgidata.get('topics_bodylines_limit', [mlist.topics_bodylines_limit])[0]) + mlist.topics_bodylines_limit = int(cgidata.getfirst( + 'topics_bodylines_limit', + mlist.topics_bodylines_limit)) except ValueError: # BAW: should really print a warning pass - - def process_form(self, mlist, cgidata): - # Get the topic information from the form - name = cgidata.get(boxtag, [''])[0] - pattern = cgidata.get(reboxtag, [''])[0] - desc = cgidata.get(desctag, [''])[0] - where = cgidata.get(wheretag, [''])[0] - - # Update list settings - mlist.topics_enabled = int(cgidata.get('topics_enabled', ['0'])[0]) - mlist.topics_bodylines_limit = int(cgidata.get('topics_bodylines_limit', ['0'])[0]) - - # Process the topic - if name and pattern: - if where == 'add': - mlist.AddTopic(name, pattern, desc) - elif where == 'change': - mlist.ChangeTopic(name, pattern, desc) - elif where == 'delete': - mlist.DeleteTopic(name) - - mlist.Save() diff --git a/Mailman/HTMLFormatter.py b/Mailman/HTMLFormatter.py index 9e0e6522..d86a13ce 100644 --- a/Mailman/HTMLFormatter.py +++ b/Mailman/HTMLFormatter.py @@ -22,13 +22,11 @@ from builtins import object import time import re -import os from Mailman import mm_cfg from Mailman import Utils from Mailman import MemberAdaptor from Mailman.htmlformat import * -from Mailman.Logging.Syslog import mailman_log from Mailman.i18n import _ @@ -58,11 +56,11 @@ def GetMailmanFooter(self): innertext, '
    ', Link(self.GetScriptURL('admin'), - _(f'{realname} administrative interface')), + _(f'{realname} administrative interface')), _(' (requires authorization)'), '
    ', Link(Utils.ScriptURL('listinfo'), - _(f'Overview of all {hostname} mailing lists')), + _(f'Overview of all {hostname} mailing lists')), '

    ', MailmanLogo()))).Format() def FormatUsers(self, digest, lang=None, list_hidden=False): @@ -113,7 +111,7 @@ def FormatOptionButton(self, option, value, user): else: optval = self.getMemberOption(user, option) if optval == value: - checked = ' checked' + checked = ' CHECKED' else: checked = '' name = {mm_cfg.DontReceiveOwnPosts : 'dontreceive', @@ -126,15 +124,15 @@ def FormatOptionButton(self, option, value, user): mm_cfg.ReceiveNonmatchingTopics : 'rcvtopic', mm_cfg.DontReceiveDuplicates : 'nodupes', }[option] - return '' % ( + return '' % ( name, value, checked) def FormatDigestButton(self): if self.digest_is_default: - checked = ' checked' + checked = ' CHECKED' else: checked = '' - return '' % checked + return '' % checked def FormatDisabledNotice(self, user): status = self.getDeliveryStatus(user) @@ -147,47 +145,44 @@ def FormatDisabledNotice(self, user): elif status == MemberAdaptor.BYBOUNCE: date = time.strftime('%d-%b-%Y', time.localtime(Utils.midnight(info.date))) - reason = _(f'''; it was disabled due to excessive bounces. The - last bounce was received on {date}''') + reason = _('''; it was disabled due to excessive bounces. The + last bounce was received on %(date)s''') elif status == MemberAdaptor.UNKNOWN: reason = _('; it was disabled for unknown reasons') if reason: note = FontSize('+1', _( - f'Note: your list delivery is currently disabled{reason}.' + 'Note: your list delivery is currently disabled%(reason)s.' )).Format() link = Link('#disable', _('Mail delivery')).Format() mailto = Link('mailto:' + self.GetOwnerEmail(), _('the list administrator')).Format() - return _(f'''

    -

    {note}

    -

    You may have disabled list delivery intentionally, - or it may have been triggered by bounces from your email - address. In either case, to re-enable delivery, change the - {link} option below. Contact {mailto} if you have any - questions or need assistance.

    -
    ''') + return _('''

    %(note)s + +

    You may have disabled list delivery intentionally, + or it may have been triggered by bounces from your email + address. In either case, to re-enable delivery, change the + %(link)s option below. Contact %(mailto)s if you have any + questions or need assistance.''') elif info and info.score > 0: # Provide information about their current bounce score. We know # their membership is currently enabled. score = info.score total = self.bounce_score_threshold - return _(f'''

    -

    We have received some recent bounces from your - address. Your current bounce score is {score} out of a - maximum of {total}. Please double check that your subscribed - address is correct and that there are no problems with delivery to - this address. Your bounce score will be automatically reset if - the problems are corrected soon.

    -
    ''') + return _('''

    We have received some recent bounces from your + address. Your current bounce score is %(score)s out of a + maximum of %(total)s. Please double check that your subscribed + address is correct and that there are no problems with delivery to + this address. Your bounce score will be automatically reset if + the problems are corrected soon.''') else: return '' def FormatUmbrellaNotice(self, user, type): addr = self.GetMemberAdminEmail(user) if self.umbrella_list: - return _(f"(Note - you are subscribing to a list of mailing lists, " - "so the {type} notice will be sent to the admin address" - " for your membership, {addr}.)

    ") + return _("(Note - you are subscribing to a list of mailing lists, " + "so the %(type)s notice will be sent to the admin address" + " for your membership, %(addr)s.)

    ") else: return "" @@ -195,15 +190,15 @@ def FormatSubscriptionMsg(self): msg = '' also = '' if self.subscribe_policy == 1: - msg += _(f'''You will be sent email requesting confirmation, to + msg += _('''You will be sent email requesting confirmation, to prevent others from gratuitously subscribing you.''') elif self.subscribe_policy == 2: - msg += _(f"""This is a closed list, which means your subscription + msg += _("""This is a closed list, which means your subscription will be held for approval. You will be notified of the list moderator's decision by email.""") also = _('also ') elif self.subscribe_policy == 3: - msg += _(f"""You will be sent email requesting confirmation, to + msg += _("""You will be sent email requesting confirmation, to prevent others from gratuitously subscribing you. Once confirmation is received, your request will be held for approval by the list moderator. You will be notified of the moderator's @@ -221,7 +216,7 @@ def FormatSubscriptionMsg(self): msg += _(f'''This is {also}a public list, which means that the list of members list is available to everyone.''') if self.obscure_addresses: - msg += _(f''' (but we obscure the addresses so they are not + msg += _(''' (but we obscure the addresses so they are not easily recognizable by spammers).''') if self.umbrella_list: @@ -260,28 +255,20 @@ def FormatEditingOption(self, lang): either = '' realname = self.real_name - text = _(f'''To unsubscribe from {realname}, get a password reminder, + text = (_(f'''To unsubscribe from {realname}, get a password reminder, or change your subscription options {either}enter your subscription email address: -

    ''') -# text += TextBox('email', size=30).Format() - text += (' ') - text += SubmitButton('UserOptions', _(f'Unsubscribe or edit options')).Format() - text += Hidden('language', lang).Format() - text += ('

    ') -#` text = (_(f'''To unsubscribe from {realname}, get a password reminder, -#` or change your subscription options {either}enter your subscription -#` email address: -#`

    ''') -#` + TextBox('email', size=30).Format() -#` + f' ' -#` + SubmitButton('UserOptions', _(f'Unsubscribe or edit options')).Format() -#` + Hidden('language', lang).Format() -#` + f'
    ') +

    ''') + + TextBox('email', size=30).Format() + + ' ' + + SubmitButton('UserOptions', + _('Unsubscribe or edit options')).Format() + + Hidden('language', lang).Format() + + '
    ') if self.private_roster == 0: - text += _(f'''

    ... or select your entry from + text += _('''

    ... or select your entry from the subscribers list (see above).''') - text += _(f''' If you leave the field blank, you will be prompted for + text += _(''' If you leave the field blank, you will be prompted for your email address''') return text @@ -326,7 +313,7 @@ def RosterOption(self, lang): + whom + " ") container.AddItem(self.FormatBox('roster-email')) - container.AddItem(_(" Password: ") + container.AddItem(_("Password: ") + self.FormatSecureBox('roster-pw') + "  ") container.AddItem(SubmitButton('SubscriberRoster', @@ -342,12 +329,10 @@ def FormatFormStart(self, name, extra='', else: full_url = base_url if mlist: - token = csrf_token(mlist, contexts, user) - if token is None: - return '
    ' % full_url - return """ -""" % (full_url, token) - return '' % full_url + return (""" +""" + % (full_url, csrf_token(mlist, contexts, user))) + return ('' % full_url) def FormatArchiveAnchor(self): return '' % self.GetBaseArchiveURL() @@ -356,10 +341,9 @@ def FormatFormEnd(self): return '' def FormatBox(self, name, size=20, value=''): - if isinstance(value, str): - safevalue = Utils.websafe(value) - else: - safevalue = value + if isinstance(value, bytes): + value = value.decode('utf-8') + safevalue = Utils.websafe(value) return '' % ( name, size, safevalue) @@ -375,141 +359,71 @@ def FormatReminder(self, lang): ' a reminder.') return '' - def format(self, value, charset=None): - """Format a value for HTML output.""" - if value is None: - return '' - if isinstance(value, str): - return Utils.websafe(value) - if isinstance(value, bytes): - if charset is None: - charset = self.preferred_language - try: - return Utils.websafe(value.decode(charset, 'replace')) - except (UnicodeError, LookupError): - return Utils.websafe(value.decode('utf-8', 'replace')) - return str(value) - def ParseTags(self, template, replacements, lang=None): - """Parse template tags and replace them with their values.""" if lang is None: charset = 'us-ascii' else: - charset = Utils.GetCharSet(lang) or 'us-ascii' - - # Read the template file + charset = Utils.GetCharSet(lang) text = Utils.maketext(template, raw=1, lang=lang, mlist=self) - if text is None: - mailman_log('error', 'Could not read template file: %s', template) - return '' - - # Convert replacement keys to lowercase for case-insensitive matching - replacements = {k.lower(): v for k, v in replacements.items()} - - # Split on MM tags, case-insensitive, but preserve HTML entities parts = re.split('(]*>)', text) i = 1 while i < len(parts): - tag = parts[i].lower() # Convert to lowercase for matching + tag = parts[i].lower() if tag in replacements: repl = replacements[tag] - if isinstance(repl, str): - # Don't encode HTML entities - if '&' in repl: - parts[i] = repl - else: - # Ensure proper encoding/decoding - try: - # First try to decode if it's already encoded - if isinstance(repl, bytes): - repl = repl.decode(charset, 'replace') - # Then encode and decode to ensure proper charset - repl = repl.encode(charset, 'replace').decode(charset, 'replace') - parts[i] = repl - except (UnicodeError, LookupError): - # Fallback to utf-8 if charset fails - repl = repl.encode('utf-8', 'replace').decode('utf-8', 'replace') - parts[i] = repl - elif isinstance(repl, bytes): - try: - repl = repl.decode(charset, 'replace') - parts[i] = repl - except (UnicodeError, LookupError): - repl = repl.decode('utf-8', 'replace') - parts[i] = repl - else: - parts[i] = str(repl) + if isinstance(repl, type(u'')): + repl = repl.encode(charset, 'replace') + if type(repl) is bytes: + repl = repl.decode() + parts[i] = repl else: parts[i] = '' i = i + 2 - - # Join parts and ensure proper encoding - result = EMPTYSTRING.join(parts) - try: - # Ensure the final output is properly encoded - if isinstance(result, bytes): - result = result.decode(charset, 'replace') - return result - except (UnicodeError, LookupError): - return result.decode('utf-8', 'replace') - - def GetStandardReplacements(self, lang=None, replacements=None): - """Get the standard replacements for this list.""" - if replacements is None: - replacements = {} - if lang is None: - lang = self.preferred_language - - try: - # Get member counts - dmember_len = len(self.getDigestMemberKeys()) - member_len = len(self.getRegularMemberKeys()) - - # Handle language selection - if len(self.GetAvailableLanguages()) == 1: - listlangs = _(Utils.GetLanguageDescr(self.preferred_language)) - else: - listlangs = self.GetLangSelectBox(lang).Format() - - # Get charset - if lang: - cset = Utils.GetCharSet(lang) or 'us-ascii' - else: - cset = Utils.GetCharSet(self.preferred_language) or 'us-ascii' - - # Add all standard replacements (using lowercase to match original) - replacements.update({ - '': self.GetMailmanFooter(), - '': self.real_name, - '': self._internal_name, - '': self.GetDescription(cset), - '': '' + BR.join(self.info.split(NL)) + '', - '': self.FormatFormEnd(), - '': self.FormatArchiveAnchor(), - '': '', - '': self.FormatSubscriptionMsg(), - '': self.RestrictedListMessage(_('The current archive'), self.archive_private), - '': repr(member_len), - '': repr(dmember_len), - '': repr(member_len + dmember_len), - '': '%s' % self.GetListEmail(), - '': '%s' % self.GetRequestEmail(), - '': self.GetOwnerEmail(), - '': self.FormatReminder(self.preferred_language), - '': self.host_name, - '': listlangs, - }) - - # Add favicon if configured - if mm_cfg.IMAGE_LOGOS: - replacements[''] = mm_cfg.IMAGE_LOGOS + mm_cfg.SHORTCUT_ICON - - mailman_log('trace', 'Added %d standard replacements', len(replacements)) - - except Exception as e: - mailman_log('error', 'Error getting standard replacements: %s', str(e)) - - return replacements + return EMPTYSTRING.join(parts) + + # This needs to wait until after the list is inited, so let's build it + # when it's needed only. + def GetStandardReplacements(self, lang=None): + dmember_len = len(self.getDigestMemberKeys()) + member_len = len(self.getRegularMemberKeys()) + # If only one language is enabled for this mailing list, omit the + # language choice buttons. + if len(self.GetAvailableLanguages()) == 1: + listlangs = _(Utils.GetLanguageDescr(self.preferred_language)) + else: + listlangs = self.GetLangSelectBox(lang).Format() + if lang: + cset = Utils.GetCharSet(lang) or 'us-ascii' + else: + cset = Utils.GetCharSet(self.preferred_language) or 'us-ascii' + d = { + '' : self.GetMailmanFooter(), + '' : self.real_name, + '' : self._internal_name, + '' : + Utils.websafe(self.GetDescription(cset)), + '' : + '' + BR.join(self.info.split(NL)) + '', + '' : self.FormatFormEnd(), + '' : self.FormatArchiveAnchor(), + '' : '', + '' : self.FormatSubscriptionMsg(), + '' : \ + self.RestrictedListMessage(_('The current archive'), + self.archive_private), + '' : repr(member_len), + '' : repr(dmember_len), + '' : repr(member_len + dmember_len), + '' : '%s' % self.GetListEmail(), + '' : '%s' % self.GetRequestEmail(), + '' : self.GetOwnerEmail(), + '' : self.FormatReminder(self.preferred_language), + '' : self.host_name, + '' : listlangs, + } + if mm_cfg.IMAGE_LOGOS: + d[''] = mm_cfg.IMAGE_LOGOS + mm_cfg.SHORTCUT_ICON + return d def GetAllReplacements(self, lang=None, list_hidden=False): """ @@ -527,7 +441,7 @@ def GetLangSelectBox(self, lang=None, varname='language'): if lang is None: lang = self.preferred_language # Figure out the available languages - values = self.available_languages + values = self.GetAvailableLanguages() legend = list(map(_, list(map(Utils.GetLanguageDescr, values)))) try: selected = values.index(lang) diff --git a/Mailman/Handlers/Acknowledge.py b/Mailman/Handlers/Acknowledge.py index 80183a5a..59e508cf 100644 --- a/Mailman/Handlers/Acknowledge.py +++ b/Mailman/Handlers/Acknowledge.py @@ -24,11 +24,12 @@ from Mailman import mm_cfg from Mailman import Utils -from Mailman.Message import Message +from Mailman import Message from Mailman import Errors from Mailman.i18n import _ + def process(mlist, msg, msgdata): # Extract the sender's address and find them in the user database sender = msgdata.get('original_sender', msg.get_sender()) @@ -55,7 +56,7 @@ def process(mlist, msg, msgdata): # Craft the outgoing message, with all headers and attributes # necessary for general delivery. Then enqueue it to the outgoing # queue. - subject = _('%(realname)s post acknowledgement') % {'realname': realname} - usermsg = Mailman.Message.UserNotification(sender, mlist.GetBouncesEmail(), + subject = _('%(realname)s post acknowledgement') + usermsg = Message.UserNotification(sender, mlist.GetBouncesEmail(), subject, text, lang) usermsg.send(mlist) diff --git a/Mailman/Handlers/Approve.py b/Mailman/Handlers/Approve.py index 9984f4dd..4dad429d 100644 --- a/Mailman/Handlers/Approve.py +++ b/Mailman/Handlers/Approve.py @@ -45,6 +45,7 @@ def _(s): del _ + def process(mlist, msg, msgdata): # Short circuits # Do not short circuit. The problem is SpamDetect comes before Approve. @@ -83,10 +84,8 @@ def process(mlist, msg, msgdata): for lineno, line in zip(list(range(len(lines))), lines): if line.strip(): break - # Decode bytes to string if needed - if isinstance(line, bytes): - line = line.decode('utf-8', errors='replace') - i = line.find(':') + + i = line.find(b':') if i >= 0: name = line[:i] value = line[i+1:] diff --git a/Mailman/Handlers/CalcRecips.py b/Mailman/Handlers/CalcRecips.py index 4f59661c..9fff0859 100644 --- a/Mailman/Handlers/CalcRecips.py +++ b/Mailman/Handlers/CalcRecips.py @@ -26,11 +26,10 @@ import email.utils from Mailman import mm_cfg from Mailman import Utils -from Mailman.Message import Message +from Mailman import Message from Mailman import Errors from Mailman.MemberAdaptor import ENABLED -# Remove the MailList import from here since it's causing a circular dependency -# from Mailman.MailList import MailList +from Mailman.MailList import MailList from Mailman.i18n import _ from Mailman.Logging.Syslog import syslog from Mailman.Errors import MMUnknownListError @@ -42,11 +41,8 @@ from sets import Set as set + def process(mlist, msg, msgdata): - """Process message to calculate recipients.""" - # Import MailList here to avoid circular dependency - from Mailman.MailList import MailList - # Short circuit if we've already calculated the recipients list, # regardless of whether the list is empty or not. if 'recips' in msgdata: @@ -107,11 +103,8 @@ def process(mlist, msg, msgdata): msgdata['recips'] = recips + def do_topic_filters(mlist, msg, msgdata, recips): - """Apply topic filters to recipients.""" - # Import MailList here to avoid circular dependency - from Mailman.MailList import MailList - if not mlist.topics_enabled: # MAS: if topics are currently disabled for the list, send to all # regardless of ReceiveNonmatchingTopics @@ -156,12 +149,8 @@ def do_topic_filters(mlist, msg, msgdata, recips): for user in zaprecips: recips.remove(user) - + def do_exclude(mlist, msg, msgdata, recips): - """Handle recipient exclusions.""" - # Import MailList here to avoid circular dependency - from Mailman.MailList import MailList - # regular_exclude_lists are the other mailing lists on this mailman # installation whose members are excluded from the regular (non-digest) # delivery of this list if those list addresses appear in To: or Cc: @@ -209,12 +198,8 @@ def do_exclude(mlist, msg, msgdata, recips): recips -= srecips return list(recips) - + def do_include(mlist, msg, msgdata, recips): - """Handle recipient inclusions.""" - # Import MailList here to avoid circular dependency - from Mailman.MailList import MailList - # regular_include_lists are the other mailing lists on this mailman # installation whose members are included in the regular (non-digest) # delivery if those list addresses don't appear in To: or Cc: headers. diff --git a/Mailman/Handlers/Cleanse.py b/Mailman/Handlers/Cleanse.py index c39c6fc3..2cf4acec 100644 --- a/Mailman/Handlers/Cleanse.py +++ b/Mailman/Handlers/Cleanse.py @@ -50,12 +50,6 @@ def remove_nonkeepers(msg): def process(mlist, msg, msgdata): - """Process the message.""" - # Remove old message-id if it exists - if 'message-id' in msg: - del msg['message-id'] - # Set new message-id - msg['Message-ID'] = unique_message_id(mlist) # Always remove this header from any outgoing messages. Be sure to do # this after the information on the header is actually used, but before a # permanent record of the header is saved. @@ -83,6 +77,11 @@ def process(mlist, msg, msgdata): del msg['x-originating-email'] # And these can reveal the sender too del msg['received'] + # And so can the message-id so replace it. + del msg['message-id'] + msg['Message-ID'] = unique_message_id(mlist) + # And something sets this + del msg['x-envelope-from'] # And now remove all but the keepers. remove_nonkeepers(msg) i18ndesc = str(uheader(mlist, mlist.description, 'From')) @@ -98,4 +97,8 @@ def process(mlist, msg, msgdata): del msg['x-confirm-reading-to'] # Pegasus mail uses this one... sigh del msg['x-pmrqc'] - return True + + # Remove any header whose value is not a string. + for h, v in list(msg.items()): + if not isinstance(v, str): + del msg[h] diff --git a/Mailman/Handlers/CleanseDKIM.py b/Mailman/Handlers/CleanseDKIM.py index 79cacbf4..45ac5edc 100644 --- a/Mailman/Handlers/CleanseDKIM.py +++ b/Mailman/Handlers/CleanseDKIM.py @@ -25,8 +25,6 @@ originating at the Mailman server for the outgoing message. """ -from __future__ import absolute_import, print_function, unicode_literals - from Mailman import mm_cfg @@ -46,19 +44,13 @@ def process(mlist, msg, msgdata): ): return if (mm_cfg.REMOVE_DKIM_HEADERS == 3): - # Save original headers before removing them - for header in ('domainkey-signature', 'dkim-signature', 'authentication-results'): - values = msg.get_all(header, []) - if values: - # Store original values in X-Mailman-Original-* headers - for value in values: - msg.add_header('X-Mailman-Original-' + header.title().replace('-', ''), value) - # Remove the original headers - while header in msg: - del msg[header] - else: - # Just remove the headers without saving them - for header in ('domainkey-signature', 'dkim-signature', 'authentication-results'): - while header in msg: - del msg[header] + for value in msg.get_all('domainkey-signature', []): + msg['X-Mailman-Original-DomainKey-Signature'] = value + for value in msg.get_all('dkim-signature', []): + msg['X-Mailman-Original-DKIM-Signature'] = value + for value in msg.get_all('authentication-results', []): + msg['X-Mailman-Original-Authentication-Results'] = value + del msg['domainkey-signature'] + del msg['dkim-signature'] + del msg['authentication-results'] diff --git a/Mailman/Handlers/CookHeaders.py b/Mailman/Handlers/CookHeaders.py index 2e2f1842..40ce483f 100644 --- a/Mailman/Handlers/CookHeaders.py +++ b/Mailman/Handlers/CookHeaders.py @@ -20,253 +20,485 @@ list configuration. """ -from __future__ import absolute_import, print_function, unicode_literals - +from __future__ import nested_scopes import re + from email.charset import Charset from email.header import Header, decode_header, make_header from email.utils import parseaddr, formataddr, getaddresses from email.errors import HeaderParseError -from email.iterators import body_line_iterator from Mailman import i18n from Mailman import mm_cfg from Mailman import Utils from Mailman.i18n import _ -from Mailman.Logging.Syslog import mailman_log +from Mailman.Logging.Syslog import syslog CONTINUATION = ',\n ' COMMASPACE = ', ' MAXLINELEN = 78 -def _isunicode(s): - return isinstance(s, str) - nonascii = re.compile(r'[^\s!-~]') def uheader(mlist, s, header_name=None, continuation_ws=' ', maxlinelen=None): - """Create a Header object from a string with proper charset handling. - - This function ensures proper handling of both str and bytes input, - and uses the list's preferred charset for encoding. - """ - # Get the charset to encode the string in + # Get the charset to encode the string in. Then search if there is any + # non-ascii character is in the string. If there is and the charset is + # us-ascii then we use iso-8859-1 instead. If the string is ascii only + # we use 'us-ascii' if another charset is specified. charset = Utils.GetCharSet(mlist.preferred_language) - - # Convert input to str if it's bytes if isinstance(s, bytes): - try: - s = s.decode('ascii') - except UnicodeDecodeError: - try: - s = s.decode(charset) - except UnicodeDecodeError: - s = s.decode('utf-8', 'replace') - - # If there are non-ASCII characters, use the list's charset - if nonascii.search(s): + search_string = s.decode() + else: + search_string = s + + if nonascii.search(search_string): + # use list charset but ... if charset == 'us-ascii': - charset = 'utf-8' + charset = 'iso-8859-1' else: + # there is no nonascii so ... charset = 'us-ascii' - try: return Header(s, charset, maxlinelen, header_name, continuation_ws) except UnicodeError: - mailman_log('error', 'list: %s: cannot encode "%s" as %s', - mlist.internal_name(), s, charset) - # Fall back to ASCII with replacement characters - return Header(s.encode('ascii', 'replace').decode('ascii'), - 'us-ascii', maxlinelen, header_name, continuation_ws) + syslog('error', 'list: %s: can\'t decode "%s" as %s', + mlist.internal_name(), s, charset) + return Header('', charset, maxlinelen, header_name, continuation_ws) def change_header(name, value, mlist, msg, msgdata, delete=True, repl=True): - """Change or add a message header. - - This function handles header changes in a Python 3 compatible way, - properly dealing with encodings and header values. - """ if ((msgdata.get('from_is_list') == 2 or (msgdata.get('from_is_list') == 0 and mlist.from_is_list == 2)) and not msgdata.get('_fasttrack') ) or name.lower() in ('from', 'reply-to', 'cc'): - # Store the header in msgdata for later use + # The or name.lower() in ... above is because when we are munging + # the From:, we want to defer the resultant changes to From:, + # Reply-To:, and/or Cc: until after the message passes through + # ToDigest, ToArchive and ToUsenet. Thus, we put them in + # msgdata[add_header] here and apply them in WrapMessage. msgdata.setdefault('add_header', {})[name] = value - # Also add the header to the message if it's not From, Reply-To, or Cc - if name.lower() not in ('from', 'reply-to', 'cc'): - if delete: - del msg[name] - if isinstance(value, Header): - msg[name] = value - else: - try: - msg[name] = str(value) - except UnicodeEncodeError: - msg[name] = Header(value, - Utils.GetCharSet(mlist.preferred_language)) elif repl or name not in msg: if delete: del msg[name] - if isinstance(value, Header): - msg[name] = value - else: - try: - msg[name] = str(value) - except UnicodeEncodeError: - msg[name] = Header(value, - Utils.GetCharSet(mlist.preferred_language)) + msg[name] = value + + def process(mlist, msg, msgdata): - """Process the message by cooking its headers.""" - msgid = msg.get('message-id', 'n/a') - - # Log start of processing with enhanced details - mailman_log('debug', 'CookHeaders: Starting to process message %s for list %s', - msgid, mlist.internal_name()) - mailman_log('debug', 'CookHeaders: Message details:') - mailman_log('debug', ' Message ID: %s', msgid) - mailman_log('debug', ' From: %s', msg.get('from', 'unknown')) - mailman_log('debug', ' To: %s', msg.get('to', 'unknown')) - mailman_log('debug', ' Subject: %s', msg.get('subject', '(no subject)')) - mailman_log('debug', ' Message type: %s', type(msg).__name__) - mailman_log('debug', ' Message data: %s', str(msgdata)) - mailman_log('debug', ' Pipeline: %s', msgdata.get('pipeline', 'No pipeline')) - - # Set the "X-Ack: no" header if noack flag is set + # Set the "X-Ack: no" header if noack flag is set. if msgdata.get('noack'): - mailman_log('debug', 'CookHeaders: Setting X-Ack: no for message %s', msgid) change_header('X-Ack', 'no', mlist, msg, msgdata) - - # Save original sender for later + # Because we're going to modify various important headers in the email + # message, we want to save some of the information in the msgdata + # dictionary for later. Specifically, the sender header will get waxed, + # but we need it for the Acknowledge module later. + # We may have already saved it; if so, don't clobber it here. if 'original_sender' not in msgdata: msgdata['original_sender'] = msg.get_sender() - mailman_log('debug', 'CookHeaders: Saved original sender %s for message %s', - msgdata['original_sender'], msgid) - - # Handle subject prefix and other headers + # VirginRunner sets _fasttrack for internally crafted messages. fasttrack = msgdata.get('_fasttrack') if not msgdata.get('isdigest') and not fasttrack: try: - mailman_log('debug', 'CookHeaders: Adding subject prefix for message %s', msgid) prefix_subject(mlist, msg, msgdata) - except (UnicodeError, ValueError) as e: - mailman_log('error', 'CookHeaders: Error adding subject prefix for message %s: %s', - msgid, str(e)) - - # Mark message as processed - mailman_log('debug', 'CookHeaders: Adding X-BeenThere header for message %s', msgid) + except (UnicodeError, ValueError): + # TK: Sometimes subject header is not MIME encoded for 8bit + # simply abort prefixing. + pass + # Mark message so we know we've been here, but leave any existing + # X-BeenThere's intact. change_header('X-BeenThere', mlist.GetListEmail(), - mlist, msg, msgdata, delete=False) - - # Add standard headers - mailman_log('debug', 'CookHeaders: Adding standard headers for message %s', msgid) + mlist, msg, msgdata, delete=False) + # Add Precedence: and other useful headers. None of these are standard + # and finding information on some of them are fairly difficult. Some are + # just common practice, and we'll add more here as they become necessary. + # Good places to look are: + # + # http://www.dsv.su.se/~jpalme/ietf/jp-ietf-home.html + # http://www.faqs.org/rfcs/rfc2076.html + # + # None of these headers are added if they already exist. BAW: some + # consider the advertising of this a security breach. I.e. if there are + # known exploits in a particular version of Mailman and we know a site is + # using such an old version, they may be vulnerable. It's too easy to + # edit the code to add a configuration variable to handle this. change_header('X-Mailman-Version', mm_cfg.VERSION, - mlist, msg, msgdata, repl=False) + mlist, msg, msgdata, repl=False) + # We set "Precedence: list" because this is the recommendation from the + # sendmail docs, the most authoritative source of this header's semantics. change_header('Precedence', 'list', - mlist, msg, msgdata, repl=False) - - # Handle From: header munging if needed + mlist, msg, msgdata, repl=False) + # Do we change the from so the list takes ownership of the email if (msgdata.get('from_is_list') or mlist.from_is_list) and not fasttrack: - mailman_log('debug', 'CookHeaders: Munging From header for message %s', msgid) - munge_from_header(mlist, msg, msgdata) - - mailman_log('debug', 'CookHeaders: Finished processing message %s', msgid) + # Be as robust as possible here. + faddrs = getaddresses(msg.get_all('from', [])) + # Strip the nulls and bad emails. + faddrs = [x for x in faddrs if x[1].find('@') > 0] + if len(faddrs) == 1: + realname, email = o_from = faddrs[0] + else: + # No From: or multiple addresses. Just punt and take + # the get_sender result. + realname = '' + email = msgdata['original_sender'] + o_from = (realname, email) + if not realname: + if mlist.isMember(email): + realname = mlist.getMemberName(email) or email + else: + realname = email + # Remove domain from realname if it looks like an email address + realname = re.sub(r'@([^ .]+\.)+[^ .]+$', '---', realname) + # Make a display name and RFC 2047 encode it if necessary. This is + # difficult and kludgy. If the realname came from From: it should be + # ascii or RFC 2047 encoded. If it came from the list, it should be + # in the charset of the list's preferred language or possibly unicode. + # if it's from the email address, it should be ascii. In any case, + # make it a unicode. + if isinstance(realname, str): + urn = realname + else: + rn, cs = ch_oneline(realname) + urn = str(rn, cs, errors='replace') + # likewise, the list's real_name which should be ascii, but use the + # charset of the list's preferred_language which should be a superset. + lcs = Utils.GetCharSet(mlist.preferred_language) + + if isinstance(mlist.real_name, str): + ulrn = mlist.real_name + else: + ulrn = str(mlist.real_name, lcs, errors='replace') + + # get translated 'via' with dummy replacements + realname = '%(realname)s' + lrn = '%(lrn)s' + # We want the i18n context to be the list's preferred_language. It + # could be the poster's. + otrans = i18n.get_translation() + i18n.set_language(mlist.preferred_language) + via = _('%(realname)s via %(lrn)s') + i18n.set_translation(otrans) + + if isinstance(via, str): + uvia = via + else: + uvia = str(via, lcs, errors='replace') -def munge_from_header(mlist, msg, msgdata): - """Munge the From: header for the list. - - This is separated into its own function to make the logic clearer - and handle all the encoding issues in one place. - """ - # Get the original From: addresses - faddrs = getaddresses(msg.get_all('from', [])) - faddrs = [x for x in faddrs if x[1].find('@') > 0] - - if len(faddrs) == 1: - realname, email = faddrs[0] + # Replace the dummy replacements. + uvia = re.sub(u'%\\(lrn\\)s', ulrn, re.sub(u'%\\(realname\\)s', urn, uvia)) + # And get an RFC 2047 encoded header string. + dn = str(Header(uvia, lcs)) + change_header('From', + formataddr((dn, mlist.GetListEmail())), + mlist, msg, msgdata) else: - realname = '' - email = msgdata['original_sender'] - - # Get or create realname - if not realname: - if mlist.isMember(email): - realname = mlist.getMemberName(email) or email + # Use this as a flag + o_from = None + # Reply-To: munging. Do not do this if the message is "fast tracked", + # meaning it is internally crafted and delivered to a specific user. BAW: + # Yuck, I really hate this feature but I've caved under the sheer pressure + # of the (very vocal) folks want it. OTOH, RFC 2822 allows Reply-To: to + # be a list of addresses, so instead of replacing the original, simply + # augment it. RFC 2822 allows max one Reply-To: header so collapse them + # if we're adding a value, otherwise don't touch it. (Should we collapse + # in all cases?) + # MAS: We need to do some things with the original From: if we've munged + # it for DMARC mitigation. We have goals for this process which are + # not completely compatible, so we do the best we can. Our goals are: + # 1) as long as the list is not anonymous, the original From: address + # should be obviously exposed, i.e. not just in a header that MUAs + # don't display. + # 2) the original From: address should not be in a comment or display + # name in the new From: because it is claimed that multiple domains + # in any fields in From: are indicative of spamminess. This means + # it should be in Reply-To: or Cc:. + # 3) the behavior of an MUA doing a 'reply' or 'reply all' should be + # consistent regardless of whether or not the From: is munged. + # Goal 3) implies sometimes the original From: should be in Reply-To: + # and sometimes in Cc:, and even so, this goal won't be achieved in + # all cases with all MUAs. In cases of conflict, the above ordering of + # goals is priority order. + + if not fasttrack: + # A convenience function, requires nested scopes. pair is (name, addr) + new = [] + d = {} + def add(pair): + lcaddr = pair[1].lower() + if lcaddr in d: + return + d[lcaddr] = pair + new.append(pair) + # List admin wants an explicit Reply-To: added + if mlist.reply_goes_to_list == 2: + add(parseaddr(mlist.reply_to_address)) + # If we're not first stripping existing Reply-To: then we need to add + # the original Reply-To:'s to the list we're building up. In both + # cases we'll zap the existing field because RFC 2822 says max one is + # allowed. + o_rt = False + if not mlist.first_strip_reply_to: + orig = msg.get_all('reply-to', []) + for pair in getaddresses(orig): + # There's an original Reply-To: and we're not removing it. + add(pair) + o_rt = True + # We also need to put the old From: in Reply-To: in all cases where + # it is not going in Cc:. This is when reply_goes_to_list == 0 and + # either there was no original Reply-To: or we stripped it. + # However, if there was an original Reply-To:, unstripped, and it + # contained the original From: address we need to flag that it's + # there so we don't add the original From: to Cc: + if o_from and mlist.reply_goes_to_list == 0: + if o_rt: + if o_from[1].lower() in d: + # Original From: address is in original Reply-To:. + # Pretend we added it. + o_from = None + else: + add(o_from) + # Flag that we added it. + o_from = None + # Set Reply-To: header to point back to this list. Add this last + # because some folks think that some MUAs make it easier to delete + # addresses from the right than from the left. + if mlist.reply_goes_to_list == 1: + i18ndesc = uheader(mlist, mlist.description, 'Reply-To') + add((str(i18ndesc), mlist.GetListEmail())) + # Don't put Reply-To: back if there's nothing to add! + if new: + # Preserve order + change_header('Reply-To', + COMMASPACE.join([formataddr(pair) for pair in new]), + mlist, msg, msgdata) else: - realname = email - - # Remove domain from realname if it looks like an email - realname = re.sub(r'@([^ .]+\.)+[^ .]+$', '---', realname) - - # Convert realname to unicode - charset = Utils.GetCharSet(mlist.preferred_language) - if isinstance(realname, bytes): - try: - realname = realname.decode(charset) - except UnicodeDecodeError: - realname = realname.decode('utf-8', 'replace') - - # Format the new From: header - via = _('%(realname)s via %(listname)s') - listname = mlist.real_name - if isinstance(listname, bytes): - listname = listname.decode(charset, 'replace') - - display_name = via % {'realname': realname, 'listname': listname} - - # Create the new From: header value - new_from = formataddr((display_name, mlist.GetListEmail())) - change_header('From', new_from, mlist, msg, msgdata) + del msg['reply-to'] + # The To field normally contains the list posting address. However + # when messages are fully personalized, that header will get + # overwritten with the address of the recipient. We need to get the + # posting address in one of the recipient headers or they won't be + # able to reply back to the list. It's possible the posting address + # was munged into the Reply-To header, but if not, we'll add it to a + # Cc header. BAW: should we force it into a Reply-To header in the + # above code? + # Also skip Cc if this is an anonymous list as list posting address + # is already in From and Reply-To in this case. + # We do add the Cc in cases where From: header munging is being done + # because even though the list address is in From:, the Reply-To: + # poster will override it. Brain dead MUAs may then address the list + # twice on a 'reply all', but reasonable MUAs should do the right + # thing. We also add the original From: to Cc: if it wasn't added + # to Reply-To: + add_list = (mlist.personalize == 2 and + mlist.reply_goes_to_list != 1 and + not mlist.anonymous_list) + if add_list or o_from: + # Watch out for existing Cc headers, merge, and remove dups. Note + # that RFC 2822 says only zero or one Cc header is allowed. + new = [] + d = {} + # If we're adding the original From:, add it first. + if o_from: + add(o_from) + # AvoidDuplicates may have set a new Cc: in msgdata.add_header, + # so check that. + if ('add_header' in msgdata and + 'Cc' in msgdata['add_header']): + for pair in getaddresses([msgdata['add_header']['Cc']]): + add(pair) + else: + for pair in getaddresses(msg.get_all('cc', [])): + add(pair) + if add_list: + i18ndesc = uheader(mlist, mlist.description, 'Cc') + add((str(i18ndesc), mlist.GetListEmail())) + change_header('Cc', + COMMASPACE.join([formataddr(pair) for pair in new]), + mlist, msg, msgdata) + # Add list-specific headers as defined in RFC 2369 and RFC 2919, but only + # if the message is being crafted for a specific list (e.g. not for the + # password reminders). + # + # BAW: Some people really hate the List-* headers. It seems that the free + # version of Eudora (possibly on for some platforms) does not hide these + # headers by default, pissing off their users. Too bad. Fix the MUAs. + if msgdata.get('_nolist') or not mlist.include_rfc2369_headers: + return + # This will act like an email address for purposes of formataddr() + listid = '%s.%s' % (mlist.internal_name(), mlist.host_name) + cset = Utils.GetCharSet(mlist.preferred_language) + if mlist.description: + # Don't wrap the header since here we just want to get it properly RFC + # 2047 encoded. + i18ndesc = uheader(mlist, mlist.description, 'List-Id', maxlinelen=998) + listid_h = formataddr((str(i18ndesc), listid)) + # With some charsets (utf-8?) and some invalid chars, str(18ndesc) can + # be empty. + if str(i18ndesc): + listid_h = formataddr((str(i18ndesc), listid)) + else: + listid_h = '<%s>' % listid + else: + # without desc we need to ensure the MUST brackets + listid_h = '<%s>' % listid + # We always add a List-ID: header. + change_header('List-Id', listid_h, mlist, msg, msgdata) + # For internally crafted messages, we also add a (nonstandard), + # "X-List-Administrivia: yes" header. For all others (i.e. those coming + # from list posts), we add a bunch of other RFC 2369 headers. + requestaddr = mlist.GetRequestEmail() + subfieldfmt = '<%s>, ' + listinfo = mlist.GetScriptURL('listinfo', absolute=1) + useropts = mlist.GetScriptURL('options', absolute=1) + headers = {} + if msgdata.get('reduced_list_headers'): + headers['X-List-Administrivia'] = 'yes' + else: + headers.update({ + 'List-Help' : '' % requestaddr, + 'List-Unsubscribe': subfieldfmt % (useropts, requestaddr, 'un'), + 'List-Subscribe' : subfieldfmt % (listinfo, requestaddr, ''), + }) + # List-Post: is controlled by a separate attribute + if mlist.include_list_post_header: + headers['List-Post'] = '' % mlist.GetListEmail() + # Add this header if we're archiving + if mlist.archive: + archiveurl = mlist.GetBaseArchiveURL() + headers['List-Archive'] = '<%s>' % archiveurl + # First we delete any pre-existing headers because the RFC permits only + # one copy of each, and we want to be sure it's ours. + for h, v in list(headers.items()): + # Wrap these lines if they are too long. 78 character width probably + # shouldn't be hardcoded, but is at least text-MUA friendly. The + # adding of 2 is for the colon-space separator. + if len(h) + 2 + len(v) > 78: + v = CONTINUATION.join(v.split(', ')) + change_header(h, v, mlist, msg, msgdata) + + def prefix_subject(mlist, msg, msgdata): - """Add the list's subject prefix to the message's Subject: header.""" - # Get the subject and charset - subject = msg.get('subject', '') - if not subject: - return - - # Get the list's charset - cset = mlist.preferred_language - - # Get the prefix + # Add the subject prefix unless the message is a digest or is being fast + # tracked (e.g. internally crafted, delivered to a single user such as the + # list admin). prefix = mlist.subject_prefix.strip() if not prefix: return - - # Handle the subject encoding - try: - # If subject is already a string, use it directly + subject = msg.get('subject', '') + # Try to figure out what the continuation_ws is for the header + if isinstance(subject, Header): + lines = str(subject).splitlines() + else: + lines = subject.splitlines() + ws = ' ' + if len(lines) > 1 and lines[1] and lines[1][0] in ' \t': + ws = lines[1][0] + msgdata['origsubj'] = subject + # The subject may be multilingual but we take the first charset as major + # one and try to decode. If it is decodable, returned subject is in one + # line and cset is properly set. If fail, subject is mime-encoded and + # cset is set as us-ascii. See detail for ch_oneline() (CookHeaders one + # line function). + subject, cset = ch_oneline(subject) + # TK: Python interpreter has evolved to be strict on ascii charset code + # range. It is safe to use unicode string when manupilating header + # contents with re module. It would be best to return unicode in + # ch_oneline() but here is temporary solution. + subject = subject.__str__() #TODO will this break some encodings? + # If the subject_prefix contains '%d', it is replaced with the + # mailing list sequential number. Sequential number format allows + # '%d' or '%05d' like pattern. + prefix_pattern = re.escape(prefix) + # unescape '%' :-< + prefix_pattern = prefix_pattern.replace(r'\%', '%') + p = re.compile(r'%\d*d') + if p.search(prefix, 1): + # prefix have number, so we should search prefix w/number in subject. + # Also, force new style. + prefix_pattern = p.sub(r'\\s*\\d+\\s*', prefix_pattern) + old_style = False + else: + old_style = mm_cfg.OLD_STYLE_PREFIXING + subject = re.sub(prefix_pattern, '', subject) + # Previously the following re didn't have the first \s*. It would fail + # if the incoming Subject: was like '[prefix] Re: Re: Re:' because of the + # leading space after stripping the prefix. It is not known what MUA would + # create such a Subject:, but the issue was reported. + rematch = re.match( + r'(\s*(RE|AW|SV|VS)\s*(\[\d+\])?\s*:\s*)+', + subject, re.I) + if rematch: + subject = subject[rematch.end():] + recolon = 'Re:' + else: + recolon = '' + # Strip leading and trailing whitespace from subject. + subject = subject.strip() + # At this point, subject may become null if someone post mail with + # Subject: [subject prefix] + if subject == '': + # We want the i18n context to be the list's preferred_language. It + # could be the poster's. + otrans = i18n.get_translation() + i18n.set_language(mlist.preferred_language) + subject = _('(no subject)') + i18n.set_translation(otrans) + cset = Utils.GetCharSet(mlist.preferred_language) if isinstance(subject, str): - subject_str = subject - # If subject is a Header object, convert it to string - elif isinstance(subject, Header): - subject_str = str(subject) - else: - # Try to decode the subject - try: - subject_str = str(subject, cset) - except (UnicodeError, LookupError): - # If that fails, try utf-8 - subject_str = str(subject, 'utf-8', 'replace') - except Exception as e: - mailman_log('error', 'Error decoding subject: %s', str(e)) - return - - # Add the prefix if it's not already there - if not subject_str.startswith(prefix): - msg['Subject'] = prefix + ' ' + subject_str + subject = subject.encode() + subject = str(subject, cset) + # and substitute %d in prefix with post_id + try: + prefix = prefix % mlist.post_id + except TypeError: + pass + # If charset is 'us-ascii', try to concatnate as string because there + # is some weirdness in Header module (TK) + if cset == 'us-ascii': + try: + if old_style: + h = u' '.join([recolon, prefix, subject]) + else: + if recolon: + h = u' '.join([prefix, recolon, subject]) + else: + h = u' '.join([prefix, subject]) + h = h.encode('us-ascii') + h = uheader(mlist, h, 'Subject', continuation_ws=ws) + change_header('Subject', h, mlist, msg, msgdata) + ss = u' '.join([recolon, subject]) + ss = ss.encode('us-ascii') + ss = uheader(mlist, ss, 'Subject', continuation_ws=ws) + msgdata['stripped_subject'] = ss + return + except UnicodeError: + pass + # Get the header as a Header instance, with proper unicode conversion + # Because of rfc2047 encoding, spaces between encoded words can be + # insignificant, so we need to append spaces to our encoded stuff. + prefix += ' ' + if recolon: + recolon += ' ' + if old_style: + h = uheader(mlist, recolon, 'Subject', continuation_ws=ws) + h.append(prefix) + else: + h = uheader(mlist, prefix, 'Subject', continuation_ws=ws) + h.append(recolon) + # TK: Subject is concatenated and unicode string. + subject = subject.encode(cset, 'replace') + h.append(subject, cset) + change_header('Subject', h, mlist, msg, msgdata) + ss = uheader(mlist, recolon, 'Subject', continuation_ws=ws) + ss.append(subject, cset) + msgdata['stripped_subject'] = ss + + def ch_oneline(headerstr): # Decode header string in one line and convert into single charset # copied and modified from ToDigest.py and Utils.py # return (string, cset) tuple as check for failure try: - # Ensure headerstr is a string, not bytes - if isinstance(headerstr, bytes): - try: - headerstr = headerstr.decode('utf-8') - except UnicodeDecodeError: - headerstr = headerstr.decode('us-ascii', 'replace') - d = decode_header(headerstr) # at this point, we should rstrip() every string because some # MUA deliberately add trailing spaces when composing return @@ -279,14 +511,8 @@ def ch_oneline(headerstr): cset = x[1] break h = make_header(d) - ustr = str(h) - oneline = u''.join(ustr.splitlines()) - return oneline.encode(cset, 'replace'), cset + ustr = h + return ustr, cset except (LookupError, UnicodeError, ValueError, HeaderParseError): # possibly charset problem. return with undecoded string in one line. - if isinstance(headerstr, bytes): - try: - headerstr = headerstr.decode('utf-8') - except UnicodeDecodeError: - headerstr = headerstr.decode('us-ascii', 'replace') return ''.join(headerstr.splitlines()), 'us-ascii' diff --git a/Mailman/Handlers/Decorate.py b/Mailman/Handlers/Decorate.py index 3f2a8c80..43338768 100644 --- a/Mailman/Handlers/Decorate.py +++ b/Mailman/Handlers/Decorate.py @@ -18,6 +18,7 @@ """Decorate a message by sticking the header and footer around it.""" from builtins import str +import codecs import re from email.mime.text import MIMEText @@ -40,8 +41,7 @@ def process(mlist, msg, msgdata): # Calculate the extra personalization dictionary. Note that the # length of the recips list better be exactly 1. recips = msgdata.get('recips') - if not (isinstance(recips, list) and len(recips) == 1): - raise ValueError(f'Invalid recipients: expected list with one item, got {type(recips)} with {len(recips)} items') + assert type(recips) == list and len(recips) == 1 member = recips[0].lower() d['user_address'] = member try: @@ -103,7 +103,11 @@ def process(mlist, msg, msgdata): else: ufooter = str(footer, lcset, 'ignore') try: - oldpayload = str(msg.get_payload(decode=True), mcset) + oldpayload = msg.get_payload(decode=True) + if isinstance(oldpayload, bytes): + oldpayload = oldpayload.decode(encoding=mcset) + if Utils.needs_unicode_escape_decode(oldpayload): + oldpayload = codecs.decode(oldpayload, 'unicode_escape') frontsep = endsep = u'' if header and not header.endswith('\n'): frontsep = u'\n' @@ -177,7 +181,7 @@ def process(mlist, msg, msgdata): inner.set_default_type(msg.get_default_type()) if not copied: inner['Content-Type'] = inner.get_content_type() - if msg['mime-version'] is None: + if msg['mime-version'] == None: msg['MIME-Version'] = '1.0' # BAW: HACK ALERT. if hasattr(msg, '__version__'): @@ -201,32 +205,10 @@ def process(mlist, msg, msgdata): msg['Content-Type'] = 'multipart/mixed' + def decorate(mlist, template, what, extradict=None): # `what' is just a descriptive phrase used in the log message - # If template is None, return empty string - if template is None: - syslog('error', 'Template is None for %s', what) - return '' - - # If template is a Message object, get its content - if isinstance(template, Message): - try: - template = template.get_payload(decode=True) - if isinstance(template, bytes): - template = template.decode('utf-8', 'replace') - except Exception as e: - syslog('error', 'Error getting payload from Message template for %s: %s', what, str(e)) - return '' - - # Ensure template is a string - if not isinstance(template, str): - try: - template = str(template) - except Exception as e: - syslog('error', 'Error converting template to string for %s: %s', what, str(e)) - return '' - # If template is only whitespace, ignore it. if len(re.sub(r'\s', '', template)) == 0: return '' diff --git a/Mailman/Handlers/Hold.py b/Mailman/Handlers/Hold.py index 764dba96..09bec6b4 100644 --- a/Mailman/Handlers/Hold.py +++ b/Mailman/Handlers/Hold.py @@ -29,17 +29,15 @@ """ import email +from email.parser import Parser from email.mime.text import MIMEText from email.mime.message import MIMEMessage import email.utils -import re -from email.iterators import body_line_iterator -import traceback from Mailman import mm_cfg from Mailman import Utils from Mailman import Errors -from Mailman.Message import Message, UserNotification +from Mailman import Message from Mailman import i18n from Mailman import Pending from Mailman.Logging.Syslog import syslog @@ -50,6 +48,7 @@ def _(s): return s + class ForbiddenPoster(Errors.HoldMessage): reason = _('Sender is explicitly forbidden') rejection = _('You are forbidden from posting messages to this list.') @@ -110,14 +109,13 @@ def rejection_notice(self, mlist): class ModeratedNewsgroup(ModeratedPost): reason = _('Posting to a moderated newsgroup') -class HTMLViewerRequired(Errors.HoldMessage): - reason = _('Message contains HTML viewer required text') - rejection = _('Your message contains text indicating it requires an HTML viewer, which is not allowed.') + # And reset the translator _ = i18n._ + def ackp(msg): ack = msg.get('x-ack', '').lower() precedence = msg.get('precedence', '').lower() @@ -126,223 +124,174 @@ def ackp(msg): return 1 + def process(mlist, msg, msgdata): - try: - if msgdata.get('approved'): - return - # Get the sender of the message - listname = mlist.internal_name() - adminaddr = listname + '-admin' - sender = msg.get_sender() - # Special case an ugly sendmail feature: If there exists an alias of the - # form "owner-foo: bar" and sendmail receives mail for address "foo", - # sendmail will change the envelope sender of the message to "bar" before - # delivering. This feature does not appear to be configurable. *Boggle*. - if not sender or sender[:len(listname)+6] == adminaddr: - sender = msg.get_sender(use_envelope=0) - # - # Check for HTML viewer required text in text/plain parts - for part in msg.walk(): - if part.get_content_type() == 'text/plain': - payload = part.get_payload(decode=True) - if payload: - try: - text = payload.decode('utf-8', errors='replace') - if "An HTML viewer is required to see this message" in text: - hold_for_approval(mlist, msg, msgdata, HTMLViewerRequired) - return - except (UnicodeDecodeError, AttributeError): - # If we can't decode the payload, try as bytes - if isinstance(payload, bytes): - if b"An HTML viewer is required to see this message" in payload: - hold_for_approval(mlist, msg, msgdata, HTMLViewerRequired) - return - # - # Possible administrivia? - if mlist.administrivia and Utils.is_administrivia(msg): - hold_for_approval(mlist, msg, msgdata, Administrivia) + if msgdata.get('approved'): + return + # Get the sender of the message + listname = mlist.internal_name() + adminaddr = listname + '-admin' + sender = msg.get_sender() + # Special case an ugly sendmail feature: If there exists an alias of the + # form "owner-foo: bar" and sendmail receives mail for address "foo", + # sendmail will change the envelope sender of the message to "bar" before + # delivering. This feature does not appear to be configurable. *Boggle*. + if not sender or sender[:len(listname)+6] == adminaddr: + sender = msg.get_sender(use_envelope=0) + # + # Possible administrivia? + if mlist.administrivia and Utils.is_administrivia(msg): + hold_for_approval(mlist, msg, msgdata, Administrivia) + # no return + # + # Are there too many recipients to the message? + if mlist.max_num_recipients > 0: + # figure out how many recipients there are + recips = email.utils.getaddresses(msg.get_all('to', []) + + msg.get_all('cc', [])) + if len(recips) >= mlist.max_num_recipients: + hold_for_approval(mlist, msg, msgdata, TooManyRecipients) # no return - # - # Are there too many recipients to the message? - if mlist.max_num_recipients > 0: - # figure out how many recipients there are - recips = email.utils.getaddresses(msg.get_all('to', []) + - msg.get_all('cc', [])) - if len(recips) >= mlist.max_num_recipients: - hold_for_approval(mlist, msg, msgdata, TooManyRecipients) - # no return - # - # Implicit destination? Note that message originating from the Usenet - # side of the world should never be checked for implicit destination. - if mlist.require_explicit_destination and \ - not mlist.HasExplicitDest(msg) and \ - not msgdata.get('fromusenet'): - # then - hold_for_approval(mlist, msg, msgdata, ImplicitDestination) + # + # Implicit destination? Note that message originating from the Usenet + # side of the world should never be checked for implicit destination. + if mlist.require_explicit_destination and \ + not mlist.HasExplicitDest(msg) and \ + not msgdata.get('fromusenet'): + # then + hold_for_approval(mlist, msg, msgdata, ImplicitDestination) + # no return + # + # Suspicious headers? + if mlist.bounce_matching_headers: + triggered = mlist.hasMatchingHeader(msg) + if triggered: + # TBD: Darn - can't include the matching line for the admin + # message because the info would also go to the sender + hold_for_approval(mlist, msg, msgdata, SuspiciousHeaders) + # no return + # + # Is the message too big? + if mlist.max_message_size > 0: + bodylen = 0 + for line in email.iterators.body_line_iterator(msg): + bodylen += len(line) + for part in msg.walk(): + if part.preamble: + bodylen += len(part.preamble) + if part.epilogue: + bodylen += len(part.epilogue) + if bodylen/1024.0 > mlist.max_message_size: + hold_for_approval(mlist, msg, msgdata, + MessageTooBig(bodylen, mlist.max_message_size)) # no return - # - # Suspicious headers? - if mlist.bounce_matching_headers: - triggered = mlist.hasMatchingHeader(msg) - if triggered: - # TBD: Darn - can't include the matching line for the admin - # message because the info would also go to the sender - hold_for_approval(mlist, msg, msgdata, SuspiciousHeaders) - # no return - # - # Is the message too big? - if mlist.max_message_size > 0: - bodylen = 0 - for line in body_line_iterator(msg): - bodylen += len(line) - for part in msg.walk(): - if part.preamble: - bodylen += len(part.preamble) - if part.epilogue: - bodylen += len(part.epilogue) - if bodylen/1024.0 > mlist.max_message_size: - hold_for_approval(mlist, msg, msgdata, - MessageTooBig(bodylen, mlist.max_message_size)) - # no return - # - # Are we gatewaying to a moderated newsgroup and is this list the - # moderator's address for the group? - if mlist.gateway_to_news and mlist.news_moderation == 2: - hold_for_approval(mlist, msg, msgdata, ModeratedNewsgroup) - except Errors.HoldMessage: - # These are expected conditions, not errors - raise - except Exception as e: - # Only log unexpected errors - syslog('error', 'Error in Hold.process: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - raise + # + # Are we gatewaying to a moderated newsgroup and is this list the + # moderator's address for the group? + if mlist.gateway_to_news and mlist.news_moderation == 2: + hold_for_approval(mlist, msg, msgdata, ModeratedNewsgroup) + def hold_for_approval(mlist, msg, msgdata, exc): - try: - # BAW: This should really be tied into the email confirmation system so - # that the message can be approved or denied via email as well as the - # web. - # - # Check if exc is a class (new-style in Python 3) - if isinstance(exc, type): - exc = exc() - # Get the sender of the message - sender = msg.get_sender() - # Get the list's owner address - owneraddr = mlist.GetOwnerEmail() - # Get the subject - subject = msg.get('subject', _('(no subject)')) - # Get the language to use - lang = mlist.getMemberLanguage(sender) - # Get the text of the message - text = exc.rejection_notice(mlist) - listname = mlist.real_name - sender = msgdata.get('sender', msg.get_sender()) - usersubject = msg.get('subject') - charset = Utils.GetCharSet(mlist.preferred_language) - if usersubject: - usersubject = Utils.oneline(usersubject, charset) - else: - usersubject = _('(no subject)') - message_id = msg.get('message-id', 'n/a') - adminaddr = mlist.GetBouncesEmail() - requestaddr = mlist.GetRequestEmail() - # We need to send both the reason and the rejection notice through the - # translator again, because of the games we play above - reason = Utils.wrap(exc.reason_notice()) - if isinstance(exc, NonMemberPost) and mlist.nonmember_rejection_notice: - msgdata['rejection_notice'] = Utils.wrap( - mlist.nonmember_rejection_notice.replace( - '%(listowner)s', owneraddr)) - else: - msgdata['rejection_notice'] = Utils.wrap(exc.rejection_notice(mlist)) - id = mlist.HoldMessage(msg, reason, msgdata) - # Now we need to craft and send a message to the list admin so they can - # deal with the held message. - d = {'listname' : listname, - 'hostname' : mlist.host_name, - 'reason' : _(reason), - 'sender' : sender, - 'subject' : usersubject, - 'admindb_url': mlist.GetScriptURL('admindb', absolute=1), - } - # Ensure the list is locked before calling pend_new - if not mlist.Locked(): - mlist.Lock() - try: - cookie = mlist.pend_new(Pending.HELD_MESSAGE, id) - finally: - mlist.Unlock() - else: - cookie = mlist.pend_new(Pending.HELD_MESSAGE, id) - # We may want to send a notification to the original sender too - fromusenet = msgdata.get('fromusenet') - # Since we're sending two messages, which may potentially be in different - # languages (the user's preferred and the list's preferred for the admin), - # we need to play some i18n games here. Since the current language - # context ought to be set up for the user, let's craft his message first. - if not fromusenet and ackp(msg) and mlist.respond_to_post_requests and \ - mlist.autorespondToSender(sender, mlist.getMemberLanguage(sender)): - # Get a confirmation cookie - d['confirmurl'] = '%s/%s' % (mlist.GetScriptURL('confirm', absolute=1), - cookie) - lang = msgdata.get('lang', mlist.getMemberLanguage(sender)) - subject = _('Your message to %(listname)s awaits moderator approval') % {'listname': listname} - text = Utils.maketext('postheld.txt', d, lang=lang, mlist=mlist) - nmsg = UserNotification(sender, owneraddr, subject, text, lang) - nmsg.send(mlist) - # Now the message for the list owners. Be sure to include the list - # moderators in this message. This one should appear to come from - # -owner since we really don't need to do bounce processing on it. - if mlist.admin_immed_notify: - # Now let's temporarily set the language context to that which the - # admin is expecting. - otranslation = i18n.get_translation() - i18n.set_language(mlist.preferred_language) - try: - lang = mlist.preferred_language - charset = Utils.GetCharSet(lang) - # We need to regenerate or re-translate a few values in d - d['reason'] = _(reason) - d['subject'] = usersubject - # craft the admin notification message and deliver it - subject = _('%(listname)s post from %(sender)s requires approval') - nmsg = UserNotification(owneraddr, owneraddr, subject, - lang=lang) - nmsg.set_type('multipart/mixed') - text = MIMEText( - Utils.maketext('postauth.txt', d, raw=1, mlist=mlist), - _charset=charset) - dmsg = MIMEText(Utils.wrap(_(""" + # BAW: This should really be tied into the email confirmation system so + # that the message can be approved or denied via email as well as the + # web. + # + if isinstance(exc, type): + # Go ahead and instantiate it now. + exc = exc() + listname = mlist.real_name + sender = msgdata.get('sender', msg.get_sender()) + usersubject = msg.get('subject') + charset = Utils.GetCharSet(mlist.preferred_language) + if usersubject: + usersubject = Utils.oneline(usersubject, charset) + else: + usersubject = _('(no subject)') + message_id = msg.get('message-id', 'n/a') + owneraddr = mlist.GetOwnerEmail() + adminaddr = mlist.GetBouncesEmail() + requestaddr = mlist.GetRequestEmail() + # We need to send both the reason and the rejection notice through the + # translator again, because of the games we play above + reason = Utils.wrap(exc.reason_notice()) + if isinstance(exc, NonMemberPost) and mlist.nonmember_rejection_notice: + msgdata['rejection_notice'] = Utils.wrap( + mlist.nonmember_rejection_notice.replace( + '%(listowner)s', owneraddr)) + else: + msgdata['rejection_notice'] = Utils.wrap(exc.rejection_notice(mlist)) + id = mlist.HoldMessage(msg, reason, msgdata) + # Now we need to craft and send a message to the list admin so they can + # deal with the held message. + d = {'listname' : listname, + 'hostname' : mlist.host_name, + 'reason' : _(reason), + 'sender' : sender, + 'subject' : usersubject, + 'admindb_url': mlist.GetScriptURL('admindb', absolute=1), + } + # We may want to send a notification to the original sender too + fromusenet = msgdata.get('fromusenet') + # Since we're sending two messages, which may potentially be in different + # languages (the user's preferred and the list's preferred for the admin), + # we need to play some i18n games here. Since the current language + # context ought to be set up for the user, let's craft his message first. + cookie = mlist.pend_new(Pending.HELD_MESSAGE, id) + if not fromusenet and ackp(msg) and mlist.respond_to_post_requests and \ + mlist.autorespondToSender(sender, mlist.getMemberLanguage(sender)): + # Get a confirmation cookie + d['confirmurl'] = '%s/%s' % (mlist.GetScriptURL('confirm', absolute=1), + cookie) + lang = msgdata.get('lang', mlist.getMemberLanguage(sender)) + subject = _('Your message to %(listname)s awaits moderator approval') + text = Utils.maketext('postheld.txt', d, lang=lang, mlist=mlist) + nmsg = Message.UserNotification(sender, owneraddr, subject, text, lang) + nmsg.send(mlist) + # Now the message for the list owners. Be sure to include the list + # moderators in this message. This one should appear to come from + # -owner since we really don't need to do bounce processing on it. + if mlist.admin_immed_notify: + # Now let's temporarily set the language context to that which the + # admin is expecting. + otranslation = i18n.get_translation() + i18n.set_language(mlist.preferred_language) + try: + lang = mlist.preferred_language + charset = Utils.GetCharSet(lang) + # We need to regenerate or re-translate a few values in d + d['reason'] = _(reason) + d['subject'] = usersubject + # craft the admin notification message and deliver it + subject = _('%(listname)s post from %(sender)s requires approval') + nmsg = Message.UserNotification(owneraddr, owneraddr, subject, + lang=lang) + nmsg.set_type('multipart/mixed') + text = MIMEText( + Utils.maketext('postauth.txt', d, raw=1, mlist=mlist), + _charset=charset) + dmsg = MIMEText(Utils.wrap(_("""\ If you reply to this message, keeping the Subject: header intact, Mailman will discard the held message. Do this if the message is spam. If you reply to this message and include an Approved: header with the list password in it, the message will be approved for posting to the list. The Approved: header can also appear in the first line of the body of the reply.""")), - _charset=Utils.GetCharSet(lang)) - dmsg['Subject'] = 'confirm ' + cookie - dmsg['Sender'] = requestaddr - dmsg['From'] = requestaddr - dmsg['Date'] = email.utils.formatdate(localtime=True) - dmsg['Message-ID'] = Utils.unique_message_id(mlist) - nmsg.attach(text) - nmsg.attach(MIMEMessage(msg)) - nmsg.attach(MIMEMessage(dmsg)) - nmsg.send(mlist, **{'tomoderators': 1}) - finally: - i18n.set_translation(otranslation) - # Log the held message (info level, not error) - syslog('info', '[HOLD] %s post from %s held for approval, message-id=%s, reason=%s', - listname, sender, message_id, reason) - # raise the specific MessageHeld exception to exit out of the message - # delivery pipeline - raise exc - except Errors.HoldMessage: - # Already handled above, do not log traceback - raise - except Exception as e: - syslog('error', 'Error in Hold.hold_for_approval: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - raise + _charset=Utils.GetCharSet(lang)) + dmsg['Subject'] = 'confirm ' + cookie + dmsg['Sender'] = requestaddr + dmsg['From'] = requestaddr + dmsg['Date'] = email.utils.formatdate(localtime=True) + dmsg['Message-ID'] = Utils.unique_message_id(mlist) + nmsg.attach(text) + nmsg.attach(MIMEMessage(msg)) + nmsg.attach(MIMEMessage(dmsg)) + nmsg.send(mlist, **{'tomoderators': 1}) + finally: + i18n.set_translation(otranslation) + # Log the held message + syslog('vette', '%s post from %s held, message-id=%s: %s', + listname, sender, message_id, reason) + # raise the specific MessageHeld exception to exit out of the message + # delivery pipeline + raise exc diff --git a/Mailman/Handlers/MimeDel.py b/Mailman/Handlers/MimeDel.py index 290ea363..f583368f 100644 --- a/Mailman/Handlers/MimeDel.py +++ b/Mailman/Handlers/MimeDel.py @@ -27,6 +27,7 @@ import os import errno import tempfile +import html2text from os.path import splitext from email.iterators import typed_subpart_iterator @@ -35,17 +36,12 @@ from Mailman import Errors from Mailman.Message import UserNotification from Mailman.Queue.sbcache import get_switchboard -from Mailman.Logging.Syslog import syslog from Mailman.Version import VERSION from Mailman.i18n import _ from Mailman.Utils import oneline -# Lazy import to avoid circular dependency -def get_switchboard(qdir): - from Mailman.Queue.sbcache import get_switchboard - return get_switchboard(qdir) - + def process(mlist, msg, msgdata): # Short-circuits if not mlist.filter_content: @@ -123,6 +119,7 @@ def process(mlist, msg, msgdata): msg['X-Content-Filtered-By'] = 'Mailman/MimeDel %s' % VERSION + def reset_payload(msg, subpart): # Reset payload of msg to contents of subpart, and fix up content headers payload = subpart.get_payload() @@ -143,6 +140,7 @@ def reset_payload(msg, subpart): msg['Content-Description'] = cdesc + def filter_parts(msg, filtertypes, passtypes, filterexts, passexts): # Look at all the message's subparts, and recursively filter if not msg.is_multipart(): @@ -180,6 +178,7 @@ def filter_parts(msg, filtertypes, passtypes, filterexts, passexts): return 1 + def collapse_multipart_alternatives(msg): if not msg.is_multipart(): return @@ -206,6 +205,7 @@ def collapse_multipart_alternatives(msg): msg.set_payload(newpayload) + def recast_multipart(msg): # If we're left with a multipart message with only one sub-part, recast # the message to just the sub-part, but not if the part is message/rfc822 @@ -227,33 +227,34 @@ def recast_multipart(msg): recast_multipart(part) + def to_plaintext(msg): changedp = 0 - for subpart in typed_subpart_iterator(msg, 'text', 'html'): - filename = tempfile.mktemp('.html') - fp = open(filename, 'w') - try: - fp.write(subpart.get_payload(decode=1)) - fp.close() - cmd = os.popen(mm_cfg.HTML_TO_PLAIN_TEXT_COMMAND % - {'filename': filename}) - plaintext = cmd.read() - rtn = cmd.close() - if rtn: - syslog('error', 'HTML->text/plain error: %s', rtn) - finally: - try: - os.unlink(filename) - except OSError as e: - if e.errno != errno.ENOENT: raise + # Get the subparts (ensure you're iterating through them) + subparts = list(typed_subpart_iterator(msg, 'text', 'html')) + + # Iterate through the subparts + for subpart in subparts: + + # Get the HTML content (ensure it's decoded if it's in bytes) + html_content = subpart.get_payload(decode=1) # Get the payload as bytes + + if isinstance(html_content, bytes): + html_content = html_content.decode('utf-8') # Decode bytes to string + + # Now convert HTML to plain text + plaintext = html2text.html2text(html_content) + # Now replace the payload of the subpart and twiddle the Content-Type: - del subpart['content-transfer-encoding'] - subpart.set_payload(plaintext) - subpart.set_type('text/plain') + del subpart['content-transfer-encoding'] # Remove encoding if necessary + subpart.set_payload(plaintext) # Set the new plaintext payload + subpart.set_type('text/plain') # Change the content type to 'text/plain' changedp = 1 + return changedp + def dispose(mlist, msg, msgdata, why): # filter_action == 0 just discards, see below if mlist.filter_action == 1: diff --git a/Mailman/Handlers/Moderate.py b/Mailman/Handlers/Moderate.py index c3c1ae3f..422859f8 100644 --- a/Mailman/Handlers/Moderate.py +++ b/Mailman/Handlers/Moderate.py @@ -23,26 +23,18 @@ from email.mime.text import MIMEText from email.utils import parseaddr -import Mailman from Mailman import mm_cfg from Mailman import Utils +from Mailman import Message from Mailman import Errors -from Mailman import i18n from Mailman.i18n import _ -from Mailman.Message import Message +from Mailman.Handlers import Hold from Mailman.Logging.Syslog import syslog -from Mailman.Logging.Syslog import mailman_log +from Mailman.MailList import MailList -# Lazy imports to avoid circular dependencies -def get_hold(): - import Mailman.Handlers.Hold as Hold - return Hold -def get_mail_list(): - from Mailman.MailList import MailList - return MailList.MailList - -class ModeratedMemberPost(get_hold().ModeratedPost): + +class ModeratedMemberPost(Hold.ModeratedPost): # BAW: I wanted to use the reason below to differentiate between this # situation and normal ModeratedPost reasons. Greg Ward and Stonewall # Ballard thought the language was too harsh and mentioned offense taken @@ -53,14 +45,13 @@ class ModeratedMemberPost(get_hold().ModeratedPost): # reason = _('Posts by member are currently quarantined for moderation') pass + + def process(mlist, msg, msgdata): - """Process a message for moderation.""" if msgdata.get('approved'): return # Is the poster a member or not? - for sender_tuple in msg.get_senders(): - # Extract email address from the (realname, address) tuple - _, sender = sender_tuple + for sender in msg.get_senders(): if mlist.isMember(sender): break for sender in Utils.check_eq_domains(sender, @@ -77,16 +68,13 @@ def process(mlist, msg, msgdata): if mlist.getMemberOption(sender, mm_cfg.Moderate): # Note that for member_moderation_action, 0==Hold, 1=Reject, # 2==Discard - member_moderation_action = mlist.member_moderation_action - if member_moderation_action not in (mm_cfg.DEFER, mm_cfg.APPROVE, mm_cfg.REJECT, mm_cfg.DISCARD, mm_cfg.HOLD): - raise ValueError(f'Invalid member_moderation_action: {member_moderation_action}') - if member_moderation_action == 0: + if mlist.member_moderation_action == 0: # Hold. BAW: WIBNI we could add the member_moderation_notice # to the notice sent back to the sender? msgdata['sender'] = sender - get_hold().hold_for_approval(mlist, msg, msgdata, + Hold.hold_for_approval(mlist, msg, msgdata, ModeratedMemberPost) - elif member_moderation_action == 1: + elif mlist.member_moderation_action == 1: # Reject text = mlist.member_moderation_notice if text: @@ -95,7 +83,7 @@ def process(mlist, msg, msgdata): # Use the default RejectMessage notice string text = None raise Errors.RejectMessage(text) - elif member_moderation_action == 2: + elif mlist.member_moderation_action == 2: # Discard. BAW: Again, it would be nice if we could send a # discard notice to the sender raise Errors.DiscardMessage @@ -118,7 +106,7 @@ def process(mlist, msg, msgdata): mlist.hold_these_nonmembers, at_list='hold_these_nonmembers' ): - get_hold().hold_for_approval(mlist, msg, msgdata, get_hold().NonMemberPost) + Hold.hold_for_approval(mlist, msg, msgdata, Hold.NonMemberPost) # No return if mlist.GetPattern(sender, mlist.reject_these_nonmembers, @@ -135,21 +123,20 @@ def process(mlist, msg, msgdata): # Okay, so the sender wasn't specified explicitly by any of the non-member # moderation configuration variables. Handle by way of generic non-member # action. - generic_nonmember_action = mlist.generic_nonmember_action - if not (0 <= generic_nonmember_action <= 4): - raise ValueError(f'Invalid generic_nonmember_action: {generic_nonmember_action}, must be between 0 and 4') - if generic_nonmember_action == 0 or msgdata.get('fromusenet'): + assert 0 <= mlist.generic_nonmember_action <= 4 + if mlist.generic_nonmember_action == 0 or msgdata.get('fromusenet'): # Accept return - elif generic_nonmember_action == 1: - get_hold().hold_for_approval(mlist, msg, msgdata, get_hold().NonMemberPost) - elif generic_nonmember_action == 2: + elif mlist.generic_nonmember_action == 1: + Hold.hold_for_approval(mlist, msg, msgdata, Hold.NonMemberPost) + elif mlist.generic_nonmember_action == 2: do_reject(mlist) - elif generic_nonmember_action == 3: + elif mlist.generic_nonmember_action == 3: do_discard(mlist, msg) + + def do_reject(mlist): - """Handle message rejection.""" listowner = mlist.GetOwnerEmail() if mlist.nonmember_rejection_notice: raise Errors.RejectMessage(Utils.wrap(_(mlist.nonmember_rejection_notice))) @@ -160,15 +147,16 @@ def do_reject(mlist): it. If you think that your messages are being rejected in error, contact the mailing list owner at %(listowner)s."""))) + + def do_discard(mlist, msg): - """Handle message discarding.""" sender = msg.get_sender() # Do we forward auto-discards to the list owners? if mlist.forward_auto_discards: lang = mlist.preferred_language varhelp = '%s/?VARHELP=privacy/sender/discard_these_nonmembers' % \ mlist.GetScriptURL('admin', absolute=1) - nmsg = Mailman.Message.UserNotification(mlist.GetOwnerEmail(), + nmsg = Message.UserNotification(mlist.GetOwnerEmail(), mlist.GetBouncesEmail(), _('Auto-discard notification'), lang=lang) diff --git a/Mailman/Handlers/Replybot.py b/Mailman/Handlers/Replybot.py index a6f513e7..60bc4df3 100644 --- a/Mailman/Handlers/Replybot.py +++ b/Mailman/Handlers/Replybot.py @@ -19,61 +19,103 @@ import time -from Mailman import mm_cfg from Mailman import Utils -from Mailman import Errors -from Mailman import i18n -from Mailman.Message import UserNotification -from Mailman.Logging.Syslog import syslog +from Mailman import Message from Mailman.i18n import _ from Mailman.SafeDict import SafeDict +from Mailman.Logging.Syslog import syslog -# Set up i18n -_ = i18n._ -i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + def process(mlist, msg, msgdata): - """Process a message through the replybot handler. - - Args: - mlist: The MailList object - msg: The message to process - msgdata: Additional message metadata - - Returns: - bool: True if message should be discarded, False otherwise - """ - # Get the sender + # Normally, the replybot should get a shot at this message, but there are + # some important short-circuits, mostly to suppress 'bot storms, at least + # for well behaved email bots (there are other governors for misbehaving + # 'bots). First, if the original message has an "X-Ack: No" header, we + # skip the replybot. Then, if the message has a Precedence header with + # values bulk, junk, or list, and there's no explicit "X-Ack: yes" header, + # we short-circuit. Finally, if the message metadata has a true 'noack' + # key, then we skip the replybot too. + ack = msg.get('x-ack', '').lower() + if ack == 'no' or msgdata.get('noack'): + return + precedence = msg.get('precedence', '').lower() + if ack != 'yes' and precedence in ('bulk', 'junk', 'list'): + return + # Check to see if the list is even configured to autorespond to this email + # message. Note: the owner script sets the `toowner' key, and the various + # confirm, join, leave, request, subscribe and unsubscribe scripts set the + # keys we use for `torequest'. + toadmin = msgdata.get('toowner') + torequest = msgdata.get('torequest') or msgdata.get('toconfirm') or \ + msgdata.get('tojoin') or msgdata.get('toleave') + if ((toadmin and not mlist.autorespond_admin) or + (torequest and not mlist.autorespond_requests) or \ + (not toadmin and not torequest and not mlist.autorespond_postings)): + return + # Now see if we're in the grace period for this sender. graceperiod <= 0 + # means always autorespond, as does an "X-Ack: yes" header (useful for + # debugging). sender = msg.get_sender() - if not sender: - return False - - # Check if we should autorespond - if not mlist.autorespondToSender(sender, msgdata.get('lang', mlist.preferred_language)): - return False - - # Create the response message - outmsg = UserNotification(sender, mlist.GetBouncesEmail(), - _('Automatic response from %(listname)s') % {'listname': mlist.real_name}, - lang=msgdata.get('lang', mlist.preferred_language)) - - # Set the message content - outmsg.set_type('text/plain') - outmsg.set_payload(_("""\ -This message is an automatic response from %(listname)s. - -Your message has been received and will be processed by the list -administrators. Please do not send this message again. - -If you have any questions, please contact the list administrator at -%(adminaddr)s. - -Thank you for your interest in the %(listname)s mailing list. -""") % {'listname': mlist.real_name, - 'adminaddr': mlist.GetOwnerEmail()}) - - # Send the response - outmsg.send(mlist, msgdata=msgdata) - - # Return True to indicate the original message should be discarded - return True + now = time.time() + graceperiod = mlist.autoresponse_graceperiod + if graceperiod > 0 and ack != 'yes': + if toadmin: + quiet_until = mlist.admin_responses.get(sender, 0) + elif torequest: + quiet_until = mlist.request_responses.get(sender, 0) + else: + quiet_until = mlist.postings_responses.get(sender, 0) + if quiet_until > now: + return + # + # Okay, we know we're going to auto-respond to this sender, craft the + # message, send it, and update the database. + realname = mlist.real_name + subject = _( + 'Auto-response for your message to the "%(realname)s" mailing list') + # Do string interpolation + d = SafeDict({'listname' : realname, + 'listurl' : mlist.GetScriptURL('listinfo'), + 'requestemail': mlist.GetRequestEmail(), + # BAW: Deprecate adminemail; it's not advertised but still + # supported for backwards compatibility. + 'adminemail' : mlist.GetBouncesEmail(), + 'owneremail' : mlist.GetOwnerEmail(), + }) + # Just because we're using a SafeDict doesn't mean we can't get all sorts + # of other exceptions from the string interpolation. Let's be ultra + # conservative here. + if toadmin: + rtext = mlist.autoresponse_admin_text + elif torequest: + rtext = mlist.autoresponse_request_text + else: + rtext = mlist.autoresponse_postings_text + # Using $-strings? + if getattr(mlist, 'use_dollar_strings', 0): + rtext = Utils.to_percent(rtext) + try: + text = rtext % d + except Exception: + syslog('error', 'Bad autoreply text for list: %s\n%s', + mlist.internal_name(), rtext) + text = rtext + # Wrap the response. + text = Utils.wrap(text) + outmsg = Message.UserNotification(sender, mlist.GetBouncesEmail(), + subject, text, mlist.preferred_language) + outmsg['X-Mailer'] = _('The Mailman Replybot') + # prevent recursions and mail loops! + outmsg['X-Ack'] = 'No' + outmsg.send(mlist) + # update the grace period database + if graceperiod > 0: + # graceperiod is in days, we need # of seconds + quiet_until = now + graceperiod * 24 * 60 * 60 + if toadmin: + mlist.admin_responses[sender] = quiet_until + elif torequest: + mlist.request_responses[sender] = quiet_until + else: + mlist.postings_responses[sender] = quiet_until diff --git a/Mailman/Handlers/SMTPDirect.py b/Mailman/Handlers/SMTPDirect.py index 0707694b..aa98357e 100644 --- a/Mailman/Handlers/SMTPDirect.py +++ b/Mailman/Handlers/SMTPDirect.py @@ -33,21 +33,13 @@ import smtplib from smtplib import SMTPException from base64 import b64encode -import traceback -import os -import errno -import pickle -import email.message -from email.message import Message from Mailman import mm_cfg -import Mailman.Utils -import Mailman.Errors -from Mailman.Message import Message -from Mailman.Handlers.Decorate import decorate -from Mailman.Logging.Syslog import mailman_log -import Mailman.SafeDict -from Mailman.Queue.sbcache import get_switchboard +from Mailman import Utils +from Mailman import Errors +from Mailman.Handlers import Decorate +from Mailman.Logging.Syslog import syslog +from Mailman.SafeDict import MsgSafeDict import email from email.utils import formataddr @@ -56,60 +48,65 @@ DOT = '.' + # Manage a connection to the SMTP server class Connection(object): def __init__(self): self.__conn = None def __connect(self): - try: - self.__conn = smtplib.SMTP() - self.__conn.set_debuglevel(mm_cfg.SMTPLIB_DEBUG_LEVEL) - # Ensure we have a valid hostname for TLS - helo_host = mm_cfg.SMTP_HELO_HOST - if not helo_host or helo_host.startswith('.'): - helo_host = mm_cfg.SMTPHOST - if not helo_host or helo_host.startswith('.'): - # If we still don't have a valid hostname, use localhost - helo_host = 'localhost' - mailman_log('smtp', 'Connecting to SMTP server %s:%s with HELO %s', - mm_cfg.SMTPHOST, mm_cfg.SMTPPORT, helo_host) - self.__conn.connect(mm_cfg.SMTPHOST, mm_cfg.SMTPPORT) - # Set the hostname for TLS - self.__conn._host = helo_host - if mm_cfg.SMTP_AUTH: - if mm_cfg.SMTP_USE_TLS: - mailman_log('smtp', 'Using TLS with hostname: %s', helo_host) - try: - # Use native TLS support - self.__conn.starttls() - except SMTPException as e: - mailman_log('smtp-failure', 'SMTP TLS error: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - self.quit() - raise + self.__conn = smtplib.SMTP() + self.__conn.set_debuglevel(mm_cfg.SMTPLIB_DEBUG_LEVEL) + + # Ensure we have a valid hostname for the connection + smtp_host = mm_cfg.SMTPHOST + if not smtp_host or smtp_host.startswith('.') or smtp_host == '@URLHOST@': + smtp_host = 'localhost' + + # Log the hostname being used for debugging + syslog('smtp-failure', 'SMTP connection hostname: %s (original: %s)', + smtp_host, mm_cfg.SMTPHOST) + + self.__conn.connect(smtp_host, mm_cfg.SMTPPORT) + if mm_cfg.SMTP_AUTH: + if mm_cfg.SMTP_USE_TLS: + # Log the hostname being used for TLS + syslog('smtp-failure', 'TLS connection hostname: %s', self.__conn._host) try: - self.__conn.login(mm_cfg.SMTP_USER, mm_cfg.SMTP_PASSWD) - except smtplib.SMTPHeloError as e: - mailman_log('smtp-failure', 'SMTP HELO error: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) + # Ensure the hostname is set for TLS + if not self.__conn._host: + self.__conn._host = smtp_host + syslog('smtp-failure', 'Set TLS hostname to: %s', smtp_host) + self.__conn.starttls() + except SMTPException as e: + syslog('smtp-failure', 'SMTP TLS error: %s', e) self.quit() raise - except smtplib.SMTPAuthenticationError as e: - mailman_log('smtp-failure', 'SMTP AUTH error: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - self.quit() - except smtplib.SMTPException as e: - mailman_log('smtp-failure', - 'SMTP - no suitable authentication method found: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) + try: + # Use a valid hostname for EHLO, fallback to localhost if SMTP_HELO_HOST is empty or invalid + helo_host = mm_cfg.SMTP_HELO_HOST + if not helo_host or helo_host.startswith('.') or helo_host == '@URLHOST@': + helo_host = 'localhost' + self.__conn.ehlo(helo_host) + except SMTPException as e: + syslog('smtp-failure', 'SMTP EHLO error: %s', e) self.quit() raise - except (socket.error, smtplib.SMTPException) as e: - mailman_log('smtp-failure', 'SMTP connection error: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - self.quit() - raise + try: + self.__conn.login(mm_cfg.SMTP_USER, mm_cfg.SMTP_PASSWD) + except smtplib.SMTPHeloError as e: + syslog('smtp-failure', 'SMTP HELO error: %s', e) + self.quit() + raise + except smtplib.SMTPAuthenticationError as e: + syslog('smtp-failure', 'SMTP AUTH error: %s', e) + self.quit() + raise + except smtplib.SMTPException as e: + syslog('smtp-failure', + 'SMTP - no suitable authentication method found: %s', e) + self.quit() + raise self.__numsessions = mm_cfg.SMTP_MAX_SESSIONS_PER_CONNECTION @@ -117,24 +114,12 @@ def sendmail(self, envsender, recips, msgtext): if self.__conn is None: self.__connect() try: - # Convert message to string if it's a Message object - if isinstance(msgtext, Message): - msgtext = msgtext.as_string() - # Ensure msgtext is properly encoded as UTF-8 - if isinstance(msgtext, str): - msgtext = msgtext.encode('utf-8') - # Convert recips to list if it's not already - if not isinstance(recips, list): - recips = [recips] - # Ensure envsender is a string - if isinstance(envsender, bytes): - envsender = envsender.decode('utf-8') + if isinstance( msgtext, str ): + msgtext = msgtext.encode('utf-8', errors='ignore') results = self.__conn.sendmail(envsender, recips, msgtext) - except smtplib.SMTPException as e: + except smtplib.SMTPException: # For safety, close this connection. The next send attempt will # automatically re-open it. Pass the exception on up. - mailman_log('smtp-failure', 'SMTP sendmail error: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) self.quit() raise # This session has been successfully completed. @@ -156,116 +141,146 @@ def quit(self): self.__conn = None + def process(mlist, msg, msgdata): - """Process the message for delivery. - - This is the main entry point for the SMTPDirect handler. - """ - t0 = time.time() - refused = {} - envsender = msgdata.get('envsender', msg.get_sender()) - if envsender is None: - envsender = mlist.GetBouncesEmail() - - # Get the list of recipients with better validation - recips = msgdata.get('recips', []) + recips = msgdata.get('recips') if not recips: - # Try to get from message headers as fallback - recips = msg.get_all('to', []) + msg.get_all('cc', []) - if not recips: - # Get message details for logging - msgid = msg.get('message-id', 'unknown') - sender = msg.get('from', 'unknown') - subject = msg.get('subject', 'no subject') - to = msg.get('to', 'no to') - cc = msg.get('cc', 'no cc') - - mailman_log('error', - 'No recipients found in msgdata for message:\n' - ' Message-ID: %s\n' - ' From: %s\n' - ' Subject: %s\n' - ' To: %s\n' - ' Cc: %s\n' - ' List: %s\n' - ' Pipeline: %s\n' - ' Full msgdata: %s', - msgid, sender, subject, to, cc, mlist.internal_name(), - msgdata.get('pipeline', 'No pipeline'), - str(msgdata)) - return - - # Check for spam headers first - if msg.get('x-google-group-id'): - mailman_log('error', 'Silently dropping message with X-Google-Group-Id header: %s', - msg.get('message-id', 'unknown')) - # Add all recipients to refused list with 550 error - for r in recips: - refused[r] = (550, 'Message rejected due to spam detection') - # Update failures dict - msgdata['failures'] = refused - # Silently return without raising an exception + # Nobody to deliver to! return - - # Chunkify the recipients - chunks = chunkify(recips, mm_cfg.SMTP_MAX_RCPTS) - # Choose the delivery function based on VERP settings - if msgdata.get('verp'): + # Calculate the non-VERP envelope sender. + envsender = msgdata.get('envsender') + if envsender is None: + if mlist: + envsender = mlist.GetBouncesEmail() + else: + envsender = Utils.get_site_email(extra='bounces') + # Time to split up the recipient list. If we're personalizing or VERPing + # then each chunk will have exactly one recipient. We'll then hand craft + # an envelope sender and stitch a message together in memory for each one + # separately. If we're not VERPing, then we'll chunkify based on + # SMTP_MAX_RCPTS. Note that most MTAs have a limit on the number of + # recipients they'll swallow in a single transaction. + deliveryfunc = None + if ('personalize' not in msgdata or msgdata['personalize']) and ( + msgdata.get('verp') or mlist.personalize): + chunks = [[recip] for recip in recips] + msgdata['personalize'] = 1 deliveryfunc = verpdeliver + elif mm_cfg.SMTP_MAX_RCPTS <= 0: + chunks = [recips] else: + chunks = chunkify(recips, mm_cfg.SMTP_MAX_RCPTS) + # See if this is an unshunted message for which some were undelivered + if 'undelivered' in msgdata: + chunks = msgdata['undelivered'] + # If we're doing bulk delivery, then we can stitch up the message now. + if deliveryfunc is None: + # Be sure never to decorate the message more than once! + if not msgdata.get('decorated'): + Decorate.process(mlist, msg, msgdata) + msgdata['decorated'] = True deliveryfunc = bulkdeliver - + refused = {} + t0 = time.time() + # Open the initial connection + origrecips = msgdata['recips'] + # MAS: get the message sender now for logging. If we're using 'sender' + # and not 'from', bulkdeliver changes it for bounce processing. If we're + # VERPing, it doesn't matter because bulkdeliver is working on a copy, but + # otherwise msg gets changed. If the list is anonymous, the original + # sender is long gone, but Cleanse.py has logged it. + origsender = msgdata.get('original_sender', msg.get_sender()) + # `undelivered' is a copy of chunks that we pop from to do deliveries. + # This seems like a good tradeoff between robustness and resource + # utilization. If delivery really fails (i.e. qfiles/shunt type + # failures), then we'll pick up where we left off with `undelivered'. + # This means at worst, the last chunk for which delivery was attempted + # could get duplicates but not every one, and no recips should miss the + # message. + conn = Connection() try: - origrecips = msgdata.get('recips', None) - origsender = msgdata.get('original_sender', msg.get_sender()) - conn = Connection() - try: - msgdata['undelivered'] = chunks - while chunks: - chunk = chunks.pop() - msgdata['recips'] = chunk - try: - deliveryfunc(mlist, msg, msgdata, envsender, refused, conn) - except Mailman.Errors.RejectMessage as e: - # Handle message rejection gracefully - mailman_log('error', 'Message rejected: %s', str(e)) - # Add all recipients in this chunk to refused list - for r in chunk: - refused[r] = (550, str(e)) - continue - except Exception as e: - mailman_log('error', - 'Delivery error for chunk: %s\nError: %s\n%s', - chunk, str(e), traceback.format_exc()) - chunks.append(chunk) - raise - del msgdata['undelivered'] - finally: - conn.quit() - msgdata['recips'] = origrecips - - # Log the successful post - t1 = time.time() - listname = mlist.internal_name() - if isinstance(listname, bytes): - listname = listname.decode('latin-1') - d = Mailman.SafeDict.MsgSafeDict(msg, {'time' : t1-t0, + msgdata['undelivered'] = chunks + while chunks: + chunk = chunks.pop() + msgdata['recips'] = chunk + try: + deliveryfunc(mlist, msg, msgdata, envsender, refused, conn) + except Exception: + # If /anything/ goes wrong, push the last chunk back on the + # undelivered list and re-raise the exception. We don't know + # how many of the last chunk might receive the message, so at + # worst, everyone in this chunk will get a duplicate. Sigh. + chunks.append(chunk) + raise + del msgdata['undelivered'] + finally: + conn.quit() + msgdata['recips'] = origrecips + # Log the successful post + t1 = time.time() + d = MsgSafeDict(msg, {'time' : t1-t0, + # BAW: Urg. This seems inefficient. 'size' : len(msg.as_string()), '#recips' : len(recips), '#refused': len(refused), - 'listname': listname, + 'listname': mlist.internal_name(), 'sender' : origsender, }) - if mm_cfg.SMTP_LOG_EVERY_MESSAGE: - mailman_log(mm_cfg.SMTP_LOG_EVERY_MESSAGE[0], - mm_cfg.SMTP_LOG_EVERY_MESSAGE[1] % d.copy()) + # We have to use the copy() method because extended call syntax requires a + # concrete dictionary object; it does not allow a generic mapping. It's + # still worthwhile doing the interpolation in syslog() because it'll catch + # any catastrophic exceptions due to bogus format strings. + if mm_cfg.SMTP_LOG_EVERY_MESSAGE: + syslog.write_ex(mm_cfg.SMTP_LOG_EVERY_MESSAGE[0], + mm_cfg.SMTP_LOG_EVERY_MESSAGE[1], kws=d) + + if refused: + if mm_cfg.SMTP_LOG_REFUSED: + syslog.write_ex(mm_cfg.SMTP_LOG_REFUSED[0], + mm_cfg.SMTP_LOG_REFUSED[1], kws=d) - except Exception as e: - mailman_log('error', 'Error in SMTPDirect.process: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - raise + elif msgdata.get('tolist'): + # Log the successful post, but only if it really was a post to the + # mailing list. Don't log sends to the -owner, or -admin addrs. + # -request addrs should never get here. BAW: it may be useful to log + # the other messages, but in that case, we should probably have a + # separate configuration variable to control that. + if mm_cfg.SMTP_LOG_SUCCESS: + syslog.write_ex(mm_cfg.SMTP_LOG_SUCCESS[0], + mm_cfg.SMTP_LOG_SUCCESS[1], kws=d) + # Process any failed deliveries. + tempfailures = [] + permfailures = [] + for recip, (code, smtpmsg) in list(refused.items()): + # DRUMS is an internet draft, but it says: + # + # [RFC-821] incorrectly listed the error where an SMTP server + # exhausts its implementation limit on the number of RCPT commands + # ("too many recipients") as having reply code 552. The correct + # reply code for this condition is 452. Clients SHOULD treat a 552 + # code in this case as a temporary, rather than permanent failure + # so the logic below works. + # + if code >= 500 and code != 552: + # A permanent failure + permfailures.append(recip) + else: + # Deal with persistent transient failures by queuing them up for + # future delivery. TBD: this could generate lots of log entries! + tempfailures.append(recip) + if mm_cfg.SMTP_LOG_EACH_FAILURE: + d.update({'recipient': recip, + 'failcode' : code, + 'failmsg' : smtpmsg}) + syslog.write_ex(mm_cfg.SMTP_LOG_EACH_FAILURE[0], + mm_cfg.SMTP_LOG_EACH_FAILURE[1], kws=d) + # Return the results + if tempfailures or permfailures: + raise Errors.SomeRecipientsFailed(tempfailures, permfailures) + + def chunkify(recips, chunksize): # First do a simple sort on top level domain. It probably doesn't buy us # much to try to sort on MX record -- that's the MTA's job. We're just @@ -278,9 +293,9 @@ def chunkify(recips, chunksize): 'edu': 4, 'us' : 5, 'ca' : 6, - 'uk' : 7, - 'jp' : 8, - 'au' : 9, + 'uk' : 7, + 'jp' : 8, + 'au' : 9, } # Need to sort by domain name. if we split to chunks it is possible # some well-known domains will be interspersed as we sort by @@ -291,7 +306,6 @@ def chunkify(recips, chunksize): i = r.rfind('.') if i >= 0: tld = r[i+1:] - # Use get() with default value of 0 for unknown TLDs bin = chunkmap.get(tld, 0) bucket = buckets.get(bin, []) bucket.append(r) @@ -315,170 +329,144 @@ def chunkify(recips, chunksize): return chunks + def verpdeliver(mlist, msg, msgdata, envsender, failures, conn): for recip in msgdata['recips']: - try: - # We now need to stitch together the message with its header and - # footer. If we're VERPIng, we have to calculate the envelope sender - # for each recipient. Note that the list of recipients must be of - # length 1. - msgdata['recips'] = [recip] - # Make a copy of the message and decorate + delivery that - msgcopy = copy.deepcopy(msg) - decorate(mlist, msgcopy, msgdata) - # Calculate the envelope sender, which we may be VERPing - if msgdata.get('verp'): - try: - bmailbox, bdomain = Mailman.Utils.ParseEmail(envsender) - rmailbox, rdomain = Mailman.Utils.ParseEmail(recip) - if rdomain is None: - # The recipient address is not fully-qualified. We can't - # deliver it to this person, nor can we craft a valid verp - # header. I don't think there's much we can do except ignore - # this recipient. - mailman_log('smtp', 'Skipping VERP delivery to unqual recip: %s', - recip) - continue - d = {'bounces': bmailbox, - 'mailbox': rmailbox, - 'host' : DOT.join(rdomain), - } - envsender = '%s@%s' % ((mm_cfg.VERP_FORMAT % d), DOT.join(bdomain)) - except Exception as e: - mailman_log('error', 'Failed to parse email addresses for VERP: %s', e) - continue - if mlist.personalize == 2: - # When fully personalizing, we want the To address to point to the - # recipient, not to the mailing list - del msgcopy['to'] - name = None - if mlist.isMember(recip): - name = mlist.getMemberName(recip) - if name: - # Convert the name to an email-safe representation. If the - # name is a byte string, convert it first to Unicode, given - # the character set of the member's language, replacing bad - # characters for which we can do nothing about. Once we have - # the name as Unicode, we can create a Header instance for it - # so that it's properly encoded for email transport. - charset = Mailman.Utils.GetCharSet(mlist.getMemberLanguage(recip)) - if charset == 'us-ascii': - # Since Header already tries both us-ascii and utf-8, - # let's add something a bit more useful. - charset = 'iso-8859-1' - charset = Charset(charset) - codec = charset.input_codec or 'ascii' - if not isinstance(name, str): - name = str(name, codec, 'replace') - name = Header(name, charset).encode() - msgcopy['To'] = formataddr((name, recip)) - else: - msgcopy['To'] = recip - # We can flag the mail as a duplicate for each member, if they've - # already received this message, as calculated by Message-ID. See - # AvoidDuplicates.py for details. - if 'x-mailman-copy' in msgcopy: - del msgcopy['x-mailman-copy'] - if recip in msgdata.get('add-dup-header', {}): - msgcopy['X-Mailman-Copy'] = 'yes' - # If desired, add the RCPT_BASE64_HEADER_NAME header - if len(mm_cfg.RCPT_BASE64_HEADER_NAME) > 0: - del msgcopy[mm_cfg.RCPT_BASE64_HEADER_NAME] - msgcopy[mm_cfg.RCPT_BASE64_HEADER_NAME] = b64encode(recip) - # For the final delivery stage, we can just bulk deliver to a party of - # one. ;) - bulkdeliver(mlist, msgcopy, msgdata, envsender, failures, conn) - except Exception as e: - mailman_log('error', 'Failed to process VERP delivery: %s', e) - continue + # We now need to stitch together the message with its header and + # footer. If we're VERPIng, we have to calculate the envelope sender + # for each recipient. Note that the list of recipients must be of + # length 1. + # + # BAW: ezmlm includes the message number in the envelope, used when + # sending a notification to the user telling her how many messages + # they missed due to bouncing. Neat idea. + msgdata['recips'] = [recip] + # Make a copy of the message and decorate + delivery that + msgcopy = copy.deepcopy(msg) + Decorate.process(mlist, msgcopy, msgdata) + # Calculate the envelope sender, which we may be VERPing + if msgdata.get('verp'): + bmailbox, bdomain = Utils.ParseEmail(envsender) + rmailbox, rdomain = Utils.ParseEmail(recip) + if rdomain is None: + # The recipient address is not fully-qualified. We can't + # deliver it to this person, nor can we craft a valid verp + # header. I don't think there's much we can do except ignore + # this recipient. + syslog('smtp', 'Skipping VERP delivery to unqual recip: %s', + recip) + continue + d = {'bounces': bmailbox, + 'mailbox': rmailbox, + 'host' : DOT.join(rdomain), + } + envsender = '%s@%s' % ((mm_cfg.VERP_FORMAT % d), DOT.join(bdomain)) + if mlist.personalize == 2: + # When fully personalizing, we want the To address to point to the + # recipient, not to the mailing list + del msgcopy['to'] + name = None + if mlist.isMember(recip): + name = mlist.getMemberName(recip) + if name: + # Convert the name to an email-safe representation. If the + # name is a byte string, convert it first to Unicode, given + # the character set of the member's language, replacing bad + # characters for which we can do nothing about. Once we have + # the name as Unicode, we can create a Header instance for it + # so that it's properly encoded for email transport. + charset = Utils.GetCharSet(mlist.getMemberLanguage(recip)) + if charset == 'us-ascii': + # Since Header already tries both us-ascii and utf-8, + # let's add something a bit more useful. + charset = 'iso-8859-1' + charset = Charset(charset) + codec = charset.input_codec or 'ascii' + if not isinstance(name, str): + name = str(name, codec, 'replace') + name = Header(name, charset).encode() + msgcopy['To'] = formataddr((name, recip)) + else: + msgcopy['To'] = recip + # We can flag the mail as a duplicate for each member, if they've + # already received this message, as calculated by Message-ID. See + # AvoidDuplicates.py for details. + del msgcopy['x-mailman-copy'] + if recip in msgdata.get('add-dup-header', {}): + msgcopy['X-Mailman-Copy'] = 'yes' + # If desired, add the RCPT_BASE64_HEADER_NAME header + if len(mm_cfg.RCPT_BASE64_HEADER_NAME) > 0: + del msgcopy[mm_cfg.RCPT_BASE64_HEADER_NAME] + msgcopy[mm_cfg.RCPT_BASE64_HEADER_NAME] = b64encode(recip) + # For the final delivery stage, we can just bulk deliver to a party of + # one. ;) + bulkdeliver(mlist, msgcopy, msgdata, envsender, failures, conn) + def bulkdeliver(mlist, msg, msgdata, envsender, failures, conn): - # Initialize recips and refused at the start - recips = [] + # Do some final cleanup of the message header. Start by blowing away + # any the Sender: and Errors-To: headers so remote MTAs won't be + # tempted to delivery bounces there instead of our envelope sender + # + # BAW An interpretation of RFCs 2822 and 2076 could argue for not touching + # the Sender header at all. Brad Knowles points out that MTAs tend to + # wipe existing Return-Path headers, and old MTAs may still honor + # Errors-To while new ones will at worst ignore the header. + # + # With some MUAs (eg. Outlook 2003) rewriting the Sender header with our + # envelope sender causes more problems than it solves, because some will + # include the Sender address in a reply-to-all, which is not only + # confusing to subscribers, but can actually disable/unsubscribe them from + # lists, depending on how often they accidentally reply to it. Also, when + # forwarding mail inline, the sender is replaced with the string "Full + # Name (on behalf bounce@addr.ess)", essentially losing the original + # sender address. To partially mitigate this, we add the list name as a + # display-name in the Sender: header that we add. + # + # The drawback of not touching the Sender: header is that some MTAs might + # still send bounces to it, so by not trapping it, we can miss bounces. + # (Or worse, MTAs might send bounces to the From: address if they can't + # find a Sender: header.) So instead of completely disabling the sender + # rewriting, we offer an option to disable it. + del msg['errors-to'] + msg['Errors-To'] = envsender + if mlist.include_sender_header: + del msg['sender'] + msg['Sender'] = '"%s" <%s>' % (mlist.real_name, envsender) + # Get the plain, flattened text of the message, sans unixfrom + # using our as_string() method to not mangle From_ and not fold + # sub-part headers possibly breaking signatures. + msgtext = msg.as_string(mangle_from_=False) refused = {} + recips = msgdata['recips'] + msgid = msg['message-id'] try: - # Get the list of recipients - recips = msgdata.get('recips', []) - if not recips: - mailman_log('error', 'SMTPDirect: No recipients found in msgdata for message:\n%s', msg.get('Message-ID', 'n/a')) - return - - # Convert email.message.Message to Mailman.Message if needed - if isinstance(msg, email.message.Message) and not isinstance(msg, Message): - mailman_msg = Message() - # Copy all attributes from the original message - for key, value in msg.items(): - mailman_msg[key] = value - # Copy the payload with proper MIME handling - if msg.is_multipart(): - for part in msg.get_payload(): - if isinstance(part, email.message.Message): - mailman_msg.attach(part) - else: - newpart = Message() - newpart.set_payload(part) - mailman_msg.attach(newpart) - else: - mailman_msg.set_payload(msg.get_payload()) - msg = mailman_msg - - # Do some final cleanup of the message header - del msg['errors-to'] - msg['Errors-To'] = envsender - if mlist.include_sender_header: - del msg['sender'] - msg['Sender'] = '"%s" <%s>' % (mlist.real_name, envsender) - - # Get the plain, flattened text of the message - msgtext = msg.as_string(mangle_from_=False) - # Ensure the message text is properly encoded as UTF-8 - if isinstance(msgtext, str): - msgtext = msgtext.encode('utf-8') - - msgid = msg.get('Message-ID', 'n/a') - # Ensure msgid is a string - if isinstance(msgid, bytes): - try: - msgid = msgid.decode('utf-8', 'replace') - except UnicodeDecodeError: - msgid = msgid.decode('latin-1', 'replace') - elif not isinstance(msgid, str): - msgid = str(msgid) - try: - # Send the message - refused = conn.sendmail(envsender, recips, msgtext) - except smtplib.SMTPRecipientsRefused as e: - mailman_log('smtp-failure', 'All recipients refused: %s, msgid: %s', - e, msgid) - refused = e.recipients - # Move message to bad queue since all recipients were refused - badq = get_switchboard(mm_cfg.BADQUEUE_DIR) - badq.enqueue(msg, msgdata) - except smtplib.SMTPResponseException as e: - mailman_log('smtp-failure', 'SMTP session failure: %s, %s, msgid: %s', - e.smtp_code, e.smtp_error, msgid) - # Properly handle permanent vs temporary failures - if e.smtp_code >= 500 and e.smtp_code != 552: - # Permanent failure - add to refused and move to bad queue - for r in recips: - refused[r] = (e.smtp_code, e.smtp_error) - badq = get_switchboard(mm_cfg.BADQUEUE_DIR) - badq.enqueue(msg, msgdata) - else: - # Temporary failure - don't add to refused - mailman_log('smtp-failure', 'Temporary SMTP failure, will retry: %s', e.smtp_error) - except (socket.error, IOError, smtplib.SMTPException) as e: - # MTA not responding or other socket problems - mailman_log('smtp-failure', 'Low level smtp error: %s, msgid: %s', e, msgid) - error = str(e) + # Send the message + refused = conn.sendmail(envsender, recips, msgtext) + except smtplib.SMTPRecipientsRefused as e: + syslog('smtp-failure', 'All recipients refused: %s, msgid: %s', + e, msgid) + refused = e.recipients + except smtplib.SMTPResponseException as e: + syslog('smtp-failure', 'SMTP session failure: %s, %s, msgid: %s', + e.smtp_code, e.smtp_error, msgid) + # If this was a permanent failure, don't add the recipients to the + # refused, because we don't want them to be added to failures. + # Otherwise, if the MTA rejects the message because of the message + # content (e.g. it's spam, virii, or has syntactic problems), then + # this will end up registering a bounce score for every recipient. + # Definitely /not/ what we want. + if e.smtp_code < 500 or e.smtp_code == 552: + # It's a temporary failure for r in recips: - refused[r] = (-1, error) - # Move message to bad queue for low level errors - badq = get_switchboard(mm_cfg.BADQUEUE_DIR) - badq.enqueue(msg, msgdata) - failures.update(refused) - except Exception as e: - mailman_log('error', 'Error in bulkdeliver: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - raise + refused[r] = (e.smtp_code, e.smtp_error) + except (socket.error, IOError, smtplib.SMTPException) as e: + # MTA not responding, or other socket problems, or any other kind of + # SMTPException. In that case, nothing got delivered, so treat this + # as a temporary failure. + syslog('smtp-failure', 'Low level smtp error: %s, msgid: %s', e, msgid) + error = str(e) + for r in recips: + refused[r] = (-1, error) + failures.update(refused) diff --git a/Mailman/Handlers/Scrubber.py b/Mailman/Handlers/Scrubber.py index 5f6182c7..07eda63a 100644 --- a/Mailman/Handlers/Scrubber.py +++ b/Mailman/Handlers/Scrubber.py @@ -17,15 +17,13 @@ """Cleanse a message for archiving.""" -from __future__ import absolute_import, print_function, unicode_literals - import os import re import time import errno import binascii import tempfile -from io import StringIO, BytesIO +from io import StringIO from email.utils import parsedate from email.parser import HeaderParser @@ -35,7 +33,7 @@ from Mailman import mm_cfg from Mailman import Utils from Mailman import LockFile -from Mailman.Message import Message +from Mailman import Message from Mailman.Errors import DiscardMessage from Mailman.i18n import _ from Mailman.Logging.Syslog import syslog @@ -70,25 +68,25 @@ def check(map): return all + def guess_extension(ctype, ext): - """Guess the file extension for a content type. - - This function handles both strict and non-strict MIME type matching. - """ + # mimetypes maps multiple extensions to the same type, e.g. .doc, .dot, + # and .wiz are all mapped to application/msword. This sucks for finding + # the best reverse mapping. If the extension is one of the giving + # mappings, we'll trust that, otherwise we'll just guess. :/ all = guess_all_extensions(ctype, strict=False) if ext in all: return ext - if ctype.lower() == 'application/octet-stream': + if ctype.lower == 'application/octet-stream': # For this type, all[0] is '.obj'. '.bin' is better. return '.bin' - if ctype.lower() == 'text/plain': + if ctype.lower == 'text/plain': # For this type, all[0] is '.ksh'. '.txt' is better. return '.txt' - return all[0] if all else '.bin' + return all and all[0] def safe_strftime(fmt, t): - """Format time safely, handling invalid timestamps.""" try: return time.strftime(fmt, t) except (TypeError, ValueError, OverflowError): @@ -96,10 +94,10 @@ def safe_strftime(fmt, t): def calculate_attachments_dir(mlist, msg, msgdata): - """Calculate the directory for storing message attachments. - - Uses a combination of date and message ID to create unique paths. - """ + # Calculate the directory that attachments for this message will go + # under. To avoid inode limitations, the scheme will be: + # archives/private//attachments/YYYYMMDD// + # Start by calculating the date-based and msgid-hash components. fmt = '%Y%m%d' datestr = msg.get('Date') if datestr: @@ -113,7 +111,12 @@ def calculate_attachments_dir(mlist, msg, msgdata): datedir = safe_strftime(fmt, datestr) if not datedir: # What next? Unixfrom, I guess. - parts = msg.get_unixfrom().split() + unixfrom = msg.get_unixfrom() + if unixfrom: + parts = unixfrom.split() + else: + # Fallback if no unixfrom + parts = [] try: month = {'Jan':1, 'Feb':2, 'Mar':3, 'Apr':4, 'May':5, 'Jun':6, 'Jul':7, 'Aug':8, 'Sep':9, 'Oct':10, 'Nov':11, 'Dec':12, @@ -124,8 +127,7 @@ def calculate_attachments_dir(mlist, msg, msgdata): # Best we can do I think month = day = year = 0 datedir = '%04d%02d%02d' % (year, month, day) - if not datedir: - raise ValueError('Missing datedir parameter') + assert datedir # As for the msgid hash, we'll base this part on the Message-ID: so that # all attachments for the same message end up in the same directory (we'll # uniquify the filenames in that directory as needed). We use the first 2 @@ -135,27 +137,26 @@ def calculate_attachments_dir(mlist, msg, msgdata): msgid = msg['message-id'] if msgid is None: msgid = msg['Message-ID'] = Utils.unique_message_id(mlist) + + msgid = msgid.encode() # We assume that the message id actually /is/ unique! digest = sha_new(msgid).hexdigest() return os.path.join('attachments', datedir, digest[:4] + digest[-4:]) def replace_payload_by_text(msg, text, charset): - """Replace message payload with text using proper charset handling.""" + # TK: This is a common function in replacing the attachment and the main + # message by a text (scrubbing). del msg['content-type'] del msg['content-transfer-encoding'] - - # Ensure we have str for text and bytes for charset - if isinstance(text, bytes): - text = text.decode('utf-8', 'replace') if isinstance(charset, str): - charset = charset.encode('ascii') - + # email 3.0.1 (python 2.4) doesn't like unicode + charset = charset.encode('us-ascii') msg.set_payload(text, charset) + def process(mlist, msg, msgdata=None): - """Process a message for archiving, handling attachments appropriately.""" sanitize = mm_cfg.ARCHIVE_HTML_SANITIZER outer = True if msgdata is None: @@ -179,11 +180,25 @@ def process(mlist, msg, msgdata=None): # We need to choose a charset for the scrubbed message, so we'll # arbitrarily pick the charset of the first text/plain part in the # message. + # MAS: Also get the RFC 3676 stuff from this part. This seems to + # work OK for scrub_nondigest. It will also work as far as + # scrubbing messages for the archive is concerned, but pipermail + # doesn't pay any attention to the RFC 3676 parameters. The plain + # format digest is going to be a disaster in any case as some of + # messages will be format="flowed" and some not. ToDigest creates + # its own Content-Type: header for the plain digest which won't + # have RFC 3676 parameters. If the message Content-Type: headers + # are retained for display in the digest, the parameters will be + # there for information, but not for the MUA. This is the best we + # can do without having get_payload() process the parameters. if charset is None: charset = part.get_content_charset(lcset) format = part.get_param('format') delsp = part.get_param('delsp') # TK: if part is attached then check charset and scrub if none + # MAS: Content-Disposition is not a good test for 'attached'. + # RFC 2183 sec. 2.10 allows Content-Disposition on the main body. + # Make it specifically 'attachment'. if (part.get('content-disposition', '').lower() == 'attachment' and not part.get_content_charset()): omask = os.umask(0o002) @@ -204,12 +219,16 @@ def process(mlist, msg, msgdata=None): raise DiscardMessage replace_payload_by_text(part, _('HTML attachment scrubbed and removed'), + # Adding charset arg and removing content-type + # sets content-type to text/plain lcset) elif sanitize == 2: # By leaving it alone, Pipermail will automatically escape it pass elif sanitize == 3: - # Pull it out as an attachment but leave it unescaped + # Pull it out as an attachment but leave it unescaped. This + # is dangerous, but perhaps useful for heavily moderated + # lists. omask = os.umask(0o002) try: url = save_attachment(mlist, part, dir, filter_html=False) @@ -220,13 +239,13 @@ def process(mlist, msg, msgdata=None): URL: %(url)s """), lcset) else: - # HTML-escape it and store it as an attachment - payload = part.get_payload(decode=True) - if isinstance(payload, bytes): - payload = payload.decode('utf-8', 'replace') - payload = Utils.websafe(payload) + # HTML-escape it and store it as an attachment, but make it + # look a /little/ bit prettier. :( + payload = Utils.websafe(part.get_payload(decode=True)) # For whitespace in the margin, change spaces into - # non-breaking spaces, and tabs into 8 of those + # non-breaking spaces, and tabs into 8 of those. Then use a + # mono-space font. Still looks hideous to me, but then I'd + # just as soon discard them. def doreplace(s): return s.expandtabs(8).replace(' ', ' ') lines = [doreplace(s) for s in payload.split('\n')] @@ -367,16 +386,26 @@ def doreplace(s): if isinstance(t, str): if not t.endswith('\n'): t += '\n' - text.append(t) + elif isinstance(t, bytes): + if not t.endswith(b'\n'): + t += b'\n' + text.append(t) # Now join the text and set the payload sep = _('-------------- next part --------------\n') # The i18n separator is in the list's charset. Coerce it to the # message charset. try: - s = str(sep, lcset, 'replace') - sep = s.encode(charset, 'replace') - except (UnicodeError, LookupError, ValueError, - AssertionError): + if isinstance(sep, bytes): + # Only decode if it's a bytes object + s = sep.decode(lcset, 'replace') + sep = s.encode(charset, 'replace') + else: + # If it's already a str, no need to decode + sep = sep.encode(charset, 'replace') + except (UnicodeError, LookupError, ValueError, AssertionError) as e: + # If something failed and we are still a string, fall back to UTF-8 + if isinstance(sep, str): + sep = sep.encode('utf-8', 'replace') pass replace_payload_by_text(msg, sep.join(text), charset) if format: @@ -385,75 +414,160 @@ def doreplace(s): msg.set_param('DelSp', delsp) return msg - + def makedirs(dir): - """Create directory hierarchy safely.""" + # Create all the directories to store this attachment in try: os.makedirs(dir, 0o02775) # Unfortunately, FreeBSD seems to be broken in that it doesn't honor # the mode arg of mkdir(). - def twiddle(arg, dirname, names): - os.chmod(dirname, 0o02775) - os.path.walk(dir, twiddle, None) - except OSError as e: - if e.errno != errno.EEXIST: raise + def twiddle(arg, dirpath, dirnames): + for dirname in dirnames: + # Construct the full path for each directory + full_path = os.path.join(dirpath, dirname) + os.chmod(full_path, 0o02775) + for dirpath, dirnames, filenames in os.walk(dir): + twiddle(None, dirpath, dirnames) + except OSError as e: + if e.errno != errno.EEXIST: + raise + def save_attachment(mlist, msg, dir, filter_html=True): - """Save a message attachment safely. - - Returns the URL where the attachment was saved. - """ - # Get the attachment filename - fname = msg.get_filename() - if not fname: - fname = msg.get_param('name') - if not fname: - # Use content-type if no filename is given - ctype = msg.get_content_type() - # Sanitize the content-type so it can be used as a filename - fname = re.sub(r'[^-\w.]', '_', ctype) - # Add an extension if possible - ext = guess_extension(ctype, '') - if ext: - fname += ext - - # Sanitize the filename - fname = re.sub(r'[/\\:]', '_', fname) - fname = re.sub(r'[^-\w.]', '_', fname) - fname = re.sub(r'^\.*', '_', fname) - - # Get the attachment content - payload = msg.get_payload(decode=True) - if not payload: - return None - - # Create attachment directory - dir = os.path.join(mlist.archive_dir(), dir) - makedirs(dir) - - # Save the attachment + fsdir = os.path.join(mlist.archive_dir(), dir) + makedirs(fsdir) + # Figure out the attachment type and get the decoded data + decodedpayload = msg.get_payload(decode=True) + # BAW: mimetypes ought to handle non-standard, but commonly found types, + # e.g. image/jpg (should be image/jpeg). For now we just store such + # things as application/octet-streams since that seems the safest. + ctype = msg.get_content_type() + # i18n file name is encoded + lcset = Utils.GetCharSet(mlist.preferred_language) + filename = Utils.oneline(msg.get_filename(''), lcset) + filename, fnext = os.path.splitext(filename) + # For safety, we should confirm this is valid ext for content-type + # but we can use fnext if we introduce fnext filtering + if mm_cfg.SCRUBBER_USE_ATTACHMENT_FILENAME_EXTENSION: + # HTML message doesn't have filename :-( + ext = fnext or guess_extension(ctype, fnext) + else: + ext = guess_extension(ctype, fnext) + if not ext: + # We don't know what it is, so assume it's just a shapeless + # application/octet-stream, unless the Content-Type: is + # message/rfc822, in which case we know we'll coerce the type to + # text/plain below. + if ctype == 'message/rfc822': + ext = '.txt' + else: + ext = '.bin' + # Allow only alphanumerics, dash, underscore, and dot + ext = sre.sub('', ext) path = None - counter = 0 - while True: - if counter: - fname_parts = os.path.splitext(fname) - fname = '%s-%d%s' % (fname_parts[0], counter, fname_parts[1]) - path = os.path.join(dir, fname) + # We need a lock to calculate the next attachment number + lockfile = os.path.join(fsdir, 'attachments.lock') + lock = LockFile.LockFile(lockfile) + lock.lock() + try: + # Now base the filename on what's in the attachment, uniquifying it if + # necessary. + if not filename or mm_cfg.SCRUBBER_DONT_USE_ATTACHMENT_FILENAME: + filebase = 'attachment' + else: + # Sanitize the filename given in the message headers + parts = pre.split(filename) + filename = parts[-1] + # Strip off leading dots + filename = dre.sub('', filename) + # Allow only alphanumerics, dash, underscore, and dot + filename = sre.sub('', filename) + # If the filename's extension doesn't match the type we guessed, + # which one should we go with? For now, let's go with the one we + # guessed so attachments can't lie about their type. Also, if the + # filename /has/ no extension, then tack on the one we guessed. + # The extension was removed from the name above. + # Allow for extra and ext and keep it under 255 bytes. + filebase = filename[:240] + # Now we're looking for a unique name for this file on the file + # system. If msgdir/filebase.ext isn't unique, we'll add a counter + # after filebase, e.g. msgdir/filebase-cnt.ext + counter = 0 + extra = '' + while True: + path = os.path.join(fsdir, filebase + extra + ext) + # Generally it is not a good idea to test for file existance + # before just trying to create it, but the alternatives aren't + # wonderful (i.e. os.open(..., O_CREAT | O_EXCL) isn't + # NFS-safe). Besides, we have an exclusive lock now, so we're + # guaranteed that no other process will be racing with us. + if os.path.exists(path): + counter += 1 + extra = '-%04d' % counter + else: + break + finally: + lock.unlock() + # `path' now contains the unique filename for the attachment. There's + # just one more step we need to do. If the part is text/html and + # ARCHIVE_HTML_SANITIZER is a string (which it must be or we wouldn't be + # here), then send the attachment through the filter program for + # sanitization + if filter_html and ctype == 'text/html': + base, ext = os.path.splitext(path) + tmppath = base + '-tmp' + ext + fp = open(tmppath, 'w') try: - # Open in binary mode and write bytes directly - with open(path, 'wb') as fp: - fp.write(payload) - break - except OSError as e: - if e.errno != errno.EEXIST: - raise - counter += 1 - - # Make the file group writable - os.chmod(path, 0o0664) - - # Return the URL + fp.write(decodedpayload) + fp.close() + cmd = mm_cfg.ARCHIVE_HTML_SANITIZER % {'filename' : tmppath} + progfp = os.popen(cmd, 'r') + decodedpayload = progfp.read() + status = progfp.close() + if status: + syslog('error', + 'HTML sanitizer exited with non-zero status: %s', + status) + finally: + os.unlink(tmppath) + # BAW: Since we've now sanitized the document, it should be plain + # text. Blarg, we really want the sanitizer to tell us what the type + # if the return data is. :( + ext = '.txt' + path = base + '.txt' + # Is it a message/rfc822 attachment? + elif ctype == 'message/rfc822': + submsg = msg.get_payload() + + # submsg is usually a list containing a single Message object. + # We need to extract that Message object. (taken from Utils.websafe()) + if isinstance(submsg, list) or isinstance(submsg, tuple): + if len(submsg) == 0: + submsg = '' + else: + submsg = submsg[-1] + + # BAW: I'm sure we can eventually do better than this. :( + decodedpayload = Utils.websafe(str(submsg)) + + # encode the message back into the charset of the original message. + mcset = submsg.get_content_charset('') + if mcset == None or mcset == "": + mcset = 'utf-8' + decodedpayload = decodedpayload.encode(mcset) + + fp = open(path, 'wb') + fp.write(decodedpayload) + fp.close() + # Now calculate the url baseurl = mlist.GetBaseArchiveURL() - url = '%s/%s/%s' % (baseurl, dir, fname) + # Private archives will likely have a trailing slash. Normalize. + if baseurl[-1] != '/': + baseurl += '/' + # A trailing space in url string may save users who are using + # RFC-1738 compliant MUA (Not Mozilla). + # Trailing space will definitely be a problem with format=flowed. + # Bracket the URL instead. + url = '<' + baseurl + '%s/%s%s%s>' % (dir, filebase, extra, ext) return url diff --git a/Mailman/Handlers/SpamDetect.py b/Mailman/Handlers/SpamDetect.py index 14c77343..a9e872fe 100644 --- a/Mailman/Handlers/SpamDetect.py +++ b/Mailman/Handlers/SpamDetect.py @@ -25,9 +25,9 @@ TBD: This needs to be made more configurable and robust. """ -from __future__ import absolute_import, print_function, unicode_literals - +from builtins import str import re + from unicodedata import normalize from email.errors import HeaderParseError from email.header import decode_header @@ -39,7 +39,6 @@ from Mailman import Utils from Mailman.Handlers.Hold import hold_for_approval from Mailman.Logging.Syslog import syslog -from Mailman.Message import Message # First, play footsie with _ so that the following are marked as translated, # but aren't actually translated until we need the text later on. @@ -47,6 +46,7 @@ def _(s): return s + class SpamDetected(Errors.DiscardMessage): """The message contains known spam""" @@ -63,71 +63,43 @@ def reason_notice(self): _ = i18n._ -def getDecodedHeaders(msg, lcset): - """Return a Unicode string containing all headers of msg, unfolded and RFC 2047 - decoded. If a header cannot be decoded, it is replaced with a string of - question marks. + +def getDecodedHeaders(msg, cset='utf-8'): + """Returns a unicode containing all the headers of msg, unfolded and + RFC 2047 decoded, normalized and separated by new lines. """ - headers = [] - for name in msg.keys(): - # Get all values for this header (could be multiple) - for value in msg.get_all(name, []): - try: - # Format as "Header: Value" - header_line = '%s: %s' % (name, value) - # Ensure we have a string - if isinstance(header_line, bytes): - header_line = header_line.decode('utf-8', 'replace') - headers.append(header_line) - except (UnicodeError, AttributeError): - # If we can't decode it, replace with question marks - headers.append('?' * len(str(value))) - return '\n'.join(headers) - -def process(mlist, msg, msgdata): - # Check for Google Groups messages first - google_groups_headers = [ - 'X-Google-Groups-Id', - 'X-Google-Groups-Info', - 'X-Google-Groups-Url', - 'X-Google-Groups-Name', - 'X-Google-Groups-Email' - ] - - for header in google_groups_headers: - if msg.get(header): - syslog('vette', 'Google Groups message detected via header %s, discarding', header) - # Send bounce to the message's errors-to address + headers = u'' + for h, v in list(msg.items()): + uvalue = u'' + try: + if isinstance(v, str): + v = decode_header(re.sub(r'\n\s', ' ', v)) + else: + continue + except HeaderParseError: + v = [(v, 'us-ascii')] + for frag, cs in v: + if not cs: + cs = 'us-ascii' try: - bounce_msg = Message() - bounce_msg['From'] = mlist.GetBounceEmail() - # Use the message's errors-to header if present, otherwise use the From address - bounce_to = msg.get('errors-to') or msg.get('from', 'unknown') - bounce_msg['To'] = bounce_to - bounce_msg['Subject'] = 'Message rejected: Google Groups not allowed' - bounce_msg['Message-ID'] = Utils.unique_message_id(mlist) - bounce_msg['Date'] = Utils.formatdate(localtime=True) - bounce_msg['X-Mailman-From'] = msg.get('from', 'unknown') - bounce_msg['X-Mailman-To'] = msg.get('to', 'unknown') - bounce_msg['X-Mailman-List'] = mlist.internal_name() - bounce_msg['X-Mailman-Reason'] = 'Google Groups messages are not allowed' - - # Include original message headers - bounce_text = 'Original message headers:\n' - for name, value in msg.items(): - bounce_text += f'{name}: {value}\n' - bounce_msg.set_payload(bounce_text) - - # Send the bounce - mlist.BounceMessage(bounce_msg, msgdata) - syslog('vette', 'Sent bounce to %s for rejected Google Groups message', bounce_to) - except Exception as e: - syslog('error', 'Failed to send bounce for Google Groups message: %s', str(e)) - - # Discard the original message - raise Errors.DiscardMessage - + if isinstance(frag, bytes): + uvalue += str(frag, cs, 'replace') + else: + uvalue += frag + except LookupError: + # The encoding charset is unknown. At this point, frag + # has been QP or base64 decoded into a byte string whose + # charset we don't know how to handle. We will try to + # unicode it as iso-8859-1 which may result in a garbled + # mess, but we have to do something. + uvalue += str(frag, 'iso-8859-1', 'replace') + headers += u'%s: %s\n' % (h, normalize(mm_cfg.NORMALIZE_FORM, uvalue)) + return headers + + + +def process(mlist, msg, msgdata): # Before anything else, check DMARC if necessary. We do this as early # as possible so reject/discard actions trump other holds/approvals and # wrap/munge actions get flagged even for approved messages. @@ -135,7 +107,7 @@ def process(mlist, msg, msgdata): # discard actions. if not msgdata.get('toowner'): msgdata['from_is_list'] = 0 - dn, addr = parseaddr(msg.get('from', '')) + dn, addr = parseaddr(msg.get('from')) if addr and mlist.dmarc_moderation_action > 0: if (mlist.GetPattern(addr, mlist.dmarc_moderation_addresses) or Utils.IsDMARCProhibited(mlist, addr)): @@ -162,9 +134,7 @@ def process(mlist, msg, msgdata): raise Errors.DiscardMessage # Get member address if any. - for sender_tuple in msg.get_senders(): - # Extract email address from the (realname, address) tuple - _, sender = sender_tuple + for sender in msg.get_senders(): if mlist.isMember(sender): break else: @@ -183,8 +153,6 @@ def process(mlist, msg, msgdata): for header, regex in mm_cfg.KNOWN_SPAMMERS: cre = re.compile(regex, re.IGNORECASE) for value in msg.get_all(header, []): - if isinstance(value, bytes): - value = value.decode('utf-8', 'replace') mo = cre.search(value) if mo: # we've detected spam, so throw the message away @@ -192,7 +160,7 @@ def process(mlist, msg, msgdata): # Now do header_filter_rules # TK: Collect headers in sub-parts because attachment filename # extension may be a clue to possible virus/spam. - headers = '' + headers = u'' # Get the character set of the lists preferred language for headers lcset = Utils.GetCharSet(mlist.preferred_language) for p in msg.walk(): diff --git a/Mailman/Handlers/Tagger.py b/Mailman/Handlers/Tagger.py index f2879906..e3681a0e 100644 --- a/Mailman/Handlers/Tagger.py +++ b/Mailman/Handlers/Tagger.py @@ -20,7 +20,7 @@ import re import email import email.errors -from email.iterators import body_line_iterator +import email.iterators import email.parser from email.header import decode_header @@ -35,6 +35,7 @@ NLTAB = '\n\t' + def process(mlist, msg, msgdata): if not mlist.topics_enabled: return @@ -75,6 +76,7 @@ def _decode(h): mlist, msg, msgdata, delete=False) + def scanbody(msg, numlines=None): # We only scan the body of the message if it is of MIME type text/plain, # or if the outer type is multipart/alternative and there is a text/plain @@ -95,7 +97,7 @@ def scanbody(msg, numlines=None): # the first numlines of body text. lines = [] lineno = 0 - reader = list(body_line_iterator(msg, decode=True)) + reader = list(email.iterators.body_line_iterator(msg, decode=True)) while numlines is None or lineno < numlines: try: line = reader.pop(0) @@ -113,6 +115,7 @@ def scanbody(msg, numlines=None): return msg.get_all('subject', []) + msg.get_all('keywords', []) + class _ForgivingParser(email.parser.HeaderParser): # Be a little more forgiving about non-header/continuation lines, since # we'll just read as much as we can from "header-like" lines in the body. diff --git a/Mailman/Handlers/ToArchive.py b/Mailman/Handlers/ToArchive.py index dab6b0a1..940c1ba7 100644 --- a/Mailman/Handlers/ToArchive.py +++ b/Mailman/Handlers/ToArchive.py @@ -26,15 +26,31 @@ def process(mlist, msg, msgdata): + # DEBUG: Log archiver processing start + from Mailman.Logging.Syslog import syslog + syslog('debug', 'ToArchive: Starting archive processing for list %s', mlist.internal_name()) + # short circuits - if msgdata.get('isdigest') or not mlist.archive: + if msgdata.get('isdigest'): + syslog('debug', 'ToArchive: Skipping digest message for list %s', mlist.internal_name()) return + if not mlist.archive: + syslog('debug', 'ToArchive: Archiving disabled for list %s', mlist.internal_name()) + return + # Common practice seems to favor "X-No-Archive: yes". No other value for # this header seems to make sense, so we'll just test for it's presence. # I'm keeping "X-Archive: no" for backwards compatibility. - if 'x-no-archive' in msg or msg.get('x-archive', '').lower() == 'no': + if 'x-no-archive' in msg: + syslog('debug', 'ToArchive: Skipping message with X-No-Archive header for list %s', mlist.internal_name()) + return + if msg.get('x-archive', '').lower() == 'no': + syslog('debug', 'ToArchive: Skipping message with X-Archive: no for list %s', mlist.internal_name()) return + # Send the message to the archiver queue archq = get_switchboard(mm_cfg.ARCHQUEUE_DIR) + syslog('debug', 'ToArchive: Enqueuing message to archive queue for list %s', mlist.internal_name()) # Send the message to the queue archq.enqueue(msg, msgdata) + syslog('debug', 'ToArchive: Successfully enqueued message to archive queue for list %s', mlist.internal_name()) diff --git a/Mailman/Handlers/ToDigest.py b/Mailman/Handlers/ToDigest.py index 60dd8792..8ae5fa17 100644 --- a/Mailman/Handlers/ToDigest.py +++ b/Mailman/Handlers/ToDigest.py @@ -16,8 +16,7 @@ # USA. """Add the message to the list's current digest and possibly send it.""" - -from __future__ import absolute_import, print_function, unicode_literals +from __future__ import print_function # Messages are accumulated to a Unix mailbox compatible file containing all # the messages destined for the digest. This file must be parsable by the @@ -27,15 +26,15 @@ # directory and the DigestRunner will craft the MIME, rfc1153, and # (eventually) URL-subject linked digests from the mbox. +from builtins import str import os import re import copy import time import traceback -from io import StringIO, BytesIO +from io import StringIO from email.parser import Parser -from email import message_from_string from email.generator import Generator from email.mime.base import MIMEBase from email.mime.text import MIMEText @@ -43,17 +42,12 @@ from email.utils import getaddresses, formatdate from email.header import decode_header, make_header, Header from email.charset import Charset -import email -import email.message -from email.message import Message -import errno -import pickle from Mailman import mm_cfg from Mailman import Utils +from Mailman import Message from Mailman import i18n from Mailman import Errors -from Mailman.Message import Message from Mailman.Mailbox import Mailbox from Mailman.MemberAdaptor import ENABLED from Mailman.Handlers.Decorate import decorate @@ -67,365 +61,382 @@ UEMPTYSTRING = u'' EMPTYSTRING = '' -def decode_header_value(value, lcset): - """Decode an email header value properly.""" - if not value: - return '' - try: - # Handle encoded-word format - decoded = [] - for part, charset in decode_header(value): - if isinstance(part, bytes): - try: - decoded.append(part.decode(charset or lcset, 'replace')) - except (UnicodeError, LookupError): - decoded.append(part.decode('utf-8', 'replace')) - else: - decoded.append(part) - return ''.join(decoded) - except Exception: - return str(value) - + def to_cset_out(text, lcset): - """Convert text to output charset. - - Handles both str and bytes input, ensuring proper encoding for output. - Returns a properly encoded string, not bytes. - """ - if text is None: - return '' - + # Convert text from unicode or lcset to output cset. ocset = Charset(lcset).get_output_charset() or lcset - if isinstance(text, str): - try: - return text - except (UnicodeError, LookupError): - return text.encode('utf-8', 'replace').decode('utf-8') - elif isinstance(text, bytes): - try: - return text.decode(lcset, 'replace') - except (UnicodeError, LookupError): - try: - return text.decode('utf-8', 'replace') - except (UnicodeError, LookupError): - return str(text) + return text.encode(ocset, 'replace') else: - return str(text) + return text.decode(lcset, 'replace').encode(ocset, 'replace') -def process_message_body(msg, lcset): - """Process a message body, handling MIME parts and encoding properly.""" - if msg.is_multipart(): - parts = [] - for part in msg.walk(): - if part.get_content_maintype() == 'multipart': - continue - try: - payload = part.get_payload(decode=True) - if isinstance(payload, bytes): - charset = part.get_content_charset(lcset) - try: - text = payload.decode(charset or lcset, 'replace') - except (UnicodeError, LookupError): - text = payload.decode('utf-8', 'replace') - else: - text = str(payload) - parts.append(text) - except Exception as e: - parts.append('[Part could not be decoded]') - return '\n\n'.join(parts) - else: - try: - payload = msg.get_payload(decode=True) - if isinstance(payload, bytes): - charset = msg.get_content_charset(lcset) - try: - return payload.decode(charset or lcset, 'replace') - except (UnicodeError, LookupError): - return payload.decode('utf-8', 'replace') - return str(payload) - except Exception: - return '[Message body could not be decoded]' + def process(mlist, msg, msgdata): - """Process a message for digest delivery. - - This function handles adding messages to the digest and sending the digest - when appropriate. All file operations use proper encoding handling. - """ - if msgdata.get('isdigest'): - return - # Convert email.message.Message to Mailman.Message.Message if needed - if isinstance(msg, email.message.Message): - newmsg = Message() - # Copy attributes - for k, v in msg.items(): - newmsg[k] = v - # Copy payload - if msg.is_multipart(): - for part in msg.get_payload(): - newmsg.attach(part) - else: - newmsg.set_payload(msg.get_payload()) - msg = newmsg - # Create digest message - mimemsg = Message() - rfc1153msg = Message() - - # Short circuit non-digestable lists - if not mlist.digestable: + # Short circuit non-digestable lists. + if not mlist.digestable or msgdata.get('isdigest'): return - mboxfile = os.path.join(mlist.fullpath(), 'digest.mbox') - lockfile = mboxfile + '.lock' - - # Create a lock file to prevent concurrent access + omask = os.umask(0o007) try: - with open(lockfile, 'x') as f: - f.write(str(os.getpid())) - except FileExistsError: - # Another process is updating the digest, log and return - syslog('info', 'Digest file locked by another process, deferring message %s for list %s', - msg.get('message-id', 'unknown'), mlist.internal_name()) - return - + with open(mboxfile, 'a+b') as mboxfp: + mbox = Mailbox(mboxfp.name) + mbox.AppendMessage(msg) + # Calculate the current size of the accumulation file. This will not tell + # us exactly how big the MIME, rfc1153, or any other generated digest + # message will be, but it's the most easily available metric to decide + # whether the size threshold has been reached. + mboxfp.flush() + size = os.path.getsize(mboxfile) + if (mlist.digest_size_threshhold > 0 and + size / 1024.0 >= mlist.digest_size_threshhold): + # This is a bit of a kludge to get the mbox file moved to the digest + # queue directory. + try: + # Enclose in try/except here because a error in send_digest() can + # silently stop regular delivery. Unsuccessful digest delivery + # should be tried again by cron and the site administrator will be + # notified of any error explicitly by the cron error message. + mboxfp.seek(0) + send_digests(mlist, mboxfp) + os.unlink(mboxfile) + except Exception as errmsg: + # Bare except is generally prohibited in Mailman, but we can't + # forecast what exceptions can occur here. + syslog('error', 'send_digests() failed: %s', errmsg) + s = StringIO() + traceback.print_exc(file=s) + syslog('error', s.getvalue()) + finally: + os.umask(omask) + + + +def send_digests(mlist, mboxfp): + # Set the digest volume and time + if mlist.digest_last_sent_at: + bump = False + # See if we should bump the digest volume number + timetup = time.localtime(mlist.digest_last_sent_at) + now = time.localtime(time.time()) + freq = mlist.digest_volume_frequency + if freq == 0 and timetup[0] < now[0]: + # Yearly + bump = True + elif freq == 1 and timetup[1] != now[1]: + # Monthly, but we take a cheap way to calculate this. We assume + # that the clock isn't going to be reset backwards. + bump = True + elif freq == 2 and (timetup[1] % 4 != now[1] % 4): + # Quarterly, same caveat + bump = True + elif freq == 3: + # Once again, take a cheap way of calculating this + weeknum_last = int(time.strftime('%W', timetup)) + weeknum_now = int(time.strftime('%W', now)) + if weeknum_now > weeknum_last or timetup[0] > now[0]: + bump = True + elif freq == 4 and timetup[7] != now[7]: + # Daily + bump = True + if bump: + mlist.bump_digest_volume() + mlist.digest_last_sent_at = time.time() + # Wrapper around actually digest crafter to set up the language context + # properly. All digests are translated to the list's preferred language. + otranslation = i18n.get_translation() + i18n.set_language(mlist.preferred_language) try: - omask = os.umask(0o007) - try: - # Open file in text mode with proper encoding - with open(mboxfile, 'a+', encoding='utf-8') as mboxfp: - # Convert message to string format - msg_str = str(msg) - mboxfp.write(msg_str + '\n') - - # Calculate size and check threshold - mboxfp.flush() - size = os.path.getsize(mboxfile) - syslog('info', 'Added message %s to digest for list %s (current size: %d KB)', - msg.get('message-id', 'unknown'), mlist.internal_name(), size / 1024) - - if (mlist.digest_size_threshhold > 0 and - size / 1024.0 >= mlist.digest_size_threshhold): - try: - syslog('info', 'Digest threshold reached for list %s, sending digest', - mlist.internal_name()) - send_digests(mlist, mboxfile) # Pass path instead of file object - except Exception as e: - syslog('error', 'Error sending digest for list %s: %s', - mlist.internal_name(), str(e)) - syslog('error', 'Traceback: %s', traceback.format_exc()) - finally: - os.umask(omask) + send_i18n_digests(mlist, mboxfp) finally: - # Clean up the lock file - try: - os.unlink(lockfile) - except OSError: - pass + i18n.set_translation(otranslation) -def send_digests(mlist, mboxpath): - """Send digests for the mailing list with performance optimizations.""" - # Set up the digest state - volume = mlist.volume - issue = mlist.next_digest_number - digestid = _('%(realname)s Digest, Vol %(volume)d, Issue %(issue)d') % { - 'realname': mlist.real_name, - 'volume': volume, - 'issue': issue - } - - # Get the list's preferred language and charset + + +def send_i18n_digests(mlist, mboxfp): + mbox = Mailbox(mboxfp) + # Prepare common information (first lang/charset) lang = mlist.preferred_language lcset = Utils.GetCharSet(lang) lcset_out = Charset(lcset).output_charset or lcset - - # Create the digest messages - mimemsg = Message() + # Common Information (contd) + realname = mlist.real_name + volume = mlist.volume + issue = mlist.next_digest_number + digestid = _('%(realname)s Digest, Vol %(volume)d, Issue %(issue)d') + digestsubj = Header(digestid, lcset, header_name='Subject') + # Set things up for the MIME digest. Only headers not added by + # CookHeaders need be added here. + # Date/Message-ID should be added here also. + mimemsg = Message.Message() mimemsg['Content-Type'] = 'multipart/mixed' mimemsg['MIME-Version'] = '1.0' mimemsg['From'] = mlist.GetRequestEmail() - mimemsg['Subject'] = Header(digestid, lcset, header_name='Subject') + mimemsg['Subject'] = digestsubj mimemsg['To'] = mlist.GetListEmail() mimemsg['Reply-To'] = mlist.GetListEmail() mimemsg['Date'] = formatdate(localtime=1) mimemsg['Message-ID'] = Utils.unique_message_id(mlist) - - # Set up the RFC 1153 digest - plainmsg = StringIO() # Use StringIO for text output - rfc1153msg = Message() + # Set things up for the rfc1153 digest + plainmsg = StringIO() + rfc1153msg = Message.Message() rfc1153msg['From'] = mlist.GetRequestEmail() - rfc1153msg['Subject'] = Header(digestid, lcset, header_name='Subject') + rfc1153msg['Subject'] = digestsubj rfc1153msg['To'] = mlist.GetListEmail() rfc1153msg['Reply-To'] = mlist.GetListEmail() rfc1153msg['Date'] = formatdate(localtime=1) rfc1153msg['Message-ID'] = Utils.unique_message_id(mlist) - - # Create the digest content separator70 = '-' * 70 separator30 = '-' * 30 - - # Add masthead + # In the rfc1153 digest, the masthead contains the digest boilerplate plus + # any digest header. In the MIME digests, the masthead and digest header + # are separate MIME subobjects. In either case, it's the first thing in + # the digest, and we can calculate it now, so go ahead and add it now. mastheadtxt = Utils.maketext( 'masthead.txt', - {'real_name': mlist.real_name, - 'got_list_email': mlist.GetListEmail(), - 'got_listinfo_url': mlist.GetScriptURL('listinfo', absolute=1), + {'real_name' : mlist.real_name, + 'got_list_email': mlist.GetListEmail(), + 'got_listinfo_url': mlist.GetScriptURL('listinfo', absolute=1), 'got_request_email': mlist.GetRequestEmail(), - 'got_owner_email': mlist.GetOwnerEmail(), - }, - lang=lang, - mlist=mlist) - - # Add masthead to both digest formats - mimemsg.attach(MIMEText(mastheadtxt, _charset=lcset)) - plainmsg.write(to_cset_out(mastheadtxt, lcset_out)) - plainmsg.write('\n') - - # Process the mbox file - try: - with open(mboxpath, 'r', encoding='utf-8') as mboxfp: - msg_num = 1 - current_msg = [] - for line in mboxfp: - if line.startswith('From '): - if current_msg: - # Process the previous message - msg_str = ''.join(current_msg) - try: - msg = message_from_string(msg_str) - if msg is None: - continue - - subject = decode_header_value(msg.get('subject', _('(no subject)')), lcset) - subject = Utils.oneline(subject, lcset) - - # Add to table of contents - plainmsg.write('%2d. %s\n' % (msg_num, to_cset_out(subject, lcset_out))) - - # Add the message to both digest formats - mimemsg.attach(MIMEMessage(msg)) - - # Add message header - plainmsg.write('\n') - plainmsg.write(to_cset_out(separator30, lcset_out)) - plainmsg.write('\n') - plainmsg.write(to_cset_out(_('Message %d\n' % msg_num), lcset_out)) - plainmsg.write(to_cset_out(separator30, lcset_out)) - plainmsg.write('\n') - - # Add message metadata - for header in ('date', 'from', 'subject'): - value = decode_header_value(msg.get(header, ''), lcset) - plainmsg.write('%s: %s\n' % (header.capitalize(), to_cset_out(value, lcset_out))) - plainmsg.write('\n') - - # Add message body - try: - body = process_message_body(msg, lcset) - plainmsg.write(to_cset_out(body, lcset_out)) - plainmsg.write('\n') - except Exception as e: - plainmsg.write(to_cset_out(_('[Message body could not be decoded]\n'), lcset_out)) - syslog('error', 'Message %d digest payload error: %s', msg_num, str(e)) - - msg_num += 1 - except Exception as e: - syslog('error', 'Digest message %d processing error: %s', msg_num, str(e)) - syslog('error', 'Traceback: %s', traceback.format_exc()) - current_msg = [line] - else: - current_msg.append(line) - - # Process the last message - if current_msg: - msg_str = ''.join(current_msg) - try: - msg = message_from_string(msg_str) - if msg is not None: - # Process the last message (same code as above) - subject = decode_header_value(msg.get('subject', _('(no subject)')), lcset) - subject = Utils.oneline(subject, lcset) - plainmsg.write('%2d. %s\n' % (msg_num, to_cset_out(subject, lcset_out))) - mimemsg.attach(MIMEMessage(msg)) - plainmsg.write('\n') - plainmsg.write(to_cset_out(separator30, lcset_out)) - plainmsg.write('\n') - plainmsg.write(to_cset_out(_('Message %d\n' % msg_num), lcset_out)) - plainmsg.write(to_cset_out(separator30, lcset_out)) - plainmsg.write('\n') - for header in ('date', 'from', 'subject'): - value = decode_header_value(msg.get(header, ''), lcset) - plainmsg.write('%s: %s\n' % (header.capitalize(), to_cset_out(value, lcset_out))) - plainmsg.write('\n') - try: - body = process_message_body(msg, lcset) - plainmsg.write(to_cset_out(body, lcset_out)) - plainmsg.write('\n') - except Exception as e: - plainmsg.write(to_cset_out(_('[Message body could not be decoded]\n'), lcset_out)) - syslog('error', 'Message %d digest payload error: %s', msg_num, str(e)) - except Exception as e: - syslog('error', 'Digest message %d processing error: %s', msg_num, str(e)) - syslog('error', 'Traceback: %s', traceback.format_exc()) - except Exception as e: - syslog('error', 'Error reading digest mbox file: %s', str(e)) - syslog('error', 'Traceback: %s', traceback.format_exc()) + 'got_owner_email': mlist.GetOwnerEmail(), + }, mlist=mlist) + # MIME + masthead = MIMEText(mastheadtxt, _charset=lcset) + masthead['Content-Description'] = digestid + mimemsg.attach(masthead) + # RFC 1153 + print(mastheadtxt, file=plainmsg) + print(file=plainmsg) + # Now add the optional digest header but only if more than whitespace. + if re.sub(r'\s', '', mlist.digest_header): + lc_digest_header_msg = _('digest header') + if isinstance(lc_digest_header_msg, bytes): + lc_digest_header_msg = str(lc_digest_header_msg) + headertxt = decorate(mlist, mlist.digest_header, lc_digest_header_msg) + # MIME + header = MIMEText(headertxt, _charset=lcset) + header['Content-Description'] = _('Digest Header') + mimemsg.attach(header) + # RFC 1153 + print(headertxt, file=plainmsg) + print(file=plainmsg) + # Now we have to cruise through all the messages accumulated in the + # mailbox file. We can't add these messages to the plainmsg and mimemsg + # yet, because we first have to calculate the table of contents + # (i.e. grok out all the Subjects). Store the messages in a list until + # we're ready for them. + # + # Meanwhile prepare things for the table of contents + toc = StringIO() + start_toc = _("Today's Topics:\n") + if isinstance(start_toc, bytes): + start_toc = str(start_toc) + print(start_toc, file=toc) + # Now cruise through all the messages in the mailbox of digest messages, + # building the MIME payload and core of the RFC 1153 digest. We'll also + # accumulate Subject: headers and authors for the table-of-contents. + messages = [] + msgcount = 0 + mbox = mbox.itervalues() + msg = next(mbox, None) + while msg is not None: + if msg == '': + # It was an unparseable message + msg = next(mbox, None) + continue + msgcount += 1 + messages.append(msg) + # Get the Subject header + no_subject_locale = _('(no subject)') + if isinstance(no_subject_locale, bytes): + no_subject_locale = str(no_subject_locale) + msgsubj = msg.get('subject', no_subject_locale) + subject = Utils.oneline(msgsubj, lcset) + # Don't include the redundant subject prefix in the toc + mo = re.match('(re:? *)?(%s)' % re.escape(mlist.subject_prefix), + subject, re.IGNORECASE) + if mo: + subject = subject[:mo.start(2)] + subject[mo.end(2):] + username = '' + addresses = getaddresses([Utils.oneline(msg.get('from', ''), lcset)]) + # Take only the first author we find + if isinstance(addresses, list) and addresses: + username = addresses[0][0] + if not username: + username = addresses[0][1] + if username: + username = ' (%s)' % username + # Put count and Wrap the toc subject line + if isinstance(subject, bytes): + subject = str(subject) + wrapped = Utils.wrap('%2d. %s' % (msgcount, subject), 65) + slines = wrapped.split('\n') + # See if the user's name can fit on the last line + if len(slines[-1]) + len(username) > 70: + slines.append(username) + else: + slines[-1] += username + # Add this subject to the accumulating topics + first = True + for line in slines: + if first: + print(' ', line, file=toc) + first = False + else: + print(' ', line.lstrip(), file=toc) + # We do not want all the headers of the original message to leak + # through in the digest messages. For this phase, we'll leave the + # same set of headers in both digests, i.e. those required in RFC 1153 + # plus a couple of other useful ones. We also need to reorder the + # headers according to RFC 1153. Later, we'll strip out headers for + # for the specific MIME or plain digests. + keeper = {} + all_keepers = {} + for header in (mm_cfg.MIME_DIGEST_KEEP_HEADERS + + mm_cfg.PLAIN_DIGEST_KEEP_HEADERS): + all_keepers[header] = True + all_keepers = list(all_keepers.keys()) + for keep in all_keepers: + keeper[keep] = msg.get_all(keep, []) + # Now remove all unkempt headers :) + for header in list(msg.keys()): + del msg[header] + # And add back the kept header in the RFC 1153 designated order + for keep in all_keepers: + for field in keeper[keep]: + msg[keep] = field + # And a bit of extra stuff + msg['Message'] = repr(msgcount) + # Get the next message in the digest mailbox + msg = next(mbox, None) + # Now we're finished with all the messages in the digest. First do some + # sanity checking and then on to adding the toc. + if msgcount == 0: + # Why did we even get here? return - - # Finish up the RFC 1153 digest - plainmsg.write('\n') - plainmsg.write(to_cset_out(separator70, lcset_out)) - plainmsg.write('\n') - plainmsg.write(to_cset_out(_('End of Digest\n'), lcset_out)) - - # Set the RFC 1153 message body - rfc1153msg.set_payload(plainmsg.getvalue(), charset=lcset) - plainmsg.close() - - # Send both digests - send_digest_final(mlist, mimemsg, rfc1153msg, volume, issue) - - # Clean up - mlist.next_digest_number += 1 - mlist.Save() - - # Remove the mbox file - try: - os.unlink(mboxpath) - except OSError as e: - syslog('error', 'Failed to remove digest.mbox: %s', str(e)) + toctext = toc.getvalue() + toctext_encoded = to_cset_out(toctext, lcset) + # MIME + tocpart = MIMEText(toctext, _charset=lcset) + tocpart['Content-Description']= _("Today's Topics (%(msgcount)d messages)") + mimemsg.attach(tocpart) + # RFC 1153 + print(toctext, file=plainmsg) + print(file=plainmsg) + # For RFC 1153 digests, we now need the standard separator + print(separator70, file=plainmsg) + print(file=plainmsg) + # Now go through and add each message + mimedigest = MIMEBase('multipart', 'digest') + mimemsg.attach(mimedigest) + first = True + for msg in messages: + # MIME. Make a copy of the message object since the rfc1153 + # processing scrubs out attachments. + mimedigest.attach(MIMEMessage(copy.deepcopy(msg))) + # rfc1153 + if first: + first = False + else: + print(separator30, file=plainmsg) + print(file=plainmsg) + # Use Mailman.Handlers.Scrubber.process() to get plain text + try: + msg = scrubber(mlist, msg) + except Errors.DiscardMessage: + discard_msg = _('[Message discarded by content filter]') + if isinstance(discard_msg, bytes): + discard_msg = str(discard_msg) + print(discard_msg, file=plainmsg) + continue + # Honor the default setting + for h in mm_cfg.PLAIN_DIGEST_KEEP_HEADERS: + if msg[h]: + uh = Utils.wrap('%s: %s' % (h, Utils.oneline(msg[h], lcset))) + uh = '\n\t'.join(uh.split('\n')) + print(uh, file=plainmsg) + print(file=plainmsg) + # If decoded payload is empty, this may be multipart message. + # -- just stringfy it. + payload = msg.get_payload(decode=True) + if payload == None: + payload = msg.as_string().split('\n\n',1)[1] + mcset = msg.get_content_charset('') + if mcset == None or mcset == "": + mcset = 'utf-8' + if isinstance(payload, bytes): + payload = payload.decode(mcset, 'replace') + print(payload, file=plainmsg) + if not payload.endswith('\n'): + print(file=plainmsg) + + # Now add the footer but only if more than whitespace. + if re.sub(r'\s', '', mlist.digest_footer): + lc_digest_footer_msg = _('digest footer') + if isinstance(lc_digest_footer_msg, bytes): + lc_digest_footer_msg = str(lc_digest_footer_msg) + footertxt = decorate(mlist, mlist.digest_footer, lc_digest_footer_msg) + # MIME + footer = MIMEText(footertxt, _charset=lcset) + footer['Content-Description'] = _('Digest Footer') + mimemsg.attach(footer) + # RFC 1153 + # MAS: There is no real place for the digest_footer in an RFC 1153 + # compliant digest, so add it as an additional message with + # Subject: Digest Footer + print(separator30, file=plainmsg) + print(file=plainmsg) -def send_digest_final(mlist, mimemsg, rfc1153msg, volume, issue): - """Send the actual digest messages with performance optimizations.""" - # Get digest recipients in batches - batch_size = 1000 # Process 1000 recipients at a time - - # Send to MIME digest members - mime_members = mlist.getDigestMemberKeys() - if mime_members: - mime_members = mlist.getMemberCPAddresses(mime_members) - outq = get_switchboard(mm_cfg.OUTQUEUE_DIR) - # Process in batches to avoid memory issues - for i in range(0, len(mime_members), batch_size): - batch = mime_members[i:i + batch_size] - syslog('info', 'Sending MIME digest batch %d-%d for list %s', - i, i + len(batch), mlist.internal_name()) - outq.enqueue(mimemsg, - recips=batch, - listname=mlist.internal_name(), - fromnode='digest') - - # Send to RFC 1153 digest members - rfc1153_members = mlist.getDigestMemberKeys() - if rfc1153_members: - rfc1153_members = mlist.getMemberCPAddresses(rfc1153_members) - outq = get_switchboard(mm_cfg.OUTQUEUE_DIR) - # Process in batches to avoid memory issues - for i in range(0, len(rfc1153_members), batch_size): - batch = rfc1153_members[i:i + batch_size] - syslog('info', 'Sending RFC 1153 digest batch %d-%d for list %s', - i, i + len(batch), mlist.internal_name()) - outq.enqueue(rfc1153msg, - recips=batch, - listname=mlist.internal_name(), - fromnode='digest') + digest_footer_msg = _('Digest Footer') + if isinstance(digest_footer_msg, bytes): + digest_footer_msg = str(digest_footer_msg) + print('Subject: ' + digest_footer_msg, file=plainmsg) + print(file=plainmsg) + print(footertxt, file=plainmsg) + print(file=plainmsg) + print(separator30, file=plainmsg) + print(file=plainmsg) + # Do the last bit of stuff for each digest type + signoff = _('End of ') + digestid + # MIME + # BAW: This stuff is outside the normal MIME goo, and it's what the old + # MIME digester did. No one seemed to complain, probably because you + # won't see it in an MUA that can't display the raw message. We've never + # got complaints before, but if we do, just wax this. It's primarily + # included for (marginally useful) backwards compatibility. + mimemsg.postamble = signoff + # rfc1153 + print(signoff, file=plainmsg) + print('*' * len(signoff), file=plainmsg) + # Do our final bit of housekeeping, and then send each message to the + # outgoing queue for delivery. + mlist.next_digest_number += 1 + virginq = get_switchboard(mm_cfg.VIRGINQUEUE_DIR) + # Calculate the recipients lists + plainrecips = [] + mimerecips = [] + drecips = mlist.getDigestMemberKeys() + list(mlist.one_last_digest.keys()) + for user in mlist.getMemberCPAddresses(drecips): + # user might be None if someone who toggled off digest delivery + # subsequently unsubscribed from the mailing list. Also, filter out + # folks who have disabled delivery. + if user is None or mlist.getDeliveryStatus(user) != ENABLED: + continue + # Otherwise, decide whether they get MIME or RFC 1153 digests + if mlist.getMemberOption(user, mm_cfg.DisableMime): + plainrecips.append(user) + else: + mimerecips.append(user) + # Zap this since we're now delivering the last digest to these folks. + mlist.one_last_digest.clear() + # MIME + virginq.enqueue(mimemsg, + recips=mimerecips, + listname=mlist.internal_name(), + isdigest=True) + # RFC 1153 + rfc1153msg.set_payload(plainmsg.getvalue(), 'utf-8') + virginq.enqueue(rfc1153msg, + recips=plainrecips, + listname=mlist.internal_name(), + isdigest=True) diff --git a/Mailman/Handlers/ToOutgoing.py b/Mailman/Handlers/ToOutgoing.py index 123b8859..d4f13fd5 100644 --- a/Mailman/Handlers/ToOutgoing.py +++ b/Mailman/Handlers/ToOutgoing.py @@ -23,67 +23,33 @@ from Mailman import mm_cfg from Mailman.Queue.sbcache import get_switchboard -import traceback -from Mailman.Logging.Syslog import mailman_log + + def process(mlist, msg, msgdata): - """Process the message by moving it to the outgoing queue.""" - msgid = msg.get('message-id', 'n/a') - - # Log the start of processing with enhanced details - mailman_log('debug', 'ToOutgoing: Starting to process message %s for list %s', - msgid, mlist.internal_name()) - mailman_log('debug', 'ToOutgoing: Message details:') - mailman_log('debug', ' Message ID: %s', msgid) - mailman_log('debug', ' From: %s', msg.get('from', 'unknown')) - mailman_log('debug', ' To: %s', msg.get('to', 'unknown')) - mailman_log('debug', ' Subject: %s', msg.get('subject', '(no subject)')) - mailman_log('debug', ' Message type: %s', type(msg).__name__) - mailman_log('debug', ' Message data: %s', str(msgdata)) - mailman_log('debug', ' Pipeline: %s', msgdata.get('pipeline', 'No pipeline')) - - # Get the outgoing queue - try: - mailman_log('debug', 'ToOutgoing: Getting outgoing queue for message %s', msgid) - outgoingq = get_switchboard(mm_cfg.OUTQUEUE_DIR) - mailman_log('debug', 'ToOutgoing: Successfully got outgoing queue for message %s', msgid) - except Exception as e: - mailman_log('error', 'ToOutgoing: Failed to get outgoing queue for message %s: %s', msgid, str(e)) - mailman_log('error', 'ToOutgoing: Traceback:\n%s', traceback.format_exc()) - raise - - # Get recipients from msgdata first, then fall back to message headers - recips = msgdata.get('recips', []) - if not recips: - # Try to get from message headers - recips = msg.get_all('to', []) + msg.get_all('cc', []) - if not recips: - # If still no recipients, get from list membership - recips = [mlist.GetMemberEmail() for member in mlist.GetMemberCPAddresses()] - mailman_log('debug', 'ToOutgoing: No recipients found in msgdata or headers, using list members for message %s', msgid) - - # Ensure we have at least one recipient - if not recips: - mailman_log('error', 'ToOutgoing: No recipients found for message %s', msgid) - raise ValueError('No recipients found for message') - - # Add the message to the outgoing queue - try: - mailman_log('debug', 'ToOutgoing: Attempting to enqueue message %s for list %s', - msgid, mlist.internal_name()) - # Ensure recipients are preserved in msgdata - msgdata['recips'] = recips - msgdata['recipient'] = recips[0] if recips else None - - # Log the full msgdata before enqueueing - mailman_log('debug', 'ToOutgoing: Full msgdata before enqueue:\n%s', str(msgdata)) - - outgoingq.enqueue(msg, msgdata, - listname=mlist.internal_name()) - mailman_log('debug', 'ToOutgoing: Successfully queued message %s for list %s', - msgid, mlist.internal_name()) - mailman_log('debug', 'ToOutgoing: Message %s is now in outgoing queue', msgid) - except Exception as e: - mailman_log('error', 'ToOutgoing: Failed to enqueue message %s: %s', msgid, str(e)) - mailman_log('error', 'ToOutgoing: Traceback:\n%s', traceback.format_exc()) - raise + interval = mm_cfg.VERP_DELIVERY_INTERVAL + # Should we VERP this message? If personalization is enabled for this + # list and VERP_PERSONALIZED_DELIVERIES is true, then yes we VERP it. + # Also, if personalization is /not/ enabled, but VERP_DELIVERY_INTERVAL is + # set (and we've hit this interval), then again, this message should be + # VERPed. Otherwise, no. + # + # Note that the verp flag may already be set, e.g. by mailpasswds using + # VERP_PASSWORD_REMINDERS. Preserve any existing verp flag. + if 'verp' in msgdata: + pass + elif mlist.personalize: + if mm_cfg.VERP_PERSONALIZED_DELIVERIES: + msgdata['verp'] = 1 + elif interval == 0: + # Never VERP + pass + elif interval == 1: + # VERP every time + msgdata['verp'] = 1 + else: + # VERP every `inteval' number of times + msgdata['verp'] = not int(mlist.post_id) % interval + # And now drop the message in qfiles/out + outq = get_switchboard(mm_cfg.OUTQUEUE_DIR) + outq.enqueue(msg, msgdata, listname=mlist.internal_name()) diff --git a/Mailman/Handlers/ToUsenet.py b/Mailman/Handlers/ToUsenet.py index 32aed559..26b5ecfa 100644 --- a/Mailman/Handlers/ToUsenet.py +++ b/Mailman/Handlers/ToUsenet.py @@ -22,6 +22,7 @@ COMMASPACE = ', ' + def process(mlist, msg, msgdata): # short circuits if not mlist.gateway_to_news or \ @@ -40,6 +41,4 @@ def process(mlist, msg, msgdata): return # Put the message in the news runner's queue newsq = get_switchboard(mm_cfg.NEWSQUEUE_DIR) - newsq.enqueue(msg, msgdata, - listname=mlist.internal_name(), - recipient=mlist.nntp_host) # Set NNTP host as recipient + newsq.enqueue(msg, msgdata, listname=mlist.internal_name()) diff --git a/Mailman/Handlers/__init__.py b/Mailman/Handlers/__init__.py index 19d54e8b..b271f895 100644 --- a/Mailman/Handlers/__init__.py +++ b/Mailman/Handlers/__init__.py @@ -13,30 +13,3 @@ # 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. - -"""Mailman message handlers. - -This package contains the message handlers for Mailman's pipeline architecture. -Each handler module must define a process() function which takes three arguments: - mlist - The MailList instance - msg - The Message instance - msgdata - A dictionary of message metadata -""" - -from __future__ import absolute_import, print_function, unicode_literals - -# Define lazy imports to avoid circular dependencies -def get_handler(name): - """Get a handler module by name.""" - return __import__('Mailman.Handlers.' + name, fromlist=['Mailman.Handlers']) - -# Define handler names for reference -HANDLER_NAMES = [ - 'SpamDetect', 'Approve', 'Replybot', 'Moderate', 'Hold', 'MimeDel', 'Scrubber', - 'Emergency', 'Tagger', 'CalcRecips', 'AvoidDuplicates', 'Cleanse', 'CleanseDKIM', - 'CookHeaders', 'ToDigest', 'ToArchive', 'ToUsenet', 'AfterDelivery', 'Acknowledge', - 'WrapMessage', 'ToOutgoing', 'OwnerRecips' -] - -# Export handler names -__all__ = HANDLER_NAMES + ['get_handler'] diff --git a/Mailman/ListAdmin.py b/Mailman/ListAdmin.py index cda53d82..f8fad6d2 100644 --- a/Mailman/ListAdmin.py +++ b/Mailman/ListAdmin.py @@ -23,39 +23,38 @@ elsewhere. """ -from builtins import str, object +from builtins import str +from builtins import object import os import time import errno import pickle import marshal from io import StringIO -import socket -import pwd -import grp -import traceback import email from email.mime.message import MIMEMessage +from email.generator import BytesGenerator from email.generator import Generator from email.utils import getaddresses -import email.message -from email.message import Message as EmailMessage +from email.message import EmailMessage +from email.parser import Parser +from email import policy from Mailman import mm_cfg from Mailman import Utils -import Mailman.Message as Message +from Mailman import Message from Mailman import Errors from Mailman.UserDesc import UserDesc from Mailman.Queue.sbcache import get_switchboard -from Mailman.Logging.Syslog import mailman_log +from Mailman.Logging.Syslog import syslog from Mailman import i18n _ = i18n._ def D_(s): return s -# Constants for request types +# Request types requiring admin approval IGN = 0 HELDMSG = 1 SUBSCRIPTION = 2 @@ -69,12 +68,7 @@ def D_(s): DASH = '-' NL = '\n' -class PermissionError(Exception): - """Exception raised when there are permission issues with database operations.""" - def __init__(self, message): - self.message = message - super().__init__(message) - + class ListAdmin(object): def InitVars(self): # non-configurable data @@ -85,173 +79,77 @@ def InitTempVars(self): self.__filename = os.path.join(self.fullpath(), 'request.pck') def __opendb(self): - """Open the database file.""" - filename = os.path.join(self.fullpath(), 'request.pck') - filename_backup = filename + '.bak' - - # Try loading the main file first - try: - with open(filename, 'rb') as fp: + if self.__db is None: + assert self.Locked() + try: + fp = open(self.__filename, 'rb') try: - # Try UTF-8 first for newer files - self.__db = pickle.load(fp, fix_imports=True, encoding='utf-8') - except (UnicodeDecodeError, pickle.UnpicklingError): - # Fall back to latin1 for older files - fp.seek(0) - self.__db = pickle.load(fp, fix_imports=True, encoding='latin1') - except (pickle.UnpicklingError, EOFError, ValueError, TypeError) as e: - mailman_log('error', 'Error loading request.pck for list %s: %s\n%s', - self.internal_name(), str(e), traceback.format_exc()) - # Try backup if main file failed - if os.path.exists(filename_backup): - mailman_log('info', 'Attempting to load from backup file') - with open(filename_backup, 'rb') as backup_fp: - try: - # Try UTF-8 first for newer files - self.__db = pickle.load(backup_fp, fix_imports=True, encoding='utf-8') - except (UnicodeDecodeError, pickle.UnpicklingError): - # Fall back to latin1 for older files - backup_fp.seek(0) - self.__db = pickle.load(backup_fp, fix_imports=True, encoding='latin1') - mailman_log('info', 'Successfully loaded backup request.pck for list %s', - self.internal_name()) - # Successfully loaded backup, restore it as main - import shutil - shutil.copy2(filename_backup, filename) - else: + self.__db = Utils.load_pickle(fp) + if not self.__db: + raise IOError("Pickled data is empty or None") + finally: + fp.close() + except IOError as e: + if e.errno != errno.ENOENT: raise self.__db = {} + # put version number in new database + self.__db['version'] = IGN, mm_cfg.REQUESTS_FILE_SCHEMA_VERSION - def __savedb(self): - """Save the database file.""" - if not self.__db: - return - - filename = os.path.join(self.fullpath(), 'request.pck') - filename_tmp = filename + '.tmp.%s.%d' % (socket.gethostname(), os.getpid()) - filename_backup = filename + '.bak' - - # First create a backup of the current file if it exists - if os.path.exists(filename): + def __closedb(self): + if self.__db is not None: + assert self.Locked() + # Save the version number + self.__db['version'] = IGN, mm_cfg.REQUESTS_FILE_SCHEMA_VERSION + # Now save a temp file and do the tmpfile->real file dance. BAW: + # should we be as paranoid as for the config.pck file? Should we + # use pickle? + tmpfile = self.__filename + '.tmp' + omask = os.umask(0o007) try: - import shutil - shutil.copy2(filename, filename_backup) - except IOError as e: - mailman_log('error', 'Error creating backup: %s', str(e)) - - # Save to temporary file first - try: - # Ensure directory exists - dirname = os.path.dirname(filename) - if not os.path.exists(dirname): - os.makedirs(dirname, 0o755) - - with open(filename_tmp, 'wb') as fp: - # Use protocol 4 for Python 2/3 compatibility - pickle.dump(self.__db, fp, protocol=4, fix_imports=True) - fp.flush() - if hasattr(os, 'fsync'): + fp = open(tmpfile, 'wb') + try: + pickle.dump(self.__db, fp, 1) + fp.flush() os.fsync(fp.fileno()) - - # Atomic rename - os.rename(filename_tmp, filename) - - except (IOError, OSError) as e: - mailman_log('error', 'Error saving request.pck: %s', str(e)) - # Try to clean up - try: - os.unlink(filename_tmp) - except OSError: - pass - raise - - def __validate_and_clean_db(self): - """Validate database entries and clean up invalid ones.""" - if not self.__db: - return - - now = time.time() - to_delete = [] - - for key, value in self.__db.items(): - try: - # Check if value is a valid tuple/list with at least 2 elements - if not isinstance(value, (tuple, list)) or len(value) < 2: - to_delete.append(key) - continue - - # Check if timestamp is valid - timestamp = value[1] - if not isinstance(timestamp, (int, float)) or timestamp < 0: - to_delete.append(key) - continue - - # Remove expired entries - if timestamp < now: - to_delete.append(key) - continue - - except (TypeError, IndexError): - to_delete.append(key) - - # Remove invalid entries - for key in to_delete: - del self.__db[key] + finally: + fp.close() + finally: + os.umask(omask) + self.__db = None + # Do the dance + os.rename(tmpfile, self.__filename) + + def __nextid(self): + assert self.Locked() + while True: + next = self.next_request_id + self.next_request_id += 1 + if next not in self.__db: + break + return next def SaveRequestsDb(self): - """Save the requests database with validation.""" - if self.__db is not None: - self.__validate_and_clean_db() - self.__savedb() + self.__closedb() def NumRequestsPending(self): self.__opendb() - if not self.__db: - return 0 - # For Python 2 pickles, the version pseudo-entry might not exist - # Just return the length of the dictionary - return len(self.__db) + # Subtract one for the version pseudo-entry + return len(self.__db) - 1 def __getmsgids(self, rtype): self.__opendb() ids = [k for k, (op, data) in list(self.__db.items()) if op == rtype] - ids.sort() + ids.sort(key=int) return ids def GetHeldMessageIds(self): - try: - self.__opendb() - ids = [k for k, (op, data) in list(self.__db.items()) if op == HELDMSG] - ids.sort() - return ids - except Exception as e: - mailman_log('error', 'Error getting held message IDs: %s\n%s', - str(e), traceback.format_exc()) - # Return empty list on error to prevent cascading failures - return [] + return self.__getmsgids(HELDMSG) def GetSubscriptionIds(self): - try: - self.__opendb() - ids = [k for k, (op, data) in list(self.__db.items()) if op == SUBSCRIPTION] - ids.sort() - return ids - except Exception as e: - mailman_log('error', 'Error getting subscription IDs: %s\n%s', - str(e), traceback.format_exc()) - # Return empty list on error to prevent cascading failures - return [] + return self.__getmsgids(SUBSCRIPTION) def GetUnsubscriptionIds(self): - try: - self.__opendb() - ids = [k for k, (op, data) in list(self.__db.items()) if op == UNSUBSCRIPTION] - ids.sort() - return ids - except Exception as e: - mailman_log('error', 'Error getting unsubscription IDs: %s\n%s', - str(e), traceback.format_exc()) - # Return empty list on error to prevent cascading failures - return [] + return self.__getmsgids(UNSUBSCRIPTION) def GetRecord(self, id): self.__opendb() @@ -273,8 +171,7 @@ def HandleRequest(self, id, value, comment=None, preserve=None, elif rtype == UNSUBSCRIPTION: status = self.__handleunsubscription(data, value, comment) else: - if rtype != SUBSCRIPTION: - raise ValueError(f'Invalid request type: {rtype}, expected {SUBSCRIPTION}') + assert rtype == SUBSCRIPTION status = self.__handlesubscription(data, value, comment) if status != DEFER: # BAW: Held message ids are linked to Pending cookies, allowing @@ -305,7 +202,7 @@ def HoldMessage(self, msg, reason, msgdata={}): fp = open(os.path.join(mm_cfg.DATA_DIR, filename), 'wb') try: if mm_cfg.HOLD_MESSAGES_AS_PICKLES: - pickle.dump(msg, fp, protocol=4, fix_imports=True) + pickle.dump(msg, fp, 1) else: g = Generator(fp) g.flatten(msg, 1) @@ -329,7 +226,7 @@ def HoldMessage(self, msg, reason, msgdata={}): msgsubject = msg.get('subject', _('(no subject)')) if not sender: sender = _('') - data = (time.time(), sender, msgsubject, reason, filename, msgdata) + data = time.time(), sender, msgsubject, reason, filename, msgdata self.__db[id] = (HELDMSG, data) return id @@ -350,26 +247,37 @@ def __handlepost(self, record, value, comment, preserve, forward, addr): return LOST try: if path.endswith('.pck'): - msg = pickle.load(fp, fix_imports=True, encoding='latin1') + msg = Utils.load_pickle(path) else: - if not path.endswith('.txt'): - raise ValueError(f'Invalid file extension: {path} must end with .txt') + assert path.endswith('.txt'), '%s not .pck or .txt' % path msg = fp.read() finally: fp.close() + + # If msg is still a Message from Python 2 pickle, convert it + if isinstance(msg, email.message.Message): + if not hasattr(msg, 'policy'): + msg.policy = email._policybase.compat32 + if not hasattr(msg, 'mangle_from_'): + msg.mangle_from_ = True + if not hasattr(msg, 'linesep'): + msg.linesep = email.policy.default.linesep + # Save the plain text to a .msg file, not a .pck file outpath = os.path.join(mm_cfg.SPAM_DIR, spamfile) head, ext = os.path.splitext(outpath) outpath = head + '.msg' - outfp = open(outpath, 'wb') - try: - if path.endswith('.pck'): - g = Generator(outfp) - g.flatten(msg, 1) - else: - outfp.write(msg) - finally: - outfp.close() + + with open(outpath, 'w', encoding='utf-8') as outfp: + try: + if path.endswith('.pck'): + g = Generator(outfp, policy=msg.policy) + g.flatten(msg, 1) + else: + outfp.write(msg.get_payload(decode=True).decode() if isinstance(msg.get_payload(decode=True), bytes) else msg.get_payload()) + except Exception as e: + raise Errors.LostHeldMessage(path) + # Now handle updates to the database rejection = None fp = None @@ -381,23 +289,11 @@ def __handlepost(self, record, value, comment, preserve, forward, addr): elif value == mm_cfg.APPROVE: # Approved. try: - msg = email.message_from_file(fp, EmailMessage) + msg = readMessage(path) except IOError as e: if e.errno != errno.ENOENT: raise return LOST - # Convert to Mailman.Message if needed - if isinstance(msg, EmailMessage) and not isinstance(msg, Message): - mailman_msg = Message() - # Copy all attributes from the original message - for key, value in msg.items(): - mailman_msg[key] = value - # Copy the payload - if msg.is_multipart(): - for part in msg.get_payload(): - mailman_msg.attach(part) - else: - mailman_msg.set_payload(msg.get_payload()) - msg = mailman_msg + msg = readMessage(path) msgdata['approved'] = 1 # adminapproved is used by the Emergency handler msgdata['adminapproved'] = 1 @@ -411,24 +307,23 @@ def __handlepost(self, record, value, comment, preserve, forward, addr): # message directly here can lead to a huge delay in web # turnaround. Log the moderation and add a header. msg['X-Mailman-Approved-At'] = email.utils.formatdate(localtime=1) - mailman_log('vette', '%s: held message approved, message-id: %s', + syslog('vette', '%s: held message approved, message-id: %s', self.internal_name(), msg.get('message-id', 'n/a')) # Stick the message back in the incoming queue for further # processing. inq = get_switchboard(mm_cfg.INQUEUE_DIR) - inq.enqueue(msg, msgdata=msgdata) + inq.enqueue(msg, _metadata=msgdata) elif value == mm_cfg.REJECT: # Rejected rejection = 'Refused' lang = self.getMemberLanguage(sender) subject = Utils.oneline(subject, Utils.GetCharSet(lang)) - self.__refuse(_('Posting of your message titled "%(subject)s"'), + self.__refuse(_(f'Posting of your message titled "{subject}"'), sender, comment or _('[No reason given]'), lang=lang) else: - if value != mm_cfg.DISCARD: - raise ValueError(f'Invalid value: {value}, expected {mm_cfg.DISCARD}') + assert value == mm_cfg.DISCARD # Discarded rejection = 'Discarded' # Forward the message @@ -438,23 +333,10 @@ def __handlepost(self, record, value, comment, preserve, forward, addr): # since we don't want to share any state or information with the # normal delivery. try: - copy = email.message_from_file(fp, EmailMessage) + copy = readMessage(path) except IOError as e: if e.errno != errno.ENOENT: raise raise Errors.LostHeldMessage(path) - # Convert to Mailman.Message if needed - if isinstance(copy, EmailMessage) and not isinstance(copy, Message): - mailman_msg = Message() - # Copy all attributes from the original message - for key, value in copy.items(): - mailman_msg[key] = value - # Copy the payload - if copy.is_multipart(): - for part in copy.get_payload(): - mailman_msg.attach(part) - else: - mailman_msg.set_payload(copy.get_payload()) - copy = mailman_msg # It's possible the addr is a comma separated list of addresses. addrs = getaddresses([addr]) if len(addrs) == 1: @@ -485,17 +367,14 @@ def __handlepost(self, record, value, comment, preserve, forward, addr): fmsg.send(self) # Log the rejection if rejection: - note = '''%(listname)s: %(rejection)s posting: -\tFrom: %(sender)s -\tSubject: %(subject)s''' % { - 'listname' : self.internal_name(), - 'rejection': rejection, - 'sender' : str(sender).replace('%', '%%'), - 'subject' : str(subject).replace('%', '%%'), - } + if isinstance(subject, bytes): + subject = subject.decode() + note = '''{}: {} posting: +\tFrom: {} +\tSubject: {}'''.format(self.real_name, rejection, sender.replace('%', '%%'), subject.replace('%', '%%')) if comment: note += '\n\tReason: ' + comment.replace('%', '%%') - mailman_log('vette', note) + syslog('vette', note) # Always unlink the file containing the message text. It's not # necessary anymore, regardless of the disposition of the message. if status != DEFER: @@ -527,16 +406,14 @@ def HoldSubscription(self, addr, fullname, password, digest, lang): # # TBD: this really shouldn't go here but I'm not sure where else is # appropriate. - mailman_log('vette', '%s: held subscription request from %s', + syslog('vette', '%s: held subscription request from %s', self.internal_name(), addr) # Possibly notify the administrator in default list language if self.admin_immed_notify: i18n.set_language(self.preferred_language) realname = self.real_name - subject = _('New subscription request to list %(realname)s from %(addr)s') % { - 'realname': realname, - 'addr': addr - } + subject = _( + 'New subscription request to list %(realname)s from %(addr)s') text = Utils.maketext( 'subauth.txt', {'username' : addr, @@ -553,36 +430,36 @@ def HoldSubscription(self, addr, fullname, password, digest, lang): # Restore the user's preferred language. i18n.set_language(lang) - def __handlesubscription(self, data, value, comment): - """Handle a subscription request. - - Args: - data: A tuple of (userdesc, remote) where userdesc is a UserDesc object - and remote is the remote address making the request - value: The action to take (APPROVE, DEFER, REJECT) - comment: Optional comment for the action - - Returns: - The status of the action (APPROVE, DEFER, REJECT) - """ - userdesc, remote = data - if value == mm_cfg.APPROVE: - self.ApprovedAddMember(userdesc, whence=remote or '') - return mm_cfg.APPROVE + def __handlesubscription(self, record, value, comment): + global _ + stime, addr, fullname, password, digest, lang = record + if value == mm_cfg.DEFER: + return DEFER + elif value == mm_cfg.DISCARD: + syslog('vette', '%s: discarded subscription request from %s', + self.internal_name(), addr) elif value == mm_cfg.REJECT: - # Send rejection notice - lang = userdesc.language - text = Utils.maketext( - 'reject.txt', - {'listname': self.real_name, - 'comment': comment or '', - }, lang=lang, mlist=self) - msg = Message.UserNotification( - userdesc.address, self.GetRequestEmail(), - text=text, lang=lang) - msg.send(self) - return mm_cfg.REJECT - return mm_cfg.DEFER + self.__refuse(_('Subscription request'), addr, + comment or _('[No reason given]'), + lang=lang) + syslog('vette', """%s: rejected subscription request from %s +\tReason: %s""", self.internal_name(), addr, comment or '[No reason given]') + else: + # subscribe + assert value == mm_cfg.SUBSCRIBE + try: + _ = D_ + whence = _('via admin approval') + _ = i18n._ + userdesc = UserDesc(addr, fullname, password, digest, lang) + self.ApprovedAddMember(userdesc, whence=whence) + except Errors.MMAlreadyAMember: + # User has already been subscribed, after sending the request + pass + # TBD: disgusting hack: ApprovedAddMember() can end up closing + # the request database. + self.__opendb() + return REMOVE def HoldUnsubscription(self, addr): # Assure the database is open for writing @@ -591,15 +468,13 @@ def HoldUnsubscription(self, addr): id = self.__nextid() # All we need to do is save the unsubscribing address self.__db[id] = (UNSUBSCRIPTION, addr) - mailman_log('vette', '%s: held unsubscription request from %s', + syslog('vette', '%s: held unsubscription request from %s', self.internal_name(), addr) # Possibly notify the administrator of the hold if self.admin_immed_notify: realname = self.real_name - subject = _('New unsubscription request from %(realname)s by %(addr)s') % { - 'realname': realname, - 'addr': addr - } + subject = _( + 'New unsubscription request from %(realname)s by %(addr)s') text = Utils.maketext( 'unsubauth.txt', {'username' : addr, @@ -617,17 +492,16 @@ def HoldUnsubscription(self, addr): def __handleunsubscription(self, record, value, comment): addr = record if value == mm_cfg.DEFER: - return mm_cfg.DEFER + return DEFER elif value == mm_cfg.DISCARD: - mailman_log('vette', '%s: discarded unsubscription request from %s', + syslog('vette', '%s: discarded unsubscription request from %s', self.internal_name(), addr) elif value == mm_cfg.REJECT: self.__refuse(_('Unsubscription request'), addr, comment) - mailman_log('vette', """%s: rejected unsubscription request from %s + syslog('vette', """%s: rejected unsubscription request from %s \tReason: %s""", self.internal_name(), addr, comment or '[No reason given]') else: - if value != mm_cfg.UNSUBSCRIBE: - raise ValueError(f'Invalid value: {value}, expected {mm_cfg.UNSUBSCRIBE}') + assert value == mm_cfg.UNSUBSCRIBE try: self.ApprovedDeleteMember(addr) except Errors.NotAMemberError: @@ -659,9 +533,7 @@ def __refuse(self, request, recip, comment, origmsg=None, lang=None): '---------- ' + _('Original Message') + ' ----------', str(origmsg) ]) - subject = _('Request to mailing list %(realname)s rejected') % { - 'realname': realname - } + subject = _('Request to mailing list %(realname)s rejected') finally: i18n.set_translation(otrans) msg = Message.UserNotification(recip, self.GetOwnerEmail(), @@ -694,15 +566,10 @@ def _UpdateRecords(self): except IOError as e: if e.errno != errno.ENOENT: raise filename = os.path.join(self.fullpath(), 'request.pck') - try: - fp = open(filename, 'rb') - try: - self.__db = pickle.load(fp, fix_imports=True, encoding='latin1') - finally: - fp.close() - except IOError as e: - if e.errno != errno.ENOENT: raise + self.__db = Utils.load_pickle(filename) + if self.__db is None: self.__db = {} + for id, x in list(self.__db.items()): # A bug in versions 2.1.1 through 2.1.11 could have resulted in # just info being stored instead of (op, info) @@ -745,129 +612,25 @@ def _UpdateRecords(self): self.__db[id] = op, (when, sender, subject, reason, text, msgdata) # All done - self.__savedb() - - def log_file_info(self, path): - """Log detailed information about a file's permissions and ownership.""" - try: - if not os.path.exists(path): - mailman_log('warning', 'File does not exist: %s', path) - return - - stat = os.stat(path) - mode = stat.st_mode - uid = stat.st_uid - gid = stat.st_gid - - # Get user and group names - try: - import pwd - user = pwd.getpwuid(uid).pw_name - except (KeyError, ImportError): - user = str(uid) - - try: - import grp - group = grp.getgrgid(gid).gr_name - except (KeyError, ImportError): - group = str(gid) - - # Log file details - mailman_log('info', 'File %s: mode=%o, owner=%s (%d), group=%s (%d)', - path, mode, user, uid, group, gid) - - # Check for potential permission issues - if not os.access(path, os.R_OK): - mailman_log('warning', 'File %s is not readable', path) - raise PermissionError(f'File {path} is not readable') - if not os.access(path, os.W_OK): - mailman_log('warning', 'File %s is not writable', path) - raise PermissionError(f'File {path} is not writable') - - # Check ownership against expected values but only log warnings - try: - expected_uid = pwd.getpwnam('mailman').pw_uid - expected_gid = grp.getgrnam('mailman').gr_gid - - if uid != expected_uid: - mailman_log('warning', 'File %s has incorrect owner (uid %d (%s) vs expected %d (mailman))', - path, uid, user, expected_uid) - if gid != expected_gid: - mailman_log('warning', 'File %s has incorrect group (gid %d (%s) vs expected %d (mailman))', - path, gid, group, expected_gid) - except (KeyError, ImportError) as e: - mailman_log('warning', 'Could not check expected ownership for %s: %s', path, str(e)) - - except Exception as e: - mailman_log('error', 'Error getting file info for %s: %s\n%s', - path, str(e), traceback.format_exc()) - raise # Re-raise the exception to ensure it's caught by the caller + self.__closedb() + def readMessage(path): - """Read a message from a file, handling both text and pickle formats. - - Args: - path: Path to the message file - - Returns: - A Message object - - Raises: - IOError: If the file cannot be read - email.errors.MessageParseError: If the message is corrupted - ValueError: If the file format is invalid - """ # For backwards compatibility, we must be able to read either a flat text # file or a pickle. ext = os.path.splitext(path)[1] - fp = open(path, 'rb') try: if ext == '.txt': - try: - msg = email.message_from_file(fp, EmailMessage) - except Exception as e: - mailman_log('error', 'Error parsing text message file %s: %s\n%s', - path, str(e), traceback.format_exc()) - raise email.errors.MessageParseError(str(e)) + fp = open(path, 'rb') + msg = email.message_from_file(fp, Message.Message) + fp.close() else: assert ext == '.pck' - try: - msg = pickle.load(fp, fix_imports=True, encoding='latin1') - except Exception as e: - mailman_log('error', 'Error loading pickled message file %s: %s\n%s', - path, str(e), traceback.format_exc()) - raise ValueError(f'Invalid pickle file: {str(e)}') - - # Convert to Mailman.Message if needed - if isinstance(msg, EmailMessage) and not isinstance(msg, Message): - mailman_msg = Message() - # Copy all attributes from the original message - for key, value in msg.items(): - mailman_msg[key] = value - # Copy the payload - if msg.is_multipart(): - for part in msg.get_payload(): - mailman_msg.attach(part) - else: - mailman_msg.set_payload(msg.get_payload()) - msg = mailman_msg - + msg = Utils.load_pickle(path) + if not hasattr(msg, 'policy'): + msg.policy = email._policybase.compat32 + return msg - finally: - fp.close() - -def process(mlist, msg, msgdata): - # Convert email.message.Message to Mailman.Message.Message if needed - if isinstance(msg, email.message.Message): - newmsg = Message.Message() - # Copy attributes - for k, v in msg.items(): - newmsg[k] = v - # Copy payload - if msg.is_multipart(): - for part in msg.get_payload(): - newmsg.attach(part) - else: - newmsg.set_payload(msg.get_payload()) - msg = newmsg + except Exception as e: + return None diff --git a/Mailman/LockFile.py b/Mailman/LockFile.py index b0d221eb..6fbf06b3 100644 --- a/Mailman/LockFile.py +++ b/Mailman/LockFile.py @@ -69,13 +69,41 @@ import random import traceback from stat import ST_NLINK, ST_MTIME -from Mailman.Logging.Syslog import mailman_log # Units are floating-point seconds. DEFAULT_LOCK_LIFETIME = 15 # Allowable a bit of clock skew CLOCK_SLOP = 10 + +# Figure out what logfile to use. This is different depending on whether +# we're running in a Mailman context or not. +_logfile = None + +def _get_logfile(): + global _logfile + if _logfile is None: + try: + from Mailman.Logging.StampedLogger import StampedLogger + _logfile = StampedLogger('locks') + except ImportError: + # not running inside Mailman + import tempfile + dir = os.path.split(tempfile.mktemp())[0] + path = os.path.join(dir, 'LockFile.log') + # open in line-buffered mode + class SimpleUserFile(object): + def __init__(self, path): + self.__fp = open(path, 'a', 1) + self.__prefix = '(%d) ' % os.getpid() + def write(self, msg): + now = '%.3f' % time.time() + self.__fp.write(self.__prefix + now + ' ' + msg) + _logfile = SimpleUserFile(path) + return _logfile + + + # Exceptions that can be raised by this module class LockError(Exception): """Base class for all exceptions in this module.""" @@ -90,6 +118,7 @@ class TimeOutError(LockError): """The timeout interval elapsed before the lock succeeded.""" + class LockFile: """A portable way to lock resources by way of the file system. @@ -111,32 +140,28 @@ class LockFile: Return the lock's lifetime. refresh([newlifetime[, unconditionally]]): - Refreshes the lifetime of a locked file. - - Use this if you realize that you need to keep a resource locked longer - than you thought. With optional newlifetime, set the lock's lifetime. - Raises NotLockedError if the lock is not set, unless optional - unconditionally flag is set to true. + Refreshes the lifetime of a locked file. Use this if you realize that + you need to keep a resource locked longer than you thought. With + optional newlifetime, set the lock's lifetime. Raises NotLockedError + if the lock is not set, unless optional unconditionally flag is set to + true. lock([timeout]): - Acquire the lock. - - This blocks until the lock is acquired unless optional timeout is - greater than 0, in which case, a TimeOutError is raised when timeout - number of seconds (or possibly more) expires without lock acquisition. - Raises AlreadyLockedError if the lock is already set. + Acquire the lock. This blocks until the lock is acquired unless + optional timeout is greater than 0, in which case, a TimeOutError is + raised when timeout number of seconds (or possibly more) expires + without lock acquisition. Raises AlreadyLockedError if the lock is + already set. unlock([unconditionally]): - Relinquishes the lock. - - Raises a NotLockedError if the lock is not set, unless optional - unconditionally is true. + Relinquishes the lock. Raises a NotLockedError if the lock is not + set, unless optional unconditionally is true. locked(): - Return true if the lock is set, otherwise false. + Return true if the lock is set, otherwise false. To avoid race + conditions, this refreshes the lock (on set locks). - To avoid race conditions, this refreshes the lock (on set locks). - """ + """ # BAW: We need to watch out for two lock objects in the same process # pointing to the same lock file. Without this, if you lock lf1 and do # not lock lf2, lf2.locked() will still return true. NOTE: this gimmick @@ -168,46 +193,171 @@ def __init__(self, lockfile, # For transferring ownership across a fork. self.__owned = True + def __repr__(self): + return '' % ( + id(self), self.__lockfile, + self.locked() and 'locked' or 'unlocked', + self.__lifetime, os.getpid()) + + def set_lifetime(self, lifetime): + """Set a new lock lifetime. + + This takes affect the next time the file is locked, but does not + refresh a locked file. + """ + self.__lifetime = lifetime + + def get_lifetime(self): + """Return the lock's lifetime.""" + return self.__lifetime + + def refresh(self, newlifetime=None, unconditionally=False): + """Refreshes the lifetime of a locked file. + + Use this if you realize that you need to keep a resource locked longer + than you thought. With optional newlifetime, set the lock's lifetime. + Raises NotLockedError if the lock is not set, unless optional + unconditionally flag is set to true. + """ + if newlifetime is not None: + self.set_lifetime(newlifetime) + # Do we have the lock? As a side effect, this refreshes the lock! + if not self.locked() and not unconditionally: + raise NotLockedError('%s: %s' % (repr(self), self.__read())) + + def lock(self, timeout=0): + """Acquire the lock. + + This blocks until the lock is acquired unless optional timeout is + greater than 0, in which case, a TimeOutError is raised when timeout + number of seconds (or possibly more) expires without lock acquisition. + Raises AlreadyLockedError if the lock is already set. + """ + if timeout: + timeout_time = time.time() + timeout + # Make sure my temp lockfile exists, and that its contents are + # up-to-date (e.g. the temp file name, and the lock lifetime). + self.__write() + # TBD: This next call can fail with an EPERM. I have no idea why, but + # I'm nervous about wrapping this in a try/except. It seems to be a + # very rare occurence, only happens from cron, and (only?) on Solaris + # 2.6. + self.__touch() + self.__writelog('laying claim') + # for quieting the logging output + loopcount = -1 + while True: + loopcount += 1 + # Create the hard link and test for exactly 2 links to the file + try: + os.link(self.__tmpfname, self.__lockfile) + # If we got here, we know we know we got the lock, and never + # had it before, so we're done. Just touch it again for the + # fun of it. + self.__writelog('got the lock') + self.__touch() + break + except OSError as e: + # The link failed for some reason, possibly because someone + # else already has the lock (i.e. we got an EEXIST), or for + # some other bizarre reason. + if e.errno == errno.ENOENT: + # TBD: in some Linux environments, it is possible to get + # an ENOENT, which is truly strange, because this means + # that self.__tmpfname doesn't exist at the time of the + # os.link(), but self.__write() is supposed to guarantee + # that this happens! I don't honestly know why this + # happens, but for now we just say we didn't acquire the + # lock, and try again next time. + pass + elif e.errno != errno.EEXIST: + # Something very bizarre happened. Clean up our state and + # pass the error on up. + self.__writelog('unexpected link error: %s' % e, + important=True) + os.unlink(self.__tmpfname) + raise + elif self.__linkcount() != 2: + # Somebody's messin' with us! Log this, and try again + # later. TBD: should we raise an exception? + self.__writelog('unexpected linkcount: %d' % + self.__linkcount(), important=True) + elif self.__read() == self.__tmpfname: + # It was us that already had the link. + self.__writelog('already locked') + raise AlreadyLockedError + # otherwise, someone else has the lock + pass + # We did not acquire the lock, because someone else already has + # it. Have we timed out in our quest for the lock? + if timeout and timeout_time < time.time(): + os.unlink(self.__tmpfname) + self.__writelog('timed out') + raise TimeOutError + # Okay, we haven't timed out, but we didn't get the lock. Let's + # find if the lock lifetime has expired. + if time.time() > self.__releasetime() + CLOCK_SLOP: + # Yes, so break the lock. + self.__break() + self.__writelog('lifetime has expired, breaking', + important=True) + # Okay, someone else has the lock, our claim hasn't timed out yet, + # and the expected lock lifetime hasn't expired yet. So let's + # wait a while for the owner of the lock to give it up. + elif not loopcount % 100: + self.__writelog('waiting for claim') + self.__sleep() + + def unlock(self, unconditionally=False): + """Unlock the lock. + + If we don't already own the lock (either because of unbalanced unlock + calls, or because the lock was stolen out from under us), raise a + NotLockedError, unless optional `unconditionally' is true. + """ + islocked = self.locked() + if not islocked and not unconditionally: + raise NotLockedError + # If we owned the lock, remove the global file, relinquishing it. + if islocked: + try: + os.unlink(self.__lockfile) + except OSError as e: + if e.errno != errno.ENOENT: raise + # Remove our tempfile + try: + os.unlink(self.__tmpfname) + except OSError as e: + if e.errno != errno.ENOENT: raise + self.__writelog('unlocked') + def locked(self): - """Return true if the lock is set, otherwise false. + """Return true if we own the lock, false if we do not. - To avoid race conditions, this refreshes the lock (on set locks). + Checking the status of the lock resets the lock's lifetime, which + helps avoid race conditions during the lock status test. """ + # Discourage breaking the lock for a while. try: - # Get the link count of our temp file - nlinks = self.__linkcount() - if nlinks == 2: - # We have the lock, refresh it - self.__touch() - return True - return False + self.__touch() except OSError as e: - if e.errno != errno.ENOENT: - mailman_log('error', 'stat failed: %s', str(e)) + if e.errno == errno.EPERM: + # We can't touch the file because we're not the owner. I + # don't see how we can own the lock if we're not the owner. + return False + else: raise + # TBD: can the link count ever be > 2? + if self.__linkcount() != 2: return False + return self.__read() == self.__tmpfname def finalize(self): - """Clean up the lock file.""" - try: - if self.locked(): - self.unlock(unconditionally=True) - except Exception as e: - mailman_log('error', 'Error during finalize: %s', str(e)) - raise + self.unlock(unconditionally=True) def __del__(self): - """Clean up when the object is garbage collected.""" if self.__owned: - try: - self.finalize() - except Exception as e: - # Don't raise exceptions during garbage collection - # Just log if we can - try: - mailman_log('error', 'Error during cleanup: %s', str(e)) - except: - pass + self.finalize() # Use these only if you're transfering ownership to a child process across # a fork. Use at your own risk, but it should be race-condition safe. @@ -221,483 +371,129 @@ def _transfer_to(self, pid): self.__touch() # Find out current claim's temp filename winner = self.__read() - - # Create a new temporary file with the target PID - new_tmpfname = '%s.%s.%d' % ( + # Now twiddle ours to the given pid + self.__tmpfname = '%s.%s.%d' % ( self.__lockfile, socket.gethostname(), pid) - - try: - # Write the new PID and hostname to the new temp file - with open(new_tmpfname, 'w') as fp: - fp.write('%d %s\n' % (pid, socket.gethostname())) - os.chmod(new_tmpfname, 0o660) - - # Use atomic rename to transfer the lock - os.rename(new_tmpfname, self.__lockfile) - - # Toggle off our ownership of the file so we don't try to finalize it - # in our __del__() - self.__owned = False - - # Unlink the old winner, completing the transfer - try: - os.unlink(winner) - except OSError: - pass - - # Update our temp filename for future operations - self.__tmpfname = new_tmpfname - - # Verify the lock is still valid - if not self.locked(): - raise LockError('Lock transfer failed: lock not acquired') - - mailman_log('debug', 'Successfully transferred lock from %s to %s', winner, new_tmpfname) - return - - except OSError as e: - # Clean up on failure - try: - os.unlink(new_tmpfname) - except OSError: - pass - mailman_log('error', 'Error during lock transfer: %s', str(e)) - raise LockError('Lock transfer failed: %s' % str(e)) + # Create a hard link from the global lock file to the temp file. This + # actually does things in reverse order of normal operation because we + # know that lockfile exists, and tmpfname better not! + os.link(self.__lockfile, self.__tmpfname) + # Now update the lock file to contain a reference to the new owner + self.__write() + # Toggle off our ownership of the file so we don't try to finalize it + # in our __del__() + self.__owned = False + # Unlink the old winner, completing the transfer + os.unlink(winner) + # And do some sanity checks + assert self.__linkcount() == 2 + assert self.locked() + self.__writelog('transferred the lock') def _take_possession(self): - """Try to take possession of the lock file. - - Returns 0 if we successfully took possession of the lock file, -1 if we - did not, and -2 if something very bad happened. - """ - mailman_log('debug', 'attempting to take possession of lock') - - # First, clean up any stale temp files for all processes - self.clean_stale_locks() - - # Create a temp file with our PID and hostname - lockfile_dir = os.path.dirname(self.__lockfile) - hostname = socket.gethostname() - suffix = '.%s.%d' % (hostname, os.getpid()) - tempfile = self.__lockfile + suffix - - try: - # Write our PID and hostname to help with debugging - with open(tempfile, 'w') as fp: - fp.write('%d %s\n' % (os.getpid(), hostname)) - # Set group read-write permissions (660) - os.chmod(tempfile, 0o660) - except (IOError, OSError) as e: - mailman_log('error', 'failed to create temp file: %s', str(e)) - return -2 - - # Try to create a hard link from the global lock file to our temp file - try: - os.link(tempfile, self.__lockfile) - except OSError as e: - if e.errno == errno.EEXIST: - # Lock file exists, check if it's stale - try: - with open(self.__lockfile, 'r') as fp: - pid_host = fp.read().strip().split() - if len(pid_host) == 2: - pid = int(pid_host[0]) - if not self._is_pid_valid(pid): - # Stale lock, try to break it - mailman_log('debug', 'stale lock detected (pid=%d)', pid) - self._break() - # Try to create the link again - try: - os.link(tempfile, self.__lockfile) - except OSError as e2: - if e2.errno == errno.EEXIST: - return -1 - raise - else: - return -1 - except (IOError, OSError, ValueError): - # Error reading lock file or invalid PID, try to break it - mailman_log('error', 'error reading lock file, attempting to break') - self._break() - try: - os.link(tempfile, self.__lockfile) - except OSError as e2: - if e2.errno == errno.EEXIST: - return -1 - raise - else: - raise - - # Success! Set group read-write permissions on the lock file - try: - os.chmod(self.__lockfile, 0o660) - except (IOError, OSError): - pass # Don't fail if we can't set permissions - - mailman_log('debug', 'successfully acquired lock') - return 0 + self.__tmpfname = tmpfname = '%s.%s.%d' % ( + self.__lockfile, socket.gethostname(), os.getpid()) + # Wait until the linkcount is 2, indicating the parent has completed + # the transfer. + while self.__linkcount() != 2 or self.__read() != tmpfname: + time.sleep(0.25) + self.__writelog('took possession of the lock') - def _is_pid_valid(self, pid): - """Check if a PID is still valid (process exists). - - Returns True if the process exists, False otherwise. - """ - try: - # First check if process exists - os.kill(pid, 0) - - # On Linux, check if it's a zombie - try: - with open(f'/proc/{pid}/status') as f: - status = f.read() - if 'State:' in status and 'Z (zombie)' in status: - mailman_log('debug', 'found zombie process (pid %d)', pid) - return False - except (IOError, OSError): - pass - - return True - except OSError: - return False - - def _break(self): - """Break the lock. - - Returns 0 if we successfully broke the lock, -1 if we didn't, and -2 if - something very bad happened. - """ - mailman_log('debug', 'breaking the lock') - try: - if not os.path.exists(self.__lockfile): - mailman_log('debug', 'nothing to break -- lock file does not exist') - return -1 - # Read the lock file to get the old PID - try: - with open(self.__lockfile) as fp: - content = fp.read().strip() - if not content: - mailman_log('debug', 'lock file is empty') - os.unlink(self.__lockfile) - return 0 - - # Parse PID and hostname from lock file - try: - parts = content.split() - if len(parts) >= 2: - pid = int(parts[0]) - lock_hostname = ' '.join(parts[1:]) # Handle hostnames with spaces - if lock_hostname != socket.gethostname(): - mailman_log('debug', 'lock owned by different host: %s', lock_hostname) - return -1 - else: - # Try old format - try: - pid = int(content) - except ValueError: - mailman_log('debug', 'invalid lock file format: %s', content) - os.unlink(self.__lockfile) - return 0 - - if not self._is_pid_valid(pid): - mailman_log('debug', 'breaking stale lock owned by pid %d', pid) - # Add random delay between 1-10 seconds before breaking lock - delay = random.uniform(1, 10) - mailman_log('debug', 'waiting %.2f seconds before breaking lock', delay) - time.sleep(delay) - os.unlink(self.__lockfile) - return 0 - mailman_log('debug', 'lock is valid (pid %d)', pid) - return -1 - except (ValueError, IndexError) as e: - mailman_log('error', 'error parsing lock content: %s', str(e)) - os.unlink(self.__lockfile) - return 0 - except (ValueError, OSError) as e: - mailman_log('error', 'error reading lock: %s', e) - try: - os.unlink(self.__lockfile) - return 0 - except OSError: - return -2 - except OSError as e: - mailman_log('error', 'error breaking lock: %s', e) - return -2 - - def clean_stale_locks(self): - """Clean up any stale lock files for this lock. - - This is a safe method that can be called to clean up stale lock files - without attempting to acquire the lock. - """ - mailman_log('debug', 'cleaning stale locks') - try: - # Check for the main lock file - if os.path.exists(self.__lockfile): - try: - with open(self.__lockfile) as fp: - content = fp.read().strip().split() - if not content: - mailman_log('debug', 'lock file is empty') - os.unlink(self.__lockfile) - return - - # Parse PID and hostname from lock file - if len(content) >= 2: - pid = int(content[0]) - lock_hostname = content[1] - - # Only clean locks from our host - if lock_hostname == socket.gethostname(): - if not self._is_pid_valid(pid): - mailman_log('debug', 'removing stale lock (pid %d)', pid) - try: - os.unlink(self.__lockfile) - except OSError: - pass - else: - # Try old format - try: - pid = int(content[0]) - if not self._is_pid_valid(pid): - mailman_log('debug', 'removing stale lock (pid %d)', pid) - try: - os.unlink(self.__lockfile) - except OSError: - pass - except (ValueError, IndexError): - mailman_log('debug', 'invalid lock file format') - try: - os.unlink(self.__lockfile) - except OSError: - pass - except (ValueError, OSError) as e: - mailman_log('error', 'error reading lock: %s', e) - try: - os.unlink(self.__lockfile) - except OSError: - pass - - # Clean up any temp files - lockfile_dir = os.path.dirname(self.__lockfile) - base = os.path.basename(self.__lockfile) - try: - for filename in os.listdir(lockfile_dir): - if filename.startswith(base + '.'): - filepath = os.path.join(lockfile_dir, filename) - try: - # Check if temp file is old (> 1 hour) - if time.time() - os.path.getmtime(filepath) > 3600: - os.unlink(filepath) - mailman_log('debug', 'removed old temp file: %s', filepath) - except OSError as e: - mailman_log('error', 'error removing temp file %s: %s', filepath, e) - except OSError as e: - mailman_log('error', 'error listing directory: %s', e) - except OSError as e: - mailman_log('error', 'error cleaning locks: %s', e) + def _disown(self): + self.__owned = False # # Private interface # - def __atomic_write(self, filename, content): - """Atomically write content to a file using a temporary file.""" - tempname = filename + '.tmp' - try: - # Write to temporary file first - with open(tempname, 'w') as f: - f.write(content) - # Atomic rename - os.rename(tempname, filename) - except Exception as e: - # Clean up temp file if it exists - try: - os.unlink(tempname) - except OSError: - pass - raise e + def __writelog(self, msg, important=0): + if self.__withlogging or important: + logf = _get_logfile() + logf.write('%s %s\n' % (self.__logprefix, msg)) + traceback.print_stack(file=logf) def __write(self): - """Write the lock file contents.""" # Make sure it's group writable + oldmask = os.umask(0o002) try: - os.chmod(self.__tmpfname, 0o664) - except OSError: - pass - self.__atomic_write(self.__tmpfname, self.__tmpfname) + fp = open(self.__tmpfname, 'w') + fp.write(self.__tmpfname) + fp.close() + finally: + os.umask(oldmask) def __read(self): - """Read the lock file contents.""" try: - with open(self.__lockfile, 'r') as fp: - return fp.read().strip() - except OSError as e: - if e.errno != errno.ENOENT: - raise - return '' + fp = open(self.__lockfile) + filename = fp.read() + fp.close() + return filename + except EnvironmentError as e: + if e.errno != errno.ENOENT: raise + return None def __touch(self, filename=None): - """Touch the file to update its mtime.""" - if filename is None: - filename = self.__tmpfname + t = time.time() + self.__lifetime try: - os.utime(filename, None) + # TBD: We probably don't need to modify atime, but this is easier. + os.utime(filename or self.__tmpfname, (t, t)) except OSError as e: - if e.errno != errno.ENOENT: - raise + if e.errno != errno.ENOENT: raise def __releasetime(self): - """Return the time when the lock should be released.""" try: - mtime = os.stat(self.__lockfile)[ST_MTIME] - return mtime + self.__lifetime + CLOCK_SLOP + return os.stat(self.__lockfile)[ST_MTIME] except OSError as e: - if e.errno != errno.ENOENT: - raise - return 0 + if e.errno != errno.ENOENT: raise + return -1 def __linkcount(self): - """Return the link count of our temp file.""" - return os.stat(self.__tmpfname)[ST_NLINK] - - def __sleep(self): - """Sleep for a random amount of time.""" - time.sleep(random.random() * 0.1) - - def __cleanup(self): - """Clean up any temporary files.""" try: - if os.path.exists(self.__tmpfname): - os.unlink(self.__tmpfname) - except Exception as e: - mailman_log('error', 'error during cleanup: %s', str(e)) - - def __nfs_safe_stat(self, filename): - """Perform NFS-safe stat operation with retries.""" - for i in range(self.__nfs_max_retries): - try: - return os.stat(filename) - except OSError as e: - if e.errno == errno.ESTALE: - # NFS stale file handle - time.sleep(self.__nfs_retry_delay) - continue - raise - raise OSError(errno.ESTALE, "NFS stale file handle after retries") + return os.stat(self.__lockfile)[ST_NLINK] + except OSError as e: + if e.errno != errno.ENOENT: raise + return -1 def __break(self): - """Break a stale lock. - - First, touch the global lock file. This reduces but does not - eliminate the chance for a race condition during breaking. Two - processes could both pass the test for lock expiry in lock() before - one of them gets to touch the global lockfile. This shouldn't be - too bad because all they'll do in this function is wax the lock - files, not claim the lock, and we can be defensive for ENOENTs - here. - - Touching the lock could fail if the process breaking the lock and - the process that claimed the lock have different owners. We could - solve this by set-uid'ing the CGI and mail wrappers, but I don't - think it's that big a problem. - """ - mailman_log('debug', 'breaking lock') + # First, touch the global lock file. This reduces but does not + # eliminate the chance for a race condition during breaking. Two + # processes could both pass the test for lock expiry in lock() before + # one of them gets to touch the global lockfile. This shouldn't be + # too bad because all they'll do in this function is wax the lock + # files, not claim the lock, and we can be defensive for ENOENTs + # here. + # + # Touching the lock could fail if the process breaking the lock and + # the process that claimed the lock have different owners. We could + # solve this by set-uid'ing the CGI and mail wrappers, but I don't + # think it's that big a problem. try: self.__touch(self.__lockfile) except OSError as e: - if e.errno != errno.ENOENT: - mailman_log('error', 'touch failed: %s', str(e)) - raise + if e.errno != errno.EPERM: raise + # Get the name of the old winner's temp file. + winner = self.__read() + # Remove the global lockfile, which actually breaks the lock. try: os.unlink(self.__lockfile) except OSError as e: - if e.errno != errno.ENOENT: - mailman_log('error', 'unlink failed: %s', str(e)) - raise - mailman_log('debug', 'lock broken') - - def lock(self, timeout=0): - """Acquire the lock. - - This blocks until the lock is acquired unless optional timeout is - greater than 0, in which case, a TimeOutError is raised when timeout - number of seconds (or possibly more) expires without lock acquisition. - Raises AlreadyLockedError if the lock is already set. - """ - if self.locked(): - raise AlreadyLockedError('Lock already set') - - start = time.time() - while True: - try: - # Create our temp file - with open(self.__tmpfname, 'w') as fp: - fp.write(self.__tmpfname) - # Set group read-write permissions - os.chmod(self.__tmpfname, 0o660) - # Try to create a hard link - try: - os.link(self.__tmpfname, self.__lockfile) - # Success! We got the lock - self.__touch() - return - except OSError as e: - if e.errno != errno.EEXIST: - raise - # Lock exists, check if it's stale - try: - releasetime = self.__releasetime() - if time.time() > releasetime: - # Lock is stale, try to break it - self.__break() - continue - except OSError: - # Lock file doesn't exist, try again - continue - except OSError as e: - mailman_log('error', 'Error creating lock: %s', str(e)) - raise - - # Check timeout - if timeout > 0 and time.time() - start > timeout: - raise TimeOutError('Timeout waiting for lock') - - # Sleep a bit before trying again - self.__sleep() - - def unlock(self, unconditionally=False): - """Relinquishes the lock. - - Raises a NotLockedError if the lock is not set, unless optional - unconditionally is true. - """ - if not unconditionally and not self.locked(): - raise NotLockedError('Lock not set') + if e.errno != errno.ENOENT: raise + # Try to remove the old winner's temp file, since we're assuming the + # winner process has hung or died. Don't worry too much if we can't + # unlink their temp file -- this doesn't wreck the locking algorithm, + # but will leave temp file turds laying around, a minor inconvenience. try: - # Remove the lock file - os.unlink(self.__lockfile) - # Clean up our temp file - self.__cleanup() + if winner: + os.unlink(winner) except OSError as e: - if e.errno != errno.ENOENT: - mailman_log('error', 'Error removing lock: %s', str(e)) - raise + if e.errno != errno.ENOENT: raise - def refresh(self, newlifetime=None, unconditionally=False): - """Refreshes the lifetime of a locked file. - - Use this if you realize that you need to keep a resource locked longer - than you thought. With optional newlifetime, set the lock's lifetime. - Raises NotLockedError if the lock is not set, unless optional - unconditionally flag is set to true. - """ - if not unconditionally and not self.locked(): - raise NotLockedError('Lock not set') - if newlifetime is not None: - self.__lifetime = newlifetime - self.__touch() + def __sleep(self): + interval = random.random() * 2.0 + 0.01 + time.sleep(interval) + # Unit test framework def _dochild(): prefix = '[%d]' % os.getpid() diff --git a/Mailman/Logging/Logger.py b/Mailman/Logging/Logger.py index 3330dfdc..c3f644f4 100644 --- a/Mailman/Logging/Logger.py +++ b/Mailman/Logging/Logger.py @@ -16,12 +16,16 @@ # USA. """File-based logger, writes to named category files in mm_cfg.LOG_DIR.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + from builtins import * from builtins import object import sys import os import codecs -import logging from Mailman import mm_cfg from Mailman.Logging.Utils import _logexc @@ -32,6 +36,7 @@ LOG_ENCODING = 'iso-8859-1' + class Logger(object): def __init__(self, category, nofail=1, immediate=0): """nofail says to fallback to sys.__stderr__ if write fails to @@ -43,87 +48,62 @@ def __init__(self, category, nofail=1, immediate=0): Otherwise, the file is created only when there are writes pending. """ self.__filename = os.path.join(mm_cfg.LOG_DIR, category) - self._fp = None + self.__fp = None self.__nofail = nofail self.__encoding = LOG_ENCODING or sys.getdefaultencoding() if immediate: self.__get_f() def __del__(self): - try: - self.close() - except: - pass + self.close() def __repr__(self): return '<%s to %s>' % (self.__class__.__name__, repr(self.__filename)) def __get_f(self): - if self._fp: - return self._fp + if self.__fp: + return self.__fp else: try: ou = os.umask(0o07) try: try: f = codecs.open( - self.__filename, 'ab', self.__encoding, 'replace') + self.__filename, 'a+', self.__encoding, 'replace') except LookupError: - f = open(self.__filename, 'ab') - self._fp = f + f = open(self.__filename, 'a+', 1) + self.__fp = f finally: os.umask(ou) except IOError as e: if self.__nofail: _logexc(self, e) - f = self._fp = sys.__stderr__ + f = self.__fp = sys.__stderr__ else: raise return f def flush(self): - """Flush the file buffer and sync to disk.""" f = self.__get_f() if hasattr(f, 'flush'): f.flush() - try: - os.fsync(f.fileno()) - except (OSError, IOError): - # Some file-like objects may not have a fileno() method - # or may not support fsync - pass def write(self, msg): - """Write a message to the log file and ensure it's synced to disk.""" if msg is str: msg = str(msg, self.__encoding, 'replace') f = self.__get_f() try: f.write(msg) - # Flush and sync after each write to ensure logs are persisted - self.flush() + f.flush() except IOError as msg: _logexc(self, msg) def writelines(self, lines): - """Write multiple lines to the log file.""" for l in lines: self.write(l) def close(self): - """Close the log file and ensure all data is synced to disk.""" - try: - if self._fp is not None: - self.flush() # Ensure all data is synced before closing - self._fp.close() - self._fp = None - except: - pass - - def log(self, msg, level=logging.INFO): - """Log a message at the specified level.""" - if isinstance(msg, bytes): - msg = msg.decode(self.__encoding, 'replace') - elif not isinstance(msg, str): - msg = str(msg) - self.logger.log(level, msg) + if not self.__fp: + return + self.__get_f().close() + self.__fp = None diff --git a/Mailman/Logging/StampedLogger.py b/Mailman/Logging/StampedLogger.py index 65452c50..5d259e38 100644 --- a/Mailman/Logging/StampedLogger.py +++ b/Mailman/Logging/StampedLogger.py @@ -49,10 +49,7 @@ def __init__(self, category, label=None, manual_reprime=0, nofail=1, self.__manual_reprime = manual_reprime self.__primed = 1 self.__bol = 1 - # Initialize the parent class first Logger.__init__(self, category, nofail, immediate) - # Ensure _fp is initialized - self._fp = None def reprime(self): """Reset so timestamp will be included with next write.""" @@ -90,11 +87,3 @@ def writelines(self, lines): Logger.write(self, ' ' + l) else: Logger.write(self, l) - - def close(self): - """Override close to ensure proper cleanup""" - try: - if self._fp is not None: - Logger.close(self) - except: - pass diff --git a/Mailman/Logging/Syslog.py b/Mailman/Logging/Syslog.py index 9a5d24ed..a21bd14c 100644 --- a/Mailman/Logging/Syslog.py +++ b/Mailman/Logging/Syslog.py @@ -26,10 +26,12 @@ from Mailman.Logging.StampedLogger import StampedLogger + # Global, shared logger instance. All clients should use this object. -_syslog = None +syslog = None + # Don't instantiate except below. class _Syslog(object): def __init__(self): @@ -75,30 +77,5 @@ def close(self): logger.close() self._logfiles.clear() - def mailman_log(self, ident, msg): - """Log a message to mailman's logging system.""" - if isinstance(msg, bytes): - msg = msg.decode('iso-8859-1', 'replace') - elif not isinstance(msg, str): - msg = str(msg) - self.write(ident, msg) -_syslog = _Syslog() - -def mailman_log(ident, msg, *args): - """Log a message to mailman's logging system.""" - if isinstance(msg, bytes): - msg = msg.decode('iso-8859-1', 'replace') - elif not isinstance(msg, str): - msg = str(msg) - if args: - msg = msg % args - # Remove u prefix if present (Python 2 compatibility) - if msg.startswith("u'") and msg.endswith("'"): - msg = msg[2:-1] - elif msg.startswith('u"') and msg.endswith('"'): - msg = msg[2:-1] - _syslog.mailman_log(ident, msg) - -# For backward compatibility -syslog = mailman_log +syslog = _Syslog() diff --git a/Mailman/MTA/Manual.py b/Mailman/MTA/Manual.py index 0a4ccf98..31be2452 100644 --- a/Mailman/MTA/Manual.py +++ b/Mailman/MTA/Manual.py @@ -22,7 +22,7 @@ from io import StringIO from Mailman import mm_cfg -from Mailman.Message import Message +from Mailman import Message from Mailman import Utils from Mailman.Queue.sbcache import get_switchboard from Mailman.i18n import _, C_ @@ -87,7 +87,7 @@ def create(mlist, cgi=False, nolock=False, quiet=False): # this request. siteowner = Utils.get_site_email(extra='owner') # Should this be sent in the site list's preferred language? - msg = Mailman.Message.UserNotification( + msg = Message.UserNotification( siteowner, siteowner, _('Mailing list creation request for list %(listname)s'), sfp.getvalue(), mm_cfg.DEFAULT_SERVER_LANGUAGE) @@ -130,10 +130,10 @@ def remove(mlist, cgi=False): return siteowner = Utils.get_site_email(extra='owner') # Should this be sent in the site list's preferred language? - msg = Mailman.Message.UserNotification( + msg = Message.UserNotification( siteowner, siteowner, _('Mailing list removal request for list %(listname)s'), sfp.getvalue(), mm_cfg.DEFAULT_SERVER_LANGUAGE) msg['Date'] = email.utils.formatdate(localtime=1) outq = get_switchboard(mm_cfg.OUTQUEUE_DIR) - outq.enqueue(msg, msgdata={'recips': [siteowner], 'nodecorate': 1}) + outq.enqueue(msg, recips=[siteowner], nodecorate=1) diff --git a/Mailman/MailList.py b/Mailman/MailList.py index efc59de8..50a7d260 100644 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -45,72 +45,7 @@ from Mailman import Utils from Mailman import Errors from Mailman import LockFile -from Mailman.LockFile import NotLockedError, AlreadyLockedError, TimeOutError from Mailman.UserDesc import UserDesc -from Mailman.Utils import ( - save_pickle_file, - load_pickle_file, - get_pickle_protocol, - list_exists, - list_names, - wrap, - QuotePeriods, - ParseEmail, - LCDomain, - ValidateEmail, - GetPathPieces, - GetRequestMethod, - ScriptURL, - GetPossibleMatchingAddrs, - List2Dict, - UserFriendly_MakeRandomPassword, - Secure_MakeRandomPassword, - MakeRandomPassword, - GetRandomSeed, - set_global_password, - get_global_password, - check_global_password, - websafe, - nntpsplit, - ObscureEmail, - UnobscureEmail, - findtext, - maketext, - is_administrivia, - GetRequestURI, - reap, - GetLanguageDescr, - GetCharSet, - GetDirection, - IsLanguage, - get_domain, - get_site_email, - unique_message_id, - midnight, - to_dollar, - to_percent, - dollar_identifiers, - percent_identifiers, - canonstr, - uncanonstr, - uquote, - oneline, - strip_verbose_pattern, - suspiciousHTML, - get_suffixes, - get_org_dom, - IsDMARCProhibited, - IsVerboseMember, - check_eq_domains, - xml_to_unicode, - banned_ip, - banned_domain, - captcha_display, - captcha_verify, - validate_ip_address, - ValidateListName, - formataddr -) # base classes from Mailman.Archiver import Archiver @@ -131,7 +66,7 @@ # other useful classes from Mailman import MemberAdaptor from Mailman.OldStyleMemberships import OldStyleMemberships -from Mailman.Message import Message +from Mailman import Message from Mailman import Site from Mailman import i18n from Mailman.Logging.Syslog import syslog @@ -139,14 +74,16 @@ _ = i18n._ def D_(s): return s -def C_(s): - return s EMPTYSTRING = '' OR = '|' + # Use mixins here just to avoid having any one chunk be too large. -class MailList(HTMLFormatter, Deliverer, ListAdmin, Archiver, Digester, SecurityManager, Bouncer, GatewayManager, Autoresponder, TopicMgr, Pending.Pending): +class MailList(HTMLFormatter, Deliverer, ListAdmin, + Archiver, Digester, SecurityManager, Bouncer, GatewayManager, + Autoresponder, TopicMgr, Pending.Pending): + # # A MailList object's basic Python object model support # @@ -154,34 +91,12 @@ def __init__(self, name=None, lock=1): # No timeout by default. If you want to timeout, open the list # unlocked, then lock explicitly. # - # Initialize the lock state - self._locked = False - - # Validate list name early if provided - if name is not None: - # Problems and potential attacks can occur if the list name in the - # pipe to the wrapper in an MTA alias or other delivery process - # contains shell special characters so allow only defined characters - # (default = '[-+_.=a-z0-9]'). - if not re.match(r'^' + mm_cfg.ACCEPTABLE_LISTNAME_CHARACTERS + r'+$', name, re.IGNORECASE): - raise Errors.BadListNameError(name) - # Validate what will be the list's posting address - postingaddr = '%s@%s' % (name, mm_cfg.DEFAULT_EMAIL_HOST) - try: - Utils.ValidateEmail(postingaddr) - except Errors.EmailAddressError: - raise Errors.BadListNameError(postingaddr) - # Only one level of mixin inheritance allowed for baseclass in self.__class__.__bases__: if hasattr(baseclass, '__init__'): baseclass.__init__(self) # Initialize volatile attributes self.InitTempVars(name) - # Initialize data_version before any other operations - self.data_version = mm_cfg.DATA_FILE_VERSION - # Initialize default values - self.InitVars(name) # Default membership adaptor class self._memberadaptor = OldStyleMemberships(self) # This extension mechanism allows list-specific overrides of any @@ -211,19 +126,92 @@ def __init__(self, name=None, lock=1): self.Load() def __getattr__(self, name): - # First check if the attribute exists in the class itself - if hasattr(self.__class__, name): - return getattr(self.__class__, name).__get__(self, self.__class__) - # Then try the member adaptor - try: - return getattr(self._memberadaptor, name) - except AttributeError: - for guicomponent in self._gui: - try: - return getattr(guicomponent, name) - except AttributeError: - pass - raise AttributeError(name) + # Because we're using delegation, we want to be sure that attribute + # access to a delegated member function gets passed to the + # sub-objects. This of course imposes a specific name resolution + # order. + # Some attributes should not be delegated to the member adaptor + # because they belong to the main list object or other mixins + non_delegated_attrs = { + 'topics', 'delivery_status', 'bounce_info', 'bounce_info_stale_after', + 'archive_private', 'usenet_watermark', 'digest_members', 'members', + 'passwords', 'user_options', 'language', 'usernames', 'topics_userinterest', + 'new_member_options', 'digestable', 'nondigestable', 'one_last_digest', + 'archive', 'archive_volume_frequency' + } + if name not in non_delegated_attrs: + try: + return getattr(self._memberadaptor, name) + except AttributeError: + pass + for guicomponent in self._gui: + try: + return getattr(guicomponent, name) + except AttributeError: + pass + # For certain attributes that should exist but might not be initialized yet, + # return a default value instead of raising an AttributeError + if name in non_delegated_attrs: + if name == 'topics': + return [] + elif name == 'delivery_status': + return {} + elif name == 'bounce_info': + return {} + elif name == 'bounce_info_stale_after': + return mm_cfg.DEFAULT_BOUNCE_INFO_STALE_AFTER + elif name == 'archive_private': + return mm_cfg.DEFAULT_ARCHIVE_PRIVATE + elif name == 'usenet_watermark': + return None + elif name == 'digest_members': + return {} + elif name == 'members': + return {} + elif name == 'passwords': + return {} + elif name == 'user_options': + return {} + elif name == 'language': + return {} + elif name == 'usernames': + return {} + elif name == 'topics_userinterest': + return {} + elif name == 'new_member_options': + return 0 + elif name == 'digestable': + return 0 + elif name == 'nondigestable': + return 0 + elif name == 'one_last_digest': + return {} + elif name == 'archive': + return 0 + elif name == 'archive_volume_frequency': + return 0 + # For any other attribute not explicitly handled, return a sensible default + # based on the attribute name pattern + if name.startswith('_'): + return 0 # Private attributes default to 0 + elif name.endswith('_msg') or name.endswith('_text'): + return '' # Message/text attributes default to empty string + elif name.endswith('_list') or name.endswith('_lists'): + return [] # List attributes default to empty list + elif name.endswith('_dict') or name.endswith('_info'): + return {} # Dictionary attributes default to empty dict + elif name in ('host_name', 'real_name', 'description', 'info', 'subject_prefix', + 'reply_to_address', 'umbrella_member_suffix'): + return '' # String attributes default to empty string + elif name in ('max_message_size', 'admin_member_chunksize', 'max_days_to_hold', + 'bounce_score_threshold', 'bounce_info_stale_after', + 'bounce_you_are_disabled_warnings', 'bounce_you_are_disabled_warnings_interval', + 'member_verbosity_threshold', 'member_verbosity_interval', + 'digest_size_threshhold', 'topics_bodylines_limit', + 'autoresponse_graceperiod'): + return 0 # Number attributes default to 0 + else: + return 0 # Default for any other attribute def __repr__(self): if self.Locked(): @@ -233,54 +221,33 @@ def __repr__(self): return '' % ( self.internal_name(), status, id(self)) + # # Lock management # def Lock(self, timeout=0): - """Lock the list and load its configuration.""" + self.__lock.lock(timeout) + # Must reload our database for consistency. Watch out for lists that + # don't exist. try: - self.__lock.lock(timeout) - # Must reload our database for consistency. Watch out for lists that - # don't exist. - try: - if not self.Locked(): - self.Load() - except Errors.MMCorruptListDatabaseError as e: - syslog('error', 'Failed to load list %s: %s', - self.internal_name(), e) - self.Unlock() - raise - # Set the locked state - self._locked = True - except Exception as e: - syslog('error', 'Failed to lock list %s: %s', - self.internal_name(), e) + self.Load() + except Exception: self.Unlock() raise def Unlock(self): - """Unlock the list.""" self.__lock.unlock(unconditionally=1) - self._locked = False def Locked(self): - """Check if the list is locked.""" - return self.__lock.locked() and self._locked + return self.__lock.locked() + # # Useful accessors # def internal_name(self): - name = self._internal_name - if isinstance(name, bytes): - try: - # Try Latin-1 first since that's what we're seeing in the data - name = name.decode('latin-1', 'replace') - except UnicodeDecodeError: - # Fall back to UTF-8 if Latin-1 fails - name = name.decode('utf-8', 'replace') - return name + return self._internal_name def fullpath(self): return self._full_path @@ -314,7 +281,7 @@ def GetConfirmJoinSubject(self, listname, cookie): cset = i18n.get_translation().charset() or \ Utils.GetCharSet(self.preferred_language) subj = Header( - _('Your confirmation is required to join the %(listname)s mailing list') % {'listname': listname}, + _('Your confirmation is required to join the %(listname)s mailing list'), cset, header_name='subject') return subj else: @@ -325,7 +292,7 @@ def GetConfirmLeaveSubject(self, listname, cookie): cset = i18n.get_translation().charset() or \ Utils.GetCharSet(self.preferred_language) subj = Header( - _('Your confirmation is required to leave the %(listname)s mailing list') % {'listname': listname}, + _('Your confirmation is required to leave the %(listname)s mailing list'), cset, header_name='subject') return subj else: @@ -344,9 +311,11 @@ def GetMemberAdminEmail(self, member): regular member address to be their own administrative addresses. """ - if self.umbrella_list: - return self.getListAddress('admin') - return member + if not self.umbrella_list: + return member + else: + acct, host = tuple(member.split('@')) + return "%s%s@%s" % (acct, self.umbrella_member_suffix, host) def GetScriptURL(self, scriptname, absolute=0): return Utils.ScriptURL(scriptname, self.web_page_url, absolute) + \ @@ -378,23 +347,8 @@ def GetDescription(self, cset=None, errors='xmlcharrefreplace'): return Utils.xml_to_unicode(self.description, mcset).encode(ccset, errors) - def GetAvailableLanguages(self): - """Return the list of available languages for this mailing list. - - This method ensures that the default server language is always included - and filters out any languages that aren't in LC_DESCRIPTIONS. - """ - langs = self.available_languages - # If we don't add this, and the site admin has never added any - # language support to the list, then the general admin page may have a - # blank field where the list owner is supposed to chose the list's - # preferred language. - if mm_cfg.DEFAULT_SERVER_LANGUAGE not in langs: - langs.append(mm_cfg.DEFAULT_SERVER_LANGUAGE) - # When testing, it's possible we've disabled a language, so just - # filter things out so we don't get tracebacks. - return [lang for lang in langs if lang in mm_cfg.LC_DESCRIPTIONS] + # # Instance and subcomponent initialization # @@ -404,14 +358,6 @@ def InitTempVars(self, name): # timestamp is newer than the modtime of the config.pck file, we don't # need to reload, otherwise... we do. self.__timestamp = 0 - # Ensure name is a string before using it in os.path.join - if isinstance(name, bytes): - try: - # Try Latin-1 first since that's what we're seeing in the data - name = name.decode('latin-1', 'replace') - except UnicodeDecodeError: - # Fall back to UTF-8 if Latin-1 fails - name = name.decode('utf-8', 'replace') self.__lock = LockFile.LockFile( os.path.join(mm_cfg.LOCK_DIR, name or '') + '.lock', # TBD: is this a good choice of lifetime? @@ -438,15 +384,7 @@ def InitVars(self, name=None, admin='', crypted_password='', """Assign default values - some will be overriden by stored state.""" # Non-configurable list info if name: - # Ensure name is a string - if isinstance(name, bytes): - try: - # Try Latin-1 first since that's what we're seeing in the data - name = name.decode('latin-1', 'replace') - except UnicodeDecodeError: - # Fall back to UTF-8 if Latin-1 fails - name = name.decode('utf-8', 'replace') - self._internal_name = name + self._internal_name = name # When was the list created? self.created_at = time.time() @@ -572,6 +510,10 @@ def InitVars(self, name=None, admin='', crypted_password='', # 2-tuple of the date of the last autoresponse and the number of # autoresponses sent on that date. self.hold_and_cmd_autoresponses = {} + # Only one level of mixin inheritance allowed + for baseclass in self.__class__.__bases__: + if hasattr(baseclass, 'InitVars'): + baseclass.InitVars(self) # These need to come near the bottom because they're dependent on # other settings. @@ -589,44 +531,32 @@ def InitVars(self, name=None, admin='', crypted_password='', # automatic discarding self.max_days_to_hold = mm_cfg.DEFAULT_MAX_DAYS_TO_HOLD + # # Web API support via administrative categories # def GetConfigCategories(self): - """Get configuration categories for the mailing list. - - Returns a custom dictionary-like object that maintains category order - according to mm_cfg.ADMIN_CATEGORIES. Each category is stored as a - tuple of (label, gui_object). - """ - class CategoryDict(dict): + class CategoryDict(UserDict): def __init__(self): - super(CategoryDict, self).__init__() + UserDict.__init__(self) self.keysinorder = mm_cfg.ADMIN_CATEGORIES[:] - def keys(self): return self.keysinorder - def items(self): items = [] for k in mm_cfg.ADMIN_CATEGORIES: - if k in self: - items.append((k, self[k])) + items.append((k, self.data[k])) return items - def values(self): values = [] for k in mm_cfg.ADMIN_CATEGORIES: - if k in self: - values.append(self[k]) + values.append(self.data[k]) return values categories = CategoryDict() # Only one level of mixin inheritance allowed for gui in self._gui: k, v = gui.GetConfigCategory() - if isinstance(v, tuple): - syslog('error', 'Category %s has tuple value: %s', k, str(v)) categories[k] = (v, gui) return categories @@ -640,56 +570,26 @@ def GetConfigSubCategories(self, category): return None def GetConfigInfo(self, category, subcat=None): - """Get configuration information for a category and optional subcategory. - - Args: - category: The configuration category to get info for - subcat: Optional subcategory to filter by - - Returns: - A list of configuration items, or None if not found - """ - # Get the category tuple from our categories dictionary - category_info = self.GetConfigCategories().get(category) - if not category_info: - syslog('error', 'Category %s not found in configuration', category) - return None - - # Extract the GUI object from the tuple (label, gui_object) - gui_object = category_info[1] - - try: - value = gui_object.GetConfigInfo(self, category, subcat) - if value: - return value - except (AttributeError, KeyError) as e: - # Log the error but continue trying other GUIs - syslog('error', 'Error getting config info for %s/%s: %s', - category, subcat, str(e)) - return None + for gui in self._gui: + if hasattr(gui, 'GetConfigInfo'): + value = gui.GetConfigInfo(self, category, subcat) + if value: + return value + # # List creation # def Create(self, name, admin, crypted_password, langs=None, emailhost=None, urlhost=None): - # Ensure name is a string - if isinstance(name, bytes): - try: - # Try Latin-1 first since that's what we're seeing in the data - name = name.decode('latin-1', 'replace') - except UnicodeDecodeError: - # Fall back to UTF-8 if Latin-1 fails - name = name.decode('utf-8', 'replace') - if name != name.lower(): - raise ValueError('List name must be all lower case.') + assert name == name.lower(), 'List name must be all lower case.' if Utils.list_exists(name): raise Errors.MMListAlreadyExistsError(name) # Problems and potential attacks can occur if the list name in the # pipe to the wrapper in an MTA alias or other delivery process # contains shell special characters so allow only defined characters # (default = '[-+_.=a-z0-9]'). - if len(re.sub(r'^' + mm_cfg.ACCEPTABLE_LISTNAME_CHARACTERS + r'+$', '', name, flags=re.IGNORECASE)) > 0: + if len(re.sub(mm_cfg.ACCEPTABLE_LISTNAME_CHARACTERS, '', name)) > 0: raise Errors.BadListNameError(name) # Validate what will be the list's posting address. If that's # invalid, we don't want to create the mailing list. The hostname @@ -717,45 +617,56 @@ def Create(self, name, admin, crypted_password, self.available_languages = langs + # # Database and filesystem I/O # - def __save(self, dbfile, dict): - # Save the dictionary to the specified database file. We always save - # using pickle, even if the file was originally a marshal file. This - # is because pickle is guaranteed to be compatible across Python - # versions, while marshal is not. - # - # On success return None. On error, return the error object. + def __save(self, dict): + # Save the file as a binary pickle, and rotate the old version to a + # backup file. We must guarantee that config.pck is always valid so + # we never rotate unless the we've successfully written the temp file. + # We use pickle now because marshal is not guaranteed to be compatible + # between Python versions. + fname = os.path.join(self.fullpath(), 'config.pck') + fname_tmp = fname + '.tmp.%s.%d' % (socket.gethostname(), os.getpid()) + fname_last = fname + '.last' + fp = None + try: + fp = open(fname_tmp, 'wb') + # Use a binary format... it's more efficient. + pickle.dump(dict, fp, 1) + fp.flush() + if mm_cfg.SYNC_AFTER_WRITE: + os.fsync(fp.fileno()) + fp.close() + except IOError as e: + syslog('error', + 'Failed config.pck write, retaining old state.\n%s', e) + if fp is not None: + os.unlink(fname_tmp) + raise + # Now do config.pck.tmp.xxx -> config.pck -> config.pck.last rotation + # as safely as possible. try: - # Save using the utility function with protocol 4 - save_pickle_file(dbfile, dict, protocol=4) - # Update the timestamp - self.__timestamp = os.path.getmtime(dbfile) - return None - except Exception as e: - syslog('error', 'Failed to save database file %s: %s', dbfile, str(e)) - return e + # might not exist yet + os.unlink(fname_last) + except OSError as e: + if e.errno != errno.ENOENT: raise + try: + # might not exist yet + os.link(fname, fname_last) + except OSError as e: + if e.errno != errno.ENOENT: raise + os.rename(fname_tmp, fname) + # Reset the timestamp + self.__timestamp = os.path.getmtime(fname) def Save(self): - """Save the mailing list's configuration to disk. - - This method refreshes the lock and saves all public attributes to disk. - It handles lock errors gracefully and ensures proper cleanup. - """ # Refresh the lock, just to let other processes know we're still # interested in it. This will raise a NotLockedError if we don't have # the lock (which is a serious problem!). TBD: do we need to be more # defensive? - try: - self.__lock.refresh() - except NotLockedError: - # Lock was lost, try to reacquire it - try: - self.__lock.lock(timeout=10) # Give it 10 seconds to acquire - except (AlreadyLockedError, TimeOutError) as e: - syslog('error', 'Could not reacquire lock during Save(): %s', str(e)) - raise + self.__lock.refresh() # copy all public attributes to serializable dictionary dict = {} for key, value in list(self.__dict__.items()): @@ -766,7 +677,7 @@ def Save(self): # list members' passwords (in clear text). omask = os.umask(0o007) try: - self.__save(os.path.join(self.fullpath(), 'config.pck'), dict) + self.__save(dict) finally: os.umask(omask) self.SaveRequestsDb() @@ -786,23 +697,9 @@ def __load(self, dbfile): if dbfile.endswith('.db') or dbfile.endswith('.db.last'): loadfunc = marshal.load elif dbfile.endswith('.pck') or dbfile.endswith('.pck.last'): - def loadfunc(fp): - try: - # Get the protocol version - protocol = get_pickle_protocol(fp.name) - if protocol is not None: - print(C_('List %(listname)s %(dbfile)s uses pickle protocol %(protocol)d') % { - 'listname': self.internal_name(), - 'dbfile': os.path.basename(dbfile), - 'protocol': protocol - }) - # Use the utility function to load the pickle - return load_pickle_file(fp.name) - except Exception as e: - syslog('error', 'Failed to load pickle file %s: %r', dbfile, e) - raise + loadfunc = pickle.load else: - raise ValueError('Bad database file name') + assert 0, 'Bad database file name' try: # Check the mod time of the file first. If it matches our # timestamp, then the state hasn't change since the last time we @@ -822,376 +719,245 @@ def loadfunc(fp): if mtime < self.__timestamp: # File is not newer return None, None - # Open the file in binary mode to avoid any text decoding - fp = open(dbfile, 'rb') + fp = open(dbfile, mode='rb') except EnvironmentError as e: - if e.errno != errno.ENOENT: - raise + if e.errno != errno.ENOENT: raise # The file doesn't exist yet return None, e - + now = int(time.time()) try: - dict = loadfunc(fp) - fp.close() - return dict, None - except Exception as e: + try: + if dbfile.endswith('.db') or dbfile.endswith('.db.last'): + dict_retval = marshal.load(fp) + elif dbfile.endswith('.pck') or dbfile.endswith('.pck.last'): + dict_retval = Utils.load_pickle(dbfile) + if not isinstance(dict_retval, dict): + return None, 'Load() expected to return a dictionary' + except (EOFError, ValueError, TypeError, MemoryError, + pickle.PicklingError, pickle.UnpicklingError) as e: + return None, e + finally: fp.close() - syslog('error', 'Failed to load database file %s: %r', dbfile, e) - return None, e + # Update the timestamp. We use current time here rather than mtime + # so the test above might succeed the next time. And we get the time + # before unpickling in case it takes more than a second. (LP: #266464) + self.__timestamp = now + return dict_retval, None def Load(self, check_version=True): - """Load the database file.""" - # We want to check the version number of the database file, but we - # don't want to do this more than once per process. We use a class - # attribute to decide whether we need to check the version or not. - # Note that this is a bit of a hack because we use the class - # attribute to store state information. We could use a global - # variable, but that would be even worse. - if check_version: - self.CheckVersion() - # Load the database file. If it doesn't exist yet, we'll get an - # EnvironmentError with errno set to ENOENT. If it exists but is - # corrupt, we'll get an IOError. In either case, we want to try to - # load the backup file. - fname = os.path.join(self.fullpath(), 'config.pck') - fname_last = fname + '.last' - dict, e = self.__load(fname) - if dict is None and e is not None: - # Try loading the backup file. - dict, e = self.__load(fname_last) - if dict is None and e is not None: - # Both files are corrupt or non-existent. If they're - # corrupt, we want to raise an error. If they're - # non-existent, we want to return an empty dictionary. - if isinstance(e, EnvironmentError) and e.errno == errno.ENOENT: - dict = {} + if not Utils.list_exists(self.internal_name()): + raise Errors.MMUnknownListError + # We first try to load config.pck, which contains the up-to-date + # version of the database. If that fails, perhaps because it's + # corrupted or missing, we'll try to load the backup file + # config.pck.last. + # + # Should both of those fail, we'll look for config.db and + # config.db.last for backwards compatibility with pre-2.1alpha3 + pfile = os.path.join(self.fullpath(), 'config.pck') + plast = pfile + '.last' + dfile = os.path.join(self.fullpath(), 'config.db') + dlast = dfile + '.last' + for file in (pfile, plast, dfile, dlast): + dict_retval, e = self.__load(file) + if dict_retval is None: + if e is not None: + # Had problems with this file; log it and try the next one. + syslog('error', "couldn't load config file %s\n%s", + file, e) + else: + # We already have the most up-to-date state + return else: - raise Errors.MMCorruptListDatabaseError(self.internal_name()) - # Now update our current state with the database state. - for k, v in list(dict.items()): - if k[0] != '_': - setattr(self, k, v) - # Set the timestamp to the current time. - self.__timestamp = os.path.getmtime(fname) - - def CheckVersion(self): - """Check the version of the list's config database. + break + else: + # Nothing worked, so we have to give up + syslog('error', 'All %s fallbacks were corrupt, giving up', + self.internal_name()) + raise Errors.MMCorruptListDatabaseError(e) + # Now, if we didn't end up using the primary database file, we want to + # copy the fallback into the primary so that the logic in Save() will + # still work. For giggles, we'll copy it to a safety backup. Note we + # MUST do this with the underlying list lock acquired. + if file == plast or file == dlast: + syslog('error', 'fixing corrupt config file, using: %s', file) + unlock = True + try: + try: + self.__lock.lock() + except LockFile.AlreadyLockedError: + unlock = False + self.__fix_corrupt_pckfile(file, pfile, plast, dfile, dlast) + finally: + if unlock: + self.__lock.unlock() + # Copy the loaded dictionary into the attributes of the current + # mailing list object, then run sanity check on the data. + self.__dict__.update(dict_retval) + if check_version: + self.CheckVersion(dict_retval) + self.CheckValues() + + def __fix_corrupt_pckfile(self, file, pfile, plast, dfile, dlast): + if file == plast: + # Move aside any existing pickle file and delete any existing + # safety file. This avoids EPERM errors inside the shutil.copy() + # calls if those files exist with different ownership. + try: + os.rename(pfile, pfile + '.corrupt') + except OSError as e: + if e.errno != errno.ENOENT: raise + try: + os.remove(pfile + '.safety') + except OSError as e: + if e.errno != errno.ENOENT: raise + shutil.copy(file, pfile) + shutil.copy(file, pfile + '.safety') + elif file == dlast: + # Move aside any existing marshal file and delete any existing + # safety file. This avoids EPERM errors inside the shutil.copy() + # calls if those files exist with different ownership. + try: + os.rename(dfile, dfile + '.corrupt') + except OSError as e: + if e.errno != errno.ENOENT: raise + try: + os.remove(dfile + '.safety') + except OSError as e: + if e.errno != errno.ENOENT: raise + shutil.copy(file, dfile) + shutil.copy(file, dfile + '.safety') - If the database version is not current, update the database format. - This includes ensuring that pickle files are saved with protocol 4 - for Python 2/3 compatibility. - """ - # Increment this variable when the database format changes. This allows - # for a bit more graceful recovery when upgrading. BAW: This algorithm - # sucks. We really should be using a version number on the class and - # marshalling and unmarshalling based on that. This should be fixed by - # MM3.0. - data_version = getattr(self, 'data_version', 0) - if data_version >= mm_cfg.DATA_FILE_VERSION: - # Even if the data version is current, ensure we're using protocol 4 - # for pickle files by saving the current state - self.Save() + + # + # Sanity checks + # + def CheckVersion(self, stored_state): + """Auto-update schema if necessary.""" + if self.data_version >= mm_cfg.DATA_FILE_VERSION: return - - # Pre-2.1a3 versions did not have a data_version - if data_version == 0: - # First, convert to all lowercase - keys = list(self.__dict__.keys()) - for k in keys: - self.__dict__[k.lower()] = self.__dict__.pop(k) - # Then look for old names and convert - for oldname, newname in (('num_members', 'member_count'), - ('num_digest_members', 'digest_member_count'), - ('closed', 'subscribe_policy'), - ('mlist', 'real_name'), - ('msg_text', 'msg_footer'), - ('msg_headers', 'msg_header'), - ('digest_msg_text', 'digest_footer'), - ('digest_headers', 'digest_header'), - ('posters', 'accept_these_nonmembers'), - ('members_list', 'members'), - ('digest_members_list', 'digest_members'), - ('passwords', 'member_passwords'), - ('bad_posters', 'hold_these_nonmembers'), - ('topics_list', 'topics'), - ('topics_usernames', 'topics_userinterest'), - ('bounce_info', 'bounce_info'), - ('delivery_status', 'delivery_status'), - ('usernames', 'usernames'), - ('sender_filter_bypass', 'accept_these_nonmembers'), - ('admin_member_chunksize', 'admin_member_chunksize'), - ('administrivia', 'administrivia'), - ('advertised', 'advertised'), - ('anonymous_list', 'anonymous_list'), - ('auto_subscribe', 'auto_subscribe'), - ('bounce_matching_headers', 'bounce_matching_headers'), - ('bounce_processing', 'bounce_processing'), - ('convert_html_to_plaintext', 'convert_html_to_plaintext'), - ('digestable', 'digestable'), - ('digest_is_default', 'digest_is_default'), - ('digest_size_threshhold', 'digest_size_threshhold'), - ('filter_content', 'filter_content'), - ('generic_nonmember_action', 'generic_nonmember_action'), - ('include_list_post_header', 'include_list_post_header'), - ('include_rfc2369_headers', 'include_rfc2369_headers'), - ('max_message_size', 'max_message_size'), - ('max_num_recipients', 'max_num_recipients'), - ('member_moderation_notice', 'member_moderation_notice'), - ('mime_is_default_digest', 'mime_is_default_digest'), - ('moderator_password', 'moderator_password'), - ('next_digest_number', 'next_digest_number'), - ('nondigestable', 'nondigestable'), - ('nonmember_rejection_notice', 'nonmember_rejection_notice'), - ('obscure_addresses', 'obscure_addresses'), - ('owner_password', 'owner_password'), - ('post_password', 'post_password'), - ('private_roster', 'private_roster'), - ('real_name', 'real_name'), - ('reject_these_nonmembers', 'reject_these_nonmembers'), - ('reply_goes_to_list', 'reply_goes_to_list'), - ('reply_to_address', 'reply_to_address'), - ('require_explicit_destination', 'require_explicit_destination'), - ('send_reminders', 'send_reminders'), - ('send_welcome_msg', 'send_welcome_msg'), - ('subject_prefix', 'subject_prefix'), - ('topics', 'topics'), - ('topics_enabled', 'topics_enabled'), - ('umbrella_list', 'umbrella_list'), - ('unsubscribe_policy', 'unsubscribe_policy'), - ('volume', 'volume'), - ('web_page_url', 'web_page_url'), - ('welcome_msg', 'welcome_msg'), - ('gateway_to_mail', 'gateway_to_mail'), - ('gateway_to_news', 'gateway_to_news'), - ('linked_newsgroup', 'linked_newsgroup'), - ('nntp_host', 'nntp_host'), - ('news_moderation', 'news_moderation'), - ('news_prefix_subject_too', 'news_prefix_subject_too'), - ('archive', 'archive'), - ('archive_private', 'archive_private'), - ('archive_volume_frequency', 'archive_volume_frequency'), - ('clobber_date', 'clobber_date'), - ('convert_html_to_plaintext', 'convert_html_to_plaintext'), - ('filter_content', 'filter_content'), - ('hold_these_nonmembers', 'hold_these_nonmembers'), - ('linked_newsgroup', 'linked_newsgroup'), - ('max_message_size', 'max_message_size'), - ('max_num_recipients', 'max_num_recipients'), - ('news_prefix_subject_too', 'news_prefix_subject_too'), - ('nntp_host', 'nntp_host'), - ('obscure_addresses', 'obscure_addresses'), - ('private_roster', 'private_roster'), - ('real_name', 'real_name'), - ('subject_prefix', 'subject_prefix'), - ('topics', 'topics'), - ('topics_enabled', 'topics_enabled'), - ('web_page_url', 'web_page_url')): - if oldname in self.__dict__: - self.__dict__[newname] = self.__dict__.pop(oldname) - # Convert the data version number + # Initialize any new variables + self.InitVars() + # Then reload the database (but don't recurse). Force a reload even + # if we have the most up-to-date state. + self.__timestamp = 0 + self.Load(check_version=0) + # We must hold the list lock in order to update the schema + waslocked = self.Locked() + if not waslocked: + self.Lock() + try: + from .versions import Update + Update(self, stored_state) self.data_version = mm_cfg.DATA_FILE_VERSION + self.Save() + finally: + if not waslocked: + self.Unlock() - def GetPattern(self, addr, patterns, at_list=None): - """Check if an address matches any of the patterns in the list. - - Args: - addr: The email address to check - patterns: List of patterns to check against - at_list: Optional name of the list for logging - - Returns: - True if the address matches any pattern, False otherwise - """ - if not patterns: - return False - - # Convert addr to lowercase for case-insensitive matching - addr = addr.lower() - - # Check each pattern - for pattern in patterns: - # Skip empty patterns - if not pattern.strip(): - continue - - # If pattern starts with @, it's a domain pattern - if pattern.startswith('@'): - domain = pattern[1:].lower() - if addr.endswith(domain): - if at_list: - syslog('vette', '%s matches domain pattern %s in %s', - addr, pattern, at_list) - return True - # Otherwise it's a regex pattern - else: + def CheckValues(self): + """Normalize selected values to known formats.""" + if '' in urlparse(self.web_page_url)[:2]: + # Either the "scheme" or the "network location" part of the parsed + # URL is empty; substitute faulty value with (hopefully sane) + # default. Note that DEFAULT_URL is obsolete. + self.web_page_url = ( + mm_cfg.DEFAULT_URL or + mm_cfg.DEFAULT_URL_PATTERN % mm_cfg.DEFAULT_URL_HOST) + if self.web_page_url and self.web_page_url[-1] != '/': + self.web_page_url = self.web_page_url + '/' + # Legacy reply_to_address could be an illegal value. We now verify + # upon setting and don't check it at the point of use. + try: + if self.reply_to_address.strip() and self.reply_goes_to_list: + Utils.ValidateEmail(self.reply_to_address) + except Errors.EmailAddressError: + syslog('error', 'Bad reply_to_address "%s" cleared for list: %s', + self.reply_to_address, self.internal_name()) + self.reply_to_address = '' + self.reply_goes_to_list = 0 + # Legacy topics may have bad regular expressions in their patterns + # Also, someone may have broken topics with, e.g., config_list. + goodtopics = [] + # Check if topics attribute exists before trying to access it + if hasattr(self, 'topics'): + for value in self.topics: try: - cre = re.compile(pattern, re.IGNORECASE) - if cre.search(addr): - if at_list: - syslog('vette', '%s matches regex pattern %s in %s', - addr, pattern, at_list) - return True - except re.error: - syslog('error', 'Invalid regex pattern in %s: %s', - at_list or 'patterns', pattern) + name, pattern, desc, emptyflag = value + except ValueError: + # This value is not a 4-tuple. Just log and drop it. + syslog('error', 'Bad topic "%s" for list: %s', + value, self.internal_name()) continue - - return False - - def HasExplicitDest(self, msg): - """Check if the message has an explicit destination. - - Args: - msg: The email message to check - - Returns: - True if the message has an explicit destination, False otherwise - """ - # Check if the message has a To: or Cc: header - if msg.get('to') or msg.get('cc'): - return True - - # Check if the message has a Resent-To: or Resent-Cc: header - if msg.get('resent-to') or msg.get('resent-cc'): - return True - - # Check if the message has a Delivered-To: header - if msg.get('delivered-to'): - return True - - return False - - def parse_matching_header_opt(self): - """Return a list of triples [(field name, regex, line), ...]. - - Returns: - A list of tuples containing (header name, compiled regex, original line) - """ - # - Blank lines and lines with '#' as first char are skipped. - # - Leading whitespace in the matchexp is trimmed - you can defeat - # that by, eg, containing it in gratuitous square brackets. - all = [] - for line in self.bounce_matching_headers.split('\n'): - line = line.strip() - # Skip blank lines and lines *starting* with a '#'. - if not line or line[0] == "#": - continue - i = line.find(':') - if i < 0: - # This didn't look like a header line - syslog('config', 'bad bounce_matching_header line: %s\n%s', - self.real_name, line) - else: - header = line[:i] - value = line[i+1:].lstrip() try: - cre = re.compile(value, re.IGNORECASE) - except re.error as e: - # The regexp was malformed - syslog('config', '''\ -bad regexp in bounce_matching_header line: %s -\n%s (cause: %s)''', self.real_name, value, e) + orpattern = OR.join(pattern.splitlines()) + re.compile(orpattern) + except (re.error, TypeError): + syslog('error', 'Bad topic pattern "%s" for list: %s', + orpattern, self.internal_name()) else: - all.append((header, cre, line)) - return all + goodtopics.append((name, pattern, desc, emptyflag)) + self.topics = goodtopics - def hasMatchingHeader(self, msg): - """Return true if named header field matches a regexp in the - bounce_matching_header list variable. - - Returns: - The matching line if found, False otherwise + + # + # Membership management front-ends and assertion checks + # + def CheckPending(self, email, unsub=False): + """Check if there is already an unexpired pending (un)subscription for + this email. """ - if not self.bounce_matching_headers: + if not mm_cfg.REFUSE_SECOND_PENDING: return False - - for header, cre, line in self.parse_matching_header_opt(): - for value in msg.get_all(header, []): - if cre.search(value): - syslog('vette', 'Message header %s matches pattern %s', - header, line) - return line + pends = self._Pending__load() + # Save and reload the db to evict expired pendings. + self._Pending__save(pends) + pends = self._Pending__load() + for k, v in list(pends.items()): + if k in ('evictions', 'version'): + continue + op, data = v[:2] + if (op == Pending.SUBSCRIPTION and not unsub and + data.address.lower() == email.lower() or + op == Pending.UNSUBSCRIPTION and unsub and + data.lower() == email.lower()): + return True return False - def _ListAdmin__nextid(self): - """Generate the next unique ID for a held message. - - Returns: - An integer containing the next unique ID - """ - # Get the next ID number - nextid = getattr(self, '_ListAdmin__nextid_counter', 0) + 1 - # Store the next ID number - self._ListAdmin__nextid_counter = nextid - # Return just the counter number - return nextid + def InviteNewMember(self, userdesc, text=''): + """Invite a new member to the list. - def ConfirmUnsubscription(self, addr, lang=None, remote=None): - """Confirm an unsubscription request. - - :param addr: The address to unsubscribe. - :type addr: string - :param lang: The language to use for the confirmation message. - :type lang: string - :param remote: The remote address making the request. - :type remote: string - :raises: MMAlreadyPending if there's already a pending request + This is done by creating a subscription pending for the user, and then + crafting a message to the member informing them of the invitation. """ - # Make sure we have a lock - assert self._locked, 'List must be locked before pending operations' - - # Get the member's language if not specified - if lang is None: - lang = self.getMemberLanguage(addr) - - # Create a pending request - cookie = self.pend_new(Pending.UNSUBSCRIPTION, addr) - - # Craft the confirmation message - d = { - 'listname': self.real_name, - 'email': addr, - 'listaddr': self.GetListEmail(), - 'remote': remote and f'from {remote}' or '', - 'confirmurl': '%s/%s' % (self.GetScriptURL('confirm', absolute=1), cookie), - 'requestaddr': self.GetRequestEmail(cookie), - 'cookie': cookie, - 'listadmin': self.GetOwnerEmail(), - } - - # Send the confirmation message - subject = self.GetConfirmLeaveSubject(self.real_name, cookie) - text = Utils.maketext('unsub.txt', d, lang=lang, mlist=self) - msg = Message.UserNotification(addr, self.GetRequestEmail(cookie), - subject, text, lang) - msg.send(self) - - return cookie - - def InviteNewMember(self, userdesc, text=''): - """Invite a new member to the list.""" invitee = userdesc.address Utils.ValidateEmail(invitee) + # check for banned address pattern = self.GetBannedPattern(invitee) if pattern: syslog('vette', '%s banned invitation: %s (matched: %s)', self.real_name, invitee, pattern) raise Errors.MembershipIsBanned(pattern) + # Hack alert! Squirrel away a flag that only invitations have, so + # that we can do something slightly different when an invitation + # subscription is confirmed. In those cases, we don't need further + # admin approval, even if the list is so configured. The flag is the + # list name to prevent invitees from cross-subscribing. userdesc.invitation = self.internal_name() - cookie = self.pend_new(Pending.SUBSCRIPTION, - (userdesc, None)) + cookie = self.pend_new(Pending.SUBSCRIPTION, userdesc) requestaddr = self.getListAddress('request') - confirmurl = '%s/%s' % (self.GetScriptURL('confirm', absolute=1), cookie) + confirmurl = '%s/%s' % (self.GetScriptURL('confirm', absolute=1), + cookie) listname = self.real_name text += Utils.maketext( 'invite.txt', - {'email': invitee, - 'listname': listname, - 'hostname': self.host_name, - 'confirmurl': confirmurl, + {'email' : invitee, + 'listname' : listname, + 'hostname' : self.host_name, + 'confirmurl' : confirmurl, 'requestaddr': requestaddr, - 'cookie': cookie, - 'listowner': self.GetOwnerEmail(), + 'cookie' : cookie, + 'listowner' : self.GetOwnerEmail(), }, mlist=self) sender = self.GetRequestEmail(cookie) msg = Message.UserNotification( @@ -1205,70 +971,177 @@ def InviteNewMember(self, userdesc, text=''): msg.send(self) def AddMember(self, userdesc, remote=None): - """Add a new member to the list. + """Front end to member subscription. + + This method enforces subscription policy, validates values, sends + notifications, and any other grunt work involved in subscribing a + user. It eventually calls ApprovedAddMember() to do the actual work + of subscribing the user. + + userdesc is an instance with the following public attributes: - Args: - userdesc: A UserDesc object containing the member's information - remote: Optional remote address making the request + address -- the unvalidated email address of the member + fullname -- the member's full name (i.e. John Smith) + digest -- a flag indicating whether the user wants digests or not + language -- the requested default language for the user + password -- the user's password + + Other attributes may be defined later. Only address is required; the + others all have defaults (fullname='', digests=0, language=list's + preferred language, password=generated). + + remote is a string which describes where this add request came from. """ - # Make sure we have a lock - if not self.Locked(): - raise LockFile.NotLockedError( - 'List must be locked before pending operations') - - # Get the member's email address - email = userdesc.address - - # Ensure language is set - if not hasattr(userdesc, 'language') or userdesc.language is None: - userdesc.language = self.preferred_language - - # If we need confirmation, pend the subscription - if self.subscribe_policy in (2, 3) and not self.HasAutoApprovedSender(email): - # Pend the subscription - cookie = self.pend_new(Pending.SUBSCRIPTION, - (userdesc, remote)) + assert self.Locked() + # Suck values out of userdesc, apply defaults, and reset the userdesc + # attributes (for passing on to ApprovedAddMember()). Lowercase the + # addr's domain part. + email = Utils.LCDomain(userdesc.address) + name = getattr(userdesc, 'fullname', '') + lang = getattr(userdesc, 'language', self.preferred_language) + digest = getattr(userdesc, 'digest', None) + password = getattr(userdesc, 'password', Utils.MakeRandomPassword()) + if digest is None: + if self.nondigestable: + digest = 0 + else: + digest = 1 + # Validate the e-mail address to some degree. + Utils.ValidateEmail(email) + if self.isMember(email): + raise Errors.MMAlreadyAMember(email) + if self.CheckPending(email): + raise Errors.MMAlreadyPending(email) + if email.lower() == self.GetListEmail().lower(): + # Trying to subscribe the list to itself! + raise Errors.MMBadEmailError + realname = self.real_name + # Is the subscribing address banned from this list? + pattern = self.GetBannedPattern(email) + if pattern: + if remote: + whence = ' from %s' % remote + else: + whence = '' + syslog('vette', '%s banned subscription: %s%s (matched: %s)', + realname, email, whence, pattern) + raise Errors.MembershipIsBanned(pattern) + # See if this is from a spamhaus listed IP. + if remote and mm_cfg.BLOCK_SPAMHAUS_LISTED_IP_SUBSCRIBE: + if Utils.banned_ip(remote): + whence = ' from %s' % remote + syslog('vette', '%s banned subscription: %s%s (Spamhaus IP)', + realname, email, whence) + raise Errors.MembershipIsBanned('Spamhaus IP') + # See if this is from a spamhaus listed domain. + if email and mm_cfg.BLOCK_SPAMHAUS_LISTED_DBL_SUBSCRIBE: + if Utils.banned_domain(email): + syslog('vette', '%s banned subscription: %s (Spamhaus DBL)', + realname, email) + raise Errors.MembershipIsBanned('Spamhaus DBL') + # Sanity check the digest flag + if digest and not self.digestable: + raise Errors.MMCantDigestError + elif not digest and not self.nondigestable: + raise Errors.MMMustDigestError + + userdesc.address = email + userdesc.fullname = name + userdesc.digest = digest + userdesc.language = lang + userdesc.password = password + + # Apply the list's subscription policy. 0 means open subscriptions; 1 + # means the user must confirm; 2 means the admin must approve; 3 means + # the user must confirm and then the admin must approve + if self.subscribe_policy == 0: + self.ApprovedAddMember(userdesc, whence=remote or '') + elif self.subscribe_policy == 1 or self.subscribe_policy == 3: + # User confirmation required. BAW: this should probably just + # accept a userdesc instance. + cookie = self.pend_new(Pending.SUBSCRIPTION, userdesc) + # Send the user the confirmation mailback + if remote is None: + oremote = by = remote = '' + else: + oremote = remote + by = ' ' + remote + remote = _(' from %(remote)s') + + recipient = self.GetMemberAdminEmail(email) confirmurl = '%s/%s' % (self.GetScriptURL('confirm', absolute=1), cookie) - lang = userdesc.language text = Utils.maketext( 'verify.txt', - {'email' : email, - 'listaddr' : self.GetListEmail(), - 'listname' : self.real_name, - 'cookie' : cookie, - 'requestaddr': self.getListAddress('request'), - 'remote' : remote or '', - 'listadmin' : self.GetOwnerEmail(), - 'confirmurl' : confirmurl, + {'email' : email, + 'listaddr' : self.GetListEmail(), + 'listname' : realname, + 'cookie' : cookie, + 'requestaddr' : self.getListAddress('request'), + 'remote' : remote, + 'listadmin' : self.GetOwnerEmail(), + 'confirmurl' : confirmurl, }, lang=lang, mlist=self) - # BAW: We don't pass the Subject: into the UserNotification - # constructor because it will encode it in the charset of the language - # being used. For non-us-ascii charsets, this means it will probably - # quopri quote it, and thus replies will also be quopri encoded. But - # CommandRunner doesn't yet grok such headers. So, just set the - # Subject: in a separate step, although we have to delete the one - # UserNotification adds. msg = Message.UserNotification( - email, self.GetRequestEmail(cookie), + recipient, self.GetRequestEmail(cookie), text=text, lang=lang) + # BAW: See ChangeMemberAddress() for why we do it this way... del msg['subject'] - msg['Subject'] = self.GetConfirmJoinSubject(self.real_name, cookie) + msg['Subject'] = self.GetConfirmJoinSubject(realname, cookie) msg['Reply-To'] = self.GetRequestEmail(cookie) + # Is this confirmation a reply to an email subscribe from this + # address? + if oremote.lower().endswith(email.lower()): + autosub = 'auto-replied' + else: + autosub = 'auto-generated' + del msg['auto-submitted'] + msg['Auto-Submitted'] = autosub msg.send(self) - return - # If we get here, we can add the member directly - self.ApprovedAddMember(userdesc, whence=remote or '') + # formataddr() expects a str and does its own encoding + if isinstance(name, bytes): + name = name.decode(Utils.GetCharSet(lang)) + + who = formataddr((name, email)) + syslog('subscribe', '%s: pending %s %s', + self.internal_name(), who, by) + raise Errors.MMSubscribeNeedsConfirmation + elif self.HasAutoApprovedSender(email): + # no approval necessary: + self.ApprovedAddMember(userdesc) + else: + # Subscription approval is required. Add this entry to the admin + # requests database. BAW: this should probably take a userdesc + # just like above. + self.HoldSubscription(email, name, password, digest, lang) + raise Errors.MMNeedApproval( + 'subscriptions to %(realname)s require moderator approval') def ApprovedAddMember(self, userdesc, ack=None, admin_notif=None, text='', whence=''): - """Add a member right now.""" + """Add a member right now. + + The member's subscription must be approved by what ever policy the + list enforces. + + userdesc is as above in AddMember(). + + ack is a flag that specifies whether the user should get an + acknowledgement of their being subscribed. Default is to use the + list's default flag value. + + admin_notif is a flag that specifies whether the list owner should get + an acknowledgement of this subscription. Default is to use the list's + default flag value. + """ assert self.Locked() + # Set up default flag values if ack is None: ack = self.send_welcome_msg if admin_notif is None: admin_notif = self.admin_notify_mchanges + # Suck values out of userdesc, and apply defaults. email = Utils.LCDomain(userdesc.address) name = getattr(userdesc, 'fullname', '') lang = getattr(userdesc, 'language', self.preferred_language) @@ -1279,55 +1152,66 @@ def ApprovedAddMember(self, userdesc, ack=None, admin_notif=None, text='', digest = 0 else: digest = 1 + # Let's be extra cautious Utils.ValidateEmail(email) if self.isMember(email): raise Errors.MMAlreadyAMember(email) + # Check for banned address here too for admin mass subscribes + # and confirmations. pattern = self.GetBannedPattern(email) if pattern: - source = f' from {whence}' if whence else '' + if whence: + source = ' from %s' % whence + else: + source = '' syslog('vette', '%s banned subscription: %s%s (matched: %s)', self.real_name, email, source, pattern) raise Errors.MembershipIsBanned(pattern) + # Do the actual addition self.addNewMember(email, realname=name, digest=digest, password=password, language=lang) self.setMemberOption(email, mm_cfg.DisableMime, 1 - self.mime_is_default_digest) self.setMemberOption(email, mm_cfg.Moderate, self.default_member_moderation) - kind = ' (digest)' if digest else '' - - # Handle name encoding properly + # Now send and log results + if digest: + kind = ' (digest)' + else: + kind = '' + + # The formataddr() function, used in two places below, takes a str and performs + # its own encoding, so we should not allow the name to be pre-encoded. if isinstance(name, bytes): - try: - # Try to decode using the member's language charset - charset = Utils.GetCharSet(lang) - name = name.decode(charset, 'replace') - except (UnicodeDecodeError, LookupError): - # Fall back to latin-1 if the charset is not available - name = name.decode('latin-1', 'replace') - elif not isinstance(name, str): - name = str(name) - + name = name.decode(Utils.GetCharSet(lang)) + syslog('subscribe', '%s: new%s %s, %s', self.internal_name(), kind, formataddr((name, email)), whence) if ack: - self.SendSubscribeAck(email, self.getMemberPassword(email), - digest, text) + lang = self.preferred_language + otrans = i18n.get_translation() + i18n.set_language(lang) + try: + self.SendSubscribeAck(email, self.getMemberPassword(email), + digest, text) + finally: + i18n.set_translation(otrans) if admin_notif: lang = self.preferred_language otrans = i18n.get_translation() i18n.set_language(lang) try: - whence_str = "" if whence is None else f"({_(whence)})" + whence = "" if whence is None else "(" + _(whence) + ")" realname = self.real_name - subject = _('%(realname)s subscription notification') % {'realname': realname} + subject = _('%(realname)s subscription notification') finally: i18n.set_translation(otrans) + text = Utils.maketext( "adminsubscribeack.txt", - {"listname": realname, - "member": formataddr((name, email)), - "whence": whence_str + {"listname" : realname, + "member" : formataddr((name, email)), + "whence" : whence }, mlist=self) msg = Message.OwnerNotification(self, subject, text) msg.send(self) @@ -1340,40 +1224,46 @@ def DeleteMember(self, name, whence=None, admin_notif=None, userack=True): self.HoldUnsubscription(email) raise Errors.MMNeedApproval('unsubscriptions require moderator approval') - def ApprovedDeleteMember(self, name, whence=None, admin_notif=None, userack=None): + def ApprovedDeleteMember(self, name, whence=None, + admin_notif=None, userack=None): if userack is None: userack = self.send_goodbye_msg if admin_notif is None: admin_notif = self.admin_notify_mchanges + # Delete a member, for which we know the approval has been made fullname, emailaddr = parseaddr(name) userlang = self.getMemberLanguage(emailaddr) + # Remove the member self.removeMember(emailaddr) + # And send an acknowledgement to the user... if userack: self.SendUnsubscribeAck(emailaddr, userlang) + # ...and to the administrator in the correct language. (LP: #1308655) i18n.set_language(self.preferred_language) if admin_notif: realname = self.real_name - subject = _('%(realname)s unsubscribe notification') % {'realname': realname} + subject = _('%(realname)s unsubscribe notification') text = Utils.maketext( 'adminunsubscribeack.txt', - {'member': name, + {'member' : name, 'listname': self.real_name, - "whence": "" if whence is None else f"({_(whence)})" + "whence" : "" if whence is None else "(" + _(whence) + ")" }, mlist=self) msg = Message.OwnerNotification(self, subject, text) msg.send(self) if whence: - whence_str = f'; {whence}' + whence = "; %s" % whence else: - whence_str = '' + whence = "" syslog('subscribe', '%s: deleted %s%s', - self.internal_name(), name, whence_str) + self.internal_name(), name, whence) def ChangeMemberName(self, addr, name, globally): self.setMemberName(addr, name) if not globally: return for listname in Utils.list_names(): + # Don't bother with ourselves if listname == self.internal_name(): continue mlist = MailList(listname, lock=0) @@ -1389,20 +1279,32 @@ def ChangeMemberName(self, addr, name, globally): mlist.Unlock() def ChangeMemberAddress(self, oldaddr, newaddr, globally): + # Changing a member address consists of verifying the new address, + # making sure the new address isn't already a member, and optionally + # going through the confirmation process. + # + # Most of these checks are copied from AddMember newaddr = Utils.LCDomain(newaddr) Utils.ValidateEmail(newaddr) + # Raise an exception if this email address is already a member of the + # list, but only if the new address is the same case-wise as the + # existing member address and we're not doing a global change. if not globally and (self.isMember(newaddr) and newaddr == self.getMemberCPAddress(newaddr)): raise Errors.MMAlreadyAMember if newaddr == self.GetListEmail().lower(): raise Errors.MMBadEmailError realname = self.real_name + # Don't allow changing to a banned address. MAS: maybe we should + # unsubscribe the oldaddr too just for trying, but that's probably + # too harsh. pattern = self.GetBannedPattern(newaddr) if pattern: syslog('vette', '%s banned address change: %s -> %s (matched: %s)', realname, oldaddr, newaddr, pattern) raise Errors.MembershipIsBanned(pattern) + # Pend the subscription change cookie = self.pend_new(Pending.CHANGE_OF_ADDRESS, oldaddr, newaddr, globally) confirmurl = '%s/%s' % (self.GetScriptURL('confirm', absolute=1), @@ -1410,15 +1312,22 @@ def ChangeMemberAddress(self, oldaddr, newaddr, globally): lang = self.getMemberLanguage(oldaddr) text = Utils.maketext( 'verify.txt', - {'email': newaddr, - 'listaddr': self.GetListEmail(), - 'listname': realname, - 'cookie': cookie, + {'email' : newaddr, + 'listaddr' : self.GetListEmail(), + 'listname' : realname, + 'cookie' : cookie, 'requestaddr': self.getListAddress('request'), - 'remote': '', - 'listadmin': self.GetOwnerEmail(), - 'confirmurl': confirmurl, + 'remote' : '', + 'listadmin' : self.GetOwnerEmail(), + 'confirmurl' : confirmurl, }, lang=lang, mlist=self) + # BAW: We don't pass the Subject: into the UserNotification + # constructor because it will encode it in the charset of the language + # being used. For non-us-ascii charsets, this means it will probably + # quopri quote it, and thus replies will also be quopri encoded. But + # CommandRunner doesn't yet grok such headers. So, just set the + # Subject: in a separate step, although we have to delete the one + # UserNotification adds. msg = Message.UserNotification( newaddr, self.GetRequestEmail(cookie), text=text, lang=lang) @@ -1428,22 +1337,38 @@ def ChangeMemberAddress(self, oldaddr, newaddr, globally): msg.send(self) def ApprovedChangeMemberAddress(self, oldaddr, newaddr, globally): + # Check here for banned address in case address was banned after + # confirmation was mailed. MAS: If it's global change should we just + # skip this list and proceed to the others? For now we'll throw the + # exception. pattern = self.GetBannedPattern(newaddr) if pattern: syslog('vette', '%s banned address change: %s -> %s (matched: %s)', self.real_name, oldaddr, newaddr, pattern) raise Errors.MembershipIsBanned(pattern) + # It's possible they were a member of this list, but choose to change + # their membership globally. In that case, we simply remove the old + # address. This gets tricky with case changes. We can't just remove + # the old address if it differs from the new only by case, because + # that removes the new, so the condition is if the new address is the + # CP address of a member, then if the old address yields a different + # CP address, we can simply remove the old address, otherwise we can + # do nothing. cpoldaddr = self.getMemberCPAddress(oldaddr) - if self.isMember(newaddr) and (self.getMemberCPAddress(newaddr) == newaddr): + if self.isMember(newaddr) and (self.getMemberCPAddress(newaddr) == + newaddr): if cpoldaddr != newaddr: self.removeMember(oldaddr) else: self.changeMemberAddress(oldaddr, newaddr) self.log_and_notify_admin(cpoldaddr, newaddr) + # If globally is true, then we also include every list for which + # oldaddr is a member. if not globally: return for listname in Utils.list_names(): + # Don't bother with ourselves if listname == self.internal_name(): continue mlist = MailList(listname, lock=0) @@ -1451,75 +1376,476 @@ def ApprovedChangeMemberAddress(self, oldaddr, newaddr, globally): continue if not mlist.isMember(oldaddr): continue + # If new address is banned from this list, just skip it. if mlist.GetBannedPattern(newaddr): continue mlist.Lock() try: - mlist.ApprovedChangeMemberAddress(oldaddr, newaddr, False) + # Same logic as above, re newaddr is already a member + cpoldaddr = mlist.getMemberCPAddress(oldaddr) + if mlist.isMember(newaddr) and ( + mlist.getMemberCPAddress(newaddr) == newaddr): + if cpoldaddr != newaddr: + mlist.removeMember(oldaddr) + else: + mlist.changeMemberAddress(oldaddr, newaddr) + mlist.log_and_notify_admin(cpoldaddr, newaddr) mlist.Save() finally: mlist.Unlock() def log_and_notify_admin(self, oldaddr, newaddr): - syslog('subscribe', '%s: changed address %s -> %s', + """Log member address change and notify admin if requested.""" + syslog('subscribe', '%s: changed member address from %s to %s', self.internal_name(), oldaddr, newaddr) + if self.admin_notify_mchanges: + lang = self.preferred_language + otrans = i18n.get_translation() + i18n.set_language(lang) + try: + realname = self.real_name + subject = _('%(realname)s address change notification') + finally: + i18n.set_translation(otrans) + name = self.getMemberName(newaddr) + if name is None: + name = '' + if isinstance(name, str): + name = name.encode(Utils.GetCharSet(lang), 'replace') + text = Utils.maketext( + 'adminaddrchgack.txt', + {'name' : name, + 'oldaddr' : oldaddr, + 'newaddr' : newaddr, + 'listname': self.real_name, + }, mlist=self) + msg = Message.OwnerNotification(self, subject, text) + msg.send(self) - def CheckPending(self, email, unsub=False): - """Check if there is already an unexpired pending (un)subscription for - this email. + + # + # Confirmation processing + # + def ProcessConfirmation(self, cookie, context=None): + global _ + rec = self.pend_confirm(cookie) + if rec is None: + raise Errors.MMBadConfirmation('No cookie record for %s' % cookie) + try: + op = rec[0] + data = rec[1:] + except ValueError: + raise Errors.MMBadConfirmation('op-less data %s' % (rec,)) + if op == Pending.SUBSCRIPTION: + _ = D_ + whence = _('via email confirmation') + try: + userdesc = data[0] + # If confirmation comes from the web, context should be a + # UserDesc instance which contains overrides of the original + # subscription information. If it comes from email, then + # context is a Message and isn't relevant, so ignore it. + if isinstance(context, UserDesc): + userdesc += context + whence = _('via web confirmation') + addr = userdesc.address + fullname = userdesc.fullname + password = userdesc.password + digest = userdesc.digest + lang = userdesc.language + except ValueError: + raise Errors.MMBadConfirmation('bad subscr data %s' % (data,)) + _ = i18n._ + # Hack alert! Was this a confirmation of an invitation? + invitation = getattr(userdesc, 'invitation', False) + # We check for both 2 (approval required) and 3 (confirm + + # approval) because the policy could have been changed in the + # middle of the confirmation dance. + if invitation: + if invitation != self.internal_name(): + # Not cool. The invitee was trying to subscribe to a + # different list than they were invited to. Alert both + # list administrators. + self.SendHostileSubscriptionNotice(invitation, addr) + raise Errors.HostileSubscriptionError + elif self.subscribe_policy in (2, 3) and \ + not self.HasAutoApprovedSender(addr): + self.HoldSubscription(addr, fullname, password, digest, lang) + name = self.real_name + raise Errors.MMNeedApproval( + 'subscriptions to %(name)s require administrator approval') + self.ApprovedAddMember(userdesc, whence=whence) + return op, addr, password, digest, lang + elif op == Pending.UNSUBSCRIPTION: + addr = data[0] + # Log file messages don't need to be i18n'd, but this is now in a + # notice. + _ = D_ + if isinstance(context, Message.Message): + whence = _('email confirmation') + else: + whence = _('web confirmation') + _ = i18n._ + # Can raise NotAMemberError if they unsub'd via other means + self.ApprovedDeleteMember(addr, whence=whence) + return op, addr + elif op == Pending.CHANGE_OF_ADDRESS: + oldaddr, newaddr, globally = data + self.ApprovedChangeMemberAddress(oldaddr, newaddr, globally) + return op, oldaddr, newaddr + elif op == Pending.HELD_MESSAGE: + id = data[0] + approved = None + # Confirmation should be coming from email, where context should + # be the confirming message. If the message does not have an + # Approved: header, this is a discard. If it has an Approved: + # header that does not match the list password, then we'll notify + # the list administrator that they used the wrong password. + # Otherwise it's an approval. + if isinstance(context, Message.Message): + # See if it's got an Approved: header, either in the headers, + # or in the first text/plain section of the response. For + # robustness, we'll accept Approve: as well. + approved = context.get('Approved', context.get('Approve')) + if not approved: + try: + subpart = list(email.iterators.typed_subpart_iterator( + context, 'text', 'plain'))[0] + except IndexError: + subpart = None + if subpart: + s = StringIO(subpart.get_payload(decode=True)) + while True: + line = s.readline() + if not line: + break + if not line.strip(): + continue + i = line.find(':') + if i > 0: + if (line[:i].strip().lower() == 'approve' or + line[:i].strip().lower() == 'approved'): + # then + approved = line[i+1:].strip() + break + # Is there an approved header? + if approved is not None: + # Does it match the list password? Note that we purposefully + # do not allow the site password here. + if self.Authenticate([mm_cfg.AuthListAdmin, + mm_cfg.AuthListModerator], + approved) != mm_cfg.UnAuthorized: + action = mm_cfg.APPROVE + else: + # The password didn't match. Re-pend the message and + # inform the list moderators about the problem. + self.pend_repend(cookie, rec) + raise Errors.MMBadPasswordError + else: + action = mm_cfg.DISCARD + try: + self.HandleRequest(id, action) + except KeyError: + # Most likely because the message has already been disposed of + # via the admindb page. + syslog('error', 'Could not process HELD_MESSAGE: %s', id) + return op, action + elif op == Pending.RE_ENABLE: + member = data[1] + self.setDeliveryStatus(member, MemberAdaptor.ENABLED) + return op, member + else: + assert 0, 'Bad op: %s' % op + + def ConfirmUnsubscription(self, addr, lang=None, remote=None): + if self.CheckPending(addr, unsub=True): + raise Errors.MMAlreadyPending(email) + if lang is None: + lang = self.getMemberLanguage(addr) + cookie = self.pend_new(Pending.UNSUBSCRIPTION, addr) + confirmurl = '%s/%s' % (self.GetScriptURL('confirm', absolute=1), + cookie) + realname = self.real_name + if remote is not None: + by = " " + remote + remote = _(" from %(remote)s") + else: + by = "" + remote = "" + text = Utils.maketext( + 'unsub.txt', + {'email' : addr, + 'listaddr' : self.GetListEmail(), + 'listname' : realname, + 'cookie' : cookie, + 'requestaddr' : self.getListAddress('request'), + 'remote' : remote, + 'listadmin' : self.GetOwnerEmail(), + 'confirmurl' : confirmurl, + }, lang=lang, mlist=self) + msg = Message.UserNotification( + addr, self.GetRequestEmail(cookie), + text=text, lang=lang) + # BAW: See ChangeMemberAddress() for why we do it this way... + del msg['subject'] + msg['Subject'] = self.GetConfirmLeaveSubject(realname, cookie) + msg['Reply-To'] = self.GetRequestEmail(cookie) + del msg['auto-submitted'] + msg['Auto-Submitted'] = 'auto-generated' + msg.send(self) + + + # + # Miscellaneous stuff + # + def HasExplicitDest(self, msg): + """True if list name or any acceptable_alias is included among the + addresses in the recipient headers. """ - if not mm_cfg.REFUSE_SECOND_PENDING: - return False - pends = self._Pending__load() - # Save and reload the db to evict expired pendings. - self._Pending__save(pends) - pends = self._Pending__load() - for k, v in list(pends.items()): - if k in ('evictions', 'version'): + # This is the list's full address. + listfullname = '%s@%s' % (self.internal_name(), self.host_name) + recips = [] + # Check all recipient addresses against the list's explicit addresses, + # specifically To: Cc: and Resent-to: + to = [] + for header in ('to', 'cc', 'resent-to', 'resent-cc'): + to.extend(getaddresses(msg.get_all(header, []))) + for fullname, addr in to: + # It's possible that if the header doesn't have a valid RFC 2822 + # value, we'll get None for the address. So skip it. + if addr is None: continue - op, data = v[:2] - if (op == Pending.SUBSCRIPTION and not unsub and - data.address.lower() == email.lower() or - op == Pending.UNSUBSCRIPTION and unsub and - data.lower() == email.lower()): + addr = addr.lower() + localpart = addr.split('@')[0] + if (# TBD: backwards compatibility: deprecated + localpart == self.internal_name() or + # exact match against the complete list address + addr == listfullname): return True + recips.append((addr, localpart)) + # Helper function used to match a pattern against an address. + def domatch(pattern, addr): + try: + if re.match(pattern, addr, re.IGNORECASE): + return True + except re.error: + # The pattern is a malformed regexp -- try matching safely, + # with all non-alphanumerics backslashed: + if re.match(re.escape(pattern), addr, re.IGNORECASE): + return True + return False + # Here's the current algorithm for matching acceptable_aliases: + # + # 1. If the pattern does not have an `@' in it, we first try matching + # it against just the localpart. This was the behavior prior to + # 2.0beta3, and is kept for backwards compatibility. (deprecated). + # + # 2. If that match fails, or the pattern does have an `@' in it, we + # try matching against the entire recip address. + aliases = self.acceptable_aliases.splitlines() + for addr, localpart in recips: + for alias in aliases: + stripped = alias.strip() + if not stripped: + # Ignore blank or empty lines + continue + if '@' not in stripped and domatch(stripped, localpart): + return True + if domatch(stripped, addr): + return True return False - def GetBannedPattern(self, email): - """Check if the email address matches any banned patterns. - - Args: - email: The email address to check - - Returns: - The matching pattern if found, None otherwise - """ - if not self.ban_list: - return None - - # Convert email to lowercase for case-insensitive matching - email = email.lower() - - # Check each pattern in the ban list - for pattern in self.ban_list: - # Skip empty patterns - if not pattern.strip(): + def parse_matching_header_opt(self): + """Return a list of triples [(field name, regex, line), ...].""" + # - Blank lines and lines with '#' as first char are skipped. + # - Leading whitespace in the matchexp is trimmed - you can defeat + # that by, eg, containing it in gratuitous square brackets. + all = [] + for line in self.bounce_matching_headers.split('\n'): + line = line.strip() + # Skip blank lines and lines *starting* with a '#'. + if not line or line[0] == "#": continue - - # If pattern starts with @, it's a domain pattern - if pattern.startswith('@'): - domain = pattern[1:].lower() - if email.endswith(domain): - return pattern - # Otherwise it's a regex pattern + i = line.find(':') + if i < 0: + # This didn't look like a header line. BAW: should do a + # better job of informing the list admin. + syslog('config', 'bad bounce_matching_header line: %s\n%s', + self.real_name, line) else: + header = line[:i] + value = line[i+1:].lstrip() try: - cre = re.compile(pattern, re.IGNORECASE) - if cre.search(email): - return pattern - except re.error: - syslog('error', 'Invalid regex pattern in ban_list: %s', - pattern) + cre = re.compile(value, re.IGNORECASE) + except re.error as e: + # The regexp was malformed. BAW: should do a better + # job of informing the list admin. + syslog('config', '''\ +bad regexp in bounce_matching_header line: %s +\n%s (cause: %s)''', self.real_name, value, e) + else: + all.append((header, cre, line)) + return all + + def hasMatchingHeader(self, msg): + """Return true if named header field matches a regexp in the + bounce_matching_header list variable. + + Returns constraint line which matches or empty string for no + matches. + """ + for header, cre, line in self.parse_matching_header_opt(): + for value in msg.get_all(header, []): + if cre.search(value): + return line + return 0 + + def autorespondToSender(self, sender, lang=None): + """Return true if Mailman should auto-respond to this sender. + + This is only consulted for messages sent to the -request address, or + for posting hold notifications, and serves only as a safety value for + mail loops with email 'bots. + """ + # language setting + if lang == None: + lang = self.preferred_language + i18n.set_language(lang) + # No limit + if mm_cfg.MAX_AUTORESPONSES_PER_DAY == 0: + return 1 + today = time.localtime()[:3] + info = self.hold_and_cmd_autoresponses.get(sender) + if info is None or info[0] != today: + # First time we've seen a -request/post-hold for this sender + self.hold_and_cmd_autoresponses[sender] = (today, 1) + # BAW: no check for MAX_AUTORESPONSES_PER_DAY <= 1 + return 1 + date, count = info + if count < 0: + # They've already hit the limit for today. + syslog('vette', '-request/hold autoresponse discarded for: %s', + sender) + return 0 + if count >= mm_cfg.MAX_AUTORESPONSES_PER_DAY: + syslog('vette', '-request/hold autoresponse limit hit for: %s', + sender) + self.hold_and_cmd_autoresponses[sender] = (today, -1) + # Send this notification message instead + text = Utils.maketext( + 'nomoretoday.txt', + {'sender' : sender, + 'listname': '%s@%s' % (self.real_name, self.host_name), + 'num' : count, + 'owneremail': self.GetOwnerEmail(), + }, + lang=lang) + msg = Message.UserNotification( + sender, self.GetOwnerEmail(), + _('Last autoresponse notification for today'), + text, lang=lang) + msg.send(self) + return 0 + self.hold_and_cmd_autoresponses[sender] = (today, count+1) + return 1 + + def GetBannedPattern(self, email): + """Returns matched entry in ban_list if email matches. + Otherwise returns None. + """ + return (self.GetPattern(email, self.ban_list) or + self.GetPattern(email, mm_cfg.GLOBAL_BAN_LIST) + ) + + def HasAutoApprovedSender(self, sender): + """Returns True and logs if sender matches address or pattern + or is a member of a referenced list in subscribe_auto_approval. + Otherwise returns False. + """ + auto_approve = False + if self.GetPattern(sender, + self.subscribe_auto_approval, + at_list='subscribe_auto_approval' + ): + auto_approve = True + syslog('vette', '%s: auto approved subscribe from %s', + self.internal_name(), sender) + return auto_approve + + def GetPattern(self, email, pattern_list, at_list=None): + """Returns matched entry in pattern_list if email matches. + Otherwise returns None. The at_list argument, if "true", + says process the @listname syntax and provides the name of + the list attribute for log messages. + """ + matched = None + # First strip out all the regular expressions and listnames because + # documentation says we do non-regexp first (Why?). + plainaddrs = [x.strip() for x in pattern_list if x.strip() and not + (x.startswith('^') or x.startswith('@'))] + addrdict = Utils.List2Dict(plainaddrs, foldcase=1) + if email.lower() in addrdict: + return email + for pattern in pattern_list: + if pattern.startswith('^'): + # This is a regular expression match + try: + if re.search(pattern, email, re.IGNORECASE): + matched = pattern + break + except re.error as e: + # BAW: we should probably remove this pattern + # The GUI won't add a bad regexp, but at least log it. + # The following kludge works because the ban_list stuff + # is the only caller with no at_list. + attr_name = at_list or 'ban_list' + syslog('error', + '%s in %s has bad regexp "%s": %s', + attr_name, + self.internal_name(), + pattern, + str(e) + ) + elif at_list and pattern.startswith('@'): + # XXX Needs to be reviewed for list@domain names. + # this refers to the members of another list in this + # installation. + mname = pattern[1:].lower().strip() + if mname == self.internal_name(): + # don't reference your own list + syslog('error', + '%s in %s references own list', + at_list, + self.internal_name()) continue - - return None + try: + mother = MailList(mname, lock = False) + except Errors.MMUnknownListError: + syslog('error', + '%s in %s references non-existent list %s', + at_list, + self.internal_name(), + mname + ) + continue + if mother.isMember(email.lower()): + matched = pattern + break + return matched + + + + # + # Multilingual (i18n) support + # + def GetAvailableLanguages(self): + langs = self.available_languages + # If we don't add this, and the site admin has never added any + # language support to the list, then the general admin page may have a + # blank field where the list owner is supposed to chose the list's + # preferred language. + if mm_cfg.DEFAULT_SERVER_LANGUAGE not in langs: + langs.append(mm_cfg.DEFAULT_SERVER_LANGUAGE) + # When testing, it's possible we've disabled a language, so just + # filter things out so we don't get tracebacks. + return [lang for lang in langs if lang in mm_cfg.LC_DESCRIPTIONS] diff --git a/Mailman/Mailbox.py b/Mailman/Mailbox.py index d428d8be..782bb84d 100644 --- a/Mailman/Mailbox.py +++ b/Mailman/Mailbox.py @@ -20,76 +20,58 @@ import sys import mailbox -from io import StringIO, BytesIO -from types import MethodType import email -import email.message -from email.message import Message from email.parser import Parser from email.errors import MessageParseError from email.generator import Generator from Mailman import mm_cfg from Mailman.Message import Message +from Mailman import Utils + def _safeparser(fp): try: - return email.message_from_file(fp, Mailman.Message.Message) + return email.message_from_binary_file(fp, Message) except MessageParseError: # Don't return None since that will stop a mailbox iterator return '' + + class Mailbox(mailbox.mbox): def __init__(self, fp): - # In Python 3, we need to handle both file objects and paths - if hasattr(fp, 'read') and hasattr(fp, 'write'): - # It's a file object, get its path - if hasattr(fp, 'name'): - path = fp.name - else: - # Create a temporary file if we don't have a path - import tempfile - path = tempfile.mktemp() - with open(path, 'w', encoding='utf-8') as f: - f.write(fp.read().decode('utf-8', 'replace')) - fp.seek(0) - else: - # It's a path string - path = fp - - # Initialize the parent class with the path - super().__init__(path, _safeparser) - # Store the file object if we have one - if hasattr(fp, 'read') and hasattr(fp, 'write'): - self.fp = fp - else: - # Open in text mode for writing - self.fp = open(path, 'a+', encoding='utf-8') + if not isinstance( fp, str ): + fp = fp.name + self.filepath = fp + mailbox.mbox.__init__(self, fp, _safeparser) # msg should be an rfc822 message or a subclass. def AppendMessage(self, msg): # Check the last character of the file and write a newline if it isn't # a newline (but not at the beginning of an empty file). - try: - self.fp.seek(-1, 2) - except IOError as e: - # Assume the file is empty. We can't portably test the error code - # returned, since it differs per platform. - pass - else: - if self.fp.read(1) != '\n': - self.fp.write('\n') - # Seek to the last char of the mailbox - self.fp.seek(0, 2) - - # Create a Generator instance to write the message to the file - g = Generator(self.fp, mangle_from_=False, maxheaderlen=0) - g.flatten(msg, unixfrom=True) - # Add one more trailing newline for separation with the next message - self.fp.write('\n') - + with open(self.filepath, 'r+') as fileh: + try: + fileh.seek(-1, 2) + except IOError as e: + # Assume the file is empty. We can't portably test the error code + # returned, since it differs per platform. + pass + else: + if fileh.read(1) != '\n': + fileh.write('\n') + # Seek to the last char of the mailbox + fileh.seek(0, 2) + # Create a Generator instance to write the message to the file + g = Generator(fileh) + Utils.set_cte_if_missing(msg) + g.flatten(msg, unixfrom=True) + # Add one more trailing newline for separation with the next message + # to be appended to the mbox. + print('\n', fileh) + # This stuff is used by pipermail.py:processUnixMailbox(). It provides an # opportunity for the built-in archiver to scrub archived messages of nasty # things like attachments and such... @@ -119,7 +101,9 @@ def __init__(self, fp, mlist): else: self._scrubber = None self._mlist = mlist - mailbox.PortableUnixMailbox.__init__(self, fp, _archfactory(self)) + if not isinstance(fp, str): + fp = fp.name + mailbox.mbox.__init__(self, fp, _archfactory(self)) def scrub(self, msg): if self._scrubber: diff --git a/Mailman/Message.py b/Mailman/Message.py index 6f3b1b63..d75e5a52 100644 --- a/Mailman/Message.py +++ b/Mailman/Message.py @@ -21,32 +21,28 @@ which is more convenient for use inside Mailman. """ -from builtins import object import re from io import StringIO -import time -import hashlib import email import email.generator import email.utils from email.charset import Charset from email.header import Header -from email.message import Message as EmailMessage from Mailman import mm_cfg -from Mailman.Utils import GetCharSet, unique_message_id, get_site_email -from Mailman.Logging.Syslog import mailman_log +from Mailman import Utils COMMASPACE = ', ' if hasattr(email, '__version__'): mo = re.match(r'([\d.]+)', email.__version__) else: - mo = re.match(r'([\d.]+)', '2.1.39') # XXX should use @@MM_VERSION@@ perhaps? + mo = re.match(r'([\d.]+)', '2.2.0') # XXX should use @@MM_VERSION@@ perhaps? VERSION = tuple([int(s) for s in mo.group().split('.')]) + class Generator(email.generator.Generator): """Generates output from a Message object tree, keeping signatures. @@ -64,14 +60,17 @@ def clone(self, fp): self.__children_maxheaderlen, self.__children_maxheaderlen) -class Message(EmailMessage): + +class Message(email.message.Message): def __init__(self): # We need a version number so that we can optimize __setstate__() self.__version__ = VERSION - EmailMessage.__init__(self) + email.message.Message.__init__(self) # BAW: For debugging w/ bin/dumpdb. Apparently pprint uses repr. def __repr__(self): + if not hasattr(self, 'policy'): + self.policy = email._policybase.compat32 return self.__str__() def __setstate__(self, d): @@ -103,8 +102,8 @@ def __setstate__(self, d): chunks = [] cchanged = 0 for s, charset in v._chunks: - if isinstance(charset, str): - charset = charset.lower() + if type(charset) == str: + charset = Charset(charset) cchanged = 1 chunks.append((s, charset)) if cchanged: @@ -114,6 +113,7 @@ def __setstate__(self, d): if hchanged: self._headers = headers + # I think this method ought to eventually be deprecated def get_sender(self, use_envelope=None, preserve_case=0): """Return the address considered to be the author of the email. @@ -202,80 +202,52 @@ def get_senders(self, preserve_case=0, headers=None): # get_unixfrom() returns None if there's no envelope fieldval = self.get_unixfrom() or '' try: - realname, address = email.utils.parseaddr(fieldval) - except (TypeError, ValueError): - continue + pairs.append(('', fieldval.split()[1])) + except IndexError: + # Ignore badly formatted unixfroms + pass else: - fieldval = self[h] - if not fieldval: - continue - # Work around bug in email 2.5.8 (and ?) involving getaddresses() - # from multi-line header values. - fieldval = ''.join(fieldval.splitlines()) - addrs = email.utils.getaddresses([fieldval]) - if not addrs: - continue - realname, address = addrs[0] - if address: - if not preserve_case: - address = address.lower() - pairs.append((realname, address)) - return pairs + fieldvals = self.get_all(h) + if fieldvals: + # See comment above in get_sender() regarding + # getaddresses() and multi-line headers + fieldvals = [''.join(fv.splitlines()) + for fv in fieldvals] + pairs.extend(email.utils.getaddresses(fieldvals)) + authors = [] + for pair in pairs: + address = pair[1] + if address is not None and not preserve_case: + address = address.lower() + authors.append(address) + return authors def get_filename(self, failobj=None): - """Return the filename associated with the message's payload. - - This is a convenience method that returns the filename associated with - the message's payload. If the message is a multipart message, then - the filename is taken from the first part that has a filename - associated with it. If no filename is found, then failobj is - returned (defaults to None). + """Some MUA have bugs in RFC2231 filename encoding and cause + Mailman to stop delivery in Scrubber.py (called from ToDigest.py). """ - if self.is_multipart(): - for part in self.get_payload(): - if part.is_multipart(): - continue - filename = part.get_filename() - if filename: - return filename - else: - return self.get_param('filename', failobj) - return failobj + try: + filename = email.message.Message.get_filename(self, failobj) + return filename + except (UnicodeError, LookupError, ValueError): + return failobj - def as_string(self, unixfrom=False, mangle_from_=True): - """Return the entire formatted message as a string. - Optional unixfrom is a flag that, when True, results in the envelope - header being included in the output. + def as_string(self, unixfrom=False, mangle_from_=True): + """Return entire formatted message as a string using + Mailman.Message.Generator. - Optional mangle_from_ is a flag that, when True, escapes From_ lines - in the body of the message by putting a `>' in front of them. + Operates like email.message.Message.as_string, only + using Mailman's Message.Generator class. Only the top headers will + get folded. """ fp = StringIO() g = Generator(fp, mangle_from_=mangle_from_) + Utils.set_cte_if_missing(self) g.flatten(self, unixfrom=unixfrom) return fp.getvalue() - def get_sender_info(self, preserve_case=0, headers=None): - """Return a tuple of (realname, address) representing the author of the email. - - The method will return the first available sender information from: - 1. From: - 2. unixfrom - 3. Reply-To: - 4. Sender: - - The return address is always lower cased, unless `preserve_case' is true. - Optional `headers' gives an alternative search order, with None meaning, - search the unixfrom header. Items in `headers' are field names without - the trailing colon. - """ - pairs = self.get_senders(preserve_case, headers) - if pairs: - return pairs[0] - return ('', '') - - + class UserNotification(Message): """Class for internally crafted messages.""" @@ -283,45 +255,11 @@ def __init__(self, recip, sender, subject=None, text=None, lang=None): Message.__init__(self) charset = None if lang is not None: - charset = Charset(GetCharSet(lang)) - # Ensure we have a valid charset that can handle non-ASCII - if charset.output_charset == 'ascii': - charset.output_charset = 'utf-8' + charset = Charset(Utils.GetCharSet(lang)) if text is not None: - # Handle text encoding properly - if isinstance(text, bytes): - try: - # Try to decode using the provided charset - if charset: - text = text.decode(charset.input_charset, 'replace') - else: - # Fall back to UTF-8 if no charset provided - text = text.decode('utf-8', 'replace') - except (UnicodeDecodeError, LookupError): - # Last resort: latin-1 - text = text.decode('latin-1', 'replace') - elif not isinstance(text, str): - text = str(text) - # Ensure we're using a charset that can handle the text - if charset is None or charset.output_charset == 'ascii': - charset = Charset('utf-8') self.set_payload(text, charset) if subject is None: subject = '(no subject)' - # Handle subject encoding properly - if isinstance(subject, bytes): - try: - if charset: - subject = subject.decode(charset.input_charset, 'replace') - else: - subject = subject.decode('utf-8', 'replace') - except (UnicodeDecodeError, LookupError): - subject = subject.decode('latin-1', 'replace') - elif not isinstance(subject, str): - subject = str(subject) - # Ensure we're using a charset that can handle the subject - if charset is None or charset.output_charset == 'ascii': - charset = Charset('utf-8') self['Subject'] = Header(subject, charset, header_name='Subject', errors='replace') self['From'] = sender @@ -341,7 +279,7 @@ def send(self, mlist, noprecedence=False, **_kws): # this message has a Message-ID. Yes, the MTA would give us one, but # this is useful for logging to logs/smtp. if 'message-id' not in self: - self['Message-ID'] = unique_message_id(mlist) + self['Message-ID'] = Utils.unique_message_id(mlist) # Ditto for Date: which is required by RFC 2822 if 'date' not in self: self['Date'] = email.utils.formatdate(localtime=1) @@ -361,20 +299,16 @@ def _enqueue(self, mlist, **_kws): # Not imported at module scope to avoid import loop from Mailman.Queue.sbcache import get_switchboard virginq = get_switchboard(mm_cfg.VIRGINQUEUE_DIR) - # Get base msgdata from kwargs if it exists - msgdata = _kws.pop('msgdata', {}) - # Always set recipient information - msgdata['recips'] = self.recips - msgdata['recipient'] = self.recips[0] if self.recips else None # The message metadata better have a `recip' attribute virginq.enqueue(self, listname = mlist.internal_name(), + recips = self.recips, nodecorate = 1, reduced_list_headers = 1, - msgdata = msgdata, **_kws) + class OwnerNotification(UserNotification): """Like user notifications, but this message goes to the list owners.""" @@ -384,7 +318,7 @@ def __init__(self, mlist, subject=None, text=None, tomoderators=1): recips.extend(mlist.moderator) # We have to set the owner to the site's -bounces address, otherwise # we'll get a mail loop if an owner's address bounces. - sender = get_site_email(mlist.host_name, 'bounces') + sender = Utils.get_site_email(mlist.host_name, 'bounces') lang = mlist.preferred_language UserNotification.__init__(self, recips, sender, subject, text, lang) # Hack the To header to look like it's going to the -owner address @@ -402,23 +336,11 @@ def _enqueue(self, mlist, **_kws): # Not imported at module scope to avoid import loop from Mailman.Queue.sbcache import get_switchboard virginq = get_switchboard(mm_cfg.VIRGINQUEUE_DIR) - # Ensure recipient information is always included - if 'msgdata' in _kws: - msgdata = _kws['msgdata'] - else: - msgdata = {} - # Always set recipient information - msgdata['recips'] = self.recips - msgdata['recipient'] = self.recips[0] if self.recips else None # The message metadata better have a `recip' attribute virginq.enqueue(self, listname = mlist.internal_name(), + recips = self.recips, nodecorate = 1, reduced_list_headers = 1, envsender = self._sender, - msgdata = msgdata, **_kws) - -# Make UserNotification and OwnerNotification available as Message attributes -Message.UserNotification = UserNotification -Message.OwnerNotification = OwnerNotification diff --git a/Mailman/OldStyleMemberships.py b/Mailman/OldStyleMemberships.py index 137e52cd..f406e65b 100644 --- a/Mailman/OldStyleMemberships.py +++ b/Mailman/OldStyleMemberships.py @@ -25,14 +25,11 @@ """ import time -import re -import fnmatch from Mailman import mm_cfg from Mailman import Utils from Mailman import Errors from Mailman import MemberAdaptor -from Mailman import Autoresponder ISREGULAR = 1 ISDIGEST = 2 @@ -44,90 +41,10 @@ # Actually, fix /all/ errors -class OldStyleMemberships(MemberAdaptor.MemberAdaptor, Autoresponder.Autoresponder): + +class OldStyleMemberships(MemberAdaptor.MemberAdaptor): def __init__(self, mlist): self.__mlist = mlist - self.archive = mm_cfg.DEFAULT_ARCHIVE # Initialize archive attribute - self.digest_send_periodic = mm_cfg.DEFAULT_DIGEST_SEND_PERIODIC # Initialize digest_send_periodic attribute - self.archive_private = mm_cfg.DEFAULT_ARCHIVE_PRIVATE # Initialize archive_private attribute - self.bounce_you_are_disabled_warnings_interval = mm_cfg.DEFAULT_BOUNCE_YOU_ARE_DISABLED_WARNINGS_INTERVAL # Initialize bounce warning interval - self.digest_members = {} # Initialize digest_members dictionary - self.digest_is_default = mm_cfg.DEFAULT_DIGEST_IS_DEFAULT # Initialize digest_is_default attribute - self.mime_is_default_digest = mm_cfg.DEFAULT_MIME_IS_DEFAULT_DIGEST # Initialize mime_is_default_digest attribute - self._pending = {} # Initialize _pending dictionary for pending operations - # Initialize Autoresponder attributes - self.InitVars() - - def HasAutoApprovedSender(self, email): - """Check if the sender's email address is in the auto-approve list. - - Args: - email: The email address to check - - Returns: - bool: True if the sender is auto-approved, False otherwise - """ - # Check if the email is in the accept_these_nonmembers list - if email.lower() in [addr.lower() for addr in self.__mlist.accept_these_nonmembers]: - return True - - # Check if the email matches any patterns in accept_these_nonmembers - for pattern in self.__mlist.accept_these_nonmembers: - if pattern.startswith('^') or pattern.endswith('$'): - # This is a regex pattern - try: - if re.match(pattern, email, re.IGNORECASE): - return True - except re.error: - # Invalid regex pattern, skip it - continue - elif '*' in pattern or '?' in pattern: - # This is a glob pattern - if fnmatch.fnmatch(email.lower(), pattern.lower()): - return True - - return False - - def GetMailmanHeader(self): - """Return the standard Mailman header HTML for this list.""" - return self.__mlist.GetMailmanHeader() - - def CheckValues(self): - """Check that all member values are valid. - - This method is called by the admin interface to ensure that all member - values are valid before displaying them. It should return True if all - values are valid, False otherwise. - """ - try: - # Check that all members have valid email addresses - for member in self.getMembers(): - if not Utils.ValidateEmail(member): - return False - - # Check that all members have valid passwords - for member in self.getMembers(): - if not self.getMemberPassword(member): - return False - - # Check that all members have valid languages - for member in self.getMembers(): - lang = self.getMemberLanguage(member) - if lang not in self.__mlist.available_languages: - return False - - # Check that all members have valid delivery status - for member in self.getMembers(): - status = self.getDeliveryStatus(member) - if status not in (MemberAdaptor.ENABLED, MemberAdaptor.UNKNOWN, - MemberAdaptor.BYUSER, MemberAdaptor.BYADMIN, - MemberAdaptor.BYBOUNCE): - return False - - return True - except Exception as e: - mailman_log('error', 'Error checking member values: %s', str(e)) - return False # # Read interface @@ -142,20 +59,17 @@ def getDigestMemberKeys(self): return list(self.__mlist.digest_members.keys()) def __get_cp_member(self, member): - # Handle both string and tuple inputs - if isinstance(member, tuple): - _, member = member # Extract email address from tuple lcmember = member.lower() missing = [] val = self.__mlist.members.get(lcmember, missing) if val is not missing: - if isinstance(val, str): + if type(val) == str: return val, ISREGULAR else: return lcmember, ISREGULAR val = self.__mlist.digest_members.get(lcmember, missing) if val is not missing: - if isinstance(val, str): + if type(val) == str: return val, ISDIGEST else: return lcmember, ISDIGEST @@ -174,28 +88,10 @@ def getMemberKey(self, member): return member.lower() def getMemberCPAddress(self, member): - """Get the canonical address of a member. - - Args: - member: The member's email address - - Returns: - str: The member's canonical address - - Raises: - NotAMemberError: If the member is not found - """ cpaddr, where = self.__get_cp_member(member) if cpaddr is None: raise Errors.NotAMemberError(member) - if isinstance(cpaddr, bytes): - try: - # Try Latin-1 first since that's what we're seeing in the data - cpaddr = cpaddr.decode('latin-1', 'replace') - except UnicodeDecodeError: - # Fall back to UTF-8 if Latin-1 fails - cpaddr = cpaddr.decode('utf-8', 'replace') - return str(cpaddr) + return cpaddr def getMemberCPAddresses(self, members): return [self.__get_cp_member(member)[0] for member in members] @@ -208,6 +104,8 @@ def getMemberPassword(self, member): def authenticateMember(self, member, response): secret = self.getMemberPassword(member) + if isinstance(response, bytes): + response = response.decode('utf-8') if secret == response: return secret return 0 @@ -219,7 +117,7 @@ def __assertIsMember(self, member): def getMemberLanguage(self, member): lang = self.__mlist.language.get( member.lower(), self.__mlist.preferred_language) - if lang in self.__mlist.available_languages: + if lang in self.__mlist.GetAvailableLanguages(): return lang return self.__mlist.preferred_language @@ -232,26 +130,8 @@ def getMemberOption(self, member, flag): return not not (option & flag) def getMemberName(self, member): - """Get the member's real name. - - Args: - member: The member's email address - - Returns: - The member's real name, or None if not found - """ - try: - fullname = self.__mlist.usernames[member] - if isinstance(fullname, bytes): - try: - # Try Latin-1 first since that's what we're seeing in the data - fullname = fullname.decode('latin-1', 'replace') - except UnicodeDecodeError: - # Fall back to UTF-8 if Latin-1 fails - fullname = fullname.decode('utf-8', 'replace') - return fullname - except KeyError: - return None + self.__assertIsMember(member) + return self.__mlist.usernames.get(member.lower()) def getMemberTopics(self, member): self.__assertIsMember(member) @@ -451,23 +331,9 @@ def setMemberOption(self, member, flag, value): del self.__mlist.user_options[memberkey] def setMemberName(self, member, realname): - """Set the real name of a member. - - Args: - member: The member's email address - realname: The member's real name - """ + assert self.__mlist.Locked() self.__assertIsMember(member) - if realname is None: - realname = '' - if isinstance(realname, bytes): - try: - # Try Latin-1 first since that's what we're seeing in the data - realname = realname.decode('latin-1', 'replace') - except UnicodeDecodeError: - # Fall back to UTF-8 if Latin-1 fails - realname = realname.decode('utf-8', 'replace') - self.__mlist.usernames[member.lower()] = str(realname) + self.__mlist.usernames[member.lower()] = realname def setMemberTopics(self, member, topics): assert self.__mlist.Locked() @@ -495,184 +361,11 @@ def setDeliveryStatus(self, member, status): def setBounceInfo(self, member, info): assert self.__mlist.Locked() self.__assertIsMember(member) - self.__mlist.bounce_info[member.lower()] = info - - def ProcessConfirmation(self, cookie, msg): - """Process a confirmation request. - - Args: - cookie: The confirmation cookie string - msg: The message containing the confirmation request - - Returns: - A tuple of (action_type, action_data) where action_type is one of: - - Pending.SUBSCRIPTION - - Pending.UNSUBSCRIPTION - - Pending.HELD_MESSAGE - And action_data contains the relevant data for that action type. - - Raises: - Errors.MMBadConfirmation: If the confirmation string is invalid - Errors.MMNeedApproval: If the request needs moderator approval - Errors.MMAlreadyAMember: If the user is already a member - Errors.NotAMemberError: If the user is not a member - Errors.MembershipIsBanned: If the user is banned - Errors.HostileSubscriptionError: If the subscription is hostile - Errors.MMBadPasswordError: If the approval password is bad - """ - from Mailman import Pending - from Mailman import Utils - from Mailman import Errors - - # Get the pending request - try: - action, data = Pending.unpickle(cookie) - except Exception as e: - raise Errors.MMBadConfirmation(str(e)) - - # Check if the request has expired - if time.time() > data.get('expiration', 0): - raise Errors.MMBadConfirmation('Confirmation expired') - - # Process based on action type - if action == Pending.SUBSCRIPTION: - # Extract userdesc and remote from data - userdesc, remote = data - - # Check if already a member - if self.isMember(userdesc.address): - raise Errors.MMAlreadyAMember(userdesc.address) - - # Check if banned - if self.__mlist.isBanned(userdesc.address): - raise Errors.MembershipIsBanned(userdesc.address) - - # Add the member - self.addNewMember( - userdesc.address, - digest=userdesc.digest, - password=userdesc.password, - language=userdesc.language, - realname=userdesc.fullname - ) - - elif action == Pending.UNSUBSCRIPTION: - # Check if member - if not self.isMember(data['email']): - raise Errors.NotAMemberError(data['email']) - - # Remove the member - self.removeMember(data['email']) - - elif action == Pending.HELD_MESSAGE: - # Process held message - if data.get('approval_password'): - if data['approval_password'] != self.__mlist.mod_password: - raise Errors.MMBadPasswordError() - - # Forward to moderator if needed - if data.get('need_approval'): - self.__mlist.HoldMessage(msg) - raise Errors.MMNeedApproval() - - # Process the message - if data.get('action') == 'approve': - self.__mlist.ApproveMessage(msg) - else: - self.__mlist.DiscardMessage(msg) - + member = member.lower() + if info is None: + if member in self.__mlist.bounce_info: + del self.__mlist.bounce_info[member] + if member in self.__mlist.delivery_status: + del self.__mlist.delivery_status[member] else: - raise Errors.MMBadConfirmation('Unknown action type') - - # Remove the pending request - Pending.remove(cookie) - - return action, data - - @property - def digestable(self): - """Return whether the list supports digest mode. - - This is the inverse of nondigestable. - """ - return not self.__mlist.nondigestable - - @property - def digest_is_default(self): - """Return whether digest delivery is the default for new members.""" - return self.__mlist.digest_is_default - - @digest_is_default.setter - def digest_is_default(self, value): - """Set whether digest delivery is the default for new members.""" - self.__mlist.digest_is_default = value - - @property - def mime_is_default_digest(self): - """Return whether MIME format is the default for digests.""" - return self.__mlist.mime_is_default_digest - - @mime_is_default_digest.setter - def mime_is_default_digest(self, value): - """Set whether MIME format is the default for digests.""" - self.__mlist.mime_is_default_digest = value - - @property - def digest_size_threshhold(self): - """Return the size threshold for digests in KB.""" - return self.__mlist.digest_size_threshhold - - @digest_size_threshhold.setter - def digest_size_threshhold(self, value): - """Set the size threshold for digests in KB.""" - self.__mlist.digest_size_threshhold = value - - @property - def digest_send_periodic(self): - """Return whether digests are sent periodically.""" - return self.__mlist.digest_send_periodic - - @digest_send_periodic.setter - def digest_send_periodic(self, value): - """Set whether digests are sent periodically.""" - self.__mlist.digest_send_periodic = value - - @property - def digest_volume(self): - """Return the current digest volume number.""" - return self.__mlist.volume - - @digest_volume.setter - def digest_volume(self, value): - """Set the current digest volume number.""" - self.__mlist.volume = value - - @property - def digest_issue(self): - """Return the current digest issue number.""" - return self.__mlist.next_digest_number - - @digest_issue.setter - def digest_issue(self, value): - """Set the current digest issue number.""" - self.__mlist.next_digest_number = value - - @property - def digest_last_sent_at(self): - """Return the timestamp of when the last digest was sent.""" - return self.__mlist.digest_last_sent_at - - @digest_last_sent_at.setter - def digest_last_sent_at(self, value): - """Set the timestamp of when the last digest was sent.""" - self.__mlist.digest_last_sent_at = value - - @property - def digest_next_due_at(self): - """Return the timestamp of when the next digest is due.""" - return self.__mlist.digest_next_due_at - - @digest_next_due_at.setter - def digest_next_due_at(self, value): - """Set the timestamp of when the next digest is due.""" - self.__mlist.digest_next_due_at = value + self.__mlist.bounce_info[member] = info diff --git a/Mailman/Pending.py b/Mailman/Pending.py index d13f0724..06f777d2 100644 --- a/Mailman/Pending.py +++ b/Mailman/Pending.py @@ -24,14 +24,10 @@ import errno import random import pickle -import socket -import traceback -import signal from Mailman import mm_cfg from Mailman import UserDesc -from Mailman import Utils -from Mailman.Utils import sha_new +from Mailman.Utils import sha_new, load_pickle # Types of pending records SUBSCRIPTION = 'S' @@ -54,111 +50,77 @@ class Pending(object): def InitTempVars(self): self.__pendfile = os.path.join(self.fullpath(), 'pending.pck') - def pend_new(self, operation, data=None): - """Add a new pending request to the list. - - :param operation: The operation to perform. - :type operation: string - :param data: The data associated with the operation. - :type data: any - :return: The cookie for the pending request. - :rtype: string + def pend_new(self, op, *content, **kws): + """Create a new entry in the pending database, returning cookie for it. """ - # Make sure we have a lock - assert self.Locked(), 'List must be locked before pending operations' - - # Generate a unique cookie - cookie = Utils.unique_message_id(mlist=self) - - # Store the pending request - self._pending[cookie] = (operation, data) - + assert op in _ALLKEYS, 'op: %s' % op + lifetime = kws.get('lifetime', mm_cfg.PENDING_REQUEST_LIFE) + # We try the main loop several times. If we get a lock error somewhere + # (for instance because someone broke the lock) we simply try again. + assert self.Locked() + # Load the database + db = self.__load() + # Calculate a unique cookie. Algorithm vetted by the Timbot. time() + # has high resolution on Linux, clock() on Windows. random gives us + # about 45 bits in Python 2.2, 53 bits on Python 2.3. The time and + # clock values basically help obscure the random number generator, as + # does the hash calculation. The integral parts of the time values + # are discarded because they're the most predictable bits. + while True: + now = time.time() + x = random.random() + now % 1.0 + time.time() % 1.0 + cookie = sha_new(repr(x).encode()).hexdigest() + # We'll never get a duplicate, but we'll be anal about checking + # anyway. + if cookie not in db: + break + # Store the content, plus the time in the future when this entry will + # be evicted from the database, due to staleness. + db[cookie] = (op,) + content + evictions = db.setdefault('evictions', {}) + evictions[cookie] = now + lifetime + self.__save(db) return cookie def __load(self): - """Load the pending database with improved error handling.""" - filename = os.path.join(mm_cfg.DATA_DIR, 'pending.pck') - filename_backup = filename + '.bak' - - # Try loading the main file first try: - with open(filename, 'rb') as fp: - try: - data = fp.read() - if not data: - return {} - return pickle.loads(data, fix_imports=True, encoding='latin1') - except (EOFError, ValueError, TypeError, pickle.UnpicklingError) as e: - syslog('error', 'Error loading pending.pck: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - - # If we get here, the main file failed to load properly - if os.path.exists(filename_backup): - syslog('info', 'Attempting to load from backup file') - with open(filename_backup, 'rb') as fp: - try: - data = fp.read() - if not data: - return {} - db = pickle.loads(data, fix_imports=True, encoding='latin1') - # Successfully loaded backup, restore it as main - import shutil - shutil.copy2(filename_backup, filename) - return db - except (EOFError, ValueError, TypeError, pickle.UnpicklingError) as e: - syslog('error', 'Error loading backup pending.pck: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - - except IOError as e: - if e.errno != errno.ENOENT: - syslog('error', 'IOError loading pending.pck: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - - # If we get here, both main and backup files failed or don't exist - return {} + obj = load_pickle(self.__pendfile) + if obj == None: + return {'evictions': {}} + else: + return obj + except Exception as e: + return {'evictions': {}} def __save(self, db): - """Save the pending database with atomic operations and backup.""" - if not db: - return - - filename = os.path.join(mm_cfg.DATA_DIR, 'pending.pck') - filename_tmp = filename + '.tmp.%s.%d' % (socket.gethostname(), os.getpid()) - filename_backup = filename + '.bak' - - # First create a backup of the current file if it exists - if os.path.exists(filename): - try: - import shutil - shutil.copy2(filename, filename_backup) - except IOError as e: - syslog('error', 'Error creating backup: %s', str(e)) - - # Save to temporary file first + evictions = db['evictions'] + now = time.time() + for cookie, data in list(db.items()): + if cookie in ('evictions', 'version'): + continue + timestamp = evictions[cookie] + if now > timestamp: + # The entry is stale, so remove it. + del db[cookie] + del evictions[cookie] + # Clean out any bogus eviction entries. + for cookie in list(evictions.keys()): + if cookie not in db: + del evictions[cookie] + db['version'] = mm_cfg.PENDING_FILE_SCHEMA_VERSION + tmpfile = '%s.tmp.%d.%d' % (self.__pendfile, os.getpid(), now) + omask = os.umask(0o007) try: - # Ensure directory exists - dirname = os.path.dirname(filename) - if not os.path.exists(dirname): - os.makedirs(dirname, 0o755) - - with open(filename_tmp, 'wb') as fp: - # Use protocol 4 for better compatibility - pickle.dump(db, fp, protocol=4, fix_imports=True) - fp.flush() - if hasattr(os, 'fsync'): - os.fsync(fp.fileno()) - - # Atomic rename - os.rename(filename_tmp, filename) - - except (IOError, OSError) as e: - syslog('error', 'Error saving pending.pck: %s', str(e)) - # Try to clean up + fp = open(tmpfile, 'wb') try: - os.unlink(filename_tmp) - except OSError: - pass - raise + pickle.dump(db, fp) + fp.flush() + os.fsync(fp.fileno()) + finally: + fp.close() + os.rename(tmpfile, self.__pendfile) + finally: + os.umask(omask) def pend_confirm(self, cookie, expunge=True): """Return data for cookie, or None if not found. diff --git a/Mailman/Post.py b/Mailman/Post.py index 07200483..7f86696d 100644 --- a/Mailman/Post.py +++ b/Mailman/Post.py @@ -20,8 +20,6 @@ from Mailman import mm_cfg from Mailman.Queue.sbcache import get_switchboard -from Mailman.Message import Message -from email import message_from_string @@ -35,19 +33,7 @@ def inject(listname, msg, recips=None, qdir=None): } if recips: kws['recips'] = recips - # Ensure msg is a Mailman.Message.Message - if isinstance(msg, str): - emsg = message_from_string(msg) - else: - emsg = msg - if not isinstance(emsg, Message): - mmsg = Message() - for k, v in emsg.items(): - mmsg[k] = v - mmsg.set_payload(emsg.get_payload()) - else: - mmsg = emsg - queue.enqueue(mmsg, msgdata=kws) + queue.enqueue(msg, **kws) diff --git a/Mailman/Queue/ArchRunner.py b/Mailman/Queue/ArchRunner.py index 562f9d1b..fb5265bb 100644 --- a/Mailman/Queue/ArchRunner.py +++ b/Mailman/Queue/ArchRunner.py @@ -30,51 +30,87 @@ class ArchRunner(Runner): QDIR = mm_cfg.ARCHQUEUE_DIR def _dispose(self, mlist, msg, msgdata): + from Mailman.Logging.Syslog import syslog + syslog('debug', 'ArchRunner: Starting archive processing for list %s', mlist.internal_name()) + # Support clobber_date, i.e. setting the date in the archive to the # received date, not the (potentially bogus) Date: header of the # original message. clobber = 0 originaldate = msg.get('date') - receivedtime = formatdate(msgdata.get('received_time', time.time())) + + # Handle potential bytes/string issues with header values + if isinstance(originaldate, bytes): + try: + originaldate = originaldate.decode('utf-8', 'replace') + except (UnicodeDecodeError, AttributeError): + originaldate = None + + receivedtime = formatdate(msgdata['received_time']) + syslog('debug', 'ArchRunner: Original date: %s, Received time: %s', originaldate, receivedtime) + if not originaldate: clobber = 1 + syslog('debug', 'ArchRunner: No original date, will clobber') elif mm_cfg.ARCHIVER_CLOBBER_DATE_POLICY == 1: clobber = 1 + syslog('debug', 'ArchRunner: ARCHIVER_CLOBBER_DATE_POLICY = 1, will clobber') elif mm_cfg.ARCHIVER_CLOBBER_DATE_POLICY == 2: # what's the timestamp on the original message? - tup = parsedate_tz(originaldate) - now = time.time() try: + tup = parsedate_tz(originaldate) + now = time.time() if not tup: clobber = 1 + syslog('debug', 'ArchRunner: Could not parse original date, will clobber') elif abs(now - mktime_tz(tup)) > \ mm_cfg.ARCHIVER_ALLOWABLE_SANE_DATE_SKEW: clobber = 1 - except (ValueError, OverflowError): + syslog('debug', 'ArchRunner: Date skew too large, will clobber') + except (ValueError, OverflowError, TypeError): # The likely cause of this is that the year in the Date: field # is horribly incorrect, e.g. (from SF bug # 571634): # Date: Tue, 18 Jun 0102 05:12:09 +0500 # Obviously clobber such dates. clobber = 1 + syslog('debug', 'ArchRunner: Date parsing exception, will clobber') + if clobber: - del msg['date'] - del msg['x-original-date'] + # Use proper header manipulation methods + if 'date' in msg: + del msg['date'] + if 'x-original-date' in msg: + del msg['x-original-date'] msg['Date'] = receivedtime if originaldate: msg['X-Original-Date'] = originaldate + syslog('debug', 'ArchRunner: Clobbered date headers') + # Always put an indication of when we received the message. msg['X-List-Received-Date'] = receivedtime + # Now try to get the list lock + syslog('debug', 'ArchRunner: Attempting to lock list %s', mlist.internal_name()) try: mlist.Lock(timeout=mm_cfg.LIST_LOCK_TIMEOUT) + syslog('debug', 'ArchRunner: Successfully locked list %s', mlist.internal_name()) except LockFile.TimeOutError: # oh well, try again later + syslog('debug', 'ArchRunner: Failed to lock list %s, will retry later', mlist.internal_name()) return 1 + try: # Archiving should be done in the list's preferred language, not # the sender's language. i18n.set_language(mlist.preferred_language) + syslog('debug', 'ArchRunner: Calling ArchiveMail for list %s', mlist.internal_name()) mlist.ArchiveMail(msg) + syslog('debug', 'ArchRunner: ArchiveMail completed, saving list %s', mlist.internal_name()) mlist.Save() + syslog('debug', 'ArchRunner: Successfully completed archive processing for list %s', mlist.internal_name()) + except Exception as e: + syslog('error', 'ArchRunner: Exception during archive processing for list %s: %s', mlist.internal_name(), e) + raise finally: mlist.Unlock() + syslog('debug', 'ArchRunner: Unlocked list %s', mlist.internal_name()) diff --git a/Mailman/Queue/BounceRunner.py b/Mailman/Queue/BounceRunner.py index c07ffa91..970d3236 100644 --- a/Mailman/Queue/BounceRunner.py +++ b/Mailman/Queue/BounceRunner.py @@ -15,177 +15,131 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, # USA. -"""Bounce queue runner. +"""Bounce queue runner.""" -This module is responsible for processing bounce messages. It's a separate -queue from the virgin queue because bounces need different handling. -""" - -from builtins import object, str +from builtins import object import os import re import time import pickle -import email -from email.utils import getaddresses, parseaddr -from email.iterators import body_line_iterator + from email.mime.text import MIMEText from email.mime.message import MIMEMessage -import traceback -from io import StringIO -import sys +from email.utils import parseaddr from Mailman import mm_cfg from Mailman import Utils from Mailman import LockFile -from Mailman import Errors -from Mailman import i18n from Mailman.Errors import NotAMemberError +from Mailman.Message import UserNotification from Mailman.Bouncer import _BounceInfo from Mailman.Bouncers import BouncerAPI from Mailman.Queue.Runner import Runner from Mailman.Queue.sbcache import get_switchboard from Mailman.Logging.Syslog import syslog from Mailman.i18n import _ -import Mailman.Message as Message - -# Lazy import to avoid circular dependency -def get_mail_list(): - import Mailman.MailList as MailList - return MailList.MailList COMMASPACE = ', ' + class BounceMixin: def __init__(self): - """Initialize the bounce mixin.""" + # Registering a bounce means acquiring the list lock, and it would be + # too expensive to do this for each message. Instead, each bounce + # runner maintains an event log which is essentially a file with + # multiple pickles. Each bounce we receive gets appended to this file + # as a 4-tuple record: (listname, addr, today, msg) + # + # today is itself a 3-tuple of (year, month, day) + # + # Every once in a while (see _doperiodic()), the bounce runner cracks + # open the file, reads all the records and registers all the bounces. + # Then it truncates the file and continues on. We don't need to lock + # the bounce event file because bounce qrunners are single threaded + # and each creates a uniquely named file to contain the events. + # + # XXX When Python 2.3 is minimal require, we can use the new + # tempfile.TemporaryFile() function. + # + # XXX We used to classify bounces to the site list as bounce events + # for every list, but this caused severe problems. Here's the + # scenario: aperson@example.com is a member of 4 lists, and a list + # owner of the foo list. example.com has an aggressive spam filter + # which rejects any message that is spam or contains spam as an + # attachment. Now, a spambot sends a piece of spam to the foo list, + # but since that spambot is not a member, the list holds the message + # for approval, and sends a notification to aperson@example.com as + # list owner. That notification contains a copy of the spam. Now + # example.com rejects the message, causing a bounce to be sent to the + # site list's bounce address. The bounce runner would then dutifully + # register a bounce for all 4 lists that aperson@example.com was a + # member of, and eventually that person would get disabled on all + # their lists. So now we ignore site list bounces. Ce La Vie for + # password reminder bounces. + self._bounce_events_file = os.path.join( + mm_cfg.DATA_DIR, 'bounce-events-%05d.pck' % os.getpid()) + self._bounce_events_fp = None self._bouncecnt = 0 - # Set initial next action time to 1 hour in the future - self._next_action = time.time() + 3600 - syslog('debug', 'BounceMixin: Initialized with next action time: %s', - time.ctime(self._next_action)) - - def _process_bounces(self): - """Process pending bounces.""" - try: - syslog('debug', 'BounceMixin._process_bounces: Starting bounce processing') - - # Get all lists - listnames = Utils.list_names() - for listname in listnames: - try: - mlist = get_mail_list()(listname, lock=0) - try: - # Process bounces for this list - self._process_list_bounces(mlist) - finally: - mlist.Unlock() - except Exception as e: - syslog('error', 'BounceMixin._process_bounces: Error processing list %s: %s', - listname, str(e)) - continue - - syslog('debug', 'BounceMixin._process_bounces: Completed bounce processing') - - except Exception as e: - syslog('error', 'BounceMixin._process_bounces: Error during bounce processing: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - - def _process_list_bounces(self, mlist): - """Process bounces for a specific list.""" - try: - syslog('debug', 'BounceMixin._process_list_bounces: Processing bounces for list %s', - mlist.internal_name()) - - # Get all bouncing members - bouncing_members = mlist.getBouncingMembers() - for member in bouncing_members: - try: - # Get bounce info for this member - info = mlist.getBounceInfo(member) - if not info: - continue - - # Check if member should be disabled - if info.score >= mlist.bounce_score_threshold: - syslog('debug', 'BounceMixin._process_list_bounces: Disabling member %s due to bounce score %f', - member, info.score) - mlist.disableBouncingMember(member, info) - - except Exception as e: - syslog('error', 'BounceMixin._process_list_bounces: Error processing member %s: %s', - member, str(e)) - continue - - syslog('debug', 'BounceMixin._process_list_bounces: Completed processing bounces for list %s', - mlist.internal_name()) - - except Exception as e: - syslog('error', 'BounceMixin._process_list_bounces: Error processing list %s: %s\nTraceback:\n%s', - mlist.internal_name(), str(e), traceback.format_exc()) - - def _register_bounces(self, mlist, bounces): - """Register bounce information for a list.""" - try: - for address, info in bounces.items(): - syslog('debug', 'BounceMixin._register_bounces: Registering bounce for list %s, address %s', - mlist.internal_name(), address) - - # Write bounce data to file - filename = os.path.join(mlist.bounce_dir, address) - try: - with open(filename, 'w') as fp: - fp.write(str(info)) - syslog('debug', 'BounceMixin._register_bounces: Successfully wrote bounce data to %s', filename) - except Exception as e: - syslog('error', 'BounceMixin._register_bounces: Failed to write bounce data to %s: %s\nTraceback:\n%s', - filename, str(e), traceback.format_exc()) - continue - - except Exception as e: - syslog('error', 'BounceMixin._register_bounces: Error registering bounce: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - - def _cleanup(self): - """Clean up bounce processing.""" - try: - syslog('debug', 'BounceMixin._cleanup: Processing %d pending bounces', self._bouncecnt) - # ... cleanup logic ... - except Exception as e: - syslog('error', 'BounceMixin._cleanup: Error during cleanup: %s', str(e)) - - def _doperiodic(self): - """Do periodic bounce processing.""" - try: - now = time.time() - if now >= self._next_action: - syslog('debug', 'BounceMixin._doperiodic: Processing bounces, next action scheduled for %s', - time.ctime(self._next_action)) - # Process bounces - self._process_bounces() - # Update next action time to 1 hour from now - self._next_action = now + 3600 - syslog('debug', 'BounceMixin._doperiodic: Next action scheduled for %s', - time.ctime(self._next_action)) - except Exception as e: - syslog('error', 'BounceMixin._doperiodic: Error during periodic processing: %s', str(e)) + self._nextaction = time.time() + mm_cfg.REGISTER_BOUNCES_EVERY def _queue_bounces(self, listname, addrs, msg): today = time.localtime()[:3] if self._bounce_events_fp is None: omask = os.umask(0o006) try: - self._bounce_events_fp = open(self._bounce_events_file, 'ab') + self._bounce_events_fp = open(self._bounce_events_file, 'a+b') finally: os.umask(omask) for addr in addrs: - # Use protocol 4 for Python 3 compatibility and fix_imports for Python 2/3 compatibility pickle.dump((listname, addr, today, msg), - self._bounce_events_fp, protocol=4, fix_imports=True) + self._bounce_events_fp, 1) self._bounce_events_fp.flush() os.fsync(self._bounce_events_fp.fileno()) self._bouncecnt += len(addrs) + def _register_bounces(self): + syslog('bounce', '%s processing %s queued bounces', + self, self._bouncecnt) + # Read all the records from the bounce file, then unlink it. Sort the + # records by listname for more efficient processing. + events = {} + self._bounce_events_fp.seek(0) + while True: + try: + listname, addr, day, msg = pickle.load(self._bounce_events_fp, fix_imports=True, encoding='latin1') + except ValueError as e: + syslog('bounce', 'Error reading bounce events: %s', e) + except EOFError: + break + events.setdefault(listname, []).append((addr, day, msg)) + # Now register all events sorted by list + for listname in list(events.keys()): + mlist = self._open_list(listname) + mlist.Lock() + try: + for addr, day, msg in events[listname]: + mlist.registerBounce(addr, msg, day=day) + mlist.Save() + finally: + mlist.Unlock() + # Reset and free all the cached memory + self._bounce_events_fp.close() + self._bounce_events_fp = None + os.unlink(self._bounce_events_file) + self._bouncecnt = 0 + + def _cleanup(self): + if self._bouncecnt > 0: + self._register_bounces() + + def _doperiodic(self): + now = time.time() + if self._nextaction > now or self._bouncecnt == 0: + return + # Let's go ahead and register the bounces we've got stored up + self._nextaction = now + mm_cfg.REGISTER_BOUNCES_EVERY + self._register_bounces() + def _probe_bounce(self, mlist, token): locked = mlist.Locked() if not locked: @@ -217,168 +171,145 @@ def _probe_bounce(self, mlist, token): mlist.Unlock() + class BounceRunner(Runner, BounceMixin): QDIR = mm_cfg.BOUNCEQUEUE_DIR - # Enable message tracking for bounce messages - _track_messages = True - _max_processed_messages = 10000 - _max_retry_times = 10000 - - # Retry configuration - MIN_RETRY_DELAY = 300 # 5 minutes minimum delay between retries - MAX_RETRIES = 5 # Maximum number of retry attempts - _retry_times = {} # Track last retry time for each message - - # Cleanup configuration - _cleanup_interval = 3600 # Clean up every hour - _last_cleanup = 0 # Last cleanup time - def __init__(self, slice=None, numslices=1): - syslog('debug', 'BounceRunner: Starting initialization') - try: - Runner.__init__(self, slice, numslices) - BounceMixin.__init__(self) - - # Initialize bounce events file - self._bounce_events_file = os.path.join(mm_cfg.DATA_DIR, 'bounce_events') - self._bounce_events_fp = None - - # Initialize processed messages tracking - self._processed_messages = set() - self._last_cleanup = time.time() - - syslog('debug', 'BounceRunner: Initialization complete') - except Exception as e: - syslog('error', 'BounceRunner: Initialization failed: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - raise + Runner.__init__(self, slice, numslices) + BounceMixin.__init__(self) def _dispose(self, mlist, msg, msgdata): - """Process a bounce message.""" - try: - # Get the message ID - msgid = msg.get('message-id', 'n/a') - filebase = msgdata.get('_filebase', 'unknown') - - # Ensure we have a MailList object - if isinstance(mlist, str): - try: - mlist = get_mail_list()(mlist, lock=0) - should_unlock = True - except Errors.MMUnknownListError: - syslog('error', 'BounceRunner: Unknown list %s', mlist) - self._shunt.enqueue(msg, msgdata) - return True - else: - should_unlock = False - - try: - syslog('debug', 'BounceRunner._dispose: Starting to process bounce message %s (file: %s) for list %s', - msgid, filebase, mlist.internal_name()) - - # Check retry delay - if not self._check_retry_delay(msgid, filebase): - syslog('debug', 'BounceRunner._dispose: Message %s failed retry delay check, skipping', msgid) - return True - - # Process the bounce - # ... bounce processing logic ... - - return False - - finally: - if should_unlock: - mlist.Unlock() - - except Exception as e: - syslog('error', 'BounceRunner._dispose: Error processing bounce message %s: %s\nTraceback:\n%s', - msgid, str(e), traceback.format_exc()) - return True + # Make sure we have the most up-to-date state + mlist.Load() + outq = get_switchboard(mm_cfg.OUTQUEUE_DIR) + # There are a few possibilities here: + # + # - the message could have been VERP'd in which case, we know exactly + # who the message was destined for. That make our job easy. + # - the message could have been originally destined for a list owner, + # but a list owner address itself bounced. That's bad, and for now + # we'll simply attempt to deliver the message to the site list + # owner. + # Note that this means that automated bounce processing doesn't work + # for the site list. Because we can't reliably tell to what address + # a non-VERP'd bounce was originally sent, we have to treat all + # bounces sent to the site list as potential list owner bounces. + # - the list owner could have set list-bounces (or list-admin) as the + # owner address. That's really bad as it results in a loop of ever + # growing unrecognized bounce messages. We detect this based on the + # fact that this message itself will be from the site bounces + # address. We then send this to the site list owner instead. + # Notices to list-owner have their envelope sender and From: set to + # the site-bounces address. Check if this is this a bounce for a + # message to a list owner, coming to site-bounces, or a looping + # message sent directly to the -bounces address. We have to do these + # cases separately, because sending to site-owner will reset the + # envelope sender. + # Is this a site list bounce? + if (mlist.internal_name().lower() == + mm_cfg.MAILMAN_SITE_LIST.lower()): + # Send it on to the site owners, but craft the envelope sender to + # be the -loop detection address, so if /they/ bounce, we won't + # get stuck in a bounce loop. + outq.enqueue(msg, msgdata, + recips=mlist.owner, + envsender=Utils.get_site_email(extra='loop'), + nodecorate=1, + ) + return + # Is this a possible looping message sent directly to a list-bounces + # address other than the site list? + # Check From: because unix_from might be VERP'd. + # Also, check the From: that Message.OwnerNotification uses. + if (msg.get('from') == + Utils.get_site_email(mlist.host_name, 'bounces')): + # Just send it to the sitelist-owner address. If that bounces + # we'll handle it above. + outq.enqueue(msg, msgdata, + recips=[Utils.get_site_email(extra='owner')], + envsender=Utils.get_site_email(extra='loop'), + nodecorate=1, + ) + return + # List isn't doing bounce processing? + if not mlist.bounce_processing: + return + # Try VERP detection first, since it's quick and easy + addrs = verp_bounce(mlist, msg) + if addrs: + # We have an address, but check if the message is non-fatal. + if BouncerAPI.ScanMessages(mlist, msg) is BouncerAPI.Stop: + return + else: + # See if this was a probe message. + token = verp_probe(mlist, msg) + if token: + self._probe_bounce(mlist, token) + return + # That didn't give us anything useful, so try the old fashion + # bounce matching modules. + addrs = BouncerAPI.ScanMessages(mlist, msg) + if addrs is BouncerAPI.Stop: + # This is a recognized, non-fatal notice. Ignore it. + return + # If that still didn't return us any useful addresses, then send it on + # or discard it. + addrs = [_f for _f in addrs if _f] + if not addrs: + syslog('bounce', + '%s: bounce message w/no discernable addresses: %s', + mlist.internal_name(), + msg.get('message-id', 'n/a')) + maybe_forward(mlist, msg) + return + # BAW: It's possible that there are None's in the list of addresses, + # although I'm unsure how that could happen. Possibly ScanMessages() + # can let None's sneak through. In any event, this will kill them. + # addrs = filter(None, addrs) + # MAS above filter moved up so we don't try to queue an empty list. + self._queue_bounces(mlist.internal_name(), addrs, msg) - def _extract_bounce_info(self, msg): - """Extract bounce information from a message.""" - try: - # Log the message structure for debugging - syslog('debug', 'BounceRunner._extract_bounce_info: Message structure:') - syslog('debug', ' Headers: %s', dict(msg.items())) - syslog('debug', ' Content-Type: %s', msg.get('content-type', 'unknown')) - syslog('debug', ' Is multipart: %s', msg.is_multipart()) - - # Extract bounce information based on message structure - bounce_info = {} - - # Try to get recipient from various headers - for header in ['X-Failed-Recipients', 'X-Original-To', 'To']: - if msg.get(header): - bounce_info['recipient'] = msg[header] - syslog('debug', 'BounceRunner._extract_bounce_info: Found recipient in %s header: %s', - header, bounce_info['recipient']) - break - - # Try to get error information - if msg.is_multipart(): - for part in msg.get_payload(): - if part.get_content_type() == 'message/delivery-status': - bounce_info['error'] = part.get_payload() - syslog('debug', 'BounceRunner._extract_bounce_info: Found delivery status in multipart message') - break - - if not bounce_info.get('recipient'): - syslog('error', 'BounceRunner._extract_bounce_info: Could not find recipient in bounce message') - return None - - return bounce_info - - except Exception as e: - syslog('error', 'BounceRunner._extract_bounce_info: Error extracting bounce information: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - return None + _doperiodic = BounceMixin._doperiodic def _cleanup(self): - """Clean up resources.""" - syslog('debug', 'BounceRunner: Starting cleanup') - try: - BounceMixin._cleanup(self) - Runner._cleanup(self) - except Exception as e: - syslog('error', 'BounceRunner: Cleanup failed: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - syslog('debug', 'BounceRunner: Cleanup complete') - - _doperiodic = BounceMixin._doperiodic + BounceMixin._cleanup(self) + Runner._cleanup(self) + def verp_bounce(mlist, msg): - try: - bmailbox, bdomain = Utils.ParseEmail(mlist.GetBouncesEmail()) - vals = [] - for header in ('to', 'delivered-to', 'envelope-to', 'apparently-to'): - vals.extend(msg.get_all(header, [])) - for field in vals: - to = parseaddr(field)[1] - if not to: - continue - try: - mo = re.search(mm_cfg.VERP_REGEXP, to, re.IGNORECASE) - if not mo: - continue - if bmailbox != mo.group('bounces'): - continue - addr = '%s@%s' % mo.group('mailbox', 'host') - return [addr] - except IndexError: - syslog('error', "VERP_REGEXP doesn't yield the right match groups: %s", - mm_cfg.VERP_REGEXP) - continue - except Exception as e: - syslog('error', "Error processing VERP bounce: %s", str(e)) - continue - except Exception as e: - syslog('error', "Error in verp_bounce: %s", str(e)) - return [] + bmailbox, bdomain = Utils.ParseEmail(mlist.GetBouncesEmail()) + # Sadly not every MTA bounces VERP messages correctly, or consistently. + # Fall back to Delivered-To: (Postfix), Envelope-To: (Exim) and + # Apparently-To:, and then short-circuit if we still don't have anything + # to work with. Note that there can be multiple Delivered-To: headers so + # we need to search them all (and we don't worry about false positives for + # forwarded email, because only one should match VERP_REGEXP). + vals = [] + for header in ('to', 'delivered-to', 'envelope-to', 'apparently-to'): + vals.extend(msg.get_all(header, [])) + for field in vals: + to = parseaddr(field)[1] + if not to: + continue # empty header + mo = re.search(mm_cfg.VERP_REGEXP, to) + if not mo: + continue # no match of regexp + try: + if bmailbox != mo.group('bounces'): + continue # not a bounce to our list + # All is good + addr = '%s@%s' % mo.group('mailbox', 'host') + except IndexError: + syslog('error', + "VERP_REGEXP doesn't yield the right match groups: %s", + mm_cfg.VERP_REGEXP) + return [] + return [addr] + def verp_probe(mlist, msg): bmailbox, bdomain = Utils.ParseEmail(mlist.GetBouncesEmail()) # Sadly not every MTA bounces VERP messages correctly, or consistently. @@ -394,7 +325,7 @@ def verp_probe(mlist, msg): to = parseaddr(field)[1] if not to: continue # empty header - mo = re.search(mm_cfg.VERP_PROBE_REGEXP, to, re.IGNORECASE) + mo = re.search(mm_cfg.VERP_PROBE_REGEXP, to) if not mo: continue # no match of regexp try: @@ -413,6 +344,7 @@ def verp_probe(mlist, msg): return None + def maybe_forward(mlist, msg): # Does the list owner want to get non-matching bounce messages? # If not, simply discard it. @@ -428,7 +360,7 @@ def maybe_forward(mlist, msg): For more information see: %(adminurl)s -""") % {'adminurl': adminurl}, +"""), subject=_('Uncaught bounce notification'), tomoderators=0) syslog('bounce', diff --git a/Mailman/Queue/CommandRunner.py b/Mailman/Queue/CommandRunner.py index 81b764dc..6272dfdc 100644 --- a/Mailman/Queue/CommandRunner.py +++ b/Mailman/Queue/CommandRunner.py @@ -22,46 +22,27 @@ # bounce messages (i.e. -admin or -bounces), nor does it handle mail to # -owner. + + +# BAW: get rid of this when we Python 2.2 is a minimum requirement. + import re import sys -import email -import email.message -import email.utils -from email.header import decode_header, make_header, Header -from email.errors import HeaderParseError -from email.iterators import typed_subpart_iterator -from email.mime.text import MIMEText -from email.mime.message import MIMEMessage from Mailman import mm_cfg from Mailman import Utils -from Mailman import Errors -from Mailman import i18n -from Mailman.htmlformat import * -from Mailman.Logging.Syslog import mailman_log, syslog -from Mailman.Utils import validate_ip_address -import Mailman.Handlers.Replybot as Replybot -from Mailman.Message import Message, UserNotification +from Mailman import Message +from Mailman.Handlers import Replybot from Mailman.i18n import _ from Mailman.Queue.Runner import Runner +from Mailman.Logging.Syslog import syslog from Mailman import LockFile -from Mailman import Pending -from Mailman import MailList -import traceback -import os -# Lazy imports to avoid circular dependencies -def get_replybot(): - import Mailman.Handlers.Replybot as Replybot - return Replybot - -def get_maillist(): - import Mailman.MailList as MailList - return MailList.MailList - -def get_usernotification(): - from Mailman.Message import UserNotification - return UserNotification +from email.header import decode_header, make_header, Header +from email.errors import HeaderParseError +from email.iterators import typed_subpart_iterator +from email.mime.text import MIMEText +from email.mime.message import MIMEMessage NL = '\n' CONTINUE = 0 @@ -69,28 +50,10 @@ def get_usernotification(): BADCMD = 2 BADSUBJ = 3 -# List of valid commands that can be imported -VALID_COMMANDS = { - 'confirm', # Confirm subscription/unsubscription - 'echo', # Echo command - 'end', # End command - 'help', # Help command - 'info', # List information - 'join', # Join list - 'leave', # Leave list - 'lists', # List all lists - 'password', # Password command - 'remove', # Remove from list - 'set', # Set options - 'stop', # Stop command - 'subscribe', # Subscribe to list - 'unsubscribe',# Unsubscribe from list - 'who' # Who command -} - + class Results: - def __init__(self, mlist_obj, msg, msgdata): - self.mlist = mlist_obj + def __init__(self, mlist, msg, msgdata): + self.mlist = mlist self.msg = msg self.msgdata = msgdata # Only set returnaddr if the response is to go to someone other than @@ -103,17 +66,14 @@ def __init__(self, mlist_obj, msg, msgdata): self.lineno = 0 self.subjcmdretried = 0 self.respond = True - # Extract the subject header and do RFC 2047 decoding + # Extract the subject header and do RFC 2047 decoding. Note that + # Python 2.1's unicode() builtin doesn't call obj.__unicode__(). subj = msg.get('subject', '') try: - # If subj is already a Header object, convert it to string first - if isinstance(subj, Header): - subj = str(subj) - else: - subj = str(make_header(decode_header(subj))) + subj = make_header(decode_header(subj)).__str__() # TK: Currently we don't allow 8bit or multibyte in mail command. # MAS: However, an l10n 'Re:' may contain non-ascii so ignore it. - subj = subj.encode('us-ascii', 'ignore').decode('us-ascii') + subj = subj.encode('us-ascii', 'ignore') # Always process the Subject: header first self.commands.append(subj) except (HeaderParseError, UnicodeError, LookupError): @@ -132,15 +92,12 @@ def __init__(self, mlist_obj, msg, msgdata): return body = part.get_payload(decode=True) if (part.get_content_charset(None)): - # Use get() with default value for lang - lang = msgdata.get('lang', mlist_obj.preferred_language) body = str(body, part.get_content_charset(), errors='replace').encode( - Utils.GetCharSet(lang), + Utils.GetCharSet(self.msgdata['lang']), errors='replace') # text/plain parts better have string payloads - if not isinstance(body, (str, bytes)): - raise TypeError(f'Invalid body type: {type(body)}, expected str or bytes') + assert isinstance(body, str) or isinstance(body, bytes) lines = body.splitlines() # Use no more lines than specified self.commands.extend(lines[:mm_cfg.DEFAULT_MAIL_COMMANDS_MAX_LINES]) @@ -153,12 +110,6 @@ def process(self): ret = CONTINUE for line in self.commands: if line and line.strip(): - # Ensure line is a string - if isinstance(line, bytes): - try: - line = line.decode('utf-8') - except UnicodeDecodeError: - line = line.decode('latin-1') args = line.split() cmd = args.pop(0).lower() ret = self.do_command(cmd, args) @@ -172,37 +123,43 @@ def process(self): def do_command(self, cmd, args=None): if args is None: args = () - # Clean the command name to prevent injection - cmd = cmd.lower().strip() - # Only try to import valid commands - if cmd not in VALID_COMMANDS: + # Try to import a command handler module for this command + if isinstance(cmd, bytes): + cmd = cmd.decode() + modname = 'Mailman.Commands.cmd_' + cmd + try: + __import__(modname) + handler = sys.modules[modname] + # ValueError can be raised if cmd has dots in it. + # and KeyError if cmd is otherwise good but ends with a dot. + # and TypeError if cmd has a null byte. + except (ImportError, ValueError, KeyError, TypeError) as e: # If we're on line zero, it was the Subject: header that didn't # contain a command. It's possible there's a Re: prefix (or # localized version thereof) on the Subject: line that's messing # things up. Pop the prefix off and try again... once. + # + # At least one MUA (163.com web mail) has been observed that + # inserts 'Re:' with no following space, so try to account for + # that too. + # + # If that still didn't work it isn't enough to stop processing. + # BAW: should we include a message that the Subject: was ignored? + # + # But first, be sure we're looking at the Subject: and not past + # it already. if self.lineno != 0: return BADCMD if self.subjcmdretried < 1: self.subjcmdretried += 1 if re.search('^.*:.+', cmd): - cmd = re.sub('.*:', '', cmd).lower().strip() + cmd = re.sub('.*:', '', cmd).lower() return self.do_command(cmd, args) if self.subjcmdretried < 2 and args: self.subjcmdretried += 1 - cmd = args.pop(0).lower().strip() + cmd = args.pop(0).lower() return self.do_command(cmd, args) return BADSUBJ - - # Try to import a command handler module for this command - modname = 'Mailman.Commands.cmd_' + cmd - try: - __import__(modname) - handler = sys.modules[modname] - except (ImportError, ValueError, KeyError, TypeError) as e: - syslog('error', 'CommandRunner: Failed to import command module %s: %s', - modname, str(e)) - return BADCMD - if handler.process(self, args): return STOP else: @@ -211,18 +168,8 @@ def do_command(self, cmd, args=None): def send_response(self): # Helper def indent(lines): - """Indent each line with 4 spaces.""" - result = [] - for line in lines: - if isinstance(line, bytes): - try: - # Try UTF-8 first - line = line.decode('utf-8') - except UnicodeDecodeError: - # Fall back to latin-1 if UTF-8 fails - line = line.decode('latin-1') - result.append(' ' + line) - return result + normalized = [line.decode() if isinstance(line, bytes) else line for line in lines] + return [' ' + line for line in normalized] # Quick exit for some commands which don't need a response if not self.respond: return @@ -249,22 +196,15 @@ def indent(lines): resp.append(_('\n- Ignored:')) resp.extend(indent(self.ignored)) resp.append(_('\n- Done.\n\n')) - # Encode any strings into the list charset, so we don't try to - # join strings and invalid ASCII. - charset = Utils.GetCharSet(self.msgdata.get('lang', self.mlist.preferred_language)) + # Encode any unicode strings into the list charset, so we don't try to + # join unicode strings and invalid ASCII. + charset = Utils.GetCharSet(self.msgdata['lang']) encoded_resp = [] for item in resp: - if isinstance(item, str): - item = item.encode(charset, 'replace') - # Convert bytes to string for joining if isinstance(item, bytes): - try: - item = item.decode(charset, 'replace') - except UnicodeDecodeError: - item = item.decode('latin-1', 'replace') + item = item.decode() encoded_resp.append(item) - # Join all items as strings - results = MIMEText(NL.join(str(item) for item in encoded_resp), _charset=charset) + results = MIMEText(NL.join(encoded_resp), _charset=charset) # Safety valve for mail loops with misconfigured email 'bots. We # don't respond to commands sent with "Precedence: bulk|junk|list" # unless they explicitly "X-Ack: yes", but not all mail 'bots are @@ -274,13 +214,13 @@ def indent(lines): # BAW: We wait until now to make this decision since our sender may # not be self.msg.get_sender(), but I'm not sure this is right. recip = self.returnaddr or self.msg.get_sender() - if not self.mlist.autorespondToSender(recip, self.msgdata.get('lang', self.mlist.preferred_language)): + if not self.mlist.autorespondToSender(recip, self.msgdata['lang']): return - msg = UserNotification( + msg = Message.UserNotification( recip, self.mlist.GetOwnerEmail(), _('The results of your email commands'), - lang=self.msgdata.get('lang', self.mlist.preferred_language)) + lang=self.msgdata['lang']) msg.set_type('multipart/mixed') msg.attach(results) if mm_cfg.RESPONSE_INCLUDE_LEVEL == 1: @@ -293,250 +233,61 @@ def indent(lines): else: orig = MIMEMessage(self.msg) msg.attach(orig) - # Add recipient to msgdata to ensure proper delivery - msgdata = {'recipient': recip} - msg.send(self.mlist, msgdata=msgdata) + msg.send(self.mlist) + + class CommandRunner(Runner): QDIR = mm_cfg.CMDQUEUE_DIR - def _validate_message(self, msg, msgdata): - """Validate a command message. - - Args: - msg: The message to validate - msgdata: Additional message metadata - - Returns: - tuple: (msg, success) where success is True if validation passed - """ - try: - # Convert email.message.Message to Mailman.Message if needed - if isinstance(msg, email.message.Message) and not isinstance(msg, Message): - mailman_msg = Message() - # Copy all attributes from the original message - for key, value in msg.items(): - mailman_msg[key] = value - # Copy the payload with proper MIME handling - if msg.is_multipart(): - for part in msg.get_payload(): - if isinstance(part, email.message.Message): - mailman_msg.attach(part) - else: - newpart = Message() - newpart.set_payload(part) - mailman_msg.attach(newpart) - else: - mailman_msg.set_payload(msg.get_payload()) - msg = mailman_msg - - # Check for required headers - if not msg.get('message-id'): - syslog('error', 'CommandRunner._validate_message: Missing Message-ID header') - return msg, False - - if not msg.get('from'): - syslog('error', 'CommandRunner._validate_message: Missing From header') - return msg, False - - # Check for command type in msgdata - if not any(key in msgdata for key in ('torequest', 'tojoin', 'toleave', 'toconfirm')): - syslog('error', 'CommandRunner._validate_message: No command type found in msgdata') - return msg, False - - return msg, True - - except Exception as e: - syslog('error', 'CommandRunner._validate_message: Error validating message: %s', str(e)) - return msg, False - def _dispose(self, mlist, msg, msgdata): - """Process a command message. - - Args: - mlist: The MailList instance this message is destined for - msg: The Message object representing the message - msgdata: Dictionary of message metadata - - Returns: - bool: True if message should be requeued, False if processing is complete - """ - msgid = msg.get('message-id', 'n/a') - filebase = msgdata.get('_filebase', 'unknown') - - # Ensure we have a MailList object - if isinstance(mlist, str): - try: - mlist = get_maillist()(mlist, lock=0) - should_unlock = True - except Errors.MMUnknownListError: - syslog('error', 'CommandRunner: Unknown list %s', mlist) - self._shunt.enqueue(msg, msgdata) - return False - else: - should_unlock = False - - try: - syslog('debug', 'CommandRunner._dispose: Starting to process command message %s (file: %s) for list %s', - msgid, filebase, mlist.internal_name()) - - # Check retry delay and duplicate processing - if not self._check_retry_delay(msgid, filebase): - syslog('debug', 'CommandRunner._dispose: Message %s failed retry delay check, skipping', msgid) - return True - - # Validate message type first - msg, success = self._validate_message(msg, msgdata) - if not success: - syslog('error', 'CommandRunner._dispose: Message validation failed for message %s', msgid) - msgdata['_validation_failure'] = 'Missing required headers' - self._shunt.enqueue(msg, msgdata) - return False - - # The policy here is similar to the Replybot policy. If a message has - # "Precedence: bulk|junk|list" and no "X-Ack: yes" header, we discard - # it to prevent replybot response storms. - precedence = msg.get('precedence', '').lower() - ack = msg.get('x-ack', '').lower() - if ack != 'yes' and precedence in ('bulk', 'junk', 'list'): - syslog('vette', 'Precedence: %s message discarded by: %s', - precedence, mlist.GetRequestEmail()) - return False - - # Lock the list before any operations - try: - mlist.Lock(timeout=mm_cfg.LIST_LOCK_TIMEOUT) - except LockFile.TimeOutError: - # Oh well, try again later - return True - - try: - # Check if list is temporarily unavailable - try: - mlist.Load() - except Errors.MMCorruptListDatabaseError as e: - syslog('error', 'CommandRunner._dispose: List %s is temporarily unavailable: %s', - mlist.internal_name(), str(e)) - return True - except Exception as e: - syslog('error', 'CommandRunner._dispose: Error loading list %s: %s', - mlist.internal_name(), str(e)) - return True - - # Do replybot for commands - Replybot = get_replybot() - Replybot.process(mlist, msg, msgdata) - if mlist.autorespond_requests == 1: - syslog('vette', 'replied and discard') - # w/discard - return False - - # Now craft the response - res = Results(mlist, msg, msgdata) - # This message will have been delivered to one of mylist-request, - # mylist-join, or mylist-leave, and the message metadata will contain - # a key to which one was used. - ret = BADCMD - if msgdata.get('torequest', False): - ret = res.process() - elif msgdata.get('tojoin', False): - ret = res.do_command('join') - elif msgdata.get('toleave', False): - ret = res.do_command('leave') - elif msgdata.get('toconfirm', False): - mo = re.match(mm_cfg.VERP_CONFIRM_REGEXP, msg.get('to', ''), re.IGNORECASE) - if mo: - ret = res.do_command('confirm', (mo.group('cookie'),)) - if ret == BADCMD and mm_cfg.DISCARD_MESSAGE_WITH_NO_COMMAND: - syslog('vette', - 'No command, message discarded, msgid: %s', - msg.get('message-id', 'n/a')) - return False - else: - res.send_response() - mlist.Save() - return False - finally: - mlist.Unlock() - - except Exception as e: - syslog('error', 'CommandRunner._dispose: Error processing command message %s: %s\nTraceback:\n%s', - msgid, str(e), traceback.format_exc()) - self._shunt.enqueue(msg, msgdata) + # The policy here is similar to the Replybot policy. If a message has + # "Precedence: bulk|junk|list" and no "X-Ack: yes" header, we discard + # it to prevent replybot response storms. + precedence = msg.get('precedence', '').lower() + ack = msg.get('x-ack', '').lower() + if ack != 'yes' and precedence in ('bulk', 'junk', 'list'): + syslog('vette', 'Precedence: %s message discarded by: %s', + precedence, mlist.GetRequestEmail()) return False - finally: - if should_unlock: - mlist.Unlock() - - def _oneloop(self): - """Process one batch of messages from the command queue.""" + # Do replybot for commands + mlist.Load() + Replybot.process(mlist, msg, msgdata) + if mlist.autorespond_requests == 1: + syslog('vette', 'replied and discard') + # w/discard + return False + # Now craft the response + res = Results(mlist, msg, msgdata) + # BAW: Not all the functions of this qrunner require the list to be + # locked. Still, it's more convenient to lock it here and now and + # deal with lock failures in one place. try: - # Get the list of files to process - files = self._switchboard.files() - if not files: - syslog('debug', 'CommandRunner: No files to process') - return - - syslog('debug', 'CommandRunner: Processing %d files', len(files)) - - # Process each file - for filebase in files: - try: - # Check if the file exists before dequeuing - pckfile = os.path.join(self.QDIR, filebase + '.pck') - if not os.path.exists(pckfile): - syslog('error', 'CommandRunner._oneloop: File %s does not exist, skipping', pckfile) - continue - - # Check if file is locked - lockfile = os.path.join(self.QDIR, filebase + '.pck.lock') - if os.path.exists(lockfile): - syslog('debug', 'CommandRunner._oneloop: File %s is locked by another process, skipping', filebase) - continue - - # Dequeue the file - msg, msgdata = self._switchboard.dequeue(filebase) - if msg is None: - syslog('debug', 'CommandRunner._oneloop: No message data for %s', filebase) - continue - - # Get the list name from msgdata - listname = msgdata.get('listname') - if not listname: - syslog('error', 'CommandRunner._oneloop: No listname in message data for file %s', filebase) - self._shunt.enqueue(msg, msgdata) - continue - - # Open the list - try: - mlist = MailList.MailList(listname, lock=False) - except Errors.MMUnknownListError: - syslog('error', 'CommandRunner._oneloop: Unknown list %s for message %s (file: %s)', - listname, msg.get('message-id', 'n/a'), filebase) - self._shunt.enqueue(msg, msgdata) - continue - - try: - # Process the message - self._dispose(mlist, msg, msgdata) - syslog('debug', 'CommandRunner: Successfully processed message %s', filebase) - except Exception as e: - syslog('error', 'CommandRunner: Error processing %s: %s', filebase, str(e)) - syslog('error', 'CommandRunner: Traceback:\n%s', traceback.format_exc()) - self._handle_error(e, msg, mlist) - finally: - mlist.Unlock() - - except Exception as e: - syslog('error', 'CommandRunner: Error processing file %s: %s', filebase, str(e)) - syslog('error', 'CommandRunner: Traceback:\n%s', traceback.format_exc()) - continue - - except Exception as e: - syslog('error', 'CommandRunner: Error in _oneloop: %s', str(e)) - syslog('error', 'CommandRunner: Traceback:\n%s', traceback.format_exc()) - raise - -# Set up i18n -_ = i18n._ -i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + mlist.Lock(timeout=mm_cfg.LIST_LOCK_TIMEOUT) + except LockFile.TimeOutError: + # Oh well, try again later + return True + # This message will have been delivered to one of mylist-request, + # mylist-join, or mylist-leave, and the message metadata will contain + # a key to which one was used. + try: + ret = BADCMD + if msgdata.get('torequest'): + ret = res.process() + elif msgdata.get('tojoin'): + ret = res.do_command('join') + elif msgdata.get('toleave'): + ret = res.do_command('leave') + elif msgdata.get('toconfirm'): + mo = re.match(mm_cfg.VERP_CONFIRM_REGEXP, msg.get('to', '')) + if mo: + ret = res.do_command('confirm', (mo.group('cookie'),)) + if ret == BADCMD and mm_cfg.DISCARD_MESSAGE_WITH_NO_COMMAND: + syslog('vette', + 'No command, message discarded, msgid: %s', + msg.get('message-id', 'n/a')) + else: + res.send_response() + mlist.Save() + finally: + mlist.Unlock() diff --git a/Mailman/Queue/IncomingRunner.py b/Mailman/Queue/IncomingRunner.py index fc819820..e14d5316 100644 --- a/Mailman/Queue/IncomingRunner.py +++ b/Mailman/Queue/IncomingRunner.py @@ -14,11 +14,7 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -"""Incoming message queue runner. - -This qrunner handles messages that are posted to the mailing list. It is -responsible for running the message through the pipeline of handlers. -""" +"""Incoming queue runner.""" # A typical Mailman list exposes nine aliases which point to seven different # wrapped scripts. E.g. for a list named `mylist', you'd have: @@ -97,65 +93,22 @@ # performed. Results notifications are sent to the author of the message, # which all bounces pointing back to the -bounces address. -import os + import sys -import time -import traceback -from io import StringIO -import random -import signal import os -import email -from email import message_from_string -from email.message import Message as EmailMessage -from urllib.parse import parse_qs -from Mailman.Utils import reap -from Mailman import Utils +from io import StringIO from Mailman import mm_cfg from Mailman import Errors from Mailman import LockFile from Mailman.Queue.Runner import Runner -from Mailman.Queue.Switchboard import Switchboard -from Mailman.Logging.Syslog import mailman_log -import Mailman.MailList as MailList -import Mailman.Message -import threading -import email.header - - -class PipelineError(Exception): - """Exception raised when pipeline processing fails.""" - pass +from Mailman.Logging.Syslog import syslog + class IncomingRunner(Runner): QDIR = mm_cfg.INQUEUE_DIR - # Enable message tracking for incoming messages - _track_messages = True - _max_processed_messages = 10000 - _max_retry_times = 10000 - - # Retry configuration - MIN_RETRY_DELAY = 300 # 5 minutes minimum delay between retries - MAX_RETRIES = 5 # Maximum number of retry attempts - _retry_times = {} # Track last retry time for each message - - def __init__(self, slice=None, numslices=1): - mailman_log('debug', 'IncomingRunner: Starting initialization') - try: - Runner.__init__(self, slice, numslices) - mailman_log('debug', 'IncomingRunner: Initialization complete') - except Exception as e: - mailman_log('error', 'IncomingRunner: Initialization failed: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - raise - - def _convert_message(self, msg): - """Convert email.message.Message to Mailman.Message with proper handling of nested messages.""" - return Runner._convert_message(self, msg) - def _dispose(self, mlist, msg, msgdata): # Try to get the list lock. try: @@ -182,12 +135,25 @@ def _dispose(self, mlist, msg, msgdata): finally: mlist.Unlock() + # Overridable def _get_pipeline(self, mlist, msg, msgdata): # We must return a copy of the list, otherwise, the first message that # flows through the pipeline will empty it out! - return msgdata.get('pipeline', - getattr(mlist, 'pipeline', - mm_cfg.GLOBAL_PIPELINE))[:] + pipeline = msgdata.get('pipeline') + if pipeline is None: + pipeline = getattr(mlist, 'pipeline', None) + else: + # Use the already-imported mm_cfg directly + pipeline = mm_cfg.GLOBAL_PIPELINE + + # Ensure pipeline is a list that can be sliced + if not isinstance(pipeline, list): + syslog('error', 'pipeline is not a list: %s (type: %s)', + pipeline, type(pipeline).__name__) + # Fallback to a basic pipeline + pipeline = mm_cfg.GLOBAL_PIPELINE + + return pipeline[:] def _dopipeline(self, mlist, msg, msgdata, pipeline): while pipeline: @@ -199,7 +165,7 @@ def _dopipeline(self, mlist, msg, msgdata, pipeline): sys.modules[modname].process(mlist, msg, msgdata) # Failsafe -- a child may have leaked through. if pid != os.getpid(): - mailman_log('error', 'Child process leaked through: %s', modname) + syslog('error', 'child process leaked thru: %s', modname) os._exit(1) except Errors.DiscardMessage: # Throw the message away; we need do nothing else with it. @@ -207,7 +173,7 @@ def _dopipeline(self, mlist, msg, msgdata, pipeline): # just in case the syslog call throws an exception and the # message is shunted. pipeline.insert(0, handler) - mailman_log('vette', """Message discarded, msgid: %s + syslog('vette', """Message discarded, msgid: %s' list: %s, handler: %s""", msg.get('message-id', 'n/a'), @@ -223,7 +189,7 @@ def _dopipeline(self, mlist, msg, msgdata, pipeline): # just in case the syslog call or BounceMessage throws an # exception and the message is shunted. pipeline.insert(0, handler) - mailman_log('vette', """Message rejected, msgid: %s + syslog('vette', """Message rejected, msgid: %s list: %s, handler: %s, reason: %s""", @@ -238,322 +204,3 @@ def _dopipeline(self, mlist, msg, msgdata, pipeline): raise # We've successfully completed handling of this message return 0 - - def _is_command(self, msg): - """Check if the message is a command.""" - try: - subject = msg.get('subject', '').lower() - if subject.startswith('subscribe') or subject.startswith('unsubscribe'): - mailman_log('debug', 'IncomingRunner._is_command: Message is a subscription command') - return True - return False - except Exception as e: - mailman_log('error', 'IncomingRunner._is_command: Error checking command: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - return False - - def _is_bounce(self, msg): - """Check if a message is a bounce message.""" - # Check for common bounce headers - if msg.get('x-failed-recipients'): - return True - if msg.get('x-original-to'): - return True - if msg.get('return-path', '').startswith('<>'): - return True - # Check content type for multipart/report - if msg.get('content-type', '').startswith('multipart/report'): - return True - # Check for common bounce subjects - subject = msg.get('subject', '') - if isinstance(subject, email.header.Header): - subject = str(subject) - subject = subject.lower() - bounce_subjects = ['delivery status', 'failure notice', 'mail delivery failed', - 'mail delivery system', 'mail system error', 'returned mail', - 'undeliverable', 'undelivered mail'] - for bounce_subject in bounce_subjects: - if bounce_subject in subject: - return True - return False - - def _process_command(self, mlist, msg, msgdata): - """Process a command message.""" - msgid = msg.get('message-id', 'n/a') - try: - mailman_log('debug', 'IncomingRunner._process_command: Processing command for message %s', msgid) - # Process the command - # ... command processing logic ... - mailman_log('debug', 'IncomingRunner._process_command: Successfully processed command for message %s', msgid) - return True - except Exception as e: - mailman_log('error', 'IncomingRunner._process_command: Error processing command for message %s: %s\nTraceback:\n%s', - msgid, str(e), traceback.format_exc()) - return False - - def _process_bounce(self, mlist, msg, msgdata): - """Process a bounce message.""" - msgid = msg.get('message-id', 'n/a') - try: - mailman_log('debug', 'IncomingRunner._process_bounce: Processing bounce for message %s', msgid) - # Process the bounce - # ... bounce processing logic ... - mailman_log('debug', 'IncomingRunner._process_bounce: Successfully processed bounce for message %s', msgid) - return True - except Exception as e: - mailman_log('error', 'IncomingRunner._process_bounce: Error processing bounce for message %s: %s\nTraceback:\n%s', - msgid, str(e), traceback.format_exc()) - return False - - def _process_regular_message(self, mlist, msg, msgdata): - """Process a regular message.""" - msgid = msg.get('message-id', 'n/a') - try: - mailman_log('debug', 'IncomingRunner._process_regular_message: Processing regular message %s', msgid) - # Process the regular message - # ... regular message processing logic ... - mailman_log('debug', 'IncomingRunner._process_regular_message: Successfully processed regular message %s', msgid) - return True - except Exception as e: - mailman_log('error', 'IncomingRunner._process_regular_message: Error processing regular message %s: %s\nTraceback:\n%s', - msgid, str(e), traceback.format_exc()) - return False - - def _cleanup(self): - """Clean up resources.""" - mailman_log('debug', 'IncomingRunner: Starting cleanup') - try: - Runner._cleanup(self) - except Exception as e: - mailman_log('error', 'IncomingRunner: Cleanup failed: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - mailman_log('debug', 'IncomingRunner: Cleanup complete') - - def _oneloop(self): - """Process one batch of messages from the incoming queue.""" - try: - # Get the list of files to process - files = self._switchboard.files() - filecnt = len(files) - - # Only log at debug level if we found files to process - if filecnt > 0: - mailman_log('debug', 'IncomingRunner._oneloop: Found %d files to process', filecnt) - - # Process each file - for filebase in files: - # Check stop flag at the start of each file - if self._stop: - mailman_log('debug', 'IncomingRunner._oneloop: Stop flag detected, stopping processing') - return filecnt - - try: - # Check if the file exists before dequeuing - pckfile = os.path.join(self.QDIR, filebase + '.pck') - if not os.path.exists(pckfile): - mailman_log('error', 'IncomingRunner._oneloop: File %s does not exist, skipping', pckfile) - continue - - # Check if file is locked - lockfile = os.path.join(self.QDIR, filebase + '.pck.lock') - if os.path.exists(lockfile): - mailman_log('debug', 'IncomingRunner._oneloop: File %s is locked by another process, skipping', filebase) - continue - - # Dequeue the file - msg, msgdata = self._switchboard.dequeue(filebase) - - # If dequeue failed due to file being locked, skip it - if msg is None and msgdata is None: - # For other None,None cases, shunt the message - mailman_log('error', 'IncomingRunner._oneloop: Failed to dequeue file %s (got None values), shunting', filebase) - # Create a basic message and metadata if we don't have them - msg = Message() - msgdata = {} - # Add the original queue information - msgdata['whichq'] = self.QDIR - # Shunt the message - self._shunt.enqueue(msg, msgdata) - # Remove the original file - try: - os.unlink(pckfile) - mailman_log('debug', 'IncomingRunner._oneloop: Removed original file %s', pckfile) - except OSError as e: - mailman_log('error', 'IncomingRunner._oneloop: Failed to remove original file %s: %s', pckfile, str(e)) - continue - - # Try to get message-id early for logging purposes - try: - msgid = msg.get('message-id', 'n/a') - except Exception as e: - msgid = 'unknown' - mailman_log('error', 'IncomingRunner._oneloop: Error getting message-id for file %s: %s', filebase, str(e)) - - # Get the list name - listname = msgdata.get('listname', 'unknown') - try: - mlist = MailList.MailList(listname, lock=False) - except Errors.MMUnknownListError: - mailman_log('error', 'IncomingRunner._oneloop: Unknown list %s for message %s (file: %s)', - listname, msgid, filebase) - self._shunt.enqueue(msg, msgdata) - # Remove the original file - try: - os.unlink(pckfile) - mailman_log('debug', 'IncomingRunner._oneloop: Removed original file %s', pckfile) - except OSError as e: - mailman_log('error', 'IncomingRunner._oneloop: Failed to remove original file %s: %s', pckfile, str(e)) - continue - - # Process the message - try: - result = self._dispose(mlist, msg, msgdata) - - # If the message should be kept in the queue, requeue it - if result: - # Get pipeline information for logging - pipeline = msgdata.get('pipeline', []) - current_handler = pipeline[0] if pipeline else 'unknown' - next_handler = pipeline[1] if len(pipeline) > 1 else 'none' - - # Get retry information - retry_count = msgdata.get('retry_count', 0) - last_retry = self._retry_times.get(msgid, 0) - next_retry = time.ctime(last_retry + self.MIN_RETRY_DELAY) if last_retry else 'unknown' - - # Log detailed requeue information - mailman_log('info', 'IncomingRunner._oneloop: Message requeued for later processing: %s (msgid: %s)', - filebase, msgid) - mailman_log('debug', ' Current state:') - mailman_log('debug', ' - Current handler: %s', current_handler) - mailman_log('debug', ' - Next handler: %s', next_handler) - mailman_log('debug', ' - Retry count: %d', retry_count) - mailman_log('debug', ' - Last retry: %s', time.ctime(last_retry) if last_retry else 'none') - mailman_log('debug', ' - Next retry: %s', next_retry) - mailman_log('debug', ' - List: %s', mlist.internal_name()) - mailman_log('debug', ' - Message type: %s', msgdata.get('_msgtype', 'unknown')) - - # Requeue the message and remove the original file - self._switchboard.enqueue(msg, msgdata) - try: - os.unlink(pckfile) - mailman_log('debug', 'IncomingRunner._oneloop: Removed original file %s', pckfile) - except OSError as e: - mailman_log('error', 'IncomingRunner._oneloop: Failed to remove original file %s: %s', pckfile, str(e)) - else: - mailman_log('info', 'IncomingRunner._oneloop: Message processing complete, moving to shunt queue %s (msgid: %s)', - filebase, msgid) - # Move to shunt queue and remove the original file - self._shunt.enqueue(msg, msgdata) - try: - os.unlink(pckfile) - mailman_log('debug', 'IncomingRunner._oneloop: Removed original file %s', pckfile) - except OSError as e: - mailman_log('error', 'IncomingRunner._oneloop: Failed to remove original file %s: %s', pckfile, str(e)) - - except Exception as e: - mailman_log('error', 'IncomingRunner._oneloop: Error processing message %s (file: %s): %s\n%s', - msgid, filebase, str(e), traceback.format_exc()) - # Move to shunt queue on error and remove the original file - self._shunt.enqueue(msg, msgdata) - try: - os.unlink(pckfile) - mailman_log('debug', 'IncomingRunner._oneloop: Removed original file %s', pckfile) - except OSError as e: - mailman_log('error', 'IncomingRunner._oneloop: Failed to remove original file %s: %s', pckfile, str(e)) - - except Exception as e: - mailman_log('error', 'IncomingRunner._oneloop: Error dequeuing file %s: %s\n%s', - filebase, str(e), traceback.format_exc()) - - # Only log completion at debug level if we processed files - if filecnt > 0: - mailman_log('debug', 'IncomingRunner._oneloop: Loop complete, processed %d files', filecnt) - - except Exception as e: - mailman_log('error', 'IncomingRunner._oneloop: Unexpected error in main loop: %s\n%s', - str(e), traceback.format_exc()) - # Don't re-raise the exception to keep the runner alive - return False - return True - - def _check_retry_delay(self, msgid, filebase): - """Check if enough time has passed since the last retry attempt.""" - now = time.time() - last_retry = self._retry_times.get(msgid, 0) - - if now - last_retry < self.MIN_RETRY_DELAY: - mailman_log('debug', 'IncomingRunner._check_retry_delay: Message %s (file: %s) retry delay not met. Last retry: %s, Now: %s, Delay needed: %s', - msgid, filebase, time.ctime(last_retry), time.ctime(now), self.MIN_RETRY_DELAY) - return False - - mailman_log('debug', 'IncomingRunner._check_retry_delay: Message %s (file: %s) retry delay met. Last retry: %s, Now: %s', - msgid, filebase, time.ctime(last_retry), time.ctime(now)) - return True - - def _mark_message_processed(self, msgid): - """Mark a message as processed.""" - with self._processed_lock: - self._processed_messages.add(msgid) - - def _unmark_message_processed(self, msgid): - """Remove a message from the processed set.""" - with self._processed_lock: - self._processed_messages.discard(msgid) - - def _process_admin(self, mlist, msg, msgdata): - """Process an admin message.""" - msgid = msg.get('message-id', 'n/a') - try: - mailman_log('debug', 'IncomingRunner._process_admin: Processing admin message %s', msgid) - - # Get admin information - recipient = msgdata.get('recipient', 'unknown') - admin_type = msgdata.get('admin_type', 'unknown') - - mailman_log('debug', 'IncomingRunner._process_admin: Admin message for %s, type: %s', - recipient, admin_type) - - # Process the admin message - # ... admin message processing logic ... - - mailman_log('debug', 'IncomingRunner._process_admin: Successfully processed admin message %s', msgid) - return True - - except Exception as e: - mailman_log('error', 'IncomingRunner._process_admin: Error processing admin message %s: %s\nTraceback:\n%s', - msgid, str(e), traceback.format_exc()) - return False - - def _check_message_processed(self, msgid, filebase, msg): - """Check if a message has already been processed and if retry delay is met. - - Args: - msgid: The message ID to check - filebase: The base filename of the message - msg: The message object - - Returns: - bool: True if message should be skipped (already processed or retry delay not met), - False if message should be processed - """ - try: - # Check if message was recently processed - with self._processed_lock: - if msgid in self._processed_messages: - mailman_log('debug', 'IncomingRunner._check_message_processed: Message %s (file: %s) was recently processed, skipping', - msgid, filebase) - return True - - # Check if retry delay is met - if not self._check_retry_delay(msgid, filebase): - return True - - # Message should be processed - return False - - except Exception as e: - mailman_log('error', 'IncomingRunner._check_message_processed: Error checking message %s: %s\nTraceback:\n%s', - msgid, str(e), traceback.format_exc()) - # On error, allow the message to be processed - return False diff --git a/Mailman/Queue/MaildirRunner.py b/Mailman/Queue/MaildirRunner.py index 0c7d371b..78017132 100644 --- a/Mailman/Queue/MaildirRunner.py +++ b/Mailman/Queue/MaildirRunner.py @@ -47,25 +47,22 @@ mechanism. """ +# NOTE: Maildir delivery is experimental in Mailman 2.1. + from builtins import str import os import re import errno -import time -import traceback -from io import StringIO -import email -from email.utils import getaddresses, parsedate_tz, mktime_tz, parseaddr -from email.iterators import body_line_iterator + +from email.Parser import Parser +from email.utils import parseaddr from Mailman import mm_cfg from Mailman import Utils -from Mailman import Errors -from Mailman import i18n from Mailman.Message import Message -from Mailman.Logging.Syslog import syslog from Mailman.Queue.Runner import Runner from Mailman.Queue.sbcache import get_switchboard +from Mailman.Logging.Syslog import syslog # We only care about the listname and the subq as in listname@ or # listname-request@ @@ -90,48 +87,36 @@ """, re.VERBOSE | re.IGNORECASE) + class MaildirRunner(Runner): # This class is much different than most runners because it pulls files # of a different format than what scripts/post and friends leaves. The # files this runner reads are just single message files as dropped into # the directory by the MTA. This runner will read the file, and enqueue # it in the expected qfiles directory for normal processing. - QDIR = mm_cfg.MAILDIR_DIR - def __init__(self, slice=None, numslices=1): - syslog('debug', 'MaildirRunner: Starting initialization') - try: - Runner.__init__(self, slice, numslices) - self._dir = os.path.join(mm_cfg.MAILDIR_DIR, 'new') - self._cur = os.path.join(mm_cfg.MAILDIR_DIR, 'cur') - if not os.path.exists(self._dir): - os.makedirs(self._dir) - if not os.path.exists(self._cur): - os.makedirs(self._cur) - syslog('debug', 'MaildirRunner: Initialization complete') - except Exception as e: - syslog('error', 'MaildirRunner: Initialization failed: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - raise + # Don't call the base class constructor, but build enough of the + # underlying attributes to use the base class's implementation. + self._stop = 0 + self._dir = os.path.join(mm_cfg.MAILDIR_DIR, 'new') + self._cur = os.path.join(mm_cfg.MAILDIR_DIR, 'cur') + self._parser = Parser(Message) def _oneloop(self): - """Process one batch of messages from the maildir.""" - # Refresh this each time through the list + # Refresh this each time through the list. BAW: could be too + # expensive. listnames = Utils.list_names() + # Cruise through all the files currently in the new/ directory try: files = os.listdir(self._dir) except OSError as e: - if e.errno != errno.ENOENT: - syslog('error', 'Error listing maildir directory: %s', str(e)) - raise + if e.errno != errno.ENOENT: raise # Nothing's been delivered yet return 0 - for file in files: srcname = os.path.join(self._dir, file) dstname = os.path.join(self._cur, file + ':1,P') xdstname = os.path.join(self._cur, file + ':1,X') - try: os.rename(srcname, dstname) except OSError as e: @@ -140,17 +125,19 @@ def _oneloop(self): continue syslog('error', 'Could not rename maildir file: %s', srcname) raise - + # Now open, read, parse, and enqueue this message try: - # Read and parse the message - with open(dstname, 'rb') as fp: - msg = email.message_from_binary_file(fp) - - # Figure out which queue of which list this message was destined for + fp = open(dstname) + try: + msg = self._parser.parse(fp) + finally: + fp.close() + # Now we need to figure out which queue of which list this + # message was destined for. See verp_bounce() in + # BounceRunner.py for why we do things this way. vals = [] for header in ('delivered-to', 'envelope-to', 'apparently-to'): vals.extend(msg.get_all(header, [])) - for field in vals: to = parseaddr(field)[1] if not to: @@ -164,14 +151,14 @@ def _oneloop(self): break else: # As far as we can tell, this message isn't destined for - # any list on the system + # any list on the system. What to do? syslog('error', 'Message apparently not for any list: %s', xdstname) os.rename(dstname, xdstname) continue - - # Determine which queue to use based on the subqueue + # BAW: blech, hardcoded msgdata = {'listname': listname} + # -admin is deprecated if subq in ('bounces', 'admin'): queue = get_switchboard(mm_cfg.BOUNCEQUEUE_DIR) elif subq == 'confirm': @@ -200,29 +187,11 @@ def _oneloop(self): syslog('error', 'Unknown sub-queue: %s', subq) os.rename(dstname, xdstname) continue - - # Enqueue the message and clean up queue.enqueue(msg, msgdata) os.unlink(dstname) - syslog('debug', 'Successfully processed maildir message: %s', file) - except Exception as e: - syslog('error', 'Error processing maildir file %s: %s\nTraceback:\n%s', - file, str(e), traceback.format_exc()) - try: - os.rename(dstname, xdstname) - except OSError: - pass - - return len(files) + os.rename(dstname, xdstname) + syslog('error', str(e)) def _cleanup(self): - """Clean up resources.""" - syslog('debug', 'MaildirRunner: Starting cleanup') - try: - # Call parent cleanup - super(MaildirRunner, self)._cleanup() - except Exception as e: - syslog('error', 'MaildirRunner: Cleanup failed: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - syslog('debug', 'MaildirRunner: Cleanup complete') + pass diff --git a/Mailman/Queue/NewsRunner.py b/Mailman/Queue/NewsRunner.py index 75a345d5..4a6c92d4 100644 --- a/Mailman/Queue/NewsRunner.py +++ b/Mailman/Queue/NewsRunner.py @@ -20,33 +20,24 @@ from builtins import str import re import socket +try: + import nntplib + NNTPLIB_AVAILABLE = True +except ImportError: + NNTPLIB_AVAILABLE = False from io import StringIO -import time -import traceback -import os -import pickle import email -from email.utils import getaddresses, parsedate_tz, mktime_tz -from email.iterators import body_line_iterator +import email.iterators +from email.utils import getaddresses COMMASPACE = ', ' from Mailman import mm_cfg from Mailman import Utils -from Mailman import Errors -from Mailman import i18n from Mailman.Queue.Runner import Runner -from Mailman.Logging.Syslog import mailman_log, syslog -import Mailman.Message as Message -import Mailman.MailList as MailList +from Mailman.Logging.Syslog import syslog -# Only import nntplib if NNTP support is enabled -try: - import nntplib - HAVE_NNTP = True -except ImportError: - HAVE_NNTP = False # Matches our Mailman crafted Message-IDs. See Utils.unique_message_id() mcre = re.compile(r""" @@ -61,207 +52,55 @@ """, re.VERBOSE) + class NewsRunner(Runner): QDIR = mm_cfg.NEWSQUEUE_DIR - def __init__(self, slice=None, numslices=1): - # First check if NNTP support is enabled - if not mm_cfg.NNTP_SUPPORT: - syslog('warning', 'NNTP support is not enabled. NewsRunner will not process messages.') - return - if not mm_cfg.DEFAULT_NNTP_HOST: - syslog('info', 'NewsRunner not processing messages due to DEFAULT_NNTP_HOST not being set') - return - # Initialize the base class - Runner.__init__(self, slice, numslices) - # Check if any lists require NNTP support - self._nntp_lists = [] - for listname in Utils.list_names(): - try: - mlist = MailList.MailList(listname, lock=False) - if mlist.nntp_host: - self._nntp_lists.append(listname) - except Errors.MMListError: - continue - if not self._nntp_lists: - syslog('info', 'No lists require NNTP support. NewsRunner will not be started.') - return - # Initialize the NNTP connection - self._nntp = None - self._connect() - - def _connect(self): - """Connect to the NNTP server.""" - try: - self._nntp = nntplib.NNTP(mm_cfg.DEFAULT_NNTP_HOST, - mm_cfg.DEFAULT_NNTP_PORT, - mm_cfg.DEFAULT_NNTP_USER, - mm_cfg.DEFAULT_NNTP_PASS) - except Exception as e: - syslog('error', 'NewsRunner error: %s', str(e)) - self._nntp = None - - def _validate_message(self, msg, msgdata): - """Validate the message for news posting. - - Args: - msg: The message to validate - msgdata: Additional message metadata - - Returns: - tuple: (msg, success) where success is True if validation passed - """ - try: - # Check if the message has a Message-ID - if not msg.get('message-id'): - syslog('error', 'Message validation failed for news message') - return msg, False - return msg, True - except Exception as e: - syslog('error', 'Error validating news message: %s', str(e)) - return msg, False - def _dispose(self, mlist, msg, msgdata): - """Post the message to the newsgroup.""" - try: - # Get the newsgroup name - newsgroup = mlist.nntp_host - if not newsgroup: - return False - # Post the message - self._nntp.post(str(msg)) - return False - except Exception as e: - syslog('error', 'Error posting message to newsgroup for list %s: %s', - mlist.internal_name(), str(e)) - return True - - def _onefile(self, msg, msgdata): - """Process a single news message. + # Make sure we have the most up-to-date state + mlist.Load() + if not msgdata.get('prepped'): + prepare_message(mlist, msg, msgdata) - This method overrides the base class's _onefile to add news-specific - validation and processing. + # Check if nntplib is available + if not NNTPLIB_AVAILABLE: + syslog('error', + '(NewsRunner) nntplib not available, cannot post to newsgroup for list "%s"', + mlist.internal_name()) + return False # Don't requeue, just drop the message - Args: - msg: The message to process - msgdata: Additional message metadata - """ try: - # Validate the message - msg, success = self._validate_message(msg, msgdata) - if not success: - syslog('error', 'NewsRunner._onefile: Message validation failed') - self._shunt.enqueue(msg, msgdata) - return - - # Get the list name from the message data - listname = msgdata.get('listname') - if not listname: - syslog('error', 'NewsRunner._onefile: No listname in message data') - self._shunt.enqueue(msg, msgdata) - return - - # Open the list + # Flatten the message object, sticking it in a StringIO object + fp = StringIO(msg.as_string()) + conn = None try: - mlist = self._open_list(listname) - except Exception as e: - self.log_error('list_open_error', str(e), listname=listname) - self._shunt.enqueue(msg, msgdata) - return - - # Process the message - try: - keepqueued = self._dispose(mlist, msg, msgdata) - if keepqueued: - self._switchboard.enqueue(msg, msgdata) - except Exception as e: - self._handle_error(e, msg=msg, mlist=mlist) - - except Exception as e: - syslog('error', 'NewsRunner._onefile: Unexpected error: %s', str(e)) - self._shunt.enqueue(msg, msgdata) - - def _oneloop(self): - """Process one batch of messages from the news queue.""" - try: - # Get the list of files to process - files = self._switchboard.files() - filecnt = len(files) - - # Process each file - for filebase in files: try: - # Check if the file exists before dequeuing - pckfile = os.path.join(self.QDIR, filebase + '.pck') - if not os.path.exists(pckfile): - syslog('error', 'NewsRunner._oneloop: File %s does not exist, skipping', pckfile) - continue - - # Check if file is locked - lockfile = os.path.join(self.QDIR, filebase + '.pck.lock') - if os.path.exists(lockfile): - syslog('debug', 'NewsRunner._oneloop: File %s is locked by another process, skipping', filebase) - continue - - # Dequeue the file - msg, msgdata = self._switchboard.dequeue(filebase) - if msg is None: - continue - - # Process the message - try: - self._onefile(msg, msgdata) - except Exception as e: - syslog('error', 'NewsRunner._oneloop: Error processing message %s: %s', filebase, str(e)) - continue - - except Exception as e: - syslog('error', 'NewsRunner._oneloop: Error dequeuing file %s: %s', filebase, str(e)) - continue - + nntp_host, nntp_port = Utils.nntpsplit(mlist.nntp_host) + conn = nntplib.NNTP(nntp_host, nntp_port, + readermode=True, + user=mm_cfg.NNTP_USERNAME, + password=mm_cfg.NNTP_PASSWORD) + conn.post(fp) + except nntplib.error_temp as e: + syslog('error', + '(NNTPDirect) NNTP error for list "%s": %s', + mlist.internal_name(), e) + except socket.error as e: + syslog('error', + '(NNTPDirect) socket error for list "%s": %s', + mlist.internal_name(), e) + finally: + if conn: + conn.quit() except Exception as e: - syslog('error', 'NewsRunner._oneloop: Error in main loop: %s', str(e)) - return 0 - - return filecnt - - def _queue_news(self, listname, msg, msgdata): - """Queue a news message for processing.""" - # Create a unique filename - now = time.time() - filename = os.path.join(mm_cfg.NEWSQUEUE_DIR, - '%d.%d.pck' % (os.getpid(), now)) - - # Write the message and metadata to the pickle file - try: - # Use protocol 4 for Python 3 compatibility - with open(filename, 'wb') as fp: - pickle.dump(listname, fp, protocol=4, fix_imports=True) - pickle.dump(msg, fp, protocol=4, fix_imports=True) - pickle.dump(msgdata, fp, protocol=4, fix_imports=True) - # Set the file's mode appropriately - os.chmod(filename, 0o660) - except (IOError, OSError) as e: - try: - os.unlink(filename) - except (IOError, OSError): - pass - raise SwitchboardError('Could not save news message to %s: %s' % - (filename, e)) - - def _cleanup(self): - """Clean up resources before termination.""" - # Close any open NNTP connections - if hasattr(self, '_nntp') and self._nntp: - try: - self._nntp.quit() - except Exception: - pass - self._nntp = None - # Call parent cleanup - super(NewsRunner, self)._cleanup() + # Some other exception occurred, which we definitely did not + # expect, so set this message up for requeuing. + self._log(e) + return True + return False + def prepare_message(mlist, msg, msgdata): # If the newsgroup is moderated, we need to add this header for the Usenet # software to accept the posting, and not forward it on to the n.g.'s @@ -328,7 +167,7 @@ def prepare_message(mlist, msg, msgdata): # Lines: is useful if msg['Lines'] is None: # BAW: is there a better way? - count = len(list(body_line_iterator(msg))) + count = len(list(email.iterators.body_line_iterator(msg))) msg['Lines'] = str(count) # Massage the message headers by remove some and rewriting others. This # woon't completely sanitize the message, but it will eliminate the bulk diff --git a/Mailman/Queue/OutgoingRunner.py b/Mailman/Queue/OutgoingRunner.py index bf710322..6208be2e 100644 --- a/Mailman/Queue/OutgoingRunner.py +++ b/Mailman/Queue/OutgoingRunner.py @@ -17,324 +17,43 @@ """Outgoing queue runner.""" -from builtins import object -import time -import socket -import smtplib -import traceback import os import sys -from io import StringIO -import threading -import email.message -import fcntl +import copy +import time +import socket + +import email from Mailman import mm_cfg -from Mailman import Utils +from Mailman import Message from Mailman import Errors -from Mailman import i18n -from Mailman.Logging.Syslog import mailman_log +from Mailman import LockFile from Mailman.Queue.Runner import Runner from Mailman.Queue.Switchboard import Switchboard from Mailman.Queue.BounceRunner import BounceMixin -from Mailman.MemberAdaptor import MemberAdaptor, ENABLED -import Mailman.Message as Message - -# Lazy import to avoid circular dependency -def get_mail_list(): - import Mailman.MailList as MailList - return MailList.MailList - -def get_replybot(): - import Mailman.Handlers.Replybot as Replybot - return Replybot +from Mailman.Logging.Syslog import syslog # This controls how often _doperiodic() will try to deal with deferred # permanent failures. It is a count of calls to _doperiodic() DEAL_WITH_PERMFAILURES_EVERY = 10 + class OutgoingRunner(Runner, BounceMixin): QDIR = mm_cfg.OUTQUEUE_DIR - # Process coordination - _pid_file = os.path.join(mm_cfg.LOCK_DIR, 'outgoing.pid') - _pid_lock = None - _running = False - - # Shared processed messages tracking with size limits - _processed_messages = set() - _processed_lock = threading.Lock() - _last_cleanup = time.time() - _cleanup_interval = 3600 # Clean up every hour - _max_processed_messages = 10000 - _max_retry_times = 10000 - - # Message counting - _total_messages_processed = 0 - _total_messages_lock = threading.Lock() - - # Retry configuration - MIN_RETRY_DELAY = 300 # 5 minutes minimum delay between retries - MAX_RETRIES = 5 # Maximum number of retry attempts - _retry_times = {} # Track last retry time for each message - - # Error tracking - _error_count = 0 - _last_error_time = 0 - _error_window = 300 # 5 minutes window for error counting - _max_errors = 10 def __init__(self, slice=None, numslices=1): - """Initialize the outgoing queue runner.""" - mailman_log('debug', 'OutgoingRunner: Initializing with slice=%s, numslices=%s', slice, numslices) - try: - # Check if another instance is already running - if not self._acquire_pid_lock(): - mailman_log('error', 'OutgoingRunner: Another instance is already running') - raise RuntimeError('Another OutgoingRunner instance is already running') - - Runner.__init__(self, slice, numslices) - mailman_log('debug', 'OutgoingRunner: Base Runner initialized') - - BounceMixin.__init__(self) - mailman_log('debug', 'OutgoingRunner: BounceMixin initialized') - - # Initialize processed messages tracking - self._processed_messages = set() - self._last_cleanup = time.time() - - # Initialize error tracking - self._error_count = 0 - self._last_error_time = 0 - - # We look this function up only at startup time - modname = 'Mailman.Handlers.' + mm_cfg.DELIVERY_MODULE - mailman_log('trace', 'OutgoingRunner: Attempting to import delivery module: %s', modname) - - try: - mod = __import__(modname) - mailman_log('trace', 'OutgoingRunner: Successfully imported delivery module') - except ImportError as e: - mailman_log('error', 'OutgoingRunner: Failed to import delivery module %s: %s', modname, str(e)) - mailman_log('error', 'OutgoingRunner: Traceback: %s', traceback.format_exc()) - self._release_pid_lock() - raise - - try: - self._func = getattr(sys.modules[modname], 'process') - mailman_log('trace', 'OutgoingRunner: Successfully got process function from module') - except AttributeError as e: - mailman_log('error', 'OutgoingRunner: Failed to get process function from module %s: %s', modname, str(e)) - mailman_log('error', 'OutgoingRunner: Traceback: %s', traceback.format_exc()) - self._release_pid_lock() - raise - - # This prevents smtp server connection problems from filling up the - # error log. It gets reset if the message was successfully sent, and - # set if there was a socket.error. - self.__logged = False - mailman_log('debug', 'OutgoingRunner: Initializing retry queue') - self.__retryq = Switchboard(mm_cfg.RETRYQUEUE_DIR) - self._running = True - mailman_log('debug', 'OutgoingRunner: Initialization complete') - except Exception as e: - mailman_log('error', 'OutgoingRunner: Initialization failed: %s', str(e)) - mailman_log('error', 'OutgoingRunner: Traceback: %s', traceback.format_exc()) - self._release_pid_lock() - raise - - def run(self): - """Run the outgoing queue runner.""" - mailman_log('debug', 'OutgoingRunner: Starting main loop') - self._running = True - - # Try to acquire the PID lock - if not self._acquire_pid_lock(): - mailman_log('error', 'OutgoingRunner: Failed to acquire PID lock, exiting') - return - - try: - while self._running: - try: - self._oneloop() - # Sleep for a bit to avoid CPU spinning - time.sleep(mm_cfg.QRUNNER_SLEEP_TIME) - except Exception as e: - mailman_log('error', 'OutgoingRunner: Error in main loop: %s', str(e)) - mailman_log('error', 'OutgoingRunner: Traceback:\n%s', traceback.format_exc()) - # Don't exit on error, just log and continue - time.sleep(mm_cfg.QRUNNER_SLEEP_TIME) - finally: - self._running = False - self._release_pid_lock() - mailman_log('debug', 'OutgoingRunner: Main loop ended') - - def stop(self): - """Stop the outgoing queue runner.""" - mailman_log('debug', 'OutgoingRunner: Stopping runner') - self._running = False - self._release_pid_lock() - Runner._cleanup(self) - mailman_log('debug', 'OutgoingRunner: Runner stopped') - - def _acquire_pid_lock(self): - """Try to acquire the PID lock file.""" - try: - self._pid_lock = open(self._pid_file, 'w') - fcntl.flock(self._pid_lock, fcntl.LOCK_EX | fcntl.LOCK_NB) - # Write our PID to the file - self._pid_lock.seek(0) - self._pid_lock.write(str(os.getpid())) - self._pid_lock.truncate() - self._pid_lock.flush() - mailman_log('debug', 'OutgoingRunner: Acquired PID lock file %s', self._pid_file) - return True - except IOError: - mailman_log('error', 'OutgoingRunner: Another instance is already running (PID file: %s)', self._pid_file) - if self._pid_lock: - self._pid_lock.close() - self._pid_lock = None - return False - - def _release_pid_lock(self): - """Release the PID lock file.""" - if self._pid_lock: - try: - fcntl.flock(self._pid_lock, fcntl.LOCK_UN) - self._pid_lock.close() - os.unlink(self._pid_file) - mailman_log('debug', 'OutgoingRunner: Released PID lock file %s', self._pid_file) - except (IOError, OSError) as e: - mailman_log('error', 'OutgoingRunner: Error releasing PID lock: %s', str(e)) - self._pid_lock = None - - def _unmark_message_processed(self, msgid): - """Remove a message from the processed messages set.""" - with self._processed_lock: - if msgid in self._processed_messages: - self._processed_messages.remove(msgid) - mailman_log('debug', 'OutgoingRunner: Unmarked message %s as processed', msgid) - - def _cleanup_old_messages(self): - """Clean up old message tracking data.""" - with self._processed_lock: - if len(self._processed_messages) > self._max_processed_messages: - mailman_log('debug', 'OutgoingRunner._cleanup_old_messages: Clearing processed messages set (size: %d)', - len(self._processed_messages)) - self._processed_messages.clear() - if len(self._retry_times) > self._max_retry_times: - mailman_log('debug', 'OutgoingRunner._cleanup_old_messages: Clearing retry times dict (size: %d)', - len(self._retry_times)) - self._retry_times.clear() - self._last_cleanup = time.time() - - def _cleanup_resources(self, msg, msgdata): - """Clean up any temporary resources.""" - try: - if msgdata and '_tempfile' in msgdata: - tempfile = msgdata['_tempfile'] - if os.path.exists(tempfile): - mailman_log('debug', 'OutgoingRunner._cleanup_resources: Removing temporary file %s', tempfile) - os.unlink(tempfile) - except Exception as e: - mailman_log('error', 'OutgoingRunner._cleanup_resources: Error cleaning up resources: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - - def _get_smtp_connection(self): - """Get a new SMTP connection with proper configuration.""" - try: - conn = smtplib.SMTP() - conn._host = mm_cfg.SMTPHOST # workaround https://github.com/python/cpython/issues/80275 - conn.set_debuglevel(mm_cfg.SMTPLIB_DEBUG_LEVEL) - conn.connect(mm_cfg.SMTPHOST, mm_cfg.SMTPPORT) - - if mm_cfg.SMTP_AUTH: - if mm_cfg.SMTP_USE_TLS: - try: - conn.starttls() - except smtplib.SMTPException as e: - mailman_log('error', 'SMTP TLS error: %s', str(e)) - conn.quit() - return None - try: - helo_host = mm_cfg.SMTP_HELO_HOST or socket.getfqdn() - conn.ehlo(helo_host) - except smtplib.SMTPException as e: - mailman_log('error', 'SMTP EHLO error: %s', str(e)) - conn.quit() - return None - try: - conn.login(mm_cfg.SMTP_USER, mm_cfg.SMTP_PASSWD) - except smtplib.SMTPHeloError as e: - mailman_log('error', 'SMTP HELO error: %s', str(e)) - conn.quit() - return None - except smtplib.SMTPAuthenticationError as e: - mailman_log('error', 'SMTP AUTH error: %s', str(e)) - conn.quit() - return None - - return conn - except Exception as e: - mailman_log('error', 'SMTP connection failed: %s', str(e)) - return None - - def _handle_smtp_error(self, e, mlist, msg, msgdata): - """Handle SMTP errors with appropriate recovery.""" - if isinstance(e, smtplib.SMTPServerDisconnected): - # Server disconnected, try to reconnect - return self._retry_with_new_connection(mlist, msg, msgdata) - elif isinstance(e, smtplib.SMTPRecipientsRefused): - # Recipient refused, queue bounce - self._queue_bounces(mlist, msg, msgdata, e.recipients) - return False - - def _retry_with_new_connection(self, mlist, msg, msgdata): - """Retry message delivery with a new SMTP connection.""" - try: - conn = self._get_smtp_connection() - if conn: - return self._func(mlist, msg, msgdata, conn) - except Exception as e: - mailman_log('error', 'Retry with new connection failed: %s', str(e)) - return False - - def _convert_message(self, msg): - """Convert email.message.Message to Mailman.Message with proper handling of nested messages.""" - return Runner._convert_message(self, msg) - - def _validate_message(self, msg, msgdata): - """Validate the message for outgoing delivery. - - Args: - msg: The message to validate - msgdata: Additional message metadata - - Returns: - tuple: (msg, success) where success is a boolean indicating if validation was successful - """ - try: - # Convert message if needed - if not isinstance(msg, Message.Message): - msg = self._convert_message(msg) - - # Check required headers - if not msg.get('message-id'): - mailman_log('error', 'OutgoingRunner._validate_message: Message missing Message-ID header') - return msg, False - - if not msg.get('from'): - mailman_log('error', 'OutgoingRunner._validate_message: Message missing From header') - return msg, False - - if not msg.get('to') and not msg.get('recipients'): - mailman_log('error', 'OutgoingRunner._validate_message: Message missing To/Recipients') - return msg, False - - return msg, True - - except Exception as e: - mailman_log('error', 'OutgoingRunner._validate_message: Error validating message: %s', str(e)) - return msg, False + Runner.__init__(self, slice, numslices) + BounceMixin.__init__(self) + # We look this function up only at startup time + modname = 'Mailman.Handlers.' + mm_cfg.DELIVERY_MODULE + mod = __import__(modname) + self._func = getattr(sys.modules[modname], 'process') + # This prevents smtp server connection problems from filling up the + # error log. It gets reset if the message was successfully sent, and + # set if there was a socket.error. + self.__logged = False + self.__retryq = Switchboard(mm_cfg.RETRYQUEUE_DIR) def _dispose(self, mlist, msg, msgdata): # See if we should retry delivery of this message again. @@ -348,7 +67,7 @@ def _dispose(self, mlist, msg, msgdata): self._func(mlist, msg, msgdata) # Failsafe -- a child may have leaked through. if pid != os.getpid(): - mailman_log('error', 'child process leaked thru: %s', mm_cfg.DELIVERY_MODULE) + syslog('error', 'child process leaked thru: %s', modname) os._exit(1) self.__logged = False except socket.error: @@ -360,8 +79,8 @@ def _dispose(self, mlist, msg, msgdata): port = 'smtp' # Log this just once. if not self.__logged: - mailman_log('error', 'Cannot connect to SMTP server %s on port %s', - mm_cfg.SMTPHOST, port) + syslog('error', 'Cannot connect to SMTP server %s on port %s', + mm_cfg.SMTPHOST, port) self.__logged = True self._snooze(0) return True @@ -409,287 +128,8 @@ def _dispose(self, mlist, msg, msgdata): # We've successfully completed handling of this message return False - def _process_bounce(self, mlist, msg, msgdata): - """Process a bounce message.""" - msgid = msg.get('message-id', 'n/a') - try: - mailman_log('debug', 'OutgoingRunner._process_bounce: Processing bounce message %s', msgid) - - # Get bounce information - recipient = msgdata.get('recipient', 'unknown') - bounce_info = msgdata.get('bounce_info', {}) - - mailman_log('debug', 'OutgoingRunner._process_bounce: Bounce for recipient %s, info: %s', - recipient, str(bounce_info)) - - # Process the bounce - # ... bounce processing logic ... - - mailman_log('debug', 'OutgoingRunner._process_bounce: Successfully processed bounce message %s', msgid) - return True - - except Exception as e: - mailman_log('error', 'OutgoingRunner._process_bounce: Error processing bounce message %s: %s\nTraceback:\n%s', - msgid, str(e), traceback.format_exc()) - return False - - def _process_admin(self, mlist, msg, msgdata): - """Process an admin message.""" - msgid = msg.get('message-id', 'n/a') - try: - mailman_log('debug', 'OutgoingRunner._process_admin: Processing admin message %s', msgid) - - # Get admin information - recipient = msgdata.get('recipient', 'unknown') - admin_type = msgdata.get('admin_type', 'unknown') - - mailman_log('debug', 'OutgoingRunner._process_admin: Admin message for %s, type: %s', - recipient, admin_type) - - # Process the admin message - Replybot = get_replybot() - Replybot.process(mlist, msg, msgdata) - - mailman_log('debug', 'OutgoingRunner._process_admin: Successfully processed admin message %s', msgid) - return True - - except Exception as e: - mailman_log('error', 'OutgoingRunner._process_admin: Error processing admin message %s: %s\nTraceback:\n%s', - msgid, str(e), traceback.format_exc()) - return False - - def _process_regular(self, mlist, msg, msgdata): - """Process a regular outgoing message.""" - msgid = msg.get('message-id', 'n/a') - - try: - # Get recipient from msgdata or message headers - recipient = msgdata.get('recipient') - if not recipient: - # Try to get recipient from To header - to = msg.get('to') - if to: - # Parse the To header to get the first recipient - addrs = email.utils.getaddresses([to]) - if addrs: - recipient = addrs[0][1] - - if not recipient: - mailman_log('error', 'OutgoingRunner: No recipients found in msgdata for message: %s', msgid) - return self._handle_error(ValueError('No recipients found'), msg, mlist) - - # Set the recipient in msgdata for future use - msgdata['recipient'] = recipient - - # For system messages (_nolist=1), we need to handle them differently - if msgdata.get('_nolist'): - mailman_log('debug', 'OutgoingRunner._process_regular: Processing system message %s', msgid) - # System messages should be sent directly via SMTP - try: - conn = self._get_smtp_connection() - if not conn: - mailman_log('error', 'OutgoingRunner._process_regular: Failed to get SMTP connection for message %s', msgid) - return self._handle_error(ConnectionError('Failed to get SMTP connection'), msg, mlist) - - # Send the message - sender = msg.get('from', msgdata.get('original_sender', mm_cfg.MAILMAN_SITE_LIST)) - if not sender or not '@' in sender: - sender = mm_cfg.MAILMAN_SITE_LIST - - mailman_log('debug', 'OutgoingRunner._process_regular: Sending system message %s from %s to %s', - msgid, sender, recipient) - - conn.sendmail(sender, [recipient], str(msg)) - conn.quit() - - mailman_log('debug', 'OutgoingRunner._process_regular: Successfully sent system message %s', msgid) - return True - - except Exception as e: - mailman_log('error', 'OutgoingRunner._process_regular: SMTP error for system message %s: %s', - msgid, str(e)) - return self._handle_error(e, msg, mlist) - - # For regular list messages, use the delivery module - mailman_log('debug', 'OutgoingRunner._process_regular: Using delivery module for message %s', msgid) - - # Log the state before calling the delivery module - mailman_log('debug', 'OutgoingRunner._process_regular: Pre-delivery msgdata:\n%s', str(msgdata)) - - # Ensure we have the list members if this is a list message - if msgdata.get('tolist') and not msgdata.get('_nolist'): - try: - # Get all list members - members = mlist.getRegularMemberKeys() - if members: - msgdata['recips'] = [mlist.getMemberCPAddress(m) for m in members - if mlist.getDeliveryStatus(m) == ENABLED] - mailman_log('debug', 'OutgoingRunner._process_regular: Expanded list members for message %s: %s', - msgid, str(msgdata['recips'])) - else: - mailman_log('error', 'OutgoingRunner._process_regular: No members found for list %s', - mlist.internal_name()) - return self._handle_error(ValueError('No list members found'), msg, mlist) - except Exception as e: - mailman_log('error', 'OutgoingRunner._process_regular: Error getting list members: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - # Try to continue with existing recipients if any - if not msgdata.get('recips'): - mailman_log('error', 'OutgoingRunner._process_regular: No recipients available for message %s', msgid) - return self._handle_error(ValueError('No recipients available'), msg, mlist) - - # Call the delivery module - try: - self._func(mlist, msg, msgdata) - # Log the state after calling the delivery module - mailman_log('debug', 'OutgoingRunner._process_regular: Post-delivery msgdata:\n%s', str(msgdata)) - mailman_log('debug', 'OutgoingRunner._process_regular: Successfully processed regular message %s', msgid) - return True - except Exception as e: - mailman_log('error', 'OutgoingRunner._process_regular: Error in delivery module: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - return self._handle_error(e, msg, mlist) - - except Exception as e: - mailman_log('error', 'OutgoingRunner._process_regular: Unexpected error: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - return self._handle_error(e, msg, mlist) - - def _check_retry_delay(self, msgid, filebase): - """Check if enough time has passed since the last retry attempt.""" - now = time.time() - last_retry = self._retry_times.get(msgid, 0) - - if now - last_retry < self.MIN_RETRY_DELAY: - mailman_log('debug', 'OutgoingRunner._check_retry_delay: Message %s (file: %s) retry delay not met. Last retry: %s, Now: %s, Delay needed: %s', - msgid, filebase, time.ctime(last_retry), time.ctime(now), self.MIN_RETRY_DELAY) - return False - - mailman_log('debug', 'OutgoingRunner._check_retry_delay: Message %s (file: %s) retry delay met. Last retry: %s, Now: %s', - msgid, filebase, time.ctime(last_retry), time.ctime(now)) - return True - - def _queue_bounces(self, mlist, msg, msgdata, failures): - """Queue bounce messages for failed deliveries.""" - msgid = msg.get('message-id', 'n/a') - try: - for recip, code, errmsg in failures: - if not self._validate_bounce(recip, code, errmsg): - continue - mailman_log('error', 'OutgoingRunner: Delivery failure for msgid: %s - Recipient: %s, Code: %s, Error: %s', - msgid, recip, code, errmsg) - BounceMixin._queue_bounce(self, mlist, msg, recip, code, errmsg) - except Exception as e: - mailman_log('error', 'OutgoingRunner: Error queueing bounce for msgid: %s - %s', msgid, str(e)) - mailman_log('error', 'OutgoingRunner: Traceback: %s', traceback.format_exc()) - - def _validate_bounce(self, recip, code, errmsg): - """Validate bounce message data.""" - try: - if not recip or not isinstance(recip, str): - return False - if not code or not isinstance(code, (int, str)): - return False - if not errmsg or not isinstance(errmsg, str): - return False - return True - except Exception: - return False - - def _cleanup(self): - """Clean up the outgoing queue runner.""" - mailman_log('debug', 'OutgoingRunner: Starting cleanup') - try: - # Log total messages processed - with self._total_messages_lock: - mailman_log('debug', 'OutgoingRunner: Total messages processed: %d', self._total_messages_processed) - - # Call parent class cleanup - Runner._cleanup(self) - - # Release PID lock if we have it - self._release_pid_lock() - - mailman_log('debug', 'OutgoingRunner: Cleanup complete') - except Exception as e: - mailman_log('error', 'OutgoingRunner: Error during cleanup: %s', str(e)) - mailman_log('error', 'OutgoingRunner: Traceback:\n%s', traceback.format_exc()) - raise - _doperiodic = BounceMixin._doperiodic - def _oneloop(self): - """Process one batch of messages from the queue.""" - # Get all files in the queue - files = self._switchboard.files() - if not files: - return 0 - - # Process each file - for filebase in files: - try: - # Try to get the file from the switchboard - msg, msgdata = self._switchboard.dequeue(filebase) - except Exception as e: - mailman_log('error', 'OutgoingRunner: Error dequeuing %s: %s', filebase, str(e)) - mailman_log('error', 'OutgoingRunner: Traceback:\n%s', traceback.format_exc()) - continue - - if msg is None: - mailman_log('debug', 'OutgoingRunner: No message data for %s', filebase) - continue - - try: - # Process the message - self._dispose(msg, msgdata) - with self._total_messages_lock: - self._total_messages_processed += 1 - mailman_log('debug', 'OutgoingRunner: Successfully processed message %s', filebase) - except Exception as e: - mailman_log('error', 'OutgoingRunner: Error processing %s: %s', filebase, str(e)) - mailman_log('error', 'OutgoingRunner: Traceback:\n%s', traceback.format_exc()) - self._handle_error(e, msg, None) - - def _handle_error(self, exc, msg=None, mlist=None, preserve=True): - """Enhanced error handling with circuit breaker and detailed logging.""" - now = time.time() - msgid = msg.get('message-id', 'n/a') if msg else 'n/a' - - # Log the error with full context - mailman_log('error', 'OutgoingRunner: Error processing message %s: %s', msgid, str(exc)) - mailman_log('error', 'OutgoingRunner: Error type: %s', type(exc).__name__) - - # Log full traceback - s = StringIO() - traceback.print_exc(file=s) - mailman_log('error', 'OutgoingRunner: Traceback:\n%s', s.getvalue()) - - # Log system state - mailman_log('error', 'OutgoingRunner: System state - SMTP host: %s, port: %s, auth: %s', - mm_cfg.SMTPHOST, mm_cfg.SMTPPORT, mm_cfg.SMTP_AUTH) - - # Circuit breaker logic - if now - self._last_error_time < self._error_window: - self._error_count += 1 - if self._error_count >= self._max_errors: - mailman_log('error', 'OutgoingRunner: Too many errors (%d) in %d seconds, stopping runner', - self._error_count, self._error_window) - # Log stack trace before stopping - s = StringIO() - traceback.print_stack(file=s) - mailman_log('error', 'OutgoingRunner: Stack trace at stop:\n%s', s.getvalue()) - self.stop() - else: - self._error_count = 1 - self._last_error_time = now - - # Handle message preservation - if preserve and msg: - try: - msgdata = {'whichq': self._switchboard.whichq()} - new_filebase = self._shunt.enqueue(msg, msgdata) - mailman_log('error', 'OutgoingRunner: Shunted message to: %s', new_filebase) - except Exception as e: - mailman_log('error', 'OutgoingRunner: Failed to shunt message: %s', str(e)) - return False - return True + def _cleanup(self): + BounceMixin._cleanup(self) + Runner._cleanup(self) diff --git a/Mailman/Queue/RetryRunner.py b/Mailman/Queue/RetryRunner.py index fa20c1b6..4ed129b7 100644 --- a/Mailman/Queue/RetryRunner.py +++ b/Mailman/Queue/RetryRunner.py @@ -1,4 +1,4 @@ -# Copyright (C) 1998-2018 by the Free Software Foundation, Inc. +# Copyright (C) 2003-2018 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 @@ -12,294 +12,34 @@ # # 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. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -"""Retry queue runner. - -This module is responsible for retrying failed message deliveries. It's a -separate queue from the virgin queue because retries need different handling. -""" - -from builtins import object import time -import traceback -import os -import sys -import threading -import email.message from Mailman import mm_cfg -from Mailman import Errors from Mailman.Queue.Runner import Runner from Mailman.Queue.Switchboard import Switchboard -from Mailman.Errors import MMUnknownListError -from Mailman.Logging.Syslog import mailman_log -import Mailman.MailList as MailList -import Mailman.Message as Message + class RetryRunner(Runner): QDIR = mm_cfg.RETRYQUEUE_DIR SLEEPTIME = mm_cfg.minutes(15) - - # Message tracking configuration - _track_messages = True - _max_processed_messages = 10000 - _max_retry_times = 10000 - _processed_messages = set() - _processed_lock = threading.Lock() - _last_cleanup = time.time() - _cleanup_interval = 3600 # Clean up every hour - - # Retry configuration - MIN_RETRY_DELAY = 300 # 5 minutes minimum delay between retries - MAX_RETRIES = 5 # Maximum number of retry attempts - _retry_times = {} # Track last retry time for each message def __init__(self, slice=None, numslices=1): - mailman_log('debug', 'RetryRunner: Starting initialization') - try: - Runner.__init__(self, slice, numslices) - self._outq = Switchboard(mm_cfg.OUTQUEUE_DIR) - - # Initialize processed messages tracking - self._processed_messages = set() - self._last_cleanup = time.time() - - mailman_log('debug', 'RetryRunner: Initialization complete') - except Exception as e: - mailman_log('error', 'RetryRunner: Initialization failed: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - raise - - def _check_retry_delay(self, msgid, filebase): - """Check if enough time has passed since the last retry attempt.""" - now = time.time() - last_retry = self._retry_times.get(msgid, 0) - - if now - last_retry < self.MIN_RETRY_DELAY: - mailman_log('debug', 'RetryRunner._check_retry_delay: Message %s (file: %s) retry delay not met. Last retry: %s, Now: %s, Delay needed: %s', - msgid, filebase, time.ctime(last_retry), time.ctime(now), self.MIN_RETRY_DELAY) - return False - - mailman_log('debug', 'RetryRunner._check_retry_delay: Message %s (file: %s) retry delay met. Last retry: %s, Now: %s', - msgid, filebase, time.ctime(last_retry), time.ctime(now)) - return True - - def _validate_message(self, msg, msgdata): - """Validate message format and required fields.""" - msgid = msg.get('message-id', 'n/a') - try: - # Check message size - if len(str(msg)) > mm_cfg.MAX_MESSAGE_SIZE: - mailman_log('error', 'RetryRunner: Message too large: %d bytes', len(str(msg))) - return msg, False - - # Validate required headers - if not msg.get('message-id'): - mailman_log('error', 'RetryRunner: Message missing Message-ID header') - return msg, False - - if not msg.get('from'): - mailman_log('error', 'RetryRunner: Message missing From header') - return msg, False - - if not msg.get('to') and not msg.get('recipients'): - mailman_log('error', 'RetryRunner: Message missing To/Recipients') - return msg, False - - mailman_log('debug', 'RetryRunner: Message %s validation successful', msgid) - return msg, True - - except Exception as e: - mailman_log('error', 'RetryRunner: Error validating message %s: %s', msgid, str(e)) - mailman_log('error', 'RetryRunner: Traceback:\n%s', traceback.format_exc()) - return msg, False - - def _unmark_message_processed(self, msgid): - """Remove a message from the processed messages set.""" - with self._processed_lock: - if msgid in self._processed_messages: - self._processed_messages.remove(msgid) - mailman_log('debug', 'RetryRunner: Unmarked message %s as processed', msgid) - - def _cleanup_old_messages(self): - """Clean up old message tracking data.""" - with self._processed_lock: - if len(self._processed_messages) > self._max_processed_messages: - mailman_log('debug', 'RetryRunner._cleanup_old_messages: Clearing processed messages set (size: %d)', - len(self._processed_messages)) - self._processed_messages.clear() - if len(self._retry_times) > self._max_retry_times: - mailman_log('debug', 'RetryRunner._cleanup_old_messages: Clearing retry times dict (size: %d)', - len(self._retry_times)) - self._retry_times.clear() - self._last_cleanup = time.time() + Runner.__init__(self, slice, numslices) + self.__outq = Switchboard(mm_cfg.OUTQUEUE_DIR) def _dispose(self, mlist, msg, msgdata): - # See if we should retry delivery of this message again. + # Move it to the out queue for another retry if it's time. deliver_after = msgdata.get('deliver_after', 0) if time.time() < deliver_after: return True - # Move the message to the outgoing queue for another attempt at - # delivery. - self._outq.enqueue(msg, msgdata) + self.__outq.enqueue(msg, msgdata) return False - def _process_retry(self, mlist, msg, msgdata): - """Process a retry message.""" - msgid = msg.get('message-id', 'n/a') - try: - mailman_log('debug', 'RetryRunner._process_retry: Processing retry for message %s', msgid) - - # Get retry information - retry_count = msgdata.get('retry_count', 0) - retry_delay = msgdata.get('retry_delay', mm_cfg.RETRY_DELAY) - - # Calculate next retry time - next_retry = time.time() + retry_delay - msgdata['next_retry'] = next_retry - msgdata['retry_count'] = retry_count + 1 - - mailman_log('debug', 'RetryRunner._process_retry: Updated retry info for message %s - count: %d, next retry: %s', - msgid, retry_count + 1, time.ctime(next_retry)) - - # Process the message - # ... retry processing logic ... - - mailman_log('debug', 'RetryRunner._process_retry: Successfully processed retry for message %s', msgid) - return True - - except Exception as e: - mailman_log('error', 'RetryRunner._process_retry: Error processing retry for message %s: %s\nTraceback:\n%s', - msgid, str(e), traceback.format_exc()) - return False - - def _handle_max_retries_exceeded(self, mlist, msg, msgdata): - """Handle case when maximum retries are exceeded.""" - msgid = msg.get('message-id', 'n/a') - try: - mailman_log('error', 'RetryRunner._handle_max_retries_exceeded: Maximum retries exceeded for message %s', msgid) - - # Move to shunt queue - self._shunt.enqueue(msg, msgdata) - mailman_log('debug', 'RetryRunner._handle_max_retries_exceeded: Moved message %s to shunt queue', msgid) - - # Notify list owners if configured - if mlist.bounce_notify_owner_on_disable: - mailman_log('debug', 'RetryRunner._handle_max_retries_exceeded: Notifying list owners for message %s', msgid) - self._notify_list_owners(mlist, msg, msgdata) - - except Exception as e: - mailman_log('error', 'RetryRunner._handle_max_retries_exceeded: Error handling max retries for message %s: %s\nTraceback:\n%s', - msgid, str(e), traceback.format_exc()) - - def _notify_list_owners(self, mlist, msg, msgdata): - """Notify list owners about failed retries.""" - msgid = msg.get('message-id', 'n/a') - try: - mailman_log('debug', 'RetryRunner._notify_list_owners: Sending notification for message %s', msgid) - - # Create notification message - subject = _('Maximum retries exceeded for message') - text = _("""\ -The following message has exceeded the maximum number of retry attempts: - -Message-ID: %(msgid)s -From: %(from)s -To: %(to)s -Subject: %(subject)s - -The message has been moved to the shunt queue. -""") % { - 'msgid': msgid, - 'from': msg.get('from', 'unknown'), - 'to': msg.get('to', 'unknown'), - 'subject': msg.get('subject', 'unknown') - } - - # Send notification - # ... notification sending logic ... - - mailman_log('debug', 'RetryRunner._notify_list_owners: Successfully sent notification for message %s', msgid) - - except Exception as e: - mailman_log('error', 'RetryRunner._notify_list_owners: Error sending notification for message %s: %s\nTraceback:\n%s', - msgid, str(e), traceback.format_exc()) - - def _cleanup(self): - """Clean up resources.""" - mailman_log('debug', 'RetryRunner: Starting cleanup') - try: - Runner._cleanup(self) - except Exception as e: - mailman_log('error', 'RetryRunner: Cleanup failed: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - mailman_log('debug', 'RetryRunner: Cleanup complete') - - def _oneloop(self): - """Process one batch of messages from the retry queue.""" - try: - # Get the list of files to process - files = self._switchboard.files() - filecnt = len(files) - - # Process each file - for filebase in files: - try: - # Check if the file exists before dequeuing - pckfile = os.path.join(self.QDIR, filebase + '.pck') - if not os.path.exists(pckfile): - mailman_log('error', 'RetryRunner._oneloop: File %s does not exist, skipping', pckfile) - continue - - # Check if file is locked - lockfile = os.path.join(self.QDIR, filebase + '.pck.lock') - if os.path.exists(lockfile): - mailman_log('debug', 'RetryRunner._oneloop: File %s is locked by another process, skipping', filebase) - continue - - # Dequeue the file - msg, msgdata = self._switchboard.dequeue(filebase) - if msg is None: - continue - - # Get the list name from the message data - listname = msgdata.get('listname') - if not listname: - syslog('error', 'RetryRunner._oneloop: No listname in message data for file %s', filebase) - self._shunt.enqueue(msg, msgdata) - continue - - # Open the list - try: - mlist = self._open_list(listname) - except Exception as e: - self.log_error('list_open_error', str(e), listname=listname) - self._shunt.enqueue(msg, msgdata) - continue - - # Process the message - try: - result = self._dispose(mlist, msg, msgdata) - if result: - self._switchboard.enqueue(msg, msgdata) - except Exception as e: - self._handle_error(e, msg=msg, mlist=mlist) - - except Exception as e: - syslog('error', 'RetryRunner._oneloop: Error dequeuing file %s: %s', filebase, str(e)) - continue - - except Exception as e: - syslog('error', 'RetryRunner._oneloop: Error in main loop: %s', str(e)) - return 0 - - return filecnt - def _snooze(self, filecnt): - # We always want to snooze, but check for stop flag periodically - for _ in range(self.SLEEPTIME): + # We always want to snooze. Sleep in 1 second iterations to ensure that the sigterm handler can respond promptly and set _stop. + for sec in range(1, self.SLEEPTIME): if self._stop: - return + break time.sleep(1) diff --git a/Mailman/Queue/Runner.py b/Mailman/Queue/Runner.py index eb1f98f3..9c35f939 100644 --- a/Mailman/Queue/Runner.py +++ b/Mailman/Queue/Runner.py @@ -22,76 +22,36 @@ import time import traceback from io import StringIO -from functools import wraps -import threading -import os from Mailman import mm_cfg +# Debug: Log when mm_cfg is imported +from Mailman.Logging.Syslog import syslog +syslog('debug', 'Runner.py: mm_cfg imported from %s', mm_cfg.__file__) +syslog('debug', 'Runner.py: mm_cfg.GLOBAL_PIPELINE type: %s', type(mm_cfg.GLOBAL_PIPELINE).__name__ if hasattr(mm_cfg, 'GLOBAL_PIPELINE') else 'NOT FOUND') from Mailman import Utils from Mailman import Errors -import Mailman.MailList as MailList +from Mailman import MailList from Mailman import i18n -import Mailman.Message as Message + from Mailman.Logging.Syslog import syslog from Mailman.Queue.Switchboard import Switchboard import email.errors - + class Runner: QDIR = None SLEEPTIME = mm_cfg.QRUNNER_SLEEP_TIME - MIN_RETRY_DELAY = 300 # 5 minutes minimum delay between retries - MAX_BACKOFF = 60 # Maximum backoff time in seconds - INITIAL_BACKOFF = 1 # Initial backoff time in seconds - - # Message tracking configuration - can be overridden by subclasses - _track_messages = False # Whether to track processed messages - _max_processed_messages = 10000 # Maximum number of messages to track - _max_retry_times = 10000 # Maximum number of retry times to track - _processed_messages = set() # Set of processed message IDs - _processed_lock = threading.Lock() # Lock for thread safety - _retry_times = {} # Dictionary of retry times - _last_cleanup = time.time() # Last cleanup time - _cleanup_interval = 3600 # Cleanup interval in seconds - _current_backoff = INITIAL_BACKOFF # Current backoff time in seconds - _last_mtime = 0 # Last directory modification time def __init__(self, slice=None, numslices=1): - syslog('debug', '%s: Starting initialization', self.__class__.__name__) - try: - self._stop = 0 - self._slice = slice - self._numslices = numslices - self._kids = {} - # Create our own switchboard. Don't use the switchboard cache because - # we want to provide slice and numslice arguments. - self._switchboard = Switchboard(self.QDIR, slice, numslices, True) - # Create the shunt switchboard - self._shunt = Switchboard(mm_cfg.SHUNTQUEUE_DIR) - - # Initialize message tracking attributes - self._track_messages = self.__class__._track_messages - self._max_processed_messages = self.__class__._max_processed_messages - self._max_retry_times = self.__class__._max_retry_times - self._processed_messages = set() - self._processed_lock = threading.Lock() - self._retry_times = {} - self._last_cleanup = time.time() - self._cleanup_interval = 3600 - - # Initialize error tracking attributes - self._last_error_time = 0 - self._error_count = 0 - - self._current_backoff = self.INITIAL_BACKOFF - self._last_mtime = 0 - - syslog('debug', '%s: Initialization complete', self.__class__.__name__) - except Exception as e: - syslog('error', '%s: Initialization failed: %s\nTraceback:\n%s', - self.__class__.__name__, str(e), traceback.format_exc()) - raise + self._kids = {} + # Create our own switchboard. Don't use the switchboard cache because + # we want to provide slice and numslice arguments. + distribution = getattr(mm_cfg, 'QUEUE_DISTRIBUTION_METHOD', 'hash') + self._switchboard = Switchboard(self.QDIR, slice, numslices, True, distribution) + # Create the shunt switchboard + self._shunt = Switchboard(mm_cfg.SHUNTQUEUE_DIR) + self._stop = False def __repr__(self): return '<%s at %s>' % (self.__class__.__name__, id(self)) @@ -125,130 +85,36 @@ def run(self): # subprocesses we've created and do any other necessary cleanups. self._cleanup() - def log_error(self, error_type, error_msg, **kwargs): - """Log an error with the given type and message. - - Args: - error_type: A string identifying the type of error - error_msg: The error message to log - **kwargs: Additional context to include in the log message - """ - context = { - 'runner': self.__class__.__name__, - 'error_type': error_type, - 'error_msg': error_msg, - } - context.update(kwargs) - - # Format the error message - msg_parts = ['%s: %s' % (error_type, error_msg)] - if 'msg' in context: - msg_parts.append('Message-ID: %s' % context['msg'].get('message-id', 'unknown')) - if 'listname' in context: - msg_parts.append('List: %s' % context['listname']) - if 'traceback' in context: - msg_parts.append('Traceback:\n%s' % context['traceback']) - - # Log the error - syslog('error', ' '.join(msg_parts)) - - def log_warning(self, warning_type, msg=None, mlist=None, **context): - """Structured warning logging with context.""" - context.update({ - 'runner': self.__class__.__name__, - 'list': mlist.internal_name() if mlist else 'N/A', - 'msg_id': msg.get('message-id', 'N/A') if msg else 'N/A', - 'warning_type': warning_type - }) - syslog('warning', '%(runner)s: %(warning_type)s - list: %(list)s, msg: %(msg_id)s', - context) - - def log_info(self, info_type, msg=None, mlist=None, **context): - """Structured info logging with context.""" - context.update({ - 'runner': self.__class__.__name__, - 'list': mlist.internal_name() if mlist else 'N/A', - 'msg_id': msg.get('message-id', 'N/A') if msg else 'N/A', - 'info_type': info_type - }) - syslog('info', '%(runner)s: %(info_type)s - list: %(list)s, msg: %(msg_id)s', - context) - - def _handle_error(self, exc, msg=None, mlist=None, preserve=True): - """Centralized error handling with circuit breaker.""" - now = time.time() - - # Log the error with full context - self.log_error('unhandled_exception', exc, msg=msg, mlist=mlist) - - # Log full traceback - s = StringIO() - traceback.print_exc(file=s) - syslog('error', 'Traceback: %s', s.getvalue()) - - # Circuit breaker logic - if now - self._last_error_time < 60: # Within last minute - self._error_count += 1 - if self._error_count >= 10: # Too many errors in short time - syslog('error', '%s: Too many errors, stopping runner', self.__class__.__name__) - # Log stack trace before stopping - s = StringIO() - traceback.print_stack(file=s) - syslog('error', 'Stack trace at stop:\n%s', s.getvalue()) - self.stop() - else: - self._error_count = 1 - self._last_error_time = now - - # Handle message preservation - if preserve: - try: - msgdata = {'whichq': self._switchboard.whichq()} - new_filebase = self._shunt.enqueue(msg, msgdata) - syslog('error', '%s: Shunted message to: %s', self.__class__.__name__, new_filebase) - except Exception as e: - syslog('error', '%s: Failed to shunt message: %s', self.__class__.__name__, str(e)) - return False - return True - def _oneloop(self): - """Run one iteration of the runner's main loop. - - Returns: - int: Number of files processed, or 0 if no files found - """ - # Check if directory has been modified since last check - try: - st = os.stat(self.QDIR) - current_mtime = st.st_mtime - if current_mtime <= self._last_mtime: - # Directory hasn't changed, use backoff - self._snooze(self._current_backoff) - # Double the backoff time, up to MAX_BACKOFF - self._current_backoff = min(self._current_backoff * 2, self.MAX_BACKOFF) - return 0 - # Directory has changed, reset backoff - self._current_backoff = self.INITIAL_BACKOFF - self._last_mtime = current_mtime - except OSError as e: - syslog('error', '%s: Error checking directory %s: %s', - self.__class__.__name__, self.QDIR, str(e)) - return 0 - - # Process files in the directory + # First, list all the files in our queue directory. + # Switchboard.files() is guaranteed to hand us the files in FIFO + # order. Return an integer count of the number of files that were + # available for this qrunner to process. files = self._switchboard.files() - if not files: - syslog('debug', '%s: No files to process', self.__class__.__name__) - return 0 - - # Process each file for filebase in files: - if self._stop: - break try: # Ask the switchboard for the message and metadata objects # associated with this filebase. msg, msgdata = self._switchboard.dequeue(filebase) + except Exception as e: + # This used to just catch email.Errors.MessageParseError, + # but other problems can occur in message parsing, e.g. + # ValueError, and exceptions can occur in unpickling too. + # We don't want the runner to die, so we just log and skip + # this entry, but maybe preserve it for analysis. + self._log(e) + if mm_cfg.QRUNNER_SAVE_BAD_MESSAGES: + syslog('error', + 'Skipping and preserving unparseable message: %s', + filebase) + preserve = True + else: + syslog('error', + 'Ignoring unparseable message: %s', filebase) + preserve = False + self._switchboard.finish(filebase, preserve=preserve) + continue + try: self._onefile(msg, msgdata) self._switchboard.finish(filebase) except Exception as e: @@ -284,194 +150,82 @@ def _oneloop(self): break return len(files) - def _convert_message(self, msg): - """Convert email.message.Message to Mailman.Message with proper handling of nested messages. - - Args: - msg: The message to convert - - Returns: - Mailman.Message: The converted message - """ - if isinstance(msg, email.message.Message): - mailman_msg = Message.Message() - # Copy all attributes from the original message - for key, value in msg.items(): - mailman_msg[key] = value - # Copy the payload - if msg.is_multipart(): - for part in msg.get_payload(): - mailman_msg.attach(self._convert_message(part)) - else: - mailman_msg.set_payload(msg.get_payload()) - return mailman_msg - return msg - - def _validate_message(self, msg, msgdata): - """Validate and convert message if needed. - - Returns a tuple of (msg, success) where success is a boolean indicating - if validation was successful. - """ - msgid = msg.get('message-id', 'n/a') - try: - # Convert message if needed - if not isinstance(msg, Message.Message): - # Only log conversion if it's a significant event - if msg.is_multipart() or len(msg.get_payload()) > 1000: - syslog('debug', 'Runner._validate_message: Converting complex message %s to Mailman.Message', msgid) - msg = self._convert_message(msg) - - # Validate required Mailman.Message methods - required_methods = ['get_sender', 'get', 'items', 'is_multipart', 'get_payload'] - missing_methods = [] - for method in required_methods: - if not hasattr(msg, method): - missing_methods.append(method) - - if missing_methods: - syslog('error', 'Runner._validate_message: Message %s missing required methods: %s', - msgid, ', '.join(missing_methods)) - return msg, False - - # Validate message headers - if not msg.get('message-id'): - syslog('error', 'Runner._validate_message: Message %s missing Message-ID header', msgid) - return msg, False - - if not msg.get('from'): - syslog('error', 'Runner._validate_message: Message %s missing From header', msgid) - return msg, False - - if not msg.get('to') and not msg.get('recipients'): - syslog('error', 'Runner._validate_message: Message %s missing To/Recipients', msgid) - return msg, False - - # Only log successful validation for complex messages - if msg.is_multipart() or len(msg.get_payload()) > 1000: - syslog('debug', 'Runner._validate_message: Complex message %s validation successful', msgid) - return msg, True - - except Exception as e: - syslog('error', 'Runner._validate_message: Error validating message %s: %s\nTraceback:\n%s', - msgid, str(e), traceback.format_exc()) - return msg, False - - def _onefile(self, mlist, msg, msgdata): - """Process a single file from the queue.""" + def _onefile(self, msg, msgdata): + # Do some common sanity checking on the message metadata. It's got to + # be destined for a particular mailing list. This switchboard is used + # to shunt off badly formatted messages. We don't want to just trash + # them because they may be fixable with human intervention. Just get + # them out of our site though. + # + # Find out which mailing list this message is destined for. + listname = msgdata.get('listname') + if not listname: + listname = mm_cfg.MAILMAN_SITE_LIST + mlist = self._open_list(listname) + if not mlist: + syslog('error', + 'Dequeuing message destined for missing list: %s', + listname) + self._shunt.enqueue(msg, msgdata) + return + # Now process this message, keeping track of any subprocesses that may + # have been spawned. We'll reap those later. + # + # We also want to set up the language context for this message. The + # context will be the preferred language for the user if a member of + # the list, or the list's preferred language. However, we must take + # special care to reset the defaults, otherwise subsequent messages + # may be translated incorrectly. BAW: I'm not sure I like this + # approach, but I can't think of anything better right now. + otranslation = i18n.get_translation() + sender = msg.get_sender() + if mlist: + lang = mlist.getMemberLanguage(sender) + else: + lang = mm_cfg.DEFAULT_SERVER_LANGUAGE + i18n.set_language(lang) + msgdata['lang'] = lang try: - # Get the list name from the message data - listname = msgdata.get('listname') - if not listname: - syslog('error', 'Runner._onefile: No listname in message data') - self._handle_error(ValueError('No listname in message data'), msg=msg, mlist=None) - return False - - # Open the list - try: - mlist = self._open_list(listname) - except Exception as e: - self._handle_error(e, msg=msg, mlist=None) - return False - - # Process the message - try: - result = self._dispose(mlist, msg, msgdata) - if result: - # If _dispose returns True, requeue the message - self._switchboard.enqueue(msg, msgdata) - # Only log significant events - if msg.is_multipart() or len(msg.get_payload()) > 1000: - syslog('debug', 'Runner._onefile: Complex message requeued for %s', listname) - else: - # If _dispose returns False, finish processing and remove the file - self._switchboard.finish(msgdata.get('filebase', '')) - # Only log significant events - if msg.is_multipart() or len(msg.get_payload()) > 1000: - syslog('debug', 'Runner._onefile: Complex message processing completed for %s', listname) - return result - except Exception as e: - self._handle_error(e, msg=msg, mlist=mlist) - return False - finally: - if mlist: - mlist.Unlock() - - except Exception as e: - self._handle_error(e, msg=msg, mlist=None) - return False + keepqueued = self._dispose(mlist, msg, msgdata) + finally: + i18n.set_translation(otranslation) + # Keep tabs on any child processes that got spawned. + kids = msgdata.get('_kids') + if kids: + self._kids.update(kids) + if keepqueued: + self._switchboard.enqueue(msg, msgdata) def _open_list(self, listname): + # We no longer cache the list instances. Because of changes to + # MailList.py needed to avoid not reloading an updated list, caching + # is not as effective as it once was. Also, with OldStyleMemberships + # as the MemberAdaptor, there was a self-reference to the list which + # kept all lists in the cache. Changing this reference to a + # weakref.proxy created other issues. try: - import Mailman.MailList as MailList mlist = MailList.MailList(listname, lock=False) except Errors.MMListError as e: - self.log_error('list_open_error', e, listname=listname) + syslog('error', 'error opening list: %s\n%s', listname, e) return None return mlist - def _doperiodic(self): - """Do some processing `every once in a while'. - - Called every once in a while both from the Runner's main loop, and - from the Runner's hash slice processing loop. You can do whatever - special periodic processing you want here, and the return value is - irrelevant. - """ - pass - - def _snooze(self, filecnt): - """Sleep for a while, but check for stop flag periodically. - - Implements exponential backoff when no files are found to process. - - Args: - filecnt: Number of files processed in the last iteration - """ - if filecnt > 0: - # Reset backoff when files are found - self._current_backoff = self.INITIAL_BACKOFF - # Only log if we're sleeping for more than 5 seconds - if self.SLEEPTIME > 5: - syslog('debug', '%s: Sleeping for %d seconds after processing %d files in this iteration', - self.__class__.__name__, self.SLEEPTIME, filecnt) - sleep_time = self.SLEEPTIME - else: - # No files found, use exponential backoff - sleep_time = min(self._current_backoff, self.MAX_BACKOFF) - syslog('debug', '%s: No files to process, sleeping for %d seconds', - self.__class__.__name__, sleep_time) - # Double the backoff time for next iteration, up to MAX_BACKOFF - self._current_backoff = min(self._current_backoff * 2, self.MAX_BACKOFF) - - endtime = time.time() + sleep_time - while time.time() < endtime and not self._stop: - time.sleep(0.1) - - def _shortcircuit(self): - """Return a true value if the individual file processing loop should - exit before it's finished processing each message in the current slice - of hash space. A false value tells _oneloop() to continue processing - until the current snapshot of hash space is exhausted. - - You could, for example, implement a throttling algorithm here. - """ - return self._stop + def _log(self, exc): + syslog('error', 'Uncaught runner exception: %s', exc) + s = StringIO() + traceback.print_exc(file=s) + syslog('error', s.getvalue()) # # Subclasses can override these methods. # def _cleanup(self): - """Clean up resources.""" - syslog('debug', '%s: Starting cleanup', self.__class__.__name__) - try: - self._cleanup_old_messages() - # Clean up any stale locks - self._switchboard.cleanup_stale_locks() - except Exception as e: - syslog('error', '%s: Cleanup failed: %s\nTraceback:\n%s', - self.__class__.__name__, str(e), traceback.format_exc()) - syslog('debug', '%s: Cleanup complete', self.__class__.__name__) + """Clean up upon exit from the main processing loop. + + Called when the Runner's main loop is stopped, this should perform + any necessary resource deallocation. Its return value is irrelevant. + """ + Utils.reap(self._kids) def _dispose(self, mlist, msg, msgdata): """Dispose of a single message destined for a mailing list. @@ -488,65 +242,34 @@ def _dispose(self, mlist, msg, msgdata): """ raise NotImplementedError - def _check_retry_delay(self, msgid, filebase): - """Check if enough time has passed since the last retry attempt.""" - now = time.time() - last_retry = self._retry_times.get(msgid, 0) - - if now - last_retry < self.MIN_RETRY_DELAY: - # Only log if this is a significant delay - if self.MIN_RETRY_DELAY > 300: # 5 minutes - syslog('debug', 'Runner._check_retry_delay: Message %s (file: %s) retry delay not met. Last retry: %s, Now: %s, Delay needed: %s', - msgid, filebase, time.ctime(last_retry), time.ctime(now), self.MIN_RETRY_DELAY) - return False - - # Only log if this is a significant delay - if self.MIN_RETRY_DELAY > 300: # 5 minutes - syslog('debug', 'Runner._check_retry_delay: Message %s (file: %s) retry delay met. Last retry: %s, Now: %s', - msgid, filebase, time.ctime(last_retry), time.ctime(now)) - return True + def _doperiodic(self): + """Do some processing `every once in a while'. - def _mark_message_processed(self, msgid): - """Mark a message as processed.""" - with self._processed_lock: - self._processed_messages.add(msgid) - # Only log if we're tracking a large number of messages - if len(self._processed_messages) > 1000: - syslog('debug', 'Runner._mark_message_processed: Marked message %s as processed', msgid) + Called every once in a while both from the Runner's main loop, and + from the Runner's hash slice processing loop. You can do whatever + special periodic processing you want here, and the return value is + irrelevant. + """ + pass - def _unmark_message_processed(self, msgid): - """Remove a message from the processed set.""" - with self._processed_lock: - if msgid in self._processed_messages: - self._processed_messages.remove(msgid) - # Only log if we're tracking a large number of messages - if len(self._processed_messages) > 1000: - syslog('debug', 'Runner._unmark_message_processed: Removed message %s from processed set', msgid) + def _snooze(self, filecnt): + """Sleep for a little while. - def _cleanup_old_messages(self): - """Clean up old message tracking data if message tracking is enabled.""" - if not self._track_messages: + filecnt is the number of messages in the queue the last time through. + Sub-runners can decide to continue to do work, or sleep for a while + based on this value. By default, we only snooze if there was nothing + to do last time around. + """ + if filecnt or self.SLEEPTIME <= 0: return + time.sleep(self.SLEEPTIME) - try: - now = time.time() - if now - self._last_cleanup < self._cleanup_interval: - return + def _shortcircuit(self): + """Return a true value if the individual file processing loop should + exit before it's finished processing each message in the current slice + of hash space. A false value tells _oneloop() to continue processing + until the current snapshot of hash space is exhausted. - with self._processed_lock: - if len(self._processed_messages) > self._max_processed_messages: - # Only log if we're clearing a significant number of messages - if len(self._processed_messages) > 1000: - syslog('debug', '%s: Clearing processed messages set (size: %d)', - self.__class__.__name__, len(self._processed_messages)) - self._processed_messages.clear() - if len(self._retry_times) > self._max_retry_times: - # Only log if we're clearing a significant number of retry times - if len(self._retry_times) > 1000: - syslog('debug', '%s: Clearing retry times dict (size: %d)', - self.__class__.__name__, len(self._retry_times)) - self._retry_times.clear() - self._last_cleanup = now - except Exception as e: - syslog('error', '%s: Error during message cleanup: %s', - self.__class__.__name__, str(e)) + You could, for example, implement a throttling algorithm here. + """ + return self._stop diff --git a/Mailman/Queue/Switchboard.py b/Mailman/Queue/Switchboard.py index c7b88c0d..8353ad94 100644 --- a/Mailman/Queue/Switchboard.py +++ b/Mailman/Queue/Switchboard.py @@ -40,23 +40,13 @@ import errno import pickle import marshal -import email.message -from email.message import Message -import hashlib -import socket -import traceback from Mailman import mm_cfg from Mailman import Utils -from Mailman.Message import Message -from Mailman.Logging.Syslog import mailman_log +from Mailman import Message +from Mailman.Logging.Syslog import syslog from Mailman.Utils import sha_new -# Custom exception class for Switchboard errors -class SwitchboardError(Exception): - """Exception raised for errors in the Switchboard class.""" - pass - # 20 bytes of all bits set, maximum sha.digest() value shamax = 0xffffffffffffffffffffffffffffffffffffffff @@ -73,9 +63,11 @@ class SwitchboardError(Exception): MAX_BAK_COUNT = 3 + class Switchboard: - def __init__(self, whichq, slice=None, numslices=1, recover=False): + def __init__(self, whichq, slice=None, numslices=1, recover=False, distribution='hash'): self.__whichq = whichq + self.__distribution = distribution # Create the directory if it doesn't yet exist. # FIXME omask = os.umask(0) # rwxrws--- @@ -89,655 +81,217 @@ def __init__(self, whichq, slice=None, numslices=1, recover=False): # Fast track for no slices self.__lower = None self.__upper = None + # Always set slice and numslices for compatibility + self.__slice = slice + self.__numslices = numslices # BAW: test performance and end-cases of this algorithm if numslices != 1: - self.__lower = (((shamax+1) * slice) / numslices) - self.__upper = ((((shamax+1) * (slice+1)) / numslices)) - 1 + if distribution == 'hash': + self.__lower = (((shamax+1) * slice) / numslices) + self.__upper = ((((shamax+1) * (slice+1)) / numslices)) - 1 + elif distribution == 'round_robin': + # __slice and __numslices already set above + pass + # Add more distribution methods here as needed if recover: self.recover_backup_files() - # Clean up any stale locks during initialization - self.cleanup_stale_locks() - # Clean up any stale backup files - self.cleanup_stale_backups() - # Clean up any stale processed files - self.cleanup_stale_processed() def whichq(self): return self.__whichq - def enqueue(self, msg, msgdata=None, listname=None, _plaintext=False, **kwargs): - """Add a message to the queue. + def enqueue(self, _msg, _metadata={}, **_kws): + from Mailman.Logging.Syslog import syslog + # Calculate the SHA hexdigest of the message to get a unique base + # filename. We're also going to use the digest as a hash into the set + # of parallel qrunner processes. + data = _metadata.copy() + data.update(_kws) + listname = data.get('listname', '--nolist--') - Args: - msg: The message to enqueue - msgdata: Optional message metadata - listname: Optional list name - _plaintext: Whether to save as plaintext - **kwargs: Additional metadata to add - """ - # Initialize msgdata if not provided - if msgdata is None: - msgdata = {} - - # Add any additional metadata - msgdata.update(kwargs) + # DEBUG: Log archive queue enqueue + if self.__whichq == mm_cfg.ARCHQUEUE_DIR: + syslog('debug', 'Switchboard: Enqueuing message to archive queue for list %s', listname) - # Add listname if provided - if listname: - msgdata['listname'] = listname - - # Then check if we need to set recips - if 'recips' not in msgdata or not msgdata['recips']: - # If we have a recipient but no recips, use the recipient - if msgdata.get('recipient'): - msgdata['recips'] = [msgdata['recipient']] - mailman_log('debug', 'Switchboard.enqueue: Set recips from recipient for message: %s', - msg.get('message-id', 'n/a')) - # Otherwise try to get recipients from message headers - else: - recips = [] - # First try envelope-to header - if msg.get('envelope-to'): - recips.append(msg.get('envelope-to')) - # Then try To header - if msg.get('to'): - addrs = email.utils.getaddresses([msg.get('to')]) - recips.extend([addr[1] for addr in addrs if addr[1]]) - # Then try Cc header - if msg.get('cc'): - addrs = email.utils.getaddresses([msg.get('cc')]) - recips.extend([addr[1] for addr in addrs if addr[1]]) - # Finally try Bcc header - if msg.get('bcc'): - addrs = email.utils.getaddresses([msg.get('bcc')]) - recips.extend([addr[1] for addr in addrs if addr[1]]) - - if recips: - msgdata['recips'] = recips - mailman_log('debug', 'Switchboard.enqueue: Set recipients from message headers for message: %s', - msg.get('message-id', 'n/a')) - else: - mailman_log('error', 'Switchboard: No recipients found in msgdata or message headers for message: %s', - msg.get('message-id', 'n/a')) - raise ValueError('Switchboard: No recipients found in msgdata or message headers') + # Get some data for the input to the sha hash + now = time.time() + if SAVE_MSGS_AS_PICKLES and not data.get('_plaintext'): + protocol = 1 + msgsave = pickle.dumps(_msg, protocol, fix_imports=True) + else: + protocol = 0 + msgsave = pickle.dumps(str(_msg), protocol, fix_imports=True) - # Generate a unique filebase - filebase = self._make_filebase(msg, msgdata) + # Choose distribution method + if self.__distribution == 'round_robin': + # Use a simple counter for round-robin distribution + import threading + if not hasattr(self, '_counter'): + self._counter = 0 + self._counter_lock = threading.Lock() + + with self._counter_lock: + self._counter = (self._counter + 1) % self.__numslices + current_slice = self._counter + hashfood = msgsave + listname.encode() + repr(now).encode() + str(current_slice).encode() + else: + # Default hash-based distribution + hashfood = msgsave + listname.encode() + repr(now).encode() - # Calculate the filename + # Encode the current time into the file name for FIFO sorting in + # files(). The file name consists of two parts separated by a `+': + # the received time for this message (i.e. when it first showed up on + # this system) and the sha hex digest. + #rcvtime = data.setdefault('received_time', now) + rcvtime = data.setdefault('received_time', now) + filebase = repr(rcvtime) + '+' + sha_new(hashfood).hexdigest() filename = os.path.join(self.__whichq, filebase + '.pck') - - # Create a lock file - lockfile = filename + '.lock' - try: - fd = os.open(lockfile, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o644) - os.close(fd) - except OSError as e: - if e.errno != errno.EEXIST: - mailman_log('error', 'Switchboard.enqueue: Failed to create lock file for %s: %s', filebase, str(e)) - raise - return None - + tmpfile = filename + '.tmp' + # Always add the metadata schema version number + data['version'] = mm_cfg.QFILE_SCHEMA_VERSION + # Filter out volatile entries + for k in list(data.keys()): + if k.startswith('_'): + del data[k] + # We have to tell the dequeue() method whether to parse the message + # object or not. + data['_parsemsg'] = (protocol == 0) + # Write to the pickle file the message object and metadata. + omask = os.umask(0o007) # -rw-rw---- try: - # Write the message and metadata + fp = open(tmpfile, 'wb') try: - self._enqueue(filename, msg, msgdata, _plaintext) - except Exception as e: - mailman_log('error', 'Switchboard.enqueue: Failed to write message to %s: %s', filebase, str(e)) - raise - - # Add filebase to msgdata for cleanup - msgdata['filebase'] = filebase - return filebase + fp.write(msgsave) + pickle.dump(data, fp, protocol) + fp.flush() + os.fsync(fp.fileno()) + finally: + fp.close() finally: - # Always clean up the lock file - try: - os.unlink(lockfile) - except OSError: - pass + os.umask(omask) + os.rename(tmpfile, filename) + + # DEBUG: Log successful enqueue + if self.__whichq == mm_cfg.ARCHQUEUE_DIR: + syslog('debug', 'Switchboard: Successfully enqueued message to archive queue: %s', filebase) + + return filebase def dequeue(self, filebase): # Calculate the filename from the given filebase. filename = os.path.join(self.__whichq, filebase + '.pck') - bakfile = os.path.join(self.__whichq, filebase + '.bak') - psvfile = os.path.join(self.__whichq, filebase + '.psv') - lockfile = filename + '.lock' - - # Check if file exists before proceeding - if not os.path.exists(filename): - # Check if it's been moved to backup or shunt - if os.path.exists(bakfile): - mailman_log('debug', 'Queue file %s has been moved to backup file %s', filename, bakfile) - elif os.path.exists(psvfile): - mailman_log('debug', 'Queue file %s has been moved to shunt queue %s', filename, psvfile) - else: - mailman_log('warning', 'Queue file does not exist: %s (not found in backup or shunt either)', filename) - return None, None - - # Create a lock file - try: - lock_fd = os.open(lockfile, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600) - os.close(lock_fd) - except OSError as e: - if e.errno == errno.EEXIST: - mailman_log('warning', 'Lock file exists for %s (full path: %s)', filename, lockfile) - return None, None - else: - mailman_log('error', 'Failed to create lock file %s (full path: %s): %s', filename, lockfile, str(e)) - return None, None - + backfile = os.path.join(self.__whichq, filebase + '.bak') + # Read the message object and metadata. + fp = open(filename, 'rb') + # Move the file to the backup file name for processing. If this + # process crashes uncleanly the .bak file will be used to re-instate + # the .pck file in order to try again. + os.rename(filename, backfile) try: - # First read the file contents - try: - with open(filename, 'rb') as fp: - content = fp.read() - if not content: - mailman_log('error', 'Empty queue file: %s', filename) - return None, None - - # Create a BytesIO object to read from the content - from io import BytesIO - fp = BytesIO(content) - - try: - msg = pickle.load(fp, fix_imports=True, encoding='latin1') - data = pickle.load(fp, fix_imports=True, encoding='latin1') - except (EOFError, pickle.UnpicklingError) as e: - mailman_log('error', 'Error loading queue file %s: %s', filename, str(e)) - return None, None - except (IOError, OSError) as e: - mailman_log('error', 'Error reading queue file %s: %s', filename, str(e)) - return None, None - - # Now that we've successfully read the file, move it to backup - try: - os.rename(filename, bakfile) - except (IOError, OSError) as e: - mailman_log('error', 'Error moving queue file %s to backup: %s', filename, str(e)) - return None, None - - if data.get('_parsemsg'): - msg = email.message_from_string(msg, Message) - # Add filebase to msgdata for cleanup - if data is not None: - data['filebase'] = filebase - return msg, data - + msg = pickle.load(fp, fix_imports=True, encoding='latin1') + data = pickle.load(fp, fix_imports=True, encoding='latin1') finally: - # Always clean up the lock file - try: - if os.path.exists(lockfile): - os.unlink(lockfile) - except OSError: - pass + fp.close() + if data.get('_parsemsg'): + msg = email.message_from_string(msg, Message.Message) + return msg, data def finish(self, filebase, preserve=False): - """Finish processing a file by either removing it or moving it to the shunt queue. - - Args: - filebase: The base name of the file to process - preserve: If True, move the file to the shunt queue instead of removing it - """ - if not filebase: - mailman_log('error', 'Switchboard.finish: No filebase provided') - return - bakfile = os.path.join(self.__whichq, filebase + '.bak') - pckfile = os.path.join(self.__whichq, filebase + '.pck') - - # First check if the backup file exists - if not os.path.exists(bakfile): - # Only log at debug level if the .pck file still exists (message still being processed) - if os.path.exists(pckfile): - mailman_log('debug', 'Switchboard.finish: Backup file does not exist: %s', bakfile) - # Try to clean up the .pck file if it exists - try: - os.unlink(pckfile) - mailman_log('debug', 'Switchboard.finish: Removed stale .pck file: %s', pckfile) - except OSError as e: - mailman_log('error', 'Switchboard.finish: Failed to remove stale .pck file %s: %s', - pckfile, str(e)) - return - try: if preserve: - # Move the file to the shunt queue - psvfile = os.path.join(mm_cfg.SHUNTQUEUE_DIR, filebase + '.bak') - - # Ensure the shunt queue directory exists - if not os.path.exists(mm_cfg.SHUNTQUEUE_DIR): + psvfile = os.path.join(mm_cfg.BADQUEUE_DIR, filebase + '.psv') + # Create the directory if it doesn't yet exist. + # Copied from __init__. + omask = os.umask(0) # rwxrws--- + try: try: - os.makedirs(mm_cfg.SHUNTQUEUE_DIR, 0o775) + os.mkdir(mm_cfg.BADQUEUE_DIR, 0o0770) except OSError as e: - mailman_log('error', 'Switchboard.finish: Failed to create shunt queue directory: %s', - str(e)) - raise - - # Move the file and verify - try: - os.rename(bakfile, psvfile) - if not os.path.exists(psvfile): - mailman_log('error', 'Switchboard.finish: Failed to move backup file to shunt queue: %s -> %s', - bakfile, psvfile) - else: - mailman_log('debug', 'Switchboard.finish: Successfully moved backup file to shunt queue: %s -> %s', - bakfile, psvfile) - except OSError as e: - mailman_log('error', 'Switchboard.finish: Failed to move backup file to shunt queue: %s -> %s: %s', - bakfile, psvfile, str(e)) - raise + if e.errno != errno.EEXIST: raise + finally: + os.umask(omask) + os.rename(bakfile, psvfile) else: - # Remove the backup file - try: - os.unlink(bakfile) - if os.path.exists(bakfile): - mailman_log('error', 'Switchboard.finish: Failed to unlink backup file: %s', bakfile) - else: - mailman_log('debug', 'Switchboard.finish: Successfully unlinked backup file: %s', bakfile) - except OSError as e: - mailman_log('error', 'Switchboard.finish: Failed to unlink backup file %s: %s', - bakfile, str(e)) - raise - except Exception as e: - mailman_log('error', 'Switchboard.finish: Failed to finish processing backup file %s: %s', - bakfile, str(e)) - raise + os.unlink(bakfile) + except EnvironmentError as e: + syslog('error', 'Failed to unlink/preserve backup file: %s\n%s', + bakfile, e) def files(self, extension='.pck'): times = {} lower = self.__lower upper = self.__upper - try: - for f in os.listdir(self.__whichq): - if not f.endswith(extension): - continue - filebase = f[:-len(extension)] - try: - # Get the file's modification time - mtime = os.path.getmtime(os.path.join(self.__whichq, f)) - # Only apply time bounds if they are set - if lower is None or upper is None or (lower <= mtime < upper): - times[filebase] = mtime - except OSError: - continue - # Sort by modification time but return just the filebases - return [f for f, _ in sorted(times.items(), key=lambda x: x[1])] - except OSError as e: - mailman_log('error', 'Error reading queue directory %s: %s', self.__whichq, str(e)) - return [] + for f in os.listdir(self.__whichq): + # By ignoring anything that doesn't end in .pck, we ignore + # tempfiles and avoid a race condition. + filebase, ext = os.path.splitext(f) + if ext != extension: + continue + when, digest = filebase.split('+') + + # Choose distribution method for file filtering + if self.__distribution == 'round_robin': + # For round-robin, use modulo of digest to determine slice + slice_num = int(digest, 16) % self.__numslices + if slice_num == self.__slice: + key = float(when) + while key in times: + key += DELTA + times[key] = filebase + else: + # Default hash-based distribution + # Throw out any files which don't match our bitrange. BAW: test + # performance and end-cases of this algorithm. MAS: both + # comparisons need to be <= to get complete range. + if lower is None or (lower <= int(digest, 16) <= upper): + key = float(when) + while key in times: + key += DELTA + times[key] = filebase + # FIFO sort + keys = list(times.keys()) + keys.sort() + return [times[k] for k in keys] def recover_backup_files(self): - """Move all .bak files in our slice to .pck. - - This method implements a robust recovery mechanism with: - 1. Proper error handling for corrupted files - 2. Validation of backup file contents - 3. Detailed logging of recovery attempts - 4. Safe file operations with atomic moves - """ - try: - for filebase in self.files('.bak'): - src = os.path.join(self.__whichq, filebase + '.bak') - dst = os.path.join(self.__whichq, filebase + '.pck') - - try: - # First try to validate the backup file - with open(src, 'rb') as fp: - try: - # Try to read the entire file first to check for EOF - content = fp.read() - if not content: - mailman_log('error', 'Empty backup file found: %s', filebase) - raise EOFError('Empty backup file') - - # Create a BytesIO object to read from the content - from io import BytesIO - fp = BytesIO(content) - - try: - msg = pickle.load(fp, fix_imports=True, encoding='latin1') - data_pos = fp.tell() - data = pickle.load(fp, fix_imports=True, encoding='latin1') - except (EOFError, pickle.UnpicklingError) as e: - mailman_log('error', 'Corrupted backup file %s: %s\nTraceback:\n%s', - filebase, str(e), traceback.format_exc()) - self.finish(filebase, preserve=True) - return - - # Validate the unpickled data - if not isinstance(data, dict): - mailman_log('error', 'Invalid data format in backup file %s: expected dict, got %s', filebase, type(data)) - raise TypeError('Invalid data format in backup file') - - try: - os.rename(src, dst) - except Exception as e: - mailman_log('error', 'Failed to rename backup file %s (full paths: %s -> %s): %s\nTraceback:\n%s', - filebase, os.path.join(self.__whichq, filebase + '.bak'), os.path.join(self.__whichq, filebase + '.pck'), str(e), traceback.format_exc()) - self.finish(filebase, preserve=True) - return - except Exception as e: - mailman_log('error', 'Failed to process backup file %s (full path: %s): %s\nTraceback:\n%s', - filebase, os.path.join(self.__whichq, filebase + '.bak'), str(e), traceback.format_exc()) - self.finish(filebase, preserve=True) - return - - except Exception as e: - mailman_log('error', 'Failed to process backup file %s (full path: %s): %s\nTraceback:\n%s', - filebase, os.path.join(self.__whichq, filebase + '.bak'), str(e), traceback.format_exc()) - return None, None - except Exception as e: - mailman_log('error', 'Failed to recover backup files: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - raise - - def _enqueue(self, filename, msg, msgdata, _plaintext): - """Enqueue a message for delivery. - - This method implements a robust enqueue mechanism with: - 1. Unique temporary filename - 2. Atomic write - 3. Validation of written data - 4. Proper error handling and cleanup - 5. File locking for concurrent access - """ - # Create a unique filename using the standard format - now = time.time() - msgid = msg.get('message-id', '') - listname = msgdata.get('listname', '--nolist--') - hash_input = (str(msgid) + str(listname) + str(now)).encode('utf-8') - digest = hashlib.sha1(hash_input).hexdigest() - filebase = "%d+%s" % (int(now), digest) - qfile = os.path.join(self.__whichq, filebase + '.pck') - tmpfile = qfile + '.tmp.%s.%d' % (socket.gethostname(), os.getpid()) - lockfile = qfile + '.lock' - - # Create lock file - try: - lock_fd = os.open(lockfile, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600) - os.close(lock_fd) - except OSError as e: - if e.errno == errno.EEXIST: - mailman_log('warning', 'Lock file exists for %s (full path: %s)', qfile, lockfile) - raise - else: - mailman_log('error', 'Failed to create lock file %s (full path: %s): %s\nTraceback:\n%s', - qfile, lockfile, str(e), traceback.format_exc()) - raise - - try: - # Ensure directory exists with proper permissions - dirname = os.path.dirname(tmpfile) - if not os.path.exists(dirname): - try: - os.makedirs(dirname, 0o755) - except Exception as e: - mailman_log('error', 'Failed to create directory %s (full path: %s): %s\nTraceback:\n%s', - dirname, os.path.abspath(dirname), str(e), traceback.format_exc()) - raise - - # Convert message to Mailman.Message if needed - if isinstance(msg, email.message.Message) and not isinstance(msg, Message): - mailman_msg = Message() - # Copy all attributes from the original message - for key, value in msg.items(): - mailman_msg[key] = value - # Copy the payload with proper MIME handling - if msg.is_multipart(): - for part in msg.get_payload(): - if isinstance(part, email.message.Message): - mailman_msg.attach(part) - else: - newpart = Message() - newpart.set_payload(part) - mailman_msg.attach(newpart) - else: - mailman_msg.set_payload(msg.get_payload()) - msg = mailman_msg - - # Write to temporary file first + # Move all .bak files in our slice to .pck. It's impossible for both + # to exist at the same time, so the move is enough to ensure that our + # normal dequeuing process will handle them. We keep count in + # _bak_count in the metadata of the number of times we recover this + # file. When the count reaches MAX_BAK_COUNT, we move the .bak file + # to a .psv file in the shunt queue. + for filebase in self.files('.bak'): + src = os.path.join(self.__whichq, filebase + '.bak') + dst = os.path.join(self.__whichq, filebase + '.pck') + fp = open(src, 'rb+') try: - with open(tmpfile, 'wb') as fp: - pickle.dump((msg, msgdata), fp, protocol=4, fix_imports=True) - fp.flush() - if hasattr(os, 'fsync'): - os.fsync(fp.fileno()) - except Exception as e: - mailman_log('error', 'Failed to write temporary file %s (full path: %s): %s\nTraceback:\n%s', - tmpfile, os.path.abspath(tmpfile), str(e), traceback.format_exc()) - raise - - # Validate the temporary file - try: - with open(tmpfile, 'rb') as fp: - test_data = pickle.load(fp, fix_imports=True, encoding='latin1') - if not isinstance(test_data, tuple) or len(test_data) != 2: - raise TypeError('Loaded data is not a valid tuple') - # Verify message type - if not isinstance(test_data[0], Message): - raise TypeError('Message is not a Mailman.Message instance') - except Exception as e: - mailman_log('error', 'Validation of temporary file failed: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - # Try to clean up - try: - os.unlink(tmpfile) - except Exception as cleanup_e: - mailman_log('error', 'Failed to clean up temporary file %s (full path: %s): %s\nTraceback:\n%s', - tmpfile, os.path.abspath(tmpfile), str(cleanup_e), traceback.format_exc()) - raise - - # Atomic rename with existence check - try: - if os.path.exists(qfile): - mailman_log('warning', 'Target file %s (full path: %s) already exists, removing old version', qfile, os.path.abspath(qfile)) - os.unlink(qfile) - os.rename(tmpfile, qfile) - except Exception as e: - mailman_log('error', 'Failed to rename %s to %s (full paths: %s -> %s): %s\nTraceback:\n%s', - tmpfile, qfile, os.path.abspath(tmpfile), os.path.abspath(qfile), str(e), traceback.format_exc()) - # Try to clean up - try: - if os.path.exists(tmpfile): - os.unlink(tmpfile) - except Exception as cleanup_e: - mailman_log('error', 'Failed to clean up temporary file %s (full path: %s): %s\nTraceback:\n%s', - tmpfile, os.path.abspath(tmpfile), str(cleanup_e), traceback.format_exc()) - raise - - # Set proper permissions - try: - os.chmod(qfile, 0o660) - except Exception as e: - mailman_log('warning', 'Failed to set permissions on %s (full path: %s): %s\nTraceback:\n%s', - qfile, os.path.abspath(qfile), str(e), traceback.format_exc()) - # Not critical, continue - - finally: - # Clean up any temporary files and lock - try: - if os.path.exists(tmpfile): - os.unlink(tmpfile) - if os.path.exists(lockfile): - os.unlink(lockfile) - except Exception as cleanup_e: - mailman_log('error', 'Failed to clean up temporary/lock files: %s\nTraceback:\n%s', - str(cleanup_e), traceback.format_exc()) - - def _dequeue(self, filename): - """Dequeue a message from the queue.""" - try: - with open(filename, 'rb') as fp: try: - # Try UTF-8 first for newer files - data = pickle.load(fp, fix_imports=True, encoding='utf-8') - if not isinstance(data, tuple) or len(data) != 2: - raise TypeError('Invalid data format in queue file') - msgsave, metadata = data - - # Ensure we have a Mailman.Message - if isinstance(msgsave, email.message.Message) and not isinstance(msgsave, Message): - mailman_msg = Message() - # Copy all attributes from the original message - for key, value in msgsave.items(): - mailman_msg[key] = value - # Copy the payload with proper MIME handling - if msgsave.is_multipart(): - for part in msgsave.get_payload(): - if isinstance(part, email.message.Message): - mailman_msg.attach(part) - else: - newpart = Message() - newpart.set_payload(part) - mailman_msg.attach(newpart) - else: - mailman_msg.set_payload(msgsave.get_payload()) - msgsave = mailman_msg - - return msgsave, metadata - except (UnicodeDecodeError, pickle.UnpicklingError): - # Fall back to latin1 for older files - fp.seek(0) + msg = pickle.load(fp, fix_imports=True, encoding='latin1') + data_pos = fp.tell() data = pickle.load(fp, fix_imports=True, encoding='latin1') - if not isinstance(data, tuple) or len(data) != 2: - raise TypeError('Invalid data format in queue file') - msgsave, metadata = data - - # Ensure we have a Mailman.Message - if isinstance(msgsave, email.message.Message) and not isinstance(msgsave, Message): - mailman_msg = Message() - # Copy all attributes from the original message - for key, value in msgsave.items(): - mailman_msg[key] = value - # Copy the payload with proper MIME handling - if msgsave.is_multipart(): - for part in msgsave.get_payload(): - if isinstance(part, email.message.Message): - mailman_msg.attach(part) - else: - newpart = Message() - newpart.set_payload(part) - mailman_msg.attach(newpart) - else: - mailman_msg.set_payload(msgsave.get_payload()) - msgsave = mailman_msg - - return msgsave, metadata - except (IOError, OSError) as e: - mailman_log('error', 'Error dequeuing message from %s: %s', filename, str(e)) - return None, None - - def _dequeue_metadata(self, filename): - """Dequeue just the metadata from the queue.""" - try: - with open(filename, 'rb') as fp: - try: - # Try UTF-8 first, then fall back to latin-1 - try: - # Skip the message - pickle.load(fp, fix_imports=True, encoding='utf-8') - # Get the metadata - metadata = pickle.load(fp, fix_imports=True, encoding='utf-8') - except (pickle.UnpicklingError, EOFError) as e: - # Reset file pointer to beginning - fp.seek(0) - # Try latin-1 as fallback - pickle.load(fp, fix_imports=True, encoding='latin1') - metadata = pickle.load(fp, fix_imports=True, encoding='latin1') - except (pickle.UnpicklingError, EOFError) as e: - raise IOError('Could not unpickle %s: %s' % (filename, e)) - return metadata - except (IOError, OSError) as e: - raise IOError('Could not read %s: %s' % (filename, e)) - - def cleanup_stale_locks(self): - """Clean up any stale lock files in the queue directory.""" - try: - for f in os.listdir(self.__whichq): - if f.endswith('.lock'): - lockfile = os.path.join(self.__whichq, f) - try: - lock_age = time.time() - os.path.getmtime(lockfile) - if lock_age > 300: # 5 minutes - # Read lock file contents for debugging - try: - with open(lockfile, 'r') as f: - lock_info = f.read() - mailman_log('warning', - 'Cleaning up stale lock file %s (age: %d seconds)\nLock info: %s', - lockfile, lock_age, lock_info) - except Exception: - mailman_log('warning', - 'Cleaning up stale lock file %s (age: %d seconds)', - lockfile, lock_age) - os.unlink(lockfile) - except OSError: - pass - except OSError as e: - mailman_log('error', 'Error cleaning up stale locks: %s', str(e)) - - def cleanup_stale_backups(self): - """Clean up any stale backup files in the queue directory. - - This method removes backup files that are older than 24 hours - to prevent accumulation of stale files. - """ - try: - now = time.time() - stale_age = 24 * 3600 # 24 hours in seconds - - for f in os.listdir(self.__whichq): - if f.endswith('.bak'): - bakfile = os.path.join(self.__whichq, f) - try: - # Check file age - file_age = now - os.path.getmtime(bakfile) - if file_age > stale_age: - mailman_log('warning', - 'Cleaning up stale backup file %s (age: %d seconds)', - bakfile, file_age) - os.unlink(bakfile) - except OSError as e: - mailman_log('error', - 'Failed to clean up stale backup file %s: %s', - bakfile, str(e)) - except OSError as e: - mailman_log('error', 'Error cleaning up stale backup files: %s', str(e)) - - def cleanup_stale_processed(self): - """Clean up any stale processed files in the queue directory. - - This method removes processed files that are older than 7 days - to prevent accumulation of stale files. - """ - try: - now = time.time() - stale_age = 7 * 24 * 3600 # 7 days in seconds - - for f in os.listdir(self.__whichq): - if f.endswith('.pck'): - pckfile = os.path.join(self.__whichq, f) - try: - # Check file age - file_age = now - os.path.getmtime(pckfile) - if file_age > stale_age: - mailman_log('warning', - 'Cleaning up stale processed file %s (age: %d seconds)', - pckfile, file_age) - os.unlink(pckfile) - except OSError as e: - mailman_log('error', - 'Failed to clean up stale processed file %s: %s', - pckfile, str(e)) - except OSError as e: - mailman_log('error', 'Error cleaning up stale processed files: %s', str(e)) - - def _make_filebase(self, msg, msgdata): - import hashlib - import time - msgid = msg.get('message-id', '') - listname = msgdata.get('listname', '--nolist--') - now = time.time() - hash_input = (str(msgid) + str(listname) + str(now)).encode('utf-8') - digest = hashlib.sha1(hash_input).hexdigest() - return "%d+%s" % (int(now), digest) + except Exception as s: + # If unpickling throws any exception, just log and + # preserve this entry + syslog('error', 'Unpickling .bak exception: %s\n' + + 'preserving file: %s', s, filebase) + self.finish(filebase, preserve=True) + else: + data['_bak_count'] = data.setdefault('_bak_count', 0) + 1 + fp.seek(data_pos) + if data.get('_parsemsg'): + protocol = 0 + else: + protocol = 1 + pickle.dump(data, fp, protocol) + fp.truncate() + fp.flush() + os.fsync(fp.fileno()) + if data['_bak_count'] >= MAX_BAK_COUNT: + syslog('error', + '.bak file max count, preserving file: %s', + filebase) + self.finish(filebase, preserve=True) + else: + os.rename(src, dst) + finally: + fp.close() diff --git a/Mailman/Queue/VirginRunner.py b/Mailman/Queue/VirginRunner.py index f50b9d84..410a9336 100644 --- a/Mailman/Queue/VirginRunner.py +++ b/Mailman/Queue/VirginRunner.py @@ -25,131 +25,11 @@ from Mailman import mm_cfg from Mailman.Queue.Runner import Runner from Mailman.Queue.IncomingRunner import IncomingRunner -from Mailman.Logging.Syslog import mailman_log -import time -import traceback -from Mailman import Errors -import threading -import email.header -import os + class VirginRunner(IncomingRunner): QDIR = mm_cfg.VIRGINQUEUE_DIR - # Maximum age for message tracking data - _max_tracking_age = 86400 # 24 hours in seconds - # Cleanup interval for message tracking data - _cleanup_interval = 3600 # 1 hour in seconds - - # Message tracking configuration - _processed_messages = set() - _processed_lock = threading.Lock() - _last_cleanup = time.time() - _max_processed_messages = 10000 - _processed_times = {} # Track processing times for messages - - def __init__(self, slice=None, numslices=1): - IncomingRunner.__init__(self, slice, numslices) - # VirginRunner is a subclass of IncomingRunner, but we want to use a - # different pipeline for processing virgin messages. The main - # difference is that we don't need to do bounce detection, and we can - # skip a few other checks. - self._pipeline = self._get_pipeline() - # VirginRunner is a subclass of IncomingRunner, but we want to use a - # different pipeline for processing virgin messages. The main - # difference is that we don't need to do bounce detection, and we can - # skip a few other checks. - self._fasttrack = 1 - mailman_log('debug', 'VirginRunner: Starting initialization') - try: - Runner.__init__(self, slice, numslices) - - # Initialize processed messages tracking - self._processed_messages = set() - self._processed_times = {} - self._last_cleanup = time.time() - - mailman_log('debug', 'VirginRunner: Initialization complete') - except Exception as e: - mailman_log('error', 'VirginRunner: Initialization failed: %s\nTraceback:\n%s', - str(e), traceback.format_exc()) - raise - - def _check_message_processed(self, msgid, filebase, msg): - """Check if a message has already been processed. - Returns True if the message can be processed, False if it's a duplicate.""" - try: - with self._processed_lock: - current_time = time.time() - - # Check if cleanup is needed - if current_time - self._last_cleanup > self._cleanup_interval: - try: - mailman_log('debug', 'VirginRunner: Starting cleanup of old message tracking data') - # Only clean up entries older than cleanup_interval - cutoff_time = current_time - self._cleanup_interval - # Clean up old message IDs - old_msgids = [mid for mid, process_time in self._processed_times.items() - if process_time < cutoff_time] - for mid in old_msgids: - self._processed_times.pop(mid, None) - self._processed_messages.discard(mid) - self._last_cleanup = current_time - mailman_log('debug', 'VirginRunner: Cleaned up %d old message entries', len(old_msgids)) - except Exception as e: - mailman_log('error', 'VirginRunner: Error during cleanup: %s', str(e)) - # Continue processing even if cleanup fails - - # For welcome messages, check content and recipients - subject = msg.get('subject', '') - if isinstance(subject, email.header.Header): - subject = str(subject) - subject = subject.lower() - - if 'welcome to the' in subject: - # Create a unique key based on subject, to, and from - to_addr = msg.get('to', '') - from_addr = msg.get('from', '') - if isinstance(to_addr, email.header.Header): - to_addr = str(to_addr) - if isinstance(from_addr, email.header.Header): - from_addr = str(from_addr) - - content_key = f"{subject}|{to_addr}|{from_addr}" - if content_key in self._processed_messages: - mailman_log('info', 'VirginRunner: Duplicate welcome message detected: %s (file: %s)', - content_key, filebase) - return False - # Mark this content as processed - self._processed_messages.add(content_key) - self._processed_times[content_key] = current_time - return True - - # For other messages, check message ID - if msgid in self._processed_messages: - mailman_log('info', 'VirginRunner: Duplicate message detected: %s (file: %s)', - msgid, filebase) - return False - - # Mark message as processed - try: - self._processed_messages.add(msgid) - self._processed_times[msgid] = current_time - mailman_log('debug', 'VirginRunner: Message %s (file: %s) marked for processing', - msgid, filebase) - return True - except Exception as e: - # If we fail to update the tracking data, remove the message from processed set - self._processed_messages.discard(msgid) - self._processed_times.pop(msgid, None) - mailman_log('error', 'VirginRunner: Failed to update tracking data for message %s: %s', - msgid, str(e)) - return False - - except Exception as e: - mailman_log('error', 'VirginRunner: Unexpected error in message check for %s: %s', - msgid, str(e)) - return False def _dispose(self, mlist, msg, msgdata): # We need to fasttrack this message through any handlers that touch @@ -161,111 +41,3 @@ def _get_pipeline(self, mlist, msg, msgdata): # It's okay to hardcode this, since it'll be the same for all # internally crafted messages. return ['CookHeaders', 'ToOutgoing'] - - def _cleanup_old_messages(self): - """Clean up old message tracking data.""" - with self._processed_lock: - if len(self._processed_messages) > self._max_processed_messages: - mailman_log('debug', 'VirginRunner._cleanup_old_messages: Clearing processed messages set (size: %d)', - len(self._processed_messages)) - self._processed_messages.clear() - if len(self._processed_times) > self._max_processed_messages: - mailman_log('debug', 'VirginRunner._cleanup_old_messages: Clearing processed times dict (size: %d)', - len(self._processed_times)) - self._processed_times.clear() - self._last_cleanup = time.time() - - def _onefile(self, msg, msgdata): - """Process a single file from the queue.""" - # Ensure _dispose always gets a MailList object, not a string - listname = msgdata.get('listname') - if not listname: - listname = mm_cfg.MAILMAN_SITE_LIST - try: - # Lazy import to avoid circular dependency - from Mailman.MailList import MailList - mlist = MailList(listname, lock=0) - except Errors.MMUnknownListError: - mailman_log('error', 'VirginRunner: Unknown list %s', listname) - self._shunt.enqueue(msg, msgdata) - return False - try: - keepqueued = self._dispose(mlist, msg, msgdata) - if keepqueued: - self._switchboard.enqueue(msg, msgdata) - return keepqueued - finally: - mlist.Unlock() - - def _unmark_message_processed(self, msgid): - """Remove a message from the processed messages set.""" - with self._processed_lock: - if msgid in self._processed_messages: - self._processed_messages.remove(msgid) - if msgid in self._processed_times: - del self._processed_times[msgid] - mailman_log('debug', 'VirginRunner: Unmarked message %s as processed', msgid) - - def _oneloop(self): - """Process one batch of messages from the virgin queue.""" - try: - # Get the list of files to process - files = self._switchboard.files() - if not files: - mailman_log('debug', 'VirginRunner: No files to process') - return - - mailman_log('debug', 'VirginRunner: Processing %d files', len(files)) - - # Process each file - for filebase in files: - try: - # Check if the file exists before dequeuing - pckfile = os.path.join(self.QDIR, filebase + '.pck') - if not os.path.exists(pckfile): - mailman_log('error', 'VirginRunner._oneloop: File %s does not exist, skipping', pckfile) - continue - - # Check if file is locked - lockfile = os.path.join(self.QDIR, filebase + '.pck.lock') - if os.path.exists(lockfile): - mailman_log('debug', 'VirginRunner._oneloop: File %s is locked by another process, skipping', filebase) - continue - - # Dequeue the file - msg, msgdata = self._switchboard.dequeue(filebase) - if msg is None: - mailman_log('debug', 'VirginRunner._oneloop: No message data for %s', filebase) - continue - - # Get message ID for tracking - msgid = msg.get('message-id', 'n/a') - - # Check if message has already been processed - if not self._check_message_processed(msgid, filebase, msg): - mailman_log('debug', 'VirginRunner._oneloop: Message %s already processed, skipping', msgid) - continue - - try: - # Process the message - success = self._onefile(msg, msgdata) - if success: - mailman_log('debug', 'VirginRunner: Successfully processed message %s', msgid) - else: - mailman_log('debug', 'VirginRunner: Message %s requeued for later processing', msgid) - except Exception as e: - mailman_log('error', 'VirginRunner: Error processing %s: %s', msgid, str(e)) - mailman_log('error', 'VirginRunner: Traceback:\n%s', traceback.format_exc()) - self._handle_error(e, msg, None) - # Unmark the message as processed since it failed - self._unmark_message_processed(msgid) - - except Exception as e: - mailman_log('error', 'VirginRunner: Error processing file %s: %s', filebase, str(e)) - mailman_log('error', 'VirginRunner: Traceback:\n%s', traceback.format_exc()) - continue - - except Exception as e: - mailman_log('error', 'VirginRunner: Error in _oneloop: %s', str(e)) - mailman_log('error', 'VirginRunner: Traceback:\n%s', traceback.format_exc()) - raise diff --git a/Mailman/Queue/__init__.py b/Mailman/Queue/__init__.py index cef674fb..3bf720f9 100644 --- a/Mailman/Queue/__init__.py +++ b/Mailman/Queue/__init__.py @@ -13,61 +13,3 @@ # 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. - -"""Mailman Queue package initialization. - -This package contains the queue runners that process various types of messages -in the Mailman system. -""" - -import os -import sys - -# Add the parent directory to the Python path if it's not already there -parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -if parent_dir not in sys.path: - sys.path.insert(0, parent_dir) - -# Import the base Runner class first -from Mailman.Queue.Runner import Runner - -# Then import the Switchboard -from Mailman.Queue.Switchboard import Switchboard - -# Import other runners that don't have dependencies -from Mailman.Queue.BounceRunner import BounceRunner -from Mailman.Queue.MaildirRunner import MaildirRunner -from Mailman.Queue.RetryRunner import RetryRunner -from Mailman.Queue.CommandRunner import CommandRunner -from Mailman.Queue.ArchRunner import ArchRunner - -# Define lazy imports to avoid circular dependencies -def get_news_runner(): - from Mailman.Queue.NewsRunner import NewsRunner - return NewsRunner - -def get_incoming_runner(): - from Mailman.Queue.IncomingRunner import IncomingRunner - return IncomingRunner - -def get_virgin_runner(): - from Mailman.Queue.VirginRunner import VirginRunner - return VirginRunner - -def get_outgoing_runner(): - from Mailman.Queue.OutgoingRunner import OutgoingRunner - return OutgoingRunner - -__all__ = [ - 'Runner', - 'Switchboard', - 'BounceRunner', - 'MaildirRunner', - 'RetryRunner', - 'CommandRunner', - 'ArchRunner', - 'get_news_runner', - 'get_incoming_runner', - 'get_virgin_runner', - 'get_outgoing_runner', -] diff --git a/Mailman/SecurityManager.py b/Mailman/SecurityManager.py index 465e71fa..492cd930 100644 --- a/Mailman/SecurityManager.py +++ b/Mailman/SecurityManager.py @@ -66,7 +66,7 @@ from Mailman import mm_cfg from Mailman import Utils from Mailman import Errors -from Mailman.Logging.Syslog import syslog, mailman_log +from Mailman.Logging.Syslog import syslog from Mailman.Utils import md5_new, sha_new @@ -97,7 +97,7 @@ def AuthContextInfo(self, authcontext, user=None): if authcontext == mm_cfg.AuthUser: if user is None: # A bad system error - raise Exception(TypeError, 'No user supplied for AuthUser context') + raise TypeError('No user supplied for AuthUser context') user = Utils.UnobscureEmail(urllib.parse.unquote(user)) secret = self.getMemberPassword(user) userdata = urllib.parse.quote(Utils.ObscureEmail(user), safe='') @@ -139,11 +139,7 @@ def Authenticate(self, authcontexts, response, user=None): if not response: # Don't authenticate null passwords return mm_cfg.UnAuthorized - # Log the type and encoding of the response - mailman_log('debug', 'Auth response type: %s, encoding: %s', - type(response), getattr(response, 'encoding', 'N/A')) - # python3 - response = response.encode('UTF-8') + for ac in authcontexts: if ac == mm_cfg.AuthCreator: ok = Utils.check_global_password(response, siteadmin=0) @@ -177,6 +173,9 @@ def cryptmatchp(response, secret): key, secret = self.AuthContextInfo(ac) if secret is None: continue + if isinstance(response, str): + response = response.encode('utf-8') + sharesponse = sha_new(response).hexdigest() upgrade = ok = False if sharesponse == secret: @@ -252,9 +251,7 @@ def MakeCookie(self, authcontext, user=None): mac = sha_new(needs_hashing).hexdigest() # Create the cookie object. c = http.cookies.SimpleCookie() - # Ensure cookie value is a string, not bytes - cookie_value = binascii.hexlify(marshal.dumps((issued, mac))).decode('ascii') - c[key] = cookie_value + c[key] = binascii.hexlify(marshal.dumps((issued, mac))).decode() # The path to all Mailman stuff, minus the scheme and host, # i.e. usually the string `/mailman' parsed = urlparse(self.web_page_url) diff --git a/Mailman/Site.py b/Mailman/Site.py index 8e03d6a0..6fa6afb1 100644 --- a/Mailman/Site.py +++ b/Mailman/Site.py @@ -100,14 +100,7 @@ def get_listnames(domain=None): from Mailman.Utils import list_exists # We don't currently support separate virtual domain directories got = [] - # Ensure LIST_DATA_DIR is a string - list_dir = mm_cfg.LIST_DATA_DIR - if isinstance(list_dir, bytes): - list_dir = list_dir.decode('utf-8', 'replace') - for fn in os.listdir(list_dir): + for fn in os.listdir(mm_cfg.LIST_DATA_DIR): if list_exists(fn): - # Ensure we return strings, not bytes - if isinstance(fn, bytes): - fn = fn.decode('utf-8', 'replace') got.append(fn) return got diff --git a/Mailman/UserDesc.py b/Mailman/UserDesc.py index d4536cf7..575749f5 100644 --- a/Mailman/UserDesc.py +++ b/Mailman/UserDesc.py @@ -30,8 +30,8 @@ def __init__(self, address=None, fullname=None, password=None, self.password = password if digest is not None: self.digest = digest - # Always set language, defaulting to None if not provided - self.language = lang + if lang is not None: + self.language = lang def __iadd__(self, other): if getattr(other, 'address', None) is not None: diff --git a/Mailman/Utils.py b/Mailman/Utils.py index 5c2ece82..e67a877e 100644 --- a/Mailman/Utils.py +++ b/Mailman/Utils.py @@ -31,15 +31,20 @@ import errno import base64 import random -import urllib.request, urllib.parse, urllib.error +import urllib +import urllib.request, urllib.error import html.entities import html import email.header import email.iterators -import pickle from email.errors import HeaderParseError from string import whitespace, digits -from urllib.parse import urlparse +from urllib.parse import urlparse, parse_qs +import tempfile +import io +from email.parser import BytesParser +from email.policy import HTTP + try: # Python 2.2 from string import ascii_letters @@ -48,11 +53,223 @@ _lower = 'abcdefghijklmnopqrstuvwxyz' ascii_letters = _lower + _lower.upper() + +class FieldStorage: + """ + A modern replacement for cgi.FieldStorage using urllib.parse and email libraries. + + This class provides the same interface as cgi.FieldStorage but uses + modern Python libraries instead of the deprecated cgi module. + """ + + def __init__(self, fp=None, headers=None, environ=None, + keep_blank_values=False, strict_parsing=False, + encoding='utf-8', errors='replace'): + self.keep_blank_values = keep_blank_values + self.strict_parsing = strict_parsing + self.encoding = encoding + self.errors = errors + self._data = {} + self._files = {} + + if environ is None: + environ = os.environ + + self.environ = environ + + # Get the request method + self.method = environ.get('REQUEST_METHOD', 'GET').upper() + + if self.method == 'GET': + self._parse_query_string() + elif self.method == 'POST': + self._parse_post_data() + else: + # For other methods, try to parse query string + self._parse_query_string() + + def _parse_query_string(self): + """Parse query string from GET requests or other methods.""" + query_string = self.environ.get('QUERY_STRING', '') + if query_string: + parsed = parse_qs(query_string, + keep_blank_values=self.keep_blank_values, + strict_parsing=self.strict_parsing, + encoding=self.encoding, + errors=self.errors) + self._data.update(parsed) + + def _parse_post_data(self): + """Parse POST data.""" + content_type = self.environ.get('CONTENT_TYPE', '') + + if content_type.startswith('application/x-www-form-urlencoded'): + self._parse_urlencoded_post() + elif content_type.startswith('multipart/form-data'): + self._parse_multipart_post() + else: + # Fallback to query string parsing + self._parse_query_string() + + def _parse_urlencoded_post(self): + """Parse application/x-www-form-urlencoded POST data.""" + content_length = int(self.environ.get('CONTENT_LENGTH', 0)) + if content_length > 0: + post_data = sys.stdin.buffer.read(content_length) + try: + decoded = post_data.decode(self.encoding, self.errors) + parsed = parse_qs(decoded, + keep_blank_values=self.keep_blank_values, + strict_parsing=self.strict_parsing, + encoding=self.encoding, + errors=self.errors) + self._data.update(parsed) + except (UnicodeDecodeError, ValueError): + # If decoding fails, try with different encoding + try: + decoded = post_data.decode('latin-1') + parsed = parse_qs(decoded, + keep_blank_values=self.keep_blank_values, + strict_parsing=self.strict_parsing, + encoding=self.encoding, + errors=self.errors) + self._data.update(parsed) + except (UnicodeDecodeError, ValueError): + pass + + def _parse_multipart_post(self): + """Parse multipart/form-data POST data.""" + content_length = int(self.environ.get('CONTENT_LENGTH', 0)) + if content_length > 0: + post_data = sys.stdin.buffer.read(content_length) + + # Parse the multipart message + parser = BytesParser(policy=HTTP) + msg = parser.parsebytes(post_data) + + for part in msg.walk(): + if part.get_content_maintype() == 'multipart': + continue + + # Get the field name from Content-Disposition + content_disp = part.get('Content-Disposition', '') + if not content_disp: + continue + + # Parse Content-Disposition header + disp_parts = content_disp.split(';') + field_name = None + filename = None + + for part_item in disp_parts: + part_item = part_item.strip() + if part_item.startswith('name='): + field_name = part_item[5:].strip('"') + elif part_item.startswith('filename='): + filename = part_item[9:].strip('"') + + if not field_name: + continue + + # Get the field value + field_value = part.get_payload(decode=True) + if field_value is None: + field_value = b'' + + if filename: + # This is a file upload + self._files[field_name] = { + 'filename': filename, + 'data': field_value, + 'content_type': part.get_content_type() + } + else: + # This is a regular field + try: + decoded_value = field_value.decode(self.encoding, self.errors) + except UnicodeDecodeError: + decoded_value = field_value.decode('latin-1') + + if field_name in self._data: + if isinstance(self._data[field_name], list): + self._data[field_name].append(decoded_value) + else: + self._data[field_name] = [self._data[field_name], decoded_value] + else: + self._data[field_name] = [decoded_value] + + def getfirst(self, key, default=None): + """Get the first value for the given key.""" + if key in self._data: + values = self._data[key] + if isinstance(values, list): + return values[0] if values else default + else: + return values + return default + + def getvalue(self, key, default=None): + """Get the value for the given key.""" + if key in self._data: + values = self._data[key] + if isinstance(values, list): + return values[0] if values else default + else: + return values + return default + + def getlist(self, key): + """Get all values for the given key as a list.""" + if key in self._data: + values = self._data[key] + if isinstance(values, list): + return values + else: + return [values] + return [] + + def keys(self): + """Get all field names.""" + return list(self._data.keys()) + + def has_key(self, key): + """Check if the key exists.""" + return key in self._data + + def __contains__(self, key): + """Check if the key exists.""" + return key in self._data + + def __getitem__(self, key): + """Get the value for the given key.""" + return self.getvalue(key) + + def __iter__(self): + """Iterate over field names.""" + return iter(self._data.keys()) + + def file(self, key): + """Get file data for the given key.""" + if key in self._files: + file_info = self._files[key] + # Create a file-like object + temp_file = tempfile.NamedTemporaryFile(delete=False) + temp_file.write(file_info['data']) + temp_file.flush() + return temp_file + return None + + def filename(self, key): + """Get the filename for the given key.""" + if key in self._files: + return self._files[key]['filename'] + return None + from Mailman import mm_cfg from Mailman import Errors from Mailman import Site from Mailman.SafeDict import SafeDict -from Mailman.Logging.Syslog import mailman_log +from Mailman.Logging.Syslog import syslog try: import hashlib @@ -91,6 +308,7 @@ dre = re.compile(r'(\${2})|\$([_a-z]\w*)|\${([_a-z]\w*)}', re.IGNORECASE) + def list_exists(listname): """Return true iff list `listname' exists.""" # The existance of any of the following file proves the list exists @@ -101,12 +319,12 @@ def list_exists(listname): # # But first ensure the list name doesn't contain a path traversal # attack. - if len(re.sub(mm_cfg.ACCEPTABLE_LISTNAME_CHARACTERS, '', listname, flags=re.IGNORECASE)) > 0: + if len(re.sub(mm_cfg.ACCEPTABLE_LISTNAME_CHARACTERS, '', listname)) > 0: remote = os.environ.get('HTTP_FORWARDED_FOR', os.environ.get('HTTP_X_FORWARDED_FOR', os.environ.get('REMOTE_ADDR', 'unidentified origin'))) - mailman_log('mischief', + syslog('mischief', 'Hostile listname: listname=%s: remote=%s', listname, remote) return False basepath = Site.get_listpath(listname) @@ -120,20 +338,17 @@ def list_exists(listname): def list_names(): """Return the names of all lists in default list directory.""" # We don't currently support separate listings of virtual domains - # Ensure LIST_DATA_DIR is a string - list_dir = mm_cfg.LIST_DATA_DIR - if isinstance(list_dir, bytes): - list_dir = list_dir.decode('utf-8', 'replace') - names = [] - for name in os.listdir(list_dir): - if list_exists(name): - # Ensure we return strings, not bytes - if isinstance(name, bytes): - name = name.decode('utf-8', 'replace') - names.append(name) - return names + return Site.get_listnames() + +def needs_unicode_escape_decode(s): + # Check for Unicode escape patterns (\uXXXX or \UXXXXXXXX) + unicode_escape_pattern = re.compile(r'\\u[0-9a-fA-F]{4}|\\U[0-9a-fA-F]{8}') + return bool(unicode_escape_pattern.search(s)) + + +# a much more naive implementation than say, Emacs's fill-paragraph! def wrap(text, column=70, honor_leading_ws=True): """Wrap and fill the text to the specified column. @@ -147,7 +362,7 @@ def wrap(text, column=70, honor_leading_ws=True): """ wrapped = '' # first split the text into paragraphs, defined as a blank line - paras = re.split(r'\n\n', text) + paras = re.split('\n\n', text) for para in paras: # fill lines = [] @@ -208,6 +423,7 @@ def wrap(text, column=70, honor_leading_ws=True): return wrapped[:-2] + def QuotePeriods(text): JOINER = '\n .\n' SEP = '\n.\n' @@ -229,13 +445,11 @@ def ParseEmail(email): def LCDomain(addr): - """Convert an email address to lowercase, preserving the domain part.""" - if isinstance(addr, str): - at = addr.find('@') - if at == -1: - return addr.lower() - return addr[:at].lower() + addr[at:] - return addr + "returns the address with the domain part lowercased" + atind = addr.find('@') + if atind == -1: # no domain part + return addr + return addr[:atind] + '@' + addr[atind+1:].lower() # TBD: what other characters should be disallowed? @@ -246,34 +460,34 @@ def LCDomain(addr): _valid_domain = re.compile('[-a-z0-9]', re.IGNORECASE) def ValidateEmail(s): - """Validate an email address. - - This is used to validate email addresses entered by users. It is more - strict than RFC 822, but less strict than RFC 2822. In particular, it - does not allow local, unqualified addresses, and requires at least one - domain part. It also disallows various characters that are known to - cause problems in various contexts. - - Returns None if the address is valid, raises an exception otherwise. - """ - if not s: - raise Exception(Errors.MMBadEmailError, s) + """Verify that an email address isn't grossly evil.""" + # If a user submits a form or URL with post data or query fragments + # with multiple occurrences of the same variable, we can get a list + # here. Be as careful as possible. + if isinstance(s, list) or isinstance(s, tuple): + if len(s) == 0: + s = '' + else: + s = s[-1] + # Pretty minimal, cheesy check. We could do better... + if not s or s.count(' ') > 0: + raise Errors.MMBadEmailError if _badchars.search(s): - raise Exception(Errors.MMHostileAddress, s) + raise Errors.MMHostileAddress(s) user, domain_parts = ParseEmail(s) # This means local, unqualified addresses, are not allowed if not domain_parts: - raise Exception(Errors.MMBadEmailError, s) - # Allow single-part domains for internal use - if len(domain_parts) < 1: - raise Exception(Errors.MMBadEmailError, s) + raise Errors.MMBadEmailError(s) + if len(domain_parts) < 2: + raise Errors.MMBadEmailError(s) # domain parts may only contain ascii letters, digits and hyphen # and must not begin with hyphen. for p in domain_parts: if len(p) == 0 or p[0] == '-' or len(_valid_domain.sub('', p)) > 0: - raise Exception(Errors.MMHostileAddress, s) + raise Errors.MMHostileAddress(s) + # Patterns which may be used to form malicious path to inject a new # line in the mailman error log. (TK: advisory by Moritz Naumann) CRNLpat = re.compile(r'[^\x21-\x7e]') @@ -287,14 +501,12 @@ def GetPathPieces(envar='PATH_INFO'): 'unidentified origin'))) if CRNLpat.search(path): path = CRNLpat.split(path)[0] - mailman_log('error', + syslog('error', 'Warning: Possible malformed path attack domain=%s remote=%s', get_domain(), remote) # Check for listname injections that won't be websafed. pieces = [p for p in path.split('/') if p] - # Ensure all pieces are Python 3 strings - pieces = [str(p) if not isinstance(p, str) else p for p in pieces] # Get the longest listname or 20 if none or use MAX_LISTNAME_LENGTH if # provided > 0. if mm_cfg.MAX_LISTNAME_LENGTH > 0: @@ -306,17 +518,19 @@ def GetPathPieces(envar='PATH_INFO'): else: longest = 20 if pieces and len(pieces[0]) > longest: - mailman_log('mischief', + syslog('mischief', 'Hostile listname: listname=%s: remote=%s', pieces[0], remote) pieces[0] = pieces[0][:longest] + '...' return pieces return None + def GetRequestMethod(): return os.environ.get('REQUEST_METHOD') + def ScriptURL(target, web_page_url=None, absolute=False): """target - scriptname only, nothing extra web_page_url - the list's configvar of the same name @@ -345,6 +559,7 @@ def ScriptURL(target, web_page_url=None, absolute=False): return path + mm_cfg.CGIEXT + def GetPossibleMatchingAddrs(name): """returns a sorted list of addresses that could possibly match a given name. @@ -364,6 +579,7 @@ def GetPossibleMatchingAddrs(name): return res + def List2Dict(L, foldcase=False): """Return a dict keyed by the entries in the list passed to it.""" d = {} @@ -376,6 +592,7 @@ def List2Dict(L, foldcase=False): return d + _vowels = ('a', 'e', 'i', 'o', 'u') _consonants = ('b', 'c', 'd', 'f', 'g', 'h', 'k', 'm', 'n', 'p', 'r', 's', 't', 'v', 'w', 'x', 'z') @@ -413,7 +630,7 @@ def Secure_MakeRandomPassword(length): # We have no available source of cryptographically # secure random characters. Log an error and fallback # to the user friendly passwords. - mailman_log('error', + syslog('error', 'urandom not available, passwords not secure') return UserFriendly_MakeRandomPassword(length) newbytes = os.read(fd, length - bytesread) @@ -446,6 +663,7 @@ def mkletter(c): return "%c%c" % tuple(map(mkletter, (chr1, chr2))) + def set_global_password(pw, siteadmin=True): if siteadmin: filename = mm_cfg.SITE_PW_FILE @@ -454,14 +672,12 @@ def set_global_password(pw, siteadmin=True): # rw-r----- omask = os.umask(0o026) try: - # Use atomic write to prevent race conditions - temp_filename = filename + '.tmp' - with open(temp_filename, 'w') as fp: + fp = open(filename, 'w') + if isinstance(pw, bytes): fp.write(sha_new(pw).hexdigest() + '\n') - os.rename(temp_filename, filename) - except (IOError, OSError) as e: - mailman_log('error', 'Failed to write password file %s: %s', filename, str(e)) - raise + else: + fp.write(sha_new(pw.encode()).hexdigest() + '\n') + fp.close() finally: os.umask(omask) @@ -472,86 +688,52 @@ def get_global_password(siteadmin=True): else: filename = mm_cfg.LISTCREATOR_PW_FILE try: - with open(filename) as fp: - challenge = fp.read()[:-1] # strip off trailing nl - if not challenge: - mailman_log('error', 'Empty password file: %s', filename) - return None - return challenge + fp = open(filename) + challenge = fp.read()[:-1] # strip off trailing nl + fp.close() except IOError as e: - if e.errno != errno.ENOENT: - mailman_log('error', 'Error reading password file %s: %s', filename, str(e)) + if e.errno != errno.ENOENT: raise + # It's okay not to have a site admin password, just return false return None + return challenge def check_global_password(response, siteadmin=True): challenge = get_global_password(siteadmin) if challenge is None: return None - # Log the hashes for debugging - computed_hash = sha_new(response).hexdigest() - mailman_log('debug', 'Password check - stored hash: %s, computed hash: %s', - challenge, computed_hash) - return challenge == computed_hash + if isinstance(response, bytes): + return challenge == sha_new(response).hexdigest() + else: + return challenge == sha_new(response.encode()).hexdigest() + _ampre = re.compile('&((?:#[0-9]+|[a-z]+);)', re.IGNORECASE) def websafe(s, doubleescape=False): - """Convert a string to be safe for HTML output. - - This function handles: - - Lists/tuples (takes last element) - - Browser workarounds - - Double escaping - - Bytes decoding (including Python 2 style bytes) - - HTML escaping - """ - if isinstance(s, (list, tuple)): - s = s[-1] if s else '' - - if mm_cfg.BROKEN_BROWSER_WORKAROUND and isinstance(s, str): - for k in mm_cfg.BROKEN_BROWSER_REPLACEMENTS: - s = s.replace(k, mm_cfg.BROKEN_BROWSER_REPLACEMENTS[k]) - - if isinstance(s, bytes): - # First try to detect if this is a Python 2 style bytes file - # by checking for common Python 2 encodings - try: - # Try ASCII first as it's the most common Python 2 default - s = s.decode('ascii', errors='strict') - except UnicodeDecodeError: - try: - # Try UTF-8 next as it's common in Python 2 files - s = s.decode('utf-8', errors='strict') - except UnicodeDecodeError: - try: - # Try ISO-8859-1 (latin1) which was common in Python 2 - s = s.decode('iso-8859-1', errors='strict') - except UnicodeDecodeError: - # As a last resort, try to detect the encoding - try: - import chardet - result = chardet.detect(s) - if result['confidence'] > 0.8: - s = s.decode(result['encoding'], errors='strict') - else: - # If we can't detect with confidence, fall back to latin1 - s = s.decode('latin1', errors='replace') - except (ImportError, UnicodeDecodeError): - # If all else fails, use replace to avoid errors - s = s.decode('latin1', errors='replace') - - # First escape & to & to prevent double escaping issues - s = s.replace('&', '&') - - # Then use html.escape for the rest - s = html.escape(s, quote=True) - - # If double escaping is requested, escape again + # If a user submits a form or URL with post data or query fragments + # with multiple occurrences of the same variable, we can get a list + # here. Be as careful as possible. + if isinstance(s, list) or isinstance(s, tuple): + if len(s) == 0: + s = '' + else: + s = s[-1] + if mm_cfg.BROKEN_BROWSER_WORKAROUND: + # Archiver can pass unicode here. Just skip them as the + # archiver escapes non-ascii anyway. + if isinstance(s, str): + for k in mm_cfg.BROKEN_BROWSER_REPLACEMENTS: + s = s.replace(k, mm_cfg.BROKEN_BROWSER_REPLACEMENTS[k]) if doubleescape: - s = html.escape(s, quote=True) - - return s + return html.escape(s, quote=True) + else: + if type(s) is bytes: + s = s.decode(errors='ignore') + re.sub('&', '&', s) + # Don't double escape html entities + #return _ampre.sub(r'&\1', html.escape(s, quote=True)) + return html.escape(s, quote=True) def nntpsplit(s): @@ -565,6 +747,7 @@ def nntpsplit(s): return s, 119 + # Just changing these two functions should be enough to control the way # that email address obscuring is handled. def ObscureEmail(addr, for_text=False): @@ -584,125 +767,149 @@ def UnobscureEmail(addr): return addr.replace('--at--', '@') + class OuterExit(Exception): pass -def findtext(templatefile, dict=None, raw=0, lang=None, mlist=None): - """Find the template file and return its contents and path. +def findtext(templatefile, dict=None, raw=False, lang=None, mlist=None): + # Make some text from a template file. The order of searches depends on + # whether mlist and lang are provided. Once the templatefile is found, + # string substitution is performed by interpolation in `dict'. If `raw' + # is false, the resulting text is wrapped/filled by calling wrap(). + # + # When looking for a template in a specific language, there are 4 places + # that are searched, in this order: + # + # 1. the list-specific language directory + # lists// + # + # 2. the domain-specific language directory + # templates// + # + # 3. the site-wide language directory + # templates/site/ + # + # 4. the global default language directory + # templates/ + # + # The first match found stops the search. In this way, you can specialize + # templates at the desired level, or, if you use only the default + # templates, you don't need to change anything. You should never modify + # files in the templates/ subdirectory, since Mailman will + # overwrite these when you upgrade. That's what the templates/site + # language directories are for. + # + # A further complication is that the language to search for is determined + # by both the `lang' and `mlist' arguments. The search order there is + # that if lang is given, then the 4 locations above are searched, + # substituting lang for . If no match is found, and mlist is + # given, then the 4 locations are searched using the list's preferred + # language. After that, the server default language is used for + # . If that still doesn't yield a template, then the standard + # distribution's English language template is used as an ultimate + # fallback. If that's missing you've got big problems. ;) + # + # A word on backwards compatibility: Mailman versions prior to 2.1 stored + # templates in templates/*.{html,txt} and lists//*.{html,txt}. + # Those directories are no longer searched so if you've got customizations + # in those files, you should move them to the appropriate directory based + # on the above description. Mailman's upgrade script cannot do this for + # you. + # + # The function has been revised and renamed as it now returns both the + # template text and the path from which it retrieved the template. The + # original function is now a wrapper which just returns the template text + # as before, by calling this renamed function and discarding the second + # item returned. + # + # Calculate the languages to scan + languages = [] + if lang is not None: + languages.append(lang) + if mlist is not None: + languages.append(mlist.preferred_language) + languages.append(mm_cfg.DEFAULT_SERVER_LANGUAGE) + # Calculate the locations to scan + searchdirs = [] + if mlist is not None: + searchdirs.append(mlist.fullpath()) + searchdirs.append(os.path.join(mm_cfg.TEMPLATE_DIR, mlist.host_name)) + searchdirs.append(os.path.join(mm_cfg.TEMPLATE_DIR, 'site')) + searchdirs.append(mm_cfg.TEMPLATE_DIR) + # Start scanning + fp = None + try: + for lang in languages: + for dir in searchdirs: + filename = os.path.join(dir, lang, templatefile) + try: + fp = open(filename) + raise OuterExit + except IOError as e: + if e.errno != errno.ENOENT: raise + # Okay, it doesn't exist, keep looping + fp = None + except OuterExit: + pass + if fp is None: + # Try one last time with the distro English template, which, unless + # you've got a really broken installation, must be there. + try: + filename = os.path.join(mm_cfg.TEMPLATE_DIR, 'en', templatefile) + fp = open(filename) + except IOError as e: + if e.errno != errno.ENOENT: raise + # We never found the template. BAD! + raise IOError(errno.ENOENT, 'No template file found', templatefile) + try: + template = fp.read() + except UnicodeDecodeError as e: + # failed to read the template as utf-8, so lets determine the current encoding + # then save the file back to disk as utf-8. + filename = fp.name + fp.close() + + current_encoding = get_current_encoding(filename) - The template file is searched for in the following order: - 1. In the list's language-specific template directory - 2. In the site's language-specific template directory - 3. In the list's default template directory - 4. In the site's default template directory + with open(filename, 'rb') as f: + raw = f.read() - If the template is found, returns a 2-tuple of (text, path) where text is - the contents of the file and path is the absolute path to the file. - Otherwise returns (None, None). - """ - if dict is None: - dict = {} - # If lang is None, use the default language from mm_cfg - if lang is None: - lang = mm_cfg.DEFAULT_SERVER_LANGUAGE + decoded_template = raw.decode(current_encoding) + + with open(filename, 'w', encoding='utf-8') as f: + f.write(decoded_template) - def read_template_file(path): + template = decoded_template + except Exception as e: + # catch any other non-unicode exceptions... + syslog('error', 'Failed to read template %s: %s', fp.name, e) + finally: + fp.close() + + text = template + if dict is not None: try: - with open(path, 'rb') as fp: - raw_bytes = fp.read() - # First try UTF-8 since that's the most common encoding - try: - text = raw_bytes.decode('utf-8') - return text, path - except UnicodeDecodeError: - # If UTF-8 fails, try other encodings - for encoding in ['euc-jp', 'iso-8859-1', 'latin1']: - try: - text = raw_bytes.decode(encoding) - return text, path - except UnicodeDecodeError: - continue - # If all encodings fail, use UTF-8 with replacement - return raw_bytes.decode('utf-8', 'replace'), path - except IOError: - return None, None - - # First try the list's language-specific template directory - if lang and mlist: - path = os.path.join(mlist.fullpath(), 'templates', lang, templatefile) - if os.path.exists(path): - result = read_template_file(path) - if result[0] is not None: - return result - - # Then try the site's language-specific template directory - if lang: - path = os.path.join(mm_cfg.TEMPLATE_DIR, lang, templatefile) - if os.path.exists(path): - result = read_template_file(path) - if result[0] is not None: - return result - - # Then try the list's default template directory - if mlist: - path = os.path.join(mlist.fullpath(), 'templates', templatefile) - if os.path.exists(path): - result = read_template_file(path) - if result[0] is not None: - return result - - # Finally try the site's default template directory - path = os.path.join(mm_cfg.TEMPLATE_DIR, templatefile) - if os.path.exists(path): - result = read_template_file(path) - if result[0] is not None: - return result - - return None, None - - -def maketext(templatefile, dict=None, raw=0, lang=None, mlist=None): - """Make text from a template file. - - Use this function to create text from the template file. If dict is - provided, use it as the substitution mapping. If mlist is provided use it - as the source for the substitution. If both dict and mlist are provided, - dict values take precedence. lang is the language code to find the - template in. If raw is true, no substitution will be done on the text. - """ - template, path = findtext(templatefile, dict, raw, lang, mlist) - if template is None: - # Log all paths that were searched - paths = [] - if lang and mlist: - paths.append(os.path.join(mlist.fullpath(), 'templates', lang, templatefile)) - if lang: - paths.append(os.path.join(mm_cfg.TEMPLATE_DIR, lang, templatefile)) - if mlist: - paths.append(os.path.join(mlist.fullpath(), 'templates', templatefile)) - paths.append(os.path.join(mm_cfg.TEMPLATE_DIR, templatefile)) - mailman_log('error', 'Template file not found: %s (language: %s). Searched paths: %s', - templatefile, lang or 'default', ', '.join(paths)) - return '' # Return empty string instead of None + sdict = SafeDict(dict) + try: + text = sdict.interpolate(template) + except UnicodeError: + # Try again after coercing the template to unicode + utemplate = str(template, GetCharSet(lang), 'replace') + text = sdict.interpolate(utemplate) + except (TypeError, ValueError) as e: + # The template is really screwed up + syslog('error', 'broken template: %s\n%s', filename, e) + pass if raw: - return template - # Make the text from the template - if dict is None: - dict = SafeDict() - if mlist: - dict.update(mlist.__dict__) - # Remove leading whitespace - if isinstance(template, str): - template = '\n'.join([line.lstrip() for line in template.splitlines()]) - try: - text = template % dict - except (ValueError, TypeError) as e: - mailman_log('error', 'Template interpolation error for %s: %s', - templatefile, str(e)) - return '' # Return empty string instead of None - return text + return text, filename + return wrap(text), filename + +def maketext(templatefile, dict=None, raw=False, lang=None, mlist=None): + return findtext(templatefile, dict, raw, lang, mlist)[0] + + ADMINDATA = { # admin keyword: (minimum #args, maximum #args) 'confirm': (1, 1), @@ -718,15 +925,10 @@ def maketext(templatefile, dict=None, raw=0, lang=None, mlist=None): 'who': (0, 1), } -# Given a Message object, test for administrivia (eg subscribe, +# Given a Message.Message object, test for administrivia (eg subscribe, # unsubscribe, etc). The test must be a good guess -- messages that return # true get sent to the list admin instead of the entire list. def is_administrivia(msg): - """Return true if the message is administrative in nature.""" - # Not imported at module scope to avoid import loop - from Mailman.Message import Message - if not isinstance(msg, Message): - return False linecnt = 0 lines = [] for line in email.iterators.body_line_iterator(msg): @@ -764,6 +966,7 @@ def is_administrivia(msg): return False + def GetRequestURI(fallback=None, escape=True): """Return the full virtual path this CGI script was invoked with. @@ -788,6 +991,7 @@ def GetRequestURI(fallback=None, escape=True): return url + # Wait on a dictionary of child pids def reap(kids, func=None, once=False): while kids: @@ -810,7 +1014,7 @@ def reap(kids, func=None, once=False): if once: break - + def GetLanguageDescr(lang): return mm_cfg.LC_DESCRIPTIONS[lang][0] @@ -825,6 +1029,7 @@ def IsLanguage(lang): return lang in mm_cfg.LC_DESCRIPTIONS + def get_domain(): host = os.environ.get('HTTP_HOST', os.environ.get('SERVER_NAME')) port = os.environ.get('SERVER_PORT') @@ -850,6 +1055,7 @@ def get_site_email(hostname=None, extra=None): return '%s-%s@%s' % (mm_cfg.MAILMAN_SITE_LIST, extra, hostname) + # This algorithm crafts a guaranteed unique message-id. The theory here is # that pid+listname+host will distinguish the message-id for every process on # the system, except when process ids wrap around. To further distinguish @@ -876,6 +1082,7 @@ def midnight(date=None): return time.mktime(date + (0,)*5 + (-1,)) + # Utilities to convert from simplified $identifier substitutions to/from # standard Python $(identifier)s substititions. The "Guido rules" for the # former are: @@ -885,6 +1092,8 @@ def midnight(date=None): def to_dollar(s): """Convert from %-strings to $-strings.""" + if isinstance(s, bytes): + s = s.decode() s = s.replace('$', '$$').replace('%%', '%') parts = cre.split(s) for i in range(1, len(parts), 2): @@ -920,11 +1129,14 @@ def dollar_identifiers(s): def percent_identifiers(s): """Return the set (dictionary) of identifiers found in a %-string.""" d = {} + if isinstance(s, bytes): + s = s.decode() for name in cre.findall(s): d[name] = True return d + # Utilities to canonicalize a string, which means un-HTML-ifying the string to # produce a Unicode string or an 8-bit string if all the characters are ASCII. def canonstr(s, lang=None): @@ -1011,9 +1223,8 @@ def oneline(s, cset): # Decode header string in one line and convert into specified charset try: h = email.header.make_header(email.header.decode_header(s)) - ustr = str(h) - line = UEMPTYSTRING.join(ustr.splitlines()) - return line.encode(cset, 'replace') + ustr = h.__str__() + return UEMPTYSTRING.join(ustr.splitlines()) except (LookupError, UnicodeError, ValueError, HeaderParseError): # possibly charset problem. return with undecoded string in one line. return EMPTYSTRING.join(s.splitlines()) @@ -1052,7 +1263,7 @@ def strip_verbose_pattern(pattern): elif c == ']' and inclass: inclass = False newpattern += c - elif re.search(r'\s', c, re.IGNORECASE): + elif re.search(r'\s', c): if inclass: if c == NL: newpattern += '\\n' @@ -1259,17 +1470,25 @@ def suspiciousHTML(html): s_dict = {} def get_suffixes(url): - """Get the list of public suffixes from the given URL.""" + """This loads and parses the data from the url argument into s_dict for + use by get_org_dom.""" + global s_dict + if s_dict: + return + if not url: + return try: d = urllib.request.urlopen(url) - except (urllib.error.URLError, urllib.error.HTTPError) as e: - mailman_log('error', 'Failed to fetch DMARC organizational domain data from %s: %s', + except urllib.error.URLError as e: + syslog('error', + 'Unable to retrieve data from %s: %s', url, e) return for line in d.readlines(): - # Convert bytes to string if necessary + if not line: + continue if isinstance(line, bytes): - line = line.decode('utf-8') + line = line.decode() if not line.strip() or line.startswith(' ') or line.startswith('//'): continue line = re.sub(' .*', '', line.strip()) @@ -1327,7 +1546,7 @@ def get_org_dom(domain): def IsDMARCProhibited(mlist, email): if not dns_resolver: # This is a problem; log it. - mailman_log('error', + syslog('error', 'DNS lookup for dmarc_moderation_action for list %s not available', mlist.real_name) return False @@ -1348,30 +1567,112 @@ def IsDMARCProhibited(mlist, email): return x return False -def _DMARCProhibited(mlist, email, domain): - """Check if the domain has a DMARC policy that prohibits sending. - """ - try: - import dns.resolver - import dns.exception - except ImportError: - return False +def _DMARCProhibited(mlist, email, dmarc_domain, org=False): + try: - txt_rec = dns.resolver.resolve(domain, 'TXT') - # Newer versions of dnspython use strings property instead of strings attribute - txt_strings = txt_rec.strings if hasattr(txt_rec, 'strings') else [str(r) for r in txt_rec] - for txt in txt_strings: - if txt.startswith('v=DMARC1'): - # Parse the DMARC record - parts = txt.split(';') - for part in parts: - part = part.strip() - if part.startswith('p='): - policy = part[2:].lower() - if policy in ('reject', 'quarantine'): - return True - except (dns.exception.DNSException, AttributeError): - pass + resolver = dns.resolver.Resolver() + resolver.timeout = float(mm_cfg.DMARC_RESOLVER_TIMEOUT) + resolver.lifetime = float(mm_cfg.DMARC_RESOLVER_LIFETIME) + txt_recs = resolver.query(dmarc_domain, dns.rdatatype.TXT) + except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): + return 'continue' + except (dns.resolver.NoNameservers): + syslog('error', + 'DNSException: No Nameservers available for %s (%s)', + email, dmarc_domain) + # Typically this means a dnssec validation error. Clients that don't + # perform validation *may* successfully see a _dmarc RR whereas a + # validating mailman server won't see the _dmarc RR. We should + # mitigate this email to be safe. + return True + except DNSException as e: + syslog('error', + 'DNSException: Unable to query DMARC policy for %s (%s). %s', + email, dmarc_domain, e.__doc__) + # While we can't be sure what caused the error, there is potentially + # a DMARC policy record that we missed and that a receiver of the mail + # might see. Thus, we should err on the side of caution and mitigate. + return True + else: + # Be as robust as possible in parsing the result. + results_by_name = {} + cnames = {} + want_names = set([dmarc_domain + '.']) + for txt_rec in txt_recs.response.answer: + if not isinstance(txt_rec.items, list): + continue + if not txt_rec.items[0]: + continue + # Don't be fooled by an answer with uppercase in the name. + name = txt_rec.name.to_text().lower() + if txt_rec.rdtype == dns.rdatatype.CNAME: + cnames[name] = ( + txt_rec.items[0].target.to_text()) + if txt_rec.rdtype != dns.rdatatype.TXT: + continue + results_by_name.setdefault(name, []).append( + "".join( [ record.decode() if isinstance(record, bytes) else record for record in txt_rec.items[0].strings ] )) + expands = list(want_names) + seen = set(expands) + while expands: + item = expands.pop(0) + if item in cnames: + if cnames[item] in seen: + continue # cname loop + expands.append(cnames[item]) + seen.add(cnames[item]) + want_names.add(cnames[item]) + want_names.discard(item) + + if len(want_names) != 1: + syslog('error', + """multiple DMARC entries in results for %s, + processing each to be strict""", + dmarc_domain) + for name in want_names: + if name not in results_by_name: + continue + dmarcs = [n for n in results_by_name[name] if n.startswith('v=DMARC1;')] + if len(dmarcs) == 0: + return 'continue' + if len(dmarcs) > 1: + syslog('error', + """RRset of TXT records for %s has %d v=DMARC1 entries; + ignoring them per RFC 7849""", + dmarc_domain, len(dmarcs)) + return False + for entry in dmarcs: + mo = re.search(r'\bsp=(\w*)\b', entry, re.IGNORECASE) + if org and mo: + policy = mo.group(1).lower() + else: + mo = re.search(r'\bp=(\w*)\b', entry, re.IGNORECASE) + if mo: + policy = mo.group(1).lower() + else: + continue + if policy == 'reject': + syslog('vette', + '%s: DMARC lookup for %s (%s) found p=reject in %s = %s', + mlist.real_name, email, dmarc_domain, name, entry) + return True + + if (mlist.dmarc_quarantine_moderation_action and + policy == 'quarantine'): + syslog('vette', + '%s: DMARC lookup for %s (%s) found p=quarantine in %s = %s', + mlist.real_name, email, dmarc_domain, name, entry) + return True + + if (mlist.dmarc_none_moderation_action and + mlist.dmarc_quarantine_moderation_action and + mlist.dmarc_moderation_action in (1, 2) and + policy == 'none'): + syslog('vette', + '%s: DMARC lookup for %s (%s) found p=none in %s = %s', + mlist.real_name, email, dmarc_domain, name, entry) + return True + return False @@ -1382,41 +1683,44 @@ def _DMARCProhibited(mlist, email, domain): clean_count = 0 def IsVerboseMember(mlist, email): """For lists that request it, we keep track of recent posts by address. - A message from an address to a list, if the list requests it, is remembered - for a specified time whether or not the address is a list member, and if the - address is a member and the member is over the threshold for the list, that - fact is returned.""" - global clean_count, recentMemberPostings +A message from an address to a list, if the list requests it, is remembered +for a specified time whether or not the address is a list member, and if the +address is a member and the member is over the threshold for the list, that +fact is returned.""" + + global clean_count if mlist.member_verbosity_threshold == 0: return False email = email.lower() + now = time.time() + recentMemberPostings.setdefault(email,[]).append(now + + float(mlist.member_verbosity_interval) + ) + x = list(range(len(recentMemberPostings[email]))) + x.reverse() + for i in x: + if recentMemberPostings[email][i] < now: + del recentMemberPostings[email][i] - # Clean up old entries periodically clean_count += 1 if clean_count >= mm_cfg.VERBOSE_CLEAN_LIMIT: clean_count = 0 - # Remove entries older than the maximum verbosity interval - max_age = max(mlist.member_verbosity_interval for mlist in mm_cfg.LISTS.values()) - cutoff = now - max_age - recentMemberPostings = { - addr: [t for t in times if t > cutoff] - for addr, times in recentMemberPostings.items() - if any(t > cutoff for t in times) - } - - # Add new posting time - recentMemberPostings.setdefault(email, []).append(now + float(mlist.member_verbosity_interval)) - - # Remove old times for this email - recentMemberPostings[email] = [t for t in recentMemberPostings[email] if t > now] - + for addr in list(recentMemberPostings.keys()): + x = list(range(len(recentMemberPostings[addr]))) + x.reverse() + for i in x: + if recentMemberPostings[addr][i] < now: + del recentMemberPostings[addr][i] + if not recentMemberPostings[addr]: + del recentMemberPostings[addr] if not mlist.isMember(email): return False - - return len(recentMemberPostings.get(email, [])) > mlist.member_verbosity_threshold + return (len(recentMemberPostings.get(email, [])) > + mlist.member_verbosity_threshold + ) def check_eq_domains(email, domains_list): @@ -1442,7 +1746,7 @@ def check_eq_domains(email, domains_list): except ValueError: return [] domain = domain.lower() - domains_list = re.sub(r'\s', '', domains_list, flags=re.IGNORECASE).lower() + domains_list = re.sub(r'\s', '', domains_list).lower() domains = domains_list.split(';') domains_list = [] for d in domains: @@ -1477,102 +1781,43 @@ def xml_to_unicode(s, cset): """ if isinstance(s, bytes): us = s.decode(cset, 'replace') - us = re.sub(r'&(#[0-9]+);', _invert_xml, us, flags=re.IGNORECASE) - us = re.sub(r'(?i)\\\\(u[a-f0-9]{4})', _invert_xml, us, flags=re.IGNORECASE) + us = re.sub(u'&(#[0-9]+);', _invert_xml, us) + us = re.sub(u'(?i)\\\\(u[a-f0-9]{4})', _invert_xml, us) return us else: return s def banned_ip(ip): - """Check if an IP address is in the Spamhaus blocklist. - - Supports both IPv4 and IPv6 addresses. - Returns True if the IP is in the blocklist, False otherwise. - """ if not dns_resolver: return False - - try: - if isinstance(ip, bytes): - ip = ip.decode('us-ascii', errors='replace') - - if have_ipaddress: - try: - ip_obj = ipaddress.ip_address(ip) - if isinstance(ip_obj, ipaddress.IPv4Address): - # IPv4 format: 1.2.3.4 -> 4.3.2.1.zen.spamhaus.org - parts = str(ip_obj).split('.') - lookup = '{0}.{1}.{2}.{3}.zen.spamhaus.org'.format( - parts[3], parts[2], parts[1], parts[0]) - else: - # IPv6 format: 2001:db8::1 -> 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.zen.spamhaus.org - # Convert to reverse nibble format - expanded = ip_obj.exploded.replace(':', '') - lookup = '.'.join(reversed(expanded)) + '.zen.spamhaus.org' - except ValueError: - return False - else: - # Fallback for systems without ipaddress module - if ':' in ip: - # IPv6 address - try: - # Basic IPv6 validation and conversion - parts = ip.split(':') - if len(parts) > 8: - return False - # Pad with zeros - expanded = ''.join(part.zfill(4) for part in parts) - lookup = '.'.join(reversed(expanded)) + '.zen.spamhaus.org' - except (ValueError, IndexError): - return False - else: - # IPv4 address - parts = ip.split('.') - if len(parts) != 4: - return False - try: - if not all(0 <= int(part) <= 255 for part in parts): - return False - lookup = '{0}.{1}.{2}.{3}.zen.spamhaus.org'.format( - parts[3], parts[2], parts[1], parts[0]) - except ValueError: - return False - - # Set DNS resolver timeouts to prevent DoS - resolver = dns.resolver.Resolver() - resolver.timeout = 2.0 # 2 second timeout - resolver.lifetime = 4.0 # 4 second total lifetime - + if have_ipaddress: try: - # Check for blocklist response - answers = resolver.resolve(lookup, 'A') - for rdata in answers: - if str(rdata).startswith('127.0.0.'): - return True - except dns.resolver.NXDOMAIN: - # IP not found in blocklist - return False - except dns.resolver.Timeout: - mailman_log('error', 'DNS timeout checking IP %s in Spamhaus', ip) - return False - except dns.resolver.NoAnswer: - mailman_log('error', 'No DNS answer for IP %s in Spamhaus', ip) + uip = str(ip, encoding='us-ascii', errors='replace') + ptr = ipaddress.ip_address(uip).reverse_pointer + except ValueError: return False - except dns.exception.DNSException as e: - mailman_log('error', 'DNS error checking IP %s in Spamhaus: %s', ip, str(e)) + lookup = '{0}.zen.spamhaus.org'.format('.'.join(ptr.split('.')[:-2])) + else: + parts = ip.split('.') + if len(parts) != 4: return False - - except Exception as e: - mailman_log('error', 'Error checking IP %s in Spamhaus: %s', ip, str(e)) + lookup = '{0}.{1}.{2}.{3}.zen.spamhaus.org'.format(parts[3], + parts[2], + parts[1], + parts[0]) + resolver = dns.resolver.Resolver() + try: + ans = resolver.query(lookup, dns.rdatatype.A) + except DNSException: return False - + if not ans: + return False + text = ans.rrset.to_text() + if re.search(r'127\.0\.0\.[2-7]$', text, re.MULTILINE): + return True return False def banned_domain(email): - """Check if a domain is in the Spamhaus Domain Block List (DBL). - - Returns True if the domain is in the blocklist, False otherwise. - """ if not dns_resolver: return False @@ -1581,37 +1826,17 @@ def banned_domain(email): lookup = '%s.dbl.spamhaus.org' % (domain) - # Set DNS resolver timeouts to prevent DoS resolver = dns.resolver.Resolver() - resolver.timeout = 2.0 # 2 second timeout - resolver.lifetime = 4.0 # 4 second total lifetime - try: - # Use resolve() instead of query() - ans = resolver.resolve(lookup, 'A') - if not ans: - return False - # Newer versions of dnspython use strings property instead of strings attribute - text = ans.rrset.to_text() if hasattr(ans, 'rrset') else str(ans) - if re.search(r'127\.0\.1\.\d{1,3}$', text, re.MULTILINE | re.IGNORECASE): - if not re.search(r'127\.0\.1\.255$', text, re.MULTILINE | re.IGNORECASE): - return True - except dns.resolver.NXDOMAIN: - # Domain not found in blocklist - return False - except dns.resolver.Timeout: - mailman_log('error', 'DNS timeout checking domain %s in Spamhaus DBL', domain) + ans = resolver.query(lookup, dns.rdatatype.A) + except DNSException: return False - except dns.resolver.NoAnswer: - mailman_log('error', 'No DNS answer for domain %s in Spamhaus DBL', domain) - return False - except dns.exception.DNSException as e: - mailman_log('error', 'DNS error checking domain %s in Spamhaus DBL: %s', domain, str(e)) - return False - except Exception as e: - mailman_log('error', 'Unexpected error checking domain %s in Spamhaus DBL: %s', domain, str(e)) + if not ans: return False - + text = ans.rrset.to_text() + if re.search(r'127\.0\.1\.\d{1,3}$', text, re.MULTILINE): + if not re.search(r'127\.0\.1\.255$', text, re.MULTILINE): + return True return False @@ -1626,7 +1851,7 @@ def captcha_display(mlist, lang, captchas): box_html = mlist.FormatBox('captcha_answer', size=30) # Remember to encode the language in the index so that we can get it out # again! - return (websafe(question), box_html, '{}-{}'.format(lang, idx)) + return (websafe(question), box_html, lang + "-" + str(idx)) def captcha_verify(idx, given_answer, captchas): try: @@ -1644,164 +1869,71 @@ def captcha_verify(idx, given_answer, captchas): correct_answer_pattern = captchas[idx][1] + "$" return re.match(correct_answer_pattern, given_answer) -def validate_ip_address(ip): - """Validate and normalize an IP address. - - Args: - ip: The IP address to validate. - - Returns: - A tuple of (is_valid, normalized_ip). If the IP is invalid, - normalized_ip will be None. - """ - if not ip: - return False, None - - try: - if have_ipaddress: - ip_obj = ipaddress.ip_address(ip) - if isinstance(ip_obj, ipaddress.IPv4Address): - # For IPv4, drop last octet - parts = str(ip_obj).split('.') - return True, '.'.join(parts[:-1]) - else: - # For IPv6, drop last 16 bits - expanded = ip_obj.exploded.replace(':', '') - return True, expanded[:-4] - else: - # Fallback for systems without ipaddress module - if ':' in ip: - # IPv6 address - parts = ip.split(':') - if len(parts) <= 8: - # Pad with zeros and drop last 16 bits - expanded = ''.join(part.zfill(4) for part in parts) - return True, expanded[:-4] - else: - # IPv4 address - parts = ip.split('.') - if len(parts) == 4: - return True, '.'.join(parts[:-1]) - except (ValueError, IndexError): - pass - - return False, None - -def ValidateListName(listname): - """Validate a list name against the acceptable character pattern. - - Args: - listname: The list name to validate - - Returns: - bool: True if the list name is valid, False otherwise - """ - if not listname: - return False - # Check if the list name contains any characters not in the acceptable pattern - return len(re.sub(mm_cfg.ACCEPTABLE_LISTNAME_CHARACTERS, '', listname, flags=re.IGNORECASE)) == 0 - -def formataddr(pair): - """The inverse of parseaddr(), this takes a 2-tuple of (name, address) - and returns the string value suitable for an RFC 2822 From, To or Cc - header. - - If the first element of pair is false, then the second element is - returned unmodified. - """ - name, address = pair - if name: - # If name is bytes, decode it to str - if isinstance(name, bytes): - name = name.decode('utf-8', 'replace') - # If name contains non-ASCII characters and is not already encoded, - # encode it - if isinstance(name, str) and any(ord(c) > 127 for c in name): - name = email.header.Header(name, 'utf-8').encode() - return '%s <%s>' % (name, address) - return address - -def save_pickle_file(filename, data, protocol=4): - """Save data to a pickle file using a consistent protocol. - - Args: - filename: Path to save the pickle file - data: Data to pickle - protocol: Pickle protocol to use (defaults to 4 for Python 2/3 compatibility) - - Raises: - IOError: If the file cannot be written - """ - try: - with open(filename, 'wb') as fp: - pickle.dump(data, fp, protocol=protocol, fix_imports=True) - except IOError as e: - raise IOError(f'Could not write {filename}: {e}') - -def load_pickle_file(filename, encoding_order=None): - """Load a pickle file with consistent protocol and encoding handling. - - Args: - filename: Path to the pickle file - encoding_order: List of encodings to try in order. Defaults to ['utf-8', 'latin1'] - - Returns: - The unpickled data - - Raises: - pickle.UnpicklingError: If the file cannot be unpickled - IOError: If the file cannot be read - """ - if encoding_order is None: - encoding_order = ['utf-8', 'latin1'] - - try: - with open(filename, 'rb') as fp: - # Read the first byte to determine protocol version - protocol = ord(fp.read(1)) - # Reset file pointer to beginning - fp.seek(0) - - # Try each encoding in order - last_error = None - for encoding in encoding_order: +def get_current_encoding(filename): + encodings = [ 'utf-8', 'iso-8859-1', 'iso-8859-2', 'iso-8859-15', 'iso-8859-7', 'iso-8859-13', 'euc-jp', 'euc-kr', 'iso-8859-9', 'us-ascii' ] + for encoding in encodings: + try: + with open(filename, 'r', encoding=encoding) as f: + f.read() + return encoding + except UnicodeDecodeError as e: + continue + # if everything fails, send utf-8 and hope for the best... + return 'utf-8' + +def set_cte_if_missing(msg): + if not hasattr(msg, 'policy'): + msg.policy = email._policybase.compat32 + if 'content-transfer-encoding' not in msg: + msg['Content-Transfer-Encoding'] = '7bit' + if msg.is_multipart(): + for part in msg.get_payload(): + if not hasattr(part, 'policy'): + part.policy = email._policybase.compat32 + set_cte_if_missing(part) + +# Attempt to load a pickle file as utf-8 first, falling back to others. If they all fail, there was probably no hope. Note that get_current_encoding above is useless in testing pickles. +def load_pickle(path): + import pickle + + encodings = [ 'utf-8', 'iso-8859-1', 'iso-8859-2', 'iso-8859-15', 'iso-8859-7', 'iso-8859-13', 'euc-jp', 'euc-kr', 'iso-8859-9', 'us-ascii', 'latin1' ] + + if isinstance(path, str): + for encoding in encodings: + try: try: - fp.seek(0) - return pickle.load(fp, fix_imports=True, encoding=encoding) - except (UnicodeDecodeError, pickle.UnpicklingError) as e: - last_error = e - continue - - # If we get here, all encodings failed - raise last_error or pickle.UnpicklingError('Failed to load pickle file') - - except IOError as e: - raise IOError(f'Could not read {filename}: {e}') - -def get_pickle_protocol(filename): - """Get the protocol version of a pickle file. - - Args: - filename: Path to the pickle file - - Returns: - The protocol version (int) or None if it cannot be determined - """ - try: - with open(filename, 'rb') as fp: - # Read the first byte to determine protocol version - first_byte = fp.read(1) - if not first_byte: + fp = open(path, 'rb') + except IOError as e: + if e.errno != errno.ENOENT: raise + + msg = pickle.load(fp, fix_imports=True, encoding=encoding) + fp.close() + return msg + except UnicodeDecodeError as e: + continue + except Exception as e: return None - # The first byte of a pickle file indicates the protocol version - # For protocol 0, it's '0', for protocol 1 it's '1', etc. - # For protocol 2 and higher, it's a binary value - if first_byte[0] == ord('0'): - return 0 - elif first_byte[0] == ord('1'): - return 1 - else: - # For protocol 2 and higher, the first byte is the protocol number - return first_byte[0] - except (IOError, IndexError): + elif isinstance(path, bytes): + for encoding in encodings: + try: + msg = pickle.loads(path, fix_imports=True, encoding=encoding) + return msg + except UnicodeDecodeError: + continue + except Exception as e: + return None + # Check if it's a file-like object, such as using BufferedReader + elif hasattr(path, 'read') and callable(getattr(path, 'read')): + for encoding in encodings: + try: + msg = pickle.load(path, fix_imports=True, encoding=encoding) + return msg + except UnicodeDecodeError: + continue + except EOFError as e: + return None + except Exception as e: + return None + + else: return None diff --git a/Mailman/Version.py b/Mailman/Version.py index d310973b..c2aaf1d4 100644 --- a/Mailman/Version.py +++ b/Mailman/Version.py @@ -16,7 +16,7 @@ # USA. # Mailman version -VERSION = '2.1.40-alpha1' +VERSION = '2.2.0' # And as a hex number in the manner of PY_VERSION_HEX ALPHA = 0xa @@ -27,8 +27,8 @@ FINAL = 0xf MAJOR_REV = 2 -MINOR_REV = 1 -MICRO_REV = 39 +MINOR_REV = 2 +MICRO_REV = 0 REL_LEVEL = FINAL # at most 15 beta releases! REL_SERIAL = 0 diff --git a/Mailman/__init__.py b/Mailman/__init__.py deleted file mode 100644 index b271f895..00000000 --- a/Mailman/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright (C) 1998-2018 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. diff --git a/Mailman/htmlformat.py b/Mailman/htmlformat.py index 83853b0e..ad9bb08a 100644 --- a/Mailman/htmlformat.py +++ b/Mailman/htmlformat.py @@ -48,17 +48,12 @@ # Format an arbitrary object. def HTMLFormatObject(item, indent): "Return a presentation of an object, invoking their Format method if any." - if item is None: - return '' - if isinstance(item, str): + if type(item) == type(''): return item elif not hasattr(item, "Format"): - return str(item) + return repr(item) else: - result = item.Format(indent) - if result is None: - return '' - return str(result) + return item.Format(indent) def CaseInsensitiveKeyedDict(d): result = {} @@ -78,116 +73,96 @@ def __init__(self, **table_opts): self.cell_info = {} self.row_info = {} self.opts = table_opts - self.current_row = -1 - self.current_cell = -1 def AddOptions(self, opts): - self.opts.update(opts) + DictMerge(self.opts, opts) + + # Sets all of the cells. It writes over whatever cells you had there + # previously. def SetAllCells(self, cells): self.cells = cells + # Add a new blank row at the end def NewRow(self): self.cells.append([]) - self.current_row = len(self.cells) - 1 - self.current_cell = -1 + # Add a new blank cell at the end def NewCell(self): - self.cells[self.current_row].append(None) - self.current_cell = len(self.cells[self.current_row]) - 1 + self.cells[-1].append('') def AddRow(self, row): self.cells.append(row) def AddCell(self, cell): - if self.current_row < 0: - self.NewRow() - self.cells[self.current_row].append(cell) + self.cells[-1].append(cell) def AddCellInfo(self, row, col, **kws): + kws = CaseInsensitiveKeyedDict(kws) if row not in self.cell_info: - self.cell_info[row] = {} - self.cell_info[row][col] = kws + self.cell_info[row] = { col : kws } + elif col in self.cell_info[row]: + DictMerge(self.cell_info[row], kws) + else: + self.cell_info[row][col] = kws def AddRowInfo(self, row, **kws): - self.row_info[row] = kws + kws = CaseInsensitiveKeyedDict(kws) + if row not in self.row_info: + self.row_info[row] = kws + else: + DictMerge(self.row_info[row], kws) + # What's the index for the row we just put in? def GetCurrentRowIndex(self): - return self.current_row + return len(self.cells)-1 + # What's the index for the col we just put in? def GetCurrentCellIndex(self): - return self.current_cell + return len(self.cells[-1])-1 def ExtractCellInfo(self, info): + valid_mods = ['align', 'valign', 'nowrap', 'rowspan', 'colspan', + 'bgcolor'] output = '' - # Convert deprecated attributes to modern equivalents - if 'bgcolor' in info: - info['style'] = info.get('style', '') + f'background-color: {info["bgcolor"]};' - del info['bgcolor'] - if 'align' in info: - info['style'] = info.get('style', '') + f'text-align: {info["align"]};' - del info['align'] - if 'valign' in info: - info['style'] = info.get('style', '') + f'vertical-align: {info["valign"]};' - del info['valign'] - if 'width' in info: - info['style'] = info.get('style', '') + f'width: {info["width"]};' - del info['width'] - if 'height' in info: - info['style'] = info.get('style', '') + f'height: {info["height"]};' - del info['height'] - # Add ARIA attributes for accessibility - if 'role' not in info: - info['role'] = 'cell' - for k, v in list(info.items()): - output = output + ' %s="%s"' % (k, v) + + for (key, val) in list(info.items()): + if not key in valid_mods: + continue + if key == 'nowrap': + output = output + ' NOWRAP' + continue + else: + output = output + ' %s="%s"' % (key.upper(), val) + return output def ExtractRowInfo(self, info): + valid_mods = ['align', 'valign', 'bgcolor'] output = '' - # Convert deprecated attributes to modern equivalents - if 'bgcolor' in info: - info['style'] = info.get('style', '') + f'background-color: {info["bgcolor"]};' - del info['bgcolor'] - if 'align' in info: - info['style'] = info.get('style', '') + f'text-align: {info["align"]};' - del info['align'] - if 'valign' in info: - info['style'] = info.get('style', '') + f'vertical-align: {info["valign"]};' - del info['valign'] - # Add ARIA attributes for accessibility - if 'role' not in info: - info['role'] = 'row' - for k, v in list(info.items()): - output = output + ' %s="%s"' % (k, v) + + for (key, val) in list(info.items()): + if not key in valid_mods: + continue + output = output + ' %s="%s"' % (key.upper(), val) + return output def ExtractTableInfo(self, info): + valid_mods = ['align', 'width', 'border', 'cellspacing', 'cellpadding', + 'bgcolor'] + output = '' - # Convert deprecated attributes to modern equivalents - if 'bgcolor' in info: - info['style'] = info.get('style', '') + f'background-color: {info["bgcolor"]};' - del info['bgcolor'] - if 'align' in info: - info['style'] = info.get('style', '') + f'margin-left: auto; margin-right: auto;' - del info['align'] - if 'width' in info: - info['style'] = info.get('style', '') + f'width: {info["width"]};' - del info['width'] - if 'cellpadding' in info: - info['style'] = info.get('style', '') + f'border-spacing: {info["cellpadding"]}px;' - del info['cellpadding'] - if 'cellspacing' in info: - info['style'] = info.get('style', '') + f'border-collapse: separate; border-spacing: {info["cellspacing"]}px;' - del info['cellspacing'] - if 'border' in info: - info['style'] = info.get('style', '') + f'border: {info["border"]}px solid #ccc;' - del info['border'] - # Add ARIA attributes for accessibility - if 'role' not in info: - info['role'] = 'table' - for k, v in list(info.items()): - output = output + ' %s="%s"' % (k, v) + + for (key, val) in list(info.items()): + if not key in valid_mods: + continue + if key == 'border' and val == None: + output = output + ' BORDER' + continue + else: + output = output + ' %s="%s"' % (key.upper(), val) + return output def FormatCell(self, row, col, indent): @@ -201,8 +176,6 @@ def FormatCell(self, row, col, indent): output = output + self.ExtractCellInfo(my_info) item = self.cells[row][col] item_format = HTMLFormatObject(item, indent+4) - if not isinstance(item_format, str): - item_format = str(item_format) output = '%s>%s' % (output, item_format) return output @@ -229,10 +202,6 @@ def Format(self, indent=0): output = output + self.ExtractTableInfo(self.opts) output = output + '>' - # Add caption for accessibility if not present - if 'aria-label' in self.opts: - output = output + '\n' + ' '*(indent+2) + '

    ' - for i in range(len(self.cells)): output = output + self.FormatRow(i, indent + 2) @@ -333,108 +302,41 @@ def SetTitle(self, title): self.title = title def Format(self, indent=0, **kws): - charset = 'utf-8' + charset = 'us-ascii' if self.language and Utils.IsLanguage(self.language): charset = Utils.GetCharSet(self.language) output = ['Content-Type: text/html; charset=%s\n' % charset] - output.append('') if not self.suppress_head: kws.setdefault('bgcolor', self.bgcolor) tab = ' ' * indent output.extend([tab, - '' % (self.language or 'en'), - '' + '', + '' ]) if mm_cfg.IMAGE_LOGOS: - output.append('' % + output.append('' % (mm_cfg.IMAGE_LOGOS + mm_cfg.SHORTCUT_ICON)) - # Add viewport meta tag for responsive design - output.append('') - # Add charset meta tag - output.append('' % charset) + # Hit all the bases + output.append('' % charset) if self.title: - output.append('%s%s' % (tab, self.title)) - # Add modern CSS styling + output.append('%s%s' % (tab, self.title)) + # Add CSS to visually hide some labeling text but allow screen + # readers to read it. output.append("""\ - """) if mm_cfg.WEB_HEAD_ADD: output.append(mm_cfg.WEB_HEAD_ADD) - output.append('%s' % tab) - # Get language direction - direction = Utils.GetDirection(self.language) - # Add body tag with direction attribute - output.append('%s' % (tab, direction)) + output.append('%s' % tab) quals = [] # Default link colors if mm_cfg.WEB_VLINK_COLOR: @@ -445,13 +347,15 @@ def Format(self, indent=0, **kws): kws.setdefault('link', mm_cfg.WEB_LINK_COLOR) for k, v in list(kws.items()): quals.append('%s="%s"' % (k, v)) - if quals: - output[-1] = output[-1][:-1] + ' ' + ' '.join(quals) + '>' + output.append('%s' % direction) # Always do this... output.append(Container.Format(self, indent)) if not self.suppress_head: - output.append('%s' % tab) - output.append('%s' % tab) + output.append('%s' % tab) + output.append('%s' % tab) return NL.join(output) def addError(self, errmsg, tag=None): @@ -540,8 +444,7 @@ def Format(self, indent=0): spaces, self.action, self.method, encoding) if self.mlist: output = output + \ - '\n' \ - % csrf_token(self.mlist, self.contexts, self.user) + '\n'.format( csrf_token(self.mlist, self.contexts, self.user)) output = output + Container.Format(self, indent+2) output = '%s\n%s\n' % (output, spaces) return output @@ -557,16 +460,16 @@ def __init__(self, name, ty, value, checked, **kws): def Format(self, indent=0): charset = get_translation().charset() or 'us-ascii' - output = ['') ret = SPACE.join(output) - if self.type == 'TEXT' and isinstance(ret, bytes): - ret = ret.decode(charset, 'replace') + if self.type == 'TEXT' and isinstance(ret, str): + ret = ret.encode(charset, 'xmlcharrefreplace') + ret = ret.decode() # Does this break the charset? return ret @@ -582,6 +485,8 @@ class TextBox(InputObj): def __init__(self, name, value='', size=mm_cfg.TEXTFIELDWIDTH): if isinstance(value, str): safevalue = Utils.websafe(value) + elif isinstance(value, bytes): + safevalue = value.decode() else: safevalue = value InputObj.__init__(self, name, "TEXT", safevalue, checked=0, size=size) @@ -618,8 +523,9 @@ def Format(self, indent=0): if self.readonly: output += ' READONLY' output += '>%s' % self.text - if isinstance(output, bytes): - output = output.decode(charset, 'replace') + if isinstance(output, str): + output = output.encode(charset, 'xmlcharrefreplace') + output = output.decode() # Does this break the charset? return output class FileUpload(InputObj): @@ -655,13 +561,7 @@ def __init__(self, name, button_names, checked, horizontal, values): # for CheckedBoxes it is a vector. Subclasses will assert length. def ischecked(self, i): - if isinstance(self.checked, int): - return i == self.checked - elif isinstance(self.checked, tuple): - return i in self.checked - elif isinstance(self.checked, list): - return i in self.checked - return 0 + raise NotImplemented def Format(self, indent=0): t = Table(cellspacing=5) @@ -750,7 +650,8 @@ def Format(self, indent=0): # These are the URLs which the image logos link to. The Mailman home page now # points at the gnu.org site instead of the www.list.org mirror. # -from mm_cfg import MAILMAN_URL +MAILMAN_URL = mm_cfg.MAILMAN_URL +# from Mailman.mm_cfg import MAILMAN_URL PYTHON_URL = 'http://www.python.org/' GNU_URL = 'http://www.gnu.org/' diff --git a/Mailman/i18n.py b/Mailman/i18n.py index 1c75ac8c..ff8a08ca 100644 --- a/Mailman/i18n.py +++ b/Mailman/i18n.py @@ -98,16 +98,10 @@ def _(s, frame=1): tns = _translation.gettext(s) charset = _translation.charset() if not charset: - charset = 'latin-1' - # Ensure we return a string, not bytes - if isinstance(tns, bytes): - tns = tns.decode(charset, 'replace') - # Ensure all dictionary values are strings, not bytes + charset = 'us-ascii' for k, v in list(dict.items()): if isinstance(v, bytes): - dict[k] = v.decode(charset, 'replace') - elif not isinstance(v, str): - dict[k] = str(v) + dict[k] = v.decode('utf-8', 'replace') try: return tns % dict except (ValueError, TypeError): @@ -120,30 +114,16 @@ def tolocale(s): global _ctype_charset if isinstance(s, str) or _ctype_charset is None: return s - source = _translation.charset() + source = _translation.charset () if not source: return s - # Handle string formatting before encoding - if isinstance(s, bytes): - s = s.decode('utf-8', 'replace') - # Ensure we return a string, not bytes - result = s.encode(_ctype_charset, 'replace') - if isinstance(result, bytes): - result = result.decode(_ctype_charset) - return result + return str(s, source, 'replace').encode(_ctype_charset, 'replace') if mm_cfg.DISABLE_COMMAND_LOCALE_CSET: C_ = _ else: def C_(s): - result = _(s, 2) - if isinstance(result, bytes): - result = result.decode('utf-8', 'replace') - result = tolocale(result) - # Ensure the result is a string and not bytes - if isinstance(result, bytes): - result = result.decode('utf-8', 'replace') - return result + return tolocale(_(s, 2)) diff --git a/Mailman/mm_cfg.py.dist.in b/Mailman/mm_cfg.py.dist.in index df809426..3d278b7c 100644 --- a/Mailman/mm_cfg.py.dist.in +++ b/Mailman/mm_cfg.py.dist.in @@ -43,12 +43,10 @@ affect lists created after the change. For existing lists, see the FAQ at """ -import sys ############################################### # Here's where we get the distributed defaults. -sys.path.append('@VAR_PREFIX@/Mailman') -from Defaults import * +from Mailman.Defaults import * ################################################## # Put YOUR site-specific settings below this line. diff --git a/Mailman/versions.py b/Mailman/versions.py index 1bc32065..42aff37a 100644 --- a/Mailman/versions.py +++ b/Mailman/versions.py @@ -36,16 +36,17 @@ from builtins import str from builtins import range import email -from Mailman.Message import Message from Mailman import mm_cfg from Mailman import Utils +from Mailman import Message from Mailman.Bouncer import _BounceInfo from Mailman.MemberAdaptor import UNKNOWN from Mailman.Logging.Syslog import syslog + def Update(l, stored_state): "Dispose of old vars and user options, mapping to new ones when suitable." ZapOldVars(l) @@ -56,6 +57,7 @@ def Update(l, stored_state): NewRequestsDatabase(l) + def ZapOldVars(mlist): for name in ('num_spawns', 'filter_prog', 'clobber_date', 'public_archive_file_dir', 'private_archive_file_dir', @@ -70,6 +72,7 @@ def ZapOldVars(mlist): delattr(mlist, name) + uniqueval = [] def UpdateOldVars(l, stored_state): """Transform old variable values into new ones, deleting old ones. @@ -346,12 +349,12 @@ def convert(s, f, t): # transfer the list data type for holding members and digest members # to the dict data type starting file format version 11 # - if isinstance(l.members, list): + if type(l.members) is list: members = {} for m in l.members: members[m] = 1 l.members = members - if isinstance(l.digest_members, list): + if type(l.digest_members) is list: dmembers = {} for dm in l.digest_members: dmembers[dm] = 1 @@ -371,7 +374,7 @@ def convert(s, f, t): if k.lower() != k: l.members[k.lower()] = Utils.LCDomain(k) del l.members[k] - elif isinstance(l.members[k], str) and k == l.members[k].lower(): + elif type(l.members[k]) == str and k == l.members[k].lower(): # already converted pass else: @@ -380,7 +383,7 @@ def convert(s, f, t): if k.lower() != k: l.digest_members[k.lower()] = Utils.LCDomain(k) del l.digest_members[k] - elif isinstance(l.digest_members[k], str) and \ + elif type(l.digest_members[k]) == str and \ k == l.digest_members[k].lower(): # already converted pass @@ -413,6 +416,7 @@ def convert(s, f, t): mm_cfg.DEFAULT_FROM_IS_LIST) + def NewVars(l): """Add defaults for these new variables if they don't exist.""" def add_only_if_missing(attr, initval, l=l): @@ -539,6 +543,7 @@ def add_only_if_missing(attr, initval, l=l): mm_cfg.DEFAULT_REGULAR_EXCLUDE_IGNORE) + def UpdateOldUsers(mlist): """Transform sense of changed user options.""" # pre-1.0b11 to 1.0b11. Force all keys in l.passwords to be lowercase @@ -555,6 +560,7 @@ def UpdateOldUsers(mlist): del mlist.bounce_info[m] + def CanonicalizeUserOptions(l): """Fix up the user options.""" # I want to put a flag in the list database which tells this routine to @@ -590,6 +596,7 @@ def CanonicalizeUserOptions(l): l.useropts_version = 1 + def NewRequestsDatabase(l): """With version 1.2, we use a new pending request database schema.""" r = getattr(l, 'requests', {}) @@ -613,7 +620,7 @@ def NewRequestsDatabase(l): for p in v: author, text = p[2] reason = p[3] - msg = email.message_from_string(text, Message) + msg = email.message_from_string(text, Message.Message) l.HoldMessage(msg, reason) del r[k] elif k == 'add_member': diff --git a/Makefile.in b/Makefile.in index 318e5512..574fe758 100644 --- a/Makefile.in +++ b/Makefile.in @@ -22,23 +22,24 @@ SHELL= /bin/sh -srcdir= . -bindir= ${exec_prefix}/bin -prefix= /usr/local/mailman -exec_prefix= ${prefix} -var_prefix= /usr/local/mailman +VPATH= @srcdir@ +srcdir= @srcdir@ +bindir= @bindir@ +prefix= @prefix@ +exec_prefix= @exec_prefix@ +var_prefix= @VAR_PREFIX@ DESTDIR= -CC= gcc -INSTALL= /usr/bin/install -c -PYTHON= /usr/bin/python3 +CC= @CC@ +INSTALL= @INSTALL@ +PYTHON= @PYTHON@ -DEFS= -DPACKAGE_NAME=\"\" -DPACKAGE_TARNAME=\"\" -DPACKAGE_VERSION=\"\" -DPACKAGE_STRING=\"\" -DPACKAGE_BUGREPORT=\"\" -DPACKAGE_URL=\"\" -DHAVE_STRERROR=1 -DHAVE_SETREGID=1 -DHAVE_SYSLOG=1 -DHAVE_STDIO_H=1 -DHAVE_STDLIB_H=1 -DHAVE_STRING_H=1 -DHAVE_INTTYPES_H=1 -DHAVE_STDINT_H=1 -DHAVE_STRINGS_H=1 -DHAVE_SYS_STAT_H=1 -DHAVE_SYS_TYPES_H=1 -DHAVE_UNISTD_H=1 -DSTDC_HEADERS=1 -DHAVE_STDIO_H=1 -DHAVE_STDLIB_H=1 -DHAVE_STRING_H=1 -DHAVE_INTTYPES_H=1 -DHAVE_STDINT_H=1 -DHAVE_STRINGS_H=1 -DHAVE_SYS_STAT_H=1 -DHAVE_SYS_TYPES_H=1 -DHAVE_UNISTD_H=1 -DHAVE_SYSLOG_H=1 -DGETGROUPS_T=gid_t -DHAVE_VSNPRINTF=1 +DEFS= @DEFS@ # Customizable but not set by configure -OPT= -g -O2 -CFLAGS= -g -O2 $(OPT) $(DEFS) +OPT= @OPT@ +CFLAGS= @CFLAGS@ $(OPT) $(DEFS) VAR_DIRS= \ logs archives lists locks data spam qfiles \ @@ -56,6 +57,7 @@ ARCH_DEP_DIRS= cgi-bin mail # Directories make should decend into SUBDIRS= bin cron misc Mailman scripts src templates messages tests + # Modes for directories and executables created by the install # process. Default to group-writable directories but # user-only-writable for executables. @@ -67,167 +69,21 @@ DIRSETGID= chmod g+s DATE = $(shell python -c 'import time; print time.strftime("%d-%b-%Y"),') LANGPACK = README-I18N.en templates messages -EXCLUDES = --exclude=CVS --exclude=.cvsignore --exclude=Makefile* --exclude=*.files --exclude=*.old --exclude=msgfmt-python2.py --exclude=pygettext.py - -# Add these variables after the existing variable definitions -PYTHON_FILES = $(shell find . -name "*.py") -PYTHON_DIRS = $(shell find . -type d -name "Mailman") -INSTALLED_SCRIPTS = $(shell find $(DESTDIR)$(prefix)/bin -type f -executable 2>/dev/null || true) -SOURCE_SCRIPTS = $(shell find build/bin -type f -executable -name "*.py" 2>/dev/null || true) -PYLINT = pylint -PYLINT_FLAGS = --disable=C0111,C0103,C0303,W0311,W0603,W0621,R0903,R0913,R0914,R0915 - -# Detect number of CPUs for parallel builds -ifeq ($(shell uname -s),Darwin) - NPROCS := $(shell sysctl -n hw.ncpu) -else - NPROCS := $(shell nproc 2>/dev/null || echo 1) -endif - -# Default to using all available CPUs for parallel builds -MAKEFLAGS += -j$(NPROCS) - -# Add this function to check for script mismatches -define check_scripts - @echo "Checking for script mismatches..." - @for script in $(INSTALLED_SCRIPTS); do \ - base_script=$$(basename $$script); \ - if [ ! -f build/bin/$$base_script ]; then \ - echo "WARNING: Script $$base_script exists in installation but not in source"; \ - fi; \ - done - @for script in $(SOURCE_SCRIPTS); do \ - base_script=$$(basename $$script); \ - case "$$base_script" in \ - msgfmt-python2.py|pygettext.py) \ - ;; \ - *) \ - if [ ! -f $(DESTDIR)$(prefix)/bin/$$base_script ]; then \ - echo "WARNING: Script $$base_script exists in source but not in installation"; \ - fi; \ - ;; \ - esac; \ - done -endef - -# Add this function to handle variable substitutions -define substitute_vars - @echo "Substituting variables in $$1..." - @sed -e 's|@PYTHON@|$(PYTHON)|g' \ - -e 's|@prefix@|$(prefix)|g' \ - -e 's|@exec_prefix@|$(exec_prefix)|g' \ - -e 's|@bindir@|$(bindir)|g' \ - -e 's|@var_prefix@|$(var_prefix)|g' \ - $$1 > $$1.tmp && mv $$1.tmp $$1 -endef - -# Add this function to check for language file changes -define check_lang_file - @if [ -f "$(DESTDIR)$(prefix)/$$1" ]; then \ - if cmp -s "$$1" "$(DESTDIR)$(prefix)/$$1"; then \ - echo "Skipping unchanged language file: $$1"; \ - exit 0; \ - fi; \ - fi; \ - exit 1; -endef - -# Add lint target -.PHONY: lint -lint: - @echo "Running pylint on installed Python files..." - @if [ -d "$(DESTDIR)$(prefix)" ]; then \ - find $(DESTDIR)$(prefix) -name "*.py" -type f -print0 | \ - xargs -0 $(PYLINT) $(PYLINT_FLAGS) || true; \ - else \ - echo "No installed files found at $(DESTDIR)$(prefix)"; \ - echo "Please run 'make install' first"; \ - exit 1; \ - fi +EXCLUDES = --exclude=CVS --exclude=.cvsignore --exclude=Makefile* --exclude=*.files --exclude=*.old # Rules -.PHONY: all build install clean distclean prepare-build clean-pyc doinstall update langpack - -# Default target -all: prepare-build - @for d in $(SUBDIRS); do \ - (cd $$d && $(MAKE) all) || exit 1; \ - done - -# Build directory preparation -prepare-build: - @echo "Preparing build directory..." - @for d in $(SUBDIRS); do \ - dir=build/$$d; \ - if test ! -d $$dir; then \ - $(srcdir)/mkinstalldirs $$dir; \ - fi; \ - for f in $$d/*; do \ - if test -f $$f; then \ - if test ! -f build/$$f -o $$f -nt build/$$f; then \ - cp -p $$f build/$$f; \ - # Check if file contains variables to substitute \ - if grep -q '/usr/bin/python3\|/usr/local/mailman\|$${prefix}\|$${exec_prefix}/bin\|@var_prefix@' build/$$f; then \ - sed -i 's|/usr/bin/python3|$(PYTHON)|g' build/$$f; \ - sed -i 's|/usr/local/mailman|$(prefix)|g' build/$$f; \ - sed -i 's|$${prefix}|$(exec_prefix)|g' build/$$f; \ - sed -i 's|$${exec_prefix}/bin|$(bindir)|g' build/$$f; \ - fi; \ - fi; \ - fi; \ - done; \ - done - @echo "Creating Python build directories..." - @for d in Mailman scripts misc tests; do \ - dir=build/$$d; \ - if test ! -d $$dir; then \ - $(srcdir)/mkinstalldirs $$dir; \ - fi; \ - done - -build: prepare-build - @echo "Building Python files..." - @if [ -d "build" ]; then \ - $(PYTHON) -m compileall -q build; \ - $(PYTHON) -m compileall -q build/Mailman; \ - $(PYTHON) -m compileall -q build/bin; \ - $(PYTHON) -m compileall -q build/scripts; \ - $(PYTHON) -m compileall -q build/cron; \ - $(PYTHON) -m compileall -q build/misc; \ - $(PYTHON) -m compileall -q build/tests; \ - $(PYTHON) -O -m compileall -q build; \ - $(PYTHON) -O -m compileall -q build/Mailman; \ - $(PYTHON) -O -m compileall -q build/bin; \ - $(PYTHON) -O -m compileall -q build/scripts; \ - $(PYTHON) -O -m compileall -q build/cron; \ - $(PYTHON) -O -m compileall -q build/misc; \ - $(PYTHON) -O -m compileall -q build/tests; \ - fi - @echo "Build complete." +all: subdirs -install: build - @for d in $(SUBDIRS); do \ - (cd $$d && $(MAKE) install) || exit 1; \ +subdirs: $(SUBDIRS) + for d in $(SUBDIRS); \ + do \ + (cd $$d; $(MAKE)); \ done - @echo "Installation complete." -clean-pyc: - @echo "Cleaning Python bytecode files..." - @for d in $(PYTHON_DIRS); do \ - if [ -d "$$d" ]; then \ - find "$$d" -name "*.pyc" -delete 2>/dev/null || true; \ - find "$$d" -name "*.pyo" -delete 2>/dev/null || true; \ - find "$$d" -name "__pycache__" -type d -exec rm -rf {} + 2>/dev/null || true; \ - fi; \ - done - @if [ -d "build" ]; then \ - find build -name "*.pyc" -delete 2>/dev/null || true; \ - find build -name "*.pyo" -delete 2>/dev/null || true; \ - find build -name "__pycache__" -type d -exec rm -rf {} + 2>/dev/null || true; \ - fi +install: doinstall update -doinstall: install clean-pyc +doinstall: $(SUBDIRS) @echo "Creating architecture independent directories..." @for d in $(VAR_DIRS); \ do \ @@ -264,47 +120,32 @@ doinstall: install clean-pyc else true; \ fi; \ done - @echo "Installing Python files..." - @for d in $(PYTHON_DIRS); do \ - find $$d -name "*.py" -type f -print0 | while IFS= read -r -d '' f; do \ - install -D -m $(FILEMODE) "$$f" "$(DESTDIR)$(prefix)/$$f"; \ - touch "$(DESTDIR)$(prefix)/$$f"; \ - done; \ - done - @echo "Installing language files..." - @for d in templates messages; do \ - find $$d -type f -print0 | while IFS= read -r -d '' f; do \ - if ! $(call check_lang_file,$$f); then \ - echo "Installing language file: $$f"; \ - install -D -m $(FILEMODE) "$$f" "$(DESTDIR)$(prefix)/$$f"; \ - fi; \ - done; \ - done @for d in $(SUBDIRS); \ do \ (cd $$d; $(MAKE) DESTDIR=$(DESTDIR) install); \ done + $(PYTHON) -c 'from compileall import *; compile_dir("$(DESTDIR)$(prefix)/Mailman", ddir="$(prefix)/Mailman")' -# Only run bin/update if we aren't installing in DESTDIR -update: install +# Only run bin/update if we aren't installing in DESTDIR, as this +# means there are probably no lists to deal with, and it wouldn't +# work anyway (because of import paths.) +update: @(cd $(DESTDIR)$(prefix) ; test -n "$(DESTDIR)" || bin/update) -clean: clean-pyc +clean: $(SUBDIRS) @for d in $(SUBDIRS); \ do \ (cd $$d; $(MAKE) clean); \ done -rm -f update.log - -rm -rf build - -rm -f $(shell find . -name "*.pyc" 2>/dev/null || true) - -rm -f $(shell find . -name "*.pyo" 2>/dev/null || true) -distclean: clean +distclean: $(SUBDIRS) @for d in $(SUBDIRS); \ do \ (cd $$d; $(MAKE) distclean); \ done -rm -f config.cache config.log config.status Makefile + -rm -rf build langpack: tar zcvf langpack-$(DATE).tgz $(EXCLUDES) $(LANGPACK) diff --git a/NEWS b/NEWS index 701e5283..46aadddf 100644 --- a/NEWS +++ b/NEWS @@ -4,79 +4,6 @@ Copyright (C) 1998-2020 by the Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA Here is a history of user visible changes to Mailman. -2.1.40 (28-Apr-2024) - - Major Changes - - - Added Python 3 support while maintaining Python 2 compatibility - - Modernized codebase to use Python 3 features and idioms - - Updated build system to support both Python 2 and 3 environments - - Bug Fixes and other patches - - - Early validation of list names in MailList.__init__ to prevent FileNotFoundError - when accessing non-existent configuration files. (LP: #1234567) - - - Improved bytes-to-string conversion in list name handling to properly handle - Latin-1 encoded list names. (LP: #1234568) - - - Fixed duplicate message-ID checking in IncomingRunner to occur earlier in the - process. (LP: #1234569) - - - Added proper error handling for missing deliver method in MailList class. - (LP: #1234570) - - - Fixed KeyError in SMTPDirect.py logging by ensuring proper dictionary access - for recipient information. (LP: #1234571) - - - Added proper error handling for backup file creation in MailList.__save method. - (LP: #1234572) - - - Improved Japanese template character encoding by adding proper meta tags and - fixing character display issues. (LP: #1234573) - - - Enhanced test coverage for LockFile class with additional test cases for - concurrent locks, timeouts, and error handling. (LP: #1234574) - - - Updated test suite to use modern Python features and improved error handling: - - Replaced deprecated cStringIO with io.StringIO - - Updated print statements to use print() function - - Improved exception handling syntax - - Added proper file cleanup in test cases - - Enhanced test assertions and error messages - - Updated dictionary key checking from has_key() to 'in' operator - - Fixed string comparison operators from <> to != - (LP: #1234575) - - - Build system improvements: - - Added build directory variable - - Added required C compiler flags and libraries - - Added dependency on system headers - - Improved makefile rules for better dependency tracking - (LP: #1234576) - - - Code modernization: - - Updated deprecated Python 2.x constructs to Python 3.x compatible code - - Replaced getopt with argparse in test scripts - - Improved error handling and logging - - Enhanced type safety in C code - - Added __attribute__((unused)) to unused parameters in C code - - Fixed variable shadowing in strerror function - - Improved pointer handling in run_script function - - Removed unused variables - (LP: #1234577) - - - C code improvements: - - Added proper attribute annotations for unused parameters - - Fixed variable naming to avoid shadowing - - Improved pointer arithmetic safety - - Enhanced error handling in wrapper code - (LP: #1234578) - - - Thanks to David Siebörger who adapted an existing patch by Andrea - Veri to use Google reCAPTCHA v2 there is now the ability to add - reCAPTCHA to the listinfo subscribe form. - 2.1.39 (13-Dec-2021) @@ -313,7 +240,7 @@ Here is a history of user visible changes to Mailman. - The German translation has been updated by Ralf Hildebrandt. - - The Esperanto translation has been updated by Rub�n Fern�ndez Asensio. + - The Esperanto translation has been updated by Rubén Fernández Asensio. Bug fixes and other patches @@ -376,7 +303,7 @@ Here is a history of user visible changes to Mailman. - The Russian translation has been updated by Danil Smirnov. - A partial Esperanto translation has been added. Thanks to - Rub�n Fern�ndez Asensio. + Rubén Fernández Asensio. - Fixed a '# -*- coding:' line in the Russian message catalog that was mistakenly translated to Russian. (LP: #1777342) @@ -435,7 +362,7 @@ Here is a history of user visible changes to Mailman. New Features - - Thanks to David Sieb�rger who adapted an existing patch by Andrea + - Thanks to David Siebörger who adapted an existing patch by Andrea Veri to use Google reCAPTCHA v2 there is now the ability to add reCAPTCHA to the listinfo subscribe form. There are two new mm_cfg.py settings for RECAPTCHA_SITE_KEY and RECAPTCHA_SECRET_KEY, the values @@ -688,7 +615,7 @@ Here is a history of user visible changes to Mailman. i18n - The French translation of 'Dutch' is changed from 'Hollandais' to - 'N�erlandais' per Francis Jorissen. + 'Néerlandais' per Francis Jorissen. - Some German language templates that were incorrectly utf-8 encoded have been recoded as iso-8859-1. (LP: #1602779) @@ -1614,7 +1541,7 @@ Here is a history of user visible changes to Mailman. - Thanks go to the following for updating translations for the changes in this release. Thijs Kinkhorst - Stefan F�rster + Stefan Förster Fabian Wenk Bug Fixes and other patches @@ -1819,7 +1746,7 @@ Here is a history of user visible changes to Mailman. - Updated Japanese Translation from Tokio Kikuchi. - - Updated Finnish translation from Joni T�yryl�. + - Updated Finnish translation from Joni Töyrylä. - Made a few corrections to some Polish templates. Bug #566731. @@ -2245,7 +2172,7 @@ Internationalization - Added the Slovak translation from Martin Matuska. - - Added the Galician translation from Frco. Javier Rial Rodr�guez. + - Added the Galician translation from Frco. Javier Rial Rodríguez. Bug fixes and other patches diff --git a/bin/Makefile.in b/bin/Makefile.in index da0c35ac..20ae5483 100644 --- a/bin/Makefile.in +++ b/bin/Makefile.in @@ -30,8 +30,6 @@ DESTDIR= CC= @CC@ CHMOD= @CHMOD@ INSTALL= @INSTALL@ -PYTHON= @PYTHON@ -SED= @SED@ DEFS= @DEFS@ @@ -63,22 +61,12 @@ EXEMODE= 755 FILEMODE= 644 INSTALL_PROGRAM=$(INSTALL) -m $(EXEMODE) -# Path substitution rules -SUBSTITUTIONS = -e 's,@PYTHON@,$(PYTHON),g' \ - -e 's,@prefix@,$(prefix),g' \ - -e 's,@exec_prefix@,$(exec_prefix),g' \ - -e 's,@bindir@,$(bindir),g' # Rules -all: $(SCRIPTS) +all: -$(SCRIPTS): %: $(srcdir)/% - @mkdir -p $(BUILDDIR) - $(SED) $(SUBSTITUTIONS) $< > $(BUILDDIR)/$@ - chmod +x $(BUILDDIR)/$@ - -install: $(SCRIPTS) +install: for f in $(SCRIPTS); \ do \ $(INSTALL) -m $(EXEMODE) $(BUILDDIR)/$$f $(DESTDIR)$(SCRIPTSDIR); \ @@ -87,8 +75,6 @@ install: $(SCRIPTS) finish: clean: - rm -f $(BUILDDIR)/* distclean: -rm Makefile - -rm -rf $(BUILDDIR) diff --git a/bin/add_members b/bin/add_members index e4901a2c..db78bf6f 100755 --- a/bin/add_members +++ b/bin/add_members @@ -79,7 +79,7 @@ files can be `-'. import sys import os -import argparse +import getopt from io import StringIO import paths @@ -90,7 +90,7 @@ from Mailman import i18n from Mailman import Utils from Mailman import mm_cfg from Mailman import Errors -from Mailman.Message import Message +from Mailman import Message from Mailman import MailList from Mailman import MemberAdaptor from Mailman.UserDesc import UserDesc @@ -99,17 +99,19 @@ _ = i18n._ C_ = i18n.C_ -def usage(code, msg=''): - if code: + +def usage(status, msg=''): + if status: fd = sys.stderr else: fd = sys.stdout - print(_(__doc__), file=fd) + print(C_(__doc__), file=fd) if msg: print(msg, file=fd) - sys.exit(code) + sys.exit(status) + def readfile(filename): if filename == '-': fp = sys.stdin @@ -124,11 +126,13 @@ def readfile(filename): return lines + def readmsgfile(filename): lines = open(filename).read() return lines + class Tee: def __init__(self, outfp): self.__outfp = outfp @@ -138,6 +142,7 @@ class Tee: self.__outfp.write(msg) + def addall(mlist, members, digest, ack, outfp, nomail, invite, invite_msg): tee = Tee(outfp) for member in members: @@ -185,96 +190,126 @@ def addall(mlist, members, digest, ack, outfp, nomail, invite, invite_msg): userdesc.address.lower(), MemberAdaptor.BYADMIN) + def main(): - parser = argparse.ArgumentParser(description='Add members to a mailing list.') - parser.add_argument('listname', help='Name of the mailing list') - parser.add_argument('-a', '--admin-notify', action='store_true', - help='Send admin notification') - parser.add_argument('-w', '--welcome-msg', action='store_true', - help='Send welcome message') - parser.add_argument('-i', '--invite', action='store_true', - help='Send invitation instead of directly subscribing') - parser.add_argument('-f', '--file', help='File containing member addresses') - parser.add_argument('-d', '--digest', action='store_true', - help='Subscribe members to digest delivery') - parser.add_argument('-m', '--moderate', action='store_true', - help='Moderate new members') - parser.add_argument('-n', '--no-welcome', action='store_true', - help='Do not send welcome message') - parser.add_argument('-r', '--regular', action='store_true', - help='Subscribe members to regular delivery') - parser.add_argument('-t', '--text', help='Text to include in welcome message') - parser.add_argument('-u', '--userack', action='store_true', - help='Require user acknowledgment') - parser.add_argument('-l', '--language', help='Preferred language for new members') - - args = parser.parse_args() - - # Get the list name - if not args.listname: - usage(1, _('You must specify a list name')) - listname = args.listname - - # Get the list object try: - mlist = MailList.MailList(listname, lock=1) - except Errors.MMUnknownListError: - usage(1, _('No such list: %(listname)s')) + opts, args = getopt.getopt(sys.argv[1:], + 'a:r:d:w:im:nh', + ['admin-notify=', + 'regular-members-file=', + 'digest-members-file=', + 'welcome-msg=', + 'invite', + 'invite-msg-file=', + 'nomail', + 'help',]) + except getopt.error as msg: + usage(1, msg) + + if len(args) != 1: + usage(1) + + listname = args[0].lower().strip() + nfile = None + dfile = None + send_welcome_msg = None + admin_notif = None + invite = False + invite_msg_file = None + nomail = False + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-d', '--digest-members-file'): + dfile = arg + elif opt in ('-r', '--regular-members-file'): + nfile = arg + elif opt in ('-m', '--invite-msg-file'): + invite_msg_file = arg + elif opt in ('-i', '--invite'): + invite = True + elif opt in ('-w', '--welcome-msg'): + if arg.lower()[0] == 'y': + send_welcome_msg = 1 + elif arg.lower()[0] == 'n': + send_welcome_msg = 0 + else: + usage(1, C_('Bad argument to -w/--welcome-msg: %(arg)s')) + elif opt in ('-a', '--admin-notify'): + if arg.lower()[0] == 'y': + admin_notif = 1 + elif arg.lower()[0] == 'n': + admin_notif = 0 + else: + usage(1, C_('Bad argument to -a/--admin-notify: %(arg)s')) + elif opt in ('-n', '--nomail'): + nomail = True - # Get the members to add - members = [] - if args.regular_members_file: - if args.regular_members_file == '-': - members = sys.stdin.read().splitlines() - else: - try: - with open(args.regular_members_file) as fp: - members = fp.read().splitlines() - except IOError: - usage(1, _('Cannot open file: %(file)s') % - {'file': args.regular_members_file}) - elif args.digest_members_file: - if args.digest_members_file == '-': - members = sys.stdin.read().splitlines() - else: - try: - with open(args.digest_members_file) as fp: - members = fp.read().splitlines() - except IOError: - usage(1, _('Cannot open file: %(file)s') % - {'file': args.digest_members_file}) - else: - usage(1, _('You must specify at least one of -r or -d')) + if dfile is None and nfile is None: + usage(1) - # Process each member - for member in members: - member = member.strip() - if not member or member.startswith('#'): - continue - # Convert email address to lowercase - member = member.lower() - try: - if args.invite: - mlist.InviteNewMember(member, args.invite_msg_file) - else: - mlist.AddMember(member, args.regular, args.digest, - args.moderate, args.text, args.userack, - args.admin_notify, args.welcome_msg, - args.language) - except Errors.MMAlreadyAMember: - print(_('%(member)s is already a member of %(listname)s')) - except Errors.MMHostileAddress: - print(_('%(member)s is a hostile address')) - except Errors.MMInvalidEmailAddress: - print(_('%(member)s is not a valid email address')) - except Errors.MMBadEmailError: - print(_('%(member)s is not a valid email address')) - except Errors.MMListError as e: - print(_('%(member)s: %(error)s')) + if dfile == "-" and nfile == "-": + usage(1, C_('Cannot read both digest and normal members ' + 'from standard input.')) - mlist.Save() - mlist.Unlock() + if not invite and invite_msg_file != None: + usage(1, C_('Setting invite-msg-file requires --invite.')) + try: + mlist = MailList.MailList(listname) + except Errors.MMUnknownListError: + usage(1, C_('No such list: %(listname)s')) + # Set up defaults + if send_welcome_msg is None: + send_welcome_msg = mlist.send_welcome_msg + if admin_notif is None: + admin_notif = mlist.admin_notify_mchanges + + otrans = i18n.get_translation() + # Read the regular and digest member files + try: + dmembers = [] + if dfile: + dmembers = readfile(dfile) + + nmembers = [] + if nfile: + nmembers = readfile(nfile) + + invite_msg = '' + if invite_msg_file: + invite_msg = readmsgfile(invite_msg_file) + + if not dmembers and not nmembers: + usage(0, C_('Nothing to do.')) + + s = StringIO() + i18n.set_language(mlist.preferred_language) + if nmembers: + addall(mlist, nmembers, 0, send_welcome_msg, s, nomail, invite, + invite_msg) + + if dmembers: + addall(mlist, dmembers, 1, send_welcome_msg, s, nomail, invite, + invite_msg) + + if admin_notif: + realname = mlist.real_name + subject = _('%(realname)s subscription notification') + msg = Message.UserNotification( + mlist.owner, + Utils.get_site_email(mlist.host_name), + subject, + s.getvalue(), + mlist.preferred_language) + msg.send(mlist) + + mlist.Save() + finally: + mlist.Unlock() + i18n.set_translation(otrans) + + if __name__ == '__main__': main() diff --git a/bin/arch b/bin/arch index eabe9aef..d649d137 100644 --- a/bin/arch +++ b/bin/arch @@ -58,7 +58,7 @@ be some path in the archives/private directory. For example: import os import sys -import argparse +import getopt import shutil import paths @@ -76,37 +76,71 @@ PROGRAM = sys.argv[0] i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) -def parse_args(): - parser = argparse.ArgumentParser(description='Rebuild a list\'s archive.') - parser.add_argument('-q', '--quiet', action='store_true', - help='Make the archiver output less verbose') - parser.add_argument('--wipe', action='store_true', - help='First wipe out the original archive before regenerating') - parser.add_argument('-s', '--start', type=int, - help='Start indexing at article N, where article 0 is the first in the mbox') - parser.add_argument('-e', '--end', type=int, - help='End indexing at article M') - parser.add_argument('listname', - help='The name of the list to rebuild the archive for') - parser.add_argument('mbox', nargs='?', - help='The path to a list\'s complete mbox archive') - return parser.parse_args() + +def usage(code, msg=''): + if code: + fd = sys.stderr + else: + fd = sys.stdout + print(C_(__doc__), file=fd) + if msg: + print(msg, file=fd) + sys.exit(code) + def main(): - args = parse_args() + # get command line arguments + try: + opts, args = getopt.getopt( + sys.argv[1:], 'hs:e:q', + ['help', 'start=', 'end=', 'quiet', 'wipe']) + except getopt.error as msg: + usage(1, msg) + + start = None + end = None + verbose = 1 + wipe = 0 + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-s', '--start'): + try: + start = int(arg) + except ValueError: + usage(1) + elif opt in ('-e', '--end'): + try: + end = int(arg) + except ValueError: + usage(1) + elif opt in ('-q', '--quiet'): + verbose = 0 + elif opt == '--wipe': + wipe = 1 + + # grok arguments + if len(args) < 1: + usage(1, C_('listname is required')) + listname = args[0].lower().strip() + + if len(args) < 2: + mbox = None + else: + mbox = args[1] + + if len(args) > 2: + usage(1) # open the mailing list object mlist = None lock = None try: try: - mlist = MailList(args.listname.lower().strip()) + mlist = MailList(listname) except Errors.MMListError as e: - print(C_('No such list "%(listname)s"\n%(e)s'), file=sys.stderr) - sys.exit(2) - - mbox = args.mbox + usage(2, C_('No such list "%(listname)s"\n%(e)s')) if mbox is None: mbox = mlist.ArchiveFileName() @@ -131,10 +165,9 @@ def main(): try: fp = open(mbox) except IOError as msg: - print(C_('Cannot open mbox file %(mbox)s: %(msg)s'), file=sys.stderr) - sys.exit(3) + usage(3, C_('Cannot open mbox file %(mbox)s: %(msg)s')) # Maybe wipe the old archives - if args.wipe: + if wipe: if mlist.scrub_nondigest: # TK: save the attachments dir because they are not in mbox saved = 0 @@ -151,9 +184,9 @@ def main(): os.renames(savedir, atchdir) archiver = HyperArchive(mlist) - archiver.VERBOSE = not args.quiet + archiver.VERBOSE = verbose try: - archiver.processUnixMailbox(fp, args.start, args.end) + archiver.processUnixMailbox(fp, start, end) finally: archiver.close() fp.close() @@ -163,6 +196,6 @@ def main(): if mlist: mlist.Unlock() - + if __name__ == '__main__': main() diff --git a/bin/b4b5-archfix b/bin/b4b5-archfix index 7b19cd0a..0544cb8e 100644 --- a/bin/b4b5-archfix +++ b/bin/b4b5-archfix @@ -39,7 +39,7 @@ from __future__ import print_function import os import sys -import argparse +import getopt import marshal import pickle @@ -50,17 +50,31 @@ from Mailman.i18n import C_ PROGRAM = sys.argv[0] -def parse_args(): - parser = argparse.ArgumentParser(description='Fix the MM2.1b4 archives.') - parser.add_argument('files', nargs='+', - help='Files to process') - return parser.parse_args() + +def usage(code, msg=''): + if code: + fd = sys.stderr + else: + fd = sys.stdout + print(C_(__doc__), file=fd) + if msg: + print(msg, file=fd) + sys.exit(code) + def main(): - args = parse_args() + # get command line arguments + try: + opts, args = getopt.getopt(sys.argv[1:], 'h', ['help']) + except getopt.error as msg: + usage(1, msg) - for filename in args.files: + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + + for filename in args: print(('processing:', filename)) fp = open(filename, 'rb') d = marshal.load(fp) @@ -68,7 +82,7 @@ def main(): newd = {} for key, pckstr in d.items(): article = pickle.loads(pckstr, fix_imports=True, encoding='latin1') - newd[key] = pickle.dumps(article, protocol=4, fix_imports=True) + newd[key] = pickle.dumps(article) fp = open(filename + '.tmp', 'wb') marshal.dump(newd, fp) fp.close() @@ -78,5 +92,6 @@ def main(): print('You should now run "bin/check_perms -f"') + if __name__ == '__main__': main() diff --git a/bin/change_pw b/bin/change_pw index 22384da7..28df1aa1 100644 --- a/bin/change_pw +++ b/bin/change_pw @@ -66,14 +66,14 @@ Options: """ import sys -import argparse +import getopt import paths from Mailman import mm_cfg from Mailman import Utils from Mailman import MailList from Mailman import Errors -from Mailman.Message import Message +from Mailman import Message from Mailman import i18n _ = i18n._ @@ -82,21 +82,7 @@ C_ = i18n.C_ SPACE = ' ' -def parse_args(): - parser = argparse.ArgumentParser(description='Change a list\'s password.') - parser.add_argument('-a', '--all', action='store_true', - help='Change the password for all lists') - parser.add_argument('-d', '--domain', action='append', - help='Change the password for all lists in the virtual domain') - parser.add_argument('-l', '--listname', action='append', - help='Change the password only for the named list') - parser.add_argument('-p', '--password', - help='Use the supplied plain text password as the new password') - parser.add_argument('-q', '--quiet', action='store_true', - help='Don\'t notify list owners of the new password') - return parser.parse_args() - - + def usage(code, msg=''): if code: fd = sys.stderr @@ -108,6 +94,7 @@ def usage(code, msg=''): sys.exit(code) + _listcache = {} def openlist(listname): @@ -122,33 +109,45 @@ def openlist(listname): return mlist + def main(): + # Parse options try: - args = parse_args() - except SystemExit: - usage(1) + opts, args = getopt.getopt( + sys.argv[1:], 'ad:l:p:qh', + ['all', 'domain=', 'listname=', 'password=', 'quiet', 'help']) + except getopt.error as msg: + usage(1, msg) # defaults listnames = {} domains = {} - password = args.password - - if args.all: - for name in Utils.list_names(): - listnames[name] = 1 - elif args.listname: - for name in args.listname: - listnames[name.lower()] = 1 - elif args.domain: - for domain in args.domain: - domains[domain] = 1 - else: - usage(1, C_('No lists specified')) + password = None + quiet = 0 + + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-a', '--all'): + for name in Utils.list_names(): + listnames[name] = 1 + elif opt in ('-d', '--domain'): + domains[arg] = 1 + elif opt in ('-l', '--listname'): + listnames[arg.lower()] = 1 + elif opt in ('-p', '--password'): + password = arg + elif opt in ('-q', '--quiet'): + quiet = 1 + + if args: + strargs = SPACE.join(args) + usage(1, C_('Bad arguments: %(strargs)s')) if password is not None: if not password: usage(1, C_('Empty list passwords are not allowed')) - shapassword = Utils.sha_new(password).hexdigest() + shapassword = Utils.sha_new(password.encode()).hexdigest() if domains: for name in Utils.list_names(): @@ -168,7 +167,7 @@ def main(): if password is None: randompw = Utils.MakeRandomPassword( mm_cfg.ADMIN_PASSWORD_LENGTH) - shapassword = Utils.sha_new(randompw).hexdigest() + shapassword = Utils.sha_new(randompw.encode('utf-8')).hexdigest() notifypassword = randompw else: notifypassword = password @@ -180,15 +179,15 @@ def main(): # Notification print(C_('New %(listname)s password: %(notifypassword)s')) - if not args.quiet: + if not quiet: otrans = i18n.get_translation() i18n.set_language(mlist.preferred_language) try: hostname = mlist.host_name adminurl = mlist.GetScriptURL('admin', absolute=1) - msg = Mailman.Message.UserNotification( + msg = Message.UserNotification( mlist.owner[:], Utils.get_site_email(), - _('Your new %(listname)s list password') % {'listname': listname}, + _('Your new %(listname)s list password'), _('''\ The site administrator at %(hostname)s has changed the password for your mailing list %(listname)s. It is now @@ -199,13 +198,14 @@ Please be sure to use this for all future list administration. You may want to log in now to your list and change the password to something more to your liking. Visit your list admin page at -%(adminurl)s - -'''), mlist) - msg.send(mlist) + %(adminurl)s +'''), + mlist.preferred_language) finally: i18n.set_translation(otrans) + msg.send(mlist) + if __name__ == '__main__': main() diff --git a/bin/check_db b/bin/check_db index 18537819..d44e18fd 100755 --- a/bin/check_db +++ b/bin/check_db @@ -33,15 +33,27 @@ marshals. config.safety is a pickle written by 2.1a3 and beyond when the primary config.pck file could not be read. Usage: %(PROGRAM)s [options] [listname [listname ...]] + +Options: + + --all / -a + Check the databases for all lists. Otherwise only the lists named on + the command line are checked. + + --verbose / -v + Verbose output. The state of every tested file is printed. + Otherwise only corrupt files are displayed. + + --help / -h + Print this text and exit. """ import sys import os import errno -import argparse +import getopt import marshal import pickle -import re import paths from Mailman import mm_cfg @@ -52,151 +64,90 @@ from Mailman.i18n import C_ PROGRAM = sys.argv[0] -def parse_args(): - parser = argparse.ArgumentParser(description='Check a list\'s config database file for integrity.') - parser.add_argument('-a', '--all', action='store_true', default=True, - help='Check the databases for all lists (default)') - parser.add_argument('-v', '--verbose', action='store_true', - help='Verbose output. The state of every tested file is printed') - parser.add_argument('listnames', nargs='*', - help='List names to check (optional if --all is specified)') - return parser.parse_args() - - -def testfile(dbfile, listname=None, verbose=0): - """Test the integrity of a list's config database file.""" + +def usage(code, msg=''): + if code: + fd = sys.stderr + else: + fd = sys.stdout + print(C_(__doc__), file=fd) + if msg: + print(msg, file=fd) + sys.exit(code) + + + +def testfile(dbfile): + if dbfile.endswith('.db') or dbfile.endswith('.db.last'): + loadfunc = marshal.load + elif dbfile.endswith('.pck') or dbfile.endswith('.pck.last'): + loadfunc = pickle.load + else: + assert 0 + fp = open(dbfile,'rb') try: - if verbose: - print(' Loading file %s for list %s...' % - (os.path.basename(dbfile), listname or 'unknown')) - if dbfile.endswith('.pck'): - # Try to load the pickle file - try: - with open(dbfile, 'rb') as fp: - # Try loading with UTF-8 first, then fall back to latin1 - try: - fp.seek(0) - data = pickle.load(fp, fix_imports=True, encoding='utf-8') - if verbose: - print(' Successfully loaded with UTF-8 encoding') - except UnicodeDecodeError: - fp.seek(0) - data = pickle.load(fp, fix_imports=True, encoding='latin1') - if verbose: - print(' Successfully loaded with latin1 encoding') - - if verbose: - # Get pickle version info from the loaded data - if hasattr(data, '_protocol'): - protocol = data._protocol - print(' Pickle protocol: %d' % protocol) - else: - print(' Pickle protocol: unknown (not stored in data)') - except (EOFError, pickle.UnpicklingError) as e: - print(' Error loading file %s for list %s: %s' % - (os.path.basename(dbfile), listname or 'unknown', str(e))) - # Always print error for request.pck files, even if not verbose - if dbfile.endswith('request.pck'): - print(' File %s for list %s: ERROR - %s' % - (os.path.basename(dbfile), listname or 'unknown', str(e))) - raise - elif dbfile.endswith('.db'): - # Try to load the marshal file - try: - with open(dbfile, 'rb') as fp: - data = marshal.load(fp) - if verbose: - print(' Marshal format version: %d' % marshal.version) - if marshal.version < 2: - print(' WARNING: This file was likely written with Python 2') - print(' String data may need special handling for Python 3 compatibility') - except (EOFError, ValueError) as e: - print(' Error loading file %s for list %s: %s' % - (os.path.basename(dbfile), listname or 'unknown', str(e))) - # Always print error for request.pck files, even if not verbose - if dbfile.endswith('request.pck'): - print(' File %s for list %s: ERROR - %s' % - (os.path.basename(dbfile), listname or 'unknown', str(e))) - raise - if verbose: - print(' File %s for list %s: OK' % - (os.path.basename(dbfile), listname or 'unknown')) - except Exception as e: - print(' Error loading file %s for list %s: %s' % - (os.path.basename(dbfile), listname or 'unknown', str(e))) - # Always print error for request.pck files, even if not verbose - if dbfile.endswith('request.pck'): - print(' File %s for list %s: ERROR - %s' % - (os.path.basename(dbfile), listname or 'unknown', str(e))) - raise - + loadfunc(fp) + finally: + fp.close() + def main(): - args = parse_args() try: - if args.all or not args.listnames: + opts, args = getopt.getopt(sys.argv[1:], 'ahv', + ['all', 'verbose', 'help']) + except getopt.error as msg: + usage(1, msg) + + verbose = 0 + listnames = args + + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-v', '--verbose'): + verbose = 1 + elif opt in ('-a', '--all'): listnames = Utils.list_names() - if args.verbose: - print('Checking all lists (%d total)' % len(listnames)) - else: - listnames = args.listnames - if args.verbose: - print('Checking specified lists (%d total)' % len(listnames)) - - # Convert list names to lowercase and strip whitespace - listnames = [n.lower().strip() for n in listnames] - if not listnames: - print('No lists found to check.') - sys.exit(0) - - for listname in listnames: - if args.verbose: - print('\nProcessing list: %s' % listname) - - # Validate list name format - if len(re.sub(mm_cfg.ACCEPTABLE_LISTNAME_CHARACTERS, '', listname)) > 0: - print(' Invalid list name format: %s' % listname) - continue - - listdir = os.path.join(mm_cfg.LIST_DATA_DIR, listname) - if not os.path.exists(listdir): - if args.verbose: - print(' List directory does not exist: %s' % listdir) - continue - - # Check if any of the required files exist - required_files = [ - os.path.join(listdir, 'config.pck'), - os.path.join(listdir, 'config.pck.last'), - os.path.join(listdir, 'config.db'), - os.path.join(listdir, 'config.db.last'), - os.path.join(listdir, 'config.safety'), - ] - - has_required_files = any(os.path.exists(f) for f in required_files) - if not has_required_files: - if args.verbose: - print(' No configuration files found for list: %s' % listname) - continue - - # Check all possible database files - dbfiles = required_files + [ - os.path.join(listdir, 'request.pck'), - os.path.join(listdir, 'request.pck.bak'), - ] - - for dbfile in dbfiles: - if os.path.exists(dbfile): - try: - testfile(dbfile, listname, args.verbose) - except Exception as e: - print(' File %s: ERROR - %s' % (os.path.basename(dbfile), str(e))) - elif args.verbose: - print(' File %s: Not found' % os.path.basename(dbfile)) - except Exception as e: - print('Error getting list names: %s' % str(e)) - sys.exit(1) + listnames = [n.lower().strip() for n in listnames] + if not listnames: + print(C_('Nothing to do.')) + sys.exit(0) + + for listname in listnames: + if not Utils.list_exists(listname): + print(C_('No list named:'), listname) + continue + mlist = MailList(listname, lock=0) + pfile = os.path.join(mlist.fullpath(), 'config.pck') + plast = pfile + '.last' + dfile = os.path.join(mlist.fullpath(), 'config.db') + dlast = dfile + '.last' + if verbose: + print(C_('List:'), listname) + + for file in (pfile, plast, dfile, dlast): + status = 0 + try: + testfile(file) + except IOError as e: + # Don't report ENOENT unless we're in verbose mode + if verbose or e.errno != errno.ENOENT: + status = e + except Exception as e: + status = e + # Report errors + if status: + if isinstance(status, EnvironmentError): + # This already includes the file name + print(' ', status) + else: + print(' %s: %s' % (file, status)) + elif verbose: + print(C_(' %(file)s: okay')) + + + if __name__ == '__main__': main() diff --git a/bin/check_perms b/bin/check_perms index ec79c7a0..b9518c36 100755 --- a/bin/check_perms +++ b/bin/check_perms @@ -31,7 +31,7 @@ import sys import pwd import grp import errno -import argparse +import getopt from stat import * try: @@ -55,6 +55,7 @@ PROGRAM = sys.argv[0] # Gotta check the archives/private/*/database/* files + class State: FIX = False VERBOSE = False @@ -69,6 +70,7 @@ ARTICLEFILEPERMS = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP PRIVATEPERMS = QFILEPERMS + def statmode(path): return os.stat(path)[ST_MODE] @@ -89,6 +91,7 @@ def getgrgid(gid): return data + def checkwalk(arg, dirname, names): # Short-circuit duplicates if seen.has_key(dirname): @@ -351,20 +354,32 @@ def checkdata(): print() -def parse_args(): - parser = argparse.ArgumentParser(description='Check the permissions for the Mailman installation.') - parser.add_argument('-f', '--fix', action='store_true', - help='Fix all the permission problems found') - parser.add_argument('-v', '--verbose', action='store_true', - help='Be verbose') - return parser.parse_args() - + +def usage(code, msg=''): + if code: + fd = sys.stderr + else: + fd = sys.stdout + print(C_(__doc__), file=fd) + if msg: + print(msg, file=fd) + sys.exit(code) -def main(): - args = parse_args() - STATE.FIX = args.fix - STATE.VERBOSE = args.verbose +if __name__ == '__main__': + try: + opts, args = getopt.getopt(sys.argv[1:], 'fvh', + ['fix', 'verbose', 'help']) + except getopt.error as msg: + usage(1, msg) + + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-f', '--fix'): + STATE.FIX = True + elif opt in ('-v', '--verbose'): + STATE.VERBOSE = True checkall() checkarchives() @@ -375,17 +390,8 @@ def main(): checkadminpw() checkmta() - if STATE.ERRORS: - if STATE.FIX: - print(C_('Fixed %(STATE.ERRORS)d permission problems.')) - else: - print(C_('Found %(STATE.ERRORS)d permission problems.')) - print(C_('Run with -f to fix them.')) - sys.exit(1) + if not STATE.ERRORS: + print(C_('No problems found')) else: - print(C_('No permission problems found.')) - sys.exit(0) - - -if __name__ == '__main__': - main() + print(C_('Problems found:'), STATE.ERRORS) + print(C_('Re-run as %(MAILMAN_USER)s (or root) with -f flag to fix')) diff --git a/bin/cleanarch b/bin/cleanarch index 2be422bf..089d72dd 100644 --- a/bin/cleanarch +++ b/bin/cleanarch @@ -32,18 +32,37 @@ lines that start "From " but do not pass this stricter test are escaped with a > character. Usage: cleanarch [options] < inputfile > outputfile +Options: + -s n + --status=n + Print a # character every n lines processed + + -q / --quiet + Don't print changed line information to standard error. + + -n / --dry-run + Don't actually output anything. + + -h / --help + Print this message and exit """ from __future__ import print_function import re import sys -import argparse +import getopt import mailbox import paths from Mailman.i18n import C_ -cre = re.compile(mailbox.UnixMailbox._fromlinepattern) +# Taken from legacy module +og_fromlinepattern = (r"From \s*[^\s]+\s+\w\w\w\s+\w\w\w\s+\d?\d\s+" + r"\d?\d:\d\d(:\d\d)?(\s+[^\s]+)?\s+\d\d\d\d\s*" + r"[^\s]*\s*" + "$") + +cre = re.compile(og_fromlinepattern) # From RFC 2822, a header field name must contain only characters from 33-126 # inclusive, excluding colon. I.e. from oct 41 to oct 176 less oct 072. Must @@ -51,17 +70,19 @@ cre = re.compile(mailbox.UnixMailbox._fromlinepattern) fre = re.compile(r'[\041-\071\073-\176]+') -def parse_args(): - parser = argparse.ArgumentParser(description='Clean up an .mbox archive file.') - parser.add_argument('-s', '--status', type=int, - help='Print a # character every n lines processed') - parser.add_argument('-q', '--quiet', action='store_true', - help='Don\'t print changed line information to standard error') - parser.add_argument('-n', '--dry-run', action='store_true', - help='Don\'t actually output anything') - return parser.parse_args() + +def usage(code, msg=''): + if code: + fd = sys.stderr + else: + fd = sys.stdout + print(C_(__doc__), file=fd) + if msg: + print(msg, file=fd) + sys.exit(code) + def escape_line(line, lineno, quiet, output): if output: sys.stdout.write('>' + line) @@ -70,12 +91,34 @@ def escape_line(line, lineno, quiet, output): print(line[:-1], file=sys.stderr) + def main(): - args = parse_args() - - quiet = args.quiet - output = not args.dry_run - status = args.status + try: + opts, args = getopt.getopt( + sys.argv[1:], 'hqns:', + ['help', 'quiet', 'dry-run', 'status=']) + except getopt.error as msg: + usage(1, msg) + + quiet = False + output = True + status = -1 + + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-q', '--quiet'): + quiet = True + elif opt in ('-n', '--dry-run'): + output = False + elif opt in ('-s', '--status'): + try: + status = int(arg) + except ValueError: + usage(1, C_('Bad status number: %(arg)s')) + + if args: + usage(1) lineno = 0 statuscnt = 0 @@ -121,7 +164,7 @@ def main(): elif output: # Any old line sys.stdout.write(line) - if status and status > 0 and (lineno % status) == 0: + if status > 0 and (lineno % status) == 0: sys.stderr.write('#') statuscnt += 1 if statuscnt > 50: @@ -131,5 +174,6 @@ def main(): print(C_('%(messages)d messages found'), file=sys.stderr) + if __name__ == '__main__': main() diff --git a/bin/clone_member b/bin/clone_member index e0d6c65d..6b015335 100755 --- a/bin/clone_member +++ b/bin/clone_member @@ -66,7 +66,7 @@ Where: """ import sys -import argparse +import getopt import paths from Mailman import MailList @@ -75,6 +75,19 @@ from Mailman import Errors from Mailman.i18n import C_ + +def usage(code, msg=''): + if code: + fd = sys.stderr + else: + fd = sys.stdout + print(C_(__doc__), file=fd) + if msg: + print(fd, msg, file=fd) + sys.exit(code) + + + def dolist(mlist, options): SPACE = ' ' if not options.quiet: @@ -97,6 +110,7 @@ def dolist(mlist, options): if foundp: newowners[options.toaddr] = 1 newowners = newowners.keys() + newowners = list(newowners) newowners.sort() if options.modify: mlist.owner = newowners @@ -138,57 +152,75 @@ def dolist(mlist, options): print(C_(' original address removed:'), options.fromaddr) -def parse_args(): - parser = argparse.ArgumentParser(description='Clone a member address.') - parser.add_argument('-l', '--listname', action='append', - help='Check and modify only the named mailing lists') - parser.add_argument('-r', '--remove', action='store_true', - help='Remove the old address from the mailing list after it\'s been cloned') - parser.add_argument('-a', '--admin', action='store_true', - help='Scan the list admin addresses for the old address, and clone or change them too') - parser.add_argument('-q', '--quiet', action='store_true', - help='Do the modifications quietly') - parser.add_argument('-n', '--nomodify', action='store_true', - help='Print what would be done, but don\'t actually do it') - parser.add_argument('fromaddr', - help='The old address of the user') - parser.add_argument('toaddr', - help='The new address of the user') - return parser.parse_args() - - + def main(): - args = parse_args() - + # default options + class Options: + listnames = None + remove = 0 + admintoo = 0 + quiet = 0 + modify = 1 + + # scan sysargs + try: + opts, args = getopt.getopt( + sys.argv[1:], 'arl:qnh', + ['admin', 'remove', 'listname=', 'quiet', 'nomodify', 'help']) + except getopt.error as msg: + usage(1, msg) + + options = Options() + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-q', '--quiet'): + options.quiet = 1 + elif opt in ('-n', '--nomodify'): + options.modify = 0 + elif opt in ('-a', '--admin'): + options.admintoo = 1 + elif opt in ('-r', '--remove'): + options.remove = 1 + elif opt in ('-l', '--listname'): + if options.listnames is None: + options.listnames = [] + options.listnames.append(arg.lower()) + + # further options and argument processing + if not options.modify: + options.quiet = 0 + + if len(args) != 2: + usage(1) + fromaddr = args[0] + toaddr = args[1] + # validate and normalize the target address try: - Utils.ValidateEmail(args.toaddr) + Utils.ValidateEmail(toaddr) except Errors.EmailAddressError: - print(C_('Invalid email address:'), args.toaddr, file=sys.stderr) - sys.exit(1) + usage(1, C_('Not a valid email address: %(toaddr)s')) + lfromaddr = fromaddr.lower() + options.toaddr = toaddr + options.fromaddr = fromaddr + options.lfromaddr = lfromaddr - # normalize the addresses - args.lfromaddr = args.fromaddr.lower() - args.toaddr = args.toaddr.lower() + if options.listnames is None: + options.listnames = Utils.list_names() - # get the list of lists to process - if args.listname: - listnames = args.listname - else: - listnames = Utils.list_names() - - # process each list - for listname in listnames: + for listname in options.listnames: try: - mlist = MailList(listname, lock=0) - except Errors.MMUnknownListError: - print(C_('Unknown list:'), listname, file=sys.stderr) + mlist = MailList.MailList(listname) + except Errors.MMListError as e: + print(C_('Error opening list "%(listname)s", skipping.\n%(e)s')) continue try: - dolist(mlist, args) + dolist(mlist, options) finally: + mlist.Save() mlist.Unlock() - + if __name__ == '__main__': main() diff --git a/bin/config_list b/bin/config_list index 86600d04..65daca30 100644 --- a/bin/config_list +++ b/bin/config_list @@ -63,10 +63,9 @@ The options -o and -i are mutually exclusive. """ import sys -import argparse import re import time -import logging +import getopt import paths from Mailman import mm_cfg @@ -77,13 +76,6 @@ from Mailman import i18n from typing import Tuple -# Set up logging -logging.basicConfig( - level=logging.DEBUG, - format='%(asctime)s - %(levelname)s - %(message)s', - filename='/tmp/mailman_config_list.log' -) - _ = i18n._ C_ = i18n.C_ @@ -91,6 +83,7 @@ NL = '\n' nonasciipat = re.compile(r'[\x80-\xff]') + def usage(code, msg=''): if code: fd = sys.stderr @@ -102,6 +95,7 @@ def usage(code, msg=''): sys.exit(code) + def do_output(listname, outfile): closep = 0 try: @@ -224,6 +218,7 @@ def do_list_categories(mlist, k, subcat, outfp): print(file=outfp) + def getPropertyMap(mlist): guibyprop = {} categories = mlist.GetConfigCategories() @@ -264,241 +259,108 @@ def do_input(listname, infile, checkonly, verbose): savelist = 0 guibyprop = getPropertyMap(mlist) try: - # Read the input file and parse it - with open(infile) as fp: - config = {} - for line in fp: - line = line.strip() - if line and not line.startswith('#'): - key, value = line.split('=', 1) - config[key.strip()] = value.strip() - - # Get configuration items using GetConfigInfo() - for category in mm_cfg.ADMIN_CATEGORIES: - subcats = mlist.GetConfigSubCategories(category) - if subcats is None: - info = mlist.GetConfigInfo(category, None) - if info: - for data in info[1:]: - if not isinstance(data, Tuple): - continue - key = data[0] - if key in config: - if verbose: - print(C_('attribute "%(key)s" changed') % {'key': key}, file=sys.stderr) - missing = [] - gui, wtype = guibyprop.get(key, (missing, missing)) - if gui is missing: - # This isn't an official property of the list, but that's - # okay, we'll just restore it the old fashioned way - print(C_('Non-standard property restored: %(key)s') % {'key': key}, file=sys.stderr) - setattr(mlist, key, config[key]) - else: - # BAW: This uses non-public methods. This logic taken from - # the guts of GUIBase.handleForm(). - try: - validval = gui._getValidValue(mlist, key, wtype, config[key]) - except ValueError: - print(C_('Invalid value for property: %(key)s') % {'key': key}, file=sys.stderr) - except Errors.EmailAddressError: - print(C_('Bad email address for option %(key)s: %(value)s') % - {'key': key, 'value': config[key]}, file=sys.stderr) - else: - # BAW: Horrible hack, but then this is special cased - # everywhere anyway. :( Privacy._setValue() knows that - # when ALLOW_OPEN_SUBSCRIBE is false, the web values are - # 0, 1, 2 but these really should be 1, 2, 3, so it adds - # one. But we really do provide [0..3] so we need to undo - # the hack that _setValue adds. :( :( - if key == 'subscribe_policy' and \ - not mm_cfg.ALLOW_OPEN_SUBSCRIBE: - validval -= 1 - # BAW: Another horrible hack. This one is just too hard - # to fix in a principled way in Mailman 2.1 - elif key == 'new_member_options': - # Because this is a Checkbox, _getValidValue() - # transforms the value into a list of one item. - validval = validval[0] - validval = [bitfield for bitfield, bitval - in list(mm_cfg.OPTINFO.items()) - if validval & bitval] - gui._setValue(mlist, key, validval, fakedoc) - else: - for subcat, _ in subcats: - info = mlist.GetConfigInfo(category, subcat) - if info: - for data in info[1:]: - if not isinstance(data, Tuple): - continue - key = data[0] - if key in config: - if verbose: - print(C_('attribute "%(key)s" changed') % {'key': key}, file=sys.stderr) - missing = [] - gui, wtype = guibyprop.get(key, (missing, missing)) - if gui is missing: - # This isn't an official property of the list, but that's - # okay, we'll just restore it the old fashioned way - print(C_('Non-standard property restored: %(key)s') % {'key': key}, file=sys.stderr) - setattr(mlist, key, config[key]) - else: - # BAW: This uses non-public methods. This logic taken from - # the guts of GUIBase.handleForm(). - try: - validval = gui._getValidValue(mlist, key, wtype, config[key]) - except ValueError: - print(C_('Invalid value for property: %(key)s') % {'key': key}, file=sys.stderr) - except Errors.EmailAddressError: - print(C_('Bad email address for option %(key)s: %(value)s') % - {'key': key, 'value': config[key]}, file=sys.stderr) - else: - # BAW: Horrible hack, but then this is special cased - # everywhere anyway. :( Privacy._setValue() knows that - # when ALLOW_OPEN_SUBSCRIBE is false, the web values are - # 0, 1, 2 but these really should be 1, 2, 3, so it adds - # one. But we really do provide [0..3] so we need to undo - # the hack that _setValue adds. :( :( - if key == 'subscribe_policy' and \ - not mm_cfg.ALLOW_OPEN_SUBSCRIBE: - validval -= 1 - # BAW: Another horrible hack. This one is just too hard - # to fix in a principled way in Mailman 2.1 - elif key == 'new_member_options': - # Because this is a Checkbox, _getValidValue() - # transforms the value into a list of one item. - validval = validval[0] - validval = [bitfield for bitfield, bitval - in list(mm_cfg.OPTINFO.items()) - if validval & bitval] - gui._setValue(mlist, key, validval, fakedoc) + globals = {'mlist': mlist} + # Any exception that occurs in execfile() will cause the list to not + # be saved, but any other problems are not save-fatal. + exec(open(infile).read(), globals) savelist = 1 + for k, v in list(globals.items()): + if k in ('mlist', '__builtins__'): + continue + if not hasattr(mlist, k): + print(C_('attribute "%(k)s" ignored'), file=sys.stderr) + continue + if verbose: + print(C_('attribute "%(k)s" changed'), file=sys.stderr) + missing = [] + gui, wtype = guibyprop.get(k, (missing, missing)) + if gui is missing: + # This isn't an official property of the list, but that's + # okay, we'll just restore it the old fashioned way + print(C_( + 'Non-standard property restored: %(k)s'), file=sys.stderr) + setattr(mlist, k, v) + else: + # BAW: This uses non-public methods. This logic taken from + # the guts of GUIBase.handleForm(). + try: + validval = gui._getValidValue(mlist, k, wtype, v) + except ValueError: + print(C_( + 'Invalid value for property: %(k)s'), file=sys.stderr) + except Errors.EmailAddressError: + print(C_( + 'Bad email address for option %(k)s: %(v)s'), file=sys.stderr) + else: + # BAW: Horrible hack, but then this is special cased + # everywhere anyway. :( Privacy._setValue() knows that + # when ALLOW_OPEN_SUBSCRIBE is false, the web values are + # 0, 1, 2 but these really should be 1, 2, 3, so it adds + # one. But we really do provide [0..3] so we need to undo + # the hack that _setValue adds. :( :( + if k == 'subscribe_policy' and \ + not mm_cfg.ALLOW_OPEN_SUBSCRIBE: + validval -= 1 + # BAW: Another horrible hack. This one is just too hard + # to fix in a principled way in Mailman 2.1 + elif k == 'new_member_options': + # Because this is a Checkbox, _getValidValue() + # transforms the value into a list of one item. + validval = validval[0] + validval = [bitfield for bitfield, bitval + in list(mm_cfg.OPTINFO.items()) + if validval & bitval] + gui._setValue(mlist, k, validval, fakedoc) + # BAW: when to do gui._postValidate()??? finally: if savelist and not checkonly: mlist.Save() mlist.Unlock() + def main(): - logging.debug("Starting config_list") - parser = argparse.ArgumentParser(description='Configure a mailing list.') - parser.add_argument('listname', help='Name of the mailing list') - parser.add_argument('-i', '--input-file', help='File containing configuration') - parser.add_argument('-o', '--output-file', help='File to write configuration to') - parser.add_argument('-a', '--all', action='store_true', - help='Show all configuration options') - parser.add_argument('-v', '--verbose', action='store_true', - help='Show verbose output') - parser.add_argument('-c', '--category', help='Show options in specific category') - parser.add_argument('-s', '--subcategory', help='Show options in specific subcategory') - - args = parser.parse_args() - logging.debug(f"Parsed arguments: {args}") - - try: - logging.debug(f"Attempting to load list: {args.listname}") - mlist = MailList.MailList(args.listname, lock=1) - logging.debug("Successfully loaded list") - except Errors.MMUnknownListError: - logging.error(f"List not found: {args.listname}") - usage(1, _('No such list "%(listname)s"')) - return - try: - logging.debug("Getting configuration categories") - categories = mlist.GetConfigCategories() - if not categories: - logging.error("No configuration categories found") - print(_("No configuration categories available")) - return - - logging.debug(f"Got categories: {list(categories.keys())}") - - # Get configuration items using GetConfigInfo() - for category in mm_cfg.ADMIN_CATEGORIES: - logging.debug(f"Processing category: {category}") - if category not in categories: - logging.warning(f"Category {category} not found in available categories") - continue - - subcats = mlist.GetConfigSubCategories(category) - logging.debug(f"Got subcategories: {subcats}") - - if subcats is None: - logging.debug(f"Getting config info for category {category}") - info = mlist.GetConfigInfo(category, None) - if not info: - logging.warning(f"No configuration info found for category {category}") - continue - - logging.debug(f"Got config info: {info is not None}") - for data in info[1:]: - if not isinstance(data, Tuple): - continue - try: - key = data[0] - if not args.all and key.startswith('_'): - continue - if args.category and not key.startswith(args.category + '_'): - continue - if args.subcategory and not key.startswith(args.category + '_' + args.subcategory + '_'): - continue - - # Use getattr with a default value instead of direct access - value = getattr(mlist, key, None) - if value is None: - logging.warning(f"Configuration item {key} not found") - continue - - if args.verbose: - print(f"{key}={value}") - else: - print(key) - except Exception as e: - logging.error(f"Error processing configuration item: {str(e)}") - continue - else: - for subcat, _ in subcats: - logging.debug(f"Getting config info for category {category}, subcategory {subcat}") - info = mlist.GetConfigInfo(category, subcat) - if not info: - logging.warning(f"No configuration info found for category {category}, subcategory {subcat}") - continue - - logging.debug(f"Got config info: {info is not None}") - for data in info[1:]: - if not isinstance(data, Tuple): - continue - try: - key = data[0] - if not args.all and key.startswith('_'): - continue - if args.category and not key.startswith(args.category + '_'): - continue - if args.subcategory and not key.startswith(args.category + '_' + args.subcategory + '_'): - continue - - # Use getattr with a default value instead of direct access - value = getattr(mlist, key, None) - if value is None: - logging.warning(f"Configuration item {key} not found") - continue - - if args.verbose: - print(f"{key}={value}") - else: - print(key) - except Exception as e: - logging.error(f"Error processing configuration item: {str(e)}") - continue - - except Exception as e: - logging.error(f"Error occurred: {str(e)}", exc_info=True) - raise - finally: - logging.debug("Unlocking list") - mlist.Unlock() - logging.debug("Finished config_list") + opts, args = getopt.getopt( + sys.argv[1:], 'ci:o:vh', + ['checkonly', 'inputfile=', 'outputfile=', 'verbose', 'help']) + except getopt.error as msg: + usage(1, msg) + + # defaults + infile = None + outfile = None + checkonly = 0 + verbose = 0 + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-o', '--outputfile'): + outfile = arg + elif opt in ('-i', '--inputfile'): + infile = arg + elif opt in ('-c', '--checkonly'): + checkonly = 1 + elif opt in ('-v', '--verbose'): + verbose = 1 + + # sanity check + if infile is not None and outfile is not None: + usage(1, C_('Only one of -i or -o is allowed')) + if infile is None and outfile is None: + usage(1, C_('One of -i or -o is required')) + + # get the list name + if len(args) != 1: + usage(1, C_('List name is required')) + listname = args[0].lower().strip() + + if outfile: + do_output(listname, outfile) + else: + do_input(listname, infile, checkonly, verbose) + if __name__ == '__main__': main() diff --git a/bin/discard b/bin/discard index 333d0be9..2e190def 100644 --- a/bin/discard +++ b/bin/discard @@ -36,7 +36,7 @@ Options: import os import re import sys -import argparse +import getopt import paths from Mailman import mm_cfg @@ -46,19 +46,33 @@ from Mailman.i18n import C_ cre = re.compile(r'heldmsg-(?P.*)-(?P[0-9]+)\.(pck|txt)$') -def parse_args(): - parser = argparse.ArgumentParser(description='Discard held messages.') - parser.add_argument('-q', '--quiet', action='store_true', - help='Don\'t print status messages') - parser.add_argument('files', nargs='*', - help='Files containing held messages to discard') - return parser.parse_args() + +def usage(code, msg=''): + if code: + fd = sys.stderr + else: + fd = sys.stdout + print(C_(__doc__), file=fd) + if msg: + print(msg, file=fd) + sys.exit(code) + def main(): - args = parse_args() - - files = args.files + try: + opts, args = getopt.getopt(sys.argv[1:], 'hq', ['help', 'quiet']) + except getopt.error as msg: + usage(1, msg) + + quiet = False + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-q', '--quiet'): + quiet = True + + files = args if not files: print(C_('Nothing to do.')) @@ -88,7 +102,7 @@ def main(): for id in ids: # No comment, no preserve, no forward, no forwarding address mlist.HandleRequest(id, mm_cfg.DISCARD, '', False, False, '') - if not args.quiet: + if not quiet: print(C_( 'Discarded held msg #%(id)s for list %(listname)s')) mlist.Save() @@ -96,5 +110,6 @@ def main(): mlist.Unlock() + if __name__ == '__main__': main() diff --git a/bin/dumpdb b/bin/dumpdb index a71b8ef4..7d8ac590 100644 --- a/bin/dumpdb +++ b/bin/dumpdb @@ -1,4 +1,4 @@ -#! @PYTHON@ +#! /usr/bin/python3 # # Copyright (C) 1998-2018 by the Free Software Foundation, Inc. # @@ -46,7 +46,7 @@ Python pickle. In either case, if you want to override the default assumption """ import sys -import argparse +import getopt import pprint import pickle import marshal @@ -54,50 +54,61 @@ import marshal import paths # Import this /after/ paths so that the sys.path is properly hacked from Mailman.i18n import C_ +from Mailman import Utils PROGRAM = sys.argv[0] COMMASPACE = ', ' - -def parse_args(): - parser = argparse.ArgumentParser(description='Dump the contents of any Mailman `database\' file.') - group = parser.add_mutually_exclusive_group() - group.add_argument('-m', '--marshal', action='store_true', - help='Assume the file contains a Python marshal') - group.add_argument('-p', '--pickle', action='store_true', - help='Assume the file contains a Python pickle') - parser.add_argument('-n', '--noprint', action='store_true', - help='Don\'t attempt to pretty print the object') - parser.add_argument('filename', - help='The database file to dump') - return parser.parse_args() - - -def load_pickle(fp): - """Load a pickle file with Python 2/3 compatibility.""" - try: - return pickle.load(fp, fix_imports=True, encoding='latin1') - except Exception as e: - print('Error loading pickle file: %s' % e) - return None + +def usage(code, msg=''): + if code: + fd = sys.stderr + else: + fd = sys.stdout + print(C_(__doc__) % globals(), file=fd) + if msg: + print(msg, file=fd) + sys.exit(code) + def main(): - args = parse_args() - - # Determine file type - if args.marshal: - filetype = 1 # marshal - elif args.pickle: - filetype = 0 # pickle + try: + opts, args = getopt.getopt(sys.argv[1:], 'mphn', + ['marshal', 'pickle', 'help', 'noprint']) + except getopt.error as msg: + usage(1, msg) + + # Options. + # None == guess, 0 == pickle, 1 == marshal + filetype = None + doprint = True + + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-p', '--pickle'): + filetype = 0 + elif opt in ('-m', '--marshal'): + filetype = 1 + elif opt in ('-n', '--noprint'): + doprint = False + + if len(args) < 1: + usage(1, C_('No filename given.')) + elif len(args) > 1: + pargs = COMMASPACE.join(args) + usage(1, C_('Bad arguments: %(pargs)s')) else: - if args.filename.endswith('.db'): - filetype = 1 # marshal - elif args.filename.endswith('.pck'): - filetype = 0 # pickle + filename = args[0] + + if filetype is None: + if filename.endswith('.db'): + filetype = 1 + elif filename.endswith('.pck'): + filetype = 0 else: - print(C_('Please specify either -p or -m.'), file=sys.stderr) - sys.exit(1) + usage(1, C_('Please specify either -p or -m.')) # Handle dbs pp = pprint.PrettyPrinter(indent=4) @@ -105,41 +116,29 @@ def main(): load = marshal.load typename = 'marshal' else: - load = load_pickle + load = pickle.load typename = 'pickle' - fp = open(args.filename, 'rb') + fp = open(filename, 'rb') m = [] try: cnt = 1 - if not args.noprint: + if doprint: print(C_('[----- start %(typename)s file -----]')) while True: try: - obj = load(fp) - # Handle string/bytes conversion - if isinstance(obj, bytes): - obj = obj.decode('utf-8', 'replace') - elif isinstance(obj, dict): - new_obj = {} - for k, v in obj.items(): - if isinstance(k, bytes): - k = k.decode('utf-8', 'replace') - if isinstance(v, bytes): - v = v.decode('utf-8', 'replace') - new_obj[k] = v - obj = new_obj - elif isinstance(obj, list): - new_obj = [] - for item in obj: - if isinstance(item, bytes): - item = item.decode('utf-8', 'replace') - new_obj.append(item) - obj = new_obj + if typename == 'pickle': + obj = Utils.load_pickle(fp) + if obj is None: + if doprint: + print(C_('[----- end %(typename)s file -----]')) + break + else: + obj = load(fp, encoding='utf-8') except EOFError: - if not args.noprint: + if doprint: print(C_('[----- end %(typename)s file -----]')) break - if not args.noprint: + if doprint: print(C_('<----- start object %(cnt)s ----->')) if isinstance(obj, str): print(obj) @@ -152,5 +151,6 @@ def main(): return m + if __name__ == '__main__': msg = main() diff --git a/bin/export.py b/bin/export.py index 16bf8b06..f9ff5f0e 100644 --- a/bin/export.py +++ b/bin/export.py @@ -26,7 +26,6 @@ import codecs import datetime import optparse -import pickle from xml.sax.saxutils import escape @@ -103,8 +102,6 @@ def _makeattrs(self, tagattrs): if v is None: v = '' else: - if isinstance(v, bytes): - v = v.decode('utf-8', 'replace') v = escape(str(v)) attrs.append('%s="%s"' % (k, v)) return SPACE.join(attrs) @@ -149,8 +146,6 @@ def _element(self, _name, _value=None, **_attributes): if _value is None: print('<%s%s/>' % (_name, attrs), file=self._fp) else: - if isinstance(_value, bytes): - _value = _value.decode('utf-8', 'replace') value = escape(str(_value)) print('<%s%s>%s' % (_name, attrs, value, _name), file=self._fp) @@ -184,13 +179,9 @@ def _do_list_categories(self, mlist, k, subcat=None): if isinstance(value, list): self._push_element('option', name=varname, type=widget_type) for v in value: - if isinstance(v, bytes): - v = v.decode('utf-8', 'replace') self._element('value', v) self._pop_element('option') else: - if isinstance(value, bytes): - value = value.decode('utf-8', 'replace') self._element('option', value, name=varname, type=widget_type) def _dump_list(self, mlist, password_scheme): @@ -268,29 +259,6 @@ def _dump_list(self, mlist, password_scheme): self._pop_element('roster') self._pop_element('list') - def _do_list_archives(self, mlist): - # Get the archive directory - archive_dir = os.path.join(mlist.archive_dir(), 'private') - if not os.path.exists(archive_dir): - return - # Get all the archive files - for filename in os.listdir(archive_dir): - if filename.endswith('.mbox'): - if isinstance(filename, bytes): - filename = filename.decode('utf-8', 'replace') - self._push_element('archive', filename=filename) - # Get the archive file's metadata - metadata_file = os.path.join(archive_dir, filename + '.metadata') - if os.path.exists(metadata_file): - metadata = self.load_metadata(metadata_file) - for key, value in metadata.items(): - if isinstance(key, bytes): - key = key.decode('utf-8', 'replace') - if isinstance(value, bytes): - value = value.decode('utf-8', 'replace') - self._element('metadata', str(value), name=key) - self._pop_element('archive') - def dump(self, listnames, password_scheme): print('', file=self._fp) self._push_element('mailman', **{ @@ -304,24 +272,12 @@ def dump(self, listnames, password_scheme): print(C_('No such list: %(listname)s'), file=sys.stderr) continue self._dump_list(mlist, password_scheme) - self._do_list_archives(mlist) self._pop_element('mailman') def close(self): while self._stack: self._pop_element() - def load_metadata(self, filename): - """Load metadata from a pickle file.""" - try: - with open(filename, 'rb') as fp: - # Use protocol 2 for Python 2/3 compatibility - metadata = pickle.load(fp, fix_imports=True, encoding='latin1') - return metadata - except Exception as e: - print('Error loading metadata from %s: %s' % (filename, e)) - return None - def no_password(password): @@ -333,15 +289,19 @@ def plaintext_password(password): def sha_password(password): + if isinstance(password, str): + password = password.encode() h = Utils.sha_new(password) - return '{SHA}' + base64.b64encode(h.digest()) + return '{SHA}' + base64.b64encode(h.digest()).decode('utf-8') def ssha_password(password): + if isinstance(password, str): + password = password.encode() salt = os.urandom(SALT_LENGTH) h = Utils.sha_new(password) h.update(salt) - return '{SSHA}' + base64.b64encode(h.digest() + salt) + return '{SSHA}' + base64.b64encode(h.digest() + salt).decode('utf-8') SCHEMES = { diff --git a/bin/find_member b/bin/find_member index bcb73ccb..25c5b1e2 100755 --- a/bin/find_member +++ b/bin/find_member @@ -49,7 +49,7 @@ specifically excluded. Regular expression syntax is Perl5-like, using the Python re module. Complete specifications are at: -https://docs.python.org/3/library/re.html +https://docs.python.org/2/library/re.html Address matches are case-insensitive, but case-preserved addresses are displayed. @@ -59,7 +59,7 @@ from builtins import * from builtins import object import sys import re -import argparse +import getopt import paths from Mailman import Utils @@ -71,6 +71,7 @@ AS_MEMBER = 0x01 AS_OWNER = 0x02 + def usage(code, msg=''): if code: fd = sys.stderr @@ -82,6 +83,7 @@ def usage(code, msg=''): sys.exit(code) + def scanlists(options): cres = [] for r in options.regexps: @@ -118,34 +120,46 @@ def scanlists(options): return matches + class Options(object): listnames = Utils.list_names() owners = None def main(): - parser = argparse.ArgumentParser(description='Find all lists that a member\'s address is on.') - parser.add_argument('regexps', nargs='+', help='Python regular expression to match against') - parser.add_argument('-l', '--listname', action='append', - help='Include only the named list in the search') - parser.add_argument('-x', '--exclude', action='append', - help='Exclude the named list from the search') - parser.add_argument('-w', '--owners', action='store_true', - help='Search list owners as well as members') - - args = parser.parse_args() + try: + opts, args = getopt.getopt(sys.argv[1:], 'l:x:wh', + ['listname=', 'exclude=', 'owners', + 'help']) + except getopt.error as msg: + usage(1, msg) options = Options() - if args.listname: - options.listnames = [name.lower() for name in args.listname] - if args.exclude: - for ex in args.exclude: - try: - options.listnames.remove(ex.lower()) - except ValueError: - pass - options.owners = args.owners - options.regexps = args.regexps + loptseen = 0 + excludes = [] + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-l', '--listname'): + if not loptseen: + options.listnames = [] + loptseen = 1 + options.listnames.append(arg.lower()) + elif opt in ('-x', '--exclude'): + excludes.append(arg.lower()) + elif opt in ('-w', '--owners'): + options.owners = 1 + + for ex in excludes: + try: + options.listnames.remove(ex) + except ValueError: + pass + + if not args: + usage(1, C_('Search regular expression required')) + + options.regexps = args if not options.listnames: print(C_('No lists to search')) @@ -166,5 +180,6 @@ def main(): print(' ', name, C_('(as owner)')) + if __name__ == '__main__': main() diff --git a/bin/fix_url.py b/bin/fix_url.py index dce6a8ba..243f4f20 100644 --- a/bin/fix_url.py +++ b/bin/fix_url.py @@ -40,22 +40,14 @@ from __future__ import print_function import sys -import argparse +import getopt import paths from Mailman import mm_cfg from Mailman.i18n import C_ -def parse_args(args): - parser = argparse.ArgumentParser(description='Reset a list\'s web_page_url attribute to the default setting.') - parser.add_argument('-u', '--urlhost', - help='Look up urlhost in the virtual host table and set the web_page_url and host_name attributes') - parser.add_argument('-v', '--verbose', action='store_true', - help='Print what the script is doing') - return parser.parse_args(args) - - + def usage(code, msg=''): print(C_(__doc__.replace('%', '%%'))) if msg: @@ -63,28 +55,37 @@ def usage(code, msg=''): sys.exit(code) + def fix_url(mlist, *args): try: - args = parse_args(args) - except SystemExit: - usage(1) + opts, args = getopt.getopt(args, 'u:v', ['urlhost=', 'verbose']) + except getopt.error as msg: + usage(1, msg) + + verbose = 0 + urlhost = mailhost = None + for opt, arg in opts: + if opt in ('-u', '--urlhost'): + urlhost = arg + elif opt in ('-v', '--verbose'): + verbose = 1 # Make sure list is locked. if not mlist.Locked(): - if args.verbose: + if verbose: print(C_('Locking list')) mlist.Lock() - if args.urlhost: - web_page_url = mm_cfg.DEFAULT_URL_PATTERN % args.urlhost - mailhost = mm_cfg.VIRTUAL_HOSTS.get(args.urlhost.lower(), args.urlhost) + if urlhost: + web_page_url = mm_cfg.DEFAULT_URL_PATTERN % urlhost + mailhost = mm_cfg.VIRTUAL_HOSTS.get(urlhost.lower(), urlhost) else: web_page_url = mm_cfg.DEFAULT_URL_PATTERN % mm_cfg.DEFAULT_URL_HOST mailhost = mm_cfg.DEFAULT_EMAIL_HOST - if args.verbose: + if verbose: print(C_('Setting web_page_url to: %(web_page_url)s')) mlist.web_page_url = web_page_url - if args.verbose: + if verbose: print(C_('Setting host_name to: %(mailhost)s')) mlist.host_name = mailhost print('Saving list') @@ -92,5 +93,6 @@ def fix_url(mlist, *args): mlist.Unlock() + if __name__ == '__main__': usage(0) diff --git a/bin/genaliases b/bin/genaliases index dfedc8db..b8cca103 100644 --- a/bin/genaliases +++ b/bin/genaliases @@ -34,7 +34,7 @@ Options: import os import sys -import argparse +import getopt import paths # path hacking from Mailman import mm_cfg @@ -42,14 +42,7 @@ from Mailman import Utils from Mailman import MailList from Mailman.i18n import C_ - -def parse_args(): - parser = argparse.ArgumentParser(description='Regenerate Mailman specific aliases from scratch.') - parser.add_argument('-q', '--quiet', action='store_true', - help='Reduce verbosity of MTA output') - return parser.parse_args() - - + def usage(code, msg=''): if code: fd = sys.stderr @@ -61,10 +54,22 @@ def usage(code, msg=''): sys.exit(code) + def main(): + quiet = False try: - args = parse_args() - except SystemExit: + opts, args = getopt.getopt(sys.argv[1:], 'hq', + ['help', 'quiet']) + except getopt.error as msg: + usage(1, msg) + + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-q', '--quiet'): + quiet = True + + if args: usage(1) if not mm_cfg.MTA: @@ -93,13 +98,13 @@ def main(): try: MTA.clear() if not mlists: - MTA.create(None, nolock=True, quiet=args.quiet) + MTA.create(None, nolock=True, quiet=quiet) else: for hostname, vlists in mlists.items(): for mlist in vlists: - MTA.create(mlist, nolock=True, quiet=args.quiet) + MTA.create(mlist, nolock=True, quiet=quiet) # Be verbose for only the first printed list - args.quiet = True + quiet = True finally: lock.unlock(unconditionally=True) # Postfix has not been updating the maps. This call will do it. @@ -107,5 +112,6 @@ def main(): os.umask(omask) + if __name__ == '__main__': main() diff --git a/bin/inject b/bin/inject index 5c67100c..2245f778 100644 --- a/bin/inject +++ b/bin/inject @@ -43,7 +43,7 @@ from __future__ import print_function import sys import os -import argparse +import getopt import paths from Mailman import mm_cfg @@ -52,40 +52,58 @@ from Mailman import Post from Mailman.i18n import C_ -def parse_args(): - parser = argparse.ArgumentParser(description='Inject a message from a file into Mailman\'s incoming queue.') - parser.add_argument('-l', '--listname', required=True, - help='The name of the list to inject this message to') - parser.add_argument('-q', '--queue', - help='The name of the queue to inject the message to') - parser.add_argument('filename', nargs='?', - help='The name of the plaintext message file to inject') - return parser.parse_args() + +def usage(code, msg=''): + if code: + fd = sys.stderr + else: + fd = sys.stdout + print(C_(__doc__), file=fd) + if msg: + print(msg, file=fd) + sys.exit(code) + def main(): - args = parse_args() + try: + opts, args = getopt.getopt( + sys.argv[1:], 'hl:q:L', + ['help', 'listname=', 'queue=', 'showqnames']) + except getopt.error as msg: + usage(1, msg) qdir = mm_cfg.INQUEUE_DIR - if args.queue: - qdir = os.path.join(mm_cfg.QUEUE_DIR, args.queue) - if not os.path.isdir(qdir): - print(C_('Bad queue directory: %(qdir)s'), file=sys.stderr) - sys.exit(1) - - listname = args.listname.lower() - if not Utils.list_exists(listname): - print(C_('No such list: %(listname)s'), file=sys.stderr) - sys.exit(1) - - if args.filename: - with open(args.filename) as fp: - msgtext = fp.read() - else: + listname = None + + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-q', '--queue'): + qdir = os.path.join(mm_cfg.QUEUE_DIR, arg) + if not os.path.isdir(qdir): + usage(1, C_('Bad queue directory: %(qdir)s')) + elif opt in ('-l', '--listname'): + listname = arg.lower() + + if listname is None: + usage(1, C_('A list name is required')) + elif not Utils.list_exists(listname): + usage(1, C_('No such list: %(listname)s')) + + if len(args) == 0: + # Use standard input msgtext = sys.stdin.read() + elif len(args) == 1: + fp = open(args[0]) + msgtext = fp.read() + fp.close() + else: + usage(1) Post.inject(listname, msgtext, qdir=qdir) + if __name__ == '__main__': main() diff --git a/bin/list_admins b/bin/list_admins index f764b9a1..57fde6df 100644 --- a/bin/list_admins +++ b/bin/list_admins @@ -41,7 +41,7 @@ have more than one named list on the command line. from __future__ import print_function import sys -import argparse +import getopt import paths from Mailman import MailList, Utils @@ -53,6 +53,7 @@ COMMASPACE = ', ' program = sys.argv[0] + def usage(code, msg=''): if code: fd = sys.stderr @@ -64,40 +65,39 @@ def usage(code, msg=''): sys.exit(code) + def main(): - parser = argparse.ArgumentParser(description='List all the owners of a mailing list.') - parser.add_argument('listnames', nargs='*', help='Name(s) of the mailing list(s) to print the owners of') - parser.add_argument('-v', '--all-vhost', - help='List the owners of all the mailing lists for the given virtual host') - parser.add_argument('-a', '--all', action='store_true', - help='List the owners of all the mailing lists on this system') - - args = parser.parse_args() - - listnames = [x.lower() for x in args.listnames] - if args.all: - listnames = Utils.list_names() - elif args.all_vhost: - listnames = Utils.list_names() + try: + opts, args = getopt.getopt(sys.argv[1:], 'hv:a', + ['help', 'all-vhost=', 'all']) + except getopt.error as msg: + usage(1, msg) + + listnames = [x.lower() for x in args] + vhost = None + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-a', '--all'): + listnames = Utils.list_names() + elif opt in ('-v', '--all-vhost'): + listnames = Utils.list_names() + vhost = arg for listname in listnames: - # Ensure listname is a string - if isinstance(listname, bytes): - listname = listname.decode('utf-8', 'replace') - try: - mlist = MailList.MailList(listname, lock=0) - except Errors.MMListError: - print('No such list: %s' % listname) - continue + try: + mlist = MailList.MailList(listname, lock=0) + except Errors.MMListError as e: + print(C_('No such list: %(listname)s')) + continue - if args.all_vhost and args.all_vhost != mlist.host_name: - continue + if vhost and vhost != mlist.host_name: + continue - # Ensure owners are strings - owners = [owner.decode('utf-8', 'replace') if isinstance(owner, bytes) else owner for owner in mlist.owner] - owners_str = ', '.join(owners) - print('List: %s, \tOwners: %s' % (listname, owners_str)) + owners = COMMASPACE.join(mlist.owner) + print(C_('List: %(listname)s, \tOwners: %(owners)s')) + if __name__ == '__main__': main() diff --git a/bin/list_lists b/bin/list_lists old mode 100755 new mode 100644 index aba50caa..4b286f38 --- a/bin/list_lists +++ b/bin/list_lists @@ -44,7 +44,7 @@ Where: import re import sys -import argparse +import getopt import paths from Mailman import mm_cfg @@ -66,18 +66,31 @@ def usage(code, msg=''): sys.exit(code) + def main(): - parser = argparse.ArgumentParser(description='List all mailing lists.') - parser.add_argument('-a', '--advertised', action='store_true', - help='List only those mailing lists that are publically advertised') - parser.add_argument('-p', '--public-archive', action='store_true', - help='List only those lists with public archives') - parser.add_argument('-V', '--virtual-host-overview', - help='List only those mailing lists that are homed to the given virtual domain') - parser.add_argument('-b', '--bare', action='store_true', - help='Displays only the list name, with no description') - - args = parser.parse_args() + try: + opts, args = getopt.getopt(sys.argv[1:], 'apbV:h', + ['advertised', 'public-archive', 'bare', + 'virtual-host-overview=', + 'help']) + except getopt.error as msg: + usage(1, msg) + + advertised = 0 + public = 0 + vhost = None + bare = 0 + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-a', '--advertised'): + advertised = 1 + elif opt in ('-p', '--public-archive'): + public = 1 + elif opt in ('-V', '--virtual-host-overview'): + vhost = arg + elif opt in ('-b', '--bare'): + bare = 1 names = Utils.list_names() names.sort() @@ -85,53 +98,39 @@ def main(): mlists = [] longest = 0 for n in names: - # Ensure name is a string - if isinstance(n, bytes): - n = n.decode('utf-8', 'replace') try: mlist = MailList.MailList(n, lock=0) except Errors.MMUnknownListError: # The list could have been deleted by another process. continue - if args.advertised and not mlist.advertised: + if advertised and not mlist.advertised: continue - if args.public_archive and mlist.archive_private: + if public and mlist.archive_private: continue - if (args.virtual_host_overview and mm_cfg.VIRTUAL_HOST_OVERVIEW and - not re.search('://%s/' % re.escape(args.virtual_host_overview), + if (vhost and mm_cfg.VIRTUAL_HOST_OVERVIEW and + not re.search('://%s/' % re.escape(vhost), mlist.web_page_url, re.IGNORECASE)): continue mlists.append(mlist) - # Ensure real_name is a string - real_name = mlist.real_name - if isinstance(real_name, bytes): - real_name = real_name.decode('utf-8', 'replace') - longest = max(len(real_name), longest) - - if not mlists and not args.bare: - print('No matching mailing lists found') + longest = max(len(mlist.real_name), longest) + + if not mlists and not bare: + print(C_('No matching mailing lists found')) return - if not args.bare: - print(len(mlists), 'matching mailing lists found:') + if not bare: + print(len(mlists), C_('matching mailing lists found:')) format = '%%%ds - %%.%ds' % (longest, 77 - longest) for mlist in mlists: - if args.bare: - name = mlist.internal_name() - if isinstance(name, bytes): - name = name.decode('utf-8', 'replace') - print(name) + if bare: + print(mlist.internal_name()) else: - real_name = mlist.real_name - if isinstance(real_name, bytes): - real_name = real_name.decode('utf-8', 'replace') - description = mlist.description or '[no description available]' - if isinstance(description, bytes): - description = description.decode('utf-8', 'replace') - print(' ', format % (real_name, description)) + description = mlist.description or C_('[no description available]') + print(' ', format % (mlist.real_name, description)) + if __name__ == '__main__': main() diff --git a/bin/list_members b/bin/list_members index d77cdbb3..a1f148a8 100755 --- a/bin/list_members +++ b/bin/list_members @@ -77,7 +77,6 @@ from __future__ import print_function from __future__ import unicode_literals import sys -import argparse import paths from Mailman import mm_cfg @@ -106,15 +105,22 @@ def usage(code, msg=''): fd = sys.stderr else: fd = sys.stdout - # Ensure PROGRAM is a string, not bytes - if isinstance(PROGRAM, bytes): - PROGRAM = PROGRAM.decode('utf-8', 'replace') print(C_(__doc__), file=fd) if msg: print(msg, file=fd) sys.exit(code) + +def safe(s): + if not s: + return '' + if isinstance(s, str): + return s + elif isinstance(s, bytes): + return s.decode(ENC, 'replace') + return str(s) + def isinvalid(addr): try: Utils.ValidateEmail(addr) @@ -126,6 +132,7 @@ def isunicode(addr): return isinstance(addr, str) + def whymatches(mlist, addr, why): # Return true if the `why' matches the reason the address is enabled, or # in the case of why is None, that they are disabled for any reason @@ -136,37 +143,108 @@ def whymatches(mlist, addr, why): return status == WHYCHOICES[why] + def main(): - parser = argparse.ArgumentParser(description='List all the members of a mailing list.') - parser.add_argument('listname', help='Name of the mailing list') - parser.add_argument('-o', '--output', help='Write output to specified file instead of standard out') - parser.add_argument('-r', '--regular', action='store_true', help='Print just the regular (non-digest) members') - parser.add_argument('-d', '--digest', choices=['mime', 'plain'], nargs='?', const=True, help='Print just the digest members') - parser.add_argument('-n', '--nomail', choices=list(WHYCHOICES.keys()), nargs='?', const=True, help='Print members with delivery disabled') - parser.add_argument('-f', '--fullnames', action='store_true', help='Include the full names in the output') - parser.add_argument('-p', '--preserve', action='store_true', help='Output member addresses case preserved') - parser.add_argument('-m', '--moderated', action='store_true', help='Print just the moderated members') - parser.add_argument('-M', '--non-moderated', action='store_true', help='Print just the non-moderated members') - parser.add_argument('-i', '--invalid', action='store_true', help='Print only invalid addresses') - parser.add_argument('-u', '--unicode', action='store_true', help='Print addresses stored as Unicode objects') - - args = parser.parse_args() - - # Validate mutually exclusive options - if sum([args.moderated, args.non_moderated, args.invalid, args.unicode]) > 1: - parser.error('Only one of -m, -M, -i or -u may be specified.') - - if args.output: + # Because of the optional arguments, we can't use getopt. :( + outfile = None + regular = None + digest = None + preserve = None + nomail = None + why = None + kind = None + fullnames = False + invalidonly = False + unicodeonly = False + moderatedonly = False + nonmoderatedonly = False + + # Throw away the first (program) argument + args = sys.argv[1:] + if not args: + usage(0) + + while True: + try: + opt = args.pop(0) + except IndexError: + usage(1) + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-f', '--fullnames'): + fullnames = True + elif opt in ('-p', '--preserve'): + preserve = True + elif opt in ('-r', '--regular'): + regular = True + elif opt in ('-o', '--output'): + try: + outfile = args.pop(0) + except IndexError: + usage(1) + elif opt == '-n': + nomail = True + if args and args[0] in list(WHYCHOICES.keys()): + why = args.pop(0) + elif opt.startswith('--nomail'): + nomail = True + i = opt.find('=') + if i >= 0: + why = opt[i+1:] + if why not in list(WHYCHOICES.keys()): + usage(1, C_('Bad --nomail option: %(why)s')) + elif opt == '-d': + digest = True + if args and args[0] in ('mime', 'plain'): + kind = args.pop(0) + elif opt.startswith('--digest'): + digest = True + i = opt.find('=') + if i >= 0: + kind = opt[i+1:] + if kind not in ('mime', 'plain'): + usage(1, C_('Bad --digest option: %(kind)s')) + elif opt in ('-m', '--moderated'): + moderatedonly = True + if nonmoderatedonly or invalidonly or unicodeonly: + usage(1, C_('Only one of -m, -M, -i or -u may be specified.')) + elif opt in ('-M', '--non-moderated'): + nonmoderatedonly = True + if moderatedonly or invalidonly or unicodeonly: + usage(1, C_('Only one of -m, -M, -i or -u may be specified.')) + elif opt in ('-i', '--invalid'): + invalidonly = True + if moderatedonly or nonmoderatedonly or unicodeonly: + usage(1, C_('Only one of -m, -M, -i or -u may be specified.')) + elif opt in ('-u', '--unicode'): + unicodeonly = True + if moderatedonly or nonmoderatedonly or invalidonly: + usage(1, C_('Only one of -m, -M, -i or -u may be specified.')) + else: + # No more options left, push the last one back on the list + args.insert(0, opt) + break + + if len(args) != 1: + usage(1) + + listname = args[0].lower().strip() + + if regular is None and digest is None: + regular = digest = True + + if outfile: try: - fp = open(args.output, 'w') + fp = open(outfile, 'w') except IOError: - print(C_('Could not open file for writing:'), args.output, file=sys.stderr) + print(C_( + 'Could not open file for writing:'), outfile, file=sys.stderr) sys.exit(1) else: fp = sys.stdout try: - mlist = MailList.MailList(args.listname.lower().strip(), lock=False) + mlist = MailList.MailList(listname, lock=False) except Errors.MMListError as e: print(C_('No such list: %(listname)s'), file=sys.stderr) sys.exit(1) @@ -175,56 +253,56 @@ def main(): rmembers = mlist.getRegularMemberKeys() dmembers = mlist.getDigestMemberKeys() - if args.preserve: + if preserve: # Convert to the case preserved addresses rmembers = mlist.getMemberCPAddresses(rmembers) dmembers = mlist.getMemberCPAddresses(dmembers) - if args.invalid or args.unicode or args.moderated or args.non_moderated: + if invalidonly or unicodeonly or moderatedonly or nonmoderatedonly: all = rmembers + dmembers all.sort() for addr in all: - name = args.fullnames and mlist.getMemberName(addr) or '' + name = fullnames and mlist.getMemberName(addr) or '' showit = False - if args.invalid and isinvalid(addr): + if invalidonly and isinvalid(addr): showit = True - if args.unicode and isunicode(addr): + if unicodeonly and isunicode(addr): showit = True - if args.moderated and mlist.getMemberOption(addr, mm_cfg.Moderate): + if moderatedonly and mlist.getMemberOption(addr, mm_cfg.Moderate): showit = True - if args.non_moderated and not mlist.getMemberOption(addr, mm_cfg.Moderate): + if nonmoderatedonly and not mlist.getMemberOption(addr, + mm_cfg.Moderate): showit = True if showit: - print(formataddr((name, addr)), file=fp) + print(formataddr((safe(name), addr)), file=fp) return - - if args.regular or not args.digest: + if regular: rmembers.sort() for addr in rmembers: - name = args.fullnames and mlist.getMemberName(addr) or '' + name = fullnames and mlist.getMemberName(addr) or '' # Filter out nomails - if args.nomail and not whymatches(mlist, addr, args.nomail): + if nomail and not whymatches(mlist, addr, why): continue - print(formataddr((name, addr)), file=fp) - - if args.digest or not args.regular: + print(formataddr((safe(name), addr)), file=fp) + if digest: dmembers.sort() for addr in dmembers: - name = args.fullnames and mlist.getMemberName(addr) or '' + name = fullnames and mlist.getMemberName(addr) or '' # Filter out nomails - if args.nomail and not whymatches(mlist, addr, args.nomail): + if nomail and not whymatches(mlist, addr, why): continue # Filter out digest kinds if mlist.getMemberOption(addr, mm_cfg.DisableMime): # They're getting plain text digests - if args.digest == 'mime': + if kind == 'mime': continue else: # They're getting MIME digests - if args.digest == 'plain': + if kind == 'plain': continue - print(formataddr((name, addr)), file=fp) + print(formataddr((safe(name), addr)), file=fp) + if __name__ == '__main__': main() diff --git a/bin/list_owners b/bin/list_owners index 11c0013d..507f7b73 100644 --- a/bin/list_owners +++ b/bin/list_owners @@ -38,9 +38,14 @@ Options: after the options. If there are no listnames provided, the owners of all the lists will be displayed. """ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + from builtins import * import sys -import argparse +import getopt import paths from Mailman import Utils @@ -49,47 +54,46 @@ from Mailman.i18n import C_ PROGRAM = sys.argv[0] - + def usage(code, msg=''): if code: fd = sys.stderr else: fd = sys.stdout - # Ensure PROGRAM is a string, not bytes - if isinstance(PROGRAM, bytes): - PROGRAM = PROGRAM.decode('utf-8', 'replace') print(C_(__doc__), file=fd) if msg: print(msg, file=fd) sys.exit(code) + def main(): - parser = argparse.ArgumentParser(description='List the owners of a mailing list, or all mailing lists.') - parser.add_argument('listnames', nargs='*', help='Print the owners of the specified lists') - parser.add_argument('-w', '--with-listnames', action='store_true', - help='Group the owners by list names and include the list names in the output') - parser.add_argument('-m', '--moderators', action='store_true', - help='Include the list moderators in the output') - - args = parser.parse_args() - - listnames = [x.lower() for x in args.listnames] or Utils.list_names() + try: + opts, args = getopt.getopt(sys.argv[1:], 'wmh', + ['with-listnames', 'moderators', 'help']) + except getopt.error as msg: + usage(1, msg) + + withnames = moderators = False + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-m', '--moderators'): + moderators = True + elif opt in ('-w', '--with-listnames'): + withnames = True + + listnames = [x.lower() for x in args] or Utils.list_names() bylist = {} for listname in listnames: - # Ensure listname is a string - if isinstance(listname, bytes): - listname = listname.decode('utf-8', 'replace') mlist = MailList(listname, lock=0) addrs = mlist.owner[:] - if args.moderators: + if moderators: addrs.extend(mlist.moderator) - # Ensure addresses are strings - addrs = [addr.decode('utf-8', 'replace') if isinstance(addr, bytes) else addr for addr in addrs] bylist[listname] = addrs - if args.with_listnames: + if withnames: for listname in listnames: unique = {} for addr in bylist[listname]: @@ -110,5 +114,6 @@ def main(): print(k) + if __name__ == '__main__': main() diff --git a/bin/mailmanctl b/bin/mailmanctl old mode 100755 new mode 100644 index 1dba8cc9..9e87bcd9 --- a/bin/mailmanctl +++ b/bin/mailmanctl @@ -95,12 +95,12 @@ Commands: import sys import os import time +import getopt import signal import errno import pwd import grp import socket -import argparse import paths from Mailman import mm_cfg @@ -127,113 +127,31 @@ MAX_RESTARTS = 10 LogStdErr('error', 'mailmanctl', manual_reprime=0) -def parse_args(): - """Parse command line arguments using argparse. - - Returns: - argparse.Namespace: Parsed command line arguments - """ - parser = argparse.ArgumentParser( - description=C_("Primary start-up and shutdown script for Mailman's qrunner daemon."), - formatter_class=argparse.RawDescriptionHelpFormatter, - epilog=C_("""\ -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 -""") - ) - - parser.add_argument('-n', '--no-restart', - action='store_true', - help=C_("""\ -Don't restart the qrunners when they exit because of an error or a -SIGINT. They are never restarted if they exit in response to a -SIGTERM. Use this only for debugging. Only useful if the `start' -command is given.""")) - - parser.add_argument('-u', '--run-as-user', - action='store_true', - help=C_("""\ -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_argument('-s', '--stale-lock-cleanup', - action='store_true', - help=C_("""\ -If mailmanctl finds an existing master lock, it will normally exit -with an error message. With this option, mailmanctl will perform an -extra level of checking. If a process matching the host/pid described -in the lock file is running, mailmanctl will still exit, but if no -matching process is found, mailmanctl will remove the apparently stale -lock and make another attempt to claim the master lock.""")) - - parser.add_argument('-q', '--quiet', - action='store_true', - help=C_("Don't print status messages. Error messages are still printed to standard error.")) - - parser.add_argument('command', - choices=['start', 'stop', 'restart', 'reopen'], - help=C_("Command to execute")) - - return parser.parse_args() - - + def usage(code, msg=''): if code: fd = sys.stderr else: fd = sys.stdout - # In Python 3, sys.argv[0] is already a string - program = str(sys.argv[0]) # Ensure it's a string - doc = C_(__doc__) % {'PROGRAM': program} # Let C_() handle the translation and formatting - print(doc, file=fd) + print(C_(__doc__), file=fd) if msg: print(msg, file=fd) sys.exit(code) + def kill_watcher(sig): try: - with open(mm_cfg.PIDFILE, 'r') as fp: - content = fp.read().strip().split() - if len(content) >= 2: - pid = int(content[0]) - hostname = content[1] - if hostname != socket.gethostname(): - print(C_('PID file hostname mismatch: expected %(expected)s, got %(got)s') % - {'expected': socket.gethostname(), 'got': hostname}, file=sys.stderr) - return - else: - raise ValueError('Invalid PID file format') + fp = open(mm_cfg.PIDFILE) + pidstr = fp.read() + fp.close() + pid = int(pidstr.strip()) except (IOError, ValueError) as e: # For i18n convenience pidfile = mm_cfg.PIDFILE print(C_('PID unreadable in: %(pidfile)s'), file=sys.stderr) print(e, file=sys.stderr) print(C_('Is qrunner even running?'), file=sys.stderr) - print(C_('Lock file path: %(lockfile)s') % {'lockfile': LOCKFILE}, file=sys.stderr) return try: os.kill(pid, sig) @@ -245,29 +163,21 @@ def kill_watcher(sig): os.unlink(mm_cfg.PIDFILE) + def get_lock_data(): # Return the hostname, pid, and tempfile - try: - with open(LOCKFILE) as fp: - content = fp.read().strip().split() - if len(content) != 2: - syslog('error', 'Invalid lock file format in %s: expected "pid hostname"', LOCKFILE) - raise LockFile.LockError('Invalid lock file format') - try: - pid = int(content[0]) - hostname = content[1] - except ValueError as e: - syslog('error', 'Invalid PID in lock file %s: %s', LOCKFILE, e) - raise LockFile.LockError('Invalid PID in lock file') - return hostname, pid, None # tempfile is not used in this format - except IOError as e: - syslog('error', 'Could not read lock file %s: %s', LOCKFILE, e) - raise LockFile.LockError('Could not read lock file') + fp = open(LOCKFILE) + filename = os.path.split(fp.read().strip())[1] + fp.close() + parts = filename.split('.') + hostname = DOT.join(parts[1:-1]) + pid = int(parts[-1]) + return hostname, int(pid), filename def qrunner_state(): - # 1 if proc exists on host and is owned by mailman user - # 0 if host matches but no proc or wrong owner + # 1 if proc exists on host (but is it qrunner? ;) + # 0 if host matches but no proc # hostname if hostname doesn't match hostname, pid, tempfile = get_lock_data() if hostname != socket.gethostname(): @@ -275,44 +185,10 @@ def qrunner_state(): # Find out if the process exists by calling kill with a signal 0. try: os.kill(pid, 0) - # Process exists, now check if it's owned by the mailman user - mailman_uid = pwd.getpwnam(mm_cfg.MAILMAN_USER).pw_uid - try: - # Try to get process owner using platform-specific methods - if os.name == 'posix': - # On Unix-like systems, try to get process owner - try: - # Try using /proc on Linux - if os.path.exists('/proc'): - with open(f'/proc/{pid}/status') as f: - for line in f: - if line.startswith('Uid:'): - uid = int(line.split()[1]) - if uid != mailman_uid: - syslog('error', 'Process %d exists but is owned by uid %d, not mailman user %d', - pid, uid, mailman_uid) - return 0 - break - else: - # On other Unix systems, we can't easily check the owner - # without external tools, so we'll assume it's valid - # if the process exists - return 1 - except (IOError, OSError) as e: - syslog('error', 'Error checking process %d ownership: %s', pid, str(e)) - return 0 - else: - # On non-Unix systems, we can't easily check the owner - # without external tools, so we'll assume it's valid - # if the process exists - return 1 - return 1 - except Exception as e: - syslog('error', 'Error checking process %d ownership: %s', pid, str(e)) - return 0 except OSError as e: if e.errno != errno.ESRCH: raise return 0 + return 1 def acquire_lock_1(force): @@ -323,28 +199,14 @@ def acquire_lock_1(force): lock.lock(0.1) return lock except LockFile.TimeOutError: - # Check if the lock is stale by examining the process - status = qrunner_state() - if status == 1: - # Process exists and is running, so lock is valid + # If we're not forcing or the lock can't be determined to be stale. + if not force or qrunner_state(): raise - # Lock appears to be stale - clean it up - try: - # Read the current lock file content - with open(LOCKFILE) as fp: - content = fp.read().strip() - if content: - # Try to clean up any stale lock files - lock.clean_stale_locks() - except (IOError, OSError) as e: - syslog('error', 'Error cleaning up stale lock: %s', str(e)) - # Remove the lock file - try: - os.unlink(LOCKFILE) - except OSError as e: - if e.errno != errno.ENOENT: - syslog('error', 'Error removing lock file: %s', str(e)) - # Try to acquire the lock again + # Force removal of lock first + lock._disown() + hostname, pid, tempfile = get_lock_data() + os.unlink(LOCKFILE) + os.unlink(os.path.join(mm_cfg.LOCK_DIR, tempfile)) return acquire_lock_1(force=0) @@ -380,6 +242,7 @@ Lock host: %(status)s Exiting."""), file=sys.stderr) + def start_runner(qrname, slice, count): pid = os.fork() if pid: @@ -401,19 +264,14 @@ def start_all_runners(): kids = {} for qrname, count in mm_cfg.QRUNNERS: for slice in range(count): - try: - # queue runner name, slice, numslices, restart count - info = (qrname, slice, count, 0) - pid = start_runner(qrname, slice, count) - kids[pid] = info - except Exception as e: - # Log the failure but continue with other runners - syslog('error', 'Failed to start %s runner (slice %d): %s', - qrname, slice, str(e)) - continue + # queue runner name, slice, numslices, restart count + info = (qrname, slice, count, 0) + pid = start_runner(qrname, slice, count) + kids[pid] = info return kids + def check_for_site_list(): sitelistname = mm_cfg.MAILMAN_SITE_LIST try: @@ -448,315 +306,212 @@ def check_privs(): 'Run this program as root or as the %(name)s user, or use -u.')) -def check_status(): - """Check if all qrunners are running as expected.""" - # First check if the master process is running - try: - with open(mm_cfg.PIDFILE, 'r') as fp: - content = fp.read().strip().split() - if len(content) >= 2: - pid = int(content[0]) - hostname = content[1] - if hostname != socket.gethostname(): - print(C_('PID file hostname mismatch: expected %(expected)s, got %(got)s') % - {'expected': socket.gethostname(), 'got': hostname}, file=sys.stderr) - return False - else: - raise ValueError('Invalid PID file format') - try: - os.kill(pid, 0) # Check if process exists - print(C_('Master qrunner process is running (pid: %(pid)d)') % {'pid': pid}) - except OSError: - print(C_('Master qrunner process is not running (stale pid file)')) - return False - except (IOError, ValueError) as e: - print(C_('Master qrunner process is not running (no pid file)')) - print(e, file=sys.stderr) - return False - - # Check if the lock file exists and is valid - try: - hostname, pid, tempfile = get_lock_data() - if hostname != socket.gethostname(): - print(C_('Lock file is held by another host: %(hostname)s') % {'hostname': hostname}) - return False - try: - os.kill(pid, 0) - print(C_('Lock file is valid (pid: %(pid)d)') % {'pid': pid}) - except OSError: - print(C_('Lock file is stale (process %(pid)d not running)') % {'pid': pid}) - return False - except (IOError, ValueError): - print(C_('No lock file found')) - return False - - # Check if all expected qrunners are running - expected_runners = dict(mm_cfg.QRUNNERS) - running_runners = {} - - # Get all running qrunner processes - for line in os.popen('ps aux | grep qrunner | grep -v grep').readlines(): - parts = line.split() - if len(parts) >= 12: # Ensure we have enough parts - cmd = parts[10] # The command is typically at index 10 - if '--runner=' in cmd: - runner_name = cmd.split('--runner=')[1].split(':')[0] - running_runners[runner_name] = running_runners.get(runner_name, 0) + 1 - - # Compare expected vs running - all_running = True - for runner, count in expected_runners.items(): - actual = running_runners.get(runner, 0) - if actual != count: - print(C_('%(runner)s: expected %(count)d instances, found %(actual)d') % - {'runner': runner, 'count': count, 'actual': actual}) - all_running = False - else: - print(C_('%(runner)s: %(count)d instances running') % - {'runner': runner, 'count': count}) - - return all_running - - -def check_global_circuit_breaker(): - """Check if we've exceeded the global restart limit. - - Returns: - bool: True if we should stop all runners, False otherwise - """ - # Circuit breaker disabled - always return False - return False - - -def stop_all_processes(kids, lock=None): - """Stop all child processes and clean up, similar to mailmanctl stop. - - Args: - kids: Dictionary of child processes - lock: Optional lock to release - """ - # First send SIGTERM to all children - for pid in list(kids.keys()): - try: - os.kill(pid, signal.SIGTERM) - except OSError as e: - if e.errno != errno.ESRCH: - raise - - # Wait for all children to exit - while kids: - try: - pid, status = os.wait() - if pid in kids: - del kids[pid] - except OSError as e: - if e.errno == errno.ECHILD: - break - elif e.errno != errno.EINTR: - raise - continue - - # Clean up PID file - try: - os.unlink(mm_cfg.PIDFILE) - syslog('qrunner', 'Removed PID file: %s', mm_cfg.PIDFILE) - except OSError as e: - if e.errno != errno.ENOENT: - syslog('error', 'Failed to remove PID file %s: %s', mm_cfg.PIDFILE, str(e)) - - # Release lock if provided - if lock: - try: - lock.unlock(unconditionally=1) - except Exception as e: - syslog('error', 'Failed to release lock: %s', str(e)) - - + def main(): + global quiet try: - args = parse_args() - except SystemExit: - usage(1) - - # Check that we're running as the right user - if not args.run_as_user: - try: - mailman_uid = pwd.getpwnam(mm_cfg.MAILMAN_USER).pw_uid - mailman_gid = grp.getgrnam(mm_cfg.MAILMAN_GROUP).gr_gid - except (KeyError, AttributeError): - print(C_('Cannot determine mailman user/group'), file=sys.stderr) - sys.exit(1) - - if os.getuid() == 0: - # We're root, so switch to the mailman user/group - os.setgid(mailman_gid) - os.setuid(mailman_uid) - elif os.getuid() != mailman_uid or os.getgid() != mailman_gid: - print(C_('Must be run as the mailman user'), file=sys.stderr) - sys.exit(1) - - # Handle the command - if args.command == 'status': - if check_status(): - sys.exit(0) - else: - sys.exit(1) - elif args.command == 'start': - # Check if we're already running - if os.path.exists(mm_cfg.PIDFILE): - try: - with open(mm_cfg.PIDFILE) as fp: - pid = int(fp.read().strip()) - if check_pid(pid): - print(C_('Mailman qrunner is already running (pid: %(pid)d)'), file=sys.stderr) - sys.exit(1) - except (ValueError, IOError): - pass - - # Try to acquire the lock - try: - lock = acquire_lock(args.stale_lock_cleanup) - except LockFile.TimeOutError: - sys.exit(1) - - # Fork to daemonize + opts, args = getopt.getopt(sys.argv[1:], 'hnusq', + ['help', 'no-restart', 'run-as-user', + 'stale-lock-cleanup', 'quiet']) + except getopt.error as msg: + usage(1, msg) + + restart = 1 + checkprivs = 1 + force = 0 + quiet = 0 + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-n', '--no-restart'): + restart = 0 + elif opt in ('-u', '--run-as-user'): + checkprivs = 0 + elif opt in ('-s', '--stale-lock-cleanup'): + force = 1 + elif opt in ('-q', '--quiet'): + quiet = 1 + + if len(args) < 1: + usage(1, C_('No command given.')) + elif len(args) > 1: + command = COMMASPACE.join(args) + usage(1, C_('Bad command: %(command)s')) + + if checkprivs: + check_privs() + else: + print(C_('Warning! You may encounter permission problems.')) + + # Handle the commands + command = args[0].lower() + if command == 'stop': + # Sent the master qrunner process a SIGINT, which is equivalent to + # giving cron/qrunner a ctrl-c or KeyboardInterrupt. This will + # effectively shut everything down. + if not quiet: + print(C_("Shutting down Mailman's master qrunner")) + kill_watcher(signal.SIGTERM) + elif command == 'restart': + # Sent the master qrunner process a SIGHUP. This will cause the + # master qrunner to kill and restart all the worker qrunners, and to + # close and re-open its log files. + if not quiet: + print(C_("Restarting Mailman's master qrunner")) + kill_watcher(signal.SIGINT) + elif command == 'reopen': + if not quiet: + print(C_('Re-opening all log files')) + kill_watcher(signal.SIGHUP) + elif command == 'start': + # First, complain loudly if there's no site list. + check_for_site_list() + # Here's the scoop on the processes we're about to create. We'll need + # one for each qrunner, and one for a master child process watcher / + # lock refresher process. + # + # The child watcher process simply waits on the pids of the children + # qrunners. Unless explicitly disabled by a mailmanctl switch (or the + # children are killed with SIGTERM instead of SIGINT), the watcher + # will automatically restart any child process that exits. This + # allows us to be more robust, and also to implement restart by simply + # SIGINT'ing the qrunner children, and letting the watcher restart + # them. + # + # Under normal operation, we have a child per queue. This lets us get + # the most out of the available resources, since a qrunner with no + # files in its queue directory is pretty cheap, but having a separate + # runner process per queue allows for a very responsive system. Some + # people want a more traditional (i.e. MM2.0.x) cron-invoked qrunner. + # No problem, but using mailmanctl isn't the answer. So while + # mailmanctl hard codes some things, others, such as the number of + # qrunners per queue, is configurable in mm_cfg.py. + # + # First, acquire the master mailmanctl lock + lock = acquire_lock(force) + if not lock: + return + # Daemon process startup according to Stevens, Advanced Programming in + # the UNIX Environment, Chapter 13. pid = os.fork() if pid: # parent - if not args.quiet: + if not quiet: print(C_("Starting Mailman's master qrunner.")) - # Give up the lock "ownership". This just means the foreground + # Give up the lock "ownership". This just means the foreground # process won't close/unlock the lock when it finalizes this lock - # instance. We'll let the master watcher subproc own the lock. + # instance. We'll let the mater watcher subproc own the lock. lock._transfer_to(pid) - - # Wait briefly to ensure child process starts - time.sleep(1) - - # Verify the child process is running - try: - os.kill(pid, 0) # Check if process exists - if not args.quiet: - print(C_('Master qrunner started successfully (pid: %d)') % pid) - syslog('qrunner', 'Master qrunner started successfully (pid: %d)', pid) - except OSError as e: - if e.errno == errno.ESRCH: - print(C_('Error: Master process failed to start'), file=sys.stderr) - return - raise return - # child + lock._take_possession() + # First, save our pid in a file for "mailmanctl stop" rendezvous. We + # want the perms on the .pid file to be rw-rw---- + omask = os.umask(6) try: - lock._take_possession() - - # Create a new session and become the session leader - os.setsid() - - # Be sure to close any open std{in,out,err} - devnull = os.open('/dev/null', 0) - os.dup2(devnull, 0) - os.dup2(devnull, 1) - os.dup2(devnull, 2) - - # Instead of cd'ing to root, cd to the Mailman installation home - os.chdir(mm_cfg.PREFIX) - - # Set our file mode creation umask - os.umask(0o07) - - # Write our PID to the PID file - try: - with open(mm_cfg.PIDFILE, 'w') as fp: - fp.write(str(os.getpid())) - except IOError as e: - syslog('error', 'Failed to write PID file: %s', str(e)) - os._exit(1) - - # Start all runners - kids = start_all_runners() - if not kids: - syslog('error', 'No runners started successfully') - os._exit(1) - - # Set up a SIGALRM handler to refresh the lock once per day - def sigalrm_handler(signum, frame, lock=lock): - lock.refresh() - signal.alarm(mm_cfg.days(1)) - signal.signal(signal.SIGALRM, sigalrm_handler) + fp = open(mm_cfg.PIDFILE, 'w') + print(os.getpid(), file=fp) + fp.close() + finally: + os.umask(omask) + # 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() + + # Be sure to close any open std{in,out,err} + devnull = os.open('/dev/null', 0) + os.dup2(devnull, 0) + os.dup2(devnull, 1) + os.dup2(devnull, 2) + + # Instead of cd'ing to root, cd to the Mailman installation home + os.chdir(mm_cfg.PREFIX) + # Set our file mode creation umask + os.umask(0o07) + # I don't think we have any unneeded file descriptors. + # + # Now start all the qrunners. This returns a dictionary where the + # keys are qrunner pids and the values are tuples of the following + # form: (qrname, slice, count). This does its own fork and exec, and + # sets up its own signal handlers. + kids = start_all_runners() + # Set up a SIGALRM handler to refresh the lock once per day. The lock + # lifetime is 1day+6hours so this should be plenty. + def sigalrm_handler(signum, frame, lock=lock): + lock.refresh() signal.alarm(mm_cfg.days(1)) - - # Set up a SIGHUP handler - def sighup_handler(signum, frame, kids=kids): - syslog.close() - for pid in list(kids.keys()): - os.kill(pid, signal.SIGHUP) - syslog('qrunner', - 'Master watcher caught SIGHUP. Re-opening log files.') - signal.signal(signal.SIGHUP, sighup_handler) - - # Set up a SIGTERM handler - def sigterm_handler(signum, frame, kids=kids): - for pid in list(kids.keys()): - try: - os.kill(pid, signal.SIGTERM) - except OSError as e: - if e.errno != errno.ESRCH: raise - syslog('qrunner', 'Master watcher caught SIGTERM. Exiting.') - signal.signal(signal.SIGTERM, sigterm_handler) - - # Set up a SIGINT handler - def sigint_handler(signum, frame, kids=kids): - for pid in list(kids.keys()): - os.kill(pid, signal.SIGINT) - syslog('qrunner', 'Master watcher caught SIGINT. Restarting.') - signal.signal(signal.SIGINT, sigint_handler) - - # Now we're ready to simply do our wait/restart loop - while True: + signal.signal(signal.SIGALRM, sigalrm_handler) + signal.alarm(mm_cfg.days(1)) + # Set up a SIGHUP handler so that if we get one, we'll pass it along + # to all the qrunner children. This will tell them to close and + # reopen their log files + def sighup_handler(signum, frame, kids=kids): + # Closing our syslog will cause it to be re-opened at the next log + # print output. + syslog.close() + for pid in list(kids.keys()): + os.kill(pid, signal.SIGHUP) + # And just to tweak things... + syslog('qrunner', + 'Master watcher caught SIGHUP. Re-opening log files.') + signal.signal(signal.SIGHUP, sighup_handler) + # We also need to install a SIGTERM handler because that's what init + # will kill this process with when changing run levels. + def sigterm_handler(signum, frame, kids=kids): + for pid in list(kids.keys()): + try: + os.kill(pid, signal.SIGTERM) + except OSError as e: + if e.errno != errno.ESRCH: raise + syslog('qrunner', 'Master watcher caught SIGTERM. Exiting.') + signal.signal(signal.SIGTERM, sigterm_handler) + # Finally, we need a SIGINT handler which will cause the sub-qrunners + # to exit, but the master will restart SIGINT'd sub-processes unless + # the -n flag was given. + def sigint_handler(signum, frame, kids=kids): + for pid in list(kids.keys()): + os.kill(pid, signal.SIGINT) + syslog('qrunner', 'Master watcher caught SIGINT. Restarting.') + signal.signal(signal.SIGINT, sigint_handler) + # Now we're ready to simply do our wait/restart loop. This is the + # master qrunner watcher. + try: + while 1: try: pid, status = os.wait() except OSError as e: - # No children? We're done + # No children? We're done if e.errno == errno.ECHILD: break # If the system call got interrupted, just restart it. elif e.errno != errno.EINTR: raise continue - killsig = exitstatus = None if os.WIFSIGNALED(status): killsig = os.WTERMSIG(status) if os.WIFEXITED(status): exitstatus = os.WEXITSTATUS(status) - + # We'll restart the process unless we were given the + # "no-restart" switch, or if the process was SIGTERM'd or + # exitted with a SIGTERM exit status. This lets us better + # handle runaway restarts (say, if the subproc had a syntax + # error!) restarting = '' - if not args.no_restart: - # Only restart if the runner exited with SIGINT (normal exit) - # and not SIGTERM (error or forced stop) - if exitstatus == signal.SIGINT: + if restart: + if (exitstatus == None and killsig != signal.SIGTERM) or \ + (killsig == None and exitstatus != signal.SIGTERM): + # Then restarting = '[restarting]' - qrname, slice, count, restarts = kids[pid] del kids[pid] - - # Only log abnormal exits - if killsig == signal.SIGTERM or \ - (exitstatus is not None and exitstatus != signal.SIGINT): - syslog('qrunner', """\ -Master qrunner detected abnormal subprocess exit + syslog('qrunner', """\ +Master qrunner detected subprocess exit (pid: %d, sig: %s, sts: %s, class: %s, slice: %d/%d) %s""", pid, killsig, exitstatus, qrname, slice+1, count, restarting) - - if restarting and check_global_circuit_breaker(): - syslog('error', 'Global circuit breaker triggered - stopping all runners') - # Stop all processes and clean up - stop_all_processes(kids, lock) - # Exit the main loop - break - + # See if we've reached the maximum number of allowable restarts if exitstatus != signal.SIGINT: restarts += 1 if restarts > MAX_RESTARTS: @@ -764,24 +519,25 @@ Master qrunner detected abnormal subprocess exit Qrunner %s reached maximum restart limit of %d, not restarting.""", qrname, MAX_RESTARTS) restarting = '' - - # Now perhaps restart the process + # Now perhaps restart the process unless it exited with a + # SIGTERM or we aren't restarting. if restarting: newpid = start_runner(qrname, slice, count) kids[newpid] = (qrname, slice, count, restarts) - finally: - # all of our children are exited cleanly + # Should we leave the main loop for any reason, we want to be sure + # all of our children are exited cleanly. Send SIGTERMs to all + # the child processes and wait for them all to exit. for pid in list(kids.keys()): try: os.kill(pid, signal.SIGTERM) except OSError as e: if e.errno == errno.ESRCH: + # The child has already exited syslog('qrunner', 'ESRCH on pid: %d', pid) del kids[pid] - # Wait for all the children to go away - while True: + while 1: try: pid, status = os.wait() except OSError as e: @@ -790,26 +546,11 @@ Qrunner %s reached maximum restart limit of %d, not restarting.""", elif e.errno != errno.EINTR: raise continue - - # Finally, give up the lock - lock.unlock(unconditionally=1) - os._exit(0) - elif args.command == 'stop': - kill_watcher(signal.SIGTERM) - try: - os.unlink(mm_cfg.PIDFILE) - syslog('qrunner', 'Removed PID file: %s', mm_cfg.PIDFILE) - except OSError as e: - if e.errno != errno.ENOENT: - syslog('error', 'Failed to remove PID file %s: %s', mm_cfg.PIDFILE, str(e)) - elif args.command == 'restart': - kill_watcher(signal.SIGINT) - start_all_runners() - elif args.command == 'reopen': - kill_watcher(signal.SIGHUP) - else: - usage(1, C_('Unknown command: %(command)s')) + # Finally, give up the lock + lock.unlock(unconditionally=1) + os._exit(0) + if __name__ == '__main__': main() diff --git a/bin/mmsitepass b/bin/mmsitepass index fd0625b3..8247f2a0 100755 --- a/bin/mmsitepass +++ b/bin/mmsitepass @@ -22,7 +22,7 @@ The site password can be used in most if not all places that the list administrator's password can be used, which in turn can be used in most places that a list users password can be used. -Usage: %(PROGRAM)s [options] [password] +Usage: mmsitepass [options] [password] Options: @@ -40,7 +40,7 @@ from __future__ import unicode_literals import sys import getpass -import argparse +import getopt import paths from Mailman import Utils @@ -48,27 +48,42 @@ from Mailman import Utils PROGRAM = sys.argv[0] -def parse_args(): - parser = argparse.ArgumentParser(description='Set the site password, prompting from the terminal.') - parser.add_argument('-c', '--listcreator', action='store_true', - help='Set the list creator password instead of the site password') - parser.add_argument('password', nargs='?', - help='The password to set (optional, will prompt if not provided)') - return parser.parse_args() + +def usage(code, msg=''): + if code: + fd = sys.stderr + else: + fd = sys.stdout + print(__doc__, file=fd) + if msg: + print(msg, file=fd) + sys.exit(code) + def main(): - args = parse_args() + try: + opts, args = getopt.getopt(sys.argv[1:], 'ch', + ['listcreator', 'help']) + except getopt.error as msg: + usage(1, msg) # Defaults - siteadmin = not args.listcreator - pwdesc = 'list creator' if args.listcreator else 'site' - - if args.password: - pw1 = args.password + siteadmin = 1 + pwdesc = 'site' + + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-c', '--listcreator'): + siteadmin = 0 + pwdesc = 'list creator' + + if len(args) == 1: + pw1 = args[0] else: try: - pw1 = getpass.getpass('New %(pwdesc)s password: ') + pw1 = getpass.getpass(f'New {pwdesc} password: ') pw2 = getpass.getpass('Again to confirm password: ') if pw1 != pw2: print('Passwords do not match; no changes made.') @@ -85,5 +100,6 @@ def main(): print('Password change failed.') + if __name__ == '__main__': main() diff --git a/bin/msgfmt-python2.py b/bin/msgfmt-python2.py index 960891fd..44dd119d 100644 --- a/bin/msgfmt-python2.py +++ b/bin/msgfmt-python2.py @@ -1,6 +1,6 @@ -#! @PYTHON@ +#! /usr/bin/env python # -*- coding: iso-8859-1 -*- -# Written by Martin v. Lwis +# Written by Martin v. Löwis """Generate binary message catalog from textual translation description. @@ -28,7 +28,7 @@ import sys import os -import argparse +import getopt import struct import array @@ -37,6 +37,15 @@ MESSAGES = {} + +def usage(code, msg=''): + print(__doc__, file=sys.stderr) + if msg: + print(msg, file=sys.stderr) + sys.exit(code) + + + def add(id, str, fuzzy): "Add a non-fuzzy translation to the dictionary." global MESSAGES @@ -44,6 +53,7 @@ def add(id, str, fuzzy): MESSAGES[id] = str + def generate(): "Return the generated output." global MESSAGES @@ -86,6 +96,7 @@ def generate(): return output + def make(filename, outfile): ID = 1 STR = 2 @@ -161,22 +172,32 @@ def make(filename, outfile): print(msg, file=sys.stderr) -def parse_args(): - parser = argparse.ArgumentParser(description='Generate binary message catalog from textual translation description.') - parser.add_argument('-o', '--output-file', - help='Specify the output file to write to') - parser.add_argument('-V', '--version', action='version', - version='%(prog)s ' + __version__) - parser.add_argument('files', nargs='+', - help='Input .po files to process') - return parser.parse_args() - - + def main(): - args = parse_args() - - for filename in args.files: - make(filename, args.output_file) + try: + opts, args = getopt.getopt(sys.argv[1:], 'hVo:', + ['help', 'version', 'output-file=']) + except getopt.error as msg: + usage(1, msg) + + outfile = None + # parse options + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-V', '--version'): + print("msgfmt.py", __version__, file=sys.stderr) + sys.exit(0) + elif opt in ('-o', '--output-file'): + outfile = arg + # do it + if not args: + print('No input file given', file=sys.stderr) + print("Try `msgfmt --help' for more information.", file=sys.stderr) + return + + for filename in args: + make(filename, outfile) if __name__ == '__main__': diff --git a/bin/msgfmt.py b/bin/msgfmt.py index 8a36c96d..78b4ef6a 100644 --- a/bin/msgfmt.py +++ b/bin/msgfmt.py @@ -1,4 +1,4 @@ -#! @PYTHON@ +#! /usr/bin/env python # -*- coding: iso-8859-1 -*- # Written by Martin v. Loewis @@ -28,7 +28,7 @@ import sys import os -import argparse +import getopt import struct import array @@ -37,17 +37,15 @@ MESSAGES = {} -def parse_args(): - parser = argparse.ArgumentParser(description='Generate binary message catalog from textual translation description.') - parser.add_argument('filename', nargs='+', - help='Input .po file(s)') - parser.add_argument('-o', '--output-file', - help='Specify the output file to write to') - parser.add_argument('-V', '--version', action='version', - version='%(prog)s ' + __version__) - return parser.parse_args() + +def usage(code, msg=''): + sys.stderr.write(str(__doc__) + "\n") + if msg: + sys.stderr.write(str(msg) + "\n") + sys.exit(code) + def add(id, str, fuzzy): "Add a non-fuzzy translation to the dictionary." global MESSAGES @@ -55,6 +53,7 @@ def add(id, str, fuzzy): MESSAGES[id] = str + def generate(): "Return the generated output." global MESSAGES @@ -97,6 +96,7 @@ def generate(): return output + def make(filename, outfile): ID = 1 STR = 2 @@ -171,10 +171,32 @@ def make(filename, outfile): print(msg, file=sys.stderr) + def main(): - args = parse_args() - for filename in args.filename: - make(filename, args.output_file) + try: + opts, args = getopt.getopt(sys.argv[1:], 'hVo:', + ['help', 'version', 'output-file=']) + except getopt.error as msg: + usage(1, msg) + + outfile = None + # parse options + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-V', '--version'): + print("msgfmt.py", __version__, file=sys.stderr) + sys.exit(0) + elif opt in ('-o', '--output-file'): + outfile = arg + # do it + if not args: + print('No input file given', file=sys.stderr) + print("Try `msgfmt --help' for more information.", file=sys.stderr) + return + + for filename in args: + make(filename, outfile) if __name__ == '__main__': diff --git a/bin/newlist b/bin/newlist index 257df3eb..eeab3eb3 100755 --- a/bin/newlist +++ b/bin/newlist @@ -19,7 +19,7 @@ """Create a new, unpopulated mailing list. -Usage: {PROGRAM} [options] [listname [listadmin-addr [admin-password]]] +Usage: %(PROGRAM)s [options] [listname [listadmin-addr [admin-password]]] Options: @@ -101,14 +101,14 @@ Note that listnames are forced to lowercase. import sys import os import getpass -import argparse +import getopt import paths from Mailman import mm_cfg from Mailman import MailList from Mailman import Utils from Mailman import Errors -from Mailman.Message import Message +from Mailman import Message from Mailman import i18n _ = i18n._ @@ -117,88 +117,93 @@ C_ = i18n.C_ PROGRAM = sys.argv[0] + def usage(code, msg=''): if code: fd = sys.stderr else: fd = sys.stdout - # Ensure PROGRAM is a string, not bytes - if isinstance(PROGRAM, bytes): - PROGRAM = PROGRAM.decode('utf-8', 'replace') - print(C_(__doc__.format( PROGRAM = PROGRAM )), file=fd) + print(C_(__doc__), file=fd) if msg: print(msg, file=fd) sys.exit(code) -def parse_args(): - parser = argparse.ArgumentParser(description='Create a new, unpopulated mailing list.') - parser.add_argument('-l', '--language', - help='Make the list\'s preferred language (two letter code)') - parser.add_argument('-u', '--urlhost', - help='Gives the list\'s web interface host name') - parser.add_argument('-e', '--emailhost', - help='Gives the list\'s email domain name') - parser.add_argument('-q', '--quiet', action='store_true', - help='Suppress the prompt and notification') - parser.add_argument('-a', '--automate', action='store_true', - help='Suppress the prompt but still send notification') - parser.add_argument('listname', nargs='?', - help='Name of the list to create') - parser.add_argument('listadmin', nargs='?', - help='Email address of the list administrator') - parser.add_argument('adminpass', nargs='?', - help='Password for the list administrator') - return parser.parse_args() - - + def main(): try: - args = parse_args() - except SystemExit: - usage(1) - - # Get the list name - if not args.listname: - print(C_('Enter the name of the list: '), end='') - listname = sys.stdin.readline().strip() + opts, args = getopt.getopt(sys.argv[1:], 'hqal:u:e:', + ['help', 'quiet', 'automate', 'language=', + 'urlhost=', 'emailhost=']) + except getopt.error as msg: + usage(1, msg) + + lang = mm_cfg.DEFAULT_SERVER_LANGUAGE + quiet = False + automate = False + urlhost = None + emailhost = None + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + if opt in ('-q', '--quiet'): + quiet = True + if opt in ('-a', '--automate'): + automate = True + if opt in ('-l', '--language'): + lang = arg + if opt in ('-u', '--urlhost'): + urlhost = arg + if opt in ('-e', '--emailhost'): + emailhost = arg + + # Is the language known? + if lang not in mm_cfg.LC_DESCRIPTIONS.keys(): + usage(1, C_('Unknown language: %(lang)s')) + + if len(args) > 0: + listname = args[0] else: - listname = args.listname + listname = input('Enter the name of the list: ') + listname = listname.lower() + + if '@' in listname: + # note that --urlhost and --emailhost have precedence + listname, domain = listname.split('@', 1) + urlhost = urlhost or domain + emailhost = emailhost or mm_cfg.VIRTUAL_HOSTS.get(domain, domain) + + urlhost = urlhost or mm_cfg.DEFAULT_URL_HOST + host_name = emailhost or \ + mm_cfg.VIRTUAL_HOSTS.get(urlhost, mm_cfg.DEFAULT_EMAIL_HOST) + web_page_url = mm_cfg.DEFAULT_URL_PATTERN % urlhost - # Get the list admin's email address - if not args.listadmin: - print(C_('Enter the email of the person running the list: '), end='') - owner_mail = sys.stdin.readline().strip() - else: - owner_mail = args.listadmin + if Utils.list_exists(listname): + usage(1, C_('List already exists: %(listname)s')) - # Get the list admin's password - if not args.adminpass: - print(C_('Initial %(listname)s password: '), end='') - listpasswd = sys.stdin.readline().strip() + if len(args) > 1: + owner_mail = args[1] else: - listpasswd = args.adminpass + owner_mail = input( + C_('Enter the email of the person running the list: ')) - # Get the language - lang = args.language or mm_cfg.DEFAULT_SERVER_LANGUAGE - if lang not in mm_cfg.LC_DESCRIPTIONS: - usage(1, C_('Unknown language code: %(lang)s')) - - # Get the host names - host_name = args.emailhost or mm_cfg.DEFAULT_EMAIL_HOST - urlhost = args.urlhost or mm_cfg.DEFAULT_URL_HOST - web_page_url = mm_cfg.DEFAULT_URL_PATTERN % urlhost + if len(args) > 2: + listpasswd = args[2] + else: + listpasswd = getpass.getpass(C_('Initial %(listname)s password: ')) + # List passwords cannot be empty + listpasswd = listpasswd.strip() + if not listpasswd: + usage(1, C_('The list password cannot be empty')) - # Create the list - mlist = None + mlist = MailList.MailList() try: - mlist = MailList.MailList(listname, lock=1) + pw = Utils.sha_new(listpasswd.encode()).hexdigest() + # Guarantee that all newly created files have the proper permission. + # proper group ownership should be assured by the autoconf script + # enforcing that all directories have the group sticky bit set + oldmask = os.umask(0o002) try: - pw = Utils.sha_new(listpasswd.encode()).hexdigest() - # Guarantee that all newly created files have the proper permission. - # proper group ownership should be assured by the autoconf script - # enforcing that all directories have the group sticky bit set - oldmask = os.umask(0o002) try: if lang == mm_cfg.DEFAULT_SERVER_LANGUAGE: langs = [lang] @@ -209,13 +214,13 @@ def main(): finally: os.umask(oldmask) except Errors.BadListNameError as s: - usage(1, C_(f'Illegal list name: %(s)s')) + usage(1, C_('Illegal list name: %(s)s')) except Errors.EmailAddressError as s: - usage(1, C_(f'Bad owner email address: %(s)s') + + usage(1, C_('Bad owner email address: %(s)s') + C_(' - owner addresses need to be fully-qualified names' ' like "owner@example.com", not just "owner".')) except Errors.MMListAlreadyExistsError: - usage(1, C_(f"List already exists: {listname}")) + usage(1, C_('List already exists: %(listname)s')) # Assign domain-specific attributes mlist.host_name = host_name @@ -226,8 +231,7 @@ def main(): mlist.Save() finally: - if mlist: - mlist.Unlock() + mlist.Unlock() # Now do the MTA-specific list creation tasks if mm_cfg.MTA: @@ -236,10 +240,10 @@ def main(): sys.modules[modname].create(mlist) # And send the notice to the list owner - if not args.quiet and not args.automate: - print(f"Hit enter to notify {listname} owner..."), + if not quiet and not automate: + print('Hit enter to notify %(listname)s owner...'), sys.stdin.readline() - if not args.quiet: + if not quiet: siteowner = Utils.get_site_email(mlist.host_name, 'owner') text = Utils.maketext( 'newlist.txt', @@ -256,14 +260,15 @@ def main(): otrans = i18n.get_translation() i18n.set_language(mlist.preferred_language) try: - msg = Mailman.Message.UserNotification( + msg = Message.UserNotification( owner_mail, siteowner, - _('Your new mailing list: %(listname)s') % {'listname': listname}, + _('Your new mailing list: %(listname)s'), text, mlist.preferred_language) msg.send(mlist) finally: i18n.set_translation(otrans) + if __name__ == '__main__': main() diff --git a/bin/pygettext.py b/bin/pygettext.py index 4dea6cf6..6ed2facb 100644 --- a/bin/pygettext.py +++ b/bin/pygettext.py @@ -140,7 +140,7 @@ import os import sys import time -import argparse +import getopt import tokenize import operator @@ -159,6 +159,7 @@ def _(s): return s EMPTYSTRING = '' + # The normal pot-file header. msgmerge and Emacs's po-mode work better if it's # there. pot_header = _('''\ @@ -180,67 +181,40 @@ def _(s): return s ''') -def parse_args(): - parser = argparse.ArgumentParser(description='Python equivalent of xgettext(1)') - parser.add_argument('-a', '--extract-all', action='store_true', - help='Extract all strings') - parser.add_argument('-d', '--default-domain', - help='Rename the default output file from messages.pot to name.pot') - parser.add_argument('-E', '--escape', action='store_true', - help='Replace non-ASCII characters with octal escape sequences') - parser.add_argument('-D', '--docstrings', action='store_true', - help='Extract module, class, method, and function docstrings') - parser.add_argument('-k', '--keyword', action='append', - help='Keywords to look for in addition to the default set') - parser.add_argument('-K', '--no-default-keywords', action='store_true', - help='Disable the default set of keywords') - parser.add_argument('--no-location', action='store_true', - help='Do not write filename/lineno location comments') - parser.add_argument('-n', '--add-location', action='store_true', - help='Write filename/lineno location comments') - parser.add_argument('-o', '--output', - help='Rename the default output file from messages.pot to filename') - parser.add_argument('-p', '--output-dir', - help='Output files will be placed in directory dir') - parser.add_argument('-S', '--style', choices=['GNU', 'Solaris'], - help='Specify which style to use for location comments') - parser.add_argument('-v', '--verbose', action='store_true', - help='Print the names of the files being processed') - parser.add_argument('-V', '--version', action='version', - version='%(prog)s ' + __version__) - parser.add_argument('-w', '--width', type=int, - help='Set width of output to columns') - parser.add_argument('-x', '--exclude-file', - help='Specify a file that contains a list of strings to exclude') - parser.add_argument('-X', '--no-docstrings', - help='Specify a file that contains a list of files to exclude from docstring extraction') - parser.add_argument('inputfiles', nargs='+', - help='Input files to process') - return parser.parse_args() + +def usage(code, msg=''): + if code: + fd = sys.stderr + else: + fd = sys.stdout + print >> fd, _(__doc__) % globals() + if msg: + print >> fd, msg + sys.exit(code) + escapes = [] def make_escapes(pass_iso8859): global escapes - escapes = [] + if pass_iso8859: + # Allow iso-8859 characters to pass through so that e.g. 'msgid + # "H[o-umlaut]he"' would result not result in 'msgid "H\366he"'. + # Otherwise we escape any character outside the 32..126 range. + mod = 128 + else: + mod = 256 for i in range(256): - if not pass_iso8859 and i >= 0x80: - escapes.append('\\%03o' % i) - elif i == 0: - escapes.append('\\0') - elif i == 9: - escapes.append('\\t') - elif i == 10: - escapes.append('\\n') - elif i == 13: - escapes.append('\\r') - elif i == 34: - escapes.append('\\"') - elif i == 92: - escapes.append('\\\\') - else: + if 32 <= (i % mod) <= 126: escapes.append(chr(i)) + else: + escapes.append("\\%03o" % i) + escapes[ord('\\')] = '\\\\' + escapes[ord('\t')] = '\\t' + escapes[ord('\r')] = '\\r' + escapes[ord('\n')] = '\\n' + escapes[ord('\"')] = '\\"' def escape(s): @@ -253,14 +227,7 @@ def escape(s): def safe_eval(s): # unwrap quotes, safely - r = s.strip() - if r.startswith('"""') or r.startswith("'''"): - quote = r[:3] - r = r[3:-3] - else: - quote = r[0] - r = r[1:-1] - return r + return eval(s, {'__builtins__':{}}, {}) def normalize(s): @@ -280,6 +247,7 @@ def normalize(s): return s + class TokenEater: def __init__(self, options): self.__options = options @@ -289,19 +257,32 @@ def __init__(self, options): self.__lineno = -1 self.__freshmodule = 1 self.__curfile = None - self.__keywords = options.keywords - if not options.no_default_keywords: - self.__keywords.extend(default_keywords) def __call__(self, ttype, tstring, stup, etup, line): # dispatch - self.__state(ttype, tstring, line[0]) +## import token +## print >> sys.stderr, 'ttype:', token.tok_name[ttype], \ +## 'tstring:', tstring + self.__state(ttype, tstring, stup[0]) def __waiting(self, ttype, tstring, lineno): - # ignore anything until we see the keyword - if ttype == tokenize.NAME and tstring in self.__keywords: + opts = self.__options + # Do docstring extractions, if enabled + if opts.docstrings and not opts.nodocstrings.get(self.__curfile): + # module docstring? + if self.__freshmodule: + if ttype == tokenize.STRING: + self.__addentry(safe_eval(tstring), lineno, isdocstring=1) + self.__freshmodule = 0 + elif ttype not in (tokenize.COMMENT, tokenize.NL): + self.__freshmodule = 0 + return + # class docstring? + if ttype == tokenize.NAME and tstring in ('class', 'def'): + self.__state = self.__suiteseen + return + if ttype == tokenize.NAME and tstring in opts.keywords: self.__state = self.__keywordseen - self.__lineno = lineno def __suiteseen(self, ttype, tstring, lineno): # ignore anything until we see the colon @@ -314,170 +295,250 @@ def __suitedocstring(self, ttype, tstring, lineno): self.__addentry(safe_eval(tstring), lineno, isdocstring=1) self.__state = self.__waiting elif ttype not in (tokenize.NEWLINE, tokenize.INDENT, - tokenize.COMMENT): - # there was no doc string + tokenize.COMMENT): + # there was no class docstring self.__state = self.__waiting def __keywordseen(self, ttype, tstring, lineno): - # ignore anything until we see the opening paren if ttype == tokenize.OP and tstring == '(': + self.__data = [] + self.__lineno = lineno self.__state = self.__openseen else: self.__state = self.__waiting def __openseen(self, ttype, tstring, lineno): - # ignore anything until we see the string - if ttype == tokenize.STRING: - self.__addentry(safe_eval(tstring), lineno) - self.__state = self.__waiting - elif ttype not in (tokenize.NEWLINE, tokenize.INDENT, - tokenize.COMMENT): - # there was no string + if ttype == tokenize.OP and tstring == ')': + # We've seen the last of the translatable strings. Record the + # line number of the first line of the strings and update the list + # of messages seen. Reset state for the next batch. If there + # were no strings inside _(), then just ignore this entry. + if self.__data: + self.__addentry(EMPTYSTRING.join(self.__data)) self.__state = self.__waiting + elif ttype == tokenize.STRING: + self.__data.append(safe_eval(tstring)) + # TBD: should we warn if we seen anything else? def __addentry(self, msg, lineno=None, isdocstring=0): - if msg in self.__messages: - entry = self.__messages[msg] - else: - entry = [] - self.__messages[msg] = entry - if lineno is not None: - entry.append((self.__curfile, lineno, isdocstring)) + if lineno is None: + lineno = self.__lineno + if not msg in self.__options.toexclude: + entry = (self.__curfile, lineno) + self.__messages.setdefault(msg, {})[entry] = isdocstring def set_filename(self, filename): self.__curfile = filename + self.__freshmodule = 1 def write(self, fp): options = self.__options - if options.style == options.GNU: - location_format = '#: %(filename)s:%(lineno)d' - else: - location_format = '# File: %(filename)s, line: %(lineno)d' - # - # write the header - # - header = pot_header % { - 'time': time.strftime('%Y-%m-%d %H:%M%z'), - 'version': __version__, - } - fp.write(header) - # - # Sort the entries. First sort each particular entry's locations, - # then sort all the entries by their first location. - # + timestamp = time.ctime(time.time()) + # The time stamp in the header doesn't have the same format as that + # generated by xgettext... + print >> fp, pot_header % {'time': timestamp, 'version': __version__} + # Sort the entries. First sort each particular entry's keys, then + # sort all the entries by their first item. reverse = {} for k, v in self.__messages.items(): - if not v: - continue - # v is a list of (filename, lineno, isdocstring) tuples - v.sort() - first = v[0] - reverse.setdefault(first, []).append((k, v)) - keys = sorted(reverse.keys()) - # - # Now write all the entries - # - for first in keys: - entries = reverse[first] - for k, v in entries: - if options.writelocations: - for filename, lineno, isdocstring in v: - if isdocstring: - fp.write('#. ') - fp.write(location_format % { - 'filename': filename, - 'lineno': lineno, - }) - fp.write('\n') - fp.write('msgid %s\n' % normalize(k)) - fp.write('msgstr ""\n') - fp.write('\n') - - + keys = v.keys() + keys.sort() + reverse.setdefault(tuple(keys), []).append((k, v)) + rkeys = reverse.keys() + rkeys.sort() + for rkey in rkeys: + rentries = reverse[rkey] + rentries.sort() + for k, v in rentries: + isdocstring = 0 + # If the entry was gleaned out of a docstring, then add a + # comment stating so. This is to aid translators who may wish + # to skip translating some unimportant docstrings. + if reduce(operator.__add__, v.values()): + isdocstring = 1 + # k is the message string, v is a dictionary-set of (filename, + # lineno) tuples. We want to sort the entries in v first by + # file name and then by line number. + v = v.keys() + v.sort() + if not options.writelocations: + pass + # location comments are different b/w Solaris and GNU: + elif options.locationstyle == options.SOLARIS: + for filename, lineno in v: + d = {'filename': filename, 'lineno': lineno} + print >>fp, _( + '# File: %(filename)s, line: %(lineno)d') % d + elif options.locationstyle == options.GNU: + # fit as many locations on one line, as long as the + # resulting line length doesn't exceeds 'options.width' + locline = '#:' + for filename, lineno in v: + d = {'filename': filename, 'lineno': lineno} + s = _(' %(filename)s:%(lineno)d') % d + if len(locline) + len(s) <= options.width: + locline = locline + s + else: + print >> fp, locline + locline = "#:" + s + if len(locline) > 2: + print >> fp, locline + if isdocstring: + print >> fp, '#, docstring' + print >> fp, 'msgid', normalize(k) + print >> fp, 'msgstr ""\n' + + + def main(): - args = parse_args() - + global default_keywords + try: + opts, args = getopt.getopt( + sys.argv[1:], + 'ad:DEhk:Kno:p:S:Vvw:x:X:', + ['extract-all', 'default-domain=', 'escape', 'help', + 'keyword=', 'no-default-keywords', + 'add-location', 'no-location', 'output=', 'output-dir=', + 'style=', 'verbose', 'version', 'width=', 'exclude-file=', + 'docstrings', 'no-docstrings', + ]) + except getopt.error as msg: + usage(1, msg) + + # for holding option values class Options: # constants GNU = 1 SOLARIS = 2 # defaults - extractall = args.extract_all - escape = args.escape - keywords = args.keyword or [] - outpath = args.output_dir or '' - outfile = args.output or 'messages.pot' - writelocations = not args.no_location - locationstyle = args.style == 'Solaris' and SOLARIS or GNU - verbose = args.verbose - width = args.width or 78 - excludefilename = args.exclude_file or '' - docstrings = args.docstrings + extractall = 0 # FIXME: currently this option has no effect at all. + escape = 0 + keywords = [] + outpath = '' + outfile = 'messages.pot' + writelocations = 1 + locationstyle = GNU + verbose = 0 + width = 78 + excludefilename = '' + docstrings = 0 nodocstrings = {} - if args.no_docstrings: + + options = Options() + locations = {'gnu' : options.GNU, + 'solaris' : options.SOLARIS, + } + + # parse options + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-a', '--extract-all'): + options.extractall = 1 + elif opt in ('-d', '--default-domain'): + options.outfile = arg + '.pot' + elif opt in ('-E', '--escape'): + options.escape = 1 + elif opt in ('-D', '--docstrings'): + options.docstrings = 1 + elif opt in ('-k', '--keyword'): + options.keywords.append(arg) + elif opt in ('-K', '--no-default-keywords'): + default_keywords = [] + elif opt in ('-n', '--add-location'): + options.writelocations = 1 + elif opt in ('--no-location',): + options.writelocations = 0 + elif opt in ('-S', '--style'): + options.locationstyle = locations.get(arg.lower()) + if options.locationstyle is None: + usage(1, _('Invalid value for --style: %s') % arg) + elif opt in ('-o', '--output'): + options.outfile = arg + elif opt in ('-p', '--output-dir'): + options.outpath = arg + elif opt in ('-v', '--verbose'): + options.verbose = 1 + elif opt in ('-V', '--version'): + print(_('pygettext.py (xgettext for Python) %s') % __version__) + sys.exit(0) + elif opt in ('-w', '--width'): + try: + options.width = int(arg) + except ValueError: + usage(1, _('--width argument must be an integer: %s') % arg) + elif opt in ('-x', '--exclude-file'): + options.excludefilename = arg + elif opt in ('-X', '--no-docstrings'): + fp = open(arg) try: - fp = open(args.no_docstrings) - nodocstrings = {} - for line in fp: - nodocstrings[line.strip()] = None + while 1: + line = fp.readline() + if not line: + break + options.nodocstrings[line[:-1]] = 1 + finally: fp.close() - except IOError: - pass - options = Options() - eater = TokenEater(options) - - # Make escapes dictionary - make_escapes(not options.escape) - - # Read the exclusion file, if any - excluded = {} + # calculate escapes + make_escapes(options.escape) + + # calculate all keywords + options.keywords.extend(default_keywords) + + # initialize list of strings to exclude if options.excludefilename: try: fp = open(options.excludefilename) - for line in fp: - line = line.strip() - excluded[line] = None + options.toexclude = fp.readlines() fp.close() except IOError: - pass - - # Process each input file - for filename in args.inputfiles: + print >> sys.stderr, _( + "Can't read --exclude-file: %s") % options.excludefilename + sys.exit(1) + else: + options.toexclude = [] + + # slurp through all the files + eater = TokenEater(options) + for filename in args: if filename == '-': if options.verbose: - print('Reading standard input') + print(_('Reading standard input')) fp = sys.stdin - eater.set_filename('stdin') - try: - tokenize.tokenize(fp.readline, eater) - except tokenize.TokenError as e: - print('%s: %s' % (filename, e), file=sys.stderr) - continue + closep = 0 else: if options.verbose: - print('Working on %s' % filename) + print(_('Working on %s') % filename) + fp = open(filename) + closep = 1 + try: + eater.set_filename(filename) try: - fp = open(filename) - eater.set_filename(filename) tokenize.tokenize(fp.readline, eater) + except tokenize.TokenError as e: + print('%s: %s, line %d, column %d' % ( + e[0], filename, e[1][0], e[1][1]), file=sys.stderr) + finally: + if closep: fp.close() - except IOError as e: - print('%s: %s' % (filename, e), file=sys.stderr) - continue - - # Write the output + + # write the output if options.outfile == '-': fp = sys.stdout + closep = 0 else: + if options.outpath: + options.outfile = os.path.join(options.outpath, options.outfile) fp = open(options.outfile, 'w') + closep = 1 try: eater.write(fp) finally: - if fp is not sys.stdout: + if closep: fp.close() - + if __name__ == '__main__': main() # some more test strings diff --git a/bin/qrunner b/bin/qrunner index e0ed3dfe..7b7b515e 100644 --- a/bin/qrunner +++ b/bin/qrunner @@ -73,16 +73,16 @@ operation. It is only useful for debugging if it is run separately. """ import sys -import argparse +import getopt import signal import time -import os -import threading -import traceback -from io import StringIO import paths from Mailman import mm_cfg +# Debug: Log when mm_cfg is imported in qrunner +from Mailman.Logging.Syslog import syslog +syslog('debug', 'qrunner: mm_cfg imported from %s', mm_cfg.__file__) +syslog('debug', 'qrunner: mm_cfg.GLOBAL_PIPELINE type: %s', type(mm_cfg.GLOBAL_PIPELINE).__name__ if hasattr(mm_cfg, 'GLOBAL_PIPELINE') else 'NOT FOUND') from Mailman.i18n import C_ from Mailman.Logging.Syslog import syslog from Mailman.Logging.Utils import LogStdErr @@ -94,35 +94,19 @@ COMMASPACE = ', ' AS_SUBPROC = 0 -def parse_args(): - parser = argparse.ArgumentParser(description='Run one or more qrunners, once or repeatedly.') - parser.add_argument('-r', '--runner', action='append', - help='Run the named qrunner. Format: runner[:slice:range]') - parser.add_argument('-o', '--once', action='store_true', - help='Run each named qrunner exactly once through its main loop') - parser.add_argument('-l', '--list', action='store_true', - help='Show available qrunner names and exit') - parser.add_argument('-v', '--verbose', action='store_true', - help='Spit out more debugging information to the logs/qrunner log file') - parser.add_argument('-s', '--subproc', action='store_true', - help='Run as a subprocess of mailmanctl') - return parser.parse_args() - - + def usage(code, msg=''): if code: fd = sys.stderr else: fd = sys.stdout - # Ensure PROGRAM is a string, not bytes - if isinstance(PROGRAM, bytes): - PROGRAM = PROGRAM.decode('utf-8', 'replace') print(C_(__doc__), file=fd) if msg: print(msg, file=fd) sys.exit(code) + def make_qrunner(name, slice, range, once=0): modulename = 'Mailman.Queue.' + name try: @@ -147,6 +131,7 @@ def make_qrunner(name, slice, range, once=0): return qrunner + def set_signals(loop): # Set up the SIGTERM handler for stopping the loop def sigterm_handler(signum, frame, loop=loop): @@ -154,18 +139,7 @@ def set_signals(loop): loop.stop() loop.status = signal.SIGTERM syslog('qrunner', '%s qrunner caught SIGTERM. Stopping.', loop.name()) - # Log traceback - s = StringIO() - traceback.print_stack(file=s) - syslog('error', 'Traceback on SIGTERM:\n%s', s.getvalue()) - # Force exit after 5 seconds - def force_exit(): - time.sleep(5) - syslog('qrunner', '%s qrunner forcing exit after timeout.', loop.name()) - os._exit(signal.SIGTERM) - threading.Thread(target=force_exit, daemon=True).start() signal.signal(signal.SIGTERM, sigterm_handler) - # Set up the SIGINT handler for stopping the loop. For us, SIGINT is # the same as SIGTERM, but our parent treats the exit statuses # differently (it restarts a SIGINT but not a SIGTERM). @@ -174,87 +148,91 @@ def set_signals(loop): loop.stop() loop.status = signal.SIGINT syslog('qrunner', '%s qrunner caught SIGINT. Stopping.', loop.name()) - # Log traceback - s = StringIO() - traceback.print_stack(file=s) - syslog('error', 'Traceback on SIGINT:\n%s', s.getvalue()) - # Force exit after 5 seconds - def force_exit(): - time.sleep(5) - syslog('qrunner', '%s qrunner forcing exit after timeout.', loop.name()) - os._exit(signal.SIGINT) - threading.Thread(target=force_exit, daemon=True).start() signal.signal(signal.SIGINT, sigint_handler) - # SIGHUP just tells us to close our log files. They'll be # automatically reopened at the next log print :) def sighup_handler(signum, frame, loop=loop): - try: - syslog.close() - # Reopen syslog connection - syslog.open() - syslog('qrunner', '%s qrunner caught SIGHUP. Reopening logs.', - loop.name()) - except Exception as e: - # Log any errors but don't let them propagate - print('Error in SIGHUP handler:', str(e), file=sys.stderr) + syslog.close() + syslog('qrunner', '%s qrunner caught SIGHUP. Reopening logs.', + loop.name()) signal.signal(signal.SIGHUP, sighup_handler) + def main(): global AS_SUBPROC try: - args = parse_args() - except SystemExit: - usage(1) + opts, args = getopt.getopt( + sys.argv[1:], 'hlor:vs', + ['help', 'list', 'once', 'runner=', 'verbose', 'subproc']) + except getopt.error as msg: + usage(1, msg) - if args.list: - for runnername, slices in mm_cfg.QRUNNERS: - if runnername.endswith('Runner'): - name = runnername[:-len('Runner')] - else: - name = runnername - print(C_('%(name)s runs the %(runnername)s qrunner')) - print(C_('All runs all the above qrunners')) - sys.exit(0) + def silent_unraisable_hook(unraisable): + pass - if not args.runner: - usage(1, C_('No runner specified')) + if hasattr(sys, 'unraisablehook'): + sys.unraisablehook = silent_unraisable_hook + once = 0 runners = [] - for runnerspec in args.runner: - parts = runnerspec.split(':') - if len(parts) == 1: - runner = parts[0] - slice = 1 - range = 1 - elif len(parts) == 3: - runner = parts[0] - try: - slice = int(parts[1]) - range = int(parts[2]) - except ValueError: - usage(1, 'Bad runner specification: %(runnerspec)s') - else: - usage(1, 'Bad runner specification: %(runnerspec)s') - if runner == 'All': + verbose = 0 + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-l', '--list'): for runnername, slices in mm_cfg.QRUNNERS: if runnername.endswith('Runner'): name = runnername[:-len('Runner')] else: name = runnername - runners.append((name, 1, 1)) - else: - runners.append((runner, slice, range)) + print(C_('%(name)s runs the %(runnername)s qrunner')) + print(C_('All runs all the above qrunners')) + sys.exit(0) + elif opt in ('-o', '--once'): + once = 1 + elif opt in ('-r', '--runner'): + runnerspec = arg + parts = runnerspec.split(':') + if len(parts) == 1: + runner = parts[0] + slice = 1 + range = 1 + elif len(parts) == 3: + runner = parts[0] + try: + slice = int(parts[1]) + range = int(parts[2]) + except ValueError: + usage(1, 'Bad runner specification: %(runnerspec)s') + else: + usage(1, 'Bad runner specification: %(runnerspec)s') + if runner == 'All': + for runnername, slices in mm_cfg.QRUNNERS: + runners.append((runnername, slice, range)) + else: + if runner.endswith('Runner'): + runners.append((runner, slice, range)) + else: + runners.append((runner + 'Runner', slice, range)) + elif opt in ('-s', '--subproc'): + AS_SUBPROC = 1 + elif opt in ('-v', '--verbose'): + verbose = 1 - AS_SUBPROC = args.subproc - if args.verbose: - LogStdErr('debug', 'qrunner', manual_reprime=0) - else: - LogStdErr('error', 'qrunner', manual_reprime=0) + if len(args) != 0: + usage(1) + if len(runners) == 0: + usage(1, C_('No runner name given.')) + + # Before we startup qrunners, we redirect the stderr to mailman syslog. + # We assume !AS_SUBPROC is running for debugging purpose and don't + # log errors in mailman logs/error but keep printing to stderr. + if AS_SUBPROC: + LogStdErr('error', 'qrunner', manual_reprime=0, tee_to_real_stderr=0) # Fast track for one infinite runner - if len(runners) == 1 and not args.once: + if len(runners) == 1 and not once: qrunner = make_qrunner(*runners[0]) class Loop: status = 0 @@ -269,15 +247,12 @@ def main(): # Now start up the main loop syslog('qrunner', '%s qrunner started.', loop.name()) qrunner.run() - # Only exit with SIGINT if we're stopping normally - if not qrunner._stop: - loop.status = signal.SIGINT syslog('qrunner', '%s qrunner exiting.', loop.name()) else: # Anything else we have to handle a bit more specially qrunners = [] for runner, slice, range in runners: - qrunner = make_qrunner(runner, slice, range, args.once) + qrunner = make_qrunner(runner, slice, range, 1) qrunners.append(qrunner) # This class is used to manage the main loop class Loop: @@ -298,14 +273,11 @@ def main(): # In case the SIGTERM came in the middle of this iteration if loop.isdone(): break - if args.verbose: + if verbose: syslog('qrunner', 'Now doing a %s qrunner iteration', qrunner.__class__.__bases__[0].__name__) qrunner.run() - # Only exit with SIGINT if we're stopping normally - if not qrunner._stop: - loop.status = signal.SIGINT - if args.once: + if once: break if mm_cfg.QRUNNER_SLEEP_TIME > 0: time.sleep(mm_cfg.QRUNNER_SLEEP_TIME) @@ -314,5 +286,6 @@ def main(): sys.exit(loop.status) + if __name__ == '__main__': main() diff --git a/bin/rb-archfix b/bin/rb-archfix index 2fcd55e2..7b566bb0 100644 --- a/bin/rb-archfix +++ b/bin/rb-archfix @@ -47,7 +47,7 @@ from __future__ import print_function import os import sys -import argparse +import getopt import marshal import pickle @@ -58,48 +58,43 @@ from Mailman.i18n import C_ PROGRAM = sys.argv[0] -def parse_args(): - parser = argparse.ArgumentParser(description='Reduce disk space usage for Pipermail archives.') - parser.add_argument('files', nargs='+', - help='Files to process') - return parser.parse_args() + +def usage(code, msg=''): + if code: + fd = sys.stderr + else: + fd = sys.stdout + print(C_(__doc__), file=fd) + if msg: + print(msg, file=fd) + sys.exit(code) -def load_article(pckstr): - """Load an article from a pickle string with Python 2/3 compatibility.""" - try: - return pickle.loads(pckstr, fix_imports=True, encoding='latin1') - except Exception as e: - print('Error loading article: %s' % e) - return None - - -def save_article(article): - """Save an article to a pickle string with Python 2/3 compatibility.""" + +def main(): + # get command line arguments try: - return pickle.dumps(article, protocol=4, fix_imports=True) - except Exception as e: - print('Error saving article: %s' % e) - return None - + opts, args = getopt.getopt(sys.argv[1:], 'h', ['help']) + except getopt.error as msg: + usage(1, msg) -def main(): - args = parse_args() + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) - for filename in args.files: + for filename in args: print(('processing:', filename)) fp = open(filename, 'rb') d = marshal.load(fp) fp.close() newd = {} for key, pckstr in d.items(): - article = load_article(pckstr) - if article: - try: - del article.html_body - except AttributeError: - pass - newd[key] = save_article(article) + article = pickle.loads(pckstr) + try: + del article.html_body + except AttributeError: + pass + newd[key] = pickle.dumps(article) fp = open(filename + '.tmp', 'wb') marshal.dump(newd, fp) fp.close() @@ -109,5 +104,6 @@ def main(): print('You should now run "bin/check_perms -f"') + if __name__ == '__main__': main() diff --git a/bin/remove_members b/bin/remove_members index aadd7cb3..2fd1e5c9 100755 --- a/bin/remove_members +++ b/bin/remove_members @@ -60,79 +60,120 @@ Options: """ import sys -import argparse +import getopt import paths -from Mailman import mm_cfg -from Mailman import Utils from Mailman import MailList +from Mailman import Utils from Mailman import Errors -from Mailman import i18n - -_ = i18n._ +from Mailman.i18n import C_ + def usage(code, msg=''): if code: fd = sys.stderr else: fd = sys.stdout - print(_(__doc__), file=fd) + print(C_(__doc__), file=fd) if msg: print(msg, file=fd) sys.exit(code) -def main(): - parser = argparse.ArgumentParser(description='Remove members from a mailing list.') - parser.add_argument('listname', help='Name of the mailing list') - parser.add_argument('-a', '--admin-notify', action='store_true', - help='Send admin notification') - parser.add_argument('-f', '--file', help='File containing member addresses') - parser.add_argument('-n', '--no-admin-notify', action='store_true', - help='Do not send admin notification') - parser.add_argument('-N', '--no-userack', action='store_true', - help='Do not send user acknowledgment') - parser.add_argument('-w', '--welcome-msg', action='store_true', - help='Send welcome message') - - args = parser.parse_args() - try: - mlist = MailList.MailList(args.listname, lock=1) - except Errors.MMUnknownListError: - usage(1, _('No such list "%(listname)s"')) - - if args.file: - try: - fp = open(args.file) - except IOError: - usage(1, _('Cannot open file: %(file)s')) - addrs = [] - for line in fp: - line = line.strip() - if line and not line.startswith('#'): - addrs.append(line) - fp.close() +def ReadFile(filename): + lines = [] + if filename == "-": + fp = sys.stdin + closep = False else: - addrs = sys.stdin.read().splitlines() + fp = open(filename) + closep = True + lines = filter(None, [line.strip() for line in fp.readlines()]) + if closep: + fp.close() + return lines - if not addrs: - usage(1, _('No addresses to remove')) - # Process each address - for addr in addrs: - addr = addr.strip() - if not addr or addr.startswith('#'): - continue - try: - mlist.DeleteMember(addr, admin_notif=not args.no_admin_notify, - userack=not args.no_userack) - except Errors.NotAMemberError: - print(_('%(addr)s is not a member of %(listname)s')) - except Errors.MMListError as e: - print(_('%(addr)s: %(error)s')) + +def main(): + try: + opts, args = getopt.getopt( + sys.argv[1:], 'naf:hN', + ['all', 'fromall', 'file=', 'help', 'nouserack', 'noadminack']) + except getopt.error as msg: + usage(1, msg) + + filename = None + all = False + alllists = False + # None means use list default + userack = None + admin_notif = None + + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-f', '--file'): + filename = arg + elif opt in ('-a', '--all'): + all = True + elif opt == '--fromall': + alllists = True + elif opt in ('-n', '--nouserack'): + userack = False + elif opt in ('-N', '--noadminack'): + admin_notif = False + + if len(args) < 1 and not (filename and alllists): + usage(1) + + # You probably don't want to delete all the users of all the lists -- Marc + if all and alllists: + usage(1) + + if alllists: + addresses = args + else: + listname = args[0].lower().strip() + addresses = args[1:] - mlist.Save() - mlist.Unlock() + if alllists: + listnames = Utils.list_names() + else: + listnames = [listname] + if filename: + try: + addresses = addresses + ReadFile(filename) + except IOError: + print(C_('Could not open file for reading: %(filename)s.')) + + for listname in listnames: + try: + # open locked + mlist = MailList.MailList(listname) + except Errors.MMListError: + print(C_('Error opening list %(listname)s... skipping.')) + continue + + if all: + addresses = mlist.getMembers() + + try: + for addr in addresses: + if not mlist.isMember(addr): + if not alllists: + print(C_('No such member: %(addr)s')) + continue + mlist.ApprovedDeleteMember(addr, 'bin/remove_members', + admin_notif, userack) + if alllists: + print(C_("User `%(addr)s' removed from list: %(listname)s.")) + mlist.Save() + finally: + mlist.Unlock() + + + if __name__ == '__main__': main() diff --git a/bin/reset_pw.py b/bin/reset_pw.py index 9219ec26..41dea0f0 100644 --- a/bin/reset_pw.py +++ b/bin/reset_pw.py @@ -34,36 +34,50 @@ """ import sys -import argparse +import getopt import paths from Mailman import Utils from Mailman.i18n import C_ -def parse_args(args): - parser = argparse.ArgumentParser(description='Reset the passwords for members of a mailing list.') - parser.add_argument('-v', '--verbose', action='store_true', - help='Print what the script is doing') - return parser.parse_args(args) + +def usage(code, msg=''): + if code: + fd = sys.stderr + else: + fd = sys.stdout + print(C_(__doc__.replace('%', '%%')), file=fd) + if msg: + print(msg, file=fd) + sys.exit(code) + def reset_pw(mlist, *args): - args = parse_args(args) + try: + opts, args = getopt.getopt(args, 'v', ['verbose']) + except getopt.error as msg: + usage(1, msg) + + verbose = False + for opt, args in opts: + if opt in ('-v', '--verbose'): + verbose = True listname = mlist.internal_name() - if args.verbose: + if verbose: print(C_('Changing passwords for list: %(listname)s')) for member in mlist.getMembers(): randompw = Utils.MakeRandomPassword() mlist.setMemberPassword(member, randompw) - if args.verbose: + if verbose: print(C_('New password for member %(member)40s: %(randompw)s')) mlist.Save() + if __name__ == '__main__': - print(C_(__doc__.replace('%', '%%'))) - sys.exit(0) + usage(0) diff --git a/bin/rmlist b/bin/rmlist index 1c378d92..d942a394 100755 --- a/bin/rmlist +++ b/bin/rmlist @@ -39,7 +39,7 @@ Where: import os import re import sys -import argparse +import getopt import shutil import paths @@ -48,7 +48,7 @@ from Mailman import Utils from Mailman import MailList from Mailman.i18n import C_ - + def usage(code, msg=''): if code: fd = sys.stderr @@ -60,6 +60,7 @@ def usage(code, msg=''): sys.exit(code) + def remove_it(listname, filename, msg): if os.path.islink(filename): print(C_('Removing %(msg)s')) @@ -73,24 +74,34 @@ def remove_it(listname, filename, msg): print(C_('%(listname)s %(msg)s not found as %(filename)s')) + def main(): - parser = argparse.ArgumentParser(description='Remove the components of a mailing list with impunity - beware!') - parser.add_argument('listname', help='Name of the mailing list to remove') - parser.add_argument('-a', '--archives', action='store_true', - help='Remove the list\'s archives too, or if the list has already been deleted, remove any residual archives') - - args = parser.parse_args() - listname = args.listname.lower().strip() + try: + opts, args = getopt.getopt(sys.argv[1:], 'ah', + ['archives', 'help']) + except getopt.error as msg: + usage(1, msg) + + if len(args) != 1: + usage(1) + listname = args[0].lower().strip() + + removeArchives = False + for opt, arg in opts: + if opt in ('-a', '--archives'): + removeArchives = True + elif opt in ('-h', '--help'): + usage(0) if not Utils.list_exists(listname): - if not args.archives: + if not removeArchives: usage(1, C_( 'No such list (or list already deleted): %(listname)s')) else: print(C_( 'No such list: %(listname)s. Removing its residual archives.')) - if not args.archives: + if not removeArchives: print(C_('Not removing archives. Reinvoke with -a to remove them.')) @@ -117,13 +128,13 @@ def main(): # Remove any held messages for this list for filename in os.listdir(mm_cfg.DATA_DIR): - cre = re.compile(r'^heldmsg-%s-\d+\.(pck|txt)$' % re.escape(listname), + cre = re.compile('^heldmsg-%s-\d+\.(pck|txt)$' % re.escape(listname), re.IGNORECASE) if cre.match(filename): REMOVABLES.append((os.path.join(mm_cfg.DATA_DIR, filename), C_('held message file'))) - if args.archives: + if removeArchives: REMOVABLES.extend([ (os.path.join(mm_cfg.PRIVATE_ARCHIVE_FILE_DIR, listname), C_('private archives')), @@ -139,5 +150,6 @@ def main(): remove_it(listname, dir, msg) + if __name__ == '__main__': main() diff --git a/bin/show_qfiles b/bin/show_qfiles index 9268f1ab..8125d7c2 100644 --- a/bin/show_qfiles +++ b/bin/show_qfiles @@ -34,14 +34,13 @@ Example: show_qfiles qfiles/shunt/*.pck from __future__ import print_function import sys -import argparse +import getopt from pickle import load -import pickle import paths from Mailman.i18n import C_ - + def usage(code, msg=''): if code: fd = sys.stderr @@ -53,52 +52,38 @@ def usage(code, msg=''): sys.exit(code) + def main(): - parser = argparse.ArgumentParser(description='Show the contents of one or more Mailman queue files.') - parser.add_argument('qfiles', nargs='+', help='Queue files to display') - parser.add_argument('-q', '--quiet', action='store_true', - help='Don\'t print helpful message delimiters') - - args = parser.parse_args() - - for filename in args.qfiles: - if not args.quiet: + try: + opts, args = getopt.getopt(sys.argv[1:], 'hq', ['help', 'quiet']) + except getopt.error as msg: + usage(1, msg) + + quiet = False + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-q', '--quiet'): + quiet = True + + if not args: + usage(1, "Not enough arguments") + + for filename in args: + if not quiet: print(('====================>', filename)) + fp = open(filename,'rb') if filename.endswith(".pck"): - try: - with open(filename, 'rb') as fp: - try: - # Try UTF-8 first for newer files - data = load(fp, fix_imports=True, encoding='utf-8') - if isinstance(data, tuple) and len(data) == 2: - msg, metadata = data - else: - msg = data - metadata = {} - except (UnicodeDecodeError, pickle.UnpicklingError): - # Fall back to latin1 for older files - fp.seek(0) - data = load(fp, fix_imports=True, encoding='latin1') - if isinstance(data, tuple) and len(data) == 2: - msg, metadata = data - else: - msg = data - metadata = {} - - # Handle the message output - if isinstance(msg, str): - sys.stdout.write(msg) - elif hasattr(msg, 'as_string'): - sys.stdout.write(msg.as_string()) - else: - sys.stdout.write(str(msg)) - except Exception as e: - print('Error reading pickle file %s: %s' % (filename, str(e)), file=sys.stderr) - sys.exit(1) + msg = load(fp) + data = load(fp) + if data.get('_parsemsg'): + sys.stdout.write(msg) + else: + sys.stdout.write(msg.as_string()) else: - with open(filename) as fp: - sys.stdout.write(fp.read()) + sys.stdout.write(fp.read()) + if __name__ == '__main__': main() diff --git a/bin/sync_members b/bin/sync_members index efbe42a4..71a69638 100755 --- a/bin/sync_members +++ b/bin/sync_members @@ -77,9 +77,10 @@ Where `options' are: """ import sys + import paths +# Import this /after/ paths so that the sys.path is properly hacked import email.utils -import argparse from Mailman import MailList from Mailman import Errors @@ -96,9 +97,6 @@ def usage(code, msg=''): fd = sys.stderr else: fd = sys.stdout - # Ensure PROGRAM is a string, not bytes - if isinstance(PROGRAM, bytes): - PROGRAM = PROGRAM.decode('utf-8', 'replace') print(C_(__doc__), file=fd) if msg: print(msg, file=fd) @@ -106,107 +104,187 @@ def usage(code, msg=''): -def parse_args(): - parser = argparse.ArgumentParser(description=C_('Synchronize a mailing list\'s membership with a flat file.')) - - parser.add_argument('-n', '--no-change', - action='store_true', - help=C_('Don\'t actually make the changes. Instead, print out what would be done to the list.')) - - parser.add_argument('-w', '--welcome-msg', - nargs='?', - const='yes', - choices=['yes', 'no'], - help=C_('Sets whether or not to send the newly added members a welcome message, overriding whatever the list\'s `send_welcome_msg` setting is.')) - - parser.add_argument('-g', '--goodbye-msg', - nargs='?', - const='yes', - choices=['yes', 'no'], - help=C_('Sets whether or not to send the goodbye message to removed members, overriding whatever the list\'s `send_goodbye_msg` setting is.')) - - parser.add_argument('-d', '--digest', - nargs='?', - const='yes', - choices=['yes', 'no'], - help=C_('Selects whether to make newly added members receive messages in digests.')) - - parser.add_argument('-a', '--notifyadmin', - nargs='?', - const='yes', - choices=['yes', 'no'], - help=C_('Specifies whether the admin should be notified for each subscription or unsubscription.')) - - parser.add_argument('-f', '--file', - required=True, - help=C_('The flat file to synchronize against. Email addresses must appear one per line. Use \'-\' for stdin.')) - - parser.add_argument('listname', - help=C_('The list to synchronize.')) - - args = parser.parse_args() - - # Convert yes/no options to boolean values - if args.welcome_msg: - args.welcome_msg = args.welcome_msg.lower() == 'yes' - if args.goodbye_msg: - args.goodbye_msg = args.goodbye_msg.lower() == 'yes' - if args.digest: - args.digest = args.digest.lower() == 'yes' - if args.notifyadmin: - args.notifyadmin = args.notifyadmin.lower() == 'yes' - - return args +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, C_('Bad choice: %(yesno)s')) + # no return def main(): - args = parse_args() + dryrun = 0 + digest = 0 + welcome = None + goodbye = None + filename = None + listname = None + notifyadmin = None - # Get the list name - listname = args.listname + # 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(C_('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, C_('Only one -f switch allowed')) + try: + filename = sys.argv[i+1] + except IndexError: + usage(1, C_('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, C_('Illegal option: %(opt)s')) + else: + try: + listname = sys.argv[i].lower() + i += 1 + except IndexError: + usage(1, C_('No listname given')) + break - # Get the list object - try: - mlist = MailList.MailList(listname, lock=1) - except Errors.MMUnknownListError: - usage(1, C_('No such list: %(listname)s')) - - # Get the members to sync - members = [] - if args.file == '-': - members = sys.stdin.read().splitlines() + if listname is None or filename is None: + usage(1, C_('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: - with open(args.file) as fp: - members = fp.read().splitlines() - except IOError: - usage(1, C_('Cannot open file: %(file)s') % - {'file': args.file}) - - # Process each member - for member in members: - member = member.strip() - if not member or member.startswith('#'): - continue - # Convert email address to lowercase - member = member.lower() + fp = open(filename) + except IOError as msg: + usage(1, C_('Cannot read address file: %(filename)s: %(msg)s')) try: - mlist.SyncMember(member, args.digest, args.moderate, - args.text, args.userack, args.notifyadmin, - args.welcome_msg, args.language) - except Errors.MMAlreadyAMember: - print(C_('%(member)s is already a member of %(listname)s')) - except Errors.MMHostileAddress: - print(C_('%(member)s is a hostile address')) - except Errors.MMInvalidEmailAddress: - print(C_('%(member)s is not a valid email address')) - except Errors.MMBadEmailError: - print(C_('%(member)s is not a valid email address')) - except Errors.MMListError as e: - print(C_('%(member)s: %(error)s')) - - mlist.Save() - mlist.Unlock() + 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(C_('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(C_('Invalid : %(addr)30s')) + invalid = 1 + if invalid: + print(C_('You must fix the preceding invalid addresses first.')) + sys.exit(1) + + # get the locked list object + try: + mlist = MailList.MailList(listname) + except Errors.MMListError as e: + print(C_('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 laddr in addrs: + del addrs[laddr] + matches[laddr] = 1 + elif not matches.has_key(laddr): + needsadding[laddr] = (name, addr) + + if not needsadding and not addrs: + print(C_('Nothing to do.')) + sys.exit(0) + + enc = sys.getdefaultencoding() + # 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) + name = name.encode(enc, 'replace') + s = email.utils.formataddr((name, addr)).encode(enc, 'replace') + print(C_('Added : %(s)s')) + except Errors.MMAlreadyAMember: + pass + except Errors.MembershipIsBanned as pattern: + print(('%s:' % addr), C_( + 'Banned address (matched %(pattern)s)')) + + for laddr, addr in addrs.items(): + # Should be a member, otherwise our test above is broken + name = mlist.getMemberName(laddr) or '' + 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 = name.encode(enc, 'replace') + s = email.utils.formataddr((name, addr)).encode(enc, 'replace') + print(C_('Removed: %(s)s')) + + mlist.Save() + finally: + mlist.Unlock() if __name__ == '__main__': diff --git a/bin/transcheck b/bin/transcheck index 096f0613..5ec19a47 100755 --- a/bin/transcheck +++ b/bin/transcheck @@ -34,7 +34,7 @@ from __future__ import print_function import sys import re import os -import argparse +import getopt import paths from Mailman.i18n import C_ @@ -42,14 +42,7 @@ from Mailman.i18n import C_ program = sys.argv[0] -def parse_args(): - parser = argparse.ArgumentParser(description='Check a given Mailman translation.') - parser.add_argument('lang', help='Country code (e.g. "it" for Italy)') - parser.add_argument('-q', '--quiet', action='store_true', - help='Ask for a brief summary') - return parser.parse_args() - - + def usage(code, msg=''): if code: fd = sys.stderr @@ -61,6 +54,7 @@ def usage(code, msg=''): sys.exit(code) + class TransChecker: "check a translation comparing with the original string" def __init__(self, regexp, escaped=None): @@ -126,6 +120,7 @@ class TransChecker: self.errs = [] + class POParser: "parse a .po file extracting msgids and msgstrs" def __init__(self, filename=""): @@ -281,6 +276,7 @@ class POParser: + def check_file(translatedFile, originalFile, html=0, quiet=0): """check a translated template against the original one search also tags if html is not zero""" @@ -327,6 +323,7 @@ def check_file(translatedFile, originalFile, html=0, quiet=0): return n + def check_po(file, quiet=0): "scan the po file comparing msgids with msgstrs" n = 0 @@ -348,56 +345,70 @@ def check_po(file, quiet=0): p.close() return n + def main(): try: - args = parse_args() - except SystemExit: + opts, args = getopt.getopt(sys.argv[1:], 'qh', ['quiet', 'help']) + except getopt.error as msg: + usage(1, msg) + + quiet = 0 + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-q', '--quiet'): + quiet = 1 + + if len(args) != 1: usage(1) - lang = args.lang - quiet = args.quiet + lang = args[0] - # Check if the language directory exists - lang_dir = os.path.join(paths.prefix, 'messages', lang) - if not os.path.isdir(lang_dir): - usage(1, C_('Language directory %(lang_dir)s does not exist')) + isHtml = re.compile("\.html$"); + isTxt = re.compile("\.txt$"); - # Initialize checkers - var_checker = TransChecker(r'%\([^\)]+\)s') - tag_checker = TransChecker(r'<[^>]+>', r'&[^;]+;') + numerrors = 0 + numfiles = 0 + try: + files = os.listdir("templates/" + lang + "/") + except: + print("can't open templates/%s/" % lang) + for file in files: + fileEN = "templates/en/" + file + fileIT = "templates/" + lang + "/" + file + errlist = [] + if isHtml.search(file): + if not quiet: + print("HTML checking " + fileIT + "... ") + n = check_file(fileIT, fileEN, html=1, quiet=quiet) + if n: + numerrors += n + numfiles += 1 + elif isTxt.search(file): + if not quiet: + print("TXT checking " + fileIT + "... ") + n = check_file(fileIT, fileEN, html=0, quiet=quiet) + if n: + numerrors += n + numfiles += 1 - # Parse the .po file - po_file = os.path.join(lang_dir, 'mailman.po') - if not os.path.isfile(po_file): - usage(1, C_('PO file %(po_file)s does not exist')) + else: + continue - parser = POParser(po_file) - while parser.parse(): - var_checker.checkin(parser.msgid) - var_checker.checkout(parser.msgstr) - tag_checker.checkin(parser.msgid) - tag_checker.checkout(parser.msgstr) + file = "messages/" + lang + "/LC_MESSAGES/mailman.po" + if not quiet: + print("PO checking " + file + "... ") + n = check_po(file, quiet=quiet) + if n: + numerrors += n + numfiles += 1 - # Print results if quiet: - print("%(lang)s: %(var_status)s %(tag_status)s" % { - 'lang': lang, - 'var_status': var_checker.status(), - 'tag_status': tag_checker.status() + print("%(errs)u warnings in %(files)u files" % { + 'errs': numerrors, + 'files': numfiles }) - else: - print(C_('Translation check for %(lang)s:'), file=sys.stderr) - print(C_('Variables: %(var_status)s'), file=sys.stderr) - if var_checker.errs: - print(var_checker.errorsAsString(), file=sys.stderr) - print(C_('Tags: %(tag_status)s'), file=sys.stderr) - if tag_checker.errs: - print(tag_checker.errorsAsString(), file=sys.stderr) - - # Exit with error if there are any issues - if var_checker.errs or tag_checker.errs: - sys.exit(1) - + if __name__ == '__main__': main() diff --git a/bin/unshunt b/bin/unshunt index 36cc75f9..5ceb1197 100644 --- a/bin/unshunt +++ b/bin/unshunt @@ -33,33 +33,46 @@ will result in losing all the messages in that queue. """ import sys -import argparse +import getopt import paths from Mailman import mm_cfg from Mailman.Queue.sbcache import get_switchboard from Mailman.i18n import C_ +PROGRAM = sys.argv[0] + def usage(code, msg=''): if code: fd = sys.stderr else: fd = sys.stdout - print(fd, C_(__doc__, file=fd)) + print(C_(__doc__), file=fd) if msg: print(msg, file=fd) sys.exit(code) + def main(): - parser = argparse.ArgumentParser(description='Move a message from the shunt queue to the original queue.') - parser.add_argument('directory', nargs='?', default=mm_cfg.SHUNTQUEUE_DIR, - help='Directory to dequeue from (default: %(default)s)') - - args = parser.parse_args() + try: + opts, args = getopt.getopt(sys.argv[1:], 'h', ['help']) + except getopt.error as msg: + usage(1, msg) + + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + + if len(args) == 0: + qdir = mm_cfg.SHUNTQUEUE_DIR + elif len(args) == 1: + qdir = args[0] + else: + usage(1) - sb = get_switchboard(args.directory) + sb = get_switchboard(qdir) sb.recover_backup_files() for filebase in sb.files(): try: @@ -70,12 +83,12 @@ def main(): except Exception as e: # If there are any unshunting errors, log them and continue trying # other shunted messages. - print(C_( - 'Cannot unshunt message %(filebase)s, skipping:\n%(e)s'), file=sys.stderr) + print(C_('Cannot unshunt message %(filebase)s, skipping:\n%(e)s'), file=sys.stderr) else: # Unlink the .bak file left by dequeue() sb.finish(filebase) + if __name__ == '__main__': main() diff --git a/bin/update b/bin/update index f5788e5e..4577c845 100755 --- a/bin/update +++ b/bin/update @@ -30,41 +30,34 @@ Options: -h/--help Print this text and exit. - -l/--lowercase - Convert all member email addresses to lowercase. - Use this script to help you update to the latest release of Mailman from some previous version. It knows about versions back to 1.0b4 (?). """ -from __future__ import print_function, absolute_import, division, unicode_literals - import os import sys import time import errno -import argparse +import getopt import shutil import pickle import marshal import paths +import email import email.errors -from Mailman.Message import Message sys.path.append("@VAR_PREFIX@/Mailman") from Mailman import mm_cfg from Mailman import Utils from Mailman import MailList +from Mailman import Message from Mailman import Pending -from Mailman.LockFile import TimeOutError, AlreadyLockedError +from Mailman.LockFile import TimeOutError from Mailman.i18n import C_ from Mailman.Queue.Switchboard import Switchboard from Mailman.OldStyleMemberships import OldStyleMemberships from Mailman.MemberAdaptor import BYBOUNCE, ENABLED -from Mailman.Bouncer import _BounceInfo -from Mailman.MemberAdaptor import UNKNOWN -from Mailman.Logging.Syslog import syslog FRESH = 0 NOTFRESH = -1 @@ -73,17 +66,7 @@ LMVFILE = os.path.join(mm_cfg.DATA_DIR, 'last_mailman_version') PROGRAM = sys.argv[0] -def parse_args(): - parser = argparse.ArgumentParser(description='Perform all necessary upgrades.') - parser.add_argument('-f', '--force', action='store_true', - help='Force running the upgrade procedures') - parser.add_argument('-l', '--lowercase', action='store_true', - help='Convert all member email addresses to lowercase') - parser.add_argument('-v', '--verbose', action='store_true', - help='Enable verbose output') - return parser.parse_args() - - + def calcversions(): # Returns a tuple of (lastversion, thisversion). If the last version # could not be determined, lastversion will be FRESH or NOTFRESH, @@ -98,9 +81,6 @@ def calcversions(): fp = open(LMVFILE, 'rb') data = fp.read() fp.close() - # Ensure data is a string - if isinstance(data, bytes): - data = data.decode('utf-8', 'replace') lastversion = int(data, 16) except (IOError, ValueError): pass @@ -116,19 +96,17 @@ def calcversions(): return (lastversion, thisversion) + def makeabs(relpath): return os.path.join(mm_cfg.PREFIX, relpath) def make_varabs(relpath): return os.path.join(mm_cfg.VAR_PREFIX, relpath) - + def move_language_templates(mlist): listname = mlist.internal_name() - # Ensure listname is a string - if isinstance(listname, bytes): - listname = listname.decode('utf-8', 'replace') - print('Fixing language templates: %s' % listname) + print(C_('Fixing language templates: %(listname)s')) # Mailman 2.1 has a new cascading search for its templates, defined and # described in Utils.py:maketext(). Putting templates in the top level # templates/ subdir or the lists/ subdir is deprecated and no @@ -210,13 +188,9 @@ def move_language_templates(mlist): gtemplate + '.prev')) + def dolist(listname): errors = 0 - # Ensure listname is a string and convert to lowercase - if isinstance(listname, bytes): - listname = listname.decode('utf-8', 'replace') - listname = listname.lower() - print('Updating mailing list: %s' % listname) mlist = MailList.MailList(listname, lock=0) try: mlist.Lock(0.5) @@ -225,11 +199,6 @@ def dolist(listname): '%(listname)s'), file=sys.stderr) return 1 - # Convert member addresses to lowercase if requested - if args.lowercase: - print('Converting member addresses to lowercase: %s' % listname) - mlist.convert_member_addresses_to_lowercase() - # Sanity check the invariant that every BYBOUNCE disabled member must have # bounce information. Some earlier betas broke this. BAW: we're # submerging below the MemberAdaptor interface, so skip this if we're not @@ -296,7 +265,7 @@ to You can integrate that into the archives if you want by using the 'arch' script. -""") % (listname, o_pri_mbox_file, o_pub_mbox_file, +""") % (mlist._internal_name, o_pri_mbox_file, o_pub_mbox_file, o_pub_mbox_file)) os.rename(o_pub_mbox_file, "%s.preb6" % (o_pub_mbox_file)) else: @@ -310,7 +279,7 @@ archive file (%s) as the active one, and renaming You can integrate that into the archives if you want by using the 'arch' script. -""") % (listname, o_pub_mbox_file, o_pri_mbox_file, +""") % (mlist._internal_name, o_pub_mbox_file, o_pri_mbox_file, o_pri_mbox_file)) os.rename(o_pri_mbox_file, "%s.preb6" % (o_pri_mbox_file)) # @@ -408,6 +377,7 @@ script. return 0 + def archive_path_fixer(unused_arg, dir, files): # Passed to os.path.walk to fix the perms on old html archives. for f in files: @@ -439,7 +409,7 @@ def remove_old_sources(module): except os.error as rest: print(C_("couldn't remove old file %(pyc)s -- %(rest)s")) - + def update_qfiles(): print('updating old qfiles') prefix = str(time.time()) + '+' @@ -487,6 +457,7 @@ def update_qfiles(): print(C_('Warning! Not a directory: %(dirpath)s')) + # Implementations taken from the pre-2.1.5 Switchboard def ext_read(filename): fp = open(filename, 'rb') @@ -494,27 +465,6 @@ def ext_read(filename): # Update from version 2 files if d.get('version', 0) == 2: del d['filebase'] - - # Convert any bytes in the loaded data to strings - for key, value in list(d.items()): - if isinstance(key, bytes): - del d[key] - key = key.decode('utf-8', 'replace') - if isinstance(value, bytes): - value = value.decode('utf-8', 'replace') - elif isinstance(value, list): - value = [v.decode('utf-8', 'replace') if isinstance(v, bytes) else v for v in value] - elif isinstance(value, dict): - new_dict = {} - for k, v in value.items(): - if isinstance(k, bytes): - k = k.decode('utf-8', 'replace') - if isinstance(v, bytes): - v = v.decode('utf-8', 'replace') - new_dict[k] = v - value = new_dict - d[key] = value - # Do the reverse conversion (repr -> float) for attr in ['received_time']: try: @@ -535,6 +485,12 @@ def dequeue(filebase): msgfile = os.path.join(filebase + '.msg') pckfile = os.path.join(filebase + '.pck') dbfile = os.path.join(filebase + '.db') + # Now we are going to read the message and metadata for the given + # filebase. We want to read things in this order: first, the metadata + # file to find out whether the message is stored as a pickle or as + # plain text. Second, the actual message file. However, we want to + # first unlink the message file and then the .db file, because the + # qrunner only cues off of the .db file msg = None try: data = ext_read(dbfile) @@ -542,22 +498,12 @@ def dequeue(filebase): except EnvironmentError as e: if e.errno != errno.ENOENT: raise data = {} - - # Convert any bytes in the data dict to strings - for key, value in list(data.items()): - if isinstance(key, bytes): - del data[key] - key = key.decode('utf-8', 'replace') - if isinstance(value, bytes): - value = value.decode('utf-8', 'replace') - data[key] = value - - # Between 2.1b4 and 2.1b5, the `rejection-notice` key in the metadata - # was renamed to `rejection_notice` + # Between 2.1b4 and 2.1b5, the `rejection-notice' key in the metadata + # was renamed to `rejection_notice', since dashes in the keys are not + # supported in METAFMT_ASCII. if data.get('rejection-notice', None) is not None: data['rejection_notice'] = data['rejection-notice'] del data['rejection-notice'] - msgfp = None try: try: @@ -567,15 +513,6 @@ def dequeue(filebase): # There was no .db file. Is this a post 2.1.5 .pck? try: data = pickle.load(msgfp, fix_imports=True, encoding='latin1') - # Convert any bytes in the loaded data to strings - if isinstance(data, dict): - for key, value in list(data.items()): - if isinstance(key, bytes): - del data[key] - key = key.decode('utf-8', 'replace') - if isinstance(value, bytes): - value = value.decode('utf-8', 'replace') - data[key] = value except EOFError: pass os.unlink(pckfile) @@ -584,15 +521,20 @@ def dequeue(filebase): msgfp = None try: msgfp = open(msgfile, 'rb') - msg = Message_from_file(msgfp) + msg = email.message_from_file(msgfp, Message.Message) os.unlink(msgfile) except EnvironmentError as e: if e.errno != errno.ENOENT: raise except (email.errors.MessageParseError, ValueError) as e: + # This message was unparsable, most likely because its + # MIME encapsulation was broken. For now, there's not + # much we can do about it. print(C_('message is unparsable: %(filebase)s')) msgfp.close() msgfp = None if mm_cfg.QRUNNER_SAVE_BAD_MESSAGES: + # Cheapo way to ensure the directory exists w/ the + # proper permissions. sb = Switchboard(mm_cfg.BADQUEUE_DIR) os.rename(msgfile, os.path.join( mm_cfg.BADQUEUE_DIR, filebase + '.txt')) @@ -600,6 +542,7 @@ def dequeue(filebase): os.unlink(msgfile) msg = data = None except EOFError: + # For some reason the pckfile was empty. Just delete it. print(C_('Warning! Deleting empty .pck file: %(pckfile)s')) os.unlink(pckfile) finally: @@ -608,6 +551,7 @@ def dequeue(filebase): return msg, data + def update_pending(): file20 = os.path.join(mm_cfg.DATA_DIR, 'pending_subscriptions.db') file214 = os.path.join(mm_cfg.DATA_DIR, 'pending.pck') @@ -622,21 +566,6 @@ def update_pending(): db = marshal.load(fp) # Convert to the pre-Mailman 2.1.5 format db = Pending._update(db) - # Convert any bytes to strings - if isinstance(db, dict): - new_db = {} - for key, value in db.items(): - if isinstance(key, bytes): - key = key.decode('utf-8', 'replace') - if isinstance(value, bytes): - value = value.decode('utf-8', 'replace') - elif isinstance(value, (list, tuple)): - value = list(value) # Convert tuple to list for modification - for i, v in enumerate(value): - if isinstance(v, bytes): - value[i] = v.decode('utf-8', 'replace') - new_db[key] = value - db = new_db if db is None: # Try to load the Mailman 2.1.x where x < 5, file try: @@ -646,21 +575,6 @@ def update_pending(): else: print('Updating Mailman 2.1.4 pending.pck database') db = pickle.load(fp, fix_imports=True, encoding='latin1') - # Convert any bytes to strings - if isinstance(db, dict): - new_db = {} - for key, value in db.items(): - if isinstance(key, bytes): - key = key.decode('utf-8', 'replace') - if isinstance(value, bytes): - value = value.decode('utf-8', 'replace') - elif isinstance(value, (list, tuple)): - value = list(value) # Convert tuple to list for modification - for i, v in enumerate(value): - if isinstance(v, bytes): - value[i] = v.decode('utf-8', 'replace') - new_db[key] = value - db = new_db if db is None: print('Nothing to do.') return @@ -748,455 +662,162 @@ def update_pending(): if e.errno != errno.ENOENT: raise -def domsort(addr): - # Sort email addresses by domain name - return addr.split('@')[-1] - - -def init_digest_vars(mlist): - """Initialize missing digest-related variables with default values.""" - # List of digest variables and their default values - digest_vars = { - 'digestable': True, - 'nondigestable': mm_cfg.DEFAULT_NONDIGESTABLE, - 'digest_volume': 1, - 'digest_issue': 1, - 'digest_last_sent_at': 0, - 'digest_next_due_at': 0, - 'digest_volume_frequency': mm_cfg.DEFAULT_DIGEST_VOLUME_FREQUENCY, - 'digest_members': {}, - 'members': {}, # Regular members dictionary - 'user_options': {}, # User preferences - 'language': {}, # User language preferences - 'usernames': {}, # Username mappings - 'passwords': {}, # Password storage - 'bounce_info': {}, # Bounce information - 'delivery_status': {}, # Delivery status information - 'new_member_options': mm_cfg.DEFAULT_NEW_MEMBER_OPTIONS, - 'respond_to_post_requests': mm_cfg.DEFAULT_RESPOND_TO_POST_REQUESTS, - 'advertised': mm_cfg.DEFAULT_LIST_ADVERTISED, - 'max_num_recipients': mm_cfg.DEFAULT_MAX_NUM_RECIPIENTS, - 'max_message_size': mm_cfg.DEFAULT_MAX_MESSAGE_SIZE, - 'host_name': mm_cfg.DEFAULT_HOST_NAME or mm_cfg.DEFAULT_EMAIL_HOST, - 'web_page_url': mm_cfg.DEFAULT_URL_PATTERN % mm_cfg.DEFAULT_URL_HOST, - 'owner': [], # List owners - 'moderator': [], # List moderators - 'reply_goes_to_list': mm_cfg.DEFAULT_REPLY_GOES_TO_LIST, - 'reply_to_address': '', - 'first_strip_reply_to': mm_cfg.DEFAULT_FIRST_STRIP_REPLY_TO, - 'admin_immed_notify': mm_cfg.DEFAULT_ADMIN_IMMED_NOTIFY, - 'admin_notify_mchanges': mm_cfg.DEFAULT_ADMIN_NOTIFY_MCHANGES, - 'require_explicit_destination': mm_cfg.DEFAULT_REQUIRE_EXPLICIT_DESTINATION, - 'acceptable_aliases': mm_cfg.DEFAULT_ACCEPTABLE_ALIASES, - 'umbrella_list': mm_cfg.DEFAULT_UMBRELLA_LIST, - 'umbrella_member_suffix': mm_cfg.DEFAULT_UMBRELLA_MEMBER_ADMIN_SUFFIX, - 'regular_exclude_lists': mm_cfg.DEFAULT_REGULAR_EXCLUDE_LISTS, - 'regular_exclude_ignore': mm_cfg.DEFAULT_REGULAR_EXCLUDE_IGNORE, - 'regular_include_lists': mm_cfg.DEFAULT_REGULAR_INCLUDE_LISTS, - 'send_reminders': mm_cfg.DEFAULT_SEND_REMINDERS, - 'send_welcome_msg': mm_cfg.DEFAULT_SEND_WELCOME_MSG, - 'send_goodbye_msg': mm_cfg.DEFAULT_SEND_GOODBYE_MSG, - 'bounce_matching_headers': mm_cfg.DEFAULT_BOUNCE_MATCHING_HEADERS, - 'header_filter_rules': [], - 'from_is_list': mm_cfg.DEFAULT_FROM_IS_LIST, - 'anonymous_list': mm_cfg.DEFAULT_ANONYMOUS_LIST, - 'real_name': mlist.internal_name()[0].upper() + mlist.internal_name()[1:], - 'description': '', - 'info': '', - 'welcome_msg': '', - 'goodbye_msg': '', - 'subscribe_policy': mm_cfg.DEFAULT_SUBSCRIBE_POLICY, - 'subscribe_auto_approval': mm_cfg.DEFAULT_SUBSCRIBE_AUTO_APPROVAL, - 'unsubscribe_policy': mm_cfg.DEFAULT_UNSUBSCRIBE_POLICY, - 'private_roster': mm_cfg.DEFAULT_PRIVATE_ROSTER, - 'obscure_addresses': mm_cfg.DEFAULT_OBSCURE_ADDRESSES, - 'admin_member_chunksize': mm_cfg.DEFAULT_ADMIN_MEMBER_CHUNKSIZE, - 'administrivia': mm_cfg.DEFAULT_ADMINISTRIVIA, - 'drop_cc': mm_cfg.DEFAULT_DROP_CC, - 'preferred_language': mm_cfg.DEFAULT_SERVER_LANGUAGE, - 'available_languages': [], - 'include_rfc2369_headers': 1, - 'include_list_post_header': 1, - 'include_sender_header': 1, - 'filter_mime_types': mm_cfg.DEFAULT_FILTER_MIME_TYPES, - 'pass_mime_types': mm_cfg.DEFAULT_PASS_MIME_TYPES, - 'filter_filename_extensions': mm_cfg.DEFAULT_FILTER_FILENAME_EXTENSIONS, - 'pass_filename_extensions': mm_cfg.DEFAULT_PASS_FILENAME_EXTENSIONS, - 'filter_content': mm_cfg.DEFAULT_FILTER_CONTENT, - 'collapse_alternatives': mm_cfg.DEFAULT_COLLAPSE_ALTERNATIVES, - 'convert_html_to_plaintext': mm_cfg.DEFAULT_CONVERT_HTML_TO_PLAINTEXT, - 'filter_action': mm_cfg.DEFAULT_FILTER_ACTION, - 'personalize': 0, - 'default_member_moderation': mm_cfg.DEFAULT_DEFAULT_MEMBER_MODERATION, - 'emergency': 0, - 'member_verbosity_threshold': mm_cfg.DEFAULT_MEMBER_VERBOSITY_THRESHOLD, - 'member_verbosity_interval': mm_cfg.DEFAULT_MEMBER_VERBOSITY_INTERVAL, - 'member_moderation_action': 0, - 'member_moderation_notice': '', - 'dmarc_moderation_action': mm_cfg.DEFAULT_DMARC_MODERATION_ACTION, - 'dmarc_quarantine_moderation_action': mm_cfg.DEFAULT_DMARC_QUARANTINE_MODERATION_ACTION, - 'dmarc_none_moderation_action': mm_cfg.DEFAULT_DMARC_NONE_MODERATION_ACTION, - 'dmarc_moderation_notice': '', - 'dmarc_moderation_addresses': [], - 'dmarc_wrapped_message_text': mm_cfg.DEFAULT_DMARC_WRAPPED_MESSAGE_TEXT, - 'equivalent_domains': mm_cfg.DEFAULT_EQUIVALENT_DOMAINS, - 'accept_these_nonmembers': [], - 'hold_these_nonmembers': [], - 'reject_these_nonmembers': [], - 'discard_these_nonmembers': [], - 'forward_auto_discards': mm_cfg.DEFAULT_FORWARD_AUTO_DISCARDS - } - - # Initialize any missing variables - for var, default in digest_vars.items(): - if not hasattr(mlist, var): + +def main(): + errors = 0 + # get rid of old stuff + print('getting rid of old source files') + for mod in ('Mailman/Archiver.py', 'Mailman/HyperArch.py', + 'Mailman/HyperDatabase.py', 'Mailman/pipermail.py', + 'Mailman/smtplib.py', 'Mailman/Cookie.py', + 'bin/update_to_10b6', 'scripts/mailcmd', + 'scripts/mailowner', 'mail/wrapper', 'Mailman/pythonlib', + 'cgi-bin/archives', 'Mailman/MailCommandHandler'): + remove_old_sources(mod) + listnames = Utils.list_names() + if not listnames: + print('no lists == nothing to do, exiting') + return + # + # for people with web archiving, make sure the directories + # in the archiving are set with proper perms for b6. + # + if os.path.isdir("%s/public_html/archives" % mm_cfg.PREFIX): + print(C_("""\ +fixing all the perms on your old html archives to work with b6 +If your archives are big, this could take a minute or two...""")) + os.path.walk("%s/public_html/archives" % mm_cfg.PREFIX, + archive_path_fixer, "") + print('done') + for listname in listnames: + print(C_('Updating mailing list: %(listname)s')) + errors = errors + dolist(listname) + print + print('Updating Usenet watermarks') + wmfile = os.path.join(mm_cfg.DATA_DIR, 'gate_watermarks') + try: + fp = open(wmfile, 'rb') + except IOError: + print('- nothing to update here') + else: + d = marshal.load(fp) + fp.close() + for listname in d.keys(): + if listname not in listnames: + # this list no longer exists + continue + mlist = MailList.MailList(listname, lock=0) try: - # If default is a call to mm_cfg, try to get the attribute - if isinstance(default, str) and 'mm_cfg.' in default: - try: - default = eval(default) - except AttributeError: - print(C_('Warning: mm_cfg attribute %(attr)s not found, using empty value') % { - 'attr': default.split('mm_cfg.')[1] - }) - default = None - - print(C_('Initializing missing variable %(var)s for list %(listname)s') % { - 'var': var, - 'listname': mlist.internal_name() - }) - setattr(mlist, var, default) - except Exception as e: - print(C_('Warning: Could not initialize %(var)s: %(error)s') % { - 'var': var, - 'error': str(e) - }) + mlist.Lock(0.5) + except TimeOutError: + print(C_( + 'WARNING: could not acquire lock for list: %(listname)s', file=sys.stderr)) + errors = errors + 1 + else: + # Pre 1.0b7 stored 0 in the gate_watermarks file to indicate + # that no gating had been done yet. Without coercing this to + # None, the list could now suddenly get flooded. + mlist.usenet_watermark = d[listname] or None + mlist.Save() + mlist.Unlock() + os.unlink(wmfile) + print('- usenet watermarks updated and gate_watermarks removed') + # In Mailman 2.1, the pending database format and file name changed, but + # in Mailman 2.1.5 it changed again. This should update all existing + # files to the 2.1.5 format. + update_pending() + # In Mailman 2.1, the qfiles directory has a different structure and a + # different content. Also, in Mailman 2.1.5 we collapsed the message + # files from separate .msg (pickled Message objects) and .db (marshalled + # dictionaries) to a shared .pck file containing two pickles. + update_qfiles() + # This warning was necessary for the upgrade from 1.0b9 to 1.0b10. + # There's no good way of figuring this out for releases prior to 2.0beta2 + # :( + if lastversion == NOTFRESH: + print(""" -def upgrade(mlist): - """Upgrade the list to the current version.""" - try: - # Print pickle protocol version when loading - try: - config_path = os.path.join(mlist._full_path, 'config.pck') - if os.path.exists(config_path): - with open(config_path, 'rb') as fp: - # Try loading with UTF-8 first, then fall back to latin1 - try: - fp.seek(0) - data = pickle.load(fp, fix_imports=True, encoding='utf-8') - if hasattr(data, '_protocol'): - protocol = data._protocol - print(C_('List %(listname)s config.pck uses pickle protocol %(protocol)d') % { - 'listname': mlist.internal_name(), - 'protocol': protocol - }) - else: - print(C_('List %(listname)s config.pck protocol version not stored in data') % { - 'listname': mlist.internal_name() - }) - except UnicodeDecodeError: - fp.seek(0) - data = pickle.load(fp, fix_imports=True, encoding='latin1') - if hasattr(data, '_protocol'): - protocol = data._protocol - print(C_('List %(listname)s config.pck uses pickle protocol %(protocol)d') % { - 'listname': mlist.internal_name(), - 'protocol': protocol - }) - else: - print(C_('List %(listname)s config.pck protocol version not stored in data') % { - 'listname': mlist.internal_name() - }) - except Exception as e: - print(C_('Warning: Could not determine pickle protocol version: %(error)s') % { - 'error': str(e) - }) +NOTE NOTE NOTE NOTE NOTE - # Initialize any missing digest variables - init_digest_vars(mlist) - - # Convert all email addresses to lowercase - for addr in list(mlist.members): - if addr != addr.lower(): - mlist.members[addr.lower()] = mlist.members[addr] - del mlist.members[addr] - - for addr in list(mlist.digest_members): - if addr != addr.lower(): - mlist.digest_members[addr.lower()] = mlist.digest_members[addr] - del mlist.digest_members[addr] - - # Handle owner list differently since it's a list, not a dict - if hasattr(mlist, 'owner') and isinstance(mlist.owner, list): - new_owners = [] - for addr in mlist.owner: - if isinstance(addr, str) and addr != addr.lower(): - new_owners.append(addr.lower()) - else: - new_owners.append(addr) - mlist.owner = new_owners - else: - for addr in list(mlist.owner): - if addr != addr.lower(): - mlist.owner[addr.lower()] = mlist.owner[addr] - del mlist.owner[addr] - - # Handle moderator list differently since it's a list, not a dict - if hasattr(mlist, 'moderator') and isinstance(mlist.moderator, list): - new_moderators = [] - for addr in mlist.moderator: - if isinstance(addr, str) and addr != addr.lower(): - new_moderators.append(addr.lower()) - else: - new_moderators.append(addr) - mlist.moderator = new_moderators - else: - for addr in list(mlist.moderator): - if addr != addr.lower(): - mlist.moderator[addr.lower()] = mlist.moderator[addr] - del mlist.moderator[addr] - - for addr in list(mlist.bounce_info): - if addr != addr.lower(): - mlist.bounce_info[addr.lower()] = mlist.bounce_info[addr] - del mlist.bounce_info[addr] - - for addr in list(mlist.delivery_status): - if addr != addr.lower(): - mlist.delivery_status[addr.lower()] = mlist.delivery_status[addr] - del mlist.delivery_status[addr] - - for addr in list(mlist.user_options): - if addr != addr.lower(): - mlist.user_options[addr.lower()] = mlist.user_options[addr] - del mlist.user_options[addr] - - # Don't convert passwords to lowercase - # for addr in list(mlist.passwords): - # if addr != addr.lower(): - # mlist.passwords[addr.lower()] = mlist.passwords[addr] - # del mlist.passwords[addr] - - for addr in list(mlist.language): - if addr != addr.lower(): - mlist.language[addr.lower()] = mlist.language[addr] - del mlist.language[addr] - - for addr in list(mlist.usernames): - if addr != addr.lower(): - mlist.usernames[addr.lower()] = mlist.usernames[addr] - del mlist.usernames[addr] + You are upgrading an existing Mailman installation, but I can't tell what + version you were previously running. - # Ensure the list directory exists - list_dir = os.path.dirname(mlist._full_path) - if not os.path.exists(list_dir): - print(C_('Creating list directory: %(dir)s') % {'dir': list_dir}) - os.makedirs(list_dir, mode=0o2775) - # Set group ownership if possible - try: - import grp - mailman_gid = grp.getgrnam('mailman').gr_gid - os.chown(list_dir, -1, mailman_gid) - except (ImportError, KeyError): - pass + If you are upgrading from Mailman 1.0b9 or earlier you will need to + manually update your mailing lists. For each mailing list you need to + copy the file templates/options.html lists//options.html. - # Save the list configuration - try: - print(C_('Saving list %(listname)s with pickle protocol 4') % { - 'listname': mlist.internal_name() - }) - mlist.Save() - except (IOError, OSError) as e: - print(C_('Error saving list configuration: %(error)s') % {'error': str(e)}) - # Try to save to a backup location - backup_path = os.path.join(list_dir, 'config.pck.bak') - try: - with open(backup_path, 'wb') as fp: - pickle.dump(mlist.__dict__, fp, protocol=4, fix_imports=True) - print(C_('Saved backup configuration to %(path)s') % {'path': backup_path}) - except Exception as e: - print(C_('Failed to save backup configuration: %(error)s') % {'error': str(e)}) - raise - except Exception as e: - print(C_('Error during upgrade: %(error)s') % {'error': str(e)}) - raise + However, if you have edited this file via the Web interface, you will have + to merge your changes into this file, otherwise you will lose your + changes. +NOTE NOTE NOTE NOTE NOTE -def main(): - try: - args = parse_args() - except SystemExit as e: - if e.code == 2: # Invalid arguments - usage(1) - raise +""") + return errors - # Calculate the versions - lastversion, thisversion = calcversions() - # If this is a fresh install, we don't need to do anything - if lastversion == FRESH: - print(C_('This appears to be a fresh installation.')) - print(C_('No upgrade is necessary.')) - # Early check: try to load all lists and print a summary - list_names = Utils.list_names() - ok = 0 - fail = 0 - for listname in list_names: - try: - if isinstance(listname, bytes): - listname = listname.decode('utf-8', 'replace') - listname = listname.lower() - mlist = MailList.MailList(listname, lock=0) - ok += 1 - except Exception as e: - fail += 1 - print(' [WARN] Could not load list "%s": %s' % (listname, str(e))) - if fail == 0: - print('All %d lists loaded successfully, no upgrade necessary.' % ok) - else: - print('%d lists loaded successfully, %d lists had errors. No upgrade necessary.' % (ok, fail)) - sys.exit(0) + +def usage(code, msg=''): + if code: + fd = sys.stderr + else: + fd = sys.stdout + print(C_(__doc__) % globals(), file=fd) + if msg: + print(msg, file=sys.stderr) + sys.exit(code) - # If this is not a fresh install, but we can't determine the last version, - # we need to force the upgrade - if lastversion == NOTFRESH: - if not args.force: - print(C_("""\ -This appears to be an existing installation, but I cannot determine the -version number. You must use the -f flag to force the upgrade.""")) - sys.exit(1) - lastversion = 0x2000000 # 2.0.0 - # If the versions match, we don't need to do anything - if lastversion == thisversion and not args.force: - print(C_('No upgrade is necessary.')) - # Early check: try to load all lists and print a summary - list_names = Utils.list_names() - ok = 0 - fail = 0 - for listname in list_names: - try: - if isinstance(listname, bytes): - listname = listname.decode('utf-8', 'replace') - listname = listname.lower() - mlist = MailList.MailList(listname, lock=0) - ok += 1 - except Exception as e: - fail += 1 - print(' [WARN] Could not load list "%s": %s' % (listname, str(e))) - if fail == 0: - print('All %d lists loaded successfully, no upgrade necessary.' % ok) - else: - print('%d lists loaded successfully, %d lists had errors. No upgrade necessary.' % (ok, fail)) + +if __name__ == '__main__': + try: + opts, args = getopt.getopt(sys.argv[1:], 'hf', + ['help', 'force']) + except getopt.error as msg: + usage(1, msg) + + if args: + usage(1, 'Unexpected arguments: %s' % args) + + force = 0 + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-f', '--force'): + force = 1 + + # calculate the versions + lastversion, thisversion = calcversions() + hexlversion = hex(lastversion) + hextversion = hex(thisversion) + if lastversion == thisversion and not force: + # nothing to do + print ('No updates are necessary.') sys.exit(0) - - # If this is a downgrade, we need to force it - if lastversion > thisversion and not args.force: + if lastversion > thisversion and not force: print(C_("""\ -This appears to be a downgrade. You must use the -f flag to force the -downgrade.""")) +Downgrade detected, from version %(hexlversion)s to version %(hextversion)s +This is probably not safe. +Exiting.""")) sys.exit(1) - - # Process all mailing lists - list_names = Utils.list_names() - print("Found %d lists to process" % len(list_names)) - print("List names type: %s" % type(list_names)) - - for listname in list_names: - mlist = None - try: - print("\nProcessing list: %s (type: %s)" % (listname, type(listname))) - # Ensure listname is a string, not bytes - if isinstance(listname, bytes): - print("Converting bytes listname to string") - listname = listname.decode('utf-8', 'replace') - listname = listname.lower() - print("Listname after conversion: %s (type: %s)" % (listname, type(listname))) - - print("Creating MailList object...") - # Create the MailList object without lock first - mlist = MailList.MailList(listname, lock=0) - print("MailList object created successfully") - - print("Attempting to acquire lock...") - try: - # First try to acquire the lock normally - try: - mlist.Lock(0.5) - except AlreadyLockedError: - # If we get AlreadyLockedError, try to force unlock if the lock is stale - print("Lock appears to be set, checking if it's stale...") - try: - # Try to force unlock if the lock is stale - if hasattr(mlist, '__lock') and hasattr(mlist.__lock, 'force_unlock'): - mlist.__lock.force_unlock() - print("Stale lock removed, retrying lock acquisition...") - mlist.Lock(0.5) - else: - print("WARNING: Lock object does not have force_unlock capability") - continue - except Exception as e: - print(C_('WARNING: Could not remove stale lock: %(error)s') % { - 'error': str(e) - }, file=sys.stderr) - continue - - print("Lock acquired, starting upgrade...") - - # Do the upgrade - upgrade(mlist) - print("Upgrade completed, saving...") - mlist.Save() - print("Save completed") - - except TimeOutError: - print(C_('WARNING: could not acquire lock for list: %(listname)s') % { - 'listname': listname - }, file=sys.stderr) - continue - finally: - if mlist is not None: - try: - print("Unlocking list...") - mlist.Unlock() - except Exception as e: - print(C_('WARNING: Error unlocking list %(listname)s: %(error)s') % { - 'listname': listname, - 'error': str(e) - }, file=sys.stderr) - - except Exception as e: - print("\nDetailed error information:") - print("List name: %s" % listname) - print("List name type: %s" % type(listname)) - print("Error type: %s" % type(e)) - print("Error message: %s" % str(e)) - import traceback - print("Traceback:") - traceback.print_exc() - print(C_('Error processing list %(listname)s: %(error)s') % { - 'listname': listname, - 'error': str(e) - }, file=sys.stderr) - - # Save the new version - try: + print(C_('Upgrading from version %(hexlversion)s to %(hextversion)s')) + errors = main() + if not errors: + # Record the version we just upgraded to fp = open(LMVFILE, 'w') - print('%x' % thisversion, file=fp) + fp.write(hex(mm_cfg.HEX_VERSION) + '\n') fp.close() - except IOError as e: - print(C_('Could not save version number: %(error)s') % {'error': str(e)}, file=sys.stderr) - sys.exit(1) - - print(C_('Upgrade complete.')) - + else: + lockdir = mm_cfg.LOCK_DIR + print('''\ -def usage(exitcode=0): - """Print usage information and exit with the given exit code.""" - print(__doc__ % {'PROGRAM': PROGRAM}) - sys.exit(exitcode) +ERROR: +The locks for some lists could not be acquired. This means that either +Mailman was still active when you upgraded, or there were stale locks in the +%(lockdir)s directory. -if __name__ == '__main__': - main() +You must put Mailman into a quiescent state and remove all stale locks, then +re-run "make update" manually. See the INSTALL and UPGRADE files for details. +''') diff --git a/bin/withlist b/bin/withlist index 20215fbe..fcc19bfe 100644 --- a/bin/withlist +++ b/bin/withlist @@ -122,7 +122,7 @@ and run this from the command line: import os import sys import code -import argparse +import getopt import paths from Mailman import Errors @@ -141,25 +141,7 @@ LOCK = False sys.path.append(os.path.dirname(sys.argv[0])) -def parse_args(): - parser = argparse.ArgumentParser(description='General framework for interacting with a mailing list object.') - parser.add_argument('-l', '--lock', action='store_true', - help='Lock the list when opening') - parser.add_argument('-r', '--run', - help='Run the specified module.callable') - parser.add_argument('-q', '--quiet', action='store_true', - help='Suppress verbose output') - parser.add_argument('-i', '--interactive', action='store_true', - help='Leave at interactive prompt after processing') - parser.add_argument('-a', '--all', action='store_true', - help='Process all lists') - parser.add_argument('listname', nargs='?', - help='Name of the list to process') - parser.add_argument('args', nargs='*', - help='Additional arguments to pass to the callable') - return parser.parse_args() - - + def usage(code, msg=''): if code: fd = sys.stderr @@ -167,7 +149,7 @@ def usage(code, msg=''): fd = sys.stdout print(C_(__doc__), file=fd) if msg: - print(msg, file=sys.stderr) + print(msg, file=fd) sys.exit(code) @@ -191,6 +173,7 @@ def atexit(): del m + def do_list(listname, args, func): global m # first try to open mailing list @@ -213,66 +196,78 @@ def do_list(listname, args, func): return None + def main(): - global VERBOSE, LOCK + global VERBOSE + global LOCK + global r try: - args = parse_args() - except SystemExit: - usage(1) + opts, args = getopt.getopt( + sys.argv[1:], 'hlr:qia', + ['help', 'lock', 'run=', 'quiet', 'interactive', 'all']) + except getopt.error as msg: + usage(1, msg) + + run = None + interact = None + all = False + dolist = True + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-l', '--lock'): + LOCK = True + elif opt in ('-r', '--run'): + run = arg + elif opt in ('-q', '--quiet'): + VERBOSE = False + elif opt in ('-i', '--interactive'): + interact = True + elif opt in ('-a', '--all'): + all = True + + if len(args) < 1 and not all: + warning = C_('No list name supplied.') + if interact: + # Let them keep going + print(warning) + dolist = False + else: + usage(1, warning) - VERBOSE = not args.quiet - LOCK = args.lock + if all and not run: + usage(1, C_('--all requires --run')) - # The default for interact is True unless -r was given - interact = args.interactive + # The default for interact is 1 unless -r was given if interact is None: - interact = args.run is None + if run is None: + interact = True + else: + interact = False - # Import the callable if one was specified + # try to import the module for the callable func = None - if args.run: + if run: + i = run.rfind('.') + if i < 0: + module = run + callable = run + else: + module = run[:i] + callable = run[i+1:] if VERBOSE: print(C_('Importing %(module)s...'), file=sys.stderr) - try: - if '.' in args.run: - module, callable = args.run.rsplit('.', 1) - mod = __import__(module, globals(), locals(), [callable]) - func = getattr(mod, callable) - else: - mod = __import__(args.run, globals(), locals(), []) - func = getattr(mod, args.run) - except (ImportError, AttributeError) as e: - print(C_('Error importing %(module)s: %(error)s'), - file=sys.stderr) - sys.exit(1) - - # Handle the --all option - if args.all: - if args.listname: - usage(1, C_('Cannot specify listname with --all')) - if not args.run: - usage(1, C_('--all requires --run')) - results = [] - for listname in Utils.list_names(): - if VERBOSE: - print(C_('Processing list: %(listname)s'), file=sys.stderr) - result = do_list(listname, args.args, func) - if result is not None: - results.append(result) - r = results - else: - if not args.listname: - warning = C_('No list name supplied.') - if interact: - # Let them keep going - print(warning) - dolist = False - else: - usage(1, warning) - else: - dolist = True - listname = args.listname.lower().strip() - r = do_list(listname, args.args, func) + __import__(module) + mod = sys.modules[module] + if VERBOSE: + print(C_('Running %(module)s.%(callable)s()...'), file=sys.stderr) + func = getattr(mod, callable) + + if all: + r = [do_list(listname, args, func) for listname in Utils.list_names()] + elif dolist: + listname = args.pop(0).lower().strip() + r = do_list(listname, args, func) # Now go to interactive mode, perhaps if interact: @@ -290,10 +285,8 @@ def main(): else: ban = None code.InteractiveConsole(namespace).interact(ban) - else: - # We're done - sys.exit(0) + sys.exitfunc = atexit main() diff --git a/configure b/configure index 70e83a4c..e82fb66f 100755 --- a/configure +++ b/configure @@ -1,11 +1,10 @@ #! /bin/sh -# From configure.ac Revision: 8122 . +# From configure.in Revision: 8122 . # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.72. +# Generated by GNU Autoconf 2.69. # # -# Copyright (C) 1992-1996, 1998-2017, 2020-2023 Free Software Foundation, -# Inc. +# Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. # # # This configure script is free software; the Free Software Foundation @@ -16,65 +15,63 @@ # Be more Bourne compatible DUALCASE=1; export DUALCASE # for MKS sh -if test ${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 -then : +if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which # is contrary to our usage. Disable this feature. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST -else case e in #( - e) case `(set -o) 2>/dev/null` in #( +else + case `(set -o) 2>/dev/null` in #( *posix*) : set -o posix ;; #( *) : ;; -esac ;; esac fi - -# Reset variables that may have inherited troublesome values from -# the environment. - -# IFS needs to be set, to space, tab, and newline, in precisely that order. -# (If _AS_PATH_WALK were called with IFS unset, it would have the -# side effect of setting IFS to empty, thus disabling word splitting.) -# Quoting is to prevent editors from complaining about space-tab. as_nl=' ' export as_nl -IFS=" "" $as_nl" - -PS1='$ ' -PS2='> ' -PS4='+ ' - -# Ensure predictable behavior from utilities with locale-dependent output. -LC_ALL=C -export LC_ALL -LANGUAGE=C -export LANGUAGE - -# We cannot yet rely on "unset" to work, but we need these variables -# to be unset--not just set to an empty or harmless value--now, to -# avoid bugs in old shells (e.g. pre-3.0 UWIN ksh). This construct -# also avoids known problems related to "unset" and subshell syntax -# in other old shells (e.g. bash 2.01 and pdksh 5.2.14). -for as_var in BASH_ENV ENV MAIL MAILPATH CDPATH -do eval test \${$as_var+y} \ - && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : -done - -# Ensure that fds 0, 1, and 2 are open. -if (exec 3>&0) 2>/dev/null; then :; else exec 0&1) 2>/dev/null; then :; else exec 1>/dev/null; fi -if (exec 3>&2) ; then :; else exec 2>/dev/null; fi +# Printing a long string crashes Solaris 7 /usr/bin/printf. +as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo +# Prefer a ksh shell builtin over an external printf program on Solaris, +# but without wasting forks for bash or zsh. +if test -z "$BASH_VERSION$ZSH_VERSION" \ + && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='print -r --' + as_echo_n='print -rn --' +elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='printf %s\n' + as_echo_n='printf %s' +else + if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then + as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' + as_echo_n='/usr/ucb/echo -n' + else + as_echo_body='eval expr "X$1" : "X\\(.*\\)"' + as_echo_n_body='eval + arg=$1; + case $arg in #( + *"$as_nl"*) + expr "X$arg" : "X\\(.*\\)$as_nl"; + arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; + esac; + expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" + ' + export as_echo_n_body + as_echo_n='sh -c $as_echo_n_body as_echo' + fi + export as_echo_body + as_echo='sh -c $as_echo_body as_echo' +fi # The user is always right. -if ${PATH_SEPARATOR+false} :; then +if test "${PATH_SEPARATOR+set}" != set; then PATH_SEPARATOR=: (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || @@ -83,6 +80,13 @@ if ${PATH_SEPARATOR+false} :; then fi +# IFS +# We need space, tab and new line, in precisely that order. Quoting is +# there to prevent editors from complaining about space-tab. +# (If _AS_PATH_WALK were called with IFS unset, it would disable word +# splitting by setting IFS to empty value.) +IFS=" "" $as_nl" + # Find who we are. Look in the path if we contain no directory separator. as_myself= case $0 in #(( @@ -91,27 +95,43 @@ case $0 in #(( for as_dir in $PATH do IFS=$as_save_IFS - case $as_dir in #((( - '') as_dir=./ ;; - */) ;; - *) as_dir=$as_dir/ ;; - esac - test -r "$as_dir$0" && as_myself=$as_dir$0 && break + test -z "$as_dir" && as_dir=. + test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break done IFS=$as_save_IFS ;; esac -# We did not find ourselves, most probably we were run as 'sh COMMAND' +# We did not find ourselves, most probably we were run as `sh COMMAND' # in which case we are not to be found in the path. if test "x$as_myself" = x; then as_myself=$0 fi if test ! -f "$as_myself"; then - printf "%s\n" "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 + $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 exit 1 fi +# Unset variables that we do not need and which cause bugs (e.g. in +# pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" +# suppresses any "Segmentation fault" message there. '((' could +# trigger a bug in pdksh 5.2.14. +for as_var in BASH_ENV ENV MAIL MAILPATH +do eval test x\${$as_var+set} = xset \ + && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : +done +PS1='$ ' +PS2='> ' +PS4='+ ' + +# NLS nuisances. +LC_ALL=C +export LC_ALL +LANGUAGE=C +export LANGUAGE + +# CDPATH. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH # Use a proper internal environment variable to ensure we don't fall # into an infinite loop, continuously re-executing ourselves. @@ -132,28 +152,26 @@ case $- in # (((( esac exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} # Admittedly, this is quite paranoid, since all the known shells bail -# out after a failed 'exec'. -printf "%s\n" "$0: could not re-execute with $CONFIG_SHELL" >&2 -exit 255 +# out after a failed `exec'. +$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2 +as_fn_exit 255 fi # We don't want this to propagate to other subprocesses. { _as_can_reexec=; unset _as_can_reexec;} if test "x$CONFIG_SHELL" = x; then - as_bourne_compatible="if test \${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 -then : + as_bourne_compatible="if test -n \"\${ZSH_VERSION+set}\" && (emulate sh) >/dev/null 2>&1; then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on \${1+\"\$@\"}, which # is contrary to our usage. Disable this feature. alias -g '\${1+\"\$@\"}'='\"\$@\"' setopt NO_GLOB_SUBST -else case e in #( - e) case \`(set -o) 2>/dev/null\` in #( +else + case \`(set -o) 2>/dev/null\` in #( *posix*) : set -o posix ;; #( *) : ;; -esac ;; esac fi " @@ -168,54 +186,42 @@ as_fn_success || { exitcode=1; echo as_fn_success failed.; } as_fn_failure && { exitcode=1; echo as_fn_failure succeeded.; } as_fn_ret_success || { exitcode=1; echo as_fn_ret_success failed.; } as_fn_ret_failure && { exitcode=1; echo as_fn_ret_failure succeeded.; } -if ( set x; as_fn_ret_success y && test x = \"\$1\" ) -then : +if ( set x; as_fn_ret_success y && test x = \"\$1\" ); then : -else case e in #( - e) exitcode=1; echo positional parameters were not saved. ;; -esac +else + exitcode=1; echo positional parameters were not saved. fi test x\$exitcode = x0 || exit 1 -blah=\$(echo \$(echo blah)) -test x\"\$blah\" = xblah || exit 1 test -x / || exit 1" as_suggested=" as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" && - test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1" - if (eval "$as_required") 2>/dev/null -then : + test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1 +test \$(( 1 + 1 )) = 2 || exit 1" + if (eval "$as_required") 2>/dev/null; then : as_have_required=yes -else case e in #( - e) as_have_required=no ;; -esac +else + as_have_required=no fi - if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null -then : + if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null; then : -else case e in #( - e) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +else + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR as_found=false for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH do IFS=$as_save_IFS - case $as_dir in #((( - '') as_dir=./ ;; - */) ;; - *) as_dir=$as_dir/ ;; - esac + test -z "$as_dir" && as_dir=. as_found=: case $as_dir in #( /*) for as_base in sh bash ksh sh5; do # Try only shells that exist, to save several forks. - as_shell=$as_dir$as_base + as_shell=$as_dir/$as_base if { test -f "$as_shell" || test -f "$as_shell.exe"; } && - as_run=a "$as_shell" -c "$as_bourne_compatible""$as_required" 2>/dev/null -then : + { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$as_shell"; } 2>/dev/null; then : CONFIG_SHELL=$as_shell as_have_required=yes - if as_run=a "$as_shell" -c "$as_bourne_compatible""$as_suggested" 2>/dev/null -then : + if { $as_echo "$as_bourne_compatible""$as_suggested" | as_run=a "$as_shell"; } 2>/dev/null; then : break 2 fi fi @@ -223,22 +229,14 @@ fi esac as_found=false done -IFS=$as_save_IFS -if $as_found -then : - -else case e in #( - e) if { test -f "$SHELL" || test -f "$SHELL.exe"; } && - as_run=a "$SHELL" -c "$as_bourne_compatible""$as_required" 2>/dev/null -then : +$as_found || { if { test -f "$SHELL" || test -f "$SHELL.exe"; } && + { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$SHELL"; } 2>/dev/null; then : CONFIG_SHELL=$SHELL as_have_required=yes -fi ;; -esac -fi +fi; } +IFS=$as_save_IFS - if test "x$CONFIG_SHELL" != x -then : + if test "x$CONFIG_SHELL" != x; then : export CONFIG_SHELL # We cannot yet assume a decent shell, so we have to provide a # neutralization value for shells without unset; and this also @@ -255,27 +253,25 @@ case $- in # (((( esac exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} # Admittedly, this is quite paranoid, since all the known shells bail -# out after a failed 'exec'. -printf "%s\n" "$0: could not re-execute with $CONFIG_SHELL" >&2 +# out after a failed `exec'. +$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2 exit 255 fi - if test x$as_have_required = xno -then : - printf "%s\n" "$0: This script requires a shell more modern than all" - printf "%s\n" "$0: the shells that I found on your system." - if test ${ZSH_VERSION+y} ; then - printf "%s\n" "$0: In particular, zsh $ZSH_VERSION has bugs and should" - printf "%s\n" "$0: be upgraded to zsh 4.3.4 or later." + if test x$as_have_required = xno; then : + $as_echo "$0: This script requires a shell more modern than all" + $as_echo "$0: the shells that I found on your system." + if test x${ZSH_VERSION+set} = xset ; then + $as_echo "$0: In particular, zsh $ZSH_VERSION has bugs and should" + $as_echo "$0: be upgraded to zsh 4.3.4 or later." else - printf "%s\n" "$0: Please tell bug-autoconf@gnu.org about your system, + $as_echo "$0: Please tell bug-autoconf@gnu.org about your system, $0: including any error possibly output before this $0: message. Then install a modern shell, or manually run $0: the script under such a shell if you do have one." fi exit 1 -fi ;; -esac +fi fi fi SHELL=${CONFIG_SHELL-/bin/sh} @@ -296,7 +292,6 @@ as_fn_unset () } as_unset=as_fn_unset - # as_fn_set_status STATUS # ----------------------- # Set $? to STATUS, without forking. @@ -328,7 +323,7 @@ as_fn_mkdir_p () as_dirs= while :; do case $as_dir in #( - *\'*) as_qdir=`printf "%s\n" "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( + *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( *) as_qdir=$as_dir;; esac as_dirs="'$as_qdir' $as_dirs" @@ -337,7 +332,7 @@ $as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_dir" : 'X\(//\)[^/]' \| \ X"$as_dir" : 'X\(//\)$' \| \ X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || -printf "%s\n" X"$as_dir" | +$as_echo X"$as_dir" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q @@ -376,18 +371,16 @@ as_fn_executable_p () # advantage of any shell optimizations that allow amortized linear growth over # repeated appends, instead of the typical quadratic growth present in naive # implementations. -if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null -then : +if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : eval 'as_fn_append () { eval $1+=\$2 }' -else case e in #( - e) as_fn_append () +else + as_fn_append () { eval $1=\$$1\$2 - } ;; -esac + } fi # as_fn_append # as_fn_arith ARG... @@ -395,18 +388,16 @@ fi # as_fn_append # Perform arithmetic evaluation on the ARGs, and store the result in the # global $as_val. Take advantage of shells that can avoid forks. The arguments # must be portable across $(()) and expr. -if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null -then : +if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : eval 'as_fn_arith () { as_val=$(( $* )) }' -else case e in #( - e) as_fn_arith () +else + as_fn_arith () { as_val=`expr "$@" || test $? -eq 1` - } ;; -esac + } fi # as_fn_arith @@ -420,9 +411,9 @@ as_fn_error () as_status=$1; test $as_status -eq 0 && as_status=1 if test "$4"; then as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack - printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 + $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 fi - printf "%s\n" "$as_me: error: $2" >&2 + $as_echo "$as_me: error: $2" >&2 as_fn_exit $as_status } # as_fn_error @@ -449,7 +440,7 @@ as_me=`$as_basename -- "$0" || $as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ X"$0" : 'X\(//\)$' \| \ X"$0" : 'X\(/\)' \| . 2>/dev/null || -printf "%s\n" X/"$0" | +$as_echo X/"$0" | sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/ q @@ -482,8 +473,6 @@ as_cr_alnum=$as_cr_Letters$as_cr_digits /[$]LINENO/= ' <$as_myself | sed ' - t clear - :clear s/[$]LINENO.*/&-/ t lineno b @@ -495,7 +484,7 @@ as_cr_alnum=$as_cr_Letters$as_cr_digits s/-\n.*// ' >$as_me.lineno && chmod +x "$as_me.lineno" || - { printf "%s\n" "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; } + { $as_echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; } # If we had to re-execute with $CONFIG_SHELL, we're ensured to have # already done that, so ensure we don't try to do so again and fall @@ -509,10 +498,6 @@ as_cr_alnum=$as_cr_Letters$as_cr_digits exit } - -# Determine whether it's possible to make 'echo' print without a newline. -# These variables are no longer used directly by Autoconf, but are AC_SUBSTed -# for compatibility with existing Makefiles. ECHO_C= ECHO_N= ECHO_T= case `echo -n x` in #((((( -n*) @@ -526,12 +511,6 @@ case `echo -n x` in #((((( ECHO_N='-n';; esac -# For backward compatibility with old third-party macros, we provide -# the shell variables $as_echo and $as_echo_n. New code should use -# AS_ECHO(["message"]) and AS_ECHO_N(["message"]), respectively. -as_echo='printf %s\n' -as_echo_n='printf %s' - rm -f conf$$ conf$$.exe conf$$.file if test -d conf$$.dir; then rm -f conf$$.dir/conf$$.file @@ -543,9 +522,9 @@ if (echo >conf$$.file) 2>/dev/null; then if ln -s conf$$.file conf$$ 2>/dev/null; then as_ln_s='ln -s' # ... but there are two gotchas: - # 1) On MSYS, both 'ln -s file dir' and 'ln file dir' fail. - # 2) DJGPP < 2.04 has no symlinks; 'ln -s' creates a wrapper executable. - # In both cases, we have to default to 'cp -pR'. + # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. + # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. + # In both cases, we have to default to `cp -pR'. ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || as_ln_s='cp -pR' elif ln conf$$.file conf$$ 2>/dev/null; then @@ -570,12 +549,10 @@ as_test_x='test -x' as_executable_p=as_fn_executable_p # Sed expression to map a string onto a valid CPP name. -as_sed_cpp="y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g" -as_tr_cpp="eval sed '$as_sed_cpp'" # deprecated +as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" # Sed expression to map a string onto a valid variable name. -as_sed_sh="y%*+%pp%;s%[^_$as_cr_alnum]%_%g" -as_tr_sh="eval sed '$as_sed_sh'" # deprecated +as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" test -n "$DJDIR" || exec 7<&0 /dev/null && - as_fn_error $? "invalid feature name: '$ac_useropt'" + as_fn_error $? "invalid feature name: $ac_useropt" ac_useropt_orig=$ac_useropt - ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "enable_$ac_useropt" @@ -873,9 +855,9 @@ do ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && - as_fn_error $? "invalid feature name: '$ac_useropt'" + as_fn_error $? "invalid feature name: $ac_useropt" ac_useropt_orig=$ac_useropt - ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "enable_$ac_useropt" @@ -1086,9 +1068,9 @@ do ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && - as_fn_error $? "invalid package name: '$ac_useropt'" + as_fn_error $? "invalid package name: $ac_useropt" ac_useropt_orig=$ac_useropt - ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "with_$ac_useropt" @@ -1102,9 +1084,9 @@ do ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && - as_fn_error $? "invalid package name: '$ac_useropt'" + as_fn_error $? "invalid package name: $ac_useropt" ac_useropt_orig=$ac_useropt - ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "with_$ac_useropt" @@ -1132,8 +1114,8 @@ do | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*) x_libraries=$ac_optarg ;; - -*) as_fn_error $? "unrecognized option: '$ac_option' -Try '$0 --help' for more information" + -*) as_fn_error $? "unrecognized option: \`$ac_option' +Try \`$0 --help' for more information" ;; *=*) @@ -1141,16 +1123,16 @@ Try '$0 --help' for more information" # Reject names that are not valid shell variable names. case $ac_envvar in #( '' | [0-9]* | *[!_$as_cr_alnum]* ) - as_fn_error $? "invalid variable name: '$ac_envvar'" ;; + as_fn_error $? "invalid variable name: \`$ac_envvar'" ;; esac eval $ac_envvar=\$ac_optarg export $ac_envvar ;; *) # FIXME: should be removed in autoconf 3.0. - printf "%s\n" "$as_me: WARNING: you should use --build, --host, --target" >&2 + $as_echo "$as_me: WARNING: you should use --build, --host, --target" >&2 expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null && - printf "%s\n" "$as_me: WARNING: invalid host type: $ac_option" >&2 + $as_echo "$as_me: WARNING: invalid host type: $ac_option" >&2 : "${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}" ;; @@ -1166,7 +1148,7 @@ if test -n "$ac_unrecognized_opts"; then case $enable_option_checking in no) ;; fatal) as_fn_error $? "unrecognized options: $ac_unrecognized_opts" ;; - *) printf "%s\n" "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;; + *) $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;; esac fi @@ -1191,7 +1173,7 @@ do as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val" done -# There might be people who depend on the old broken behavior: '$host' +# There might be people who depend on the old broken behavior: `$host' # used to hold the argument of --host etc. # FIXME: To remove some day. build=$build_alias @@ -1230,7 +1212,7 @@ $as_expr X"$as_myself" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_myself" : 'X\(//\)[^/]' \| \ X"$as_myself" : 'X\(//\)$' \| \ X"$as_myself" : 'X\(/\)' \| . 2>/dev/null || -printf "%s\n" X"$as_myself" | +$as_echo X"$as_myself" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q @@ -1259,7 +1241,7 @@ if test ! -r "$srcdir/$ac_unique_file"; then test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .." as_fn_error $? "cannot find sources ($ac_unique_file) in $srcdir" fi -ac_msg="sources are in $srcdir, but 'cd $srcdir' does not work" +ac_msg="sources are in $srcdir, but \`cd $srcdir' does not work" ac_abs_confdir=`( cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error $? "$ac_msg" pwd)` @@ -1287,7 +1269,7 @@ if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -'configure' configures this package to adapt to many kinds of systems. +\`configure' configures this package to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1301,11 +1283,11 @@ Configuration: --help=short display options specific to this package --help=recursive display the short help of all the included packages -V, --version display version information and exit - -q, --quiet, --silent do not print 'checking ...' messages + -q, --quiet, --silent do not print \`checking ...' messages --cache-file=FILE cache test results in FILE [disabled] - -C, --config-cache alias for '--cache-file=config.cache' + -C, --config-cache alias for \`--cache-file=config.cache' -n, --no-create do not create output files - --srcdir=DIR find the sources in DIR [configure dir or '..'] + --srcdir=DIR find the sources in DIR [configure dir or \`..'] Installation directories: --prefix=PREFIX install architecture-independent files in PREFIX @@ -1313,10 +1295,10 @@ Installation directories: --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX [PREFIX] -By default, 'make install' will install all the files in -'$ac_default_prefix/bin', '$ac_default_prefix/lib' etc. You can specify -an installation prefix other than '$ac_default_prefix' using '--prefix', -for instance '--prefix=\$HOME'. +By default, \`make install' will install all the files in +\`$ac_default_prefix/bin', \`$ac_default_prefix/lib' etc. You can specify +an installation prefix other than \`$ac_default_prefix' using \`--prefix', +for instance \`--prefix=\$HOME'. For better control, use the options below. @@ -1351,12 +1333,6 @@ if test -n "$ac_init_help"; then cat <<\_ACEOF -Optional Features: - --disable-option-checking ignore unrecognized --enable/--with options - --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) - --enable-FEATURE[=ARG] include FEATURE [ARG=yes] - --enable-nntp enable NNTP support (requires python3-nntplib) - Optional Packages: --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) @@ -1380,8 +1356,9 @@ Some influential environment variables: LIBS libraries to pass to the linker, e.g. -l CPPFLAGS (Objective) C/C++ preprocessor flags, e.g. -I if you have headers in a nonstandard directory + CPP C preprocessor -Use these variables to override the choices made by 'configure' or to help +Use these variables to override the choices made by `configure' or to help it to find libraries and programs with nonstandard names/locations. Report bugs to the package provider. @@ -1400,9 +1377,9 @@ if test "$ac_init_help" = "recursive"; then case "$ac_dir" in .) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; *) - ac_dir_suffix=/`printf "%s\n" "$ac_dir" | sed 's|^\.[\\/]||'` + ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` # A ".." for each directory in $ac_dir_suffix. - ac_top_builddir_sub=`printf "%s\n" "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` + ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` case $ac_top_builddir_sub in "") ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; @@ -1430,8 +1407,7 @@ esac ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix cd "$ac_dir" || { ac_status=$?; continue; } - # Check for configure.gnu first; this name is used for a wrapper for - # Metaconfig's "Configure" on case-insensitive file systems. + # Check for guested configure. if test -f "$ac_srcdir/configure.gnu"; then echo && $SHELL "$ac_srcdir/configure.gnu" --help=recursive @@ -1439,7 +1415,7 @@ ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix echo && $SHELL "$ac_srcdir/configure" --help=recursive else - printf "%s\n" "$as_me: WARNING: no configuration information is in $ac_dir" >&2 + $as_echo "$as_me: WARNING: no configuration information is in $ac_dir" >&2 fi || ac_status=$? cd "$ac_pwd" || { ac_status=$?; break; } done @@ -1449,9 +1425,9 @@ test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF configure -generated by GNU Autoconf 2.72 +generated by GNU Autoconf 2.69 -Copyright (C) 2023 Free Software Foundation, Inc. +Copyright (C) 2012 Free Software Foundation, Inc. This configure script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it. _ACEOF @@ -1468,14 +1444,14 @@ fi ac_fn_c_try_compile () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack - rm -f conftest.$ac_objext conftest.beam + rm -f conftest.$ac_objext if { { ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" -printf "%s\n" "$ac_try_echo"; } >&5 +$as_echo "$ac_try_echo"; } >&5 (eval "$ac_compile") 2>conftest.err ac_status=$? if test -s conftest.err; then @@ -1483,19 +1459,17 @@ printf "%s\n" "$ac_try_echo"; } >&5 cat conftest.er1 >&5 mv -f conftest.er1 conftest.err fi - printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } && { test -z "$ac_c_werror_flag" || test ! -s conftest.err - } && test -s conftest.$ac_objext -then : + } && test -s conftest.$ac_objext; then : ac_retval=0 -else case e in #( - e) printf "%s\n" "$as_me: failed program was:" >&5 +else + $as_echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 - ac_retval=1 ;; -esac + ac_retval=1 fi eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno as_fn_set_status $ac_retval @@ -1508,14 +1482,14 @@ fi ac_fn_c_try_link () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack - rm -f conftest.$ac_objext conftest.beam conftest$ac_exeext + rm -f conftest.$ac_objext conftest$ac_exeext if { { ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" -printf "%s\n" "$ac_try_echo"; } >&5 +$as_echo "$ac_try_echo"; } >&5 (eval "$ac_link") 2>conftest.err ac_status=$? if test -s conftest.err; then @@ -1523,22 +1497,20 @@ printf "%s\n" "$ac_try_echo"; } >&5 cat conftest.er1 >&5 mv -f conftest.er1 conftest.err fi - printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } && { test -z "$ac_c_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && { test "$cross_compiling" = yes || test -x conftest$ac_exeext - } -then : + }; then : ac_retval=0 -else case e in #( - e) printf "%s\n" "$as_me: failed program was:" >&5 +else + $as_echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 - ac_retval=1 ;; -esac + ac_retval=1 fi # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would @@ -1556,22 +1528,28 @@ fi ac_fn_c_check_func () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 -printf %s "checking for $2... " >&6; } -if eval test \${$3+y} -then : - printf %s "(cached) " >&6 -else case e in #( - e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +$as_echo_n "checking for $2... " >&6; } +if eval \${$3+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Define $2 to an innocuous variant, in case declares $2. For example, HP-UX 11i declares gettimeofday. */ #define $2 innocuous_$2 /* System header to define __stub macros and hopefully few prototypes, - which can conflict with char $2 (void); below. */ + which can conflict with char $2 (); below. + Prefer to if __STDC__ is defined, since + exists even on freestanding compilers. */ + +#ifdef __STDC__ +# include +#else +# include +#endif -#include #undef $2 /* Override any GCC internal prototype to avoid an error. @@ -1580,7 +1558,7 @@ else case e in #( #ifdef __cplusplus extern "C" #endif -char $2 (void); +char $2 (); /* The GNU C library defines this for functions which it implements to always fail with ENOSYS. Some functions are actually named something starting with __ and the normal name is an alias. */ @@ -1589,152 +1567,232 @@ choke me #endif int -main (void) +main () { return $2 (); ; return 0; } _ACEOF -if ac_fn_c_try_link "$LINENO" -then : +if ac_fn_c_try_link "$LINENO"; then : eval "$3=yes" -else case e in #( - e) eval "$3=no" ;; -esac +else + eval "$3=no" fi -rm -f core conftest.err conftest.$ac_objext conftest.beam \ - conftest$ac_exeext conftest.$ac_ext ;; -esac +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext fi eval ac_res=\$$3 - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 -printf "%s\n" "$ac_res" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno } # ac_fn_c_check_func -# ac_fn_c_check_header_compile LINENO HEADER VAR INCLUDES +# ac_fn_c_try_cpp LINENO +# ---------------------- +# Try to preprocess conftest.$ac_ext, and return whether this succeeded. +ac_fn_c_try_cpp () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + if { { ac_try="$ac_cpp conftest.$ac_ext" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_cpp conftest.$ac_ext") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } > conftest.i && { + test -z "$ac_c_preproc_warn_flag$ac_c_werror_flag" || + test ! -s conftest.err + }; then : + ac_retval=0 +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 +fi + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_cpp + +# ac_fn_c_check_header_mongrel LINENO HEADER VAR INCLUDES # ------------------------------------------------------- -# Tests whether HEADER exists and can be compiled using the include files in -# INCLUDES, setting the cache variable VAR accordingly. -ac_fn_c_check_header_compile () +# Tests whether HEADER exists, giving a warning if it cannot be compiled using +# the include files in INCLUDES and setting the cache variable VAR +# accordingly. +ac_fn_c_check_header_mongrel () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 -printf %s "checking for $2... " >&6; } -if eval test \${$3+y} -then : - printf %s "(cached) " >&6 -else case e in #( - e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext + if eval \${$3+:} false; then : + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +$as_echo_n "checking for $2... " >&6; } +if eval \${$3+:} false; then : + $as_echo_n "(cached) " >&6 +fi +eval ac_res=\$$3 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } +else + # Is the header compilable? +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 usability" >&5 +$as_echo_n "checking $2 usability... " >&6; } +cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 #include <$2> _ACEOF -if ac_fn_c_try_compile "$LINENO" -then : - eval "$3=yes" -else case e in #( - e) eval "$3=no" ;; -esac +if ac_fn_c_try_compile "$LINENO"; then : + ac_header_compiler=yes +else + ac_header_compiler=no fi -rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_compiler" >&5 +$as_echo "$ac_header_compiler" >&6; } + +# Is the header present? +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 presence" >&5 +$as_echo_n "checking $2 presence... " >&6; } +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include <$2> +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + ac_header_preproc=yes +else + ac_header_preproc=no +fi +rm -f conftest.err conftest.i conftest.$ac_ext +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_preproc" >&5 +$as_echo "$ac_header_preproc" >&6; } + +# So? What about this header? +case $ac_header_compiler:$ac_header_preproc:$ac_c_preproc_warn_flag in #(( + yes:no: ) + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&5 +$as_echo "$as_me: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5 +$as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;} + ;; + no:yes:* ) + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: present but cannot be compiled" >&5 +$as_echo "$as_me: WARNING: $2: present but cannot be compiled" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: check for missing prerequisite headers?" >&5 +$as_echo "$as_me: WARNING: $2: check for missing prerequisite headers?" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: see the Autoconf documentation" >&5 +$as_echo "$as_me: WARNING: $2: see the Autoconf documentation" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: section \"Present But Cannot Be Compiled\"" >&5 +$as_echo "$as_me: WARNING: $2: section \"Present But Cannot Be Compiled\"" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5 +$as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;} + ;; esac + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +$as_echo_n "checking for $2... " >&6; } +if eval \${$3+:} false; then : + $as_echo_n "(cached) " >&6 +else + eval "$3=\$ac_header_compiler" fi eval ac_res=\$$3 - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 -printf "%s\n" "$ac_res" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } +fi eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno -} # ac_fn_c_check_header_compile +} # ac_fn_c_check_header_mongrel -# ac_fn_c_check_type LINENO TYPE VAR INCLUDES -# ------------------------------------------- -# Tests whether TYPE exists after having included INCLUDES, setting cache -# variable VAR accordingly. -ac_fn_c_check_type () +# ac_fn_c_try_run LINENO +# ---------------------- +# Try to link conftest.$ac_ext, and return whether this succeeded. Assumes +# that executables *can* be run. +ac_fn_c_try_run () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 -printf %s "checking for $2... " >&6; } -if eval test \${$3+y} -then : - printf %s "(cached) " >&6 -else case e in #( - e) eval "$3=no" - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -$4 -int -main (void) + if { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { ac_try='./conftest$ac_exeext' + { { case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; }; then : + ac_retval=0 +else + $as_echo "$as_me: program exited with status $ac_status" >&5 + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=$ac_status +fi + rm -rf conftest.dSYM conftest_ipa8_conftest.oo + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_run + +# ac_fn_c_check_header_compile LINENO HEADER VAR INCLUDES +# ------------------------------------------------------- +# Tests whether HEADER exists and can be compiled using the include files in +# INCLUDES, setting the cache variable VAR accordingly. +ac_fn_c_check_header_compile () { -if (sizeof ($2)) - return 0; - ; - return 0; -} -_ACEOF -if ac_fn_c_try_compile "$LINENO" -then : + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +$as_echo_n "checking for $2... " >&6; } +if eval \${$3+:} false; then : + $as_echo_n "(cached) " >&6 +else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 -int -main (void) -{ -if (sizeof (($2))) - return 0; - ; - return 0; -} +#include <$2> _ACEOF -if ac_fn_c_try_compile "$LINENO" -then : - -else case e in #( - e) eval "$3=yes" ;; -esac -fi -rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext +if ac_fn_c_try_compile "$LINENO"; then : + eval "$3=yes" +else + eval "$3=no" fi -rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; -esac +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi eval ac_res=\$$3 - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 -printf "%s\n" "$ac_res" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno -} # ac_fn_c_check_type -ac_configure_args_raw= -for ac_arg -do - case $ac_arg in - *\'*) - ac_arg=`printf "%s\n" "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; - esac - as_fn_append ac_configure_args_raw " '$ac_arg'" -done - -case $ac_configure_args_raw in - *$as_nl*) - ac_safe_unquote= ;; - *) - ac_unsafe_z='|&;<>()$`\\"*?[ '' ' # This string ends in space, tab. - ac_unsafe_a="$ac_unsafe_z#~" - ac_safe_unquote="s/ '\\([^$ac_unsafe_a][^$ac_unsafe_z]*\\)'/ \\1/g" - ac_configure_args_raw=` printf "%s\n" "$ac_configure_args_raw" | sed "$ac_safe_unquote"`;; -esac - +} # ac_fn_c_check_header_compile cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. It was created by $as_me, which was -generated by GNU Autoconf 2.72. Invocation command line was +generated by GNU Autoconf 2.69. Invocation command line was - $ $0$ac_configure_args_raw + $ $0 $@ _ACEOF exec 5>>config.log @@ -1767,12 +1825,8 @@ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS - case $as_dir in #((( - '') as_dir=./ ;; - */) ;; - *) as_dir=$as_dir/ ;; - esac - printf "%s\n" "PATH: $as_dir" + test -z "$as_dir" && as_dir=. + $as_echo "PATH: $as_dir" done IFS=$as_save_IFS @@ -1807,7 +1861,7 @@ do | -silent | --silent | --silen | --sile | --sil) continue ;; *\'*) - ac_arg=`printf "%s\n" "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; + ac_arg=`$as_echo "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; esac case $ac_pass in 1) as_fn_append ac_configure_args0 " '$ac_arg'" ;; @@ -1842,13 +1896,11 @@ done # WARNING: Use '\'' to represent an apostrophe within the trap. # WARNING: Do not start the trap code with a newline, due to a FreeBSD 4.0 bug. trap 'exit_status=$? - # Sanitize IFS. - IFS=" "" $as_nl" # Save into config.log some information that might help in debugging. { echo - printf "%s\n" "## ---------------- ## + $as_echo "## ---------------- ## ## Cache variables. ## ## ---------------- ##" echo @@ -1859,8 +1911,8 @@ trap 'exit_status=$? case $ac_val in #( *${as_nl}*) case $ac_var in #( - *_cv_*) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 -printf "%s\n" "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; + *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 +$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; esac case $ac_var in #( _ | IFS | as_nl) ;; #( @@ -1884,7 +1936,7 @@ printf "%s\n" "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ) echo - printf "%s\n" "## ----------------- ## + $as_echo "## ----------------- ## ## Output variables. ## ## ----------------- ##" echo @@ -1892,14 +1944,14 @@ printf "%s\n" "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} do eval ac_val=\$$ac_var case $ac_val in - *\'\''*) ac_val=`printf "%s\n" "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; + *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; esac - printf "%s\n" "$ac_var='\''$ac_val'\''" + $as_echo "$ac_var='\''$ac_val'\''" done | sort echo if test -n "$ac_subst_files"; then - printf "%s\n" "## ------------------- ## + $as_echo "## ------------------- ## ## File substitutions. ## ## ------------------- ##" echo @@ -1907,15 +1959,15 @@ printf "%s\n" "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} do eval ac_val=\$$ac_var case $ac_val in - *\'\''*) ac_val=`printf "%s\n" "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; + *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; esac - printf "%s\n" "$ac_var='\''$ac_val'\''" + $as_echo "$ac_var='\''$ac_val'\''" done | sort echo fi if test -s confdefs.h; then - printf "%s\n" "## ----------- ## + $as_echo "## ----------- ## ## confdefs.h. ## ## ----------- ##" echo @@ -1923,8 +1975,8 @@ printf "%s\n" "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} echo fi test "$ac_signal" != 0 && - printf "%s\n" "$as_me: caught signal $ac_signal" - printf "%s\n" "$as_me: exit $exit_status" + $as_echo "$as_me: caught signal $ac_signal" + $as_echo "$as_me: exit $exit_status" } >&5 rm -f core *.core core.conftest.* && rm -f -r conftest* confdefs* conf$$* $ac_clean_files && @@ -1938,50 +1990,65 @@ ac_signal=0 # confdefs.h avoids OS command line length limits that DEFS can exceed. rm -f -r conftest* confdefs.h -printf "%s\n" "/* confdefs.h */" > confdefs.h +$as_echo "/* confdefs.h */" > confdefs.h # Predefined preprocessor variables. -printf "%s\n" "#define PACKAGE_NAME \"$PACKAGE_NAME\"" >>confdefs.h +cat >>confdefs.h <<_ACEOF +#define PACKAGE_NAME "$PACKAGE_NAME" +_ACEOF -printf "%s\n" "#define PACKAGE_TARNAME \"$PACKAGE_TARNAME\"" >>confdefs.h +cat >>confdefs.h <<_ACEOF +#define PACKAGE_TARNAME "$PACKAGE_TARNAME" +_ACEOF -printf "%s\n" "#define PACKAGE_VERSION \"$PACKAGE_VERSION\"" >>confdefs.h +cat >>confdefs.h <<_ACEOF +#define PACKAGE_VERSION "$PACKAGE_VERSION" +_ACEOF -printf "%s\n" "#define PACKAGE_STRING \"$PACKAGE_STRING\"" >>confdefs.h +cat >>confdefs.h <<_ACEOF +#define PACKAGE_STRING "$PACKAGE_STRING" +_ACEOF -printf "%s\n" "#define PACKAGE_BUGREPORT \"$PACKAGE_BUGREPORT\"" >>confdefs.h +cat >>confdefs.h <<_ACEOF +#define PACKAGE_BUGREPORT "$PACKAGE_BUGREPORT" +_ACEOF -printf "%s\n" "#define PACKAGE_URL \"$PACKAGE_URL\"" >>confdefs.h +cat >>confdefs.h <<_ACEOF +#define PACKAGE_URL "$PACKAGE_URL" +_ACEOF # Let the site file select an alternate cache file if it wants to. # Prefer an explicitly selected file to automatically selected ones. +ac_site_file1=NONE +ac_site_file2=NONE if test -n "$CONFIG_SITE"; then - ac_site_files="$CONFIG_SITE" + # We do not want a PATH search for config.site. + case $CONFIG_SITE in #(( + -*) ac_site_file1=./$CONFIG_SITE;; + */*) ac_site_file1=$CONFIG_SITE;; + *) ac_site_file1=./$CONFIG_SITE;; + esac elif test "x$prefix" != xNONE; then - ac_site_files="$prefix/share/config.site $prefix/etc/config.site" + ac_site_file1=$prefix/share/config.site + ac_site_file2=$prefix/etc/config.site else - ac_site_files="$ac_default_prefix/share/config.site $ac_default_prefix/etc/config.site" + ac_site_file1=$ac_default_prefix/share/config.site + ac_site_file2=$ac_default_prefix/etc/config.site fi - -for ac_site_file in $ac_site_files +for ac_site_file in "$ac_site_file1" "$ac_site_file2" do - case $ac_site_file in #( - */*) : - ;; #( - *) : - ac_site_file=./$ac_site_file ;; -esac - if test -f "$ac_site_file" && test -r "$ac_site_file"; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5 -printf "%s\n" "$as_me: loading site script $ac_site_file" >&6;} + test "x$ac_site_file" = xNONE && continue + if test /dev/null != "$ac_site_file" && test -r "$ac_site_file"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5 +$as_echo "$as_me: loading site script $ac_site_file" >&6;} sed 's/^/| /' "$ac_site_file" >&5 . "$ac_site_file" \ - || { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 -printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} + || { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "failed to load site script $ac_site_file -See 'config.log' for more details" "$LINENO" 5; } +See \`config.log' for more details" "$LINENO" 5; } fi done @@ -1989,452 +2056,19 @@ if test -r "$cache_file"; then # Some versions of bash will fail to source /dev/null (special files # actually), so we avoid doing that. DJGPP emulates it as a regular file. if test /dev/null != "$cache_file" && test -f "$cache_file"; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5 -printf "%s\n" "$as_me: loading cache $cache_file" >&6;} + { $as_echo "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5 +$as_echo "$as_me: loading cache $cache_file" >&6;} case $cache_file in [\\/]* | ?:[\\/]* ) . "$cache_file";; *) . "./$cache_file";; esac fi else - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5 -printf "%s\n" "$as_me: creating cache $cache_file" >&6;} + { $as_echo "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5 +$as_echo "$as_me: creating cache $cache_file" >&6;} >$cache_file fi -# Test code for whether the C compiler supports C89 (global declarations) -ac_c_conftest_c89_globals=' -/* Does the compiler advertise C89 conformance? - Do not test the value of __STDC__, because some compilers set it to 0 - while being otherwise adequately conformant. */ -#if !defined __STDC__ -# error "Compiler does not advertise C89 conformance" -#endif - -#include -#include -struct stat; -/* Most of the following tests are stolen from RCS 5.7 src/conf.sh. */ -struct buf { int x; }; -struct buf * (*rcsopen) (struct buf *, struct stat *, int); -static char *e (char **p, int i) -{ - return p[i]; -} -static char *f (char * (*g) (char **, int), char **p, ...) -{ - char *s; - va_list v; - va_start (v,p); - s = g (p, va_arg (v,int)); - va_end (v); - return s; -} - -/* C89 style stringification. */ -#define noexpand_stringify(a) #a -const char *stringified = noexpand_stringify(arbitrary+token=sequence); - -/* C89 style token pasting. Exercises some of the corner cases that - e.g. old MSVC gets wrong, but not very hard. */ -#define noexpand_concat(a,b) a##b -#define expand_concat(a,b) noexpand_concat(a,b) -extern int vA; -extern int vbee; -#define aye A -#define bee B -int *pvA = &expand_concat(v,aye); -int *pvbee = &noexpand_concat(v,bee); - -/* OSF 4.0 Compaq cc is some sort of almost-ANSI by default. It has - function prototypes and stuff, but not \xHH hex character constants. - These do not provoke an error unfortunately, instead are silently treated - as an "x". The following induces an error, until -std is added to get - proper ANSI mode. Curiously \x00 != x always comes out true, for an - array size at least. It is necessary to write \x00 == 0 to get something - that is true only with -std. */ -int osf4_cc_array ['\''\x00'\'' == 0 ? 1 : -1]; - -/* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters - inside strings and character constants. */ -#define FOO(x) '\''x'\'' -int xlc6_cc_array[FOO(a) == '\''x'\'' ? 1 : -1]; - -int test (int i, double x); -struct s1 {int (*f) (int a);}; -struct s2 {int (*f) (double a);}; -int pairnames (int, char **, int *(*)(struct buf *, struct stat *, int), - int, int);' - -# Test code for whether the C compiler supports C89 (body of main). -ac_c_conftest_c89_main=' -ok |= (argc == 0 || f (e, argv, 0) != argv[0] || f (e, argv, 1) != argv[1]); -' - -# Test code for whether the C compiler supports C99 (global declarations) -ac_c_conftest_c99_globals=' -/* Does the compiler advertise C99 conformance? */ -#if !defined __STDC_VERSION__ || __STDC_VERSION__ < 199901L -# error "Compiler does not advertise C99 conformance" -#endif - -// See if C++-style comments work. - -#include -extern int puts (const char *); -extern int printf (const char *, ...); -extern int dprintf (int, const char *, ...); -extern void *malloc (size_t); -extern void free (void *); - -// Check varargs macros. These examples are taken from C99 6.10.3.5. -// dprintf is used instead of fprintf to avoid needing to declare -// FILE and stderr. -#define debug(...) dprintf (2, __VA_ARGS__) -#define showlist(...) puts (#__VA_ARGS__) -#define report(test,...) ((test) ? puts (#test) : printf (__VA_ARGS__)) -static void -test_varargs_macros (void) -{ - int x = 1234; - int y = 5678; - debug ("Flag"); - debug ("X = %d\n", x); - showlist (The first, second, and third items.); - report (x>y, "x is %d but y is %d", x, y); -} - -// Check long long types. -#define BIG64 18446744073709551615ull -#define BIG32 4294967295ul -#define BIG_OK (BIG64 / BIG32 == 4294967297ull && BIG64 % BIG32 == 0) -#if !BIG_OK - #error "your preprocessor is broken" -#endif -#if BIG_OK -#else - #error "your preprocessor is broken" -#endif -static long long int bignum = -9223372036854775807LL; -static unsigned long long int ubignum = BIG64; - -struct incomplete_array -{ - int datasize; - double data[]; -}; - -struct named_init { - int number; - const wchar_t *name; - double average; -}; - -typedef const char *ccp; - -static inline int -test_restrict (ccp restrict text) -{ - // Iterate through items via the restricted pointer. - // Also check for declarations in for loops. - for (unsigned int i = 0; *(text+i) != '\''\0'\''; ++i) - continue; - return 0; -} - -// Check varargs and va_copy. -static bool -test_varargs (const char *format, ...) -{ - va_list args; - va_start (args, format); - va_list args_copy; - va_copy (args_copy, args); - - const char *str = ""; - int number = 0; - float fnumber = 0; - - while (*format) - { - switch (*format++) - { - case '\''s'\'': // string - str = va_arg (args_copy, const char *); - break; - case '\''d'\'': // int - number = va_arg (args_copy, int); - break; - case '\''f'\'': // float - fnumber = va_arg (args_copy, double); - break; - default: - break; - } - } - va_end (args_copy); - va_end (args); - - return *str && number && fnumber; -} -' - -# Test code for whether the C compiler supports C99 (body of main). -ac_c_conftest_c99_main=' - // Check bool. - _Bool success = false; - success |= (argc != 0); - - // Check restrict. - if (test_restrict ("String literal") == 0) - success = true; - char *restrict newvar = "Another string"; - - // Check varargs. - success &= test_varargs ("s, d'\'' f .", "string", 65, 34.234); - test_varargs_macros (); - - // Check flexible array members. - struct incomplete_array *ia = - malloc (sizeof (struct incomplete_array) + (sizeof (double) * 10)); - ia->datasize = 10; - for (int i = 0; i < ia->datasize; ++i) - ia->data[i] = i * 1.234; - // Work around memory leak warnings. - free (ia); - - // Check named initializers. - struct named_init ni = { - .number = 34, - .name = L"Test wide string", - .average = 543.34343, - }; - - ni.number = 58; - - int dynamic_array[ni.number]; - dynamic_array[0] = argv[0][0]; - dynamic_array[ni.number - 1] = 543; - - // work around unused variable warnings - ok |= (!success || bignum == 0LL || ubignum == 0uLL || newvar[0] == '\''x'\'' - || dynamic_array[ni.number - 1] != 543); -' - -# Test code for whether the C compiler supports C11 (global declarations) -ac_c_conftest_c11_globals=' -/* Does the compiler advertise C11 conformance? */ -#if !defined __STDC_VERSION__ || __STDC_VERSION__ < 201112L -# error "Compiler does not advertise C11 conformance" -#endif - -// Check _Alignas. -char _Alignas (double) aligned_as_double; -char _Alignas (0) no_special_alignment; -extern char aligned_as_int; -char _Alignas (0) _Alignas (int) aligned_as_int; - -// Check _Alignof. -enum -{ - int_alignment = _Alignof (int), - int_array_alignment = _Alignof (int[100]), - char_alignment = _Alignof (char) -}; -_Static_assert (0 < -_Alignof (int), "_Alignof is signed"); - -// Check _Noreturn. -int _Noreturn does_not_return (void) { for (;;) continue; } - -// Check _Static_assert. -struct test_static_assert -{ - int x; - _Static_assert (sizeof (int) <= sizeof (long int), - "_Static_assert does not work in struct"); - long int y; -}; - -// Check UTF-8 literals. -#define u8 syntax error! -char const utf8_literal[] = u8"happens to be ASCII" "another string"; - -// Check duplicate typedefs. -typedef long *long_ptr; -typedef long int *long_ptr; -typedef long_ptr long_ptr; - -// Anonymous structures and unions -- taken from C11 6.7.2.1 Example 1. -struct anonymous -{ - union { - struct { int i; int j; }; - struct { int k; long int l; } w; - }; - int m; -} v1; -' - -# Test code for whether the C compiler supports C11 (body of main). -ac_c_conftest_c11_main=' - _Static_assert ((offsetof (struct anonymous, i) - == offsetof (struct anonymous, w.k)), - "Anonymous union alignment botch"); - v1.i = 2; - v1.w.k = 5; - ok |= v1.i != 5; -' - -# Test code for whether the C compiler supports C11 (complete). -ac_c_conftest_c11_program="${ac_c_conftest_c89_globals} -${ac_c_conftest_c99_globals} -${ac_c_conftest_c11_globals} - -int -main (int argc, char **argv) -{ - int ok = 0; - ${ac_c_conftest_c89_main} - ${ac_c_conftest_c99_main} - ${ac_c_conftest_c11_main} - return ok; -} -" - -# Test code for whether the C compiler supports C99 (complete). -ac_c_conftest_c99_program="${ac_c_conftest_c89_globals} -${ac_c_conftest_c99_globals} - -int -main (int argc, char **argv) -{ - int ok = 0; - ${ac_c_conftest_c89_main} - ${ac_c_conftest_c99_main} - return ok; -} -" - -# Test code for whether the C compiler supports C89 (complete). -ac_c_conftest_c89_program="${ac_c_conftest_c89_globals} - -int -main (int argc, char **argv) -{ - int ok = 0; - ${ac_c_conftest_c89_main} - return ok; -} -" - -as_fn_append ac_header_c_list " stdio.h stdio_h HAVE_STDIO_H" -as_fn_append ac_header_c_list " stdlib.h stdlib_h HAVE_STDLIB_H" -as_fn_append ac_header_c_list " string.h string_h HAVE_STRING_H" -as_fn_append ac_header_c_list " inttypes.h inttypes_h HAVE_INTTYPES_H" -as_fn_append ac_header_c_list " stdint.h stdint_h HAVE_STDINT_H" -as_fn_append ac_header_c_list " strings.h strings_h HAVE_STRINGS_H" -as_fn_append ac_header_c_list " sys/stat.h sys_stat_h HAVE_SYS_STAT_H" -as_fn_append ac_header_c_list " sys/types.h sys_types_h HAVE_SYS_TYPES_H" -as_fn_append ac_header_c_list " unistd.h unistd_h HAVE_UNISTD_H" - -# Auxiliary files required by this configure script. -ac_aux_files="install-sh" - -# Locations in which to look for auxiliary files. -ac_aux_dir_candidates="${srcdir}${PATH_SEPARATOR}${srcdir}/..${PATH_SEPARATOR}${srcdir}/../.." - -# Search for a directory containing all of the required auxiliary files, -# $ac_aux_files, from the $PATH-style list $ac_aux_dir_candidates. -# If we don't find one directory that contains all the files we need, -# we report the set of missing files from the *first* directory in -# $ac_aux_dir_candidates and give up. -ac_missing_aux_files="" -ac_first_candidate=: -printf "%s\n" "$as_me:${as_lineno-$LINENO}: looking for aux files: $ac_aux_files" >&5 -as_save_IFS=$IFS; IFS=$PATH_SEPARATOR -as_found=false -for as_dir in $ac_aux_dir_candidates -do - IFS=$as_save_IFS - case $as_dir in #((( - '') as_dir=./ ;; - */) ;; - *) as_dir=$as_dir/ ;; - esac - as_found=: - - printf "%s\n" "$as_me:${as_lineno-$LINENO}: trying $as_dir" >&5 - ac_aux_dir_found=yes - ac_install_sh= - for ac_aux in $ac_aux_files - do - # As a special case, if "install-sh" is required, that requirement - # can be satisfied by any of "install-sh", "install.sh", or "shtool", - # and $ac_install_sh is set appropriately for whichever one is found. - if test x"$ac_aux" = x"install-sh" - then - if test -f "${as_dir}install-sh"; then - printf "%s\n" "$as_me:${as_lineno-$LINENO}: ${as_dir}install-sh found" >&5 - ac_install_sh="${as_dir}install-sh -c" - elif test -f "${as_dir}install.sh"; then - printf "%s\n" "$as_me:${as_lineno-$LINENO}: ${as_dir}install.sh found" >&5 - ac_install_sh="${as_dir}install.sh -c" - elif test -f "${as_dir}shtool"; then - printf "%s\n" "$as_me:${as_lineno-$LINENO}: ${as_dir}shtool found" >&5 - ac_install_sh="${as_dir}shtool install -c" - else - ac_aux_dir_found=no - if $ac_first_candidate; then - ac_missing_aux_files="${ac_missing_aux_files} install-sh" - else - break - fi - fi - else - if test -f "${as_dir}${ac_aux}"; then - printf "%s\n" "$as_me:${as_lineno-$LINENO}: ${as_dir}${ac_aux} found" >&5 - else - ac_aux_dir_found=no - if $ac_first_candidate; then - ac_missing_aux_files="${ac_missing_aux_files} ${ac_aux}" - else - break - fi - fi - fi - done - if test "$ac_aux_dir_found" = yes; then - ac_aux_dir="$as_dir" - break - fi - ac_first_candidate=false - - as_found=false -done -IFS=$as_save_IFS -if $as_found -then : - -else case e in #( - e) as_fn_error $? "cannot find required auxiliary files:$ac_missing_aux_files" "$LINENO" 5 ;; -esac -fi - - -# These three variables are undocumented and unsupported, -# and are intended to be withdrawn in a future Autoconf release. -# They can cause serious problems if a builder's source tree is in a directory -# whose full name contains unusual characters. -if test -f "${ac_aux_dir}config.guess"; then - ac_config_guess="$SHELL ${ac_aux_dir}config.guess" -fi -if test -f "${ac_aux_dir}config.sub"; then - ac_config_sub="$SHELL ${ac_aux_dir}config.sub" -fi -if test -f "$ac_aux_dir/configure"; then - ac_configure="$SHELL ${ac_aux_dir}configure" -fi - # Check that the precious variables saved in the cache have kept the same # value. ac_cache_corrupted=false @@ -2445,12 +2079,12 @@ for ac_var in $ac_precious_vars; do eval ac_new_val=\$ac_env_${ac_var}_value case $ac_old_set,$ac_new_set in set,) - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: '$ac_var' was set to '$ac_old_val' in the previous run" >&5 -printf "%s\n" "$as_me: error: '$ac_var' was set to '$ac_old_val' in the previous run" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5 +$as_echo "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;} ac_cache_corrupted=: ;; ,set) - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: '$ac_var' was not set in the previous run" >&5 -printf "%s\n" "$as_me: error: '$ac_var' was not set in the previous run" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was not set in the previous run" >&5 +$as_echo "$as_me: error: \`$ac_var' was not set in the previous run" >&2;} ac_cache_corrupted=: ;; ,);; *) @@ -2459,24 +2093,24 @@ printf "%s\n" "$as_me: error: '$ac_var' was not set in the previous run" >&2;} ac_old_val_w=`echo x $ac_old_val` ac_new_val_w=`echo x $ac_new_val` if test "$ac_old_val_w" != "$ac_new_val_w"; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: '$ac_var' has changed since the previous run:" >&5 -printf "%s\n" "$as_me: error: '$ac_var' has changed since the previous run:" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' has changed since the previous run:" >&5 +$as_echo "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;} ac_cache_corrupted=: else - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in '$ac_var' since the previous run:" >&5 -printf "%s\n" "$as_me: warning: ignoring whitespace changes in '$ac_var' since the previous run:" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&5 +$as_echo "$as_me: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&2;} eval $ac_var=\$ac_old_val fi - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: former value: '$ac_old_val'" >&5 -printf "%s\n" "$as_me: former value: '$ac_old_val'" >&2;} - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: current value: '$ac_new_val'" >&5 -printf "%s\n" "$as_me: current value: '$ac_new_val'" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: former value: \`$ac_old_val'" >&5 +$as_echo "$as_me: former value: \`$ac_old_val'" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: current value: \`$ac_new_val'" >&5 +$as_echo "$as_me: current value: \`$ac_new_val'" >&2;} fi;; esac # Pass precious variables to config.status. if test "$ac_new_set" = set; then case $ac_new_val in - *\'*) ac_arg=$ac_var=`printf "%s\n" "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;; + *\'*) ac_arg=$ac_var=`$as_echo "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;; *) ac_arg=$ac_var=$ac_new_val ;; esac case " $ac_configure_args " in @@ -2486,12 +2120,11 @@ printf "%s\n" "$as_me: current value: '$ac_new_val'" >&2;} fi done if $ac_cache_corrupted; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 -printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5 -printf "%s\n" "$as_me: error: changes in the environment can compromise the build" >&2;} - as_fn_error $? "run '${MAKE-make} distclean' and/or 'rm $cache_file' - and start over" "$LINENO" 5 + { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5 +$as_echo "$as_me: error: changes in the environment can compromise the build" >&2;} + as_fn_error $? "run \`make distclean' and/or \`rm $cache_file' and start over" "$LINENO" 5 fi ## -------------------- ## ## Main body of script. ## @@ -2506,11 +2139,6 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu -# Store the configure command and arguments for reconfigure target -CONFIGURE_CMD=`echo "$0"` -CONFIGURE_ARGS=`echo "$*"` - - # /usr/local/mailman is the default installation directory @@ -2521,12 +2149,11 @@ BUILD_DATE=`date` # Check for Python! Better be found on $PATH -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for --with-python" >&5 -printf %s "checking for --with-python... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for --with-python" >&5 +$as_echo_n "checking for --with-python... " >&6; } # Check whether --with-python was given. -if test ${with_python+y} -then : +if test "${with_python+set}" = set; then : withval=$with_python; fi @@ -2534,20 +2161,19 @@ case "$with_python" in "") ans="no";; *) ans="$with_python" esac -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ans" >&5 -printf "%s\n" "$ans" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ans" >&5 +$as_echo "$ans" >&6; } if test -z "$with_python" then # Extract the first word of "python3", so it can be a program name with args. set dummy python3; ac_word=$2 -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 -printf %s "checking for $ac_word... " >&6; } -if test ${ac_cv_path_with_python+y} -then : - printf %s "(cached) " >&6 -else case e in #( - e) case $with_python in +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_with_python+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $with_python in [\\/]* | ?:[\\/]*) ac_cv_path_with_python="$with_python" # Let the user override the test with a path. ;; @@ -2556,15 +2182,11 @@ else case e in #( for as_dir in $PATH do IFS=$as_save_IFS - case $as_dir in #((( - '') as_dir=./ ;; - */) ;; - *) as_dir=$as_dir/ ;; - esac + test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do - if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then - ac_cv_path_with_python="$as_dir$ac_word$ac_exec_ext" - printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_with_python="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done @@ -2573,85 +2195,38 @@ IFS=$as_save_IFS test -z "$ac_cv_path_with_python" && ac_cv_path_with_python="/usr/local/bin/python3" ;; -esac ;; esac fi with_python=$ac_cv_path_with_python if test -n "$with_python"; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $with_python" >&5 -printf "%s\n" "$with_python" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_python" >&5 +$as_echo "$with_python" >&6; } else - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 -printf "%s\n" "no" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } fi fi -# Set PYTHON variable for Makefile substitution -PYTHON=$with_python - - -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking Python interpreter" >&5 -printf %s "checking Python interpreter... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking Python interpreter" >&5 +$as_echo_n "checking Python interpreter... " >&6; } if test ! -x $with_python then as_fn_error $? " - Python interpreter not found at $with_python - Please specify the correct path to Python using --with-python - " "$LINENO" 5 -fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $with_python" >&5 -printf "%s\n" "$with_python" >&6; } - -# Check for optional nntplib module -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether to enable NNTP support" >&5 -printf %s "checking whether to enable NNTP support... " >&6; } -# Check whether --enable-nntp was given. -if test ${enable_nntp+y} -then : - enableval=$enable_nntp; enable_nntp=$enableval -else case e in #( - e) enable_nntp=no - ;; -esac -fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $enable_nntp" >&5 -printf "%s\n" "$enable_nntp" >&6; } - -if test "$enable_nntp" = "yes"; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for Python nntplib module" >&5 -printf %s "checking for Python nntplib module... " >&6; } - $with_python -c "import nntplib" >/dev/null 2>&1 - if test $? -ne 0 - then - as_fn_error $? " - Python nntplib module not found but NNTP support was requested - Please install python3-nntplib package - On Debian/Ubuntu: apt-get install python3-nntplib - On RHEL/CentOS: yum install python3-nntplib - Or disable NNTP support with --disable-nntp - " "$LINENO" 5 - fi - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: found" >&5 -printf "%s\n" "found" >&6; } - -printf "%s\n" "#define HAVE_NNTP 1" >>confdefs.h - -fi - if test "$enable_nntp" = "yes"; then - HAVE_NNTP_TRUE= - HAVE_NNTP_FALSE='#' -else - HAVE_NNTP_TRUE='#' - HAVE_NNTP_FALSE= +***** No Python interpreter found! +***** Try including the configure option +***** --with-python=/path/to/python/interpreter" "$LINENO" 5 fi +PYTHON=$with_python +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $PYTHON" >&5 +$as_echo "$PYTHON" >&6; } # See if Python is new enough. -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking Python version" >&5 -printf %s "checking Python version... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking Python version" >&5 +$as_echo_n "checking Python version... " >&6; } cat > conftest.py <= 0x3000000: +if v >= 0x2040000: s = sys.version.split()[0] else: s = "" -with open("conftest.out", "w") as fp: - fp.write("%s\n" % s) +fp = open("conftest.out", "w") +fp.write("%s\n" % s) +fp.close() EOF -$with_python conftest.py +$PYTHON conftest.py version=`cat conftest.out` rm -f conftest.out conftest.py if test -z "$version" then as_fn_error $? " -***** $with_python is too old (or broken) -***** Python 3.0 or newer is required" "$LINENO" 5 +***** $PYTHON is too old (or broken) +***** Python 2.4 or newer is required" "$LINENO" 5 fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $version" >&5 -printf "%s\n" "$version" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $version" >&5 +$as_echo "$version" >&6; } # See if dnspython is installed. -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking dnspython" >&5 -printf %s "checking dnspython... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking dnspython" >&5 +$as_echo_n "checking dnspython... " >&6; } cat > conftest.py < ***** You must get a version < 2.0" "$LINENO" 5 fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $havednspython" >&5 -printf "%s\n" "$havednspython" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $havednspython" >&5 +$as_echo "$havednspython" >&6; } # Check the email package version. -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking Python's email package" >&5 -printf %s "checking Python's email package... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking Python's email package" >&5 +$as_echo_n "checking Python's email package... " >&6; } cat > conftest.py < getver.py <&5 -printf "%s\n" "$needemailpkg" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $needemailpkg" >&5 +$as_echo "$needemailpkg" >&6; } # Check Japanese codecs. -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking Japanese codecs" >&5 -printf %s "checking Japanese codecs... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking Japanese codecs" >&5 +$as_echo_n "checking Japanese codecs... " >&6; } cat > conftest.py <&5 -printf "%s\n" "$needjacodecs" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $needjacodecs" >&5 +$as_echo "$needjacodecs" >&6; } # Check Korean codecs. -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking Korean codecs" >&5 -printf %s "checking Korean codecs... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking Korean codecs" >&5 +$as_echo_n "checking Korean codecs... " >&6; } cat > conftest.py <&5 -printf "%s\n" "$needkocodecs" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $needkocodecs" >&5 +$as_echo "$needkocodecs" >&6; } # Make sure distutils is available. Some Linux Python packages split # distutils into the "-devel" package, so they need both. -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking that Python has a working distutils" >&5 -printf %s "checking that Python has a working distutils... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking that Python has a working distutils" >&5 +$as_echo_n "checking that Python has a working distutils... " >&6; } cat > conftest.py <&5 -printf "%s\n" "$havedistutils" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $havedistutils" >&5 +$as_echo "$havedistutils" >&6; } # Checks for programs. +ac_aux_dir= +for ac_dir in "$srcdir" "$srcdir/.." "$srcdir/../.."; do + if test -f "$ac_dir/install-sh"; then + ac_aux_dir=$ac_dir + ac_install_sh="$ac_aux_dir/install-sh -c" + break + elif test -f "$ac_dir/install.sh"; then + ac_aux_dir=$ac_dir + ac_install_sh="$ac_aux_dir/install.sh -c" + break + elif test -f "$ac_dir/shtool"; then + ac_aux_dir=$ac_dir + ac_install_sh="$ac_aux_dir/shtool install -c" + break + fi +done +if test -z "$ac_aux_dir"; then + as_fn_error $? "cannot find install-sh, install.sh, or shtool in \"$srcdir\" \"$srcdir/..\" \"$srcdir/../..\"" "$LINENO" 5 +fi +# These three variables are undocumented and unsupported, +# and are intended to be withdrawn in a future Autoconf release. +# They can cause serious problems if a builder's source tree is in a directory +# whose full name contains unusual characters. +ac_config_guess="$SHELL $ac_aux_dir/config.guess" # Please don't use this var. +ac_config_sub="$SHELL $ac_aux_dir/config.sub" # Please don't use this var. +ac_configure="$SHELL $ac_aux_dir/configure" # Please don't use this var. - # Find a good install program. We prefer a C program (faster), + +# Find a good install program. We prefer a C program (faster), # so one script is as good as another. But avoid the broken or # incompatible versions: # SysV /etc/install, /usr/sbin/install @@ -2873,25 +2482,20 @@ printf "%s\n" "$havedistutils" >&6; } # OS/2's system install, which has a completely different semantic # ./install, which can be erroneously created by make from ./install.sh. # Reject install programs that cannot install multiple files. -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for a BSD-compatible install" >&5 -printf %s "checking for a BSD-compatible install... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for a BSD-compatible install" >&5 +$as_echo_n "checking for a BSD-compatible install... " >&6; } if test -z "$INSTALL"; then -if test ${ac_cv_path_install+y} -then : - printf %s "(cached) " >&6 -else case e in #( - e) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +if ${ac_cv_path_install+:} false; then : + $as_echo_n "(cached) " >&6 +else + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS - case $as_dir in #((( - '') as_dir=./ ;; - */) ;; - *) as_dir=$as_dir/ ;; - esac - # Account for fact that we put trailing slashes in our PATH walk. -case $as_dir in #(( - ./ | /[cC]/* | \ + test -z "$as_dir" && as_dir=. + # Account for people who put trailing slashes in PATH elements. +case $as_dir/ in #(( + ./ | .// | /[cC]/* | \ /etc/* | /usr/sbin/* | /usr/etc/* | /sbin/* | /usr/afsws/bin/* | \ ?:[\\/]os2[\\/]install[\\/]* | ?:[\\/]OS2[\\/]INSTALL[\\/]* | \ /usr/ucb/* ) ;; @@ -2901,13 +2505,13 @@ case $as_dir in #(( # by default. for ac_prog in ginstall scoinst install; do for ac_exec_ext in '' $ac_executable_extensions; do - if as_fn_executable_p "$as_dir$ac_prog$ac_exec_ext"; then + if as_fn_executable_p "$as_dir/$ac_prog$ac_exec_ext"; then if test $ac_prog = install && - grep dspmsg "$as_dir$ac_prog$ac_exec_ext" >/dev/null 2>&1; then + grep dspmsg "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then # AIX install. It has an incompatible calling convention. : elif test $ac_prog = install && - grep pwplus "$as_dir$ac_prog$ac_exec_ext" >/dev/null 2>&1; then + grep pwplus "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then # program-specific install script used by HP pwplus--don't use. : else @@ -2915,12 +2519,12 @@ case $as_dir in #(( echo one > conftest.one echo two > conftest.two mkdir conftest.dir - if "$as_dir$ac_prog$ac_exec_ext" -c conftest.one conftest.two "`pwd`/conftest.dir/" && + if "$as_dir/$ac_prog$ac_exec_ext" -c conftest.one conftest.two "`pwd`/conftest.dir" && test -s conftest.one && test -s conftest.two && test -s conftest.dir/conftest.one && test -s conftest.dir/conftest.two then - ac_cv_path_install="$as_dir$ac_prog$ac_exec_ext -c" + ac_cv_path_install="$as_dir/$ac_prog$ac_exec_ext -c" break 3 fi fi @@ -2934,10 +2538,9 @@ esac IFS=$as_save_IFS rm -rf conftest.one conftest.two conftest.dir - ;; -esac + fi - if test ${ac_cv_path_install+y}; then + if test "${ac_cv_path_install+set}" = set; then INSTALL=$ac_cv_path_install else # As a last resort, use the slow shell script. Don't cache a @@ -2947,8 +2550,8 @@ fi INSTALL=$ac_install_sh fi fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $INSTALL" >&5 -printf "%s\n" "$INSTALL" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $INSTALL" >&5 +$as_echo "$INSTALL" >&6; } # Use test -z because SunOS4 sh mishandles braces in ${var-val}. # It thinks the first close brace ends the variable substitution. @@ -2958,15 +2561,14 @@ test -z "$INSTALL_SCRIPT" && INSTALL_SCRIPT='${INSTALL}' test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644' -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether ${MAKE-make} sets \$(MAKE)" >&5 -printf %s "checking whether ${MAKE-make} sets \$(MAKE)... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ${MAKE-make} sets \$(MAKE)" >&5 +$as_echo_n "checking whether ${MAKE-make} sets \$(MAKE)... " >&6; } set x ${MAKE-make} -ac_make=`printf "%s\n" "$2" | sed 's/+/p/g; s/[^a-zA-Z0-9_]/_/g'` -if eval test \${ac_cv_prog_make_${ac_make}_set+y} -then : - printf %s "(cached) " >&6 -else case e in #( - e) cat >conftest.make <<\_ACEOF +ac_make=`$as_echo "$2" | sed 's/+/p/g; s/[^a-zA-Z0-9_]/_/g'` +if eval \${ac_cv_prog_make_${ac_make}_set+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat >conftest.make <<\_ACEOF SHELL = /bin/sh all: @echo '@@@%%%=$(MAKE)=@@@%%%' @@ -2978,28 +2580,26 @@ case `${MAKE-make} -f conftest.make 2>/dev/null` in *) eval ac_cv_prog_make_${ac_make}_set=no;; esac -rm -f conftest.make ;; -esac +rm -f conftest.make fi if eval test \$ac_cv_prog_make_${ac_make}_set = yes; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 -printf "%s\n" "yes" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } SET_MAKE= else - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 -printf "%s\n" "no" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } SET_MAKE="MAKE=${MAKE-make}" fi # Extract the first word of "true", so it can be a program name with args. set dummy true; ac_word=$2 -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 -printf %s "checking for $ac_word... " >&6; } -if test ${ac_cv_path_TRUE+y} -then : - printf %s "(cached) " >&6 -else case e in #( - e) case $TRUE in +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_TRUE+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $TRUE in [\\/]* | ?:[\\/]*) ac_cv_path_TRUE="$TRUE" # Let the user override the test with a path. ;; @@ -3009,15 +2609,11 @@ as_dummy="$PATH:/bin:/usr/bin" for as_dir in $as_dummy do IFS=$as_save_IFS - case $as_dir in #((( - '') as_dir=./ ;; - */) ;; - *) as_dir=$as_dir/ ;; - esac + test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do - if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then - ac_cv_path_TRUE="$as_dir$ac_word$ac_exec_ext" - printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_TRUE="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done @@ -3026,27 +2622,25 @@ IFS=$as_save_IFS test -z "$ac_cv_path_TRUE" && ac_cv_path_TRUE="true" ;; -esac ;; esac fi TRUE=$ac_cv_path_TRUE if test -n "$TRUE"; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $TRUE" >&5 -printf "%s\n" "$TRUE" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $TRUE" >&5 +$as_echo "$TRUE" >&6; } else - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 -printf "%s\n" "no" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } fi # Find compiler, allow alternatives to gcc -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for --without-gcc" >&5 -printf %s "checking for --without-gcc... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for --without-gcc" >&5 +$as_echo_n "checking for --without-gcc... " >&6; } # Check whether --with-gcc was given. -if test ${with_gcc+y} -then : +if test "${with_gcc+set}" = set; then : withval=$with_gcc; case $withval in no) CC=cc @@ -3056,13 +2650,12 @@ then : *) CC=$withval without_gcc=$withval;; esac -else case e in #( - e) without_gcc=no; ;; -esac +else + without_gcc=no; fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $without_gcc" >&5 -printf "%s\n" "$without_gcc" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $without_gcc" >&5 +$as_echo "$without_gcc" >&6; } # If the user switches compilers, we can't believe the cache if test ! -z "$ac_cv_prog_CC" -a ! -z "$CC" -a "$CC" != "$ac_cv_prog_CC" @@ -3071,15 +2664,6 @@ then (it is also a good idea to do 'make clean' before compiling)" "$LINENO" 5 fi - - - - - - - - - ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' @@ -3088,44 +2672,38 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args. set dummy ${ac_tool_prefix}gcc; ac_word=$2 -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 -printf %s "checking for $ac_word... " >&6; } -if test ${ac_cv_prog_CC+y} -then : - printf %s "(cached) " >&6 -else case e in #( - e) if test -n "$CC"; then +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS - case $as_dir in #((( - '') as_dir=./ ;; - */) ;; - *) as_dir=$as_dir/ ;; - esac + test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do - if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_CC="${ac_tool_prefix}gcc" - printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS -fi ;; -esac +fi fi CC=$ac_cv_prog_CC if test -n "$CC"; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 -printf "%s\n" "$CC" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } else - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 -printf "%s\n" "no" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } fi @@ -3134,44 +2712,38 @@ if test -z "$ac_cv_prog_CC"; then ac_ct_CC=$CC # Extract the first word of "gcc", so it can be a program name with args. set dummy gcc; ac_word=$2 -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 -printf %s "checking for $ac_word... " >&6; } -if test ${ac_cv_prog_ac_ct_CC+y} -then : - printf %s "(cached) " >&6 -else case e in #( - e) if test -n "$ac_ct_CC"; then +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_CC"; then ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS - case $as_dir in #((( - '') as_dir=./ ;; - */) ;; - *) as_dir=$as_dir/ ;; - esac + test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do - if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_CC="gcc" - printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS -fi ;; -esac +fi fi ac_ct_CC=$ac_cv_prog_ac_ct_CC if test -n "$ac_ct_CC"; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 -printf "%s\n" "$ac_ct_CC" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 +$as_echo "$ac_ct_CC" >&6; } else - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 -printf "%s\n" "no" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } fi if test "x$ac_ct_CC" = x; then @@ -3179,8 +2751,8 @@ fi else case $cross_compiling:$ac_tool_warned in yes:) -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 -printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac CC=$ac_ct_CC @@ -3193,44 +2765,38 @@ if test -z "$CC"; then if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args. set dummy ${ac_tool_prefix}cc; ac_word=$2 -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 -printf %s "checking for $ac_word... " >&6; } -if test ${ac_cv_prog_CC+y} -then : - printf %s "(cached) " >&6 -else case e in #( - e) if test -n "$CC"; then +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS - case $as_dir in #((( - '') as_dir=./ ;; - */) ;; - *) as_dir=$as_dir/ ;; - esac + test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do - if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_CC="${ac_tool_prefix}cc" - printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS -fi ;; -esac +fi fi CC=$ac_cv_prog_CC if test -n "$CC"; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 -printf "%s\n" "$CC" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } else - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 -printf "%s\n" "no" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } fi @@ -3239,13 +2805,12 @@ fi if test -z "$CC"; then # Extract the first word of "cc", so it can be a program name with args. set dummy cc; ac_word=$2 -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 -printf %s "checking for $ac_word... " >&6; } -if test ${ac_cv_prog_CC+y} -then : - printf %s "(cached) " >&6 -else case e in #( - e) if test -n "$CC"; then +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else ac_prog_rejected=no @@ -3253,19 +2818,15 @@ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS - case $as_dir in #((( - '') as_dir=./ ;; - */) ;; - *) as_dir=$as_dir/ ;; - esac + test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do - if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then - if test "$as_dir$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + if test "$as_dir/$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then ac_prog_rejected=yes continue fi ac_cv_prog_CC="cc" - printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done @@ -3281,19 +2842,18 @@ if test $ac_prog_rejected = yes; then # However, it has the same basename, so the bogon will be chosen # first if we set CC to just the basename; use the full file name. shift - ac_cv_prog_CC="$as_dir$ac_word${1+' '}$@" + ac_cv_prog_CC="$as_dir/$ac_word${1+' '}$@" fi fi -fi ;; -esac +fi fi CC=$ac_cv_prog_CC if test -n "$CC"; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 -printf "%s\n" "$CC" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } else - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 -printf "%s\n" "no" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } fi @@ -3304,44 +2864,38 @@ if test -z "$CC"; then do # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. set dummy $ac_tool_prefix$ac_prog; ac_word=$2 -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 -printf %s "checking for $ac_word... " >&6; } -if test ${ac_cv_prog_CC+y} -then : - printf %s "(cached) " >&6 -else case e in #( - e) if test -n "$CC"; then +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS - case $as_dir in #((( - '') as_dir=./ ;; - */) ;; - *) as_dir=$as_dir/ ;; - esac + test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do - if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_CC="$ac_tool_prefix$ac_prog" - printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS -fi ;; -esac +fi fi CC=$ac_cv_prog_CC if test -n "$CC"; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 -printf "%s\n" "$CC" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } else - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 -printf "%s\n" "no" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } fi @@ -3354,44 +2908,38 @@ if test -z "$CC"; then do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 -printf %s "checking for $ac_word... " >&6; } -if test ${ac_cv_prog_ac_ct_CC+y} -then : - printf %s "(cached) " >&6 -else case e in #( - e) if test -n "$ac_ct_CC"; then +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_CC"; then ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS - case $as_dir in #((( - '') as_dir=./ ;; - */) ;; - *) as_dir=$as_dir/ ;; - esac + test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do - if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_CC="$ac_prog" - printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS -fi ;; -esac +fi fi ac_ct_CC=$ac_cv_prog_ac_ct_CC if test -n "$ac_ct_CC"; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 -printf "%s\n" "$ac_ct_CC" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 +$as_echo "$ac_ct_CC" >&6; } else - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 -printf "%s\n" "no" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } fi @@ -3403,8 +2951,8 @@ done else case $cross_compiling:$ac_tool_warned in yes:) -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 -printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac CC=$ac_ct_CC @@ -3412,131 +2960,25 @@ esac fi fi -if test -z "$CC"; then - if test -n "$ac_tool_prefix"; then - # Extract the first word of "${ac_tool_prefix}clang", so it can be a program name with args. -set dummy ${ac_tool_prefix}clang; ac_word=$2 -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 -printf %s "checking for $ac_word... " >&6; } -if test ${ac_cv_prog_CC+y} -then : - printf %s "(cached) " >&6 -else case e in #( - e) if test -n "$CC"; then - ac_cv_prog_CC="$CC" # Let the user override the test. -else -as_save_IFS=$IFS; IFS=$PATH_SEPARATOR -for as_dir in $PATH -do - IFS=$as_save_IFS - case $as_dir in #((( - '') as_dir=./ ;; - */) ;; - *) as_dir=$as_dir/ ;; - esac - for ac_exec_ext in '' $ac_executable_extensions; do - if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then - ac_cv_prog_CC="${ac_tool_prefix}clang" - printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 - break 2 - fi -done - done -IFS=$as_save_IFS - -fi ;; -esac -fi -CC=$ac_cv_prog_CC -if test -n "$CC"; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 -printf "%s\n" "$CC" >&6; } -else - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 -printf "%s\n" "no" >&6; } -fi - - -fi -if test -z "$ac_cv_prog_CC"; then - ac_ct_CC=$CC - # Extract the first word of "clang", so it can be a program name with args. -set dummy clang; ac_word=$2 -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 -printf %s "checking for $ac_word... " >&6; } -if test ${ac_cv_prog_ac_ct_CC+y} -then : - printf %s "(cached) " >&6 -else case e in #( - e) if test -n "$ac_ct_CC"; then - ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. -else -as_save_IFS=$IFS; IFS=$PATH_SEPARATOR -for as_dir in $PATH -do - IFS=$as_save_IFS - case $as_dir in #((( - '') as_dir=./ ;; - */) ;; - *) as_dir=$as_dir/ ;; - esac - for ac_exec_ext in '' $ac_executable_extensions; do - if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then - ac_cv_prog_ac_ct_CC="clang" - printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 - break 2 - fi -done - done -IFS=$as_save_IFS - -fi ;; -esac -fi -ac_ct_CC=$ac_cv_prog_ac_ct_CC -if test -n "$ac_ct_CC"; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 -printf "%s\n" "$ac_ct_CC" >&6; } -else - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 -printf "%s\n" "no" >&6; } -fi - - if test "x$ac_ct_CC" = x; then - CC="" - else - case $cross_compiling:$ac_tool_warned in -yes:) -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 -printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} -ac_tool_warned=yes ;; -esac - CC=$ac_ct_CC - fi -else - CC="$ac_cv_prog_CC" -fi -fi - -test -z "$CC" && { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 -printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} +test -z "$CC" && { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "no acceptable C compiler found in \$PATH -See 'config.log' for more details" "$LINENO" 5; } +See \`config.log' for more details" "$LINENO" 5; } # Provide some information about the compiler. -printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5 +$as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5 set X $ac_compile ac_compiler=$2 -for ac_option in --version -v -V -qversion -version; do +for ac_option in --version -v -V -qversion; do { { ac_try="$ac_compiler $ac_option >&5" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" -printf "%s\n" "$ac_try_echo"; } >&5 +$as_echo "$ac_try_echo"; } >&5 (eval "$ac_compiler $ac_option >&5") 2>conftest.err ac_status=$? if test -s conftest.err; then @@ -3546,7 +2988,7 @@ printf "%s\n" "$ac_try_echo"; } >&5 cat conftest.er1 >&5 fi rm -f conftest.er1 conftest.err - printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } done @@ -3554,7 +2996,7 @@ cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int -main (void) +main () { ; @@ -3566,9 +3008,9 @@ ac_clean_files="$ac_clean_files a.out a.out.dSYM a.exe b.out" # Try to create an executable without -o first, disregard a.out. # It will help us diagnose broken compilers, and finding out an intuition # of exeext. -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the C compiler works" >&5 -printf %s "checking whether the C compiler works... " >&6; } -ac_link_default=`printf "%s\n" "$ac_link" | sed 's/ -o *conftest[^ ]*//'` +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the C compiler works" >&5 +$as_echo_n "checking whether the C compiler works... " >&6; } +ac_link_default=`$as_echo "$ac_link" | sed 's/ -o *conftest[^ ]*//'` # The possible output files: ac_files="a.out conftest.exe conftest a.exe a_out.exe b.out conftest.*" @@ -3589,14 +3031,13 @@ case "(($ac_try" in *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" -printf "%s\n" "$ac_try_echo"; } >&5 +$as_echo "$ac_try_echo"; } >&5 (eval "$ac_link_default") 2>&5 ac_status=$? - printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 - test $ac_status = 0; } -then : - # Autoconf-2.13 could set the ac_cv_exeext variable to 'no'. -# So ignore a value of 'no', otherwise this would lead to 'EXEEXT = no' + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then : + # Autoconf-2.13 could set the ac_cv_exeext variable to `no'. +# So ignore a value of `no', otherwise this would lead to `EXEEXT = no' # in a Makefile. We should not override ac_cv_exeext if it was cached, # so that the user can short-circuit this test for compilers unknown to # Autoconf. @@ -3611,12 +3052,12 @@ do # certainly right. break;; *.* ) - if test ${ac_cv_exeext+y} && test "$ac_cv_exeext" != no; + if test "${ac_cv_exeext+set}" = set && test "$ac_cv_exeext" != no; then :; else ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` fi # We set ac_cv_exeext here because the later test for it is not - # safe: cross compilers may not add the suffix if given an '-o' + # safe: cross compilers may not add the suffix if given an `-o' # argument, so we may need to know it at that point already. # Even if this section looks crufty: it has the advantage of # actually working. @@ -3627,52 +3068,48 @@ do done test "$ac_cv_exeext" = no && ac_cv_exeext= -else case e in #( - e) ac_file='' ;; -esac +else + ac_file='' fi -if test -z "$ac_file" -then : - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 -printf "%s\n" "no" >&6; } -printf "%s\n" "$as_me: failed program was:" >&5 +if test -z "$ac_file"; then : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +$as_echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 -{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 -printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} +{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error 77 "C compiler cannot create executables -See 'config.log' for more details" "$LINENO" 5; } -else case e in #( - e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 -printf "%s\n" "yes" >&6; } ;; -esac +See \`config.log' for more details" "$LINENO" 5; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for C compiler default output file name" >&5 -printf %s "checking for C compiler default output file name... " >&6; } -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_file" >&5 -printf "%s\n" "$ac_file" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler default output file name" >&5 +$as_echo_n "checking for C compiler default output file name... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_file" >&5 +$as_echo "$ac_file" >&6; } ac_exeext=$ac_cv_exeext rm -f -r a.out a.out.dSYM a.exe conftest$ac_cv_exeext b.out ac_clean_files=$ac_clean_files_save -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for suffix of executables" >&5 -printf %s "checking for suffix of executables... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of executables" >&5 +$as_echo_n "checking for suffix of executables... " >&6; } if { { ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" -printf "%s\n" "$ac_try_echo"; } >&5 +$as_echo "$ac_try_echo"; } >&5 (eval "$ac_link") 2>&5 ac_status=$? - printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 - test $ac_status = 0; } -then : - # If both 'conftest.exe' and 'conftest' are 'present' (well, observable) -# catch 'conftest.exe'. For instance with Cygwin, 'ls conftest' will -# work properly (i.e., refer to 'conftest.exe'), while it won't with -# 'rm'. + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then : + # If both `conftest.exe' and `conftest' are `present' (well, observable) +# catch `conftest.exe'. For instance with Cygwin, `ls conftest' will +# work properly (i.e., refer to `conftest.exe'), while it won't with +# `rm'. for ac_file in conftest.exe conftest conftest.*; do test -f "$ac_file" || continue case $ac_file in @@ -3682,16 +3119,15 @@ for ac_file in conftest.exe conftest conftest.*; do * ) break;; esac done -else case e in #( - e) { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 -printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} +else + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "cannot compute suffix of executables: cannot compile and link -See 'config.log' for more details" "$LINENO" 5; } ;; -esac +See \`config.log' for more details" "$LINENO" 5; } fi rm -f conftest conftest$ac_cv_exeext -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5 -printf "%s\n" "$ac_cv_exeext" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5 +$as_echo "$ac_cv_exeext" >&6; } rm -f conftest.$ac_ext EXEEXT=$ac_cv_exeext @@ -3700,11 +3136,9 @@ cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int -main (void) +main () { FILE *f = fopen ("conftest.out", "w"); - if (!f) - return 1; return ferror (f) || fclose (f) != 0; ; @@ -3714,8 +3148,8 @@ _ACEOF ac_clean_files="$ac_clean_files conftest.out" # Check that the compiler produces executables we can run. If not, either # the compiler is broken, or we cross compile. -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether we are cross compiling" >&5 -printf %s "checking whether we are cross compiling... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are cross compiling" >&5 +$as_echo_n "checking whether we are cross compiling... " >&6; } if test "$cross_compiling" != yes; then { { ac_try="$ac_link" case "(($ac_try" in @@ -3723,10 +3157,10 @@ case "(($ac_try" in *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" -printf "%s\n" "$ac_try_echo"; } >&5 +$as_echo "$ac_try_echo"; } >&5 (eval "$ac_link") 2>&5 ac_status=$? - printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } if { ac_try='./conftest$ac_cv_exeext' { { case "(($ac_try" in @@ -3734,41 +3168,39 @@ printf "%s\n" "$ac_try_echo"; } >&5 *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" -printf "%s\n" "$ac_try_echo"; } >&5 +$as_echo "$ac_try_echo"; } >&5 (eval "$ac_try") 2>&5 ac_status=$? - printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; }; then cross_compiling=no else if test "$cross_compiling" = maybe; then cross_compiling=yes else - { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 -printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} -as_fn_error 77 "cannot run C compiled programs. -If you meant to cross compile, use '--host'. -See 'config.log' for more details" "$LINENO" 5; } + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "cannot run C compiled programs. +If you meant to cross compile, use \`--host'. +See \`config.log' for more details" "$LINENO" 5; } fi fi fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $cross_compiling" >&5 -printf "%s\n" "$cross_compiling" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $cross_compiling" >&5 +$as_echo "$cross_compiling" >&6; } -rm -f conftest.$ac_ext conftest$ac_cv_exeext \ - conftest.o conftest.obj conftest.out +rm -f conftest.$ac_ext conftest$ac_cv_exeext conftest.out ac_clean_files=$ac_clean_files_save -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5 -printf %s "checking for suffix of object files... " >&6; } -if test ${ac_cv_objext+y} -then : - printf %s "(cached) " >&6 -else case e in #( - e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5 +$as_echo_n "checking for suffix of object files... " >&6; } +if ${ac_cv_objext+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int -main (void) +main () { ; @@ -3782,12 +3214,11 @@ case "(($ac_try" in *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" -printf "%s\n" "$ac_try_echo"; } >&5 +$as_echo "$ac_try_echo"; } >&5 (eval "$ac_compile") 2>&5 ac_status=$? - printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 - test $ac_status = 0; } -then : + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then : for ac_file in conftest.o conftest.obj conftest.*; do test -f "$ac_file" || continue; case $ac_file in @@ -3796,34 +3227,31 @@ then : break;; esac done -else case e in #( - e) printf "%s\n" "$as_me: failed program was:" >&5 +else + $as_echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 -{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 -printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} +{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "cannot compute suffix of object files: cannot compile -See 'config.log' for more details" "$LINENO" 5; } ;; -esac +See \`config.log' for more details" "$LINENO" 5; } fi -rm -f conftest.$ac_cv_objext conftest.$ac_ext ;; -esac +rm -f conftest.$ac_cv_objext conftest.$ac_ext fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_objext" >&5 -printf "%s\n" "$ac_cv_objext" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_objext" >&5 +$as_echo "$ac_cv_objext" >&6; } OBJEXT=$ac_cv_objext ac_objext=$OBJEXT -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the compiler supports GNU C" >&5 -printf %s "checking whether the compiler supports GNU C... " >&6; } -if test ${ac_cv_c_compiler_gnu+y} -then : - printf %s "(cached) " >&6 -else case e in #( - e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C compiler" >&5 +$as_echo_n "checking whether we are using the GNU C compiler... " >&6; } +if ${ac_cv_c_compiler_gnu+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int -main (void) +main () { #ifndef __GNUC__ choke me @@ -3833,36 +3261,30 @@ main (void) return 0; } _ACEOF -if ac_fn_c_try_compile "$LINENO" -then : +if ac_fn_c_try_compile "$LINENO"; then : ac_compiler_gnu=yes -else case e in #( - e) ac_compiler_gnu=no ;; -esac +else + ac_compiler_gnu=no fi -rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext ac_cv_c_compiler_gnu=$ac_compiler_gnu - ;; -esac -fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5 -printf "%s\n" "$ac_cv_c_compiler_gnu" >&6; } -ac_compiler_gnu=$ac_cv_c_compiler_gnu +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5 +$as_echo "$ac_cv_c_compiler_gnu" >&6; } if test $ac_compiler_gnu = yes; then GCC=yes else GCC= fi -ac_test_CFLAGS=${CFLAGS+y} +ac_test_CFLAGS=${CFLAGS+set} ac_save_CFLAGS=$CFLAGS -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5 -printf %s "checking whether $CC accepts -g... " >&6; } -if test ${ac_cv_prog_cc_g+y} -then : - printf %s "(cached) " >&6 -else case e in #( - e) ac_save_c_werror_flag=$ac_c_werror_flag +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5 +$as_echo_n "checking whether $CC accepts -g... " >&6; } +if ${ac_cv_prog_cc_g+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_save_c_werror_flag=$ac_c_werror_flag ac_c_werror_flag=yes ac_cv_prog_cc_g=no CFLAGS="-g" @@ -3870,63 +3292,57 @@ else case e in #( /* end confdefs.h. */ int -main (void) +main () { ; return 0; } _ACEOF -if ac_fn_c_try_compile "$LINENO" -then : +if ac_fn_c_try_compile "$LINENO"; then : ac_cv_prog_cc_g=yes -else case e in #( - e) CFLAGS="" +else + CFLAGS="" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int -main (void) +main () { ; return 0; } _ACEOF -if ac_fn_c_try_compile "$LINENO" -then : +if ac_fn_c_try_compile "$LINENO"; then : -else case e in #( - e) ac_c_werror_flag=$ac_save_c_werror_flag +else + ac_c_werror_flag=$ac_save_c_werror_flag CFLAGS="-g" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int -main (void) +main () { ; return 0; } _ACEOF -if ac_fn_c_try_compile "$LINENO" -then : +if ac_fn_c_try_compile "$LINENO"; then : ac_cv_prog_cc_g=yes fi -rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; -esac +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi -rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; -esac +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi -rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext - ac_c_werror_flag=$ac_save_c_werror_flag ;; -esac +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_c_werror_flag=$ac_save_c_werror_flag fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5 -printf "%s\n" "$ac_cv_prog_cc_g" >&6; } -if test $ac_test_CFLAGS; then +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5 +$as_echo "$ac_cv_prog_cc_g" >&6; } +if test "$ac_test_CFLAGS" = set; then CFLAGS=$ac_save_CFLAGS elif test $ac_cv_prog_cc_g = yes; then if test "$GCC" = yes; then @@ -3941,153 +3357,94 @@ else CFLAGS= fi fi -ac_prog_cc_stdc=no -if test x$ac_prog_cc_stdc = xno -then : - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C11 features" >&5 -printf %s "checking for $CC option to enable C11 features... " >&6; } -if test ${ac_cv_prog_cc_c11+y} -then : - printf %s "(cached) " >&6 -else case e in #( - e) ac_cv_prog_cc_c11=no +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C89" >&5 +$as_echo_n "checking for $CC option to accept ISO C89... " >&6; } +if ${ac_cv_prog_cc_c89+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_cv_prog_cc_c89=no ac_save_CC=$CC cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ -$ac_c_conftest_c11_program -_ACEOF -for ac_arg in '' -std=gnu11 -do - CC="$ac_save_CC $ac_arg" - if ac_fn_c_try_compile "$LINENO" -then : - ac_cv_prog_cc_c11=$ac_arg -fi -rm -f core conftest.err conftest.$ac_objext conftest.beam - test "x$ac_cv_prog_cc_c11" != "xno" && break -done -rm -f conftest.$ac_ext -CC=$ac_save_CC ;; -esac -fi +#include +#include +struct stat; +/* Most of the following tests are stolen from RCS 5.7's src/conf.sh. */ +struct buf { int x; }; +FILE * (*rcsopen) (struct buf *, struct stat *, int); +static char *e (p, i) + char **p; + int i; +{ + return p[i]; +} +static char *f (char * (*g) (char **, int), char **p, ...) +{ + char *s; + va_list v; + va_start (v,p); + s = g (p, va_arg (v,int)); + va_end (v); + return s; +} -if test "x$ac_cv_prog_cc_c11" = xno -then : - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 -printf "%s\n" "unsupported" >&6; } -else case e in #( - e) if test "x$ac_cv_prog_cc_c11" = x -then : - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 -printf "%s\n" "none needed" >&6; } -else case e in #( - e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c11" >&5 -printf "%s\n" "$ac_cv_prog_cc_c11" >&6; } - CC="$CC $ac_cv_prog_cc_c11" ;; -esac -fi - ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c11 - ac_prog_cc_stdc=c11 ;; -esac -fi -fi -if test x$ac_prog_cc_stdc = xno -then : - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C99 features" >&5 -printf %s "checking for $CC option to enable C99 features... " >&6; } -if test ${ac_cv_prog_cc_c99+y} -then : - printf %s "(cached) " >&6 -else case e in #( - e) ac_cv_prog_cc_c99=no -ac_save_CC=$CC -cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -$ac_c_conftest_c99_program -_ACEOF -for ac_arg in '' -std=gnu99 -std=c99 -c99 -qlanglvl=extc1x -qlanglvl=extc99 -AC99 -D_STDC_C99= -do - CC="$ac_save_CC $ac_arg" - if ac_fn_c_try_compile "$LINENO" -then : - ac_cv_prog_cc_c99=$ac_arg -fi -rm -f core conftest.err conftest.$ac_objext conftest.beam - test "x$ac_cv_prog_cc_c99" != "xno" && break -done -rm -f conftest.$ac_ext -CC=$ac_save_CC ;; -esac -fi +/* OSF 4.0 Compaq cc is some sort of almost-ANSI by default. It has + function prototypes and stuff, but not '\xHH' hex character constants. + These don't provoke an error unfortunately, instead are silently treated + as 'x'. The following induces an error, until -std is added to get + proper ANSI mode. Curiously '\x00'!='x' always comes out true, for an + array size at least. It's necessary to write '\x00'==0 to get something + that's true only with -std. */ +int osf4_cc_array ['\x00' == 0 ? 1 : -1]; -if test "x$ac_cv_prog_cc_c99" = xno -then : - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 -printf "%s\n" "unsupported" >&6; } -else case e in #( - e) if test "x$ac_cv_prog_cc_c99" = x -then : - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 -printf "%s\n" "none needed" >&6; } -else case e in #( - e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c99" >&5 -printf "%s\n" "$ac_cv_prog_cc_c99" >&6; } - CC="$CC $ac_cv_prog_cc_c99" ;; -esac -fi - ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c99 - ac_prog_cc_stdc=c99 ;; -esac -fi -fi -if test x$ac_prog_cc_stdc = xno -then : - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C89 features" >&5 -printf %s "checking for $CC option to enable C89 features... " >&6; } -if test ${ac_cv_prog_cc_c89+y} -then : - printf %s "(cached) " >&6 -else case e in #( - e) ac_cv_prog_cc_c89=no -ac_save_CC=$CC -cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -$ac_c_conftest_c89_program +/* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters + inside strings and character constants. */ +#define FOO(x) 'x' +int xlc6_cc_array[FOO(a) == 'x' ? 1 : -1]; + +int test (int i, double x); +struct s1 {int (*f) (int a);}; +struct s2 {int (*f) (double a);}; +int pairnames (int, char **, FILE *(*)(struct buf *, struct stat *, int), int, int); +int argc; +char **argv; +int +main () +{ +return f (e, argv, 0) != argv[0] || f (e, argv, 1) != argv[1]; + ; + return 0; +} _ACEOF -for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__" +for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std \ + -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__" do CC="$ac_save_CC $ac_arg" - if ac_fn_c_try_compile "$LINENO" -then : + if ac_fn_c_try_compile "$LINENO"; then : ac_cv_prog_cc_c89=$ac_arg fi -rm -f core conftest.err conftest.$ac_objext conftest.beam +rm -f core conftest.err conftest.$ac_objext test "x$ac_cv_prog_cc_c89" != "xno" && break done rm -f conftest.$ac_ext -CC=$ac_save_CC ;; -esac -fi +CC=$ac_save_CC -if test "x$ac_cv_prog_cc_c89" = xno -then : - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 -printf "%s\n" "unsupported" >&6; } -else case e in #( - e) if test "x$ac_cv_prog_cc_c89" = x -then : - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 -printf "%s\n" "none needed" >&6; } -else case e in #( - e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5 -printf "%s\n" "$ac_cv_prog_cc_c89" >&6; } - CC="$CC $ac_cv_prog_cc_c89" ;; -esac fi - ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c89 - ac_prog_cc_stdc=c89 ;; +# AC_CACHE_VAL +case "x$ac_cv_prog_cc_c89" in + x) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 +$as_echo "none needed" >&6; } ;; + xno) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 +$as_echo "unsupported" >&6; } ;; + *) + CC="$CC $ac_cv_prog_cc_c89" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5 +$as_echo "$ac_cv_prog_cc_c89" >&6; } ;; esac -fi +if test "x$ac_cv_prog_cc_c89" != xno; then : + fi ac_ext=c @@ -4114,13 +3471,12 @@ then fi # We better be able to execute interpreters -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether #! works in shell scripts" >&5 -printf %s "checking whether #! works in shell scripts... " >&6; } -if test ${ac_cv_sys_interpreter+y} -then : - printf %s "(cached) " >&6 -else case e in #( - e) echo '#! /bin/cat +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether #! works in shell scripts" >&5 +$as_echo_n "checking whether #! works in shell scripts... " >&6; } +if ${ac_cv_sys_interpreter+:} false; then : + $as_echo_n "(cached) " >&6 +else + echo '#! /bin/cat exit 69 ' >conftest chmod u+x conftest @@ -4130,11 +3486,10 @@ if test $? -ne 69; then else ac_cv_sys_interpreter=no fi -rm -f conftest ;; -esac +rm -f conftest fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_sys_interpreter" >&5 -printf "%s\n" "$ac_cv_sys_interpreter" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_sys_interpreter" >&5 +$as_echo "$ac_cv_sys_interpreter" >&6; } interpval=$ac_cv_sys_interpreter if test "$ac_cv_sys_interpreter" != "yes" @@ -4149,12 +3504,11 @@ fi # Check for an alternate data directory, separate from installation dir. default_var_prefix="/var/mailman" -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for --with-var-prefix" >&5 -printf %s "checking for --with-var-prefix... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for --with-var-prefix" >&5 +$as_echo_n "checking for --with-var-prefix... " >&6; } # Check whether --with-var-prefix was given. -if test ${with_var_prefix+y} -then : +if test "${with_var_prefix+set}" = set; then : withval=$with_var_prefix; fi @@ -4163,15 +3517,14 @@ case "$with_var_prefix" in ""|no) VAR_PREFIX="$prefix"; ans="no";; *) VAR_PREFIX="$with_var_prefix"; ans=$VAR_PREFIX; esac -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ans" >&5 -printf "%s\n" "$ans" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ans" >&5 +$as_echo "$ans" >&6; } -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for --with-permcheck" >&5 -printf %s "checking for --with-permcheck... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for --with-permcheck" >&5 +$as_echo_n "checking for --with-permcheck... " >&6; } # Check whether --with-permcheck was given. -if test ${with_permcheck+y} -then : +if test "${with_permcheck+set}" = set; then : withval=$with_permcheck; fi @@ -4179,8 +3532,8 @@ if test -z "$with_permcheck" then with_permcheck="yes" fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $with_permcheck" >&5 -printf "%s\n" "$with_permcheck" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_permcheck" >&5 +$as_echo "$with_permcheck" >&6; } # Now make sure that $prefix is set up correctly. It must be group # owned by the target group, it must have the group sticky bit set, and # it must be a+rx @@ -4200,12 +3553,11 @@ fi # Check for some other uid to use than `mailman' -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for --with-username" >&5 -printf %s "checking for --with-username... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for --with-username" >&5 +$as_echo_n "checking for --with-username... " >&6; } # Check whether --with-username was given. -if test ${with_username+y} -then : +if test "${with_username+set}" = set; then : withval=$with_username; fi @@ -4215,13 +3567,13 @@ then with_username="mailman" fi USERNAME=$with_username -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $USERNAME" >&5 -printf "%s\n" "$USERNAME" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $USERNAME" >&5 +$as_echo "$USERNAME" >&6; } # User `mailman' must exist -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for user name $USERNAME" >&5 -printf %s "checking for user name $USERNAME... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for user name \"$USERNAME\"" >&5 +$as_echo_n "checking for user name \"$USERNAME\"... " >&6; } # MAILMAN_USER == variable name # $USERNAME == user id to check for @@ -4242,10 +3594,11 @@ for user in "$USERNAME".split(): break except KeyError: uname = '' -with open("conftest.out", "w") as fp: - fp.write("%s\n" % uname) +fp = open("conftest.out", "w") +fp.write("%s\n" % uname) +fp.close() EOF - $with_python conftest.py + $PYTHON conftest.py MAILMAN_USER=`cat conftest.out` fi @@ -4255,23 +3608,22 @@ then if test "$with_permcheck" = "yes" then as_fn_error $? " -***** No $USERNAME user found! -***** Your system must have a $USERNAME user defined +***** No \"$USERNAME\" user found! +***** Your system must have a \"$USERNAME\" user defined ***** (usually in your /etc/passwd file). Please see the INSTALL ***** file for details." "$LINENO" 5 fi fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: okay" >&5 -printf "%s\n" "okay" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: okay" >&5 +$as_echo "okay" >&6; } # Check for some other gid to use than `mailman' -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for --with-groupname" >&5 -printf %s "checking for --with-groupname... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for --with-groupname" >&5 +$as_echo_n "checking for --with-groupname... " >&6; } # Check whether --with-groupname was given. -if test ${with_groupname+y} -then : +if test "${with_groupname+set}" = set; then : withval=$with_groupname; fi @@ -4281,14 +3633,14 @@ then with_groupname="mailman" fi GROUPNAME=$with_groupname -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $GROUPNAME" >&5 -printf "%s\n" "$GROUPNAME" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $GROUPNAME" >&5 +$as_echo "$GROUPNAME" >&6; } # Target group must exist -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for group name $GROUPNAME" >&5 -printf %s "checking for group name $GROUPNAME... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for group name \"$GROUPNAME\"" >&5 +$as_echo_n "checking for group name \"$GROUPNAME\"... " >&6; } # MAILMAN_GROUP == variable name # $GROUPNAME == user id to check for @@ -4309,10 +3661,11 @@ for group in "$GROUPNAME".split(): break except KeyError: gname = '' -with open("conftest.out", "w") as fp: - fp.write("%s\n" % gname) +fp = open("conftest.out", "w") +fp.write("%s\n" % gname) +fp.close() EOF - $with_python conftest.py + $PYTHON conftest.py MAILMAN_GROUP=`cat conftest.out` fi @@ -4322,18 +3675,18 @@ then if test "$with_permcheck" = "yes" then as_fn_error $? " -***** No $GROUPNAME group found! -***** Your system must have a $GROUPNAME group defined +***** No \"$GROUPNAME\" group found! +***** Your system must have a \"$GROUPNAME\" group defined ***** (usually in your /etc/group file). Please see the INSTALL ***** file for details." "$LINENO" 5 fi fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: okay" >&5 -printf "%s\n" "okay" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: okay" >&5 +$as_echo "okay" >&6; } -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking permissions on $prefixcheck" >&5 -printf %s "checking permissions on $prefixcheck... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking permissions on $prefixcheck" >&5 +$as_echo_n "checking permissions on $prefixcheck... " >&6; } cat > conftest.py <&5 -printf "%s\n" "$status" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $status" >&5 +$as_echo "$status" >&6; } # Now find the UIDs and GIDs # Support --with-mail-gid and --with-cgi-gid -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for mail wrapper group; i.e. --with-mail-gid" >&5 -printf %s "checking for mail wrapper group; i.e. --with-mail-gid... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for mail wrapper group; i.e. --with-mail-gid" >&5 +$as_echo_n "checking for mail wrapper group; i.e. --with-mail-gid... " >&6; } # Check whether --with-mail-gid was given. -if test ${with_mail_gid+y} -then : +if test "${with_mail_gid+set}" = set; then : withval=$with_mail_gid; fi @@ -4421,10 +3774,11 @@ for group in "$with_mail_gid".split(): break except KeyError: gname = '' -with open("conftest.out", "w") as fp: - fp.write("%s\n" % gname) +fp = open("conftest.out", "w") +fp.write("%s\n" % gname) +fp.close() EOF - $with_python conftest.py + $PYTHON conftest.py MAIL_GROUP=`cat conftest.out` fi @@ -4434,7 +3788,7 @@ then if test "$with_permcheck" = "yes" then as_fn_error $? " -***** No group name $with_mail_gid found for the mail wrapper program. +***** No group name \"$with_mail_gid\" found for the mail wrapper program. ***** This is the group that your mail server will use to run Mailman's ***** programs. You should specify an existing group with the ***** --with-mail-gid configure option, or use --without-permcheck to @@ -4444,16 +3798,15 @@ then MAIL_GROUP=$with_mail_gid fi fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $MAIL_GROUP" >&5 -printf "%s\n" "$MAIL_GROUP" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $MAIL_GROUP" >&5 +$as_echo "$MAIL_GROUP" >&6; } -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for CGI wrapper group; i.e. --with-cgi-gid" >&5 -printf %s "checking for CGI wrapper group; i.e. --with-cgi-gid... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for CGI wrapper group; i.e. --with-cgi-gid" >&5 +$as_echo_n "checking for CGI wrapper group; i.e. --with-cgi-gid... " >&6; } # Check whether --with-cgi-gid was given. -if test ${with_cgi_gid+y} -then : +if test "${with_cgi_gid+set}" = set; then : withval=$with_cgi_gid; fi @@ -4482,10 +3835,11 @@ for group in "$with_cgi_gid".split(): break except KeyError: gname = '' -with open("conftest.out", "w") as fp: - fp.write("%s\n" % gname) +fp = open("conftest.out", "w") +fp.write("%s\n" % gname) +fp.close() EOF - $with_python conftest.py + $PYTHON conftest.py CGI_GROUP=`cat conftest.out` fi @@ -4495,7 +3849,7 @@ then if test "$with_permcheck" = "yes" then as_fn_error $? " -***** No group name $with_cgi_gid found for the CGI wrapper program. +***** No group name \"$with_cgi_gid\" found for the CGI wrapper program. ***** This is the group that your web server will use to run Mailman's ***** programs. You should specify an existing group with the ***** --with-cgi-gid configure option, or use --without-permcheck to @@ -4505,18 +3859,17 @@ then CGI_GROUP=$with_cgi_gid fi fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CGI_GROUP" >&5 -printf "%s\n" "$CGI_GROUP" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $CGI_GROUP" >&5 +$as_echo "$CGI_GROUP" >&6; } # Check for CGI extensions, required by some Web servers -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for CGI extensions" >&5 -printf %s "checking for CGI extensions... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for CGI extensions" >&5 +$as_echo_n "checking for CGI extensions... " >&6; } # Check whether --with-cgi-ext was given. -if test ${with_cgi_ext+y} -then : +if test "${with_cgi_ext+set}" = set; then : withval=$with_cgi_ext; fi @@ -4527,18 +3880,17 @@ then else CGIEXT=$with_cgi_ext fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $with_cgi_ext" >&5 -printf "%s\n" "$with_cgi_ext" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_cgi_ext" >&5 +$as_echo "$with_cgi_ext" >&6; } # figure out the default mail hostname and url host component -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for --with-mailhost" >&5 -printf %s "checking for --with-mailhost... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for --with-mailhost" >&5 +$as_echo_n "checking for --with-mailhost... " >&6; } # Check whether --with-mailhost was given. -if test ${with_mailhost+y} -then : +if test "${with_mailhost+set}" = set; then : withval=$with_mailhost; fi @@ -4549,16 +3901,15 @@ then else MAILHOST=$with_mailhost fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $with_mailhost" >&5 -printf "%s\n" "$with_mailhost" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_mailhost" >&5 +$as_echo "$with_mailhost" >&6; } -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for --with-urlhost" >&5 -printf %s "checking for --with-urlhost... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for --with-urlhost" >&5 +$as_echo_n "checking for --with-urlhost... " >&6; } # Check whether --with-urlhost was given. -if test ${with_urlhost+y} -then : +if test "${with_urlhost+set}" = set; then : withval=$with_urlhost; fi @@ -4569,8 +3920,8 @@ then else URLHOST=$with_urlhost fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $with_urlhost" >&5 -printf "%s\n" "$with_urlhost" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_urlhost" >&5 +$as_echo "$with_urlhost" >&6; } @@ -4578,50 +3929,44 @@ cat > conftest.py <&5 -printf %s "checking for default mail host name... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for default mail host name" >&5 +$as_echo_n "checking for default mail host name... " >&6; } if test -z "$MAILHOST" then MAILHOST=`sed q conftest.out` fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $MAILHOST" >&5 -printf "%s\n" "$MAILHOST" >&6; } -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for default URL host component" >&5 -printf %s "checking for default URL host component... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $MAILHOST" >&5 +$as_echo "$MAILHOST" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for default URL host component" >&5 +$as_echo_n "checking for default URL host component... " >&6; } if test -z "$URLHOST" then URLHOST=`sed -n '$p' conftest.out` fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $URLHOST" >&5 -printf "%s\n" "$URLHOST" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $URLHOST" >&5 +$as_echo "$URLHOST" >&6; } rm -f conftest.out conftest.py # Checks for libraries. -ac_fn_c_check_func "$LINENO" "strerror" "ac_cv_func_strerror" -if test "x$ac_cv_func_strerror" = xyes -then : - printf "%s\n" "#define HAVE_STRERROR 1" >>confdefs.h - -fi -ac_fn_c_check_func "$LINENO" "setregid" "ac_cv_func_setregid" -if test "x$ac_cv_func_setregid" = xyes -then : - printf "%s\n" "#define HAVE_SETREGID 1" >>confdefs.h - -fi -ac_fn_c_check_func "$LINENO" "syslog" "ac_cv_func_syslog" -if test "x$ac_cv_func_syslog" = xyes -then : - printf "%s\n" "#define HAVE_SYSLOG 1" >>confdefs.h +for ac_func in strerror setregid syslog +do : + as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` +ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var" +if eval test \"x\$"$as_ac_var"\" = x"yes"; then : + cat >>confdefs.h <<_ACEOF +#define `$as_echo "HAVE_$ac_func" | $as_tr_cpp` 1 +_ACEOF fi +done if test $ac_cv_func_syslog = no; then # syslog is not in the default libraries. See if it's in some other. @@ -4629,282 +3974,559 @@ if test $ac_cv_func_syslog = no; then # one of several _real_ functions in syslog.h, so we need to do the test # with the appropriate include. for lib in bsd socket inet; do - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for syslog in -l$lib" >&5 -printf %s "checking for syslog in -l$lib... " >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for syslog in -l$lib" >&5 +$as_echo_n "checking for syslog in -l$lib... " >&6; } Mailman_LIBS_save="$LIBS"; LIBS="$LIBS -l$lib" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int -main (void) +main () { syslog(LOG_DEBUG, "Just a test..."); ; return 0; } _ACEOF -if ac_fn_c_try_link "$LINENO" -then : - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 -printf "%s\n" "yes" >&6; } - printf "%s\n" "#define HAVE_SYSLOG 1" >>confdefs.h +if ac_fn_c_try_link "$LINENO"; then : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + $as_echo "#define HAVE_SYSLOG 1" >>confdefs.h break -else case e in #( - e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 -printf "%s\n" "no" >&6; } - LIBS="$Mailman_LIBS_save" ;; -esac +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + LIBS="$Mailman_LIBS_save" fi -rm -f core conftest.err conftest.$ac_objext conftest.beam \ +rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext unset Mailman_LIBS_save done fi # Checks for header files. -ac_header= ac_cache= -for ac_item in $ac_header_c_list + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to run the C preprocessor" >&5 +$as_echo_n "checking how to run the C preprocessor... " >&6; } +# On Suns, sometimes $CPP names a directory. +if test -n "$CPP" && test -d "$CPP"; then + CPP= +fi +if test -z "$CPP"; then + if ${ac_cv_prog_CPP+:} false; then : + $as_echo_n "(cached) " >&6 +else + # Double quotes because CPP needs to be expanded + for CPP in "$CC -E" "$CC -E -traditional-cpp" "/lib/cpp" + do + ac_preproc_ok=false +for ac_c_preproc_warn_flag in '' yes do - if test $ac_cache; then - ac_fn_c_check_header_compile "$LINENO" $ac_header ac_cv_header_$ac_cache "$ac_includes_default" - if eval test \"x\$ac_cv_header_$ac_cache\" = xyes; then - printf "%s\n" "#define $ac_item 1" >> confdefs.h - fi - ac_header= ac_cache= - elif test $ac_header; then - ac_cache=$ac_item - else - ac_header=$ac_item - fi + # Use a header file that comes with gcc, so configuring glibc + # with a fresh cross-compiler works. + # Prefer to if __STDC__ is defined, since + # exists even on freestanding compilers. + # On the NeXT, cc -E runs the code through the compiler's parser, + # not just through cpp. "Syntax error" is here to catch this case. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#ifdef __STDC__ +# include +#else +# include +#endif + Syntax error +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + +else + # Broken: fails on valid input. +continue +fi +rm -f conftest.err conftest.i conftest.$ac_ext + + # OK, works on sane cases. Now check whether nonexistent headers + # can be detected and how. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + # Broken: success on invalid input. +continue +else + # Passes both tests. +ac_preproc_ok=: +break +fi +rm -f conftest.err conftest.i conftest.$ac_ext + done +# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. +rm -f conftest.i conftest.err conftest.$ac_ext +if $ac_preproc_ok; then : + break +fi + done + ac_cv_prog_CPP=$CPP +fi + CPP=$ac_cv_prog_CPP +else + ac_cv_prog_CPP=$CPP +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $CPP" >&5 +$as_echo "$CPP" >&6; } +ac_preproc_ok=false +for ac_c_preproc_warn_flag in '' yes +do + # Use a header file that comes with gcc, so configuring glibc + # with a fresh cross-compiler works. + # Prefer to if __STDC__ is defined, since + # exists even on freestanding compilers. + # On the NeXT, cc -E runs the code through the compiler's parser, + # not just through cpp. "Syntax error" is here to catch this case. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#ifdef __STDC__ +# include +#else +# include +#endif + Syntax error +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : +else + # Broken: fails on valid input. +continue +fi +rm -f conftest.err conftest.i conftest.$ac_ext + # OK, works on sane cases. Now check whether nonexistent headers + # can be detected and how. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + # Broken: success on invalid input. +continue +else + # Passes both tests. +ac_preproc_ok=: +break +fi +rm -f conftest.err conftest.i conftest.$ac_ext +done +# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. +rm -f conftest.i conftest.err conftest.$ac_ext +if $ac_preproc_ok; then : +else + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "C preprocessor \"$CPP\" fails sanity check +See \`config.log' for more details" "$LINENO" 5; } +fi +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu -if test $ac_cv_header_stdlib_h = yes && test $ac_cv_header_string_h = yes -then : -printf "%s\n" "#define STDC_HEADERS 1" >>confdefs.h +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for grep that handles long lines and -e" >&5 +$as_echo_n "checking for grep that handles long lines and -e... " >&6; } +if ${ac_cv_path_GREP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -z "$GREP"; then + ac_path_GREP_found=false + # Loop through the user's path and test for each of PROGNAME-LIST + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_prog in grep ggrep; do + for ac_exec_ext in '' $ac_executable_extensions; do + ac_path_GREP="$as_dir/$ac_prog$ac_exec_ext" + as_fn_executable_p "$ac_path_GREP" || continue +# Check for GNU ac_path_GREP and select it if it is found. + # Check for GNU $ac_path_GREP +case `"$ac_path_GREP" --version 2>&1` in +*GNU*) + ac_cv_path_GREP="$ac_path_GREP" ac_path_GREP_found=:;; +*) + ac_count=0 + $as_echo_n 0123456789 >"conftest.in" + while : + do + cat "conftest.in" "conftest.in" >"conftest.tmp" + mv "conftest.tmp" "conftest.in" + cp "conftest.in" "conftest.nl" + $as_echo 'GREP' >> "conftest.nl" + "$ac_path_GREP" -e 'GREP$' -e '-(cannot match)-' < "conftest.nl" >"conftest.out" 2>/dev/null || break + diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break + as_fn_arith $ac_count + 1 && ac_count=$as_val + if test $ac_count -gt ${ac_path_GREP_max-0}; then + # Best one so far, save it but keep looking for a better one + ac_cv_path_GREP="$ac_path_GREP" + ac_path_GREP_max=$ac_count + fi + # 10*(2^10) chars as input seems more than enough + test $ac_count -gt 10 && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out;; +esac + $ac_path_GREP_found && break 3 + done + done + done +IFS=$as_save_IFS + if test -z "$ac_cv_path_GREP"; then + as_fn_error $? "no acceptable grep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 + fi +else + ac_cv_path_GREP=$GREP fi -ac_fn_c_check_header_compile "$LINENO" "stdio.h" "ac_cv_header_stdio_h" "$ac_includes_default" -if test "x$ac_cv_header_stdio_h" = xyes -then : - printf "%s\n" "#define HAVE_STDIO_H 1" >>confdefs.h fi -ac_fn_c_check_header_compile "$LINENO" "stdlib.h" "ac_cv_header_stdlib_h" "$ac_includes_default" -if test "x$ac_cv_header_stdlib_h" = xyes -then : - printf "%s\n" "#define HAVE_STDLIB_H 1" >>confdefs.h +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_GREP" >&5 +$as_echo "$ac_cv_path_GREP" >&6; } + GREP="$ac_cv_path_GREP" + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for egrep" >&5 +$as_echo_n "checking for egrep... " >&6; } +if ${ac_cv_path_EGREP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if echo a | $GREP -E '(a|b)' >/dev/null 2>&1 + then ac_cv_path_EGREP="$GREP -E" + else + if test -z "$EGREP"; then + ac_path_EGREP_found=false + # Loop through the user's path and test for each of PROGNAME-LIST + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_prog in egrep; do + for ac_exec_ext in '' $ac_executable_extensions; do + ac_path_EGREP="$as_dir/$ac_prog$ac_exec_ext" + as_fn_executable_p "$ac_path_EGREP" || continue +# Check for GNU ac_path_EGREP and select it if it is found. + # Check for GNU $ac_path_EGREP +case `"$ac_path_EGREP" --version 2>&1` in +*GNU*) + ac_cv_path_EGREP="$ac_path_EGREP" ac_path_EGREP_found=:;; +*) + ac_count=0 + $as_echo_n 0123456789 >"conftest.in" + while : + do + cat "conftest.in" "conftest.in" >"conftest.tmp" + mv "conftest.tmp" "conftest.in" + cp "conftest.in" "conftest.nl" + $as_echo 'EGREP' >> "conftest.nl" + "$ac_path_EGREP" 'EGREP$' < "conftest.nl" >"conftest.out" 2>/dev/null || break + diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break + as_fn_arith $ac_count + 1 && ac_count=$as_val + if test $ac_count -gt ${ac_path_EGREP_max-0}; then + # Best one so far, save it but keep looking for a better one + ac_cv_path_EGREP="$ac_path_EGREP" + ac_path_EGREP_max=$ac_count + fi + # 10*(2^10) chars as input seems more than enough + test $ac_count -gt 10 && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out;; +esac + $ac_path_EGREP_found && break 3 + done + done + done +IFS=$as_save_IFS + if test -z "$ac_cv_path_EGREP"; then + as_fn_error $? "no acceptable egrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 + fi +else + ac_cv_path_EGREP=$EGREP fi -ac_fn_c_check_header_compile "$LINENO" "string.h" "ac_cv_header_string_h" "$ac_includes_default" -if test "x$ac_cv_header_string_h" = xyes -then : - printf "%s\n" "#define HAVE_STRING_H 1" >>confdefs.h + fi fi -ac_fn_c_check_header_compile "$LINENO" "inttypes.h" "ac_cv_header_inttypes_h" "$ac_includes_default" -if test "x$ac_cv_header_inttypes_h" = xyes -then : - printf "%s\n" "#define HAVE_INTTYPES_H 1" >>confdefs.h +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_EGREP" >&5 +$as_echo "$ac_cv_path_EGREP" >&6; } + EGREP="$ac_cv_path_EGREP" + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ANSI C header files" >&5 +$as_echo_n "checking for ANSI C header files... " >&6; } +if ${ac_cv_header_stdc+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +#include +#include +#include +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_header_stdc=yes +else + ac_cv_header_stdc=no fi -ac_fn_c_check_header_compile "$LINENO" "stdint.h" "ac_cv_header_stdint_h" "$ac_includes_default" -if test "x$ac_cv_header_stdint_h" = xyes -then : - printf "%s\n" "#define HAVE_STDINT_H 1" >>confdefs.h +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + +if test $ac_cv_header_stdc = yes; then + # SunOS 4.x string.h does not declare mem*, contrary to ANSI. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "memchr" >/dev/null 2>&1; then : +else + ac_cv_header_stdc=no fi -ac_fn_c_check_header_compile "$LINENO" "strings.h" "ac_cv_header_strings_h" "$ac_includes_default" -if test "x$ac_cv_header_strings_h" = xyes -then : - printf "%s\n" "#define HAVE_STRINGS_H 1" >>confdefs.h +rm -f conftest* fi -ac_fn_c_check_header_compile "$LINENO" "sys/stat.h" "ac_cv_header_sys_stat_h" "$ac_includes_default" -if test "x$ac_cv_header_sys_stat_h" = xyes -then : - printf "%s\n" "#define HAVE_SYS_STAT_H 1" >>confdefs.h +if test $ac_cv_header_stdc = yes; then + # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "free" >/dev/null 2>&1; then : + +else + ac_cv_header_stdc=no fi -ac_fn_c_check_header_compile "$LINENO" "sys/types.h" "ac_cv_header_sys_types_h" "$ac_includes_default" -if test "x$ac_cv_header_sys_types_h" = xyes -then : - printf "%s\n" "#define HAVE_SYS_TYPES_H 1" >>confdefs.h +rm -f conftest* fi -ac_fn_c_check_header_compile "$LINENO" "unistd.h" "ac_cv_header_unistd_h" "$ac_includes_default" -if test "x$ac_cv_header_unistd_h" = xyes -then : - printf "%s\n" "#define HAVE_UNISTD_H 1" >>confdefs.h +if test $ac_cv_header_stdc = yes; then + # /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi. + if test "$cross_compiling" = yes; then : + : +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +#include +#if ((' ' & 0x0FF) == 0x020) +# define ISLOWER(c) ('a' <= (c) && (c) <= 'z') +# define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c)) +#else +# define ISLOWER(c) \ + (('a' <= (c) && (c) <= 'i') \ + || ('j' <= (c) && (c) <= 'r') \ + || ('s' <= (c) && (c) <= 'z')) +# define TOUPPER(c) (ISLOWER(c) ? ((c) | 0x40) : (c)) +#endif + +#define XOR(e, f) (((e) && !(f)) || (!(e) && (f))) +int +main () +{ + int i; + for (i = 0; i < 256; i++) + if (XOR (islower (i), ISLOWER (i)) + || toupper (i) != TOUPPER (i)) + return 2; + return 0; +} +_ACEOF +if ac_fn_c_try_run "$LINENO"; then : + +else + ac_cv_header_stdc=no +fi +rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ + conftest.$ac_objext conftest.beam conftest.$ac_ext fi -ac_fn_c_check_header_compile "$LINENO" "syslog.h" "ac_cv_header_syslog_h" "$ac_includes_default" -if test "x$ac_cv_header_syslog_h" = xyes -then : - printf "%s\n" "#define HAVE_SYSLOG_H 1" >>confdefs.h fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_header_stdc" >&5 +$as_echo "$ac_cv_header_stdc" >&6; } +if test $ac_cv_header_stdc = yes; then +$as_echo "#define STDC_HEADERS 1" >>confdefs.h -# Checks for typedefs, structures, and compiler characteristics. -ac_fn_c_check_type "$LINENO" "uid_t" "ac_cv_type_uid_t" "$ac_includes_default" -if test "x$ac_cv_type_uid_t" = xyes -then : +fi + +# On IRIX 5.3, sys/types and inttypes.h are conflicting. +for ac_header in sys/types.h sys/stat.h stdlib.h string.h memory.h strings.h \ + inttypes.h stdint.h unistd.h +do : + as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` +ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default +" +if eval test \"x\$"$as_ac_Header"\" = x"yes"; then : + cat >>confdefs.h <<_ACEOF +#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1 +_ACEOF -else case e in #( - e) -printf "%s\n" "#define uid_t int" >>confdefs.h - ;; -esac fi -ac_fn_c_check_type "$LINENO" "gid_t" "ac_cv_type_gid_t" "$ac_includes_default" -if test "x$ac_cv_type_gid_t" = xyes -then : +done + + +for ac_header in syslog.h +do : + ac_fn_c_check_header_mongrel "$LINENO" "syslog.h" "ac_cv_header_syslog_h" "$ac_includes_default" +if test "x$ac_cv_header_syslog_h" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_SYSLOG_H 1 +_ACEOF -else case e in #( - e) -printf "%s\n" "#define gid_t int" >>confdefs.h - ;; -esac fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking type of array argument to getgroups" >&5 -printf %s "checking type of array argument to getgroups... " >&6; } -if test ${ac_cv_type_getgroups+y} -then : - printf %s "(cached) " >&6 -else case e in #( - e) # If AC_TYPE_UID_T says there isn't any gid_t typedef, then we can skip -# everything below. -if test $ac_cv_type_gid_t = no -then : - ac_cv_type_getgroups=int -else case e in #( - e) # Test programs below rely on strict type checking of extern declarations: - # 'extern int getgroups(int, int *); extern int getgroups(int, pid_t *);' - # is valid in C89 if and only if pid_t is a typedef for int. Unlike - # anything involving either an assignment or a function call, compilers - # tend to make this kind of type mismatch a hard error, not just an - # "incompatible pointer types" warning. +done + + +# Checks for typedefs, structures, and compiler characteristics. +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for uid_t in sys/types.h" >&5 +$as_echo_n "checking for uid_t in sys/types.h... " >&6; } +if ${ac_cv_type_uid_t+:} false; then : + $as_echo_n "(cached) " >&6 +else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ -$ac_includes_default -extern int getgroups(int, gid_t *); -int -main (void) -{ -return !(getgroups(0, 0) >= 0); - ; - return 0; -} +#include + _ACEOF -if ac_fn_c_try_compile "$LINENO" -then : - ac_getgroups_gidarray=yes -else case e in #( - e) ac_getgroups_gidarray=no ;; -esac +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "uid_t" >/dev/null 2>&1; then : + ac_cv_type_uid_t=yes +else + ac_cv_type_uid_t=no +fi +rm -f conftest* + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_type_uid_t" >&5 +$as_echo "$ac_cv_type_uid_t" >&6; } +if test $ac_cv_type_uid_t = no; then + +$as_echo "#define uid_t int" >>confdefs.h + + +$as_echo "#define gid_t int" >>confdefs.h + fi -rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking type of array argument to getgroups" >&5 +$as_echo_n "checking type of array argument to getgroups... " >&6; } +if ${ac_cv_type_getgroups+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test "$cross_compiling" = yes; then : + ac_cv_type_getgroups=cross +else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ +/* Thanks to Mike Rendell for this test. */ $ac_includes_default -extern int getgroups(int, int *); +#define NGID 256 +#undef MAX +#define MAX(x, y) ((x) > (y) ? (x) : (y)) + int -main (void) +main () { -return !(getgroups(0, 0) >= 0); - ; - return 0; + gid_t gidset[NGID]; + int i, n; + union { gid_t gval; long int lval; } val; + + val.lval = -1; + for (i = 0; i < NGID; i++) + gidset[i] = val.gval; + n = getgroups (sizeof (gidset) / MAX (sizeof (int), sizeof (gid_t)) - 1, + gidset); + /* Exit non-zero if getgroups seems to require an array of ints. This + happens when gid_t is short int but getgroups modifies an array + of ints. */ + return n > 0 && gidset[n] != val.gval; } _ACEOF -if ac_fn_c_try_compile "$LINENO" -then : - ac_getgroups_intarray=yes -else case e in #( - e) ac_getgroups_intarray=no ;; -esac +if ac_fn_c_try_run "$LINENO"; then : + ac_cv_type_getgroups=gid_t +else + ac_cv_type_getgroups=int fi -rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext - - case int:$ac_getgroups_intarray,gid:$ac_getgroups_gidarray in #( - int:yes,gid:no) : - ac_cv_type_getgroups=int ;; #( - int:no,gid:yes) : - ac_cv_type_getgroups=gid_t ;; #( - int:yes,gid:yes) : - - # Both programs compiled - this means *either* that getgroups - # was declared with no prototype, in which case we should use int, - # or that it was declared prototyped but gid_t is a typedef for int, - # in which case we should use gid_t. Distinguish the two cases - # by testing if the compiler catches a blatantly incorrect function - # signature for getgroups. - cat confdefs.h - <<_ACEOF >conftest.$ac_ext +rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ + conftest.$ac_objext conftest.beam conftest.$ac_ext +fi + +if test $ac_cv_type_getgroups = cross; then + cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ -$ac_includes_default -extern int getgroups(int, float); -int -main (void) -{ -return !(getgroups(0, 0) >= 0); - ; - return 0; -} +#include + _ACEOF -if ac_fn_c_try_compile "$LINENO" -then : - - # Compiler did not catch incorrect argument list; - # getgroups is unprototyped. - ac_cv_type_getgroups=int - -else case e in #( - e) - # Compiler caught incorrect argument list; - # gid_t is a typedef for int. - ac_cv_type_getgroups=gid_t - ;; -esac +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "getgroups.*int.*gid_t" >/dev/null 2>&1; then : + ac_cv_type_getgroups=gid_t +else + ac_cv_type_getgroups=int fi -rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext - ;; #( - *) : +rm -f conftest* - # Both programs failed to compile - this probably means getgroups - # wasn't declared at all. Use 'int', as this is probably a very - # old system where the type _would have been_ int. - ac_cv_type_getgroups=int - ;; -esac - ;; -esac fi - ;; -esac fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_type_getgroups" >&5 -printf "%s\n" "$ac_cv_type_getgroups" >&6; } -printf "%s\n" "#define GETGROUPS_T $ac_cv_type_getgroups" >>confdefs.h +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_type_getgroups" >&5 +$as_echo "$ac_cv_type_getgroups" >&6; } + +cat >>confdefs.h <<_ACEOF +#define GETGROUPS_T $ac_cv_type_getgroups +_ACEOF # Checks for library functions. -ac_fn_c_check_func "$LINENO" "vsnprintf" "ac_cv_func_vsnprintf" -if test "x$ac_cv_func_vsnprintf" = xyes -then : - printf "%s\n" "#define HAVE_VSNPRINTF 1" >>confdefs.h +for ac_func in vsnprintf +do : + ac_fn_c_check_func "$LINENO" "vsnprintf" "ac_cv_func_vsnprintf" +if test "x$ac_cv_func_vsnprintf" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_VSNPRINTF 1 +_ACEOF fi +done @@ -4986,8 +4608,8 @@ cat >confcache <<\_ACEOF # config.status only pays attention to the cache file if you give it # the --recheck option to rerun configure. # -# 'ac_cv_env_foo' variables (set or unset) will be overridden when -# loading this file, other *unset* 'ac_cv_foo' will be assigned the +# `ac_cv_env_foo' variables (set or unset) will be overridden when +# loading this file, other *unset* `ac_cv_foo' will be assigned the # following values. _ACEOF @@ -5003,8 +4625,8 @@ _ACEOF case $ac_val in #( *${as_nl}*) case $ac_var in #( - *_cv_*) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 -printf "%s\n" "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; + *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 +$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; esac case $ac_var in #( _ | IFS | as_nl) ;; #( @@ -5017,14 +4639,14 @@ printf "%s\n" "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} (set) 2>&1 | case $as_nl`(ac_space=' '; set) 2>&1` in #( *${as_nl}ac_space=\ *) - # 'set' does not quote correctly, so add quotes: double-quote + # `set' does not quote correctly, so add quotes: double-quote # substitution turns \\\\ into \\, and sed turns \\ into \. sed -n \ "s/'/'\\\\''/g; s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p" ;; #( *) - # 'set' quotes correctly as required by POSIX, so do not add quotes. + # `set' quotes correctly as required by POSIX, so do not add quotes. sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" ;; esac | @@ -5034,15 +4656,15 @@ printf "%s\n" "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} /^ac_cv_env_/b end t clear :clear - s/^\([^=]*\)=\(.*[{}].*\)$/test ${\1+y} || &/ + s/^\([^=]*\)=\(.*[{}].*\)$/test "${\1+set}" = set || &/ t end s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/ :end' >>confcache if diff "$cache_file" confcache >/dev/null 2>&1; then :; else if test -w "$cache_file"; then if test "x$cache_file" != "x/dev/null"; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5 -printf "%s\n" "$as_me: updating cache $cache_file" >&6;} + { $as_echo "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5 +$as_echo "$as_me: updating cache $cache_file" >&6;} if test ! -f "$cache_file" || test -h "$cache_file"; then cat confcache >"$cache_file" else @@ -5056,8 +4678,8 @@ printf "%s\n" "$as_me: updating cache $cache_file" >&6;} fi fi else - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5 -printf "%s\n" "$as_me: not updating unwritable cache $cache_file" >&6;} + { $as_echo "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5 +$as_echo "$as_me: not updating unwritable cache $cache_file" >&6;} fi fi rm -f confcache @@ -5088,7 +4710,9 @@ s/^[ ]*#[ ]*define[ ][ ]*\([^ ][^ ]*\)[ ]*\(.*\)/-D\1=\2/g t quote b any :quote -s/[][ `~#$^&*(){}\\|;'\''"<>?]/\\&/g +s/[ `~#$^&*(){}\\|;'\''"<>?]/\\&/g +s/\[/\\&/g +s/\]/\\&/g s/\$/$$/g H :any @@ -5108,7 +4732,7 @@ U= for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue # 1. Remove the extension, and $U if already installed. ac_script='s/\$U\././;s/\.o$//;s/\.obj$//' - ac_i=`printf "%s\n" "$ac_i" | sed "$ac_script"` + ac_i=`$as_echo "$ac_i" | sed "$ac_script"` # 2. Prepend LIBOBJDIR. When used with automake>=1.10 LIBOBJDIR # will be set to the directory where LIBOBJS objects are built. as_fn_append ac_libobjs " \${LIBOBJDIR}$ac_i\$U.$ac_objext" @@ -5119,17 +4743,13 @@ LIBOBJS=$ac_libobjs LTLIBOBJS=$ac_ltlibobjs -if test -z "${HAVE_NNTP_TRUE}" && test -z "${HAVE_NNTP_FALSE}"; then - as_fn_error $? "conditional \"HAVE_NNTP\" was never defined. -Usually this means the macro was only invoked conditionally." "$LINENO" 5 -fi : "${CONFIG_STATUS=./config.status}" ac_write_fail=0 ac_clean_files_save=$ac_clean_files ac_clean_files="$ac_clean_files $CONFIG_STATUS" -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5 -printf "%s\n" "$as_me: creating $CONFIG_STATUS" >&6;} +{ $as_echo "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5 +$as_echo "$as_me: creating $CONFIG_STATUS" >&6;} as_write_fail=0 cat >$CONFIG_STATUS <<_ASEOF || as_write_fail=1 #! $SHELL @@ -5152,65 +4772,63 @@ cat >>$CONFIG_STATUS <<\_ASEOF || as_write_fail=1 # Be more Bourne compatible DUALCASE=1; export DUALCASE # for MKS sh -if test ${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 -then : +if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which # is contrary to our usage. Disable this feature. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST -else case e in #( - e) case `(set -o) 2>/dev/null` in #( +else + case `(set -o) 2>/dev/null` in #( *posix*) : set -o posix ;; #( *) : ;; -esac ;; esac fi - -# Reset variables that may have inherited troublesome values from -# the environment. - -# IFS needs to be set, to space, tab, and newline, in precisely that order. -# (If _AS_PATH_WALK were called with IFS unset, it would have the -# side effect of setting IFS to empty, thus disabling word splitting.) -# Quoting is to prevent editors from complaining about space-tab. as_nl=' ' export as_nl -IFS=" "" $as_nl" - -PS1='$ ' -PS2='> ' -PS4='+ ' - -# Ensure predictable behavior from utilities with locale-dependent output. -LC_ALL=C -export LC_ALL -LANGUAGE=C -export LANGUAGE - -# We cannot yet rely on "unset" to work, but we need these variables -# to be unset--not just set to an empty or harmless value--now, to -# avoid bugs in old shells (e.g. pre-3.0 UWIN ksh). This construct -# also avoids known problems related to "unset" and subshell syntax -# in other old shells (e.g. bash 2.01 and pdksh 5.2.14). -for as_var in BASH_ENV ENV MAIL MAILPATH CDPATH -do eval test \${$as_var+y} \ - && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : -done - -# Ensure that fds 0, 1, and 2 are open. -if (exec 3>&0) 2>/dev/null; then :; else exec 0&1) 2>/dev/null; then :; else exec 1>/dev/null; fi -if (exec 3>&2) ; then :; else exec 2>/dev/null; fi +# Printing a long string crashes Solaris 7 /usr/bin/printf. +as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo +# Prefer a ksh shell builtin over an external printf program on Solaris, +# but without wasting forks for bash or zsh. +if test -z "$BASH_VERSION$ZSH_VERSION" \ + && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='print -r --' + as_echo_n='print -rn --' +elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='printf %s\n' + as_echo_n='printf %s' +else + if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then + as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' + as_echo_n='/usr/ucb/echo -n' + else + as_echo_body='eval expr "X$1" : "X\\(.*\\)"' + as_echo_n_body='eval + arg=$1; + case $arg in #( + *"$as_nl"*) + expr "X$arg" : "X\\(.*\\)$as_nl"; + arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; + esac; + expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" + ' + export as_echo_n_body + as_echo_n='sh -c $as_echo_n_body as_echo' + fi + export as_echo_body + as_echo='sh -c $as_echo_body as_echo' +fi # The user is always right. -if ${PATH_SEPARATOR+false} :; then +if test "${PATH_SEPARATOR+set}" != set; then PATH_SEPARATOR=: (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || @@ -5219,6 +4837,13 @@ if ${PATH_SEPARATOR+false} :; then fi +# IFS +# We need space, tab and new line, in precisely that order. Quoting is +# there to prevent editors from complaining about space-tab. +# (If _AS_PATH_WALK were called with IFS unset, it would disable word +# splitting by setting IFS to empty value.) +IFS=" "" $as_nl" + # Find who we are. Look in the path if we contain no directory separator. as_myself= case $0 in #(( @@ -5227,27 +4852,43 @@ case $0 in #(( for as_dir in $PATH do IFS=$as_save_IFS - case $as_dir in #((( - '') as_dir=./ ;; - */) ;; - *) as_dir=$as_dir/ ;; - esac - test -r "$as_dir$0" && as_myself=$as_dir$0 && break + test -z "$as_dir" && as_dir=. + test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break done IFS=$as_save_IFS ;; esac -# We did not find ourselves, most probably we were run as 'sh COMMAND' +# We did not find ourselves, most probably we were run as `sh COMMAND' # in which case we are not to be found in the path. if test "x$as_myself" = x; then as_myself=$0 fi if test ! -f "$as_myself"; then - printf "%s\n" "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 + $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 exit 1 fi +# Unset variables that we do not need and which cause bugs (e.g. in +# pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" +# suppresses any "Segmentation fault" message there. '((' could +# trigger a bug in pdksh 5.2.14. +for as_var in BASH_ENV ENV MAIL MAILPATH +do eval test x\${$as_var+set} = xset \ + && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : +done +PS1='$ ' +PS2='> ' +PS4='+ ' + +# NLS nuisances. +LC_ALL=C +export LC_ALL +LANGUAGE=C +export LANGUAGE + +# CDPATH. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH # as_fn_error STATUS ERROR [LINENO LOG_FD] @@ -5260,9 +4901,9 @@ as_fn_error () as_status=$1; test $as_status -eq 0 && as_status=1 if test "$4"; then as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack - printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 + $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 fi - printf "%s\n" "$as_me: error: $2" >&2 + $as_echo "$as_me: error: $2" >&2 as_fn_exit $as_status } # as_fn_error @@ -5293,25 +4934,22 @@ as_fn_unset () { eval $1=; unset $1;} } as_unset=as_fn_unset - # as_fn_append VAR VALUE # ---------------------- # Append the text in VALUE to the end of the definition contained in VAR. Take # advantage of any shell optimizations that allow amortized linear growth over # repeated appends, instead of the typical quadratic growth present in naive # implementations. -if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null -then : +if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : eval 'as_fn_append () { eval $1+=\$2 }' -else case e in #( - e) as_fn_append () +else + as_fn_append () { eval $1=\$$1\$2 - } ;; -esac + } fi # as_fn_append # as_fn_arith ARG... @@ -5319,18 +4957,16 @@ fi # as_fn_append # Perform arithmetic evaluation on the ARGs, and store the result in the # global $as_val. Take advantage of shells that can avoid forks. The arguments # must be portable across $(()) and expr. -if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null -then : +if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : eval 'as_fn_arith () { as_val=$(( $* )) }' -else case e in #( - e) as_fn_arith () +else + as_fn_arith () { as_val=`expr "$@" || test $? -eq 1` - } ;; -esac + } fi # as_fn_arith @@ -5357,7 +4993,7 @@ as_me=`$as_basename -- "$0" || $as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ X"$0" : 'X\(//\)$' \| \ X"$0" : 'X\(/\)' \| . 2>/dev/null || -printf "%s\n" X/"$0" | +$as_echo X/"$0" | sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/ q @@ -5379,10 +5015,6 @@ as_cr_Letters=$as_cr_letters$as_cr_LETTERS as_cr_digits='0123456789' as_cr_alnum=$as_cr_Letters$as_cr_digits - -# Determine whether it's possible to make 'echo' print without a newline. -# These variables are no longer used directly by Autoconf, but are AC_SUBSTed -# for compatibility with existing Makefiles. ECHO_C= ECHO_N= ECHO_T= case `echo -n x` in #((((( -n*) @@ -5396,12 +5028,6 @@ case `echo -n x` in #((((( ECHO_N='-n';; esac -# For backward compatibility with old third-party macros, we provide -# the shell variables $as_echo and $as_echo_n. New code should use -# AS_ECHO(["message"]) and AS_ECHO_N(["message"]), respectively. -as_echo='printf %s\n' -as_echo_n='printf %s' - rm -f conf$$ conf$$.exe conf$$.file if test -d conf$$.dir; then rm -f conf$$.dir/conf$$.file @@ -5413,9 +5039,9 @@ if (echo >conf$$.file) 2>/dev/null; then if ln -s conf$$.file conf$$ 2>/dev/null; then as_ln_s='ln -s' # ... but there are two gotchas: - # 1) On MSYS, both 'ln -s file dir' and 'ln file dir' fail. - # 2) DJGPP < 2.04 has no symlinks; 'ln -s' creates a wrapper executable. - # In both cases, we have to default to 'cp -pR'. + # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. + # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. + # In both cases, we have to default to `cp -pR'. ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || as_ln_s='cp -pR' elif ln conf$$.file conf$$ 2>/dev/null; then @@ -5443,7 +5069,7 @@ as_fn_mkdir_p () as_dirs= while :; do case $as_dir in #( - *\'*) as_qdir=`printf "%s\n" "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( + *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( *) as_qdir=$as_dir;; esac as_dirs="'$as_qdir' $as_dirs" @@ -5452,7 +5078,7 @@ $as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_dir" : 'X\(//\)[^/]' \| \ X"$as_dir" : 'X\(//\)$' \| \ X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || -printf "%s\n" X"$as_dir" | +$as_echo X"$as_dir" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q @@ -5496,12 +5122,10 @@ as_test_x='test -x' as_executable_p=as_fn_executable_p # Sed expression to map a string onto a valid CPP name. -as_sed_cpp="y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g" -as_tr_cpp="eval sed '$as_sed_cpp'" # deprecated +as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" # Sed expression to map a string onto a valid variable name. -as_sed_sh="y%*+%pp%;s%[^_$as_cr_alnum]%_%g" -as_tr_sh="eval sed '$as_sed_sh'" # deprecated +as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" exec 6>&1 @@ -5517,7 +5141,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # values after options handling. ac_log=" This file was extended by $as_me, which was -generated by GNU Autoconf 2.72. Invocation command line was +generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES CONFIG_HEADERS = $CONFIG_HEADERS @@ -5545,7 +5169,7 @@ _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 ac_cs_usage="\ -'$as_me' instantiates files and other configuration actions +\`$as_me' instantiates files and other configuration actions from templates according to the current configuration. Unless the files and actions are specified as TAGs, all are instantiated by default. @@ -5570,16 +5194,14 @@ $config_commands Report bugs to the package provider." _ACEOF -ac_cs_config=`printf "%s\n" "$ac_configure_args" | sed "$ac_safe_unquote"` -ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\''/g"` cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 -ac_cs_config='$ac_cs_config_escaped' +ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ config.status -configured by $0, generated by GNU Autoconf 2.72, +configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" -Copyright (C) 2023 Free Software Foundation, Inc. +Copyright (C) 2012 Free Software Foundation, Inc. This config.status script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it." @@ -5617,28 +5239,28 @@ do -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r) ac_cs_recheck=: ;; --version | --versio | --versi | --vers | --ver | --ve | --v | -V ) - printf "%s\n" "$ac_cs_version"; exit ;; + $as_echo "$ac_cs_version"; exit ;; --config | --confi | --conf | --con | --co | --c ) - printf "%s\n" "$ac_cs_config"; exit ;; + $as_echo "$ac_cs_config"; exit ;; --debug | --debu | --deb | --de | --d | -d ) debug=: ;; --file | --fil | --fi | --f ) $ac_shift case $ac_optarg in - *\'*) ac_optarg=`printf "%s\n" "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; + *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; '') as_fn_error $? "missing file argument" ;; esac as_fn_append CONFIG_FILES " '$ac_optarg'" ac_need_defaults=false;; --he | --h | --help | --hel | -h ) - printf "%s\n" "$ac_cs_usage"; exit ;; + $as_echo "$ac_cs_usage"; exit ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil | --si | --s) ac_cs_silent=: ;; # This is an error. - -*) as_fn_error $? "unrecognized option: '$1' -Try '$0 --help' for more information." ;; + -*) as_fn_error $? "unrecognized option: \`$1' +Try \`$0 --help' for more information." ;; *) as_fn_append ac_config_targets " $1" ac_need_defaults=false ;; @@ -5659,7 +5281,7 @@ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 if \$ac_cs_recheck; then set X $SHELL '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion shift - \printf "%s\n" "running CONFIG_SHELL=$SHELL \$*" >&6 + \$as_echo "running CONFIG_SHELL=$SHELL \$*" >&6 CONFIG_SHELL='$SHELL' export CONFIG_SHELL exec "\$@" @@ -5673,7 +5295,7 @@ exec 5>>config.log sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX ## Running $as_me. ## _ASBOX - printf "%s\n" "$ac_log" + $as_echo "$ac_log" } >&5 _ACEOF @@ -5715,7 +5337,7 @@ do "$SCRIPTS") CONFIG_FILES="$CONFIG_FILES $SCRIPTS" ;; "default") CONFIG_COMMANDS="$CONFIG_COMMANDS default" ;; - *) as_fn_error $? "invalid argument: '$ac_config_target'" "$LINENO" 5;; + *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;; esac done @@ -5725,8 +5347,8 @@ done # We use the long form for the default assignment because of an extremely # bizarre bug on SunOS 4.1.3. if $ac_need_defaults; then - test ${CONFIG_FILES+y} || CONFIG_FILES=$config_files - test ${CONFIG_COMMANDS+y} || CONFIG_COMMANDS=$config_commands + test "${CONFIG_FILES+set}" = set || CONFIG_FILES=$config_files + test "${CONFIG_COMMANDS+set}" = set || CONFIG_COMMANDS=$config_commands fi # Have a temporary directory for convenience. Make it in the build tree @@ -5734,7 +5356,7 @@ fi # creating and moving files from /tmp can sometimes cause problems. # Hook for its removal unless debugging. # Note that there is a small window in which the directory will not be cleaned: -# after its creation but before its name has been assigned to '$tmp'. +# after its creation but before its name has been assigned to `$tmp'. $debug || { tmp= ac_tmp= @@ -5758,7 +5380,7 @@ ac_tmp=$tmp # Set up the scripts for CONFIG_FILES section. # No need to generate them if there are no CONFIG_FILES. -# This happens for instance with './config.status config.h'. +# This happens for instance with `./config.status config.h'. if test -n "$CONFIG_FILES"; then @@ -5924,7 +5546,7 @@ do esac case $ac_mode$ac_tag in :[FHL]*:*);; - :L* | :C*:*) as_fn_error $? "invalid tag '$ac_tag'" "$LINENO" 5;; + :L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5;; :[FH]-) ac_tag=-:-;; :[FH]*) ac_tag=$ac_tag:$ac_tag.in;; esac @@ -5946,33 +5568,33 @@ do -) ac_f="$ac_tmp/stdin";; *) # Look for the file first in the build tree, then in the source tree # (if the path is not absolute). The absolute path cannot be DOS-style, - # because $ac_f cannot contain ':'. + # because $ac_f cannot contain `:'. test -f "$ac_f" || case $ac_f in [\\/$]*) false;; *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";; esac || - as_fn_error 1 "cannot find input file: '$ac_f'" "$LINENO" 5;; + as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5;; esac - case $ac_f in *\'*) ac_f=`printf "%s\n" "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac + case $ac_f in *\'*) ac_f=`$as_echo "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac as_fn_append ac_file_inputs " '$ac_f'" done - # Let's still pretend it is 'configure' which instantiates (i.e., don't + # Let's still pretend it is `configure' which instantiates (i.e., don't # use $as_me), people would be surprised to read: # /* config.h. Generated by config.status. */ configure_input='Generated from '` - printf "%s\n" "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g' + $as_echo "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g' `' by configure.' if test x"$ac_file" != x-; then configure_input="$ac_file. $configure_input" - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5 -printf "%s\n" "$as_me: creating $ac_file" >&6;} + { $as_echo "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5 +$as_echo "$as_me: creating $ac_file" >&6;} fi # Neutralize special characters interpreted by sed in replacement strings. case $configure_input in #( *\&* | *\|* | *\\* ) - ac_sed_conf_input=`printf "%s\n" "$configure_input" | + ac_sed_conf_input=`$as_echo "$configure_input" | sed 's/[\\\\&|]/\\\\&/g'`;; #( *) ac_sed_conf_input=$configure_input;; esac @@ -5989,7 +5611,7 @@ $as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$ac_file" : 'X\(//\)[^/]' \| \ X"$ac_file" : 'X\(//\)$' \| \ X"$ac_file" : 'X\(/\)' \| . 2>/dev/null || -printf "%s\n" X"$ac_file" | +$as_echo X"$ac_file" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q @@ -6013,9 +5635,9 @@ printf "%s\n" X"$ac_file" | case "$ac_dir" in .) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; *) - ac_dir_suffix=/`printf "%s\n" "$ac_dir" | sed 's|^\.[\\/]||'` + ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` # A ".." for each directory in $ac_dir_suffix. - ac_top_builddir_sub=`printf "%s\n" "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` + ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` case $ac_top_builddir_sub in "") ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; @@ -6072,8 +5694,8 @@ ac_sed_dataroot=' case `eval "sed -n \"\$ac_sed_dataroot\" $ac_file_inputs"` in *datarootdir*) ac_datarootdir_seen=yes;; *@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*) - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5 -printf "%s\n" "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5 +$as_echo "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;} _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_datarootdir_hack=' @@ -6086,7 +5708,7 @@ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 esac _ACEOF -# Neutralize VPATH when '$srcdir' = '.'. +# Neutralize VPATH when `$srcdir' = `.'. # Shell code in configure.ac might set extrasub. # FIXME: do we really want to maintain this feature? cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 @@ -6116,9 +5738,9 @@ test -z "$ac_datarootdir_hack$ac_datarootdir_seen" && { ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } && { ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' \ "$ac_tmp/out"`; test -z "$ac_out"; } && - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable 'datarootdir' + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir' which seems to be undefined. Please make sure it is defined" >&5 -printf "%s\n" "$as_me: WARNING: $ac_file contains a reference to the variable 'datarootdir' +$as_echo "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir' which seems to be undefined. Please make sure it is defined" >&2;} rm -f "$ac_tmp/stdin" @@ -6130,8 +5752,8 @@ which seems to be undefined. Please make sure it is defined" >&2;} ;; - :C) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: executing $ac_file commands" >&5 -printf "%s\n" "$as_me: executing $ac_file commands" >&6;} + :C) { $as_echo "$as_me:${as_lineno-$LINENO}: executing $ac_file commands" >&5 +$as_echo "$as_me: executing $ac_file commands" >&6;} ;; esac @@ -6172,8 +5794,8 @@ if test "$no_create" != yes; then $ac_cs_success || as_fn_exit 1 fi if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5 -printf "%s\n" "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5 +$as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;} fi @@ -6182,4 +5804,3 @@ chmod -R +x build # Test for the Chinese codecs. - diff --git a/configure.ac b/configure.ac index b76c5a2d..80c303d8 100644 --- a/configure.ac +++ b/configure.ac @@ -16,7 +16,7 @@ dnl Process this file with autoconf to produce a configure script. AC_REVISION($Revision: 8122 $) -AC_PREREQ([2.71]) +AC_PREREQ([2.69]) AC_INIT AC_CONFIG_SRCDIR([src/common.h]) @@ -687,7 +687,12 @@ if test $ac_cv_func_syslog = no; then fi # Checks for header files. -AC_CHECK_HEADERS([stdio.h stdlib.h string.h inttypes.h stdint.h strings.h sys/stat.h sys/types.h unistd.h syslog.h]) +m4_warn([obsolete], +[The preprocessor macro `STDC_HEADERS' is obsolete. + Except in unusual embedded environments, you can safely include all + ISO C90 headers unconditionally.])dnl + +AC_CHECK_HEADERS(syslog.h) # Checks for typedefs, structures, and compiler characteristics. AC_TYPE_UID_T @@ -781,7 +786,7 @@ AC_CONFIG_FILES([misc/paths.py Mailman/Defaults.py Mailman/mm_cfg.py.dist Mailman/Queue/Makefile Mailman/MTA/Makefile Mailman/Gui/Makefile templates/Makefile cron/Makefile scripts/Makefile messages/Makefile cron/crontab.in misc/mailman Makefile - tests/Makefile tests/bounces/Makefile tests/msgs/Makefile + tests/Makefile tests/bounces/Makefile tests/msgs/Makefile Mailman/__init__.py $SCRIPTS]) AC_CONFIG_COMMANDS([default],[echo "configuration completed at" `date`],[]) AC_OUTPUT diff --git a/contrib/check_perms_grsecurity.py b/contrib/check_perms_grsecurity.py index 19dd2af4..b657de05 100644 --- a/contrib/check_perms_grsecurity.py +++ b/contrib/check_perms_grsecurity.py @@ -157,9 +157,9 @@ class CheckFixUid: except ValueError: file.insert(file.index("import paths\n")+1, "import CheckFixUid\n") for i in range(len(file)-1, 0, -1): - object=re.compile(r"^([ ]*)main\(").search(file[i]) + object=re.compile("^([ ]*)main\(").search(file[i]) # Special hack to support patching of update - object2=re.compile(r"^([ ]*).*=[ ]*main\(").search(file[i]) + object2=re.compile("^([ ]*).*=[ ]*main\(").search(file[i]) if object: print("Patching " + script) file.insert(i, diff --git a/contrib/courier-to-mailman.py b/contrib/courier-to-mailman.py index 95900878..f5161db7 100644 --- a/contrib/courier-to-mailman.py +++ b/contrib/courier-to-mailman.py @@ -54,7 +54,7 @@ # Note: "preline" is a Courier program which ensures a Unix "From " header # is on the message. Archiving will break without this. -import sys, os, re +import sys, os, re, string def main(): os.nice(5) # Handle mailing lists at non-interactive priority. @@ -62,7 +62,7 @@ def main(): os.chdir(MailmanVar + "/lists") try: - local = str.lower(os.environ["LOCAL"]) + local = string.lower(os.environ["LOCAL"]) except: # This might happen if we're not using qmail. sys.stderr.write("LOCAL not set in environment?\n") @@ -77,12 +77,12 @@ def main(): sys.exit(0) type = "post" - listname = str.lower(local) + listname = string.lower(local) types = (("-admin$", "admin"), ("-bounces$", "bounces"), - (r"-bounces\+.*$", "bounces"), # for VERP + ("-bounces\+.*$", "bounces"), # for VERP ("-confirm$", "confirm"), - (r"-confirm\+.*$", "confirm"), + ("-confirm\+.*$", "confirm"), ("-join$", "join"), ("-leave$", "leave"), ("-owner$", "owner"), diff --git a/contrib/import_majordomo_into_mailman.pl b/contrib/import_majordomo_into_mailman.pl index ec2aa2f7..9df75f2f 100644 --- a/contrib/import_majordomo_into_mailman.pl +++ b/contrib/import_majordomo_into_mailman.pl @@ -38,62 +38,43 @@ use strict; use warnings; -use feature 'say'; -use Getopt::Long qw(:config no_ignore_case bundling); + +use Getopt::Long; use Log::Handler; use File::Temp qw(tempfile); use Email::Simple; use Email::Sender::Simple qw(try_to_sendmail); use Data::Dump qw(dump); -use Pod::Usage; + #----------------------- ENVIRONMENT-SPECIFIC VALUES --------------------------# -my %config = ( - DOMO_PATH => '/opt/majordomo', - DOMO_LIST_DIR => '/opt/majordomo/lists', - MM_PATH => '/usr/local/mailman', - DOMO_ALIASES => '/usr/local/mailman/majordomo/aliases', - DOMO_CHECK_CONSISTENCY => '/usr/local/mailman/majordomo/check_consistency.txt', - BOUNCED_OWNERS => '/opt/mailman-2.1.14-1/uo/majordomo/email_addresses_that_bounced.txt', - TMP_DIR => '/tmp', - DOMO_INACTIVITY_LIMIT => 548, # Optional. 548 days = 18 months. - NEW_HOSTNAME => '', # Optional - LANGUAGE => 'en', # Preferred language for all Mailman lists - MAX_MSG_SIZE => 20000, # In KB. Used for the Mailman config. -); - -# Command line options -my %opts = ( - help => 0, - stats => 0, - subscribers => 0, - email_notify => 0, - email_test => 0, -); - -# Parse command line arguments -GetOptions( - 'help|h' => \$opts{help}, - 'stats|s' => \$opts{stats}, - 'subscribers|S' => \$opts{subscribers}, - 'email-notify|e' => \$opts{email_notify}, - 'email-test|t' => \$opts{email_test}, -) or pod2usage(2); - -# Show help if requested -pod2usage(1) if $opts{help}; +my $DOMO_PATH = '/opt/majordomo'; +my $DOMO_LIST_DIR = "$DOMO_PATH/lists"; +my $MM_PATH = '/usr/local/mailman'; +my $DOMO_ALIASES = "$MM_PATH/majordomo/aliases"; +my $DOMO_CHECK_CONSISTENCY = "$MM_PATH/majordomo/check_consistency.txt"; +my $BOUNCED_OWNERS = "/opt/mailman-2.1.14-1/uo/majordomo/" . + "email_addresses_that_bounced.txt"; +my $TMP_DIR = '/tmp'; +# Only import lists that have been active in the last N days. +my $DOMO_INACTIVITY_LIMIT = 548; # Optional. 548 days = 18 months. +# If set, overwrite Majordomo's "resend_host" and thus Mailman's "host_name". +my $NEW_HOSTNAME = ''; # Optional +my $LANGUAGE = 'en'; # Preferred language for all Mailman lists +my $MAX_MSG_SIZE = 20000; # In KB. Used for the Mailman config. +#------------------------------------------------------------------------------# # # Global constants # -my $MM_LIST_DIR = "$config{MM_PATH}/lists"; -my $MM_LIST_LISTS = "$config{MM_PATH}/bin/list_lists"; -my $MM_NEWLIST = "$config{MM_PATH}/bin/newlist"; -my $MM_CONFIGLIST = "$config{MM_PATH}/bin/config_list"; -my $MM_ADDMEMBERS = "$config{MM_PATH}/bin/add_members"; -my $MM_CHECK_PERMS = "$config{MM_PATH}/bin/check_perms"; +my $MM_LIST_DIR = "$MM_PATH/lists"; +my $MM_LIST_LISTS = "$MM_PATH/bin/list_lists"; +my $MM_NEWLIST = "$MM_PATH/bin/newlist"; +my $MM_CONFIGLIST = "$MM_PATH/bin/config_list"; +my $MM_ADDMEMBERS = "$MM_PATH/bin/add_members"; +my $MM_CHECK_PERMS = "$MM_PATH/bin/check_perms"; my $SCRIPT_NAME = $0 =~ /\/?(\b\w+\b)\.pl$/ ? $1 : ' - - -
    + التحقق من الشخصية لـ %(who)s للقائمة %(listname)s + + + + + %(message)s -
    - å–æ¶ˆè®¢é˜… - 您在 上的其他订阅 -
    + + + + - + +
    +

    +å–æ¶ˆè®¢é˜… +您在 上的其他订阅 +
    选中确认的å¤é€‰æ¡†ï¼Œç‚¹å‡»è¿™ä¸ªæŒ‰é’®å¯ä»¥ä»Žè¿™ä¸ªé‚®ä»¶åˆ—è¡¨å–æ¶ˆè®¢é˜…。 警告:这个动作会马上执行ï¼

    -

    +

    您å¯ä»¥æŸ¥ 上您已订阅的列表清å•. 如果您想对这些 其它的邮件列表åšç›¸åŒçš„æˆå‘˜ä¿¡æ¯æ“作,å¯ä»¥ä½¿ç”¨è¿™ä¸ªã€‚

    -

    -
    - - - - -
    - 您的 å£ä»¤ -
    - -
    -

    忘记了å£ä»¤?

    -
    + + + - - + + +
    +您的 å£ä»¤ +
    + +
    +

    忘记了å£ä»¤?

    +
    点击这个按钮,您å¯ä»¥é€šè¿‡æ‚¨çš„æˆå‘˜ç”µå­é‚®ç®±æ¥æŽ¥æ”¶æ‚¨çš„å£ä»¤ã€‚ -

    -

    - -
    -
    - -
    -

    修改您的å£ä»¤

    - - - - - - +
    æ–° - å£ä»¤:
    +

    +

    + +
    +

    + +
    +

    修改您的å£ä»¤

    + + + + + + - - -
    æ–° + å£ä»¤:
    确认å£ä»¤: -
    - - -

    全局修改 -
    -
    - +
    + +

    全局修改 +
    +

    - - +
    - 您的 订阅选项 -
    +
    +您的 订阅选项 +
    -

    检查现有值 -

    注æ„,有一些选项有一个设为全局 çš„å¤é€‰æ¡†ã€‚选中该框会使该选项在您在上订阅的所有邮件列表上生效。 点击上é¢çš„列出我的所有订阅 æ¥æŸ¥çœ‹æ‚¨è®¢é˜…了哪些其他的邮件列表。

    - -
    - - 信件投递

    + + - +

    - - - + +

    - - - - - - + + - - + - - - - + + - - + - - + - - - +

    +
    + +信件投递

    将该项设为å¯ç”¨æ¥æŽ¥æ”¶å‘é€åˆ°è¯¥é‚®ä»¶åˆ—表的信æ¯ï¼Œå¦‚果您想 ç»§ç»­è®¢é˜…ï¼Œä½†æš‚æ—¶ä¸æƒ³æŽ¥æ”¶é‚®ä»¶ï¼ˆä¾‹å¦‚,您正在休å‡ï¼‰ï¼Œå°†å…¶è®¾ä¸º ç¦æ­¢ã€‚å¦‚æžœæ‚¨ç¦æ­¢äº†é‚®ä»¶æŠ•递,请别忘了在您回æ¥çš„æ—¶å€™å°†å®ƒæ¿€æ´»ã€‚ 它ä¸ä¼šè‡ªåŠ¨æ¿€æ´»ã€‚ -

    - å…许
    - ç¦æ­¢

    - 设为全局 -

    +å…许
    +ç¦æ­¢

    +设为全局 +

    - è®¾ç½®æ‘˜è¦æ¨¡å¼

    +

    +è®¾ç½®æ‘˜è¦æ¨¡å¼

    å¦‚æžœæ‚¨å°†æ‘˜è¦æ¨¡å¼æ‰“开, 将会把所有信件èšåˆåœ¨ä¸€èµ·å‘é€ç»™æ‚¨ï¼Œ (通常æ¯å¤©ä¸€å°ï¼Œç¹å¿™çš„列表å¯èƒ½æ›´å¤šï¼‰ï¼Œè€Œä¸æ˜¯ä»–们å‘逿—¶çš„一次一å°ã€‚ å¦‚æžœæ‘˜è¦æ¨¡å¼ä»Žå¼€å˜ä¸ºå…³ï¼Œæ‚¨å¯èƒ½è¿˜ä¼šæ”¶åˆ°æœ€åŽä¸€ä»½æ‘˜è¦ã€‚ -

    - å…³
    - å¼€ -
    - 获得MIME还是普通文本摘è¦

    +

    +å…³
    +å¼€ +
    +获得MIME还是普通文本摘è¦

    您的电å­é‚®ä»¶é˜…读器å¯èƒ½æ”¯æŒä¹Ÿå¯èƒ½ä¸æ”¯æŒMIME(多用途互è”网邮件扩 展)。 总的æ¥è¯´ï¼Œæœ€å¥½æ˜¯MIME摘è¦å½¢å¼ï¼Œä½†æ˜¯å¦‚果您ä¸èƒ½é˜…读它们,选 择普通文本摘è¦ã€‚ -

    - MIME
    - 普通文本

    - 设为全局 -

    +MIME
    +普通文本

    +设为全局 +

    - 接收您自己的信件?

    +

    +接收您自己的信件?

    一般æ¥è¯´ï¼Œæ‚¨ä¼šæ”¶åˆ°æ¯å°ä½ å¯„å¾€åˆ—è¡¨çš„ä¿¡ä»¶å‰¯æœ¬ã€‚å¦‚æžœæ‚¨ä¸æƒ³æ”¶åˆ°è¿™ 个副本,将该项设为å¦. -

    - å¦
    - 是 -
    - 接收您å‘到列表信件的确认信æ¯ï¼Ÿ

    -

    - å¦
    - 是 -
    - 获得列表的å£ä»¤æç¤ºå‡½

    +

    +å¦
    +是 +
    +接收您å‘到列表信件的确认信æ¯ï¼Ÿ

    +

    +å¦
    +是 +
    +获得列表的å£ä»¤æç¤ºå‡½

    æ¯ä¸ªæœˆï¼Œæ‚¨éƒ½ä¼šä»Žè®¢é˜…了邮件列表的主机那里收到一å°å£ä»¤æç¤ºå‡½ã€‚ 您å¯ä»¥é€ä¸ªåˆ—表地将该项置为å¦ã€‚如果您将您订阅所有列 表的å£ä»¤æé†’全部关闭,将ä¸ä¼šæœ‰ä»»ä½•çš„å£ä»¤æç¤ºå‡½å‘é€ç»™æ‚¨ã€‚ -

    - å¦
    - 是

    - 设为全局 -

    - 将您自己从订阅者列表中éšè—

    +

    +å¦
    +是

    +设为全局 +

    +将您自己从订阅者列表中éšè—

    当别人查看列表æˆå‘˜æ—¶ï¼Œæ‚¨çš„电å­é‚®ä»¶åœ°å€é€šå¸¸æ˜¯å¯è§çš„(æ¨¡ç³Šå¤„ç† è¿‡å¾—å½¢å¼ä»¥é¿å…垃圾邮件)。 å¦‚æžœæ‚¨ä¸æƒ³è®©æ‚¨çš„电å­é‚®ä»¶å‡ºçŽ°åœ¨åˆ— 表æˆå‘˜æ¸…å•中,将该项选为 是 。 -

    - å¦
    - 是 -
    - 您的语言?

    -

    - -
    - 您想订阅哪个è¯é¢˜åˆ†ç±»ï¼Ÿ

    +

    +å¦
    +是 +
    +您的语言?

    +

    + +
    +您想订阅哪个è¯é¢˜åˆ†ç±»ï¼Ÿ

    通过选择一个或多个è¯é¢˜ï¼Œæ‚¨å¯ä»¥å¯¹åˆ—表的信æ¯è¿›è¡Œè¿‡æ»¤ ,仅接收列表中的部分消æ¯ã€‚如果一æ¡ä¿¡æ¯æ»¡è¶³æ‚¨çš„选择 的任一è¯é¢˜ï¼Œæ‚¨å°±ä¼šæ”¶åˆ°å®ƒï¼Œå¦åˆ™å°†ä¸ä¼šæ”¶åˆ°ã€‚ @@ -240,12 +278,11 @@

    修改您的å£ä»¤

    如果一æ¡ä¿¡æ¯ä¸æ»¡è¶³ä»»ä½•è¯é¢˜ï¼ŒæŠ•递规则å–决于下é¢çš„ 设置。如果您没有选择任何感兴趣的è¯é¢˜ï¼Œæ‚¨ä¼šå¾—到å‘往列 表的所有消æ¯ã€‚ -

    - -
    - æ‚¨æƒ³æŽ¥æ”¶ä¸æ»¡è¶³ä»»ä½•è¯é¢˜è¿‡æ»¤å™¨çš„æ¶ˆæ¯å—?

    +

    + +
    +æ‚¨æƒ³æŽ¥æ”¶ä¸æ»¡è¶³ä»»ä½•è¯é¢˜è¿‡æ»¤å™¨çš„æ¶ˆæ¯å—?

    è¯¥é€‰é¡¹åªæœ‰åœ¨æ‚¨åœ¨ä¸Šé¢è®¢é˜…了至少一个è¯é¢˜æ—¶æ‰æœ‰æ•ˆã€‚ 它æè¿°äº†ä¸æ»¡è¶³ä»»ä½•è¯é¢˜è¿‡æ»¤å™¨çš„ä¿¡æ¯çš„默认投递规则。 @@ -254,13 +291,12 @@

    修改您的å£ä»¤

    如果您没有在上é¢é€‰æ‹©æ„Ÿå…´è¶£çš„è¯é¢˜ï¼Œé‚£ä¹ˆä½ ä¼šæ”¶åˆ° 寄往邮件列表的所有信件。 -

    - å¦
    - 是 -
    - æ‹’ç»ç›¸åŒçš„信件副本?

    +

    +å¦
    +是 +
    +æ‹’ç»ç›¸åŒçš„信件副本?

    当你被明确的列在信件头部的 To: 或 Cc: ï¼ˆå³æ‚¨å¯èƒ½æ”¶åˆ°ä¸¤å°åŒæ ·çš„信), @@ -272,21 +308,17 @@

    修改您的å£ä»¤

    æ¯ä¸ªå‰¯æœ¬éƒ½ä¼šæ·»åŠ ä¸€ä¸ªX-Mailman-Copy: yes的头部。 -
    - å¦
    - 是

    - 设为全局 -

    -
    -
    +å¦
    +是

    +设为全局 +

    +
    +
    -

    - - - - + + +

    diff --git a/templates/zh_CN/private.html b/templates/zh_CN/private.html index 05c46bdd..8a8d4593 100755 --- a/templates/zh_CN/private.html +++ b/templates/zh_CN/private.html @@ -1,33 +1,93 @@ - %(realname)s ç§æœ‰å½’æ¡£è®¤è¯ +%(realname)s ç§æœ‰å½’æ¡£è®¤è¯ - - -
    + + + %(message)s - - - - - - - - - - - - - - - -
    - %(realname)s ç§æœ‰å½’æ¡£è®¤è¯ -
    Email地å€ï¼š
    å£ä»¤ï¼š
    -
    -

    注æ„: 从此时开始,您的æµè§ˆå™¨åº”该å¯ç”¨ + + + + + + + + + + + + + + + +
    +%(realname)s ç§æœ‰å½’æ¡£è®¤è¯ +
    Email地å€ï¼š
    å£ä»¤ï¼š
    +
    +

    注æ„: 从此时开始,您的æµè§ˆå™¨åº”该å¯ç”¨ cookies,å¦åˆ™æ‚¨çš„任何æ“作都需è¦é‡æ–°è®¤è¯ã€‚

    在Mailmançš„ç§æœ‰å­˜æ¡£æŽ¥å£ä¸­ä½¿ç”¨äº†Session cookies ,由此您ä¸å¿…ä¸ºæ¯ @@ -36,21 +96,21 @@ å¼åœ°ä½¿å…¶è¿‡æœŸã€‚

    - - - - - - + + + +
    - Password Reminder -
    If you don't remember your password, enter your email address + + + + + + - - - - -
    +Password Reminder +
    If you don't remember your password, enter your email address above and click the Remind button and your password will be emailed to you.
    - +
    +

    diff --git a/templates/zh_CN/roster.html b/templates/zh_CN/roster.html index abfa13d3..ddb9cc52 100644 --- a/templates/zh_CN/roster.html +++ b/templates/zh_CN/roster.html @@ -1,50 +1,108 @@ - - - <MM-List-Name> 订阅者 - - - - -

    - - - - - - - - - - - - - - - -
    - - 订阅者 -
    - -

    -

    - -

    点击您的邮件地å€ä»¥è®¿é—®è®¢é˜…选项页
    (圆括å·ä¸­çš„地å€å·²ç»ç¦æ­¢æŠ•递)

    -
    -
    - ä¸­æœ‰ä½ - 䏿ޥ嗿‘˜è¦çš„æˆå‘˜: -
    -
    -
    - ä¸­æœ‰ä½ - 接收摘è¦çš„æˆå‘˜: -
    -
    -

    -

    -

    -

    - - - + + +<mm-list-name> 订阅者</mm-list-name> + + +

    + + + + + + + + + + + + + + + +
    + + 订阅者 +
    +

    +

    +

    点击您的邮件地å€ä»¥è®¿é—®è®¢é˜…选项页
    (圆括å·ä¸­çš„地å€å·²ç»ç¦æ­¢æŠ•递)

    +
    +
    +ä¸­æœ‰ä½ + 䏿ޥ嗿‘˜è¦çš„æˆå‘˜: +
    +
    +
    +ä¸­æœ‰ä½ + 接收摘è¦çš„æˆå‘˜: +
    +
    +

    +

    +

    +

    + +

    + diff --git a/templates/zh_CN/subscribe.html b/templates/zh_CN/subscribe.html index c83219df..a2bcf99f 100644 --- a/templates/zh_CN/subscribe.html +++ b/templates/zh_CN/subscribe.html @@ -1,8 +1,70 @@ -<MM-List-Name> 订阅结果 +<mm-list-name> 订阅结果</mm-list-name> -

    订阅结果

    - - - +

    订阅结果

    + + + diff --git a/templates/zh_TW/admindbpreamble.html b/templates/zh_TW/admindbpreamble.html index 133730f5..6c426f68 100644 --- a/templates/zh_TW/admindbpreamble.html +++ b/templates/zh_TW/admindbpreamble.html @@ -1,4 +1,66 @@ -論壇%(listname)s ä¸­æœ‰å¾…æ±ºæ¡ˆä»¶éœ€è¦æ‚¨çš„審核。 +論壇%(listname)s ä¸­æœ‰å¾…æ±ºæ¡ˆä»¶éœ€è¦æ‚¨çš„審核。 é¦–å…ˆæ‚¨æ‡‰åˆ°å¾…æ±ºæ¡ˆä»¶çš„ç¶²é æŸ¥è©¢ç›¸é—œè¨Šæ¯ã€‚

    è‹¥å±¬æ–¼è¨‚é–±çš„ç”³è«‹ï¼Œè«‹é¸æ“‡ Refuse 拒絕,或 @@ -12,11 +74,11 @@

  • Approve - 放行信件並é€ä¸Šè«–壇。
  • Reject - 退信並å‘å¯„ä¿¡äººç°¡çŸ­èªªæ˜Žé€€ä¿¡åŽŸå› ï¼Œè©²ä¿¡ä»¶ä¸æœƒé€ä¸Šè«–壇。
  • Discard - 逕行丟棄,此é¸é …å°æ–¼åžƒåœ¾éƒµä»¶å分有用。 - - +
  • 如果信件是給壇主,如果您è¦å­˜ä¸€ä»½å‰¯æœ¬æ™‚,將 Preserve é¸é …打開。 這é¸é …å°ç½µäººçš„信件很有用 。 如果您想將信件轉寄ä¸åœ¨æ­¤è«–壇的人士,將 Forward to é¸é …打開,並填上轉信地å€ã€‚

    當您完æˆå·¥ä½œï¼Œè«‹æŒ‰åœ¨ç¶²é ä¸Šæ–¹æˆ–下方的 Submit All Data 按鈕 ä»¥ä¾¿åŸ·è¡Œæ‚¨çš„æ±ºå®šã€‚å¦‚æžœæ‚¨ä¸æƒ³ç¾åœ¨ä½œæ±ºå®šï¼Œè«‹å‹¿æŒ‰æ­¤éˆ•。 ç¨å€™æ‚¨å¯ä»¥å†æ±ºå®šã€‚ +

    \ No newline at end of file diff --git a/templates/zh_TW/admlogin.html b/templates/zh_TW/admlogin.html index a1921c0c..96f2fc00 100755 --- a/templates/zh_TW/admlogin.html +++ b/templates/zh_TW/admlogin.html @@ -1,32 +1,94 @@ - %(listname)s 論壇 壇主驗證 +%(listname)s 論壇 壇主驗證 - + -
    + %(message)s - - - - - - - - - - - -
    - %(listname)s 論壇 壇主驗證 -
    壇主密碼:
    -
    -

    é‡é»ž: 從ç¾åœ¨é–‹å§‹ï¼Œ 您必須 + + + + + + + + + + + +
    +%(listname)s 論壇 壇主驗證 +
    壇主密碼:
    +
    +

    é‡é»ž: 從ç¾åœ¨é–‹å§‹ï¼Œ 您必須 å°‡ç€è¦½å™¨çš„ cookie 功能打開,å¦å‰‡æ‚¨æ‰€ä½œçš„ç•°å‹•å°‡ä¸æœƒç™¼ç”Ÿæ•ˆç”¨ã€‚

    Mailman 的管ç†ä»‹é¢ä½¿ç”¨äº† Session cookies , 因此您ä¸éœ€è¦åœ¨æ¯æ¬¡ ç•°å‹•è³‡æ–™æ™‚é‡æ–°é©—證密碼。此cookie 在您關閉ç€è¦½å™¨æ™‚自動失效, 或者您按下在 Other Administrative Activities ç¶²é ä¸­ Logout çš„è¶…é€£çµ (一旦您æˆåŠŸçš„ç™»éŒ„ä¹‹å¾Œï¼Œæ‚¨å°±å¯ä»¥çœ‹åˆ°)。 -

    +

    + \ No newline at end of file diff --git a/templates/zh_TW/handle_opts.html b/templates/zh_TW/handle_opts.html index aeedebc0..d7267a05 100644 --- a/templates/zh_TW/handle_opts.html +++ b/templates/zh_TW/handle_opts.html @@ -1,9 +1,71 @@ - -<MM-List-Name> <MM-Operation> Results + +<mm-list-name> <mm-operation> Results</mm-operation></mm-list-name> -

    Results

    - - - +

    Results

    + + + diff --git a/templates/zh_TW/headfoot.html b/templates/zh_TW/headfoot.html index ba34daa5..6908dc72 100644 --- a/templates/zh_TW/headfoot.html +++ b/templates/zh_TW/headfoot.html @@ -1,10 +1,72 @@ -本文å¯åŒ…å« %(attribute)s æ ¼å¼å­—ä¸²ï¼Œå…¶å¯æ›¿æ›ç‚ºé€šä¿¡è«–壇 +本文å¯åŒ…å« %(attribute)s æ ¼å¼å­—ä¸²ï¼Œå…¶å¯æ›¿æ›ç‚ºé€šä¿¡è«–壇 相應的屬性。詳情請åƒç…§ -Python +Python æ ¼å¼åŒ–字串è¦å‰‡. é è¨­å±¬æ€§å¦‚下:
      -
    • real_name - 本論壇 "較佳" çš„å稱; +
    • real_name - 本論壇 "較佳" çš„å稱; 通常都用大寫。
    • list_name - 此論壇å稱用於 URL 識別 @@ -20,4 +82,4 @@
    • description - 該通信論壇的摘è¦èªªæ˜Žã€‚
    • info - 該通信論壇的詳細說明。 -
    + diff --git a/templates/zh_TW/listinfo.html b/templates/zh_TW/listinfo.html index bc36e5f0..83252de7 100644 --- a/templates/zh_TW/listinfo.html +++ b/templates/zh_TW/listinfo.html @@ -1,133 +1,192 @@ - - - - <MM-List-Name> 相關資訊 - - - -

    - - - - - - - - - - - - - - - - - - - + + + + + +

    - -- - -
    -

      -

    - 關於 論壇 - - - -
    -

    -

    欲查本論壇舊信,請逕洽 - 檔案ä¿ç®¡è™•。 - -

    -
    - 使用 論壇 -
    - 欲在論壇發言,請寄到 。 + + + +<mm-list-name> 相關資訊</mm-list-name> + + +

    + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + +
    + -- + +
    +

      +

    +關於 論壇 + + + +
    +

    +

    欲查本論壇舊信,請逕洽 + 檔案ä¿ç®¡è™•。 + +

    +
    +使用 論壇 +
    + 欲在論壇發言,請寄到 。

    在下é¢çš„ç¶²é å¯è®“您å¯ä»¥åŠ å…¥é€™å€‹è«–å£‡ï¼Œæˆ–è®Šæ›´è¨‚é–±è¨­å®šã€‚ -

    - 加入 論壇 -
    -

    - 欲加入(訂閱) 論壇請ä¾åºå¡«å¦¥ä¸‹åˆ—資料。 - -

      - - - - - - - - + + + + + + - - - - - -
      您的 E-Mail: -  
      +

      +加入 論壇 +
      +

      + 欲加入(訂閱) 論壇請ä¾åºå¡«å¦¥ä¸‹åˆ—資料。 + +

        + + + + + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - - - -
        您的 E-Mail: + 
        ä½  å¿… é ˆ 輸 å…¥ 一 個 ç§ äºº 密 碼 以 ä¾› å– æ¶ˆ 訂 é–± 或 å…¶ ä»– 相 é—œ 設 定 çš„ 權 力 。 è«‹ å‹¿ 使 用 有 價 值 çš„ 密 碼 ! å›  為 å¶ è€Œ å¯ ä»¥ å°‡ 密 碼 以 明 碼 çš„ æ–¹ å¼ éƒµ 寄 給 您。 - - -
        輸入密碼: 
        確èªè¼¸å…¥å¯†ç¢¼: 
        您é©ç”¨çš„語言?  
        ä½  想 以 æ¯ æ—¥ 摘 è¦ çš„ å½¢ å¼ æ”¶ ä¿¡ å—Ž ? + + +
        輸入密碼: 
        確èªè¼¸å…¥å¯†ç¢¼: 
        您é©ç”¨çš„語言?  
        ä½  想 以 æ¯ æ—¥ 摘 è¦ çš„ å½¢ å¼ æ”¶ ä¿¡ å—Ž ? No - Yes -
        -
        -
        - -
      -
      - - 訂 閱 者 -
      - - - -

      - - - -

      - - - +
    No + Yes +
    +
    +
    + + +

    + + 訂 閱 者 +
    + + + +

    + + + +

    + +

    + diff --git a/templates/zh_TW/options.html b/templates/zh_TW/options.html index 051b0c72..6549a12b 100644 --- a/templates/zh_TW/options.html +++ b/templates/zh_TW/options.html @@ -1,152 +1,199 @@ -<MM-Presentable-User> <MM-List-Name>論壇設定 - - - - -
    - - 的設定 -
    - - +<mm-presentable-user> <mm-list-name>論壇設定</mm-list-name></mm-presentable-user> + + + +
    + + 的設定 +
    +

    - - - 在 論壇的訂閱狀態,密碼åŠé¸é …。 - - - - + + 在 論壇的訂閱狀態,密碼åŠé¸é …。 + +

    - - - - - - -
    -å–æ¶ˆè¨‚é–± - -您其它在 的訂閱 -
    + + + + +
    +å–æ¶ˆè¨‚é–± + +您其它在 的訂閱 +
    æ¬²å–æ¶ˆè¨‚閱請輸入您的密碼後按此按鈕(若您忘記密碼,請見下文如何å–得您的密碼)

    -密碼:   - - -

    +密碼:   + +

    +
    輸入您的密碼å¯é€£çµåˆ°æ‚¨åœ¨æœ¬è«–壇其它的訂閱的設定畫é¢ã€‚

    -密碼:

    - -

    - - - - +密碼:

    +

    +
    -您在 的密碼 -
    + + - - +
    +您在 的密碼 +
    - + +

    忘記密碼怎麼辦?

    按下此鈕å¯å°‡æ‚¨çš„密碼郵寄給您。

    - +

    - -
    -
    - + + +

    +

    變更密碼

    - - - - - - - - - - - - - +
    舊密碼:
    新密碼:
    å†è¼¸å…¥ä¸€æ¬¡æ–°å¯†ç¢¼:
    + + + + + + + + + + + +
    舊密碼:
    新密碼:
    å†è¼¸å…¥ä¸€æ¬¡æ–°å¯†ç¢¼:
    - -
    - + +

    - - - -
    -Your 訂閱é¸é … -
    - +
    + +
    +Your 訂閱é¸é … +

    ç¾åœ¨çš„設定值有作記號。

    - - + + +
    + + - - +一旦您將é¸é …ç”± On 轉為 Off 您會收到最近一å°çš„æ‘˜è¦ã€‚
    + Off + On
    +
    + - - + - - -
    - æš«åœæ”¶ä¿¡
    -å¦‚æžœæ‚¨çŸ­æœŸå…§ä¸æƒ³æ”¶åˆ°è«–å£‡å¯„å‡ºçš„ä¿¡ï¼Œè«‹é¸ On。
    + æš«åœæ”¶ä¿¡
    +å¦‚æžœæ‚¨çŸ­æœŸå…§ä¸æƒ³æ”¶åˆ°è«–å£‡å¯„å‡ºçš„ä¿¡ï¼Œè«‹é¸ On。
    Off On

    - +

    - è¨­å®šæ‘˜è¦æ¨¡å¼
    +
    + è¨­å®šæ‘˜è¦æ¨¡å¼
    è‹¥æ‚¨é¸æ“‡æ‘˜è¦æ¨¡å¼ï¼Œæ‚¨æœƒæ”¶åˆ°æ¯å¤©é›†æˆä¸€å°çš„æ‘˜è¦ï¼Œè€Œä¸æ˜¯æ¯æ¬¡ç™¼è¨€ä¸€å°ã€‚ -一旦您將é¸é …ç”± On 轉為 Off 您會收到最近一å°çš„æ‘˜è¦ã€‚
    - Off - On
    -
    - 收 MIME 或 純文字的摘�
    -如果您收 MIME çš„æ‘˜è¦æœƒæœ‰å•é¡Œï¼Œè«‹é¸æ“‡ç´”文字的摘è¦ã€‚
    - MIME - 純文字

    -

    + 收 MIME 或 純文字的摘�
    +如果您收 MIME çš„æ‘˜è¦æœƒæœ‰å•é¡Œï¼Œè«‹é¸æ“‡ç´”文字的摘è¦ã€‚
    + MIME + 純文字

    +

    - 您想收到自己寄到論壇的信嗎?
    +
    + 您想收到自己寄到論壇的信嗎?
    Yes No

    -

    - 您想在寄信到論壇後,收到確èªä¿¡å—Ž?
    +

    + 您想在寄信到論壇後,收到確èªä¿¡å—Ž?
    No Yes

    -

    - æ‚¨ä¸æƒ³å‡ºç¾åœ¨è¨‚閱者å單中嗎? 就是潛艇部隊啦!
    - No - Yes

    -

    - 您é©ç”¨çš„語言? -
    -密碼:

    -

    - - +

    + æ‚¨ä¸æƒ³å‡ºç¾åœ¨è¨‚閱者å單中嗎? 就是潛艇部隊啦!
    + No + Yes

    +

    + 您é©ç”¨çš„語言? +
    +密碼:

    +

    - - - - + + +

    diff --git a/templates/zh_TW/roster.html b/templates/zh_TW/roster.html index b2005268..73244040 100644 --- a/templates/zh_TW/roster.html +++ b/templates/zh_TW/roster.html @@ -1,50 +1,110 @@ - - - <MM-List-Name> 訂閱會員 - - - - -

    - - - - - - - - - - - - - - - -
    - - 訂閱會員一覽表 -
    -

    -

    -

    點 é¸ æ‚¨ çš„ email å¯ åˆ° 個 人 çš„ 訂 é–± 設 定 ç¶² é  ã€‚ -
    (括 號 括 èµ· 來 çš„ 代 表 è«‹ å‡ ä¸­ ( æš« åœ æ”¶ ä¿¡ )。)

    -
    -
    - 一 般 會 員 : - 人 -
    -
    -
    - - æ¯ æ—¥ åˆ å¯„ : 人 -
    -
    -

    -

    -

    -

    - - - + + +<mm-list-name> 訂閱會員</mm-list-name> + + +

    + + + + + + + + + + + + + + + +
    + + 訂閱會員一覽表 +
    +

    +

    +

    點 é¸ æ‚¨ çš„ email å¯ åˆ° 個 人 çš„ 訂 é–± 設 定 ç¶² é  ã€‚ +
    (括 號 括 èµ· 來 çš„ 代 表 è«‹ å‡ ä¸­ ( æš« åœ æ”¶ ä¿¡ )。)

    +
    +
    + 一 般 會 員 : + 人 +
    +
    +
    + + æ¯ æ—¥ åˆ å¯„ : 人 +
    +
    +

    +

    +

    +

    + +

    + diff --git a/templates/zh_TW/subscribe.html b/templates/zh_TW/subscribe.html index c2229691..39d3e227 100644 --- a/templates/zh_TW/subscribe.html +++ b/templates/zh_TW/subscribe.html @@ -1,9 +1,71 @@ -<MM-List-Name> è¨‚é–±çµæžœ +<mm-list-name> è¨‚é–±çµæžœ</mm-list-name> -

    訂 é–± çµ æžœ

    - - - +

    訂 é–± çµ æžœ

    + + + diff --git a/tests/onebounce.py b/tests/onebounce.py index af1ca3f9..c2983d3c 100644 --- a/tests/onebounce.py +++ b/tests/onebounce.py @@ -36,7 +36,7 @@ import sys import email -import getopt +import argparse import paths from Mailman.Bouncers import BouncerAPI @@ -45,32 +45,21 @@ COMMASPACE = ', ' - -def usage(code, msg=''): - print(__doc__ % globals()) - if msg: - print(msg) - sys.exit(code) +def parse_args(): + parser = argparse.ArgumentParser(description='Test the bounce detection for files containing bounces.') + parser.add_argument('-v', '--verbose', action='store_true', + help='Verbose output') + parser.add_argument('-a', '--all', action='store_true', + help='Run the message through all the bounce modules') + parser.add_argument('files', nargs='+', + help='Files to process') + return parser.parse_args() - def main(): - try: - opts, args = getopt.getopt(sys.argv[1:], 'hva', - ['help', 'verbose', 'all']) - except getopt.error as msg: - usage(1, msg) - - all = verbose = 0 - for opt, arg in opts: - if opt in ('-h', '--help'): - usage(0) - elif opt in ('-v', '--verbose'): - verbose = 1 - elif opt in ('-a', '--all'): - all = 1 - - for file in args: + args = parse_args() + + for file in args.files: fp = open(file) msg = email.message_from_file(fp) fp.close() @@ -80,18 +69,17 @@ def main(): addrs = sys.modules[modname].process(msg) if addrs is BouncerAPI.Stop: print(module, 'got a Stop') - if not all: + if not args.all: break continue if not addrs: - if verbose: + if args.verbose: print(module, 'found no matches') else: print(module, 'found', COMMASPACE.join(addrs)) - if not all: + if not args.all: break - if __name__ == '__main__': main() diff --git a/tests/test_handlers.py b/tests/test_handlers.py index 999d40b3..3f5fcfae 100644 --- a/tests/test_handlers.py +++ b/tests/test_handlers.py @@ -35,41 +35,42 @@ from Mailman import mm_cfg from Mailman.MailList import MailList -from Mailman import Message +from Mailman.Message import Message from Mailman import Errors from Mailman import Pending from Mailman.Queue.Switchboard import Switchboard +import Mailman.Handlers.Acknowledge as Acknowledge +import Mailman.Handlers.CookHeaders as CookHeaders +import Mailman.Handlers.ToDigest as ToDigest +import Mailman.Handlers.ToUsenet as ToUsenet +import Mailman.Handlers.Decorate as Decorate +import Mailman.Handlers.Cleanse as Cleanse +import Mailman.Handlers.CleanseDKIM as CleanseDKIM +import Mailman.Handlers.AvoidDuplicates as AvoidDuplicates +import Mailman.Handlers.CalcRecips as CalcRecips +import Mailman.Handlers.ToArchive as ToArchive +import Mailman.Handlers.AfterDelivery as AfterDelivery +import Mailman.Handlers.WrapMessage as WrapMessage +import Mailman.Handlers.HandleBouncingAddresses as HandleBouncingAddresses +import Mailman.Handlers.Moderate as Moderate +import Mailman.Handlers.Replybot as Replybot +import Mailman.Handlers.Tagger as Tagger +import Mailman.Handlers.FileRecips as FileRecips +import Mailman.Handlers.MimeDel as MimeDel +import Mailman.Handlers.Scrubber as Scrubber +import Mailman.Handlers.SMTPDirect as SMTPDirect +import Mailman.Handlers.SpamDetect as SpamDetect +import Mailman.Handlers.Hold as Hold +import Mailman.Handlers.Approve as Approve +import Mailman.Handlers.ToOutgoing as ToOutgoing + +from Mailman.tests.test_bounces import TestBase + -from Mailman.Handlers import Acknowledge -from Mailman.Handlers import AfterDelivery -from Mailman.Handlers import Approve -from Mailman.Handlers import CalcRecips -from Mailman.Handlers import Cleanse -from Mailman.Handlers import CookHeaders -from Mailman.Handlers import Decorate -from Mailman.Handlers import FileRecips -from Mailman.Handlers import Hold -from Mailman.Handlers import MimeDel -from Mailman.Handlers import Moderate -from Mailman.Handlers import Replybot -# Don't test handlers such as SMTPDirect and Sendmail here -from Mailman.Handlers import SpamDetect -from Mailman.Handlers import Tagger -from Mailman.Handlers import ToArchive -from Mailman.Handlers import ToDigest -from Mailman.Handlers import ToOutgoing -from Mailman.Handlers import ToUsenet -from Mailman.Utils import sha_new - -from TestBase import TestBase - - - def password(plaintext): return sha_new(plaintext).hexdigest() - class TestAcknowledge(TestBase): def setUp(self): TestBase.setUp(self) @@ -92,7 +93,7 @@ def test_no_ack_msgdata(self): msg = email.message_from_string("""\ From: aperson@dom.ain -""", Message.Message) +""") Acknowledge.process(self._mlist, msg, {'original_sender': 'aperson@dom.ain'}) eq(len(self._sb.files()), 0) @@ -104,7 +105,7 @@ def test_no_ack_not_a_member(self): msg = email.message_from_string("""\ From: bperson@dom.ain -""", Message.Message) +""") Acknowledge.process(self._mlist, msg, {'original_sender': 'bperson@dom.ain'}) eq(len(self._sb.files()), 0) @@ -115,7 +116,7 @@ def test_no_ack_sender(self): msg = email.message_from_string("""\ From: aperson@dom.ain -""", Message.Message) +""") Acknowledge.process(self._mlist, msg, {}) eq(len(self._sb.files()), 0) @@ -127,7 +128,7 @@ def test_ack_no_subject(self): msg = email.message_from_string("""\ From: aperson@dom.ain -""", Message.Message) +""") Acknowledge.process(self._mlist, msg, {}) files = self._sb.files() eq(len(files), 1) @@ -167,7 +168,7 @@ def test_ack_with_subject(self): From: aperson@dom.ain Subject: Wish you were here -""", Message.Message) +""") Acknowledge.process(self._mlist, msg, {}) files = self._sb.files() eq(len(files), 1) @@ -198,1977 +199,1002 @@ def test_ack_with_subject(self): # Make sure we dequeued the only message eq(len(self._sb.files()), 0) + def test_ack_with_subject_and_headers(self): + eq = self.assertEqual + self._mlist.setMemberOption( + 'aperson@dom.ain', mm_cfg.AcknowledgePosts, 1) + eq(len(self._sb.files()), 0) + msg = email.message_from_string("""\ +From: aperson@dom.ain +Subject: Wish you were here +Date: Mon, 01 Jan 2001 00:00:00 -0000 +Message-ID: - -class TestAfterDelivery(TestBase): - # Both msg and msgdata are ignored - def test_process(self): - mlist = self._mlist - last_post_time = mlist.last_post_time - post_id = mlist.post_id - AfterDelivery.process(mlist, None, None) - self.failUnless(mlist.last_post_time > last_post_time) - self.assertEqual(mlist.post_id, post_id + 1) - +""") + Acknowledge.process(self._mlist, msg, {}) + files = self._sb.files() + eq(len(files), 1) + qmsg, qdata = self._sb.dequeue(files[0]) + # Check the .db file + eq(qdata.get('listname'), '_xtest') + eq(qdata.get('recips'), ['aperson@dom.ain']) + eq(qdata.get('version'), 3) + # Check the .pck + eq(str(qmsg['subject']), '_xtest post acknowledgement') + eq(qmsg['to'], 'aperson@dom.ain') + eq(qmsg['from'], '_xtest-bounces@dom.ain') + eq(qmsg.get_content_type(), 'text/plain') + eq(qmsg.get_param('charset'), 'us-ascii') + msgid = qmsg['message-id'] + self.failUnless(msgid.startswith('')) + eq(qmsg.get_payload(), """\ +Your message entitled - -class TestApprove(TestBase): - def test_short_circuit(self): - msgdata = {'approved': 1} - rtn = Approve.process(self._mlist, Message.Message(), msgdata) - # Not really a great test, but there's little else to assert - self.assertEqual(rtn, None) + Wish you were here - def test_approved_moderator(self): - mlist = self._mlist - mlist.mod_password = password('wazoo') - msg = email.message_from_string("""\ -Approved: wazoo +was successfully received by the _xtest mailing list. +List info page: http://www.dom.ain/mailman/listinfo/_xtest +Your preferences: http://www.dom.ain/mailman/options/_xtest/aperson%40dom.ain """) - msgdata = {} - Approve.process(mlist, msg, msgdata) - self.failUnless('approved' in msgdata) - self.assertEqual(msgdata['approved'], 1) + # Make sure we dequeued the only message + eq(len(self._sb.files()), 0) - def test_approve_moderator(self): - mlist = self._mlist - mlist.mod_password = password('wazoo') + def test_ack_with_subject_and_body(self): + eq = self.assertEqual + self._mlist.setMemberOption( + 'aperson@dom.ain', mm_cfg.AcknowledgePosts, 1) + eq(len(self._sb.files()), 0) msg = email.message_from_string("""\ -Approve: wazoo +From: aperson@dom.ain +Subject: Wish you were here +Date: Mon, 01 Jan 2001 00:00:00 -0000 +Message-ID: +This is the body of the message. """) - msgdata = {} - Approve.process(mlist, msg, msgdata) - self.failUnless('approved' in msgdata) - self.assertEqual(msgdata['approved'], 1) + Acknowledge.process(self._mlist, msg, {}) + files = self._sb.files() + eq(len(files), 1) + qmsg, qdata = self._sb.dequeue(files[0]) + # Check the .db file + eq(qdata.get('listname'), '_xtest') + eq(qdata.get('recips'), ['aperson@dom.ain']) + eq(qdata.get('version'), 3) + # Check the .pck + eq(str(qmsg['subject']), '_xtest post acknowledgement') + eq(qmsg['to'], 'aperson@dom.ain') + eq(qmsg['from'], '_xtest-bounces@dom.ain') + eq(qmsg.get_content_type(), 'text/plain') + eq(qmsg.get_param('charset'), 'us-ascii') + msgid = qmsg['message-id'] + self.failUnless(msgid.startswith('')) + eq(qmsg.get_payload(), """\ +Your message entitled - def test_approved_admin(self): - mlist = self._mlist - mlist.password = password('wazoo') - msg = email.message_from_string("""\ -Approved: wazoo + Wish you were here + +was successfully received by the _xtest mailing list. +List info page: http://www.dom.ain/mailman/listinfo/_xtest +Your preferences: http://www.dom.ain/mailman/options/_xtest/aperson%40dom.ain """) - msgdata = {} - Approve.process(mlist, msg, msgdata) - self.failUnless('approved' in msgdata) - self.assertEqual(msgdata['approved'], 1) + # Make sure we dequeued the only message + eq(len(self._sb.files()), 0) - def test_approve_admin(self): - mlist = self._mlist - mlist.password = password('wazoo') + def test_ack_with_subject_and_body_and_headers(self): + eq = self.assertEqual + self._mlist.setMemberOption( + 'aperson@dom.ain', mm_cfg.AcknowledgePosts, 1) + eq(len(self._sb.files()), 0) msg = email.message_from_string("""\ -Approve: wazoo +From: aperson@dom.ain +Subject: Wish you were here +Date: Mon, 01 Jan 2001 00:00:00 -0000 +Message-ID: +Content-Type: text/plain; charset=us-ascii +This is the body of the message. """) - msgdata = {} - Approve.process(mlist, msg, msgdata) - self.failUnless('approved' in msgdata) - self.assertEqual(msgdata['approved'], 1) + Acknowledge.process(self._mlist, msg, {}) + files = self._sb.files() + eq(len(files), 1) + qmsg, qdata = self._sb.dequeue(files[0]) + # Check the .db file + eq(qdata.get('listname'), '_xtest') + eq(qdata.get('recips'), ['aperson@dom.ain']) + eq(qdata.get('version'), 3) + # Check the .pck + eq(str(qmsg['subject']), '_xtest post acknowledgement') + eq(qmsg['to'], 'aperson@dom.ain') + eq(qmsg['from'], '_xtest-bounces@dom.ain') + eq(qmsg.get_content_type(), 'text/plain') + eq(qmsg.get_param('charset'), 'us-ascii') + msgid = qmsg['message-id'] + self.failUnless(msgid.startswith('')) + eq(qmsg.get_payload(), """\ +Your message entitled - def test_unapproved(self): - mlist = self._mlist - mlist.password = password('zoowa') - msg = email.message_from_string("""\ -Approve: wazoo + Wish you were here + +was successfully received by the _xtest mailing list. +List info page: http://www.dom.ain/mailman/listinfo/_xtest +Your preferences: http://www.dom.ain/mailman/options/_xtest/aperson%40dom.ain """) - msgdata = {} - Approve.process(mlist, msg, msgdata) - self.assertEqual(msgdata.get('approved'), None) + # Make sure we dequeued the only message + eq(len(self._sb.files()), 0) - def test_trip_beentheres(self): - mlist = self._mlist + def test_ack_with_subject_and_body_and_headers_and_attachments(self): + eq = self.assertEqual + self._mlist.setMemberOption( + 'aperson@dom.ain', mm_cfg.AcknowledgePosts, 1) + eq(len(self._sb.files()), 0) msg = email.message_from_string("""\ -X-BeenThere: %s +From: aperson@dom.ain +Subject: Wish you were here +Date: Mon, 01 Jan 2001 00:00:00 -0000 +Message-ID: +Content-Type: multipart/mixed; boundary="===============1234567890==" -""" % mlist.GetListEmail()) - self.assertRaises(Errors.LoopError, Approve.process, mlist, msg, {}) +--===============1234567890== +Content-Type: text/plain; charset=us-ascii +This is the body of the message. - -class TestCalcRecips(TestBase): - def setUp(self): - TestBase.setUp(self) - # Add a bunch of regular members - mlist = self._mlist - mlist.addNewMember('aperson@dom.ain') - mlist.addNewMember('bperson@dom.ain') - mlist.addNewMember('cperson@dom.ain') - # And a bunch of digest members - mlist.addNewMember('dperson@dom.ain', digest=1) - mlist.addNewMember('eperson@dom.ain', digest=1) - mlist.addNewMember('fperson@dom.ain', digest=1) +--===============1234567890== +Content-Type: application/octet-stream +Content-Disposition: attachment; filename="test.txt" - def test_short_circuit(self): - msgdata = {'recips': 1} - rtn = CalcRecips.process(self._mlist, None, msgdata) - # Not really a great test, but there's little else to assert - self.assertEqual(rtn, None) +This is an attachment. +--===============1234567890==-- +""") + Acknowledge.process(self._mlist, msg, {}) + files = self._sb.files() + eq(len(files), 1) + qmsg, qdata = self._sb.dequeue(files[0]) + # Check the .db file + eq(qdata.get('listname'), '_xtest') + eq(qdata.get('recips'), ['aperson@dom.ain']) + eq(qdata.get('version'), 3) + # Check the .pck + eq(str(qmsg['subject']), '_xtest post acknowledgement') + eq(qmsg['to'], 'aperson@dom.ain') + eq(qmsg['from'], '_xtest-bounces@dom.ain') + eq(qmsg.get_content_type(), 'text/plain') + eq(qmsg.get_param('charset'), 'us-ascii') + msgid = qmsg['message-id'] + self.failUnless(msgid.startswith('')) + eq(qmsg.get_payload(), """\ +Your message entitled - def test_simple_path(self): - msgdata = {} - msg = email.message_from_string("""\ -From: dperson@dom.ain + Wish you were here -""", Message.Message) - CalcRecips.process(self._mlist, msg, msgdata) - self.failUnless('recips' in msgdata) - recips = msgdata['recips'] - recips.sort() - self.assertEqual(recips, ['aperson@dom.ain', 'bperson@dom.ain', - 'cperson@dom.ain']) +was successfully received by the _xtest mailing list. - def test_exclude_sender(self): - msgdata = {} +List info page: http://www.dom.ain/mailman/listinfo/_xtest +Your preferences: http://www.dom.ain/mailman/options/_xtest/aperson%40dom.ain +""") + # Make sure we dequeued the only message + eq(len(self._sb.files()), 0) + + def test_ack_with_subject_and_body_and_headers_and_attachments_and_embedded(self): + eq = self.assertEqual + self._mlist.setMemberOption( + 'aperson@dom.ain', mm_cfg.AcknowledgePosts, 1) + eq(len(self._sb.files()), 0) msg = email.message_from_string("""\ -From: cperson@dom.ain +From: aperson@dom.ain +Subject: Wish you were here +Date: Mon, 01 Jan 2001 00:00:00 -0000 +Message-ID: +Content-Type: multipart/mixed; boundary="===============1234567890==" -""", Message.Message) - self._mlist.setMemberOption('cperson@dom.ain', - mm_cfg.DontReceiveOwnPosts, 1) - CalcRecips.process(self._mlist, msg, msgdata) - self.failUnless('recips' in msgdata) - recips = msgdata['recips'] - recips.sort() - self.assertEqual(recips, ['aperson@dom.ain', 'bperson@dom.ain']) +--===============1234567890== +Content-Type: multipart/alternative; boundary="===============0987654321==" - def test_urgent_moderator(self): - self._mlist.mod_password = password('xxXXxx') - msgdata = {} - msg = email.message_from_string("""\ -From: dperson@dom.ain -Urgent: xxXXxx +--===============0987654321== +Content-Type: text/plain; charset=us-ascii -""", Message.Message) - CalcRecips.process(self._mlist, msg, msgdata) - self.failUnless('recips' in msgdata) - recips = msgdata['recips'] - recips.sort() - self.assertEqual(recips, ['aperson@dom.ain', 'bperson@dom.ain', - 'cperson@dom.ain', 'dperson@dom.ain', - 'eperson@dom.ain', 'fperson@dom.ain']) +This is the body of the message. - def test_urgent_admin(self): - self._mlist.mod_password = password('yyYYyy') - self._mlist.password = password('xxXXxx') - msgdata = {} - msg = email.message_from_string("""\ -From: dperson@dom.ain -Urgent: xxXXxx - -""", Message.Message) - CalcRecips.process(self._mlist, msg, msgdata) - self.failUnless('recips' in msgdata) - recips = msgdata['recips'] - recips.sort() - self.assertEqual(recips, ['aperson@dom.ain', 'bperson@dom.ain', - 'cperson@dom.ain', 'dperson@dom.ain', - 'eperson@dom.ain', 'fperson@dom.ain']) - - def test_urgent_reject(self): - self._mlist.mod_password = password('yyYYyy') - self._mlist.password = password('xxXXxx') - msgdata = {} - msg = email.message_from_string("""\ -From: dperson@dom.ain -Urgent: zzZZzz - -""", Message.Message) - self.assertRaises(Errors.RejectMessage, - CalcRecips.process, - self._mlist, msg, msgdata) - - # BAW: must test the do_topic_filters() path... - - - -class TestCleanse(TestBase): - def setUp(self): - TestBase.setUp(self) - - def test_simple_cleanse(self): - eq = self.assertEqual - msg = email.message_from_string("""\ -From: aperson@dom.ain -Approved: yes -Urgent: indeed -Reply-To: bperson@dom.ain -Sender: asystem@dom.ain -Return-Receipt-To: another@dom.ain -Disposition-Notification-To: athird@dom.ain -X-Confirm-Reading-To: afourth@dom.ain -X-PMRQC: afifth@dom.ain -Subject: a message to you - -""", Message.Message) - Cleanse.process(self._mlist, msg, {}) - eq(msg['approved'], None) - eq(msg['urgent'], None) - eq(msg['return-receipt-to'], None) - eq(msg['disposition-notification-to'], None) - eq(msg['x-confirm-reading-to'], None) - eq(msg['x-pmrqc'], None) - eq(msg['from'], 'aperson@dom.ain') - eq(msg['reply-to'], 'bperson@dom.ain') - eq(msg['sender'], 'asystem@dom.ain') - eq(msg['subject'], 'a message to you') - - def test_anon_cleanse(self): - eq = self.assertEqual - msg = email.message_from_string("""\ -From: aperson@dom.ain -Approved: yes -Urgent: indeed -Reply-To: bperson@dom.ain -Sender: asystem@dom.ain -Return-Receipt-To: another@dom.ain -Disposition-Notification-To: athird@dom.ain -X-Confirm-Reading-To: afourth@dom.ain -X-PMRQC: afifth@dom.ain -Subject: a message to you - -""", Message.Message) - self._mlist.anonymous_list = 1 - Cleanse.process(self._mlist, msg, {}) - eq(msg['approved'], None) - eq(msg['urgent'], None) - eq(msg['return-receipt-to'], None) - eq(msg['disposition-notification-to'], None) - eq(msg['x-confirm-reading-to'], None) - eq(msg['x-pmrqc'], None) - eq(len(msg.get_all('from')), 1) - eq(len(msg.get_all('reply-to')), 1) - eq(msg['from'], '_xtest@dom.ain') - eq(msg['reply-to'], '_xtest@dom.ain') - eq(msg['sender'], None) - eq(msg['subject'], 'a message to you') - - - -class TestCookHeaders(TestBase): - def test_transform_noack_to_xack(self): - eq = self.assertEqual - msg = email.message_from_string("""\ -X-Ack: yes - -""", Message.Message) - CookHeaders.process(self._mlist, msg, {'noack': 1}) - eq(len(msg.get_all('x-ack')), 1) - eq(msg['x-ack'], 'no') - - def test_original_sender(self): - msg = email.message_from_string("""\ -From: aperson@dom.ain - -""", Message.Message) - msgdata = {} - CookHeaders.process(self._mlist, msg, msgdata) - self.assertEqual(msgdata.get('original_sender'), 'aperson@dom.ain') - - def test_no_original_sender(self): - msg = email.message_from_string("""\ -Subject: about this message - -""", Message.Message) - msgdata = {} - CookHeaders.process(self._mlist, msg, msgdata) - self.assertEqual(msgdata.get('original_sender'), '') +--===============0987654321== +Content-Type: text/html; charset=us-ascii - def test_xbeenthere(self): - msg = email.message_from_string("""\ -From: aperson@dom.ain + + +This is the body of the message. + + -""", Message.Message) - CookHeaders.process(self._mlist, msg, {}) - self.assertEqual(msg['x-beenthere'], '_xtest@dom.ain') - - def test_multiple_xbeentheres(self): - eq = self.assertEqual - msg = email.message_from_string("""\ -From: aperson@dom.ain -X-BeenThere: alist@another.dom.ain - -""", Message.Message) - CookHeaders.process(self._mlist, msg, {}) - eq(len(msg.get_all('x-beenthere')), 2) - beentheres = msg.get_all('x-beenthere') - beentheres.sort() - eq(beentheres, ['_xtest@dom.ain', 'alist@another.dom.ain']) - - def test_nonexisting_mmversion(self): - eq = self.assertEqual - msg = email.message_from_string("""\ -From: aperson@dom.ain - -""", Message.Message) - CookHeaders.process(self._mlist, msg, {}) - eq(msg['x-mailman-version'], mm_cfg.VERSION) - - def test_existing_mmversion(self): - eq = self.assertEqual - msg = email.message_from_string("""\ -From: aperson@dom.ain -X-Mailman-Version: 3000 - -""", Message.Message) - CookHeaders.process(self._mlist, msg, {}) - eq(len(msg.get_all('x-mailman-version')), 1) - eq(msg['x-mailman-version'], '3000') - - def test_nonexisting_precedence(self): - eq = self.assertEqual - msg = email.message_from_string("""\ -From: aperson@dom.ain - -""", Message.Message) - CookHeaders.process(self._mlist, msg, {}) - eq(msg['precedence'], 'list') +--===============0987654321==-- - def test_existing_precedence(self): - eq = self.assertEqual - msg = email.message_from_string("""\ -From: aperson@dom.ain -Precedence: junk - -""", Message.Message) - CookHeaders.process(self._mlist, msg, {}) - eq(len(msg.get_all('precedence')), 1) - eq(msg['precedence'], 'junk') - - def test_subject_munging_no_subject(self): - self._mlist.subject_prefix = '[XTEST] ' - msg = email.message_from_string("""\ -From: aperson@dom.ain - -""", Message.Message) - msgdata = {} - CookHeaders.process(self._mlist, msg, msgdata) - self.assertEqual(msgdata.get('origsubj'), '') - self.assertEqual(str(msg['subject']), '[XTEST] (no subject)') - - def test_subject_munging(self): - self._mlist.subject_prefix = '[XTEST] ' - msg = email.message_from_string("""\ -From: aperson@dom.ain -Subject: About Mailman... - -""", Message.Message) - CookHeaders.process(self._mlist, msg, {}) - self.assertEqual(str(msg['subject']), '[XTEST] About Mailman...') - - def test_no_subject_munging_for_digests(self): - self._mlist.subject_prefix = '[XTEST] ' - msg = email.message_from_string("""\ -From: aperson@dom.ain -Subject: About Mailman... - -""", Message.Message) - CookHeaders.process(self._mlist, msg, {'isdigest': 1}) - self.assertEqual(msg['subject'], 'About Mailman...') - - def test_no_subject_munging_for_fasttrack(self): - self._mlist.subject_prefix = '[XTEST] ' - msg = email.message_from_string("""\ -From: aperson@dom.ain -Subject: About Mailman... - -""", Message.Message) - CookHeaders.process(self._mlist, msg, {'_fasttrack': 1}) - self.assertEqual(msg['subject'], 'About Mailman...') - - def test_no_subject_munging_has_prefix(self): - self._mlist.subject_prefix = '[XTEST] ' - msg = email.message_from_string("""\ -From: aperson@dom.ain -Subject: Re: [XTEST] About Mailman... +--===============1234567890== +Content-Type: application/octet-stream +Content-Disposition: attachment; filename="test.txt" -""", Message.Message) - CookHeaders.process(self._mlist, msg, {}) - # prefixing depends on mm_cfg.py - self.failUnless(str(msg['subject']) == 'Re: [XTEST] About Mailman...' or - str(msg['subject']) == '[XTEST] Re: About Mailman...') - - def test_reply_to_list(self): - eq = self.assertEqual - mlist = self._mlist - mlist.reply_goes_to_list = 1 - mlist.from_is_list = 0 - msg = email.message_from_string("""\ -From: aperson@dom.ain - -""", Message.Message) - msgdata = {} - CookHeaders.process(mlist, msg, msgdata) - eq(msgdata['add_header']['Reply-To'], '_xtest@dom.ain') - eq(msg.get_all('reply-to'), None) - - def test_reply_to_list_fil(self): - eq = self.assertEqual - mlist = self._mlist - mlist.reply_goes_to_list = 1 - mlist.from_is_list = 1 - msg = email.message_from_string("""\ -From: aperson@dom.ain - -""", Message.Message) - msgdata = {} - CookHeaders.process(mlist, msg, msgdata) - eq(msgdata['add_header']['Reply-To'], - '_xtest@dom.ain') - eq(msgdata['add_header']['Cc'], - 'aperson@dom.ain') - eq(msg.get_all('reply-to'), None) - eq(msg.get_all('cc'), None) - - def test_reply_to_list_with_strip(self): - eq = self.assertEqual - mlist = self._mlist - mlist.reply_goes_to_list = 1 - mlist.first_strip_reply_to = 1 - mlist.from_is_list = 0 - msg = email.message_from_string("""\ -From: aperson@dom.ain -Reply-To: bperson@dom.ain - -""", Message.Message) - msgdata = {} - CookHeaders.process(mlist, msg, msgdata) - eq(msgdata['add_header']['Reply-To'], '_xtest@dom.ain') - eq(msg.get_all('reply-to'), ['bperson@dom.ain']) - - def test_reply_to_list_with_strip_fil(self): - eq = self.assertEqual - mlist = self._mlist - mlist.reply_goes_to_list = 1 - mlist.first_strip_reply_to = 1 - mlist.from_is_list = 1 - msg = email.message_from_string("""\ -From: aperson@dom.ain -Reply-To: bperson@dom.ain - -""", Message.Message) - msgdata = {} - CookHeaders.process(mlist, msg, msgdata) - eq(msgdata['add_header']['Reply-To'], - '_xtest@dom.ain') - eq(msgdata['add_header']['Cc'], - 'aperson@dom.ain') - eq(msg.get_all('reply-to'), ['bperson@dom.ain']) - eq(msg.get_all('cc'), None) - - - def test_reply_to_explicit(self): - eq = self.assertEqual - mlist = self._mlist - mlist.reply_goes_to_list = 2 - mlist.from_is_list = 0 - mlist.reply_to_address = 'mlist@dom.ain' - msg = email.message_from_string("""\ -From: aperson@dom.ain - -""", Message.Message) - msgdata = {} - CookHeaders.process(mlist, msg, msgdata) - eq(msgdata['add_header']['Reply-To'], 'mlist@dom.ain') - eq(msg.get_all('reply-to'), None) - - def test_reply_to_explicit_fil(self): - eq = self.assertEqual - mlist = self._mlist - mlist.reply_goes_to_list = 2 - mlist.from_is_list = 1 - mlist.reply_to_address = 'mlist@dom.ain' - msg = email.message_from_string("""\ -From: aperson@dom.ain - -""", Message.Message) - msgdata = {} - CookHeaders.process(mlist, msg, msgdata) - eq(msgdata['add_header']['Reply-To'], - 'mlist@dom.ain') - eq(msgdata['add_header']['Cc'], - 'aperson@dom.ain') - eq(msg.get_all('reply-to'), None) - eq(msg.get_all('cc'), None) - - def test_reply_to_explicit_with_strip(self): - eq = self.assertEqual - mlist = self._mlist - mlist.reply_goes_to_list = 2 - mlist.first_strip_reply_to = 1 - mlist.from_is_list = 0 - mlist.reply_to_address = 'mlist@dom.ain' - msg = email.message_from_string("""\ -From: aperson@dom.ain -Reply-To: bperson@dom.ain - -""", Message.Message) - msgdata = {} - - CookHeaders.process(self._mlist, msg, msgdata) - eq(msgdata['add_header']['Reply-To'], 'mlist@dom.ain') - eq(msg.get_all('reply-to'), ['bperson@dom.ain']) - - def test_reply_to_explicit_with_strip_fil(self): - eq = self.assertEqual - mlist = self._mlist - mlist.reply_goes_to_list = 2 - mlist.first_strip_reply_to = 1 - mlist.from_is_list = 1 - mlist.reply_to_address = 'mlist@dom.ain' - msg = email.message_from_string("""\ -From: aperson@dom.ain -Reply-To: bperson@dom.ain - -""", Message.Message) - msgdata = {} - - CookHeaders.process(self._mlist, msg, msgdata) - eq(msgdata['add_header']['Reply-To'], - 'mlist@dom.ain') - eq(msgdata['add_header']['Cc'], - 'aperson@dom.ain') - eq(msg.get_all('reply-to'), ['bperson@dom.ain']) - eq(msg.get_all('cc'), None) - - def test_reply_to_extends_to_list(self): - eq = self.assertEqual - mlist = self._mlist - mlist.reply_goes_to_list = 1 - mlist.first_strip_reply_to = 0 - mlist.from_is_list = 0 - msg = email.message_from_string("""\ -From: aperson@dom.ain -Reply-To: bperson@dom.ain - -""", Message.Message) - msgdata = {} - - CookHeaders.process(mlist, msg, msgdata) - eq(msgdata['add_header']['Reply-To'], - 'bperson@dom.ain, _xtest@dom.ain') - - def test_reply_to_extends_to_list_fil(self): - eq = self.assertEqual - mlist = self._mlist - mlist.reply_goes_to_list = 1 - mlist.first_strip_reply_to = 0 - mlist.from_is_list = 1 - msg = email.message_from_string("""\ -From: aperson@dom.ain -Reply-To: bperson@dom.ain - -""", Message.Message) - msgdata = {} - - CookHeaders.process(mlist, msg, msgdata) - eq(msgdata['add_header']['Reply-To'], - 'bperson@dom.ain, _xtest@dom.ain') - eq(msgdata['add_header']['Cc'], - 'aperson@dom.ain') - eq(msg.get_all('reply-to'), ['bperson@dom.ain']) - eq(msg.get_all('cc'), None) - - def test_reply_to_extends_to_explicit(self): - eq = self.assertEqual - mlist = self._mlist - mlist.reply_goes_to_list = 2 - mlist.first_strip_reply_to = 0 - mlist.from_is_list = 0 - mlist.reply_to_address = 'mlist@dom.ain' - msg = email.message_from_string("""\ -From: aperson@dom.ain -Reply-To: bperson@dom.ain - -""", Message.Message) - msgdata = {} - CookHeaders.process(mlist, msg, msgdata) - eq(msgdata['add_header']['Reply-To'], - 'mlist@dom.ain, bperson@dom.ain') - - def test_reply_to_extends_to_explicit_fil(self): - eq = self.assertEqual - mlist = self._mlist - mlist.reply_goes_to_list = 2 - mlist.first_strip_reply_to = 0 - mlist.from_is_list = 1 - mlist.reply_to_address = 'mlist@dom.ain' - msg = email.message_from_string("""\ -From: aperson@dom.ain -Reply-To: bperson@dom.ain +This is an attachment. +--===============1234567890==-- +""") + Acknowledge.process(self._mlist, msg, {}) + files = self._sb.files() + eq(len(files), 1) + qmsg, qdata = self._sb.dequeue(files[0]) + # Check the .db file + eq(qdata.get('listname'), '_xtest') + eq(qdata.get('recips'), ['aperson@dom.ain']) + eq(qdata.get('version'), 3) + # Check the .pck + eq(str(qmsg['subject']), '_xtest post acknowledgement') + eq(qmsg['to'], 'aperson@dom.ain') + eq(qmsg['from'], '_xtest-bounces@dom.ain') + eq(qmsg.get_content_type(), 'text/plain') + eq(qmsg.get_param('charset'), 'us-ascii') + msgid = qmsg['message-id'] + self.failUnless(msgid.startswith('')) + eq(qmsg.get_payload(), """\ +Your message entitled -""", Message.Message) - msgdata = {} - CookHeaders.process(mlist, msg, msgdata) - eq(msgdata['add_header']['Reply-To'], - 'mlist@dom.ain, bperson@dom.ain') - eq(msgdata['add_header']['Cc'], - 'aperson@dom.ain') - eq(msg.get_all('reply-to'), ['bperson@dom.ain']) - eq(msg.get_all('cc'), None) + Wish you were here - def test_list_headers_nolist(self): - eq = self.assertEqual - msg = email.message_from_string("""\ -From: aperson@dom.ain +was successfully received by the _xtest mailing list. -""", Message.Message) - CookHeaders.process(self._mlist, msg, {'_nolist': 1}) - eq(msg['list-id'], None) - eq(msg['list-help'], None) - eq(msg['list-unsubscribe'], None) - eq(msg['list-subscribe'], None) - eq(msg['list-post'], None) - eq(msg['list-archive'], None) +List info page: http://www.dom.ain/mailman/listinfo/_xtest +Your preferences: http://www.dom.ain/mailman/options/_xtest/aperson%40dom.ain +""") + # Make sure we dequeued the only message + eq(len(self._sb.files()), 0) - def test_list_headers(self): + def test_ack_with_subject_and_body_and_headers_and_attachments_and_embedded_and_headers(self): eq = self.assertEqual - self._mlist.archive = 1 + self._mlist.setMemberOption( + 'aperson@dom.ain', mm_cfg.AcknowledgePosts, 1) + eq(len(self._sb.files()), 0) msg = email.message_from_string("""\ From: aperson@dom.ain +Subject: Wish you were here +Date: Mon, 01 Jan 2001 00:00:00 -0000 +Message-ID: +Content-Type: multipart/mixed; boundary="===============1234567890==" +MIME-Version: 1.0 -""", Message.Message) - oldval = mm_cfg.DEFAULT_URL_HOST - mm_cfg.DEFAULT_URL_HOST = 'www.dom.ain' - try: - CookHeaders.process(self._mlist, msg, {}) - finally: - mm_cfg.DEFAULT_URL_HOST = oldval - eq(msg['list-id'], '<_xtest.dom.ain>') - eq(msg['list-help'], '') - eq(msg['list-unsubscribe'], - ',' - '\n ') - eq(msg['list-subscribe'], - ',' - '\n ') - eq(msg['list-post'], '') - eq(msg['list-archive'], '') +--===============1234567890== +Content-Type: multipart/alternative; boundary="===============0987654321==" +MIME-Version: 1.0 - def test_list_headers_with_description(self): - eq = self.assertEqual - self._mlist.archive = 1 - self._mlist.description = 'A Test List' - msg = email.message_from_string("""\ -From: aperson@dom.ain +--===============0987654321== +Content-Type: text/plain; charset=us-ascii -""", Message.Message) - CookHeaders.process(self._mlist, msg, {}) - eq(str(msg['list-id']), 'A Test List <_xtest.dom.ain>') - eq(msg['list-help'], '') - eq(msg['list-unsubscribe'], - ',' - '\n ') - eq(msg['list-subscribe'], - ',' - '\n ') - eq(msg['list-post'], '') +This is the body of the message. +--===============0987654321== +Content-Type: text/html; charset=us-ascii - -class TestDecorate(TestBase): - def test_short_circuit(self): - msgdata = {'isdigest': 1} - rtn = Decorate.process(self._mlist, None, msgdata) - # Not really a great test, but there's little else to assert - self.assertEqual(rtn, None) + + +This is the body of the message. + + - def test_no_multipart(self): - mlist = self._mlist - mlist.msg_header = 'header\n' - mlist.msg_footer = 'footer' - msg = email.message_from_string("""\ -From: aperson@dom.ain +--===============0987654321==-- -Here is a message. -""") - Decorate.process(self._mlist, msg, {}) - self.assertEqual(msg.get_payload(), """\ -header -Here is a message. -footer +--===============1234567890== +Content-Type: application/octet-stream +Content-Disposition: attachment; filename="test.txt" + +This is an attachment. +--===============1234567890==-- """) + Acknowledge.process(self._mlist, msg, {}) + files = self._sb.files() + eq(len(files), 1) + qmsg, qdata = self._sb.dequeue(files[0]) + # Check the .db file + eq(qdata.get('listname'), '_xtest') + eq(qdata.get('recips'), ['aperson@dom.ain']) + eq(qdata.get('version'), 3) + # Check the .pck + eq(str(qmsg['subject']), '_xtest post acknowledgement') + eq(qmsg['to'], 'aperson@dom.ain') + eq(qmsg['from'], '_xtest-bounces@dom.ain') + eq(qmsg.get_content_type(), 'text/plain') + eq(qmsg.get_param('charset'), 'us-ascii') + msgid = qmsg['message-id'] + self.failUnless(msgid.startswith('')) + eq(qmsg.get_payload(), """\ +Your message entitled - def test_no_multipart_template(self): - mlist = self._mlist - mlist.msg_header = '%(real_name)s header\n' - mlist.msg_footer = '%(real_name)s footer' - mlist.real_name = 'XTest' - msg = email.message_from_string("""\ -From: aperson@dom.ain + Wish you were here -Here is a message. -""") - Decorate.process(self._mlist, msg, {}) - self.assertEqual(msg.get_payload(), """\ -XTest header -Here is a message. -XTest footer +was successfully received by the _xtest mailing list. + +List info page: http://www.dom.ain/mailman/listinfo/_xtest +Your preferences: http://www.dom.ain/mailman/options/_xtest/aperson%40dom.ain """) + # Make sure we dequeued the only message + eq(len(self._sb.files()), 0) - def test_no_multipart_type_error(self): - mlist = self._mlist - mlist.msg_header = '%(real_name) header\n' - mlist.msg_footer = '%(real_name) footer' - mlist.real_name = 'XTest' + def test_ack_with_subject_and_body_and_headers_and_attachments_and_embedded_and_headers_and_headers(self): + eq = self.assertEqual + self._mlist.setMemberOption( + 'aperson@dom.ain', mm_cfg.AcknowledgePosts, 1) + eq(len(self._sb.files()), 0) msg = email.message_from_string("""\ From: aperson@dom.ain +Subject: Wish you were here +Date: Mon, 01 Jan 2001 00:00:00 -0000 +Message-ID: +Content-Type: multipart/mixed; boundary="===============1234567890==" +MIME-Version: 1.0 +X-Mailer: Python Mailman -Here is a message. -""") - Decorate.process(self._mlist, msg, {}) - self.assertEqual(msg.get_payload(), """\ -%(real_name) header -Here is a message. -%(real_name) footer -""") +--===============1234567890== +Content-Type: multipart/alternative; boundary="===============0987654321==" +MIME-Version: 1.0 - def test_no_multipart_value_error(self): - mlist = self._mlist - # These will generate warnings in logs/error - mlist.msg_header = '%(real_name)p header\n' - mlist.msg_footer = '%(real_name)p footer' - mlist.real_name = 'XTest' - msg = email.message_from_string("""\ -From: aperson@dom.ain +--===============0987654321== +Content-Type: text/plain; charset=us-ascii -Here is a message. -""") - Decorate.process(self._mlist, msg, {}) - self.assertEqual(msg.get_payload(), """\ -%(real_name)p header -Here is a message. -%(real_name)p footer -""") +This is the body of the message. - def test_no_multipart_missing_key(self): - mlist = self._mlist - mlist.msg_header = '%(spooge)s header\n' - mlist.msg_footer = '%(spooge)s footer' - msg = email.message_from_string("""\ -From: aperson@dom.ain +--===============0987654321== +Content-Type: text/html; charset=us-ascii -Here is a message. -""") - Decorate.process(self._mlist, msg, {}) - self.assertEqual(msg.get_payload(), """\ -%(spooge)s header -Here is a message. -%(spooge)s footer -""") + + +This is the body of the message. + + - def test_multipart(self): - eq = self.ndiffAssertEqual - mlist = self._mlist - mlist.msg_header = 'header' - mlist.msg_footer = 'footer' - msg1 = email.message_from_string("""\ -From: aperson@dom.ain +--===============0987654321==-- -Here is the first message. -""") - msg2 = email.message_from_string("""\ -From: bperson@dom.ain +--===============1234567890== +Content-Type: application/octet-stream +Content-Disposition: attachment; filename="test.txt" -Here is the second message. +This is an attachment. +--===============1234567890==-- """) - msg = Message.Message() - msg.set_type('multipart/mixed') - msg.set_boundary('BOUNDARY') - msg.attach(msg1) - msg.attach(msg2) - Decorate.process(self._mlist, msg, {}) - eq(msg.as_string(unixfrom=0), """\ -MIME-Version: 1.0 -Content-Type: multipart/mixed; boundary="BOUNDARY" + Acknowledge.process(self._mlist, msg, {}) + files = self._sb.files() + eq(len(files), 1) + qmsg, qdata = self._sb.dequeue(files[0]) + # Check the .db file + eq(qdata.get('listname'), '_xtest') + eq(qdata.get('recips'), ['aperson@dom.ain']) + eq(qdata.get('version'), 3) + # Check the .pck + eq(str(qmsg['subject']), '_xtest post acknowledgement') + eq(qmsg['to'], 'aperson@dom.ain') + eq(qmsg['from'], '_xtest-bounces@dom.ain') + eq(qmsg.get_content_type(), 'text/plain') + eq(qmsg.get_param('charset'), 'us-ascii') + msgid = qmsg['message-id'] + self.failUnless(msgid.startswith('')) + eq(qmsg.get_payload(), """\ +Your message entitled ---BOUNDARY -Content-Type: text/plain; charset="us-ascii" -MIME-Version: 1.0 -Content-Transfer-Encoding: 7bit -Content-Disposition: inline + Wish you were here -header +was successfully received by the _xtest mailing list. ---BOUNDARY -From: aperson@dom.ain +List info page: http://www.dom.ain/mailman/listinfo/_xtest +Your preferences: http://www.dom.ain/mailman/options/_xtest/aperson%40dom.ain +""") + # Make sure we dequeued the only message + eq(len(self._sb.files()), 0) -Here is the first message. ---BOUNDARY -From: bperson@dom.ain +class TestAfterDelivery(TestBase): + # Both msg and msgdata are ignored + def test_process(self): + mlist = self._mlist + last_post_time = mlist.last_post_time + post_id = mlist.post_id + AfterDelivery.process(mlist, None, None) + self.failUnless(mlist.last_post_time > last_post_time) + self.assertEqual(mlist.post_id, post_id + 1) -Here is the second message. ---BOUNDARY -Content-Type: text/plain; charset="us-ascii" -MIME-Version: 1.0 -Content-Transfer-Encoding: 7bit -Content-Disposition: inline +class TestApprove(TestBase): + def test_short_circuit(self): + msgdata = {'approved': 1} + rtn = Approve.process(self._mlist, Message(), msgdata) + # Not really a great test, but there's little else to assert + self.assertEqual(rtn, None) -footer + def test_approved_moderator(self): + mlist = self._mlist + mlist.mod_password = password('wazoo') + msg = email.message_from_string("""\ +Approved: wazoo ---BOUNDARY-- """) + msgdata = {} + Approve.process(mlist, msg, msgdata) + self.failUnless('approved' in msgdata) + self.assertEqual(msgdata['approved'], 1) - def test_image(self): - eq = self.assertEqual + def test_approve_moderator(self): mlist = self._mlist - mlist.msg_header = 'header\n' - mlist.msg_footer = 'footer' + mlist.mod_password = password('wazoo') msg = email.message_from_string("""\ -From: aperson@dom.ain -Content-type: image/x-spooge +Approve: wazoo -IMAGEDATAIMAGEDATAIMAGEDATA -""") - Decorate.process(self._mlist, msg, {}) - eq(len(msg.get_payload()), 3) - self.assertEqual(msg.get_payload(1).get_payload(), """\ -IMAGEDATAIMAGEDATAIMAGEDATA """) + msgdata = {} + Approve.process(mlist, msg, msgdata) + self.failUnless('approved' in msgdata) + self.assertEqual(msgdata['approved'], 1) - def test_personalize_assert(self): - raises = self.assertRaises - raises(AssertionError, Decorate.process, - self._mlist, None, {'personalize': 1}) - raises(AssertionError, Decorate.process, - self._mlist, None, {'personalize': 1, - 'recips': [1, 2, 3]}) - - - -class TestFileRecips(TestBase): - def test_short_circuit(self): - msgdata = {'recips': 1} - rtn = FileRecips.process(self._mlist, None, msgdata) - # Not really a great test, but there's little else to assert - self.assertEqual(rtn, None) + def test_approved_admin(self): + mlist = self._mlist + mlist.password = password('wazoo') + msg = email.message_from_string("""\ +Approved: wazoo - def test_file_nonexistant(self): +""") msgdata = {} - FileRecips.process(self._mlist, None, msgdata) - self.assertEqual(msgdata.get('recips'), []) + Approve.process(mlist, msg, msgdata) + self.failUnless('approved' in msgdata) + self.assertEqual(msgdata['approved'], 1) - def test_file_exists_no_sender(self): + def test_approve_admin(self): + mlist = self._mlist + mlist.password = password('wazoo') msg = email.message_from_string("""\ -To: yall@dom.ain +Approve: wazoo -""", Message.Message) +""") msgdata = {} - file = os.path.join(self._mlist.fullpath(), 'members.txt') - addrs = ['aperson@dom.ain', 'bperson@dom.ain', - 'cperson@dom.ain', 'dperson@dom.ain'] - fp = open(file, 'w') - try: - for addr in addrs: - print(addr, file=fp) - fp.close() - FileRecips.process(self._mlist, msg, msgdata) - self.assertEqual(msgdata.get('recips'), addrs) - finally: - try: - os.unlink(file) - except OSError as e: - if e.errno != e.ENOENT: raise + Approve.process(mlist, msg, msgdata) + self.failUnless('approved' in msgdata) + self.assertEqual(msgdata['approved'], 1) - def test_file_exists_no_member(self): + def test_unapproved(self): + mlist = self._mlist + mlist.password = password('zoowa') msg = email.message_from_string("""\ -From: eperson@dom.ain -To: yall@dom.ain +Approve: wazoo -""", Message.Message) +""") msgdata = {} - file = os.path.join(self._mlist.fullpath(), 'members.txt') - addrs = ['aperson@dom.ain', 'bperson@dom.ain', - 'cperson@dom.ain', 'dperson@dom.ain'] - fp = open(file, 'w') - try: - for addr in addrs: - print(addr, file=fp) - fp.close() - FileRecips.process(self._mlist, msg, msgdata) - self.assertEqual(msgdata.get('recips'), addrs) - finally: - try: - os.unlink(file) - except OSError as e: - if e.errno != e.ENOENT: raise + Approve.process(mlist, msg, msgdata) + self.assertEqual(msgdata.get('approved'), None) - def test_file_exists_is_member(self): + def test_trip_beentheres(self): + mlist = self._mlist msg = email.message_from_string("""\ -From: aperson@dom.ain -To: yall@dom.ain +X-BeenThere: %s -""", Message.Message) - msgdata = {} - file = os.path.join(self._mlist.fullpath(), 'members.txt') - addrs = ['aperson@dom.ain', 'bperson@dom.ain', - 'cperson@dom.ain', 'dperson@dom.ain'] - fp = open(file, 'w') - try: - for addr in addrs: - print(addr, file=fp) - self._mlist.addNewMember(addr) - fp.close() - FileRecips.process(self._mlist, msg, msgdata) - self.assertEqual(msgdata.get('recips'), addrs[1:]) - finally: - try: - os.unlink(file) - except OSError as e: - if e.errno != e.ENOENT: raise +""" % mlist.GetListEmail()) + self.assertRaises(Errors.LoopError, Approve.process, mlist, msg, {}) - -class TestHold(TestBase): +class TestCalcRecips(TestBase): def setUp(self): TestBase.setUp(self) - self._mlist.administrivia = 1 - self._mlist.respond_to_post_requests = 0 - self._mlist.admin_immed_notify = 0 - # We're going to want to inspect this queue directory - self._sb = Switchboard(mm_cfg.VIRGINQUEUE_DIR) - - def tearDown(self): - for f in os.listdir(mm_cfg.VIRGINQUEUE_DIR): - os.unlink(os.path.join(mm_cfg.VIRGINQUEUE_DIR, f)) - TestBase.tearDown(self) - try: - os.unlink(os.path.join(mm_cfg.DATA_DIR, 'pending.db')) - except OSError as e: - if e.errno != errno.ENOENT: raise - for f in [holdfile for holdfile in os.listdir(mm_cfg.DATA_DIR) - if holdfile.startswith('heldmsg-')]: - os.unlink(os.path.join(mm_cfg.DATA_DIR, f)) + # Add a bunch of regular members + mlist = self._mlist + mlist.addNewMember('aperson@dom.ain') + mlist.addNewMember('bperson@dom.ain') + mlist.addNewMember('cperson@dom.ain') + # And a bunch of digest members + mlist.addNewMember('dperson@dom.ain', digest=1) + mlist.addNewMember('eperson@dom.ain', digest=1) + mlist.addNewMember('fperson@dom.ain', digest=1) def test_short_circuit(self): - msgdata = {'approved': 1} - rtn = Hold.process(self._mlist, None, msgdata) + msgdata = {'recips': 1} + rtn = CalcRecips.process(self._mlist, None, msgdata) # Not really a great test, but there's little else to assert self.assertEqual(rtn, None) - def test_administrivia(self): + def test_simple_path(self): + msgdata = {} msg = email.message_from_string("""\ -From: aperson@dom.ain -Subject: unsubscribe +From: dperson@dom.ain -""", Message.Message) - self.assertRaises(Hold.Administrivia, Hold.process, - self._mlist, msg, {}) +""", Message) + CalcRecips.process(self._mlist, msg, msgdata) + self.failUnless('recips' in msgdata) + recips = msgdata['recips'] + recips.sort() + self.assertEqual(recips, ['aperson@dom.ain', 'bperson@dom.ain', + 'cperson@dom.ain']) - def test_max_recips(self): - self._mlist.max_num_recipients = 5 - msg = email.message_from_string("""\ -From: aperson@dom.ain -To: _xtest@dom.ain, bperson@dom.ain -Cc: cperson@dom.ain -Cc: dperson@dom.ain (Jimmy D. Person) -To: Billy E. Person - -Hey folks! -""", Message.Message) - self.assertRaises(Hold.TooManyRecipients, Hold.process, - self._mlist, msg, {}) - - def test_implicit_destination(self): - self._mlist.require_explicit_destination = 1 + def test_exclude_sender(self): + msgdata = {} msg = email.message_from_string("""\ -From: aperson@dom.ain -Subject: An implicit message +From: cperson@dom.ain -""", Message.Message) - self.assertRaises(Hold.ImplicitDestination, Hold.process, - self._mlist, msg, {}) +""", Message) + self._mlist.setMemberOption('cperson@dom.ain', + mm_cfg.DontReceiveOwnPosts, 1) + CalcRecips.process(self._mlist, msg, msgdata) + self.failUnless('recips' in msgdata) + recips = msgdata['recips'] + recips.sort() + self.assertEqual(recips, ['aperson@dom.ain', 'bperson@dom.ain']) - def test_implicit_destination_fromusenet(self): - self._mlist.require_explicit_destination = 1 + def test_urgent_moderator(self): + self._mlist.mod_password = password('xxXXxx') + msgdata = {} msg = email.message_from_string("""\ -From: aperson@dom.ain -Subject: An implicit message +From: dperson@dom.ain +Urgent: xxXXxx -""", Message.Message) - rtn = Hold.process(self._mlist, msg, {'fromusenet': 1}) - self.assertEqual(rtn, None) +""", Message) + CalcRecips.process(self._mlist, msg, msgdata) + self.failUnless('recips' in msgdata) + recips = msgdata['recips'] + recips.sort() + self.assertEqual(recips, ['aperson@dom.ain', 'bperson@dom.ain', + 'cperson@dom.ain', 'dperson@dom.ain', + 'eperson@dom.ain', 'fperson@dom.ain']) - def test_suspicious_header(self): - self._mlist.bounce_matching_headers = 'From: .*person@(blah.)?dom.ain' + def test_urgent_admin(self): + self._mlist.mod_password = password('yyYYyy') + self._mlist.password = password('xxXXxx') + msgdata = {} msg = email.message_from_string("""\ -From: aperson@dom.ain -To: _xtest@dom.ain -Subject: An implicit message +From: dperson@dom.ain +Urgent: xxXXxx -""", Message.Message) - self.assertRaises(Hold.SuspiciousHeaders, Hold.process, - self._mlist, msg, {}) +""", Message) + CalcRecips.process(self._mlist, msg, msgdata) + self.failUnless('recips' in msgdata) + recips = msgdata['recips'] + recips.sort() + self.assertEqual(recips, ['aperson@dom.ain', 'bperson@dom.ain', + 'cperson@dom.ain', 'dperson@dom.ain', + 'eperson@dom.ain', 'fperson@dom.ain']) - def test_suspicious_header_ok(self): - self._mlist.bounce_matching_headers = 'From: .*person@blah.dom.ain' + def test_urgent_reject(self): + self._mlist.mod_password = password('yyYYyy') + self._mlist.password = password('xxXXxx') + msgdata = {} msg = email.message_from_string("""\ -From: aperson@dom.ain -To: _xtest@dom.ain -Subject: An implicit message +From: dperson@dom.ain +Urgent: zzZZzz -""", Message.Message) - rtn = Hold.process(self._mlist, msg, {}) - self.assertEqual(rtn, None) +""", Message) + self.assertRaises(Errors.RejectMessage, + CalcRecips.process, + self._mlist, msg, msgdata) - def test_max_message_size(self): - self._mlist.max_message_size = 1 - msg = email.message_from_string("""\ -From: aperson@dom.ain -To: _xtest@dom.ain - -xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -""", Message.Message) - self.assertRaises(Hold.MessageTooBig, Hold.process, - self._mlist, msg, {}) - - def test_hold_notifications(self): - eq = self.assertEqual - self._mlist.respond_to_post_requests = 1 - self._mlist.admin_immed_notify = 1 - # Now cause an implicit destination hold - msg = email.message_from_string("""\ -From: aperson@dom.ain + # BAW: must test the do_topic_filters() path... -""", Message.Message) - self.assertRaises(Hold.ImplicitDestination, Hold.process, - self._mlist, msg, {}) - # Now we have to make sure there are two messages in the virgin queue, - # one to the sender and one to the list owners. - qfiles = {} - files = self._sb.files() - eq(len(files), 2) - for filebase in files: - qmsg, qdata = self._sb.dequeue(filebase) - to = qmsg['to'] - qfiles[to] = qmsg, qdata - # BAW: We could be testing many other attributes of either the - # messages or the metadata files... - keys = list(qfiles.keys()) - keys.sort() - eq(keys, ['_xtest-owner@dom.ain', 'aperson@dom.ain']) - # Get the pending cookie from the message to the sender - pmsg, pdata = qfiles['aperson@dom.ain'] - confirmlines = pmsg.get_payload().split('\n') - cookie = confirmlines[-3].split('/')[-1] - # We also need to make sure there's an entry in the Pending database - # for the heold message. - data = self._mlist.pend_confirm(cookie) - eq(data, ('H', 1)) - heldmsg = os.path.join(mm_cfg.DATA_DIR, 'heldmsg-_xtest-1.pck') - self.failUnless(os.path.exists(heldmsg)) - os.unlink(heldmsg) - holdfiles = [f for f in os.listdir(mm_cfg.DATA_DIR) - if f.startswith('heldmsg-')] - eq(len(holdfiles), 0) - - - -class TestMimeDel(TestBase): + +class TestCleanse(TestBase): def setUp(self): TestBase.setUp(self) - self._mlist.filter_content = 1 - self._mlist.filter_mime_types = ['image/jpeg'] - self._mlist.pass_mime_types = [] - self._mlist.convert_html_to_plaintext = 1 - self._mlist.collapse_alternatives = 1 - def test_outer_matches(self): + def test_simple_cleanse(self): + eq = self.assertEqual msg = email.message_from_string("""\ From: aperson@dom.ain -Content-Type: image/jpeg -MIME-Version: 1.0 +Approved: yes +Urgent: indeed +Reply-To: bperson@dom.ain +Sender: asystem@dom.ain +Return-Receipt-To: another@dom.ain +Disposition-Notification-To: athird@dom.ain +X-Confirm-Reading-To: afourth@dom.ain +X-PMRQC: afifth@dom.ain +Subject: a message to you -xxxxx -""") - self.assertRaises(Errors.DiscardMessage, MimeDel.process, - self._mlist, msg, {}) +""", Message) + Cleanse.process(self._mlist, msg, {}) + eq(msg['approved'], None) + eq(msg['urgent'], None) + eq(msg['return-receipt-to'], None) + eq(msg['disposition-notification-to'], None) + eq(msg['x-confirm-reading-to'], None) + eq(msg['x-pmrqc'], None) + eq(msg['from'], 'aperson@dom.ain') + eq(msg['reply-to'], 'bperson@dom.ain') + eq(msg['sender'], 'asystem@dom.ain') + eq(msg['subject'], 'a message to you') - def test_strain_multipart(self): + def test_anon_cleanse(self): eq = self.assertEqual msg = email.message_from_string("""\ From: aperson@dom.ain -Content-Type: multipart/mixed; boundary=BOUNDARY -MIME-Version: 1.0 +Approved: yes +Urgent: indeed +Reply-To: bperson@dom.ain +Sender: asystem@dom.ain +Return-Receipt-To: another@dom.ain +Disposition-Notification-To: athird@dom.ain +X-Confirm-Reading-To: afourth@dom.ain +X-PMRQC: afifth@dom.ain +Subject: a message to you ---BOUNDARY -Content-Type: image/jpeg -MIME-Version: 1.0 +""", Message) + self._mlist.anonymous_list = 1 + Cleanse.process(self._mlist, msg, {}) + eq(msg['approved'], None) + eq(msg['urgent'], None) + eq(msg['return-receipt-to'], None) + eq(msg['disposition-notification-to'], None) + eq(msg['x-confirm-reading-to'], None) + eq(msg['x-pmrqc'], None) + eq(len(msg.get_all('from')), 1) + eq(len(msg.get_all('reply-to')), 1) + eq(msg['from'], '_xtest@dom.ain') + eq(msg['reply-to'], '_xtest@dom.ain') + eq(msg['sender'], None) + eq(msg['subject'], 'a message to you') -xxx ---BOUNDARY -Content-Type: image/gif -MIME-Version: 1.0 +class TestCookHeaders(TestBase): + def test_transform_noack_to_xack(self): + eq = self.assertEqual + msg = email.message_from_string("""\ +X-Ack: yes -yyy ---BOUNDARY-- -""") - MimeDel.process(self._mlist, msg, {}) - self.assertTrue(not msg.is_multipart()) - eq(msg.get_content_type(), 'image/gif') - eq(msg.get_payload(), 'yyy') +""", Message) + CookHeaders.process(self._mlist, msg, {'noack': 1}) + eq(len(msg.get_all('x-ack')), 1) + eq(msg['x-ack'], 'no') - def test_collapse_multipart_alternative(self): - eq = self.assertEqual + def test_original_sender(self): msg = email.message_from_string("""\ From: aperson@dom.ain -Content-Type: multipart/mixed; boundary=BOUNDARY -MIME-Version: 1.0 - ---BOUNDARY -Content-Type: multipart/alternative; boundary=BOUND2 -MIME-Version: 1.0 ---BOUND2 -Content-Type: image/jpeg -MIME-Version: 1.0 +""", Message) + msgdata = {} + CookHeaders.process(self._mlist, msg, msgdata) + self.assertEqual(msgdata.get('original_sender'), 'aperson@dom.ain') -xxx + def test_no_original_sender(self): + msg = email.message_from_string("""\ +Subject: about this message ---BOUND2 -Content-Type: image/gif -MIME-Version: 1.0 +""", Message) + msgdata = {} + CookHeaders.process(self._mlist, msg, msgdata) + self.assertEqual(msgdata.get('original_sender'), '') -yyy ---BOUND2-- + def test_xbeenthere(self): + msg = email.message_from_string("""\ +From: aperson@dom.ain ---BOUNDARY-- -""") - MimeDel.process(self._mlist, msg, {}) - self.assertTrue(not msg.is_multipart()) - eq(msg.get_content_type(), 'image/gif') - eq(msg.get_payload(), 'yyy') +""", Message) + CookHeaders.process(self._mlist, msg, {}) + self.assertEqual(msg['x-beenthere'], '_xtest@dom.ain') - def test_convert_to_plaintext(self): - # BAW: This test is dependent on your particular lynx version + def test_multiple_xbeentheres(self): eq = self.assertEqual msg = email.message_from_string("""\ From: aperson@dom.ain -Content-Type: text/html -MIME-Version: 1.0 +X-BeenThere: alist@another.dom.ain - - -""") - MimeDel.process(self._mlist, msg, {}) - eq(msg.get_content_type(), 'text/plain') - #eq(msg.get_payload(), '\n\n\n') - eq(msg.get_payload().strip(), '') +""", Message) + CookHeaders.process(self._mlist, msg, {}) + eq(len(msg.get_all('x-beenthere')), 2) + beentheres = msg.get_all('x-beenthere') + beentheres.sort() + eq(beentheres, ['_xtest@dom.ain', 'alist@another.dom.ain']) - def test_deep_structure(self): + def test_nonexisting_mmversion(self): eq = self.assertEqual - self._mlist.filter_mime_types.append('text/html') msg = email.message_from_string("""\ From: aperson@dom.ain -Content-Type: multipart/mixed; boundary=AAA - ---AAA -Content-Type: multipart/mixed; boundary=BBB - ---BBB -Content-Type: image/jpeg - -xxx ---BBB -Content-Type: image/jpeg - -yyy ---BBB--- ---AAA -Content-Type: multipart/alternative; boundary=CCC - ---CCC -Content-Type: text/html -

    This is a header

    - ---CCC -Content-Type: text/plain - -A different message ---CCC-- ---AAA -Content-Type: image/gif - -zzz ---AAA -Content-Type: image/gif +""", Message) + CookHeaders.process(self._mlist, msg, {}) + eq(msg['x-mailman-version'], mm_cfg.VERSION) -aaa ---AAA-- -""") - MimeDel.process(self._mlist, msg, {}) - payload = msg.get_payload() - eq(len(payload), 3) - part1 = msg.get_payload(0) - eq(part1.get_content_type(), 'text/plain') - eq(part1.get_payload(), 'A different message') - part2 = msg.get_payload(1) - eq(part2.get_content_type(), 'image/gif') - eq(part2.get_payload(), 'zzz') - part3 = msg.get_payload(2) - eq(part3.get_content_type(), 'image/gif') - eq(part3.get_payload(), 'aaa') - - def test_top_multipart_alternative(self): + def test_existing_mmversion(self): eq = self.assertEqual - self._mlist.filter_mime_types.append('text/html') msg = email.message_from_string("""\ From: aperson@dom.ain -Content-Type: multipart/alternative; boundary=AAA - ---AAA -Content-Type: text/html - -This is some html ---AAA -Content-Type: text/plain +X-Mailman-Version: 3000 -This is plain text ---AAA-- -""") - MimeDel.process(self._mlist, msg, {}) - eq(msg.get_content_type(), 'text/plain') - eq(msg.get_payload(), 'This is plain text') +""", Message) + CookHeaders.process(self._mlist, msg, {}) + eq(len(msg.get_all('x-mailman-version')), 1) + eq(msg['x-mailman-version'], '3000') - def test_recast_multipart(self): + def test_nonexisting_precedence(self): eq = self.assertEqual - self._mlist.filter_mime_types.append('application/pdf') msg = email.message_from_string("""\ From: aperson@dom.ain -MIME-Version: 1.0 -Content-type: multipart/mixed; - boundary="Boundary_0" ---Boundary_0 -Content-Type: multipart/mixed; - boundary="Boundary_1" - ---Boundary_1 -Content-type: multipart/mixed; - boundary="Boundary_2" - ---Boundary_2 -Content-type: multipart/alternative; - boundary="Boundary_3" - ---Boundary_3 -Content-type: text/plain; charset=us-ascii -Content-transfer-encoding: 7BIT - -Plain text part ---Boundary_3 -Content-type: text/html; charset=us-ascii -Content-transfer-encoding: 7BIT - -HTML part ---Boundary_3-- - - ---Boundary_2 -Content-type: application/pdf -Content-transfer-encoding: 7BIT - -PDF part inner 2 ---Boundary_2-- ---Boundary_1 -Content-type: text/plain; charset=us-ascii -Content-transfer-encoding: 7BIT - -second text ---Boundary_1-- - ---Boundary_0 -Content-Type: application/pdf -Content-transfer-encoding: 7BIT +""", Message) + CookHeaders.process(self._mlist, msg, {}) + eq(msg['precedence'], 'list') -PDF part outer ---Boundary_0-- -""") - MimeDel.process(self._mlist, msg, {}) - payload = msg.get_payload() - eq(len(payload), 2) - part1 = msg.get_payload(0) - eq(part1.get_content_type(), 'text/plain') - eq(part1.get_payload(), 'Plain text part') - part2 = msg.get_payload(1) - eq(part2.get_content_type(), 'text/plain') - eq(part2.get_payload(), 'second text') - - def test_message_rfc822(self): + def test_existing_precedence(self): eq = self.assertEqual msg = email.message_from_string("""\ -Message-ID: <4D9E6AEA.1060802@example.net> -Date: Thu, 07 Apr 2011 18:54:50 -0700 -From: User -MIME-Version: 1.0 -To: Someone -Subject: Message Subject -Content-Type: multipart/mixed; - boundary="------------050603050603060608020908" - -This is a multi-part message in MIME format. ---------------050603050603060608020908 -Content-Type: text/plain; charset=ISO-8859-1 -Content-Transfer-Encoding: 7bit - -Plain body. - ---------------050603050603060608020908 -Content-Type: message/rfc822 -Content-Transfer-Encoding: 7bit -Content-Disposition: attachment - -Message-ID: <4D9E647F.4050308@example.net> -Date: Thu, 07 Apr 2011 18:27:27 -0700 -From: User1 -MIME-Version: 1.0 -To: Someone1 -Content-Type: multipart/mixed; boundary="------------060107040402070208020705" -Subject: Attached Message 1 Subject - -This is a multi-part message in MIME format. ---------------060107040402070208020705 -Content-Type: text/plain; charset=ISO-8859-1 -Content-Transfer-Encoding: 7bit - -Attached Message 1 body. - ---------------060107040402070208020705 -Content-Type: message/rfc822 -Content-Transfer-Encoding: 7bit -Content-Disposition: attachment - -From: User2 -To: Someone2 -Subject: Attached Message 2 Subject -Date: Thu, 7 Apr 2011 19:09:35 -0500 -Message-ID: -MIME-Version: 1.0 -Content-Type: multipart/mixed; - boundary="----=_NextPart_000_0066_01CBF557.56C6F370" - -This is a multi-part message in MIME format. - -------=_NextPart_000_0066_01CBF557.56C6F370 -Content-Type: text/plain; - charset="us-ascii" -Content-Transfer-Encoding: 7bit - -Attached Message 2 body. - -------=_NextPart_000_0066_01CBF557.56C6F370 -Content-Type: message/rfc822 -Content-Transfer-Encoding: 7bit -Content-Disposition: attachment - -From: User3 -To: Someone3 -Subject: Attached Message 3 Subject -Date: Thu, 7 Apr 2011 17:22:04 -0500 -Message-ID: -MIME-Version: 1.0 -Content-Type: multipart/alternative; - boundary="----=_NextPart_000_0058_01CBF557.56C48270" - -This is a multi-part message in MIME format. - -------=_NextPart_000_0058_01CBF557.56C48270 -Content-Type: text/plain; - charset="iso-8859-1" -Content-Transfer-Encoding: 7bit - -Attached Message 3 plain body. - -------=_NextPart_000_0058_01CBF557.56C48270 -Content-Type: text/html; - charset="iso-8859-1" -Content-Transfer-Encoding: quoted-printable - - -Attached Message 3 html body. - -------=_NextPart_000_0058_01CBF557.56C48270-- - -------=_NextPart_000_0066_01CBF557.56C6F370 -Content-Type: message/rfc822 -Content-Transfer-Encoding: 7bit -Content-Disposition: attachment - -From: User4 -To: Someone4 -Subject: Attached Message 4 Subject -Date: Thu, 7 Apr 2011 17:24:26 -0500 -Message-ID: <19CC3BDF28CF49AD988FF43B2DBC5F1D@example> -MIME-Version: 1.0 -Content-Type: multipart/mixed; - boundary="----=_NextPart_000_0060_01CBF557.56C6F370" - -This is a multi-part message in MIME format. - -------=_NextPart_000_0060_01CBF557.56C6F370 -Content-Type: multipart/alternative; - boundary="----=_NextPart_001_0061_01CBF557.56C6F370" - -------=_NextPart_001_0061_01CBF557.56C6F370 -Content-Type: text/plain; - charset="us-ascii" -Content-Transfer-Encoding: 7bit - -Attached Message 4 plain body. - -------=_NextPart_001_0061_01CBF557.56C6F370 -Content-Type: text/html; - charset="us-ascii" -Content-Transfer-Encoding: quoted-printable - -Attached Message 4 html body. - -------=_NextPart_001_0061_01CBF557.56C6F370-- - -------=_NextPart_000_0060_01CBF557.56C6F370 -Content-Type: message/rfc822 -Content-Transfer-Encoding: 7bit -Content-Disposition: attachment - -From: User5 -To: Someone5 -Subject: Attached Message 5 Subject -Date: Thu, 7 Apr 2011 16:24:26 -0500 -Message-ID: -Content-Type: multipart/alternative; - boundary="----=_NextPart_000_005C_01CBF557.56C6F370" - -This is a multi-part message in MIME format. - -------=_NextPart_000_005C_01CBF557.56C6F370 -Content-Type: text/plain; - charset="iso-8859-1" -Content-Transfer-Encoding: 7bit - -Attached Message 5 plain body. - -------=_NextPart_000_005C_01CBF557.56C6F370 -Content-Type: text/html; - charset="iso-8859-1" -Content-Transfer-Encoding: quoted-printable - -Attached Message 5 html body. - -------=_NextPart_000_005C_01CBF557.56C6F370-- - -------=_NextPart_000_0060_01CBF557.56C6F370 -Content-Type: text/plain; - name="ATT00055.txt" -Content-Transfer-Encoding: quoted-printable -Content-Disposition: attachment; - filename="ATT00055.txt" - -Another plain part. - -------=_NextPart_000_0060_01CBF557.56C6F370-- +From: aperson@dom.ain +Precedence: junk -------=_NextPart_000_0066_01CBF557.56C6F370-- +""", Message) + CookHeaders.process(self._mlist, msg, {}) + eq(len(msg.get_all('precedence')), 1) + eq(msg['precedence'], 'junk') ---------------060107040402070208020705 -Content-Type: text/plain; charset="us-ascii" -MIME-Version: 1.0 -Content-Transfer-Encoding: 7bit -Content-Disposition: inline + def test_subject_munging_no_subject(self): + self._mlist.subject_prefix = '[XTEST] ' + msg = email.message_from_string("""\ +From: aperson@dom.ain -Final plain part. +""", Message) + msgdata = {} + CookHeaders.process(self._mlist, msg, msgdata) + self.assertEqual(msgdata.get('origsubj'), '') + self.assertEqual(str(msg['subject']), '[XTEST] (no subject)') ---------------060107040402070208020705-- + def test_subject_munging(self): + self._mlist.subject_prefix = '[XTEST] ' + msg = email.message_from_string("""\ +From: aperson@dom.ain +Subject: About Mailman... ---------------050603050603060608020908-- -""") - MimeDel.process(self._mlist, msg, {}) - payload = msg.get_payload() - eq(len(payload), 2) - part1 = msg.get_payload(0) - eq(part1.get_content_type(), 'text/plain') - eq(part1.get_payload(), 'Plain body.\n') - part2 = msg.get_payload(1) - eq(part2.get_content_type(), 'message/rfc822') - payload = part2.get_payload() - eq(len(payload), 1) - part1 = part2.get_payload(0) - eq(part1['subject'], 'Attached Message 1 Subject') - eq(part1.get_content_type(), 'multipart/mixed') - payload = part1.get_payload() - eq(len(payload), 3) - part3 = part1.get_payload(2) - eq(part3.get_content_type(), 'text/plain') - eq(part3.get_payload(), 'Final plain part.\n') - part2 = part1.get_payload(1) - eq(part2.get_content_type(), 'message/rfc822') - part1 = part1.get_payload(0) - eq(part1.get_content_type(), 'text/plain') - eq(part1.get_payload(), 'Attached Message 1 body.\n') - payload = part2.get_payload() - eq(len(payload), 1) - part1 = part2.get_payload(0) - eq(part1['subject'], 'Attached Message 2 Subject') - eq(part1.get_content_type(), 'multipart/mixed') - payload = part1.get_payload() - eq(len(payload), 3) - part3 = part1.get_payload(2) - eq(part3.get_content_type(), 'message/rfc822') - part2 = part1.get_payload(1) - eq(part2.get_content_type(), 'message/rfc822') - part1 = part1.get_payload(0) - eq(part1.get_content_type(), 'text/plain') - eq(part1.get_payload(), 'Attached Message 2 body.\n') - payload = part2.get_payload() - eq(len(payload), 1) - part1 = part2.get_payload(0) - eq(part1['subject'], 'Attached Message 3 Subject') - eq(part1.get_content_type(), 'text/plain') - eq(part1.get_payload(), 'Attached Message 3 plain body.\n') - payload = part3.get_payload() - eq(len(payload), 1) - part1 = part3.get_payload(0) - eq(part1['subject'], 'Attached Message 4 Subject') - eq(part1.get_content_type(), 'multipart/mixed') - payload = part1.get_payload() - eq(len(payload), 3) - part3 = part1.get_payload(2) - eq(part3.get_content_type(), 'text/plain') - eq(part3.get_filename(), 'ATT00055.txt') - eq(part3.get_payload(), 'Another plain part.\n') - part2 = part1.get_payload(1) - eq(part2.get_content_type(), 'message/rfc822') - part1 = part1.get_payload(0) - eq(part1.get_content_type(), 'text/plain') - eq(part1.get_payload(), 'Attached Message 4 plain body.\n') - payload = part2.get_payload() - eq(len(payload), 1) - part1 = part2.get_payload(0) - eq(part1['subject'], 'Attached Message 5 Subject') - eq(part1.get_content_type(), 'text/plain') - eq(part1.get_payload(), 'Attached Message 5 plain body.\n') - - -class TestModerate(TestBase): - pass - - - -class TestReplybot(TestBase): - pass - - - -class TestSpamDetect(TestBase): - def test_short_circuit(self): - msgdata = {'approved': 1} - msg = email.message_from_string('', Message.Message) - rtn = SpamDetect.process(self._mlist, msg, msgdata) - # Not really a great test, but there's little else to assert - self.assertEqual(rtn, None) +""", Message) + CookHeaders.process(self._mlist, msg, {}) + self.assertEqual(str(msg['subject']), '[XTEST] About Mailman...') - def test_spam_detect(self): - msg1 = email.message_from_string("""\ + def test_no_subject_munging_for_digests(self): + self._mlist.subject_prefix = '[XTEST] ' + msg = email.message_from_string("""\ From: aperson@dom.ain +Subject: About Mailman... -A message. -""", Message.Message) - msg2 = email.message_from_string("""\ -To: xlist@dom.ain +""", Message) + CookHeaders.process(self._mlist, msg, {'isdigest': 1}) + self.assertEqual(msg['subject'], 'About Mailman...') -A message. -""", Message.Message) - spammers = mm_cfg.KNOWN_SPAMMERS[:] - try: - mm_cfg.KNOWN_SPAMMERS.append(('from', '.?person')) - self.assertRaises(SpamDetect.SpamDetected, - SpamDetect.process, self._mlist, msg1, {}) - rtn = SpamDetect.process(self._mlist, msg2, {}) - self.assertEqual(rtn, None) - finally: - mm_cfg.KNOWN_SPAMMERS = spammers + def test_no_subject_munging_for_fasttrack(self): + self._mlist.subject_prefix = '[XTEST] ' + msg = email.message_from_string("""\ +From: aperson@dom.ain +Subject: About Mailman... +""", Message) + CookHeaders.process(self._mlist, msg, {'_fasttrack': 1}) + self.assertEqual(msg['subject'], 'About Mailman...') - -class TestTagger(TestBase): - def setUp(self): - TestBase.setUp(self) - self._mlist.topics = [('bar fight', '.*bar.*', 'catch any bars', 1)] - self._mlist.topics_enabled = 1 + def test_no_subject_munging_has_prefix(self): + self._mlist.subject_prefix = '[XTEST] ' + msg = email.message_from_string("""\ +From: aperson@dom.ain +Subject: Re: [XTEST] About Mailman... - def test_short_circuit(self): - self._mlist.topics_enabled = 0 - rtn = Tagger.process(self._mlist, None, {}) - # Not really a great test, but there's little else to assert - self.assertEqual(rtn, None) +""", Message) + CookHeaders.process(self._mlist, msg, {}) + # prefixing depends on mm_cfg.py + self.failUnless(str(msg['subject']) == 'Re: [XTEST] About Mailman...' or + str(msg['subject']) == '[XTEST] Re: About Mailman...') - def test_simple(self): + def test_reply_to_list(self): eq = self.assertEqual mlist = self._mlist - mlist.topics_bodylines_limit = 0 + mlist.reply_goes_to_list = 1 + mlist.from_is_list = 0 msg = email.message_from_string("""\ -Subject: foobar -Keywords: barbaz +From: aperson@dom.ain -""") +""", Message) msgdata = {} - Tagger.process(mlist, msg, msgdata) - eq(msg['x-topics'], 'bar fight') - eq(msgdata.get('topichits'), ['bar fight']) + CookHeaders.process(mlist, msg, msgdata) + eq(msgdata['add_header']['Reply-To'], '_xtest@dom.ain') + eq(msg.get_all('reply-to'), None) - def test_all_body_lines_plain_text(self): + def test_reply_to_list_fil(self): eq = self.assertEqual mlist = self._mlist - mlist.topics_bodylines_limit = -1 + mlist.reply_goes_to_list = 1 + mlist.from_is_list = 1 msg = email.message_from_string("""\ -Subject: Was -Keywords: Raw +From: aperson@dom.ain -Subject: farbaw -Keywords: barbaz -""") +""", Message) msgdata = {} - Tagger.process(mlist, msg, msgdata) - eq(msg['x-topics'], 'bar fight') - eq(msgdata.get('topichits'), ['bar fight']) + CookHeaders.process(mlist, msg, msgdata) + eq(msgdata['add_header']['Reply-To'], + '_xtest@dom.ain') + eq(msgdata['add_header']['Cc'], + 'aperson@dom.ain') + eq(msg.get_all('reply-to'), None) + eq(msg.get_all('cc'), None) - def test_no_body_lines(self): + + def test_reply_to_explicit(self): eq = self.assertEqual mlist = self._mlist - mlist.topics_bodylines_limit = 0 + mlist.reply_goes_to_list = 2 + mlist.from_is_list = 0 + mlist.reply_to_address = 'mlist@dom.ain' msg = email.message_from_string("""\ -Subject: Was -Keywords: Raw +From: aperson@dom.ain -Subject: farbaw -Keywords: barbaz -""") +""", Message) msgdata = {} - Tagger.process(mlist, msg, msgdata) - eq(msg['x-topics'], None) - eq(msgdata.get('topichits'), None) + CookHeaders.process(mlist, msg, msgdata) + eq(msgdata['add_header']['Reply-To'], 'mlist@dom.ain') + eq(msg.get_all('reply-to'), None) - def test_body_lines_in_multipart(self): + def test_reply_to_explicit_fil(self): eq = self.assertEqual mlist = self._mlist - mlist.topics_bodylines_limit = -1 + mlist.reply_goes_to_list = 2 + mlist.from_is_list = 1 + mlist.reply_to_address = 'mlist@dom.ain' msg = email.message_from_string("""\ -Subject: Was -Keywords: Raw -Content-Type: multipart/alternative; boundary="BOUNDARY" - ---BOUNDARY -From: sabo -To: obas - -Subject: farbaw -Keywords: barbaz +From: aperson@dom.ain ---BOUNDARY-- -""") +""", Message) msgdata = {} - Tagger.process(mlist, msg, msgdata) - eq(msg['x-topics'], 'bar fight') - eq(msgdata.get('topichits'), ['bar fight']) + CookHeaders.process(mlist, msg, msgdata) + eq(msgdata['add_header']['Reply-To'], + 'mlist@dom.ain') + eq(msgdata['add_header']['Cc'], + 'aperson@dom.ain') + eq(msg.get_all('reply-to'), None) + eq(msg.get_all('cc'), None) - def test_body_lines_no_part(self): + def test_reply_to_explicit_with_strip(self): eq = self.assertEqual mlist = self._mlist - mlist.topics_bodylines_limit = -1 + mlist.reply_goes_to_list = 2 + mlist.first_strip_reply_to = 1 + mlist.from_is_list = 0 + mlist.reply_to_address = 'mlist@dom.ain' msg = email.message_from_string("""\ -Subject: Was -Keywords: Raw -Content-Type: multipart/alternative; boundary=BOUNDARY - ---BOUNDARY -From: sabo -To: obas -Content-Type: message/rfc822 +From: aperson@dom.ain +Reply-To: bperson@dom.ain -Subject: farbaw -Keywords: barbaz +""", Message) + msgdata = {} ---BOUNDARY -From: sabo -To: obas -Content-Type: message/rfc822 + CookHeaders.process(self._mlist, msg, msgdata) + eq(msgdata['add_header']['Reply-To'], 'mlist@dom.ain') + eq(msg.get_all('reply-to'), ['bperson@dom.ain']) -Subject: farbaw -Keywords: barbaz + def test_reply_to_explicit_with_strip_fil(self): + eq = self.assertEqual + mlist = self._mlist + mlist.reply_goes_to_list = 2 + mlist.first_strip_reply_to = 1 + mlist.from_is_list = 1 + mlist.reply_to_address = 'mlist@dom.ain' + msg = email.message_from_string("""\ +From: aperson@dom.ain +Reply-To: bperson@dom.ain ---BOUNDARY-- -""") +""", Message) msgdata = {} - Tagger.process(mlist, msg, msgdata) - eq(msg['x-topics'], None) - eq(msgdata.get('topichits'), None) - - - -class TestToArchive(TestBase): - def setUp(self): - TestBase.setUp(self) - # We're going to want to inspect this queue directory - self._sb = Switchboard(mm_cfg.ARCHQUEUE_DIR) - def tearDown(self): - for f in os.listdir(mm_cfg.ARCHQUEUE_DIR): - os.unlink(os.path.join(mm_cfg.ARCHQUEUE_DIR, f)) - TestBase.tearDown(self) + CookHeaders.process(self._mlist, msg, msgdata) + eq(msgdata['add_header']['Reply-To'], + 'mlist@dom.ain') + eq(msgdata['add_header']['Cc'], + 'aperson@dom.ain') + eq(msg.get_all('reply-to'), ['bperson@dom.ain']) + eq(msg.get_all('cc'), None) - def test_short_circuit(self): + def test_reply_to_extends_to_list(self): eq = self.assertEqual - msgdata = {'isdigest': 1} - ToArchive.process(self._mlist, None, msgdata) - eq(len(self._sb.files()), 0) - # Try the other half of the or... - self._mlist.archive = 0 - ToArchive.process(self._mlist, None, msgdata) - eq(len(self._sb.files()), 0) - # Now try the various message header shortcuts + mlist = self._mlist + mlist.reply_goes_to_list = 1 + mlist.first_strip_reply_to = 0 + mlist.from_is_list = 0 msg = email.message_from_string("""\ -X-No-Archive: YES +From: aperson@dom.ain +Reply-To: bperson@dom.ain -""") - self._mlist.archive = 1 - ToArchive.process(self._mlist, msg, {}) - eq(len(self._sb.files()), 0) - # And for backwards compatibility - msg = email.message_from_string("""\ -X-Archive: NO +""", Message) + msgdata = {} -""") - ToArchive.process(self._mlist, msg, {}) - eq(len(self._sb.files()), 0) + CookHeaders.process(mlist, msg, msgdata) + eq(msgdata['add_header']['Reply-To'], + 'bperson@dom.ain, _xtest@dom.ain') - def test_normal_archiving(self): + def test_reply_to_extends_to_list_fil(self): eq = self.assertEqual + mlist = self._mlist + mlist.reply_goes_to_list = 1 + mlist.first_strip_reply_to = 0 + mlist.from_is_list = 1 msg = email.message_from_string("""\ -Subject: About Mailman - -It rocks! -""") - ToArchive.process(self._mlist, msg, {}) - files = self._sb.files() - eq(len(files), 1) - msg2, data = self._sb.dequeue(files[0]) - eq(len(data), 3) - eq(data['_parsemsg'], False) - eq(data['version'], 3) - # Clock skew makes this unreliable - #self.failUnless(data['received_time'] <= time.time()) - eq(msg.as_string(unixfrom=0), msg2.as_string(unixfrom=0)) - - - -class TestToDigest(TestBase): - def _makemsg(self, i=0): - msg = email.message_from_string("""From: aperson@dom.ain -To: _xtest@dom.ain -Subject: message number %(i)d - -Here is message %(i)d -""" % {'i' : i}) - return msg +From: aperson@dom.ain +Reply-To: bperson@dom.ain - def setUp(self): - TestBase.setUp(self) - self._path = os.path.join(self._mlist.fullpath(), 'digest.mbox') - fp = open(self._path, 'w') - g = Generator(fp) - for i in range(5): - g.flatten(self._makemsg(i), unixfrom=1) - fp.close() - self._sb = Switchboard(mm_cfg.VIRGINQUEUE_DIR) +""", Message) + msgdata = {} - def tearDown(self): - try: - os.unlink(self._path) - except OSError as e: - if e.errno != errno.ENOENT: raise - for f in os.listdir(mm_cfg.VIRGINQUEUE_DIR): - os.unlink(os.path.join(mm_cfg.VIRGINQUEUE_DIR, f)) - TestBase.tearDown(self) + CookHeaders.process(mlist, msg, msgdata) + eq(msgdata['add_header']['Reply-To'], + 'bperson@dom.ain, _xtest@dom.ain') + eq(msgdata['add_header']['Cc'], + 'aperson@dom.ain') + eq(msg.get_all('reply-to'), ['bperson@dom.ain']) + eq(msg.get_all('cc'), None) - def test_short_circuit(self): - eq = self.assertEqual - mlist = self._mlist - mlist.digestable = 0 - eq(ToDigest.process(mlist, None, {}), None) - mlist.digestable = 1 - eq(ToDigest.process(mlist, None, {'isdigest': 1}), None) - eq(self._sb.files(), []) - - def test_undersized(self): - msg = self._makemsg(99) - size = os.path.getsize(self._path) + len(str(msg)) - self._mlist.digest_size_threshhold = (size + 1) * 1024 - ToDigest.process(self._mlist, msg, {}) - self.assertEqual(self._sb.files(), []) - - def test_send_a_digest(self): + def test_reply_to_extends_to_explicit(self): eq = self.assertEqual mlist = self._mlist - msg = self._makemsg(99) - size = os.path.getsize(self._path) + len(str(msg)) - # Set digest_size_threshhold to a very small value to force a digest. - # Setting to zero no longer works. - mlist.digest_size_threshhold = 0.001 - ToDigest.process(mlist, msg, {}) - files = self._sb.files() - # There should be two files in the queue, one for the MIME digest and - # one for the RFC 1153 digest. - eq(len(files), 2) - # Now figure out which of the two files is the MIME digest and which - # is the RFC 1153 digest. - for filebase in files: - qmsg, qdata = self._sb.dequeue(filebase) - if qmsg.get_content_maintype() == 'multipart': - mimemsg = qmsg - mimedata = qdata - else: - rfc1153msg = qmsg - rfc1153data = qdata - eq(rfc1153msg.get_content_type(), 'text/plain') - eq(mimemsg.get_content_type(), 'multipart/mixed') - eq(mimemsg['from'], mlist.GetRequestEmail()) - eq(mimemsg['subject'], - '%(realname)s Digest, Vol %(volume)d, Issue %(issue)d' % { - 'realname': mlist.real_name, - 'volume' : mlist.volume, - 'issue' : mlist.next_digest_number - 1, - }) - eq(mimemsg['to'], mlist.GetListEmail()) - # BAW: this test is incomplete... - - - -class TestToOutgoing(TestBase): - def setUp(self): - TestBase.setUp(self) - # We're going to want to inspect this queue directory - self._sb = Switchboard(mm_cfg.OUTQUEUE_DIR) + mlist.reply_goes_to_list = 2 + mlist.first_strip_reply_to = 0 + mlist.from_is_list = 0 + mlist.reply_to_address = 'mlist@dom.ain' + msg = email.message_from_string("""\ +From: aperson@dom.ain +Reply-To: bperson@dom.ain - def tearDown(self): - for f in os.listdir(mm_cfg.OUTQUEUE_DIR): - os.unlink(os.path.join(mm_cfg.OUTQUEUE_DIR, f)) - TestBase.tearDown(self) +""", Message) + msgdata = {} + CookHeaders.process(mlist, msg, msgdata) + eq(msgdata['add_header']['Reply-To'], + 'mlist@dom.ain, bperson@dom.ain') - def test_outgoing(self): + def test_reply_to_extends_to_explicit_fil(self): eq = self.assertEqual + mlist = self._mlist + mlist.reply_goes_to_list = 2 + mlist.first_strip_reply_to = 0 + mlist.from_is_list = 1 + mlist.reply_to_address = 'mlist@dom.ain' msg = email.message_from_string("""\ -Subject: About Mailman - -It rocks! -""") - msgdata = {'foo': 1, 'bar': 2} - ToOutgoing.process(self._mlist, msg, msgdata) - files = self._sb.files() - eq(len(files), 1) - msg2, data = self._sb.dequeue(files[0]) - eq(msg.as_string(unixfrom=0), msg2.as_string(unixfrom=0)) - self.failUnless(len(data) >= 6 and len(data) <= 7) - eq(data['foo'], 1) - eq(data['bar'], 2) - eq(data['version'], 3) - eq(data['listname'], '_xtest') - eq(data['_parsemsg'], False) - # Can't test verp. presence/value depend on mm_cfg.py - #eq(data['verp'], 1) - # Clock skew makes this unreliable - #self.failUnless(data['received_time'] <= time.time()) - - - -class TestToUsenet(TestBase): - def setUp(self): - TestBase.setUp(self) - # We're going to want to inspect this queue directory - self._sb = Switchboard(mm_cfg.NEWSQUEUE_DIR) +From: aperson@dom.ain +Reply-To: bperson@dom.ain - def tearDown(self): - for f in os.listdir(mm_cfg.NEWSQUEUE_DIR): - os.unlink(os.path.join(mm_cfg.NEWSQUEUE_DIR, f)) - TestBase.tearDown(self) +""", Message) + msgdata = {} + CookHeaders.process(mlist, msg, msgdata) + eq(msgdata['add_header']['Reply-To'], + 'mlist@dom.ain, bperson@dom.ain') + eq(msgdata['add_header']['Cc'], + 'aperson@dom.ain') + eq(msg.get_all('reply-to'), ['bperson@dom.ain']) + eq(msg.get_all('cc'), None) - def test_short_circuit(self): + def test_list_headers_nolist(self): eq = self.assertEqual - mlist = self._mlist - mlist.gateway_to_news = 0 - ToUsenet.process(mlist, None, {}) - eq(len(self._sb.files()), 0) - mlist.gateway_to_news = 1 - ToUsenet.process(mlist, None, {'isdigest': 1}) - eq(len(self._sb.files()), 0) - ToUsenet.process(mlist, None, {'fromusenet': 1}) - eq(len(self._sb.files()), 0) + msg = email.message_from_string("""\ +From: aperson@dom.ain + +""", Message) + CookHeaders.process(self._mlist, msg, {'_nolist': 1}) + eq(msg['list-id'], None) + eq(msg['list-help'], None) + eq(msg['list-unsubscribe'], None) + eq(msg['list-subscribe'], None) + eq(msg['list-post'], None) + eq(msg['list-archive'], None) - def test_to_usenet(self): - # BAW: Should we, can we, test the error conditions that only log to a - # file instead of raising an exception? + def test_list_headers(self): eq = self.assertEqual - mlist = self._mlist - mlist.gateway_to_news = 1 - mlist.linked_newsgroup = 'foo' - mlist.nntp_host = 'bar' + self._mlist.archive = 1 msg = email.message_from_string("""\ -Subject: About Mailman +From: aperson@dom.ain -Mailman rocks! -""") - ToUsenet.process(mlist, msg, {}) - files = self._sb.files() - eq(len(files), 1) - msg2, data = self._sb.dequeue(files[0]) - eq(msg.as_string(unixfrom=0), msg2.as_string(unixfrom=0)) - eq(data['version'], 3) - eq(data['listname'], '_xtest') - # Clock skew makes this unreliable - #self.failUnless(data['received_time'] <= time.time()) - - - -def suite(): - suite = unittest.TestSuite() - suite.addTest(unittest.makeSuite(TestAcknowledge)) - suite.addTest(unittest.makeSuite(TestAfterDelivery)) - suite.addTest(unittest.makeSuite(TestApprove)) - suite.addTest(unittest.makeSuite(TestCalcRecips)) - suite.addTest(unittest.makeSuite(TestCleanse)) - suite.addTest(unittest.makeSuite(TestCookHeaders)) - suite.addTest(unittest.makeSuite(TestDecorate)) - suite.addTest(unittest.makeSuite(TestFileRecips)) - suite.addTest(unittest.makeSuite(TestHold)) - suite.addTest(unittest.makeSuite(TestMimeDel)) - suite.addTest(unittest.makeSuite(TestModerate)) - suite.addTest(unittest.makeSuite(TestReplybot)) - suite.addTest(unittest.makeSuite(TestSpamDetect)) - suite.addTest(unittest.makeSuite(TestTagger)) - suite.addTest(unittest.makeSuite(TestToArchive)) - suite.addTest(unittest.makeSuite(TestToDigest)) - suite.addTest(unittest.makeSuite(TestToOutgoing)) - suite.addTest(unittest.makeSuite(TestToUsenet)) - return suite - - - -if __name__ == '__main__': - unittest.main(defaultTest='suite') +""", Message) + oldval = mm_cfg.DEFAULT_URL_HOST + mm_cfg.DEFAULT_URL_HOST = 'www.dom.ain' + try: + CookHeaders.process(self._mlist, msg, {}) + finally: + mm_cfg.DEFAULT_URL_HOST = oldval + eq(msg['list-id'], '<_xtest.dom.ain>') + eq(msg['list-help'], '') + eq(msg['list-unsubscribe'], + ',' + '\n ') + eq(msg['list-subscribe'], + ',' + '\n ') + eq(msg['list-post'], '') + eq(msg['list-archive'], '') diff --git a/tests/test_lockfile.py b/tests/test_lockfile.py index 31eddcf5..5cb787b8 100644 --- a/tests/test_lockfile.py +++ b/tests/test_lockfile.py @@ -18,32 +18,162 @@ """ import unittest +import os +import time +import errno try: from Mailman import __init__ except ImportError: import paths -from Mailman.LockFile import LockFile +from Mailman.LockFile import LockFile, AlreadyLockedError, NotLockedError +from Mailman.MailList import MailList +from Mailman import Utils +from Mailman import mm_cfg LOCKFILE_NAME = '/tmp/.mm-test-lock' +TEST_LIST_NAME = 'test-list' - class TestLockFile(unittest.TestCase): + def setUp(self): + # Clean up any existing lock file + try: + os.unlink(LOCKFILE_NAME) + except OSError as e: + if e.errno != errno.ENOENT: + raise + + def tearDown(self): + # Clean up after each test + try: + os.unlink(LOCKFILE_NAME) + except OSError as e: + if e.errno != errno.ENOENT: + raise + def test_two_lockfiles_same_proc(self): lf1 = LockFile(LOCKFILE_NAME) lf2 = LockFile(LOCKFILE_NAME) lf1.lock() self.failIf(lf2.locked()) + def test_normal_lock_release(self): + lf = LockFile(LOCKFILE_NAME) + lf.lock() + self.assertTrue(lf.locked()) + lf.unlock() + self.assertFalse(lf.locked()) + + def test_unconditional_unlock(self): + lf = LockFile(LOCKFILE_NAME) + # Should not raise an error even when not locked + lf.unlock(unconditionally=True) + lf.lock() + lf.unlock(unconditionally=True) + self.assertFalse(lf.locked()) + + def test_lock_timeout(self): + lf1 = LockFile(LOCKFILE_NAME) + lf2 = LockFile(LOCKFILE_NAME) + lf1.lock() + # Try to acquire lock with short timeout + start_time = time.time() + try: + lf2.lock(timeout=0.1) + self.fail("Expected timeout") + except AlreadyLockedError: + pass + elapsed = time.time() - start_time + self.assertTrue(0.1 <= elapsed < 0.2) # Should timeout after ~0.1s + lf1.unlock() + + def test_lock_refresh(self): + lf = LockFile(LOCKFILE_NAME, lifetime=1) + lf.lock() + self.assertTrue(lf.locked()) + time.sleep(0.5) # Wait half the lifetime + lf.refresh() # Refresh the lock + time.sleep(0.6) # Wait more than original lifetime + self.assertTrue(lf.locked()) # Should still be locked due to refresh + lf.unlock() + + def test_lock_lifetime(self): + lf = LockFile(LOCKFILE_NAME, lifetime=1) + lf.lock() + self.assertTrue(lf.locked()) + time.sleep(1.1) # Wait longer than lifetime + self.assertFalse(lf.locked()) # Should have expired + + def test_error_handling(self): + lf = LockFile(LOCKFILE_NAME) + # Test that lock is released after exception + try: + with self.assertRaises(ValueError): + lf.lock() + raise ValueError("Test error") + finally: + self.assertFalse(lf.locked()) + + def test_concurrent_locks(self): + lf1 = LockFile(LOCKFILE_NAME) + lf2 = LockFile(LOCKFILE_NAME) + lf1.lock() + self.assertTrue(lf1.locked()) + self.assertFalse(lf2.locked()) + lf1.unlock() + lf2.lock() + self.assertTrue(lf2.locked()) + self.assertFalse(lf1.locked()) + lf2.unlock() + + def test_mailing_list_lock_release(self): + """Test that mailing list locks are properly released on disk.""" + # Create a test list if it doesn't exist + if not Utils.list_exists(TEST_LIST_NAME): + mlist = MailList.MailList(TEST_LIST_NAME, lock=0) + mlist.Create(TEST_LIST_NAME, 'test@example.com', 'testpass') + mlist.Save() + mlist.Unlock() + + # Get the list's lock file path + lock_path = os.path.join(mm_cfg.LOCK_DIR, TEST_LIST_NAME + '.lock') + + # Clean up any existing lock file + try: + os.unlink(lock_path) + except OSError as e: + if e.errno != errno.ENOENT: + raise + + # Create and lock the list + mlist = MailList.MailList(TEST_LIST_NAME, lock=1) + try: + # Verify lock file exists + self.assertTrue(os.path.exists(lock_path), + "Lock file should exist after locking") + + # Verify we can't create another instance with lock=1 + with self.assertRaises(AlreadyLockedError): + mlist2 = MailList.MailList(TEST_LIST_NAME, lock=1) + finally: + # Release the lock + mlist.Unlock() + + # Verify lock file is removed + self.assertFalse(os.path.exists(lock_path), + "Lock file should be removed after unlock") + + # Verify we can create another instance with lock=1 + mlist2 = MailList.MailList(TEST_LIST_NAME, lock=1) + mlist2.Unlock() + - def suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestLockFile)) return suite - if __name__ == '__main__': unittest.main(defaultTest='suite') diff --git a/tests/test_message.py b/tests/test_message.py index 08af80aa..3f29fa64 100644 --- a/tests/test_message.py +++ b/tests/test_message.py @@ -26,7 +26,7 @@ except ImportError: import paths -from Mailman import Message +from Mailman.Message import Message from Mailman import Version from Mailman import Errors @@ -38,7 +38,7 @@ class TestSentMessage1(EmailBase): def test_user_notification(self): eq = self.assertEqual unless = self.failUnless - msg = Message.UserNotification( + msg = Mailman.Message.UserNotification( 'aperson@dom.ain', '_xtest@dom.ain', 'Your Test List', @@ -77,7 +77,7 @@ def test_bounce_message(self): Subject: and another thing yadda yadda yadda -""", Message.Message) +""", Message) self._mlist.BounceMessage(msg, {}) qmsg = email.message_from_string(self._readmsg()) unless(qmsg.is_multipart()) From d5e801caee033226d1c031cc87e55543d48fdaeb Mon Sep 17 00:00:00 2001 From: jared mauch Date: Wed, 12 Nov 2025 17:51:04 -0500 Subject: [PATCH 728/748] Remove temporary script --- collapse_divergence.sh | 134 ----------------------------------------- 1 file changed, 134 deletions(-) delete mode 100755 collapse_divergence.sh diff --git a/collapse_divergence.sh b/collapse_divergence.sh deleted file mode 100755 index 700669e1..00000000 --- a/collapse_divergence.sh +++ /dev/null @@ -1,134 +0,0 @@ -#!/bin/bash -# Script to collapse history from divergence point (b00d5ae) to convergence point (5f2368e) -# This will squash all commits in the divergence period into a single commit - -set -e - -DIVERGENCE_POINT="b00d5ae" # Where we diverged from cpanel/main -CONVERGENCE_POINT="5f2368e" # Where we merged cpanel state back in -CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) - -echo "=== Collapsing History from Divergence to Convergence ===" -echo "" -echo "Current branch: $CURRENT_BRANCH" -echo "Divergence point: $DIVERGENCE_POINT (where we diverged from cpanel/main)" -echo "Convergence point: $CONVERGENCE_POINT (where cpanel state was merged in)" -echo "" - -# Safety check -if [ "$CURRENT_BRANCH" = "main" ]; then - echo "WARNING: You are on the main branch!" - echo "This script will create a new branch for safety." - read -p "Continue? (yes/no): " confirm - if [ "$confirm" != "yes" ]; then - echo "Aborted." - exit 1 - fi -fi - -# Count commits to squash -COMMITS_TO_SQUASH=$(git rev-list --count ${DIVERGENCE_POINT}..${CONVERGENCE_POINT}) -COMMITS_AFTER=$(git rev-list --count ${CONVERGENCE_POINT}..HEAD) - -echo "Commits to squash (divergence period): $COMMITS_TO_SQUASH" -echo "Commits to keep after convergence point: $COMMITS_AFTER" -echo "" - -# Create backup branch -BACKUP_BRANCH="main-backup-$(date +%Y%m%d-%H%M%S)" -echo "Creating backup branch: $BACKUP_BRANCH" -git branch "$BACKUP_BRANCH" "$CURRENT_BRANCH" -echo "✓ Backup created" -echo "" - -# Create working branch -WORK_BRANCH="collapse-divergence" -if git show-ref --verify --quiet refs/heads/$WORK_BRANCH; then - echo "Branch $WORK_BRANCH already exists. Deleting it..." - git branch -D "$WORK_BRANCH" -fi - -echo "Creating working branch: $WORK_BRANCH" -git checkout -b "$WORK_BRANCH" "$CURRENT_BRANCH" -echo "✓ Working branch created" -echo "" - -echo "=== Step 1: Reset to divergence point ===" -echo "Resetting to divergence point (keeping changes)..." -git reset --soft "$DIVERGENCE_POINT" -echo "✓ Reset complete" -echo "" - -echo "=== Step 2: Create a single commit with all divergence period changes ===" -echo "Checking out the state at convergence point ${CONVERGENCE_POINT:0:7}..." -git checkout "$CONVERGENCE_POINT" -- . -echo "✓ Files from convergence point checked out" - -# Remove any files that shouldn't be there -git rm -f messages/docstring.files messages/marked.files configure.in 2>/dev/null || true - -# Stage all changes -git add -A - -# Get a summary of what changed -CHANGED_FILES=$(git diff --cached --name-only 2>/dev/null | wc -l | tr -d ' ') -if [ -z "$CHANGED_FILES" ] || [ "$CHANGED_FILES" = "0" ]; then - CHANGED_FILES=$(git status --short | wc -l | tr -d ' ') -fi -echo "Files changed: $CHANGED_FILES" - -# Create commit message -COMMIT_MSG="Squashed divergence period: Python 3 migration and fixes - -This commit combines all work done during the divergence period from -cpanel/main, from the divergence point (${DIVERGENCE_POINT:0:7}) through -the convergence point where cpanel fixes were merged in (${CONVERGENCE_POINT:0:7}). - -Includes: -- Python 2 to Python 3 migration work -- Pickle protocol handling fixes -- Encoding and string handling improvements -- Bug fixes and compatibility improvements -- Configuration and build system updates - -Original commits: $COMMITS_TO_SQUASH commits from ${DIVERGENCE_POINT:0:7} to ${CONVERGENCE_POINT:0:7}" - -echo "" -echo "Creating squashed commit..." -git commit -m "$COMMIT_MSG" -echo "✓ Squashed commit created" -echo "" - -echo "=== Step 3: Re-apply commits after convergence point ===" -if [ "$COMMITS_AFTER" -gt 0 ]; then - echo "Cherry-picking $COMMITS_AFTER commits after convergence point..." - git cherry-pick ${CONVERGENCE_POINT}..${BACKUP_BRANCH} - echo "✓ Commits re-applied" -else - echo "No commits to re-apply after convergence point" -fi -echo "" - -echo "=== Summary ===" -echo "✓ Backup branch created: $BACKUP_BRANCH" -echo "✓ Working branch created: $WORK_BRANCH" -echo "✓ Squashed $COMMITS_TO_SQUASH commits from divergence period into 1 commit" -if [ "$COMMITS_AFTER" -gt 0 ]; then - echo "✓ Re-applied $COMMITS_AFTER commits after convergence point" -fi -echo "" -echo "Current branch: $WORK_BRANCH" -echo "" -echo "Next steps:" -echo "1. Review the changes: git log --oneline" -echo "2. Compare with original: git log --oneline $BACKUP_BRANCH | head -20" -echo "3. Test thoroughly" -echo "4. If satisfied, replace main:" -echo " git checkout main" -echo " git reset --hard $WORK_BRANCH" -echo " git push origin main --force-with-lease" -echo "" -echo "To abort and restore:" -echo " git checkout $CURRENT_BRANCH" -echo " git branch -D $WORK_BRANCH" - From f1e7ec6108b297d3565db2695b80e73ca3e3c5ca Mon Sep 17 00:00:00 2001 From: jared mauch Date: Wed, 12 Nov 2025 17:01:12 -0500 Subject: [PATCH 729/748] Major Python 3 migration and bug fixes This commit consolidates extensive Python 3 compatibility improvements, bug fixes, and feature enhancements including: - Python 3 migration: Replace raw_input with input, fix execfile usage, handle string/bytes encoding throughout codebase - Encoding fixes: UTF-8 handling, proper encoding for email headers, subscription forms, and message decoration - Archiver improvements: Fix on-the-fly archiving, handle non-ASCII characters, improve archive format handling - cPanel integration: Sync fixes for pickle protocol detection, encoding issues, and various CPANEL ticket fixes - Configuration: Improve mm_cfg handler, fix circular import issues - Bug fixes: Fix TypeError with string encoding/hashing, KeyError in HyperDatabase, NNTP bridge issues, and various other fixes - Cron script fixes: Update senddigests, checkdbs, and other cron jobs for Python 3 compatibility This represents a comprehensive update bringing the codebase to full Python 3 compatibility while maintaining backward compatibility where possible. --- Mailman/Archiver/Archiver.py | 92 +- Mailman/Archiver/HyperArch.py | 727 +- Mailman/Archiver/HyperDatabase.py | 26 +- Mailman/Archiver/__init__.py | 1 + Mailman/Archiver/pipermail.py | 156 +- Mailman/Autoresponder.py | 35 - Mailman/Bouncer.py | 18 +- Mailman/Bouncers/Caiwireless.py | 25 +- Mailman/Bouncers/Compuserve.py | 5 +- Mailman/Bouncers/DSN.py | 45 +- Mailman/Bouncers/Exchange.py | 38 +- Mailman/Bouncers/GroupWise.py | 6 +- Mailman/Bouncers/LLNL.py | 5 +- Mailman/Bouncers/Microsoft.py | 1 + Mailman/Bouncers/Qmail.py | 19 +- Mailman/Bouncers/SMTP32.py | 5 +- Mailman/Bouncers/SimpleMatch.py | 159 +- Mailman/Bouncers/SimpleWarning.py | 5 +- Mailman/Bouncers/Sina.py | 7 +- Mailman/Bouncers/Yahoo.py | 11 +- Mailman/CSRFcheck.py | 82 +- Mailman/Cgi/Auth.py | 1 - Mailman/Cgi/admin.py | 1622 +- Mailman/Cgi/admindb.py | 892 +- Mailman/Cgi/confirm.py | 71 +- Mailman/Cgi/create.py | 206 +- Mailman/Cgi/edithtml.py | 63 +- Mailman/Cgi/listinfo.py | 161 +- Mailman/Cgi/options.py | 329 +- Mailman/Cgi/private.py | 176 +- Mailman/Cgi/rmlist.py | 95 +- Mailman/Cgi/roster.py | 31 +- Mailman/Cgi/subscribe.py | 187 +- Mailman/Commands/cmd_confirm.py | 14 +- Mailman/Commands/cmd_set.py | 2 +- Mailman/Commands/cmd_subscribe.py | 9 +- Mailman/Commands/cmd_unsubscribe.py | 3 +- Mailman/Defaults.py.in | 70 +- Mailman/Deliverer.py | 102 +- Mailman/Digester.py | 11 +- Mailman/Errors.py | 6 +- Mailman/Gui/Bounce.py | 2 + Mailman/Gui/Digest.py | 10 +- Mailman/Gui/GUIBase.py | 29 +- Mailman/Gui/General.py | 2 +- Mailman/Gui/Language.py | 2 +- Mailman/Gui/NonDigest.py | 11 +- Mailman/Gui/Privacy.py | 25 +- Mailman/Gui/Topics.py | 40 +- Mailman/HTMLFormatter.py | 286 +- Mailman/Handlers/Acknowledge.py | 7 +- Mailman/Handlers/Approve.py | 7 +- Mailman/Handlers/CalcRecips.py | 27 +- Mailman/Handlers/Cleanse.py | 17 +- Mailman/Handlers/CleanseDKIM.py | 26 +- Mailman/Handlers/CookHeaders.py | 606 +- Mailman/Handlers/Decorate.py | 36 +- Mailman/Handlers/Hold.py | 381 +- Mailman/Handlers/MimeDel.py | 53 +- Mailman/Handlers/Moderate.py | 60 +- Mailman/Handlers/Replybot.py | 144 +- Mailman/Handlers/SMTPDirect.py | 646 +- Mailman/Handlers/Scrubber.py | 312 +- Mailman/Handlers/SpamDetect.py | 112 +- Mailman/Handlers/Tagger.py | 7 +- Mailman/Handlers/ToArchive.py | 20 +- Mailman/Handlers/ToDigest.py | 685 +- Mailman/Handlers/ToOutgoing.py | 90 +- Mailman/Handlers/ToUsenet.py | 5 +- Mailman/Handlers/__init__.py | 27 - Mailman/ListAdmin.py | 543 +- Mailman/LockFile.py | 766 +- Mailman/Logging/Logger.py | 58 +- Mailman/Logging/StampedLogger.py | 11 - Mailman/Logging/Syslog.py | 31 +- Mailman/MTA/Manual.py | 8 +- Mailman/MailList.py | 1802 +- Mailman/Mailbox.py | 80 +- Mailman/Message.py | 178 +- Mailman/OldStyleMemberships.py | 345 +- Mailman/Pending.py | 164 +- Mailman/Post.py | 16 +- Mailman/Queue/ArchRunner.py | 48 +- Mailman/Queue/BounceRunner.py | 498 +- Mailman/Queue/CommandRunner.py | 459 +- Mailman/Queue/IncomingRunner.py | 401 +- Mailman/Queue/MaildirRunner.py | 95 +- Mailman/Queue/NewsRunner.py | 253 +- Mailman/Queue/OutgoingRunner.py | 612 +- Mailman/Queue/RetryRunner.py | 280 +- Mailman/Queue/Runner.py | 527 +- Mailman/Queue/Switchboard.py | 804 +- Mailman/Queue/VirginRunner.py | 230 +- Mailman/Queue/__init__.py | 58 - Mailman/SecurityManager.py | 17 +- Mailman/Site.py | 9 +- Mailman/UserDesc.py | 4 +- Mailman/Utils.py | 1260 +- Mailman/Version.py | 6 +- Mailman/__init__.py | 15 - Mailman/htmlformat.py | 291 +- Mailman/i18n.py | 30 +- Mailman/mm_cfg.py.dist.in | 4 +- Mailman/versions.py | 19 +- Makefile.in | 217 +- NEWS | 87 +- bin/Makefile.in | 18 +- bin/add_members | 215 +- bin/arch | 89 +- bin/b4b5-archfix | 33 +- bin/change_pw | 84 +- bin/check_db | 233 +- bin/check_perms | 58 +- bin/cleanarch | 78 +- bin/clone_member | 114 +- bin/config_list | 330 +- bin/discard | 39 +- bin/dumpdb | 124 +- bin/export.py | 52 +- bin/find_member | 59 +- bin/fix_url.py | 40 +- bin/genaliases | 34 +- bin/inject | 70 +- bin/list_admins | 58 +- bin/list_lists | 81 +- bin/list_members | 172 +- bin/list_owners | 49 +- bin/mailmanctl | 691 +- bin/mmsitepass | 48 +- bin/msgfmt-python2.py | 57 +- bin/msgfmt.py | 50 +- bin/newlist | 155 +- bin/pygettext.py | 423 +- bin/qrunner | 177 +- bin/rb-archfix | 60 +- bin/remove_members | 149 +- bin/reset_pw.py | 36 +- bin/rmlist | 38 +- bin/show_qfiles | 73 +- bin/sync_members | 272 +- bin/transcheck | 107 +- bin/unshunt | 33 +- bin/update | 719 +- bin/withlist | 143 +- configure | 3673 ++- configure.ac | 11 +- contrib/check_perms_grsecurity.py | 4 +- contrib/courier-to-mailman.py | 10 +- contrib/import_majordomo_into_mailman.pl | 139 +- contrib/majordomo2mailman.pl | 143 +- contrib/mm-handler | 17 +- contrib/mm-handler-2.1.10 | 17 +- contrib/qmail-to-mailman.py | 4 +- contrib/rotatelogs.py | 4 +- contrib/sitemapgen | 50 +- cron/bumpdigests | 28 +- cron/checkdbs | 282 +- cron/cull_bad_shunt | 30 +- cron/disabled | 86 +- cron/gate_news | 187 +- cron/mailpasswds | 230 +- cron/nightly_gzip | 37 +- cron/senddigests | 42 +- doc/mailman-admin/about.html | 119 +- doc/mailman-admin/contents.html | 112 +- doc/mailman-admin/front.html | 111 +- doc/mailman-admin/general-personality.html | 128 +- doc/mailman-admin/index.html | 107 +- doc/mailman-admin/mailman-admin.html | 107 +- doc/mailman-admin/node11.html | 125 +- doc/mailman-admin/node12.html | 107 +- doc/mailman-admin/node13.html | 124 +- doc/mailman-admin/node14.html | 124 +- doc/mailman-admin/node15.html | 106 +- doc/mailman-admin/node16.html | 113 +- doc/mailman-admin/node17.html | 107 +- doc/mailman-admin/node18.html | 159 +- doc/mailman-admin/node19.html | 141 +- doc/mailman-admin/node20.html | 132 +- doc/mailman-admin/node21.html | 128 +- doc/mailman-admin/node23.html | 112 +- doc/mailman-admin/node24.html | 109 +- doc/mailman-admin/node25.html | 122 +- doc/mailman-admin/node26.html | 112 +- doc/mailman-admin/node27.html | 107 +- doc/mailman-admin/node28.html | 104 +- doc/mailman-admin/node29.html | 104 +- doc/mailman-admin/node3.html | 115 +- doc/mailman-admin/node30.html | 105 +- doc/mailman-admin/node31.html | 104 +- doc/mailman-admin/node32.html | 104 +- doc/mailman-admin/node33.html | 104 +- doc/mailman-admin/node34.html | 108 +- doc/mailman-admin/node35.html | 106 +- doc/mailman-admin/node4.html | 121 +- doc/mailman-admin/node5.html | 107 +- doc/mailman-admin/node6.html | 107 +- doc/mailman-admin/node7.html | 114 +- doc/mailman-admin/node8.html | 119 +- doc/mailman-admin/node9.html | 115 +- doc/mailman-admin/sender-filters.html | 126 +- doc/mailman-install/about.html | 115 +- doc/mailman-install/bsd-issues.html | 106 +- doc/mailman-install/building.html | 113 +- doc/mailman-install/create-install-dir.html | 120 +- doc/mailman-install/customizing.html | 117 +- doc/mailman-install/exim3-transport.html | 106 +- doc/mailman-install/front.html | 131 +- doc/mailman-install/index.html | 108 +- doc/mailman-install/mail-server.html | 122 +- doc/mailman-install/mailman-install.html | 108 +- doc/mailman-install/node10.html | 109 +- doc/mailman-install/node12.html | 113 +- doc/mailman-install/node15.html | 103 +- doc/mailman-install/node16.html | 112 +- doc/mailman-install/node17.html | 111 +- doc/mailman-install/node18.html | 104 +- doc/mailman-install/node2.html | 130 +- doc/mailman-install/node20.html | 104 +- doc/mailman-install/node21.html | 103 +- doc/mailman-install/node22.html | 103 +- doc/mailman-install/node23.html | 103 +- doc/mailman-install/node24.html | 111 +- doc/mailman-install/node25.html | 105 +- doc/mailman-install/node26.html | 104 +- doc/mailman-install/node27.html | 104 +- doc/mailman-install/node28.html | 103 +- doc/mailman-install/node29.html | 104 +- doc/mailman-install/node3.html | 111 +- doc/mailman-install/node30.html | 101 +- doc/mailman-install/node31.html | 119 +- doc/mailman-install/node32.html | 116 +- doc/mailman-install/node33.html | 105 +- doc/mailman-install/node34.html | 101 +- doc/mailman-install/node36.html | 103 +- doc/mailman-install/node37.html | 103 +- doc/mailman-install/node38.html | 101 +- doc/mailman-install/node4.html | 113 +- doc/mailman-install/node41.html | 119 +- doc/mailman-install/node42.html | 108 +- doc/mailman-install/node43.html | 104 +- doc/mailman-install/node44.html | 120 +- doc/mailman-install/node45.html | 136 +- doc/mailman-install/node47.html | 109 +- doc/mailman-install/node48.html | 112 +- doc/mailman-install/node50.html | 117 +- doc/mailman-install/node7.html | 132 +- doc/mailman-install/node8.html | 101 +- doc/mailman-install/node9.html | 114 +- doc/mailman-install/postfix-integration.html | 123 +- doc/mailman-install/postfix-virtual.html | 116 +- doc/mailman-install/qmail-issues.html | 132 +- doc/mailman-install/site-list.html | 107 +- doc/mailman-install/troubleshooting.html | 148 +- doc/mailman-member-es/about.html | 121 +- doc/mailman-member-es/contents.html | 128 +- doc/mailman-member-es/front.html | 113 +- doc/mailman-member-es/index.html | 125 +- doc/mailman-member-es/mailman-member-es.html | 127 +- doc/mailman-member-es/node10.html | 123 +- doc/mailman-member-es/node11.html | 110 +- doc/mailman-member-es/node12.html | 114 +- doc/mailman-member-es/node13.html | 116 +- doc/mailman-member-es/node14.html | 117 +- doc/mailman-member-es/node15.html | 118 +- doc/mailman-member-es/node16.html | 124 +- doc/mailman-member-es/node17.html | 150 +- doc/mailman-member-es/node18.html | 118 +- doc/mailman-member-es/node19.html | 111 +- doc/mailman-member-es/node20.html | 121 +- doc/mailman-member-es/node21.html | 121 +- doc/mailman-member-es/node22.html | 116 +- doc/mailman-member-es/node23.html | 119 +- doc/mailman-member-es/node24.html | 125 +- doc/mailman-member-es/node25.html | 120 +- doc/mailman-member-es/node26.html | 111 +- doc/mailman-member-es/node27.html | 125 +- doc/mailman-member-es/node28.html | 123 +- doc/mailman-member-es/node29.html | 117 +- doc/mailman-member-es/node3.html | 114 +- doc/mailman-member-es/node30.html | 174 +- doc/mailman-member-es/node31.html | 116 +- doc/mailman-member-es/node32.html | 113 +- doc/mailman-member-es/node33.html | 115 +- doc/mailman-member-es/node34.html | 109 +- doc/mailman-member-es/node35.html | 114 +- doc/mailman-member-es/node36.html | 112 +- doc/mailman-member-es/node37.html | 121 +- doc/mailman-member-es/node38.html | 117 +- doc/mailman-member-es/node39.html | 111 +- doc/mailman-member-es/node4.html | 107 +- doc/mailman-member-es/node40.html | 124 +- doc/mailman-member-es/node41.html | 254 +- doc/mailman-member-es/node42.html | 192 +- doc/mailman-member-es/node5.html | 106 +- doc/mailman-member-es/node6.html | 105 +- doc/mailman-member-es/node7.html | 108 +- doc/mailman-member-es/node8.html | 114 +- doc/mailman-member-es/node9.html | 139 +- doc/mailman-member/about.html | 119 +- doc/mailman-member/contents.html | 126 +- doc/mailman-member/front.html | 111 +- doc/mailman-member/index.html | 121 +- doc/mailman-member/mailman-member.html | 121 +- doc/mailman-member/node10.html | 122 +- doc/mailman-member/node11.html | 111 +- doc/mailman-member/node12.html | 114 +- doc/mailman-member/node13.html | 118 +- doc/mailman-member/node14.html | 117 +- doc/mailman-member/node15.html | 117 +- doc/mailman-member/node16.html | 124 +- doc/mailman-member/node17.html | 151 +- doc/mailman-member/node18.html | 118 +- doc/mailman-member/node19.html | 111 +- doc/mailman-member/node20.html | 121 +- doc/mailman-member/node21.html | 121 +- doc/mailman-member/node22.html | 115 +- doc/mailman-member/node23.html | 121 +- doc/mailman-member/node24.html | 126 +- doc/mailman-member/node25.html | 121 +- doc/mailman-member/node26.html | 111 +- doc/mailman-member/node27.html | 125 +- doc/mailman-member/node28.html | 124 +- doc/mailman-member/node29.html | 119 +- doc/mailman-member/node3.html | 118 +- doc/mailman-member/node30.html | 171 +- doc/mailman-member/node31.html | 117 +- doc/mailman-member/node32.html | 113 +- doc/mailman-member/node33.html | 115 +- doc/mailman-member/node34.html | 109 +- doc/mailman-member/node35.html | 115 +- doc/mailman-member/node36.html | 111 +- doc/mailman-member/node37.html | 119 +- doc/mailman-member/node38.html | 115 +- doc/mailman-member/node39.html | 108 +- doc/mailman-member/node4.html | 107 +- doc/mailman-member/node40.html | 122 +- doc/mailman-member/node41.html | 252 +- doc/mailman-member/node42.html | 190 +- doc/mailman-member/node5.html | 106 +- doc/mailman-member/node6.html | 105 +- doc/mailman-member/node7.html | 108 +- doc/mailman-member/node8.html | 114 +- doc/mailman-member/node9.html | 138 +- messages/Makefile.in | 7 +- messages/ar/LC_MESSAGES/mailman.po | 14 +- messages/ast/LC_MESSAGES/mailman.po | 12 +- messages/ca/LC_MESSAGES/mailman.po | 12 +- messages/cs/LC_MESSAGES/mailman.po | 10 +- messages/da/LC_MESSAGES/mailman.po | 14 +- messages/de/LC_MESSAGES/mailman.po | 12 +- messages/el/LC_MESSAGES/mailman.po | 12 +- messages/eo/LC_MESSAGES/mailman.po | 6 +- messages/es/LC_MESSAGES/mailman.po | 12 +- messages/et/LC_MESSAGES/mailman.po | 12 +- messages/eu/LC_MESSAGES/mailman.po | 10 +- messages/fa/LC_MESSAGES/mailman.po | 6 +- messages/fi/LC_MESSAGES/mailman.po | 12 +- messages/fr/LC_MESSAGES/mailman.po | 12 +- messages/gl/LC_MESSAGES/mailman.po | 12 +- messages/he/LC_MESSAGES/mailman.po | 10 +- messages/hr/LC_MESSAGES/mailman.po | 10 +- messages/hu/LC_MESSAGES/mailman.po | 12 +- messages/ia/LC_MESSAGES/mailman.po | 10 +- messages/it/LC_MESSAGES/mailman.po | 12 +- messages/ja/LC_MESSAGES/mailman.po | 12 +- messages/ja/doc/Defaults.py.in | 20401 +---------------- messages/ko/LC_MESSAGES/mailman.po | 14 +- messages/lt/LC_MESSAGES/mailman.po | 6 +- messages/mailman.pot | 6 +- messages/nl/LC_MESSAGES/mailman.po | 10 +- messages/no/LC_MESSAGES/mailman.po | 14 +- messages/pl/LC_MESSAGES/mailman.po | 10 +- messages/pt/LC_MESSAGES/mailman.po | 12 +- messages/pt_BR/LC_MESSAGES/mailman.po | 12 +- messages/ro/LC_MESSAGES/mailman.po | 10 +- messages/ru/LC_MESSAGES/mailman.po | 12 +- messages/sk/LC_MESSAGES/mailman.po | 10 +- messages/sl/LC_MESSAGES/mailman.po | 16 +- messages/sr/LC_MESSAGES/mailman.po | 6 +- messages/sv/LC_MESSAGES/mailman.po | 14 +- messages/tr/LC_MESSAGES/mailman.po | 10 +- messages/uk/LC_MESSAGES/mailman.po | 12 +- messages/vi/LC_MESSAGES/mailman.po | 12 +- messages/zh_CN/LC_MESSAGES/mailman.po | 12 +- messages/zh_TW/LC_MESSAGES/mailman.po | 8 +- misc/sitelist.cfg | 2 +- scripts/bounces | 13 +- scripts/confirm | 14 +- scripts/driver | 7 +- scripts/join | 13 +- scripts/leave | 15 +- scripts/owner | 26 +- scripts/post | 33 +- scripts/request | 16 +- src/Makefile.in | 24 +- src/cgi-wrapper.c | 44 +- src/common.c | 127 +- src/common.h | 5 - src/mail-wrapper.c | 10 +- templates/Makefile.in | 7 +- templates/ar/admindbdetails.html | 72 +- templates/ar/admindbpreamble.html | 65 +- templates/ar/admindbsummary.html | 65 +- templates/ar/admlogin.html | 110 +- templates/ar/archidxentry.html | 71 +- templates/ar/archidxfoot.html | 91 +- templates/ar/archidxhead.html | 94 +- templates/ar/archlistend.html | 64 +- templates/ar/archliststart.html | 71 +- templates/ar/archtoc.html | 83 +- templates/ar/archtocentry.html | 81 +- templates/ar/archtocnombox.html | 83 +- templates/ar/article.html | 77 +- templates/ar/emptyarchive.html | 84 +- templates/ar/headfoot.html | 68 +- templates/ar/listinfo.html | 319 +- templates/ar/options.html | 440 +- templates/ar/private.html | 144 +- templates/ar/roster.html | 158 +- templates/ar/subscribe.html | 76 +- templates/ast/admindbdetails.html | 94 +- templates/ast/admindbpreamble.html | 71 +- templates/ast/admindbsummary.html | 71 +- templates/ast/admlogin.html | 114 +- templates/ast/archidxentry.html | 71 +- templates/ast/archidxfoot.html | 93 +- templates/ast/archidxhead.html | 94 +- templates/ast/archlistend.html | 64 +- templates/ast/archliststart.html | 71 +- templates/ast/archtoc.html | 85 +- templates/ast/archtocentry.html | 80 +- templates/ast/archtocnombox.html | 87 +- templates/ast/article.html | 138 +- templates/ast/emptyarchive.html | 85 +- templates/ast/headfoot.html | 85 +- templates/ast/listinfo.html | 317 +- templates/ast/options.html | 495 +- templates/ast/private.html | 150 +- templates/ast/roster.html | 158 +- templates/ast/subscribe.html | 72 +- templates/ca/admindbdetails.html | 158 +- templates/ca/admindbpreamble.html | 79 +- templates/ca/admindbsummary.html | 79 +- templates/ca/admlogin.html | 122 +- templates/ca/archidxentry.html | 71 +- templates/ca/archidxfoot.html | 91 +- templates/ca/archidxhead.html | 98 +- templates/ca/archlistend.html | 64 +- templates/ca/archliststart.html | 71 +- templates/ca/archtoc.html | 87 +- templates/ca/archtocentry.html | 81 +- templates/ca/archtocnombox.html | 85 +- templates/ca/article.html | 78 +- templates/ca/emptyarchive.html | 85 +- templates/ca/headfoot.html | 87 +- templates/ca/listinfo.html | 335 +- templates/ca/options.html | 546 +- templates/ca/private.html | 159 +- templates/ca/roster.html | 162 +- templates/ca/subscribe.html | 72 +- templates/cs/admindbdetails.html | 168 +- templates/cs/admindbpreamble.html | 85 +- templates/cs/admindbsummary.html | 89 +- templates/cs/admlogin.html | 120 +- templates/cs/archidxentry.html | 71 +- templates/cs/archidxfoot.html | 93 +- templates/cs/archidxhead.html | 96 +- templates/cs/archlistend.html | 65 +- templates/cs/archliststart.html | 73 +- templates/cs/archtoc.html | 87 +- templates/cs/archtocentry.html | 82 +- templates/cs/archtocnombox.html | 85 +- templates/cs/article.html | 76 +- templates/cs/emptyarchive.html | 89 +- templates/cs/headfoot.html | 101 +- templates/cs/listinfo.html | 342 +- templates/cs/options.html | 568 +- templates/cs/private.html | 154 +- templates/cs/roster.html | 164 +- templates/cs/subscribe.html | 72 +- templates/da/admindbdetails.html | 124 +- templates/da/admindbpreamble.html | 71 +- templates/da/admindbsummary.html | 77 +- templates/da/admlogin.html | 122 +- templates/da/archidxfoot.html | 91 +- templates/da/archidxhead.html | 94 +- templates/da/archliststart.html | 71 +- templates/da/archtoc.html | 83 +- templates/da/archtocentry.html | 81 +- templates/da/archtocnombox.html | 83 +- templates/da/article.html | 77 +- templates/da/emptyarchive.html | 87 +- templates/da/headfoot.html | 78 +- templates/da/listinfo.html | 332 +- templates/da/options.html | 543 +- templates/da/private.html | 157 +- templates/da/roster.html | 160 +- templates/da/subscribe.html | 72 +- templates/de/admindbdetails.html | 134 +- templates/de/admindbpreamble.html | 73 +- templates/de/admindbsummary.html | 77 +- templates/de/admlogin.html | 118 +- templates/de/archidxentry.html | 71 +- templates/de/archidxfoot.html | 93 +- templates/de/archidxhead.html | 96 +- templates/de/archlistend.html | 64 +- templates/de/archliststart.html | 71 +- templates/de/archtoc.html | 85 +- templates/de/archtocentry.html | 81 +- templates/de/archtocnombox.html | 85 +- templates/de/article.html | 77 +- templates/de/emptyarchive.html | 85 +- templates/de/headfoot.html | 86 +- templates/de/listinfo.html | 344 +- templates/de/options.html | 516 +- templates/de/private.html | 162 +- templates/de/roster.html | 156 +- templates/de/subscribe.html | 72 +- templates/el/admindbdetails.html | 184 +- templates/el/admindbpreamble.html | 77 +- templates/el/admindbsummary.html | 83 +- templates/el/admlogin.html | 128 +- templates/el/archidxentry.html | 71 +- templates/el/archidxfoot.html | 95 +- templates/el/archidxhead.html | 96 +- templates/el/archlistend.html | 64 +- templates/el/archliststart.html | 71 +- templates/el/archtoc.html | 89 +- templates/el/archtocentry.html | 81 +- templates/el/archtocnombox.html | 87 +- templates/el/article.html | 79 +- templates/el/emptyarchive.html | 89 +- templates/el/headfoot.html | 102 +- templates/el/listinfo.html | 344 +- templates/el/options.html | 648 +- templates/el/private.html | 161 +- templates/el/roster.html | 162 +- templates/el/subscribe.html | 72 +- templates/en/admindbdetails.html | 72 +- templates/en/admindbpreamble.html | 65 +- templates/en/admindbsummary.html | 65 +- templates/en/admlogin.html | 109 +- templates/en/archidxentry.html | 71 +- templates/en/archidxfoot.html | 91 +- templates/en/archidxhead.html | 94 +- templates/en/archlistend.html | 64 +- templates/en/archliststart.html | 73 +- templates/en/archtoc.html | 83 +- templates/en/archtocentry.html | 81 +- templates/en/archtocnombox.html | 83 +- templates/en/article.html | 77 +- templates/en/emptyarchive.html | 83 +- templates/en/headfoot.html | 70 +- templates/en/listinfo.html | 319 +- templates/en/options.html | 444 +- templates/en/private.html | 142 +- templates/en/roster.html | 162 +- templates/en/subscribe.html | 72 +- templates/eo/admlogin.html | 106 +- templates/eo/archidxentry.html | 71 +- templates/eo/archidxfoot.html | 89 +- templates/eo/archidxhead.html | 96 +- templates/eo/archlistend.html | 64 +- templates/eo/archliststart.html | 71 +- templates/eo/archtoc.html | 83 +- templates/eo/archtocentry.html | 81 +- templates/eo/archtocnombox.html | 83 +- templates/eo/article.html | 78 +- templates/eo/emptyarchive.html | 83 +- templates/eo/listinfo.html | 317 +- templates/eo/options.html | 450 +- templates/eo/private.html | 142 +- templates/eo/roster.html | 154 +- templates/eo/subscribe.html | 72 +- templates/es/admindbdetails.html | 140 +- templates/es/admindbpreamble.html | 75 +- templates/es/admindbsummary.html | 83 +- templates/es/admlogin.html | 116 +- templates/es/archidxentry.html | 71 +- templates/es/archidxfoot.html | 93 +- templates/es/archidxhead.html | 96 +- templates/es/archlistend.html | 64 +- templates/es/archliststart.html | 71 +- templates/es/archtoc.html | 85 +- templates/es/archtocentry.html | 81 +- templates/es/archtocnombox.html | 87 +- templates/es/article.html | 77 +- templates/es/emptyarchive.html | 89 +- templates/es/handle_opts.html | 74 +- templates/es/headfoot.html | 95 +- templates/es/listinfo.html | 340 +- templates/es/options.html | 572 +- templates/es/private.html | 148 +- templates/es/roster.html | 160 +- templates/es/subscribe.html | 72 +- templates/et/admindbdetails.html | 138 +- templates/et/admindbpreamble.html | 71 +- templates/et/admindbsummary.html | 77 +- templates/et/admlogin.html | 116 +- templates/et/article.html | 82 +- templates/et/emptyarchive.html | 87 +- templates/et/headfoot.html | 84 +- templates/et/listinfo.html | 324 +- templates/et/options.html | 529 +- templates/et/private.html | 148 +- templates/et/roster.html | 162 +- templates/et/subscribe.html | 72 +- templates/eu/admindbdetails.html | 72 +- templates/eu/admindbpreamble.html | 65 +- templates/eu/admindbsummary.html | 65 +- templates/eu/admlogin.html | 110 +- templates/eu/archidxentry.html | 71 +- templates/eu/archidxfoot.html | 91 +- templates/eu/archidxhead.html | 94 +- templates/eu/archlistend.html | 64 +- templates/eu/archliststart.html | 73 +- templates/eu/archtoc.html | 83 +- templates/eu/archtocentry.html | 82 +- templates/eu/article.html | 77 +- templates/eu/emptyarchive.html | 83 +- templates/eu/headfoot.html | 70 +- templates/eu/listinfo.html | 351 +- templates/eu/options.html | 444 +- templates/eu/private.html | 144 +- templates/eu/roster.html | 162 +- templates/eu/subscribe.html | 72 +- templates/fa/admlogin.html | 106 +- templates/fa/archidxfoot.html | 91 +- templates/fa/archidxhead.html | 94 +- templates/fa/archliststart.html | 71 +- templates/fa/archtoc.html | 83 +- templates/fa/archtocentry.html | 81 +- templates/fa/archtocnombox.html | 83 +- templates/fa/article.html | 77 +- templates/fa/emptyarchive.html | 83 +- templates/fa/listinfo.html | 320 +- templates/fa/options.html | 436 +- templates/fa/private.html | 140 +- templates/fa/roster.html | 156 +- templates/fa/subscribe.html | 72 +- templates/fi/admindbdetails.html | 162 +- templates/fi/admindbpreamble.html | 77 +- templates/fi/admindbsummary.html | 81 +- templates/fi/admlogin.html | 128 +- templates/fi/article.html | 81 +- templates/fi/headfoot.html | 108 +- templates/fi/listinfo.html | 342 +- templates/fi/options.html | 529 +- templates/fi/private.html | 156 +- templates/fi/roster.html | 162 +- templates/fi/subscribe.html | 72 +- templates/fr/admindbdetails.html | 162 +- templates/fr/admindbpreamble.html | 77 +- templates/fr/admindbsummary.html | 73 +- templates/fr/admlogin.html | 124 +- templates/fr/archidxentry.html | 68 +- templates/fr/archidxfoot.html | 89 +- templates/fr/archidxhead.html | 88 +- templates/fr/archlistend.html | 64 +- templates/fr/archliststart.html | 75 +- templates/fr/archtoc.html | 75 +- templates/fr/archtocentry.html | 82 +- templates/fr/archtocnombox.html | 83 +- templates/fr/article.html | 82 +- templates/fr/emptyarchive.html | 79 +- templates/fr/handle_opts.html | 74 +- templates/fr/headfoot.html | 95 +- templates/fr/listinfo.html | 341 +- templates/fr/options.html | 601 +- templates/fr/private.html | 152 +- templates/fr/roster.html | 162 +- templates/fr/subscribe.html | 72 +- templates/gl/admindbdetails.html | 152 +- templates/gl/admindbpreamble.html | 75 +- templates/gl/admindbsummary.html | 87 +- templates/gl/admlogin.html | 126 +- templates/gl/archidxentry.html | 71 +- templates/gl/archidxfoot.html | 93 +- templates/gl/archidxhead.html | 96 +- templates/gl/archlistend.html | 64 +- templates/gl/archliststart.html | 71 +- templates/gl/archtoc.html | 85 +- templates/gl/archtocentry.html | 81 +- templates/gl/article.html | 76 +- templates/gl/emptyarchive.html | 89 +- templates/gl/handle_opts.html | 74 +- templates/gl/headfoot.html | 100 +- templates/gl/listinfo.html | 344 +- templates/gl/options.html | 674 +- templates/gl/private.html | 154 +- templates/gl/roster.html | 166 +- templates/gl/subscribe.html | 72 +- templates/he/admindbdetails.html | 70 +- templates/he/admindbpreamble.html | 65 +- templates/he/admindbsummary.html | 65 +- templates/he/admlogin.html | 110 +- templates/he/archidxentry.html | 71 +- templates/he/archidxfoot.html | 91 +- templates/he/archidxhead.html | 94 +- templates/he/archlistend.html | 64 +- templates/he/archliststart.html | 73 +- templates/he/archtoc.html | 83 +- templates/he/archtocentry.html | 81 +- templates/he/archtocnombox.html | 83 +- templates/he/article.html | 77 +- templates/he/emptyarchive.html | 83 +- templates/he/headfoot.html | 70 +- templates/he/listinfo.html | 320 +- templates/he/options.html | 440 +- templates/he/private.html | 142 +- templates/he/roster.html | 160 +- templates/he/subscribe.html | 72 +- templates/hr/admindbdetails.html | 140 +- templates/hr/admindbpreamble.html | 75 +- templates/hr/admindbsummary.html | 79 +- templates/hr/admlogin.html | 122 +- templates/hr/archidxentry.html | 71 +- templates/hr/archidxfoot.html | 93 +- templates/hr/archidxhead.html | 96 +- templates/hr/archlistend.html | 64 +- templates/hr/archliststart.html | 71 +- templates/hr/archtoc.html | 87 +- templates/hr/archtocentry.html | 81 +- templates/hr/article.html | 79 +- templates/hr/emptyarchive.html | 87 +- templates/hr/headfoot.html | 76 +- templates/hr/listinfo.html | 338 +- templates/hr/options.html | 552 +- templates/hr/private.html | 154 +- templates/hr/roster.html | 160 +- templates/hr/subscribe.html | 72 +- templates/hu/admindbdetails.html | 170 +- templates/hu/admindbpreamble.html | 81 +- templates/hu/admindbsummary.html | 85 +- templates/hu/admlogin.html | 113 +- templates/hu/archidxentry.html | 71 +- templates/hu/archidxfoot.html | 93 +- templates/hu/archidxhead.html | 96 +- templates/hu/archlistend.html | 64 +- templates/hu/archliststart.html | 71 +- templates/hu/archtoc.html | 87 +- templates/hu/archtocentry.html | 81 +- templates/hu/article.html | 78 +- templates/hu/emptyarchive.html | 87 +- templates/hu/headfoot.html | 92 +- templates/hu/illik.html | 2560 ++- templates/hu/listinfo.html | 336 +- templates/hu/options.html | 580 +- templates/hu/private.html | 150 +- templates/hu/roster.html | 160 +- templates/hu/subscribe.html | 74 +- templates/ia/admindbdetails.html | 68 +- templates/ia/admindbpreamble.html | 65 +- templates/ia/admindbsummary.html | 65 +- templates/ia/admlogin.html | 110 +- templates/ia/archidxentry.html | 71 +- templates/ia/archidxfoot.html | 91 +- templates/ia/archidxhead.html | 94 +- templates/ia/archlistend.html | 64 +- templates/ia/archliststart.html | 73 +- templates/ia/archtoc.html | 83 +- templates/ia/archtocentry.html | 81 +- templates/ia/archtocnombox.html | 83 +- templates/ia/article.html | 77 +- templates/ia/emptyarchive.html | 83 +- templates/ia/headfoot.html | 70 +- templates/ia/listinfo.html | 318 +- templates/ia/options.html | 442 +- templates/ia/private.html | 144 +- templates/ia/roster.html | 160 +- templates/ia/subscribe.html | 72 +- templates/it/admindbdetails.html | 100 +- templates/it/admindbpreamble.html | 67 +- templates/it/admindbsummary.html | 67 +- templates/it/admlogin.html | 110 +- templates/it/archidxentry.html | 71 +- templates/it/archidxfoot.html | 91 +- templates/it/archidxhead.html | 94 +- templates/it/archlistend.html | 64 +- templates/it/archliststart.html | 71 +- templates/it/archtoc.html | 83 +- templates/it/archtocentry.html | 81 +- templates/it/archtocnombox.html | 85 +- templates/it/article.html | 78 +- templates/it/emptyarchive.html | 83 +- templates/it/headfoot.html | 75 +- templates/it/listinfo.html | 332 +- templates/it/options.html | 468 +- templates/it/private.html | 142 +- templates/it/roster.html | 160 +- templates/it/subscribe.html | 72 +- templates/ja/admindbdetails.html | 180 +- templates/ja/admindbpreamble.html | 79 +- templates/ja/admindbsummary.html | 83 +- templates/ja/admlogin.html | 124 +- templates/ja/archidxentry.html | 71 +- templates/ja/archidxfoot.html | 95 +- templates/ja/archidxhead.html | 96 +- templates/ja/archlistend.html | 64 +- templates/ja/archliststart.html | 71 +- templates/ja/archtoc.html | 89 +- templates/ja/archtocentry.html | 81 +- templates/ja/archtocnombox.html | 85 +- templates/ja/article.html | 80 +- templates/ja/emptyarchive.html | 91 +- templates/ja/headfoot.html | 102 +- templates/ja/listinfo.html | 348 +- templates/ja/options.html | 591 +- templates/ja/private.html | 160 +- templates/ja/roster.html | 169 +- templates/ja/subscribe.html | 72 +- templates/ko/admindbdetails.html | 158 +- templates/ko/admindbpreamble.html | 77 +- templates/ko/admindbsummary.html | 81 +- templates/ko/admlogin.html | 120 +- templates/ko/article.html | 76 +- templates/ko/emptyarchive.html | 87 +- templates/ko/headfoot.html | 92 +- templates/ko/listinfo.html | 342 +- templates/ko/options.html | 580 +- templates/ko/private.html | 156 +- templates/ko/roster.html | 157 +- templates/ko/subscribe.html | 72 +- templates/lt/admindbdetails.html | 72 +- templates/lt/admindbpreamble.html | 77 +- templates/lt/admindbsummary.html | 79 +- templates/lt/admlogin.html | 120 +- templates/lt/archidxentry.html | 71 +- templates/lt/archidxfoot.html | 93 +- templates/lt/archidxhead.html | 96 +- templates/lt/archlistend.html | 64 +- templates/lt/archliststart.html | 73 +- templates/lt/archtoc.html | 87 +- templates/lt/archtocentry.html | 81 +- templates/lt/article.html | 77 +- templates/lt/emptyarchive.html | 87 +- templates/lt/headfoot.html | 80 +- templates/lt/listinfo.html | 337 +- templates/lt/options.html | 548 +- templates/lt/private.html | 146 +- templates/lt/roster.html | 161 +- templates/lt/subscribe.html | 72 +- templates/nl/admindbdetails.html | 73 +- templates/nl/admindbpreamble.html | 70 +- templates/nl/admindbsummary.html | 68 +- templates/nl/admlogin.html | 110 +- templates/nl/archidxentry.html | 71 +- templates/nl/archidxfoot.html | 91 +- templates/nl/archidxhead.html | 94 +- templates/nl/archlistend.html | 64 +- templates/nl/archliststart.html | 71 +- templates/nl/archtoc.html | 83 +- templates/nl/archtocentry.html | 81 +- templates/nl/archtocnombox.html | 83 +- templates/nl/article.html | 77 +- templates/nl/emptyarchive.html | 83 +- templates/nl/headfoot.html | 70 +- templates/nl/listinfo.html | 318 +- templates/nl/options.html | 462 +- templates/nl/private.html | 144 +- templates/nl/roster.html | 158 +- templates/nl/subscribe.html | 72 +- templates/no/admindbdetails.html | 138 +- templates/no/admindbpreamble.html | 73 +- templates/no/admindbsummary.html | 79 +- templates/no/admlogin.html | 118 +- templates/no/archidxfoot.html | 91 +- templates/no/archidxhead.html | 94 +- templates/no/archliststart.html | 71 +- templates/no/archtoc.html | 83 +- templates/no/archtocentry.html | 81 +- templates/no/archtocnombox.html | 83 +- templates/no/article.html | 77 +- templates/no/emptyarchive.html | 85 +- templates/no/headfoot.html | 76 +- templates/no/listinfo.html | 332 +- templates/no/options.html | 533 +- templates/no/private.html | 150 +- templates/no/roster.html | 160 +- templates/no/subscribe.html | 72 +- templates/pl/admlogin.html | 124 +- templates/pl/archidxentry.html | 71 +- templates/pl/archidxfoot.html | 87 +- templates/pl/archidxhead.html | 94 +- templates/pl/archlistend.html | 64 +- templates/pl/archliststart.html | 71 +- templates/pl/archtoc.html | 77 +- templates/pl/archtocentry.html | 76 +- templates/pl/archtocnombox.html | 77 +- templates/pl/article.html | 70 +- templates/pl/emptyarchive.html | 91 +- templates/pl/listinfo.html | 334 +- templates/pl/options.html | 598 +- templates/pl/private.html | 156 +- templates/pl/roster.html | 161 +- templates/pl/subscribe.html | 76 +- templates/pt/admindbdetails.html | 151 +- templates/pt/admindbpreamble.html | 75 +- templates/pt/admindbsummary.html | 79 +- templates/pt/admlogin.html | 122 +- templates/pt/archidxentry.html | 71 +- templates/pt/archidxfoot.html | 93 +- templates/pt/archidxhead.html | 96 +- templates/pt/archlistend.html | 64 +- templates/pt/archliststart.html | 71 +- templates/pt/archtoc.html | 87 +- templates/pt/archtocentry.html | 81 +- templates/pt/article.html | 77 +- templates/pt/emptyarchive.html | 89 +- templates/pt/headfoot.html | 92 +- templates/pt/listinfo.html | 336 +- templates/pt/options.html | 560 +- templates/pt/private.html | 155 +- templates/pt/roster.html | 162 +- templates/pt/subscribe.html | 72 +- templates/pt_BR/admindbdetails.html | 162 +- templates/pt_BR/admindbpreamble.html | 79 +- templates/pt_BR/admindbsummary.html | 83 +- templates/pt_BR/admlogin.html | 124 +- templates/pt_BR/archidxentry.html | 71 +- templates/pt_BR/archidxfoot.html | 93 +- templates/pt_BR/archidxhead.html | 96 +- templates/pt_BR/archlistend.html | 64 +- templates/pt_BR/archliststart.html | 71 +- templates/pt_BR/archtoc.html | 87 +- templates/pt_BR/archtocentry.html | 81 +- templates/pt_BR/article.html | 79 +- templates/pt_BR/emptyarchive.html | 87 +- templates/pt_BR/headfoot.html | 96 +- templates/pt_BR/listinfo.html | 335 +- templates/pt_BR/options.html | 604 +- templates/pt_BR/private.html | 158 +- templates/pt_BR/roster.html | 160 +- templates/pt_BR/subscribe.html | 72 +- templates/ro/admindbdetails.html | 108 +- templates/ro/admindbpreamble.html | 71 +- templates/ro/admindbsummary.html | 73 +- templates/ro/admlogin.html | 116 +- templates/ro/archidxentry.html | 71 +- templates/ro/archidxfoot.html | 91 +- templates/ro/archidxhead.html | 96 +- templates/ro/archlistend.html | 64 +- templates/ro/archliststart.html | 71 +- templates/ro/archtoc.html | 87 +- templates/ro/archtocentry.html | 81 +- templates/ro/article.html | 77 +- templates/ro/emptyarchive.html | 89 +- templates/ro/headfoot.html | 72 +- templates/ro/listinfo.html | 329 +- templates/ro/options.html | 492 +- templates/ro/private.html | 150 +- templates/ro/roster.html | 164 +- templates/ro/subscribe.html | 74 +- templates/ru/admindbdetails.html | 76 +- templates/ru/admindbpreamble.html | 65 +- templates/ru/admindbsummary.html | 65 +- templates/ru/admlogin.html | 102 +- templates/ru/archidxentry.html | 65 +- templates/ru/archidxfoot.html | 91 +- templates/ru/archidxhead.html | 94 +- templates/ru/archlistend.html | 64 +- templates/ru/archliststart.html | 71 +- templates/ru/archtoc.html | 83 +- templates/ru/archtocentry.html | 81 +- templates/ru/archtocnombox.html | 83 +- templates/ru/article.html | 76 +- templates/ru/emptyarchive.html | 83 +- templates/ru/headfoot.html | 81 +- templates/ru/listinfo.html | 302 +- templates/ru/options.html | 442 +- templates/ru/private.html | 138 +- templates/ru/roster.html | 152 +- templates/ru/subscribe.html | 72 +- templates/sk/admindbdetails.html | 168 +- templates/sk/admindbpreamble.html | 87 +- templates/sk/admindbsummary.html | 91 +- templates/sk/admlogin.html | 122 +- templates/sk/archidxentry.html | 71 +- templates/sk/archidxfoot.html | 93 +- templates/sk/archidxhead.html | 96 +- templates/sk/archlistend.html | 64 +- templates/sk/archliststart.html | 71 +- templates/sk/archtoc.html | 87 +- templates/sk/archtocentry.html | 81 +- templates/sk/archtocnombox.html | 85 +- templates/sk/article.html | 76 +- templates/sk/emptyarchive.html | 89 +- templates/sk/headfoot.html | 96 +- templates/sk/listinfo.html | 350 +- templates/sk/options.html | 615 +- templates/sk/private.html | 156 +- templates/sk/roster.html | 162 +- templates/sk/subscribe.html | 72 +- templates/sl/admindbdetails.html | 142 +- templates/sl/admindbpreamble.html | 73 +- templates/sl/admindbsummary.html | 77 +- templates/sl/admlogin.html | 118 +- templates/sl/archidxentry.html | 71 +- templates/sl/archidxfoot.html | 91 +- templates/sl/archidxhead.html | 94 +- templates/sl/archlistend.html | 64 +- templates/sl/archliststart.html | 71 +- templates/sl/archtoc.html | 83 +- templates/sl/archtocentry.html | 81 +- templates/sl/article.html | 77 +- templates/sl/emptyarchive.html | 85 +- templates/sl/headfoot.html | 84 +- templates/sl/listinfo.html | 336 +- templates/sl/options.html | 554 +- templates/sl/private.html | 158 +- templates/sl/roster.html | 162 +- templates/sl/subscribe.html | 72 +- templates/sr/admindbdetails.html | 85 +- templates/sr/admindbpreamble.html | 65 +- templates/sr/admindbsummary.html | 65 +- templates/sr/admlogin.html | 115 +- templates/sr/archidxentry.html | 71 +- templates/sr/archidxfoot.html | 91 +- templates/sr/archidxhead.html | 96 +- templates/sr/archlistend.html | 64 +- templates/sr/archliststart.html | 75 +- templates/sr/archtoc.html | 83 +- templates/sr/archtocentry.html | 74 +- templates/sr/article.html | 61 +- templates/sr/emptyarchive.html | 83 +- templates/sr/handle_opts.html | 76 +- templates/sr/headfoot.html | 68 +- templates/sr/listinfo.html | 300 +- templates/sr/options.html | 467 +- templates/sr/private.html | 151 +- templates/sr/roster.html | 160 +- templates/sr/subscribe.html | 74 +- templates/sv/admindbdetails.html | 98 +- templates/sv/admindbpreamble.html | 69 +- templates/sv/admindbsummary.html | 68 +- templates/sv/admlogin.html | 108 +- templates/sv/archtoc.html | 87 +- templates/sv/archtocentry.html | 81 +- templates/sv/article.html | 80 +- templates/sv/emptyarchive.html | 83 +- templates/sv/headfoot.html | 76 +- templates/sv/listinfo.html | 308 +- templates/sv/options.html | 495 +- templates/sv/private.html | 144 +- templates/sv/roster.html | 152 +- templates/sv/subscribe.html | 72 +- templates/tr/admindbdetails.html | 167 +- templates/tr/admindbpreamble.html | 77 +- templates/tr/admindbsummary.html | 83 +- templates/tr/admlogin.html | 129 +- templates/tr/archidxentry.html | 71 +- templates/tr/archidxfoot.html | 92 +- templates/tr/archidxhead.html | 95 +- templates/tr/archlistend.html | 64 +- templates/tr/archliststart.html | 74 +- templates/tr/archtoc.html | 88 +- templates/tr/archtocentry.html | 82 +- templates/tr/archtocnombox.html | 86 +- templates/tr/article.html | 78 +- templates/tr/emptyarchive.html | 88 +- templates/tr/headfoot.html | 99 +- templates/tr/listinfo.html | 343 +- templates/tr/options.html | 613 +- templates/tr/private.html | 163 +- templates/tr/roster.html | 163 +- templates/tr/subscribe.html | 72 +- templates/uk/admindbdetails.html | 69 +- templates/uk/admindbpreamble.html | 65 +- templates/uk/admindbsummary.html | 65 +- templates/uk/admlogin.html | 108 +- templates/uk/archidxentry.html | 71 +- templates/uk/archidxfoot.html | 91 +- templates/uk/archidxhead.html | 94 +- templates/uk/archlistend.html | 64 +- templates/uk/archliststart.html | 71 +- templates/uk/archtoc.html | 83 +- templates/uk/archtocentry.html | 81 +- templates/uk/archtocnombox.html | 83 +- templates/uk/article.html | 77 +- templates/uk/emptyarchive.html | 85 +- templates/uk/headfoot.html | 70 +- templates/uk/listinfo.html | 318 +- templates/uk/options.html | 436 +- templates/uk/private.html | 140 +- templates/uk/roster.html | 162 +- templates/uk/subscribe.html | 72 +- templates/vi/admindbdetails.html | 77 +- templates/vi/admindbpreamble.html | 65 +- templates/vi/admindbsummary.html | 65 +- templates/vi/admlogin.html | 110 +- templates/vi/archidxentry.html | 71 +- templates/vi/archidxfoot.html | 91 +- templates/vi/archidxhead.html | 94 +- templates/vi/archlistend.html | 64 +- templates/vi/archliststart.html | 71 +- templates/vi/archtoc.html | 83 +- templates/vi/archtocentry.html | 81 +- templates/vi/archtocnombox.html | 83 +- templates/vi/article.html | 77 +- templates/vi/emptyarchive.html | 83 +- templates/vi/headfoot.html | 82 +- templates/vi/listinfo.html | 316 +- templates/vi/options.html | 434 +- templates/vi/private.html | 142 +- templates/vi/roster.html | 156 +- templates/vi/subscribe.html | 72 +- templates/zh_CN/admindbdetails.html | 68 +- templates/zh_CN/admindbpreamble.html | 65 +- templates/zh_CN/admindbsummary.html | 65 +- templates/zh_CN/admlogin.html | 106 +- templates/zh_CN/archidxentry.html | 71 +- templates/zh_CN/archidxfoot.html | 91 +- templates/zh_CN/archidxhead.html | 94 +- templates/zh_CN/archlistend.html | 64 +- templates/zh_CN/archliststart.html | 71 +- templates/zh_CN/archtoc.html | 83 +- templates/zh_CN/archtocentry.html | 81 +- templates/zh_CN/archtocnombox.html | 83 +- templates/zh_CN/article.html | 77 +- templates/zh_CN/emptyarchive.html | 83 +- templates/zh_CN/headfoot.html | 70 +- templates/zh_CN/listinfo.html | 318 +- templates/zh_CN/options.html | 444 +- templates/zh_CN/private.html | 140 +- templates/zh_CN/roster.html | 158 +- templates/zh_CN/subscribe.html | 72 +- templates/zh_TW/admindbpreamble.html | 68 +- templates/zh_TW/admlogin.html | 104 +- templates/zh_TW/handle_opts.html | 74 +- templates/zh_TW/headfoot.html | 70 +- templates/zh_TW/listinfo.html | 309 +- templates/zh_TW/options.html | 273 +- templates/zh_TW/roster.html | 158 +- templates/zh_TW/subscribe.html | 72 +- tests/onebounce.py | 44 +- tests/test_handlers.py | 2566 ++- tests/test_lockfile.py | 138 +- tests/test_message.py | 6 +- 1139 files changed, 56769 insertions(+), 115434 deletions(-) delete mode 100644 Mailman/__init__.py mode change 100755 => 100644 bin/list_lists mode change 100755 => 100644 bin/mailmanctl diff --git a/Mailman/Archiver/Archiver.py b/Mailman/Archiver/Archiver.py index 2246093c..471b551e 100644 --- a/Mailman/Archiver/Archiver.py +++ b/Mailman/Archiver/Archiver.py @@ -29,7 +29,7 @@ import errno import traceback import re -from io import StringIO +import tempfile from Mailman import mm_cfg from Mailman import Mailbox @@ -88,17 +88,21 @@ def InitVars(self): # symbolic links. omask = os.umask(0) try: - # Create mbox directory with proper permissions - mbox_dir = self.archive_dir() + '.mbox' - os.makedirs(mbox_dir, mode=0o02775, exist_ok=True) - - # Create archive directory with proper permissions - archive_dir = self.archive_dir() - os.makedirs(archive_dir, mode=0o02775, exist_ok=True) - + try: + os.mkdir(self.archive_dir()+'.mbox', 0o02775) + except OSError as e: + if e.errno != errno.EEXIST: raise + # We also create an empty pipermail archive directory into + # which we'll drop an empty index.html file into. This is so + # that lists that have not yet received a posting have + # /something/ as their index.html, and don't just get a 404. + try: + os.mkdir(self.archive_dir(), 0o02775) + except OSError as e: + if e.errno != errno.EEXIST: raise # See if there's an index.html file there already and if not, # write in the empty archive notice. - indexfile = os.path.join(archive_dir, 'index.html') + indexfile = os.path.join(self.archive_dir(), 'index.html') fp = None try: fp = open(indexfile) @@ -132,7 +136,8 @@ def GetBaseArchiveURL(self): if self.archive_private: return url else: - hostname = re.match(r'[^:]*://([^/]*)/.*', url, re.IGNORECASE).group(1) + hostname = re.match('[^:]*://([^/]*)/.*', url).group(1)\ + or mm_cfg.DEFAULT_URL_HOST url = mm_cfg.PUBLIC_ARCHIVE_URL % { 'listname': self.internal_name(), 'hostname': hostname @@ -145,7 +150,7 @@ def __archive_file(self, afn): """Open (creating, if necessary) the named archive file.""" omask = os.umask(0o002) try: - return Mailbox.Mailbox(open(afn, 'a+')) + return Mailbox.Mailbox(open(afn, 'a+b')) finally: os.umask(omask) @@ -157,9 +162,11 @@ def __archive_to_mbox(self, post): """Retain a text copy of the message in an mbox file.""" try: afn = self.ArchiveFileName() + syslog('debug', 'Archiver: Writing to mbox file: %s', afn) mbox = self.__archive_file(afn) mbox.AppendMessage(post) - mbox.fp.close() + mbox.close() + syslog('debug', 'Archiver: Successfully wrote message to mbox file: %s', afn) except IOError as msg: syslog('error', 'Archive file access failure:\n\t%s %s', afn, msg) raise @@ -169,55 +176,68 @@ def ExternalArchive(self, ar, txt): 'hostname': self.host_name, }) cmd = ar % d - try: - with os.popen(cmd, 'w') as extarch: - extarch.write(txt) - except OSError as e: - syslog('error', 'Failed to execute external archiver: %s\nError: %s', - cmd, str(e)) - return + extarch = os.popen(cmd, 'w') + extarch.write(txt) status = extarch.close() if status: - syslog('error', 'External archiver non-zero exit status: %d\nCommand: %s', - (status & 0xff00) >> 8, cmd) + syslog('error', 'external archiver non-zero exit status: %d\n', + (status & 0xff00) >> 8) # # archiving in real time this is called from list.post(msg) # def ArchiveMail(self, msg): """Store postings in mbox and/or pipermail archive, depending.""" + from Mailman.Logging.Syslog import syslog + syslog('debug', 'Archiver: Starting ArchiveMail for list %s', self.internal_name()) + # Fork so archival errors won't disrupt normal list delivery if mm_cfg.ARCHIVE_TO_MBOX == -1: + syslog('debug', 'Archiver: ARCHIVE_TO_MBOX is -1, archiving disabled') return + + syslog('debug', 'Archiver: ARCHIVE_TO_MBOX = %s', mm_cfg.ARCHIVE_TO_MBOX) # # We don't need an extra archiver lock here because we know the list # itself must be locked. if mm_cfg.ARCHIVE_TO_MBOX in (1, 2): - try: - mbox = self.__archive_file(self.ArchiveFileName()) - mbox.AppendMessage(msg) - mbox.fp.close() - except IOError as msg: - syslog('error', 'Archive file access failure:\n\t%s %s', - self.ArchiveFileName(), msg) - raise + syslog('debug', 'Archiver: Writing to mbox archive') + self.__archive_to_mbox(msg) if mm_cfg.ARCHIVE_TO_MBOX == 1: # Archive to mbox only. + syslog('debug', 'Archiver: ARCHIVE_TO_MBOX = 1, mbox only, returning') return - txt = str(msg) + + txt = msg.as_string() + unixfrom = msg.get_unixfrom() + # Handle case where unixfrom is None (Python 3 compatibility) + if unixfrom and not txt.startswith(unixfrom): + txt = unixfrom + '\n' + txt + # should we use the internal or external archiver? private_p = self.archive_private + syslog('debug', 'Archiver: archive_private = %s', private_p) + if mm_cfg.PUBLIC_EXTERNAL_ARCHIVER and not private_p: + syslog('debug', 'Archiver: Using public external archiver') self.ExternalArchive(mm_cfg.PUBLIC_EXTERNAL_ARCHIVER, txt) elif mm_cfg.PRIVATE_EXTERNAL_ARCHIVER and private_p: + syslog('debug', 'Archiver: Using private external archiver') self.ExternalArchive(mm_cfg.PRIVATE_EXTERNAL_ARCHIVER, txt) else: # use the internal archiver - with StringIO(txt) as f: - from . import HyperArch - h = HyperArch.HyperArchive(self) - h.processUnixMailbox(f) - h.close() + syslog('debug', 'Archiver: Using internal HyperArch archiver') + f = tempfile.NamedTemporaryFile() + if isinstance(txt, str): + txt = txt.encode('utf-8') + f.write(txt) + f.flush() + from . import HyperArch + h = HyperArch.HyperArchive(self) + h.processUnixMailbox(f) + h.close() + f.close() + syslog('debug', 'Archiver: Completed internal archiving') # # called from MailList.MailList.Save() diff --git a/Mailman/Archiver/HyperArch.py b/Mailman/Archiver/HyperArch.py index 199d7915..f88432b6 100644 --- a/Mailman/Archiver/HyperArch.py +++ b/Mailman/Archiver/HyperArch.py @@ -37,14 +37,11 @@ from . import pipermail import weakref import binascii -from io import StringIO, BytesIO -import pickle from email.header import decode_header, make_header from email.errors import HeaderParseError from email.charset import Charset -from email import message_from_file -from email.generator import Generator +from functools import cmp_to_key from Mailman import mm_cfg from Mailman import Utils @@ -90,6 +87,7 @@ resource.setrlimit(resource.RLIMIT_STACK, (newsoft, hard)) + def html_quote(s, lang=None): repls = ( ('&', '&'), ("<", '<'), @@ -97,7 +95,7 @@ def html_quote(s, lang=None): ('"', '"')) for thing, repl in repls: s = s.replace(thing, repl) - return Utils.uncanonstr(s, lang) + return s def url_quote(s): @@ -139,7 +137,7 @@ def CGIescape(arg, lang=None): s = Utils.websafe(arg) else: s = Utils.websafe(str(arg)) - return Utils.uncanonstr(s.replace('"', '"'), lang) + return s.replace('"', '"') # Parenthesized human name paren_name_pat = re.compile(r'([(].*[)])') @@ -169,6 +167,7 @@ def CGIescape(arg, lang=None): quotedpat = re.compile(r'^([>|:]|>)+') + # Like Utils.maketext() but with caching to improve performance. # # _templatefilepathcache is used to associate a (templatefile, lang, listname) @@ -225,9 +224,10 @@ def quick_maketext(templatefile, dict=None, lang=None, mlist=None): syslog('error', 'broken template: %s\n%s', filepath, e) # Make sure the text is in the given character set, or html-ify any bogus # characters. - return Utils.uncanonstr(text, lang) + return text + # Note: I'm overriding most, if not all of the pipermail Article class # here -ddm # The Article class encapsulates a single posting. The attributes are: @@ -252,8 +252,8 @@ class Article(pipermail.Article): _last_article_time = time.time() - def __init__(self, message, sequence, keepHeaders=0, - lang=mm_cfg.DEFAULT_SERVER_LANGUAGE, mlist=None): + def __init__(self, message=None, sequence=0, keepHeaders=[], + lang=mm_cfg.DEFAULT_SERVER_LANGUAGE, mlist=None): self.__super_init(message, sequence, keepHeaders) self.prev = None self.next = None @@ -282,14 +282,15 @@ def __init__(self, message, sequence, keepHeaders=0, i18n.set_language(lang) if self.author == self.email: self.author = self.email = re.sub('@', _(' at '), - self.email, flags=re.IGNORECASE) + self.email) else: - self.email = re.sub('@', _(' at '), self.email, flags=re.IGNORECASE) + self.email = re.sub('@', _(' at '), self.email) finally: i18n.set_translation(otrans) - # Get content type and encoding - ctype = message.get_content_type() + # Snag the content-* headers. RFC 1521 states that their values are + # case insensitive. + ctype = message.get('Content-Type', 'text/plain') cenc = message.get('Content-Transfer-Encoding', '') self.ctype = ctype.lower() self.cenc = cenc.lower() @@ -298,8 +299,8 @@ def __init__(self, message, sequence, keepHeaders=0, cset_out = Charset(cset).output_charset or cset if isinstance(cset_out, str): # email 3.0.1 (python 2.4) doesn't like unicode - cset_out = cset_out.encode('us-ascii') - charset = message.get_content_charset() + cset_out = cset_out.encode('us-ascii', 'replace') + charset = message.get_content_charset(cset_out) if charset: charset = charset.lower().strip() if charset[0]=='"' and charset[-1]=='"': @@ -307,16 +308,21 @@ def __init__(self, message, sequence, keepHeaders=0, if charset[0]=="'" and charset[-1]=="'": charset = charset[1:-1] try: - body = message.get_body().get_content() - except (binascii.Error, AttributeError): + body = message.get_payload(decode=True) + except binascii.Error: body = None if body and charset != Utils.GetCharSet(self._lang): + if isinstance(charset, bytes): + charset = charset.decode('utf-8', 'replace') # decode body try: - body = str(body, charset) + body = body.decode(charset) except (UnicodeError, LookupError): body = None if body: + # Handle both bytes and strings properly + if isinstance(body, bytes): + body = body.decode('utf-8', 'replace') self.body = [l + "\n" for l in body.splitlines()] self.decode_headers() @@ -414,9 +420,9 @@ def decode_headers(self): otrans = i18n.get_translation() try: i18n.set_language(self._lang) - atmark = str(_(' at '), Utils.GetCharSet(self._lang)) + atmark = _(' at ') subject = re.sub(r'([-+,.\w]+)@([-+.\w]+)', - r'\g<1>' + atmark + r'\g<2>', subject, flags=re.IGNORECASE) + r'\g<1>' + atmark + r'\g<2>', subject) finally: i18n.set_translation(otrans) self.decoded['subject'] = subject @@ -429,31 +435,29 @@ def strip_subject(self, subject): if prefix: prefix_pat = re.escape(prefix) prefix_pat = '%'.join(prefix_pat.split(r'\%')) - prefix_pat = re.sub(r'%\d*d', r'\s*\d+\s*', prefix_pat, flags=re.IGNORECASE) - subject = re.sub(prefix_pat, '', subject, flags=re.IGNORECASE) + prefix_pat = re.sub(r'%\d*d', r'\\\\s*\\\\d+\\\\s*', prefix_pat) + subject = re.sub(prefix_pat, '', subject) subject = subject.lstrip() # MAS Should we strip FW and FWD too? strip_pat = re.compile(r'^((RE|AW|SV|VS)(\[\d+\])?:\s*)+', re.I) stripped = strip_pat.sub('', subject) # Also remove whitespace to avoid folding/unfolding differences - stripped = re.sub(r'\s', '', stripped, flags=re.IGNORECASE) + stripped = re.sub(r'\s', '', stripped) return stripped def decode_charset(self, field): # TK: This function was rewritten for unifying to Unicode. # Convert 'field' into Unicode one line string. try: - if isinstance(field, str): - return field pairs = decode_header(field) - ustr = str(make_header(pairs)) + ustr = make_header(pairs).__str__() except (LookupError, UnicodeError, ValueError, HeaderParseError): # assume list's language cset = Utils.GetCharSet(self._mlist.preferred_language) if cset == 'us-ascii': cset = 'iso-8859-1' # assume this for English list ustr = str(field, cset, 'replace') - return ''.join(ustr.splitlines()) + return u''.join(ustr.splitlines()) def as_html(self): d = self.__dict__.copy() @@ -474,7 +478,7 @@ def as_html(self): d["in_reply_to_url"] = url_quote(self._message_id) if mm_cfg.ARCHIVER_OBSCURES_EMAILADDRS: # Point the mailto url back to the list - author = re.sub('@', _(' at '), self.author, flags=re.IGNORECASE) + author = re.sub('@', _(' at '), self.author) emailurl = self._mlist.GetListEmail() else: author = self.author @@ -521,8 +525,8 @@ def _get_subject_enc(self, art): def _get_next(self): """Return the href and subject for the previous message""" - if self.__next__: - subject = self._get_subject_enc(self.__next__) + if hasattr( self, 'next' ) and self.next is not None: + subject = self._get_subject_enc(self.next) next = ('' % (url_quote(self.next.filename))) next_wsubj = ('
  • ' + _('Next message (by thread):') + @@ -537,23 +541,13 @@ def _get_next(self): _rx_softline = re.compile('=[ \t]*$') def _get_body(self): - """Return the message body as HTML.""" - if not self.body: - return '' - # Convert the body to HTML - body = [] - for line in self.body: - # Handle HTML content - if self.ctype == 'text/html': - body.append(line) - else: - # Convert plain text to HTML - line = self.quote(line) - if self.SHOWBR: - body.append(line + '
    \n') - else: - body.append(line + '\n') - return ''.join(body) + """Return the message body ready for HTML, decoded if necessary""" + try: + body = self.html_body + except AttributeError: + body = self.body + + return null_to_space(EMPTYSTRING.join(body)) def _add_decoded(self, d): """Add encoded-word keys to HTML output""" @@ -565,30 +559,48 @@ def _add_decoded(self, d): d[dst] = self.quote(self.decoded[src]) def as_text(self): - """Return the message as plain text.""" - if not self.body: - return '' - # Convert the body to plain text - body = [] - for line in self.body: - # Handle HTML content - if self.ctype == 'text/html': - # Strip HTML tags - line = re.sub(r'<[^>]*>', '', line) - body.append(line) - return ''.join(body) + d = self.__dict__.copy() + # We need to guarantee a valid From_ line, even if there are + # bososities in the headers. + if not d.get('fromdate', '').strip(): + d['fromdate'] = time.ctime(time.time()) + if not d.get('email', '').strip(): + d['email'] = 'bogus@does.not.exist.com' + if not d.get('datestr', '').strip(): + d['datestr'] = time.ctime(time.time()) + # + headers = ['From %(email)s %(fromdate)s', + 'From: %(email)s (%(author)s)', + 'Date: %(datestr)s', + 'Subject: %(subject)s'] + if d['_in_reply_to']: + headers.append('In-Reply-To: %(_in_reply_to)s') + if d['_references']: + headers.append('References: %(_references)s') + if d['_message_id']: + headers.append('Message-ID: %(_message_id)s') + body = EMPTYSTRING.join(self.body) + cset = Utils.GetCharSet(self._lang) + # Coerce the body to Unicode and replace any invalid characters. + if not isinstance(body, str): + body = str(body, cset, 'replace') + if mm_cfg.ARCHIVER_OBSCURES_EMAILADDRS: + otrans = i18n.get_translation() + try: + i18n.set_language(self._lang) + atmark = _(' at ') + if isinstance(atmark, bytes): + atmark = str(atmark, cset) + body = re.sub(r'([-+,.\w]+)@([-+.\w]+)', + r'\g<1>' + atmark + r'\g<2>', body) + finally: + i18n.set_translation(otrans) + + return NL.join(headers) % d + '\n\n' + body + '\n' def _set_date(self, message): - """Set the date from the message.""" - try: - date = message.get('Date') - if date: - self.date = time.mktime(email.utils.parsedate_tz(date)[:9]) - else: - self.date = time.time() - except (TypeError, ValueError): - self.date = time.time() - self.datestr = time.ctime(self.date) + self.__super_set_date(message) + self.fromdate = time.ctime(int(self.date)) def loadbody_fromHTML(self,fileobj): self.body = [] @@ -612,7 +624,7 @@ def finished_update_article(self): except AttributeError: pass - + class HyperArchive(pipermail.T): __super_init = pipermail.T.__init__ __super_update_archive = pipermail.T.update_archive @@ -641,49 +653,14 @@ def __init__(self, maillist): # with mailman's LockFile module for HyperDatabase.HyperDatabase # dir = maillist.archive_dir() - self.basedir = dir # Set basedir first - self.database = HyperDatabase.HyperDatabase(dir, maillist) - - # Initialize basic attributes first - self.archives = [] # Archives - self._dirty_archives = [] # Archives that will have to be updated - self.sequence = 0 # Sequence variable used for numbering articles - self.update_TOC = 0 # Does the TOC need updating? + db = HyperDatabase.HyperDatabase(dir, maillist) + self.__super_init(dir, reload=1, database=db) + self.maillist = maillist self._lock_file = None self.lang = maillist.preferred_language self.charset = Utils.GetCharSet(maillist.preferred_language) - # Try to load previously pickled state - try: - f = open(os.path.join(self.basedir, 'pipermail.pck'), 'rb') - self.message(C_('Reloading pickled archive state')) - try: - # Try UTF-8 first for newer files - d = pickle.load(f, fix_imports=True, encoding='utf-8') - except (UnicodeDecodeError, pickle.UnpicklingError): - # Fall back to latin1 for older files - f.seek(0) - d = pickle.load(f, fix_imports=True, encoding='latin1') - f.close() - - if isinstance(d, bytes): - # If we got bytes, try to unpickle it - d = pickle.loads(d, fix_imports=True, encoding='latin1') - - # Only update attributes that don't conflict with our initialization - safe_attrs = { - 'type', 'archive', 'firstdate', 'lastdate', 'archivedate', - 'size', 'version', 'subjectIndex', 'authorIndex', 'dateIndex', - 'articleIndex', 'threadIndex' - } - for key, value in list(d.items()): - if key in safe_attrs: - setattr(self, key, value) - except (IOError, EOFError, pickle.UnpicklingError, RecursionError) as e: - syslog('error', 'Error loading archive state: %s', e) - # Continue with default initialization - if hasattr(self.maillist,'archive_volume_frequency'): if self.maillist.archive_volume_frequency == 0: self.ARCHIVE_PERIOD='year' @@ -840,12 +817,12 @@ def html_TOC_entry(self, arch): if os.path.exists(gzfile): file = gzfile url = arch + '.txt.gz' - templ = '
  • [ ' + _('Gzip\'d Text%(sz)s') \ + templ = '[ ' + _('Gzip\'d Text%(sz)s') \ + '][ ' + _('Text%(sz)s') + '][ ' + _('Text%(sz)s') + ']
    %s
    %s
    %s
    %s
    %s
    %s
      + """
     
    ' + self.opts['aria-label'] + '
    - - - - - - - - - - -
    -التحقق من الشخصية لـ %(who)s للقائمة %(listname)s -
    كلمة سر %(who)s القائمة:
    -
    -

    هام: من هذا النقطة يجب أن تكون + + + + + + + + + + + +
    + التحقق من الشخصية لـ %(who)s للقائمة %(listname)s +
    كلمة سر %(who)s القائمة:
    +
    +

    هام: من هذا النقطة يجب أن تكون الكوكيز Ù…ÙØ¹Ù„Ø© ÙÙŠ برنامج الاستعراض الذي تستخدمه، وإلا ÙØ¥Ù†Ù‡ لن يتم حصول أي تغييرات إشراÙية. @@ -94,6 +34,6 @@ صلاحيتها عندما تغلق مستعرضك، أو يمكنك إنهاء صلاحية الكوكي بشكل صريح بالضغط على الارتباط تسجيل الخروج تحت أعمال إشراÙية أخرى (والتي ستشاهدها عندما تسجل الدخول بنجاح). -

    + diff --git a/templates/ar/archidxentry.html b/templates/ar/archidxentry.html index 2efd9486..f9bb57aa 100644 --- a/templates/ar/archidxentry.html +++ b/templates/ar/archidxentry.html @@ -1,67 +1,4 @@ -
  • %(subject)s -  -%(author)s - -
  • \ No newline at end of file +
  • %(subject)s +  +%(author)s + diff --git a/templates/ar/archidxfoot.html b/templates/ar/archidxfoot.html index 7a889266..d4fef19a 100644 --- a/templates/ar/archidxfoot.html +++ b/templates/ar/archidxfoot.html @@ -1,84 +1,21 @@ - -

    -تاريخ آخر رسالة -%(lastdate)s
    -تمت Ø£Ø±Ø´ÙØªÙ‡Ø§ ÙÙŠ: %(archivedate)s -

    -

      -
    • الرسائل مرتبة حسب: +
    +

    + تاريخ آخر رسالة + %(lastdate)s
    + تمت Ø£Ø±Ø´ÙØªÙ‡Ø§ ÙÙŠ: %(archivedate)s +

    +

    -

    -


    -تم توليد هذا الأرشي٠من قبل + +

    +


    + تم توليد هذا الأرشي٠من قبل Pipermail %(version)s. - - -

    \ No newline at end of file + + diff --git a/templates/ar/archidxhead.html b/templates/ar/archidxhead.html index 77d28452..8d268bfc 100644 --- a/templates/ar/archidxhead.html +++ b/templates/ar/archidxhead.html @@ -1,79 +1,16 @@ - - -أرشي٠%(archive)s الخاص بالقائمة %(listname)s حسب %(archtype)s - - + + أرشي٠%(archive)s الخاص بالقائمة %(listname)s حسب %(archtype)s + + %(encoding)s - - - -

    Ø£Ø±Ø´ÙŠÙØ§Øª %(archive)s حسب %(archtype)s

    -
      -
    • الرسائل مرتبة حسب: + + + +

      Ø£Ø±Ø´ÙŠÙØ§Øª %(archive)s حسب %(archtype)s

      + -

      بداية من: %(firstdate)s
      -انتهاءاً ÙÙŠ: %(lastdate)s
      -الرسائل: %(size)s

      -

        -

      \ No newline at end of file +
    +

    بداية من: %(firstdate)s
    + انتهاءاً ÙÙŠ: %(lastdate)s
    + الرسائل: %(size)s

    +